1/* 2 * Watchdog driver for SBC-FITPC2 board 3 * 4 * Author: Denis Turischev <denis@compulab.co.il> 5 * 6 * Adapted from the IXP2000 watchdog driver by Deepak Saxena. 7 * 8 * This file is licensed under the terms of the GNU General Public 9 * License version 2. This program is licensed "as is" without any 10 * warranty of any kind, whether express or implied. 11 */ 12 13#define pr_fmt(fmt) KBUILD_MODNAME " WATCHDOG: " fmt 14 15#include <linux/module.h> 16#include <linux/types.h> 17#include <linux/miscdevice.h> 18#include <linux/watchdog.h> 19#include <linux/ioport.h> 20#include <linux/delay.h> 21#include <linux/fs.h> 22#include <linux/init.h> 23#include <linux/moduleparam.h> 24#include <linux/dmi.h> 25#include <linux/io.h> 26#include <linux/uaccess.h> 27 28 29static bool nowayout = WATCHDOG_NOWAYOUT; 30static unsigned int margin = 60; /* (secs) Default is 1 minute */ 31static unsigned long wdt_status; 32static DEFINE_MUTEX(wdt_lock); 33 34#define WDT_IN_USE 0 35#define WDT_OK_TO_CLOSE 1 36 37#define COMMAND_PORT 0x4c 38#define DATA_PORT 0x48 39 40#define IFACE_ON_COMMAND 1 41#define REBOOT_COMMAND 2 42 43#define WATCHDOG_NAME "SBC-FITPC2 Watchdog" 44 45static void wdt_send_data(unsigned char command, unsigned char data) 46{ 47 outb(data, DATA_PORT); 48 msleep(200); 49 outb(command, COMMAND_PORT); 50 msleep(100); 51} 52 53static void wdt_enable(void) 54{ 55 mutex_lock(&wdt_lock); 56 wdt_send_data(IFACE_ON_COMMAND, 1); 57 wdt_send_data(REBOOT_COMMAND, margin); 58 mutex_unlock(&wdt_lock); 59} 60 61static void wdt_disable(void) 62{ 63 mutex_lock(&wdt_lock); 64 wdt_send_data(IFACE_ON_COMMAND, 0); 65 wdt_send_data(REBOOT_COMMAND, 0); 66 mutex_unlock(&wdt_lock); 67} 68 69static int fitpc2_wdt_open(struct inode *inode, struct file *file) 70{ 71 if (test_and_set_bit(WDT_IN_USE, &wdt_status)) 72 return -EBUSY; 73 74 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 75 76 wdt_enable(); 77 78 return nonseekable_open(inode, file); 79} 80 81static ssize_t fitpc2_wdt_write(struct file *file, const char *data, 82 size_t len, loff_t *ppos) 83{ 84 size_t i; 85 86 if (!len) 87 return 0; 88 89 if (nowayout) { 90 len = 0; 91 goto out; 92 } 93 94 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 95 96 for (i = 0; i != len; i++) { 97 char c; 98 99 if (get_user(c, data + i)) 100 return -EFAULT; 101 102 if (c == 'V') 103 set_bit(WDT_OK_TO_CLOSE, &wdt_status); 104 } 105 106out: 107 wdt_enable(); 108 109 return len; 110} 111 112 113static const struct watchdog_info ident = { 114 .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | 115 WDIOF_KEEPALIVEPING, 116 .identity = WATCHDOG_NAME, 117}; 118 119 120static long fitpc2_wdt_ioctl(struct file *file, unsigned int cmd, 121 unsigned long arg) 122{ 123 int ret = -ENOTTY; 124 int time; 125 126 switch (cmd) { 127 case WDIOC_GETSUPPORT: 128 ret = copy_to_user((struct watchdog_info *)arg, &ident, 129 sizeof(ident)) ? -EFAULT : 0; 130 break; 131 132 case WDIOC_GETSTATUS: 133 ret = put_user(0, (int *)arg); 134 break; 135 136 case WDIOC_GETBOOTSTATUS: 137 ret = put_user(0, (int *)arg); 138 break; 139 140 case WDIOC_KEEPALIVE: 141 wdt_enable(); 142 ret = 0; 143 break; 144 145 case WDIOC_SETTIMEOUT: 146 ret = get_user(time, (int *)arg); 147 if (ret) 148 break; 149 150 if (time < 31 || time > 255) { 151 ret = -EINVAL; 152 break; 153 } 154 155 margin = time; 156 wdt_enable(); 157 /* Fall through */ 158 159 case WDIOC_GETTIMEOUT: 160 ret = put_user(margin, (int *)arg); 161 break; 162 } 163 164 return ret; 165} 166 167static int fitpc2_wdt_release(struct inode *inode, struct file *file) 168{ 169 if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) { 170 wdt_disable(); 171 pr_info("Device disabled\n"); 172 } else { 173 pr_warn("Device closed unexpectedly - timer will not stop\n"); 174 wdt_enable(); 175 } 176 177 clear_bit(WDT_IN_USE, &wdt_status); 178 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 179 180 return 0; 181} 182 183 184static const struct file_operations fitpc2_wdt_fops = { 185 .owner = THIS_MODULE, 186 .llseek = no_llseek, 187 .write = fitpc2_wdt_write, 188 .unlocked_ioctl = fitpc2_wdt_ioctl, 189 .open = fitpc2_wdt_open, 190 .release = fitpc2_wdt_release, 191}; 192 193static struct miscdevice fitpc2_wdt_miscdev = { 194 .minor = WATCHDOG_MINOR, 195 .name = "watchdog", 196 .fops = &fitpc2_wdt_fops, 197}; 198 199static int __init fitpc2_wdt_init(void) 200{ 201 int err; 202 const char *brd_name; 203 204 brd_name = dmi_get_system_info(DMI_BOARD_NAME); 205 206 if (!brd_name || !strstr(brd_name, "SBC-FITPC2")) 207 return -ENODEV; 208 209 pr_info("%s found\n", brd_name); 210 211 if (!request_region(COMMAND_PORT, 1, WATCHDOG_NAME)) { 212 pr_err("I/O address 0x%04x already in use\n", COMMAND_PORT); 213 return -EIO; 214 } 215 216 if (!request_region(DATA_PORT, 1, WATCHDOG_NAME)) { 217 pr_err("I/O address 0x%04x already in use\n", DATA_PORT); 218 err = -EIO; 219 goto err_data_port; 220 } 221 222 if (margin < 31 || margin > 255) { 223 pr_err("margin must be in range 31 - 255 seconds, you tried to set %d\n", 224 margin); 225 err = -EINVAL; 226 goto err_margin; 227 } 228 229 err = misc_register(&fitpc2_wdt_miscdev); 230 if (err) { 231 pr_err("cannot register miscdev on minor=%d (err=%d)\n", 232 WATCHDOG_MINOR, err); 233 goto err_margin; 234 } 235 236 return 0; 237 238err_margin: 239 release_region(DATA_PORT, 1); 240err_data_port: 241 release_region(COMMAND_PORT, 1); 242 243 return err; 244} 245 246static void __exit fitpc2_wdt_exit(void) 247{ 248 misc_deregister(&fitpc2_wdt_miscdev); 249 release_region(DATA_PORT, 1); 250 release_region(COMMAND_PORT, 1); 251} 252 253module_init(fitpc2_wdt_init); 254module_exit(fitpc2_wdt_exit); 255 256MODULE_AUTHOR("Denis Turischev <denis@compulab.co.il>"); 257MODULE_DESCRIPTION("SBC-FITPC2 Watchdog"); 258 259module_param(margin, int, 0); 260MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)"); 261 262module_param(nowayout, bool, 0); 263MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); 264 265MODULE_LICENSE("GPL"); 266