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