diff options
Diffstat (limited to 'drivers/watchdog/pnx4008_wdt.c')
-rw-r--r-- | drivers/watchdog/pnx4008_wdt.c | 358 |
1 files changed, 358 insertions, 0 deletions
diff --git a/drivers/watchdog/pnx4008_wdt.c b/drivers/watchdog/pnx4008_wdt.c new file mode 100644 index 000000000000..22f8873dd092 --- /dev/null +++ b/drivers/watchdog/pnx4008_wdt.c | |||
@@ -0,0 +1,358 @@ | |||
1 | /* | ||
2 | * drivers/char/watchdog/pnx4008_wdt.c | ||
3 | * | ||
4 | * Watchdog driver for PNX4008 board | ||
5 | * | ||
6 | * Authors: Dmitry Chigirev <source@mvista.com>, | ||
7 | * Vitaly Wool <vitalywool@gmail.com> | ||
8 | * Based on sa1100 driver, | ||
9 | * Copyright (C) 2000 Oleg Drokin <green@crimea.edu> | ||
10 | * | ||
11 | * 2005-2006 (c) MontaVista Software, Inc. This file is licensed under | ||
12 | * the terms of the GNU General Public License version 2. This program | ||
13 | * is licensed "as is" without any warranty of any kind, whether express | ||
14 | * or implied. | ||
15 | */ | ||
16 | |||
17 | #include <linux/module.h> | ||
18 | #include <linux/moduleparam.h> | ||
19 | #include <linux/types.h> | ||
20 | #include <linux/kernel.h> | ||
21 | #include <linux/fs.h> | ||
22 | #include <linux/miscdevice.h> | ||
23 | #include <linux/watchdog.h> | ||
24 | #include <linux/init.h> | ||
25 | #include <linux/bitops.h> | ||
26 | #include <linux/ioport.h> | ||
27 | #include <linux/device.h> | ||
28 | #include <linux/platform_device.h> | ||
29 | #include <linux/clk.h> | ||
30 | #include <linux/spinlock.h> | ||
31 | |||
32 | #include <asm/hardware.h> | ||
33 | #include <asm/uaccess.h> | ||
34 | #include <asm/io.h> | ||
35 | |||
36 | #define MODULE_NAME "PNX4008-WDT: " | ||
37 | |||
38 | /* WatchDog Timer - Chapter 23 Page 207 */ | ||
39 | |||
40 | #define DEFAULT_HEARTBEAT 19 | ||
41 | #define MAX_HEARTBEAT 60 | ||
42 | |||
43 | /* Watchdog timer register set definition */ | ||
44 | #define WDTIM_INT(p) ((p) + 0x0) | ||
45 | #define WDTIM_CTRL(p) ((p) + 0x4) | ||
46 | #define WDTIM_COUNTER(p) ((p) + 0x8) | ||
47 | #define WDTIM_MCTRL(p) ((p) + 0xC) | ||
48 | #define WDTIM_MATCH0(p) ((p) + 0x10) | ||
49 | #define WDTIM_EMR(p) ((p) + 0x14) | ||
50 | #define WDTIM_PULSE(p) ((p) + 0x18) | ||
51 | #define WDTIM_RES(p) ((p) + 0x1C) | ||
52 | |||
53 | /* WDTIM_INT bit definitions */ | ||
54 | #define MATCH_INT 1 | ||
55 | |||
56 | /* WDTIM_CTRL bit definitions */ | ||
57 | #define COUNT_ENAB 1 | ||
58 | #define RESET_COUNT (1<<1) | ||
59 | #define DEBUG_EN (1<<2) | ||
60 | |||
61 | /* WDTIM_MCTRL bit definitions */ | ||
62 | #define MR0_INT 1 | ||
63 | #undef RESET_COUNT0 | ||
64 | #define RESET_COUNT0 (1<<2) | ||
65 | #define STOP_COUNT0 (1<<2) | ||
66 | #define M_RES1 (1<<3) | ||
67 | #define M_RES2 (1<<4) | ||
68 | #define RESFRC1 (1<<5) | ||
69 | #define RESFRC2 (1<<6) | ||
70 | |||
71 | /* WDTIM_EMR bit definitions */ | ||
72 | #define EXT_MATCH0 1 | ||
73 | #define MATCH_OUTPUT_HIGH (2<<4) /*a MATCH_CTRL setting */ | ||
74 | |||
75 | /* WDTIM_RES bit definitions */ | ||
76 | #define WDOG_RESET 1 /* read only */ | ||
77 | |||
78 | #define WDOG_COUNTER_RATE 13000000 /*the counter clock is 13 MHz fixed */ | ||
79 | |||
80 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
81 | static int heartbeat = DEFAULT_HEARTBEAT; | ||
82 | |||
83 | static spinlock_t io_lock; | ||
84 | static unsigned long wdt_status; | ||
85 | #define WDT_IN_USE 0 | ||
86 | #define WDT_OK_TO_CLOSE 1 | ||
87 | #define WDT_REGION_INITED 2 | ||
88 | #define WDT_DEVICE_INITED 3 | ||
89 | |||
90 | static unsigned long boot_status; | ||
91 | |||
92 | static struct resource *wdt_mem; | ||
93 | static void __iomem *wdt_base; | ||
94 | struct clk *wdt_clk; | ||
95 | |||
96 | static void wdt_enable(void) | ||
97 | { | ||
98 | spin_lock(&io_lock); | ||
99 | |||
100 | if (wdt_clk) | ||
101 | clk_set_rate(wdt_clk, 1); | ||
102 | |||
103 | /* stop counter, initiate counter reset */ | ||
104 | __raw_writel(RESET_COUNT, WDTIM_CTRL(wdt_base)); | ||
105 | /*wait for reset to complete. 100% guarantee event */ | ||
106 | while (__raw_readl(WDTIM_COUNTER(wdt_base))) | ||
107 | cpu_relax(); | ||
108 | /* internal and external reset, stop after that */ | ||
109 | __raw_writel(M_RES2 | STOP_COUNT0 | RESET_COUNT0, | ||
110 | WDTIM_MCTRL(wdt_base)); | ||
111 | /* configure match output */ | ||
112 | __raw_writel(MATCH_OUTPUT_HIGH, WDTIM_EMR(wdt_base)); | ||
113 | /* clear interrupt, just in case */ | ||
114 | __raw_writel(MATCH_INT, WDTIM_INT(wdt_base)); | ||
115 | /* the longest pulse period 65541/(13*10^6) seconds ~ 5 ms. */ | ||
116 | __raw_writel(0xFFFF, WDTIM_PULSE(wdt_base)); | ||
117 | __raw_writel(heartbeat * WDOG_COUNTER_RATE, WDTIM_MATCH0(wdt_base)); | ||
118 | /*enable counter, stop when debugger active */ | ||
119 | __raw_writel(COUNT_ENAB | DEBUG_EN, WDTIM_CTRL(wdt_base)); | ||
120 | |||
121 | spin_unlock(&io_lock); | ||
122 | } | ||
123 | |||
124 | static void wdt_disable(void) | ||
125 | { | ||
126 | spin_lock(&io_lock); | ||
127 | |||
128 | __raw_writel(0, WDTIM_CTRL(wdt_base)); /*stop counter */ | ||
129 | if (wdt_clk) | ||
130 | clk_set_rate(wdt_clk, 0); | ||
131 | |||
132 | spin_unlock(&io_lock); | ||
133 | } | ||
134 | |||
135 | static int pnx4008_wdt_open(struct inode *inode, struct file *file) | ||
136 | { | ||
137 | if (test_and_set_bit(WDT_IN_USE, &wdt_status)) | ||
138 | return -EBUSY; | ||
139 | |||
140 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
141 | |||
142 | wdt_enable(); | ||
143 | |||
144 | return nonseekable_open(inode, file); | ||
145 | } | ||
146 | |||
147 | static ssize_t | ||
148 | pnx4008_wdt_write(struct file *file, const char *data, size_t len, | ||
149 | loff_t * ppos) | ||
150 | { | ||
151 | if (len) { | ||
152 | if (!nowayout) { | ||
153 | size_t i; | ||
154 | |||
155 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
156 | |||
157 | for (i = 0; i != len; i++) { | ||
158 | char c; | ||
159 | |||
160 | if (get_user(c, data + i)) | ||
161 | return -EFAULT; | ||
162 | if (c == 'V') | ||
163 | set_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
164 | } | ||
165 | } | ||
166 | wdt_enable(); | ||
167 | } | ||
168 | |||
169 | return len; | ||
170 | } | ||
171 | |||
172 | static struct watchdog_info ident = { | ||
173 | .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | | ||
174 | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, | ||
175 | .identity = "PNX4008 Watchdog", | ||
176 | }; | ||
177 | |||
178 | static int | ||
179 | pnx4008_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, | ||
180 | unsigned long arg) | ||
181 | { | ||
182 | int ret = -ENOTTY; | ||
183 | int time; | ||
184 | |||
185 | switch (cmd) { | ||
186 | case WDIOC_GETSUPPORT: | ||
187 | ret = copy_to_user((struct watchdog_info *)arg, &ident, | ||
188 | sizeof(ident)) ? -EFAULT : 0; | ||
189 | break; | ||
190 | |||
191 | case WDIOC_GETSTATUS: | ||
192 | ret = put_user(0, (int *)arg); | ||
193 | break; | ||
194 | |||
195 | case WDIOC_GETBOOTSTATUS: | ||
196 | ret = put_user(boot_status, (int *)arg); | ||
197 | break; | ||
198 | |||
199 | case WDIOC_SETTIMEOUT: | ||
200 | ret = get_user(time, (int *)arg); | ||
201 | if (ret) | ||
202 | break; | ||
203 | |||
204 | if (time <= 0 || time > MAX_HEARTBEAT) { | ||
205 | ret = -EINVAL; | ||
206 | break; | ||
207 | } | ||
208 | |||
209 | heartbeat = time; | ||
210 | wdt_enable(); | ||
211 | /* Fall through */ | ||
212 | |||
213 | case WDIOC_GETTIMEOUT: | ||
214 | ret = put_user(heartbeat, (int *)arg); | ||
215 | break; | ||
216 | |||
217 | case WDIOC_KEEPALIVE: | ||
218 | wdt_enable(); | ||
219 | ret = 0; | ||
220 | break; | ||
221 | } | ||
222 | return ret; | ||
223 | } | ||
224 | |||
225 | static int pnx4008_wdt_release(struct inode *inode, struct file *file) | ||
226 | { | ||
227 | if (!test_bit(WDT_OK_TO_CLOSE, &wdt_status)) | ||
228 | printk(KERN_WARNING "WATCHDOG: Device closed unexpectdly\n"); | ||
229 | |||
230 | wdt_disable(); | ||
231 | clear_bit(WDT_IN_USE, &wdt_status); | ||
232 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
233 | |||
234 | return 0; | ||
235 | } | ||
236 | |||
237 | static const struct file_operations pnx4008_wdt_fops = { | ||
238 | .owner = THIS_MODULE, | ||
239 | .llseek = no_llseek, | ||
240 | .write = pnx4008_wdt_write, | ||
241 | .ioctl = pnx4008_wdt_ioctl, | ||
242 | .open = pnx4008_wdt_open, | ||
243 | .release = pnx4008_wdt_release, | ||
244 | }; | ||
245 | |||
246 | static struct miscdevice pnx4008_wdt_miscdev = { | ||
247 | .minor = WATCHDOG_MINOR, | ||
248 | .name = "watchdog", | ||
249 | .fops = &pnx4008_wdt_fops, | ||
250 | }; | ||
251 | |||
252 | static int pnx4008_wdt_probe(struct platform_device *pdev) | ||
253 | { | ||
254 | int ret = 0, size; | ||
255 | struct resource *res; | ||
256 | |||
257 | spin_lock_init(&io_lock); | ||
258 | |||
259 | if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) | ||
260 | heartbeat = DEFAULT_HEARTBEAT; | ||
261 | |||
262 | printk(KERN_INFO MODULE_NAME | ||
263 | "PNX4008 Watchdog Timer: heartbeat %d sec\n", heartbeat); | ||
264 | |||
265 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
266 | if (res == NULL) { | ||
267 | printk(KERN_INFO MODULE_NAME | ||
268 | "failed to get memory region resouce\n"); | ||
269 | return -ENOENT; | ||
270 | } | ||
271 | |||
272 | size = res->end - res->start + 1; | ||
273 | wdt_mem = request_mem_region(res->start, size, pdev->name); | ||
274 | |||
275 | if (wdt_mem == NULL) { | ||
276 | printk(KERN_INFO MODULE_NAME "failed to get memory region\n"); | ||
277 | return -ENOENT; | ||
278 | } | ||
279 | wdt_base = (void __iomem *)IO_ADDRESS(res->start); | ||
280 | |||
281 | wdt_clk = clk_get(&pdev->dev, "wdt_ck"); | ||
282 | if (IS_ERR(wdt_clk)) { | ||
283 | ret = PTR_ERR(wdt_clk); | ||
284 | release_resource(wdt_mem); | ||
285 | kfree(wdt_mem); | ||
286 | goto out; | ||
287 | } else | ||
288 | clk_set_rate(wdt_clk, 1); | ||
289 | |||
290 | ret = misc_register(&pnx4008_wdt_miscdev); | ||
291 | if (ret < 0) { | ||
292 | printk(KERN_ERR MODULE_NAME "cannot register misc device\n"); | ||
293 | release_resource(wdt_mem); | ||
294 | kfree(wdt_mem); | ||
295 | clk_set_rate(wdt_clk, 0); | ||
296 | } else { | ||
297 | boot_status = (__raw_readl(WDTIM_RES(wdt_base)) & WDOG_RESET) ? | ||
298 | WDIOF_CARDRESET : 0; | ||
299 | wdt_disable(); /*disable for now */ | ||
300 | set_bit(WDT_DEVICE_INITED, &wdt_status); | ||
301 | } | ||
302 | |||
303 | out: | ||
304 | return ret; | ||
305 | } | ||
306 | |||
307 | static int pnx4008_wdt_remove(struct platform_device *pdev) | ||
308 | { | ||
309 | misc_deregister(&pnx4008_wdt_miscdev); | ||
310 | if (wdt_clk) { | ||
311 | clk_set_rate(wdt_clk, 0); | ||
312 | clk_put(wdt_clk); | ||
313 | wdt_clk = NULL; | ||
314 | } | ||
315 | if (wdt_mem) { | ||
316 | release_resource(wdt_mem); | ||
317 | kfree(wdt_mem); | ||
318 | wdt_mem = NULL; | ||
319 | } | ||
320 | return 0; | ||
321 | } | ||
322 | |||
323 | static struct platform_driver platform_wdt_driver = { | ||
324 | .driver = { | ||
325 | .name = "watchdog", | ||
326 | }, | ||
327 | .probe = pnx4008_wdt_probe, | ||
328 | .remove = pnx4008_wdt_remove, | ||
329 | }; | ||
330 | |||
331 | static int __init pnx4008_wdt_init(void) | ||
332 | { | ||
333 | return platform_driver_register(&platform_wdt_driver); | ||
334 | } | ||
335 | |||
336 | static void __exit pnx4008_wdt_exit(void) | ||
337 | { | ||
338 | return platform_driver_unregister(&platform_wdt_driver); | ||
339 | } | ||
340 | |||
341 | module_init(pnx4008_wdt_init); | ||
342 | module_exit(pnx4008_wdt_exit); | ||
343 | |||
344 | MODULE_AUTHOR("MontaVista Software, Inc. <source@mvista.com>"); | ||
345 | MODULE_DESCRIPTION("PNX4008 Watchdog Driver"); | ||
346 | |||
347 | module_param(heartbeat, int, 0); | ||
348 | MODULE_PARM_DESC(heartbeat, | ||
349 | "Watchdog heartbeat period in seconds from 1 to " | ||
350 | __MODULE_STRING(MAX_HEARTBEAT) ", default " | ||
351 | __MODULE_STRING(DEFAULT_HEARTBEAT)); | ||
352 | |||
353 | module_param(nowayout, int, 0); | ||
354 | MODULE_PARM_DESC(nowayout, | ||
355 | "Set to 1 to keep watchdog running after device release"); | ||
356 | |||
357 | MODULE_LICENSE("GPL"); | ||
358 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||