Commit | Line | Data |
---|---|---|
25134eaf AS |
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> | |
a2363f9d LW |
15 | #include <linux/gpio/consumer.h> |
16 | #include <linux/of.h> | |
25134eaf | 17 | #include <linux/platform_device.h> |
25134eaf AS |
18 | #include <linux/watchdog.h> |
19 | ||
20 | #define SOFT_TIMEOUT_MIN 1 | |
21 | #define SOFT_TIMEOUT_DEF 60 | |
25134eaf AS |
22 | |
23 | enum { | |
24 | HW_ALGO_TOGGLE, | |
25 | HW_ALGO_LEVEL, | |
26 | }; | |
27 | ||
28 | struct gpio_wdt_priv { | |
a2363f9d | 29 | struct gpio_desc *gpiod; |
25134eaf | 30 | bool state; |
ba804a95 | 31 | bool always_running; |
25134eaf | 32 | unsigned int hw_algo; |
25134eaf AS |
33 | struct watchdog_device wdd; |
34 | }; | |
35 | ||
36 | static void gpio_wdt_disable(struct gpio_wdt_priv *priv) | |
37 | { | |
a2363f9d LW |
38 | /* Eternal ping */ |
39 | gpiod_set_value_cansleep(priv->gpiod, 1); | |
25134eaf AS |
40 | |
41 | /* Put GPIO back to tristate */ | |
42 | if (priv->hw_algo == HW_ALGO_TOGGLE) | |
a2363f9d | 43 | gpiod_direction_input(priv->gpiod); |
25134eaf AS |
44 | } |
45 | ||
03bca158 | 46 | static int gpio_wdt_ping(struct watchdog_device *wdd) |
4f2d0b2d | 47 | { |
4f2d0b2d UKK |
48 | struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd); |
49 | ||
4f2d0b2d UKK |
50 | switch (priv->hw_algo) { |
51 | case HW_ALGO_TOGGLE: | |
52 | /* Toggle output pin */ | |
53 | priv->state = !priv->state; | |
a2363f9d | 54 | gpiod_set_value_cansleep(priv->gpiod, priv->state); |
4f2d0b2d UKK |
55 | break; |
56 | case HW_ALGO_LEVEL: | |
57 | /* Pulse */ | |
a2363f9d | 58 | gpiod_set_value_cansleep(priv->gpiod, 1); |
4f2d0b2d | 59 | udelay(1); |
a2363f9d | 60 | gpiod_set_value_cansleep(priv->gpiod, 0); |
4f2d0b2d UKK |
61 | break; |
62 | } | |
03bca158 | 63 | return 0; |
ba804a95 ML |
64 | } |
65 | ||
66 | static int gpio_wdt_start(struct watchdog_device *wdd) | |
67 | { | |
68 | struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd); | |
69 | ||
a2363f9d LW |
70 | priv->state = 0; |
71 | gpiod_direction_output(priv->gpiod, priv->state); | |
25134eaf | 72 | |
03bca158 GR |
73 | set_bit(WDOG_HW_RUNNING, &wdd->status); |
74 | ||
75 | return gpio_wdt_ping(wdd); | |
25134eaf AS |
76 | } |
77 | ||
78 | static int gpio_wdt_stop(struct watchdog_device *wdd) | |
79 | { | |
80 | struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd); | |
81 | ||
ba804a95 | 82 | if (!priv->always_running) { |
ba804a95 | 83 | gpio_wdt_disable(priv); |
bc137dfd RV |
84 | } else { |
85 | set_bit(WDOG_HW_RUNNING, &wdd->status); | |
ba804a95 | 86 | } |
25134eaf AS |
87 | |
88 | return 0; | |
89 | } | |
90 | ||
25134eaf AS |
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, | |
25134eaf AS |
102 | }; |
103 | ||
104 | static int gpio_wdt_probe(struct platform_device *pdev) | |
105 | { | |
d0d0677e LW |
106 | struct device *dev = &pdev->dev; |
107 | struct device_node *np = dev->of_node; | |
25134eaf | 108 | struct gpio_wdt_priv *priv; |
a2363f9d | 109 | enum gpiod_flags gflags; |
25134eaf | 110 | unsigned int hw_margin; |
25134eaf AS |
111 | const char *algo; |
112 | int ret; | |
113 | ||
d0d0677e | 114 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); |
25134eaf AS |
115 | if (!priv) |
116 | return -ENOMEM; | |
117 | ||
1ac06563 WY |
118 | platform_set_drvdata(pdev, priv); |
119 | ||
d0d0677e | 120 | ret = of_property_read_string(np, "hw_algo", &algo); |
25134eaf AS |
121 | if (ret) |
122 | return ret; | |
0a0a542f | 123 | if (!strcmp(algo, "toggle")) { |
25134eaf | 124 | priv->hw_algo = HW_ALGO_TOGGLE; |
a2363f9d | 125 | gflags = GPIOD_IN; |
0a0a542f | 126 | } else if (!strcmp(algo, "level")) { |
25134eaf | 127 | priv->hw_algo = HW_ALGO_LEVEL; |
a2363f9d | 128 | gflags = GPIOD_OUT_LOW; |
25134eaf AS |
129 | } else { |
130 | return -EINVAL; | |
131 | } | |
132 | ||
a2363f9d LW |
133 | priv->gpiod = devm_gpiod_get(dev, NULL, gflags); |
134 | if (IS_ERR(priv->gpiod)) | |
135 | return PTR_ERR(priv->gpiod); | |
25134eaf | 136 | |
d0d0677e | 137 | ret = of_property_read_u32(np, |
25134eaf AS |
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 | ||
d0d0677e | 145 | priv->always_running = of_property_read_bool(np, |
ba804a95 ML |
146 | "always-running"); |
147 | ||
25134eaf AS |
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; | |
03bca158 | 153 | priv->wdd.max_hw_heartbeat_ms = hw_margin; |
d0d0677e | 154 | priv->wdd.parent = dev; |
65adfa22 | 155 | priv->wdd.timeout = SOFT_TIMEOUT_DEF; |
25134eaf | 156 | |
65adfa22 | 157 | watchdog_init_timeout(&priv->wdd, 0, &pdev->dev); |
25134eaf | 158 | |
28e805b4 DR |
159 | watchdog_stop_on_reboot(&priv->wdd); |
160 | ||
ba804a95 | 161 | if (priv->always_running) |
03bca158 | 162 | gpio_wdt_start(&priv->wdd); |
ba804a95 | 163 | |
03bca158 GR |
164 | ret = watchdog_register_device(&priv->wdd); |
165 | ||
166 | return ret; | |
25134eaf AS |
167 | } |
168 | ||
169 | static int gpio_wdt_remove(struct platform_device *pdev) | |
170 | { | |
171 | struct gpio_wdt_priv *priv = platform_get_drvdata(pdev); | |
172 | ||
25134eaf AS |
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", | |
25134eaf AS |
187 | .of_match_table = gpio_wdt_dt_ids, |
188 | }, | |
189 | .probe = gpio_wdt_probe, | |
190 | .remove = gpio_wdt_remove, | |
191 | }; | |
5e53c8ed JBT |
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 | |
25134eaf | 200 | module_platform_driver(gpio_wdt_driver); |
5e53c8ed | 201 | #endif |
25134eaf AS |
202 | |
203 | MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>"); | |
204 | MODULE_DESCRIPTION("GPIO Watchdog"); | |
205 | MODULE_LICENSE("GPL"); |