Merge to Fedora kernel-2.6.7-1.441
[linux-2.6.git] / drivers / char / watchdog / scx200_wdt.c
1 /* drivers/char/watchdog/scx200_wdt.c
2
3    National Semiconductor SCx200 Watchdog support
4
5    Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>
6
7    Som code taken from:
8    National Semiconductor PC87307/PC97307 (ala SC1200) WDT driver
9    (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com>
10
11    This program is free software; you can redistribute it and/or
12    modify it under the terms of the GNU General Public License as
13    published by the Free Software Foundation; either version 2 of the
14    License, or (at your option) any later version.
15
16    The author(s) of this software shall not be held liable for damages
17    of any nature resulting due to the use of this software. This
18    software is provided AS-IS with no warranties. */
19
20 #include <linux/config.h>
21 #include <linux/module.h>
22 #include <linux/moduleparam.h>
23 #include <linux/init.h>
24 #include <linux/miscdevice.h>
25 #include <linux/watchdog.h>
26 #include <linux/notifier.h>
27 #include <linux/reboot.h>
28 #include <linux/fs.h>
29 #include <linux/pci.h>
30 #include <linux/scx200.h>
31
32 #include <asm/uaccess.h>
33 #include <asm/io.h>
34
35 #define NAME "scx200_wdt"
36
37 MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>");
38 MODULE_DESCRIPTION("NatSemi SCx200 Watchdog Driver");
39 MODULE_LICENSE("GPL");
40 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
41
42 #ifndef CONFIG_WATCHDOG_NOWAYOUT
43 #define CONFIG_WATCHDOG_NOWAYOUT 0
44 #endif
45
46 static int margin = 60;         /* in seconds */
47 module_param(margin, int, 0);
48 MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
49
50 static int nowayout = CONFIG_WATCHDOG_NOWAYOUT;
51 module_param(nowayout, int, 0);
52 MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
53
54 static u16 wdto_restart;
55 static struct semaphore open_semaphore;
56 static char expect_close;
57
58 /* Bits of the WDCNFG register */
59 #define W_ENABLE 0x00fa         /* Enable watchdog */
60 #define W_DISABLE 0x0000        /* Disable watchdog */
61
62 /* The scaling factor for the timer, this depends on the value of W_ENABLE */
63 #define W_SCALE (32768/1024)
64
65 static void scx200_wdt_ping(void)
66 {
67         outw(wdto_restart, SCx200_CB_BASE + SCx200_WDT_WDTO);
68 }
69
70 static void scx200_wdt_update_margin(void)
71 {
72         printk(KERN_INFO NAME ": timer margin %d seconds\n", margin);
73         wdto_restart = margin * W_SCALE;
74 }
75
76 static void scx200_wdt_enable(void)
77 {
78         printk(KERN_DEBUG NAME ": enabling watchdog timer, wdto_restart = %d\n",
79                wdto_restart);
80
81         outw(0, SCx200_CB_BASE + SCx200_WDT_WDTO);
82         outb(SCx200_WDT_WDSTS_WDOVF, SCx200_CB_BASE + SCx200_WDT_WDSTS);
83         outw(W_ENABLE, SCx200_CB_BASE + SCx200_WDT_WDCNFG);
84
85         scx200_wdt_ping();
86 }
87
88 static void scx200_wdt_disable(void)
89 {
90         printk(KERN_DEBUG NAME ": disabling watchdog timer\n");
91
92         outw(0, SCx200_CB_BASE + SCx200_WDT_WDTO);
93         outb(SCx200_WDT_WDSTS_WDOVF, SCx200_CB_BASE + SCx200_WDT_WDSTS);
94         outw(W_DISABLE, SCx200_CB_BASE + SCx200_WDT_WDCNFG);
95 }
96
97 static int scx200_wdt_open(struct inode *inode, struct file *file)
98 {
99         /* only allow one at a time */
100         if (down_trylock(&open_semaphore))
101                 return -EBUSY;
102         scx200_wdt_enable();
103
104         return 0;
105 }
106
107 static int scx200_wdt_release(struct inode *inode, struct file *file)
108 {
109         if (expect_close != 42) {
110                 printk(KERN_WARNING NAME ": watchdog device closed unexpectedly, will not disable the watchdog timer\n");
111         } else if (!nowayout) {
112                 scx200_wdt_disable();
113         }
114         expect_close = 0;
115         up(&open_semaphore);
116
117         return 0;
118 }
119
120 static int scx200_wdt_notify_sys(struct notifier_block *this,
121                                       unsigned long code, void *unused)
122 {
123         if (code == SYS_HALT || code == SYS_POWER_OFF)
124                 if (!nowayout)
125                         scx200_wdt_disable();
126
127         return NOTIFY_DONE;
128 }
129
130 static struct notifier_block scx200_wdt_notifier =
131 {
132         .notifier_call = scx200_wdt_notify_sys,
133 };
134
135 static ssize_t scx200_wdt_write(struct file *file, const char __user *data,
136                                      size_t len, loff_t *ppos)
137 {
138         if (ppos != &file->f_pos)
139                 return -ESPIPE;
140
141         /* check for a magic close character */
142         if (len)
143         {
144                 size_t i;
145
146                 scx200_wdt_ping();
147
148                 expect_close = 0;
149                 for (i = 0; i < len; ++i) {
150                         char c;
151                         if (get_user(c, data+i))
152                                 return -EFAULT;
153                         if (c == 'V')
154                                 expect_close = 42;
155                 }
156
157                 return len;
158         }
159
160         return 0;
161 }
162
163 static int scx200_wdt_ioctl(struct inode *inode, struct file *file,
164         unsigned int cmd, unsigned long arg)
165 {
166         void __user *argp = (void __user *)arg;
167         int __user *p = argp;
168         static struct watchdog_info ident = {
169                 .identity = "NatSemi SCx200 Watchdog",
170                 .firmware_version = 1,
171                 .options = (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING),
172         };
173         int new_margin;
174
175         switch (cmd) {
176         default:
177                 return -ENOIOCTLCMD;
178         case WDIOC_GETSUPPORT:
179                 if(copy_to_user(argp, &ident, sizeof(ident)))
180                         return -EFAULT;
181                 return 0;
182         case WDIOC_GETSTATUS:
183         case WDIOC_GETBOOTSTATUS:
184                 if (put_user(0, p))
185                         return -EFAULT;
186                 return 0;
187         case WDIOC_KEEPALIVE:
188                 scx200_wdt_ping();
189                 return 0;
190         case WDIOC_SETTIMEOUT:
191                 if (get_user(new_margin, p))
192                         return -EFAULT;
193                 if (new_margin < 1)
194                         return -EINVAL;
195                 margin = new_margin;
196                 scx200_wdt_update_margin();
197                 scx200_wdt_ping();
198         case WDIOC_GETTIMEOUT:
199                 if (put_user(margin, p))
200                         return -EFAULT;
201                 return 0;
202         }
203 }
204
205 static struct file_operations scx200_wdt_fops = {
206         .owner   = THIS_MODULE,
207         .write   = scx200_wdt_write,
208         .ioctl   = scx200_wdt_ioctl,
209         .open    = scx200_wdt_open,
210         .release = scx200_wdt_release,
211 };
212
213 static struct miscdevice scx200_wdt_miscdev = {
214         .minor = WATCHDOG_MINOR,
215         .name  = NAME,
216         .fops  = &scx200_wdt_fops,
217 };
218
219 static int __init scx200_wdt_init(void)
220 {
221         int r;
222
223         printk(KERN_DEBUG NAME ": NatSemi SCx200 Watchdog Driver\n");
224
225         /*
226          * First check that this really is a NatSemi SCx200 CPU or a Geode
227          * SC1100 processor
228          */
229         if ((pci_find_device(PCI_VENDOR_ID_NS,
230                              PCI_DEVICE_ID_NS_SCx200_BRIDGE,
231                              NULL)) == NULL
232             && (pci_find_device(PCI_VENDOR_ID_NS,
233                                 PCI_DEVICE_ID_NS_SC1100_BRIDGE,
234                                 NULL)) == NULL)
235                 return -ENODEV;
236
237         /* More sanity checks, verify that the configuration block is there */
238         if (!scx200_cb_probe(SCx200_CB_BASE)) {
239                 printk(KERN_WARNING NAME ": no configuration block found\n");
240                 return -ENODEV;
241         }
242
243         if (!request_region(SCx200_CB_BASE + SCx200_WDT_OFFSET,
244                             SCx200_WDT_SIZE,
245                             "NatSemi SCx200 Watchdog")) {
246                 printk(KERN_WARNING NAME ": watchdog I/O region busy\n");
247                 return -EBUSY;
248         }
249
250         scx200_wdt_update_margin();
251         scx200_wdt_disable();
252
253         sema_init(&open_semaphore, 1);
254
255         r = misc_register(&scx200_wdt_miscdev);
256         if (r) {
257                 release_region(SCx200_CB_BASE + SCx200_WDT_OFFSET,
258                                 SCx200_WDT_SIZE);
259                 return r;
260         }
261
262         r = register_reboot_notifier(&scx200_wdt_notifier);
263         if (r) {
264                 printk(KERN_ERR NAME ": unable to register reboot notifier");
265                 misc_deregister(&scx200_wdt_miscdev);
266                 release_region(SCx200_CB_BASE + SCx200_WDT_OFFSET,
267                                 SCx200_WDT_SIZE);
268                 return r;
269         }
270
271         return 0;
272 }
273
274 static void __exit scx200_wdt_cleanup(void)
275 {
276         unregister_reboot_notifier(&scx200_wdt_notifier);
277         misc_deregister(&scx200_wdt_miscdev);
278         release_region(SCx200_CB_BASE + SCx200_WDT_OFFSET,
279                        SCx200_WDT_SIZE);
280 }
281
282 module_init(scx200_wdt_init);
283 module_exit(scx200_wdt_cleanup);
284
285 /*
286     Local variables:
287         compile-command: "make -k -C ../.. SUBDIRS=drivers/char modules"
288         c-basic-offset: 8
289     End:
290 */