1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * ICP Wafer 5823 Single Board Computer WDT driver 4 * http://www.icpamerica.com/wafer_5823.php 5 * May also work on other similar models 6 * 7 * (c) Copyright 2002 Justin Cormack <justin@street-vision.com> 8 * 9 * Release 0.02 10 * 11 * Based on advantechwdt.c which is based on wdt.c. 12 * Original copyright messages: 13 * 14 * (c) Copyright 1996-1997 Alan Cox <alan@lxorguk.ukuu.org.uk>, 15 * All Rights Reserved. 16 * 17 * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide 18 * warranty for any of this software. This material is provided 19 * "AS-IS" and at no charge. 20 * 21 * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk> 22 * 23 */ 24 25 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 26 27 #include <linux/module.h> 28 #include <linux/moduleparam.h> 29 #include <linux/miscdevice.h> 30 #include <linux/watchdog.h> 31 #include <linux/fs.h> 32 #include <linux/ioport.h> 33 #include <linux/notifier.h> 34 #include <linux/reboot.h> 35 #include <linux/init.h> 36 #include <linux/spinlock.h> 37 #include <linux/io.h> 38 #include <linux/uaccess.h> 39 40 #define WATCHDOG_NAME "Wafer 5823 WDT" 41 #define PFX WATCHDOG_NAME ": " 42 #define WD_TIMO 60 /* 60 sec default timeout */ 43 44 static unsigned long wafwdt_is_open; 45 static char expect_close; 46 static DEFINE_SPINLOCK(wafwdt_lock); 47 48 /* 49 * You must set these - there is no sane way to probe for this board. 50 * 51 * To enable, write the timeout value in seconds (1 to 255) to I/O 52 * port WDT_START, then read the port to start the watchdog. To pat 53 * the dog, read port WDT_STOP to stop the timer, then read WDT_START 54 * to restart it again. 55 */ 56 57 static int wdt_stop = 0x843; 58 static int wdt_start = 0x443; 59 60 static int timeout = WD_TIMO; /* in seconds */ 61 module_param(timeout, int, 0); 62 MODULE_PARM_DESC(timeout, 63 "Watchdog timeout in seconds. 1 <= timeout <= 255, default=" 64 __MODULE_STRING(WD_TIMO) "."); 65 66 static bool nowayout = WATCHDOG_NOWAYOUT; 67 module_param(nowayout, bool, 0); 68 MODULE_PARM_DESC(nowayout, 69 "Watchdog cannot be stopped once started (default=" 70 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 71 72 static void wafwdt_ping(void) 73 { 74 /* pat watchdog */ 75 spin_lock(&wafwdt_lock); 76 inb_p(wdt_stop); 77 inb_p(wdt_start); 78 spin_unlock(&wafwdt_lock); 79 } 80 81 static void wafwdt_start(void) 82 { 83 /* start up watchdog */ 84 outb_p(timeout, wdt_start); 85 inb_p(wdt_start); 86 } 87 88 static void wafwdt_stop(void) 89 { 90 /* stop watchdog */ 91 inb_p(wdt_stop); 92 } 93 94 static ssize_t wafwdt_write(struct file *file, const char __user *buf, 95 size_t count, loff_t *ppos) 96 { 97 /* See if we got the magic character 'V' and reload the timer */ 98 if (count) { 99 if (!nowayout) { 100 size_t i; 101 102 /* In case it was set long ago */ 103 expect_close = 0; 104 105 /* scan to see whether or not we got the magic 106 character */ 107 for (i = 0; i != count; i++) { 108 char c; 109 if (get_user(c, buf + i)) 110 return -EFAULT; 111 if (c == 'V') 112 expect_close = 42; 113 } 114 } 115 /* Well, anyhow someone wrote to us, we should 116 return that favour */ 117 wafwdt_ping(); 118 } 119 return count; 120 } 121 122 static long wafwdt_ioctl(struct file *file, unsigned int cmd, 123 unsigned long arg) 124 { 125 int new_timeout; 126 void __user *argp = (void __user *)arg; 127 int __user *p = argp; 128 static const struct watchdog_info ident = { 129 .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | 130 WDIOF_MAGICCLOSE, 131 .firmware_version = 1, 132 .identity = "Wafer 5823 WDT", 133 }; 134 135 switch (cmd) { 136 case WDIOC_GETSUPPORT: 137 if (copy_to_user(argp, &ident, sizeof(ident))) 138 return -EFAULT; 139 break; 140 141 case WDIOC_GETSTATUS: 142 case WDIOC_GETBOOTSTATUS: 143 return put_user(0, p); 144 145 case WDIOC_SETOPTIONS: 146 { 147 int options, retval = -EINVAL; 148 149 if (get_user(options, p)) 150 return -EFAULT; 151 152 if (options & WDIOS_DISABLECARD) { 153 wafwdt_stop(); 154 retval = 0; 155 } 156 157 if (options & WDIOS_ENABLECARD) { 158 wafwdt_start(); 159 retval = 0; 160 } 161 162 return retval; 163 } 164 165 case WDIOC_KEEPALIVE: 166 wafwdt_ping(); 167 break; 168 169 case WDIOC_SETTIMEOUT: 170 if (get_user(new_timeout, p)) 171 return -EFAULT; 172 if ((new_timeout < 1) || (new_timeout > 255)) 173 return -EINVAL; 174 timeout = new_timeout; 175 wafwdt_stop(); 176 wafwdt_start(); 177 /* Fall through */ 178 case WDIOC_GETTIMEOUT: 179 return put_user(timeout, p); 180 181 default: 182 return -ENOTTY; 183 } 184 return 0; 185 } 186 187 static int wafwdt_open(struct inode *inode, struct file *file) 188 { 189 if (test_and_set_bit(0, &wafwdt_is_open)) 190 return -EBUSY; 191 192 /* 193 * Activate 194 */ 195 wafwdt_start(); 196 return stream_open(inode, file); 197 } 198 199 static int wafwdt_close(struct inode *inode, struct file *file) 200 { 201 if (expect_close == 42) 202 wafwdt_stop(); 203 else { 204 pr_crit("WDT device closed unexpectedly. WDT will not stop!\n"); 205 wafwdt_ping(); 206 } 207 clear_bit(0, &wafwdt_is_open); 208 expect_close = 0; 209 return 0; 210 } 211 212 /* 213 * Notifier for system down 214 */ 215 216 static int wafwdt_notify_sys(struct notifier_block *this, unsigned long code, 217 void *unused) 218 { 219 if (code == SYS_DOWN || code == SYS_HALT) 220 wafwdt_stop(); 221 return NOTIFY_DONE; 222 } 223 224 /* 225 * Kernel Interfaces 226 */ 227 228 static const struct file_operations wafwdt_fops = { 229 .owner = THIS_MODULE, 230 .llseek = no_llseek, 231 .write = wafwdt_write, 232 .unlocked_ioctl = wafwdt_ioctl, 233 .compat_ioctl = compat_ptr_ioctl, 234 .open = wafwdt_open, 235 .release = wafwdt_close, 236 }; 237 238 static struct miscdevice wafwdt_miscdev = { 239 .minor = WATCHDOG_MINOR, 240 .name = "watchdog", 241 .fops = &wafwdt_fops, 242 }; 243 244 /* 245 * The WDT needs to learn about soft shutdowns in order to 246 * turn the timebomb registers off. 247 */ 248 249 static struct notifier_block wafwdt_notifier = { 250 .notifier_call = wafwdt_notify_sys, 251 }; 252 253 static int __init wafwdt_init(void) 254 { 255 int ret; 256 257 pr_info("WDT driver for Wafer 5823 single board computer initialising\n"); 258 259 if (timeout < 1 || timeout > 255) { 260 timeout = WD_TIMO; 261 pr_info("timeout value must be 1 <= x <= 255, using %d\n", 262 timeout); 263 } 264 265 if (wdt_stop != wdt_start) { 266 if (!request_region(wdt_stop, 1, "Wafer 5823 WDT")) { 267 pr_err("I/O address 0x%04x already in use\n", wdt_stop); 268 ret = -EIO; 269 goto error; 270 } 271 } 272 273 if (!request_region(wdt_start, 1, "Wafer 5823 WDT")) { 274 pr_err("I/O address 0x%04x already in use\n", wdt_start); 275 ret = -EIO; 276 goto error2; 277 } 278 279 ret = register_reboot_notifier(&wafwdt_notifier); 280 if (ret != 0) { 281 pr_err("cannot register reboot notifier (err=%d)\n", ret); 282 goto error3; 283 } 284 285 ret = misc_register(&wafwdt_miscdev); 286 if (ret != 0) { 287 pr_err("cannot register miscdev on minor=%d (err=%d)\n", 288 WATCHDOG_MINOR, ret); 289 goto error4; 290 } 291 292 pr_info("initialized. timeout=%d sec (nowayout=%d)\n", 293 timeout, nowayout); 294 295 return ret; 296 error4: 297 unregister_reboot_notifier(&wafwdt_notifier); 298 error3: 299 release_region(wdt_start, 1); 300 error2: 301 if (wdt_stop != wdt_start) 302 release_region(wdt_stop, 1); 303 error: 304 return ret; 305 } 306 307 static void __exit wafwdt_exit(void) 308 { 309 misc_deregister(&wafwdt_miscdev); 310 unregister_reboot_notifier(&wafwdt_notifier); 311 if (wdt_stop != wdt_start) 312 release_region(wdt_stop, 1); 313 release_region(wdt_start, 1); 314 } 315 316 module_init(wafwdt_init); 317 module_exit(wafwdt_exit); 318 319 MODULE_AUTHOR("Justin Cormack"); 320 MODULE_DESCRIPTION("ICP Wafer 5823 Single Board Computer WDT driver"); 321 MODULE_LICENSE("GPL"); 322 323 /* end of wafer5823wdt.c */ 324