diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/char/watchdog/Kconfig | 10 | ||||
-rw-r--r-- | drivers/char/watchdog/Makefile | 1 | ||||
-rw-r--r-- | drivers/char/watchdog/rm9k_wdt.c | 431 |
3 files changed, 442 insertions, 0 deletions
diff --git a/drivers/char/watchdog/Kconfig b/drivers/char/watchdog/Kconfig index a52ecad7d56c..ea09d0c974ea 100644 --- a/drivers/char/watchdog/Kconfig +++ b/drivers/char/watchdog/Kconfig | |||
@@ -575,6 +575,16 @@ config INDYDOG | |||
575 | timer expired and no process has written to /dev/watchdog during | 575 | timer expired and no process has written to /dev/watchdog during |
576 | that time. | 576 | that time. |
577 | 577 | ||
578 | config WDT_RM9K_GPI | ||
579 | tristate "RM9000/GPI hardware watchdog" | ||
580 | depends on WATCHDOG && CPU_RM9000 | ||
581 | help | ||
582 | Watchdog implementation using the GPI hardware found on | ||
583 | PMC-Sierra RM9xxx CPUs. | ||
584 | |||
585 | To compile this driver as a module, choose M here: the | ||
586 | module will be called rm9k_wdt. | ||
587 | |||
578 | # S390 Architecture | 588 | # S390 Architecture |
579 | 589 | ||
580 | config ZVM_WATCHDOG | 590 | config ZVM_WATCHDOG |
diff --git a/drivers/char/watchdog/Makefile b/drivers/char/watchdog/Makefile index 81abdfa10a07..2cd8ff8d10ac 100644 --- a/drivers/char/watchdog/Makefile +++ b/drivers/char/watchdog/Makefile | |||
@@ -73,6 +73,7 @@ obj-$(CONFIG_WATCHDOG_RTAS) += wdrtas.o | |||
73 | 73 | ||
74 | # MIPS Architecture | 74 | # MIPS Architecture |
75 | obj-$(CONFIG_INDYDOG) += indydog.o | 75 | obj-$(CONFIG_INDYDOG) += indydog.o |
76 | obj-$(CONFIG_WDT_RM9K_GPI) += rm9k_wdt.o | ||
76 | 77 | ||
77 | # S390 Architecture | 78 | # S390 Architecture |
78 | 79 | ||
diff --git a/drivers/char/watchdog/rm9k_wdt.c b/drivers/char/watchdog/rm9k_wdt.c new file mode 100644 index 000000000000..8cd12ab9bff4 --- /dev/null +++ b/drivers/char/watchdog/rm9k_wdt.c | |||
@@ -0,0 +1,431 @@ | |||
1 | /* | ||
2 | * Watchdog implementation for GPI h/w found on PMC-Sierra RM9xxx | ||
3 | * chips. | ||
4 | * | ||
5 | * Copyright (C) 2004 by Basler Vision Technologies AG | ||
6 | * Author: Thomas Koeller <thomas.koeller@baslerweb.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU General Public License for more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License | ||
19 | * along with this program; if not, write to the Free Software | ||
20 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
21 | */ | ||
22 | |||
23 | #include <linux/platform_device.h> | ||
24 | #include <linux/module.h> | ||
25 | #include <linux/moduleparam.h> | ||
26 | #include <linux/interrupt.h> | ||
27 | #include <linux/fs.h> | ||
28 | #include <linux/reboot.h> | ||
29 | #include <linux/miscdevice.h> | ||
30 | #include <linux/watchdog.h> | ||
31 | #include <asm/io.h> | ||
32 | #include <asm/atomic.h> | ||
33 | #include <asm/processor.h> | ||
34 | #include <asm/uaccess.h> | ||
35 | #include <asm/system.h> | ||
36 | #include <asm/rm9k-ocd.h> | ||
37 | |||
38 | #include <rm9k_wdt.h> | ||
39 | |||
40 | |||
41 | #define CLOCK 125000000 | ||
42 | #define MAX_TIMEOUT_SECONDS 32 | ||
43 | #define CPCCR 0x0080 | ||
44 | #define CPGIG1SR 0x0044 | ||
45 | #define CPGIG1ER 0x0054 | ||
46 | |||
47 | |||
48 | |||
49 | /* Function prototypes */ | ||
50 | static int __init wdt_gpi_probe(struct device *); | ||
51 | static int __exit wdt_gpi_remove(struct device *); | ||
52 | static void wdt_gpi_set_timeout(unsigned int); | ||
53 | static int wdt_gpi_open(struct inode *, struct file *); | ||
54 | static int wdt_gpi_release(struct inode *, struct file *); | ||
55 | static ssize_t wdt_gpi_write(struct file *, const char __user *, size_t, loff_t *); | ||
56 | static long wdt_gpi_ioctl(struct file *, unsigned int, unsigned long); | ||
57 | static const struct resource *wdt_gpi_get_resource(struct platform_device *, const char *, unsigned int); | ||
58 | static int wdt_gpi_notify(struct notifier_block *, unsigned long, void *); | ||
59 | static irqreturn_t wdt_gpi_irqhdl(int, void *, struct pt_regs *); | ||
60 | |||
61 | |||
62 | |||
63 | |||
64 | static const char wdt_gpi_name[] = "wdt_gpi"; | ||
65 | static atomic_t opencnt; | ||
66 | static int expect_close; | ||
67 | static int locked = 0; | ||
68 | |||
69 | |||
70 | |||
71 | /* These are set from device resources */ | ||
72 | static void __iomem * wd_regs; | ||
73 | static unsigned int wd_irq, wd_ctr; | ||
74 | |||
75 | |||
76 | |||
77 | /* Module arguments */ | ||
78 | static int timeout = MAX_TIMEOUT_SECONDS; | ||
79 | module_param(timeout, int, 0444); | ||
80 | static unsigned long resetaddr = 0xbffdc200; | ||
81 | module_param(resetaddr, ulong, 0444); | ||
82 | static unsigned long flagaddr = 0xbffdc104; | ||
83 | module_param(flagaddr, ulong, 0444); | ||
84 | static int powercycle = 0; | ||
85 | module_param(powercycle, bool, 0444); | ||
86 | |||
87 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
88 | module_param(nowayout, bool, 0444); | ||
89 | |||
90 | |||
91 | |||
92 | static struct file_operations fops = { | ||
93 | .owner = THIS_MODULE, | ||
94 | .open = wdt_gpi_open, | ||
95 | .release = wdt_gpi_release, | ||
96 | .write = wdt_gpi_write, | ||
97 | .unlocked_ioctl = wdt_gpi_ioctl, | ||
98 | }; | ||
99 | |||
100 | static struct miscdevice miscdev = { | ||
101 | .minor = WATCHDOG_MINOR, | ||
102 | .name = wdt_gpi_name, | ||
103 | .fops = &fops, | ||
104 | }; | ||
105 | |||
106 | static struct device_driver wdt_gpi_driver = { | ||
107 | .name = (char *) wdt_gpi_name, | ||
108 | .bus = &platform_bus_type, | ||
109 | .owner = THIS_MODULE, | ||
110 | .probe = wdt_gpi_probe, | ||
111 | .remove = __exit_p(wdt_gpi_remove), | ||
112 | .shutdown = NULL, | ||
113 | .suspend = NULL, | ||
114 | .resume = NULL, | ||
115 | }; | ||
116 | |||
117 | static struct notifier_block wdt_gpi_shutdown = { | ||
118 | .notifier_call = wdt_gpi_notify, | ||
119 | }; | ||
120 | |||
121 | |||
122 | |||
123 | static const struct resource * | ||
124 | wdt_gpi_get_resource(struct platform_device *pdv, const char *name, | ||
125 | unsigned int type) | ||
126 | { | ||
127 | char buf[80]; | ||
128 | if (snprintf(buf, sizeof buf, "%s_0", name) >= sizeof buf) | ||
129 | return NULL; | ||
130 | return platform_get_resource_byname(pdv, type, buf); | ||
131 | } | ||
132 | |||
133 | |||
134 | |||
135 | /* No hotplugging on the platform bus - use __init */ | ||
136 | static int __init wdt_gpi_probe(struct device *dev) | ||
137 | { | ||
138 | int res; | ||
139 | struct platform_device * const pdv = to_platform_device(dev); | ||
140 | const struct resource | ||
141 | * const rr = wdt_gpi_get_resource(pdv, WDT_RESOURCE_REGS, | ||
142 | IORESOURCE_MEM), | ||
143 | * const ri = wdt_gpi_get_resource(pdv, WDT_RESOURCE_IRQ, | ||
144 | IORESOURCE_IRQ), | ||
145 | * const rc = wdt_gpi_get_resource(pdv, WDT_RESOURCE_COUNTER, | ||
146 | 0); | ||
147 | |||
148 | if (unlikely(!rr || !ri || !rc)) | ||
149 | return -ENXIO; | ||
150 | |||
151 | wd_regs = ioremap_nocache(rr->start, rr->end + 1 - rr->start); | ||
152 | if (unlikely(!wd_regs)) | ||
153 | return -ENOMEM; | ||
154 | wd_irq = ri->start; | ||
155 | wd_ctr = rc->start; | ||
156 | res = misc_register(&miscdev); | ||
157 | if (res) | ||
158 | iounmap(wd_regs); | ||
159 | else | ||
160 | register_reboot_notifier(&wdt_gpi_shutdown); | ||
161 | return res; | ||
162 | } | ||
163 | |||
164 | |||
165 | |||
166 | static int __exit wdt_gpi_remove(struct device *dev) | ||
167 | { | ||
168 | int res; | ||
169 | |||
170 | unregister_reboot_notifier(&wdt_gpi_shutdown); | ||
171 | res = misc_deregister(&miscdev); | ||
172 | iounmap(wd_regs); | ||
173 | wd_regs = NULL; | ||
174 | return res; | ||
175 | } | ||
176 | |||
177 | |||
178 | static void wdt_gpi_set_timeout(unsigned int to) | ||
179 | { | ||
180 | u32 reg; | ||
181 | const u32 wdval = (to * CLOCK) & ~0x0000000f; | ||
182 | |||
183 | lock_titan_regs(); | ||
184 | reg = titan_readl(CPCCR) & ~(0xf << (wd_ctr * 4)); | ||
185 | titan_writel(reg, CPCCR); | ||
186 | wmb(); | ||
187 | __raw_writel(wdval, wd_regs + 0x0000); | ||
188 | wmb(); | ||
189 | titan_writel(reg | (0x2 << (wd_ctr * 4)), CPCCR); | ||
190 | wmb(); | ||
191 | titan_writel(reg | (0x5 << (wd_ctr * 4)), CPCCR); | ||
192 | iob(); | ||
193 | unlock_titan_regs(); | ||
194 | } | ||
195 | |||
196 | |||
197 | |||
198 | static int wdt_gpi_open(struct inode *i, struct file *f) | ||
199 | { | ||
200 | int res; | ||
201 | u32 reg; | ||
202 | |||
203 | if (unlikely(0 > atomic_dec_if_positive(&opencnt))) | ||
204 | return -EBUSY; | ||
205 | |||
206 | expect_close = 0; | ||
207 | if (locked) { | ||
208 | module_put(THIS_MODULE); | ||
209 | free_irq(wd_irq, &miscdev); | ||
210 | locked = 0; | ||
211 | } | ||
212 | |||
213 | res = request_irq(wd_irq, wdt_gpi_irqhdl, SA_SHIRQ | SA_INTERRUPT, | ||
214 | wdt_gpi_name, &miscdev); | ||
215 | if (unlikely(res)) | ||
216 | return res; | ||
217 | |||
218 | wdt_gpi_set_timeout(timeout); | ||
219 | |||
220 | lock_titan_regs(); | ||
221 | reg = titan_readl(CPGIG1ER); | ||
222 | titan_writel(reg | (0x100 << wd_ctr), CPGIG1ER); | ||
223 | iob(); | ||
224 | unlock_titan_regs(); | ||
225 | |||
226 | printk(KERN_INFO "%s: watchdog started, timeout = %u seconds\n", | ||
227 | wdt_gpi_name, timeout); | ||
228 | return 0; | ||
229 | } | ||
230 | |||
231 | |||
232 | |||
233 | static int wdt_gpi_release(struct inode *i, struct file *f) | ||
234 | { | ||
235 | if (nowayout) { | ||
236 | printk(KERN_NOTICE "%s: no way out - watchdog left running\n", | ||
237 | wdt_gpi_name); | ||
238 | __module_get(THIS_MODULE); | ||
239 | locked = 1; | ||
240 | } else { | ||
241 | if (expect_close) { | ||
242 | u32 reg; | ||
243 | |||
244 | lock_titan_regs(); | ||
245 | reg = titan_readl(CPCCR) & ~(0xf << (wd_ctr * 4)); | ||
246 | titan_writel(reg, CPCCR); | ||
247 | reg = titan_readl(CPGIG1ER); | ||
248 | titan_writel(reg & ~(0x100 << wd_ctr), CPGIG1ER); | ||
249 | iob(); | ||
250 | unlock_titan_regs(); | ||
251 | free_irq(wd_irq, &miscdev); | ||
252 | printk(KERN_INFO "%s: watchdog stopped\n", wdt_gpi_name); | ||
253 | } else { | ||
254 | printk(KERN_NOTICE "%s: unexpected close() -" | ||
255 | " watchdog left running\n", | ||
256 | wdt_gpi_name); | ||
257 | wdt_gpi_set_timeout(timeout); | ||
258 | __module_get(THIS_MODULE); | ||
259 | locked = 1; | ||
260 | } | ||
261 | } | ||
262 | |||
263 | atomic_inc(&opencnt); | ||
264 | return 0; | ||
265 | } | ||
266 | |||
267 | |||
268 | |||
269 | static ssize_t | ||
270 | wdt_gpi_write(struct file *f, const char __user *d, size_t s, loff_t *o) | ||
271 | { | ||
272 | char val; | ||
273 | |||
274 | wdt_gpi_set_timeout(timeout); | ||
275 | expect_close = (s > 0) && !get_user(val, d) && (val == 'V'); | ||
276 | return s ? 1 : 0; | ||
277 | } | ||
278 | |||
279 | |||
280 | |||
281 | static long | ||
282 | wdt_gpi_ioctl(struct file *f, unsigned int cmd, unsigned long arg) | ||
283 | { | ||
284 | long res = -ENOTTY; | ||
285 | const long size = _IOC_SIZE(cmd); | ||
286 | int stat; | ||
287 | static struct watchdog_info wdinfo = { | ||
288 | .identity = "RM9xxx/GPI watchdog", | ||
289 | .firmware_version = 0, | ||
290 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | ||
291 | }; | ||
292 | |||
293 | if (unlikely(_IOC_TYPE(cmd) != WATCHDOG_IOCTL_BASE)) | ||
294 | return -ENOTTY; | ||
295 | |||
296 | if ((_IOC_DIR(cmd) & _IOC_READ) | ||
297 | && !access_ok(VERIFY_WRITE, arg, size)) | ||
298 | return -EFAULT; | ||
299 | |||
300 | if ((_IOC_DIR(cmd) & _IOC_WRITE) | ||
301 | && !access_ok(VERIFY_READ, arg, size)) | ||
302 | return -EFAULT; | ||
303 | |||
304 | expect_close = 0; | ||
305 | |||
306 | switch (cmd) { | ||
307 | case WDIOC_GETSUPPORT: | ||
308 | wdinfo.options = nowayout ? | ||
309 | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING : | ||
310 | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE; | ||
311 | res = __copy_to_user((void __user *)arg, &wdinfo, size) ? | ||
312 | -EFAULT : size; | ||
313 | break; | ||
314 | |||
315 | case WDIOC_GETSTATUS: | ||
316 | break; | ||
317 | |||
318 | case WDIOC_GETBOOTSTATUS: | ||
319 | stat = (*(volatile char *) flagaddr & 0x01) | ||
320 | ? WDIOF_CARDRESET : 0; | ||
321 | res = __copy_to_user((void __user *)arg, &stat, size) ? | ||
322 | -EFAULT : size; | ||
323 | break; | ||
324 | |||
325 | case WDIOC_SETOPTIONS: | ||
326 | break; | ||
327 | |||
328 | case WDIOC_KEEPALIVE: | ||
329 | wdt_gpi_set_timeout(timeout); | ||
330 | res = size; | ||
331 | break; | ||
332 | |||
333 | case WDIOC_SETTIMEOUT: | ||
334 | { | ||
335 | int val; | ||
336 | if (unlikely(__copy_from_user(&val, (const void __user *) arg, | ||
337 | size))) { | ||
338 | res = -EFAULT; | ||
339 | break; | ||
340 | } | ||
341 | |||
342 | if (val > 32) | ||
343 | val = 32; | ||
344 | timeout = val; | ||
345 | wdt_gpi_set_timeout(val); | ||
346 | res = size; | ||
347 | printk("%s: timeout set to %u seconds\n", | ||
348 | wdt_gpi_name, timeout); | ||
349 | } | ||
350 | break; | ||
351 | |||
352 | case WDIOC_GETTIMEOUT: | ||
353 | res = __copy_to_user((void __user *) arg, &timeout, size) ? | ||
354 | -EFAULT : size; | ||
355 | break; | ||
356 | } | ||
357 | |||
358 | return res; | ||
359 | } | ||
360 | |||
361 | |||
362 | |||
363 | |||
364 | static irqreturn_t wdt_gpi_irqhdl(int irq, void *ctxt, struct pt_regs *regs) | ||
365 | { | ||
366 | if (!unlikely(__raw_readl(wd_regs + 0x0008) & 0x1)) | ||
367 | return IRQ_NONE; | ||
368 | __raw_writel(0x1, wd_regs + 0x0008); | ||
369 | |||
370 | |||
371 | printk(KERN_WARNING "%s: watchdog expired - resetting system\n", | ||
372 | wdt_gpi_name); | ||
373 | |||
374 | *(volatile char *) flagaddr |= 0x01; | ||
375 | *(volatile char *) resetaddr = powercycle ? 0x01 : 0x2; | ||
376 | iob(); | ||
377 | while (1) | ||
378 | cpu_relax(); | ||
379 | } | ||
380 | |||
381 | |||
382 | |||
383 | static int | ||
384 | wdt_gpi_notify(struct notifier_block *this, unsigned long code, void *unused) | ||
385 | { | ||
386 | if(code == SYS_DOWN || code == SYS_HALT) { | ||
387 | u32 reg; | ||
388 | |||
389 | lock_titan_regs(); | ||
390 | reg = titan_readl(CPCCR) & ~(0xf << (wd_ctr * 4)); | ||
391 | titan_writel(reg, CPCCR); | ||
392 | reg = titan_readl(CPGIG1ER); | ||
393 | titan_writel(reg & ~(0x100 << wd_ctr), CPGIG1ER); | ||
394 | iob(); | ||
395 | unlock_titan_regs(); | ||
396 | } | ||
397 | return NOTIFY_DONE; | ||
398 | } | ||
399 | |||
400 | |||
401 | |||
402 | static int __init wdt_gpi_init_module(void) | ||
403 | { | ||
404 | atomic_set(&opencnt, 1); | ||
405 | if (timeout > MAX_TIMEOUT_SECONDS) | ||
406 | timeout = MAX_TIMEOUT_SECONDS; | ||
407 | return driver_register(&wdt_gpi_driver); | ||
408 | } | ||
409 | |||
410 | |||
411 | |||
412 | static void __exit wdt_gpi_cleanup_module(void) | ||
413 | { | ||
414 | driver_unregister(&wdt_gpi_driver); | ||
415 | } | ||
416 | |||
417 | module_init(wdt_gpi_init_module); | ||
418 | module_exit(wdt_gpi_cleanup_module); | ||
419 | |||
420 | |||
421 | |||
422 | MODULE_AUTHOR("Thomas Koeller <thomas.koeller@baslerweb.com>"); | ||
423 | MODULE_DESCRIPTION("Basler eXcite watchdog driver for gpi devices"); | ||
424 | MODULE_VERSION("0.1"); | ||
425 | MODULE_LICENSE("GPL"); | ||
426 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
427 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds"); | ||
428 | MODULE_PARM_DESC(resetaddr, "Address to write to to force a reset"); | ||
429 | MODULE_PARM_DESC(flagaddr, "Address to write to boot flags to"); | ||
430 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be disabled once started"); | ||
431 | MODULE_PARM_DESC(powercycle, "Cycle power if watchdog expires"); | ||