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