1 /* 2 * Watchdog driver for Cirrus Logic EP93xx family of devices. 3 * 4 * Copyright (c) 2004 Ray Lehtiniemi 5 * Copyright (c) 2006 Tower Technologies 6 * Based on ep93xx driver, bits from alim7101_wdt.c 7 * 8 * Authors: Ray Lehtiniemi <rayl@mail.com>, 9 * Alessandro Zummo <a.zummo@towertech.it> 10 * 11 * This file is licensed under the terms of the GNU General Public 12 * License version 2. This program is licensed "as is" without any 13 * warranty of any kind, whether express or implied. 14 * 15 * This watchdog fires after 250msec, which is a too short interval 16 * for us to rely on the user space daemon alone. So we ping the 17 * wdt each ~200msec and eventually stop doing it if the user space 18 * daemon dies. 19 * 20 * TODO: 21 * 22 * - Test last reset from watchdog status 23 * - Add a few missing ioctls 24 */ 25 26 #include <linux/module.h> 27 #include <linux/fs.h> 28 #include <linux/miscdevice.h> 29 #include <linux/watchdog.h> 30 #include <linux/timer.h> 31 #include <linux/uaccess.h> 32 #include <linux/io.h> 33 #include <mach/hardware.h> 34 35 #define WDT_VERSION "0.3" 36 #define PFX "ep93xx_wdt: " 37 38 /* default timeout (secs) */ 39 #define WDT_TIMEOUT 30 40 41 static int nowayout = WATCHDOG_NOWAYOUT; 42 static int timeout = WDT_TIMEOUT; 43 44 static struct timer_list timer; 45 static unsigned long next_heartbeat; 46 static unsigned long wdt_status; 47 static unsigned long boot_status; 48 49 #define WDT_IN_USE 0 50 #define WDT_OK_TO_CLOSE 1 51 52 #define EP93XX_WDT_REG(x) (EP93XX_WATCHDOG_BASE + (x)) 53 #define EP93XX_WDT_WATCHDOG EP93XX_WDT_REG(0x00) 54 #define EP93XX_WDT_WDSTATUS EP93XX_WDT_REG(0x04) 55 56 /* reset the wdt every ~200ms */ 57 #define WDT_INTERVAL (HZ/5) 58 59 static void wdt_enable(void) 60 { 61 __raw_writew(0xaaaa, EP93XX_WDT_WATCHDOG); 62 } 63 64 static void wdt_disable(void) 65 { 66 __raw_writew(0xaa55, EP93XX_WDT_WATCHDOG); 67 } 68 69 static inline void wdt_ping(void) 70 { 71 __raw_writew(0x5555, EP93XX_WDT_WATCHDOG); 72 } 73 74 static void wdt_startup(void) 75 { 76 next_heartbeat = jiffies + (timeout * HZ); 77 78 wdt_enable(); 79 mod_timer(&timer, jiffies + WDT_INTERVAL); 80 } 81 82 static void wdt_shutdown(void) 83 { 84 del_timer_sync(&timer); 85 wdt_disable(); 86 } 87 88 static void wdt_keepalive(void) 89 { 90 /* user land ping */ 91 next_heartbeat = jiffies + (timeout * HZ); 92 } 93 94 static int ep93xx_wdt_open(struct inode *inode, struct file *file) 95 { 96 if (test_and_set_bit(WDT_IN_USE, &wdt_status)) 97 return -EBUSY; 98 99 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 100 101 wdt_startup(); 102 103 return nonseekable_open(inode, file); 104 } 105 106 static ssize_t 107 ep93xx_wdt_write(struct file *file, const char __user *data, size_t len, 108 loff_t *ppos) 109 { 110 if (len) { 111 if (!nowayout) { 112 size_t i; 113 114 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 115 116 for (i = 0; i != len; i++) { 117 char c; 118 119 if (get_user(c, data + i)) 120 return -EFAULT; 121 122 if (c == 'V') 123 set_bit(WDT_OK_TO_CLOSE, &wdt_status); 124 else 125 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 126 } 127 } 128 wdt_keepalive(); 129 } 130 131 return len; 132 } 133 134 static const struct watchdog_info ident = { 135 .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE, 136 .identity = "EP93xx Watchdog", 137 }; 138 139 static long ep93xx_wdt_ioctl(struct file *file, 140 unsigned int cmd, unsigned long arg) 141 { 142 int ret = -ENOTTY; 143 144 switch (cmd) { 145 case WDIOC_GETSUPPORT: 146 ret = copy_to_user((struct watchdog_info __user *)arg, &ident, 147 sizeof(ident)) ? -EFAULT : 0; 148 break; 149 150 case WDIOC_GETSTATUS: 151 ret = put_user(0, (int __user *)arg); 152 break; 153 154 case WDIOC_GETBOOTSTATUS: 155 ret = put_user(boot_status, (int __user *)arg); 156 break; 157 158 case WDIOC_KEEPALIVE: 159 wdt_keepalive(); 160 ret = 0; 161 break; 162 163 case WDIOC_GETTIMEOUT: 164 /* actually, it is 0.250 seconds.... */ 165 ret = put_user(1, (int __user *)arg); 166 break; 167 } 168 return ret; 169 } 170 171 static int ep93xx_wdt_release(struct inode *inode, struct file *file) 172 { 173 if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) 174 wdt_shutdown(); 175 else 176 printk(KERN_CRIT PFX 177 "Device closed unexpectedly - timer will not stop\n"); 178 179 clear_bit(WDT_IN_USE, &wdt_status); 180 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 181 182 return 0; 183 } 184 185 static const struct file_operations ep93xx_wdt_fops = { 186 .owner = THIS_MODULE, 187 .write = ep93xx_wdt_write, 188 .unlocked_ioctl = ep93xx_wdt_ioctl, 189 .open = ep93xx_wdt_open, 190 .release = ep93xx_wdt_release, 191 .llseek = no_llseek, 192 }; 193 194 static struct miscdevice ep93xx_wdt_miscdev = { 195 .minor = WATCHDOG_MINOR, 196 .name = "watchdog", 197 .fops = &ep93xx_wdt_fops, 198 }; 199 200 static void ep93xx_timer_ping(unsigned long data) 201 { 202 if (time_before(jiffies, next_heartbeat)) 203 wdt_ping(); 204 205 /* Re-set the timer interval */ 206 mod_timer(&timer, jiffies + WDT_INTERVAL); 207 } 208 209 static int __init ep93xx_wdt_init(void) 210 { 211 int err; 212 213 err = misc_register(&ep93xx_wdt_miscdev); 214 215 boot_status = __raw_readl(EP93XX_WDT_WATCHDOG) & 0x01 ? 1 : 0; 216 217 printk(KERN_INFO PFX "EP93XX watchdog, driver version " 218 WDT_VERSION "%s\n", 219 (__raw_readl(EP93XX_WDT_WATCHDOG) & 0x08) 220 ? " (nCS1 disable detected)" : ""); 221 222 if (timeout < 1 || timeout > 3600) { 223 timeout = WDT_TIMEOUT; 224 printk(KERN_INFO PFX 225 "timeout value must be 1<=x<=3600, using %d\n", 226 timeout); 227 } 228 229 setup_timer(&timer, ep93xx_timer_ping, 1); 230 return err; 231 } 232 233 static void __exit ep93xx_wdt_exit(void) 234 { 235 wdt_shutdown(); 236 misc_deregister(&ep93xx_wdt_miscdev); 237 } 238 239 module_init(ep93xx_wdt_init); 240 module_exit(ep93xx_wdt_exit); 241 242 module_param(nowayout, int, 0); 243 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); 244 245 module_param(timeout, int, 0); 246 MODULE_PARM_DESC(timeout, 247 "Watchdog timeout in seconds. (1<=timeout<=3600, default=" 248 __MODULE_STRING(WDT_TIMEOUT) ")"); 249 250 MODULE_AUTHOR("Ray Lehtiniemi <rayl@mail.com>," 251 "Alessandro Zummo <a.zummo@towertech.it>"); 252 MODULE_DESCRIPTION("EP93xx Watchdog"); 253 MODULE_LICENSE("GPL"); 254 MODULE_VERSION(WDT_VERSION); 255 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 256