ftp://ftp.kernel.org/pub/linux/kernel/v2.6/linux-2.6.6.tar.bz2
[linux-2.6.git] / drivers / char / watchdog / sc520_wdt.c
1 /*
2  *      AMD Elan SC520 processor Watchdog Timer driver
3  *
4  *      Based on acquirewdt.c by Alan Cox,
5  *           and sbc60xxwdt.c by Jakob Oestergaard <jakob@unthought.net>
6  *
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.
11  *
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.
15  *
16  *      (c) Copyright 2001    Scott Jennings <linuxdrivers@oro.net>
17  *           9/27 - 2001      [Initial release]
18  *
19  *      Additional fixes Alan Cox
20  *      -       Fixed formatting
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
26  *
27  *     4/12 - 2002 Changes by Rob Radez <rob@osinvestor.com>
28  *     -       Change comments
29  *     -       Eliminate fop_llseek
30  *     -       Change CONFIG_WATCHDOG_NOWAYOUT semantics
31  *     -       Add KERN_* tags to printks
32  *     -       fix possible wdt_is_open race
33  *     -       Report proper capabilities in watchdog_info
34  *     -       Add WDIOC_{GETSTATUS, GETBOOTSTATUS, SETTIMEOUT,
35  *             GETTIMEOUT, SETOPTIONS} ioctls
36  *     09/8 - 2003 Changes by Wim Van Sebroeck <wim@iguana.be>
37  *     -       cleanup of trailing spaces
38  *     -       added extra printk's for startup problems
39  *     -       use module_param
40  *     -       made timeout (the emulated heartbeat) a module_param
41  *     -       made the keepalive ping an internal subroutine
42  *
43  *  This WDT driver is different from most other Linux WDT
44  *  drivers in that the driver will ping the watchdog by itself,
45  *  because this particular WDT has a very short timeout (1.6
46  *  seconds) and it would be insane to count on any userspace
47  *  daemon always getting scheduled within that time frame.
48  *
49  *  This driver uses memory mapped IO, and spinlock.
50  */
51
52 #include <linux/module.h>
53 #include <linux/moduleparam.h>
54 #include <linux/types.h>
55 #include <linux/timer.h>
56 #include <linux/miscdevice.h>
57 #include <linux/watchdog.h>
58 #include <linux/fs.h>
59 #include <linux/ioport.h>
60 #include <linux/notifier.h>
61 #include <linux/reboot.h>
62 #include <linux/init.h>
63
64 #include <asm/io.h>
65 #include <asm/uaccess.h>
66 #include <asm/system.h>
67
68 /*
69  * The SC520 can timeout anywhere from 492us to 32.21s.
70  * If we reset the watchdog every ~250ms we should be safe.
71  */
72
73 #define WDT_INTERVAL (HZ/4+1)
74
75 /*
76  * We must not require too good response from the userspace daemon.
77  * Here we require the userspace daemon to send us a heartbeat
78  * char to /dev/watchdog every 30 seconds.
79  */
80
81 #define WATCHDOG_TIMEOUT 30             /* 30 sec default timeout */
82 static int timeout = WATCHDOG_TIMEOUT;  /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */
83 module_param(timeout, int, 0);
84 MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
85
86 /*
87  * AMD Elan SC520 timeout value is 492us times a power of 2 (0-7)
88  *
89  *   0: 492us    2: 1.01s    4: 4.03s   6: 16.22s
90  *   1: 503ms    3: 2.01s    5: 8.05s   7: 32.21s
91  */
92
93 #define TIMEOUT_EXPONENT ( 1 << 3 )  /* 0x08 = 2.01s */
94
95 /* #define MMCR_BASE_DEFAULT 0xfffef000 */
96 #define MMCR_BASE_DEFAULT ((__u16 *)0xffffe)
97 #define OFFS_WDTMRCTL ((unsigned int)0xcb0)
98 #define WDT_ENB 0x8000          /* [15] Watchdog Timer Enable */
99 #define WDT_WRST_ENB 0x4000     /* [14] Watchdog Timer Reset Enable */
100
101 #define OUR_NAME "sc520_wdt"
102 #define PFX OUR_NAME ": "
103
104 #define WRT_DOG(data) *wdtmrctl=data
105
106 static __u16 *wdtmrctl;
107
108 static void wdt_timer_ping(unsigned long);
109 static struct timer_list timer;
110 static unsigned long next_heartbeat;
111 static unsigned long wdt_is_open;
112 static char wdt_expect_close;
113 static spinlock_t wdt_spinlock;
114
115 #ifdef CONFIG_WATCHDOG_NOWAYOUT
116 static int nowayout = 1;
117 #else
118 static int nowayout = 0;
119 #endif
120
121 module_param(nowayout, int, 0);
122 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
123
124 /*
125  *      Whack the dog
126  */
127
128 static void wdt_timer_ping(unsigned long data)
129 {
130         /* If we got a heartbeat pulse within the WDT_US_INTERVAL
131          * we agree to ping the WDT
132          */
133         if(time_before(jiffies, next_heartbeat))
134         {
135                 /* Ping the WDT */
136                 spin_lock(&wdt_spinlock);
137                 writew(0xAAAA, wdtmrctl);
138                 writew(0x5555, wdtmrctl);
139                 spin_unlock(&wdt_spinlock);
140
141                 /* Re-set the timer interval */
142                 timer.expires = jiffies + WDT_INTERVAL;
143                 add_timer(&timer);
144         } else {
145                 printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n");
146         }
147 }
148
149 /*
150  * Utility routines
151  */
152
153 static void wdt_config(int writeval)
154 {
155         __u16 dummy;
156         unsigned long flags;
157
158         /* buy some time (ping) */
159         spin_lock_irqsave(&wdt_spinlock, flags);
160         dummy=readw(wdtmrctl);  /* ensure write synchronization */
161         writew(0xAAAA, wdtmrctl);
162         writew(0x5555, wdtmrctl);
163         /* make WDT configuration register writable one time */
164         writew(0x3333, wdtmrctl);
165         writew(0xCCCC, wdtmrctl);
166         /* write WDT configuration register */
167         writew(writeval, wdtmrctl);
168         spin_unlock_irqrestore(&wdt_spinlock, flags);
169 }
170
171 static void wdt_startup(void)
172 {
173         next_heartbeat = jiffies + (timeout * HZ);
174
175         /* Start the timer */
176         timer.expires = jiffies + WDT_INTERVAL;
177         add_timer(&timer);
178
179         wdt_config(WDT_ENB | WDT_WRST_ENB | TIMEOUT_EXPONENT);
180         printk(KERN_INFO PFX "Watchdog timer is now enabled.\n");
181 }
182
183 static void wdt_turnoff(void)
184 {
185         if (!nowayout) {
186                 /* Stop the timer */
187                 del_timer(&timer);
188                 wdt_config(0);
189                 printk(KERN_INFO PFX "Watchdog timer is now disabled...\n");
190         }
191 }
192
193 static void wdt_keepalive(void)
194 {
195         /* user land ping */
196         next_heartbeat = jiffies + (timeout * HZ);
197 }
198
199 /*
200  * /dev/watchdog handling
201  */
202
203 static ssize_t fop_write(struct file * file, const char * buf, size_t count, loff_t * ppos)
204 {
205         /* We can't seek */
206         if(ppos != &file->f_pos)
207                 return -ESPIPE;
208
209         /* See if we got the magic character 'V' and reload the timer */
210         if(count)
211         {
212                 if (!nowayout)
213                 {
214                         size_t ofs;
215
216                         /* note: just in case someone wrote the magic character
217                          * five months ago... */
218                         wdt_expect_close = 0;
219
220                         /* now scan */
221                         for(ofs = 0; ofs != count; ofs++) {
222                                 char c;
223                                 if (get_user(c, buf + ofs))
224                                         return -EFAULT;
225                                 if(c == 'V')
226                                         wdt_expect_close = 42;
227                         }
228                 }
229
230                 /* Well, anyhow someone wrote to us, we should return that favour */
231                 wdt_keepalive();
232         }
233         return count;
234 }
235
236 static int fop_open(struct inode * inode, struct file * file)
237 {
238         /* Just in case we're already talking to someone... */
239         if(test_and_set_bit(0, &wdt_is_open))
240                 return -EBUSY;
241         if (nowayout)
242                 __module_get(THIS_MODULE);
243
244         /* Good, fire up the show */
245         wdt_startup();
246         return 0;
247 }
248
249 static int fop_close(struct inode * inode, struct file * file)
250 {
251         if(wdt_expect_close == 42)
252                 wdt_turnoff();
253         else {
254                 del_timer(&timer);
255                 printk(KERN_CRIT PFX "device file closed unexpectedly. Will not stop the WDT!\n");
256         }
257         clear_bit(0, &wdt_is_open);
258         wdt_expect_close = 0;
259         return 0;
260 }
261
262 static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
263         unsigned long arg)
264 {
265         static struct watchdog_info ident=
266         {
267                 .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
268                 .firmware_version = 1,
269                 .identity = "SC520",
270         };
271
272         switch(cmd)
273         {
274                 default:
275                         return -ENOIOCTLCMD;
276                 case WDIOC_GETSUPPORT:
277                         return copy_to_user((struct watchdog_info *)arg, &ident, sizeof(ident))?-EFAULT:0;
278                 case WDIOC_GETSTATUS:
279                 case WDIOC_GETBOOTSTATUS:
280                         return put_user(0, (int *)arg);
281                 case WDIOC_KEEPALIVE:
282                         wdt_keepalive();
283                         return 0;
284                 case WDIOC_SETOPTIONS:
285                 {
286                         int new_options, retval = -EINVAL;
287
288                         if(get_user(new_options, (int *)arg))
289                                 return -EFAULT;
290
291                         if(new_options & WDIOS_DISABLECARD) {
292                                 wdt_turnoff();
293                                 retval = 0;
294                         }
295
296                         if(new_options & WDIOS_ENABLECARD) {
297                                 wdt_startup();
298                                 retval = 0;
299                         }
300
301                         return retval;
302                 }
303                 case WDIOC_SETTIMEOUT:
304                 {
305                         int new_timeout;
306
307                         if(get_user(new_timeout, (int *)arg))
308                                 return -EFAULT;
309
310                         if(new_timeout < 1 || new_timeout > 3600) /* arbitrary upper limit */
311                                 return -EINVAL;
312
313                         timeout = new_timeout;
314                         wdt_keepalive();
315                         /* Fall through */
316                 }
317                 case WDIOC_GETTIMEOUT:
318                         return put_user(timeout, (int *)arg);
319         }
320 }
321
322 static struct file_operations wdt_fops = {
323         .owner          = THIS_MODULE,
324         .llseek         = no_llseek,
325         .write          = fop_write,
326         .open           = fop_open,
327         .release        = fop_close,
328         .ioctl          = fop_ioctl,
329 };
330
331 static struct miscdevice wdt_miscdev = {
332         .minor  = WATCHDOG_MINOR,
333         .name   = "watchdog",
334         .fops   = &wdt_fops,
335 };
336
337 /*
338  *      Notifier for system down
339  */
340
341 static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
342         void *unused)
343 {
344         if(code==SYS_DOWN || code==SYS_HALT)
345                 wdt_turnoff();
346         return NOTIFY_DONE;
347 }
348
349 /*
350  *      The WDT needs to learn about soft shutdowns in order to
351  *      turn the timebomb registers off.
352  */
353
354 static struct notifier_block wdt_notifier=
355 {
356         .notifier_call = wdt_notify_sys,
357 };
358
359 static void __exit sc520_wdt_unload(void)
360 {
361         wdt_turnoff();
362
363         /* Deregister */
364         misc_deregister(&wdt_miscdev);
365         iounmap(wdtmrctl);
366         unregister_reboot_notifier(&wdt_notifier);
367 }
368
369 static int __init sc520_wdt_init(void)
370 {
371         int rc = -EBUSY;
372         unsigned long cbar;
373
374         spin_lock_init(&wdt_spinlock);
375
376         if(timeout < 1 || timeout > 3600) /* arbitrary upper limit */
377         {
378                 timeout = WATCHDOG_TIMEOUT;
379                 printk(KERN_INFO PFX "timeout value must be 1<=x<=3600, using %d\n",
380                         timeout);
381         }
382
383         init_timer(&timer);
384         timer.function = wdt_timer_ping;
385         timer.data = 0;
386
387         rc = misc_register(&wdt_miscdev);
388         if (rc)
389         {
390                 printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
391                         wdt_miscdev.minor, rc);
392                 goto err_out_region2;
393         }
394
395         rc = register_reboot_notifier(&wdt_notifier);
396         if (rc)
397         {
398                 printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
399                         rc);
400                 goto err_out_miscdev;
401         }
402
403         /* get the Base Address Register */
404         cbar = inl_p(0xfffc);
405         printk(KERN_INFO PFX "CBAR: 0x%08lx\n", cbar);
406         /* check if MMCR aliasing bit is set */
407         if (cbar & 0x80000000) {
408                 printk(KERN_INFO PFX "MMCR Aliasing enabled.\n");
409                 wdtmrctl = (__u16 *)(cbar & 0x3fffffff);
410         } else {
411                 printk(KERN_INFO PFX "!!! WARNING !!!\n"
412                   "\t MMCR Aliasing found NOT enabled!\n"
413                   "\t Using default value of: %p\n"
414                   "\t This has not been tested!\n"
415                   "\t Please email Scott Jennings <smj@oro.net>\n"
416                   "\t  and Bill Jennings <bj@oro.net> if it works!\n"
417                   , MMCR_BASE_DEFAULT
418                   );
419           wdtmrctl = MMCR_BASE_DEFAULT;
420         }
421
422         wdtmrctl = (__u16 *)((char *)wdtmrctl + OFFS_WDTMRCTL);
423         wdtmrctl = ioremap((unsigned long)wdtmrctl, 2);
424         if (!wdtmrctl) {
425                 printk (KERN_ERR PFX "Unable to remap memory.\n");
426                 rc = -ENOMEM;
427                 goto err_out_notifier;
428         }
429         printk(KERN_INFO PFX "WDT driver for SC520 initialised. timeout=%d sec (nowayout=%d)\n",
430                 timeout,nowayout);
431
432         return 0;
433
434 err_out_notifier:
435         unregister_reboot_notifier(&wdt_notifier);
436 err_out_miscdev:
437         misc_deregister(&wdt_miscdev);
438 err_out_region2:
439         return rc;
440 }
441
442 module_init(sc520_wdt_init);
443 module_exit(sc520_wdt_unload);
444
445 MODULE_AUTHOR("Scott and Bill Jennings");
446 MODULE_DESCRIPTION("Driver for watchdog timer in AMD \"Elan\" SC520 uProcessor");
447 MODULE_LICENSE("GPL");
448 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);