diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/hwmon/Kconfig | 6 | ||||
-rw-r--r-- | drivers/hwmon/sch5627.c | 11 | ||||
-rw-r--r-- | drivers/hwmon/sch5636.c | 11 | ||||
-rw-r--r-- | drivers/hwmon/sch56xx-common.c | 519 | ||||
-rw-r--r-- | drivers/hwmon/sch56xx-common.h | 10 |
5 files changed, 550 insertions, 7 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 1cd0e201819c..811e6c47e7e6 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig | |||
@@ -1028,7 +1028,8 @@ config SENSORS_SCH5627 | |||
1028 | select SENSORS_SCH56XX_COMMON | 1028 | select SENSORS_SCH56XX_COMMON |
1029 | help | 1029 | help |
1030 | If you say yes here you get support for the hardware monitoring | 1030 | If you say yes here you get support for the hardware monitoring |
1031 | features of the SMSC SCH5627 Super-I/O chip. | 1031 | features of the SMSC SCH5627 Super-I/O chip including support for |
1032 | the integrated watchdog. | ||
1032 | 1033 | ||
1033 | This driver can also be built as a module. If so, the module | 1034 | This driver can also be built as a module. If so, the module |
1034 | will be called sch5627. | 1035 | will be called sch5627. |
@@ -1044,7 +1045,8 @@ config SENSORS_SCH5636 | |||
1044 | 1045 | ||
1045 | Currently this driver only supports the Fujitsu Theseus SCH5636 based | 1046 | Currently this driver only supports the Fujitsu Theseus SCH5636 based |
1046 | hwmon solution. Say yes here if you want support for the Fujitsu | 1047 | hwmon solution. Say yes here if you want support for the Fujitsu |
1047 | Theseus' hardware monitoring features. | 1048 | Theseus' hardware monitoring features including support for the |
1049 | integrated watchdog. | ||
1048 | 1050 | ||
1049 | This driver can also be built as a module. If so, the module | 1051 | This driver can also be built as a module. If so, the module |
1050 | will be called sch5636. | 1052 | will be called sch5636. |
diff --git a/drivers/hwmon/sch5627.c b/drivers/hwmon/sch5627.c index 79b6dabe3161..8ec6dfbccb64 100644 --- a/drivers/hwmon/sch5627.c +++ b/drivers/hwmon/sch5627.c | |||
@@ -1,5 +1,5 @@ | |||
1 | /*************************************************************************** | 1 | /*************************************************************************** |
2 | * Copyright (C) 2010-2011 Hans de Goede <hdegoede@redhat.com> * | 2 | * Copyright (C) 2010-2012 Hans de Goede <hdegoede@redhat.com> * |
3 | * * | 3 | * * |
4 | * This program is free software; you can redistribute it and/or modify * | 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 * | 5 | * it under the terms of the GNU General Public License as published by * |
@@ -79,6 +79,7 @@ static const char * const SCH5627_IN_LABELS[SCH5627_NO_IN] = { | |||
79 | struct sch5627_data { | 79 | struct sch5627_data { |
80 | unsigned short addr; | 80 | unsigned short addr; |
81 | struct device *hwmon_dev; | 81 | struct device *hwmon_dev; |
82 | struct sch56xx_watchdog_data *watchdog; | ||
82 | u8 control; | 83 | u8 control; |
83 | u8 temp_max[SCH5627_NO_TEMPS]; | 84 | u8 temp_max[SCH5627_NO_TEMPS]; |
84 | u8 temp_crit[SCH5627_NO_TEMPS]; | 85 | u8 temp_crit[SCH5627_NO_TEMPS]; |
@@ -453,6 +454,9 @@ static int sch5627_remove(struct platform_device *pdev) | |||
453 | { | 454 | { |
454 | struct sch5627_data *data = platform_get_drvdata(pdev); | 455 | struct sch5627_data *data = platform_get_drvdata(pdev); |
455 | 456 | ||
457 | if (data->watchdog) | ||
458 | sch56xx_watchdog_unregister(data->watchdog); | ||
459 | |||
456 | if (data->hwmon_dev) | 460 | if (data->hwmon_dev) |
457 | hwmon_device_unregister(data->hwmon_dev); | 461 | hwmon_device_unregister(data->hwmon_dev); |
458 | 462 | ||
@@ -574,6 +578,11 @@ static int __devinit sch5627_probe(struct platform_device *pdev) | |||
574 | goto error; | 578 | goto error; |
575 | } | 579 | } |
576 | 580 | ||
581 | /* Note failing to register the watchdog is not a fatal error */ | ||
582 | data->watchdog = sch56xx_watchdog_register(data->addr, | ||
583 | (build_code << 24) | (build_id << 8) | hwmon_rev, | ||
584 | &data->update_lock, 1); | ||
585 | |||
577 | return 0; | 586 | return 0; |
578 | 587 | ||
579 | error: | 588 | error: |
diff --git a/drivers/hwmon/sch5636.c b/drivers/hwmon/sch5636.c index 9d5236fb09b4..906d4ed32d81 100644 --- a/drivers/hwmon/sch5636.c +++ b/drivers/hwmon/sch5636.c | |||
@@ -1,5 +1,5 @@ | |||
1 | /*************************************************************************** | 1 | /*************************************************************************** |
2 | * Copyright (C) 2011 Hans de Goede <hdegoede@redhat.com> * | 2 | * Copyright (C) 2011-2012 Hans de Goede <hdegoede@redhat.com> * |
3 | * * | 3 | * * |
4 | * This program is free software; you can redistribute it and/or modify * | 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 * | 5 | * it under the terms of the GNU General Public License as published by * |
@@ -67,6 +67,7 @@ static const u16 SCH5636_REG_FAN_VAL[SCH5636_NO_FANS] = { | |||
67 | struct sch5636_data { | 67 | struct sch5636_data { |
68 | unsigned short addr; | 68 | unsigned short addr; |
69 | struct device *hwmon_dev; | 69 | struct device *hwmon_dev; |
70 | struct sch56xx_watchdog_data *watchdog; | ||
70 | 71 | ||
71 | struct mutex update_lock; | 72 | struct mutex update_lock; |
72 | char valid; /* !=0 if following fields are valid */ | 73 | char valid; /* !=0 if following fields are valid */ |
@@ -384,6 +385,9 @@ static int sch5636_remove(struct platform_device *pdev) | |||
384 | struct sch5636_data *data = platform_get_drvdata(pdev); | 385 | struct sch5636_data *data = platform_get_drvdata(pdev); |
385 | int i; | 386 | int i; |
386 | 387 | ||
388 | if (data->watchdog) | ||
389 | sch56xx_watchdog_unregister(data->watchdog); | ||
390 | |||
387 | if (data->hwmon_dev) | 391 | if (data->hwmon_dev) |
388 | hwmon_device_unregister(data->hwmon_dev); | 392 | hwmon_device_unregister(data->hwmon_dev); |
389 | 393 | ||
@@ -505,6 +509,11 @@ static int __devinit sch5636_probe(struct platform_device *pdev) | |||
505 | goto error; | 509 | goto error; |
506 | } | 510 | } |
507 | 511 | ||
512 | /* Note failing to register the watchdog is not a fatal error */ | ||
513 | data->watchdog = sch56xx_watchdog_register(data->addr, | ||
514 | (revision[0] << 8) | revision[1], | ||
515 | &data->update_lock, 0); | ||
516 | |||
508 | return 0; | 517 | return 0; |
509 | 518 | ||
510 | error: | 519 | error: |
diff --git a/drivers/hwmon/sch56xx-common.c b/drivers/hwmon/sch56xx-common.c index fac32ee0b10e..ce52fc57d41d 100644 --- a/drivers/hwmon/sch56xx-common.c +++ b/drivers/hwmon/sch56xx-common.c | |||
@@ -1,5 +1,5 @@ | |||
1 | /*************************************************************************** | 1 | /*************************************************************************** |
2 | * Copyright (C) 2010-2011 Hans de Goede <hdegoede@redhat.com> * | 2 | * Copyright (C) 2010-2012 Hans de Goede <hdegoede@redhat.com> * |
3 | * * | 3 | * * |
4 | * This program is free software; you can redistribute it and/or modify * | 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 * | 5 | * it under the terms of the GNU General Public License as published by * |
@@ -26,8 +26,20 @@ | |||
26 | #include <linux/io.h> | 26 | #include <linux/io.h> |
27 | #include <linux/acpi.h> | 27 | #include <linux/acpi.h> |
28 | #include <linux/delay.h> | 28 | #include <linux/delay.h> |
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> | ||
29 | #include "sch56xx-common.h" | 35 | #include "sch56xx-common.h" |
30 | 36 | ||
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 | |||
31 | #define SIO_SCH56XX_LD_EM 0x0C /* Embedded uController Logical Dev */ | 43 | #define SIO_SCH56XX_LD_EM 0x0C /* Embedded uController Logical Dev */ |
32 | #define SIO_UNLOCK_KEY 0x55 /* Key to enable Super-I/O */ | 44 | #define SIO_UNLOCK_KEY 0x55 /* Key to enable Super-I/O */ |
33 | #define SIO_LOCK_KEY 0xAA /* Key to disable Super-I/O */ | 45 | #define SIO_LOCK_KEY 0xAA /* Key to disable Super-I/O */ |
@@ -40,13 +52,45 @@ | |||
40 | #define SIO_SCH5627_ID 0xC6 /* Chipset ID */ | 52 | #define SIO_SCH5627_ID 0xC6 /* Chipset ID */ |
41 | #define SIO_SCH5636_ID 0xC7 /* Chipset ID */ | 53 | #define SIO_SCH5636_ID 0xC7 /* Chipset ID */ |
42 | 54 | ||
43 | #define REGION_LENGTH 9 | 55 | #define REGION_LENGTH 10 |
44 | 56 | ||
45 | #define SCH56XX_CMD_READ 0x02 | 57 | #define SCH56XX_CMD_READ 0x02 |
46 | #define SCH56XX_CMD_WRITE 0x03 | 58 | #define SCH56XX_CMD_WRITE 0x03 |
47 | 59 | ||
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 | |||
48 | static struct platform_device *sch56xx_pdev; | 83 | static struct platform_device *sch56xx_pdev; |
49 | 84 | ||
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 | |||
50 | /* Super I/O functions */ | 94 | /* Super I/O functions */ |
51 | static inline int superio_inb(int base, int reg) | 95 | static inline int superio_inb(int base, int reg) |
52 | { | 96 | { |
@@ -224,6 +268,477 @@ int sch56xx_read_virtual_reg12(u16 addr, u16 msb_reg, u16 lsn_reg, | |||
224 | } | 268 | } |
225 | EXPORT_SYMBOL(sch56xx_read_virtual_reg12); | 269 | EXPORT_SYMBOL(sch56xx_read_virtual_reg12); |
226 | 270 | ||
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 | |||
227 | static int __init sch56xx_find(int sioaddr, unsigned short *address, | 742 | static int __init sch56xx_find(int sioaddr, unsigned short *address, |
228 | const char **name) | 743 | const char **name) |
229 | { | 744 | { |
diff --git a/drivers/hwmon/sch56xx-common.h b/drivers/hwmon/sch56xx-common.h index d5eaf3b9ebf5..7475086eb978 100644 --- a/drivers/hwmon/sch56xx-common.h +++ b/drivers/hwmon/sch56xx-common.h | |||
@@ -1,5 +1,5 @@ | |||
1 | /*************************************************************************** | 1 | /*************************************************************************** |
2 | * Copyright (C) 2010-2011 Hans de Goede <hdegoede@redhat.com> * | 2 | * Copyright (C) 2010-2012 Hans de Goede <hdegoede@redhat.com> * |
3 | * * | 3 | * * |
4 | * This program is free software; you can redistribute it and/or modify * | 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 * | 5 | * it under the terms of the GNU General Public License as published by * |
@@ -17,8 +17,16 @@ | |||
17 | * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * | 17 | * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * |
18 | ***************************************************************************/ | 18 | ***************************************************************************/ |
19 | 19 | ||
20 | #include <linux/mutex.h> | ||
21 | |||
22 | struct sch56xx_watchdog_data; | ||
23 | |||
20 | int sch56xx_read_virtual_reg(u16 addr, u16 reg); | 24 | int sch56xx_read_virtual_reg(u16 addr, u16 reg); |
21 | int sch56xx_write_virtual_reg(u16 addr, u16 reg, u8 val); | 25 | int sch56xx_write_virtual_reg(u16 addr, u16 reg, u8 val); |
22 | int sch56xx_read_virtual_reg16(u16 addr, u16 reg); | 26 | int sch56xx_read_virtual_reg16(u16 addr, u16 reg); |
23 | int sch56xx_read_virtual_reg12(u16 addr, u16 msb_reg, u16 lsn_reg, | 27 | int sch56xx_read_virtual_reg12(u16 addr, u16 msb_reg, u16 lsn_reg, |
24 | int high_nibble); | 28 | int high_nibble); |
29 | |||
30 | struct sch56xx_watchdog_data *sch56xx_watchdog_register( | ||
31 | u16 addr, u32 revision, struct mutex *io_lock, int check_enabled); | ||
32 | void sch56xx_watchdog_unregister(struct sch56xx_watchdog_data *data); | ||