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