diff options
Diffstat (limited to 'drivers/watchdog/rm9k_wdt.c')
-rw-r--r-- | drivers/watchdog/rm9k_wdt.c | 420 |
1 files changed, 420 insertions, 0 deletions
diff --git a/drivers/watchdog/rm9k_wdt.c b/drivers/watchdog/rm9k_wdt.c new file mode 100644 index 000000000000..5c921e471564 --- /dev/null +++ b/drivers/watchdog/rm9k_wdt.c | |||
@@ -0,0 +1,420 @@ | |||
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/notifier.h> | ||
30 | #include <linux/miscdevice.h> | ||
31 | #include <linux/watchdog.h> | ||
32 | #include <asm/io.h> | ||
33 | #include <asm/atomic.h> | ||
34 | #include <asm/processor.h> | ||
35 | #include <asm/uaccess.h> | ||
36 | #include <asm/system.h> | ||
37 | #include <asm/rm9k-ocd.h> | ||
38 | |||
39 | #include <rm9k_wdt.h> | ||
40 | |||
41 | |||
42 | #define CLOCK 125000000 | ||
43 | #define MAX_TIMEOUT_SECONDS 32 | ||
44 | #define CPCCR 0x0080 | ||
45 | #define CPGIG1SR 0x0044 | ||
46 | #define CPGIG1ER 0x0054 | ||
47 | |||
48 | |||
49 | /* Function prototypes */ | ||
50 | static irqreturn_t wdt_gpi_irqhdl(int, void *); | ||
51 | static void wdt_gpi_start(void); | ||
52 | static void wdt_gpi_stop(void); | ||
53 | static void wdt_gpi_set_timeout(unsigned int); | ||
54 | static int wdt_gpi_open(struct inode *, struct file *); | ||
55 | static int wdt_gpi_release(struct inode *, struct file *); | ||
56 | static ssize_t wdt_gpi_write(struct file *, const char __user *, size_t, loff_t *); | ||
57 | static long wdt_gpi_ioctl(struct file *, unsigned int, unsigned long); | ||
58 | static int wdt_gpi_notify(struct notifier_block *, unsigned long, void *); | ||
59 | static const struct resource *wdt_gpi_get_resource(struct platform_device *, const char *, unsigned int); | ||
60 | static int __init wdt_gpi_probe(struct device *); | ||
61 | static int __exit wdt_gpi_remove(struct device *); | ||
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; | ||
68 | |||
69 | |||
70 | /* These are set from device resources */ | ||
71 | static void __iomem * wd_regs; | ||
72 | static unsigned int wd_irq, wd_ctr; | ||
73 | |||
74 | |||
75 | /* Module arguments */ | ||
76 | static int timeout = MAX_TIMEOUT_SECONDS; | ||
77 | module_param(timeout, int, 0444); | ||
78 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds"); | ||
79 | |||
80 | static unsigned long resetaddr = 0xbffdc200; | ||
81 | module_param(resetaddr, ulong, 0444); | ||
82 | MODULE_PARM_DESC(resetaddr, "Address to write to to force a reset"); | ||
83 | |||
84 | static unsigned long flagaddr = 0xbffdc104; | ||
85 | module_param(flagaddr, ulong, 0444); | ||
86 | MODULE_PARM_DESC(flagaddr, "Address to write to boot flags to"); | ||
87 | |||
88 | static int powercycle; | ||
89 | module_param(powercycle, bool, 0444); | ||
90 | MODULE_PARM_DESC(powercycle, "Cycle power if watchdog expires"); | ||
91 | |||
92 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
93 | module_param(nowayout, bool, 0444); | ||
94 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be disabled once started"); | ||
95 | |||
96 | |||
97 | /* Kernel interfaces */ | ||
98 | static const struct file_operations fops = { | ||
99 | .owner = THIS_MODULE, | ||
100 | .open = wdt_gpi_open, | ||
101 | .release = wdt_gpi_release, | ||
102 | .write = wdt_gpi_write, | ||
103 | .unlocked_ioctl = wdt_gpi_ioctl, | ||
104 | }; | ||
105 | |||
106 | static struct miscdevice miscdev = { | ||
107 | .minor = WATCHDOG_MINOR, | ||
108 | .name = wdt_gpi_name, | ||
109 | .fops = &fops, | ||
110 | }; | ||
111 | |||
112 | static struct notifier_block wdt_gpi_shutdown = { | ||
113 | .notifier_call = wdt_gpi_notify, | ||
114 | }; | ||
115 | |||
116 | |||
117 | /* Interrupt handler */ | ||
118 | static irqreturn_t wdt_gpi_irqhdl(int irq, void *ctxt) | ||
119 | { | ||
120 | if (!unlikely(__raw_readl(wd_regs + 0x0008) & 0x1)) | ||
121 | return IRQ_NONE; | ||
122 | __raw_writel(0x1, wd_regs + 0x0008); | ||
123 | |||
124 | |||
125 | printk(KERN_CRIT "%s: watchdog expired - resetting system\n", | ||
126 | wdt_gpi_name); | ||
127 | |||
128 | *(volatile char *) flagaddr |= 0x01; | ||
129 | *(volatile char *) resetaddr = powercycle ? 0x01 : 0x2; | ||
130 | iob(); | ||
131 | while (1) | ||
132 | cpu_relax(); | ||
133 | } | ||
134 | |||
135 | |||
136 | /* Watchdog functions */ | ||
137 | static void wdt_gpi_start(void) | ||
138 | { | ||
139 | u32 reg; | ||
140 | |||
141 | lock_titan_regs(); | ||
142 | reg = titan_readl(CPGIG1ER); | ||
143 | titan_writel(reg | (0x100 << wd_ctr), CPGIG1ER); | ||
144 | iob(); | ||
145 | unlock_titan_regs(); | ||
146 | } | ||
147 | |||
148 | static void wdt_gpi_stop(void) | ||
149 | { | ||
150 | u32 reg; | ||
151 | |||
152 | lock_titan_regs(); | ||
153 | reg = titan_readl(CPCCR) & ~(0xf << (wd_ctr * 4)); | ||
154 | titan_writel(reg, CPCCR); | ||
155 | reg = titan_readl(CPGIG1ER); | ||
156 | titan_writel(reg & ~(0x100 << wd_ctr), CPGIG1ER); | ||
157 | iob(); | ||
158 | unlock_titan_regs(); | ||
159 | } | ||
160 | |||
161 | static void wdt_gpi_set_timeout(unsigned int to) | ||
162 | { | ||
163 | u32 reg; | ||
164 | const u32 wdval = (to * CLOCK) & ~0x0000000f; | ||
165 | |||
166 | lock_titan_regs(); | ||
167 | reg = titan_readl(CPCCR) & ~(0xf << (wd_ctr * 4)); | ||
168 | titan_writel(reg, CPCCR); | ||
169 | wmb(); | ||
170 | __raw_writel(wdval, wd_regs + 0x0000); | ||
171 | wmb(); | ||
172 | titan_writel(reg | (0x2 << (wd_ctr * 4)), CPCCR); | ||
173 | wmb(); | ||
174 | titan_writel(reg | (0x5 << (wd_ctr * 4)), CPCCR); | ||
175 | iob(); | ||
176 | unlock_titan_regs(); | ||
177 | } | ||
178 | |||
179 | |||
180 | /* /dev/watchdog operations */ | ||
181 | static int wdt_gpi_open(struct inode *inode, struct file *file) | ||
182 | { | ||
183 | int res; | ||
184 | |||
185 | if (unlikely(atomic_dec_if_positive(&opencnt) < 0)) | ||
186 | return -EBUSY; | ||
187 | |||
188 | expect_close = 0; | ||
189 | if (locked) { | ||
190 | module_put(THIS_MODULE); | ||
191 | free_irq(wd_irq, &miscdev); | ||
192 | locked = 0; | ||
193 | } | ||
194 | |||
195 | res = request_irq(wd_irq, wdt_gpi_irqhdl, IRQF_SHARED | IRQF_DISABLED, | ||
196 | wdt_gpi_name, &miscdev); | ||
197 | if (unlikely(res)) | ||
198 | return res; | ||
199 | |||
200 | wdt_gpi_set_timeout(timeout); | ||
201 | wdt_gpi_start(); | ||
202 | |||
203 | printk(KERN_INFO "%s: watchdog started, timeout = %u seconds\n", | ||
204 | wdt_gpi_name, timeout); | ||
205 | return nonseekable_open(inode, file); | ||
206 | } | ||
207 | |||
208 | static int wdt_gpi_release(struct inode *inode, struct file *file) | ||
209 | { | ||
210 | if (nowayout) { | ||
211 | printk(KERN_INFO "%s: no way out - watchdog left running\n", | ||
212 | wdt_gpi_name); | ||
213 | __module_get(THIS_MODULE); | ||
214 | locked = 1; | ||
215 | } else { | ||
216 | if (expect_close) { | ||
217 | wdt_gpi_stop(); | ||
218 | free_irq(wd_irq, &miscdev); | ||
219 | printk(KERN_INFO "%s: watchdog stopped\n", wdt_gpi_name); | ||
220 | } else { | ||
221 | printk(KERN_CRIT "%s: unexpected close() -" | ||
222 | " watchdog left running\n", | ||
223 | wdt_gpi_name); | ||
224 | wdt_gpi_set_timeout(timeout); | ||
225 | __module_get(THIS_MODULE); | ||
226 | locked = 1; | ||
227 | } | ||
228 | } | ||
229 | |||
230 | atomic_inc(&opencnt); | ||
231 | return 0; | ||
232 | } | ||
233 | |||
234 | static ssize_t | ||
235 | wdt_gpi_write(struct file *f, const char __user *d, size_t s, loff_t *o) | ||
236 | { | ||
237 | char val; | ||
238 | |||
239 | wdt_gpi_set_timeout(timeout); | ||
240 | expect_close = (s > 0) && !get_user(val, d) && (val == 'V'); | ||
241 | return s ? 1 : 0; | ||
242 | } | ||
243 | |||
244 | static long | ||
245 | wdt_gpi_ioctl(struct file *f, unsigned int cmd, unsigned long arg) | ||
246 | { | ||
247 | long res = -ENOTTY; | ||
248 | const long size = _IOC_SIZE(cmd); | ||
249 | int stat; | ||
250 | void __user *argp = (void __user *)arg; | ||
251 | static struct watchdog_info wdinfo = { | ||
252 | .identity = "RM9xxx/GPI watchdog", | ||
253 | .firmware_version = 0, | ||
254 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | ||
255 | }; | ||
256 | |||
257 | if (unlikely(_IOC_TYPE(cmd) != WATCHDOG_IOCTL_BASE)) | ||
258 | return -ENOTTY; | ||
259 | |||
260 | if ((_IOC_DIR(cmd) & _IOC_READ) | ||
261 | && !access_ok(VERIFY_WRITE, arg, size)) | ||
262 | return -EFAULT; | ||
263 | |||
264 | if ((_IOC_DIR(cmd) & _IOC_WRITE) | ||
265 | && !access_ok(VERIFY_READ, arg, size)) | ||
266 | return -EFAULT; | ||
267 | |||
268 | expect_close = 0; | ||
269 | |||
270 | switch (cmd) { | ||
271 | case WDIOC_GETSUPPORT: | ||
272 | wdinfo.options = nowayout ? | ||
273 | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING : | ||
274 | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE; | ||
275 | res = __copy_to_user(argp, &wdinfo, size) ? -EFAULT : size; | ||
276 | break; | ||
277 | |||
278 | case WDIOC_GETSTATUS: | ||
279 | break; | ||
280 | |||
281 | case WDIOC_GETBOOTSTATUS: | ||
282 | stat = (*(volatile char *) flagaddr & 0x01) | ||
283 | ? WDIOF_CARDRESET : 0; | ||
284 | res = __copy_to_user(argp, &stat, size) ? | ||
285 | -EFAULT : size; | ||
286 | break; | ||
287 | |||
288 | case WDIOC_SETOPTIONS: | ||
289 | break; | ||
290 | |||
291 | case WDIOC_KEEPALIVE: | ||
292 | wdt_gpi_set_timeout(timeout); | ||
293 | res = size; | ||
294 | break; | ||
295 | |||
296 | case WDIOC_SETTIMEOUT: | ||
297 | { | ||
298 | int val; | ||
299 | if (unlikely(__copy_from_user(&val, argp, size))) { | ||
300 | res = -EFAULT; | ||
301 | break; | ||
302 | } | ||
303 | |||
304 | if (val > MAX_TIMEOUT_SECONDS) | ||
305 | val = MAX_TIMEOUT_SECONDS; | ||
306 | timeout = val; | ||
307 | wdt_gpi_set_timeout(val); | ||
308 | res = size; | ||
309 | printk(KERN_INFO "%s: timeout set to %u seconds\n", | ||
310 | wdt_gpi_name, timeout); | ||
311 | } | ||
312 | break; | ||
313 | |||
314 | case WDIOC_GETTIMEOUT: | ||
315 | res = __copy_to_user(argp, &timeout, size) ? | ||
316 | -EFAULT : size; | ||
317 | break; | ||
318 | } | ||
319 | |||
320 | return res; | ||
321 | } | ||
322 | |||
323 | |||
324 | /* Shutdown notifier */ | ||
325 | static int | ||
326 | wdt_gpi_notify(struct notifier_block *this, unsigned long code, void *unused) | ||
327 | { | ||
328 | if (code == SYS_DOWN || code == SYS_HALT) | ||
329 | wdt_gpi_stop(); | ||
330 | |||
331 | return NOTIFY_DONE; | ||
332 | } | ||
333 | |||
334 | |||
335 | /* Init & exit procedures */ | ||
336 | static const struct resource * | ||
337 | wdt_gpi_get_resource(struct platform_device *pdv, const char *name, | ||
338 | unsigned int type) | ||
339 | { | ||
340 | char buf[80]; | ||
341 | if (snprintf(buf, sizeof buf, "%s_0", name) >= sizeof buf) | ||
342 | return NULL; | ||
343 | return platform_get_resource_byname(pdv, type, buf); | ||
344 | } | ||
345 | |||
346 | /* No hotplugging on the platform bus - use __init */ | ||
347 | static int __init wdt_gpi_probe(struct device *dev) | ||
348 | { | ||
349 | int res; | ||
350 | struct platform_device * const pdv = to_platform_device(dev); | ||
351 | const struct resource | ||
352 | * const rr = wdt_gpi_get_resource(pdv, WDT_RESOURCE_REGS, | ||
353 | IORESOURCE_MEM), | ||
354 | * const ri = wdt_gpi_get_resource(pdv, WDT_RESOURCE_IRQ, | ||
355 | IORESOURCE_IRQ), | ||
356 | * const rc = wdt_gpi_get_resource(pdv, WDT_RESOURCE_COUNTER, | ||
357 | 0); | ||
358 | |||
359 | if (unlikely(!rr || !ri || !rc)) | ||
360 | return -ENXIO; | ||
361 | |||
362 | wd_regs = ioremap_nocache(rr->start, rr->end + 1 - rr->start); | ||
363 | if (unlikely(!wd_regs)) | ||
364 | return -ENOMEM; | ||
365 | wd_irq = ri->start; | ||
366 | wd_ctr = rc->start; | ||
367 | res = misc_register(&miscdev); | ||
368 | if (res) | ||
369 | iounmap(wd_regs); | ||
370 | else | ||
371 | register_reboot_notifier(&wdt_gpi_shutdown); | ||
372 | return res; | ||
373 | } | ||
374 | |||
375 | static int __exit wdt_gpi_remove(struct device *dev) | ||
376 | { | ||
377 | int res; | ||
378 | |||
379 | unregister_reboot_notifier(&wdt_gpi_shutdown); | ||
380 | res = misc_deregister(&miscdev); | ||
381 | iounmap(wd_regs); | ||
382 | wd_regs = NULL; | ||
383 | return res; | ||
384 | } | ||
385 | |||
386 | |||
387 | /* Device driver init & exit */ | ||
388 | static struct device_driver wdt_gpi_driver = { | ||
389 | .name = (char *) wdt_gpi_name, | ||
390 | .bus = &platform_bus_type, | ||
391 | .owner = THIS_MODULE, | ||
392 | .probe = wdt_gpi_probe, | ||
393 | .remove = __exit_p(wdt_gpi_remove), | ||
394 | .shutdown = NULL, | ||
395 | .suspend = NULL, | ||
396 | .resume = NULL, | ||
397 | }; | ||
398 | |||
399 | static int __init wdt_gpi_init_module(void) | ||
400 | { | ||
401 | atomic_set(&opencnt, 1); | ||
402 | if (timeout > MAX_TIMEOUT_SECONDS) | ||
403 | timeout = MAX_TIMEOUT_SECONDS; | ||
404 | return driver_register(&wdt_gpi_driver); | ||
405 | } | ||
406 | |||
407 | static void __exit wdt_gpi_cleanup_module(void) | ||
408 | { | ||
409 | driver_unregister(&wdt_gpi_driver); | ||
410 | } | ||
411 | |||
412 | module_init(wdt_gpi_init_module); | ||
413 | module_exit(wdt_gpi_cleanup_module); | ||
414 | |||
415 | MODULE_AUTHOR("Thomas Koeller <thomas.koeller@baslerweb.com>"); | ||
416 | MODULE_DESCRIPTION("Basler eXcite watchdog driver for gpi devices"); | ||
417 | MODULE_VERSION("0.1"); | ||
418 | MODULE_LICENSE("GPL"); | ||
419 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); | ||
420 | |||