diff options
Diffstat (limited to 'drivers/watchdog/it87_wdt.c')
| -rw-r--r-- | drivers/watchdog/it87_wdt.c | 364 |
1 files changed, 217 insertions, 147 deletions
diff --git a/drivers/watchdog/it87_wdt.c b/drivers/watchdog/it87_wdt.c index cc133c531d0..0b93739c010 100644 --- a/drivers/watchdog/it87_wdt.c +++ b/drivers/watchdog/it87_wdt.c @@ -12,7 +12,8 @@ * http://www.ite.com.tw/ * * Support of the watchdog timers, which are available on - * IT8716, IT8718, IT8726 and IT8712 (J,K version). + * IT8702, IT8712, IT8716, IT8718, IT8720, IT8721, IT8726 + * and IT8728. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -29,6 +30,8 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/types.h> @@ -43,18 +46,17 @@ #include <linux/uaccess.h> #include <linux/io.h> -#include <asm/system.h> -#define WATCHDOG_VERSION "1.12" +#define WATCHDOG_VERSION "1.14" #define WATCHDOG_NAME "IT87 WDT" -#define PFX WATCHDOG_NAME ": " #define DRIVER_VERSION WATCHDOG_NAME " driver, v" WATCHDOG_VERSION "\n" #define WD_MAGIC 'V' /* Defaults for Module Parameter */ #define DEFAULT_NOGAMEPORT 0 +#define DEFAULT_NOCIR 0 #define DEFAULT_EXCLUSIVE 1 -#define DEFAULT_TIMEOUT 60 +#define DEFAULT_TIMEOUT 60 #define DEFAULT_TESTMODE 0 #define DEFAULT_NOWAYOUT WATCHDOG_NOWAYOUT @@ -70,20 +72,24 @@ /* Configuration Registers and Functions */ #define LDNREG 0x07 #define CHIPID 0x20 -#define CHIPREV 0x22 +#define CHIPREV 0x22 #define ACTREG 0x30 -#define BASEREG 0x60 +#define BASEREG 0x60 /* Chip Id numbers */ #define NO_DEV_ID 0xffff +#define IT8702_ID 0x8702 #define IT8705_ID 0x8705 #define IT8712_ID 0x8712 #define IT8716_ID 0x8716 #define IT8718_ID 0x8718 +#define IT8720_ID 0x8720 +#define IT8721_ID 0x8721 #define IT8726_ID 0x8726 /* the data sheet suggest wrongly 0x8716 */ +#define IT8728_ID 0x8728 /* GPIO Configuration Registers LDN=0x07 */ -#define WDTCTRL 0x71 +#define WDTCTRL 0x71 #define WDTCFG 0x72 #define WDTVALLSB 0x73 #define WDTVALMSB 0x74 @@ -92,7 +98,7 @@ #define WDT_CIRINT 0x80 #define WDT_MOUSEINT 0x40 #define WDT_KYBINT 0x20 -#define WDT_GAMEPORT 0x10 /* not it8718 */ +#define WDT_GAMEPORT 0x10 /* not in it8718, it8720, it8721, it8728 */ #define WDT_FORCE 0x02 #define WDT_ZERO 0x01 @@ -100,11 +106,11 @@ #define WDT_TOV1 0x80 #define WDT_KRST 0x40 #define WDT_TOVE 0x20 -#define WDT_PWROK 0x10 +#define WDT_PWROK 0x10 /* not in it8721 */ #define WDT_INT_MASK 0x0f /* CIR Configuration Register LDN=0x0a */ -#define CIR_ILS 0x70 +#define CIR_ILS 0x70 /* The default Base address is not always available, we use this */ #define CIR_BASE 0x0208 @@ -131,20 +137,24 @@ #define WDTS_LOCKED 3 #define WDTS_USE_GP 4 #define WDTS_EXPECTED 5 +#define WDTS_USE_CIR 6 -static unsigned int base, gpact, ciract; +static unsigned int base, gpact, ciract, max_units, chip_type; static unsigned long wdt_status; -static DEFINE_SPINLOCK(spinlock); static int nogameport = DEFAULT_NOGAMEPORT; +static int nocir = DEFAULT_NOCIR; static int exclusive = DEFAULT_EXCLUSIVE; static int timeout = DEFAULT_TIMEOUT; static int testmode = DEFAULT_TESTMODE; -static int nowayout = DEFAULT_NOWAYOUT; +static bool nowayout = DEFAULT_NOWAYOUT; module_param(nogameport, int, 0); MODULE_PARM_DESC(nogameport, "Forbid the activation of game port, default=" __MODULE_STRING(DEFAULT_NOGAMEPORT)); +module_param(nocir, int, 0); +MODULE_PARM_DESC(nocir, "Forbid the use of Consumer IR interrupts to reset timer, default=" + __MODULE_STRING(DEFAULT_NOCIR)); module_param(exclusive, int, 0); MODULE_PARM_DESC(exclusive, "Watchdog exclusive device open, default=" __MODULE_STRING(DEFAULT_EXCLUSIVE)); @@ -154,24 +164,32 @@ MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds, default=" module_param(testmode, int, 0); MODULE_PARM_DESC(testmode, "Watchdog test mode (1 = no reboot), default=" __MODULE_STRING(DEFAULT_TESTMODE)); -module_param(nowayout, int, 0); +module_param(nowayout, bool, 0); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started, default=" __MODULE_STRING(WATCHDOG_NOWAYOUT)); /* Superio Chip */ -static inline void superio_enter(void) +static inline int superio_enter(void) { + /* + * Try to reserve REG and REG + 1 for exclusive access. + */ + if (!request_muxed_region(REG, 2, WATCHDOG_NAME)) + return -EBUSY; + outb(0x87, REG); outb(0x01, REG); outb(0x55, REG); outb(0x55, REG); + return 0; } static inline void superio_exit(void) { outb(0x02, REG); outb(0x02, VAL); + release_region(REG, 2); } static inline void superio_select(int ldn) @@ -210,88 +228,120 @@ static inline void superio_outw(int val, int reg) outb(val, VAL); } +/* Internal function, should be called after superio_select(GPIO) */ +static void wdt_update_timeout(void) +{ + unsigned char cfg = WDT_KRST; + int tm = timeout; + + if (testmode) + cfg = 0; + + if (tm <= max_units) + cfg |= WDT_TOV1; + else + tm /= 60; + + if (chip_type != IT8721_ID) + cfg |= WDT_PWROK; + + superio_outb(cfg, WDTCFG); + superio_outb(tm, WDTVALLSB); + if (max_units > 255) + superio_outb(tm>>8, WDTVALMSB); +} + +static int wdt_round_time(int t) +{ + t += 59; + t -= t % 60; + return t; +} + /* watchdog timer handling */ static void wdt_keepalive(void) { if (test_bit(WDTS_USE_GP, &wdt_status)) inb(base); - else + else if (test_bit(WDTS_USE_CIR, &wdt_status)) /* The timer reloads with around 5 msec delay */ outb(0x55, CIR_DR(base)); + else { + if (superio_enter()) + return; + + superio_select(GPIO); + wdt_update_timeout(); + superio_exit(); + } set_bit(WDTS_KEEPALIVE, &wdt_status); } -static void wdt_start(void) +static int wdt_start(void) { - unsigned long flags; - - spin_lock_irqsave(&spinlock, flags); - superio_enter(); + int ret = superio_enter(); + if (ret) + return ret; superio_select(GPIO); if (test_bit(WDTS_USE_GP, &wdt_status)) superio_outb(WDT_GAMEPORT, WDTCTRL); - else + else if (test_bit(WDTS_USE_CIR, &wdt_status)) superio_outb(WDT_CIRINT, WDTCTRL); - if (!testmode) - superio_outb(WDT_TOV1 | WDT_KRST | WDT_PWROK, WDTCFG); - else - superio_outb(WDT_TOV1, WDTCFG); - superio_outb(timeout>>8, WDTVALMSB); - superio_outb(timeout, WDTVALLSB); + wdt_update_timeout(); superio_exit(); - spin_unlock_irqrestore(&spinlock, flags); + + return 0; } -static void wdt_stop(void) +static int wdt_stop(void) { - unsigned long flags; - - spin_lock_irqsave(&spinlock, flags); - superio_enter(); + int ret = superio_enter(); + if (ret) + return ret; superio_select(GPIO); superio_outb(0x00, WDTCTRL); superio_outb(WDT_TOV1, WDTCFG); - superio_outb(0x00, WDTVALMSB); superio_outb(0x00, WDTVALLSB); + if (max_units > 255) + superio_outb(0x00, WDTVALMSB); superio_exit(); - spin_unlock_irqrestore(&spinlock, flags); + return 0; } /** * wdt_set_timeout - set a new timeout value with watchdog ioctl * @t: timeout value in seconds * - * The hardware device has a 16 bit watchdog timer, thus the - * timeout time ranges between 1 and 65535 seconds. + * The hardware device has a 8 or 16 bit watchdog timer (depends on + * chip version) that can be configured to count seconds or minutes. * * Used within WDIOC_SETTIMEOUT watchdog device ioctl. */ static int wdt_set_timeout(int t) { - unsigned long flags; - - if (t < 1 || t > 65535) + if (t < 1 || t > max_units * 60) return -EINVAL; - timeout = t; + if (t > max_units) + timeout = wdt_round_time(t); + else + timeout = t; - spin_lock_irqsave(&spinlock, flags); if (test_bit(WDTS_TIMER_RUN, &wdt_status)) { - superio_enter(); + int ret = superio_enter(); + if (ret) + return ret; superio_select(GPIO); - superio_outb(t>>8, WDTVALMSB); - superio_outb(t, WDTVALLSB); - + wdt_update_timeout(); superio_exit(); } - spin_unlock_irqrestore(&spinlock, flags); return 0; } @@ -310,12 +360,12 @@ static int wdt_set_timeout(int t) static int wdt_get_status(int *status) { - unsigned long flags; - *status = 0; if (testmode) { - spin_lock_irqsave(&spinlock, flags); - superio_enter(); + int ret = superio_enter(); + if (ret) + return ret; + superio_select(GPIO); if (superio_inb(WDTCTRL) & WDT_ZERO) { superio_outb(0x00, WDTCTRL); @@ -324,7 +374,6 @@ static int wdt_get_status(int *status) } superio_exit(); - spin_unlock_irqrestore(&spinlock, flags); } if (test_and_clear_bit(WDTS_KEEPALIVE, &wdt_status)) *status |= WDIOF_KEEPALIVEPING; @@ -350,9 +399,17 @@ static int wdt_open(struct inode *inode, struct file *file) if (exclusive && test_and_set_bit(WDTS_DEV_OPEN, &wdt_status)) return -EBUSY; if (!test_and_set_bit(WDTS_TIMER_RUN, &wdt_status)) { + int ret; if (nowayout && !test_and_set_bit(WDTS_LOCKED, &wdt_status)) __module_get(THIS_MODULE); - wdt_start(); + + ret = wdt_start(); + if (ret) { + clear_bit(WDTS_LOCKED, &wdt_status); + clear_bit(WDTS_TIMER_RUN, &wdt_status); + clear_bit(WDTS_DEV_OPEN, &wdt_status); + return ret; + } } return nonseekable_open(inode, file); } @@ -374,12 +431,20 @@ static int wdt_release(struct inode *inode, struct file *file) { if (test_bit(WDTS_TIMER_RUN, &wdt_status)) { if (test_and_clear_bit(WDTS_EXPECTED, &wdt_status)) { - wdt_stop(); + int ret = wdt_stop(); + if (ret) { + /* + * Stop failed. Just keep the watchdog alive + * and hope nothing bad happens. + */ + set_bit(WDTS_EXPECTED, &wdt_status); + wdt_keepalive(); + return ret; + } clear_bit(WDTS_TIMER_RUN, &wdt_status); } else { wdt_keepalive(); - printk(KERN_CRIT PFX - "unexpected close, not stopping watchdog!\n"); + pr_crit("unexpected close, not stopping watchdog!\n"); } } clear_bit(WDTS_DEV_OPEN, &wdt_status); @@ -421,7 +486,7 @@ static ssize_t wdt_write(struct file *file, const char __user *buf, return count; } -static struct watchdog_info ident = { +static const struct watchdog_info ident = { .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, .firmware_version = 1, .identity = WATCHDOG_NAME, @@ -455,7 +520,9 @@ static long wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) &ident, sizeof(ident)) ? -EFAULT : 0; case WDIOC_GETSTATUS: - wdt_get_status(&status); + rc = wdt_get_status(&status); + if (rc) + return rc; return put_user(status, uarg.i); case WDIOC_GETBOOTSTATUS: @@ -471,14 +538,22 @@ static long wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) switch (new_options) { case WDIOS_DISABLECARD: - if (test_bit(WDTS_TIMER_RUN, &wdt_status)) - wdt_stop(); + if (test_bit(WDTS_TIMER_RUN, &wdt_status)) { + rc = wdt_stop(); + if (rc) + return rc; + } clear_bit(WDTS_TIMER_RUN, &wdt_status); return 0; case WDIOS_ENABLECARD: - if (!test_and_set_bit(WDTS_TIMER_RUN, &wdt_status)) - wdt_start(); + if (!test_and_set_bit(WDTS_TIMER_RUN, &wdt_status)) { + rc = wdt_start(); + if (rc) { + clear_bit(WDTS_TIMER_RUN, &wdt_status); + return rc; + } + } return 0; default: @@ -529,49 +604,61 @@ static struct notifier_block wdt_notifier = { static int __init it87_wdt_init(void) { int rc = 0; - u16 chip_type; + int try_gameport = !nogameport; u8 chip_rev; - unsigned long flags; + int gp_rreq_fail = 0; + + wdt_status = 0; + + rc = superio_enter(); + if (rc) + return rc; - spin_lock_irqsave(&spinlock, flags); - superio_enter(); chip_type = superio_inw(CHIPID); chip_rev = superio_inb(CHIPREV) & 0x0f; superio_exit(); - spin_unlock_irqrestore(&spinlock, flags); switch (chip_type) { + case IT8702_ID: + max_units = 255; + break; + case IT8712_ID: + max_units = (chip_rev < 8) ? 255 : 65535; + break; case IT8716_ID: - case IT8718_ID: case IT8726_ID: + max_units = 65535; + break; + case IT8718_ID: + case IT8720_ID: + case IT8721_ID: + case IT8728_ID: + max_units = 65535; + try_gameport = 0; break; - case IT8712_ID: - if (chip_rev > 7) - break; case IT8705_ID: - printk(KERN_ERR PFX - "Unsupported Chip found, Chip %04x Revision %02x\n", + pr_err("Unsupported Chip found, Chip %04x Revision %02x\n", chip_type, chip_rev); return -ENODEV; case NO_DEV_ID: - printk(KERN_ERR PFX "no device\n"); + pr_err("no device\n"); return -ENODEV; default: - printk(KERN_ERR PFX - "Unknown Chip found, Chip %04x Revision %04x\n", + pr_err("Unknown Chip found, Chip %04x Revision %04x\n", chip_type, chip_rev); return -ENODEV; } - spin_lock_irqsave(&spinlock, flags); - superio_enter(); + rc = superio_enter(); + if (rc) + return rc; superio_select(GPIO); superio_outb(WDT_TOV1, WDTCFG); superio_outb(0x00, WDTCTRL); /* First try to get Gameport support */ - if (chip_type != IT8718_ID && !nogameport) { + if (try_gameport) { superio_select(GAMEPORT); base = superio_inw(BASEREG); if (!base) { @@ -580,73 +667,62 @@ static int __init it87_wdt_init(void) } gpact = superio_inb(ACTREG); superio_outb(0x01, ACTREG); - superio_exit(); - spin_unlock_irqrestore(&spinlock, flags); if (request_region(base, 1, WATCHDOG_NAME)) set_bit(WDTS_USE_GP, &wdt_status); else - rc = -EIO; - } else { - superio_exit(); - spin_unlock_irqrestore(&spinlock, flags); + gp_rreq_fail = 1; } /* If we haven't Gameport support, try to get CIR support */ - if (!test_bit(WDTS_USE_GP, &wdt_status)) { + if (!nocir && !test_bit(WDTS_USE_GP, &wdt_status)) { if (!request_region(CIR_BASE, 8, WATCHDOG_NAME)) { - if (rc == -EIO) - printk(KERN_ERR PFX - "I/O Address 0x%04x and 0x%04x" - " already in use\n", base, CIR_BASE); + if (gp_rreq_fail) + pr_err("I/O Address 0x%04x and 0x%04x already in use\n", + base, CIR_BASE); else - printk(KERN_ERR PFX - "I/O Address 0x%04x already in use\n", - CIR_BASE); + pr_err("I/O Address 0x%04x already in use\n", + CIR_BASE); rc = -EIO; goto err_out; } base = CIR_BASE; - spin_lock_irqsave(&spinlock, flags); - superio_enter(); superio_select(CIR); superio_outw(base, BASEREG); superio_outb(0x00, CIR_ILS); ciract = superio_inb(ACTREG); superio_outb(0x01, ACTREG); - if (rc == -EIO) { + if (gp_rreq_fail) { superio_select(GAMEPORT); superio_outb(gpact, ACTREG); } - - superio_exit(); - spin_unlock_irqrestore(&spinlock, flags); + set_bit(WDTS_USE_CIR, &wdt_status); } - if (timeout < 1 || timeout > 65535) { + if (timeout < 1 || timeout > max_units * 60) { timeout = DEFAULT_TIMEOUT; - printk(KERN_WARNING PFX - "Timeout value out of range, use default %d sec\n", - DEFAULT_TIMEOUT); + pr_warn("Timeout value out of range, use default %d sec\n", + DEFAULT_TIMEOUT); } + if (timeout > max_units) + timeout = wdt_round_time(timeout); + rc = register_reboot_notifier(&wdt_notifier); if (rc) { - printk(KERN_ERR PFX - "Cannot register reboot notifier (err=%d)\n", rc); + pr_err("Cannot register reboot notifier (err=%d)\n", rc); goto err_out_region; } rc = misc_register(&wdt_miscdev); if (rc) { - printk(KERN_ERR PFX - "Cannot register miscdev on minor=%d (err=%d)\n", - wdt_miscdev.minor, rc); + pr_err("Cannot register miscdev on minor=%d (err=%d)\n", + wdt_miscdev.minor, rc); goto err_out_reboot; } /* Initialize CIR to use it as keepalive source */ - if (!test_bit(WDTS_USE_GP, &wdt_status)) { + if (test_bit(WDTS_USE_CIR, &wdt_status)) { outb(0x00, CIR_RCR(base)); outb(0xc0, CIR_TCR1(base)); outb(0x5c, CIR_TCR2(base)); @@ -656,64 +732,59 @@ static int __init it87_wdt_init(void) outb(0x09, CIR_IER(base)); } - printk(KERN_INFO PFX "Chip it%04x revision %d initialized. " - "timeout=%d sec (nowayout=%d testmode=%d exclusive=%d " - "nogameport=%d)\n", chip_type, chip_rev, timeout, - nowayout, testmode, exclusive, nogameport); + pr_info("Chip IT%04x revision %d initialized. timeout=%d sec (nowayout=%d testmode=%d exclusive=%d nogameport=%d nocir=%d)\n", + chip_type, chip_rev, timeout, + nowayout, testmode, exclusive, nogameport, nocir); + superio_exit(); return 0; err_out_reboot: unregister_reboot_notifier(&wdt_notifier); err_out_region: - release_region(base, test_bit(WDTS_USE_GP, &wdt_status) ? 1 : 8); - if (!test_bit(WDTS_USE_GP, &wdt_status)) { - spin_lock_irqsave(&spinlock, flags); - superio_enter(); + if (test_bit(WDTS_USE_GP, &wdt_status)) + release_region(base, 1); + else if (test_bit(WDTS_USE_CIR, &wdt_status)) { + release_region(base, 8); superio_select(CIR); superio_outb(ciract, ACTREG); - superio_exit(); - spin_unlock_irqrestore(&spinlock, flags); } err_out: - if (chip_type != IT8718_ID && !nogameport) { - spin_lock_irqsave(&spinlock, flags); - superio_enter(); + if (try_gameport) { superio_select(GAMEPORT); superio_outb(gpact, ACTREG); - superio_exit(); - spin_unlock_irqrestore(&spinlock, flags); } + superio_exit(); return rc; } static void __exit it87_wdt_exit(void) { - unsigned long flags; - int nolock; - - nolock = !spin_trylock_irqsave(&spinlock, flags); - superio_enter(); - superio_select(GPIO); - superio_outb(0x00, WDTCTRL); - superio_outb(0x00, WDTCFG); - superio_outb(0x00, WDTVALMSB); - superio_outb(0x00, WDTVALLSB); - if (test_bit(WDTS_USE_GP, &wdt_status)) { - superio_select(GAMEPORT); - superio_outb(gpact, ACTREG); - } else { - superio_select(CIR); - superio_outb(ciract, ACTREG); + if (superio_enter() == 0) { + superio_select(GPIO); + superio_outb(0x00, WDTCTRL); + superio_outb(0x00, WDTCFG); + superio_outb(0x00, WDTVALLSB); + if (max_units > 255) + superio_outb(0x00, WDTVALMSB); + if (test_bit(WDTS_USE_GP, &wdt_status)) { + superio_select(GAMEPORT); + superio_outb(gpact, ACTREG); + } else if (test_bit(WDTS_USE_CIR, &wdt_status)) { + superio_select(CIR); + superio_outb(ciract, ACTREG); + } + superio_exit(); } - superio_exit(); - if (!nolock) - spin_unlock_irqrestore(&spinlock, flags); misc_deregister(&wdt_miscdev); unregister_reboot_notifier(&wdt_notifier); - release_region(base, test_bit(WDTS_USE_GP, &wdt_status) ? 1 : 8); + + if (test_bit(WDTS_USE_GP, &wdt_status)) + release_region(base, 1); + else if (test_bit(WDTS_USE_CIR, &wdt_status)) + release_region(base, 8); } module_init(it87_wdt_init); @@ -722,4 +793,3 @@ module_exit(it87_wdt_exit); MODULE_AUTHOR("Oliver Schuster"); MODULE_DESCRIPTION("Hardware Watchdog Device Driver for IT87xx EC-LPC I/O"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); |
