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 platform_set_drvdata(pdev, priv); 155 156 priv->gpio = of_get_gpio_flags(pdev->dev.of_node, 0, &flags); 157 if (!gpio_is_valid(priv->gpio)) 158 return priv->gpio; 159 160 priv->active_low = flags & OF_GPIO_ACTIVE_LOW; 161 162 ret = of_property_read_string(pdev->dev.of_node, "hw_algo", &algo); 163 if (ret) 164 return ret; 165 if (!strcmp(algo, "toggle")) { 166 priv->hw_algo = HW_ALGO_TOGGLE; 167 f = GPIOF_IN; 168 } else if (!strcmp(algo, "level")) { 169 priv->hw_algo = HW_ALGO_LEVEL; 170 f = priv->active_low ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW; 171 } else { 172 return -EINVAL; 173 } 174 175 ret = devm_gpio_request_one(&pdev->dev, priv->gpio, f, 176 dev_name(&pdev->dev)); 177 if (ret) 178 return ret; 179 180 ret = of_property_read_u32(pdev->dev.of_node, 181 "hw_margin_ms", &hw_margin); 182 if (ret) 183 return ret; 184 /* Disallow values lower than 2 and higher than 65535 ms */ 185 if (hw_margin < 2 || hw_margin > 65535) 186 return -EINVAL; 187 188 /* Use safe value (1/2 of real timeout) */ 189 priv->hw_margin = msecs_to_jiffies(hw_margin / 2); 190 191 priv->always_running = of_property_read_bool(pdev->dev.of_node, 192 "always-running"); 193 194 watchdog_set_drvdata(&priv->wdd, priv); 195 196 priv->wdd.info = &gpio_wdt_ident; 197 priv->wdd.ops = &gpio_wdt_ops; 198 priv->wdd.min_timeout = SOFT_TIMEOUT_MIN; 199 priv->wdd.max_timeout = SOFT_TIMEOUT_MAX; 200 priv->wdd.parent = &pdev->dev; 201 202 if (watchdog_init_timeout(&priv->wdd, 0, &pdev->dev) < 0) 203 priv->wdd.timeout = SOFT_TIMEOUT_DEF; 204 205 setup_timer(&priv->timer, gpio_wdt_hwping, (unsigned long)&priv->wdd); 206 207 watchdog_stop_on_reboot(&priv->wdd); 208 209 ret = watchdog_register_device(&priv->wdd); 210 if (ret) 211 return ret; 212 213 if (priv->always_running) 214 gpio_wdt_start_impl(priv); 215 216 return 0; 217 } 218 219 static int gpio_wdt_remove(struct platform_device *pdev) 220 { 221 struct gpio_wdt_priv *priv = platform_get_drvdata(pdev); 222 223 del_timer_sync(&priv->timer); 224 watchdog_unregister_device(&priv->wdd); 225 226 return 0; 227 } 228 229 static const struct of_device_id gpio_wdt_dt_ids[] = { 230 { .compatible = "linux,wdt-gpio", }, 231 { } 232 }; 233 MODULE_DEVICE_TABLE(of, gpio_wdt_dt_ids); 234 235 static struct platform_driver gpio_wdt_driver = { 236 .driver = { 237 .name = "gpio-wdt", 238 .of_match_table = gpio_wdt_dt_ids, 239 }, 240 .probe = gpio_wdt_probe, 241 .remove = gpio_wdt_remove, 242 }; 243 244 #ifdef CONFIG_GPIO_WATCHDOG_ARCH_INITCALL 245 static int __init gpio_wdt_init(void) 246 { 247 return platform_driver_register(&gpio_wdt_driver); 248 } 249 arch_initcall(gpio_wdt_init); 250 #else 251 module_platform_driver(gpio_wdt_driver); 252 #endif 253 254 MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>"); 255 MODULE_DESCRIPTION("GPIO Watchdog"); 256 MODULE_LICENSE("GPL"); 257