13268b561SMartyn Welch /* 2cda61c94SMartyn Welch * GE watchdog userspace interface 33268b561SMartyn Welch * 4cda61c94SMartyn Welch * Author: Martyn Welch <martyn.welch@ge.com> 53268b561SMartyn Welch * 6cda61c94SMartyn Welch * Copyright 2008 GE 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 2727c766aaSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 2827c766aaSJoe Perches 293268b561SMartyn Welch #include <linux/kernel.h> 303268b561SMartyn Welch #include <linux/compiler.h> 313268b561SMartyn Welch #include <linux/init.h> 323268b561SMartyn Welch #include <linux/module.h> 333268b561SMartyn Welch #include <linux/miscdevice.h> 343268b561SMartyn Welch #include <linux/watchdog.h> 35f6e0722fSWolfram Sang & Martyn Welch #include <linux/fs.h> 363268b561SMartyn Welch #include <linux/of.h> 373268b561SMartyn Welch #include <linux/of_platform.h> 383268b561SMartyn Welch #include <linux/io.h> 393268b561SMartyn Welch #include <linux/uaccess.h> 403268b561SMartyn Welch 413268b561SMartyn Welch #include <sysdev/fsl_soc.h> 423268b561SMartyn Welch 433268b561SMartyn Welch /* 443268b561SMartyn Welch * The watchdog configuration register contains a pair of 2-bit fields, 453268b561SMartyn Welch * 1. a reload field, bits 27-26, which triggers a reload of 463268b561SMartyn Welch * the countdown register, and 473268b561SMartyn Welch * 2. an enable field, bits 25-24, which toggles between 483268b561SMartyn Welch * enabling and disabling the watchdog timer. 493268b561SMartyn Welch * Bit 31 is a read-only field which indicates whether the 503268b561SMartyn Welch * watchdog timer is currently enabled. 513268b561SMartyn Welch * 523268b561SMartyn Welch * The low 24 bits contain the timer reload value. 533268b561SMartyn Welch */ 543268b561SMartyn Welch #define GEF_WDC_ENABLE_SHIFT 24 553268b561SMartyn Welch #define GEF_WDC_SERVICE_SHIFT 26 563268b561SMartyn Welch #define GEF_WDC_ENABLED_SHIFT 31 573268b561SMartyn Welch 583268b561SMartyn Welch #define GEF_WDC_ENABLED_TRUE 1 593268b561SMartyn Welch #define GEF_WDC_ENABLED_FALSE 0 603268b561SMartyn Welch 613268b561SMartyn Welch /* Flags bits */ 623268b561SMartyn Welch #define GEF_WDOG_FLAG_OPENED 0 633268b561SMartyn Welch 643268b561SMartyn Welch static unsigned long wdt_flags; 653268b561SMartyn Welch static int wdt_status; 663268b561SMartyn Welch static void __iomem *gef_wdt_regs; 673268b561SMartyn Welch static int gef_wdt_timeout; 683268b561SMartyn Welch static int gef_wdt_count; 693268b561SMartyn Welch static unsigned int bus_clk; 703268b561SMartyn Welch static char expect_close; 713268b561SMartyn Welch static DEFINE_SPINLOCK(gef_wdt_spinlock); 723268b561SMartyn Welch 7386a1e189SWim Van Sebroeck static bool nowayout = WATCHDOG_NOWAYOUT; 7486a1e189SWim Van Sebroeck module_param(nowayout, bool, 0); 753268b561SMartyn Welch MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 763268b561SMartyn Welch __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 773268b561SMartyn Welch 783268b561SMartyn Welch 793268b561SMartyn Welch static int gef_wdt_toggle_wdc(int enabled_predicate, int field_shift) 803268b561SMartyn Welch { 813268b561SMartyn Welch u32 data; 823268b561SMartyn Welch u32 enabled; 833268b561SMartyn Welch int ret = 0; 843268b561SMartyn Welch 853268b561SMartyn Welch spin_lock(&gef_wdt_spinlock); 863268b561SMartyn Welch data = ioread32be(gef_wdt_regs); 873268b561SMartyn Welch enabled = (data >> GEF_WDC_ENABLED_SHIFT) & 1; 883268b561SMartyn Welch 893268b561SMartyn Welch /* only toggle the requested field if enabled state matches predicate */ 903268b561SMartyn Welch if ((enabled ^ enabled_predicate) == 0) { 913268b561SMartyn Welch /* We write a 1, then a 2 -- to the appropriate field */ 923268b561SMartyn Welch data = (1 << field_shift) | gef_wdt_count; 933268b561SMartyn Welch iowrite32be(data, gef_wdt_regs); 943268b561SMartyn Welch 953268b561SMartyn Welch data = (2 << field_shift) | gef_wdt_count; 963268b561SMartyn Welch iowrite32be(data, gef_wdt_regs); 973268b561SMartyn Welch ret = 1; 983268b561SMartyn Welch } 993268b561SMartyn Welch spin_unlock(&gef_wdt_spinlock); 1003268b561SMartyn Welch 1013268b561SMartyn Welch return ret; 1023268b561SMartyn Welch } 1033268b561SMartyn Welch 1043268b561SMartyn Welch static void gef_wdt_service(void) 1053268b561SMartyn Welch { 1063268b561SMartyn Welch gef_wdt_toggle_wdc(GEF_WDC_ENABLED_TRUE, 1073268b561SMartyn Welch GEF_WDC_SERVICE_SHIFT); 1083268b561SMartyn Welch } 1093268b561SMartyn Welch 1103268b561SMartyn Welch static void gef_wdt_handler_enable(void) 1113268b561SMartyn Welch { 1123268b561SMartyn Welch if (gef_wdt_toggle_wdc(GEF_WDC_ENABLED_FALSE, 1133268b561SMartyn Welch GEF_WDC_ENABLE_SHIFT)) { 1143268b561SMartyn Welch gef_wdt_service(); 11527c766aaSJoe Perches pr_notice("watchdog activated\n"); 1163268b561SMartyn Welch } 1173268b561SMartyn Welch } 1183268b561SMartyn Welch 1193268b561SMartyn Welch static void gef_wdt_handler_disable(void) 1203268b561SMartyn Welch { 1213268b561SMartyn Welch if (gef_wdt_toggle_wdc(GEF_WDC_ENABLED_TRUE, 1223268b561SMartyn Welch GEF_WDC_ENABLE_SHIFT)) 12327c766aaSJoe Perches pr_notice("watchdog deactivated\n"); 1243268b561SMartyn Welch } 1253268b561SMartyn Welch 1263268b561SMartyn Welch static void gef_wdt_set_timeout(unsigned int timeout) 1273268b561SMartyn Welch { 1283268b561SMartyn Welch /* maximum bus cycle count is 0xFFFFFFFF */ 1293268b561SMartyn Welch if (timeout > 0xFFFFFFFF / bus_clk) 1303268b561SMartyn Welch timeout = 0xFFFFFFFF / bus_clk; 1313268b561SMartyn Welch 1323268b561SMartyn Welch /* Register only holds upper 24 bits, bit shifted into lower 24 */ 1333268b561SMartyn Welch gef_wdt_count = (timeout * bus_clk) >> 8; 1343268b561SMartyn Welch gef_wdt_timeout = timeout; 1353268b561SMartyn Welch } 1363268b561SMartyn Welch 1373268b561SMartyn Welch 1383268b561SMartyn Welch static ssize_t gef_wdt_write(struct file *file, const char __user *data, 1393268b561SMartyn Welch size_t len, loff_t *ppos) 1403268b561SMartyn Welch { 1413268b561SMartyn Welch if (len) { 1423268b561SMartyn Welch if (!nowayout) { 1433268b561SMartyn Welch size_t i; 1443268b561SMartyn Welch 1453268b561SMartyn Welch expect_close = 0; 1463268b561SMartyn Welch 1473268b561SMartyn Welch for (i = 0; i != len; i++) { 1483268b561SMartyn Welch char c; 1493268b561SMartyn Welch if (get_user(c, data + i)) 1503268b561SMartyn Welch return -EFAULT; 1513268b561SMartyn Welch if (c == 'V') 1523268b561SMartyn Welch expect_close = 42; 1533268b561SMartyn Welch } 1543268b561SMartyn Welch } 1553268b561SMartyn Welch gef_wdt_service(); 1563268b561SMartyn Welch } 1573268b561SMartyn Welch 1583268b561SMartyn Welch return len; 1593268b561SMartyn Welch } 1603268b561SMartyn Welch 1613268b561SMartyn Welch static long gef_wdt_ioctl(struct file *file, unsigned int cmd, 1623268b561SMartyn Welch unsigned long arg) 1633268b561SMartyn Welch { 1643268b561SMartyn Welch int timeout; 1653268b561SMartyn Welch int options; 1663268b561SMartyn Welch void __user *argp = (void __user *)arg; 16742747d71SWim Van Sebroeck static const struct watchdog_info info = { 1683268b561SMartyn Welch .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | 1693268b561SMartyn Welch WDIOF_KEEPALIVEPING, 1703268b561SMartyn Welch .firmware_version = 0, 171cda61c94SMartyn Welch .identity = "GE watchdog", 1723268b561SMartyn Welch }; 1733268b561SMartyn Welch 1743268b561SMartyn Welch switch (cmd) { 1753268b561SMartyn Welch case WDIOC_GETSUPPORT: 1763268b561SMartyn Welch if (copy_to_user(argp, &info, sizeof(info))) 1773268b561SMartyn Welch return -EFAULT; 1783268b561SMartyn Welch break; 1793268b561SMartyn Welch 1803268b561SMartyn Welch case WDIOC_GETSTATUS: 1813268b561SMartyn Welch case WDIOC_GETBOOTSTATUS: 1823268b561SMartyn Welch if (put_user(wdt_status, (int __user *)argp)) 1833268b561SMartyn Welch return -EFAULT; 1843268b561SMartyn Welch wdt_status &= ~WDIOF_KEEPALIVEPING; 1853268b561SMartyn Welch break; 1863268b561SMartyn Welch 1873268b561SMartyn Welch case WDIOC_SETOPTIONS: 1883268b561SMartyn Welch if (get_user(options, (int __user *)argp)) 1893268b561SMartyn Welch return -EFAULT; 1903268b561SMartyn Welch 1913268b561SMartyn Welch if (options & WDIOS_DISABLECARD) 1923268b561SMartyn Welch gef_wdt_handler_disable(); 1933268b561SMartyn Welch 1943268b561SMartyn Welch if (options & WDIOS_ENABLECARD) 1953268b561SMartyn Welch gef_wdt_handler_enable(); 1963268b561SMartyn Welch break; 1973268b561SMartyn Welch 1983268b561SMartyn Welch case WDIOC_KEEPALIVE: 1993268b561SMartyn Welch gef_wdt_service(); 2003268b561SMartyn Welch wdt_status |= WDIOF_KEEPALIVEPING; 2013268b561SMartyn Welch break; 2023268b561SMartyn Welch 2033268b561SMartyn Welch case WDIOC_SETTIMEOUT: 2043268b561SMartyn Welch if (get_user(timeout, (int __user *)argp)) 2053268b561SMartyn Welch return -EFAULT; 2063268b561SMartyn Welch gef_wdt_set_timeout(timeout); 2073268b561SMartyn Welch /* Fall through */ 2083268b561SMartyn Welch 2093268b561SMartyn Welch case WDIOC_GETTIMEOUT: 2103268b561SMartyn Welch if (put_user(gef_wdt_timeout, (int __user *)argp)) 2113268b561SMartyn Welch return -EFAULT; 2123268b561SMartyn Welch break; 2133268b561SMartyn Welch 2143268b561SMartyn Welch default: 2153268b561SMartyn Welch return -ENOTTY; 2163268b561SMartyn Welch } 2173268b561SMartyn Welch 2183268b561SMartyn Welch return 0; 2193268b561SMartyn Welch } 2203268b561SMartyn Welch 2213268b561SMartyn Welch static int gef_wdt_open(struct inode *inode, struct file *file) 2223268b561SMartyn Welch { 2233268b561SMartyn Welch if (test_and_set_bit(GEF_WDOG_FLAG_OPENED, &wdt_flags)) 2243268b561SMartyn Welch return -EBUSY; 2253268b561SMartyn Welch 2263268b561SMartyn Welch if (nowayout) 2273268b561SMartyn Welch __module_get(THIS_MODULE); 2283268b561SMartyn Welch 2293268b561SMartyn Welch gef_wdt_handler_enable(); 2303268b561SMartyn Welch 2313268b561SMartyn Welch return nonseekable_open(inode, file); 2323268b561SMartyn Welch } 2333268b561SMartyn Welch 2343268b561SMartyn Welch static int gef_wdt_release(struct inode *inode, struct file *file) 2353268b561SMartyn Welch { 2363268b561SMartyn Welch if (expect_close == 42) 2373268b561SMartyn Welch gef_wdt_handler_disable(); 2383268b561SMartyn Welch else { 23927c766aaSJoe Perches pr_crit("unexpected close, not stopping timer!\n"); 2403268b561SMartyn Welch gef_wdt_service(); 2413268b561SMartyn Welch } 2423268b561SMartyn Welch expect_close = 0; 2433268b561SMartyn Welch 2443268b561SMartyn Welch clear_bit(GEF_WDOG_FLAG_OPENED, &wdt_flags); 2453268b561SMartyn Welch 2463268b561SMartyn Welch return 0; 2473268b561SMartyn Welch } 2483268b561SMartyn Welch 2493268b561SMartyn Welch static const struct file_operations gef_wdt_fops = { 2503268b561SMartyn Welch .owner = THIS_MODULE, 2513268b561SMartyn Welch .llseek = no_llseek, 2523268b561SMartyn Welch .write = gef_wdt_write, 2533268b561SMartyn Welch .unlocked_ioctl = gef_wdt_ioctl, 2543268b561SMartyn Welch .open = gef_wdt_open, 2553268b561SMartyn Welch .release = gef_wdt_release, 2563268b561SMartyn Welch }; 2573268b561SMartyn Welch 2583268b561SMartyn Welch static struct miscdevice gef_wdt_miscdev = { 2593268b561SMartyn Welch .minor = WATCHDOG_MINOR, 2603268b561SMartyn Welch .name = "watchdog", 2613268b561SMartyn Welch .fops = &gef_wdt_fops, 2623268b561SMartyn Welch }; 2633268b561SMartyn Welch 2643268b561SMartyn Welch 2652d991a16SBill Pemberton static int gef_wdt_probe(struct platform_device *dev) 2663268b561SMartyn Welch { 2673268b561SMartyn Welch int timeout = 10; 2683268b561SMartyn Welch u32 freq; 2693268b561SMartyn Welch 2703268b561SMartyn Welch bus_clk = 133; /* in MHz */ 2713268b561SMartyn Welch 2723268b561SMartyn Welch freq = fsl_get_sys_freq(); 27326952669SRoel Kluin if (freq != -1) 2743268b561SMartyn Welch bus_clk = freq; 2753268b561SMartyn Welch 2763268b561SMartyn Welch /* Map devices registers into memory */ 277b74dbf2aSAnatolij Gustschin gef_wdt_regs = of_iomap(dev->dev.of_node, 0); 2783268b561SMartyn Welch if (gef_wdt_regs == NULL) 2793268b561SMartyn Welch return -ENOMEM; 2803268b561SMartyn Welch 2813268b561SMartyn Welch gef_wdt_set_timeout(timeout); 2823268b561SMartyn Welch 2833268b561SMartyn Welch gef_wdt_handler_disable(); /* in case timer was already running */ 2843268b561SMartyn Welch 2853268b561SMartyn Welch return misc_register(&gef_wdt_miscdev); 2863268b561SMartyn Welch } 2873268b561SMartyn Welch 2883268b561SMartyn Welch static int __devexit gef_wdt_remove(struct platform_device *dev) 2893268b561SMartyn Welch { 2903268b561SMartyn Welch misc_deregister(&gef_wdt_miscdev); 2913268b561SMartyn Welch 2923268b561SMartyn Welch gef_wdt_handler_disable(); 2933268b561SMartyn Welch 2943268b561SMartyn Welch iounmap(gef_wdt_regs); 2953268b561SMartyn Welch 2963268b561SMartyn Welch return 0; 2973268b561SMartyn Welch } 2983268b561SMartyn Welch 2993268b561SMartyn Welch static const struct of_device_id gef_wdt_ids[] = { 3003268b561SMartyn Welch { 3013268b561SMartyn Welch .compatible = "gef,fpga-wdt", 3023268b561SMartyn Welch }, 3033268b561SMartyn Welch {}, 3043268b561SMartyn Welch }; 3053268b561SMartyn Welch 3061c48a5c9SGrant Likely static struct platform_driver gef_wdt_driver = { 3074018294bSGrant Likely .driver = { 3083268b561SMartyn Welch .name = "gef_wdt", 3094018294bSGrant Likely .owner = THIS_MODULE, 3104018294bSGrant Likely .of_match_table = gef_wdt_ids, 3114018294bSGrant Likely }, 3123268b561SMartyn Welch .probe = gef_wdt_probe, 3133268b561SMartyn Welch }; 3143268b561SMartyn Welch 3153268b561SMartyn Welch static int __init gef_wdt_init(void) 3163268b561SMartyn Welch { 31727c766aaSJoe Perches pr_info("GE watchdog driver\n"); 3181c48a5c9SGrant Likely return platform_driver_register(&gef_wdt_driver); 3193268b561SMartyn Welch } 3203268b561SMartyn Welch 3213268b561SMartyn Welch static void __exit gef_wdt_exit(void) 3223268b561SMartyn Welch { 3231c48a5c9SGrant Likely platform_driver_unregister(&gef_wdt_driver); 3243268b561SMartyn Welch } 3253268b561SMartyn Welch 3263268b561SMartyn Welch module_init(gef_wdt_init); 3273268b561SMartyn Welch module_exit(gef_wdt_exit); 3283268b561SMartyn Welch 329cda61c94SMartyn Welch MODULE_AUTHOR("Martyn Welch <martyn.welch@ge.com>"); 330cda61c94SMartyn Welch MODULE_DESCRIPTION("GE watchdog driver"); 3313268b561SMartyn Welch MODULE_LICENSE("GPL"); 3323268b561SMartyn Welch MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 3333268b561SMartyn Welch MODULE_ALIAS("platform:gef_wdt"); 334