From ee249f271524d111aed8d6e7c61e220aa6b4d714 Mon Sep 17 00:00:00 2001 From: Lei YU Date: Mon, 13 Nov 2017 11:27:33 +0800 Subject: 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"); -- cgit v1.2.3 From ba3d8588442dbf954ff4aa77fbb16bfecc7e5b98 Mon Sep 17 00:00:00 2001 From: Lei YU Date: Mon, 13 Nov 2017 11:27:34 +0800 Subject: 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 -- cgit v1.2.3 From 6335d98abd942bfb28d908f712b7c7d73aaea871 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 19 Nov 2017 09:14:50 -0800 Subject: 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. -- cgit v1.2.3 From b9ccff233e5eb1ae6adc831c0aa1f456d0cbc5cb Mon Sep 17 00:00:00 2001 From: Sinan Kaya Date: Wed, 22 Nov 2017 00:30:56 -0500 Subject: 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. -- cgit v1.2.3 From 666c14906b496f148e437404283f6a6a84cee719 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 28 Nov 2017 18:33:06 -0800 Subject: 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}, -- cgit v1.2.3 From d206636e7697f47332774f29b90b92f6047d265d Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Mon, 20 Nov 2017 15:12:03 +1030 Subject: 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. -- cgit v1.2.3 From 56ad86b4b16e4b7154300d71f8e93cca64b98e92 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Mon, 20 Nov 2017 15:12:04 +1030 Subject: 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, -- cgit v1.2.3 From 464df6fa3766784b85b00d56cd4d7c706aee5375 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Mon, 20 Nov 2017 15:12:05 +1030 Subject: 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) + return 0; - if (page >= 0 && page != data->currpage) { + 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); -- cgit v1.2.3 From cf583b4275761754638c946ff777546d1a9b6744 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Mon, 20 Nov 2017 15:12:06 +1030 Subject: 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); } -- cgit v1.2.3 From 5d389b125186cf254ad5b8015763ac07c151aea4 Mon Sep 17 00:00:00 2001 From: Maciej Purski Date: Wed, 22 Nov 2017 16:32:15 +0100 Subject: 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; -- cgit v1.2.3 From 98b16a09861aa85d68853728515738b19a7cd8d4 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Thu, 30 Nov 2017 19:12:57 +0100 Subject: 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, -- cgit v1.2.3 From 3870945aeb0e94126b9ea1cbd4f2f50d99eb948d Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 3 Dec 2017 15:15:00 -0800 Subject: 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_regis