commented early_printk patch because of rejects.
[linux-flexiantxendom0-3.2.10.git] / drivers / char / watchdog / sc520_wdt.c
1 /*
2  *      AMD Elan SC520 processor Watchdog Timer driver for Linux 2.4.x
3  *
4  *      Based on acquirewdt.c by Alan Cox,
5  *           and sbc60xxwdt.c by Jakob Oestergaard <jakob@ostenfeld.dk>
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  *  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.
31  *
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.
41  *
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.
47  *
48  *  This driver uses memory mapped IO, and spinlock.
49  */
50
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>
57 #include <linux/fs.h>
58 #include <linux/ioport.h>
59 #include <linux/notifier.h>
60 #include <linux/reboot.h>
61 #include <linux/init.h>
62
63 #include <asm/io.h>
64 #include <asm/uaccess.h>
65 #include <asm/system.h>
66
67 /*
68  * The SC520 can timeout anywhere from 492us to 32.21s.
69  * If we reset the watchdog every ~250ms we should be safe.
70  */
71
72 #define WDT_INTERVAL (HZ/4+1)
73
74 /*
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.
78  */
79
80 #define WDT_HEARTBEAT (HZ * 30)
81
82 /*
83  * AMD Elan SC520 timeout value is 492us times a power of 2 (0-7)
84  *
85  *   0: 492us    2: 1.01s    4: 4.03s   6: 16.22s
86  *   1: 503ms    3: 2.01s    5: 8.05s   7: 32.21s
87  */
88
89 #define TIMEOUT_EXPONENT ( 1 << 3 )  /* 0x08 = 2.01s */
90
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 */
96
97 #define OUR_NAME "sc520_wdt"
98
99 #define WRT_DOG(data) *wdtmrctl=data
100
101 static __u16 *wdtmrctl;
102
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;
108
109 #ifdef CONFIG_WATCHDOG_NOWAYOUT
110 static int nowayout = 1;
111 #else
112 static int nowayout = 0;
113 #endif
114
115 module_param(nowayout, int, 0);
116 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
117
118 static spinlock_t wdt_spinlock;
119 /*
120  *      Whack the dog
121  */
122
123 static void wdt_timer_ping(unsigned long data)
124 {
125         /* If we got a heartbeat pulse within the WDT_US_INTERVAL
126          * we agree to ping the WDT 
127          */
128         if(time_before(jiffies, next_heartbeat)) 
129         {
130                 /* Ping the WDT */
131                 spin_lock(&wdt_spinlock);
132                 writew(0xAAAA, wdtmrctl);
133                 writew(0x5555, wdtmrctl);
134                 spin_unlock(&wdt_spinlock);
135
136                 /* Re-set the timer interval */
137                 timer.expires = jiffies + WDT_INTERVAL;
138                 add_timer(&timer);
139         } else {
140                 printk(OUR_NAME ": Heartbeat lost! Will not ping the watchdog\n");
141         }
142 }
143
144 /* 
145  * Utility routines
146  */
147
148 static void wdt_config(int writeval)
149 {
150         __u16 dummy;
151         unsigned long flags;
152
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);
164 }
165
166 static void wdt_startup(void)
167 {
168         next_heartbeat = jiffies + WDT_HEARTBEAT;
169
170         /* Start the timer */
171         timer.expires = jiffies + WDT_INTERVAL; 
172         add_timer(&timer);
173
174         wdt_config(WDT_ENB | WDT_WRST_ENB | TIMEOUT_EXPONENT);
175         printk(OUR_NAME ": Watchdog timer is now enabled.\n");  
176 }
177
178 static void wdt_turnoff(void)
179 {
180         if (!nowayout) {
181                 /* Stop the timer */
182                 del_timer(&timer);
183                 wdt_config(0);
184                 printk(OUR_NAME ": Watchdog timer is now disabled...\n");
185         }
186 }
187
188
189 /*
190  * /dev/watchdog handling
191  */
192
193 static ssize_t fop_write(struct file * file, const char * buf, size_t count, loff_t * ppos)
194 {
195         /* We can't seek */
196         if(ppos != &file->f_pos)
197                 return -ESPIPE;
198
199         /* See if we got the magic character */
200         if(count) 
201         {
202                 size_t ofs;
203
204                 /* note: just in case someone wrote the magic character
205                  * five months ago... */
206                 wdt_expect_close = 0;
207
208                 /* now scan */
209                 for(ofs = 0; ofs != count; ofs++) {
210                         char c;
211                         if (get_user(c, buf + ofs))
212                                 return -EFAULT;
213                         if(c == 'V')
214                                 wdt_expect_close = 1;
215                 }
216
217                 /* Well, anyhow someone wrote to us, we should return that favour */
218                 next_heartbeat = jiffies + WDT_HEARTBEAT;
219                 return 1;
220         }
221         return 0;
222 }
223
224 static int fop_open(struct inode * inode, struct file * file)
225 {
226         switch(minor(inode->i_rdev)) 
227         {
228                 case WATCHDOG_MINOR:
229                         /* Just in case we're already talking to someone... */
230                         if(test_and_set_bit(0, &wdt_is_open))
231                                 return -EBUSY;
232                         /* Good, fire up the show */
233                         wdt_startup();
234                         if (nowayout)
235                                 __module_get(THIS_MODULE);
236
237                         return 0;
238                 default:
239                         return -ENODEV;
240         }
241 }
242
243 static int fop_close(struct inode * inode, struct file * file)
244 {
245         if(minor(inode->i_rdev) == WATCHDOG_MINOR) 
246         {
247                 if(wdt_expect_close)
248                         wdt_turnoff();
249                 else {
250                         del_timer(&timer);
251                         printk(OUR_NAME ": device file closed unexpectedly. Will not stop the WDT!\n");
252                 }
253         }
254         clear_bit(0, &wdt_is_open);
255         return 0;
256 }
257
258 static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
259         unsigned long arg)
260 {
261         static struct watchdog_info ident=
262         {
263                 .options = WDIOF_MAGICCLOSE,
264                 .firmware_version = 1,
265                 .identity = "SC520"
266         };
267         
268         switch(cmd)
269         {
270                 default:
271                         return -ENOIOCTLCMD;
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;
276                         return 0;
277         }
278 }
279
280 static struct file_operations wdt_fops = {
281         .owner          = THIS_MODULE,
282         .llseek         = no_llseek,
283         .write          = fop_write,
284         .open           = fop_open,
285         .release        = fop_close,
286         .ioctl          = fop_ioctl
287 };
288
289 static struct miscdevice wdt_miscdev = {
290         .minor  = WATCHDOG_MINOR,
291         .name   = "watchdog",
292         .fops   = &wdt_fops
293 };
294
295 /*
296  *      Notifier for system down
297  */
298
299 static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
300         void *unused)
301 {
302         if(code==SYS_DOWN || code==SYS_HALT) 
303                 wdt_turnoff();
304         return NOTIFY_DONE;
305 }
306  
307 /*
308  *      The WDT needs to learn about soft shutdowns in order to
309  *      turn the timebomb registers off. 
310  */
311  
312 static struct notifier_block wdt_notifier=
313 {
314         .notifier_call = wdt_notify_sys,
315         .next = NULL,
316         .priority = 0
317 };
318
319 static void __exit sc520_wdt_unload(void)
320 {
321         wdt_turnoff();
322
323         /* Deregister */
324         misc_deregister(&wdt_miscdev);
325         iounmap(wdtmrctl);
326         unregister_reboot_notifier(&wdt_notifier);
327 }
328
329 static int __init sc520_wdt_init(void)
330 {
331         int rc = -EBUSY;
332         unsigned long cbar;
333
334         spin_lock_init(&wdt_spinlock);
335
336         init_timer(&timer);
337         timer.function = wdt_timer_ping;
338         timer.data = 0;
339
340         rc = misc_register(&wdt_miscdev);
341         if (rc)
342                 goto err_out_region2;
343
344         rc = register_reboot_notifier(&wdt_notifier);
345         if (rc)
346                 goto err_out_miscdev;
347
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);
355         } else {
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"
362                   , MMCR_BASE_DEFAULT
363                   );
364           wdtmrctl = MMCR_BASE_DEFAULT;
365         }
366
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");
370
371         return 0;
372
373 err_out_miscdev:
374         misc_deregister(&wdt_miscdev);
375 err_out_region2:
376         return rc;
377 }
378
379 module_init(sc520_wdt_init);
380 module_exit(sc520_wdt_unload);
381
382 MODULE_AUTHOR("Scott and Bill Jennings");
383 MODULE_DESCRIPTION("Driver for watchdog timer in AMD \"Elan\" SC520 uProcessor");
384 MODULE_LICENSE("GPL");