1*2e62c498SMarcus Folkesson // SPDX-License-Identifier: GPL-2.0 2e7bf0289SKeiji Hayashibara /* 3e7bf0289SKeiji Hayashibara * Watchdog driver for the UniPhier watchdog timer 4e7bf0289SKeiji Hayashibara * 5e7bf0289SKeiji Hayashibara * (c) Copyright 2014 Panasonic Corporation 6e7bf0289SKeiji Hayashibara * (c) Copyright 2016 Socionext Inc. 7e7bf0289SKeiji Hayashibara * All rights reserved. 8e7bf0289SKeiji Hayashibara */ 9e7bf0289SKeiji Hayashibara 10e7bf0289SKeiji Hayashibara #include <linux/bitops.h> 11e7bf0289SKeiji Hayashibara #include <linux/mfd/syscon.h> 12e7bf0289SKeiji Hayashibara #include <linux/module.h> 13e7bf0289SKeiji Hayashibara #include <linux/of.h> 14e7bf0289SKeiji Hayashibara #include <linux/platform_device.h> 15e7bf0289SKeiji Hayashibara #include <linux/regmap.h> 16e7bf0289SKeiji Hayashibara #include <linux/watchdog.h> 17e7bf0289SKeiji Hayashibara 18e7bf0289SKeiji Hayashibara /* WDT timer setting register */ 19e7bf0289SKeiji Hayashibara #define WDTTIMSET 0x3004 20e7bf0289SKeiji Hayashibara #define WDTTIMSET_PERIOD_MASK (0xf << 0) 21e7bf0289SKeiji Hayashibara #define WDTTIMSET_PERIOD_1_SEC (0x3 << 0) 22e7bf0289SKeiji Hayashibara 23e7bf0289SKeiji Hayashibara /* WDT reset selection register */ 24e7bf0289SKeiji Hayashibara #define WDTRSTSEL 0x3008 25e7bf0289SKeiji Hayashibara #define WDTRSTSEL_RSTSEL_MASK (0x3 << 0) 26e7bf0289SKeiji Hayashibara #define WDTRSTSEL_RSTSEL_BOTH (0x0 << 0) 27e7bf0289SKeiji Hayashibara #define WDTRSTSEL_RSTSEL_IRQ_ONLY (0x2 << 0) 28e7bf0289SKeiji Hayashibara 29e7bf0289SKeiji Hayashibara /* WDT control register */ 30e7bf0289SKeiji Hayashibara #define WDTCTRL 0x300c 31e7bf0289SKeiji Hayashibara #define WDTCTRL_STATUS BIT(8) 32e7bf0289SKeiji Hayashibara #define WDTCTRL_CLEAR BIT(1) 33e7bf0289SKeiji Hayashibara #define WDTCTRL_ENABLE BIT(0) 34e7bf0289SKeiji Hayashibara 35e7bf0289SKeiji Hayashibara #define SEC_TO_WDTTIMSET_PRD(sec) \ 36e7bf0289SKeiji Hayashibara (ilog2(sec) + WDTTIMSET_PERIOD_1_SEC) 37e7bf0289SKeiji Hayashibara 38e7bf0289SKeiji Hayashibara #define WDTST_TIMEOUT 1000 /* usec */ 39e7bf0289SKeiji Hayashibara 40e7bf0289SKeiji Hayashibara #define WDT_DEFAULT_TIMEOUT 64 /* Default is 64 seconds */ 41e7bf0289SKeiji Hayashibara #define WDT_PERIOD_MIN 1 42e7bf0289SKeiji Hayashibara #define WDT_PERIOD_MAX 128 43e7bf0289SKeiji Hayashibara 44e7bf0289SKeiji Hayashibara static unsigned int timeout = 0; 45e7bf0289SKeiji Hayashibara static bool nowayout = WATCHDOG_NOWAYOUT; 46e7bf0289SKeiji Hayashibara 47e7bf0289SKeiji Hayashibara struct uniphier_wdt_dev { 48e7bf0289SKeiji Hayashibara struct watchdog_device wdt_dev; 49e7bf0289SKeiji Hayashibara struct regmap *regmap; 50e7bf0289SKeiji Hayashibara }; 51e7bf0289SKeiji Hayashibara 52e7bf0289SKeiji Hayashibara /* 53e7bf0289SKeiji Hayashibara * UniPhier Watchdog operations 54e7bf0289SKeiji Hayashibara */ 55e7bf0289SKeiji Hayashibara static int uniphier_watchdog_ping(struct watchdog_device *w) 56e7bf0289SKeiji Hayashibara { 57e7bf0289SKeiji Hayashibara struct uniphier_wdt_dev *wdev = watchdog_get_drvdata(w); 58e7bf0289SKeiji Hayashibara unsigned int val; 59e7bf0289SKeiji Hayashibara int ret; 60e7bf0289SKeiji Hayashibara 61e7bf0289SKeiji Hayashibara /* Clear counter */ 62e7bf0289SKeiji Hayashibara ret = regmap_write_bits(wdev->regmap, WDTCTRL, 63e7bf0289SKeiji Hayashibara WDTCTRL_CLEAR, WDTCTRL_CLEAR); 64e7bf0289SKeiji Hayashibara if (!ret) 65e7bf0289SKeiji Hayashibara /* 66e7bf0289SKeiji Hayashibara * As SoC specification, after clear counter, 67e7bf0289SKeiji Hayashibara * it needs to wait until counter status is 1. 68e7bf0289SKeiji Hayashibara */ 69e7bf0289SKeiji Hayashibara ret = regmap_read_poll_timeout(wdev->regmap, WDTCTRL, val, 70e7bf0289SKeiji Hayashibara (val & WDTCTRL_STATUS), 71e7bf0289SKeiji Hayashibara 0, WDTST_TIMEOUT); 72e7bf0289SKeiji Hayashibara 73e7bf0289SKeiji Hayashibara return ret; 74e7bf0289SKeiji Hayashibara } 75e7bf0289SKeiji Hayashibara 76e7bf0289SKeiji Hayashibara static int __uniphier_watchdog_start(struct regmap *regmap, unsigned int sec) 77e7bf0289SKeiji Hayashibara { 78e7bf0289SKeiji Hayashibara unsigned int val; 79e7bf0289SKeiji Hayashibara int ret; 80e7bf0289SKeiji Hayashibara 81e7bf0289SKeiji Hayashibara ret = regmap_read_poll_timeout(regmap, WDTCTRL, val, 82e7bf0289SKeiji Hayashibara !(val & WDTCTRL_STATUS), 83e7bf0289SKeiji Hayashibara 0, WDTST_TIMEOUT); 84e7bf0289SKeiji Hayashibara if (ret) 85e7bf0289SKeiji Hayashibara return ret; 86e7bf0289SKeiji Hayashibara 87e7bf0289SKeiji Hayashibara /* Setup period */ 88e7bf0289SKeiji Hayashibara ret = regmap_write(regmap, WDTTIMSET, 89e7bf0289SKeiji Hayashibara SEC_TO_WDTTIMSET_PRD(sec)); 90e7bf0289SKeiji Hayashibara if (ret) 91e7bf0289SKeiji Hayashibara return ret; 92e7bf0289SKeiji Hayashibara 93e7bf0289SKeiji Hayashibara /* Enable and clear watchdog */ 94e7bf0289SKeiji Hayashibara ret = regmap_write(regmap, WDTCTRL, WDTCTRL_ENABLE | WDTCTRL_CLEAR); 95e7bf0289SKeiji Hayashibara if (!ret) 96e7bf0289SKeiji Hayashibara /* 97e7bf0289SKeiji Hayashibara * As SoC specification, after clear counter, 98e7bf0289SKeiji Hayashibara * it needs to wait until counter status is 1. 99e7bf0289SKeiji Hayashibara */ 100e7bf0289SKeiji Hayashibara ret = regmap_read_poll_timeout(regmap, WDTCTRL, val, 101e7bf0289SKeiji Hayashibara (val & WDTCTRL_STATUS), 102e7bf0289SKeiji Hayashibara 0, WDTST_TIMEOUT); 103e7bf0289SKeiji Hayashibara 104e7bf0289SKeiji Hayashibara return ret; 105e7bf0289SKeiji Hayashibara } 106e7bf0289SKeiji Hayashibara 107e7bf0289SKeiji Hayashibara static int __uniphier_watchdog_stop(struct regmap *regmap) 108e7bf0289SKeiji Hayashibara { 109e7bf0289SKeiji Hayashibara /* Disable and stop watchdog */ 110e7bf0289SKeiji Hayashibara return regmap_write_bits(regmap, WDTCTRL, WDTCTRL_ENABLE, 0); 111e7bf0289SKeiji Hayashibara } 112e7bf0289SKeiji Hayashibara 113e7bf0289SKeiji Hayashibara static int __uniphier_watchdog_restart(struct regmap *regmap, unsigned int sec) 114e7bf0289SKeiji Hayashibara { 115e7bf0289SKeiji Hayashibara int ret; 116e7bf0289SKeiji Hayashibara 117e7bf0289SKeiji Hayashibara ret = __uniphier_watchdog_stop(regmap); 118e7bf0289SKeiji Hayashibara if (ret) 119e7bf0289SKeiji Hayashibara return ret; 120e7bf0289SKeiji Hayashibara 121e7bf0289SKeiji Hayashibara return __uniphier_watchdog_start(regmap, sec); 122e7bf0289SKeiji Hayashibara } 123e7bf0289SKeiji Hayashibara 124e7bf0289SKeiji Hayashibara static int uniphier_watchdog_start(struct watchdog_device *w) 125e7bf0289SKeiji Hayashibara { 126e7bf0289SKeiji Hayashibara struct uniphier_wdt_dev *wdev = watchdog_get_drvdata(w); 127e7bf0289SKeiji Hayashibara unsigned int tmp_timeout; 128e7bf0289SKeiji Hayashibara 129e7bf0289SKeiji Hayashibara tmp_timeout = roundup_pow_of_two(w->timeout); 130e7bf0289SKeiji Hayashibara 131e7bf0289SKeiji Hayashibara return __uniphier_watchdog_start(wdev->regmap, tmp_timeout); 132e7bf0289SKeiji Hayashibara } 133e7bf0289SKeiji Hayashibara 134e7bf0289SKeiji Hayashibara static int uniphier_watchdog_stop(struct watchdog_device *w) 135e7bf0289SKeiji Hayashibara { 136e7bf0289SKeiji Hayashibara struct uniphier_wdt_dev *wdev = watchdog_get_drvdata(w); 137e7bf0289SKeiji Hayashibara 138e7bf0289SKeiji Hayashibara return __uniphier_watchdog_stop(wdev->regmap); 139e7bf0289SKeiji Hayashibara } 140e7bf0289SKeiji Hayashibara 141e7bf0289SKeiji Hayashibara static int uniphier_watchdog_set_timeout(struct watchdog_device *w, 142e7bf0289SKeiji Hayashibara unsigned int t) 143e7bf0289SKeiji Hayashibara { 144e7bf0289SKeiji Hayashibara struct uniphier_wdt_dev *wdev = watchdog_get_drvdata(w); 145e7bf0289SKeiji Hayashibara unsigned int tmp_timeout; 146e7bf0289SKeiji Hayashibara int ret; 147e7bf0289SKeiji Hayashibara 148e7bf0289SKeiji Hayashibara tmp_timeout = roundup_pow_of_two(t); 149e7bf0289SKeiji Hayashibara if (tmp_timeout == w->timeout) 150e7bf0289SKeiji Hayashibara return 0; 151e7bf0289SKeiji Hayashibara 152e7bf0289SKeiji Hayashibara if (watchdog_active(w)) { 153e7bf0289SKeiji Hayashibara ret = __uniphier_watchdog_restart(wdev->regmap, tmp_timeout); 154e7bf0289SKeiji Hayashibara if (ret) 155e7bf0289SKeiji Hayashibara return ret; 156e7bf0289SKeiji Hayashibara } 157e7bf0289SKeiji Hayashibara 158e7bf0289SKeiji Hayashibara w->timeout = tmp_timeout; 159e7bf0289SKeiji Hayashibara 160e7bf0289SKeiji Hayashibara return 0; 161e7bf0289SKeiji Hayashibara } 162e7bf0289SKeiji Hayashibara 163e7bf0289SKeiji Hayashibara /* 164e7bf0289SKeiji Hayashibara * Kernel Interfaces 165e7bf0289SKeiji Hayashibara */ 166e7bf0289SKeiji Hayashibara static const struct watchdog_info uniphier_wdt_info = { 167e7bf0289SKeiji Hayashibara .identity = "uniphier-wdt", 168e7bf0289SKeiji Hayashibara .options = WDIOF_SETTIMEOUT | 169e7bf0289SKeiji Hayashibara WDIOF_KEEPALIVEPING | 170e7bf0289SKeiji Hayashibara WDIOF_MAGICCLOSE | 171e7bf0289SKeiji Hayashibara WDIOF_OVERHEAT, 172e7bf0289SKeiji Hayashibara }; 173e7bf0289SKeiji Hayashibara 174e7bf0289SKeiji Hayashibara static const struct watchdog_ops uniphier_wdt_ops = { 175e7bf0289SKeiji Hayashibara .owner = THIS_MODULE, 176e7bf0289SKeiji Hayashibara .start = uniphier_watchdog_start, 177e7bf0289SKeiji Hayashibara .stop = uniphier_watchdog_stop, 178e7bf0289SKeiji Hayashibara .ping = uniphier_watchdog_ping, 179e7bf0289SKeiji Hayashibara .set_timeout = uniphier_watchdog_set_timeout, 180e7bf0289SKeiji Hayashibara }; 181e7bf0289SKeiji Hayashibara 182e7bf0289SKeiji Hayashibara static int uniphier_wdt_probe(struct platform_device *pdev) 183e7bf0289SKeiji Hayashibara { 184e7bf0289SKeiji Hayashibara struct device *dev = &pdev->dev; 185e7bf0289SKeiji Hayashibara struct uniphier_wdt_dev *wdev; 186e7bf0289SKeiji Hayashibara struct regmap *regmap; 187e7bf0289SKeiji Hayashibara struct device_node *parent; 188e7bf0289SKeiji Hayashibara int ret; 189e7bf0289SKeiji Hayashibara 190e7bf0289SKeiji Hayashibara wdev = devm_kzalloc(dev, sizeof(*wdev), GFP_KERNEL); 191e7bf0289SKeiji Hayashibara if (!wdev) 192e7bf0289SKeiji Hayashibara return -ENOMEM; 193e7bf0289SKeiji Hayashibara 194e7bf0289SKeiji Hayashibara parent = of_get_parent(dev->of_node); /* parent should be syscon node */ 195e7bf0289SKeiji Hayashibara regmap = syscon_node_to_regmap(parent); 196e7bf0289SKeiji Hayashibara of_node_put(parent); 197e7bf0289SKeiji Hayashibara if (IS_ERR(regmap)) 198e7bf0289SKeiji Hayashibara return PTR_ERR(regmap); 199e7bf0289SKeiji Hayashibara 200e7bf0289SKeiji Hayashibara wdev->regmap = regmap; 201e7bf0289SKeiji Hayashibara wdev->wdt_dev.info = &uniphier_wdt_info; 202e7bf0289SKeiji Hayashibara wdev->wdt_dev.ops = &uniphier_wdt_ops; 203e7bf0289SKeiji Hayashibara wdev->wdt_dev.max_timeout = WDT_PERIOD_MAX; 204e7bf0289SKeiji Hayashibara wdev->wdt_dev.min_timeout = WDT_PERIOD_MIN; 20563089396SMarcus Folkesson wdev->wdt_dev.timeout = WDT_DEFAULT_TIMEOUT; 206e7bf0289SKeiji Hayashibara wdev->wdt_dev.parent = dev; 207e7bf0289SKeiji Hayashibara 20863089396SMarcus Folkesson watchdog_init_timeout(&wdev->wdt_dev, timeout, dev); 209e7bf0289SKeiji Hayashibara watchdog_set_nowayout(&wdev->wdt_dev, nowayout); 210e7bf0289SKeiji Hayashibara watchdog_stop_on_reboot(&wdev->wdt_dev); 211e7bf0289SKeiji Hayashibara 212e7bf0289SKeiji Hayashibara watchdog_set_drvdata(&wdev->wdt_dev, wdev); 213e7bf0289SKeiji Hayashibara 214e7bf0289SKeiji Hayashibara uniphier_watchdog_stop(&wdev->wdt_dev); 215e7bf0289SKeiji Hayashibara ret = regmap_write(wdev->regmap, WDTRSTSEL, WDTRSTSEL_RSTSEL_BOTH); 216e7bf0289SKeiji Hayashibara if (ret) 217e7bf0289SKeiji Hayashibara return ret; 218e7bf0289SKeiji Hayashibara 219e7bf0289SKeiji Hayashibara ret = devm_watchdog_register_device(dev, &wdev->wdt_dev); 220e7bf0289SKeiji Hayashibara if (ret) 221e7bf0289SKeiji Hayashibara return ret; 222e7bf0289SKeiji Hayashibara 223e7bf0289SKeiji Hayashibara dev_info(dev, "watchdog driver (timeout=%d sec, nowayout=%d)\n", 224e7bf0289SKeiji Hayashibara wdev->wdt_dev.timeout, nowayout); 225e7bf0289SKeiji Hayashibara 226e7bf0289SKeiji Hayashibara return 0; 227e7bf0289SKeiji Hayashibara } 228e7bf0289SKeiji Hayashibara 229e7bf0289SKeiji Hayashibara static const struct of_device_id uniphier_wdt_dt_ids[] = { 230e7bf0289SKeiji Hayashibara { .compatible = "socionext,uniphier-wdt" }, 231e7bf0289SKeiji Hayashibara { /* sentinel */ } 232e7bf0289SKeiji Hayashibara }; 233e7bf0289SKeiji Hayashibara MODULE_DEVICE_TABLE(of, uniphier_wdt_dt_ids); 234e7bf0289SKeiji Hayashibara 235e7bf0289SKeiji Hayashibara static struct platform_driver uniphier_wdt_driver = { 236e7bf0289SKeiji Hayashibara .probe = uniphier_wdt_probe, 237e7bf0289SKeiji Hayashibara .driver = { 238e7bf0289SKeiji Hayashibara .name = "uniphier-wdt", 239e7bf0289SKeiji Hayashibara .of_match_table = uniphier_wdt_dt_ids, 240e7bf0289SKeiji Hayashibara }, 241e7bf0289SKeiji Hayashibara }; 242e7bf0289SKeiji Hayashibara 243e7bf0289SKeiji Hayashibara module_platform_driver(uniphier_wdt_driver); 244e7bf0289SKeiji Hayashibara 245e7bf0289SKeiji Hayashibara module_param(timeout, uint, 0000); 246e7bf0289SKeiji Hayashibara MODULE_PARM_DESC(timeout, 247e7bf0289SKeiji Hayashibara "Watchdog timeout seconds in power of 2. (0 < timeout < 128, default=" 248e7bf0289SKeiji Hayashibara __MODULE_STRING(WDT_DEFAULT_TIMEOUT) ")"); 249e7bf0289SKeiji Hayashibara 250e7bf0289SKeiji Hayashibara module_param(nowayout, bool, 0000); 251e7bf0289SKeiji Hayashibara MODULE_PARM_DESC(nowayout, 252e7bf0289SKeiji Hayashibara "Watchdog cannot be stopped once started (default=" 253e7bf0289SKeiji Hayashibara __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 254e7bf0289SKeiji Hayashibara 255e7bf0289SKeiji Hayashibara MODULE_AUTHOR("Keiji Hayashibara <hayashibara.keiji@socionext.com>"); 256e7bf0289SKeiji Hayashibara MODULE_DESCRIPTION("UniPhier Watchdog Device Driver"); 257e7bf0289SKeiji Hayashibara MODULE_LICENSE("GPL v2"); 258