diff options
Diffstat (limited to 'drivers/hwmon')
61 files changed, 7276 insertions, 1671 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 64bd3dfba2c4..9ab4e9b3d27b 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -174,6 +174,7 @@ config SENSORS_ADM9240 config SENSORS_ADT7X10 tristate + select REGMAP help This module contains common code shared by the ADT7310/ADT7320 and ADT7410/ADT7420 temperature monitoring chip drivers. @@ -505,6 +506,21 @@ config SENSORS_DELL_SMM When option I8K is also enabled this driver provides legacy /proc/i8k userspace interface for i8kutils package. +config I8K + bool "Legacy /proc/i8k interface of Dell laptop SMM BIOS hwmon driver" + depends on SENSORS_DELL_SMM + depends on PROC_FS + help + This option enables the legacy /proc/i8k userspace interface of the + dell-smm-hwmon driver. The character file /proc/i8k exposes the BIOS + version, temperatures and allows control of fan speeds of some Dell + laptops. Sometimes it also reports power and hotkey status. + + This interface is required to run programs from the i8kutils package. + + Say Y if you intend to run userspace programs that use this interface. + Say N otherwise. + config SENSORS_DA9052_ADC tristate "Dialog DA9052/DA9053 ADC" depends on PMIC_DA9052 @@ -1208,8 +1224,8 @@ config SENSORS_LM70 depends on SPI_MASTER help If you say yes here you get support for the National Semiconductor - LM70, LM71, LM74 and Texas Instruments TMP121/TMP123 digital tempera- - ture sensor chips. + LM70, LM71, LM74 and Texas Instruments TMP121/TMP123, TMP122/TMP124, + TMP125 digital temperature sensor chips. This driver can also be built as a module. If so, the module will be called lm70. @@ -1288,6 +1304,7 @@ config SENSORS_LM80 config SENSORS_LM83 tristate "National Semiconductor LM83 and compatibles" depends on I2C + select REGMAP help If you say yes here you get support for National Semiconductor LM82 and LM83 sensor chips. @@ -1414,8 +1431,8 @@ config SENSORS_PC87427 will be called pc87427. config SENSORS_NTC_THERMISTOR - tristate "NTC thermistor support from Murata" - depends on !OF || IIO=n || IIO + tristate "NTC thermistor support" + depends on IIO depends on THERMAL || !THERMAL_OF help This driver supports NTC thermistors sensor reading and its @@ -1513,6 +1530,16 @@ config SENSORS_NZXT_KRAKEN2 This driver can also be built as a module. If so, the module will be called nzxt-kraken2. +config SENSORS_NZXT_SMART2 + tristate "NZXT RGB & Fan Controller/Smart Device v2" + depends on USB_HID + help + If you say yes here you get support for hardware monitoring for the + NZXT RGB & Fan Controller/Smart Device v2. + + This driver can also be built as a module. If so, the module + will be called nzxt-smart2. + source "drivers/hwmon/occ/Kconfig" config SENSORS_PCF8591 @@ -1872,6 +1899,18 @@ config SENSORS_INA2XX This driver can also be built as a module. If so, the module will be called ina2xx. +config SENSORS_INA238 + tristate "Texas Instruments INA238" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for the INA238 power monitor + chip. This driver supports voltage, current, power and temperature + measurements as well as alarm configuration. + + This driver can also be built as a module. If so, the module + will be called ina238. + config SENSORS_INA3221 tristate "Texas Instruments INA3221 Triple Power Monitor" depends on I2C @@ -1939,6 +1978,7 @@ config SENSORS_TMP108 config SENSORS_TMP401 tristate "Texas Instruments TMP401 and compatibles" depends on I2C + select REGMAP help If you say yes here you get support for Texas Instruments TMP401, TMP411, TMP431, TMP432, and TMP435 temperature sensor chips. @@ -1956,6 +1996,17 @@ config SENSORS_TMP421 This driver can also be built as a module. If so, the module will be called tmp421. +config SENSORS_TMP464 + tristate "Texas Instruments TMP464 and compatible" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for Texas Instruments TMP464 + and TMP468 temperature sensor chips. + + This driver can also be built as a module. If so, the module + will be called tmp464. + config SENSORS_TMP513 tristate "Texas Instruments TMP513 and compatibles" depends on I2C @@ -2215,6 +2266,45 @@ config SENSORS_ATK0110 This driver can also be built as a module. If so, the module will be called asus_atk0110. +config SENSORS_ASUS_WMI + tristate "ASUS WMI X370/X470/B450/X399" + depends on ACPI_WMI + help + If you say yes here you get support for the ACPI hardware monitoring + interface found in X370/X470/B450/X399 ASUS motherboards. This driver + will provide readings of fans, voltages and temperatures through the system + firmware. + + This driver can also be built as a module. If so, the module + will be called asus_wmi_sensors. + +config SENSORS_ASUS_WMI_EC + tristate "ASUS WMI B550/X570" + depends on ACPI_WMI && SENSORS_ASUS_EC=n + help + If you say yes here you get support for the ACPI embedded controller + hardware monitoring interface found in B550/X570 ASUS motherboards. + This driver will provide readings of fans, voltages and temperatures + through the system firmware. + + This driver is deprecated in favor of the ASUS EC Sensors driver + which provides fully compatible output. + + This driver can also be built as a module. If so, the module + will be called asus_wmi_sensors_ec. + +config SENSORS_ASUS_EC + tristate "ASUS EC Sensors" + depends on X86 + help + If you say yes here you get support for the ACPI embedded controller + hardware monitoring interface found in ASUS motherboards. The driver + currently supports B550/X570 boards, although other ASUS boards might + provide this monitoring interface as well. + + This driver can also be built as a module. If so, the module + will be called asus_ec_sensors. + endif # ACPI endif # HWMON diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index baee6a8d4dd1..4ed138d0621f 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -9,6 +9,9 @@ obj-$(CONFIG_HWMON_VID) += hwmon-vid.o # APCI drivers obj-$(CONFIG_SENSORS_ACPI_POWER) += acpi_power_meter.o obj-$(CONFIG_SENSORS_ATK0110) += asus_atk0110.o +obj-$(CONFIG_SENSORS_ASUS_EC) += asus-ec-sensors.o +obj-$(CONFIG_SENSORS_ASUS_WMI) += asus_wmi_sensors.o +obj-$(CONFIG_SENSORS_ASUS_WMI_EC) += asus_wmi_ec_sensors.o # Native drivers # asb100, then w83781d go first, as they can override other drivers' addresses. @@ -90,6 +93,7 @@ obj-$(CONFIG_SENSORS_IBMPOWERNV)+= ibmpowernv.o obj-$(CONFIG_SENSORS_IIO_HWMON) += iio_hwmon.o obj-$(CONFIG_SENSORS_INA209) += ina209.o obj-$(CONFIG_SENSORS_INA2XX) += ina2xx.o +obj-$(CONFIG_SENSORS_INA238) += ina238.o obj-$(CONFIG_SENSORS_INA3221) += ina3221.o obj-$(CONFIG_SENSORS_INTEL_M10_BMC_HWMON) += intel-m10-bmc-hwmon.o obj-$(CONFIG_SENSORS_IT87) += it87.o @@ -157,6 +161,7 @@ obj-$(CONFIG_SENSORS_NPCM7XX) += npcm750-pwm-fan.o obj-$(CONFIG_SENSORS_NSA320) += nsa320-hwmon.o obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o obj-$(CONFIG_SENSORS_NZXT_KRAKEN2) += nzxt-kraken2.o +obj-$(CONFIG_SENSORS_NZXT_SMART2) += nzxt-smart2.o obj-$(CONFIG_SENSORS_PC87360) += pc87360.o obj-$(CONFIG_SENSORS_PC87427) += pc87427.o obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o @@ -190,6 +195,7 @@ obj-$(CONFIG_SENSORS_TMP103) += tmp103.o obj-$(CONFIG_SENSORS_TMP108) += tmp108.o obj-$(CONFIG_SENSORS_TMP401) += tmp401.o obj-$(CONFIG_SENSORS_TMP421) += tmp421.o +obj-$(CONFIG_SENSORS_TMP464) += tmp464.o obj-$(CONFIG_SENSORS_TMP513) += tmp513.o obj-$(CONFIG_SENSORS_VEXPRESS) += vexpress-hwmon.o obj-$(CONFIG_SENSORS_VIA_CPUTEMP)+= via-cputemp.o diff --git a/drivers/hwmon/adcxx.c b/drivers/hwmon/adcxx.c index e5bc5ce09f4e..de37bce24fa6 100644 --- a/drivers/hwmon/adcxx.c +++ b/drivers/hwmon/adcxx.c @@ -194,7 +194,7 @@ out_err: return status; } -static int adcxx_remove(struct spi_device *spi) +static void adcxx_remove(struct spi_device *spi) { struct adcxx *adc = spi_get_drvdata(spi); int i; @@ -205,8 +205,6 @@ static int adcxx_remove(struct spi_device *spi) device_remove_file(&spi->dev, &ad_input[i].dev_attr); mutex_unlock(&adc->lock); - - return 0; } static const struct spi_device_id adcxx_ids[] = { diff --git a/drivers/hwmon/adm1021.c b/drivers/hwmon/adm1021.c index 38b447c6e8cd..91ecfee243bf 100644 --- a/drivers/hwmon/adm1021.c +++ b/drivers/hwmon/adm1021.c @@ -324,7 +324,7 @@ static int adm1021_detect(struct i2c_client *client, { struct i2c_adapter *adapter = client->adapter; const char *type_name; - int conv_rate, status, config, man_id, dev_id; + int reg, conv_rate, status, config, man_id, dev_id; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { pr_debug("detect failed, smbus byte data not supported!\n"); @@ -349,9 +349,19 @@ static int adm1021_detect(struct i2c_client *client, if (man_id < 0 || dev_id < 0) return -ENODEV; - if (man_id == 0x4d && dev_id == 0x01) + if (man_id == 0x4d && dev_id == 0x01) { + /* + * dev_id 0x01 matches MAX6680, MAX6695, MAX6696, and possibly + * others. Read register which is unsupported on MAX1617 but + * exists on all those chips and compare with the dev_id + * register. If it matches, it may be a MAX1617A. + */ + reg = i2c_smbus_read_byte_data(client, + ADM1023_REG_REM_TEMP_PREC); + if (reg != dev_id) + return -ENODEV; type_name = "max1617a"; - else if (man_id == 0x41) { + } else if (man_id == 0x41) { if ((dev_id & 0xF0) == 0x30) type_name = "adm1023"; else if ((dev_id & 0xF0) == 0x00) @@ -395,13 +405,18 @@ static int adm1021_detect(struct i2c_client *client, /* * LM84 Mfr ID is in a different place, - * and it has more unused bits. + * and it has more unused bits. Registers at 0xfe and 0xff + * are undefined and return the most recently read value, + * here the value of the configuration register. */ if (conv_rate == 0x00 + && man_id == config && dev_id == config && (config & 0x7F) == 0x00 && (status & 0xAB) == 0x00) { type_name = "lm84"; } else { + if ((config & 0x3f) || (status & 0x03)) + return -ENODEV; /* fail if low limits are larger than high limits */ if ((s8)llo > lhi || (s8)rlo > rhi) return -ENODEV; diff --git a/drivers/hwmon/adm1031.c b/drivers/hwmon/adm1031.c index 257ec53ae723..ac841fa3a369 100644 --- a/drivers/hwmon/adm1031.c +++ b/drivers/hwmon/adm1031.c @@ -242,9 +242,8 @@ static int FAN_TO_REG(int reg, int div) static int AUTO_TEMP_MAX_TO_REG(int val, int reg, int pwm) { int ret; - int range = val - AUTO_TEMP_MIN_FROM_REG(reg); + int range = ((val - AUTO_TEMP_MIN_FROM_REG(reg)) * 10) / (16 - pwm); - range = ((val - AUTO_TEMP_MIN_FROM_REG(reg))*10)/(16 - pwm); ret = ((reg & 0xf8) | (range < 10000 ? 0 : range < 20000 ? 1 : diff --git a/drivers/hwmon/adt7310.c b/drivers/hwmon/adt7310.c index c40cac16af68..1efc0bdcceab 100644 --- a/drivers/hwmon/adt7310.c +++ b/drivers/hwmon/adt7310.c @@ -8,6 +8,7 @@ #include <linux/module.h> #include <linux/init.h> +#include <linux/regmap.h> #include <linux/spi/spi.h> #include <asm/unaligned.h> @@ -38,16 +39,13 @@ static const u8 adt7310_reg_table[] = { #define AD7310_COMMAND(reg) (adt7310_reg_table[(reg)] << ADT7310_CMD_REG_OFFSET) -static int adt7310_spi_read_word(struct device *dev, u8 reg) +static int adt7310_spi_read_word(struct spi_device *spi, u8 reg) { - struct spi_device *spi = to_spi_device(dev); - return spi_w8r16be(spi, AD7310_COMMAND(reg) | ADT7310_CMD_READ); } -static int adt7310_spi_write_word(struct device *dev, u8 reg, u16 data) +static int adt7310_spi_write_word(struct spi_device *spi, u8 reg, u16 data) { - struct spi_device *spi = to_spi_device(dev); u8 buf[3]; buf[0] = AD7310_COMMAND(reg); @@ -56,17 +54,13 @@ static int adt7310_spi_write_word(struct device *dev, u8 reg, u16 data) return spi_write(spi, buf, sizeof(buf)); } -static int adt7310_spi_read_byte(struct device *dev, u8 reg) +static int adt7310_spi_read_byte(struct spi_device *spi, u8 reg) { - struct spi_device *spi = to_spi_device(dev); - return spi_w8r8(spi, AD7310_COMMAND(reg) | ADT7310_CMD_READ); } -static int adt7310_spi_write_byte(struct device *dev, u8 reg, - u8 data) +static int adt7310_spi_write_byte(struct spi_device *spi, u8 reg, u8 data) { - struct spi_device *spi = to_spi_device(dev); u8 buf[2]; buf[0] = AD7310_COMMAND(reg); @@ -75,25 +69,79 @@ static int adt7310_spi_write_byte(struct device *dev, u8 reg, return spi_write(spi, buf, sizeof(buf)); } -static const struct adt7x10_ops adt7310_spi_ops = { - .read_word = adt7310_spi_read_word, - .write_word = adt7310_spi_write_word, - .read_byte = adt7310_spi_read_byte, - .write_byte = adt7310_spi_write_byte, -}; - -static int adt7310_spi_probe(struct spi_device *spi) +static bool adt7310_regmap_is_volatile(struct device *dev, unsigned int reg) { - return adt7x10_probe(&spi->dev, spi_get_device_id(spi)->name, spi->irq, - &adt7310_spi_ops); + switch (reg) { + case ADT7X10_TEMPERATURE: + case ADT7X10_STATUS: + return true; + default: + return false; + } } -static int adt7310_spi_remove(struct spi_device *spi) +static int adt7310_reg_read(void *context, unsigned int reg, unsigned int *val) { - adt7x10_remove(&spi->dev, spi->irq); + struct spi_device *spi = context; + int regval; + + switch (reg) { + case ADT7X10_TEMPERATURE: + case ADT7X10_T_ALARM_HIGH: + case ADT7X10_T_ALARM_LOW: + case ADT7X10_T_CRIT: + regval = adt7310_spi_read_word(spi, reg); + break; + default: + regval = adt7310_spi_read_byte(spi, reg); + break; + } + if (regval < 0) + return regval; + *val = regval; return 0; } +static int adt7310_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct spi_device *spi = context; + int ret; + + switch (reg) { + case ADT7X10_TEMPERATURE: + case ADT7X10_T_ALARM_HIGH: + case ADT7X10_T_ALARM_LOW: + case ADT7X10_T_CRIT: + ret = adt7310_spi_write_word(spi, reg, val); + break; + default: + ret = adt7310_spi_write_byte(spi, reg, val); + break; + } + return ret; +} + +static const struct regmap_config adt7310_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = adt7310_regmap_is_volatile, + .reg_read = adt7310_reg_read, + .reg_write = adt7310_reg_write, +}; + +static int adt7310_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + + regmap = devm_regmap_init(&spi->dev, NULL, spi, &adt7310_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return adt7x10_probe(&spi->dev, spi_get_device_id(spi)->name, spi->irq, + regmap); +} + static const struct spi_device_id adt7310_id[] = { { "adt7310", 0 }, { "adt7320", 0 }, @@ -107,7 +155,6 @@ static struct spi_driver adt7310_driver = { .pm = ADT7X10_DEV_PM_OPS, }, .probe = adt7310_spi_probe, - .remove = adt7310_spi_remove, .id_table = adt7310_id, }; module_spi_driver(adt7310_driver); diff --git a/drivers/hwmon/adt7410.c b/drivers/hwmon/adt7410.c index 973db057427b..aede5baca7b9 100644 --- a/drivers/hwmon/adt7410.c +++ b/drivers/hwmon/adt7410.c @@ -9,49 +9,82 @@ #include <linux/module.h> #include <linux/init.h> #include <linux/i2c.h> +#include <linux/regmap.h> #include "adt7x10.h" -static int adt7410_i2c_read_word(struct device *dev, u8 reg) +static bool adt7410_regmap_is_volatile(struct device *dev, unsigned int reg) { - return i2c_smbus_read_word_swapped(to_i2c_client(dev), reg); + switch (reg) { + case ADT7X10_TEMPERATURE: + case ADT7X10_STATUS: + return true; + default: + return false; + } } -static int adt7410_i2c_write_word(struct device *dev, u8 reg, u16 data) +static int adt7410_reg_read(void *context, unsigned int reg, unsigned int *val) { - return i2c_smbus_write_word_swapped(to_i2c_client(dev), reg, data); -} + struct i2c_client *client = context; + int regval; -static int adt7410_i2c_read_byte(struct device *dev, u8 reg) -{ - return i2c_smbus_read_byte_data(to_i2c_client(dev), reg); + switch (reg) { + case ADT7X10_TEMPERATURE: + case ADT7X10_T_ALARM_HIGH: + case ADT7X10_T_ALARM_LOW: + case ADT7X10_T_CRIT: + regval = i2c_smbus_read_word_swapped(client, reg); + break; + default: + regval = i2c_smbus_read_byte_data(client, reg); + break; + } + if (regval < 0) + return regval; + *val = regval; + return 0; } -static int adt7410_i2c_write_byte(struct device *dev, u8 reg, u8 data) +static int adt7410_reg_write(void *context, unsigned int reg, unsigned int val) { - return i2c_smbus_write_byte_data(to_i2c_client(dev), reg, data); + struct i2c_client *client = context; + int ret; + + switch (reg) { + case ADT7X10_TEMPERATURE: + case ADT7X10_T_ALARM_HIGH: + case ADT7X10_T_ALARM_LOW: + case ADT7X10_T_CRIT: + ret = i2c_smbus_write_word_swapped(client, reg, val); + break; + default: + ret = i2c_smbus_write_byte_data(client, reg, val); + break; + } + return ret; } -static const struct adt7x10_ops adt7410_i2c_ops = { - .read_word = adt7410_i2c_read_word, - .write_word = adt7410_i2c_write_word, - .read_byte = adt7410_i2c_read_byte, - .write_byte = adt7410_i2c_write_byte, +static const struct regmap_config adt7410_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = ADT7X10_ID, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = adt7410_regmap_is_volatile, + .reg_read = adt7410_reg_read, + .reg_write = adt7410_reg_write, }; static int adt7410_i2c_probe(struct i2c_client *client) { - if (!i2c_check_functionality(client->adapter, - I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA)) - return -ENODEV; + struct regmap *regmap; - return adt7x10_probe(&client->dev, NULL, client->irq, &adt7410_i2c_ops); -} + regmap = devm_regmap_init(&client->dev, NULL, client, + &adt7410_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); -static int adt7410_i2c_remove(struct i2c_client *client) -{ - adt7x10_remove(&client->dev, client->irq); - return 0; + return adt7x10_probe(&client->dev, client->name, client->irq, regmap); } static const struct i2c_device_id adt7410_ids[] = { @@ -68,7 +101,6 @@ static struct i2c_driver adt7410_driver = { .pm = ADT7X10_DEV_PM_OPS, }, .probe_new = adt7410_i2c_probe, - .remove = adt7410_i2c_remove, .id_table = adt7410_ids, .address_list = I2C_ADDRS(0x48, 0x49, 0x4a, 0x4b), }; diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c index d519aca4a9d6..fb6d14d213a1 100644 --- a/drivers/hwmon/adt7470.c +++ b/drivers/hwmon/adt7470.c @@ -662,6 +662,9 @@ static int adt7470_fan_write(struct device *dev, u32 attr, int channel, long val struct adt7470_data *data = dev_get_drvdata(dev); int err; + if (val <= 0) + return -EINVAL; + val = FAN_RPM_TO_PERIOD(val); val = clamp_val(val, 1, 65534); diff --git a/drivers/hwmon/adt7x10.c b/drivers/hwmon/adt7x10.c index e9d33aa78a19..ce54bffab2ec 100644 --- a/drivers/hwmon/adt7x10.c +++ b/drivers/hwmon/adt7x10.c @@ -8,16 +8,17 @@ * and adt7410.c from iio-staging by Sonic Zhang <sonic.zhang@analog.com> */ +#include <linux/device.h> #include <linux/module.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/jiffies.h> #include <linux/hwmon.h> -#include <linux/hwmon-sysfs.h> #include <linux/err.h> #include <linux/mutex.h> #include <linux/delay.h> #include <linux/interrupt.h> +#include <linux/regmap.h> #include "adt7x10.h" @@ -53,80 +54,57 @@ /* Each client has this additional data */ struct adt7x10_data { - const struct adt7x10_ops *ops; - const char *name; - struct device *hwmon_dev; + struct regmap *regmap; struct mutex update_lock; u8 config; u8 oldconfig; - bool valid; /* true if registers valid */ - unsigned long last_updated; /* In jiffies */ - s16 temp[4]; /* Register values, - 0 = input - 1 = high - 2 = low - 3 = critical */ - u8 hyst; /* hysteresis offset */ + bool valid; /* true if temperature valid */ }; -static int adt7x10_read_byte(struct device *dev, u8 reg) -{ - struct adt7x10_data *d = dev_get_drvdata(dev); - return d->ops->read_byte(dev, reg); -} - -static int adt7x10_write_byte(struct device *dev, u8 reg, u8 data) -{ - struct adt7x10_data *d = dev_get_drvdata(dev); - return d->ops->write_byte(dev, reg, data); -} - -static int adt7x10_read_word(struct device *dev, u8 reg) -{ - struct adt7x10_data *d = dev_get_drvdata(dev); - return d->ops->read_word(dev, reg); -} - -static int adt7x10_write_word(struct device *dev, u8 reg, u16 data) -{ - struct adt7x10_data *d = dev_get_drvdata(dev); - return d->ops->write_word(dev, reg, data); -} +enum { + adt7x10_temperature = 0, + adt7x10_t_alarm_high, + adt7x10_t_alarm_low, + adt7x10_t_crit, +}; -static const u8 ADT7X10_REG_TEMP[4] = { - ADT7X10_TEMPERATURE, /* input */ - ADT7X10_T_ALARM_HIGH, /* high */ - ADT7X10_T_ALARM_LOW, /* low */ - ADT7X10_T_CRIT, /* critical */ +static const u8 ADT7X10_REG_TEMP[] = { + [adt7x10_temperature] = ADT7X10_TEMPERATURE, /* input */ + [adt7x10_t_alarm_high] = ADT7X10_T_ALARM_HIGH, /* high */ + [adt7x10_t_alarm_low] = ADT7X10_T_ALARM_LOW, /* low */ + [adt7x10_t_crit] = ADT7X10_T_CRIT, /* critical */ }; static irqreturn_t adt7x10_irq_handler(int irq, void *private) { struct device *dev = private; - int status; + struct adt7x10_data *d = dev_get_drvdata(dev); + unsigned int status; + int ret; - status = adt7x10_read_byte(dev, ADT7X10_STATUS); - if (status < 0) + ret = regmap_read(d->regmap, ADT7X10_STATUS, &status); + if (ret < 0) return IRQ_HANDLED; if (status & ADT7X10_STAT_T_HIGH) - sysfs_notify(&dev->kobj, NULL, "temp1_max_alarm"); + hwmon_notify_event(dev, hwmon_temp, hwmon_temp_max_alarm, 0); if (status & ADT7X10_STAT_T_LOW) - sysfs_notify(&dev->kobj, NULL, "temp1_min_alarm"); + hwmon_notify_event(dev, hwmon_temp, hwmon_temp_min_alarm, 0); if (status & ADT7X10_STAT_T_CRIT) - sysfs_notify(&dev->kobj, NULL, "temp1_crit_alarm"); + hwmon_notify_event(dev, hwmon_temp, hwmon_temp_crit_alarm, 0); return IRQ_HANDLED; } -static int adt7x10_temp_ready(struct device *dev) +static int adt7x10_temp_ready(struct regmap *regmap) { - int i, status; + unsigned int status; + int i, ret; for (i = 0; i < 6; i++) { - status = adt7x10_read_byte(dev, ADT7X10_STATUS); - if (status < 0) - return status; + ret = regmap_read(regmap, ADT7X10_STATUS, &status); + if (ret < 0) + return ret; if (!(status & ADT7X10_STAT_NOT_RDY)) return 0; msleep(60); @@ -134,71 +112,10 @@ static int adt7x10_temp_ready(struct device *dev) return -ETIMEDOUT; } -static int adt7x10_update_temp(struct device *dev) -{ - struct adt7x10_data *data = dev_get_drvdata(dev); - int ret = 0; - - mutex_lock(&data->update_lock); - - if (time_after(jiffies, data->last_updated + HZ + HZ / 2) - || !data->valid) { - int temp; - - dev_dbg(dev, "Starting update\n"); - - ret = adt7x10_temp_ready(dev); /* check for new value */ - if (ret) - goto abort; - - temp = adt7x10_read_word(dev, ADT7X10_REG_TEMP[0]); - if (temp < 0) { - ret = temp; - dev_dbg(dev, "Failed to read value: reg %d, error %d\n", - ADT7X10_REG_TEMP[0], ret); - goto abort; - } - data->temp[0] = temp; - data->last_updated = jiffies; - data->valid = true; - } - -abort: - mutex_unlock(&data->update_lock); - return ret; -} - -static int adt7x10_fill_cache(struct device *dev) -{ - struct adt7x10_data *data = dev_get_drvdata(dev); - int ret; - int i; - - for (i = 1; i < ARRAY_SIZE(data->temp); i++) { - ret = adt7x10_read_word(dev, ADT7X10_REG_TEMP[i]); - if (ret < 0) { - dev_dbg(dev, "Failed to read value: reg %d, error %d\n", - ADT7X10_REG_TEMP[i], ret); - return ret; - } - data->temp[i] = ret; - } - - ret = adt7x10_read_byte(dev, ADT7X10_T_HYST); - if (ret < 0) { - dev_dbg(dev, "Failed to read value: reg %d, error %d\n", - ADT7X10_T_HYST, ret); - return ret; - } - data->hyst = ret; - - return 0; -} - static s16 ADT7X10_TEMP_TO_REG(long temp) { return DIV_ROUND_CLOSEST(clamp_val(temp, ADT7X10_TEMP_MIN, - ADT7X10_TEMP_MAX) * 128, 1000); + ADT7X10_TEMP_MAX) * 128, 1000); } static int ADT7X10_REG_TO_TEMP(struct adt7x10_data *data, s16 reg) @@ -215,170 +132,233 @@ static int ADT7X10_REG_TO_TEMP(struct adt7x10_data *data, s16 reg) /*-----------------------------------------------------------------------*/ -/* sysfs attributes for hwmon */ - -static ssize_t adt7x10_temp_show(struct device *dev, - struct device_attribute *da, char *buf) +static int adt7x10_temp_read(struct adt7x10_data *data, int index, long *val) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(da); - struct adt7x10_data *data = dev_get_drvdata(dev); - - - if (attr->index == 0) { - int ret; + unsigned int regval; + int ret; - ret = adt7x10_update_temp(dev); - if (ret) + mutex_lock(&data->update_lock); + if (index == adt7x10_temperature && !data->valid) { + /* wait for valid temperature */ + ret = adt7x10_temp_ready(data->regmap); + if (ret) { + mutex_unlock(&data->update_lock); return ret; + } + data->valid = true; } + mutex_unlock(&data->update_lock); - return sprintf(buf, "%d\n", ADT7X10_REG_TO_TEMP(data, - data->temp[attr->index])); + ret = regmap_read(data->regmap, ADT7X10_REG_TEMP[index], ®val); + if (ret) + return ret; + + *val = ADT7X10_REG_TO_TEMP(data, regval); + return 0; } -static ssize_t adt7x10_temp_store(struct device *dev, - struct device_attribute *da, - const char *buf, size_t count) +static int adt7x10_temp_write(struct adt7x10_data *data, int index, long temp) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(da); - struct adt7x10_data *data = dev_get_drvdata(dev); - int nr = attr->index; - long temp; int ret; - ret = kstrtol(buf, 10, &temp); - if (ret) - return ret; - mutex_lock(&data->update_lock); - data->temp[nr] = ADT7X10_TEMP_TO_REG(temp); - ret = adt7x10_write_word(dev, ADT7X10_REG_TEMP[nr], data->temp[nr]); - if (ret) - count = ret; + ret = regmap_write(data->regmap, ADT7X10_REG_TEMP[index], + ADT7X10_TEMP_TO_REG(temp)); mutex_unlock(&data->update_lock); - return count; + return ret; } -static ssize_t adt7x10_t_hyst_show(struct device *dev, - struct device_attribute *da, char *buf) +static int adt7x10_hyst_read(struct adt7x10_data *data, int index, long *val) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(da); - struct adt7x10_data *data = dev_get_drvdata(dev); - int nr = attr->index; - int hyst; + int hyst, temp, ret; + + mutex_lock(&data->update_lock); + ret = regmap_read(data->regmap, ADT7X10_T_HYST, &hyst); + if (ret) { + mutex_unlock(&data->update_lock); + return ret; + } + + ret = regmap_read(data->regmap, ADT7X10_REG_TEMP[index], &temp); + mutex_unlock(&data->update_lock); + if (ret) + return ret; - hyst = (data->hyst & ADT7X10_T_HYST_MASK) * 1000; + hyst = (hyst & ADT7X10_T_HYST_MASK) * 1000; /* * hysteresis is stored as a 4 bit offset in the device, convert it * to an absolute value */ - if (nr == 2) /* min has positive offset, others have negative */ + /* min has positive offset, others have negative */ + if (index == adt7x10_t_alarm_low) hyst = -hyst; - return sprintf(buf, "%d\n", - ADT7X10_REG_TO_TEMP(data, data->temp[nr]) - hyst); + + *val = ADT7X10_REG_TO_TEMP(data, temp) - hyst; + return 0; } -static ssize_t adt7x10_t_hyst_store(struct device *dev, - struct device_attribute *da, - const char *buf, size_t count) +static int adt7x10_hyst_write(struct adt7x10_data *data, long hyst) { - struct adt7x10_data *data = dev_get_drvdata(dev); + unsigned int regval; int limit, ret; - long hyst; - ret = kstrtol(buf, 10, &hyst); - if (ret) - return ret; + mutex_lock(&data->update_lock); + /* convert absolute hysteresis value to a 4 bit delta value */ - limit = ADT7X10_REG_TO_TEMP(data, data->temp[1]); - hyst = clamp_val(hyst, ADT7X10_TEMP_MIN, ADT7X10_TEMP_MAX); - data->hyst = clamp_val(DIV_ROUND_CLOSEST(limit - hyst, 1000), - 0, ADT7X10_T_HYST_MASK); - ret = adt7x10_write_byte(dev, ADT7X10_T_HYST, data->hyst); - if (ret) - return ret; + ret = regmap_read(data->regmap, ADT7X10_T_ALARM_HIGH, ®val); + if (ret < 0) + goto abort; + + limit = ADT7X10_REG_TO_TEMP(data, regval); - return count; + hyst = clamp_val(hyst, ADT7X10_TEMP_MIN, ADT7X10_TEMP_MAX); + regval = clamp_val(DIV_ROUND_CLOSEST(limit - hyst, 1000), 0, + ADT7X10_T_HYST_MASK); + ret = regmap_write(data->regmap, ADT7X10_T_HYST, regval); +abort: + mutex_unlock(&data->update_lock); + return ret; } -static ssize_t adt7x10_alarm_show(struct device *dev, - struct device_attribute *da, char *buf) +static int adt7x10_alarm_read(struct adt7x10_data *data, int index, long *val) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + unsigned int status; int ret; - ret = adt7x10_read_byte(dev, ADT7X10_STATUS); + ret = regmap_read(data->regmap, ADT7X10_STATUS, &status); if (ret < 0) return ret; - return sprintf(buf, "%d\n", !!(ret & attr->index)); + *val = !!(status & index); + + return 0; +} + +static umode_t adt7x10_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (attr) { + case hwmon_temp_max: + case hwmon_temp_min: + case hwmon_temp_crit: + case hwmon_temp_max_hyst: + return 0644; + case hwmon_temp_input: + case hwmon_temp_min_alarm: + case hwmon_temp_max_alarm: + case hwmon_temp_crit_alarm: + case hwmon_temp_min_hyst: + case hwmon_temp_crit_hyst: + return 0444; + default: + break; + } + + return 0; } -static ssize_t name_show(struct device *dev, struct device_attribute *da, - char *buf) +static int adt7x10_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) { struct adt7x10_data *data = dev_get_drvdata(dev); - return sprintf(buf, "%s\n", data->name); + switch (attr) { + case hwmon_temp_input: + return adt7x10_temp_read(data, adt7x10_temperature, val); + case hwmon_temp_max: + return adt7x10_temp_read(data, adt7x10_t_alarm_high, val); + case hwmon_temp_min: + return adt7x10_temp_read(data, adt7x10_t_alarm_low, val); + case hwmon_temp_crit: + return adt7x10_temp_read(data, adt7x10_t_crit, val); + case hwmon_temp_max_hyst: + return adt7x10_hyst_read(data, adt7x10_t_alarm_high, val); + case hwmon_temp_min_hyst: + return adt7x10_hyst_read(data, adt7x10_t_alarm_low, val); + case hwmon_temp_crit_hyst: + return adt7x10_hyst_read(data, adt7x10_t_crit, val); + case hwmon_temp_min_alarm: + return adt7x10_alarm_read(data, ADT7X10_STAT_T_LOW, val); + case hwmon_temp_max_alarm: + return adt7x10_alarm_read(data, ADT7X10_STAT_T_HIGH, val); + case hwmon_temp_crit_alarm: + return adt7x10_alarm_read(data, ADT7X10_STAT_T_CRIT, val); + default: + return -EOPNOTSUPP; + } } -static SENSOR_DEVICE_ATTR_RO(temp1_input, adt7x10_temp, 0); -static SENSOR_DEVICE_ATTR_RW(temp1_max, adt7x10_temp, 1); -static SENSOR_DEVICE_ATTR_RW(temp1_min, adt7x10_temp, 2); -static SENSOR_DEVICE_ATTR_RW(temp1_crit, adt7x10_temp, 3); -static SENSOR_DEVICE_ATTR_RW(temp1_max_hyst, adt7x10_t_hyst, 1); -static SENSOR_DEVICE_ATTR_RO(temp1_min_hyst, adt7x10_t_hyst, 2); -static SENSOR_DEVICE_ATTR_RO(temp1_crit_hyst, adt7x10_t_hyst, 3); -static SENSOR_DEVICE_ATTR_RO(temp1_min_alarm, adt7x10_alarm, - ADT7X10_STAT_T_LOW); -static SENSOR_DEVICE_ATTR_RO(temp1_max_alarm, adt7x10_alarm, - ADT7X10_STAT_T_HIGH); -static SENSOR_DEVICE_ATTR_RO(temp1_crit_alarm, adt7x10_alarm, - ADT7X10_STAT_T_CRIT); -static DEVICE_ATTR_RO(name); - -static struct attribute *adt7x10_attributes[] = { - &sensor_dev_attr_temp1_input.dev_attr.attr, - &sensor_dev_attr_temp1_max.dev_attr.attr, - &sensor_dev_attr_temp1_min.dev_attr.attr, - &sensor_dev_attr_temp1_crit.dev_attr.attr, - &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, - &sensor_dev_attr_temp1_min_hyst.dev_attr.attr, - &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr, - &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, - &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, - NULL +static int adt7x10_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct adt7x10_data *data = dev_get_drvdata(dev); + + switch (attr) { + case hwmon_temp_max: + return adt7x10_temp_write(data, adt7x10_t_alarm_high, val); + case hwmon_temp_min: + return adt7x10_temp_write(data, adt7x10_t_alarm_low, val); + case hwmon_temp_crit: + return adt7x10_temp_write(data, adt7x10_t_crit, val); + case hwmon_temp_max_hyst: + return adt7x10_hyst_write(data, val); + default: + return -EOPNOTSUPP; + } +} + +static const struct hwmon_channel_info *adt7x10_info[] = { + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MIN | + HWMON_T_CRIT | HWMON_T_MAX_HYST | HWMON_T_MIN_HYST | + HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM), + NULL, +}; + +static const struct hwmon_ops adt7x10_hwmon_ops = { + .is_visible = adt7x10_is_visible, + .read = adt7x10_read, + .write = adt7x10_write, }; -static const struct attribute_group adt7x10_group = { - .attrs = adt7x10_attributes, +static const struct hwmon_chip_info adt7x10_chip_info = { + .ops = &adt7x10_hwmon_ops, + .info = adt7x10_info, }; +static void adt7x10_restore_config(void *private) +{ + struct adt7x10_data *data = private; + + regmap_write(data->regmap, ADT7X10_CONFIG, data->oldconfig); +} + int adt7x10_probe(struct device *dev, const char *name, int irq, - const struct adt7x10_ops *ops) + struct regmap *regmap) { struct adt7x10_data *data; + unsigned int config; + struct device *hdev; int ret; data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; - data->ops = ops; - data->name = name; + data->regmap = regmap; dev_set_drvdata(dev, data); mutex_init(&data->update_lock); /* configure as specified */ - ret = adt7x10_read_byte(dev, ADT7X10_CONFIG); + ret = regmap_read(regmap, ADT7X10_CONFIG, &config); if (ret < 0) { dev_dbg(dev, "Can't read config? %d\n", ret); return ret; } - data->oldconfig = ret; + data->oldconfig = config; /* * Set to 16 bit resolution, continous conversion and comparator mode. @@ -389,92 +369,49 @@ int adt7x10_probe(struct device *dev, const char *name, int irq, data->config |= ADT7X10_FULL | ADT7X10_RESOLUTION | ADT7X10_EVENT_MODE; if (data->config != data->oldconfig) { - ret = adt7x10_write_byte(dev, ADT7X10_CONFIG, data->config); + ret = regmap_write(regmap, ADT7X10_CONFIG, data->config); if (ret) return ret; - } - dev_dbg(dev, "Config %02x\n", data->config); - - ret = adt7x10_fill_cache(dev); - if (ret) - goto exit_restore; - - /* Register sysfs hooks */ - ret = sysfs_create_group(&dev->kobj, &adt7x10_group); - if (ret) - goto exit_restore; - - /* - * The I2C device will already have it's own 'name' attribute, but for - * the SPI device we need to register it. name will only be non NULL if - * the device doesn't register the 'name' attribute on its own. - */ - if (name) { - ret = device_create_file(dev, &dev_attr_name); + ret = devm_add_action_or_reset(dev, adt7x10_restore_config, data); if (ret) - goto exit_remove; + return ret; } + dev_dbg(dev, "Config %02x\n", data->config); - data->hwmon_dev = hwmon_device_register(dev); - if (IS_ERR(data->hwmon_dev)) { - ret = PTR_ERR(data->hwmon_dev); - goto exit_remove_name; - } + hdev = devm_hwmon_device_register_with_info(dev, name, data, + &adt7x10_chip_info, NULL); + if (IS_ERR(hdev)) + return PTR_ERR(hdev); if (irq > 0) { - ret = request_threaded_irq(irq, NULL, adt7x10_irq_handler, - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, - dev_name(dev), dev); + ret = devm_request_threaded_irq(dev, irq, NULL, + adt7x10_irq_handler, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + dev_name(dev), hdev); if (ret) - goto exit_hwmon_device_unregister; + return ret; } return 0; - -exit_hwmon_device_unregister: - hwmon_device_unregister(data->hwmon_dev); -exit_remove_name: - if (name) - device_remove_file(dev, &dev_attr_name); -exit_remove: - sysfs_remove_group(&dev->kobj, &adt7x10_group); -exit_restore: - adt7x10_write_byte(dev, ADT7X10_CONFIG, data->oldconfig); - return ret; } EXPORT_SYMBOL_GPL(adt7x10_probe); -void adt7x10_remove(struct device *dev, int irq) -{ - struct adt7x10_data *data = dev_get_drvdata(dev); - - if (irq > 0) - free_irq(irq, dev); - - hwmon_device_unregister(data->hwmon_dev); - if (data->name) - device_remove_file(dev, &dev_attr_name); - sysfs_remove_group(&dev->kobj, &adt7x10_group); - if (data->oldconfig != data->config) - adt7x10_write_byte(dev, ADT7X10_CONFIG, data->oldconfig); -} -EXPORT_SYMBOL_GPL(adt7x10_remove); - #ifdef CONFIG_PM_SLEEP static int adt7x10_suspend(struct device *dev) { struct adt7x10_data *data = dev_get_drvdata(dev); - return adt7x10_write_byte(dev, ADT7X10_CONFIG, - data->config | ADT7X10_PD); + return regmap_write(data->regmap, ADT7X10_CONFIG, + data->config | ADT7X10_PD); } static int adt7x10_resume(struct device *dev) { struct adt7x10_data *data = dev_get_drvdata(dev); - return adt7x10_write_byte(dev, ADT7X10_CONFIG, data->config); + return regmap_write(data->regmap, ADT7X10_CONFIG, data->config); } SIMPLE_DEV_PM_OPS(adt7x10_dev_pm_ops, adt7x10_suspend, adt7x10_resume); diff --git a/drivers/hwmon/adt7x10.h b/drivers/hwmon/adt7x10.h index a1ae682eb32e..ba22c32c8355 100644 --- a/drivers/hwmon/adt7x10.h +++ b/drivers/hwmon/adt7x10.h @@ -17,16 +17,8 @@ struct device; -struct adt7x10_ops { - int (*read_byte)(struct device *, u8 reg); - int (*write_byte)(struct device *, u8 reg, u8 data); - int (*read_word)(struct device *, u8 reg); - int (*write_word)(struct device *, u8 reg, u16 data); -}; - int adt7x10_probe(struct device *dev, const char *name, int irq, - const struct adt7x10_ops *ops); -void adt7x10_remove(struct device *dev, int irq); + struct regmap *regmap); #ifdef CONFIG_PM_SLEEP extern const struct dev_pm_ops adt7x10_dev_pm_ops; diff --git a/drivers/hwmon/aquacomputer_d5next.c b/drivers/hwmon/aquacomputer_d5next.c index fb9341a53051..525809cf7c95 100644 --- a/drivers/hwmon/aquacomputer_d5next.c +++ b/drivers/hwmon/aquacomputer_d5next.c @@ -1,32 +1,41 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * hwmon driver for Aquacomputer D5 Next watercooling pump + * hwmon driver for Aquacomputer devices (D5 Next, Farbwerk 360) * - * The D5 Next sends HID reports (with ID 0x01) every second to report sensor values - * (coolant temperature, pump and fan speed, voltage, current and power). It responds to - * Get_Report requests, but returns a dummy value of no use. + * Aquacomputer devices send HID reports (with ID 0x01) every second to report + * sensor values. * * Copyright 2021 Aleksa Savic <savicaleksa83@gmail.com> */ -#include <asm/unaligned.h> #include <linux/debugfs.h> #include <linux/hid.h> #include <linux/hwmon.h> #include <linux/jiffies.h> #include <linux/module.h> #include <linux/seq_file.h> +#include <asm/unaligned.h> -#define DRIVER_NAME "aquacomputer-d5next" +#define USB_VENDOR_ID_AQUACOMPUTER 0x0c70 +#define USB_PRODUCT_ID_D5NEXT 0xf00e +#define USB_PRODUCT_ID_FARBWERK360 0xf010 -#define D5NEXT_STATUS_REPORT_ID 0x01 -#define D5NEXT_STATUS_UPDATE_INTERVAL (2 * HZ) /* In seconds */ +enum kinds { d5next, farbwerk360 }; -/* Register offsets for the D5 Next pump */ +static const char *const aqc_device_names[] = { + [d5next] = "d5next", + [farbwerk360] = "farbwerk360" +}; -#define D5NEXT_SERIAL_FIRST_PART 3 -#define D5NEXT_SERIAL_SECOND_PART 5 -#define D5NEXT_FIRMWARE_VERSION 13 +#define DRIVER_NAME "aquacomputer_d5next" + +#define STATUS_REPORT_ID 0x01 +#define STATUS_UPDATE_INTERVAL (2 * HZ) /* In seconds */ +#define SERIAL_FIRST_PART 3 +#define SERIAL_SECOND_PART 5 +#define FIRMWARE_VERSION 13 + +/* Register offsets for the D5 Next pump */ #define D5NEXT_POWER_CYCLES 24 #define D5NEXT_COOLANT_TEMP 87 @@ -44,76 +53,118 @@ #define D5NEXT_PUMP_CURRENT 112 #define D5NEXT_FAN_CURRENT 99 -/* Labels for provided values */ +/* Register offsets for the Farbwerk 360 RGB controller */ +#define FARBWERK360_NUM_SENSORS 4 +#define FARBWERK360_SENSOR_START 0x32 +#define FARBWERK360_SENSOR_SIZE 0x02 +#define FARBWERK360_SENSOR_DISCONNECTED 0x7FFF -#define L_COOLANT_TEMP "Coolant temp" +/* Labels for D5 Next */ +#define L_D5NEXT_COOLANT_TEMP "Coolant temp" -#define L_PUMP_SPEED "Pump speed" -#define L_FAN_SPEED "Fan speed" - -#define L_PUMP_POWER "Pump power" -#define L_FAN_POWER "Fan power" - -#define L_PUMP_VOLTAGE "Pump voltage" -#define L_FAN_VOLTAGE "Fan voltage" -#define L_5V_VOLTAGE "+5V voltage" - -#define L_PUMP_CURRENT "Pump current" -#define L_FAN_CURRENT "Fan current" +static const char *const label_d5next_speeds[] = { + "Pump speed", + "Fan speed" +}; -static const char *const label_speeds[] = { - L_PUMP_SPEED, - L_FAN_SPEED, +static const char *const label_d5next_power[] = { + "Pump power", + "Fan power" }; -static const char *const label_power[] = { - L_PUMP_POWER, - L_FAN_POWER, +static const char *const label_d5next_voltages[] = { + "Pump voltage", + "Fan voltage", + "+5V voltage" }; -static const char *const label_voltages[] = { - L_PUMP_VOLTAGE, - L_FAN_VOLTAGE, - L_5V_VOLTAGE, +static const char *const label_d5next_current[] = { + "Pump current", + "Fan current" }; -static const char *const label_current[] = { - L_PUMP_CURRENT, - L_FAN_CURRENT, +/* Labels for Farbwerk 360 temperature sensors */ +static const char *const label_temp_sensors[] = { + "Sensor 1", + "Sensor 2", + "Sensor 3", + "Sensor 4" }; -struct d5next_data { +struct aqc_data { struct hid_device *hdev; struct device *hwmon_dev; struct dentry *debugfs; - s32 temp_input; + enum kinds kind; + const char *name; + + /* General info, same across all devices */ + u32 serial_number[2]; + u16 firmware_version; + + /* D5 Next specific - how many times the device was powered on */ + u32 power_cycles; + + /* Sensor values */ + s32 temp_input[4]; u16 speed_input[2]; u32 power_input[2]; u16 voltage_input[3]; u16 current_input[2]; - u32 serial_number[2]; - u16 firmware_version; - u32 power_cycles; /* How many times the device was powered on */ + unsigned long updated; }; -static umode_t d5next_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, - int channel) +static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, + int channel) { - return 0444; + const struct aqc_data *priv = data; + + switch (type) { + case hwmon_temp: + switch (priv->kind) { + case d5next: + if (channel == 0) + return 0444; + break; + case farbwerk360: + return 0444; + default: + break; + } + break; + case hwmon_fan: + case hwmon_power: + case hwmon_in: + case hwmon_curr: + switch (priv->kind) { + case d5next: + return 0444; + default: + break; + } + break; + default: + break; + } + + return 0; } -static int d5next_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, - long *val) +static int aqc_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long *val) { - struct d5next_data *priv = dev_get_drvdata(dev); + struct aqc_data *priv = dev_get_drvdata(dev); - if (time_after(jiffies, priv->updated + D5NEXT_STATUS_UPDATE_INTERVAL)) + if (time_after(jiffies, priv->updated + STATUS_UPDATE_INTERVAL)) return -ENODATA; switch (type) { case hwmon_temp: - *val = priv->temp_input; + if (priv->temp_input[channel] == -ENODATA) + return -ENODATA; + + *val = priv->temp_input[channel]; break; case hwmon_fan: *val = priv->speed_input[channel]; @@ -134,24 +185,59 @@ static int d5next_read(struct device *dev, enum hwmon_sensor_types type, u32 att return 0; } -static int d5next_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, - int channel, const char **str) +static int aqc_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) { + struct aqc_data *priv = dev_get_drvdata(dev); + switch (type) { case hwmon_temp: - *str = L_COOLANT_TEMP; + switch (priv->kind) { + case d5next: + *str = L_D5NEXT_COOLANT_TEMP; + break; + case farbwerk360: + *str = label_temp_sensors[channel]; + break; + default: + break; + } break; case hwmon_fan: - *str = label_speeds[channel]; + switch (priv->kind) { + case d5next: + *str = label_d5next_speeds[channel]; + break; + default: + break; + } break; case hwmon_power: - *str = label_power[channel]; + switch (priv->kind) { + case d5next: + *str = label_d5next_power[channel]; + break; + default: + break; + } break; case hwmon_in: - *str = label_voltages[channel]; + switch (priv->kind) { + case d5next: + *str = label_d5next_voltages[channel]; + break; + default: + break; + } break; case hwmon_curr: - *str = label_current[channel]; + switch (priv->kind) { + case d5next: + *str = label_d5next_current[channel]; + break; + default: + break; + } break; default: return -EOPNOTSUPP; @@ -160,60 +246,89 @@ static int d5next_read_string(struct device *dev, enum hwmon_sensor_types type, return 0; } -static const struct hwmon_ops d5next_hwmon_ops = { - .is_visible = d5next_is_visible, - .read = d5next_read, - .read_string = d5next_read_string, +static const struct hwmon_ops aqc_hwmon_ops = { + .is_visible = aqc_is_visible, + .read = aqc_read, + .read_string = aqc_read_string, }; -static const struct hwmon_channel_info *d5next_info[] = { - HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL), - HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL, HWMON_F_INPUT | HWMON_F_LABEL), - HWMON_CHANNEL_INFO(power, HWMON_P_INPUT | HWMON_P_LABEL, HWMON_P_INPUT | HWMON_P_LABEL), - HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, +static const struct hwmon_channel_info *aqc_info[] = { + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL), + HWMON_CHANNEL_INFO(fan, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL), + HWMON_CHANNEL_INFO(power, + HWMON_P_INPUT | HWMON_P_LABEL, + HWMON_P_INPUT | HWMON_P_LABEL), + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL), - HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT | HWMON_C_LABEL, HWMON_C_INPUT | HWMON_C_LABEL), + HWMON_CHANNEL_INFO(curr, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL), NULL }; -static const struct hwmon_chip_info d5next_chip_info = { - .ops = &d5next_hwmon_ops, - .info = d5next_info, +static const struct hwmon_chip_info aqc_chip_info = { + .ops = &aqc_hwmon_ops, + .info = aqc_info, }; -static int d5next_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) +static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, + int size) { - struct d5next_data *priv; + int i, sensor_value; + struct aqc_data *priv; - if (report->id != D5NEXT_STATUS_REPORT_ID) + if (report->id != STATUS_REPORT_ID) return 0; priv = hid_get_drvdata(hdev); /* Info provided with every report */ - - priv->serial_number[0] = get_unaligned_be16(data + D5NEXT_SERIAL_FIRST_PART); - priv->serial_number[1] = get_unaligned_be16(data + D5NEXT_SERIAL_SECOND_PART); - - priv->firmware_version = get_unaligned_be16(data + D5NEXT_FIRMWARE_VERSION); - priv->power_cycles = get_unaligned_be32(data + D5NEXT_POWER_CYCLES); + priv->serial_number[0] = get_unaligned_be16(data + SERIAL_FIRST_PART); + priv->serial_number[1] = get_unaligned_be16(data + SERIAL_SECOND_PART); + priv->firmware_version = get_unaligned_be16(data + FIRMWARE_VERSION); /* Sensor readings */ + switch (priv->kind) { + case d5next: + priv->power_cycles = get_unaligned_be32(data + D5NEXT_POWER_CYCLES); - priv->temp_input = get_unaligned_be16(data + D5NEXT_COOLANT_TEMP) * 10; + priv->temp_input[0] = get_unaligned_be16(data + D5NEXT_COOLANT_TEMP) * 10; - priv->speed_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_SPEED); - priv->speed_input[1] = get_unaligned_be16(data + D5NEXT_FAN_SPEED); + priv->speed_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_SPEED); + priv->speed_input[1] = get_unaligned_be16(data + D5NEXT_FAN_SPEED); - priv->power_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_POWER) * 10000; - priv->power_input[1] = get_unaligned_be16(data + D5NEXT_FAN_POWER) * 10000; + priv->power_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_POWER) * 10000; + priv->power_input[1] = get_unaligned_be16(data + D5NEXT_FAN_POWER) * 10000; - priv->voltage_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_VOLTAGE) * 10; - priv->voltage_input[1] = get_unaligned_be16(data + D5NEXT_FAN_VOLTAGE) * 10; - priv->voltage_input[2] = get_unaligned_be16(data + D5NEXT_5V_VOLTAGE) * 10; + priv->voltage_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_VOLTAGE) * 10; + priv->voltage_input[1] = get_unaligned_be16(data + D5NEXT_FAN_VOLTAGE) * 10; + priv->voltage_input[2] = get_unaligned_be16(data + D5NEXT_5V_VOLTAGE) * 10; - priv->current_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_CURRENT); - priv->current_input[1] = get_unaligned_be16(data + D5NEXT_FAN_CURRENT); + priv->current_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_CURRENT); + priv->current_input[1] = get_unaligned_be16(data + D5NEXT_FAN_CURRENT); + break; + case farbwerk360: + /* Temperature sensor readings */ + for (i = 0; i < FARBWERK360_NUM_SENSORS; i++) { + sensor_value = get_unaligned_be16(data + FARBWERK360_SENSOR_START + + i * FARBWERK360_SENSOR_SIZE); + if (sensor_value == FARBWERK360_SENSOR_DISCONNECTED) + priv->temp_input[i] = -ENODATA; + else + priv->temp_input[i] = sensor_value * 10; + } + break; + default: + break; + } priv->updated = jiffies; @@ -224,7 +339,7 @@ static int d5next_raw_event(struct hid_device *hdev, struct hid_report *report, static int serial_number_show(struct seq_file *seqf, void *unused) { - struct d5next_data *priv = seqf->private; + struct aqc_data *priv = seqf->private; seq_printf(seqf, "%05u-%05u\n", priv->serial_number[0], priv->serial_number[1]); @@ -234,7 +349,7 @@ DEFINE_SHOW_ATTRIBUTE(serial_number); static int firmware_version_show(struct seq_file *seqf, void *unused) { - struct d5next_data *priv = seqf->private; + struct aqc_data *priv = seqf->private; seq_printf(seqf, "%u\n", priv->firmware_version); @@ -244,7 +359,7 @@ DEFINE_SHOW_ATTRIBUTE(firmware_version); static int power_cycles_show(struct seq_file *seqf, void *unused) { - struct d5next_data *priv = seqf->private; + struct aqc_data *priv = seqf->private; seq_printf(seqf, "%u\n", priv->power_cycles); @@ -252,29 +367,32 @@ static int power_cycles_show(struct seq_file *seqf, void *unused) } DEFINE_SHOW_ATTRIBUTE(power_cycles); -static void d5next_debugfs_init(struct d5next_data *priv) +static void aqc_debugfs_init(struct aqc_data *priv) { - char name[32]; + char name[64]; - scnprintf(name, sizeof(name), "%s-%s", DRIVER_NAME, dev_name(&priv->hdev->dev)); + scnprintf(name, sizeof(name), "%s_%s-%s", "aquacomputer", priv->name, + dev_name(&priv->hdev->dev)); priv->debugfs = debugfs_create_dir(name, NULL); debugfs_create_file("serial_number", 0444, priv->debugfs, priv, &serial_number_fops); debugfs_create_file("firmware_version", 0444, priv->debugfs, priv, &firmware_version_fops); - debugfs_create_file("power_cycles", 0444, priv->debugfs, priv, &power_cycles_fops); + + if (priv->kind == d5next) + debugfs_create_file("power_cycles", 0444, priv->debugfs, priv, &power_cycles_fops); } #else -static void d5next_debugfs_init(struct d5next_data *priv) +static void aqc_debugfs_init(struct aqc_data *priv) { } #endif -static int d5next_probe(struct hid_device *hdev, const struct hid_device_id *id) +static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) { - struct d5next_data *priv; + struct aqc_data *priv; int ret; priv = devm_kzalloc(&hdev->dev, sizeof(*priv), GFP_KERNEL); @@ -284,7 +402,7 @@ static int d5next_probe(struct hid_device *hdev, const struct hid_device_id *id) priv->hdev = hdev; hid_set_drvdata(hdev, priv); - priv->updated = jiffies - D5NEXT_STATUS_UPDATE_INTERVAL; + priv->updated = jiffies - STATUS_UPDATE_INTERVAL; ret = hid_parse(hdev); if (ret) @@ -298,15 +416,28 @@ static int d5next_probe(struct hid_device *hdev, const struct hid_device_id *id) if (ret) goto fail_and_stop; - priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "d5next", priv, - &d5next_chip_info, NULL); + switch (hdev->product) { + case USB_PRODUCT_ID_D5NEXT: + priv->kind = d5next; + break; + case USB_PRODUCT_ID_FARBWERK360: + priv->kind = farbwerk360; + break; + default: + break; + } + + priv->name = aqc_device_names[priv->kind]; + + priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, priv->name, priv, + &aqc_chip_info, NULL); if (IS_ERR(priv->hwmon_dev)) { ret = PTR_ERR(priv->hwmon_dev); goto fail_and_close; } - d5next_debugfs_init(priv); + aqc_debugfs_init(priv); return 0; @@ -317,9 +448,9 @@ fail_and_stop: return ret; } -static void d5next_remove(struct hid_device *hdev) +static void aqc_remove(struct hid_device *hdev) { - struct d5next_data *priv = hid_get_drvdata(hdev); + struct aqc_data *priv = hid_get_drvdata(hdev); debugfs_remove_recursive(priv->debugfs); hwmon_device_unregister(priv->hwmon_dev); @@ -328,36 +459,36 @@ static void d5next_remove(struct hid_device *hdev) hid_hw_stop(hdev); } -static const struct hid_device_id d5next_table[] = { - { HID_USB_DEVICE(0x0c70, 0xf00e) }, /* Aquacomputer D5 Next */ - {}, +static const struct hid_device_id aqc_table[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_D5NEXT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_FARBWERK360) }, + { } }; -MODULE_DEVICE_TABLE(hid, d5next_table); +MODULE_DEVICE_TABLE(hid, aqc_table); -static struct hid_driver d5next_driver = { +static struct hid_driver aqc_driver = { .name = DRIVER_NAME, - .id_table = d5next_table, - .probe = d5next_probe, - .remove = d5next_remove, - .raw_event = d5next_raw_event, + .id_table = aqc_table, + .probe = aqc_probe, + .remove = aqc_remove, + .raw_event = aqc_raw_event, }; -static int __init d5next_init(void) +static int __init aqc_init(void) { - return hid_register_driver(&d5next_driver); + return hid_register_driver(&aqc_driver); } -static void __exit d5next_exit(void) +static void __exit aqc_exit(void) { - hid_unregister_driver(&d5next_driver); + hid_unregister_driver(&aqc_driver); } /* Request to initialize after the HID bus to ensure it's not being loaded before */ - -late_initcall(d5next_init); -module_exit(d5next_exit); +late_initcall(aqc_init); +module_exit(aqc_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Aleksa Savic <savicaleksa83@gmail.com>"); -MODULE_DESCRIPTION("Hwmon driver for Aquacomputer D5 Next pump"); +MODULE_DESCRIPTION("Hwmon driver for Aquacomputer devices"); diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c new file mode 100644 index 000000000000..b5cf0136360c --- /dev/null +++ b/drivers/hwmon/asus-ec-sensors.c @@ -0,0 +1,716 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * HWMON driver for ASUS motherboards that publish some sensor values + * via the embedded controller registers. + * + * Copyright (C) 2021 Eugene Shalygin <eugene.shalygin@gmail.com> + + * EC provides: + * - Chipset temperature + * - CPU temperature + * - Motherboard temperature + * - T_Sensor temperature + * - VRM temperature + * - Water In temperature + * - Water Out temperature + * - CPU Optional fan RPM + * - Chipset fan RPM + * - VRM Heat Sink fan RPM + * - Water Flow fan RPM + * - CPU current + * - CPU core voltage + */ + +#include <linux/acpi.h> +#include <linux/bitops.h> +#include <linux/dev_printk.h> +#include <linux/dmi.h> +#include <linux/hwmon.h> +#include <linux/init.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/sort.h> +#include <linux/units.h> + +#include <asm/unaligned.h> + +static char *mutex_path_override; + +/* Writing to this EC register switches EC bank */ +#define ASUS_EC_BANK_REGISTER 0xff +#define SENSOR_LABEL_LEN 16 + +/* + * Arbitrary set max. allowed bank number. Required for sorting banks and + * currently is overkill with just 2 banks used at max, but for the sake + * of alignment let's set it to a higher value. + */ +#define ASUS_EC_MAX_BANK 3 + +#define ACPI_LOCK_DELAY_MS 500 + +/* ACPI mutex for locking access to the EC for the firmware */ +#define ASUS_HW_ACCESS_MUTEX_ASMX "\\AMW0.ASMX" + +/* There are two variants of the vendor spelling */ +#define VENDOR_ASUS_UPPER_CASE "ASUSTeK COMPUTER INC." + +typedef union { + u32 value; + struct { + u8 index; + u8 bank; + u8 size; + u8 dummy; + } components; +} sensor_address; + +#define MAKE_SENSOR_ADDRESS(size, bank, index) { \ + .value = (size << 16) + (bank << 8) + index \ + } + +static u32 hwmon_attributes[hwmon_max] = { + [hwmon_chip] = HWMON_C_REGISTER_TZ, + [hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL, + [hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL, + [hwmon_curr] = HWMON_C_INPUT | HWMON_C_LABEL, + [hwmon_fan] = HWMON_F_INPUT | HWMON_F_LABEL, +}; + +struct ec_sensor_info { + char label[SENSOR_LABEL_LEN]; + enum hwmon_sensor_types type; + sensor_address addr; +}; + +#define EC_SENSOR(sensor_label, sensor_type, size, bank, index) { \ + .label = sensor_label, .type = sensor_type, \ + .addr = MAKE_SENSOR_ADDRESS(size, bank, index), \ + } + +enum ec_sensors { + /* chipset temperature [℃] */ + ec_sensor_temp_chipset, + /* CPU temperature [℃] */ + ec_sensor_temp_cpu, + /* motherboard temperature [℃] */ + ec_sensor_temp_mb, + /* "T_Sensor" temperature sensor reading [℃] */ + ec_sensor_temp_t_sensor, + /* VRM temperature [℃] */ + ec_sensor_temp_vrm, + /* CPU Core voltage [mV] */ + ec_sensor_in_cpu_core, + /* CPU_Opt fan [RPM] */ + ec_sensor_fan_cpu_opt, + /* VRM heat sink fan [RPM] */ + ec_sensor_fan_vrm_hs, + /* Chipset fan [RPM] */ + ec_sensor_fan_chipset, + /* Water flow sensor reading [RPM] */ + ec_sensor_fan_water_flow, + /* CPU current [A] */ + ec_sensor_curr_cpu, + /* "Water_In" temperature sensor reading [℃] */ + ec_sensor_temp_water_in, + /* "Water_Out" temperature sensor reading [℃] */ + ec_sensor_temp_water_out, +}; + +#define SENSOR_TEMP_CHIPSET BIT(ec_sensor_temp_chipset) +#define SENSOR_TEMP_CPU BIT(ec_sensor_temp_cpu) +#define SENSOR_TEMP_MB BIT(ec_sensor_temp_mb) +#define SENSOR_TEMP_T_SENSOR BIT(ec_sensor_temp_t_sensor) +#define SENSOR_TEMP_VRM BIT(ec_sensor_temp_vrm) +#define SENSOR_IN_CPU_CORE BIT(ec_sensor_in_cpu_core) +#define SENSOR_FAN_CPU_OPT BIT(ec_sensor_fan_cpu_opt) +#define SENSOR_FAN_VRM_HS BIT(ec_sensor_fan_vrm_hs) +#define SENSOR_FAN_CHIPSET BIT(ec_sensor_fan_chipset) +#define SENSOR_FAN_WATER_FLOW BIT(ec_sensor_fan_water_flow) +#define SENSOR_CURR_CPU BIT(ec_sensor_curr_cpu) +#define SENSOR_TEMP_WATER_IN BIT(ec_sensor_temp_water_in) +#define SENSOR_TEMP_WATER_OUT BIT(ec_sensor_temp_water_out) + +/* All the known sensors for ASUS EC controllers */ +static const struct ec_sensor_info known_ec_sensors[] = { + [ec_sensor_temp_chipset] = + EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a), + [ec_sensor_temp_cpu] = EC_SENSOR("CPU", hwmon_temp, 1, 0x00, 0x3b), + [ec_sensor_temp_mb] = + EC_SENSOR("Motherboard", hwmon_temp, 1, 0x00, 0x3c), + [ec_sensor_temp_t_sensor] = + EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x3d), + [ec_sensor_temp_vrm] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x3e), + [ec_sensor_in_cpu_core] = + EC_SENSOR("CPU Core", hwmon_in, 2, 0x00, 0xa2), + [ec_sensor_fan_cpu_opt] = + EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0), + [ec_sensor_fan_vrm_hs] = EC_SENSOR("VRM HS", hwmon_fan, 2, 0x00, 0xb2), + [ec_sensor_fan_chipset] = + EC_SENSOR("Chipset", hwmon_fan, 2, 0x00, 0xb4), + [ec_sensor_fan_water_flow] = + EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xbc), + [ec_sensor_curr_cpu] = EC_SENSOR("CPU", hwmon_curr, 1, 0x00, 0xf4), + [ec_sensor_temp_water_in] = + EC_SENSOR("Water_In", hwmon_temp, 1, 0x01, 0x00), + [ec_sensor_temp_water_out] = + EC_SENSOR("Water_Out", hwmon_temp, 1, 0x01, 0x01), +}; + +/* Shortcuts for common combinations */ +#define SENSOR_SET_TEMP_CHIPSET_CPU_MB \ + (SENSOR_TEMP_CHIPSET | SENSOR_TEMP_CPU | SENSOR_TEMP_MB) +#define SENSOR_SET_TEMP_WATER (SENSOR_TEMP_WATER_IN | SENSOR_TEMP_WATER_OUT) + +#define DMI_EXACT_MATCH_BOARD(vendor, name, sensors) { \ + .matches = { \ + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, vendor), \ + DMI_EXACT_MATCH(DMI_BOARD_NAME, name), \ + }, \ + .driver_data = (void *)(sensors), \ +} + +static const struct dmi_system_id asus_ec_dmi_table[] __initconst = { + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "PRIME X570-PRO", + SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | + SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET), + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "Pro WS X570-ACE", + SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | + SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, + "ROG CROSSHAIR VIII DARK HERO", + SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | + SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | + SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW | + SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, + "ROG CROSSHAIR VIII FORMULA", + SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | + SENSOR_TEMP_VRM | SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET | + SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG CROSSHAIR VIII HERO", + SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | + SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | + SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET | + SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, + "ROG CROSSHAIR VIII HERO (WI-FI)", + SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | + SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | + SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET | + SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, + "ROG CROSSHAIR VIII IMPACT", + SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | + SENSOR_TEMP_VRM | SENSOR_FAN_CHIPSET | + SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX B550-E GAMING", + SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | + SENSOR_TEMP_VRM | SENSOR_FAN_CPU_OPT), + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX B550-I GAMING", + SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | + SENSOR_TEMP_VRM | SENSOR_FAN_VRM_HS | + SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX X570-E GAMING", + SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | + SENSOR_TEMP_VRM | SENSOR_FAN_CHIPSET | + SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX X570-F GAMING", + SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET), + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX X570-I GAMING", + SENSOR_TEMP_T_SENSOR | SENSOR_FAN_VRM_HS | + SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), + {} +}; + +struct ec_sensor { + unsigned int info_index; + s32 cached_value; +}; + +struct ec_sensors_data { + unsigned long board_sensors; + struct ec_sensor *sensors; + /* EC registers to read from */ + u16 *registers; + u8 *read_buffer; + /* sorted list of unique register banks */ + u8 banks[ASUS_EC_MAX_BANK + 1]; + /* in jiffies */ + unsigned long last_updated; + acpi_handle aml_mutex; + /* number of board EC sensors */ + u8 nr_sensors; + /* + * number of EC registers to read + * (sensor might span more than 1 register) + */ + u8 nr_registers; + /* number of unique register banks */ + u8 nr_banks; +}; + +static u8 register_bank(u16 reg) +{ + return reg >> 8; +} + +static u8 register_index(u16 reg) +{ + return reg & 0x00ff; +} + +static bool is_sensor_data_signed(const struct ec_sensor_info *si) +{ + /* + * guessed from WMI functions in DSDT code for boards + * of the X470 generation + */ + return si->type == hwmon_temp; +} + +static const struct ec_sensor_info * +get_sensor_info(const struct ec_sensors_data *state, int index) +{ + return &known_ec_sensors[state->sensors[index].info_index]; +} + +static int find_ec_sensor_index(const struct ec_sensors_data *ec, + enum hwmon_sensor_types type, int channel) +{ + unsigned int i; + + for (i = 0; i < ec->nr_sensors; i++) { + if (get_sensor_info(ec, i)->type == type) { + if (channel == 0) + return i; + channel--; + } + } + return -ENOENT; +} + +static int __init bank_compare(const void *a, const void *b) +{ + return *((const s8 *)a) - *((const s8 *)b); +} + +static int __init board_sensors_count(unsigned long sensors) +{ + return hweight_long(sensors); +} + +static void __init setup_sensor_data(struct ec_sensors_data *ec) +{ + struct ec_sensor *s = ec->sensors; + bool bank_found; + int i, j; + u8 bank; + + ec->nr_banks = 0; + ec->nr_registers = 0; + + for_each_set_bit(i, &ec->board_sensors, + BITS_PER_TYPE(ec->board_sensors)) { + s->info_index = i; + s->cached_value = 0; + ec->nr_registers += + known_ec_sensors[s->info_index].addr.components.size; + bank_found = false; + bank = known_ec_sensors[s->info_index].addr.components.bank; + for (j = 0; j < ec->nr_banks; j++) { + if (ec->banks[j] == bank) { + bank_found = true; + break; + } + } + if (!bank_found) { + ec->banks[ec->nr_banks++] = bank; + } + s++; + } + sort(ec->banks, ec->nr_banks, 1, bank_compare, NULL); +} + +static void __init fill_ec_registers(struct ec_sensors_data *ec) +{ + const struct ec_sensor_info *si; + unsigned int i, j, register_idx = 0; + + for (i = 0; i < ec->nr_sensors; ++i) { + si = get_sensor_info(ec, i); + for (j = 0; j < si->addr.components.size; ++j, ++register_idx) { + ec->registers[register_idx] = + (si->addr.components.bank << 8) + + si->addr.components.index + j; + } + } +} + +static acpi_handle __init asus_hw_access_mutex(struct device *dev) +{ + const char *mutex_path; + acpi_handle res; + int status; + + mutex_path = mutex_path_override ? + mutex_path_override : ASUS_HW_ACCESS_MUTEX_ASMX; + + status = acpi_get_handle(NULL, (acpi_string)mutex_path, &res); + if (ACPI_FAILURE(status)) { + dev_err(dev, + "Could not get hardware access guard mutex '%s': error %d", + mutex_path, status); + return NULL; + } + return res; +} + +static int asus_ec_bank_switch(u8 bank, u8 *old) +{ + int status = 0; + + if (old) { + status = ec_read(ASUS_EC_BANK_REGISTER, old); + } + if (status || (old && (*old == bank))) + return status; + return ec_write(ASUS_EC_BANK_REGISTER, bank); +} + +static int asus_ec_block_read(const struct device *dev, + struct ec_sensors_data *ec) +{ + int ireg, ibank, status; + u8 bank, reg_bank, prev_bank; + + bank = 0; + status = asus_ec_bank_switch(bank, &prev_bank); + if (status) { + dev_warn(dev, "EC bank switch failed"); + return status; + } + + if (prev_bank) { + /* oops... somebody else is working with the EC too */ + dev_warn(dev, + "Concurrent access to the ACPI EC detected.\nRace condition possible."); + } + + /* read registers minimizing bank switches. */ + for (ibank = 0; ibank < ec->nr_banks; ibank++) { + if (bank != ec->banks[ibank]) { + bank = ec->banks[ibank]; + if (asus_ec_bank_switch(bank, NULL)) { + dev_warn(dev, "EC bank switch to %d failed", + bank); + break; + } + } + for (ireg = 0; ireg < ec->nr_registers; ireg++) { + reg_bank = register_bank(ec->registers[ireg]); + if (reg_bank < bank) { + continue; + } + ec_read(register_index(ec->registers[ireg]), + ec->read_buffer + ireg); + } + } + + status = asus_ec_bank_switch(prev_bank, NULL); + return status; +} + +static inline s32 get_sensor_value(const struct ec_sensor_info *si, u8 *data) +{ + if (is_sensor_data_signed(si)) { + switch (si->addr.components.size) { + case 1: + return (s8)*data; + case 2: + return (s16)get_unaligned_be16(data); + case 4: + return (s32)get_unaligned_be32(data); + default: + return 0; + } + } else { + switch (si->addr.components.size) { + case 1: + return *data; + case 2: + return get_unaligned_be16(data); + case 4: + return get_unaligned_be32(data); + default: + return 0; + } + } +} + +static void update_sensor_values(struct ec_sensors_data *ec, u8 *data) +{ + const struct ec_sensor_info *si; + struct ec_sensor *s; + + for (s = ec->sensors; s != ec->sensors + ec->nr_sensors; s++) { + si = &known_ec_sensors[s->info_index]; + s->cached_value = get_sensor_value(si, data); + data += si->addr.components.size; + } +} + +static int update_ec_sensors(const struct device *dev, + struct ec_sensors_data *ec) +{ + int status; + + /* + * ASUS DSDT does not specify that access to the EC has to be guarded, + * but firmware does access it via ACPI + */ + if (ACPI_FAILURE(acpi_acquire_mutex(ec->aml_mutex, NULL, + ACPI_LOCK_DELAY_MS))) { + dev_err(dev, "Failed to acquire AML mutex"); + status = -EBUSY; + goto cleanup; + } + + status = asus_ec_block_read(dev, ec); + + if (!status) { + update_sensor_values(ec, ec->read_buffer); + } + if (ACPI_FAILURE(acpi_release_mutex(ec->aml_mutex, NULL))) { + dev_err(dev, "Failed to release AML mutex"); + } +cleanup: + return status; +} + +static long scale_sensor_value(s32 value, int data_type) +{ + switch (data_type) { + case hwmon_curr: + case hwmon_temp: + return value * MILLI; + default: + return value; + } +} + +static int get_cached_value_or_update(const struct device *dev, + int sensor_index, + struct ec_sensors_data *state, s32 *value) +{ + if (time_after(jiffies, state->last_updated + HZ)) { + if (update_ec_sensors(dev, state)) { + dev_err(dev, "update_ec_sensors() failure\n"); + return -EIO; + } + + state->last_updated = jiffies; + } + + *value = state->sensors[sensor_index].cached_value; + return 0; +} + +/* + * Now follow the functions that implement the hwmon interface + */ + +static int asus_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + int ret; + s32 value = 0; + + struct ec_sensors_data *state = dev_get_drvdata(dev); + int sidx = find_ec_sensor_index(state, type, channel); + + if (sidx < 0) { + return sidx; + } + + ret = get_cached_value_or_update(dev, sidx, state, &value); + if (!ret) { + *val = scale_sensor_value(value, + get_sensor_info(state, sidx)->type); + } + + return ret; +} + +static int asus_ec_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + struct ec_sensors_data *state = dev_get_drvdata(dev); + int sensor_index = find_ec_sensor_index(state, type, channel); + *str = get_sensor_info(state, sensor_index)->label; + + return 0; +} + +static umode_t asus_ec_hwmon_is_visible(const void *drvdata, + enum hwmon_sensor_types type, u32 attr, + int channel) +{ + const struct ec_sensors_data *state = drvdata; + + return find_ec_sensor_index(state, type, channel) >= 0 ? S_IRUGO : 0; +} + +static int __init +asus_ec_hwmon_add_chan_info(struct hwmon_channel_info *asus_ec_hwmon_chan, + struct device *dev, int num, + enum hwmon_sensor_types type, u32 config) +{ + int i; + u32 *cfg = devm_kcalloc(dev, num + 1, sizeof(*cfg), GFP_KERNEL); + + if (!cfg) + return -ENOMEM; + + asus_ec_hwmon_chan->type = type; + asus_ec_hwmon_chan->config = cfg; + for (i = 0; i < num; i++, cfg++) + *cfg = config; + + return 0; +} + +static const struct hwmon_ops asus_ec_hwmon_ops = { + .is_visible = asus_ec_hwmon_is_visible, + .read = asus_ec_hwmon_read, + .read_string = asus_ec_hwmon_read_string, +}; + +static struct hwmon_chip_info asus_ec_chip_info = { + .ops = &asus_ec_hwmon_ops, +}; + +static unsigned long __init get_board_sensors(void) +{ + const struct dmi_system_id *dmi_entry = + dmi_first_match(asus_ec_dmi_table); + + return dmi_entry ? (unsigned long)dmi_entry->driver_data : 0; +} + +static int __init asus_ec_probe(struct platform_device *pdev) +{ + const struct hwmon_channel_info **ptr_asus_ec_ci; + int nr_count[hwmon_max] = { 0 }, nr_types = 0; + struct hwmon_channel_info *asus_ec_hwmon_chan; + const struct hwmon_chip_info *chip_info; + struct device *dev = &pdev->dev; + struct ec_sensors_data *ec_data; + const struct ec_sensor_info *si; + enum hwmon_sensor_types type; + unsigned long board_sensors; + struct device *hwdev; + unsigned int i; + + board_sensors = get_board_sensors(); + if (!board_sensors) + return -ENODEV; + + ec_data = devm_kzalloc(dev, sizeof(struct ec_sensors_data), + GFP_KERNEL); + if (!ec_data) + return -ENOMEM; + + dev_set_drvdata(dev, ec_data); + ec_data->board_sensors = board_sensors; + ec_data->nr_sensors = board_sensors_count(ec_data->board_sensors); + ec_data->sensors = devm_kcalloc(dev, ec_data->nr_sensors, + sizeof(struct ec_sensor), GFP_KERNEL); + + setup_sensor_data(ec_data); + ec_data->registers = devm_kcalloc(dev, ec_data->nr_registers, + sizeof(u16), GFP_KERNEL); + ec_data->read_buffer = devm_kcalloc(dev, ec_data->nr_registers, + sizeof(u8), GFP_KERNEL); + + if (!ec_data->registers || !ec_data->read_buffer) + return -ENOMEM; + + fill_ec_registers(ec_data); + + ec_data->aml_mutex = asus_hw_access_mutex(dev); + + for (i = 0; i < ec_data->nr_sensors; ++i) { + si = get_sensor_info(ec_data, i); + if (!nr_count[si->type]) + ++nr_types; + ++nr_count[si->type]; + } + + if (nr_count[hwmon_temp]) + nr_count[hwmon_chip]++, nr_types++; + + asus_ec_hwmon_chan = devm_kcalloc( + dev, nr_types, sizeof(*asus_ec_hwmon_chan), GFP_KERNEL); + if (!asus_ec_hwmon_chan) + return -ENOMEM; + + ptr_asus_ec_ci = devm_kcalloc(dev, nr_types + 1, + sizeof(*ptr_asus_ec_ci), GFP_KERNEL); + if (!ptr_asus_ec_ci) + return -ENOMEM; + + asus_ec_chip_info.info = ptr_asus_ec_ci; + chip_info = &asus_ec_chip_info; + + for (type = 0; type < hwmon_max; ++type) { + if (!nr_count[type]) + continue; + + asus_ec_hwmon_add_chan_info(asus_ec_hwmon_chan, dev, + nr_count[type], type, + hwmon_attributes[type]); + *ptr_asus_ec_ci++ = asus_ec_hwmon_chan++; + } + + dev_info(dev, "board has %d EC sensors that span %d registers", + ec_data->nr_sensors, ec_data->nr_registers); + + hwdev = devm_hwmon_device_register_with_info(dev, "asusec", + ec_data, chip_info, NULL); + + return PTR_ERR_OR_ZERO(hwdev); +} + + +static const struct acpi_device_id acpi_ec_ids[] = { + /* Embedded Controller Device */ + { "PNP0C09", 0 }, + {} +}; + +static struct platform_driver asus_ec_sensors_platform_driver = { + .driver = { + .name = "asus-ec-sensors", + .acpi_match_table = acpi_ec_ids, + }, +}; + +MODULE_DEVICE_TABLE(dmi, asus_ec_dmi_table); +module_platform_driver_probe(asus_ec_sensors_platform_driver, asus_ec_probe); + +module_param_named(mutex_path, mutex_path_override, charp, 0); +MODULE_PARM_DESC(mutex_path, + "Override ACPI mutex path used to guard access to hardware"); + +MODULE_AUTHOR("Eugene Shalygin <eugene.shalygin@gmail.com>"); +MODULE_DESCRIPTION( + "HWMON driver for sensors accessible via ACPI EC in ASUS motherboards"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/asus_wmi_ec_sensors.c b/drivers/hwmon/asus_wmi_ec_sensors.c new file mode 100644 index 000000000000..a3a2f014dec0 --- /dev/null +++ b/drivers/hwmon/asus_wmi_ec_sensors.c @@ -0,0 +1,622 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HWMON driver for ASUS B550/X570 motherboards that publish sensor + * values via the embedded controller registers. + * + * Copyright (C) 2021 Eugene Shalygin <eugene.shalygin@gmail.com> + * Copyright (C) 2018-2019 Ed Brindley <kernel@maidavale.org> + * + * EC provides: + * - Chipset temperature + * - CPU temperature + * - Motherboard temperature + * - T_Sensor temperature + * - VRM temperature + * - Water In temperature + * - Water Out temperature + * - CPU Optional Fan RPM + * - Chipset Fan RPM + * - Water Flow Fan RPM + * - CPU current + */ + +#include <linux/acpi.h> +#include <linux/dmi.h> +#include <linux/hwmon.h> +#include <linux/init.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/nls.h> +#include <linux/units.h> +#include <linux/wmi.h> + +#include <asm/unaligned.h> + +#define ASUSWMI_MONITORING_GUID "466747A0-70EC-11DE-8A39-0800200C9A66" +#define ASUSWMI_METHODID_BLOCK_READ_EC 0x42524543 /* BREC */ +/* From the ASUS DSDT source */ +#define ASUSWMI_BREC_REGISTERS_MAX 16 +#define ASUSWMI_MAX_BUF_LEN 128 +#define SENSOR_LABEL_LEN 16 + +static u32 hwmon_attributes[hwmon_max] = { + [hwmon_chip] = HWMON_C_REGISTER_TZ, + [hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL, + [hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL, + [hwmon_curr] = HWMON_C_INPUT | HWMON_C_LABEL, + [hwmon_fan] = HWMON_F_INPUT | HWMON_F_LABEL, +}; + +struct asus_wmi_ec_sensor_address { + u8 index; + u8 bank; + u8 size; +}; + +#define MAKE_SENSOR_ADDRESS(size_i, bank_i, index_i) { \ + .size = size_i, \ + .bank = bank_i, \ + .index = index_i, \ +} + +struct ec_sensor_info { + struct asus_wmi_ec_sensor_address addr; + char label[SENSOR_LABEL_LEN]; + enum hwmon_sensor_types type; +}; + +#define EC_SENSOR(sensor_label, sensor_type, size, bank, index) { \ + .addr = MAKE_SENSOR_ADDRESS(size, bank, index), \ + .label = sensor_label, \ + .type = sensor_type, \ +} + +enum known_ec_sensor { + SENSOR_TEMP_CHIPSET, + SENSOR_TEMP_CPU, + SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, + SENSOR_TEMP_VRM, + SENSOR_FAN_CPU_OPT, + SENSOR_FAN_CHIPSET, + SENSOR_FAN_VRM_HS, + SENSOR_FAN_WATER_FLOW, + SENSOR_CURR_CPU, + SENSOR_TEMP_WATER_IN, + SENSOR_TEMP_WATER_OUT, + SENSOR_MAX +}; + +/* All known sensors for ASUS EC controllers */ +static const struct ec_sensor_info known_ec_sensors[] = { + [SENSOR_TEMP_CHIPSET] = EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a), + [SENSOR_TEMP_CPU] = EC_SENSOR("CPU", hwmon_temp, 1, 0x00, 0x3b), + [SENSOR_TEMP_MB] = EC_SENSOR("Motherboard", hwmon_temp, 1, 0x00, 0x3c), + [SENSOR_TEMP_T_SENSOR] = EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x3d), + [SENSOR_TEMP_VRM] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x3e), + [SENSOR_FAN_CPU_OPT] = EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0), + [SENSOR_FAN_VRM_HS] = EC_SENSOR("VRM HS", hwmon_fan, 2, 0x00, 0xb2), + [SENSOR_FAN_CHIPSET] = EC_SENSOR("Chipset", hwmon_fan, 2, 0x00, 0xb4), + [SENSOR_FAN_WATER_FLOW] = EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xbc), + [SENSOR_CURR_CPU] = EC_SENSOR("CPU", hwmon_curr, 1, 0x00, 0xf4), + [SENSOR_TEMP_WATER_IN] = EC_SENSOR("Water_In", hwmon_temp, 1, 0x01, 0x00), + [SENSOR_TEMP_WATER_OUT] = EC_SENSOR("Water_Out", hwmon_temp, 1, 0x01, 0x01), +}; + +struct asus_wmi_data { + const enum known_ec_sensor known_board_sensors[SENSOR_MAX + 1]; +}; + +/* boards with EC support */ +static struct asus_wmi_data sensors_board_PW_X570_P = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_FAN_CHIPSET, + SENSOR_MAX + }, +}; + +static struct asus_wmi_data sensors_board_PW_X570_A = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, SENSOR_TEMP_VRM, + SENSOR_FAN_CHIPSET, + SENSOR_CURR_CPU, + SENSOR_MAX + }, +}; + +static struct asus_wmi_data sensors_board_R_C8H = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_TEMP_WATER_IN, SENSOR_TEMP_WATER_OUT, + SENSOR_FAN_CPU_OPT, SENSOR_FAN_CHIPSET, SENSOR_FAN_WATER_FLOW, + SENSOR_CURR_CPU, + SENSOR_MAX + }, +}; + +/* Same as Hero but without chipset fan */ +static struct asus_wmi_data sensors_board_R_C8DH = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_TEMP_WATER_IN, SENSOR_TEMP_WATER_OUT, + SENSOR_FAN_CPU_OPT, SENSOR_FAN_WATER_FLOW, + SENSOR_CURR_CPU, + SENSOR_MAX + }, +}; + +/* Same as Hero but without water */ +static struct asus_wmi_data sensors_board_R_C8F = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_FAN_CPU_OPT, SENSOR_FAN_CHIPSET, + SENSOR_CURR_CPU, + SENSOR_MAX + }, +}; + +static struct asus_wmi_data sensors_board_RS_B550_E_G = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_FAN_CPU_OPT, + SENSOR_MAX + }, +}; + +static struct asus_wmi_data sensors_board_RS_B550_I_G = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_FAN_VRM_HS, + SENSOR_CURR_CPU, + SENSOR_MAX + }, +}; + +static struct asus_wmi_data sensors_board_RS_X570_E_G = { + .known_board_sensors = { + SENSOR_TEMP_CHIPSET, SENSOR_TEMP_CPU, SENSOR_TEMP_MB, + SENSOR_TEMP_T_SENSOR, SENSOR_TEMP_VRM, + SENSOR_FAN_CHIPSET, + SENSOR_CURR_CPU, + SENSOR_MAX + }, +}; + +#define DMI_EXACT_MATCH_ASUS_BOARD_NAME(name, sensors) { \ + .matches = { \ + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), \ + DMI_EXACT_MATCH(DMI_BOARD_NAME, name), \ + }, \ + .driver_data = sensors, \ +} + +static const struct dmi_system_id asus_wmi_ec_dmi_table[] = { + DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X570-PRO", &sensors_board_PW_X570_P), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS X570-ACE", &sensors_board_PW_X570_A), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII DARK HERO", &sensors_board_R_C8DH), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII FORMULA", &sensors_board_R_C8F), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII HERO", &sensors_board_R_C8H), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-E GAMING", &sensors_board_RS_B550_E_G), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-I GAMING", &sensors_board_RS_B550_I_G), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-E GAMING", &sensors_board_RS_X570_E_G), + {} +}; +MODULE_DEVICE_TABLE(dmi, asus_wmi_ec_dmi_table); + +struct ec_sensor { + enum known_ec_sensor info_index; + long cached_value; +}; + +/** + * struct asus_wmi_ec_info - sensor info. + * @sensors: list of sensors. + * @read_arg: UTF-16LE string to pass to BRxx() WMI function. + * @read_buffer: decoded output from WMI result. + * @nr_sensors: number of board EC sensors. + * @nr_registers: number of EC registers to read (sensor might span more than 1 register). + * @last_updated: in jiffies. + */ +struct asus_wmi_ec_info { + struct ec_sensor sensors[SENSOR_MAX]; + char read_arg[(ASUSWMI_BREC_REGISTERS_MAX * 4 + 1) * 2]; + u8 read_buffer[ASUSWMI_BREC_REGISTERS_MAX]; + unsigned int nr_sensors; + unsigned int nr_registers; + unsigned long last_updated; +}; + +struct asus_wmi_sensors { + struct asus_wmi_ec_info ec; + /* lock access to internal cache */ + struct mutex lock; +}; + +static int asus_wmi_ec_fill_board_sensors(struct asus_wmi_ec_info *ec, + const enum known_ec_sensor *bsi) +{ + struct ec_sensor *s = ec->sensors; + int i; + + ec->nr_sensors = 0; + ec->nr_registers = 0; + + for (i = 0; bsi[i] != SENSOR_MAX; i++) { + s[i].info_index = bsi[i]; + ec->nr_sensors++; + ec->nr_registers += known_ec_sensors[bsi[i]].addr.size; + } + + return 0; +} + +/* + * The next four functions convert to or from BRxx string argument format. + * The format of the string is as follows: + * - The string consists of two-byte UTF-16LE characters. + * - The value of the very first byte in the string is equal to the total + * length of the next string in bytes, thus excluding the first two-byte + * character. + * - The rest of the string encodes the pairs of (bank, index) pairs, where + * both values are byte-long (0x00 to 0xFF). + * - Numbers are encoded as UTF-16LE hex values. + */ +static int asus_wmi_ec_decode_reply_buffer(const u8 *in, u32 length, u8 *out) +{ + char buffer[ASUSWMI_MAX_BUF_LEN * 2]; + u32 len = min_t(u32, get_unaligned_le16(in), length - 2); + + utf16s_to_utf8s((wchar_t *)(in + 2), len / 2, UTF16_LITTLE_ENDIAN, buffer, sizeof(buffer)); + + return hex2bin(out, buffer, len / 4); +} + +static void asus_wmi_ec_encode_registers(const u8 *in, u32 len, char *out) +{ + char buffer[ASUSWMI_MAX_BUF_LEN * 2]; + + bin2hex(buffer, in, len); + + utf8s_to_utf16s(buffer, len * 2, UTF16_LITTLE_ENDIAN, (wchar_t *)(out + 2), len * 2); + + put_unaligned_le16(len * 4, out); +} + +static void asus_wmi_ec_make_block_read_query(struct asus_wmi_ec_info *ec) +{ + u8 registers[ASUSWMI_BREC_REGISTERS_MAX * 2]; + const struct ec_sensor_info *si; + int i, j, offset; + + offset = 0; + for (i = 0; i < ec->nr_sensors; i++) { + si = &known_ec_sensors[ec->sensors[i].info_index]; + for (j = 0; j < si->addr.size; j++) { + registers[offset++] = si->addr.bank; + registers[offset++] = si->addr.index + j; + } + } + + asus_wmi_ec_encode_registers(registers, offset, ec->read_arg); +} + +static int asus_wmi_ec_block_read(u32 method_id, char *query, u8 *out) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_buffer input; + union acpi_object *obj; + acpi_status status; + int ret; + + /* The first byte of the BRxx() argument string has to be the string size. */ + input.length = query[0] + 2; + input.pointer = query; + status = wmi_evaluate_method(ASUSWMI_MONITORING_GUID, 0, method_id, &input, &output); + if (ACPI_FAILURE(status)) + return -EIO; + + obj = output.pointer; + if (!obj) + return -EIO; + + if (obj->type != ACPI_TYPE_BUFFER || obj->buffer.length < 2) { + ret = -EIO; + goto out_free_obj; + } + + ret = asus_wmi_ec_decode_reply_buffer(obj->buffer.pointer, obj->buffer.length, out); + +out_free_obj: + ACPI_FREE(obj); + return ret; +} + +static inline long get_sensor_value(const struct ec_sensor_info *si, u8 *data) +{ + switch (si->addr.size) { + case 1: + return *data; + case 2: + return get_unaligned_be16(data); + case 4: + return get_unaligned_be32(data); + default: + return 0; + } +} + +static void asus_wmi_ec_update_ec_sensors(struct asus_wmi_ec_info *ec) +{ + const struct ec_sensor_info *si; + struct ec_sensor *s; + u8 i_sensor; + u8 *data; + + data = ec->read_buffer; + for (i_sensor = 0; i_sensor < ec->nr_sensors; i_sensor++) { + s = &ec->sensors[i_sensor]; + si = &known_ec_sensors[s->info_index]; + s->cached_value = get_sensor_value(si, data); + data += si->addr.size; + } +} + +static long asus_wmi_ec_scale_sensor_value(long value, int data_type) +{ + switch (data_type) { + case hwmon_curr: + case hwmon_temp: + case hwmon_in: + return value * MILLI; + default: + return value; + } +} + +static int asus_wmi_ec_find_sensor_index(const struct asus_wmi_ec_info *ec, + enum hwmon_sensor_types type, int channel) +{ + int i; + + for (i = 0; i < ec->nr_sensors; i++) { + if (known_ec_sensors[ec->sensors[i].info_index].type == type) { + if (channel == 0) + return i; + + channel--; + } + } + return -EINVAL; +} + +static int asus_wmi_ec_get_cached_value_or_update(struct asus_wmi_sensors *sensor_data, + int sensor_index, + long *value) +{ + struct asus_wmi_ec_info *ec = &sensor_data->ec; + int ret = 0; + + mutex_lock(&sensor_data->lock); + + if (time_after(jiffies, ec->last_updated + HZ)) { + ret = asus_wmi_ec_block_read(ASUSWMI_METHODID_BLOCK_READ_EC, + ec->read_arg, ec->read_buffer); + if (ret) + goto unlock; + + asus_wmi_ec_update_ec_sensors(ec); + ec->last_updated = jiffies; + } + + *value = ec->sensors[sensor_index].cached_value; + +unlock: + mutex_unlock(&sensor_data->lock); + + return ret; +} + +/* Now follow the functions that implement the hwmon interface */ + +static int asus_wmi_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); + struct asus_wmi_ec_info *ec = &sensor_data->ec; + int ret, sidx, info_index; + long value = 0; + + sidx = asus_wmi_ec_find_sensor_index(ec, type, channel); + if (sidx < 0) + return sidx; + + ret = asus_wmi_ec_get_cached_value_or_update(sensor_data, sidx, &value); + if (ret) + return ret; + + info_index = ec->sensors[sidx].info_index; + *val = asus_wmi_ec_scale_sensor_value(value, known_ec_sensors[info_index].type); + + return ret; +} + +static int asus_wmi_ec_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); + struct asus_wmi_ec_info *ec = &sensor_data->ec; + int sensor_index; + + sensor_index = asus_wmi_ec_find_sensor_index(ec, type, channel); + *str = known_ec_sensors[ec->sensors[sensor_index].info_index].label; + + return 0; +} + +static umode_t asus_wmi_ec_hwmon_is_visible(const void *drvdata, + enum hwmon_sensor_types type, u32 attr, + int channel) +{ + const struct asus_wmi_sensors *sensor_data = drvdata; + const struct asus_wmi_ec_info *ec = &sensor_data->ec; + int index; + + index = asus_wmi_ec_find_sensor_index(ec, type, channel); + + return index < 0 ? 0 : 0444; +} + +static int asus_wmi_hwmon_add_chan_info(struct hwmon_channel_info *asus_wmi_hwmon_chan, + struct device *dev, int num, + enum hwmon_sensor_types type, u32 config) +{ + u32 *cfg; + + cfg = devm_kcalloc(dev, num + 1, sizeof(*cfg), GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + asus_wmi_hwmon_chan->type = type; + asus_wmi_hwmon_chan->config = cfg; + memset32(cfg, config, num); + + return 0; +} + +static const struct hwmon_ops asus_wmi_ec_hwmon_ops = { + .is_visible = asus_wmi_ec_hwmon_is_visible, + .read = asus_wmi_ec_hwmon_read, + .read_string = asus_wmi_ec_hwmon_read_string, +}; + +static struct hwmon_chip_info asus_wmi_ec_chip_info = { + .ops = &asus_wmi_ec_hwmon_ops, +}; + +static int asus_wmi_ec_configure_sensor_setup(struct device *dev, + const enum known_ec_sensor *bsi) +{ + struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); + struct asus_wmi_ec_info *ec = &sensor_data->ec; + struct hwmon_channel_info *asus_wmi_hwmon_chan; + const struct hwmon_channel_info **asus_wmi_ci; + int nr_count[hwmon_max] = {}, nr_types = 0; + const struct hwmon_chip_info *chip_info; + const struct ec_sensor_info *si; + enum hwmon_sensor_types type; + struct device *hwdev; + int i, ret; + + ret = asus_wmi_ec_fill_board_sensors(ec, bsi); + if (ret) + return ret; + + if (!sensor_data->ec.nr_sensors) + return -ENODEV; + + for (i = 0; i < ec->nr_sensors; i++) { + si = &known_ec_sensors[ec->sensors[i].info_index]; + if (!nr_count[si->type]) + nr_types++; + nr_count[si->type]++; + } + + if (nr_count[hwmon_temp]) { + nr_count[hwmon_chip]++; + nr_types++; + } + + /* + * If we can get values for all the registers in a single query, + * the query will not change from call to call. + */ + asus_wmi_ec_make_block_read_query(ec); + + asus_wmi_hwmon_chan = devm_kcalloc(dev, nr_types, sizeof(*asus_wmi_hwmon_chan), + GFP_KERNEL); + if (!asus_wmi_hwmon_chan) + return -ENOMEM; + + asus_wmi_ci = devm_kcalloc(dev, nr_types + 1, sizeof(*asus_wmi_ci), GFP_KERNEL); + if (!asus_wmi_ci) + return -ENOMEM; + + asus_wmi_ec_chip_info.info = asus_wmi_ci; + chip_info = &asus_wmi_ec_chip_info; + + for (type = 0; type < hwmon_max; type++) { + if (!nr_count[type]) + continue; + + ret = asus_wmi_hwmon_add_chan_info(asus_wmi_hwmon_chan, dev, + nr_count[type], type, + hwmon_attributes[type]); + if (ret) + return ret; + + *asus_wmi_ci++ = asus_wmi_hwmon_chan++; + } + + dev_dbg(dev, "board has %d EC sensors that span %d registers", + ec->nr_sensors, ec->nr_registers); + + hwdev = devm_hwmon_device_register_with_info(dev, "asus_wmi_ec_sensors", + sensor_data, chip_info, NULL); + + return PTR_ERR_OR_ZERO(hwdev); +} + +static int asus_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct asus_wmi_sensors *sensor_data; + struct asus_wmi_data *board_sensors; + const struct dmi_system_id *dmi_id; + const enum known_ec_sensor *bsi; + struct device *dev = &wdev->dev; + + dmi_id = dmi_first_match(asus_wmi_ec_dmi_table); + if (!dmi_id) + return -ENODEV; + + board_sensors = dmi_id->driver_data; + bsi = board_sensors->known_board_sensors; + + sensor_data = devm_kzalloc(dev, sizeof(*sensor_data), GFP_KERNEL); + if (!sensor_data) + return -ENOMEM; + + mutex_init(&sensor_data->lock); + + dev_set_drvdata(dev, sensor_data); + + return asus_wmi_ec_configure_sensor_setup(dev, bsi); +} + +static const struct wmi_device_id asus_ec_wmi_id_table[] = { + { ASUSWMI_MONITORING_GUID, NULL }, + { } +}; + +static struct wmi_driver asus_sensors_wmi_driver = { + .driver = { + .name = "asus_wmi_ec_sensors", + }, + .id_table = asus_ec_wmi_id_table, + .probe = asus_wmi_probe, +}; +module_wmi_driver(asus_sensors_wmi_driver); + +MODULE_AUTHOR("Ed Brindley <kernel@maidavale.org>"); +MODULE_AUTHOR("Eugene Shalygin <eugene.shalygin@gmail.com>"); +MODULE_DESCRIPTION("Asus WMI Sensors Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/asus_wmi_sensors.c b/drivers/hwmon/asus_wmi_sensors.c new file mode 100644 index 000000000000..8fdcb62ae52d --- /dev/null +++ b/drivers/hwmon/asus_wmi_sensors.c @@ -0,0 +1,665 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HWMON driver for ASUS motherboards that provides sensor readouts via WMI + * interface present in the UEFI of the X370/X470/B450/X399 Ryzen motherboards. + * + * Copyright (C) 2018-2019 Ed Brindley <kernel@maidavale.org> + * + * WMI interface provides: + * - CPU Core Voltage, + * - CPU SOC Voltage, + * - DRAM Voltage, + * - VDDP Voltage, + * - 1.8V PLL Voltage, + * - +12V Voltage, + * - +5V Voltage, + * - 3VSB Voltage, + * - VBAT Voltage, + * - AVCC3 Voltage, + * - SB 1.05V Voltage, + * - CPU Core Voltage, + * - CPU SOC Voltage, + * - DRAM Voltage, + * - CPU Fan RPM, + * - Chassis Fan 1 RPM, + * - Chassis Fan 2 RPM, + * - Chassis Fan 3 RPM, + * - HAMP Fan RPM, + * - Water Pump RPM, + * - CPU OPT RPM, + * - Water Flow RPM, + * - AIO Pump RPM, + * - CPU Temperature, + * - CPU Socket Temperature, + * - Motherboard Temperature, + * - Chipset Temperature, + * - Tsensor 1 Temperature, + * - CPU VRM Temperature, + * - Water In, + * - Water Out, + * - CPU VRM Output Current. + */ + +#include <linux/acpi.h> +#include <linux/dmi.h> +#include <linux/hwmon.h> +#include <linux/init.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/units.h> +#include <linux/wmi.h> + +#define ASUSWMI_MONITORING_GUID "466747A0-70EC-11DE-8A39-0800200C9A66" +#define ASUSWMI_METHODID_GET_VALUE 0x52574543 /* RWEC */ +#define ASUSWMI_METHODID_UPDATE_BUFFER 0x51574543 /* QWEC */ +#define ASUSWMI_METHODID_GET_INFO 0x50574543 /* PWEC */ +#define ASUSWMI_METHODID_GET_NUMBER 0x50574572 /* PWEr */ +#define ASUSWMI_METHODID_GET_VERSION 0x50574574 /* PWEt */ + +#define ASUS_WMI_MAX_STR_SIZE 32 + +#define DMI_EXACT_MATCH_ASUS_BOARD_NAME(name) { \ + .matches = { \ + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), \ + DMI_EXACT_MATCH(DMI_BOARD_NAME, name), \ + }, \ +} + +static const struct dmi_system_id asus_wmi_dmi_table[] = { + DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X399-A"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X470-PRO"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VI EXTREME"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VI HERO"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VI HERO (WI-FI AC)"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VII HERO"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VII HERO (WI-FI)"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-E GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-F GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-F GAMING II"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B450-I GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X399-E GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X470-F GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X470-I GAMING"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH EXTREME"), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH EXTREME ALPHA"), + {} +}; +MODULE_DEVICE_TABLE(dmi, asus_wmi_dmi_table); + +enum asus_wmi_sensor_class { + VOLTAGE = 0x0, + TEMPERATURE_C = 0x1, + FAN_RPM = 0x2, + CURRENT = 0x3, + WATER_FLOW = 0x4, +}; + +enum asus_wmi_location { + CPU = 0x0, + CPU_SOC = 0x1, + DRAM = 0x2, + MOTHERBOARD = 0x3, + CHIPSET = 0x4, + AUX = 0x5, + VRM = 0x6, + COOLER = 0x7 +}; + +enum asus_wmi_type { + SIGNED_INT = 0x0, + UNSIGNED_INT = 0x1, + SCALED = 0x3, +}; + +enum asus_wmi_source { + SIO = 0x1, + EC = 0x2 +}; + +static enum hwmon_sensor_types asus_data_types[] = { + [VOLTAGE] = hwmon_in, + [TEMPERATURE_C] = hwmon_temp, + [FAN_RPM] = hwmon_fan, + [CURRENT] = hwmon_curr, + [WATER_FLOW] = hwmon_fan, +}; + +static u32 hwmon_attributes[hwmon_max] = { + [hwmon_chip] = HWMON_C_REGISTER_TZ, + [hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL, + [hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL, + [hwmon_curr] = HWMON_C_INPUT | HWMON_C_LABEL, + [hwmon_fan] = HWMON_F_INPUT | HWMON_F_LABEL, +}; + +/** + * struct asus_wmi_sensor_info - sensor info. + * @id: sensor id. + * @data_type: sensor class e.g. voltage, temp etc. + * @location: sensor location. + * @name: sensor name. + * @source: sensor source. + * @type: sensor type signed, unsigned etc. + * @cached_value: cached sensor value. + */ +struct asus_wmi_sensor_info { + u32 id; + int data_type; + int location; + char name[ASUS_WMI_MAX_STR_SIZE]; + int source; + int type; + long cached_value; +}; + +struct asus_wmi_wmi_info { + unsigned long source_last_updated[3]; /* in jiffies */ + int sensor_count; + + const struct asus_wmi_sensor_info **info[hwmon_max]; + struct asus_wmi_sensor_info **info_by_id; +}; + +struct asus_wmi_sensors { + struct asus_wmi_wmi_info wmi; + /* lock access to internal cache */ + struct mutex lock; +}; + +/* + * Universal method for calling WMI method + */ +static int asus_wmi_call_method(u32 method_id, u32 *args, struct acpi_buffer *output) +{ + struct acpi_buffer input = {(acpi_size) sizeof(*args), args }; + acpi_status status; + + status = wmi_evaluate_method(ASUSWMI_MONITORING_GUID, 0, + method_id, &input, output); + if (ACPI_FAILURE(status)) + return -EIO; + + return 0; +} + +/* + * Gets the version of the ASUS sensors interface implemented + */ +static int asus_wmi_get_version(u32 *version) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + u32 args[] = {0, 0, 0}; + union acpi_object *obj; + int err; + + err = asus_wmi_call_method(ASUSWMI_METHODID_GET_VERSION, args, &output); + if (err) + return err; + + obj = output.pointer; + if (!obj) + return -EIO; + + if (obj->type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + err = 0; + *version = obj->integer.value; + +out_free_obj: + ACPI_FREE(obj); + return err; +} + +/* + * Gets the number of sensor items + */ +static int asus_wmi_get_item_count(u32 *count) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + u32 args[] = {0, 0, 0}; + union acpi_object *obj; + int err; + + err = asus_wmi_call_method(ASUSWMI_METHODID_GET_NUMBER, args, &output); + if (err) + return err; + + obj = output.pointer; + if (!obj) + return -EIO; + + if (obj->type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + err = 0; + *count = obj->integer.value; + +out_free_obj: + ACPI_FREE(obj); + return err; +} + +static int asus_wmi_hwmon_add_chan_info(struct hwmon_channel_info *asus_wmi_hwmon_chan, + struct device *dev, int num, + enum hwmon_sensor_types type, u32 config) +{ + u32 *cfg; + + cfg = devm_kcalloc(dev, num + 1, sizeof(*cfg), GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + asus_wmi_hwmon_chan->type = type; + asus_wmi_hwmon_chan->config = cfg; + memset32(cfg, config, num); + + return 0; +} + +/* + * For a given sensor item returns details e.g. type (voltage/temperature/fan speed etc), bank etc + */ +static int asus_wmi_sensor_info(int index, struct asus_wmi_sensor_info *s) +{ + union acpi_object name_obj, data_type_obj, location_obj, source_obj, type_obj; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + u32 args[] = {index, 0}; + union acpi_object *obj; + int err; + + err = asus_wmi_call_method(ASUSWMI_METHODID_GET_INFO, args, &output); + if (err) + return err; + + s->id = index; + + obj = output.pointer; + if (!obj) + return -EIO; + + if (obj->type != ACPI_TYPE_PACKAGE) { + err = -EIO; + goto out_free_obj; + } + + if (obj->package.count != 5) { + err = -EIO; + goto out_free_obj; + } + + name_obj = obj->package.elements[0]; + if (name_obj.type != ACPI_TYPE_STRING) { + err = -EIO; + goto out_free_obj; + } + + strncpy(s->name, name_obj.string.pointer, sizeof(s->name) - 1); + + data_type_obj = obj->package.elements[1]; + if (data_type_obj.type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + s->data_type = data_type_obj.integer.value; + + location_obj = obj->package.elements[2]; + if (location_obj.type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + s->location = location_obj.integer.value; + + source_obj = obj->package.elements[3]; + if (source_obj.type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + s->source = source_obj.integer.value; + + type_obj = obj->package.elements[4]; + if (type_obj.type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + err = 0; + s->type = type_obj.integer.value; + +out_free_obj: + ACPI_FREE(obj); + return err; +} + +static int asus_wmi_update_buffer(int source) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + u32 args[] = {source, 0}; + + return asus_wmi_call_method(ASUSWMI_METHODID_UPDATE_BUFFER, args, &output); +} + +static int asus_wmi_get_sensor_value(u8 index, long *value) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + u32 args[] = {index, 0}; + union acpi_object *obj; + int err; + + err = asus_wmi_call_method(ASUSWMI_METHODID_GET_VALUE, args, &output); + if (err) + return err; + + obj = output.pointer; + if (!obj) + return -EIO; + + if (obj->type != ACPI_TYPE_INTEGER) { + err = -EIO; + goto out_free_obj; + } + + err = 0; + *value = obj->integer.value; + +out_free_obj: + ACPI_FREE(obj); + return err; +} + +static int asus_wmi_update_values_for_source(u8 source, struct asus_wmi_sensors *sensor_data) +{ + struct asus_wmi_sensor_info *sensor; + long value = 0; + int ret; + int i; + + for (i = 0; i < sensor_data->wmi.sensor_count; i++) { + sensor = sensor_data->wmi.info_by_id[i]; + if (sensor && sensor->source == source) { + ret = asus_wmi_get_sensor_value(sensor->id, &value); + if (ret) + return ret; + + sensor->cached_value = value; + } + } + + return 0; +} + +static int asus_wmi_scale_sensor_value(u32 value, int data_type) +{ + /* FAN_RPM and WATER_FLOW don't need scaling */ + switch (data_type) { + case VOLTAGE: + /* value in microVolts */ + return DIV_ROUND_CLOSEST(value, KILO); + case TEMPERATURE_C: + /* value in Celsius */ + return value * MILLIDEGREE_PER_DEGREE; + case CURRENT: + /* value in Amperes */ + return value * MILLI; + } + return value; +} + +static int asus_wmi_get_cached_value_or_update(const struct asus_wmi_sensor_info *sensor, + struct asus_wmi_sensors *sensor_data, + u32 *value) +{ + int ret = 0; + + mutex_lock(&sensor_data->lock); + + if (time_after(jiffies, sensor_data->wmi.source_last_updated[sensor->source] + HZ)) { + ret = asus_wmi_update_buffer(sensor->source); + if (ret) + goto unlock; + + ret = asus_wmi_update_values_for_source(sensor->source, sensor_data); + if (ret) + goto unlock; + + sensor_data->wmi.source_last_updated[sensor->source] = jiffies; + } + + *value = sensor->cached_value; + +unlock: + mutex_unlock(&sensor_data->lock); + + return ret; +} + +/* Now follow the functions that implement the hwmon interface */ +static int asus_wmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + const struct asus_wmi_sensor_info *sensor; + u32 value = 0; + int ret; + + struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); + + sensor = *(sensor_data->wmi.info[type] + channel); + + ret = asus_wmi_get_cached_value_or_update(sensor, sensor_data, &value); + if (ret) + return ret; + + *val = asus_wmi_scale_sensor_value(value, sensor->data_type); + + return ret; +} + +static int asus_wmi_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + struct asus_wmi_sensors *sensor_data = dev_get_drvdata(dev); + const struct asus_wmi_sensor_info *sensor; + + sensor = *(sensor_data->wmi.info[type] + channel); + *str = sensor->name; + + return 0; +} + +static umode_t asus_wmi_hwmon_is_visible(const void *drvdata, + enum hwmon_sensor_types type, u32 attr, + int channel) +{ + const struct asus_wmi_sensors *sensor_data = drvdata; + const struct asus_wmi_sensor_info *sensor; + + sensor = *(sensor_data->wmi.info[type] + channel); + if (sensor) + return 0444; + + return 0; +} + +static const struct hwmon_ops asus_wmi_hwmon_ops = { + .is_visible = asus_wmi_hwmon_is_visible, + .read = asus_wmi_hwmon_read, + .read_string = asus_wmi_hwmon_read_string, +}; + +static struct hwmon_chip_info asus_wmi_chip_info = { + .ops = &asus_wmi_hwmon_ops, + .info = NULL, +}; + +static int asus_wmi_configure_sensor_setup(struct device *dev, + struct asus_wmi_sensors *sensor_data) +{ + const struct hwmon_channel_info **ptr_asus_wmi_ci; + struct hwmon_channel_info *asus_wmi_hwmon_chan; + int nr_count[hwmon_max] = {}, nr_types = 0; + struct asus_wmi_sensor_info *temp_sensor; + const struct hwmon_chip_info *chip_info; + enum hwmon_sensor_types type; + struct device *hwdev; + int i, idx; + int err; + + temp_sensor = devm_kcalloc(dev, 1, sizeof(*temp_sensor), GFP_KERNEL); + if (!temp_sensor) + return -ENOMEM; + + for (i = 0; i < sensor_data->wmi.sensor_count; i++) { + err = asus_wmi_sensor_info(i, temp_sensor); + if (err) + return err; + + switch (temp_sensor->data_type) { + case TEMPERATURE_C: + case VOLTAGE: + case CURRENT: + case FAN_RPM: + case WATER_FLOW: + type = asus_data_types[temp_sensor->data_type]; + if (!nr_count[type]) + nr_types++; + nr_count[type]++; + break; + } + } + + if (nr_count[hwmon_temp]) + nr_count[hwmon_chip]++, nr_types++; + + asus_wmi_hwmon_chan = devm_kcalloc(dev, nr_types, + sizeof(*asus_wmi_hwmon_chan), + GFP_KERNEL); + if (!asus_wmi_hwmon_chan) + return -ENOMEM; + + ptr_asus_wmi_ci = devm_kcalloc(dev, nr_types + 1, + sizeof(*ptr_asus_wmi_ci), GFP_KERNEL); + if (!ptr_asus_wmi_ci) + return -ENOMEM; + + asus_wmi_chip_info.info = ptr_asus_wmi_ci; + chip_info = &asus_wmi_chip_info; + + sensor_data->wmi.info_by_id = devm_kcalloc(dev, sensor_data->wmi.sensor_count, + sizeof(*sensor_data->wmi.info_by_id), + GFP_KERNEL); + + if (!sensor_data->wmi.info_by_id) + return -ENOMEM; + + for (type = 0; type < hwmon_max; type++) { + if (!nr_count[type]) + continue; + + err = asus_wmi_hwmon_add_chan_info(asus_wmi_hwmon_chan, dev, + nr_count[type], type, + hwmon_attributes[type]); + if (err) + return err; + + *ptr_asus_wmi_ci++ = asus_wmi_hwmon_chan++; + + sensor_data->wmi.info[type] = devm_kcalloc(dev, + nr_count[type], + sizeof(*sensor_data->wmi.info), + GFP_KERNEL); + if (!sensor_data->wmi.info[type]) + return -ENOMEM; + } + + for (i = sensor_data->wmi.sensor_count - 1; i >= 0; i--) { + temp_sensor = devm_kzalloc(dev, sizeof(*temp_sensor), GFP_KERNEL); + if (!temp_sensor) + return -ENOMEM; + + err = asus_wmi_sensor_info(i, temp_sensor); + if (err) + continue; + + switch (temp_sensor->data_type) { + case TEMPERATURE_C: + case VOLTAGE: + case CURRENT: + case FAN_RPM: + case WATER_FLOW: + type = asus_data_types[temp_sensor->data_type]; + idx = --nr_count[type]; + *(sensor_data->wmi.info[type] + idx) = temp_sensor; + sensor_data->wmi.info_by_id[i] = temp_sensor; + break; + } + } + + dev_dbg(dev, "board has %d sensors", + sensor_data->wmi.sensor_count); + + hwdev = devm_hwmon_device_register_with_info(dev, "asus_wmi_sensors", + sensor_data, chip_info, NULL); + + return PTR_ERR_OR_ZERO(hwdev); +} + +static int asus_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct asus_wmi_sensors *sensor_data; + struct device *dev = &wdev->dev; + u32 version = 0; + + if (!dmi_check_system(asus_wmi_dmi_table)) + return -ENODEV; + + sensor_data = devm_kzalloc(dev, sizeof(*sensor_data), GFP_KERNEL); + if (!sensor_data) + return -ENOMEM; + + if (asus_wmi_get_version(&version)) + return -ENODEV; + + if (asus_wmi_get_item_count(&sensor_data->wmi.sensor_count)) + return -ENODEV; + + if (sensor_data->wmi.sensor_count <= 0 || version < 2) { + dev_info(dev, "version: %u with %d sensors is unsupported\n", + version, sensor_data->wmi.sensor_count); + + return -ENODEV; + } + + mutex_init(&sensor_data->lock); + + dev_set_drvdata(dev, sensor_data); + + return asus_wmi_configure_sensor_setup(dev, sensor_data); +} + +static const struct wmi_device_id asus_wmi_id_table[] = { + { ASUSWMI_MONITORING_GUID, NULL }, + { } +}; + +static struct wmi_driver asus_sensors_wmi_driver = { + .driver = { + .name = "asus_wmi_sensors", + }, + .id_table = asus_wmi_id_table, + .probe = asus_wmi_probe, +}; +module_wmi_driver(asus_sensors_wmi_driver); + +MODULE_AUTHOR("Ed Brindley <kernel@maidavale.org>"); +MODULE_DESCRIPTION("Asus WMI Sensors Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/axi-fan-control.c b/drivers/hwmon/axi-fan-control.c index d2092c17d993..96c4a5c45291 100644 --- a/drivers/hwmon/axi-fan-control.c +++ b/drivers/hwmon/axi-fan-control.c @@ -339,7 +339,8 @@ static irqreturn_t axi_fan_control_irq_handler(int irq, void *data) ctl->update_tacho_params = true; } else { ctl->hw_pwm_req = false; - sysfs_notify(&ctl->hdev->kobj, NULL, "pwm1"); + hwmon_notify_event(ctl->hdev, hwmon_pwm, + hwmon_pwm_input, 0); } } diff --git a/drivers/hwmon/corsair-psu.c b/drivers/hwmon/corsair-psu.c index 731d5117f9f1..14389fd7afb8 100644 --- a/drivers/hwmon/corsair-psu.c +++ b/drivers/hwmon/corsair-psu.c @@ -729,7 +729,7 @@ static int corsairpsu_probe(struct hid_device *hdev, const struct hid_device_id corsairpsu_check_cmd_support(priv); priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, "corsairpsu", priv, - &corsairpsu_chip_info, 0); + &corsairpsu_chip_info, NULL); if (IS_ERR(priv->hwmon_dev)) { ret = PTR_ERR(priv->hwmon_dev); diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index eaace478f508..84cb1ede7bc0 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -21,6 +21,7 @@ #include <linux/errno.h> #include <linux/hwmon.h> #include <linux/init.h> +#include <linux/kernel.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/platform_device.h> @@ -86,8 +87,8 @@ MODULE_LICENSE("GPL"); MODULE_ALIAS("i8k"); static bool force; -module_param(force, bool, 0); -MODULE_PARM_DESC(force, "Force loading without checking for supported models"); +module_param_unsafe(force, bool, 0); +MODULE_PARM_DESC(force, "Force loading without checking for supported models and features"); static bool ignore_dmi; module_param(ignore_dmi, bool, 0); @@ -113,12 +114,12 @@ MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)" struct smm_regs { unsigned int eax; - unsigned int ebx __packed; - unsigned int ecx __packed; - unsigned int edx __packed; - unsigned int esi __packed; - unsigned int edi __packed; -}; + unsigned int ebx; + unsigned int ecx; + unsigned int edx; + unsigned int esi; + unsigned int edi; +} __packed; static const char * const temp_labels[] = { "CPU", @@ -250,46 +251,52 @@ static int i8k_smm(struct smm_regs *regs) /* * Read the fan status. */ -static int i8k_get_fan_status(const struct dell_smm_data *data, int fan) +static int i8k_get_fan_status(const struct dell_smm_data *data, u8 fan) { - struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, }; + struct smm_regs regs = { + .eax = I8K_SMM_GET_FAN, + .ebx = fan, + }; if (data->disallow_fan_support) return -EINVAL; - regs.ebx = fan & 0xff; return i8k_smm(®s) ? : regs.eax & 0xff; } /* * Read the fan speed in RPM. */ -static int i8k_get_fan_speed(const struct dell_smm_data *data, int fan) +static int i8k_get_fan_speed(const struct dell_smm_data *data, u8 fan) { - struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, }; + struct smm_regs regs = { + .eax = I8K_SMM_GET_SPEED, + .ebx = fan, + }; if (data->disallow_fan_support) return -EINVAL; - regs.ebx = fan & 0xff; return i8k_smm(®s) ? : (regs.eax & 0xffff) * data->i8k_fan_mult; } /* * Read the fan type. */ -static int _i8k_get_fan_type(const struct dell_smm_data *data, int fan) +static int _i8k_get_fan_type(const struct dell_smm_data *data, u8 fan) { - struct smm_regs regs = { .eax = I8K_SMM_GET_FAN_TYPE, }; + struct smm_regs regs = { + .eax = I8K_SMM_GET_FAN_TYPE, + .ebx = fan, + }; if (data->disallow_fan_support || data->disallow_fan_type_call) return -EINVAL; - regs.ebx = fan & 0xff; return i8k_smm(®s) ? : regs.eax & 0xff; } -static int i8k_get_fan_type(struct dell_smm_data *data, int fan) +static int i8k_get_fan_type(struct dell_smm_data *data, u8 fan) { /* I8K_SMM_GET_FAN_TYPE SMM call is expensive, so cache values */ if (data->fan_type[fan] == INT_MIN) @@ -301,14 +308,16 @@ static int i8k_get_fan_type(struct dell_smm_data *data, int fan) /* * Read the fan nominal rpm for specific fan speed. */ -static int __init i8k_get_fan_nominal_speed(const struct dell_smm_data *data, int fan, int speed) +static int __init i8k_get_fan_nominal_speed(const struct dell_smm_data *data, u8 fan, int speed) { - struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, }; + struct smm_regs regs = { + .eax = I8K_SMM_GET_NOM_SPEED, + .ebx = fan | (speed << 8), + }; if (data->disallow_fan_support) return -EINVAL; - regs.ebx = (fan & 0xff) | (speed << 8); return i8k_smm(®s) ? : (regs.eax & 0xffff) * data->i8k_fan_mult; } @@ -329,7 +338,7 @@ static int i8k_enable_fan_auto_mode(const struct dell_smm_data *data, bool enabl /* * Set the fan speed (off, low, high, ...). */ -static int i8k_set_fan(const struct dell_smm_data *data, int fan, int speed) +static int i8k_set_fan(const struct dell_smm_data *data, u8 fan, int speed) { struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, }; @@ -337,33 +346,35 @@ static int i8k_set_fan(const struct dell_smm_data *data, int fan, int speed) return -EINVAL; speed = (speed < 0) ? 0 : ((speed > data->i8k_fan_max) ? data->i8k_fan_max : speed); - regs.ebx = (fan & 0xff) | (speed << 8); + regs.ebx = fan | (speed << 8); return i8k_smm(®s); } -static int __init i8k_get_temp_type(int sensor) +static int __init i8k_get_temp_type(u8 sensor) { - struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP_TYPE, }; + struct smm_regs regs = { + .eax = I8K_SMM_GET_TEMP_TYPE, + .ebx = sensor, + }; - regs.ebx = sensor & 0xff; return i8k_smm(®s) ? : regs.eax & 0xff; } /* * Read the cpu temperature. */ -static int _i8k_get_temp(int sensor) +static int _i8k_get_temp(u8 sensor) { struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP, - .ebx = sensor & 0xff, + .ebx = sensor, }; return i8k_smm(®s) ? : regs.eax & 0xff; } -static int i8k_get_temp(int sensor) +static int i8k_get_temp(u8 sensor) { int temp = _i8k_get_temp(sensor); @@ -449,13 +460,12 @@ static int i8k_get_power_status(void) * Procfs interface */ -static int -i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd, unsigned long arg) +static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) { - int val = 0; - int speed, err; - unsigned char buff[16]; + struct dell_smm_data *data = pde_data(file_inode(fp)); int __user *argp = (int __user *)arg; + int speed, err; + int val = 0; if (!argp) return -EINVAL; @@ -468,15 +478,19 @@ i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd val = (data->bios_version[0] << 16) | (data->bios_version[1] << 8) | data->bios_version[2]; - break; + if (copy_to_user(argp, &val, sizeof(val))) + return -EFAULT; + + return 0; case I8K_MACHINE_ID: if (restricted && !capable(CAP_SYS_ADMIN)) return -EPERM; - strscpy_pad(buff, data->bios_machineid, sizeof(buff)); - break; + if (copy_to_user(argp, data->bios_machineid, sizeof(data->bios_machineid))) + return -EFAULT; + return 0; case I8K_FN_STATUS: val = i8k_get_fn_status(); break; @@ -493,6 +507,9 @@ i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd if (copy_from_user(&val, argp, sizeof(int))) return -EFAULT; + if (val > U8_MAX || val < 0) + return -EINVAL; + val = i8k_get_fan_speed(data, val); break; @@ -500,6 +517,9 @@ i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd if (copy_from_user(&val, argp, sizeof(int))) return -EFAULT; + if (val > U8_MAX || val < 0) + return -EINVAL; + val = i8k_get_fan_status(data, val); break; @@ -510,14 +530,19 @@ i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd if (copy_from_user(&val, argp, sizeof(int))) return -EFAULT; + if (val > U8_MAX || val < 0) + return -EINVAL; + if (copy_from_user(&speed, argp + 1, sizeof(int))) return -EFAULT; + mutex_lock(&data->i8k_mutex); err = i8k_set_fan(data, val, speed); if (err < 0) - return err; - - val = i8k_get_fan_status(data, val); + val = err; + else + val = i8k_get_fan_status(data, val); + mutex_unlock(&data->i8k_mutex); break; default: @@ -527,39 +552,12 @@ i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd if (val < 0) return val; - switch (cmd) { - case I8K_BIOS_VERSION: - if (copy_to_user(argp, &val, 4)) - return -EFAULT; - - break; - case I8K_MACHINE_ID: - if (copy_to_user(argp, buff, 16)) - return -EFAULT; - - break; - default: - if (copy_to_user(argp, &val, sizeof(int))) - return -EFAULT; - - break; - } + if (copy_to_user(argp, &val, sizeof(int))) + return -EFAULT; return 0; } -static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) -{ - struct dell_smm_data *data = PDE_DATA(file_inode(fp)); - long ret; - - mutex_lock(&data->i8k_mutex); - ret = i8k_ioctl_unlocked(fp, data, cmd, arg); - mutex_unlock(&data->i8k_mutex); - - return ret; -} - /* * Print the information for /proc/i8k. */ @@ -607,7 +605,7 @@ static int i8k_proc_show(struct seq_file *seq, void *offset) static int i8k_open_fs(struct inode *inode, struct file *file) { - return single_open(file, i8k_proc_show, PDE_DATA(inode)); + return single_open(file, i8k_proc_show, pde_data(inode)); } static const struct proc_ops i8k_proc_ops = { @@ -627,10 +625,9 @@ static void __init i8k_init_procfs(struct device *dev) { struct dell_smm_data *data = dev_get_drvdata(dev); - /* Register the proc entry */ - proc_create_data("i8k", 0, NULL, &i8k_proc_ops, data); - - devm_add_action_or_reset(dev, i8k_exit_procfs, NULL); + /* Only register exit function if creation was successful */ + if (proc_create_data("i8k", 0, NULL, &i8k_proc_ops, data)) + devm_add_action_or_reset(dev, i8k_exit_procfs, NULL); } #else @@ -654,6 +651,11 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types case hwmon_temp: switch (attr) { case hwmon_temp_input: + /* _i8k_get_temp() is fine since we do not care about the actual value */ + if (data->temp_type[channel] >= 0 || _i8k_get_temp(channel) >= 0) + return 0444; + + break; case hwmon_temp_label: if (data->temp_type[channel] >= 0) return 0444; @@ -943,7 +945,8 @@ static int __init dell_smm_init_hwmon(struct device *dev) { struct dell_smm_data *data = dev_get_drvdata(dev); struct device *dell_smm_hwmon_dev; - int i, state, err; + int state, err; + u8 i; for (i = 0; i < DELL_SMM_NO_TEMP; i++) { data->temp_type[i] = i8k_get_temp_type(i); @@ -1154,6 +1157,13 @@ static const struct dmi_system_id i8k_blacklist_fan_type_dmi_table[] __initconst DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 580 "), }, }, + { + .ident = "Dell Inspiron 3505", + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 3505"), + }, + }, { } }; @@ -1259,7 +1269,8 @@ static int __init dell_smm_probe(struct platform_device *pdev) { struct dell_smm_data *data; const struct dmi_system_id *id, *fan_control; - int fan, ret; + int ret; + u8 fan; data = devm_kzalloc(&pdev->dev, sizeof(struct dell_smm_data), GFP_KERNEL); if (!data) diff --git a/drivers/hwmon/f71882fg.c b/drivers/hwmon/f71882fg.c index 4673d403759a..938a8b9ec70d 100644 --- a/drivers/hwmon/f71882fg.c +++ b/drivers/hwmon/f71882fg.c @@ -49,6 +49,7 @@ #define SIO_F81768D_ID 0x1210 /* Chipset ID */ #define SIO_F81865_ID 0x0704 /* Chipset ID */ #define SIO_F81866_ID 0x1010 /* Chipset ID */ +#define SIO_F81966_ID 0x1502 /* Chipset ID */ #define REGION_LENGTH 8 #define ADDR_REG_OFFSET 5 @@ -2672,6 +2673,7 @@ static int __init f71882fg_find(int sioaddr, struct f71882fg_sio_data *sio_data) sio_data->type = f81865f; break; case SIO_F81866_ID: + case SIO_F81966_ID: sio_data->type = f81866a; break; default: diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 3501a3ead4ba..989e2c8496dd 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -18,6 +18,7 @@ #include <linux/list.h> #include <linux/module.h> #include <linux/pci.h> +#include <linux/property.h> #include <linux/slab.h> #include <linux/string.h> #include <linux/thermal.h> @@ -30,6 +31,7 @@ struct hwmon_device { const char *name; + const char *label; struct device dev; const struct hwmon_chip_info *chip; struct list_head tzdata; @@ -71,17 +73,29 @@ name_show(struct device *dev, struct device_attribute *attr, char *buf) } static DEVICE_ATTR_RO(name); +static ssize_t +label_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%s\n", to_hwmon_device(dev)->label); +} +static DEVICE_ATTR_RO(label); + static struct attribute *hwmon_dev_attrs[] = { &dev_attr_name.attr, + &dev_attr_label.attr, NULL }; -static umode_t hwmon_dev_name_is_visible(struct kobject *kobj, +static umode_t hwmon_dev_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) { struct device *dev = kobj_to_dev(kobj); + struct hwmon_device *hdev = to_hwmon_device(dev); - if (to_hwmon_device(dev)->name == NULL) + if (attr == &dev_attr_name.attr && hdev->name == NULL) + return 0; + + if (attr == &dev_attr_label.attr && hdev->label == NULL) return 0; return attr->mode; @@ -89,7 +103,7 @@ static umode_t hwmon_dev_name_is_visible(struct kobject *kobj, static const struct attribute_group hwmon_dev_attr_group = { .attrs = hwmon_dev_attrs, - .is_visible = hwmon_dev_name_is_visible, + .is_visible = hwmon_dev_attr_is_visible, }; static const struct attribute_group *hwmon_dev_attr_groups[] = { @@ -117,6 +131,7 @@ static void hwmon_dev_release(struct device *dev) if (hwdev->group.attrs) hwmon_free_attrs(hwdev->group.attrs); kfree(hwdev->groups); + kfree(hwdev->label); kfree(hwdev); } @@ -214,12 +229,14 @@ static int hwmon_thermal_add_sensor(struct device *dev, int index) tzd = devm_thermal_zone_of_sensor_register(dev, index, tdata, &hwmon_thermal_ops); - /* - * If CONFIG_THERMAL_OF is disabled, this returns -ENODEV, - * so ignore that error but forward any other error. - */ - if (IS_ERR(tzd) && (PTR_ERR(tzd) != -ENODEV)) - return PTR_ERR(tzd); + if (IS_ERR(tzd)) { + if (PTR_ERR(tzd) != -ENODEV) + return PTR_ERR(tzd); + dev_info(dev, "temp%d_input not attached to any thermal zone\n", + index + 1); + devm_kfree(dev, tdata); + return 0; + } err = devm_add_action(dev, hwmon_thermal_remove_sensor, &tdata->node); if (err) @@ -587,6 +604,7 @@ static const char * const hwmon_pwm_attr_templates[] = { [hwmon_pwm_enable] = "pwm%d_enable", [hwmon_pwm_mode] = "pwm%d_mode", [hwmon_pwm_freq] = "pwm%d_freq", + [hwmon_pwm_auto_channels_temp] = "pwm%d_auto_channels_temp", }; static const char * const hwmon_intrusion_attr_templates[] = { @@ -623,7 +641,9 @@ static const int __templates_size[] = { int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel) { + char event[MAX_SYSFS_ATTR_NAME_LENGTH + 5]; char sattr[MAX_SYSFS_ATTR_NAME_LENGTH]; + char *envp[] = { event, NULL }; const char * const *templates; const char *template; int base; @@ -639,8 +659,9 @@ int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type, base = hwmon_attr_base(type); scnprintf(sattr, MAX_SYSFS_ATTR_NAME_LENGTH, template, base + channel); + scnprintf(event, sizeof(event), "NAME=%s", sattr); sysfs_notify(&dev->kobj, NULL, sattr); - kobject_uevent(&dev->kobj, KOBJ_CHANGE); + kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); if (type == hwmon_temp) hwmon_thermal_notify(dev, channel); @@ -733,6 +754,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, const struct attribute_group **groups) { struct hwmon_device *hwdev; + const char *label; struct device *hdev; int i, err, id; @@ -788,6 +810,18 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, hdev->groups = groups; } + if (dev && device_property_present(dev, "label")) { + err = device_property_read_string(dev, "label", &label); + if (err < 0) + goto free_hwmon; + + hwdev->label = kstrdup(label, GFP_KERNEL); + if (hwdev->label == NULL) { + err = -ENOMEM; + goto free_hwmon; + } + } + hwdev->name = name; hdev->class = &hwmon_class; hdev->parent = dev; diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c new file mode 100644 index 000000000000..50eb9c5e132e --- /dev/null +++ b/drivers/hwmon/ina238.c @@ -0,0 +1,644 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for Texas Instruments INA238 power monitor chip + * Datasheet: https://www.ti.com/product/ina238 + * + * Copyright (C) 2021 Nathan Rossi <nathan.rossi@digi.com> + */ + +#include <linux/err.h> +#include <linux/hwmon.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regmap.h> + +#include <linux/platform_data/ina2xx.h> + +/* INA238 register definitions */ +#define INA238_CONFIG 0x0 +#define INA238_ADC_CONFIG 0x1 +#define INA238_SHUNT_CALIBRATION 0x2 +#define INA238_SHUNT_VOLTAGE 0x4 +#define INA238_BUS_VOLTAGE 0x5 +#define INA238_DIE_TEMP 0x6 +#define INA238_CURRENT 0x7 +#define INA238_POWER 0x8 +#define INA238_DIAG_ALERT 0xb +#define INA238_SHUNT_OVER_VOLTAGE 0xc +#define INA238_SHUNT_UNDER_VOLTAGE 0xd +#define INA238_BUS_OVER_VOLTAGE 0xe +#define INA238_BUS_UNDER_VOLTAGE 0xf +#define INA238_TEMP_LIMIT 0x10 +#define INA238_POWER_LIMIT 0x11 +#define INA238_DEVICE_ID 0x3f + +#define INA238_CONFIG_ADCRANGE BIT(4) + +#define INA238_DIAG_ALERT_TMPOL BIT(7) +#define INA238_DIAG_ALERT_SHNTOL BIT(6) +#define INA238_DIAG_ALERT_SHNTUL BIT(5) +#define INA238_DIAG_ALERT_BUSOL BIT(4) +#define INA238_DIAG_ALERT_BUSUL BIT(3) +#define INA238_DIAG_ALERT_POL BIT(2) + +#define INA238_REGISTERS 0x11 + +#define INA238_RSHUNT_DEFAULT 10000 /* uOhm */ + +/* Default configuration of device on reset. */ +#define INA238_CONFIG_DEFAULT 0 +/* 16 sample averaging, 1052us conversion time, continuous mode */ +#define INA238_ADC_CONFIG_DEFAULT 0xfb6a +/* Configure alerts to be based on averaged value (SLOWALERT) */ +#define INA238_DIAG_ALERT_DEFAULT 0x2000 +/* + * This driver uses a fixed calibration value in order to scale current/power + * based on a fixed shunt resistor value. This allows for conversion within the + * device to avoid integer limits whilst current/power accuracy is scaled + * relative to the shunt resistor value within the driver. This is similar to + * how the ina2xx driver handles current/power scaling. + * + * The end result of this is that increasing shunt values (from a fixed 20 mOhm + * shunt) increase the effective current/power accuracy whilst limiting the + * range and decreasing shunt values decrease the effective accuracy but + * increase the range. + * + * The value of the Current register is calculated given the following: + * Current (A) = (shunt voltage register * 5) * calibration / 81920 + * + * The maximum shunt voltage is 163.835 mV (0x7fff, ADC_RANGE = 0, gain = 4). + * With the maximum current value of 0x7fff and a fixed shunt value results in + * a calibration value of 16384 (0x4000). + * + * 0x7fff = (0x7fff * 5) * calibration / 81920 + * calibration = 0x4000 + * + * Equivalent calibration is applied for the Power register (maximum value for + * bus voltage is 102396.875 mV, 0x7fff), where the maximum power that can + * occur is ~16776192 uW (register value 0x147a8): + * + * This scaling means the resulting values for Current and Power registers need + * to be scaled by the difference between the fixed shunt resistor and the + * actual shunt resistor: + * + * shunt = 0x4000 / (819.2 * 10^6) / 0.001 = 20000 uOhms (with 1mA/lsb) + * + * Current (mA) = register value * 20000 / rshunt / 4 * gain + * Power (W) = 0.2 * register value * 20000 / rshunt / 4 * gain + */ +#define INA238_CALIBRATION_VALUE 16384 +#define INA238_FIXED_SHUNT 20000 + +#define INA238_SHUNT_VOLTAGE_LSB 5 /* 5 uV/lsb */ +#define INA238_BUS_VOLTAGE_LSB 3125 /* 3.125 mV/lsb */ +#define INA238_DIE_TEMP_LSB 125 /* 125 mC/lsb */ + +static struct regmap_config ina238_regmap_config = { + .max_register = INA238_REGISTERS, + .reg_bits = 8, + .val_bits = 16, +}; + +struct ina238_data { + struct i2c_client *client; + struct mutex config_lock; + struct regmap *regmap; + u32 rshunt; + int gain; +}; + +static int ina238_read_reg24(const struct i2c_client *client, u8 reg, u32 *val) +{ + u8 data[3]; + int err; + + /* 24-bit register read */ + err = i2c_smbus_read_i2c_block_data(client, reg, 3, data); + if (err < 0) + return err; + if (err != 3) + return -EIO; + *val = (data[0] << 16) | (data[1] << 8) | data[2]; + + return 0; +} + +static int ina238_read_in(struct device *dev, u32 attr, int channel, + long *val) +{ + struct ina238_data *data = dev_get_drvdata(dev); + int reg, mask; + int regval; + int err; + + switch (channel) { + case 0: + switch (attr) { + case hwmon_in_input: + reg = INA238_SHUNT_VOLTAGE; + break; + case hwmon_in_max: + reg = INA238_SHUNT_OVER_VOLTAGE; + break; + case hwmon_in_min: + reg = INA238_SHUNT_UNDER_VOLTAGE; + break; + case hwmon_in_max_alarm: + reg = INA238_DIAG_ALERT; + mask = INA238_DIAG_ALERT_SHNTOL; + break; + case hwmon_in_min_alarm: + reg = INA238_DIAG_ALERT; + mask = INA238_DIAG_ALERT_SHNTUL; + break; + default: + return -EOPNOTSUPP; + } + break; + case 1: + switch (attr) { + case hwmon_in_input: + reg = INA238_BUS_VOLTAGE; + break; + case hwmon_in_max: + reg = INA238_BUS_OVER_VOLTAGE; + break; + case hwmon_in_min: + reg = INA238_BUS_UNDER_VOLTAGE; + break; + case hwmon_in_max_alarm: + reg = INA238_DIAG_ALERT; + mask = INA238_DIAG_ALERT_BUSOL; + break; + case hwmon_in_min_alarm: + reg = INA238_DIAG_ALERT; + mask = INA238_DIAG_ALERT_BUSUL; + break; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; + } + + err = regmap_read(data->regmap, reg, ®val); + if (err < 0) + return err; + + switch (attr) { + case hwmon_in_input: + case hwmon_in_max: + case hwmon_in_min: + /* signed register, value in mV */ + regval = (s16)regval; + if (channel == 0) + /* gain of 1 -> LSB / 4 */ + *val = (regval * INA238_SHUNT_VOLTAGE_LSB) / + (1000 * (4 - data->gain + 1)); + else + *val = (regval * INA238_BUS_VOLTAGE_LSB) / 1000; + break; + case hwmon_in_max_alarm: + case hwmon_in_min_alarm: + *val = !!(regval & mask); + break; + } + + return 0; +} + +static int ina238_write_in(struct device *dev, u32 attr, int channel, + long val) +{ + struct ina238_data *data = dev_get_drvdata(dev); + int regval; + + if (attr != hwmon_in_max && attr != hwmon_in_min) + return -EOPNOTSUPP; + + /* convert decimal to register value */ + switch (channel) { + case 0: + /* signed value, clamp to max range +/-163 mV */ + regval = clamp_val(val, -163, 163); + regval = (regval * 1000 * (4 - data->gain + 1)) / + INA238_SHUNT_VOLTAGE_LSB; + regval = clamp_val(regval, S16_MIN, S16_MAX); + + switch (attr) { + case hwmon_in_max: + return regmap_write(data->regmap, + INA238_SHUNT_OVER_VOLTAGE, regval); + case hwmon_in_min: + return regmap_write(data->regmap, + INA238_SHUNT_UNDER_VOLTAGE, regval); + default: + return -EOPNOTSUPP; + } + case 1: + /* signed value, positive values only. Clamp to max 102.396 V */ + regval = clamp_val(val, 0, 102396); + regval = (regval * 1000) / INA238_BUS_VOLTAGE_LSB; + regval = clamp_val(regval, 0, S16_MAX); + + switch (attr) { + case hwmon_in_max: + return regmap_write(data->regmap, + INA238_BUS_OVER_VOLTAGE, regval); + case hwmon_in_min: + return regmap_write(data->regmap, + INA238_BUS_UNDER_VOLTAGE, regval); + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } +} + +static int ina238_read_current(struct device *dev, u32 attr, long *val) +{ + struct ina238_data *data = dev_get_drvdata(dev); + int regval; + int err; + + switch (attr) { + case hwmon_curr_input: + err = regmap_read(data->regmap, INA238_CURRENT, ®val); + if (err < 0) + return err; + + /* Signed register, fixed 1mA current lsb. result in mA */ + *val = div_s64((s16)regval * INA238_FIXED_SHUNT * data->gain, + data->rshunt * 4); + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int ina238_read_power(struct device *dev, u32 attr, long *val) +{ + struct ina238_data *data = dev_get_drvdata(dev); + long long power; + int regval; + int err; + + switch (attr) { + case hwmon_power_input: + err = ina238_read_reg24(data->client, INA238_POWER, ®val); + if (err) + return err; + + /* Fixed 1mA lsb, scaled by 1000000 to have result in uW */ + power = div_u64(regval * 1000ULL * INA238_FIXED_SHUNT * + data->gain, 20 * data->rshunt); + /* Clamp value to maximum value of long */ + *val = clamp_val(power, 0, LONG_MAX); + break; + case hwmon_power_max: + err = regmap_read(data->regmap, INA238_POWER_LIMIT, ®val); + if (err) + return err; + + /* + * Truncated 24-bit compare register, lower 8-bits are + * truncated. Same conversion to/from uW as POWER register. + */ + power = div_u64((regval << 8) * 1000ULL * INA238_FIXED_SHUNT * + data->gain, 20 * data->rshunt); + /* Clamp value to maximum value of long */ + *val = clamp_val(power, 0, LONG_MAX); + break; + case hwmon_power_max_alarm: + err = regmap_read(data->regmap, INA238_DIAG_ALERT, ®val); + if (err) + return err; + + *val = !!(regval & INA238_DIAG_ALERT_POL); + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int ina238_write_power(struct device *dev, u32 attr, long val) +{ + struct ina238_data *data = dev_get_drvdata(dev); + long regval; + + if (attr != hwmon_power_max) + return -EOPNOTSUPP; + + /* + * Unsigned postive values. Compared against the 24-bit power register, + * lower 8-bits are truncated. Same conversion to/from uW as POWER + * register. + */ + regval = clamp_val(val, 0, LONG_MAX); + regval = div_u64(val * 20ULL * data->rshunt, + 1000ULL * INA238_FIXED_SHUNT * data->gain); + regval = clamp_val(regval >> 8, 0, U16_MAX); + + return regmap_write(data->regmap, INA238_POWER_LIMIT, regval); +} + +static int ina238_read_temp(struct device *dev, u32 attr, long *val) +{ + struct ina238_data *data = dev_get_drvdata(dev); + int regval; + int err; + + switch (attr) { + case hwmon_temp_input: + err = regmap_read(data->regmap, INA238_DIE_TEMP, ®val); + if (err) + return err; + + /* Signed, bits 15-4 of register, result in mC */ + *val = ((s16)regval >> 4) * INA238_DIE_TEMP_LSB; + break; + case hwmon_temp_max: + err = regmap_read(data->regmap, INA238_TEMP_LIMIT, ®val); + if (err) + return err; + + /* Signed, bits 15-4 of register, result in mC */ + *val = ((s16)regval >> 4) * INA238_DIE_TEMP_LSB; + break; + case hwmon_temp_max_alarm: + err = regmap_read(data->regmap, INA238_DIAG_ALERT, ®val); + if (err) + return err; + + *val = !!(regval & INA238_DIAG_ALERT_TMPOL); + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int ina238_write_temp(struct device *dev, u32 attr, long val) +{ + struct ina238_data *data = dev_get_drvdata(dev); + int regval; + + if (attr != hwmon_temp_max) + return -EOPNOTSUPP; + + /* Signed, bits 15-4 of register */ + regval = (val / INA238_DIE_TEMP_LSB) << 4; + regval = clamp_val(regval, S16_MIN, S16_MAX) & 0xfff0; + + return regmap_write(data->regmap, INA238_TEMP_LIMIT, regval); +} + +static int ina238_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_in: + return ina238_read_in(dev, attr, channel, val); + case hwmon_curr: + return ina238_read_current(dev, attr, val); + case hwmon_power: + return ina238_read_power(dev, attr, val); + case hwmon_temp: + return ina238_read_temp(dev, attr, val); + default: + return -EOPNOTSUPP; + } + return 0; +} + +static int ina238_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct ina238_data *data = dev_get_drvdata(dev); + int err; + + mutex_lock(&data->config_lock); + + switch (type) { + case hwmon_in: + err = ina238_write_in(dev, attr, channel, val); + break; + case hwmon_power: + err = ina238_write_power(dev, attr, val); + break; + case hwmon_temp: + err = ina238_write_temp(dev, attr, val); + break; + default: + err = -EOPNOTSUPP; + break; + } + + mutex_unlock(&data->config_lock); + return err; +} + +static umode_t ina238_is_visible(const void *drvdata, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_input: + case hwmon_in_max_alarm: + case hwmon_in_min_alarm: + return 0444; + case hwmon_in_max: + case hwmon_in_min: + return 0644; + default: + return 0; + } + case hwmon_curr: + switch (attr) { + case hwmon_curr_input: + return 0444; + default: + return 0; + } + case hwmon_power: + switch (attr) { + case hwmon_power_input: + case hwmon_power_max_alarm: + return 0444; + case hwmon_power_max: + return 0644; + default: + return 0; + } + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_max_alarm: + return 0444; + case hwmon_temp_max: + return 0644; + default: + return 0; + } + default: + return 0; + } +} + +#define INA238_HWMON_IN_CONFIG (HWMON_I_INPUT | \ + HWMON_I_MAX | HWMON_I_MAX_ALARM | \ + HWMON_I_MIN | HWMON_I_MIN_ALARM) + +static const struct hwmon_channel_info *ina238_info[] = { + HWMON_CHANNEL_INFO(in, + /* 0: shunt voltage */ + INA238_HWMON_IN_CONFIG, + /* 1: bus voltage */ + INA238_HWMON_IN_CONFIG), + HWMON_CHANNEL_INFO(curr, + /* 0: current through shunt */ + HWMON_C_INPUT), + HWMON_CHANNEL_INFO(power, + /* 0: power */ + HWMON_P_INPUT | HWMON_P_MAX | HWMON_P_MAX_ALARM), + HWMON_CHANNEL_INFO(temp, + /* 0: die temperature */ + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_ALARM), + NULL +}; + +static const struct hwmon_ops ina238_hwmon_ops = { + .is_visible = ina238_is_visible, + .read = ina238_read, + .write = ina238_write, +}; + +static const struct hwmon_chip_info ina238_chip_info = { + .ops = &ina238_hwmon_ops, + .info = ina238_info, +}; + +static int ina238_probe(struct i2c_client *client) +{ + struct ina2xx_platform_data *pdata = dev_get_platdata(&client->dev); + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct ina238_data *data; + int config; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + mutex_init(&data->config_lock); + + data->regmap = devm_regmap_init_i2c(client, &ina238_regmap_config); + if (IS_ERR(data->regmap)) { + dev_err(dev, "failed to allocate register map\n"); + return PTR_ERR(data->regmap); + } + + /* load shunt value */ + data->rshunt = INA238_RSHUNT_DEFAULT; + if (device_property_read_u32(dev, "shunt-resistor", &data->rshunt) < 0 && pdata) + data->rshunt = pdata->shunt_uohms; + if (data->rshunt == 0) { + dev_err(dev, "invalid shunt resister value %u\n", data->rshunt); + return -EINVAL; + } + + /* load shunt gain value */ + if (device_property_read_u32(dev, "ti,shunt-gain", &data->gain) < 0) + data->gain = 4; /* Default of ADCRANGE = 0 */ + if (data->gain != 1 && data->gain != 4) { + dev_err(dev, "invalid shunt gain value %u\n", data->gain); + return -EINVAL; + } + + /* Setup CONFIG register */ + config = INA238_CONFIG_DEFAULT; + if (data->gain == 1) + config |= INA238_CONFIG_ADCRANGE; /* ADCRANGE = 1 is /1 */ + ret = regmap_write(data->regmap, INA238_CONFIG, config); + if (ret < 0) { + dev_err(dev, "error configuring the device: %d\n", ret); + return -ENODEV; + } + + /* Setup ADC_CONFIG register */ + ret = regmap_write(data->regmap, INA238_ADC_CONFIG, + INA238_ADC_CONFIG_DEFAULT); + if (ret < 0) { + dev_err(dev, "error configuring the device: %d\n", ret); + return -ENODEV; + } + + /* Setup SHUNT_CALIBRATION register with fixed value */ + ret = regmap_write(data->regmap, INA238_SHUNT_CALIBRATION, + INA238_CALIBRATION_VALUE); + if (ret < 0) { + dev_err(dev, "error configuring the device: %d\n", ret); + return -ENODEV; + } + + /* Setup alert/alarm configuration */ + ret = regmap_write(data->regmap, INA238_DIAG_ALERT, + INA238_DIAG_ALERT_DEFAULT); + if (ret < 0) { + dev_err(dev, "error configuring the device: %d\n", ret); + return -ENODEV; + } + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, + &ina238_chip_info, + NULL); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + dev_info(dev, "power monitor %s (Rshunt = %u uOhm, gain = %u)\n", + client->name, data->rshunt, data->gain); + + return 0; +} + +static const struct i2c_device_id ina238_id[] = { + { "ina238", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ina238_id); + +static const struct of_device_id __maybe_unused ina238_of_match[] = { + { .compatible = "ti,ina238" }, + { }, +}; +MODULE_DEVICE_TABLE(of, ina238_of_match); + +static struct i2c_driver ina238_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "ina238", + .of_match_table = of_match_ptr(ina238_of_match), + }, + .probe_new = ina238_probe, + .id_table = ina238_id, +}; + +module_i2c_driver(ina238_driver); + +MODULE_AUTHOR("Nathan Rossi <nathan.rossi@digi.com>"); +MODULE_DESCRIPTION("ina238 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/jc42.c b/drivers/hwmon/jc42.c index 4a03d010ec5a..cb347a6bd8d9 100644 --- a/drivers/hwmon/jc42.c +++ b/drivers/hwmon/jc42.c @@ -137,6 +137,9 @@ static const unsigned short normal_i2c[] = { #define CAT34TS04_DEVID 0x2200 #define CAT34TS04_DEVID_MASK 0xfff0 +#define N34TS04_DEVID 0x2230 +#define N34TS04_DEVID_MASK 0xfff0 + /* ST Microelectronics */ #define STTS424_DEVID 0x0101 #define STTS424_DEVID_MASK 0xffff @@ -181,6 +184,7 @@ static struct jc42_chips jc42_chips[] = { { ONS_MANID, CAT6095_DEVID, CAT6095_DEVID_MASK }, { ONS_MANID, CAT34TS02C_DEVID, CAT34TS02C_DEVID_MASK }, { ONS_MANID, CAT34TS04_DEVID, CAT34TS04_DEVID_MASK }, + { ONS_MANID, N34TS04_DEVID, N34TS04_DEVID_MASK }, { NXP_MANID, SE98_DEVID, SE98_DEVID_MASK }, { STM_MANID, STTS424_DEVID, STTS424_DEVID_MASK }, { STM_MANID, STTS424E_DEVID, STTS424E_DEVID_MASK }, diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index 3618a924e78e..4e239bd75b1d 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -76,26 +76,6 @@ static DEFINE_MUTEX(nb_smu_ind_mutex); #define ZEN_CUR_TEMP_SHIFT 21 #define ZEN_CUR_TEMP_RANGE_SEL_MASK BIT(19) -#define ZEN_SVI_BASE 0x0005A000 - -/* F17h thermal registers through SMN */ -#define F17H_M01H_SVI_TEL_PLANE0 (ZEN_SVI_BASE + 0xc) -#define F17H_M01H_SVI_TEL_PLANE1 (ZEN_SVI_BASE + 0x10) -#define F17H_M31H_SVI_TEL_PLANE0 (ZEN_SVI_BASE + 0x14) -#define F17H_M31H_SVI_TEL_PLANE1 (ZEN_SVI_BASE + 0x10) - -#define F17H_M01H_CFACTOR_ICORE 1000000 /* 1A / LSB */ -#define F17H_M01H_CFACTOR_ISOC 250000 /* 0.25A / LSB */ -#define F17H_M31H_CFACTOR_ICORE 1000000 /* 1A / LSB */ -#define F17H_M31H_CFACTOR_ISOC 310000 /* 0.31A / LSB */ - -/* F19h thermal registers through SMN */ -#define F19H_M01_SVI_TEL_PLANE0 (ZEN_SVI_BASE + 0x14) -#define F19H_M01_SVI_TEL_PLANE1 (ZEN_SVI_BASE + 0x10) - -#define F19H_M01H_CFACTOR_ICORE 1000000 /* 1A / LSB */ -#define F19H_M01H_CFACTOR_ISOC 310000 /* 0.31A / LSB */ - struct k10temp_data { struct pci_dev *pdev; void (*read_htcreg)(struct pci_dev *pdev, u32 *regval); @@ -191,6 +171,10 @@ static const char *k10temp_temp_label[] = { "Tccd6", "Tccd7", "Tccd8", + "Tccd9", + "Tccd10", + "Tccd11", + "Tccd12", }; static int k10temp_read_labels(struct device *dev, @@ -226,7 +210,7 @@ static int k10temp_read_temp(struct device *dev, u32 attr, int channel, if (*val < 0) *val = 0; break; - case 2 ... 9: /* Tccd{1-8} */ + case 2 ... 13: /* Tccd{1-12} */ amd_smn_read(amd_pci_dev_to_node_id(data->pdev), ZEN_CCD_TEMP(data->ccd_offset, channel - 2), ®val); @@ -361,6 +345,10 @@ static const struct hwmon_channel_info *k10temp_info[] = { HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL), NULL }; @@ -457,6 +445,11 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) data->ccd_offset = 0x300; k10temp_get_ccd_support(pdev, data, 8); break; + case 0x10 ... 0x1f: + case 0xa0 ... 0xaf: + data->ccd_offset = 0x300; + k10temp_get_ccd_support(pdev, data, 12); + break; } } else { data->read_htcreg = read_htcreg_pci; @@ -497,6 +490,7 @@ static const struct pci_device_id k10temp_id_table[] = { { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M60H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_17H_M70H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_DF_F3) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M10H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M40H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M50H_DF_F3) }, { PCI_VDEVICE(HYGON, PCI_DEVICE_ID_AMD_17H_DF_F3) }, diff --git a/drivers/hwmon/lm70.c b/drivers/hwmon/lm70.c index d2a60de5b8de..c20a749fc7f2 100644 --- a/drivers/hwmon/lm70.c +++ b/drivers/hwmon/lm70.c @@ -34,6 +34,7 @@ #define LM70_CHIP_LM71 2 /* NS LM71 */ #define LM70_CHIP_LM74 3 /* NS LM74 */ #define LM70_CHIP_TMP122 4 /* TI TMP122/TMP124 */ +#define LM70_CHIP_TMP125 5 /* TI TMP125 */ struct lm70 { struct spi_device *spi; @@ -87,6 +88,12 @@ static ssize_t temp1_input_show(struct device *dev, * LM71: * 14 bits of 2's complement data, discard LSB 2 bits, * resolution 0.0312 degrees celsius. + * + * TMP125: + * MSB/D15 is a leading zero. D14 is the sign-bit. This is + * followed by 9 temperature bits (D13..D5) in 2's complement + * data format with a resolution of 0.25 degrees celsius per unit. + * LSB 5 bits (D4..D0) share the same value as D5 and get discarded. */ switch (p_lm70->chip) { case LM70_CHIP_LM70: @@ -102,6 +109,10 @@ static ssize_t temp1_input_show(struct device *dev, case LM70_CHIP_LM71: val = ((int)raw / 4) * 3125 / 100; break; + + case LM70_CHIP_TMP125: + val = (sign_extend32(raw, 14) / 32) * 250; + break; } status = sprintf(buf, "%d\n", val); /* millidegrees Celsius */ @@ -136,6 +147,10 @@ static const struct of_device_id lm70_of_ids[] = { .data = (void *) LM70_CHIP_TMP122, }, { + .compatible = "ti,tmp125", + .data = (void *) LM70_CHIP_TMP125, + }, + { .compatible = "ti,lm71", .data = (void *) LM70_CHIP_LM71, }, @@ -184,6 +199,7 @@ static const struct spi_device_id lm70_ids[] = { { "lm70", LM70_CHIP_LM70 }, { "tmp121", LM70_CHIP_TMP121 }, { "tmp122", LM70_CHIP_TMP122 }, + { "tmp125", LM70_CHIP_TMP125 }, { "lm71", LM70_CHIP_LM71 }, { "lm74", LM70_CHIP_LM74 }, { }, diff --git a/drivers/hwmon/lm83.c b/drivers/hwmon/lm83.c index 74fd7aa373a3..12370dcefa6a 100644 --- a/drivers/hwmon/lm83.c +++ b/drivers/hwmon/lm83.c @@ -18,15 +18,15 @@ * http://www.national.com/pf/LM/LM82.html */ -#include <linux/module.h> -#include <linux/init.h> -#include <linux/slab.h> -#include <linux/jiffies.h> +#include <linux/bits.h> +#include <linux/err.h> #include <linux/i2c.h> -#include <linux/hwmon-sysfs.h> +#include <linux/init.h> #include <linux/hwmon.h> -#include <linux/err.h> +#include <linux/module.h> #include <linux/mutex.h> +#include <linux/regmap.h> +#include <linux/slab.h> #include <linux/sysfs.h> /* @@ -66,35 +66,35 @@ enum chips { lm83, lm82 }; #define LM83_REG_R_TCRIT 0x42 #define LM83_REG_W_TCRIT 0x5A -/* - * Conversions and various macros - * The LM83 uses signed 8-bit values with LSB = 1 degree Celsius. - */ - -#define TEMP_FROM_REG(val) ((val) * 1000) -#define TEMP_TO_REG(val) ((val) <= -128000 ? -128 : \ - (val) >= 127000 ? 127 : \ - (val) < 0 ? ((val) - 500) / 1000 : \ - ((val) + 500) / 1000) - -static const u8 LM83_REG_R_TEMP[] = { +static const u8 LM83_REG_TEMP[] = { LM83_REG_R_LOCAL_TEMP, LM83_REG_R_REMOTE1_TEMP, LM83_REG_R_REMOTE2_TEMP, LM83_REG_R_REMOTE3_TEMP, +}; + +static const u8 LM83_REG_MAX[] = { LM83_REG_R_LOCAL_HIGH, LM83_REG_R_REMOTE1_HIGH, LM83_REG_R_REMOTE2_HIGH, LM83_REG_R_REMOTE3_HIGH, - LM83_REG_R_TCRIT, }; -static const u8 LM83_REG_W_HIGH[] = { - LM83_REG_W_LOCAL_HIGH, - LM83_REG_W_REMOTE1_HIGH, - LM83_REG_W_REMOTE2_HIGH, - LM83_REG_W_REMOTE3_HIGH, - LM83_REG_W_TCRIT, +/* alarm and fault registers and bits, indexed by channel */ +static const u8 LM83_ALARM_REG[] = { + LM83_REG_R_STATUS1, LM83_REG_R_STATUS2, LM83_REG_R_STATUS1, LM83_REG_R_STATUS2 +}; + +static const u8 LM83_MAX_ALARM_BIT[] = { + BIT(6), BIT(7), BIT(4), BIT(4) +}; + +static const u8 LM83_CRIT_ALARM_BIT[] = { + BIT(0), BIT(0), BIT(1), BIT(1) +}; + +static const u8 LM83_FAULT_BIT[] = { + 0, BIT(5), BIT(2), BIT(2) }; /* @@ -102,180 +102,274 @@ static const u8 LM83_REG_W_HIGH[] = { */ struct lm83_data { - struct i2c_client *client; - const struct attribute_group *groups[3]; - struct mutex update_lock; - bool valid; /* false until following fields are valid */ - unsigned long last_updated; /* in jiffies */ - - /* registers values */ - s8 temp[9]; /* 0..3: input 1-4, - 4..7: high limit 1-4, - 8 : critical limit */ - u16 alarms; /* bitvector, combined */ + struct regmap *regmap; + enum chips type; }; -static struct lm83_data *lm83_update_device(struct device *dev) +/* regmap code */ + +static int lm83_regmap_reg_read(void *context, unsigned int reg, unsigned int *val) { - struct lm83_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; + struct i2c_client *client = context; + int ret; - mutex_lock(&data->update_lock); + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) + return ret; - if (time_after(jiffies, data->last_updated + HZ * 2) || !data->valid) { - int nr; + *val = ret; + return 0; +} - dev_dbg(&client->dev, "Updating lm83 data.\n"); - for (nr = 0; nr < 9; nr++) { - data->temp[nr] = - i2c_smbus_read_byte_data(client, - LM83_REG_R_TEMP[nr]); - } - data->alarms = - i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS1) - + (i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS2) - << 8); +/* + * The regmap write function maps read register addresses to write register + * addresses. This is necessary for regmap register caching to work. + * An alternative would be to clear the regmap cache whenever a register is + * written, but that would be much more expensive. + */ +static int lm83_regmap_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct i2c_client *client = context; - data->last_updated = jiffies; - data->valid = true; + switch (reg) { + case LM83_REG_R_CONFIG: + case LM83_REG_R_LOCAL_HIGH: + case LM83_REG_R_REMOTE2_HIGH: + reg += 0x06; + break; + case LM83_REG_R_REMOTE1_HIGH: + case LM83_REG_R_REMOTE3_HIGH: + case LM83_REG_R_TCRIT: + reg += 0x18; + break; + default: + break; } - mutex_unlock(&data->update_lock); + return i2c_smbus_write_byte_data(client, reg, val); +} - return data; +static bool lm83_regmap_is_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LM83_REG_R_LOCAL_TEMP: + case LM83_REG_R_REMOTE1_TEMP: + case LM83_REG_R_REMOTE2_TEMP: + case LM83_REG_R_REMOTE3_TEMP: + case LM83_REG_R_STATUS1: + case LM83_REG_R_STATUS2: + return true; + default: + return false; + } } -/* - * Sysfs stuff - */ +static const struct regmap_config lm83_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = lm83_regmap_is_volatile, + .reg_read = lm83_regmap_reg_read, + .reg_write = lm83_regmap_reg_write, +}; -static ssize_t temp_show(struct device *dev, struct device_attribute *devattr, - char *buf) +/* hwmon API */ + +static int lm83_temp_read(struct device *dev, u32 attr, int channel, long *val) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct lm83_data *data = lm83_update_device(dev); - return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp[attr->index])); + struct lm83_data *data = dev_get_drvdata(dev); + unsigned int regval; + int err; + + switch (attr) { + case hwmon_temp_input: + err = regmap_read(data->regmap, LM83_REG_TEMP[channel], ®val); + if (err < 0) + return err; + *val = (s8)regval * 1000; + break; + case hwmon_temp_max: + err = regmap_read(data->regmap, LM83_REG_MAX[channel], ®val); + if (err < 0) + return err; + *val = (s8)regval * 1000; + break; + case hwmon_temp_crit: + err = regmap_read(data->regmap, LM83_REG_R_TCRIT, ®val); + if (err < 0) + return err; + *val = (s8)regval * 1000; + break; + case hwmon_temp_max_alarm: + err = regmap_read(data->regmap, LM83_ALARM_REG[channel], ®val); + if (err < 0) + return err; + *val = !!(regval & LM83_MAX_ALARM_BIT[channel]); + break; + case hwmon_temp_crit_alarm: + err = regmap_read(data->regmap, LM83_ALARM_REG[channel], ®val); + if (err < 0) + return err; + *val = !!(regval & LM83_CRIT_ALARM_BIT[channel]); + break; + case hwmon_temp_fault: + err = regmap_read(data->regmap, LM83_ALARM_REG[channel], ®val); + if (err < 0) + return err; + *val = !!(regval & LM83_FAULT_BIT[channel]); + break; + default: + return -EOPNOTSUPP; + } + return 0; } -static ssize_t temp_store(struct device *dev, - struct device_attribute *devattr, const char *buf, - size_t count) +static int lm83_temp_write(struct device *dev, u32 attr, int channel, long val) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct lm83_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; - long val; - int nr = attr->index; + unsigned int regval; int err; - err = kstrtol(buf, 10, &val); - if (err < 0) - return err; + regval = DIV_ROUND_CLOSEST(clamp_val(val, -128000, 127000), 1000); - mutex_lock(&data->update_lock); - data->temp[nr] = TEMP_TO_REG(val); - i2c_smbus_write_byte_data(client, LM83_REG_W_HIGH[nr - 4], - data->temp[nr]); - mutex_unlock(&data->update_lock); - return count; + switch (attr) { + case hwmon_temp_max: + err = regmap_write(data->regmap, LM83_REG_MAX[channel], regval); + if (err < 0) + return err; + break; + case hwmon_temp_crit: + err = regmap_write(data->regmap, LM83_REG_R_TCRIT, regval); + if (err < 0) + return err; + break; + default: + return -EOPNOTSUPP; + } + return 0; } -static ssize_t alarms_show(struct device *dev, struct device_attribute *dummy, - char *buf) +static int lm83_chip_read(struct device *dev, u32 attr, int channel, long *val) { - struct lm83_data *data = lm83_update_device(dev); - return sprintf(buf, "%d\n", data->alarms); + struct lm83_data *data = dev_get_drvdata(dev); + unsigned int regval; + int err; + + switch (attr) { + case hwmon_chip_alarms: + err = regmap_read(data->regmap, LM83_REG_R_STATUS1, ®val); + if (err < 0) + return err; + *val = regval; + err = regmap_read(data->regmap, LM83_REG_R_STATUS2, ®val); + if (err < 0) + return err; + *val |= regval << 8; + return 0; + default: + return -EOPNOTSUPP; + } + + return 0; } -static ssize_t alarm_show(struct device *dev, - struct device_attribute *devattr, char *buf) +static int lm83_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct lm83_data *data = lm83_update_device(dev); - int bitnr = attr->index; + switch (type) { + case hwmon_chip: + return lm83_chip_read(dev, attr, channel, val); + case hwmon_temp: + return lm83_temp_read(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} - return sprintf(buf, "%d\n", (data->alarms >> bitnr) & 1); +static int lm83_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + switch (type) { + case hwmon_temp: + return lm83_temp_write(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } } -static SENSOR_DEVICE_ATTR_RO(temp1_input, temp, 0); -static SENSOR_DEVICE_ATTR_RO(temp2_input, temp, 1); -static SENSOR_DEVICE_ATTR_RO(temp3_input, temp, 2); -static SENSOR_DEVICE_ATTR_RO(temp4_input, temp, 3); -static SENSOR_DEVICE_ATTR_RW(temp1_max, temp, 4); -static SENSOR_DEVICE_ATTR_RW(temp2_max, temp, 5); -static SENSOR_DEVICE_ATTR_RW(temp3_max, temp, 6); -static SENSOR_DEVICE_ATTR_RW(temp4_max, temp, 7); -static SENSOR_DEVICE_ATTR_RO(temp1_crit, temp, 8); -static SENSOR_DEVICE_ATTR_RO(temp2_crit, temp, 8); -static SENSOR_DEVICE_ATTR_RW(temp3_crit, temp, 8); -static SENSOR_DEVICE_ATTR_RO(temp4_crit, temp, 8); - -/* Individual alarm files */ -static SENSOR_DEVICE_ATTR_RO(temp1_crit_alarm, alarm, 0); -static SENSOR_DEVICE_ATTR_RO(temp3_crit_alarm, alarm, 1); -static SENSOR_DEVICE_ATTR_RO(temp3_fault, alarm, 2); -static SENSOR_DEVICE_ATTR_RO(temp3_max_alarm, alarm, 4); -static SENSOR_DEVICE_ATTR_RO(temp1_max_alarm, alarm, 6); -static SENSOR_DEVICE_ATTR_RO(temp2_crit_alarm, alarm, 8); -static SENSOR_DEVICE_ATTR_RO(temp4_crit_alarm, alarm, 9); -static SENSOR_DEVICE_ATTR_RO(temp4_fault, alarm, 10); -static SENSOR_DEVICE_ATTR_RO(temp4_max_alarm, alarm, 12); -static SENSOR_DEVICE_ATTR_RO(temp2_fault, alarm, 13); -static SENSOR_DEVICE_ATTR_RO(temp2_max_alarm, alarm, 15); -/* Raw alarm file for compatibility */ -static DEVICE_ATTR_RO(alarms); - -static struct attribute *lm83_attributes[] = { - &sensor_dev_attr_temp1_input.dev_attr.attr, - &sensor_dev_attr_temp3_input.dev_attr.attr, - &sensor_dev_attr_temp1_max.dev_attr.attr, - &sensor_dev_attr_temp3_max.dev_attr.attr, - &sensor_dev_attr_temp1_crit.dev_attr.attr, - &sensor_dev_attr_temp3_crit.dev_attr.attr, - - &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, - &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr, - &sensor_dev_attr_temp3_fault.dev_attr.attr, - &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, - &dev_attr_alarms.attr, - NULL -}; +static umode_t lm83_is_visible(const void *_data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct lm83_data *data = _data; -static const struct attribute_group lm83_group = { - .attrs = lm83_attributes, -}; + /* + * LM82 only supports a single external channel, modeled as channel 2. + */ + if (data->type == lm82 && (channel == 1 || channel == 3)) + return 0; -static struct attribute *lm83_attributes_opt[] = { - &sensor_dev_attr_temp2_input.dev_attr.attr, - &sensor_dev_attr_temp4_input.dev_attr.attr, - &sensor_dev_attr_temp2_max.dev_attr.attr, - &sensor_dev_attr_temp4_max.dev_attr.attr, - &sensor_dev_attr_temp2_crit.dev_attr.attr, - &sensor_dev_attr_temp4_crit.dev_attr.attr, - - &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, - &sensor_dev_attr_temp4_crit_alarm.dev_attr.attr, - &sensor_dev_attr_temp4_fault.dev_attr.attr, - &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_fault.dev_attr.attr, - &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + switch (type) { + case hwmon_chip: + if (attr == hwmon_chip_alarms) + return 0444; + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_max_alarm: + case hwmon_temp_crit_alarm: + return 0444; + case hwmon_temp_fault: + if (channel) + return 0444; + break; + case hwmon_temp_max: + return 0644; + case hwmon_temp_crit: + if (channel == 2) + return 0644; + return 0444; + default: + break; + } + break; + default: + break; + } + return 0; +} + +static const struct hwmon_channel_info *lm83_info[] = { + HWMON_CHANNEL_INFO(chip, HWMON_C_ALARMS), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT + ), NULL }; -static const struct attribute_group lm83_group_opt = { - .attrs = lm83_attributes_opt, +static const struct hwmon_ops lm83_hwmon_ops = { + .is_visible = lm83_is_visible, + .read = lm83_read, + .write = lm83_write, }; -/* - * Real code - */ +static const struct hwmon_chip_info lm83_chip_info = { + .ops = &lm83_hwmon_ops, + .info = lm83_info, +}; /* Return 0 if detection is successful, -ENODEV otherwise */ -static int lm83_detect(struct i2c_client *new_client, +static int lm83_detect(struct i2c_client *client, struct i2c_board_info *info) { - struct i2c_adapter *adapter = new_client->adapter; + struct i2c_adapter *adapter = client->adapter; const char *name; u8 man_id, chip_id; @@ -283,22 +377,30 @@ static int lm83_detect(struct i2c_client *new_client, return -ENODEV; /* Detection */ - if ((i2c_smbus_read_byte_data(new_client, LM83_REG_R_STATUS1) & 0xA8) || - (i2c_smbus_read_byte_data(new_client, LM83_REG_R_STATUS2) & 0x48) || - (i2c_smbus_read_byte_data(new_client, LM83_REG_R_CONFIG) & 0x41)) { + if ((i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS1) & 0xA8) || + (i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS2) & 0x48) || + (i2c_smbus_read_byte_data(client, LM83_REG_R_CONFIG) & 0x41)) { dev_dbg(&adapter->dev, "LM83 detection failed at 0x%02x\n", - new_client->addr); + client->addr); return -ENODEV; } /* Identification */ - man_id = i2c_smbus_read_byte_data(new_client, LM83_REG_R_MAN_ID); + man_id = i2c_smbus_read_byte_data(client, LM83_REG_R_MAN_ID); if (man_id != 0x01) /* National Semiconductor */ return -ENODEV; - chip_id = i2c_smbus_read_byte_data(new_client, LM83_REG_R_CHIP_ID); + chip_id = i2c_smbus_read_byte_data(client, LM83_REG_R_CHIP_ID); switch (chip_id) { case 0x03: + /* + * According to the LM82 datasheet dated March 2013, recent + * revisions of LM82 have a die revision of 0x03. This was + * confirmed with a real chip. Further details in this revision + * of the LM82 datasheet strongly suggest that LM82 is just a + * repackaged LM83. It is therefore impossible to distinguish + * those chips from LM83, and they will be misdetected as LM83. + */ name = "lm83"; break; case 0x01: @@ -306,9 +408,9 @@ static int lm83_detect(struct i2c_client *new_client, break; default: /* identification failed */ - dev_info(&adapter->dev, - "Unsupported chip (man_id=0x%02X, chip_id=0x%02X)\n", - man_id, chip_id); + dev_dbg(&adapter->dev, + "Unsupported chip (man_id=0x%02X, chip_id=0x%02X)\n", + man_id, chip_id); return -ENODEV; } @@ -317,34 +419,31 @@ static int lm83_detect(struct i2c_client *new_client, return 0; } -static const struct i2c_device_id lm83_id[]; +static const struct i2c_device_id lm83_id[] = { + { "lm83", lm83 }, + { "lm82", lm82 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lm83_id); -static int lm83_probe(struct i2c_client *new_client) +static int lm83_probe(struct i2c_client *client) { + struct device *dev = &client->dev; struct device *hwmon_dev; struct lm83_data *data; - data = devm_kzalloc(&new_client->dev, sizeof(struct lm83_data), - GFP_KERNEL); + data = devm_kzalloc(dev, sizeof(struct lm83_data), GFP_KERNEL); if (!data) return -ENOMEM; - data->client = new_client; - mutex_init(&data->update_lock); + data->regmap = devm_regmap_init(dev, NULL, client, &lm83_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); - /* - * Register sysfs hooks - * The LM82 can only monitor one external diode which is - * at the same register as the LM83 temp3 entry - so we - * declare 1 and 3 common, and then 2 and 4 only for the LM83. - */ - data->groups[0] = &lm83_group; - if (i2c_match_id(lm83_id, new_client)->driver_data == lm83) - data->groups[1] = &lm83_group_opt; + data->type = i2c_match_id(lm83_id, client)->driver_data; - hwmon_dev = devm_hwmon_device_register_with_groups(&new_client->dev, - new_client->name, - data, data->groups); + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + data, &lm83_chip_info, NULL); return PTR_ERR_OR_ZERO(hwmon_dev); } @@ -352,13 +451,6 @@ static int lm83_probe(struct i2c_client *new_client) * Driver data (common to all clients) */ -static const struct i2c_device_id lm83_id[] = { - { "lm83", lm83 }, - { "lm82", lm82 }, - { } -}; -MODULE_DEVICE_TABLE(i2c, lm83_id); - static struct i2c_driver lm83_driver = { .class = I2C_CLASS_HWMON, .driver = { diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c index 618052c6cdb6..1c9493c70813 100644 --- a/drivers/hwmon/lm90.c +++ b/drivers/hwmon/lm90.c @@ -35,13 +35,14 @@ * explicitly as max6659, or if its address is not 0x4c. * These chips lack the remote temperature offset feature. * - * This driver also supports the MAX6654 chip made by Maxim. This chip can - * be at 9 different addresses, similar to MAX6680/MAX6681. The MAX6654 is - * otherwise similar to MAX6657/MAX6658/MAX6659. Extended range is available - * by setting the configuration register accordingly, and is done during - * initialization. Extended precision is only available at conversion rates - * of 1 Hz and slower. Note that extended precision is not enabled by - * default, as this driver initializes all chips to 2 Hz by design. + * This driver also supports the MAX6654 chip made by Maxim. This chip can be + * at 9 different addresses, similar to MAX6680/MAX6681. The MAX6654 is similar + * to MAX6657/MAX6658/MAX6659, but does not support critical temperature + * limits. Extended range is available by setting the configuration register + * accordingly, and is done during initialization. Extended precision is only + * available at conversion rates of 1 Hz and slower. Note that extended + * precision is not enabled by default, as this driver initializes all chips + * to 2 Hz by design. * * This driver also supports the MAX6646, MAX6647, MAX6648, MAX6649 and * MAX6692 chips made by Maxim. These are again similar to the LM86, @@ -188,6 +189,8 @@ enum chips { lm90, adm1032, lm99, lm86, max6657, max6659, adt7461, max6680, #define LM90_HAVE_BROKEN_ALERT (1 << 7) /* Broken alert */ #define LM90_HAVE_EXTENDED_TEMP (1 << 8) /* extended temperature support*/ #define LM90_PAUSE_FOR_CONFIG (1 << 9) /* Pause conversion for config */ +#define LM90_HAVE_CRIT (1 << 10)/* Chip supports CRIT/OVERT register */ +#define LM90_HAVE_CRIT_ALRM_SWP (1 << 11)/* critical alarm bits swapped */ /* LM90 status */ #define LM90_STATUS_LTHRM (1 << 0) /* local THERM limit tripped */ @@ -197,6 +200,7 @@ enum chips { lm90, adm1032, lm99, lm86, max6657, max6659, adt7461, max6680, #define LM90_STATUS_RHIGH (1 << 4) /* remote high temp limit tripped */ #define LM90_STATUS_LLOW (1 << 5) /* local low temp limit tripped */ #define LM90_STATUS_LHIGH (1 << 6) /* local high temp limit tripped */ +#define LM90_STATUS_BUSY (1 << 7) /* conversion is ongoing */ #define MAX6696_STATUS2_R2THRM (1 << 1) /* remote2 THERM limit tripped */ #define MAX6696_STATUS2_R2OPEN (1 << 2) /* remote2 is an open circuit */ @@ -354,92 +358,99 @@ struct lm90_params { static const struct lm90_params lm90_params[] = { [adm1032] = { .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT - | LM90_HAVE_BROKEN_ALERT, + | LM90_HAVE_BROKEN_ALERT | LM90_HAVE_CRIT, .alert_alarms = 0x7c, .max_convrate = 10, }, [adt7461] = { .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT - | LM90_HAVE_BROKEN_ALERT | LM90_HAVE_EXTENDED_TEMP, + | LM90_HAVE_BROKEN_ALERT | LM90_HAVE_EXTENDED_TEMP + | LM90_HAVE_CRIT, .alert_alarms = 0x7c, .max_convrate = 10, }, [g781] = { .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT - | LM90_HAVE_BROKEN_ALERT, + | LM90_HAVE_BROKEN_ALERT | LM90_HAVE_CRIT, .alert_alarms = 0x7c, - .max_convrate = 8, + .max_convrate = 7, }, [lm86] = { - .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT, + .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT + | LM90_HAVE_CRIT, .alert_alarms = 0x7b, .max_convrate = 9, }, [lm90] = { - .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT, + .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT + | LM90_HAVE_CRIT, .alert_alarms = 0x7b, .max_convrate = 9, }, [lm99] = { - .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT, + .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT + | LM90_HAVE_CRIT, .alert_alarms = 0x7b, .max_convrate = 9, }, [max6646] = { + .flags = LM90_HAVE_CRIT | LM90_HAVE_BROKEN_ALERT, .alert_alarms = 0x7c, .max_convrate = 6, .reg_local_ext = MAX6657_REG_R_LOCAL_TEMPL, }, [max6654] = { + .flags = LM90_HAVE_BROKEN_ALERT, .alert_alarms = 0x7c, .max_convrate = 7, .reg_local_ext = MAX6657_REG_R_LOCAL_TEMPL, }, [max6657] = { - .flags = LM90_PAUSE_FOR_CONFIG, + .flags = LM90_PAUSE_FOR_CONFIG | LM90_HAVE_CRIT, .alert_alarms = 0x7c, .max_convrate = 8, .reg_local_ext = MAX6657_REG_R_LOCAL_TEMPL, }, [max6659] = { - .flags = LM90_HAVE_EMERGENCY, + .flags = LM90_HAVE_EMERGENCY | LM90_HAVE_CRIT, .alert_alarms = 0x7c, .max_convrate = 8, .reg_local_ext = MAX6657_REG_R_LOCAL_TEMPL, }, [max6680] = { - .flags = LM90_HAVE_OFFSET, + .flags = LM90_HAVE_OFFSET | LM90_HAVE_CRIT + | LM90_HAVE_CRIT_ALRM_SWP | LM90_HAVE_BROKEN_ALERT, .alert_alarms = 0x7c, .max_convrate = 7, }, [max6696] = { .flags = LM90_HAVE_EMERGENCY - | LM90_HAVE_EMERGENCY_ALARM | LM90_HAVE_TEMP3, + | LM90_HAVE_EMERGENCY_ALARM | LM90_HAVE_TEMP3 | LM90_HAVE_CRIT, .alert_alarms = 0x1c7c, .max_convrate = 6, .reg_local_ext = MAX6657_REG_R_LOCAL_TEMPL, }, [w83l771] = { - .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT, + .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT | LM90_HAVE_CRIT, .alert_alarms = 0x7c, .max_convrate = 8, }, [sa56004] = { - .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT, + .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT | LM90_HAVE_CRIT, .alert_alarms = 0x7b, .max_convrate = 9, .reg_local_ext = SA56004_REG_R_LOCAL_TEMPL, }, [tmp451] = { .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT - | LM90_HAVE_BROKEN_ALERT | LM90_HAVE_EXTENDED_TEMP, + | LM90_HAVE_BROKEN_ALERT | LM90_HAVE_EXTENDED_TEMP | LM90_HAVE_CRIT, .alert_alarms = 0x7c, .max_convrate = 9, .reg_local_ext = TMP451_REG_R_LOCAL_TEMPL, }, [tmp461] = { .flags = LM90_HAVE_OFFSET | LM90_HAVE_REM_LIMIT_EXT - | LM90_HAVE_BROKEN_ALERT | LM90_HAVE_EXTENDED_TEMP, + | LM90_HAVE_BROKEN_ALERT | LM90_HAVE_EXTENDED_TEMP | LM90_HAVE_CRIT, .alert_alarms = 0x7c, .max_convrate = 9, .reg_local_ext = TMP451_REG_R_LOCAL_TEMPL, @@ -668,20 +679,22 @@ static int lm90_update_limits(struct device *dev) struct i2c_client *client = data->client; int val; - val = lm90_read_reg(client, LM90_REG_R_LOCAL_CRIT); - if (val < 0) - return val; - data->temp8[LOCAL_CRIT] = val; + if (data->flags & LM90_HAVE_CRIT) { + val = lm90_read_reg(client, LM90_REG_R_LOCAL_CRIT); + if (val < 0) + return val; + data->temp8[LOCAL_CRIT] = val; - val = lm90_read_reg(client, LM90_REG_R_REMOTE_CRIT); - if (val < 0) - return val; - data->temp8[REMOTE_CRIT] = val; + val = lm90_read_reg(client, LM90_REG_R_REMOTE_CRIT); + if (val < 0) + return val; + data->temp8[REMOTE_CRIT] = val; - val = lm90_read_reg(client, LM90_REG_R_TCRIT_HYST); - if (val < 0) - return val; - data->temp_hyst = val; + val = lm90_read_reg(client, LM90_REG_R_TCRIT_HYST); + if (val < 0) + return val; + data->temp_hyst = val; + } val = lm90_read_reg(client, LM90_REG_R_REMOTE_LOWH); if (val < 0) @@ -809,7 +822,7 @@ static int lm90_update_device(struct device *dev) val = lm90_read_reg(client, LM90_REG_R_STATUS); if (val < 0) return val; - data->alarms = val; /* lower 8 bit of alarms */ + data->alarms = val & ~LM90_STATUS_BUSY; if (data->kind == max6696) { val = lm90_select_remote_channel(data, 1); @@ -836,7 +849,7 @@ static int lm90_update_device(struct device *dev) * Re-enable ALERT# output if it was originally enabled and * relevant alarms are all clear */ - if (!(data->config_orig & 0x80) && + if ((client->irq || !(data->config_orig & 0x80)) && !(data->alarms & data->alert_alarms)) { if (data->config & 0x80) { dev_dbg(&client->dev, "Re-enabling ALERT#\n"); @@ -1160,8 +1173,8 @@ static int lm90_set_temphyst(struct lm90_data *data, long val) else temp = temp_from_s8(data->temp8[LOCAL_CRIT]); - /* prevent integer underflow */ - val = max(val, -128000l); + /* prevent integer overflow/underflow */ + val = clamp_val(val, -128000l, 255000l); data->temp_hyst = hyst_to_reg(temp - val); err = i2c_smbus_write_byte_data(client, LM90_REG_W_TCRIT_HYST, @@ -1192,6 +1205,7 @@ static const u8 lm90_temp_emerg_index[3] = { static const u8 lm90_min_alarm_bits[3] = { 5, 3, 11 }; static const u8 lm90_max_alarm_bits[3] = { 6, 4, 12 }; static const u8 lm90_crit_alarm_bits[3] = { 0, 1, 9 }; +static const u8 lm90_crit_alarm_bits_swapped[3] = { 1, 0, 9 }; static const u8 lm90_emergency_alarm_bits[3] = { 15, 13, 14 }; static const u8 lm90_fault_bits[3] = { 0, 2, 10 }; @@ -1217,7 +1231,10 @@ static int lm90_temp_read(struct device *dev, u32 attr, int channel, long *val) *val = (data->alarms >> lm90_max_alarm_bits[channel]) & 1; break; case hwmon_temp_crit_alarm: - *val = (data->alarms >> lm90_crit_alarm_bits[channel]) & 1; + if (data->flags & LM90_HAVE_CRIT_ALRM_SWP) + *val = (data->alarms >> lm90_crit_alarm_bits_swapped[channel]) & 1; + else + *val = (data->alarms >> lm90_crit_alarm_bits[channel]) & 1; break; case hwmon_temp_emergency_alarm: *val = (data->alarms >> lm90_emergency_alarm_bits[channel]) & 1; @@ -1465,12 +1482,11 @@ static int lm90_detect(struct i2c_client *client, if (man_id < 0 || chip_id < 0 || config1 < 0 || convrate < 0) return -ENODEV; - if (man_id == 0x01 || man_id == 0x5C || man_id == 0x41) { + if (man_id == 0x01 || man_id == 0x5C || man_id == 0xA1) { config2 = i2c_smbus_read_byte_data(client, LM90_REG_R_CONFIG2); if (config2 < 0) return -ENODEV; - } else - config2 = 0; /* Make compiler happy */ + } if ((address == 0x4C || address == 0x4D) && man_id == 0x01) { /* National Semiconductor */ @@ -1792,22 +1808,22 @@ static bool lm90_is_tripped(struct i2c_client *client, u16 *status) if (st & LM90_STATUS_LLOW) hwmon_notify_event(data->hwmon_dev, hwmon_temp, - hwmon_temp_min, 0); + hwmon_temp_min_alarm, 0); if (st & LM90_STATUS_RLOW) hwmon_notify_event(data->hwmon_dev, hwmon_temp, - hwmon_temp_min, 1); + hwmon_temp_min_alarm, 1); if (st2 & MAX6696_STATUS2_R2LOW) hwmon_notify_event(data->hwmon_dev, hwmon_temp, - hwmon_temp_min, 2); + hwmon_temp_min_alarm, 2); if (st & LM90_STATUS_LHIGH) hwmon_notify_event(data->hwmon_dev, hwmon_temp, - hwmon_temp_max, 0); + hwmon_temp_max_alarm, 0); if (st & LM90_STATUS_RHIGH) hwmon_notify_event(data->hwmon_dev, hwmon_temp, - hwmon_temp_max, 1); + hwmon_temp_max_alarm, 1); if (st2 & MAX6696_STATUS2_R2HIGH) hwmon_notify_event(data->hwmon_dev, hwmon_temp, - hwmon_temp_max, 2); + hwmon_temp_max_alarm, 2); return true; } @@ -1903,11 +1919,14 @@ static int lm90_probe(struct i2c_client *client) info->config = data->channel_config; data->channel_config[0] = HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | - HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM | - HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM; + HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM; data->channel_config[1] = HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | - HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM | - HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT; + HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM | HWMON_T_FAULT; + + if (data->flags & LM90_HAVE_CRIT) { + data->channel_config[0] |= HWMON_T_CRIT | HWMON_T_CRIT_ALARM | HWMON_T_CRIT_HYST; + data->channel_config[1] |= HWMON_T_CRIT | HWMON_T_CRIT_ALARM | HWMON_T_CRIT_HYST; + } if (data->flags & LM90_HAVE_OFFSET) data->channel_config[1] |= HWMON_T_OFFSET; diff --git a/drivers/hwmon/ltc2992.c b/drivers/hwmon/ltc2992.c index 2a4bed0ab226..7352d2b3c756 100644 --- a/drivers/hwmon/ltc2992.c +++ b/drivers/hwmon/ltc2992.c @@ -248,8 +248,7 @@ static int ltc2992_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask gpio_status = reg; - gpio_nr = 0; - for_each_set_bit_from(gpio_nr, mask, LTC2992_GPIO_NR) { + for_each_set_bit(gpio_nr, mask, LTC2992_GPIO_NR) { if (test_bit(LTC2992_GPIO_BIT(gpio_nr), &gpio_status)) set_bit(gpio_nr, bits); } diff --git a/drivers/hwmon/max1111.c b/drivers/hwmon/max1111.c index 5fcfd57df61e..4c5487aeb3cf 100644 --- a/drivers/hwmon/max1111.c +++ b/drivers/hwmon/max1111.c @@ -254,7 +254,7 @@ err_remove: return err; } -static int max1111_remove(struct spi_device *spi) +static void max1111_remove(struct spi_device *spi) { struct max1111_data *data = spi_get_drvdata(spi); @@ -265,7 +265,6 @@ static int max1111_remove(struct spi_device *spi) sysfs_remove_group(&spi->dev.kobj, &max1110_attr_group); sysfs_remove_group(&spi->dev.kobj, &max1111_attr_group); mutex_destroy(&data->drvdata_lock); - return 0; } static const struct spi_device_id max1111_ids[] = { diff --git a/drivers/hwmon/max31722.c b/drivers/hwmon/max31722.c index 4cf4fe6809a3..93e048ee4955 100644 --- a/drivers/hwmon/max31722.c +++ b/drivers/hwmon/max31722.c @@ -100,7 +100,7 @@ static int max31722_probe(struct spi_device *spi) return 0; } -static int max31722_remove(struct spi_device *spi) +static void max31722_remove(struct spi_device *spi) { struct max31722_data *data = spi_get_drvdata(spi); int ret; @@ -111,8 +111,6 @@ static int max31722_remove(struct spi_device *spi) if (ret) /* There is nothing we can do about this ... */ dev_warn(&spi->dev, "Failed to put device in stand-by mode\n"); - - return 0; } static int __maybe_unused max31722_suspend(struct device *dev) diff --git a/drivers/hwmon/max6639.c b/drivers/hwmon/max6639.c index ccc0f047bd44..14bb7726f8d7 100644 --- a/drivers/hwmon/max6639.c +++ b/drivers/hwmon/max6639.c @@ -87,6 +87,9 @@ struct max6639_data { /* Register values initialized only once */ u8 ppr; /* Pulses per rotation 0..3 for 1..4 ppr */ u8 rpm_range; /* Index in above rpm_ranges table */ + + /* Optional regulator for FAN supply */ + struct regulator *reg; }; static struct max6639_data *max6639_update_device(struct device *dev) @@ -516,6 +519,11 @@ static int max6639_detect(struct i2c_client *client, return 0; } +static void max6639_regulator_disable(void *data) +{ + regulator_disable(data); +} + static int max6639_probe(struct i2c_client *client) { struct device *dev = &client->dev; @@ -528,6 +536,28 @@ static int max6639_probe(struct i2c_client *client) return -ENOMEM; data->client = client; + + data->reg = devm_regulator_get_optional(dev, "fan"); + if (IS_ERR(data->reg)) { + if (PTR_ERR(data->reg) != -ENODEV) + return PTR_ERR(data->reg); + + data->reg = NULL; + } else { + /* Spin up fans */ + err = regulator_enable(data->reg); + if (err) { + dev_err(dev, "Failed to enable fan supply: %d\n", err); + return err; + } + err = devm_add_action_or_reset(dev, max6639_regulator_disable, + data->reg); + if (err) { + dev_err(dev, "Failed to register action: %d\n", err); + return err; + } + } + mutex_init(&data->update_lock); /* Initialize the max6639 chip */ @@ -545,23 +575,39 @@ static int max6639_probe(struct i2c_client *client) static int max6639_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); - int data = i2c_smbus_read_byte_data(client, MAX6639_REG_GCONFIG); - if (data < 0) - return data; + struct max6639_data *data = dev_get_drvdata(dev); + int ret = i2c_smbus_read_byte_data(client, MAX6639_REG_GCONFIG); + + if (ret < 0) + return ret; + + if (data->reg) + regulator_disable(data->reg); return i2c_smbus_write_byte_data(client, - MAX6639_REG_GCONFIG, data | MAX6639_GCONFIG_STANDBY); + MAX6639_REG_GCONFIG, ret | MAX6639_GCONFIG_STANDBY); } static int max6639_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); - int data = i2c_smbus_read_byte_data(client, MAX6639_REG_GCONFIG); - if (data < 0) - return data; + struct max6639_data *data = dev_get_drvdata(dev); + int ret; + + if (data->reg) { + ret = regulator_enable(data->reg); + if (ret) { + dev_err(dev, "Failed to enable fan supply: %d\n", ret); + return ret; + } + } + + ret = i2c_smbus_read_byte_data(client, MAX6639_REG_GCONFIG); + if (ret < 0) + return ret; return i2c_smbus_write_byte_data(client, - MAX6639_REG_GCONFIG, data & ~MAX6639_GCONFIG_STANDBY); + MAX6639_REG_GCONFIG, ret & ~MAX6639_GCONFIG_STANDBY); } #endif /* CONFIG_PM_SLEEP */ diff --git a/drivers/hwmon/mlxreg-fan.c b/drivers/hwmon/mlxreg-fan.c index 4a8becdb0d58..b48bd7c961d6 100644 --- a/drivers/hwmon/mlxreg-fan.c +++ b/drivers/hwmon/mlxreg-fan.c @@ -18,15 +18,6 @@ #define MLXREG_FAN_MAX_STATE 10 #define MLXREG_FAN_MIN_DUTY 51 /* 20% */ #define MLXREG_FAN_MAX_DUTY 255 /* 100% */ -/* - * Minimum and maximum FAN allowed speed in percent: from 20% to 100%. Values - * MLXREG_FAN_MAX_STATE + x, where x is between 2 and 10 are used for - * setting FAN speed dynamic minimum. For example, if value is set to 14 (40%) - * cooling levels vector will be set to 4, 4, 4, 4, 4, 5, 6, 7, 8, 9, 10 to - * introduce PWM speed in percent: 40, 40, 40, 40, 40, 50, 60. 70, 80, 90, 100. - */ -#define MLXREG_FAN_SPEED_MIN (MLXREG_FAN_MAX_STATE + 2) -#define MLXREG_FAN_SPEED_MAX (MLXREG_FAN_MAX_STATE * 2) #define MLXREG_FAN_SPEED_MIN_LEVEL 2 /* 20 percent */ #define MLXREG_FAN_TACHO_SAMPLES_PER_PULSE_DEF 44 #define MLXREG_FAN_TACHO_DIV_MIN 283 @@ -87,13 +78,16 @@ struct mlxreg_fan_tacho { * @connected: indicates if PWM is connected; * @reg: register offset; * @cooling: cooling device levels; + * @last_hwmon_state: last cooling state set by hwmon subsystem; + * @last_thermal_state: last cooling state set by thermal subsystem; * @cdev: cooling device; */ struct mlxreg_fan_pwm { struct mlxreg_fan *fan; bool connected; u32 reg; - u8 cooling_levels[MLXREG_FAN_MAX_STATE + 1]; + unsigned long last_hwmon_state; + unsigned long last_thermal_state; struct thermal_cooling_device *cdev; }; @@ -119,6 +113,9 @@ struct mlxreg_fan { int divider; }; +static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state); + static int mlxreg_fan_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) @@ -213,6 +210,18 @@ mlxreg_fan_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, val > MLXREG_FAN_MAX_DUTY) return -EINVAL; pwm = &fan->pwm[channel]; + /* If thermal is configured - handle PWM limit setting. */ + if (IS_REACHABLE(CONFIG_THERMAL)) { + pwm->last_hwmon_state = MLXREG_FAN_PWM_DUTY2STATE(val); + /* + * Update PWM only in case requested state is not less than the + * last thermal state. + */ + if (pwm->last_hwmon_state >= pwm->last_thermal_state) + return mlxreg_fan_set_cur_state(pwm->cdev, + pwm->last_hwmon_state); + return 0; + } return regmap_write(fan->regmap, pwm->reg, val); default: return -EOPNOTSUPP; @@ -338,58 +347,22 @@ static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev, { struct mlxreg_fan_pwm *pwm = cdev->devdata; struct mlxreg_fan *fan = pwm->fan; - unsigned long cur_state; - int i, config = 0; - u32 regval; int err; - /* - * Verify if this request is for changing allowed FAN dynamical - * minimum. If it is - update cooling levels accordingly and update - * state, if current state is below the newly requested minimum state. - * For example, if current state is 5, and minimal state is to be - * changed from 4 to 6, fan->cooling_levels[0 to 5] will be changed all - * from 4 to 6. And state 5 (fan->cooling_levels[4]) should be - * overwritten. - */ - if (state >= MLXREG_FAN_SPEED_MIN && state <= MLXREG_FAN_SPEED_MAX) { - /* - * This is configuration change, which is only supported through sysfs. - * For configuration non-zero value is to be returned to avoid thermal - * statistics update. - */ - config = 1; - state -= MLXREG_FAN_MAX_STATE; - for (i = 0; i < state; i++) - pwm->cooling_levels[i] = state; - for (i = state; i <= MLXREG_FAN_MAX_STATE; i++) - pwm->cooling_levels[i] = i; - - err = regmap_read(fan->regmap, pwm->reg, ®val); - if (err) { - dev_err(fan->dev, "Failed to query PWM duty\n"); - return err; - } - - cur_state = MLXREG_FAN_PWM_DUTY2STATE(regval); - if (state < cur_state) - return config; - - state = cur_state; - } - if (state > MLXREG_FAN_MAX_STATE) return -EINVAL; - /* Normalize the state to the valid speed range. */ - state = pwm->cooling_levels[state]; + /* Save thermal state. */ + pwm->last_thermal_state = state; + + state = max_t(unsigned long, state, pwm->last_hwmon_state); err = regmap_write(fan->regmap, pwm->reg, MLXREG_FAN_PWM_STATE2DUTY(state)); if (err) { dev_err(fan->dev, "Failed to write PWM duty\n"); return err; } - return config; + return 0; } static const struct thermal_cooling_device_ops mlxreg_fan_cooling_ops = { @@ -564,7 +537,7 @@ static int mlxreg_fan_config(struct mlxreg_fan *fan, static int mlxreg_fan_cooling_config(struct device *dev, struct mlxreg_fan *fan) { - int i, j; + int i; for (i = 0; i < MLXREG_FAN_MAX_PWM; i++) { struct mlxreg_fan_pwm *pwm = &fan->pwm[i]; @@ -579,11 +552,8 @@ static int mlxreg_fan_cooling_config(struct device *dev, struct mlxreg_fan *fan) return PTR_ERR(pwm->cdev); } - /* Init cooling levels per PWM state. */ - for (j = 0; j < MLXREG_FAN_SPEED_MIN_LEVEL; j++) - pwm->cooling_levels[j] = MLXREG_FAN_SPEED_MIN_LEVEL; - for (j = MLXREG_FAN_SPEED_MIN_LEVEL; j <= MLXREG_FAN_MAX_STATE; j++) - pwm->cooling_levels[j] = j; + /* Set minimal PWM speed. */ + pwm->last_hwmon_state = MLXREG_FAN_PWM_DUTY2STATE(MLXREG_FAN_MIN_DUTY); } return 0; diff --git a/drivers/hwmon/mr75203.c b/drivers/hwmon/mr75203.c index 868243dba1ee..1ba1e3145969 100644 --- a/drivers/hwmon/mr75203.c +++ b/drivers/hwmon/mr75203.c @@ -93,7 +93,7 @@ #define VM_CH_REQ BIT(21) #define IP_TMR 0x05 -#define POWER_DELAY_CYCLE_256 0x80 +#define POWER_DELAY_CYCLE_256 0x100 #define POWER_DELAY_CYCLE_64 0x40 #define PVT_POLL_DELAY_US 20 diff --git a/drivers/hwmon/nct6775.c b/drivers/hwmon/nct6775.c index 93dca471972e..2b91f7e05126 100644 --- a/drivers/hwmon/nct6775.c +++ b/drivers/hwmon/nct6775.c @@ -308,6 +308,7 @@ static void superio_exit(struct nct6775_sio_data *sio_data) #define NUM_TEMP 10 /* Max number of temp attribute sets w/ limits*/ #define NUM_TEMP_FIXED 6 /* Max number of fixed temp attribute sets */ +#define NUM_TSI_TEMP 8 /* Max number of TSI temp register pairs */ #define NUM_REG_ALARM 7 /* Max number of alarm registers */ #define NUM_REG_BEEP 5 /* Max number of beep registers */ @@ -498,6 +499,8 @@ static const u16 NCT6775_REG_TEMP_CRIT[32] = { [11] = 0xa07 }; +static const u16 NCT6775_REG_TSI_TEMP[] = { 0x669 }; + /* NCT6776 specific data */ /* STEP_UP_TIME and STEP_DOWN_TIME regs are swapped for all chips but NCT6775 */ @@ -581,6 +584,9 @@ static const u16 NCT6776_REG_TEMP_CRIT[32] = { [12] = 0x70a, }; +static const u16 NCT6776_REG_TSI_TEMP[] = { + 0x409, 0x40b, 0x40d, 0x40f, 0x411, 0x413, 0x415, 0x417 }; + /* NCT6779 specific data */ static const u16 NCT6779_REG_IN[] = { @@ -864,6 +870,8 @@ static const char *const nct6796_temp_label[] = { #define NCT6796_TEMP_MASK 0xbfff0ffe #define NCT6796_VIRT_TEMP_MASK 0x80000c00 +static const u16 NCT6796_REG_TSI_TEMP[] = { 0x409, 0x40b }; + static const char *const nct6798_temp_label[] = { "", "SYSTIN", @@ -1005,6 +1013,8 @@ static const u16 NCT6106_REG_TEMP_CRIT[32] = { [12] = 0x205, }; +static const u16 NCT6106_REG_TSI_TEMP[] = { 0x59, 0x5b, 0x5d, 0x5f, 0x61, 0x63, 0x65, 0x67 }; + /* NCT6112D/NCT6114D/NCT6116D specific data */ static const u16 NCT6116_REG_FAN[] = { 0x20, 0x22, 0x24, 0x26, 0x28 }; @@ -1069,6 +1079,8 @@ static const s8 NCT6116_BEEP_BITS[] = { 34, -1 /* intrusion0, intrusion1 */ }; +static const u16 NCT6116_REG_TSI_TEMP[] = { 0x59, 0x5b }; + static enum pwm_enable reg_to_pwm_enable(int pwm, int mode) { if (mode == 0 && pwm == 255) @@ -1169,17 +1181,23 @@ static inline u8 in_to_reg(u32 val, u8 nr) return clamp_val(DIV_ROUND_CLOSEST(val * 100, scale_in[nr]), 0, 255); } +/* TSI temperatures are in 8.3 format */ +static inline unsigned int tsi_temp_from_reg(unsigned int reg) +{ + return (reg >> 5) * 125; +} + /* * Data structures and manipulation thereof */ struct nct6775_data { int addr; /* IO base of hw monitor block */ - int sioreg; /* SIO register address */ + struct nct6775_sio_data *sio_data; enum kinds kind; const char *name; - const struct attribute_group *groups[6]; + const struct attribute_group *groups[7]; u16 reg_temp[5][NUM_TEMP]; /* 0=temp, 1=temp_over, 2=temp_hyst, * 3=temp_crit, 4=temp_lcrit @@ -1240,6 +1258,8 @@ struct nct6775_data { const u16 *REG_ALARM; const u16 *REG_BEEP; + const u16 *REG_TSI_TEMP; + unsigned int (*fan_from_reg)(u16 reg, unsigned int divreg); unsigned int (*fan_from_reg_min)(u16 reg, unsigned int divreg); @@ -1267,6 +1287,7 @@ struct nct6775_data { s8 temp_offset[NUM_TEMP_FIXED]; s16 temp[5][NUM_TEMP]; /* 0=temp, 1=temp_over, 2=temp_hyst, * 3=temp_crit, 4=temp_lcrit */ + s16 tsi_temp[NUM_TSI_TEMP]; u64 alarms; u64 beeps; @@ -1315,6 +1336,7 @@ struct nct6775_data { u16 have_temp; u16 have_temp_fixed; + u16 have_tsi_temp; u16 have_in; /* Remember extra register values over suspend/resume */ @@ -1464,13 +1486,15 @@ static bool is_word_sized(struct nct6775_data *data, u16 reg) switch (data->kind) { case nct6106: return reg == 0x20 || reg == 0x22 || reg == 0x24 || + (reg >= 0x59 && reg < 0x69 && (reg & 1)) || reg == 0xe0 || reg == 0xe2 || reg == 0xe4 || reg == 0x111 || reg == 0x121 || reg == 0x131; case nct6116: return reg == 0x20 || reg == 0x22 || reg == 0x24 || - reg == 0x26 || reg == 0x28 || reg == 0xe0 || reg == 0xe2 || - reg == 0xe4 || reg == 0xe6 || reg == 0xe8 || reg == 0x111 || - reg == 0x121 || reg == 0x131 || reg == 0x191 || reg == 0x1a1; + reg == 0x26 || reg == 0x28 || reg == 0x59 || reg == 0x5b || + reg == 0xe0 || reg == 0xe2 || reg == 0xe4 || reg == 0xe6 || + reg == 0xe8 || reg == 0x111 || reg == 0x121 || reg == 0x131 || + reg == 0x191 || reg == 0x1a1; case nct6775: return (((reg & 0xff00) == 0x100 || (reg & 0xff00) == 0x200) && @@ -1479,7 +1503,7 @@ static bool is_word_sized(struct nct6775_data *data, u16 reg) (reg & 0x00ff) == 0x55)) || (reg & 0xfff0) == 0x630 || reg == 0x640 || reg == 0x642 || - reg == 0x662 || + reg == 0x662 || reg == 0x669 || ((reg & 0xfff0) == 0x650 && (reg & 0x000f) >= 0x06) || reg == 0x73 || reg == 0x75 || reg == 0x77; case nct6776: @@ -1490,6 +1514,7 @@ static bool is_word_sized(struct nct6775_data *data, u16 reg) (reg & 0x00ff) == 0x55)) || (reg & 0xfff0) == 0x630 || reg == 0x402 || + (reg >= 0x409 && reg < 0x419 && (reg & 1)) || reg == 0x640 || reg == 0x642 || ((reg & 0xfff0) == 0x650 && (reg & 0x000f) >= 0x06) || reg == 0x73 || reg == 0x75 || reg == 0x77; @@ -1504,6 +1529,7 @@ static bool is_word_sized(struct nct6775_data *data, u16 reg) return reg == 0x150 || reg == 0x153 || reg == 0x155 || (reg & 0xfff0) == 0x4c0 || reg == 0x402 || + (reg >= 0x409 && reg < 0x419 && (reg & 1)) || reg == 0x63a || reg == 0x63c || reg == 0x63e || reg == 0x640 || reg == 0x642 || reg == 0x64a || reg == 0x64c || @@ -1527,7 +1553,7 @@ static u16 nct6775_wmi_read_value(struct nct6775_data *data, u16 reg) nct6775_wmi_set_bank(data, reg); - err = nct6775_asuswmi_read(data->bank, reg, &tmp); + err = nct6775_asuswmi_read(data->bank, reg & 0xff, &tmp); if (err) return 0; @@ -1987,6 +2013,12 @@ static struct nct6775_data *nct6775_update_device(struct device *dev) data->REG_TEMP_OFFSET[i]); } + for (i = 0; i < NUM_TSI_TEMP; i++) { + if (!(data->have_tsi_temp & BIT(i))) + continue; + data->tsi_temp[i] = data->read_value(data, data->REG_TSI_TEMP[i]); + } + data->alarms = 0; for (i = 0; i < NUM_REG_ALARM; i++) { u8 alarm; @@ -2670,6 +2702,44 @@ static const struct sensor_template_group nct6775_temp_template_group = { .base = 1, }; +static ssize_t show_tsi_temp(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + + return sysfs_emit(buf, "%u\n", tsi_temp_from_reg(data->tsi_temp[sattr->index])); +} + +static ssize_t show_tsi_temp_label(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + + return sysfs_emit(buf, "TSI%d_TEMP\n", sattr->index); +} + +SENSOR_TEMPLATE(tsi_temp_input, "temp%d_input", 0444, show_tsi_temp, NULL, 0); +SENSOR_TEMPLATE(tsi_temp_label, "temp%d_label", 0444, show_tsi_temp_label, NULL, 0); + +static umode_t nct6775_tsi_temp_is_visible(struct kobject *kobj, struct attribute *attr, + int index) +{ + struct device *dev = kobj_to_dev(kobj); + struct nct6775_data *data = dev_get_drvdata(dev); + int temp = index / 2; + + return (data->have_tsi_temp & BIT(temp)) ? attr->mode : 0; +} + +/* + * The index calculation in nct6775_tsi_temp_is_visible() must be kept in + * sync with the size of this array. + */ +static struct sensor_device_template *nct6775_tsi_temp_template[] = { + &sensor_dev_template_tsi_temp_input, + &sensor_dev_template_tsi_temp_label, + NULL +}; + static ssize_t show_pwm_mode(struct device *dev, struct device_attribute *attr, char *buf) { @@ -3154,10 +3224,8 @@ store_speed_tolerance(struct device *dev, struct device_attribute *attr, if (err < 0) return err; - high = fan_from_reg16(data->target_speed[nr], - data->fan_div[nr]) + val; - low = fan_from_reg16(data->target_speed[nr], - data->fan_div[nr]) - val; + high = fan_from_reg16(data->target_speed[nr], data->fan_div[nr]) + val; + low = fan_from_reg16(data->target_speed[nr], data->fan_div[nr]) - val; if (low <= 0) low = 1; if (high < low) @@ -3561,7 +3629,7 @@ clear_caseopen(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct nct6775_data *data = dev_get_drvdata(dev); - struct nct6775_sio_data *sio_data = dev_get_platdata(dev); + struct nct6775_sio_data *sio_data = data->sio_data; int nr = to_sensor_dev_attr(attr)->index - INTRUSION_ALARM_BASE; unsigned long val; u8 reg; @@ -3950,10 +4018,11 @@ static int nct6775_probe(struct platform_device *pdev) const u16 *reg_temp, *reg_temp_over, *reg_temp_hyst, *reg_temp_config; const u16 *reg_temp_mon, *reg_temp_alternate, *reg_temp_crit; const u16 *reg_temp_crit_l = NULL, *reg_temp_crit_h = NULL; - int num_reg_temp, num_reg_temp_mon; + int num_reg_temp, num_reg_temp_mon, num_reg_tsi_temp; u8 cr2a; struct attribute_group *group; struct device *hwmon_dev; + struct sensor_template_group tsi_temp_tg; int num_attr_groups = 0; if (sio_data->access == access_direct) { @@ -3969,7 +4038,7 @@ static int nct6775_probe(struct platform_device *pdev) return -ENOMEM; data->kind = sio_data->kind; - data->sioreg = sio_data->sioreg; + data->sio_data = sio_data; if (sio_data->access == access_direct) { data->addr = res->start; @@ -4045,11 +4114,13 @@ static int nct6775_probe(struct platform_device *pdev) data->ALARM_BITS = NCT6106_ALARM_BITS; data->REG_BEEP = NCT6106_REG_BEEP; data->BEEP_BITS = NCT6106_BEEP_BITS; + data->REG_TSI_TEMP = NCT6106_REG_TSI_TEMP; reg_temp = NCT6106_REG_TEMP; reg_temp_mon = NCT6106_REG_TEMP_MON; num_reg_temp = ARRAY_SIZE(NCT6106_REG_TEMP); num_reg_temp_mon = ARRAY_SIZE(NCT6106_REG_TEMP_MON); + num_reg_tsi_temp = ARRAY_SIZE(NCT6106_REG_TSI_TEMP); reg_temp_over = NCT6106_REG_TEMP_OVER; reg_temp_hyst = NCT6106_REG_TEMP_HYST; reg_temp_config = NCT6106_REG_TEMP_CONFIG; @@ -4118,11 +4189,13 @@ static int nct6775_probe(struct platform_device *pdev) data->ALARM_BITS = NCT6116_ALARM_BITS; data->REG_BEEP = NCT6106_REG_BEEP; data->BEEP_BITS = NCT6116_BEEP_BITS; + data->REG_TSI_TEMP = NCT6116_REG_TSI_TEMP; reg_temp = NCT6106_REG_TEMP; reg_temp_mon = NCT6106_REG_TEMP_MON; num_reg_temp = ARRAY_SIZE(NCT6106_REG_TEMP); num_reg_temp_mon = ARRAY_SIZE(NCT6106_REG_TEMP_MON); + num_reg_tsi_temp = ARRAY_SIZE(NCT6116_REG_TSI_TEMP); reg_temp_over = NCT6106_REG_TEMP_OVER; reg_temp_hyst = NCT6106_REG_TEMP_HYST; reg_temp_config = NCT6106_REG_TEMP_CONFIG; @@ -4193,11 +4266,13 @@ static int nct6775_probe(struct platform_device *pdev) data->REG_WEIGHT_TEMP[2] = NCT6775_REG_WEIGHT_TEMP_BASE; data->REG_ALARM = NCT6775_REG_ALARM; data->REG_BEEP = NCT6775_REG_BEEP; + data->REG_TSI_TEMP = NCT6775_REG_TSI_TEMP; reg_temp = NCT6775_REG_TEMP; reg_temp_mon = NCT6775_REG_TEMP_MON; num_reg_temp = ARRAY_SIZE(NCT6775_REG_TEMP); num_reg_temp_mon = ARRAY_SIZE(NCT6775_REG_TEMP_MON); + num_reg_tsi_temp = ARRAY_SIZE(NCT6775_REG_TSI_TEMP); reg_temp_over = NCT6775_REG_TEMP_OVER; reg_temp_hyst = NCT6775_REG_TEMP_HYST; reg_temp_config = NCT6775_REG_TEMP_CONFIG; @@ -4266,11 +4341,13 @@ static int nct6775_probe(struct platform_device *pdev) data->REG_WEIGHT_TEMP[2] = NCT6775_REG_WEIGHT_TEMP_BASE; data->REG_ALARM = NCT6775_REG_ALARM; data->REG_BEEP = NCT6776_REG_BEEP; + data->REG_TSI_TEMP = NCT6776_REG_TSI_TEMP; reg_temp = NCT6775_REG_TEMP; reg_temp_mon = NCT6775_REG_TEMP_MON; num_reg_temp = ARRAY_SIZE(NCT6775_REG_TEMP); num_reg_temp_mon = ARRAY_SIZE(NCT6775_REG_TEMP_MON); + num_reg_tsi_temp = ARRAY_SIZE(NCT6776_REG_TSI_TEMP); reg_temp_over = NCT6775_REG_TEMP_OVER; reg_temp_hyst = NCT6775_REG_TEMP_HYST; reg_temp_config = NCT6776_REG_TEMP_CONFIG; @@ -4343,11 +4420,13 @@ static int nct6775_probe(struct platform_device *pdev) data->REG_WEIGHT_TEMP[2] = NCT6775_REG_WEIGHT_TEMP_BASE; data->REG_ALARM = NCT6779_REG_ALARM; data->REG_BEEP = NCT6776_REG_BEEP; + data->REG_TSI_TEMP = NCT6776_REG_TSI_TEMP; reg_temp = NCT6779_REG_TEMP; reg_temp_mon = NCT6779_REG_TEMP_MON; num_reg_temp = ARRAY_SIZE(NCT6779_REG_TEMP); num_reg_temp_mon = ARRAY_SIZE(NCT6779_REG_TEMP_MON); + num_reg_tsi_temp = ARRAY_SIZE(NCT6776_REG_TSI_TEMP); reg_temp_over = NCT6779_REG_TEMP_OVER; reg_temp_hyst = NCT6779_REG_TEMP_HYST; reg_temp_config = NCT6779_REG_TEMP_CONFIG; @@ -4462,6 +4541,24 @@ static int nct6775_probe(struct platform_device *pdev) data->REG_BEEP = NCT6776_REG_BEEP; else data->REG_BEEP = NCT6792_REG_BEEP; + switch (data->kind) { + case nct6791: + case nct6792: + case nct6793: + data->REG_TSI_TEMP = NCT6776_REG_TSI_TEMP; + num_reg_tsi_temp = ARRAY_SIZE(NCT6776_REG_TSI_TEMP); + break; + case nct6795: + case nct6796: + case nct6797: + case nct6798: + data->REG_TSI_TEMP = NCT6796_REG_TSI_TEMP; + num_reg_tsi_temp = ARRAY_SIZE(NCT6796_REG_TSI_TEMP); + break; + default: + num_reg_tsi_temp = 0; + break; + } reg_temp = NCT6779_REG_TEMP; num_reg_temp = ARRAY_SIZE(NCT6779_REG_TEMP); @@ -4661,6 +4758,12 @@ static int nct6775_probe(struct platform_device *pdev) } #endif /* USE_ALTERNATE */ + /* Check which TSIx_TEMP registers are active */ + for (i = 0; i < num_reg_tsi_temp; i++) { + if (data->read_value(data, data->REG_TSI_TEMP[i])) + data->have_tsi_temp |= BIT(i); + } + /* Initialize the chip */ nct6775_init_device(data); @@ -4768,6 +4871,18 @@ static int nct6775_probe(struct platform_device *pdev) return PTR_ERR(group); data->groups[num_attr_groups++] = group; + + if (data->have_tsi_temp) { + tsi_temp_tg.templates = nct6775_tsi_temp_template; + tsi_temp_tg.is_visible = nct6775_tsi_temp_is_visible; + tsi_temp_tg.base = fls(data->have_temp) + 1; + group = nct6775_create_attr_group(dev, &tsi_temp_tg, fls(data->have_tsi_temp)); + if (IS_ERR(group)) + return PTR_ERR(group); + + data->groups[num_attr_groups++] = group; + } + data->groups[num_attr_groups++] = &nct6775_group_other; hwmon_dev = devm_hwmon_device_register_with_groups(dev, data->name, @@ -4987,20 +5102,39 @@ static struct platform_device *pdev[2]; static const char * const asus_wmi_boards[] = { "ProArt X570-CREATOR WIFI", + "Pro B550M-C", "Pro WS X570-ACE", "PRIME B360-PLUS", "PRIME B460-PLUS", + "PRIME B550-PLUS", + "PRIME B550M-A", + "PRIME B550M-A (WI-FI)", + "PRIME X570-P", "PRIME X570-PRO", "ROG CROSSHAIR VIII DARK HERO", "ROG CROSSHAIR VIII FORMULA", "ROG CROSSHAIR VIII HERO", "ROG CROSSHAIR VIII IMPACT", + "ROG STRIX B550-A GAMING", "ROG STRIX B550-E GAMING", "ROG STRIX B550-F GAMING", "ROG STRIX B550-F GAMING (WI-FI)", + "ROG STRIX B550-F GAMING WIFI II", "ROG STRIX B550-I GAMING", + "ROG STRIX B550-XE GAMING (WI-FI)", + "ROG STRIX X570-E GAMING", "ROG STRIX X570-F GAMING", + "ROG STRIX X570-I GAMING", "ROG STRIX Z390-E GAMING", + "ROG STRIX Z390-F GAMING", + "ROG STRIX Z390-H GAMING", + "ROG STRIX Z390-I GAMING", + "ROG STRIX Z490-A GAMING", + "ROG STRIX Z490-E GAMING", + "ROG STRIX Z490-F GAMING", + "ROG STRIX Z490-G GAMING", + "ROG STRIX Z490-G GAMING (WI-FI)", + "ROG STRIX Z490-H GAMING", "ROG STRIX Z490-I GAMING", "TUF GAMING B550M-PLUS", "TUF GAMING B550M-PLUS (WI-FI)", @@ -5038,7 +5172,7 @@ static int __init sensors_nct6775_init(void) board_name); if (err >= 0) { /* if reading chip id via WMI succeeds, use WMI */ - if (!nct6775_asuswmi_read(0, NCT6775_PORT_CHIPID, &tmp)) { + if (!nct6775_asuswmi_read(0, NCT6775_PORT_CHIPID, &tmp) && tmp) { pr_info("Using Asus WMI to access %#x chip.\n", tmp); access = access_asuswmi; } else { diff --git a/drivers/hwmon/ntc_thermistor.c b/drivers/hwmon/ntc_thermistor.c index cf26c44f2b88..9c9e9f4ccb9e 100644 --- a/drivers/hwmon/ntc_thermistor.c +++ b/drivers/hwmon/ntc_thermistor.c @@ -9,18 +9,23 @@ #include <linux/slab.h> #include <linux/module.h> #include <linux/math64.h> +#include <linux/mod_devicetable.h> #include <linux/platform_device.h> +#include <linux/property.h> #include <linux/err.h> -#include <linux/of.h> -#include <linux/of_device.h> #include <linux/fixp-arith.h> - -#include <linux/platform_data/ntc_thermistor.h> - #include <linux/iio/consumer.h> - #include <linux/hwmon.h> +enum ntc_thermistor_type { + TYPE_B57330V2103, + TYPE_B57891S0103, + TYPE_NCPXXWB473, + TYPE_NCPXXWF104, + TYPE_NCPXXWL333, + TYPE_NCPXXXH103, +}; + struct ntc_compensation { int temp_c; unsigned int ohm; @@ -40,6 +45,7 @@ enum { NTC_NCP15XH103, NTC_NCP18WB473, NTC_NCP21WB473, + NTC_SSG1404001221, NTC_LAST, }; @@ -53,6 +59,7 @@ static const struct platform_device_id ntc_thermistor_id[] = { [NTC_NCP15XH103] = { "ncp15xh103", TYPE_NCPXXXH103 }, [NTC_NCP18WB473] = { "ncp18wb473", TYPE_NCPXXWB473 }, [NTC_NCP21WB473] = { "ncp21wb473", TYPE_NCPXXWB473 }, + [NTC_SSG1404001221] = { "ssg1404_001221", TYPE_NCPXXWB473 }, [NTC_LAST] = { }, }; @@ -313,16 +320,30 @@ static const struct ntc_type ntc_type[] = { NTC_TYPE(TYPE_NCPXXXH103, ncpXXxh103), }; +/* + * pullup_uV, pullup_ohm, pulldown_ohm, and connect are required. + * + * How to setup pullup_ohm, pulldown_ohm, and connect is + * described at Documentation/hwmon/ntc_thermistor.rst + * + * pullup/down_ohm: 0 for infinite / not-connected + * + * chan: iio_channel pointer to communicate with the ADC which the + * thermistor is using for conversion of the analog values. + */ struct ntc_data { - struct ntc_thermistor_platform_data *pdata; const struct ntc_compensation *comp; int n_comp; + unsigned int pullup_uv; + unsigned int pullup_ohm; + unsigned int pulldown_ohm; + enum { NTC_CONNECTED_POSITIVE, NTC_CONNECTED_GROUND } connect; + struct iio_channel *chan; }; -#if defined(CONFIG_OF) && IS_ENABLED(CONFIG_IIO) -static int ntc_adc_iio_read(struct ntc_thermistor_platform_data *pdata) +static int ntc_adc_iio_read(struct ntc_data *data) { - struct iio_channel *channel = pdata->chan; + struct iio_channel *channel = data->chan; int uv, ret; ret = iio_read_channel_processed_scale(channel, &uv, 1000); @@ -342,103 +363,13 @@ static int ntc_adc_iio_read(struct ntc_thermistor_platform_data *pdata) ret = iio_convert_raw_to_processed(channel, raw, &uv, 1000); if (ret < 0) { /* Assume 12 bit ADC with vref at pullup_uv */ - uv = (pdata->pullup_uv * (s64)raw) >> 12; + uv = (data->pullup_uv * (s64)raw) >> 12; } } return uv; } -static const struct of_device_id ntc_match[] = { - { .compatible = "epcos,b57330v2103", - .data = &ntc_thermistor_id[NTC_B57330V2103]}, - { .compatible = "epcos,b57891s0103", - .data = &ntc_thermistor_id[NTC_B57891S0103] }, - { .compatible = "murata,ncp03wb473", - .data = &ntc_thermistor_id[NTC_NCP03WB473] }, - { .compatible = "murata,ncp03wf104", - .data = &ntc_thermistor_id[NTC_NCP03WF104] }, - { .compatible = "murata,ncp15wb473", - .data = &ntc_thermistor_id[NTC_NCP15WB473] }, - { .compatible = "murata,ncp15wl333", - .data = &ntc_thermistor_id[NTC_NCP15WL333] }, - { .compatible = "murata,ncp15xh103", - .data = &ntc_thermistor_id[NTC_NCP15XH103] }, - { .compatible = "murata,ncp18wb473", - .data = &ntc_thermistor_id[NTC_NCP18WB473] }, - { .compatible = "murata,ncp21wb473", - .data = &ntc_thermistor_id[NTC_NCP21WB473] }, - - /* Usage of vendor name "ntc" is deprecated */ - { .compatible = "ntc,ncp03wb473", - .data = &ntc_thermistor_id[NTC_NCP03WB473] }, - { .compatible = "ntc,ncp15wb473", - .data = &ntc_thermistor_id[NTC_NCP15WB473] }, - { .compatible = "ntc,ncp15wl333", - .data = &ntc_thermistor_id[NTC_NCP15WL333] }, - { .compatible = "ntc,ncp18wb473", - .data = &ntc_thermistor_id[NTC_NCP18WB473] }, - { .compatible = "ntc,ncp21wb473", - .data = &ntc_thermistor_id[NTC_NCP21WB473] }, - { }, -}; -MODULE_DEVICE_TABLE(of, ntc_match); - -static struct ntc_thermistor_platform_data * -ntc_thermistor_parse_dt(struct device *dev) -{ - struct iio_channel *chan; - enum iio_chan_type type; - struct device_node *np = dev->of_node; - struct ntc_thermistor_platform_data *pdata; - int ret; - - if (!np) - return NULL; - - pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); - if (!pdata) - return ERR_PTR(-ENOMEM); - - chan = devm_iio_channel_get(dev, NULL); - if (IS_ERR(chan)) - return ERR_CAST(chan); - - ret = iio_get_channel_type(chan, &type); - if (ret < 0) - return ERR_PTR(ret); - - if (type != IIO_VOLTAGE) - return ERR_PTR(-EINVAL); - - if (of_property_read_u32(np, "pullup-uv", &pdata->pullup_uv)) - return ERR_PTR(-ENODEV); - if (of_property_read_u32(np, "pullup-ohm", &pdata->pullup_ohm)) - return ERR_PTR(-ENODEV); - if (of_property_read_u32(np, "pulldown-ohm", &pdata->pulldown_ohm)) - return ERR_PTR(-ENODEV); - - if (of_find_property(np, "connected-positive", NULL)) - pdata->connect = NTC_CONNECTED_POSITIVE; - else /* status change should be possible if not always on. */ - pdata->connect = NTC_CONNECTED_GROUND; - - pdata->chan = chan; - pdata->read_uv = ntc_adc_iio_read; - - return pdata; -} -#else -static struct ntc_thermistor_platform_data * -ntc_thermistor_parse_dt(struct device *dev) -{ - return NULL; -} - -#define ntc_match NULL - -#endif - static inline u64 div64_u64_safe(u64 dividend, u64 divisor) { if (divisor == 0 && dividend == 0) @@ -450,24 +381,23 @@ static inline u64 div64_u64_safe(u64 dividend, u64 divisor) static int get_ohm_of_thermistor(struct ntc_data *data, unsigned int uv) { - struct ntc_thermistor_platform_data *pdata = data->pdata; - u32 puv = pdata->pullup_uv; + u32 puv = data->pullup_uv; u64 n, puo, pdo; - puo = pdata->pullup_ohm; - pdo = pdata->pulldown_ohm; + puo = data->pullup_ohm; + pdo = data->pulldown_ohm; if (uv == 0) - return (pdata->connect == NTC_CONNECTED_POSITIVE) ? + return (data->connect == NTC_CONNECTED_POSITIVE) ? INT_MAX : 0; if (uv >= puv) - return (pdata->connect == NTC_CONNECTED_POSITIVE) ? + return (data->connect == NTC_CONNECTED_POSITIVE) ? 0 : INT_MAX; - if (pdata->connect == NTC_CONNECTED_POSITIVE && puo == 0) + if (data->connect == NTC_CONNECTED_POSITIVE && puo == 0) n = div_u64(pdo * (puv - uv), uv); - else if (pdata->connect == NTC_CONNECTED_GROUND && pdo == 0) + else if (data->connect == NTC_CONNECTED_GROUND && pdo == 0) n = div_u64(puo * uv, puv - uv); - else if (pdata->connect == NTC_CONNECTED_POSITIVE) + else if (data->connect == NTC_CONNECTED_POSITIVE) n = div64_u64_safe(pdo * puo * (puv - uv), puo * uv - pdo * (puv - uv)); else @@ -567,16 +497,10 @@ static int ntc_thermistor_get_ohm(struct ntc_data *data) { int read_uv; - if (data->pdata->read_ohm) - return data->pdata->read_ohm(); - - if (data->pdata->read_uv) { - read_uv = data->pdata->read_uv(data->pdata); - if (read_uv < 0) - return read_uv; - return get_ohm_of_thermistor(data, read_uv); - } - return -EINVAL; + read_uv = ntc_adc_iio_read(data); + if (read_uv < 0) + return read_uv; + return get_ohm_of_thermistor(data, read_uv); } static int ntc_read(struct device *dev, enum hwmon_sensor_types type, @@ -638,58 +562,74 @@ static const struct hwmon_chip_info ntc_chip_info = { .info = ntc_info, }; -static int ntc_thermistor_probe(struct platform_device *pdev) +static int ntc_thermistor_parse_props(struct device *dev, + struct ntc_data *data) { - struct device *dev = &pdev->dev; - const struct of_device_id *of_id = - of_match_device(of_match_ptr(ntc_match), dev); - const struct platform_device_id *pdev_id; - struct ntc_thermistor_platform_data *pdata; - struct device *hwmon_dev; - struct ntc_data *data; + struct iio_channel *chan; + enum iio_chan_type type; + int ret; - pdata = ntc_thermistor_parse_dt(dev); - if (IS_ERR(pdata)) - return PTR_ERR(pdata); - else if (pdata == NULL) - pdata = dev_get_platdata(dev); + chan = devm_iio_channel_get(dev, NULL); + if (IS_ERR(chan)) + return PTR_ERR(chan); - if (!pdata) { - dev_err(dev, "No platform init data supplied.\n"); - return -ENODEV; - } + ret = iio_get_channel_type(chan, &type); + if (ret < 0) + return ret; - /* Either one of the two is required. */ - if (!pdata->read_uv && !pdata->read_ohm) { - dev_err(dev, - "Both read_uv and read_ohm missing. Need either one of the two.\n"); + if (type != IIO_VOLTAGE) return -EINVAL; - } - if (pdata->read_uv && pdata->read_ohm) { - dev_warn(dev, - "Only one of read_uv and read_ohm is needed; ignoring read_uv.\n"); - pdata->read_uv = NULL; - } + ret = device_property_read_u32(dev, "pullup-uv", &data->pullup_uv); + if (ret) + return dev_err_probe(dev, ret, "pullup-uv not specified\n"); - if (pdata->read_uv && (pdata->pullup_uv == 0 || - (pdata->pullup_ohm == 0 && pdata->connect == - NTC_CONNECTED_GROUND) || - (pdata->pulldown_ohm == 0 && pdata->connect == - NTC_CONNECTED_POSITIVE) || - (pdata->connect != NTC_CONNECTED_POSITIVE && - pdata->connect != NTC_CONNECTED_GROUND))) { - dev_err(dev, "Required data to use read_uv not supplied.\n"); - return -EINVAL; - } + ret = device_property_read_u32(dev, "pullup-ohm", &data->pullup_ohm); + if (ret) + return dev_err_probe(dev, ret, "pullup-ohm not specified\n"); + + ret = device_property_read_u32(dev, "pulldown-ohm", &data->pulldown_ohm); + if (ret) + return dev_err_probe(dev, ret, "pulldown-ohm not specified\n"); + + if (device_property_read_bool(dev, "connected-positive")) + data->connect = NTC_CONNECTED_POSITIVE; + else /* status change should be possible if not always on. */ + data->connect = NTC_CONNECTED_GROUND; + + data->chan = chan; + + return 0; +} - data = devm_kzalloc(dev, sizeof(struct ntc_data), GFP_KERNEL); +static int ntc_thermistor_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct platform_device_id *pdev_id; + struct device *hwmon_dev; + struct ntc_data *data; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; - pdev_id = of_id ? of_id->data : platform_get_device_id(pdev); + ret = ntc_thermistor_parse_props(dev, data); + if (ret) + return ret; + + if (data->pullup_uv == 0 || + (data->pullup_ohm == 0 && data->connect == + NTC_CONNECTED_GROUND) || + (data->pulldown_ohm == 0 && data->connect == + NTC_CONNECTED_POSITIVE) || + (data->connect != NTC_CONNECTED_POSITIVE && + data->connect != NTC_CONNECTED_GROUND)) { + dev_err(dev, "Required data to use NTC driver not supplied.\n"); + return -EINVAL; + } - data->pdata = pdata; + pdev_id = device_get_match_data(dev); if (pdev_id->driver_data >= ARRAY_SIZE(ntc_type)) { dev_err(dev, "Unknown device type: %lu(%s)\n", @@ -714,10 +654,47 @@ static int ntc_thermistor_probe(struct platform_device *pdev) return 0; } +static const struct of_device_id ntc_match[] = { + { .compatible = "epcos,b57330v2103", + .data = &ntc_thermistor_id[NTC_B57330V2103]}, + { .compatible = "epcos,b57891s0103", + .data = &ntc_thermistor_id[NTC_B57891S0103] }, + { .compatible = "murata,ncp03wb473", + .data = &ntc_thermistor_id[NTC_NCP03WB473] }, + { .compatible = "murata,ncp03wf104", + .data = &ntc_thermistor_id[NTC_NCP03WF104] }, + { .compatible = "murata,ncp15wb473", + .data = &ntc_thermistor_id[NTC_NCP15WB473] }, + { .compatible = "murata,ncp15wl333", + .data = &ntc_thermistor_id[NTC_NCP15WL333] }, + { .compatible = "murata,ncp15xh103", + .data = &ntc_thermistor_id[NTC_NCP15XH103] }, + { .compatible = "murata,ncp18wb473", + .data = &ntc_thermistor_id[NTC_NCP18WB473] }, + { .compatible = "murata,ncp21wb473", + .data = &ntc_thermistor_id[NTC_NCP21WB473] }, + { .compatible = "samsung,1404-001221", + .data = &ntc_thermistor_id[NTC_SSG1404001221] }, + + /* Usage of vendor name "ntc" is deprecated */ + { .compatible = "ntc,ncp03wb473", + .data = &ntc_thermistor_id[NTC_NCP03WB473] }, + { .compatible = "ntc,ncp15wb473", + .data = &ntc_thermistor_id[NTC_NCP15WB473] }, + { .compatible = "ntc,ncp15wl333", + .data = &ntc_thermistor_id[NTC_NCP15WL333] }, + { .compatible = "ntc,ncp18wb473", + .data = &ntc_thermistor_id[NTC_NCP18WB473] }, + { .compatible = "ntc,ncp21wb473", + .data = &ntc_thermistor_id[NTC_NCP21WB473] }, + { }, +}; +MODULE_DEVICE_TABLE(of, ntc_match); + static struct platform_driver ntc_thermistor_driver = { .driver = { .name = "ntc-thermistor", - .of_match_table = of_match_ptr(ntc_match), + .of_match_table = ntc_match, }, .probe = ntc_thermistor_probe, .id_table = ntc_thermistor_id, diff --git a/drivers/hwmon/nzxt-smart2.c b/drivers/hwmon/nzxt-smart2.c new file mode 100644 index 000000000000..dd892ff5a3e8 --- /dev/null +++ b/drivers/hwmon/nzxt-smart2.c @@ -0,0 +1,829 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Reverse-engineered NZXT RGB & Fan Controller/Smart Device v2 driver. + * + * Copyright (c) 2021 Aleksandr Mezin + */ + +#include <linux/hid.h> +#include <linux/hwmon.h> +#include <linux/math.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <linux/wait.h> + +#include <asm/byteorder.h> +#include <asm/unaligned.h> + +/* + * The device has only 3 fan channels/connectors. But all HID reports have + * space reserved for up to 8 channels. + */ +#define FAN_CHANNELS 3 +#define FAN_CHANNELS_MAX 8 + +#define UPDATE_INTERVAL_DEFAULT_MS 1000 + +/* These strings match labels on the device exactly */ +static const char *const fan_label[] = { + "FAN 1", + "FAN 2", + "FAN 3", +}; + +static const char *const curr_label[] = { + "FAN 1 Current", + "FAN 2 Current", + "FAN 3 Current", +}; + +static const char *const in_label[] = { + "FAN 1 Voltage", + "FAN 2 Voltage", + "FAN 3 Voltage", +}; + +enum { + INPUT_REPORT_ID_FAN_CONFIG = 0x61, + INPUT_REPORT_ID_FAN_STATUS = 0x67, +}; + +enum { + FAN_STATUS_REPORT_SPEED = 0x02, + FAN_STATUS_REPORT_VOLTAGE = 0x04, +}; + +enum { + FAN_TYPE_NONE = 0, + FAN_TYPE_DC = 1, + FAN_TYPE_PWM = 2, +}; + +struct unknown_static_data { + /* + * Some configuration data? Stays the same after fan speed changes, + * changes in fan configuration, reboots and driver reloads. + * + * The same data in multiple report types. + * + * Byte 12 seems to be the number of fan channels, but I am not sure. + */ + u8 unknown1[14]; +} __packed; + +/* + * The device sends this input report in response to "detect fans" command: + * a 2-byte output report { 0x60, 0x03 }. + */ +struct fan_config_report { + /* report_id should be INPUT_REPORT_ID_FAN_CONFIG = 0x61 */ + u8 report_id; + /* Always 0x03 */ + u8 magic; + struct unknown_static_data unknown_data; + /* Fan type as detected by the device. See FAN_TYPE_* enum. */ + u8 fan_type[FAN_CHANNELS_MAX]; +} __packed; + +/* + * The device sends these reports at a fixed interval (update interval) - + * one report with type = FAN_STATUS_REPORT_SPEED, and one report with type = + * FAN_STATUS_REPORT_VOLTAGE per update interval. + */ +struct fan_status_report { + /* report_id should be INPUT_REPORT_ID_STATUS = 0x67 */ + u8 report_id; + /* FAN_STATUS_REPORT_SPEED = 0x02 or FAN_STATUS_REPORT_VOLTAGE = 0x04 */ + u8 type; + struct unknown_static_data unknown_data; + /* Fan type as detected by the device. See FAN_TYPE_* enum. */ + u8 fan_type[FAN_CHANNELS_MAX]; + + union { + /* When type == FAN_STATUS_REPORT_SPEED */ + struct { + /* + * Fan speed, in RPM. Zero for channels without fans + * connected. + */ + __le16 fan_rpm[FAN_CHANNELS_MAX]; + /* + * Fan duty cycle, in percent. Non-zero even for + * channels without fans connected. + */ + u8 duty_percent[FAN_CHANNELS_MAX]; + /* + * Exactly the same values as duty_percent[], non-zero + * for disconnected fans too. + */ + u8 duty_percent_dup[FAN_CHANNELS_MAX]; + /* "Case Noise" in db */ + u8 noise_db; + } __packed fan_speed; + /* When type == FAN_STATUS_REPORT_VOLTAGE */ + struct { + /* + * Voltage, in millivolts. Non-zero even when fan is + * not connected. + */ + __le16 fan_in[FAN_CHANNELS_MAX]; + /* + * Current, in milliamperes. Near-zero when + * disconnected. + */ + __le16 fan_current[FAN_CHANNELS_MAX]; + } __packed fan_voltage; + } __packed; +} __packed; + +#define OUTPUT_REPORT_SIZE 64 + +enum { + OUTPUT_REPORT_ID_INIT_COMMAND = 0x60, + OUTPUT_REPORT_ID_SET_FAN_SPEED = 0x62, +}; + +enum { + INIT_COMMAND_SET_UPDATE_INTERVAL = 0x02, + INIT_COMMAND_DETECT_FANS = 0x03, +}; + +/* + * This output report sets pwm duty cycle/target fan speed for one or more + * channels. + */ +struct set_fan_speed_report { + /* report_id should be OUTPUT_REPORT_ID_SET_FAN_SPEED = 0x62 */ + u8 report_id; + /* Should be 0x01 */ + u8 magic; + /* To change fan speed on i-th channel, set i-th bit here */ + u8 channel_bit_mask; + /* + * Fan duty cycle/target speed in percent. For voltage-controlled fans, + * the minimal voltage (duty_percent = 1) is about 9V. + * Setting duty_percent to 0 (if the channel is selected in + * channel_bit_mask) turns off the fan completely (regardless of the + * control mode). + */ + u8 duty_percent[FAN_CHANNELS_MAX]; +} __packed; + +struct drvdata { + struct hid_device *hid; + struct device *hwmon; + + u8 fan_duty_percent[FAN_CHANNELS]; + u16 fan_rpm[FAN_CHANNELS]; + bool pwm_status_received; + + u16 fan_in[FAN_CHANNELS]; + u16 fan_curr[FAN_CHANNELS]; + bool voltage_status_received; + + u8 fan_type[FAN_CHANNELS]; + bool fan_config_received; + + /* + * wq is used to wait for *_received flags to become true. + * All accesses to *_received flags and fan_* arrays are performed with + * wq.lock held. + */ + wait_queue_head_t wq; + /* + * mutex is used to: + * 1) Prevent concurrent conflicting changes to update interval and pwm + * values (after sending an output hid report, the corresponding field + * in drvdata must be updated, and only then new output reports can be + * sent). + * 2) Synchronize access to output_buffer (well, the buffer is here, + * because synchronization is necessary anyway - so why not get rid of + * a kmalloc?). + */ + struct mutex mutex; + long update_interval; + u8 output_buffer[OUTPUT_REPORT_SIZE]; +}; + +static long scale_pwm_value(long val, long orig_max, long new_max) +{ + if (val <= 0) + return 0; + + /* + * Positive values should not become zero: 0 completely turns off the + * fan. + */ + return max(1L, DIV_ROUND_CLOSEST(min(val, orig_max) * new_max, orig_max)); +} + +static void handle_fan_config_report(struct drvdata *drvdata, void *data, int size) +{ + struct fan_config_report *report = data; + int i; + + if (size < sizeof(struct fan_config_report)) + return; + + if (report->magic != 0x03) + return; + + spin_lock(&drvdata->wq.lock); + + for (i = 0; i < FAN_CHANNELS; i++) + drvdata->fan_type[i] = report->fan_type[i]; + + drvdata->fan_config_received = true; + wake_up_all_locked(&drvdata->wq); + spin_unlock(&drvdata->wq.lock); +} + +static void handle_fan_status_report(struct drvdata *drvdata, void *data, int size) +{ + struct fan_status_report *report = data; + int i; + + if (size < sizeof(struct fan_status_report)) + return; + + spin_lock(&drvdata->wq.lock); + + /* + * The device sends INPUT_REPORT_ID_FAN_CONFIG = 0x61 report in response + * to "detect fans" command. Only accept other data after getting 0x61, + * to make sure that fan detection is complete. In particular, fan + * detection resets pwm values. + */ + if (!drvdata->fan_config_received) { + spin_unlock(&drvdata->wq.lock); + return; + } + + for (i = 0; i < FAN_CHANNELS; i++) { + if (drvdata->fan_type[i] == report->fan_type[i]) + continue; + + /* + * This should not happen (if my expectations about the device + * are correct). + * + * Even if the userspace sends fan detect command through + * hidraw, fan config report should arrive first. + */ + hid_warn_once(drvdata->hid, + "Fan %d type changed unexpectedly from %d to %d", + i, drvdata->fan_type[i], report->fan_type[i]); + drvdata->fan_type[i] = report->fan_type[i]; + } + + switch (report->type) { + case FAN_STATUS_REPORT_SPEED: + for (i = 0; i < FAN_CHANNELS; i++) { + drvdata->fan_rpm[i] = + get_unaligned_le16(&report->fan_speed.fan_rpm[i]); + drvdata->fan_duty_percent[i] = + report->fan_speed.duty_percent[i]; + } + + drvdata->pwm_status_received = true; + wake_up_all_locked(&drvdata->wq); + break; + + case FAN_STATUS_REPORT_VOLTAGE: + for (i = 0; i < FAN_CHANNELS; i++) { + drvdata->fan_in[i] = + get_unaligned_le16(&report->fan_voltage.fan_in[i]); + drvdata->fan_curr[i] = + get_unaligned_le16(&report->fan_voltage.fan_current[i]); + } + + drvdata->voltage_status_received = true; + wake_up_all_locked(&drvdata->wq); + break; + } + + spin_unlock(&drvdata->wq.lock); +} + +static umode_t nzxt_smart2_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + case hwmon_pwm_enable: + return 0644; + + default: + return 0444; + } + + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + return 0644; + + default: + return 0444; + } + + default: + return 0444; + } +} + +static int nzxt_smart2_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct drvdata *drvdata = dev_get_drvdata(dev); + int res = -EINVAL; + + if (type == hwmon_chip) { + switch (attr) { + case hwmon_chip_update_interval: + *val = drvdata->update_interval; + return 0; + + default: + return -EINVAL; + } + } + + spin_lock_irq(&drvdata->wq.lock); + + switch (type) { + case hwmon_pwm: + /* + * fancontrol: + * 1) remembers pwm* values when it starts + * 2) needs pwm*_enable to be 1 on controlled fans + * So make sure we have correct data before allowing pwm* reads. + * Returning errors for pwm of fan speed read can even cause + * fancontrol to shut down. So the wait is unavoidable. + */ + switch (attr) { + case hwmon_pwm_enable: + res = wait_event_interruptible_locked_irq(drvdata->wq, + drvdata->fan_config_received); + if (res) + goto unlock; + + *val = drvdata->fan_type[channel] != FAN_TYPE_NONE; + break; + + case hwmon_pwm_mode: + res = wait_event_interruptible_locked_irq(drvdata->wq, + drvdata->fan_config_received); + if (res) + goto unlock; + + *val = drvdata->fan_type[channel] == FAN_TYPE_PWM; + break; + + case hwmon_pwm_input: + res = wait_event_interruptible_locked_irq(drvdata->wq, + drvdata->pwm_status_received); + if (res) + goto unlock; + + *val = scale_pwm_value(drvdata->fan_duty_percent[channel], + 100, 255); + break; + } + break; + + case hwmon_fan: + /* + * It's not strictly necessary to wait for *_received in the + * remaining cases (fancontrol doesn't care about them). But I'm + * doing it to have consistent behavior. + */ + if (attr == hwmon_fan_input) { + res = wait_event_interruptible_locked_irq(drvdata->wq, + drvdata->pwm_status_received); + if (res) + goto unlock; + + *val = drvdata->fan_rpm[channel]; + } + break; + + case hwmon_in: + if (attr == hwmon_in_input) { + res = wait_event_interruptible_locked_irq(drvdata->wq, + drvdata->voltage_status_received); + if (res) + goto unlock; + + *val = drvdata->fan_in[channel]; + } + break; + + case hwmon_curr: + if (attr == hwmon_curr_input) { + res = wait_event_interruptible_locked_irq(drvdata->wq, + drvdata->voltage_status_received); + if (res) + goto unlock; + + *val = drvdata->fan_curr[channel]; + } + break; + + default: + break; + } + +unlock: + spin_unlock_irq(&drvdata->wq.lock); + return res; +} + +static int send_output_report(struct drvdata *drvdata, const void *data, + size_t data_size) +{ + int ret; + + if (data_size > sizeof(drvdata->output_buffer)) + return -EINVAL; + + memcpy(drvdata->output_buffer, data, data_size); + + if (data_size < sizeof(drvdata->output_buffer)) + memset(drvdata->output_buffer + data_size, 0, + sizeof(drvdata->output_buffer) - data_size); + + ret = hid_hw_output_report(drvdata->hid, drvdata->output_buffer, + sizeof(drvdata->output_buffer)); + return ret < 0 ? ret : 0; +} + +static int set_pwm(struct drvdata *drvdata, int channel, long val) +{ + int ret; + u8 duty_percent = scale_pwm_value(val, 255, 100); + + struct set_fan_speed_report report = { + .report_id = OUTPUT_REPORT_ID_SET_FAN_SPEED, + .magic = 1, + .channel_bit_mask = 1 << channel + }; + + ret = mutex_lock_interruptible(&drvdata->mutex); + if (ret) + return ret; + + report.duty_percent[channel] = duty_percent; + ret = send_output_report(drvdata, &report, sizeof(report)); + if (ret) + goto unlock; + + /* + * pwmconfig and fancontrol scripts expect pwm writes to take effect + * immediately (i. e. read from pwm* sysfs should return the value + * written into it). The device seems to always accept pwm values - even + * when there is no fan connected - so update pwm status without waiting + * for a report, to make pwmconfig and fancontrol happy. Worst case - + * if the device didn't accept new pwm value for some reason (never seen + * this in practice) - it will be reported incorrectly only until next + * update. This avoids "fan stuck" messages from pwmconfig, and + * fancontrol setting fan speed to 100% during shutdown. + */ + spin_lock_bh(&drvdata->wq.lock); + drvdata->fan_duty_percent[channel] = duty_percent; + spin_unlock_bh(&drvdata->wq.lock); + +unlock: + mutex_unlock(&drvdata->mutex); + return ret; +} + +/* + * Workaround for fancontrol/pwmconfig trying to write to pwm*_enable even if it + * already is 1 and read-only. Otherwise, fancontrol won't restore pwm on + * shutdown properly. + */ +static int set_pwm_enable(struct drvdata *drvdata, int channel, long val) +{ + long expected_val; + int res; + + spin_lock_irq(&drvdata->wq.lock); + + res = wait_event_interruptible_locked_irq(drvdata->wq, + drvdata->fan_config_received); + if (res) { + spin_unlock_irq(&drvdata->wq.lock); + return res; + } + + expected_val = drvdata->fan_type[channel] != FAN_TYPE_NONE; + + spin_unlock_irq(&drvdata->wq.lock); + + return (val == expected_val) ? 0 : -EOPNOTSUPP; +} + +/* + * Control byte | Actual update interval in seconds + * 0xff | 65.5 + * 0xf7 | 63.46 + * 0x7f | 32.74 + * 0x3f | 16.36 + * 0x1f | 8.17 + * 0x0f | 4.07 + * 0x07 | 2.02 + * 0x03 | 1.00 + * 0x02 | 0.744 + * 0x01 | 0.488 + * 0x00 | 0.25 + */ +static u8 update_interval_to_control_byte(long interval) +{ + if (interval <= 250) + return 0; + + return clamp_val(1 + DIV_ROUND_CLOSEST(interval - 488, 256), 0, 255); +} + +static long control_byte_to_update_interval(u8 control_byte) +{ + if (control_byte == 0) + return 250; + + return 488 + (control_byte - 1) * 256; +} + +static int set_update_interval(struct drvdata *drvdata, long val) +{ + u8 control = update_interval_to_control_byte(val); + u8 report[] = { + OUTPUT_REPORT_ID_INIT_COMMAND, + INIT_COMMAND_SET_UPDATE_INTERVAL, + 0x01, + 0xe8, + control, + 0x01, + 0xe8, + control, + }; + int ret; + + ret = send_output_report(drvdata, report, sizeof(report)); + if (ret) + return ret; + + drvdata->update_interval = control_byte_to_update_interval(control); + return 0; +} + +static int init_device(struct drvdata *drvdata, long update_interval) +{ + int ret; + static const u8 detect_fans_report[] = { + OUTPUT_REPORT_ID_INIT_COMMAND, + INIT_COMMAND_DETECT_FANS, + }; + + ret = send_output_report(drvdata, detect_fans_report, + sizeof(detect_fans_report)); + if (ret) + return ret; + + return set_update_interval(drvdata, update_interval); +} + +static int nzxt_smart2_hwmon_write(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, long val) +{ + struct drvdata *drvdata = dev_get_drvdata(dev); + int ret; + + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_enable: + return set_pwm_enable(drvdata, channel, val); + + case hwmon_pwm_input: + return set_pwm(drvdata, channel, val); + + default: + return -EINVAL; + } + + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + ret = mutex_lock_interruptible(&drvdata->mutex); + if (ret) + return ret; + + ret = set_update_interval(drvdata, val); + + mutex_unlock(&drvdata->mutex); + return ret; + + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +static int nzxt_smart2_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + switch (type) { + case hwmon_fan: + *str = fan_label[channel]; + return 0; + case hwmon_curr: + *str = curr_label[channel]; + return 0; + case hwmon_in: + *str = in_label[channel]; + return 0; + default: + return -EINVAL; + } +} + +static const struct hwmon_ops nzxt_smart2_hwmon_ops = { + .is_visible = nzxt_smart2_hwmon_is_visible, + .read = nzxt_smart2_hwmon_read, + .read_string = nzxt_smart2_hwmon_read_string, + .write = nzxt_smart2_hwmon_write, +}; + +static const struct hwmon_channel_info *nzxt_smart2_channel_info[] = { + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL), + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_MODE | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT | HWMON_PWM_MODE | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT | HWMON_PWM_MODE | HWMON_PWM_ENABLE), + HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL), + HWMON_CHANNEL_INFO(curr, HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL), + HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL), + NULL +}; + +static const struct hwmon_chip_info nzxt_smart2_chip_info = { + .ops = &nzxt_smart2_hwmon_ops, + .info = nzxt_smart2_channel_info, +}; + +static int nzxt_smart2_hid_raw_event(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size) +{ + struct drvdata *drvdata = hid_get_drvdata(hdev); + u8 report_id = *data; + + switch (report_id) { + case INPUT_REPORT_ID_FAN_CONFIG: + handle_fan_config_report(drvdata, data, size); + break; + + case INPUT_REPORT_ID_FAN_STATUS: + handle_fan_status_report(drvdata, data, size); + break; + } + + return 0; +} + +static int __maybe_unused nzxt_smart2_hid_reset_resume(struct hid_device *hdev) +{ + struct drvdata *drvdata = hid_get_drvdata(hdev); + + /* + * Userspace is still frozen (so no concurrent sysfs attribute access + * is possible), but raw_event can already be called concurrently. + */ + spin_lock_bh(&drvdata->wq.lock); + drvdata->fan_config_received = false; + drvdata->pwm_status_received = false; + drvdata->voltage_status_received = false; + spin_unlock_bh(&drvdata->wq.lock); + + return init_device(drvdata, drvdata->update_interval); +} + +static int nzxt_smart2_hid_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + struct drvdata *drvdata; + int ret; + + drvdata = devm_kzalloc(&hdev->dev, sizeof(struct drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->hid = hdev; + hid_set_drvdata(hdev, drvdata); + + init_waitqueue_head(&drvdata->wq); + + mutex_init(&drvdata->mutex); + devm_add_action(&hdev->dev, (void (*)(void *))mutex_destroy, + &drvdata->mutex); + + ret = hid_parse(hdev); + if (ret) + return ret; + + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); + if (ret) + return ret; + + ret = hid_hw_open(hdev); + if (ret) + goto out_hw_stop; + + hid_device_io_start(hdev); + + init_device(drvdata, UPDATE_INTERVAL_DEFAULT_MS); + + drvdata->hwmon = + hwmon_device_register_with_info(&hdev->dev, "nzxtsmart2", drvdata, + &nzxt_smart2_chip_info, NULL); + if (IS_ERR(drvdata->hwmon)) { + ret = PTR_ERR(drvdata->hwmon); + goto out_hw_close; + } + + return 0; + +out_hw_close: + hid_hw_close(hdev); + +out_hw_stop: + hid_hw_stop(hdev); + return ret; +} + +static void nzxt_smart2_hid_remove(struct hid_device *hdev) +{ + struct drvdata *drvdata = hid_get_drvdata(hdev); + + hwmon_device_unregister(drvdata->hwmon); + + hid_hw_close(hdev); + hid_hw_stop(hdev); +} + +static const struct hid_device_id nzxt_smart2_hid_id_table[] = { + { HID_USB_DEVICE(0x1e71, 0x2006) }, /* NZXT Smart Device V2 */ + { HID_USB_DEVICE(0x1e71, 0x200d) }, /* NZXT Smart Device V2 */ + { HID_USB_DEVICE(0x1e71, 0x2009) }, /* NZXT RGB & Fan Controller */ + { HID_USB_DEVICE(0x1e71, 0x200e) }, /* NZXT RGB & Fan Controller */ + { HID_USB_DEVICE(0x1e71, 0x2010) }, /* NZXT RGB & Fan Controller */ + {}, +}; + +static struct hid_driver nzxt_smart2_hid_driver = { + .name = "nzxt-smart2", + .id_table = nzxt_smart2_hid_id_table, + .probe = nzxt_smart2_hid_probe, + .remove = nzxt_smart2_hid_remove, + .raw_event = nzxt_smart2_hid_raw_event, +#ifdef CONFIG_PM + .reset_resume = nzxt_smart2_hid_reset_resume, +#endif +}; + +static int __init nzxt_smart2_init(void) +{ + return hid_register_driver(&nzxt_smart2_hid_driver); +} + +static void __exit nzxt_smart2_exit(void) +{ + hid_unregister_driver(&nzxt_smart2_hid_driver); +} + +MODULE_DEVICE_TABLE(hid, nzxt_smart2_hid_id_table); +MODULE_AUTHOR("Aleksandr Mezin <mezin.alexander@gmail.com>"); +MODULE_DESCRIPTION("Driver for NZXT RGB & Fan Controller/Smart Device V2"); +MODULE_LICENSE("GPL"); + +/* + * With module_init()/module_hid_driver() and the driver built into the kernel: + * + * Driver 'nzxt_smart2' was unable to register with bus_type 'hid' because the + * bus was not initialized. + */ +late_initcall(nzxt_smart2_init); +module_exit(nzxt_smart2_exit); diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index 0cb4a0a6cbc1..f00cd59f1d19 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -674,6 +674,9 @@ static ssize_t occ_show_caps_3(struct device *dev, case 7: val = caps->user_source; break; + case 8: + val = get_unaligned_be16(&caps->soft_min) * 1000000ULL; + break; default: return -EINVAL; } @@ -835,12 +838,13 @@ static int occ_setup_sensor_attrs(struct occ *occ) case 1: num_attrs += (sensors->caps.num_sensors * 7); break; - case 3: - show_caps = occ_show_caps_3; - fallthrough; case 2: num_attrs += (sensors->caps.num_sensors * 8); break; + case 3: + show_caps = occ_show_caps_3; + num_attrs += (sensors->caps.num_sensors * 9); + break; default: sensors->caps.num_sensors = 0; } @@ -1047,6 +1051,15 @@ static int occ_setup_sensor_attrs(struct occ *occ) attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL, 7, 0); attr++; + + if (sensors->caps.version > 2) { + snprintf(attr->name, sizeof(attr->name), + "power%d_cap_min_soft", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_caps, NULL, + 8, 0); + attr++; + } } } diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h index 5020117be740..2dd4a4d240c0 100644 --- a/drivers/hwmon/occ/common.h +++ b/drivers/hwmon/occ/common.h @@ -119,6 +119,8 @@ struct occ { u8 prev_stat; u8 prev_ext_stat; u8 prev_occs_present; + u8 prev_ips_status; + u8 prev_mode; }; int occ_setup(struct occ *occ, const char *name); diff --git a/drivers/hwmon/occ/sysfs.c b/drivers/hwmon/occ/sysfs.c index 03b16abef67f..b2f788a77746 100644 --- a/drivers/hwmon/occ/sysfs.c +++ b/drivers/hwmon/occ/sysfs.c @@ -19,6 +19,8 @@ #define OCC_EXT_STAT_DVFS_POWER BIT(6) #define OCC_EXT_STAT_MEM_THROTTLE BIT(5) #define OCC_EXT_STAT_QUICK_DROP BIT(4) +#define OCC_EXT_STAT_DVFS_VDD BIT(3) +#define OCC_EXT_STAT_GPU_THROTTLE GENMASK(2, 0) static ssize_t occ_sysfs_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -63,6 +65,18 @@ static ssize_t occ_sysfs_show(struct device *dev, else val = 1; break; + case 8: + val = header->ips_status; + break; + case 9: + val = header->mode; + break; + case 10: + val = !!(header->ext_status & OCC_EXT_STAT_DVFS_VDD); + break; + case 11: + val = header->ext_status & OCC_EXT_STAT_GPU_THROTTLE; + break; default: return -EINVAL; } @@ -88,6 +102,10 @@ static SENSOR_DEVICE_ATTR(occ_mem_throttle, 0444, occ_sysfs_show, NULL, 4); static SENSOR_DEVICE_ATTR(occ_quick_pwr_drop, 0444, occ_sysfs_show, NULL, 5); static SENSOR_DEVICE_ATTR(occ_state, 0444, occ_sysfs_show, NULL, 6); static SENSOR_DEVICE_ATTR(occs_present, 0444, occ_sysfs_show, NULL, 7); +static SENSOR_DEVICE_ATTR(occ_ips_status, 0444, occ_sysfs_show, NULL, 8); +static SENSOR_DEVICE_ATTR(occ_mode, 0444, occ_sysfs_show, NULL, 9); +static SENSOR_DEVICE_ATTR(occ_dvfs_vdd, 0444, occ_sysfs_show, NULL, 10); +static SENSOR_DEVICE_ATTR(occ_gpu_throttle, 0444, occ_sysfs_show, NULL, 11); static DEVICE_ATTR_RO(occ_error); static struct attribute *occ_attributes[] = { @@ -99,6 +117,10 @@ static struct attribute *occ_attributes[] = { &sensor_dev_attr_occ_quick_pwr_drop.dev_attr.attr, &sensor_dev_attr_occ_state.dev_attr.attr, &sensor_dev_attr_occs_present.dev_attr.attr, + &sensor_dev_attr_occ_ips_status.dev_attr.attr, + &sensor_dev_attr_occ_mode.dev_attr.attr, + &sensor_dev_attr_occ_dvfs_vdd.dev_attr.attr, + &sensor_dev_attr_occ_gpu_throttle.dev_attr.attr, &dev_attr_occ_error.attr, NULL }; @@ -156,12 +178,34 @@ void occ_sysfs_poll_done(struct occ *occ) sysfs_notify(&occ->bus_dev->kobj, NULL, name); } + if ((header->ext_status & OCC_EXT_STAT_DVFS_VDD) != + (occ->prev_ext_stat & OCC_EXT_STAT_DVFS_VDD)) { + name = sensor_dev_attr_occ_dvfs_vdd.dev_attr.attr.name; + sysfs_notify(&occ->bus_dev->kobj, NULL, name); + } + + if ((header->ext_status & OCC_EXT_STAT_GPU_THROTTLE) != + (occ->prev_ext_stat & OCC_EXT_STAT_GPU_THROTTLE)) { + name = sensor_dev_attr_occ_gpu_throttle.dev_attr.attr.name; + sysfs_notify(&occ->bus_dev->kobj, NULL, name); + } + if ((header->status & OCC_STAT_MASTER) && header->occs_present != occ->prev_occs_present) { name = sensor_dev_attr_occs_present.dev_attr.attr.name; sysfs_notify(&occ->bus_dev->kobj, NULL, name); } + if (header->ips_status != occ->prev_ips_status) { + name = sensor_dev_attr_occ_ips_status.dev_attr.attr.name; + sysfs_notify(&occ->bus_dev->kobj, NULL, name); + } + + if (header->mode != occ->prev_mode) { + name = sensor_dev_attr_occ_mode.dev_attr.attr.name; + sysfs_notify(&occ->bus_dev->kobj, NULL, name); + } + if (occ->error && occ->error != occ->prev_error) { name = dev_attr_occ_error.attr.name; sysfs_notify(&occ->bus_dev->kobj, NULL, name); @@ -174,6 +218,8 @@ done: occ->prev_stat = header->status; occ->prev_ext_stat = header->ext_status; occ->prev_occs_present = header->occs_present; + occ->prev_ips_status = header->ips_status; + occ->prev_mode = header->mode; } int occ_setup_sysfs(struct occ *occ) diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index ffb609cee3a4..a2ea1d5a8765 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -66,6 +66,16 @@ config SENSORS_BPA_RS600 This driver can also be built as a module. If so, the module will be called bpa-rs600. +config SENSORS_DELTA_AHE50DC_FAN + tristate "Delta AHE-50DC fan control module" + help + If you say yes here you get hardware monitoring support for + the integrated fan control module of the Delta AHE-50DC + Open19 power shelf. + + This driver can also be built as a module. If so, the module + will be called delta-ahe50dc-fan. + config SENSORS_FSP_3Y tristate "FSP/3Y-Power power supplies" help @@ -123,14 +133,20 @@ config SENSORS_IR36021 be called ir36021. config SENSORS_IR38064 - tristate "Infineon IR38064" + tristate "Infineon IR38064 and compatibles" help If you say yes here you get hardware monitoring support for Infineon - IR38064. + IR38060, IR38064, IR38164 and IR38263. This driver can also be built as a module. If so, the module will be called ir38064. +config SENSORS_IR38064_REGULATOR + bool "Regulator support for IR38064 and compatibles" + depends on SENSORS_IR38064 && REGULATOR + help + Uses the IR38064 or compatible as regulator. + config SENSORS_IRPS5401 tristate "Infineon IRPS5401" help @@ -158,6 +174,13 @@ config SENSORS_LM25066 This driver can also be built as a module. If so, the module will be called lm25066. +config SENSORS_LM25066_REGULATOR + bool "Regulator support for LM25066 and compatibles" + depends on SENSORS_LM25066 && REGULATOR + help + If you say yes here you get regulator support for National + Semiconductor LM25066, LM5064, and LM5066. + config SENSORS_LTC2978 tristate "Linear Technologies LTC2978 and compatibles" help @@ -173,8 +196,8 @@ config SENSORS_LTC2978_REGULATOR depends on SENSORS_LTC2978 && REGULATOR help If you say yes here you get regulator support for Linear Technology - LTC3880, LTC3883, LTC3884, LTC3886, LTC3887, LTC3889, LTC7880, - LTM4644, LTM4675, LTM4676, LTM4677, LTM4678, LTM4680, LTM4686, + LTC3880, LTC3883, LTC3884, LTC3886, LTC3887, LTC3889, LTC7880, + LTM4644, LTM4675, LTM4676, LTM4677, LTM4678, LTM4680, LTM4686, and LTM4700. config SENSORS_LTC3815 @@ -276,6 +299,15 @@ config SENSORS_MP2975 This driver can also be built as a module. If so, the module will be called mp2975. +config SENSORS_MP5023 + tristate "MPS MP5023" + help + If you say yes here you get hardware monitoring support for MPS + MP5023. + + This driver can also be built as a module. If so, the module will + be called mp5023. + config SENSORS_PIM4328 tristate "Flex PIM4328 and compatibles" help @@ -285,6 +317,22 @@ config SENSORS_PIM4328 This driver can also be built as a module. If so, the module will be called pim4328. +config SENSORS_PLI1209BC + tristate "Vicor PLI1209BC" + help + If you say yes here you get hardware monitoring support for Vicor + PLI1209BC Digital Supervisor. + + This driver can also be built as a module. If so, the module will + be called pli1209bc. + +config SENSORS_PLI1209BC_REGULATOR + bool "Regulator support for PLI1209BC" + depends on SENSORS_PLI1209BC && REGULATOR + help + If you say yes here you get regulator support for Vicor PLI1209BC + Digital Supervisor. + config SENSORS_PM6764TR tristate "ST PM6764TR" help @@ -369,6 +417,12 @@ config SENSORS_XDPE122 This driver can also be built as a module. If so, the module will be called xdpe12284. +config SENSORS_XDPE122_REGULATOR + bool "Regulator support for XDPE122 and compatibles" + depends on SENSORS_XDPE122 && REGULATOR + help + Uses the xdpe12284 or compatible as regulator. + config SENSORS_ZL6100 tristate "Intersil ZL6100 and compatibles" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index 0ed4d596a948..a4a96ac71de7 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_SENSORS_ADM1266) += adm1266.o obj-$(CONFIG_SENSORS_ADM1275) += adm1275.o obj-$(CONFIG_SENSORS_BEL_PFE) += bel-pfe.o obj-$(CONFIG_SENSORS_BPA_RS600) += bpa-rs600.o +obj-$(CONFIG_SENSORS_DELTA_AHE50DC_FAN) += delta-ahe50dc-fan.o obj-$(CONFIG_SENSORS_FSP_3Y) += fsp-3y.o obj-$(CONFIG_SENSORS_IBM_CFFPS) += ibm-cffps.o obj-$(CONFIG_SENSORS_DPS920AB) += dps920ab.o @@ -31,6 +32,8 @@ obj-$(CONFIG_SENSORS_MAX34440) += max34440.o obj-$(CONFIG_SENSORS_MAX8688) += max8688.o obj-$(CONFIG_SENSORS_MP2888) += mp2888.o obj-$(CONFIG_SENSORS_MP2975) += mp2975.o +obj-$(CONFIG_SENSORS_MP5023) += mp5023.o +obj-$(CONFIG_SENSORS_PLI1209BC) += pli1209bc.o obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o obj-$(CONFIG_SENSORS_PXE1610) += pxe1610.o obj-$(CONFIG_SENSORS_Q54SJ108A2) += q54sj108a2.o diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c index d311e0557401..3b07bfb43e93 100644 --- a/drivers/hwmon/pmbus/adm1275.c +++ b/drivers/hwmon/pmbus/adm1275.c @@ -475,6 +475,7 @@ static int adm1275_probe(struct i2c_client *client) int vindex = -1, voindex = -1, cindex = -1, pindex = -1; int tindex = -1; u32 shunt; + u32 avg; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BYTE_DATA @@ -687,7 +688,7 @@ static int adm1275_probe(struct i2c_client *client) if ((config & (ADM1278_VOUT_EN | ADM1278_TEMP1_EN)) != (ADM1278_VOUT_EN | ADM1278_TEMP1_EN)) { config |= ADM1278_VOUT_EN | ADM1278_TEMP1_EN; - ret = i2c_smbus_write_byte_data(client, + ret = i2c_smbus_write_word_data(client, ADM1275_PMON_CONFIG, config); if (ret < 0) { @@ -756,6 +757,43 @@ static int adm1275_probe(struct i2c_client *client) return -ENODEV; } + if (data->have_power_sampling && + of_property_read_u32(client->dev.of_node, + "adi,power-sample-average", &avg) == 0) { + if (!avg || avg > ADM1275_SAMPLES_AVG_MAX || + BIT(__fls(avg)) != avg) { + dev_err(&client->dev, + "Invalid number of power samples"); + return -EINVAL; + } + ret = adm1275_write_pmon_config(data, client, true, + ilog2(avg)); + if (ret < 0) { + dev_err(&client->dev, + "Setting power sample averaging failed with error %d", + ret); + return ret; + } + } + + if (of_property_read_u32(client->dev.of_node, + "adi,volt-curr-sample-average", &avg) == 0) { + if (!avg || avg > ADM1275_SAMPLES_AVG_MAX || + BIT(__fls(avg)) != avg) { + dev_err(&client->dev, + "Invalid number of voltage/current samples"); + return -EINVAL; + } + ret = adm1275_write_pmon_config(data, client, false, + ilog2(avg)); + if (ret < 0) { + dev_err(&client->dev, + "Setting voltage and current sample averaging failed with error %d", + ret); + return ret; + } + } + if (voindex < 0) voindex = vindex; if (vindex >= 0) { diff --git a/drivers/hwmon/pmbus/delta-ahe50dc-fan.c b/drivers/hwmon/pmbus/delta-ahe50dc-fan.c new file mode 100644 index 000000000000..40dffd9c4cbf --- /dev/null +++ b/drivers/hwmon/pmbus/delta-ahe50dc-fan.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Delta AHE-50DC power shelf fan control module driver + * + * Copyright 2021 Zev Weiss <zev@bewilderbeest.net> + */ + +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pmbus.h> + +#include "pmbus.h" + +#define AHE50DC_PMBUS_READ_TEMP4 0xd0 + +static int ahe50dc_fan_read_word_data(struct i2c_client *client, int page, int phase, int reg) +{ + /* temp1 in (virtual) page 1 is remapped to mfr-specific temp4 */ + if (page == 1) { + if (reg == PMBUS_READ_TEMPERATURE_1) + return i2c_smbus_read_word_data(client, AHE50DC_PMBUS_READ_TEMP4); + return -EOPNOTSUPP; + } + + /* + * There's a fairly limited set of commands this device actually + * supports, so here we block attempts to read anything else (which + * return 0xffff and would cause confusion elsewhere). + */ + switch (reg) { + case PMBUS_STATUS_WORD: + case PMBUS_FAN_COMMAND_1: + case PMBUS_FAN_COMMAND_2: + case PMBUS_FAN_COMMAND_3: + case PMBUS_FAN_COMMAND_4: + case PMBUS_STATUS_FAN_12: + case PMBUS_STATUS_FAN_34: + case PMBUS_READ_VIN: + case PMBUS_READ_TEMPERATURE_1: + case PMBUS_READ_TEMPERATURE_2: + case PMBUS_READ_TEMPERATURE_3: + case PMBUS_READ_FAN_SPEED_1: + case PMBUS_READ_FAN_SPEED_2: + case PMBUS_READ_FAN_SPEED_3: + case PMBUS_READ_FAN_SPEED_4: + return -ENODATA; + default: + return -EOPNOTSUPP; + } +} + +static struct pmbus_driver_info ahe50dc_fan_info = { + .pages = 2, + .format[PSC_FAN] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_VOLTAGE_IN] = direct, + .m[PSC_FAN] = 1, + .b[PSC_FAN] = 0, + .R[PSC_FAN] = 0, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 1, + .m[PSC_VOLTAGE_IN] = 1, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 3, + .func[0] = PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 | + PMBUS_HAVE_VIN | PMBUS_HAVE_FAN12 | PMBUS_HAVE_FAN34 | + PMBUS_HAVE_STATUS_FAN12 | PMBUS_HAVE_STATUS_FAN34 | PMBUS_PAGE_VIRTUAL, + .func[1] = PMBUS_HAVE_TEMP | PMBUS_PAGE_VIRTUAL, + .read_word_data = ahe50dc_fan_read_word_data, +}; + +/* + * CAPABILITY returns 0xff, which appears to be this device's way indicating + * it doesn't support something (and if we enable I2C_CLIENT_PEC on seeing bit + * 7 being set it generates bad PECs, so let's not go there). + */ +static struct pmbus_platform_data ahe50dc_fan_data = { + .flags = PMBUS_NO_CAPABILITY, +}; + +static int ahe50dc_fan_probe(struct i2c_client *client) +{ + client->dev.platform_data = &ahe50dc_fan_data; + return pmbus_do_probe(client, &ahe50dc_fan_info); +} + +static const struct i2c_device_id ahe50dc_fan_id[] = { + { "ahe50dc_fan" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ahe50dc_fan_id); + +static const struct of_device_id __maybe_unused ahe50dc_fan_of_match[] = { + { .compatible = "delta,ahe50dc-fan" }, + { } +}; +MODULE_DEVICE_TABLE(of, ahe50dc_fan_of_match); + +static struct i2c_driver ahe50dc_fan_driver = { + .driver = { + .name = "ahe50dc_fan", + .of_match_table = of_match_ptr(ahe50dc_fan_of_match), + }, + .probe_new = ahe50dc_fan_probe, + .id_table = ahe50dc_fan_id, +}; +module_i2c_driver(ahe50dc_fan_driver); + +MODULE_AUTHOR("Zev Weiss <zev@bewilderbeest.net>"); +MODULE_DESCRIPTION("Driver for Delta AHE-50DC power shelf fan control module"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/ir38064.c b/drivers/hwmon/pmbus/ir38064.c index 1fb7f1248639..09276e397194 100644 --- a/drivers/hwmon/pmbus/ir38064.c +++ b/drivers/hwmon/pmbus/ir38064.c @@ -16,8 +16,16 @@ #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> +#include <linux/of_device.h> +#include <linux/regulator/driver.h> #include "pmbus.h" +#if IS_ENABLED(CONFIG_SENSORS_IR38064_REGULATOR) +static const struct regulator_desc ir38064_reg_desc[] = { + PMBUS_REGULATOR("vout", 0), +}; +#endif /* CONFIG_SENSORS_IR38064_REGULATOR */ + static struct pmbus_driver_info ir38064_info = { .pages = 1, .format[PSC_VOLTAGE_IN] = linear, @@ -33,6 +41,10 @@ static struct pmbus_driver_info ir38064_info = { | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT, +#if IS_ENABLED(CONFIG_SENSORS_IR38064_REGULATOR) + .num_regulators = 1, + .reg_desc = ir38064_reg_desc, +#endif }; static int ir38064_probe(struct i2c_client *client) @@ -41,16 +53,30 @@ static int ir38064_probe(struct i2c_client *client) } static const struct i2c_device_id ir38064_id[] = { + {"ir38060", 0}, {"ir38064", 0}, + {"ir38164", 0}, + {"ir38263", 0}, {} }; MODULE_DEVICE_TABLE(i2c, ir38064_id); +static const struct of_device_id __maybe_unused ir38064_of_match[] = { + { .compatible = "infineon,ir38060" }, + { .compatible = "infineon,ir38064" }, + { .compatible = "infineon,ir38164" }, + { .compatible = "infineon,ir38263" }, + {} +}; + +MODULE_DEVICE_TABLE(of, ir38064_of_match); + /* This is the driver that will be inserted */ static struct i2c_driver ir38064_driver = { .driver = { .name = "ir38064", + .of_match_table = of_match_ptr(ir38064_of_match), }, .probe_new = ir38064_probe, .id_table = ir38064_id, @@ -59,6 +85,6 @@ static struct i2c_driver ir38064_driver = { module_i2c_driver(ir38064_driver); MODULE_AUTHOR("Maxim Sloyko <maxims@google.com>"); -MODULE_DESCRIPTION("PMBus driver for Infineon IR38064"); +MODULE_DESCRIPTION("PMBus driver for Infineon IR38064 and compatible chips"); MODULE_LICENSE("GPL"); MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c index 8402b41520eb..09792cd03d9f 100644 --- a/drivers/hwmon/pmbus/lm25066.c +++ b/drivers/hwmon/pmbus/lm25066.c @@ -435,6 +435,12 @@ static int lm25066_write_word_data(struct i2c_client *client, int page, int reg, return ret; } +#if IS_ENABLED(CONFIG_SENSORS_LM25066_REGULATOR) +static const struct regulator_desc lm25066_reg_desc[] = { + PMBUS_REGULATOR("vout", 0), +}; +#endif + static const struct i2c_device_id lm25066_id[] = { {"lm25056", lm25056}, {"lm25066", lm25066}, @@ -545,6 +551,14 @@ static int lm25066_probe(struct i2c_client *client) info->m[PSC_CURRENT_IN] = info->m[PSC_CURRENT_IN] * shunt / 1000; info->m[PSC_POWER] = info->m[PSC_POWER] * shunt / 1000; +#if IS_ENABLED(CONFIG_SENSORS_LM25066_REGULATOR) + /* LM25056 doesn't support OPERATION */ + if (data->id != lm25056) { + info->num_regulators = ARRAY_SIZE(lm25066_reg_desc); + info->reg_desc = lm25066_reg_desc; + } +#endif + return pmbus_do_probe(client, info); } diff --git a/drivers/hwmon/pmbus/mp5023.c b/drivers/hwmon/pmbus/mp5023.c new file mode 100644 index 000000000000..791a06c3c54a --- /dev/null +++ b/drivers/hwmon/pmbus/mp5023.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for MPS MP5023 Hot-Swap Controller + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include "pmbus.h" + +static struct pmbus_driver_info mp5023_info = { + .pages = 1, + + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_POWER] = direct, + .format[PSC_TEMPERATURE] = direct, + + .m[PSC_VOLTAGE_IN] = 32, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 0, + .m[PSC_VOLTAGE_OUT] = 32, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 0, + .m[PSC_CURRENT_OUT] = 16, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 0, + .m[PSC_POWER] = 1, + .b[PSC_POWER] = 0, + .R[PSC_POWER] = 0, + .m[PSC_TEMPERATURE] = 2, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 0, + + .func[0] = + PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_PIN | + PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT | + PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP, +}; + +static int mp5023_probe(struct i2c_client *client) +{ + return pmbus_do_probe(client, &mp5023_info); +} + +static const struct of_device_id __maybe_unused mp5023_of_match[] = { + { .compatible = "mps,mp5023", }, + {} +}; + +MODULE_DEVICE_TABLE(of, mp5023_of_match); + +static struct i2c_driver mp5023_driver = { + .driver = { + .name = "mp5023", + .of_match_table = of_match_ptr(mp5023_of_match), + }, + .probe_new = mp5023_probe, +}; + +module_i2c_driver(mp5023_driver); + +MODULE_AUTHOR("Howard Chiu <howard.chiu@quantatw.com>"); +MODULE_DESCRIPTION("PMBus driver for MPS MP5023 HSC"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/pli1209bc.c b/drivers/hwmon/pmbus/pli1209bc.c new file mode 100644 index 000000000000..05b4ee35ba27 --- /dev/null +++ b/drivers/hwmon/pmbus/pli1209bc.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Hardware monitoring driver for Vicor PLI1209BC Digital Supervisor + * + * Copyright (c) 2022 9elements GmbH + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pmbus.h> +#include <linux/regulator/driver.h> +#include "pmbus.h" + +/* + * The capability command is only supported at page 0. Probing the device while + * the page register is set to 1 will falsely enable PEC support. Disable + * capability probing accordingly, since the PLI1209BC does not have any + * additional capabilities. + */ +static struct pmbus_platform_data pli1209bc_plat_data = { + .flags = PMBUS_NO_CAPABILITY, +}; + +static int pli1209bc_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + int data; + + switch (reg) { + /* PMBUS_READ_POUT uses a direct format with R=0 */ + case PMBUS_READ_POUT: + data = pmbus_read_word_data(client, page, phase, reg); + if (data < 0) + return data; + data = sign_extend32(data, 15) * 10; + return clamp_val(data, -32768, 32767) & 0xffff; + /* + * PMBUS_READ_VOUT and PMBUS_READ_TEMPERATURE_1 return invalid data + * when the BCM is turned off. Since it is not possible to return + * ENODATA error, return zero instead. + */ + case PMBUS_READ_VOUT: + case PMBUS_READ_TEMPERATURE_1: + data = pmbus_read_word_data(client, page, phase, + PMBUS_STATUS_WORD); + if (data < 0) + return data; + if (data & PB_STATUS_POWER_GOOD_N) + return 0; + return pmbus_read_word_data(client, page, phase, reg); + default: + return -ENODATA; + } +} + +#if IS_ENABLED(CONFIG_SENSORS_PLI1209BC_REGULATOR) +static const struct regulator_desc pli1209bc_reg_desc = { + .name = "vout2", + .id = 1, + .of_match = of_match_ptr("vout2"), + .regulators_node = of_match_ptr("regulators"), + .ops = &pmbus_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, +}; +#endif + +static struct pmbus_driver_info pli1209bc_info = { + .pages = 2, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_CURRENT_IN] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_POWER] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_IN] = 1, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 1, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 1, + .m[PSC_CURRENT_IN] = 1, + .b[PSC_CURRENT_IN] = 0, + .R[PSC_CURRENT_IN] = 3, + .m[PSC_CURRENT_OUT] = 1, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 2, + .m[PSC_POWER] = 1, + .b[PSC_POWER] = 0, + .R[PSC_POWER] = 1, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 0, + /* + * Page 0 sums up all attributes except voltage readings. + * The pli1209 digital supervisor only contains a single BCM, making + * page 0 redundant. + */ + .func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT + | PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT + | PMBUS_HAVE_PIN | PMBUS_HAVE_POUT + | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP + | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_INPUT, + .read_word_data = pli1209bc_read_word_data, +#if IS_ENABLED(CONFIG_SENSORS_PLI1209BC_REGULATOR) + .num_regulators = 1, + .reg_desc = &pli1209bc_reg_desc, +#endif +}; + +static int pli1209bc_probe(struct i2c_client *client) +{ + client->dev.platform_data = &pli1209bc_plat_data; + return pmbus_do_probe(client, &pli1209bc_info); +} + +static const struct i2c_device_id pli1209bc_id[] = { + {"pli1209bc", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, pli1209bc_id); + +#ifdef CONFIG_OF +static const struct of_device_id pli1209bc_of_match[] = { + { .compatible = "vicor,pli1209bc" }, + { }, +}; +MODULE_DEVICE_TABLE(of, pli1209bc_of_match); +#endif + +static struct i2c_driver pli1209bc_driver = { + .driver = { + .name = "pli1209bc", + .of_match_table = of_match_ptr(pli1209bc_of_match), + }, + .probe_new = pli1209bc_probe, + .id_table = pli1209bc_id, +}; + +module_i2c_driver(pli1209bc_driver); + +MODULE_AUTHOR("Marcello Sylvester Bauer <sylv@sylv.io>"); +MODULE_DESCRIPTION("PMBus driver for Vicor PLI1209BC"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index e0aa8aa46d8c..e74b6ef070f3 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -319,6 +319,7 @@ enum pmbus_fan_mode { percent = 0, rpm }; /* * STATUS_VOUT, STATUS_INPUT */ +#define PB_VOLTAGE_VIN_OFF BIT(3) #define PB_VOLTAGE_UV_FAULT BIT(4) #define PB_VOLTAGE_UV_WARNING BIT(5) #define PB_VOLTAGE_OV_WARNING BIT(6) @@ -464,6 +465,7 @@ extern const struct regulator_ops pmbus_regulator_ops; #define PMBUS_REGULATOR(_name, _id) \ [_id] = { \ .name = (_name # _id), \ + .supply_name = "vin", \ .id = (_id), \ .of_match = of_match_ptr(_name # _id), \ .regulators_node = of_match_ptr("regulators"), \ diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 776ee2237be2..b2618b1d529e 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -911,6 +911,11 @@ static int pmbus_get_boolean(struct i2c_client *client, struct pmbus_boolean *b, pmbus_update_sensor_data(client, s2); regval = status & mask; + if (regval) { + ret = pmbus_write_byte_data(client, page, reg, regval); + if (ret) + goto unlock; + } if (s1 && s2) { s64 v1, v2; @@ -1368,7 +1373,7 @@ static const struct pmbus_limit_attr vin_limit_attrs[] = { .reg = PMBUS_VIN_UV_FAULT_LIMIT, .attr = "lcrit", .alarm = "lcrit_alarm", - .sbit = PB_VOLTAGE_UV_FAULT, + .sbit = PB_VOLTAGE_UV_FAULT | PB_VOLTAGE_VIN_OFF, }, { .reg = PMBUS_VIN_OV_WARN_LIMIT, .attr = "max", @@ -2386,10 +2391,14 @@ static int pmbus_regulator_is_enabled(struct regulator_dev *rdev) { struct device *dev = rdev_get_dev(rdev); struct i2c_client *client = to_i2c_client(dev->parent); + struct pmbus_data *data = i2c_get_clientdata(client); u8 page = rdev_get_id(rdev); int ret; + mutex_lock(&data->update_lock); ret = pmbus_read_byte_data(client, page, PMBUS_OPERATION); + mutex_unlock(&data->update_lock); + if (ret < 0) return ret; @@ -2400,11 +2409,17 @@ static int _pmbus_regulator_on_off(struct regulator_dev *rdev, bool enable) { struct device *dev = rdev_get_dev(rdev); struct i2c_client *client = to_i2c_client(dev->parent); + struct pmbus_data *data = i2c_get_clientdata(client); u8 page = rdev_get_id(rdev); + int ret; - return pmbus_update_byte_data(client, page, PMBUS_OPERATION, - PB_OPERATION_CONTROL_ON, - enable ? PB_OPERATION_CONTROL_ON : 0); + mutex_lock(&data->update_lock); + ret = pmbus_update_byte_data(client, page, PMBUS_OPERATION, + PB_OPERATION_CONTROL_ON, + enable ? PB_OPERATION_CONTROL_ON : 0); + mutex_unlock(&data->update_lock); + + return ret; } static int pmbus_regulator_enable(struct regulator_dev *rdev) @@ -2417,10 +2432,124 @@ static int pmbus_regulator_disable(struct regulator_dev *rdev) return _pmbus_regulator_on_off(rdev, 0); } +/* A PMBus status flag and the corresponding REGULATOR_ERROR_* flag */ +struct pmbus_regulator_status_assoc { + int pflag, rflag; +}; + +/* PMBus->regulator bit mappings for a PMBus status register */ +struct pmbus_regulator_status_category { + int func; + int reg; + const struct pmbus_regulator_status_assoc *bits; /* zero-terminated */ +}; + +static const struct pmbus_regulator_status_category pmbus_regulator_flag_map[] = { + { + .func = PMBUS_HAVE_STATUS_VOUT, + .reg = PMBUS_STATUS_VOUT, + .bits = (const struct pmbus_regulator_status_assoc[]) { + { PB_VOLTAGE_UV_WARNING, REGULATOR_ERROR_UNDER_VOLTAGE_WARN }, + { PB_VOLTAGE_UV_FAULT, REGULATOR_ERROR_UNDER_VOLTAGE }, + { PB_VOLTAGE_OV_WARNING, REGULATOR_ERROR_OVER_VOLTAGE_WARN }, + { PB_VOLTAGE_OV_FAULT, REGULATOR_ERROR_REGULATION_OUT }, + { }, + }, + }, { + .func = PMBUS_HAVE_STATUS_IOUT, + .reg = PMBUS_STATUS_IOUT, + .bits = (const struct pmbus_regulator_status_assoc[]) { + { PB_IOUT_OC_WARNING, REGULATOR_ERROR_OVER_CURRENT_WARN }, + { PB_IOUT_OC_FAULT, REGULATOR_ERROR_OVER_CURRENT }, + { PB_IOUT_OC_LV_FAULT, REGULATOR_ERROR_OVER_CURRENT }, + { }, + }, + }, { + .func = PMBUS_HAVE_STATUS_TEMP, + .reg = PMBUS_STATUS_TEMPERATURE, + .bits = (const struct pmbus_regulator_status_assoc[]) { + { PB_TEMP_OT_WARNING, REGULATOR_ERROR_OVER_TEMP_WARN }, + { PB_TEMP_OT_FAULT, REGULATOR_ERROR_OVER_TEMP }, + { }, + }, + }, +}; + +static int pmbus_regulator_get_error_flags(struct regulator_dev *rdev, unsigned int *flags) +{ + int i, status; + const struct pmbus_regulator_status_category *cat; + const struct pmbus_regulator_status_assoc *bit; + struct device *dev = rdev_get_dev(rdev); + struct i2c_client *client = to_i2c_client(dev->parent); + struct pmbus_data *data = i2c_get_clientdata(client); + u8 page = rdev_get_id(rdev); + int func = data->info->func[page]; + + *flags = 0; + + mutex_lock(&data->update_lock); + + for (i = 0; i < ARRAY_SIZE(pmbus_regulator_flag_map); i++) { + cat = &pmbus_regulator_flag_map[i]; + if (!(func & cat->func)) + continue; + + status = pmbus_read_byte_data(client, page, cat->reg); + if (status < 0) { + mutex_unlock(&data->update_lock); + return status; + } + + for (bit = cat->bits; bit->pflag; bit++) { + if (status & bit->pflag) + *flags |= bit->rflag; + } + } + + /* + * Map what bits of STATUS_{WORD,BYTE} we can to REGULATOR_ERROR_* + * bits. Some of the other bits are tempting (especially for cases + * where we don't have the relevant PMBUS_HAVE_STATUS_* + * functionality), but there's an unfortunate ambiguity in that + * they're defined as indicating a fault *or* a warning, so we can't + * easily determine whether to report REGULATOR_ERROR_<foo> or + * REGULATOR_ERROR_<foo>_WARN. + */ + status = pmbus_get_status(client, page, PMBUS_STATUS_WORD); + mutex_unlock(&data->update_lock); + if (status < 0) + return status; + + if (pmbus_regulator_is_enabled(rdev) && (status & PB_STATUS_OFF)) + *flags |= REGULATOR_ERROR_FAIL; + + /* + * Unlike most other status bits, PB_STATUS_{IOUT_OC,VOUT_OV} are + * defined strictly as fault indicators (not warnings). + */ + if (status & PB_STATUS_IOUT_OC) + *flags |= REGULATOR_ERROR_OVER_CURRENT; + if (status & PB_STATUS_VOUT_OV) + *flags |= REGULATOR_ERROR_REGULATION_OUT; + + /* + * If we haven't discovered any thermal faults or warnings via + * PMBUS_STATUS_TEMPERATURE, map PB_STATUS_TEMPERATURE to a warning as + * a (conservative) best-effort interpretation. + */ + if (!(*flags & (REGULATOR_ERROR_OVER_TEMP | REGULATOR_ERROR_OVER_TEMP_WARN)) && + (status & PB_STATUS_TEMPERATURE)) + *flags |= REGULATOR_ERROR_OVER_TEMP_WARN; + + return 0; +} + const struct regulator_ops pmbus_regulator_ops = { .enable = pmbus_regulator_enable, .disable = pmbus_regulator_disable, .is_enabled = pmbus_regulator_is_enabled, + .get_error_flags = pmbus_regulator_get_error_flags, }; EXPORT_SYMBOL_NS_GPL(pmbus_regulator_ops, PMBUS); diff --git a/drivers/hwmon/pmbus/xdpe12284.c b/drivers/hwmon/pmbus/xdpe12284.c index b07da06a40c9..18fffc5d749b 100644 --- a/drivers/hwmon/pmbus/xdpe12284.c +++ b/drivers/hwmon/pmbus/xdpe12284.c @@ -10,6 +10,8 @@ #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> +#include <linux/regulator/driver.h> + #include "pmbus.h" #define XDPE122_PROT_VR12_5MV 0x01 /* VR12.0 mode, 5-mV DAC */ @@ -76,7 +78,22 @@ static int xdpe122_identify(struct i2c_client *client, struct pmbus_driver_info *info) { u8 vout_params; - int i, ret; + int i, ret, vout_mode; + + vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); + if (vout_mode >= 0 && vout_mode != 0xff) { + switch (vout_mode >> 5) { + case 0: + info->format[PSC_VOLTAGE_OUT] = linear; + return 0; + case 1: + info->format[PSC_VOLTAGE_OUT] = vid; + info->read_word_data = xdpe122_read_word_data; + break; + default: + return -ENODEV; + } + } for (i = 0; i < XDPE122_PAGE_NUM; i++) { /* Read the register with VOUT scaling value.*/ @@ -107,10 +124,14 @@ static int xdpe122_identify(struct i2c_client *client, return 0; } +static const struct regulator_desc xdpe122_reg_desc[] = { + PMBUS_REGULATOR("vout", 0), + PMBUS_REGULATOR("vout", 1), +}; + static struct pmbus_driver_info xdpe122_info = { .pages = XDPE122_PAGE_NUM, .format[PSC_VOLTAGE_IN] = linear, - .format[PSC_VOLTAGE_OUT] = vid, .format[PSC_TEMPERATURE] = linear, .format[PSC_CURRENT_IN] = linear, .format[PSC_CURRENT_OUT] = linear, @@ -124,7 +145,10 @@ static struct pmbus_driver_info xdpe122_info = { PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT, .identify = xdpe122_identify, - .read_word_data = xdpe122_read_word_data, +#if IS_ENABLED(CONFIG_SENSORS_XDPE122_REGULATOR) + .num_regulators = 2, + .reg_desc = xdpe122_reg_desc, +#endif }; static int xdpe122_probe(struct i2c_client *client) @@ -140,6 +164,7 @@ static int xdpe122_probe(struct i2c_client *client) } static const struct i2c_device_id xdpe122_id[] = { + {"xdpe11280", 0}, {"xdpe12254", 0}, {"xdpe12284", 0}, {} @@ -148,6 +173,7 @@ static const struct i2c_device_id xdpe122_id[] = { MODULE_DEVICE_TABLE(i2c, xdpe122_id); static const struct of_device_id __maybe_unused xdpe122_of_match[] = { + {.compatible = "infineon,xdpe11280"}, {.compatible = "infineon,xdpe12254"}, {.compatible = "infineon,xdpe12284"}, {} diff --git a/drivers/hwmon/powr1220.c b/drivers/hwmon/powr1220.c index 9e086338dcba..f77dc6db31ac 100644 --- a/drivers/hwmon/powr1220.c +++ b/drivers/hwmon/powr1220.c @@ -22,6 +22,8 @@ #define ADC_STEP_MV 2 #define ADC_MAX_LOW_MEASUREMENT_MV 2000 +enum powr1xxx_chips { powr1014, powr1220 }; + enum powr1220_regs { VMON_STATUS0, VMON_STATUS1, @@ -74,6 +76,7 @@ enum powr1220_adc_values { struct powr1220_data { struct i2c_client *client; struct mutex update_lock; + u8 max_channels; bool adc_valid[MAX_POWR1220_ADC_VALUES]; /* the next value is in jiffies */ unsigned long adc_last_updated[MAX_POWR1220_ADC_VALUES]; @@ -111,7 +114,7 @@ static int powr1220_read_adc(struct device *dev, int ch_num) mutex_lock(&data->update_lock); if (time_after(jiffies, data->adc_last_updated[ch_num] + HZ) || - !data->adc_valid[ch_num]) { + !data->adc_valid[ch_num]) { /* * figure out if we need to use the attenuator for * high inputs or inputs that we don't yet have a measurement @@ -119,12 +122,12 @@ static int powr1220_read_adc(struct device *dev, int ch_num) * max reading. */ if (data->adc_maxes[ch_num] > ADC_MAX_LOW_MEASUREMENT_MV || - data->adc_maxes[ch_num] == 0) + data->adc_maxes[ch_num] == 0) adc_range = 1 << 4; /* set the attenuator and mux */ result = i2c_smbus_write_byte_data(data->client, ADC_MUX, - adc_range | ch_num); + adc_range | ch_num); if (result) goto exit; @@ -167,135 +170,116 @@ exit: return result; } -/* Shows the voltage associated with the specified ADC channel */ -static ssize_t powr1220_voltage_show(struct device *dev, - struct device_attribute *dev_attr, - char *buf) +static umode_t +powr1220_is_visible(const void *data, enum hwmon_sensor_types type, u32 + attr, int channel) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); - int adc_val = powr1220_read_adc(dev, attr->index); - - if (adc_val < 0) - return adc_val; + struct powr1220_data *chip_data = (struct powr1220_data *)data; + + if (channel >= chip_data->max_channels) + return 0; + + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_input: + case hwmon_in_highest: + case hwmon_in_label: + return 0444; + default: + break; + } + break; + default: + break; + } - return sprintf(buf, "%d\n", adc_val); + return 0; } -/* Shows the maximum setting associated with the specified ADC channel */ -static ssize_t powr1220_max_show(struct device *dev, - struct device_attribute *dev_attr, char *buf) +static int +powr1220_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); - struct powr1220_data *data = dev_get_drvdata(dev); + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_label: + *str = input_names[channel]; + return 0; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; + } - return sprintf(buf, "%d\n", data->adc_maxes[attr->index]); + return -EOPNOTSUPP; } -/* Shows the label associated with the specified ADC channel */ -static ssize_t powr1220_label_show(struct device *dev, - struct device_attribute *dev_attr, - char *buf) +static int +powr1220_read(struct device *dev, enum hwmon_sensor_types type, u32 + attr, int channel, long *val) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + struct powr1220_data *data = dev_get_drvdata(dev); + int ret; + + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_input: + ret = powr1220_read_adc(dev, channel); + if (ret < 0) + return ret; + *val = ret; + break; + case hwmon_in_highest: + *val = data->adc_maxes[channel]; + break; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; +} - return sprintf(buf, "%s\n", input_names[attr->index]); + return 0; } -static SENSOR_DEVICE_ATTR_RO(in0_input, powr1220_voltage, VMON1); -static SENSOR_DEVICE_ATTR_RO(in1_input, powr1220_voltage, VMON2); -static SENSOR_DEVICE_ATTR_RO(in2_input, powr1220_voltage, VMON3); -static SENSOR_DEVICE_ATTR_RO(in3_input, powr1220_voltage, VMON4); -static SENSOR_DEVICE_ATTR_RO(in4_input, powr1220_voltage, VMON5); -static SENSOR_DEVICE_ATTR_RO(in5_input, powr1220_voltage, VMON6); -static SENSOR_DEVICE_ATTR_RO(in6_input, powr1220_voltage, VMON7); -static SENSOR_DEVICE_ATTR_RO(in7_input, powr1220_voltage, VMON8); -static SENSOR_DEVICE_ATTR_RO(in8_input, powr1220_voltage, VMON9); -static SENSOR_DEVICE_ATTR_RO(in9_input, powr1220_voltage, VMON10); -static SENSOR_DEVICE_ATTR_RO(in10_input, powr1220_voltage, VMON11); -static SENSOR_DEVICE_ATTR_RO(in11_input, powr1220_voltage, VMON12); -static SENSOR_DEVICE_ATTR_RO(in12_input, powr1220_voltage, VCCA); -static SENSOR_DEVICE_ATTR_RO(in13_input, powr1220_voltage, VCCINP); - -static SENSOR_DEVICE_ATTR_RO(in0_highest, powr1220_max, VMON1); -static SENSOR_DEVICE_ATTR_RO(in1_highest, powr1220_max, VMON2); -static SENSOR_DEVICE_ATTR_RO(in2_highest, powr1220_max, VMON3); -static SENSOR_DEVICE_ATTR_RO(in3_highest, powr1220_max, VMON4); -static SENSOR_DEVICE_ATTR_RO(in4_highest, powr1220_max, VMON5); -static SENSOR_DEVICE_ATTR_RO(in5_highest, powr1220_max, VMON6); -static SENSOR_DEVICE_ATTR_RO(in6_highest, powr1220_max, VMON7); -static SENSOR_DEVICE_ATTR_RO(in7_highest, powr1220_max, VMON8); -static SENSOR_DEVICE_ATTR_RO(in8_highest, powr1220_max, VMON9); -static SENSOR_DEVICE_ATTR_RO(in9_highest, powr1220_max, VMON10); -static SENSOR_DEVICE_ATTR_RO(in10_highest, powr1220_max, VMON11); -static SENSOR_DEVICE_ATTR_RO(in11_highest, powr1220_max, VMON12); -static SENSOR_DEVICE_ATTR_RO(in12_highest, powr1220_max, VCCA); -static SENSOR_DEVICE_ATTR_RO(in13_highest, powr1220_max, VCCINP); - -static SENSOR_DEVICE_ATTR_RO(in0_label, powr1220_label, VMON1); -static SENSOR_DEVICE_ATTR_RO(in1_label, powr1220_label, VMON2); -static SENSOR_DEVICE_ATTR_RO(in2_label, powr1220_label, VMON3); -static SENSOR_DEVICE_ATTR_RO(in3_label, powr1220_label, VMON4); -static SENSOR_DEVICE_ATTR_RO(in4_label, powr1220_label, VMON5); -static SENSOR_DEVICE_ATTR_RO(in5_label, powr1220_label, VMON6); -static SENSOR_DEVICE_ATTR_RO(in6_label, powr1220_label, VMON7); -static SENSOR_DEVICE_ATTR_RO(in7_label, powr1220_label, VMON8); -static SENSOR_DEVICE_ATTR_RO(in8_label, powr1220_label, VMON9); -static SENSOR_DEVICE_ATTR_RO(in9_label, powr1220_label, VMON10); -static SENSOR_DEVICE_ATTR_RO(in10_label, powr1220_label, VMON11); -static SENSOR_DEVICE_ATTR_RO(in11_label, powr1220_label, VMON12); -static SENSOR_DEVICE_ATTR_RO(in12_label, powr1220_label, VCCA); -static SENSOR_DEVICE_ATTR_RO(in13_label, powr1220_label, VCCINP); - -static struct attribute *powr1220_attrs[] = { - &sensor_dev_attr_in0_input.dev_attr.attr, - &sensor_dev_attr_in1_input.dev_attr.attr, - &sensor_dev_attr_in2_input.dev_attr.attr, - &sensor_dev_attr_in3_input.dev_attr.attr, - &sensor_dev_attr_in4_input.dev_attr.attr, - &sensor_dev_attr_in5_input.dev_attr.attr, - &sensor_dev_attr_in6_input.dev_attr.attr, - &sensor_dev_attr_in7_input.dev_attr.attr, - &sensor_dev_attr_in8_input.dev_attr.attr, - &sensor_dev_attr_in9_input.dev_attr.attr, - &sensor_dev_attr_in10_input.dev_attr.attr, - &sensor_dev_attr_in11_input.dev_attr.attr, - &sensor_dev_attr_in12_input.dev_attr.attr, - &sensor_dev_attr_in13_input.dev_attr.attr, - - &sensor_dev_attr_in0_highest.dev_attr.attr, - &sensor_dev_attr_in1_highest.dev_attr.attr, - &sensor_dev_attr_in2_highest.dev_attr.attr, - &sensor_dev_attr_in3_highest.dev_attr.attr, - &sensor_dev_attr_in4_highest.dev_attr.attr, - &sensor_dev_attr_in5_highest.dev_attr.attr, - &sensor_dev_attr_in6_highest.dev_attr.attr, - &sensor_dev_attr_in7_highest.dev_attr.attr, - &sensor_dev_attr_in8_highest.dev_attr.attr, - &sensor_dev_attr_in9_highest.dev_attr.attr, - &sensor_dev_attr_in10_highest.dev_attr.attr, - &sensor_dev_attr_in11_highest.dev_attr.attr, - &sensor_dev_attr_in12_highest.dev_attr.attr, - &sensor_dev_attr_in13_highest.dev_attr.attr, - - &sensor_dev_attr_in0_label.dev_attr.attr, - &sensor_dev_attr_in1_label.dev_attr.attr, - &sensor_dev_attr_in2_label.dev_attr.attr, - &sensor_dev_attr_in3_label.dev_attr.attr, - &sensor_dev_attr_in4_label.dev_attr.attr, - &sensor_dev_attr_in5_label.dev_attr.attr, - &sensor_dev_attr_in6_label.dev_attr.attr, - &sensor_dev_attr_in7_label.dev_attr.attr, - &sensor_dev_attr_in8_label.dev_attr.attr, - &sensor_dev_attr_in9_label.dev_attr.attr, - &sensor_dev_attr_in10_label.dev_attr.attr, - &sensor_dev_attr_in11_label.dev_attr.attr, - &sensor_dev_attr_in12_label.dev_attr.attr, - &sensor_dev_attr_in13_label.dev_attr.attr, +static const struct hwmon_channel_info *powr1220_info[] = { + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_HIGHEST | HWMON_I_LABEL), NULL }; -ATTRIBUTE_GROUPS(powr1220); +static const struct hwmon_ops powr1220_hwmon_ops = { + .read = powr1220_read, + .read_string = powr1220_read_string, + .is_visible = powr1220_is_visible, +}; + +static const struct hwmon_chip_info powr1220_chip_info = { + .ops = &powr1220_hwmon_ops, + .info = powr1220_info, +}; + +static const struct i2c_device_id powr1220_ids[]; static int powr1220_probe(struct i2c_client *client) { @@ -309,17 +293,30 @@ static int powr1220_probe(struct i2c_client *client) if (!data) return -ENOMEM; + switch (i2c_match_id(powr1220_ids, client)->driver_data) { + case powr1014: + data->max_channels = 10; + break; + default: + data->max_channels = 12; + break; + } + mutex_init(&data->update_lock); data->client = client; - hwmon_dev = devm_hwmon_device_register_with_groups(&client->dev, - client->name, data, powr1220_groups); + hwmon_dev = devm_hwmon_device_register_with_info(&client->dev, + client->name, + data, + &powr1220_chip_info, + NULL); return PTR_ERR_OR_ZERO(hwmon_dev); } static const struct i2c_device_id powr1220_ids[] = { - { "powr1220", 0, }, + { "powr1014", powr1014, }, + { "powr1220", powr1220, }, { } }; diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c index 17518b4cab1b..f12b9a28a232 100644 --- a/drivers/hwmon/pwm-fan.c +++ b/drivers/hwmon/pwm-fan.c @@ -336,8 +336,6 @@ static int pwm_fan_probe(struct platform_device *pdev) return ret; } - ctx->pwm_value = MAX_PWM; - pwm_init_state(ctx->pwm, &ctx->pwm_state); /* diff --git a/drivers/hwmon/raspberrypi-hwmon.c b/drivers/hwmon/raspberrypi-hwmon.c index 573f53d52912..1650d3b4c26e 100644 --- a/drivers/hwmon/raspberrypi-hwmon.c +++ b/drivers/hwmon/raspberrypi-hwmon.c @@ -120,6 +120,8 @@ static int rpi_hwmon_probe(struct platform_device *pdev) data, &rpi_chip_info, NULL); + if (IS_ERR(data->hwmon_dev)) + return PTR_ERR(data->hwmon_dev); ret = devm_delayed_work_autocancel(dev, &data->get_values_poll_work, get_values_poll); @@ -127,10 +129,9 @@ static int rpi_hwmon_probe(struct platform_device *pdev) return ret; platform_set_drvdata(pdev, data); - if (!PTR_ERR_OR_ZERO(data->hwmon_dev)) - schedule_delayed_work(&data->get_values_poll_work, 2 * HZ); + schedule_delayed_work(&data->get_values_poll_work, 2 * HZ); - return PTR_ERR_OR_ZERO(data->hwmon_dev); + return 0; } static struct platform_driver rpi_hwmon_driver = { diff --git a/drivers/hwmon/sch5627.c b/drivers/hwmon/sch5627.c index 8f1b569c69e7..25fbbd4c9a2b 100644 --- a/drivers/hwmon/sch5627.c +++ b/drivers/hwmon/sch5627.c @@ -7,6 +7,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/module.h> +#include <linux/mod_devicetable.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/jiffies.h> @@ -51,6 +52,9 @@ static const u16 SCH5627_REG_FAN[SCH5627_NO_FANS] = { static const u16 SCH5627_REG_FAN_MIN[SCH5627_NO_FANS] = { 0x62, 0x64, 0x66, 0x68 }; +static const u16 SCH5627_REG_PWM_MAP[SCH5627_NO_FANS] = { + 0xA0, 0xA1, 0xA2, 0xA3 }; + static const u16 SCH5627_REG_IN_MSB[SCH5627_NO_IN] = { 0x22, 0x23, 0x24, 0x25, 0x189 }; static const u16 SCH5627_REG_IN_LSN[SCH5627_NO_IN] = { @@ -222,6 +226,9 @@ static int reg_to_rpm(u16 reg) static umode_t sch5627_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr, int channel) { + if (type == hwmon_pwm && attr == hwmon_pwm_auto_channels_temp) + return 0644; + return 0444; } @@ -277,6 +284,23 @@ static int sch5627_read(struct device *dev, enum hwmon_sensor_types type, u32 at break; } break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_auto_channels_temp: + mutex_lock(&data->update_lock); + ret = sch56xx_read_virtual_reg(data->addr, SCH5627_REG_PWM_MAP[channel]); + mutex_unlock(&data->update_lock); + + if (ret < 0) + return ret; + + *val = ret; + + return 0; + default: + break; + } + break; case hwmon_in: ret = sch5627_update_in(data); if (ret < 0) @@ -317,10 +341,42 @@ static int sch5627_read_string(struct device *dev, enum hwmon_sensor_types type, return -EOPNOTSUPP; } +static int sch5627_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, + long val) +{ + struct sch5627_data *data = dev_get_drvdata(dev); + int ret; + + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_auto_channels_temp: + /* registers are 8 bit wide */ + if (val > U8_MAX || val < 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + ret = sch56xx_write_virtual_reg(data->addr, SCH5627_REG_PWM_MAP[channel], + val); + mutex_unlock(&data->update_lock); + + return ret; + default: + break; + } + break; + default: + break; + } + + return -EOPNOTSUPP; +} + static const struct hwmon_ops sch5627_ops = { .is_visible = sch5627_is_visible, .read = sch5627_read, .read_string = sch5627_read_string, + .write = sch5627_write, }; static const struct hwmon_channel_info *sch5627_info[] = { @@ -341,6 +397,12 @@ static const struct hwmon_channel_info *sch5627_info[] = { HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_FAULT, HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_FAULT ), + HWMON_CHANNEL_INFO(pwm, + HWMON_PWM_AUTO_CHANNELS_TEMP, + HWMON_PWM_AUTO_CHANNELS_TEMP, + HWMON_PWM_AUTO_CHANNELS_TEMP, + HWMON_PWM_AUTO_CHANNELS_TEMP + ), HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, @@ -456,11 +518,20 @@ static int sch5627_probe(struct platform_device *pdev) return 0; } +static const struct platform_device_id sch5627_device_id[] = { + { + .name = "sch5627", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, sch5627_device_id); + static struct platform_driver sch5627_driver = { .driver = { .name = DRVNAME, }, .probe = sch5627_probe, + .id_table = sch5627_device_id, }; module_platform_driver(sch5627_driver); diff --git a/drivers/hwmon/sch5636.c b/drivers/hwmon/sch5636.c index 39ff1c9b1df5..269757bc3a9e 100644 --- a/drivers/hwmon/sch5636.c +++ b/drivers/hwmon/sch5636.c @@ -7,6 +7,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/module.h> +#include <linux/mod_devicetable.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/jiffies.h> @@ -501,12 +502,21 @@ error: return err; } +static const struct platform_device_id sch5636_device_id[] = { + { + .name = "sch5636", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, sch5636_device_id); + static struct platform_driver sch5636_driver = { .driver = { .name = DRVNAME, }, .probe = sch5636_probe, .remove = sch5636_remove, + .id_table = sch5636_device_id, }; module_platform_driver(sch5636_driver); diff --git a/drivers/hwmon/sch56xx-common.c b/drivers/hwmon/sch56xx-common.c index 40cdadad35e5..3ece53adabd6 100644 --- a/drivers/hwmon/sch56xx-common.c +++ b/drivers/hwmon/sch56xx-common.c @@ -7,8 +7,10 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/module.h> +#include <linux/mod_devicetable.h> #include <linux/init.h> #include <linux/platform_device.h> +#include <linux/dmi.h> #include <linux/err.h> #include <linux/io.h> #include <linux/acpi.h> @@ -19,7 +21,10 @@ #include <linux/slab.h> #include "sch56xx-common.h" -/* Insmod parameters */ +static bool ignore_dmi; +module_param(ignore_dmi, bool, 0); +MODULE_PARM_DESC(ignore_dmi, "Omit DMI check for supported devices (default=0)"); + static bool nowayout = WATCHDOG_NOWAYOUT; module_param(nowayout, bool, 0); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" @@ -134,7 +139,7 @@ static int sch56xx_send_cmd(u16 addr, u8 cmd, u16 reg, u8 v) /* EM Interface Polling "Algorithm" */ for (i = 0; i < max_busy_polls + max_lazy_polls; i++) { if (i >= max_busy_polls) - msleep(1); + usleep_range(1000, 2000); /* Read Interrupt source Register */ val = inb(addr + 8); /* Write Clear the interrupt source bits */ @@ -422,7 +427,7 @@ void sch56xx_watchdog_register(struct device *parent, u16 addr, u32 revision, data->wddev.max_timeout = 255 * 60; watchdog_set_nowayout(&data->wddev, nowayout); if (output_enable & SCH56XX_WDOG_OUTPUT_ENABLE) - set_bit(WDOG_ACTIVE, &data->wddev.status); + set_bit(WDOG_HW_RUNNING, &data->wddev.status); /* Since the watchdog uses a downcounter there is no register to read the BIOS set timeout from (if any was set at all) -> @@ -518,11 +523,42 @@ static int __init sch56xx_device_add(int address, const char *name) return PTR_ERR_OR_ZERO(sch56xx_pdev); } +/* For autoloading only */ +static const struct dmi_system_id sch56xx_dmi_table[] __initconst = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + }, + }, + { } +}; +MODULE_DEVICE_TABLE(dmi, sch56xx_dmi_table); + static int __init sch56xx_init(void) { - int address; const char *name = NULL; + int address; + if (!ignore_dmi) { + if (!dmi_check_system(sch56xx_dmi_table)) + return -ENODEV; + + /* + * Some machines like the Esprimo P720 and Esprimo C700 have + * onboard devices named " Antiope"/" Theseus" instead of + * "Antiope"/"Theseus", so we need to check for both. + */ + if (!dmi_find_device(DMI_DEV_TYPE_OTHER, "Antiope", NULL) && + !dmi_find_device(DMI_DEV_TYPE_OTHER, " Antiope", NULL) && + !dmi_find_device(DMI_DEV_TYPE_OTHER, "Theseus", NULL) && + !dmi_find_device(DMI_DEV_TYPE_OTHER, " Theseus", NULL)) + return -ENODEV; + } + + /* + * Some devices like the Esprimo C700 have both onboard devices, + * so we still have to check manually + */ address = sch56xx_find(0x4e, &name); if (address < 0) address = sch56xx_find(0x2e, &name); diff --git a/drivers/hwmon/scpi-hwmon.c b/drivers/hwmon/scpi-hwmon.c index 919877970ae3..5187c6dd5a4f 100644 --- a/drivers/hwmon/scpi-hwmon.c +++ b/drivers/hwmon/scpi-hwmon.c @@ -141,7 +141,6 @@ static int scpi_hwmon_probe(struct platform_device *pdev) struct scpi_ops *scpi_ops; struct device *hwdev, *dev = &pdev->dev; struct scpi_sensors *scpi_sensors; - const struct of_device_id *of_id; int idx, ret; scpi_ops = get_scpi_ops(); @@ -171,12 +170,11 @@ static int scpi_hwmon_probe(struct platform_device *pdev) scpi_sensors->scpi_ops = scpi_ops; - of_id = of_match_device(scpi_of_match, &pdev->dev); - if (!of_id) { + scale = of_device_get_match_data(&pdev->dev); + if (!scale) { dev_err(&pdev->dev, "Unable to initialize scpi-hwmon data\n"); return -ENODEV; } - scale = of_id->data; for (i = 0, idx = 0; i < nr_sensors; i++) { struct sensor_data *sensor = &scpi_sensors->data[idx]; diff --git a/drivers/hwmon/sht4x.c b/drivers/hwmon/sht4x.c index 09c2a0b06444..c19df3ade48e 100644 --- a/drivers/hwmon/sht4x.c +++ b/drivers/hwmon/sht4x.c @@ -23,7 +23,7 @@ /* * I2C command delays (in microseconds) */ -#define SHT4X_MEAS_DELAY 1000 +#define SHT4X_MEAS_DELAY_HPM 8200 /* see t_MEAS,h in datasheet */ #define SHT4X_DELAY_EXTRA 10000 /* @@ -90,7 +90,7 @@ static int sht4x_read_values(struct sht4x_data *data) if (ret < 0) goto unlock; - usleep_range(SHT4X_MEAS_DELAY, SHT4X_MEAS_DELAY + SHT4X_DELAY_EXTRA); + usleep_range(SHT4X_MEAS_DELAY_HPM, SHT4X_MEAS_DELAY_HPM + SHT4X_DELAY_EXTRA); ret = i2c_master_recv(client, raw_data, SHT4X_RESPONSE_LENGTH); if (ret != SHT4X_RESPONSE_LENGTH) { @@ -281,9 +281,16 @@ static const struct i2c_device_id sht4x_id[] = { }; MODULE_DEVICE_TABLE(i2c, sht4x_id); +static const struct of_device_id sht4x_of_match[] = { + { .compatible = "sensirion,sht4x" }, + { } +}; +MODULE_DEVICE_TABLE(of, sht4x_of_match); + static struct i2c_driver sht4x_driver = { .driver = { .name = "sht4x", + .of_match_table = sht4x_of_match, }, .probe = sht4x_probe, .id_table = sht4x_id, diff --git a/drivers/hwmon/tc654.c b/drivers/hwmon/tc654.c index a52ca72af120..54cd33d09688 100644 --- a/drivers/hwmon/tc654.c +++ b/drivers/hwmon/tc654.c @@ -15,6 +15,7 @@ #include <linux/module.h> #include <linux/mutex.h> #include <linux/slab.h> +#include <linux/thermal.h> #include <linux/util_macros.h> enum tc654_regs { @@ -379,28 +380,20 @@ static ssize_t pwm_show(struct device *dev, struct device_attribute *da, return sprintf(buf, "%d\n", pwm); } -static ssize_t pwm_store(struct device *dev, struct device_attribute *da, - const char *buf, size_t count) +static int _set_pwm(struct tc654_data *data, unsigned long val) { - struct tc654_data *data = dev_get_drvdata(dev); struct i2c_client *client = data->client; - unsigned long val; int ret; - if (kstrtoul(buf, 10, &val)) - return -EINVAL; - if (val > 255) - return -EINVAL; - mutex_lock(&data->update_lock); - if (val == 0) + if (val == 0) { data->config |= TC654_REG_CONFIG_SDM; - else + data->duty_cycle = 0; + } else { data->config &= ~TC654_REG_CONFIG_SDM; - - data->duty_cycle = find_closest(val, tc654_pwm_map, - ARRAY_SIZE(tc654_pwm_map)); + data->duty_cycle = val - 1; + } ret = i2c_smbus_write_byte_data(client, TC654_REG_CONFIG, data->config); if (ret < 0) @@ -411,6 +404,24 @@ static ssize_t pwm_store(struct device *dev, struct device_attribute *da, out: mutex_unlock(&data->update_lock); + return ret; +} + +static ssize_t pwm_store(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct tc654_data *data = dev_get_drvdata(dev); + unsigned long val; + int ret; + + if (kstrtoul(buf, 10, &val)) + return -EINVAL; + if (val > 255) + return -EINVAL; + if (val > 0) + val = find_closest(val, tc654_pwm_map, ARRAY_SIZE(tc654_pwm_map)) + 1; + + ret = _set_pwm(data, val); return ret < 0 ? ret : count; } @@ -443,6 +454,58 @@ static struct attribute *tc654_attrs[] = { ATTRIBUTE_GROUPS(tc654); /* + * thermal cooling device functions + * + * Account for the "ShutDown Mode (SDM)" state by offsetting + * the 16 PWM duty cycle states by 1. + * + * State 0 = 0% PWM | Shutdown - Fan(s) are off + * State 1 = 30% PWM | duty_cycle = 0 + * State 2 = ~35% PWM | duty_cycle = 1 + * [...] + * State 15 = ~95% PWM | duty_cycle = 14 + * State 16 = 100% PWM | duty_cycle = 15 + */ +#define TC654_MAX_COOLING_STATE 16 + +static int tc654_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) +{ + *state = TC654_MAX_COOLING_STATE; + return 0; +} + +static int tc654_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state) +{ + struct tc654_data *data = tc654_update_client(cdev->devdata); + + if (IS_ERR(data)) + return PTR_ERR(data); + + if (data->config & TC654_REG_CONFIG_SDM) + *state = 0; /* FAN is off */ + else + *state = data->duty_cycle + 1; /* offset PWM States by 1 */ + + return 0; +} + +static int tc654_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) +{ + struct tc654_data *data = tc654_update_client(cdev->devdata); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return _set_pwm(data, clamp_val(state, 0, TC654_MAX_COOLING_STATE)); +} + +static const struct thermal_cooling_device_ops tc654_fan_cool_ops = { + .get_max_state = tc654_get_max_state, + .get_cur_state = tc654_get_cur_state, + .set_cur_state = tc654_set_cur_state, +}; + +/* * device probe and removal */ @@ -472,7 +535,18 @@ static int tc654_probe(struct i2c_client *client) hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, data, tc654_groups); - return PTR_ERR_OR_ZERO(hwmon_dev); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + if (IS_ENABLED(CONFIG_THERMAL)) { + struct thermal_cooling_device *cdev; + + cdev = devm_thermal_of_cooling_device_register(dev, dev->of_node, client->name, + hwmon_dev, &tc654_fan_cool_ops); + return PTR_ERR_OR_ZERO(cdev); + } + + return 0; } static const struct i2c_device_id tc654_id[] = { diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c index b31f4964f852..b86d9df7105d 100644 --- a/drivers/hwmon/tmp401.c +++ b/drivers/hwmon/tmp401.c @@ -18,17 +18,15 @@ * and thus has 16 bits registers for its value and limit instead of 8 bits. */ -#include <linux/module.h> -#include <linux/init.h> #include <linux/bitops.h> -#include <linux/slab.h> -#include <linux/jiffies.h> +#include <linux/err.h> #include <linux/i2c.h> #include <linux/hwmon.h> -#include <linux/hwmon-sysfs.h> -#include <linux/err.h> +#include <linux/init.h> +#include <linux/module.h> #include <linux/mutex.h> -#include <linux/sysfs.h> +#include <linux/regmap.h> +#include <linux/slab.h> /* Addresses to scan */ static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4c, 0x4d, @@ -41,44 +39,19 @@ enum chips { tmp401, tmp411, tmp431, tmp432, tmp435 }; * reading and writing */ #define TMP401_STATUS 0x02 -#define TMP401_CONFIG_READ 0x03 -#define TMP401_CONFIG_WRITE 0x09 -#define TMP401_CONVERSION_RATE_READ 0x04 -#define TMP401_CONVERSION_RATE_WRITE 0x0A +#define TMP401_CONFIG 0x03 +#define TMP401_CONVERSION_RATE 0x04 #define TMP401_TEMP_CRIT_HYST 0x21 #define TMP401_MANUFACTURER_ID_REG 0xFE #define TMP401_DEVICE_ID_REG 0xFF -static const u8 TMP401_TEMP_MSB_READ[7][2] = { - { 0x00, 0x01 }, /* temp */ - { 0x06, 0x08 }, /* low limit */ - { 0x05, 0x07 }, /* high limit */ - { 0x20, 0x19 }, /* therm (crit) limit */ - { 0x30, 0x34 }, /* lowest */ - { 0x32, 0x36 }, /* highest */ -}; - -static const u8 TMP401_TEMP_MSB_WRITE[7][2] = { - { 0, 0 }, /* temp (unused) */ - { 0x0C, 0x0E }, /* low limit */ - { 0x0B, 0x0D }, /* high limit */ - { 0x20, 0x19 }, /* therm (crit) limit */ - { 0x30, 0x34 }, /* lowest */ - { 0x32, 0x36 }, /* highest */ -}; - -static const u8 TMP432_TEMP_MSB_READ[4][3] = { +static const u8 TMP401_TEMP_MSB[7][3] = { { 0x00, 0x01, 0x23 }, /* temp */ { 0x06, 0x08, 0x16 }, /* low limit */ { 0x05, 0x07, 0x15 }, /* high limit */ - { 0x20, 0x19, 0x1A }, /* therm (crit) limit */ -}; - -static const u8 TMP432_TEMP_MSB_WRITE[4][3] = { - { 0, 0, 0 }, /* temp - unused */ - { 0x0C, 0x0E, 0x16 }, /* low limit */ - { 0x0B, 0x0D, 0x15 }, /* high limit */ - { 0x20, 0x19, 0x1A }, /* therm (crit) limit */ + { 0x20, 0x19, 0x1a }, /* therm (crit) limit */ + { 0x30, 0x34, 0x00 }, /* lowest */ + { 0x32, 0xf6, 0x00 }, /* highest */ }; /* [0] = fault, [1] = low, [2] = high, [3] = therm/crit */ @@ -131,311 +104,323 @@ MODULE_DEVICE_TABLE(i2c, tmp401_id); struct tmp401_data { struct i2c_client *client; - const struct attribute_group *groups[3]; + struct regmap *regmap; struct mutex update_lock; - bool valid; /* false until following fields are valid */ - unsigned long last_updated; /* in jiffies */ enum chips kind; - unsigned int update_interval; /* in milliseconds */ + bool extended_range; - /* register values */ - u8 status[4]; - u8 config; - u16 temp[7][3]; - u8 temp_crit_hyst; + /* hwmon API configuration data */ + u32 chip_channel_config[4]; + struct hwmon_channel_info chip_info; + u32 temp_channel_config[4]; + struct hwmon_channel_info temp_info; + const struct hwmon_channel_info *info[3]; + struct hwmon_chip_info chip; }; -/* - * Sysfs attr show / store functions - */ - -static int tmp401_register_to_temp(u16 reg, u8 config) -{ - int temp = reg; - - if (config & TMP401_CONFIG_RANGE) - temp -= 64 * 256; - - return DIV_ROUND_CLOSEST(temp * 125, 32); -} +/* regmap */ -static u16 tmp401_temp_to_register(long temp, u8 config, int zbits) +static bool tmp401_regmap_is_volatile(struct device *dev, unsigned int reg) { - if (config & TMP401_CONFIG_RANGE) { - temp = clamp_val(temp, -64000, 191000); - temp += 64000; - } else - temp = clamp_val(temp, 0, 127000); - - return DIV_ROUND_CLOSEST(temp * (1 << (8 - zbits)), 1000) << zbits; -} - -static int tmp401_update_device_reg16(struct i2c_client *client, - struct tmp401_data *data) -{ - int i, j, val; - int num_regs = data->kind == tmp411 ? 6 : 4; - int num_sensors = data->kind == tmp432 ? 3 : 2; - - for (i = 0; i < num_sensors; i++) { /* local / r1 / r2 */ - for (j = 0; j < num_regs; j++) { /* temp / low / ... */ - u8 regaddr; - - regaddr = data->kind == tmp432 ? - TMP432_TEMP_MSB_READ[j][i] : - TMP401_TEMP_MSB_READ[j][i]; - if (j == 3) { /* crit is msb only */ - val = i2c_smbus_read_byte_data(client, regaddr); - } else { - val = i2c_smbus_read_word_swapped(client, - regaddr); - } - if (val < 0) - return val; - - data->temp[j][i] = j == 3 ? val << 8 : val; - } + switch (reg) { + case 0: /* local temp msb */ + case 1: /* remote temp msb */ + case 2: /* status */ + case 0x10: /* remote temp lsb */ + case 0x15: /* local temp lsb */ + case 0x1b: /* status (tmp432) */ + case 0x23 ... 0x24: /* remote temp 2 msb / lsb */ + case 0x30 ... 0x37: /* lowest/highest temp; status (tmp432) */ + return true; + default: + return false; } - return 0; } -static struct tmp401_data *tmp401_update_device(struct device *dev) +static int tmp401_reg_read(void *context, unsigned int reg, unsigned int *val) { - struct tmp401_data *data = dev_get_drvdata(dev); + struct tmp401_data *data = context; struct i2c_client *client = data->client; - struct tmp401_data *ret = data; - int i, val; - unsigned long next_update; - - mutex_lock(&data->update_lock); + int regval; - next_update = data->last_updated + - msecs_to_jiffies(data->update_interval); - if (time_after(jiffies, next_update) || !data->valid) { - if (data->kind != tmp432) { - /* - * The driver uses the TMP432 status format internally. - * Convert status to TMP432 format for other chips. - */ - val = i2c_smbus_read_byte_data(client, TMP401_STATUS); - if (val < 0) { - ret = ERR_PTR(val); - goto abort; - } - data->status[0] = - (val & TMP401_STATUS_REMOTE_OPEN) >> 1; - data->status[1] = - ((val & TMP401_STATUS_REMOTE_LOW) >> 2) | - ((val & TMP401_STATUS_LOCAL_LOW) >> 5); - data->status[2] = - ((val & TMP401_STATUS_REMOTE_HIGH) >> 3) | - ((val & TMP401_STATUS_LOCAL_HIGH) >> 6); - data->status[3] = val & (TMP401_STATUS_LOCAL_CRIT - | TMP401_STATUS_REMOTE_CRIT); - } else { - for (i = 0; i < ARRAY_SIZE(data->status); i++) { - val = i2c_smbus_read_byte_data(client, - TMP432_STATUS_REG[i]); - if (val < 0) { - ret = ERR_PTR(val); - goto abort; - } - data->status[i] = val; - } - } - - val = i2c_smbus_read_byte_data(client, TMP401_CONFIG_READ); - if (val < 0) { - ret = ERR_PTR(val); - goto abort; - } - data->config = val; - val = tmp401_update_device_reg16(client, data); - if (val < 0) { - ret = ERR_PTR(val); - goto abort; + switch (reg) { + case 0: /* local temp msb */ + case 1: /* remote temp msb */ + case 5: /* local temp high limit msb */ + case 6: /* local temp low limit msb */ + case 7: /* remote temp ligh limit msb */ + case 8: /* remote temp low limit msb */ + case 0x15: /* remote temp 2 high limit msb */ + case 0x16: /* remote temp 2 low limit msb */ + case 0x23: /* remote temp 2 msb */ + case 0x30: /* local temp minimum, tmp411 */ + case 0x32: /* local temp maximum, tmp411 */ + case 0x34: /* remote temp minimum, tmp411 */ + case 0xf6: /* remote temp maximum, tmp411 (really 0x36) */ + /* work around register overlap between TMP411 and TMP432 */ + if (reg == 0xf6) + reg = 0x36; + regval = i2c_smbus_read_word_swapped(client, reg); + if (regval < 0) + return regval; + *val = regval; + break; + case 0x19: /* critical limits, 8-bit registers */ + case 0x1a: + case 0x20: + regval = i2c_smbus_read_byte_data(client, reg); + if (regval < 0) + return regval; + *val = regval << 8; + break; + case 0x1b: + case 0x35 ... 0x37: + if (data->kind == tmp432) { + regval = i2c_smbus_read_byte_data(client, reg); + if (regval < 0) + return regval; + *val = regval; + break; } - val = i2c_smbus_read_byte_data(client, TMP401_TEMP_CRIT_HYST); - if (val < 0) { - ret = ERR_PTR(val); - goto abort; + /* simulate TMP432 status registers */ + regval = i2c_smbus_read_byte_data(client, TMP401_STATUS); + if (regval < 0) + return regval; + *val = 0; + switch (reg) { + case 0x1b: /* open / fault */ + if (regval & TMP401_STATUS_REMOTE_OPEN) + *val |= BIT(1); + break; + case 0x35: /* high limit */ + if (regval & TMP401_STATUS_LOCAL_HIGH) + *val |= BIT(0); + if (regval & TMP401_STATUS_REMOTE_HIGH) + *val |= BIT(1); + break; + case 0x36: /* low limit */ + if (regval & TMP401_STATUS_LOCAL_LOW) + *val |= BIT(0); + if (regval & TMP401_STATUS_REMOTE_LOW) + *val |= BIT(1); + break; + case 0x37: /* therm / crit limit */ + if (regval & TMP401_STATUS_LOCAL_CRIT) + *val |= BIT(0); + if (regval & TMP401_STATUS_REMOTE_CRIT) + *val |= BIT(1); + break; } - data->temp_crit_hyst = val; - - data->last_updated = jiffies; - data->valid = true; + break; + default: + regval = i2c_smbus_read_byte_data(client, reg); + if (regval < 0) + return regval; + *val = regval; + break; } - -abort: - mutex_unlock(&data->update_lock); - return ret; + return 0; } -static ssize_t temp_show(struct device *dev, struct device_attribute *devattr, - char *buf) +static int tmp401_reg_write(void *context, unsigned int reg, unsigned int val) { - int nr = to_sensor_dev_attr_2(devattr)->nr; - int index = to_sensor_dev_attr_2(devattr)->index; - struct tmp401_data *data = tmp401_update_device(dev); - - if (IS_ERR(data)) - return PTR_ERR(data); + struct tmp401_data *data = context; + struct i2c_client *client = data->client; - return sprintf(buf, "%d\n", - tmp401_register_to_temp(data->temp[nr][index], data->config)); + switch (reg) { + case 0x05: /* local temp high limit msb */ + case 0x06: /* local temp low limit msb */ + case 0x07: /* remote temp ligh limit msb */ + case 0x08: /* remote temp low limit msb */ + reg += 6; /* adjust for register write address */ + fallthrough; + case 0x15: /* remote temp 2 high limit msb */ + case 0x16: /* remote temp 2 low limit msb */ + return i2c_smbus_write_word_swapped(client, reg, val); + case 0x19: /* critical limits, 8-bit registers */ + case 0x1a: + case 0x20: + return i2c_smbus_write_byte_data(client, reg, val >> 8); + case TMP401_CONVERSION_RATE: + case TMP401_CONFIG: + reg += 6; /* adjust for register write address */ + fallthrough; + default: + return i2c_smbus_write_byte_data(client, reg, val); + } } -static ssize_t temp_crit_hyst_show(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - int temp, index = to_sensor_dev_attr(devattr)->index; - struct tmp401_data *data = tmp401_update_device(dev); - - if (IS_ERR(data)) - return PTR_ERR(data); - - mutex_lock(&data->update_lock); - temp = tmp401_register_to_temp(data->temp[3][index], data->config); - temp -= data->temp_crit_hyst * 1000; - mutex_unlock(&data->update_lock); +static const struct regmap_config tmp401_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = tmp401_regmap_is_volatile, + .reg_read = tmp401_reg_read, + .reg_write = tmp401_reg_write, +}; - return sprintf(buf, "%d\n", temp); -} +/* temperature conversion */ -static ssize_t status_show(struct device *dev, - struct device_attribute *devattr, char *buf) +static int tmp401_register_to_temp(u16 reg, bool extended) { - int nr = to_sensor_dev_attr_2(devattr)->nr; - int mask = to_sensor_dev_attr_2(devattr)->index; - struct tmp401_data *data = tmp401_update_device(dev); + int temp = reg; - if (IS_ERR(data)) - return PTR_ERR(data); + if (extended) + temp -= 64 * 256; - return sprintf(buf, "%d\n", !!(data->status[nr] & mask)); + return DIV_ROUND_CLOSEST(temp * 125, 32); } -static ssize_t temp_store(struct device *dev, - struct device_attribute *devattr, const char *buf, - size_t count) +static u16 tmp401_temp_to_register(long temp, bool extended, int zbits) { - int nr = to_sensor_dev_attr_2(devattr)->nr; - int index = to_sensor_dev_attr_2(devattr)->index; - struct tmp401_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; - long val; - u16 reg; - u8 regaddr; - - if (kstrtol(buf, 10, &val)) - return -EINVAL; - - reg = tmp401_temp_to_register(val, data->config, nr == 3 ? 8 : 4); - - mutex_lock(&data->update_lock); - - regaddr = data->kind == tmp432 ? TMP432_TEMP_MSB_WRITE[nr][index] - : TMP401_TEMP_MSB_WRITE[nr][index]; - if (nr == 3) { /* crit is msb only */ - i2c_smbus_write_byte_data(client, regaddr, reg >> 8); + if (extended) { + temp = clamp_val(temp, -64000, 191000); + temp += 64000; } else { - /* Hardware expects big endian data --> use _swapped */ - i2c_smbus_write_word_swapped(client, regaddr, reg); + temp = clamp_val(temp, 0, 127000); } - data->temp[nr][index] = reg; - - mutex_unlock(&data->update_lock); - return count; + return DIV_ROUND_CLOSEST(temp * (1 << (8 - zbits)), 1000) << zbits; } -static ssize_t temp_crit_hyst_store(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) -{ - int temp, index = to_sensor_dev_attr(devattr)->index; - struct tmp401_data *data = tmp401_update_device(dev); - long val; - u8 reg; - - if (IS_ERR(data)) - return PTR_ERR(data); - - if (kstrtol(buf, 10, &val)) - return -EINVAL; - - if (data->config & TMP401_CONFIG_RANGE) - val = clamp_val(val, -64000, 191000); - else - val = clamp_val(val, 0, 127000); - - mutex_lock(&data->update_lock); - temp = tmp401_register_to_temp(data->temp[3][index], data->config); - val = clamp_val(val, temp - 255000, temp); - reg = ((temp - val) + 500) / 1000; - - i2c_smbus_write_byte_data(data->client, TMP401_TEMP_CRIT_HYST, - reg); +/* hwmon API functions */ - data->temp_crit_hyst = reg; +static const u8 tmp401_temp_reg_index[] = { + [hwmon_temp_input] = 0, + [hwmon_temp_min] = 1, + [hwmon_temp_max] = 2, + [hwmon_temp_crit] = 3, + [hwmon_temp_lowest] = 4, + [hwmon_temp_highest] = 5, +}; - mutex_unlock(&data->update_lock); +static const u8 tmp401_status_reg_index[] = { + [hwmon_temp_fault] = 0, + [hwmon_temp_min_alarm] = 1, + [hwmon_temp_max_alarm] = 2, + [hwmon_temp_crit_alarm] = 3, +}; - return count; +static int tmp401_temp_read(struct device *dev, u32 attr, int channel, long *val) +{ + struct tmp401_data *data = dev_get_drvdata(dev); + struct regmap *regmap = data->regmap; + unsigned int regval; + int reg, ret; + + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_min: + case hwmon_temp_max: + case hwmon_temp_crit: + case hwmon_temp_lowest: + case hwmon_temp_highest: + reg = TMP401_TEMP_MSB[tmp401_temp_reg_index[attr]][channel]; + ret = regmap_read(regmap, reg, ®val); + if (ret < 0) + return ret; + *val = tmp401_register_to_temp(regval, data->extended_range); + break; + case hwmon_temp_crit_hyst: + mutex_lock(&data->update_lock); + reg = TMP401_TEMP_MSB[3][channel]; + ret = regmap_read(regmap, reg, ®val); + if (ret < 0) + goto unlock; + *val = tmp401_register_to_temp(regval, data->extended_range); + ret = regmap_read(regmap, TMP401_TEMP_CRIT_HYST, ®val); + if (ret < 0) + goto unlock; + *val -= regval * 1000; +unlock: + mutex_unlock(&data->update_lock); + if (ret < 0) + return ret; + break; + case hwmon_temp_fault: + case hwmon_temp_min_alarm: + case hwmon_temp_max_alarm: + case hwmon_temp_crit_alarm: + reg = TMP432_STATUS_REG[tmp401_status_reg_index[attr]]; + ret = regmap_read(regmap, reg, ®val); + if (ret < 0) + return ret; + *val = !!(regval & BIT(channel)); + break; + default: + return -EOPNOTSUPP; + } + return 0; } -/* - * Resets the historical measurements of minimum and maximum temperatures. - * This is done by writing any value to any of the minimum/maximum registers - * (0x30-0x37). - */ -static ssize_t reset_temp_history_store(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) +static int tmp401_temp_write(struct device *dev, u32 attr, int channel, + long val) { struct tmp401_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; - long val; - - if (kstrtol(buf, 10, &val)) - return -EINVAL; + struct regmap *regmap = data->regmap; + unsigned int regval; + int reg, ret, temp; - if (val != 1) { - dev_err(dev, - "temp_reset_history value %ld not supported. Use 1 to reset the history!\n", - val); - return -EINVAL; - } mutex_lock(&data->update_lock); - i2c_smbus_write_byte_data(client, TMP401_TEMP_MSB_WRITE[5][0], val); - data->valid = false; + switch (attr) { + case hwmon_temp_min: + case hwmon_temp_max: + case hwmon_temp_crit: + reg = TMP401_TEMP_MSB[tmp401_temp_reg_index[attr]][channel]; + regval = tmp401_temp_to_register(val, data->extended_range, + attr == hwmon_temp_crit ? 8 : 4); + ret = regmap_write(regmap, reg, regval); + break; + case hwmon_temp_crit_hyst: + if (data->extended_range) + val = clamp_val(val, -64000, 191000); + else + val = clamp_val(val, 0, 127000); + + reg = TMP401_TEMP_MSB[3][channel]; + ret = regmap_read(regmap, reg, ®val); + if (ret < 0) + break; + temp = tmp401_register_to_temp(regval, data->extended_range); + val = clamp_val(val, temp - 255000, temp); + regval = ((temp - val) + 500) / 1000; + ret = regmap_write(regmap, TMP401_TEMP_CRIT_HYST, regval); + break; + default: + ret = -EOPNOTSUPP; + break; + } mutex_unlock(&data->update_lock); - - return count; + return ret; } -static ssize_t update_interval_show(struct device *dev, - struct device_attribute *attr, char *buf) +static int tmp401_chip_read(struct device *dev, u32 attr, int channel, long *val) { struct tmp401_data *data = dev_get_drvdata(dev); + u32 regval; + int ret; + + switch (attr) { + case hwmon_chip_update_interval: + ret = regmap_read(data->regmap, TMP401_CONVERSION_RATE, ®val); + if (ret < 0) + return ret; + *val = (1 << (7 - regval)) * 125; + break; + case hwmon_chip_temp_reset_history: + *val = 0; + break; + default: + return -EOPNOTSUPP; + } - return sprintf(buf, "%u\n", data->update_interval); + return 0; } -static ssize_t update_interval_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static int tmp401_set_convrate(struct regmap *regmap, long val) { - struct tmp401_data *data = dev_get_drvdata(dev); - struct i2c_client *client = data->client; - unsigned long val; - int err, rate; - - err = kstrtoul(buf, 10, &val); - if (err) - return err; + int rate; /* * For valid rates, interval can be calculated as @@ -447,153 +432,137 @@ static ssize_t update_interval_store(struct device *dev, */ val = clamp_val(val, 125, 16000); rate = 7 - __fls(val * 4 / (125 * 3)); + return regmap_write(regmap, TMP401_CONVERSION_RATE, rate); +} + +static int tmp401_chip_write(struct device *dev, u32 attr, int channel, long val) +{ + struct tmp401_data *data = dev_get_drvdata(dev); + struct regmap *regmap = data->regmap; + int err; + mutex_lock(&data->update_lock); - i2c_smbus_write_byte_data(client, TMP401_CONVERSION_RATE_WRITE, rate); - data->update_interval = (1 << (7 - rate)) * 125; + switch (attr) { + case hwmon_chip_update_interval: + err = tmp401_set_convrate(regmap, val); + break; + case hwmon_chip_temp_reset_history: + if (val != 1) { + err = -EINVAL; + break; + } + /* + * Reset history by writing any value to any of the + * minimum/maximum registers (0x30-0x37). + */ + err = regmap_write(regmap, 0x30, 0); + break; + default: + err = -EOPNOTSUPP; + break; + } mutex_unlock(&data->update_lock); - return count; + return err; } -static SENSOR_DEVICE_ATTR_2_RO(temp1_input, temp, 0, 0); -static SENSOR_DEVICE_ATTR_2_RW(temp1_min, temp, 1, 0); -static SENSOR_DEVICE_ATTR_2_RW(temp1_max, temp, 2, 0); -static SENSOR_DEVICE_ATTR_2_RW(temp1_crit, temp, 3, 0); -static SENSOR_DEVICE_ATTR_RW(temp1_crit_hyst, temp_crit_hyst, 0); -static SENSOR_DEVICE_ATTR_2_RO(temp1_min_alarm, status, 1, - TMP432_STATUS_LOCAL); -static SENSOR_DEVICE_ATTR_2_RO(temp1_max_alarm, status, 2, - TMP432_STATUS_LOCAL); -static SENSOR_DEVICE_ATTR_2_RO(temp1_crit_alarm, status, 3, - TMP432_STATUS_LOCAL); -static SENSOR_DEVICE_ATTR_2_RO(temp2_input, temp, 0, 1); -static SENSOR_DEVICE_ATTR_2_RW(temp2_min, temp, 1, 1); -static SENSOR_DEVICE_ATTR_2_RW(temp2_max, temp, 2, 1); -static SENSOR_DEVICE_ATTR_2_RW(temp2_crit, temp, 3, 1); -static SENSOR_DEVICE_ATTR_RO(temp2_crit_hyst, temp_crit_hyst, 1); -static SENSOR_DEVICE_ATTR_2_RO(temp2_fault, status, 0, TMP432_STATUS_REMOTE1); -static SENSOR_DEVICE_ATTR_2_RO(temp2_min_alarm, status, 1, - TMP432_STATUS_REMOTE1); -static SENSOR_DEVICE_ATTR_2_RO(temp2_max_alarm, status, 2, - TMP432_STATUS_REMOTE1); -static SENSOR_DEVICE_ATTR_2_RO(temp2_crit_alarm, status, 3, - TMP432_STATUS_REMOTE1); - -static DEVICE_ATTR_RW(update_interval); - -static struct attribute *tmp401_attributes[] = { - &sensor_dev_attr_temp1_input.dev_attr.attr, - &sensor_dev_attr_temp1_min.dev_attr.attr, - &sensor_dev_attr_temp1_max.dev_attr.attr, - &sensor_dev_attr_temp1_crit.dev_attr.attr, - &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr, - &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, - &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, - - &sensor_dev_attr_temp2_input.dev_attr.attr, - &sensor_dev_attr_temp2_min.dev_attr.attr, - &sensor_dev_attr_temp2_max.dev_attr.attr, - &sensor_dev_attr_temp2_crit.dev_attr.attr, - &sensor_dev_attr_temp2_crit_hyst.dev_attr.attr, - &sensor_dev_attr_temp2_fault.dev_attr.attr, - &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, - - &dev_attr_update_interval.attr, - - NULL -}; - -static const struct attribute_group tmp401_group = { - .attrs = tmp401_attributes, -}; - -/* - * Additional features of the TMP411 chip. - * The TMP411 stores the minimum and maximum - * temperature measured since power-on, chip-reset, or - * minimum and maximum register reset for both the local - * and remote channels. - */ -static SENSOR_DEVICE_ATTR_2_RO(temp1_lowest, temp, 4, 0); -static SENSOR_DEVICE_ATTR_2_RO(temp1_highest, temp, 5, 0); -static SENSOR_DEVICE_ATTR_2_RO(temp2_lowest, temp, 4, 1); -static SENSOR_DEVICE_ATTR_2_RO(temp2_highest, temp, 5, 1); -static SENSOR_DEVICE_ATTR_WO(temp_reset_history, reset_temp_history, 0); - -static struct attribute *tmp411_attributes[] = { - &sensor_dev_attr_temp1_highest.dev_attr.attr, - &sensor_dev_attr_temp1_lowest.dev_attr.attr, - &sensor_dev_attr_temp2_highest.dev_attr.attr, - &sensor_dev_attr_temp2_lowest.dev_attr.attr, - &sensor_dev_attr_temp_reset_history.dev_attr.attr, - NULL -}; +static int tmp401_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_chip: + return tmp401_chip_read(dev, attr, channel, val); + case hwmon_temp: + return tmp401_temp_read(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} -static const struct attribute_group tmp411_group = { - .attrs = tmp411_attributes, -}; +static int tmp401_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + switch (type) { + case hwmon_chip: + return tmp401_chip_write(dev, attr, channel, val); + case hwmon_temp: + return tmp401_temp_write(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} -static SENSOR_DEVICE_ATTR_2_RO(temp3_input, temp, 0, 2); -static SENSOR_DEVICE_ATTR_2_RW(temp3_min, temp, 1, 2); -static SENSOR_DEVICE_ATTR_2_RW(temp3_max, temp, 2, 2); -static SENSOR_DEVICE_ATTR_2_RW(temp3_crit, temp, 3, 2); -static SENSOR_DEVICE_ATTR_RO(temp3_crit_hyst, temp_crit_hyst, 2); -static SENSOR_DEVICE_ATTR_2_RO(temp3_fault, status, 0, TMP432_STATUS_REMOTE2); -static SENSOR_DEVICE_ATTR_2_RO(temp3_min_alarm, status, 1, - TMP432_STATUS_REMOTE2); -static SENSOR_DEVICE_ATTR_2_RO(temp3_max_alarm, status, 2, - TMP432_STATUS_REMOTE2); -static SENSOR_DEVICE_ATTR_2_RO(temp3_crit_alarm, status, 3, - TMP432_STATUS_REMOTE2); - -static struct attribute *tmp432_attributes[] = { - &sensor_dev_attr_temp3_input.dev_attr.attr, - &sensor_dev_attr_temp3_min.dev_attr.attr, - &sensor_dev_attr_temp3_max.dev_attr.attr, - &sensor_dev_attr_temp3_crit.dev_attr.attr, - &sensor_dev_attr_temp3_crit_hyst.dev_attr.attr, - &sensor_dev_attr_temp3_fault.dev_attr.attr, - &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp3_min_alarm.dev_attr.attr, - &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr, - - NULL -}; +static umode_t tmp401_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: + case hwmon_chip_temp_reset_history: + return 0644; + default: + break; + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_min_alarm: + case hwmon_temp_max_alarm: + case hwmon_temp_crit_alarm: + case hwmon_temp_fault: + case hwmon_temp_lowest: + case hwmon_temp_highest: + return 0444; + case hwmon_temp_min: + case hwmon_temp_max: + case hwmon_temp_crit: + case hwmon_temp_crit_hyst: + return 0644; + default: + break; + } + break; + default: + break; + } + return 0; +} -static const struct attribute_group tmp432_group = { - .attrs = tmp432_attributes, +static const struct hwmon_ops tmp401_ops = { + .is_visible = tmp401_is_visible, + .read = tmp401_read, + .write = tmp401_write, }; -/* - * Begin non sysfs callback code (aka Real code) - */ +/* chip initialization, detect, probe */ -static int tmp401_init_client(struct tmp401_data *data, - struct i2c_client *client) +static int tmp401_init_client(struct tmp401_data *data) { - int config, config_orig, status = 0; + struct regmap *regmap = data->regmap; + u32 config, config_orig; + int ret; - /* Set the conversion rate to 2 Hz */ - i2c_smbus_write_byte_data(client, TMP401_CONVERSION_RATE_WRITE, 5); - data->update_interval = 500; + /* Set conversion rate to 2 Hz */ + ret = regmap_write(regmap, TMP401_CONVERSION_RATE, 5); + if (ret < 0) + return ret; /* Start conversions (disable shutdown if necessary) */ - config = i2c_smbus_read_byte_data(client, TMP401_CONFIG_READ); - if (config < 0) - return config; + ret = regmap_read(regmap, TMP401_CONFIG, &config); + if (ret < 0) + return ret; config_orig = config; config &= ~TMP401_CONFIG_SHUTDOWN; + data->extended_range = !!(config & TMP401_CONFIG_RANGE); + if (config != config_orig) - status = i2c_smbus_write_byte_data(client, - TMP401_CONFIG_WRITE, - config); + ret = regmap_write(regmap, TMP401_CONFIG, config); - return status; + return ret; } static int tmp401_detect(struct i2c_client *client, @@ -651,11 +620,11 @@ static int tmp401_detect(struct i2c_client *client, return -ENODEV; } - reg = i2c_smbus_read_byte_data(client, TMP401_CONFIG_READ); + reg = i2c_smbus_read_byte_data(client, TMP401_CONFIG); if (reg & 0x1b) return -ENODEV; - reg = i2c_smbus_read_byte_data(client, TMP401_CONVERSION_RATE_READ); + reg = i2c_smbus_read_byte_data(client, TMP401_CONVERSION_RATE); /* Datasheet says: 0x1-0x6 */ if (reg > 15) return -ENODEV; @@ -671,9 +640,10 @@ static int tmp401_probe(struct i2c_client *client) "TMP401", "TMP411", "TMP431", "TMP432", "TMP435" }; struct device *dev = &client->dev; + struct hwmon_channel_info *info; struct device *hwmon_dev; struct tmp401_data *data; - int groups = 0, status; + int status; data = devm_kzalloc(dev, sizeof(struct tmp401_data), GFP_KERNEL); if (!data) @@ -683,24 +653,53 @@ static int tmp401_probe(struct i2c_client *client) mutex_init(&data->update_lock); data->kind = i2c_match_id(tmp401_id, client)->driver_data; - /* Initialize the TMP401 chip */ - status = tmp401_init_client(data, client); - if (status < 0) - return status; + data->regmap = devm_regmap_init(dev, NULL, data, &tmp401_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); - /* Register sysfs hooks */ - data->groups[groups++] = &tmp401_group; + /* initialize configuration data */ + data->chip.ops = &tmp401_ops; + data->chip.info = data->info; - /* Register additional tmp411 sysfs hooks */ - if (data->kind == tmp411) - data->groups[groups++] = &tmp411_group; + data->info[0] = &data->chip_info; + data->info[1] = &data->temp_info; - /* Register additional tmp432 sysfs hooks */ - if (data->kind == tmp432) - data->groups[groups++] = &tmp432_group; + info = &data->chip_info; + info->type = hwmon_chip; + info->config = data->chip_channel_config; + + data->chip_channel_config[0] = HWMON_C_UPDATE_INTERVAL; + + info = &data->temp_info; + info->type = hwmon_temp; + info->config = data->temp_channel_config; + + data->temp_channel_config[0] = HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM; + data->temp_channel_config[1] = HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT; + + if (data->kind == tmp411) { + data->temp_channel_config[0] |= HWMON_T_HIGHEST | HWMON_T_LOWEST; + data->temp_channel_config[1] |= HWMON_T_HIGHEST | HWMON_T_LOWEST; + data->chip_channel_config[0] |= HWMON_C_TEMP_RESET_HISTORY; + } + + if (data->kind == tmp432) { + data->temp_channel_config[2] = HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MIN_ALARM | + HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | HWMON_T_FAULT; + } + + /* Initialize the TMP401 chip */ + status = tmp401_init_client(data); + if (status < 0) + return status; - hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, - data, data->groups); + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, + &data->chip, NULL); if (IS_ERR(hwmon_dev)) return PTR_ERR(hwmon_dev); diff --git a/drivers/hwmon/tmp464.c b/drivers/hwmon/tmp464.c new file mode 100644 index 000000000000..7814f39bd1a3 --- /dev/null +++ b/drivers/hwmon/tmp464.c @@ -0,0 +1,712 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* Driver for the Texas Instruments TMP464 SMBus temperature sensor IC. + * Supported models: TMP464, TMP468 + + * Copyright (C) 2022 Agathe Porte <agathe.porte@nokia.com> + * Preliminary support by: + * Lionel Pouliquen <lionel.lp.pouliquen@nokia.com> + */ + +#include <linux/err.h> +#include <linux/hwmon.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b, I2C_CLIENT_END }; + +#define TMP464_NUM_CHANNELS 5 /* chan 0 is internal, 1-4 are remote */ +#define TMP468_NUM_CHANNELS 9 /* chan 0 is internal, 1-8 are remote */ + +#define MAX_CHANNELS 9 + +#define TMP464_TEMP_REG(channel) (channel) +#define TMP464_TEMP_OFFSET_REG(channel) (0x40 + ((channel) - 1) * 8) +#define TMP464_N_FACTOR_REG(channel) (0x41 + ((channel) - 1) * 8) + +static const u8 TMP464_THERM_LIMIT[MAX_CHANNELS] = { + 0x39, 0x42, 0x4A, 0x52, 0x5A, 0x62, 0x6a, 0x72, 0x7a }; +static const u8 TMP464_THERM2_LIMIT[MAX_CHANNELS] = { + 0x3A, 0x43, 0x4B, 0x53, 0x5B, 0x63, 0x6b, 0x73, 0x7b }; + +#define TMP464_THERM_STATUS_REG 0x21 +#define TMP464_THERM2_STATUS_REG 0x22 +#define TMP464_REMOTE_OPEN_REG 0x23 +#define TMP464_CONFIG_REG 0x30 +#define TMP464_TEMP_HYST_REG 0x38 +#define TMP464_LOCK_REG 0xc4 + +/* Identification */ +#define TMP464_MANUFACTURER_ID_REG 0xFE +#define TMP464_DEVICE_ID_REG 0xFF + +/* Flags */ +#define TMP464_CONFIG_SHUTDOWN BIT(5) +#define TMP464_CONFIG_RANGE 0x04 +#define TMP464_CONFIG_REG_REN(x) (BIT(7 + (x))) +#define TMP464_CONFIG_REG_REN_MASK GENMASK(15, 7) +#define TMP464_CONFIG_CONVERSION_RATE_B0 2 +#define TMP464_CONFIG_CONVERSION_RATE_B2 4 +#define TMP464_CONFIG_CONVERSION_RATE_MASK GENMASK(TMP464_CONFIG_CONVERSION_RATE_B2, \ + TMP464_CONFIG_CONVERSION_RATE_B0) + +#define TMP464_UNLOCK_VAL 0xeb19 +#define TMP464_LOCK_VAL 0x5ca6 +#define TMP464_LOCKED 0x8000 + +/* Manufacturer / Device ID's */ +#define TMP464_MANUFACTURER_ID 0x5449 +#define TMP464_DEVICE_ID 0x1468 +#define TMP468_DEVICE_ID 0x0468 + +static const struct i2c_device_id tmp464_id[] = { + { "tmp464", TMP464_NUM_CHANNELS }, + { "tmp468", TMP468_NUM_CHANNELS }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tmp464_id); + +static const struct of_device_id __maybe_unused tmp464_of_match[] = { + { + .compatible = "ti,tmp464", + .data = (void *)TMP464_NUM_CHANNELS + }, + { + .compatible = "ti,tmp468", + .data = (void *)TMP468_NUM_CHANNELS + }, + {}, +}; +MODULE_DEVICE_TABLE(of, tmp464_of_match); + +struct tmp464_channel { + const char *label; + bool enabled; +}; + +struct tmp464_data { + struct regmap *regmap; + struct mutex update_lock; + int channels; + s16 config_orig; + u16 open_reg; + unsigned long last_updated; + bool valid; + int update_interval; + struct tmp464_channel channel[MAX_CHANNELS]; +}; + +static int temp_from_reg(s16 reg) +{ + return DIV_ROUND_CLOSEST((reg >> 3) * 625, 10); +} + +static s16 temp_to_limit_reg(long temp) +{ + return DIV_ROUND_CLOSEST(temp, 500) << 6; +} + +static s16 temp_to_offset_reg(long temp) +{ + return DIV_ROUND_CLOSEST(temp * 10, 625) << 3; +} + +static int tmp464_enable_channels(struct tmp464_data *data) +{ + struct regmap *regmap = data->regmap; + u16 enable = 0; + int i; + + for (i = 0; i < data->channels; i++) + if (data->channel[i].enabled) + enable |= TMP464_CONFIG_REG_REN(i); + + return regmap_update_bits(regmap, TMP464_CONFIG_REG, TMP464_CONFIG_REG_REN_MASK, enable); +} + +static int tmp464_chip_read(struct device *dev, u32 attr, int channel, long *val) +{ + struct tmp464_data *data = dev_get_drvdata(dev); + + switch (attr) { + case hwmon_chip_update_interval: + *val = data->update_interval; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static int tmp464_temp_read(struct device *dev, u32 attr, int channel, long *val) +{ + struct tmp464_data *data = dev_get_drvdata(dev); + struct regmap *regmap = data->regmap; + unsigned int regval, regval2; + int err = 0; + + mutex_lock(&data->update_lock); + + switch (attr) { + case hwmon_temp_max_alarm: + err = regmap_read(regmap, TMP464_THERM_STATUS_REG, ®val); + if (err < 0) + break; + *val = !!(regval & BIT(channel + 7)); + break; + case hwmon_temp_crit_alarm: + err = regmap_read(regmap, TMP464_THERM2_STATUS_REG, ®val); + if (err < 0) + break; + *val = !!(regval & BIT(channel + 7)); + break; + case hwmon_temp_fault: + /* + * The chip clears TMP464_REMOTE_OPEN_REG after it is read + * and only updates it after the next measurement cycle is + * complete. That means we have to cache the value internally + * for one measurement cycle and report the cached value. + */ + if (!data->valid || time_after(jiffies, data->last_updated + + msecs_to_jiffies(data->update_interval))) { + err = regmap_read(regmap, TMP464_REMOTE_OPEN_REG, ®val); + if (err < 0) + break; + data->open_reg = regval; + data->last_updated = jiffies; + data->valid = true; + } + *val = !!(data->open_reg & BIT(channel + 7)); + break; + case hwmon_temp_max_hyst: + err = regmap_read(regmap, TMP464_THERM_LIMIT[channel], ®val); + if (err < 0) + break; + err = regmap_read(regmap, TMP464_TEMP_HYST_REG, ®val2); + if (err < 0) + break; + regval -= regval2; + *val = temp_from_reg(regval); + break; + case hwmon_temp_max: + err = regmap_read(regmap, TMP464_THERM_LIMIT[channel], ®val); + if (err < 0) + break; + *val = temp_from_reg(regval); + break; + case hwmon_temp_crit_hyst: + err = regmap_read(regmap, TMP464_THERM2_LIMIT[channel], ®val); + if (err < 0) + break; + err = regmap_read(regmap, TMP464_TEMP_HYST_REG, ®val2); + if (err < 0) + break; + regval -= regval2; + *val = temp_from_reg(regval); + break; + case hwmon_temp_crit: + err = regmap_read(regmap, TMP464_THERM2_LIMIT[channel], ®val); + if (err < 0) + break; + *val = temp_from_reg(regval); + break; + case hwmon_temp_offset: + err = regmap_read(regmap, TMP464_TEMP_OFFSET_REG(channel), ®val); + if (err < 0) + break; + *val = temp_from_reg(regval); + break; + case hwmon_temp_input: + if (!data->channel[channel].enabled) { + err = -ENODATA; + break; + } + err = regmap_read(regmap, TMP464_TEMP_REG(channel), ®val); + if (err < 0) + break; + *val = temp_from_reg(regval); + break; + case hwmon_temp_enable: + *val = data->channel[channel].enabled; + break; + default: + err = -EOPNOTSUPP; + break; + } + + mutex_unlock(&data->update_lock); + + return err; +} + +static int tmp464_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_chip: + return tmp464_chip_read(dev, attr, channel, val); + case hwmon_temp: + return tmp464_temp_read(dev, attr, channel, val); + default: + return -EOPNOTSUPP; + } +} + +static int tmp464_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + struct tmp464_data *data = dev_get_drvdata(dev); + + *str = data->channel[channel].label; + + return 0; +} + +static int tmp464_set_convrate(struct tmp464_data *data, long interval) +{ + int rate; + + /* + * For valid rates, interval in milli-seconds can be calculated as + * interval = 125 << (7 - rate); + * or + * interval = (1 << (7 - rate)) * 125; + * The rate is therefore + * rate = 7 - __fls(interval / 125); + * and the rounded rate is + * rate = 7 - __fls(interval * 4 / (125 * 3)); + * Use clamp_val() to avoid overflows, and to ensure valid input + * for __fls. + */ + interval = clamp_val(interval, 125, 16000); + rate = 7 - __fls(interval * 4 / (125 * 3)); + data->update_interval = 125 << (7 - rate); + + return regmap_update_bits(data->regmap, TMP464_CONFIG_REG, + TMP464_CONFIG_CONVERSION_RATE_MASK, + rate << TMP464_CONFIG_CONVERSION_RATE_B0); +} + +static int tmp464_chip_write(struct tmp464_data *data, u32 attr, int channel, long val) +{ + switch (attr) { + case hwmon_chip_update_interval: + return tmp464_set_convrate(data, val); + default: + return -EOPNOTSUPP; + } +} + +static int tmp464_temp_write(struct tmp464_data *data, u32 attr, int channel, long val) +{ + struct regmap *regmap = data->regmap; + unsigned int regval; + int err = 0; + + switch (attr) { + case hwmon_temp_max_hyst: + err = regmap_read(regmap, TMP464_THERM_LIMIT[0], ®val); + if (err < 0) + break; + val = clamp_val(val, -256000, 256000); /* prevent overflow/underflow */ + val = clamp_val(temp_from_reg(regval) - val, 0, 255000); + err = regmap_write(regmap, TMP464_TEMP_HYST_REG, + DIV_ROUND_CLOSEST(val, 1000) << 7); + break; + case hwmon_temp_max: + val = temp_to_limit_reg(clamp_val(val, -255000, 255500)); + err = regmap_write(regmap, TMP464_THERM_LIMIT[channel], val); + break; + case hwmon_temp_crit: + val = temp_to_limit_reg(clamp_val(val, -255000, 255500)); + err = regmap_write(regmap, TMP464_THERM2_LIMIT[channel], val); + break; + case hwmon_temp_offset: + val = temp_to_offset_reg(clamp_val(val, -128000, 127937)); + err = regmap_write(regmap, TMP464_TEMP_OFFSET_REG(channel), val); + break; + case hwmon_temp_enable: + data->channel[channel].enabled = !!val; + err = tmp464_enable_channels(data); + break; + default: + err = -EOPNOTSUPP; + break; + } + + return err; +} + +static int tmp464_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct tmp464_data *data = dev_get_drvdata(dev); + int err; + + mutex_lock(&data->update_lock); + + switch (type) { + case hwmon_chip: + err = tmp464_chip_write(data, attr, channel, val); + break; + case hwmon_temp: + err = tmp464_temp_write(data, attr, channel, val); + break; + default: + err = -EOPNOTSUPP; + break; + } + + mutex_unlock(&data->update_lock); + + return err; +} + +static umode_t tmp464_is_visible(const void *_data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct tmp464_data *data = _data; + + if (channel >= data->channels) + return 0; + + if (type == hwmon_chip) { + if (attr == hwmon_chip_update_interval) + return 0644; + return 0; + } + + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_max_alarm: + case hwmon_temp_crit_alarm: + case hwmon_temp_crit_hyst: + return 0444; + case hwmon_temp_enable: + case hwmon_temp_max: + case hwmon_temp_crit: + return 0644; + case hwmon_temp_max_hyst: + if (!channel) + return 0644; + return 0444; + case hwmon_temp_label: + if (data->channel[channel].label) + return 0444; + return 0; + case hwmon_temp_fault: + if (channel) + return 0444; + return 0; + case hwmon_temp_offset: + if (channel) + return 0644; + return 0; + default: + return 0; + } +} + +static void tmp464_restore_lock(void *regmap) +{ + regmap_write(regmap, TMP464_LOCK_REG, TMP464_LOCK_VAL); +} + +static void tmp464_restore_config(void *_data) +{ + struct tmp464_data *data = _data; + + regmap_write(data->regmap, TMP464_CONFIG_REG, data->config_orig); +} + +static int tmp464_init_client(struct device *dev, struct tmp464_data *data) +{ + struct regmap *regmap = data->regmap; + unsigned int regval; + int err; + + err = regmap_read(regmap, TMP464_LOCK_REG, ®val); + if (err) + return err; + if (regval == TMP464_LOCKED) { + /* Explicitly unlock chip if it is locked */ + err = regmap_write(regmap, TMP464_LOCK_REG, TMP464_UNLOCK_VAL); + if (err) + return err; + /* and lock it again when unloading the driver */ + err = devm_add_action_or_reset(dev, tmp464_restore_lock, regmap); + if (err) + return err; + } + + err = regmap_read(regmap, TMP464_CONFIG_REG, ®val); + if (err) + return err; + data->config_orig = regval; + err = devm_add_action_or_reset(dev, tmp464_restore_config, data); + if (err) + return err; + + /* Default to 500 ms update interval */ + err = regmap_update_bits(regmap, TMP464_CONFIG_REG, + TMP464_CONFIG_CONVERSION_RATE_MASK | TMP464_CONFIG_SHUTDOWN, + BIT(TMP464_CONFIG_CONVERSION_RATE_B0) | + BIT(TMP464_CONFIG_CONVERSION_RATE_B2)); + if (err) + return err; + + data->update_interval = 500; + + return tmp464_enable_channels(data); +} + +static int tmp464_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + char *name, *chip; + int reg; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + reg = i2c_smbus_read_word_swapped(client, TMP464_MANUFACTURER_ID_REG); + if (reg < 0) + return reg; + if (reg != TMP464_MANUFACTURER_ID) + return -ENODEV; + + /* Check for "always return zero" bits */ + reg = i2c_smbus_read_word_swapped(client, TMP464_THERM_STATUS_REG); + if (reg < 0) + return reg; + if (reg & 0x1f) + return -ENODEV; + reg = i2c_smbus_read_word_swapped(client, TMP464_THERM2_STATUS_REG); + if (reg < 0) + return reg; + if (reg & 0x1f) + return -ENODEV; + + reg = i2c_smbus_read_word_swapped(client, TMP464_DEVICE_ID_REG); + if (reg < 0) + return reg; + switch (reg) { + case TMP464_DEVICE_ID: + name = "tmp464"; + chip = "TMP464"; + break; + case TMP468_DEVICE_ID: + name = "tmp468"; + chip = "TMP468"; + break; + default: + return -ENODEV; + } + + strscpy(info->type, name, I2C_NAME_SIZE); + dev_info(&adapter->dev, "Detected TI %s chip at 0x%02x\n", chip, client->addr); + + return 0; +} + +static int tmp464_probe_child_from_dt(struct device *dev, + struct device_node *child, + struct tmp464_data *data) + +{ + struct regmap *regmap = data->regmap; + u32 channel; + s32 nfactor; + int err; + + err = of_property_read_u32(child, "reg", &channel); + if (err) { + dev_err(dev, "missing reg property of %pOFn\n", child); + return err; + } + + if (channel >= data->channels) { + dev_err(dev, "invalid reg %d of %pOFn\n", channel, child); + return -EINVAL; + } + + of_property_read_string(child, "label", &data->channel[channel].label); + + data->channel[channel].enabled = of_device_is_available(child); + + err = of_property_read_s32(child, "ti,n-factor", &nfactor); + if (err && err != -EINVAL) + return err; + if (!err) { + if (channel == 0) { + dev_err(dev, "n-factor can't be set for internal channel\n"); + return -EINVAL; + } + if (nfactor > 127 || nfactor < -128) { + dev_err(dev, "n-factor for channel %d invalid (%d)\n", + channel, nfactor); + return -EINVAL; + } + err = regmap_write(regmap, TMP464_N_FACTOR_REG(channel), + (nfactor << 8) & 0xff00); + if (err) + return err; + } + + return 0; +} + +static int tmp464_probe_from_dt(struct device *dev, struct tmp464_data *data) +{ + const struct device_node *np = dev->of_node; + struct device_node *child; + int err; + + for_each_child_of_node(np, child) { + if (strcmp(child->name, "channel")) + continue; + + err = tmp464_probe_child_from_dt(dev, child, data); + if (err) { + of_node_put(child); + return err; + } + } + + return 0; +} + +static const struct hwmon_ops tmp464_ops = { + .is_visible = tmp464_is_visible, + .read = tmp464_read, + .read_string = tmp464_read_string, + .write = tmp464_write, +}; + +static const struct hwmon_channel_info *tmp464_info[] = { + HWMON_CHANNEL_INFO(chip, + HWMON_C_UPDATE_INTERVAL), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST | HWMON_T_CRIT | + HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM | HWMON_T_CRIT_ALARM | + HWMON_T_LABEL | HWMON_T_ENABLE, + HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM | + HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE, + HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM | + HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE, + HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM | + HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE, + HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM | + HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE, + HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM | + HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE, + HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM | + HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE, + HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM | + HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE, + HWMON_T_INPUT | HWMON_T_OFFSET | HWMON_T_MAX | HWMON_T_MAX_HYST | + HWMON_T_CRIT | HWMON_T_CRIT_HYST | HWMON_T_MAX_ALARM | + HWMON_T_CRIT_ALARM | HWMON_T_FAULT | HWMON_T_LABEL | HWMON_T_ENABLE), + NULL +}; + +static const struct hwmon_chip_info tmp464_chip_info = { + .ops = &tmp464_ops, + .info = tmp464_info, +}; + +/* regmap */ + +static bool tmp464_is_volatile_reg(struct device *dev, unsigned int reg) +{ + return (reg < TMP464_TEMP_REG(TMP468_NUM_CHANNELS) || + reg == TMP464_THERM_STATUS_REG || + reg == TMP464_THERM2_STATUS_REG || + reg == TMP464_REMOTE_OPEN_REG); +} + +static const struct regmap_config tmp464_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = TMP464_DEVICE_ID_REG, + .volatile_reg = tmp464_is_volatile_reg, + .val_format_endian = REGMAP_ENDIAN_BIG, + .cache_type = REGCACHE_RBTREE, + .use_single_read = true, + .use_single_write = true, +}; + +static int tmp464_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct device *hwmon_dev; + struct tmp464_data *data; + int i, err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) { + dev_err(&client->dev, "i2c functionality check failed\n"); + return -ENODEV; + } + data = devm_kzalloc(dev, sizeof(struct tmp464_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + mutex_init(&data->update_lock); + + if (dev->of_node) + data->channels = (int)(unsigned long)of_device_get_match_data(&client->dev); + else + data->channels = i2c_match_id(tmp464_id, client)->driver_data; + + data->regmap = devm_regmap_init_i2c(client, &tmp464_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + + for (i = 0; i < data->channels; i++) + data->channel[i].enabled = true; + + err = tmp464_init_client(dev, data); + if (err) + return err; + + if (dev->of_node) { + err = tmp464_probe_from_dt(dev, data); + if (err) + return err; + } + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, + data, &tmp464_chip_info, NULL); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static struct i2c_driver tmp464_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "tmp464", + .of_match_table = of_match_ptr(tmp464_of_match), + }, + .probe_new = tmp464_probe, + .id_table = tmp464_id, + .detect = tmp464_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(tmp464_driver); + +MODULE_AUTHOR("Agathe Porte <agathe.porte@nokia.com>"); +MODULE_DESCRIPTION("Texas Instruments TMP464 temperature sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/vexpress-hwmon.c b/drivers/hwmon/vexpress-hwmon.c index 44d798be3d59..2ac5fb96bba4 100644 --- a/drivers/hwmon/vexpress-hwmon.c +++ b/drivers/hwmon/vexpress-hwmon.c @@ -207,7 +207,6 @@ MODULE_DEVICE_TABLE(of, vexpress_hwmon_of_match); static int vexpress_hwmon_probe(struct platform_device *pdev) { - const struct of_device_id *match; struct vexpress_hwmon_data *data; const struct vexpress_hwmon_type *type; @@ -216,10 +215,9 @@ static int vexpress_hwmon_probe(struct platform_device *pdev) return -ENOMEM; platform_set_drvdata(pdev, data); - match = of_match_device(vexpress_hwmon_of_match, &pdev->dev); - if (!match) + type = of_device_get_match_data(&pdev->dev); + if (!type) return -ENODEV; - type = match->data; data->reg = devm_regmap_init_vexpress_config(&pdev->dev); if (IS_ERR(data->reg)) diff --git a/drivers/hwmon/xgene-hwmon.c b/drivers/hwmon/xgene-hwmon.c index 30aae8642069..5cde837bfd09 100644 --- a/drivers/hwmon/xgene-hwmon.c +++ b/drivers/hwmon/xgene-hwmon.c @@ -659,8 +659,10 @@ static int xgene_hwmon_probe(struct platform_device *pdev) acpi_id = acpi_match_device(pdev->dev.driver->acpi_match_table, &pdev->dev); - if (!acpi_id) - return -EINVAL; + if (!acpi_id) { + rc = -EINVAL; + goto out_mbox_free; + } version = (int)acpi_id->driver_data; |