/* * W83627HF watchdog driver for Linux 2.2.x * * (c) Copyright 2002 Zhou HongZhen. * * 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. * * (c) Copyright 2002 Zhou HongZhen * * * Release 1.0. * * Fixes * * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if 0 #define W83627HF_WDT_DEBUG #endif #if 0 #undef CONFIG_WATCHDOG_NOWAYOUT #else #define CONFIG_WATCHDOG_NOWAYOUT #endif #ifdef W83627HF_WDT_DEBUG #define assert_w83627hf_wdt(p) if (1) {\ if (!(p)) \ panic("assert failed in %s line %d\n", __FILE__, __LINE__);\ } else #else #define assert_w83627hf_wdt(p) #endif #define W83627HF_LPCIP 0x295 /* LPC Index Port */ #define W83627HF_LPCDP 0x296 /* LPC Data Port */ #define W83627HF_EFER 0x2E /* Extended Function Enable Register */ #define W83627HF_EFIR 0x2E /* Extended Function Index Register*/ #define W83627HF_EFDR 0x2F /* Extended Function Data Register */ #define W83627HF_ENTER_EFM 0x87 /* enter externed function mode */ #define W83627HF_EXIT_EFM 0xAA /* exit externed function mode */ /* default watchdog timeout value -- 12s */ #define W83627HF_WDT_DEF_TIMO 0x0C #define W83627HF_WDT_MAX_TIMO 0xFF #define W83627HF_WDT_MIN_TIMO 0x01 /* #define W83627HF_WDT_MIN_TIMO 0x05 */ /* default watchdog timeout unit -- second */ #define W83627HF_WDT_DEF_TIMU 0x00 /* only one can open the watchdog device */ static unsigned long wdt_is_open = 0; #ifndef CONFIG_WATCHDOG_NOWAYOUT static unsigned long wdt_expect_close = 0; #endif /* 0x01--0xFF seconds(minutes). 0x00 to disable watchdog. */ static unsigned int w83627hf_margin = W83627HF_WDT_DEF_TIMO; static unsigned int w83627hf_timeunit = W83627HF_WDT_DEF_TIMU; #define W83627HF_WDT_MAGIC_NUM 13 #define W83627HF_WDT_MAGIC_U 3 #define W83627HF_WDT_MAGIC_M 1 /* magic word which change margin and timeunit */ static char w83627hf_wdt_magic[W83627HF_WDT_MAGIC_NUM] = "?M?UFH72638W"; #define w83627hf_wdt_feed() if (1) {\ w83627hf_timeunit = w83627hf_timeunit ? \ 0x08 : 0x00;\ \ if (W83627HF_WDT_MAX_TIMO < w83627hf_margin) \ w83627hf_margin = W83627HF_WDT_MAX_TIMO;\ \ if (0!=w83627hf_margin &&\ W83627HF_WDT_MIN_TIMO>w83627hf_margin) \ w83627hf_margin = W83627HF_WDT_MIN_TIMO; \ \ outb(W83627HF_ENTER_EFM, W83627HF_EFER); \ outb(W83627HF_ENTER_EFM, W83627HF_EFER); \ \ outb(0x07, W83627HF_EFIR);\ outb(0x08, W83627HF_EFDR);\ \ outb(0xF5, W83627HF_EFIR);\ outb((unsigned char)w83627hf_timeunit, W83627HF_EFDR);\ \ outb(0xF6, W83627HF_EFIR);\ outb((unsigned char)w83627hf_margin, W83627HF_EFDR);\ \ outb(W83627HF_EXIT_EFM, W83627HF_EFER); \ } else #define w83627hf_wdt_stop() if (1) {\ outb(W83627HF_ENTER_EFM, W83627HF_EFER); \ outb(W83627HF_ENTER_EFM, W83627HF_EFER); \ \ outb(0x07, W83627HF_EFIR);\ outb(0x08, W83627HF_EFDR);\ \ /* 0x00 to disable watchdog */\ outb(0xF6, W83627HF_EFIR);\ outb(0x00, W83627HF_EFDR);\ \ outb(W83627HF_EXIT_EFM, W83627HF_EFER); \ } else #ifdef W83627HF_WDT_DEBUG /* watchdog sysctl configuration(just for debug) */ enum { SYSCTL_W83627HF_WDT_MARGIN=1, SYSCTL_W83627HF_WDT_TIMEUNIT, SYSCTL_W83627HF_WDT_FEED }; enum { SYSCTL_W83627HF_WDT=21 }; static int sysctl_w83627hf_wdt_noused = 0; static int w83627hf_wdt_sysctl_feed(struct ctl_table *table, int write, struct file *filp, void *buffer, size_t *lenp); static struct ctl_table sysctl_w83627hf_wdt_dir[] = { {SYSCTL_W83627HF_WDT_MARGIN, "margin", &w83627hf_margin, sizeof(int), 0644, 0, &proc_dointvec}, {SYSCTL_W83627HF_WDT_TIMEUNIT, "timeunit", &w83627hf_timeunit, sizeof(int), 0644, 0, &proc_dointvec}, {SYSCTL_W83627HF_WDT_FEED, "feed", &sysctl_w83627hf_wdt_noused, sizeof(int), 0200, 0, &w83627hf_wdt_sysctl_feed}, {0} }; static struct ctl_table sysctl_w83627hf_wdt_root[] = { {SYSCTL_W83627HF_WDT, "w83627hf_wdt", 0, 0, 0555, sysctl_w83627hf_wdt_dir}, {0} }; static struct ctl_table_header *sysctl_w83627hf_wdt_header = 0; /* * When you write something into /proc/sys/w83627hf_wdt/feed, the dog begin * watch the system for you. * Remember to feed it again before the dog burks. * Set /proc/sys/w83627hf_wdt/margin to 0, and update * /proc/sys/w83627hf_wdt/feed will stop the dog. * */ int w83627hf_wdt_sysctl_feed(struct ctl_table *table, int write, struct file *filp, void *buffer, size_t *lenp) { if (!write) return -EPERM; w83627hf_wdt_feed(); return 0; } int w83627hf_wdt_sysctl_create(void) { sysctl_w83627hf_wdt_header = register_sysctl_table(sysctl_w83627hf_wdt_root, 0); if (0 == sysctl_w83627hf_wdt_header) { printk("W83627HF WDT: register_sysctl_table() failed\n"); return -1; } return 0; } void w83627hf_wdt_sysctl_clean(void) { unregister_sysctl_table(sysctl_w83627hf_wdt_header); sysctl_w83627hf_wdt_header = 0; } #endif /* * Read the margin and timeunit from watchdog device, just for debug. * */ static ssize_t w83627hf_wdt_read(struct file *file, char *buf, size_t len, loff_t *ppos) { unsigned char t[2]; assert_w83627hf_wdt(wdt_is_open); if (ppos != &file->f_pos) { #ifdef W83627HF_WDT_DEBUG printk("W83627HF WDT: device can NOT be seek\n"); #endif return -ESPIPE; } if (2 > len) { #ifdef W83627HF_WDT_DEBUG printk("W83627HF WDT: read invalid data length %d\n", len); #endif return -EINVAL; } outb(W83627HF_ENTER_EFM, W83627HF_EFER); outb(W83627HF_ENTER_EFM, W83627HF_EFER); outb(0x07, W83627HF_EFIR); outb(0x08, W83627HF_EFDR); outb(0xF5, W83627HF_EFIR); t[0] = (0x08 & inb(W83627HF_EFDR)) ? 0x01 : 0x00; outb(0xF6, W83627HF_EFIR); t[1] = inb(W83627HF_EFDR); outb(W83627HF_EXIT_EFM, W83627HF_EFER); if (copy_to_user(buf, t, 2)) { #ifdef W83627HF_WDT_DEBUG printk("W83627HF WDT: copy_to_user failed\n"); #endif return -EFAULT; } return 2; } static ssize_t w83627hf_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos) { size_t i; int j = 0, seek = 0; unsigned int timeunit = 0; assert_w83627hf_wdt(wdt_is_open); if (ppos != &file->f_pos) { #ifdef W83627HF_WDT_DEBUG printk("W83627HF WDT: device can NOT be seek\n"); #endif return -ESPIPE; } if (0 == len) goto ping; for (i=0; i=data[i]) { w83627hf_margin = data[i]; w83627hf_timeunit = timeunit; printk(KERN_DEBUG "margin chanded! " "margin == %d, unit == %d\n", w83627hf_margin, w83627hf_timeunit); } j = 0; seek = 0; } else if (data[i] == w83627hf_wdt_magic[j - 1]) { j--; } else { j = 0; seek = 0; } break; } } ping: if (wdt_is_open) { w83627hf_wdt_feed(); } return len; } static int w83627hf_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { int ret = 0; #if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,0) int time; #endif struct watchdog_info ident = { #if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,0) WDIOF_SETTIMEOUT | #endif 0, 1, "W83627HF" }; assert_w83627hf_wdt(wdt_is_open); if (WATCHDOG_MINOR != MINOR(inode->i_rdev)) return -ENODEV; switch (cmd) { case WDIOC_GETSUPPORT: if (copy_to_user((struct watchdog_info *)arg, &ident, sizeof(ident))) ret = -EFAULT; break; case WDIOC_KEEPALIVE: if (wdt_is_open) { w83627hf_wdt_feed(); } break; #if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,0) case WDIOC_SETTIMEOUT: if (copy_from_user(&time, (int *)arg, sizeof(int))) { ret = -EFAULT; break; } printk(KERN_DEBUG "W83627HF WDT: w83627hf_margin %d.\n", w83627hf_margin); if (W83627HF_WDT_MAX_TIMO < time) { printk("W83627HF WDT: w83637hf_margin > 0xFF\n"); time = W83627HF_WDT_MAX_TIMO; } if (0!=time && W83627HF_WDT_MIN_TIMO>time) { printk("W83627HF WDT: w83627hf_margin < 0x01\n"); time = W83627HF_WDT_MIN_TIMO; } w83627hf_margin = time; /* Fall */ #if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,18) case WDIOC_GETTIMEOUT: if (copy_to_user((int *)arg, &w83627hf_margin, sizeof(int))) ret = -EFAULT; #endif break; #endif default: ret = -ENOTTY; break; } return ret; } /* * Once the user application open the device file, watchdog start work. * You MUST feed the dog before it burks. * */ static int w83627hf_wdt_open(struct inode *inode, struct file *file) { if (WATCHDOG_MINOR != MINOR(inode->i_rdev)) return -ENODEV; /* only one can open this device */ if (wdt_is_open) return -EBUSY; wdt_is_open = 1; MOD_INC_USE_COUNT; #ifndef CONFIG_WATCHDOG_NOWAYOUT wdt_expect_close = 0; #endif if (W83627HF_WDT_MAX_TIMO < w83627hf_margin) { printk("W83627HF WDT: w83627hf_margin > 0xFF\n"); w83627hf_margin = W83627HF_WDT_MAX_TIMO; } if (0!=w83627hf_margin && W83627HF_WDT_MIN_TIMO>w83627hf_margin) { printk("W83627HF WDT: w83627hf_margin < 0x01\n"); w83627hf_margin = W83627HF_WDT_MIN_TIMO; } /* enter the extended function mode */ outb(W83627HF_ENTER_EFM, W83627HF_EFER); outb(W83627HF_ENTER_EFM, W83627HF_EFER); /* * watchdog timer is controlled by CRF5, CRF6, CRF7 of logical * device 8. * */ outb(0x2B, W83627HF_EFIR); outb(0xC0, W83627HF_EFDR); /* write CR07 to select logical device A */ outb(0x07, W83627HF_EFIR); outb(0x0A, W83627HF_EFDR); /* disable watchdog IRQ */ outb(0xF7, W83627HF_EFIR); outb(0x00, W83627HF_EFDR); /* write CR07 to select logical device 8 */ outb(0x07, W83627HF_EFIR); outb(0x08, W83627HF_EFDR); /* count time base second(NOT minute). */ outb(0xF5, W83627HF_EFIR); outb((unsigned char)W83627HF_WDT_DEF_TIMU, W83627HF_EFDR); /* write the timeout vlaue into CRF6 */ outb(0xF6, W83627HF_EFIR); outb((unsigned char)w83627hf_margin, W83627HF_EFDR); /* watchdog will NOT be reset upon a mouse/keyboard interrupt. */ outb(0xF7, W83627HF_EFIR); outb(0x00, W83627HF_EFDR); /* exit extended function mode */ outb(W83627HF_EXIT_EFM, W83627HF_EFER); printk(KERN_INFO "W83627HF WDT: wake up, current timeout %d.\n", w83627hf_margin); return 0; } static int w83627hf_wdt_release(struct inode *inode, struct file *file) { assert_w83627hf_wdt(wdt_is_open); if (WATCHDOG_MINOR != MINOR(inode->i_rdev)) return -ENODEV; #ifndef CONFIG_WATCHDOG_NOWAYOUT if (wdt_expect_close) { if(wdt_is_open) { w83627hf_wdt_stop(); } } wdt_expect_close = 0; #endif wdt_is_open = 0; MOD_DEC_USE_COUNT; printk(KERN_INFO "W83627HF WDT: sleeping again.\n"); return 0; } /* * probe the W83627HF chip. * */ static int w83627hf_probe(void) { unsigned char device; unsigned char chip; int ret = -1; if (0 > check_region(W83627HF_LPCIP, 2)) { ret = -EBUSY; goto out; } outb(0x49, W83627HF_LPCIP); device = inb(W83627HF_LPCDP); if (0x01 != (device >> 1)) { /*printk("W83627HF WDT: Device ID %x != 0x01\n", device >> 1);*/ goto out; } outb(0x58, W83627HF_LPCIP); chip = inb(W83627HF_LPCDP); if (0x21 != chip) { /*printk("W83627HF WDT: Chip ID %x != 0x21\n", chip);*/ goto out; } /*printk(KERN_INFO "W83627HF WDT: W83627HF chip found\n");*/ ret = 0; out: return ret; } static int w83627hf_wdt_notify(struct notifier_block *this, unsigned long code, void *unused) { if (SYS_DOWN==code || SYS_HALT==code) { if (wdt_is_open) { w83627hf_wdt_stop(); #ifndef CONFIG_WATCHDOG_NOWAYOUT wdt_expect_close = 0; #endif wdt_is_open = 0; } } return NOTIFY_DONE; } static struct notifier_block w83627hf_wdt_notifier= { w83627hf_wdt_notify, 0, 0 }; static struct file_operations w83627hf_wdt_fops = { llseek: 0, read: w83627hf_wdt_read, write: w83627hf_wdt_write, readdir: 0, poll: 0, ioctl: w83627hf_wdt_ioctl, mmap: 0, open: w83627hf_wdt_open, flush: 0, release: w83627hf_wdt_release, }; static struct miscdevice w83627hf_wdt_miscdev = { WATCHDOG_MINOR, "watchdog", &w83627hf_wdt_fops }; int init_module(void) { int ret; if ((ret = w83627hf_probe())) goto out; ret = -1; request_region(W83627HF_LPCIP, 2, "W83627HF_WDT"); if (misc_register(&w83627hf_wdt_miscdev)) { printk("W83627HF WDT: misc_register() failed\n"); goto clean_region; } if (register_reboot_notifier(&w83627hf_wdt_notifier)) { printk("W83627HF WDT: register_reboot_notifier() failed\n"); goto clean_miscdev; } #ifdef W83627HF_WDT_DEBUG if (w83627hf_wdt_sysctl_create()) goto clean_notifier; #endif if (W83627HF_WDT_MAX_TIMO < w83627hf_margin) { printk("W83627HF WDT: w83627hf_margin > 0xFF\n"); w83627hf_margin = W83627HF_WDT_MAX_TIMO; } if (W83627HF_WDT_MIN_TIMO > w83627hf_margin) { printk("W83627HF WDT: w83627hf_margin < 0x01\n"); w83627hf_margin = W83627HF_WDT_MIN_TIMO; } printk(KERN_INFO "W83627HF WDT: sleeping.\n"); ret = 0; goto out; #ifdef W83627HF_WDT_DEBUG clean_notifier: unregister_reboot_notifier(&w83627hf_wdt_notifier); #endif clean_miscdev: misc_deregister(&w83627hf_wdt_miscdev); clean_region: release_region(W83627HF_LPCIP, 2); out: return ret; } void cleanup_module(void) { #ifdef W83627HF_WDT_DEBUG w83627hf_wdt_sysctl_clean(); #endif unregister_reboot_notifier(&w83627hf_wdt_notifier); misc_deregister(&w83627hf_wdt_miscdev); release_region(W83627HF_LPCIP, 2); } MODULE_PARM(w83627hf_margin, "i"); MODULE_PARM_DESC(w83627hf_magin, "WDT timeout (default 12 <0x01-0xFF>)"); MODULE_AUTHOR("Zhou HongZhen -- |"); MODULE_DESCRIPTION("Watchdog driver for Winbond W83627HF chip"); #if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,0) MODULE_LICENSE("GPL"); #endif EXPORT_NO_SYMBOLS;