From ee249f271524d111aed8d6e7c61e220aa6b4d714 Mon Sep 17 00:00:00 2001 From: Lei YU Date: Mon, 13 Nov 2017 11:27:33 +0800 Subject: [PATCH 01/32] hwmon: Add W83773G driver Nuvoton W83773G is a hardware monitor IC providing one local temperature and two remote temperature sensors. Signed-off-by: Lei YU Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 10 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/w83773g.c | 329 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 340 insertions(+) create mode 100644 drivers/hwmon/w83773g.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 7ad017690e3a..530ff7c9234c 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1725,6 +1725,16 @@ config SENSORS_VT8231 This driver can also be built as a module. If so, the module will be called vt8231. +config SENSORS_W83773G + tristate "Nuvoton W83773G" + depends on I2C + help + If you say yes here you get support for the Nuvoton W83773G hardware + monitoring chip. + + This driver can also be built as a module. If so, the module + will be called w83773g. + config SENSORS_W83781D tristate "Winbond W83781D, W83782D, W83783S, Asus AS99127F" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 0fe489fab663..f814b4ace138 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_SENSORS_ATK0110) += asus_atk0110.o # asb100, then w83781d go first, as they can override other drivers' addresses. obj-$(CONFIG_SENSORS_ASB100) += asb100.o obj-$(CONFIG_SENSORS_W83627HF) += w83627hf.o +obj-$(CONFIG_SENSORS_W83773G) += w83773g.o obj-$(CONFIG_SENSORS_W83792D) += w83792d.o obj-$(CONFIG_SENSORS_W83793) += w83793.o obj-$(CONFIG_SENSORS_W83795) += w83795.o diff --git a/drivers/hwmon/w83773g.c b/drivers/hwmon/w83773g.c new file mode 100644 index 000000000000..0b97c285b049 --- /dev/null +++ b/drivers/hwmon/w83773g.c @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2017 IBM Corp. + * + * 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. + * + * Driver for the Nuvoton W83773G SMBus temperature sensor IC. + * Supported models: W83773G + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* W83773 has 3 channels */ +#define W83773_CHANNELS 3 + +/* The W83773 registers */ +#define W83773_CONVERSION_RATE_REG_READ 0x04 +#define W83773_CONVERSION_RATE_REG_WRITE 0x0A +#define W83773_MANUFACTURER_ID_REG 0xFE +#define W83773_LOCAL_TEMP 0x00 + +static const u8 W83773_STATUS[2] = { 0x02, 0x17 }; + +static const u8 W83773_TEMP_LSB[2] = { 0x10, 0x25 }; +static const u8 W83773_TEMP_MSB[2] = { 0x01, 0x24 }; + +static const u8 W83773_OFFSET_LSB[2] = { 0x12, 0x16 }; +static const u8 W83773_OFFSET_MSB[2] = { 0x11, 0x15 }; + +/* this is the number of sensors in the device */ +static const struct i2c_device_id w83773_id[] = { + { "w83773g" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, w83773_id); + +static const struct of_device_id w83773_of_match[] = { + { + .compatible = "nuvoton,w83773g" + }, + { }, +}; +MODULE_DEVICE_TABLE(of, w83773_of_match); + +static inline long temp_of_local(s8 reg) +{ + return reg * 1000; +} + +static inline long temp_of_remote(s8 hb, u8 lb) +{ + return (hb << 3 | lb >> 5) * 125; +} + +static int get_local_temp(struct regmap *regmap, long *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(regmap, W83773_LOCAL_TEMP, ®val); + if (ret < 0) + return ret; + + *val = temp_of_local(regval); + return 0; +} + +static int get_remote_temp(struct regmap *regmap, int index, long *val) +{ + unsigned int regval_high; + unsigned int regval_low; + int ret; + + ret = regmap_read(regmap, W83773_TEMP_MSB[index], ®val_high); + if (ret < 0) + return ret; + + ret = regmap_read(regmap, W83773_TEMP_LSB[index], ®val_low); + if (ret < 0) + return ret; + + *val = temp_of_remote(regval_high, regval_low); + return 0; +} + +static int get_fault(struct regmap *regmap, int index, long *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(regmap, W83773_STATUS[index], ®val); + if (ret < 0) + return ret; + + *val = (u8)regval & 0x04 >> 2; + return 0; +} + +static int get_offset(struct regmap *regmap, int index, long *val) +{ + unsigned int regval_high; + unsigned int regval_low; + int ret; + + ret = regmap_read(regmap, W83773_OFFSET_MSB[index], ®val_high); + if (ret < 0) + return ret; + + ret = regmap_read(regmap, W83773_OFFSET_LSB[index], ®val_low); + if (ret < 0) + return ret; + + *val = temp_of_remote(regval_high, regval_low); + return 0; +} + +static int set_offset(struct regmap *regmap, int index, long val) +{ + int ret; + u8 high_byte; + u8 low_byte; + + val = clamp_val(val, -127825, 127825); + /* offset value equals to (high_byte << 3 | low_byte >> 5) * 125 */ + val /= 125; + high_byte = val >> 3; + low_byte = (val & 0x07) << 5; + + ret = regmap_write(regmap, W83773_OFFSET_MSB[index], high_byte); + if (ret < 0) + return ret; + + return regmap_write(regmap, W83773_OFFSET_LSB[index], low_byte); +} + +static int get_update_interval(struct regmap *regmap, long *val) +{ + unsigned int regval; + int ret; + + ret = regmap_read(regmap, W83773_CONVERSION_RATE_REG_READ, ®val); + if (ret < 0) + return ret; + + *val = 16000 >> regval; + return 0; +} + +static int set_update_interval(struct regmap *regmap, long val) +{ + int rate; + + /* + * For valid rates, interval can be calculated as + * interval = (1 << (8 - rate)) * 62.5; + * Rounded rate is therefore + * rate = 8 - __fls(interval * 8 / (62.5 * 7)); + * Use clamp_val() to avoid overflows, and to ensure valid input + * for __fls. + */ + val = clamp_val(val, 62, 16000) * 10; + rate = 8 - __fls((val * 8 / (625 * 7))); + return regmap_write(regmap, W83773_CONVERSION_RATE_REG_WRITE, rate); +} + +static int w83773_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct regmap *regmap = dev_get_drvdata(dev); + + if (type == hwmon_chip) { + if (attr == hwmon_chip_update_interval) + return get_update_interval(regmap, val); + return -EOPNOTSUPP; + } + + switch (attr) { + case hwmon_temp_input: + if (channel == 0) + return get_local_temp(regmap, val); + return get_remote_temp(regmap, channel - 1, val); + case hwmon_temp_fault: + return get_fault(regmap, channel - 1, val); + case hwmon_temp_offset: + return get_offset(regmap, channel - 1, val); + default: + return -EOPNOTSUPP; + } +} + +static int w83773_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct regmap *regmap = dev_get_drvdata(dev); + + if (type == hwmon_chip && attr == hwmon_chip_update_interval) + return set_update_interval(regmap, val); + + if (type == hwmon_temp && attr == hwmon_temp_offset) + return set_offset(regmap, channel - 1, val); + + return -EOPNOTSUPP; +} + +static umode_t w83773_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + return 0644; + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_fault: + return 0444; + case hwmon_temp_offset: + return 0644; + } + break; + default: + break; + } + return 0; +} + +static const u32 w83773_chip_config[] = { + HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL, + 0 +}; + +static const struct hwmon_channel_info w83773_chip = { + .type = hwmon_chip, + .config = w83773_chip_config, +}; + +static const u32 w83773_temp_config[] = { + HWMON_T_INPUT, + HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_OFFSET, + HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_OFFSET, + 0 +}; + +static const struct hwmon_channel_info w83773_temp = { + .type = hwmon_temp, + .config = w83773_temp_config, +}; + +static const struct hwmon_channel_info *w83773_info[] = { + &w83773_chip, + &w83773_temp, + NULL +}; + +static const struct hwmon_ops w83773_ops = { + .is_visible = w83773_is_visible, + .read = w83773_read, + .write = w83773_write, +}; + +static const struct hwmon_chip_info w83773_chip_info = { + .ops = &w83773_ops, + .info = w83773_info, +}; + +static const struct regmap_config w83773_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int w83773_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct regmap *regmap; + int ret; + + regmap = devm_regmap_init_i2c(client, &w83773_regmap_config); + if (IS_ERR(regmap)) { + dev_err(dev, "failed to allocate register map\n"); + return PTR_ERR(regmap); + } + + /* Set the conversion rate to 2 Hz */ + ret = regmap_write(regmap, W83773_CONVERSION_RATE_REG_WRITE, 0x05); + if (ret < 0) { + dev_err(&client->dev, "error writing config rate register\n"); + return ret; + } + + i2c_set_clientdata(client, regmap); + + hwmon_dev = devm_hwmon_device_register_with_info(dev, + client->name, + regmap, + &w83773_chip_info, + NULL); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static struct i2c_driver w83773_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "w83773g", + .of_match_table = of_match_ptr(w83773_of_match), + }, + .probe = w83773_probe, + .id_table = w83773_id, +}; + +module_i2c_driver(w83773_driver); + +MODULE_AUTHOR("Lei YU "); +MODULE_DESCRIPTION("W83773G temperature sensor driver"); +MODULE_LICENSE("GPL"); From ba3d8588442dbf954ff4aa77fbb16bfecc7e5b98 Mon Sep 17 00:00:00 2001 From: Lei YU Date: Mon, 13 Nov 2017 11:27:34 +0800 Subject: [PATCH 02/32] hwmon: (w83773g) Add documentation Add documentation for the w83773g driver. Signed-off-by: Lei YU Signed-off-by: Guenter Roeck --- Documentation/hwmon/w83773g | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 Documentation/hwmon/w83773g diff --git a/Documentation/hwmon/w83773g b/Documentation/hwmon/w83773g new file mode 100644 index 000000000000..4cc6c0b8257f --- /dev/null +++ b/Documentation/hwmon/w83773g @@ -0,0 +1,33 @@ +Kernel driver w83773g +==================== + +Supported chips: + * Nuvoton W83773G + Prefix: 'w83773g' + Addresses scanned: I2C 0x4c and 0x4d + Datasheet: https://www.nuvoton.com/resource-files/W83773G_SG_DatasheetV1_2.pdf + +Authors: + Lei YU + +Description +----------- + +This driver implements support for Nuvoton W83773G temperature sensor +chip. This chip implements one local and two remote sensors. +The chip also features offsets for the two remote sensors which get added to +the input readings. The chip does all the scaling by itself and the driver +therefore reports true temperatures that don't need any user-space adjustments. +Temperature is measured in degrees Celsius. +The chip is wired over I2C/SMBus and specified over a temperature +range of -40 to +125 degrees Celsius (for local sensor) and -40 to +127 +degrees Celsius (for remote sensors). +Resolution for both the local and remote channels is 0.125 degree C. + +The chip supports only temperature measurement. The driver exports +the temperature values via the following sysfs files: + +temp[1-3]_input +temp[2-3]_fault +temp[2-3]_offset +update_interval From 6335d98abd942bfb28d908f712b7c7d73aaea871 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 19 Nov 2017 09:14:50 -0800 Subject: [PATCH 03/32] hwmon: Drop unnecessary 'default n' from Kconfig 'default n' is default, so there is no need to specify it explicitly. Reviewed-by: Jean Delvare Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 530ff7c9234c..ef23553ff5cb 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -26,11 +26,9 @@ if HWMON config HWMON_VID tristate - default n config HWMON_DEBUG_CHIP bool "Hardware Monitoring Chip debugging messages" - default n help Say Y here if you want the I2C chip drivers to produce a bunch of debug messages to the system log. Select this if you are having @@ -42,7 +40,6 @@ comment "Native drivers" config SENSORS_AB8500 tristate "AB8500 thermal monitoring" depends on AB8500_GPADC && AB8500_BM - default n help If you say yes here you get support for the thermal sensor part of the AB8500 chip. The driver includes thermal management for @@ -302,7 +299,6 @@ config SENSORS_APPLESMC select NEW_LEDS select LEDS_CLASS select INPUT_POLLDEV - default n help This driver provides support for the Apple System Management Controller, which provides an accelerometer (Apple Sudden Motion @@ -678,7 +674,6 @@ config SENSORS_JC42 config SENSORS_POWR1220 tristate "Lattice POWR1220 Power Monitoring" depends on I2C - default n help If you say yes here you get access to the hardware monitoring functions of the Lattice POWR1220 isp Power Supply Monitoring, @@ -702,7 +697,6 @@ config SENSORS_LTC2945 tristate "Linear Technology LTC2945" depends on I2C select REGMAP_I2C - default n help If you say yes here you get support for Linear Technology LTC2945 I2C System Monitor. @@ -727,7 +721,6 @@ config SENSORS_LTC2990 config SENSORS_LTC4151 tristate "Linear Technology LTC4151" depends on I2C - default n help If you say yes here you get support for Linear Technology LTC4151 High Voltage I2C Current and Voltage Monitor interface. @@ -738,7 +731,6 @@ config SENSORS_LTC4151 config SENSORS_LTC4215 tristate "Linear Technology LTC4215" depends on I2C - default n help If you say yes here you get support for Linear Technology LTC4215 Hot Swap Controller I2C interface. @@ -750,7 +742,6 @@ config SENSORS_LTC4222 tristate "Linear Technology LTC4222" depends on I2C select REGMAP_I2C - default n help If you say yes here you get support for Linear Technology LTC4222 Dual Hot Swap Controller I2C interface. @@ -761,7 +752,6 @@ config SENSORS_LTC4222 config SENSORS_LTC4245 tristate "Linear Technology LTC4245" depends on I2C - default n help If you say yes here you get support for Linear Technology LTC4245 Multiple Supply Hot Swap Controller I2C interface. @@ -773,7 +763,6 @@ config SENSORS_LTC4260 tristate "Linear Technology LTC4260" depends on I2C select REGMAP_I2C - default n help If you say yes here you get support for Linear Technology LTC4260 Positive Voltage Hot Swap Controller I2C interface. @@ -784,7 +773,6 @@ config SENSORS_LTC4260 config SENSORS_LTC4261 tristate "Linear Technology LTC4261" depends on I2C - default n help If you say yes here you get support for Linear Technology LTC4261 Negative Voltage Hot Swap Controller I2C interface. @@ -1276,7 +1264,6 @@ config SENSORS_NSA320 config SENSORS_PCF8591 tristate "Philips PCF8591 ADC/DAC" depends on I2C - default n help If you say yes here you get support for Philips PCF8591 4-channel ADC, 1-channel DAC chips. @@ -1459,7 +1446,6 @@ config SENSORS_SMSC47B397 config SENSORS_SCH56XX_COMMON tristate - default n config SENSORS_SCH5627 tristate "SMSC SCH5627" @@ -1505,7 +1491,6 @@ config SENSORS_STTS751 config SENSORS_SMM665 tristate "Summit Microelectronics SMM665" depends on I2C - default n help If you say yes here you get support for the hardware monitoring features of the Summit Microelectronics SMM665/SMM665B Six-Channel @@ -1792,7 +1777,6 @@ config SENSORS_W83795 config SENSORS_W83795_FANCTRL bool "Include automatic fan control support (DANGEROUS)" depends on SENSORS_W83795 - default n help If you say yes here, support for automatic fan speed control will be included in the driver. From b9ccff233e5eb1ae6adc831c0aa1f456d0cbc5cb Mon Sep 17 00:00:00 2001 From: Sinan Kaya Date: Wed, 22 Nov 2017 00:30:56 -0500 Subject: [PATCH 04/32] hwmon: (coretemp) deprecate pci_get_bus_and_slot() pci_get_bus_and_slot() is restrictive such that it assumes domain=0 as where a PCI device is present. This restricts the device drivers to be reused for other domain numbers. Use pci_get_domain_bus_and_slot() with a domain number of 0 where we can't extract the domain number. Other places, use the actual domain number from the device. Signed-off-by: Sinan Kaya Signed-off-by: Guenter Roeck --- drivers/hwmon/coretemp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index c13a4fd86b3c..4bdbf77f7197 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -246,7 +246,8 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev) int err; u32 eax, edx; int i; - struct pci_dev *host_bridge = pci_get_bus_and_slot(0, PCI_DEVFN(0, 0)); + u16 devfn = PCI_DEVFN(0, 0); + struct pci_dev *host_bridge = pci_get_domain_bus_and_slot(0, 0, devfn); /* * Explicit tjmax table entries override heuristics. From 666c14906b496f148e437404283f6a6a84cee719 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 28 Nov 2017 18:33:06 -0800 Subject: [PATCH 05/32] hwmon: (pmbus/lm25066) Drop support for LM25063 LM25063 was never released. Drop support for it. Signed-off-by: Guenter Roeck --- Documentation/hwmon/lm25066 | 20 +---------- drivers/hwmon/pmbus/lm25066.c | 67 ++--------------------------------- 2 files changed, 3 insertions(+), 84 deletions(-) diff --git a/Documentation/hwmon/lm25066 b/Documentation/hwmon/lm25066 index 3fa6bf820c88..51b32aa203a8 100644 --- a/Documentation/hwmon/lm25066 +++ b/Documentation/hwmon/lm25066 @@ -8,11 +8,6 @@ Supported chips: Datasheets: http://www.ti.com/lit/gpn/lm25056 http://www.ti.com/lit/gpn/lm25056a - * TI LM25063 - Prefix: 'lm25063' - Addresses scanned: - - Datasheet: - To be announced * National Semiconductor LM25066 Prefix: 'lm25066' Addresses scanned: - @@ -42,7 +37,7 @@ Description ----------- This driver supports hardware monitoring for National Semiconductor / TI LM25056, -LM25063, LM25066, LM5064, and LM5066/LM5066I Power Management, Monitoring, +LM25066, LM5064, and LM5066/LM5066I Power Management, Monitoring, Control, and Protection ICs. The driver is a client driver to the core PMBus driver. Please see @@ -74,12 +69,8 @@ in1_input Measured input voltage. in1_average Average measured input voltage. in1_min Minimum input voltage. in1_max Maximum input voltage. -in1_crit Critical high input voltage (LM25063 only). -in1_lcrit Critical low input voltage (LM25063 only). in1_min_alarm Input voltage low alarm. in1_max_alarm Input voltage high alarm. -in1_lcrit_alarm Input voltage critical low alarm (LM25063 only). -in1_crit_alarm Input voltage critical high alarm. (LM25063 only). in2_label "vmon" in2_input Measured voltage on VAUX pin @@ -94,16 +85,12 @@ in3_input Measured output voltage. in3_average Average measured output voltage. in3_min Minimum output voltage. in3_min_alarm Output voltage low alarm. -in3_highest Historical minimum output voltage (LM25063 only). -in3_lowest Historical maximum output voltage (LM25063 only). curr1_label "iin" curr1_input Measured input current. curr1_average Average measured input current. curr1_max Maximum input current. -curr1_crit Critical input current (LM25063 only). curr1_max_alarm Input current high alarm. -curr1_crit_alarm Input current critical high alarm (LM25063 only). power1_label "pin" power1_input Measured input power. @@ -113,11 +100,6 @@ power1_alarm Input power alarm power1_input_highest Historical maximum power. power1_reset_history Write any value to reset maximum power history. -power2_label "pout". LM25063 only. -power2_input Measured output power. -power2_max Maximum output power limit. -power2_crit Critical output power limit. - temp1_input Measured temperature. temp1_max Maximum temperature. temp1_crit Critical high temperature. diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c index 10d17fb8f283..53db78753a0d 100644 --- a/drivers/hwmon/pmbus/lm25066.c +++ b/drivers/hwmon/pmbus/lm25066.c @@ -1,5 +1,5 @@ /* - * Hardware monitoring driver for LM25056 / LM25063 / LM25066 / LM5064 / LM5066 + * Hardware monitoring driver for LM25056 / LM25066 / LM5064 / LM5066 * * Copyright (c) 2011 Ericsson AB. * Copyright (c) 2013 Guenter Roeck @@ -28,7 +28,7 @@ #include #include "pmbus.h" -enum chips { lm25056, lm25063, lm25066, lm5064, lm5066, lm5066i }; +enum chips { lm25056, lm25066, lm5064, lm5066, lm5066i }; #define LM25066_READ_VAUX 0xd0 #define LM25066_MFR_READ_IIN 0xd1 @@ -53,11 +53,6 @@ enum chips { lm25056, lm25063, lm25066, lm5064, lm5066, lm5066i }; #define LM25056_MFR_STS_VAUX_OV_WARN BIT(1) #define LM25056_MFR_STS_VAUX_UV_WARN BIT(0) -/* LM25063 only */ - -#define LM25063_READ_VOUT_MAX 0xe5 -#define LM25063_READ_VOUT_MIN 0xe6 - struct __coeff { short m, b, R; }; @@ -122,36 +117,6 @@ static struct __coeff lm25066_coeff[6][PSC_NUM_CLASSES + 2] = { .m = 16, }, }, - [lm25063] = { - [PSC_VOLTAGE_IN] = { - .m = 16000, - .R = -2, - }, - [PSC_VOLTAGE_OUT] = { - .m = 16000, - .R = -2, - }, - [PSC_CURRENT_IN] = { - .m = 10000, - .R = -2, - }, - [PSC_CURRENT_IN_L] = { - .m = 10000, - .R = -2, - }, - [PSC_POWER] = { - .m = 5000, - .R = -3, - }, - [PSC_POWER_L] = { - .m = 5000, - .R = -3, - }, - [PSC_TEMPERATURE] = { - .m = 15596, - .R = -3, - }, - }, [lm5064] = { [PSC_VOLTAGE_IN] = { .m = 4611, @@ -272,10 +237,6 @@ static int lm25066_read_word_data(struct i2c_client *client, int page, int reg) /* VIN: 6.14 mV VAUX: 293 uV LSB */ ret = DIV_ROUND_CLOSEST(ret * 293, 6140); break; - case lm25063: - /* VIN: 6.25 mV VAUX: 200.0 uV LSB */ - ret = DIV_ROUND_CLOSEST(ret * 20, 625); - break; case lm25066: /* VIN: 4.54 mV VAUX: 283.2 uV LSB */ ret = DIV_ROUND_CLOSEST(ret * 2832, 45400); @@ -330,24 +291,6 @@ static int lm25066_read_word_data(struct i2c_client *client, int page, int reg) return ret; } -static int lm25063_read_word_data(struct i2c_client *client, int page, int reg) -{ - int ret; - - switch (reg) { - case PMBUS_VIRT_READ_VOUT_MAX: - ret = pmbus_read_word_data(client, 0, LM25063_READ_VOUT_MAX); - break; - case PMBUS_VIRT_READ_VOUT_MIN: - ret = pmbus_read_word_data(client, 0, LM25063_READ_VOUT_MIN); - break; - default: - ret = lm25066_read_word_data(client, page, reg); - break; - } - return ret; -} - static int lm25056_read_word_data(struct i2c_client *client, int page, int reg) { int ret; @@ -502,11 +445,6 @@ static int lm25066_probe(struct i2c_client *client, info->read_word_data = lm25056_read_word_data; info->read_byte_data = lm25056_read_byte_data; data->rlimit = 0x0fff; - } else if (data->id == lm25063) { - info->func[0] |= PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT - | PMBUS_HAVE_POUT; - info->read_word_data = lm25063_read_word_data; - data->rlimit = 0xffff; } else { info->func[0] |= PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; info->read_word_data = lm25066_read_word_data; @@ -543,7 +481,6 @@ static int lm25066_probe(struct i2c_client *client, static const struct i2c_device_id lm25066_id[] = { {"lm25056", lm25056}, - {"lm25063", lm25063}, {"lm25066", lm25066}, {"lm5064", lm5064}, {"lm5066", lm5066}, From d206636e7697f47332774f29b90b92f6047d265d Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Mon, 20 Nov 2017 15:12:03 +1030 Subject: [PATCH 06/32] hwmon: (pmbus) Add fan control support Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes. Fans in a PMBus device are driven by the configuration of two registers, FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan and the tacho operate (if installed), while FAN_COMMAND_x sets the desired fan rate. The unit of FAN_COMMAND_x is dependent on the operational fan mode, RPM or PWM percent duty, as determined by the corresponding configuration in FAN_CONFIG_x_y. The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and FAN_COMMAND_x is implemented with the addition of virtual registers to facilitate the necessary side-effects of each access: 1. PMBUS_VIRT_FAN_TARGET_x 2. PMBUS_VIRT_PWM_x 3. PMBUS_VIRT_PWM_ENABLE_x Some complexity arises with the fanX_target and pwmX attributes both mapping onto FAN_COMMAND_x: There is no general mapping between PWM percent duty and RPM, so we can't display values in either attribute in terms of the other (which in my mind is the intuitive, if impossible, behaviour). This problem also affects the pwmX_enable attribute which allows userspace to switch between full speed, manual PWM and a number of automatic control modes, possibly including a switch to RPM behaviour (e.g. automatically adjusting PWM duty to reach a RPM target, the behaviour of fanX_target). The next most intuitive behaviour is for fanX_target and pwmX to simply be independent, to retain their most recently set value even if that value is not active on the hardware (due to switching to the alternative control mode). This property of retaining the value independent of the hardware state has useful results for both userspace and the kernel: Userspace always sees a sensible value in the attribute (the last thing it was set to, as opposed to 0 or receiving an error on read), and the kernel can use the attributes as a value cache. This latter point eases the implementation of pwmX_enable, which can look up the associated pmbus_sensor object, take its cached value and apply it to hardware on changing control mode. This ensures we will not arbitrarily set a PWM value as an RPM value or vice versa, and we can assume that the RPM or PWM value set was sensible at least at some point in the past. Finally, the DIRECT mode coefficients of some controllers is different between RPM and PWM percent duty control modes, so PSC_PWM is introduced to capture the necessary coefficients. As pmbus core had no PWM support previously PSC_FAN continues to be used to capture the RPM DIRECT coefficients, but in order to avoid falsely applying RPM scaling to PWM values I have introduced the PMBUS_HAVE_PWM12 and PMB_BUS_HAVE_PWM34 feature bits. These feature bits allow drivers to explicitly declare PWM support in order to have the attributes exposed. Signed-off-by: Andrew Jeffery Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus.h | 39 ++++- drivers/hwmon/pmbus/pmbus_core.c | 238 ++++++++++++++++++++++++++++--- 2 files changed, 259 insertions(+), 18 deletions(-) diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index fa613bd209e3..b54d7604d3ef 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -190,6 +190,33 @@ enum pmbus_regs { PMBUS_VIRT_VMON_UV_FAULT_LIMIT, PMBUS_VIRT_VMON_OV_FAULT_LIMIT, PMBUS_VIRT_STATUS_VMON, + + /* + * RPM and PWM Fan control + * + * Drivers wanting to expose PWM control must define the behaviour of + * PMBUS_VIRT_PWM_[1-4] and PMBUS_VIRT_PWM_ENABLE_[1-4] in the + * {read,write}_word_data callback. + * + * pmbus core provides a default implementation for + * PMBUS_VIRT_FAN_TARGET_[1-4]. + * + * TARGET, PWM and PWM_ENABLE members must be defined sequentially; + * pmbus core uses the difference between the provided register and + * it's _1 counterpart to calculate the FAN/PWM ID. + */ + PMBUS_VIRT_FAN_TARGET_1, + PMBUS_VIRT_FAN_TARGET_2, + PMBUS_VIRT_FAN_TARGET_3, + PMBUS_VIRT_FAN_TARGET_4, + PMBUS_VIRT_PWM_1, + PMBUS_VIRT_PWM_2, + PMBUS_VIRT_PWM_3, + PMBUS_VIRT_PWM_4, + PMBUS_VIRT_PWM_ENABLE_1, + PMBUS_VIRT_PWM_ENABLE_2, + PMBUS_VIRT_PWM_ENABLE_3, + PMBUS_VIRT_PWM_ENABLE_4, }; /* @@ -223,6 +250,8 @@ enum pmbus_regs { #define PB_FAN_1_RPM BIT(6) #define PB_FAN_1_INSTALLED BIT(7) +enum pmbus_fan_mode { percent = 0, rpm }; + /* * STATUS_BYTE, STATUS_WORD (lower) */ @@ -313,6 +342,7 @@ enum pmbus_sensor_classes { PSC_POWER, PSC_TEMPERATURE, PSC_FAN, + PSC_PWM, PSC_NUM_CLASSES /* Number of power sensor classes */ }; @@ -339,6 +369,8 @@ enum pmbus_sensor_classes { #define PMBUS_HAVE_STATUS_FAN34 BIT(17) #define PMBUS_HAVE_VMON BIT(18) #define PMBUS_HAVE_STATUS_VMON BIT(19) +#define PMBUS_HAVE_PWM12 BIT(20) +#define PMBUS_HAVE_PWM34 BIT(21) enum pmbus_data_format { linear = 0, direct, vid }; enum vrm_version { vr11 = 0, vr12, vr13 }; @@ -421,5 +453,10 @@ int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id, int pmbus_do_remove(struct i2c_client *client); const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client *client); - +int pmbus_get_fan_rate_device(struct i2c_client *client, int page, int id, + enum pmbus_fan_mode mode); +int pmbus_get_fan_rate_cached(struct i2c_client *client, int page, int id, + enum pmbus_fan_mode mode); +int pmbus_update_fan(struct i2c_client *client, int page, int id, + u8 config, u8 mask, u16 command); #endif /* PMBUS_H */ diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index a139940cd991..fdd33857f117 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -65,6 +65,7 @@ struct pmbus_sensor { u16 reg; /* register */ enum pmbus_sensor_classes class; /* sensor class */ bool update; /* runtime sensor update needed */ + bool convert; /* Whether or not to apply linear/vid/direct */ int data; /* Sensor data. Negative if there was a read error */ }; @@ -129,6 +130,27 @@ struct pmbus_debugfs_entry { u8 reg; }; +static const int pmbus_fan_rpm_mask[] = { + PB_FAN_1_RPM, + PB_FAN_2_RPM, + PB_FAN_1_RPM, + PB_FAN_2_RPM, +}; + +static const int pmbus_fan_config_registers[] = { + PMBUS_FAN_CONFIG_12, + PMBUS_FAN_CONFIG_12, + PMBUS_FAN_CONFIG_34, + PMBUS_FAN_CONFIG_34 +}; + +static const int pmbus_fan_command_registers[] = { + PMBUS_FAN_COMMAND_1, + PMBUS_FAN_COMMAND_2, + PMBUS_FAN_COMMAND_3, + PMBUS_FAN_COMMAND_4, +}; + void pmbus_clear_cache(struct i2c_client *client) { struct pmbus_data *data = i2c_get_clientdata(client); @@ -198,6 +220,28 @@ int pmbus_write_word_data(struct i2c_client *client, int page, u8 reg, } EXPORT_SYMBOL_GPL(pmbus_write_word_data); + +static int pmbus_write_virt_reg(struct i2c_client *client, int page, int reg, + u16 word) +{ + int bit; + int id; + int rv; + + switch (reg) { + case PMBUS_VIRT_FAN_TARGET_1 ... PMBUS_VIRT_FAN_TARGET_4: + id = reg - PMBUS_VIRT_FAN_TARGET_1; + bit = pmbus_fan_rpm_mask[id]; + rv = pmbus_update_fan(client, page, id, bit, bit, word); + break; + default: + rv = -ENXIO; + break; + } + + return rv; +} + /* * _pmbus_write_word_data() is similar to pmbus_write_word_data(), but checks if * a device specific mapping function exists and calls it if necessary. @@ -214,11 +258,38 @@ static int _pmbus_write_word_data(struct i2c_client *client, int page, int reg, if (status != -ENODATA) return status; } + if (reg >= PMBUS_VIRT_BASE) - return -ENXIO; + return pmbus_write_virt_reg(client, page, reg, word); + return pmbus_write_word_data(client, page, reg, word); } +int pmbus_update_fan(struct i2c_client *client, int page, int id, + u8 config, u8 mask, u16 command) +{ + int from; + int rv; + u8 to; + + from = pmbus_read_byte_data(client, page, + pmbus_fan_config_registers[id]); + if (from < 0) + return from; + + to = (from & ~mask) | (config & mask); + if (to != from) { + rv = pmbus_write_byte_data(client, page, + pmbus_fan_config_registers[id], to); + if (rv < 0) + return rv; + } + + return _pmbus_write_word_data(client, page, + pmbus_fan_command_registers[id], command); +} +EXPORT_SYMBOL_GPL(pmbus_update_fan); + int pmbus_read_word_data(struct i2c_client *client, int page, u8 reg) { int rv; @@ -231,6 +302,24 @@ int pmbus_read_word_data(struct i2c_client *client, int page, u8 reg) } EXPORT_SYMBOL_GPL(pmbus_read_word_data); +static int pmbus_read_virt_reg(struct i2c_client *client, int page, int reg) +{ + int rv; + int id; + + switch (reg) { + case PMBUS_VIRT_FAN_TARGET_1 ... PMBUS_VIRT_FAN_TARGET_4: + id = reg - PMBUS_VIRT_FAN_TARGET_1; + rv = pmbus_get_fan_rate_device(client, page, id, rpm); + break; + default: + rv = -ENXIO; + break; + } + + return rv; +} + /* * _pmbus_read_word_data() is similar to pmbus_read_word_data(), but checks if * a device specific mapping function exists and calls it if necessary. @@ -246,8 +335,10 @@ static int _pmbus_read_word_data(struct i2c_client *client, int page, int reg) if (status != -ENODATA) return status; } + if (reg >= PMBUS_VIRT_BASE) - return -ENXIO; + return pmbus_read_virt_reg(client, page, reg); + return pmbus_read_word_data(client, page, reg); } @@ -312,6 +403,68 @@ static int _pmbus_read_byte_data(struct i2c_client *client, int page, int reg) return pmbus_read_byte_data(client, page, reg); } +static struct pmbus_sensor *pmbus_find_sensor(struct pmbus_data *data, int page, + int reg) +{ + struct pmbus_sensor *sensor; + + for (sensor = data->sensors; sensor; sensor = sensor->next) { + if (sensor->page == page && sensor->reg == reg) + return sensor; + } + + return ERR_PTR(-EINVAL); +} + +static int pmbus_get_fan_rate(struct i2c_client *client, int page, int id, + enum pmbus_fan_mode mode, + bool from_cache) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + bool want_rpm, have_rpm; + struct pmbus_sensor *s; + int config; + int reg; + + want_rpm = (mode == rpm); + + if (from_cache) { + reg = want_rpm ? PMBUS_VIRT_FAN_TARGET_1 : PMBUS_VIRT_PWM_1; + s = pmbus_find_sensor(data, page, reg + id); + if (IS_ERR(s)) + return PTR_ERR(s); + + return s->data; + } + + config = pmbus_read_byte_data(client, page, + pmbus_fan_config_registers[id]); + if (config < 0) + return config; + + have_rpm = !!(config & pmbus_fan_rpm_mask[id]); + if (want_rpm == have_rpm) + return pmbus_read_word_data(client, page, + pmbus_fan_command_registers[id]); + + /* Can't sensibly map between RPM and PWM, just return zero */ + return 0; +} + +int pmbus_get_fan_rate_device(struct i2c_client *client, int page, int id, + enum pmbus_fan_mode mode) +{ + return pmbus_get_fan_rate(client, page, id, mode, false); +} +EXPORT_SYMBOL_GPL(pmbus_get_fan_rate_device); + +int pmbus_get_fan_rate_cached(struct i2c_client *client, int page, int id, + enum pmbus_fan_mode mode) +{ + return pmbus_get_fan_rate(client, page, id, mode, true); +} +EXPORT_SYMBOL_GPL(pmbus_get_fan_rate_cached); + static void pmbus_clear_fault_page(struct i2c_client *client, int page) { _pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS); @@ -513,7 +666,7 @@ static long pmbus_reg2data_direct(struct pmbus_data *data, /* X = 1/m * (Y * 10^-R - b) */ R = -R; /* scale result to milli-units for everything but fans */ - if (sensor->class != PSC_FAN) { + if (!(sensor->class == PSC_FAN || sensor->class == PSC_PWM)) { R += 3; b *= 1000; } @@ -568,6 +721,9 @@ static long pmbus_reg2data(struct pmbus_data *data, struct pmbus_sensor *sensor) { long val; + if (!sensor->convert) + return sensor->data; + switch (data->info->format[sensor->class]) { case direct: val = pmbus_reg2data_direct(data, sensor); @@ -672,7 +828,7 @@ static u16 pmbus_data2reg_direct(struct pmbus_data *data, } /* Calculate Y = (m * X + b) * 10^R */ - if (sensor->class != PSC_FAN) { + if (!(sensor->class == PSC_FAN || sensor->class == PSC_PWM)) { R -= 3; /* Adjust R and b for data in milli-units */ b *= 1000; } @@ -703,6 +859,9 @@ static u16 pmbus_data2reg(struct pmbus_data *data, { u16 regval; + if (!sensor->convert) + return val; + switch (data->info->format[sensor->class]) { case direct: regval = pmbus_data2reg_direct(data, sensor, val); @@ -915,7 +1074,8 @@ static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data, const char *name, const char *type, int seq, int page, int reg, enum pmbus_sensor_classes class, - bool update, bool readonly) + bool update, bool readonly, + bool convert) { struct pmbus_sensor *sensor; struct device_attribute *a; @@ -925,12 +1085,18 @@ static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data, return NULL; a = &sensor->attribute; - snprintf(sensor->name, sizeof(sensor->name), "%s%d_%s", - name, seq, type); + if (type) + snprintf(sensor->name, sizeof(sensor->name), "%s%d_%s", + name, seq, type); + else + snprintf(sensor->name, sizeof(sensor->name), "%s%d", + name, seq); + sensor->page = page; sensor->reg = reg; sensor->class = class; sensor->update = update; + sensor->convert = convert; pmbus_dev_attr_init(a, sensor->name, readonly ? S_IRUGO : S_IRUGO | S_IWUSR, pmbus_show_sensor, pmbus_set_sensor); @@ -1029,7 +1195,7 @@ static int pmbus_add_limit_attrs(struct i2c_client *client, curr = pmbus_add_sensor(data, name, l->attr, index, page, l->reg, attr->class, attr->update || l->update, - false); + false, true); if (!curr) return -ENOMEM; if (l->sbit && (info->func[page] & attr->sfunc)) { @@ -1068,7 +1234,7 @@ static int pmbus_add_sensor_attrs_one(struct i2c_client *client, return ret; } base = pmbus_add_sensor(data, name, "input", index, page, attr->reg, - attr->class, true, true); + attr->class, true, true, true); if (!base) return -ENOMEM; if (attr->sfunc) { @@ -1592,13 +1758,6 @@ static const int pmbus_fan_registers[] = { PMBUS_READ_FAN_SPEED_4 }; -static const int pmbus_fan_config_registers[] = { - PMBUS_FAN_CONFIG_12, - PMBUS_FAN_CONFIG_12, - PMBUS_FAN_CONFIG_34, - PMBUS_FAN_CONFIG_34 -}; - static const int pmbus_fan_status_registers[] = { PMBUS_STATUS_FAN_12, PMBUS_STATUS_FAN_12, @@ -1621,6 +1780,42 @@ static const u32 pmbus_fan_status_flags[] = { }; /* Fans */ + +/* Precondition: FAN_CONFIG_x_y and FAN_COMMAND_x must exist for the fan ID */ +static int pmbus_add_fan_ctrl(struct i2c_client *client, + struct pmbus_data *data, int index, int page, int id, + u8 config) +{ + struct pmbus_sensor *sensor; + + sensor = pmbus_add_sensor(data, "fan", "target", index, page, + PMBUS_VIRT_FAN_TARGET_1 + id, PSC_FAN, + false, false, true); + + if (!sensor) + return -ENOMEM; + + if (!((data->info->func[page] & PMBUS_HAVE_PWM12) || + (data->info->func[page] & PMBUS_HAVE_PWM34))) + return 0; + + sensor = pmbus_add_sensor(data, "pwm", NULL, index, page, + PMBUS_VIRT_PWM_1 + id, PSC_PWM, + false, false, true); + + if (!sensor) + return -ENOMEM; + + sensor = pmbus_add_sensor(data, "pwm", "enable", index, page, + PMBUS_VIRT_PWM_ENABLE_1 + id, PSC_PWM, + true, false, false); + + if (!sensor) + return -ENOMEM; + + return 0; +} + static int pmbus_add_fan_attributes(struct i2c_client *client, struct pmbus_data *data) { @@ -1655,9 +1850,18 @@ static int pmbus_add_fan_attributes(struct i2c_client *client, if (pmbus_add_sensor(data, "fan", "input", index, page, pmbus_fan_registers[f], - PSC_FAN, true, true) == NULL) + PSC_FAN, true, true, true) == NULL) return -ENOMEM; + /* Fan control */ + if (pmbus_check_word_register(client, page, + pmbus_fan_command_registers[f])) { + ret = pmbus_add_fan_ctrl(client, data, index, + page, f, regval); + if (ret < 0) + return ret; + } + /* * Each fan status register covers multiple fans, * so we have to do some magic. From 56ad86b4b16e4b7154300d71f8e93cca64b98e92 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Mon, 20 Nov 2017 15:12:04 +1030 Subject: [PATCH 07/32] hwmon: (pmbus/max31785) Add fan control The implementation makes use of the new fan control virtual registers exposed by the pmbus core. It mixes use of the default implementations with some overrides via the read/write handlers to handle FAN_COMMAND_1 on the MAX31785, whose definition breaks the value range into various control bands dependent on RPM or PWM mode. Signed-off-by: Andrew Jeffery Signed-off-by: Guenter Roeck --- Documentation/hwmon/max31785 | 7 ++ drivers/hwmon/pmbus/max31785.c | 138 ++++++++++++++++++++++++++++++++- 2 files changed, 144 insertions(+), 1 deletion(-) diff --git a/Documentation/hwmon/max31785 b/Documentation/hwmon/max31785 index 45fb6093dec2..7b0a0a8cdb6b 100644 --- a/Documentation/hwmon/max31785 +++ b/Documentation/hwmon/max31785 @@ -32,6 +32,7 @@ Sysfs attributes fan[1-4]_alarm Fan alarm. fan[1-4]_fault Fan fault. fan[1-4]_input Fan RPM. +fan[1-4]_target Fan input target in[1-6]_crit Critical maximum output voltage in[1-6]_crit_alarm Output voltage critical high alarm @@ -44,6 +45,12 @@ in[1-6]_max_alarm Output voltage high alarm in[1-6]_min Minimum output voltage in[1-6]_min_alarm Output voltage low alarm +pwm[1-4] Fan target duty cycle (0..255) +pwm[1-4]_enable 0: Full-speed + 1: Manual PWM control + 2: Automatic PWM (tach-feedback RPM fan-control) + 3: Automatic closed-loop (temp-feedback fan-control) + temp[1-11]_crit Critical high temperature temp[1-11]_crit_alarm Chip temperature critical high alarm temp[1-11]_input Measured temperature diff --git a/drivers/hwmon/pmbus/max31785.c b/drivers/hwmon/pmbus/max31785.c index 9313849d5160..8706a696c89a 100644 --- a/drivers/hwmon/pmbus/max31785.c +++ b/drivers/hwmon/pmbus/max31785.c @@ -20,8 +20,136 @@ enum max31785_regs { #define MAX31785_NR_PAGES 23 +static int max31785_get_pwm(struct i2c_client *client, int page) +{ + int rv; + + rv = pmbus_get_fan_rate_device(client, page, 0, percent); + if (rv < 0) + return rv; + else if (rv >= 0x8000) + return 0; + else if (rv >= 0x2711) + return 0x2710; + + return rv; +} + +static int max31785_get_pwm_mode(struct i2c_client *client, int page) +{ + int config; + int command; + + config = pmbus_read_byte_data(client, page, PMBUS_FAN_CONFIG_12); + if (config < 0) + return config; + + command = pmbus_read_word_data(client, page, PMBUS_FAN_COMMAND_1); + if (command < 0) + return command; + + if (config & PB_FAN_1_RPM) + return (command >= 0x8000) ? 3 : 2; + + if (command >= 0x8000) + return 3; + else if (command >= 0x2711) + return 0; + + return 1; +} + +static int max31785_read_word_data(struct i2c_client *client, int page, + int reg) +{ + int rv; + + switch (reg) { + case PMBUS_VIRT_PWM_1: + rv = max31785_get_pwm(client, page); + break; + case PMBUS_VIRT_PWM_ENABLE_1: + rv = max31785_get_pwm_mode(client, page); + break; + default: + rv = -ENODATA; + break; + } + + return rv; +} + +static inline u32 max31785_scale_pwm(u32 sensor_val) +{ + /* + * The datasheet describes the accepted value range for manual PWM as + * [0, 0x2710], while the hwmon pwmX sysfs interface accepts values in + * [0, 255]. The MAX31785 uses DIRECT mode to scale the FAN_COMMAND + * registers and in PWM mode the coefficients are m=1, b=0, R=2. The + * important observation here is that 0x2710 == 10000 == 100 * 100. + * + * R=2 (== 10^2 == 100) accounts for scaling the value provided at the + * sysfs interface into the required hardware resolution, but it does + * not yet yield a value that we can write to the device (this initial + * scaling is handled by pmbus_data2reg()). Multiplying by 100 below + * translates the parameter value into the percentage units required by + * PMBus, and then we scale back by 255 as required by the hwmon pwmX + * interface to yield the percentage value at the appropriate + * resolution for hardware. + */ + return (sensor_val * 100) / 255; +} + +static int max31785_pwm_enable(struct i2c_client *client, int page, + u16 word) +{ + int config = 0; + int rate; + + switch (word) { + case 0: + rate = 0x7fff; + break; + case 1: + rate = pmbus_get_fan_rate_cached(client, page, 0, percent); + if (rate < 0) + return rate; + rate = max31785_scale_pwm(rate); + break; + case 2: + config = PB_FAN_1_RPM; + rate = pmbus_get_fan_rate_cached(client, page, 0, rpm); + if (rate < 0) + return rate; + break; + case 3: + rate = 0xffff; + break; + default: + return -EINVAL; + } + + return pmbus_update_fan(client, page, 0, config, PB_FAN_1_RPM, rate); +} + +static int max31785_write_word_data(struct i2c_client *client, int page, + int reg, u16 word) +{ + switch (reg) { + case PMBUS_VIRT_PWM_1: + return pmbus_update_fan(client, page, 0, 0, PB_FAN_1_RPM, + max31785_scale_pwm(word)); + case PMBUS_VIRT_PWM_ENABLE_1: + return max31785_pwm_enable(client, page, word); + default: + break; + } + + return -ENODATA; +} + #define MAX31785_FAN_FUNCS \ - (PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12) + (PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12 | PMBUS_HAVE_PWM12) #define MAX31785_TEMP_FUNCS \ (PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP) @@ -32,11 +160,19 @@ enum max31785_regs { static const struct pmbus_driver_info max31785_info = { .pages = MAX31785_NR_PAGES, + .write_word_data = max31785_write_word_data, + .read_word_data = max31785_read_word_data, + /* RPM */ .format[PSC_FAN] = direct, .m[PSC_FAN] = 1, .b[PSC_FAN] = 0, .R[PSC_FAN] = 0, + /* PWM */ + .format[PSC_PWM] = direct, + .m[PSC_PWM] = 1, + .b[PSC_PWM] = 0, + .R[PSC_PWM] = 2, .func[0] = MAX31785_FAN_FUNCS, .func[1] = MAX31785_FAN_FUNCS, .func[2] = MAX31785_FAN_FUNCS, From 464df6fa3766784b85b00d56cd4d7c706aee5375 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Mon, 20 Nov 2017 15:12:05 +1030 Subject: [PATCH 08/32] hwmon: (pmbus) Add virtual page config bit Some circumstances call for virtual pages, to expose multiple values packed into an extended PMBus register in a manner non-compliant with the PMBus standard. An example of this is the Maxim MAX31785 controller, which extends the READ_FAN_SPEED_1 PMBus register from two to four bytes to support tach readings for both rotors of a dual rotor fan. This extended register contains two word-sized values, one reporting the rate of the fastest rotor, the other the rate of the slowest. The concept of virtual pages aids this situation by mapping the page number onto the value to be selected from the vectored result. We should not try to set virtual pages on the device as such a page explicitly doesn't exist; add a flag so we can avoid doing so. Signed-off-by: Andrew Jeffery Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus.h | 2 ++ drivers/hwmon/pmbus/pmbus_core.c | 27 ++++++++++++++++++--------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index b54d7604d3ef..d39d506aa63e 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -372,6 +372,8 @@ enum pmbus_sensor_classes { #define PMBUS_HAVE_PWM12 BIT(20) #define PMBUS_HAVE_PWM34 BIT(21) +#define PMBUS_PAGE_VIRTUAL BIT(31) + enum pmbus_data_format { linear = 0, direct, vid }; enum vrm_version { vr11 = 0, vr12, vr13 }; diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index fdd33857f117..99ab39f19bf4 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -162,18 +162,27 @@ EXPORT_SYMBOL_GPL(pmbus_clear_cache); int pmbus_set_page(struct i2c_client *client, int page) { struct pmbus_data *data = i2c_get_clientdata(client); - int rv = 0; - int newpage; + int rv; - if (page >= 0 && page != data->currpage) { + if (page < 0 || page == data->currpage) + return 0; + + if (!(data->info->func[page] & PMBUS_PAGE_VIRTUAL)) { rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); - newpage = i2c_smbus_read_byte_data(client, PMBUS_PAGE); - if (newpage != page) - rv = -EIO; - else - data->currpage = page; + if (rv < 0) + return rv; + + rv = i2c_smbus_read_byte_data(client, PMBUS_PAGE); + if (rv < 0) + return rv; + + if (rv != page) + return -EIO; } - return rv; + + data->currpage = page; + + return 0; } EXPORT_SYMBOL_GPL(pmbus_set_page); From cf583b4275761754638c946ff777546d1a9b6744 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Mon, 20 Nov 2017 15:12:06 +1030 Subject: [PATCH 09/32] hwmon: (pmbus/max31785) Add dual tachometer support The dual tachometer feature is implemented in hardware with a TACHSEL input to indicate the rotor under measurement, and exposed on the device by extending the READ_FAN_SPEED_1 word with two extra bytes*. The need to read the non-standard four-byte response leads to a cut-down implementation of i2c_smbus_xfer_emulated() included in the driver. Further, to expose the second rotor tachometer value to userspace the values are exposed through virtual pages. We re-route accesses to FAN_CONFIG_1_2 and READ_FAN_SPEED_1 on pages 23-28 (not defined by the hardware) to the same registers on pages 0-5, and with the latter command we extract the value from the second word of the four-byte response. * The documentation recommends the slower rotor be associated with TACHSEL=0, which corresponds to the first word of the response. The TACHSEL=0 measurement is used by the controller's closed-loop fan management to judge target fan rate. Signed-off-by: Andrew Jeffery Signed-off-by: Guenter Roeck --- Documentation/hwmon/max31785 | 8 +- drivers/hwmon/pmbus/max31785.c | 147 +++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 3 deletions(-) diff --git a/Documentation/hwmon/max31785 b/Documentation/hwmon/max31785 index 7b0a0a8cdb6b..270c5f865261 100644 --- a/Documentation/hwmon/max31785 +++ b/Documentation/hwmon/max31785 @@ -17,8 +17,9 @@ management with temperature and remote voltage sensing. Various fan control features are provided, including PWM frequency control, temperature hysteresis, dual tachometer measurements, and fan health monitoring. -For dual rotor fan configuration, the MAX31785 exposes the slowest rotor of the -two in the fan[1-4]_input attributes. +For dual-rotor configurations the MAX31785A exposes the second rotor tachometer +readings in attributes fan[5-8]_input. By contrast the MAX31785 only exposes +the slowest rotor measurement, and does so in the fan[1-4]_input attributes. Usage Notes ----------- @@ -31,7 +32,8 @@ Sysfs attributes fan[1-4]_alarm Fan alarm. fan[1-4]_fault Fan fault. -fan[1-4]_input Fan RPM. +fan[1-8]_input Fan RPM. On the MAX31785A, inputs 5-8 correspond to the + second rotor of fans 1-4 fan[1-4]_target Fan input target in[1-6]_crit Critical maximum output voltage diff --git a/drivers/hwmon/pmbus/max31785.c b/drivers/hwmon/pmbus/max31785.c index 8706a696c89a..bffab449be39 100644 --- a/drivers/hwmon/pmbus/max31785.c +++ b/drivers/hwmon/pmbus/max31785.c @@ -16,9 +16,79 @@ enum max31785_regs { MFR_REVISION = 0x9b, + MFR_FAN_CONFIG = 0xf1, }; +#define MAX31785 0x3030 +#define MAX31785A 0x3040 + +#define MFR_FAN_CONFIG_DUAL_TACH BIT(12) + #define MAX31785_NR_PAGES 23 +#define MAX31785_NR_FAN_PAGES 6 + +static int max31785_read_byte_data(struct i2c_client *client, int page, + int reg) +{ + if (page < MAX31785_NR_PAGES) + return -ENODATA; + + switch (reg) { + case PMBUS_VOUT_MODE: + return -ENOTSUPP; + case PMBUS_FAN_CONFIG_12: + return pmbus_read_byte_data(client, page - MAX31785_NR_PAGES, + reg); + } + + return -ENODATA; +} + +static int max31785_write_byte(struct i2c_client *client, int page, u8 value) +{ + if (page < MAX31785_NR_PAGES) + return -ENODATA; + + return -ENOTSUPP; +} + +static int max31785_read_long_data(struct i2c_client *client, int page, + int reg, u32 *data) +{ + unsigned char cmdbuf[1]; + unsigned char rspbuf[4]; + int rc; + + struct i2c_msg msg[2] = { + { + .addr = client->addr, + .flags = 0, + .len = sizeof(cmdbuf), + .buf = cmdbuf, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = sizeof(rspbuf), + .buf = rspbuf, + }, + }; + + cmdbuf[0] = reg; + + rc = pmbus_set_page(client, page); + if (rc < 0) + return rc; + + rc = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (rc < 0) + return rc; + + *data = (rspbuf[0] << (0 * 8)) | (rspbuf[1] << (1 * 8)) | + (rspbuf[2] << (2 * 8)) | (rspbuf[3] << (3 * 8)); + + return rc; +} static int max31785_get_pwm(struct i2c_client *client, int page) { @@ -62,9 +132,30 @@ static int max31785_get_pwm_mode(struct i2c_client *client, int page) static int max31785_read_word_data(struct i2c_client *client, int page, int reg) { + u32 val; int rv; switch (reg) { + case PMBUS_READ_FAN_SPEED_1: + if (page < MAX31785_NR_PAGES) + return -ENODATA; + + rv = max31785_read_long_data(client, page - MAX31785_NR_PAGES, + reg, &val); + if (rv < 0) + return rv; + + rv = (val >> 16) & 0xffff; + break; + case PMBUS_FAN_COMMAND_1: + /* + * PMBUS_FAN_COMMAND_x is probed to judge whether or not to + * expose fan control registers. + * + * Don't expose fan_target attribute for virtual pages. + */ + rv = (page >= MAX31785_NR_PAGES) ? -ENOTSUPP : -ENODATA; + break; case PMBUS_VIRT_PWM_1: rv = max31785_get_pwm(client, page); break; @@ -157,11 +248,15 @@ static int max31785_write_word_data(struct i2c_client *client, int page, #define MAX31785_VOUT_FUNCS \ (PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT) +#define MAX37185_NUM_FAN_PAGES 6 + static const struct pmbus_driver_info max31785_info = { .pages = MAX31785_NR_PAGES, .write_word_data = max31785_write_word_data, + .read_byte_data = max31785_read_byte_data, .read_word_data = max31785_read_word_data, + .write_byte = max31785_write_byte, /* RPM */ .format[PSC_FAN] = direct, @@ -208,13 +303,46 @@ static const struct pmbus_driver_info max31785_info = { .func[22] = MAX31785_VOUT_FUNCS, }; +static int max31785_configure_dual_tach(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + int ret; + int i; + + for (i = 0; i < MAX31785_NR_FAN_PAGES; i++) { + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_word_data(client, MFR_FAN_CONFIG); + if (ret < 0) + return ret; + + if (ret & MFR_FAN_CONFIG_DUAL_TACH) { + int virtual = MAX31785_NR_PAGES + i; + + info->pages = virtual + 1; + info->func[virtual] |= PMBUS_HAVE_FAN12; + info->func[virtual] |= PMBUS_PAGE_VIRTUAL; + } + } + + return 0; +} + static int max31785_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct device *dev = &client->dev; struct pmbus_driver_info *info; + bool dual_tach = false; s64 ret; + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + info = devm_kzalloc(dev, sizeof(struct pmbus_driver_info), GFP_KERNEL); if (!info) return -ENOMEM; @@ -225,6 +353,25 @@ static int max31785_probe(struct i2c_client *client, if (ret < 0) return ret; + ret = i2c_smbus_read_word_data(client, MFR_REVISION); + if (ret < 0) + return ret; + + if (ret == MAX31785A) { + dual_tach = true; + } else if (ret == MAX31785) { + if (!strcmp("max31785a", id->name)) + dev_warn(dev, "Expected max3175a, found max31785: cannot provide secondary tachometer readings\n"); + } else { + return -ENODEV; + } + + if (dual_tach) { + ret = max31785_configure_dual_tach(client, info); + if (ret < 0) + return ret; + } + return pmbus_do_probe(client, id, info); } From 5d389b125186cf254ad5b8015763ac07c151aea4 Mon Sep 17 00:00:00 2001 From: Maciej Purski Date: Wed, 22 Nov 2017 16:32:15 +0100 Subject: [PATCH 10/32] hwmon: (ina2xx) Make calibration register value fixed Calibration register is used for calculating current register in hardware according to datasheet: current = shunt_volt * calib_register / 2048 (ina 226) current = shunt_volt * calib_register / 4096 (ina 219) Fix calib_register value to 2048 for ina226 and 4096 for ina 219 in order to avoid truncation error and provide best precision allowed by shunt_voltage measurement. Make current scale value follow changes of shunt_resistor from sysfs as calib_register value is now fixed. Power_lsb value should also follow shunt_resistor changes as stated in datasheet: power_lsb = 25 * current_lsb (ina 226) power_lsb = 20 * current_lsb (ina 219) Signed-off-by: Maciej Purski Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 87 ++++++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 37 deletions(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index 62e38fa8cda2..e362a932fe8c 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -95,18 +95,20 @@ enum ina2xx_ids { ina219, ina226 }; struct ina2xx_config { u16 config_default; - int calibration_factor; + int calibration_value; int registers; int shunt_div; int bus_voltage_shift; int bus_voltage_lsb; /* uV */ - int power_lsb; /* uW */ + int power_lsb_factor; }; struct ina2xx_data { const struct ina2xx_config *config; long rshunt; + long current_lsb_uA; + long power_lsb_uW; struct mutex config_lock; struct regmap *regmap; @@ -116,21 +118,21 @@ struct ina2xx_data { static const struct ina2xx_config ina2xx_config[] = { [ina219] = { .config_default = INA219_CONFIG_DEFAULT, - .calibration_factor = 40960000, + .calibration_value = 4096, .registers = INA219_REGISTERS, .shunt_div = 100, .bus_voltage_shift = 3, .bus_voltage_lsb = 4000, - .power_lsb = 20000, + .power_lsb_factor = 20, }, [ina226] = { .config_default = INA226_CONFIG_DEFAULT, - .calibration_factor = 5120000, + .calibration_value = 2048, .registers = INA226_REGISTERS, .shunt_div = 400, .bus_voltage_shift = 0, .bus_voltage_lsb = 1250, - .power_lsb = 25000, + .power_lsb_factor = 25, }, }; @@ -169,12 +171,16 @@ static u16 ina226_interval_to_reg(int interval) return INA226_SHIFT_AVG(avg_bits); } +/* + * Calibration register is set to the best value, which eliminates + * truncation errors on calculating current register in hardware. + * According to datasheet (eq. 3) the best values are 2048 for + * ina226 and 4096 for ina219. They are hardcoded as calibration_value. + */ static int ina2xx_calibrate(struct ina2xx_data *data) { - u16 val = DIV_ROUND_CLOSEST(data->config->calibration_factor, - data->rshunt); - - return regmap_write(data->regmap, INA2XX_CALIBRATION, val); + return regmap_write(data->regmap, INA2XX_CALIBRATION, + data->config->calibration_value); } /* @@ -187,10 +193,6 @@ static int ina2xx_init(struct ina2xx_data *data) if (ret < 0) return ret; - /* - * Set current LSB to 1mA, shunt is in uOhms - * (equation 13 in datasheet). - */ return ina2xx_calibrate(data); } @@ -268,15 +270,15 @@ static int ina2xx_get_value(struct ina2xx_data *data, u8 reg, val = DIV_ROUND_CLOSEST(val, 1000); break; case INA2XX_POWER: - val = regval * data->config->power_lsb; + val = regval * data->power_lsb_uW; break; case INA2XX_CURRENT: - /* signed register, LSB=1mA (selected), in mA */ - val = (s16)regval; + /* signed register, result in mA */ + val = regval * data->current_lsb_uA; + val = DIV_ROUND_CLOSEST(val, 1000); break; case INA2XX_CALIBRATION: - val = DIV_ROUND_CLOSEST(data->config->calibration_factor, - regval); + val = regval; break; default: /* programmer goofed */ @@ -304,9 +306,32 @@ static ssize_t ina2xx_show_value(struct device *dev, ina2xx_get_value(data, attr->index, regval)); } -static ssize_t ina2xx_set_shunt(struct device *dev, - struct device_attribute *da, - const char *buf, size_t count) +/* + * In order to keep calibration register value fixed, the product + * of current_lsb and shunt_resistor should also be fixed and equal + * to shunt_voltage_lsb = 1 / shunt_div multiplied by 10^9 in order + * to keep the scale. + */ +static int ina2xx_set_shunt(struct ina2xx_data *data, long val) +{ + unsigned int dividend = DIV_ROUND_CLOSEST(1000000000, + data->config->shunt_div); + if (val <= 0 || val > dividend) + return -EINVAL; + + mutex_lock(&data->config_lock); + data->rshunt = val; + data->current_lsb_uA = DIV_ROUND_CLOSEST(dividend, val); + data->power_lsb_uW = data->config->power_lsb_factor * + data->current_lsb_uA; + mutex_unlock(&data->config_lock); + + return 0; +} + +static ssize_t ina2xx_store_shunt(struct device *dev, + struct device_attribute *da, + const char *buf, size_t count) { unsigned long val; int status; @@ -316,18 +341,9 @@ static ssize_t ina2xx_set_shunt(struct device *dev, if (status < 0) return status; - if (val == 0 || - /* Values greater than the calibration factor make no sense. */ - val > data->config->calibration_factor) - return -EINVAL; - - mutex_lock(&data->config_lock); - data->rshunt = val; - status = ina2xx_calibrate(data); - mutex_unlock(&data->config_lock); + status = ina2xx_set_shunt(data, val); if (status < 0) return status; - return count; } @@ -387,7 +403,7 @@ static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, ina2xx_show_value, NULL, /* shunt resistance */ static SENSOR_DEVICE_ATTR(shunt_resistor, S_IRUGO | S_IWUSR, - ina2xx_show_value, ina2xx_set_shunt, + ina2xx_show_value, ina2xx_store_shunt, INA2XX_CALIBRATION); /* update interval (ina226 only) */ @@ -448,10 +464,7 @@ static int ina2xx_probe(struct i2c_client *client, val = INA2XX_RSHUNT_DEFAULT; } - if (val <= 0 || val > data->config->calibration_factor) - return -ENODEV; - - data->rshunt = val; + ina2xx_set_shunt(data, val); ina2xx_regmap_config.max_register = data->config->registers; From 98b16a09861aa85d68853728515738b19a7cd8d4 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Thu, 30 Nov 2017 19:12:57 +0100 Subject: [PATCH 11/32] hwmon: (max31785) Add OF device ID table The driver doesn't have a struct of_device_id table but supported devices are registered via Device Trees. This is working on the assumption that a I2C device registered via OF will always match a legacy I2C device ID and that the MODALIAS reported will always be of the form i2c:. But this could change in the future so the correct approach is to have an OF device ID table if the devices are registered via OF. Before this patch: $ modinfo drivers/hwmon/pmbus/max31785.ko | grep alias alias: i2c:max31785a alias: i2c:max31785 After this patch: $ modinfo drivers/hwmon/pmbus/max31785.ko | grep alias alias: i2c:max31785a alias: i2c:max31785 alias: of:N*T*Cmaxim,max31785aC* alias: of:N*T*Cmaxim,max31785a alias: of:N*T*Cmaxim,max31785C* alias: of:N*T*Cmaxim,max31785 Signed-off-by: Javier Martinez Canillas Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/max31785.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/hwmon/pmbus/max31785.c b/drivers/hwmon/pmbus/max31785.c index bffab449be39..c9dc8799b5e1 100644 --- a/drivers/hwmon/pmbus/max31785.c +++ b/drivers/hwmon/pmbus/max31785.c @@ -383,9 +383,18 @@ static const struct i2c_device_id max31785_id[] = { MODULE_DEVICE_TABLE(i2c, max31785_id); +static const struct of_device_id max31785_of_match[] = { + { .compatible = "maxim,max31785" }, + { .compatible = "maxim,max31785a" }, + { }, +}; + +MODULE_DEVICE_TABLE(of, max31785_of_match); + static struct i2c_driver max31785_driver = { .driver = { .name = "max31785", + .of_match_table = max31785_of_match, }, .probe = max31785_probe, .remove = pmbus_do_remove, From 3870945aeb0e94126b9ea1cbd4f2f50d99eb948d Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 3 Dec 2017 15:15:00 -0800 Subject: [PATCH 12/32] hwmon: Fix parameter documentation sparse reports: drivers/hwmon/hwmon.c:681: warning: No description found for parameter 'chip' drivers/hwmon/hwmon.c:681: warning: Excess function parameter 'info' description in 'hwmon_device_register_with_info' drivers/hwmon/hwmon.c:789: warning: No description found for parameter 'chip' drivers/hwmon/hwmon.c:789: warning: No description found for parameter 'groups' drivers/hwmon/hwmon.c:789: warning: Excess function parameter 'info' description in 'devm_hwmon_device_register_with_info' Signed-off-by: Guenter Roeck --- drivers/hwmon/hwmon.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index af5123042990..32083e452cde 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -678,7 +678,7 @@ 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 + * @chip: 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 @@ -785,11 +785,11 @@ EXPORT_SYMBOL_GPL(devm_hwmon_device_register_with_groups); /** * devm_hwmon_device_register_with_info - register w/ hwmon - * @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 + * @dev: the parent device + * @name: hwmon name attribute + * @drvdata: driver data to attach to created device + * @chip: pointer to hwmon chip information + * @groups: pointer to list of driver specific attribute groups * * Returns the pointer to the new device. The new device is automatically * unregistered with the parent device. From 679f50b8d1f80a7083444c5a8aa02281b9ff9cc1 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 3 Dec 2017 15:16:52 -0800 Subject: [PATCH 13/32] hwmon: (sht21) Fix documentation of struct sht21 Sparse reports: drivers/hwmon/sht21.c:60: warning: No description found for parameter 'client' drivers/hwmon/sht21.c:60: warning: Excess struct member 'hwmon_dev' description in 'sht21' Signed-off-by: Guenter Roeck --- drivers/hwmon/sht21.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/sht21.c b/drivers/hwmon/sht21.c index 06706d288355..190e7b39ce32 100644 --- a/drivers/hwmon/sht21.c +++ b/drivers/hwmon/sht21.c @@ -41,7 +41,7 @@ /** * struct sht21 - SHT21 device specific data - * @hwmon_dev: device registered with hwmon + * @client: I2C client device * @lock: mutex to protect measurement values * @last_update: time of last update (jiffies) * @temperature: cached temperature measurement value From d5324e90957adb4f2ab144d14ed74853a8d9c525 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 3 Dec 2017 15:18:34 -0800 Subject: [PATCH 14/32] hwmon: (sht15) Fix parameter documentation of sht15_crc8() Sparse reports: drivers/hwmon/sht15.c:188: warning: No description found for parameter 'len' Signed-off-by: Guenter Roeck --- drivers/hwmon/sht15.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/sht15.c b/drivers/hwmon/sht15.c index 25d28343ba93..2be77752cd56 100644 --- a/drivers/hwmon/sht15.c +++ b/drivers/hwmon/sht15.c @@ -179,6 +179,7 @@ struct sht15_data { * sht15_crc8() - compute crc8 * @data: sht15 specific data. * @value: sht15 retrieved data. + * @len: Length of retrieved data * * This implements section 2 of the CRC datasheet. */ From 7f6d70cd478452d2534b6c33b752a8009783383a Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 3 Dec 2017 15:22:26 -0800 Subject: [PATCH 15/32] hwmon: (iio_hwmon) Fix documentation of struct iio_hwmon_state Sparse reports: drivers/hwmon/iio_hwmon.c:36: warning: No description found for parameter 'groups' Signed-off-by: Guenter Roeck --- drivers/hwmon/iio_hwmon.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/iio_hwmon.c b/drivers/hwmon/iio_hwmon.c index f6a76679c650..5e5b32a1ec4b 100644 --- a/drivers/hwmon/iio_hwmon.c +++ b/drivers/hwmon/iio_hwmon.c @@ -23,7 +23,8 @@ * @channels: filled with array of channels from iio * @num_channels: number of channels in channels (saves counting twice) * @hwmon_dev: associated hwmon device - * @attr_group: the group of attributes + * @attr_group: the group of attributes + * @groups: null terminated array of attribute groups * @attrs: null terminated array of attribute pointers. */ struct iio_hwmon_state { From a7a9b15a35d2504aa83e140d4cf50c85ac01e965 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 3 Dec 2017 15:24:57 -0800 Subject: [PATCH 16/32] hwmon: (hih6130) Fix documentation of struct hih6130 Sparse reports: drivers/hwmon/hih6130.c:56: warning: No description found for parameter 'client' drivers/hwmon/hih6130.c:56: warning: Excess struct member 'hwmon_dev' description in 'hih6130' Signed-off-by: Guenter Roeck --- drivers/hwmon/hih6130.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/hih6130.c b/drivers/hwmon/hih6130.c index 7b73d2002d3e..0ae1ee1dbf76 100644 --- a/drivers/hwmon/hih6130.c +++ b/drivers/hwmon/hih6130.c @@ -37,7 +37,7 @@ /** * struct hih6130 - HIH-6130 device specific data - * @hwmon_dev: device registered with hwmon + * @client: pointer to I2C client device * @lock: mutex to protect measurement values * @valid: only false before first measurement is taken * @last_update: time of last update (jiffies) From 571e3f3a8e6582ecce2c7fd5d0a9e2ae74cece02 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 3 Dec 2017 15:27:56 -0800 Subject: [PATCH 17/32] hwmon: (w83773g) Fix fault detection and reporting Smatch reports: drivers/hwmon/w83773g.c:105 get_fault() warn: shift has higher precedence than mask Code analysis shows that the code is indeed wrong. Fix it, and while we are at it, drop unnecessary typecast. Fixes: 86a10c802362 ("hwmon: Add W83773G driver") Cc: Lei YU Reviewed-by: Lei YU Signed-off-by: Guenter Roeck --- drivers/hwmon/w83773g.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/w83773g.c b/drivers/hwmon/w83773g.c index 0b97c285b049..e858093ac806 100644 --- a/drivers/hwmon/w83773g.c +++ b/drivers/hwmon/w83773g.c @@ -102,7 +102,7 @@ static int get_fault(struct regmap *regmap, int index, long *val) if (ret < 0) return ret; - *val = (u8)regval & 0x04 >> 2; + *val = (regval & 0x04) >> 2; return 0; } From eb6489b696ad22a8464e20502e18014434b4b0ea Mon Sep 17 00:00:00 2001 From: "Edward A. James" Date: Mon, 11 Dec 2017 15:32:49 -0600 Subject: [PATCH 18/32] hwmon: (pmbus) Export pmbus device debugfs directory entry Pmbus client drivers, if they want to use debugfs, should use the same root directory as the pmbus debugfs entries are using. Therefore, export the device dentry for the pmbus client. Signed-off-by: Edward A. James Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus.h | 2 ++ drivers/hwmon/pmbus/pmbus_core.c | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index d39d506aa63e..1d24397d36ec 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -461,4 +461,6 @@ int pmbus_get_fan_rate_cached(struct i2c_client *client, int page, int id, enum pmbus_fan_mode mode); int pmbus_update_fan(struct i2c_client *client, int page, int id, u8 config, u8 mask, u16 command); +struct dentry *pmbus_get_debugfs_dir(struct i2c_client *client); + #endif /* PMBUS_H */ diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 99ab39f19bf4..f7c47d7994e7 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -2381,6 +2381,14 @@ int pmbus_do_remove(struct i2c_client *client) } EXPORT_SYMBOL_GPL(pmbus_do_remove); +struct dentry *pmbus_get_debugfs_dir(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + + return data->debugfs; +} +EXPORT_SYMBOL_GPL(pmbus_get_debugfs_dir); + static int __init pmbus_core_init(void) { pmbus_debugfs_dir = debugfs_create_dir("pmbus", NULL); From d6bb645a1704cba3884bf03d5a8bd86915b5e650 Mon Sep 17 00:00:00 2001 From: "Edward A. James" Date: Mon, 11 Dec 2017 15:32:50 -0600 Subject: [PATCH 19/32] hwmon: (pmbus) cffps: Add debugfs entries Add debugfs entries for additional power supply data, including part number, serial number, FRU number, firmware revision, ccin, and the input history of the power supply. The input history is 10 minutes of input power data in the form of twenty 30-second packets. Each packet contains average and maximum power for that 30 second period. Signed-off-by: Edward A. James [groeck: Fixed endianness problem] Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/ibm-cffps.c | 202 +++++++++++++++++++++++++++++++- 1 file changed, 201 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/pmbus/ibm-cffps.c b/drivers/hwmon/pmbus/ibm-cffps.c index cb56da6834e5..de2547476253 100644 --- a/drivers/hwmon/pmbus/ibm-cffps.c +++ b/drivers/hwmon/pmbus/ibm-cffps.c @@ -8,12 +8,26 @@ */ #include +#include #include +#include #include +#include #include +#include #include "pmbus.h" +#define CFFPS_FRU_CMD 0x9A +#define CFFPS_PN_CMD 0x9B +#define CFFPS_SN_CMD 0x9E +#define CFFPS_CCIN_CMD 0xBD +#define CFFPS_FW_CMD_START 0xFA +#define CFFPS_FW_NUM_BYTES 4 + +#define CFFPS_INPUT_HISTORY_CMD 0xD6 +#define CFFPS_INPUT_HISTORY_SIZE 100 + /* STATUS_MFR_SPECIFIC bits */ #define CFFPS_MFR_FAN_FAULT BIT(0) #define CFFPS_MFR_THERMAL_FAULT BIT(1) @@ -24,6 +38,144 @@ #define CFFPS_MFR_VAUX_FAULT BIT(6) #define CFFPS_MFR_CURRENT_SHARE_WARNING BIT(7) +enum { + CFFPS_DEBUGFS_INPUT_HISTORY = 0, + CFFPS_DEBUGFS_FRU, + CFFPS_DEBUGFS_PN, + CFFPS_DEBUGFS_SN, + CFFPS_DEBUGFS_CCIN, + CFFPS_DEBUGFS_FW, + CFFPS_DEBUGFS_NUM_ENTRIES +}; + +struct ibm_cffps_input_history { + struct mutex update_lock; + unsigned long last_update; + + u8 byte_count; + u8 data[CFFPS_INPUT_HISTORY_SIZE]; +}; + +struct ibm_cffps { + struct i2c_client *client; + + struct ibm_cffps_input_history input_history; + + int debugfs_entries[CFFPS_DEBUGFS_NUM_ENTRIES]; +}; + +#define to_psu(x, y) container_of((x), struct ibm_cffps, debugfs_entries[(y)]) + +static ssize_t ibm_cffps_read_input_history(struct ibm_cffps *psu, + char __user *buf, size_t count, + loff_t *ppos) +{ + int rc; + u8 msgbuf0[1] = { CFFPS_INPUT_HISTORY_CMD }; + u8 msgbuf1[CFFPS_INPUT_HISTORY_SIZE + 1] = { 0 }; + struct i2c_msg msg[2] = { + { + .addr = psu->client->addr, + .flags = psu->client->flags, + .len = 1, + .buf = msgbuf0, + }, { + .addr = psu->client->addr, + .flags = psu->client->flags | I2C_M_RD, + .len = CFFPS_INPUT_HISTORY_SIZE + 1, + .buf = msgbuf1, + }, + }; + + if (!*ppos) { + mutex_lock(&psu->input_history.update_lock); + if (time_after(jiffies, psu->input_history.last_update + HZ)) { + /* + * Use a raw i2c transfer, since we need more bytes + * than Linux I2C supports through smbus xfr (only 32). + */ + rc = i2c_transfer(psu->client->adapter, msg, 2); + if (rc < 0) { + mutex_unlock(&psu->input_history.update_lock); + return rc; + } + + psu->input_history.byte_count = msgbuf1[0]; + memcpy(psu->input_history.data, &msgbuf1[1], + CFFPS_INPUT_HISTORY_SIZE); + psu->input_history.last_update = jiffies; + } + + mutex_unlock(&psu->input_history.update_lock); + } + + return simple_read_from_buffer(buf, count, ppos, + psu->input_history.data, + psu->input_history.byte_count); +} + +static ssize_t ibm_cffps_debugfs_op(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + u8 cmd; + int i, rc; + int *idxp = file->private_data; + int idx = *idxp; + struct ibm_cffps *psu = to_psu(idxp, idx); + char data[I2C_SMBUS_BLOCK_MAX] = { 0 }; + + switch (idx) { + case CFFPS_DEBUGFS_INPUT_HISTORY: + return ibm_cffps_read_input_history(psu, buf, count, ppos); + case CFFPS_DEBUGFS_FRU: + cmd = CFFPS_FRU_CMD; + break; + case CFFPS_DEBUGFS_PN: + cmd = CFFPS_PN_CMD; + break; + case CFFPS_DEBUGFS_SN: + cmd = CFFPS_SN_CMD; + break; + case CFFPS_DEBUGFS_CCIN: + rc = i2c_smbus_read_word_swapped(psu->client, CFFPS_CCIN_CMD); + if (rc < 0) + return rc; + + rc = snprintf(data, 5, "%04X", rc); + goto done; + case CFFPS_DEBUGFS_FW: + for (i = 0; i < CFFPS_FW_NUM_BYTES; ++i) { + rc = i2c_smbus_read_byte_data(psu->client, + CFFPS_FW_CMD_START + i); + if (rc < 0) + return rc; + + snprintf(&data[i * 2], 3, "%02X", rc); + } + + rc = i * 2; + goto done; + default: + return -EINVAL; + } + + rc = i2c_smbus_read_block_data(psu->client, cmd, data); + if (rc < 0) + return rc; + +done: + data[rc] = '\n'; + rc += 2; + + return simple_read_from_buffer(buf, count, ppos, data, rc); +} + +static const struct file_operations ibm_cffps_fops = { + .llseek = noop_llseek, + .read = ibm_cffps_debugfs_op, + .open = simple_open, +}; + static int ibm_cffps_read_byte_data(struct i2c_client *client, int page, int reg) { @@ -119,7 +271,55 @@ static struct pmbus_driver_info ibm_cffps_info = { static int ibm_cffps_probe(struct i2c_client *client, const struct i2c_device_id *id) { - return pmbus_do_probe(client, id, &ibm_cffps_info); + int i, rc; + struct dentry *debugfs; + struct dentry *ibm_cffps_dir; + struct ibm_cffps *psu; + + rc = pmbus_do_probe(client, id, &ibm_cffps_info); + if (rc) + return rc; + + /* Don't fail the probe if we can't create debugfs */ + debugfs = pmbus_get_debugfs_dir(client); + if (!debugfs) + return 0; + + ibm_cffps_dir = debugfs_create_dir(client->name, debugfs); + if (!ibm_cffps_dir) + return 0; + + psu = devm_kzalloc(&client->dev, sizeof(*psu), GFP_KERNEL); + if (!psu) + return 0; + + psu->client = client; + mutex_init(&psu->input_history.update_lock); + psu->input_history.last_update = jiffies - HZ; + + for (i = 0; i < CFFPS_DEBUGFS_NUM_ENTRIES; ++i) + psu->debugfs_entries[i] = i; + + debugfs_create_file("input_history", 0444, ibm_cffps_dir, + &psu->debugfs_entries[CFFPS_DEBUGFS_INPUT_HISTORY], + &ibm_cffps_fops); + debugfs_create_file("fru", 0444, ibm_cffps_dir, + &psu->debugfs_entries[CFFPS_DEBUGFS_FRU], + &ibm_cffps_fops); + debugfs_create_file("part_number", 0444, ibm_cffps_dir, + &psu->debugfs_entries[CFFPS_DEBUGFS_PN], + &ibm_cffps_fops); + debugfs_create_file("serial_number", 0444, ibm_cffps_dir, + &psu->debugfs_entries[CFFPS_DEBUGFS_SN], + &ibm_cffps_fops); + debugfs_create_file("ccin", 0444, ibm_cffps_dir, + &psu->debugfs_entries[CFFPS_DEBUGFS_CCIN], + &ibm_cffps_fops); + debugfs_create_file("fw_version", 0444, ibm_cffps_dir, + &psu->debugfs_entries[CFFPS_DEBUGFS_FW], + &ibm_cffps_fops); + + return 0; } static const struct i2c_device_id ibm_cffps_id[] = { From 8c9e52705740b21c546907711630c389ef09715e Mon Sep 17 00:00:00 2001 From: Joel Date: Sat, 23 Dec 2017 23:35:27 +1030 Subject: [PATCH 20/32] dt-bindings: hwmon: aspeed-pwm-tacho: Add reset node The device tree bindings are updated to document the resets phandle, and the example is updated to match what is expected for both the reset and clock phandle. Note that the bindings should have always had the reset controller, as the hardware is unusable without it. Acked-by: Rob Herring Signed-off-by: Joel Stanley Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/aspeed-pwm-tacho.txt | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/Documentation/devicetree/bindings/hwmon/aspeed-pwm-tacho.txt b/Documentation/devicetree/bindings/hwmon/aspeed-pwm-tacho.txt index 367c8203213b..3ac02988a1a5 100644 --- a/Documentation/devicetree/bindings/hwmon/aspeed-pwm-tacho.txt +++ b/Documentation/devicetree/bindings/hwmon/aspeed-pwm-tacho.txt @@ -22,8 +22,9 @@ Required properties for pwm-tacho node: - compatible : should be "aspeed,ast2400-pwm-tacho" for AST2400 and "aspeed,ast2500-pwm-tacho" for AST2500. -- clocks : a fixed clock providing input clock frequency(PWM - and Fan Tach clock) +- clocks : phandle to clock provider with the clock number in the second cell + +- resets : phandle to reset controller with the reset number in the second cell fan subnode format: =================== @@ -48,19 +49,14 @@ Required properties for each child node: Examples: -pwm_tacho_fixed_clk: fixedclk { - compatible = "fixed-clock"; - #clock-cells = <0>; - clock-frequency = <24000000>; -}; - pwm_tacho: pwmtachocontroller@1e786000 { #address-cells = <1>; #size-cells = <1>; #cooling-cells = <2>; reg = <0x1E786000 0x1000>; compatible = "aspeed,ast2500-pwm-tacho"; - clocks = <&pwm_tacho_fixed_clk>; + clocks = <&syscon ASPEED_CLK_APB>; + resets = <&syscon ASPEED_RESET_PWM>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_pwm0_default &pinctrl_pwm1_default>; From 18c514cc0e0278b7852a6741973a9523ad012700 Mon Sep 17 00:00:00 2001 From: Joel Date: Sat, 23 Dec 2017 23:35:28 +1030 Subject: [PATCH 21/32] hwmon: (aspeed-pwm-tacho) Deassert reset in probe The ASPEED SoC must deassert a reset in order to use the PWM/tach peripheral. Signed-off-by: Joel Stanley Signed-off-by: Guenter Roeck --- drivers/hwmon/aspeed-pwm-tacho.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/drivers/hwmon/aspeed-pwm-tacho.c b/drivers/hwmon/aspeed-pwm-tacho.c index 63a95e23ca81..693a3d53cab5 100644 --- a/drivers/hwmon/aspeed-pwm-tacho.c +++ b/drivers/hwmon/aspeed-pwm-tacho.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -181,6 +182,7 @@ struct aspeed_cooling_device { struct aspeed_pwm_tacho_data { struct regmap *regmap; + struct reset_control *rst; unsigned long clk_freq; bool pwm_present[8]; bool fan_tach_present[16]; @@ -905,6 +907,13 @@ static int aspeed_create_fan(struct device *dev, return 0; } +static void aspeed_pwm_tacho_remove(void *data) +{ + struct aspeed_pwm_tacho_data *priv = data; + + reset_control_assert(priv->rst); +} + static int aspeed_pwm_tacho_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -931,6 +940,19 @@ static int aspeed_pwm_tacho_probe(struct platform_device *pdev) &aspeed_pwm_tacho_regmap_config); if (IS_ERR(priv->regmap)) return PTR_ERR(priv->regmap); + + priv->rst = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(priv->rst)) { + dev_err(dev, + "missing or invalid reset controller device tree entry"); + return PTR_ERR(priv->rst); + } + reset_control_deassert(priv->rst); + + ret = devm_add_action_or_reset(dev, aspeed_pwm_tacho_remove, priv); + if (ret) + return ret; + regmap_write(priv->regmap, ASPEED_PTCR_TACH_SOURCE, 0); regmap_write(priv->regmap, ASPEED_PTCR_TACH_SOURCE_EXT, 0); From 4471879acf926221231fe68ebc30f9cad0d7f7c8 Mon Sep 17 00:00:00 2001 From: Eddie James Date: Mon, 8 Jan 2018 15:10:09 -0600 Subject: [PATCH 22/32] hwmon: (pmbus) cffps: Add PMBUS_SKIP_STATUS_CHECK This power supply device regularly fails to read VOUT_MODE due to the CML bit going high. This results in an incorrect exponent used for the voltage data, and therefore the power supply reports incorrect voltage. Work around this by setting the pmbus flag to skip the CML check. Signed-off-by: Eddie James Fixes: f69316d62c70 ("hwmon: (pmbus) Add IBM Common Form Factor (CFF) ...") Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/ibm-cffps.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/hwmon/pmbus/ibm-cffps.c b/drivers/hwmon/pmbus/ibm-cffps.c index de2547476253..2d6f4f41e736 100644 --- a/drivers/hwmon/pmbus/ibm-cffps.c +++ b/drivers/hwmon/pmbus/ibm-cffps.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "pmbus.h" @@ -268,6 +269,10 @@ static struct pmbus_driver_info ibm_cffps_info = { .read_word_data = ibm_cffps_read_word_data, }; +static struct pmbus_platform_data ibm_cffps_pdata = { + .flags = PMBUS_SKIP_STATUS_CHECK, +}; + static int ibm_cffps_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -276,6 +281,7 @@ static int ibm_cffps_probe(struct i2c_client *client, struct dentry *ibm_cffps_dir; struct ibm_cffps *psu; + client->dev.platform_data = &ibm_cffps_pdata; rc = pmbus_do_probe(client, id, &ibm_cffps_info); if (rc) return rc; From ef9e1cdf419a37065364edb47bd6dd9aee1bcb7d Mon Sep 17 00:00:00 2001 From: "eajames@linux.vnet.ibm.com" Date: Thu, 11 Jan 2018 11:20:10 -0600 Subject: [PATCH 23/32] hwmon: (pmbus/cffps) Add led class device for power supply fault led This power supply device doesn't correctly manage it's own fault led. Add an led class device and register it so that userspace can manage power supply fault led as necessary. Signed-off-by: Eddie James Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/ibm-cffps.c | 96 ++++++++++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/pmbus/ibm-cffps.c b/drivers/hwmon/pmbus/ibm-cffps.c index 2d6f4f41e736..93d9a9ea112b 100644 --- a/drivers/hwmon/pmbus/ibm-cffps.c +++ b/drivers/hwmon/pmbus/ibm-cffps.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,7 @@ #define CFFPS_CCIN_CMD 0xBD #define CFFPS_FW_CMD_START 0xFA #define CFFPS_FW_NUM_BYTES 4 +#define CFFPS_SYS_CONFIG_CMD 0xDA #define CFFPS_INPUT_HISTORY_CMD 0xD6 #define CFFPS_INPUT_HISTORY_SIZE 100 @@ -39,6 +41,11 @@ #define CFFPS_MFR_VAUX_FAULT BIT(6) #define CFFPS_MFR_CURRENT_SHARE_WARNING BIT(7) +#define CFFPS_LED_BLINK BIT(0) +#define CFFPS_LED_ON BIT(1) +#define CFFPS_LED_OFF BIT(2) +#define CFFPS_BLINK_RATE_MS 250 + enum { CFFPS_DEBUGFS_INPUT_HISTORY = 0, CFFPS_DEBUGFS_FRU, @@ -63,6 +70,10 @@ struct ibm_cffps { struct ibm_cffps_input_history input_history; int debugfs_entries[CFFPS_DEBUGFS_NUM_ENTRIES]; + + char led_name[32]; + u8 led_state; + struct led_classdev led; }; #define to_psu(x, y) container_of((x), struct ibm_cffps, debugfs_entries[(y)]) @@ -258,6 +269,69 @@ static int ibm_cffps_read_word_data(struct i2c_client *client, int page, return rc; } +static void ibm_cffps_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + int rc; + struct ibm_cffps *psu = container_of(led_cdev, struct ibm_cffps, led); + + if (brightness == LED_OFF) { + psu->led_state = CFFPS_LED_OFF; + } else { + brightness = LED_FULL; + if (psu->led_state != CFFPS_LED_BLINK) + psu->led_state = CFFPS_LED_ON; + } + + rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD, + psu->led_state); + if (rc < 0) + return; + + led_cdev->brightness = brightness; +} + +static int ibm_cffps_led_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + int rc; + struct ibm_cffps *psu = container_of(led_cdev, struct ibm_cffps, led); + + psu->led_state = CFFPS_LED_BLINK; + + if (led_cdev->brightness == LED_OFF) + return 0; + + rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD, + CFFPS_LED_BLINK); + if (rc < 0) + return rc; + + *delay_on = CFFPS_BLINK_RATE_MS; + *delay_off = CFFPS_BLINK_RATE_MS; + + return 0; +} + +static void ibm_cffps_create_led_class(struct ibm_cffps *psu) +{ + int rc; + struct i2c_client *client = psu->client; + struct device *dev = &client->dev; + + snprintf(psu->led_name, sizeof(psu->led_name), "%s-%02x", client->name, + client->addr); + psu->led.name = psu->led_name; + psu->led.max_brightness = LED_FULL; + psu->led.brightness_set = ibm_cffps_led_brightness_set; + psu->led.blink_set = ibm_cffps_led_blink_set; + + rc = devm_led_classdev_register(dev, &psu->led); + if (rc) + dev_warn(dev, "failed to register led class: %d\n", rc); +} + static struct pmbus_driver_info ibm_cffps_info = { .pages = 1, .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | @@ -286,6 +360,20 @@ static int ibm_cffps_probe(struct i2c_client *client, if (rc) return rc; + /* + * Don't fail the probe if there isn't enough memory for leds and + * debugfs. + */ + psu = devm_kzalloc(&client->dev, sizeof(*psu), GFP_KERNEL); + if (!psu) + return 0; + + psu->client = client; + mutex_init(&psu->input_history.update_lock); + psu->input_history.last_update = jiffies - HZ; + + ibm_cffps_create_led_class(psu); + /* Don't fail the probe if we can't create debugfs */ debugfs = pmbus_get_debugfs_dir(client); if (!debugfs) @@ -295,14 +383,6 @@ static int ibm_cffps_probe(struct i2c_client *client, if (!ibm_cffps_dir) return 0; - psu = devm_kzalloc(&client->dev, sizeof(*psu), GFP_KERNEL); - if (!psu) - return 0; - - psu->client = client; - mutex_init(&psu->input_history.update_lock); - psu->input_history.last_update = jiffies - HZ; - for (i = 0; i < CFFPS_DEBUGFS_NUM_ENTRIES; ++i) psu->debugfs_entries[i] = i; From b0076406495cd5905284d1f235db77d8ab54951e Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 12 Jan 2018 16:49:00 +0100 Subject: [PATCH 24/32] hwmon: (pmbus/ibm-cffps) Add dependency on LEDS_CLASS Building without CONFIG_LEDS_CLASS causes a link failure: drivers/hwmon/pmbus/ibm-cffps.o: In function `ibm_cffps_probe': ibm-cffps.c:(.text+0x4f4): undefined reference to `devm_of_led_classdev_register' This adds the required dependency. Fixes: ef9e1cdf419a ("hwmon: (pmbus/cffps) Add led class device ...") Signed-off-by: Arnd Bergmann Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 08479006c7f9..6e4298e99222 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -39,6 +39,7 @@ config SENSORS_ADM1275 config SENSORS_IBM_CFFPS tristate "IBM Common Form Factor Power Supply" + depends on LEDS_CLASS help If you say yes here you get hardware monitoring support for the IBM Common Form Factor power supply. From abe6c3b06fa80777e321add05345c799a74e624a Mon Sep 17 00:00:00 2001 From: Emiliano Ingrassia Date: Fri, 12 Jan 2018 21:42:28 +0100 Subject: [PATCH 25/32] hwmon: (sht3x) wait predefined limits loading complete before access An sht3x sensor include limits register which contains temperature and humidity limit values. After a reset, pre-defined values are loaded into that register. During the probe function, the driver reads the limits register. However, if the reads are made too early, and the bus is clocked at high frequencies (e.g. 100 kHz or more), the loading could be not completed and the sensor returns a NACK which causes the probe to fail. A delay of at least 500 us before the first read solves this issue. Signed-off-by: Emiliano Ingrassia Signed-off-by: Guenter Roeck --- drivers/hwmon/sht3x.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/hwmon/sht3x.c b/drivers/hwmon/sht3x.c index 6ea99cd6ae79..370b57dafab7 100644 --- a/drivers/hwmon/sht3x.c +++ b/drivers/hwmon/sht3x.c @@ -732,6 +732,13 @@ static int sht3x_probe(struct i2c_client *client, mutex_init(&data->i2c_lock); mutex_init(&data->data_lock); + /* + * An attempt to read limits register too early + * causes a NACK response from the chip. + * Waiting for an empirical delay of 500 us solves the issue. + */ + usleep_range(500, 600); + ret = limits_update(data); if (ret) return ret; From 08f411bcb5fbd96c53d4535e0526f70b971ee5d5 Mon Sep 17 00:00:00 2001 From: Samuel Mendoza-Jonas Date: Mon, 15 Jan 2018 16:58:58 +1100 Subject: [PATCH 26/32] hwmon: (pmbus/ir35221) Remove unnecessary scaling The ir35221 datasheet describes specific scaling factors for a number of commands which the current driver applies when reading. However now that the ir35221 has been tested on machines with more easily verifiable readings these descriptions have turned out to be superfluous and reading each command according to the linear format is sufficient. Signed-off-by: Samuel Mendoza-Jonas Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/ir35221.c | 189 ---------------------------------- 1 file changed, 189 deletions(-) diff --git a/drivers/hwmon/pmbus/ir35221.c b/drivers/hwmon/pmbus/ir35221.c index 8b906b44484b..977315b0fd90 100644 --- a/drivers/hwmon/pmbus/ir35221.c +++ b/drivers/hwmon/pmbus/ir35221.c @@ -25,168 +25,19 @@ #define IR35221_MFR_IOUT_VALLEY 0xcb #define IR35221_MFR_TEMP_VALLEY 0xcc -static long ir35221_reg2data(int data, enum pmbus_sensor_classes class) -{ - s16 exponent; - s32 mantissa; - long val; - - /* We only modify LINEAR11 formats */ - exponent = ((s16)data) >> 11; - mantissa = ((s16)((data & 0x7ff) << 5)) >> 5; - - val = mantissa * 1000L; - - /* scale result to micro-units for power sensors */ - if (class == PSC_POWER) - val = val * 1000L; - - if (exponent >= 0) - val <<= exponent; - else - val >>= -exponent; - - return val; -} - -#define MAX_MANTISSA (1023 * 1000) -#define MIN_MANTISSA (511 * 1000) - -static u16 ir35221_data2reg(long val, enum pmbus_sensor_classes class) -{ - s16 exponent = 0, mantissa; - bool negative = false; - - if (val == 0) - return 0; - - if (val < 0) { - negative = true; - val = -val; - } - - /* Power is in uW. Convert to mW before converting. */ - if (class == PSC_POWER) - val = DIV_ROUND_CLOSEST(val, 1000L); - - /* Reduce large mantissa until it fits into 10 bit */ - while (val >= MAX_MANTISSA && exponent < 15) { - exponent++; - val >>= 1; - } - /* Increase small mantissa to improve precision */ - while (val < MIN_MANTISSA && exponent > -15) { - exponent--; - val <<= 1; - } - - /* Convert mantissa from milli-units to units */ - mantissa = DIV_ROUND_CLOSEST(val, 1000); - - /* Ensure that resulting number is within range */ - if (mantissa > 0x3ff) - mantissa = 0x3ff; - - /* restore sign */ - if (negative) - mantissa = -mantissa; - - /* Convert to 5 bit exponent, 11 bit mantissa */ - return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800); -} - -static u16 ir35221_scale_result(s16 data, int shift, - enum pmbus_sensor_classes class) -{ - long val; - - val = ir35221_reg2data(data, class); - - if (shift < 0) - val >>= -shift; - else - val <<= shift; - - return ir35221_data2reg(val, class); -} - static int ir35221_read_word_data(struct i2c_client *client, int page, int reg) { int ret; switch (reg) { - case PMBUS_IOUT_OC_FAULT_LIMIT: - case PMBUS_IOUT_OC_WARN_LIMIT: - ret = pmbus_read_word_data(client, page, reg); - if (ret < 0) - break; - ret = ir35221_scale_result(ret, 1, PSC_CURRENT_OUT); - break; - case PMBUS_VIN_OV_FAULT_LIMIT: - case PMBUS_VIN_OV_WARN_LIMIT: - case PMBUS_VIN_UV_WARN_LIMIT: - ret = pmbus_read_word_data(client, page, reg); - ret = ir35221_scale_result(ret, -4, PSC_VOLTAGE_IN); - break; - case PMBUS_IIN_OC_WARN_LIMIT: - ret = pmbus_read_word_data(client, page, reg); - if (ret < 0) - break; - ret = ir35221_scale_result(ret, -1, PSC_CURRENT_IN); - break; - case PMBUS_READ_VIN: - ret = pmbus_read_word_data(client, page, PMBUS_READ_VIN); - if (ret < 0) - break; - ret = ir35221_scale_result(ret, -5, PSC_VOLTAGE_IN); - break; - case PMBUS_READ_IIN: - ret = pmbus_read_word_data(client, page, PMBUS_READ_IIN); - if (ret < 0) - break; - if (page == 0) - ret = ir35221_scale_result(ret, -4, PSC_CURRENT_IN); - else - ret = ir35221_scale_result(ret, -5, PSC_CURRENT_IN); - break; - case PMBUS_READ_POUT: - ret = pmbus_read_word_data(client, page, PMBUS_READ_POUT); - if (ret < 0) - break; - ret = ir35221_scale_result(ret, -1, PSC_POWER); - break; - case PMBUS_READ_PIN: - ret = pmbus_read_word_data(client, page, PMBUS_READ_PIN); - if (ret < 0) - break; - ret = ir35221_scale_result(ret, -1, PSC_POWER); - break; - case PMBUS_READ_IOUT: - ret = pmbus_read_word_data(client, page, PMBUS_READ_IOUT); - if (ret < 0) - break; - if (page == 0) - ret = ir35221_scale_result(ret, -1, PSC_CURRENT_OUT); - else - ret = ir35221_scale_result(ret, -2, PSC_CURRENT_OUT); - break; case PMBUS_VIRT_READ_VIN_MAX: ret = pmbus_read_word_data(client, page, IR35221_MFR_VIN_PEAK); - if (ret < 0) - break; - ret = ir35221_scale_result(ret, -5, PSC_VOLTAGE_IN); break; case PMBUS_VIRT_READ_VOUT_MAX: ret = pmbus_read_word_data(client, page, IR35221_MFR_VOUT_PEAK); break; case PMBUS_VIRT_READ_IOUT_MAX: ret = pmbus_read_word_data(client, page, IR35221_MFR_IOUT_PEAK); - if (ret < 0) - break; - if (page == 0) - ret = ir35221_scale_result(ret, -1, PSC_CURRENT_IN); - else - ret = ir35221_scale_result(ret, -2, PSC_CURRENT_IN); break; case PMBUS_VIRT_READ_TEMP_MAX: ret = pmbus_read_word_data(client, page, IR35221_MFR_TEMP_PEAK); @@ -194,9 +45,6 @@ static int ir35221_read_word_data(struct i2c_client *client, int page, int reg) case PMBUS_VIRT_READ_VIN_MIN: ret = pmbus_read_word_data(client, page, IR35221_MFR_VIN_VALLEY); - if (ret < 0) - break; - ret = ir35221_scale_result(ret, -5, PSC_VOLTAGE_IN); break; case PMBUS_VIRT_READ_VOUT_MIN: ret = pmbus_read_word_data(client, page, @@ -205,12 +53,6 @@ static int ir35221_read_word_data(struct i2c_client *client, int page, int reg) case PMBUS_VIRT_READ_IOUT_MIN: ret = pmbus_read_word_data(client, page, IR35221_MFR_IOUT_VALLEY); - if (ret < 0) - break; - if (page == 0) - ret = ir35221_scale_result(ret, -1, PSC_CURRENT_IN); - else - ret = ir35221_scale_result(ret, -2, PSC_CURRENT_IN); break; case PMBUS_VIRT_READ_TEMP_MIN: ret = pmbus_read_word_data(client, page, @@ -224,36 +66,6 @@ static int ir35221_read_word_data(struct i2c_client *client, int page, int reg) return ret; } -static int ir35221_write_word_data(struct i2c_client *client, int page, int reg, - u16 word) -{ - int ret; - u16 val; - - switch (reg) { - case PMBUS_IOUT_OC_FAULT_LIMIT: - case PMBUS_IOUT_OC_WARN_LIMIT: - val = ir35221_scale_result(word, -1, PSC_CURRENT_OUT); - ret = pmbus_write_word_data(client, page, reg, val); - break; - case PMBUS_VIN_OV_FAULT_LIMIT: - case PMBUS_VIN_OV_WARN_LIMIT: - case PMBUS_VIN_UV_WARN_LIMIT: - val = ir35221_scale_result(word, 4, PSC_VOLTAGE_IN); - ret = pmbus_write_word_data(client, page, reg, val); - break; - case PMBUS_IIN_OC_WARN_LIMIT: - val = ir35221_scale_result(word, 1, PSC_CURRENT_IN); - ret = pmbus_write_word_data(client, page, reg, val); - break; - default: - ret = -ENODATA; - break; - } - - return ret; -} - static int ir35221_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -292,7 +104,6 @@ static int ir35221_probe(struct i2c_client *client, if (!info) return -ENOMEM; - info->write_word_data = ir35221_write_word_data; info->read_word_data = ir35221_read_word_data; info->pages = 2; From 0c4c5860e9983eb3da7a3d73ca987643c3ed034b Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Mon, 15 Jan 2018 14:58:21 +0100 Subject: [PATCH 27/32] hwmon: (ina2xx) Fix access to uninitialized mutex Initialize data->config_lock mutex before it is used by the driver code. This fixes following warning on Odroid XU3 boards: INFO: trying to register non-static key. the code is fine but needs lockdep annotation. turning off the locking correctness validator. CPU: 5 PID: 1 Comm: swapper/0 Not tainted 4.15.0-rc7-next-20180115-00001-gb75575dee3f2 #107 Hardware name: SAMSUNG EXYNOS (Flattened Device Tree) [] (unwind_backtrace) from [] (show_stack+0x10/0x14) [] (show_stack) from [] (dump_stack+0x90/0xc8) [] (dump_stack) from [] (register_lock_class+0x1c0/0x59c) [] (register_lock_class) from [] (__lock_acquire+0x78/0x1850) [] (__lock_acquire) from [] (lock_acquire+0xc8/0x2b8) [] (lock_acquire) from [] (__mutex_lock+0x60/0xa0c) [] (__mutex_lock) from [] (mutex_lock_nested+0x1c/0x24) [] (mutex_lock_nested) from [] (ina2xx_set_shunt+0x70/0xb0) [] (ina2xx_set_shunt) from [] (ina2xx_probe+0x88/0x1b0) [] (ina2xx_probe) from [] (i2c_device_probe+0x1e0/0x2d0) [] (i2c_device_probe) from [] (driver_probe_device+0x2b8/0x4a0) [] (driver_probe_device) from [] (__driver_attach+0xfc/0x120) [] (__driver_attach) from [] (bus_for_each_dev+0x58/0x7c) [] (bus_for_each_dev) from [] (bus_add_driver+0x174/0x250) [] (bus_add_driver) from [] (driver_register+0x78/0xf4) [] (driver_register) from [] (i2c_register_driver+0x38/0xa8) [] (i2c_register_driver) from [] (do_one_initcall+0x48/0x18c) [] (do_one_initcall) from [] (kernel_init_freeable+0x110/0x1d4) [] (kernel_init_freeable) from [] (kernel_init+0x8/0x114) [] (kernel_init) from [] (ret_from_fork+0x14/0x20) Fixes: 5d389b125186 ("hwmon: (ina2xx) Make calibration register value fixed") Signed-off-by: Marek Szyprowski Signed-off-by: Guenter Roeck --- drivers/hwmon/ina2xx.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c index e362a932fe8c..e9e6aeabbf84 100644 --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -454,6 +454,7 @@ static int ina2xx_probe(struct i2c_client *client, /* set the device type */ data->config = &ina2xx_config[chip]; + mutex_init(&data->config_lock); if (of_property_read_u32(dev->of_node, "shunt-resistor", &val) < 0) { struct ina2xx_platform_data *pdata = dev_get_platdata(dev); @@ -480,8 +481,6 @@ static int ina2xx_probe(struct i2c_client *client, return -ENODEV; } - mutex_init(&data->config_lock); - data->groups[group++] = &ina2xx_group; if (id->driver_data == ina226) data->groups[group++] = &ina226_group; From ccffe776700343fbd127c8624d9962ebea1bfbb7 Mon Sep 17 00:00:00 2001 From: Luis de Bethencourt Date: Wed, 17 Jan 2018 18:24:48 +0000 Subject: [PATCH 28/32] hwmon: (lm75) Fix trailing semicolon The trailing semicolon is an empty statement that does no operation. Removing it since it doesn't do anything. Signed-off-by: Luis de Bethencourt Signed-off-by: Guenter Roeck --- drivers/hwmon/lm75.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c index 005ffb5ffa92..49f4b33a5685 100644 --- a/drivers/hwmon/lm75.c +++ b/drivers/hwmon/lm75.c @@ -100,7 +100,7 @@ static int lm75_read(struct device *dev, enum hwmon_sensor_types type, switch (attr) { case hwmon_chip_update_interval: *val = data->sample_time; - break;; + break; default: return -EINVAL; } From 6509614fdd2d05c6926d50901a45d5dfb852b715 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 19 Jan 2018 06:38:03 -0800 Subject: [PATCH 29/32] hwmon: (k10temp) Add temperature offset for Ryzen 1900X Like the other CPUs from the same series, the 1900X has a temperature offset of 27 degrees C. Signed-off-by: Guenter Roeck --- drivers/hwmon/k10temp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index 0721e175664a..06b4e1c78bd8 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -86,6 +86,7 @@ static const struct tctl_offset tctl_offset_table[] = { { 0x17, "AMD Ryzen 7 1800X", 20000 }, { 0x17, "AMD Ryzen Threadripper 1950X", 27000 }, { 0x17, "AMD Ryzen Threadripper 1920X", 27000 }, + { 0x17, "AMD Ryzen Threadripper 1900X", 27000 }, { 0x17, "AMD Ryzen Threadripper 1950", 10000 }, { 0x17, "AMD Ryzen Threadripper 1920", 10000 }, { 0x17, "AMD Ryzen Threadripper 1910", 10000 }, From 836ad11235d04f7a6fcd41b1abceaeacc70153c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Sat, 27 Jan 2018 17:22:01 +0100 Subject: [PATCH 30/32] hwmon: (dell-smm) Enable broken functionality via "force" module param MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some Dell machines are broken and some functionality is disabled. Show warning into dmesg about this fact and allow user via "force" module param to override brokenness and enable broken functionality. Signed-off-by: Pali Rohár Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index c7c9e95e58a8..aef4f8317ae2 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -1060,8 +1060,11 @@ static int __init i8k_probe(void) i8k_get_dmi_data(DMI_BIOS_VERSION)); } - if (dmi_check_system(i8k_blacklist_fan_type_dmi_table)) - disallow_fan_type_call = true; + if (dmi_check_system(i8k_blacklist_fan_type_dmi_table)) { + pr_warn("broken Dell BIOS detected, disallow fan type call\n"); + if (!force) + disallow_fan_type_call = true; + } strlcpy(bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION), sizeof(bios_version)); From f480ea90b9dd7dfcf9031c82abc294f86d7db435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Sat, 27 Jan 2018 17:28:34 +0100 Subject: [PATCH 31/32] hwmon: (dell-smm) Disable fan support for Dell Inspiron 7720 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Calling fan related SMM functions implemented by Dell BIOS firmware on Dell Inspiron 7720 freeze kernel for about 500ms. Until Dell fixes it we need to disable fan support for Dell Inspiron 7720 as it makes system unusable. Via "force" module param fan support can be enabled. Reported-by: vova7890@mail.ru Signed-off-by: Pali Rohár Link: https://bugzilla.kernel.org/show_bug.cgi?id=195751 Cc: stable@vger.kernel.org # v4.0+, will need backport Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 40 +++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index aef4f8317ae2..3f8b4e482b64 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -76,6 +76,7 @@ static uint i8k_fan_mult = I8K_FAN_MULT; static uint i8k_pwm_mult; static uint i8k_fan_max = I8K_FAN_HIGH; static bool disallow_fan_type_call; +static bool disallow_fan_support; #define I8K_HWMON_HAVE_TEMP1 (1 << 0) #define I8K_HWMON_HAVE_TEMP2 (1 << 1) @@ -242,6 +243,9 @@ static int i8k_get_fan_status(int fan) { struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, }; + if (disallow_fan_support) + return -EINVAL; + regs.ebx = fan & 0xff; return i8k_smm(®s) ? : regs.eax & 0xff; } @@ -253,6 +257,9 @@ static int i8k_get_fan_speed(int fan) { struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, }; + if (disallow_fan_support) + return -EINVAL; + regs.ebx = fan & 0xff; return i8k_smm(®s) ? : (regs.eax & 0xffff) * i8k_fan_mult; } @@ -264,7 +271,7 @@ static int _i8k_get_fan_type(int fan) { struct smm_regs regs = { .eax = I8K_SMM_GET_FAN_TYPE, }; - if (disallow_fan_type_call) + if (disallow_fan_support || disallow_fan_type_call) return -EINVAL; regs.ebx = fan & 0xff; @@ -289,6 +296,9 @@ static int i8k_get_fan_nominal_speed(int fan, int speed) { struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, }; + if (disallow_fan_support) + return -EINVAL; + regs.ebx = (fan & 0xff) | (speed << 8); return i8k_smm(®s) ? : (regs.eax & 0xffff) * i8k_fan_mult; } @@ -300,6 +310,9 @@ static int i8k_set_fan(int fan, int speed) { struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, }; + if (disallow_fan_support) + return -EINVAL; + speed = (speed < 0) ? 0 : ((speed > i8k_fan_max) ? i8k_fan_max : speed); regs.ebx = (fan & 0xff) | (speed << 8); @@ -772,6 +785,8 @@ static struct attribute *i8k_attrs[] = { static umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr, int index) { + if (disallow_fan_support && index >= 8) + return 0; if (disallow_fan_type_call && (index == 9 || index == 12 || index == 15)) return 0; @@ -1038,6 +1053,23 @@ static const struct dmi_system_id i8k_blacklist_fan_type_dmi_table[] __initconst { } }; +/* + * On some machines all fan related SMM functions implemented by Dell BIOS + * firmware freeze kernel for about 500ms. Until Dell fixes these problems fan + * support for affected blacklisted Dell machines stay disabled. + * See bug: https://bugzilla.kernel.org/show_bug.cgi?id=195751 + */ +static struct dmi_system_id i8k_blacklist_fan_support_dmi_table[] __initdata = { + { + .ident = "Dell Inspiron 7720", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 7720"), + }, + }, + { } +}; + /* * Probe for the presence of a supported laptop. */ @@ -1060,6 +1092,12 @@ static int __init i8k_probe(void) i8k_get_dmi_data(DMI_BIOS_VERSION)); } + if (dmi_check_system(i8k_blacklist_fan_support_dmi_table)) { + pr_warn("broken Dell BIOS detected, disallow fan support\n"); + if (!force) + disallow_fan_support = true; + } + if (dmi_check_system(i8k_blacklist_fan_type_dmi_table)) { pr_warn("broken Dell BIOS detected, disallow fan type call\n"); if (!force) From 6fbc4232a5ac944531bda397f3644d1cf66bdd13 Mon Sep 17 00:00:00 2001 From: Oleksandr Natalenko Date: Sat, 27 Jan 2018 17:23:29 +0100 Subject: [PATCH 32/32] hwmon: (dell-smm) Disable fan support for Dell Vostro 3360 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Calling fan related SMM functions implemented by Dell BIOS firmware on Dell Vostro 3360 freeze kernel for about 500ms. Unfortunately, it is unlikely for Dell to fix this since the machine is pretty old, so this commit just disables fan support to make the system usable again. Via "force" module param fan support can be enabled. Link: https://bugzilla.kernel.org/show_bug.cgi?id=195751 Link: http://lkml.iu.edu/hypermail/linux/kernel/1711.2/06083.html Reviewed-by: Pali Rohár Signed-off-by: Oleksandr Natalenko Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 3f8b4e482b64..bf3bb7e1adab 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -1067,6 +1067,13 @@ static struct dmi_system_id i8k_blacklist_fan_support_dmi_table[] __initdata = { DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 7720"), }, }, + { + .ident = "Dell Vostro 3360", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Vostro 3360"), + }, + }, { } };