Commit bb133450ee95746a9387f12de8bd738e79c21433
Committed by
Wim Van Sebroeck
1 parent
2ffbb8377c
Exists in
master
and in
7 other branches
[WATCHDOG] at32ap700x_wdt: add support for boot status and add fix for silicon errata
This patch enables the watchdog to read out the reset cause after a boot and provide this to the user. The driver will now also return -EIO if probed when booting from a watchdog reset. This is due to a silicon errata in the AT32AP700x devices. Detailed description and work-arounds can be found in the errata section of the datasheet avilable from http://www.atmel.com/dyn/products/datasheets.asp?family_id=682 Signed-off-by: Hans-Christian Egtvedt <hcegtvedt@atmel.com> Signed-off-by: Wim Van Sebroeck <wim@iguana.be> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Showing 1 changed file with 65 additions and 4 deletions Side-by-side Diff
drivers/watchdog/at32ap700x_wdt.c
... | ... | @@ -6,6 +6,19 @@ |
6 | 6 | * This program is free software; you can redistribute it and/or modify |
7 | 7 | * it under the terms of the GNU General Public License version 2 as |
8 | 8 | * published by the Free Software Foundation. |
9 | + * | |
10 | + * | |
11 | + * Errata: WDT Clear is blocked after WDT Reset | |
12 | + * | |
13 | + * A watchdog timer event will, after reset, block writes to the WDT_CLEAR | |
14 | + * register, preventing the program to clear the next Watchdog Timer Reset. | |
15 | + * | |
16 | + * If you still want to use the WDT after a WDT reset a small code can be | |
17 | + * insterted at the startup checking the AVR32_PM.rcause register for WDT reset | |
18 | + * and use a GPIO pin to reset the system. This method requires that one of the | |
19 | + * GPIO pins are available and connected externally to the RESET_N pin. After | |
20 | + * the GPIO pin has pulled down the reset line the GPIO will be reset and leave | |
21 | + * the pin tristated with pullup. | |
9 | 22 | */ |
10 | 23 | |
11 | 24 | #include <linux/init.h> |
... | ... | @@ -44,6 +57,13 @@ |
44 | 57 | |
45 | 58 | #define WDT_CLR 0x04 |
46 | 59 | |
60 | +#define WDT_RCAUSE 0x10 | |
61 | +#define WDT_RCAUSE_POR 0 | |
62 | +#define WDT_RCAUSE_EXT 2 | |
63 | +#define WDT_RCAUSE_WDT 3 | |
64 | +#define WDT_RCAUSE_JTAG 4 | |
65 | +#define WDT_RCAUSE_SERP 5 | |
66 | + | |
47 | 67 | #define WDT_BIT(name) (1 << WDT_##name) |
48 | 68 | #define WDT_BF(name, value) ((value) << WDT_##name) |
49 | 69 | |
... | ... | @@ -56,6 +76,7 @@ |
56 | 76 | void __iomem *regs; |
57 | 77 | spinlock_t io_lock; |
58 | 78 | int timeout; |
79 | + int boot_status; | |
59 | 80 | unsigned long users; |
60 | 81 | struct miscdevice miscdev; |
61 | 82 | }; |
... | ... | @@ -126,7 +147,7 @@ |
126 | 147 | at32_wdt_stop(); |
127 | 148 | } else { |
128 | 149 | dev_dbg(wdt->miscdev.parent, |
129 | - "Unexpected close, not stopping watchdog!\n"); | |
150 | + "unexpected close, not stopping watchdog!\n"); | |
130 | 151 | at32_wdt_pat(); |
131 | 152 | } |
132 | 153 | clear_bit(1, &wdt->users); |
... | ... | @@ -154,6 +175,33 @@ |
154 | 175 | return 0; |
155 | 176 | } |
156 | 177 | |
178 | +/* | |
179 | + * Get the watchdog status. | |
180 | + */ | |
181 | +static int at32_wdt_get_status(void) | |
182 | +{ | |
183 | + int rcause; | |
184 | + int status = 0; | |
185 | + | |
186 | + rcause = wdt_readl(wdt, RCAUSE); | |
187 | + | |
188 | + switch (rcause) { | |
189 | + case WDT_BIT(RCAUSE_EXT): | |
190 | + status = WDIOF_EXTERN1; | |
191 | + break; | |
192 | + case WDT_BIT(RCAUSE_WDT): | |
193 | + status = WDIOF_CARDRESET; | |
194 | + break; | |
195 | + case WDT_BIT(RCAUSE_POR): /* fall through */ | |
196 | + case WDT_BIT(RCAUSE_JTAG): /* fall through */ | |
197 | + case WDT_BIT(RCAUSE_SERP): /* fall through */ | |
198 | + default: | |
199 | + break; | |
200 | + } | |
201 | + | |
202 | + return status; | |
203 | +} | |
204 | + | |
157 | 205 | static struct watchdog_info at32_wdt_info = { |
158 | 206 | .identity = "at32ap700x watchdog", |
159 | 207 | .options = WDIOF_SETTIMEOUT | |
160 | 208 | |
... | ... | @@ -194,10 +242,12 @@ |
194 | 242 | case WDIOC_GETTIMEOUT: |
195 | 243 | ret = put_user(wdt->timeout, p); |
196 | 244 | break; |
197 | - case WDIOC_GETSTATUS: /* fall through */ | |
198 | - case WDIOC_GETBOOTSTATUS: | |
245 | + case WDIOC_GETSTATUS: | |
199 | 246 | ret = put_user(0, p); |
200 | 247 | break; |
248 | + case WDIOC_GETBOOTSTATUS: | |
249 | + ret = put_user(wdt->boot_status, p); | |
250 | + break; | |
201 | 251 | case WDIOC_SETOPTIONS: |
202 | 252 | ret = get_user(time, p); |
203 | 253 | if (ret) |
204 | 254 | |
... | ... | @@ -282,8 +332,19 @@ |
282 | 332 | dev_dbg(&pdev->dev, "could not map I/O memory\n"); |
283 | 333 | goto err_free; |
284 | 334 | } |
335 | + | |
285 | 336 | spin_lock_init(&wdt->io_lock); |
286 | - wdt->users = 0; | |
337 | + wdt->boot_status = at32_wdt_get_status(); | |
338 | + | |
339 | + /* Work-around for watchdog silicon errata. */ | |
340 | + if (wdt->boot_status & WDIOF_CARDRESET) { | |
341 | + dev_info(&pdev->dev, "CPU must be reset with external " | |
342 | + "reset or POR due to silicon errata.\n"); | |
343 | + ret = -EIO; | |
344 | + goto err_iounmap; | |
345 | + } else { | |
346 | + wdt->users = 0; | |
347 | + } | |
287 | 348 | wdt->miscdev.minor = WATCHDOG_MINOR; |
288 | 349 | wdt->miscdev.name = "watchdog"; |
289 | 350 | wdt->miscdev.fops = &at32_wdt_fops; |