xref: /linux/drivers/watchdog/gpio_wdt.c (revision d91517839e5d95adc0cf4b28caa7af62a71de526)
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");
255