Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 | // SPDX-License-Identifier: GPL-2.0+ /* * Watchdog driver for Marvell Armada 37xx SoCs * * Author: Marek BehĂșn <kabel@kernel.org> */ #include <linux/clk.h> #include <linux/err.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/kernel.h> #include <linux/mfd/syscon.h> #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/of.h> #include <linux/platform_device.h> #include <linux/regmap.h> #include <linux/types.h> #include <linux/watchdog.h> /* * There are four counters that can be used for watchdog on Armada 37xx. * The addresses for counter control registers are register base plus ID*0x10, * where ID is 0, 1, 2 or 3. * * In this driver we use IDs 0 and 1. Counter ID 1 is used as watchdog counter, * while counter ID 0 is used to implement pinging the watchdog: counter ID 1 is * set to restart counting from initial value on counter ID 0 end count event. * Pinging is done by forcing immediate end count event on counter ID 0. * If only one counter was used, pinging would have to be implemented by * disabling and enabling the counter, leaving the system in a vulnerable state * for a (really) short period of time. * * Counters ID 2 and 3 are enabled by default even before U-Boot loads, * therefore this driver does not provide a way to use them, eg. by setting a * property in device tree. */ #define CNTR_ID_RETRIGGER 0 #define CNTR_ID_WDOG 1 /* relative to cpu_misc */ #define WDT_TIMER_SELECT 0x64 #define WDT_TIMER_SELECT_MASK 0xf #define WDT_TIMER_SELECT_VAL BIT(CNTR_ID_WDOG) /* relative to reg */ #define CNTR_CTRL(id) ((id) * 0x10) #define CNTR_CTRL_ENABLE 0x0001 #define CNTR_CTRL_ACTIVE 0x0002 #define CNTR_CTRL_MODE_MASK 0x000c #define CNTR_CTRL_MODE_ONESHOT 0x0000 #define CNTR_CTRL_MODE_HWSIG 0x000c #define CNTR_CTRL_TRIG_SRC_MASK 0x00f0 #define CNTR_CTRL_TRIG_SRC_PREV_CNTR 0x0050 #define CNTR_CTRL_PRESCALE_MASK 0xff00 #define CNTR_CTRL_PRESCALE_MIN 2 #define CNTR_CTRL_PRESCALE_SHIFT 8 #define CNTR_COUNT_LOW(id) (CNTR_CTRL(id) + 0x4) #define CNTR_COUNT_HIGH(id) (CNTR_CTRL(id) + 0x8) #define WATCHDOG_TIMEOUT 120 static unsigned int timeout; module_param(timeout, int, 0); MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds"); static bool nowayout = WATCHDOG_NOWAYOUT; module_param(nowayout, bool, 0); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); struct armada_37xx_watchdog { struct watchdog_device wdt; struct regmap *cpu_misc; void __iomem *reg; u64 timeout; /* in clock ticks */ unsigned long clk_rate; struct clk *clk; }; static u64 get_counter_value(struct armada_37xx_watchdog *dev, int id) { u64 val; /* * when low is read, high is latched into flip-flops so that it can be * read consistently without using software debouncing */ val = readl(dev->reg + CNTR_COUNT_LOW(id)); val |= ((u64)readl(dev->reg + CNTR_COUNT_HIGH(id))) << 32; return val; } static void set_counter_value(struct armada_37xx_watchdog *dev, int id, u64 val) { writel(val & 0xffffffff, dev->reg + CNTR_COUNT_LOW(id)); writel(val >> 32, dev->reg + CNTR_COUNT_HIGH(id)); } static void counter_enable(struct armada_37xx_watchdog *dev, int id) { u32 reg; reg = readl(dev->reg + CNTR_CTRL(id)); reg |= CNTR_CTRL_ENABLE; writel(reg, dev->reg + CNTR_CTRL(id)); } static void counter_disable(struct armada_37xx_watchdog *dev, int id) { u32 reg; reg = readl(dev->reg + CNTR_CTRL(id)); reg &= ~CNTR_CTRL_ENABLE; writel(reg, dev->reg + CNTR_CTRL(id)); } static void init_counter(struct armada_37xx_watchdog *dev, int id, u32 mode, u32 trig_src) { u32 reg; reg = readl(dev->reg + CNTR_CTRL(id)); reg &= ~(CNTR_CTRL_MODE_MASK | CNTR_CTRL_PRESCALE_MASK | CNTR_CTRL_TRIG_SRC_MASK); /* set mode */ reg |= mode & CNTR_CTRL_MODE_MASK; /* set prescaler to the min value */ reg |= CNTR_CTRL_PRESCALE_MIN << CNTR_CTRL_PRESCALE_SHIFT; /* set trigger source */ reg |= trig_src & CNTR_CTRL_TRIG_SRC_MASK; writel(reg, dev->reg + CNTR_CTRL(id)); } static int armada_37xx_wdt_ping(struct watchdog_device *wdt) { struct armada_37xx_watchdog *dev = watchdog_get_drvdata(wdt); /* counter 1 is retriggered by forcing end count on counter 0 */ counter_disable(dev, CNTR_ID_RETRIGGER); counter_enable(dev, CNTR_ID_RETRIGGER); return 0; } static unsigned int armada_37xx_wdt_get_timeleft(struct watchdog_device *wdt) { struct armada_37xx_watchdog *dev = watchdog_get_drvdata(wdt); u64 res; res = get_counter_value(dev, CNTR_ID_WDOG) * CNTR_CTRL_PRESCALE_MIN; do_div(res, dev->clk_rate); return res; } static int armada_37xx_wdt_set_timeout(struct watchdog_device *wdt, unsigned int timeout) { struct armada_37xx_watchdog *dev = watchdog_get_drvdata(wdt); wdt->timeout = timeout; /* * Compute the timeout in clock rate. We use smallest possible * prescaler, which divides the clock rate by 2 * (CNTR_CTRL_PRESCALE_MIN). */ dev->timeout = (u64)dev->clk_rate * timeout; do_div(dev->timeout, CNTR_CTRL_PRESCALE_MIN); set_counter_value(dev, CNTR_ID_WDOG, dev->timeout); return 0; } static bool armada_37xx_wdt_is_running(struct armada_37xx_watchdog *dev) { u32 reg; regmap_read(dev->cpu_misc, WDT_TIMER_SELECT, ®); if ((reg & WDT_TIMER_SELECT_MASK) != WDT_TIMER_SELECT_VAL) return false; reg = readl(dev->reg + CNTR_CTRL(CNTR_ID_WDOG)); return !!(reg & CNTR_CTRL_ACTIVE); } static int armada_37xx_wdt_start(struct watchdog_device *wdt) { struct armada_37xx_watchdog *dev = watchdog_get_drvdata(wdt); /* select counter 1 as watchdog counter */ regmap_write(dev->cpu_misc, WDT_TIMER_SELECT, WDT_TIMER_SELECT_VAL); /* init counter 0 as retrigger counter for counter 1 */ init_counter(dev, CNTR_ID_RETRIGGER, CNTR_CTRL_MODE_ONESHOT, 0); set_counter_value(dev, CNTR_ID_RETRIGGER, 0); /* init counter 1 to be retriggerable by counter 0 end count */ init_counter(dev, CNTR_ID_WDOG, CNTR_CTRL_MODE_HWSIG, CNTR_CTRL_TRIG_SRC_PREV_CNTR); set_counter_value(dev, CNTR_ID_WDOG, dev->timeout); /* enable counter 1 */ counter_enable(dev, CNTR_ID_WDOG); /* start counter 1 by forcing immediate end count on counter 0 */ counter_enable(dev, CNTR_ID_RETRIGGER); return 0; } static int armada_37xx_wdt_stop(struct watchdog_device *wdt) { struct armada_37xx_watchdog *dev = watchdog_get_drvdata(wdt); counter_disable(dev, CNTR_ID_WDOG); counter_disable(dev, CNTR_ID_RETRIGGER); regmap_write(dev->cpu_misc, WDT_TIMER_SELECT, 0); return 0; } static const struct watchdog_info armada_37xx_wdt_info = { .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, .identity = "Armada 37xx Watchdog", }; static const struct watchdog_ops armada_37xx_wdt_ops = { .owner = THIS_MODULE, .start = armada_37xx_wdt_start, .stop = armada_37xx_wdt_stop, .ping = armada_37xx_wdt_ping, .set_timeout = armada_37xx_wdt_set_timeout, .get_timeleft = armada_37xx_wdt_get_timeleft, }; static int armada_37xx_wdt_probe(struct platform_device *pdev) { struct armada_37xx_watchdog *dev; struct resource *res; struct regmap *regmap; int ret; dev = devm_kzalloc(&pdev->dev, sizeof(struct armada_37xx_watchdog), GFP_KERNEL); if (!dev) return -ENOMEM; dev->wdt.info = &armada_37xx_wdt_info; dev->wdt.ops = &armada_37xx_wdt_ops; regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, "marvell,system-controller"); if (IS_ERR(regmap)) return PTR_ERR(regmap); dev->cpu_misc = regmap; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) return -ENODEV; dev->reg = devm_ioremap(&pdev->dev, res->start, resource_size(res)); if (!dev->reg) return -ENOMEM; /* init clock */ dev->clk = devm_clk_get_enabled(&pdev->dev, NULL); if (IS_ERR(dev->clk)) return PTR_ERR(dev->clk); dev->clk_rate = clk_get_rate(dev->clk); if (!dev->clk_rate) return -EINVAL; /* * Since the timeout in seconds is given as 32 bit unsigned int, and * the counters hold 64 bit values, even after multiplication by clock * rate the counter can hold timeout of UINT_MAX seconds. */ dev->wdt.min_timeout = 1; dev->wdt.max_timeout = UINT_MAX; dev->wdt.parent = &pdev->dev; /* default value, possibly override by module parameter or dtb */ dev->wdt.timeout = WATCHDOG_TIMEOUT; watchdog_init_timeout(&dev->wdt, timeout, &pdev->dev); platform_set_drvdata(pdev, &dev->wdt); watchdog_set_drvdata(&dev->wdt, dev); armada_37xx_wdt_set_timeout(&dev->wdt, dev->wdt.timeout); if (armada_37xx_wdt_is_running(dev)) set_bit(WDOG_HW_RUNNING, &dev->wdt.status); watchdog_set_nowayout(&dev->wdt, nowayout); watchdog_stop_on_reboot(&dev->wdt); ret = devm_watchdog_register_device(&pdev->dev, &dev->wdt); if (ret) return ret; dev_info(&pdev->dev, "Initial timeout %d sec%s\n", dev->wdt.timeout, nowayout ? ", nowayout" : ""); return 0; } static int __maybe_unused armada_37xx_wdt_suspend(struct device *dev) { struct watchdog_device *wdt = dev_get_drvdata(dev); return armada_37xx_wdt_stop(wdt); } static int __maybe_unused armada_37xx_wdt_resume(struct device *dev) { struct watchdog_device *wdt = dev_get_drvdata(dev); if (watchdog_active(wdt)) return armada_37xx_wdt_start(wdt); return 0; } static const struct dev_pm_ops armada_37xx_wdt_dev_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(armada_37xx_wdt_suspend, armada_37xx_wdt_resume) }; #ifdef CONFIG_OF static const struct of_device_id armada_37xx_wdt_match[] = { { .compatible = "marvell,armada-3700-wdt", }, {}, }; MODULE_DEVICE_TABLE(of, armada_37xx_wdt_match); #endif static struct platform_driver armada_37xx_wdt_driver = { .probe = armada_37xx_wdt_probe, .driver = { .name = "armada_37xx_wdt", .of_match_table = of_match_ptr(armada_37xx_wdt_match), .pm = &armada_37xx_wdt_dev_pm_ops, }, }; module_platform_driver(armada_37xx_wdt_driver); MODULE_AUTHOR("Marek Behun <kabel@kernel.org>"); MODULE_DESCRIPTION("Armada 37xx CPU Watchdog"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:armada_37xx_wdt"); |