Commit | Line | Data |
---|---|---|
28ff2f7a | 1 | /*************************************************************************** |
312869ec | 2 | * Copyright (C) 2010-2012 Hans de Goede <hdegoede@redhat.com> * |
28ff2f7a HG |
3 | * * |
4 | * This program is free software; you can redistribute it and/or modify * | |
5 | * it under the terms of the GNU General Public License as published by * | |
6 | * the Free Software Foundation; either version 2 of the License, or * | |
7 | * (at your option) any later version. * | |
8 | * * | |
9 | * This program is distributed in the hope that it will be useful, * | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | |
12 | * GNU General Public License for more details. * | |
13 | * * | |
14 | * You should have received a copy of the GNU General Public License * | |
15 | * along with this program; if not, write to the * | |
16 | * Free Software Foundation, Inc., * | |
17 | * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * | |
18 | ***************************************************************************/ | |
19 | ||
20 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
21 | ||
22 | #include <linux/module.h> | |
23 | #include <linux/init.h> | |
24 | #include <linux/platform_device.h> | |
25 | #include <linux/err.h> | |
26 | #include <linux/io.h> | |
27 | #include <linux/acpi.h> | |
28 | #include <linux/delay.h> | |
312869ec HG |
29 | #include <linux/fs.h> |
30 | #include <linux/watchdog.h> | |
31 | #include <linux/miscdevice.h> | |
32 | #include <linux/uaccess.h> | |
33 | #include <linux/kref.h> | |
34 | #include <linux/slab.h> | |
28ff2f7a HG |
35 | #include "sch56xx-common.h" |
36 | ||
312869ec HG |
37 | /* Insmod parameters */ |
38 | static int nowayout = WATCHDOG_NOWAYOUT; | |
39 | module_param(nowayout, int, 0); | |
40 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" | |
41 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | |
42 | ||
28ff2f7a HG |
43 | #define SIO_SCH56XX_LD_EM 0x0C /* Embedded uController Logical Dev */ |
44 | #define SIO_UNLOCK_KEY 0x55 /* Key to enable Super-I/O */ | |
45 | #define SIO_LOCK_KEY 0xAA /* Key to disable Super-I/O */ | |
46 | ||
47 | #define SIO_REG_LDSEL 0x07 /* Logical device select */ | |
48 | #define SIO_REG_DEVID 0x20 /* Device ID */ | |
49 | #define SIO_REG_ENABLE 0x30 /* Logical device enable */ | |
50 | #define SIO_REG_ADDR 0x66 /* Logical device address (2 bytes) */ | |
51 | ||
52 | #define SIO_SCH5627_ID 0xC6 /* Chipset ID */ | |
0772a640 | 53 | #define SIO_SCH5636_ID 0xC7 /* Chipset ID */ |
28ff2f7a | 54 | |
312869ec | 55 | #define REGION_LENGTH 10 |
28ff2f7a HG |
56 | |
57 | #define SCH56XX_CMD_READ 0x02 | |
58 | #define SCH56XX_CMD_WRITE 0x03 | |
59 | ||
312869ec HG |
60 | /* Watchdog registers */ |
61 | #define SCH56XX_REG_WDOG_PRESET 0x58B | |
62 | #define SCH56XX_REG_WDOG_CONTROL 0x58C | |
63 | #define SCH56XX_WDOG_TIME_BASE_SEC 0x01 | |
64 | #define SCH56XX_REG_WDOG_OUTPUT_ENABLE 0x58E | |
65 | #define SCH56XX_WDOG_OUTPUT_ENABLE 0x02 | |
66 | ||
67 | struct sch56xx_watchdog_data { | |
68 | u16 addr; | |
69 | u32 revision; | |
70 | struct mutex *io_lock; | |
71 | struct mutex watchdog_lock; | |
72 | struct list_head list; /* member of the watchdog_data_list */ | |
73 | struct kref kref; | |
74 | struct miscdevice watchdog_miscdev; | |
75 | unsigned long watchdog_is_open; | |
76 | char watchdog_name[10]; /* must be unique to avoid sysfs conflict */ | |
77 | char watchdog_expect_close; | |
78 | u8 watchdog_preset; | |
79 | u8 watchdog_control; | |
80 | u8 watchdog_output_enable; | |
81 | }; | |
82 | ||
28ff2f7a HG |
83 | static struct platform_device *sch56xx_pdev; |
84 | ||
312869ec HG |
85 | /* |
86 | * Somewhat ugly :( global data pointer list with all sch56xx devices, so that | |
87 | * we can find our device data as when using misc_register there is no other | |
88 | * method to get to ones device data from the open fop. | |
89 | */ | |
90 | static LIST_HEAD(watchdog_data_list); | |
91 | /* Note this lock not only protect list access, but also data.kref access */ | |
92 | static DEFINE_MUTEX(watchdog_data_mutex); | |
93 | ||
28ff2f7a HG |
94 | /* Super I/O functions */ |
95 | static inline int superio_inb(int base, int reg) | |
96 | { | |
97 | outb(reg, base); | |
98 | return inb(base + 1); | |
99 | } | |
100 | ||
101 | static inline int superio_enter(int base) | |
102 | { | |
103 | /* Don't step on other drivers' I/O space by accident */ | |
104 | if (!request_muxed_region(base, 2, "sch56xx")) { | |
105 | pr_err("I/O address 0x%04x already in use\n", base); | |
106 | return -EBUSY; | |
107 | } | |
108 | ||
109 | outb(SIO_UNLOCK_KEY, base); | |
110 | ||
111 | return 0; | |
112 | } | |
113 | ||
114 | static inline void superio_select(int base, int ld) | |
115 | { | |
116 | outb(SIO_REG_LDSEL, base); | |
117 | outb(ld, base + 1); | |
118 | } | |
119 | ||
120 | static inline void superio_exit(int base) | |
121 | { | |
122 | outb(SIO_LOCK_KEY, base); | |
123 | release_region(base, 2); | |
124 | } | |
125 | ||
126 | static int sch56xx_send_cmd(u16 addr, u8 cmd, u16 reg, u8 v) | |
127 | { | |
128 | u8 val; | |
129 | int i; | |
130 | /* | |
131 | * According to SMSC for the commands we use the maximum time for | |
132 | * the EM to respond is 15 ms, but testing shows in practice it | |
133 | * responds within 15-32 reads, so we first busy poll, and if | |
134 | * that fails sleep a bit and try again until we are way past | |
135 | * the 15 ms maximum response time. | |
136 | */ | |
137 | const int max_busy_polls = 64; | |
138 | const int max_lazy_polls = 32; | |
139 | ||
140 | /* (Optional) Write-Clear the EC to Host Mailbox Register */ | |
141 | val = inb(addr + 1); | |
142 | outb(val, addr + 1); | |
143 | ||
144 | /* Set Mailbox Address Pointer to first location in Region 1 */ | |
145 | outb(0x00, addr + 2); | |
146 | outb(0x80, addr + 3); | |
147 | ||
148 | /* Write Request Packet Header */ | |
149 | outb(cmd, addr + 4); /* VREG Access Type read:0x02 write:0x03 */ | |
150 | outb(0x01, addr + 5); /* # of Entries: 1 Byte (8-bit) */ | |
151 | outb(0x04, addr + 2); /* Mailbox AP to first data entry loc. */ | |
152 | ||
153 | /* Write Value field */ | |
154 | if (cmd == SCH56XX_CMD_WRITE) | |
155 | outb(v, addr + 4); | |
156 | ||
157 | /* Write Address field */ | |
158 | outb(reg & 0xff, addr + 6); | |
159 | outb(reg >> 8, addr + 7); | |
160 | ||
161 | /* Execute the Random Access Command */ | |
162 | outb(0x01, addr); /* Write 01h to the Host-to-EC register */ | |
163 | ||
164 | /* EM Interface Polling "Algorithm" */ | |
165 | for (i = 0; i < max_busy_polls + max_lazy_polls; i++) { | |
166 | if (i >= max_busy_polls) | |
167 | msleep(1); | |
168 | /* Read Interrupt source Register */ | |
169 | val = inb(addr + 8); | |
170 | /* Write Clear the interrupt source bits */ | |
171 | if (val) | |
172 | outb(val, addr + 8); | |
173 | /* Command Completed ? */ | |
174 | if (val & 0x01) | |
175 | break; | |
176 | } | |
177 | if (i == max_busy_polls + max_lazy_polls) { | |
178 | pr_err("Max retries exceeded reading virtual " | |
179 | "register 0x%04hx (%d)\n", reg, 1); | |
180 | return -EIO; | |
181 | } | |
182 | ||
183 | /* | |
184 | * According to SMSC we may need to retry this, but sofar I've always | |
185 | * seen this succeed in 1 try. | |
186 | */ | |
187 | for (i = 0; i < max_busy_polls; i++) { | |
188 | /* Read EC-to-Host Register */ | |
189 | val = inb(addr + 1); | |
190 | /* Command Completed ? */ | |
191 | if (val == 0x01) | |
192 | break; | |
193 | ||
194 | if (i == 0) | |
195 | pr_warn("EC reports: 0x%02x reading virtual register " | |
196 | "0x%04hx\n", (unsigned int)val, reg); | |
197 | } | |
198 | if (i == max_busy_polls) { | |
199 | pr_err("Max retries exceeded reading virtual " | |
200 | "register 0x%04hx (%d)\n", reg, 2); | |
201 | return -EIO; | |
202 | } | |
203 | ||
204 | /* | |
205 | * According to the SMSC app note we should now do: | |
206 | * | |
207 | * Set Mailbox Address Pointer to first location in Region 1 * | |
208 | * outb(0x00, addr + 2); | |
209 | * outb(0x80, addr + 3); | |
210 | * | |
211 | * But if we do that things don't work, so let's not. | |
212 | */ | |
213 | ||
214 | /* Read Value field */ | |
215 | if (cmd == SCH56XX_CMD_READ) | |
216 | return inb(addr + 4); | |
217 | ||
218 | return 0; | |
219 | } | |
220 | ||
221 | int sch56xx_read_virtual_reg(u16 addr, u16 reg) | |
222 | { | |
223 | return sch56xx_send_cmd(addr, SCH56XX_CMD_READ, reg, 0); | |
224 | } | |
225 | EXPORT_SYMBOL(sch56xx_read_virtual_reg); | |
226 | ||
227 | int sch56xx_write_virtual_reg(u16 addr, u16 reg, u8 val) | |
228 | { | |
229 | return sch56xx_send_cmd(addr, SCH56XX_CMD_WRITE, reg, val); | |
230 | } | |
231 | EXPORT_SYMBOL(sch56xx_write_virtual_reg); | |
232 | ||
233 | int sch56xx_read_virtual_reg16(u16 addr, u16 reg) | |
234 | { | |
235 | int lsb, msb; | |
236 | ||
237 | /* Read LSB first, this will cause the matching MSB to be latched */ | |
238 | lsb = sch56xx_read_virtual_reg(addr, reg); | |
239 | if (lsb < 0) | |
240 | return lsb; | |
241 | ||
242 | msb = sch56xx_read_virtual_reg(addr, reg + 1); | |
243 | if (msb < 0) | |
244 | return msb; | |
245 | ||
246 | return lsb | (msb << 8); | |
247 | } | |
248 | EXPORT_SYMBOL(sch56xx_read_virtual_reg16); | |
249 | ||
250 | int sch56xx_read_virtual_reg12(u16 addr, u16 msb_reg, u16 lsn_reg, | |
251 | int high_nibble) | |
252 | { | |
253 | int msb, lsn; | |
254 | ||
255 | /* Read MSB first, this will cause the matching LSN to be latched */ | |
256 | msb = sch56xx_read_virtual_reg(addr, msb_reg); | |
257 | if (msb < 0) | |
258 | return msb; | |
259 | ||
260 | lsn = sch56xx_read_virtual_reg(addr, lsn_reg); | |
261 | if (lsn < 0) | |
262 | return lsn; | |
263 | ||
264 | if (high_nibble) | |
265 | return (msb << 4) | (lsn >> 4); | |
266 | else | |
267 | return (msb << 4) | (lsn & 0x0f); | |
268 | } | |
269 | EXPORT_SYMBOL(sch56xx_read_virtual_reg12); | |
270 | ||
312869ec HG |
271 | /* |
272 | * Watchdog routines | |
273 | */ | |
274 | ||
275 | /* | |
276 | * Release our data struct when the platform device has been released *and* | |
277 | * all references to our watchdog device are released. | |
278 | */ | |
279 | static void sch56xx_watchdog_release_resources(struct kref *r) | |
280 | { | |
281 | struct sch56xx_watchdog_data *data = | |
282 | container_of(r, struct sch56xx_watchdog_data, kref); | |
283 | kfree(data); | |
284 | } | |
285 | ||
286 | static int watchdog_set_timeout(struct sch56xx_watchdog_data *data, | |
287 | int timeout) | |
288 | { | |
289 | int ret, resolution; | |
290 | u8 control; | |
291 | ||
292 | /* 1 second or 60 second resolution? */ | |
293 | if (timeout <= 255) | |
294 | resolution = 1; | |
295 | else | |
296 | resolution = 60; | |
297 | ||
298 | if (timeout < resolution || timeout > (resolution * 255)) | |
299 | return -EINVAL; | |
300 | ||
301 | mutex_lock(&data->watchdog_lock); | |
302 | if (!data->addr) { | |
303 | ret = -ENODEV; | |
304 | goto leave; | |
305 | } | |
306 | ||
307 | if (resolution == 1) | |
308 | control = data->watchdog_control | SCH56XX_WDOG_TIME_BASE_SEC; | |
309 | else | |
310 | control = data->watchdog_control & ~SCH56XX_WDOG_TIME_BASE_SEC; | |
311 | ||
312 | if (data->watchdog_control != control) { | |
313 | mutex_lock(data->io_lock); | |
314 | ret = sch56xx_write_virtual_reg(data->addr, | |
315 | SCH56XX_REG_WDOG_CONTROL, | |
316 | control); | |
317 | mutex_unlock(data->io_lock); | |
318 | if (ret) | |
319 | goto leave; | |
320 | ||
321 | data->watchdog_control = control; | |
322 | } | |
323 | ||
324 | /* | |
325 | * Remember new timeout value, but do not write as that (re)starts | |
326 | * the watchdog countdown. | |
327 | */ | |
328 | data->watchdog_preset = DIV_ROUND_UP(timeout, resolution); | |
329 | ||
330 | ret = data->watchdog_preset * resolution; | |
331 | leave: | |
332 | mutex_unlock(&data->watchdog_lock); | |
333 | return ret; | |
334 | } | |
335 | ||
336 | static int watchdog_get_timeout(struct sch56xx_watchdog_data *data) | |
337 | { | |
338 | int timeout; | |
339 | ||
340 | mutex_lock(&data->watchdog_lock); | |
341 | if (data->watchdog_control & SCH56XX_WDOG_TIME_BASE_SEC) | |
342 | timeout = data->watchdog_preset; | |
343 | else | |
344 | timeout = data->watchdog_preset * 60; | |
345 | mutex_unlock(&data->watchdog_lock); | |
346 | ||
347 | return timeout; | |
348 | } | |
349 | ||
350 | static int watchdog_start(struct sch56xx_watchdog_data *data) | |
351 | { | |
352 | int ret; | |
353 | u8 val; | |
354 | ||
355 | mutex_lock(&data->watchdog_lock); | |
356 | if (!data->addr) { | |
357 | ret = -ENODEV; | |
358 | goto leave_unlock_watchdog; | |
359 | } | |
360 | ||
361 | /* | |
362 | * The sch56xx's watchdog cannot really be started / stopped | |
363 | * it is always running, but we can avoid the timer expiring | |
364 | * from causing a system reset by clearing the output enable bit. | |
365 | * | |
366 | * The sch56xx's watchdog will set the watchdog event bit, bit 0 | |
367 | * of the second interrupt source register (at base-address + 9), | |
368 | * when the timer expires. | |
369 | * | |
370 | * This will only cause a system reset if the 0-1 flank happens when | |
371 | * output enable is true. Setting output enable after the flank will | |
372 | * not cause a reset, nor will the timer expiring a second time. | |
373 | * This means we must clear the watchdog event bit in case it is set. | |
374 | * | |
375 | * The timer may still be running (after a recent watchdog_stop) and | |
376 | * mere milliseconds away from expiring, so the timer must be reset | |
377 | * first! | |
378 | */ | |
379 | ||
380 | mutex_lock(data->io_lock); | |
381 | ||
382 | /* 1. Reset the watchdog countdown counter */ | |
383 | ret = sch56xx_write_virtual_reg(data->addr, SCH56XX_REG_WDOG_PRESET, | |
384 | data->watchdog_preset); | |
385 | if (ret) | |
386 | goto leave; | |
387 | ||
388 | /* 2. Enable output (if not already enabled) */ | |
389 | if (!(data->watchdog_output_enable & SCH56XX_WDOG_OUTPUT_ENABLE)) { | |
390 | val = data->watchdog_output_enable | | |
391 | SCH56XX_WDOG_OUTPUT_ENABLE; | |
392 | ret = sch56xx_write_virtual_reg(data->addr, | |
393 | SCH56XX_REG_WDOG_OUTPUT_ENABLE, | |
394 | val); | |
395 | if (ret) | |
396 | goto leave; | |
397 | ||
398 | data->watchdog_output_enable = val; | |
399 | } | |
400 | ||
401 | /* 3. Clear the watchdog event bit if set */ | |
402 | val = inb(data->addr + 9); | |
403 | if (val & 0x01) | |
404 | outb(0x01, data->addr + 9); | |
405 | ||
406 | leave: | |
407 | mutex_unlock(data->io_lock); | |
408 | leave_unlock_watchdog: | |
409 | mutex_unlock(&data->watchdog_lock); | |
410 | return ret; | |
411 | } | |
412 | ||
413 | static int watchdog_trigger(struct sch56xx_watchdog_data *data) | |
414 | { | |
415 | int ret; | |
416 | ||
417 | mutex_lock(&data->watchdog_lock); | |
418 | if (!data->addr) { | |
419 | ret = -ENODEV; | |
420 | goto leave; | |
421 | } | |
422 | ||
423 | /* Reset the watchdog countdown counter */ | |
424 | mutex_lock(data->io_lock); | |
425 | ret = sch56xx_write_virtual_reg(data->addr, SCH56XX_REG_WDOG_PRESET, | |
426 | data->watchdog_preset); | |
427 | mutex_unlock(data->io_lock); | |
428 | leave: | |
429 | mutex_unlock(&data->watchdog_lock); | |
430 | return ret; | |
431 | } | |
432 | ||
433 | static int watchdog_stop_unlocked(struct sch56xx_watchdog_data *data) | |
434 | { | |
435 | int ret = 0; | |
436 | u8 val; | |
437 | ||
438 | if (!data->addr) | |
439 | return -ENODEV; | |
440 | ||
441 | if (data->watchdog_output_enable & SCH56XX_WDOG_OUTPUT_ENABLE) { | |
442 | val = data->watchdog_output_enable & | |
443 | ~SCH56XX_WDOG_OUTPUT_ENABLE; | |
444 | mutex_lock(data->io_lock); | |
445 | ret = sch56xx_write_virtual_reg(data->addr, | |
446 | SCH56XX_REG_WDOG_OUTPUT_ENABLE, | |
447 | val); | |
448 | mutex_unlock(data->io_lock); | |
449 | if (ret) | |
450 | return ret; | |
451 | ||
452 | data->watchdog_output_enable = val; | |
453 | } | |
454 | ||
455 | return ret; | |
456 | } | |
457 | ||
458 | static int watchdog_stop(struct sch56xx_watchdog_data *data) | |
459 | { | |
460 | int ret; | |
461 | ||
462 | mutex_lock(&data->watchdog_lock); | |
463 | ret = watchdog_stop_unlocked(data); | |
464 | mutex_unlock(&data->watchdog_lock); | |
465 | ||
466 | return ret; | |
467 | } | |
468 | ||
469 | static int watchdog_release(struct inode *inode, struct file *filp) | |
470 | { | |
471 | struct sch56xx_watchdog_data *data = filp->private_data; | |
472 | ||
473 | if (data->watchdog_expect_close) { | |
474 | watchdog_stop(data); | |
475 | data->watchdog_expect_close = 0; | |
476 | } else { | |
477 | watchdog_trigger(data); | |
478 | pr_crit("unexpected close, not stopping watchdog!\n"); | |
479 | } | |
480 | ||
481 | clear_bit(0, &data->watchdog_is_open); | |
482 | ||
483 | mutex_lock(&watchdog_data_mutex); | |
484 | kref_put(&data->kref, sch56xx_watchdog_release_resources); | |
485 | mutex_unlock(&watchdog_data_mutex); | |
486 | ||
487 | return 0; | |
488 | } | |
489 | ||
490 | static int watchdog_open(struct inode *inode, struct file *filp) | |
491 | { | |
492 | struct sch56xx_watchdog_data *pos, *data = NULL; | |
493 | int ret, watchdog_is_open; | |
494 | ||
495 | /* | |
496 | * We get called from drivers/char/misc.c with misc_mtx hold, and we | |
497 | * call misc_register() from sch56xx_watchdog_probe() with | |
498 | * watchdog_data_mutex hold, as misc_register() takes the misc_mtx | |
499 | * lock, this is a possible deadlock, so we use mutex_trylock here. | |
500 | */ | |
501 | if (!mutex_trylock(&watchdog_data_mutex)) | |
502 | return -ERESTARTSYS; | |
503 | list_for_each_entry(pos, &watchdog_data_list, list) { | |
504 | if (pos->watchdog_miscdev.minor == iminor(inode)) { | |
505 | data = pos; | |
506 | break; | |
507 | } | |
508 | } | |
509 | /* Note we can never not have found data, so we don't check for this */ | |
510 | watchdog_is_open = test_and_set_bit(0, &data->watchdog_is_open); | |
511 | if (!watchdog_is_open) | |
512 | kref_get(&data->kref); | |
513 | mutex_unlock(&watchdog_data_mutex); | |
514 | ||
515 | if (watchdog_is_open) | |
516 | return -EBUSY; | |
517 | ||
518 | filp->private_data = data; | |
519 | ||
520 | /* Start the watchdog */ | |
521 | ret = watchdog_start(data); | |
522 | if (ret) { | |
523 | watchdog_release(inode, filp); | |
524 | return ret; | |
525 | } | |
526 | ||
527 | return nonseekable_open(inode, filp); | |
528 | } | |
529 | ||
530 | static ssize_t watchdog_write(struct file *filp, const char __user *buf, | |
531 | size_t count, loff_t *offset) | |
532 | { | |
533 | int ret; | |
534 | struct sch56xx_watchdog_data *data = filp->private_data; | |
535 | ||
536 | if (count) { | |
537 | if (!nowayout) { | |
538 | size_t i; | |
539 | ||
540 | /* Clear it in case it was set with a previous write */ | |
541 | data->watchdog_expect_close = 0; | |
542 | ||
543 | for (i = 0; i != count; i++) { | |
544 | char c; | |
545 | if (get_user(c, buf + i)) | |
546 | return -EFAULT; | |
547 | if (c == 'V') | |
548 | data->watchdog_expect_close = 1; | |
549 | } | |
550 | } | |
551 | ret = watchdog_trigger(data); | |
552 | if (ret) | |
553 | return ret; | |
554 | } | |
555 | return count; | |
556 | } | |
557 | ||
558 | static long watchdog_ioctl(struct file *filp, unsigned int cmd, | |
559 | unsigned long arg) | |
560 | { | |
561 | struct watchdog_info ident = { | |
562 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT, | |
563 | .identity = "sch56xx watchdog" | |
564 | }; | |
565 | int i, ret = 0; | |
566 | struct sch56xx_watchdog_data *data = filp->private_data; | |
567 | ||
568 | switch (cmd) { | |
569 | case WDIOC_GETSUPPORT: | |
570 | ident.firmware_version = data->revision; | |
571 | if (!nowayout) | |
572 | ident.options |= WDIOF_MAGICCLOSE; | |
573 | if (copy_to_user((void __user *)arg, &ident, sizeof(ident))) | |
574 | ret = -EFAULT; | |
575 | break; | |
576 | ||
577 | case WDIOC_GETSTATUS: | |
578 | case WDIOC_GETBOOTSTATUS: | |
579 | ret = put_user(0, (int __user *)arg); | |
580 | break; | |
581 | ||
582 | case WDIOC_KEEPALIVE: | |
583 | ret = watchdog_trigger(data); | |
584 | break; | |
585 | ||
586 | case WDIOC_GETTIMEOUT: | |
587 | i = watchdog_get_timeout(data); | |
588 | ret = put_user(i, (int __user *)arg); | |
589 | break; | |
590 | ||
591 | case WDIOC_SETTIMEOUT: | |
592 | if (get_user(i, (int __user *)arg)) { | |
593 | ret = -EFAULT; | |
594 | break; | |
595 | } | |
596 | ret = watchdog_set_timeout(data, i); | |
597 | if (ret >= 0) | |
598 | ret = put_user(ret, (int __user *)arg); | |
599 | break; | |
600 | ||
601 | case WDIOC_SETOPTIONS: | |
602 | if (get_user(i, (int __user *)arg)) { | |
603 | ret = -EFAULT; | |
604 | break; | |
605 | } | |
606 | ||
607 | if (i & WDIOS_DISABLECARD) | |
608 | ret = watchdog_stop(data); | |
609 | else if (i & WDIOS_ENABLECARD) | |
610 | ret = watchdog_trigger(data); | |
611 | else | |
612 | ret = -EINVAL; | |
613 | break; | |
614 | ||
615 | default: | |
616 | ret = -ENOTTY; | |
617 | } | |
618 | return ret; | |
619 | } | |
620 | ||
621 | static const struct file_operations watchdog_fops = { | |
622 | .owner = THIS_MODULE, | |
623 | .llseek = no_llseek, | |
624 | .open = watchdog_open, | |
625 | .release = watchdog_release, | |
626 | .write = watchdog_write, | |
627 | .unlocked_ioctl = watchdog_ioctl, | |
628 | }; | |
629 | ||
630 | struct sch56xx_watchdog_data *sch56xx_watchdog_register( | |
631 | u16 addr, u32 revision, struct mutex *io_lock, int check_enabled) | |
632 | { | |
633 | struct sch56xx_watchdog_data *data; | |
634 | int i, err, control, output_enable; | |
635 | const int watchdog_minors[] = { WATCHDOG_MINOR, 212, 213, 214, 215 }; | |
636 | ||
637 | /* Cache the watchdog registers */ | |
638 | mutex_lock(io_lock); | |
639 | control = | |
640 | sch56xx_read_virtual_reg(addr, SCH56XX_REG_WDOG_CONTROL); | |
641 | output_enable = | |
642 | sch56xx_read_virtual_reg(addr, SCH56XX_REG_WDOG_OUTPUT_ENABLE); | |
643 | mutex_unlock(io_lock); | |
644 | ||
645 | if (control < 0) | |
646 | return NULL; | |
647 | if (output_enable < 0) | |
648 | return NULL; | |
649 | if (check_enabled && !(output_enable & SCH56XX_WDOG_OUTPUT_ENABLE)) { | |
650 | pr_warn("Watchdog not enabled by BIOS, not registering\n"); | |
651 | return NULL; | |
652 | } | |
653 | ||
654 | data = kzalloc(sizeof(struct sch56xx_watchdog_data), GFP_KERNEL); | |
655 | if (!data) | |
656 | return NULL; | |
657 | ||
658 | data->addr = addr; | |
659 | data->revision = revision; | |
660 | data->io_lock = io_lock; | |
661 | data->watchdog_control = control; | |
662 | data->watchdog_output_enable = output_enable; | |
663 | mutex_init(&data->watchdog_lock); | |
664 | INIT_LIST_HEAD(&data->list); | |
665 | kref_init(&data->kref); | |
666 | ||
667 | err = watchdog_set_timeout(data, 60); | |
668 | if (err < 0) | |
669 | goto error; | |
670 | ||
671 | /* | |
672 | * We take the data_mutex lock early so that watchdog_open() cannot | |
673 | * run when misc_register() has completed, but we've not yet added | |
674 | * our data to the watchdog_data_list. | |
675 | */ | |
676 | mutex_lock(&watchdog_data_mutex); | |
677 | for (i = 0; i < ARRAY_SIZE(watchdog_minors); i++) { | |
678 | /* Register our watchdog part */ | |
679 | snprintf(data->watchdog_name, sizeof(data->watchdog_name), | |
680 | "watchdog%c", (i == 0) ? '\0' : ('0' + i)); | |
681 | data->watchdog_miscdev.name = data->watchdog_name; | |
682 | data->watchdog_miscdev.fops = &watchdog_fops; | |
683 | data->watchdog_miscdev.minor = watchdog_minors[i]; | |
684 | err = misc_register(&data->watchdog_miscdev); | |
685 | if (err == -EBUSY) | |
686 | continue; | |
687 | if (err) | |
688 | break; | |
689 | ||
690 | list_add(&data->list, &watchdog_data_list); | |
691 | pr_info("Registered /dev/%s chardev major 10, minor: %d\n", | |
692 | data->watchdog_name, watchdog_minors[i]); | |
693 | break; | |
694 | } | |
695 | mutex_unlock(&watchdog_data_mutex); | |
696 | ||
697 | if (err) { | |
698 | pr_err("Registering watchdog chardev: %d\n", err); | |
699 | goto error; | |
700 | } | |
701 | if (i == ARRAY_SIZE(watchdog_minors)) { | |
702 | pr_warn("Couldn't register watchdog (no free minor)\n"); | |
703 | goto error; | |
704 | } | |
705 | ||
706 | return data; | |
707 | ||
708 | error: | |
709 | kfree(data); | |
710 | return NULL; | |
711 | } | |
712 | EXPORT_SYMBOL(sch56xx_watchdog_register); | |
713 | ||
714 | void sch56xx_watchdog_unregister(struct sch56xx_watchdog_data *data) | |
715 | { | |
716 | mutex_lock(&watchdog_data_mutex); | |
717 | misc_deregister(&data->watchdog_miscdev); | |
718 | list_del(&data->list); | |
719 | mutex_unlock(&watchdog_data_mutex); | |
720 | ||
721 | mutex_lock(&data->watchdog_lock); | |
722 | if (data->watchdog_is_open) { | |
723 | pr_warn("platform device unregistered with watchdog " | |
724 | "open! Stopping watchdog.\n"); | |
725 | watchdog_stop_unlocked(data); | |
726 | } | |
727 | /* Tell the wdog start/stop/trigger functions our dev is gone */ | |
728 | data->addr = 0; | |
729 | data->io_lock = NULL; | |
730 | mutex_unlock(&data->watchdog_lock); | |
731 | ||
732 | mutex_lock(&watchdog_data_mutex); | |
733 | kref_put(&data->kref, sch56xx_watchdog_release_resources); | |
734 | mutex_unlock(&watchdog_data_mutex); | |
735 | } | |
736 | EXPORT_SYMBOL(sch56xx_watchdog_unregister); | |
737 | ||
738 | /* | |
739 | * platform dev find, add and remove functions | |
740 | */ | |
741 | ||
28ff2f7a HG |
742 | static int __init sch56xx_find(int sioaddr, unsigned short *address, |
743 | const char **name) | |
744 | { | |
745 | u8 devid; | |
746 | int err; | |
747 | ||
748 | err = superio_enter(sioaddr); | |
749 | if (err) | |
750 | return err; | |
751 | ||
752 | devid = superio_inb(sioaddr, SIO_REG_DEVID); | |
753 | switch (devid) { | |
754 | case SIO_SCH5627_ID: | |
755 | *name = "sch5627"; | |
756 | break; | |
0772a640 HG |
757 | case SIO_SCH5636_ID: |
758 | *name = "sch5636"; | |
759 | break; | |
28ff2f7a HG |
760 | default: |
761 | pr_debug("Unsupported device id: 0x%02x\n", | |
762 | (unsigned int)devid); | |
763 | err = -ENODEV; | |
764 | goto exit; | |
765 | } | |
766 | ||
767 | superio_select(sioaddr, SIO_SCH56XX_LD_EM); | |
768 | ||
769 | if (!(superio_inb(sioaddr, SIO_REG_ENABLE) & 0x01)) { | |
770 | pr_warn("Device not activated\n"); | |
771 | err = -ENODEV; | |
772 | goto exit; | |
773 | } | |
774 | ||
775 | /* | |
776 | * Warning the order of the low / high byte is the other way around | |
777 | * as on most other superio devices!! | |
778 | */ | |
779 | *address = superio_inb(sioaddr, SIO_REG_ADDR) | | |
780 | superio_inb(sioaddr, SIO_REG_ADDR + 1) << 8; | |
781 | if (*address == 0) { | |
782 | pr_warn("Base address not set\n"); | |
783 | err = -ENODEV; | |
784 | goto exit; | |
785 | } | |
786 | ||
787 | exit: | |
788 | superio_exit(sioaddr); | |
789 | return err; | |
790 | } | |
791 | ||
792 | static int __init sch56xx_device_add(unsigned short address, const char *name) | |
793 | { | |
794 | struct resource res = { | |
795 | .start = address, | |
796 | .end = address + REGION_LENGTH - 1, | |
797 | .flags = IORESOURCE_IO, | |
798 | }; | |
799 | int err; | |
800 | ||
801 | sch56xx_pdev = platform_device_alloc(name, address); | |
802 | if (!sch56xx_pdev) | |
803 | return -ENOMEM; | |
804 | ||
805 | res.name = sch56xx_pdev->name; | |
806 | err = acpi_check_resource_conflict(&res); | |
807 | if (err) | |
808 | goto exit_device_put; | |
809 | ||
810 | err = platform_device_add_resources(sch56xx_pdev, &res, 1); | |
811 | if (err) { | |
812 | pr_err("Device resource addition failed\n"); | |
813 | goto exit_device_put; | |
814 | } | |
815 | ||
816 | err = platform_device_add(sch56xx_pdev); | |
817 | if (err) { | |
818 | pr_err("Device addition failed\n"); | |
819 | goto exit_device_put; | |
820 | } | |
821 | ||
822 | return 0; | |
823 | ||
824 | exit_device_put: | |
825 | platform_device_put(sch56xx_pdev); | |
826 | ||
827 | return err; | |
828 | } | |
829 | ||
830 | static int __init sch56xx_init(void) | |
831 | { | |
832 | int err; | |
833 | unsigned short address; | |
834 | const char *name; | |
835 | ||
836 | err = sch56xx_find(0x4e, &address, &name); | |
837 | if (err) | |
838 | err = sch56xx_find(0x2e, &address, &name); | |
839 | if (err) | |
840 | return err; | |
841 | ||
842 | return sch56xx_device_add(address, name); | |
843 | } | |
844 | ||
845 | static void __exit sch56xx_exit(void) | |
846 | { | |
847 | platform_device_unregister(sch56xx_pdev); | |
848 | } | |
849 | ||
850 | MODULE_DESCRIPTION("SMSC SCH56xx Hardware Monitoring Common Code"); | |
851 | MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); | |
852 | MODULE_LICENSE("GPL"); | |
853 | ||
854 | module_init(sch56xx_init); | |
855 | module_exit(sch56xx_exit); |