2013-11-30 11:54:32 +04:00
/*
* Driver for watchdog device controlled through GPIO - line
*
* Author : 2013 , Alexander Shiyan < shc_work @ mail . ru >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*/
# include <linux/err.h>
# include <linux/delay.h>
# include <linux/module.h>
# include <linux/of_gpio.h>
# include <linux/platform_device.h>
# include <linux/watchdog.h>
# define SOFT_TIMEOUT_MIN 1
# define SOFT_TIMEOUT_DEF 60
enum {
HW_ALGO_TOGGLE ,
HW_ALGO_LEVEL ,
} ;
struct gpio_wdt_priv {
int gpio ;
bool active_low ;
bool state ;
2015-01-14 07:28:29 +01:00
bool always_running ;
2013-11-30 11:54:32 +04:00
unsigned int hw_algo ;
struct watchdog_device wdd ;
} ;
static void gpio_wdt_disable ( struct gpio_wdt_priv * priv )
{
gpio_set_value_cansleep ( priv - > gpio , ! priv - > active_low ) ;
/* Put GPIO back to tristate */
if ( priv - > hw_algo = = HW_ALGO_TOGGLE )
gpio_direction_input ( priv - > gpio ) ;
}
2016-02-28 13:12:23 -08:00
static int gpio_wdt_ping ( struct watchdog_device * wdd )
2015-07-31 09:21:36 +02:00
{
struct gpio_wdt_priv * priv = watchdog_get_drvdata ( wdd ) ;
switch ( priv - > hw_algo ) {
case HW_ALGO_TOGGLE :
/* Toggle output pin */
priv - > state = ! priv - > state ;
gpio_set_value_cansleep ( priv - > gpio , priv - > state ) ;
break ;
case HW_ALGO_LEVEL :
/* Pulse */
gpio_set_value_cansleep ( priv - > gpio , ! priv - > active_low ) ;
udelay ( 1 ) ;
gpio_set_value_cansleep ( priv - > gpio , priv - > active_low ) ;
break ;
}
2016-02-28 13:12:23 -08:00
return 0 ;
2015-01-14 07:28:29 +01:00
}
static int gpio_wdt_start ( struct watchdog_device * wdd )
{
struct gpio_wdt_priv * priv = watchdog_get_drvdata ( wdd ) ;
2016-02-28 13:12:23 -08:00
priv - > state = priv - > active_low ;
gpio_direction_output ( priv - > gpio , priv - > state ) ;
2013-11-30 11:54:32 +04:00
2016-02-28 13:12:23 -08:00
set_bit ( WDOG_HW_RUNNING , & wdd - > status ) ;
return gpio_wdt_ping ( wdd ) ;
2013-11-30 11:54:32 +04:00
}
static int gpio_wdt_stop ( struct watchdog_device * wdd )
{
struct gpio_wdt_priv * priv = watchdog_get_drvdata ( wdd ) ;
2015-01-14 07:28:29 +01:00
if ( ! priv - > always_running ) {
gpio_wdt_disable ( priv ) ;
2016-02-28 13:12:23 -08:00
clear_bit ( WDOG_HW_RUNNING , & wdd - > status ) ;
2015-01-14 07:28:29 +01:00
}
2013-11-30 11:54:32 +04:00
return 0 ;
}
static const struct watchdog_info gpio_wdt_ident = {
. options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING |
WDIOF_SETTIMEOUT ,
. identity = " GPIO Watchdog " ,
} ;
static const struct watchdog_ops gpio_wdt_ops = {
. owner = THIS_MODULE ,
. start = gpio_wdt_start ,
. stop = gpio_wdt_stop ,
. ping = gpio_wdt_ping ,
} ;
static int gpio_wdt_probe ( struct platform_device * pdev )
{
struct gpio_wdt_priv * priv ;
enum of_gpio_flags flags ;
unsigned int hw_margin ;
unsigned long f = 0 ;
const char * algo ;
int ret ;
priv = devm_kzalloc ( & pdev - > dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
2016-07-26 14:50:14 +00:00
platform_set_drvdata ( pdev , priv ) ;
2013-11-30 11:54:32 +04:00
priv - > gpio = of_get_gpio_flags ( pdev - > dev . of_node , 0 , & flags ) ;
if ( ! gpio_is_valid ( priv - > gpio ) )
return priv - > gpio ;
priv - > active_low = flags & OF_GPIO_ACTIVE_LOW ;
ret = of_property_read_string ( pdev - > dev . of_node , " hw_algo " , & algo ) ;
if ( ret )
return ret ;
2015-07-30 11:32:23 +02:00
if ( ! strcmp ( algo , " toggle " ) ) {
2013-11-30 11:54:32 +04:00
priv - > hw_algo = HW_ALGO_TOGGLE ;
f = GPIOF_IN ;
2015-07-30 11:32:23 +02:00
} else if ( ! strcmp ( algo , " level " ) ) {
2013-11-30 11:54:32 +04:00
priv - > hw_algo = HW_ALGO_LEVEL ;
f = priv - > active_low ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW ;
} else {
return - EINVAL ;
}
ret = devm_gpio_request_one ( & pdev - > dev , priv - > gpio , f ,
dev_name ( & pdev - > dev ) ) ;
if ( ret )
return ret ;
ret = of_property_read_u32 ( pdev - > dev . of_node ,
" hw_margin_ms " , & hw_margin ) ;
if ( ret )
return ret ;
/* Disallow values lower than 2 and higher than 65535 ms */
if ( hw_margin < 2 | | hw_margin > 65535 )
return - EINVAL ;
2015-01-14 07:28:29 +01:00
priv - > always_running = of_property_read_bool ( pdev - > dev . of_node ,
" always-running " ) ;
2013-11-30 11:54:32 +04:00
watchdog_set_drvdata ( & priv - > wdd , priv ) ;
priv - > wdd . info = & gpio_wdt_ident ;
priv - > wdd . ops = & gpio_wdt_ops ;
priv - > wdd . min_timeout = SOFT_TIMEOUT_MIN ;
2016-02-28 13:12:23 -08:00
priv - > wdd . max_hw_heartbeat_ms = hw_margin ;
2015-08-20 14:05:01 +05:30
priv - > wdd . parent = & pdev - > dev ;
2013-11-30 11:54:32 +04:00
if ( watchdog_init_timeout ( & priv - > wdd , 0 , & pdev - > dev ) < 0 )
priv - > wdd . timeout = SOFT_TIMEOUT_DEF ;
2015-11-20 16:54:54 -05:00
watchdog_stop_on_reboot ( & priv - > wdd ) ;
2015-01-14 07:28:29 +01:00
if ( priv - > always_running )
2016-02-28 13:12:23 -08:00
gpio_wdt_start ( & priv - > wdd ) ;
2015-01-14 07:28:29 +01:00
2016-02-28 13:12:23 -08:00
ret = watchdog_register_device ( & priv - > wdd ) ;
return ret ;
2013-11-30 11:54:32 +04:00
}
static int gpio_wdt_remove ( struct platform_device * pdev )
{
struct gpio_wdt_priv * priv = platform_get_drvdata ( pdev ) ;
watchdog_unregister_device ( & priv - > wdd ) ;
return 0 ;
}
static const struct of_device_id gpio_wdt_dt_ids [ ] = {
{ . compatible = " linux,wdt-gpio " , } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , gpio_wdt_dt_ids ) ;
static struct platform_driver gpio_wdt_driver = {
. driver = {
. name = " gpio-wdt " ,
. of_match_table = gpio_wdt_dt_ids ,
} ,
. probe = gpio_wdt_probe ,
. remove = gpio_wdt_remove ,
} ;
2015-06-09 09:55:03 -07:00
# ifdef CONFIG_GPIO_WATCHDOG_ARCH_INITCALL
static int __init gpio_wdt_init ( void )
{
return platform_driver_register ( & gpio_wdt_driver ) ;
}
arch_initcall ( gpio_wdt_init ) ;
# else
2013-11-30 11:54:32 +04:00
module_platform_driver ( gpio_wdt_driver ) ;
2015-06-09 09:55:03 -07:00
# endif
2013-11-30 11:54:32 +04:00
MODULE_AUTHOR ( " Alexander Shiyan <shc_work@mail.ru> " ) ;
MODULE_DESCRIPTION ( " GPIO Watchdog " ) ;
MODULE_LICENSE ( " GPL " ) ;