xref: /openbmc/linux/drivers/watchdog/gef_wdt.c (revision 3268b561)
13268b561SMartyn Welch /*
23268b561SMartyn Welch  * GE Fanuc watchdog userspace interface
33268b561SMartyn Welch  *
43268b561SMartyn Welch  * Author:  Martyn Welch <martyn.welch@gefanuc.com>
53268b561SMartyn Welch  *
63268b561SMartyn Welch  * Copyright 2008 GE Fanuc Intelligent Platforms Embedded Systems, Inc.
73268b561SMartyn Welch  *
83268b561SMartyn Welch  * This program is free software; you can redistribute  it and/or modify it
93268b561SMartyn Welch  * under  the terms of  the GNU General  Public License as published by the
103268b561SMartyn Welch  * Free Software Foundation;  either version 2 of the  License, or (at your
113268b561SMartyn Welch  * option) any later version.
123268b561SMartyn Welch  *
133268b561SMartyn Welch  * Based on: mv64x60_wdt.c (MV64X60 watchdog userspace interface)
143268b561SMartyn Welch  *   Author: James Chapman <jchapman@katalix.com>
153268b561SMartyn Welch  */
163268b561SMartyn Welch 
173268b561SMartyn Welch /* TODO:
183268b561SMartyn Welch  * This driver does not provide support for the hardwares capability of sending
193268b561SMartyn Welch  * an interrupt at a programmable threshold.
203268b561SMartyn Welch  *
213268b561SMartyn Welch  * This driver currently can only support 1 watchdog - there are 2 in the
223268b561SMartyn Welch  * hardware that this driver supports. Thus one could be configured as a
233268b561SMartyn Welch  * process-based watchdog (via /dev/watchdog), the second (using the interrupt
243268b561SMartyn Welch  * capabilities) a kernel-based watchdog.
253268b561SMartyn Welch  */
263268b561SMartyn Welch 
273268b561SMartyn Welch #include <linux/kernel.h>
283268b561SMartyn Welch #include <linux/compiler.h>
293268b561SMartyn Welch #include <linux/init.h>
303268b561SMartyn Welch #include <linux/module.h>
313268b561SMartyn Welch #include <linux/miscdevice.h>
323268b561SMartyn Welch #include <linux/watchdog.h>
333268b561SMartyn Welch #include <linux/of.h>
343268b561SMartyn Welch #include <linux/of_platform.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 
703268b561SMartyn Welch static int nowayout = WATCHDOG_NOWAYOUT;
713268b561SMartyn Welch module_param(nowayout, int, 0);
723268b561SMartyn Welch MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
733268b561SMartyn Welch 	__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
743268b561SMartyn Welch 
753268b561SMartyn Welch 
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 
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 
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();
1123268b561SMartyn Welch 		printk(KERN_NOTICE "gef_wdt: watchdog activated\n");
1133268b561SMartyn Welch 	}
1143268b561SMartyn Welch }
1153268b561SMartyn Welch 
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))
1203268b561SMartyn Welch 		printk(KERN_NOTICE "gef_wdt: watchdog deactivated\n");
1213268b561SMartyn Welch }
1223268b561SMartyn Welch 
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 
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 
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;
1643268b561SMartyn Welch 	static struct watchdog_info info = {
1653268b561SMartyn Welch 		.options =	WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE |
1663268b561SMartyn Welch 				WDIOF_KEEPALIVEPING,
1673268b561SMartyn Welch 		.firmware_version = 0,
1683268b561SMartyn Welch 		.identity = "GE Fanuc 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);
2043268b561SMartyn Welch 		/* Fall through */
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 
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 
2283268b561SMartyn Welch 	return nonseekable_open(inode, file);
2293268b561SMartyn Welch }
2303268b561SMartyn Welch 
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 {
2363268b561SMartyn Welch 		printk(KERN_CRIT
2373268b561SMartyn Welch 		       "gef_wdt: unexpected close, not stopping timer!\n");
2383268b561SMartyn Welch 		gef_wdt_service();
2393268b561SMartyn Welch 	}
2403268b561SMartyn Welch 	expect_close = 0;
2413268b561SMartyn Welch 
2423268b561SMartyn Welch 	clear_bit(GEF_WDOG_FLAG_OPENED, &wdt_flags);
2433268b561SMartyn Welch 
2443268b561SMartyn Welch 	return 0;
2453268b561SMartyn Welch }
2463268b561SMartyn Welch 
2473268b561SMartyn Welch static const struct file_operations gef_wdt_fops = {
2483268b561SMartyn Welch 	.owner = THIS_MODULE,
2493268b561SMartyn Welch 	.llseek = no_llseek,
2503268b561SMartyn Welch 	.write = gef_wdt_write,
2513268b561SMartyn Welch 	.unlocked_ioctl = gef_wdt_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 
2633268b561SMartyn Welch static int __devinit gef_wdt_probe(struct of_device *dev,
2643268b561SMartyn Welch 	const struct of_device_id *match)
2653268b561SMartyn Welch {
2663268b561SMartyn Welch 	int timeout = 10;
2673268b561SMartyn Welch 	u32 freq;
2683268b561SMartyn Welch 
2693268b561SMartyn Welch 	bus_clk = 133; /* in MHz */
2703268b561SMartyn Welch 
2713268b561SMartyn Welch 	freq = fsl_get_sys_freq();
2723268b561SMartyn Welch 	if (freq > 0)
2733268b561SMartyn Welch 		bus_clk = freq;
2743268b561SMartyn Welch 
2753268b561SMartyn Welch 	/* Map devices registers into memory */
2763268b561SMartyn Welch 	gef_wdt_regs = of_iomap(dev->node, 0);
2773268b561SMartyn Welch 	if (gef_wdt_regs == NULL)
2783268b561SMartyn Welch 		return -ENOMEM;
2793268b561SMartyn Welch 
2803268b561SMartyn Welch 	gef_wdt_set_timeout(timeout);
2813268b561SMartyn Welch 
2823268b561SMartyn Welch 	gef_wdt_handler_disable();	/* in case timer was already running */
2833268b561SMartyn Welch 
2843268b561SMartyn Welch 	return misc_register(&gef_wdt_miscdev);
2853268b561SMartyn Welch }
2863268b561SMartyn Welch 
2873268b561SMartyn Welch static int __devexit gef_wdt_remove(struct platform_device *dev)
2883268b561SMartyn Welch {
2893268b561SMartyn Welch 	misc_deregister(&gef_wdt_miscdev);
2903268b561SMartyn Welch 
2913268b561SMartyn Welch 	gef_wdt_handler_disable();
2923268b561SMartyn Welch 
2933268b561SMartyn Welch 	iounmap(gef_wdt_regs);
2943268b561SMartyn Welch 
2953268b561SMartyn Welch 	return 0;
2963268b561SMartyn Welch }
2973268b561SMartyn Welch 
2983268b561SMartyn Welch static const struct of_device_id gef_wdt_ids[] = {
2993268b561SMartyn Welch 	{
3003268b561SMartyn Welch 		.compatible = "gef,fpga-wdt",
3013268b561SMartyn Welch 	},
3023268b561SMartyn Welch 	{},
3033268b561SMartyn Welch };
3043268b561SMartyn Welch 
3053268b561SMartyn Welch static struct of_platform_driver gef_wdt_driver = {
3063268b561SMartyn Welch 	.owner		= THIS_MODULE,
3073268b561SMartyn Welch 	.name		= "gef_wdt",
3083268b561SMartyn Welch 	.match_table	= gef_wdt_ids,
3093268b561SMartyn Welch 	.probe		= gef_wdt_probe,
3103268b561SMartyn Welch };
3113268b561SMartyn Welch 
3123268b561SMartyn Welch static int __init gef_wdt_init(void)
3133268b561SMartyn Welch {
3143268b561SMartyn Welch 	printk(KERN_INFO "GE Fanuc watchdog driver\n");
3153268b561SMartyn Welch 	return of_register_platform_driver(&gef_wdt_driver);
3163268b561SMartyn Welch }
3173268b561SMartyn Welch 
3183268b561SMartyn Welch static void __exit gef_wdt_exit(void)
3193268b561SMartyn Welch {
3203268b561SMartyn Welch 	of_unregister_platform_driver(&gef_wdt_driver);
3213268b561SMartyn Welch }
3223268b561SMartyn Welch 
3233268b561SMartyn Welch module_init(gef_wdt_init);
3243268b561SMartyn Welch module_exit(gef_wdt_exit);
3253268b561SMartyn Welch 
3263268b561SMartyn Welch MODULE_AUTHOR("Martyn Welch <martyn.welch@gefanuc.com>");
3273268b561SMartyn Welch MODULE_DESCRIPTION("GE Fanuc watchdog driver");
3283268b561SMartyn Welch MODULE_LICENSE("GPL");
3293268b561SMartyn Welch MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
3303268b561SMartyn Welch MODULE_ALIAS("platform: gef_wdt");
331