diff options
Diffstat (limited to 'drivers/sbus/char/riowatchdog.c')
-rw-r--r-- | drivers/sbus/char/riowatchdog.c | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/drivers/sbus/char/riowatchdog.c b/drivers/sbus/char/riowatchdog.c new file mode 100644 index 000000000000..d1babff6a535 --- /dev/null +++ b/drivers/sbus/char/riowatchdog.c | |||
@@ -0,0 +1,293 @@ | |||
1 | /* $Id: riowatchdog.c,v 1.3.2.2 2002/01/23 18:48:02 davem Exp $ | ||
2 | * riowatchdog.c - driver for hw watchdog inside Super I/O of RIO | ||
3 | * | ||
4 | * Copyright (C) 2001 David S. Miller (davem@redhat.com) | ||
5 | */ | ||
6 | |||
7 | #include <linux/kernel.h> | ||
8 | #include <linux/module.h> | ||
9 | #include <linux/types.h> | ||
10 | #include <linux/fs.h> | ||
11 | #include <linux/errno.h> | ||
12 | #include <linux/init.h> | ||
13 | #include <linux/miscdevice.h> | ||
14 | |||
15 | #include <asm/io.h> | ||
16 | #include <asm/ebus.h> | ||
17 | #include <asm/bbc.h> | ||
18 | #include <asm/oplib.h> | ||
19 | #include <asm/uaccess.h> | ||
20 | |||
21 | #include <asm/watchdog.h> | ||
22 | |||
23 | /* RIO uses the NatSemi Super I/O power management logical device | ||
24 | * as its' watchdog. | ||
25 | * | ||
26 | * When the watchdog triggers, it asserts a line to the BBC (Boot Bus | ||
27 | * Controller) of the machine. The BBC can only be configured to | ||
28 | * trigger a power-on reset when the signal is asserted. The BBC | ||
29 | * can be configured to ignore the signal entirely as well. | ||
30 | * | ||
31 | * The only Super I/O device register we care about is at index | ||
32 | * 0x05 (WDTO_INDEX) which is the watchdog time-out in minutes (1-255). | ||
33 | * If set to zero, this disables the watchdog. When set, the system | ||
34 | * must periodically (before watchdog expires) clear (set to zero) and | ||
35 | * re-set the watchdog else it will trigger. | ||
36 | * | ||
37 | * There are two other indexed watchdog registers inside this Super I/O | ||
38 | * logical device, but they are unused. The first, at index 0x06 is | ||
39 | * the watchdog control and can be used to make the watchdog timer re-set | ||
40 | * when the PS/2 mouse or serial lines show activity. The second, at | ||
41 | * index 0x07 is merely a sampling of the line from the watchdog to the | ||
42 | * BBC. | ||
43 | * | ||
44 | * The watchdog device generates no interrupts. | ||
45 | */ | ||
46 | |||
47 | MODULE_AUTHOR("David S. Miller <davem@redhat.com>"); | ||
48 | MODULE_DESCRIPTION("Hardware watchdog driver for Sun RIO"); | ||
49 | MODULE_SUPPORTED_DEVICE("watchdog"); | ||
50 | MODULE_LICENSE("GPL"); | ||
51 | |||
52 | #define RIOWD_NAME "pmc" | ||
53 | #define RIOWD_MINOR 215 | ||
54 | |||
55 | static DEFINE_SPINLOCK(riowd_lock); | ||
56 | |||
57 | static void __iomem *bbc_regs; | ||
58 | static void __iomem *riowd_regs; | ||
59 | #define WDTO_INDEX 0x05 | ||
60 | |||
61 | static int riowd_timeout = 1; /* in minutes */ | ||
62 | module_param(riowd_timeout, int, 0); | ||
63 | MODULE_PARM_DESC(riowd_timeout, "Watchdog timeout in minutes"); | ||
64 | |||
65 | #if 0 /* Currently unused. */ | ||
66 | static u8 riowd_readreg(int index) | ||
67 | { | ||
68 | unsigned long flags; | ||
69 | u8 ret; | ||
70 | |||
71 | spin_lock_irqsave(&riowd_lock, flags); | ||
72 | writeb(index, riowd_regs + 0); | ||
73 | ret = readb(riowd_regs + 1); | ||
74 | spin_unlock_irqrestore(&riowd_lock, flags); | ||
75 | |||
76 | return ret; | ||
77 | } | ||
78 | #endif | ||
79 | |||
80 | static void riowd_writereg(u8 val, int index) | ||
81 | { | ||
82 | unsigned long flags; | ||
83 | |||
84 | spin_lock_irqsave(&riowd_lock, flags); | ||
85 | writeb(index, riowd_regs + 0); | ||
86 | writeb(val, riowd_regs + 1); | ||
87 | spin_unlock_irqrestore(&riowd_lock, flags); | ||
88 | } | ||
89 | |||
90 | static void riowd_pingtimer(void) | ||
91 | { | ||
92 | riowd_writereg(riowd_timeout, WDTO_INDEX); | ||
93 | } | ||
94 | |||
95 | static void riowd_stoptimer(void) | ||
96 | { | ||
97 | u8 val; | ||
98 | |||
99 | riowd_writereg(0, WDTO_INDEX); | ||
100 | |||
101 | val = readb(bbc_regs + BBC_WDACTION); | ||
102 | val &= ~BBC_WDACTION_RST; | ||
103 | writeb(val, bbc_regs + BBC_WDACTION); | ||
104 | } | ||
105 | |||
106 | static void riowd_starttimer(void) | ||
107 | { | ||
108 | u8 val; | ||
109 | |||
110 | riowd_writereg(riowd_timeout, WDTO_INDEX); | ||
111 | |||
112 | val = readb(bbc_regs + BBC_WDACTION); | ||
113 | val |= BBC_WDACTION_RST; | ||
114 | writeb(val, bbc_regs + BBC_WDACTION); | ||
115 | } | ||
116 | |||
117 | static int riowd_open(struct inode *inode, struct file *filp) | ||
118 | { | ||
119 | nonseekable_open(inode, filp); | ||
120 | return 0; | ||
121 | } | ||
122 | |||
123 | static int riowd_release(struct inode *inode, struct file *filp) | ||
124 | { | ||
125 | return 0; | ||
126 | } | ||
127 | |||
128 | static int riowd_ioctl(struct inode *inode, struct file *filp, | ||
129 | unsigned int cmd, unsigned long arg) | ||
130 | { | ||
131 | static struct watchdog_info info = { | ||
132 | WDIOF_SETTIMEOUT, 0, "Natl. Semiconductor PC97317" | ||
133 | }; | ||
134 | void __user *argp = (void __user *)arg; | ||
135 | unsigned int options; | ||
136 | int new_margin; | ||
137 | |||
138 | switch (cmd) { | ||
139 | case WDIOC_GETSUPPORT: | ||
140 | if (copy_to_user(argp, &info, sizeof(info))) | ||
141 | return -EFAULT; | ||
142 | break; | ||
143 | |||
144 | case WDIOC_GETSTATUS: | ||
145 | case WDIOC_GETBOOTSTATUS: | ||
146 | if (put_user(0, (int __user *)argp)) | ||
147 | return -EFAULT; | ||
148 | break; | ||
149 | |||
150 | case WDIOC_KEEPALIVE: | ||
151 | riowd_pingtimer(); | ||
152 | break; | ||
153 | |||
154 | case WDIOC_SETOPTIONS: | ||
155 | if (copy_from_user(&options, argp, sizeof(options))) | ||
156 | return -EFAULT; | ||
157 | |||
158 | if (options & WDIOS_DISABLECARD) | ||
159 | riowd_stoptimer(); | ||
160 | else if (options & WDIOS_ENABLECARD) | ||
161 | riowd_starttimer(); | ||
162 | else | ||
163 | return -EINVAL; | ||
164 | |||
165 | break; | ||
166 | |||
167 | case WDIOC_SETTIMEOUT: | ||
168 | if (get_user(new_margin, (int __user *)argp)) | ||
169 | return -EFAULT; | ||
170 | if ((new_margin < 60) || (new_margin > (255 * 60))) | ||
171 | return -EINVAL; | ||
172 | riowd_timeout = (new_margin + 59) / 60; | ||
173 | riowd_pingtimer(); | ||
174 | /* Fall */ | ||
175 | |||
176 | case WDIOC_GETTIMEOUT: | ||
177 | return put_user(riowd_timeout * 60, (int __user *)argp); | ||
178 | |||
179 | default: | ||
180 | return -EINVAL; | ||
181 | }; | ||
182 | |||
183 | return 0; | ||
184 | } | ||
185 | |||
186 | static ssize_t riowd_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) | ||
187 | { | ||
188 | if (count) { | ||
189 | riowd_pingtimer(); | ||
190 | return 1; | ||
191 | } | ||
192 | |||
193 | return 0; | ||
194 | } | ||
195 | |||
196 | static struct file_operations riowd_fops = { | ||
197 | .owner = THIS_MODULE, | ||
198 | .ioctl = riowd_ioctl, | ||
199 | .open = riowd_open, | ||
200 | .write = riowd_write, | ||
201 | .release = riowd_release, | ||
202 | }; | ||
203 | |||
204 | static struct miscdevice riowd_miscdev = { RIOWD_MINOR, RIOWD_NAME, &riowd_fops }; | ||
205 | |||
206 | static int __init riowd_bbc_init(void) | ||
207 | { | ||
208 | struct linux_ebus *ebus = NULL; | ||
209 | struct linux_ebus_device *edev = NULL; | ||
210 | u8 val; | ||
211 | |||
212 | for_each_ebus(ebus) { | ||
213 | for_each_ebusdev(edev, ebus) { | ||
214 | if (!strcmp(edev->prom_name, "bbc")) | ||
215 | goto found_bbc; | ||
216 | } | ||
217 | } | ||
218 | |||
219 | found_bbc: | ||
220 | if (!edev) | ||
221 | return -ENODEV; | ||
222 | bbc_regs = ioremap(edev->resource[0].start, BBC_REGS_SIZE); | ||
223 | if (!bbc_regs) | ||
224 | return -ENODEV; | ||
225 | |||
226 | /* Turn it off. */ | ||
227 | val = readb(bbc_regs + BBC_WDACTION); | ||
228 | val &= ~BBC_WDACTION_RST; | ||
229 | writeb(val, bbc_regs + BBC_WDACTION); | ||
230 | |||
231 | return 0; | ||
232 | } | ||
233 | |||
234 | static int __init riowd_init(void) | ||
235 | { | ||
236 | struct linux_ebus *ebus = NULL; | ||
237 | struct linux_ebus_device *edev = NULL; | ||
238 | |||
239 | for_each_ebus(ebus) { | ||
240 | for_each_ebusdev(edev, ebus) { | ||
241 | if (!strcmp(edev->prom_name, RIOWD_NAME)) | ||
242 | goto ebus_done; | ||
243 | } | ||
244 | } | ||
245 | |||
246 | ebus_done: | ||
247 | if (!edev) | ||
248 | goto fail; | ||
249 | |||
250 | riowd_regs = ioremap(edev->resource[0].start, 2); | ||
251 | if (riowd_regs == NULL) { | ||
252 | printk(KERN_ERR "pmc: Cannot map registers.\n"); | ||
253 | return -ENODEV; | ||
254 | } | ||
255 | |||
256 | if (riowd_bbc_init()) { | ||
257 | printk(KERN_ERR "pmc: Failure initializing BBC config.\n"); | ||
258 | goto fail; | ||
259 | } | ||
260 | |||
261 | if (misc_register(&riowd_miscdev)) { | ||
262 | printk(KERN_ERR "pmc: Cannot register watchdog misc device.\n"); | ||
263 | goto fail; | ||
264 | } | ||
265 | |||
266 | printk(KERN_INFO "pmc: Hardware watchdog [%i minutes], " | ||
267 | "regs at %p\n", riowd_timeout, riowd_regs); | ||
268 | |||
269 | return 0; | ||
270 | |||
271 | fail: | ||
272 | if (riowd_regs) { | ||
273 | iounmap(riowd_regs); | ||
274 | riowd_regs = NULL; | ||
275 | } | ||
276 | if (bbc_regs) { | ||
277 | iounmap(bbc_regs); | ||
278 | bbc_regs = NULL; | ||
279 | } | ||
280 | return -ENODEV; | ||
281 | } | ||
282 | |||
283 | static void __exit riowd_cleanup(void) | ||
284 | { | ||
285 | misc_deregister(&riowd_miscdev); | ||
286 | iounmap(riowd_regs); | ||
287 | riowd_regs = NULL; | ||
288 | iounmap(bbc_regs); | ||
289 | bbc_regs = NULL; | ||
290 | } | ||
291 | |||
292 | module_init(riowd_init); | ||
293 | module_exit(riowd_cleanup); | ||