aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/generic/patches-3.19/990-gpio_wdt.patch
blob: 47422734f3adef739dc18cfec31b50eb818db25f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
This generic GPIO watchdog is used on Huawei E970 (brcm47xx)

Signed-off-by: Mathias Adam <m.adam--openwrt@adamis.de>

--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -1139,6 +1139,15 @@ config WDT_MTX1
 	  Hardware driver for the MTX-1 boards. This is a watchdog timer that
 	  will reboot the machine after a 100 seconds timer expired.
 
+config GPIO_WDT
+	tristate "GPIO Hardware Watchdog"
+ 	help
+	  Hardware driver for GPIO-controlled watchdogs. GPIO pin and
+	  toggle interval settings are platform-specific. The driver
+	  will stop toggling the GPIO (i.e. machine reboots) after a
+	  100 second timer expired and no process has written to
+	  /dev/watchdog during that time.
+
 config PNX833X_WDT
 	tristate "PNX833x Hardware Watchdog"
 	depends on SOC_PNX8335
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -134,6 +134,7 @@ obj-$(CONFIG_RC32434_WDT) += rc32434_wdt
 obj-$(CONFIG_INDYDOG) += indydog.o
 obj-$(CONFIG_JZ4740_WDT) += jz4740_wdt.o
 obj-$(CONFIG_WDT_MTX1) += mtx-1_wdt.o
+obj-$(CONFIG_GPIO_WDT) += old_gpio_wdt.o
 obj-$(CONFIG_PNX833X_WDT) += pnx833x_wdt.o
 obj-$(CONFIG_SIBYTE_WDOG) += sb_wdog.o
 obj-$(CONFIG_AR7_WDT) += ar7_wdt.o
--- /dev/null
+++ b/drivers/watchdog/old_gpio_wdt.c
@@ -0,0 +1,301 @@
+/*
+ *      Driver for GPIO-controlled Hardware Watchdogs.
+ *
+ *      Copyright (C) 2013 Mathias Adam <m.adam--linux@adamis.de>
+ *
+ *      Replaces mtx1_wdt (driver for the MTX-1 Watchdog):
+ *
+ *      (C) Copyright 2005 4G Systems <info@4g-systems.biz>,
+ *                              All Rights Reserved.
+ *                              http://www.4g-systems.biz
+ *
+ *      (C) Copyright 2007 OpenWrt.org, Florian Fainelli <florian@openwrt.org>
+ *
+ *      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.
+ *
+ *      Neither Michael Stickel nor 4G Systems admit liability nor provide
+ *      warranty for any of this software. This material is provided
+ *      "AS-IS" and at no charge.
+ *
+ *      (c) Copyright 2005    4G Systems <info@4g-systems.biz>
+ *
+ *      Release 0.01.
+ *      Author: Michael Stickel  michael.stickel@4g-systems.biz
+ *
+ *      Release 0.02.
+ *      Author: Florian Fainelli florian@openwrt.org
+ *              use the Linux watchdog/timer APIs
+ *
+ *      Release 0.03.
+ *      Author: Mathias Adam <m.adam--linux@adamis.de>
+ *              make it a generic gpio watchdog driver
+ *
+ *      The Watchdog is configured to reset the MTX-1
+ *      if it is not triggered for 100 seconds.
+ *      It should not be triggered more often than 1.6 seconds.
+ *
+ *      A timer triggers the watchdog every 5 seconds, until
+ *      it is opened for the first time. After the first open
+ *      it MUST be triggered every 2..95 seconds.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/timer.h>
+#include <linux/completion.h>
+#include <linux/jiffies.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <linux/gpio.h>
+#include <linux/old_gpio_wdt.h>
+
+static int ticks = 100 * HZ;
+
+static struct {
+	struct completion stop;
+	spinlock_t lock;
+	int running;
+	struct timer_list timer;
+	int queue;
+	int default_ticks;
+	unsigned long inuse;
+	unsigned gpio;
+	unsigned int gstate;
+	int interval;
+	int first_interval;
+} gpio_wdt_device;
+
+static void gpio_wdt_trigger(unsigned long unused)
+{
+	spin_lock(&gpio_wdt_device.lock);
+	if (gpio_wdt_device.running && ticks > 0)
+		ticks -= gpio_wdt_device.interval;
+
+	/* toggle wdt gpio */
+	gpio_wdt_device.gstate = !gpio_wdt_device.gstate;
+	gpio_set_value(gpio_wdt_device.gpio, gpio_wdt_device.gstate);
+
+	if (gpio_wdt_device.queue && ticks > 0)
+		mod_timer(&gpio_wdt_device.timer, jiffies + gpio_wdt_device.interval);
+	else
+		complete(&gpio_wdt_device.stop);
+	spin_unlock(&gpio_wdt_device.lock);
+}
+
+static void gpio_wdt_reset(void)
+{
+	ticks = gpio_wdt_device.default_ticks;
+}
+
+
+static void gpio_wdt_start(void)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gpio_wdt_device.lock, flags);
+	if (!gpio_wdt_device.queue) {
+		gpio_wdt_device.queue = 1;
+		gpio_wdt_device.gstate = 1;
+		gpio_set_value(gpio_wdt_device.gpio, 1);
+		mod_timer(&gpio_wdt_device.timer, jiffies + gpio_wdt_device.first_interval);
+	}
+	gpio_wdt_device.running++;
+	spin_unlock_irqrestore(&gpio_wdt_device.lock, flags);
+}
+
+static int gpio_wdt_stop(void)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gpio_wdt_device.lock, flags);
+	if (gpio_wdt_device.queue) {
+		gpio_wdt_device.queue = 0;
+		gpio_wdt_device.gstate = 0;
+		gpio_set_value(gpio_wdt_device.gpio, 0);
+	}
+	ticks = gpio_wdt_device.default_ticks;
+	spin_unlock_irqrestore(&gpio_wdt_device.lock, flags);
+	return 0;
+}
+
+/* Filesystem functions */
+
+static int gpio_wdt_open(struct inode *inode, struct file *file)
+{
+	if (test_and_set_bit(0, &gpio_wdt_device.inuse))
+		return -EBUSY;
+	return nonseekable_open(inode, file);
+}
+
+
+static int gpio_wdt_release(struct inode *inode, struct file *file)
+{
+	clear_bit(0, &gpio_wdt_device.inuse);
+	return 0;
+}
+
+static long gpio_wdt_ioctl(struct file *file, unsigned int cmd,
+							unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int __user *p = (int __user *)argp;
+	unsigned int value;
+	static const struct watchdog_info ident = {
+		.options = WDIOF_CARDRESET,
+		.identity = "GPIO WDT",
+	};
+
+	switch (cmd) {
+	case WDIOC_GETSUPPORT:
+		if (copy_to_user(argp, &ident, sizeof(ident)))
+			return -EFAULT;
+		break;
+	case WDIOC_GETSTATUS:
+	case WDIOC_GETBOOTSTATUS:
+		put_user(0, p);
+		break;
+	case WDIOC_SETOPTIONS:
+		if (get_user(value, p))
+			return -EFAULT;
+		if (value & WDIOS_ENABLECARD)
+			gpio_wdt_start();
+		else if (value & WDIOS_DISABLECARD)
+			gpio_wdt_stop();
+		else
+			return -EINVAL;
+		return 0;
+	case WDIOC_KEEPALIVE:
+		gpio_wdt_reset();
+		break;
+	default:
+		return -ENOTTY;
+	}
+	return 0;
+}
+
+
+static ssize_t gpio_wdt_write(struct file *file, const char *buf,
+						size_t count, loff_t *ppos)
+{
+	if (!count)
+		return -EIO;
+	gpio_wdt_reset();
+	return count;
+}
+
+static const struct file_operations gpio_wdt_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.unlocked_ioctl	= gpio_wdt_ioctl,
+	.open		= gpio_wdt_open,
+	.write		= gpio_wdt_write,
+	.release	= gpio_wdt_release,
+};
+
+
+static struct miscdevice gpio_wdt_misc = {
+	.minor	= WATCHDOG_MINOR,
+	.name	= "watchdog",
+	.fops	= &gpio_wdt_fops,
+};
+
+
+static int gpio_wdt_probe(struct platform_device *pdev)
+{
+	int ret;
+	struct gpio_wdt_platform_data *gpio_wdt_data = pdev->dev.platform_data;
+
+	gpio_wdt_device.gpio = gpio_wdt_data->gpio;
+	gpio_wdt_device.interval = gpio_wdt_data->interval;
+	gpio_wdt_device.first_interval = gpio_wdt_data->first_interval;
+	if (gpio_wdt_device.first_interval <= 0) {
+		gpio_wdt_device.first_interval = gpio_wdt_device.interval;
+	}
+
+	ret = gpio_request(gpio_wdt_device.gpio, "gpio-wdt");
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to request gpio");
+		return ret;
+	}
+
+	spin_lock_init(&gpio_wdt_device.lock);
+	init_completion(&gpio_wdt_device.stop);
+	gpio_wdt_device.queue = 0;
+	clear_bit(0, &gpio_wdt_device.inuse);
+	setup_timer(&gpio_wdt_device.timer, gpio_wdt_trigger, 0L);
+	gpio_wdt_device.default_ticks = ticks;
+
+	gpio_wdt_start();
+	dev_info(&pdev->dev, "GPIO Hardware Watchdog driver (gpio=%i interval=%i/%i)\n",
+		gpio_wdt_data->gpio, gpio_wdt_data->first_interval, gpio_wdt_data->interval);
+	return 0;
+}
+
+static int gpio_wdt_remove(struct platform_device *pdev)
+{
+	/* FIXME: do we need to lock this test ? */
+	if (gpio_wdt_device.queue) {
+		gpio_wdt_device.queue = 0;
+		wait_for_completion(&gpio_wdt_device.stop);
+	}
+
+	gpio_free(gpio_wdt_device.gpio);
+	misc_deregister(&gpio_wdt_misc);
+	return 0;
+}
+
+static struct platform_driver gpio_wdt_driver = {
+	.probe = gpio_wdt_probe,
+	.remove = gpio_wdt_remove,
+	.driver.name = "gpio-wdt",
+	.driver.owner = THIS_MODULE,
+};
+
+static int __init gpio_wdt_init(void)
+{
+	return platform_driver_register(&gpio_wdt_driver);
+}
+arch_initcall(gpio_wdt_init);
+
+/*
+ * We do wdt initialization in two steps: arch_initcall probes the wdt
+ * very early to start pinging the watchdog (misc devices are not yet
+ * available), and later module_init() just registers the misc device.
+ */
+static int gpio_wdt_init_late(void)
+{
+	int ret;
+
+	ret = misc_register(&gpio_wdt_misc);
+	if (ret < 0) {
+		pr_err("GPIO_WDT: failed to register misc device\n");
+		return ret;
+	}
+	return 0;
+}
+#ifndef MODULE
+module_init(gpio_wdt_init_late);
+#endif
+
+static void __exit gpio_wdt_exit(void)
+{
+	platform_driver_unregister(&gpio_wdt_driver);
+}
+module_exit(gpio_wdt_exit);
+
+MODULE_AUTHOR("Michael Stickel, Florian Fainelli, Mathias Adam");
+MODULE_DESCRIPTION("Driver for GPIO hardware watchdogs");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+MODULE_ALIAS("platform:gpio-wdt");
--- /dev/null
+++ b/include/linux/old_gpio_wdt.h
@@ -0,0 +1,21 @@
+/*
+ *  Definitions for the GPIO watchdog driver
+ *
+ *  Copyright (C) 2013 Mathias Adam <m.adam--linux@adamis.de>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ *
+ */
+
+#ifndef _GPIO_WDT_H_
+#define _GPIO_WDT_H_
+
+struct gpio_wdt_platform_data {
+	int	gpio;		/* GPIO line number */
+	int	interval;	/* watchdog reset interval in system ticks */
+	int	first_interval;	/* first wd reset interval in system ticks */
+};
+
+#endif /* _GPIO_WDT_H_ */