Root/
1 | /* |
2 | * Intel_SCU 0.2: An Intel SCU IOH Based Watchdog Device |
3 | * for Intel part #(s): |
4 | * - AF82MP20 PCH |
5 | * |
6 | * Copyright (C) 2009-2010 Intel Corporation. All rights reserved. |
7 | * |
8 | * This program is free software; you can redistribute it and/or |
9 | * modify it under the terms of version 2 of the GNU General |
10 | * Public License as published by the Free Software Foundation. |
11 | * |
12 | * This program is distributed in the hope that it will be |
13 | * useful, but WITHOUT ANY WARRANTY; without even the implied |
14 | * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR |
15 | * PURPOSE. See the GNU General Public License for more details. |
16 | * You should have received a copy of the GNU General Public |
17 | * License along with this program; if not, write to the Free |
18 | * Software Foundation, Inc., 59 Temple Place - Suite 330, |
19 | * Boston, MA 02111-1307, USA. |
20 | * The full GNU General Public License is included in this |
21 | * distribution in the file called COPYING. |
22 | * |
23 | */ |
24 | |
25 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
26 | |
27 | #include <linux/compiler.h> |
28 | #include <linux/module.h> |
29 | #include <linux/kernel.h> |
30 | #include <linux/moduleparam.h> |
31 | #include <linux/types.h> |
32 | #include <linux/miscdevice.h> |
33 | #include <linux/watchdog.h> |
34 | #include <linux/fs.h> |
35 | #include <linux/notifier.h> |
36 | #include <linux/reboot.h> |
37 | #include <linux/init.h> |
38 | #include <linux/jiffies.h> |
39 | #include <linux/uaccess.h> |
40 | #include <linux/slab.h> |
41 | #include <linux/io.h> |
42 | #include <linux/interrupt.h> |
43 | #include <linux/delay.h> |
44 | #include <linux/sched.h> |
45 | #include <linux/signal.h> |
46 | #include <linux/sfi.h> |
47 | #include <asm/irq.h> |
48 | #include <linux/atomic.h> |
49 | #include <asm/intel_scu_ipc.h> |
50 | #include <asm/apb_timer.h> |
51 | #include <asm/mrst.h> |
52 | |
53 | #include "intel_scu_watchdog.h" |
54 | |
55 | /* Bounds number of times we will retry loading time count */ |
56 | /* This retry is a work around for a silicon bug. */ |
57 | #define MAX_RETRY 16 |
58 | |
59 | #define IPC_SET_WATCHDOG_TIMER 0xF8 |
60 | |
61 | static int timer_margin = DEFAULT_SOFT_TO_HARD_MARGIN; |
62 | module_param(timer_margin, int, 0); |
63 | MODULE_PARM_DESC(timer_margin, |
64 | "Watchdog timer margin" |
65 | "Time between interrupt and resetting the system" |
66 | "The range is from 1 to 160" |
67 | "This is the time for all keep alives to arrive"); |
68 | |
69 | static int timer_set = DEFAULT_TIME; |
70 | module_param(timer_set, int, 0); |
71 | MODULE_PARM_DESC(timer_set, |
72 | "Default Watchdog timer setting" |
73 | "Complete cycle time" |
74 | "The range is from 1 to 170" |
75 | "This is the time for all keep alives to arrive"); |
76 | |
77 | /* After watchdog device is closed, check force_boot. If: |
78 | * force_boot == 0, then force boot on next watchdog interrupt after close, |
79 | * force_boot == 1, then force boot immediately when device is closed. |
80 | */ |
81 | static int force_boot; |
82 | module_param(force_boot, int, 0); |
83 | MODULE_PARM_DESC(force_boot, |
84 | "A value of 1 means that the driver will reboot" |
85 | "the system immediately if the /dev/watchdog device is closed" |
86 | "A value of 0 means that when /dev/watchdog device is closed" |
87 | "the watchdog timer will be refreshed for one more interval" |
88 | "of length: timer_set. At the end of this interval, the" |
89 | "watchdog timer will reset the system." |
90 | ); |
91 | |
92 | /* there is only one device in the system now; this can be made into |
93 | * an array in the future if we have more than one device */ |
94 | |
95 | static struct intel_scu_watchdog_dev watchdog_device; |
96 | |
97 | /* Forces restart, if force_reboot is set */ |
98 | static void watchdog_fire(void) |
99 | { |
100 | if (force_boot) { |
101 | pr_crit("Initiating system reboot\n"); |
102 | emergency_restart(); |
103 | pr_crit("Reboot didn't ?????\n"); |
104 | } |
105 | |
106 | else { |
107 | pr_crit("Immediate Reboot Disabled\n"); |
108 | pr_crit("System will reset when watchdog timer times out!\n"); |
109 | } |
110 | } |
111 | |
112 | static int check_timer_margin(int new_margin) |
113 | { |
114 | if ((new_margin < MIN_TIME_CYCLE) || |
115 | (new_margin > MAX_TIME - timer_set)) { |
116 | pr_debug("value of new_margin %d is out of the range %d to %d\n", |
117 | new_margin, MIN_TIME_CYCLE, MAX_TIME - timer_set); |
118 | return -EINVAL; |
119 | } |
120 | return 0; |
121 | } |
122 | |
123 | /* |
124 | * IPC operations |
125 | */ |
126 | static int watchdog_set_ipc(int soft_threshold, int threshold) |
127 | { |
128 | u32 *ipc_wbuf; |
129 | u8 cbuf[16] = { '\0' }; |
130 | int ipc_ret = 0; |
131 | |
132 | ipc_wbuf = (u32 *)&cbuf; |
133 | ipc_wbuf[0] = soft_threshold; |
134 | ipc_wbuf[1] = threshold; |
135 | |
136 | ipc_ret = intel_scu_ipc_command( |
137 | IPC_SET_WATCHDOG_TIMER, |
138 | 0, |
139 | ipc_wbuf, |
140 | 2, |
141 | NULL, |
142 | 0); |
143 | |
144 | if (ipc_ret != 0) |
145 | pr_err("Error setting SCU watchdog timer: %x\n", ipc_ret); |
146 | |
147 | return ipc_ret; |
148 | }; |
149 | |
150 | /* |
151 | * Intel_SCU operations |
152 | */ |
153 | |
154 | /* timer interrupt handler */ |
155 | static irqreturn_t watchdog_timer_interrupt(int irq, void *dev_id) |
156 | { |
157 | int int_status; |
158 | int_status = ioread32(watchdog_device.timer_interrupt_status_addr); |
159 | |
160 | pr_debug("irq, int_status: %x\n", int_status); |
161 | |
162 | if (int_status != 0) |
163 | return IRQ_NONE; |
164 | |
165 | /* has the timer been started? If not, then this is spurious */ |
166 | if (watchdog_device.timer_started == 0) { |
167 | pr_debug("spurious interrupt received\n"); |
168 | return IRQ_HANDLED; |
169 | } |
170 | |
171 | /* temporarily disable the timer */ |
172 | iowrite32(0x00000002, watchdog_device.timer_control_addr); |
173 | |
174 | /* set the timer to the threshold */ |
175 | iowrite32(watchdog_device.threshold, |
176 | watchdog_device.timer_load_count_addr); |
177 | |
178 | /* allow the timer to run */ |
179 | iowrite32(0x00000003, watchdog_device.timer_control_addr); |
180 | |
181 | return IRQ_HANDLED; |
182 | } |
183 | |
184 | static int intel_scu_keepalive(void) |
185 | { |
186 | |
187 | /* read eoi register - clears interrupt */ |
188 | ioread32(watchdog_device.timer_clear_interrupt_addr); |
189 | |
190 | /* temporarily disable the timer */ |
191 | iowrite32(0x00000002, watchdog_device.timer_control_addr); |
192 | |
193 | /* set the timer to the soft_threshold */ |
194 | iowrite32(watchdog_device.soft_threshold, |
195 | watchdog_device.timer_load_count_addr); |
196 | |
197 | /* allow the timer to run */ |
198 | iowrite32(0x00000003, watchdog_device.timer_control_addr); |
199 | |
200 | return 0; |
201 | } |
202 | |
203 | static int intel_scu_stop(void) |
204 | { |
205 | iowrite32(0, watchdog_device.timer_control_addr); |
206 | return 0; |
207 | } |
208 | |
209 | static int intel_scu_set_heartbeat(u32 t) |
210 | { |
211 | int ipc_ret; |
212 | int retry_count; |
213 | u32 soft_value; |
214 | u32 hw_pre_value; |
215 | u32 hw_value; |
216 | |
217 | watchdog_device.timer_set = t; |
218 | watchdog_device.threshold = |
219 | timer_margin * watchdog_device.timer_tbl_ptr->freq_hz; |
220 | watchdog_device.soft_threshold = |
221 | (watchdog_device.timer_set - timer_margin) |
222 | * watchdog_device.timer_tbl_ptr->freq_hz; |
223 | |
224 | pr_debug("set_heartbeat: timer freq is %d\n", |
225 | watchdog_device.timer_tbl_ptr->freq_hz); |
226 | pr_debug("set_heartbeat: timer_set is %x (hex)\n", |
227 | watchdog_device.timer_set); |
228 | pr_debug("set_hearbeat: timer_margin is %x (hex)\n", timer_margin); |
229 | pr_debug("set_heartbeat: threshold is %x (hex)\n", |
230 | watchdog_device.threshold); |
231 | pr_debug("set_heartbeat: soft_threshold is %x (hex)\n", |
232 | watchdog_device.soft_threshold); |
233 | |
234 | /* Adjust thresholds by FREQ_ADJUSTMENT factor, to make the */ |
235 | /* watchdog timing come out right. */ |
236 | watchdog_device.threshold = |
237 | watchdog_device.threshold / FREQ_ADJUSTMENT; |
238 | watchdog_device.soft_threshold = |
239 | watchdog_device.soft_threshold / FREQ_ADJUSTMENT; |
240 | |
241 | /* temporarily disable the timer */ |
242 | iowrite32(0x00000002, watchdog_device.timer_control_addr); |
243 | |
244 | /* send the threshold and soft_threshold via IPC to the processor */ |
245 | ipc_ret = watchdog_set_ipc(watchdog_device.soft_threshold, |
246 | watchdog_device.threshold); |
247 | |
248 | if (ipc_ret != 0) { |
249 | /* Make sure the watchdog timer is stopped */ |
250 | intel_scu_stop(); |
251 | return ipc_ret; |
252 | } |
253 | |
254 | /* Soft Threshold set loop. Early versions of silicon did */ |
255 | /* not always set this count correctly. This loop checks */ |
256 | /* the value and retries if it was not set correctly. */ |
257 | |
258 | retry_count = 0; |
259 | soft_value = watchdog_device.soft_threshold & 0xFFFF0000; |
260 | do { |
261 | |
262 | /* Make sure timer is stopped */ |
263 | intel_scu_stop(); |
264 | |
265 | if (MAX_RETRY < retry_count++) { |
266 | /* Unable to set timer value */ |
267 | pr_err("Unable to set timer\n"); |
268 | return -ENODEV; |
269 | } |
270 | |
271 | /* set the timer to the soft threshold */ |
272 | iowrite32(watchdog_device.soft_threshold, |
273 | watchdog_device.timer_load_count_addr); |
274 | |
275 | /* read count value before starting timer */ |
276 | hw_pre_value = ioread32(watchdog_device.timer_load_count_addr); |
277 | hw_pre_value = hw_pre_value & 0xFFFF0000; |
278 | |
279 | /* Start the timer */ |
280 | iowrite32(0x00000003, watchdog_device.timer_control_addr); |
281 | |
282 | /* read the value the time loaded into its count reg */ |
283 | hw_value = ioread32(watchdog_device.timer_load_count_addr); |
284 | hw_value = hw_value & 0xFFFF0000; |
285 | |
286 | |
287 | } while (soft_value != hw_value); |
288 | |
289 | watchdog_device.timer_started = 1; |
290 | |
291 | return 0; |
292 | } |
293 | |
294 | /* |
295 | * /dev/watchdog handling |
296 | */ |
297 | |
298 | static int intel_scu_open(struct inode *inode, struct file *file) |
299 | { |
300 | |
301 | /* Set flag to indicate that watchdog device is open */ |
302 | if (test_and_set_bit(0, &watchdog_device.driver_open)) |
303 | return -EBUSY; |
304 | |
305 | /* Check for reopen of driver. Reopens are not allowed */ |
306 | if (watchdog_device.driver_closed) |
307 | return -EPERM; |
308 | |
309 | return nonseekable_open(inode, file); |
310 | } |
311 | |
312 | static int intel_scu_release(struct inode *inode, struct file *file) |
313 | { |
314 | /* |
315 | * This watchdog should not be closed, after the timer |
316 | * is started with the WDIPC_SETTIMEOUT ioctl |
317 | * If force_boot is set watchdog_fire() will cause an |
318 | * immediate reset. If force_boot is not set, the watchdog |
319 | * timer is refreshed for one more interval. At the end |
320 | * of that interval, the watchdog timer will reset the system. |
321 | */ |
322 | |
323 | if (!test_and_clear_bit(0, &watchdog_device.driver_open)) { |
324 | pr_debug("intel_scu_release, without open\n"); |
325 | return -ENOTTY; |
326 | } |
327 | |
328 | if (!watchdog_device.timer_started) { |
329 | /* Just close, since timer has not been started */ |
330 | pr_debug("closed, without starting timer\n"); |
331 | return 0; |
332 | } |
333 | |
334 | pr_crit("Unexpected close of /dev/watchdog!\n"); |
335 | |
336 | /* Since the timer was started, prevent future reopens */ |
337 | watchdog_device.driver_closed = 1; |
338 | |
339 | /* Refresh the timer for one more interval */ |
340 | intel_scu_keepalive(); |
341 | |
342 | /* Reboot system (if force_boot is set) */ |
343 | watchdog_fire(); |
344 | |
345 | /* We should only reach this point if force_boot is not set */ |
346 | return 0; |
347 | } |
348 | |
349 | static ssize_t intel_scu_write(struct file *file, |
350 | char const *data, |
351 | size_t len, |
352 | loff_t *ppos) |
353 | { |
354 | |
355 | if (watchdog_device.timer_started) |
356 | /* Watchdog already started, keep it alive */ |
357 | intel_scu_keepalive(); |
358 | else |
359 | /* Start watchdog with timer value set by init */ |
360 | intel_scu_set_heartbeat(watchdog_device.timer_set); |
361 | |
362 | return len; |
363 | } |
364 | |
365 | static long intel_scu_ioctl(struct file *file, |
366 | unsigned int cmd, |
367 | unsigned long arg) |
368 | { |
369 | void __user *argp = (void __user *)arg; |
370 | u32 __user *p = argp; |
371 | u32 new_margin; |
372 | |
373 | |
374 | static const struct watchdog_info ident = { |
375 | .options = WDIOF_SETTIMEOUT |
376 | | WDIOF_KEEPALIVEPING, |
377 | .firmware_version = 0, /* @todo Get from SCU via |
378 | ipc_get_scu_fw_version()? */ |
379 | .identity = "Intel_SCU IOH Watchdog" /* len < 32 */ |
380 | }; |
381 | |
382 | switch (cmd) { |
383 | case WDIOC_GETSUPPORT: |
384 | return copy_to_user(argp, |
385 | &ident, |
386 | sizeof(ident)) ? -EFAULT : 0; |
387 | case WDIOC_GETSTATUS: |
388 | case WDIOC_GETBOOTSTATUS: |
389 | return put_user(0, p); |
390 | case WDIOC_KEEPALIVE: |
391 | intel_scu_keepalive(); |
392 | |
393 | return 0; |
394 | case WDIOC_SETTIMEOUT: |
395 | if (get_user(new_margin, p)) |
396 | return -EFAULT; |
397 | |
398 | if (check_timer_margin(new_margin)) |
399 | return -EINVAL; |
400 | |
401 | if (intel_scu_set_heartbeat(new_margin)) |
402 | return -EINVAL; |
403 | return 0; |
404 | case WDIOC_GETTIMEOUT: |
405 | return put_user(watchdog_device.soft_threshold, p); |
406 | |
407 | default: |
408 | return -ENOTTY; |
409 | } |
410 | } |
411 | |
412 | /* |
413 | * Notifier for system down |
414 | */ |
415 | static int intel_scu_notify_sys(struct notifier_block *this, |
416 | unsigned long code, |
417 | void *another_unused) |
418 | { |
419 | if (code == SYS_DOWN || code == SYS_HALT) |
420 | /* Turn off the watchdog timer. */ |
421 | intel_scu_stop(); |
422 | return NOTIFY_DONE; |
423 | } |
424 | |
425 | /* |
426 | * Kernel Interfaces |
427 | */ |
428 | static const struct file_operations intel_scu_fops = { |
429 | .owner = THIS_MODULE, |
430 | .llseek = no_llseek, |
431 | .write = intel_scu_write, |
432 | .unlocked_ioctl = intel_scu_ioctl, |
433 | .open = intel_scu_open, |
434 | .release = intel_scu_release, |
435 | }; |
436 | |
437 | static int __init intel_scu_watchdog_init(void) |
438 | { |
439 | int ret; |
440 | u32 __iomem *tmp_addr; |
441 | |
442 | /* |
443 | * We don't really need to check this as the SFI timer get will fail |
444 | * but if we do so we can exit with a clearer reason and no noise. |
445 | * |
446 | * If it isn't an intel MID device then it doesn't have this watchdog |
447 | */ |
448 | if (!mrst_identify_cpu()) |
449 | return -ENODEV; |
450 | |
451 | /* Check boot parameters to verify that their initial values */ |
452 | /* are in range. */ |
453 | /* Check value of timer_set boot parameter */ |
454 | if ((timer_set < MIN_TIME_CYCLE) || |
455 | (timer_set > MAX_TIME - MIN_TIME_CYCLE)) { |
456 | pr_err("value of timer_set %x (hex) is out of range from %x to %x (hex)\n", |
457 | timer_set, MIN_TIME_CYCLE, MAX_TIME - MIN_TIME_CYCLE); |
458 | return -EINVAL; |
459 | } |
460 | |
461 | /* Check value of timer_margin boot parameter */ |
462 | if (check_timer_margin(timer_margin)) |
463 | return -EINVAL; |
464 | |
465 | watchdog_device.timer_tbl_ptr = sfi_get_mtmr(sfi_mtimer_num-1); |
466 | |
467 | if (watchdog_device.timer_tbl_ptr == NULL) { |
468 | pr_debug("timer is not available\n"); |
469 | return -ENODEV; |
470 | } |
471 | /* make sure the timer exists */ |
472 | if (watchdog_device.timer_tbl_ptr->phys_addr == 0) { |
473 | pr_debug("timer %d does not have valid physical memory\n", |
474 | sfi_mtimer_num); |
475 | return -ENODEV; |
476 | } |
477 | |
478 | if (watchdog_device.timer_tbl_ptr->irq == 0) { |
479 | pr_debug("timer %d invalid irq\n", sfi_mtimer_num); |
480 | return -ENODEV; |
481 | } |
482 | |
483 | tmp_addr = ioremap_nocache(watchdog_device.timer_tbl_ptr->phys_addr, |
484 | 20); |
485 | |
486 | if (tmp_addr == NULL) { |
487 | pr_debug("timer unable to ioremap\n"); |
488 | return -ENOMEM; |
489 | } |
490 | |
491 | watchdog_device.timer_load_count_addr = tmp_addr++; |
492 | watchdog_device.timer_current_value_addr = tmp_addr++; |
493 | watchdog_device.timer_control_addr = tmp_addr++; |
494 | watchdog_device.timer_clear_interrupt_addr = tmp_addr++; |
495 | watchdog_device.timer_interrupt_status_addr = tmp_addr++; |
496 | |
497 | /* Set the default time values in device structure */ |
498 | |
499 | watchdog_device.timer_set = timer_set; |
500 | watchdog_device.threshold = |
501 | timer_margin * watchdog_device.timer_tbl_ptr->freq_hz; |
502 | watchdog_device.soft_threshold = |
503 | (watchdog_device.timer_set - timer_margin) |
504 | * watchdog_device.timer_tbl_ptr->freq_hz; |
505 | |
506 | |
507 | watchdog_device.intel_scu_notifier.notifier_call = |
508 | intel_scu_notify_sys; |
509 | |
510 | ret = register_reboot_notifier(&watchdog_device.intel_scu_notifier); |
511 | if (ret) { |
512 | pr_err("cannot register notifier %d)\n", ret); |
513 | goto register_reboot_error; |
514 | } |
515 | |
516 | watchdog_device.miscdev.minor = WATCHDOG_MINOR; |
517 | watchdog_device.miscdev.name = "watchdog"; |
518 | watchdog_device.miscdev.fops = &intel_scu_fops; |
519 | |
520 | ret = misc_register(&watchdog_device.miscdev); |
521 | if (ret) { |
522 | pr_err("cannot register miscdev %d err =%d\n", |
523 | WATCHDOG_MINOR, ret); |
524 | goto misc_register_error; |
525 | } |
526 | |
527 | ret = request_irq((unsigned int)watchdog_device.timer_tbl_ptr->irq, |
528 | watchdog_timer_interrupt, |
529 | IRQF_SHARED, "watchdog", |
530 | &watchdog_device.timer_load_count_addr); |
531 | if (ret) { |
532 | pr_err("error requesting irq %d\n", ret); |
533 | goto request_irq_error; |
534 | } |
535 | /* Make sure timer is disabled before returning */ |
536 | intel_scu_stop(); |
537 | return 0; |
538 | |
539 | /* error cleanup */ |
540 | |
541 | request_irq_error: |
542 | misc_deregister(&watchdog_device.miscdev); |
543 | misc_register_error: |
544 | unregister_reboot_notifier(&watchdog_device.intel_scu_notifier); |
545 | register_reboot_error: |
546 | intel_scu_stop(); |
547 | iounmap(watchdog_device.timer_load_count_addr); |
548 | return ret; |
549 | } |
550 | |
551 | static void __exit intel_scu_watchdog_exit(void) |
552 | { |
553 | |
554 | misc_deregister(&watchdog_device.miscdev); |
555 | unregister_reboot_notifier(&watchdog_device.intel_scu_notifier); |
556 | /* disable the timer */ |
557 | iowrite32(0x00000002, watchdog_device.timer_control_addr); |
558 | iounmap(watchdog_device.timer_load_count_addr); |
559 | } |
560 | |
561 | late_initcall(intel_scu_watchdog_init); |
562 | module_exit(intel_scu_watchdog_exit); |
563 | |
564 | MODULE_AUTHOR("Intel Corporation"); |
565 | MODULE_DESCRIPTION("Intel SCU Watchdog Device Driver"); |
566 | MODULE_LICENSE("GPL"); |
567 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); |
568 | MODULE_VERSION(WDT_VER); |
569 |
Branches:
ben-wpan
ben-wpan-stefan
javiroman/ks7010
jz-2.6.34
jz-2.6.34-rc5
jz-2.6.34-rc6
jz-2.6.34-rc7
jz-2.6.35
jz-2.6.36
jz-2.6.37
jz-2.6.38
jz-2.6.39
jz-3.0
jz-3.1
jz-3.11
jz-3.12
jz-3.13
jz-3.15
jz-3.16
jz-3.18-dt
jz-3.2
jz-3.3
jz-3.4
jz-3.5
jz-3.6
jz-3.6-rc2-pwm
jz-3.9
jz-3.9-clk
jz-3.9-rc8
jz47xx
jz47xx-2.6.38
master
Tags:
od-2011-09-04
od-2011-09-18
v2.6.34-rc5
v2.6.34-rc6
v2.6.34-rc7
v3.9