|
75 | 75 | #define SBSA_GWDT_VERSION_MASK 0xF
|
76 | 76 | #define SBSA_GWDT_VERSION_SHIFT 16
|
77 | 77 |
|
| 78 | +#define SBSA_GWDT_IMPL_MASK 0x7FF |
| 79 | +#define SBSA_GWDT_IMPL_SHIFT 0 |
| 80 | +#define SBSA_GWDT_IMPL_MEDIATEK 0x426 |
| 81 | + |
78 | 82 | /**
|
79 | 83 | * struct sbsa_gwdt - Internal representation of the SBSA GWDT
|
80 | 84 | * @wdd: kernel watchdog_device structure
|
81 | 85 | * @clk: store the System Counter clock frequency, in Hz.
|
82 | 86 | * @version: store the architecture version
|
| 87 | + * @need_ws0_race_workaround: |
| 88 | + * indicate whether to adjust wdd->timeout to avoid a race with WS0 |
83 | 89 | * @refresh_base: Virtual address of the watchdog refresh frame
|
84 | 90 | * @control_base: Virtual address of the watchdog control frame
|
85 | 91 | */
|
86 | 92 | struct sbsa_gwdt {
|
87 | 93 | struct watchdog_device wdd;
|
88 | 94 | u32 clk;
|
89 | 95 | int version;
|
| 96 | + bool need_ws0_race_workaround; |
90 | 97 | void __iomem *refresh_base;
|
91 | 98 | void __iomem *control_base;
|
92 | 99 | };
|
@@ -161,6 +168,31 @@ static int sbsa_gwdt_set_timeout(struct watchdog_device *wdd,
|
161 | 168 | */
|
162 | 169 | sbsa_gwdt_reg_write(((u64)gwdt->clk / 2) * timeout, gwdt);
|
163 | 170 |
|
| 171 | + /* |
| 172 | + * Some watchdog hardware has a race condition where it will ignore |
| 173 | + * sbsa_gwdt_keepalive() if it is called at the exact moment that a |
| 174 | + * timeout occurs and WS0 is being asserted. Unfortunately, the default |
| 175 | + * behavior of the watchdog core is very likely to trigger this race |
| 176 | + * when action=0 because it programs WOR to be half of the desired |
| 177 | + * timeout, and watchdog_next_keepalive() chooses the exact same time to |
| 178 | + * send keepalive pings. |
| 179 | + * |
| 180 | + * This triggers a race where sbsa_gwdt_keepalive() can be called right |
| 181 | + * as WS0 is being asserted, and affected hardware will ignore that |
| 182 | + * write and continue to assert WS0. After another (timeout / 2) |
| 183 | + * seconds, the same race happens again. If the driver wins then the |
| 184 | + * explicit refresh will reset WS0 to false but if the hardware wins, |
| 185 | + * then WS1 is asserted and the system resets. |
| 186 | + * |
| 187 | + * Avoid the problem by scheduling keepalive heartbeats one second later |
| 188 | + * than the WOR timeout. |
| 189 | + * |
| 190 | + * This workaround might not be needed in a future revision of the |
| 191 | + * hardware. |
| 192 | + */ |
| 193 | + if (gwdt->need_ws0_race_workaround) |
| 194 | + wdd->min_hw_heartbeat_ms = timeout * 500 + 1000; |
| 195 | + |
164 | 196 | return 0;
|
165 | 197 | }
|
166 | 198 |
|
@@ -202,12 +234,15 @@ static int sbsa_gwdt_keepalive(struct watchdog_device *wdd)
|
202 | 234 | static void sbsa_gwdt_get_version(struct watchdog_device *wdd)
|
203 | 235 | {
|
204 | 236 | struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd);
|
205 |
| - int ver; |
| 237 | + int iidr, ver, impl; |
206 | 238 |
|
207 |
| - ver = readl(gwdt->control_base + SBSA_GWDT_W_IIDR); |
208 |
| - ver = (ver >> SBSA_GWDT_VERSION_SHIFT) & SBSA_GWDT_VERSION_MASK; |
| 239 | + iidr = readl(gwdt->control_base + SBSA_GWDT_W_IIDR); |
| 240 | + ver = (iidr >> SBSA_GWDT_VERSION_SHIFT) & SBSA_GWDT_VERSION_MASK; |
| 241 | + impl = (iidr >> SBSA_GWDT_IMPL_SHIFT) & SBSA_GWDT_IMPL_MASK; |
209 | 242 |
|
210 | 243 | gwdt->version = ver;
|
| 244 | + gwdt->need_ws0_race_workaround = |
| 245 | + !action && (impl == SBSA_GWDT_IMPL_MEDIATEK); |
211 | 246 | }
|
212 | 247 |
|
213 | 248 | static int sbsa_gwdt_start(struct watchdog_device *wdd)
|
@@ -299,6 +334,15 @@ static int sbsa_gwdt_probe(struct platform_device *pdev)
|
299 | 334 | else
|
300 | 335 | wdd->max_hw_heartbeat_ms = GENMASK_ULL(47, 0) / gwdt->clk * 1000;
|
301 | 336 |
|
| 337 | + if (gwdt->need_ws0_race_workaround) { |
| 338 | + /* |
| 339 | + * A timeout of 3 seconds means that WOR will be set to 1.5 |
| 340 | + * seconds and the heartbeat will be scheduled every 2.5 |
| 341 | + * seconds. |
| 342 | + */ |
| 343 | + wdd->min_timeout = 3; |
| 344 | + } |
| 345 | + |
302 | 346 | status = readl(cf_base + SBSA_GWDT_WCS);
|
303 | 347 | if (status & SBSA_GWDT_WCS_WS1) {
|
304 | 348 | dev_warn(dev, "System reset by WDT.\n");
|
|
0 commit comments