From 6771ea1fff988651593f78c122bc02e80f5100a0 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Fri, 5 Mar 2010 22:17:13 +0100 Subject: hwmon: (lm90) Add support for the Winbond/Nuvoton W83L771AWG/ASG This chips is found on several Zotac Ion ITX boards, amongst others. Signed-off-by: Jean Delvare Cc: MC Matti Cc: Manuel Lamotte-Schubert --- Documentation/hwmon/lm90 | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'Documentation') diff --git a/Documentation/hwmon/lm90 b/Documentation/hwmon/lm90 index 93d8e3d5515..08106ad7089 100644 --- a/Documentation/hwmon/lm90 +++ b/Documentation/hwmon/lm90 @@ -84,6 +84,10 @@ Supported chips: Addresses scanned: I2C 0x4c Datasheet: Publicly available at the Maxim website http://www.maxim-ic.com/quick_view2.cfm/qv_pk/3500 + * Winbond/Nuvoton W83L771AWG/ASG + Prefix: 'w83l771' + Addresses scanned: I2C 0x4c + Datasheet: Not publicly available, can be requested from Nuvoton Author: Jean Delvare @@ -147,6 +151,12 @@ MAX6680 and MAX6681: * Selectable address * Remote sensor type selection +W83L771AWG/ASG + * The AWG and ASG variants only differ in package format. + * Filter and alert configuration register at 0xBF + * Diode ideality factor configuration (remote sensor) at 0xE3 + * Moving average (depending on conversion rate) + All temperature values are given in degrees Celsius. Resolution is 1.0 degree for the local temperature, 0.125 degree for the remote temperature, except for the MAX6657, MAX6658 and MAX6659 which have a -- cgit v1.2.3-70-g09d2 From 53de33427fa3d7dd62cc5ec75ce0d4e6c6d602dd Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Fri, 5 Mar 2010 22:17:15 +0100 Subject: hwmon: (lm90) Add SMBus alert support Tested successfully with an ADM1032 chip on its evaluation board. It should work fine with all other chips as well. At this point this is more of a proof-of-concept, we don't do anything terribly useful on SMBus alert: we simply log the event. But this could later evolve into libsensors signaling so that user-space applications can take an appropriate action. Signed-off-by: Jean Delvare Cc: David Brownell Cc: Trent Piepho --- Documentation/hwmon/lm90 | 12 +++++++++ drivers/hwmon/lm90.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) (limited to 'Documentation') diff --git a/Documentation/hwmon/lm90 b/Documentation/hwmon/lm90 index 08106ad7089..6a03dd4bcc9 100644 --- a/Documentation/hwmon/lm90 +++ b/Documentation/hwmon/lm90 @@ -173,6 +173,18 @@ The lm90 driver will not update its values more frequently than every other second; reading them more often will do no harm, but will return 'old' values. +SMBus Alert Support +------------------- + +This driver has basic support for SMBus alert. When an alert is received, +the status register is read and the faulty temperature channel is logged. + +The Analog Devices chips (ADM1032 and ADT7461) do not implement the SMBus +alert protocol properly so additional care is needed: the ALERT output is +disabled when an alert is received, and is re-enabled only when the alarm +is gone. Otherwise the chip would block alerts from other chips in the bus +as long as the alarm is active. + PEC Support ----------- diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c index 4cbbf1563de..7cc2708871a 100644 --- a/drivers/hwmon/lm90.c +++ b/drivers/hwmon/lm90.c @@ -152,6 +152,7 @@ static int lm90_detect(struct i2c_client *client, struct i2c_board_info *info); static int lm90_probe(struct i2c_client *client, const struct i2c_device_id *id); static void lm90_init_client(struct i2c_client *client); +static void lm90_alert(struct i2c_client *client, unsigned int flag); static int lm90_remove(struct i2c_client *client); static struct lm90_data *lm90_update_device(struct device *dev); @@ -186,6 +187,7 @@ static struct i2c_driver lm90_driver = { }, .probe = lm90_probe, .remove = lm90_remove, + .alert = lm90_alert, .id_table = lm90_id, .detect = lm90_detect, .address_list = normal_i2c, @@ -204,6 +206,7 @@ struct lm90_data { int flags; u8 config_orig; /* Original configuration register value */ + u8 alert_alarms; /* Which alarm bits trigger ALERT# */ /* registers values */ s8 temp8[4]; /* 0: local low limit @@ -806,6 +809,19 @@ static int lm90_probe(struct i2c_client *new_client, new_client->flags &= ~I2C_CLIENT_PEC; } + /* Different devices have different alarm bits triggering the + * ALERT# output */ + switch (data->kind) { + case lm90: + case lm99: + case lm86: + data->alert_alarms = 0x7b; + break; + default: + data->alert_alarms = 0x7c; + break; + } + /* Initialize the LM90 chip */ lm90_init_client(new_client); @@ -895,6 +911,38 @@ static int lm90_remove(struct i2c_client *client) return 0; } +static void lm90_alert(struct i2c_client *client, unsigned int flag) +{ + struct lm90_data *data = i2c_get_clientdata(client); + u8 config, alarms; + + lm90_read_reg(client, LM90_REG_R_STATUS, &alarms); + if ((alarms & 0x7f) == 0) { + dev_info(&client->dev, "Everything OK\n"); + } else { + if (alarms & 0x61) + dev_warn(&client->dev, + "temp%d out of range, please check!\n", 1); + if (alarms & 0x1a) + dev_warn(&client->dev, + "temp%d out of range, please check!\n", 2); + if (alarms & 0x04) + dev_warn(&client->dev, + "temp%d diode open, please check!\n", 2); + + /* Disable ALERT# output, because these chips don't implement + SMBus alert correctly; they should only hold the alert line + low briefly. */ + if ((data->kind == adm1032 || data->kind == adt7461) + && (alarms & data->alert_alarms)) { + dev_dbg(&client->dev, "Disabling ALERT#\n"); + lm90_read_reg(client, LM90_REG_R_CONFIG1, &config); + i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1, + config | 0x80); + } + } +} + static int lm90_read16(struct i2c_client *client, u8 regh, u8 regl, u16 *value) { int err; @@ -982,6 +1030,21 @@ static struct lm90_data *lm90_update_device(struct device *dev) } lm90_read_reg(client, LM90_REG_R_STATUS, &data->alarms); + /* Re-enable ALERT# output if it was originally enabled and + * relevant alarms are all clear */ + if ((data->config_orig & 0x80) == 0 + && (data->alarms & data->alert_alarms) == 0) { + u8 config; + + lm90_read_reg(client, LM90_REG_R_CONFIG1, &config); + if (config & 0x80) { + dev_dbg(&client->dev, "Re-enabling ALERT#\n"); + i2c_smbus_write_byte_data(client, + LM90_REG_W_CONFIG1, + config & ~0x80); + } + } + data->last_updated = jiffies; data->valid = 1; } -- cgit v1.2.3-70-g09d2 From d9b327c310030fa80abbbc9eaaca9f1a6228dbf3 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Fri, 5 Mar 2010 22:17:17 +0100 Subject: hwmon: (it87) Add support for beep on alarm The IT87xxF chips support beeping on alarm, if properly wired and configured. There is one control bit for each input type (temperature, fan, voltage.) Let the user see and change them. Signed-off-by: Jean Delvare --- Documentation/hwmon/it87 | 4 ++ drivers/hwmon/it87.c | 122 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) (limited to 'Documentation') diff --git a/Documentation/hwmon/it87 b/Documentation/hwmon/it87 index f9ba96c0ac4..23b773fa267 100644 --- a/Documentation/hwmon/it87 +++ b/Documentation/hwmon/it87 @@ -136,6 +136,10 @@ registers are read whenever any data is read (unless it is less than 1.5 seconds since the last update). This means that you can easily miss once-only alarms. +Out-of-limit readings can also result in beeping, if the chip is properly +wired and configured. Beeping can be enabled or disabled per sensor type +(temperatures, voltages and fans.) + The IT87xx only updates its values each 1.5 seconds; reading it more often will do no harm, but will return 'old' values. diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c index 7a349e066fd..d70c6ec3172 100644 --- a/drivers/hwmon/it87.c +++ b/drivers/hwmon/it87.c @@ -128,6 +128,7 @@ superio_exit(void) #define IT87_SIO_GPIO5_REG 0x29 #define IT87_SIO_PINX2_REG 0x2c /* Pin selection */ #define IT87_SIO_VID_REG 0xfc /* VID value */ +#define IT87_SIO_BEEP_PIN_REG 0xf6 /* Beep pin mapping */ /* Update battery voltage after every reading if true */ static int update_vbat; @@ -187,6 +188,7 @@ static const u8 IT87_REG_FANX_MIN[] = { 0x1b, 0x1c, 0x1d, 0x85, 0x87 }; #define IT87_REG_VIN_ENABLE 0x50 #define IT87_REG_TEMP_ENABLE 0x51 +#define IT87_REG_BEEP_ENABLE 0x5c #define IT87_REG_CHIPID 0x58 @@ -246,6 +248,7 @@ struct it87_sio_data { /* Values read from Super-I/O config space */ u8 revision; u8 vid_value; + u8 beep_pin; /* Features skipped based on config or DMI */ u8 skip_vid; u8 skip_fan; @@ -279,6 +282,7 @@ struct it87_data { u8 vid; /* Register encoding, combined */ u8 vrm; u32 alarms; /* Register encoding, combined */ + u8 beeps; /* Register encoding */ u8 fan_main_ctrl; /* Register value */ u8 fan_ctl; /* Register value */ @@ -919,6 +923,55 @@ static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 16); static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 17); static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 18); +static ssize_t show_beep(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int bitnr = to_sensor_dev_attr(attr)->index; + struct it87_data *data = it87_update_device(dev); + return sprintf(buf, "%u\n", (data->beeps >> bitnr) & 1); +} +static ssize_t set_beep(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int bitnr = to_sensor_dev_attr(attr)->index; + struct it87_data *data = dev_get_drvdata(dev); + long val; + + if (strict_strtol(buf, 10, &val) < 0 + || (val != 0 && val != 1)) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->beeps = it87_read_value(data, IT87_REG_BEEP_ENABLE); + if (val) + data->beeps |= (1 << bitnr); + else + data->beeps &= ~(1 << bitnr); + it87_write_value(data, IT87_REG_BEEP_ENABLE, data->beeps); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(in0_beep, S_IRUGO | S_IWUSR, + show_beep, set_beep, 1); +static SENSOR_DEVICE_ATTR(in1_beep, S_IRUGO, show_beep, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_beep, S_IRUGO, show_beep, NULL, 1); +static SENSOR_DEVICE_ATTR(in3_beep, S_IRUGO, show_beep, NULL, 1); +static SENSOR_DEVICE_ATTR(in4_beep, S_IRUGO, show_beep, NULL, 1); +static SENSOR_DEVICE_ATTR(in5_beep, S_IRUGO, show_beep, NULL, 1); +static SENSOR_DEVICE_ATTR(in6_beep, S_IRUGO, show_beep, NULL, 1); +static SENSOR_DEVICE_ATTR(in7_beep, S_IRUGO, show_beep, NULL, 1); +/* fanX_beep writability is set later */ +static SENSOR_DEVICE_ATTR(fan1_beep, S_IRUGO, show_beep, set_beep, 0); +static SENSOR_DEVICE_ATTR(fan2_beep, S_IRUGO, show_beep, set_beep, 0); +static SENSOR_DEVICE_ATTR(fan3_beep, S_IRUGO, show_beep, set_beep, 0); +static SENSOR_DEVICE_ATTR(fan4_beep, S_IRUGO, show_beep, set_beep, 0); +static SENSOR_DEVICE_ATTR(fan5_beep, S_IRUGO, show_beep, set_beep, 0); +static SENSOR_DEVICE_ATTR(temp1_beep, S_IRUGO | S_IWUSR, + show_beep, set_beep, 2); +static SENSOR_DEVICE_ATTR(temp2_beep, S_IRUGO, show_beep, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_beep, S_IRUGO, show_beep, NULL, 2); + static ssize_t show_vrm_reg(struct device *dev, struct device_attribute *attr, char *buf) { @@ -1014,6 +1067,26 @@ static const struct attribute_group it87_group = { .attrs = it87_attributes, }; +static struct attribute *it87_attributes_beep[] = { + &sensor_dev_attr_in0_beep.dev_attr.attr, + &sensor_dev_attr_in1_beep.dev_attr.attr, + &sensor_dev_attr_in2_beep.dev_attr.attr, + &sensor_dev_attr_in3_beep.dev_attr.attr, + &sensor_dev_attr_in4_beep.dev_attr.attr, + &sensor_dev_attr_in5_beep.dev_attr.attr, + &sensor_dev_attr_in6_beep.dev_attr.attr, + &sensor_dev_attr_in7_beep.dev_attr.attr, + + &sensor_dev_attr_temp1_beep.dev_attr.attr, + &sensor_dev_attr_temp2_beep.dev_attr.attr, + &sensor_dev_attr_temp3_beep.dev_attr.attr, + NULL +}; + +static const struct attribute_group it87_group_beep = { + .attrs = it87_attributes_beep, +}; + static struct attribute *it87_attributes_fan16[5][3+1] = { { &sensor_dev_attr_fan1_input16.dev_attr.attr, &sensor_dev_attr_fan1_min16.dev_attr.attr, @@ -1107,6 +1180,14 @@ static const struct attribute_group it87_group_pwm[3] = { { .attrs = it87_attributes_pwm[2] }, }; +static struct attribute *it87_attributes_fan_beep[] = { + &sensor_dev_attr_fan1_beep.dev_attr.attr, + &sensor_dev_attr_fan2_beep.dev_attr.attr, + &sensor_dev_attr_fan3_beep.dev_attr.attr, + &sensor_dev_attr_fan4_beep.dev_attr.attr, + &sensor_dev_attr_fan5_beep.dev_attr.attr, +}; + static struct attribute *it87_attributes_vid[] = { &dev_attr_vrm.attr, &dev_attr_cpu0_vid.attr, @@ -1174,6 +1255,10 @@ static int __init it87_find(unsigned short *address, if (sio_data->type == it87) { /* The IT8705F doesn't have VID pins at all */ sio_data->skip_vid = 1; + + /* The IT8705F has a different LD number for GPIO */ + superio_select(5); + sio_data->beep_pin = superio_inb(IT87_SIO_BEEP_PIN_REG) & 0x3f; } else { int reg; @@ -1207,7 +1292,11 @@ static int __init it87_find(unsigned short *address, pr_info("it87: in3 is VCC (+5V)\n"); if (reg & (1 << 1)) pr_info("it87: in7 is VCCH (+5V Stand-By)\n"); + + sio_data->beep_pin = superio_inb(IT87_SIO_BEEP_PIN_REG) & 0x3f; } + if (sio_data->beep_pin) + pr_info("it87: Beeping is supported\n"); /* Disable specific features based on DMI strings */ board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); @@ -1240,10 +1329,15 @@ static void it87_remove_files(struct device *dev) int i; sysfs_remove_group(&dev->kobj, &it87_group); + if (sio_data->beep_pin) + sysfs_remove_group(&dev->kobj, &it87_group_beep); for (i = 0; i < 5; i++) { if (!(data->has_fan & (1 << i))) continue; sysfs_remove_group(&dev->kobj, &fan_group[i]); + if (sio_data->beep_pin) + sysfs_remove_file(&dev->kobj, + it87_attributes_fan_beep[i]); } for (i = 0; i < 3; i++) { if (sio_data->skip_pwm & (1 << 0)) @@ -1263,6 +1357,7 @@ static int __devinit it87_probe(struct platform_device *pdev) const struct attribute_group *fan_group; int err = 0, i; int enable_pwm_interface; + int fan_beep_need_rw; static const char *names[] = { "it87", "it8712", @@ -1311,14 +1406,40 @@ static int __devinit it87_probe(struct platform_device *pdev) if ((err = sysfs_create_group(&dev->kobj, &it87_group))) goto ERROR2; + if (sio_data->beep_pin) { + err = sysfs_create_group(&dev->kobj, &it87_group_beep); + if (err) + goto ERROR4; + } + /* Do not create fan files for disabled fans */ fan_group = it87_get_fan_group(data); + fan_beep_need_rw = 1; for (i = 0; i < 5; i++) { if (!(data->has_fan & (1 << i))) continue; err = sysfs_create_group(&dev->kobj, &fan_group[i]); if (err) goto ERROR4; + + if (sio_data->beep_pin) { + err = sysfs_create_file(&dev->kobj, + it87_attributes_fan_beep[i]); + if (err) + goto ERROR4; + if (!fan_beep_need_rw) + continue; + + /* As we have a single beep enable bit for all fans, + * only the first enabled fan has a writable attribute + * for it. */ + if (sysfs_chmod_file(&dev->kobj, + it87_attributes_fan_beep[i], + S_IRUGO | S_IWUSR)) + dev_dbg(dev, "chmod +w fan%d_beep failed\n", + i + 1); + fan_beep_need_rw = 0; + } } if (enable_pwm_interface) { @@ -1608,6 +1729,7 @@ static struct it87_data *it87_update_device(struct device *dev) it87_read_value(data, IT87_REG_ALARM1) | (it87_read_value(data, IT87_REG_ALARM2) << 8) | (it87_read_value(data, IT87_REG_ALARM3) << 16); + data->beeps = it87_read_value(data, IT87_REG_BEEP_ENABLE); data->fan_main_ctrl = it87_read_value(data, IT87_REG_FAN_MAIN_CTRL); -- cgit v1.2.3-70-g09d2 From 404a552d8ae6847ae17f3c749bd5d312da08efe4 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Fri, 5 Mar 2010 22:17:21 +0100 Subject: hwmon: (it87) Drop dead web links in documentation Unfortunately ITE is no longer publicly providing datasheets for their IT87xxF series of chips. They may send them on request if you ask politely. Signed-off-by: Jean Delvare --- Documentation/hwmon/it87 | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/it87 b/Documentation/hwmon/it87 index 23b773fa267..a96a70fe802 100644 --- a/Documentation/hwmon/it87 +++ b/Documentation/hwmon/it87 @@ -5,31 +5,23 @@ Supported chips: * IT8705F Prefix: 'it87' Addresses scanned: from Super I/O config space (8 I/O ports) - Datasheet: Publicly available at the ITE website - http://www.ite.com.tw/product_info/file/pc/IT8705F_V.0.4.1.pdf + Datasheet: Once publicly available at the ITE website, but no longer * IT8712F Prefix: 'it8712' Addresses scanned: from Super I/O config space (8 I/O ports) - Datasheet: Publicly available at the ITE website - http://www.ite.com.tw/product_info/file/pc/IT8712F_V0.9.1.pdf - http://www.ite.com.tw/product_info/file/pc/Errata%20V0.1%20for%20IT8712F%20V0.9.1.pdf - http://www.ite.com.tw/product_info/file/pc/IT8712F_V0.9.3.pdf + Datasheet: Once publicly available at the ITE website, but no longer * IT8716F/IT8726F Prefix: 'it8716' Addresses scanned: from Super I/O config space (8 I/O ports) - Datasheet: Publicly available at the ITE website - http://www.ite.com.tw/product_info/file/pc/IT8716F_V0.3.ZIP - http://www.ite.com.tw/product_info/file/pc/IT8726F_V0.3.pdf + Datasheet: Once publicly available at the ITE website, but no longer * IT8718F Prefix: 'it8718' Addresses scanned: from Super I/O config space (8 I/O ports) - Datasheet: Publicly available at the ITE website - http://www.ite.com.tw/product_info/file/pc/IT8718F_V0.2.zip - http://www.ite.com.tw/product_info/file/pc/IT8718F_V0%203_(for%20C%20version).zip + Datasheet: Once publicly available at the ITE website, but no longer * IT8720F Prefix: 'it8720' Addresses scanned: from Super I/O config space (8 I/O ports) - Datasheet: Not yet publicly available. + Datasheet: Not publicly available * SiS950 [clone of IT8705F] Prefix: 'it87' Addresses scanned: from Super I/O config space (8 I/O ports) -- cgit v1.2.3-70-g09d2 From 4f3f51bc21d434f8d91a79438a1957ec0baa9e30 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Fri, 5 Mar 2010 22:17:21 +0100 Subject: hwmon: (it87) Add support for old automatic fan speed control Add support for the automatic fan speed control interface as implemented by IT8705F chips up to revision F and IT8712F chips up to revision G. This implementation fits very well in our standard sysfs interface. I implemented the old and not the new interface because the only chip I have at hand is an old one, and the new interface is more difficult to map to the standard sysfs interface. Adding support later should be possible though, if someone with a supported chip is interested. Signed-off-by: Jean Delvare --- Documentation/hwmon/it87 | 27 ++++++- drivers/hwmon/it87.c | 182 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 205 insertions(+), 4 deletions(-) (limited to 'Documentation') diff --git a/Documentation/hwmon/it87 b/Documentation/hwmon/it87 index a96a70fe802..0083bd31735 100644 --- a/Documentation/hwmon/it87 +++ b/Documentation/hwmon/it87 @@ -146,11 +146,34 @@ Fan speed control ----------------- The fan speed control features are limited to manual PWM mode. Automatic -"Smart Guardian" mode control handling is not implemented. However -if you want to go for "manual mode" just write 1 to pwmN_enable. +"Smart Guardian" mode control handling is only implemented for older chips +(see below.) However if you want to go for "manual mode" just write 1 to +pwmN_enable. If you are only able to control the fan speed with very small PWM values, try lowering the PWM base frequency (pwm1_freq). Depending on the fan, it may give you a somewhat greater control range. The same frequency is used to drive all fan outputs, which is why pwm2_freq and pwm3_freq are read-only. + + +Automatic fan speed control (old interface) +------------------------------------------- + +The driver supports the old interface to automatic fan speed control +which is implemented by IT8705F chips up to revision F and IT8712F +chips up to revision G. + +This interface implements 4 temperature vs. PWM output trip points. +The PWM output of trip point 4 is always the maximum value (fan running +at full speed) while the PWM output of the other 3 trip points can be +freely chosen. The temperature of all 4 trip points can be freely chosen. +Additionally, trip point 1 has an hysteresis temperature attached, to +prevent fast switching between fan on and off. + +The chip automatically computes the PWM output value based on the input +temperature, based on this simple rule: if the temperature value is +between trip point N and trip point N+1 then the PWM output value is +the one of trip point N. The automatic control mode is less flexible +than the manual control mode, but it reacts faster, is more robust and +doesn't use CPU cycles. diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c index 8282282eb4c..bbb0c7443b9 100644 --- a/drivers/hwmon/it87.c +++ b/drivers/hwmon/it87.c @@ -192,6 +192,9 @@ static const u8 IT87_REG_FANX_MIN[] = { 0x1b, 0x1c, 0x1d, 0x85, 0x87 }; #define IT87_REG_CHIPID 0x58 +#define IT87_REG_AUTO_TEMP(nr, i) (0x60 + (nr) * 8 + (i)) +#define IT87_REG_AUTO_PWM(nr, i) (0x65 + (nr) * 8 + (i)) + #define IN_TO_REG(val) (SENSORS_LIMIT((((val) + 8)/16),0,255)) #define IN_FROM_REG(val) ((val) * 16) @@ -293,6 +296,10 @@ struct it87_data { u8 pwm_ctrl[3]; /* Register value */ u8 pwm_duty[3]; /* Manual PWM value set by user (bit 6-0) */ u8 pwm_temp_map[3]; /* PWM to temp. chan. mapping (bits 1-0) */ + + /* Automatic fan speed control registers */ + u8 auto_pwm[3][4]; /* [nr][3] is hard-coded */ + s8 auto_temp[3][5]; /* [nr][0] is point1_temp_hyst */ }; static inline int has_16bit_fans(const struct it87_data *data) @@ -307,6 +314,15 @@ static inline int has_16bit_fans(const struct it87_data *data) || data->type == it8720; } +static inline int has_old_autopwm(const struct it87_data *data) +{ + /* The old automatic fan speed control interface is implemented + by IT8705F chips up to revision F and IT8712F chips up to + revision G. */ + return (data->type == it87 && data->revision < 0x03) + || (data->type == it8712 && data->revision < 0x08); +} + static int it87_probe(struct platform_device *pdev); static int __devexit it87_remove(struct platform_device *pdev); @@ -813,6 +829,13 @@ static ssize_t set_pwm_temp_map(struct device *dev, long val; u8 reg; + /* This check can go away if we ever support automatic fan speed + control on newer chips. */ + if (!has_old_autopwm(data)) { + dev_notice(dev, "Mapping change disabled for safety reasons\n"); + return -EINVAL; + } + if (strict_strtol(buf, 10, &val) < 0) return -EINVAL; @@ -842,6 +865,72 @@ static ssize_t set_pwm_temp_map(struct device *dev, return count; } +static ssize_t show_auto_pwm(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct it87_data *data = it87_update_device(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int point = sensor_attr->index; + + return sprintf(buf, "%d\n", PWM_FROM_REG(data->auto_pwm[nr][point])); +} + +static ssize_t set_auto_pwm(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct it87_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int point = sensor_attr->index; + long val; + + if (strict_strtol(buf, 10, &val) < 0 || val < 0 || val > 255) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->auto_pwm[nr][point] = PWM_TO_REG(val); + it87_write_value(data, IT87_REG_AUTO_PWM(nr, point), + data->auto_pwm[nr][point]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_auto_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct it87_data *data = it87_update_device(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int point = sensor_attr->index; + + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->auto_temp[nr][point])); +} + +static ssize_t set_auto_temp(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct it87_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int point = sensor_attr->index; + long val; + + if (strict_strtol(buf, 10, &val) < 0 || val < -128000 || val > 127000) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->auto_temp[nr][point] = TEMP_TO_REG(val); + it87_write_value(data, IT87_REG_AUTO_TEMP(nr, point), + data->auto_temp[nr][point]); + mutex_unlock(&data->update_lock); + return count; +} + #define show_fan_offset(offset) \ static SENSOR_DEVICE_ATTR(fan##offset##_input, S_IRUGO, \ show_fan, NULL, offset - 1); \ @@ -863,8 +952,34 @@ static DEVICE_ATTR(pwm##offset##_freq, \ (offset == 1 ? S_IRUGO | S_IWUSR : S_IRUGO), \ show_pwm_freq, (offset == 1 ? set_pwm_freq : NULL)); \ static SENSOR_DEVICE_ATTR(pwm##offset##_auto_channels_temp, \ - S_IRUGO, show_pwm_temp_map, set_pwm_temp_map, \ - offset - 1); + S_IRUGO | S_IWUSR, show_pwm_temp_map, set_pwm_temp_map, \ + offset - 1); \ +static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point1_pwm, \ + S_IRUGO | S_IWUSR, show_auto_pwm, set_auto_pwm, \ + offset - 1, 0); \ +static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point2_pwm, \ + S_IRUGO | S_IWUSR, show_auto_pwm, set_auto_pwm, \ + offset - 1, 1); \ +static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point3_pwm, \ + S_IRUGO | S_IWUSR, show_auto_pwm, set_auto_pwm, \ + offset - 1, 2); \ +static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point4_pwm, \ + S_IRUGO, show_auto_pwm, NULL, offset - 1, 3); \ +static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point1_temp, \ + S_IRUGO | S_IWUSR, show_auto_temp, set_auto_temp, \ + offset - 1, 1); \ +static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point1_temp_hyst, \ + S_IRUGO | S_IWUSR, show_auto_temp, set_auto_temp, \ + offset - 1, 0); \ +static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point2_temp, \ + S_IRUGO | S_IWUSR, show_auto_temp, set_auto_temp, \ + offset - 1, 2); \ +static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point3_temp, \ + S_IRUGO | S_IWUSR, show_auto_temp, set_auto_temp, \ + offset - 1, 3); \ +static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point4_temp, \ + S_IRUGO | S_IWUSR, show_auto_temp, set_auto_temp, \ + offset - 1, 4); show_pwm_offset(1); show_pwm_offset(2); @@ -1219,6 +1334,47 @@ static const struct attribute_group it87_group_pwm[3] = { { .attrs = it87_attributes_pwm[2] }, }; +static struct attribute *it87_attributes_autopwm[3][9+1] = { { + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr, + NULL +}, { + &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point4_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point1_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point4_temp.dev_attr.attr, + NULL +}, { + &sensor_dev_attr_pwm3_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point4_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point1_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point4_temp.dev_attr.attr, + NULL +} }; + +static const struct attribute_group it87_group_autopwm[3] = { + { .attrs = it87_attributes_autopwm[0] }, + { .attrs = it87_attributes_autopwm[1] }, + { .attrs = it87_attributes_autopwm[2] }, +}; + static struct attribute *it87_attributes_fan_beep[] = { &sensor_dev_attr_fan1_beep.dev_attr.attr, &sensor_dev_attr_fan2_beep.dev_attr.attr, @@ -1382,6 +1538,9 @@ static void it87_remove_files(struct device *dev) if (sio_data->skip_pwm & (1 << 0)) continue; sysfs_remove_group(&dev->kobj, &it87_group_pwm[i]); + if (has_old_autopwm(data)) + sysfs_remove_group(&dev->kobj, + &it87_group_autopwm[i]); } if (!sio_data->skip_vid) sysfs_remove_group(&dev->kobj, &it87_group_vid); @@ -1491,6 +1650,13 @@ static int __devinit it87_probe(struct platform_device *pdev) &it87_group_pwm[i]); if (err) goto ERROR4; + + if (!has_old_autopwm(data)) + continue; + err = sysfs_create_group(&dev->kobj, + &it87_group_autopwm[i]); + if (err) + goto ERROR4; } } @@ -1624,6 +1790,7 @@ static void __devinit it87_init_device(struct platform_device *pdev) for (i = 0; i < 3; i++) { data->pwm_temp_map[i] = i; data->pwm_duty[i] = 0x7f; /* Full speed */ + data->auto_pwm[i][3] = 0x7f; /* Full speed, hard-coded */ } /* Some chips seem to have default value 0xff for all limit @@ -1703,6 +1870,17 @@ static void it87_update_pwm_ctrl(struct it87_data *data, int nr) data->pwm_temp_map[nr] = data->pwm_ctrl[nr] & 0x03; else /* Manual mode */ data->pwm_duty[nr] = data->pwm_ctrl[nr] & 0x7f; + + if (has_old_autopwm(data)) { + int i; + + for (i = 0; i < 5 ; i++) + data->auto_temp[nr][i] = it87_read_value(data, + IT87_REG_AUTO_TEMP(nr, i)); + for (i = 0; i < 3 ; i++) + data->auto_pwm[nr][i] = it87_read_value(data, + IT87_REG_AUTO_PWM(nr, i)); + } } static struct it87_data *it87_update_device(struct device *dev) -- cgit v1.2.3-70-g09d2 From cccfc9c4bb3c7888f8249b36e08d6e115238c613 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Fri, 5 Mar 2010 22:17:21 +0100 Subject: hwmon: (it87) Validate auto pwm settings Before switching to automatic fan control mode, make sure that all the trip points make sense. Otherwise, the control loop could lead to weird fan behavior. Signed-off-by: Jean Delvare --- Documentation/hwmon/it87 | 4 ++++ drivers/hwmon/it87.c | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) (limited to 'Documentation') diff --git a/Documentation/hwmon/it87 b/Documentation/hwmon/it87 index 0083bd31735..8d08bf0d38e 100644 --- a/Documentation/hwmon/it87 +++ b/Documentation/hwmon/it87 @@ -177,3 +177,7 @@ between trip point N and trip point N+1 then the PWM output value is the one of trip point N. The automatic control mode is less flexible than the manual control mode, but it reacts faster, is more robust and doesn't use CPU cycles. + +Trip points must be set properly before switching to automatic fan speed +control mode. The driver will perform basic integrity checks before +actually switching to automatic control mode. diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c index bbb0c7443b9..1002befd87d 100644 --- a/drivers/hwmon/it87.c +++ b/drivers/hwmon/it87.c @@ -719,6 +719,32 @@ static ssize_t set_fan_div(struct device *dev, struct device_attribute *attr, mutex_unlock(&data->update_lock); return count; } + +/* Returns 0 if OK, -EINVAL otherwise */ +static int check_trip_points(struct device *dev, int nr) +{ + const struct it87_data *data = dev_get_drvdata(dev); + int i, err = 0; + + if (has_old_autopwm(data)) { + for (i = 0; i < 3; i++) { + if (data->auto_temp[nr][i] > data->auto_temp[nr][i + 1]) + err = -EINVAL; + } + for (i = 0; i < 2; i++) { + if (data->auto_pwm[nr][i] > data->auto_pwm[nr][i + 1]) + err = -EINVAL; + } + } + + if (err) { + dev_err(dev, "Inconsistent trip points, not switching to " + "automatic mode\n"); + dev_err(dev, "Adjust the trip points and try again\n"); + } + return err; +} + static ssize_t set_pwm_enable(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { @@ -731,6 +757,12 @@ static ssize_t set_pwm_enable(struct device *dev, if (strict_strtol(buf, 10, &val) < 0 || val < 0 || val > 2) return -EINVAL; + /* Check trip points before switching to automatic mode */ + if (val == 2) { + if (check_trip_points(dev, nr) < 0) + return -EINVAL; + } + mutex_lock(&data->update_lock); if (val == 0) { -- cgit v1.2.3-70-g09d2 From d84ca5b345c2b77ebf053d534ada6af2332a43b6 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Fri, 5 Mar 2010 22:17:23 +0100 Subject: hwmon: Add driver for ADT7411 voltage and temperature sensor Add basic support for the ADT7411. Reads out all conversion results (via I2C, SPI yet missing) and allows some on-the-fly configuration. Tested with a custom board. Signed-off-by: Sascha Hauer Signed-off-by: Wolfram Sang Signed-off-by: Jean Delvare --- Documentation/hwmon/adt7411 | 42 ++++++ drivers/hwmon/Kconfig | 10 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/adt7411.c | 359 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 412 insertions(+) create mode 100644 Documentation/hwmon/adt7411 create mode 100644 drivers/hwmon/adt7411.c (limited to 'Documentation') diff --git a/Documentation/hwmon/adt7411 b/Documentation/hwmon/adt7411 new file mode 100644 index 00000000000..1632960f974 --- /dev/null +++ b/Documentation/hwmon/adt7411 @@ -0,0 +1,42 @@ +Kernel driver adt7411 +===================== + +Supported chips: + * Analog Devices ADT7411 + Prefix: 'adt7411' + Addresses scanned: 0x48, 0x4a, 0x4b + Datasheet: Publicly available at the Analog Devices website + +Author: Wolfram Sang (based on adt7470 by Darrick J. Wong) + +Description +----------- + +This driver implements support for the Analog Devices ADT7411 chip. There may +be other chips that implement this interface. + +The ADT7411 can use an I2C/SMBus compatible 2-wire interface or an +SPI-compatible 4-wire interface. It provides a 10-bit analog to digital +converter which measures 1 temperature, vdd and 8 input voltages. It has an +internal temperature sensor, but an external one can also be connected (one +loses 2 inputs then). There are high- and low-limit registers for all inputs. + +Check the datasheet for details. + +sysfs-Interface +--------------- + +in0_input - vdd voltage input +in[1-8]_input - analog 1-8 input +temp1_input - temperature input + +Besides standard interfaces, this driver adds (0 = off, 1 = on): + + adc_ref_vdd - Use vdd as reference instead of 2.25 V + fast_sampling - Sample at 22.5 kHz instead of 1.4 kHz, but drop filters + no_average - Turn off averaging over 16 samples + +Notes +----- + +SPI, external temperature sensor and limit registers are not supported yet. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index cdfb0637f24..77d032fb813 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -170,6 +170,16 @@ config SENSORS_ADM9240 This driver can also be built as a module. If so, the module will be called adm9240. +config SENSORS_ADT7411 + tristate "Analog Devices ADT7411" + depends on I2C && EXPERIMENTAL + help + If you say yes here you get support for the Analog Devices + ADT7411 voltage and temperature monitoring chip. + + This driver can also be built as a module. If so, the module + will be called adt7411. + config SENSORS_ADT7462 tristate "Analog Devices ADT7462" depends on I2C && EXPERIMENTAL diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 4bc215c0953..5fe67bf961b 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_SENSORS_ADM1029) += adm1029.o obj-$(CONFIG_SENSORS_ADM1031) += adm1031.o obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o obj-$(CONFIG_SENSORS_ADS7828) += ads7828.o +obj-$(CONFIG_SENSORS_ADT7411) += adt7411.o obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o obj-$(CONFIG_SENSORS_ADT7473) += adt7473.o diff --git a/drivers/hwmon/adt7411.c b/drivers/hwmon/adt7411.c new file mode 100644 index 00000000000..9e9246fe224 --- /dev/null +++ b/drivers/hwmon/adt7411.c @@ -0,0 +1,359 @@ +/* + * Driver for the ADT7411 (I2C/SPI 8 channel 10 bit ADC & temperature-sensor) + * + * Copyright (C) 2008, 2010 Pengutronix + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * TODO: SPI, support for external temperature sensor + * use power-down mode for suspend?, interrupt handling? + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ADT7411_REG_INT_TEMP_VDD_LSB 0x03 +#define ADT7411_REG_EXT_TEMP_AIN14_LSB 0x04 +#define ADT7411_REG_VDD_MSB 0x06 +#define ADT7411_REG_INT_TEMP_MSB 0x07 +#define ADT7411_REG_EXT_TEMP_AIN1_MSB 0x08 + +#define ADT7411_REG_CFG1 0x18 +#define ADT7411_CFG1_START_MONITOR (1 << 0) + +#define ADT7411_REG_CFG2 0x19 +#define ADT7411_CFG2_DISABLE_AVG (1 << 5) + +#define ADT7411_REG_CFG3 0x1a +#define ADT7411_CFG3_ADC_CLK_225 (1 << 0) +#define ADT7411_CFG3_REF_VDD (1 << 4) + +#define ADT7411_REG_DEVICE_ID 0x4d +#define ADT7411_REG_MANUFACTURER_ID 0x4e + +#define ADT7411_DEVICE_ID 0x2 +#define ADT7411_MANUFACTURER_ID 0x41 + +static const unsigned short normal_i2c[] = { 0x48, 0x4a, 0x4b, I2C_CLIENT_END }; + +struct adt7411_data { + struct mutex device_lock; /* for "atomic" device accesses */ + unsigned long next_update; + int vref_cached; + bool ref_is_vdd; + struct device *hwmon_dev; +}; + +/* + * When reading a register containing (up to 4) lsb, all associated + * msb-registers get locked by the hardware. After _one_ of those msb is read, + * _all_ are unlocked. In order to use this locking correctly, reading lsb/msb + * is protected here with a mutex, too. + */ +static int adt7411_read_10_bit(struct i2c_client *client, u8 lsb_reg, + u8 msb_reg, u8 lsb_shift) +{ + struct adt7411_data *data = i2c_get_clientdata(client); + int val, tmp; + + mutex_lock(&data->device_lock); + + val = i2c_smbus_read_byte_data(client, lsb_reg); + if (val < 0) + goto exit_unlock; + + tmp = (val >> lsb_shift) & 3; + val = i2c_smbus_read_byte_data(client, msb_reg); + + if (val >= 0) + val = (val << 2) | tmp; + + exit_unlock: + mutex_unlock(&data->device_lock); + + return val; +} + +static int adt7411_modify_bit(struct i2c_client *client, u8 reg, u8 bit, + bool flag) +{ + struct adt7411_data *data = i2c_get_clientdata(client); + int ret, val; + + mutex_lock(&data->device_lock); + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) + goto exit_unlock; + + if (flag) + val = ret | bit; + else + val = ret & ~bit; + + ret = i2c_smbus_write_byte_data(client, reg, val); + + exit_unlock: + mutex_unlock(&data->device_lock); + return ret; +} + +static ssize_t adt7411_show_vdd(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + int ret = adt7411_read_10_bit(client, ADT7411_REG_INT_TEMP_VDD_LSB, + ADT7411_REG_VDD_MSB, 2); + + return ret < 0 ? ret : sprintf(buf, "%u\n", ret * 7000 / 1024); +} + +static ssize_t adt7411_show_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + int val = adt7411_read_10_bit(client, ADT7411_REG_INT_TEMP_VDD_LSB, + ADT7411_REG_INT_TEMP_MSB, 0); + + if (val < 0) + return val; + + val = val & 0x200 ? val - 0x400 : val; /* 10 bit signed */ + + return sprintf(buf, "%d\n", val * 250); +} + +static ssize_t adt7411_show_input(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct i2c_client *client = to_i2c_client(dev); + struct adt7411_data *data = i2c_get_clientdata(client); + int val; + u8 lsb_reg, lsb_shift; + + if (time_after_eq(jiffies, data->next_update)) { + val = i2c_smbus_read_byte_data(client, ADT7411_REG_CFG3); + if (val < 0) + return val; + data->ref_is_vdd = val & ADT7411_CFG3_REF_VDD; + + if (data->ref_is_vdd) { + val = adt7411_read_10_bit(client, + ADT7411_REG_INT_TEMP_VDD_LSB, + ADT7411_REG_VDD_MSB, 2); + if (val < 0) + return val; + + data->vref_cached = val * 7000 / 1024; + } else { + data->vref_cached = 2250; + } + + data->next_update = jiffies + HZ; + } + + lsb_reg = ADT7411_REG_EXT_TEMP_AIN14_LSB + (nr >> 2); + lsb_shift = 2 * (nr & 0x03); + val = adt7411_read_10_bit(client, lsb_reg, + ADT7411_REG_EXT_TEMP_AIN1_MSB + nr, lsb_shift); + + return val < 0 ? val : + sprintf(buf, "%u\n", val * data->vref_cached / 1024); +} + +static ssize_t adt7411_show_bit(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *attr2 = to_sensor_dev_attr_2(attr); + struct i2c_client *client = to_i2c_client(dev); + int ret = i2c_smbus_read_byte_data(client, attr2->index); + + return ret < 0 ? ret : sprintf(buf, "%u\n", !!(ret & attr2->nr)); +} + +static ssize_t adt7411_set_bit(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct sensor_device_attribute_2 *s_attr2 = to_sensor_dev_attr_2(attr); + struct i2c_client *client = to_i2c_client(dev); + struct adt7411_data *data = i2c_get_clientdata(client); + int ret; + unsigned long flag; + + ret = strict_strtoul(buf, 0, &flag); + if (ret || flag > 1) + return -EINVAL; + + ret = adt7411_modify_bit(client, s_attr2->index, s_attr2->nr, flag); + + /* force update */ + data->next_update = jiffies; + + return ret < 0 ? ret : count; +} + +#define ADT7411_BIT_ATTR(__name, __reg, __bit) \ + SENSOR_DEVICE_ATTR_2(__name, S_IRUGO | S_IWUSR, adt7411_show_bit, \ + adt7411_set_bit, __bit, __reg) + +static DEVICE_ATTR(temp1_input, S_IRUGO, adt7411_show_temp, NULL); +static DEVICE_ATTR(in0_input, S_IRUGO, adt7411_show_vdd, NULL); +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, adt7411_show_input, NULL, 0); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, adt7411_show_input, NULL, 1); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, adt7411_show_input, NULL, 2); +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, adt7411_show_input, NULL, 3); +static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, adt7411_show_input, NULL, 4); +static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, adt7411_show_input, NULL, 5); +static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, adt7411_show_input, NULL, 6); +static SENSOR_DEVICE_ATTR(in8_input, S_IRUGO, adt7411_show_input, NULL, 7); +static ADT7411_BIT_ATTR(no_average, ADT7411_REG_CFG2, ADT7411_CFG2_DISABLE_AVG); +static ADT7411_BIT_ATTR(fast_sampling, ADT7411_REG_CFG3, ADT7411_CFG3_ADC_CLK_225); +static ADT7411_BIT_ATTR(adc_ref_vdd, ADT7411_REG_CFG3, ADT7411_CFG3_REF_VDD); + +static struct attribute *adt7411_attrs[] = { + &dev_attr_temp1_input.attr, + &dev_attr_in0_input.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in8_input.dev_attr.attr, + &sensor_dev_attr_no_average.dev_attr.attr, + &sensor_dev_attr_fast_sampling.dev_attr.attr, + &sensor_dev_attr_adc_ref_vdd.dev_attr.attr, + NULL +}; + +static const struct attribute_group adt7411_attr_grp = { + .attrs = adt7411_attrs, +}; + +static int adt7411_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + int val; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + val = i2c_smbus_read_byte_data(client, ADT7411_REG_MANUFACTURER_ID); + if (val < 0 || val != ADT7411_MANUFACTURER_ID) { + dev_dbg(&client->dev, "Wrong manufacturer ID. Got %d, " + "expected %d\n", val, ADT7411_MANUFACTURER_ID); + return -ENODEV; + } + + val = i2c_smbus_read_byte_data(client, ADT7411_REG_DEVICE_ID); + if (val < 0 || val != ADT7411_DEVICE_ID) { + dev_dbg(&client->dev, "Wrong device ID. Got %d, " + "expected %d\n", val, ADT7411_DEVICE_ID); + return -ENODEV; + } + + strlcpy(info->type, "adt7411", I2C_NAME_SIZE); + + return 0; +} + +static int __devinit adt7411_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adt7411_data *data; + int ret; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->device_lock); + + ret = adt7411_modify_bit(client, ADT7411_REG_CFG1, + ADT7411_CFG1_START_MONITOR, 1); + if (ret < 0) + goto exit_free; + + /* force update on first occasion */ + data->next_update = jiffies; + + ret = sysfs_create_group(&client->dev.kobj, &adt7411_attr_grp); + if (ret) + goto exit_free; + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + goto exit_remove; + } + + dev_info(&client->dev, "successfully registered\n"); + + return 0; + + exit_remove: + sysfs_remove_group(&client->dev.kobj, &adt7411_attr_grp); + exit_free: + i2c_set_clientdata(client, NULL); + kfree(data); + return ret; +} + +static int __devexit adt7411_remove(struct i2c_client *client) +{ + struct adt7411_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &adt7411_attr_grp); + i2c_set_clientdata(client, NULL); + kfree(data); + return 0; +} + +static const struct i2c_device_id adt7411_id[] = { + { "adt7411", 0 }, + { } +}; + +static struct i2c_driver adt7411_driver = { + .driver = { + .name = "adt7411", + }, + .probe = adt7411_probe, + .remove = __devexit_p(adt7411_remove), + .id_table = adt7411_id, + .detect = adt7411_detect, + .address_list = normal_i2c, + .class = I2C_CLASS_HWMON, +}; + +static int __init sensors_adt7411_init(void) +{ + return i2c_add_driver(&adt7411_driver); +} +module_init(sensors_adt7411_init) + +static void __exit sensors_adt7411_exit(void) +{ + i2c_del_driver(&adt7411_driver); +} +module_exit(sensors_adt7411_exit) + +MODULE_AUTHOR("Sascha Hauer and " + "Wolfram Sang "); +MODULE_DESCRIPTION("ADT7411 driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-70-g09d2 From d58de038728221f780e11d50b32aa40d420c1150 Mon Sep 17 00:00:00 2001 From: George Joseph Date: Fri, 5 Mar 2010 22:17:25 +0100 Subject: hwmon: Driver for Andigilog aSC7621 family monitoring chips Hwmon driver for Andigilog aSC7621 family monitoring chips. Signed-off-by: George Joseph Acked-by: Hans de Goede Signed-off-by: Jean Delvare --- Documentation/hwmon/asc7621 | 296 ++++++++++ MAINTAINERS | 7 + drivers/hwmon/Kconfig | 13 + drivers/hwmon/Makefile | 1 + drivers/hwmon/asc7621.c | 1255 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1572 insertions(+) create mode 100644 Documentation/hwmon/asc7621 create mode 100644 drivers/hwmon/asc7621.c (limited to 'Documentation') diff --git a/Documentation/hwmon/asc7621 b/Documentation/hwmon/asc7621 new file mode 100644 index 00000000000..7287be7e1f2 --- /dev/null +++ b/Documentation/hwmon/asc7621 @@ -0,0 +1,296 @@ +Kernel driver asc7621 +================== + +Supported chips: + Andigilog aSC7621 and aSC7621a + Prefix: 'asc7621' + Addresses scanned: I2C 0x2c, 0x2d, 0x2e + Datasheet: http://www.fairview5.com/linux/asc7621/asc7621.pdf + +Author: + George Joseph + +Description provided by Dave Pivin @ Andigilog: + +Andigilog has both the PECI and pre-PECI versions of the Heceta-6, as +Intel calls them. Heceta-6e has high frequency PWM and Heceta-6p has +added PECI and a 4th thermal zone. The Andigilog aSC7611 is the +Heceta-6e part and aSC7621 is the Heceta-6p part. They are both in +volume production, shipping to Intel and their subs. + +We have enhanced both parts relative to the governing Intel +specification. First enhancement is temperature reading resolution. We +have used registers below 20h for vendor-specific functions in addition +to those in the Intel-specified vendor range. + +Our conversion process produces a result that is reported as two bytes. +The fan speed control uses this finer value to produce a "step-less" fan +PWM output. These two bytes are "read-locked" to guarantee that once a +high or low byte is read, the other byte is locked-in until after the +next read of any register. So to get an atomic reading, read high or low +byte, then the very next read should be the opposite byte. Our data +sheet says 10-bits of resolution, although you may find the lower bits +are active, they are not necessarily reliable or useful externally. We +chose not to mask them. + +We employ significant filtering that is user tunable as described in the +data sheet. Our temperature reports and fan PWM outputs are very smooth +when compared to the competition, in addition to the higher resolution +temperature reports. The smoother PWM output does not require user +intervention. + +We offer GPIO features on the former VID pins. These are open-drain +outputs or inputs and may be used as general purpose I/O or as alarm +outputs that are based on temperature limits. These are in 19h and 1Ah. + +We offer flexible mapping of temperature readings to thermal zones. Any +temperature may be mapped to any zone, which has a default assignment +that follows Intel's specs. + +Since there is a fan to zone assignment that allows for the "hotter" of +a set of zones to control the PWM of an individual fan, but there is no +indication to the user, we have added an indicator that shows which zone +is currently controlling the PWM for a given fan. This is in register +00h. + +Both remote diode temperature readings may be given an offset value such +that the reported reading as well as the temperature used to determine +PWM may be offset for system calibration purposes. + +PECI Extended configuration allows for having more than two domains per +PECI address and also provides an enabling function for each PECI +address. One could use our flexible zone assignment to have a zone +assigned to up to 4 PECI addresses. This is not possible in the default +Intel configuration. This would be useful in multi-CPU systems with +individual fans on each that would benefit from individual fan control. +This is in register 0Eh. + +The tachometer measurement system is flexible and able to adapt to many +fan types. We can also support pulse-stretched PWM so that 3-wire fans +may be used. These characteristics are in registers 04h to 07h. + +Finally, we have added a tach disable function that turns off the tach +measurement system for individual tachs in order to save power. That is +in register 75h. + +-- +aSC7621 Product Description + +The aSC7621 has a two wire digital interface compatible with SMBus 2.0. +Using a 10-bit ADC, the aSC7621 measures the temperature of two remote diode +connected transistors as well as its own die. Support for Platform +Environmental Control Interface (PECI) is included. + +Using temperature information from these four zones, an automatic fan speed +control algorithm is employed to minimize acoustic impact while achieving +recommended CPU temperature under varying operational loads. + +To set fan speed, the aSC7621 has three independent pulse width modulation +(PWM) outputs that are controlled by one, or a combination of three, +temperature zones. Both high- and low-frequency PWM ranges are supported. + +The aSC7621 also includes a digital filter that can be invoked to smooth +temperature readings for better control of fan speed and minimum acoustic +impact. + +The aSC7621 has tachometer inputs to measure fan speed on up to four fans. +Limit and status registers for all measured values are included to alert +the system host that any measurements are outside of programmed limits +via status registers. + +System voltages of VCCP, 2.5V, 3.3V, 5.0V, and 12V motherboard power are +monitored efficiently with internal scaling resistors. + +Features +- Supports PECI interface and monitors internal and remote thermal diodes +- 2-wire, SMBus 2.0 compliant, serial interface +- 10-bit ADC +- Monitors VCCP, 2.5V, 3.3V, 5.0V, and 12V motherboard/processor supplies +- Programmable autonomous fan control based on temperature readings +- Noise filtering of temperature reading for fan speed control +- 0.25C digital temperature sensor resolution +- 3 PWM fan speed control outputs for 2-, 3- or 4-wire fans and up to 4 fan + tachometer inputs +- Enhanced measured temperature to Temperature Zone assignment. +- Provides high and low PWM frequency ranges +- 3 GPIO pins for custom use +- 24-Lead QSOP package + +Configuration Notes +=================== + +Except where noted below, the sysfs entries created by this driver follow +the standards defined in "sysfs-interface". + +temp1_source + 0 (default) peci_legacy = 0, Remote 1 Temperature + peci_legacy = 1, PECI Processor Temperature 0 + 1 Remote 1 Temperature + 2 Remote 2 Temperature + 3 Internal Temperature + 4 PECI Processor Temperature 0 + 5 PECI Processor Temperature 1 + 6 PECI Processor Temperature 2 + 7 PECI Processor Temperature 3 + +temp2_source + 0 (default) Internal Temperature + 1 Remote 1 Temperature + 2 Remote 2 Temperature + 3 Internal Temperature + 4 PECI Processor Temperature 0 + 5 PECI Processor Temperature 1 + 6 PECI Processor Temperature 2 + 7 PECI Processor Temperature 3 + +temp3_source + 0 (default) Remote 2 Temperature + 1 Remote 1 Temperature + 2 Remote 2 Temperature + 3 Internal Temperature + 4 PECI Processor Temperature 0 + 5 PECI Processor Temperature 1 + 6 PECI Processor Temperature 2 + 7 PECI Processor Temperature 3 + +temp4_source + 0 (default) peci_legacy = 0, PECI Processor Temperature 0 + peci_legacy = 1, Remote 1 Temperature + 1 Remote 1 Temperature + 2 Remote 2 Temperature + 3 Internal Temperature + 4 PECI Processor Temperature 0 + 5 PECI Processor Temperature 1 + 6 PECI Processor Temperature 2 + 7 PECI Processor Temperature 3 + +temp[1-4]_smoothing_enable +temp[1-4]_smoothing_time + Smooths spikes in temp readings caused by noise. + Valid values in milliseconds are: + 35000 + 17600 + 11800 + 7000 + 4400 + 3000 + 1600 + 800 + +temp[1-4]_crit + When the corresponding zone temperature reaches this value, + ALL pwm outputs will got to 100%. + +temp[5-8]_input +temp[5-8]_enable + The aSC7621 can also read temperatures provided by the processor + via the PECI bus. Usually these are "core" temps and are relative + to the point where the automatic thermal control circuit starts + throttling. This means that these are usually negative numbers. + +pwm[1-3]_enable + 0 Fan off. + 1 Fan on manual control. + 2 Fan on automatic control and will run at the minimum pwm + if the temperature for the zone is below the minimum. + 3 Fan on automatic control but will be off if the temperature + for the zone is below the minimum. + 4-254 Ignored. + 255 Fan on full. + +pwm[1-3]_auto_channels + Bitmap as described in sysctl-interface with the following + exceptions... + Only the following combination of zones (and their corresponding masks) + are valid: + 1 + 2 + 3 + 2,3 + 1,2,3 + 4 + 1,2,3,4 + + Special values: + 0 Disabled. + 16 Fan on manual control. + 31 Fan on full. + + +pwm[1-3]_invert + When set, inverts the meaning of pwm[1-3]. + i.e. when pwm = 0, the fan will be on full and + when pwm = 255 the fan will be off. + +pwm[1-3]_freq + PWM frequency in Hz + Valid values in Hz are: + + 10 + 15 + 23 + 30 (default) + 38 + 47 + 62 + 94 + 23000 + 24000 + 25000 + 26000 + 27000 + 28000 + 29000 + 30000 + + Setting any other value will be ignored. + +peci_enable + Enables or disables PECI + +peci_avg + Input filter average time. + + 0 0 Sec. (no Smoothing) (default) + 1 0.25 Sec. + 2 0.5 Sec. + 3 1.0 Sec. + 4 2.0 Sec. + 5 4.0 Sec. + 6 8.0 Sec. + 7 0.0 Sec. + +peci_legacy + + 0 Standard Mode (default) + Remote Diode 1 reading is associated with + Temperature Zone 1, PECI is associated with + Zone 4 + + 1 Legacy Mode + PECI is associated with Temperature Zone 1, + Remote Diode 1 is associated with Zone 4 + +peci_diode + Diode filter + + 0 0.25 Sec. + 1 1.1 Sec. + 2 2.4 Sec. (default) + 3 3.4 Sec. + 4 5.0 Sec. + 5 6.8 Sec. + 6 10.2 Sec. + 7 16.4 Sec. + +peci_4domain + Four domain enable + + 0 1 or 2 Domains for enabled processors (default) + 1 3 or 4 Domains for enabled processors + +peci_domain + Domain + + 0 Processor contains a single domain (0) (default) + 1 Processor contains two domains (0,1) diff --git a/MAINTAINERS b/MAINTAINERS index bb6ec71f025..d6cbddb5732 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -966,6 +966,13 @@ W: http://www.arm.linux.org.uk/ S: Maintained F: arch/arm/vfp/ +ASC7621 HARDWARE MONITOR DRIVER +M: George Joseph +L: lm-sensors@lm-sensors.org +S: Maintained +F: Documentation/hwmon/asc7621 +F: drivers/hwmon/asc7621.c + ASUS ACPI EXTRAS DRIVER M: Corentin Chary M: Karol Kozimor diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 77d032fb813..b6d65aa2082 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -226,6 +226,19 @@ config SENSORS_ADT7475 This driver can also be build as a module. If so, the module will be called adt7475. +config SENSORS_ASC7621 + tristate "Andigilog aSC7621" + depends on HWMON && I2C + help + If you say yes here you get support for the aSC7621 + family of SMBus sensors chip found on most Intel X48, X38, 975, + 965 and 945 desktop boards. Currently supported chips: + aSC7621 + aSC7621a + + This driver can also be built as a module. If so, the module + will be called asc7621. + config SENSORS_K8TEMP tristate "AMD Athlon64/FX or Opteron temperature sensor" depends on X86 && PCI && EXPERIMENTAL diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 5fe67bf961b..865da80f2b9 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_SENSORS_ADT7473) += adt7473.o obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o obj-$(CONFIG_SENSORS_AMS) += ams/ +obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o obj-$(CONFIG_SENSORS_DME1737) += dme1737.o diff --git a/drivers/hwmon/asc7621.c b/drivers/hwmon/asc7621.c new file mode 100644 index 00000000000..7f948105d8a --- /dev/null +++ b/drivers/hwmon/asc7621.c @@ -0,0 +1,1255 @@ +/* + * asc7621.c - Part of lm_sensors, Linux kernel modules for hardware monitoring + * Copyright (c) 2007, 2010 George Joseph + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { + 0x2c, 0x2d, 0x2e, I2C_CLIENT_END +}; + +enum asc7621_type { + asc7621, + asc7621a +}; + +#define INTERVAL_HIGH (HZ + HZ / 2) +#define INTERVAL_LOW (1 * 60 * HZ) +#define PRI_NONE 0 +#define PRI_LOW 1 +#define PRI_HIGH 2 +#define FIRST_CHIP asc7621 +#define LAST_CHIP asc7621a + +struct asc7621_chip { + char *name; + enum asc7621_type chip_type; + u8 company_reg; + u8 company_id; + u8 verstep_reg; + u8 verstep_id; + unsigned short *addresses; +}; + +static struct asc7621_chip asc7621_chips[] = { + { + .name = "asc7621", + .chip_type = asc7621, + .company_reg = 0x3e, + .company_id = 0x61, + .verstep_reg = 0x3f, + .verstep_id = 0x6c, + .addresses = normal_i2c, + }, + { + .name = "asc7621a", + .chip_type = asc7621a, + .company_reg = 0x3e, + .company_id = 0x61, + .verstep_reg = 0x3f, + .verstep_id = 0x6d, + .addresses = normal_i2c, + }, +}; + +/* + * Defines the highest register to be used, not the count. + * The actual count will probably be smaller because of gaps + * in the implementation (unused register locations). + * This define will safely set the array size of both the parameter + * and data arrays. + * This comes from the data sheet register description table. + */ +#define LAST_REGISTER 0xff + +struct asc7621_data { + struct i2c_client client; + struct device *class_dev; + struct mutex update_lock; + int valid; /* !=0 if following fields are valid */ + unsigned long last_high_reading; /* In jiffies */ + unsigned long last_low_reading; /* In jiffies */ + /* + * Registers we care about occupy the corresponding index + * in the array. Registers we don't care about are left + * at 0. + */ + u8 reg[LAST_REGISTER + 1]; +}; + +/* + * Macro to get the parent asc7621_param structure + * from a sensor_device_attribute passed into the + * show/store functions. + */ +#define to_asc7621_param(_sda) \ + container_of(_sda, struct asc7621_param, sda) + +/* + * Each parameter to be retrieved needs an asc7621_param structure + * allocated. It contains the sensor_device_attribute structure + * and the control info needed to retrieve the value from the register map. + */ +struct asc7621_param { + struct sensor_device_attribute sda; + u8 priority; + u8 msb[3]; + u8 lsb[3]; + u8 mask[3]; + u8 shift[3]; +}; + +/* + * This is the map that ultimately indicates whether we'll be + * retrieving a register value or not, and at what frequency. + */ +static u8 asc7621_register_priorities[255]; + +static struct asc7621_data *asc7621_update_device(struct device *dev); + +static inline u8 read_byte(struct i2c_client *client, u8 reg) +{ + int res = i2c_smbus_read_byte_data(client, reg); + if (res < 0) { + dev_err(&client->dev, + "Unable to read from register 0x%02x.\n", reg); + return 0; + }; + return res & 0xff; +} + +static inline int write_byte(struct i2c_client *client, u8 reg, u8 data) +{ + int res = i2c_smbus_write_byte_data(client, reg, data); + if (res < 0) { + dev_err(&client->dev, + "Unable to write value 0x%02x to register 0x%02x.\n", + data, reg); + }; + return res; +} + +/* + * Data Handlers + * Each function handles the formatting, storage + * and retrieval of like parameters. + */ + +#define SETUP_SHOW_data_param(d, a) \ + struct sensor_device_attribute *sda = to_sensor_dev_attr(a); \ + struct asc7621_data *data = asc7621_update_device(d); \ + struct asc7621_param *param = to_asc7621_param(sda) + +#define SETUP_STORE_data_param(d, a) \ + struct sensor_device_attribute *sda = to_sensor_dev_attr(a); \ + struct i2c_client *client = to_i2c_client(d); \ + struct asc7621_data *data = i2c_get_clientdata(client); \ + struct asc7621_param *param = to_asc7621_param(sda) + +/* + * u8 is just what it sounds like...an unsigned byte with no + * special formatting. + */ +static ssize_t show_u8(struct device *dev, struct device_attribute *attr, + char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + + return sprintf(buf, "%u\n", data->reg[param->msb[0]]); +} + +static ssize_t store_u8(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + reqval = SENSORS_LIMIT(reqval, 0, 255); + + mutex_lock(&data->update_lock); + data->reg[param->msb[0]] = reqval; + write_byte(client, param->msb[0], reqval); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * Many of the config values occupy only a few bits of a register. + */ +static ssize_t show_bitmask(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + + return sprintf(buf, "%u\n", + (data->reg[param->msb[0]] >> param-> + shift[0]) & param->mask[0]); +} + +static ssize_t store_bitmask(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval; + u8 currval; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + reqval = SENSORS_LIMIT(reqval, 0, param->mask[0]); + + reqval = (reqval & param->mask[0]) << param->shift[0]; + + mutex_lock(&data->update_lock); + currval = read_byte(client, param->msb[0]); + reqval |= (currval & ~(param->mask[0] << param->shift[0])); + data->reg[param->msb[0]] = reqval; + write_byte(client, param->msb[0], reqval); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * 16 bit fan rpm values + * reported by the device as the number of 11.111us periods (90khz) + * between full fan rotations. Therefore... + * RPM = (90000 * 60) / register value + */ +static ssize_t show_fan16(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u16 regval; + + mutex_lock(&data->update_lock); + regval = (data->reg[param->msb[0]] << 8) | data->reg[param->lsb[0]]; + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%u\n", + (regval == 0 ? -1 : (regval) == + 0xffff ? 0 : 5400000 / regval)); +} + +static ssize_t store_fan16(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + reqval = + (SENSORS_LIMIT((reqval) <= 0 ? 0 : 5400000 / (reqval), 0, 65534)); + + mutex_lock(&data->update_lock); + data->reg[param->msb[0]] = (reqval >> 8) & 0xff; + data->reg[param->lsb[0]] = reqval & 0xff; + write_byte(client, param->msb[0], data->reg[param->msb[0]]); + write_byte(client, param->lsb[0], data->reg[param->lsb[0]]); + mutex_unlock(&data->update_lock); + + return count; +} + +/* + * Voltages are scaled in the device so that the nominal voltage + * is 3/4ths of the 0-255 range (i.e. 192). + * If all voltages are 'normal' then all voltage registers will + * read 0xC0. This doesn't help us if we don't have a point of refernce. + * The data sheet however provides us with the full scale value for each + * which is stored in in_scaling. The sda->index parameter value provides + * the index into in_scaling. + * + * NOTE: The chip expects the first 2 inputs be 2.5 and 2.25 volts + * respectively. That doesn't mean that's what the motherboard provides. :) + */ + +static int asc7621_in_scaling[] = { + 3320, 3000, 4380, 6640, 16000 +}; + +static ssize_t show_in10(struct device *dev, struct device_attribute *attr, + char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u16 regval; + u8 nr = sda->index; + + mutex_lock(&data->update_lock); + regval = (data->reg[param->msb[0]] * asc7621_in_scaling[nr]) / 256; + + /* The LSB value is a 2-bit scaling of the MSB's LSbit value. + * I.E. If the maximim voltage for this input is 6640 millivolts then + * a MSB register value of 0 = 0mv and 255 = 6640mv. + * A 1 step change therefore represents 25.9mv (6640 / 256). + * The extra 2-bits therefore represent increments of 6.48mv. + */ + regval += ((asc7621_in_scaling[nr] / 256) / 4) * + (data->reg[param->lsb[0]] >> 6); + + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%u\n", regval); +} + +/* 8 bit voltage values (the mins and maxs) */ +static ssize_t show_in8(struct device *dev, struct device_attribute *attr, + char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u8 nr = sda->index; + + return sprintf(buf, "%u\n", + ((data->reg[param->msb[0]] * + asc7621_in_scaling[nr]) / 256)); +} + +static ssize_t store_in8(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval; + u8 nr = sda->index; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + reqval = SENSORS_LIMIT(reqval, 0, asc7621_in_scaling[nr]); + + reqval = (reqval * 255 + 128) / asc7621_in_scaling[nr]; + + mutex_lock(&data->update_lock); + data->reg[param->msb[0]] = reqval; + write_byte(client, param->msb[0], reqval); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_temp8(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + + return sprintf(buf, "%d\n", ((s8) data->reg[param->msb[0]]) * 1000); +} + +static ssize_t store_temp8(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval; + s8 temp; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + reqval = SENSORS_LIMIT(reqval, -127000, 127000); + + temp = reqval / 1000; + + mutex_lock(&data->update_lock); + data->reg[param->msb[0]] = temp; + write_byte(client, param->msb[0], temp); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * Temperatures that occupy 2 bytes always have the whole + * number of degrees in the MSB with some part of the LSB + * indicating fractional degrees. + */ + +/* mmmmmmmm.llxxxxxx */ +static ssize_t show_temp10(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u8 msb, lsb; + int temp; + + mutex_lock(&data->update_lock); + msb = data->reg[param->msb[0]]; + lsb = (data->reg[param->lsb[0]] >> 6) & 0x03; + temp = (((s8) msb) * 1000) + (lsb * 250); + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%d\n", temp); +} + +/* mmmmmm.ll */ +static ssize_t show_temp62(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u8 regval = data->reg[param->msb[0]]; + int temp = ((s8) (regval & 0xfc) * 1000) + ((regval & 0x03) * 250); + + return sprintf(buf, "%d\n", temp); +} + +static ssize_t store_temp62(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval, i, f; + s8 temp; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + reqval = SENSORS_LIMIT(reqval, -32000, 31750); + i = reqval / 1000; + f = reqval - (i * 1000); + temp = i << 2; + temp |= f / 250; + + mutex_lock(&data->update_lock); + data->reg[param->msb[0]] = temp; + write_byte(client, param->msb[0], temp); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * The aSC7621 doesn't provide an "auto_point2". Instead, you + * specify the auto_point1 and a range. To keep with the sysfs + * hwmon specs, we synthesize the auto_point_2 from them. + */ + +static u32 asc7621_range_map[] = { + 2000, 2500, 3330, 4000, 5000, 6670, 8000, 10000, + 13330, 16000, 20000, 26670, 32000, 40000, 53330, 80000, +}; + +static ssize_t show_ap2_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + long auto_point1; + u8 regval; + int temp; + + mutex_lock(&data->update_lock); + auto_point1 = ((s8) data->reg[param->msb[1]]) * 1000; + regval = + ((data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]); + temp = auto_point1 + asc7621_range_map[SENSORS_LIMIT(regval, 0, 15)]; + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%d\n", temp); + +} + +static ssize_t store_ap2_temp(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval, auto_point1; + int i; + u8 currval, newval = 0; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + mutex_lock(&data->update_lock); + auto_point1 = data->reg[param->msb[1]] * 1000; + reqval = SENSORS_LIMIT(reqval, auto_point1 + 2000, auto_point1 + 80000); + + for (i = ARRAY_SIZE(asc7621_range_map) - 1; i >= 0; i--) { + if (reqval >= auto_point1 + asc7621_range_map[i]) { + newval = i; + break; + } + } + + newval = (newval & param->mask[0]) << param->shift[0]; + currval = read_byte(client, param->msb[0]); + newval |= (currval & ~(param->mask[0] << param->shift[0])); + data->reg[param->msb[0]] = newval; + write_byte(client, param->msb[0], newval); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_pwm_ac(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u8 config, altbit, regval; + u8 map[] = { + 0x01, 0x02, 0x04, 0x1f, 0x00, 0x06, 0x07, 0x10, + 0x08, 0x0f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f + }; + + mutex_lock(&data->update_lock); + config = (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]; + altbit = (data->reg[param->msb[1]] >> param->shift[1]) & param->mask[1]; + regval = config | (altbit << 3); + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%u\n", map[SENSORS_LIMIT(regval, 0, 15)]); +} + +static ssize_t store_pwm_ac(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + unsigned long reqval; + u8 currval, config, altbit, newval; + u16 map[] = { + 0x04, 0x00, 0x01, 0xff, 0x02, 0xff, 0x05, 0x06, + 0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, + 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, + }; + + if (strict_strtoul(buf, 10, &reqval)) + return -EINVAL; + + if (reqval > 31) + return -EINVAL; + + reqval = map[reqval]; + if (reqval == 0xff) + return -EINVAL; + + config = reqval & 0x07; + altbit = (reqval >> 3) & 0x01; + + config = (config & param->mask[0]) << param->shift[0]; + altbit = (altbit & param->mask[1]) << param->shift[1]; + + mutex_lock(&data->update_lock); + currval = read_byte(client, param->msb[0]); + newval = config | (currval & ~(param->mask[0] << param->shift[0])); + newval = altbit | (newval & ~(param->mask[1] << param->shift[1])); + data->reg[param->msb[0]] = newval; + write_byte(client, param->msb[0], newval); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_pwm_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u8 config, altbit, minoff, val, newval; + + mutex_lock(&data->update_lock); + config = (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]; + altbit = (data->reg[param->msb[1]] >> param->shift[1]) & param->mask[1]; + minoff = (data->reg[param->msb[2]] >> param->shift[2]) & param->mask[2]; + mutex_unlock(&data->update_lock); + + val = config | (altbit << 3); + newval = 0; + + if (val == 3 || val >= 10) + newval = 255; + else if (val == 4) + newval = 0; + else if (val == 7) + newval = 1; + else if (minoff == 1) + newval = 2; + else + newval = 3; + + return sprintf(buf, "%u\n", newval); +} + +static ssize_t store_pwm_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval; + u8 currval, config, altbit, newval, minoff = 255; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + switch (reqval) { + case 0: + newval = 0x04; + break; + case 1: + newval = 0x07; + break; + case 2: + newval = 0x00; + minoff = 1; + break; + case 3: + newval = 0x00; + minoff = 0; + break; + case 255: + newval = 0x03; + break; + default: + return -EINVAL; + } + + config = newval & 0x07; + altbit = (newval >> 3) & 0x01; + + mutex_lock(&data->update_lock); + config = (config & param->mask[0]) << param->shift[0]; + altbit = (altbit & param->mask[1]) << param->shift[1]; + currval = read_byte(client, param->msb[0]); + newval = config | (currval & ~(param->mask[0] << param->shift[0])); + newval = altbit | (newval & ~(param->mask[1] << param->shift[1])); + data->reg[param->msb[0]] = newval; + write_byte(client, param->msb[0], newval); + if (minoff < 255) { + minoff = (minoff & param->mask[2]) << param->shift[2]; + currval = read_byte(client, param->msb[2]); + newval = + minoff | (currval & ~(param->mask[2] << param->shift[2])); + data->reg[param->msb[2]] = newval; + write_byte(client, param->msb[2], newval); + } + mutex_unlock(&data->update_lock); + return count; +} + +static u32 asc7621_pwm_freq_map[] = { + 10, 15, 23, 30, 38, 47, 62, 94, + 23000, 24000, 25000, 26000, 27000, 28000, 29000, 30000 +}; + +static ssize_t show_pwm_freq(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u8 regval = + (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]; + + regval = SENSORS_LIMIT(regval, 0, 15); + + return sprintf(buf, "%u\n", asc7621_pwm_freq_map[regval]); +} + +static ssize_t store_pwm_freq(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + unsigned long reqval; + u8 currval, newval = 255; + int i; + + if (strict_strtoul(buf, 10, &reqval)) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(asc7621_pwm_freq_map); i++) { + if (reqval == asc7621_pwm_freq_map[i]) { + newval = i; + break; + } + } + if (newval == 255) + return -EINVAL; + + newval = (newval & param->mask[0]) << param->shift[0]; + + mutex_lock(&data->update_lock); + currval = read_byte(client, param->msb[0]); + newval |= (currval & ~(param->mask[0] << param->shift[0])); + data->reg[param->msb[0]] = newval; + write_byte(client, param->msb[0], newval); + mutex_unlock(&data->update_lock); + return count; +} + +static u32 asc7621_pwm_auto_spinup_map[] = { + 0, 100, 250, 400, 700, 1000, 2000, 4000 +}; + +static ssize_t show_pwm_ast(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u8 regval = + (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]; + + regval = SENSORS_LIMIT(regval, 0, 7); + + return sprintf(buf, "%u\n", asc7621_pwm_auto_spinup_map[regval]); + +} + +static ssize_t store_pwm_ast(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval; + u8 currval, newval = 255; + u32 i; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(asc7621_pwm_auto_spinup_map); i++) { + if (reqval == asc7621_pwm_auto_spinup_map[i]) { + newval = i; + break; + } + } + if (newval == 255) + return -EINVAL; + + newval = (newval & param->mask[0]) << param->shift[0]; + + mutex_lock(&data->update_lock); + currval = read_byte(client, param->msb[0]); + newval |= (currval & ~(param->mask[0] << param->shift[0])); + data->reg[param->msb[0]] = newval; + write_byte(client, param->msb[0], newval); + mutex_unlock(&data->update_lock); + return count; +} + +static u32 asc7621_temp_smoothing_time_map[] = { + 35000, 17600, 11800, 7000, 4400, 3000, 1600, 800 +}; + +static ssize_t show_temp_st(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u8 regval = + (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]; + regval = SENSORS_LIMIT(regval, 0, 7); + + return sprintf(buf, "%u\n", asc7621_temp_smoothing_time_map[regval]); +} + +static ssize_t store_temp_st(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval; + u8 currval, newval = 255; + u32 i; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(asc7621_temp_smoothing_time_map); i++) { + if (reqval == asc7621_temp_smoothing_time_map[i]) { + newval = i; + break; + } + } + + if (newval == 255) + return -EINVAL; + + newval = (newval & param->mask[0]) << param->shift[0]; + + mutex_lock(&data->update_lock); + currval = read_byte(client, param->msb[0]); + newval |= (currval & ~(param->mask[0] << param->shift[0])); + data->reg[param->msb[0]] = newval; + write_byte(client, param->msb[0], newval); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * End of data handlers + * + * These defines do nothing more than make the table easier + * to read when wrapped at column 80. + */ + +/* + * Creates a variable length array inititalizer. + * VAA(1,3,5,7) would produce {1,3,5,7} + */ +#define VAA(args...) {args} + +#define PREAD(name, n, pri, rm, rl, m, s, r) \ + {.sda = SENSOR_ATTR(name, S_IRUGO, show_##r, NULL, n), \ + .priority = pri, .msb[0] = rm, .lsb[0] = rl, .mask[0] = m, \ + .shift[0] = s,} + +#define PWRITE(name, n, pri, rm, rl, m, s, r) \ + {.sda = SENSOR_ATTR(name, S_IRUGO | S_IWUSR, show_##r, store_##r, n), \ + .priority = pri, .msb[0] = rm, .lsb[0] = rl, .mask[0] = m, \ + .shift[0] = s,} + +/* + * PWRITEM assumes that the initializers for the .msb, .lsb, .mask and .shift + * were created using the VAA macro. + */ +#define PWRITEM(name, n, pri, rm, rl, m, s, r) \ + {.sda = SENSOR_ATTR(name, S_IRUGO | S_IWUSR, show_##r, store_##r, n), \ + .priority = pri, .msb = rm, .lsb = rl, .mask = m, .shift = s,} + +static struct asc7621_param asc7621_params[] = { + PREAD(in0_input, 0, PRI_HIGH, 0x20, 0x13, 0, 0, in10), + PREAD(in1_input, 1, PRI_HIGH, 0x21, 0x18, 0, 0, in10), + PREAD(in2_input, 2, PRI_HIGH, 0x22, 0x11, 0, 0, in10), + PREAD(in3_input, 3, PRI_HIGH, 0x23, 0x12, 0, 0, in10), + PREAD(in4_input, 4, PRI_HIGH, 0x24, 0x14, 0, 0, in10), + + PWRITE(in0_min, 0, PRI_LOW, 0x44, 0, 0, 0, in8), + PWRITE(in1_min, 1, PRI_LOW, 0x46, 0, 0, 0, in8), + PWRITE(in2_min, 2, PRI_LOW, 0x48, 0, 0, 0, in8), + PWRITE(in3_min, 3, PRI_LOW, 0x4a, 0, 0, 0, in8), + PWRITE(in4_min, 4, PRI_LOW, 0x4c, 0, 0, 0, in8), + + PWRITE(in0_max, 0, PRI_LOW, 0x45, 0, 0, 0, in8), + PWRITE(in1_max, 1, PRI_LOW, 0x47, 0, 0, 0, in8), + PWRITE(in2_max, 2, PRI_LOW, 0x49, 0, 0, 0, in8), + PWRITE(in3_max, 3, PRI_LOW, 0x4b, 0, 0, 0, in8), + PWRITE(in4_max, 4, PRI_LOW, 0x4d, 0, 0, 0, in8), + + PREAD(in0_alarm, 0, PRI_LOW, 0x41, 0, 0x01, 0, bitmask), + PREAD(in1_alarm, 1, PRI_LOW, 0x41, 0, 0x01, 1, bitmask), + PREAD(in2_alarm, 2, PRI_LOW, 0x41, 0, 0x01, 2, bitmask), + PREAD(in3_alarm, 3, PRI_LOW, 0x41, 0, 0x01, 3, bitmask), + PREAD(in4_alarm, 4, PRI_LOW, 0x42, 0, 0x01, 0, bitmask), + + PREAD(fan1_input, 0, PRI_HIGH, 0x29, 0x28, 0, 0, fan16), + PREAD(fan2_input, 1, PRI_HIGH, 0x2b, 0x2a, 0, 0, fan16), + PREAD(fan3_input, 2, PRI_HIGH, 0x2d, 0x2c, 0, 0, fan16), + PREAD(fan4_input, 3, PRI_HIGH, 0x2f, 0x2e, 0, 0, fan16), + + PWRITE(fan1_min, 0, PRI_LOW, 0x55, 0x54, 0, 0, fan16), + PWRITE(fan2_min, 1, PRI_LOW, 0x57, 0x56, 0, 0, fan16), + PWRITE(fan3_min, 2, PRI_LOW, 0x59, 0x58, 0, 0, fan16), + PWRITE(fan4_min, 3, PRI_LOW, 0x5b, 0x5a, 0, 0, fan16), + + PREAD(fan1_alarm, 0, PRI_LOW, 0x42, 0, 0x01, 0, bitmask), + PREAD(fan2_alarm, 1, PRI_LOW, 0x42, 0, 0x01, 1, bitmask), + PREAD(fan3_alarm, 2, PRI_LOW, 0x42, 0, 0x01, 2, bitmask), + PREAD(fan4_alarm, 3, PRI_LOW, 0x42, 0, 0x01, 3, bitmask), + + PREAD(temp1_input, 0, PRI_HIGH, 0x25, 0x10, 0, 0, temp10), + PREAD(temp2_input, 1, PRI_HIGH, 0x26, 0x15, 0, 0, temp10), + PREAD(temp3_input, 2, PRI_HIGH, 0x27, 0x16, 0, 0, temp10), + PREAD(temp4_input, 3, PRI_HIGH, 0x33, 0x17, 0, 0, temp10), + PREAD(temp5_input, 4, PRI_HIGH, 0xf7, 0xf6, 0, 0, temp10), + PREAD(temp6_input, 5, PRI_HIGH, 0xf9, 0xf8, 0, 0, temp10), + PREAD(temp7_input, 6, PRI_HIGH, 0xfb, 0xfa, 0, 0, temp10), + PREAD(temp8_input, 7, PRI_HIGH, 0xfd, 0xfc, 0, 0, temp10), + + PWRITE(temp1_min, 0, PRI_LOW, 0x4e, 0, 0, 0, temp8), + PWRITE(temp2_min, 1, PRI_LOW, 0x50, 0, 0, 0, temp8), + PWRITE(temp3_min, 2, PRI_LOW, 0x52, 0, 0, 0, temp8), + PWRITE(temp4_min, 3, PRI_LOW, 0x34, 0, 0, 0, temp8), + + PWRITE(temp1_max, 0, PRI_LOW, 0x4f, 0, 0, 0, temp8), + PWRITE(temp2_max, 1, PRI_LOW, 0x51, 0, 0, 0, temp8), + PWRITE(temp3_max, 2, PRI_LOW, 0x53, 0, 0, 0, temp8), + PWRITE(temp4_max, 3, PRI_LOW, 0x35, 0, 0, 0, temp8), + + PREAD(temp1_alarm, 0, PRI_LOW, 0x41, 0, 0x01, 4, bitmask), + PREAD(temp2_alarm, 1, PRI_LOW, 0x41, 0, 0x01, 5, bitmask), + PREAD(temp3_alarm, 2, PRI_LOW, 0x41, 0, 0x01, 6, bitmask), + PREAD(temp4_alarm, 3, PRI_LOW, 0x43, 0, 0x01, 0, bitmask), + + PWRITE(temp1_source, 0, PRI_LOW, 0x02, 0, 0x07, 4, bitmask), + PWRITE(temp2_source, 1, PRI_LOW, 0x02, 0, 0x07, 0, bitmask), + PWRITE(temp3_source, 2, PRI_LOW, 0x03, 0, 0x07, 4, bitmask), + PWRITE(temp4_source, 3, PRI_LOW, 0x03, 0, 0x07, 0, bitmask), + + PWRITE(temp1_smoothing_enable, 0, PRI_LOW, 0x62, 0, 0x01, 3, bitmask), + PWRITE(temp2_smoothing_enable, 1, PRI_LOW, 0x63, 0, 0x01, 7, bitmask), + PWRITE(temp3_smoothing_enable, 2, PRI_LOW, 0x64, 0, 0x01, 3, bitmask), + PWRITE(temp4_smoothing_enable, 3, PRI_LOW, 0x3c, 0, 0x01, 3, bitmask), + + PWRITE(temp1_smoothing_time, 0, PRI_LOW, 0x62, 0, 0x07, 0, temp_st), + PWRITE(temp2_smoothing_time, 1, PRI_LOW, 0x63, 0, 0x07, 4, temp_st), + PWRITE(temp3_smoothing_time, 2, PRI_LOW, 0x63, 0, 0x07, 0, temp_st), + PWRITE(temp4_smoothing_time, 3, PRI_LOW, 0x3c, 0, 0x07, 0, temp_st), + + PWRITE(temp1_auto_point1_temp_hyst, 0, PRI_LOW, 0x6d, 0, 0x0f, 4, + bitmask), + PWRITE(temp2_auto_point1_temp_hyst, 1, PRI_LOW, 0x6d, 0, 0x0f, 0, + bitmask), + PWRITE(temp3_auto_point1_temp_hyst, 2, PRI_LOW, 0x6e, 0, 0x0f, 4, + bitmask), + PWRITE(temp4_auto_point1_temp_hyst, 3, PRI_LOW, 0x6e, 0, 0x0f, 0, + bitmask), + + PREAD(temp1_auto_point2_temp_hyst, 0, PRI_LOW, 0x6d, 0, 0x0f, 4, + bitmask), + PREAD(temp2_auto_point2_temp_hyst, 1, PRI_LOW, 0x6d, 0, 0x0f, 0, + bitmask), + PREAD(temp3_auto_point2_temp_hyst, 2, PRI_LOW, 0x6e, 0, 0x0f, 4, + bitmask), + PREAD(temp4_auto_point2_temp_hyst, 3, PRI_LOW, 0x6e, 0, 0x0f, 0, + bitmask), + + PWRITE(temp1_auto_point1_temp, 0, PRI_LOW, 0x67, 0, 0, 0, temp8), + PWRITE(temp2_auto_point1_temp, 1, PRI_LOW, 0x68, 0, 0, 0, temp8), + PWRITE(temp3_auto_point1_temp, 2, PRI_LOW, 0x69, 0, 0, 0, temp8), + PWRITE(temp4_auto_point1_temp, 3, PRI_LOW, 0x3b, 0, 0, 0, temp8), + + PWRITEM(temp1_auto_point2_temp, 0, PRI_LOW, VAA(0x5f, 0x67), VAA(0), + VAA(0x0f), VAA(4), ap2_temp), + PWRITEM(temp2_auto_point2_temp, 1, PRI_LOW, VAA(0x60, 0x68), VAA(0), + VAA(0x0f), VAA(4), ap2_temp), + PWRITEM(temp3_auto_point2_temp, 2, PRI_LOW, VAA(0x61, 0x69), VAA(0), + VAA(0x0f), VAA(4), ap2_temp), + PWRITEM(temp4_auto_point2_temp, 3, PRI_LOW, VAA(0x3c, 0x3b), VAA(0), + VAA(0x0f), VAA(4), ap2_temp), + + PWRITE(temp1_crit, 0, PRI_LOW, 0x6a, 0, 0, 0, temp8), + PWRITE(temp2_crit, 1, PRI_LOW, 0x6b, 0, 0, 0, temp8), + PWRITE(temp3_crit, 2, PRI_LOW, 0x6c, 0, 0, 0, temp8), + PWRITE(temp4_crit, 3, PRI_LOW, 0x3d, 0, 0, 0, temp8), + + PWRITE(temp5_enable, 4, PRI_LOW, 0x0e, 0, 0x01, 0, bitmask), + PWRITE(temp6_enable, 5, PRI_LOW, 0x0e, 0, 0x01, 1, bitmask), + PWRITE(temp7_enable, 6, PRI_LOW, 0x0e, 0, 0x01, 2, bitmask), + PWRITE(temp8_enable, 7, PRI_LOW, 0x0e, 0, 0x01, 3, bitmask), + + PWRITE(remote1_offset, 0, PRI_LOW, 0x1c, 0, 0, 0, temp62), + PWRITE(remote2_offset, 1, PRI_LOW, 0x1d, 0, 0, 0, temp62), + + PWRITE(pwm1, 0, PRI_HIGH, 0x30, 0, 0, 0, u8), + PWRITE(pwm2, 1, PRI_HIGH, 0x31, 0, 0, 0, u8), + PWRITE(pwm3, 2, PRI_HIGH, 0x32, 0, 0, 0, u8), + + PWRITE(pwm1_invert, 0, PRI_LOW, 0x5c, 0, 0x01, 4, bitmask), + PWRITE(pwm2_invert, 1, PRI_LOW, 0x5d, 0, 0x01, 4, bitmask), + PWRITE(pwm3_invert, 2, PRI_LOW, 0x5e, 0, 0x01, 4, bitmask), + + PWRITEM(pwm1_enable, 0, PRI_LOW, VAA(0x5c, 0x5c, 0x62), VAA(0, 0, 0), + VAA(0x07, 0x01, 0x01), VAA(5, 3, 5), pwm_enable), + PWRITEM(pwm2_enable, 1, PRI_LOW, VAA(0x5d, 0x5d, 0x62), VAA(0, 0, 0), + VAA(0x07, 0x01, 0x01), VAA(5, 3, 6), pwm_enable), + PWRITEM(pwm3_enable, 2, PRI_LOW, VAA(0x5e, 0x5e, 0x62), VAA(0, 0, 0), + VAA(0x07, 0x01, 0x01), VAA(5, 3, 7), pwm_enable), + + PWRITEM(pwm1_auto_channels, 0, PRI_LOW, VAA(0x5c, 0x5c), VAA(0, 0), + VAA(0x07, 0x01), VAA(5, 3), pwm_ac), + PWRITEM(pwm2_auto_channels, 1, PRI_LOW, VAA(0x5d, 0x5d), VAA(0, 0), + VAA(0x07, 0x01), VAA(5, 3), pwm_ac), + PWRITEM(pwm3_auto_channels, 2, PRI_LOW, VAA(0x5e, 0x5e), VAA(0, 0), + VAA(0x07, 0x01), VAA(5, 3), pwm_ac), + + PWRITE(pwm1_auto_point1_pwm, 0, PRI_LOW, 0x64, 0, 0, 0, u8), + PWRITE(pwm2_auto_point1_pwm, 1, PRI_LOW, 0x65, 0, 0, 0, u8), + PWRITE(pwm3_auto_point1_pwm, 2, PRI_LOW, 0x66, 0, 0, 0, u8), + + PWRITE(pwm1_auto_point2_pwm, 0, PRI_LOW, 0x38, 0, 0, 0, u8), + PWRITE(pwm2_auto_point2_pwm, 1, PRI_LOW, 0x39, 0, 0, 0, u8), + PWRITE(pwm3_auto_point2_pwm, 2, PRI_LOW, 0x3a, 0, 0, 0, u8), + + PWRITE(pwm1_freq, 0, PRI_LOW, 0x5f, 0, 0x0f, 0, pwm_freq), + PWRITE(pwm2_freq, 1, PRI_LOW, 0x60, 0, 0x0f, 0, pwm_freq), + PWRITE(pwm3_freq, 2, PRI_LOW, 0x61, 0, 0x0f, 0, pwm_freq), + + PREAD(pwm1_auto_zone_assigned, 0, PRI_LOW, 0, 0, 0x03, 2, bitmask), + PREAD(pwm2_auto_zone_assigned, 1, PRI_LOW, 0, 0, 0x03, 4, bitmask), + PREAD(pwm3_auto_zone_assigned, 2, PRI_LOW, 0, 0, 0x03, 6, bitmask), + + PWRITE(pwm1_auto_spinup_time, 0, PRI_LOW, 0x5c, 0, 0x07, 0, pwm_ast), + PWRITE(pwm2_auto_spinup_time, 1, PRI_LOW, 0x5d, 0, 0x07, 0, pwm_ast), + PWRITE(pwm3_auto_spinup_time, 2, PRI_LOW, 0x5e, 0, 0x07, 0, pwm_ast), + + PWRITE(peci_enable, 0, PRI_LOW, 0x40, 0, 0x01, 4, bitmask), + PWRITE(peci_avg, 0, PRI_LOW, 0x36, 0, 0x07, 0, bitmask), + PWRITE(peci_domain, 0, PRI_LOW, 0x36, 0, 0x01, 3, bitmask), + PWRITE(peci_legacy, 0, PRI_LOW, 0x36, 0, 0x01, 4, bitmask), + PWRITE(peci_diode, 0, PRI_LOW, 0x0e, 0, 0x07, 4, bitmask), + PWRITE(peci_4domain, 0, PRI_LOW, 0x0e, 0, 0x01, 4, bitmask), + +}; + +static struct asc7621_data *asc7621_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct asc7621_data *data = i2c_get_clientdata(client); + int i; + +/* + * The asc7621 chips guarantee consistent reads of multi-byte values + * regardless of the order of the reads. No special logic is needed + * so we can just read the registers in whatever order they appear + * in the asc7621_params array. + */ + + mutex_lock(&data->update_lock); + + /* Read all the high priority registers */ + + if (!data->valid || + time_after(jiffies, data->last_high_reading + INTERVAL_HIGH)) { + + for (i = 0; i < ARRAY_SIZE(asc7621_register_priorities); i++) { + if (asc7621_register_priorities[i] == PRI_HIGH) { + data->reg[i] = + i2c_smbus_read_byte_data(client, i) & 0xff; + } + } + data->last_high_reading = jiffies; + }; /* last_reading */ + + /* Read all the low priority registers. */ + + if (!data->valid || + time_after(jiffies, data->last_low_reading + INTERVAL_LOW)) { + + for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) { + if (asc7621_register_priorities[i] == PRI_LOW) { + data->reg[i] = + i2c_smbus_read_byte_data(client, i) & 0xff; + } + } + data->last_low_reading = jiffies; + }; /* last_reading */ + + data->valid = 1; + + mutex_unlock(&data->update_lock); + + return data; +} + +/* + * Standard detection and initialization below + * + * Helper function that checks if an address is valid + * for a particular chip. + */ + +static inline int valid_address_for_chip(int chip_type, int address) +{ + int i; + + for (i = 0; asc7621_chips[chip_type].addresses[i] != I2C_CLIENT_END; + i++) { + if (asc7621_chips[chip_type].addresses[i] == address) + return 1; + } + return 0; +} + +static void asc7621_init_client(struct i2c_client *client) +{ + int value; + + /* Warn if part was not "READY" */ + + value = read_byte(client, 0x40); + + if (value & 0x02) { + dev_err(&client->dev, + "Client (%d,0x%02x) config is locked.\n", + i2c_adapter_id(client->adapter), client->addr); + }; + if (!(value & 0x04)) { + dev_err(&client->dev, "Client (%d,0x%02x) is not ready.\n", + i2c_adapter_id(client->adapter), client->addr); + }; + +/* + * Start monitoring + * + * Try to clear LOCK, Set START, save everything else + */ + value = (value & ~0x02) | 0x01; + write_byte(client, 0x40, value & 0xff); + +} + +static int +asc7621_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct asc7621_data *data; + int i, err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + data = kzalloc(sizeof(struct asc7621_data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + i2c_set_clientdata(client, data); + data->valid = 0; + mutex_init(&data->update_lock); + + /* Initialize the asc7621 chip */ + asc7621_init_client(client); + + /* Create the sysfs entries */ + for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) { + err = + device_create_file(&client->dev, + &(asc7621_params[i].sda.dev_attr)); + if (err) + goto exit_remove; + } + + data->class_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->class_dev)) { + err = PTR_ERR(data->class_dev); + goto exit_remove; + } + + return 0; + +exit_remove: + for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) { + device_remove_file(&client->dev, + &(asc7621_params[i].sda.dev_attr)); + } + + i2c_set_clientdata(client, NULL); + kfree(data); + return err; +} + +static int asc7621_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int company, verstep, chip_index; + struct device *dev; + + dev = &client->dev; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + for (chip_index = FIRST_CHIP; chip_index <= LAST_CHIP; chip_index++) { + + if (!valid_address_for_chip(chip_index, client->addr)) + continue; + + company = read_byte(client, + asc7621_chips[chip_index].company_reg); + verstep = read_byte(client, + asc7621_chips[chip_index].verstep_reg); + + if (company == asc7621_chips[chip_index].company_id && + verstep == asc7621_chips[chip_index].verstep_id) { + strlcpy(client->name, asc7621_chips[chip_index].name, + I2C_NAME_SIZE); + strlcpy(info->type, asc7621_chips[chip_index].name, + I2C_NAME_SIZE); + + dev_info(&adapter->dev, "Matched %s\n", + asc7621_chips[chip_index].name); + return 0; + } + } + + return -ENODEV; +} + +static int asc7621_remove(struct i2c_client *client) +{ + struct asc7621_data *data = i2c_get_clientdata(client); + int i; + + hwmon_device_unregister(data->class_dev); + + for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) { + device_remove_file(&client->dev, + &(asc7621_params[i].sda.dev_attr)); + } + + i2c_set_clientdata(client, NULL); + kfree(data); + return 0; +} + +static const struct i2c_device_id asc7621_id[] = { + {"asc7621", asc7621}, + {"asc7621a", asc7621a}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, asc7621_id); + +static struct i2c_driver asc7621_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "asc7621", + }, + .probe = asc7621_probe, + .remove = asc7621_remove, + .id_table = asc7621_id, + .detect = asc7621_detect, + .address_list = normal_i2c, +}; + +static int __init sm_asc7621_init(void) +{ + int i, j; +/* + * Collect all the registers needed into a single array. + * This way, if a register isn't actually used for anything, + * we don't retrieve it. + */ + + for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) { + for (j = 0; j < ARRAY_SIZE(asc7621_params[i].msb); j++) + asc7621_register_priorities[asc7621_params[i].msb[j]] = + asc7621_params[i].priority; + for (j = 0; j < ARRAY_SIZE(asc7621_params[i].lsb); j++) + asc7621_register_priorities[asc7621_params[i].lsb[j]] = + asc7621_params[i].priority; + } + return i2c_add_driver(&asc7621_driver); +} + +static void __exit sm_asc7621_exit(void) +{ + i2c_del_driver(&asc7621_driver); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("George Joseph"); +MODULE_DESCRIPTION("Andigilog aSC7621 and aSC7621a driver"); + +module_init(sm_asc7621_init); +module_exit(sm_asc7621_exit); -- cgit v1.2.3-70-g09d2 From a0a5e3488a51c46f383c5faa86b53828e30ce153 Mon Sep 17 00:00:00 2001 From: Amerigo Wang Date: Fri, 5 Mar 2010 22:17:26 +0100 Subject: hwmon: Remove the deprecated adt7473 driver adt7473 driver is obsoleted by adt7475 driver. And it is scheduled to be removed in Feb 2010. Signed-off-by: WANG Cong Signed-off-by: Jean Delvare --- Documentation/feature-removal-schedule.txt | 6 - Documentation/hwmon/adt7473 | 74 -- drivers/hwmon/Kconfig | 14 - drivers/hwmon/Makefile | 1 - drivers/hwmon/adt7473.c | 1180 ---------------------------- 5 files changed, 1275 deletions(-) delete mode 100644 Documentation/hwmon/adt7473 delete mode 100644 drivers/hwmon/adt7473.c (limited to 'Documentation') diff --git a/Documentation/feature-removal-schedule.txt b/Documentation/feature-removal-schedule.txt index 03497909539..8debdd625e1 100644 --- a/Documentation/feature-removal-schedule.txt +++ b/Documentation/feature-removal-schedule.txt @@ -449,12 +449,6 @@ Who: Alok N Kataria ---------------------------- -What: adt7473 hardware monitoring driver -When: February 2010 -Why: Obsoleted by the adt7475 driver. -Who: Jean Delvare - ---------------------------- What: Support for lcd_switch and display_get in asus-laptop driver When: March 2010 Why: These two features use non-standard interfaces. There are the diff --git a/Documentation/hwmon/adt7473 b/Documentation/hwmon/adt7473 deleted file mode 100644 index 446612bd1fb..00000000000 --- a/Documentation/hwmon/adt7473 +++ /dev/null @@ -1,74 +0,0 @@ -Kernel driver adt7473 -====================== - -Supported chips: - * Analog Devices ADT7473 - Prefix: 'adt7473' - Addresses scanned: I2C 0x2C, 0x2D, 0x2E - Datasheet: Publicly available at the Analog Devices website - -Author: Darrick J. Wong - -This driver is depreacted, please use the adt7475 driver instead. - -Description ------------ - -This driver implements support for the Analog Devices ADT7473 chip family. - -The ADT7473 uses the 2-wire interface compatible with the SMBUS 2.0 -specification. Using an analog to digital converter it measures three (3) -temperatures and two (2) voltages. It has four (4) 16-bit counters for -measuring fan speed. There are three (3) PWM outputs that can be used -to control fan speed. - -A sophisticated control system for the PWM outputs is designed into the -ADT7473 that allows fan speed to be adjusted automatically based on any of the -three temperature sensors. Each PWM output is individually adjustable and -programmable. Once configured, the ADT7473 will adjust the PWM outputs in -response to the measured temperatures without further host intervention. -This feature can also be disabled for manual control of the PWM's. - -Each of the measured inputs (voltage, temperature, fan speed) has -corresponding high/low limit values. The ADT7473 will signal an ALARM if -any measured value exceeds either limit. - -The ADT7473 samples all inputs continuously. The driver will not read -the registers more often than once every other second. Further, -configuration data is only read once per minute. - -Special Features ----------------- - -The ADT7473 have a 10-bit ADC and can therefore measure temperatures -with 0.25 degC resolution. Temperature readings can be configured either -for twos complement format or "Offset 64" format, wherein 63 is subtracted -from the raw value to get the temperature value. - -The Analog Devices datasheet is very detailed and describes a procedure for -determining an optimal configuration for the automatic PWM control. - -Configuration Notes -------------------- - -Besides standard interfaces driver adds the following: - -* PWM Control - -* pwm#_auto_point1_pwm and temp#_auto_point1_temp and -* pwm#_auto_point2_pwm and temp#_auto_point2_temp - - -point1: Set the pwm speed at a lower temperature bound. -point2: Set the pwm speed at a higher temperature bound. - -The ADT7473 will scale the pwm between the lower and higher pwm speed when -the temperature is between the two temperature boundaries. PWM values range -from 0 (off) to 255 (full speed). Fan speed will be set to maximum when the -temperature sensor associated with the PWM control exceeds temp#_max. - -Notes ------ - -The NVIDIA binary driver presents an ADT7473 chip via an on-card i2c bus. -Unfortunately, they fail to set the i2c adapter class, so this driver may -fail to find the chip until the nvidia driver is patched. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index b6d65aa2082..e4595e6147b 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -200,20 +200,6 @@ config SENSORS_ADT7470 This driver can also be built as a module. If so, the module will be called adt7470. -config SENSORS_ADT7473 - tristate "Analog Devices ADT7473 (DEPRECATED)" - depends on I2C && EXPERIMENTAL - select SENSORS_ADT7475 - help - If you say yes here you get support for the Analog Devices - ADT7473 temperature monitoring chips. - - This driver is deprecated, you should use the adt7475 driver - instead. - - This driver can also be built as a module. If so, the module - will be called adt7473. - config SENSORS_ADT7475 tristate "Analog Devices ADT7473, ADT7475, ADT7476 and ADT7490" depends on I2C && EXPERIMENTAL diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 865da80f2b9..4aa1a3d112a 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -32,7 +32,6 @@ obj-$(CONFIG_SENSORS_ADS7828) += ads7828.o obj-$(CONFIG_SENSORS_ADT7411) += adt7411.o obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o -obj-$(CONFIG_SENSORS_ADT7473) += adt7473.o obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o obj-$(CONFIG_SENSORS_AMS) += ams/ diff --git a/drivers/hwmon/adt7473.c b/drivers/hwmon/adt7473.c deleted file mode 100644 index 434576f61c8..00000000000 --- a/drivers/hwmon/adt7473.c +++ /dev/null @@ -1,1180 +0,0 @@ -/* - * A hwmon driver for the Analog Devices ADT7473 - * Copyright (C) 2007 IBM - * - * Author: Darrick J. Wong - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Addresses to scan */ -static const unsigned short normal_i2c[] = { 0x2C, 0x2D, 0x2E, I2C_CLIENT_END }; - -/* ADT7473 registers */ -#define ADT7473_REG_BASE_ADDR 0x20 - -#define ADT7473_REG_VOLT_BASE_ADDR 0x21 -#define ADT7473_REG_VOLT_MIN_BASE_ADDR 0x46 - -#define ADT7473_REG_TEMP_BASE_ADDR 0x25 -#define ADT7473_REG_TEMP_LIMITS_BASE_ADDR 0x4E -#define ADT7473_REG_TEMP_TMIN_BASE_ADDR 0x67 -#define ADT7473_REG_TEMP_TMAX_BASE_ADDR 0x6A - -#define ADT7473_REG_FAN_BASE_ADDR 0x28 -#define ADT7473_REG_FAN_MIN_BASE_ADDR 0x54 - -#define ADT7473_REG_PWM_BASE_ADDR 0x30 -#define ADT7473_REG_PWM_MIN_BASE_ADDR 0x64 -#define ADT7473_REG_PWM_MAX_BASE_ADDR 0x38 -#define ADT7473_REG_PWM_BHVR_BASE_ADDR 0x5C -#define ADT7473_PWM_BHVR_MASK 0xE0 -#define ADT7473_PWM_BHVR_SHIFT 5 - -#define ADT7473_REG_CFG1 0x40 -#define ADT7473_CFG1_START 0x01 -#define ADT7473_CFG1_READY 0x04 -#define ADT7473_REG_CFG2 0x73 -#define ADT7473_REG_CFG3 0x78 -#define ADT7473_REG_CFG4 0x7D -#define ADT7473_CFG4_MAX_DUTY_AT_OVT 0x08 -#define ADT7473_REG_CFG5 0x7C -#define ADT7473_CFG5_TEMP_TWOS 0x01 -#define ADT7473_CFG5_TEMP_OFFSET 0x02 - -#define ADT7473_REG_DEVICE 0x3D -#define ADT7473_VENDOR 0x41 -#define ADT7473_REG_VENDOR 0x3E -#define ADT7473_DEVICE 0x73 -#define ADT7473_REG_REVISION 0x3F -#define ADT7473_REV_68 0x68 -#define ADT7473_REV_69 0x69 - -#define ADT7473_REG_ALARM1 0x41 -#define ADT7473_VCCP_ALARM 0x02 -#define ADT7473_VCC_ALARM 0x04 -#define ADT7473_R1T_ALARM 0x10 -#define ADT7473_LT_ALARM 0x20 -#define ADT7473_R2T_ALARM 0x40 -#define ADT7473_OOL 0x80 -#define ADT7473_REG_ALARM2 0x42 -#define ADT7473_OVT_ALARM 0x02 -#define ADT7473_FAN1_ALARM 0x04 -#define ADT7473_FAN2_ALARM 0x08 -#define ADT7473_FAN3_ALARM 0x10 -#define ADT7473_FAN4_ALARM 0x20 -#define ADT7473_R1T_SHORT 0x40 -#define ADT7473_R2T_SHORT 0x80 - -#define ALARM2(x) ((x) << 8) - -#define ADT7473_VOLT_COUNT 2 -#define ADT7473_REG_VOLT(x) (ADT7473_REG_VOLT_BASE_ADDR + (x)) -#define ADT7473_REG_VOLT_MIN(x) (ADT7473_REG_VOLT_MIN_BASE_ADDR + ((x) * 2)) -#define ADT7473_REG_VOLT_MAX(x) (ADT7473_REG_VOLT_MIN_BASE_ADDR + \ - ((x) * 2) + 1) - -#define ADT7473_TEMP_COUNT 3 -#define ADT7473_REG_TEMP(x) (ADT7473_REG_TEMP_BASE_ADDR + (x)) -#define ADT7473_REG_TEMP_MIN(x) (ADT7473_REG_TEMP_LIMITS_BASE_ADDR + ((x) * 2)) -#define ADT7473_REG_TEMP_MAX(x) (ADT7473_REG_TEMP_LIMITS_BASE_ADDR + \ - ((x) * 2) + 1) -#define ADT7473_REG_TEMP_TMIN(x) (ADT7473_REG_TEMP_TMIN_BASE_ADDR + (x)) -#define ADT7473_REG_TEMP_TMAX(x) (ADT7473_REG_TEMP_TMAX_BASE_ADDR + (x)) - -#define ADT7473_FAN_COUNT 4 -#define ADT7473_REG_FAN(x) (ADT7473_REG_FAN_BASE_ADDR + ((x) * 2)) -#define ADT7473_REG_FAN_MIN(x) (ADT7473_REG_FAN_MIN_BASE_ADDR + ((x) * 2)) - -#define ADT7473_PWM_COUNT 3 -#define ADT7473_REG_PWM(x) (ADT7473_REG_PWM_BASE_ADDR + (x)) -#define ADT7473_REG_PWM_MAX(x) (ADT7473_REG_PWM_MAX_BASE_ADDR + (x)) -#define ADT7473_REG_PWM_MIN(x) (ADT7473_REG_PWM_MIN_BASE_ADDR + (x)) -#define ADT7473_REG_PWM_BHVR(x) (ADT7473_REG_PWM_BHVR_BASE_ADDR + (x)) - -/* How often do we reread sensors values? (In jiffies) */ -#define SENSOR_REFRESH_INTERVAL (2 * HZ) - -/* How often do we reread sensor limit values? (In jiffies) */ -#define LIMIT_REFRESH_INTERVAL (60 * HZ) - -/* datasheet says to divide this number by the fan reading to get fan rpm */ -#define FAN_PERIOD_TO_RPM(x) ((90000 * 60) / (x)) -#define FAN_RPM_TO_PERIOD FAN_PERIOD_TO_RPM -#define FAN_PERIOD_INVALID 65535 -#define FAN_DATA_VALID(x) ((x) && (x) != FAN_PERIOD_INVALID) - -struct adt7473_data { - struct device *hwmon_dev; - struct attribute_group attrs; - struct mutex lock; - char sensors_valid; - char limits_valid; - unsigned long sensors_last_updated; /* In jiffies */ - unsigned long limits_last_updated; /* In jiffies */ - - u8 volt[ADT7473_VOLT_COUNT]; - s8 volt_min[ADT7473_VOLT_COUNT]; - s8 volt_max[ADT7473_VOLT_COUNT]; - - s8 temp[ADT7473_TEMP_COUNT]; - s8 temp_min[ADT7473_TEMP_COUNT]; - s8 temp_max[ADT7473_TEMP_COUNT]; - s8 temp_tmin[ADT7473_TEMP_COUNT]; - /* This is called the !THERM limit in the datasheet */ - s8 temp_tmax[ADT7473_TEMP_COUNT]; - - u16 fan[ADT7473_FAN_COUNT]; - u16 fan_min[ADT7473_FAN_COUNT]; - - u8 pwm[ADT7473_PWM_COUNT]; - u8 pwm_max[ADT7473_PWM_COUNT]; - u8 pwm_min[ADT7473_PWM_COUNT]; - u8 pwm_behavior[ADT7473_PWM_COUNT]; - - u8 temp_twos_complement; - u8 temp_offset; - - u16 alarm; - u8 max_duty_at_overheat; -}; - -static int adt7473_probe(struct i2c_client *client, - const struct i2c_device_id *id); -static int adt7473_detect(struct i2c_client *client, - struct i2c_board_info *info); -static int adt7473_remove(struct i2c_client *client); - -static const struct i2c_device_id adt7473_id[] = { - { "adt7473", 0 }, - { } -}; - -static struct i2c_driver adt7473_driver = { - .class = I2C_CLASS_HWMON, - .driver = { - .name = "adt7473", - }, - .probe = adt7473_probe, - .remove = adt7473_remove, - .id_table = adt7473_id, - .detect = adt7473_detect, - .address_list = normal_i2c, -}; - -/* - * 16-bit registers on the ADT7473 are low-byte first. The data sheet says - * that the low byte must be read before the high byte. - */ -static inline int adt7473_read_word_data(struct i2c_client *client, u8 reg) -{ - u16 foo; - foo = i2c_smbus_read_byte_data(client, reg); - foo |= ((u16)i2c_smbus_read_byte_data(client, reg + 1) << 8); - return foo; -} - -static inline int adt7473_write_word_data(struct i2c_client *client, u8 reg, - u16 value) -{ - return i2c_smbus_write_byte_data(client, reg, value & 0xFF) - && i2c_smbus_write_byte_data(client, reg + 1, value >> 8); -} - -static void adt7473_init_client(struct i2c_client *client) -{ - int reg = i2c_smbus_read_byte_data(client, ADT7473_REG_CFG1); - - if (!(reg & ADT7473_CFG1_READY)) { - dev_err(&client->dev, "Chip not ready.\n"); - } else { - /* start monitoring */ - i2c_smbus_write_byte_data(client, ADT7473_REG_CFG1, - reg | ADT7473_CFG1_START); - } -} - -static struct adt7473_data *adt7473_update_device(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - unsigned long local_jiffies = jiffies; - u8 cfg; - int i; - - mutex_lock(&data->lock); - if (time_before(local_jiffies, data->sensors_last_updated + - SENSOR_REFRESH_INTERVAL) - && data->sensors_valid) - goto no_sensor_update; - - for (i = 0; i < ADT7473_VOLT_COUNT; i++) - data->volt[i] = i2c_smbus_read_byte_data(client, - ADT7473_REG_VOLT(i)); - - /* Determine temperature encoding */ - cfg = i2c_smbus_read_byte_data(client, ADT7473_REG_CFG5); - data->temp_twos_complement = (cfg & ADT7473_CFG5_TEMP_TWOS); - - /* - * What does this do? it implies a variable temperature sensor - * offset, but the datasheet doesn't say anything about this bit - * and other parts of the datasheet imply that "offset64" mode - * means that you shift temp values by -64 if the above bit was set. - */ - data->temp_offset = (cfg & ADT7473_CFG5_TEMP_OFFSET); - - for (i = 0; i < ADT7473_TEMP_COUNT; i++) - data->temp[i] = i2c_smbus_read_byte_data(client, - ADT7473_REG_TEMP(i)); - - for (i = 0; i < ADT7473_FAN_COUNT; i++) - data->fan[i] = adt7473_read_word_data(client, - ADT7473_REG_FAN(i)); - - for (i = 0; i < ADT7473_PWM_COUNT; i++) - data->pwm[i] = i2c_smbus_read_byte_data(client, - ADT7473_REG_PWM(i)); - - data->alarm = i2c_smbus_read_byte_data(client, ADT7473_REG_ALARM1); - if (data->alarm & ADT7473_OOL) - data->alarm |= ALARM2(i2c_smbus_read_byte_data(client, - ADT7473_REG_ALARM2)); - - data->sensors_last_updated = local_jiffies; - data->sensors_valid = 1; - -no_sensor_update: - if (time_before(local_jiffies, data->limits_last_updated + - LIMIT_REFRESH_INTERVAL) - && data->limits_valid) - goto out; - - for (i = 0; i < ADT7473_VOLT_COUNT; i++) { - data->volt_min[i] = i2c_smbus_read_byte_data(client, - ADT7473_REG_VOLT_MIN(i)); - data->volt_max[i] = i2c_smbus_read_byte_data(client, - ADT7473_REG_VOLT_MAX(i)); - } - - for (i = 0; i < ADT7473_TEMP_COUNT; i++) { - data->temp_min[i] = i2c_smbus_read_byte_data(client, - ADT7473_REG_TEMP_MIN(i)); - data->temp_max[i] = i2c_smbus_read_byte_data(client, - ADT7473_REG_TEMP_MAX(i)); - data->temp_tmin[i] = i2c_smbus_read_byte_data(client, - ADT7473_REG_TEMP_TMIN(i)); - data->temp_tmax[i] = i2c_smbus_read_byte_data(client, - ADT7473_REG_TEMP_TMAX(i)); - } - - for (i = 0; i < ADT7473_FAN_COUNT; i++) - data->fan_min[i] = adt7473_read_word_data(client, - ADT7473_REG_FAN_MIN(i)); - - for (i = 0; i < ADT7473_PWM_COUNT; i++) { - data->pwm_max[i] = i2c_smbus_read_byte_data(client, - ADT7473_REG_PWM_MAX(i)); - data->pwm_min[i] = i2c_smbus_read_byte_data(client, - ADT7473_REG_PWM_MIN(i)); - data->pwm_behavior[i] = i2c_smbus_read_byte_data(client, - ADT7473_REG_PWM_BHVR(i)); - } - - i = i2c_smbus_read_byte_data(client, ADT7473_REG_CFG4); - data->max_duty_at_overheat = !!(i & ADT7473_CFG4_MAX_DUTY_AT_OVT); - - data->limits_last_updated = local_jiffies; - data->limits_valid = 1; - -out: - mutex_unlock(&data->lock); - return data; -} - -/* - * Conversions - */ - -/* IN are scaled acording to built-in resistors */ -static const int adt7473_scaling[] = { /* .001 Volts */ - 2250, 3300 -}; -#define SCALE(val, from, to) (((val) * (to) + ((from) / 2)) / (from)) - -static int decode_volt(int volt_index, u8 raw) -{ - return SCALE(raw, 192, adt7473_scaling[volt_index]); -} - -static u8 encode_volt(int volt_index, int cooked) -{ - int raw = SCALE(cooked, adt7473_scaling[volt_index], 192); - return SENSORS_LIMIT(raw, 0, 255); -} - -static ssize_t show_volt_min(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - return sprintf(buf, "%d\n", - decode_volt(attr->index, data->volt_min[attr->index])); -} - -static ssize_t set_volt_min(struct device *dev, - struct device_attribute *devattr, - const char *buf, - size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long volt; - - if (strict_strtol(buf, 10, &volt)) - return -EINVAL; - - volt = encode_volt(attr->index, volt); - - mutex_lock(&data->lock); - data->volt_min[attr->index] = volt; - i2c_smbus_write_byte_data(client, ADT7473_REG_VOLT_MIN(attr->index), - volt); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_volt_max(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - return sprintf(buf, "%d\n", - decode_volt(attr->index, data->volt_max[attr->index])); -} - -static ssize_t set_volt_max(struct device *dev, - struct device_attribute *devattr, - const char *buf, - size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long volt; - - if (strict_strtol(buf, 10, &volt)) - return -EINVAL; - - volt = encode_volt(attr->index, volt); - - mutex_lock(&data->lock); - data->volt_max[attr->index] = volt; - i2c_smbus_write_byte_data(client, ADT7473_REG_VOLT_MAX(attr->index), - volt); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_volt(struct device *dev, struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - - return sprintf(buf, "%d\n", - decode_volt(attr->index, data->volt[attr->index])); -} - -/* - * This chip can report temperature data either as a two's complement - * number in the range -128 to 127, or as an unsigned number that must - * be offset by 64. - */ -static int decode_temp(u8 twos_complement, u8 raw) -{ - return twos_complement ? (s8)raw : raw - 64; -} - -static u8 encode_temp(u8 twos_complement, int cooked) -{ - u8 ret = twos_complement ? cooked & 0xFF : cooked + 64; - return SENSORS_LIMIT(ret, 0, 255); -} - -static ssize_t show_temp_min(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - return sprintf(buf, "%d\n", 1000 * decode_temp( - data->temp_twos_complement, - data->temp_min[attr->index])); -} - -static ssize_t set_temp_min(struct device *dev, - struct device_attribute *devattr, - const char *buf, - size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long temp; - - if (strict_strtol(buf, 10, &temp)) - return -EINVAL; - - temp = DIV_ROUND_CLOSEST(temp, 1000); - temp = encode_temp(data->temp_twos_complement, temp); - - mutex_lock(&data->lock); - data->temp_min[attr->index] = temp; - i2c_smbus_write_byte_data(client, ADT7473_REG_TEMP_MIN(attr->index), - temp); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_temp_max(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - return sprintf(buf, "%d\n", 1000 * decode_temp( - data->temp_twos_complement, - data->temp_max[attr->index])); -} - -static ssize_t set_temp_max(struct device *dev, - struct device_attribute *devattr, - const char *buf, - size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long temp; - - if (strict_strtol(buf, 10, &temp)) - return -EINVAL; - - temp = DIV_ROUND_CLOSEST(temp, 1000); - temp = encode_temp(data->temp_twos_complement, temp); - - mutex_lock(&data->lock); - data->temp_max[attr->index] = temp; - i2c_smbus_write_byte_data(client, ADT7473_REG_TEMP_MAX(attr->index), - temp); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_temp(struct device *dev, struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - return sprintf(buf, "%d\n", 1000 * decode_temp( - data->temp_twos_complement, - data->temp[attr->index])); -} - -static ssize_t show_fan_min(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - - if (FAN_DATA_VALID(data->fan_min[attr->index])) - return sprintf(buf, "%d\n", - FAN_PERIOD_TO_RPM(data->fan_min[attr->index])); - else - return sprintf(buf, "0\n"); -} - -static ssize_t set_fan_min(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long temp; - - if (strict_strtol(buf, 10, &temp) || !temp) - return -EINVAL; - - temp = FAN_RPM_TO_PERIOD(temp); - temp = SENSORS_LIMIT(temp, 1, 65534); - - mutex_lock(&data->lock); - data->fan_min[attr->index] = temp; - adt7473_write_word_data(client, ADT7473_REG_FAN_MIN(attr->index), temp); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_fan(struct device *dev, struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - - if (FAN_DATA_VALID(data->fan[attr->index])) - return sprintf(buf, "%d\n", - FAN_PERIOD_TO_RPM(data->fan[attr->index])); - else - return sprintf(buf, "0\n"); -} - -static ssize_t show_max_duty_at_crit(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct adt7473_data *data = adt7473_update_device(dev); - return sprintf(buf, "%d\n", data->max_duty_at_overheat); -} - -static ssize_t set_max_duty_at_crit(struct device *dev, - struct device_attribute *devattr, - const char *buf, - size_t count) -{ - u8 reg; - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long temp; - - if (strict_strtol(buf, 10, &temp)) - return -EINVAL; - - mutex_lock(&data->lock); - data->max_duty_at_overheat = !!temp; - reg = i2c_smbus_read_byte_data(client, ADT7473_REG_CFG4); - if (temp) - reg |= ADT7473_CFG4_MAX_DUTY_AT_OVT; - else - reg &= ~ADT7473_CFG4_MAX_DUTY_AT_OVT; - i2c_smbus_write_byte_data(client, ADT7473_REG_CFG4, reg); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_pwm(struct device *dev, struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - return sprintf(buf, "%d\n", data->pwm[attr->index]); -} - -static ssize_t set_pwm(struct device *dev, struct device_attribute *devattr, - const char *buf, size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long temp; - - if (strict_strtol(buf, 10, &temp)) - return -EINVAL; - - temp = SENSORS_LIMIT(temp, 0, 255); - - mutex_lock(&data->lock); - data->pwm[attr->index] = temp; - i2c_smbus_write_byte_data(client, ADT7473_REG_PWM(attr->index), temp); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_pwm_max(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - return sprintf(buf, "%d\n", data->pwm_max[attr->index]); -} - -static ssize_t set_pwm_max(struct device *dev, - struct device_attribute *devattr, - const char *buf, - size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long temp; - - if (strict_strtol(buf, 10, &temp)) - return -EINVAL; - - temp = SENSORS_LIMIT(temp, 0, 255); - - mutex_lock(&data->lock); - data->pwm_max[attr->index] = temp; - i2c_smbus_write_byte_data(client, ADT7473_REG_PWM_MAX(attr->index), - temp); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_pwm_min(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - return sprintf(buf, "%d\n", data->pwm_min[attr->index]); -} - -static ssize_t set_pwm_min(struct device *dev, - struct device_attribute *devattr, - const char *buf, - size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long temp; - - if (strict_strtol(buf, 10, &temp)) - return -EINVAL; - - temp = SENSORS_LIMIT(temp, 0, 255); - - mutex_lock(&data->lock); - data->pwm_min[attr->index] = temp; - i2c_smbus_write_byte_data(client, ADT7473_REG_PWM_MIN(attr->index), - temp); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_temp_tmax(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - return sprintf(buf, "%d\n", 1000 * decode_temp( - data->temp_twos_complement, - data->temp_tmax[attr->index])); -} - -static ssize_t set_temp_tmax(struct device *dev, - struct device_attribute *devattr, - const char *buf, - size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long temp; - - if (strict_strtol(buf, 10, &temp)) - return -EINVAL; - - temp = DIV_ROUND_CLOSEST(temp, 1000); - temp = encode_temp(data->temp_twos_complement, temp); - - mutex_lock(&data->lock); - data->temp_tmax[attr->index] = temp; - i2c_smbus_write_byte_data(client, ADT7473_REG_TEMP_TMAX(attr->index), - temp); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_temp_tmin(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - return sprintf(buf, "%d\n", 1000 * decode_temp( - data->temp_twos_complement, - data->temp_tmin[attr->index])); -} - -static ssize_t set_temp_tmin(struct device *dev, - struct device_attribute *devattr, - const char *buf, - size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long temp; - - if (strict_strtol(buf, 10, &temp)) - return -EINVAL; - - temp = DIV_ROUND_CLOSEST(temp, 1000); - temp = encode_temp(data->temp_twos_complement, temp); - - mutex_lock(&data->lock); - data->temp_tmin[attr->index] = temp; - i2c_smbus_write_byte_data(client, ADT7473_REG_TEMP_TMIN(attr->index), - temp); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_pwm_enable(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - - switch (data->pwm_behavior[attr->index] >> ADT7473_PWM_BHVR_SHIFT) { - case 3: - return sprintf(buf, "0\n"); - case 7: - return sprintf(buf, "1\n"); - default: - return sprintf(buf, "2\n"); - } -} - -static ssize_t set_pwm_enable(struct device *dev, - struct device_attribute *devattr, - const char *buf, - size_t count) -{ - u8 reg; - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long temp; - - if (strict_strtol(buf, 10, &temp)) - return -EINVAL; - - switch (temp) { - case 0: - temp = 3; - break; - case 1: - temp = 7; - break; - case 2: - /* Enter automatic mode with fans off */ - temp = 4; - break; - default: - return -EINVAL; - } - - mutex_lock(&data->lock); - reg = i2c_smbus_read_byte_data(client, - ADT7473_REG_PWM_BHVR(attr->index)); - reg = (temp << ADT7473_PWM_BHVR_SHIFT) | - (reg & ~ADT7473_PWM_BHVR_MASK); - i2c_smbus_write_byte_data(client, ADT7473_REG_PWM_BHVR(attr->index), - reg); - data->pwm_behavior[attr->index] = reg; - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_pwm_auto_temp(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - int bhvr = data->pwm_behavior[attr->index] >> ADT7473_PWM_BHVR_SHIFT; - - switch (bhvr) { - case 3: - case 4: - case 7: - return sprintf(buf, "0\n"); - case 0: - case 1: - case 5: - case 6: - return sprintf(buf, "%d\n", bhvr + 1); - case 2: - return sprintf(buf, "4\n"); - } - /* shouldn't ever get here */ - BUG(); -} - -static ssize_t set_pwm_auto_temp(struct device *dev, - struct device_attribute *devattr, - const char *buf, - size_t count) -{ - u8 reg; - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long temp; - - if (strict_strtol(buf, 10, &temp)) - return -EINVAL; - - switch (temp) { - case 1: - case 2: - case 6: - case 7: - temp--; - break; - case 0: - temp = 4; - break; - default: - return -EINVAL; - } - - mutex_lock(&data->lock); - reg = i2c_smbus_read_byte_data(client, - ADT7473_REG_PWM_BHVR(attr->index)); - reg = (temp << ADT7473_PWM_BHVR_SHIFT) | - (reg & ~ADT7473_PWM_BHVR_MASK); - i2c_smbus_write_byte_data(client, ADT7473_REG_PWM_BHVR(attr->index), - reg); - data->pwm_behavior[attr->index] = reg; - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_alarm(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - - if (data->alarm & attr->index) - return sprintf(buf, "1\n"); - else - return sprintf(buf, "0\n"); -} - - -static SENSOR_DEVICE_ATTR(in1_max, S_IWUSR | S_IRUGO, show_volt_max, - set_volt_max, 0); -static SENSOR_DEVICE_ATTR(in2_max, S_IWUSR | S_IRUGO, show_volt_max, - set_volt_max, 1); - -static SENSOR_DEVICE_ATTR(in1_min, S_IWUSR | S_IRUGO, show_volt_min, - set_volt_min, 0); -static SENSOR_DEVICE_ATTR(in2_min, S_IWUSR | S_IRUGO, show_volt_min, - set_volt_min, 1); - -static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_volt, NULL, 0); -static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_volt, NULL, 1); - -static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, - ADT7473_VCCP_ALARM); -static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, - ADT7473_VCC_ALARM); - -static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp_max, - set_temp_max, 0); -static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_temp_max, - set_temp_max, 1); -static SENSOR_DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, show_temp_max, - set_temp_max, 2); - -static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_temp_min, - set_temp_min, 0); -static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_temp_min, - set_temp_min, 1); -static SENSOR_DEVICE_ATTR(temp3_min, S_IWUSR | S_IRUGO, show_temp_min, - set_temp_min, 2); - -static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); -static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1); -static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2); - -static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, - ADT7473_R1T_ALARM | ALARM2(ADT7473_R1T_SHORT)); -static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, - ADT7473_LT_ALARM); -static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, - ADT7473_R2T_ALARM | ALARM2(ADT7473_R2T_SHORT)); - -static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan_min, - set_fan_min, 0); -static SENSOR_DEVICE_ATTR(fan2_min, S_IWUSR | S_IRUGO, show_fan_min, - set_fan_min, 1); -static SENSOR_DEVICE_ATTR(fan3_min, S_IWUSR | S_IRUGO, show_fan_min, - set_fan_min, 2); -static SENSOR_DEVICE_ATTR(fan4_min, S_IWUSR | S_IRUGO, show_fan_min, - set_fan_min, 3); - -static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0); -static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1); -static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2); -static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3); - -static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, - ALARM2(ADT7473_FAN1_ALARM)); -static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, - ALARM2(ADT7473_FAN2_ALARM)); -static SENSOR_DEVICE_ATTR(fan3_alarm, S_IRUGO, show_alarm, NULL, - ALARM2(ADT7473_FAN3_ALARM)); -static SENSOR_DEVICE_ATTR(fan4_alarm, S_IRUGO, show_alarm, NULL, - ALARM2(ADT7473_FAN4_ALARM)); - -static SENSOR_DEVICE_ATTR(pwm_use_point2_pwm_at_crit, S_IWUSR | S_IRUGO, - show_max_duty_at_crit, set_max_duty_at_crit, 0); - -static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 0); -static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 1); -static SENSOR_DEVICE_ATTR(pwm3, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 2); - -static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IWUSR | S_IRUGO, - show_pwm_min, set_pwm_min, 0); -static SENSOR_DEVICE_ATTR(pwm2_auto_point1_pwm, S_IWUSR | S_IRUGO, - show_pwm_min, set_pwm_min, 1); -static SENSOR_DEVICE_ATTR(pwm3_auto_point1_pwm, S_IWUSR | S_IRUGO, - show_pwm_min, set_pwm_min, 2); - -static SENSOR_DEVICE_ATTR(pwm1_auto_point2_pwm, S_IWUSR | S_IRUGO, - show_pwm_max, set_pwm_max, 0); -static SENSOR_DEVICE_ATTR(pwm2_auto_point2_pwm, S_IWUSR | S_IRUGO, - show_pwm_max, set_pwm_max, 1); -static SENSOR_DEVICE_ATTR(pwm3_auto_point2_pwm, S_IWUSR | S_IRUGO, - show_pwm_max, set_pwm_max, 2); - -static SENSOR_DEVICE_ATTR(temp1_auto_point1_temp, S_IWUSR | S_IRUGO, - show_temp_tmin, set_temp_tmin, 0); -static SENSOR_DEVICE_ATTR(temp2_auto_point1_temp, S_IWUSR | S_IRUGO, - show_temp_tmin, set_temp_tmin, 1); -static SENSOR_DEVICE_ATTR(temp3_auto_point1_temp, S_IWUSR | S_IRUGO, - show_temp_tmin, set_temp_tmin, 2); - -static SENSOR_DEVICE_ATTR(temp1_auto_point2_temp, S_IWUSR | S_IRUGO, - show_temp_tmax, set_temp_tmax, 0); -static SENSOR_DEVICE_ATTR(temp2_auto_point2_temp, S_IWUSR | S_IRUGO, - show_temp_tmax, set_temp_tmax, 1); -static SENSOR_DEVICE_ATTR(temp3_auto_point2_temp, S_IWUSR | S_IRUGO, - show_temp_tmax, set_temp_tmax, 2); - -static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, show_pwm_enable, - set_pwm_enable, 0); -static SENSOR_DEVICE_ATTR(pwm2_enable, S_IWUSR | S_IRUGO, show_pwm_enable, - set_pwm_enable, 1); -static SENSOR_DEVICE_ATTR(pwm3_enable, S_IWUSR | S_IRUGO, show_pwm_enable, - set_pwm_enable, 2); - -static SENSOR_DEVICE_ATTR(pwm1_auto_channels_temp, S_IWUSR | S_IRUGO, - show_pwm_auto_temp, set_pwm_auto_temp, 0); -static SENSOR_DEVICE_ATTR(pwm2_auto_channels_temp, S_IWUSR | S_IRUGO, - show_pwm_auto_temp, set_pwm_auto_temp, 1); -static SENSOR_DEVICE_ATTR(pwm3_auto_channels_temp, S_IWUSR | S_IRUGO, - show_pwm_auto_temp, set_pwm_auto_temp, 2); - -static struct attribute *adt7473_attr[] = -{ - &sensor_dev_attr_in1_max.dev_attr.attr, - &sensor_dev_attr_in2_max.dev_attr.attr, - &sensor_dev_attr_in1_min.dev_attr.attr, - &sensor_dev_attr_in2_min.dev_attr.attr, - &sensor_dev_attr_in1_input.dev_attr.attr, - &sensor_dev_attr_in2_input.dev_attr.attr, - &sensor_dev_attr_in1_alarm.dev_attr.attr, - &sensor_dev_attr_in2_alarm.dev_attr.attr, - - &sensor_dev_attr_temp1_max.dev_attr.attr, - &sensor_dev_attr_temp2_max.dev_attr.attr, - &sensor_dev_attr_temp3_max.dev_attr.attr, - &sensor_dev_attr_temp1_min.dev_attr.attr, - &sensor_dev_attr_temp2_min.dev_attr.attr, - &sensor_dev_attr_temp3_min.dev_attr.attr, - &sensor_dev_attr_temp1_input.dev_attr.attr, - &sensor_dev_attr_temp2_input.dev_attr.attr, - &sensor_dev_attr_temp3_input.dev_attr.attr, - &sensor_dev_attr_temp1_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_alarm.dev_attr.attr, - &sensor_dev_attr_temp3_alarm.dev_attr.attr, - &sensor_dev_attr_temp1_auto_point1_temp.dev_attr.attr, - &sensor_dev_attr_temp2_auto_point1_temp.dev_attr.attr, - &sensor_dev_attr_temp3_auto_point1_temp.dev_attr.attr, - &sensor_dev_attr_temp1_auto_point2_temp.dev_attr.attr, - &sensor_dev_attr_temp2_auto_point2_temp.dev_attr.attr, - &sensor_dev_attr_temp3_auto_point2_temp.dev_attr.attr, - - &sensor_dev_attr_fan1_min.dev_attr.attr, - &sensor_dev_attr_fan2_min.dev_attr.attr, - &sensor_dev_attr_fan3_min.dev_attr.attr, - &sensor_dev_attr_fan4_min.dev_attr.attr, - &sensor_dev_attr_fan1_input.dev_attr.attr, - &sensor_dev_attr_fan2_input.dev_attr.attr, - &sensor_dev_attr_fan3_input.dev_attr.attr, - &sensor_dev_attr_fan4_input.dev_attr.attr, - &sensor_dev_attr_fan1_alarm.dev_attr.attr, - &sensor_dev_attr_fan2_alarm.dev_attr.attr, - &sensor_dev_attr_fan3_alarm.dev_attr.attr, - &sensor_dev_attr_fan4_alarm.dev_attr.attr, - - &sensor_dev_attr_pwm_use_point2_pwm_at_crit.dev_attr.attr, - - &sensor_dev_attr_pwm1.dev_attr.attr, - &sensor_dev_attr_pwm2.dev_attr.attr, - &sensor_dev_attr_pwm3.dev_attr.attr, - &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, - &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr, - &sensor_dev_attr_pwm3_auto_point1_pwm.dev_attr.attr, - &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr, - &sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr, - &sensor_dev_attr_pwm3_auto_point2_pwm.dev_attr.attr, - - &sensor_dev_attr_pwm1_enable.dev_attr.attr, - &sensor_dev_attr_pwm2_enable.dev_attr.attr, - &sensor_dev_attr_pwm3_enable.dev_attr.attr, - &sensor_dev_attr_pwm1_auto_channels_temp.dev_attr.attr, - &sensor_dev_attr_pwm2_auto_channels_temp.dev_attr.attr, - &sensor_dev_attr_pwm3_auto_channels_temp.dev_attr.attr, - - NULL -}; - -/* Return 0 if detection is successful, -ENODEV otherwise */ -static int adt7473_detect(struct i2c_client *client, - struct i2c_board_info *info) -{ - struct i2c_adapter *adapter = client->adapter; - int vendor, device, revision; - - if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) - return -ENODEV; - - vendor = i2c_smbus_read_byte_data(client, ADT7473_REG_VENDOR); - if (vendor != ADT7473_VENDOR) - return -ENODEV; - - device = i2c_smbus_read_byte_data(client, ADT7473_REG_DEVICE); - if (device != ADT7473_DEVICE) - return -ENODEV; - - revision = i2c_smbus_read_byte_data(client, ADT7473_REG_REVISION); - if (revision != ADT7473_REV_68 && revision != ADT7473_REV_69) - return -ENODEV; - - strlcpy(info->type, "adt7473", I2C_NAME_SIZE); - - return 0; -} - -static int adt7473_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct adt7473_data *data; - int err; - - data = kzalloc(sizeof(struct adt7473_data), GFP_KERNEL); - if (!data) { - err = -ENOMEM; - goto exit; - } - - i2c_set_clientdata(client, data); - mutex_init(&data->lock); - - dev_info(&client->dev, "%s chip found\n", client->name); - - /* Initialize the ADT7473 chip */ - adt7473_init_client(client); - - /* Register sysfs hooks */ - data->attrs.attrs = adt7473_attr; - err = sysfs_create_group(&client->dev.kobj, &data->attrs); - if (err) - goto exit_free; - - data->hwmon_dev = hwmon_device_register(&client->dev); - if (IS_ERR(data->hwmon_dev)) { - err = PTR_ERR(data->hwmon_dev); - goto exit_remove; - } - - return 0; - -exit_remove: - sysfs_remove_group(&client->dev.kobj, &data->attrs); -exit_free: - kfree(data); -exit: - return err; -} - -static int adt7473_remove(struct i2c_client *client) -{ - struct adt7473_data *data = i2c_get_clientdata(client); - - hwmon_device_unregister(data->hwmon_dev); - sysfs_remove_group(&client->dev.kobj, &data->attrs); - kfree(data); - return 0; -} - -static int __init adt7473_init(void) -{ - pr_notice("The adt7473 driver is deprecated, please use the adt7475 " - "driver instead\n"); - return i2c_add_driver(&adt7473_driver); -} - -static void __exit adt7473_exit(void) -{ - i2c_del_driver(&adt7473_driver); -} - -MODULE_AUTHOR("Darrick J. Wong "); -MODULE_DESCRIPTION("ADT7473 driver"); -MODULE_LICENSE("GPL"); - -module_init(adt7473_init); -module_exit(adt7473_exit); -- cgit v1.2.3-70-g09d2