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