]>
Commit | Line | Data |
---|---|---|
1da177e4 LT |
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) { | |
95b0ce9d | 214 | if (!strcmp(edev->ofdev.node->name, "bbc")) |
1da177e4 LT |
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) { | |
95b0ce9d | 241 | if (!strcmp(edev->ofdev.node->name, RIOWD_NAME)) |
1da177e4 LT |
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); |