diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/watchdog/Kconfig | 43 | ||||
-rw-r--r-- | drivers/watchdog/Makefile | 4 | ||||
-rw-r--r-- | drivers/watchdog/at91sam9_wdt.c | 328 | ||||
-rw-r--r-- | drivers/watchdog/it87_wdt.c | 725 | ||||
-rw-r--r-- | drivers/watchdog/omap_wdt.c | 337 | ||||
-rw-r--r-- | drivers/watchdog/omap_wdt.h | 28 | ||||
-rw-r--r-- | drivers/watchdog/orion5x_wdt.c | 245 | ||||
-rw-r--r-- | drivers/watchdog/w83697ug_wdt.c | 392 |
8 files changed, 1970 insertions, 132 deletions
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index c51036716700..1a22fe782a27 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig | |||
@@ -66,6 +66,13 @@ config AT91RM9200_WATCHDOG | |||
66 | Watchdog timer embedded into AT91RM9200 chips. This will reboot your | 66 | Watchdog timer embedded into AT91RM9200 chips. This will reboot your |
67 | system when the timeout is reached. | 67 | system when the timeout is reached. |
68 | 68 | ||
69 | config AT91SAM9X_WATCHDOG | ||
70 | tristate "AT91SAM9X watchdog" | ||
71 | depends on WATCHDOG && (ARCH_AT91SAM9260 || ARCH_AT91SAM9261) | ||
72 | help | ||
73 | Watchdog timer embedded into AT91SAM9X chips. This will reboot your | ||
74 | system when the timeout is reached. | ||
75 | |||
69 | config 21285_WATCHDOG | 76 | config 21285_WATCHDOG |
70 | tristate "DC21285 watchdog" | 77 | tristate "DC21285 watchdog" |
71 | depends on FOOTBRIDGE | 78 | depends on FOOTBRIDGE |
@@ -217,6 +224,15 @@ config DAVINCI_WATCHDOG | |||
217 | NOTE: once enabled, this timer cannot be disabled. | 224 | NOTE: once enabled, this timer cannot be disabled. |
218 | Say N if you are unsure. | 225 | Say N if you are unsure. |
219 | 226 | ||
227 | config ORION5X_WATCHDOG | ||
228 | tristate "Orion5x watchdog" | ||
229 | depends on ARCH_ORION5X | ||
230 | help | ||
231 | Say Y here if to include support for the watchdog timer | ||
232 | in the Orion5x ARM SoCs. | ||
233 | To compile this driver as a module, choose M here: the | ||
234 | module will be called orion5x_wdt. | ||
235 | |||
220 | # ARM26 Architecture | 236 | # ARM26 Architecture |
221 | 237 | ||
222 | # AVR32 Architecture | 238 | # AVR32 Architecture |
@@ -416,6 +432,18 @@ config IT8712F_WDT | |||
416 | To compile this driver as a module, choose M here: the | 432 | To compile this driver as a module, choose M here: the |
417 | module will be called it8712f_wdt. | 433 | module will be called it8712f_wdt. |
418 | 434 | ||
435 | config IT87_WDT | ||
436 | tristate "IT87 Watchdog Timer" | ||
437 | depends on X86 && EXPERIMENTAL | ||
438 | ---help--- | ||
439 | This is the driver for the hardware watchdog on the ITE IT8716, | ||
440 | IT8718, IT8726, IT8712(Version J,K) Super I/O chips. This watchdog | ||
441 | simply watches your kernel to make sure it doesn't freeze, and if | ||
442 | it does, it reboots your computer after a certain amount of time. | ||
443 | |||
444 | To compile this driver as a module, choose M here: the module will | ||
445 | be called it87_wdt. | ||
446 | |||
419 | config HP_WATCHDOG | 447 | config HP_WATCHDOG |
420 | tristate "HP Proliant iLO 2 Hardware Watchdog Timer" | 448 | tristate "HP Proliant iLO 2 Hardware Watchdog Timer" |
421 | depends on X86 | 449 | depends on X86 |
@@ -573,6 +601,21 @@ config W83697HF_WDT | |||
573 | 601 | ||
574 | Most people will say N. | 602 | Most people will say N. |
575 | 603 | ||
604 | config W83697UG_WDT | ||
605 | tristate "W83697UG/W83697UF Watchdog Timer" | ||
606 | depends on X86 | ||
607 | ---help--- | ||
608 | This is the driver for the hardware watchdog on the W83697UG/UF | ||
609 | chipset as used in MSI Fuzzy CX700 VIA motherboards (and likely others). | ||
610 | This watchdog simply watches your kernel to make sure it doesn't | ||
611 | freeze, and if it does, it reboots your computer after a certain | ||
612 | amount of time. | ||
613 | |||
614 | To compile this driver as a module, choose M here: the | ||
615 | module will be called w83697ug_wdt. | ||
616 | |||
617 | Most people will say N. | ||
618 | |||
576 | config W83877F_WDT | 619 | config W83877F_WDT |
577 | tristate "W83877F (EMACS) Watchdog Timer" | 620 | tristate "W83877F (EMACS) Watchdog Timer" |
578 | depends on X86 | 621 | depends on X86 |
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 6702d2ef0434..e352bbb7630b 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile | |||
@@ -26,6 +26,7 @@ obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o | |||
26 | 26 | ||
27 | # ARM Architecture | 27 | # ARM Architecture |
28 | obj-$(CONFIG_AT91RM9200_WATCHDOG) += at91rm9200_wdt.o | 28 | obj-$(CONFIG_AT91RM9200_WATCHDOG) += at91rm9200_wdt.o |
29 | obj-$(CONFIG_AT91SAM9X_WATCHDOG) += at91sam9_wdt.o | ||
29 | obj-$(CONFIG_OMAP_WATCHDOG) += omap_wdt.o | 30 | obj-$(CONFIG_OMAP_WATCHDOG) += omap_wdt.o |
30 | obj-$(CONFIG_21285_WATCHDOG) += wdt285.o | 31 | obj-$(CONFIG_21285_WATCHDOG) += wdt285.o |
31 | obj-$(CONFIG_977_WATCHDOG) += wdt977.o | 32 | obj-$(CONFIG_977_WATCHDOG) += wdt977.o |
@@ -39,6 +40,7 @@ obj-$(CONFIG_EP93XX_WATCHDOG) += ep93xx_wdt.o | |||
39 | obj-$(CONFIG_PNX4008_WATCHDOG) += pnx4008_wdt.o | 40 | obj-$(CONFIG_PNX4008_WATCHDOG) += pnx4008_wdt.o |
40 | obj-$(CONFIG_IOP_WATCHDOG) += iop_wdt.o | 41 | obj-$(CONFIG_IOP_WATCHDOG) += iop_wdt.o |
41 | obj-$(CONFIG_DAVINCI_WATCHDOG) += davinci_wdt.o | 42 | obj-$(CONFIG_DAVINCI_WATCHDOG) += davinci_wdt.o |
43 | obj-$(CONFIG_ORION5X_WATCHDOG) += orion5x_wdt.o | ||
42 | 44 | ||
43 | # ARM26 Architecture | 45 | # ARM26 Architecture |
44 | 46 | ||
@@ -71,6 +73,7 @@ ifeq ($(CONFIG_ITCO_VENDOR_SUPPORT),y) | |||
71 | obj-$(CONFIG_ITCO_WDT) += iTCO_vendor_support.o | 73 | obj-$(CONFIG_ITCO_WDT) += iTCO_vendor_support.o |
72 | endif | 74 | endif |
73 | obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o | 75 | obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o |
76 | obj-$(CONFIG_IT87_WDT) += it87_wdt.o | ||
74 | obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o | 77 | obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o |
75 | obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o | 78 | obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o |
76 | obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o | 79 | obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o |
@@ -83,6 +86,7 @@ obj-$(CONFIG_CPU5_WDT) += cpu5wdt.o | |||
83 | obj-$(CONFIG_SMSC37B787_WDT) += smsc37b787_wdt.o | 86 | obj-$(CONFIG_SMSC37B787_WDT) += smsc37b787_wdt.o |
84 | obj-$(CONFIG_W83627HF_WDT) += w83627hf_wdt.o | 87 | obj-$(CONFIG_W83627HF_WDT) += w83627hf_wdt.o |
85 | obj-$(CONFIG_W83697HF_WDT) += w83697hf_wdt.o | 88 | obj-$(CONFIG_W83697HF_WDT) += w83697hf_wdt.o |
89 | obj-$(CONFIG_W83697UG_WDT) += w83697ug_wdt.o | ||
86 | obj-$(CONFIG_W83877F_WDT) += w83877f_wdt.o | 90 | obj-$(CONFIG_W83877F_WDT) += w83877f_wdt.o |
87 | obj-$(CONFIG_W83977F_WDT) += w83977f_wdt.o | 91 | obj-$(CONFIG_W83977F_WDT) += w83977f_wdt.o |
88 | obj-$(CONFIG_MACHZ_WDT) += machzwd.o | 92 | obj-$(CONFIG_MACHZ_WDT) += machzwd.o |
diff --git a/drivers/watchdog/at91sam9_wdt.c b/drivers/watchdog/at91sam9_wdt.c new file mode 100644 index 000000000000..b4babfc31586 --- /dev/null +++ b/drivers/watchdog/at91sam9_wdt.c | |||
@@ -0,0 +1,328 @@ | |||
1 | /* | ||
2 | * Watchdog driver for Atmel AT91SAM9x processors. | ||
3 | * | ||
4 | * Copyright (C) 2008 Renaud CERRATO r.cerrato@til-technologies.fr | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or | ||
7 | * modify it under the terms of the GNU General Public License | ||
8 | * as published by the Free Software Foundation; either version | ||
9 | * 2 of the License, or (at your option) any later version. | ||
10 | */ | ||
11 | |||
12 | /* | ||
13 | * The Watchdog Timer Mode Register can be only written to once. If the | ||
14 | * timeout need to be set from Linux, be sure that the bootstrap or the | ||
15 | * bootloader doesn't write to this register. | ||
16 | */ | ||
17 | |||
18 | #include <linux/errno.h> | ||
19 | #include <linux/fs.h> | ||
20 | #include <linux/init.h> | ||
21 | #include <linux/kernel.h> | ||
22 | #include <linux/miscdevice.h> | ||
23 | #include <linux/module.h> | ||
24 | #include <linux/moduleparam.h> | ||
25 | #include <linux/platform_device.h> | ||
26 | #include <linux/types.h> | ||
27 | #include <linux/watchdog.h> | ||
28 | #include <linux/jiffies.h> | ||
29 | #include <linux/timer.h> | ||
30 | #include <linux/bitops.h> | ||
31 | #include <linux/uaccess.h> | ||
32 | |||
33 | #include <asm/arch/at91_wdt.h> | ||
34 | |||
35 | #define DRV_NAME "AT91SAM9 Watchdog" | ||
36 | |||
37 | /* AT91SAM9 watchdog runs a 12bit counter @ 256Hz, | ||
38 | * use this to convert a watchdog | ||
39 | * value from/to milliseconds. | ||
40 | */ | ||
41 | #define ms_to_ticks(t) (((t << 8) / 1000) - 1) | ||
42 | #define ticks_to_ms(t) (((t + 1) * 1000) >> 8) | ||
43 | |||
44 | /* Hardware timeout in seconds */ | ||
45 | #define WDT_HW_TIMEOUT 2 | ||
46 | |||
47 | /* Timer heartbeat (500ms) */ | ||
48 | #define WDT_TIMEOUT (HZ/2) | ||
49 | |||
50 | /* User land timeout */ | ||
51 | #define WDT_HEARTBEAT 15 | ||
52 | static int heartbeat = WDT_HEARTBEAT; | ||
53 | module_param(heartbeat, int, 0); | ||
54 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. " | ||
55 | "(default = " __MODULE_STRING(WDT_HEARTBEAT) ")"); | ||
56 | |||
57 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
58 | module_param(nowayout, int, 0); | ||
59 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " | ||
60 | "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
61 | |||
62 | static void at91_ping(unsigned long data); | ||
63 | |||
64 | static struct { | ||
65 | unsigned long next_heartbeat; /* the next_heartbeat for the timer */ | ||
66 | unsigned long open; | ||
67 | char expect_close; | ||
68 | struct timer_list timer; /* The timer that pings the watchdog */ | ||
69 | } at91wdt_private; | ||
70 | |||
71 | /* ......................................................................... */ | ||
72 | |||
73 | |||
74 | /* | ||
75 | * Reload the watchdog timer. (ie, pat the watchdog) | ||
76 | */ | ||
77 | static inline void at91_wdt_reset(void) | ||
78 | { | ||
79 | at91_sys_write(AT91_WDT_CR, AT91_WDT_KEY | AT91_WDT_WDRSTT); | ||
80 | } | ||
81 | |||
82 | /* | ||
83 | * Timer tick | ||
84 | */ | ||
85 | static void at91_ping(unsigned long data) | ||
86 | { | ||
87 | if (time_before(jiffies, at91wdt_private.next_heartbeat) || | ||
88 | (!nowayout && !at91wdt_private.open)) { | ||
89 | at91_wdt_reset(); | ||
90 | mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); | ||
91 | } else | ||
92 | printk(KERN_CRIT DRV_NAME": I will reset your machine !\n"); | ||
93 | } | ||
94 | |||
95 | /* | ||
96 | * Watchdog device is opened, and watchdog starts running. | ||
97 | */ | ||
98 | static int at91_wdt_open(struct inode *inode, struct file *file) | ||
99 | { | ||
100 | if (test_and_set_bit(0, &at91wdt_private.open)) | ||
101 | return -EBUSY; | ||
102 | |||
103 | at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; | ||
104 | mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); | ||
105 | |||
106 | return nonseekable_open(inode, file); | ||
107 | } | ||
108 | |||
109 | /* | ||
110 | * Close the watchdog device. | ||
111 | */ | ||
112 | static int at91_wdt_close(struct inode *inode, struct file *file) | ||
113 | { | ||
114 | clear_bit(0, &at91wdt_private.open); | ||
115 | |||
116 | /* stop internal ping */ | ||
117 | if (!at91wdt_private.expect_close) | ||
118 | del_timer(&at91wdt_private.timer); | ||
119 | |||
120 | at91wdt_private.expect_close = 0; | ||
121 | return 0; | ||
122 | } | ||
123 | |||
124 | /* | ||
125 | * Set the watchdog time interval in 1/256Hz (write-once) | ||
126 | * Counter is 12 bit. | ||
127 | */ | ||
128 | static int at91_wdt_settimeout(unsigned int timeout) | ||
129 | { | ||
130 | unsigned int reg; | ||
131 | unsigned int mr; | ||
132 | |||
133 | /* Check if disabled */ | ||
134 | mr = at91_sys_read(AT91_WDT_MR); | ||
135 | if (mr & AT91_WDT_WDDIS) { | ||
136 | printk(KERN_ERR DRV_NAME": sorry, watchdog is disabled\n"); | ||
137 | return -EIO; | ||
138 | } | ||
139 | |||
140 | /* | ||
141 | * All counting occurs at SLOW_CLOCK / 128 = 256 Hz | ||
142 | * | ||
143 | * Since WDV is a 12-bit counter, the maximum period is | ||
144 | * 4096 / 256 = 16 seconds. | ||
145 | */ | ||
146 | reg = AT91_WDT_WDRSTEN /* causes watchdog reset */ | ||
147 | /* | AT91_WDT_WDRPROC causes processor reset only */ | ||
148 | | AT91_WDT_WDDBGHLT /* disabled in debug mode */ | ||
149 | | AT91_WDT_WDD /* restart at any time */ | ||
150 | | (timeout & AT91_WDT_WDV); /* timer value */ | ||
151 | at91_sys_write(AT91_WDT_MR, reg); | ||
152 | |||
153 | return 0; | ||
154 | } | ||
155 | |||
156 | static const struct watchdog_info at91_wdt_info = { | ||
157 | .identity = DRV_NAME, | ||
158 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, | ||
159 | }; | ||
160 | |||
161 | /* | ||
162 | * Handle commands from user-space. | ||
163 | */ | ||
164 | static long at91_wdt_ioctl(struct file *file, | ||
165 | unsigned int cmd, unsigned long arg) | ||
166 | { | ||
167 | void __user *argp = (void __user *)arg; | ||
168 | int __user *p = argp; | ||
169 | int new_value; | ||
170 | |||
171 | switch (cmd) { | ||
172 | case WDIOC_GETSUPPORT: | ||
173 | return copy_to_user(argp, &at91_wdt_info, | ||
174 | sizeof(at91_wdt_info)) ? -EFAULT : 0; | ||
175 | |||
176 | case WDIOC_GETSTATUS: | ||
177 | case WDIOC_GETBOOTSTATUS: | ||
178 | return put_user(0, p); | ||
179 | |||
180 | case WDIOC_KEEPALIVE: | ||
181 | at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; | ||
182 | return 0; | ||
183 | |||
184 | case WDIOC_SETTIMEOUT: | ||
185 | if (get_user(new_value, p)) | ||
186 | return -EFAULT; | ||
187 | |||
188 | heartbeat = new_value; | ||
189 | at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; | ||
190 | |||
191 | return put_user(new_value, p); /* return current value */ | ||
192 | |||
193 | case WDIOC_GETTIMEOUT: | ||
194 | return put_user(heartbeat, p); | ||
195 | } | ||
196 | return -ENOTTY; | ||
197 | } | ||
198 | |||
199 | /* | ||
200 | * Pat the watchdog whenever device is written to. | ||
201 | */ | ||
202 | static ssize_t at91_wdt_write(struct file *file, const char *data, size_t len, | ||
203 | loff_t *ppos) | ||
204 | { | ||
205 | if (!len) | ||
206 | return 0; | ||
207 | |||
208 | /* Scan for magic character */ | ||
209 | if (!nowayout) { | ||
210 | size_t i; | ||
211 | |||
212 | at91wdt_private.expect_close = 0; | ||
213 | |||
214 | for (i = 0; i < len; i++) { | ||
215 | char c; | ||
216 | if (get_user(c, data + i)) | ||
217 | return -EFAULT; | ||
218 | if (c == 'V') { | ||
219 | at91wdt_private.expect_close = 42; | ||
220 | break; | ||
221 | } | ||
222 | } | ||
223 | } | ||
224 | |||
225 | at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; | ||
226 | |||
227 | return len; | ||
228 | } | ||
229 | |||
230 | /* ......................................................................... */ | ||
231 | |||
232 | static const struct file_operations at91wdt_fops = { | ||
233 | .owner = THIS_MODULE, | ||
234 | .llseek = no_llseek, | ||
235 | .unlocked_ioctl = at91_wdt_ioctl, | ||
236 | .open = at91_wdt_open, | ||
237 | .release = at91_wdt_close, | ||
238 | .write = at91_wdt_write, | ||
239 | }; | ||
240 | |||
241 | static struct miscdevice at91wdt_miscdev = { | ||
242 | .minor = WATCHDOG_MINOR, | ||
243 | .name = "watchdog", | ||
244 | .fops = &at91wdt_fops, | ||
245 | }; | ||
246 | |||
247 | static int __init at91wdt_probe(struct platform_device *pdev) | ||
248 | { | ||
249 | int res; | ||
250 | |||
251 | if (at91wdt_miscdev.parent) | ||
252 | return -EBUSY; | ||
253 | at91wdt_miscdev.parent = &pdev->dev; | ||
254 | |||
255 | /* Set watchdog */ | ||
256 | res = at91_wdt_settimeout(ms_to_ticks(WDT_HW_TIMEOUT * 1000)); | ||
257 | if (res) | ||
258 | return res; | ||
259 | |||
260 | res = misc_register(&at91wdt_miscdev); | ||
261 | if (res) | ||
262 | return res; | ||
263 | |||
264 | at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; | ||
265 | setup_timer(&at91wdt_private.timer, at91_ping, 0); | ||
266 | mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); | ||
267 | |||
268 | printk(KERN_INFO DRV_NAME " enabled (heartbeat=%d sec, nowayout=%d)\n", | ||
269 | heartbeat, nowayout); | ||
270 | |||
271 | return 0; | ||
272 | } | ||
273 | |||
274 | static int __exit at91wdt_remove(struct platform_device *pdev) | ||
275 | { | ||
276 | int res; | ||
277 | |||
278 | res = misc_deregister(&at91wdt_miscdev); | ||
279 | if (!res) | ||
280 | at91wdt_miscdev.parent = NULL; | ||
281 | |||
282 | return res; | ||
283 | } | ||
284 | |||
285 | #ifdef CONFIG_PM | ||
286 | |||
287 | static int at91wdt_suspend(struct platform_device *pdev, pm_message_t message) | ||
288 | { | ||
289 | return 0; | ||
290 | } | ||
291 | |||
292 | static int at91wdt_resume(struct platform_device *pdev) | ||
293 | { | ||
294 | return 0; | ||
295 | } | ||
296 | |||
297 | #else | ||
298 | #define at91wdt_suspend NULL | ||
299 | #define at91wdt_resume NULL | ||
300 | #endif | ||
301 | |||
302 | static struct platform_driver at91wdt_driver = { | ||
303 | .remove = __exit_p(at91wdt_remove), | ||
304 | .suspend = at91wdt_suspend, | ||
305 | .resume = at91wdt_resume, | ||
306 | .driver = { | ||
307 | .name = "at91_wdt", | ||
308 | .owner = THIS_MODULE, | ||
309 | }, | ||
310 | }; | ||
311 | |||
312 | static int __init at91sam_wdt_init(void) | ||
313 | { | ||
314 | return platform_driver_probe(&at91wdt_driver, at91wdt_probe); | ||
315 | } | ||
316 | |||
317 | static void __exit at91sam_wdt_exit(void) | ||
318 | { | ||
319 | platform_driver_unregister(&at91wdt_driver); | ||
320 | } | ||
321 | |||
322 | module_init(at91sam_wdt_init); | ||
323 | module_exit(at91sam_wdt_exit); | ||
324 | |||
325 | MODULE_AUTHOR("Renaud CERRATO <r.cerrato@til-technologies.fr>"); | ||
326 | MODULE_DESCRIPTION("Watchdog driver for Atmel AT91SAM9x processors"); | ||
327 | MODULE_LICENSE("GPL"); | ||
328 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/it87_wdt.c b/drivers/watchdog/it87_wdt.c new file mode 100644 index 000000000000..afb8af397a9f --- /dev/null +++ b/drivers/watchdog/it87_wdt.c | |||
@@ -0,0 +1,725 @@ | |||
1 | /* | ||
2 | * Watchdog Timer Driver | ||
3 | * for ITE IT87xx Environment Control - Low Pin Count Input / Output | ||
4 | * | ||
5 | * (c) Copyright 2007 Oliver Schuster <olivers137@aol.com> | ||
6 | * | ||
7 | * Based on softdog.c by Alan Cox, | ||
8 | * 83977f_wdt.c by Jose Goncalves, | ||
9 | * it87.c by Chris Gauthron, Jean Delvare | ||
10 | * | ||
11 | * Data-sheets: Publicly available at the ITE website | ||
12 | * http://www.ite.com.tw/ | ||
13 | * | ||
14 | * Support of the watchdog timers, which are available on | ||
15 | * IT8716, IT8718, IT8726 and IT8712 (J,K version). | ||
16 | * | ||
17 | * This program is free software; you can redistribute it and/or | ||
18 | * modify it under the terms of the GNU General Public License | ||
19 | * as published by the Free Software Foundation; either version | ||
20 | * 2 of the License, or (at your option) any later version. | ||
21 | * | ||
22 | * This program is distributed in the hope that it will be useful, | ||
23 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
24 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
25 | * GNU General Public License for more details. | ||
26 | * | ||
27 | * You should have received a copy of the GNU General Public License | ||
28 | * along with this program; if not, write to the Free Software | ||
29 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
30 | */ | ||
31 | |||
32 | #include <linux/module.h> | ||
33 | #include <linux/moduleparam.h> | ||
34 | #include <linux/types.h> | ||
35 | #include <linux/kernel.h> | ||
36 | #include <linux/fs.h> | ||
37 | #include <linux/miscdevice.h> | ||
38 | #include <linux/init.h> | ||
39 | #include <linux/ioport.h> | ||
40 | #include <linux/watchdog.h> | ||
41 | #include <linux/notifier.h> | ||
42 | #include <linux/reboot.h> | ||
43 | #include <linux/uaccess.h> | ||
44 | #include <linux/io.h> | ||
45 | |||
46 | #include <asm/system.h> | ||
47 | |||
48 | #define WATCHDOG_VERSION "1.12" | ||
49 | #define WATCHDOG_NAME "IT87 WDT" | ||
50 | #define PFX WATCHDOG_NAME ": " | ||
51 | #define DRIVER_VERSION WATCHDOG_NAME " driver, v" WATCHDOG_VERSION "\n" | ||
52 | #define WD_MAGIC 'V' | ||
53 | |||
54 | /* Defaults for Module Parameter */ | ||
55 | #define DEFAULT_NOGAMEPORT 0 | ||
56 | #define DEFAULT_EXCLUSIVE 1 | ||
57 | #define DEFAULT_TIMEOUT 60 | ||
58 | #define DEFAULT_TESTMODE 0 | ||
59 | #define DEFAULT_NOWAYOUT WATCHDOG_NOWAYOUT | ||
60 | |||
61 | /* IO Ports */ | ||
62 | #define REG 0x2e | ||
63 | #define VAL 0x2f | ||
64 | |||
65 | /* Logical device Numbers LDN */ | ||
66 | #define GPIO 0x07 | ||
67 | #define GAMEPORT 0x09 | ||
68 | #define CIR 0x0a | ||
69 | |||
70 | /* Configuration Registers and Functions */ | ||
71 | #define LDNREG 0x07 | ||
72 | #define CHIPID 0x20 | ||
73 | #define CHIPREV 0x22 | ||
74 | #define ACTREG 0x30 | ||
75 | #define BASEREG 0x60 | ||
76 | |||
77 | /* Chip Id numbers */ | ||
78 | #define NO_DEV_ID 0xffff | ||
79 | #define IT8705_ID 0x8705 | ||
80 | #define IT8712_ID 0x8712 | ||
81 | #define IT8716_ID 0x8716 | ||
82 | #define IT8718_ID 0x8718 | ||
83 | #define IT8726_ID 0x8726 /* the data sheet suggest wrongly 0x8716 */ | ||
84 | |||
85 | /* GPIO Configuration Registers LDN=0x07 */ | ||
86 | #define WDTCTRL 0x71 | ||
87 | #define WDTCFG 0x72 | ||
88 | #define WDTVALLSB 0x73 | ||
89 | #define WDTVALMSB 0x74 | ||
90 | |||
91 | /* GPIO Bits WDTCTRL */ | ||
92 | #define WDT_CIRINT 0x80 | ||
93 | #define WDT_MOUSEINT 0x40 | ||
94 | #define WDT_KYBINT 0x20 | ||
95 | #define WDT_GAMEPORT 0x10 /* not it8718 */ | ||
96 | #define WDT_FORCE 0x02 | ||
97 | #define WDT_ZERO 0x01 | ||
98 | |||
99 | /* GPIO Bits WDTCFG */ | ||
100 | #define WDT_TOV1 0x80 | ||
101 | #define WDT_KRST 0x40 | ||
102 | #define WDT_TOVE 0x20 | ||
103 | #define WDT_PWROK 0x10 | ||
104 | #define WDT_INT_MASK 0x0f | ||
105 | |||
106 | /* CIR Configuration Register LDN=0x0a */ | ||
107 | #define CIR_ILS 0x70 | ||
108 | |||
109 | /* The default Base address is not always available, we use this */ | ||
110 | #define CIR_BASE 0x0208 | ||
111 | |||
112 | /* CIR Controller */ | ||
113 | #define CIR_DR(b) (b) | ||
114 | #define CIR_IER(b) (b + 1) | ||
115 | #define CIR_RCR(b) (b + 2) | ||
116 | #define CIR_TCR1(b) (b + 3) | ||
117 | #define CIR_TCR2(b) (b + 4) | ||
118 | #define CIR_TSR(b) (b + 5) | ||
119 | #define CIR_RSR(b) (b + 6) | ||
120 | #define CIR_BDLR(b) (b + 5) | ||
121 | #define CIR_BDHR(b) (b + 6) | ||
122 | #define CIR_IIR(b) (b + 7) | ||
123 | |||
124 | /* Default Base address of Game port */ | ||
125 | #define GP_BASE_DEFAULT 0x0201 | ||
126 | |||
127 | /* wdt_status */ | ||
128 | #define WDTS_TIMER_RUN 0 | ||
129 | #define WDTS_DEV_OPEN 1 | ||
130 | #define WDTS_KEEPALIVE 2 | ||
131 | #define WDTS_LOCKED 3 | ||
132 | #define WDTS_USE_GP 4 | ||
133 | #define WDTS_EXPECTED 5 | ||
134 | |||
135 | static unsigned int base, gpact, ciract; | ||
136 | static unsigned long wdt_status; | ||
137 | static DEFINE_SPINLOCK(spinlock); | ||
138 | |||
139 | static int nogameport = DEFAULT_NOGAMEPORT; | ||
140 | static int exclusive = DEFAULT_EXCLUSIVE; | ||
141 | static int timeout = DEFAULT_TIMEOUT; | ||
142 | static int testmode = DEFAULT_TESTMODE; | ||
143 | static int nowayout = DEFAULT_NOWAYOUT; | ||
144 | |||
145 | module_param(nogameport, int, 0); | ||
146 | MODULE_PARM_DESC(nogameport, "Forbid the activation of game port, default=" | ||
147 | __MODULE_STRING(DEFAULT_NOGAMEPORT)); | ||
148 | module_param(exclusive, int, 0); | ||
149 | MODULE_PARM_DESC(exclusive, "Watchdog exclusive device open, default=" | ||
150 | __MODULE_STRING(DEFAULT_EXCLUSIVE)); | ||
151 | module_param(timeout, int, 0); | ||
152 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds, default=" | ||
153 | __MODULE_STRING(DEFAULT_TIMEOUT)); | ||
154 | module_param(testmode, int, 0); | ||
155 | MODULE_PARM_DESC(testmode, "Watchdog test mode (1 = no reboot), default=" | ||
156 | __MODULE_STRING(DEFAULT_TESTMODE)); | ||
157 | module_param(nowayout, int, 0); | ||
158 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started, default=" | ||
159 | __MODULE_STRING(WATCHDOG_NOWAYOUT)); | ||
160 | |||
161 | /* Superio Chip */ | ||
162 | |||
163 | static inline void superio_enter(void) | ||
164 | { | ||
165 | outb(0x87, REG); | ||
166 | outb(0x01, REG); | ||
167 | outb(0x55, REG); | ||
168 | outb(0x55, REG); | ||
169 | } | ||
170 | |||
171 | static inline void superio_exit(void) | ||
172 | { | ||
173 | outb(0x02, REG); | ||
174 | outb(0x02, VAL); | ||
175 | } | ||
176 | |||
177 | static inline void superio_select(int ldn) | ||
178 | { | ||
179 | outb(LDNREG, REG); | ||
180 | outb(ldn, VAL); | ||
181 | } | ||
182 | |||
183 | static inline int superio_inb(int reg) | ||
184 | { | ||
185 | outb(reg, REG); | ||
186 | return inb(VAL); | ||
187 | } | ||
188 | |||
189 | static inline void superio_outb(int val, int reg) | ||
190 | { | ||
191 | outb(reg, REG); | ||
192 | outb(val, VAL); | ||
193 | } | ||
194 | |||
195 | static inline int superio_inw(int reg) | ||
196 | { | ||
197 | int val; | ||
198 | outb(reg++, REG); | ||
199 | val = inb(VAL) << 8; | ||
200 | outb(reg, REG); | ||
201 | val |= inb(VAL); | ||
202 | return val; | ||
203 | } | ||
204 | |||
205 | static inline void superio_outw(int val, int reg) | ||
206 | { | ||
207 | outb(reg++, REG); | ||
208 | outb(val >> 8, VAL); | ||
209 | outb(reg, REG); | ||
210 | outb(val, VAL); | ||
211 | } | ||
212 | |||
213 | /* watchdog timer handling */ | ||
214 | |||
215 | static void wdt_keepalive(void) | ||
216 | { | ||
217 | if (test_bit(WDTS_USE_GP, &wdt_status)) | ||
218 | inb(base); | ||
219 | else | ||
220 | /* The timer reloads with around 5 msec delay */ | ||
221 | outb(0x55, CIR_DR(base)); | ||
222 | set_bit(WDTS_KEEPALIVE, &wdt_status); | ||
223 | } | ||
224 | |||
225 | static void wdt_start(void) | ||
226 | { | ||
227 | unsigned long flags; | ||
228 | |||
229 | spin_lock_irqsave(&spinlock, flags); | ||
230 | superio_enter(); | ||
231 | |||
232 | superio_select(GPIO); | ||
233 | if (test_bit(WDTS_USE_GP, &wdt_status)) | ||
234 | superio_outb(WDT_GAMEPORT, WDTCTRL); | ||
235 | else | ||
236 | superio_outb(WDT_CIRINT, WDTCTRL); | ||
237 | if (!testmode) | ||
238 | superio_outb(WDT_TOV1 | WDT_KRST | WDT_PWROK, WDTCFG); | ||
239 | else | ||
240 | superio_outb(WDT_TOV1, WDTCFG); | ||
241 | superio_outb(timeout>>8, WDTVALMSB); | ||
242 | superio_outb(timeout, WDTVALLSB); | ||
243 | |||
244 | superio_exit(); | ||
245 | spin_unlock_irqrestore(&spinlock, flags); | ||
246 | } | ||
247 | |||
248 | static void wdt_stop(void) | ||
249 | { | ||
250 | unsigned long flags; | ||
251 | |||
252 | spin_lock_irqsave(&spinlock, flags); | ||
253 | superio_enter(); | ||
254 | |||
255 | superio_select(GPIO); | ||
256 | superio_outb(0x00, WDTCTRL); | ||
257 | superio_outb(WDT_TOV1, WDTCFG); | ||
258 | superio_outb(0x00, WDTVALMSB); | ||
259 | superio_outb(0x00, WDTVALLSB); | ||
260 | |||
261 | superio_exit(); | ||
262 | spin_unlock_irqrestore(&spinlock, flags); | ||
263 | } | ||
264 | |||
265 | /** | ||
266 | * wdt_set_timeout - set a new timeout value with watchdog ioctl | ||
267 | * @t: timeout value in seconds | ||
268 | * | ||
269 | * The hardware device has a 16 bit watchdog timer, thus the | ||
270 | * timeout time ranges between 1 and 65535 seconds. | ||
271 | * | ||
272 | * Used within WDIOC_SETTIMEOUT watchdog device ioctl. | ||
273 | */ | ||
274 | |||
275 | static int wdt_set_timeout(int t) | ||
276 | { | ||
277 | unsigned long flags; | ||
278 | |||
279 | if (t < 1 || t > 65535) | ||
280 | return -EINVAL; | ||
281 | |||
282 | timeout = t; | ||
283 | |||
284 | spin_lock_irqsave(&spinlock, flags); | ||
285 | if (test_bit(WDTS_TIMER_RUN, &wdt_status)) { | ||
286 | superio_enter(); | ||
287 | |||
288 | superio_select(GPIO); | ||
289 | superio_outb(t>>8, WDTVALMSB); | ||
290 | superio_outb(t, WDTVALLSB); | ||
291 | |||
292 | superio_exit(); | ||
293 | } | ||
294 | spin_unlock_irqrestore(&spinlock, flags); | ||
295 | return 0; | ||
296 | } | ||
297 | |||
298 | /** | ||
299 | * wdt_get_status - determines the status supported by watchdog ioctl | ||
300 | * @status: status returned to user space | ||
301 | * | ||
302 | * The status bit of the device does not allow to distinguish | ||
303 | * between a regular system reset and a watchdog forced reset. | ||
304 | * But, in test mode it is useful, so it is supported through | ||
305 | * WDIOC_GETSTATUS watchdog ioctl. Additionally the driver | ||
306 | * reports the keepalive signal and the acception of the magic. | ||
307 | * | ||
308 | * Used within WDIOC_GETSTATUS watchdog device ioctl. | ||
309 | */ | ||
310 | |||
311 | static int wdt_get_status(int *status) | ||
312 | { | ||
313 | unsigned long flags; | ||
314 | |||
315 | *status = 0; | ||
316 | if (testmode) { | ||
317 | spin_lock_irqsave(&spinlock, flags); | ||
318 | superio_enter(); | ||
319 | superio_select(GPIO); | ||
320 | if (superio_inb(WDTCTRL) & WDT_ZERO) { | ||
321 | superio_outb(0x00, WDTCTRL); | ||
322 | clear_bit(WDTS_TIMER_RUN, &wdt_status); | ||
323 | *status |= WDIOF_CARDRESET; | ||
324 | } | ||
325 | |||
326 | superio_exit(); | ||
327 | spin_unlock_irqrestore(&spinlock, flags); | ||
328 | } | ||
329 | if (test_and_clear_bit(WDTS_KEEPALIVE, &wdt_status)) | ||
330 | *status |= WDIOF_KEEPALIVEPING; | ||
331 | if (test_bit(WDTS_EXPECTED, &wdt_status)) | ||
332 | *status |= WDIOF_MAGICCLOSE; | ||
333 | return 0; | ||
334 | } | ||
335 | |||
336 | /* /dev/watchdog handling */ | ||
337 | |||
338 | /** | ||
339 | * wdt_open - watchdog file_operations .open | ||
340 | * @inode: inode of the device | ||
341 | * @file: file handle to the device | ||
342 | * | ||
343 | * The watchdog timer starts by opening the device. | ||
344 | * | ||
345 | * Used within the file operation of the watchdog device. | ||
346 | */ | ||
347 | |||
348 | static int wdt_open(struct inode *inode, struct file *file) | ||
349 | { | ||
350 | if (exclusive && test_and_set_bit(WDTS_DEV_OPEN, &wdt_status)) | ||
351 | return -EBUSY; | ||
352 | if (!test_and_set_bit(WDTS_TIMER_RUN, &wdt_status)) { | ||
353 | if (nowayout && !test_and_set_bit(WDTS_LOCKED, &wdt_status)) | ||
354 | __module_get(THIS_MODULE); | ||
355 | wdt_start(); | ||
356 | } | ||
357 | return nonseekable_open(inode, file); | ||
358 | } | ||
359 | |||
360 | /** | ||
361 | * wdt_release - watchdog file_operations .release | ||
362 | * @inode: inode of the device | ||
363 | * @file: file handle to the device | ||
364 | * | ||
365 | * Closing the watchdog device either stops the watchdog timer | ||
366 | * or in the case, that nowayout is set or the magic character | ||
367 | * wasn't written, a critical warning about an running watchdog | ||
368 | * timer is given. | ||
369 | * | ||
370 | * Used within the file operation of the watchdog device. | ||
371 | */ | ||
372 | |||
373 | static int wdt_release(struct inode *inode, struct file *file) | ||
374 | { | ||
375 | if (test_bit(WDTS_TIMER_RUN, &wdt_status)) { | ||
376 | if (test_and_clear_bit(WDTS_EXPECTED, &wdt_status)) { | ||
377 | wdt_stop(); | ||
378 | clear_bit(WDTS_TIMER_RUN, &wdt_status); | ||
379 | } else { | ||
380 | wdt_keepalive(); | ||
381 | printk(KERN_CRIT PFX | ||
382 | "unexpected close, not stopping watchdog!\n"); | ||
383 | } | ||
384 | } | ||
385 | clear_bit(WDTS_DEV_OPEN, &wdt_status); | ||
386 | return 0; | ||
387 | } | ||
388 | |||
389 | /** | ||
390 | * wdt_write - watchdog file_operations .write | ||
391 | * @file: file handle to the watchdog | ||
392 | * @buf: buffer to write | ||
393 | * @count: count of bytes | ||
394 | * @ppos: pointer to the position to write. No seeks allowed | ||
395 | * | ||
396 | * A write to a watchdog device is defined as a keepalive signal. Any | ||
397 | * write of data will do, as we don't define content meaning. | ||
398 | * | ||
399 | * Used within the file operation of the watchdog device. | ||
400 | */ | ||
401 | |||
402 | static ssize_t wdt_write(struct file *file, const char __user *buf, | ||
403 | size_t count, loff_t *ppos) | ||
404 | { | ||
405 | if (count) { | ||
406 | clear_bit(WDTS_EXPECTED, &wdt_status); | ||
407 | wdt_keepalive(); | ||
408 | } | ||
409 | if (!nowayout) { | ||
410 | size_t ofs; | ||
411 | |||
412 | /* note: just in case someone wrote the magic character long ago */ | ||
413 | for (ofs = 0; ofs != count; ofs++) { | ||
414 | char c; | ||
415 | if (get_user(c, buf + ofs)) | ||
416 | return -EFAULT; | ||
417 | if (c == WD_MAGIC) | ||
418 | set_bit(WDTS_EXPECTED, &wdt_status); | ||
419 | } | ||
420 | } | ||
421 | return count; | ||
422 | } | ||
423 | |||
424 | static struct watchdog_info ident = { | ||
425 | .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, | ||
426 | .firmware_version = 1, | ||
427 | .identity = WATCHDOG_NAME, | ||
428 | }; | ||
429 | |||
430 | /** | ||
431 | * wdt_ioctl - watchdog file_operations .unlocked_ioctl | ||
432 | * @file: file handle to the device | ||
433 | * @cmd: watchdog command | ||
434 | * @arg: argument pointer | ||
435 | * | ||
436 | * The watchdog API defines a common set of functions for all watchdogs | ||
437 | * according to their available features. | ||
438 | * | ||
439 | * Used within the file operation of the watchdog device. | ||
440 | */ | ||
441 | |||
442 | static long wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) | ||
443 | { | ||
444 | int rc = 0, status, new_options, new_timeout; | ||
445 | union { | ||
446 | struct watchdog_info __user *ident; | ||
447 | int __user *i; | ||
448 | } uarg; | ||
449 | |||
450 | uarg.i = (int __user *)arg; | ||
451 | |||
452 | switch (cmd) { | ||
453 | case WDIOC_GETSUPPORT: | ||
454 | return copy_to_user(uarg.ident, | ||
455 | &ident, sizeof(ident)) ? -EFAULT : 0; | ||
456 | |||
457 | case WDIOC_GETSTATUS: | ||
458 | wdt_get_status(&status); | ||
459 | return put_user(status, uarg.i); | ||
460 | |||
461 | case WDIOC_GETBOOTSTATUS: | ||
462 | return put_user(0, uarg.i); | ||
463 | |||
464 | case WDIOC_KEEPALIVE: | ||
465 | wdt_keepalive(); | ||
466 | return 0; | ||
467 | |||
468 | case WDIOC_SETOPTIONS: | ||
469 | if (get_user(new_options, uarg.i)) | ||
470 | return -EFAULT; | ||
471 | |||
472 | switch (new_options) { | ||
473 | case WDIOS_DISABLECARD: | ||
474 | if (test_bit(WDTS_TIMER_RUN, &wdt_status)) | ||
475 | wdt_stop(); | ||
476 | clear_bit(WDTS_TIMER_RUN, &wdt_status); | ||
477 | return 0; | ||
478 | |||
479 | case WDIOS_ENABLECARD: | ||
480 | if (!test_and_set_bit(WDTS_TIMER_RUN, &wdt_status)) | ||
481 | wdt_start(); | ||
482 | return 0; | ||
483 | |||
484 | default: | ||
485 | return -EFAULT; | ||
486 | } | ||
487 | |||
488 | case WDIOC_SETTIMEOUT: | ||
489 | if (get_user(new_timeout, uarg.i)) | ||
490 | return -EFAULT; | ||
491 | rc = wdt_set_timeout(new_timeout); | ||
492 | case WDIOC_GETTIMEOUT: | ||
493 | if (put_user(timeout, uarg.i)) | ||
494 | return -EFAULT; | ||
495 | return rc; | ||
496 | |||
497 | default: | ||
498 | return -ENOTTY; | ||
499 | } | ||
500 | } | ||
501 | |||
502 | static int wdt_notify_sys(struct notifier_block *this, unsigned long code, | ||
503 | void *unused) | ||
504 | { | ||
505 | if (code == SYS_DOWN || code == SYS_HALT) | ||
506 | wdt_stop(); | ||
507 | return NOTIFY_DONE; | ||
508 | } | ||
509 | |||
510 | static const struct file_operations wdt_fops = { | ||
511 | .owner = THIS_MODULE, | ||
512 | .llseek = no_llseek, | ||
513 | .write = wdt_write, | ||
514 | .unlocked_ioctl = wdt_ioctl, | ||
515 | .open = wdt_open, | ||
516 | .release = wdt_release, | ||
517 | }; | ||
518 | |||
519 | static struct miscdevice wdt_miscdev = { | ||
520 | .minor = WATCHDOG_MINOR, | ||
521 | .name = "watchdog", | ||
522 | .fops = &wdt_fops, | ||
523 | }; | ||
524 | |||
525 | static struct notifier_block wdt_notifier = { | ||
526 | .notifier_call = wdt_notify_sys, | ||
527 | }; | ||
528 | |||
529 | static int __init it87_wdt_init(void) | ||
530 | { | ||
531 | int rc = 0; | ||
532 | u16 chip_type; | ||
533 | u8 chip_rev; | ||
534 | unsigned long flags; | ||
535 | |||
536 | spin_lock_irqsave(&spinlock, flags); | ||
537 | superio_enter(); | ||
538 | chip_type = superio_inw(CHIPID); | ||
539 | chip_rev = superio_inb(CHIPREV) & 0x0f; | ||
540 | superio_exit(); | ||
541 | spin_unlock_irqrestore(&spinlock, flags); | ||
542 | |||
543 | switch (chip_type) { | ||
544 | case IT8716_ID: | ||
545 | case IT8718_ID: | ||
546 | case IT8726_ID: | ||
547 | break; | ||
548 | case IT8712_ID: | ||
549 | if (chip_rev > 7) | ||
550 | break; | ||
551 | case IT8705_ID: | ||
552 | printk(KERN_ERR PFX | ||
553 | "Unsupported Chip found, Chip %04x Revision %02x\n", | ||
554 | chip_type, chip_rev); | ||
555 | return -ENODEV; | ||
556 | case NO_DEV_ID: | ||
557 | printk(KERN_ERR PFX "no device\n"); | ||
558 | return -ENODEV; | ||
559 | default: | ||
560 | printk(KERN_ERR PFX | ||
561 | "Unknown Chip found, Chip %04x Revision %04x\n", | ||
562 | chip_type, chip_rev); | ||
563 | return -ENODEV; | ||
564 | } | ||
565 | |||
566 | spin_lock_irqsave(&spinlock, flags); | ||
567 | superio_enter(); | ||
568 | |||
569 | superio_select(GPIO); | ||
570 | superio_outb(WDT_TOV1, WDTCFG); | ||
571 | superio_outb(0x00, WDTCTRL); | ||
572 | |||
573 | /* First try to get Gameport support */ | ||
574 | if (chip_type != IT8718_ID && !nogameport) { | ||
575 | superio_select(GAMEPORT); | ||
576 | base = superio_inw(BASEREG); | ||
577 | if (!base) { | ||
578 | base = GP_BASE_DEFAULT; | ||
579 | superio_outw(base, BASEREG); | ||
580 | } | ||
581 | gpact = superio_inb(ACTREG); | ||
582 | superio_outb(0x01, ACTREG); | ||
583 | superio_exit(); | ||
584 | spin_unlock_irqrestore(&spinlock, flags); | ||
585 | if (request_region(base, 1, WATCHDOG_NAME)) | ||
586 | set_bit(WDTS_USE_GP, &wdt_status); | ||
587 | else | ||
588 | rc = -EIO; | ||
589 | } else { | ||
590 | superio_exit(); | ||
591 | spin_unlock_irqrestore(&spinlock, flags); | ||
592 | } | ||
593 | |||
594 | /* If we haven't Gameport support, try to get CIR support */ | ||
595 | if (!test_bit(WDTS_USE_GP, &wdt_status)) { | ||
596 | if (!request_region(CIR_BASE, 8, WATCHDOG_NAME)) { | ||
597 | if (rc == -EIO) | ||
598 | printk(KERN_ERR PFX | ||
599 | "I/O Address 0x%04x and 0x%04x" | ||
600 | " already in use\n", base, CIR_BASE); | ||
601 | else | ||
602 | printk(KERN_ERR PFX | ||
603 | "I/O Address 0x%04x already in use\n", | ||
604 | CIR_BASE); | ||
605 | rc = -EIO; | ||
606 | goto err_out; | ||
607 | } | ||
608 | base = CIR_BASE; | ||
609 | spin_lock_irqsave(&spinlock, flags); | ||
610 | superio_enter(); | ||
611 | |||
612 | superio_select(CIR); | ||
613 | superio_outw(base, BASEREG); | ||
614 | superio_outb(0x00, CIR_ILS); | ||
615 | ciract = superio_inb(ACTREG); | ||
616 | superio_outb(0x01, ACTREG); | ||
617 | if (rc == -EIO) { | ||
618 | superio_select(GAMEPORT); | ||
619 | superio_outb(gpact, ACTREG); | ||
620 | } | ||
621 | |||
622 | superio_exit(); | ||
623 | spin_unlock_irqrestore(&spinlock, flags); | ||
624 | } | ||
625 | |||
626 | if (timeout < 1 || timeout > 65535) { | ||
627 | timeout = DEFAULT_TIMEOUT; | ||
628 | printk(KERN_WARNING PFX | ||
629 | "Timeout value out of range, use default %d sec\n", | ||
630 | DEFAULT_TIMEOUT); | ||
631 | } | ||
632 | |||
633 | rc = register_reboot_notifier(&wdt_notifier); | ||
634 | if (rc) { | ||
635 | printk(KERN_ERR PFX | ||
636 | "Cannot register reboot notifier (err=%d)\n", rc); | ||
637 | goto err_out_region; | ||
638 | } | ||
639 | |||
640 | rc = misc_register(&wdt_miscdev); | ||
641 | if (rc) { | ||
642 | printk(KERN_ERR PFX | ||
643 | "Cannot register miscdev on minor=%d (err=%d)\n", | ||
644 | wdt_miscdev.minor, rc); | ||
645 | goto err_out_reboot; | ||
646 | } | ||
647 | |||
648 | /* Initialize CIR to use it as keepalive source */ | ||
649 | if (!test_bit(WDTS_USE_GP, &wdt_status)) { | ||
650 | outb(0x00, CIR_RCR(base)); | ||
651 | outb(0xc0, CIR_TCR1(base)); | ||
652 | outb(0x5c, CIR_TCR2(base)); | ||
653 | outb(0x10, CIR_IER(base)); | ||
654 | outb(0x00, CIR_BDHR(base)); | ||
655 | outb(0x01, CIR_BDLR(base)); | ||
656 | outb(0x09, CIR_IER(base)); | ||
657 | } | ||
658 | |||
659 | printk(KERN_INFO PFX "Chip it%04x revision %d initialized. " | ||
660 | "timeout=%d sec (nowayout=%d testmode=%d exclusive=%d " | ||
661 | "nogameport=%d)\n", chip_type, chip_rev, timeout, | ||
662 | nowayout, testmode, exclusive, nogameport); | ||
663 | |||
664 | return 0; | ||
665 | |||
666 | err_out_reboot: | ||
667 | unregister_reboot_notifier(&wdt_notifier); | ||
668 | err_out_region: | ||
669 | release_region(base, test_bit(WDTS_USE_GP, &wdt_status) ? 1 : 8); | ||
670 | if (!test_bit(WDTS_USE_GP, &wdt_status)) { | ||
671 | spin_lock_irqsave(&spinlock, flags); | ||
672 | superio_enter(); | ||
673 | superio_select(CIR); | ||
674 | superio_outb(ciract, ACTREG); | ||
675 | superio_exit(); | ||
676 | spin_unlock_irqrestore(&spinlock, flags); | ||
677 | } | ||
678 | err_out: | ||
679 | if (chip_type != IT8718_ID && !nogameport) { | ||
680 | spin_lock_irqsave(&spinlock, flags); | ||
681 | superio_enter(); | ||
682 | superio_select(GAMEPORT); | ||
683 | superio_outb(gpact, ACTREG); | ||
684 | superio_exit(); | ||
685 | spin_unlock_irqrestore(&spinlock, flags); | ||
686 | } | ||
687 | |||
688 | return rc; | ||
689 | } | ||
690 | |||
691 | static void __exit it87_wdt_exit(void) | ||
692 | { | ||
693 | unsigned long flags; | ||
694 | int nolock; | ||
695 | |||
696 | nolock = !spin_trylock_irqsave(&spinlock, flags); | ||
697 | superio_enter(); | ||
698 | superio_select(GPIO); | ||
699 | superio_outb(0x00, WDTCTRL); | ||
700 | superio_outb(0x00, WDTCFG); | ||
701 | superio_outb(0x00, WDTVALMSB); | ||
702 | superio_outb(0x00, WDTVALLSB); | ||
703 | if (test_bit(WDTS_USE_GP, &wdt_status)) { | ||
704 | superio_select(GAMEPORT); | ||
705 | superio_outb(gpact, ACTREG); | ||
706 | } else { | ||
707 | superio_select(CIR); | ||
708 | superio_outb(ciract, ACTREG); | ||
709 | } | ||
710 | superio_exit(); | ||
711 | if (!nolock) | ||
712 | spin_unlock_irqrestore(&spinlock, flags); | ||
713 | |||
714 | misc_deregister(&wdt_miscdev); | ||
715 | unregister_reboot_notifier(&wdt_notifier); | ||
716 | release_region(base, test_bit(WDTS_USE_GP, &wdt_status) ? 1 : 8); | ||
717 | } | ||
718 | |||
719 | module_init(it87_wdt_init); | ||
720 | module_exit(it87_wdt_exit); | ||
721 | |||
722 | MODULE_AUTHOR("Oliver Schuster"); | ||
723 | MODULE_DESCRIPTION("Hardware Watchdog Device Driver for IT87xx EC-LPC I/O"); | ||
724 | MODULE_LICENSE("GPL"); | ||
725 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/omap_wdt.c b/drivers/watchdog/omap_wdt.c index 3a11dadfd8e7..7bcbb7f4745f 100644 --- a/drivers/watchdog/omap_wdt.c +++ b/drivers/watchdog/omap_wdt.c | |||
@@ -1,7 +1,7 @@ | |||
1 | /* | 1 | /* |
2 | * linux/drivers/char/watchdog/omap_wdt.c | 2 | * omap_wdt.c |
3 | * | 3 | * |
4 | * Watchdog driver for the TI OMAP 16xx & 24xx 32KHz (non-secure) watchdog | 4 | * Watchdog driver for the TI OMAP 16xx & 24xx/34xx 32KHz (non-secure) watchdog |
5 | * | 5 | * |
6 | * Author: MontaVista Software, Inc. | 6 | * Author: MontaVista Software, Inc. |
7 | * <gdavis@mvista.com> or <source@mvista.com> | 7 | * <gdavis@mvista.com> or <source@mvista.com> |
@@ -47,50 +47,68 @@ | |||
47 | 47 | ||
48 | #include "omap_wdt.h" | 48 | #include "omap_wdt.h" |
49 | 49 | ||
50 | static struct platform_device *omap_wdt_dev; | ||
51 | |||
50 | static unsigned timer_margin; | 52 | static unsigned timer_margin; |
51 | module_param(timer_margin, uint, 0); | 53 | module_param(timer_margin, uint, 0); |
52 | MODULE_PARM_DESC(timer_margin, "initial watchdog timeout (in seconds)"); | 54 | MODULE_PARM_DESC(timer_margin, "initial watchdog timeout (in seconds)"); |
53 | 55 | ||
54 | static int omap_wdt_users; | ||
55 | static struct clk *armwdt_ck; | ||
56 | static struct clk *mpu_wdt_ick; | ||
57 | static struct clk *mpu_wdt_fck; | ||
58 | |||
59 | static unsigned int wdt_trgr_pattern = 0x1234; | 56 | static unsigned int wdt_trgr_pattern = 0x1234; |
60 | static spinlock_t wdt_lock; | 57 | static spinlock_t wdt_lock; |
61 | 58 | ||
62 | static void omap_wdt_ping(void) | 59 | struct omap_wdt_dev { |
60 | void __iomem *base; /* physical */ | ||
61 | struct device *dev; | ||
62 | int omap_wdt_users; | ||
63 | struct clk *armwdt_ck; | ||
64 | struct clk *mpu_wdt_ick; | ||
65 | struct clk *mpu_wdt_fck; | ||
66 | struct resource *mem; | ||
67 | struct miscdevice omap_wdt_miscdev; | ||
68 | }; | ||
69 | |||
70 | static void omap_wdt_ping(struct omap_wdt_dev *wdev) | ||
63 | { | 71 | { |
72 | void __iomem *base = wdev->base; | ||
73 | |||
64 | /* wait for posted write to complete */ | 74 | /* wait for posted write to complete */ |
65 | while ((omap_readl(OMAP_WATCHDOG_WPS)) & 0x08) | 75 | while ((__raw_readl(base + OMAP_WATCHDOG_WPS)) & 0x08) |
66 | cpu_relax(); | 76 | cpu_relax(); |
77 | |||
67 | wdt_trgr_pattern = ~wdt_trgr_pattern; | 78 | wdt_trgr_pattern = ~wdt_trgr_pattern; |
68 | omap_writel(wdt_trgr_pattern, (OMAP_WATCHDOG_TGR)); | 79 | __raw_writel(wdt_trgr_pattern, (base + OMAP_WATCHDOG_TGR)); |
80 | |||
69 | /* wait for posted write to complete */ | 81 | /* wait for posted write to complete */ |
70 | while ((omap_readl(OMAP_WATCHDOG_WPS)) & 0x08) | 82 | while ((__raw_readl(base + OMAP_WATCHDOG_WPS)) & 0x08) |
71 | cpu_relax(); | 83 | cpu_relax(); |
72 | /* reloaded WCRR from WLDR */ | 84 | /* reloaded WCRR from WLDR */ |
73 | } | 85 | } |
74 | 86 | ||
75 | static void omap_wdt_enable(void) | 87 | static void omap_wdt_enable(struct omap_wdt_dev *wdev) |
76 | { | 88 | { |
89 | void __iomem *base = wdev->base; | ||
90 | |||
77 | /* Sequence to enable the watchdog */ | 91 | /* Sequence to enable the watchdog */ |
78 | omap_writel(0xBBBB, OMAP_WATCHDOG_SPR); | 92 | __raw_writel(0xBBBB, base + OMAP_WATCHDOG_SPR); |
79 | while ((omap_readl(OMAP_WATCHDOG_WPS)) & 0x10) | 93 | while ((__raw_readl(base + OMAP_WATCHDOG_WPS)) & 0x10) |
80 | cpu_relax(); | 94 | cpu_relax(); |
81 | omap_writel(0x4444, OMAP_WATCHDOG_SPR); | 95 | |
82 | while ((omap_readl(OMAP_WATCHDOG_WPS)) & 0x10) | 96 | __raw_writel(0x4444, base + OMAP_WATCHDOG_SPR); |
97 | while ((__raw_readl(base + OMAP_WATCHDOG_WPS)) & 0x10) | ||
83 | cpu_relax(); | 98 | cpu_relax(); |
84 | } | 99 | } |
85 | 100 | ||
86 | static void omap_wdt_disable(void) | 101 | static void omap_wdt_disable(struct omap_wdt_dev *wdev) |
87 | { | 102 | { |
103 | void __iomem *base = wdev->base; | ||
104 | |||
88 | /* sequence required to disable watchdog */ | 105 | /* sequence required to disable watchdog */ |
89 | omap_writel(0xAAAA, OMAP_WATCHDOG_SPR); /* TIMER_MODE */ | 106 | __raw_writel(0xAAAA, base + OMAP_WATCHDOG_SPR); /* TIMER_MODE */ |
90 | while (omap_readl(OMAP_WATCHDOG_WPS) & 0x10) | 107 | while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x10) |
91 | cpu_relax(); | 108 | cpu_relax(); |
92 | omap_writel(0x5555, OMAP_WATCHDOG_SPR); /* TIMER_MODE */ | 109 | |
93 | while (omap_readl(OMAP_WATCHDOG_WPS) & 0x10) | 110 | __raw_writel(0x5555, base + OMAP_WATCHDOG_SPR); /* TIMER_MODE */ |
111 | while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x10) | ||
94 | cpu_relax(); | 112 | cpu_relax(); |
95 | } | 113 | } |
96 | 114 | ||
@@ -103,83 +121,90 @@ static void omap_wdt_adjust_timeout(unsigned new_timeout) | |||
103 | timer_margin = new_timeout; | 121 | timer_margin = new_timeout; |
104 | } | 122 | } |
105 | 123 | ||
106 | static void omap_wdt_set_timeout(void) | 124 | static void omap_wdt_set_timeout(struct omap_wdt_dev *wdev) |
107 | { | 125 | { |
108 | u32 pre_margin = GET_WLDR_VAL(timer_margin); | 126 | u32 pre_margin = GET_WLDR_VAL(timer_margin); |
127 | void __iomem *base = wdev->base; | ||
109 | 128 | ||
110 | /* just count up at 32 KHz */ | 129 | /* just count up at 32 KHz */ |
111 | while (omap_readl(OMAP_WATCHDOG_WPS) & 0x04) | 130 | while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x04) |
112 | cpu_relax(); | 131 | cpu_relax(); |
113 | omap_writel(pre_margin, OMAP_WATCHDOG_LDR); | 132 | |
114 | while (omap_readl(OMAP_WATCHDOG_WPS) & 0x04) | 133 | __raw_writel(pre_margin, base + OMAP_WATCHDOG_LDR); |
134 | while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x04) | ||
115 | cpu_relax(); | 135 | cpu_relax(); |
116 | } | 136 | } |
117 | 137 | ||
118 | /* | 138 | /* |
119 | * Allow only one task to hold it open | 139 | * Allow only one task to hold it open |
120 | */ | 140 | */ |
121 | |||
122 | static int omap_wdt_open(struct inode *inode, struct file *file) | 141 | static int omap_wdt_open(struct inode *inode, struct file *file) |
123 | { | 142 | { |
124 | if (test_and_set_bit(1, (unsigned long *)&omap_wdt_users)) | 143 | struct omap_wdt_dev *wdev = platform_get_drvdata(omap_wdt_dev); |
144 | void __iomem *base = wdev->base; | ||
145 | |||
146 | if (test_and_set_bit(1, (unsigned long *)&(wdev->omap_wdt_users))) | ||
125 | return -EBUSY; | 147 | return -EBUSY; |
126 | 148 | ||
127 | if (cpu_is_omap16xx()) | 149 | if (cpu_is_omap16xx()) |
128 | clk_enable(armwdt_ck); /* Enable the clock */ | 150 | clk_enable(wdev->armwdt_ck); /* Enable the clock */ |
129 | 151 | ||
130 | if (cpu_is_omap24xx()) { | 152 | if (cpu_is_omap24xx() || cpu_is_omap34xx()) { |
131 | clk_enable(mpu_wdt_ick); /* Enable the interface clock */ | 153 | clk_enable(wdev->mpu_wdt_ick); /* Enable the interface clock */ |
132 | clk_enable(mpu_wdt_fck); /* Enable the functional clock */ | 154 | clk_enable(wdev->mpu_wdt_fck); /* Enable the functional clock */ |
133 | } | 155 | } |
134 | 156 | ||
135 | /* initialize prescaler */ | 157 | /* initialize prescaler */ |
136 | while (omap_readl(OMAP_WATCHDOG_WPS) & 0x01) | 158 | while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x01) |
137 | cpu_relax(); | 159 | cpu_relax(); |
138 | omap_writel((1 << 5) | (PTV << 2), OMAP_WATCHDOG_CNTRL); | 160 | |
139 | while (omap_readl(OMAP_WATCHDOG_WPS) & 0x01) | 161 | __raw_writel((1 << 5) | (PTV << 2), base + OMAP_WATCHDOG_CNTRL); |
162 | while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x01) | ||
140 | cpu_relax(); | 163 | cpu_relax(); |
141 | 164 | ||
142 | omap_wdt_set_timeout(); | 165 | file->private_data = (void *) wdev; |
143 | omap_wdt_enable(); | 166 | |
167 | omap_wdt_set_timeout(wdev); | ||
168 | omap_wdt_enable(wdev); | ||
169 | |||
144 | return nonseekable_open(inode, file); | 170 | return nonseekable_open(inode, file); |
145 | } | 171 | } |
146 | 172 | ||
147 | static int omap_wdt_release(struct inode *inode, struct file *file) | 173 | static int omap_wdt_release(struct inode *inode, struct file *file) |
148 | { | 174 | { |
175 | struct omap_wdt_dev *wdev = file->private_data; | ||
176 | |||
149 | /* | 177 | /* |
150 | * Shut off the timer unless NOWAYOUT is defined. | 178 | * Shut off the timer unless NOWAYOUT is defined. |
151 | */ | 179 | */ |
152 | #ifndef CONFIG_WATCHDOG_NOWAYOUT | 180 | #ifndef CONFIG_WATCHDOG_NOWAYOUT |
153 | omap_wdt_disable(); | ||
154 | 181 | ||
155 | if (cpu_is_omap16xx()) { | 182 | omap_wdt_disable(wdev); |
156 | clk_disable(armwdt_ck); /* Disable the clock */ | ||
157 | clk_put(armwdt_ck); | ||
158 | armwdt_ck = NULL; | ||
159 | } | ||
160 | 183 | ||
161 | if (cpu_is_omap24xx()) { | 184 | if (cpu_is_omap16xx()) |
162 | clk_disable(mpu_wdt_ick); /* Disable the clock */ | 185 | clk_disable(wdev->armwdt_ck); /* Disable the clock */ |
163 | clk_disable(mpu_wdt_fck); /* Disable the clock */ | 186 | |
164 | clk_put(mpu_wdt_ick); | 187 | if (cpu_is_omap24xx() || cpu_is_omap34xx()) { |
165 | clk_put(mpu_wdt_fck); | 188 | clk_disable(wdev->mpu_wdt_ick); /* Disable the clock */ |
166 | mpu_wdt_ick = NULL; | 189 | clk_disable(wdev->mpu_wdt_fck); /* Disable the clock */ |
167 | mpu_wdt_fck = NULL; | ||
168 | } | 190 | } |
169 | #else | 191 | #else |
170 | printk(KERN_CRIT "omap_wdt: Unexpected close, not stopping!\n"); | 192 | printk(KERN_CRIT "omap_wdt: Unexpected close, not stopping!\n"); |
171 | #endif | 193 | #endif |
172 | omap_wdt_users = 0; | 194 | wdev->omap_wdt_users = 0; |
195 | |||
173 | return 0; | 196 | return 0; |
174 | } | 197 | } |
175 | 198 | ||
176 | static ssize_t omap_wdt_write(struct file *file, const char __user *data, | 199 | static ssize_t omap_wdt_write(struct file *file, const char __user *data, |
177 | size_t len, loff_t *ppos) | 200 | size_t len, loff_t *ppos) |
178 | { | 201 | { |
202 | struct omap_wdt_dev *wdev = file->private_data; | ||
203 | |||
179 | /* Refresh LOAD_TIME. */ | 204 | /* Refresh LOAD_TIME. */ |
180 | if (len) { | 205 | if (len) { |
181 | spin_lock(&wdt_lock); | 206 | spin_lock(&wdt_lock); |
182 | omap_wdt_ping(); | 207 | omap_wdt_ping(wdev); |
183 | spin_unlock(&wdt_lock); | 208 | spin_unlock(&wdt_lock); |
184 | } | 209 | } |
185 | return len; | 210 | return len; |
@@ -188,6 +213,7 @@ static ssize_t omap_wdt_write(struct file *file, const char __user *data, | |||
188 | static long omap_wdt_ioctl(struct file *file, unsigned int cmd, | 213 | static long omap_wdt_ioctl(struct file *file, unsigned int cmd, |
189 | unsigned long arg) | 214 | unsigned long arg) |
190 | { | 215 | { |
216 | struct omap_wdt_dev *wdev; | ||
191 | int new_margin; | 217 | int new_margin; |
192 | static const struct watchdog_info ident = { | 218 | static const struct watchdog_info ident = { |
193 | .identity = "OMAP Watchdog", | 219 | .identity = "OMAP Watchdog", |
@@ -195,6 +221,8 @@ static long omap_wdt_ioctl(struct file *file, unsigned int cmd, | |||
195 | .firmware_version = 0, | 221 | .firmware_version = 0, |
196 | }; | 222 | }; |
197 | 223 | ||
224 | wdev = file->private_data; | ||
225 | |||
198 | switch (cmd) { | 226 | switch (cmd) { |
199 | case WDIOC_GETSUPPORT: | 227 | case WDIOC_GETSUPPORT: |
200 | return copy_to_user((struct watchdog_info __user *)arg, &ident, | 228 | return copy_to_user((struct watchdog_info __user *)arg, &ident, |
@@ -203,14 +231,14 @@ static long omap_wdt_ioctl(struct file *file, unsigned int cmd, | |||
203 | return put_user(0, (int __user *)arg); | 231 | return put_user(0, (int __user *)arg); |
204 | case WDIOC_GETBOOTSTATUS: | 232 | case WDIOC_GETBOOTSTATUS: |
205 | if (cpu_is_omap16xx()) | 233 | if (cpu_is_omap16xx()) |
206 | return put_user(omap_readw(ARM_SYSST), | 234 | return put_user(__raw_readw(ARM_SYSST), |
207 | (int __user *)arg); | 235 | (int __user *)arg); |
208 | if (cpu_is_omap24xx()) | 236 | if (cpu_is_omap24xx()) |
209 | return put_user(omap_prcm_get_reset_sources(), | 237 | return put_user(omap_prcm_get_reset_sources(), |
210 | (int __user *)arg); | 238 | (int __user *)arg); |
211 | case WDIOC_KEEPALIVE: | 239 | case WDIOC_KEEPALIVE: |
212 | spin_lock(&wdt_lock); | 240 | spin_lock(&wdt_lock); |
213 | omap_wdt_ping(); | 241 | omap_wdt_ping(wdev); |
214 | spin_unlock(&wdt_lock); | 242 | spin_unlock(&wdt_lock); |
215 | return 0; | 243 | return 0; |
216 | case WDIOC_SETTIMEOUT: | 244 | case WDIOC_SETTIMEOUT: |
@@ -219,11 +247,11 @@ static long omap_wdt_ioctl(struct file *file, unsigned int cmd, | |||
219 | omap_wdt_adjust_timeout(new_margin); | 247 | omap_wdt_adjust_timeout(new_margin); |
220 | 248 | ||
221 | spin_lock(&wdt_lock); | 249 | spin_lock(&wdt_lock); |
222 | omap_wdt_disable(); | 250 | omap_wdt_disable(wdev); |
223 | omap_wdt_set_timeout(); | 251 | omap_wdt_set_timeout(wdev); |
224 | omap_wdt_enable(); | 252 | omap_wdt_enable(wdev); |
225 | 253 | ||
226 | omap_wdt_ping(); | 254 | omap_wdt_ping(wdev); |
227 | spin_unlock(&wdt_lock); | 255 | spin_unlock(&wdt_lock); |
228 | /* Fall */ | 256 | /* Fall */ |
229 | case WDIOC_GETTIMEOUT: | 257 | case WDIOC_GETTIMEOUT: |
@@ -241,96 +269,173 @@ static const struct file_operations omap_wdt_fops = { | |||
241 | .release = omap_wdt_release, | 269 | .release = omap_wdt_release, |
242 | }; | 270 | }; |
243 | 271 | ||
244 | static struct miscdevice omap_wdt_miscdev = { | ||
245 | .minor = WATCHDOG_MINOR, | ||
246 | .name = "watchdog", | ||
247 | .fops = &omap_wdt_fops, | ||
248 | }; | ||
249 | |||
250 | static int __init omap_wdt_probe(struct platform_device *pdev) | 272 | static int __init omap_wdt_probe(struct platform_device *pdev) |
251 | { | 273 | { |
252 | struct resource *res, *mem; | 274 | struct resource *res, *mem; |
275 | struct omap_wdt_dev *wdev; | ||
253 | int ret; | 276 | int ret; |
254 | 277 | ||
255 | /* reserve static register mappings */ | 278 | /* reserve static register mappings */ |
256 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 279 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
257 | if (!res) | 280 | if (!res) { |
258 | return -ENOENT; | 281 | ret = -ENOENT; |
282 | goto err_get_resource; | ||
283 | } | ||
284 | |||
285 | if (omap_wdt_dev) { | ||
286 | ret = -EBUSY; | ||
287 | goto err_busy; | ||
288 | } | ||
259 | 289 | ||
260 | mem = request_mem_region(res->start, res->end - res->start + 1, | 290 | mem = request_mem_region(res->start, res->end - res->start + 1, |
261 | pdev->name); | 291 | pdev->name); |
262 | if (mem == NULL) | 292 | if (!mem) { |
263 | return -EBUSY; | 293 | ret = -EBUSY; |
294 | goto err_busy; | ||
295 | } | ||
264 | 296 | ||
265 | platform_set_drvdata(pdev, mem); | 297 | wdev = kzalloc(sizeof(struct omap_wdt_dev), GFP_KERNEL); |
298 | if (!wdev) { | ||
299 | ret = -ENOMEM; | ||
300 | goto err_kzalloc; | ||
301 | } | ||
266 | 302 | ||
267 | omap_wdt_users = 0; | 303 | wdev->omap_wdt_users = 0; |
304 | wdev->mem = mem; | ||
268 | 305 | ||
269 | if (cpu_is_omap16xx()) { | 306 | if (cpu_is_omap16xx()) { |
270 | armwdt_ck = clk_get(&pdev->dev, "armwdt_ck"); | 307 | wdev->armwdt_ck = clk_get(&pdev->dev, "armwdt_ck"); |
271 | if (IS_ERR(armwdt_ck)) { | 308 | if (IS_ERR(wdev->armwdt_ck)) { |
272 | ret = PTR_ERR(armwdt_ck); | 309 | ret = PTR_ERR(wdev->armwdt_ck); |
273 | armwdt_ck = NULL; | 310 | wdev->armwdt_ck = NULL; |
274 | goto fail; | 311 | goto err_clk; |
275 | } | 312 | } |
276 | } | 313 | } |
277 | 314 | ||
278 | if (cpu_is_omap24xx()) { | 315 | if (cpu_is_omap24xx()) { |
279 | mpu_wdt_ick = clk_get(&pdev->dev, "mpu_wdt_ick"); | 316 | wdev->mpu_wdt_ick = clk_get(&pdev->dev, "mpu_wdt_ick"); |
280 | if (IS_ERR(mpu_wdt_ick)) { | 317 | if (IS_ERR(wdev->mpu_wdt_ick)) { |
281 | ret = PTR_ERR(mpu_wdt_ick); | 318 | ret = PTR_ERR(wdev->mpu_wdt_ick); |
282 | mpu_wdt_ick = NULL; | 319 | wdev->mpu_wdt_ick = NULL; |
283 | goto fail; | 320 | goto err_clk; |
284 | } | 321 | } |
285 | mpu_wdt_fck = clk_get(&pdev->dev, "mpu_wdt_fck"); | 322 | wdev->mpu_wdt_fck = clk_get(&pdev->dev, "mpu_wdt_fck"); |
286 | if (IS_ERR(mpu_wdt_fck)) { | 323 | if (IS_ERR(wdev->mpu_wdt_fck)) { |
287 | ret = PTR_ERR(mpu_wdt_fck); | 324 | ret = PTR_ERR(wdev->mpu_wdt_fck); |
288 | mpu_wdt_fck = NULL; | 325 | wdev->mpu_wdt_fck = NULL; |
289 | goto fail; | 326 | goto err_clk; |
290 | } | 327 | } |
291 | } | 328 | } |
292 | 329 | ||
293 | omap_wdt_disable(); | 330 | if (cpu_is_omap34xx()) { |
331 | wdev->mpu_wdt_ick = clk_get(&pdev->dev, "wdt2_ick"); | ||
332 | if (IS_ERR(wdev->mpu_wdt_ick)) { | ||
333 | ret = PTR_ERR(wdev->mpu_wdt_ick); | ||
334 | wdev->mpu_wdt_ick = NULL; | ||
335 | goto err_clk; | ||
336 | } | ||
337 | wdev->mpu_wdt_fck = clk_get(&pdev->dev, "wdt2_fck"); | ||
338 | if (IS_ERR(wdev->mpu_wdt_fck)) { | ||
339 | ret = PTR_ERR(wdev->mpu_wdt_fck); | ||
340 | wdev->mpu_wdt_fck = NULL; | ||
341 | goto err_clk; | ||
342 | } | ||
343 | } | ||
344 | wdev->base = ioremap(res->start, res->end - res->start + 1); | ||
345 | if (!wdev->base) { | ||
346 | ret = -ENOMEM; | ||
347 | goto err_ioremap; | ||
348 | } | ||
349 | |||
350 | platform_set_drvdata(pdev, wdev); | ||
351 | |||
352 | omap_wdt_disable(wdev); | ||
294 | omap_wdt_adjust_timeout(timer_margin); | 353 | omap_wdt_adjust_timeout(timer_margin); |
295 | 354 | ||
296 | omap_wdt_miscdev.parent = &pdev->dev; | 355 | wdev->omap_wdt_miscdev.parent = &pdev->dev; |
297 | ret = misc_register(&omap_wdt_miscdev); | 356 | wdev->omap_wdt_miscdev.minor = WATCHDOG_MINOR; |
357 | wdev->omap_wdt_miscdev.name = "watchdog"; | ||
358 | wdev->omap_wdt_miscdev.fops = &omap_wdt_fops; | ||
359 | |||
360 | ret = misc_register(&(wdev->omap_wdt_miscdev)); | ||
298 | if (ret) | 361 | if (ret) |
299 | goto fail; | 362 | goto err_misc; |
300 | 363 | ||
301 | pr_info("OMAP Watchdog Timer: initial timeout %d sec\n", timer_margin); | 364 | pr_info("OMAP Watchdog Timer Rev 0x%02x: initial timeout %d sec\n", |
365 | __raw_readl(wdev->base + OMAP_WATCHDOG_REV) & 0xFF, | ||
366 | timer_margin); | ||
302 | 367 | ||
303 | /* autogate OCP interface clock */ | 368 | /* autogate OCP interface clock */ |
304 | omap_writel(0x01, OMAP_WATCHDOG_SYS_CONFIG); | 369 | __raw_writel(0x01, wdev->base + OMAP_WATCHDOG_SYS_CONFIG); |
370 | |||
371 | omap_wdt_dev = pdev; | ||
372 | |||
305 | return 0; | 373 | return 0; |
306 | 374 | ||
307 | fail: | 375 | err_misc: |
308 | if (armwdt_ck) | 376 | platform_set_drvdata(pdev, NULL); |
309 | clk_put(armwdt_ck); | 377 | iounmap(wdev->base); |
310 | if (mpu_wdt_ick) | 378 | |
311 | clk_put(mpu_wdt_ick); | 379 | err_ioremap: |
312 | if (mpu_wdt_fck) | 380 | wdev->base = NULL; |
313 | clk_put(mpu_wdt_fck); | 381 | |
314 | release_resource(mem); | 382 | err_clk: |
383 | if (wdev->armwdt_ck) | ||
384 | clk_put(wdev->armwdt_ck); | ||
385 | if (wdev->mpu_wdt_ick) | ||
386 | clk_put(wdev->mpu_wdt_ick); | ||
387 | if (wdev->mpu_wdt_fck) | ||
388 | clk_put(wdev->mpu_wdt_fck); | ||
389 | kfree(wdev); | ||
390 | |||
391 | err_kzalloc: | ||
392 | release_mem_region(res->start, res->end - res->start + 1); | ||
393 | |||
394 | err_busy: | ||
395 | err_get_resource: | ||
396 | |||
315 | return ret; | 397 | return ret; |
316 | } | 398 | } |
317 | 399 | ||
318 | static void omap_wdt_shutdown(struct platform_device *pdev) | 400 | static void omap_wdt_shutdown(struct platform_device *pdev) |
319 | { | 401 | { |
320 | omap_wdt_disable(); | 402 | struct omap_wdt_dev *wdev = platform_get_drvdata(pdev); |
403 | |||
404 | if (wdev->omap_wdt_users) | ||
405 | omap_wdt_disable(wdev); | ||
321 | } | 406 | } |
322 | 407 | ||
323 | static int omap_wdt_remove(struct platform_device *pdev) | 408 | static int omap_wdt_remove(struct platform_device *pdev) |
324 | { | 409 | { |
325 | struct resource *mem = platform_get_drvdata(pdev); | 410 | struct omap_wdt_dev *wdev = platform_get_drvdata(pdev); |
326 | misc_deregister(&omap_wdt_miscdev); | 411 | struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
327 | release_resource(mem); | 412 | |
328 | if (armwdt_ck) | 413 | if (!res) |
329 | clk_put(armwdt_ck); | 414 | return -ENOENT; |
330 | if (mpu_wdt_ick) | 415 | |
331 | clk_put(mpu_wdt_ick); | 416 | misc_deregister(&(wdev->omap_wdt_miscdev)); |
332 | if (mpu_wdt_fck) | 417 | release_mem_region(res->start, res->end - res->start + 1); |
333 | clk_put(mpu_wdt_fck); | 418 | platform_set_drvdata(pdev, NULL); |
419 | |||
420 | if (wdev->armwdt_ck) { | ||
421 | clk_put(wdev->armwdt_ck); | ||
422 | wdev->armwdt_ck = NULL; | ||
423 | } | ||
424 | |||
425 | if (wdev->mpu_wdt_ick) { | ||
426 | clk_put(wdev->mpu_wdt_ick); | ||
427 | wdev->mpu_wdt_ick = NULL; | ||
428 | } | ||
429 | |||
430 | if (wdev->mpu_wdt_fck) { | ||
431 | clk_put(wdev->mpu_wdt_fck); | ||
432 | wdev->mpu_wdt_fck = NULL; | ||
433 | } | ||
434 | iounmap(wdev->base); | ||
435 | |||
436 | kfree(wdev); | ||
437 | omap_wdt_dev = NULL; | ||
438 | |||
334 | return 0; | 439 | return 0; |
335 | } | 440 | } |
336 | 441 | ||
@@ -344,17 +449,23 @@ static int omap_wdt_remove(struct platform_device *pdev) | |||
344 | 449 | ||
345 | static int omap_wdt_suspend(struct platform_device *pdev, pm_message_t state) | 450 | static int omap_wdt_suspend(struct platform_device *pdev, pm_message_t state) |
346 | { | 451 | { |
347 | if (omap_wdt_users) | 452 | struct omap_wdt_dev *wdev = platform_get_drvdata(pdev); |
348 | omap_wdt_disable(); | 453 | |
454 | if (wdev->omap_wdt_users) | ||
455 | omap_wdt_disable(wdev); | ||
456 | |||
349 | return 0; | 457 | return 0; |
350 | } | 458 | } |
351 | 459 | ||
352 | static int omap_wdt_resume(struct platform_device *pdev) | 460 | static int omap_wdt_resume(struct platform_device *pdev) |
353 | { | 461 | { |
354 | if (omap_wdt_users) { | 462 | struct omap_wdt_dev *wdev = platform_get_drvdata(pdev); |
355 | omap_wdt_enable(); | 463 | |
356 | omap_wdt_ping(); | 464 | if (wdev->omap_wdt_users) { |
465 | omap_wdt_enable(wdev); | ||
466 | omap_wdt_ping(wdev); | ||
357 | } | 467 | } |
468 | |||
358 | return 0; | 469 | return 0; |
359 | } | 470 | } |
360 | 471 | ||
diff --git a/drivers/watchdog/omap_wdt.h b/drivers/watchdog/omap_wdt.h index 52a532a5114a..fc02ec6a0386 100644 --- a/drivers/watchdog/omap_wdt.h +++ b/drivers/watchdog/omap_wdt.h | |||
@@ -30,25 +30,15 @@ | |||
30 | #ifndef _OMAP_WATCHDOG_H | 30 | #ifndef _OMAP_WATCHDOG_H |
31 | #define _OMAP_WATCHDOG_H | 31 | #define _OMAP_WATCHDOG_H |
32 | 32 | ||
33 | #define OMAP1610_WATCHDOG_BASE 0xfffeb000 | 33 | #define OMAP_WATCHDOG_REV (0x00) |
34 | #define OMAP2420_WATCHDOG_BASE 0x48022000 /*WDT Timer 2 */ | 34 | #define OMAP_WATCHDOG_SYS_CONFIG (0x10) |
35 | 35 | #define OMAP_WATCHDOG_STATUS (0x14) | |
36 | #ifdef CONFIG_ARCH_OMAP24XX | 36 | #define OMAP_WATCHDOG_CNTRL (0x24) |
37 | #define OMAP_WATCHDOG_BASE OMAP2420_WATCHDOG_BASE | 37 | #define OMAP_WATCHDOG_CRR (0x28) |
38 | #else | 38 | #define OMAP_WATCHDOG_LDR (0x2c) |
39 | #define OMAP_WATCHDOG_BASE OMAP1610_WATCHDOG_BASE | 39 | #define OMAP_WATCHDOG_TGR (0x30) |
40 | #define RM_RSTST_WKUP 0 | 40 | #define OMAP_WATCHDOG_WPS (0x34) |
41 | #endif | 41 | #define OMAP_WATCHDOG_SPR (0x48) |
42 | |||
43 | #define OMAP_WATCHDOG_REV (OMAP_WATCHDOG_BASE + 0x00) | ||
44 | #define OMAP_WATCHDOG_SYS_CONFIG (OMAP_WATCHDOG_BASE + 0x10) | ||
45 | #define OMAP_WATCHDOG_STATUS (OMAP_WATCHDOG_BASE + 0x14) | ||
46 | #define OMAP_WATCHDOG_CNTRL (OMAP_WATCHDOG_BASE + 0x24) | ||
47 | #define OMAP_WATCHDOG_CRR (OMAP_WATCHDOG_BASE + 0x28) | ||
48 | #define OMAP_WATCHDOG_LDR (OMAP_WATCHDOG_BASE + 0x2c) | ||
49 | #define OMAP_WATCHDOG_TGR (OMAP_WATCHDOG_BASE + 0x30) | ||
50 | #define OMAP_WATCHDOG_WPS (OMAP_WATCHDOG_BASE + 0x34) | ||
51 | #define OMAP_WATCHDOG_SPR (OMAP_WATCHDOG_BASE + 0x48) | ||
52 | 42 | ||
53 | /* Using the prescaler, the OMAP watchdog could go for many | 43 | /* Using the prescaler, the OMAP watchdog could go for many |
54 | * months before firing. These limits work without scaling, | 44 | * months before firing. These limits work without scaling, |
diff --git a/drivers/watchdog/orion5x_wdt.c b/drivers/watchdog/orion5x_wdt.c new file mode 100644 index 000000000000..14a339f58b6a --- /dev/null +++ b/drivers/watchdog/orion5x_wdt.c | |||
@@ -0,0 +1,245 @@ | |||
1 | /* | ||
2 | * drivers/watchdog/orion5x_wdt.c | ||
3 | * | ||
4 | * Watchdog driver for Orion5x processors | ||
5 | * | ||
6 | * Author: Sylver Bruneau <sylver.bruneau@googlemail.com> | ||
7 | * | ||
8 | * This file is licensed under the terms of the GNU General Public | ||
9 | * License version 2. This program is licensed "as is" without any | ||
10 | * warranty of any kind, whether express or implied. | ||
11 | */ | ||
12 | |||
13 | #include <linux/module.h> | ||
14 | #include <linux/moduleparam.h> | ||
15 | #include <linux/types.h> | ||
16 | #include <linux/kernel.h> | ||
17 | #include <linux/fs.h> | ||
18 | #include <linux/miscdevice.h> | ||
19 | #include <linux/watchdog.h> | ||
20 | #include <linux/init.h> | ||
21 | #include <linux/uaccess.h> | ||
22 | #include <linux/io.h> | ||
23 | #include <linux/spinlock.h> | ||
24 | |||
25 | /* | ||
26 | * Watchdog timer block registers. | ||
27 | */ | ||
28 | #define TIMER_CTRL (TIMER_VIRT_BASE + 0x0000) | ||
29 | #define WDT_EN 0x0010 | ||
30 | #define WDT_VAL (TIMER_VIRT_BASE + 0x0024) | ||
31 | |||
32 | #define WDT_MAX_DURATION (0xffffffff / ORION5X_TCLK) | ||
33 | #define WDT_IN_USE 0 | ||
34 | #define WDT_OK_TO_CLOSE 1 | ||
35 | |||
36 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
37 | static int heartbeat = WDT_MAX_DURATION; /* (seconds) */ | ||
38 | static unsigned long wdt_status; | ||
39 | static spinlock_t wdt_lock; | ||
40 | |||
41 | static void wdt_enable(void) | ||
42 | { | ||
43 | u32 reg; | ||
44 | |||
45 | spin_lock(&wdt_lock); | ||
46 | |||
47 | /* Set watchdog duration */ | ||
48 | writel(ORION5X_TCLK * heartbeat, WDT_VAL); | ||
49 | |||
50 | /* Clear watchdog timer interrupt */ | ||
51 | reg = readl(BRIDGE_CAUSE); | ||
52 | reg &= ~WDT_INT_REQ; | ||
53 | writel(reg, BRIDGE_CAUSE); | ||
54 | |||
55 | /* Enable watchdog timer */ | ||
56 | reg = readl(TIMER_CTRL); | ||
57 | reg |= WDT_EN; | ||
58 | writel(reg, TIMER_CTRL); | ||
59 | |||
60 | /* Enable reset on watchdog */ | ||
61 | reg = readl(CPU_RESET_MASK); | ||
62 | reg |= WDT_RESET; | ||
63 | writel(reg, CPU_RESET_MASK); | ||
64 | |||
65 | spin_unlock(&wdt_lock); | ||
66 | } | ||
67 | |||
68 | static void wdt_disable(void) | ||
69 | { | ||
70 | u32 reg; | ||
71 | |||
72 | spin_lock(&wdt_lock); | ||
73 | |||
74 | /* Disable reset on watchdog */ | ||
75 | reg = readl(CPU_RESET_MASK); | ||
76 | reg &= ~WDT_RESET; | ||
77 | writel(reg, CPU_RESET_MASK); | ||
78 | |||
79 | /* Disable watchdog timer */ | ||
80 | reg = readl(TIMER_CTRL); | ||
81 | reg &= ~WDT_EN; | ||
82 | writel(reg, TIMER_CTRL); | ||
83 | |||
84 | spin_unlock(&wdt_lock); | ||
85 | } | ||
86 | |||
87 | static int orion5x_wdt_get_timeleft(int *time_left) | ||
88 | { | ||
89 | spin_lock(&wdt_lock); | ||
90 | *time_left = readl(WDT_VAL) / ORION5X_TCLK; | ||
91 | spin_unlock(&wdt_lock); | ||
92 | return 0; | ||
93 | } | ||
94 | |||
95 | static int orion5x_wdt_open(struct inode *inode, struct file *file) | ||
96 | { | ||
97 | if (test_and_set_bit(WDT_IN_USE, &wdt_status)) | ||
98 | return -EBUSY; | ||
99 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
100 | wdt_enable(); | ||
101 | return nonseekable_open(inode, file); | ||
102 | } | ||
103 | |||
104 | static ssize_t orion5x_wdt_write(struct file *file, const char *data, | ||
105 | size_t len, loff_t *ppos) | ||
106 | { | ||
107 | if (len) { | ||
108 | if (!nowayout) { | ||
109 | size_t i; | ||
110 | |||
111 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
112 | for (i = 0; i != len; i++) { | ||
113 | char c; | ||
114 | |||
115 | if (get_user(c, data + i)) | ||
116 | return -EFAULT; | ||
117 | if (c == 'V') | ||
118 | set_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
119 | } | ||
120 | } | ||
121 | wdt_enable(); | ||
122 | } | ||
123 | return len; | ||
124 | } | ||
125 | |||
126 | static struct watchdog_info ident = { | ||
127 | .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | | ||
128 | WDIOF_KEEPALIVEPING, | ||
129 | .identity = "Orion5x Watchdog", | ||
130 | }; | ||
131 | |||
132 | |||
133 | static long orion5x_wdt_ioctl(struct file *file, unsigned int cmd, | ||
134 | unsigned long arg) | ||
135 | { | ||
136 | int ret = -ENOTTY; | ||
137 | int time; | ||
138 | |||
139 | switch (cmd) { | ||
140 | case WDIOC_GETSUPPORT: | ||
141 | ret = copy_to_user((struct watchdog_info *)arg, &ident, | ||
142 | sizeof(ident)) ? -EFAULT : 0; | ||
143 | break; | ||
144 | |||
145 | case WDIOC_GETSTATUS: | ||
146 | case WDIOC_GETBOOTSTATUS: | ||
147 | ret = put_user(0, (int *)arg); | ||
148 | break; | ||
149 | |||
150 | case WDIOC_KEEPALIVE: | ||
151 | wdt_enable(); | ||
152 | ret = 0; | ||
153 | break; | ||
154 | |||
155 | case WDIOC_SETTIMEOUT: | ||
156 | ret = get_user(time, (int *)arg); | ||
157 | if (ret) | ||
158 | break; | ||
159 | |||
160 | if (time <= 0 || time > WDT_MAX_DURATION) { | ||
161 | ret = -EINVAL; | ||
162 | break; | ||
163 | } | ||
164 | heartbeat = time; | ||
165 | wdt_enable(); | ||
166 | /* Fall through */ | ||
167 | |||
168 | case WDIOC_GETTIMEOUT: | ||
169 | ret = put_user(heartbeat, (int *)arg); | ||
170 | break; | ||
171 | |||
172 | case WDIOC_GETTIMELEFT: | ||
173 | if (orion5x_wdt_get_timeleft(&time)) { | ||
174 | ret = -EINVAL; | ||
175 | break; | ||
176 | } | ||
177 | ret = put_user(time, (int *)arg); | ||
178 | break; | ||
179 | } | ||
180 | return ret; | ||
181 | } | ||
182 | |||
183 | static int orion5x_wdt_release(struct inode *inode, struct file *file) | ||
184 | { | ||
185 | if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) | ||
186 | wdt_disable(); | ||
187 | else | ||
188 | printk(KERN_CRIT "WATCHDOG: Device closed unexpectedly - " | ||
189 | "timer will not stop\n"); | ||
190 | clear_bit(WDT_IN_USE, &wdt_status); | ||
191 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | ||
192 | |||
193 | return 0; | ||
194 | } | ||
195 | |||
196 | |||
197 | static const struct file_operations orion5x_wdt_fops = { | ||
198 | .owner = THIS_MODULE, | ||
199 | .llseek = no_llseek, | ||
200 | .write = orion5x_wdt_write, | ||
201 | .unlocked_ioctl = orion5x_wdt_ioctl, | ||
202 | .open = orion5x_wdt_open, | ||
203 | .release = orion5x_wdt_release, | ||
204 | }; | ||
205 | |||
206 | static struct miscdevice orion5x_wdt_miscdev = { | ||
207 | .minor = WATCHDOG_MINOR, | ||
208 | .name = "watchdog", | ||
209 | .fops = &orion5x_wdt_fops, | ||
210 | }; | ||
211 | |||
212 | static int __init orion5x_wdt_init(void) | ||
213 | { | ||
214 | int ret; | ||
215 | |||
216 | spin_lock_init(&wdt_lock); | ||
217 | |||
218 | ret = misc_register(&orion5x_wdt_miscdev); | ||
219 | if (ret == 0) | ||
220 | printk("Orion5x Watchdog Timer: heartbeat %d sec\n", | ||
221 | heartbeat); | ||
222 | |||
223 | return ret; | ||
224 | } | ||
225 | |||
226 | static void __exit orion5x_wdt_exit(void) | ||
227 | { | ||
228 | misc_deregister(&orion5x_wdt_miscdev); | ||
229 | } | ||
230 | |||
231 | module_init(orion5x_wdt_init); | ||
232 | module_exit(orion5x_wdt_exit); | ||
233 | |||
234 | MODULE_AUTHOR("Sylver Bruneau <sylver.bruneau@googlemail.com>"); | ||
235 | MODULE_DESCRIPTION("Orion5x Processor Watchdog"); | ||
236 | |||
237 | module_param(heartbeat, int, 0); | ||
238 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default is " | ||
239 | __MODULE_STRING(WDT_MAX_DURATION) ")"); | ||
240 | |||
241 | module_param(nowayout, int, 0); | ||
242 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); | ||
243 | |||
244 | MODULE_LICENSE("GPL"); | ||
245 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
diff --git a/drivers/watchdog/w83697ug_wdt.c b/drivers/watchdog/w83697ug_wdt.c new file mode 100644 index 000000000000..c73b5e2919c6 --- /dev/null +++ b/drivers/watchdog/w83697ug_wdt.c | |||
@@ -0,0 +1,392 @@ | |||
1 | /* | ||
2 | * w83697ug/uf WDT driver | ||
3 | * | ||
4 | * (c) Copyright 2008 Flemming Fransen <ff@nrvissing.net> | ||
5 | * reused original code to supoprt w83697ug/uf. | ||
6 | * | ||
7 | * Based on w83627hf_wdt.c which is based on advantechwdt.c | ||
8 | * which is based on wdt.c. | ||
9 | * Original copyright messages: | ||
10 | * | ||
11 | * (c) Copyright 2007 Vlad Drukker <vlad@storewiz.com> | ||
12 | * added support for W83627THF. | ||
13 | * | ||
14 | * (c) Copyright 2003 Pádraig Brady <P@draigBrady.com> | ||
15 | * | ||
16 | * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl> | ||
17 | * | ||
18 | * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. | ||
19 | * http://www.redhat.com | ||
20 | * | ||
21 | * This program is free software; you can redistribute it and/or | ||
22 | * modify it under the terms of the GNU General Public License | ||
23 | * as published by the Free Software Foundation; either version | ||
24 | * 2 of the License, or (at your option) any later version. | ||
25 | * | ||
26 | * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide | ||
27 | * warranty for any of this software. This material is provided | ||
28 | * "AS-IS" and at no charge. | ||
29 | * | ||
30 | * (c) Copyright 1995 Alan Cox <alan@redhat.com> | ||
31 | */ | ||
32 | |||
33 | #include <linux/module.h> | ||
34 | #include <linux/moduleparam.h> | ||
35 | #include <linux/types.h> | ||
36 | #include <linux/miscdevice.h> | ||
37 | #include <linux/watchdog.h> | ||
38 | #include <linux/fs.h> | ||
39 | #include <linux/ioport.h> | ||
40 | #include <linux/notifier.h> | ||
41 | #include <linux/reboot.h> | ||
42 | #include <linux/init.h> | ||
43 | #include <linux/spinlock.h> | ||
44 | #include <linux/io.h> | ||
45 | #include <linux/uaccess.h> | ||
46 | |||
47 | #include <asm/system.h> | ||
48 | |||
49 | #define WATCHDOG_NAME "w83697ug/uf WDT" | ||
50 | #define PFX WATCHDOG_NAME ": " | ||
51 | #define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ | ||
52 | |||
53 | static unsigned long wdt_is_open; | ||
54 | static char expect_close; | ||
55 | static DEFINE_SPINLOCK(io_lock); | ||
56 | |||
57 | static int wdt_io = 0x2e; | ||
58 | module_param(wdt_io, int, 0); | ||
59 | MODULE_PARM_DESC(wdt_io, "w83697ug/uf WDT io port (default 0x2e)"); | ||
60 | |||
61 | static int timeout = WATCHDOG_TIMEOUT; /* in seconds */ | ||
62 | module_param(timeout, int, 0); | ||
63 | MODULE_PARM_DESC(timeout, | ||
64 | "Watchdog timeout in seconds. 1<= timeout <=255 (default=" | ||
65 | __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); | ||
66 | |||
67 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
68 | module_param(nowayout, int, 0); | ||
69 | MODULE_PARM_DESC(nowayout, | ||
70 | "Watchdog cannot be stopped once started (default=" | ||
71 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
72 | |||
73 | /* | ||
74 | * Kernel methods. | ||
75 | */ | ||
76 | |||
77 | #define WDT_EFER (wdt_io+0) /* Extended Function Enable Registers */ | ||
78 | #define WDT_EFIR (wdt_io+0) /* Extended Function Index Register | ||
79 | (same as EFER) */ | ||
80 | #define WDT_EFDR (WDT_EFIR+1) /* Extended Function Data Register */ | ||
81 | |||
82 | static void w83697ug_select_wd_register(void) | ||
83 | { | ||
84 | unsigned char c; | ||
85 | unsigned char version; | ||
86 | |||
87 | outb_p(0x87, WDT_EFER); /* Enter extended function mode */ | ||
88 | outb_p(0x87, WDT_EFER); /* Again according to manual */ | ||
89 | |||
90 | outb(0x20, WDT_EFER); /* check chip version */ | ||
91 | version = inb(WDT_EFDR); | ||
92 | |||
93 | if (version == 0x68) { /* W83697UG */ | ||
94 | printk(KERN_INFO PFX "Watchdog chip version 0x%02x = " | ||
95 | "W83697UG/UF found at 0x%04x\n", version, wdt_io); | ||
96 | |||
97 | outb_p(0x2b, WDT_EFER); | ||
98 | c = inb_p(WDT_EFDR); /* select WDT0 */ | ||
99 | c &= ~0x04; | ||
100 | outb_p(0x2b, WDT_EFER); | ||
101 | outb_p(c, WDT_EFDR); /* set pin118 to WDT0 */ | ||
102 | |||
103 | } else { | ||
104 | printk(KERN_ERR PFX "No W83697UG/UF could be found\n"); | ||
105 | return -EIO; | ||
106 | } | ||
107 | |||
108 | outb_p(0x07, WDT_EFER); /* point to logical device number reg */ | ||
109 | outb_p(0x08, WDT_EFDR); /* select logical device 8 (GPIO2) */ | ||
110 | outb_p(0x30, WDT_EFER); /* select CR30 */ | ||
111 | c = inb_p(WDT_EFDR); | ||
112 | outb_p(c || 0x01, WDT_EFDR); /* set bit 0 to activate GPIO2 */ | ||
113 | } | ||
114 | |||
115 | static void w83697ug_unselect_wd_register(void) | ||
116 | { | ||
117 | outb_p(0xAA, WDT_EFER); /* Leave extended function mode */ | ||
118 | } | ||
119 | |||
120 | static void w83697ug_init(void) | ||
121 | { | ||
122 | unsigned char t; | ||
123 | |||
124 | w83697ug_select_wd_register(); | ||
125 | |||
126 | outb_p(0xF6, WDT_EFER); /* Select CRF6 */ | ||
127 | t = inb_p(WDT_EFDR); /* read CRF6 */ | ||
128 | if (t != 0) { | ||
129 | printk(KERN_INFO PFX "Watchdog already running." | ||
130 | " Resetting timeout to %d sec\n", timeout); | ||
131 | outb_p(timeout, WDT_EFDR); /* Write back to CRF6 */ | ||
132 | } | ||
133 | outb_p(0xF5, WDT_EFER); /* Select CRF5 */ | ||
134 | t = inb_p(WDT_EFDR); /* read CRF5 */ | ||
135 | t &= ~0x0C; /* set second mode & | ||
136 | disable keyboard turning off watchdog */ | ||
137 | outb_p(t, WDT_EFDR); /* Write back to CRF5 */ | ||
138 | |||
139 | w83697ug_unselect_wd_register(); | ||
140 | } | ||
141 | |||
142 | static void wdt_ctrl(int timeout) | ||
143 | { | ||
144 | spin_lock(&io_lock); | ||
145 | |||
146 | w83697ug_select_wd_register(); | ||
147 | |||
148 | outb_p(0xF4, WDT_EFER); /* Select CRF4 */ | ||
149 | outb_p(timeout, WDT_EFDR); /* Write Timeout counter to CRF4 */ | ||
150 | |||
151 | w83697ug_unselect_wd_register(); | ||
152 | |||
153 | spin_unlock(&io_lock); | ||
154 | } | ||
155 | |||
156 | static int wdt_ping(void) | ||
157 | { | ||
158 | wdt_ctrl(timeout); | ||
159 | return 0; | ||
160 | } | ||
161 | |||
162 | static int wdt_disable(void) | ||
163 | { | ||
164 | wdt_ctrl(0); | ||
165 | return 0; | ||
166 | } | ||
167 | |||
168 | static int wdt_set_heartbeat(int t) | ||
169 | { | ||
170 | if (t < 1 || t > 255) | ||
171 | return -EINVAL; | ||
172 | |||
173 | timeout = t; | ||
174 | return 0; | ||
175 | } | ||
176 | |||
177 | static ssize_t wdt_write(struct file *file, const char __user *buf, | ||
178 | size_t count, loff_t *ppos) | ||
179 | { | ||
180 | if (count) { | ||
181 | if (!nowayout) { | ||
182 | size_t i; | ||
183 | |||
184 | expect_close = 0; | ||
185 | |||
186 | for (i = 0; i != count; i++) { | ||
187 | char c; | ||
188 | if (get_user(c, buf + i)) | ||
189 | return -EFAULT; | ||
190 | if (c == 'V') | ||
191 | expect_close = 42; | ||
192 | } | ||
193 | } | ||
194 | wdt_ping(); | ||
195 | } | ||
196 | return count; | ||
197 | } | ||
198 | |||
199 | static long wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) | ||
200 | { | ||
201 | void __user *argp = (void __user *)arg; | ||
202 | int __user *p = argp; | ||
203 | int new_timeout; | ||
204 | static const struct watchdog_info ident = { | ||
205 | .options = WDIOF_KEEPALIVEPING | | ||
206 | WDIOF_SETTIMEOUT | | ||
207 | WDIOF_MAGICCLOSE, | ||
208 | .firmware_version = 1, | ||
209 | .identity = "W83697UG WDT", | ||
210 | }; | ||
211 | |||
212 | switch (cmd) { | ||
213 | case WDIOC_GETSUPPORT: | ||
214 | if (copy_to_user(argp, &ident, sizeof(ident))) | ||
215 | return -EFAULT; | ||
216 | break; | ||
217 | |||
218 | case WDIOC_GETSTATUS: | ||
219 | case WDIOC_GETBOOTSTATUS: | ||
220 | return put_user(0, p); | ||
221 | |||
222 | case WDIOC_SETOPTIONS: | ||
223 | { | ||
224 | int options, retval = -EINVAL; | ||
225 | |||
226 | if (get_user(options, p)) | ||
227 | return -EFAULT; | ||
228 | |||
229 | if (options & WDIOS_DISABLECARD) { | ||
230 | wdt_disable(); | ||
231 | retval = 0; | ||
232 | } | ||
233 | |||
234 | if (options & WDIOS_ENABLECARD) { | ||
235 | wdt_ping(); | ||
236 | retval = 0; | ||
237 | } | ||
238 | |||
239 | return retval; | ||
240 | } | ||
241 | |||
242 | case WDIOC_KEEPALIVE: | ||
243 | wdt_ping(); | ||
244 | break; | ||
245 | |||
246 | case WDIOC_SETTIMEOUT: | ||
247 | if (get_user(new_timeout, p)) | ||
248 | return -EFAULT; | ||
249 | if (wdt_set_heartbeat(new_timeout)) | ||
250 | return -EINVAL; | ||
251 | wdt_ping(); | ||
252 | /* Fall */ | ||
253 | |||
254 | case WDIOC_GETTIMEOUT: | ||
255 | return put_user(timeout, p); | ||
256 | |||
257 | default: | ||
258 | return -ENOTTY; | ||
259 | } | ||
260 | return 0; | ||
261 | } | ||
262 | |||
263 | static int wdt_open(struct inode *inode, struct file *file) | ||
264 | { | ||
265 | if (test_and_set_bit(0, &wdt_is_open)) | ||
266 | return -EBUSY; | ||
267 | /* | ||
268 | * Activate | ||
269 | */ | ||
270 | |||
271 | wdt_ping(); | ||
272 | return nonseekable_open(inode, file); | ||
273 | } | ||
274 | |||
275 | static int wdt_close(struct inode *inode, struct file *file) | ||
276 | { | ||
277 | if (expect_close == 42) | ||
278 | wdt_disable(); | ||
279 | else { | ||
280 | printk(KERN_CRIT PFX | ||
281 | "Unexpected close, not stopping watchdog!\n"); | ||
282 | wdt_ping(); | ||
283 | } | ||
284 | expect_close = 0; | ||
285 | clear_bit(0, &wdt_is_open); | ||
286 | return 0; | ||
287 | } | ||
288 | |||
289 | /* | ||
290 | * Notifier for system down | ||
291 | */ | ||
292 | |||
293 | static int wdt_notify_sys(struct notifier_block *this, unsigned long code, | ||
294 | void *unused) | ||
295 | { | ||
296 | if (code == SYS_DOWN || code == SYS_HALT) | ||
297 | wdt_disable(); /* Turn the WDT off */ | ||
298 | |||
299 | return NOTIFY_DONE; | ||
300 | } | ||
301 | |||
302 | /* | ||
303 | * Kernel Interfaces | ||
304 | */ | ||
305 | |||
306 | static const struct file_operations wdt_fops = { | ||
307 | .owner = THIS_MODULE, | ||
308 | .llseek = no_llseek, | ||
309 | .write = wdt_write, | ||
310 | .unlocked_ioctl = wdt_ioctl, | ||
311 | .open = wdt_open, | ||
312 | .release = wdt_close, | ||
313 | }; | ||
314 | |||
315 | static struct miscdevice wdt_miscdev = { | ||
316 | .minor = WATCHDOG_MINOR, | ||
317 | .name = "watchdog", | ||
318 | .fops = &wdt_fops, | ||
319 | }; | ||
320 | |||
321 | /* | ||
322 | * The WDT needs to learn about soft shutdowns in order to | ||
323 | * turn the timebomb registers off. | ||
324 | */ | ||
325 | |||
326 | static struct notifier_block wdt_notifier = { | ||
327 | .notifier_call = wdt_notify_sys, | ||
328 | }; | ||
329 | |||
330 | static int __init wdt_init(void) | ||
331 | { | ||
332 | int ret; | ||
333 | |||
334 | printk(KERN_INFO "WDT driver for the Winbond(TM) W83697UG/UF Super I/O chip initialising.\n"); | ||
335 | |||
336 | if (wdt_set_heartbeat(timeout)) { | ||
337 | wdt_set_heartbeat(WATCHDOG_TIMEOUT); | ||
338 | printk(KERN_INFO PFX | ||
339 | "timeout value must be 1<=timeout<=255, using %d\n", | ||
340 | WATCHDOG_TIMEOUT); | ||
341 | } | ||
342 | |||
343 | if (!request_region(wdt_io, 1, WATCHDOG_NAME)) { | ||
344 | printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", | ||
345 | wdt_io); | ||
346 | ret = -EIO; | ||
347 | goto out; | ||
348 | } | ||
349 | |||
350 | w83697ug_init(); | ||
351 | |||
352 | ret = register_reboot_notifier(&wdt_notifier); | ||
353 | if (ret != 0) { | ||
354 | printk(KERN_ERR PFX | ||
355 | "cannot register reboot notifier (err=%d)\n", ret); | ||
356 | goto unreg_regions; | ||
357 | } | ||
358 | |||
359 | ret = misc_register(&wdt_miscdev); | ||
360 | if (ret != 0) { | ||
361 | printk(KERN_ERR PFX | ||
362 | "cannot register miscdev on minor=%d (err=%d)\n", | ||
363 | WATCHDOG_MINOR, ret); | ||
364 | goto unreg_reboot; | ||
365 | } | ||
366 | |||
367 | printk(KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n", | ||
368 | timeout, nowayout); | ||
369 | |||
370 | out: | ||
371 | return ret; | ||
372 | unreg_reboot: | ||
373 | unregister_reboot_notifier(&wdt_notifier); | ||
374 | unreg_regions: | ||
375 | release_region(wdt_io, 1); | ||
376 | goto out; | ||
377 | } | ||
378 | |||
379 | static void __exit wdt_exit(void) | ||
380 | { | ||
381 | misc_deregister(&wdt_miscdev); | ||
382 | unregister_reboot_notifier(&wdt_notifier); | ||
383 | release_region(wdt_io, 1); | ||
384 | } | ||
385 | |||
386 | module_init(wdt_init); | ||
387 | module_exit(wdt_exit); | ||
388 | |||
389 | MODULE_LICENSE("GPL"); | ||
390 | MODULE_AUTHOR("Flemming Frandsen <ff@nrvissing.net>"); | ||
391 | MODULE_DESCRIPTION("w83697ug/uf WDT driver"); | ||
392 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||