1 /* linux/drivers/char/watchdog/s3c2410_wdt.c
3 * Copyright (c) 2004 Simtec Electronics
4 * Ben Dooks <ben@simtec.co.uk>
6 * S3C2410 Watchdog Timer Support
8 * Based on, softdog.c by Alan Cox,
9 * (c) Copyright 1996 Alan Cox <alan@redhat.com>
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.
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.
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
26 * 05-Oct-2004 BJD Added semaphore init to stop crashes on open
27 * Fixed tmr_count / wdt_count confusion
28 * Added configurable debug
30 * 11-Jan-2004 BJD Fixed divide-by-2 in timeout code
32 * 10-Mar-2005 LCVR Changed S3C2410_VA to S3C24XX_VA
35 #include <linux/module.h>
36 #include <linux/moduleparam.h>
37 #include <linux/config.h>
38 #include <linux/types.h>
39 #include <linux/timer.h>
40 #include <linux/miscdevice.h>
41 #include <linux/watchdog.h>
43 #include <linux/notifier.h>
44 #include <linux/reboot.h>
45 #include <linux/init.h>
46 #include <linux/device.h>
47 #include <linux/interrupt.h>
49 #include <asm/uaccess.h>
52 #include <asm/arch/map.h>
53 #include <asm/hardware/clock.h>
55 #undef S3C24XX_VA_WATCHDOG
56 #define S3C24XX_VA_WATCHDOG (0)
58 #include <asm/arch/regs-watchdog.h>
60 #define PFX "s3c2410-wdt: "
62 #define CONFIG_S3C2410_WATCHDOG_ATBOOT (0)
63 #define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15)
65 #ifdef CONFIG_WATCHDOG_NOWAYOUT
66 static int nowayout
= 1;
68 static int nowayout
= 0;
71 static int tmr_margin
= CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME
;
72 static int tmr_atboot
= CONFIG_S3C2410_WATCHDOG_ATBOOT
;
73 static int soft_noboot
= 0;
76 module_param(tmr_margin
, int, 0);
77 module_param(tmr_atboot
, int, 0);
78 module_param(nowayout
, int, 0);
79 module_param(soft_noboot
, int, 0);
80 module_param(debug
, int, 0);
82 MODULE_PARM_DESC(tmr_margin
, "Watchdog tmr_margin in seconds. default=" __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME
) ")");
84 MODULE_PARM_DESC(tmr_atboot
, "Watchdog is started at boot time if set to 1, default=" __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_ATBOOT
));
86 MODULE_PARM_DESC(nowayout
, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
88 MODULE_PARM_DESC(soft_noboot
, "Watchdog action, set to 1 to ignore reboots, 0 to reboot (default depends on ONLY_TESTING)");
90 MODULE_PARM_DESC(debug
, "Watchdog debug, set to >1 for debug, (default 0)");
93 typedef enum close_state
{
95 CLOSE_STATE_ALLOW
=0x4021
98 static DECLARE_MUTEX(open_lock
);
100 static struct resource
*wdt_mem
;
101 static struct resource
*wdt_irq
;
102 static struct clk
*wdt_clock
;
103 static void __iomem
*wdt_base
;
104 static unsigned int wdt_count
;
105 static close_state_t allow_close
;
107 /* watchdog control routines */
109 #define DBG(msg...) do { \
111 printk(KERN_INFO msg); \
116 static int s3c2410wdt_keepalive(void)
118 writel(wdt_count
, wdt_base
+ S3C2410_WTCNT
);
122 static int s3c2410wdt_stop(void)
126 wtcon
= readl(wdt_base
+ S3C2410_WTCON
);
127 wtcon
&= ~(S3C2410_WTCON_ENABLE
| S3C2410_WTCON_RSTEN
);
128 writel(wtcon
, wdt_base
+ S3C2410_WTCON
);
133 static int s3c2410wdt_start(void)
139 wtcon
= readl(wdt_base
+ S3C2410_WTCON
);
140 wtcon
|= S3C2410_WTCON_ENABLE
| S3C2410_WTCON_DIV128
;
143 wtcon
|= S3C2410_WTCON_INTEN
;
144 wtcon
&= ~S3C2410_WTCON_RSTEN
;
146 wtcon
&= ~S3C2410_WTCON_INTEN
;
147 wtcon
|= S3C2410_WTCON_RSTEN
;
150 DBG("%s: wdt_count=0x%08x, wtcon=%08lx\n",
151 __FUNCTION__
, wdt_count
, wtcon
);
153 writel(wdt_count
, wdt_base
+ S3C2410_WTDAT
);
154 writel(wdt_count
, wdt_base
+ S3C2410_WTCNT
);
155 writel(wtcon
, wdt_base
+ S3C2410_WTCON
);
160 static int s3c2410wdt_set_heartbeat(int timeout
)
162 unsigned int freq
= clk_get_rate(wdt_clock
);
164 unsigned int divisor
= 1;
171 count
= timeout
* freq
;
173 DBG("%s: count=%d, timeout=%d, freq=%d\n",
174 __FUNCTION__
, count
, timeout
, freq
);
176 /* if the count is bigger than the watchdog register,
177 then work out what we need to do (and if) we can
178 actually make this value
181 if (count
>= 0x10000) {
182 for (divisor
= 1; divisor
<= 0x100; divisor
++) {
183 if ((count
/ divisor
) < 0x10000)
187 if ((count
/ divisor
) >= 0x10000) {
188 printk(KERN_ERR PFX
"timeout %d too big\n", timeout
);
193 tmr_margin
= timeout
;
195 DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n",
196 __FUNCTION__
, timeout
, divisor
, count
, count
/divisor
);
201 /* update the pre-scaler */
202 wtcon
= readl(wdt_base
+ S3C2410_WTCON
);
203 wtcon
&= ~S3C2410_WTCON_PRESCALE_MASK
;
204 wtcon
|= S3C2410_WTCON_PRESCALE(divisor
-1);
206 writel(count
, wdt_base
+ S3C2410_WTDAT
);
207 writel(wtcon
, wdt_base
+ S3C2410_WTCON
);
213 * /dev/watchdog handling
216 static int s3c2410wdt_open(struct inode
*inode
, struct file
*file
)
218 if(down_trylock(&open_lock
))
222 __module_get(THIS_MODULE
);
224 allow_close
= CLOSE_STATE_ALLOW
;
227 /* start the timer */
229 return nonseekable_open(inode
, file
);
232 static int s3c2410wdt_release(struct inode
*inode
, struct file
*file
)
235 * Shut off the timer.
236 * Lock it in if it's a module and we set nowayout
238 if (allow_close
== CLOSE_STATE_ALLOW
) {
241 printk(KERN_CRIT PFX
"Unexpected close, not stopping watchdog!\n");
242 s3c2410wdt_keepalive();
245 allow_close
= CLOSE_STATE_NOT
;
250 static ssize_t
s3c2410wdt_write(struct file
*file
, const char __user
*data
,
251 size_t len
, loff_t
*ppos
)
260 /* In case it was set long ago */
261 allow_close
= CLOSE_STATE_NOT
;
263 for (i
= 0; i
!= len
; i
++) {
266 if (get_user(c
, data
+ i
))
269 allow_close
= CLOSE_STATE_ALLOW
;
273 s3c2410wdt_keepalive();
278 #define OPTIONS WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE
280 static struct watchdog_info s3c2410_wdt_ident
= {
282 .firmware_version
= 0,
283 .identity
= "S3C2410 Watchdog",
287 static int s3c2410wdt_ioctl(struct inode
*inode
, struct file
*file
,
288 unsigned int cmd
, unsigned long arg
)
290 void __user
*argp
= (void __user
*)arg
;
291 int __user
*p
= argp
;
298 case WDIOC_GETSUPPORT
:
299 return copy_to_user(argp
, &s3c2410_wdt_ident
,
300 sizeof(s3c2410_wdt_ident
)) ? -EFAULT
: 0;
302 case WDIOC_GETSTATUS
:
303 case WDIOC_GETBOOTSTATUS
:
304 return put_user(0, p
);
306 case WDIOC_KEEPALIVE
:
307 s3c2410wdt_keepalive();
310 case WDIOC_SETTIMEOUT
:
311 if (get_user(new_margin
, p
))
314 if (s3c2410wdt_set_heartbeat(new_margin
))
317 s3c2410wdt_keepalive();
318 return put_user(tmr_margin
, p
);
320 case WDIOC_GETTIMEOUT
:
321 return put_user(tmr_margin
, p
);
326 * Notifier for system down
329 static int s3c2410wdt_notify_sys(struct notifier_block
*this, unsigned long code
,
332 if(code
==SYS_DOWN
|| code
==SYS_HALT
) {
333 /* Turn the WDT off */
339 /* kernel interface */
341 static struct file_operations s3c2410wdt_fops
= {
342 .owner
= THIS_MODULE
,
344 .write
= s3c2410wdt_write
,
345 .ioctl
= s3c2410wdt_ioctl
,
346 .open
= s3c2410wdt_open
,
347 .release
= s3c2410wdt_release
,
350 static struct miscdevice s3c2410wdt_miscdev
= {
351 .minor
= WATCHDOG_MINOR
,
353 .fops
= &s3c2410wdt_fops
,
356 static struct notifier_block s3c2410wdt_notifier
= {
357 .notifier_call
= s3c2410wdt_notify_sys
,
360 /* interrupt handler code */
362 static irqreturn_t
s3c2410wdt_irq(int irqno
, void *param
,
363 struct pt_regs
*regs
)
365 printk(KERN_INFO PFX
"Watchdog timer expired!\n");
367 s3c2410wdt_keepalive();
370 /* device interface */
372 static int s3c2410wdt_probe(struct device
*dev
)
374 struct platform_device
*pdev
= to_platform_device(dev
);
375 struct resource
*res
;
380 DBG("%s: probe=%p, device=%p\n", __FUNCTION__
, pdev
, dev
);
382 /* get the memory region for the watchdog timer */
384 res
= platform_get_resource(pdev
, IORESOURCE_MEM
, 0);
386 printk(KERN_INFO PFX
"failed to get memory region resouce\n");
390 size
= (res
->end
-res
->start
)+1;
391 wdt_mem
= request_mem_region(res
->start
, size
, pdev
->name
);
392 if (wdt_mem
== NULL
) {
393 printk(KERN_INFO PFX
"failed to get memory region\n");
397 wdt_base
= ioremap(res
->start
, size
);
399 printk(KERN_INFO PFX
"failed to ioremap() region\n");
403 DBG("probe: mapped wdt_base=%p\n", wdt_base
);
405 res
= platform_get_resource(pdev
, IORESOURCE_IRQ
, 0);
407 printk(KERN_INFO PFX
"failed to get irq resource\n");
411 ret
= request_irq(res
->start
, s3c2410wdt_irq
, 0, pdev
->name
, dev
);
413 printk(KERN_INFO PFX
"failed to install irq (%d)\n", ret
);
417 wdt_clock
= clk_get(dev
, "watchdog");
418 if (wdt_clock
== NULL
) {
419 printk(KERN_INFO PFX
"failed to find watchdog clock source\n");
424 clk_enable(wdt_clock
);
426 /* see if we can actually set the requested timer margin, and if
427 * not, try the default value */
429 if (s3c2410wdt_set_heartbeat(tmr_margin
)) {
430 started
= s3c2410wdt_set_heartbeat(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME
);
433 printk(KERN_INFO PFX
"tmr_margin value out of range, default %d used\n",
434 CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME
);
436 printk(KERN_INFO PFX
"default timer value is out of range, cannot start\n");
440 ret
= register_reboot_notifier(&s3c2410wdt_notifier
);
442 printk (KERN_ERR PFX
"cannot register reboot notifier (%d)\n",
447 ret
= misc_register(&s3c2410wdt_miscdev
);
449 printk (KERN_ERR PFX
"cannot register miscdev on minor=%d (%d)\n",
450 WATCHDOG_MINOR
, ret
);
451 unregister_reboot_notifier(&s3c2410wdt_notifier
);
455 if (tmr_atboot
&& started
== 0) {
456 printk(KERN_INFO PFX
"Starting Watchdog Timer\n");
463 static int s3c2410wdt_remove(struct device
*dev
)
465 if (wdt_mem
!= NULL
) {
466 release_resource(wdt_mem
);
471 if (wdt_irq
!= NULL
) {
472 free_irq(wdt_irq
->start
, dev
);
476 if (wdt_clock
!= NULL
) {
477 clk_disable(wdt_clock
);
478 clk_unuse(wdt_clock
);
483 misc_deregister(&s3c2410wdt_miscdev
);
487 static struct device_driver s3c2410wdt_driver
= {
488 .name
= "s3c2410-wdt",
489 .bus
= &platform_bus_type
,
490 .probe
= s3c2410wdt_probe
,
491 .remove
= s3c2410wdt_remove
,
496 static char banner
[] __initdata
= KERN_INFO
"S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n";
498 static int __init
watchdog_init(void)
501 return driver_register(&s3c2410wdt_driver
);
504 static void __exit
watchdog_exit(void)
506 driver_unregister(&s3c2410wdt_driver
);
507 unregister_reboot_notifier(&s3c2410wdt_notifier
);
510 module_init(watchdog_init
);
511 module_exit(watchdog_exit
);
513 MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
514 MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver");
515 MODULE_LICENSE("GPL");
516 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR
);