From 13edb767aa609b6efb7c0c2b57fbd72a6ded0eed Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Mon, 7 Nov 2016 17:31:44 -0300 Subject: [PATCH 01/41] hwmon: (scpi) Fix module autoload If the driver is built as a module, autoload won't work because the module alias information is not filled. So user-space can't match the registered device with the corresponding module. Export the module alias information using the MODULE_DEVICE_TABLE() macro. Before this patch: $ modinfo drivers/hwmon/scpi-hwmon.ko | grep alias $ After this patch: $ modinfo drivers/hwmon/scpi-hwmon.ko | grep alias alias: of:N*T*Carm,scpi-sensorsC* alias: of:N*T*Carm,scpi-sensors Signed-off-by: Javier Martinez Canillas Fixes: ea98b29a05e9c ("hwmon: Support sensors exported via ARM SCP interface") Signed-off-by: Guenter Roeck --- drivers/hwmon/scpi-hwmon.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/scpi-hwmon.c b/drivers/hwmon/scpi-hwmon.c index 559a3dcd64d8..094f948f99ff 100644 --- a/drivers/hwmon/scpi-hwmon.c +++ b/drivers/hwmon/scpi-hwmon.c @@ -251,6 +251,7 @@ static const struct of_device_id scpi_of_match[] = { {.compatible = "arm,scpi-sensors"}, {}, }; +MODULE_DEVICE_TABLE(of, scpi_of_match); static struct platform_driver scpi_hwmon_platdrv = { .driver = { From 4538bfbf2d9f1fc48c07ac0cc0ee58716fe7fe96 Mon Sep 17 00:00:00 2001 From: Jared Bents Date: Fri, 18 Nov 2016 22:20:38 -0600 Subject: [PATCH 02/41] hwmon: (amc6821) sign extension temperature Converts the unsigned temperature values from the i2c read to be sign extended as defined in the datasheet so that negative temperatures are properly read. Fixes: 28e6274d8fa67 ("hwmon: (amc6821) Avoid forward declaration") Signed-off-by: Jared Bents Signed-off-by: Matt Weber [groeck: Dropped unnecessary continuation line] Signed-off-by: Guenter Roeck --- drivers/hwmon/amc6821.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/amc6821.c b/drivers/hwmon/amc6821.c index 12e851a5af48..46b4e35fd555 100644 --- a/drivers/hwmon/amc6821.c +++ b/drivers/hwmon/amc6821.c @@ -188,8 +188,8 @@ static struct amc6821_data *amc6821_update_device(struct device *dev) !data->valid) { for (i = 0; i < TEMP_IDX_LEN; i++) - data->temp[i] = i2c_smbus_read_byte_data(client, - temp_reg[i]); + data->temp[i] = (int8_t)i2c_smbus_read_byte_data( + client, temp_reg[i]); data->stat1 = i2c_smbus_read_byte_data(client, AMC6821_REG_STAT1); From aaf6fabf1be80b66aade0544e0ad619682e26616 Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Tue, 11 Oct 2016 10:26:31 +1300 Subject: [PATCH 03/41] hwmon: Add tc654 driver Add support for the tc654 and tc655 fan controllers from Microchip. http://ww1.microchip.com/downloads/en/DeviceDoc/20001734C.pdf Signed-off-by: Chris Packham Acked-by: Rob Herring [groeck: Fixed continuation line alignments] Signed-off-by: Guenter Roeck --- .../bindings/i2c/trivial-devices.txt | 2 + Documentation/hwmon/tc654 | 31 ++ drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/tc654.c | 514 ++++++++++++++++++ 5 files changed, 559 insertions(+) create mode 100644 Documentation/hwmon/tc654 create mode 100644 drivers/hwmon/tc654.c diff --git a/Documentation/devicetree/bindings/i2c/trivial-devices.txt b/Documentation/devicetree/bindings/i2c/trivial-devices.txt index fbbad6446741..beddd1a8d09d 100644 --- a/Documentation/devicetree/bindings/i2c/trivial-devices.txt +++ b/Documentation/devicetree/bindings/i2c/trivial-devices.txt @@ -121,6 +121,8 @@ microchip,mcp4662-502 Microchip 8-bit Dual I2C Digital Potentiometer with NV Mem microchip,mcp4662-103 Microchip 8-bit Dual I2C Digital Potentiometer with NV Memory (10k) microchip,mcp4662-503 Microchip 8-bit Dual I2C Digital Potentiometer with NV Memory (50k) microchip,mcp4662-104 Microchip 8-bit Dual I2C Digital Potentiometer with NV Memory (100k) +microchip,tc654 PWM Fan Speed Controller With Fan Fault Detection +microchip,tc655 PWM Fan Speed Controller With Fan Fault Detection national,lm63 Temperature sensor with integrated fan control national,lm75 I2C TEMP SENSOR national,lm80 Serial Interface ACPI-Compatible Microprocessor System Hardware Monitor diff --git a/Documentation/hwmon/tc654 b/Documentation/hwmon/tc654 new file mode 100644 index 000000000000..91a2843f5f98 --- /dev/null +++ b/Documentation/hwmon/tc654 @@ -0,0 +1,31 @@ +Kernel driver tc654 +=================== + +Supported chips: + * Microship TC654 and TC655 + Prefix: 'tc654' + Datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/20001734C.pdf + +Authors: + Chris Packham + Masahiko Iwamoto + +Description +----------- +This driver implements support for the Microchip TC654 and TC655. + +The TC654 uses the 2-wire interface compatible with the SMBUS 2.0 +specification. The TC654 has two (2) inputs for measuring fan RPM and +one (1) PWM output which can be used for fan control. + +Configuration Notes +------------------- +Ordinarily the pwm1_mode ABI is used for controlling the pwm output +mode. However, for this chip the output is always pwm, and the +pwm1_mode determines if the pwm output is controlled via the pwm1 value +or via the Vin analog input. + + +Setting pwm1_mode to 1 will cause the pwm output to be driven based on +the pwm1 value. Setting pwm1_mode to 0 will cause the pwm output to be +driven based on the Vin input. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 45cef3d2c75c..8681bc65cde5 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -907,6 +907,17 @@ config SENSORS_MCP3021 This driver can also be built as a module. If so, the module will be called mcp3021. +config SENSORS_TC654 + tristate "Microchip TC654/TC655 and compatibles" + depends on I2C + help + If you say yes here you get support for TC654 and TC655. + The TC654 and TC655 are PWM mode fan speed controllers with + FanSense technology for use with brushless DC fans. + + This driver can also be built as a module. If so, the module + will be called tc654. + config SENSORS_MENF21BMC_HWMON tristate "MEN 14F021P00 BMC Hardware Monitoring" depends on MFD_MENF21BMC diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index aecf4ba17460..c651f0f1d047 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -122,6 +122,7 @@ obj-$(CONFIG_SENSORS_MAX6697) += max6697.o obj-$(CONFIG_SENSORS_MAX31790) += max31790.o obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o +obj-$(CONFIG_SENSORS_TC654) += tc654.o obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o obj-$(CONFIG_SENSORS_NCT6683) += nct6683.o obj-$(CONFIG_SENSORS_NCT6775) += nct6775.o diff --git a/drivers/hwmon/tc654.c b/drivers/hwmon/tc654.c new file mode 100644 index 000000000000..18136e1f95fd --- /dev/null +++ b/drivers/hwmon/tc654.c @@ -0,0 +1,514 @@ +/* + * tc654.c - Linux kernel modules for fan speed controller + * + * Copyright (C) 2016 Allied Telesis Labs NZ + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum tc654_regs { + TC654_REG_RPM1 = 0x00, /* RPM Output 1 */ + TC654_REG_RPM2 = 0x01, /* RPM Output 2 */ + TC654_REG_FAN_FAULT1 = 0x02, /* Fan Fault 1 Threshold */ + TC654_REG_FAN_FAULT2 = 0x03, /* Fan Fault 2 Threshold */ + TC654_REG_CONFIG = 0x04, /* Configuration */ + TC654_REG_STATUS = 0x05, /* Status */ + TC654_REG_DUTY_CYCLE = 0x06, /* Fan Speed Duty Cycle */ + TC654_REG_MFR_ID = 0x07, /* Manufacturer Identification */ + TC654_REG_VER_ID = 0x08, /* Version Identification */ +}; + +/* Macros to easily index the registers */ +#define TC654_REG_RPM(idx) (TC654_REG_RPM1 + (idx)) +#define TC654_REG_FAN_FAULT(idx) (TC654_REG_FAN_FAULT1 + (idx)) + +/* Config register bits */ +#define TC654_REG_CONFIG_RES BIT(6) /* Resolution Selection */ +#define TC654_REG_CONFIG_DUTYC BIT(5) /* Duty Cycle Control */ +#define TC654_REG_CONFIG_SDM BIT(0) /* Shutdown Mode */ + +/* Status register bits */ +#define TC654_REG_STATUS_F2F BIT(1) /* Fan 2 Fault */ +#define TC654_REG_STATUS_F1F BIT(0) /* Fan 1 Fault */ + +/* RPM resolution for RPM Output registers */ +#define TC654_HIGH_RPM_RESOLUTION 25 /* 25 RPM resolution */ +#define TC654_LOW_RPM_RESOLUTION 50 /* 50 RPM resolution */ + +/* Convert to the fan fault RPM threshold from register value */ +#define TC654_FAN_FAULT_FROM_REG(val) ((val) * 50) /* 50 RPM resolution */ + +/* Convert to register value from the fan fault RPM threshold */ +#define TC654_FAN_FAULT_TO_REG(val) (((val) / 50) & 0xff) + +/* Register data is read (and cached) at most once per second. */ +#define TC654_UPDATE_INTERVAL HZ + +struct tc654_data { + struct i2c_client *client; + + /* update mutex */ + struct mutex update_lock; + + /* tc654 register cache */ + bool valid; + unsigned long last_updated; /* in jiffies */ + + u8 rpm_output[2]; /* The fan RPM data for fans 1 and 2 is then + * written to registers RPM1 and RPM2 + */ + u8 fan_fault[2]; /* The Fan Fault Threshold Registers are used to + * set the fan fault threshold levels for fan 1 + * and fan 2 + */ + u8 config; /* The Configuration Register is an 8-bit read/ + * writable multi-function control register + * 7: Fan Fault Clear + * 1 = Clear Fan Fault + * 0 = Normal Operation (default) + * 6: Resolution Selection for RPM Output Registers + * RPM Output Registers (RPM1 and RPM2) will be + * set for + * 1 = 25 RPM (9-bit) resolution + * 0 = 50 RPM (8-bit) resolution (default) + * 5: Duty Cycle Control Method + * The V OUT duty cycle will be controlled via + * 1 = the SMBus interface. + * 0 = via the V IN analog input pin. (default) + * 4,3: Fan 2 Pulses Per Rotation + * 00 = 1 + * 01 = 2 (default) + * 10 = 4 + * 11 = 8 + * 2,1: Fan 1 Pulses Per Rotation + * 00 = 1 + * 01 = 2 (default) + * 10 = 4 + * 11 = 8 + * 0: Shutdown Mode + * 1 = Shutdown mode. + * 0 = Normal operation. (default) + */ + u8 status; /* The Status register provides all the information + * about what is going on within the TC654/TC655 + * devices. + * 7,6: Unimplemented, Read as '0' + * 5: Over-Temperature Fault Condition + * 1 = Over-Temperature condition has occurred + * 0 = Normal operation. V IN is less than 2.6V + * 4: RPM2 Counter Overflow + * 1 = Fault condition + * 0 = Normal operation + * 3: RPM1 Counter Overflow + * 1 = Fault condition + * 0 = Normal operation + * 2: V IN Input Status + * 1 = V IN is open + * 0 = Normal operation. voltage present at V IN + * 1: Fan 2 Fault + * 1 = Fault condition + * 0 = Normal operation + * 0: Fan 1 Fault + * 1 = Fault condition + * 0 = Normal operation + */ + u8 duty_cycle; /* The DUTY_CYCLE register is a 4-bit read/ + * writable register used to control the duty + * cycle of the V OUT output. + */ +}; + +/* helper to grab and cache data, at most one time per second */ +static struct tc654_data *tc654_update_client(struct device *dev) +{ + struct tc654_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int ret = 0; + + mutex_lock(&data->update_lock); + if (time_before(jiffies, data->last_updated + TC654_UPDATE_INTERVAL) && + likely(data->valid)) + goto out; + + ret = i2c_smbus_read_byte_data(client, TC654_REG_RPM(0)); + if (ret < 0) + goto out; + data->rpm_output[0] = ret; + + ret = i2c_smbus_read_byte_data(client, TC654_REG_RPM(1)); + if (ret < 0) + goto out; + data->rpm_output[1] = ret; + + ret = i2c_smbus_read_byte_data(client, TC654_REG_FAN_FAULT(0)); + if (ret < 0) + goto out; + data->fan_fault[0] = ret; + + ret = i2c_smbus_read_byte_data(client, TC654_REG_FAN_FAULT(1)); + if (ret < 0) + goto out; + data->fan_fault[1] = ret; + + ret = i2c_smbus_read_byte_data(client, TC654_REG_CONFIG); + if (ret < 0) + goto out; + data->config = ret; + + ret = i2c_smbus_read_byte_data(client, TC654_REG_STATUS); + if (ret < 0) + goto out; + data->status = ret; + + ret = i2c_smbus_read_byte_data(client, TC654_REG_DUTY_CYCLE); + if (ret < 0) + goto out; + data->duty_cycle = ret & 0x0f; + + data->last_updated = jiffies; + data->valid = true; +out: + mutex_unlock(&data->update_lock); + + if (ret < 0) /* upon error, encode it in return value */ + data = ERR_PTR(ret); + + return data; +} + +/* + * sysfs attributes + */ + +static ssize_t show_fan(struct device *dev, struct device_attribute *da, + char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct tc654_data *data = tc654_update_client(dev); + int val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + if (data->config & TC654_REG_CONFIG_RES) + val = data->rpm_output[nr] * TC654_HIGH_RPM_RESOLUTION; + else + val = data->rpm_output[nr] * TC654_LOW_RPM_RESOLUTION; + + return sprintf(buf, "%d\n", val); +} + +static ssize_t show_fan_min(struct device *dev, struct device_attribute *da, + char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct tc654_data *data = tc654_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", + TC654_FAN_FAULT_FROM_REG(data->fan_fault[nr])); +} + +static ssize_t set_fan_min(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(da)->index; + struct tc654_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int ret; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + val = clamp_val(val, 0, 12750); + + mutex_lock(&data->update_lock); + + data->fan_fault[nr] = TC654_FAN_FAULT_TO_REG(val); + ret = i2c_smbus_write_byte_data(client, TC654_REG_FAN_FAULT(nr), + data->fan_fault[nr]); + + mutex_unlock(&data->update_lock); + return ret < 0 ? ret : count; +} + +static ssize_t show_fan_alarm(struct device *dev, struct device_attribute *da, + char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct tc654_data *data = tc654_update_client(dev); + int val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + if (nr == 0) + val = !!(data->status & TC654_REG_STATUS_F1F); + else + val = !!(data->status & TC654_REG_STATUS_F2F); + + return sprintf(buf, "%d\n", val); +} + +static const u8 TC654_FAN_PULSE_SHIFT[] = { 1, 3 }; + +static ssize_t show_fan_pulses(struct device *dev, struct device_attribute *da, + char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct tc654_data *data = tc654_update_client(dev); + u8 val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + val = BIT((data->config >> TC654_FAN_PULSE_SHIFT[nr]) & 0x03); + return sprintf(buf, "%d\n", val); +} + +static ssize_t set_fan_pulses(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(da)->index; + struct tc654_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + u8 config; + unsigned long val; + int ret; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + switch (val) { + case 1: + config = 0; + break; + case 2: + config = 1; + break; + case 4: + config = 2; + break; + case 8: + config = 3; + break; + default: + return -EINVAL; + } + + mutex_lock(&data->update_lock); + + data->config &= ~(0x03 << TC654_FAN_PULSE_SHIFT[nr]); + data->config |= (config << TC654_FAN_PULSE_SHIFT[nr]); + ret = i2c_smbus_write_byte_data(client, TC654_REG_CONFIG, data->config); + + mutex_unlock(&data->update_lock); + return ret < 0 ? ret : count; +} + +static ssize_t show_pwm_mode(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct tc654_data *data = tc654_update_client(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", !!(data->config & TC654_REG_CONFIG_DUTYC)); +} + +static ssize_t set_pwm_mode(struct device *dev, + struct device_attribute *da, + const char *buf, size_t count) +{ + struct tc654_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int ret; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + + if (val != 0 && val != 1) + return -EINVAL; + + mutex_lock(&data->update_lock); + + if (val) + data->config |= TC654_REG_CONFIG_DUTYC; + else + data->config &= ~TC654_REG_CONFIG_DUTYC; + + ret = i2c_smbus_write_byte_data(client, TC654_REG_CONFIG, data->config); + + mutex_unlock(&data->update_lock); + return ret < 0 ? ret : count; +} + +static const int tc654_pwm_map[16] = { 77, 88, 102, 112, 124, 136, 148, 160, + 172, 184, 196, 207, 219, 231, 243, 255}; + +static ssize_t show_pwm(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct tc654_data *data = tc654_update_client(dev); + int pwm; + + if (IS_ERR(data)) + return PTR_ERR(data); + + if (data->config & TC654_REG_CONFIG_SDM) + pwm = 0; + else + pwm = tc654_pwm_map[data->duty_cycle]; + + return sprintf(buf, "%d\n", pwm); +} + +static ssize_t set_pwm(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct tc654_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + unsigned long val; + int ret; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + if (val > 255) + return -EINVAL; + + mutex_lock(&data->update_lock); + + if (val == 0) + data->config |= TC654_REG_CONFIG_SDM; + else + data->config &= ~TC654_REG_CONFIG_SDM; + + data->duty_cycle = find_closest(val, tc654_pwm_map, + ARRAY_SIZE(tc654_pwm_map)); + + ret = i2c_smbus_write_byte_data(client, TC654_REG_CONFIG, data->config); + if (ret < 0) + goto out; + + ret = i2c_smbus_write_byte_data(client, TC654_REG_DUTY_CYCLE, + data->duty_cycle); + +out: + mutex_unlock(&data->update_lock); + return ret < 0 ? ret : count; +} + +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(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(fan1_alarm, S_IRUGO, show_fan_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(fan1_pulses, S_IWUSR | S_IRUGO, show_fan_pulses, + set_fan_pulses, 0); +static SENSOR_DEVICE_ATTR(fan2_pulses, S_IWUSR | S_IRUGO, show_fan_pulses, + set_fan_pulses, 1); +static SENSOR_DEVICE_ATTR(pwm1_mode, S_IWUSR | S_IRUGO, + show_pwm_mode, set_pwm_mode, 0); +static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm, + set_pwm, 0); + +/* Driver data */ +static struct attribute *tc654_attrs[] = { + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan2_min.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + &sensor_dev_attr_fan1_pulses.dev_attr.attr, + &sensor_dev_attr_fan2_pulses.dev_attr.attr, + &sensor_dev_attr_pwm1_mode.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + NULL +}; + +ATTRIBUTE_GROUPS(tc654); + +/* + * device probe and removal + */ + +static int tc654_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct tc654_data *data; + struct device *hwmon_dev; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + data = devm_kzalloc(dev, sizeof(struct tc654_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->update_lock); + + ret = i2c_smbus_read_byte_data(client, TC654_REG_CONFIG); + if (ret < 0) + return ret; + + data->config = ret; + + hwmon_dev = + devm_hwmon_device_register_with_groups(dev, client->name, data, + tc654_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct i2c_device_id tc654_id[] = { + {"tc654", 0}, + {"tc655", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, tc654_id); + +static struct i2c_driver tc654_driver = { + .driver = { + .name = "tc654", + }, + .probe = tc654_probe, + .id_table = tc654_id, +}; + +module_i2c_driver(tc654_driver); + +MODULE_AUTHOR("Allied Telesis Labs"); +MODULE_DESCRIPTION("Microchip TC654/TC655 driver"); +MODULE_LICENSE("GPL"); From 2b3d0c19537c1bd0d493e302b7949460901ef489 Mon Sep 17 00:00:00 2001 From: Yi Li Date: Mon, 17 Oct 2016 18:38:53 +1030 Subject: [PATCH 04/41] hwmon: (adm1275) Enable adm1278 VOUT sampling The adm1278 can optionally monitor the VOUT pin. This functionality is not enabled at reset, so PMON_CONFIG needs to be modified in order to enable it. Signed-off-by: Yi Li Signed-off-by: Joel Stanley Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/adm1275.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c index 3baa4f4a8c5e..4ab5293c7bf0 100644 --- a/drivers/hwmon/pmbus/adm1275.c +++ b/drivers/hwmon/pmbus/adm1275.c @@ -499,15 +499,27 @@ static int adm1275_probe(struct i2c_client *client, pindex = 2; tindex = 3; - info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT; + info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; + + /* Enable VOUT if not enabled (it is disabled by default) */ + if (!(config & ADM1278_VOUT_EN)) { + config |= ADM1278_VOUT_EN; + ret = i2c_smbus_write_byte_data(client, + ADM1275_PMON_CONFIG, + config); + if (ret < 0) { + dev_err(&client->dev, + "Failed to enable VOUT monitoring\n"); + return -ENODEV; + } + } + if (config & ADM1278_TEMP1_EN) info->func[0] |= PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; if (config & ADM1278_VIN_EN) info->func[0] |= PMBUS_HAVE_VIN; - if (config & ADM1278_VOUT_EN) - info->func[0] |= - PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; break; case adm1293: case adm1294: From 00a0c905ab4b2b47372be8da6a8442dcd3681903 Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Wed, 26 Oct 2016 10:56:59 -0600 Subject: [PATCH 05/41] hwmon: (lm87) Use hwmon to create the sysfs groups This is the expected thing for a hwmon driver to do, this changes the sysfs paths from, say: /sys/bus/i2c/devices/0-002c/temp1_input to: /sys/bus/i2c/devices/0-002c/hwmon/hwmon0/temp1_input Signed-off-by: Jason Gunthorpe Signed-off-by: Guenter Roeck --- drivers/hwmon/lm87.c | 128 ++++++++++++++++--------------------------- 1 file changed, 47 insertions(+), 81 deletions(-) diff --git a/drivers/hwmon/lm87.c b/drivers/hwmon/lm87.c index a5e295826aea..81cb898245a1 100644 --- a/drivers/hwmon/lm87.c +++ b/drivers/hwmon/lm87.c @@ -154,7 +154,6 @@ static u8 LM87_REG_TEMP_LOW[3] = { 0x3A, 0x38, 0x2C }; */ struct lm87_data { - struct device *hwmon_dev; struct mutex update_lock; char valid; /* zero until following fields are valid */ unsigned long last_updated; /* In jiffies */ @@ -181,6 +180,8 @@ struct lm87_data { u16 alarms; /* register values, combined */ u8 vid; /* register values, combined */ u8 vrm; + + const struct attribute_group *attr_groups[6]; }; static inline int lm87_read_value(struct i2c_client *client, u8 reg) @@ -195,7 +196,7 @@ static inline int lm87_write_value(struct i2c_client *client, u8 reg, u8 value) static struct lm87_data *lm87_update_device(struct device *dev) { - struct i2c_client *client = to_i2c_client(dev); + struct i2c_client *client = dev_get_drvdata(dev); struct lm87_data *data = i2c_get_clientdata(client); mutex_lock(&data->update_lock); @@ -309,7 +310,7 @@ static ssize_t show_in_max(struct device *dev, static ssize_t set_in_min(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); + struct i2c_client *client = dev_get_drvdata(dev); struct lm87_data *data = i2c_get_clientdata(client); int nr = to_sensor_dev_attr(attr)->index; long val; @@ -330,7 +331,7 @@ static ssize_t set_in_min(struct device *dev, struct device_attribute *attr, static ssize_t set_in_max(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); + struct i2c_client *client = dev_get_drvdata(dev); struct lm87_data *data = i2c_get_clientdata(client); int nr = to_sensor_dev_attr(attr)->index; long val; @@ -396,7 +397,7 @@ static ssize_t show_temp_high(struct device *dev, static ssize_t set_temp_low(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); + struct i2c_client *client = dev_get_drvdata(dev); struct lm87_data *data = i2c_get_clientdata(client); int nr = to_sensor_dev_attr(attr)->index; long val; @@ -416,7 +417,7 @@ static ssize_t set_temp_low(struct device *dev, struct device_attribute *attr, static ssize_t set_temp_high(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); + struct i2c_client *client = dev_get_drvdata(dev); struct lm87_data *data = i2c_get_clientdata(client); int nr = to_sensor_dev_attr(attr)->index; long val; @@ -495,7 +496,7 @@ static ssize_t show_fan_div(struct device *dev, static ssize_t set_fan_min(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); + struct i2c_client *client = dev_get_drvdata(dev); struct lm87_data *data = i2c_get_clientdata(client); int nr = to_sensor_dev_attr(attr)->index; long val; @@ -522,7 +523,7 @@ static ssize_t set_fan_min(struct device *dev, struct device_attribute *attr, static ssize_t set_fan_div(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); + struct i2c_client *client = dev_get_drvdata(dev); struct lm87_data *data = i2c_get_clientdata(client); int nr = to_sensor_dev_attr(attr)->index; long val; @@ -635,7 +636,7 @@ static ssize_t show_aout(struct device *dev, struct device_attribute *attr, static ssize_t set_aout(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev); + struct i2c_client *client = dev_get_drvdata(dev); struct lm87_data *data = i2c_get_clientdata(client); long val; int err; @@ -841,23 +842,18 @@ static int lm87_detect(struct i2c_client *client, struct i2c_board_info *info) return 0; } -static void lm87_remove_files(struct i2c_client *client) +static void lm87_restore_config(void *arg) { - struct device *dev = &client->dev; + struct i2c_client *client = arg; + struct lm87_data *data = i2c_get_clientdata(client); - sysfs_remove_group(&dev->kobj, &lm87_group); - sysfs_remove_group(&dev->kobj, &lm87_group_in6); - sysfs_remove_group(&dev->kobj, &lm87_group_fan1); - sysfs_remove_group(&dev->kobj, &lm87_group_in7); - sysfs_remove_group(&dev->kobj, &lm87_group_fan2); - sysfs_remove_group(&dev->kobj, &lm87_group_temp3); - sysfs_remove_group(&dev->kobj, &lm87_group_in0_5); - sysfs_remove_group(&dev->kobj, &lm87_group_vid); + lm87_write_value(client, LM87_REG_CONFIG, data->config); } -static void lm87_init_client(struct i2c_client *client) +static int lm87_init_client(struct i2c_client *client) { struct lm87_data *data = i2c_get_clientdata(client); + int rc; if (dev_get_platdata(&client->dev)) { data->channel = *(u8 *)dev_get_platdata(&client->dev); @@ -868,6 +864,10 @@ static void lm87_init_client(struct i2c_client *client) } data->config = lm87_read_value(client, LM87_REG_CONFIG) & 0x6F; + rc = devm_add_action(&client->dev, lm87_restore_config, client); + if (rc) + return rc; + if (!(data->config & 0x01)) { int i; @@ -895,12 +895,15 @@ static void lm87_init_client(struct i2c_client *client) if ((data->config & 0x09) != 0x01) lm87_write_value(client, LM87_REG_CONFIG, (data->config & 0x77) | 0x01); + return 0; } static int lm87_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct lm87_data *data; + struct device *hwmon_dev; int err; + unsigned int group_tail = 0; data = devm_kzalloc(&client->dev, sizeof(struct lm87_data), GFP_KERNEL); if (!data) @@ -910,7 +913,9 @@ static int lm87_probe(struct i2c_client *client, const struct i2c_device_id *id) mutex_init(&data->update_lock); /* Initialize the LM87 chip */ - lm87_init_client(client); + err = lm87_init_client(client); + if (err) + return err; data->in_scale[0] = 2500; data->in_scale[1] = 2700; @@ -921,72 +926,34 @@ static int lm87_probe(struct i2c_client *client, const struct i2c_device_id *id) data->in_scale[6] = 1875; data->in_scale[7] = 1875; - /* Register sysfs hooks */ - err = sysfs_create_group(&client->dev.kobj, &lm87_group); - if (err) - goto exit_stop; + /* + * Construct the list of attributes, the list depends on the + * configuration of the chip + */ + data->attr_groups[group_tail++] = &lm87_group; + if (data->channel & CHAN_NO_FAN(0)) + data->attr_groups[group_tail++] = &lm87_group_in6; + else + data->attr_groups[group_tail++] = &lm87_group_fan1; - if (data->channel & CHAN_NO_FAN(0)) { - err = sysfs_create_group(&client->dev.kobj, &lm87_group_in6); - if (err) - goto exit_remove; - } else { - err = sysfs_create_group(&client->dev.kobj, &lm87_group_fan1); - if (err) - goto exit_remove; - } + if (data->channel & CHAN_NO_FAN(1)) + data->attr_groups[group_tail++] = &lm87_group_in7; + else + data->attr_groups[group_tail++] = &lm87_group_fan2; - if (data->channel & CHAN_NO_FAN(1)) { - err = sysfs_create_group(&client->dev.kobj, &lm87_group_in7); - if (err) - goto exit_remove; - } else { - err = sysfs_create_group(&client->dev.kobj, &lm87_group_fan2); - if (err) - goto exit_remove; - } - - if (data->channel & CHAN_TEMP3) { - err = sysfs_create_group(&client->dev.kobj, &lm87_group_temp3); - if (err) - goto exit_remove; - } else { - err = sysfs_create_group(&client->dev.kobj, &lm87_group_in0_5); - if (err) - goto exit_remove; - } + if (data->channel & CHAN_TEMP3) + data->attr_groups[group_tail++] = &lm87_group_temp3; + else + data->attr_groups[group_tail++] = &lm87_group_in0_5; if (!(data->channel & CHAN_NO_VID)) { data->vrm = vid_which_vrm(); - err = sysfs_create_group(&client->dev.kobj, &lm87_group_vid); - if (err) - goto exit_remove; + data->attr_groups[group_tail++] = &lm87_group_vid; } - 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: - lm87_remove_files(client); -exit_stop: - lm87_write_value(client, LM87_REG_CONFIG, data->config); - return err; -} - -static int lm87_remove(struct i2c_client *client) -{ - struct lm87_data *data = i2c_get_clientdata(client); - - hwmon_device_unregister(data->hwmon_dev); - lm87_remove_files(client); - - lm87_write_value(client, LM87_REG_CONFIG, data->config); - return 0; + hwmon_dev = devm_hwmon_device_register_with_groups( + &client->dev, client->name, client, data->attr_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); } /* @@ -1006,7 +973,6 @@ static struct i2c_driver lm87_driver = { .name = "lm87", }, .probe = lm87_probe, - .remove = lm87_remove, .id_table = lm87_id, .detect = lm87_detect, .address_list = normal_i2c, From 10de07f446a6aea66c9fea937f8e3a498b1b6766 Mon Sep 17 00:00:00 2001 From: Clemens Gruber Date: Fri, 28 Oct 2016 00:33:44 +0200 Subject: [PATCH 06/41] hwmon: (mcp3021) add devicetree bindings documentation Document the devicetree bindings for the Microchip MCP3021/3221. Signed-off-by: Clemens Gruber Acked-by: Rob Herring Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/mcp3021.txt | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/mcp3021.txt diff --git a/Documentation/devicetree/bindings/hwmon/mcp3021.txt b/Documentation/devicetree/bindings/hwmon/mcp3021.txt new file mode 100644 index 000000000000..294318ba6914 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/mcp3021.txt @@ -0,0 +1,21 @@ +mcp3021 properties + +Required properties: +- compatible: Must be one of the following: + - "microchip,mcp3021" for mcp3021 + - "microchip,mcp3221" for mcp3221 +- reg: I2C address + +Optional properties: + +- reference-voltage-microvolt + Reference voltage in microvolt (uV) + +Example: + +mcp3021@4d { + compatible = "microchip,mcp3021"; + reg = <0x4d>; + + reference-voltage-microvolt = <4500000>; /* 4.5 V */ +}; From b502a926d29ed23eb7f6d61a89f6fb5d9b742b3e Mon Sep 17 00:00:00 2001 From: Clemens Gruber Date: Wed, 9 Nov 2016 18:16:14 +0100 Subject: [PATCH 07/41] hwmon: (mcp3021) replace S_IRUGO with 0444 Replace S_IRUGO with the better readable 0444. This fixes a checkpatch warning. Signed-off-by: Clemens Gruber Signed-off-by: Guenter Roeck --- drivers/hwmon/mcp3021.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/mcp3021.c b/drivers/hwmon/mcp3021.c index 972444a14cca..7225d24e52ae 100644 --- a/drivers/hwmon/mcp3021.c +++ b/drivers/hwmon/mcp3021.c @@ -99,7 +99,7 @@ static ssize_t show_in_input(struct device *dev, struct device_attribute *attr, return sprintf(buf, "%d\n", in_input); } -static DEVICE_ATTR(in0_input, S_IRUGO, show_in_input, NULL); +static DEVICE_ATTR(in0_input, 0444, show_in_input, NULL); static int mcp3021_probe(struct i2c_client *client, const struct i2c_device_id *id) From f4dc811c16b91ed0a8c9dd5a863409279d14a581 Mon Sep 17 00:00:00 2001 From: Clemens Gruber Date: Wed, 9 Nov 2016 22:22:34 +0100 Subject: [PATCH 08/41] hwmon: (mcp3021) add devicetree support Support setting the reference voltage from the device tree. Signed-off-by: Clemens Gruber Signed-off-by: Guenter Roeck --- drivers/hwmon/mcp3021.c | 48 ++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/drivers/hwmon/mcp3021.c b/drivers/hwmon/mcp3021.c index 7225d24e52ae..1929734c3b1d 100644 --- a/drivers/hwmon/mcp3021.c +++ b/drivers/hwmon/mcp3021.c @@ -4,6 +4,7 @@ * Copyright (C) 2008-2009, 2012 Freescale Semiconductor, Inc. * Author: Mingkai Hu * Reworked by Sven Schuchmann + * DT support added by Clemens Gruber * * This driver export the value of analog input voltage to sysfs, the * voltage unit is mV. Through the sysfs interface, lm-sensors tool @@ -22,11 +23,13 @@ #include #include #include +#include +#include -/* Vdd info */ -#define MCP3021_VDD_MAX 5500 -#define MCP3021_VDD_MIN 2700 -#define MCP3021_VDD_REF 3300 +/* Vdd / reference voltage in millivolt */ +#define MCP3021_VDD_REF_MAX 5500 +#define MCP3021_VDD_REF_MIN 2700 +#define MCP3021_VDD_REF_DEFAULT 3300 /* output format */ #define MCP3021_SAR_SHIFT 2 @@ -47,7 +50,7 @@ enum chips { */ struct mcp3021_data { struct device *hwmon_dev; - u32 vdd; /* device power supply */ + u32 vdd; /* supply and reference voltage in millivolt */ u16 sar_shift; u16 sar_mask; u8 output_res; @@ -106,6 +109,7 @@ static int mcp3021_probe(struct i2c_client *client, { int err; struct mcp3021_data *data = NULL; + struct device_node *np = client->dev.of_node; if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) return -ENODEV; @@ -117,6 +121,21 @@ static int mcp3021_probe(struct i2c_client *client, i2c_set_clientdata(client, data); + if (np) { + if (!of_property_read_u32(np, "reference-voltage-microvolt", + &data->vdd)) + data->vdd /= 1000; + else + data->vdd = MCP3021_VDD_REF_DEFAULT; + } else { + u32 *pdata = dev_get_platdata(&client->dev); + + if (pdata) + data->vdd = *pdata; + else + data->vdd = MCP3021_VDD_REF_DEFAULT; + } + switch (id->driver_data) { case mcp3021: data->sar_shift = MCP3021_SAR_SHIFT; @@ -131,13 +150,8 @@ static int mcp3021_probe(struct i2c_client *client, break; } - if (dev_get_platdata(&client->dev)) { - data->vdd = *(u32 *)dev_get_platdata(&client->dev); - if (data->vdd > MCP3021_VDD_MAX || data->vdd < MCP3021_VDD_MIN) - return -EINVAL; - } else { - data->vdd = MCP3021_VDD_REF; - } + if (data->vdd > MCP3021_VDD_REF_MAX || data->vdd < MCP3021_VDD_REF_MIN) + return -EINVAL; err = sysfs_create_file(&client->dev.kobj, &dev_attr_in0_input.attr); if (err) @@ -173,9 +187,19 @@ static const struct i2c_device_id mcp3021_id[] = { }; MODULE_DEVICE_TABLE(i2c, mcp3021_id); +#ifdef CONFIG_OF +static const struct of_device_id of_mcp3021_match[] = { + { .compatible = "microchip,mcp3021", .data = (void *)mcp3021 }, + { .compatible = "microchip,mcp3221", .data = (void *)mcp3221 }, + { } +}; +MODULE_DEVICE_TABLE(of, of_mcp3021_match); +#endif + static struct i2c_driver mcp3021_driver = { .driver = { .name = "mcp3021", + .of_match_table = of_match_ptr(of_mcp3021_match), }, .probe = mcp3021_probe, .remove = mcp3021_remove, From 3d7e0a24947c076a6cf6080c5f076c60ae8d9543 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Thu, 17 Nov 2016 19:35:26 +0100 Subject: [PATCH 09/41] hwmon: (via-cputemp) Remove pointless CPU check on each CPU The check loop for the cpu type is pointless as we already have a cpu model match before that. The only thing which is not covered by that check would be a smp system with two different cores. Not likely to happen. Cc: Jean Delvare Cc: Guenter Roeck Signed-off-by: Thomas Gleixner Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Guenter Roeck --- drivers/hwmon/via-cputemp.c | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/drivers/hwmon/via-cputemp.c b/drivers/hwmon/via-cputemp.c index ac91c07e3f90..5b9866b1b437 100644 --- a/drivers/hwmon/via-cputemp.c +++ b/drivers/hwmon/via-cputemp.c @@ -319,22 +319,8 @@ static int __init via_cputemp_init(void) goto exit; cpu_notifier_register_begin(); - for_each_online_cpu(i) { - struct cpuinfo_x86 *c = &cpu_data(i); - - if (c->x86 != 6) - continue; - - if (c->x86_model < 0x0a) - continue; - - if (c->x86_model > 0x0f) { - pr_warn("Unknown CPU model 0x%x\n", c->x86_model); - continue; - } - + for_each_online_cpu(i) via_cputemp_device_add(i); - } #ifndef CONFIG_HOTPLUG_CPU if (list_empty(&pdev_list)) { From 1b109c49b72dc3cb8392bbc22bad662f71b8fd80 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Fri, 14 Oct 2016 11:43:34 +0200 Subject: [PATCH 10/41] hwmon: (adt7411) update to new hwmon registration API This is also a preparation for to support more properties like min, max and alarm. Signed-off-by: Michael Walle [groeck: Minor alignment changes] Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7411.c | 303 ++++++++++++++++++++++++---------------- 1 file changed, 181 insertions(+), 122 deletions(-) diff --git a/drivers/hwmon/adt7411.c b/drivers/hwmon/adt7411.c index 812fbc00f693..bdeaece9641d 100644 --- a/drivers/hwmon/adt7411.c +++ b/drivers/hwmon/adt7411.c @@ -55,7 +55,7 @@ struct adt7411_data { struct mutex device_lock; /* for "atomic" device accesses */ struct mutex update_lock; unsigned long next_update; - int vref_cached; + long vref_cached; struct i2c_client *client; bool use_ext_temp; }; @@ -114,85 +114,6 @@ static int adt7411_modify_bit(struct i2c_client *client, u8 reg, u8 bit, return ret; } -static ssize_t adt7411_show_vdd(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct adt7411_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; - 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) -{ - int nr = to_sensor_dev_attr(attr)->index; - struct adt7411_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; - int val; - struct { - u8 low; - u8 high; - } reg[2] = { - { ADT7411_REG_INT_TEMP_VDD_LSB, ADT7411_REG_INT_TEMP_MSB }, - { ADT7411_REG_EXT_TEMP_AIN14_LSB, - ADT7411_REG_EXT_TEMP_AIN1_MSB }, - }; - - val = adt7411_read_10_bit(client, reg[nr].low, reg[nr].high, 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 adt7411_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; - int val; - u8 lsb_reg, lsb_shift; - - mutex_lock(&data->update_lock); - if (time_after_eq(jiffies, data->next_update)) { - val = i2c_smbus_read_byte_data(client, ADT7411_REG_CFG3); - if (val < 0) - goto exit_unlock; - - if (val & ADT7411_CFG3_REF_VDD) { - val = adt7411_read_10_bit(client, - ADT7411_REG_INT_TEMP_VDD_LSB, - ADT7411_REG_VDD_MSB, 2); - if (val < 0) - goto exit_unlock; - - 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); - if (val < 0) - goto exit_unlock; - - val = sprintf(buf, "%u\n", val * data->vref_cached / 1024); - exit_unlock: - mutex_unlock(&data->update_lock); - return val; -} - static ssize_t adt7411_show_bit(struct device *dev, struct device_attribute *attr, char *buf) { @@ -228,65 +149,157 @@ static ssize_t adt7411_set_bit(struct device *dev, 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 SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, adt7411_show_temp, NULL, 0); -static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, adt7411_show_temp, NULL, 1); -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[] = { - &sensor_dev_attr_temp1_input.dev_attr.attr, - &sensor_dev_attr_temp2_input.dev_attr.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 }; +ATTRIBUTE_GROUPS(adt7411); -static umode_t adt7411_attrs_visible(struct kobject *kobj, - struct attribute *attr, int index) +static int adt7411_read_in_vdd(struct device *dev, u32 attr, long *val) { - struct device *dev = container_of(kobj, struct device, kobj); struct adt7411_data *data = dev_get_drvdata(dev); - bool visible = true; + struct i2c_client *client = data->client; + int ret; - if (attr == &sensor_dev_attr_temp2_input.dev_attr.attr) - visible = data->use_ext_temp; - else if (attr == &sensor_dev_attr_in1_input.dev_attr.attr || - attr == &sensor_dev_attr_in2_input.dev_attr.attr) - visible = !data->use_ext_temp; - - return visible ? attr->mode : 0; + switch (attr) { + case hwmon_in_input: + ret = adt7411_read_10_bit(client, ADT7411_REG_INT_TEMP_VDD_LSB, + ADT7411_REG_VDD_MSB, 2); + if (ret < 0) + return ret; + *val = ret * 7000 / 1024; + return 0; + default: + return -EOPNOTSUPP; + } } -static const struct attribute_group adt7411_group = { - .attrs = adt7411_attrs, - .is_visible = adt7411_attrs_visible, -}; -__ATTRIBUTE_GROUPS(adt7411); +static int adt7411_read_in_chan(struct device *dev, u32 attr, int channel, + long *val) +{ + struct adt7411_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + + int ret; + int lsb_reg, lsb_shift; + int nr = channel - 1; + + mutex_lock(&data->update_lock); + if (time_after_eq(jiffies, data->next_update)) { + ret = i2c_smbus_read_byte_data(client, ADT7411_REG_CFG3); + if (ret < 0) + goto exit_unlock; + + if (ret & ADT7411_CFG3_REF_VDD) { + ret = adt7411_read_in_vdd(dev, hwmon_in_input, + &data->vref_cached); + if (ret < 0) + goto exit_unlock; + } else { + data->vref_cached = 2250; + } + + data->next_update = jiffies + HZ; + } + + switch (attr) { + case hwmon_in_input: + lsb_reg = ADT7411_REG_EXT_TEMP_AIN14_LSB + (nr >> 2); + lsb_shift = 2 * (nr & 0x03); + ret = adt7411_read_10_bit(client, lsb_reg, + ADT7411_REG_EXT_TEMP_AIN1_MSB + nr, + lsb_shift); + if (ret < 0) + goto exit_unlock; + *val = ret * data->vref_cached / 1024; + ret = 0; + break; + default: + ret = -EOPNOTSUPP; + break; + } + exit_unlock: + mutex_unlock(&data->update_lock); + return ret; +} + +static int adt7411_read_in(struct device *dev, u32 attr, int channel, + long *val) +{ + if (channel == 0) + return adt7411_read_in_vdd(dev, attr, val); + else + return adt7411_read_in_chan(dev, attr, channel, val); +} + +static int adt7411_read_temp(struct device *dev, u32 attr, int channel, + long *val) +{ + struct adt7411_data *data = dev_get_drvdata(dev); + struct i2c_client *client = data->client; + int ret, regl, regh; + + switch (attr) { + case hwmon_temp_input: + regl = channel ? ADT7411_REG_EXT_TEMP_AIN14_LSB : + ADT7411_REG_INT_TEMP_VDD_LSB; + regh = channel ? ADT7411_REG_EXT_TEMP_AIN1_MSB : + ADT7411_REG_INT_TEMP_MSB; + ret = adt7411_read_10_bit(client, regl, regh, 0); + if (ret < 0) + return ret; + ret = ret & 0x200 ? ret - 0x400 : ret; /* 10 bit signed */ + *val = ret * 250; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int adt7411_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_in: + return adt7411_read_in(dev, attr, channel, val); + case hwmon_temp: + return adt7411_read_temp(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static umode_t adt7411_is_visible(const void *_data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct adt7411_data *data = _data; + + switch (type) { + case hwmon_in: + if (channel > 0 && channel < 3) + return data->use_ext_temp ? 0 : S_IRUGO; + else + return S_IRUGO; + case hwmon_temp: + if (channel == 1) + return data->use_ext_temp ? S_IRUGO : 0; + else + return S_IRUGO; + default: + return 0; + } +} static int adt7411_detect(struct i2c_client *client, struct i2c_board_info *info) @@ -358,6 +371,51 @@ static int adt7411_init_device(struct adt7411_data *data) return i2c_smbus_write_byte_data(data->client, ADT7411_REG_CFG1, val); } +static const u32 adt7411_in_config[] = { + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + HWMON_I_INPUT, + 0 +}; + +static const struct hwmon_channel_info adt7411_in = { + .type = hwmon_in, + .config = adt7411_in_config, +}; + +static const u32 adt7411_temp_config[] = { + HWMON_T_INPUT, + HWMON_T_INPUT, + 0 +}; + +static const struct hwmon_channel_info adt7411_temp = { + .type = hwmon_temp, + .config = adt7411_temp_config, +}; + +static const struct hwmon_channel_info *adt7411_info[] = { + &adt7411_in, + &adt7411_temp, + NULL +}; + +static const struct hwmon_ops adt7411_hwmon_ops = { + .is_visible = adt7411_is_visible, + .read = adt7411_read, +}; + +static const struct hwmon_chip_info adt7411_chip_info = { + .ops = &adt7411_hwmon_ops, + .info = adt7411_info, +}; + static int adt7411_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -382,9 +440,10 @@ static int adt7411_probe(struct i2c_client *client, /* force update on first occasion */ data->next_update = jiffies; - hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, - data, - adt7411_groups); + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + data, + &adt7411_chip_info, + adt7411_groups); return PTR_ERR_OR_ZERO(hwmon_dev); } From 0fb620c4334eab14e85b7f66389e9061a225fb7e Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 20 Nov 2016 10:14:09 -0800 Subject: [PATCH 11/41] hwmon: (adm9240) Fix overflows seen when writing into limit attributes Module test reports: in0_min: Suspected overflow: [3320 vs. 0] in0_max: Suspected overflow: [3320 vs. 0] in4_min: Suspected overflow: [15938 vs. 0] in4_max: Suspected overflow: [15938 vs. 0] temp1_max: Suspected overflow: [127000 vs. 0] temp1_max_hyst: Suspected overflow: [127000 vs. 0] aout_output: Suspected overflow: [1250 vs. 0] Code analysis reveals that the overflows are caused by conversions from unsigned long to long to int, combined with multiplications on passed values. Reviewed-by: Jean Delvare Signed-off-by: Guenter Roeck --- drivers/hwmon/adm9240.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/adm9240.c b/drivers/hwmon/adm9240.c index 2fe1828bd10b..72bf2489511e 100644 --- a/drivers/hwmon/adm9240.c +++ b/drivers/hwmon/adm9240.c @@ -98,13 +98,15 @@ static inline unsigned int IN_FROM_REG(u8 reg, int n) static inline u8 IN_TO_REG(unsigned long val, int n) { - return clamp_val(SCALE(val, 192, nom_mv[n]), 0, 255); + val = clamp_val(val, 0, nom_mv[n] * 255 / 192); + return SCALE(val, 192, nom_mv[n]); } /* temperature range: -40..125, 127 disables temperature alarm */ static inline s8 TEMP_TO_REG(long val) { - return clamp_val(SCALE(val, 1, 1000), -40, 127); + val = clamp_val(val, -40000, 127000); + return SCALE(val, 1, 1000); } /* two fans, each with low fan speed limit */ @@ -122,7 +124,8 @@ static inline unsigned int FAN_FROM_REG(u8 reg, u8 div) /* analog out 0..1250mV */ static inline u8 AOUT_TO_REG(unsigned long val) { - return clamp_val(SCALE(val, 255, 1250), 0, 255); + val = clamp_val(val, 0, 1250); + return SCALE(val, 255, 1250); } static inline unsigned int AOUT_FROM_REG(u8 reg) From e36ce99ee0815d7919a7b589bfb66f3de50b6bc7 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 20 Nov 2016 10:37:39 -0800 Subject: [PATCH 12/41] hwmon: (ds620) Fix overflows seen when writing temperature limits Module test reports: temp1_max: Suspected overflow: [160000 vs. 0] temp1_min: Suspected overflow: [160000 vs. 0] This is seen because the values passed when writing temperature limits are unbound. Reviewed-by: Jean Delvare Fixes: 6099469805c2 ("hwmon: Support for Dallas Semiconductor DS620") Signed-off-by: Guenter Roeck --- drivers/hwmon/ds620.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/ds620.c b/drivers/hwmon/ds620.c index edf550fc4eef..0043a4c02b85 100644 --- a/drivers/hwmon/ds620.c +++ b/drivers/hwmon/ds620.c @@ -166,7 +166,7 @@ static ssize_t set_temp(struct device *dev, struct device_attribute *da, if (res) return res; - val = (val * 10 / 625) * 8; + val = (clamp_val(val, -128000, 128000) * 10 / 625) * 8; mutex_lock(&data->update_lock); data->temp[attr->index] = val; From 78305ae70cbad28acba65b69d3497cd301cf3479 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 20 Nov 2016 14:16:16 -0800 Subject: [PATCH 13/41] hwmon: (smsc47m192) Fix overflows seen when writing into limit attributes Module test reports overflows when writing into temperature and voltage limit attributes temp1_min: Suspected overflow: [127000 vs. 0] temp1_max: Suspected overflow: [127000 vs. 0] temp1_offset: Suspected overflow: [127000 vs. 0] temp2_min: Suspected overflow: [127000 vs. 0] temp2_max: Suspected overflow: [127000 vs. 0] temp2_offset: Suspected overflow: [127000 vs. 0] temp3_min: Suspected overflow: [127000 vs. 0] temp3_max: Suspected overflow: [127000 vs. 0] temp3_offset: Suspected overflow: [127000 vs. 0] in0_min: Suspected overflow: [3320 vs. 0] in0_max: Suspected overflow: [3320 vs. 0] in4_min: Suspected overflow: [15938 vs. 0] in4_max: Suspected overflow: [15938 vs. 0] in6_min: Suspected overflow: [1992 vs. 0] in6_max: Suspected overflow: [1992 vs. 0] in7_min: Suspected overflow: [2391 vs. 0] in7_max: Suspected overflow: [2391 vs. 0] The problem is caused by conversions from unsigned long to long and from long to int. Reviewed-by: Jean Delvare Signed-off-by: Guenter Roeck --- drivers/hwmon/smsc47m192.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/smsc47m192.c b/drivers/hwmon/smsc47m192.c index 6ac7cda72d4c..15650f247679 100644 --- a/drivers/hwmon/smsc47m192.c +++ b/drivers/hwmon/smsc47m192.c @@ -77,14 +77,15 @@ static inline unsigned int IN_FROM_REG(u8 reg, int n) static inline u8 IN_TO_REG(unsigned long val, int n) { - return clamp_val(SCALE(val, 192, nom_mv[n]), 0, 255); + val = clamp_val(val, 0, nom_mv[n] * 255 / 192); + return SCALE(val, 192, nom_mv[n]); } /* * TEMP: 0.001 degC units (-128C to +127C) * REG: 1C/bit, two's complement */ -static inline s8 TEMP_TO_REG(int val) +static inline s8 TEMP_TO_REG(long val) { return SCALE(clamp_val(val, -128000, 127000), 1, 1000); } From 723f573433b2900f1d4539b8eb0d24cd17a42c65 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 22 Nov 2016 17:42:02 +0000 Subject: [PATCH 14/41] hwmon: (coretemp) Fixup target cpu for package when cpu is offlined When a CPU is offlined nothing checks whether it is the target CPU for the package temperature sysfs interface. As a consequence all future readouts of the package temperature return crap: 90000 which is Tjmax of that package. Check whether the outgoing CPU is the target for the package and assign it to some other still online CPU in the package. Protect the change against the rdmsr_on_cpu() in show_crit_alarm(). Signed-off-by: Thomas Gleixner Signed-off-by: Guenter Roeck --- drivers/hwmon/coretemp.c | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index 6a27eb2fed17..3ae16c37bd92 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -51,6 +51,7 @@ static int force_tjmax; module_param_named(tjmax, force_tjmax, int, 0444); MODULE_PARM_DESC(tjmax, "TjMax value in degrees Celsius"); +#define PKG_SYSFS_ATTR_NO 1 /* Sysfs attribute for package temp */ #define BASE_SYSFS_ATTR_NO 2 /* Sysfs Base attr no for coretemp */ #define NUM_REAL_CORES 128 /* Number of Real cores per cpu */ #define CORETEMP_NAME_LENGTH 19 /* String Length of attrs */ @@ -138,7 +139,9 @@ static ssize_t show_crit_alarm(struct device *dev, struct platform_data *pdata = dev_get_drvdata(dev); struct temp_data *tdata = pdata->core_data[attr->index]; + mutex_lock(&tdata->update_lock); rdmsr_on_cpu(tdata->cpu, tdata->status_reg, &eax, &edx); + mutex_unlock(&tdata->update_lock); return sprintf(buf, "%d\n", (eax >> 5) & 1); } @@ -483,7 +486,7 @@ static int create_core_data(struct platform_device *pdev, unsigned int cpu, * The attr number is always core id + 2 * The Pkgtemp will always show up as temp1_*, if available */ - attr_no = pkg_flag ? 1 : TO_ATTR_NO(cpu); + attr_no = pkg_flag ? PKG_SYSFS_ATTR_NO : TO_ATTR_NO(cpu); if (attr_no > MAX_CORE_DATA - 1) return -ERANGE; @@ -662,7 +665,7 @@ static void coretemp_device_remove(unsigned int cpu) mutex_unlock(&pdev_list_mutex); } -static bool is_any_core_online(struct platform_data *pdata) +static int get_online_core_in_package(struct platform_data *pdata) { int i; @@ -670,10 +673,10 @@ static bool is_any_core_online(struct platform_data *pdata) for (i = MAX_CORE_DATA - 1; i >= 0; --i) { if (pdata->core_data[i] && !pdata->core_data[i]->is_pkg_data) { - return true; + return pdata->core_data[i]->cpu; } } - return false; + return nr_cpu_ids; } static void get_core_online(unsigned int cpu) @@ -720,9 +723,10 @@ static void get_core_online(unsigned int cpu) static void put_core_offline(unsigned int cpu) { - int i, indx; - struct platform_data *pdata; struct platform_device *pdev = coretemp_get_pdev(cpu); + struct platform_data *pdata; + struct temp_data *tdata; + int i, indx, target; /* If the physical CPU device does not exist, just return */ if (!pdev) @@ -762,8 +766,21 @@ static void put_core_offline(unsigned int cpu) * which in turn calls coretemp_remove. This removes the * pkgtemp entry and does other clean ups. */ - if (!is_any_core_online(pdata)) + target = get_online_core_in_package(pdata); + if (target >= nr_cpu_ids) { coretemp_device_remove(cpu); + return; + } + /* + * Check whether this core is the target for the package + * interface. We need to assign it to some other cpu. + */ + tdata = pdata->core_data[PKG_SYSFS_ATTR_NO]; + if (tdata && tdata->cpu == cpu) { + mutex_lock(&tdata->update_lock); + tdata->cpu = target; + mutex_unlock(&tdata->update_lock); + } } static int coretemp_cpu_callback(struct notifier_block *nfb, From e1b370b64031a01bb0c4158ce250073a88921fe1 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 22 Nov 2016 17:42:02 +0000 Subject: [PATCH 15/41] hwmon: (coretemp) Simplify sibling management The coretemp driver provides a sysfs interface per physical core. If hyperthreading is enabled and one of the siblings goes offline the sysfs interface is removed and then immeditately created again for the sibling. The only difference of them is the target cpu for the rdmsr_on_cpu() in the sysfs show functions. It's way simpler to keep a cpumask of cpus which are active in a package and only remove the interface when the last sibling goes offline. Otherwise just move the target cpu for the sysfs show functions to the still online sibling. Signed-off-by: Thomas Gleixner Signed-off-by: Guenter Roeck --- drivers/hwmon/coretemp.c | 93 ++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 56 deletions(-) diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index 3ae16c37bd92..cda09cf2e84b 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -103,9 +103,10 @@ struct temp_data { /* Platform Data per Physical CPU */ struct platform_data { - struct device *hwmon_dev; - u16 phys_proc_id; - struct temp_data *core_data[MAX_CORE_DATA]; + struct device *hwmon_dev; + u16 phys_proc_id; + struct cpumask cpumask; + struct temp_data *core_data[MAX_CORE_DATA]; struct device_attribute name_attr; }; @@ -491,16 +492,6 @@ static int create_core_data(struct platform_device *pdev, unsigned int cpu, if (attr_no > MAX_CORE_DATA - 1) return -ERANGE; - /* - * Provide a single set of attributes for all HT siblings of a core - * to avoid duplicate sensors (the processor ID and core ID of all - * HT siblings of a core are the same). - * Skip if a HT sibling of this core is already registered. - * This is not an error. - */ - if (pdata->core_data[attr_no] != NULL) - return 0; - tdata = init_temp_data(cpu, pkg_flag); if (!tdata) return -ENOMEM; @@ -665,24 +656,11 @@ static void coretemp_device_remove(unsigned int cpu) mutex_unlock(&pdev_list_mutex); } -static int get_online_core_in_package(struct platform_data *pdata) -{ - int i; - - /* Find online cores, except pkgtemp data */ - for (i = MAX_CORE_DATA - 1; i >= 0; --i) { - if (pdata->core_data[i] && - !pdata->core_data[i]->is_pkg_data) { - return pdata->core_data[i]->cpu; - } - } - return nr_cpu_ids; -} - static void get_core_online(unsigned int cpu) { - struct cpuinfo_x86 *c = &cpu_data(cpu); struct platform_device *pdev = coretemp_get_pdev(cpu); + struct cpuinfo_x86 *c = &cpu_data(cpu); + struct platform_data *pdata; int err; /* @@ -707,6 +685,8 @@ static void get_core_online(unsigned int cpu) err = coretemp_device_add(cpu); if (err) return; + + pdev = coretemp_get_pdev(cpu); /* * Check whether pkgtemp support is available. * If so, add interfaces for pkgtemp. @@ -714,60 +694,60 @@ static void get_core_online(unsigned int cpu) if (cpu_has(c, X86_FEATURE_PTS)) coretemp_add_core(cpu, 1); } + + pdata = platform_get_drvdata(pdev); /* - * Physical CPU device already exists. - * So, just add interfaces for this core. + * Check whether a thread sibling is already online. If not add the + * interface for this CPU core. */ - coretemp_add_core(cpu, 0); + if (!cpumask_intersects(&pdata->cpumask, topology_sibling_cpumask(cpu))) + coretemp_add_core(cpu, 0); + + cpumask_set_cpu(cpu, &pdata->cpumask); } static void put_core_offline(unsigned int cpu) { struct platform_device *pdev = coretemp_get_pdev(cpu); - struct platform_data *pdata; + struct platform_data *pd; struct temp_data *tdata; - int i, indx, target; + int indx, target; /* If the physical CPU device does not exist, just return */ if (!pdev) return; - pdata = platform_get_drvdata(pdev); - - indx = TO_ATTR_NO(cpu); - /* The core id is too big, just return */ + indx = TO_ATTR_NO(cpu); if (indx > MAX_CORE_DATA - 1) return; - if (pdata->core_data[indx] && pdata->core_data[indx]->cpu == cpu) - coretemp_remove_core(pdata, indx); + pd = platform_get_drvdata(pdev); + tdata = pd->core_data[indx]; + + cpumask_clear_cpu(cpu, &pd->cpumask); /* - * If a HT sibling of a core is taken offline, but another HT sibling - * of the same core is still online, register the alternate sibling. - * This ensures that exactly one set of attributes is provided as long - * as at least one HT sibling of a core is online. + * If this is the last thread sibling, remove the CPU core + * interface, If there is still a sibling online, transfer the + * target cpu of that core interface to it. */ - for_each_sibling(i, cpu) { - if (i != cpu) { - get_core_online(i); - /* - * Display temperature sensor data for one HT sibling - * per core only, so abort the loop after one such - * sibling has been found. - */ - break; - } + target = cpumask_any_and(&pd->cpumask, topology_sibling_cpumask(cpu)); + if (target >= nr_cpu_ids) { + coretemp_remove_core(pd, indx); + } else if (tdata && tdata->cpu == cpu) { + mutex_lock(&tdata->update_lock); + tdata->cpu = target; + mutex_unlock(&tdata->update_lock); } + /* * If all cores in this pkg are offline, remove the device. * coretemp_device_remove calls unregister_platform_device, * which in turn calls coretemp_remove. This removes the * pkgtemp entry and does other clean ups. */ - target = get_online_core_in_package(pdata); - if (target >= nr_cpu_ids) { + if (cpumask_empty(&pd->cpumask)) { coretemp_device_remove(cpu); return; } @@ -775,8 +755,9 @@ static void put_core_offline(unsigned int cpu) * Check whether this core is the target for the package * interface. We need to assign it to some other cpu. */ - tdata = pdata->core_data[PKG_SYSFS_ATTR_NO]; + tdata = pd->core_data[PKG_SYSFS_ATTR_NO]; if (tdata && tdata->cpu == cpu) { + target = cpumask_first(&pd->cpumask); mutex_lock(&tdata->update_lock); tdata->cpu = target; mutex_unlock(&tdata->update_lock); From 4b138cf73f4548d256f26006a756bc0fe3ad786e Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 22 Nov 2016 17:42:03 +0000 Subject: [PATCH 16/41] hwmon: (coretemp) Avoid redundant lookups No point in looking up the same thing over and over. Signed-off-by: Thomas Gleixner Signed-off-by: Guenter Roeck --- drivers/hwmon/coretemp.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index cda09cf2e84b..1bcc90a20ed1 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -533,21 +533,14 @@ exit_free: return err; } -static void coretemp_add_core(unsigned int cpu, int pkg_flag) +static void +coretemp_add_core(struct platform_device *pdev, unsigned int cpu, int pkg_flag) { - struct platform_device *pdev = coretemp_get_pdev(cpu); - int err; - - if (!pdev) - return; - - err = create_core_data(pdev, cpu, pkg_flag); - if (err) + if (create_core_data(pdev, cpu, pkg_flag)) dev_err(&pdev->dev, "Adding Core %u failed\n", cpu); } -static void coretemp_remove_core(struct platform_data *pdata, - int indx) +static void coretemp_remove_core(struct platform_data *pdata, int indx) { struct temp_data *tdata = pdata->core_data[indx]; @@ -692,7 +685,7 @@ static void get_core_online(unsigned int cpu) * If so, add interfaces for pkgtemp. */ if (cpu_has(c, X86_FEATURE_PTS)) - coretemp_add_core(cpu, 1); + coretemp_add_core(pdev, cpu, 1); } pdata = platform_get_drvdata(pdev); @@ -701,7 +694,7 @@ static void get_core_online(unsigned int cpu) * interface for this CPU core. */ if (!cpumask_intersects(&pdata->cpumask, topology_sibling_cpumask(cpu))) - coretemp_add_core(cpu, 0); + coretemp_add_core(pdev, cpu, 0); cpumask_set_cpu(cpu, &pdata->cpumask); } From e00ca5df37adc68052ea699cbd010ee4e19e39e4 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 22 Nov 2016 17:42:04 +0000 Subject: [PATCH 17/41] hwmon: (coretemp) Convert to hotplug state machine Install the callbacks via the state machine. Setup and teardown are handled by the hotplug core. Signed-off-by: Sebastian Andrzej Siewior Cc: linux-hwmon@vger.kernel.org Cc: Fenghua Yu Cc: Jean Delvare Cc: rt@linuxtronix.de Cc: Guenter Roeck Link: http://lkml.kernel.org/r/20161117183541.8588-5-bigeasy@linutronix.de Signed-off-by: Guenter Roeck --- drivers/hwmon/coretemp.c | 86 ++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 57 deletions(-) diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index 1bcc90a20ed1..984c02334910 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -649,7 +649,7 @@ static void coretemp_device_remove(unsigned int cpu) mutex_unlock(&pdev_list_mutex); } -static void get_core_online(unsigned int cpu) +static int coretemp_cpu_online(unsigned int cpu) { struct platform_device *pdev = coretemp_get_pdev(cpu); struct cpuinfo_x86 *c = &cpu_data(cpu); @@ -662,12 +662,12 @@ static void get_core_online(unsigned int cpu) * without thermal sensors will be filtered out. */ if (!cpu_has(c, X86_FEATURE_DTHERM)) - return; + return 0; if (!pdev) { /* Check the microcode version of the CPU */ if (chk_ucode_version(cpu)) - return; + return 0; /* * Alright, we have DTS support. @@ -677,7 +677,7 @@ static void get_core_online(unsigned int cpu) */ err = coretemp_device_add(cpu); if (err) - return; + return 0; pdev = coretemp_get_pdev(cpu); /* @@ -697,9 +697,10 @@ static void get_core_online(unsigned int cpu) coretemp_add_core(pdev, cpu, 0); cpumask_set_cpu(cpu, &pdata->cpumask); + return 0; } -static void put_core_offline(unsigned int cpu) +static int coretemp_cpu_offline(unsigned int cpu) { struct platform_device *pdev = coretemp_get_pdev(cpu); struct platform_data *pd; @@ -708,12 +709,12 @@ static void put_core_offline(unsigned int cpu) /* If the physical CPU device does not exist, just return */ if (!pdev) - return; + return 0; /* The core id is too big, just return */ indx = TO_ATTR_NO(cpu); if (indx > MAX_CORE_DATA - 1) - return; + return 0; pd = platform_get_drvdata(pdev); tdata = pd->core_data[indx]; @@ -742,7 +743,7 @@ static void put_core_offline(unsigned int cpu) */ if (cpumask_empty(&pd->cpumask)) { coretemp_device_remove(cpu); - return; + return 0; } /* * Check whether this core is the target for the package @@ -755,38 +756,19 @@ static void put_core_offline(unsigned int cpu) tdata->cpu = target; mutex_unlock(&tdata->update_lock); } + return 0; } - -static int coretemp_cpu_callback(struct notifier_block *nfb, - unsigned long action, void *hcpu) -{ - unsigned int cpu = (unsigned long) hcpu; - - switch (action) { - case CPU_ONLINE: - case CPU_DOWN_FAILED: - get_core_online(cpu); - break; - case CPU_DOWN_PREPARE: - put_core_offline(cpu); - break; - } - return NOTIFY_OK; -} - -static struct notifier_block coretemp_cpu_notifier __refdata = { - .notifier_call = coretemp_cpu_callback, -}; - static const struct x86_cpu_id __initconst coretemp_ids[] = { { X86_VENDOR_INTEL, X86_FAMILY_ANY, X86_MODEL_ANY, X86_FEATURE_DTHERM }, {} }; MODULE_DEVICE_TABLE(x86cpu, coretemp_ids); +static enum cpuhp_state coretemp_hp_online; + static int __init coretemp_init(void) { - int i, err; + int err; /* * CPUID.06H.EAX[0] indicates whether the CPU has thermal @@ -798,52 +780,42 @@ static int __init coretemp_init(void) err = platform_driver_register(&coretemp_driver); if (err) - goto exit; + return err; - cpu_notifier_register_begin(); - for_each_online_cpu(i) - get_core_online(i); + get_online_cpus(); + err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "hwmon/coretemp:online", + coretemp_cpu_online, coretemp_cpu_offline); + if (err < 0) + goto exit_driver_unreg; + coretemp_hp_online = err; #ifndef CONFIG_HOTPLUG_CPU if (list_empty(&pdev_list)) { - cpu_notifier_register_done(); err = -ENODEV; - goto exit_driver_unreg; + goto exit_hp_unreg; } #endif - - __register_hotcpu_notifier(&coretemp_cpu_notifier); - cpu_notifier_register_done(); + put_online_cpus(); return 0; #ifndef CONFIG_HOTPLUG_CPU +exit_hp_unreg: + cpuhp_remove_state(coretemp_hp_online); + put_online_cpus(); +#endif exit_driver_unreg: platform_driver_unregister(&coretemp_driver); -#endif -exit: return err; } +module_init(coretemp_init) static void __exit coretemp_exit(void) { - struct pdev_entry *p, *n; - - cpu_notifier_register_begin(); - __unregister_hotcpu_notifier(&coretemp_cpu_notifier); - mutex_lock(&pdev_list_mutex); - list_for_each_entry_safe(p, n, &pdev_list, list) { - platform_device_unregister(p->pdev); - list_del(&p->list); - kfree(p); - } - mutex_unlock(&pdev_list_mutex); - cpu_notifier_register_done(); + cpuhp_remove_state(coretemp_hp_online); platform_driver_unregister(&coretemp_driver); } +module_exit(coretemp_exit) MODULE_AUTHOR("Rudolf Marek "); MODULE_DESCRIPTION("Intel Core temperature monitor"); MODULE_LICENSE("GPL"); - -module_init(coretemp_init) -module_exit(coretemp_exit) From 2195c31b127def509c806fe8a9d3b4092a28ce31 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 22 Nov 2016 17:42:05 +0000 Subject: [PATCH 18/41] hwmon: (coretemp) Use proper error codes in cpu online callback The cpu online callback returns success unconditionally even when the device has no support, micro code mismatches or device allocation fails. Only if CPU_HOTPLUG is disabled, the init function checks whether the device list is empty and removes the driver. This does not make sense. If CPU HOTPLUG is enabled then there is no point to keep the driver around when it failed to initialize on the already online cpus. The chance that not yet online CPUs will provide a functional interface later is very close to zero. Add proper error return codes, so the setup of the cpu hotplug states fails when the device cannot be initialized and remove all the magic cruft. Signed-off-by: Thomas Gleixner Signed-off-by: Guenter Roeck --- drivers/hwmon/coretemp.c | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index 984c02334910..c364bfd92d23 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -662,12 +662,12 @@ static int coretemp_cpu_online(unsigned int cpu) * without thermal sensors will be filtered out. */ if (!cpu_has(c, X86_FEATURE_DTHERM)) - return 0; + return -ENODEV; if (!pdev) { /* Check the microcode version of the CPU */ if (chk_ucode_version(cpu)) - return 0; + return -EINVAL; /* * Alright, we have DTS support. @@ -677,7 +677,7 @@ static int coretemp_cpu_online(unsigned int cpu) */ err = coretemp_device_add(cpu); if (err) - return 0; + return err; pdev = coretemp_get_pdev(cpu); /* @@ -782,28 +782,14 @@ static int __init coretemp_init(void) if (err) return err; - get_online_cpus(); err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "hwmon/coretemp:online", coretemp_cpu_online, coretemp_cpu_offline); if (err < 0) - goto exit_driver_unreg; + goto outdrv; coretemp_hp_online = err; - -#ifndef CONFIG_HOTPLUG_CPU - if (list_empty(&pdev_list)) { - err = -ENODEV; - goto exit_hp_unreg; - } -#endif - put_online_cpus(); return 0; -#ifndef CONFIG_HOTPLUG_CPU -exit_hp_unreg: - cpuhp_remove_state(coretemp_hp_online); - put_online_cpus(); -#endif -exit_driver_unreg: +outdrv: platform_driver_unregister(&coretemp_driver); return err; } From 712668460594294d74c13f2a023398a597fbe95f Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 22 Nov 2016 17:42:06 +0000 Subject: [PATCH 19/41] hwmon: (coretemp) Simplify package management Keeping track of the per package platform devices requires an extra object, which is held in a linked list. The maximum number of packages is known at init() time. So the extra object and linked list management can be replaced by an array of platform device pointers in which the per package devices pointers can be stored. Lookup becomes a simple array lookup instead of a list walk. The mutex protecting the list can be removed as well because the array is only accessed from cpu hotplug callbacks which are already serialized. Signed-off-by: Thomas Gleixner Signed-off-by: Guenter Roeck --- drivers/hwmon/coretemp.c | 120 +++++++++++++-------------------------- 1 file changed, 38 insertions(+), 82 deletions(-) diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index c364bfd92d23..3ac4c03ba77b 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -59,7 +59,6 @@ MODULE_PARM_DESC(tjmax, "TjMax value in degrees Celsius"); #define TOTAL_ATTRS (MAX_CORE_ATTRS + 1) #define MAX_CORE_DATA (NUM_REAL_CORES + BASE_SYSFS_ATTR_NO) -#define TO_PHYS_ID(cpu) (cpu_data(cpu).phys_proc_id) #define TO_CORE_ID(cpu) (cpu_data(cpu).cpu_core_id) #define TO_ATTR_NO(cpu) (TO_CORE_ID(cpu) + BASE_SYSFS_ATTR_NO) @@ -104,20 +103,16 @@ struct temp_data { /* Platform Data per Physical CPU */ struct platform_data { struct device *hwmon_dev; - u16 phys_proc_id; + u16 pkg_id; struct cpumask cpumask; struct temp_data *core_data[MAX_CORE_DATA]; struct device_attribute name_attr; }; -struct pdev_entry { - struct list_head list; - struct platform_device *pdev; - u16 phys_proc_id; -}; - -static LIST_HEAD(pdev_list); -static DEFINE_MUTEX(pdev_list_mutex); +/* Keep track of how many package pointers we allocated in init() */ +static int max_packages __read_mostly; +/* Array of package pointers. Serialized by cpu hotplug lock */ +static struct platform_device **pkg_devices; static ssize_t show_label(struct device *dev, struct device_attribute *devattr, char *buf) @@ -127,7 +122,7 @@ static ssize_t show_label(struct device *dev, struct temp_data *tdata = pdata->core_data[attr->index]; if (tdata->is_pkg_data) - return sprintf(buf, "Physical id %u\n", pdata->phys_proc_id); + return sprintf(buf, "Package id %u\n", pdata->pkg_id); return sprintf(buf, "Core %u\n", tdata->cpu_core_id); } @@ -439,18 +434,10 @@ static int chk_ucode_version(unsigned int cpu) static struct platform_device *coretemp_get_pdev(unsigned int cpu) { - u16 phys_proc_id = TO_PHYS_ID(cpu); - struct pdev_entry *p; + int pkgid = topology_logical_package_id(cpu); - mutex_lock(&pdev_list_mutex); - - list_for_each_entry(p, &pdev_list, list) - if (p->phys_proc_id == phys_proc_id) { - mutex_unlock(&pdev_list_mutex); - return p->pdev; - } - - mutex_unlock(&pdev_list_mutex); + if (pkgid >= 0 && pkgid < max_packages) + return pkg_devices[pkgid]; return NULL; } @@ -561,7 +548,7 @@ static int coretemp_probe(struct platform_device *pdev) if (!pdata) return -ENOMEM; - pdata->phys_proc_id = pdev->id; + pdata->pkg_id = pdev->id; platform_set_drvdata(pdev, pdata); pdata->hwmon_dev = devm_hwmon_device_register_with_groups(dev, DRVNAME, @@ -589,64 +576,26 @@ static struct platform_driver coretemp_driver = { .remove = coretemp_remove, }; -static int coretemp_device_add(unsigned int cpu) +static struct platform_device *coretemp_device_add(unsigned int cpu) { - int err; + int err, pkgid = topology_logical_package_id(cpu); struct platform_device *pdev; - struct pdev_entry *pdev_entry; - mutex_lock(&pdev_list_mutex); + if (pkgid < 0) + return ERR_PTR(-ENOMEM); - pdev = platform_device_alloc(DRVNAME, TO_PHYS_ID(cpu)); - if (!pdev) { - err = -ENOMEM; - pr_err("Device allocation failed\n"); - goto exit; - } - - pdev_entry = kzalloc(sizeof(struct pdev_entry), GFP_KERNEL); - if (!pdev_entry) { - err = -ENOMEM; - goto exit_device_put; - } + pdev = platform_device_alloc(DRVNAME, pkgid); + if (!pdev) + return ERR_PTR(-ENOMEM); err = platform_device_add(pdev); if (err) { - pr_err("Device addition failed (%d)\n", err); - goto exit_device_free; + platform_device_put(pdev); + return ERR_PTR(err); } - pdev_entry->pdev = pdev; - pdev_entry->phys_proc_id = pdev->id; - - list_add_tail(&pdev_entry->list, &pdev_list); - mutex_unlock(&pdev_list_mutex); - - return 0; - -exit_device_free: - kfree(pdev_entry); -exit_device_put: - platform_device_put(pdev); -exit: - mutex_unlock(&pdev_list_mutex); - return err; -} - -static void coretemp_device_remove(unsigned int cpu) -{ - struct pdev_entry *p, *n; - u16 phys_proc_id = TO_PHYS_ID(cpu); - - mutex_lock(&pdev_list_mutex); - list_for_each_entry_safe(p, n, &pdev_list, list) { - if (p->phys_proc_id != phys_proc_id) - continue; - platform_device_unregister(p->pdev); - list_del(&p->list); - kfree(p); - } - mutex_unlock(&pdev_list_mutex); + pkg_devices[pkgid] = pdev; + return pdev; } static int coretemp_cpu_online(unsigned int cpu) @@ -654,7 +603,6 @@ static int coretemp_cpu_online(unsigned int cpu) struct platform_device *pdev = coretemp_get_pdev(cpu); struct cpuinfo_x86 *c = &cpu_data(cpu); struct platform_data *pdata; - int err; /* * CPUID.06H.EAX[0] indicates whether the CPU has thermal @@ -675,11 +623,10 @@ static int coretemp_cpu_online(unsigned int cpu) * online. So, initialize per-pkg data structures and * then bring this core online. */ - err = coretemp_device_add(cpu); - if (err) - return err; + pdev = coretemp_device_add(cpu); + if (IS_ERR(pdev)) + return PTR_ERR(pdev); - pdev = coretemp_get_pdev(cpu); /* * Check whether pkgtemp support is available. * If so, add interfaces for pkgtemp. @@ -736,15 +683,16 @@ static int coretemp_cpu_offline(unsigned int cpu) } /* - * If all cores in this pkg are offline, remove the device. - * coretemp_device_remove calls unregister_platform_device, - * which in turn calls coretemp_remove. This removes the - * pkgtemp entry and does other clean ups. + * If all cores in this pkg are offline, remove the device. This + * will invoke the platform driver remove function, which cleans up + * the rest. */ if (cpumask_empty(&pd->cpumask)) { - coretemp_device_remove(cpu); + pkg_devices[topology_logical_package_id(cpu)] = NULL; + platform_device_unregister(pdev); return 0; } + /* * Check whether this core is the target for the package * interface. We need to assign it to some other cpu. @@ -778,6 +726,12 @@ static int __init coretemp_init(void) if (!x86_match_cpu(coretemp_ids)) return -ENODEV; + max_packages = topology_max_packages(); + pkg_devices = kzalloc(max_packages * sizeof(struct platform_device *), + GFP_KERNEL); + if (!pkg_devices) + return -ENOMEM; + err = platform_driver_register(&coretemp_driver); if (err) return err; @@ -791,6 +745,7 @@ static int __init coretemp_init(void) outdrv: platform_driver_unregister(&coretemp_driver); + kfree(pkg_devices); return err; } module_init(coretemp_init) @@ -799,6 +754,7 @@ static void __exit coretemp_exit(void) { cpuhp_remove_state(coretemp_hp_online); platform_driver_unregister(&coretemp_driver); + kfree(pkg_devices); } module_exit(coretemp_exit) From a2a0c3c57a8c3c92d63ae7025829bc508e347aab Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Thu, 24 Nov 2016 14:34:25 +0100 Subject: [PATCH 20/41] hwmon: (lm90) Mention support for TI TMP451 in Kconfig description The lm90 driver also supports the Texas Instruments TMP451 sensor chip. Since the Kconfig description for the driver includes a list of all compatible chips, mention the TI TMP451 there as well. Signed-off-by: Tobias Klauser Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 8681bc65cde5..1adda8a5adce 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1079,8 +1079,8 @@ config SENSORS_LM90 LM86, LM89 and LM99, Analog Devices ADM1032, ADT7461, and ADT7461A, Maxim MAX6646, MAX6647, MAX6648, MAX6649, MAX6657, MAX6658, MAX6659, MAX6680, MAX6681, MAX6692, MAX6695, MAX6696, ON Semiconductor NCT1008, - Winbond/Nuvoton W83L771W/G/AWG/ASG, Philips SA56004, and GMT G781 - sensor chips. + Winbond/Nuvoton W83L771W/G/AWG/ASG, Philips SA56004, GMT G781, and + Texas Instruments TMP451 sensor chips. This driver can also be built as a module. If so, the module will be called lm90. From f680b2845694a197e7bfc9f56b241f9a3f17296e Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 20 Nov 2016 02:55:45 -0800 Subject: [PATCH 21/41] hwmon: (core) Clarify when read and write callbacks are mandatory The callback descrption in hwmon.h was misleading and stated that read and write callbacks would be optional. More accurate is is that the callbacks are mandatory if readable / writeable attributes are present. Signed-off-by: Guenter Roeck --- include/linux/hwmon.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index 9d2f8bde7d12..b6a86aa4a9e2 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -298,8 +298,7 @@ enum hwmon_pwm_attributes { * Channel number * The function returns the file permissions. * If the return value is 0, no attribute will be created. - * @read: Read callback. Optional. If not provided, attributes - * will not be readable. + * @read: Read callback. Mandatory if readable attributes are present. * Parameters are: * @dev: Pointer to hardware monitoring device * @type: Sensor type @@ -308,8 +307,7 @@ enum hwmon_pwm_attributes { * Channel number * @val: Pointer to returned value * The function returns 0 on success or a negative error number. - * @write: Write callback. Optional. If not provided, attributes - * will not be writable. + * @write: Write callback. Mandatory if writeable attributes are present. * Parameters are: * @dev: Pointer to hardware monitoring device * @type: Sensor type From e159ab5cb1afb519601a961405933c61cdd5a56a Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 7 Aug 2016 20:51:25 -0700 Subject: [PATCH 22/41] hwmon: (core) Add support for string attributes to new API The new API is so far only suited for data attributes and does not work well for string attributes, specifically for the 'label' attributes. Provide a separate callback function for those. Signed-off-by: Guenter Roeck --- drivers/hwmon/hwmon.c | 33 +++++++++++++++++++++++++++++++-- include/linux/hwmon.h | 19 +++++++++++++++++-- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index a74c075a30ec..491231fa0580 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -178,6 +178,22 @@ static ssize_t hwmon_attr_show(struct device *dev, return sprintf(buf, "%ld\n", val); } +static ssize_t hwmon_attr_show_string(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + char *s; + int ret; + + ret = hattr->ops->read_string(dev, hattr->type, hattr->attr, + hattr->index, &s); + if (ret < 0) + return ret; + + return sprintf(buf, "%s\n", s); +} + static ssize_t hwmon_attr_store(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) @@ -205,6 +221,17 @@ static int hwmon_attr_base(enum hwmon_sensor_types type) return 1; } +static bool is_string_attr(enum hwmon_sensor_types type, u32 attr) +{ + return (type == hwmon_temp && attr == hwmon_temp_label) || + (type == hwmon_in && attr == hwmon_in_label) || + (type == hwmon_curr && attr == hwmon_curr_label) || + (type == hwmon_power && attr == hwmon_power_label) || + (type == hwmon_energy && attr == hwmon_energy_label) || + (type == hwmon_humidity && attr == hwmon_humidity_label) || + (type == hwmon_fan && attr == hwmon_fan_label); +} + static struct attribute *hwmon_genattr(struct device *dev, const void *drvdata, enum hwmon_sensor_types type, @@ -218,6 +245,7 @@ static struct attribute *hwmon_genattr(struct device *dev, struct attribute *a; umode_t mode; char *name; + bool is_string = is_string_attr(type, attr); /* The attribute is invisible if there is no template string */ if (!template) @@ -227,7 +255,8 @@ static struct attribute *hwmon_genattr(struct device *dev, if (!mode) return ERR_PTR(-ENOENT); - if ((mode & S_IRUGO) && !ops->read) + if ((mode & S_IRUGO) && ((is_string && !ops->read_string) || + (!is_string && !ops->read))) return ERR_PTR(-EINVAL); if ((mode & S_IWUGO) && !ops->write) return ERR_PTR(-EINVAL); @@ -252,7 +281,7 @@ static struct attribute *hwmon_genattr(struct device *dev, hattr->ops = ops; dattr = &hattr->dev_attr; - dattr->show = hwmon_attr_show; + dattr->show = is_string ? hwmon_attr_show_string : hwmon_attr_show; dattr->store = hwmon_attr_store; a = &dattr->attr; diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index b6a86aa4a9e2..e68334aede4c 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -298,7 +298,8 @@ enum hwmon_pwm_attributes { * Channel number * The function returns the file permissions. * If the return value is 0, no attribute will be created. - * @read: Read callback. Mandatory if readable attributes are present. + * @read: Read callback for data attributes. Mandatory if readable + * data attributes are present. * Parameters are: * @dev: Pointer to hardware monitoring device * @type: Sensor type @@ -307,7 +308,19 @@ enum hwmon_pwm_attributes { * Channel number * @val: Pointer to returned value * The function returns 0 on success or a negative error number. - * @write: Write callback. Mandatory if writeable attributes are present. + * @read_string: + * Read callback for string attributes. Mandatory if string + * attributes are present. + * Parameters are: + * @dev: Pointer to hardware monitoring device + * @type: Sensor type + * @attr: Sensor attribute + * @channel: + * Channel number + * @str: Pointer to returned string + * The function returns 0 on success or a negative error number. + * @write: Write callback for data attributes. Mandatory if writeable + * data attributes are present. * Parameters are: * @dev: Pointer to hardware monitoring device * @type: Sensor type @@ -322,6 +335,8 @@ struct hwmon_ops { u32 attr, int channel); int (*read)(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val); + int (*read_string)(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, char **str); int (*write)(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long val); }; From f4d325d5ed099c3ca9b2c23a53dc64e871a659c8 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 16 Oct 2016 10:38:52 -0700 Subject: [PATCH 23/41] hwmon: (core) Clarify use of chip attributes Describing chip attributes as "attributes which apply to the entire chip" is confusing. Rephrase to "attributes which are not bound to a specific input or output". Also rename hwmon_chip_attr_templates[] to hwmon_chip_attrs[] to indicate that the respective strings strings are not templates but actual attribute names. Signed-off-by: Guenter Roeck --- Documentation/hwmon/hwmon-kernel-api.txt | 2 +- drivers/hwmon/hwmon.c | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Documentation/hwmon/hwmon-kernel-api.txt b/Documentation/hwmon/hwmon-kernel-api.txt index ef9d74947f5c..562ef44adb5e 100644 --- a/Documentation/hwmon/hwmon-kernel-api.txt +++ b/Documentation/hwmon/hwmon-kernel-api.txt @@ -160,7 +160,7 @@ It contains following fields: * type: The hardware monitoring sensor type. Supported sensor types are * hwmon_chip A virtual sensor type, used to describe attributes - which apply to the entire chip. + * which are not bound to a specific input or output * hwmon_temp Temperature sensor * hwmon_in Voltage sensor * hwmon_curr Current sensor diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 491231fa0580..112aae60f51f 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -292,7 +292,11 @@ static struct attribute *hwmon_genattr(struct device *dev, return a; } -static const char * const hwmon_chip_attr_templates[] = { +/* + * Chip attributes are not attribute templates but actual sysfs attributes. + * See hwmon_genattr() for special handling. + */ +static const char * const hwmon_chip_attrs[] = { [hwmon_chip_temp_reset_history] = "temp_reset_history", [hwmon_chip_in_reset_history] = "in_reset_history", [hwmon_chip_curr_reset_history] = "curr_reset_history", @@ -429,7 +433,7 @@ static const char * const hwmon_pwm_attr_templates[] = { }; static const char * const *__templates[] = { - [hwmon_chip] = hwmon_chip_attr_templates, + [hwmon_chip] = hwmon_chip_attrs, [hwmon_temp] = hwmon_temp_attr_templates, [hwmon_in] = hwmon_in_attr_templates, [hwmon_curr] = hwmon_curr_attr_templates, @@ -441,7 +445,7 @@ static const char * const *__templates[] = { }; static const int __templates_size[] = { - [hwmon_chip] = ARRAY_SIZE(hwmon_chip_attr_templates), + [hwmon_chip] = ARRAY_SIZE(hwmon_chip_attrs), [hwmon_temp] = ARRAY_SIZE(hwmon_temp_attr_templates), [hwmon_in] = ARRAY_SIZE(hwmon_in_attr_templates), [hwmon_curr] = ARRAY_SIZE(hwmon_curr_attr_templates), From af1bd36c06b5fad33baa7ee16820226efbd96cd9 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 16 Oct 2016 11:31:08 -0700 Subject: [PATCH 24/41] hwmon: (core) Deprecate hwmon_device_register() Inform the user that hwmon_device_register() is deprecated, and suggest conversion to the newest API. Also remove hwmon_device_register() from the kernel API documentation. Note that hwmon_device_register() is not marked as __deprecated() since doing so might result in build errors. Signed-off-by: Guenter Roeck --- Documentation/hwmon/hwmon-kernel-api.txt | 34 ++++++++++-------------- drivers/hwmon/hwmon.c | 3 +++ include/linux/hwmon.h | 2 ++ 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/Documentation/hwmon/hwmon-kernel-api.txt b/Documentation/hwmon/hwmon-kernel-api.txt index 562ef44adb5e..633fa90909eb 100644 --- a/Documentation/hwmon/hwmon-kernel-api.txt +++ b/Documentation/hwmon/hwmon-kernel-api.txt @@ -23,7 +23,6 @@ Each hardware monitoring driver must #include and, in most cases, . linux/hwmon.h declares the following register/unregister functions: -struct device *hwmon_device_register(struct device *dev); struct device * hwmon_device_register_with_groups(struct device *dev, const char *name, void *drvdata, @@ -50,24 +49,19 @@ devm_hwmon_device_register_with_info(struct device *dev, void hwmon_device_unregister(struct device *dev); void devm_hwmon_device_unregister(struct device *dev); -hwmon_device_register registers a hardware monitoring device. The parameter -of this function is a pointer to the parent device. -This function returns a pointer to the newly created hardware monitoring device -or PTR_ERR for failure. If this registration function is used, hardware -monitoring sysfs attributes are expected to have been created and attached to -the parent device prior to calling hwmon_device_register. A name attribute must -have been created by the caller. - -hwmon_device_register_with_groups is similar to hwmon_device_register. However, -it has additional parameters. The name parameter is a pointer to the hwmon -device name. The registration function wil create a name sysfs attribute -pointing to this name. The drvdata parameter is the pointer to the local -driver data. hwmon_device_register_with_groups will attach this pointer -to the newly allocated hwmon device. The pointer can be retrieved by the driver -using dev_get_drvdata() on the hwmon device pointer. The groups parameter is +hwmon_device_register_with_groups registers a hardware monitoring device. +The first parameter of this function is a pointer to the parent device. +The name parameter is a pointer to the hwmon device name. The registration +function wil create a name sysfs attribute pointing to this name. +The drvdata parameter is the pointer to the local driver data. +hwmon_device_register_with_groups will attach this pointer to the newly +allocated hwmon device. The pointer can be retrieved by the driver using +dev_get_drvdata() on the hwmon device pointer. The groups parameter is a pointer to a list of sysfs attribute groups. The list must be NULL terminated. hwmon_device_register_with_groups creates the hwmon device with name attribute as well as all sysfs attributes attached to the hwmon device. +This function returns a pointer to the newly created hardware monitoring device +or PTR_ERR for failure. devm_hwmon_device_register_with_groups is similar to hwmon_device_register_with_groups. However, it is device managed, meaning the @@ -87,13 +81,13 @@ hwmon_device_unregister deregisters a registered hardware monitoring device. The parameter of this function is the pointer to the registered hardware monitoring device structure. This function must be called from the driver remove function if the hardware monitoring device was registered with -hwmon_device_register, hwmon_device_register_with_groups, or -hwmon_device_register_with_info. +hwmon_device_register_with_groups or hwmon_device_register_with_info. devm_hwmon_device_unregister does not normally have to be called. It is only needed for error handling, and only needed if the driver probe fails after -the call to devm_hwmon_device_register_with_groups and if the automatic -(device managed) removal would be too late. +the call to devm_hwmon_device_register_with_groups or +hwmon_device_register_with_info and if the automatic (device managed) +removal would be too late. Using devm_hwmon_device_register_with_info() -------------------------------------------- diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 112aae60f51f..971c9eb78e6a 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -691,6 +691,9 @@ EXPORT_SYMBOL_GPL(hwmon_device_register_with_info); */ struct device *hwmon_device_register(struct device *dev) { + dev_warn(dev, + "hwmon_device_register() is deprecated. Please convert the driver to use hwmon_device_register_with_info().\n"); + return hwmon_device_register_with_groups(dev, NULL, NULL, NULL); } EXPORT_SYMBOL_GPL(hwmon_device_register); diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index e68334aede4c..2588e6ee7660 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -362,7 +362,9 @@ struct hwmon_chip_info { const struct hwmon_channel_info **info; }; +/* hwmon_device_register() is deprecated */ struct device *hwmon_device_register(struct device *dev); + struct device * hwmon_device_register_with_groups(struct device *dev, const char *name, void *drvdata, From 239552f495b91fc8f3fba4b5094233e053d265cb Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 16 Oct 2016 17:06:20 -0700 Subject: [PATCH 25/41] hwmon: (core) Make is_visible callback truly mandatory The is_visible callback provides the sysfs attribute mode and is thus truly mandatory as documented. Check it once at registration and remove other checks for its existence. Signed-off-by: Guenter Roeck --- drivers/hwmon/hwmon.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 971c9eb78e6a..a0b5becf91fa 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -559,7 +559,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, hdev = &hwdev->dev; - if (chip && chip->ops->is_visible) { + if (chip) { struct attribute **attrs; int ngroups = 2; @@ -605,7 +605,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, if (err) goto free_hwmon; - if (chip && chip->ops->is_visible && chip->ops->read && + if (chip && chip->ops->read && chip->info[0]->type == hwmon_chip && (chip->info[0]->config[0] & HWMON_C_REGISTER_TZ)) { const struct hwmon_channel_info **info = chip->info; @@ -673,7 +673,7 @@ hwmon_device_register_with_info(struct device *dev, const char *name, const struct hwmon_chip_info *chip, const struct attribute_group **groups) { - if (chip && (!chip->ops || !chip->info)) + if (chip && (!chip->ops || !chip->ops->is_visible || !chip->info)) return ERR_PTR(-EINVAL); return __hwmon_device_register(dev, name, drvdata, chip, groups); From b2a4cc3a060da0de17ab1e854cef89f5e74e2064 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 16 Oct 2016 17:11:52 -0700 Subject: [PATCH 26/41] hwmon: (core) Explain why at least two attribute groups are allocated A list of sysfs attribute groups is NULL-terminated, so we always need to allocate data for at least two groups (the dynamically generated group plus the NULL pointer). Add a comment to explain the situation. Signed-off-by: Guenter Roeck --- drivers/hwmon/hwmon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index a0b5becf91fa..8dc0466a9307 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -561,7 +561,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, if (chip) { struct attribute **attrs; - int ngroups = 2; + int ngroups = 2; /* terminating NULL plus &hwdev->groups */ if (groups) for (i = 0; groups[i]; i++) From 848ba0a2f20dc121a3ef5272a24641d2bd963d8b Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 16 Oct 2016 17:20:43 -0700 Subject: [PATCH 27/41] hwmon: (core) Rename groups parameter in API to extra_groups The 'groups' parameter of hwmon_device_register_with_info() and devm_hwmon_device_register_with_info() is only necessary if extra non-standard attributes need to be provided. Rename the parameter to extra_groups and clarify the documentation. Signed-off-by: Guenter Roeck --- Documentation/hwmon/hwmon-kernel-api.txt | 22 +++++++++++----------- drivers/hwmon/hwmon.c | 8 ++++---- include/linux/hwmon.h | 8 ++++---- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Documentation/hwmon/hwmon-kernel-api.txt b/Documentation/hwmon/hwmon-kernel-api.txt index 633fa90909eb..2505ae67e2b6 100644 --- a/Documentation/hwmon/hwmon-kernel-api.txt +++ b/Documentation/hwmon/hwmon-kernel-api.txt @@ -37,14 +37,14 @@ struct device * hwmon_device_register_with_info(struct device *dev, const char *name, void *drvdata, const struct hwmon_chip_info *info, - const struct attribute_group **groups); + const struct attribute_group **extra_groups); struct device * devm_hwmon_device_register_with_info(struct device *dev, - const char *name, - void *drvdata, - const struct hwmon_chip_info *info, - const struct attribute_group **groups); + const char *name, + void *drvdata, + const struct hwmon_chip_info *info, + const struct attribute_group **extra_groups); void hwmon_device_unregister(struct device *dev); void devm_hwmon_device_unregister(struct device *dev); @@ -100,9 +100,9 @@ const char *name Device name void *drvdata Driver private data const struct hwmon_chip_info *info Pointer to chip description. -const struct attribute_group **groups - Null-terminated list of additional sysfs attribute - groups. +const struct attribute_group **extra_groups + Null-terminated list of additional non-standard + sysfs attribute groups. This function returns a pointer to the created hardware monitoring device on success and a negative error code for failure. @@ -287,9 +287,9 @@ Driver-provided sysfs attributes If the hardware monitoring device is registered with hwmon_device_register_with_info or devm_hwmon_device_register_with_info, -it is most likely not necessary to provide sysfs attributes. Only non-standard -sysfs attributes need to be provided when one of those registration functions -is used. +it is most likely not necessary to provide sysfs attributes. Only additional +non-standard sysfs attributes need to be provided when one of those registration +functions is used. The header file linux/hwmon-sysfs.h provides a number of useful macros to declare and use hardware monitoring sysfs attributes. diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 8dc0466a9307..58c328f4508d 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -659,8 +659,8 @@ EXPORT_SYMBOL_GPL(hwmon_device_register_with_groups); * @dev: the parent device * @name: hwmon name attribute * @drvdata: driver data to attach to created device - * @info: Pointer to hwmon chip information - * @groups - pointer to list of driver specific attribute groups + * @info: pointer to hwmon chip information + * @extra_groups: pointer to list of additional non-standard attribute groups * * hwmon_device_unregister() must be called when the device is no * longer needed. @@ -671,12 +671,12 @@ struct device * hwmon_device_register_with_info(struct device *dev, const char *name, void *drvdata, const struct hwmon_chip_info *chip, - const struct attribute_group **groups) + const struct attribute_group **extra_groups) { if (chip && (!chip->ops || !chip->ops->is_visible || !chip->info)) return ERR_PTR(-EINVAL); - return __hwmon_device_register(dev, name, drvdata, chip, groups); + return __hwmon_device_register(dev, name, drvdata, chip, extra_groups); } EXPORT_SYMBOL_GPL(hwmon_device_register_with_info); diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index 2588e6ee7660..78d59dba563e 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -377,12 +377,12 @@ struct device * hwmon_device_register_with_info(struct device *dev, const char *name, void *drvdata, const struct hwmon_chip_info *info, - const struct attribute_group **groups); + const struct attribute_group **extra_groups); struct device * devm_hwmon_device_register_with_info(struct device *dev, - const char *name, void *drvdata, - const struct hwmon_chip_info *info, - const struct attribute_group **groups); + const char *name, void *drvdata, + const struct hwmon_chip_info *info, + const struct attribute_group **extra_groups); void hwmon_device_unregister(struct device *dev); void devm_hwmon_device_unregister(struct device *dev); From 3a412d5e4a1c831723d0aaf305f1cf9a78ad9c90 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 16 Oct 2016 10:52:04 -0700 Subject: [PATCH 28/41] hwmon: (core) Simplify sysfs attribute name allocation Allocating the sysfs attribute name only if needed and only with the required minimum length looks optimal, but does not take the additional overhead for both devm_ data structures and the allocation header itself into account. This also results in unnecessary memory fragmentation. Move the sysfs name string into struct hwmon_device_attribute and give it a sufficient length to reduce this overhead. Signed-off-by: Guenter Roeck --- drivers/hwmon/hwmon.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 58c328f4508d..3932f9276c07 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -38,12 +38,15 @@ struct hwmon_device { #define to_hwmon_device(d) container_of(d, struct hwmon_device, dev) +#define MAX_SYSFS_ATTR_NAME_LENGTH 32 + struct hwmon_device_attribute { struct device_attribute dev_attr; const struct hwmon_ops *ops; enum hwmon_sensor_types type; u32 attr; int index; + char name[MAX_SYSFS_ATTR_NAME_LENGTH]; }; #define to_hwmon_attr(d) \ @@ -261,20 +264,18 @@ static struct attribute *hwmon_genattr(struct device *dev, if ((mode & S_IWUGO) && !ops->write) return ERR_PTR(-EINVAL); - if (type == hwmon_chip) { - name = (char *)template; - } else { - name = devm_kzalloc(dev, strlen(template) + 16, GFP_KERNEL); - if (!name) - return ERR_PTR(-ENOMEM); - scnprintf(name, strlen(template) + 16, template, - index + hwmon_attr_base(type)); - } - hattr = devm_kzalloc(dev, sizeof(*hattr), GFP_KERNEL); if (!hattr) return ERR_PTR(-ENOMEM); + if (type == hwmon_chip) { + name = (char *)template; + } else { + scnprintf(hattr->name, sizeof(hattr->name), template, + index + hwmon_attr_base(type)); + name = hattr->name; + } + hattr->type = type; hattr->attr = attr; hattr->index = index; From 66e1c91713396734f8cf778a2fc5212876c04bc0 Mon Sep 17 00:00:00 2001 From: John Muir Date: Thu, 1 Dec 2016 18:32:41 -0800 Subject: [PATCH 29/41] hwmon: Add Texas Instruments TMP108 temperature sensor driver. Add support for the TI TMP108 temperature sensor with some device configuration parameters. Signed-off-by: John Muir [groeck: Initialize of_match_table] Signed-off-by: Guenter Roeck --- Documentation/hwmon/tmp108 | 36 +++ drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/tmp108.c | 469 +++++++++++++++++++++++++++++++++++++ 4 files changed, 517 insertions(+) create mode 100644 Documentation/hwmon/tmp108 create mode 100644 drivers/hwmon/tmp108.c diff --git a/Documentation/hwmon/tmp108 b/Documentation/hwmon/tmp108 new file mode 100644 index 000000000000..25802df23010 --- /dev/null +++ b/Documentation/hwmon/tmp108 @@ -0,0 +1,36 @@ +Kernel driver tmp108 +==================== + +Supported chips: + * Texas Instruments TMP108 + Prefix: 'tmp108' + Addresses scanned: none + Datasheet: http://www.ti.com/product/tmp108 + +Author: + John Muir + +Description +----------- + +The Texas Instruments TMP108 implements one temperature sensor. An alert pin +can be set when temperatures exceed minimum or maximum values plus or minus a +hysteresis value. (This driver does not support interrupts for the alert pin, +and the device runs in comparator mode.) + +The sensor is accurate to 0.75C over the range of -25 to +85 C, and to 1.0 +degree from -40 to +125 C. Resolution of the sensor is 0.0625 degree. The +operating temperature has a minimum of -55 C and a maximum of +150 C. +Hysteresis values can be set to 0, 1, 2, or 4C. + +The TMP108 has a programmable update rate that can select between 8, 4, 1, and +0.5 Hz. + +By default the TMP108 reads the temperature continuously. To conserve power, +the TMP108 has a one-shot mode where the device is normally shut-down. When a +one shot is requested the temperature is read, the result can be retrieved, +and then the device is shut down automatically. (This driver only supports +continuous mode.) + +The driver provides the common sysfs-interface for temperatures (see +Documentation/hwmon/sysfs-interface under Temperatures). diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 1adda8a5adce..190d270b20a2 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1602,6 +1602,17 @@ config SENSORS_TMP103 This driver can also be built as a module. If so, the module will be called tmp103. +config SENSORS_TMP108 + tristate "Texas Instruments TMP108" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for Texas Instruments TMP108 + sensor chips. + + This driver can also be built as a module. If so, the module + will be called tmp108. + config SENSORS_TMP401 tristate "Texas Instruments TMP401 and compatibles" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index c651f0f1d047..d2cb7e804a0f 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -153,6 +153,7 @@ obj-$(CONFIG_SENSORS_TC74) += tc74.o obj-$(CONFIG_SENSORS_THMC50) += thmc50.o obj-$(CONFIG_SENSORS_TMP102) += tmp102.o obj-$(CONFIG_SENSORS_TMP103) += tmp103.o +obj-$(CONFIG_SENSORS_TMP108) += tmp108.o obj-$(CONFIG_SENSORS_TMP401) += tmp401.o obj-$(CONFIG_SENSORS_TMP421) += tmp421.o obj-$(CONFIG_SENSORS_TWL4030_MADC)+= twl4030-madc-hwmon.o diff --git a/drivers/hwmon/tmp108.c b/drivers/hwmon/tmp108.c new file mode 100644 index 000000000000..91bb94639286 --- /dev/null +++ b/drivers/hwmon/tmp108.c @@ -0,0 +1,469 @@ +/* Texas Instruments TMP108 SMBus temperature sensor driver + * + * Copyright (C) 2016 John Muir + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "tmp108" + +#define TMP108_REG_TEMP 0x00 +#define TMP108_REG_CONF 0x01 +#define TMP108_REG_TLOW 0x02 +#define TMP108_REG_THIGH 0x03 + +#define TMP108_TEMP_MIN_MC -50000 /* Minimum millicelcius. */ +#define TMP108_TEMP_MAX_MC 127937 /* Maximum millicelcius. */ + +/* Configuration register bits. + * Note: these bit definitions are byte swapped. + */ +#define TMP108_CONF_M0 0x0100 /* Sensor mode. */ +#define TMP108_CONF_M1 0x0200 +#define TMP108_CONF_TM 0x0400 /* Thermostat mode. */ +#define TMP108_CONF_FL 0x0800 /* Watchdog flag - TLOW */ +#define TMP108_CONF_FH 0x1000 /* Watchdog flag - THIGH */ +#define TMP108_CONF_CR0 0x2000 /* Conversion rate. */ +#define TMP108_CONF_CR1 0x4000 +#define TMP108_CONF_ID 0x8000 +#define TMP108_CONF_HYS0 0x0010 /* Hysteresis. */ +#define TMP108_CONF_HYS1 0x0020 +#define TMP108_CONF_POL 0x0080 /* Polarity of alert. */ + +/* Defaults set by the hardware upon reset. */ +#define TMP108_CONF_DEFAULTS (TMP108_CONF_CR0 | TMP108_CONF_TM |\ + TMP108_CONF_HYS0 | TMP108_CONF_M1) +/* These bits are read-only. */ +#define TMP108_CONF_READ_ONLY (TMP108_CONF_FL | TMP108_CONF_FH |\ + TMP108_CONF_ID) + +#define TMP108_CONF_MODE_MASK (TMP108_CONF_M0|TMP108_CONF_M1) +#define TMP108_MODE_SHUTDOWN 0x0000 +#define TMP108_MODE_ONE_SHOT TMP108_CONF_M0 +#define TMP108_MODE_CONTINUOUS TMP108_CONF_M1 /* Default */ + /* When M1 is set, M0 is ignored. */ + +#define TMP108_CONF_CONVRATE_MASK (TMP108_CONF_CR0|TMP108_CONF_CR1) +#define TMP108_CONVRATE_0P25HZ 0x0000 +#define TMP108_CONVRATE_1HZ TMP108_CONF_CR0 /* Default */ +#define TMP108_CONVRATE_4HZ TMP108_CONF_CR1 +#define TMP108_CONVRATE_16HZ (TMP108_CONF_CR0|TMP108_CONF_CR1) + +#define TMP108_CONF_HYSTERESIS_MASK (TMP108_CONF_HYS0|TMP108_CONF_HYS1) +#define TMP108_HYSTERESIS_0C 0x0000 +#define TMP108_HYSTERESIS_1C TMP108_CONF_HYS0 /* Default */ +#define TMP108_HYSTERESIS_2C TMP108_CONF_HYS1 +#define TMP108_HYSTERESIS_4C (TMP108_CONF_HYS0|TMP108_CONF_HYS1) + +#define TMP108_CONVERSION_TIME_MS 30 /* in milli-seconds */ + +struct tmp108 { + struct regmap *regmap; + u16 orig_config; + unsigned long ready_time; +}; + +/* convert 12-bit TMP108 register value to milliCelsius */ +static inline int tmp108_temp_reg_to_mC(s16 val) +{ + return (val & ~0x0f) * 1000 / 256; +} + +/* convert milliCelsius to left adjusted 12-bit TMP108 register value */ +static inline u16 tmp108_mC_to_temp_reg(int val) +{ + return (val * 256) / 1000; +} + +static int tmp108_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *temp) +{ + struct tmp108 *tmp108 = dev_get_drvdata(dev); + unsigned int regval; + int err, hyst; + + if (type == hwmon_chip) { + if (attr == hwmon_chip_update_interval) { + err = regmap_read(tmp108->regmap, TMP108_REG_CONF, + ®val); + if (err < 0) + return err; + switch (regval & TMP108_CONF_CONVRATE_MASK) { + case TMP108_CONVRATE_0P25HZ: + default: + *temp = 4000; + break; + case TMP108_CONVRATE_1HZ: + *temp = 1000; + break; + case TMP108_CONVRATE_4HZ: + *temp = 250; + break; + case TMP108_CONVRATE_16HZ: + *temp = 63; + break; + } + return 0; + } + return -EOPNOTSUPP; + } + + switch (attr) { + case hwmon_temp_input: + /* Is it too early to return a conversion ? */ + if (time_before(jiffies, tmp108->ready_time)) { + dev_dbg(dev, "%s: Conversion not ready yet..\n", + __func__); + return -EAGAIN; + } + err = regmap_read(tmp108->regmap, TMP108_REG_TEMP, ®val); + if (err < 0) + return err; + *temp = tmp108_temp_reg_to_mC(regval); + break; + case hwmon_temp_min: + case hwmon_temp_max: + err = regmap_read(tmp108->regmap, attr == hwmon_temp_min ? + TMP108_REG_TLOW : TMP108_REG_THIGH, ®val); + if (err < 0) + return err; + *temp = tmp108_temp_reg_to_mC(regval); + break; + case hwmon_temp_min_alarm: + case hwmon_temp_max_alarm: + err = regmap_read(tmp108->regmap, TMP108_REG_CONF, ®val); + if (err < 0) + return err; + *temp = !!(regval & (attr == hwmon_temp_min_alarm ? + TMP108_CONF_FL : TMP108_CONF_FH)); + break; + case hwmon_temp_min_hyst: + case hwmon_temp_max_hyst: + err = regmap_read(tmp108->regmap, TMP108_REG_CONF, ®val); + if (err < 0) + return err; + switch (regval & TMP108_CONF_HYSTERESIS_MASK) { + case TMP108_HYSTERESIS_0C: + default: + hyst = 0; + break; + case TMP108_HYSTERESIS_1C: + hyst = 1000; + break; + case TMP108_HYSTERESIS_2C: + hyst = 2000; + break; + case TMP108_HYSTERESIS_4C: + hyst = 4000; + break; + } + err = regmap_read(tmp108->regmap, attr == hwmon_temp_min_hyst ? + TMP108_REG_TLOW : TMP108_REG_THIGH, ®val); + if (err < 0) + return err; + *temp = tmp108_temp_reg_to_mC(regval); + if (attr == hwmon_temp_min_hyst) + *temp += hyst; + else + *temp -= hyst; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int tmp108_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long temp) +{ + struct tmp108 *tmp108 = dev_get_drvdata(dev); + u32 regval, mask; + int err; + + if (type == hwmon_chip) { + if (attr == hwmon_chip_update_interval) { + if (temp < 156) + mask = TMP108_CONVRATE_16HZ; + else if (temp < 625) + mask = TMP108_CONVRATE_4HZ; + else if (temp < 2500) + mask = TMP108_CONVRATE_1HZ; + else + mask = TMP108_CONVRATE_0P25HZ; + return regmap_update_bits(tmp108->regmap, + TMP108_REG_CONF, + TMP108_CONF_CONVRATE_MASK, + mask); + } + return -EOPNOTSUPP; + } + + switch (attr) { + case hwmon_temp_min: + case hwmon_temp_max: + temp = clamp_val(temp, TMP108_TEMP_MIN_MC, TMP108_TEMP_MAX_MC); + return regmap_write(tmp108->regmap, + attr == hwmon_temp_min ? + TMP108_REG_TLOW : TMP108_REG_THIGH, + tmp108_mC_to_temp_reg(temp)); + case hwmon_temp_min_hyst: + case hwmon_temp_max_hyst: + temp = clamp_val(temp, TMP108_TEMP_MIN_MC, TMP108_TEMP_MAX_MC); + err = regmap_read(tmp108->regmap, + attr == hwmon_temp_min_hyst ? + TMP108_REG_TLOW : TMP108_REG_THIGH, + ®val); + if (err < 0) + return err; + if (attr == hwmon_temp_min_hyst) + temp -= tmp108_temp_reg_to_mC(regval); + else + temp = tmp108_temp_reg_to_mC(regval) - temp; + if (temp < 500) + mask = TMP108_HYSTERESIS_0C; + else if (temp < 1500) + mask = TMP108_HYSTERESIS_1C; + else if (temp < 3000) + mask = TMP108_HYSTERESIS_2C; + else + mask = TMP108_HYSTERESIS_4C; + return regmap_update_bits(tmp108->regmap, TMP108_REG_CONF, + TMP108_CONF_HYSTERESIS_MASK, mask); + default: + return -EOPNOTSUPP; + } +} + +static umode_t tmp108_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + if (type == hwmon_chip && attr == hwmon_chip_update_interval) + return 0644; + + if (type != hwmon_temp) + return 0; + + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_min_alarm: + case hwmon_temp_max_alarm: + return 0444; + case hwmon_temp_min: + case hwmon_temp_max: + case hwmon_temp_min_hyst: + case hwmon_temp_max_hyst: + return 0644; + default: + return 0; + } +} + +static u32 tmp108_chip_config[] = { + HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL, + 0 +}; + +static const struct hwmon_channel_info tmp108_chip = { + .type = hwmon_chip, + .config = tmp108_chip_config, +}; + +static u32 tmp108_temp_config[] = { + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | HWMON_T_MIN_HYST + | HWMON_T_MAX_HYST | HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM, + 0 +}; + +static const struct hwmon_channel_info tmp108_temp = { + .type = hwmon_temp, + .config = tmp108_temp_config, +}; + +static const struct hwmon_channel_info *tmp108_info[] = { + &tmp108_chip, + &tmp108_temp, + NULL +}; + +static const struct hwmon_ops tmp108_hwmon_ops = { + .is_visible = tmp108_is_visible, + .read = tmp108_read, + .write = tmp108_write, +}; + +static const struct hwmon_chip_info tmp108_chip_info = { + .ops = &tmp108_hwmon_ops, + .info = tmp108_info, +}; + +static void tmp108_restore_config(void *data) +{ + struct tmp108 *tmp108 = data; + + regmap_write(tmp108->regmap, TMP108_REG_CONF, tmp108->orig_config); +} + +static bool tmp108_is_writeable_reg(struct device *dev, unsigned int reg) +{ + return reg != TMP108_REG_TEMP; +} + +static bool tmp108_is_volatile_reg(struct device *dev, unsigned int reg) +{ + /* Configuration register must be volatile to enable FL and FH. */ + return reg == TMP108_REG_TEMP || reg == TMP108_REG_CONF; +} + +static const struct regmap_config tmp108_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = TMP108_REG_THIGH, + .writeable_reg = tmp108_is_writeable_reg, + .volatile_reg = tmp108_is_volatile_reg, + .val_format_endian = REGMAP_ENDIAN_BIG, + .cache_type = REGCACHE_RBTREE, + .use_single_rw = true, +}; + +static int tmp108_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct tmp108 *tmp108; + int err; + u32 config; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WORD_DATA)) { + dev_err(dev, + "adapter doesn't support SMBus word transactions\n"); + return -ENODEV; + } + + tmp108 = devm_kzalloc(dev, sizeof(*tmp108), GFP_KERNEL); + if (!tmp108) + return -ENOMEM; + + dev_set_drvdata(dev, tmp108); + + tmp108->regmap = devm_regmap_init_i2c(client, &tmp108_regmap_config); + if (IS_ERR(tmp108->regmap)) { + err = PTR_ERR(tmp108->regmap); + dev_err(dev, "regmap init failed: %d", err); + return err; + } + + err = regmap_read(tmp108->regmap, TMP108_REG_CONF, &config); + if (err < 0) { + dev_err(dev, "error reading config register: %d", err); + return err; + } + tmp108->orig_config = config; + + /* Only continuous mode is supported. */ + config &= ~TMP108_CONF_MODE_MASK; + config |= TMP108_MODE_CONTINUOUS; + + /* Only comparator mode is supported. */ + config &= ~TMP108_CONF_TM; + + err = regmap_write(tmp108->regmap, TMP108_REG_CONF, config); + if (err < 0) { + dev_err(dev, "error writing config register: %d", err); + return err; + } + + tmp108->ready_time = jiffies; + if ((tmp108->orig_config & TMP108_CONF_MODE_MASK) == + TMP108_MODE_SHUTDOWN) + tmp108->ready_time += + msecs_to_jiffies(TMP108_CONVERSION_TIME_MS); + + err = devm_add_action_or_reset(dev, tmp108_restore_config, tmp108); + if (err) { + dev_err(dev, "add action or reset failed: %d", err); + return err; + } + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + tmp108, + &tmp108_chip_info, + NULL); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static int __maybe_unused tmp108_suspend(struct device *dev) +{ + struct tmp108 *tmp108 = dev_get_drvdata(dev); + + return regmap_update_bits(tmp108->regmap, TMP108_REG_CONF, + TMP108_CONF_MODE_MASK, TMP108_MODE_SHUTDOWN); +} + +static int __maybe_unused tmp108_resume(struct device *dev) +{ + struct tmp108 *tmp108 = dev_get_drvdata(dev); + int err; + + err = regmap_update_bits(tmp108->regmap, TMP108_REG_CONF, + TMP108_CONF_MODE_MASK, TMP108_MODE_CONTINUOUS); + tmp108->ready_time = jiffies + + msecs_to_jiffies(TMP108_CONVERSION_TIME_MS); + return err; +} + +static SIMPLE_DEV_PM_OPS(tmp108_dev_pm_ops, tmp108_suspend, tmp108_resume); + +static const struct i2c_device_id tmp108_i2c_ids[] = { + { "tmp108", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tmp108_i2c_ids); + +#ifdef CONFIG_OF +static const struct of_device_id tmp108_of_ids[] = { + { .compatible = "ti,tmp108", }, + {} +}; +MODULE_DEVICE_TABLE(of, tmp108_of_ids); +#endif + +static struct i2c_driver tmp108_driver = { + .driver = { + .name = DRIVER_NAME, + .pm = &tmp108_dev_pm_ops, + .of_match_table = of_match_ptr(tmp108_of_ids), + }, + .probe = tmp108_probe, + .id_table = tmp108_i2c_ids, +}; + +module_i2c_driver(tmp108_driver); + +MODULE_AUTHOR("John Muir "); +MODULE_DESCRIPTION("Texas Instruments TMP108 temperature sensor driver"); +MODULE_LICENSE("GPL"); From 8017c0f2989f077a665bbf7881a1154ecd8334e7 Mon Sep 17 00:00:00 2001 From: John Muir Date: Thu, 1 Dec 2016 18:32:42 -0800 Subject: [PATCH 30/41] devicetree: hwmon: Add documentation for TMP108 driver. Simple hwmon binding documentation. Signed-off-by: John Muir Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/tmp108.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/tmp108.txt diff --git a/Documentation/devicetree/bindings/hwmon/tmp108.txt b/Documentation/devicetree/bindings/hwmon/tmp108.txt new file mode 100644 index 000000000000..8c4b10df86d9 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/tmp108.txt @@ -0,0 +1,14 @@ +TMP108 temperature sensor +------------------------- + +This device supports I2C only. + +Requires node properties: +- compatible : "ti,tmp108" +- reg : the I2C address of the device. This is 0x48, 0x49, 0x4a, or 0x4b. + +Example: + tmp108@48 { + compatible = "ti,tmp108"; + reg = <0x48>; + }; From df60d7013c36f86f7653af387915019d7da49613 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Fri, 18 Nov 2016 16:09:50 +0100 Subject: [PATCH 31/41] hwmon: (via-cputemp) Convert to hotplug state machine Install the callbacks via the state machine and let the core invoke the callbacks on the already online CPUs. When the hotplug state is unregistered the cleanup function is called for each cpu. So both cpu loops in init() and exit() are not longer required. Cc: Jean Delvare Cc: Guenter Roeck Cc: linux-hwmon@vger.kernel.org Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Guenter Roeck --- drivers/hwmon/via-cputemp.c | 63 ++++++++++--------------------------- 1 file changed, 17 insertions(+), 46 deletions(-) diff --git a/drivers/hwmon/via-cputemp.c b/drivers/hwmon/via-cputemp.c index 5b9866b1b437..d1f209a5feac 100644 --- a/drivers/hwmon/via-cputemp.c +++ b/drivers/hwmon/via-cputemp.c @@ -220,7 +220,7 @@ struct pdev_entry { static LIST_HEAD(pdev_list); static DEFINE_MUTEX(pdev_list_mutex); -static int via_cputemp_device_add(unsigned int cpu) +static int via_cputemp_online(unsigned int cpu) { int err; struct platform_device *pdev; @@ -261,7 +261,7 @@ exit: return err; } -static void via_cputemp_device_remove(unsigned int cpu) +static int via_cputemp_down_prep(unsigned int cpu) { struct pdev_entry *p; @@ -272,33 +272,13 @@ static void via_cputemp_device_remove(unsigned int cpu) list_del(&p->list); mutex_unlock(&pdev_list_mutex); kfree(p); - return; + return 0; } } mutex_unlock(&pdev_list_mutex); + return 0; } -static int via_cputemp_cpu_callback(struct notifier_block *nfb, - unsigned long action, void *hcpu) -{ - unsigned int cpu = (unsigned long) hcpu; - - switch (action) { - case CPU_ONLINE: - case CPU_DOWN_FAILED: - via_cputemp_device_add(cpu); - break; - case CPU_DOWN_PREPARE: - via_cputemp_device_remove(cpu); - break; - } - return NOTIFY_OK; -} - -static struct notifier_block via_cputemp_cpu_notifier __refdata = { - .notifier_call = via_cputemp_cpu_callback, -}; - static const struct x86_cpu_id __initconst cputemp_ids[] = { { X86_VENDOR_CENTAUR, 6, 0xa, }, /* C7 A */ { X86_VENDOR_CENTAUR, 6, 0xd, }, /* C7 D */ @@ -307,9 +287,11 @@ static const struct x86_cpu_id __initconst cputemp_ids[] = { }; MODULE_DEVICE_TABLE(x86cpu, cputemp_ids); +static enum cpuhp_state via_temp_online; + static int __init via_cputemp_init(void) { - int i, err; + int err; if (!x86_match_cpu(cputemp_ids)) return -ENODEV; @@ -318,44 +300,33 @@ static int __init via_cputemp_init(void) if (err) goto exit; - cpu_notifier_register_begin(); - for_each_online_cpu(i) - via_cputemp_device_add(i); + err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "hwmon/via:online", + via_cputemp_online, via_cputemp_down_prep); + if (err < 0) + goto exit_driver_unreg; + via_temp_online = err; #ifndef CONFIG_HOTPLUG_CPU if (list_empty(&pdev_list)) { - cpu_notifier_register_done(); err = -ENODEV; - goto exit_driver_unreg; + goto exit_hp_unreg; } #endif - - __register_hotcpu_notifier(&via_cputemp_cpu_notifier); - cpu_notifier_register_done(); return 0; #ifndef CONFIG_HOTPLUG_CPU +exit_hp_unreg: + cpuhp_remove_state_nocalls(via_temp_online); +#endif exit_driver_unreg: platform_driver_unregister(&via_cputemp_driver); -#endif exit: return err; } static void __exit via_cputemp_exit(void) { - struct pdev_entry *p, *n; - - cpu_notifier_register_begin(); - __unregister_hotcpu_notifier(&via_cputemp_cpu_notifier); - mutex_lock(&pdev_list_mutex); - list_for_each_entry_safe(p, n, &pdev_list, list) { - platform_device_unregister(p->pdev); - list_del(&p->list); - kfree(p); - } - mutex_unlock(&pdev_list_mutex); - cpu_notifier_register_done(); + cpuhp_remove_state(via_temp_online); platform_driver_unregister(&via_cputemp_driver); } From 7cb53e28b03006fa655632fe07b27a862f3f5cb5 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 3 Dec 2016 11:07:14 -0800 Subject: [PATCH 32/41] hwmon: (adm1025) Fix overflows seen when writing voltage limits Writes into voltage limit attributes can overflow due to an unbound multiplication. Reviewed-by: Jean Delvare Signed-off-by: Guenter Roeck --- drivers/hwmon/adm1025.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/adm1025.c b/drivers/hwmon/adm1025.c index d6c767ace916..1abb4609b412 100644 --- a/drivers/hwmon/adm1025.c +++ b/drivers/hwmon/adm1025.c @@ -93,7 +93,7 @@ static const int in_scale[6] = { 2500, 2250, 3300, 5000, 12000, 3300 }; #define IN_FROM_REG(reg, scale) (((reg) * (scale) + 96) / 192) #define IN_TO_REG(val, scale) ((val) <= 0 ? 0 : \ - (val) * 192 >= (scale) * 255 ? 255 : \ + (val) >= (scale) * 255 / 192 ? 255 : \ ((val) * 192 + (scale) / 2) / (scale)) #define TEMP_FROM_REG(reg) ((reg) * 1000) From f1b9baa99bd98f83e8601e5cfd3bd28027a6f5a8 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 3 Dec 2016 11:08:07 -0800 Subject: [PATCH 33/41] hwmon: (adm1026) Fix overflows seen when writing into limit attributes Fix overflows seen when writing large values into voltage limit, temperature limit, temperature offset, and DAC attributes. Overflows are seen due to unbound multiplications and additions. While at it, change the low temperature limit to -128 degrees C, since this is the minimum temperature accepted by the chip. Reviewed-by: Jean Delvare Signed-off-by: Guenter Roeck --- drivers/hwmon/adm1026.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/drivers/hwmon/adm1026.c b/drivers/hwmon/adm1026.c index e67b9a50ac7c..b2a5d9e5c590 100644 --- a/drivers/hwmon/adm1026.c +++ b/drivers/hwmon/adm1026.c @@ -197,8 +197,9 @@ static int adm1026_scaling[] = { /* .001 Volts */ }; #define NEG12_OFFSET 16000 #define SCALE(val, from, to) (((val)*(to) + ((from)/2))/(from)) -#define INS_TO_REG(n, val) (clamp_val(SCALE(val, adm1026_scaling[n], 192),\ - 0, 255)) +#define INS_TO_REG(n, val) \ + SCALE(clamp_val(val, 0, 255 * adm1026_scaling[n] / 192), \ + adm1026_scaling[n], 192) #define INS_FROM_REG(n, val) (SCALE(val, 192, adm1026_scaling[n])) /* @@ -215,11 +216,11 @@ static int adm1026_scaling[] = { /* .001 Volts */ #define DIV_TO_REG(val) ((val) >= 8 ? 3 : (val) >= 4 ? 2 : (val) >= 2 ? 1 : 0) /* Temperature is reported in 1 degC increments */ -#define TEMP_TO_REG(val) (clamp_val(((val) + ((val) < 0 ? -500 : 500)) \ - / 1000, -127, 127)) +#define TEMP_TO_REG(val) DIV_ROUND_CLOSEST(clamp_val(val, -128000, 127000), \ + 1000) #define TEMP_FROM_REG(val) ((val) * 1000) -#define OFFSET_TO_REG(val) (clamp_val(((val) + ((val) < 0 ? -500 : 500)) \ - / 1000, -127, 127)) +#define OFFSET_TO_REG(val) DIV_ROUND_CLOSEST(clamp_val(val, -128000, 127000), \ + 1000) #define OFFSET_FROM_REG(val) ((val) * 1000) #define PWM_TO_REG(val) (clamp_val(val, 0, 255)) @@ -233,7 +234,8 @@ static int adm1026_scaling[] = { /* .001 Volts */ * indicates that the DAC could be used to drive the fans, but in our * example board (Arima HDAMA) it isn't connected to the fans at all. */ -#define DAC_TO_REG(val) (clamp_val(((((val) * 255) + 500) / 2500), 0, 255)) +#define DAC_TO_REG(val) DIV_ROUND_CLOSEST(clamp_val(val, 0, 2500) * 255, \ + 2500) #define DAC_FROM_REG(val) (((val) * 2500) / 255) /* @@ -593,7 +595,10 @@ static ssize_t set_in16_min(struct device *dev, struct device_attribute *attr, return err; mutex_lock(&data->update_lock); - data->in_min[16] = INS_TO_REG(16, val + NEG12_OFFSET); + data->in_min[16] = INS_TO_REG(16, + clamp_val(val, INT_MIN, + INT_MAX - NEG12_OFFSET) + + NEG12_OFFSET); adm1026_write_value(client, ADM1026_REG_IN_MIN[16], data->in_min[16]); mutex_unlock(&data->update_lock); return count; @@ -618,7 +623,10 @@ static ssize_t set_in16_max(struct device *dev, struct device_attribute *attr, return err; mutex_lock(&data->update_lock); - data->in_max[16] = INS_TO_REG(16, val+NEG12_OFFSET); + data->in_max[16] = INS_TO_REG(16, + clamp_val(val, INT_MIN, + INT_MAX - NEG12_OFFSET) + + NEG12_OFFSET); adm1026_write_value(client, ADM1026_REG_IN_MAX[16], data->in_max[16]); mutex_unlock(&data->update_lock); return count; From b94793b4da1011f24321e2ecc5e173a7198358a5 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 3 Dec 2016 11:09:55 -0800 Subject: [PATCH 34/41] hwmon: (adt7462) Fix overflows seen when writing into limit attributes Fix overflows seen when writing large values into temperature limit, voltage limit, and pwm hysteresis attributes. The input parameter to DIV_ROUND_CLOSEST() needs to be clamped to avoid such overflows. Reviewed-by: Jean Delvare Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7462.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/hwmon/adt7462.c b/drivers/hwmon/adt7462.c index 5929e126da63..19f2a6d48bac 100644 --- a/drivers/hwmon/adt7462.c +++ b/drivers/hwmon/adt7462.c @@ -810,8 +810,8 @@ static ssize_t set_temp_min(struct device *dev, if (kstrtol(buf, 10, &temp) || !temp_enabled(data, attr->index)) return -EINVAL; + temp = clamp_val(temp, -64000, 191000); temp = DIV_ROUND_CLOSEST(temp, 1000) + 64; - temp = clamp_val(temp, 0, 255); mutex_lock(&data->lock); data->temp_min[attr->index] = temp; @@ -848,8 +848,8 @@ static ssize_t set_temp_max(struct device *dev, if (kstrtol(buf, 10, &temp) || !temp_enabled(data, attr->index)) return -EINVAL; + temp = clamp_val(temp, -64000, 191000); temp = DIV_ROUND_CLOSEST(temp, 1000) + 64; - temp = clamp_val(temp, 0, 255); mutex_lock(&data->lock); data->temp_max[attr->index] = temp; @@ -912,9 +912,9 @@ static ssize_t set_volt_max(struct device *dev, if (kstrtol(buf, 10, &temp) || !x) return -EINVAL; + temp = clamp_val(temp, 0, 255 * x / 1000); temp *= 1000; /* convert mV to uV */ temp = DIV_ROUND_CLOSEST(temp, x); - temp = clamp_val(temp, 0, 255); mutex_lock(&data->lock); data->volt_max[attr->index] = temp; @@ -954,9 +954,9 @@ static ssize_t set_volt_min(struct device *dev, if (kstrtol(buf, 10, &temp) || !x) return -EINVAL; + temp = clamp_val(temp, 0, 255 * x / 1000); temp *= 1000; /* convert mV to uV */ temp = DIV_ROUND_CLOSEST(temp, x); - temp = clamp_val(temp, 0, 255); mutex_lock(&data->lock); data->volt_min[attr->index] = temp; @@ -1220,8 +1220,8 @@ static ssize_t set_pwm_hyst(struct device *dev, if (kstrtol(buf, 10, &temp)) return -EINVAL; + temp = clamp_val(temp, 0, 15000); temp = DIV_ROUND_CLOSEST(temp, 1000); - temp = clamp_val(temp, 0, 15); /* package things up */ temp &= ADT7462_PWM_HYST_MASK; @@ -1306,8 +1306,8 @@ static ssize_t set_pwm_tmin(struct device *dev, if (kstrtol(buf, 10, &temp)) return -EINVAL; + temp = clamp_val(temp, -64000, 191000); temp = DIV_ROUND_CLOSEST(temp, 1000) + 64; - temp = clamp_val(temp, 0, 255); mutex_lock(&data->lock); data->pwm_tmin[attr->index] = temp; From 64bd708ae0edced5eee764c2321b280bc1e16550 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 3 Dec 2016 11:10:34 -0800 Subject: [PATCH 35/41] hwmon: (adt7470) Fix overflows seen when writing into limit attributes Fix overflows seen when writing large values into various temperature limit attributes. The input value passed to DIV_ROUND_CLOSEST() needs to be clamped to avoid such overflows. Reviewed-by: Jean Delvare Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7470.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c index 6e60ca53406e..c9a1d9c25572 100644 --- a/drivers/hwmon/adt7470.c +++ b/drivers/hwmon/adt7470.c @@ -483,8 +483,8 @@ static ssize_t set_temp_min(struct device *dev, if (kstrtol(buf, 10, &temp)) return -EINVAL; + temp = clamp_val(temp, -128000, 127000); temp = DIV_ROUND_CLOSEST(temp, 1000); - temp = clamp_val(temp, -128, 127); mutex_lock(&data->lock); data->temp_min[attr->index] = temp; @@ -517,8 +517,8 @@ static ssize_t set_temp_max(struct device *dev, if (kstrtol(buf, 10, &temp)) return -EINVAL; + temp = clamp_val(temp, -128000, 127000); temp = DIV_ROUND_CLOSEST(temp, 1000); - temp = clamp_val(temp, -128, 127); mutex_lock(&data->lock); data->temp_max[attr->index] = temp; @@ -880,8 +880,8 @@ static ssize_t set_pwm_tmin(struct device *dev, if (kstrtol(buf, 10, &temp)) return -EINVAL; + temp = clamp_val(temp, -128000, 127000); temp = DIV_ROUND_CLOSEST(temp, 1000); - temp = clamp_val(temp, -128, 127); mutex_lock(&data->lock); data->pwm_tmin[attr->index] = temp; From c0d04e9112ad59d73f23f3b0f6726c5e798dfcbf Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 4 Dec 2016 18:15:25 -0800 Subject: [PATCH 36/41] hwmon: (nct7802) Fix overflows seen when writing into limit attributes Fix overflows seen when writing voltage and temperature limit attributes. The value passed to DIV_ROUND_CLOSEST() needs to be clamped, and the value parameter passed to nct7802_write_fan_min() is an unsigned long. Also, writing values larger than 2700000 into a fan limit attribute results in writing 0 into the chip's limit registers. The exact behavior when writing this value is unspecified. For consistency, report a limit of 1350000 if the chip register reads 0. This may be wrong, and the chip behavior should be verified with the actual chip, but it is better than reporting a value of 0 (which, when written, results in writing a value of 0x1fff into the chip register). Fixes: 3434f3783580 ("hwmon: Driver for Nuvoton NCT7802Y") Reviewed-by: Jean Delvare Signed-off-by: Guenter Roeck --- drivers/hwmon/nct7802.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/nct7802.c b/drivers/hwmon/nct7802.c index 3ce33d244cc0..12b94b094c0d 100644 --- a/drivers/hwmon/nct7802.c +++ b/drivers/hwmon/nct7802.c @@ -259,13 +259,15 @@ static int nct7802_read_fan_min(struct nct7802_data *data, u8 reg_fan_low, ret = 0; else if (ret) ret = DIV_ROUND_CLOSEST(1350000U, ret); + else + ret = 1350000U; abort: mutex_unlock(&data->access_lock); return ret; } static int nct7802_write_fan_min(struct nct7802_data *data, u8 reg_fan_low, - u8 reg_fan_high, unsigned int limit) + u8 reg_fan_high, unsigned long limit) { int err; @@ -326,8 +328,8 @@ static int nct7802_write_voltage(struct nct7802_data *data, int nr, int index, int shift = 8 - REG_VOLTAGE_LIMIT_MSB_SHIFT[index - 1][nr]; int err; + voltage = clamp_val(voltage, 0, 0x3ff * nct7802_vmul[nr]); voltage = DIV_ROUND_CLOSEST(voltage, nct7802_vmul[nr]); - voltage = clamp_val(voltage, 0, 0x3ff); mutex_lock(&data->access_lock); err = regmap_write(data->regmap, @@ -402,7 +404,7 @@ static ssize_t store_temp(struct device *dev, struct device_attribute *attr, if (err < 0) return err; - val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), -128, 127); + val = DIV_ROUND_CLOSEST(clamp_val(val, -128000, 127000), 1000); err = regmap_write(data->regmap, nr, val & 0xff); return err ? : count; From 12fa55ccc49df52617d7454ba448c1876e189bac Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 4 Dec 2016 18:16:02 -0800 Subject: [PATCH 37/41] hwmon: (lm87) Fix overflow seen when writing voltage limit attributes Writes into voltage limit attributes can overflow due to an unbound multiplication. Reviewed-by: Jean Delvare Signed-off-by: Guenter Roeck --- drivers/hwmon/lm87.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/lm87.c b/drivers/hwmon/lm87.c index 81cb898245a1..13cca3606e06 100644 --- a/drivers/hwmon/lm87.c +++ b/drivers/hwmon/lm87.c @@ -121,7 +121,7 @@ static u8 LM87_REG_TEMP_LOW[3] = { 0x3A, 0x38, 0x2C }; #define IN_FROM_REG(reg, scale) (((reg) * (scale) + 96) / 192) #define IN_TO_REG(val, scale) ((val) <= 0 ? 0 : \ - (val) * 192 >= (scale) * 255 ? 255 : \ + (val) >= (scale) * 255 / 192 ? 255 : \ ((val) * 192 + (scale) / 2) / (scale)) #define TEMP_FROM_REG(reg) ((reg) * 1000) From 67b2003485ee48dcfcb5338171defa4093bba02e Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 4 Dec 2016 18:16:48 -0800 Subject: [PATCH 38/41] hwmon: (lm85) Fix overflows seen when writing voltage limit attributes Writes into voltage limit attributes can overflow due to an unbound multiplication. Reviewed-by: Jean Delvare Signed-off-by: Guenter Roeck --- drivers/hwmon/lm85.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/lm85.c b/drivers/hwmon/lm85.c index 6ff773fcaefb..29c8136ce9c5 100644 --- a/drivers/hwmon/lm85.c +++ b/drivers/hwmon/lm85.c @@ -136,7 +136,8 @@ static const int lm85_scaling[] = { /* .001 Volts */ #define SCALE(val, from, to) (((val) * (to) + ((from) / 2)) / (from)) #define INS_TO_REG(n, val) \ - clamp_val(SCALE(val, lm85_scaling[n], 192), 0, 255) + SCALE(clamp_val(val, 0, 255 * lm85_scaling[n] / 192), \ + lm85_scaling[n], 192) #define INSEXT_FROM_REG(n, val, ext) \ SCALE(((val) << 4) + (ext), 192 << 4, lm85_scaling[n]) From ca1b10b8250247fb5f241d1cb894c102203378bb Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 4 Dec 2016 18:19:51 -0800 Subject: [PATCH 39/41] hwmon: (emc2103) Fix overflows seen when temperature limit attributes Writes into temperature limit attributes can overflow due to unbound values passed to DIV_ROUND_CLOSEST(). Cc: Steve Glendinning Reviewed-by: Jean Delvare Signed-off-by: Guenter Roeck --- drivers/hwmon/emc2103.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/emc2103.c b/drivers/hwmon/emc2103.c index 24e395c5907d..4b870ee9b0d3 100644 --- a/drivers/hwmon/emc2103.c +++ b/drivers/hwmon/emc2103.c @@ -251,7 +251,7 @@ static ssize_t set_temp_min(struct device *dev, struct device_attribute *da, if (result < 0) return result; - val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), -63, 127); + val = DIV_ROUND_CLOSEST(clamp_val(val, -63000, 127000), 1000); mutex_lock(&data->update_lock); data->temp_min[nr] = val; @@ -273,7 +273,7 @@ static ssize_t set_temp_max(struct device *dev, struct device_attribute *da, if (result < 0) return result; - val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), -63, 127); + val = DIV_ROUND_CLOSEST(clamp_val(val, -63000, 127000), 1000); mutex_lock(&data->update_lock); data->temp_max[nr] = val; From 59715f4d16c27985b6866ebdced76e4e9cd63c33 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 4 Dec 2016 18:20:52 -0800 Subject: [PATCH 40/41] hwmon: (emcw201) Fix overflows seen when writing into limit attributes Writes into temperature and voltage limit attributes can overflow due to multiplications with unchecked parameters. Also, the input parameter to DIV_ROUND_CLOSEST() needis to be range checked. Reviewed-by: Jean Delvare Signed-off-by: Guenter Roeck --- drivers/hwmon/emc6w201.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/emc6w201.c b/drivers/hwmon/emc6w201.c index f37fe2011640..4aee5adf9ef2 100644 --- a/drivers/hwmon/emc6w201.c +++ b/drivers/hwmon/emc6w201.c @@ -215,12 +215,13 @@ static ssize_t set_in(struct device *dev, struct device_attribute *devattr, if (err < 0) return err; - val = DIV_ROUND_CLOSEST(val * 0xC0, nominal_mv[nr]); + val = clamp_val(val, 0, 255 * nominal_mv[nr] / 192); + val = DIV_ROUND_CLOSEST(val * 192, nominal_mv[nr]); reg = (sf == min) ? EMC6W201_REG_IN_LOW(nr) : EMC6W201_REG_IN_HIGH(nr); mutex_lock(&data->update_lock); - data->in[sf][nr] = clamp_val(val, 0, 255); + data->in[sf][nr] = val; err = emc6w201_write8(client, reg, data->in[sf][nr]); mutex_unlock(&data->update_lock); @@ -252,12 +253,13 @@ static ssize_t set_temp(struct device *dev, struct device_attribute *devattr, if (err < 0) return err; + val = clamp_val(val, -127000, 127000); val = DIV_ROUND_CLOSEST(val, 1000); reg = (sf == min) ? EMC6W201_REG_TEMP_LOW(nr) : EMC6W201_REG_TEMP_HIGH(nr); mutex_lock(&data->update_lock); - data->temp[sf][nr] = clamp_val(val, -127, 127); + data->temp[sf][nr] = val; err = emc6w201_write8(client, reg, data->temp[sf][nr]); mutex_unlock(&data->update_lock); From 4fccd4a1e8944033bcd7693ea4e8fb478cd2059a Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 11 Dec 2016 13:27:42 -0800 Subject: [PATCH 41/41] hwmon: (g762) Fix overflows and crash seen when writing limit attributes Fix overflows seen when writing into fan speed limit attributes. Also fix crash due to division by zero, seen when certain very large values (such as 2147483648, or 0x80000000) are written into fan speed limit attributes. Fixes: 594fbe713bf60 ("Add support for GMT G762/G763 PWM fan controllers") Cc: Arnaud Ebalard Reviewed-by: Jean Delvare Signed-off-by: Guenter Roeck --- drivers/hwmon/g762.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/g762.c b/drivers/hwmon/g762.c index b96a2a9e4df7..628be9c95ff9 100644 --- a/drivers/hwmon/g762.c +++ b/drivers/hwmon/g762.c @@ -193,14 +193,17 @@ static inline unsigned int rpm_from_cnt(u8 cnt, u32 clk_freq, u16 p, * Convert fan RPM value from sysfs into count value for fan controller * register (FAN_SET_CNT). */ -static inline unsigned char cnt_from_rpm(u32 rpm, u32 clk_freq, u16 p, +static inline unsigned char cnt_from_rpm(unsigned long rpm, u32 clk_freq, u16 p, u8 clk_div, u8 gear_mult) { - if (!rpm) /* to stop the fan, set cnt to 255 */ + unsigned long f1 = clk_freq * 30 * gear_mult; + unsigned long f2 = p * clk_div; + + if (!rpm) /* to stop the fan, set cnt to 255 */ return 0xff; - return clamp_val(((clk_freq * 30 * gear_mult) / (rpm * p * clk_div)), - 0, 255); + rpm = clamp_val(rpm, f1 / (255 * f2), ULONG_MAX / f2); + return DIV_ROUND_CLOSEST(f1, rpm * f2); } /* helper to grab and cache data, at most one time per second */