1 /* 2 * IT8712F "Smart Guardian" Watchdog support 3 * 4 * Copyright (c) 2006-2007 Jorge Boncompte - DTI2 <jorge@dti2.net> 5 * 6 * Based on info and code taken from: 7 * 8 * drivers/char/watchdog/scx200_wdt.c 9 * drivers/hwmon/it87.c 10 * IT8712F EC-LPC I/O Preliminary Specification 0.8.2 11 * IT8712F EC-LPC I/O Preliminary Specification 0.9.3 12 * 13 * This program is free software; you can redistribute it and/or 14 * modify it under the terms of the GNU General Public License as 15 * published by the Free Software Foundation; either version 2 of the 16 * License, or (at your option) any later version. 17 * 18 * The author(s) of this software shall not be held liable for damages 19 * of any nature resulting due to the use of this software. This 20 * software is provided AS-IS with no warranties. 21 */ 22 23 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 24 25 #include <linux/module.h> 26 #include <linux/moduleparam.h> 27 #include <linux/init.h> 28 #include <linux/miscdevice.h> 29 #include <linux/watchdog.h> 30 #include <linux/notifier.h> 31 #include <linux/reboot.h> 32 #include <linux/fs.h> 33 #include <linux/spinlock.h> 34 #include <linux/uaccess.h> 35 #include <linux/io.h> 36 #include <linux/ioport.h> 37 38 #define DEBUG 39 #define NAME "it8712f_wdt" 40 41 MODULE_AUTHOR("Jorge Boncompte - DTI2 <jorge@dti2.net>"); 42 MODULE_DESCRIPTION("IT8712F Watchdog Driver"); 43 MODULE_LICENSE("GPL"); 44 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 45 46 static int max_units = 255; 47 static int margin = 60; /* in seconds */ 48 module_param(margin, int, 0); 49 MODULE_PARM_DESC(margin, "Watchdog margin in seconds"); 50 51 static bool nowayout = WATCHDOG_NOWAYOUT; 52 module_param(nowayout, bool, 0); 53 MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close"); 54 55 static unsigned long wdt_open; 56 static unsigned expect_close; 57 static unsigned char revision; 58 59 /* Dog Food address - We use the game port address */ 60 static unsigned short address; 61 62 #define REG 0x2e /* The register to read/write */ 63 #define VAL 0x2f /* The value to read/write */ 64 65 #define LDN 0x07 /* Register: Logical device select */ 66 #define DEVID 0x20 /* Register: Device ID */ 67 #define DEVREV 0x22 /* Register: Device Revision */ 68 #define ACT_REG 0x30 /* LDN Register: Activation */ 69 #define BASE_REG 0x60 /* LDN Register: Base address */ 70 71 #define IT8712F_DEVID 0x8712 72 73 #define LDN_GPIO 0x07 /* GPIO and Watch Dog Timer */ 74 #define LDN_GAME 0x09 /* Game Port */ 75 76 #define WDT_CONTROL 0x71 /* WDT Register: Control */ 77 #define WDT_CONFIG 0x72 /* WDT Register: Configuration */ 78 #define WDT_TIMEOUT 0x73 /* WDT Register: Timeout Value */ 79 80 #define WDT_RESET_GAME 0x10 /* Reset timer on read or write to game port */ 81 #define WDT_RESET_KBD 0x20 /* Reset timer on keyboard interrupt */ 82 #define WDT_RESET_MOUSE 0x40 /* Reset timer on mouse interrupt */ 83 #define WDT_RESET_CIR 0x80 /* Reset timer on consumer IR interrupt */ 84 85 #define WDT_UNIT_SEC 0x80 /* If 0 in MINUTES */ 86 87 #define WDT_OUT_PWROK 0x10 /* Pulse PWROK on timeout */ 88 #define WDT_OUT_KRST 0x40 /* Pulse reset on timeout */ 89 90 static int wdt_control_reg = WDT_RESET_GAME; 91 module_param(wdt_control_reg, int, 0); 92 MODULE_PARM_DESC(wdt_control_reg, "Value to write to watchdog control " 93 "register. The default WDT_RESET_GAME resets the timer on " 94 "game port reads that this driver generates. You can also " 95 "use KBD, MOUSE or CIR if you have some external way to " 96 "generate those interrupts."); 97 98 static int superio_inb(int reg) 99 { 100 outb(reg, REG); 101 return inb(VAL); 102 } 103 104 static void superio_outb(int val, int reg) 105 { 106 outb(reg, REG); 107 outb(val, VAL); 108 } 109 110 static int superio_inw(int reg) 111 { 112 int val; 113 outb(reg++, REG); 114 val = inb(VAL) << 8; 115 outb(reg, REG); 116 val |= inb(VAL); 117 return val; 118 } 119 120 static inline void superio_select(int ldn) 121 { 122 outb(LDN, REG); 123 outb(ldn, VAL); 124 } 125 126 static inline int superio_enter(void) 127 { 128 /* 129 * Try to reserve REG and REG + 1 for exclusive access. 130 */ 131 if (!request_muxed_region(REG, 2, NAME)) 132 return -EBUSY; 133 134 outb(0x87, REG); 135 outb(0x01, REG); 136 outb(0x55, REG); 137 outb(0x55, REG); 138 return 0; 139 } 140 141 static inline void superio_exit(void) 142 { 143 outb(0x02, REG); 144 outb(0x02, VAL); 145 release_region(REG, 2); 146 } 147 148 static inline void it8712f_wdt_ping(void) 149 { 150 if (wdt_control_reg & WDT_RESET_GAME) 151 inb(address); 152 } 153 154 static void it8712f_wdt_update_margin(void) 155 { 156 int config = WDT_OUT_KRST | WDT_OUT_PWROK; 157 int units = margin; 158 159 /* Switch to minutes precision if the configured margin 160 * value does not fit within the register width. 161 */ 162 if (units <= max_units) { 163 config |= WDT_UNIT_SEC; /* else UNIT is MINUTES */ 164 pr_info("timer margin %d seconds\n", units); 165 } else { 166 units /= 60; 167 pr_info("timer margin %d minutes\n", units); 168 } 169 superio_outb(config, WDT_CONFIG); 170 171 if (revision >= 0x08) 172 superio_outb(units >> 8, WDT_TIMEOUT + 1); 173 superio_outb(units, WDT_TIMEOUT); 174 } 175 176 static int it8712f_wdt_get_status(void) 177 { 178 if (superio_inb(WDT_CONTROL) & 0x01) 179 return WDIOF_CARDRESET; 180 else 181 return 0; 182 } 183 184 static int it8712f_wdt_enable(void) 185 { 186 int ret = superio_enter(); 187 if (ret) 188 return ret; 189 190 pr_debug("enabling watchdog timer\n"); 191 superio_select(LDN_GPIO); 192 193 superio_outb(wdt_control_reg, WDT_CONTROL); 194 195 it8712f_wdt_update_margin(); 196 197 superio_exit(); 198 199 it8712f_wdt_ping(); 200 201 return 0; 202 } 203 204 static int it8712f_wdt_disable(void) 205 { 206 int ret = superio_enter(); 207 if (ret) 208 return ret; 209 210 pr_debug("disabling watchdog timer\n"); 211 superio_select(LDN_GPIO); 212 213 superio_outb(0, WDT_CONFIG); 214 superio_outb(0, WDT_CONTROL); 215 if (revision >= 0x08) 216 superio_outb(0, WDT_TIMEOUT + 1); 217 superio_outb(0, WDT_TIMEOUT); 218 219 superio_exit(); 220 return 0; 221 } 222 223 static int it8712f_wdt_notify(struct notifier_block *this, 224 unsigned long code, void *unused) 225 { 226 if (code == SYS_HALT || code == SYS_POWER_OFF) 227 if (!nowayout) 228 it8712f_wdt_disable(); 229 230 return NOTIFY_DONE; 231 } 232 233 static struct notifier_block it8712f_wdt_notifier = { 234 .notifier_call = it8712f_wdt_notify, 235 }; 236 237 static ssize_t it8712f_wdt_write(struct file *file, const char __user *data, 238 size_t len, loff_t *ppos) 239 { 240 /* check for a magic close character */ 241 if (len) { 242 size_t i; 243 244 it8712f_wdt_ping(); 245 246 expect_close = 0; 247 for (i = 0; i < len; ++i) { 248 char c; 249 if (get_user(c, data + i)) 250 return -EFAULT; 251 if (c == 'V') 252 expect_close = 42; 253 } 254 } 255 256 return len; 257 } 258 259 static long it8712f_wdt_ioctl(struct file *file, unsigned int cmd, 260 unsigned long arg) 261 { 262 void __user *argp = (void __user *)arg; 263 int __user *p = argp; 264 static const struct watchdog_info ident = { 265 .identity = "IT8712F Watchdog", 266 .firmware_version = 1, 267 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | 268 WDIOF_MAGICCLOSE, 269 }; 270 int value; 271 int ret; 272 273 switch (cmd) { 274 case WDIOC_GETSUPPORT: 275 if (copy_to_user(argp, &ident, sizeof(ident))) 276 return -EFAULT; 277 return 0; 278 case WDIOC_GETSTATUS: 279 ret = superio_enter(); 280 if (ret) 281 return ret; 282 superio_select(LDN_GPIO); 283 284 value = it8712f_wdt_get_status(); 285 286 superio_exit(); 287 288 return put_user(value, p); 289 case WDIOC_GETBOOTSTATUS: 290 return put_user(0, p); 291 case WDIOC_KEEPALIVE: 292 it8712f_wdt_ping(); 293 return 0; 294 case WDIOC_SETTIMEOUT: 295 if (get_user(value, p)) 296 return -EFAULT; 297 if (value < 1) 298 return -EINVAL; 299 if (value > (max_units * 60)) 300 return -EINVAL; 301 margin = value; 302 ret = superio_enter(); 303 if (ret) 304 return ret; 305 superio_select(LDN_GPIO); 306 307 it8712f_wdt_update_margin(); 308 309 superio_exit(); 310 it8712f_wdt_ping(); 311 /* Fall through */ 312 case WDIOC_GETTIMEOUT: 313 if (put_user(margin, p)) 314 return -EFAULT; 315 return 0; 316 default: 317 return -ENOTTY; 318 } 319 } 320 321 static int it8712f_wdt_open(struct inode *inode, struct file *file) 322 { 323 int ret; 324 /* only allow one at a time */ 325 if (test_and_set_bit(0, &wdt_open)) 326 return -EBUSY; 327 328 ret = it8712f_wdt_enable(); 329 if (ret) 330 return ret; 331 return nonseekable_open(inode, file); 332 } 333 334 static int it8712f_wdt_release(struct inode *inode, struct file *file) 335 { 336 if (expect_close != 42) { 337 pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n"); 338 } else if (!nowayout) { 339 if (it8712f_wdt_disable()) 340 pr_warn("Watchdog disable failed\n"); 341 } 342 expect_close = 0; 343 clear_bit(0, &wdt_open); 344 345 return 0; 346 } 347 348 static const struct file_operations it8712f_wdt_fops = { 349 .owner = THIS_MODULE, 350 .llseek = no_llseek, 351 .write = it8712f_wdt_write, 352 .unlocked_ioctl = it8712f_wdt_ioctl, 353 .open = it8712f_wdt_open, 354 .release = it8712f_wdt_release, 355 }; 356 357 static struct miscdevice it8712f_wdt_miscdev = { 358 .minor = WATCHDOG_MINOR, 359 .name = "watchdog", 360 .fops = &it8712f_wdt_fops, 361 }; 362 363 static int __init it8712f_wdt_find(unsigned short *address) 364 { 365 int err = -ENODEV; 366 int chip_type; 367 int ret = superio_enter(); 368 if (ret) 369 return ret; 370 371 chip_type = superio_inw(DEVID); 372 if (chip_type != IT8712F_DEVID) 373 goto exit; 374 375 superio_select(LDN_GAME); 376 superio_outb(1, ACT_REG); 377 if (!(superio_inb(ACT_REG) & 0x01)) { 378 pr_err("Device not activated, skipping\n"); 379 goto exit; 380 } 381 382 *address = superio_inw(BASE_REG); 383 if (*address == 0) { 384 pr_err("Base address not set, skipping\n"); 385 goto exit; 386 } 387 388 err = 0; 389 revision = superio_inb(DEVREV) & 0x0f; 390 391 /* Later revisions have 16-bit values per datasheet 0.9.1 */ 392 if (revision >= 0x08) 393 max_units = 65535; 394 395 if (margin > (max_units * 60)) 396 margin = (max_units * 60); 397 398 pr_info("Found IT%04xF chip revision %d - using DogFood address 0x%x\n", 399 chip_type, revision, *address); 400 401 exit: 402 superio_exit(); 403 return err; 404 } 405 406 static int __init it8712f_wdt_init(void) 407 { 408 int err = 0; 409 410 if (it8712f_wdt_find(&address)) 411 return -ENODEV; 412 413 if (!request_region(address, 1, "IT8712F Watchdog")) { 414 pr_warn("watchdog I/O region busy\n"); 415 return -EBUSY; 416 } 417 418 err = it8712f_wdt_disable(); 419 if (err) { 420 pr_err("unable to disable watchdog timer\n"); 421 goto out; 422 } 423 424 err = register_reboot_notifier(&it8712f_wdt_notifier); 425 if (err) { 426 pr_err("unable to register reboot notifier\n"); 427 goto out; 428 } 429 430 err = misc_register(&it8712f_wdt_miscdev); 431 if (err) { 432 pr_err("cannot register miscdev on minor=%d (err=%d)\n", 433 WATCHDOG_MINOR, err); 434 goto reboot_out; 435 } 436 437 return 0; 438 439 440 reboot_out: 441 unregister_reboot_notifier(&it8712f_wdt_notifier); 442 out: 443 release_region(address, 1); 444 return err; 445 } 446 447 static void __exit it8712f_wdt_exit(void) 448 { 449 misc_deregister(&it8712f_wdt_miscdev); 450 unregister_reboot_notifier(&it8712f_wdt_notifier); 451 release_region(address, 1); 452 } 453 454 module_init(it8712f_wdt_init); 455 module_exit(it8712f_wdt_exit); 456