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