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