diff options
author | David S. Miller <davem@davemloft.net> | 2008-08-29 20:07:01 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2008-08-29 20:07:01 -0400 |
commit | 8ab0dc333eacb2249c63d1fc7c5241299fa0493f (patch) | |
tree | 5e4118de12ec27b390a5e6238d4f69378ada6092 /drivers/watchdog | |
parent | c5f8556cb5b8ab020f234191a6071cbeeeabd638 (diff) |
cpwatchdog: Move to drivers/watchdog/cpwd.c
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/watchdog')
-rw-r--r-- | drivers/watchdog/Makefile | 1 | ||||
-rw-r--r-- | drivers/watchdog/cpwd.c | 695 |
2 files changed, 696 insertions, 0 deletions
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 57c7e8bd8c88..b214612d819d 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile | |||
@@ -123,6 +123,7 @@ obj-$(CONFIG_SH_WDT) += shwdt.o | |||
123 | # SPARC64 Architecture | 123 | # SPARC64 Architecture |
124 | 124 | ||
125 | obj-$(CONFIG_WATCHDOG_RIO) += riowd.o | 125 | obj-$(CONFIG_WATCHDOG_RIO) += riowd.o |
126 | obj-$(CONFIG_WATCHDOG_CP1XXX) += cpwd.o | ||
126 | 127 | ||
127 | # XTENSA Architecture | 128 | # XTENSA Architecture |
128 | 129 | ||
diff --git a/drivers/watchdog/cpwd.c b/drivers/watchdog/cpwd.c new file mode 100644 index 000000000000..1f1ee2520f17 --- /dev/null +++ b/drivers/watchdog/cpwd.c | |||
@@ -0,0 +1,695 @@ | |||
1 | /* cpwd.c - driver implementation for hardware watchdog | ||
2 | * timers found on Sun Microsystems CP1400 and CP1500 boards. | ||
3 | * | ||
4 | * This device supports both the generic Linux watchdog | ||
5 | * interface and Solaris-compatible ioctls as best it is | ||
6 | * able. | ||
7 | * | ||
8 | * NOTE: CP1400 systems appear to have a defective intr_mask | ||
9 | * register on the PLD, preventing the disabling of | ||
10 | * timer interrupts. We use a timer to periodically | ||
11 | * reset 'stopped' watchdogs on affected platforms. | ||
12 | * | ||
13 | * Copyright (c) 2000 Eric Brower (ebrower@usa.net) | ||
14 | * Copyright (C) 2008 David S. Miller <davem@davemloft.net> | ||
15 | */ | ||
16 | |||
17 | #include <linux/kernel.h> | ||
18 | #include <linux/module.h> | ||
19 | #include <linux/fs.h> | ||
20 | #include <linux/errno.h> | ||
21 | #include <linux/major.h> | ||
22 | #include <linux/init.h> | ||
23 | #include <linux/miscdevice.h> | ||
24 | #include <linux/interrupt.h> | ||
25 | #include <linux/ioport.h> | ||
26 | #include <linux/timer.h> | ||
27 | #include <linux/smp_lock.h> | ||
28 | #include <linux/io.h> | ||
29 | #include <linux/of.h> | ||
30 | #include <linux/of_device.h> | ||
31 | |||
32 | #include <asm/irq.h> | ||
33 | #include <asm/uaccess.h> | ||
34 | |||
35 | #include <asm/watchdog.h> | ||
36 | |||
37 | #define DRIVER_NAME "cpwd" | ||
38 | #define PFX DRIVER_NAME ": " | ||
39 | |||
40 | #define WD_OBPNAME "watchdog" | ||
41 | #define WD_BADMODEL "SUNW,501-5336" | ||
42 | #define WD_BTIMEOUT (jiffies + (HZ * 1000)) | ||
43 | #define WD_BLIMIT 0xFFFF | ||
44 | |||
45 | #define WD0_MINOR 212 | ||
46 | #define WD1_MINOR 213 | ||
47 | #define WD2_MINOR 214 | ||
48 | |||
49 | /* Internal driver definitions. */ | ||
50 | #define WD0_ID 0 | ||
51 | #define WD1_ID 1 | ||
52 | #define WD2_ID 2 | ||
53 | #define WD_NUMDEVS 3 | ||
54 | |||
55 | #define WD_INTR_OFF 0 | ||
56 | #define WD_INTR_ON 1 | ||
57 | |||
58 | #define WD_STAT_INIT 0x01 /* Watchdog timer is initialized */ | ||
59 | #define WD_STAT_BSTOP 0x02 /* Watchdog timer is brokenstopped */ | ||
60 | #define WD_STAT_SVCD 0x04 /* Watchdog interrupt occurred */ | ||
61 | |||
62 | /* Register value definitions | ||
63 | */ | ||
64 | #define WD0_INTR_MASK 0x01 /* Watchdog device interrupt masks */ | ||
65 | #define WD1_INTR_MASK 0x02 | ||
66 | #define WD2_INTR_MASK 0x04 | ||
67 | |||
68 | #define WD_S_RUNNING 0x01 /* Watchdog device status running */ | ||
69 | #define WD_S_EXPIRED 0x02 /* Watchdog device status expired */ | ||
70 | |||
71 | struct cpwd { | ||
72 | void __iomem *regs; | ||
73 | spinlock_t lock; | ||
74 | |||
75 | unsigned int irq; | ||
76 | |||
77 | unsigned long timeout; | ||
78 | bool enabled; | ||
79 | bool reboot; | ||
80 | bool broken; | ||
81 | bool initialized; | ||
82 | |||
83 | struct { | ||
84 | struct miscdevice misc; | ||
85 | void __iomem *regs; | ||
86 | u8 intr_mask; | ||
87 | u8 runstatus; | ||
88 | u16 timeout; | ||
89 | } devs[WD_NUMDEVS]; | ||
90 | }; | ||
91 | |||
92 | static struct cpwd *cpwd_device; | ||
93 | |||
94 | /* Sun uses Altera PLD EPF8820ATC144-4 | ||
95 | * providing three hardware watchdogs: | ||
96 | * | ||
97 | * 1) RIC - sends an interrupt when triggered | ||
98 | * 2) XIR - asserts XIR_B_RESET when triggered, resets CPU | ||
99 | * 3) POR - asserts POR_B_RESET when triggered, resets CPU, backplane, board | ||
100 | * | ||
101 | *** Timer register block definition (struct wd_timer_regblk) | ||
102 | * | ||
103 | * dcntr and limit registers (halfword access): | ||
104 | * ------------------- | ||
105 | * | 15 | ...| 1 | 0 | | ||
106 | * ------------------- | ||
107 | * |- counter val -| | ||
108 | * ------------------- | ||
109 | * dcntr - Current 16-bit downcounter value. | ||
110 | * When downcounter reaches '0' watchdog expires. | ||
111 | * Reading this register resets downcounter with 'limit' value. | ||
112 | * limit - 16-bit countdown value in 1/10th second increments. | ||
113 | * Writing this register begins countdown with input value. | ||
114 | * Reading from this register does not affect counter. | ||
115 | * NOTES: After watchdog reset, dcntr and limit contain '1' | ||
116 | * | ||
117 | * status register (byte access): | ||
118 | * --------------------------- | ||
119 | * | 7 | ... | 2 | 1 | 0 | | ||
120 | * --------------+------------ | ||
121 | * |- UNUSED -| EXP | RUN | | ||
122 | * --------------------------- | ||
123 | * status- Bit 0 - Watchdog is running | ||
124 | * Bit 1 - Watchdog has expired | ||
125 | * | ||
126 | *** PLD register block definition (struct wd_pld_regblk) | ||
127 | * | ||
128 | * intr_mask register (byte access): | ||
129 | * --------------------------------- | ||
130 | * | 7 | ... | 3 | 2 | 1 | 0 | | ||
131 | * +-------------+------------------ | ||
132 | * |- UNUSED -| WD3 | WD2 | WD1 | | ||
133 | * --------------------------------- | ||
134 | * WD3 - 1 == Interrupt disabled for watchdog 3 | ||
135 | * WD2 - 1 == Interrupt disabled for watchdog 2 | ||
136 | * WD1 - 1 == Interrupt disabled for watchdog 1 | ||
137 | * | ||
138 | * pld_status register (byte access): | ||
139 | * UNKNOWN, MAGICAL MYSTERY REGISTER | ||
140 | * | ||
141 | */ | ||
142 | #define WD_TIMER_REGSZ 16 | ||
143 | #define WD0_OFF 0 | ||
144 | #define WD1_OFF (WD_TIMER_REGSZ * 1) | ||
145 | #define WD2_OFF (WD_TIMER_REGSZ * 2) | ||
146 | #define PLD_OFF (WD_TIMER_REGSZ * 3) | ||
147 | |||
148 | #define WD_DCNTR 0x00 | ||
149 | #define WD_LIMIT 0x04 | ||
150 | #define WD_STATUS 0x08 | ||
151 | |||
152 | #define PLD_IMASK (PLD_OFF + 0x00) | ||
153 | #define PLD_STATUS (PLD_OFF + 0x04) | ||
154 | |||
155 | static struct timer_list cpwd_timer; | ||
156 | |||
157 | static int wd0_timeout = 0; | ||
158 | static int wd1_timeout = 0; | ||
159 | static int wd2_timeout = 0; | ||
160 | |||
161 | module_param (wd0_timeout, int, 0); | ||
162 | MODULE_PARM_DESC(wd0_timeout, "Default watchdog0 timeout in 1/10secs"); | ||
163 | module_param (wd1_timeout, int, 0); | ||
164 | MODULE_PARM_DESC(wd1_timeout, "Default watchdog1 timeout in 1/10secs"); | ||
165 | module_param (wd2_timeout, int, 0); | ||
166 | MODULE_PARM_DESC(wd2_timeout, "Default watchdog2 timeout in 1/10secs"); | ||
167 | |||
168 | MODULE_AUTHOR("Eric Brower <ebrower@usa.net>"); | ||
169 | MODULE_DESCRIPTION("Hardware watchdog driver for Sun Microsystems CP1400/1500"); | ||
170 | MODULE_LICENSE("GPL"); | ||
171 | MODULE_SUPPORTED_DEVICE("watchdog"); | ||
172 | |||
173 | static void cpwd_writew(u16 val, void __iomem *addr) | ||
174 | { | ||
175 | writew(cpu_to_le16(val), addr); | ||
176 | } | ||
177 | static u16 cpwd_readw(void __iomem *addr) | ||
178 | { | ||
179 | u16 val = readw(addr); | ||
180 | |||
181 | return le16_to_cpu(val); | ||
182 | } | ||
183 | |||
184 | static void cpwd_writeb(u8 val, void __iomem *addr) | ||
185 | { | ||
186 | writeb(val, addr); | ||
187 | } | ||
188 | |||
189 | static u8 cpwd_readb(void __iomem *addr) | ||
190 | { | ||
191 | return readb(addr); | ||
192 | } | ||
193 | |||
194 | /* Enable or disable watchdog interrupts | ||
195 | * Because of the CP1400 defect this should only be | ||
196 | * called during initialzation or by wd_[start|stop]timer() | ||
197 | * | ||
198 | * index - sub-device index, or -1 for 'all' | ||
199 | * enable - non-zero to enable interrupts, zero to disable | ||
200 | */ | ||
201 | static void cpwd_toggleintr(struct cpwd *p, int index, int enable) | ||
202 | { | ||
203 | unsigned char curregs = cpwd_readb(p->regs + PLD_IMASK); | ||
204 | unsigned char setregs = | ||
205 | (index == -1) ? | ||
206 | (WD0_INTR_MASK | WD1_INTR_MASK | WD2_INTR_MASK) : | ||
207 | (p->devs[index].intr_mask); | ||
208 | |||
209 | if (enable == WD_INTR_ON) | ||
210 | curregs &= ~setregs; | ||
211 | else | ||
212 | curregs |= setregs; | ||
213 | |||
214 | cpwd_writeb(curregs, p->regs + PLD_IMASK); | ||
215 | } | ||
216 | |||
217 | /* Restarts timer with maximum limit value and | ||
218 | * does not unset 'brokenstop' value. | ||
219 | */ | ||
220 | static void cpwd_resetbrokentimer(struct cpwd *p, int index) | ||
221 | { | ||
222 | cpwd_toggleintr(p, index, WD_INTR_ON); | ||
223 | cpwd_writew(WD_BLIMIT, p->devs[index].regs + WD_LIMIT); | ||
224 | } | ||
225 | |||
226 | /* Timer method called to reset stopped watchdogs-- | ||
227 | * because of the PLD bug on CP1400, we cannot mask | ||
228 | * interrupts within the PLD so me must continually | ||
229 | * reset the timers ad infinitum. | ||
230 | */ | ||
231 | static void cpwd_brokentimer(unsigned long data) | ||
232 | { | ||
233 | struct cpwd *p = (struct cpwd *) data; | ||
234 | int id, tripped = 0; | ||
235 | |||
236 | /* kill a running timer instance, in case we | ||
237 | * were called directly instead of by kernel timer | ||
238 | */ | ||
239 | if (timer_pending(&cpwd_timer)) | ||
240 | del_timer(&cpwd_timer); | ||
241 | |||
242 | for (id = 0; id < WD_NUMDEVS; id++) { | ||
243 | if (p->devs[id].runstatus & WD_STAT_BSTOP) { | ||
244 | ++tripped; | ||
245 | cpwd_resetbrokentimer(p, id); | ||
246 | } | ||
247 | } | ||
248 | |||
249 | if (tripped) { | ||
250 | /* there is at least one timer brokenstopped-- reschedule */ | ||
251 | cpwd_timer.expires = WD_BTIMEOUT; | ||
252 | add_timer(&cpwd_timer); | ||
253 | } | ||
254 | } | ||
255 | |||
256 | /* Reset countdown timer with 'limit' value and continue countdown. | ||
257 | * This will not start a stopped timer. | ||
258 | */ | ||
259 | static void cpwd_pingtimer(struct cpwd *p, int index) | ||
260 | { | ||
261 | if (cpwd_readb(p->devs[index].regs + WD_STATUS) & WD_S_RUNNING) | ||
262 | cpwd_readw(p->devs[index].regs + WD_DCNTR); | ||
263 | } | ||
264 | |||
265 | /* Stop a running watchdog timer-- the timer actually keeps | ||
266 | * running, but the interrupt is masked so that no action is | ||
267 | * taken upon expiration. | ||
268 | */ | ||
269 | static void cpwd_stoptimer(struct cpwd *p, int index) | ||
270 | { | ||
271 | if (cpwd_readb(p->devs[index].regs + WD_STATUS) & WD_S_RUNNING) { | ||
272 | cpwd_toggleintr(p, index, WD_INTR_OFF); | ||
273 | |||
274 | if (p->broken) { | ||
275 | p->devs[index].runstatus |= WD_STAT_BSTOP; | ||
276 | cpwd_brokentimer((unsigned long) p); | ||
277 | } | ||
278 | } | ||
279 | } | ||
280 | |||
281 | /* Start a watchdog timer with the specified limit value | ||
282 | * If the watchdog is running, it will be restarted with | ||
283 | * the provided limit value. | ||
284 | * | ||
285 | * This function will enable interrupts on the specified | ||
286 | * watchdog. | ||
287 | */ | ||
288 | static void cpwd_starttimer(struct cpwd *p, int index) | ||
289 | { | ||
290 | if (p->broken) | ||
291 | p->devs[index].runstatus &= ~WD_STAT_BSTOP; | ||
292 | |||
293 | p->devs[index].runstatus &= ~WD_STAT_SVCD; | ||
294 | |||
295 | cpwd_writew(p->devs[index].timeout, p->devs[index].regs + WD_LIMIT); | ||
296 | cpwd_toggleintr(p, index, WD_INTR_ON); | ||
297 | } | ||
298 | |||
299 | static int cpwd_getstatus(struct cpwd *p, int index) | ||
300 | { | ||
301 | unsigned char stat = cpwd_readb(p->devs[index].regs + WD_STATUS); | ||
302 | unsigned char intr = cpwd_readb(p->devs[index].regs + PLD_IMASK); | ||
303 | unsigned char ret = WD_STOPPED; | ||
304 | |||
305 | /* determine STOPPED */ | ||
306 | if (!stat) | ||
307 | return ret; | ||
308 | |||
309 | /* determine EXPIRED vs FREERUN vs RUNNING */ | ||
310 | else if (WD_S_EXPIRED & stat) { | ||
311 | ret = WD_EXPIRED; | ||
312 | } else if(WD_S_RUNNING & stat) { | ||
313 | if (intr & p->devs[index].intr_mask) { | ||
314 | ret = WD_FREERUN; | ||
315 | } else { | ||
316 | /* Fudge WD_EXPIRED status for defective CP1400-- | ||
317 | * IF timer is running | ||
318 | * AND brokenstop is set | ||
319 | * AND an interrupt has been serviced | ||
320 | * we are WD_EXPIRED. | ||
321 | * | ||
322 | * IF timer is running | ||
323 | * AND brokenstop is set | ||
324 | * AND no interrupt has been serviced | ||
325 | * we are WD_FREERUN. | ||
326 | */ | ||
327 | if (p->broken && | ||
328 | (p->devs[index].runstatus & WD_STAT_BSTOP)) { | ||
329 | if (p->devs[index].runstatus & WD_STAT_SVCD) { | ||
330 | ret = WD_EXPIRED; | ||
331 | } else { | ||
332 | /* we could as well pretend we are expired */ | ||
333 | ret = WD_FREERUN; | ||
334 | } | ||
335 | } else { | ||
336 | ret = WD_RUNNING; | ||
337 | } | ||
338 | } | ||
339 | } | ||
340 | |||
341 | /* determine SERVICED */ | ||
342 | if (p->devs[index].runstatus & WD_STAT_SVCD) | ||
343 | ret |= WD_SERVICED; | ||
344 | |||
345 | return(ret); | ||
346 | } | ||
347 | |||
348 | static irqreturn_t cpwd_interrupt(int irq, void *dev_id) | ||
349 | { | ||
350 | struct cpwd *p = dev_id; | ||
351 | |||
352 | /* Only WD0 will interrupt-- others are NMI and we won't | ||
353 | * see them here.... | ||
354 | */ | ||
355 | spin_lock_irq(&p->lock); | ||
356 | |||
357 | cpwd_stoptimer(p, WD0_ID); | ||
358 | p->devs[WD0_ID].runstatus |= WD_STAT_SVCD; | ||
359 | |||
360 | spin_unlock_irq(&p->lock); | ||
361 | |||
362 | return IRQ_HANDLED; | ||
363 | } | ||
364 | |||
365 | static int cpwd_open(struct inode *inode, struct file *f) | ||
366 | { | ||
367 | struct cpwd *p = cpwd_device; | ||
368 | |||
369 | lock_kernel(); | ||
370 | switch(iminor(inode)) { | ||
371 | case WD0_MINOR: | ||
372 | case WD1_MINOR: | ||
373 | case WD2_MINOR: | ||
374 | break; | ||
375 | |||
376 | default: | ||
377 | unlock_kernel(); | ||
378 | return -ENODEV; | ||
379 | } | ||
380 | |||
381 | /* Register IRQ on first open of device */ | ||
382 | if (!p->initialized) { | ||
383 | if (request_irq(p->irq, &cpwd_interrupt, | ||
384 | IRQF_SHARED, DRIVER_NAME, p)) { | ||
385 | printk(KERN_ERR PFX "Cannot register IRQ %d\n", | ||
386 | p->irq); | ||
387 | unlock_kernel(); | ||
388 | return -EBUSY; | ||
389 | } | ||
390 | p->initialized = true; | ||
391 | } | ||
392 | |||
393 | unlock_kernel(); | ||
394 | |||
395 | return nonseekable_open(inode, f); | ||
396 | } | ||
397 | |||
398 | static int cpwd_release(struct inode *inode, struct file *file) | ||
399 | { | ||
400 | return 0; | ||
401 | } | ||
402 | |||
403 | static int cpwd_ioctl(struct inode *inode, struct file *file, | ||
404 | unsigned int cmd, unsigned long arg) | ||
405 | { | ||
406 | static struct watchdog_info info = { | ||
407 | .options = WDIOF_SETTIMEOUT, | ||
408 | .firmware_version = 1, | ||
409 | .identity = DRIVER_NAME, | ||
410 | }; | ||
411 | void __user *argp = (void __user *)arg; | ||
412 | int index = iminor(inode) - WD0_MINOR; | ||
413 | struct cpwd *p = cpwd_device; | ||
414 | int setopt = 0; | ||
415 | |||
416 | switch (cmd) { | ||
417 | /* Generic Linux IOCTLs */ | ||
418 | case WDIOC_GETSUPPORT: | ||
419 | if (copy_to_user(argp, &info, sizeof(struct watchdog_info))) | ||
420 | return -EFAULT; | ||
421 | break; | ||
422 | |||
423 | case WDIOC_GETSTATUS: | ||
424 | case WDIOC_GETBOOTSTATUS: | ||
425 | if (put_user(0, (int __user *)argp)) | ||
426 | return -EFAULT; | ||
427 | break; | ||
428 | |||
429 | case WDIOC_KEEPALIVE: | ||
430 | cpwd_pingtimer(p, index); | ||
431 | break; | ||
432 | |||
433 | case WDIOC_SETOPTIONS: | ||
434 | if (copy_from_user(&setopt, argp, sizeof(unsigned int))) | ||
435 | return -EFAULT; | ||
436 | |||
437 | if (setopt & WDIOS_DISABLECARD) { | ||
438 | if (p->enabled) | ||
439 | return -EINVAL; | ||
440 | cpwd_stoptimer(p, index); | ||
441 | } else if (setopt & WDIOS_ENABLECARD) { | ||
442 | cpwd_starttimer(p, index); | ||
443 | } else { | ||
444 | return -EINVAL; | ||
445 | } | ||
446 | break; | ||
447 | |||
448 | /* Solaris-compatible IOCTLs */ | ||
449 | case WIOCGSTAT: | ||
450 | setopt = cpwd_getstatus(p, index); | ||
451 | if (copy_to_user(argp, &setopt, sizeof(unsigned int))) | ||
452 | return -EFAULT; | ||
453 | break; | ||
454 | |||
455 | case WIOCSTART: | ||
456 | cpwd_starttimer(p, index); | ||
457 | break; | ||
458 | |||
459 | case WIOCSTOP: | ||
460 | if (p->enabled) | ||
461 | return(-EINVAL); | ||
462 | |||
463 | cpwd_stoptimer(p, index); | ||
464 | break; | ||
465 | |||
466 | default: | ||
467 | return -EINVAL; | ||
468 | } | ||
469 | |||
470 | return 0; | ||
471 | } | ||
472 | |||
473 | static long cpwd_compat_ioctl(struct file *file, unsigned int cmd, | ||
474 | unsigned long arg) | ||
475 | { | ||
476 | int rval = -ENOIOCTLCMD; | ||
477 | |||
478 | switch (cmd) { | ||
479 | /* solaris ioctls are specific to this driver */ | ||
480 | case WIOCSTART: | ||
481 | case WIOCSTOP: | ||
482 | case WIOCGSTAT: | ||
483 | lock_kernel(); | ||
484 | rval = cpwd_ioctl(file->f_path.dentry->d_inode, file, cmd, arg); | ||
485 | unlock_kernel(); | ||
486 | break; | ||
487 | |||
488 | /* everything else is handled by the generic compat layer */ | ||
489 | default: | ||
490 | break; | ||
491 | } | ||
492 | |||
493 | return rval; | ||
494 | } | ||
495 | |||
496 | static ssize_t cpwd_write(struct file *file, const char __user *buf, | ||
497 | size_t count, loff_t *ppos) | ||
498 | { | ||
499 | struct inode *inode = file->f_path.dentry->d_inode; | ||
500 | struct cpwd *p = cpwd_device; | ||
501 | int index = iminor(inode); | ||
502 | |||
503 | if (count) { | ||
504 | cpwd_pingtimer(p, index); | ||
505 | return 1; | ||
506 | } | ||
507 | |||
508 | return 0; | ||
509 | } | ||
510 | |||
511 | static ssize_t cpwd_read(struct file * file, char __user *buffer, | ||
512 | size_t count, loff_t *ppos) | ||
513 | { | ||
514 | return -EINVAL; | ||
515 | } | ||
516 | |||
517 | static const struct file_operations cpwd_fops = { | ||
518 | .owner = THIS_MODULE, | ||
519 | .ioctl = cpwd_ioctl, | ||
520 | .compat_ioctl = cpwd_compat_ioctl, | ||
521 | .open = cpwd_open, | ||
522 | .write = cpwd_write, | ||
523 | .read = cpwd_read, | ||
524 | .release = cpwd_release, | ||
525 | }; | ||
526 | |||
527 | static int __devinit cpwd_probe(struct of_device *op, | ||
528 | const struct of_device_id *match) | ||
529 | { | ||
530 | struct device_node *options; | ||
531 | const char *str_prop; | ||
532 | const void *prop_val; | ||
533 | int i, err = -EINVAL; | ||
534 | struct cpwd *p; | ||
535 | |||
536 | if (cpwd_device) | ||
537 | return -EINVAL; | ||
538 | |||
539 | p = kzalloc(sizeof(*p), GFP_KERNEL); | ||
540 | err = -ENOMEM; | ||
541 | if (!p) { | ||
542 | printk(KERN_ERR PFX "Unable to allocate struct cpwd.\n"); | ||
543 | goto out; | ||
544 | } | ||
545 | |||
546 | p->irq = op->irqs[0]; | ||
547 | |||
548 | spin_lock_init(&p->lock); | ||
549 | |||
550 | p->regs = of_ioremap(&op->resource[0], 0, | ||
551 | 4 * WD_TIMER_REGSZ, DRIVER_NAME); | ||
552 | if (!p->regs) { | ||
553 | printk(KERN_ERR PFX "Unable to map registers.\n"); | ||
554 | goto out_free; | ||
555 | } | ||
556 | |||
557 | options = of_find_node_by_path("/options"); | ||
558 | err = -ENODEV; | ||
559 | if (!options) { | ||
560 | printk(KERN_ERR PFX "Unable to find /options node.\n"); | ||
561 | goto out_iounmap; | ||
562 | } | ||
563 | |||
564 | prop_val = of_get_property(options, "watchdog-enable?", NULL); | ||
565 | p->enabled = (prop_val ? true : false); | ||
566 | |||
567 | prop_val = of_get_property(options, "watchdog-reboot?", NULL); | ||
568 | p->reboot = (prop_val ? true : false); | ||
569 | |||
570 | str_prop = of_get_property(options, "watchdog-timeout", NULL); | ||
571 | if (str_prop) | ||
572 | p->timeout = simple_strtoul(str_prop, NULL, 10); | ||
573 | |||
574 | /* CP1400s seem to have broken PLD implementations-- the | ||
575 | * interrupt_mask register cannot be written, so no timer | ||
576 | * interrupts can be masked within the PLD. | ||
577 | */ | ||
578 | str_prop = of_get_property(op->node, "model", NULL); | ||
579 | p->broken = (str_prop && !strcmp(str_prop, WD_BADMODEL)); | ||
580 | |||
581 | if (!p->enabled) | ||
582 | cpwd_toggleintr(p, -1, WD_INTR_OFF); | ||
583 | |||
584 | for (i = 0; i < WD_NUMDEVS; i++) { | ||
585 | static const char *cpwd_names[] = { "RIC", "XIR", "POR" }; | ||
586 | static int *parms[] = { &wd0_timeout, | ||
587 | &wd1_timeout, | ||
588 | &wd2_timeout }; | ||
589 | struct miscdevice *mp = &p->devs[i].misc; | ||
590 | |||
591 | mp->minor = WD0_MINOR + i; | ||
592 | mp->name = cpwd_names[i]; | ||
593 | mp->fops = &cpwd_fops; | ||
594 | |||
595 | p->devs[i].regs = p->regs + (i * WD_TIMER_REGSZ); | ||
596 | p->devs[i].intr_mask = (WD0_INTR_MASK << i); | ||
597 | p->devs[i].runstatus &= ~WD_STAT_BSTOP; | ||
598 | p->devs[i].runstatus |= WD_STAT_INIT; | ||
599 | p->devs[i].timeout = p->timeout; | ||
600 | if (*parms[i]) | ||
601 | p->devs[i].timeout = *parms[i]; | ||
602 | |||
603 | err = misc_register(&p->devs[i].misc); | ||
604 | if (err) { | ||
605 | printk(KERN_ERR "Could not register misc device for " | ||
606 | "dev %d\n", i); | ||
607 | goto out_unregister; | ||
608 | } | ||
609 | } | ||
610 | |||
611 | if (p->broken) { | ||
612 | init_timer(&cpwd_timer); | ||
613 | cpwd_timer.function = cpwd_brokentimer; | ||
614 | cpwd_timer.data = (unsigned long) p; | ||
615 | cpwd_timer.expires = WD_BTIMEOUT; | ||
616 | |||
617 | printk(KERN_INFO PFX "PLD defect workaround enabled for " | ||
618 | "model " WD_BADMODEL ".\n"); | ||
619 | } | ||
620 | |||
621 | dev_set_drvdata(&op->dev, p); | ||
622 | cpwd_device = p; | ||
623 | err = 0; | ||
624 | |||
625 | out: | ||
626 | return err; | ||
627 | |||
628 | out_unregister: | ||
629 | for (i--; i >= 0; i--) | ||
630 | misc_deregister(&p->devs[i].misc); | ||
631 | |||
632 | out_iounmap: | ||
633 | of_iounmap(&op->resource[0], p->regs, 4 * WD_TIMER_REGSZ); | ||
634 | |||
635 | out_free: | ||
636 | kfree(p); | ||
637 | goto out; | ||
638 | } | ||
639 | |||
640 | static int __devexit cpwd_remove(struct of_device *op) | ||
641 | { | ||
642 | struct cpwd *p = dev_get_drvdata(&op->dev); | ||
643 | int i; | ||
644 | |||
645 | for (i = 0; i < 4; i++) { | ||
646 | misc_deregister(&p->devs[i].misc); | ||
647 | |||
648 | if (!p->enabled) { | ||
649 | cpwd_stoptimer(p, i); | ||
650 | if (p->devs[i].runstatus & WD_STAT_BSTOP) | ||
651 | cpwd_resetbrokentimer(p, i); | ||
652 | } | ||
653 | } | ||
654 | |||
655 | if (p->broken) | ||
656 | del_timer_sync(&cpwd_timer); | ||
657 | |||
658 | if (p->initialized) | ||
659 | free_irq(p->irq, p); | ||
660 | |||
661 | of_iounmap(&op->resource[0], p->regs, 4 * WD_TIMER_REGSZ); | ||
662 | kfree(p); | ||
663 | |||
664 | cpwd_device = NULL; | ||
665 | |||
666 | return 0; | ||
667 | } | ||
668 | |||
669 | static struct of_device_id cpwd_match[] = { | ||
670 | { | ||
671 | .name = "watchdog", | ||
672 | }, | ||
673 | {}, | ||
674 | }; | ||
675 | MODULE_DEVICE_TABLE(of, cpwd_match); | ||
676 | |||
677 | static struct of_platform_driver cpwd_driver = { | ||
678 | .name = DRIVER_NAME, | ||
679 | .match_table = cpwd_match, | ||
680 | .probe = cpwd_probe, | ||
681 | .remove = __devexit_p(cpwd_remove), | ||
682 | }; | ||
683 | |||
684 | static int __init cpwd_init(void) | ||
685 | { | ||
686 | return of_register_driver(&cpwd_driver, &of_bus_type); | ||
687 | } | ||
688 | |||
689 | static void __exit cpwd_exit(void) | ||
690 | { | ||
691 | of_unregister_driver(&cpwd_driver); | ||
692 | } | ||
693 | |||
694 | module_init(cpwd_init); | ||
695 | module_exit(cpwd_exit); | ||