aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/watchdog
diff options
context:
space:
mode:
authorRenaud CERRATO <r.cerrato@til-technologies.fr>2008-06-23 11:05:49 -0400
committerWim Van Sebroeck <wim@iguana.be>2008-10-10 09:09:51 -0400
commite6bb42e3d669afbeb4c971994614bcf241687666 (patch)
tree707f13c14ccfa2bea894f7e670d6ffe69dd67fc2 /drivers/watchdog
parent7d8b09066117e3023e55964ae4626c107f437b60 (diff)
[WATCHDOG] Add AT91SAM9X watchdog
Add a driver for the watchdog timer embedded into AT91SAM9X chips. Signed-off-by: Renaud Cerrato <r.cerrato@til-technologies.fr> Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
Diffstat (limited to 'drivers/watchdog')
-rw-r--r--drivers/watchdog/Kconfig7
-rw-r--r--drivers/watchdog/Makefile1
-rw-r--r--drivers/watchdog/at91sam9_wdt.c328
3 files changed, 336 insertions, 0 deletions
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 37dc27c4ecdc..6a711eb6998b 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -66,6 +66,13 @@ config AT91RM9200_WATCHDOG
66 Watchdog timer embedded into AT91RM9200 chips. This will reboot your 66 Watchdog timer embedded into AT91RM9200 chips. This will reboot your
67 system when the timeout is reached. 67 system when the timeout is reached.
68 68
69config AT91SAM9X_WATCHDOG
70 tristate "AT91SAM9X watchdog"
71 depends on WATCHDOG && (ARCH_AT91SAM9260 || ARCH_AT91SAM9261)
72 help
73 Watchdog timer embedded into AT91SAM9X chips. This will reboot your
74 system when the timeout is reached.
75
69config 21285_WATCHDOG 76config 21285_WATCHDOG
70 tristate "DC21285 watchdog" 77 tristate "DC21285 watchdog"
71 depends on FOOTBRIDGE 78 depends on FOOTBRIDGE
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 6f018e5b3dd3..f896d6f666bc 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o
26 26
27# ARM Architecture 27# ARM Architecture
28obj-$(CONFIG_AT91RM9200_WATCHDOG) += at91rm9200_wdt.o 28obj-$(CONFIG_AT91RM9200_WATCHDOG) += at91rm9200_wdt.o
29obj-$(CONFIG_AT91SAM9X_WATCHDOG) += at91sam9_wdt.o
29obj-$(CONFIG_OMAP_WATCHDOG) += omap_wdt.o 30obj-$(CONFIG_OMAP_WATCHDOG) += omap_wdt.o
30obj-$(CONFIG_21285_WATCHDOG) += wdt285.o 31obj-$(CONFIG_21285_WATCHDOG) += wdt285.o
31obj-$(CONFIG_977_WATCHDOG) += wdt977.o 32obj-$(CONFIG_977_WATCHDOG) += wdt977.o
diff --git a/drivers/watchdog/at91sam9_wdt.c b/drivers/watchdog/at91sam9_wdt.c
new file mode 100644
index 000000000000..b4babfc31586
--- /dev/null
+++ b/drivers/watchdog/at91sam9_wdt.c
@@ -0,0 +1,328 @@
1/*
2 * Watchdog driver for Atmel AT91SAM9x processors.
3 *
4 * Copyright (C) 2008 Renaud CERRATO r.cerrato@til-technologies.fr
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
10 */
11
12/*
13 * The Watchdog Timer Mode Register can be only written to once. If the
14 * timeout need to be set from Linux, be sure that the bootstrap or the
15 * bootloader doesn't write to this register.
16 */
17
18#include <linux/errno.h>
19#include <linux/fs.h>
20#include <linux/init.h>
21#include <linux/kernel.h>
22#include <linux/miscdevice.h>
23#include <linux/module.h>
24#include <linux/moduleparam.h>
25#include <linux/platform_device.h>
26#include <linux/types.h>
27#include <linux/watchdog.h>
28#include <linux/jiffies.h>
29#include <linux/timer.h>
30#include <linux/bitops.h>
31#include <linux/uaccess.h>
32
33#include <asm/arch/at91_wdt.h>
34
35#define DRV_NAME "AT91SAM9 Watchdog"
36
37/* AT91SAM9 watchdog runs a 12bit counter @ 256Hz,
38 * use this to convert a watchdog
39 * value from/to milliseconds.
40 */
41#define ms_to_ticks(t) (((t << 8) / 1000) - 1)
42#define ticks_to_ms(t) (((t + 1) * 1000) >> 8)
43
44/* Hardware timeout in seconds */
45#define WDT_HW_TIMEOUT 2
46
47/* Timer heartbeat (500ms) */
48#define WDT_TIMEOUT (HZ/2)
49
50/* User land timeout */
51#define WDT_HEARTBEAT 15
52static int heartbeat = WDT_HEARTBEAT;
53module_param(heartbeat, int, 0);
54MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. "
55 "(default = " __MODULE_STRING(WDT_HEARTBEAT) ")");
56
57static int nowayout = WATCHDOG_NOWAYOUT;
58module_param(nowayout, int, 0);
59MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
60 "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
61
62static void at91_ping(unsigned long data);
63
64static struct {
65 unsigned long next_heartbeat; /* the next_heartbeat for the timer */
66 unsigned long open;
67 char expect_close;
68 struct timer_list timer; /* The timer that pings the watchdog */
69} at91wdt_private;
70
71/* ......................................................................... */
72
73
74/*
75 * Reload the watchdog timer. (ie, pat the watchdog)
76 */
77static inline void at91_wdt_reset(void)
78{
79 at91_sys_write(AT91_WDT_CR, AT91_WDT_KEY | AT91_WDT_WDRSTT);
80}
81
82/*
83 * Timer tick
84 */
85static void at91_ping(unsigned long data)
86{
87 if (time_before(jiffies, at91wdt_private.next_heartbeat) ||
88 (!nowayout && !at91wdt_private.open)) {
89 at91_wdt_reset();
90 mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT);
91 } else
92 printk(KERN_CRIT DRV_NAME": I will reset your machine !\n");
93}
94
95/*
96 * Watchdog device is opened, and watchdog starts running.
97 */
98static int at91_wdt_open(struct inode *inode, struct file *file)
99{
100 if (test_and_set_bit(0, &at91wdt_private.open))
101 return -EBUSY;
102
103 at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ;
104 mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT);
105
106 return nonseekable_open(inode, file);
107}
108
109/*
110 * Close the watchdog device.
111 */
112static int at91_wdt_close(struct inode *inode, struct file *file)
113{
114 clear_bit(0, &at91wdt_private.open);
115
116 /* stop internal ping */
117 if (!at91wdt_private.expect_close)
118 del_timer(&at91wdt_private.timer);
119
120 at91wdt_private.expect_close = 0;
121 return 0;
122}
123
124/*
125 * Set the watchdog time interval in 1/256Hz (write-once)
126 * Counter is 12 bit.
127 */
128static int at91_wdt_settimeout(unsigned int timeout)
129{
130 unsigned int reg;
131 unsigned int mr;
132
133 /* Check if disabled */
134 mr = at91_sys_read(AT91_WDT_MR);
135 if (mr & AT91_WDT_WDDIS) {
136 printk(KERN_ERR DRV_NAME": sorry, watchdog is disabled\n");
137 return -EIO;
138 }
139
140 /*
141 * All counting occurs at SLOW_CLOCK / 128 = 256 Hz
142 *
143 * Since WDV is a 12-bit counter, the maximum period is
144 * 4096 / 256 = 16 seconds.
145 */
146 reg = AT91_WDT_WDRSTEN /* causes watchdog reset */
147 /* | AT91_WDT_WDRPROC causes processor reset only */
148 | AT91_WDT_WDDBGHLT /* disabled in debug mode */
149 | AT91_WDT_WDD /* restart at any time */
150 | (timeout & AT91_WDT_WDV); /* timer value */
151 at91_sys_write(AT91_WDT_MR, reg);
152
153 return 0;
154}
155
156static const struct watchdog_info at91_wdt_info = {
157 .identity = DRV_NAME,
158 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
159};
160
161/*
162 * Handle commands from user-space.
163 */
164static long at91_wdt_ioctl(struct file *file,
165 unsigned int cmd, unsigned long arg)
166{
167 void __user *argp = (void __user *)arg;
168 int __user *p = argp;
169 int new_value;
170
171 switch (cmd) {
172 case WDIOC_GETSUPPORT:
173 return copy_to_user(argp, &at91_wdt_info,
174 sizeof(at91_wdt_info)) ? -EFAULT : 0;
175
176 case WDIOC_GETSTATUS:
177 case WDIOC_GETBOOTSTATUS:
178 return put_user(0, p);
179
180 case WDIOC_KEEPALIVE:
181 at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ;
182 return 0;
183
184 case WDIOC_SETTIMEOUT:
185 if (get_user(new_value, p))
186 return -EFAULT;
187
188 heartbeat = new_value;
189 at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ;
190
191 return put_user(new_value, p); /* return current value */
192
193 case WDIOC_GETTIMEOUT:
194 return put_user(heartbeat, p);
195 }
196 return -ENOTTY;
197}
198
199/*
200 * Pat the watchdog whenever device is written to.
201 */
202static ssize_t at91_wdt_write(struct file *file, const char *data, size_t len,
203 loff_t *ppos)
204{
205 if (!len)
206 return 0;
207
208 /* Scan for magic character */
209 if (!nowayout) {
210 size_t i;
211
212 at91wdt_private.expect_close = 0;
213
214 for (i = 0; i < len; i++) {
215 char c;
216 if (get_user(c, data + i))
217 return -EFAULT;
218 if (c == 'V') {
219 at91wdt_private.expect_close = 42;
220 break;
221 }
222 }
223 }
224
225 at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ;
226
227 return len;
228}
229
230/* ......................................................................... */
231
232static const struct file_operations at91wdt_fops = {
233 .owner = THIS_MODULE,
234 .llseek = no_llseek,
235 .unlocked_ioctl = at91_wdt_ioctl,
236 .open = at91_wdt_open,
237 .release = at91_wdt_close,
238 .write = at91_wdt_write,
239};
240
241static struct miscdevice at91wdt_miscdev = {
242 .minor = WATCHDOG_MINOR,
243 .name = "watchdog",
244 .fops = &at91wdt_fops,
245};
246
247static int __init at91wdt_probe(struct platform_device *pdev)
248{
249 int res;
250
251 if (at91wdt_miscdev.parent)
252 return -EBUSY;
253 at91wdt_miscdev.parent = &pdev->dev;
254
255 /* Set watchdog */
256 res = at91_wdt_settimeout(ms_to_ticks(WDT_HW_TIMEOUT * 1000));
257 if (res)
258 return res;
259
260 res = misc_register(&at91wdt_miscdev);
261 if (res)
262 return res;
263
264 at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ;
265 setup_timer(&at91wdt_private.timer, at91_ping, 0);
266 mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT);
267
268 printk(KERN_INFO DRV_NAME " enabled (heartbeat=%d sec, nowayout=%d)\n",
269 heartbeat, nowayout);
270
271 return 0;
272}
273
274static int __exit at91wdt_remove(struct platform_device *pdev)
275{
276 int res;
277
278 res = misc_deregister(&at91wdt_miscdev);
279 if (!res)
280 at91wdt_miscdev.parent = NULL;
281
282 return res;
283}
284
285#ifdef CONFIG_PM
286
287static int at91wdt_suspend(struct platform_device *pdev, pm_message_t message)
288{
289 return 0;
290}
291
292static int at91wdt_resume(struct platform_device *pdev)
293{
294 return 0;
295}
296
297#else
298#define at91wdt_suspend NULL
299#define at91wdt_resume NULL
300#endif
301
302static struct platform_driver at91wdt_driver = {
303 .remove = __exit_p(at91wdt_remove),
304 .suspend = at91wdt_suspend,
305 .resume = at91wdt_resume,
306 .driver = {
307 .name = "at91_wdt",
308 .owner = THIS_MODULE,
309 },
310};
311
312static int __init at91sam_wdt_init(void)
313{
314 return platform_driver_probe(&at91wdt_driver, at91wdt_probe);
315}
316
317static void __exit at91sam_wdt_exit(void)
318{
319 platform_driver_unregister(&at91wdt_driver);
320}
321
322module_init(at91sam_wdt_init);
323module_exit(at91sam_wdt_exit);
324
325MODULE_AUTHOR("Renaud CERRATO <r.cerrato@til-technologies.fr>");
326MODULE_DESCRIPTION("Watchdog driver for Atmel AT91SAM9x processors");
327MODULE_LICENSE("GPL");
328MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);