| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * Copyright 2010-2011 Picochip Ltd., Jamie Iles |
|---|
| 3 | | - * http://www.picochip.com |
|---|
| 4 | | - * |
|---|
| 5 | | - * This program is free software; you can redistribute it and/or |
|---|
| 6 | | - * modify it under the terms of the GNU General Public License |
|---|
| 7 | | - * as published by the Free Software Foundation; either version |
|---|
| 8 | | - * 2 of the License, or (at your option) any later version. |
|---|
| 4 | + * https://www.picochip.com |
|---|
| 9 | 5 | * |
|---|
| 10 | 6 | * This file implements a driver for the Synopsys DesignWare watchdog device |
|---|
| 11 | 7 | * in the many subsystems. The watchdog has 16 different timeout periods |
|---|
| .. | .. |
|---|
| 16 | 12 | * heartbeat requests after the watchdog device has been closed. |
|---|
| 17 | 13 | */ |
|---|
| 18 | 14 | |
|---|
| 19 | | -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
|---|
| 20 | | - |
|---|
| 21 | 15 | #include <linux/bitops.h> |
|---|
| 16 | +#include <linux/limits.h> |
|---|
| 17 | +#include <linux/kernel.h> |
|---|
| 22 | 18 | #include <linux/clk.h> |
|---|
| 23 | 19 | #include <linux/delay.h> |
|---|
| 24 | 20 | #include <linux/err.h> |
|---|
| .. | .. |
|---|
| 26 | 22 | #include <linux/kernel.h> |
|---|
| 27 | 23 | #include <linux/module.h> |
|---|
| 28 | 24 | #include <linux/moduleparam.h> |
|---|
| 25 | +#include <linux/interrupt.h> |
|---|
| 29 | 26 | #include <linux/of.h> |
|---|
| 30 | 27 | #include <linux/pm.h> |
|---|
| 31 | 28 | #include <linux/platform_device.h> |
|---|
| 32 | 29 | #include <linux/reset.h> |
|---|
| 33 | 30 | #include <linux/watchdog.h> |
|---|
| 31 | +#include <linux/debugfs.h> |
|---|
| 34 | 32 | |
|---|
| 35 | 33 | #define WDOG_CONTROL_REG_OFFSET 0x00 |
|---|
| 36 | 34 | #define WDOG_CONTROL_REG_WDT_EN_MASK 0x01 |
|---|
| .. | .. |
|---|
| 40 | 38 | #define WDOG_CURRENT_COUNT_REG_OFFSET 0x08 |
|---|
| 41 | 39 | #define WDOG_COUNTER_RESTART_REG_OFFSET 0x0c |
|---|
| 42 | 40 | #define WDOG_COUNTER_RESTART_KICK_VALUE 0x76 |
|---|
| 41 | +#define WDOG_INTERRUPT_STATUS_REG_OFFSET 0x10 |
|---|
| 42 | +#define WDOG_INTERRUPT_CLEAR_REG_OFFSET 0x14 |
|---|
| 43 | +#define WDOG_COMP_PARAMS_5_REG_OFFSET 0xe4 |
|---|
| 44 | +#define WDOG_COMP_PARAMS_4_REG_OFFSET 0xe8 |
|---|
| 45 | +#define WDOG_COMP_PARAMS_3_REG_OFFSET 0xec |
|---|
| 46 | +#define WDOG_COMP_PARAMS_2_REG_OFFSET 0xf0 |
|---|
| 47 | +#define WDOG_COMP_PARAMS_1_REG_OFFSET 0xf4 |
|---|
| 48 | +#define WDOG_COMP_PARAMS_1_USE_FIX_TOP BIT(6) |
|---|
| 49 | +#define WDOG_COMP_VERSION_REG_OFFSET 0xf8 |
|---|
| 50 | +#define WDOG_COMP_TYPE_REG_OFFSET 0xfc |
|---|
| 43 | 51 | |
|---|
| 44 | | -/* The maximum TOP (timeout period) value that can be set in the watchdog. */ |
|---|
| 45 | | -#define DW_WDT_MAX_TOP 15 |
|---|
| 52 | +/* There are sixteen TOPs (timeout periods) that can be set in the watchdog. */ |
|---|
| 53 | +#define DW_WDT_NUM_TOPS 16 |
|---|
| 54 | +#define DW_WDT_FIX_TOP(_idx) (1U << (16 + _idx)) |
|---|
| 46 | 55 | |
|---|
| 47 | 56 | #define DW_WDT_DEFAULT_SECONDS 30 |
|---|
| 57 | + |
|---|
| 58 | +static const u32 dw_wdt_fix_tops[DW_WDT_NUM_TOPS] = { |
|---|
| 59 | + DW_WDT_FIX_TOP(0), DW_WDT_FIX_TOP(1), DW_WDT_FIX_TOP(2), |
|---|
| 60 | + DW_WDT_FIX_TOP(3), DW_WDT_FIX_TOP(4), DW_WDT_FIX_TOP(5), |
|---|
| 61 | + DW_WDT_FIX_TOP(6), DW_WDT_FIX_TOP(7), DW_WDT_FIX_TOP(8), |
|---|
| 62 | + DW_WDT_FIX_TOP(9), DW_WDT_FIX_TOP(10), DW_WDT_FIX_TOP(11), |
|---|
| 63 | + DW_WDT_FIX_TOP(12), DW_WDT_FIX_TOP(13), DW_WDT_FIX_TOP(14), |
|---|
| 64 | + DW_WDT_FIX_TOP(15) |
|---|
| 65 | +}; |
|---|
| 48 | 66 | |
|---|
| 49 | 67 | static bool nowayout = WATCHDOG_NOWAYOUT; |
|---|
| 50 | 68 | module_param(nowayout, bool, 0); |
|---|
| 51 | 69 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " |
|---|
| 52 | 70 | "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); |
|---|
| 53 | 71 | |
|---|
| 72 | +enum dw_wdt_rmod { |
|---|
| 73 | + DW_WDT_RMOD_RESET = 1, |
|---|
| 74 | + DW_WDT_RMOD_IRQ = 2 |
|---|
| 75 | +}; |
|---|
| 76 | + |
|---|
| 77 | +struct dw_wdt_timeout { |
|---|
| 78 | + u32 top_val; |
|---|
| 79 | + unsigned int sec; |
|---|
| 80 | + unsigned int msec; |
|---|
| 81 | +}; |
|---|
| 82 | + |
|---|
| 54 | 83 | struct dw_wdt { |
|---|
| 55 | 84 | void __iomem *regs; |
|---|
| 56 | 85 | struct clk *clk; |
|---|
| 57 | 86 | struct clk *pclk; |
|---|
| 58 | 87 | unsigned long rate; |
|---|
| 88 | + enum dw_wdt_rmod rmod; |
|---|
| 89 | + struct dw_wdt_timeout timeouts[DW_WDT_NUM_TOPS]; |
|---|
| 59 | 90 | struct watchdog_device wdd; |
|---|
| 60 | 91 | struct reset_control *rst; |
|---|
| 61 | 92 | /* Save/restore */ |
|---|
| 62 | 93 | u32 control; |
|---|
| 63 | 94 | u32 timeout; |
|---|
| 95 | + |
|---|
| 96 | +#ifdef CONFIG_DEBUG_FS |
|---|
| 97 | + struct dentry *dbgfs_dir; |
|---|
| 98 | +#endif |
|---|
| 64 | 99 | }; |
|---|
| 65 | 100 | |
|---|
| 66 | 101 | #define to_dw_wdt(wdd) container_of(wdd, struct dw_wdt, wdd) |
|---|
| .. | .. |
|---|
| 71 | 106 | WDOG_CONTROL_REG_WDT_EN_MASK; |
|---|
| 72 | 107 | } |
|---|
| 73 | 108 | |
|---|
| 74 | | -static inline int dw_wdt_top_in_seconds(struct dw_wdt *dw_wdt, unsigned top) |
|---|
| 109 | +static void dw_wdt_update_mode(struct dw_wdt *dw_wdt, enum dw_wdt_rmod rmod) |
|---|
| 75 | 110 | { |
|---|
| 76 | | - /* |
|---|
| 77 | | - * There are 16 possible timeout values in 0..15 where the number of |
|---|
| 78 | | - * cycles is 2 ^ (16 + i) and the watchdog counts down. |
|---|
| 79 | | - */ |
|---|
| 80 | | - return (1U << (16 + top)) / dw_wdt->rate; |
|---|
| 111 | + u32 val; |
|---|
| 112 | + |
|---|
| 113 | + val = readl(dw_wdt->regs + WDOG_CONTROL_REG_OFFSET); |
|---|
| 114 | + if (rmod == DW_WDT_RMOD_IRQ) |
|---|
| 115 | + val |= WDOG_CONTROL_REG_RESP_MODE_MASK; |
|---|
| 116 | + else |
|---|
| 117 | + val &= ~WDOG_CONTROL_REG_RESP_MODE_MASK; |
|---|
| 118 | + writel(val, dw_wdt->regs + WDOG_CONTROL_REG_OFFSET); |
|---|
| 119 | + |
|---|
| 120 | + dw_wdt->rmod = rmod; |
|---|
| 81 | 121 | } |
|---|
| 82 | 122 | |
|---|
| 83 | | -static int dw_wdt_get_top(struct dw_wdt *dw_wdt) |
|---|
| 123 | +static unsigned int dw_wdt_find_best_top(struct dw_wdt *dw_wdt, |
|---|
| 124 | + unsigned int timeout, u32 *top_val) |
|---|
| 84 | 125 | { |
|---|
| 85 | | - int top = readl(dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET) & 0xF; |
|---|
| 126 | + int idx; |
|---|
| 86 | 127 | |
|---|
| 87 | | - return dw_wdt_top_in_seconds(dw_wdt, top); |
|---|
| 128 | + /* |
|---|
| 129 | + * Find a TOP with timeout greater or equal to the requested number. |
|---|
| 130 | + * Note we'll select a TOP with maximum timeout if the requested |
|---|
| 131 | + * timeout couldn't be reached. |
|---|
| 132 | + */ |
|---|
| 133 | + for (idx = 0; idx < DW_WDT_NUM_TOPS; ++idx) { |
|---|
| 134 | + if (dw_wdt->timeouts[idx].sec >= timeout) |
|---|
| 135 | + break; |
|---|
| 136 | + } |
|---|
| 137 | + |
|---|
| 138 | + if (idx == DW_WDT_NUM_TOPS) |
|---|
| 139 | + --idx; |
|---|
| 140 | + |
|---|
| 141 | + *top_val = dw_wdt->timeouts[idx].top_val; |
|---|
| 142 | + |
|---|
| 143 | + return dw_wdt->timeouts[idx].sec; |
|---|
| 144 | +} |
|---|
| 145 | + |
|---|
| 146 | +static unsigned int dw_wdt_get_min_timeout(struct dw_wdt *dw_wdt) |
|---|
| 147 | +{ |
|---|
| 148 | + int idx; |
|---|
| 149 | + |
|---|
| 150 | + /* |
|---|
| 151 | + * We'll find a timeout greater or equal to one second anyway because |
|---|
| 152 | + * the driver probe would have failed if there was none. |
|---|
| 153 | + */ |
|---|
| 154 | + for (idx = 0; idx < DW_WDT_NUM_TOPS; ++idx) { |
|---|
| 155 | + if (dw_wdt->timeouts[idx].sec) |
|---|
| 156 | + break; |
|---|
| 157 | + } |
|---|
| 158 | + |
|---|
| 159 | + /* For Coverity check */ |
|---|
| 160 | + if (idx == DW_WDT_NUM_TOPS) |
|---|
| 161 | + idx = 0; |
|---|
| 162 | + |
|---|
| 163 | + return dw_wdt->timeouts[idx].sec; |
|---|
| 164 | +} |
|---|
| 165 | + |
|---|
| 166 | +static unsigned int dw_wdt_get_max_timeout_ms(struct dw_wdt *dw_wdt) |
|---|
| 167 | +{ |
|---|
| 168 | + struct dw_wdt_timeout *timeout = &dw_wdt->timeouts[DW_WDT_NUM_TOPS - 1]; |
|---|
| 169 | + u64 msec; |
|---|
| 170 | + |
|---|
| 171 | + msec = (u64)timeout->sec * MSEC_PER_SEC + timeout->msec; |
|---|
| 172 | + |
|---|
| 173 | + return msec < UINT_MAX ? msec : UINT_MAX; |
|---|
| 174 | +} |
|---|
| 175 | + |
|---|
| 176 | +static unsigned int dw_wdt_get_timeout(struct dw_wdt *dw_wdt) |
|---|
| 177 | +{ |
|---|
| 178 | + int top_val = readl(dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET) & 0xF; |
|---|
| 179 | + int idx; |
|---|
| 180 | + |
|---|
| 181 | + for (idx = 0; idx < DW_WDT_NUM_TOPS; ++idx) { |
|---|
| 182 | + if (dw_wdt->timeouts[idx].top_val == top_val) |
|---|
| 183 | + break; |
|---|
| 184 | + } |
|---|
| 185 | + |
|---|
| 186 | + if (idx == DW_WDT_NUM_TOPS) |
|---|
| 187 | + idx = 0; |
|---|
| 188 | + |
|---|
| 189 | + /* |
|---|
| 190 | + * In IRQ mode due to the two stages counter, the actual timeout is |
|---|
| 191 | + * twice greater than the TOP setting. |
|---|
| 192 | + */ |
|---|
| 193 | + return dw_wdt->timeouts[idx].sec * dw_wdt->rmod; |
|---|
| 88 | 194 | } |
|---|
| 89 | 195 | |
|---|
| 90 | 196 | static int dw_wdt_ping(struct watchdog_device *wdd) |
|---|
| .. | .. |
|---|
| 100 | 206 | static int dw_wdt_set_timeout(struct watchdog_device *wdd, unsigned int top_s) |
|---|
| 101 | 207 | { |
|---|
| 102 | 208 | struct dw_wdt *dw_wdt = to_dw_wdt(wdd); |
|---|
| 103 | | - int i, top_val = DW_WDT_MAX_TOP; |
|---|
| 209 | + unsigned int timeout; |
|---|
| 210 | + u32 top_val; |
|---|
| 104 | 211 | |
|---|
| 105 | 212 | /* |
|---|
| 106 | | - * Iterate over the timeout values until we find the closest match. We |
|---|
| 107 | | - * always look for >=. |
|---|
| 213 | + * Note IRQ mode being enabled means having a non-zero pre-timeout |
|---|
| 214 | + * setup. In this case we try to find a TOP as close to the half of the |
|---|
| 215 | + * requested timeout as possible since DW Watchdog IRQ mode is designed |
|---|
| 216 | + * in two stages way - first timeout rises the pre-timeout interrupt, |
|---|
| 217 | + * second timeout performs the system reset. So basically the effective |
|---|
| 218 | + * watchdog-caused reset happens after two watchdog TOPs elapsed. |
|---|
| 108 | 219 | */ |
|---|
| 109 | | - for (i = 0; i <= DW_WDT_MAX_TOP; ++i) |
|---|
| 110 | | - if (dw_wdt_top_in_seconds(dw_wdt, i) >= top_s) { |
|---|
| 111 | | - top_val = i; |
|---|
| 112 | | - break; |
|---|
| 113 | | - } |
|---|
| 220 | + timeout = dw_wdt_find_best_top(dw_wdt, DIV_ROUND_UP(top_s, dw_wdt->rmod), |
|---|
| 221 | + &top_val); |
|---|
| 222 | + if (dw_wdt->rmod == DW_WDT_RMOD_IRQ) |
|---|
| 223 | + wdd->pretimeout = timeout; |
|---|
| 224 | + else |
|---|
| 225 | + wdd->pretimeout = 0; |
|---|
| 114 | 226 | |
|---|
| 115 | 227 | /* |
|---|
| 116 | 228 | * Set the new value in the watchdog. Some versions of dw_wdt |
|---|
| .. | .. |
|---|
| 121 | 233 | writel(top_val | top_val << WDOG_TIMEOUT_RANGE_TOPINIT_SHIFT, |
|---|
| 122 | 234 | dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET); |
|---|
| 123 | 235 | |
|---|
| 124 | | - wdd->timeout = dw_wdt_top_in_seconds(dw_wdt, top_val); |
|---|
| 236 | + /* Kick new TOP value into the watchdog counter if activated. */ |
|---|
| 237 | + if (watchdog_active(wdd)) |
|---|
| 238 | + dw_wdt_ping(wdd); |
|---|
| 239 | + |
|---|
| 240 | + /* |
|---|
| 241 | + * In case users set bigger timeout value than HW can support, |
|---|
| 242 | + * kernel(watchdog_dev.c) helps to feed watchdog before |
|---|
| 243 | + * wdd->max_hw_heartbeat_ms |
|---|
| 244 | + */ |
|---|
| 245 | + if (top_s * 1000 <= wdd->max_hw_heartbeat_ms) |
|---|
| 246 | + wdd->timeout = timeout * dw_wdt->rmod; |
|---|
| 247 | + else |
|---|
| 248 | + wdd->timeout = top_s; |
|---|
| 249 | + |
|---|
| 250 | + return 0; |
|---|
| 251 | +} |
|---|
| 252 | + |
|---|
| 253 | +static int dw_wdt_set_pretimeout(struct watchdog_device *wdd, unsigned int req) |
|---|
| 254 | +{ |
|---|
| 255 | + struct dw_wdt *dw_wdt = to_dw_wdt(wdd); |
|---|
| 256 | + |
|---|
| 257 | + /* |
|---|
| 258 | + * We ignore actual value of the timeout passed from user-space |
|---|
| 259 | + * using it as a flag whether the pretimeout functionality is intended |
|---|
| 260 | + * to be activated. |
|---|
| 261 | + */ |
|---|
| 262 | + dw_wdt_update_mode(dw_wdt, req ? DW_WDT_RMOD_IRQ : DW_WDT_RMOD_RESET); |
|---|
| 263 | + dw_wdt_set_timeout(wdd, wdd->timeout); |
|---|
| 125 | 264 | |
|---|
| 126 | 265 | return 0; |
|---|
| 127 | 266 | } |
|---|
| .. | .. |
|---|
| 130 | 269 | { |
|---|
| 131 | 270 | u32 val = readl(dw_wdt->regs + WDOG_CONTROL_REG_OFFSET); |
|---|
| 132 | 271 | |
|---|
| 133 | | - /* Disable interrupt mode; always perform system reset. */ |
|---|
| 134 | | - val &= ~WDOG_CONTROL_REG_RESP_MODE_MASK; |
|---|
| 272 | + /* Disable/enable interrupt mode depending on the RMOD flag. */ |
|---|
| 273 | + if (dw_wdt->rmod == DW_WDT_RMOD_IRQ) |
|---|
| 274 | + val |= WDOG_CONTROL_REG_RESP_MODE_MASK; |
|---|
| 275 | + else |
|---|
| 276 | + val &= ~WDOG_CONTROL_REG_RESP_MODE_MASK; |
|---|
| 135 | 277 | /* Enable watchdog. */ |
|---|
| 136 | 278 | val |= WDOG_CONTROL_REG_WDT_EN_MASK; |
|---|
| 137 | 279 | writel(val, dw_wdt->regs + WDOG_CONTROL_REG_OFFSET); |
|---|
| .. | .. |
|---|
| 169 | 311 | struct dw_wdt *dw_wdt = to_dw_wdt(wdd); |
|---|
| 170 | 312 | |
|---|
| 171 | 313 | writel(0, dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET); |
|---|
| 314 | + dw_wdt_update_mode(dw_wdt, DW_WDT_RMOD_RESET); |
|---|
| 172 | 315 | if (dw_wdt_is_enabled(dw_wdt)) |
|---|
| 173 | 316 | writel(WDOG_COUNTER_RESTART_KICK_VALUE, |
|---|
| 174 | 317 | dw_wdt->regs + WDOG_COUNTER_RESTART_REG_OFFSET); |
|---|
| .. | .. |
|---|
| 184 | 327 | static unsigned int dw_wdt_get_timeleft(struct watchdog_device *wdd) |
|---|
| 185 | 328 | { |
|---|
| 186 | 329 | struct dw_wdt *dw_wdt = to_dw_wdt(wdd); |
|---|
| 330 | + unsigned int sec; |
|---|
| 331 | + u32 val; |
|---|
| 187 | 332 | |
|---|
| 188 | | - return readl(dw_wdt->regs + WDOG_CURRENT_COUNT_REG_OFFSET) / |
|---|
| 189 | | - dw_wdt->rate; |
|---|
| 333 | + val = readl(dw_wdt->regs + WDOG_CURRENT_COUNT_REG_OFFSET); |
|---|
| 334 | + sec = val / dw_wdt->rate; |
|---|
| 335 | + |
|---|
| 336 | + if (dw_wdt->rmod == DW_WDT_RMOD_IRQ) { |
|---|
| 337 | + val = readl(dw_wdt->regs + WDOG_INTERRUPT_STATUS_REG_OFFSET); |
|---|
| 338 | + if (!val) |
|---|
| 339 | + sec += wdd->pretimeout; |
|---|
| 340 | + } |
|---|
| 341 | + |
|---|
| 342 | + return sec; |
|---|
| 190 | 343 | } |
|---|
| 191 | 344 | |
|---|
| 192 | 345 | static const struct watchdog_info dw_wdt_ident = { |
|---|
| 193 | 346 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | |
|---|
| 194 | 347 | WDIOF_MAGICCLOSE, |
|---|
| 348 | + .identity = "Synopsys DesignWare Watchdog", |
|---|
| 349 | +}; |
|---|
| 350 | + |
|---|
| 351 | +static const struct watchdog_info dw_wdt_pt_ident = { |
|---|
| 352 | + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | |
|---|
| 353 | + WDIOF_PRETIMEOUT | WDIOF_MAGICCLOSE, |
|---|
| 195 | 354 | .identity = "Synopsys DesignWare Watchdog", |
|---|
| 196 | 355 | }; |
|---|
| 197 | 356 | |
|---|
| .. | .. |
|---|
| 201 | 360 | .stop = dw_wdt_stop, |
|---|
| 202 | 361 | .ping = dw_wdt_ping, |
|---|
| 203 | 362 | .set_timeout = dw_wdt_set_timeout, |
|---|
| 363 | + .set_pretimeout = dw_wdt_set_pretimeout, |
|---|
| 204 | 364 | .get_timeleft = dw_wdt_get_timeleft, |
|---|
| 205 | 365 | .restart = dw_wdt_restart, |
|---|
| 206 | 366 | }; |
|---|
| 367 | + |
|---|
| 368 | +static irqreturn_t dw_wdt_irq(int irq, void *devid) |
|---|
| 369 | +{ |
|---|
| 370 | + struct dw_wdt *dw_wdt = devid; |
|---|
| 371 | + u32 val; |
|---|
| 372 | + |
|---|
| 373 | + /* |
|---|
| 374 | + * We don't clear the IRQ status. It's supposed to be done by the |
|---|
| 375 | + * following ping operations. |
|---|
| 376 | + */ |
|---|
| 377 | + val = readl(dw_wdt->regs + WDOG_INTERRUPT_STATUS_REG_OFFSET); |
|---|
| 378 | + if (!val) |
|---|
| 379 | + return IRQ_NONE; |
|---|
| 380 | + |
|---|
| 381 | + watchdog_notify_pretimeout(&dw_wdt->wdd); |
|---|
| 382 | + |
|---|
| 383 | + return IRQ_HANDLED; |
|---|
| 384 | +} |
|---|
| 207 | 385 | |
|---|
| 208 | 386 | #ifdef CONFIG_PM_SLEEP |
|---|
| 209 | 387 | static int dw_wdt_suspend(struct device *dev) |
|---|
| .. | .. |
|---|
| 244 | 422 | |
|---|
| 245 | 423 | static SIMPLE_DEV_PM_OPS(dw_wdt_pm_ops, dw_wdt_suspend, dw_wdt_resume); |
|---|
| 246 | 424 | |
|---|
| 425 | +/* |
|---|
| 426 | + * In case if DW WDT IP core is synthesized with fixed TOP feature disabled the |
|---|
| 427 | + * TOPs array can be arbitrary ordered with nearly any sixteen uint numbers |
|---|
| 428 | + * depending on the system engineer imagination. The next method handles the |
|---|
| 429 | + * passed TOPs array to pre-calculate the effective timeouts and to sort the |
|---|
| 430 | + * TOP items out in the ascending order with respect to the timeouts. |
|---|
| 431 | + */ |
|---|
| 432 | + |
|---|
| 433 | +static void dw_wdt_handle_tops(struct dw_wdt *dw_wdt, const u32 *tops) |
|---|
| 434 | +{ |
|---|
| 435 | + struct dw_wdt_timeout tout, *dst; |
|---|
| 436 | + int val, tidx; |
|---|
| 437 | + u64 msec; |
|---|
| 438 | + |
|---|
| 439 | + /* |
|---|
| 440 | + * We walk over the passed TOPs array and calculate corresponding |
|---|
| 441 | + * timeouts in seconds and milliseconds. The milliseconds granularity |
|---|
| 442 | + * is needed to distinguish the TOPs with very close timeouts and to |
|---|
| 443 | + * set the watchdog max heartbeat setting further. |
|---|
| 444 | + */ |
|---|
| 445 | + for (val = 0; val < DW_WDT_NUM_TOPS; ++val) { |
|---|
| 446 | + tout.top_val = val; |
|---|
| 447 | + tout.sec = tops[val] / dw_wdt->rate; |
|---|
| 448 | + msec = (u64)tops[val] * MSEC_PER_SEC; |
|---|
| 449 | + do_div(msec, dw_wdt->rate); |
|---|
| 450 | + tout.msec = msec - ((u64)tout.sec * MSEC_PER_SEC); |
|---|
| 451 | + |
|---|
| 452 | + /* |
|---|
| 453 | + * Find a suitable place for the current TOP in the timeouts |
|---|
| 454 | + * array so that the list is remained in the ascending order. |
|---|
| 455 | + */ |
|---|
| 456 | + for (tidx = 0; tidx < val; ++tidx) { |
|---|
| 457 | + dst = &dw_wdt->timeouts[tidx]; |
|---|
| 458 | + if (tout.sec > dst->sec || (tout.sec == dst->sec && |
|---|
| 459 | + tout.msec >= dst->msec)) |
|---|
| 460 | + continue; |
|---|
| 461 | + else |
|---|
| 462 | + swap(*dst, tout); |
|---|
| 463 | + } |
|---|
| 464 | + |
|---|
| 465 | + dw_wdt->timeouts[val] = tout; |
|---|
| 466 | + } |
|---|
| 467 | +} |
|---|
| 468 | + |
|---|
| 469 | +static int dw_wdt_init_timeouts(struct dw_wdt *dw_wdt, struct device *dev) |
|---|
| 470 | +{ |
|---|
| 471 | + u32 data, of_tops[DW_WDT_NUM_TOPS]; |
|---|
| 472 | + const u32 *tops; |
|---|
| 473 | + int ret; |
|---|
| 474 | + |
|---|
| 475 | + /* |
|---|
| 476 | + * Retrieve custom or fixed counter values depending on the |
|---|
| 477 | + * WDT_USE_FIX_TOP flag found in the component specific parameters |
|---|
| 478 | + * #1 register. |
|---|
| 479 | + */ |
|---|
| 480 | + data = readl(dw_wdt->regs + WDOG_COMP_PARAMS_1_REG_OFFSET); |
|---|
| 481 | + if (data & WDOG_COMP_PARAMS_1_USE_FIX_TOP) { |
|---|
| 482 | + tops = dw_wdt_fix_tops; |
|---|
| 483 | + } else { |
|---|
| 484 | + ret = of_property_read_variable_u32_array(dev_of_node(dev), |
|---|
| 485 | + "snps,watchdog-tops", of_tops, DW_WDT_NUM_TOPS, |
|---|
| 486 | + DW_WDT_NUM_TOPS); |
|---|
| 487 | + if (ret < 0) { |
|---|
| 488 | + dev_warn(dev, "No valid TOPs array specified\n"); |
|---|
| 489 | + tops = dw_wdt_fix_tops; |
|---|
| 490 | + } else { |
|---|
| 491 | + tops = of_tops; |
|---|
| 492 | + } |
|---|
| 493 | + } |
|---|
| 494 | + |
|---|
| 495 | + /* Convert the specified TOPs into an array of watchdog timeouts. */ |
|---|
| 496 | + dw_wdt_handle_tops(dw_wdt, tops); |
|---|
| 497 | + if (!dw_wdt->timeouts[DW_WDT_NUM_TOPS - 1].sec) { |
|---|
| 498 | + dev_err(dev, "No any valid TOP detected\n"); |
|---|
| 499 | + return -EINVAL; |
|---|
| 500 | + } |
|---|
| 501 | + |
|---|
| 502 | + return 0; |
|---|
| 503 | +} |
|---|
| 504 | + |
|---|
| 505 | +#ifdef CONFIG_DEBUG_FS |
|---|
| 506 | + |
|---|
| 507 | +#define DW_WDT_DBGFS_REG(_name, _off) \ |
|---|
| 508 | +{ \ |
|---|
| 509 | + .name = _name, \ |
|---|
| 510 | + .offset = _off \ |
|---|
| 511 | +} |
|---|
| 512 | + |
|---|
| 513 | +static const struct debugfs_reg32 dw_wdt_dbgfs_regs[] = { |
|---|
| 514 | + DW_WDT_DBGFS_REG("cr", WDOG_CONTROL_REG_OFFSET), |
|---|
| 515 | + DW_WDT_DBGFS_REG("torr", WDOG_TIMEOUT_RANGE_REG_OFFSET), |
|---|
| 516 | + DW_WDT_DBGFS_REG("ccvr", WDOG_CURRENT_COUNT_REG_OFFSET), |
|---|
| 517 | + DW_WDT_DBGFS_REG("crr", WDOG_COUNTER_RESTART_REG_OFFSET), |
|---|
| 518 | + DW_WDT_DBGFS_REG("stat", WDOG_INTERRUPT_STATUS_REG_OFFSET), |
|---|
| 519 | + DW_WDT_DBGFS_REG("param5", WDOG_COMP_PARAMS_5_REG_OFFSET), |
|---|
| 520 | + DW_WDT_DBGFS_REG("param4", WDOG_COMP_PARAMS_4_REG_OFFSET), |
|---|
| 521 | + DW_WDT_DBGFS_REG("param3", WDOG_COMP_PARAMS_3_REG_OFFSET), |
|---|
| 522 | + DW_WDT_DBGFS_REG("param2", WDOG_COMP_PARAMS_2_REG_OFFSET), |
|---|
| 523 | + DW_WDT_DBGFS_REG("param1", WDOG_COMP_PARAMS_1_REG_OFFSET), |
|---|
| 524 | + DW_WDT_DBGFS_REG("version", WDOG_COMP_VERSION_REG_OFFSET), |
|---|
| 525 | + DW_WDT_DBGFS_REG("type", WDOG_COMP_TYPE_REG_OFFSET) |
|---|
| 526 | +}; |
|---|
| 527 | + |
|---|
| 528 | +static void dw_wdt_dbgfs_init(struct dw_wdt *dw_wdt) |
|---|
| 529 | +{ |
|---|
| 530 | + struct device *dev = dw_wdt->wdd.parent; |
|---|
| 531 | + struct debugfs_regset32 *regset; |
|---|
| 532 | + |
|---|
| 533 | + regset = devm_kzalloc(dev, sizeof(*regset), GFP_KERNEL); |
|---|
| 534 | + if (!regset) |
|---|
| 535 | + return; |
|---|
| 536 | + |
|---|
| 537 | + regset->regs = dw_wdt_dbgfs_regs; |
|---|
| 538 | + regset->nregs = ARRAY_SIZE(dw_wdt_dbgfs_regs); |
|---|
| 539 | + regset->base = dw_wdt->regs; |
|---|
| 540 | + |
|---|
| 541 | + dw_wdt->dbgfs_dir = debugfs_create_dir(dev_name(dev), NULL); |
|---|
| 542 | + |
|---|
| 543 | + debugfs_create_regset32("registers", 0444, dw_wdt->dbgfs_dir, regset); |
|---|
| 544 | +} |
|---|
| 545 | + |
|---|
| 546 | +static void dw_wdt_dbgfs_clear(struct dw_wdt *dw_wdt) |
|---|
| 547 | +{ |
|---|
| 548 | + debugfs_remove_recursive(dw_wdt->dbgfs_dir); |
|---|
| 549 | +} |
|---|
| 550 | + |
|---|
| 551 | +#else /* !CONFIG_DEBUG_FS */ |
|---|
| 552 | + |
|---|
| 553 | +static void dw_wdt_dbgfs_init(struct dw_wdt *dw_wdt) {} |
|---|
| 554 | +static void dw_wdt_dbgfs_clear(struct dw_wdt *dw_wdt) {} |
|---|
| 555 | + |
|---|
| 556 | +#endif /* !CONFIG_DEBUG_FS */ |
|---|
| 557 | + |
|---|
| 247 | 558 | static int dw_wdt_drv_probe(struct platform_device *pdev) |
|---|
| 248 | 559 | { |
|---|
| 249 | 560 | struct device *dev = &pdev->dev; |
|---|
| 250 | 561 | struct watchdog_device *wdd; |
|---|
| 251 | 562 | struct dw_wdt *dw_wdt; |
|---|
| 252 | | - struct resource *mem; |
|---|
| 253 | 563 | int ret; |
|---|
| 254 | 564 | |
|---|
| 255 | 565 | dw_wdt = devm_kzalloc(dev, sizeof(*dw_wdt), GFP_KERNEL); |
|---|
| 256 | 566 | if (!dw_wdt) |
|---|
| 257 | 567 | return -ENOMEM; |
|---|
| 258 | 568 | |
|---|
| 259 | | - mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
|---|
| 260 | | - dw_wdt->regs = devm_ioremap_resource(dev, mem); |
|---|
| 569 | + dw_wdt->regs = devm_platform_ioremap_resource(pdev, 0); |
|---|
| 261 | 570 | if (IS_ERR(dw_wdt->regs)) |
|---|
| 262 | 571 | return PTR_ERR(dw_wdt->regs); |
|---|
| 263 | 572 | |
|---|
| .. | .. |
|---|
| 307 | 616 | goto out_disable_pclk; |
|---|
| 308 | 617 | } |
|---|
| 309 | 618 | |
|---|
| 619 | + /* Enable normal reset without pre-timeout by default. */ |
|---|
| 620 | + dw_wdt_update_mode(dw_wdt, DW_WDT_RMOD_RESET); |
|---|
| 621 | + |
|---|
| 622 | + /* |
|---|
| 623 | + * Pre-timeout IRQ is optional, since some hardware may lack support |
|---|
| 624 | + * of it. Note we must request rising-edge IRQ, since the lane is left |
|---|
| 625 | + * pending either until the next watchdog kick event or up to the |
|---|
| 626 | + * system reset. |
|---|
| 627 | + */ |
|---|
| 628 | + ret = platform_get_irq_optional(pdev, 0); |
|---|
| 629 | + if (ret > 0) { |
|---|
| 630 | + ret = devm_request_irq(dev, ret, dw_wdt_irq, |
|---|
| 631 | + IRQF_SHARED | IRQF_TRIGGER_RISING, |
|---|
| 632 | + pdev->name, dw_wdt); |
|---|
| 633 | + if (ret) |
|---|
| 634 | + goto out_disable_pclk; |
|---|
| 635 | + |
|---|
| 636 | + dw_wdt->wdd.info = &dw_wdt_pt_ident; |
|---|
| 637 | + } else { |
|---|
| 638 | + if (ret == -EPROBE_DEFER) |
|---|
| 639 | + goto out_disable_pclk; |
|---|
| 640 | + |
|---|
| 641 | + dw_wdt->wdd.info = &dw_wdt_ident; |
|---|
| 642 | + } |
|---|
| 643 | + |
|---|
| 310 | 644 | reset_control_deassert(dw_wdt->rst); |
|---|
| 311 | 645 | |
|---|
| 646 | + ret = dw_wdt_init_timeouts(dw_wdt, dev); |
|---|
| 647 | + if (ret) |
|---|
| 648 | + goto out_assert_rst; |
|---|
| 649 | + |
|---|
| 312 | 650 | wdd = &dw_wdt->wdd; |
|---|
| 313 | | - wdd->info = &dw_wdt_ident; |
|---|
| 314 | 651 | wdd->ops = &dw_wdt_ops; |
|---|
| 315 | | - wdd->min_timeout = 1; |
|---|
| 316 | | - wdd->max_hw_heartbeat_ms = |
|---|
| 317 | | - dw_wdt_top_in_seconds(dw_wdt, DW_WDT_MAX_TOP) * 1000; |
|---|
| 652 | + wdd->min_timeout = dw_wdt_get_min_timeout(dw_wdt); |
|---|
| 653 | + wdd->max_hw_heartbeat_ms = dw_wdt_get_max_timeout_ms(dw_wdt); |
|---|
| 318 | 654 | wdd->parent = dev; |
|---|
| 319 | 655 | |
|---|
| 320 | 656 | watchdog_set_drvdata(wdd, dw_wdt); |
|---|
| .. | .. |
|---|
| 327 | 663 | * devicetree. |
|---|
| 328 | 664 | */ |
|---|
| 329 | 665 | if (dw_wdt_is_enabled(dw_wdt)) { |
|---|
| 330 | | - wdd->timeout = dw_wdt_get_top(dw_wdt); |
|---|
| 666 | + wdd->timeout = dw_wdt_get_timeout(dw_wdt); |
|---|
| 331 | 667 | set_bit(WDOG_HW_RUNNING, &wdd->status); |
|---|
| 332 | 668 | } else { |
|---|
| 333 | 669 | wdd->timeout = DW_WDT_DEFAULT_SECONDS; |
|---|
| .. | .. |
|---|
| 340 | 676 | |
|---|
| 341 | 677 | ret = watchdog_register_device(wdd); |
|---|
| 342 | 678 | if (ret) |
|---|
| 343 | | - goto out_disable_pclk; |
|---|
| 679 | + goto out_assert_rst; |
|---|
| 680 | + |
|---|
| 681 | + dw_wdt_dbgfs_init(dw_wdt); |
|---|
| 344 | 682 | |
|---|
| 345 | 683 | return 0; |
|---|
| 684 | + |
|---|
| 685 | +out_assert_rst: |
|---|
| 686 | + reset_control_assert(dw_wdt->rst); |
|---|
| 346 | 687 | |
|---|
| 347 | 688 | out_disable_pclk: |
|---|
| 348 | 689 | clk_disable_unprepare(dw_wdt->pclk); |
|---|
| .. | .. |
|---|
| 356 | 697 | { |
|---|
| 357 | 698 | struct dw_wdt *dw_wdt = platform_get_drvdata(pdev); |
|---|
| 358 | 699 | |
|---|
| 700 | + dw_wdt_dbgfs_clear(dw_wdt); |
|---|
| 701 | + |
|---|
| 359 | 702 | watchdog_unregister_device(&dw_wdt->wdd); |
|---|
| 360 | 703 | reset_control_assert(dw_wdt->rst); |
|---|
| 361 | 704 | clk_disable_unprepare(dw_wdt->pclk); |
|---|