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