2 * AMD Elan SC520 processor Watchdog Timer driver for Linux 2.4.x
4 * Based on acquirewdt.c by Alan Cox,
5 * and sbc60xxwdt.c by Jakob Oestergaard <jakob@ostenfeld.dk>
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version
10 * 2 of the License, or (at your option) any later version.
12 * The authors do NOT admit liability nor provide warranty for
13 * any of this software. This material is provided "AS-IS" in
14 * the hope that it may be useful for others.
16 * (c) Copyright 2001 Scott Jennings <linuxdrivers@oro.net>
17 * 9/27 - 2001 [Initial release]
19 * Additional fixes Alan Cox
21 * - Removed debug printks
22 * - Fixed SMP built kernel deadlock
23 * - Switched to private locks not lock_kernel
24 * - Used ioremap/writew/readw
25 * - Added NOWAYOUT support
27 * Theory of operation:
28 * A Watchdog Timer (WDT) is a hardware circuit that can
29 * reset the computer system in case of a software fault.
30 * You probably knew that already.
32 * Usually a userspace daemon will notify the kernel WDT driver
33 * via the /proc/watchdog special device file that userspace is
34 * still alive, at regular intervals. When such a notification
35 * occurs, the driver will usually tell the hardware watchdog
36 * that everything is in order, and that the watchdog should wait
37 * for yet another little while to reset the system.
38 * If userspace fails (RAM error, kernel bug, whatever), the
39 * notifications cease to occur, and the hardware watchdog will
40 * reset the system (causing a reboot) after the timeout occurs.
42 * This WDT driver is different from most other Linux WDT
43 * drivers in that the driver will ping the watchdog by itself,
44 * because this particular WDT has a very short timeout (1.6
45 * seconds) and it would be insane to count on any userspace
46 * daemon always getting scheduled within that time frame.
48 * This driver uses memory mapped IO, and spinlock.
51 #include <linux/module.h>
52 #include <linux/moduleparam.h>
53 #include <linux/types.h>
54 #include <linux/timer.h>
55 #include <linux/miscdevice.h>
56 #include <linux/watchdog.h>
58 #include <linux/ioport.h>
59 #include <linux/notifier.h>
60 #include <linux/reboot.h>
61 #include <linux/init.h>
64 #include <asm/uaccess.h>
65 #include <asm/system.h>
68 * The SC520 can timeout anywhere from 492us to 32.21s.
69 * If we reset the watchdog every ~250ms we should be safe.
72 #define WDT_INTERVAL (HZ/4+1)
75 * We must not require too good response from the userspace daemon.
76 * Here we require the userspace daemon to send us a heartbeat
77 * char to /dev/watchdog every 30 seconds.
80 #define WDT_HEARTBEAT (HZ * 30)
83 * AMD Elan SC520 timeout value is 492us times a power of 2 (0-7)
85 * 0: 492us 2: 1.01s 4: 4.03s 6: 16.22s
86 * 1: 503ms 3: 2.01s 5: 8.05s 7: 32.21s
89 #define TIMEOUT_EXPONENT ( 1 << 3 ) /* 0x08 = 2.01s */
91 /* #define MMCR_BASE_DEFAULT 0xfffef000 */
92 #define MMCR_BASE_DEFAULT ((__u16 *)0xffffe)
93 #define OFFS_WDTMRCTL ((unsigned int)0xcb0)
94 #define WDT_ENB 0x8000 /* [15] Watchdog Timer Enable */
95 #define WDT_WRST_ENB 0x4000 /* [14] Watchdog Timer Reset Enable */
97 #define OUR_NAME "sc520_wdt"
99 #define WRT_DOG(data) *wdtmrctl=data
101 static __u16 *wdtmrctl;
103 static void wdt_timer_ping(unsigned long);
104 static struct timer_list timer;
105 static unsigned long next_heartbeat;
106 static unsigned long wdt_is_open;
107 static int wdt_expect_close;
109 #ifdef CONFIG_WATCHDOG_NOWAYOUT
110 static int nowayout = 1;
112 static int nowayout = 0;
115 module_param(nowayout, int, 0);
116 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
118 static spinlock_t wdt_spinlock;
123 static void wdt_timer_ping(unsigned long data)
125 /* If we got a heartbeat pulse within the WDT_US_INTERVAL
126 * we agree to ping the WDT
128 if(time_before(jiffies, next_heartbeat))
131 spin_lock(&wdt_spinlock);
132 writew(0xAAAA, wdtmrctl);
133 writew(0x5555, wdtmrctl);
134 spin_unlock(&wdt_spinlock);
136 /* Re-set the timer interval */
137 timer.expires = jiffies + WDT_INTERVAL;
140 printk(OUR_NAME ": Heartbeat lost! Will not ping the watchdog\n");
148 static void wdt_config(int writeval)
153 /* buy some time (ping) */
154 spin_lock_irqsave(&wdt_spinlock, flags);
155 dummy=readw(wdtmrctl); /* ensure write synchronization */
156 writew(0xAAAA, wdtmrctl);
157 writew(0x5555, wdtmrctl);
158 /* make WDT configuration register writable one time */
159 writew(0x3333, wdtmrctl);
160 writew(0xCCCC, wdtmrctl);
161 /* write WDT configuration register */
162 writew(writeval, wdtmrctl);
163 spin_unlock_irqrestore(&wdt_spinlock, flags);
166 static void wdt_startup(void)
168 next_heartbeat = jiffies + WDT_HEARTBEAT;
170 /* Start the timer */
171 timer.expires = jiffies + WDT_INTERVAL;
174 wdt_config(WDT_ENB | WDT_WRST_ENB | TIMEOUT_EXPONENT);
175 printk(OUR_NAME ": Watchdog timer is now enabled.\n");
178 static void wdt_turnoff(void)
184 printk(OUR_NAME ": Watchdog timer is now disabled...\n");
190 * /dev/watchdog handling
193 static ssize_t fop_write(struct file * file, const char * buf, size_t count, loff_t * ppos)
196 if(ppos != &file->f_pos)
199 /* See if we got the magic character */
204 /* note: just in case someone wrote the magic character
205 * five months ago... */
206 wdt_expect_close = 0;
209 for(ofs = 0; ofs != count; ofs++) {
211 if (get_user(c, buf + ofs))
214 wdt_expect_close = 1;
217 /* Well, anyhow someone wrote to us, we should return that favour */
218 next_heartbeat = jiffies + WDT_HEARTBEAT;
224 static int fop_open(struct inode * inode, struct file * file)
226 switch(minor(inode->i_rdev))
229 /* Just in case we're already talking to someone... */
230 if(test_and_set_bit(0, &wdt_is_open))
232 /* Good, fire up the show */
235 __module_get(THIS_MODULE);
243 static int fop_close(struct inode * inode, struct file * file)
245 if(minor(inode->i_rdev) == WATCHDOG_MINOR)
251 printk(OUR_NAME ": device file closed unexpectedly. Will not stop the WDT!\n");
254 clear_bit(0, &wdt_is_open);
258 static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
261 static struct watchdog_info ident=
263 .options = WDIOF_MAGICCLOSE,
264 .firmware_version = 1,
272 case WDIOC_GETSUPPORT:
273 return copy_to_user((struct watchdog_info *)arg, &ident, sizeof(ident))?-EFAULT:0;
274 case WDIOC_KEEPALIVE:
275 next_heartbeat = jiffies + WDT_HEARTBEAT;
280 static struct file_operations wdt_fops = {
281 .owner = THIS_MODULE,
285 .release = fop_close,
289 static struct miscdevice wdt_miscdev = {
290 .minor = WATCHDOG_MINOR,
296 * Notifier for system down
299 static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
302 if(code==SYS_DOWN || code==SYS_HALT)
308 * The WDT needs to learn about soft shutdowns in order to
309 * turn the timebomb registers off.
312 static struct notifier_block wdt_notifier=
314 .notifier_call = wdt_notify_sys,
319 static void __exit sc520_wdt_unload(void)
324 misc_deregister(&wdt_miscdev);
326 unregister_reboot_notifier(&wdt_notifier);
329 static int __init sc520_wdt_init(void)
334 spin_lock_init(&wdt_spinlock);
337 timer.function = wdt_timer_ping;
340 rc = misc_register(&wdt_miscdev);
342 goto err_out_region2;
344 rc = register_reboot_notifier(&wdt_notifier);
346 goto err_out_miscdev;
348 /* get the Base Address Register */
349 cbar = inl_p(0xfffc);
350 printk(OUR_NAME ": CBAR: 0x%08lx\n", cbar);
351 /* check if MMCR aliasing bit is set */
352 if (cbar & 0x80000000) {
353 printk(OUR_NAME ": MMCR Aliasing enabled.\n");
354 wdtmrctl = (__u16 *)(cbar & 0x3fffffff);
356 printk(OUR_NAME "!!! WARNING !!!\n"
357 "\t MMCR Aliasing found NOT enabled!\n"
358 "\t Using default value of: %p\n"
359 "\t This has not been tested!\n"
360 "\t Please email Scott Jennings <smj@oro.net>\n"
361 "\t and Bill Jennings <bj@oro.net> if it works!\n"
364 wdtmrctl = MMCR_BASE_DEFAULT;
367 wdtmrctl = (__u16 *)((char *)wdtmrctl + OFFS_WDTMRCTL);
368 wdtmrctl = ioremap((unsigned long)wdtmrctl, 2);
369 printk(KERN_INFO OUR_NAME ": WDT driver for SC520 initialised.\n");
374 misc_deregister(&wdt_miscdev);
379 module_init(sc520_wdt_init);
380 module_exit(sc520_wdt_unload);
382 MODULE_AUTHOR("Scott and Bill Jennings");
383 MODULE_DESCRIPTION("Driver for watchdog timer in AMD \"Elan\" SC520 uProcessor");
384 MODULE_LICENSE("GPL");