1*25134eafSAlexander Shiyan /* 2*25134eafSAlexander Shiyan * Driver for watchdog device controlled through GPIO-line 3*25134eafSAlexander Shiyan * 4*25134eafSAlexander Shiyan * Author: 2013, Alexander Shiyan <shc_work@mail.ru> 5*25134eafSAlexander Shiyan * 6*25134eafSAlexander Shiyan * This program is free software; you can redistribute it and/or modify 7*25134eafSAlexander Shiyan * it under the terms of the GNU General Public License as published by 8*25134eafSAlexander Shiyan * the Free Software Foundation; either version 2 of the License, or 9*25134eafSAlexander Shiyan * (at your option) any later version. 10*25134eafSAlexander Shiyan */ 11*25134eafSAlexander Shiyan 12*25134eafSAlexander Shiyan #include <linux/err.h> 13*25134eafSAlexander Shiyan #include <linux/delay.h> 14*25134eafSAlexander Shiyan #include <linux/module.h> 15*25134eafSAlexander Shiyan #include <linux/notifier.h> 16*25134eafSAlexander Shiyan #include <linux/of_gpio.h> 17*25134eafSAlexander Shiyan #include <linux/platform_device.h> 18*25134eafSAlexander Shiyan #include <linux/reboot.h> 19*25134eafSAlexander Shiyan #include <linux/watchdog.h> 20*25134eafSAlexander Shiyan 21*25134eafSAlexander Shiyan #define SOFT_TIMEOUT_MIN 1 22*25134eafSAlexander Shiyan #define SOFT_TIMEOUT_DEF 60 23*25134eafSAlexander Shiyan #define SOFT_TIMEOUT_MAX 0xffff 24*25134eafSAlexander Shiyan 25*25134eafSAlexander Shiyan enum { 26*25134eafSAlexander Shiyan HW_ALGO_TOGGLE, 27*25134eafSAlexander Shiyan HW_ALGO_LEVEL, 28*25134eafSAlexander Shiyan }; 29*25134eafSAlexander Shiyan 30*25134eafSAlexander Shiyan struct gpio_wdt_priv { 31*25134eafSAlexander Shiyan int gpio; 32*25134eafSAlexander Shiyan bool active_low; 33*25134eafSAlexander Shiyan bool state; 34*25134eafSAlexander Shiyan unsigned int hw_algo; 35*25134eafSAlexander Shiyan unsigned int hw_margin; 36*25134eafSAlexander Shiyan unsigned long last_jiffies; 37*25134eafSAlexander Shiyan struct notifier_block notifier; 38*25134eafSAlexander Shiyan struct timer_list timer; 39*25134eafSAlexander Shiyan struct watchdog_device wdd; 40*25134eafSAlexander Shiyan }; 41*25134eafSAlexander Shiyan 42*25134eafSAlexander Shiyan static void gpio_wdt_disable(struct gpio_wdt_priv *priv) 43*25134eafSAlexander Shiyan { 44*25134eafSAlexander Shiyan gpio_set_value_cansleep(priv->gpio, !priv->active_low); 45*25134eafSAlexander Shiyan 46*25134eafSAlexander Shiyan /* Put GPIO back to tristate */ 47*25134eafSAlexander Shiyan if (priv->hw_algo == HW_ALGO_TOGGLE) 48*25134eafSAlexander Shiyan gpio_direction_input(priv->gpio); 49*25134eafSAlexander Shiyan } 50*25134eafSAlexander Shiyan 51*25134eafSAlexander Shiyan static int gpio_wdt_start(struct watchdog_device *wdd) 52*25134eafSAlexander Shiyan { 53*25134eafSAlexander Shiyan struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd); 54*25134eafSAlexander Shiyan 55*25134eafSAlexander Shiyan priv->state = priv->active_low; 56*25134eafSAlexander Shiyan gpio_direction_output(priv->gpio, priv->state); 57*25134eafSAlexander Shiyan priv->last_jiffies = jiffies; 58*25134eafSAlexander Shiyan mod_timer(&priv->timer, priv->last_jiffies + priv->hw_margin); 59*25134eafSAlexander Shiyan 60*25134eafSAlexander Shiyan return 0; 61*25134eafSAlexander Shiyan } 62*25134eafSAlexander Shiyan 63*25134eafSAlexander Shiyan static int gpio_wdt_stop(struct watchdog_device *wdd) 64*25134eafSAlexander Shiyan { 65*25134eafSAlexander Shiyan struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd); 66*25134eafSAlexander Shiyan 67*25134eafSAlexander Shiyan mod_timer(&priv->timer, 0); 68*25134eafSAlexander Shiyan gpio_wdt_disable(priv); 69*25134eafSAlexander Shiyan 70*25134eafSAlexander Shiyan return 0; 71*25134eafSAlexander Shiyan } 72*25134eafSAlexander Shiyan 73*25134eafSAlexander Shiyan static int gpio_wdt_ping(struct watchdog_device *wdd) 74*25134eafSAlexander Shiyan { 75*25134eafSAlexander Shiyan struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd); 76*25134eafSAlexander Shiyan 77*25134eafSAlexander Shiyan priv->last_jiffies = jiffies; 78*25134eafSAlexander Shiyan 79*25134eafSAlexander Shiyan return 0; 80*25134eafSAlexander Shiyan } 81*25134eafSAlexander Shiyan 82*25134eafSAlexander Shiyan static int gpio_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t) 83*25134eafSAlexander Shiyan { 84*25134eafSAlexander Shiyan wdd->timeout = t; 85*25134eafSAlexander Shiyan 86*25134eafSAlexander Shiyan return gpio_wdt_ping(wdd); 87*25134eafSAlexander Shiyan } 88*25134eafSAlexander Shiyan 89*25134eafSAlexander Shiyan static void gpio_wdt_hwping(unsigned long data) 90*25134eafSAlexander Shiyan { 91*25134eafSAlexander Shiyan struct watchdog_device *wdd = (struct watchdog_device *)data; 92*25134eafSAlexander Shiyan struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd); 93*25134eafSAlexander Shiyan 94*25134eafSAlexander Shiyan if (time_after(jiffies, priv->last_jiffies + 95*25134eafSAlexander Shiyan msecs_to_jiffies(wdd->timeout * 1000))) { 96*25134eafSAlexander Shiyan dev_crit(wdd->dev, "Timer expired. System will reboot soon!\n"); 97*25134eafSAlexander Shiyan return; 98*25134eafSAlexander Shiyan } 99*25134eafSAlexander Shiyan 100*25134eafSAlexander Shiyan /* Restart timer */ 101*25134eafSAlexander Shiyan mod_timer(&priv->timer, jiffies + priv->hw_margin); 102*25134eafSAlexander Shiyan 103*25134eafSAlexander Shiyan switch (priv->hw_algo) { 104*25134eafSAlexander Shiyan case HW_ALGO_TOGGLE: 105*25134eafSAlexander Shiyan /* Toggle output pin */ 106*25134eafSAlexander Shiyan priv->state = !priv->state; 107*25134eafSAlexander Shiyan gpio_set_value_cansleep(priv->gpio, priv->state); 108*25134eafSAlexander Shiyan break; 109*25134eafSAlexander Shiyan case HW_ALGO_LEVEL: 110*25134eafSAlexander Shiyan /* Pulse */ 111*25134eafSAlexander Shiyan gpio_set_value_cansleep(priv->gpio, !priv->active_low); 112*25134eafSAlexander Shiyan udelay(1); 113*25134eafSAlexander Shiyan gpio_set_value_cansleep(priv->gpio, priv->active_low); 114*25134eafSAlexander Shiyan break; 115*25134eafSAlexander Shiyan } 116*25134eafSAlexander Shiyan } 117*25134eafSAlexander Shiyan 118*25134eafSAlexander Shiyan static int gpio_wdt_notify_sys(struct notifier_block *nb, unsigned long code, 119*25134eafSAlexander Shiyan void *unused) 120*25134eafSAlexander Shiyan { 121*25134eafSAlexander Shiyan struct gpio_wdt_priv *priv = container_of(nb, struct gpio_wdt_priv, 122*25134eafSAlexander Shiyan notifier); 123*25134eafSAlexander Shiyan 124*25134eafSAlexander Shiyan mod_timer(&priv->timer, 0); 125*25134eafSAlexander Shiyan 126*25134eafSAlexander Shiyan switch (code) { 127*25134eafSAlexander Shiyan case SYS_HALT: 128*25134eafSAlexander Shiyan case SYS_POWER_OFF: 129*25134eafSAlexander Shiyan gpio_wdt_disable(priv); 130*25134eafSAlexander Shiyan break; 131*25134eafSAlexander Shiyan default: 132*25134eafSAlexander Shiyan break; 133*25134eafSAlexander Shiyan } 134*25134eafSAlexander Shiyan 135*25134eafSAlexander Shiyan return NOTIFY_DONE; 136*25134eafSAlexander Shiyan } 137*25134eafSAlexander Shiyan 138*25134eafSAlexander Shiyan static const struct watchdog_info gpio_wdt_ident = { 139*25134eafSAlexander Shiyan .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | 140*25134eafSAlexander Shiyan WDIOF_SETTIMEOUT, 141*25134eafSAlexander Shiyan .identity = "GPIO Watchdog", 142*25134eafSAlexander Shiyan }; 143*25134eafSAlexander Shiyan 144*25134eafSAlexander Shiyan static const struct watchdog_ops gpio_wdt_ops = { 145*25134eafSAlexander Shiyan .owner = THIS_MODULE, 146*25134eafSAlexander Shiyan .start = gpio_wdt_start, 147*25134eafSAlexander Shiyan .stop = gpio_wdt_stop, 148*25134eafSAlexander Shiyan .ping = gpio_wdt_ping, 149*25134eafSAlexander Shiyan .set_timeout = gpio_wdt_set_timeout, 150*25134eafSAlexander Shiyan }; 151*25134eafSAlexander Shiyan 152*25134eafSAlexander Shiyan static int gpio_wdt_probe(struct platform_device *pdev) 153*25134eafSAlexander Shiyan { 154*25134eafSAlexander Shiyan struct gpio_wdt_priv *priv; 155*25134eafSAlexander Shiyan enum of_gpio_flags flags; 156*25134eafSAlexander Shiyan unsigned int hw_margin; 157*25134eafSAlexander Shiyan unsigned long f = 0; 158*25134eafSAlexander Shiyan const char *algo; 159*25134eafSAlexander Shiyan int ret; 160*25134eafSAlexander Shiyan 161*25134eafSAlexander Shiyan priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 162*25134eafSAlexander Shiyan if (!priv) 163*25134eafSAlexander Shiyan return -ENOMEM; 164*25134eafSAlexander Shiyan 165*25134eafSAlexander Shiyan priv->gpio = of_get_gpio_flags(pdev->dev.of_node, 0, &flags); 166*25134eafSAlexander Shiyan if (!gpio_is_valid(priv->gpio)) 167*25134eafSAlexander Shiyan return priv->gpio; 168*25134eafSAlexander Shiyan 169*25134eafSAlexander Shiyan priv->active_low = flags & OF_GPIO_ACTIVE_LOW; 170*25134eafSAlexander Shiyan 171*25134eafSAlexander Shiyan ret = of_property_read_string(pdev->dev.of_node, "hw_algo", &algo); 172*25134eafSAlexander Shiyan if (ret) 173*25134eafSAlexander Shiyan return ret; 174*25134eafSAlexander Shiyan if (!strncmp(algo, "toggle", 6)) { 175*25134eafSAlexander Shiyan priv->hw_algo = HW_ALGO_TOGGLE; 176*25134eafSAlexander Shiyan f = GPIOF_IN; 177*25134eafSAlexander Shiyan } else if (!strncmp(algo, "level", 5)) { 178*25134eafSAlexander Shiyan priv->hw_algo = HW_ALGO_LEVEL; 179*25134eafSAlexander Shiyan f = priv->active_low ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW; 180*25134eafSAlexander Shiyan } else { 181*25134eafSAlexander Shiyan return -EINVAL; 182*25134eafSAlexander Shiyan } 183*25134eafSAlexander Shiyan 184*25134eafSAlexander Shiyan ret = devm_gpio_request_one(&pdev->dev, priv->gpio, f, 185*25134eafSAlexander Shiyan dev_name(&pdev->dev)); 186*25134eafSAlexander Shiyan if (ret) 187*25134eafSAlexander Shiyan return ret; 188*25134eafSAlexander Shiyan 189*25134eafSAlexander Shiyan ret = of_property_read_u32(pdev->dev.of_node, 190*25134eafSAlexander Shiyan "hw_margin_ms", &hw_margin); 191*25134eafSAlexander Shiyan if (ret) 192*25134eafSAlexander Shiyan return ret; 193*25134eafSAlexander Shiyan /* Disallow values lower than 2 and higher than 65535 ms */ 194*25134eafSAlexander Shiyan if (hw_margin < 2 || hw_margin > 65535) 195*25134eafSAlexander Shiyan return -EINVAL; 196*25134eafSAlexander Shiyan 197*25134eafSAlexander Shiyan /* Use safe value (1/2 of real timeout) */ 198*25134eafSAlexander Shiyan priv->hw_margin = msecs_to_jiffies(hw_margin / 2); 199*25134eafSAlexander Shiyan 200*25134eafSAlexander Shiyan watchdog_set_drvdata(&priv->wdd, priv); 201*25134eafSAlexander Shiyan 202*25134eafSAlexander Shiyan priv->wdd.info = &gpio_wdt_ident; 203*25134eafSAlexander Shiyan priv->wdd.ops = &gpio_wdt_ops; 204*25134eafSAlexander Shiyan priv->wdd.min_timeout = SOFT_TIMEOUT_MIN; 205*25134eafSAlexander Shiyan priv->wdd.max_timeout = SOFT_TIMEOUT_MAX; 206*25134eafSAlexander Shiyan 207*25134eafSAlexander Shiyan if (watchdog_init_timeout(&priv->wdd, 0, &pdev->dev) < 0) 208*25134eafSAlexander Shiyan priv->wdd.timeout = SOFT_TIMEOUT_DEF; 209*25134eafSAlexander Shiyan 210*25134eafSAlexander Shiyan setup_timer(&priv->timer, gpio_wdt_hwping, (unsigned long)&priv->wdd); 211*25134eafSAlexander Shiyan 212*25134eafSAlexander Shiyan ret = watchdog_register_device(&priv->wdd); 213*25134eafSAlexander Shiyan if (ret) 214*25134eafSAlexander Shiyan return ret; 215*25134eafSAlexander Shiyan 216*25134eafSAlexander Shiyan priv->notifier.notifier_call = gpio_wdt_notify_sys; 217*25134eafSAlexander Shiyan ret = register_reboot_notifier(&priv->notifier); 218*25134eafSAlexander Shiyan if (ret) 219*25134eafSAlexander Shiyan watchdog_unregister_device(&priv->wdd); 220*25134eafSAlexander Shiyan 221*25134eafSAlexander Shiyan return ret; 222*25134eafSAlexander Shiyan } 223*25134eafSAlexander Shiyan 224*25134eafSAlexander Shiyan static int gpio_wdt_remove(struct platform_device *pdev) 225*25134eafSAlexander Shiyan { 226*25134eafSAlexander Shiyan struct gpio_wdt_priv *priv = platform_get_drvdata(pdev); 227*25134eafSAlexander Shiyan 228*25134eafSAlexander Shiyan del_timer_sync(&priv->timer); 229*25134eafSAlexander Shiyan unregister_reboot_notifier(&priv->notifier); 230*25134eafSAlexander Shiyan watchdog_unregister_device(&priv->wdd); 231*25134eafSAlexander Shiyan 232*25134eafSAlexander Shiyan return 0; 233*25134eafSAlexander Shiyan } 234*25134eafSAlexander Shiyan 235*25134eafSAlexander Shiyan static const struct of_device_id gpio_wdt_dt_ids[] = { 236*25134eafSAlexander Shiyan { .compatible = "linux,wdt-gpio", }, 237*25134eafSAlexander Shiyan { } 238*25134eafSAlexander Shiyan }; 239*25134eafSAlexander Shiyan MODULE_DEVICE_TABLE(of, gpio_wdt_dt_ids); 240*25134eafSAlexander Shiyan 241*25134eafSAlexander Shiyan static struct platform_driver gpio_wdt_driver = { 242*25134eafSAlexander Shiyan .driver = { 243*25134eafSAlexander Shiyan .name = "gpio-wdt", 244*25134eafSAlexander Shiyan .owner = THIS_MODULE, 245*25134eafSAlexander Shiyan .of_match_table = gpio_wdt_dt_ids, 246*25134eafSAlexander Shiyan }, 247*25134eafSAlexander Shiyan .probe = gpio_wdt_probe, 248*25134eafSAlexander Shiyan .remove = gpio_wdt_remove, 249*25134eafSAlexander Shiyan }; 250*25134eafSAlexander Shiyan module_platform_driver(gpio_wdt_driver); 251*25134eafSAlexander Shiyan 252*25134eafSAlexander Shiyan MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>"); 253*25134eafSAlexander Shiyan MODULE_DESCRIPTION("GPIO Watchdog"); 254*25134eafSAlexander Shiyan MODULE_LICENSE("GPL"); 255