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