/* * Watchdog driver for Cirrus Logic EP93xx family of devices. * * Copyright (c) 2004 Ray Lehtiniemi * Copyright (c) 2006 Tower Technologies * Based on ep93xx driver, bits from alim7101_wdt.c * * Authors: Ray Lehtiniemi <rayl@mail.com>, * Alessandro Zummo <a.zummo@towertech.it> * * This file is licensed under the terms of the GNU General Public * License version 2. This program is licensed "as is" without any * warranty of any kind, whether express or implied. * * This watchdog fires after 250msec, which is a too short interval * for us to rely on the user space daemon alone. So we ping the * wdt each ~200msec and eventually stop doing it if the user space * daemon dies. * * TODO: * * - Test last reset from watchdog status * - Add a few missing ioctls */ #include <linux/module.h> #include <linux/fs.h> #include <linux/miscdevice.h> #include <linux/watchdog.h> #include <linux/timer.h> #include <linux/uaccess.h> #include <linux/io.h> #include <mach/hardware.h> #define WDT_VERSION "0.3" #define PFX "ep93xx_wdt: " /* default timeout (secs) */ #define WDT_TIMEOUT 30 static int nowayout = WATCHDOG_NOWAYOUT; static int timeout = WDT_TIMEOUT; static struct timer_list timer; static unsigned long next_heartbeat; static unsigned long wdt_status; static unsigned long boot_status; #define WDT_IN_USE 0 #define WDT_OK_TO_CLOSE 1 #define EP93XX_WDT_REG(x) (EP93XX_WATCHDOG_BASE + (x)) #define EP93XX_WDT_WATCHDOG EP93XX_WDT_REG(0x00) #define EP93XX_WDT_WDSTATUS EP93XX_WDT_REG(0x04) /* reset the wdt every ~200ms */ #define WDT_INTERVAL (HZ/5) static void wdt_enable(void) { __raw_writew(0xaaaa, EP93XX_WDT_WATCHDOG); } static void wdt_disable(void) { __raw_writew(0xaa55, EP93XX_WDT_WATCHDOG); } static inline void wdt_ping(void) { __raw_writew(0x5555, EP93XX_WDT_WATCHDOG); } static void wdt_startup(void) { next_heartbeat = jiffies + (timeout * HZ); wdt_enable(); mod_timer(&timer, jiffies + WDT_INTERVAL); } static void wdt_shutdown(void) { del_timer_sync(&timer); wdt_disable(); } static void wdt_keepalive(void) { /* user land ping */ next_heartbeat = jiffies + (timeout * HZ); } static int ep93xx_wdt_open(struct inode *inode, struct file *file) { if (test_and_set_bit(WDT_IN_USE, &wdt_status)) return -EBUSY; clear_bit(WDT_OK_TO_CLOSE, &wdt_status); wdt_startup(); return nonseekable_open(inode, file); } static ssize_t ep93xx_wdt_write(struct file *file, const char __user *data, size_t len, loff_t *ppos) { if (len) { if (!nowayout) { size_t i; clear_bit(WDT_OK_TO_CLOSE, &wdt_status); for (i = 0; i != len; i++) { char c; if (get_user(c, data + i)) return -EFAULT; if (c == 'V') set_bit(WDT_OK_TO_CLOSE, &wdt_status); else clear_bit(WDT_OK_TO_CLOSE, &wdt_status); } } wdt_keepalive(); } return len; } static const struct watchdog_info ident = { .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE, .identity = "EP93xx Watchdog", }; static long ep93xx_wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int ret = -ENOTTY; switch (cmd) { case WDIOC_GETSUPPORT: ret = copy_to_user((struct watchdog_info __user *)arg, &ident, sizeof(ident)) ? -EFAULT : 0; break; case WDIOC_GETSTATUS: ret = put_user(0, (int __user *)arg); break; case WDIOC_GETBOOTSTATUS: ret = put_user(boot_status, (int __user *)arg); break; case WDIOC_KEEPALIVE: wdt_keepalive(); ret = 0; break; case WDIOC_GETTIMEOUT: /* actually, it is 0.250 seconds.... */ ret = put_user(1, (int __user *)arg); break; } return ret; } static int ep93xx_wdt_release(struct inode *inode, struct file *file) { if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) wdt_shutdown(); else printk(KERN_CRIT PFX "Device closed unexpectedly - timer will not stop\n"); clear_bit(WDT_IN_USE, &wdt_status); clear_bit(WDT_OK_TO_CLOSE, &wdt_status); return 0; } static const struct file_operations ep93xx_wdt_fops = { .owner = THIS_MODULE, .write = ep93xx_wdt_write, .unlocked_ioctl = ep93xx_wdt_ioctl, .open = ep93xx_wdt_open, .release = ep93xx_wdt_release, .llseek = no_llseek, }; static struct miscdevice ep93xx_wdt_miscdev = { .minor = WATCHDOG_MINOR, .name = "watchdog", .fops = &ep93xx_wdt_fops, }; static void ep93xx_timer_ping(unsigned long data) { if (time_before(jiffies, next_heartbeat)) wdt_ping(); /* Re-set the timer interval */ mod_timer(&timer, jiffies + WDT_INTERVAL); } static int __init ep93xx_wdt_init(void) { int err; err = misc_register(&ep93xx_wdt_miscdev); boot_status = __raw_readl(EP93XX_WDT_WATCHDOG) & 0x01 ? 1 : 0; printk(KERN_INFO PFX "EP93XX watchdog, driver version " WDT_VERSION "%s\n", (__raw_readl(EP93XX_WDT_WATCHDOG) & 0x08) ? " (nCS1 disable detected)" : ""); if (timeout < 1 || timeout > 3600) { timeout = WDT_TIMEOUT; printk(KERN_INFO PFX "timeout value must be 1<=x<=3600, using %d\n", timeout); } setup_timer(&timer, ep93xx_timer_ping, 1); return err; } static void __exit ep93xx_wdt_exit(void) { wdt_shutdown(); misc_deregister(&ep93xx_wdt_miscdev); } module_init(ep93xx_wdt_init); module_exit(ep93xx_wdt_exit); module_param(nowayout, int, 0); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); module_param(timeout, int, 0); MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __MODULE_STRING(WDT_TIMEOUT) ")"); MODULE_AUTHOR("Ray Lehtiniemi <rayl@mail.com>," "Alessandro Zummo <a.zummo@towertech.it>"); MODULE_DESCRIPTION("EP93xx Watchdog"); MODULE_LICENSE("GPL"); MODULE_VERSION(WDT_VERSION); MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);