diff options
Diffstat (limited to 'drivers/char/watchdog/bfin_wdt.c')
-rw-r--r-- | drivers/char/watchdog/bfin_wdt.c | 490 |
1 files changed, 490 insertions, 0 deletions
diff --git a/drivers/char/watchdog/bfin_wdt.c b/drivers/char/watchdog/bfin_wdt.c new file mode 100644 index 000000000000..309d27913fc1 --- /dev/null +++ b/drivers/char/watchdog/bfin_wdt.c | |||
@@ -0,0 +1,490 @@ | |||
1 | /* | ||
2 | * Blackfin On-Chip Watchdog Driver | ||
3 | * Supports BF53[123]/BF53[467]/BF54[2489]/BF561 | ||
4 | * | ||
5 | * Originally based on softdog.c | ||
6 | * Copyright 2006-2007 Analog Devices Inc. | ||
7 | * Copyright 2006-2007 Michele d'Amico | ||
8 | * Copyright 1996 Alan Cox <alan@redhat.com> | ||
9 | * | ||
10 | * Enter bugs at http://blackfin.uclinux.org/ | ||
11 | * | ||
12 | * Licensed under the GPL-2 or later. | ||
13 | */ | ||
14 | |||
15 | #include <linux/platform_device.h> | ||
16 | #include <linux/module.h> | ||
17 | #include <linux/moduleparam.h> | ||
18 | #include <linux/types.h> | ||
19 | #include <linux/timer.h> | ||
20 | #include <linux/miscdevice.h> | ||
21 | #include <linux/watchdog.h> | ||
22 | #include <linux/fs.h> | ||
23 | #include <linux/notifier.h> | ||
24 | #include <linux/reboot.h> | ||
25 | #include <linux/init.h> | ||
26 | #include <linux/interrupt.h> | ||
27 | #include <asm/blackfin.h> | ||
28 | #include <asm/uaccess.h> | ||
29 | |||
30 | #define stamp(fmt, args...) pr_debug("%s:%i: " fmt "\n", __func__, __LINE__, ## args) | ||
31 | #define stampit() stamp("here i am") | ||
32 | |||
33 | #define WATCHDOG_NAME "bfin-wdt" | ||
34 | #define PFX WATCHDOG_NAME ": " | ||
35 | |||
36 | /* The BF561 has two watchdogs (one per core), but since Linux | ||
37 | * only runs on core A, we'll just work with that one. | ||
38 | */ | ||
39 | #ifdef BF561_FAMILY | ||
40 | # define bfin_read_WDOG_CTL() bfin_read_WDOGA_CTL() | ||
41 | # define bfin_read_WDOG_CNT() bfin_read_WDOGA_CNT() | ||
42 | # define bfin_read_WDOG_STAT() bfin_read_WDOGA_STAT() | ||
43 | # define bfin_write_WDOG_CTL(x) bfin_write_WDOGA_CTL(x) | ||
44 | # define bfin_write_WDOG_CNT(x) bfin_write_WDOGA_CNT(x) | ||
45 | # define bfin_write_WDOG_STAT(x) bfin_write_WDOGA_STAT(x) | ||
46 | #endif | ||
47 | |||
48 | /* Bit in SWRST that indicates boot caused by watchdog */ | ||
49 | #define SWRST_RESET_WDOG 0x4000 | ||
50 | |||
51 | /* Bit in WDOG_CTL that indicates watchdog has expired (WDR0) */ | ||
52 | #define WDOG_EXPIRED 0x8000 | ||
53 | |||
54 | /* Masks for WDEV field in WDOG_CTL register */ | ||
55 | #define ICTL_RESET 0x0 | ||
56 | #define ICTL_NMI 0x2 | ||
57 | #define ICTL_GPI 0x4 | ||
58 | #define ICTL_NONE 0x6 | ||
59 | #define ICTL_MASK 0x6 | ||
60 | |||
61 | /* Masks for WDEN field in WDOG_CTL register */ | ||
62 | #define WDEN_MASK 0x0FF0 | ||
63 | #define WDEN_ENABLE 0x0000 | ||
64 | #define WDEN_DISABLE 0x0AD0 | ||
65 | |||
66 | /* some defaults */ | ||
67 | #define WATCHDOG_TIMEOUT 20 | ||
68 | |||
69 | static unsigned int timeout = WATCHDOG_TIMEOUT; | ||
70 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
71 | static struct watchdog_info bfin_wdt_info; | ||
72 | static unsigned long open_check; | ||
73 | static char expect_close; | ||
74 | static spinlock_t bfin_wdt_spinlock = SPIN_LOCK_UNLOCKED; | ||
75 | |||
76 | /** | ||
77 | * bfin_wdt_keepalive - Keep the Userspace Watchdog Alive | ||
78 | * | ||
79 | * The Userspace watchdog got a KeepAlive: schedule the next timeout. | ||
80 | */ | ||
81 | static int bfin_wdt_keepalive(void) | ||
82 | { | ||
83 | stampit(); | ||
84 | bfin_write_WDOG_STAT(0); | ||
85 | return 0; | ||
86 | } | ||
87 | |||
88 | /** | ||
89 | * bfin_wdt_stop - Stop the Watchdog | ||
90 | * | ||
91 | * Stops the on-chip watchdog. | ||
92 | */ | ||
93 | static int bfin_wdt_stop(void) | ||
94 | { | ||
95 | stampit(); | ||
96 | bfin_write_WDOG_CTL(WDEN_DISABLE); | ||
97 | return 0; | ||
98 | } | ||
99 | |||
100 | /** | ||
101 | * bfin_wdt_start - Start the Watchdog | ||
102 | * | ||
103 | * Starts the on-chip watchdog. Automatically loads WDOG_CNT | ||
104 | * into WDOG_STAT for us. | ||
105 | */ | ||
106 | static int bfin_wdt_start(void) | ||
107 | { | ||
108 | stampit(); | ||
109 | bfin_write_WDOG_CTL(WDEN_ENABLE | ICTL_RESET); | ||
110 | return 0; | ||
111 | } | ||
112 | |||
113 | /** | ||
114 | * bfin_wdt_running - Check Watchdog status | ||
115 | * | ||
116 | * See if the watchdog is running. | ||
117 | */ | ||
118 | static int bfin_wdt_running(void) | ||
119 | { | ||
120 | stampit(); | ||
121 | return ((bfin_read_WDOG_CTL() & WDEN_MASK) != WDEN_DISABLE); | ||
122 | } | ||
123 | |||
124 | /** | ||
125 | * bfin_wdt_set_timeout - Set the Userspace Watchdog timeout | ||
126 | * @t: new timeout value (in seconds) | ||
127 | * | ||
128 | * Translate the specified timeout in seconds into System Clock | ||
129 | * terms which is what the on-chip Watchdog requires. | ||
130 | */ | ||
131 | static int bfin_wdt_set_timeout(unsigned long t) | ||
132 | { | ||
133 | u32 cnt; | ||
134 | unsigned long flags; | ||
135 | |||
136 | stampit(); | ||
137 | |||
138 | cnt = t * get_sclk(); | ||
139 | if (cnt < get_sclk()) { | ||
140 | printk(KERN_WARNING PFX "timeout value is too large\n"); | ||
141 | return -EINVAL; | ||
142 | } | ||
143 | |||
144 | spin_lock_irqsave(&bfin_wdt_spinlock, flags); | ||
145 | { | ||
146 | int run = bfin_wdt_running(); | ||
147 | bfin_wdt_stop(); | ||
148 | bfin_write_WDOG_CNT(cnt); | ||
149 | if (run) bfin_wdt_start(); | ||
150 | } | ||
151 | spin_unlock_irqrestore(&bfin_wdt_spinlock, flags); | ||
152 | |||
153 | timeout = t; | ||
154 | |||
155 | return 0; | ||
156 | } | ||
157 | |||
158 | /** | ||
159 | * bfin_wdt_open - Open the Device | ||
160 | * @inode: inode of device | ||
161 | * @file: file handle of device | ||
162 | * | ||
163 | * Watchdog device is opened and started. | ||
164 | */ | ||
165 | static int bfin_wdt_open(struct inode *inode, struct file *file) | ||
166 | { | ||
167 | stampit(); | ||
168 | |||
169 | if (test_and_set_bit(0, &open_check)) | ||
170 | return -EBUSY; | ||
171 | |||
172 | if (nowayout) | ||
173 | __module_get(THIS_MODULE); | ||
174 | |||
175 | bfin_wdt_keepalive(); | ||
176 | bfin_wdt_start(); | ||
177 | |||
178 | return nonseekable_open(inode, file); | ||
179 | } | ||
180 | |||
181 | /** | ||
182 | * bfin_wdt_close - Close the Device | ||
183 | * @inode: inode of device | ||
184 | * @file: file handle of device | ||
185 | * | ||
186 | * Watchdog device is closed and stopped. | ||
187 | */ | ||
188 | static int bfin_wdt_release(struct inode *inode, struct file *file) | ||
189 | { | ||
190 | stampit(); | ||
191 | |||
192 | if (expect_close == 42) { | ||
193 | bfin_wdt_stop(); | ||
194 | } else { | ||
195 | printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); | ||
196 | bfin_wdt_keepalive(); | ||
197 | } | ||
198 | |||
199 | expect_close = 0; | ||
200 | clear_bit(0, &open_check); | ||
201 | |||
202 | return 0; | ||
203 | } | ||
204 | |||
205 | /** | ||
206 | * bfin_wdt_write - Write to Device | ||
207 | * @file: file handle of device | ||
208 | * @buf: buffer to write | ||
209 | * @count: length of buffer | ||
210 | * @ppos: offset | ||
211 | * | ||
212 | * Pings the watchdog on write. | ||
213 | */ | ||
214 | static ssize_t bfin_wdt_write(struct file *file, const char __user *data, | ||
215 | size_t len, loff_t *ppos) | ||
216 | { | ||
217 | stampit(); | ||
218 | |||
219 | if (len) { | ||
220 | if (!nowayout) { | ||
221 | size_t i; | ||
222 | |||
223 | /* In case it was set long ago */ | ||
224 | expect_close = 0; | ||
225 | |||
226 | for (i = 0; i != len; i++) { | ||
227 | char c; | ||
228 | if (get_user(c, data + i)) | ||
229 | return -EFAULT; | ||
230 | if (c == 'V') | ||
231 | expect_close = 42; | ||
232 | } | ||
233 | } | ||
234 | bfin_wdt_keepalive(); | ||
235 | } | ||
236 | |||
237 | return len; | ||
238 | } | ||
239 | |||
240 | /** | ||
241 | * bfin_wdt_ioctl - Query Device | ||
242 | * @inode: inode of device | ||
243 | * @file: file handle of device | ||
244 | * @cmd: watchdog command | ||
245 | * @arg: argument | ||
246 | * | ||
247 | * Query basic information from the device or ping it, as outlined by the | ||
248 | * watchdog API. | ||
249 | */ | ||
250 | static int bfin_wdt_ioctl(struct inode *inode, struct file *file, | ||
251 | unsigned int cmd, unsigned long arg) | ||
252 | { | ||
253 | void __user *argp = (void __user *)arg; | ||
254 | int __user *p = argp; | ||
255 | |||
256 | stampit(); | ||
257 | |||
258 | switch (cmd) { | ||
259 | default: | ||
260 | return -ENOTTY; | ||
261 | |||
262 | case WDIOC_GETSUPPORT: | ||
263 | if (copy_to_user(argp, &bfin_wdt_info, sizeof(bfin_wdt_info))) | ||
264 | return -EFAULT; | ||
265 | else | ||
266 | return 0; | ||
267 | |||
268 | case WDIOC_GETSTATUS: | ||
269 | case WDIOC_GETBOOTSTATUS: | ||
270 | return put_user(!!(_bfin_swrst & SWRST_RESET_WDOG), p); | ||
271 | |||
272 | case WDIOC_KEEPALIVE: | ||
273 | bfin_wdt_keepalive(); | ||
274 | return 0; | ||
275 | |||
276 | case WDIOC_SETTIMEOUT: { | ||
277 | int new_timeout; | ||
278 | |||
279 | if (get_user(new_timeout, p)) | ||
280 | return -EFAULT; | ||
281 | |||
282 | if (bfin_wdt_set_timeout(new_timeout)) | ||
283 | return -EINVAL; | ||
284 | } | ||
285 | /* Fall */ | ||
286 | case WDIOC_GETTIMEOUT: | ||
287 | return put_user(timeout, p); | ||
288 | |||
289 | case WDIOC_SETOPTIONS: { | ||
290 | unsigned long flags; | ||
291 | int options, ret = -EINVAL; | ||
292 | |||
293 | if (get_user(options, p)) | ||
294 | return -EFAULT; | ||
295 | |||
296 | spin_lock_irqsave(&bfin_wdt_spinlock, flags); | ||
297 | |||
298 | if (options & WDIOS_DISABLECARD) { | ||
299 | bfin_wdt_stop(); | ||
300 | ret = 0; | ||
301 | } | ||
302 | |||
303 | if (options & WDIOS_ENABLECARD) { | ||
304 | bfin_wdt_start(); | ||
305 | ret = 0; | ||
306 | } | ||
307 | |||
308 | spin_unlock_irqrestore(&bfin_wdt_spinlock, flags); | ||
309 | |||
310 | return ret; | ||
311 | } | ||
312 | } | ||
313 | } | ||
314 | |||
315 | /** | ||
316 | * bfin_wdt_notify_sys - Notifier Handler | ||
317 | * @this: notifier block | ||
318 | * @code: notifier event | ||
319 | * @unused: unused | ||
320 | * | ||
321 | * Handles specific events, such as turning off the watchdog during a | ||
322 | * shutdown event. | ||
323 | */ | ||
324 | static int bfin_wdt_notify_sys(struct notifier_block *this, unsigned long code, | ||
325 | void *unused) | ||
326 | { | ||
327 | stampit(); | ||
328 | |||
329 | if (code == SYS_DOWN || code == SYS_HALT) | ||
330 | bfin_wdt_stop(); | ||
331 | |||
332 | return NOTIFY_DONE; | ||
333 | } | ||
334 | |||
335 | #ifdef CONFIG_PM | ||
336 | static int state_before_suspend; | ||
337 | |||
338 | /** | ||
339 | * bfin_wdt_suspend - suspend the watchdog | ||
340 | * @pdev: device being suspended | ||
341 | * @state: requested suspend state | ||
342 | * | ||
343 | * Remember if the watchdog was running and stop it. | ||
344 | * TODO: is this even right? Doesn't seem to be any | ||
345 | * standard in the watchdog world ... | ||
346 | */ | ||
347 | static int bfin_wdt_suspend(struct platform_device *pdev, pm_message_t state) | ||
348 | { | ||
349 | stampit(); | ||
350 | |||
351 | state_before_suspend = bfin_wdt_running(); | ||
352 | bfin_wdt_stop(); | ||
353 | |||
354 | return 0; | ||
355 | } | ||
356 | |||
357 | /** | ||
358 | * bfin_wdt_resume - resume the watchdog | ||
359 | * @pdev: device being resumed | ||
360 | * | ||
361 | * If the watchdog was running, turn it back on. | ||
362 | */ | ||
363 | static int bfin_wdt_resume(struct platform_device *pdev) | ||
364 | { | ||
365 | stampit(); | ||
366 | |||
367 | if (state_before_suspend) { | ||
368 | bfin_wdt_set_timeout(timeout); | ||
369 | bfin_wdt_start(); | ||
370 | } | ||
371 | |||
372 | return 0; | ||
373 | } | ||
374 | #else | ||
375 | # define bfin_wdt_suspend NULL | ||
376 | # define bfin_wdt_resume NULL | ||
377 | #endif | ||
378 | |||
379 | static struct platform_device bfin_wdt_device = { | ||
380 | .name = WATCHDOG_NAME, | ||
381 | .id = -1, | ||
382 | }; | ||
383 | |||
384 | static struct platform_driver bfin_wdt_driver = { | ||
385 | .driver = { | ||
386 | .name = WATCHDOG_NAME, | ||
387 | .owner = THIS_MODULE, | ||
388 | }, | ||
389 | .suspend = bfin_wdt_suspend, | ||
390 | .resume = bfin_wdt_resume, | ||
391 | }; | ||
392 | |||
393 | static struct file_operations bfin_wdt_fops = { | ||
394 | .owner = THIS_MODULE, | ||
395 | .llseek = no_llseek, | ||
396 | .write = bfin_wdt_write, | ||
397 | .ioctl = bfin_wdt_ioctl, | ||
398 | .open = bfin_wdt_open, | ||
399 | .release = bfin_wdt_release, | ||
400 | }; | ||
401 | |||
402 | static struct miscdevice bfin_wdt_miscdev = { | ||
403 | .minor = WATCHDOG_MINOR, | ||
404 | .name = "watchdog", | ||
405 | .fops = &bfin_wdt_fops, | ||
406 | }; | ||
407 | |||
408 | static struct watchdog_info bfin_wdt_info = { | ||
409 | .identity = "Blackfin Watchdog", | ||
410 | .options = WDIOF_SETTIMEOUT | | ||
411 | WDIOF_KEEPALIVEPING | | ||
412 | WDIOF_MAGICCLOSE, | ||
413 | }; | ||
414 | |||
415 | static struct notifier_block bfin_wdt_notifier = { | ||
416 | .notifier_call = bfin_wdt_notify_sys, | ||
417 | }; | ||
418 | |||
419 | /** | ||
420 | * bfin_wdt_init - Initialize module | ||
421 | * | ||
422 | * Registers the device and notifier handler. Actual device | ||
423 | * initialization is handled by bfin_wdt_open(). | ||
424 | */ | ||
425 | static int __init bfin_wdt_init(void) | ||
426 | { | ||
427 | int ret; | ||
428 | |||
429 | stampit(); | ||
430 | |||
431 | /* Check that the timeout value is within range */ | ||
432 | if (bfin_wdt_set_timeout(timeout)) | ||
433 | return -EINVAL; | ||
434 | |||
435 | /* Since this is an on-chip device and needs no board-specific | ||
436 | * resources, we'll handle all the platform device stuff here. | ||
437 | */ | ||
438 | ret = platform_device_register(&bfin_wdt_device); | ||
439 | if (ret) | ||
440 | return ret; | ||
441 | |||
442 | ret = platform_driver_probe(&bfin_wdt_driver, NULL); | ||
443 | if (ret) | ||
444 | return ret; | ||
445 | |||
446 | ret = register_reboot_notifier(&bfin_wdt_notifier); | ||
447 | if (ret) { | ||
448 | printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", ret); | ||
449 | return ret; | ||
450 | } | ||
451 | |||
452 | ret = misc_register(&bfin_wdt_miscdev); | ||
453 | if (ret) { | ||
454 | printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", | ||
455 | WATCHDOG_MINOR, ret); | ||
456 | unregister_reboot_notifier(&bfin_wdt_notifier); | ||
457 | return ret; | ||
458 | } | ||
459 | |||
460 | printk(KERN_INFO PFX "initialized: timeout=%d sec (nowayout=%d)\n", | ||
461 | timeout, nowayout); | ||
462 | |||
463 | return 0; | ||
464 | } | ||
465 | |||
466 | /** | ||
467 | * bfin_wdt_exit - Deinitialize module | ||
468 | * | ||
469 | * Unregisters the device and notifier handler. Actual device | ||
470 | * deinitialization is handled by bfin_wdt_close(). | ||
471 | */ | ||
472 | static void __exit bfin_wdt_exit(void) | ||
473 | { | ||
474 | misc_deregister(&bfin_wdt_miscdev); | ||
475 | unregister_reboot_notifier(&bfin_wdt_notifier); | ||
476 | } | ||
477 | |||
478 | module_init(bfin_wdt_init); | ||
479 | module_exit(bfin_wdt_exit); | ||
480 | |||
481 | MODULE_AUTHOR("Michele d'Amico, Mike Frysinger <vapier@gentoo.org>"); | ||
482 | MODULE_DESCRIPTION("Blackfin Watchdog Device Driver"); | ||
483 | MODULE_LICENSE("GPL"); | ||
484 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
485 | |||
486 | module_param(timeout, uint, 0); | ||
487 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=((2^32)/SCLK), default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); | ||
488 | |||
489 | module_param(nowayout, int, 0); | ||
490 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||