12e62c498SMarcus Folkesson // SPDX-License-Identifier: GPL-2.0+ 2e9659e69SAlejandro Cabrera /* 39419c07cSMichal Simek * Watchdog Device Driver for Xilinx axi/xps_timebase_wdt 4e9659e69SAlejandro Cabrera * 5d14fd964SMichal Simek * (C) Copyright 2013 - 2014 Xilinx, Inc. 6e9659e69SAlejandro Cabrera * (C) Copyright 2011 (Alejandro Cabrera <aldaya@gmail.com>) 7e9659e69SAlejandro Cabrera */ 8e9659e69SAlejandro Cabrera 973ec9440SSrinivas Goud #include <linux/bits.h> 109d6b4efcSShubhrajyoti Datta #include <linux/clk.h> 11f06cdfd1SMichal Simek #include <linux/err.h> 12e9659e69SAlejandro Cabrera #include <linux/module.h> 13e9659e69SAlejandro Cabrera #include <linux/types.h> 14e9659e69SAlejandro Cabrera #include <linux/kernel.h> 15e9659e69SAlejandro Cabrera #include <linux/ioport.h> 16e9659e69SAlejandro Cabrera #include <linux/watchdog.h> 17e9659e69SAlejandro Cabrera #include <linux/io.h> 18e9659e69SAlejandro Cabrera #include <linux/of.h> 19e9659e69SAlejandro Cabrera #include <linux/of_device.h> 20e9659e69SAlejandro Cabrera #include <linux/of_address.h> 21e9659e69SAlejandro Cabrera 22e9659e69SAlejandro Cabrera /* Register offsets for the Wdt device */ 23e9659e69SAlejandro Cabrera #define XWT_TWCSR0_OFFSET 0x0 /* Control/Status Register0 */ 24e9659e69SAlejandro Cabrera #define XWT_TWCSR1_OFFSET 0x4 /* Control/Status Register1 */ 25e9659e69SAlejandro Cabrera #define XWT_TBR_OFFSET 0x8 /* Timebase Register Offset */ 26e9659e69SAlejandro Cabrera 27e9659e69SAlejandro Cabrera /* Control/Status Register Masks */ 2873ec9440SSrinivas Goud #define XWT_CSR0_WRS_MASK BIT(3) /* Reset status */ 2973ec9440SSrinivas Goud #define XWT_CSR0_WDS_MASK BIT(2) /* Timer state */ 3073ec9440SSrinivas Goud #define XWT_CSR0_EWDT1_MASK BIT(1) /* Enable bit 1 */ 31e9659e69SAlejandro Cabrera 32e9659e69SAlejandro Cabrera /* Control/Status Register 0/1 bits */ 3373ec9440SSrinivas Goud #define XWT_CSRX_EWDT2_MASK BIT(0) /* Enable bit 2 */ 34e9659e69SAlejandro Cabrera 35e9659e69SAlejandro Cabrera /* SelfTest constants */ 36e9659e69SAlejandro Cabrera #define XWT_MAX_SELFTEST_LOOP_COUNT 0x00010000 37e9659e69SAlejandro Cabrera #define XWT_TIMER_FAILED 0xFFFFFFFF 38e9659e69SAlejandro Cabrera 39e9659e69SAlejandro Cabrera #define WATCHDOG_NAME "Xilinx Watchdog" 40e9659e69SAlejandro Cabrera 41e9659e69SAlejandro Cabrera struct xwdt_device { 42e9659e69SAlejandro Cabrera void __iomem *base; 43e9659e69SAlejandro Cabrera u32 wdt_interval; 44b2802e78SSrinivas Goud spinlock_t spinlock; /* spinlock for register handling */ 4590663171SMichal Simek struct watchdog_device xilinx_wdt_wdd; 469d6b4efcSShubhrajyoti Datta struct clk *clk; 47e9659e69SAlejandro Cabrera }; 48e9659e69SAlejandro Cabrera 49d14fd964SMichal Simek static int xilinx_wdt_start(struct watchdog_device *wdd) 50e9659e69SAlejandro Cabrera { 51b6bc4164SMaulik Jodhani int ret; 525cf4e69dSMichal Simek u32 control_status_reg; 5390663171SMichal Simek struct xwdt_device *xdev = watchdog_get_drvdata(wdd); 545cf4e69dSMichal Simek 55b6bc4164SMaulik Jodhani ret = clk_enable(xdev->clk); 56b6bc4164SMaulik Jodhani if (ret) { 57b6bc4164SMaulik Jodhani dev_err(wdd->parent, "Failed to enable clock\n"); 58b6bc4164SMaulik Jodhani return ret; 59b6bc4164SMaulik Jodhani } 60b6bc4164SMaulik Jodhani 6190663171SMichal Simek spin_lock(&xdev->spinlock); 62e9659e69SAlejandro Cabrera 63e9659e69SAlejandro Cabrera /* Clean previous status and enable the watchdog timer */ 6490663171SMichal Simek control_status_reg = ioread32(xdev->base + XWT_TWCSR0_OFFSET); 65e9659e69SAlejandro Cabrera control_status_reg |= (XWT_CSR0_WRS_MASK | XWT_CSR0_WDS_MASK); 66e9659e69SAlejandro Cabrera 67e9659e69SAlejandro Cabrera iowrite32((control_status_reg | XWT_CSR0_EWDT1_MASK), 6890663171SMichal Simek xdev->base + XWT_TWCSR0_OFFSET); 69e9659e69SAlejandro Cabrera 7090663171SMichal Simek iowrite32(XWT_CSRX_EWDT2_MASK, xdev->base + XWT_TWCSR1_OFFSET); 71e9659e69SAlejandro Cabrera 7290663171SMichal Simek spin_unlock(&xdev->spinlock); 73d14fd964SMichal Simek 74a40b2c3dSSrinivas Goud dev_dbg(wdd->parent, "Watchdog Started!\n"); 75a40b2c3dSSrinivas Goud 76d14fd964SMichal Simek return 0; 77e9659e69SAlejandro Cabrera } 78e9659e69SAlejandro Cabrera 79d14fd964SMichal Simek static int xilinx_wdt_stop(struct watchdog_device *wdd) 80e9659e69SAlejandro Cabrera { 815cf4e69dSMichal Simek u32 control_status_reg; 8290663171SMichal Simek struct xwdt_device *xdev = watchdog_get_drvdata(wdd); 835cf4e69dSMichal Simek 8490663171SMichal Simek spin_lock(&xdev->spinlock); 85e9659e69SAlejandro Cabrera 8690663171SMichal Simek control_status_reg = ioread32(xdev->base + XWT_TWCSR0_OFFSET); 87e9659e69SAlejandro Cabrera 88e9659e69SAlejandro Cabrera iowrite32((control_status_reg & ~XWT_CSR0_EWDT1_MASK), 8990663171SMichal Simek xdev->base + XWT_TWCSR0_OFFSET); 90e9659e69SAlejandro Cabrera 9190663171SMichal Simek iowrite32(0, xdev->base + XWT_TWCSR1_OFFSET); 92e9659e69SAlejandro Cabrera 9390663171SMichal Simek spin_unlock(&xdev->spinlock); 94b6bc4164SMaulik Jodhani 95b6bc4164SMaulik Jodhani clk_disable(xdev->clk); 96b6bc4164SMaulik Jodhani 97a40b2c3dSSrinivas Goud dev_dbg(wdd->parent, "Watchdog Stopped!\n"); 98d14fd964SMichal Simek 99d14fd964SMichal Simek return 0; 100e9659e69SAlejandro Cabrera } 101e9659e69SAlejandro Cabrera 102d14fd964SMichal Simek static int xilinx_wdt_keepalive(struct watchdog_device *wdd) 103e9659e69SAlejandro Cabrera { 1045cf4e69dSMichal Simek u32 control_status_reg; 10590663171SMichal Simek struct xwdt_device *xdev = watchdog_get_drvdata(wdd); 1065cf4e69dSMichal Simek 10790663171SMichal Simek spin_lock(&xdev->spinlock); 108e9659e69SAlejandro Cabrera 10990663171SMichal Simek control_status_reg = ioread32(xdev->base + XWT_TWCSR0_OFFSET); 110e9659e69SAlejandro Cabrera control_status_reg |= (XWT_CSR0_WRS_MASK | XWT_CSR0_WDS_MASK); 11190663171SMichal Simek iowrite32(control_status_reg, xdev->base + XWT_TWCSR0_OFFSET); 112e9659e69SAlejandro Cabrera 11390663171SMichal Simek spin_unlock(&xdev->spinlock); 114d14fd964SMichal Simek 115d14fd964SMichal Simek return 0; 116e9659e69SAlejandro Cabrera } 117e9659e69SAlejandro Cabrera 118d14fd964SMichal Simek static const struct watchdog_info xilinx_wdt_ident = { 119d14fd964SMichal Simek .options = WDIOF_MAGICCLOSE | 120d14fd964SMichal Simek WDIOF_KEEPALIVEPING, 121d14fd964SMichal Simek .firmware_version = 1, 122d14fd964SMichal Simek .identity = WATCHDOG_NAME, 123d14fd964SMichal Simek }; 124e9659e69SAlejandro Cabrera 125d14fd964SMichal Simek static const struct watchdog_ops xilinx_wdt_ops = { 126d14fd964SMichal Simek .owner = THIS_MODULE, 127d14fd964SMichal Simek .start = xilinx_wdt_start, 128d14fd964SMichal Simek .stop = xilinx_wdt_stop, 129d14fd964SMichal Simek .ping = xilinx_wdt_keepalive, 130d14fd964SMichal Simek }; 131e9659e69SAlejandro Cabrera 13290663171SMichal Simek static u32 xwdt_selftest(struct xwdt_device *xdev) 133e9659e69SAlejandro Cabrera { 134e9659e69SAlejandro Cabrera int i; 135e9659e69SAlejandro Cabrera u32 timer_value1; 136e9659e69SAlejandro Cabrera u32 timer_value2; 137e9659e69SAlejandro Cabrera 13890663171SMichal Simek spin_lock(&xdev->spinlock); 139e9659e69SAlejandro Cabrera 14090663171SMichal Simek timer_value1 = ioread32(xdev->base + XWT_TBR_OFFSET); 14190663171SMichal Simek timer_value2 = ioread32(xdev->base + XWT_TBR_OFFSET); 142e9659e69SAlejandro Cabrera 143e9659e69SAlejandro Cabrera for (i = 0; 144e9659e69SAlejandro Cabrera ((i <= XWT_MAX_SELFTEST_LOOP_COUNT) && 145e9659e69SAlejandro Cabrera (timer_value2 == timer_value1)); i++) { 14690663171SMichal Simek timer_value2 = ioread32(xdev->base + XWT_TBR_OFFSET); 147e9659e69SAlejandro Cabrera } 148e9659e69SAlejandro Cabrera 14990663171SMichal Simek spin_unlock(&xdev->spinlock); 150e9659e69SAlejandro Cabrera 151e9659e69SAlejandro Cabrera if (timer_value2 != timer_value1) 152e9659e69SAlejandro Cabrera return ~XWT_TIMER_FAILED; 153e9659e69SAlejandro Cabrera else 154e9659e69SAlejandro Cabrera return XWT_TIMER_FAILED; 155e9659e69SAlejandro Cabrera } 156e9659e69SAlejandro Cabrera 157*801cdffeSGuenter Roeck static void xwdt_clk_disable_unprepare(void *data) 158*801cdffeSGuenter Roeck { 159*801cdffeSGuenter Roeck clk_disable_unprepare(data); 160*801cdffeSGuenter Roeck } 161*801cdffeSGuenter Roeck 1622d991a16SBill Pemberton static int xwdt_probe(struct platform_device *pdev) 163e9659e69SAlejandro Cabrera { 164*801cdffeSGuenter Roeck struct device *dev = &pdev->dev; 165e9659e69SAlejandro Cabrera int rc; 1668d6a140bSMichal Simek u32 pfreq = 0, enable_once = 0; 16790663171SMichal Simek struct xwdt_device *xdev; 16890663171SMichal Simek struct watchdog_device *xilinx_wdt_wdd; 16990663171SMichal Simek 170*801cdffeSGuenter Roeck xdev = devm_kzalloc(dev, sizeof(*xdev), GFP_KERNEL); 17190663171SMichal Simek if (!xdev) 17290663171SMichal Simek return -ENOMEM; 17390663171SMichal Simek 17490663171SMichal Simek xilinx_wdt_wdd = &xdev->xilinx_wdt_wdd; 17590663171SMichal Simek xilinx_wdt_wdd->info = &xilinx_wdt_ident; 17690663171SMichal Simek xilinx_wdt_wdd->ops = &xilinx_wdt_ops; 177*801cdffeSGuenter Roeck xilinx_wdt_wdd->parent = dev; 178e9659e69SAlejandro Cabrera 1790f0a6a28SGuenter Roeck xdev->base = devm_platform_ioremap_resource(pdev, 0); 18090663171SMichal Simek if (IS_ERR(xdev->base)) 18190663171SMichal Simek return PTR_ERR(xdev->base); 182f06cdfd1SMichal Simek 183*801cdffeSGuenter Roeck rc = of_property_read_u32(dev->of_node, "xlnx,wdt-interval", 1842e79a368SMichal Simek &xdev->wdt_interval); 1858d6a140bSMichal Simek if (rc) 186*801cdffeSGuenter Roeck dev_warn(dev, "Parameter \"xlnx,wdt-interval\" not found\n"); 187e9659e69SAlejandro Cabrera 188*801cdffeSGuenter Roeck rc = of_property_read_u32(dev->of_node, "xlnx,wdt-enable-once", 1892e79a368SMichal Simek &enable_once); 1902e79a368SMichal Simek if (rc) 191*801cdffeSGuenter Roeck dev_warn(dev, 1924c7fbbc4SMichal Simek "Parameter \"xlnx,wdt-enable-once\" not found\n"); 1932e79a368SMichal Simek 1942e79a368SMichal Simek watchdog_set_nowayout(xilinx_wdt_wdd, enable_once); 195e9659e69SAlejandro Cabrera 196*801cdffeSGuenter Roeck xdev->clk = devm_clk_get(dev, NULL); 197b6bc4164SMaulik Jodhani if (IS_ERR(xdev->clk)) { 198b6bc4164SMaulik Jodhani if (PTR_ERR(xdev->clk) != -ENOENT) 199b6bc4164SMaulik Jodhani return PTR_ERR(xdev->clk); 200b6bc4164SMaulik Jodhani 201b6bc4164SMaulik Jodhani /* 202b6bc4164SMaulik Jodhani * Clock framework support is optional, continue on 203b6bc4164SMaulik Jodhani * anyways if we don't find a matching clock. 204b6bc4164SMaulik Jodhani */ 205b6bc4164SMaulik Jodhani xdev->clk = NULL; 206b6bc4164SMaulik Jodhani 207*801cdffeSGuenter Roeck rc = of_property_read_u32(dev->of_node, "clock-frequency", 208b6bc4164SMaulik Jodhani &pfreq); 209b6bc4164SMaulik Jodhani if (rc) 210*801cdffeSGuenter Roeck dev_warn(dev, 211b6bc4164SMaulik Jodhani "The watchdog clock freq cannot be obtained\n"); 212b6bc4164SMaulik Jodhani } else { 213b6bc4164SMaulik Jodhani pfreq = clk_get_rate(xdev->clk); 214f185de22SSrinivas Neeli rc = clk_prepare_enable(xdev->clk); 215f185de22SSrinivas Neeli if (rc) { 216f185de22SSrinivas Neeli dev_err(dev, "unable to enable clock\n"); 217f185de22SSrinivas Neeli return rc; 218f185de22SSrinivas Neeli } 219f185de22SSrinivas Neeli rc = devm_add_action_or_reset(dev, xwdt_clk_disable_unprepare, 220f185de22SSrinivas Neeli xdev->clk); 221f185de22SSrinivas Neeli if (rc) 222f185de22SSrinivas Neeli return rc; 223b6bc4164SMaulik Jodhani } 224b6bc4164SMaulik Jodhani 225e9659e69SAlejandro Cabrera /* 226e9659e69SAlejandro Cabrera * Twice of the 2^wdt_interval / freq because the first wdt overflow is 227e9659e69SAlejandro Cabrera * ignored (interrupt), reset is only generated at second wdt overflow 228e9659e69SAlejandro Cabrera */ 2298d6a140bSMichal Simek if (pfreq && xdev->wdt_interval) 23090663171SMichal Simek xilinx_wdt_wdd->timeout = 2 * ((1 << xdev->wdt_interval) / 2312e79a368SMichal Simek pfreq); 232e9659e69SAlejandro Cabrera 23390663171SMichal Simek spin_lock_init(&xdev->spinlock); 23490663171SMichal Simek watchdog_set_drvdata(xilinx_wdt_wdd, xdev); 23590663171SMichal Simek 23690663171SMichal Simek rc = xwdt_selftest(xdev); 237e9659e69SAlejandro Cabrera if (rc == XWT_TIMER_FAILED) { 238*801cdffeSGuenter Roeck dev_err(dev, "SelfTest routine error\n"); 239*801cdffeSGuenter Roeck return rc; 240e9659e69SAlejandro Cabrera } 241e9659e69SAlejandro Cabrera 242*801cdffeSGuenter Roeck rc = devm_watchdog_register_device(dev, xilinx_wdt_wdd); 2430fa6cf71SWolfram Sang if (rc) 244*801cdffeSGuenter Roeck return rc; 245e9659e69SAlejandro Cabrera 246b6bc4164SMaulik Jodhani clk_disable(xdev->clk); 247b6bc4164SMaulik Jodhani 24848027d0dSSrinivas Neeli dev_info(dev, "Xilinx Watchdog Timer with timeout %ds\n", 24948027d0dSSrinivas Neeli xilinx_wdt_wdd->timeout); 25090663171SMichal Simek 25190663171SMichal Simek platform_set_drvdata(pdev, xdev); 252e9659e69SAlejandro Cabrera 253e9659e69SAlejandro Cabrera return 0; 254e9659e69SAlejandro Cabrera } 255e9659e69SAlejandro Cabrera 2566f671c6bSMichal Simek /** 2576f671c6bSMichal Simek * xwdt_suspend - Suspend the device. 2586f671c6bSMichal Simek * 2596f671c6bSMichal Simek * @dev: handle to the device structure. 2606f671c6bSMichal Simek * Return: 0 always. 2616f671c6bSMichal Simek */ 2626f671c6bSMichal Simek static int __maybe_unused xwdt_suspend(struct device *dev) 2636f671c6bSMichal Simek { 26420745634SWolfram Sang struct xwdt_device *xdev = dev_get_drvdata(dev); 2656f671c6bSMichal Simek 2666f671c6bSMichal Simek if (watchdog_active(&xdev->xilinx_wdt_wdd)) 2676f671c6bSMichal Simek xilinx_wdt_stop(&xdev->xilinx_wdt_wdd); 2686f671c6bSMichal Simek 2696f671c6bSMichal Simek return 0; 2706f671c6bSMichal Simek } 2716f671c6bSMichal Simek 2726f671c6bSMichal Simek /** 2736f671c6bSMichal Simek * xwdt_resume - Resume the device. 2746f671c6bSMichal Simek * 2756f671c6bSMichal Simek * @dev: handle to the device structure. 2766f671c6bSMichal Simek * Return: 0 on success, errno otherwise. 2776f671c6bSMichal Simek */ 2786f671c6bSMichal Simek static int __maybe_unused xwdt_resume(struct device *dev) 2796f671c6bSMichal Simek { 28020745634SWolfram Sang struct xwdt_device *xdev = dev_get_drvdata(dev); 2816f671c6bSMichal Simek int ret = 0; 2826f671c6bSMichal Simek 2836f671c6bSMichal Simek if (watchdog_active(&xdev->xilinx_wdt_wdd)) 2846f671c6bSMichal Simek ret = xilinx_wdt_start(&xdev->xilinx_wdt_wdd); 2856f671c6bSMichal Simek 2866f671c6bSMichal Simek return ret; 2876f671c6bSMichal Simek } 2886f671c6bSMichal Simek 2896f671c6bSMichal Simek static SIMPLE_DEV_PM_OPS(xwdt_pm_ops, xwdt_suspend, xwdt_resume); 2906f671c6bSMichal Simek 291e9659e69SAlejandro Cabrera /* Match table for of_platform binding */ 2929ebf1855SJingoo Han static const struct of_device_id xwdt_of_match[] = { 2938fce9b36SMichal Simek { .compatible = "xlnx,xps-timebase-wdt-1.00.a", }, 294e9659e69SAlejandro Cabrera { .compatible = "xlnx,xps-timebase-wdt-1.01.a", }, 295e9659e69SAlejandro Cabrera {}, 296e9659e69SAlejandro Cabrera }; 297e9659e69SAlejandro Cabrera MODULE_DEVICE_TABLE(of, xwdt_of_match); 298e9659e69SAlejandro Cabrera 299e9659e69SAlejandro Cabrera static struct platform_driver xwdt_driver = { 300e9659e69SAlejandro Cabrera .probe = xwdt_probe, 301e9659e69SAlejandro Cabrera .driver = { 302e9659e69SAlejandro Cabrera .name = WATCHDOG_NAME, 303e9659e69SAlejandro Cabrera .of_match_table = xwdt_of_match, 3046f671c6bSMichal Simek .pm = &xwdt_pm_ops, 305e9659e69SAlejandro Cabrera }, 306e9659e69SAlejandro Cabrera }; 307e9659e69SAlejandro Cabrera 308b8ec6118SAxel Lin module_platform_driver(xwdt_driver); 309e9659e69SAlejandro Cabrera 310e9659e69SAlejandro Cabrera MODULE_AUTHOR("Alejandro Cabrera <aldaya@gmail.com>"); 311e9659e69SAlejandro Cabrera MODULE_DESCRIPTION("Xilinx Watchdog driver"); 3122e62c498SMarcus Folkesson MODULE_LICENSE("GPL"); 313