1*a0d261ccSBagas Sanjaya // SPDX-License-Identifier: GPL-2.0-only
23a5f9000SDenis Turischev /*
33a5f9000SDenis Turischev * Watchdog driver for SBC-FITPC2 board
43a5f9000SDenis Turischev *
53a5f9000SDenis Turischev * Author: Denis Turischev <denis@compulab.co.il>
63a5f9000SDenis Turischev *
73a5f9000SDenis Turischev * Adapted from the IXP2000 watchdog driver by Deepak Saxena.
83a5f9000SDenis Turischev *
93a5f9000SDenis Turischev */
103a5f9000SDenis Turischev
113a5f9000SDenis Turischev #define pr_fmt(fmt) KBUILD_MODNAME " WATCHDOG: " fmt
123a5f9000SDenis Turischev
133a5f9000SDenis Turischev #include <linux/module.h>
143a5f9000SDenis Turischev #include <linux/types.h>
153a5f9000SDenis Turischev #include <linux/miscdevice.h>
163a5f9000SDenis Turischev #include <linux/watchdog.h>
173a5f9000SDenis Turischev #include <linux/ioport.h>
183a5f9000SDenis Turischev #include <linux/delay.h>
193a5f9000SDenis Turischev #include <linux/fs.h>
203a5f9000SDenis Turischev #include <linux/init.h>
213a5f9000SDenis Turischev #include <linux/moduleparam.h>
223a5f9000SDenis Turischev #include <linux/dmi.h>
233a5f9000SDenis Turischev #include <linux/io.h>
243a5f9000SDenis Turischev #include <linux/uaccess.h>
253a5f9000SDenis Turischev
263a5f9000SDenis Turischev
2786a1e189SWim Van Sebroeck static bool nowayout = WATCHDOG_NOWAYOUT;
283a5f9000SDenis Turischev static unsigned int margin = 60; /* (secs) Default is 1 minute */
293a5f9000SDenis Turischev static unsigned long wdt_status;
30322af98cSDenis Turischev static DEFINE_MUTEX(wdt_lock);
313a5f9000SDenis Turischev
323a5f9000SDenis Turischev #define WDT_IN_USE 0
333a5f9000SDenis Turischev #define WDT_OK_TO_CLOSE 1
343a5f9000SDenis Turischev
353a5f9000SDenis Turischev #define COMMAND_PORT 0x4c
363a5f9000SDenis Turischev #define DATA_PORT 0x48
373a5f9000SDenis Turischev
383a5f9000SDenis Turischev #define IFACE_ON_COMMAND 1
393a5f9000SDenis Turischev #define REBOOT_COMMAND 2
403a5f9000SDenis Turischev
413a5f9000SDenis Turischev #define WATCHDOG_NAME "SBC-FITPC2 Watchdog"
423a5f9000SDenis Turischev
wdt_send_data(unsigned char command,unsigned char data)433a5f9000SDenis Turischev static void wdt_send_data(unsigned char command, unsigned char data)
443a5f9000SDenis Turischev {
453a5f9000SDenis Turischev outb(data, DATA_PORT);
46ef39a1bfSDenis Turischev msleep(200);
47fcf1dd7eSDenis Turischev outb(command, COMMAND_PORT);
48fcf1dd7eSDenis Turischev msleep(100);
493a5f9000SDenis Turischev }
503a5f9000SDenis Turischev
wdt_enable(void)513a5f9000SDenis Turischev static void wdt_enable(void)
523a5f9000SDenis Turischev {
53322af98cSDenis Turischev mutex_lock(&wdt_lock);
543a5f9000SDenis Turischev wdt_send_data(IFACE_ON_COMMAND, 1);
553a5f9000SDenis Turischev wdt_send_data(REBOOT_COMMAND, margin);
56322af98cSDenis Turischev mutex_unlock(&wdt_lock);
573a5f9000SDenis Turischev }
583a5f9000SDenis Turischev
wdt_disable(void)593a5f9000SDenis Turischev static void wdt_disable(void)
603a5f9000SDenis Turischev {
61322af98cSDenis Turischev mutex_lock(&wdt_lock);
623a5f9000SDenis Turischev wdt_send_data(IFACE_ON_COMMAND, 0);
633a5f9000SDenis Turischev wdt_send_data(REBOOT_COMMAND, 0);
64322af98cSDenis Turischev mutex_unlock(&wdt_lock);
653a5f9000SDenis Turischev }
663a5f9000SDenis Turischev
fitpc2_wdt_open(struct inode * inode,struct file * file)673a5f9000SDenis Turischev static int fitpc2_wdt_open(struct inode *inode, struct file *file)
683a5f9000SDenis Turischev {
693a5f9000SDenis Turischev if (test_and_set_bit(WDT_IN_USE, &wdt_status))
703a5f9000SDenis Turischev return -EBUSY;
713a5f9000SDenis Turischev
723a5f9000SDenis Turischev clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
733a5f9000SDenis Turischev
743a5f9000SDenis Turischev wdt_enable();
753a5f9000SDenis Turischev
76c5bf68feSKirill Smelkov return stream_open(inode, file);
773a5f9000SDenis Turischev }
783a5f9000SDenis Turischev
fitpc2_wdt_write(struct file * file,const char __user * data,size_t len,loff_t * ppos)79347755d2SRasmus Villemoes static ssize_t fitpc2_wdt_write(struct file *file, const char __user *data,
803a5f9000SDenis Turischev size_t len, loff_t *ppos)
813a5f9000SDenis Turischev {
823a5f9000SDenis Turischev size_t i;
833a5f9000SDenis Turischev
843a5f9000SDenis Turischev if (!len)
853a5f9000SDenis Turischev return 0;
863a5f9000SDenis Turischev
873a5f9000SDenis Turischev if (nowayout) {
883a5f9000SDenis Turischev len = 0;
893a5f9000SDenis Turischev goto out;
903a5f9000SDenis Turischev }
913a5f9000SDenis Turischev
923a5f9000SDenis Turischev clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
933a5f9000SDenis Turischev
943a5f9000SDenis Turischev for (i = 0; i != len; i++) {
953a5f9000SDenis Turischev char c;
963a5f9000SDenis Turischev
973a5f9000SDenis Turischev if (get_user(c, data + i))
983a5f9000SDenis Turischev return -EFAULT;
993a5f9000SDenis Turischev
1003a5f9000SDenis Turischev if (c == 'V')
1013a5f9000SDenis Turischev set_bit(WDT_OK_TO_CLOSE, &wdt_status);
1023a5f9000SDenis Turischev }
1033a5f9000SDenis Turischev
1043a5f9000SDenis Turischev out:
1053a5f9000SDenis Turischev wdt_enable();
1063a5f9000SDenis Turischev
1073a5f9000SDenis Turischev return len;
1083a5f9000SDenis Turischev }
1093a5f9000SDenis Turischev
1103a5f9000SDenis Turischev
11142747d71SWim Van Sebroeck static const struct watchdog_info ident = {
1123a5f9000SDenis Turischev .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT |
1133a5f9000SDenis Turischev WDIOF_KEEPALIVEPING,
1143a5f9000SDenis Turischev .identity = WATCHDOG_NAME,
1153a5f9000SDenis Turischev };
1163a5f9000SDenis Turischev
1173a5f9000SDenis Turischev
fitpc2_wdt_ioctl(struct file * file,unsigned int cmd,unsigned long arg)1183a5f9000SDenis Turischev static long fitpc2_wdt_ioctl(struct file *file, unsigned int cmd,
1193a5f9000SDenis Turischev unsigned long arg)
1203a5f9000SDenis Turischev {
1213a5f9000SDenis Turischev int ret = -ENOTTY;
1223a5f9000SDenis Turischev int time;
1233a5f9000SDenis Turischev
1243a5f9000SDenis Turischev switch (cmd) {
1253a5f9000SDenis Turischev case WDIOC_GETSUPPORT:
126347755d2SRasmus Villemoes ret = copy_to_user((struct watchdog_info __user *)arg, &ident,
1273a5f9000SDenis Turischev sizeof(ident)) ? -EFAULT : 0;
1283a5f9000SDenis Turischev break;
1293a5f9000SDenis Turischev
1303a5f9000SDenis Turischev case WDIOC_GETSTATUS:
131347755d2SRasmus Villemoes ret = put_user(0, (int __user *)arg);
1323a5f9000SDenis Turischev break;
1333a5f9000SDenis Turischev
1343a5f9000SDenis Turischev case WDIOC_GETBOOTSTATUS:
135347755d2SRasmus Villemoes ret = put_user(0, (int __user *)arg);
1363a5f9000SDenis Turischev break;
1373a5f9000SDenis Turischev
1383a5f9000SDenis Turischev case WDIOC_KEEPALIVE:
1393a5f9000SDenis Turischev wdt_enable();
1403a5f9000SDenis Turischev ret = 0;
1413a5f9000SDenis Turischev break;
1423a5f9000SDenis Turischev
1433a5f9000SDenis Turischev case WDIOC_SETTIMEOUT:
144347755d2SRasmus Villemoes ret = get_user(time, (int __user *)arg);
1453a5f9000SDenis Turischev if (ret)
1463a5f9000SDenis Turischev break;
1473a5f9000SDenis Turischev
1483a5f9000SDenis Turischev if (time < 31 || time > 255) {
1493a5f9000SDenis Turischev ret = -EINVAL;
1503a5f9000SDenis Turischev break;
1513a5f9000SDenis Turischev }
1523a5f9000SDenis Turischev
1533a5f9000SDenis Turischev margin = time;
1543a5f9000SDenis Turischev wdt_enable();
155bd490f82SGustavo A. R. Silva fallthrough;
1563a5f9000SDenis Turischev
1573a5f9000SDenis Turischev case WDIOC_GETTIMEOUT:
158347755d2SRasmus Villemoes ret = put_user(margin, (int __user *)arg);
1593a5f9000SDenis Turischev break;
1603a5f9000SDenis Turischev }
1613a5f9000SDenis Turischev
1623a5f9000SDenis Turischev return ret;
1633a5f9000SDenis Turischev }
1643a5f9000SDenis Turischev
fitpc2_wdt_release(struct inode * inode,struct file * file)1653a5f9000SDenis Turischev static int fitpc2_wdt_release(struct inode *inode, struct file *file)
1663a5f9000SDenis Turischev {
1673a5f9000SDenis Turischev if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) {
1683a5f9000SDenis Turischev wdt_disable();
1693a5f9000SDenis Turischev pr_info("Device disabled\n");
1703a5f9000SDenis Turischev } else {
17127c766aaSJoe Perches pr_warn("Device closed unexpectedly - timer will not stop\n");
1723a5f9000SDenis Turischev wdt_enable();
1733a5f9000SDenis Turischev }
1743a5f9000SDenis Turischev
1753a5f9000SDenis Turischev clear_bit(WDT_IN_USE, &wdt_status);
1763a5f9000SDenis Turischev clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
1773a5f9000SDenis Turischev
1783a5f9000SDenis Turischev return 0;
1793a5f9000SDenis Turischev }
1803a5f9000SDenis Turischev
1813a5f9000SDenis Turischev
1823a5f9000SDenis Turischev static const struct file_operations fitpc2_wdt_fops = {
1833a5f9000SDenis Turischev .owner = THIS_MODULE,
1843a5f9000SDenis Turischev .llseek = no_llseek,
1853a5f9000SDenis Turischev .write = fitpc2_wdt_write,
1863a5f9000SDenis Turischev .unlocked_ioctl = fitpc2_wdt_ioctl,
187b6dfb247SArnd Bergmann .compat_ioctl = compat_ptr_ioctl,
1883a5f9000SDenis Turischev .open = fitpc2_wdt_open,
1893a5f9000SDenis Turischev .release = fitpc2_wdt_release,
1903a5f9000SDenis Turischev };
1913a5f9000SDenis Turischev
1923a5f9000SDenis Turischev static struct miscdevice fitpc2_wdt_miscdev = {
1933a5f9000SDenis Turischev .minor = WATCHDOG_MINOR,
1943a5f9000SDenis Turischev .name = "watchdog",
1953a5f9000SDenis Turischev .fops = &fitpc2_wdt_fops,
1963a5f9000SDenis Turischev };
1973a5f9000SDenis Turischev
fitpc2_wdt_init(void)1983a5f9000SDenis Turischev static int __init fitpc2_wdt_init(void)
1993a5f9000SDenis Turischev {
2003a5f9000SDenis Turischev int err;
201d4065775SJiri Slaby const char *brd_name;
2023a5f9000SDenis Turischev
203d4065775SJiri Slaby brd_name = dmi_get_system_info(DMI_BOARD_NAME);
204d4065775SJiri Slaby
205d4065775SJiri Slaby if (!brd_name || !strstr(brd_name, "SBC-FITPC2"))
2063a5f9000SDenis Turischev return -ENODEV;
207ef39a1bfSDenis Turischev
208d4065775SJiri Slaby pr_info("%s found\n", brd_name);
2093a5f9000SDenis Turischev
2103a5f9000SDenis Turischev if (!request_region(COMMAND_PORT, 1, WATCHDOG_NAME)) {
2113a5f9000SDenis Turischev pr_err("I/O address 0x%04x already in use\n", COMMAND_PORT);
2123a5f9000SDenis Turischev return -EIO;
2133a5f9000SDenis Turischev }
2143a5f9000SDenis Turischev
2153a5f9000SDenis Turischev if (!request_region(DATA_PORT, 1, WATCHDOG_NAME)) {
2163a5f9000SDenis Turischev pr_err("I/O address 0x%04x already in use\n", DATA_PORT);
2173a5f9000SDenis Turischev err = -EIO;
2183a5f9000SDenis Turischev goto err_data_port;
2193a5f9000SDenis Turischev }
2203a5f9000SDenis Turischev
2213a5f9000SDenis Turischev if (margin < 31 || margin > 255) {
22227c766aaSJoe Perches pr_err("margin must be in range 31 - 255 seconds, you tried to set %d\n",
22327c766aaSJoe Perches margin);
2243a5f9000SDenis Turischev err = -EINVAL;
2253a5f9000SDenis Turischev goto err_margin;
2263a5f9000SDenis Turischev }
2273a5f9000SDenis Turischev
2283a5f9000SDenis Turischev err = misc_register(&fitpc2_wdt_miscdev);
2291efd374dSDenis Turischev if (err) {
2303a5f9000SDenis Turischev pr_err("cannot register miscdev on minor=%d (err=%d)\n",
2313a5f9000SDenis Turischev WATCHDOG_MINOR, err);
2323a5f9000SDenis Turischev goto err_margin;
2333a5f9000SDenis Turischev }
2343a5f9000SDenis Turischev
2353a5f9000SDenis Turischev return 0;
2363a5f9000SDenis Turischev
2373a5f9000SDenis Turischev err_margin:
2383a5f9000SDenis Turischev release_region(DATA_PORT, 1);
2393a5f9000SDenis Turischev err_data_port:
2403a5f9000SDenis Turischev release_region(COMMAND_PORT, 1);
2413a5f9000SDenis Turischev
2423a5f9000SDenis Turischev return err;
2433a5f9000SDenis Turischev }
2443a5f9000SDenis Turischev
fitpc2_wdt_exit(void)2453a5f9000SDenis Turischev static void __exit fitpc2_wdt_exit(void)
2463a5f9000SDenis Turischev {
2473a5f9000SDenis Turischev misc_deregister(&fitpc2_wdt_miscdev);
2483a5f9000SDenis Turischev release_region(DATA_PORT, 1);
2493a5f9000SDenis Turischev release_region(COMMAND_PORT, 1);
2503a5f9000SDenis Turischev }
2513a5f9000SDenis Turischev
2523a5f9000SDenis Turischev module_init(fitpc2_wdt_init);
2533a5f9000SDenis Turischev module_exit(fitpc2_wdt_exit);
2543a5f9000SDenis Turischev
2553a5f9000SDenis Turischev MODULE_AUTHOR("Denis Turischev <denis@compulab.co.il>");
2563a5f9000SDenis Turischev MODULE_DESCRIPTION("SBC-FITPC2 Watchdog");
2573a5f9000SDenis Turischev
2583a5f9000SDenis Turischev module_param(margin, int, 0);
2593a5f9000SDenis Turischev MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)");
2603a5f9000SDenis Turischev
26186a1e189SWim Van Sebroeck module_param(nowayout, bool, 0);
2623a5f9000SDenis Turischev MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
2633a5f9000SDenis Turischev
2643a5f9000SDenis Turischev MODULE_LICENSE("GPL");
265