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