2 * Copyright (c) 2009 Nuvoton technology corporation.
4 * Wan ZongShun <mcuos.com@gmail.com>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation;version 2 of the License.
12 #include <linux/bitops.h>
13 #include <linux/errno.h>
15 #include <linux/init.h>
17 #include <linux/clk.h>
18 #include <linux/kernel.h>
19 #include <linux/miscdevice.h>
20 #include <linux/module.h>
21 #include <linux/moduleparam.h>
22 #include <linux/platform_device.h>
23 #include <linux/slab.h>
24 #include <linux/interrupt.h>
25 #include <linux/types.h>
26 #include <linux/watchdog.h>
27 #include <linux/uaccess.h>
30 #define WTCLK (0x01 << 10)
31 #define WTE (0x01 << 7) /*wdt enable*/
32 #define WTIS (0x03 << 4)
33 #define WTIF (0x01 << 3)
34 #define WTRF (0x01 << 2)
35 #define WTRE (0x01 << 1)
36 #define WTR (0x01 << 0)
38 * The watchdog time interval can be calculated via following formula:
39 * WTIS real time interval (formula)
40 * 0x00 ((2^ 14 ) * ((external crystal freq) / 256))seconds
41 * 0x01 ((2^ 16 ) * ((external crystal freq) / 256))seconds
42 * 0x02 ((2^ 18 ) * ((external crystal freq) / 256))seconds
43 * 0x03 ((2^ 20 ) * ((external crystal freq) / 256))seconds
45 * The external crystal freq is 15Mhz in the nuc900 evaluation board.
46 * So 0x00 = +-0.28 seconds, 0x01 = +-1.12 seconds, 0x02 = +-4.48 seconds,
47 * 0x03 = +- 16.92 seconds..
49 #define WDT_HW_TIMEOUT 0x02
50 #define WDT_TIMEOUT (HZ/2)
51 #define WDT_HEARTBEAT 15
53 static int heartbeat
= WDT_HEARTBEAT
;
54 module_param(heartbeat
, int, 0);
55 MODULE_PARM_DESC(heartbeat
, "Watchdog heartbeats in seconds. "
56 "(default = " __MODULE_STRING(WDT_HEARTBEAT
) ")");
58 static bool nowayout
= WATCHDOG_NOWAYOUT
;
59 module_param(nowayout
, bool, 0);
60 MODULE_PARM_DESC(nowayout
, "Watchdog cannot be stopped once started "
61 "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT
) ")");
64 struct clk
*wdt_clock
;
65 struct platform_device
*pdev
;
66 void __iomem
*wdt_base
;
68 struct timer_list timer
;
70 unsigned long next_heartbeat
;
73 static unsigned long nuc900wdt_busy
;
74 static struct nuc900_wdt
*nuc900_wdt
;
76 static inline void nuc900_wdt_keepalive(void)
80 spin_lock(&nuc900_wdt
->wdt_lock
);
82 val
= __raw_readl(nuc900_wdt
->wdt_base
+ REG_WTCR
);
84 __raw_writel(val
, nuc900_wdt
->wdt_base
+ REG_WTCR
);
86 spin_unlock(&nuc900_wdt
->wdt_lock
);
89 static inline void nuc900_wdt_start(void)
93 spin_lock(&nuc900_wdt
->wdt_lock
);
95 val
= __raw_readl(nuc900_wdt
->wdt_base
+ REG_WTCR
);
96 val
|= (WTRE
| WTE
| WTR
| WTCLK
| WTIF
);
98 val
|= (WDT_HW_TIMEOUT
<< 0x04);
99 __raw_writel(val
, nuc900_wdt
->wdt_base
+ REG_WTCR
);
101 spin_unlock(&nuc900_wdt
->wdt_lock
);
103 nuc900_wdt
->next_heartbeat
= jiffies
+ heartbeat
* HZ
;
104 mod_timer(&nuc900_wdt
->timer
, jiffies
+ WDT_TIMEOUT
);
107 static inline void nuc900_wdt_stop(void)
111 del_timer(&nuc900_wdt
->timer
);
113 spin_lock(&nuc900_wdt
->wdt_lock
);
115 val
= __raw_readl(nuc900_wdt
->wdt_base
+ REG_WTCR
);
117 __raw_writel(val
, nuc900_wdt
->wdt_base
+ REG_WTCR
);
119 spin_unlock(&nuc900_wdt
->wdt_lock
);
122 static inline void nuc900_wdt_ping(void)
124 nuc900_wdt
->next_heartbeat
= jiffies
+ heartbeat
* HZ
;
127 static int nuc900_wdt_open(struct inode
*inode
, struct file
*file
)
130 if (test_and_set_bit(0, &nuc900wdt_busy
))
135 return nonseekable_open(inode
, file
);
138 static int nuc900_wdt_close(struct inode
*inode
, struct file
*file
)
140 if (nuc900_wdt
->expect_close
== 42)
143 dev_crit(&nuc900_wdt
->pdev
->dev
,
144 "Unexpected close, not stopping watchdog!\n");
148 nuc900_wdt
->expect_close
= 0;
149 clear_bit(0, &nuc900wdt_busy
);
153 static const struct watchdog_info nuc900_wdt_info
= {
154 .identity
= "nuc900 watchdog",
155 .options
= WDIOF_SETTIMEOUT
| WDIOF_KEEPALIVEPING
|
159 static long nuc900_wdt_ioctl(struct file
*file
,
160 unsigned int cmd
, unsigned long arg
)
162 void __user
*argp
= (void __user
*)arg
;
163 int __user
*p
= argp
;
167 case WDIOC_GETSUPPORT
:
168 return copy_to_user(argp
, &nuc900_wdt_info
,
169 sizeof(nuc900_wdt_info
)) ? -EFAULT
: 0;
170 case WDIOC_GETSTATUS
:
171 case WDIOC_GETBOOTSTATUS
:
172 return put_user(0, p
);
174 case WDIOC_KEEPALIVE
:
178 case WDIOC_SETTIMEOUT
:
179 if (get_user(new_value
, p
))
182 heartbeat
= new_value
;
185 return put_user(new_value
, p
);
186 case WDIOC_GETTIMEOUT
:
187 return put_user(heartbeat
, p
);
193 static ssize_t
nuc900_wdt_write(struct file
*file
, const char __user
*data
,
194 size_t len
, loff_t
*ppos
)
199 /* Scan for magic character */
203 nuc900_wdt
->expect_close
= 0;
205 for (i
= 0; i
< len
; i
++) {
207 if (get_user(c
, data
+ i
))
210 nuc900_wdt
->expect_close
= 42;
220 static void nuc900_wdt_timer_ping(unsigned long data
)
222 if (time_before(jiffies
, nuc900_wdt
->next_heartbeat
)) {
223 nuc900_wdt_keepalive();
224 mod_timer(&nuc900_wdt
->timer
, jiffies
+ WDT_TIMEOUT
);
226 dev_warn(&nuc900_wdt
->pdev
->dev
, "Will reset the machine !\n");
229 static const struct file_operations nuc900wdt_fops
= {
230 .owner
= THIS_MODULE
,
232 .unlocked_ioctl
= nuc900_wdt_ioctl
,
233 .open
= nuc900_wdt_open
,
234 .release
= nuc900_wdt_close
,
235 .write
= nuc900_wdt_write
,
238 static struct miscdevice nuc900wdt_miscdev
= {
239 .minor
= WATCHDOG_MINOR
,
241 .fops
= &nuc900wdt_fops
,
244 static int nuc900wdt_probe(struct platform_device
*pdev
)
246 struct resource
*res
;
249 nuc900_wdt
= devm_kzalloc(&pdev
->dev
, sizeof(*nuc900_wdt
),
254 nuc900_wdt
->pdev
= pdev
;
256 spin_lock_init(&nuc900_wdt
->wdt_lock
);
258 res
= platform_get_resource(pdev
, IORESOURCE_MEM
, 0);
259 nuc900_wdt
->wdt_base
= devm_ioremap_resource(&pdev
->dev
, res
);
260 if (IS_ERR(nuc900_wdt
->wdt_base
))
261 return PTR_ERR(nuc900_wdt
->wdt_base
);
263 nuc900_wdt
->wdt_clock
= devm_clk_get(&pdev
->dev
, NULL
);
264 if (IS_ERR(nuc900_wdt
->wdt_clock
)) {
265 dev_err(&pdev
->dev
, "failed to find watchdog clock source\n");
266 return PTR_ERR(nuc900_wdt
->wdt_clock
);
269 clk_enable(nuc900_wdt
->wdt_clock
);
271 setup_timer(&nuc900_wdt
->timer
, nuc900_wdt_timer_ping
, 0);
273 ret
= misc_register(&nuc900wdt_miscdev
);
275 dev_err(&pdev
->dev
, "err register miscdev on minor=%d (%d)\n",
276 WATCHDOG_MINOR
, ret
);
283 clk_disable(nuc900_wdt
->wdt_clock
);
287 static int nuc900wdt_remove(struct platform_device
*pdev
)
289 misc_deregister(&nuc900wdt_miscdev
);
291 clk_disable(nuc900_wdt
->wdt_clock
);
296 static struct platform_driver nuc900wdt_driver
= {
297 .probe
= nuc900wdt_probe
,
298 .remove
= nuc900wdt_remove
,
300 .name
= "nuc900-wdt",
301 .owner
= THIS_MODULE
,
305 module_platform_driver(nuc900wdt_driver
);
307 MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>");
308 MODULE_DESCRIPTION("Watchdog driver for NUC900");
309 MODULE_LICENSE("GPL");
310 MODULE_ALIAS("platform:nuc900-wdt");