diff options
author | Alexander Shiyan <shc_work@mail.ru> | 2013-11-30 02:54:32 -0500 |
---|---|---|
committer | Wim Van Sebroeck <wim@iguana.be> | 2014-01-28 15:21:02 -0500 |
commit | 25134eafb05eef6dd4b6caee3a711b63ee0c3737 (patch) | |
tree | 17cd804d9904ad9c561b7efcbd9341c8578b7094 /drivers | |
parent | 8832b20093434514fda5e8a0c885270eda4fbf40 (diff) |
watchdog: GPIO-controlled watchdog
This patch adds a watchdog driver for devices controlled through GPIO,
(Analog Devices ADM706, Maxim MAX823, National NE555 etc).
Signed-off-by: Alexander Shiyan <shc_work@mail.ru>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/watchdog/Kconfig | 8 | ||||
-rw-r--r-- | drivers/watchdog/Makefile | 1 | ||||
-rw-r--r-- | drivers/watchdog/gpio_wdt.c | 254 |
3 files changed, 263 insertions, 0 deletions
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index af8f7c75e03c..7a2fedab0fb5 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig | |||
@@ -87,6 +87,14 @@ config DA9055_WATCHDOG | |||
87 | This driver can also be built as a module. If so, the module | 87 | This driver can also be built as a module. If so, the module |
88 | will be called da9055_wdt. | 88 | will be called da9055_wdt. |
89 | 89 | ||
90 | config GPIO_WATCHDOG | ||
91 | tristate "Watchdog device controlled through GPIO-line" | ||
92 | depends on OF_GPIO | ||
93 | select WATCHDOG_CORE | ||
94 | help | ||
95 | If you say yes here you get support for watchdog device | ||
96 | controlled through GPIO-line. | ||
97 | |||
90 | config WM831X_WATCHDOG | 98 | config WM831X_WATCHDOG |
91 | tristate "WM831x watchdog" | 99 | tristate "WM831x watchdog" |
92 | depends on MFD_WM831X | 100 | depends on MFD_WM831X |
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index af2251682214..985a66cda76f 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile | |||
@@ -172,6 +172,7 @@ obj-$(CONFIG_XEN_WDT) += xen_wdt.o | |||
172 | # Architecture Independent | 172 | # Architecture Independent |
173 | obj-$(CONFIG_DA9052_WATCHDOG) += da9052_wdt.o | 173 | obj-$(CONFIG_DA9052_WATCHDOG) += da9052_wdt.o |
174 | obj-$(CONFIG_DA9055_WATCHDOG) += da9055_wdt.o | 174 | obj-$(CONFIG_DA9055_WATCHDOG) += da9055_wdt.o |
175 | obj-$(CONFIG_GPIO_WATCHDOG) += gpio_wdt.o | ||
175 | obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o | 176 | obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o |
176 | obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o | 177 | obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o |
177 | obj-$(CONFIG_MAX63XX_WATCHDOG) += max63xx_wdt.o | 178 | obj-$(CONFIG_MAX63XX_WATCHDOG) += max63xx_wdt.o |
diff --git a/drivers/watchdog/gpio_wdt.c b/drivers/watchdog/gpio_wdt.c new file mode 100644 index 000000000000..220a9e07cfd5 --- /dev/null +++ b/drivers/watchdog/gpio_wdt.c | |||
@@ -0,0 +1,254 @@ | |||
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 | .owner = THIS_MODULE, | ||
245 | .of_match_table = gpio_wdt_dt_ids, | ||
246 | }, | ||
247 | .probe = gpio_wdt_probe, | ||
248 | .remove = gpio_wdt_remove, | ||
249 | }; | ||
250 | module_platform_driver(gpio_wdt_driver); | ||
251 | |||
252 | MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>"); | ||
253 | MODULE_DESCRIPTION("GPIO Watchdog"); | ||
254 | MODULE_LICENSE("GPL"); | ||