xref: /openbmc/linux/drivers/watchdog/sbc_fitpc2_wdt.c (revision 2612e3bbc0386368a850140a6c9b990cd496a5ec)
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