ftp://ftp.kernel.org/pub/linux/kernel/v2.6/linux-2.6.6.tar.bz2
[linux-2.6.git] / drivers / char / watchdog / alim1535_wdt.c
1 /*
2  *      Watchdog for the 7101 PMU version found in the ALi M1535 chipsets
3  *
4  *      This program is free software; you can redistribute it and/or
5  *      modify it under the terms of the GNU General Public License
6  *      as published by the Free Software Foundation; either version
7  *      2 of the License, or (at your option) any later version.
8  */
9
10 #include <linux/module.h>
11 #include <linux/moduleparam.h>
12 #include <linux/types.h>
13 #include <linux/miscdevice.h>
14 #include <linux/watchdog.h>
15 #include <linux/ioport.h>
16 #include <linux/notifier.h>
17 #include <linux/reboot.h>
18 #include <linux/init.h>
19 #include <linux/pci.h>
20
21 #include <asm/uaccess.h>
22 #include <asm/io.h>
23
24 #define WATCHDOG_NAME "ALi_M1535"
25 #define PFX WATCHDOG_NAME ": "
26 #define WATCHDOG_TIMEOUT 60     /* 60 sec default timeout */
27
28 /* internal variables */
29 static unsigned long ali_is_open;
30 static char ali_expect_release;
31 static struct pci_dev *ali_pci;
32 static u32 ali_timeout_bits;    /* stores the computed timeout */
33 static spinlock_t ali_lock;     /* Guards the hardware */
34
35 /* module parameters */
36 static int timeout = WATCHDOG_TIMEOUT;
37 module_param(timeout, int, 0);
38 MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (0<timeout<18000, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
39
40 #ifdef CONFIG_WATCHDOG_NOWAYOUT
41 static int nowayout = 1;
42 #else
43 static int nowayout = 0;
44 #endif
45
46 module_param(nowayout, int, 0);
47 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
48
49 /*
50  *      ali_start       -       start watchdog countdown
51  *
52  *      Starts the timer running providing the timer has a counter
53  *      configuration set.
54  */
55
56 static void ali_start(void)
57 {
58         u32 val;
59
60         spin_lock(&ali_lock);
61
62         pci_read_config_dword(ali_pci, 0xCC, &val);
63         val &= ~0x3F;   /* Mask count */
64         val |= (1<<25) | ali_timeout_bits;
65         pci_write_config_dword(ali_pci, 0xCC, val);
66
67         spin_unlock(&ali_lock);
68 }
69
70 /*
71  *      ali_stop        -       stop the timer countdown
72  *
73  *      Stop the ALi watchdog countdown
74  */
75
76 static void ali_stop(void)
77 {
78         u32 val;
79
80         spin_lock(&ali_lock);
81
82         pci_read_config_dword(ali_pci, 0xCC, &val);
83         val &= ~0x3F;   /* Mask count to zero (disabled) */
84         val &= ~(1<<25);/* and for safety mask the reset enable */
85         pci_write_config_dword(ali_pci, 0xCC, val);
86
87         spin_unlock(&ali_lock);
88 }
89
90 /*
91  *      ali_keepalive   -       send a keepalive to the watchdog
92  *
93  *      Send a keepalive to the timer (actually we restart the timer).
94  */
95
96 static void ali_keepalive(void)
97 {
98         ali_start();
99 }
100
101 /*
102  *      ali_settimer    -       compute the timer reload value
103  *      @t: time in seconds
104  *
105  *      Computes the timeout values needed
106  */
107
108 static int ali_settimer(int t)
109 {
110         if(t < 0)
111                 return -EINVAL;
112         else if(t < 60)
113                 ali_timeout_bits = t|(1<<6);
114         else if(t < 3600)
115                 ali_timeout_bits = (t/60)|(1<<7);
116         else if(t < 18000)
117                 ali_timeout_bits = (t/300)|(1<<6)|(1<<7);
118         else return -EINVAL;
119
120         timeout = t;
121         return 0;
122 }
123
124 /*
125  *      /dev/watchdog handling
126  */
127
128 /*
129  *      ali_write       -       writes to ALi watchdog
130  *      @file: file from VFS
131  *      @data: user address of data
132  *      @len: length of data
133  *      @ppos: pointer to the file offset
134  *
135  *      Handle a write to the ALi watchdog. Writing to the file pings
136  *      the watchdog and resets it. Writing the magic 'V' sequence allows
137  *      the next close to turn off the watchdog.
138  */
139
140 static ssize_t ali_write(struct file *file, const char *data,
141                               size_t len, loff_t * ppos)
142 {
143         /*  Can't seek (pwrite) on this device  */
144         if (ppos != &file->f_pos)
145                 return -ESPIPE;
146
147         /* See if we got the magic character 'V' and reload the timer */
148         if (len) {
149                 if (!nowayout) {
150                         size_t i;
151
152                         /* note: just in case someone wrote the magic character
153                          * five months ago... */
154                         ali_expect_release = 0;
155
156                         /* scan to see whether or not we got the magic character */
157                         for (i = 0; i != len; i++) {
158                                 char c;
159                                 if(get_user(c, data+i))
160                                         return -EFAULT;
161                                 if (c == 'V')
162                                         ali_expect_release = 42;
163                         }
164                 }
165
166                 /* someone wrote to us, we should reload the timer */
167                 ali_start();
168         }
169         return len;
170 }
171
172 /*
173  *      ali_ioctl       -       handle watchdog ioctls
174  *      @inode: VFS inode
175  *      @file: VFS file pointer
176  *      @cmd: ioctl number
177  *      @arg: arguments to the ioctl
178  *
179  *      Handle the watchdog ioctls supported by the ALi driver. Really
180  *      we want an extension to enable irq ack monitoring and the like
181  */
182
183 static int ali_ioctl(struct inode *inode, struct file *file,
184                           unsigned int cmd, unsigned long arg)
185 {
186         static struct watchdog_info ident = {
187                 .options =              WDIOF_KEEPALIVEPING |
188                                         WDIOF_SETTIMEOUT |
189                                         WDIOF_MAGICCLOSE,
190                 .firmware_version =     0,
191                 .identity =             "ALi M1535 WatchDog Timer",
192         };
193
194         switch (cmd) {
195                 case WDIOC_GETSUPPORT:
196                         return copy_to_user((struct watchdog_info *) arg, &ident,
197                                 sizeof (ident)) ? -EFAULT : 0;
198
199                 case WDIOC_GETSTATUS:
200                 case WDIOC_GETBOOTSTATUS:
201                         return put_user(0, (int *) arg);
202
203                 case WDIOC_KEEPALIVE:
204                         ali_keepalive();
205                         return 0;
206
207                 case WDIOC_SETOPTIONS:
208                 {
209                         int new_options, retval = -EINVAL;
210
211                         if (get_user (new_options, (int *) arg))
212                                 return -EFAULT;
213
214                         if (new_options & WDIOS_DISABLECARD) {
215                                 ali_stop();
216                                 retval = 0;
217                         }
218
219                         if (new_options & WDIOS_ENABLECARD) {
220                                 ali_start();
221                                 retval = 0;
222                         }
223
224                         return retval;
225                 }
226
227                 case WDIOC_SETTIMEOUT:
228                 {
229                         int new_timeout;
230
231                         if (get_user(new_timeout, (int *) arg))
232                                 return -EFAULT;
233
234                         if (ali_settimer(new_timeout))
235                             return -EINVAL;
236
237                         ali_keepalive();
238                         /* Fall */
239                 }
240
241                 case WDIOC_GETTIMEOUT:
242                         return put_user(timeout, (int *)arg);
243
244                 default:
245                         return -ENOIOCTLCMD;
246         }
247 }
248
249 /*
250  *      ali_open        -       handle open of ali watchdog
251  *      @inode: inode from VFS
252  *      @file: file from VFS
253  *
254  *      Open the ALi watchdog device. Ensure only one person opens it
255  *      at a time. Also start the watchdog running.
256  */
257
258 static int ali_open(struct inode *inode, struct file *file)
259 {
260         /* /dev/watchdog can only be opened once */
261         if (test_and_set_bit(0, &ali_is_open))
262                 return -EBUSY;
263
264         /* Activate */
265         ali_start();
266         return 0;
267 }
268
269 /*
270  *      ali_release     -       close an ALi watchdog
271  *      @inode: inode from VFS
272  *      @file: file from VFS
273  *
274  *      Close the ALi watchdog device. Actual shutdown of the timer
275  *      only occurs if the magic sequence has been set.
276  */
277
278 static int ali_release(struct inode *inode, struct file *file)
279 {
280         /*
281          *      Shut off the timer.
282          */
283         if (ali_expect_release == 42) {
284                 ali_stop();
285         } else {
286                 printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
287                 ali_keepalive();
288         }
289         clear_bit(0, &ali_is_open);
290         ali_expect_release = 0;
291         return 0;
292 }
293
294 /*
295  *      ali_notify_sys  -       System down notifier
296  *
297  *      Notifier for system down
298  */
299
300
301 static int ali_notify_sys(struct notifier_block *this, unsigned long code, void *unused)
302 {
303         if (code==SYS_DOWN || code==SYS_HALT) {
304                 /* Turn the WDT off */
305                 ali_stop();
306         }
307
308         return NOTIFY_DONE;
309 }
310
311 /*
312  *      Data for PCI driver interface
313  *
314  *      This data only exists for exporting the supported
315  *      PCI ids via MODULE_DEVICE_TABLE.  We do not actually
316  *      register a pci_driver, because someone else might one day
317  *      want to register another driver on the same PCI id.
318  */
319
320 static struct pci_device_id ali_pci_tbl[] = {
321         { PCI_VENDOR_ID_AL, 1535, PCI_ANY_ID, PCI_ANY_ID,},
322         { 0, },
323 };
324 MODULE_DEVICE_TABLE(pci, ali_pci_tbl);
325
326 /*
327  *      ali_find_watchdog       -       find a 1535 and 7101
328  *
329  *      Scans the PCI hardware for a 1535 series bridge and matching 7101
330  *      watchdog device. This may be overtight but it is better to be safe
331  */
332
333 static int __init ali_find_watchdog(void)
334 {
335         struct pci_dev *pdev;
336         u32 wdog;
337
338         /* Check for a 1535 series bridge */
339         pdev = pci_find_device(PCI_VENDOR_ID_AL, 0x1535, NULL);
340         if(pdev == NULL)
341                 return -ENODEV;
342
343         /* Check for the a 7101 PMU */
344         pdev = pci_find_device(PCI_VENDOR_ID_AL, 0x7101, NULL);
345         if(pdev == NULL)
346                 return -ENODEV;
347
348         if(pci_enable_device(pdev))
349                 return -EIO;
350
351         ali_pci = pdev;
352
353         /*
354          *      Initialize the timer bits
355          */
356         pci_read_config_dword(pdev, 0xCC, &wdog);
357
358         wdog &= ~0x3F;          /* Timer bits */
359         wdog &= ~((1<<27)|(1<<26)|(1<<25)|(1<<24));     /* Issued events */
360         wdog &= ~((1<<16)|(1<<13)|(1<<12)|(1<<11)|(1<<10)|(1<<9));      /* No monitor bits */
361
362         pci_write_config_dword(pdev, 0xCC, wdog);
363
364         return 0;
365 }
366
367 /*
368  *      Kernel Interfaces
369  */
370
371 static struct file_operations ali_fops = {
372         .owner =        THIS_MODULE,
373         .llseek =       no_llseek,
374         .write =        ali_write,
375         .ioctl =        ali_ioctl,
376         .open =         ali_open,
377         .release =      ali_release,
378 };
379
380 static struct miscdevice ali_miscdev = {
381         .minor =        WATCHDOG_MINOR,
382         .name =         "watchdog",
383         .fops =         &ali_fops,
384 };
385
386 static struct notifier_block ali_notifier = {
387         .notifier_call =        ali_notify_sys,
388 };
389
390 /*
391  *      watchdog_init   -       module initialiser
392  *
393  *      Scan for a suitable watchdog and if so initialize it. Return an error
394  *      if we cannot, the error causes the module to unload
395  */
396
397 static int __init watchdog_init(void)
398 {
399         int ret;
400
401         spin_lock_init(&ali_lock);
402
403         /* Check whether or not the hardware watchdog is there */
404         if (ali_find_watchdog() != 0) {
405                 return -ENODEV;
406         }
407
408         /* Check that the timeout value is within it's range ; if not reset to the default */
409         if (timeout < 1 || timeout >= 18000) {
410                 timeout = WATCHDOG_TIMEOUT;
411                 printk(KERN_INFO PFX "timeout value must be 0<timeout<18000, using %d\n",
412                         timeout);
413         }
414
415         /* Calculate the watchdog's timeout */
416         ali_settimer(timeout);
417
418         ret = misc_register(&ali_miscdev);
419         if (ret != 0) {
420                 printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
421                         WATCHDOG_MINOR, ret);
422                 goto out;
423         }
424
425         ret = register_reboot_notifier(&ali_notifier);
426         if (ret != 0) {
427                 printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
428                         ret);
429                 goto unreg_miscdev;
430         }
431
432         printk(KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n",
433                 timeout, nowayout);
434
435 out:
436         return ret;
437 unreg_miscdev:
438         misc_deregister(&ali_miscdev);
439         goto out;
440 }
441
442 /*
443  *      watchdog_exit   -       module de-initialiser
444  *
445  *      Called while unloading a successfully installed watchdog module.
446  */
447
448 static void __exit watchdog_exit(void)
449 {
450         /* Stop the timer before we leave */
451         ali_stop();
452
453         /* Deregister */
454         unregister_reboot_notifier(&ali_notifier);
455         misc_deregister(&ali_miscdev);
456 }
457
458 module_init(watchdog_init);
459 module_exit(watchdog_exit);
460
461 MODULE_AUTHOR("Alan Cox");
462 MODULE_DESCRIPTION("ALi M1535 PMU Watchdog Timer driver");
463 MODULE_LICENSE("GPL");
464 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);