From df149d02ea8ee49cd14c6609cc7ef980d62dce80 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sat, 14 Aug 2010 21:08:47 +0200 Subject: hwmon: (k8temp) Adjust confusing if indentation Move the if(err) statement after the if into the if branch indicated by its indentation. The preceding if(err) test implies that err cannot be nonzero unless the if branch is taken. The semantic match that finds this problem is as follows: (http://coccinelle.lip6.fr/) // @r disable braces5@ position p1,p2; statement S1,S2; @@ ( if (...) { ... } | if (...) S1@p1 S2@p2 ) @script:python@ p1 << r.p1; p2 << r.p2; @@ if (p1[0].column == p2[0].column): cocci.print_main("branch",p4) cocci.print_secs("after",p5) // Signed-off-by: Julia Lawall Signed-off-by: Jean Delvare --- drivers/hwmon/k8temp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/k8temp.c b/drivers/hwmon/k8temp.c index 8bdf80d91598..b9bb3e0ca530 100644 --- a/drivers/hwmon/k8temp.c +++ b/drivers/hwmon/k8temp.c @@ -252,12 +252,13 @@ static int __devinit k8temp_probe(struct pci_dev *pdev, &sensor_dev_attr_temp3_input.dev_attr); if (err) goto exit_remove; - if (data->sensorsp & SEL_PLACE) + if (data->sensorsp & SEL_PLACE) { err = device_create_file(&pdev->dev, &sensor_dev_attr_temp4_input. dev_attr); if (err) goto exit_remove; + } } err = device_create_file(&pdev->dev, &dev_attr_name); -- cgit v1.2.2 From b9783dcebe952bf73449fe70a19ee4814adc81a0 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Sat, 14 Aug 2010 21:08:48 +0200 Subject: hwmon: (pc87360) Fix device resource declaration It's not OK to call platform_device_add_resources() multiple times in a row. Despite its name, this functions sets the resources, it doesn't add them. So we have to prepare an array with all the resources, and then call platform_device_add_resources() once. Before this fix, only the last I/O resource would be actually registered. The other I/O resources were leaked. Signed-off-by: Jean Delvare Cc: Jim Cromie Cc: stable@kernel.org --- drivers/hwmon/pc87360.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/drivers/hwmon/pc87360.c b/drivers/hwmon/pc87360.c index 4a64b85d4ec9..68e69a49633c 100644 --- a/drivers/hwmon/pc87360.c +++ b/drivers/hwmon/pc87360.c @@ -1610,11 +1610,8 @@ static struct pc87360_data *pc87360_update_device(struct device *dev) static int __init pc87360_device_add(unsigned short address) { - struct resource res = { - .name = "pc87360", - .flags = IORESOURCE_IO, - }; - int err, i; + struct resource res[3]; + int err, i, res_count; pdev = platform_device_alloc("pc87360", address); if (!pdev) { @@ -1623,22 +1620,28 @@ static int __init pc87360_device_add(unsigned short address) goto exit; } + memset(res, 0, 3 * sizeof(struct resource)); + res_count = 0; for (i = 0; i < 3; i++) { if (!extra_isa[i]) continue; - res.start = extra_isa[i]; - res.end = extra_isa[i] + PC87360_EXTENT - 1; + res[res_count].start = extra_isa[i]; + res[res_count].end = extra_isa[i] + PC87360_EXTENT - 1; + res[res_count].name = "pc87360", + res[res_count].flags = IORESOURCE_IO, - err = acpi_check_resource_conflict(&res); + err = acpi_check_resource_conflict(&res[res_count]); if (err) goto exit_device_put; - err = platform_device_add_resources(pdev, &res, 1); - if (err) { - printk(KERN_ERR "pc87360: Device resource[%d] " - "addition failed (%d)\n", i, err); - goto exit_device_put; - } + res_count++; + } + + err = platform_device_add_resources(pdev, res, res_count); + if (err) { + printk(KERN_ERR "pc87360: Device resources addition failed " + "(%d)\n", err); + goto exit_device_put; } err = platform_device_add(pdev); -- cgit v1.2.2 From 5950ec8d3e47a08ec0b678a0e0ba5d1b9b62dd8e Mon Sep 17 00:00:00 2001 From: "Ira W. Snyder" Date: Sat, 14 Aug 2010 21:08:49 +0200 Subject: hwmon: (ltc4245) Expose all GPIO pins as analog voltages Add support for exposing all GPIO pins as analog voltages. Though this is not an ideal use of the chip, some hardware engineers may decide that the LTC4245 meets their design requirements when studying the datasheet. The GPIO pins are sampled in round-robin fashion, meaning that a slow reader will see stale data. A userspace application can detect this, because it will get -EAGAIN when reading from a sysfs file which contains stale data. Users can choose to use this feature on a per-chip basis by using either platform data or the OF device tree (where applicable). Signed-off-by: Ira W. Snyder Signed-off-by: Jean Delvare --- Documentation/hwmon/ltc4245 | 24 +++++- drivers/hwmon/ltc4245.c | 177 +++++++++++++++++++++++++++++++++++++++++--- include/linux/i2c/ltc4245.h | 21 ++++++ 3 files changed, 211 insertions(+), 11 deletions(-) create mode 100644 include/linux/i2c/ltc4245.h diff --git a/Documentation/hwmon/ltc4245 b/Documentation/hwmon/ltc4245 index 86b5880d8502..b478b0864965 100644 --- a/Documentation/hwmon/ltc4245 +++ b/Documentation/hwmon/ltc4245 @@ -72,9 +72,31 @@ in6_min_alarm 5v output undervoltage alarm in7_min_alarm 3v output undervoltage alarm in8_min_alarm Vee (-12v) output undervoltage alarm -in9_input GPIO voltage data +in9_input GPIO voltage data (see note 1) +in10_input GPIO voltage data (see note 1) +in11_input GPIO voltage data (see note 1) power1_input 12v power usage (mW) power2_input 5v power usage (mW) power3_input 3v power usage (mW) power4_input Vee (-12v) power usage (mW) + + +Note 1 +------ + +If you have NOT configured the driver to sample all GPIO pins as analog +voltages, then the in10_input and in11_input sysfs attributes will not be +created. The driver will sample the GPIO pin that is currently connected to the +ADC as an analog voltage, and report the value in in9_input. + +If you have configured the driver to sample all GPIO pins as analog voltages, +then they will be sampled in round-robin fashion. If userspace reads too +slowly, -EAGAIN will be returned when you read the sysfs attribute containing +the sensor reading. + +The LTC4245 chip can be configured to sample all GPIO pins with two methods: +1) platform data -- see include/linux/i2c/ltc4245.h +2) OF device tree -- add the "ltc4245,use-extra-gpios" property to each chip + +The default mode of operation is to sample a single GPIO pin. diff --git a/drivers/hwmon/ltc4245.c b/drivers/hwmon/ltc4245.c index 21d201befc2c..659308329308 100644 --- a/drivers/hwmon/ltc4245.c +++ b/drivers/hwmon/ltc4245.c @@ -21,6 +21,7 @@ #include #include #include +#include /* Here are names of the chip's registers (a.k.a. commands) */ enum ltc4245_cmd { @@ -60,8 +61,72 @@ struct ltc4245_data { /* Voltage registers */ u8 vregs[0x0d]; + + /* GPIO ADC registers */ + bool use_extra_gpios; + int gpios[3]; }; +/* + * Update the readings from the GPIO pins. If the driver has been configured to + * sample all GPIO's as analog voltages, a round-robin sampling method is used. + * Otherwise, only the configured GPIO pin is sampled. + * + * LOCKING: must hold data->update_lock + */ +static void ltc4245_update_gpios(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ltc4245_data *data = i2c_get_clientdata(client); + u8 gpio_curr, gpio_next, gpio_reg; + int i; + + /* no extra gpio support, we're basically done */ + if (!data->use_extra_gpios) { + data->gpios[0] = data->vregs[LTC4245_GPIOADC - 0x10]; + return; + } + + /* + * If the last reading was too long ago, then we mark all old GPIO + * readings as stale by setting them to -EAGAIN + */ + if (time_after(jiffies, data->last_updated + 5 * HZ)) { + dev_dbg(&client->dev, "Marking GPIOs invalid\n"); + for (i = 0; i < ARRAY_SIZE(data->gpios); i++) + data->gpios[i] = -EAGAIN; + } + + /* + * Get the current GPIO pin + * + * The datasheet calls these GPIO[1-3], but we'll calculate the zero + * based array index instead, and call them GPIO[0-2]. This is much + * easier to think about. + */ + gpio_curr = (data->cregs[LTC4245_GPIO] & 0xc0) >> 6; + if (gpio_curr > 0) + gpio_curr -= 1; + + /* Read the GPIO voltage from the GPIOADC register */ + data->gpios[gpio_curr] = data->vregs[LTC4245_GPIOADC - 0x10]; + + /* Find the next GPIO pin to read */ + gpio_next = (gpio_curr + 1) % ARRAY_SIZE(data->gpios); + + /* + * Calculate the correct setting for the GPIO register so it will + * sample the next GPIO pin + */ + gpio_reg = (data->cregs[LTC4245_GPIO] & 0x3f) | ((gpio_next + 1) << 6); + + /* Update the GPIO register */ + i2c_smbus_write_byte_data(client, LTC4245_GPIO, gpio_reg); + + /* Update saved data */ + data->cregs[LTC4245_GPIO] = gpio_reg; +} + static struct ltc4245_data *ltc4245_update_device(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); @@ -93,6 +158,9 @@ static struct ltc4245_data *ltc4245_update_device(struct device *dev) data->vregs[i] = val; } + /* Update GPIO readings */ + ltc4245_update_gpios(dev); + data->last_updated = jiffies; data->valid = 1; } @@ -233,6 +301,22 @@ static ssize_t ltc4245_show_alarm(struct device *dev, return snprintf(buf, PAGE_SIZE, "%u\n", (reg & mask) ? 1 : 0); } +static ssize_t ltc4245_show_gpio(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ltc4245_data *data = ltc4245_update_device(dev); + int val = data->gpios[attr->index]; + + /* handle stale GPIO's */ + if (val < 0) + return val; + + /* Convert to millivolts and print */ + return snprintf(buf, PAGE_SIZE, "%u\n", val * 10); +} + /* These macros are used below in constructing device attribute objects * for use with sysfs_create_group() to make a sysfs device file * for each register. @@ -254,6 +338,10 @@ static ssize_t ltc4245_show_alarm(struct device *dev, static SENSOR_DEVICE_ATTR_2(name, S_IRUGO, \ ltc4245_show_alarm, NULL, (mask), reg) +#define LTC4245_GPIO_VOLTAGE(name, gpio_num) \ + static SENSOR_DEVICE_ATTR(name, S_IRUGO, \ + ltc4245_show_gpio, NULL, gpio_num) + /* Construct a sensor_device_attribute structure for each register */ /* Input voltages */ @@ -293,7 +381,9 @@ LTC4245_ALARM(in7_min_alarm, (1 << 2), LTC4245_FAULT2); LTC4245_ALARM(in8_min_alarm, (1 << 3), LTC4245_FAULT2); /* GPIO voltages */ -LTC4245_VOLTAGE(in9_input, LTC4245_GPIOADC); +LTC4245_GPIO_VOLTAGE(in9_input, 0); +LTC4245_GPIO_VOLTAGE(in10_input, 1); +LTC4245_GPIO_VOLTAGE(in11_input, 2); /* Power Consumption (virtual) */ LTC4245_POWER(power1_input, LTC4245_12VSENSE); @@ -304,7 +394,7 @@ LTC4245_POWER(power4_input, LTC4245_VEESENSE); /* Finally, construct an array of pointers to members of the above objects, * as required for sysfs_create_group() */ -static struct attribute *ltc4245_attributes[] = { +static struct attribute *ltc4245_std_attributes[] = { &sensor_dev_attr_in1_input.dev_attr.attr, &sensor_dev_attr_in2_input.dev_attr.attr, &sensor_dev_attr_in3_input.dev_attr.attr, @@ -345,10 +435,77 @@ static struct attribute *ltc4245_attributes[] = { NULL, }; -static const struct attribute_group ltc4245_group = { - .attrs = ltc4245_attributes, +static struct attribute *ltc4245_gpio_attributes[] = { + &sensor_dev_attr_in10_input.dev_attr.attr, + &sensor_dev_attr_in11_input.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ltc4245_std_group = { + .attrs = ltc4245_std_attributes, +}; + +static const struct attribute_group ltc4245_gpio_group = { + .attrs = ltc4245_gpio_attributes, }; +static int ltc4245_sysfs_create_groups(struct i2c_client *client) +{ + struct ltc4245_data *data = i2c_get_clientdata(client); + struct device *dev = &client->dev; + int ret; + + /* register the standard sysfs attributes */ + ret = sysfs_create_group(&dev->kobj, <c4245_std_group); + if (ret) { + dev_err(dev, "unable to register standard attributes\n"); + return ret; + } + + /* if we're using the extra gpio support, register it's attributes */ + if (data->use_extra_gpios) { + ret = sysfs_create_group(&dev->kobj, <c4245_gpio_group); + if (ret) { + dev_err(dev, "unable to register gpio attributes\n"); + sysfs_remove_group(&dev->kobj, <c4245_std_group); + return ret; + } + } + + return 0; +} + +static void ltc4245_sysfs_remove_groups(struct i2c_client *client) +{ + struct ltc4245_data *data = i2c_get_clientdata(client); + struct device *dev = &client->dev; + + if (data->use_extra_gpios) + sysfs_remove_group(&dev->kobj, <c4245_gpio_group); + + sysfs_remove_group(&dev->kobj, <c4245_std_group); +} + +static bool ltc4245_use_extra_gpios(struct i2c_client *client) +{ + struct ltc4245_platform_data *pdata = dev_get_platdata(&client->dev); +#ifdef CONFIG_OF + struct device_node *np = client->dev.of_node; +#endif + + /* prefer platform data */ + if (pdata) + return pdata->use_extra_gpios; + +#ifdef CONFIG_OF + /* fallback on OF */ + if (of_find_property(np, "ltc4245,use-extra-gpios", NULL)) + return true; +#endif + + return false; +} + static int ltc4245_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -367,15 +524,16 @@ static int ltc4245_probe(struct i2c_client *client, i2c_set_clientdata(client, data); mutex_init(&data->update_lock); + data->use_extra_gpios = ltc4245_use_extra_gpios(client); /* Initialize the LTC4245 chip */ i2c_smbus_write_byte_data(client, LTC4245_FAULT1, 0x00); i2c_smbus_write_byte_data(client, LTC4245_FAULT2, 0x00); /* Register sysfs hooks */ - ret = sysfs_create_group(&client->dev.kobj, <c4245_group); + ret = ltc4245_sysfs_create_groups(client); if (ret) - goto out_sysfs_create_group; + goto out_sysfs_create_groups; data->hwmon_dev = hwmon_device_register(&client->dev); if (IS_ERR(data->hwmon_dev)) { @@ -386,8 +544,8 @@ static int ltc4245_probe(struct i2c_client *client, return 0; out_hwmon_device_register: - sysfs_remove_group(&client->dev.kobj, <c4245_group); -out_sysfs_create_group: + ltc4245_sysfs_remove_groups(client); +out_sysfs_create_groups: kfree(data); out_kzalloc: return ret; @@ -398,8 +556,7 @@ static int ltc4245_remove(struct i2c_client *client) struct ltc4245_data *data = i2c_get_clientdata(client); hwmon_device_unregister(data->hwmon_dev); - sysfs_remove_group(&client->dev.kobj, <c4245_group); - + ltc4245_sysfs_remove_groups(client); kfree(data); return 0; diff --git a/include/linux/i2c/ltc4245.h b/include/linux/i2c/ltc4245.h new file mode 100644 index 000000000000..56bda4be0016 --- /dev/null +++ b/include/linux/i2c/ltc4245.h @@ -0,0 +1,21 @@ +/* + * Platform Data for LTC4245 hardware monitor chip + * + * Copyright (c) 2010 Ira W. Snyder + * + * 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. + */ + +#ifndef LINUX_LTC4245_H +#define LINUX_LTC4245_H + +#include + +struct ltc4245_platform_data { + bool use_extra_gpios; +}; + +#endif /* LINUX_LTC4245_H */ -- cgit v1.2.2 From 960f12f4d1eb5ba3c76dc6b57a909a65dd59e1c2 Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Sat, 14 Aug 2010 21:08:49 +0200 Subject: hwmon: (emc1403) Add power support Add back the power interface we lost due to a slight misunderstanding of the maintainers wishes. Signed-off-by: Alan Cox Signed-off-by: Jean Delvare --- drivers/hwmon/emc1403.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/drivers/hwmon/emc1403.c b/drivers/hwmon/emc1403.c index 0e4b5642638d..5b58b20dead1 100644 --- a/drivers/hwmon/emc1403.c +++ b/drivers/hwmon/emc1403.c @@ -89,6 +89,35 @@ static ssize_t store_temp(struct device *dev, return count; } +static ssize_t store_bit(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct thermal_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 *sda = to_sensor_dev_attr_2(attr); + unsigned long val; + int retval; + + if (strict_strtoul(buf, 10, &val)) + return -EINVAL; + + mutex_lock(&data->mutex); + retval = i2c_smbus_read_byte_data(client, sda->nr); + if (retval < 0) + goto fail; + + retval &= ~sda->index; + if (val) + retval |= sda->index; + + retval = i2c_smbus_write_byte_data(client, sda->index, retval); + if (retval == 0) + retval = count; +fail: + mutex_unlock(&data->mutex); + return retval; +} + static ssize_t show_hyst(struct device *dev, struct device_attribute *attr, char *buf) { @@ -200,6 +229,9 @@ static SENSOR_DEVICE_ATTR_2(temp3_crit_alarm, S_IRUGO, static SENSOR_DEVICE_ATTR(temp3_crit_hyst, S_IRUGO | S_IWUSR, show_hyst, store_hyst, 0x1A); +static SENSOR_DEVICE_ATTR_2(power_state, S_IRUGO | S_IWUSR, + show_bit, store_bit, 0x03, 0x40); + static struct attribute *mid_att_thermal[] = { &sensor_dev_attr_temp1_min.dev_attr.attr, &sensor_dev_attr_temp1_max.dev_attr.attr, @@ -225,6 +257,7 @@ static struct attribute *mid_att_thermal[] = { &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr, &sensor_dev_attr_temp3_crit_hyst.dev_attr.attr, + &sensor_dev_attr_power_state.dev_attr.attr, NULL }; -- cgit v1.2.2 From 9914518e79800c977e20eda1335d43a4df813e3d Mon Sep 17 00:00:00 2001 From: Shubhrajyoti Datta Date: Sat, 14 Aug 2010 21:08:50 +0200 Subject: hwmon: (lm75) Add suspend/resume feature There is a shutdown feature at suspend it can be enabled to reduce current consumption and resume it can be switched off. Signed-off-by: Shubhrajyoti Datta Signed-off-by: Jean Delvare --- drivers/hwmon/lm75.c | 39 +++++++++++++++++++++++++++++++++++++++ drivers/hwmon/lm75.h | 1 + 2 files changed, 40 insertions(+) diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c index 393f354f92a4..ab5b87a81677 100644 --- a/drivers/hwmon/lm75.c +++ b/drivers/hwmon/lm75.c @@ -280,10 +280,49 @@ static int lm75_detect(struct i2c_client *new_client, return 0; } +#ifdef CONFIG_PM +static int lm75_suspend(struct device *dev) +{ + int status; + struct i2c_client *client = to_i2c_client(dev); + status = lm75_read_value(client, LM75_REG_CONF); + if (status < 0) { + dev_dbg(&client->dev, "Can't read config? %d\n", status); + return status; + } + status = status | LM75_SHUTDOWN; + lm75_write_value(client, LM75_REG_CONF, status); + return 0; +} + +static int lm75_resume(struct device *dev) +{ + int status; + struct i2c_client *client = to_i2c_client(dev); + status = lm75_read_value(client, LM75_REG_CONF); + if (status < 0) { + dev_dbg(&client->dev, "Can't read config? %d\n", status); + return status; + } + status = status & ~LM75_SHUTDOWN; + lm75_write_value(client, LM75_REG_CONF, status); + return 0; +} + +static const struct dev_pm_ops lm75_dev_pm_ops = { + .suspend = lm75_suspend, + .resume = lm75_resume, +}; +#define LM75_DEV_PM_OPS (&lm75_dev_pm_ops) +#else +#define LM75_DEV_PM_OPS NULL +#endif /* CONFIG_PM */ + static struct i2c_driver lm75_driver = { .class = I2C_CLASS_HWMON, .driver = { .name = "lm75", + .pm = LM75_DEV_PM_OPS, }, .probe = lm75_probe, .remove = lm75_remove, diff --git a/drivers/hwmon/lm75.h b/drivers/hwmon/lm75.h index 7c93454bb4e3..e547a3eb4de3 100644 --- a/drivers/hwmon/lm75.h +++ b/drivers/hwmon/lm75.h @@ -30,6 +30,7 @@ /* straight from the datasheet */ #define LM75_TEMP_MIN (-55000) #define LM75_TEMP_MAX 125000 +#define LM75_SHUTDOWN 0x01 /* TEMP: 0.001C/bit (-55C to +125C) REG: (0.5C/bit, two's complement) << 7 */ -- cgit v1.2.2 From 738e5e05362bdbb31dbc270d31005c12cec8dbb2 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Sat, 14 Aug 2010 21:08:50 +0200 Subject: hwmon: (it87) Export labels for internal sensors Some voltage sensors can be wired internally to the IT87xxF chip's own power supply channels. In that case, we can inform user-space that the wiring is known by exporting proper labels for these sensors. Signed-off-by: Jean Delvare --- drivers/hwmon/it87.c | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c index 25763d2223b6..f7701295937d 100644 --- a/drivers/hwmon/it87.c +++ b/drivers/hwmon/it87.c @@ -259,6 +259,7 @@ struct it87_sio_data { u8 revision; u8 vid_value; u8 beep_pin; + u8 internal; /* Internal sensors can be labeled */ /* Features skipped based on config or DMI */ u8 skip_vid; u8 skip_fan; @@ -1194,6 +1195,22 @@ static ssize_t show_vid_reg(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR(cpu0_vid, S_IRUGO, show_vid_reg, NULL); +static ssize_t show_label(struct device *dev, struct device_attribute *attr, + char *buf) +{ + static const char *labels[] = { + "+5V", + "5VSB", + "Vbat", + }; + int nr = to_sensor_dev_attr(attr)->index; + + return sprintf(buf, "%s\n", labels[nr]); +} +static SENSOR_DEVICE_ATTR(in3_label, S_IRUGO, show_label, NULL, 0); +static SENSOR_DEVICE_ATTR(in7_label, S_IRUGO, show_label, NULL, 1); +static SENSOR_DEVICE_ATTR(in8_label, S_IRUGO, show_label, NULL, 2); + static ssize_t show_name(struct device *dev, struct device_attribute *devattr, char *buf) { @@ -1434,6 +1451,17 @@ static const struct attribute_group it87_group_vid = { .attrs = it87_attributes_vid, }; +static struct attribute *it87_attributes_label[] = { + &sensor_dev_attr_in3_label.dev_attr.attr, + &sensor_dev_attr_in7_label.dev_attr.attr, + &sensor_dev_attr_in8_label.dev_attr.attr, + NULL +}; + +static const struct attribute_group it87_group_label = { + .attrs = it87_attributes_vid, +}; + /* SuperIO detection - will change isa_address if a chip is found */ static int __init it87_find(unsigned short *address, struct it87_sio_data *sio_data) @@ -1487,6 +1515,9 @@ static int __init it87_find(unsigned short *address, pr_info("it87: Found IT%04xF chip at 0x%x, revision %d\n", chip_type, *address, sio_data->revision); + /* in8 (Vbat) is always internal */ + sio_data->internal = (1 << 2); + /* Read GPIO config and VID value from LDN 7 (GPIO) */ if (sio_data->type == it87) { /* The IT8705F doesn't have VID pins at all */ @@ -1540,9 +1571,9 @@ static int __init it87_find(unsigned short *address, pr_notice("it87: Routing internal VCCH to in7\n"); } if (reg & (1 << 0)) - pr_info("it87: in3 is VCC (+5V)\n"); + sio_data->internal |= (1 << 0); if (reg & (1 << 1)) - pr_info("it87: in7 is VCCH (+5V Stand-By)\n"); + sio_data->internal |= (1 << 1); sio_data->beep_pin = superio_inb(IT87_SIO_BEEP_PIN_REG) & 0x3f; } @@ -1600,6 +1631,7 @@ static void it87_remove_files(struct device *dev) } if (!sio_data->skip_vid) sysfs_remove_group(&dev->kobj, &it87_group_vid); + sysfs_remove_group(&dev->kobj, &it87_group_label); } static int __devinit it87_probe(struct platform_device *pdev) @@ -1725,6 +1757,16 @@ static int __devinit it87_probe(struct platform_device *pdev) goto ERROR4; } + /* Export labels for internal sensors */ + for (i = 0; i < 3; i++) { + if (!(sio_data->internal & (1 << i))) + continue; + err = sysfs_create_file(&dev->kobj, + it87_attributes_label[i]); + if (err) + goto ERROR4; + } + data->hwmon_dev = hwmon_device_register(dev); if (IS_ERR(data->hwmon_dev)) { err = PTR_ERR(data->hwmon_dev); -- cgit v1.2.2 From 28cbd461f022e30dba555926b76625a35e194166 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Sat, 14 Aug 2010 21:08:52 +0200 Subject: hwmon: (asc7621) Clean up and improve detect function * The dev variable is never used. * Detect functions only need to set info->type, not client->name. * Include the device address in the log message. Signed-off-by: Jean Delvare Cc: George Joseph Cc: Ken Milmore --- drivers/hwmon/asc7621.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/hwmon/asc7621.c b/drivers/hwmon/asc7621.c index 3b973f30b1f6..89b4f3babe87 100644 --- a/drivers/hwmon/asc7621.c +++ b/drivers/hwmon/asc7621.c @@ -1150,9 +1150,6 @@ static int asc7621_detect(struct i2c_client *client, { 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; @@ -1169,13 +1166,11 @@ static int asc7621_detect(struct i2c_client *client, 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); + dev_info(&adapter->dev, "Matched %s at 0x%02x\n", + asc7621_chips[chip_index].name, client->addr); return 0; } } -- cgit v1.2.2 From f46fc8cd382f318a7d9c6cd0af1b771c74e5dfd0 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 14 Aug 2010 21:08:52 +0200 Subject: hwmon: Add 3 critical limit attributes to sysfs-interface Added _lcrit and _crit to voltage attributes. Added _lcrit to temperature attributes. Signed-off-by: Guenter Roeck Signed-off-by: Jean Delvare --- Documentation/hwmon/sysfs-interface | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Documentation/hwmon/sysfs-interface b/Documentation/hwmon/sysfs-interface index d4e2917c6f18..2e492187a665 100644 --- a/Documentation/hwmon/sysfs-interface +++ b/Documentation/hwmon/sysfs-interface @@ -107,10 +107,24 @@ in[0-*]_min Voltage min value. Unit: millivolt RW +in[0-*]_lcrit Voltage critical min value. + Unit: millivolt + RW + If voltage drops to or below this limit, the system may + take drastic action such as power down or reset. At the very + least, it should report a fault. + in[0-*]_max Voltage max value. Unit: millivolt RW +in[0-*]_crit Voltage critical max value. + Unit: millivolt + RW + If voltage reaches or exceeds this limit, the system may + take drastic action such as power down or reset. At the very + least, it should report a fault. + in[0-*]_input Voltage input value. Unit: millivolt RO @@ -284,7 +298,7 @@ temp[1-*]_input Temperature input value. Unit: millidegree Celsius RO -temp[1-*]_crit Temperature critical value, typically greater than +temp[1-*]_crit Temperature critical max value, typically greater than corresponding temp_max values. Unit: millidegree Celsius RW @@ -296,6 +310,11 @@ temp[1-*]_crit_hyst from the critical value. RW +temp[1-*]_lcrit Temperature critical min value, typically lower than + corresponding temp_min values. + Unit: millidegree Celsius + RW + temp[1-*]_offset Temperature offset which is added to the temperature reading by the chip. -- cgit v1.2.2 From e04a715e4e1210a6ecfcaa0e6ceefde07020710b Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 14 Aug 2010 21:08:53 +0200 Subject: hwmon: Add 4 current alarm/beep attributes to sysfs-interface Add currX_alarm, currX_min_alarm, currX_max_alarm and currX_beep attributes to the hwmon sysfs API. currX_min_alarm and currX_max_alarm are already supported by the LTC4215 and LTC4245 drivers. currX_alarm is supported by the LTC4261 driver. Signed-off-by: Guenter Roeck Signed-off-by: Jean Delvare --- Documentation/hwmon/sysfs-interface | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Documentation/hwmon/sysfs-interface b/Documentation/hwmon/sysfs-interface index 2e492187a665..d4d175b8a54a 100644 --- a/Documentation/hwmon/sysfs-interface +++ b/Documentation/hwmon/sysfs-interface @@ -363,9 +363,6 @@ Also see the Alarms section for status flags associated with temperatures. * Currents * ************ -Note that no known chip provides current measurements as of writing, -so this part is theoretical, so to say. - curr[1-*]_max Current max value Unit: milliampere RW @@ -490,6 +487,7 @@ limit-related alarms, not both. The driver should just reflect the hardware implementation. in[0-*]_alarm +curr[1-*]_alarm fan[1-*]_alarm temp[1-*]_alarm Channel alarm @@ -501,6 +499,8 @@ OR in[0-*]_min_alarm in[0-*]_max_alarm +curr[1-*]_min_alarm +curr[1-*]_max_alarm fan[1-*]_min_alarm fan[1-*]_max_alarm temp[1-*]_min_alarm @@ -532,6 +532,7 @@ beep_enable Master beep enable RW in[0-*]_beep +curr[1-*]_beep fan[1-*]_beep temp[1-*]_beep Channel beep -- cgit v1.2.2 From 5a9c2cd881f48d4bec627cccde386f8e3a28bb4a Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Sat, 14 Aug 2010 21:08:54 +0200 Subject: hwmon: Remove in[0-*]_fault from sysfs-interface Fault files are for hardware failures that can be reported. So far we've seen chips reporting such failures for temperature sensors and fans, but not for voltages. Remove in[0-*]_fault for now. It can be added back later if really needed, but I doubt it. Signed-off-by: Jean Delvare Acked-by: Guenter Roeck --- Documentation/hwmon/sysfs-interface | 1 - 1 file changed, 1 deletion(-) diff --git a/Documentation/hwmon/sysfs-interface b/Documentation/hwmon/sysfs-interface index d4d175b8a54a..ff45d1f837c8 100644 --- a/Documentation/hwmon/sysfs-interface +++ b/Documentation/hwmon/sysfs-interface @@ -516,7 +516,6 @@ to notify open diodes, unconnected fans etc. where the hardware supports it. When this boolean has value 1, the measurement for that channel should not be trusted. -in[0-*]_fault fan[1-*]_fault temp[1-*]_fault Input fault condition -- cgit v1.2.2 From 9df7305b5a8651eb940e98496bc1d4742379c578 Mon Sep 17 00:00:00 2001 From: Steve Glendinning Date: Sat, 14 Aug 2010 21:08:54 +0200 Subject: hwmon: Add driver for SMSC EMC2103 temperature monitor and fan controller SMSC's EMC2103 family of temperature/fan controllers have 1 onboard and up to 3 external temperature sensors, and allow closed-loop control of one fan. This patch adds support for them. Signed-off-by: Steve Glendinning Signed-off-by: Jean Delvare --- Documentation/hwmon/emc2103 | 33 ++ MAINTAINERS | 7 + drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/emc2103.c | 740 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 791 insertions(+) create mode 100644 Documentation/hwmon/emc2103 create mode 100644 drivers/hwmon/emc2103.c diff --git a/Documentation/hwmon/emc2103 b/Documentation/hwmon/emc2103 new file mode 100644 index 000000000000..a12b2c127140 --- /dev/null +++ b/Documentation/hwmon/emc2103 @@ -0,0 +1,33 @@ +Kernel driver emc2103 +====================== + +Supported chips: + * SMSC EMC2103 + Addresses scanned: I2C 0x2e + Prefix: 'emc2103' + Datasheet: Not public + +Authors: + Steve Glendinning + +Description +----------- + +The Standard Microsystems Corporation (SMSC) EMC2103 chips +contain up to 4 temperature sensors and a single fan controller. + +Fan rotation speeds are reported in RPM (rotations per minute). An alarm is +triggered if the rotation speed has dropped below a programmable limit. Fan +readings can be divided by a programmable divider (1, 2, 4 or 8) to give +the readings more range or accuracy. Not all RPM values can accurately be +represented, so some rounding is done. With a divider of 1, the lowest +representable value is 480 RPM. + +This driver supports RPM based control, to use this a fan target +should be written to fan1_target and pwm1_enable should be set to 3. + +The 2103-2 and 2103-4 variants have a third temperature sensor, which can +be connected to two anti-parallel diodes. These values can be read +as temp3 and temp4. If only one diode is attached to this channel, temp4 +will show as "fault". The module parameter "apd=0" can be used to suppress +this 4th channel when anti-parallel diodes are not fitted. diff --git a/MAINTAINERS b/MAINTAINERS index 39d8c5ce9913..67fe8b1d8847 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5279,6 +5279,13 @@ S: Maintained F: Documentation/hwmon/smm665 F: drivers/hwmon/smm665.c +SMSC EMC2103 HARDWARE MONITOR DRIVER +M: Steve Glendinning +L: lm-sensors@lm-sensors.org +S: Supported +F: Documentation/hwmon/emc2103 +F: drivers/hwmon/emc2103.c + SMSC47B397 HARDWARE MONITOR DRIVER M: "Mark M. Hoffman" L: lm-sensors@lm-sensors.org diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index f3adf18bfa05..d50e9fa8016f 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -804,6 +804,16 @@ config SENSORS_EMC1403 Threshold values can be configured using sysfs. Data from the different diodes are accessible via sysfs. +config SENSORS_EMC2103 + tristate "SMSC EMC2103" + depends on I2C + help + If you say yes here you get support for the temperature + and fan sensors of the SMSC EMC2103 chips. + + This driver can also be built as a module. If so, the module + will be called emc2103. + config SENSORS_SMSC47M1 tristate "SMSC LPC47M10x and compatibles" help diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 13d913e34dbf..e3c2484f6c5f 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_SENSORS_PKGTEMP) += pkgtemp.o obj-$(CONFIG_SENSORS_DME1737) += dme1737.o obj-$(CONFIG_SENSORS_DS1621) += ds1621.o obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o +obj-$(CONFIG_SENSORS_EMC2103) += emc2103.o obj-$(CONFIG_SENSORS_F71805F) += f71805f.o obj-$(CONFIG_SENSORS_F71882FG) += f71882fg.o obj-$(CONFIG_SENSORS_F75375S) += f75375s.o diff --git a/drivers/hwmon/emc2103.c b/drivers/hwmon/emc2103.c new file mode 100644 index 000000000000..af914ad93ece --- /dev/null +++ b/drivers/hwmon/emc2103.c @@ -0,0 +1,740 @@ +/* + emc2103.c - Support for SMSC EMC2103 + Copyright (c) 2010 SMSC + + 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 scanned */ +static const unsigned short normal_i2c[] = { 0x2E, I2C_CLIENT_END }; + +static const u8 REG_TEMP[4] = { 0x00, 0x02, 0x04, 0x06 }; +static const u8 REG_TEMP_MIN[4] = { 0x3c, 0x38, 0x39, 0x3a }; +static const u8 REG_TEMP_MAX[4] = { 0x34, 0x30, 0x31, 0x32 }; + +#define REG_CONF1 0x20 +#define REG_TEMP_MAX_ALARM 0x24 +#define REG_TEMP_MIN_ALARM 0x25 +#define REG_FAN_CONF1 0x42 +#define REG_FAN_TARGET_LO 0x4c +#define REG_FAN_TARGET_HI 0x4d +#define REG_FAN_TACH_HI 0x4e +#define REG_FAN_TACH_LO 0x4f +#define REG_PRODUCT_ID 0xfd +#define REG_MFG_ID 0xfe + +/* equation 4 from datasheet: rpm = (3932160 * multipler) / count */ +#define FAN_RPM_FACTOR 3932160 + +/* 2103-2 and 2103-4's 3rd temperature sensor can be connected to two diodes + * in anti-parallel mode, and in this configuration both can be read + * independently (so we have 4 temperature inputs). The device can't + * detect if it's connected in this mode, so we have to manually enable + * it. Default is to leave the device in the state it's already in (-1). + * This parameter allows APD mode to be optionally forced on or off */ +static int apd = -1; +module_param(apd, bool, 0); +MODULE_PARM_DESC(init, "Set to zero to disable anti-parallel diode mode"); + +struct temperature { + s8 degrees; + u8 fraction; /* 0-7 multiples of 0.125 */ +}; + +struct emc2103_data { + struct device *hwmon_dev; + struct mutex update_lock; + bool valid; /* registers are valid */ + bool fan_rpm_control; + int temp_count; /* num of temp sensors */ + unsigned long last_updated; /* in jiffies */ + struct temperature temp[4]; /* internal + 3 external */ + s8 temp_min[4]; /* no fractional part */ + s8 temp_max[4]; /* no fractional part */ + u8 temp_min_alarm; + u8 temp_max_alarm; + u8 fan_multiplier; + u16 fan_tach; + u16 fan_target; +}; + +static int read_u8_from_i2c(struct i2c_client *client, u8 i2c_reg, u8 *output) +{ + int status = i2c_smbus_read_byte_data(client, i2c_reg); + if (status < 0) { + dev_warn(&client->dev, "reg 0x%02x, err %d\n", + i2c_reg, status); + } else { + *output = status; + } + return status; +} + +static void read_temp_from_i2c(struct i2c_client *client, u8 i2c_reg, + struct temperature *temp) +{ + u8 degrees, fractional; + + if (read_u8_from_i2c(client, i2c_reg, °rees) < 0) + return; + + if (read_u8_from_i2c(client, i2c_reg + 1, &fractional) < 0) + return; + + temp->degrees = degrees; + temp->fraction = (fractional & 0xe0) >> 5; +} + +static void read_fan_from_i2c(struct i2c_client *client, u16 *output, + u8 hi_addr, u8 lo_addr) +{ + u8 high_byte, lo_byte; + + if (read_u8_from_i2c(client, hi_addr, &high_byte) < 0) + return; + + if (read_u8_from_i2c(client, lo_addr, &lo_byte) < 0) + return; + + *output = ((u16)high_byte << 5) | (lo_byte >> 3); +} + +static void write_fan_target_to_i2c(struct i2c_client *client, u16 new_target) +{ + u8 high_byte = (new_target & 0x1fe0) >> 5; + u8 low_byte = (new_target & 0x001f) << 3; + i2c_smbus_write_byte_data(client, REG_FAN_TARGET_LO, low_byte); + i2c_smbus_write_byte_data(client, REG_FAN_TARGET_HI, high_byte); +} + +static void read_fan_config_from_i2c(struct i2c_client *client) + +{ + struct emc2103_data *data = i2c_get_clientdata(client); + u8 conf1; + + if (read_u8_from_i2c(client, REG_FAN_CONF1, &conf1) < 0) + return; + + data->fan_multiplier = 1 << ((conf1 & 0x60) >> 5); + data->fan_rpm_control = (conf1 & 0x80) != 0; +} + +static struct emc2103_data *emc2103_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct emc2103_data *data = i2c_get_clientdata(client); + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) + || !data->valid) { + int i; + + for (i = 0; i < data->temp_count; i++) { + read_temp_from_i2c(client, REG_TEMP[i], &data->temp[i]); + read_u8_from_i2c(client, REG_TEMP_MIN[i], + &data->temp_min[i]); + read_u8_from_i2c(client, REG_TEMP_MAX[i], + &data->temp_max[i]); + } + + read_u8_from_i2c(client, REG_TEMP_MIN_ALARM, + &data->temp_min_alarm); + read_u8_from_i2c(client, REG_TEMP_MAX_ALARM, + &data->temp_max_alarm); + + read_fan_from_i2c(client, &data->fan_tach, + REG_FAN_TACH_HI, REG_FAN_TACH_LO); + read_fan_from_i2c(client, &data->fan_target, + REG_FAN_TARGET_HI, REG_FAN_TARGET_LO); + read_fan_config_from_i2c(client); + + data->last_updated = jiffies; + data->valid = true; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +static ssize_t +show_temp(struct device *dev, struct device_attribute *da, char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct emc2103_data *data = emc2103_update_device(dev); + int millidegrees = data->temp[nr].degrees * 1000 + + data->temp[nr].fraction * 125; + return sprintf(buf, "%d\n", millidegrees); +} + +static ssize_t +show_temp_min(struct device *dev, struct device_attribute *da, char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct emc2103_data *data = emc2103_update_device(dev); + int millidegrees = data->temp_min[nr] * 1000; + return sprintf(buf, "%d\n", millidegrees); +} + +static ssize_t +show_temp_max(struct device *dev, struct device_attribute *da, char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct emc2103_data *data = emc2103_update_device(dev); + int millidegrees = data->temp_max[nr] * 1000; + return sprintf(buf, "%d\n", millidegrees); +} + +static ssize_t +show_temp_fault(struct device *dev, struct device_attribute *da, char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct emc2103_data *data = emc2103_update_device(dev); + bool fault = (data->temp[nr].degrees == -128); + return sprintf(buf, "%d\n", fault ? 1 : 0); +} + +static ssize_t +show_temp_min_alarm(struct device *dev, struct device_attribute *da, char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct emc2103_data *data = emc2103_update_device(dev); + bool alarm = data->temp_min_alarm & (1 << nr); + return sprintf(buf, "%d\n", alarm ? 1 : 0); +} + +static ssize_t +show_temp_max_alarm(struct device *dev, struct device_attribute *da, char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct emc2103_data *data = emc2103_update_device(dev); + bool alarm = data->temp_max_alarm & (1 << nr); + return sprintf(buf, "%d\n", alarm ? 1 : 0); +} + +static ssize_t set_temp_min(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(da)->index; + struct i2c_client *client = to_i2c_client(dev); + struct emc2103_data *data = i2c_get_clientdata(client); + long val; + + int result = strict_strtol(buf, 10, &val); + if (result < 0) + return -EINVAL; + + val = DIV_ROUND_CLOSEST(val, 1000); + if ((val < -63) || (val > 127)) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->temp_min[nr] = val; + i2c_smbus_write_byte_data(client, REG_TEMP_MIN[nr], val); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t set_temp_max(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(da)->index; + struct i2c_client *client = to_i2c_client(dev); + struct emc2103_data *data = i2c_get_clientdata(client); + long val; + + int result = strict_strtol(buf, 10, &val); + if (result < 0) + return -EINVAL; + + val = DIV_ROUND_CLOSEST(val, 1000); + if ((val < -63) || (val > 127)) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->temp_max[nr] = val; + i2c_smbus_write_byte_data(client, REG_TEMP_MAX[nr], val); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t +show_fan(struct device *dev, struct device_attribute *da, char *buf) +{ + struct emc2103_data *data = emc2103_update_device(dev); + int rpm = 0; + if (data->fan_tach != 0) + rpm = (FAN_RPM_FACTOR * data->fan_multiplier) / data->fan_tach; + return sprintf(buf, "%d\n", rpm); +} + +static ssize_t +show_fan_div(struct device *dev, struct device_attribute *da, char *buf) +{ + struct emc2103_data *data = emc2103_update_device(dev); + int fan_div = 8 / data->fan_multiplier; + return sprintf(buf, "%d\n", fan_div); +} + +/* Note: we also update the fan target here, because its value is + determined in part by the fan clock divider. This follows the principle + of least surprise; the user doesn't expect the fan target to change just + because the divider changed. */ +static ssize_t set_fan_div(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct emc2103_data *data = emc2103_update_device(dev); + struct i2c_client *client = to_i2c_client(dev); + int new_range_bits, old_div = 8 / data->fan_multiplier; + long new_div; + + int status = strict_strtol(buf, 10, &new_div); + if (status < 0) + return -EINVAL; + + if (new_div == old_div) /* No change */ + return count; + + switch (new_div) { + case 1: + new_range_bits = 3; + break; + case 2: + new_range_bits = 2; + break; + case 4: + new_range_bits = 1; + break; + case 8: + new_range_bits = 0; + break; + default: + return -EINVAL; + } + + mutex_lock(&data->update_lock); + + status = i2c_smbus_read_byte_data(client, REG_FAN_CONF1); + if (status < 0) { + dev_dbg(&client->dev, "reg 0x%02x, err %d\n", + REG_FAN_CONF1, status); + mutex_unlock(&data->update_lock); + return -EIO; + } + status &= 0x9F; + status |= (new_range_bits << 5); + i2c_smbus_write_byte_data(client, REG_FAN_CONF1, status); + + data->fan_multiplier = 8 / new_div; + + /* update fan target if high byte is not disabled */ + if ((data->fan_target & 0x1fe0) != 0x1fe0) { + u16 new_target = (data->fan_target * old_div) / new_div; + data->fan_target = min(new_target, (u16)0x1fff); + write_fan_target_to_i2c(client, data->fan_target); + } + + /* invalidate data to force re-read from hardware */ + data->valid = false; + + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_fan_target(struct device *dev, struct device_attribute *da, char *buf) +{ + struct emc2103_data *data = emc2103_update_device(dev); + int rpm = 0; + + /* high byte of 0xff indicates disabled so return 0 */ + if ((data->fan_target != 0) && ((data->fan_target & 0x1fe0) != 0x1fe0)) + rpm = (FAN_RPM_FACTOR * data->fan_multiplier) + / data->fan_target; + + return sprintf(buf, "%d\n", rpm); +} + +static ssize_t set_fan_target(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct emc2103_data *data = emc2103_update_device(dev); + struct i2c_client *client = to_i2c_client(dev); + long rpm_target; + + int result = strict_strtol(buf, 10, &rpm_target); + if (result < 0) + return -EINVAL; + + /* Datasheet states 16384 as maximum RPM target (table 3.2) */ + if ((rpm_target < 0) || (rpm_target > 16384)) + return -EINVAL; + + mutex_lock(&data->update_lock); + + if (rpm_target == 0) + data->fan_target = 0x1fff; + else + data->fan_target = SENSORS_LIMIT( + (FAN_RPM_FACTOR * data->fan_multiplier) / rpm_target, + 0, 0x1fff); + + write_fan_target_to_i2c(client, data->fan_target); + + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_fan_fault(struct device *dev, struct device_attribute *da, char *buf) +{ + struct emc2103_data *data = emc2103_update_device(dev); + bool fault = ((data->fan_tach & 0x1fe0) == 0x1fe0); + return sprintf(buf, "%d\n", fault ? 1 : 0); +} + +static ssize_t +show_pwm_enable(struct device *dev, struct device_attribute *da, char *buf) +{ + struct emc2103_data *data = emc2103_update_device(dev); + return sprintf(buf, "%d\n", data->fan_rpm_control ? 3 : 0); +} + +static ssize_t set_pwm_enable(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct emc2103_data *data = i2c_get_clientdata(client); + long new_value; + u8 conf_reg; + + int result = strict_strtol(buf, 10, &new_value); + if (result < 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + switch (new_value) { + case 0: + data->fan_rpm_control = false; + break; + case 3: + data->fan_rpm_control = true; + break; + default: + mutex_unlock(&data->update_lock); + return -EINVAL; + } + + read_u8_from_i2c(client, REG_FAN_CONF1, &conf_reg); + + if (data->fan_rpm_control) + conf_reg |= 0x80; + else + conf_reg &= ~0x80; + + i2c_smbus_write_byte_data(client, REG_FAN_CONF1, conf_reg); + + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO | S_IWUSR, show_temp_min, + set_temp_min, 0); +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, show_temp_max, + set_temp_max, 0); +static SENSOR_DEVICE_ATTR(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_temp_min_alarm, + NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_temp_max_alarm, + NULL, 0); + +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO | S_IWUSR, show_temp_min, + set_temp_min, 1); +static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO | S_IWUSR, show_temp_max, + set_temp_max, 1); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_temp_min_alarm, + NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_temp_max_alarm, + NULL, 1); + +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_min, S_IRUGO | S_IWUSR, show_temp_min, + set_temp_min, 2); +static SENSOR_DEVICE_ATTR(temp3_max, S_IRUGO | S_IWUSR, show_temp_max, + set_temp_max, 2); +static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_temp_fault, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_min_alarm, S_IRUGO, show_temp_min_alarm, + NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_temp_max_alarm, + NULL, 2); + +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_min, S_IRUGO | S_IWUSR, show_temp_min, + set_temp_min, 3); +static SENSOR_DEVICE_ATTR(temp4_max, S_IRUGO | S_IWUSR, show_temp_max, + set_temp_max, 3); +static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_temp_fault, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_min_alarm, S_IRUGO, show_temp_min_alarm, + NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_temp_max_alarm, + NULL, 3); + +static DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL); +static DEVICE_ATTR(fan1_div, S_IRUGO | S_IWUSR, show_fan_div, set_fan_div); +static DEVICE_ATTR(fan1_target, S_IRUGO | S_IWUSR, show_fan_target, + set_fan_target); +static DEVICE_ATTR(fan1_fault, S_IRUGO, show_fan_fault, NULL); + +static DEVICE_ATTR(pwm1_enable, S_IRUGO | S_IWUSR, show_pwm_enable, + set_pwm_enable); + +/* sensors present on all models */ +static struct attribute *emc2103_attributes[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_fault.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &dev_attr_fan1_input.attr, + &dev_attr_fan1_div.attr, + &dev_attr_fan1_target.attr, + &dev_attr_fan1_fault.attr, + &dev_attr_pwm1_enable.attr, + NULL +}; + +/* extra temperature sensors only present on 2103-2 and 2103-4 */ +static struct attribute *emc2103_attributes_temp3[] = { + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + &sensor_dev_attr_temp3_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, + NULL +}; + +/* extra temperature sensors only present on 2103-2 and 2103-4 in APD mode */ +static struct attribute *emc2103_attributes_temp4[] = { + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp4_min.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp4_fault.dev_attr.attr, + &sensor_dev_attr_temp4_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group emc2103_group = { + .attrs = emc2103_attributes, +}; + +static const struct attribute_group emc2103_temp3_group = { + .attrs = emc2103_attributes_temp3, +}; + +static const struct attribute_group emc2103_temp4_group = { + .attrs = emc2103_attributes_temp4, +}; + +static int +emc2103_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct emc2103_data *data; + int status; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + data = kzalloc(sizeof(struct emc2103_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* 2103-2 and 2103-4 have 3 external diodes, 2103-1 has 1 */ + status = i2c_smbus_read_byte_data(client, REG_PRODUCT_ID); + if (status == 0x24) { + /* 2103-1 only has 1 external diode */ + data->temp_count = 2; + } else { + /* 2103-2 and 2103-4 have 3 or 4 external diodes */ + status = i2c_smbus_read_byte_data(client, REG_CONF1); + if (status < 0) { + dev_dbg(&client->dev, "reg 0x%02x, err %d\n", REG_CONF1, + status); + goto exit_free; + } + + /* detect current state of hardware */ + data->temp_count = (status & 0x01) ? 4 : 3; + + /* force APD state if module parameter is set */ + if (apd == 0) { + /* force APD mode off */ + data->temp_count = 3; + status &= ~(0x01); + i2c_smbus_write_byte_data(client, REG_CONF1, status); + } else if (apd == 1) { + /* force APD mode on */ + data->temp_count = 4; + status |= 0x01; + i2c_smbus_write_byte_data(client, REG_CONF1, status); + } + } + + /* Register sysfs hooks */ + status = sysfs_create_group(&client->dev.kobj, &emc2103_group); + if (status) + goto exit_free; + + if (data->temp_count >= 3) { + status = sysfs_create_group(&client->dev.kobj, + &emc2103_temp3_group); + if (status) + goto exit_remove; + } + + if (data->temp_count == 4) { + status = sysfs_create_group(&client->dev.kobj, + &emc2103_temp4_group); + if (status) + goto exit_remove_temp3; + } + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + status = PTR_ERR(data->hwmon_dev); + goto exit_remove_temp4; + } + + dev_info(&client->dev, "%s: sensor '%s'\n", + dev_name(data->hwmon_dev), client->name); + + return 0; + +exit_remove_temp4: + if (data->temp_count == 4) + sysfs_remove_group(&client->dev.kobj, &emc2103_temp4_group); +exit_remove_temp3: + if (data->temp_count >= 3) + sysfs_remove_group(&client->dev.kobj, &emc2103_temp3_group); +exit_remove: + sysfs_remove_group(&client->dev.kobj, &emc2103_group); +exit_free: + kfree(data); + return status; +} + +static int emc2103_remove(struct i2c_client *client) +{ + struct emc2103_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + + if (data->temp_count == 4) + sysfs_remove_group(&client->dev.kobj, &emc2103_temp4_group); + + if (data->temp_count >= 3) + sysfs_remove_group(&client->dev.kobj, &emc2103_temp3_group); + + sysfs_remove_group(&client->dev.kobj, &emc2103_group); + + kfree(data); + return 0; +} + +static const struct i2c_device_id emc2103_ids[] = { + { "emc2103", 0, }, + { /* LIST END */ } +}; +MODULE_DEVICE_TABLE(i2c, emc2103_ids); + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int +emc2103_detect(struct i2c_client *new_client, struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = new_client->adapter; + int manufacturer, product; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + manufacturer = i2c_smbus_read_byte_data(new_client, REG_MFG_ID); + if (manufacturer != 0x5D) + return -ENODEV; + + product = i2c_smbus_read_byte_data(new_client, REG_PRODUCT_ID); + if ((product != 0x24) && (product != 0x26)) + return -ENODEV; + + strlcpy(info->type, "emc2103", I2C_NAME_SIZE); + + return 0; +} + +static struct i2c_driver emc2103_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "emc2103", + }, + .probe = emc2103_probe, + .remove = emc2103_remove, + .id_table = emc2103_ids, + .detect = emc2103_detect, + .address_list = normal_i2c, +}; + +static int __init sensors_emc2103_init(void) +{ + return i2c_add_driver(&emc2103_driver); +} + +static void __exit sensors_emc2103_exit(void) +{ + i2c_del_driver(&emc2103_driver); +} + +MODULE_AUTHOR("Steve Glendinning "); +MODULE_DESCRIPTION("SMSC EMC2103 hwmon driver"); +MODULE_LICENSE("GPL"); + +module_init(sensors_emc2103_init); +module_exit(sensors_emc2103_exit); -- cgit v1.2.2 From da2e025590cf7038440132d4bbc967a579b11112 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 14 Aug 2010 21:08:55 +0200 Subject: hwmon: (w83627ehf) Driver cleanup - Moved fan pwm register array pointers into per-instance data. - Only read fan pwm data for installed/supported fans. - Update fan max output and fan step output information from data in registers. - Create max_output and step_output attribute files only if respective fan pwm registers exist. Signed-off-by: Guenter Roeck Signed-off-by: Jean Delvare --- drivers/hwmon/w83627ehf.c | 59 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c index 0dcaba9b7189..e01a3e97aa17 100644 --- a/drivers/hwmon/w83627ehf.c +++ b/drivers/hwmon/w83627ehf.c @@ -277,6 +277,11 @@ struct w83627ehf_data { struct device *hwmon_dev; struct mutex lock; + const u8 *REG_FAN_START_OUTPUT; + const u8 *REG_FAN_STOP_OUTPUT; + const u8 *REG_FAN_MAX_OUTPUT; + const u8 *REG_FAN_STEP_OUTPUT; + struct mutex update_lock; char valid; /* !=0 if following fields are valid */ unsigned long last_updated; /* In jiffies */ @@ -524,7 +529,10 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) } } - for (i = 0; i < 4; i++) { + for (i = 0; i < data->pwm_num; i++) { + if (!(data->has_fan & (1 << i))) + continue; + /* pwmcfg, tolerance mapped for i=0, i=1 to same reg */ if (i != 1) { pwmcfg = w83627ehf_read_value(data, @@ -546,6 +554,17 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) W83627EHF_REG_FAN_STOP_OUTPUT[i]); data->fan_stop_time[i] = w83627ehf_read_value(data, W83627EHF_REG_FAN_STOP_TIME[i]); + + if (data->REG_FAN_MAX_OUTPUT[i] != 0xff) + data->fan_max_output[i] = + w83627ehf_read_value(data, + data->REG_FAN_MAX_OUTPUT[i]); + + if (data->REG_FAN_STEP_OUTPUT[i] != 0xff) + data->fan_step_output[i] = + w83627ehf_read_value(data, + data->REG_FAN_STEP_OUTPUT[i]); + data->target_temp[i] = w83627ehf_read_value(data, W83627EHF_REG_TARGET[i]) & @@ -1126,7 +1145,7 @@ store_##reg(struct device *dev, struct device_attribute *attr, \ u32 val = SENSORS_LIMIT(simple_strtoul(buf, NULL, 10), 1, 255); \ mutex_lock(&data->update_lock); \ data->reg[nr] = val; \ - w83627ehf_write_value(data, W83627EHF_REG_##REG[nr], val); \ + w83627ehf_write_value(data, data->REG_##REG[nr], val); \ mutex_unlock(&data->update_lock); \ return count; \ } @@ -1206,12 +1225,26 @@ static struct sensor_device_attribute sda_sf3_arrays[] = { store_fan_stop_output, 1), SENSOR_ATTR(pwm3_stop_output, S_IWUSR | S_IRUGO, show_fan_stop_output, store_fan_stop_output, 2), +}; - /* pwm1 and pwm3 don't support max and step settings */ + +/* + * pwm1 and pwm3 don't support max and step settings on all chips. + * Need to check support while generating/removing attribute files. + */ +static struct sensor_device_attribute sda_sf3_max_step_arrays[] = { + SENSOR_ATTR(pwm1_max_output, S_IWUSR | S_IRUGO, show_fan_max_output, + store_fan_max_output, 0), + SENSOR_ATTR(pwm1_step_output, S_IWUSR | S_IRUGO, show_fan_step_output, + store_fan_step_output, 0), SENSOR_ATTR(pwm2_max_output, S_IWUSR | S_IRUGO, show_fan_max_output, store_fan_max_output, 1), SENSOR_ATTR(pwm2_step_output, S_IWUSR | S_IRUGO, show_fan_step_output, store_fan_step_output, 1), + SENSOR_ATTR(pwm3_max_output, S_IWUSR | S_IRUGO, show_fan_max_output, + store_fan_max_output, 2), + SENSOR_ATTR(pwm3_step_output, S_IWUSR | S_IRUGO, show_fan_step_output, + store_fan_step_output, 2), }; static ssize_t @@ -1235,6 +1268,12 @@ static void w83627ehf_device_remove_files(struct device *dev) for (i = 0; i < ARRAY_SIZE(sda_sf3_arrays); i++) device_remove_file(dev, &sda_sf3_arrays[i].dev_attr); + for (i = 0; i < ARRAY_SIZE(sda_sf3_max_step_arrays); i++) { + struct sensor_device_attribute *attr = + &sda_sf3_max_step_arrays[i]; + if (data->REG_FAN_STEP_OUTPUT[attr->index] != 0xff) + device_remove_file(dev, &attr->dev_attr); + } for (i = 0; i < ARRAY_SIZE(sda_sf3_arrays_fan4); i++) device_remove_file(dev, &sda_sf3_arrays_fan4[i].dev_attr); for (i = 0; i < data->in_num; i++) { @@ -1352,6 +1391,11 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) data->in6_skip = !data->temp3_disable; } + data->REG_FAN_START_OUTPUT = W83627EHF_REG_FAN_START_OUTPUT; + data->REG_FAN_STOP_OUTPUT = W83627EHF_REG_FAN_STOP_OUTPUT; + data->REG_FAN_MAX_OUTPUT = W83627EHF_REG_FAN_MAX_OUTPUT; + data->REG_FAN_STEP_OUTPUT = W83627EHF_REG_FAN_STEP_OUTPUT; + /* Initialize the chip */ w83627ehf_init_device(data); @@ -1440,6 +1484,15 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) &sda_sf3_arrays[i].dev_attr))) goto exit_remove; + for (i = 0; i < ARRAY_SIZE(sda_sf3_max_step_arrays); i++) { + struct sensor_device_attribute *attr = + &sda_sf3_max_step_arrays[i]; + if (data->REG_FAN_STEP_OUTPUT[attr->index] != 0xff) { + err = device_create_file(dev, &attr->dev_attr); + if (err) + goto exit_remove; + } + } /* if fan4 is enabled create the sf3 files for it */ if ((data->has_fan & (1 << 3)) && data->pwm_num >= 4) for (i = 0; i < ARRAY_SIZE(sda_sf3_arrays_fan4); i++) { -- cgit v1.2.2 From c39aedafb242601729bef48db052ebc055ce3ab4 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 14 Aug 2010 21:08:55 +0200 Subject: hwmon: (w83627ehf) Add support for W83667HG-B Add support for W83667HG-B (very similar to the W83667HG). Signed-off-by: Guenter Roeck Signed-off-by: Jean Delvare --- Documentation/hwmon/w83627ehf | 15 ++++++++++----- drivers/hwmon/w83627ehf.c | 42 +++++++++++++++++++++++++++++++++--------- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/Documentation/hwmon/w83627ehf b/Documentation/hwmon/w83627ehf index b7e42ec4b26b..13d556112fc0 100644 --- a/Documentation/hwmon/w83627ehf +++ b/Documentation/hwmon/w83627ehf @@ -20,6 +20,10 @@ Supported chips: Prefix: 'w83667hg' Addresses scanned: ISA address retrieved from Super I/O registers Datasheet: not available + * Winbond W83667HG-B + Prefix: 'w83667hg' + Addresses scanned: ISA address retrieved from Super I/O registers + Datasheet: Available from Nuvoton upon request Authors: Jean Delvare @@ -32,8 +36,8 @@ Description ----------- This driver implements support for the Winbond W83627EHF, W83627EHG, -W83627DHG, W83627DHG-P and W83667HG super I/O chips. We will refer to them -collectively as Winbond chips. +W83627DHG, W83627DHG-P, W83667HG and W83667HG-B super I/O chips. +We will refer to them collectively as Winbond chips. The chips implement three temperature sensors, five fan rotation speed sensors, ten analog voltage sensors (only nine for the 627DHG), one @@ -68,14 +72,15 @@ follows: temp1 -> pwm1 temp2 -> pwm2 temp3 -> pwm3 -prog -> pwm4 (not on 667HG; the programmable setting is not supported by - the driver) +prog -> pwm4 (not on 667HG and 667HG-B; the programmable setting is not + supported by the driver) /sys files ---------- name - this is a standard hwmon device entry. For the W83627EHF and W83627EHG, - it is set to "w83627ehf" and for the W83627DHG it is set to "w83627dhg" + it is set to "w83627ehf", for the W83627DHG it is set to "w83627dhg", + and for the W83667HG it is set to "w83667hg". pwm[1-4] - this file stores PWM duty cycle or DC value (fan speed) in range: 0 (stop) to 255 (full) diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c index e01a3e97aa17..e96e69dd36fb 100644 --- a/drivers/hwmon/w83627ehf.c +++ b/drivers/hwmon/w83627ehf.c @@ -39,6 +39,7 @@ w83627dhg 9 5 4 3 0xa020 0xc1 0x5ca3 w83627dhg-p 9 5 4 3 0xb070 0xc1 0x5ca3 w83667hg 9 5 3 3 0xa510 0xc1 0x5ca3 + w83667hg-b 9 5 3 3 0xb350 0xc1 0x5ca3 */