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 22 enum { 23 HW_ALGO_TOGGLE, 24 HW_ALGO_LEVEL, 25 }; 26 27 struct gpio_wdt_priv { 28 int gpio; 29 bool active_low; 30 bool state; 31 bool always_running; 32 unsigned int hw_algo; 33 struct watchdog_device wdd; 34 }; 35 36 static void gpio_wdt_disable(struct gpio_wdt_priv *priv) 37 { 38 gpio_set_value_cansleep(priv->gpio, !priv->active_low); 39 40 /* Put GPIO back to tristate */ 41 if (priv->hw_algo == HW_ALGO_TOGGLE) 42 gpio_direction_input(priv->gpio); 43 } 44 45 static int gpio_wdt_ping(struct watchdog_device *wdd) 46 { 47 struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd); 48 49 switch (priv->hw_algo) { 50 case HW_ALGO_TOGGLE: 51 /* Toggle output pin */ 52 priv->state = !priv->state; 53 gpio_set_value_cansleep(priv->gpio, priv->state); 54 break; 55 case HW_ALGO_LEVEL: 56 /* Pulse */ 57 gpio_set_value_cansleep(priv->gpio, !priv->active_low); 58 udelay(1); 59 gpio_set_value_cansleep(priv->gpio, priv->active_low); 60 break; 61 } 62 return 0; 63 } 64 65 static int gpio_wdt_start(struct watchdog_device *wdd) 66 { 67 struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd); 68 69 priv->state = priv->active_low; 70 gpio_direction_output(priv->gpio, priv->state); 71 72 set_bit(WDOG_HW_RUNNING, &wdd->status); 73 74 return gpio_wdt_ping(wdd); 75 } 76 77 static int gpio_wdt_stop(struct watchdog_device *wdd) 78 { 79 struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd); 80 81 if (!priv->always_running) { 82 gpio_wdt_disable(priv); 83 clear_bit(WDOG_HW_RUNNING, &wdd->status); 84 } 85 86 return 0; 87 } 88 89 static const struct watchdog_info gpio_wdt_ident = { 90 .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | 91 WDIOF_SETTIMEOUT, 92 .identity = "GPIO Watchdog", 93 }; 94 95 static const struct watchdog_ops gpio_wdt_ops = { 96 .owner = THIS_MODULE, 97 .start = gpio_wdt_start, 98 .stop = gpio_wdt_stop, 99 .ping = gpio_wdt_ping, 100 }; 101 102 static int gpio_wdt_probe(struct platform_device *pdev) 103 { 104 struct gpio_wdt_priv *priv; 105 enum of_gpio_flags flags; 106 unsigned int hw_margin; 107 unsigned long f = 0; 108 const char *algo; 109 int ret; 110 111 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 112 if (!priv) 113 return -ENOMEM; 114 115 platform_set_drvdata(pdev, priv); 116 117 priv->gpio = of_get_gpio_flags(pdev->dev.of_node, 0, &flags); 118 if (!gpio_is_valid(priv->gpio)) 119 return priv->gpio; 120 121 priv->active_low = flags & OF_GPIO_ACTIVE_LOW; 122 123 ret = of_property_read_string(pdev->dev.of_node, "hw_algo", &algo); 124 if (ret) 125 return ret; 126 if (!strcmp(algo, "toggle")) { 127 priv->hw_algo = HW_ALGO_TOGGLE; 128 f = GPIOF_IN; 129 } else if (!strcmp(algo, "level")) { 130 priv->hw_algo = HW_ALGO_LEVEL; 131 f = priv->active_low ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW; 132 } else { 133 return -EINVAL; 134 } 135 136 ret = devm_gpio_request_one(&pdev->dev, priv->gpio, f, 137 dev_name(&pdev->dev)); 138 if (ret) 139 return ret; 140 141 ret = of_property_read_u32(pdev->dev.of_node, 142 "hw_margin_ms", &hw_margin); 143 if (ret) 144 return ret; 145 /* Disallow values lower than 2 and higher than 65535 ms */ 146 if (hw_margin < 2 || hw_margin > 65535) 147 return -EINVAL; 148 149 priv->always_running = of_property_read_bool(pdev->dev.of_node, 150 "always-running"); 151 152 watchdog_set_drvdata(&priv->wdd, priv); 153 154 priv->wdd.info = &gpio_wdt_ident; 155 priv->wdd.ops = &gpio_wdt_ops; 156 priv->wdd.min_timeout = SOFT_TIMEOUT_MIN; 157 priv->wdd.max_hw_heartbeat_ms = hw_margin; 158 priv->wdd.parent = &pdev->dev; 159 160 if (watchdog_init_timeout(&priv->wdd, 0, &pdev->dev) < 0) 161 priv->wdd.timeout = SOFT_TIMEOUT_DEF; 162 163 watchdog_stop_on_reboot(&priv->wdd); 164 165 if (priv->always_running) 166 gpio_wdt_start(&priv->wdd); 167 168 ret = watchdog_register_device(&priv->wdd); 169 170 return ret; 171 } 172 173 static int gpio_wdt_remove(struct platform_device *pdev) 174 { 175 struct gpio_wdt_priv *priv = platform_get_drvdata(pdev); 176 177 watchdog_unregister_device(&priv->wdd); 178 179 return 0; 180 } 181 182 static const struct of_device_id gpio_wdt_dt_ids[] = { 183 { .compatible = "linux,wdt-gpio", }, 184 { } 185 }; 186 MODULE_DEVICE_TABLE(of, gpio_wdt_dt_ids); 187 188 static struct platform_driver gpio_wdt_driver = { 189 .driver = { 190 .name = "gpio-wdt", 191 .of_match_table = gpio_wdt_dt_ids, 192 }, 193 .probe = gpio_wdt_probe, 194 .remove = gpio_wdt_remove, 195 }; 196 197 #ifdef CONFIG_GPIO_WATCHDOG_ARCH_INITCALL 198 static int __init gpio_wdt_init(void) 199 { 200 return platform_driver_register(&gpio_wdt_driver); 201 } 202 arch_initcall(gpio_wdt_init); 203 #else 204 module_platform_driver(gpio_wdt_driver); 205 #endif 206 207 MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>"); 208 MODULE_DESCRIPTION("GPIO Watchdog"); 209 MODULE_LICENSE("GPL"); 210