xref: /openbmc/linux/drivers/watchdog/gef_wdt.c (revision cc85f87a)
12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
23268b561SMartyn Welch /*
3cda61c94SMartyn Welch  * GE watchdog userspace interface
43268b561SMartyn Welch  *
5cda61c94SMartyn Welch  * Author:  Martyn Welch <martyn.welch@ge.com>
63268b561SMartyn Welch  *
7cda61c94SMartyn Welch  * Copyright 2008 GE Intelligent Platforms Embedded Systems, Inc.
83268b561SMartyn Welch  *
93268b561SMartyn Welch  * Based on: mv64x60_wdt.c (MV64X60 watchdog userspace interface)
103268b561SMartyn Welch  *   Author: James Chapman <jchapman@katalix.com>
113268b561SMartyn Welch  */
123268b561SMartyn Welch 
133268b561SMartyn Welch /* TODO:
143268b561SMartyn Welch  * This driver does not provide support for the hardwares capability of sending
153268b561SMartyn Welch  * an interrupt at a programmable threshold.
163268b561SMartyn Welch  *
173268b561SMartyn Welch  * This driver currently can only support 1 watchdog - there are 2 in the
183268b561SMartyn Welch  * hardware that this driver supports. Thus one could be configured as a
193268b561SMartyn Welch  * process-based watchdog (via /dev/watchdog), the second (using the interrupt
203268b561SMartyn Welch  * capabilities) a kernel-based watchdog.
213268b561SMartyn Welch  */
223268b561SMartyn Welch 
2327c766aaSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2427c766aaSJoe Perches 
253268b561SMartyn Welch #include <linux/kernel.h>
263268b561SMartyn Welch #include <linux/compiler.h>
273268b561SMartyn Welch #include <linux/init.h>
283268b561SMartyn Welch #include <linux/module.h>
293268b561SMartyn Welch #include <linux/miscdevice.h>
303268b561SMartyn Welch #include <linux/watchdog.h>
31f6e0722fSWolfram Sang & Martyn Welch #include <linux/fs.h>
323268b561SMartyn Welch #include <linux/of.h>
335af50730SRob Herring #include <linux/of_address.h>
34*cc85f87aSRob Herring #include <linux/platform_device.h>
353268b561SMartyn Welch #include <linux/io.h>
363268b561SMartyn Welch #include <linux/uaccess.h>
373268b561SMartyn Welch 
383268b561SMartyn Welch #include <sysdev/fsl_soc.h>
393268b561SMartyn Welch 
403268b561SMartyn Welch /*
413268b561SMartyn Welch  * The watchdog configuration register contains a pair of 2-bit fields,
423268b561SMartyn Welch  *   1.  a reload field, bits 27-26, which triggers a reload of
433268b561SMartyn Welch  *       the countdown register, and
443268b561SMartyn Welch  *   2.  an enable field, bits 25-24, which toggles between
453268b561SMartyn Welch  *       enabling and disabling the watchdog timer.
463268b561SMartyn Welch  * Bit 31 is a read-only field which indicates whether the
473268b561SMartyn Welch  * watchdog timer is currently enabled.
483268b561SMartyn Welch  *
493268b561SMartyn Welch  * The low 24 bits contain the timer reload value.
503268b561SMartyn Welch  */
513268b561SMartyn Welch #define GEF_WDC_ENABLE_SHIFT	24
523268b561SMartyn Welch #define GEF_WDC_SERVICE_SHIFT	26
533268b561SMartyn Welch #define GEF_WDC_ENABLED_SHIFT	31
543268b561SMartyn Welch 
553268b561SMartyn Welch #define GEF_WDC_ENABLED_TRUE	1
563268b561SMartyn Welch #define GEF_WDC_ENABLED_FALSE	0
573268b561SMartyn Welch 
583268b561SMartyn Welch /* Flags bits */
593268b561SMartyn Welch #define GEF_WDOG_FLAG_OPENED	0
603268b561SMartyn Welch 
613268b561SMartyn Welch static unsigned long wdt_flags;
623268b561SMartyn Welch static int wdt_status;
633268b561SMartyn Welch static void __iomem *gef_wdt_regs;
643268b561SMartyn Welch static int gef_wdt_timeout;
653268b561SMartyn Welch static int gef_wdt_count;
663268b561SMartyn Welch static unsigned int bus_clk;
673268b561SMartyn Welch static char expect_close;
683268b561SMartyn Welch static DEFINE_SPINLOCK(gef_wdt_spinlock);
693268b561SMartyn Welch 
7086a1e189SWim Van Sebroeck static bool nowayout = WATCHDOG_NOWAYOUT;
7186a1e189SWim Van Sebroeck module_param(nowayout, bool, 0);
723268b561SMartyn Welch MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
733268b561SMartyn Welch 	__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
743268b561SMartyn Welch 
753268b561SMartyn Welch 
gef_wdt_toggle_wdc(int enabled_predicate,int field_shift)763268b561SMartyn Welch static int gef_wdt_toggle_wdc(int enabled_predicate, int field_shift)
773268b561SMartyn Welch {
783268b561SMartyn Welch 	u32 data;
793268b561SMartyn Welch 	u32 enabled;
803268b561SMartyn Welch 	int ret = 0;
813268b561SMartyn Welch 
823268b561SMartyn Welch 	spin_lock(&gef_wdt_spinlock);
833268b561SMartyn Welch 	data = ioread32be(gef_wdt_regs);
843268b561SMartyn Welch 	enabled = (data >> GEF_WDC_ENABLED_SHIFT) & 1;
853268b561SMartyn Welch 
863268b561SMartyn Welch 	/* only toggle the requested field if enabled state matches predicate */
873268b561SMartyn Welch 	if ((enabled ^ enabled_predicate) == 0) {
883268b561SMartyn Welch 		/* We write a 1, then a 2 -- to the appropriate field */
893268b561SMartyn Welch 		data = (1 << field_shift) | gef_wdt_count;
903268b561SMartyn Welch 		iowrite32be(data, gef_wdt_regs);
913268b561SMartyn Welch 
923268b561SMartyn Welch 		data = (2 << field_shift) | gef_wdt_count;
933268b561SMartyn Welch 		iowrite32be(data, gef_wdt_regs);
943268b561SMartyn Welch 		ret = 1;
953268b561SMartyn Welch 	}
963268b561SMartyn Welch 	spin_unlock(&gef_wdt_spinlock);
973268b561SMartyn Welch 
983268b561SMartyn Welch 	return ret;
993268b561SMartyn Welch }
1003268b561SMartyn Welch 
gef_wdt_service(void)1013268b561SMartyn Welch static void gef_wdt_service(void)
1023268b561SMartyn Welch {
1033268b561SMartyn Welch 	gef_wdt_toggle_wdc(GEF_WDC_ENABLED_TRUE,
1043268b561SMartyn Welch 		GEF_WDC_SERVICE_SHIFT);
1053268b561SMartyn Welch }
1063268b561SMartyn Welch 
gef_wdt_handler_enable(void)1073268b561SMartyn Welch static void gef_wdt_handler_enable(void)
1083268b561SMartyn Welch {
1093268b561SMartyn Welch 	if (gef_wdt_toggle_wdc(GEF_WDC_ENABLED_FALSE,
1103268b561SMartyn Welch 				   GEF_WDC_ENABLE_SHIFT)) {
1113268b561SMartyn Welch 		gef_wdt_service();
11227c766aaSJoe Perches 		pr_notice("watchdog activated\n");
1133268b561SMartyn Welch 	}
1143268b561SMartyn Welch }
1153268b561SMartyn Welch 
gef_wdt_handler_disable(void)1163268b561SMartyn Welch static void gef_wdt_handler_disable(void)
1173268b561SMartyn Welch {
1183268b561SMartyn Welch 	if (gef_wdt_toggle_wdc(GEF_WDC_ENABLED_TRUE,
1193268b561SMartyn Welch 				   GEF_WDC_ENABLE_SHIFT))
12027c766aaSJoe Perches 		pr_notice("watchdog deactivated\n");
1213268b561SMartyn Welch }
1223268b561SMartyn Welch 
gef_wdt_set_timeout(unsigned int timeout)1233268b561SMartyn Welch static void gef_wdt_set_timeout(unsigned int timeout)
1243268b561SMartyn Welch {
1253268b561SMartyn Welch 	/* maximum bus cycle count is 0xFFFFFFFF */
1263268b561SMartyn Welch 	if (timeout > 0xFFFFFFFF / bus_clk)
1273268b561SMartyn Welch 		timeout = 0xFFFFFFFF / bus_clk;
1283268b561SMartyn Welch 
1293268b561SMartyn Welch 	/* Register only holds upper 24 bits, bit shifted into lower 24 */
1303268b561SMartyn Welch 	gef_wdt_count = (timeout * bus_clk) >> 8;
1313268b561SMartyn Welch 	gef_wdt_timeout = timeout;
1323268b561SMartyn Welch }
1333268b561SMartyn Welch 
1343268b561SMartyn Welch 
gef_wdt_write(struct file * file,const char __user * data,size_t len,loff_t * ppos)1353268b561SMartyn Welch static ssize_t gef_wdt_write(struct file *file, const char __user *data,
1363268b561SMartyn Welch 				 size_t len, loff_t *ppos)
1373268b561SMartyn Welch {
1383268b561SMartyn Welch 	if (len) {
1393268b561SMartyn Welch 		if (!nowayout) {
1403268b561SMartyn Welch 			size_t i;
1413268b561SMartyn Welch 
1423268b561SMartyn Welch 			expect_close = 0;
1433268b561SMartyn Welch 
1443268b561SMartyn Welch 			for (i = 0; i != len; i++) {
1453268b561SMartyn Welch 				char c;
1463268b561SMartyn Welch 				if (get_user(c, data + i))
1473268b561SMartyn Welch 					return -EFAULT;
1483268b561SMartyn Welch 				if (c == 'V')
1493268b561SMartyn Welch 					expect_close = 42;
1503268b561SMartyn Welch 			}
1513268b561SMartyn Welch 		}
1523268b561SMartyn Welch 		gef_wdt_service();
1533268b561SMartyn Welch 	}
1543268b561SMartyn Welch 
1553268b561SMartyn Welch 	return len;
1563268b561SMartyn Welch }
1573268b561SMartyn Welch 
gef_wdt_ioctl(struct file * file,unsigned int cmd,unsigned long arg)1583268b561SMartyn Welch static long gef_wdt_ioctl(struct file *file, unsigned int cmd,
1593268b561SMartyn Welch 							unsigned long arg)
1603268b561SMartyn Welch {
1613268b561SMartyn Welch 	int timeout;
1623268b561SMartyn Welch 	int options;
1633268b561SMartyn Welch 	void __user *argp = (void __user *)arg;
16442747d71SWim Van Sebroeck 	static const struct watchdog_info info = {
1653268b561SMartyn Welch 		.options =	WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE |
1663268b561SMartyn Welch 				WDIOF_KEEPALIVEPING,
1673268b561SMartyn Welch 		.firmware_version = 0,
168cda61c94SMartyn Welch 		.identity = "GE watchdog",
1693268b561SMartyn Welch 	};
1703268b561SMartyn Welch 
1713268b561SMartyn Welch 	switch (cmd) {
1723268b561SMartyn Welch 	case WDIOC_GETSUPPORT:
1733268b561SMartyn Welch 		if (copy_to_user(argp, &info, sizeof(info)))
1743268b561SMartyn Welch 			return -EFAULT;
1753268b561SMartyn Welch 		break;
1763268b561SMartyn Welch 
1773268b561SMartyn Welch 	case WDIOC_GETSTATUS:
1783268b561SMartyn Welch 	case WDIOC_GETBOOTSTATUS:
1793268b561SMartyn Welch 		if (put_user(wdt_status, (int __user *)argp))
1803268b561SMartyn Welch 			return -EFAULT;
1813268b561SMartyn Welch 		wdt_status &= ~WDIOF_KEEPALIVEPING;
1823268b561SMartyn Welch 		break;
1833268b561SMartyn Welch 
1843268b561SMartyn Welch 	case WDIOC_SETOPTIONS:
1853268b561SMartyn Welch 		if (get_user(options, (int __user *)argp))
1863268b561SMartyn Welch 			return -EFAULT;
1873268b561SMartyn Welch 
1883268b561SMartyn Welch 		if (options & WDIOS_DISABLECARD)
1893268b561SMartyn Welch 			gef_wdt_handler_disable();
1903268b561SMartyn Welch 
1913268b561SMartyn Welch 		if (options & WDIOS_ENABLECARD)
1923268b561SMartyn Welch 			gef_wdt_handler_enable();
1933268b561SMartyn Welch 		break;
1943268b561SMartyn Welch 
1953268b561SMartyn Welch 	case WDIOC_KEEPALIVE:
1963268b561SMartyn Welch 		gef_wdt_service();
1973268b561SMartyn Welch 		wdt_status |= WDIOF_KEEPALIVEPING;
1983268b561SMartyn Welch 		break;
1993268b561SMartyn Welch 
2003268b561SMartyn Welch 	case WDIOC_SETTIMEOUT:
2013268b561SMartyn Welch 		if (get_user(timeout, (int __user *)argp))
2023268b561SMartyn Welch 			return -EFAULT;
2033268b561SMartyn Welch 		gef_wdt_set_timeout(timeout);
204bd490f82SGustavo A. R. Silva 		fallthrough;
2053268b561SMartyn Welch 
2063268b561SMartyn Welch 	case WDIOC_GETTIMEOUT:
2073268b561SMartyn Welch 		if (put_user(gef_wdt_timeout, (int __user *)argp))
2083268b561SMartyn Welch 			return -EFAULT;
2093268b561SMartyn Welch 		break;
2103268b561SMartyn Welch 
2113268b561SMartyn Welch 	default:
2123268b561SMartyn Welch 		return -ENOTTY;
2133268b561SMartyn Welch 	}
2143268b561SMartyn Welch 
2153268b561SMartyn Welch 	return 0;
2163268b561SMartyn Welch }
2173268b561SMartyn Welch 
gef_wdt_open(struct inode * inode,struct file * file)2183268b561SMartyn Welch static int gef_wdt_open(struct inode *inode, struct file *file)
2193268b561SMartyn Welch {
2203268b561SMartyn Welch 	if (test_and_set_bit(GEF_WDOG_FLAG_OPENED, &wdt_flags))
2213268b561SMartyn Welch 		return -EBUSY;
2223268b561SMartyn Welch 
2233268b561SMartyn Welch 	if (nowayout)
2243268b561SMartyn Welch 		__module_get(THIS_MODULE);
2253268b561SMartyn Welch 
2263268b561SMartyn Welch 	gef_wdt_handler_enable();
2273268b561SMartyn Welch 
228c5bf68feSKirill Smelkov 	return stream_open(inode, file);
2293268b561SMartyn Welch }
2303268b561SMartyn Welch 
gef_wdt_release(struct inode * inode,struct file * file)2313268b561SMartyn Welch static int gef_wdt_release(struct inode *inode, struct file *file)
2323268b561SMartyn Welch {
2333268b561SMartyn Welch 	if (expect_close == 42)
2343268b561SMartyn Welch 		gef_wdt_handler_disable();
2353268b561SMartyn Welch 	else {
23627c766aaSJoe Perches 		pr_crit("unexpected close, not stopping timer!\n");
2373268b561SMartyn Welch 		gef_wdt_service();
2383268b561SMartyn Welch 	}
2393268b561SMartyn Welch 	expect_close = 0;
2403268b561SMartyn Welch 
2413268b561SMartyn Welch 	clear_bit(GEF_WDOG_FLAG_OPENED, &wdt_flags);
2423268b561SMartyn Welch 
2433268b561SMartyn Welch 	return 0;
2443268b561SMartyn Welch }
2453268b561SMartyn Welch 
2463268b561SMartyn Welch static const struct file_operations gef_wdt_fops = {
2473268b561SMartyn Welch 	.owner = THIS_MODULE,
2483268b561SMartyn Welch 	.llseek = no_llseek,
2493268b561SMartyn Welch 	.write = gef_wdt_write,
2503268b561SMartyn Welch 	.unlocked_ioctl = gef_wdt_ioctl,
251b6dfb247SArnd Bergmann 	.compat_ioctl	= compat_ptr_ioctl,
2523268b561SMartyn Welch 	.open = gef_wdt_open,
2533268b561SMartyn Welch 	.release = gef_wdt_release,
2543268b561SMartyn Welch };
2553268b561SMartyn Welch 
2563268b561SMartyn Welch static struct miscdevice gef_wdt_miscdev = {
2573268b561SMartyn Welch 	.minor = WATCHDOG_MINOR,
2583268b561SMartyn Welch 	.name = "watchdog",
2593268b561SMartyn Welch 	.fops = &gef_wdt_fops,
2603268b561SMartyn Welch };
2613268b561SMartyn Welch 
2623268b561SMartyn Welch 
gef_wdt_probe(struct platform_device * dev)2632d991a16SBill Pemberton static int gef_wdt_probe(struct platform_device *dev)
2643268b561SMartyn Welch {
2653268b561SMartyn Welch 	int timeout = 10;
2663268b561SMartyn Welch 	u32 freq;
2673268b561SMartyn Welch 
2683268b561SMartyn Welch 	bus_clk = 133; /* in MHz */
2693268b561SMartyn Welch 
2703268b561SMartyn Welch 	freq = fsl_get_sys_freq();
27126952669SRoel Kluin 	if (freq != -1)
2723268b561SMartyn Welch 		bus_clk = freq;
2733268b561SMartyn Welch 
2743268b561SMartyn Welch 	/* Map devices registers into memory */
275b74dbf2aSAnatolij Gustschin 	gef_wdt_regs = of_iomap(dev->dev.of_node, 0);
2763268b561SMartyn Welch 	if (gef_wdt_regs == NULL)
2773268b561SMartyn Welch 		return -ENOMEM;
2783268b561SMartyn Welch 
2793268b561SMartyn Welch 	gef_wdt_set_timeout(timeout);
2803268b561SMartyn Welch 
2813268b561SMartyn Welch 	gef_wdt_handler_disable();	/* in case timer was already running */
2823268b561SMartyn Welch 
2833268b561SMartyn Welch 	return misc_register(&gef_wdt_miscdev);
2843268b561SMartyn Welch }
2853268b561SMartyn Welch 
gef_wdt_remove(struct platform_device * dev)286b5f2e366SUwe Kleine-König static void gef_wdt_remove(struct platform_device *dev)
2873268b561SMartyn Welch {
2883268b561SMartyn Welch 	misc_deregister(&gef_wdt_miscdev);
2893268b561SMartyn Welch 
2903268b561SMartyn Welch 	gef_wdt_handler_disable();
2913268b561SMartyn Welch 
2923268b561SMartyn Welch 	iounmap(gef_wdt_regs);
2933268b561SMartyn Welch }
2943268b561SMartyn Welch 
2953268b561SMartyn Welch static const struct of_device_id gef_wdt_ids[] = {
2963268b561SMartyn Welch 	{
2973268b561SMartyn Welch 		.compatible = "gef,fpga-wdt",
2983268b561SMartyn Welch 	},
2993268b561SMartyn Welch 	{},
3003268b561SMartyn Welch };
301c73318f4SLuis de Bethencourt MODULE_DEVICE_TABLE(of, gef_wdt_ids);
3023268b561SMartyn Welch 
3031c48a5c9SGrant Likely static struct platform_driver gef_wdt_driver = {
3044018294bSGrant Likely 	.driver = {
3053268b561SMartyn Welch 		.name = "gef_wdt",
3064018294bSGrant Likely 		.of_match_table = gef_wdt_ids,
3074018294bSGrant Likely 	},
3083268b561SMartyn Welch 	.probe		= gef_wdt_probe,
309b5f2e366SUwe Kleine-König 	.remove_new	= gef_wdt_remove,
3103268b561SMartyn Welch };
3113268b561SMartyn Welch 
gef_wdt_init(void)3123268b561SMartyn Welch static int __init gef_wdt_init(void)
3133268b561SMartyn Welch {
31427c766aaSJoe Perches 	pr_info("GE watchdog driver\n");
3151c48a5c9SGrant Likely 	return platform_driver_register(&gef_wdt_driver);
3163268b561SMartyn Welch }
3173268b561SMartyn Welch 
gef_wdt_exit(void)3183268b561SMartyn Welch static void __exit gef_wdt_exit(void)
3193268b561SMartyn Welch {
3201c48a5c9SGrant Likely 	platform_driver_unregister(&gef_wdt_driver);
3213268b561SMartyn Welch }
3223268b561SMartyn Welch 
3233268b561SMartyn Welch module_init(gef_wdt_init);
3243268b561SMartyn Welch module_exit(gef_wdt_exit);
3253268b561SMartyn Welch 
326cda61c94SMartyn Welch MODULE_AUTHOR("Martyn Welch <martyn.welch@ge.com>");
327cda61c94SMartyn Welch MODULE_DESCRIPTION("GE watchdog driver");
3283268b561SMartyn Welch MODULE_LICENSE("GPL");
3293268b561SMartyn Welch MODULE_ALIAS("platform:gef_wdt");
330