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