]>
Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* linux/drivers/char/watchdog/s3c2410_wdt.c |
2 | * | |
3 | * Copyright (c) 2004 Simtec Electronics | |
4 | * Ben Dooks <ben@simtec.co.uk> | |
5 | * | |
6 | * S3C2410 Watchdog Timer Support | |
7 | * | |
8 | * Based on, softdog.c by Alan Cox, | |
9 | * (c) Copyright 1996 Alan Cox <alan@redhat.com> | |
10 | * | |
11 | * This program is free software; you can redistribute it and/or modify | |
12 | * it under the terms of the GNU General Public License as published by | |
13 | * the Free Software Foundation; either version 2 of the License, or | |
14 | * (at your option) any later version. | |
15 | * | |
16 | * This program is distributed in the hope that it will be useful, | |
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
19 | * GNU General Public License for more details. | |
20 | * | |
21 | * You should have received a copy of the GNU General Public License | |
22 | * along with this program; if not, write to the Free Software | |
23 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
24 | * | |
25 | * Changelog: | |
26 | * 05-Oct-2004 BJD Added semaphore init to stop crashes on open | |
27 | * Fixed tmr_count / wdt_count confusion | |
28 | * Added configurable debug | |
29 | * | |
af4bb822 BD |
30 | * 11-Jan-2005 BJD Fixed divide-by-2 in timeout code |
31 | * | |
32 | * 25-Jan-2005 DA Added suspend/resume support | |
94f1e9f3 | 33 | * Replaced reboot notifier with .shutdown method |
1da177e4 LT |
34 | * |
35 | * 10-Mar-2005 LCVR Changed S3C2410_VA to S3C24XX_VA | |
36 | */ | |
37 | ||
38 | #include <linux/module.h> | |
39 | #include <linux/moduleparam.h> | |
1da177e4 LT |
40 | #include <linux/types.h> |
41 | #include <linux/timer.h> | |
42 | #include <linux/miscdevice.h> | |
43 | #include <linux/watchdog.h> | |
44 | #include <linux/fs.h> | |
1da177e4 | 45 | #include <linux/init.h> |
d052d1be | 46 | #include <linux/platform_device.h> |
1da177e4 | 47 | #include <linux/interrupt.h> |
f8ce2547 | 48 | #include <linux/clk.h> |
41dc8b72 AC |
49 | #include <linux/uaccess.h> |
50 | #include <linux/io.h> | |
1da177e4 LT |
51 | |
52 | #include <asm/arch/map.h> | |
1da177e4 | 53 | |
b430708a BD |
54 | #undef S3C_VA_WATCHDOG |
55 | #define S3C_VA_WATCHDOG (0) | |
1da177e4 | 56 | |
b430708a | 57 | #include <asm/plat-s3c/regs-watchdog.h> |
1da177e4 LT |
58 | |
59 | #define PFX "s3c2410-wdt: " | |
60 | ||
61 | #define CONFIG_S3C2410_WATCHDOG_ATBOOT (0) | |
62 | #define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15) | |
63 | ||
25ff3780 | 64 | static int nowayout = WATCHDOG_NOWAYOUT; |
1da177e4 LT |
65 | static int tmr_margin = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME; |
66 | static int tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT; | |
41dc8b72 AC |
67 | static int soft_noboot; |
68 | static int debug; | |
1da177e4 LT |
69 | |
70 | module_param(tmr_margin, int, 0); | |
71 | module_param(tmr_atboot, int, 0); | |
72 | module_param(nowayout, int, 0); | |
73 | module_param(soft_noboot, int, 0); | |
74 | module_param(debug, int, 0); | |
75 | ||
41dc8b72 AC |
76 | MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. default=" |
77 | __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME) ")"); | |
78 | MODULE_PARM_DESC(tmr_atboot, | |
79 | "Watchdog is started at boot time if set to 1, default=" | |
80 | __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_ATBOOT)); | |
81 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" | |
82 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | |
1da177e4 | 83 | MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, 0 to reboot (default depends on ONLY_TESTING)"); |
1da177e4 LT |
84 | MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug, (default 0)"); |
85 | ||
86 | ||
87 | typedef enum close_state { | |
88 | CLOSE_STATE_NOT, | |
41dc8b72 | 89 | CLOSE_STATE_ALLOW = 0x4021 |
1da177e4 LT |
90 | } close_state_t; |
91 | ||
41dc8b72 | 92 | static unsigned long open_lock; |
e8ef92b8 | 93 | static struct device *wdt_dev; /* platform device attached to */ |
1da177e4 LT |
94 | static struct resource *wdt_mem; |
95 | static struct resource *wdt_irq; | |
96 | static struct clk *wdt_clock; | |
97 | static void __iomem *wdt_base; | |
98 | static unsigned int wdt_count; | |
99 | static close_state_t allow_close; | |
41dc8b72 | 100 | static DEFINE_SPINLOCK(wdt_lock); |
1da177e4 LT |
101 | |
102 | /* watchdog control routines */ | |
103 | ||
104 | #define DBG(msg...) do { \ | |
105 | if (debug) \ | |
106 | printk(KERN_INFO msg); \ | |
41dc8b72 | 107 | } while (0) |
1da177e4 LT |
108 | |
109 | /* functions */ | |
110 | ||
41dc8b72 | 111 | static void s3c2410wdt_keepalive(void) |
1da177e4 | 112 | { |
41dc8b72 | 113 | spin_lock(&wdt_lock); |
1da177e4 | 114 | writel(wdt_count, wdt_base + S3C2410_WTCNT); |
41dc8b72 | 115 | spin_unlock(&wdt_lock); |
1da177e4 LT |
116 | } |
117 | ||
41dc8b72 | 118 | static void __s3c2410wdt_stop(void) |
1da177e4 LT |
119 | { |
120 | unsigned long wtcon; | |
121 | ||
41dc8b72 | 122 | spin_lock(&wdt_lock); |
1da177e4 LT |
123 | wtcon = readl(wdt_base + S3C2410_WTCON); |
124 | wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN); | |
125 | writel(wtcon, wdt_base + S3C2410_WTCON); | |
41dc8b72 AC |
126 | spin_unlock(&wdt_lock); |
127 | } | |
1da177e4 | 128 | |
41dc8b72 AC |
129 | static void __s3c2410wdt_stop(void) |
130 | { | |
131 | unsigned long wtcon; | |
132 | ||
133 | wtcon = readl(wdt_base + S3C2410_WTCON); | |
134 | wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN); | |
135 | writel(wtcon, wdt_base + S3C2410_WTCON); | |
136 | } | |
137 | ||
138 | static void s3c2410wdt_stop(void) | |
139 | { | |
140 | spin_lock(&wdt_lock); | |
141 | __s3c2410wdt_stop(); | |
142 | spin_unlock(&wdt_lock); | |
1da177e4 LT |
143 | } |
144 | ||
41dc8b72 | 145 | static void s3c2410wdt_start(void) |
1da177e4 LT |
146 | { |
147 | unsigned long wtcon; | |
148 | ||
41dc8b72 AC |
149 | spin_lock(&wdt_lock); |
150 | ||
151 | __s3c2410wdt_stop(); | |
1da177e4 LT |
152 | |
153 | wtcon = readl(wdt_base + S3C2410_WTCON); | |
154 | wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128; | |
155 | ||
156 | if (soft_noboot) { | |
157 | wtcon |= S3C2410_WTCON_INTEN; | |
158 | wtcon &= ~S3C2410_WTCON_RSTEN; | |
159 | } else { | |
160 | wtcon &= ~S3C2410_WTCON_INTEN; | |
161 | wtcon |= S3C2410_WTCON_RSTEN; | |
162 | } | |
163 | ||
164 | DBG("%s: wdt_count=0x%08x, wtcon=%08lx\n", | |
fa9363c5 | 165 | __func__, wdt_count, wtcon); |
1da177e4 LT |
166 | |
167 | writel(wdt_count, wdt_base + S3C2410_WTDAT); | |
168 | writel(wdt_count, wdt_base + S3C2410_WTCNT); | |
169 | writel(wtcon, wdt_base + S3C2410_WTCON); | |
41dc8b72 | 170 | spin_unlock(&wdt_lock); |
1da177e4 LT |
171 | |
172 | return 0; | |
173 | } | |
174 | ||
175 | static int s3c2410wdt_set_heartbeat(int timeout) | |
176 | { | |
177 | unsigned int freq = clk_get_rate(wdt_clock); | |
178 | unsigned int count; | |
179 | unsigned int divisor = 1; | |
180 | unsigned long wtcon; | |
181 | ||
182 | if (timeout < 1) | |
183 | return -EINVAL; | |
184 | ||
185 | freq /= 128; | |
186 | count = timeout * freq; | |
187 | ||
188 | DBG("%s: count=%d, timeout=%d, freq=%d\n", | |
fa9363c5 | 189 | __func__, count, timeout, freq); |
1da177e4 LT |
190 | |
191 | /* if the count is bigger than the watchdog register, | |
192 | then work out what we need to do (and if) we can | |
193 | actually make this value | |
194 | */ | |
195 | ||
196 | if (count >= 0x10000) { | |
197 | for (divisor = 1; divisor <= 0x100; divisor++) { | |
198 | if ((count / divisor) < 0x10000) | |
199 | break; | |
200 | } | |
201 | ||
202 | if ((count / divisor) >= 0x10000) { | |
e8ef92b8 | 203 | dev_err(wdt_dev, "timeout %d too big\n", timeout); |
1da177e4 LT |
204 | return -EINVAL; |
205 | } | |
206 | } | |
207 | ||
208 | tmr_margin = timeout; | |
209 | ||
210 | DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n", | |
fa9363c5 | 211 | __func__, timeout, divisor, count, count/divisor); |
1da177e4 LT |
212 | |
213 | count /= divisor; | |
214 | wdt_count = count; | |
215 | ||
216 | /* update the pre-scaler */ | |
217 | wtcon = readl(wdt_base + S3C2410_WTCON); | |
218 | wtcon &= ~S3C2410_WTCON_PRESCALE_MASK; | |
219 | wtcon |= S3C2410_WTCON_PRESCALE(divisor-1); | |
220 | ||
221 | writel(count, wdt_base + S3C2410_WTDAT); | |
222 | writel(wtcon, wdt_base + S3C2410_WTCON); | |
223 | ||
224 | return 0; | |
225 | } | |
226 | ||
227 | /* | |
228 | * /dev/watchdog handling | |
229 | */ | |
230 | ||
231 | static int s3c2410wdt_open(struct inode *inode, struct file *file) | |
232 | { | |
41dc8b72 | 233 | if (test_and_set_bit(0, &open_lock)) |
1da177e4 LT |
234 | return -EBUSY; |
235 | ||
25ff3780 | 236 | if (nowayout) |
1da177e4 | 237 | __module_get(THIS_MODULE); |
25ff3780 BD |
238 | |
239 | allow_close = CLOSE_STATE_NOT; | |
1da177e4 LT |
240 | |
241 | /* start the timer */ | |
242 | s3c2410wdt_start(); | |
243 | return nonseekable_open(inode, file); | |
244 | } | |
245 | ||
246 | static int s3c2410wdt_release(struct inode *inode, struct file *file) | |
247 | { | |
248 | /* | |
249 | * Shut off the timer. | |
250 | * Lock it in if it's a module and we set nowayout | |
251 | */ | |
25ff3780 | 252 | |
41dc8b72 | 253 | if (allow_close == CLOSE_STATE_ALLOW) |
1da177e4 | 254 | s3c2410wdt_stop(); |
41dc8b72 | 255 | else { |
e8ef92b8 | 256 | dev_err(wdt_dev, "Unexpected close, not stopping watchdog\n"); |
1da177e4 LT |
257 | s3c2410wdt_keepalive(); |
258 | } | |
1da177e4 | 259 | allow_close = CLOSE_STATE_NOT; |
41dc8b72 | 260 | clear_bit(0, &open_lock); |
1da177e4 LT |
261 | return 0; |
262 | } | |
263 | ||
264 | static ssize_t s3c2410wdt_write(struct file *file, const char __user *data, | |
265 | size_t len, loff_t *ppos) | |
266 | { | |
267 | /* | |
268 | * Refresh the timer. | |
269 | */ | |
41dc8b72 | 270 | if (len) { |
1da177e4 LT |
271 | if (!nowayout) { |
272 | size_t i; | |
273 | ||
274 | /* In case it was set long ago */ | |
275 | allow_close = CLOSE_STATE_NOT; | |
276 | ||
277 | for (i = 0; i != len; i++) { | |
278 | char c; | |
279 | ||
280 | if (get_user(c, data + i)) | |
281 | return -EFAULT; | |
282 | if (c == 'V') | |
283 | allow_close = CLOSE_STATE_ALLOW; | |
284 | } | |
285 | } | |
1da177e4 LT |
286 | s3c2410wdt_keepalive(); |
287 | } | |
288 | return len; | |
289 | } | |
290 | ||
291 | #define OPTIONS WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | |
292 | ||
41dc8b72 | 293 | static const struct watchdog_info s3c2410_wdt_ident = { |
1da177e4 LT |
294 | .options = OPTIONS, |
295 | .firmware_version = 0, | |
296 | .identity = "S3C2410 Watchdog", | |
297 | }; | |
298 | ||
299 | ||
41dc8b72 AC |
300 | static long s3c2410wdt_ioctl(struct file *file, unsigned int cmd, |
301 | unsigned long arg) | |
1da177e4 LT |
302 | { |
303 | void __user *argp = (void __user *)arg; | |
304 | int __user *p = argp; | |
305 | int new_margin; | |
306 | ||
307 | switch (cmd) { | |
41dc8b72 AC |
308 | default: |
309 | return -ENOTTY; | |
310 | case WDIOC_GETSUPPORT: | |
311 | return copy_to_user(argp, &s3c2410_wdt_ident, | |
312 | sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0; | |
313 | case WDIOC_GETSTATUS: | |
314 | case WDIOC_GETBOOTSTATUS: | |
315 | return put_user(0, p); | |
316 | case WDIOC_KEEPALIVE: | |
317 | s3c2410wdt_keepalive(); | |
318 | return 0; | |
319 | case WDIOC_SETTIMEOUT: | |
320 | if (get_user(new_margin, p)) | |
321 | return -EFAULT; | |
322 | if (s3c2410wdt_set_heartbeat(new_margin)) | |
323 | return -EINVAL; | |
324 | s3c2410wdt_keepalive(); | |
325 | return put_user(tmr_margin, p); | |
326 | case WDIOC_GETTIMEOUT: | |
327 | return put_user(tmr_margin, p); | |
1da177e4 LT |
328 | } |
329 | } | |
330 | ||
1da177e4 LT |
331 | /* kernel interface */ |
332 | ||
62322d25 | 333 | static const struct file_operations s3c2410wdt_fops = { |
1da177e4 LT |
334 | .owner = THIS_MODULE, |
335 | .llseek = no_llseek, | |
336 | .write = s3c2410wdt_write, | |
41dc8b72 | 337 | .unlocked_ioctl = s3c2410wdt_ioctl, |
1da177e4 LT |
338 | .open = s3c2410wdt_open, |
339 | .release = s3c2410wdt_release, | |
340 | }; | |
341 | ||
342 | static struct miscdevice s3c2410wdt_miscdev = { | |
343 | .minor = WATCHDOG_MINOR, | |
344 | .name = "watchdog", | |
345 | .fops = &s3c2410wdt_fops, | |
346 | }; | |
347 | ||
1da177e4 LT |
348 | /* interrupt handler code */ |
349 | ||
7d12e780 | 350 | static irqreturn_t s3c2410wdt_irq(int irqno, void *param) |
1da177e4 | 351 | { |
e8ef92b8 | 352 | dev_info(wdt_dev, "watchdog timer expired (irq)\n"); |
1da177e4 LT |
353 | |
354 | s3c2410wdt_keepalive(); | |
355 | return IRQ_HANDLED; | |
356 | } | |
357 | /* device interface */ | |
358 | ||
3ae5eaec | 359 | static int s3c2410wdt_probe(struct platform_device *pdev) |
1da177e4 | 360 | { |
1da177e4 | 361 | struct resource *res; |
e8ef92b8 | 362 | struct device *dev; |
46b814d6 | 363 | unsigned int wtcon; |
1da177e4 LT |
364 | int started = 0; |
365 | int ret; | |
366 | int size; | |
367 | ||
fa9363c5 | 368 | DBG("%s: probe=%p\n", __func__, pdev); |
1da177e4 | 369 | |
e8ef92b8 BD |
370 | dev = &pdev->dev; |
371 | wdt_dev = &pdev->dev; | |
372 | ||
1da177e4 LT |
373 | /* get the memory region for the watchdog timer */ |
374 | ||
375 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
376 | if (res == NULL) { | |
e8ef92b8 | 377 | dev_err(dev, "no memory resource specified\n"); |
1da177e4 LT |
378 | return -ENOENT; |
379 | } | |
380 | ||
381 | size = (res->end-res->start)+1; | |
382 | wdt_mem = request_mem_region(res->start, size, pdev->name); | |
383 | if (wdt_mem == NULL) { | |
e8ef92b8 | 384 | dev_err(dev, "failed to get memory region\n"); |
0b6dd8a6 BD |
385 | ret = -ENOENT; |
386 | goto err_req; | |
1da177e4 LT |
387 | } |
388 | ||
389 | wdt_base = ioremap(res->start, size); | |
390 | if (wdt_base == 0) { | |
e8ef92b8 | 391 | dev_err(dev, "failed to ioremap() region\n"); |
0b6dd8a6 BD |
392 | ret = -EINVAL; |
393 | goto err_req; | |
1da177e4 LT |
394 | } |
395 | ||
396 | DBG("probe: mapped wdt_base=%p\n", wdt_base); | |
397 | ||
62be0741 AP |
398 | wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); |
399 | if (wdt_irq == NULL) { | |
e8ef92b8 | 400 | dev_err(dev, "no irq resource specified\n"); |
0b6dd8a6 BD |
401 | ret = -ENOENT; |
402 | goto err_map; | |
1da177e4 LT |
403 | } |
404 | ||
62be0741 | 405 | ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev); |
1da177e4 | 406 | if (ret != 0) { |
e8ef92b8 | 407 | dev_err(dev, "failed to install irq (%d)\n", ret); |
0b6dd8a6 | 408 | goto err_map; |
1da177e4 LT |
409 | } |
410 | ||
3ae5eaec | 411 | wdt_clock = clk_get(&pdev->dev, "watchdog"); |
9cd44619 | 412 | if (IS_ERR(wdt_clock)) { |
e8ef92b8 | 413 | dev_err(dev, "failed to find watchdog clock source\n"); |
9cd44619 | 414 | ret = PTR_ERR(wdt_clock); |
0b6dd8a6 | 415 | goto err_irq; |
1da177e4 LT |
416 | } |
417 | ||
1da177e4 LT |
418 | clk_enable(wdt_clock); |
419 | ||
420 | /* see if we can actually set the requested timer margin, and if | |
421 | * not, try the default value */ | |
422 | ||
423 | if (s3c2410wdt_set_heartbeat(tmr_margin)) { | |
41dc8b72 AC |
424 | started = s3c2410wdt_set_heartbeat( |
425 | CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); | |
1da177e4 | 426 | |
41dc8b72 AC |
427 | if (started == 0) |
428 | dev_info(dev, | |
429 | "tmr_margin value out of range, default %d used\n", | |
1da177e4 | 430 | CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); |
41dc8b72 | 431 | else |
e8ef92b8 | 432 | dev_info(dev, "default timer value is out of range, cannot start\n"); |
1da177e4 LT |
433 | } |
434 | ||
1da177e4 LT |
435 | ret = misc_register(&s3c2410wdt_miscdev); |
436 | if (ret) { | |
e8ef92b8 | 437 | dev_err(dev, "cannot register miscdev on minor=%d (%d)\n", |
1da177e4 | 438 | WATCHDOG_MINOR, ret); |
0b6dd8a6 | 439 | goto err_clk; |
1da177e4 LT |
440 | } |
441 | ||
442 | if (tmr_atboot && started == 0) { | |
e8ef92b8 | 443 | dev_info(dev, "starting watchdog timer\n"); |
1da177e4 | 444 | s3c2410wdt_start(); |
655516c8 BD |
445 | } else if (!tmr_atboot) { |
446 | /* if we're not enabling the watchdog, then ensure it is | |
447 | * disabled if it has been left running from the bootloader | |
448 | * or other source */ | |
449 | ||
450 | s3c2410wdt_stop(); | |
1da177e4 LT |
451 | } |
452 | ||
46b814d6 BD |
453 | /* print out a statement of readiness */ |
454 | ||
455 | wtcon = readl(wdt_base + S3C2410_WTCON); | |
456 | ||
e8ef92b8 | 457 | dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled\n", |
46b814d6 BD |
458 | (wtcon & S3C2410_WTCON_ENABLE) ? "" : "in", |
459 | (wtcon & S3C2410_WTCON_RSTEN) ? "" : "dis", | |
460 | (wtcon & S3C2410_WTCON_INTEN) ? "" : "en"); | |
41dc8b72 | 461 | |
1da177e4 | 462 | return 0; |
0b6dd8a6 BD |
463 | |
464 | err_clk: | |
465 | clk_disable(wdt_clock); | |
466 | clk_put(wdt_clock); | |
467 | ||
468 | err_irq: | |
469 | free_irq(wdt_irq->start, pdev); | |
470 | ||
471 | err_map: | |
472 | iounmap(wdt_base); | |
473 | ||
474 | err_req: | |
475 | release_resource(wdt_mem); | |
476 | kfree(wdt_mem); | |
477 | ||
478 | return ret; | |
1da177e4 LT |
479 | } |
480 | ||
3ae5eaec | 481 | static int s3c2410wdt_remove(struct platform_device *dev) |
1da177e4 | 482 | { |
0b6dd8a6 BD |
483 | release_resource(wdt_mem); |
484 | kfree(wdt_mem); | |
485 | wdt_mem = NULL; | |
1da177e4 | 486 | |
0b6dd8a6 BD |
487 | free_irq(wdt_irq->start, dev); |
488 | wdt_irq = NULL; | |
1da177e4 | 489 | |
0b6dd8a6 BD |
490 | clk_disable(wdt_clock); |
491 | clk_put(wdt_clock); | |
492 | wdt_clock = NULL; | |
1da177e4 | 493 | |
e34477e9 | 494 | iounmap(wdt_base); |
1da177e4 LT |
495 | misc_deregister(&s3c2410wdt_miscdev); |
496 | return 0; | |
497 | } | |
498 | ||
3ae5eaec | 499 | static void s3c2410wdt_shutdown(struct platform_device *dev) |
94f1e9f3 | 500 | { |
41dc8b72 | 501 | s3c2410wdt_stop(); |
94f1e9f3 BD |
502 | } |
503 | ||
af4bb822 BD |
504 | #ifdef CONFIG_PM |
505 | ||
506 | static unsigned long wtcon_save; | |
507 | static unsigned long wtdat_save; | |
508 | ||
3ae5eaec | 509 | static int s3c2410wdt_suspend(struct platform_device *dev, pm_message_t state) |
af4bb822 | 510 | { |
9480e307 RK |
511 | /* Save watchdog state, and turn it off. */ |
512 | wtcon_save = readl(wdt_base + S3C2410_WTCON); | |
513 | wtdat_save = readl(wdt_base + S3C2410_WTDAT); | |
af4bb822 | 514 | |
9480e307 RK |
515 | /* Note that WTCNT doesn't need to be saved. */ |
516 | s3c2410wdt_stop(); | |
af4bb822 BD |
517 | |
518 | return 0; | |
519 | } | |
520 | ||
3ae5eaec | 521 | static int s3c2410wdt_resume(struct platform_device *dev) |
af4bb822 | 522 | { |
9480e307 | 523 | /* Restore watchdog state. */ |
af4bb822 | 524 | |
9480e307 RK |
525 | writel(wtdat_save, wdt_base + S3C2410_WTDAT); |
526 | writel(wtdat_save, wdt_base + S3C2410_WTCNT); /* Reset count */ | |
527 | writel(wtcon_save, wdt_base + S3C2410_WTCON); | |
af4bb822 | 528 | |
9480e307 RK |
529 | printk(KERN_INFO PFX "watchdog %sabled\n", |
530 | (wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis"); | |
af4bb822 BD |
531 | |
532 | return 0; | |
533 | } | |
534 | ||
535 | #else | |
536 | #define s3c2410wdt_suspend NULL | |
537 | #define s3c2410wdt_resume NULL | |
538 | #endif /* CONFIG_PM */ | |
539 | ||
540 | ||
3ae5eaec | 541 | static struct platform_driver s3c2410wdt_driver = { |
1da177e4 LT |
542 | .probe = s3c2410wdt_probe, |
543 | .remove = s3c2410wdt_remove, | |
94f1e9f3 | 544 | .shutdown = s3c2410wdt_shutdown, |
af4bb822 BD |
545 | .suspend = s3c2410wdt_suspend, |
546 | .resume = s3c2410wdt_resume, | |
3ae5eaec RK |
547 | .driver = { |
548 | .owner = THIS_MODULE, | |
549 | .name = "s3c2410-wdt", | |
550 | }, | |
1da177e4 LT |
551 | }; |
552 | ||
553 | ||
41dc8b72 AC |
554 | static char banner[] __initdata = |
555 | KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n"; | |
1da177e4 LT |
556 | |
557 | static int __init watchdog_init(void) | |
558 | { | |
559 | printk(banner); | |
3ae5eaec | 560 | return platform_driver_register(&s3c2410wdt_driver); |
1da177e4 LT |
561 | } |
562 | ||
563 | static void __exit watchdog_exit(void) | |
564 | { | |
3ae5eaec | 565 | platform_driver_unregister(&s3c2410wdt_driver); |
1da177e4 LT |
566 | } |
567 | ||
568 | module_init(watchdog_init); | |
569 | module_exit(watchdog_exit); | |
570 | ||
af4bb822 BD |
571 | MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>, " |
572 | "Dimitry Andric <dimitry.andric@tomtom.com>"); | |
1da177e4 LT |
573 | MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver"); |
574 | MODULE_LICENSE("GPL"); | |
575 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | |
f37d193c | 576 | MODULE_ALIAS("platform:s3c2410-wdt"); |