Commit | Line | Data |
---|---|---|
825d3748 TK |
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> | |
d5d06ff7 | 29 | #include <linux/notifier.h> |
825d3748 TK |
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 | ||
825d3748 | 49 | /* Function prototypes */ |
74e86ab8 | 50 | static irqreturn_t wdt_gpi_irqhdl(int, void *); |
d5d06ff7 WVS |
51 | static void wdt_gpi_start(void); |
52 | static void wdt_gpi_stop(void); | |
825d3748 TK |
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); | |
825d3748 | 58 | static int wdt_gpi_notify(struct notifier_block *, unsigned long, void *); |
d5d06ff7 | 59 | static const struct resource *wdt_gpi_get_resource(struct platform_device *, const char *, unsigned int); |
97846e3c WVS |
60 | static int __init wdt_gpi_probe(struct device *); |
61 | static int __exit wdt_gpi_remove(struct device *); | |
825d3748 TK |
62 | |
63 | ||
825d3748 TK |
64 | static const char wdt_gpi_name[] = "wdt_gpi"; |
65 | static atomic_t opencnt; | |
66 | static int expect_close; | |
d5d06ff7 | 67 | static int locked; |
825d3748 TK |
68 | |
69 | ||
825d3748 TK |
70 | /* These are set from device resources */ |
71 | static void __iomem * wd_regs; | |
72 | static unsigned int wd_irq, wd_ctr; | |
73 | ||
74 | ||
825d3748 TK |
75 | /* Module arguments */ |
76 | static int timeout = MAX_TIMEOUT_SECONDS; | |
77 | module_param(timeout, int, 0444); | |
cd57eeab WVS |
78 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds"); |
79 | ||
825d3748 TK |
80 | static unsigned long resetaddr = 0xbffdc200; |
81 | module_param(resetaddr, ulong, 0444); | |
cd57eeab WVS |
82 | MODULE_PARM_DESC(resetaddr, "Address to write to to force a reset"); |
83 | ||
825d3748 TK |
84 | static unsigned long flagaddr = 0xbffdc104; |
85 | module_param(flagaddr, ulong, 0444); | |
cd57eeab WVS |
86 | MODULE_PARM_DESC(flagaddr, "Address to write to boot flags to"); |
87 | ||
d5d06ff7 | 88 | static int powercycle; |
825d3748 | 89 | module_param(powercycle, bool, 0444); |
cd57eeab | 90 | MODULE_PARM_DESC(powercycle, "Cycle power if watchdog expires"); |
825d3748 TK |
91 | |
92 | static int nowayout = WATCHDOG_NOWAYOUT; | |
93 | module_param(nowayout, bool, 0444); | |
cd57eeab | 94 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be disabled once started"); |
825d3748 TK |
95 | |
96 | ||
bec4f749 | 97 | /* Kernel interfaces */ |
2b8693c0 | 98 | static const struct file_operations fops = { |
bec4f749 TK |
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 | ||
97846e3c | 117 | /* Interrupt handler */ |
74e86ab8 | 118 | static irqreturn_t wdt_gpi_irqhdl(int irq, void *ctxt) |
825d3748 | 119 | { |
97846e3c WVS |
120 | if (!unlikely(__raw_readl(wd_regs + 0x0008) & 0x1)) |
121 | return IRQ_NONE; | |
122 | __raw_writel(0x1, wd_regs + 0x0008); | |
825d3748 TK |
123 | |
124 | ||
d5d06ff7 | 125 | printk(KERN_CRIT "%s: watchdog expired - resetting system\n", |
97846e3c | 126 | wdt_gpi_name); |
825d3748 | 127 | |
97846e3c WVS |
128 | *(volatile char *) flagaddr |= 0x01; |
129 | *(volatile char *) resetaddr = powercycle ? 0x01 : 0x2; | |
130 | iob(); | |
131 | while (1) | |
132 | cpu_relax(); | |
825d3748 TK |
133 | } |
134 | ||
135 | ||
97846e3c | 136 | /* Watchdog functions */ |
414a6759 WVS |
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 | ||
825d3748 TK |
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 | ||
97846e3c | 180 | /* /dev/watchdog operations */ |
d5d06ff7 | 181 | static int wdt_gpi_open(struct inode *inode, struct file *file) |
825d3748 TK |
182 | { |
183 | int res; | |
825d3748 | 184 | |
d5d06ff7 | 185 | if (unlikely(atomic_dec_if_positive(&opencnt) < 0)) |
825d3748 TK |
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 | ||
38515e90 | 195 | res = request_irq(wd_irq, wdt_gpi_irqhdl, IRQF_SHARED | IRQF_DISABLED, |
825d3748 TK |
196 | wdt_gpi_name, &miscdev); |
197 | if (unlikely(res)) | |
198 | return res; | |
199 | ||
200 | wdt_gpi_set_timeout(timeout); | |
414a6759 | 201 | wdt_gpi_start(); |
825d3748 TK |
202 | |
203 | printk(KERN_INFO "%s: watchdog started, timeout = %u seconds\n", | |
204 | wdt_gpi_name, timeout); | |
d5d06ff7 | 205 | return nonseekable_open(inode, file); |
825d3748 TK |
206 | } |
207 | ||
d5d06ff7 | 208 | static int wdt_gpi_release(struct inode *inode, struct file *file) |
825d3748 TK |
209 | { |
210 | if (nowayout) { | |
d5d06ff7 | 211 | printk(KERN_INFO "%s: no way out - watchdog left running\n", |
825d3748 TK |
212 | wdt_gpi_name); |
213 | __module_get(THIS_MODULE); | |
214 | locked = 1; | |
215 | } else { | |
216 | if (expect_close) { | |
414a6759 | 217 | wdt_gpi_stop(); |
825d3748 TK |
218 | free_irq(wd_irq, &miscdev); |
219 | printk(KERN_INFO "%s: watchdog stopped\n", wdt_gpi_name); | |
220 | } else { | |
d5d06ff7 | 221 | printk(KERN_CRIT "%s: unexpected close() -" |
825d3748 TK |
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 | ||
825d3748 TK |
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 | ||
825d3748 TK |
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; | |
d5d06ff7 | 250 | void __user *argp = (void __user *)arg; |
825d3748 TK |
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; | |
d5d06ff7 | 275 | res = __copy_to_user(argp, &wdinfo, size) ? -EFAULT : size; |
825d3748 TK |
276 | break; |
277 | ||
278 | case WDIOC_GETSTATUS: | |
279 | break; | |
280 | ||
281 | case WDIOC_GETBOOTSTATUS: | |
282 | stat = (*(volatile char *) flagaddr & 0x01) | |
283 | ? WDIOF_CARDRESET : 0; | |
d5d06ff7 | 284 | res = __copy_to_user(argp, &stat, size) ? |
825d3748 TK |
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; | |
d5d06ff7 | 299 | if (unlikely(__copy_from_user(&val, argp, size))) { |
825d3748 TK |
300 | res = -EFAULT; |
301 | break; | |
302 | } | |
303 | ||
d5d06ff7 WVS |
304 | if (val > MAX_TIMEOUT_SECONDS) |
305 | val = MAX_TIMEOUT_SECONDS; | |
825d3748 TK |
306 | timeout = val; |
307 | wdt_gpi_set_timeout(val); | |
308 | res = size; | |
d5d06ff7 | 309 | printk(KERN_INFO "%s: timeout set to %u seconds\n", |
825d3748 TK |
310 | wdt_gpi_name, timeout); |
311 | } | |
312 | break; | |
313 | ||
314 | case WDIOC_GETTIMEOUT: | |
d5d06ff7 | 315 | res = __copy_to_user(argp, &timeout, size) ? |
825d3748 TK |
316 | -EFAULT : size; |
317 | break; | |
318 | } | |
319 | ||
320 | return res; | |
321 | } | |
322 | ||
323 | ||
d5d06ff7 | 324 | /* Shutdown notifier */ |
825d3748 TK |
325 | static int |
326 | wdt_gpi_notify(struct notifier_block *this, unsigned long code, void *unused) | |
327 | { | |
414a6759 WVS |
328 | if (code == SYS_DOWN || code == SYS_HALT) |
329 | wdt_gpi_stop(); | |
330 | ||
825d3748 TK |
331 | return NOTIFY_DONE; |
332 | } | |
333 | ||
334 | ||
97846e3c WVS |
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 | }; | |
825d3748 TK |
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 | ||
825d3748 TK |
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 | ||
825d3748 TK |
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); | |
cd57eeab | 420 |