Merge git://git.infradead.org/mtd-2.6
[linux-2.6-block.git] / drivers / watchdog / w83697hf_wdt.c
CommitLineData
f9a8c891 1/*
de710d68 2 * w83697hf/hg WDT driver
f9a8c891 3 *
3fdee8db 4 * (c) Copyright 2006 Samuel Tardieu <sam@rfc1149.net>
f9a8c891
MJ
5 * (c) Copyright 2006 Marcus Junker <junker@anduras.de>
6 *
de710d68
ST
7 * Based on w83627hf_wdt.c which is based on advantechwdt.c
8 * which is based on wdt.c.
f9a8c891
MJ
9 * Original copyright messages:
10 *
96de0e25 11 * (c) Copyright 2003 Pádraig Brady <P@draigBrady.com>
f9a8c891
MJ
12 *
13 * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl>
14 *
15 * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved.
16 * http://www.redhat.com
17 *
18 * This program is free software; you can redistribute it and/or
19 * modify it under the terms of the GNU General Public License
20 * as published by the Free Software Foundation; either version
21 * 2 of the License, or (at your option) any later version.
22 *
23 * Neither Marcus Junker nor ANDURAS AG admit liability nor provide
24 * warranty for any of this software. This material is provided
25 * "AS-IS" and at no charge.
26 */
27
28#include <linux/module.h>
29#include <linux/moduleparam.h>
30#include <linux/types.h>
31#include <linux/miscdevice.h>
32#include <linux/watchdog.h>
33#include <linux/fs.h>
34#include <linux/ioport.h>
35#include <linux/notifier.h>
36#include <linux/reboot.h>
37#include <linux/init.h>
ab9d4414 38#include <linux/spinlock.h>
f9a8c891
MJ
39
40#include <asm/io.h>
41#include <asm/uaccess.h>
42#include <asm/system.h>
43
de710d68 44#define WATCHDOG_NAME "w83697hf/hg WDT"
f9a8c891
MJ
45#define PFX WATCHDOG_NAME ": "
46#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */
47
48static unsigned long wdt_is_open;
49static char expect_close;
c7dfd0cc 50static DEFINE_SPINLOCK(io_lock);
f9a8c891
MJ
51
52/* You must set this - there is no sane way to probe for this board. */
c81b2996 53static int wdt_io = 0x2e;
f9a8c891 54module_param(wdt_io, int, 0);
c81b2996 55MODULE_PARM_DESC(wdt_io, "w83697hf/hg WDT io port (default 0x2e, 0 = autodetect)");
f9a8c891
MJ
56
57static int timeout = WATCHDOG_TIMEOUT; /* in seconds */
58module_param(timeout, int, 0);
eb64419e 59MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. 1<= timeout <=255, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ".");
f9a8c891
MJ
60
61static int nowayout = WATCHDOG_NOWAYOUT;
62module_param(nowayout, int, 0);
bffda5c8 63MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
f9a8c891
MJ
64
65/*
66 * Kernel methods.
67 */
68
44d7d328
ST
69#define W83697HF_EFER (wdt_io+0) /* Extended Function Enable Register */
70#define W83697HF_EFIR (wdt_io+0) /* Extended Function Index Register (same as EFER) */
71#define W83697HF_EFDR (wdt_io+1) /* Extended Function Data Register */
f9a8c891 72
f7be3328
ST
73static inline void
74w83697hf_unlock(void)
f9a8c891 75{
44d7d328
ST
76 outb_p(0x87, W83697HF_EFER); /* Enter extended function mode */
77 outb_p(0x87, W83697HF_EFER); /* Again according to manual */
f7be3328
ST
78}
79
fe851eba
ST
80static inline void
81w83697hf_lock(void)
82{
83 outb_p(0xAA, W83697HF_EFER); /* Leave extended function mode */
84}
85
0cd54476 86/*
d46ab596
ST
87 * The three functions w83697hf_get_reg(), w83697hf_set_reg() and
88 * w83697hf_write_timeout() must be called with the device unlocked.
0cd54476
ST
89 */
90
91static unsigned char
92w83697hf_get_reg(unsigned char reg)
93{
94 outb_p(reg, W83697HF_EFIR);
95 return inb_p(W83697HF_EFDR);
96}
97
98static void
99w83697hf_set_reg(unsigned char reg, unsigned char data)
100{
101 outb_p(reg, W83697HF_EFIR);
102 outb_p(data, W83697HF_EFDR);
103}
104
d46ab596
ST
105static void
106w83697hf_write_timeout(int timeout)
107{
108 w83697hf_set_reg(0xF4, timeout); /* Write Timeout counter to CRF4 */
109}
110
a7933e05
ST
111static void
112w83697hf_select_wdt(void)
113{
114 w83697hf_unlock();
115 w83697hf_set_reg(0x07, 0x08); /* Switch to logic device 8 (GPIO2) */
116}
117
118static inline void
119w83697hf_deselect_wdt(void)
120{
121 w83697hf_lock();
122}
123
f9a8c891
MJ
124static void
125w83697hf_init(void)
126{
b7b9868b 127 unsigned char bbuf;
f9a8c891 128
fa69afd3
ST
129 w83697hf_select_wdt();
130
b7b9868b
ST
131 bbuf = w83697hf_get_reg(0x29);
132 bbuf &= ~0x60;
133 bbuf |= 0x20;
134 w83697hf_set_reg(0x29, bbuf); /* Set pin 119 to WDTO# mode (= CR29, WDT0) */
f9a8c891 135
b7b9868b
ST
136 bbuf = w83697hf_get_reg(0xF3);
137 bbuf &= ~0x04;
138 w83697hf_set_reg(0xF3, bbuf); /* Count mode is seconds */
f9a8c891 139
fa69afd3 140 w83697hf_deselect_wdt();
f9a8c891
MJ
141}
142
089d8139
ST
143static int
144wdt_ping(void)
f9a8c891 145{
ab9d4414 146 spin_lock(&io_lock);
a7933e05 147 w83697hf_select_wdt();
f9a8c891 148
d46ab596 149 w83697hf_write_timeout(timeout);
f9a8c891 150
a7933e05 151 w83697hf_deselect_wdt();
ab9d4414 152 spin_unlock(&io_lock);
089d8139 153 return 0;
f9a8c891
MJ
154}
155
156static int
089d8139 157wdt_enable(void)
f9a8c891 158{
089d8139
ST
159 spin_lock(&io_lock);
160 w83697hf_select_wdt();
161
162 w83697hf_write_timeout(timeout);
163 w83697hf_set_reg(0x30, 1); /* Enable timer */
164
165 w83697hf_deselect_wdt();
166 spin_unlock(&io_lock);
f9a8c891
MJ
167 return 0;
168}
169
170static int
171wdt_disable(void)
172{
089d8139
ST
173 spin_lock(&io_lock);
174 w83697hf_select_wdt();
175
176 w83697hf_set_reg(0x30, 0); /* Disable timer */
177 w83697hf_write_timeout(0);
178
179 w83697hf_deselect_wdt();
180 spin_unlock(&io_lock);
f9a8c891
MJ
181 return 0;
182}
183
184static int
185wdt_set_heartbeat(int t)
186{
eb64419e 187 if ((t < 1) || (t > 255))
f9a8c891
MJ
188 return -EINVAL;
189
190 timeout = t;
191 return 0;
192}
193
194static ssize_t
195wdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
196{
197 if (count) {
198 if (!nowayout) {
199 size_t i;
200
201 expect_close = 0;
202
203 for (i = 0; i != count; i++) {
204 char c;
205 if (get_user(c, buf+i))
206 return -EFAULT;
207 if (c == 'V')
208 expect_close = 42;
209 }
210 }
211 wdt_ping();
212 }
213 return count;
214}
215
216static int
217wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
218 unsigned long arg)
219{
220 void __user *argp = (void __user *)arg;
221 int __user *p = argp;
222 int new_timeout;
223 static struct watchdog_info ident = {
224 .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
225 .firmware_version = 1,
226 .identity = "W83697HF WDT",
227 };
228
229 switch (cmd) {
230 case WDIOC_GETSUPPORT:
db16525e
ST
231 if (copy_to_user(argp, &ident, sizeof(ident)))
232 return -EFAULT;
233 break;
f9a8c891
MJ
234
235 case WDIOC_GETSTATUS:
236 case WDIOC_GETBOOTSTATUS:
db16525e 237 return put_user(0, p);
f9a8c891
MJ
238
239 case WDIOC_KEEPALIVE:
db16525e
ST
240 wdt_ping();
241 break;
f9a8c891
MJ
242
243 case WDIOC_SETTIMEOUT:
db16525e
ST
244 if (get_user(new_timeout, p))
245 return -EFAULT;
246 if (wdt_set_heartbeat(new_timeout))
247 return -EINVAL;
248 wdt_ping();
249 /* Fall */
f9a8c891
MJ
250
251 case WDIOC_GETTIMEOUT:
db16525e 252 return put_user(timeout, p);
f9a8c891
MJ
253
254 case WDIOC_SETOPTIONS:
255 {
db16525e 256 int options, retval = -EINVAL;
f9a8c891 257
db16525e
ST
258 if (get_user(options, p))
259 return -EFAULT;
f9a8c891 260
db16525e
ST
261 if (options & WDIOS_DISABLECARD) {
262 wdt_disable();
263 retval = 0;
264 }
f9a8c891 265
db16525e 266 if (options & WDIOS_ENABLECARD) {
089d8139 267 wdt_enable();
db16525e
ST
268 retval = 0;
269 }
f9a8c891 270
db16525e 271 return retval;
f9a8c891
MJ
272 }
273
274 default:
db16525e 275 return -ENOTTY;
f9a8c891
MJ
276 }
277 return 0;
278}
279
280static int
281wdt_open(struct inode *inode, struct file *file)
282{
283 if (test_and_set_bit(0, &wdt_is_open))
284 return -EBUSY;
285 /*
286 * Activate
287 */
288
089d8139 289 wdt_enable();
f9a8c891
MJ
290 return nonseekable_open(inode, file);
291}
292
293static int
294wdt_close(struct inode *inode, struct file *file)
295{
296 if (expect_close == 42) {
297 wdt_disable();
298 } else {
db16525e 299 printk (KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
f9a8c891
MJ
300 wdt_ping();
301 }
302 expect_close = 0;
303 clear_bit(0, &wdt_is_open);
304 return 0;
305}
306
307/*
308 * Notifier for system down
309 */
310
311static int
312wdt_notify_sys(struct notifier_block *this, unsigned long code,
313 void *unused)
314{
315 if (code == SYS_DOWN || code == SYS_HALT) {
316 /* Turn the WDT off */
317 wdt_disable();
318 }
319 return NOTIFY_DONE;
320}
321
322/*
323 * Kernel Interfaces
324 */
325
2b8693c0 326static const struct file_operations wdt_fops = {
f9a8c891
MJ
327 .owner = THIS_MODULE,
328 .llseek = no_llseek,
329 .write = wdt_write,
330 .ioctl = wdt_ioctl,
331 .open = wdt_open,
332 .release = wdt_close,
333};
334
335static struct miscdevice wdt_miscdev = {
336 .minor = WATCHDOG_MINOR,
337 .name = "watchdog",
338 .fops = &wdt_fops,
339};
340
341/*
342 * The WDT needs to learn about soft shutdowns in order to
343 * turn the timebomb registers off.
344 */
345
346static struct notifier_block wdt_notifier = {
347 .notifier_call = wdt_notify_sys,
348};
349
c81b2996
ST
350static int
351w83697hf_check_wdt(void)
352{
353 if (!request_region(wdt_io, 2, WATCHDOG_NAME)) {
354 printk (KERN_ERR PFX "I/O address 0x%x already in use\n", wdt_io);
355 return -EIO;
356 }
357
358 printk (KERN_DEBUG PFX "Looking for watchdog at address 0x%x\n", wdt_io);
359 w83697hf_unlock();
360 if (w83697hf_get_reg(0x20) == 0x60) {
361 printk (KERN_INFO PFX "watchdog found at address 0x%x\n", wdt_io);
362 w83697hf_lock();
363 return 0;
364 }
365 w83697hf_lock(); /* Reprotect in case it was a compatible device */
366
367 printk (KERN_INFO PFX "watchdog not found at address 0x%x\n", wdt_io);
368 release_region(wdt_io, 2);
369 return -EIO;
370}
371
e223f01a
WVS
372static int w83697hf_ioports[] = { 0x2e, 0x4e, 0x00 };
373
f9a8c891
MJ
374static int __init
375wdt_init(void)
376{
e223f01a 377 int ret, i, found = 0;
f9a8c891 378
de710d68 379 printk (KERN_INFO PFX "WDT driver for W83697HF/HG initializing\n");
f9a8c891 380
e223f01a
WVS
381 if (wdt_io == 0) {
382 /* we will autodetect the W83697HF/HG watchdog */
383 for (i = 0; ((!found) && (w83697hf_ioports[i] != 0)); i++) {
384 wdt_io = w83697hf_ioports[i];
cde10ba3 385 if (!w83697hf_check_wdt())
e223f01a
WVS
386 found++;
387 }
388 } else {
c81b2996 389 if (!w83697hf_check_wdt())
e223f01a 390 found++;
c81b2996
ST
391 }
392
e223f01a
WVS
393 if (!found) {
394 printk (KERN_ERR PFX "No W83697HF/HG could be found\n");
395 ret = -EIO;
396 goto out;
397 }
c81b2996 398
fa69afd3
ST
399 w83697hf_init();
400 wdt_disable(); /* Disable watchdog until first use */
c81b2996 401
f9a8c891
MJ
402 if (wdt_set_heartbeat(timeout)) {
403 wdt_set_heartbeat(WATCHDOG_TIMEOUT);
eb64419e 404 printk (KERN_INFO PFX "timeout value must be 1<=timeout<=255, using %d\n",
f9a8c891
MJ
405 WATCHDOG_TIMEOUT);
406 }
407
f9a8c891
MJ
408 ret = register_reboot_notifier(&wdt_notifier);
409 if (ret != 0) {
410 printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
411 ret);
412 goto unreg_regions;
413 }
414
415 ret = misc_register(&wdt_miscdev);
416 if (ret != 0) {
417 printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
418 WATCHDOG_MINOR, ret);
419 goto unreg_reboot;
420 }
421
422 printk (KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n",
423 timeout, nowayout);
424
425out:
426 return ret;
427unreg_reboot:
428 unregister_reboot_notifier(&wdt_notifier);
429unreg_regions:
b41a9f59 430 release_region(wdt_io, 2);
f9a8c891
MJ
431 goto out;
432}
433
434static void __exit
435wdt_exit(void)
436{
437 misc_deregister(&wdt_miscdev);
438 unregister_reboot_notifier(&wdt_notifier);
b41a9f59 439 release_region(wdt_io, 2);
f9a8c891
MJ
440}
441
442module_init(wdt_init);
443module_exit(wdt_exit);
444
445MODULE_LICENSE("GPL");
3fdee8db 446MODULE_AUTHOR("Marcus Junker <junker@anduras.de>, Samuel Tardieu <sam@rfc1149.net>");
de710d68 447MODULE_DESCRIPTION("w83697hf/hg WDT driver");
f9a8c891 448MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);