]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blame - drivers/watchdog/gpio_wdt.c
watchdog: tangox: Print info message using pointer to platform device
[mirror_ubuntu-bionic-kernel.git] / drivers / watchdog / gpio_wdt.c
CommitLineData
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>
25134eaf
AS
15#include <linux/of_gpio.h>
16#include <linux/platform_device.h>
25134eaf
AS
17#include <linux/watchdog.h>
18
19#define SOFT_TIMEOUT_MIN 1
20#define SOFT_TIMEOUT_DEF 60
21#define SOFT_TIMEOUT_MAX 0xffff
22
23enum {
24 HW_ALGO_TOGGLE,
25 HW_ALGO_LEVEL,
26};
27
28struct gpio_wdt_priv {
29 int gpio;
30 bool active_low;
31 bool state;
ba804a95
ML
32 bool always_running;
33 bool armed;
25134eaf
AS
34 unsigned int hw_algo;
35 unsigned int hw_margin;
36 unsigned long last_jiffies;
25134eaf
AS
37 struct timer_list timer;
38 struct watchdog_device wdd;
39};
40
41static void gpio_wdt_disable(struct gpio_wdt_priv *priv)
42{
43 gpio_set_value_cansleep(priv->gpio, !priv->active_low);
44
45 /* Put GPIO back to tristate */
46 if (priv->hw_algo == HW_ALGO_TOGGLE)
47 gpio_direction_input(priv->gpio);
48}
49
4f2d0b2d
UKK
50static void gpio_wdt_hwping(unsigned long data)
51{
52 struct watchdog_device *wdd = (struct watchdog_device *)data;
53 struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
54
55 if (priv->armed && time_after(jiffies, priv->last_jiffies +
56 msecs_to_jiffies(wdd->timeout * 1000))) {
57 dev_crit(wdd->dev, "Timer expired. System will reboot soon!\n");
58 return;
59 }
60
61 /* Restart timer */
62 mod_timer(&priv->timer, jiffies + priv->hw_margin);
63
64 switch (priv->hw_algo) {
65 case HW_ALGO_TOGGLE:
66 /* Toggle output pin */
67 priv->state = !priv->state;
68 gpio_set_value_cansleep(priv->gpio, priv->state);
69 break;
70 case HW_ALGO_LEVEL:
71 /* Pulse */
72 gpio_set_value_cansleep(priv->gpio, !priv->active_low);
73 udelay(1);
74 gpio_set_value_cansleep(priv->gpio, priv->active_low);
75 break;
76 }
77}
78
ba804a95 79static void gpio_wdt_start_impl(struct gpio_wdt_priv *priv)
25134eaf 80{
25134eaf
AS
81 priv->state = priv->active_low;
82 gpio_direction_output(priv->gpio, priv->state);
83 priv->last_jiffies = jiffies;
4f2d0b2d 84 gpio_wdt_hwping((unsigned long)&priv->wdd);
ba804a95
ML
85}
86
87static int gpio_wdt_start(struct watchdog_device *wdd)
88{
89 struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
90
91 gpio_wdt_start_impl(priv);
92 priv->armed = true;
25134eaf
AS
93
94 return 0;
95}
96
97static int gpio_wdt_stop(struct watchdog_device *wdd)
98{
99 struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
100
ba804a95
ML
101 priv->armed = false;
102 if (!priv->always_running) {
103 mod_timer(&priv->timer, 0);
104 gpio_wdt_disable(priv);
105 }
25134eaf
AS
106
107 return 0;
108}
109
110static int gpio_wdt_ping(struct watchdog_device *wdd)
111{
112 struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
113
114 priv->last_jiffies = jiffies;
115
116 return 0;
117}
118
119static int gpio_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t)
120{
121 wdd->timeout = t;
122
123 return gpio_wdt_ping(wdd);
124}
125
25134eaf
AS
126static const struct watchdog_info gpio_wdt_ident = {
127 .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING |
128 WDIOF_SETTIMEOUT,
129 .identity = "GPIO Watchdog",
130};
131
132static const struct watchdog_ops gpio_wdt_ops = {
133 .owner = THIS_MODULE,
134 .start = gpio_wdt_start,
135 .stop = gpio_wdt_stop,
136 .ping = gpio_wdt_ping,
137 .set_timeout = gpio_wdt_set_timeout,
138};
139
140static int gpio_wdt_probe(struct platform_device *pdev)
141{
142 struct gpio_wdt_priv *priv;
143 enum of_gpio_flags flags;
144 unsigned int hw_margin;
145 unsigned long f = 0;
146 const char *algo;
147 int ret;
148
149 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
150 if (!priv)
151 return -ENOMEM;
152
153 priv->gpio = of_get_gpio_flags(pdev->dev.of_node, 0, &flags);
154 if (!gpio_is_valid(priv->gpio))
155 return priv->gpio;
156
157 priv->active_low = flags & OF_GPIO_ACTIVE_LOW;
158
159 ret = of_property_read_string(pdev->dev.of_node, "hw_algo", &algo);
160 if (ret)
161 return ret;
0a0a542f 162 if (!strcmp(algo, "toggle")) {
25134eaf
AS
163 priv->hw_algo = HW_ALGO_TOGGLE;
164 f = GPIOF_IN;
0a0a542f 165 } else if (!strcmp(algo, "level")) {
25134eaf
AS
166 priv->hw_algo = HW_ALGO_LEVEL;
167 f = priv->active_low ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW;
168 } else {
169 return -EINVAL;
170 }
171
172 ret = devm_gpio_request_one(&pdev->dev, priv->gpio, f,
173 dev_name(&pdev->dev));
174 if (ret)
175 return ret;
176
177 ret = of_property_read_u32(pdev->dev.of_node,
178 "hw_margin_ms", &hw_margin);
179 if (ret)
180 return ret;
181 /* Disallow values lower than 2 and higher than 65535 ms */
182 if (hw_margin < 2 || hw_margin > 65535)
183 return -EINVAL;
184
185 /* Use safe value (1/2 of real timeout) */
186 priv->hw_margin = msecs_to_jiffies(hw_margin / 2);
187
ba804a95
ML
188 priv->always_running = of_property_read_bool(pdev->dev.of_node,
189 "always-running");
190
25134eaf
AS
191 watchdog_set_drvdata(&priv->wdd, priv);
192
193 priv->wdd.info = &gpio_wdt_ident;
194 priv->wdd.ops = &gpio_wdt_ops;
195 priv->wdd.min_timeout = SOFT_TIMEOUT_MIN;
196 priv->wdd.max_timeout = SOFT_TIMEOUT_MAX;
6551881c 197 priv->wdd.parent = &pdev->dev;
25134eaf
AS
198
199 if (watchdog_init_timeout(&priv->wdd, 0, &pdev->dev) < 0)
200 priv->wdd.timeout = SOFT_TIMEOUT_DEF;
201
202 setup_timer(&priv->timer, gpio_wdt_hwping, (unsigned long)&priv->wdd);
203
28e805b4
DR
204 watchdog_stop_on_reboot(&priv->wdd);
205
25134eaf
AS
206 ret = watchdog_register_device(&priv->wdd);
207 if (ret)
208 return ret;
209
ba804a95
ML
210 if (priv->always_running)
211 gpio_wdt_start_impl(priv);
212
213 return 0;
25134eaf
AS
214}
215
216static int gpio_wdt_remove(struct platform_device *pdev)
217{
218 struct gpio_wdt_priv *priv = platform_get_drvdata(pdev);
219
220 del_timer_sync(&priv->timer);
25134eaf
AS
221 watchdog_unregister_device(&priv->wdd);
222
223 return 0;
224}
225
226static const struct of_device_id gpio_wdt_dt_ids[] = {
227 { .compatible = "linux,wdt-gpio", },
228 { }
229};
230MODULE_DEVICE_TABLE(of, gpio_wdt_dt_ids);
231
232static struct platform_driver gpio_wdt_driver = {
233 .driver = {
234 .name = "gpio-wdt",
25134eaf
AS
235 .of_match_table = gpio_wdt_dt_ids,
236 },
237 .probe = gpio_wdt_probe,
238 .remove = gpio_wdt_remove,
239};
5e53c8ed
JBT
240
241#ifdef CONFIG_GPIO_WATCHDOG_ARCH_INITCALL
242static int __init gpio_wdt_init(void)
243{
244 return platform_driver_register(&gpio_wdt_driver);
245}
246arch_initcall(gpio_wdt_init);
247#else
25134eaf 248module_platform_driver(gpio_wdt_driver);
5e53c8ed 249#endif
25134eaf
AS
250
251MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
252MODULE_DESCRIPTION("GPIO Watchdog");
253MODULE_LICENSE("GPL");