diff options
Diffstat (limited to 'drivers/watchdog')
-rw-r--r-- | drivers/watchdog/Kconfig | 10 | ||||
-rw-r--r-- | drivers/watchdog/Makefile | 1 | ||||
-rw-r--r-- | drivers/watchdog/coh901327_wdt.c | 537 |
3 files changed, 548 insertions, 0 deletions
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 373c22fd24ea..631599b7e6a2 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig | |||
@@ -240,6 +240,16 @@ config ORION_WATCHDOG | |||
240 | To compile this driver as a module, choose M here: the | 240 | To compile this driver as a module, choose M here: the |
241 | module will be called orion_wdt. | 241 | module will be called orion_wdt. |
242 | 242 | ||
243 | config COH901327_WATCHDOG | ||
244 | bool "ST-Ericsson COH 901 327 watchdog" | ||
245 | depends on ARCH_U300 | ||
246 | default y if MACH_U300 | ||
247 | help | ||
248 | Say Y here to include Watchdog timer support for the | ||
249 | watchdog embedded into the ST-Ericsson U300 series platforms. | ||
250 | This watchdog is used to reset the system and thus cannot be | ||
251 | compiled as a module. | ||
252 | |||
243 | # AVR32 Architecture | 253 | # AVR32 Architecture |
244 | 254 | ||
245 | config AT32AP700X_WDT | 255 | config AT32AP700X_WDT |
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 7231a1500860..c3a22d3d5498 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile | |||
@@ -41,6 +41,7 @@ obj-$(CONFIG_PNX4008_WATCHDOG) += pnx4008_wdt.o | |||
41 | obj-$(CONFIG_IOP_WATCHDOG) += iop_wdt.o | 41 | obj-$(CONFIG_IOP_WATCHDOG) += iop_wdt.o |
42 | obj-$(CONFIG_DAVINCI_WATCHDOG) += davinci_wdt.o | 42 | obj-$(CONFIG_DAVINCI_WATCHDOG) += davinci_wdt.o |
43 | obj-$(CONFIG_ORION_WATCHDOG) += orion_wdt.o | 43 | obj-$(CONFIG_ORION_WATCHDOG) += orion_wdt.o |
44 | obj-$(CONFIG_COH901327_WATCHDOG) += coh901327_wdt.o | ||
44 | 45 | ||
45 | # AVR32 Architecture | 46 | # AVR32 Architecture |
46 | obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o | 47 | obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o |
diff --git a/drivers/watchdog/coh901327_wdt.c b/drivers/watchdog/coh901327_wdt.c new file mode 100644 index 000000000000..fecb307d28e9 --- /dev/null +++ b/drivers/watchdog/coh901327_wdt.c | |||
@@ -0,0 +1,537 @@ | |||
1 | /* | ||
2 | * coh901327_wdt.c | ||
3 | * | ||
4 | * Copyright (C) 2008-2009 ST-Ericsson AB | ||
5 | * License terms: GNU General Public License (GPL) version 2 | ||
6 | * Watchdog driver for the ST-Ericsson AB COH 901 327 IP core | ||
7 | * Author: Linus Walleij <linus.walleij@stericsson.com> | ||
8 | */ | ||
9 | #include <linux/module.h> | ||
10 | #include <linux/types.h> | ||
11 | #include <linux/fs.h> | ||
12 | #include <linux/miscdevice.h> | ||
13 | #include <linux/watchdog.h> | ||
14 | #include <linux/interrupt.h> | ||
15 | #include <linux/pm.h> | ||
16 | #include <linux/platform_device.h> | ||
17 | #include <linux/io.h> | ||
18 | #include <linux/bitops.h> | ||
19 | #include <linux/uaccess.h> | ||
20 | #include <linux/clk.h> | ||
21 | |||
22 | #define DRV_NAME "WDOG COH 901 327" | ||
23 | |||
24 | /* | ||
25 | * COH 901 327 register definitions | ||
26 | */ | ||
27 | |||
28 | /* WDOG_FEED Register 32bit (-/W) */ | ||
29 | #define U300_WDOG_FR 0x00 | ||
30 | #define U300_WDOG_FR_FEED_RESTART_TIMER 0xFEEDU | ||
31 | /* WDOG_TIMEOUT Register 32bit (R/W) */ | ||
32 | #define U300_WDOG_TR 0x04 | ||
33 | #define U300_WDOG_TR_TIMEOUT_MASK 0x7FFFU | ||
34 | /* WDOG_DISABLE1 Register 32bit (-/W) */ | ||
35 | #define U300_WDOG_D1R 0x08 | ||
36 | #define U300_WDOG_D1R_DISABLE1_DISABLE_TIMER 0x2BADU | ||
37 | /* WDOG_DISABLE2 Register 32bit (R/W) */ | ||
38 | #define U300_WDOG_D2R 0x0C | ||
39 | #define U300_WDOG_D2R_DISABLE2_DISABLE_TIMER 0xCAFEU | ||
40 | #define U300_WDOG_D2R_DISABLE_STATUS_DISABLED 0xDABEU | ||
41 | #define U300_WDOG_D2R_DISABLE_STATUS_ENABLED 0x0000U | ||
42 | /* WDOG_STATUS Register 32bit (R/W) */ | ||
43 | #define U300_WDOG_SR 0x10 | ||
44 | #define U300_WDOG_SR_STATUS_TIMED_OUT 0xCFE8U | ||
45 | #define U300_WDOG_SR_STATUS_NORMAL 0x0000U | ||
46 | #define U300_WDOG_SR_RESET_STATUS_RESET 0xE8B4U | ||
47 | /* WDOG_COUNT Register 32bit (R/-) */ | ||
48 | #define U300_WDOG_CR 0x14 | ||
49 | #define U300_WDOG_CR_VALID_IND 0x8000U | ||
50 | #define U300_WDOG_CR_VALID_STABLE 0x0000U | ||
51 | #define U300_WDOG_CR_COUNT_VALUE_MASK 0x7FFFU | ||
52 | /* WDOG_JTAGOVR Register 32bit (R/W) */ | ||
53 | #define U300_WDOG_JOR 0x18 | ||
54 | #define U300_WDOG_JOR_JTAG_MODE_IND 0x0002U | ||
55 | #define U300_WDOG_JOR_JTAG_WATCHDOG_ENABLE 0x0001U | ||
56 | /* WDOG_RESTART Register 32bit (-/W) */ | ||
57 | #define U300_WDOG_RR 0x1C | ||
58 | #define U300_WDOG_RR_RESTART_VALUE_RESUME 0xACEDU | ||
59 | /* WDOG_IRQ_EVENT Register 32bit (R/W) */ | ||
60 | #define U300_WDOG_IER 0x20 | ||
61 | #define U300_WDOG_IER_WILL_BARK_IRQ_EVENT_IND 0x0001U | ||
62 | #define U300_WDOG_IER_WILL_BARK_IRQ_ACK_ENABLE 0x0001U | ||
63 | /* WDOG_IRQ_MASK Register 32bit (R/W) */ | ||
64 | #define U300_WDOG_IMR 0x24 | ||
65 | #define U300_WDOG_IMR_WILL_BARK_IRQ_ENABLE 0x0001U | ||
66 | /* WDOG_IRQ_FORCE Register 32bit (R/W) */ | ||
67 | #define U300_WDOG_IFR 0x28 | ||
68 | #define U300_WDOG_IFR_WILL_BARK_IRQ_FORCE_ENABLE 0x0001U | ||
69 | |||
70 | /* Default timeout in seconds = 1 minute */ | ||
71 | static int margin = 60; | ||
72 | static resource_size_t phybase; | ||
73 | static resource_size_t physize; | ||
74 | static int irq; | ||
75 | static void __iomem *virtbase; | ||
76 | static unsigned long coh901327_users; | ||
77 | static unsigned long boot_status; | ||
78 | static u16 wdogenablestore; | ||
79 | static u16 irqmaskstore; | ||
80 | static struct device *parent; | ||
81 | |||
82 | /* | ||
83 | * The watchdog block is of course always clocked, the | ||
84 | * clk_enable()/clk_disable() calls are mainly for performing reference | ||
85 | * counting higher up in the clock hierarchy. | ||
86 | */ | ||
87 | static struct clk *clk; | ||
88 | |||
89 | /* | ||
90 | * Enabling and disabling functions. | ||
91 | */ | ||
92 | static void coh901327_enable(u16 timeout) | ||
93 | { | ||
94 | u16 val; | ||
95 | |||
96 | clk_enable(clk); | ||
97 | /* Restart timer if it is disabled */ | ||
98 | val = readw(virtbase + U300_WDOG_D2R); | ||
99 | if (val == U300_WDOG_D2R_DISABLE_STATUS_DISABLED) | ||
100 | writew(U300_WDOG_RR_RESTART_VALUE_RESUME, | ||
101 | virtbase + U300_WDOG_RR); | ||
102 | /* Acknowledge any pending interrupt so it doesn't just fire off */ | ||
103 | writew(U300_WDOG_IER_WILL_BARK_IRQ_ACK_ENABLE, | ||
104 | virtbase + U300_WDOG_IER); | ||
105 | /* Enable the watchdog interrupt */ | ||
106 | writew(U300_WDOG_IMR_WILL_BARK_IRQ_ENABLE, virtbase + U300_WDOG_IMR); | ||
107 | /* Activate the watchdog timer */ | ||
108 | writew(timeout, virtbase + U300_WDOG_TR); | ||
109 | /* Start the watchdog timer */ | ||
110 | writew(U300_WDOG_FR_FEED_RESTART_TIMER, virtbase + U300_WDOG_FR); | ||
111 | /* | ||
112 | * Extra read so that this change propagate in the watchdog. | ||
113 | */ | ||
114 | (void) readw(virtbase + U300_WDOG_CR); | ||
115 | val = readw(virtbase + U300_WDOG_D2R); | ||
116 | clk_disable(clk); | ||
117 | if (val != U300_WDOG_D2R_DISABLE_STATUS_ENABLED) | ||
118 | dev_err(parent, | ||
119 | "%s(): watchdog not enabled! D2R value %04x\n", | ||
120 | __func__, val); | ||
121 | } | ||
122 | |||
123 | static void coh901327_disable(void) | ||
124 | { | ||
125 | u16 val; | ||
126 | |||
127 | clk_enable(clk); | ||
128 | /* Disable the watchdog interrupt if it is active */ | ||
129 | writew(0x0000U, virtbase + U300_WDOG_IMR); | ||
130 | /* If the watchdog is currently enabled, attempt to disable it */ | ||
131 | val = readw(virtbase + U300_WDOG_D2R); | ||
132 | if (val != U300_WDOG_D2R_DISABLE_STATUS_DISABLED) { | ||
133 | writew(U300_WDOG_D1R_DISABLE1_DISABLE_TIMER, | ||
134 | virtbase + U300_WDOG_D1R); | ||
135 | writew(U300_WDOG_D2R_DISABLE2_DISABLE_TIMER, | ||
136 | virtbase + U300_WDOG_D2R); | ||
137 | /* Write this twice (else problems occur) */ | ||
138 | writew(U300_WDOG_D2R_DISABLE2_DISABLE_TIMER, | ||
139 | virtbase + U300_WDOG_D2R); | ||
140 | } | ||
141 | val = readw(virtbase + U300_WDOG_D2R); | ||
142 | clk_disable(clk); | ||
143 | if (val != U300_WDOG_D2R_DISABLE_STATUS_DISABLED) | ||
144 | dev_err(parent, | ||
145 | "%s(): watchdog not disabled! D2R value %04x\n", | ||
146 | __func__, val); | ||
147 | } | ||
148 | |||
149 | static void coh901327_start(void) | ||
150 | { | ||
151 | coh901327_enable(margin * 100); | ||
152 | } | ||
153 | |||
154 | static void coh901327_keepalive(void) | ||
155 | { | ||
156 | clk_enable(clk); | ||
157 | /* Feed the watchdog */ | ||
158 | writew(U300_WDOG_FR_FEED_RESTART_TIMER, | ||
159 | virtbase + U300_WDOG_FR); | ||
160 | clk_disable(clk); | ||
161 | } | ||
162 | |||
163 | static int coh901327_settimeout(int time) | ||
164 | { | ||
165 | /* | ||
166 | * Max margin is 327 since the 10ms | ||
167 | * timeout register is max | ||
168 | * 0x7FFF = 327670ms ~= 327s. | ||
169 | */ | ||
170 | if (time <= 0 || time > 327) | ||
171 | return -EINVAL; | ||
172 | |||
173 | margin = time; | ||
174 | clk_enable(clk); | ||
175 | /* Set new timeout value */ | ||
176 | writew(margin * 100, virtbase + U300_WDOG_TR); | ||
177 | /* Feed the dog */ | ||
178 | writew(U300_WDOG_FR_FEED_RESTART_TIMER, | ||
179 | virtbase + U300_WDOG_FR); | ||
180 | clk_disable(clk); | ||
181 | return 0; | ||
182 | } | ||
183 | |||
184 | /* | ||
185 | * This interrupt occurs 10 ms before the watchdog WILL bark. | ||
186 | */ | ||
187 | static irqreturn_t coh901327_interrupt(int irq, void *data) | ||
188 | { | ||
189 | u16 val; | ||
190 | |||
191 | /* | ||
192 | * Ack IRQ? If this occurs we're FUBAR anyway, so | ||
193 | * just acknowledge, disable the interrupt and await the imminent end. | ||
194 | * If you at some point need a host of callbacks to be called | ||
195 | * when the system is about to watchdog-reset, add them here! | ||
196 | * | ||
197 | * NOTE: on future versions of this IP-block, it will be possible | ||
198 | * to prevent a watchdog reset by feeding the watchdog at this | ||
199 | * point. | ||
200 | */ | ||
201 | clk_enable(clk); | ||
202 | val = readw(virtbase + U300_WDOG_IER); | ||
203 | if (val == U300_WDOG_IER_WILL_BARK_IRQ_EVENT_IND) | ||
204 | writew(U300_WDOG_IER_WILL_BARK_IRQ_ACK_ENABLE, | ||
205 | virtbase + U300_WDOG_IER); | ||
206 | writew(0x0000U, virtbase + U300_WDOG_IMR); | ||
207 | clk_disable(clk); | ||
208 | dev_crit(parent, "watchdog is barking!\n"); | ||
209 | return IRQ_HANDLED; | ||
210 | } | ||
211 | |||
212 | /* | ||
213 | * Allow only one user (daemon) to open the watchdog | ||
214 | */ | ||
215 | static int coh901327_open(struct inode *inode, struct file *file) | ||
216 | { | ||
217 | if (test_and_set_bit(1, &coh901327_users)) | ||
218 | return -EBUSY; | ||
219 | coh901327_start(); | ||
220 | return nonseekable_open(inode, file); | ||
221 | } | ||
222 | |||
223 | static int coh901327_release(struct inode *inode, struct file *file) | ||
224 | { | ||
225 | clear_bit(1, &coh901327_users); | ||
226 | coh901327_disable(); | ||
227 | return 0; | ||
228 | } | ||
229 | |||
230 | static ssize_t coh901327_write(struct file *file, const char __user *data, | ||
231 | size_t len, loff_t *ppos) | ||
232 | { | ||
233 | if (len) | ||
234 | coh901327_keepalive(); | ||
235 | return len; | ||
236 | } | ||
237 | |||
238 | static long coh901327_ioctl(struct file *file, unsigned int cmd, | ||
239 | unsigned long arg) | ||
240 | { | ||
241 | int ret = -ENOTTY; | ||
242 | u16 val; | ||
243 | int time; | ||
244 | int new_options; | ||
245 | union { | ||
246 | struct watchdog_info __user *ident; | ||
247 | int __user *i; | ||
248 | } uarg; | ||
249 | static struct watchdog_info ident = { | ||
250 | .options = WDIOF_CARDRESET | | ||
251 | WDIOF_SETTIMEOUT | | ||
252 | WDIOF_KEEPALIVEPING, | ||
253 | .identity = "COH 901 327 Watchdog", | ||
254 | .firmware_version = 1, | ||
255 | }; | ||
256 | uarg.i = (int __user *)arg; | ||
257 | |||
258 | switch (cmd) { | ||
259 | case WDIOC_GETSUPPORT: | ||
260 | ret = copy_to_user(uarg.ident, &ident, | ||
261 | sizeof(ident)) ? -EFAULT : 0; | ||
262 | break; | ||
263 | |||
264 | case WDIOC_GETSTATUS: | ||
265 | ret = put_user(0, uarg.i); | ||
266 | break; | ||
267 | |||
268 | case WDIOC_GETBOOTSTATUS: | ||
269 | ret = put_user(boot_status, uarg.i); | ||
270 | break; | ||
271 | |||
272 | case WDIOC_SETOPTIONS: | ||
273 | ret = get_user(new_options, uarg.i); | ||
274 | if (ret) | ||
275 | break; | ||
276 | if (new_options & WDIOS_DISABLECARD) | ||
277 | coh901327_disable(); | ||
278 | if (new_options & WDIOS_ENABLECARD) | ||
279 | coh901327_start(); | ||
280 | ret = 0; | ||
281 | break; | ||
282 | |||
283 | case WDIOC_KEEPALIVE: | ||
284 | coh901327_keepalive(); | ||
285 | ret = 0; | ||
286 | break; | ||
287 | |||
288 | case WDIOC_SETTIMEOUT: | ||
289 | ret = get_user(time, uarg.i); | ||
290 | if (ret) | ||
291 | break; | ||
292 | |||
293 | ret = coh901327_settimeout(time); | ||
294 | if (ret) | ||
295 | break; | ||
296 | /* Then fall through to return set value */ | ||
297 | |||
298 | case WDIOC_GETTIMEOUT: | ||
299 | ret = put_user(margin, uarg.i); | ||
300 | break; | ||
301 | |||
302 | case WDIOC_GETTIMELEFT: | ||
303 | clk_enable(clk); | ||
304 | /* Read repeatedly until the value is stable! */ | ||
305 | val = readw(virtbase + U300_WDOG_CR); | ||
306 | while (val & U300_WDOG_CR_VALID_IND) | ||
307 | val = readw(virtbase + U300_WDOG_CR); | ||
308 | val &= U300_WDOG_CR_COUNT_VALUE_MASK; | ||
309 | clk_disable(clk); | ||
310 | if (val != 0) | ||
311 | val /= 100; | ||
312 | ret = put_user(val, uarg.i); | ||
313 | break; | ||
314 | } | ||
315 | return ret; | ||
316 | } | ||
317 | |||
318 | static const struct file_operations coh901327_fops = { | ||
319 | .owner = THIS_MODULE, | ||
320 | .llseek = no_llseek, | ||
321 | .write = coh901327_write, | ||
322 | .unlocked_ioctl = coh901327_ioctl, | ||
323 | .open = coh901327_open, | ||
324 | .release = coh901327_release, | ||
325 | }; | ||
326 | |||
327 | static struct miscdevice coh901327_miscdev = { | ||
328 | .minor = WATCHDOG_MINOR, | ||
329 | .name = "watchdog", | ||
330 | .fops = &coh901327_fops, | ||
331 | }; | ||
332 | |||
333 | static int __exit coh901327_remove(struct platform_device *pdev) | ||
334 | { | ||
335 | misc_deregister(&coh901327_miscdev); | ||
336 | coh901327_disable(); | ||
337 | free_irq(irq, pdev); | ||
338 | clk_put(clk); | ||
339 | iounmap(virtbase); | ||
340 | release_mem_region(phybase, physize); | ||
341 | return 0; | ||
342 | } | ||
343 | |||
344 | |||
345 | static int __init coh901327_probe(struct platform_device *pdev) | ||
346 | { | ||
347 | int ret; | ||
348 | u16 val; | ||
349 | struct resource *res; | ||
350 | |||
351 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
352 | if (!res) | ||
353 | return -ENOENT; | ||
354 | |||
355 | parent = &pdev->dev; | ||
356 | physize = resource_size(res); | ||
357 | phybase = res->start; | ||
358 | |||
359 | if (request_mem_region(phybase, physize, DRV_NAME) == NULL) { | ||
360 | ret = -EBUSY; | ||
361 | goto out; | ||
362 | } | ||
363 | |||
364 | virtbase = ioremap(phybase, physize); | ||
365 | if (!virtbase) { | ||
366 | ret = -ENOMEM; | ||
367 | goto out_no_remap; | ||
368 | } | ||
369 | |||
370 | clk = clk_get(&pdev->dev, NULL); | ||
371 | if (IS_ERR(clk)) { | ||
372 | ret = PTR_ERR(clk); | ||
373 | dev_err(&pdev->dev, "could not get clock\n"); | ||
374 | goto out_no_clk; | ||
375 | } | ||
376 | ret = clk_enable(clk); | ||
377 | if (ret) { | ||
378 | dev_err(&pdev->dev, "could not enable clock\n"); | ||
379 | goto out_no_clk_enable; | ||
380 | } | ||
381 | |||
382 | val = readw(virtbase + U300_WDOG_SR); | ||
383 | switch (val) { | ||
384 | case U300_WDOG_SR_STATUS_TIMED_OUT: | ||
385 | dev_info(&pdev->dev, | ||
386 | "watchdog timed out since last chip reset!\n"); | ||
387 | boot_status = WDIOF_CARDRESET; | ||
388 | /* Status will be cleared below */ | ||
389 | break; | ||
390 | case U300_WDOG_SR_STATUS_NORMAL: | ||
391 | dev_info(&pdev->dev, | ||
392 | "in normal status, no timeouts have occurred.\n"); | ||
393 | break; | ||
394 | default: | ||
395 | dev_info(&pdev->dev, | ||
396 | "contains an illegal status code (%08x)\n", val); | ||
397 | break; | ||
398 | } | ||
399 | |||
400 | val = readw(virtbase + U300_WDOG_D2R); | ||
401 | switch (val) { | ||
402 | case U300_WDOG_D2R_DISABLE_STATUS_DISABLED: | ||
403 | dev_info(&pdev->dev, "currently disabled.\n"); | ||
404 | break; | ||
405 | case U300_WDOG_D2R_DISABLE_STATUS_ENABLED: | ||
406 | dev_info(&pdev->dev, | ||
407 | "currently enabled! (disabling it now)\n"); | ||
408 | coh901327_disable(); | ||
409 | break; | ||
410 | default: | ||
411 | dev_err(&pdev->dev, | ||
412 | "contains an illegal enable/disable code (%08x)\n", | ||
413 | val); | ||
414 | break; | ||
415 | } | ||
416 | |||
417 | /* Reset the watchdog */ | ||
418 | writew(U300_WDOG_SR_RESET_STATUS_RESET, virtbase + U300_WDOG_SR); | ||
419 | |||
420 | irq = platform_get_irq(pdev, 0); | ||
421 | if (request_irq(irq, coh901327_interrupt, IRQF_DISABLED, | ||
422 | DRV_NAME " Bark", pdev)) { | ||
423 | ret = -EIO; | ||
424 | goto out_no_irq; | ||
425 | } | ||
426 | |||
427 | clk_disable(clk); | ||
428 | |||
429 | ret = misc_register(&coh901327_miscdev); | ||
430 | if (ret == 0) | ||
431 | dev_info(&pdev->dev, | ||
432 | "initialized. timer margin=%d sec\n", margin); | ||
433 | else | ||
434 | goto out_no_wdog; | ||
435 | |||
436 | return 0; | ||
437 | |||
438 | out_no_wdog: | ||
439 | free_irq(irq, pdev); | ||
440 | out_no_irq: | ||
441 | clk_disable(clk); | ||
442 | out_no_clk_enable: | ||
443 | clk_put(clk); | ||
444 | out_no_clk: | ||
445 | iounmap(virtbase); | ||
446 | out_no_remap: | ||
447 | release_mem_region(phybase, SZ_4K); | ||
448 | out: | ||
449 | return ret; | ||
450 | } | ||
451 | |||
452 | #ifdef CONFIG_PM | ||
453 | static int coh901327_suspend(struct platform_device *pdev, pm_message_t state) | ||
454 | { | ||
455 | irqmaskstore = readw(virtbase + U300_WDOG_IMR) & 0x0001U; | ||
456 | wdogenablestore = readw(virtbase + U300_WDOG_D2R); | ||
457 | /* If watchdog is on, disable it here and now */ | ||
458 | if (wdogenablestore == U300_WDOG_D2R_DISABLE_STATUS_ENABLED) | ||
459 | coh901327_disable(); | ||
460 | return 0; | ||
461 | } | ||
462 | |||
463 | static int coh901327_resume(struct platform_device *pdev) | ||
464 | { | ||
465 | /* Restore the watchdog interrupt */ | ||
466 | writew(irqmaskstore, virtbase + U300_WDOG_IMR); | ||
467 | if (wdogenablestore == U300_WDOG_D2R_DISABLE_STATUS_ENABLED) { | ||
468 | /* Restart the watchdog timer */ | ||
469 | writew(U300_WDOG_RR_RESTART_VALUE_RESUME, | ||
470 | virtbase + U300_WDOG_RR); | ||
471 | writew(U300_WDOG_FR_FEED_RESTART_TIMER, | ||
472 | virtbase + U300_WDOG_FR); | ||
473 | } | ||
474 | return 0; | ||
475 | } | ||
476 | #else | ||
477 | #define coh901327_suspend NULL | ||
478 | #define coh901327_resume NULL | ||
479 | #endif | ||
480 | |||
481 | /* | ||
482 | * Mistreating the watchdog is the only way to perform a software reset of the | ||
483 | * system on EMP platforms. So we implement this and export a symbol for it. | ||
484 | */ | ||
485 | void coh901327_watchdog_reset(void) | ||
486 | { | ||
487 | /* Enable even if on JTAG too */ | ||
488 | writew(U300_WDOG_JOR_JTAG_WATCHDOG_ENABLE, | ||
489 | virtbase + U300_WDOG_JOR); | ||
490 | /* | ||
491 | * Timeout = 5s, we have to wait for the watchdog reset to | ||
492 | * actually take place: the watchdog will be reloaded with the | ||
493 | * default value immediately, so we HAVE to reboot and get back | ||
494 | * into the kernel in 30s, or the device will reboot again! | ||
495 | * The boot loader will typically deactivate the watchdog, so we | ||
496 | * need time enough for the boot loader to get to the point of | ||
497 | * deactivating the watchdog before it is shut down by it. | ||
498 | * | ||
499 | * NOTE: on future versions of the watchdog, this restriction is | ||
500 | * gone: the watchdog will be reloaded with a defaul value (1 min) | ||
501 | * instead of last value, and you can conveniently set the watchdog | ||
502 | * timeout to 10ms (value = 1) without any problems. | ||
503 | */ | ||
504 | coh901327_enable(500); | ||
505 | /* Return and await doom */ | ||
506 | } | ||
507 | |||
508 | static struct platform_driver coh901327_driver = { | ||
509 | .driver = { | ||
510 | .owner = THIS_MODULE, | ||
511 | .name = "coh901327_wdog", | ||
512 | }, | ||
513 | .remove = __exit_p(coh901327_remove), | ||
514 | .suspend = coh901327_suspend, | ||
515 | .resume = coh901327_resume, | ||
516 | }; | ||
517 | |||
518 | static int __init coh901327_init(void) | ||
519 | { | ||
520 | return platform_driver_probe(&coh901327_driver, coh901327_probe); | ||
521 | } | ||
522 | module_init(coh901327_init); | ||
523 | |||
524 | static void __exit coh901327_exit(void) | ||
525 | { | ||
526 | platform_driver_unregister(&coh901327_driver); | ||
527 | } | ||
528 | module_exit(coh901327_exit); | ||
529 | |||
530 | MODULE_AUTHOR("Linus Walleij <linus.walleij@stericsson.com>"); | ||
531 | MODULE_DESCRIPTION("COH 901 327 Watchdog"); | ||
532 | |||
533 | module_param(margin, int, 0); | ||
534 | MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)"); | ||
535 | |||
536 | MODULE_LICENSE("GPL"); | ||
537 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||