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