1 /* 2 * NANO7240 SBC Watchdog device driver 3 * 4 * Based on w83877f.c by Scott Jennings, 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 2 as 8 * published by the Free Software Foundation; 9 * 10 * Software distributed under the License is distributed on an "AS IS" 11 * basis, WITHOUT WARRANTY OF ANY KIND, either express or 12 * implied. See the License for the specific language governing 13 * rights and limitations under the License. 14 * 15 * (c) Copyright 2007 Gilles GIGAN <gilles.gigan@jcu.edu.au> 16 * 17 */ 18 19 #include <linux/fs.h> 20 #include <linux/init.h> 21 #include <linux/ioport.h> 22 #include <linux/jiffies.h> 23 #include <linux/module.h> 24 #include <linux/moduleparam.h> 25 #include <linux/miscdevice.h> 26 #include <linux/notifier.h> 27 #include <linux/reboot.h> 28 #include <linux/types.h> 29 #include <linux/watchdog.h> 30 #include <linux/io.h> 31 #include <linux/uaccess.h> 32 #include <asm/atomic.h> 33 #include <asm/system.h> 34 35 #define SBC7240_PREFIX "sbc7240_wdt: " 36 37 #define SBC7240_ENABLE_PORT 0x443 38 #define SBC7240_DISABLE_PORT 0x043 39 #define SBC7240_SET_TIMEOUT_PORT SBC7240_ENABLE_PORT 40 #define SBC7240_MAGIC_CHAR 'V' 41 42 #define SBC7240_TIMEOUT 30 43 #define SBC7240_MAX_TIMEOUT 255 44 static int timeout = SBC7240_TIMEOUT; /* in seconds */ 45 module_param(timeout, int, 0); 46 MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=" 47 __MODULE_STRING(SBC7240_MAX_TIMEOUT) ", default=" 48 __MODULE_STRING(SBC7240_TIMEOUT) ")"); 49 50 static int nowayout = WATCHDOG_NOWAYOUT; 51 module_param(nowayout, int, 0); 52 MODULE_PARM_DESC(nowayout, "Disable watchdog when closing device file"); 53 54 #define SBC7240_OPEN_STATUS_BIT 0 55 #define SBC7240_ENABLED_STATUS_BIT 1 56 #define SBC7240_EXPECT_CLOSE_STATUS_BIT 2 57 static unsigned long wdt_status; 58 59 /* 60 * Utility routines 61 */ 62 63 static void wdt_disable(void) 64 { 65 /* disable the watchdog */ 66 if (test_and_clear_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status)) { 67 inb_p(SBC7240_DISABLE_PORT); 68 printk(KERN_INFO SBC7240_PREFIX 69 "Watchdog timer is now disabled.\n"); 70 } 71 } 72 73 static void wdt_enable(void) 74 { 75 /* enable the watchdog */ 76 if (!test_and_set_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status)) { 77 inb_p(SBC7240_ENABLE_PORT); 78 printk(KERN_INFO SBC7240_PREFIX 79 "Watchdog timer is now enabled.\n"); 80 } 81 } 82 83 static int wdt_set_timeout(int t) 84 { 85 if (t < 1 || t > SBC7240_MAX_TIMEOUT) { 86 printk(KERN_ERR SBC7240_PREFIX 87 "timeout value must be 1<=x<=%d\n", 88 SBC7240_MAX_TIMEOUT); 89 return -1; 90 } 91 /* set the timeout */ 92 outb_p((unsigned)t, SBC7240_SET_TIMEOUT_PORT); 93 timeout = t; 94 printk(KERN_INFO SBC7240_PREFIX "timeout set to %d seconds\n", t); 95 return 0; 96 } 97 98 /* Whack the dog */ 99 static inline void wdt_keepalive(void) 100 { 101 if (test_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status)) 102 inb_p(SBC7240_ENABLE_PORT); 103 } 104 105 /* 106 * /dev/watchdog handling 107 */ 108 static ssize_t fop_write(struct file *file, const char __user *buf, 109 size_t count, loff_t *ppos) 110 { 111 size_t i; 112 char c; 113 114 if (count) { 115 if (!nowayout) { 116 clear_bit(SBC7240_EXPECT_CLOSE_STATUS_BIT, 117 &wdt_status); 118 119 /* is there a magic char ? */ 120 for (i = 0; i != count; i++) { 121 if (get_user(c, buf + i)) 122 return -EFAULT; 123 if (c == SBC7240_MAGIC_CHAR) { 124 set_bit(SBC7240_EXPECT_CLOSE_STATUS_BIT, 125 &wdt_status); 126 break; 127 } 128 } 129 } 130 131 wdt_keepalive(); 132 } 133 134 return count; 135 } 136 137 static int fop_open(struct inode *inode, struct file *file) 138 { 139 if (test_and_set_bit(SBC7240_OPEN_STATUS_BIT, &wdt_status)) 140 return -EBUSY; 141 142 wdt_enable(); 143 144 return nonseekable_open(inode, file); 145 } 146 147 static int fop_close(struct inode *inode, struct file *file) 148 { 149 if (test_and_clear_bit(SBC7240_EXPECT_CLOSE_STATUS_BIT, &wdt_status) 150 || !nowayout) { 151 wdt_disable(); 152 } else { 153 printk(KERN_CRIT SBC7240_PREFIX 154 "Unexpected close, not stopping watchdog!\n"); 155 wdt_keepalive(); 156 } 157 158 clear_bit(SBC7240_OPEN_STATUS_BIT, &wdt_status); 159 return 0; 160 } 161 162 static const struct watchdog_info ident = { 163 .options = WDIOF_KEEPALIVEPING| 164 WDIOF_SETTIMEOUT| 165 WDIOF_MAGICCLOSE, 166 .firmware_version = 1, 167 .identity = "SBC7240", 168 }; 169 170 171 static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg) 172 { 173 switch (cmd) { 174 case WDIOC_GETSUPPORT: 175 return copy_to_user((void __user *)arg, &ident, sizeof(ident)) 176 ? -EFAULT : 0; 177 case WDIOC_GETSTATUS: 178 case WDIOC_GETBOOTSTATUS: 179 return put_user(0, (int __user *)arg); 180 case WDIOC_SETOPTIONS: 181 { 182 int options; 183 int retval = -EINVAL; 184 185 if (get_user(options, (int __user *)arg)) 186 return -EFAULT; 187 188 if (options & WDIOS_DISABLECARD) { 189 wdt_disable(); 190 retval = 0; 191 } 192 193 if (options & WDIOS_ENABLECARD) { 194 wdt_enable(); 195 retval = 0; 196 } 197 198 return retval; 199 } 200 case WDIOC_KEEPALIVE: 201 wdt_keepalive(); 202 return 0; 203 case WDIOC_SETTIMEOUT: 204 { 205 int new_timeout; 206 207 if (get_user(new_timeout, (int __user *)arg)) 208 return -EFAULT; 209 210 if (wdt_set_timeout(new_timeout)) 211 return -EINVAL; 212 213 /* Fall through */ 214 } 215 case WDIOC_GETTIMEOUT: 216 return put_user(timeout, (int __user *)arg); 217 default: 218 return -ENOTTY; 219 } 220 } 221 222 static const struct file_operations wdt_fops = { 223 .owner = THIS_MODULE, 224 .llseek = no_llseek, 225 .write = fop_write, 226 .open = fop_open, 227 .release = fop_close, 228 .unlocked_ioctl = fop_ioctl, 229 }; 230 231 static struct miscdevice wdt_miscdev = { 232 .minor = WATCHDOG_MINOR, 233 .name = "watchdog", 234 .fops = &wdt_fops, 235 }; 236 237 /* 238 * Notifier for system down 239 */ 240 241 static int wdt_notify_sys(struct notifier_block *this, unsigned long code, 242 void *unused) 243 { 244 if (code == SYS_DOWN || code == SYS_HALT) 245 wdt_disable(); 246 return NOTIFY_DONE; 247 } 248 249 static struct notifier_block wdt_notifier = { 250 .notifier_call = wdt_notify_sys, 251 }; 252 253 static void __exit sbc7240_wdt_unload(void) 254 { 255 printk(KERN_INFO SBC7240_PREFIX "Removing watchdog\n"); 256 misc_deregister(&wdt_miscdev); 257 258 unregister_reboot_notifier(&wdt_notifier); 259 release_region(SBC7240_ENABLE_PORT, 1); 260 } 261 262 static int __init sbc7240_wdt_init(void) 263 { 264 int rc = -EBUSY; 265 266 if (!request_region(SBC7240_ENABLE_PORT, 1, "SBC7240 WDT")) { 267 printk(KERN_ERR SBC7240_PREFIX 268 "I/O address 0x%04x already in use\n", 269 SBC7240_ENABLE_PORT); 270 rc = -EIO; 271 goto err_out; 272 } 273 274 /* The IO port 0x043 used to disable the watchdog 275 * is already claimed by the system timer, so we 276 * cant request_region() it ...*/ 277 278 if (timeout < 1 || timeout > SBC7240_MAX_TIMEOUT) { 279 timeout = SBC7240_TIMEOUT; 280 printk(KERN_INFO SBC7240_PREFIX 281 "timeout value must be 1<=x<=%d, using %d\n", 282 SBC7240_MAX_TIMEOUT, timeout); 283 } 284 wdt_set_timeout(timeout); 285 wdt_disable(); 286 287 rc = register_reboot_notifier(&wdt_notifier); 288 if (rc) { 289 printk(KERN_ERR SBC7240_PREFIX 290 "cannot register reboot notifier (err=%d)\n", rc); 291 goto err_out_region; 292 } 293 294 rc = misc_register(&wdt_miscdev); 295 if (rc) { 296 printk(KERN_ERR SBC7240_PREFIX 297 "cannot register miscdev on minor=%d (err=%d)\n", 298 wdt_miscdev.minor, rc); 299 goto err_out_reboot_notifier; 300 } 301 302 printk(KERN_INFO SBC7240_PREFIX 303 "Watchdog driver for SBC7240 initialised (nowayout=%d)\n", 304 nowayout); 305 306 return 0; 307 308 err_out_reboot_notifier: 309 unregister_reboot_notifier(&wdt_notifier); 310 err_out_region: 311 release_region(SBC7240_ENABLE_PORT, 1); 312 err_out: 313 return rc; 314 } 315 316 module_init(sbc7240_wdt_init); 317 module_exit(sbc7240_wdt_unload); 318 319 MODULE_AUTHOR("Gilles Gigan"); 320 MODULE_DESCRIPTION("Watchdog device driver for single board" 321 " computers EPIC Nano 7240 from iEi"); 322 MODULE_LICENSE("GPL"); 323 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 324 325