1/* 2 * w83697ug/uf WDT driver 3 * 4 * (c) Copyright 2008 Flemming Fransen <ff@nrvissing.net> 5 * reused original code to support w83697ug/uf. 6 * 7 * Based on w83627hf_wdt.c which is based on advantechwdt.c 8 * which is based on wdt.c. 9 * Original copyright messages: 10 * 11 * (c) Copyright 2007 Vlad Drukker <vlad@storewiz.com> 12 * added support for W83627THF. 13 * 14 * (c) Copyright 2003 P��draig Brady <P@draigBrady.com> 15 * 16 * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl> 17 * 18 * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. 19 * http://www.redhat.com 20 * 21 * This program is free software; you can redistribute it and/or 22 * modify it under the terms of the GNU General Public License 23 * as published by the Free Software Foundation; either version 24 * 2 of the License, or (at your option) any later version. 25 * 26 * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide 27 * warranty for any of this software. This material is provided 28 * "AS-IS" and at no charge. 29 * 30 * (c) Copyright 1995 Alan Cox <alan@redhat.com> 31 */ 32 33#include <linux/module.h> 34#include <linux/moduleparam.h> 35#include <linux/types.h> 36#include <linux/miscdevice.h> 37#include <linux/watchdog.h> 38#include <linux/fs.h> 39#include <linux/ioport.h> 40#include <linux/notifier.h> 41#include <linux/reboot.h> 42#include <linux/init.h> 43#include <linux/spinlock.h> 44#include <linux/io.h> 45#include <linux/uaccess.h> 46 47#include <asm/system.h> 48 49#define WATCHDOG_NAME "w83697ug/uf WDT" 50#define PFX WATCHDOG_NAME ": " 51#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ 52 53static unsigned long wdt_is_open; 54static char expect_close; 55static DEFINE_SPINLOCK(io_lock); 56 57static int wdt_io = 0x2e; 58module_param(wdt_io, int, 0); 59MODULE_PARM_DESC(wdt_io, "w83697ug/uf WDT io port (default 0x2e)"); 60 61static int timeout = WATCHDOG_TIMEOUT; /* in seconds */ 62module_param(timeout, int, 0); 63MODULE_PARM_DESC(timeout, 64 "Watchdog timeout in seconds. 1<= timeout <=255 (default=" 65 __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); 66 67static int nowayout = WATCHDOG_NOWAYOUT; 68module_param(nowayout, int, 0); 69MODULE_PARM_DESC(nowayout, 70 "Watchdog cannot be stopped once started (default=" 71 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 72 73/* 74 * Kernel methods. 75 */ 76 77#define WDT_EFER (wdt_io+0) /* Extended Function Enable Registers */ 78#define WDT_EFIR (wdt_io+0) /* Extended Function Index Register 79 (same as EFER) */ 80#define WDT_EFDR (WDT_EFIR+1) /* Extended Function Data Register */ 81 82static int w83697ug_select_wd_register(void) 83{ 84 unsigned char c; 85 unsigned char version; 86 87 outb_p(0x87, WDT_EFER); /* Enter extended function mode */ 88 outb_p(0x87, WDT_EFER); /* Again according to manual */ 89 90 outb(0x20, WDT_EFER); /* check chip version */ 91 version = inb(WDT_EFDR); 92 93 if (version == 0x68) { /* W83697UG */ 94 printk(KERN_INFO PFX "Watchdog chip version 0x%02x = " 95 "W83697UG/UF found at 0x%04x\n", version, wdt_io); 96 97 outb_p(0x2b, WDT_EFER); 98 c = inb_p(WDT_EFDR); /* select WDT0 */ 99 c &= ~0x04; 100 outb_p(0x2b, WDT_EFER); 101 outb_p(c, WDT_EFDR); /* set pin118 to WDT0 */ 102 103 } else { 104 printk(KERN_ERR PFX "No W83697UG/UF could be found\n"); 105 return -ENODEV; 106 } 107 108 outb_p(0x07, WDT_EFER); /* point to logical device number reg */ 109 outb_p(0x08, WDT_EFDR); /* select logical device 8 (GPIO2) */ 110 outb_p(0x30, WDT_EFER); /* select CR30 */ 111 c = inb_p(WDT_EFDR); 112 outb_p(c || 0x01, WDT_EFDR); /* set bit 0 to activate GPIO2 */ 113 114 return 0; 115} 116 117static void w83697ug_unselect_wd_register(void) 118{ 119 outb_p(0xAA, WDT_EFER); /* Leave extended function mode */ 120} 121 122static int w83697ug_init(void) 123{ 124 int ret; 125 unsigned char t; 126 127 ret = w83697ug_select_wd_register(); 128 if (ret != 0) 129 return ret; 130 131 outb_p(0xF6, WDT_EFER); /* Select CRF6 */ 132 t = inb_p(WDT_EFDR); /* read CRF6 */ 133 if (t != 0) { 134 printk(KERN_INFO PFX "Watchdog already running." 135 " Resetting timeout to %d sec\n", timeout); 136 outb_p(timeout, WDT_EFDR); /* Write back to CRF6 */ 137 } 138 outb_p(0xF5, WDT_EFER); /* Select CRF5 */ 139 t = inb_p(WDT_EFDR); /* read CRF5 */ 140 t &= ~0x0C; /* set second mode & 141 disable keyboard turning off watchdog */ 142 outb_p(t, WDT_EFDR); /* Write back to CRF5 */ 143 144 w83697ug_unselect_wd_register(); 145 return 0; 146} 147 148static void wdt_ctrl(int timeout) 149{ 150 spin_lock(&io_lock); 151 152 if (w83697ug_select_wd_register() < 0) { 153 spin_unlock(&io_lock); 154 return; 155 } 156 157 outb_p(0xF4, WDT_EFER); /* Select CRF4 */ 158 outb_p(timeout, WDT_EFDR); /* Write Timeout counter to CRF4 */ 159 160 w83697ug_unselect_wd_register(); 161 162 spin_unlock(&io_lock); 163} 164 165static int wdt_ping(void) 166{ 167 wdt_ctrl(timeout); 168 return 0; 169} 170 171static int wdt_disable(void) 172{ 173 wdt_ctrl(0); 174 return 0; 175} 176 177static int wdt_set_heartbeat(int t) 178{ 179 if (t < 1 || t > 255) 180 return -EINVAL; 181 182 timeout = t; 183 return 0; 184} 185 186static ssize_t wdt_write(struct file *file, const char __user *buf, 187 size_t count, loff_t *ppos) 188{ 189 if (count) { 190 if (!nowayout) { 191 size_t i; 192 193 expect_close = 0; 194 195 for (i = 0; i != count; i++) { 196 char c; 197 if (get_user(c, buf + i)) 198 return -EFAULT; 199 if (c == 'V') 200 expect_close = 42; 201 } 202 } 203 wdt_ping(); 204 } 205 return count; 206} 207 208static long wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) 209{ 210 void __user *argp = (void __user *)arg; 211 int __user *p = argp; 212 int new_timeout; 213 static const struct watchdog_info ident = { 214 .options = WDIOF_KEEPALIVEPING | 215 WDIOF_SETTIMEOUT | 216 WDIOF_MAGICCLOSE, 217 .firmware_version = 1, 218 .identity = "W83697UG WDT", 219 }; 220 221 switch (cmd) { 222 case WDIOC_GETSUPPORT: 223 if (copy_to_user(argp, &ident, sizeof(ident))) 224 return -EFAULT; 225 break; 226 227 case WDIOC_GETSTATUS: 228 case WDIOC_GETBOOTSTATUS: 229 return put_user(0, p); 230 231 case WDIOC_SETOPTIONS: 232 { 233 int options, retval = -EINVAL; 234 235 if (get_user(options, p)) 236 return -EFAULT; 237 238 if (options & WDIOS_DISABLECARD) { 239 wdt_disable(); 240 retval = 0; 241 } 242 243 if (options & WDIOS_ENABLECARD) { 244 wdt_ping(); 245 retval = 0; 246 } 247 248 return retval; 249 } 250 251 case WDIOC_KEEPALIVE: 252 wdt_ping(); 253 break; 254 255 case WDIOC_SETTIMEOUT: 256 if (get_user(new_timeout, p)) 257 return -EFAULT; 258 if (wdt_set_heartbeat(new_timeout)) 259 return -EINVAL; 260 wdt_ping(); 261 /* Fall */ 262 263 case WDIOC_GETTIMEOUT: 264 return put_user(timeout, p); 265 266 default: 267 return -ENOTTY; 268 } 269 return 0; 270} 271 272static int wdt_open(struct inode *inode, struct file *file) 273{ 274 if (test_and_set_bit(0, &wdt_is_open)) 275 return -EBUSY; 276 /* 277 * Activate 278 */ 279 280 wdt_ping(); 281 return nonseekable_open(inode, file); 282} 283 284static int wdt_close(struct inode *inode, struct file *file) 285{ 286 if (expect_close == 42) 287 wdt_disable(); 288 else { 289 printk(KERN_CRIT PFX 290 "Unexpected close, not stopping watchdog!\n"); 291 wdt_ping(); 292 } 293 expect_close = 0; 294 clear_bit(0, &wdt_is_open); 295 return 0; 296} 297 298/* 299 * Notifier for system down 300 */ 301 302static int wdt_notify_sys(struct notifier_block *this, unsigned long code, 303 void *unused) 304{ 305 if (code == SYS_DOWN || code == SYS_HALT) 306 wdt_disable(); /* Turn the WDT off */ 307 308 return NOTIFY_DONE; 309} 310 311/* 312 * Kernel Interfaces 313 */ 314 315static const struct file_operations wdt_fops = { 316 .owner = THIS_MODULE, 317 .llseek = no_llseek, 318 .write = wdt_write, 319 .unlocked_ioctl = wdt_ioctl, 320 .open = wdt_open, 321 .release = wdt_close, 322}; 323 324static struct miscdevice wdt_miscdev = { 325 .minor = WATCHDOG_MINOR, 326 .name = "watchdog", 327 .fops = &wdt_fops, 328}; 329 330/* 331 * The WDT needs to learn about soft shutdowns in order to 332 * turn the timebomb registers off. 333 */ 334 335static struct notifier_block wdt_notifier = { 336 .notifier_call = wdt_notify_sys, 337}; 338 339static int __init wdt_init(void) 340{ 341 int ret; 342 343 printk(KERN_INFO "WDT driver for the Winbond(TM) W83697UG/UF Super I/O chip initialising.\n"); 344 345 if (wdt_set_heartbeat(timeout)) { 346 wdt_set_heartbeat(WATCHDOG_TIMEOUT); 347 printk(KERN_INFO PFX 348 "timeout value must be 1<=timeout<=255, using %d\n", 349 WATCHDOG_TIMEOUT); 350 } 351 352 if (!request_region(wdt_io, 1, WATCHDOG_NAME)) { 353 printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", 354 wdt_io); 355 ret = -EIO; 356 goto out; 357 } 358 359 ret = w83697ug_init(); 360 if (ret != 0) 361 goto unreg_regions; 362 363 ret = register_reboot_notifier(&wdt_notifier); 364 if (ret != 0) { 365 printk(KERN_ERR PFX 366 "cannot register reboot notifier (err=%d)\n", ret); 367 goto unreg_regions; 368 } 369 370 ret = misc_register(&wdt_miscdev); 371 if (ret != 0) { 372 printk(KERN_ERR PFX 373 "cannot register miscdev on minor=%d (err=%d)\n", 374 WATCHDOG_MINOR, ret); 375 goto unreg_reboot; 376 } 377 378 printk(KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n", 379 timeout, nowayout); 380 381out: 382 return ret; 383unreg_reboot: 384 unregister_reboot_notifier(&wdt_notifier); 385unreg_regions: 386 release_region(wdt_io, 1); 387 goto out; 388} 389 390static void __exit wdt_exit(void) 391{ 392 misc_deregister(&wdt_miscdev); 393 unregister_reboot_notifier(&wdt_notifier); 394 release_region(wdt_io, 1); 395} 396 397module_init(wdt_init); 398module_exit(wdt_exit); 399 400MODULE_LICENSE("GPL"); 401MODULE_AUTHOR("Flemming Frandsen <ff@nrvissing.net>"); 402MODULE_DESCRIPTION("w83697ug/uf WDT driver"); 403MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 404