Blame view

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