2019-05-27 09:55:01 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2013-11-30 11:54:32 +04:00
/*
* Driver for watchdog device controlled through GPIO - line
*
* Author : 2013 , Alexander Shiyan < shc_work @ mail . ru >
*/
# include <linux/err.h>
# include <linux/delay.h>
# include <linux/module.h>
2017-10-09 02:28:47 +03:00
# include <linux/gpio/consumer.h>
# include <linux/of.h>
2013-11-30 11:54:32 +04:00
# include <linux/platform_device.h>
# include <linux/watchdog.h>
2019-05-28 12:09:47 +03:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , 0 ) ;
MODULE_PARM_DESC ( nowayout ,
" Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2013-11-30 11:54:32 +04:00
# define SOFT_TIMEOUT_MIN 1
# define SOFT_TIMEOUT_DEF 60
enum {
HW_ALGO_TOGGLE ,
HW_ALGO_LEVEL ,
} ;
struct gpio_wdt_priv {
2017-10-09 02:28:47 +03:00
struct gpio_desc * gpiod ;
2013-11-30 11:54:32 +04:00
bool state ;
2015-01-14 09:28:29 +03: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 )
{
2017-10-09 02:28:47 +03:00
/* Eternal ping */
gpiod_set_value_cansleep ( priv - > gpiod , 1 ) ;
2013-11-30 11:54:32 +04:00
/* Put GPIO back to tristate */
if ( priv - > hw_algo = = HW_ALGO_TOGGLE )
2017-10-09 02:28:47 +03:00
gpiod_direction_input ( priv - > gpiod ) ;
2013-11-30 11:54:32 +04:00
}
2016-02-29 00:12:23 +03:00
static int gpio_wdt_ping ( struct watchdog_device * wdd )
2015-07-31 10:21:36 +03: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 ;
2017-10-09 02:28:47 +03:00
gpiod_set_value_cansleep ( priv - > gpiod , priv - > state ) ;
2015-07-31 10:21:36 +03:00
break ;
case HW_ALGO_LEVEL :
/* Pulse */
2017-10-09 02:28:47 +03:00
gpiod_set_value_cansleep ( priv - > gpiod , 1 ) ;
2015-07-31 10:21:36 +03:00
udelay ( 1 ) ;
2017-10-09 02:28:47 +03:00
gpiod_set_value_cansleep ( priv - > gpiod , 0 ) ;
2015-07-31 10:21:36 +03:00
break ;
}
2016-02-29 00:12:23 +03:00
return 0 ;
2015-01-14 09:28:29 +03:00
}
static int gpio_wdt_start ( struct watchdog_device * wdd )
{
struct gpio_wdt_priv * priv = watchdog_get_drvdata ( wdd ) ;
2017-10-09 02:28:47 +03:00
priv - > state = 0 ;
gpiod_direction_output ( priv - > gpiod , priv - > state ) ;
2013-11-30 11:54:32 +04:00
2016-02-29 00:12:23 +03: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 09:28:29 +03:00
if ( ! priv - > always_running ) {
gpio_wdt_disable ( priv ) ;
2017-11-09 16:39:55 +03:00
} else {
set_bit ( WDOG_HW_RUNNING , & wdd - > status ) ;
2015-01-14 09:28:29 +03: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 )
{
2017-10-09 02:28:46 +03:00
struct device * dev = & pdev - > dev ;
struct device_node * np = dev - > of_node ;
2013-11-30 11:54:32 +04:00
struct gpio_wdt_priv * priv ;
2017-10-09 02:28:47 +03:00
enum gpiod_flags gflags ;
2013-11-30 11:54:32 +04:00
unsigned int hw_margin ;
const char * algo ;
int ret ;
2017-10-09 02:28:46 +03:00
priv = devm_kzalloc ( dev , sizeof ( * priv ) , GFP_KERNEL ) ;
2013-11-30 11:54:32 +04:00
if ( ! priv )
return - ENOMEM ;
2016-07-26 17:50:14 +03:00
platform_set_drvdata ( pdev , priv ) ;
2017-10-09 02:28:46 +03:00
ret = of_property_read_string ( np , " hw_algo " , & algo ) ;
2013-11-30 11:54:32 +04:00
if ( ret )
return ret ;
2015-07-30 12:32:23 +03:00
if ( ! strcmp ( algo , " toggle " ) ) {
2013-11-30 11:54:32 +04:00
priv - > hw_algo = HW_ALGO_TOGGLE ;
2017-10-09 02:28:47 +03:00
gflags = GPIOD_IN ;
2015-07-30 12:32:23 +03:00
} else if ( ! strcmp ( algo , " level " ) ) {
2013-11-30 11:54:32 +04:00
priv - > hw_algo = HW_ALGO_LEVEL ;
2017-10-09 02:28:47 +03:00
gflags = GPIOD_OUT_LOW ;
2013-11-30 11:54:32 +04:00
} else {
return - EINVAL ;
}
2017-10-09 02:28:47 +03:00
priv - > gpiod = devm_gpiod_get ( dev , NULL , gflags ) ;
if ( IS_ERR ( priv - > gpiod ) )
return PTR_ERR ( priv - > gpiod ) ;
2013-11-30 11:54:32 +04:00
2017-10-09 02:28:46 +03:00
ret = of_property_read_u32 ( np ,
2013-11-30 11:54:32 +04:00
" 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 ;
2017-10-09 02:28:46 +03:00
priv - > always_running = of_property_read_bool ( np ,
2015-01-14 09:28:29 +03:00
" 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-29 00:12:23 +03:00
priv - > wdd . max_hw_heartbeat_ms = hw_margin ;
2017-10-09 02:28:46 +03:00
priv - > wdd . parent = dev ;
2018-02-10 23:36:22 +03:00
priv - > wdd . timeout = SOFT_TIMEOUT_DEF ;
2013-11-30 11:54:32 +04:00
2019-04-08 22:38:40 +03:00
watchdog_init_timeout ( & priv - > wdd , 0 , dev ) ;
2019-05-28 12:09:47 +03:00
watchdog_set_nowayout ( & priv - > wdd , nowayout ) ;
2013-11-30 11:54:32 +04:00
2015-11-21 00:54:54 +03:00
watchdog_stop_on_reboot ( & priv - > wdd ) ;
2015-01-14 09:28:29 +03:00
if ( priv - > always_running )
2016-02-29 00:12:23 +03:00
gpio_wdt_start ( & priv - > wdd ) ;
2015-01-14 09:28:29 +03:00
2019-04-08 22:38:40 +03:00
return devm_watchdog_register_device ( dev , & priv - > wdd ) ;
2013-11-30 11:54:32 +04:00
}
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 ,
} ;
2015-06-09 19:55:03 +03: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 19:55:03 +03: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 " ) ;