Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

watchdog: uniphier: add UniPhier watchdog driver

Add a watchdog driver for Socionext UniPhier series SoC.
Note that the timeout value for this device must be a power
of 2 because of the specification.

Signed-off-by: Keiji Hayashibara <hayashibara.keiji@socionext.com>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>

authored by

Keiji Hayashibara and committed by
Wim Van Sebroeck
e7bf0289 e75bf0ea

+287
+6
Documentation/watchdog/watchdog-parameters.txt
··· 369 369 nowayout: Watchdog cannot be stopped once started 370 370 (default=kernel config parameter) 371 371 ------------------------------------------------- 372 + uniphier_wdt: 373 + timeout: Watchdog timeout in power of two seconds. 374 + (1 <= timeout <= 128, default=64) 375 + nowayout: Watchdog cannot be stopped once started 376 + (default=kernel config parameter) 377 + ------------------------------------------------- 372 378 w83627hf_wdt: 373 379 wdt_io: w83627hf/thf WDT io port (default 0x2E) 374 380 timeout: Watchdog timeout in seconds. 1 <= timeout <= 255, default=60.
+12
drivers/watchdog/Kconfig
··· 775 775 To compile this driver as a module, choose M here: the 776 776 module will be called stm32_iwdg. 777 777 778 + config UNIPHIER_WATCHDOG 779 + tristate "UniPhier watchdog support" 780 + depends on ARCH_UNIPHIER || COMPILE_TEST 781 + depends on OF && MFD_SYSCON 782 + select WATCHDOG_CORE 783 + help 784 + Say Y here to include support watchdog timer embedded 785 + into the UniPhier system. 786 + 787 + To compile this driver as a module, choose M here: the 788 + module will be called uniphier_wdt. 789 + 778 790 # AVR32 Architecture 779 791 780 792 config AT32AP700X_WDT
+1
drivers/watchdog/Makefile
··· 86 86 obj-$(CONFIG_ASPEED_WATCHDOG) += aspeed_wdt.o 87 87 obj-$(CONFIG_ZX2967_WATCHDOG) += zx2967_wdt.o 88 88 obj-$(CONFIG_STM32_WATCHDOG) += stm32_iwdg.o 89 + obj-$(CONFIG_UNIPHIER_WATCHDOG) += uniphier_wdt.o 89 90 90 91 # AVR32 Architecture 91 92 obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o
+268
drivers/watchdog/uniphier_wdt.c
··· 1 + /* 2 + * Watchdog driver for the UniPhier watchdog timer 3 + * 4 + * (c) Copyright 2014 Panasonic Corporation 5 + * (c) Copyright 2016 Socionext Inc. 6 + * All rights reserved. 7 + * 8 + * This program is free software; you can redistribute it and/or modify 9 + * it under the terms of the GNU General Public License version 2 as 10 + * published by the Free Software Foundation. 11 + * 12 + * This program is distributed in the hope that it will be useful, 13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 + * GNU General Public License for more details. 16 + */ 17 + 18 + #include <linux/bitops.h> 19 + #include <linux/mfd/syscon.h> 20 + #include <linux/module.h> 21 + #include <linux/of.h> 22 + #include <linux/platform_device.h> 23 + #include <linux/regmap.h> 24 + #include <linux/watchdog.h> 25 + 26 + /* WDT timer setting register */ 27 + #define WDTTIMSET 0x3004 28 + #define WDTTIMSET_PERIOD_MASK (0xf << 0) 29 + #define WDTTIMSET_PERIOD_1_SEC (0x3 << 0) 30 + 31 + /* WDT reset selection register */ 32 + #define WDTRSTSEL 0x3008 33 + #define WDTRSTSEL_RSTSEL_MASK (0x3 << 0) 34 + #define WDTRSTSEL_RSTSEL_BOTH (0x0 << 0) 35 + #define WDTRSTSEL_RSTSEL_IRQ_ONLY (0x2 << 0) 36 + 37 + /* WDT control register */ 38 + #define WDTCTRL 0x300c 39 + #define WDTCTRL_STATUS BIT(8) 40 + #define WDTCTRL_CLEAR BIT(1) 41 + #define WDTCTRL_ENABLE BIT(0) 42 + 43 + #define SEC_TO_WDTTIMSET_PRD(sec) \ 44 + (ilog2(sec) + WDTTIMSET_PERIOD_1_SEC) 45 + 46 + #define WDTST_TIMEOUT 1000 /* usec */ 47 + 48 + #define WDT_DEFAULT_TIMEOUT 64 /* Default is 64 seconds */ 49 + #define WDT_PERIOD_MIN 1 50 + #define WDT_PERIOD_MAX 128 51 + 52 + static unsigned int timeout = 0; 53 + static bool nowayout = WATCHDOG_NOWAYOUT; 54 + 55 + struct uniphier_wdt_dev { 56 + struct watchdog_device wdt_dev; 57 + struct regmap *regmap; 58 + }; 59 + 60 + /* 61 + * UniPhier Watchdog operations 62 + */ 63 + static int uniphier_watchdog_ping(struct watchdog_device *w) 64 + { 65 + struct uniphier_wdt_dev *wdev = watchdog_get_drvdata(w); 66 + unsigned int val; 67 + int ret; 68 + 69 + /* Clear counter */ 70 + ret = regmap_write_bits(wdev->regmap, WDTCTRL, 71 + WDTCTRL_CLEAR, WDTCTRL_CLEAR); 72 + if (!ret) 73 + /* 74 + * As SoC specification, after clear counter, 75 + * it needs to wait until counter status is 1. 76 + */ 77 + ret = regmap_read_poll_timeout(wdev->regmap, WDTCTRL, val, 78 + (val & WDTCTRL_STATUS), 79 + 0, WDTST_TIMEOUT); 80 + 81 + return ret; 82 + } 83 + 84 + static int __uniphier_watchdog_start(struct regmap *regmap, unsigned int sec) 85 + { 86 + unsigned int val; 87 + int ret; 88 + 89 + ret = regmap_read_poll_timeout(regmap, WDTCTRL, val, 90 + !(val & WDTCTRL_STATUS), 91 + 0, WDTST_TIMEOUT); 92 + if (ret) 93 + return ret; 94 + 95 + /* Setup period */ 96 + ret = regmap_write(regmap, WDTTIMSET, 97 + SEC_TO_WDTTIMSET_PRD(sec)); 98 + if (ret) 99 + return ret; 100 + 101 + /* Enable and clear watchdog */ 102 + ret = regmap_write(regmap, WDTCTRL, WDTCTRL_ENABLE | WDTCTRL_CLEAR); 103 + if (!ret) 104 + /* 105 + * As SoC specification, after clear counter, 106 + * it needs to wait until counter status is 1. 107 + */ 108 + ret = regmap_read_poll_timeout(regmap, WDTCTRL, val, 109 + (val & WDTCTRL_STATUS), 110 + 0, WDTST_TIMEOUT); 111 + 112 + return ret; 113 + } 114 + 115 + static int __uniphier_watchdog_stop(struct regmap *regmap) 116 + { 117 + /* Disable and stop watchdog */ 118 + return regmap_write_bits(regmap, WDTCTRL, WDTCTRL_ENABLE, 0); 119 + } 120 + 121 + static int __uniphier_watchdog_restart(struct regmap *regmap, unsigned int sec) 122 + { 123 + int ret; 124 + 125 + ret = __uniphier_watchdog_stop(regmap); 126 + if (ret) 127 + return ret; 128 + 129 + return __uniphier_watchdog_start(regmap, sec); 130 + } 131 + 132 + static int uniphier_watchdog_start(struct watchdog_device *w) 133 + { 134 + struct uniphier_wdt_dev *wdev = watchdog_get_drvdata(w); 135 + unsigned int tmp_timeout; 136 + 137 + tmp_timeout = roundup_pow_of_two(w->timeout); 138 + 139 + return __uniphier_watchdog_start(wdev->regmap, tmp_timeout); 140 + } 141 + 142 + static int uniphier_watchdog_stop(struct watchdog_device *w) 143 + { 144 + struct uniphier_wdt_dev *wdev = watchdog_get_drvdata(w); 145 + 146 + return __uniphier_watchdog_stop(wdev->regmap); 147 + } 148 + 149 + static int uniphier_watchdog_set_timeout(struct watchdog_device *w, 150 + unsigned int t) 151 + { 152 + struct uniphier_wdt_dev *wdev = watchdog_get_drvdata(w); 153 + unsigned int tmp_timeout; 154 + int ret; 155 + 156 + tmp_timeout = roundup_pow_of_two(t); 157 + if (tmp_timeout == w->timeout) 158 + return 0; 159 + 160 + if (watchdog_active(w)) { 161 + ret = __uniphier_watchdog_restart(wdev->regmap, tmp_timeout); 162 + if (ret) 163 + return ret; 164 + } 165 + 166 + w->timeout = tmp_timeout; 167 + 168 + return 0; 169 + } 170 + 171 + /* 172 + * Kernel Interfaces 173 + */ 174 + static const struct watchdog_info uniphier_wdt_info = { 175 + .identity = "uniphier-wdt", 176 + .options = WDIOF_SETTIMEOUT | 177 + WDIOF_KEEPALIVEPING | 178 + WDIOF_MAGICCLOSE | 179 + WDIOF_OVERHEAT, 180 + }; 181 + 182 + static const struct watchdog_ops uniphier_wdt_ops = { 183 + .owner = THIS_MODULE, 184 + .start = uniphier_watchdog_start, 185 + .stop = uniphier_watchdog_stop, 186 + .ping = uniphier_watchdog_ping, 187 + .set_timeout = uniphier_watchdog_set_timeout, 188 + }; 189 + 190 + static int uniphier_wdt_probe(struct platform_device *pdev) 191 + { 192 + struct device *dev = &pdev->dev; 193 + struct uniphier_wdt_dev *wdev; 194 + struct regmap *regmap; 195 + struct device_node *parent; 196 + int ret; 197 + 198 + wdev = devm_kzalloc(dev, sizeof(*wdev), GFP_KERNEL); 199 + if (!wdev) 200 + return -ENOMEM; 201 + 202 + platform_set_drvdata(pdev, wdev); 203 + 204 + parent = of_get_parent(dev->of_node); /* parent should be syscon node */ 205 + regmap = syscon_node_to_regmap(parent); 206 + of_node_put(parent); 207 + if (IS_ERR(regmap)) 208 + return PTR_ERR(regmap); 209 + 210 + wdev->regmap = regmap; 211 + wdev->wdt_dev.info = &uniphier_wdt_info; 212 + wdev->wdt_dev.ops = &uniphier_wdt_ops; 213 + wdev->wdt_dev.max_timeout = WDT_PERIOD_MAX; 214 + wdev->wdt_dev.min_timeout = WDT_PERIOD_MIN; 215 + wdev->wdt_dev.parent = dev; 216 + 217 + if (watchdog_init_timeout(&wdev->wdt_dev, timeout, dev) < 0) { 218 + wdev->wdt_dev.timeout = WDT_DEFAULT_TIMEOUT; 219 + } 220 + watchdog_set_nowayout(&wdev->wdt_dev, nowayout); 221 + watchdog_stop_on_reboot(&wdev->wdt_dev); 222 + 223 + watchdog_set_drvdata(&wdev->wdt_dev, wdev); 224 + 225 + uniphier_watchdog_stop(&wdev->wdt_dev); 226 + ret = regmap_write(wdev->regmap, WDTRSTSEL, WDTRSTSEL_RSTSEL_BOTH); 227 + if (ret) 228 + return ret; 229 + 230 + ret = devm_watchdog_register_device(dev, &wdev->wdt_dev); 231 + if (ret) 232 + return ret; 233 + 234 + dev_info(dev, "watchdog driver (timeout=%d sec, nowayout=%d)\n", 235 + wdev->wdt_dev.timeout, nowayout); 236 + 237 + return 0; 238 + } 239 + 240 + static const struct of_device_id uniphier_wdt_dt_ids[] = { 241 + { .compatible = "socionext,uniphier-wdt" }, 242 + { /* sentinel */ } 243 + }; 244 + MODULE_DEVICE_TABLE(of, uniphier_wdt_dt_ids); 245 + 246 + static struct platform_driver uniphier_wdt_driver = { 247 + .probe = uniphier_wdt_probe, 248 + .driver = { 249 + .name = "uniphier-wdt", 250 + .of_match_table = uniphier_wdt_dt_ids, 251 + }, 252 + }; 253 + 254 + module_platform_driver(uniphier_wdt_driver); 255 + 256 + module_param(timeout, uint, 0000); 257 + MODULE_PARM_DESC(timeout, 258 + "Watchdog timeout seconds in power of 2. (0 < timeout < 128, default=" 259 + __MODULE_STRING(WDT_DEFAULT_TIMEOUT) ")"); 260 + 261 + module_param(nowayout, bool, 0000); 262 + MODULE_PARM_DESC(nowayout, 263 + "Watchdog cannot be stopped once started (default=" 264 + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 265 + 266 + MODULE_AUTHOR("Keiji Hayashibara <hayashibara.keiji@socionext.com>"); 267 + MODULE_DESCRIPTION("UniPhier Watchdog Device Driver"); 268 + MODULE_LICENSE("GPL v2");