diff options
Diffstat (limited to 'drivers/hwmon/fschmd.c')
-rw-r--r-- | drivers/hwmon/fschmd.c | 448 |
1 files changed, 405 insertions, 43 deletions
diff --git a/drivers/hwmon/fschmd.c b/drivers/hwmon/fschmd.c index 8b2d756595d9..d07f4ef75092 100644 --- a/drivers/hwmon/fschmd.c +++ b/drivers/hwmon/fschmd.c | |||
@@ -1,6 +1,6 @@ | |||
1 | /* fschmd.c | 1 | /* fschmd.c |
2 | * | 2 | * |
3 | * Copyright (C) 2007 Hans de Goede <j.w.r.degoede@hhs.nl> | 3 | * Copyright (C) 2007,2008 Hans de Goede <hdegoede@redhat.com> |
4 | * | 4 | * |
5 | * This program is free software; you can redistribute it and/or modify | 5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by | 6 | * it under the terms of the GNU General Public License as published by |
@@ -42,11 +42,20 @@ | |||
42 | #include <linux/mutex.h> | 42 | #include <linux/mutex.h> |
43 | #include <linux/sysfs.h> | 43 | #include <linux/sysfs.h> |
44 | #include <linux/dmi.h> | 44 | #include <linux/dmi.h> |
45 | #include <linux/fs.h> | ||
46 | #include <linux/watchdog.h> | ||
47 | #include <linux/miscdevice.h> | ||
48 | #include <linux/uaccess.h> | ||
49 | #include <linux/kref.h> | ||
45 | 50 | ||
46 | /* Addresses to scan */ | 51 | /* Addresses to scan */ |
47 | static const unsigned short normal_i2c[] = { 0x73, I2C_CLIENT_END }; | 52 | static const unsigned short normal_i2c[] = { 0x73, I2C_CLIENT_END }; |
48 | 53 | ||
49 | /* Insmod parameters */ | 54 | /* Insmod parameters */ |
55 | static int nowayout = WATCHDOG_NOWAYOUT; | ||
56 | module_param(nowayout, int, 0); | ||
57 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" | ||
58 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | ||
50 | I2C_CLIENT_INSMOD_5(fscpos, fscher, fscscy, fschrc, fschmd); | 59 | I2C_CLIENT_INSMOD_5(fscpos, fscher, fscscy, fschrc, fschmd); |
51 | 60 | ||
52 | /* | 61 | /* |
@@ -63,13 +72,20 @@ I2C_CLIENT_INSMOD_5(fscpos, fscher, fscscy, fschrc, fschmd); | |||
63 | #define FSCHMD_REG_EVENT_STATE 0x04 | 72 | #define FSCHMD_REG_EVENT_STATE 0x04 |
64 | #define FSCHMD_REG_CONTROL 0x05 | 73 | #define FSCHMD_REG_CONTROL 0x05 |
65 | 74 | ||
66 | #define FSCHMD_CONTROL_ALERT_LED_MASK 0x01 | 75 | #define FSCHMD_CONTROL_ALERT_LED 0x01 |
67 | 76 | ||
68 | /* watchdog (support to be implemented) */ | 77 | /* watchdog */ |
69 | #define FSCHMD_REG_WDOG_PRESET 0x28 | 78 | #define FSCHMD_REG_WDOG_PRESET 0x28 |
70 | #define FSCHMD_REG_WDOG_STATE 0x23 | 79 | #define FSCHMD_REG_WDOG_STATE 0x23 |
71 | #define FSCHMD_REG_WDOG_CONTROL 0x21 | 80 | #define FSCHMD_REG_WDOG_CONTROL 0x21 |
72 | 81 | ||
82 | #define FSCHMD_WDOG_CONTROL_TRIGGER 0x10 | ||
83 | #define FSCHMD_WDOG_CONTROL_STARTED 0x10 /* the same as trigger */ | ||
84 | #define FSCHMD_WDOG_CONTROL_STOP 0x20 | ||
85 | #define FSCHMD_WDOG_CONTROL_RESOLUTION 0x40 | ||
86 | |||
87 | #define FSCHMD_WDOG_STATE_CARDRESET 0x02 | ||
88 | |||
73 | /* voltages, weird order is to keep the same order as the old drivers */ | 89 | /* voltages, weird order is to keep the same order as the old drivers */ |
74 | static const u8 FSCHMD_REG_VOLT[3] = { 0x45, 0x42, 0x48 }; | 90 | static const u8 FSCHMD_REG_VOLT[3] = { 0x45, 0x42, 0x48 }; |
75 | 91 | ||
@@ -115,8 +131,8 @@ static const u8 FSCHMD_REG_FAN_RIPPLE[5][6] = { | |||
115 | static const int FSCHMD_NO_FAN_SENSORS[5] = { 3, 3, 6, 4, 5 }; | 131 | static const int FSCHMD_NO_FAN_SENSORS[5] = { 3, 3, 6, 4, 5 }; |
116 | 132 | ||
117 | /* Fan status register bitmasks */ | 133 | /* Fan status register bitmasks */ |
118 | #define FSCHMD_FAN_ALARM_MASK 0x04 /* called fault by FSC! */ | 134 | #define FSCHMD_FAN_ALARM 0x04 /* called fault by FSC! */ |
119 | #define FSCHMD_FAN_NOT_PRESENT_MASK 0x08 /* not documented */ | 135 | #define FSCHMD_FAN_NOT_PRESENT 0x08 /* not documented */ |
120 | 136 | ||
121 | 137 | ||
122 | /* actual temperature registers */ | 138 | /* actual temperature registers */ |
@@ -158,14 +174,11 @@ static const u8 FSCHER_REG_TEMP_AUTOP2[] = { 0x75, 0x85, 0x95 }; */ | |||
158 | static const int FSCHMD_NO_TEMP_SENSORS[5] = { 3, 3, 4, 3, 5 }; | 174 | static const int FSCHMD_NO_TEMP_SENSORS[5] = { 3, 3, 4, 3, 5 }; |
159 | 175 | ||
160 | /* temp status register bitmasks */ | 176 | /* temp status register bitmasks */ |
161 | #define FSCHMD_TEMP_WORKING_MASK 0x01 | 177 | #define FSCHMD_TEMP_WORKING 0x01 |
162 | #define FSCHMD_TEMP_ALERT_MASK 0x02 | 178 | #define FSCHMD_TEMP_ALERT 0x02 |
163 | /* there only really is an alarm if the sensor is working and alert == 1 */ | 179 | /* there only really is an alarm if the sensor is working and alert == 1 */ |
164 | #define FSCHMD_TEMP_ALARM_MASK \ | 180 | #define FSCHMD_TEMP_ALARM_MASK \ |
165 | (FSCHMD_TEMP_WORKING_MASK | FSCHMD_TEMP_ALERT_MASK) | 181 | (FSCHMD_TEMP_WORKING | FSCHMD_TEMP_ALERT) |
166 | |||
167 | /* our driver name */ | ||
168 | #define FSCHMD_NAME "fschmd" | ||
169 | 182 | ||
170 | /* | 183 | /* |
171 | * Functions declarations | 184 | * Functions declarations |
@@ -195,7 +208,7 @@ MODULE_DEVICE_TABLE(i2c, fschmd_id); | |||
195 | static struct i2c_driver fschmd_driver = { | 208 | static struct i2c_driver fschmd_driver = { |
196 | .class = I2C_CLASS_HWMON, | 209 | .class = I2C_CLASS_HWMON, |
197 | .driver = { | 210 | .driver = { |
198 | .name = FSCHMD_NAME, | 211 | .name = "fschmd", |
199 | }, | 212 | }, |
200 | .probe = fschmd_probe, | 213 | .probe = fschmd_probe, |
201 | .remove = fschmd_remove, | 214 | .remove = fschmd_remove, |
@@ -209,14 +222,26 @@ static struct i2c_driver fschmd_driver = { | |||
209 | */ | 222 | */ |
210 | 223 | ||
211 | struct fschmd_data { | 224 | struct fschmd_data { |
225 | struct i2c_client *client; | ||
212 | struct device *hwmon_dev; | 226 | struct device *hwmon_dev; |
213 | struct mutex update_lock; | 227 | struct mutex update_lock; |
228 | struct mutex watchdog_lock; | ||
229 | struct list_head list; /* member of the watchdog_data_list */ | ||
230 | struct kref kref; | ||
231 | struct miscdevice watchdog_miscdev; | ||
214 | int kind; | 232 | int kind; |
233 | unsigned long watchdog_is_open; | ||
234 | char watchdog_expect_close; | ||
235 | char watchdog_name[10]; /* must be unique to avoid sysfs conflict */ | ||
215 | char valid; /* zero until following fields are valid */ | 236 | char valid; /* zero until following fields are valid */ |
216 | unsigned long last_updated; /* in jiffies */ | 237 | unsigned long last_updated; /* in jiffies */ |
217 | 238 | ||
218 | /* register values */ | 239 | /* register values */ |
240 | u8 revision; /* chip revision */ | ||
219 | u8 global_control; /* global control register */ | 241 | u8 global_control; /* global control register */ |
242 | u8 watchdog_control; /* watchdog control register */ | ||
243 | u8 watchdog_state; /* watchdog status register */ | ||
244 | u8 watchdog_preset; /* watchdog counter preset on trigger val */ | ||
220 | u8 volt[3]; /* 12, 5, battery voltage */ | 245 | u8 volt[3]; /* 12, 5, battery voltage */ |
221 | u8 temp_act[5]; /* temperature */ | 246 | u8 temp_act[5]; /* temperature */ |
222 | u8 temp_status[5]; /* status of sensor */ | 247 | u8 temp_status[5]; /* status of sensor */ |
@@ -228,11 +253,28 @@ struct fschmd_data { | |||
228 | }; | 253 | }; |
229 | 254 | ||
230 | /* Global variables to hold information read from special DMI tables, which are | 255 | /* Global variables to hold information read from special DMI tables, which are |
231 | available on FSC machines with an fscher or later chip. */ | 256 | available on FSC machines with an fscher or later chip. There is no need to |
257 | protect these with a lock as they are only modified from our attach function | ||
258 | which always gets called with the i2c-core lock held and never accessed | ||
259 | before the attach function is done with them. */ | ||
232 | static int dmi_mult[3] = { 490, 200, 100 }; | 260 | static int dmi_mult[3] = { 490, 200, 100 }; |
233 | static int dmi_offset[3] = { 0, 0, 0 }; | 261 | static int dmi_offset[3] = { 0, 0, 0 }; |
234 | static int dmi_vref = -1; | 262 | static int dmi_vref = -1; |
235 | 263 | ||
264 | /* Somewhat ugly :( global data pointer list with all fschmd devices, so that | ||
265 | we can find our device data as when using misc_register there is no other | ||
266 | method to get to ones device data from the open fop. */ | ||
267 | static LIST_HEAD(watchdog_data_list); | ||
268 | /* Note this lock not only protect list access, but also data.kref access */ | ||
269 | static DEFINE_MUTEX(watchdog_data_mutex); | ||
270 | |||
271 | /* Release our data struct when we're detached from the i2c client *and* all | ||
272 | references to our watchdog device are released */ | ||
273 | static void fschmd_release_resources(struct kref *ref) | ||
274 | { | ||
275 | struct fschmd_data *data = container_of(ref, struct fschmd_data, kref); | ||
276 | kfree(data); | ||
277 | } | ||
236 | 278 | ||
237 | /* | 279 | /* |
238 | * Sysfs attr show / store functions | 280 | * Sysfs attr show / store functions |
@@ -300,7 +342,7 @@ static ssize_t show_temp_fault(struct device *dev, | |||
300 | struct fschmd_data *data = fschmd_update_device(dev); | 342 | struct fschmd_data *data = fschmd_update_device(dev); |
301 | 343 | ||
302 | /* bit 0 set means sensor working ok, so no fault! */ | 344 | /* bit 0 set means sensor working ok, so no fault! */ |
303 | if (data->temp_status[index] & FSCHMD_TEMP_WORKING_MASK) | 345 | if (data->temp_status[index] & FSCHMD_TEMP_WORKING) |
304 | return sprintf(buf, "0\n"); | 346 | return sprintf(buf, "0\n"); |
305 | else | 347 | else |
306 | return sprintf(buf, "1\n"); | 348 | return sprintf(buf, "1\n"); |
@@ -385,7 +427,7 @@ static ssize_t show_fan_alarm(struct device *dev, | |||
385 | int index = to_sensor_dev_attr(devattr)->index; | 427 | int index = to_sensor_dev_attr(devattr)->index; |
386 | struct fschmd_data *data = fschmd_update_device(dev); | 428 | struct fschmd_data *data = fschmd_update_device(dev); |
387 | 429 | ||
388 | if (data->fan_status[index] & FSCHMD_FAN_ALARM_MASK) | 430 | if (data->fan_status[index] & FSCHMD_FAN_ALARM) |
389 | return sprintf(buf, "1\n"); | 431 | return sprintf(buf, "1\n"); |
390 | else | 432 | else |
391 | return sprintf(buf, "0\n"); | 433 | return sprintf(buf, "0\n"); |
@@ -397,7 +439,7 @@ static ssize_t show_fan_fault(struct device *dev, | |||
397 | int index = to_sensor_dev_attr(devattr)->index; | 439 | int index = to_sensor_dev_attr(devattr)->index; |
398 | struct fschmd_data *data = fschmd_update_device(dev); | 440 | struct fschmd_data *data = fschmd_update_device(dev); |
399 | 441 | ||
400 | if (data->fan_status[index] & FSCHMD_FAN_NOT_PRESENT_MASK) | 442 | if (data->fan_status[index] & FSCHMD_FAN_NOT_PRESENT) |
401 | return sprintf(buf, "1\n"); | 443 | return sprintf(buf, "1\n"); |
402 | else | 444 | else |
403 | return sprintf(buf, "0\n"); | 445 | return sprintf(buf, "0\n"); |
@@ -449,7 +491,7 @@ static ssize_t show_alert_led(struct device *dev, | |||
449 | { | 491 | { |
450 | struct fschmd_data *data = fschmd_update_device(dev); | 492 | struct fschmd_data *data = fschmd_update_device(dev); |
451 | 493 | ||
452 | if (data->global_control & FSCHMD_CONTROL_ALERT_LED_MASK) | 494 | if (data->global_control & FSCHMD_CONTROL_ALERT_LED) |
453 | return sprintf(buf, "1\n"); | 495 | return sprintf(buf, "1\n"); |
454 | else | 496 | else |
455 | return sprintf(buf, "0\n"); | 497 | return sprintf(buf, "0\n"); |
@@ -467,9 +509,9 @@ static ssize_t store_alert_led(struct device *dev, | |||
467 | reg = i2c_smbus_read_byte_data(to_i2c_client(dev), FSCHMD_REG_CONTROL); | 509 | reg = i2c_smbus_read_byte_data(to_i2c_client(dev), FSCHMD_REG_CONTROL); |
468 | 510 | ||
469 | if (v) | 511 | if (v) |
470 | reg |= FSCHMD_CONTROL_ALERT_LED_MASK; | 512 | reg |= FSCHMD_CONTROL_ALERT_LED; |
471 | else | 513 | else |
472 | reg &= ~FSCHMD_CONTROL_ALERT_LED_MASK; | 514 | reg &= ~FSCHMD_CONTROL_ALERT_LED; |
473 | 515 | ||
474 | i2c_smbus_write_byte_data(to_i2c_client(dev), FSCHMD_REG_CONTROL, reg); | 516 | i2c_smbus_write_byte_data(to_i2c_client(dev), FSCHMD_REG_CONTROL, reg); |
475 | 517 | ||
@@ -551,7 +593,265 @@ static struct sensor_device_attribute fschmd_fan_attr[] = { | |||
551 | 593 | ||
552 | 594 | ||
553 | /* | 595 | /* |
554 | * Real code | 596 | * Watchdog routines |
597 | */ | ||
598 | |||
599 | static int watchdog_set_timeout(struct fschmd_data *data, int timeout) | ||
600 | { | ||
601 | int ret, resolution; | ||
602 | int kind = data->kind + 1; /* 0-x array index -> 1-x module param */ | ||
603 | |||
604 | /* 2 second or 60 second resolution? */ | ||
605 | if (timeout <= 510 || kind == fscpos || kind == fscscy) | ||
606 | resolution = 2; | ||
607 | else | ||
608 | resolution = 60; | ||
609 | |||
610 | if (timeout < resolution || timeout > (resolution * 255)) | ||
611 | return -EINVAL; | ||
612 | |||
613 | mutex_lock(&data->watchdog_lock); | ||
614 | if (!data->client) { | ||
615 | ret = -ENODEV; | ||
616 | goto leave; | ||
617 | } | ||
618 | |||
619 | if (resolution == 2) | ||
620 | data->watchdog_control &= ~FSCHMD_WDOG_CONTROL_RESOLUTION; | ||
621 | else | ||
622 | data->watchdog_control |= FSCHMD_WDOG_CONTROL_RESOLUTION; | ||
623 | |||
624 | data->watchdog_preset = DIV_ROUND_UP(timeout, resolution); | ||
625 | |||
626 | /* Write new timeout value */ | ||
627 | i2c_smbus_write_byte_data(data->client, FSCHMD_REG_WDOG_PRESET, | ||
628 | data->watchdog_preset); | ||
629 | /* Write new control register, do not trigger! */ | ||
630 | i2c_smbus_write_byte_data(data->client, FSCHMD_REG_WDOG_CONTROL, | ||
631 | data->watchdog_control & ~FSCHMD_WDOG_CONTROL_TRIGGER); | ||
632 | |||
633 | ret = data->watchdog_preset * resolution; | ||
634 | |||
635 | leave: | ||
636 | mutex_unlock(&data->watchdog_lock); | ||
637 | return ret; | ||
638 | } | ||
639 | |||
640 | static int watchdog_get_timeout(struct fschmd_data *data) | ||
641 | { | ||
642 | int timeout; | ||
643 | |||
644 | mutex_lock(&data->watchdog_lock); | ||
645 | if (data->watchdog_control & FSCHMD_WDOG_CONTROL_RESOLUTION) | ||
646 | timeout = data->watchdog_preset * 60; | ||
647 | else | ||
648 | timeout = data->watchdog_preset * 2; | ||
649 | mutex_unlock(&data->watchdog_lock); | ||
650 | |||
651 | return timeout; | ||
652 | } | ||
653 | |||
654 | static int watchdog_trigger(struct fschmd_data *data) | ||
655 | { | ||
656 | int ret = 0; | ||
657 | |||
658 | mutex_lock(&data->watchdog_lock); | ||
659 | if (!data->client) { | ||
660 | ret = -ENODEV; | ||
661 | goto leave; | ||
662 | } | ||
663 | |||
664 | data->watchdog_control |= FSCHMD_WDOG_CONTROL_TRIGGER; | ||
665 | i2c_smbus_write_byte_data(data->client, FSCHMD_REG_WDOG_CONTROL, | ||
666 | data->watchdog_control); | ||
667 | leave: | ||
668 | mutex_unlock(&data->watchdog_lock); | ||
669 | return ret; | ||
670 | } | ||
671 | |||
672 | static int watchdog_stop(struct fschmd_data *data) | ||
673 | { | ||
674 | int ret = 0; | ||
675 | |||
676 | mutex_lock(&data->watchdog_lock); | ||
677 | if (!data->client) { | ||
678 | ret = -ENODEV; | ||
679 | goto leave; | ||
680 | } | ||
681 | |||
682 | data->watchdog_control &= ~FSCHMD_WDOG_CONTROL_STARTED; | ||
683 | /* Don't store the stop flag in our watchdog control register copy, as | ||
684 | its a write only bit (read always returns 0) */ | ||
685 | i2c_smbus_write_byte_data(data->client, FSCHMD_REG_WDOG_CONTROL, | ||
686 | data->watchdog_control | FSCHMD_WDOG_CONTROL_STOP); | ||
687 | leave: | ||
688 | mutex_unlock(&data->watchdog_lock); | ||
689 | return ret; | ||
690 | } | ||
691 | |||
692 | static int watchdog_open(struct inode *inode, struct file *filp) | ||
693 | { | ||
694 | struct fschmd_data *pos, *data = NULL; | ||
695 | |||
696 | /* We get called from drivers/char/misc.c with misc_mtx hold, and we | ||
697 | call misc_register() from fschmd_probe() with watchdog_data_mutex | ||
698 | hold, as misc_register() takes the misc_mtx lock, this is a possible | ||
699 | deadlock, so we use mutex_trylock here. */ | ||
700 | if (!mutex_trylock(&watchdog_data_mutex)) | ||
701 | return -ERESTARTSYS; | ||
702 | list_for_each_entry(pos, &watchdog_data_list, list) { | ||
703 | if (pos->watchdog_miscdev.minor == iminor(inode)) { | ||
704 | data = pos; | ||
705 | break; | ||
706 | } | ||
707 | } | ||
708 | /* Note we can never not have found data, so we don't check for this */ | ||
709 | kref_get(&data->kref); | ||
710 | mutex_unlock(&watchdog_data_mutex); | ||
711 | |||
712 | if (test_and_set_bit(0, &data->watchdog_is_open)) | ||
713 | return -EBUSY; | ||
714 | |||
715 | /* Start the watchdog */ | ||
716 | watchdog_trigger(data); | ||
717 | filp->private_data = data; | ||
718 | |||
719 | return nonseekable_open(inode, filp); | ||
720 | } | ||
721 | |||
722 | static int watchdog_release(struct inode *inode, struct file *filp) | ||
723 | { | ||
724 | struct fschmd_data *data = filp->private_data; | ||
725 | |||
726 | if (data->watchdog_expect_close) { | ||
727 | watchdog_stop(data); | ||
728 | data->watchdog_expect_close = 0; | ||
729 | } else { | ||
730 | watchdog_trigger(data); | ||
731 | dev_crit(&data->client->dev, | ||
732 | "unexpected close, not stopping watchdog!\n"); | ||
733 | } | ||
734 | |||
735 | clear_bit(0, &data->watchdog_is_open); | ||
736 | |||
737 | mutex_lock(&watchdog_data_mutex); | ||
738 | kref_put(&data->kref, fschmd_release_resources); | ||
739 | mutex_unlock(&watchdog_data_mutex); | ||
740 | |||
741 | return 0; | ||
742 | } | ||
743 | |||
744 | static ssize_t watchdog_write(struct file *filp, const char __user *buf, | ||
745 | size_t count, loff_t *offset) | ||
746 | { | ||
747 | size_t ret; | ||
748 | struct fschmd_data *data = filp->private_data; | ||
749 | |||
750 | if (count) { | ||
751 | if (!nowayout) { | ||
752 | size_t i; | ||
753 | |||
754 | /* Clear it in case it was set with a previous write */ | ||
755 | data->watchdog_expect_close = 0; | ||
756 | |||
757 | for (i = 0; i != count; i++) { | ||
758 | char c; | ||
759 | if (get_user(c, buf + i)) | ||
760 | return -EFAULT; | ||
761 | if (c == 'V') | ||
762 | data->watchdog_expect_close = 1; | ||
763 | } | ||
764 | } | ||
765 | ret = watchdog_trigger(data); | ||
766 | if (ret < 0) | ||
767 | return ret; | ||
768 | } | ||
769 | return count; | ||
770 | } | ||
771 | |||
772 | static int watchdog_ioctl(struct inode *inode, struct file *filp, | ||
773 | unsigned int cmd, unsigned long arg) | ||
774 | { | ||
775 | static struct watchdog_info ident = { | ||
776 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | | ||
777 | WDIOF_CARDRESET, | ||
778 | .identity = "FSC watchdog" | ||
779 | }; | ||
780 | int i, ret = 0; | ||
781 | struct fschmd_data *data = filp->private_data; | ||
782 | |||
783 | switch (cmd) { | ||
784 | case WDIOC_GETSUPPORT: | ||
785 | ident.firmware_version = data->revision; | ||
786 | if (!nowayout) | ||
787 | ident.options |= WDIOF_MAGICCLOSE; | ||
788 | if (copy_to_user((void __user *)arg, &ident, sizeof(ident))) | ||
789 | ret = -EFAULT; | ||
790 | break; | ||
791 | |||
792 | case WDIOC_GETSTATUS: | ||
793 | ret = put_user(0, (int __user *)arg); | ||
794 | break; | ||
795 | |||
796 | case WDIOC_GETBOOTSTATUS: | ||
797 | if (data->watchdog_state & FSCHMD_WDOG_STATE_CARDRESET) | ||
798 | ret = put_user(WDIOF_CARDRESET, (int __user *)arg); | ||
799 | else | ||
800 | ret = put_user(0, (int __user *)arg); | ||
801 | break; | ||
802 | |||
803 | case WDIOC_KEEPALIVE: | ||
804 | ret = watchdog_trigger(data); | ||
805 | break; | ||
806 | |||
807 | case WDIOC_GETTIMEOUT: | ||
808 | i = watchdog_get_timeout(data); | ||
809 | ret = put_user(i, (int __user *)arg); | ||
810 | break; | ||
811 | |||
812 | case WDIOC_SETTIMEOUT: | ||
813 | if (get_user(i, (int __user *)arg)) { | ||
814 | ret = -EFAULT; | ||
815 | break; | ||
816 | } | ||
817 | ret = watchdog_set_timeout(data, i); | ||
818 | if (ret > 0) | ||
819 | ret = put_user(ret, (int __user *)arg); | ||
820 | break; | ||
821 | |||
822 | case WDIOC_SETOPTIONS: | ||
823 | if (get_user(i, (int __user *)arg)) { | ||
824 | ret = -EFAULT; | ||
825 | break; | ||
826 | } | ||
827 | |||
828 | if (i & WDIOS_DISABLECARD) | ||
829 | ret = watchdog_stop(data); | ||
830 | else if (i & WDIOS_ENABLECARD) | ||
831 | ret = watchdog_trigger(data); | ||
832 | else | ||
833 | ret = -EINVAL; | ||
834 | |||
835 | break; | ||
836 | default: | ||
837 | ret = -ENOTTY; | ||
838 | } | ||
839 | |||
840 | return ret; | ||
841 | } | ||
842 | |||
843 | static struct file_operations watchdog_fops = { | ||
844 | .owner = THIS_MODULE, | ||
845 | .llseek = no_llseek, | ||
846 | .open = watchdog_open, | ||
847 | .release = watchdog_release, | ||
848 | .write = watchdog_write, | ||
849 | .ioctl = watchdog_ioctl, | ||
850 | }; | ||
851 | |||
852 | |||
853 | /* | ||
854 | * Detect, register, unregister and update device functions | ||
555 | */ | 855 | */ |
556 | 856 | ||
557 | /* DMI decode routine to read voltage scaling factors from special DMI tables, | 857 | /* DMI decode routine to read voltage scaling factors from special DMI tables, |
@@ -661,9 +961,9 @@ static int fschmd_probe(struct i2c_client *client, | |||
661 | const struct i2c_device_id *id) | 961 | const struct i2c_device_id *id) |
662 | { | 962 | { |
663 | struct fschmd_data *data; | 963 | struct fschmd_data *data; |
664 | u8 revision; | ||
665 | const char * const names[5] = { "Poseidon", "Hermes", "Scylla", | 964 | const char * const names[5] = { "Poseidon", "Hermes", "Scylla", |
666 | "Heracles", "Heimdall" }; | 965 | "Heracles", "Heimdall" }; |
966 | const int watchdog_minors[] = { WATCHDOG_MINOR, 212, 213, 214, 215 }; | ||
667 | int i, err; | 967 | int i, err; |
668 | enum chips kind = id->driver_data; | 968 | enum chips kind = id->driver_data; |
669 | 969 | ||
@@ -673,6 +973,13 @@ static int fschmd_probe(struct i2c_client *client, | |||
673 | 973 | ||
674 | i2c_set_clientdata(client, data); | 974 | i2c_set_clientdata(client, data); |
675 | mutex_init(&data->update_lock); | 975 | mutex_init(&data->update_lock); |
976 | mutex_init(&data->watchdog_lock); | ||
977 | INIT_LIST_HEAD(&data->list); | ||
978 | kref_init(&data->kref); | ||
979 | /* Store client pointer in our data struct for watchdog usage | ||
980 | (where the client is found through a data ptr instead of the | ||
981 | otherway around) */ | ||
982 | data->client = client; | ||
676 | 983 | ||
677 | if (kind == fscpos) { | 984 | if (kind == fscpos) { |
678 | /* The Poseidon has hardwired temp limits, fill these | 985 | /* The Poseidon has hardwired temp limits, fill these |
@@ -683,16 +990,27 @@ static int fschmd_probe(struct i2c_client *client, | |||
683 | } | 990 | } |
684 | 991 | ||
685 | /* Read the special DMI table for fscher and newer chips */ | 992 | /* Read the special DMI table for fscher and newer chips */ |
686 | if (kind == fscher || kind >= fschrc) { | 993 | if ((kind == fscher || kind >= fschrc) && dmi_vref == -1) { |
687 | dmi_walk(fschmd_dmi_decode); | 994 | dmi_walk(fschmd_dmi_decode); |
688 | if (dmi_vref == -1) { | 995 | if (dmi_vref == -1) { |
689 | printk(KERN_WARNING FSCHMD_NAME | 996 | dev_warn(&client->dev, |
690 | ": Couldn't get voltage scaling factors from " | 997 | "Couldn't get voltage scaling factors from " |
691 | "BIOS DMI table, using builtin defaults\n"); | 998 | "BIOS DMI table, using builtin defaults\n"); |
692 | dmi_vref = 33; | 999 | dmi_vref = 33; |
693 | } | 1000 | } |
694 | } | 1001 | } |
695 | 1002 | ||
1003 | /* Read in some never changing registers */ | ||
1004 | data->revision = i2c_smbus_read_byte_data(client, FSCHMD_REG_REVISION); | ||
1005 | data->global_control = i2c_smbus_read_byte_data(client, | ||
1006 | FSCHMD_REG_CONTROL); | ||
1007 | data->watchdog_control = i2c_smbus_read_byte_data(client, | ||
1008 | FSCHMD_REG_WDOG_CONTROL); | ||
1009 | data->watchdog_state = i2c_smbus_read_byte_data(client, | ||
1010 | FSCHMD_REG_WDOG_STATE); | ||
1011 | data->watchdog_preset = i2c_smbus_read_byte_data(client, | ||
1012 | FSCHMD_REG_WDOG_PRESET); | ||
1013 | |||
696 | /* i2c kind goes from 1-5, we want from 0-4 to address arrays */ | 1014 | /* i2c kind goes from 1-5, we want from 0-4 to address arrays */ |
697 | data->kind = kind - 1; | 1015 | data->kind = kind - 1; |
698 | 1016 | ||
@@ -735,9 +1053,43 @@ static int fschmd_probe(struct i2c_client *client, | |||
735 | goto exit_detach; | 1053 | goto exit_detach; |
736 | } | 1054 | } |
737 | 1055 | ||
738 | revision = i2c_smbus_read_byte_data(client, FSCHMD_REG_REVISION); | 1056 | /* We take the data_mutex lock early so that watchdog_open() cannot |
739 | printk(KERN_INFO FSCHMD_NAME ": Detected FSC %s chip, revision: %d\n", | 1057 | run when misc_register() has completed, but we've not yet added |
740 | names[data->kind], (int) revision); | 1058 | our data to the watchdog_data_list (and set the default timeout) */ |
1059 | mutex_lock(&watchdog_data_mutex); | ||
1060 | for (i = 0; i < ARRAY_SIZE(watchdog_minors); i++) { | ||
1061 | /* Register our watchdog part */ | ||
1062 | snprintf(data->watchdog_name, sizeof(data->watchdog_name), | ||
1063 | "watchdog%c", (i == 0) ? '\0' : ('0' + i)); | ||
1064 | data->watchdog_miscdev.name = data->watchdog_name; | ||
1065 | data->watchdog_miscdev.fops = &watchdog_fops; | ||
1066 | data->watchdog_miscdev.minor = watchdog_minors[i]; | ||
1067 | err = misc_register(&data->watchdog_miscdev); | ||
1068 | if (err == -EBUSY) | ||
1069 | continue; | ||
1070 | if (err) { | ||
1071 | data->watchdog_miscdev.minor = 0; | ||
1072 | dev_err(&client->dev, | ||
1073 | "Registering watchdog chardev: %d\n", err); | ||
1074 | break; | ||
1075 | } | ||
1076 | |||
1077 | list_add(&data->list, &watchdog_data_list); | ||
1078 | watchdog_set_timeout(data, 60); | ||
1079 | dev_info(&client->dev, | ||
1080 | "Registered watchdog chardev major 10, minor: %d\n", | ||
1081 | watchdog_minors[i]); | ||
1082 | break; | ||
1083 | } | ||
1084 | if (i == ARRAY_SIZE(watchdog_minors)) { | ||
1085 | data->watchdog_miscdev.minor = 0; | ||
1086 | dev_warn(&client->dev, "Couldn't register watchdog chardev " | ||
1087 | "(due to no free minor)\n"); | ||
1088 | } | ||
1089 | mutex_unlock(&watchdog_data_mutex); | ||
1090 | |||
1091 | dev_info(&client->dev, "Detected FSC %s chip, revision: %d\n", | ||
1092 | names[data->kind], (int) data->revision); | ||
741 | 1093 | ||
742 | return 0; | 1094 | return 0; |
743 | 1095 | ||
@@ -751,6 +1103,24 @@ static int fschmd_remove(struct i2c_client *client) | |||
751 | struct fschmd_data *data = i2c_get_clientdata(client); | 1103 | struct fschmd_data *data = i2c_get_clientdata(client); |
752 | int i; | 1104 | int i; |
753 | 1105 | ||
1106 | /* Unregister the watchdog (if registered) */ | ||
1107 | if (data->watchdog_miscdev.minor) { | ||
1108 | misc_deregister(&data->watchdog_miscdev); | ||
1109 | if (data->watchdog_is_open) { | ||
1110 | dev_warn(&client->dev, | ||
1111 | "i2c client detached with watchdog open! " | ||
1112 | "Stopping watchdog.\n"); | ||
1113 | watchdog_stop(data); | ||
1114 | } | ||
1115 | mutex_lock(&watchdog_data_mutex); | ||
1116 | list_del(&data->list); | ||
1117 | mutex_unlock(&watchdog_data_mutex); | ||
1118 | /* Tell the watchdog code the client is gone */ | ||
1119 | mutex_lock(&data->watchdog_lock); | ||
1120 | data->client = NULL; | ||
1121 | mutex_unlock(&data->watchdog_lock); | ||
1122 | } | ||
1123 | |||
754 | /* Check if registered in case we're called from fschmd_detect | 1124 | /* Check if registered in case we're called from fschmd_detect |
755 | to cleanup after an error */ | 1125 | to cleanup after an error */ |
756 | if (data->hwmon_dev) | 1126 | if (data->hwmon_dev) |
@@ -765,7 +1135,10 @@ static int fschmd_remove(struct i2c_client *client) | |||
765 | device_remove_file(&client->dev, | 1135 | device_remove_file(&client->dev, |
766 | &fschmd_fan_attr[i].dev_attr); | 1136 | &fschmd_fan_attr[i].dev_attr); |
767 | 1137 | ||
768 | kfree(data); | 1138 | mutex_lock(&watchdog_data_mutex); |
1139 | kref_put(&data->kref, fschmd_release_resources); | ||
1140 | mutex_unlock(&watchdog_data_mutex); | ||
1141 | |||
769 | return 0; | 1142 | return 0; |
770 | } | 1143 | } |
771 | 1144 | ||
@@ -798,7 +1171,7 @@ static struct fschmd_data *fschmd_update_device(struct device *dev) | |||
798 | data->temp_act[i] < data->temp_max[i]) | 1171 | data->temp_act[i] < data->temp_max[i]) |
799 | i2c_smbus_write_byte_data(client, | 1172 | i2c_smbus_write_byte_data(client, |
800 | FSCHMD_REG_TEMP_STATE[data->kind][i], | 1173 | FSCHMD_REG_TEMP_STATE[data->kind][i], |
801 | FSCHMD_TEMP_ALERT_MASK); | 1174 | FSCHMD_TEMP_ALERT); |
802 | } | 1175 | } |
803 | 1176 | ||
804 | for (i = 0; i < FSCHMD_NO_FAN_SENSORS[data->kind]; i++) { | 1177 | for (i = 0; i < FSCHMD_NO_FAN_SENSORS[data->kind]; i++) { |
@@ -816,28 +1189,17 @@ static struct fschmd_data *fschmd_update_device(struct device *dev) | |||
816 | FSCHMD_REG_FAN_MIN[data->kind][i]); | 1189 | FSCHMD_REG_FAN_MIN[data->kind][i]); |
817 | 1190 | ||
818 | /* reset fan status if speed is back to > 0 */ | 1191 | /* reset fan status if speed is back to > 0 */ |
819 | if ((data->fan_status[i] & FSCHMD_FAN_ALARM_MASK) && | 1192 | if ((data->fan_status[i] & FSCHMD_FAN_ALARM) && |
820 | data->fan_act[i]) | 1193 | data->fan_act[i]) |
821 | i2c_smbus_write_byte_data(client, | 1194 | i2c_smbus_write_byte_data(client, |
822 | FSCHMD_REG_FAN_STATE[data->kind][i], | 1195 | FSCHMD_REG_FAN_STATE[data->kind][i], |
823 | FSCHMD_FAN_ALARM_MASK); | 1196 | FSCHMD_FAN_ALARM); |
824 | } | 1197 | } |
825 | 1198 | ||
826 | for (i = 0; i < 3; i++) | 1199 | for (i = 0; i < 3; i++) |
827 | data->volt[i] = i2c_smbus_read_byte_data(client, | 1200 | data->volt[i] = i2c_smbus_read_byte_data(client, |
828 | FSCHMD_REG_VOLT[i]); | 1201 | FSCHMD_REG_VOLT[i]); |
829 | 1202 | ||
830 | data->global_control = i2c_smbus_read_byte_data(client, | ||
831 | FSCHMD_REG_CONTROL); | ||
832 | |||
833 | /* To be implemented in the future | ||
834 | data->watchdog[0] = i2c_smbus_read_byte_data(client, | ||
835 | FSCHMD_REG_WDOG_PRESET); | ||
836 | data->watchdog[1] = i2c_smbus_read_byte_data(client, | ||
837 | FSCHMD_REG_WDOG_STATE); | ||
838 | data->watchdog[2] = i2c_smbus_read_byte_data(client, | ||
839 | FSCHMD_REG_WDOG_CONTROL); */ | ||
840 | |||
841 | data->last_updated = jiffies; | 1203 | data->last_updated = jiffies; |
842 | data->valid = 1; | 1204 | data->valid = 1; |
843 | } | 1205 | } |
@@ -857,7 +1219,7 @@ static void __exit fschmd_exit(void) | |||
857 | i2c_del_driver(&fschmd_driver); | 1219 | i2c_del_driver(&fschmd_driver); |
858 | } | 1220 | } |
859 | 1221 | ||
860 | MODULE_AUTHOR("Hans de Goede <j.w.r.degoede@hhs.nl>"); | 1222 | MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); |
861 | MODULE_DESCRIPTION("FSC Poseidon, Hermes, Scylla, Heracles and " | 1223 | MODULE_DESCRIPTION("FSC Poseidon, Hermes, Scylla, Heracles and " |
862 | "Heimdall driver"); | 1224 | "Heimdall driver"); |
863 | MODULE_LICENSE("GPL"); | 1225 | MODULE_LICENSE("GPL"); |