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