.. | .. |
---|
| 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); |
---|