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