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