diff options
-rw-r--r-- | drivers/watchdog/at91sam9_wdt.c | 309 |
1 files changed, 223 insertions, 86 deletions
diff --git a/drivers/watchdog/at91sam9_wdt.c b/drivers/watchdog/at91sam9_wdt.c index be37dde4f864..9bd089ebb70f 100644 --- a/drivers/watchdog/at91sam9_wdt.c +++ b/drivers/watchdog/at91sam9_wdt.c | |||
@@ -19,11 +19,13 @@ | |||
19 | 19 | ||
20 | #include <linux/errno.h> | 20 | #include <linux/errno.h> |
21 | #include <linux/init.h> | 21 | #include <linux/init.h> |
22 | #include <linux/interrupt.h> | ||
22 | #include <linux/io.h> | 23 | #include <linux/io.h> |
23 | #include <linux/kernel.h> | 24 | #include <linux/kernel.h> |
24 | #include <linux/module.h> | 25 | #include <linux/module.h> |
25 | #include <linux/moduleparam.h> | 26 | #include <linux/moduleparam.h> |
26 | #include <linux/platform_device.h> | 27 | #include <linux/platform_device.h> |
28 | #include <linux/reboot.h> | ||
27 | #include <linux/types.h> | 29 | #include <linux/types.h> |
28 | #include <linux/watchdog.h> | 30 | #include <linux/watchdog.h> |
29 | #include <linux/jiffies.h> | 31 | #include <linux/jiffies.h> |
@@ -31,22 +33,33 @@ | |||
31 | #include <linux/bitops.h> | 33 | #include <linux/bitops.h> |
32 | #include <linux/uaccess.h> | 34 | #include <linux/uaccess.h> |
33 | #include <linux/of.h> | 35 | #include <linux/of.h> |
36 | #include <linux/of_irq.h> | ||
34 | 37 | ||
35 | #include "at91sam9_wdt.h" | 38 | #include "at91sam9_wdt.h" |
36 | 39 | ||
37 | #define DRV_NAME "AT91SAM9 Watchdog" | 40 | #define DRV_NAME "AT91SAM9 Watchdog" |
38 | 41 | ||
39 | #define wdt_read(field) \ | 42 | #define wdt_read(wdt, field) \ |
40 | __raw_readl(at91wdt_private.base + field) | 43 | __raw_readl((wdt)->base + (field)) |
41 | #define wdt_write(field, val) \ | 44 | #define wdt_write(wtd, field, val) \ |
42 | __raw_writel((val), at91wdt_private.base + field) | 45 | __raw_writel((val), (wdt)->base + (field)) |
43 | 46 | ||
44 | /* AT91SAM9 watchdog runs a 12bit counter @ 256Hz, | 47 | /* AT91SAM9 watchdog runs a 12bit counter @ 256Hz, |
45 | * use this to convert a watchdog | 48 | * use this to convert a watchdog |
46 | * value from/to milliseconds. | 49 | * value from/to milliseconds. |
47 | */ | 50 | */ |
48 | #define ms_to_ticks(t) (((t << 8) / 1000) - 1) | 51 | #define ticks_to_hz_rounddown(t) ((((t) + 1) * HZ) >> 8) |
49 | #define ticks_to_ms(t) (((t + 1) * 1000) >> 8) | 52 | #define ticks_to_hz_roundup(t) (((((t) + 1) * HZ) + 255) >> 8) |
53 | #define ticks_to_secs(t) (((t) + 1) >> 8) | ||
54 | #define secs_to_ticks(s) (((s) << 8) - 1) | ||
55 | |||
56 | #define WDT_MR_RESET 0x3FFF2FFF | ||
57 | |||
58 | /* Watchdog max counter value in ticks */ | ||
59 | #define WDT_COUNTER_MAX_TICKS 0xFFF | ||
60 | |||
61 | /* Watchdog max delta/value in secs */ | ||
62 | #define WDT_COUNTER_MAX_SECS ticks_to_secs(WDT_COUNTER_MAX_TICKS) | ||
50 | 63 | ||
51 | /* Hardware timeout in seconds */ | 64 | /* Hardware timeout in seconds */ |
52 | #define WDT_HW_TIMEOUT 2 | 65 | #define WDT_HW_TIMEOUT 2 |
@@ -66,23 +79,40 @@ module_param(nowayout, bool, 0); | |||
66 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " | 79 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " |
67 | "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | 80 | "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); |
68 | 81 | ||
69 | static struct watchdog_device at91_wdt_dev; | 82 | #define to_wdt(wdd) container_of(wdd, struct at91wdt, wdd) |
70 | static void at91_ping(unsigned long data); | 83 | struct at91wdt { |
71 | 84 | struct watchdog_device wdd; | |
72 | static struct { | ||
73 | void __iomem *base; | 85 | void __iomem *base; |
74 | unsigned long next_heartbeat; /* the next_heartbeat for the timer */ | 86 | unsigned long next_heartbeat; /* the next_heartbeat for the timer */ |
75 | struct timer_list timer; /* The timer that pings the watchdog */ | 87 | struct timer_list timer; /* The timer that pings the watchdog */ |
76 | } at91wdt_private; | 88 | u32 mr; |
89 | u32 mr_mask; | ||
90 | unsigned long heartbeat; /* WDT heartbeat in jiffies */ | ||
91 | bool nowayout; | ||
92 | unsigned int irq; | ||
93 | }; | ||
77 | 94 | ||
78 | /* ......................................................................... */ | 95 | /* ......................................................................... */ |
79 | 96 | ||
97 | static irqreturn_t wdt_interrupt(int irq, void *dev_id) | ||
98 | { | ||
99 | struct at91wdt *wdt = (struct at91wdt *)dev_id; | ||
100 | |||
101 | if (wdt_read(wdt, AT91_WDT_SR)) { | ||
102 | pr_crit("at91sam9 WDT software reset\n"); | ||
103 | emergency_restart(); | ||
104 | pr_crit("Reboot didn't ?????\n"); | ||
105 | } | ||
106 | |||
107 | return IRQ_HANDLED; | ||
108 | } | ||
109 | |||
80 | /* | 110 | /* |
81 | * Reload the watchdog timer. (ie, pat the watchdog) | 111 | * Reload the watchdog timer. (ie, pat the watchdog) |
82 | */ | 112 | */ |
83 | static inline void at91_wdt_reset(void) | 113 | static inline void at91_wdt_reset(struct at91wdt *wdt) |
84 | { | 114 | { |
85 | wdt_write(AT91_WDT_CR, AT91_WDT_KEY | AT91_WDT_WDRSTT); | 115 | wdt_write(wdt, AT91_WDT_CR, AT91_WDT_KEY | AT91_WDT_WDRSTT); |
86 | } | 116 | } |
87 | 117 | ||
88 | /* | 118 | /* |
@@ -90,26 +120,21 @@ static inline void at91_wdt_reset(void) | |||
90 | */ | 120 | */ |
91 | static void at91_ping(unsigned long data) | 121 | static void at91_ping(unsigned long data) |
92 | { | 122 | { |
93 | if (time_before(jiffies, at91wdt_private.next_heartbeat) || | 123 | struct at91wdt *wdt = (struct at91wdt *)data; |
94 | (!watchdog_active(&at91_wdt_dev))) { | 124 | if (time_before(jiffies, wdt->next_heartbeat) || |
95 | at91_wdt_reset(); | 125 | !watchdog_active(&wdt->wdd)) { |
96 | mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); | 126 | at91_wdt_reset(wdt); |
97 | } else | 127 | mod_timer(&wdt->timer, jiffies + wdt->heartbeat); |
128 | } else { | ||
98 | pr_crit("I will reset your machine !\n"); | 129 | pr_crit("I will reset your machine !\n"); |
99 | } | 130 | } |
100 | |||
101 | static int at91_wdt_ping(struct watchdog_device *wdd) | ||
102 | { | ||
103 | /* calculate when the next userspace timeout will be */ | ||
104 | at91wdt_private.next_heartbeat = jiffies + wdd->timeout * HZ; | ||
105 | return 0; | ||
106 | } | 131 | } |
107 | 132 | ||
108 | static int at91_wdt_start(struct watchdog_device *wdd) | 133 | static int at91_wdt_start(struct watchdog_device *wdd) |
109 | { | 134 | { |
110 | /* calculate the next userspace timeout and modify the timer */ | 135 | struct at91wdt *wdt = to_wdt(wdd); |
111 | at91_wdt_ping(wdd); | 136 | /* calculate when the next userspace timeout will be */ |
112 | mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); | 137 | wdt->next_heartbeat = jiffies + wdd->timeout * HZ; |
113 | return 0; | 138 | return 0; |
114 | } | 139 | } |
115 | 140 | ||
@@ -122,39 +147,89 @@ static int at91_wdt_stop(struct watchdog_device *wdd) | |||
122 | static int at91_wdt_set_timeout(struct watchdog_device *wdd, unsigned int new_timeout) | 147 | static int at91_wdt_set_timeout(struct watchdog_device *wdd, unsigned int new_timeout) |
123 | { | 148 | { |
124 | wdd->timeout = new_timeout; | 149 | wdd->timeout = new_timeout; |
125 | return 0; | 150 | return at91_wdt_start(wdd); |
126 | } | 151 | } |
127 | 152 | ||
128 | /* | 153 | static int at91_wdt_init(struct platform_device *pdev, struct at91wdt *wdt) |
129 | * Set the watchdog time interval in 1/256Hz (write-once) | ||
130 | * Counter is 12 bit. | ||
131 | */ | ||
132 | static int at91_wdt_settimeout(unsigned int timeout) | ||
133 | { | 154 | { |
134 | unsigned int reg; | 155 | u32 tmp; |
135 | unsigned int mr; | 156 | u32 delta; |
136 | 157 | u32 value; | |
137 | /* Check if disabled */ | 158 | int err; |
138 | mr = wdt_read(AT91_WDT_MR); | 159 | u32 mask = wdt->mr_mask; |
139 | if (mr & AT91_WDT_WDDIS) { | 160 | unsigned long min_heartbeat = 1; |
140 | pr_err("sorry, watchdog is disabled\n"); | 161 | struct device *dev = &pdev->dev; |
141 | return -EIO; | 162 | |
163 | tmp = wdt_read(wdt, AT91_WDT_MR); | ||
164 | if ((tmp & mask) != (wdt->mr & mask)) { | ||
165 | if (tmp == WDT_MR_RESET) { | ||
166 | wdt_write(wdt, AT91_WDT_MR, wdt->mr); | ||
167 | tmp = wdt_read(wdt, AT91_WDT_MR); | ||
168 | } | ||
169 | } | ||
170 | |||
171 | if (tmp & AT91_WDT_WDDIS) { | ||
172 | if (wdt->mr & AT91_WDT_WDDIS) | ||
173 | return 0; | ||
174 | dev_err(dev, "watchdog is disabled\n"); | ||
175 | return -EINVAL; | ||
176 | } | ||
177 | |||
178 | value = tmp & AT91_WDT_WDV; | ||
179 | delta = (tmp & AT91_WDT_WDD) >> 16; | ||
180 | |||
181 | if (delta < value) | ||
182 | min_heartbeat = ticks_to_hz_roundup(value - delta); | ||
183 | |||
184 | wdt->heartbeat = ticks_to_hz_rounddown(value); | ||
185 | if (!wdt->heartbeat) { | ||
186 | dev_err(dev, | ||
187 | "heartbeat is too small for the system to handle it correctly\n"); | ||
188 | return -EINVAL; | ||
189 | } | ||
190 | |||
191 | if (wdt->heartbeat < min_heartbeat + 4) { | ||
192 | wdt->heartbeat = min_heartbeat; | ||
193 | dev_warn(dev, | ||
194 | "min heartbeat and max heartbeat might be too close for the system to handle it correctly\n"); | ||
195 | if (wdt->heartbeat < 4) | ||
196 | dev_warn(dev, | ||
197 | "heartbeat might be too small for the system to handle it correctly\n"); | ||
198 | } else { | ||
199 | wdt->heartbeat -= 4; | ||
142 | } | 200 | } |
143 | 201 | ||
144 | /* | 202 | if ((tmp & AT91_WDT_WDFIEN) && wdt->irq) { |
145 | * All counting occurs at SLOW_CLOCK / 128 = 256 Hz | 203 | err = request_irq(wdt->irq, wdt_interrupt, |
146 | * | 204 | IRQF_SHARED | IRQF_IRQPOLL, |
147 | * Since WDV is a 12-bit counter, the maximum period is | 205 | pdev->name, wdt); |
148 | * 4096 / 256 = 16 seconds. | 206 | if (err) |
149 | */ | 207 | return err; |
150 | reg = AT91_WDT_WDRSTEN /* causes watchdog reset */ | 208 | } |
151 | /* | AT91_WDT_WDRPROC causes processor reset only */ | 209 | |
152 | | AT91_WDT_WDDBGHLT /* disabled in debug mode */ | 210 | if ((tmp & wdt->mr_mask) != (wdt->mr & wdt->mr_mask)) |
153 | | AT91_WDT_WDD /* restart at any time */ | 211 | dev_warn(dev, |
154 | | (timeout & AT91_WDT_WDV); /* timer value */ | 212 | "watchdog already configured differently (mr = %x expecting %x)\n", |
155 | wdt_write(AT91_WDT_MR, reg); | 213 | tmp & wdt->mr_mask, wdt->mr & wdt->mr_mask); |
214 | |||
215 | setup_timer(&wdt->timer, at91_ping, (unsigned long)wdt); | ||
216 | mod_timer(&wdt->timer, jiffies + wdt->heartbeat); | ||
217 | |||
218 | /* Try to set timeout from device tree first */ | ||
219 | if (watchdog_init_timeout(&wdt->wdd, 0, dev)) | ||
220 | watchdog_init_timeout(&wdt->wdd, heartbeat, dev); | ||
221 | watchdog_set_nowayout(&wdt->wdd, wdt->nowayout); | ||
222 | err = watchdog_register_device(&wdt->wdd); | ||
223 | if (err) | ||
224 | goto out_stop_timer; | ||
225 | |||
226 | wdt->next_heartbeat = jiffies + wdt->wdd.timeout * HZ; | ||
156 | 227 | ||
157 | return 0; | 228 | return 0; |
229 | |||
230 | out_stop_timer: | ||
231 | del_timer(&wdt->timer); | ||
232 | return err; | ||
158 | } | 233 | } |
159 | 234 | ||
160 | /* ......................................................................... */ | 235 | /* ......................................................................... */ |
@@ -169,61 +244,123 @@ static const struct watchdog_ops at91_wdt_ops = { | |||
169 | .owner = THIS_MODULE, | 244 | .owner = THIS_MODULE, |
170 | .start = at91_wdt_start, | 245 | .start = at91_wdt_start, |
171 | .stop = at91_wdt_stop, | 246 | .stop = at91_wdt_stop, |
172 | .ping = at91_wdt_ping, | ||
173 | .set_timeout = at91_wdt_set_timeout, | 247 | .set_timeout = at91_wdt_set_timeout, |
174 | }; | 248 | }; |
175 | 249 | ||
176 | static struct watchdog_device at91_wdt_dev = { | 250 | #if defined(CONFIG_OF) |
177 | .info = &at91_wdt_info, | 251 | static int of_at91wdt_init(struct device_node *np, struct at91wdt *wdt) |
178 | .ops = &at91_wdt_ops, | 252 | { |
179 | .timeout = WDT_HEARTBEAT, | 253 | u32 min = 0; |
180 | .min_timeout = 1, | 254 | u32 max = WDT_COUNTER_MAX_SECS; |
181 | .max_timeout = 0xFFFF, | 255 | const char *tmp; |
182 | }; | 256 | |
257 | /* Get the interrupts property */ | ||
258 | wdt->irq = irq_of_parse_and_map(np, 0); | ||
259 | if (!wdt->irq) | ||
260 | dev_warn(wdt->wdd.parent, "failed to get IRQ from DT\n"); | ||
261 | |||
262 | if (!of_property_read_u32_index(np, "atmel,max-heartbeat-sec", 0, | ||
263 | &max)) { | ||
264 | if (!max || max > WDT_COUNTER_MAX_SECS) | ||
265 | max = WDT_COUNTER_MAX_SECS; | ||
266 | |||
267 | if (!of_property_read_u32_index(np, "atmel,min-heartbeat-sec", | ||
268 | 0, &min)) { | ||
269 | if (min >= max) | ||
270 | min = max - 1; | ||
271 | } | ||
272 | } | ||
273 | |||
274 | min = secs_to_ticks(min); | ||
275 | max = secs_to_ticks(max); | ||
276 | |||
277 | wdt->mr_mask = 0x3FFFFFFF; | ||
278 | wdt->mr = 0; | ||
279 | if (!of_property_read_string(np, "atmel,watchdog-type", &tmp) && | ||
280 | !strcmp(tmp, "software")) { | ||
281 | wdt->mr |= AT91_WDT_WDFIEN; | ||
282 | wdt->mr_mask &= ~AT91_WDT_WDRPROC; | ||
283 | } else { | ||
284 | wdt->mr |= AT91_WDT_WDRSTEN; | ||
285 | } | ||
286 | |||
287 | if (!of_property_read_string(np, "atmel,reset-type", &tmp) && | ||
288 | !strcmp(tmp, "proc")) | ||
289 | wdt->mr |= AT91_WDT_WDRPROC; | ||
290 | |||
291 | if (of_property_read_bool(np, "atmel,disable")) { | ||
292 | wdt->mr |= AT91_WDT_WDDIS; | ||
293 | wdt->mr_mask &= AT91_WDT_WDDIS; | ||
294 | } | ||
295 | |||
296 | if (of_property_read_bool(np, "atmel,idle-halt")) | ||
297 | wdt->mr |= AT91_WDT_WDIDLEHLT; | ||
298 | |||
299 | if (of_property_read_bool(np, "atmel,dbg-halt")) | ||
300 | wdt->mr |= AT91_WDT_WDDBGHLT; | ||
301 | |||
302 | wdt->mr |= max | ((max - min) << 16); | ||
303 | |||
304 | return 0; | ||
305 | } | ||
306 | #else | ||
307 | static inline int of_at91wdt_init(struct device_node *np, struct at91wdt *wdt) | ||
308 | { | ||
309 | return 0; | ||
310 | } | ||
311 | #endif | ||
183 | 312 | ||
184 | static int __init at91wdt_probe(struct platform_device *pdev) | 313 | static int __init at91wdt_probe(struct platform_device *pdev) |
185 | { | 314 | { |
186 | struct resource *r; | 315 | struct resource *r; |
187 | int res; | 316 | int err; |
317 | struct at91wdt *wdt; | ||
188 | 318 | ||
189 | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 319 | wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); |
190 | if (!r) | 320 | if (!wdt) |
191 | return -ENODEV; | ||
192 | at91wdt_private.base = ioremap(r->start, resource_size(r)); | ||
193 | if (!at91wdt_private.base) { | ||
194 | dev_err(&pdev->dev, "failed to map registers, aborting.\n"); | ||
195 | return -ENOMEM; | 321 | return -ENOMEM; |
196 | } | ||
197 | 322 | ||
198 | at91_wdt_dev.parent = &pdev->dev; | 323 | wdt->mr = (WDT_HW_TIMEOUT * 256) | AT91_WDT_WDRSTEN | AT91_WDT_WDD | |
199 | watchdog_init_timeout(&at91_wdt_dev, heartbeat, &pdev->dev); | 324 | AT91_WDT_WDDBGHLT | AT91_WDT_WDIDLEHLT; |
200 | watchdog_set_nowayout(&at91_wdt_dev, nowayout); | 325 | wdt->mr_mask = 0x3FFFFFFF; |
326 | wdt->nowayout = nowayout; | ||
327 | wdt->wdd.parent = &pdev->dev; | ||
328 | wdt->wdd.info = &at91_wdt_info; | ||
329 | wdt->wdd.ops = &at91_wdt_ops; | ||
330 | wdt->wdd.timeout = WDT_HEARTBEAT; | ||
331 | wdt->wdd.min_timeout = 1; | ||
332 | wdt->wdd.max_timeout = 0xFFFF; | ||
201 | 333 | ||
202 | /* Set watchdog */ | 334 | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
203 | res = at91_wdt_settimeout(ms_to_ticks(WDT_HW_TIMEOUT * 1000)); | 335 | wdt->base = devm_ioremap_resource(&pdev->dev, r); |
204 | if (res) | 336 | if (IS_ERR(wdt->base)) |
205 | return res; | 337 | return PTR_ERR(wdt->base); |
338 | |||
339 | if (pdev->dev.of_node) { | ||
340 | err = of_at91wdt_init(pdev->dev.of_node, wdt); | ||
341 | if (err) | ||
342 | return err; | ||
343 | } | ||
206 | 344 | ||
207 | res = watchdog_register_device(&at91_wdt_dev); | 345 | err = at91_wdt_init(pdev, wdt); |
208 | if (res) | 346 | if (err) |
209 | return res; | 347 | return err; |
210 | 348 | ||
211 | at91wdt_private.next_heartbeat = jiffies + at91_wdt_dev.timeout * HZ; | 349 | platform_set_drvdata(pdev, wdt); |
212 | setup_timer(&at91wdt_private.timer, at91_ping, 0); | ||
213 | mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); | ||
214 | 350 | ||
215 | pr_info("enabled (heartbeat=%d sec, nowayout=%d)\n", | 351 | pr_info("enabled (heartbeat=%d sec, nowayout=%d)\n", |
216 | at91_wdt_dev.timeout, nowayout); | 352 | wdt->wdd.timeout, wdt->nowayout); |
217 | 353 | ||
218 | return 0; | 354 | return 0; |
219 | } | 355 | } |
220 | 356 | ||
221 | static int __exit at91wdt_remove(struct platform_device *pdev) | 357 | static int __exit at91wdt_remove(struct platform_device *pdev) |
222 | { | 358 | { |
223 | watchdog_unregister_device(&at91_wdt_dev); | 359 | struct at91wdt *wdt = platform_get_drvdata(pdev); |
360 | watchdog_unregister_device(&wdt->wdd); | ||
224 | 361 | ||
225 | pr_warn("I quit now, hardware will probably reboot!\n"); | 362 | pr_warn("I quit now, hardware will probably reboot!\n"); |
226 | del_timer(&at91wdt_private.timer); | 363 | del_timer(&wdt->timer); |
227 | 364 | ||
228 | return 0; | 365 | return 0; |
229 | } | 366 | } |