diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -224,6 +224,7 @@ config SENSORS_ADT7462 config SENSORS_ADT7470 tristate "Analog Devices ADT7470" depends on I2C + depends on PWM || PWM=n select REGMAP_I2C help If you say yes here you get support for the Analog Devices @@ -292,11 +293,11 @@ config SENSORS_ASC7621 will be called asc7621. config SENSORS_ASUS_ROG_RYUJIN - tristate "ASUS ROG RYUJIN II 360 hardware monitoring driver" + tristate "ASUS ROG RYUJIN hardware monitoring driver" depends on HID help If you say yes here you get support for the fans and sensors of - the ASUS ROG RYUJIN II 360 AIO CPU liquid cooler. + supported ASUS ROG RYUJIN II and III AIO CPU liquid coolers. This driver can also be built as a module. If so, the module will be called asus_rog_ryujin. @@ -335,6 +336,17 @@ config SENSORS_K10TEMP This driver can also be built as a module. If so, the module will be called k10temp. +config SENSORS_KB9002 + tristate "Kandou KB9002 PCIe retimer" + depends on I2C + help + If you say yes here you get support for the integrated + temperature sensor and firmware version readout on Kandou + KB9002 PCIe 5.0 retimers, accessed over SMBus. + + This driver can also be built as a module. If so, the module + will be called kb9002. + config SENSORS_KBATT tristate "KEBA battery controller support" depends on KEBA_CP500 @@ -403,7 +415,6 @@ config SENSORS_ARCTIC_FAN_CONTROLLER config SENSORS_ARM_SCMI tristate "ARM SCMI Sensors" depends on ARM_SCMI_PROTOCOL - depends on THERMAL || !THERMAL_OF help This driver provides support for temperature, voltage, current and power sensors available on SCMI based platforms. The actual @@ -415,7 +426,6 @@ config SENSORS_ARM_SCMI config SENSORS_ARM_SCPI tristate "ARM SCPI Sensors" depends on ARM_SCPI_PROTOCOL - depends on THERMAL || !THERMAL_OF help This driver provides support for temperature, voltage, current and power sensors available on ARM Ltd's SCP based platforms. The @@ -435,7 +445,6 @@ config SENSORS_ASB100 config SENSORS_ASPEED tristate "ASPEED AST2400/AST2500 PWM and Fan tach driver" depends on ARCH_ASPEED || COMPILE_TEST - depends on THERMAL || THERMAL=n select REGMAP help This driver provides support for ASPEED AST2400/AST2500 PWM @@ -506,8 +515,8 @@ config SENSORS_CORSAIR_PSU If you say yes here you get support for Corsair PSUs with a HID interface. Currently this driver supports the (RM/HX)550i, (RM/HX)650i, - (RM/HX)750i, (RM/HX)850i, (RM/HX)1000i and HX1200i power supplies - by Corsair. + (RM/HX)750i, (RM/HX)850i, (RM/HX)1000i, HX1200i and HX1500i power + supplies by Corsair. This driver can also be built as a module. If so, the module will be called corsair-psu. @@ -788,7 +797,6 @@ config SENSORS_G762 config SENSORS_GPIO_FAN tristate "GPIO fan" depends on GPIOLIB || COMPILE_TEST - depends on THERMAL || THERMAL=n help If you say yes here you get support for fans connected to GPIO lines. @@ -802,7 +810,7 @@ config SENSORS_GXP_FAN_CTRL If you say yes here you get support for GXP fan control functionality. The GXP controls fan function via the CPLD through the use of PWM - registers. This driver reports status and pwm setting of the fans. + registers. This driver enables pwm setting of the fans. config SENSORS_HIH6130 tristate "Honeywell Humidicon HIH-6130 humidity/temperature sensor" @@ -942,6 +950,19 @@ config SENSORS_JC42 This driver can also be built as a module. If so, the module will be called jc42. +config SENSORS_POLARFIRE_SOC_TVS + tristate "PolarFire SoC (MPFS) temperature and voltage sensor" + depends on POLARFIRE_SOC_SYSCONS || COMPILE_TEST + depends on MFD_SYSCON + help + This driver adds support for the PolarFire SoC (MPFS) Temperature and + Voltage Sensor. + + To compile this driver as a module, choose M here. the + module will be called tvs-mpfs. + + If unsure, say N. + config SENSORS_POWERZ tristate "ChargerLAB POWER-Z USB-C tester" depends on USB @@ -1357,7 +1378,6 @@ config SENSORS_MAX6639 config SENSORS_MAX6650 tristate "Maxim MAX6650 sensor chip" depends on I2C - depends on THERMAL || THERMAL=n help If you say yes here you get support for the MAX6650 / MAX6651 sensor chips. @@ -1732,7 +1752,6 @@ config SENSORS_PC87427 config SENSORS_NTC_THERMISTOR tristate "NTC thermistor support" depends on IIO - depends on THERMAL || !THERMAL_OF help This driver supports NTC thermistors sensor reading and its interpretation. The driver can also monitor the temperature and @@ -1933,7 +1952,6 @@ config SENSORS_PT5161L config SENSORS_PWM_FAN tristate "PWM fan" depends on PWM || COMPILE_TEST - depends on THERMAL || THERMAL=n help If you say yes here you get support for fans connected to PWM lines. The driver uses the generic PWM interface, thus it will work on a @@ -1945,7 +1963,6 @@ config SENSORS_PWM_FAN config SENSORS_QNAP_MCU_HWMON tristate "QNAP MCU hardware monitoring" depends on MFD_QNAP_MCU - depends on THERMAL || THERMAL=n help Say yes here to enable support for fan and temperature sensor connected to a QNAP MCU, as found in a number of QNAP network @@ -2010,8 +2027,9 @@ config SENSORS_SHT3x depends on I2C select CRC8 help - If you say yes here you get support for the Sensiron SHT30, SHT31 and - SHT85 humidity and temperature sensors. + If you say yes here you get support for the Sensiron SHT30, SHT31, + SHT85 and the compatible GXCAS GXHT30 humidity and temperature + sensors. This driver can also be built as a module. If so, the module will be called sht3x. @@ -2070,6 +2088,17 @@ config SENSORS_DME1737 This driver can also be built as a module. If so, the module will be called dme1737. +config SENSORS_EIC7700_PVT + tristate "Eswin EIC7700 Voltage, Temperature sensor driver" + depends on ARCH_ESWIN || COMPILE_TEST + select POLYNOMIAL + help + If you say yes here you get support for Eswin EIC7700 PVT sensor + embedded into the SoC. + + This driver can also be built as a module. If so, the module will be + called eic7700-pvt. + config SENSORS_EMC1403 tristate "SMSC EMC1403/23 thermal sensor" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -72,6 +72,7 @@ obj-$(CONFIG_SENSORS_DME1737) += dme1737.o obj-$(CONFIG_SENSORS_DRIVETEMP) += drivetemp.o obj-$(CONFIG_SENSORS_DS620) += ds620.o obj-$(CONFIG_SENSORS_DS1621) += ds1621.o +obj-$(CONFIG_SENSORS_EIC7700_PVT) += eic7700-pvt.o obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o obj-$(CONFIG_SENSORS_EMC1812) += emc1812.o obj-$(CONFIG_SENSORS_EMC2103) += emc2103.o @@ -112,6 +113,7 @@ obj-$(CONFIG_SENSORS_IT87) += it87.o obj-$(CONFIG_SENSORS_JC42) += jc42.o obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o +obj-$(CONFIG_SENSORS_KB9002) += kb9002.o obj-$(CONFIG_SENSORS_KBATT) += kbatt.o obj-$(CONFIG_SENSORS_KFAN) += kfan.o obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o @@ -197,6 +199,7 @@ 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 +obj-$(CONFIG_SENSORS_POLARFIRE_SOC_TVS) += tvs-mpfs.o obj-$(CONFIG_SENSORS_POWERZ) += powerz.o obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o obj-$(CONFIG_SENSORS_PROM21_XHCI) += prom21-xhci.o diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c --- a/drivers/hwmon/acpi_power_meter.c +++ b/drivers/hwmon/acpi_power_meter.c @@ -21,8 +21,6 @@ #include #define ACPI_POWER_METER_NAME "power_meter" -#define ACPI_POWER_METER_DEVICE_NAME "Power Meter" -#define ACPI_POWER_METER_CLASS "pwr_meter_resource" #define NUM_SENSORS 17 @@ -877,7 +875,7 @@ static void acpi_power_meter_notify(acpi_handle handle, u32 event, void *data) break; } - acpi_bus_generate_netlink_event(ACPI_POWER_METER_CLASS, + acpi_bus_generate_netlink_event("pwr_meter_resource", dev_name(&resource->acpi_dev->dev), event, 0); } @@ -899,8 +897,6 @@ static int acpi_power_meter_probe(struct platform_device *pdev) resource->sensors_valid = 0; resource->acpi_dev = device; mutex_init(&resource->lock); - strscpy(acpi_device_name(device), ACPI_POWER_METER_DEVICE_NAME); - strscpy(acpi_device_class(device), ACPI_POWER_METER_CLASS); platform_set_drvdata(pdev, resource); diff --git a/drivers/hwmon/adt7470.c b/drivers/hwmon/adt7470.c --- a/drivers/hwmon/adt7470.c +++ b/drivers/hwmon/adt7470.c @@ -22,6 +22,9 @@ #include #include #include +#include +#include +#include /* Addresses to scan */ static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END }; @@ -100,6 +103,7 @@ static const unsigned short normal_i2c[] = { 0x2C, 0x2E, 0x2F, I2C_CLIENT_END }; #define ADT7470_REG_FAN_MAX(x) (ADT7470_REG_FAN_MAX_BASE_ADDR + ((x) * 2)) #define ADT7470_PWM_COUNT 4 +#define ADT7470_PWM_MAX 255 #define ADT7470_REG_PWM(x) (ADT7470_REG_PWM_BASE_ADDR + (x)) #define ADT7470_REG_PWM_MAX(x) (ADT7470_REG_PWM_MAX_BASE_ADDR + (x)) #define ADT7470_REG_PWM_MIN(x) (ADT7470_REG_PWM_MIN_BASE_ADDR + (x)) @@ -163,6 +167,7 @@ struct adt7470_data { char limits_valid; unsigned long sensors_last_updated; /* In jiffies */ unsigned long limits_last_updated; /* In jiffies */ + bool use_pwm_framework; int num_temp_sensors; /* -1 = probe */ int temperatures_probed; @@ -850,7 +855,7 @@ static int adt7470_pwm_write(struct device *dev, u32 attr, int channel, long val switch (attr) { case hwmon_pwm_input: - val = clamp_val(val, 0, 255); + val = clamp_val(val, 0, ADT7470_PWM_MAX); mutex_lock(&data->lock); err = regmap_write(data->regmap, ADT7470_REG_PWM(channel), val); @@ -886,6 +891,133 @@ static int adt7470_pwm_write(struct device *dev, u32 attr, int channel, long val return err; } +struct adt7470_pwm_wfhw { + u8 val; +}; + +static int adt7470_pwm_round_waveform_tohw(struct pwm_chip *chip, + struct pwm_device *pwm, + const struct pwm_waveform *wf, + void *_wfhw) +{ + struct adt7470_data *data = pwmchip_get_drvdata(chip); + struct adt7470_pwm_wfhw *wfhw = _wfhw; + u64 actual_period; + + if (wf->duty_length_ns == 0) { + wfhw->val = 0; + return 0; + } + + /* + * The PWM frequency (period) is a single chip-wide setting shared by + * all 4 channels, so it cannot be changed on a per-pwm_device basis + * through this API. The duty cycle is rounded against the currently + * configured hardware period rather than the period requested in + * @wf; round_waveform_fromhw() reports the actual resulting + * waveform back so the core/consumer can detect a mismatch. + */ + actual_period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, data->pwm_freq); + + if (actual_period > wf->period_length_ns) + /* period too short */ + return 1; + + if (wf->duty_length_ns >= actual_period) { + wfhw->val = ADT7470_PWM_MAX; + } else { + wfhw->val = mul_u64_u64_div_u64(wf->duty_length_ns, + ADT7470_PWM_MAX, + actual_period); + } + + return 0; +} + +static int adt7470_pwm_round_waveform_fromhw(struct pwm_chip *chip, + struct pwm_device *pwm, + const void *_wfhw, + struct pwm_waveform *wf) +{ + struct adt7470_data *data = pwmchip_get_drvdata(chip); + const struct adt7470_pwm_wfhw *wfhw = _wfhw; + + wf->period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, data->pwm_freq); + wf->duty_offset_ns = 0; + wf->duty_length_ns = DIV_ROUND_UP_ULL((u64)wfhw->val * wf->period_length_ns, + ADT7470_PWM_MAX); + return 0; +} + +static int adt7470_pwm_read_waveform(struct pwm_chip *chip, + struct pwm_device *pwm, + void *_wfhw) +{ + struct adt7470_data *data = pwmchip_get_drvdata(chip); + struct device *dev = regmap_get_device(data->regmap); + struct adt7470_pwm_wfhw *wfhw = _wfhw; + + data = adt7470_update_device(dev); + if (IS_ERR(data)) + return PTR_ERR(data); + + /* + * No lock needed: like the other hwmon_ops read callbacks in this + * driver (e.g. adt7470_pwm_read()), this only does a single byte + * read from the cache populated by adt7470_update_device(). + */ + wfhw->val = data->pwm[pwm->hwpwm]; + + return 0; +} + +static int adt7470_pwm_write_waveform(struct pwm_chip *chip, + struct pwm_device *pwm, + const void *_wfhw) +{ + struct adt7470_data *data = pwmchip_get_drvdata(chip); + const struct adt7470_pwm_wfhw *wfhw = _wfhw; + unsigned int pwm_auto_reg_mask; + int err; + + if (pwm->hwpwm % 2) + pwm_auto_reg_mask = ADT7470_PWM2_AUTO_MASK; + else + pwm_auto_reg_mask = ADT7470_PWM1_AUTO_MASK; + + guard(mutex)(&data->lock); + + if (data->pwm[pwm->hwpwm] == wfhw->val && + data->pwm_automatic[pwm->hwpwm] == 0) + return 0; + + /* Put the PWM channel in manual mode before updating it. */ + err = regmap_update_bits(data->regmap, + ADT7470_REG_PWM_CFG(pwm->hwpwm), + pwm_auto_reg_mask, 0); + if (err < 0) + return err; + + data->pwm_automatic[pwm->hwpwm] = 0; + + err = regmap_write(data->regmap, + ADT7470_REG_PWM(pwm->hwpwm), wfhw->val); + if (err < 0) + return err; + + data->pwm[pwm->hwpwm] = wfhw->val; + + return 0; +} + +static const struct pwm_ops adt7470_pwm_ops = { + .sizeof_wfhw = sizeof(struct adt7470_pwm_wfhw), + .round_waveform_tohw = adt7470_pwm_round_waveform_tohw, + .round_waveform_fromhw = adt7470_pwm_round_waveform_fromhw, + .read_waveform = adt7470_pwm_read_waveform, + .write_waveform = adt7470_pwm_write_waveform, +}; + static ssize_t pwm_max_show(struct device *dev, struct device_attribute *devattr, char *buf) { @@ -910,7 +1042,7 @@ static ssize_t pwm_max_store(struct device *dev, if (kstrtol(buf, 10, &temp)) return -EINVAL; - temp = clamp_val(temp, 0, 255); + temp = clamp_val(temp, 0, ADT7470_PWM_MAX); mutex_lock(&data->lock); data->pwm_max[attr->index] = temp; @@ -945,7 +1077,7 @@ static ssize_t pwm_min_store(struct device *dev, if (kstrtol(buf, 10, &temp)) return -EINVAL; - temp = clamp_val(temp, 0, 255); + temp = clamp_val(temp, 0, ADT7470_PWM_MAX); mutex_lock(&data->lock); data->pwm_min[attr->index] = temp; @@ -1106,6 +1238,10 @@ static struct attribute *adt7470_attrs[] = { &dev_attr_alarm_mask.attr, &dev_attr_num_temp_sensors.attr, &dev_attr_auto_update_interval.attr, + NULL +}; + +static struct attribute *adt7470_pwm_attrs[] = { &sensor_dev_attr_force_pwm_max.dev_attr.attr, &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr, @@ -1130,7 +1266,32 @@ static struct attribute *adt7470_attrs[] = { NULL }; -ATTRIBUTE_GROUPS(adt7470); +static const struct attribute_group adt7470_group = { + .attrs = adt7470_attrs, +}; + +static umode_t adt7470_pwm_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = kobj_to_dev(kobj); + struct adt7470_data *data = dev_get_drvdata(dev); + + if (data->use_pwm_framework) + return 0; + + return attr->mode; +} + +static const struct attribute_group adt7470_pwm_group = { + .attrs = adt7470_pwm_attrs, + .is_visible = adt7470_pwm_is_visible, +}; + +static const struct attribute_group *adt7470_groups[] = { + &adt7470_group, + &adt7470_pwm_group, + NULL, +}; static int adt7470_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) @@ -1165,6 +1326,7 @@ static int adt7470_write(struct device *dev, enum hwmon_sensor_types type, u32 a static umode_t adt7470_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr, int channel) { + const struct adt7470_data *data = _data; umode_t mode = 0; switch (type) { @@ -1197,6 +1359,14 @@ static umode_t adt7470_is_visible(const void *_data, enum hwmon_sensor_types typ } break; case hwmon_pwm: + /* Hide all pwm attributes if this device is exposed to the PWM + * framework + */ + if (data->use_pwm_framework) { + mode = 0; + break; + } + switch (attr) { case hwmon_pwm_input: case hwmon_pwm_enable: @@ -1226,6 +1396,8 @@ static const struct hwmon_ops adt7470_hwmon_ops = { }; static const struct hwmon_channel_info * const adt7470_info[] = { + HWMON_CHANNEL_INFO(chip, + HWMON_C_REGISTER_TZ), HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM, HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_ALARM, @@ -1327,6 +1499,29 @@ static int adt7470_probe(struct i2c_client *client) data->pwm_freq = (u32)freq_val; + data->use_pwm_framework = false; + + if (device_property_present(dev, "#pwm-cells")) { + if (IS_REACHABLE(CONFIG_PWM)) { + struct pwm_chip *chip; + + chip = devm_pwmchip_alloc(dev, ADT7470_PWM_COUNT, 0); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + chip->ops = &adt7470_pwm_ops; + pwmchip_set_drvdata(chip, data); + + err = devm_pwmchip_add(dev, chip); + if (err) + return dev_err_probe(dev, err, "failed to register PWM chip\n"); + + data->use_pwm_framework = true; + } else { + dev_warn(dev, "#pwm-cells present but CONFIG_PWM disabled. HWMON PWM attributes not hidden.\n"); + } + } + /* Register sysfs hooks */ hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, &adt7470_chip_info, diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c --- a/drivers/hwmon/applesmc.c +++ b/drivers/hwmon/applesmc.c @@ -33,6 +33,7 @@ #include #include #include +#include /* data port used by Apple SMC */ #define APPLESMC_DATA_PORT 0x300 @@ -133,6 +134,7 @@ static struct applesmc_registers { bool init_complete; /* true when fully initialized */ struct applesmc_entry *cache; /* cached key entries */ const char **index; /* temperature key index */ + char fan_positions[10][17]; /* cached fan position labels */ } smcreg = { .mutex = __MUTEX_INITIALIZER(smcreg.mutex), }; @@ -143,8 +145,8 @@ static s16 rest_x; static s16 rest_y; static u8 backlight_state[2]; -static struct device *hwmon_dev; static struct input_dev *applesmc_idev; +static struct device *hwmon_dev; /* * Last index written to key_at_index sysfs file, and value to use for all other @@ -372,7 +374,8 @@ static const struct applesmc_entry *applesmc_get_entry_by_index(int index) __be32 be; int ret = 0; - if (cache->valid) + /* Pairs with smp_store_release() to ensure cache contents are visible */ + if (smp_load_acquire(&cache->valid)) return cache; mutex_lock(&smcreg.mutex); @@ -391,7 +394,8 @@ static const struct applesmc_entry *applesmc_get_entry_by_index(int index) cache->len = info[0]; memcpy(cache->type, &info[1], 4); cache->flags = info[5]; - cache->valid = true; + /* Pairs with smp_load_acquire() to commit cache contents before setting valid */ + smp_store_release(&cache->valid, true); out: mutex_unlock(&smcreg.mutex); @@ -566,7 +570,7 @@ static int applesmc_init_smcreg_try(void) { struct applesmc_registers *s = &smcreg; bool left_light_sensor = false, right_light_sensor = false; - unsigned int count; + unsigned int count, i; u8 tmp[1]; int ret; @@ -597,6 +601,16 @@ static int applesmc_init_smcreg_try(void) if (s->fan_count > 10) s->fan_count = 10; + for (i = 0; i < s->fan_count; i++) { + char newkey[5]; + + scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, i); + ret = applesmc_read_key(newkey, s->fan_positions[i], 16); + s->fan_positions[i][16] = 0; + if (ret) + scnprintf(s->fan_positions[i], 17, " Fan %d", i); + } + ret = applesmc_get_lower_bound(&s->temp_begin, "T"); if (ret) return ret; @@ -808,33 +822,6 @@ static ssize_t applesmc_light_show(struct device *dev, return sysfs_emit(sysfsbuf, "(%d,%d)\n", left, right); } -/* Displays sensor key as label */ -static ssize_t applesmc_show_sensor_label(struct device *dev, - struct device_attribute *devattr, char *sysfsbuf) -{ - const char *key = smcreg.index[to_index(devattr)]; - - return sysfs_emit(sysfsbuf, "%s\n", key); -} - -/* Displays degree Celsius * 1000 */ -static ssize_t applesmc_show_temperature(struct device *dev, - struct device_attribute *devattr, char *sysfsbuf) -{ - const char *key = smcreg.index[to_index(devattr)]; - int ret; - s16 value; - int temp; - - ret = applesmc_read_s16(key, &value); - if (ret) - return ret; - - temp = 250 * (value >> 6); - - return sysfs_emit(sysfsbuf, "%d\n", temp); -} - static ssize_t applesmc_show_fan_speed(struct device *dev, struct device_attribute *attr, char *sysfsbuf) { @@ -854,99 +841,6 @@ static ssize_t applesmc_show_fan_speed(struct device *dev, return sysfs_emit(sysfsbuf, "%u\n", speed); } -static ssize_t applesmc_store_fan_speed(struct device *dev, - struct device_attribute *attr, - const char *sysfsbuf, size_t count) -{ - int ret; - unsigned long speed; - char newkey[5]; - u8 buffer[2]; - - if (kstrtoul(sysfsbuf, 10, &speed) < 0 || speed >= 0x4000) - return -EINVAL; /* Bigger than a 14-bit value */ - - scnprintf(newkey, sizeof(newkey), fan_speed_fmt[to_option(attr)], - to_index(attr)); - - buffer[0] = (speed >> 6) & 0xff; - buffer[1] = (speed << 2) & 0xff; - ret = applesmc_write_key(newkey, buffer, 2); - - if (ret) - return ret; - else - return count; -} - -static ssize_t applesmc_show_fan_manual(struct device *dev, - struct device_attribute *attr, char *sysfsbuf) -{ - int ret; - u16 manual = 0; - u8 buffer[2]; - - ret = applesmc_read_key(FANS_MANUAL, buffer, 2); - if (ret) - return ret; - - manual = ((buffer[0] << 8 | buffer[1]) >> to_index(attr)) & 0x01; - return sysfs_emit(sysfsbuf, "%d\n", manual); -} - -static ssize_t applesmc_store_fan_manual(struct device *dev, - struct device_attribute *attr, - const char *sysfsbuf, size_t count) -{ - int ret; - u8 buffer[2]; - unsigned long input; - u16 val; - - if (kstrtoul(sysfsbuf, 10, &input) < 0) - return -EINVAL; - - ret = applesmc_read_key(FANS_MANUAL, buffer, 2); - if (ret) - goto out; - - val = (buffer[0] << 8 | buffer[1]); - - if (input) - val = val | (0x01 << to_index(attr)); - else - val = val & ~(0x01 << to_index(attr)); - - buffer[0] = (val >> 8) & 0xFF; - buffer[1] = val & 0xFF; - - ret = applesmc_write_key(FANS_MANUAL, buffer, 2); - -out: - if (ret) - return ret; - else - return count; -} - -static ssize_t applesmc_show_fan_position(struct device *dev, - struct device_attribute *attr, char *sysfsbuf) -{ - int ret; - char newkey[5]; - u8 buffer[17]; - - scnprintf(newkey, sizeof(newkey), FAN_ID_FMT, to_index(attr)); - - ret = applesmc_read_key(newkey, buffer, 16); - buffer[16] = 0; - - if (ret) - return ret; - - return sysfs_emit(sysfsbuf, "%s\n", buffer + 4); -} - static ssize_t applesmc_calibrate_show(struct device *dev, struct device_attribute *attr, char *sysfsbuf) { @@ -1094,22 +988,7 @@ static struct applesmc_node_group light_sensor_group[] = { { } }; -static struct applesmc_node_group fan_group[] = { - { "fan%d_label", applesmc_show_fan_position }, - { "fan%d_input", applesmc_show_fan_speed, NULL, 0 }, - { "fan%d_min", applesmc_show_fan_speed, applesmc_store_fan_speed, 1 }, - { "fan%d_max", applesmc_show_fan_speed, NULL, 2 }, - { "fan%d_safe", applesmc_show_fan_speed, NULL, 3 }, - { "fan%d_output", applesmc_show_fan_speed, applesmc_store_fan_speed, 4 }, - { "fan%d_manual", applesmc_show_fan_manual, applesmc_store_fan_manual }, - { } -}; -static struct applesmc_node_group temp_group[] = { - { "temp%d_label", applesmc_show_sensor_label }, - { "temp%d_input", applesmc_show_temperature }, - { } -}; /* Module stuff */ @@ -1312,8 +1191,238 @@ static const struct dmi_system_id applesmc_whitelist[] __initconst = { }; MODULE_DEVICE_TABLE(dmi, applesmc_whitelist); +static struct applesmc_dev_attr *fan_safe_attrs; +static struct attribute **fan_safe_attr_list; +static struct attribute_group fan_safe_group; +static const struct attribute_group *applesmc_extra_groups[2]; + +static u32 *applesmc_temp_config; +static u32 *applesmc_fan_config; +static u32 *applesmc_pwm_config; +static struct hwmon_channel_info *applesmc_info_temp; +static struct hwmon_channel_info *applesmc_info_fan; +static struct hwmon_channel_info *applesmc_info_pwm; +static const struct hwmon_channel_info **applesmc_info_arr; +static struct hwmon_chip_info *applesmc_chip; + +static void applesmc_free_hwmon(void) +{ + kfree(applesmc_temp_config); + kfree(applesmc_fan_config); + kfree(applesmc_pwm_config); + kfree(applesmc_info_temp); + kfree(applesmc_info_fan); + kfree(applesmc_info_pwm); + kfree(applesmc_info_arr); + kfree(applesmc_chip); + kfree(fan_safe_attrs); + kfree(fan_safe_attr_list); +} + +static umode_t applesmc_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_input || attr == hwmon_temp_label) + return 0444; + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: + case hwmon_fan_label: + case hwmon_fan_max: + return 0444; + case hwmon_fan_min: + case hwmon_fan_target: + return 0644; + default: + break; + } + break; + case hwmon_pwm: + if (attr == hwmon_pwm_enable) + return 0644; + break; + default: + break; + } + return 0; +} + +static int applesmc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + int ret; + + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_input) { + const char *key = smcreg.index[channel]; + s16 value; + + ret = applesmc_read_s16(key, &value); + if (ret) + return ret; + *val = 250 * (value >> 6); + return 0; + } + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dAc", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + case hwmon_fan_min: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dMn", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + case hwmon_fan_max: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dMx", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + case hwmon_fan_target: { + char key[5]; + u8 buffer[2]; + + scnprintf(key, sizeof(key), "F%dTg", channel); + ret = applesmc_read_key(key, buffer, 2); + if (ret) + return ret; + *val = ((buffer[0] << 8 | buffer[1]) >> 2); + return 0; + } + default: + break; + } + break; + case hwmon_pwm: + if (attr == hwmon_pwm_enable) { + u8 buffer[2]; + + ret = applesmc_read_key(FANS_MANUAL, buffer, 2); + if (ret) + return ret; + *val = (((buffer[0] << 8 | buffer[1]) >> channel) & 0x01) ? 1 : 2; + return 0; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +static int applesmc_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + int ret; + + switch (type) { + case hwmon_fan: + if (attr == hwmon_fan_min || attr == hwmon_fan_target) { + char key[5]; + u8 buffer[2]; + const char *fmt = (attr == hwmon_fan_min) ? "F%dMn" : "F%dTg"; + + if (val < 0 || val >= 0x4000) + return -EINVAL; + scnprintf(key, sizeof(key), fmt, channel); + buffer[0] = (val >> 6) & 0xff; + buffer[1] = (val << 2) & 0xff; + return applesmc_write_key(key, buffer, 2); + } + break; + case hwmon_pwm: + if (attr == hwmon_pwm_enable) { + u8 buffer[2]; + u16 manual_val; + const struct applesmc_entry *entry; + + if (val != 1 && val != 2) + return -EINVAL; + + entry = applesmc_get_entry_by_key(FANS_MANUAL); + if (IS_ERR(entry)) + return PTR_ERR(entry); + + mutex_lock(&smcreg.mutex); + ret = read_smc(APPLESMC_READ_CMD, entry->key, buffer, 2); + if (ret) + goto out_unlock; + manual_val = (buffer[0] << 8 | buffer[1]); + if (val == 1) + manual_val |= (0x01 << channel); + else + manual_val &= ~(0x01 << channel); + buffer[0] = (manual_val >> 8) & 0xff; + buffer[1] = manual_val & 0xff; + ret = write_smc(APPLESMC_WRITE_CMD, entry->key, buffer, 2); +out_unlock: + mutex_unlock(&smcreg.mutex); + return ret; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +static int applesmc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_label) { + *str = smcreg.index[channel]; + return 0; + } + break; + case hwmon_fan: + if (attr == hwmon_fan_label) { + *str = smcreg.fan_positions[channel] + 4; + return 0; + } + break; + default: + break; + } + return -EOPNOTSUPP; +} + +static const struct hwmon_ops applesmc_hwmon_ops = { + .is_visible = applesmc_hwmon_is_visible, + .read = applesmc_hwmon_read, + .write = applesmc_hwmon_write, + .read_string = applesmc_hwmon_read_string, +}; + static int __init applesmc_init(void) { + int i; int ret; if (!dmi_check_system(applesmc_whitelist)) { @@ -1348,17 +1457,97 @@ static int __init applesmc_init(void) if (ret) goto out_smcreg; - ret = applesmc_create_nodes(fan_group, smcreg.fan_count); - if (ret) + /* allocate hwmon channel configs */ + applesmc_temp_config = kcalloc(smcreg.index_count + 1, + sizeof(*applesmc_temp_config), GFP_KERNEL); + applesmc_fan_config = kcalloc(smcreg.fan_count + 1, + sizeof(*applesmc_fan_config), GFP_KERNEL); + applesmc_pwm_config = kcalloc(smcreg.fan_count + 1, + sizeof(*applesmc_pwm_config), GFP_KERNEL); + if (!applesmc_temp_config || !applesmc_fan_config || !applesmc_pwm_config) { + ret = -ENOMEM; goto out_info; + } - ret = applesmc_create_nodes(temp_group, smcreg.index_count); - if (ret) - goto out_fans; + for (i = 0; i < smcreg.index_count; i++) + applesmc_temp_config[i] = HWMON_T_INPUT | HWMON_T_LABEL; + applesmc_temp_config[smcreg.index_count] = 0; + + for (i = 0; i < smcreg.fan_count; i++) { + applesmc_fan_config[i] = HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | + HWMON_F_MAX | HWMON_F_TARGET; + applesmc_pwm_config[i] = HWMON_PWM_ENABLE; + } + applesmc_fan_config[smcreg.fan_count] = 0; + applesmc_pwm_config[smcreg.fan_count] = 0; + + applesmc_info_temp = kzalloc_obj(*applesmc_info_temp, GFP_KERNEL); + applesmc_info_fan = kzalloc_obj(*applesmc_info_fan, GFP_KERNEL); + applesmc_info_pwm = kzalloc_obj(*applesmc_info_pwm, GFP_KERNEL); + if (!applesmc_info_temp || !applesmc_info_fan || !applesmc_info_pwm) { + ret = -ENOMEM; + goto out_info; + } + + applesmc_info_temp->type = hwmon_temp; + applesmc_info_temp->config = applesmc_temp_config; + + applesmc_info_fan->type = hwmon_fan; + applesmc_info_fan->config = applesmc_fan_config; + + applesmc_info_pwm->type = hwmon_pwm; + applesmc_info_pwm->config = applesmc_pwm_config; + + applesmc_info_arr = kcalloc(4, sizeof(*applesmc_info_arr), GFP_KERNEL); + if (!applesmc_info_arr) { + ret = -ENOMEM; + goto out_info; + } + + applesmc_info_arr[0] = applesmc_info_temp; + applesmc_info_arr[1] = applesmc_info_fan; + applesmc_info_arr[2] = applesmc_info_pwm; + applesmc_info_arr[3] = NULL; + + applesmc_chip = kzalloc_obj(*applesmc_chip, GFP_KERNEL); + if (!applesmc_chip) { + ret = -ENOMEM; + goto out_info; + } + + applesmc_chip->ops = &applesmc_hwmon_ops; + applesmc_chip->info = applesmc_info_arr; + + /* Create non-standard fanX_safe attributes group */ + fan_safe_attrs = kcalloc(smcreg.fan_count, + sizeof(*fan_safe_attrs), GFP_KERNEL); + fan_safe_attr_list = kcalloc(smcreg.fan_count + 1, + sizeof(*fan_safe_attr_list), GFP_KERNEL); + if (!fan_safe_attrs || !fan_safe_attr_list) { + ret = -ENOMEM; + goto out_info; + } + + for (i = 0; i < smcreg.fan_count; i++) { + struct applesmc_dev_attr *node = &fan_safe_attrs[i]; + + scnprintf(node->name, sizeof(node->name), "fan%d_safe", i + 1); + node->sda.index = (3 << 16) | (i & 0xffff); /* Option 3 (safe speed) */ + node->sda.dev_attr.show = applesmc_show_fan_speed; + node->sda.dev_attr.store = NULL; + sysfs_attr_init(&node->sda.dev_attr.attr); + node->sda.dev_attr.attr.name = node->name; + node->sda.dev_attr.attr.mode = 0444; + fan_safe_attr_list[i] = &node->sda.dev_attr.attr; + } + fan_safe_attr_list[smcreg.fan_count] = NULL; + fan_safe_group.attrs = fan_safe_attr_list; + applesmc_extra_groups[0] = &fan_safe_group; + applesmc_extra_groups[1] = NULL; ret = applesmc_create_accelerometer(); if (ret) - goto out_temperature; + goto out_info; ret = applesmc_create_light_sensor(); if (ret) @@ -1368,7 +1557,8 @@ static int __init applesmc_init(void) if (ret) goto out_light_sysfs; - hwmon_dev = hwmon_device_register(&pdev->dev); + hwmon_dev = hwmon_device_register_with_info(&pdev->dev, "applesmc", NULL, + applesmc_chip, applesmc_extra_groups); if (IS_ERR(hwmon_dev)) { ret = PTR_ERR(hwmon_dev); goto out_light_ledclass; @@ -1382,11 +1572,8 @@ static int __init applesmc_init(void) applesmc_release_light_sensor(); out_accelerometer: applesmc_release_accelerometer(); -out_temperature: - applesmc_destroy_nodes(temp_group); -out_fans: - applesmc_destroy_nodes(fan_group); out_info: + applesmc_free_hwmon(); applesmc_destroy_nodes(info_group); out_smcreg: applesmc_destroy_smcreg(); @@ -1407,13 +1594,12 @@ static void __exit applesmc_exit(void) applesmc_release_key_backlight(); applesmc_release_light_sensor(); applesmc_release_accelerometer(); - applesmc_destroy_nodes(temp_group); - applesmc_destroy_nodes(fan_group); applesmc_destroy_nodes(info_group); applesmc_destroy_smcreg(); platform_device_unregister(pdev); platform_driver_unregister(&applesmc_driver); release_region(APPLESMC_DATA_PORT, APPLESMC_NR_PORTS); + applesmc_free_hwmon(); } module_init(applesmc_init); diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -185,6 +185,20 @@ enum ec_sensors { #define SENSOR_TEMP_SENSOR_EXTRA_2 BIT(ec_sensor_temp_sensor_extra_2) #define SENSOR_TEMP_SENSOR_EXTRA_3 BIT(ec_sensor_temp_sensor_extra_3) +/* + * The values for temperature sensor readings without physical sensors connected. + * The value varies across generations and is seemingly defined by the EC chip + * used in the given board. + */ +static const s32 temperature_blank_values[] = {-62, -60, -40}; + +static const s32 environment_temp_sensors = + SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_T_SENSOR_ALT1 | + SENSOR_TEMP_WATER_IN | SENSOR_TEMP_WATER_OUT | + SENSOR_TEMP_WATER_BLOCK_IN | SENSOR_TEMP_WATER_BLOCK_OUT | + SENSOR_TEMP_T_SENSOR_2 | SENSOR_TEMP_SENSOR_EXTRA_1 | + SENSOR_TEMP_SENSOR_EXTRA_2 | SENSOR_TEMP_SENSOR_EXTRA_3; + enum board_family { family_unknown, family_amd_400_series, @@ -386,6 +400,8 @@ static const struct ec_sensor_info sensors_family_intel_600[] = { [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_fan_cpu_opt] = + EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0), [ec_sensor_fan_water_flow] = EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xbe), [ec_sensor_temp_water_in] = @@ -487,6 +503,14 @@ static const struct ec_board_info board_info_crosshair_x670e_hero = { .family = family_amd_600_series, }; +static const struct ec_board_info board_info_crosshair_x870e_hero = { + .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | + SENSOR_TEMP_MB | SENSOR_TEMP_VRM | + SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT, + .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0, + .family = family_amd_800_series, +}; + static const struct ec_board_info board_info_maximus_vi_hero = { .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | @@ -527,6 +551,14 @@ static const struct ec_board_info board_info_maximus_z790_extreme = { .family = family_intel_700_series, }; +static const struct ec_board_info board_info_maximus_z790_hero = { + .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | + SENSOR_SET_TEMP_WATER | SENSOR_FAN_WATER_FLOW | + SENSOR_FAN_CPU_OPT, + .mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX, + .family = family_intel_700_series, +}; + static const struct ec_board_info board_info_prime_x470_pro = { .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | @@ -590,6 +622,13 @@ static const struct ec_board_info board_info_pro_art_x870E_creator_wifi = { .family = family_amd_800_series, }; +static const struct ec_board_info board_info_pro_art_z690_creator_wifi = { + .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | + SENSOR_FAN_CPU_OPT, + .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PC00_LPCB_SIO1_MUT0, + .family = family_intel_600_series, +}; + static const struct ec_board_info board_info_pro_ws_trx50_sage_wifi = { /* Board also has a nct6798 */ .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | SENSOR_TEMP_VRME | @@ -743,7 +782,7 @@ static const struct ec_board_info board_info_strix_x870_i_gaming_wifi = { static const struct ec_board_info board_info_strix_x870e_e_gaming_wifi = { .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | - SENSOR_TEMP_MB | SENSOR_TEMP_VRM | + SENSOR_TEMP_MB | SENSOR_TEMP_VRM | SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT, .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0, .family = family_amd_800_series, @@ -859,6 +898,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_pro_art_x670E_creator_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt X870E-CREATOR WIFI", &board_info_pro_art_x870E_creator_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt Z690-CREATOR WIFI", + &board_info_pro_art_z690_creator_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS TRX50-SAGE WIFI", &board_info_pro_ws_trx50_sage_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS TRX50-SAGE WIFI A", @@ -883,6 +924,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_crosshair_x670e_gene), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E HERO", &board_info_crosshair_x670e_hero), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X870E HERO", + &board_info_crosshair_x870e_hero), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS XI HERO", &board_info_maximus_xi_hero), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS XI HERO (WI-FI)", @@ -893,6 +936,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_maximus_z690_formula), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS Z790 EXTREME", &board_info_maximus_z790_extreme), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS Z790 HERO", + &board_info_maximus_z790_hero), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-E GAMING", &board_info_strix_b550_e_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-I GAMING", @@ -927,8 +972,12 @@ static const struct dmi_system_id dmi_table[] = { &board_info_strix_x870_i_gaming_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-E GAMING WIFI", &board_info_strix_x870e_e_gaming_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-E GAMING WIFI7 R2", + &board_info_strix_x870e_e_gaming_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-H GAMING WIFI7", &board_info_strix_x870e_h_gaming_wifi7), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z390-E GAMING", + &board_info_strix_z390_f_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z390-F GAMING", &board_info_strix_z390_f_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z490-F GAMING", @@ -955,6 +1004,7 @@ static const struct dmi_system_id dmi_table[] = { }; struct ec_sensor { + /* this is ec_sensors enum value */ unsigned int info_index; s32 cached_value; }; @@ -1047,6 +1097,12 @@ get_sensor_info(const struct ec_sensors_data *state, int index) return state->sensors_info + state->sensors[index].info_index; } +static enum ec_sensors +get_ec_sensor_type(const struct ec_sensors_data *state, int index) +{ + return state->sensors[index].info_index; +} + static int find_ec_sensor_index(const struct ec_sensors_data *ec, enum hwmon_sensor_types type, int channel) { @@ -1290,6 +1346,17 @@ static int get_cached_value_or_update(const struct device *dev, return 0; } +static bool is_blank_temperature_value(s32 value) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(temperature_blank_values); ++i) { + if (value == temperature_blank_values[i]) + return true; + } + return false; +} + /* * Now follow the functions that implement the hwmon interface */ @@ -1297,6 +1364,8 @@ static int get_cached_value_or_update(const struct device *dev, static int asus_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { + const struct ec_sensor_info *sensor_info; + enum ec_sensors ec_sensor; int ret; s32 value = 0; @@ -1308,12 +1377,19 @@ static int asus_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, } ret = get_cached_value_or_update(dev, sidx, state, &value); - if (!ret) { - *val = scale_sensor_value(value, - get_sensor_info(state, sidx)->type); - } + if (ret) + return ret; - return ret; + sensor_info = get_sensor_info(state, sidx); + if (sensor_info->type == hwmon_temp) { + ec_sensor = get_ec_sensor_type(state, sidx); + if ((environment_temp_sensors & BIT(ec_sensor)) && + is_blank_temperature_value(value)) + return -ENODATA; + } + *val = scale_sensor_value(value, sensor_info->type); + + return 0; } static int asus_ec_hwmon_read_string(struct device *dev, diff --git a/drivers/hwmon/asus_rog_ryujin.c b/drivers/hwmon/asus_rog_ryujin.c --- a/drivers/hwmon/asus_rog_ryujin.c +++ b/drivers/hwmon/asus_rog_ryujin.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * hwmon driver for Asus ROG Ryujin II 360 AIO cooler. + * hwmon driver for Asus ROG Ryujin AIO coolers. * * Copyright 2024 Aleksa Savic */ @@ -17,6 +17,9 @@ #define USB_VENDOR_ID_ASUS_ROG 0x0b05 #define USB_PRODUCT_ID_RYUJIN_AIO 0x1988 /* ASUS ROG RYUJIN II 360 */ +#define USB_PRODUCT_ID_RYUJIN_III_EXTREME 0x1bcb +#define USB_PRODUCT_ID_RYUJIN_III_EVA 0x1ade +#define USB_PRODUCT_ID_RYUJIN_III_WHITE 0x1ada struct rog_ryujin_device_info { u8 temp_offset; @@ -34,6 +37,14 @@ static const struct rog_ryujin_device_info rog_ryujin_ii_360_info = { .has_controller = true, }; +static const struct rog_ryujin_device_info rog_ryujin_iii_info = { + .temp_offset = 5, + .pump_speed_offset = 7, + .fan_speed_offset = 10, + .duty_channel = 1, + .has_controller = false, +}; + #define STATUS_VALIDITY 1500 /* ms */ #define MAX_REPORT_LENGTH 65 @@ -173,7 +184,6 @@ static int rog_ryujin_write_expanded(struct rog_ryujin_data *priv, const u8 *cmd static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, int cmd_length, struct completion *status_completion) { - unsigned long flags; int ret; /* @@ -181,9 +191,9 @@ static int rog_ryujin_execute_cmd(struct rog_ryujin_data *priv, const u8 *cmd, i * completion. Reinit is done because hidraw could have triggered * the raw event parsing and marked the passed in completion as done. */ - spin_lock_irqsave(&priv->status_report_request_lock, flags); + spin_lock_bh(&priv->status_report_request_lock); reinit_completion(status_completion); - spin_unlock_irqrestore(&priv->status_report_request_lock, flags); + spin_unlock_bh(&priv->status_report_request_lock); /* Send command for getting data */ ret = rog_ryujin_write_expanded(priv, cmd, cmd_length); @@ -411,18 +421,15 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo int size) { struct rog_ryujin_data *priv = hid_get_drvdata(hdev); - unsigned long flags; if (size < 2 || data[0] != RYUJIN_CMD_PREFIX) return 0; - spin_lock_irqsave(&priv->status_report_request_lock, flags); - if (data[1] == RYUJIN_GET_COOLER_STATUS_CMD_RESPONSE) { if (size <= priv->info->temp_offset + 1 || size <= priv->info->pump_speed_offset + 1 || size <= priv->info->fan_speed_offset + 1) - goto unlock; + return 0; /* Received coolant temp and speeds of pump and internal fan */ priv->temp_input[0] = data[priv->info->temp_offset] * 1000 + @@ -436,7 +443,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo complete_all(&priv->cooler_status_received); } else if (data[1] == RYUJIN_GET_CONTROLLER_SPEED_CMD_RESPONSE) { if (size <= RYUJIN_CONTROLLER_SPEED_3 + 1) - goto unlock; + return 0; /* Received speeds of four fans attached to the controller */ priv->speed_input[2] = get_unaligned_le16(data + RYUJIN_CONTROLLER_SPEED_1); @@ -448,7 +455,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo complete_all(&priv->controller_status_received); } else if (data[1] == RYUJIN_GET_COOLER_DUTY_CMD_RESPONSE) { if (size <= RYUJIN_INTERNAL_FAN_DUTY) - goto unlock; + return 0; /* Received report for pump and internal fan duties (in %) */ if (data[RYUJIN_PUMP_DUTY] == 0 && data[RYUJIN_INTERNAL_FAN_DUTY] == 0) { @@ -467,7 +474,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo * We're expecting a report, so parse it. */ goto read_cooler_duty; - goto unlock; + return 0; } read_cooler_duty: priv->duty_input[0] = rog_ryujin_percent_to_pwm(data[RYUJIN_PUMP_DUTY]); @@ -477,7 +484,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo complete_all(&priv->cooler_duty_received); } else if (data[1] == RYUJIN_GET_CONTROLLER_DUTY_CMD_RESPONSE) { if (size <= RYUJIN_CONTROLLER_DUTY) - goto unlock; + return 0; /* Received report for controller duty for fans (in PWM) */ if (data[RYUJIN_CONTROLLER_DUTY] == 0) { @@ -496,7 +503,7 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo * We're expecting a report, so parse it. */ goto read_controller_duty; - goto unlock; + return 0; } read_controller_duty: priv->duty_input[2] = data[RYUJIN_CONTROLLER_DUTY]; @@ -505,8 +512,6 @@ static int rog_ryujin_raw_event(struct hid_device *hdev, struct hid_report *repo complete_all(&priv->controller_duty_received); } -unlock: - spin_unlock_irqrestore(&priv->status_report_request_lock, flags); return 0; } @@ -596,6 +601,12 @@ static void rog_ryujin_remove(struct hid_device *hdev) static const struct hid_device_id rog_ryujin_table[] = { { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_AIO), .driver_data = (kernel_ulong_t)&rog_ryujin_ii_360_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III_EXTREME), + .driver_data = (kernel_ulong_t)&rog_ryujin_iii_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III_EVA), + .driver_data = (kernel_ulong_t)&rog_ryujin_iii_info }, + { HID_USB_DEVICE(USB_VENDOR_ID_ASUS_ROG, USB_PRODUCT_ID_RYUJIN_III_WHITE), + .driver_data = (kernel_ulong_t)&rog_ryujin_iii_info }, { } }; @@ -625,4 +636,4 @@ module_exit(rog_ryujin_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Aleksa Savic "); -MODULE_DESCRIPTION("Hwmon driver for Asus ROG Ryujin II 360 AIO cooler"); +MODULE_DESCRIPTION("Hwmon driver for Asus ROG Ryujin AIO coolers"); diff --git a/drivers/hwmon/axi-fan-control.c b/drivers/hwmon/axi-fan-control.c --- a/drivers/hwmon/axi-fan-control.c +++ b/drivers/hwmon/axi-fan-control.c @@ -508,8 +508,7 @@ static int axi_fan_control_probe(struct platform_device *pdev) IRQF_ONESHOT | IRQF_TRIGGER_HIGH, NULL, ctl); if (ret) - return dev_err_probe(&pdev->dev, ret, - "failed to request an irq\n"); + return ret; return 0; } diff --git a/drivers/hwmon/chipcap2.c b/drivers/hwmon/chipcap2.c --- a/drivers/hwmon/chipcap2.c +++ b/drivers/hwmon/chipcap2.c @@ -22,6 +22,8 @@ #include #include #include +#include +#include #define CC2_START_CM 0xA0 #define CC2_START_NOM 0x80 @@ -83,6 +85,7 @@ struct cc2_data { struct i2c_client *client; struct regulator *regulator; const char *name; + const char *label; int irq_ready; int irq_low; int irq_high; @@ -444,6 +447,8 @@ static umode_t cc2_is_visible(const void *data, enum hwmon_sensor_types type, switch (attr) { case hwmon_humidity_input: return 0444; + case hwmon_humidity_label: + return cc2->label ? 0444 : 0; case hwmon_humidity_min_alarm: return cc2->rh_alarm.low_alarm_visible ? 0444 : 0; case hwmon_humidity_max_alarm: @@ -461,6 +466,8 @@ static umode_t cc2_is_visible(const void *data, enum hwmon_sensor_types type, switch (attr) { case hwmon_temp_input: return 0444; + case hwmon_temp_label: + return cc2->label ? 0444 : 0; default: return 0; } @@ -547,6 +554,16 @@ static int cc2_humidity_max_alarm_status(struct cc2_data *data, long *val) return 0; } +static int cc2_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + struct cc2_data *data = dev_get_drvdata(dev); + + *str = data->label; + + return 0; +} + static int cc2_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { @@ -665,8 +682,9 @@ static int cc2_request_alarm_irqs(struct cc2_data *data, struct device *dev) } static const struct hwmon_channel_info *cc2_info[] = { - HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), - HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT | HWMON_H_MIN | HWMON_H_MAX | + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL), + HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT | HWMON_H_LABEL | + HWMON_H_MIN | HWMON_H_MAX | HWMON_H_MIN_HYST | HWMON_H_MAX_HYST | HWMON_H_MIN_ALARM | HWMON_H_MAX_ALARM), NULL @@ -675,6 +693,7 @@ static const struct hwmon_channel_info *cc2_info[] = { static const struct hwmon_ops cc2_hwmon_ops = { .is_visible = cc2_is_visible, .read = cc2_read, + .read_string = cc2_read_string, .write = cc2_write, }; @@ -705,6 +724,8 @@ static int cc2_probe(struct i2c_client *client) return dev_err_probe(dev, PTR_ERR(data->regulator), "Failed to get regulator\n"); + device_property_read_string(dev, "label", &data->label); + ret = cc2_request_ready_irq(data, dev); if (ret) return dev_err_probe(dev, ret, "Failed to request ready irq\n"); diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -68,7 +68,7 @@ enum coretemp_attr_index { * This value is passed as "id" field to rdmsr/wrmsr functions. * @status_reg: One of IA32_THERM_STATUS or IA32_PACKAGE_THERM_STATUS, * from where the temperature values should be read. - * @attr_size: Total number of pre-core attrs displayed in the sysfs. + * @attr_size: Number of sysfs attributes in this temperature data group. */ struct temp_data { int temp; diff --git a/drivers/hwmon/corsair-psu.c b/drivers/hwmon/corsair-psu.c --- a/drivers/hwmon/corsair-psu.c +++ b/drivers/hwmon/corsair-psu.c @@ -884,7 +884,7 @@ static const struct hid_device_id corsairpsu_idtable[] = { { HID_USB_DEVICE(0x1b1c, 0x1c0c) }, /* Corsair RM850i */ { HID_USB_DEVICE(0x1b1c, 0x1c0d) }, /* Corsair RM1000i */ { HID_USB_DEVICE(0x1b1c, 0x1c1e) }, /* Corsair HX1000i Series 2023 */ - { HID_USB_DEVICE(0x1b1c, 0x1c1f) }, /* Corsair HX1500i Legacy and Series 2023 */ + { HID_USB_DEVICE(0x1b1c, 0x1c1f) }, /* Corsair HX1500i Legacy, Series 2023 and 2025 */ { HID_USB_DEVICE(0x1b1c, 0x1c23) }, /* Corsair HX1200i Series 2023 */ { HID_USB_DEVICE(0x1b1c, 0x1c27) }, /* Corsair HX1200i Series 2025 */ { }, diff --git a/drivers/hwmon/cros_ec_hwmon.c b/drivers/hwmon/cros_ec_hwmon.c --- a/drivers/hwmon/cros_ec_hwmon.c +++ b/drivers/hwmon/cros_ec_hwmon.c @@ -148,9 +148,17 @@ static bool cros_ec_hwmon_is_error_temp(u8 temp) temp == EC_TEMP_SENSOR_NOT_CALIBRATED; } +/* This differs slightly from the variant in units.h to avoid rounding inconsistencies. */ +#define CROS_EC_HWMON_ABSOLUTE_ZERO_MILLICELSIUS (-273000) + +static long cros_ec_hwmon_kelvin_to_millicelsius(long t) +{ + return t * MILLIDEGREE_PER_DEGREE + CROS_EC_HWMON_ABSOLUTE_ZERO_MILLICELSIUS; +} + static long cros_ec_hwmon_temp_to_millicelsius(u8 temp) { - return kelvin_to_millicelsius((((long)temp) + EC_TEMP_SENSOR_OFFSET)); + return cros_ec_hwmon_kelvin_to_millicelsius((((long)temp) + EC_TEMP_SENSOR_OFFSET)); } static bool cros_ec_hwmon_attr_is_temp_threshold(u32 attr) @@ -228,8 +236,13 @@ static int cros_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, ret = cros_ec_hwmon_read_temp_threshold(priv->cros_ec, channel, cros_ec_hwmon_attr_to_thres(attr), &threshold); - if (ret == 0) - *val = kelvin_to_millicelsius(threshold); + if (ret == 0) { + /* Limit to sensible, non-overflowing values. */ + if (threshold > 255 + 273) + *val = 255000; + else + *val = cros_ec_hwmon_kelvin_to_millicelsius(threshold); + } } } diff --git a/drivers/hwmon/da9055-hwmon.c b/drivers/hwmon/da9055-hwmon.c --- a/drivers/hwmon/da9055-hwmon.c +++ b/drivers/hwmon/da9055-hwmon.c @@ -256,11 +256,8 @@ static int da9055_hwmon_probe(struct platform_device *pdev) NULL, da9055_auxadc_irq, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "adc-irq", hwmon); - if (ret != 0) { - dev_err(hwmon->da9055->dev, "DA9055 ADC IRQ failed ret=%d\n", - ret); + if (ret != 0) return ret; - } hwmon_dev = devm_hwmon_device_register_with_groups(dev, "da9055", hwmon, diff --git a/drivers/hwmon/eic7700-pvt.c b/drivers/hwmon/eic7700-pvt.c new file mode 100644 --- /dev/null +++ b/drivers/hwmon/eic7700-pvt.c @@ -0,0 +1,509 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ESWIN EIC7700 Voltage, Temperature sensor driver + * + * Copyright 2026, Beijing ESWIN Computing Technology Co., Ltd. + * + * Authors: + * Yulin Lu + * Huan He + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "eic7700-pvt.h" + +static const struct pvt_sensor_info pvt_info[] = { + PVT_SENSOR_INFO(0, "Temperature", hwmon_temp, TEMP), + PVT_SENSOR_INFO(0, "Voltage", hwmon_in, VOLT), +}; + +static const char * const pvt_clk_names[PVT_CLK_NUM] = {"enable", "apb"}; + +/* + * The original translation formulae of the temperature (in degrees of Celsius) + * to PVT data and vice-versa are following: + * N = 6.0818e-8*(T^4) +1.2873e-5*(T^3) + 7.2244e-3*(T^2) + 3.6484*(T^1) + + * 1.6198e2, + * T = -1.8439e-11*(N^4) + 8.0705e-8*(N^3) + -1.8501e-4*(N^2) + + * 3.2843e-1*(N^1) - 4.8690e1, + * where T = [-40, 125]C and N = [27, 771]. + * They must be accordingly altered to be suitable for the integer arithmetics. + * The technique is called 'factor redistribution', which just makes sure the + * multiplications and divisions are made so to have a result of the operations + * within the integer numbers limit. In addition we need to translate the + * formulae to accept millidegrees of Celsius. Here what they look like after + * the alterations: + * N = (60818e-20*(T^4) + 12873e-14*(T^3) + 72244e-9*(T^2) + 36484e-3*T + + * 16198e2) / 1e4, + * T = -18439e-12*(N^4) + 80705e-9*(N^3) - 185010e-6*(N^2) + 328430e-3*N - + * 48690, + * where T = [-40000, 125000] mC and N = [27, 771]. + */ +static const struct polynomial poly_N_to_temp = { + .total_divider = 1, + .terms = { + {4, -18439, 1000, 1}, + {3, 80705, 1000, 1}, + {2, -185010, 1000, 1}, + {1, 328430, 1000, 1}, + {0, -48690, 1, 1} + } +}; + +/* + * Similar alterations are performed for the voltage conversion equations. + * The original formulae are: + * N = 1.3905e3*V - 5.7685e2, + * V = (N + 5.7685e2) / 1.3905e3, + * where V = [0.72, 0.88] V and N = [424, 646]. + * After the optimization they looks as follows: + * N = (13905e-3*V - 5768.5) / 10, + * V = (N * 10^5 / 13905 + 57685 * 10^3 / 13905) / 10. + * where V = [720, 880] mV and N = [424, 646]. + */ +static const struct polynomial poly_N_to_volt = { + .total_divider = 10, + .terms = { + {1, 100000, 13905, 1}, + {0, 57685000, 1, 13905} + } +}; + +static inline u32 eic7700_pvt_update(void __iomem *reg, u32 mask, u32 data) +{ + u32 old; + + old = readl_relaxed(reg); + writel((old & ~mask) | (data & mask), reg); + + return old & mask; +} + +static inline void eic7700_pvt_set_mode(struct pvt_hwmon *pvt, u32 mode) +{ + u32 old; + + mode = FIELD_PREP(PVT_MODE_MASK, mode); + + old = eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); + eic7700_pvt_update(pvt->regs + PVT_MODE, PVT_MODE_MASK, mode); + eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, old); +} + +static inline void eic7700_pvt_set_trim(struct pvt_hwmon *pvt, u32 val) +{ + u32 old; + + old = eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); + writel(val, pvt->regs + PVT_TRIM); + eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, old); +} + +static irqreturn_t eic7700_pvt_hard_isr(int irq, void *data) +{ + struct pvt_hwmon *pvt = data; + u32 stat, val; + int active; + + if (IS_ENABLED(CONFIG_PM)) { + active = pm_runtime_get_if_active(pvt->dev); + if (active <= 0) + return IRQ_NONE; + } + + stat = readl(pvt->regs + PVT_INT); + if (!(stat & PVT_INT_STAT)) { + if (IS_ENABLED(CONFIG_PM)) + pm_runtime_put(pvt->dev); + return IRQ_NONE; + } + + eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); + /* + * Read the data, update the cache and notify a waiter of this event. + */ + val = readl(pvt->regs + PVT_DATA); + WRITE_ONCE(pvt->data_cache, FIELD_GET(PVT_DATA_OUT, val)); + complete(&pvt->conversion); + + if (IS_ENABLED(CONFIG_PM)) + pm_runtime_put(pvt->dev); + + return IRQ_HANDLED; +} + +static int eic7700_pvt_read_data(struct pvt_hwmon *pvt, + enum pvt_sensor_type type, long *val) +{ + unsigned long timeout; + u32 data; + int ret; + + /* + * Wait for PVT conversion to complete and update the data cache. The + * data read procedure is following: set the requested PVT sensor mode, + * enable conversion, wait until conversion is finished, then disable + * conversion and IRQ, and read the cached data. + */ + reinit_completion(&pvt->conversion); + + eic7700_pvt_set_mode(pvt, pvt_info[type].mode); + eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, PVT_ENA_EN); + + /* + * Wait with timeout since in case if the sensor is suddenly powered + * down the request won't be completed and the caller will hang up on + * this procedure until the power is back up again. Multiply the + * timeout by the factor of two to prevent a false timeout. + */ + timeout = 2 * usecs_to_jiffies(ktime_to_us(pvt->timeout)); + ret = wait_for_completion_timeout(&pvt->conversion, timeout); + + eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); + eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); + + if (!ret) + synchronize_irq(pvt->irq); + + data = READ_ONCE(pvt->data_cache); + + if (!ret) + return -ETIMEDOUT; + + if (type == PVT_TEMP) + *val = polynomial_calc(&poly_N_to_temp, data); + else + *val = polynomial_calc(&poly_N_to_volt, data); + + return 0; +} + +static const struct hwmon_channel_info *pvt_channel_info[] = { + HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL), + HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL), + NULL +}; + +static umode_t eic7700_pvt_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int ch) +{ + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_label: + return 0444; + } + break; + case hwmon_in: + switch (attr) { + case hwmon_in_input: + case hwmon_in_label: + return 0444; + } + break; + default: + break; + } + + return 0; +} + +static int eic7700_pvt_hwmon_read(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int ch, long *val) +{ + struct pvt_hwmon *pvt = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_get_sync(pvt->dev); + if (ret < 0) { + dev_err(pvt->dev, "Failed to resume PVT device: %d\n", ret); + pm_runtime_put_noidle(pvt->dev); + return ret; + } + + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + ret = eic7700_pvt_read_data(pvt, ch, val); + break; + default: + ret = -EOPNOTSUPP; + } + break; + case hwmon_in: + if (attr == hwmon_in_input) + ret = eic7700_pvt_read_data(pvt, PVT_VOLT + ch, val); + else + ret = -EOPNOTSUPP; + break; + default: + ret = -EOPNOTSUPP; + } + + pm_runtime_mark_last_busy(pvt->dev); + pm_runtime_put_autosuspend(pvt->dev); + return ret; +} + +static int eic7700_pvt_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int ch, const char **str) +{ + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_label) { + *str = pvt_info[ch].label; + return 0; + } + break; + case hwmon_in: + if (attr == hwmon_in_label) { + *str = pvt_info[PVT_VOLT + ch].label; + return 0; + } + break; + default: + break; + } + + return -EOPNOTSUPP; +} + +static const struct hwmon_ops pvt_hwmon_ops = { + .is_visible = eic7700_pvt_hwmon_is_visible, + .read = eic7700_pvt_hwmon_read, + .read_string = eic7700_pvt_hwmon_read_string +}; + +static const struct hwmon_chip_info pvt_hwmon_info = { + .ops = &pvt_hwmon_ops, + .info = pvt_channel_info +}; + +static struct pvt_hwmon *eic7700_pvt_create_data(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pvt_hwmon *pvt; + + pvt = devm_kzalloc(dev, sizeof(*pvt), GFP_KERNEL); + if (!pvt) + return ERR_PTR(-ENOMEM); + + pvt->dev = dev; + init_completion(&pvt->conversion); + + return pvt; +} + +static int eic7700_pvt_init_iface(struct pvt_hwmon *pvt) +{ + /* + * Make sure controller are disabled so not to accidentally have ISR + * executed before the driver data is fully initialized. Clear the IRQ + * status as well. + */ + eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); + eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); + readl(pvt->regs + PVT_INT); + readl(pvt->regs + PVT_DATA); + + /* Setup default sensor mode and temperature trim. */ + eic7700_pvt_set_mode(pvt, pvt_info[PVT_TEMP].mode); + + /* + * Max conversion latency (~333 µs) derived from PVT spec: + * maximum sampling rate = 3000 samples/sec. + */ + pvt->timeout = ns_to_ktime(PVT_TOUT_MIN); + + eic7700_pvt_set_trim(pvt, PVT_TRIM_DEF); + + return 0; +} + +static int eic7700_pvt_request_irq(struct pvt_hwmon *pvt) +{ + struct platform_device *pdev = to_platform_device(pvt->dev); + int ret; + + pvt->irq = platform_get_irq(pdev, 0); + if (pvt->irq < 0) + return pvt->irq; + + ret = devm_request_threaded_irq(pvt->dev, pvt->irq, + eic7700_pvt_hard_isr, NULL, + IRQF_TRIGGER_HIGH, "pvt", pvt); + if (ret) { + dev_err(pvt->dev, "Couldn't request PVT IRQ\n"); + return ret; + } + + return 0; +} + +static int eic7700_pvt_create_hwmon(struct pvt_hwmon *pvt) +{ + pvt->hwmon = devm_hwmon_device_register_with_info(pvt->dev, "pvt", + pvt, &pvt_hwmon_info, + NULL); + if (IS_ERR(pvt->hwmon)) { + dev_err(pvt->dev, "Couldn't create hwmon device\n"); + return PTR_ERR(pvt->hwmon); + } + + return 0; +} + +static void eic7700_pvt_disable_pm_runtime(void *data) +{ + struct pvt_hwmon *pvt = data; + + pm_runtime_dont_use_autosuspend(pvt->dev); + pm_runtime_disable(pvt->dev); + + if (!pm_runtime_status_suspended(pvt->dev)) { + clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); + pm_runtime_set_suspended(pvt->dev); + } +} + +static int eic7700_pvt_probe(struct platform_device *pdev) +{ + struct reset_control *rst; + struct pvt_hwmon *pvt; + int i, ret; + + pvt = eic7700_pvt_create_data(pdev); + if (IS_ERR(pvt)) + return PTR_ERR(pvt); + + platform_set_drvdata(pdev, pvt); + + pvt->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(pvt->regs)) + return PTR_ERR(pvt->regs); + + for (i = 0; i < PVT_CLK_NUM; i++) + pvt->clks[i].id = pvt_clk_names[i]; + + ret = devm_clk_bulk_get(&pdev->dev, PVT_CLK_NUM, pvt->clks); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Couldn't get clock descriptors\n"); + + rst = devm_reset_control_get_exclusive_deasserted(&pdev->dev, NULL); + if (IS_ERR(rst)) + return dev_err_probe(pvt->dev, PTR_ERR(rst), + "Couldn't get reset control\n"); + + ret = clk_bulk_prepare_enable(PVT_CLK_NUM, pvt->clks); + if (ret) + return dev_err_probe(pvt->dev, ret, + "Failed to enable clocks\n"); + + ret = eic7700_pvt_init_iface(pvt); + if (ret) { + clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); + return ret; + } + + if (IS_ENABLED(CONFIG_PM)) + clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); + + pm_runtime_enable(&pdev->dev); + pm_runtime_set_autosuspend_delay(&pdev->dev, 3000); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_get_noresume(&pdev->dev); + + ret = devm_add_action_or_reset(pvt->dev, eic7700_pvt_disable_pm_runtime, + pvt); + if (ret) { + pm_runtime_put_noidle(&pdev->dev); + return dev_err_probe(&pdev->dev, ret, + "Can't register PM cleanup\n"); + } + + ret = eic7700_pvt_request_irq(pvt); + if (ret) + goto err_put_pm_runtime; + + ret = eic7700_pvt_create_hwmon(pvt); + if (ret) + goto err_put_pm_runtime; + + pm_runtime_put_autosuspend(&pdev->dev); + + return 0; + +err_put_pm_runtime: + pm_runtime_put_noidle(&pdev->dev); + return ret; +} + +static int __maybe_unused eic7700_pvt_runtime_resume(struct device *dev) +{ + struct pvt_hwmon *pvt = dev_get_drvdata(dev); + int ret; + + ret = clk_bulk_prepare_enable(PVT_CLK_NUM, pvt->clks); + if (ret) { + dev_err(dev, "Failed to enable clocks: %d\n", ret); + return ret; + } + + eic7700_pvt_set_trim(pvt, PVT_TRIM_DEF); + + return 0; +} + +static int __maybe_unused eic7700_pvt_runtime_suspend(struct device *dev) +{ + struct pvt_hwmon *pvt = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); + + return 0; +} + +static const struct dev_pm_ops eic7700_pvt_pm_ops = { + SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) + RUNTIME_PM_OPS(eic7700_pvt_runtime_suspend, eic7700_pvt_runtime_resume, + NULL) +}; + +static const struct of_device_id pvt_of_match[] = { + { .compatible = "eswin,eic7700-pvt"}, + { } +}; +MODULE_DEVICE_TABLE(of, pvt_of_match); + +static struct platform_driver pvt_driver = { + .probe = eic7700_pvt_probe, + .driver = { + .name = "eic7700-pvt", + .of_match_table = pvt_of_match, + .pm = pm_ptr(&eic7700_pvt_pm_ops), + }, +}; +module_platform_driver(pvt_driver); + +MODULE_AUTHOR("Yulin Lu "); +MODULE_AUTHOR("Huan He "); +MODULE_DESCRIPTION("Eswin eic7700 PVT driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/eic7700-pvt.h b/drivers/hwmon/eic7700-pvt.h new file mode 100644 --- /dev/null +++ b/drivers/hwmon/eic7700-pvt.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ESWIN EIC7700 Voltage, Temperature sensor driver + * + * Copyright 2026, Beijing ESWIN Computing Technology Co., Ltd. + */ +#ifndef __HWMON_EIC7700_PVT_H__ +#define __HWMON_EIC7700_PVT_H__ + +#include +#include +#include +#include + +/* ESWIN EIC7700 PVT registers and their bitfields */ +#define PVT_TRIM 0x04 +#define PVT_MODE 0x08 +#define PVT_MODE_MASK GENMASK(2, 0) +#define PVT_CTRL_MODE_TEMP 0x0 +#define PVT_CTRL_MODE_VOLT 0x4 +#define PVT_ENA 0x0c +#define PVT_ENA_EN BIT(0) +#define PVT_INT 0x10 +#define PVT_INT_STAT BIT(0) +#define PVT_INT_CLR BIT(1) +#define PVT_DATA 0x14 +#define PVT_DATA_OUT GENMASK(9, 0) + +/* + * PVT sensors-related limits and default values + * @PVT_TEMP_CHS: Number of temperature hwmon channels. + * @PVT_VOLT_CHS: Number of voltage hwmon channels. + * @PVT_TRIM_DEF: Default temperature sensor trim value (set a proper value + * when one is determined for ESWIN EIC7700 SoC). + * @PVT_TOUT_MIN: Minimal timeout between samples in nanoseconds. + */ +#define PVT_TEMP_CHS 1 +#define PVT_VOLT_CHS 1 +#define PVT_TRIM_DEF 0 +#define PVT_TOUT_MIN (NSEC_PER_SEC / 3000) + +/* + * enum pvt_sensor_type - ESWIN EIC7700 PVT sensor types (correspond to each PVT + * sampling mode) + * @PVT_TEMP: PVT Temperature sensor. + * @PVT_VOLT: PVT Voltage sensor. + */ +enum pvt_sensor_type { + PVT_TEMP = 0, + PVT_VOLT +}; + +#define PVT_CLK_NUM 2 + +/* + * struct pvt_sensor_info - ESWIN EIC7700 PVT sensor informational structure + * @channel: Sensor channel ID. + * @label: hwmon sensor label. + * @mode: PVT mode corresponding to the channel. + * @type: Sensor type. + */ +struct pvt_sensor_info { + int channel; + const char *label; + u32 mode; + enum hwmon_sensor_types type; +}; + +#define PVT_SENSOR_INFO(_ch, _label, _type, _mode) \ + { \ + .channel = _ch, \ + .label = _label, \ + .mode = PVT_CTRL_MODE_ ##_mode, \ + .type = _type, \ + } + +/* + * struct pvt_hwmon - Eswin EIC7700 PVT private data + * @dev: device structure of the PVT platform device. + * @hwmon: hwmon device structure. + * @regs: pointer to the Eswin EIC7700 PVT registers region. + * @irq: PVT events IRQ number. + * @clks: PVT clock descriptors. + * @data_cache: data cache in raw format. + * @conversion: data conversion completion. + * @timeout: conversion timeout. + */ +struct pvt_hwmon { + struct device *dev; + struct device *hwmon; + void __iomem *regs; + int irq; + struct clk_bulk_data clks[PVT_CLK_NUM]; + u32 data_cache; + struct completion conversion; + ktime_t timeout; +}; + +#endif /* __HWMON_EIC7700_PVT_H__ */ diff --git a/drivers/hwmon/emc1403.c b/drivers/hwmon/emc1403.c --- a/drivers/hwmon/emc1403.c +++ b/drivers/hwmon/emc1403.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #define THERMAL_PID_REG 0xfd @@ -654,14 +655,20 @@ static int emc1403_probe(struct i2c_client *client) { struct thermal_data *data; struct device *hwmon_dev; - const struct i2c_device_id *id = i2c_match_id(emc1403_idtable, client); + int ret; + + ret = devm_regulator_get_enable(&client->dev, "vdd"); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to enable regulator\n"); data = devm_kzalloc(&client->dev, sizeof(struct thermal_data), GFP_KERNEL); if (!data) return -ENOMEM; - data->chip = id->driver_data; + data->chip = (uintptr_t)i2c_get_match_data(client); + data->regmap = devm_regmap_init_i2c(client, &emc1403_regmap_config); if (IS_ERR(data->regmap)) return PTR_ERR(data->regmap); @@ -677,10 +684,20 @@ static const unsigned short emc1403_address_list[] = { 0x18, 0x1c, 0x29, 0x3c, 0x4c, 0x4d, 0x5c, I2C_CLIENT_END }; +static const struct of_device_id emc1403_of_match[] = { + { .compatible = "smsc,emc1402", .data = (void *)emc1402 }, + { .compatible = "smsc,emc1403", .data = (void *)emc1403 }, + { .compatible = "smsc,emc1404", .data = (void *)emc1404 }, + { .compatible = "smsc,emc1428", .data = (void *)emc1428 }, + { } +}; +MODULE_DEVICE_TABLE(of, emc1403_of_match); + static struct i2c_driver sensor_emc1403 = { .class = I2C_CLASS_HWMON, .driver = { .name = "emc1403", + .of_match_table = emc1403_of_match, }, .detect = emc1403_detect, .probe = emc1403_probe, diff --git a/drivers/hwmon/gxp-fan-ctrl.c b/drivers/hwmon/gxp-fan-ctrl.c --- a/drivers/hwmon/gxp-fan-ctrl.c +++ b/drivers/hwmon/gxp-fan-ctrl.c @@ -1,59 +1,16 @@ // SPDX-License-Identifier: GPL-2.0-only -/* Copyright (C) 2022 Hewlett-Packard Enterprise Development Company, L.P. */ +/* Copyright (C) 2023 Hewlett-Packard Enterprise Development Company, L.P. */ -#include #include #include #include #include #include -#define OFS_FAN_INST 0 /* Is 0 because plreg base will be set at INST */ -#define OFS_FAN_FAIL 2 /* Is 2 bytes after base */ -#define OFS_SEVSTAT 0 /* Is 0 because fn2 base will be set at SEVSTAT */ -#define POWER_BIT 24 - struct gxp_fan_ctrl_drvdata { void __iomem *base; - void __iomem *plreg; - void __iomem *fn2; }; -static bool fan_installed(struct device *dev, int fan) -{ - struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); - u8 val; - - val = readb(drvdata->plreg + OFS_FAN_INST); - - return !!(val & BIT(fan)); -} - -static long fan_failed(struct device *dev, int fan) -{ - struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); - u8 val; - - val = readb(drvdata->plreg + OFS_FAN_FAIL); - - return !!(val & BIT(fan)); -} - -static long fan_enabled(struct device *dev, int fan) -{ - struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); - u32 val; - - /* - * Check the power status as if the platform is off the value - * reported for the PWM will be incorrect. Report fan as - * disabled. - */ - val = readl(drvdata->fn2 + OFS_SEVSTAT); - - return !!((val & BIT(POWER_BIT)) && fan_installed(dev, fan)); -} - static int gxp_pwm_write(struct device *dev, u32 attr, int channel, long val) { struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); @@ -80,37 +37,11 @@ static int gxp_fan_ctrl_write(struct device *dev, enum hwmon_sensor_types type, } } -static int gxp_fan_read(struct device *dev, u32 attr, int channel, long *val) -{ - switch (attr) { - case hwmon_fan_enable: - *val = fan_enabled(dev, channel); - return 0; - case hwmon_fan_fault: - *val = fan_failed(dev, channel); - return 0; - default: - return -EOPNOTSUPP; - } -} - static int gxp_pwm_read(struct device *dev, u32 attr, int channel, long *val) { struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); - u32 reg; - /* - * Check the power status of the platform. If the platform is off - * the value reported for the PWM will be incorrect. In this case - * report a PWM of zero. - */ - - reg = readl(drvdata->fn2 + OFS_SEVSTAT); - - if (reg & BIT(POWER_BIT)) - *val = fan_installed(dev, channel) ? readb(drvdata->base + channel) : 0; - else - *val = 0; + *val = readb(drvdata->base + channel); return 0; } @@ -119,8 +50,6 @@ static int gxp_fan_ctrl_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { switch (type) { - case hwmon_fan: - return gxp_fan_read(dev, attr, channel, val); case hwmon_pwm: return gxp_pwm_read(dev, attr, channel, val); default: @@ -135,16 +64,6 @@ static umode_t gxp_fan_ctrl_is_visible(const void *_data, umode_t mode = 0; switch (type) { - case hwmon_fan: - switch (attr) { - case hwmon_fan_enable: - case hwmon_fan_fault: - mode = 0444; - break; - default: - break; - } - break; case hwmon_pwm: switch (attr) { case hwmon_pwm_input: @@ -168,15 +87,6 @@ static const struct hwmon_ops gxp_fan_ctrl_ops = { }; static const struct hwmon_channel_info * const gxp_fan_ctrl_info[] = { - HWMON_CHANNEL_INFO(fan, - HWMON_F_FAULT | HWMON_F_ENABLE, - HWMON_F_FAULT | HWMON_F_ENABLE, - HWMON_F_FAULT | HWMON_F_ENABLE, - HWMON_F_FAULT | HWMON_F_ENABLE, - HWMON_F_FAULT | HWMON_F_ENABLE, - HWMON_F_FAULT | HWMON_F_ENABLE, - HWMON_F_FAULT | HWMON_F_ENABLE, - HWMON_F_FAULT | HWMON_F_ENABLE), HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT, HWMON_PWM_INPUT, @@ -211,18 +121,6 @@ static int gxp_fan_ctrl_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(drvdata->base), "failed to map base\n"); - drvdata->plreg = devm_platform_ioremap_resource_byname(pdev, - "pl"); - if (IS_ERR(drvdata->plreg)) - return dev_err_probe(dev, PTR_ERR(drvdata->plreg), - "failed to map plreg\n"); - - drvdata->fn2 = devm_platform_ioremap_resource_byname(pdev, - "fn2"); - if (IS_ERR(drvdata->fn2)) - return dev_err_probe(dev, PTR_ERR(drvdata->fn2), - "failed to map fn2\n"); - hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, "hpe_gxp_fan_ctrl", drvdata, diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -57,8 +57,8 @@ struct hwmon_device_attribute { }; #define to_hwmon_attr(d) \ - container_of(d, struct hwmon_device_attribute, dev_attr) -#define to_dev_attr(a) container_of(a, struct device_attribute, attr) + container_of_const(d, struct hwmon_device_attribute, dev_attr) +#define to_dev_attr(a) container_of_const(a, struct device_attribute, attr) /* * Thermal zone information @@ -71,27 +71,27 @@ struct hwmon_thermal_data { }; static ssize_t -name_show(struct device *dev, struct device_attribute *attr, char *buf) +name_show(struct device *dev, const struct device_attribute *attr, char *buf) { return sysfs_emit(buf, "%s\n", to_hwmon_device(dev)->name); } -static DEVICE_ATTR_RO(name); +static const DEVICE_ATTR_RO(name); static ssize_t -label_show(struct device *dev, struct device_attribute *attr, char *buf) +label_show(struct device *dev, const struct device_attribute *attr, char *buf) { return sysfs_emit(buf, "%s\n", to_hwmon_device(dev)->label); } -static DEVICE_ATTR_RO(label); +static const DEVICE_ATTR_RO(label); -static struct attribute *hwmon_dev_attrs[] = { +static const struct attribute *const hwmon_dev_attrs[] = { &dev_attr_name.attr, &dev_attr_label.attr, NULL }; static umode_t hwmon_dev_attr_is_visible(struct kobject *kobj, - struct attribute *attr, int n) + const struct attribute *attr, int n) { struct device *dev = kobj_to_dev(kobj); struct hwmon_device *hdev = to_hwmon_device(dev); @@ -106,8 +106,8 @@ static umode_t hwmon_dev_attr_is_visible(struct kobject *kobj, } static const struct attribute_group hwmon_dev_attr_group = { - .attrs = hwmon_dev_attrs, - .is_visible = hwmon_dev_attr_is_visible, + .attrs_const = hwmon_dev_attrs, + .is_visible_const = hwmon_dev_attr_is_visible, }; static const struct attribute_group *hwmon_dev_attr_groups[] = { @@ -115,13 +115,13 @@ static const struct attribute_group *hwmon_dev_attr_groups[] = { NULL }; -static void hwmon_free_attrs(struct attribute **attrs) +static void hwmon_free_attrs(const struct attribute *const *attrs) { int i; for (i = 0; attrs[i]; i++) { - struct device_attribute *dattr = to_dev_attr(attrs[i]); - struct hwmon_device_attribute *hattr = to_hwmon_attr(dattr); + const struct device_attribute *dattr = to_dev_attr(attrs[i]); + const struct hwmon_device_attribute *hattr = to_hwmon_attr(dattr); kfree(hattr); } @@ -132,8 +132,8 @@ static void hwmon_dev_release(struct device *dev) { struct hwmon_device *hwdev = to_hwmon_device(dev); - if (hwdev->group.attrs) - hwmon_free_attrs(hwdev->group.attrs); + if (hwdev->group.attrs_const) + hwmon_free_attrs(hwdev->group.attrs_const); kfree(hwdev->groups); kfree(hwdev->label); kfree(hwdev); @@ -341,7 +341,7 @@ static int hwmon_match_device(struct device *dev, const void *data) return dev->class == &hwmon_class; } -static ssize_t pec_show(struct device *dev, struct device_attribute *dummy, +static ssize_t pec_show(struct device *dev, const struct device_attribute *dummy, char *buf) { struct i2c_client *client = to_i2c_client(dev); @@ -349,7 +349,7 @@ static ssize_t pec_show(struct device *dev, struct device_attribute *dummy, return sysfs_emit(buf, "%d\n", !!(client->flags & I2C_CLIENT_PEC)); } -static ssize_t pec_store(struct device *dev, struct device_attribute *devattr, +static ssize_t pec_store(struct device *dev, const struct device_attribute *devattr, const char *buf, size_t count) { struct i2c_client *client = to_i2c_client(dev); @@ -389,7 +389,7 @@ static ssize_t pec_store(struct device *dev, struct device_attribute *devattr, return err; } -static DEVICE_ATTR_RW(pec); +static const DEVICE_ATTR_RW(pec); static void hwmon_remove_pec(void *dev) { @@ -424,9 +424,9 @@ static int hwmon_pec_register(struct device *hdev) /* sysfs attribute management */ static ssize_t hwmon_attr_show(struct device *dev, - struct device_attribute *devattr, char *buf) + const struct device_attribute *devattr, char *buf) { - struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + const struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); struct hwmon_device *hwdev = to_hwmon_device(dev); s64 val64; long val; @@ -449,10 +449,10 @@ static ssize_t hwmon_attr_show(struct device *dev, } static ssize_t hwmon_attr_show_string(struct device *dev, - struct device_attribute *devattr, + const struct device_attribute *devattr, char *buf) { - struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + const struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); struct hwmon_device *hwdev = to_hwmon_device(dev); enum hwmon_sensor_types type = hattr->type; const char *s; @@ -472,10 +472,10 @@ static ssize_t hwmon_attr_show_string(struct device *dev, } static ssize_t hwmon_attr_store(struct device *dev, - struct device_attribute *devattr, + const struct device_attribute *devattr, const char *buf, size_t count) { - struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + const struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); struct hwmon_device *hwdev = to_hwmon_device(dev); long val; int ret; @@ -509,12 +509,12 @@ static bool is_string_attr(enum hwmon_sensor_types type, u32 attr) (type == hwmon_fan && attr == hwmon_fan_label); } -static struct attribute *hwmon_genattr(const void *drvdata, - enum hwmon_sensor_types type, - u32 attr, - int index, - const char *template, - const struct hwmon_ops *ops) +static const struct attribute *hwmon_genattr(const void *drvdata, + enum hwmon_sensor_types type, + u32 attr, + int index, + const char *template, + const struct hwmon_ops *ops) { struct hwmon_device_attribute *hattr; struct device_attribute *dattr; @@ -551,8 +551,8 @@ static struct attribute *hwmon_genattr(const void *drvdata, hattr->ops = ops; dattr = &hattr->dev_attr; - dattr->show = is_string ? hwmon_attr_show_string : hwmon_attr_show; - dattr->store = hwmon_attr_store; + dattr->show_const = is_string ? hwmon_attr_show_string : hwmon_attr_show; + dattr->store_const = hwmon_attr_store; a = &dattr->attr; sysfs_attr_init(a); @@ -830,7 +830,7 @@ static int hwmon_num_channel_attrs(const struct hwmon_channel_info *info) } static int hwmon_genattrs(const void *drvdata, - struct attribute **attrs, + const struct attribute **attrs, const struct hwmon_ops *ops, const struct hwmon_channel_info *info) { @@ -849,7 +849,7 @@ static int hwmon_genattrs(const void *drvdata, u32 attr; while (attr_mask) { - struct attribute *a; + const struct attribute *a; attr = __ffs(attr_mask); attr_mask &= ~BIT(attr); @@ -868,11 +868,11 @@ static int hwmon_genattrs(const void *drvdata, return aindex; } -static struct attribute ** +static const struct attribute ** __hwmon_create_attrs(const void *drvdata, const struct hwmon_chip_info *chip) { int ret, i, aindex = 0, nattrs = 0; - struct attribute **attrs; + const struct attribute **attrs; for (i = 0; chip->info[i]; i++) nattrs += hwmon_num_channel_attrs(chip->info[i]); @@ -927,7 +927,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, hdev = &hwdev->dev; if (chip) { - struct attribute **attrs; + const struct attribute **attrs; int ngroups = 2; /* terminating NULL plus &hwdev->groups */ if (groups) @@ -946,7 +946,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, goto free_hwmon; } - hwdev->group.attrs = attrs; + hwdev->group.attrs_const = attrs; ngroups = 0; hwdev->groups[ngroups++] = &hwdev->group; diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c --- a/drivers/hwmon/ina2xx.c +++ b/drivers/hwmon/ina2xx.c @@ -124,6 +124,7 @@ static const struct regmap_config ina2xx_regmap_config = { enum ina2xx_ids { ina219, ina226, + ina232, ina234, ina260, sy24655 @@ -210,6 +211,20 @@ static const struct ina2xx_config ina2xx_config[] = { .current_shift = 4, .has_update_interval = true, }, + [ina232] = { + .config_default = INA226_CONFIG_DEFAULT, + .calibration_value = 2048, + .shunt_div = 400, + .shunt_voltage_shift = 0, + .bus_voltage_shift = 0, + .bus_voltage_lsb = 1600, + .power_lsb_factor = 32, + .has_alerts = true, + .has_ishunt = false, + .has_power_average = false, + .current_shift = 0, + .has_update_interval = true, + }, [ina260] = { .config_default = INA260_CONFIG_DEFAULT, .shunt_div = 400, @@ -1123,6 +1138,7 @@ static const struct i2c_device_id ina2xx_id[] = { { .name = "ina226", .driver_data = ina226 }, { .name = "ina230", .driver_data = ina226 }, { .name = "ina231", .driver_data = ina226 }, + { .name = "ina232", .driver_data = ina232 }, { .name = "ina234", .driver_data = ina234 }, { .name = "ina260", .driver_data = ina260 }, { .name = "sy24655", .driver_data = sy24655 }, @@ -1155,6 +1171,10 @@ static const struct of_device_id __maybe_unused ina2xx_of_match[] = { .compatible = "ti,ina231", .data = (void *)ina226 }, + { + .compatible = "ti,ina232", + .data = (void *)ina232 + }, { .compatible = "ti,ina234", .data = (void *)ina234 diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -207,6 +207,10 @@ static const char *k10temp_temp_label[] = { "Tccd10", "Tccd11", "Tccd12", + "Tccd13", + "Tccd14", + "Tccd15", + "Tccd16", }; static int k10temp_read_labels(struct device *dev, @@ -243,7 +247,7 @@ static int k10temp_read_temp(struct device *dev, u32 attr, int channel, if (*val < 0 && !data->disp_negative) *val = 0; break; - case 2 ... 13: /* Tccd{1-12} */ + case 2 ... 17: /* Tccd{1-16} */ ret = read_ccd_temp_reg(data, channel - 2, ®val); if (ret) @@ -384,6 +388,10 @@ static const struct hwmon_channel_info * const 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 }; @@ -515,6 +523,10 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) } } else if (boot_cpu_data.x86 == 0x1a) { switch (boot_cpu_data.x86_model) { + case 0x00 ... 0x2f: /* Zen5 Turin */ + data->ccd_offset = 0x1F0; + k10temp_get_ccd_support(data, 16); + break; case 0x40 ... 0x4f: /* Zen5 Ryzen Desktop */ data->ccd_offset = 0x308; k10temp_get_ccd_support(data, 8); diff --git a/drivers/hwmon/kb9002.c b/drivers/hwmon/kb9002.c new file mode 100644 --- /dev/null +++ b/drivers/hwmon/kb9002.c @@ -0,0 +1,460 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Kandou KB9002 PCIe 5.0 retimer hwmon driver. + * + * The retimer exposes a system management bus (SMBus 3.0 with PEC) + * target for firmware-managed status registers. This driver assumes + * the chip is strapped to SMBus mode and exports the aggregated + * maximum die temperature as hwmon temp1_input (millidegrees Celsius) + * plus the firmware version and boot status under debugfs. + * + * The raw-I2C path (kb9002_i2c_read/write, used only at probe to switch + * the host interface) carries the 32-bit register address and data + * big-endian. The SMBus path (kb9002_fw_read/kb9002_smbus_hw_read) + * carries the register address and returned data little-endian. + * + * Datasheet: Kandou KB9002 PCIe retimer (KA-015171-PD). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define KB9002_DEV_NAME "kb9002" + +/* + * SMBus read command codes. Each read is a two-phase PEC-protected + * transaction: prime writes the target address, data reads it back with + * the register contents. FW reads use a 16-bit address, HW reads 32-bit. + */ +#define KB9002_CC_FW_READ_PRIME 0x82 +#define KB9002_CC_FW_READ_DATA 0x81 +#define KB9002_CC_HW_READ_PRIME 0x8a +#define KB9002_CC_HW_READ_DATA 0x89 + +/* Firmware register offsets (16-bit). */ +#define KB9002_FW_REG_VID 0x0004 +#define KB9002_FW_REG_FW_VERSION 0x0500 +#define KB9002_FW_REG_TEMP_MAXIMUM 0x0550 + +#define KB9002_VID_MASK GENMASK(31, 16) +#define KB9002_VID_KANDOU 0x1e6f + +/* Firmware boot status: 0xe8 in the top byte means init completed OK. */ +#define KB9002_HW_REG_FW_BOOT_STATUS 0xe0090008 +#define KB9002_FW_BOOT_STATUS_OK_MSB 0xe8 + +/* + * Hardware registers reached over raw I2C (32-bit addressing). The + * host-interface bit selects SMBus (set) vs raw-I2C target; parts + * strapped to raw I2C need it set before SMBus access works. + */ +#define KB9002_HW_REG_REVID 0x00480004 +#define KB9002_HW_REG_HOST_IF 0x00480008 +#define KB9002_HOST_IF_SMBUS BIT(1) + +#define KB9002_REVID_MASK GENMASK(7, 0) +#define KB9002_REVID_B0 0x10 +#define KB9002_REVID_B1 0x11 + +/* Retries to drain a stray leading 0xff from the raw-I2C FIFO. */ +#define KB9002_REVID_READ_RETRIES 16 + +/* Temperature: 32-bit Q16.16 absolute Kelvin. */ +#define KB9002_TEMP_FRAC_BITS 16 +#define KB9002_ABS_ZERO_MILLI_C (-273150) + +/* Firmware takes up to ~2s to respond after a host-interface change. */ +#define KB9002_FW_READY_POLL_US (25 * USEC_PER_MSEC) +#define KB9002_FW_READY_TIMEOUT_US (2 * USEC_PER_SEC) + +struct kb9002_data { + struct i2c_client *client; + struct device *hwmon_dev; +}; + +/* Raw-I2C read: write the 32-bit BE address, then read 4 BE data bytes. */ +static int kb9002_i2c_read(struct i2c_client *client, u32 reg, u32 *val) +{ + u8 addr[4]; + u8 rbuf[4]; + struct i2c_msg msgs[2] = { + { + .addr = client->addr, + .flags = 0, + .len = sizeof(addr), + .buf = addr, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = sizeof(rbuf), + .buf = rbuf, + }, + }; + int ret; + + put_unaligned_be32(reg, addr); + + ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + return ret; + if (ret != ARRAY_SIZE(msgs)) + return -EIO; + + *val = get_unaligned_be32(rbuf); + return 0; +} + +/* Raw-I2C write: 4 BE address bytes followed by 4 BE data bytes. */ +static int kb9002_i2c_write(struct i2c_client *client, u32 reg, u32 val) +{ + u8 buf[8]; + struct i2c_msg msg = { + .addr = client->addr, + .flags = 0, + .len = sizeof(buf), + .buf = buf, + }; + int ret; + + put_unaligned_be32(reg, &buf[0]); + put_unaligned_be32(val, &buf[4]); + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) + return ret; + if (ret != 1) + return -EIO; + + return 0; +} + +/* + * Read the silicon revision ID. A fresh FIFO may start with a stray + * 0xff that shifts the result, so drain one byte between retries until + * the top byte is no longer 0xff. + */ +static int kb9002_read_revid(struct i2c_client *client, u32 *revid) +{ + u8 dummy; + int ret; + int i; + + for (i = 0; i < KB9002_REVID_READ_RETRIES; i++) { + ret = kb9002_i2c_read(client, KB9002_HW_REG_REVID, revid); + if (ret) + return ret; + if ((*revid >> 24) != 0xff) + return 0; + /* Drain one byte from the chip to re-align the I2C FIFO. */ + i2c_master_recv(client, &dummy, 1); + } + + return -EIO; +} + +/* + * Read a 32-bit firmware register over SMBus: block-write the 16-bit LE + * address, then block-read the echoed address plus 4 LE data bytes. + */ +static int kb9002_fw_read(struct kb9002_data *data, u16 reg, u32 *val) +{ + struct i2c_client *client = data->client; + u8 addr[2]; + u8 rbuf[I2C_SMBUS_BLOCK_MAX]; + int ret; + + put_unaligned_le16(reg, addr); + + ret = i2c_smbus_write_block_data(client, KB9002_CC_FW_READ_PRIME, + sizeof(addr), addr); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_block_data(client, KB9002_CC_FW_READ_DATA, rbuf); + if (ret < 0) + return ret; + if (ret < (int)(sizeof(addr) + sizeof(*val))) + return -EIO; + + *val = get_unaligned_le32(&rbuf[sizeof(addr)]); + return 0; +} + +/* Like kb9002_fw_read but for a hardware register (32-bit LE address). */ +static int kb9002_smbus_hw_read(struct kb9002_data *data, u32 reg, u32 *val) +{ + struct i2c_client *client = data->client; + u8 addr[4]; + u8 rbuf[I2C_SMBUS_BLOCK_MAX]; + int ret; + + put_unaligned_le32(reg, addr); + + ret = i2c_smbus_write_block_data(client, KB9002_CC_HW_READ_PRIME, + sizeof(addr), addr); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_block_data(client, KB9002_CC_HW_READ_DATA, rbuf); + if (ret < 0) + return ret; + if (ret < (int)(sizeof(addr) + sizeof(*val))) + return -EIO; + + *val = get_unaligned_le32(&rbuf[sizeof(addr)]); + return 0; +} + +/* + * Switch the host interface from raw-I2C to SMBus and wait for firmware + * to come back up. Called only when SMBus access failed in probe, i.e. + * the chip is strapped to raw-I2C mode. Confirms the revision, sets the + * SMBus-mode bit, then polls until firmware responds again. + */ +static int kb9002_enable_smbus_target(struct kb9002_data *data) +{ + struct i2c_client *client = data->client; + u32 revid; + u32 val; + int op_ret; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return dev_err_probe(&client->dev, -ENODEV, + "raw I2C required to switch to SMBus mode\n"); + + ret = kb9002_read_revid(client, &revid); + if (ret) + return dev_err_probe(&client->dev, ret, + "revision ID read failed\n"); + + switch (FIELD_GET(KB9002_REVID_MASK, revid)) { + case KB9002_REVID_B0: + case KB9002_REVID_B1: + break; + default: + return dev_err_probe(&client->dev, -ENODEV, + "unsupported revision ID 0x%08x\n", revid); + } + + ret = kb9002_i2c_read(client, KB9002_HW_REG_HOST_IF, &val); + if (ret) + return dev_err_probe(&client->dev, ret, + "host interface read failed\n"); + + val |= KB9002_HOST_IF_SMBUS; + + ret = kb9002_i2c_write(client, KB9002_HW_REG_HOST_IF, val); + if (ret) + return dev_err_probe(&client->dev, ret, + "host interface write failed\n"); + + /* Wait until firmware re-initialisation completes. */ + ret = read_poll_timeout(kb9002_fw_read, op_ret, op_ret == 0, + KB9002_FW_READY_POLL_US, + KB9002_FW_READY_TIMEOUT_US, true, + data, KB9002_FW_REG_VID, &val); + if (ret) + return dev_err_probe(&client->dev, ret, + "firmware not responding over SMBus\n"); + + return 0; +} + +/* Convert Q16.16 absolute Kelvin to millidegrees Celsius. */ +static long kb9002_temp_to_milli_c(u32 raw) +{ + s64 milli_k = ((s64)raw * 1000) >> KB9002_TEMP_FRAC_BITS; + + return (long)milli_k + KB9002_ABS_ZERO_MILLI_C; +} + +static int kb9002_read_temp(struct kb9002_data *data, long *val) +{ + u32 raw; + int ret; + + ret = kb9002_fw_read(data, KB9002_FW_REG_TEMP_MAXIMUM, &raw); + if (ret) + return ret; + + *val = kb9002_temp_to_milli_c(raw); + return 0; +} + +static umode_t kb9002_is_visible(const void *drvdata, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + return 0444; +} + +static int kb9002_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct kb9002_data *data = dev_get_drvdata(dev); + + if (type == hwmon_temp && attr == hwmon_temp_input) + return kb9002_read_temp(data, val); + + return -EOPNOTSUPP; +} + +static const struct hwmon_ops kb9002_hwmon_ops = { + .is_visible = kb9002_is_visible, + .read = kb9002_read, +}; + +static const struct hwmon_channel_info * const kb9002_hwmon_info[] = { + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), + NULL, +}; + +static const struct hwmon_chip_info kb9002_chip_info = { + .ops = &kb9002_hwmon_ops, + .info = kb9002_hwmon_info, +}; + +static int kb9002_fw_version_show(struct seq_file *s, void *unused) +{ + struct kb9002_data *data = s->private; + u32 ver; + int ret; + + guard(hwmon_lock)(data->hwmon_dev); + + ret = kb9002_fw_read(data, KB9002_FW_REG_FW_VERSION, &ver); + if (ret) + return ret; + + seq_printf(s, "%u.%02u.%02u.%u\n", + (ver >> 24) & 0xff, (ver >> 16) & 0xff, + (ver >> 8) & 0xff, (ver >> 0) & 0xff); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(kb9002_fw_version); + +static int kb9002_fw_load_status_show(struct seq_file *s, void *unused) +{ + struct kb9002_data *data = s->private; + u32 status; + int ret; + + guard(hwmon_lock)(data->hwmon_dev); + + ret = kb9002_smbus_hw_read(data, KB9002_HW_REG_FW_BOOT_STATUS, &status); + if (ret) + return ret; + + seq_printf(s, "%s\n", + (status >> 24) == KB9002_FW_BOOT_STATUS_OK_MSB ? + "normal" : "abnormal"); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(kb9002_fw_load_status); + +static void kb9002_debugfs_init(struct kb9002_data *data) +{ + struct dentry *dir = data->client->debugfs; + + debugfs_create_file("fw_ver", 0444, dir, data, + &kb9002_fw_version_fops); + debugfs_create_file("fw_load_status", 0444, dir, data, + &kb9002_fw_load_status_fops); +} + +static int kb9002_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct kb9002_data *data; + struct device *hwmon_dev; + u32 vid; + int ret; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BLOCK_DATA | + I2C_FUNC_SMBUS_PEC)) + return -ENODEV; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->client = client; + + /* All firmware register accesses are PEC-protected. */ + client->flags |= I2C_CLIENT_PEC; + + i2c_set_clientdata(client, data); + + /* + * Try SMBus first. If the chip is strapped to raw-I2C mode it + * will not respond to SMBus framing, so fall back to switching + * the host interface over raw I2C and retry. + */ + ret = kb9002_fw_read(data, KB9002_FW_REG_VID, &vid); + if (ret) { + dev_dbg(dev, "SMBus probe failed (%d), trying raw-I2C host-interface switch\n", + ret); + ret = kb9002_enable_smbus_target(data); + if (ret) + return ret; + ret = kb9002_fw_read(data, KB9002_FW_REG_VID, &vid); + if (ret) + return dev_err_probe(dev, ret, + "VID read failed after host-interface switch\n"); + } + if (FIELD_GET(KB9002_VID_MASK, vid) != KB9002_VID_KANDOU) + return dev_err_probe(dev, -ENODEV, + "unexpected VID 0x%08x\n", vid); + + hwmon_dev = devm_hwmon_device_register_with_info(dev, KB9002_DEV_NAME, + data, + &kb9002_chip_info, + NULL); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + data->hwmon_dev = hwmon_dev; + kb9002_debugfs_init(data); + return 0; +} + +static const struct i2c_device_id kb9002_id[] = { + { .name = KB9002_DEV_NAME }, + { } +}; +MODULE_DEVICE_TABLE(i2c, kb9002_id); + +static const struct of_device_id kb9002_of_match[] = { + { .compatible = "kandou,kb9002" }, + { } +}; +MODULE_DEVICE_TABLE(of, kb9002_of_match); + +static struct i2c_driver kb9002_driver = { + .driver = { + .name = KB9002_DEV_NAME, + .of_match_table = kb9002_of_match, + }, + .probe = kb9002_probe, + .id_table = kb9002_id, +}; +module_i2c_driver(kb9002_driver); + +MODULE_AUTHOR("Andy Chung "); +MODULE_DESCRIPTION("Kandou KB9002 PCIe retimer hwmon driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c --- a/drivers/hwmon/lm90.c +++ b/drivers/hwmon/lm90.c @@ -101,6 +101,7 @@ #include #include #include +#include #include #include #include @@ -108,7 +109,7 @@ #include #include #include -#include +#include #include #include #include @@ -295,7 +296,7 @@ static const struct i2c_device_id lm90_id[] = { }; MODULE_DEVICE_TABLE(i2c, lm90_id); -static const struct of_device_id __maybe_unused lm90_of_match[] = { +static const struct of_device_id lm90_of_match[] = { { .compatible = "adi,adm1032", .data = (void *)adm1032 @@ -2602,7 +2603,6 @@ static void lm90_stop_work(void *_data) static int lm90_init_client(struct i2c_client *client, struct lm90_data *data) { - struct device_node *np = client->dev.of_node; int config, convrate; if (data->flags & LM90_HAVE_CONVRATE) { @@ -2626,7 +2626,7 @@ static int lm90_init_client(struct i2c_client *client, struct lm90_data *data) /* Check Temperature Range Select */ if (data->flags & LM90_HAVE_EXTENDED_TEMP) { - if (of_property_read_bool(np, "ti,extended-range-enable")) + if (device_property_read_bool(&client->dev, "ti,extended-range-enable")) config |= 0x04; if (!(config & 0x04)) data->flags &= ~LM90_HAVE_EXTENDED_TEMP; @@ -2692,36 +2692,41 @@ static irqreturn_t lm90_irq_thread(int irq, void *dev_id) return IRQ_NONE; } -static int lm90_probe_channel_from_dt(struct i2c_client *client, - struct device_node *child, - struct lm90_data *data) +static int lm90_probe_channel(struct i2c_client *client, + struct fwnode_handle *child, + struct lm90_data *data) { u32 id; s32 val; int err; struct device *dev = &client->dev; - err = of_property_read_u32(child, "reg", &id); + err = fwnode_property_read_u32(child, "reg", &id); if (err) { - dev_err(dev, "missing reg property of %pOFn\n", child); + dev_err(dev, "missing reg property of %pfw\n", child); return err; } if (id >= MAX_CHANNELS) { - dev_err(dev, "invalid reg property value %d in %pOFn\n", id, child); + dev_err(dev, "invalid reg property value %d in %pfw\n", id, child); return -EINVAL; } - err = of_property_read_string(child, "label", &data->channel_label[id]); + err = fwnode_property_read_string(child, "label", &data->channel_label[id]); if (err == -ENODATA || err == -EILSEQ) { - dev_err(dev, "invalid label property in %pOFn\n", child); + dev_err(dev, "invalid label property in %pfw\n", child); return err; } if (data->channel_label[id]) data->channel_config[id] |= HWMON_T_LABEL; - err = of_property_read_s32(child, "temperature-offset-millicelsius", &val); + /* + * fwnode_property_read_u32() has no signed equivalent. + * temperature-offset-millicelsius is signed, so read and reinterpret it as s32 to + * preserve negative offsets values (same behavior as the old of_property_read_s32()). + */ + err = fwnode_property_read_u32(child, "temperature-offset-millicelsius", (u32 *)&val); if (!err) { if (id == 0) { dev_err(dev, "temperature-offset-millicelsius can't be set for internal channel\n"); @@ -2739,18 +2744,17 @@ static int lm90_probe_channel_from_dt(struct i2c_client *client, return 0; } -static int lm90_parse_dt_channel_info(struct i2c_client *client, - struct lm90_data *data) +static int lm90_parse_channel_info(struct i2c_client *client, + struct lm90_data *data) { int err; struct device *dev = &client->dev; - const struct device_node *np = dev->of_node; - for_each_child_of_node_scoped(np, child) { - if (strcmp(child->name, "channel")) + device_for_each_child_node_scoped(dev, child) { + if (!fwnode_name_eq(child, "channel")) continue; - err = lm90_probe_channel_from_dt(client, child, data); + err = lm90_probe_channel(client, child, data); if (err) return err; } @@ -2887,12 +2891,10 @@ static int lm90_probe(struct i2c_client *client) /* Set maximum conversion rate */ data->max_convrate = lm90_params[data->kind].max_convrate; - /* Parse device-tree channel information */ - if (client->dev.of_node) { - err = lm90_parse_dt_channel_info(client, data); - if (err) - return err; - } + /* Parse channel information */ + err = lm90_parse_channel_info(client, data); + if (err) + return err; /* Initialize the LM90 chip */ err = lm90_init_client(client, data); @@ -2918,10 +2920,8 @@ static int lm90_probe(struct i2c_client *client) err = devm_request_threaded_irq(dev, client->irq, NULL, lm90_irq_thread, IRQF_ONESHOT, "lm90", client); - if (err < 0) { - dev_err(dev, "cannot request IRQ %d\n", client->irq); + if (err < 0) return err; - } } return 0; @@ -2985,7 +2985,7 @@ static struct i2c_driver lm90_driver = { .class = I2C_CLASS_HWMON, .driver = { .name = "lm90", - .of_match_table = of_match_ptr(lm90_of_match), + .of_match_table = lm90_of_match, .pm = pm_sleep_ptr(&lm90_pm_ops), }, .probe = lm90_probe, diff --git a/drivers/hwmon/nct6775-core.c b/drivers/hwmon/nct6775-core.c --- a/drivers/hwmon/nct6775-core.c +++ b/drivers/hwmon/nct6775-core.c @@ -33,6 +33,7 @@ * (0xd451) * nct6798d 14 7 7 2+6 0xd428 0xc1 0x5ca3 * (0xd429) + * nct5585d 14 7 7 2+6 0xd428 0xc1 0x5ca3 * nct6796d-s 18 7 7 6+2 0xd801 0xc1 0x5ca3 * nct6799d-r 18 7 7 6+2 0xd802 0xc1 0x5ca3 * diff --git a/drivers/hwmon/nct6775-platform.c b/drivers/hwmon/nct6775-platform.c --- a/drivers/hwmon/nct6775-platform.c +++ b/drivers/hwmon/nct6775-platform.c @@ -34,7 +34,7 @@ static const char * const nct6775_sio_names[] __initconst = { [nct6795] = "NCT6795D", [nct6796] = "NCT6796D", [nct6797] = "NCT6797D", - [nct6798] = "NCT6798D", + [nct6798] = "NCT6798D/NCT5585D", [nct6799] = "NCT6796D-S/NCT6799D-R", }; diff --git a/drivers/hwmon/npcm750-pwm-fan.c b/drivers/hwmon/npcm750-pwm-fan.c --- a/drivers/hwmon/npcm750-pwm-fan.c +++ b/drivers/hwmon/npcm750-pwm-fan.c @@ -996,10 +996,8 @@ static int npcm7xx_pwm_fan_probe(struct platform_device *pdev) sprintf(name, "NPCM7XX-FAN-MD%d", i); ret = devm_request_irq(dev, data->fan_irq[i], npcm7xx_fan_isr, 0, name, (void *)data); - if (ret) { - dev_err(dev, "register IRQ fan%d failed\n", i); + if (ret) return ret; - } } for_each_child_of_node_scoped(np, child) { diff --git a/drivers/hwmon/nzxt-kraken3.c b/drivers/hwmon/nzxt-kraken3.c --- a/drivers/hwmon/nzxt-kraken3.c +++ b/drivers/hwmon/nzxt-kraken3.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * hwmon driver for NZXT Kraken X53/X63/X73, Z53/Z63/Z73 and 2023/2023 Elite all in one coolers. + * hwmon driver for NZXT Kraken X53/X63/X73, Z53/Z63/Z73, 2023/2023 Elite, and + * 2024 Elite all in one coolers. * X53 and Z53 in code refer to all models in their respective series (shortened for brevity). * 2023 models use the Z53 code paths. * @@ -25,6 +26,7 @@ #define USB_PRODUCT_ID_Z53 0x3008 #define USB_PRODUCT_ID_KRAKEN2023 0x300E #define USB_PRODUCT_ID_KRAKEN2023_ELITE 0x300C +#define USB_PRODUCT_ID_KRAKEN2024_ELITE 0x3012 enum kinds { X53, Z53, KRAKEN2023 } __packed; enum pwm_enable { off, manual, curve } __packed; @@ -927,6 +929,10 @@ static int kraken3_probe(struct hid_device *hdev, const struct hid_device_id *id priv->kind = KRAKEN2023; device_name = "kraken2023elite"; break; + case USB_PRODUCT_ID_KRAKEN2024_ELITE: + priv->kind = KRAKEN2023; + device_name = "kraken2024elite"; + break; default: ret = -ENODEV; goto fail_and_close; @@ -994,6 +1000,7 @@ static const struct hid_device_id kraken3_table[] = { { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_Z53) }, { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2023) }, { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2023_ELITE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_NZXT, USB_PRODUCT_ID_KRAKEN2024_ELITE) }, { } }; diff --git a/drivers/hwmon/peci/common.h b/drivers/hwmon/peci/common.h --- a/drivers/hwmon/peci/common.h +++ b/drivers/hwmon/peci/common.h @@ -31,7 +31,7 @@ struct peci_sensor_data { /** * peci_sensor_need_update() - check whether sensor update is needed or not - * @sensor: pointer to sensor data struct + * @state: pointer to sensor state struct * * Return: true if update is needed, false if not. */ @@ -44,7 +44,7 @@ static inline bool peci_sensor_need_update(struct peci_sensor_state *state) /** * peci_sensor_mark_updated() - mark the sensor is updated - * @sensor: pointer to sensor data struct + * @state: pointer to sensor state struct */ static inline void peci_sensor_mark_updated(struct peci_sensor_state *state) { diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -52,8 +52,8 @@ config SENSORS_ADM1275 help If you say yes here you get hardware monitoring support for Analog Devices ADM1075, ADM1272, ADM1273, ADM1275, ADM1276, ADM1278, ADM1281, - ADM1293, ADM1294 and SQ24905C Hot-Swap Controller and - Digital Power Monitors. + ADM1293, ADM1294, ROHM BD12780, ROHM BD12790, and SQ24905C + Hot-Swap Controller and Digital Power Monitors. This driver can also be built as a module. If so, the module will be called adm1275. @@ -394,10 +394,10 @@ config SENSORS_MAX20751 be called max20751. config SENSORS_MAX20830 - tristate "Analog Devices MAX20830" + tristate "Analog Devices MAX20830 and compatibles" help If you say yes here you get hardware monitoring support for Analog - Devices MAX20830. + Devices MAX20830, MAX20830C, and MAX20840C. This driver can also be built as a module. If so, the module will be called max20830. @@ -434,8 +434,9 @@ config SENSORS_MAX34440 tristate "Maxim MAX34440 and compatibles" help If you say yes here you get hardware monitoring support for Maxim - MAX34440, MAX34441, MAX34446, MAX34451, MAX34460, and MAX34461. - Other compatible are ADPM12160, and ADPM12200. + MAX34440, MAX34441, MAX34446, MAX34451, MAX34452, MAX34460, and + MAX34461. Other compatible devices are ADPM12160, ADPM12200, and + ADPM12250. This driver can also be built as a module. If so, the module will be called max34440. @@ -607,6 +608,15 @@ config SENSORS_MPQ7932 This driver can also be built as a module. If so, the module will be called mpq7932. +config SENSORS_MPQ82D00 + tristate "MPS MPQ82D00" + help + If you say yes here you get hardware monitoring functionality support + for power management IC MPS MPQ82D00. + + This driver can also be built as a module. If so, the module will + be called mpq82d00. + config SENSORS_MPQ8785 tristate "MPS MPQ8785" help @@ -616,6 +626,37 @@ config SENSORS_MPQ8785 This driver can also be built as a module. If so, the module will be called mpq8785. +config SENSORS_MPQ8646 + tristate "MPS MPQ8646" + depends on REGULATOR || !REGULATOR + depends on NVMEM || !NVMEM + help + If you say yes here you get hardware monitoring support for the + Monolithic Power Systems MPQ8646. + + This driver can also be built as a module. If so, the module + will be called mpq8646. + +config SENSORS_MPQ8646_DEBUG_UNSAFE + bool "MPQ8646 unsafe write/provisioning debugfs (DANGEROUS)" + depends on SENSORS_MPQ8646 && DEBUG_FS + default n + help + Expose additional WRITE-able debugfs files in the client's + pmbus debugfs directory, + /sys/kernel/debug/i2c/i2c-/-/ + + These are provisioning and bring-up aids. On many designs the + MPQ8646 powers the SoC core rail, so a wrong write can brown out + or permanently mis-provision the board. + + WARNING: Wrong register writes can and likely will physically + damage or destroy the chip and/or the board. + + It shall never be set in a production, shipping, or default configuration. + + If unsure, say N. + config SENSORS_PIM4328 tristate "Flex PIM4328 and compatibles" help @@ -677,6 +718,25 @@ config SENSORS_STEF48H28 This driver can also be built as a module. If so, the module will be called stef48h28. +config SENSORS_SQ24860 + tristate "Silergy SQ24860" + help + If you say yes here you get hardware monitoring support for Silergy + SQ24860 eFuse. + + This driver can also be built as a module. If so, the module will + be called sq24860. + +config SENSORS_SQ24860_REGULATOR + bool "Regulator support for SQ24860" + depends on SENSORS_SQ24860 && REGULATOR + default SENSORS_SQ24860 + help + If you say yes here you get regulator support for Silergy SQ24860. + The regulator is registered through the PMBus regulator framework and + can be used to control the output exposed by the device. + This option is only useful if regulator framework support is needed. + config SENSORS_STPDDC60 tristate "ST STPDDC60" help @@ -767,6 +827,15 @@ config SENSORS_UCD9200 This driver can also be built as a module. If so, the module will be called ucd9200. +config SENSORS_VT7505 + tristate "Analog Devices MAX16545, MAX16550 and Volterra VT7505" + help + If you say yes here you get hardware monitoring support for Analog + Devices MAX16545, MAX16550 and Volterra VT7505 PMBus controllers. + + This driver can also be built as a module. If so, the module will + be called vt7505. + config SENSORS_XDP710 tristate "Infineon XDP710 family" help @@ -803,6 +872,16 @@ config SENSORS_XDPE1A2G7B This driver can also be built as a module. If so, the module will be called xdpe1a2g7b. +config SENSORS_XDPE1A2G7B_REGULATOR + bool "Regulator support for XDPE1A2G7B and compatibles" + depends on SENSORS_XDPE1A2G7B && REGULATOR + help + If you say yes here you get regulator support for Infineon + XDPE1A2G5B and XDPE1A2G7B multi-phase digital controllers. + + This enables the controllers to be used as regulator devices, + providing voltage control through the regulator framework. + config SENSORS_XDPE122 tristate "Infineon XDPE122 family" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -59,12 +59,15 @@ obj-$(CONFIG_SENSORS_MP5990) += mp5990.o obj-$(CONFIG_SENSORS_MP9941) += mp9941.o obj-$(CONFIG_SENSORS_MP9945) += mp9945.o obj-$(CONFIG_SENSORS_MPQ7932) += mpq7932.o +obj-$(CONFIG_SENSORS_MPQ82D00) += mpq82d00.o obj-$(CONFIG_SENSORS_MPQ8785) += mpq8785.o +obj-$(CONFIG_SENSORS_MPQ8646) += mpq8646.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 obj-$(CONFIG_SENSORS_STEF48H28) += stef48h28.o +obj-$(CONFIG_SENSORS_SQ24860) += sq24860.o obj-$(CONFIG_SENSORS_STPDDC60) += stpddc60.o obj-$(CONFIG_SENSORS_TDA38640) += tda38640.o obj-$(CONFIG_SENSORS_TPS25990) += tps25990.o @@ -73,6 +76,7 @@ obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o obj-$(CONFIG_SENSORS_TPS546D24) += tps546d24.o obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o +obj-$(CONFIG_SENSORS_VT7505) += vt7505.o obj-$(CONFIG_SENSORS_XDP710) += xdp710.o obj-$(CONFIG_SENSORS_XDP720) += xdp720.o obj-$(CONFIG_SENSORS_XDPE122) += xdpe12284.o diff --git a/drivers/hwmon/pmbus/acbel-fsg032.c b/drivers/hwmon/pmbus/acbel-fsg032.c --- a/drivers/hwmon/pmbus/acbel-fsg032.c +++ b/drivers/hwmon/pmbus/acbel-fsg032.c @@ -52,6 +52,7 @@ static const struct i2c_device_id acbel_fsg032_id[] = { { .name = "acbel_fsg032" }, { } }; +MODULE_DEVICE_TABLE(i2c, acbel_fsg032_id); static struct pmbus_driver_info acbel_fsg032_info = { .pages = 1, diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c --- a/drivers/hwmon/pmbus/adm1275.c +++ b/drivers/hwmon/pmbus/adm1275.c @@ -19,7 +19,7 @@ #include "pmbus.h" enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281, - adm1293, adm1294, sq24905c }; + adm1293, adm1294, bd12780, bd12790, sq24905c }; #define ADM1275_MFR_STATUS_IOUT_WARN2 BIT(0) #define ADM1293_MFR_STATUS_VAUX_UV_WARN BIT(5) @@ -47,6 +47,8 @@ enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281, #define ADM1278_VOUT_EN BIT(1) #define ADM1278_PMON_DEFCONFIG (ADM1278_VOUT_EN | ADM1278_TEMP1_EN | ADM1278_TSFILT) +/* The BD127[89]0 data sheets mark TSFILT bit as reserved. */ +#define BD12780_PMON_DEFCONFIG (ADM1278_VOUT_EN | ADM1278_TEMP1_EN) #define ADM1293_IRANGE_25 0 #define ADM1293_IRANGE_50 BIT(6) @@ -134,6 +136,30 @@ static const struct coefficients adm1272_coefficients[] = { }; +/* + * BD12790 coefficients derived from preliminary datasheet, Table 1 (p.18) + * and the PMBus direct-format relationship X = (Y * 10^(-R) - b) / m. + * + * Voltage: V[V] = 14.77e-3 * code (60V) / 24.62e-3 * code (100V) + * -> m = 6770, R=-2 / m = 4062, R=-2 + * Current: code = I[A] * RS * 132802.1 + 2048 (15mV) / * 66401.06 + 2048 (30mV) + * -> m = 1328, b = 2048 * 10^(-R) = 20480, R=-1 / m = 664, same b and R + * Power: code = k * RS * PIN, k = 35119.94 / 17559.97 / 21071.44 / 10535.72 + * -> m = round(k * 10^(-3-R)), R=-2 for 60V/15mV, R=-3 for the other three + * Temperature: code = 4.2 * T + 3188 -> m = 42, b = 3188 * 10 = 31880, R=-1 + */ +static const struct coefficients bd12790_coefficients[] = { + [0] = { 6770, 0, -2 }, /* voltage, vrange 60V */ + [1] = { 4062, 0, -2 }, /* voltage, vrange 100V */ + [2] = { 1328, 20480, -1 }, /* current, vsense range 15mV */ + [3] = { 664, 20480, -1 }, /* current, vsense range 30mV */ + [4] = { 3512, 0, -2 }, /* power, vrange 60V, irange 15mV */ + [5] = { 21071, 0, -3 }, /* power, vrange 100V, irange 15mV */ + [6] = { 17560, 0, -3 }, /* power, vrange 60V, irange 30mV */ + [7] = { 10536, 0, -3 }, /* power, vrange 100V, irange 30mV */ + [8] = { 42, 31880, -1 }, /* temperature */ +}; + static const struct coefficients adm1275_coefficients[] = { [0] = { 19199, 0, -2 }, /* voltage, vrange set */ [1] = { 6720, 0, -1 }, /* voltage, vrange not set */ @@ -487,6 +513,8 @@ static const struct i2c_device_id adm1275_id[] = { { .name = "adm1281", .driver_data = adm1281 }, { .name = "adm1293", .driver_data = adm1293 }, { .name = "adm1294", .driver_data = adm1294 }, + { .name = "bd12780", .driver_data = bd12780 }, + { .name = "bd12790", .driver_data = bd12790 }, { .name = "mc09c", .driver_data = sq24905c }, { } }; @@ -494,12 +522,13 @@ MODULE_DEVICE_TABLE(i2c, adm1275_id); /* Enable VOUT & TEMP1 if not enabled (disabled by default) */ static int adm1275_enable_vout_temp(struct adm1275_data *data, - struct i2c_client *client, int config) + struct i2c_client *client, int config, + u16 defconfig) { int ret; - if ((config & ADM1278_PMON_DEFCONFIG) != ADM1278_PMON_DEFCONFIG) { - config |= ADM1278_PMON_DEFCONFIG; + if ((config & defconfig) != defconfig) { + config |= defconfig; ret = adm1275_write_pmon_config(data, client, config); if (ret < 0) { dev_err(&client->dev, "Failed to enable VOUT/TEMP1 monitoring\n"); @@ -524,23 +553,19 @@ static int adm1275_probe(struct i2c_client *client) u32 shunt; u32 avg; - if (!i2c_check_functionality(client->adapter, - I2C_FUNC_SMBUS_READ_BYTE_DATA - | I2C_FUNC_SMBUS_BLOCK_DATA)) - return -ENODEV; - - ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, block_buffer); + ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_ID, block_buffer); if (ret < 0) { dev_err(&client->dev, "Failed to read Manufacturer ID\n"); return ret; } if ((ret != 3 || strncmp(block_buffer, "ADI", 3)) && - (ret != 2 || strncmp(block_buffer, "SY", 2))) { + (ret != 2 || strncmp(block_buffer, "SY", 2)) && + (ret != 4 || strncmp(block_buffer, "ROHM", 4))) { dev_err(&client->dev, "Unsupported Manufacturer ID\n"); return -ENODEV; } - ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, block_buffer); + ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_MODEL, block_buffer); if (ret < 0) { dev_err(&client->dev, "Failed to read Manufacturer Model\n"); return ret; @@ -562,6 +587,7 @@ static int adm1275_probe(struct i2c_client *client) if (mid->driver_data == adm1272 || mid->driver_data == adm1273 || mid->driver_data == adm1278 || mid->driver_data == adm1281 || mid->driver_data == adm1293 || mid->driver_data == adm1294 || + mid->driver_data == bd12780 || mid->driver_data == bd12790 || mid->driver_data == sq24905c) config_read_fn = i2c_smbus_read_word_data; else @@ -642,6 +668,7 @@ static int adm1275_probe(struct i2c_client *client) data->have_power_sampling = true; coefficients = adm1272_coefficients; + vindex = (config & ADM1275_VRANGE) ? 1 : 0; cindex = (config & ADM1272_IRANGE) ? 3 : 2; /* pindex depends on the combination of the above */ @@ -666,7 +693,53 @@ static int adm1275_probe(struct i2c_client *client) PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; - ret = adm1275_enable_vout_temp(data, client, config); + ret = adm1275_enable_vout_temp(data, client, config, + ADM1278_PMON_DEFCONFIG); + if (ret) + return ret; + + if (config & ADM1278_VIN_EN) + info->func[0] |= PMBUS_HAVE_VIN; + break; + + /* + * The BD12790 is almost identical to the adm1272. Only the defconfig + * and coefficients have minor differences. + */ + case bd12790: + data->have_vout = true; + data->have_pin_max = true; + data->have_temp_max = true; + data->have_power_sampling = true; + + coefficients = bd12790_coefficients; + + vindex = (config & ADM1275_VRANGE) ? 1 : 0; + cindex = (config & ADM1272_IRANGE) ? 3 : 2; + /* pindex depends on the combination of the above */ + switch (config & (ADM1275_VRANGE | ADM1272_IRANGE)) { + case 0: + default: + pindex = 4; + break; + case ADM1275_VRANGE: + pindex = 5; + break; + case ADM1272_IRANGE: + pindex = 6; + break; + case ADM1275_VRANGE | ADM1272_IRANGE: + pindex = 7; + break; + } + tindex = 8; + + info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + + ret = adm1275_enable_vout_temp(data, client, config, + BD12780_PMON_DEFCONFIG); if (ret) return ret; @@ -728,13 +801,45 @@ static int adm1275_probe(struct i2c_client *client) PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; - ret = adm1275_enable_vout_temp(data, client, config); + ret = adm1275_enable_vout_temp(data, client, config, + ADM1278_PMON_DEFCONFIG); if (ret) return ret; if (config & ADM1278_VIN_EN) info->func[0] |= PMBUS_HAVE_VIN; break; + + /* + * The BD12780 is almost functionally identical with the adm1278 above. + * Only differences visible to the driver are lack of TSFILT bits and + * different identification register contents. + */ + case bd12780: + data->have_vout = true; + data->have_pin_max = true; + data->have_temp_max = true; + data->have_power_sampling = true; + + coefficients = adm1278_coefficients; + vindex = 0; + cindex = 1; + pindex = 2; + tindex = 3; + + info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + + ret = adm1275_enable_vout_temp(data, client, config, + BD12780_PMON_DEFCONFIG); + if (ret) + return ret; + + if (config & ADM1278_VIN_EN) + info->func[0] |= PMBUS_HAVE_VIN; + + break; case adm1293: case adm1294: data->have_iout_min = true; @@ -870,9 +975,27 @@ static int adm1275_probe(struct i2c_client *client) return pmbus_do_probe(client, info); } +static const struct of_device_id adm1275_of_match[] = { + { .compatible = "adi,adm1075", }, + { .compatible = "adi,adm1272", }, + { .compatible = "adi,adm1273", }, + { .compatible = "adi,adm1275", }, + { .compatible = "adi,adm1276", }, + { .compatible = "adi,adm1278", }, + { .compatible = "adi,adm1281", }, + { .compatible = "adi,adm1293", }, + { .compatible = "adi,adm1294", }, + { .compatible = "rohm,bd12780", }, + { .compatible = "rohm,bd12790", }, + { .compatible = "silergy,mc09c", }, + { } +}; +MODULE_DEVICE_TABLE(of, adm1275_of_match); + static struct i2c_driver adm1275_driver = { .driver = { .name = "adm1275", + .of_match_table = adm1275_of_match, }, .probe = adm1275_probe, .id_table = adm1275_id, diff --git a/drivers/hwmon/pmbus/ir36021.c b/drivers/hwmon/pmbus/ir36021.c --- a/drivers/hwmon/pmbus/ir36021.c +++ b/drivers/hwmon/pmbus/ir36021.c @@ -31,21 +31,13 @@ static int ir36021_probe(struct i2c_client *client) u8 buf[I2C_SMBUS_BLOCK_MAX]; int ret; - if (!i2c_check_functionality(client->adapter, - I2C_FUNC_SMBUS_READ_BYTE_DATA - | I2C_FUNC_SMBUS_READ_WORD_DATA - | I2C_FUNC_SMBUS_READ_BLOCK_DATA)) - return -ENODEV; - - ret = i2c_smbus_read_i2c_block_data(client, PMBUS_MFR_MODEL, 2, buf); - if (ret < 0) { - dev_err(&client->dev, "Failed to read PMBUS_MFR_MODEL\n"); - return ret; - } - if (ret != 2 || buf[0] != 0x01 || buf[1] != 0x2d) { - dev_err(&client->dev, "MFR_MODEL unrecognised\n"); - return -ENODEV; - } + ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_MODEL, buf); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to read PMBUS_MFR_MODEL\n"); + if (ret != 1 || buf[0] != 0x2d) + return dev_err_probe(&client->dev, -ENODEV, + "MFR_MODEL unrecognised\n"); return pmbus_do_probe(client, &ir36021_info); } diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c --- a/drivers/hwmon/pmbus/lm25066.c +++ b/drivers/hwmon/pmbus/lm25066.c @@ -35,6 +35,7 @@ enum chips { lm25056, lm25066, lm5064, lm5066, lm5066i }; #define LM25066_READ_AVG_PIN 0xdf #define LM25066_DEV_SETUP_CL BIT(4) /* Current limit */ +#define LM25066_DEV_SETUP_CL_CFG BIT(2) /* Current limit configuration */ #define LM25066_SAMPLES_FOR_AVG_MAX 4096 @@ -485,6 +486,42 @@ static int lm25066_probe(struct i2c_client *client) data->id = (enum chips)(unsigned long)i2c_get_match_data(client); + if (data->id != lm25056) { + int config_new = config; + const char *cl_setting; + int ret; + + if (!of_property_read_string(client->dev.of_node, + "ti,current-range", &cl_setting)) { + config_new |= LM25066_DEV_SETUP_CL_CFG; + if (strcmp(cl_setting, "high") == 0) { + if (data->id == lm25066) + config_new |= LM25066_DEV_SETUP_CL; + else + config_new &= ~LM25066_DEV_SETUP_CL; + } else if (strcmp(cl_setting, "low") == 0) { + if (data->id == lm25066) + config_new &= ~LM25066_DEV_SETUP_CL; + else + config_new |= LM25066_DEV_SETUP_CL; + } else { + dev_err(&client->dev, + "invalid current-range setting: %s\n", + cl_setting); + return -EINVAL; + } + } + + if (config_new != config) { + ret = i2c_smbus_write_byte_data(client, + LM25066_DEVICE_SETUP, + config_new); + if (ret < 0) + return ret; + config = config_new; + } + } + info = &data->info; info->pages = 1; diff --git a/drivers/hwmon/pmbus/ltc2978.c b/drivers/hwmon/pmbus/ltc2978.c --- a/drivers/hwmon/pmbus/ltc2978.c +++ b/drivers/hwmon/pmbus/ltc2978.c @@ -611,17 +611,13 @@ static int ltc2978_get_id(struct i2c_client *client) u8 buf[I2C_SMBUS_BLOCK_MAX]; int ret; - if (!i2c_check_functionality(client->adapter, - I2C_FUNC_SMBUS_READ_BLOCK_DATA)) - return -ENODEV; - - ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf); + ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_ID, buf); if (ret < 0) return ret; if (ret < 3 || (strncmp(buf, "LTC", 3) && strncmp(buf, "ADI", 3))) return -ENODEV; - ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf); + ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_MFR_MODEL, buf); if (ret < 0) return ret; for (id = <c2978_id[0]; strlen(id->name); id++) { @@ -637,16 +633,17 @@ static int ltc2978_get_id(struct i2c_client *client) u8 buf[I2C_SMBUS_BLOCK_MAX]; int ret; - ret = i2c_smbus_read_i2c_block_data(client, PMBUS_IC_DEVICE_ID, - sizeof(buf), buf); + ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_IC_DEVICE_ID, + buf); if (ret < 0) return ret; - if (!strncmp(buf + 1, "LT7170", 6) || - !strncmp(buf + 1, "LT7170-1", 8)) + if (ret < 6) + return -ENODEV; + + if (!strncmp(buf, "LT7170", 6)) return lt7170; - if (!strncmp(buf + 1, "LT7171", 6) || - !strncmp(buf + 1, "LT7171-1", 8)) + if (!strncmp(buf, "LT7171", 6)) return lt7171; return -ENODEV; diff --git a/drivers/hwmon/pmbus/max20830.c b/drivers/hwmon/pmbus/max20830.c --- a/drivers/hwmon/pmbus/max20830.c +++ b/drivers/hwmon/pmbus/max20830.c @@ -7,12 +7,69 @@ #include #include +#include #include #include #include "pmbus.h" #define MAX20830_IC_DEVICE_ID_LENGTH 9 +struct max20830_data { + struct pmbus_driver_info info; + u32 vout_rfb1; + u32 vout_rfb2; +}; + +static const char * const supported_chip_ids[] = { + "MAX20830", + "MAX20830C", + "MAX20840C", +}; + +/* + * MAX20830 only supports READ_VOUT for VOUT monitoring. + * + * Limit registers (VOUT_OV_WARN_LIMIT, VOUT_OV_FAULT_LIMIT, etc.) are not + * supported by this driver and return -ENODATA. This means sysfs attributes + * like in1_max, in1_crit, etc. will not be available. Only in1_input (the + * scaled output voltage) is supported. + * + * MAX20830 uses an external resistor divider for voltage sensing: + * - VOUT_COMMAND and VOUT_MAX set the reference voltage at the feedback pin + * - READ_VOUT reports the feedback voltage, which needs to be scaled for actual + * output voltage + * + * Scaling formula: vout_actual = vout_fb × (1 + RFB1 / RFB2) + * + * If regulator support is added in the future, some adjustments are needed to + * ensure correct feedback voltages are set. + */ +static int max20830_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + const struct max20830_data *data = container_of(info, struct max20830_data, info); + int ret; + u64 temp; + + switch (reg) { + case PMBUS_READ_VOUT: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + /* Apply voltage divider scaling if resistors are non-zero */ + if (data->vout_rfb1 && data->vout_rfb2) { + temp = (u64)data->vout_rfb1 + (u64)data->vout_rfb2; + temp = DIV_ROUND_CLOSEST_ULL((u64)ret * temp, data->vout_rfb2); + ret = clamp_val(temp, 0, 0xFFFF); + } + return ret; + default: + return -ENODATA; + } +} + static struct pmbus_driver_info max20830_info = { .pages = 1, .format[PSC_VOLTAGE_IN] = linear, @@ -23,61 +80,53 @@ static struct pmbus_driver_info max20830_info = { PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP, + .read_word_data = max20830_read_word_data, + .have_pmbus_revision = true, + .pmbus_revision = PMBUS_REV_13, }; static int max20830_probe(struct i2c_client *client) { u8 buf[I2C_SMBUS_BLOCK_MAX + 1] = {}; - int ret; + struct max20830_data *data; + int i, ret; - if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BLOCK_DATA) && - !i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) - return -ENODEV; + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; - /* - * Use i2c_smbus_read_block_data() if supported, otherwise fall back - * to i2c_smbus_read_i2c_block_data() to support I2C controllers - * which do not support SMBus block reads. - */ - if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_BLOCK_DATA)) { - /* Reads 9 Data bytes from MAX20830 */ - ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf); - if (ret < 0) - return dev_err_probe(&client->dev, ret, - "Failed to read IC_DEVICE_ID\n"); - } else { - /* Reads 1 length byte + 9 Data bytes from MAX20830 */ - ret = i2c_smbus_read_i2c_block_data(client, PMBUS_IC_DEVICE_ID, - MAX20830_IC_DEVICE_ID_LENGTH + 1, - buf); - if (ret < 0) - return dev_err_probe(&client->dev, ret, - "Failed to read IC_DEVICE_ID\n"); - /* - * Moves data forward, removing the length byte, this is to - * match the format of i2c_smbus_read_block_data(). - * Also adjust return value to reflect length byte removal. - */ - memmove(buf, buf + 1, MAX20830_IC_DEVICE_ID_LENGTH); - ret = ret - 1; - } + data->info = max20830_info; - /* - * MAX20830 IC_DEVICE_ID sends string data "MAX20830\0". - * Return value should at least be 9 bytes of data. - */ + /* Read optional voltage divider resistor values */ + device_property_read_u32(&client->dev, "adi,vout-rfb1-ohms", &data->vout_rfb1); + device_property_read_u32(&client->dev, "adi,vout-rfb2-ohms", &data->vout_rfb2); + + ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_IC_DEVICE_ID, buf); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to read IC_DEVICE_ID\n"); + + /* Verify we read the expected number of bytes */ if (ret < MAX20830_IC_DEVICE_ID_LENGTH) return dev_err_probe(&client->dev, -ENODEV, - "IC_DEVICE_ID too short: expected at least 9 bytes, got %d\n", - ret); + "IC_DEVICE_ID too short: expected %d bytes, got %d\n", + MAX20830_IC_DEVICE_ID_LENGTH, ret); - /* 9 bytes of data, buf[0]-buf[7] = "MAX20830", buf[8] = '\0' */ - buf[MAX20830_IC_DEVICE_ID_LENGTH - 1] = '\0'; - if (strncmp(buf, "MAX20830", MAX20830_IC_DEVICE_ID_LENGTH - 1)) + /* Null-terminate the string */ + buf[ret] = '\0'; + + /* Verify the device ID matches what we expect */ + for (i = 0; i < ARRAY_SIZE(supported_chip_ids); i++) { + if (!strcmp(buf, supported_chip_ids[i])) + break; + } + + /* No match found - unsupported device */ + if (i == ARRAY_SIZE(supported_chip_ids)) return dev_err_probe(&client->dev, -ENODEV, - "Unsupported device: '%s'\n", buf); + "Unsupported device: '%*pE'\n", ret, buf); - return pmbus_do_probe(client, &max20830_info); + return pmbus_do_probe(client, &data->info); } static const struct i2c_device_id max20830_id[] = { diff --git a/drivers/hwmon/pmbus/max34440.c b/drivers/hwmon/pmbus/max34440.c --- a/drivers/hwmon/pmbus/max34440.c +++ b/drivers/hwmon/pmbus/max34440.c @@ -23,6 +23,7 @@ enum chips { max34441, max34446, max34451, + max34452, max34460, max34461, }; @@ -59,6 +60,7 @@ enum chips { #define MAX34440_IOUT_OC_FAULT_LIMIT 0x4A #define MAX34451ETNA6_MFR_REV 0x0012 +#define MAX34451ETNA8_MFR_REV 0x0014 #define MAX34451_MFR_CHANNEL_CONFIG 0xe4 #define MAX34451_MFR_CHANNEL_CONFIG_SEL_MASK 0x3f @@ -105,16 +107,21 @@ static int max34440_read_word_data(struct i2c_client *client, int page, case PMBUS_UT_FAULT_LIMIT: case PMBUS_MFR_MAX_TEMP_1: /* - * MAX34451/ADPM family do not support VIN/IIN limit registers, - * manufacturer-specific min/max registers, or undercurrent/ - * undertemperature fault limits. Accessing these triggers CML - * error and asserts ALERT. + * MAX34451/MAX34452/ADPM family do not support VIN/IIN limit + * registers, manufacturer-specific min/max registers, or + * undercurrent/undertemperature fault limits. Accessing these + * triggers CML error and asserts ALERT. */ - if (data->id == max34451 || data->id == adpm12160 || - data->id == adpm12200 || data->id == adpm12250) + if (data->id == max34451 || data->id == max34452 || + data->id == adpm12160 || data->id == adpm12200 || + data->id == adpm12250) return -ENXIO; ret = -ENODATA; break; + case PMBUS_VOUT_OV_WARN_LIMIT: + if (data->id == max34452) + return -ENXIO; + return -ENODATA; case PMBUS_VIRT_READ_VOUT_MIN: ret = pmbus_read_word_data(client, page, phase, MAX34440_MFR_VOUT_MIN); @@ -125,8 +132,8 @@ static int max34440_read_word_data(struct i2c_client *client, int page, break; case PMBUS_VIRT_READ_IOUT_AVG: if (data->id != max34446 && data->id != max34451 && - data->id != adpm12160 && data->id != adpm12200 && - data->id != adpm12250) + data->id != max34452 && data->id != adpm12160 && + data->id != adpm12200 && data->id != adpm12250) return -ENXIO; ret = pmbus_read_word_data(client, page, phase, MAX34446_MFR_IOUT_AVG); @@ -191,6 +198,10 @@ static int max34440_write_word_data(struct i2c_client *client, int page, ret = pmbus_write_word_data(client, page, data->iout_oc_warn_limit, word); break; + case PMBUS_VOUT_OV_WARN_LIMIT: + if (data->id == max34452) + return -ENXIO; + return -ENODATA; case PMBUS_VIRT_RESET_POUT_HISTORY: ret = pmbus_write_word_data(client, page, MAX34446_MFR_POUT_PEAK, 0); @@ -211,8 +222,8 @@ static int max34440_write_word_data(struct i2c_client *client, int page, ret = pmbus_write_word_data(client, page, MAX34440_MFR_IOUT_PEAK, 0); if (!ret && (data->id == max34446 || data->id == max34451 || - data->id == adpm12160 || data->id == adpm12200 || - data->id == adpm12250)) + data->id == max34452 || data->id == adpm12160 || + data->id == adpm12200 || data->id == adpm12250)) ret = pmbus_write_word_data(client, page, MAX34446_MFR_IOUT_AVG, 0); @@ -280,12 +291,13 @@ static int max34451_read_byte_data(struct i2c_client *client, int page, int reg) case PMBUS_STATUS_BYTE: case PMBUS_STATUS_OTHER: /* - * MAX34451/ADPM family do not support STATUS_BYTE or + * MAX34451/MAX34452/ADPM family do not support STATUS_BYTE or * STATUS_OTHER registers. Accessing them triggers CML * error and asserts ALERT. */ - if (data->id == max34451 || data->id == adpm12160 || - data->id == adpm12200 || data->id == adpm12250) + if (data->id == max34451 || data->id == max34452 || + data->id == adpm12160 || data->id == adpm12200 || + data->id == adpm12250) return -ENXIO; return -ENODATA; default: @@ -303,12 +315,13 @@ static int max34451_write_byte_data(struct i2c_client *client, int page, case PMBUS_STATUS_BYTE: case PMBUS_STATUS_OTHER: /* - * MAX34451/ADPM family do not support STATUS_BYTE or + * MAX34451/MAX34452/ADPM family do not support STATUS_BYTE or * STATUS_OTHER registers. Writing to them triggers CML * error and asserts ALERT. */ - if (data->id == max34451 || data->id == adpm12160 || - data->id == adpm12200 || data->id == adpm12250) + if (data->id == max34451 || data->id == max34452 || + data->id == adpm12160 || data->id == adpm12200 || + data->id == adpm12250) return -ENXIO; return -ENODATA; default: @@ -339,16 +352,28 @@ static int max34451_set_supported_funcs(struct i2c_client *client, if (rv < 0) return rv; - if (rv >= MAX34451ETNA6_MFR_REV) { + if (data->id == max34451 && rv >= MAX34451ETNA6_MFR_REV) { max34451_na6 = true; data->info.format[PSC_VOLTAGE_IN] = direct; data->info.format[PSC_CURRENT_IN] = direct; - data->info.m[PSC_VOLTAGE_IN] = 1; + data->info.b[PSC_VOLTAGE_IN] = 0; - data->info.R[PSC_VOLTAGE_IN] = 3; - data->info.m[PSC_CURRENT_IN] = 1; data->info.b[PSC_CURRENT_IN] = 0; - data->info.R[PSC_CURRENT_IN] = 2; + if (rv >= MAX34451ETNA8_MFR_REV) { + data->info.m[PSC_VOLTAGE_IN] = 125; + data->info.R[PSC_VOLTAGE_IN] = 0; + data->info.m[PSC_VOLTAGE_OUT] = 125; + data->info.R[PSC_VOLTAGE_OUT] = 0; + data->info.m[PSC_CURRENT_IN] = 250; + data->info.R[PSC_CURRENT_IN] = -1; + data->info.m[PSC_CURRENT_OUT] = 250; + data->info.R[PSC_CURRENT_OUT] = -1; + } else { + data->info.m[PSC_VOLTAGE_IN] = 1; + data->info.R[PSC_VOLTAGE_IN] = 3; + data->info.m[PSC_CURRENT_IN] = 1; + data->info.R[PSC_CURRENT_IN] = 2; + } data->iout_oc_fault_limit = PMBUS_IOUT_OC_FAULT_LIMIT; data->iout_oc_warn_limit = PMBUS_IOUT_OC_WARN_LIMIT; } @@ -665,6 +690,32 @@ static struct pmbus_driver_info max34440_info[] = { .write_word_data = max34440_write_word_data, .page_change_delay = MAX34440_PAGE_CHANGE_DELAY, }, + [max34452] = { + .pages = 21, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_CURRENT_OUT] = direct, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_CURRENT_OUT] = 1, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 2, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + /* func 0-15 is set dynamically before probing */ + .func[16] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[17] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[18] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[19] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[20] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .read_byte_data = max34451_read_byte_data, + .read_word_data = max34440_read_word_data, + .write_byte_data = max34451_write_byte_data, + .write_word_data = max34440_write_word_data, + .page_change_delay = MAX34440_PAGE_CHANGE_DELAY, + }, [max34460] = { .pages = 18, .format[PSC_VOLTAGE_OUT] = direct, @@ -748,7 +799,7 @@ static int max34440_probe(struct i2c_client *client) data->iout_oc_fault_limit = MAX34440_IOUT_OC_FAULT_LIMIT; data->iout_oc_warn_limit = MAX34440_IOUT_OC_WARN_LIMIT; - if (data->id == max34451) { + if (data->id == max34451 || data->id == max34452) { rv = max34451_set_supported_funcs(client, data); if (rv) return rv; @@ -769,6 +820,7 @@ static const struct i2c_device_id max34440_id[] = { { .name = "max34441", .driver_data = max34441 }, { .name = "max34446", .driver_data = max34446 }, { .name = "max34451", .driver_data = max34451 }, + { .name = "max34452", .driver_data = max34452 }, { .name = "max34460", .driver_data = max34460 }, { .name = "max34461", .driver_data = max34461 }, { } diff --git a/drivers/hwmon/pmbus/mp2975.c b/drivers/hwmon/pmbus/mp2975.c --- a/drivers/hwmon/pmbus/mp2975.c +++ b/drivers/hwmon/pmbus/mp2975.c @@ -176,7 +176,6 @@ mp2975_vid2direct(int vrf, int val) static u16 mp2975_data2reg_linear11(s64 val) { s16 exponent = 0, mantissa; - bool negative = false; /* simple case */ if (val == 0) @@ -196,10 +195,6 @@ static u16 mp2975_data2reg_linear11(s64 val) /* Convert mantissa from milli-units to units */ mantissa = clamp_val(DIV_ROUND_CLOSEST_ULL(val, 1000), 0, 0x3ff); - /* restore sign */ - if (negative) - mantissa = -mantissa; - /* Convert to 5 bit exponent, 11 bit mantissa */ return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800); } diff --git a/drivers/hwmon/pmbus/mpq82d00.c b/drivers/hwmon/pmbus/mpq82d00.c new file mode 100644 --- /dev/null +++ b/drivers/hwmon/pmbus/mpq82d00.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for MPS Synchronous Step-Down Converter(MPQ82D00) + */ + +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +#define MPQ82D00_VOUT_DIV 64 + +#define MPQ82D00_PAGE_NUM 1 + +#define MPQ82D00_RAIL1_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \ + PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP | \ + PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | \ + PMBUS_HAVE_STATUS_VOUT | \ + PMBUS_HAVE_STATUS_IOUT | \ + PMBUS_HAVE_STATUS_TEMP | \ + PMBUS_HAVE_STATUS_INPUT) + +struct mpq82d00_data { + struct pmbus_driver_info info; + int vout_scale; +}; + +#define to_mpq82d00_data(x) container_of(x, struct mpq82d00_data, info) + +static u16 mpq82d00_linear_exp_transfer(u16 word, u16 expect_exponent) +{ + s16 exponent, mantissa, target_exponent; + + exponent = ((s16)word) >> 11; + mantissa = ((s16)((word & 0x7ff) << 5)) >> 5; + target_exponent = (s16)((expect_exponent & 0x1f) << 11) >> 11; + + /* + * The MPQ82D00 does not support negtive limit value, if a negtive + * limit value is written, the limit value will become to 0. And + * the maximum positive limit value is limitted to 0x3FF. + */ + if (mantissa < 0) { + mantissa = 0; + } else { + if (exponent > target_exponent) { + mantissa = (1023 >> (exponent - target_exponent)) >= mantissa ? + mantissa << (exponent - target_exponent) : + 0x3FF; + } else { + mantissa = clamp_val(mantissa >> (target_exponent - exponent), + 0, 0x3FF); + } + } + + return mantissa | ((expect_exponent << 11) & 0xf800); +} + +static int mpq82d00_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret; + + switch (reg) { + case PMBUS_VOUT_MODE: + /* + * The MPQ82D00 does not follow standard PMBus protocol completely, + * and the calculation of vout in this driver is based on direct + * format. As a result, the format of vout is enforced to direct. + */ + ret = PB_VOUT_MODE_DIRECT; + break; + default: + /* + * These registers are not explicitly handled by the driver, + * as a result, return -ENODATA directly. + */ + ret = -ENODATA; + break; + } + + return ret; +} + +static int mpq82d00_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct mpq82d00_data *data = to_mpq82d00_data(info); + int ret; + + switch (reg) { + case PMBUS_STATUS_WORD: + case PMBUS_READ_VIN: + case PMBUS_READ_IOUT: + case PMBUS_READ_POUT: + case PMBUS_READ_PIN: + case PMBUS_READ_TEMPERATURE_1: + case PMBUS_IOUT_OC_FAULT_LIMIT: + case PMBUS_OT_FAULT_LIMIT: + /* + * These registers are not explicitly handled by the driver, + * as a result, return -ENODATA directly. + */ + ret = -ENODATA; + break; + case PMBUS_READ_VOUT: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + ret = DIV_ROUND_CLOSEST((ret & GENMASK(11, 0)) * data->vout_scale, + MPQ82D00_VOUT_DIV); + break; + default: + /* + * The MPQ82D00 do not support other telemetry and limit + * value reading, so, return -EINVAL directly. + */ + ret = -EINVAL; + break; + } + + return ret; +} + +static int mpq82d00_write_word_data(struct i2c_client *client, int page, int reg, + u16 word) +{ + int ret; + + switch (reg) { + case PMBUS_OT_FAULT_LIMIT: + /* + * The PMBUS_OT_FAULT_LIMIT of MPQ82D00 is linear11 format, + * and the exponent is a constant value(5'b00000), so the + * exponent of word parameter should be converted to 5'b00000. + */ + ret = pmbus_write_word_data(client, page, reg, + mpq82d00_linear_exp_transfer(word, 0x00)); + break; + case PMBUS_IOUT_OC_FAULT_LIMIT: + /* + * The PMBUS_IOUT_OC_FAULT_LIMIT of MPQ82D00 is linear11 format, + * and the exponent is a constant value(5'b11100), so the + * exponent of word parameter should be converted to 5'b11100. + */ + ret = pmbus_write_word_data(client, page, reg, + mpq82d00_linear_exp_transfer(word, 0x1C)); + break; + default: + /* + * The MPQ82D00 do not support other limit value configuration, + * so, return -EINVAL directly. + */ + ret = -EINVAL; + break; + } + + return ret; +} + +static int mpq82d00_identify(struct i2c_client *client, struct pmbus_driver_info *info) +{ + struct mpq82d00_data *data = to_mpq82d00_data(info); + int ret; + + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(client, PMBUS_VOUT_MODE); + if (ret < 0) + return ret; + + /* + * The MPQ82D00 supports three vout mode. If PMBUS_VOUT_MODE + * bit5 is 1, the vout scale is 5mv/LSB.If PMBUS_VOUT_MODE bit5 + * is 0, it is linear mode, the vout scale is 1.953125mv/LSB. + */ + if (FIELD_GET(GENMASK(5, 5), ret)) + data->vout_scale = 320; + else + data->vout_scale = 125; + + return 0; +} + +static struct pmbus_driver_info mpq82d00_info = { + .pages = MPQ82D00_PAGE_NUM, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_POWER] = linear, + + .m[PSC_VOLTAGE_OUT] = 1, + .R[PSC_VOLTAGE_OUT] = 3, + .b[PSC_VOLTAGE_OUT] = 0, + + .func[0] = MPQ82D00_RAIL1_FUNC, + .read_word_data = mpq82d00_read_word_data, + .read_byte_data = mpq82d00_read_byte_data, + .write_word_data = mpq82d00_write_word_data, + .identify = mpq82d00_identify, +}; + +static int mpq82d00_probe(struct i2c_client *client) +{ + struct mpq82d00_data *data; + + data = devm_kzalloc(&client->dev, sizeof(struct mpq82d00_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + memcpy(&data->info, &mpq82d00_info, sizeof(mpq82d00_info)); + + return pmbus_do_probe(client, &data->info); +} + +static const struct i2c_device_id mpq82d00_id[] = { + {.name = "mpq82d00"}, + {} +}; +MODULE_DEVICE_TABLE(i2c, mpq82d00_id); + +static const struct of_device_id __maybe_unused mpq82d00_of_match[] = { + {.compatible = "mps,mpq82d00"}, + {} +}; +MODULE_DEVICE_TABLE(of, mpq82d00_of_match); + +static struct i2c_driver mpq82d00_driver = { + .driver = { + .name = "mpq82d00", + .of_match_table = mpq82d00_of_match, + }, + .probe = mpq82d00_probe, + .id_table = mpq82d00_id, +}; + +module_i2c_driver(mpq82d00_driver); + +MODULE_AUTHOR("Wensheng Wang + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pmbus.h" + +/* Default cadence for the in-driver alarm-poll fallback (ms) */ +#define MPQ8646_ALARM_POLL_MS_DEFAULT 1000 + +/* MPS vendor-extended command codes (NOT in PMBus 1.3 Part II) */ +#define MPS_CLEAR_LAST_FAULT 0x08 +#define MPS_MFR_CFG_EXT 0xF5 +#define MPS_MFR_CFG_EXT_CLR_LAST_EN BIT(6) +#define MPS_PROTECTION_LAST 0xFB + +/* + * PMBus 1.3 NVM commit / revert commands. MPS equivalent of + * STORE_ALL (15h) and RESTORE_ALL (16h). + */ +#define PMBUS_STORE_USER_ALL 0x15 +#define PMBUS_RESTORE_USER_ALL 0x16 + +/* PMBus 1.3 timing / UVLO command codes */ +#define PMBUS_VIN_ON 0x35 +#define PMBUS_VIN_OFF 0x36 +#define PMBUS_TON_DELAY 0x60 +#define PMBUS_TON_RISE 0x61 +#define PMBUS_TOFF_DELAY 0x64 +#define PMBUS_TOFF_FALL 0x65 + +/* MPS vendor-extended observability / identity registers */ +#define MPS_MFR_CONFIG_ID 0xC0 +#define MPS_MFR_CONFIG_CODE_REV 0xC1 +#define MPS_MFR_PRODUCT_REV_USER 0xC2 +#define MPS_MFR_SILICON_REV 0xC3 +#define MPS_MFR_RETRY_TIMES 0xF4 +#define MPS_MFR_VBOOT_CFG 0xFC + +/* + * MPS_MFR_PMBUS_LOCK (EEh): 16-bit WORD whose low two bits gate + * subsequent PMBus writes + * bits[1:0] = 00 -- unlocked (POR default) + * 01 -- lock all writes EXCEPT VOUT_COMMAND (0x21) + * so the operator can still DVFS the rail + * 11 -- lock all writes + * A negative-going PG edge resets these bits to 00, the lock + * is operationally reversible without a full chip POR. + */ +#define MPS_MFR_PMBUS_LOCK 0xEE + +/* + * Retry parameters for the MFR_CFG_EXT gate-close write after + * CLEAR_LAST_FAULT. Bench-observed NVM-busy NACK window on this + * silicon is about 1 ms; the datasheet does not have information. + */ +#define MPQ8646_NVM_RETRY_MAX 5 +#define MPQ8646_NVM_RETRY_DELAY_US_MIN 2000 +#define MPQ8646_NVM_RETRY_DELAY_US_MAX 4000 + +#define MPQ8646_DEBUG(client, fmt, ...) \ + dev_dbg(&(client)->dev, fmt, ##__VA_ARGS__) + +/* + * Maximum legal value for VOUT_SCALE_LOOP (0x29) on the MPQ8646 silicon + * The chip uses an 11-bit VOUT feedback-divider scale register. + */ +#define MPQ8646_VOUT_SCALE_LOOP_MAX GENMASK(10, 0) + +/* Forward declaration */ +struct mpq8646_dbg_reg_ctx; + +/* Per-instance state */ +struct mpq8646_priv { + struct pmbus_driver_info info; /* must be first, container_of target */ + struct i2c_client *client; + + /* Serialises CLEAR_LAST_FAULT sequences and PROTECTION_LAST reads */ + struct mutex mps_lock; + + /* Set alarm_poll_interval_ms = 0 to disable. */ + struct delayed_work alarm_poll_work; + u32 alarm_poll_interval_ms; + +#ifdef CONFIG_DEBUG_FS + /* the only debugfs file that can re-arm the poll worker */ + struct dentry *dbg_poll; + struct mpq8646_dbg_reg_ctx *dbg_reg_ctx; +#endif +}; + +static inline struct mpq8646_priv *mpq8646_priv_from_client(struct i2c_client *client) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + + return container_of(info, struct mpq8646_priv, info); +} + +/* + * VID-mode m/b/R coefficients, same values as the mpq8785 driver for + * the MPS VID encoding. Per the PMBus Direct formula used by + * pmbus_core, X = (Y * 10^-R - b) / m, so m=64 / b=0 / R=1 yields + * 1.5625 mV per LSB. + */ +#define MPQ8646_VID_M 64 +#define MPQ8646_VID_B 0 +#define MPQ8646_VID_R 1 + +static int mpq8646_identify(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + int vout_mode; + + vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); + if (vout_mode < 0) + return vout_mode; + + switch (vout_mode & PB_VOUT_MODE_MODE_MASK) { + case PB_VOUT_MODE_LINEAR: + info->format[PSC_VOLTAGE_OUT] = linear; + break; + case PB_VOUT_MODE_VID: + case PB_VOUT_MODE_DIRECT: + info->format[PSC_VOLTAGE_OUT] = direct; + info->m[PSC_VOLTAGE_OUT] = MPQ8646_VID_M; + info->b[PSC_VOLTAGE_OUT] = MPQ8646_VID_B; + info->R[PSC_VOLTAGE_OUT] = MPQ8646_VID_R; + break; + default: + return -ENODEV; + } + + return 0; +}; + +static int mpq8646_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret; + + MPQ8646_DEBUG(client, "read_byte_data page=%d reg=0x%02x\n", page, reg); + + switch (reg) { + case PMBUS_VOUT_MODE: + ret = pmbus_read_byte_data(client, page, reg); + MPQ8646_DEBUG(client, " VOUT_MODE raw=0x%02x ret=%d\n", ret, ret); + if (ret < 0) + return ret; + + if ((ret & PB_VOUT_MODE_MODE_MASK) == PB_VOUT_MODE_VID) + return PB_VOUT_MODE_DIRECT; + + return ret; + case PMBUS_STATUS_BYTE: + case PMBUS_STATUS_CML: + case PMBUS_STATUS_OTHER: + case PMBUS_STATUS_MFR_SPECIFIC: + case PMBUS_STATUS_FAN_12: + case PMBUS_STATUS_FAN_34: + return -ENXIO; + case PMBUS_MFR_LOCATION: + case PMBUS_MFR_DATE: + case PMBUS_MFR_SERIAL: + case PMBUS_IC_DEVICE_ID: + case PMBUS_IC_DEVICE_REV: + MPQ8646_DEBUG(client, " unsupported mfr-info reg 0x%02x -> ENXIO\n", reg); + return -ENXIO; + default: + return -ENODATA; + } +} + +/* + * Reference: ltc2978.c::ltc2978_write_word_data which uses the + * same virtual-register channel for its real chip-side peak reset. + */ +static int mpq8646_write_word_data(struct i2c_client *client, int page, + int reg, u16 word) +{ + struct mpq8646_priv *priv = mpq8646_priv_from_client(client); + int rc; + + switch (reg) { + case PMBUS_VIRT_RESET_VIN_HISTORY: + case PMBUS_VIRT_RESET_VOUT_HISTORY: + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_RESET_TEMP_HISTORY: + MPQ8646_DEBUG(client, "reset_history virt reg=0x%04x -> CLEAR_FAULTS\n", + reg); + rc = i2c_smbus_write_byte(priv->client, PMBUS_CLEAR_FAULTS); + if (rc < 0) + MPQ8646_DEBUG(client, " CLEAR_FAULTS rc=%d\n", rc); + return rc < 0 ? rc : 0; + default: + return -ENODATA; /* let pmbus_core do the direct write */ + } +} + +static int mpq8646_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + int rc; + + MPQ8646_DEBUG(client, "read_word_data page=%d phase=%d reg=0x%02x\n", + page, phase, reg); + + switch (reg) { + case PMBUS_READ_VIN: + case PMBUS_READ_VOUT: + case PMBUS_READ_IOUT: + case PMBUS_READ_TEMPERATURE_1: + case PMBUS_STATUS_WORD: + case PMBUS_VOUT_OV_FAULT_LIMIT: + case PMBUS_VOUT_OV_WARN_LIMIT: + case PMBUS_VOUT_UV_WARN_LIMIT: + case PMBUS_VOUT_UV_FAULT_LIMIT: + case PMBUS_IOUT_OC_FAULT_LIMIT: + case PMBUS_IOUT_OC_WARN_LIMIT: + case PMBUS_OT_FAULT_LIMIT: + case PMBUS_OT_WARN_LIMIT: + case PMBUS_VIN_OV_FAULT_LIMIT: + case PMBUS_VIN_OV_WARN_LIMIT: + case PMBUS_VIN_UV_WARN_LIMIT: + case PMBUS_VIN_UV_FAULT_LIMIT: + case PMBUS_MFR_VIN_MAX: + case PMBUS_MFR_VOUT_MAX: + case PMBUS_MFR_IOUT_MAX: + case PMBUS_MFR_MAX_TEMP_1: + break; + case PMBUS_VIRT_RESET_VIN_HISTORY: + case PMBUS_VIRT_RESET_VOUT_HISTORY: + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_RESET_TEMP_HISTORY: + return 0; + default: + return -ENODATA; + } + + rc = i2c_smbus_read_word_data(client, reg); + + MPQ8646_DEBUG(client, " reg=0x%02x rc=%d\n", reg, rc); + return rc; +} + +static struct pmbus_driver_info mpq8646_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_IN] = 4, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 1, + .m[PSC_CURRENT_OUT] = 16, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 0, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 0, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP | + PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_TEMP, +}; + +#if IS_ENABLED(CONFIG_REGULATOR) +static const struct regulator_desc mpq8646_reg_desc[] = { + PMBUS_REGULATOR("vout", 0), +}; +#endif /* CONFIG_REGULATOR */ + +#if IS_ENABLED(CONFIG_NVMEM) +/* + * Expose using + * /sys/bus/nvmem/devices//nvmem + * + * Layout (16 bytes): + * offset 0..1 PROTECTION_LAST (0xFB) word, LE -- NVM + * offset 2..3 MFR_RETRY_TIMES (0xF4) word, LE -- NVM + * offset 4..5 MFR_CONFIG_ID (0xC0) word, LE -- NVM + * offset 6..7 MFR_VBOOT_CFG (0xFC) word, LE -- NVM + * offset 8 MFR_SILICON_REV (0xC3) byte -- NVM + * offset 9..15 reserved (zero-fill, leaves room for additions) + */ +#define MPQ8646_NVMEM_SIZE 16 + +struct mpq8646_nvmem_entry { + unsigned int off; + u8 reg; + bool is_word; +}; + +static const struct mpq8646_nvmem_entry mpq8646_nvmem_map[] = { + { 0, MPS_PROTECTION_LAST, true }, + { 2, MPS_MFR_RETRY_TIMES, true }, + { 4, MPS_MFR_CONFIG_ID, true }, + { 6, MPS_MFR_VBOOT_CFG, true }, + { 8, MPS_MFR_SILICON_REV, false }, +}; + +static int mpq8646_nvmem_read(void *data, unsigned int offset, void *val, + size_t bytes) +{ + struct mpq8646_priv *priv = data; + u8 *out = val; + size_t i; + + if (offset >= MPQ8646_NVMEM_SIZE) + return -EINVAL; + if (offset + bytes > MPQ8646_NVMEM_SIZE) + bytes = MPQ8646_NVMEM_SIZE - offset; + + memset(out, 0, bytes); + + guard(mutex)(&priv->mps_lock); + for (i = 0; i < ARRAY_SIZE(mpq8646_nvmem_map); i++) { + const struct mpq8646_nvmem_entry *e = &mpq8646_nvmem_map[i]; + unsigned int e_start = e->off; + unsigned int e_end = e_start + (e->is_word ? 2 : 1); + u8 raw[2]; + int rc; + unsigned int j; + + if (e_end <= offset || e_start >= offset + bytes) + continue; /* outside requested slice */ + + if (e->is_word) + rc = i2c_smbus_read_word_data(priv->client, e->reg); + else + rc = i2c_smbus_read_byte_data(priv->client, e->reg); + if (rc < 0) + continue; /* leave the zero-fill in place */ + + raw[0] = rc & 0xff; + raw[1] = (rc >> 8) & 0xff; + + for (j = 0; j < e_end - e_start; j++) { + unsigned int abs = e_start + j; + + if (abs >= offset && abs < offset + bytes) + out[abs - offset] = raw[j]; + } + } + return 0; +} +#endif /* CONFIG_NVMEM */ + +static const struct i2c_device_id mpq8646_id[] = { + { .name = "mpq8646" }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, mpq8646_id); + +static const struct of_device_id __maybe_unused mpq8646_of_match[] = { + { .compatible = "mps,mpq8646" }, + {} +}; +MODULE_DEVICE_TABLE(of, mpq8646_of_match); + +static struct pmbus_platform_data mpq8646_no_pec_pdata = { + .flags = PMBUS_NO_CAPABILITY, +}; + +#ifdef CONFIG_DEBUG_FS +/* + * Read-only decode cases in the client's pmbus debugfs directory: + * the MPS-specific STATUS_WORD and PROTECTION_LAST bit decode plus the + * identity/timing registers. + */ + +struct mpq_status_bit { + u16 mask; + const char *name; +}; + +static const struct mpq_status_bit mpq8646_status_word_bits[] = { + { PB_STATUS_VOUT, "VOUT" }, + { PB_STATUS_IOUT_POUT, "IOUT_POUT" }, + { PB_STATUS_INPUT, "INPUT" }, + { PB_STATUS_WORD_MFR, "NVM_SUMMARY" }, + { PB_STATUS_POWER_GOOD_N, "POWER_GOOD#" }, + { PB_STATUS_FANS, "FANS" }, + { PB_STATUS_OTHER, "OTHER" }, + { PB_STATUS_UNKNOWN, "WATCH_DOG" }, + { PB_STATUS_BUSY, "BUSY" }, + { PB_STATUS_OFF, "OFF" }, + { PB_STATUS_VOUT_OV, "VOUT_OV_FAULT" }, + { PB_STATUS_IOUT_OC, "IOUT_OC_FAULT" }, + { PB_STATUS_VIN_UV, "VIN_UV_FAULT" }, + { PB_STATUS_TEMPERATURE, "TEMP" }, + { PB_STATUS_CML, "CML" }, + { PB_STATUS_NONE_ABOVE, "DRMOS_FAULT" }, + { /* sentinel */ } +}; + +/* PROTECTION_LAST (0xFB) bit names, it survives chip POR */ +static const struct mpq_status_bit mpq8646_protection_last_bits[] = { + { BIT(15), "INIT_FAULT" }, + { BIT(14), "NVM_CRC_ERROR" }, + { BIT(13), "NVM_FAULT" }, + { BIT(12), "OC_PHASE_FAULT" }, + { BIT(11), "OTP_SELF_FAULT" }, + { BIT(9), "SWITCH_PRD_FAULT" }, + { BIT(8), "VIN_OV_FAULT" }, + { BIT(7), "VOUT_OV_FAULT" }, + { BIT(6), "VOUT_UV_FAULT" }, + { BIT(5), "OC_TOT_FAULT" }, + { BIT(4), "VIN_UVLO_FAULT" }, + { BIT(3), "DRMOS_OTP" }, + { /* sentinel */ } +}; + +static void mpq8646_print_bits(struct seq_file *s, u16 v, + const struct mpq_status_bit *tab) +{ + const struct mpq_status_bit *t; + bool first = true; + + seq_printf(s, "0x%04x", v); + if (!v) { + seq_puts(s, " [clean]\n"); + return; + } + seq_puts(s, " ["); + for (t = tab; t->mask; t++) { + if (v & t->mask) { + if (!first) + seq_putc(s, ' '); + seq_puts(s, t->name); + first = false; + } + } + seq_puts(s, "]\n"); +} + +static int mpq8646_dbg_status_decoded_show(struct seq_file *s, void *unused) +{ + struct mpq8646_priv *priv = s->private; + int rc; + + scoped_guard(pmbus_lock, priv->client) + rc = i2c_smbus_read_word_data(priv->client, PMBUS_STATUS_WORD); + if (rc < 0) { + seq_printf(s, "ERROR: STATUS_WORD read failed (%d)\n", rc); + return 0; + } + seq_puts(s, "STATUS_WORD: "); + mpq8646_print_bits(s, (u16)rc, mpq8646_status_word_bits); + seq_puts(s, "(MPS extensions: bit12=NVM_SUMMARY, bit8=WATCH_DOG, bit0=DRMOS_FAULT)\n"); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_status_decoded); + +static int mpq8646_dbg_protection_last_show(struct seq_file *s, void *unused) +{ + struct mpq8646_priv *priv = s->private; + int rc; + + guard(pmbus_lock)(priv->client); + scoped_guard(mutex, &priv->mps_lock) + rc = i2c_smbus_read_word_data(priv->client, MPS_PROTECTION_LAST); + + if (rc < 0) { + seq_printf(s, "ERROR: PROTECTION_LAST read failed (%d)\n", rc); + return 0; + } + seq_puts(s, "PROTECTION_LAST: "); + mpq8646_print_bits(s, (u16)rc, mpq8646_protection_last_bits); + seq_puts(s, "(NVM-backed, survives chip POR)\n"); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_protection_last); + +struct mpq8646_dbg_reg { + u8 reg; + bool is_word; + const char *name; +}; + +static const struct mpq8646_dbg_reg mpq8646_dbg_regs[] = { + /* MPS vendor extensions (read-only identity / observability) */ + { MPS_MFR_CONFIG_ID, true, "mfr_config_id" }, + { MPS_MFR_CONFIG_CODE_REV, true, "mfr_config_code_rev" }, + { MPS_MFR_SILICON_REV, false, "mfr_silicon_rev" }, + { MPS_MFR_RETRY_TIMES, true, "mfr_retry_times" }, + { MPS_MFR_VBOOT_CFG, true, "mfr_vboot_cfg" }, + /* PMBus 1.3 standard timing / UVLO (read-only introspection) */ + { PMBUS_VIN_ON, true, "vin_on" }, + { PMBUS_VIN_OFF, true, "vin_off" }, + { PMBUS_TON_DELAY, true, "ton_delay" }, + { PMBUS_TON_RISE, true, "ton_rise" }, + { PMBUS_TOFF_DELAY, true, "toff_delay" }, + { PMBUS_TOFF_FALL, true, "toff_fall" }, +}; + +struct mpq8646_dbg_reg_ctx { + struct mpq8646_priv *priv; + const struct mpq8646_dbg_reg *desc; +}; + +static int mpq8646_dbg_reg_show(struct seq_file *s, void *unused) +{ + struct mpq8646_dbg_reg_ctx *ctx = s->private; + int rc; + + guard(pmbus_lock)(ctx->priv->client); + if (ctx->desc->is_word) + rc = i2c_smbus_read_word_data(ctx->priv->client, + ctx->desc->reg); + else + rc = i2c_smbus_read_byte_data(ctx->priv->client, + ctx->desc->reg); + + if (rc < 0) { + seq_printf(s, "ERROR: reg 0x%02x (%s) read failed (%d)\n", + ctx->desc->reg, ctx->desc->name, rc); + return 0; + } + seq_printf(s, "0x%0*x\n", ctx->desc->is_word ? 4 : 2, rc); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_reg); + +#ifdef CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE +/* + * Write/provisioning data, disabled by default: NVM commit and + * revert, the CLEAR_LAST_FAULT sequences and a small set of named + * writable registers. + */ + +static void mpq8646_unsafe_banner(struct mpq8646_priv *priv) +{ + dev_warn(&priv->client->dev, + "**********************************************************\n" + "** WARNING WARNING WARNING WARNING WARNING WARNING **\n" + "** **\n" + "** The MPQ8646 provisioning debugfs writes are enabled. **\n" + "** Wrong register writes can and likely will physically **\n" + "** damage or destroy the chip and/or the board. **\n" + "** **\n" + "** If you see this message and you are not debugging **\n" + "** the kernel, report this immediately to your system **\n" + "** administrator! **\n" + "** **\n" + "** WARNING WARNING WARNING WARNING WARNING WARNING **\n" + "**********************************************************\n"); +} + +static int mpq8646_dbg_clear_protection_last(void *data, u64 val) +{ + struct mpq8646_priv *priv = data; + int rc; + + if (!val) + return 0; + + guard(pmbus_lock)(priv->client); + scoped_guard(mutex, &priv->mps_lock) + rc = i2c_smbus_write_byte(priv->client, MPS_CLEAR_LAST_FAULT); + if (rc < 0) + dev_warn(&priv->client->dev, + "clear_protection_last: CLEAR_LAST_FAULT write failed (%d)\n", + rc); + return rc; +} +DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_clear_protection_last_fops, + NULL, mpq8646_dbg_clear_protection_last, "%llu\n"); + +static int mpq8646_dbg_clear_protection_last_force(void *data, u64 val) +{ + struct mpq8646_priv *priv = data; + int rc, ret; + int wp_orig, cfg_orig; + + if (!val) + return 0; + + guard(pmbus_lock)(priv->client); + guard(mutex)(&priv->mps_lock); + + wp_orig = i2c_smbus_read_byte_data(priv->client, PMBUS_WRITE_PROTECT); + if (wp_orig < 0) { + dev_warn(&priv->client->dev, + "clear_protection_last_force: WRITE_PROTECT read failed (%d), aborting\n", + wp_orig); + return wp_orig; + } + cfg_orig = i2c_smbus_read_word_data(priv->client, MPS_MFR_CFG_EXT); + if (cfg_orig < 0) { + dev_warn(&priv->client->dev, + "clear_protection_last_force: MFR_CFG_EXT read failed (%d), aborting\n", + cfg_orig); + return cfg_orig; + } + + if (wp_orig != 0) { + rc = i2c_smbus_write_byte_data(priv->client, + PMBUS_WRITE_PROTECT, 0); + if (rc < 0) { + dev_warn(&priv->client->dev, + "clear_protection_last_force: WP clear failed (%d), aborting\n", + rc); + return rc; + } + } + + ret = i2c_smbus_write_word_data(priv->client, MPS_MFR_CFG_EXT, + (u16)cfg_orig | MPS_MFR_CFG_EXT_CLR_LAST_EN); + if (ret < 0) { + dev_warn(&priv->client->dev, + "clear_protection_last_force: gate open failed (%d)\n", + ret); + } else { + ret = i2c_smbus_write_byte(priv->client, MPS_CLEAR_LAST_FAULT); + if (ret < 0) + dev_warn(&priv->client->dev, + "clear_protection_last_force: CLEAR_LAST_FAULT failed (%d) even with gate open\n", + ret); + + for (int attempt = 0; attempt < MPQ8646_NVM_RETRY_MAX; attempt++) { + rc = i2c_smbus_write_word_data(priv->client, + MPS_MFR_CFG_EXT, + (u16)cfg_orig); + if (rc >= 0) + break; + usleep_range(MPQ8646_NVM_RETRY_DELAY_US_MIN, + MPQ8646_NVM_RETRY_DELAY_US_MAX); + } + if (rc < 0) { + dev_warn(&priv->client->dev, + "clear_protection_last_force: MFR_CFG_EXT restore failed after retries (%d): gate may stay open until POR\n", + rc); + if (ret == 0) + ret = rc; + } + } + + if (wp_orig != 0) { + rc = i2c_smbus_write_byte_data(priv->client, + PMBUS_WRITE_PROTECT, + (u8)wp_orig); + if (rc < 0) { + dev_warn(&priv->client->dev, + "clear_protection_last_force: WP restore failed (%d)\n", + rc); + if (ret == 0) + ret = rc; + } + } + return ret; +} +DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_clear_protection_last_force_fops, + NULL, mpq8646_dbg_clear_protection_last_force, "%llu\n"); + +static int mpq8646_dbg_store_all(void *data, u64 val) +{ + struct mpq8646_priv *priv = data; + int rc; + + if (!val) + return 0; + + guard(pmbus_lock)(priv->client); + scoped_guard(mutex, &priv->mps_lock) + rc = i2c_smbus_write_byte(priv->client, PMBUS_STORE_USER_ALL); + if (rc < 0) + dev_warn(&priv->client->dev, + "store_all: STORE_USER_ALL (0x15) write failed (%d)\n", + rc); + return rc; +} +DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_store_all_fops, + NULL, mpq8646_dbg_store_all, "%llu\n"); + +static int mpq8646_dbg_restore_all(void *data, u64 val) +{ + struct mpq8646_priv *priv = data; + int rc; + + if (!val) + return 0; + + guard(pmbus_lock)(priv->client); + scoped_guard(mutex, &priv->mps_lock) + rc = i2c_smbus_write_byte(priv->client, PMBUS_RESTORE_USER_ALL); + if (rc < 0) + dev_warn(&priv->client->dev, + "restore_all: RESTORE_USER_ALL (0x16) write failed (%d)\n", + rc); + return rc; +} +DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_restore_all_fops, + NULL, mpq8646_dbg_restore_all, "%llu\n"); + +static const struct mpq8646_dbg_reg mpq8646_dbg_regs_unsafe[] = { + /* PMBus 1.3 control / margin */ + { PMBUS_ON_OFF_CONFIG, false, "on_off_config" }, + { PMBUS_VOUT_MARGIN_HIGH, true, "vout_margin_high" }, + { PMBUS_VOUT_MARGIN_LOW, true, "vout_margin_low" }, + /* MPS PMBus-level write-protect */ + { MPS_MFR_PMBUS_LOCK, true, "mfr_pmbus_lock" }, + /* MPS user-writable product revision */ + { MPS_MFR_PRODUCT_REV_USER, true, "mfr_product_rev_user" }, +}; + +static int mpq8646_dbg_reg_get(void *data, u64 *val) +{ + struct mpq8646_dbg_reg_ctx *ctx = data; + int rc; + + guard(pmbus_lock)(ctx->priv->client); + if (ctx->desc->is_word) + rc = i2c_smbus_read_word_data(ctx->priv->client, + ctx->desc->reg); + else + rc = i2c_smbus_read_byte_data(ctx->priv->client, + ctx->desc->reg); + if (rc < 0) + return rc; + *val = rc; + return 0; +} + +static int mpq8646_dbg_reg_set(void *data, u64 val) +{ + struct mpq8646_dbg_reg_ctx *ctx = data; + int rc; + + guard(pmbus_lock)(ctx->priv->client); + if (ctx->desc->is_word) + rc = i2c_smbus_write_word_data(ctx->priv->client, + ctx->desc->reg, (u16)val); + else + rc = i2c_smbus_write_byte_data(ctx->priv->client, + ctx->desc->reg, (u8)val); + return rc < 0 ? rc : 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_reg_rw_fops, + mpq8646_dbg_reg_get, mpq8646_dbg_reg_set, "0x%llx\n"); + +static void mpq8646_debugfs_register_unsafe(struct mpq8646_priv *priv, + struct dentry *root) +{ + struct mpq8646_dbg_reg_ctx *ctx; + size_t i; + + mpq8646_unsafe_banner(priv); + + debugfs_create_file_unsafe("clear_protection_last", 0200, root, priv, + &mpq8646_dbg_clear_protection_last_fops); + debugfs_create_file_unsafe("clear_protection_last_force", 0200, root, + priv, + &mpq8646_dbg_clear_protection_last_force_fops); + debugfs_create_file_unsafe("store_all", 0200, root, priv, + &mpq8646_dbg_store_all_fops); + debugfs_create_file_unsafe("restore_all", 0200, root, priv, + &mpq8646_dbg_restore_all_fops); + + ctx = devm_kcalloc(&priv->client->dev, + ARRAY_SIZE(mpq8646_dbg_regs_unsafe), + sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return; + for (i = 0; i < ARRAY_SIZE(mpq8646_dbg_regs_unsafe); i++) { + ctx[i].priv = priv; + ctx[i].desc = &mpq8646_dbg_regs_unsafe[i]; + debugfs_create_file_unsafe(mpq8646_dbg_regs_unsafe[i].name, + 0600, root, &ctx[i], + &mpq8646_dbg_reg_rw_fops); + } +} +#else +static inline void mpq8646_debugfs_register_unsafe(struct mpq8646_priv *priv, + struct dentry *root) {} +#endif /* CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE */ + +static int mpq8646_dbg_poll_interval_get(void *data, u64 *val) +{ + struct mpq8646_priv *priv = data; + + *val = priv->alarm_poll_interval_ms; + return 0; +} + +static int mpq8646_dbg_poll_interval_set(void *data, u64 val) +{ + struct mpq8646_priv *priv = data; + bool was_off = !priv->alarm_poll_interval_ms; + + priv->alarm_poll_interval_ms = (u32)val; + + if (val && was_off && !priv->client->irq) + schedule_delayed_work(&priv->alarm_poll_work, + msecs_to_jiffies((u32)val)); + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_poll_interval_fops, + mpq8646_dbg_poll_interval_get, + mpq8646_dbg_poll_interval_set, "%llu\n"); + +static void mpq8646_debugfs_register(struct mpq8646_priv *priv) +{ + struct dentry *root; + size_t i; + + root = pmbus_get_debugfs_dir(priv->client); + if (!root) + return; + + /* MPS extensions: status decode + NVM-backed PROTECTION_LAST */ + debugfs_create_file("status_decoded", 0400, root, priv, + &mpq8646_dbg_status_decoded_fops); + debugfs_create_file("protection_last", 0400, root, priv, + &mpq8646_dbg_protection_last_fops); + priv->dbg_poll = + debugfs_create_file_unsafe("alarm_poll_interval_ms", 0600, + root, priv, + &mpq8646_dbg_poll_interval_fops); + + priv->dbg_reg_ctx = devm_kcalloc(&priv->client->dev, + ARRAY_SIZE(mpq8646_dbg_regs), + sizeof(*priv->dbg_reg_ctx), + GFP_KERNEL); + if (!priv->dbg_reg_ctx) + return; + for (i = 0; i < ARRAY_SIZE(mpq8646_dbg_regs); i++) { + priv->dbg_reg_ctx[i].priv = priv; + priv->dbg_reg_ctx[i].desc = &mpq8646_dbg_regs[i]; + debugfs_create_file(mpq8646_dbg_regs[i].name, 0400, + root, &priv->dbg_reg_ctx[i], + &mpq8646_dbg_reg_fops); + } + + mpq8646_debugfs_register_unsafe(priv, root); +} + +static void mpq8646_debugfs_unregister(struct mpq8646_priv *priv) +{ + debugfs_remove(priv->dbg_poll); +} +#else +static inline void mpq8646_debugfs_register(struct mpq8646_priv *priv) {} +static inline void mpq8646_debugfs_unregister(struct mpq8646_priv *priv) {} +#endif /* CONFIG_DEBUG_FS */ + +static void mpq8646_alarm_poll_work(struct work_struct *work) +{ + struct mpq8646_priv *priv = container_of(to_delayed_work(work), + struct mpq8646_priv, + alarm_poll_work); + + if (priv->client->irq) + return; /* SMBALERT# wired; polling not needed */ + + if (!priv->alarm_poll_interval_ms) + return; /* polling disabled; don't re-arm */ + + pmbus_check_and_notify_faults(priv->client); + + schedule_delayed_work(&priv->alarm_poll_work, + msecs_to_jiffies(priv->alarm_poll_interval_ms)); +} + +static int mpq8646_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct pmbus_driver_info *info; + struct mpq8646_priv *priv; + u32 voltage_scale; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + priv->client = client; + mutex_init(&priv->mps_lock); + memcpy(&priv->info, &mpq8646_info, sizeof(priv->info)); + info = &priv->info; + + info->identify = mpq8646_identify; + info->read_byte_data = mpq8646_read_byte_data; + info->read_word_data = mpq8646_read_word_data; + info->write_word_data = mpq8646_write_word_data; + dev->platform_data = &mpq8646_no_pec_pdata; + +#if IS_ENABLED(CONFIG_REGULATOR) + info->reg_desc = mpq8646_reg_desc; + info->num_regulators = ARRAY_SIZE(mpq8646_reg_desc); +#endif + + INIT_DELAYED_WORK(&priv->alarm_poll_work, mpq8646_alarm_poll_work); + priv->alarm_poll_interval_ms = MPQ8646_ALARM_POLL_MS_DEFAULT; + + if (!device_property_read_u32(dev, "mps,vout-fb-divider-ratio-permille", + &voltage_scale)) { + if (voltage_scale > MPQ8646_VOUT_SCALE_LOOP_MAX) + return -EINVAL; + + ret = i2c_smbus_write_word_data(client, PMBUS_VOUT_SCALE_LOOP, + voltage_scale); + if (ret) + return ret; + } + + ret = pmbus_do_probe(client, info); + if (ret) + return ret; + + mpq8646_debugfs_register(priv); + + if (!client->irq) + schedule_delayed_work(&priv->alarm_poll_work, + msecs_to_jiffies(priv->alarm_poll_interval_ms)); + +#if IS_ENABLED(CONFIG_NVMEM) + { + struct nvmem_config cfg = { + .dev = &client->dev, + .name = dev_name(&client->dev), + .owner = THIS_MODULE, + .read_only = true, + .root_only = true, + .word_size = 1, + .stride = 1, + .size = MPQ8646_NVMEM_SIZE, + .reg_read = mpq8646_nvmem_read, + .priv = priv, + }; + struct nvmem_device *nv = devm_nvmem_register(&client->dev, &cfg); + + if (IS_ERR(nv)) + dev_warn(&client->dev, + "nvmem snapshot register failed (%pe)\n", + nv); + } +#endif + return 0; +}; + +static void mpq8646_remove(struct i2c_client *client) +{ + struct mpq8646_priv *priv = mpq8646_priv_from_client(client); + + mpq8646_debugfs_unregister(priv); + cancel_delayed_work_sync(&priv->alarm_poll_work); +} + +static struct i2c_driver mpq8646_driver = { + .driver = { + .name = "mpq8646", + .of_match_table = of_match_ptr(mpq8646_of_match), + }, + .probe = mpq8646_probe, + .remove = mpq8646_remove, + .id_table = mpq8646_id, +}; + +module_i2c_driver(mpq8646_driver); + +MODULE_AUTHOR("Vincent Jardin "); +MODULE_DESCRIPTION("PMBus driver for MPS MPQ8646 (extended observability)"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("PMBUS"); diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -421,10 +421,12 @@ enum pmbus_data_format { linear = 0, ieee754, direct, vid }; enum vrm_version { vr11 = 0, vr12, vr13, imvp9, amd625mv, nvidia195mv }; /* PMBus revision identifiers */ -#define PMBUS_REV_10 0x00 /* PMBus revision 1.0 */ -#define PMBUS_REV_11 0x11 /* PMBus revision 1.1 */ -#define PMBUS_REV_12 0x22 /* PMBus revision 1.2 */ -#define PMBUS_REV_13 0x33 /* PMBus revision 1.3 */ +#define PMBUS_REV_10 0x00 /* PMBus revision 1.0 */ +#define PMBUS_REV_11 0x11 /* PMBus revision 1.1 */ +#define PMBUS_REV_12 0x22 /* PMBus revision 1.2 */ +#define PMBUS_REV_13 0x33 /* PMBus revision 1.3 */ +#define PMBUS_REV_131 0x44 /* PMBus revision 1.3.1 */ +#define PMBUS_REV_14 0x55 /* PMBus revision 1.4 */ /* Operation type flags for pmbus_update_ts */ #define PMBUS_OP_WRITE BIT(0) @@ -489,6 +491,16 @@ struct pmbus_driver_info { int access_delay; /* in microseconds */ int write_delay; /* in microseconds */ int page_change_delay; /* in microseconds */ + + /* + * Some chips do not support the PMBUS_REVISION command. + * Drivers for such chips can report the supported PMBus revision here. + * + * Drivers must set have_pmbus_revision to true and provide the + * supported PMBus version in pmbus_revision. + */ + bool have_pmbus_revision; /* true if pmbus_revision is valid */ + u8 pmbus_revision; /* PMBus revision */ }; /* Regulator ops */ @@ -550,6 +562,7 @@ void pmbus_set_update(struct i2c_client *client, u8 reg, bool update); void pmbus_wait(struct i2c_client *client); void pmbus_update_ts(struct i2c_client *client, int op); int pmbus_set_page(struct i2c_client *client, int page, int phase); +int pmbus_read_smbus_i2c_block_data(struct i2c_client *client, u8 reg, char *data_buf); int pmbus_read_word_data(struct i2c_client *client, int page, int phase, u8 reg); int pmbus_write_word_data(struct i2c_client *client, int page, u8 reg, @@ -561,6 +574,7 @@ int pmbus_write_byte_data(struct i2c_client *client, int page, u8 reg, int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg, u8 mask, u8 value); void pmbus_clear_faults(struct i2c_client *client); +void pmbus_check_and_notify_faults(struct i2c_client *client); bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg); bool pmbus_check_word_register(struct i2c_client *client, int page, int reg); int pmbus_do_probe(struct i2c_client *client, struct pmbus_driver_info *info); diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -96,7 +96,8 @@ struct pmbus_data { u32 flags; /* from platform data */ - u8 revision; /* The PMBus revision the device is compliant with */ + bool have_pmbus_revision; + u8 revision; /* The PMBus revision the device is compliant with */ int exponent[PMBUS_PAGES]; /* linear mode: exponent for output voltages */ @@ -181,7 +182,12 @@ EXPORT_SYMBOL_NS_GPL(pmbus_set_update, "PMBUS"); void pmbus_wait(struct i2c_client *client) { struct pmbus_data *data = i2c_get_clientdata(client); - s64 delay = ktime_us_delta(data->next_access_backoff, ktime_get()); + s64 delay; + + if (!data) + return; + + delay = ktime_us_delta(data->next_access_backoff, ktime_get()); if (delay > 0) fsleep(delay); @@ -192,8 +198,14 @@ EXPORT_SYMBOL_NS_GPL(pmbus_wait, "PMBUS"); void pmbus_update_ts(struct i2c_client *client, int op) { struct pmbus_data *data = i2c_get_clientdata(client); - const struct pmbus_driver_info *info = data->info; - int delay = info->access_delay; + const struct pmbus_driver_info *info; + int delay; + + if (!data) + return; + + info = data->info; + delay = info->access_delay; if (op & PMBUS_OP_WRITE) delay = max(delay, info->write_delay); @@ -517,6 +529,81 @@ int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg, } EXPORT_SYMBOL_NS_GPL(pmbus_update_byte_data, "PMBUS"); +/** + * pmbus_read_smbus_i2c_block_data() - Read SMBus/I2C block data + * @client: Handle to slave device + * @reg: Byte interpreted by slave + * @data_buf: Byte array into which data will be read + * Return: Negative errno or number of bytes read + * + * PMBus internal function to read a SMBus block from a PMBus chip. + * + * PMBus chips report various properties using SMBus block read operations. + * However, not all I2C controllers support this operation. + * + * Execute SMBus block read if supported. If not supported, but SMBus I2C block + * read is supported, use it instead. Note that at most 31 data bytes can be + * read from the device if i2c_smbus_read_i2c_block_data() is used to read the + * data. This is a SMBUs protocol limit which can not be avoided. + * + * Return -EOPNOTSUPP if neither I2C_FUNC_SMBUS_READ_BLOCK_DATA nor + * I2C_FUNC_SMBUS_READ_I2C_BLOCK is supported. + * + * Callers must hold pmbus_lock or execute calls from the probe function. + */ +int pmbus_read_smbus_i2c_block_data(struct i2c_client *client, u8 reg, char *data_buf) +{ + u8 buf[I2C_SMBUS_BLOCK_MAX]; + int blen, len, ret; + + if (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_BLOCK_DATA)) { + pmbus_wait(client); + ret = i2c_smbus_read_block_data(client, reg, data_buf); + pmbus_update_ts(client, 0); + return ret; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { + dev_err_once(&client->dev, "I2C adapter does not support I2C_FUNC_SMBUS_READ_I2C_BLOCK\n"); + return -EOPNOTSUPP; + } + + /* + * If the returned data is valid SMBus block data, the first byte + * must be the data length. + * + * i2c_smbus_read_i2c_block_data() may return an error if the chip + * sends NACK before the number of requested bytes is received. + * Handle this by reading the data length first, then reading the + * entire message up to I2C_SMBUS_BLOCK_MAX bytes. This ensures + * that requested number of bytes never exceeds the number of + * bytes sent by the chip. + */ + pmbus_wait(client); + ret = i2c_smbus_read_i2c_block_data(client, reg, 1, buf); + pmbus_update_ts(client, 0); + if (ret < 0) + return ret; + + len = buf[0]; + if (len == 0) + return 0; + blen = len; + if (len >= I2C_SMBUS_BLOCK_MAX) + len = I2C_SMBUS_BLOCK_MAX - 1; + pmbus_wait(client); + ret = i2c_smbus_read_i2c_block_data(client, reg, len + 1, buf); + pmbus_update_ts(client, 0); + if (ret < 0) + return ret; + if (buf[0] != blen) + return -EIO; + memcpy(data_buf, buf + 1, len); + return len; +} +EXPORT_SYMBOL_NS_GPL(pmbus_read_smbus_i2c_block_data, "PMBUS"); + static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg, char *data_buf) { @@ -526,11 +613,7 @@ static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg, if (rv < 0) return rv; - pmbus_wait(client); - rv = i2c_smbus_read_block_data(client, reg, data_buf); - pmbus_update_ts(client, 0); - - return rv; + return pmbus_read_smbus_i2c_block_data(client, reg, data_buf); } static struct pmbus_sensor *pmbus_find_sensor(struct pmbus_data *data, int page, @@ -2849,9 +2932,16 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data, if (!(data->flags & PMBUS_NO_WRITE_PROTECT)) pmbus_init_wp(client, data); - ret = i2c_smbus_read_byte_data(client, PMBUS_REVISION); - if (ret >= 0) - data->revision = ret; + if (info->have_pmbus_revision) { + data->have_pmbus_revision = true; + data->revision = info->pmbus_revision; + } else { + ret = i2c_smbus_read_byte_data(client, PMBUS_REVISION); + if (ret >= 0) { + data->have_pmbus_revision = true; + data->revision = ret; + } + } if (data->info->pages) pmbus_clear_faults(client); @@ -3455,10 +3545,9 @@ static int pmbus_write_smbalert_mask(struct i2c_client *client, u8 page, u8 reg, return ret; } -static irqreturn_t pmbus_fault_handler(int irq, void *pdata) +void pmbus_check_and_notify_faults(struct i2c_client *client) { - struct pmbus_data *data = pdata; - struct i2c_client *client = to_i2c_client(data->dev); + struct pmbus_data *data = i2c_get_clientdata(client); int i, status, event; guard(pmbus_lock)(client); @@ -3471,6 +3560,15 @@ static irqreturn_t pmbus_fault_handler(int irq, void *pdata) } pmbus_clear_faults(client); +} +EXPORT_SYMBOL_NS_GPL(pmbus_check_and_notify_faults, "PMBUS"); + +static irqreturn_t pmbus_fault_handler(int irq, void *pdata) +{ + struct pmbus_data *data = pdata; + struct i2c_client *client = to_i2c_client(data->dev); + + pmbus_check_and_notify_faults(client); return IRQ_HANDLED; } @@ -3514,10 +3612,8 @@ static int pmbus_irq_setup(struct i2c_client *client, struct pmbus_data *data) /* Register notifiers */ err = devm_request_threaded_irq(dev, client->irq, NULL, pmbus_fault_handler, IRQF_ONESHOT, "pmbus-irq", data); - if (err) { - dev_err(dev, "failed to request an irq %d\n", err); + if (err) return err; - } return 0; } @@ -3543,6 +3639,17 @@ static int pmbus_debugfs_get(void *data, u64 *val) DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_ops, pmbus_debugfs_get, NULL, "0x%02llx\n"); +static int pmbus_debugfs_get_revision(void *data, u64 *val) +{ + struct pmbus_data *pdata = data; + + *val = pdata->revision; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_revision_ops, pmbus_debugfs_get_revision, NULL, + "0x%02llx\n"); + static int pmbus_debugfs_get_status(void *data, u64 *val) { struct pmbus_debugfs_entry *entry = data; @@ -3698,14 +3805,9 @@ static void pmbus_init_debugfs(struct i2c_client *client, &entries[idx++], &pmbus_debugfs_ops); } - if (pmbus_check_byte_register(client, 0, PMBUS_REVISION)) { - entries[idx].client = client; - entries[idx].page = 0; - entries[idx].reg = PMBUS_REVISION; - debugfs_create_file("pmbus_revision", 0444, debugfs, - &entries[idx++], - &pmbus_debugfs_ops); - } + if (data->have_pmbus_revision) + debugfs_create_file("pmbus_revision", 0444, debugfs, data, + &pmbus_debugfs_revision_ops); for (i = 0; i < ARRAY_SIZE(pmbus_debugfs_block_data); i++) { const struct pmbus_debugfs_data *d = &pmbus_debugfs_block_data[i]; diff --git a/drivers/hwmon/pmbus/sq24860.c b/drivers/hwmon/pmbus/sq24860.c new file mode 100644 --- /dev/null +++ b/drivers/hwmon/pmbus/sq24860.c @@ -0,0 +1,430 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Author: Ziming Zhu + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "pmbus.h" + +#define SQ24860_IIN_CAL_GAIN 0x38 +#define SQ24860_READ_VAUX 0xd0 +#define SQ24860_READ_VIN_MIN 0xd1 +#define SQ24860_READ_VIN_PEAK 0xd2 +#define SQ24860_READ_IIN_PEAK 0xd4 +#define SQ24860_READ_PIN_PEAK 0xd5 +#define SQ24860_READ_TEMP_AVG 0xd6 +#define SQ24860_READ_TEMP_PEAK 0xd7 +#define SQ24860_READ_VOUT_MIN 0xda +#define SQ24860_READ_VIN_AVG 0xdc +#define SQ24860_READ_VOUT_AVG 0xdd +#define SQ24860_READ_IIN_AVG 0xde +#define SQ24860_READ_PIN_AVG 0xdf +#define SQ24860_VIREF 0xe0 +#define SQ24860_PK_MIN_AVG 0xea +#define PK_MIN_AVG_RST_PEAK BIT(7) +#define PK_MIN_AVG_RST_AVG BIT(6) +#define PK_MIN_AVG_RST_MIN BIT(5) +#define PK_MIN_AVG_AVG_CNT GENMASK(2, 0) +#define SQ24860_MFR_WRITE_PROTECT 0xf8 +#define SQ24860_UNLOCKED BIT(7) + +#define SQ24860_8B_SHIFT 2 +#define SQ24860_IIN_OCF_NUM 1000000 +#define SQ24860_IIN_OCF_DIV 129278 +#define SQ24860_IIN_OCF_OFF 165 + +#define PK_MIN_AVG_RST_MASK (PK_MIN_AVG_RST_PEAK | \ + PK_MIN_AVG_RST_AVG | \ + PK_MIN_AVG_RST_MIN) +#define SQ24860_MAX_SAMPLES BIT(FIELD_MAX(PK_MIN_AVG_AVG_CNT)) +/* + * Arbitrary default Rimon value: 1.6kOhm + */ +#define SQ24860_DEFAULT_RIMON 1600000000 +#define SQ24860_GIMON 18180 + +#define SQ24860_VAUX_DIV 20 + +static int sq24860_write_iin_cal_gain(struct i2c_client *client, u32 rimon) +{ + u64 temp = 6400ULL * 1000000000ULL * 1000ULL; + u64 denom; + u64 word; + + if (!rimon) + return -EINVAL; + + denom = (u64)rimon * SQ24860_GIMON; + word = div64_u64(temp, denom); + if (!word || word > U16_MAX) + return -EINVAL; + + return i2c_smbus_write_word_data(client, SQ24860_IIN_CAL_GAIN, + (u16)word); +} + +static int sq24860_mfr_write_protect_set(struct i2c_client *client, + u8 protect) +{ + u8 val; + + switch (protect) { + case 0: + val = 0xa2; + break; + case PB_WP_ALL: + val = 0x0; + break; + default: + return -EINVAL; + } + + return pmbus_write_byte_data(client, -1, SQ24860_MFR_WRITE_PROTECT, + val); +} + +static int sq24860_mfr_write_protect_get(struct i2c_client *client) +{ + int ret = pmbus_read_byte_data(client, -1, SQ24860_MFR_WRITE_PROTECT); + + if (ret < 0) + return ret; + + return (ret & SQ24860_UNLOCKED) ? 0 : PB_WP_ALL; +} + +static int sq24860_read_word_data(struct i2c_client *client, + int page, int phase, int reg) +{ + int ret; + + switch (reg) { + case PMBUS_VIRT_READ_VIN_MAX: + ret = pmbus_read_word_data(client, page, phase, + SQ24860_READ_VIN_PEAK); + break; + + case PMBUS_VIRT_READ_VIN_MIN: + ret = pmbus_read_word_data(client, page, phase, + SQ24860_READ_VIN_MIN); + break; + + case PMBUS_VIRT_READ_VIN_AVG: + ret = pmbus_read_word_data(client, page, phase, + SQ24860_READ_VIN_AVG); + break; + + case PMBUS_VIRT_READ_VOUT_MIN: + ret = pmbus_read_word_data(client, page, phase, + SQ24860_READ_VOUT_MIN); + break; + + case PMBUS_VIRT_READ_VOUT_AVG: + ret = pmbus_read_word_data(client, page, phase, + SQ24860_READ_VOUT_AVG); + break; + + case PMBUS_VIRT_READ_IIN_AVG: + ret = pmbus_read_word_data(client, page, phase, + SQ24860_READ_IIN_AVG); + break; + + case PMBUS_VIRT_READ_IIN_MAX: + ret = pmbus_read_word_data(client, page, phase, + SQ24860_READ_IIN_PEAK); + break; + + case PMBUS_VIRT_READ_TEMP_AVG: + ret = pmbus_read_word_data(client, page, phase, + SQ24860_READ_TEMP_AVG); + break; + + case PMBUS_VIRT_READ_TEMP_MAX: + ret = pmbus_read_word_data(client, page, phase, + SQ24860_READ_TEMP_PEAK); + break; + + case PMBUS_VIRT_READ_PIN_AVG: + ret = pmbus_read_word_data(client, page, phase, + SQ24860_READ_PIN_AVG); + break; + + case PMBUS_VIRT_READ_PIN_MAX: + ret = pmbus_read_word_data(client, page, phase, + SQ24860_READ_PIN_PEAK); + break; + + case PMBUS_VIRT_READ_VMON: + ret = pmbus_read_word_data(client, page, phase, + SQ24860_READ_VAUX); + if (ret < 0) + break; + ret = DIV_ROUND_CLOSEST(ret, SQ24860_VAUX_DIV); + break; + + case PMBUS_VIN_UV_WARN_LIMIT: + case PMBUS_VIN_UV_FAULT_LIMIT: + case PMBUS_VIN_OV_WARN_LIMIT: + case PMBUS_VIN_OV_FAULT_LIMIT: + case PMBUS_VOUT_UV_WARN_LIMIT: + case PMBUS_IIN_OC_WARN_LIMIT: + case PMBUS_OT_WARN_LIMIT: + case PMBUS_OT_FAULT_LIMIT: + case PMBUS_PIN_OP_WARN_LIMIT: + /* + * These registers provide an 8 bits value instead of a + * 10bits one. Just shifting twice the register value is + * enough to make the sensor type conversion work, even + * if the datasheet provides different m, b and R for + * those. + */ + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + break; + ret <<= SQ24860_8B_SHIFT; + break; + + case PMBUS_IIN_OC_FAULT_LIMIT: + /* + * VIREF directly sets the over-current limit at which the eFuse + * will turn the FET off and trigger a fault. Expose it through + * this generic property instead of a manufacturer specific one. + */ + ret = pmbus_read_byte_data(client, page, SQ24860_VIREF); + if (ret < 0) + break; + ret = DIV_ROUND_CLOSEST(ret * SQ24860_IIN_OCF_NUM, + SQ24860_IIN_OCF_DIV); + ret += SQ24860_IIN_OCF_OFF; + break; + + case PMBUS_VIRT_SAMPLES: + ret = pmbus_read_byte_data(client, page, SQ24860_PK_MIN_AVG); + if (ret < 0) + break; + ret = BIT(FIELD_GET(PK_MIN_AVG_AVG_CNT, ret)); + break; + + case PMBUS_VIRT_RESET_TEMP_HISTORY: + case PMBUS_VIRT_RESET_VIN_HISTORY: + case PMBUS_VIRT_RESET_IIN_HISTORY: + case PMBUS_VIRT_RESET_PIN_HISTORY: + case PMBUS_VIRT_RESET_VOUT_HISTORY: + ret = 0; + break; + + default: + ret = -ENODATA; + break; + } + + return ret; +} + +static int sq24860_write_word_data(struct i2c_client *client, + int page, int reg, u16 value) +{ + int ret; + + switch (reg) { + case PMBUS_VIN_UV_WARN_LIMIT: + case PMBUS_VIN_UV_FAULT_LIMIT: + case PMBUS_VIN_OV_WARN_LIMIT: + case PMBUS_VIN_OV_FAULT_LIMIT: + case PMBUS_VOUT_UV_WARN_LIMIT: + case PMBUS_IIN_OC_WARN_LIMIT: + case PMBUS_OT_WARN_LIMIT: + case PMBUS_OT_FAULT_LIMIT: + case PMBUS_PIN_OP_WARN_LIMIT: + value = max_t(s16, (s16)value, 0); + value >>= SQ24860_8B_SHIFT; + value = clamp_val(value, 0, 0xff); + ret = pmbus_write_word_data(client, page, reg, value); + break; + + case PMBUS_IIN_OC_FAULT_LIMIT: + value = max_t(s16, (s16)value, SQ24860_IIN_OCF_OFF); + value -= SQ24860_IIN_OCF_OFF; + value = DIV_ROUND_CLOSEST(((unsigned int)value) * SQ24860_IIN_OCF_DIV, + SQ24860_IIN_OCF_NUM); + value = clamp_val(value, 0, 0x3f); + ret = pmbus_write_byte_data(client, page, SQ24860_VIREF, value); + break; + + case PMBUS_VIRT_SAMPLES: + value = clamp_val(value, 1, SQ24860_MAX_SAMPLES); + value = ilog2(value); + ret = pmbus_update_byte_data(client, page, SQ24860_PK_MIN_AVG, + PK_MIN_AVG_AVG_CNT, + FIELD_PREP(PK_MIN_AVG_AVG_CNT, value)); + break; + + case PMBUS_VIRT_RESET_TEMP_HISTORY: + case PMBUS_VIRT_RESET_VIN_HISTORY: + case PMBUS_VIRT_RESET_IIN_HISTORY: + case PMBUS_VIRT_RESET_PIN_HISTORY: + case PMBUS_VIRT_RESET_VOUT_HISTORY: + /* + * SQ24860 has history resets based on MIN/AVG/PEAK instead of per + * sensor type. Exposing this quirk in hwmon is not desirable so + * reset MIN, AVG and PEAK together. Even is there effectively only + * one reset, which resets everything, expose the 5 entries so + * userspace is not required map a sensor type to another to trigger + * a reset + */ + ret = pmbus_update_byte_data(client, 0, SQ24860_PK_MIN_AVG, + PK_MIN_AVG_RST_MASK, + PK_MIN_AVG_RST_MASK); + break; + + default: + ret = -ENODATA; + break; + } + + return ret; +} + +static int sq24860_read_byte_data(struct i2c_client *client, + int page, int reg) +{ + int ret; + + switch (reg) { + case PMBUS_WRITE_PROTECT: + ret = sq24860_mfr_write_protect_get(client); + break; + + default: + ret = -ENODATA; + break; + } + + return ret; +} + +static int sq24860_write_byte_data(struct i2c_client *client, + int page, int reg, u8 byte) +{ + int ret; + + switch (reg) { + case PMBUS_WRITE_PROTECT: + ret = sq24860_mfr_write_protect_set(client, byte); + break; + + default: + ret = -ENODATA; + break; + } + + return ret; +} + +#if IS_ENABLED(CONFIG_SENSORS_SQ24860_REGULATOR) +static const struct regulator_desc sq24860_reg_desc[] = { + PMBUS_REGULATOR_ONE_NODE("vout"), +}; +#endif + +static const struct pmbus_driver_info sq24860_base_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = direct, + .m[PSC_VOLTAGE_IN] = 64, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 0, + .format[PSC_VOLTAGE_OUT] = direct, + .m[PSC_VOLTAGE_OUT] = 64, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 0, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 0, + /* + * Current and power measurements depend on the calibration gain + * programmed from the board-specific IMON resistor value. + */ + .format[PSC_CURRENT_IN] = direct, + .m[PSC_CURRENT_IN] = 16, + .b[PSC_CURRENT_IN] = 0, + .R[PSC_CURRENT_IN] = 0, + .format[PSC_POWER] = direct, + .m[PSC_POWER] = 2, + .b[PSC_POWER] = 0, + .R[PSC_POWER] = 0, + .func[0] = PMBUS_HAVE_VIN | + PMBUS_HAVE_VOUT | + PMBUS_HAVE_VMON | + PMBUS_HAVE_IIN | + PMBUS_HAVE_PIN | + PMBUS_HAVE_TEMP | + PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_SAMPLES, + .read_word_data = sq24860_read_word_data, + .write_word_data = sq24860_write_word_data, + .read_byte_data = sq24860_read_byte_data, + .write_byte_data = sq24860_write_byte_data, + +#if IS_ENABLED(CONFIG_SENSORS_SQ24860_REGULATOR) + .reg_desc = sq24860_reg_desc, + .num_regulators = ARRAY_SIZE(sq24860_reg_desc), +#endif +}; + +static const struct i2c_device_id sq24860_i2c_id[] = { + { "sq24860" }, + {} +}; +MODULE_DEVICE_TABLE(i2c, sq24860_i2c_id); + +static const struct of_device_id sq24860_of_match[] = { + { .compatible = "silergy,sq24860" }, + {} +}; +MODULE_DEVICE_TABLE(of, sq24860_of_match); + +static int sq24860_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct pmbus_driver_info *info; + u32 rimon; + int ret; + + if (device_property_read_u32(dev, "silergy,rimon-micro-ohms", &rimon)) + rimon = SQ24860_DEFAULT_RIMON; + ret = sq24860_write_iin_cal_gain(client, rimon); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to set gain\n"); + info = devm_kmemdup(dev, &sq24860_base_info, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + return pmbus_do_probe(client, info); +} + +static struct i2c_driver sq24860_driver = { + .driver = { + .name = "sq24860", + .of_match_table = sq24860_of_match, + }, + .probe = sq24860_probe, + .id_table = sq24860_i2c_id, +}; +module_i2c_driver(sq24860_driver); + +MODULE_AUTHOR("Ziming Zhu "); +MODULE_DESCRIPTION("PMBUS driver for SQ24860 eFuse"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("PMBUS"); diff --git a/drivers/hwmon/pmbus/vt7505.c b/drivers/hwmon/pmbus/vt7505.c new file mode 100644 --- /dev/null +++ b/drivers/hwmon/pmbus/vt7505.c @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for Analog Devices MAX16545/MAX16550 and + * Volterra VT7505 PMBus controllers. + * + * Copyright 2026 Hewlett Packard Enterprise Development LP + */ + +#include +#include +#include +#include +#include +#include + +#include "pmbus.h" + +#define VT7505_MFR_CONFIG 0xd0 +#define VT7505_CFG_OCP_S_FILT_MASK GENMASK(15, 14) + +#define VT7505_MFR_PEAK_VIN 0xd1 +#define VT7505_MFR_PEAK_IOUT 0xd2 +#define VT7505_MFR_PEAK_PIN 0xd3 +#define VT7505_MFR_PEAK_TEMP 0xd4 +#define VT7505_MFR_CLEAR_PEAKS 0xd5 +#define VT7505_MFR_PEAK_VOUT 0xfd + +#define VT7505_RLOAD_DEFAULT 4750 +/* Largest RLOAD for which coeff * rload / 1000 still fits in s32. */ +#define VT7505_RLOAD_MAX 283383959U + +struct vt7505_chip_data { + int temp_m; + int temp_b; + bool has_ocp_filter; +}; + +static const struct vt7505_chip_data max16545_data = { + .temp_m = 205, + .temp_b = 6545, +}; + +static const struct vt7505_chip_data max16550_data = { + .temp_m = 199, + .temp_b = 7046, + .has_ocp_filter = true, +}; + +static const struct vt7505_chip_data vt7505_data = { + .temp_m = 205, + .temp_b = 6545, + .has_ocp_filter = true, +}; + +static int vt7505_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + switch (reg) { + case PMBUS_VIRT_READ_VIN_MAX: + return pmbus_read_word_data(client, page, phase, + VT7505_MFR_PEAK_VIN); + case PMBUS_VIRT_READ_IOUT_MAX: + return pmbus_read_word_data(client, page, phase, + VT7505_MFR_PEAK_IOUT); + case PMBUS_VIRT_READ_PIN_MAX: + return pmbus_read_word_data(client, page, phase, + VT7505_MFR_PEAK_PIN); + case PMBUS_VIRT_READ_TEMP_MAX: + return pmbus_read_word_data(client, page, phase, + VT7505_MFR_PEAK_TEMP); + case PMBUS_VIRT_READ_VOUT_MAX: + return pmbus_read_word_data(client, page, phase, + VT7505_MFR_PEAK_VOUT); + case PMBUS_VIRT_RESET_VIN_HISTORY: + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_RESET_PIN_HISTORY: + case PMBUS_VIRT_RESET_TEMP_HISTORY: + case PMBUS_VIRT_RESET_VOUT_HISTORY: + return 0; + default: + return -ENODATA; + } +} + +static int vt7505_write_word_data(struct i2c_client *client, int page, + int reg, u16 word) +{ + switch (reg) { + /* + * A single reset command clears all peak values. CLEAR_PEAKS is a + * send-byte command; the device NAKs a word or byte-data write to it. + */ + case PMBUS_VIRT_RESET_VIN_HISTORY: + case PMBUS_VIRT_RESET_IOUT_HISTORY: + case PMBUS_VIRT_RESET_PIN_HISTORY: + case PMBUS_VIRT_RESET_TEMP_HISTORY: + case PMBUS_VIRT_RESET_VOUT_HISTORY: + return pmbus_write_byte(client, page, + VT7505_MFR_CLEAR_PEAKS); + default: + return -ENODATA; + } +} + +/* + * None of these controllers implement the standard PMBus WRITE_PROTECT + * (0x10) register, so tell the core not to access it. + */ +static struct pmbus_platform_data vt7505_pdata = { + .flags = PMBUS_NO_WRITE_PROTECT, +}; + +static int vt7505_set_ocp_filter(struct i2c_client *client) +{ + u32 ocp_us; + u8 field; + int ret; + u16 word; + + if (of_property_read_u32(client->dev.of_node, "adi,ocp-severe-filter-us", + &ocp_us)) + return 0; + + switch (ocp_us) { + case 0: + field = 0; + break; + case 1: + field = 1; + break; + case 2: + field = 2; + break; + case 10: + field = 3; + break; + default: + return dev_err_probe(&client->dev, -EINVAL, + "invalid adi,ocp-severe-filter-us value %u\n", + ocp_us); + } + + ret = i2c_smbus_read_word_data(client, VT7505_MFR_CONFIG); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "failed to read MFR_CONFIG\n"); + + word = ret & ~VT7505_CFG_OCP_S_FILT_MASK; + word |= FIELD_PREP(VT7505_CFG_OCP_S_FILT_MASK, field); + + ret = i2c_smbus_write_word_data(client, VT7505_MFR_CONFIG, word); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "failed to write MFR_CONFIG\n"); + + return 0; +} + +static void vt7505_set_m(int *m, u32 rload) +{ + u64 val = (u64)*m * rload; + + /* rload is range-checked in probe, so the result fits in int. */ + *m = DIV_ROUND_CLOSEST_ULL(val, 1000); +} + +static int vt7505_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + const struct vt7505_chip_data *chip; + struct pmbus_driver_info *info; + u32 rload; + int ret; + + chip = i2c_get_match_data(client); + if (!chip) + return -ENODEV; + + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + dev->platform_data = &vt7505_pdata; + + /* + * The m coefficient used in the direct-format current and power + * calculations depends on RLOAD, the external current-report resistor + * connected between the ILOAD pin and ground. Use the default value if + * none is specified. + */ + if (of_property_read_u32(dev->of_node, "adi,rload-ohms", &rload)) + rload = VT7505_RLOAD_DEFAULT; + + if (!rload || rload > VT7505_RLOAD_MAX) + return dev_err_probe(dev, -EINVAL, + "adi,rload-ohms must be 1-%u\n", + VT7505_RLOAD_MAX); + + info->pages = 1; + info->read_word_data = vt7505_read_word_data; + info->write_word_data = vt7505_write_word_data; + + info->format[PSC_VOLTAGE_IN] = direct; + info->format[PSC_VOLTAGE_OUT] = direct; + info->format[PSC_CURRENT_IN] = direct; + info->format[PSC_CURRENT_OUT] = direct; + info->format[PSC_POWER] = direct; + info->format[PSC_TEMPERATURE] = direct; + + /* + * Direct data format coefficients from the device datasheet ("PMBus + * Equation Parameters"). The current and power m coefficients scale + * with RLOAD; vt7505_set_m() applies the 1/1000 factor below, giving + * m = 3.824 * RLOAD for current and 0.895 * RLOAD for power. The + * temperature coefficients are chip specific (see the chip data). + */ + info->m[PSC_VOLTAGE_IN] = 7578; + info->R[PSC_VOLTAGE_IN] = -2; + info->m[PSC_VOLTAGE_OUT] = 7578; + info->R[PSC_VOLTAGE_OUT] = -2; + info->m[PSC_CURRENT_IN] = 3824; + info->b[PSC_CURRENT_IN] = -4300; + info->R[PSC_CURRENT_IN] = -3; + info->m[PSC_CURRENT_OUT] = 3824; + info->b[PSC_CURRENT_OUT] = -4300; + info->R[PSC_CURRENT_OUT] = -3; + info->m[PSC_POWER] = 895; + info->b[PSC_POWER] = -9100; + info->R[PSC_POWER] = -2; + + vt7505_set_m(&info->m[PSC_CURRENT_IN], rload); + vt7505_set_m(&info->m[PSC_CURRENT_OUT], rload); + vt7505_set_m(&info->m[PSC_POWER], rload); + + info->m[PSC_TEMPERATURE] = chip->temp_m; + info->b[PSC_TEMPERATURE] = chip->temp_b; + info->R[PSC_TEMPERATURE] = -2; + + info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT | + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_IIN | PMBUS_HAVE_PIN; + + /* + * The severe OCP deglitch filter is programmable on the MAX16550 and + * the VT7505, but fixed on the MAX16545. + */ + if (chip->has_ocp_filter) { + ret = vt7505_set_ocp_filter(client); + if (ret) + return ret; + } + + return pmbus_do_probe(client, info); +} + +static const struct i2c_device_id vt7505_id[] = { + { .name = "max16545", .driver_data = (kernel_ulong_t)&max16545_data }, + { .name = "max16550", .driver_data = (kernel_ulong_t)&max16550_data }, + { .name = "vt7505", .driver_data = (kernel_ulong_t)&vt7505_data }, + { } +}; +MODULE_DEVICE_TABLE(i2c, vt7505_id); + +static const struct of_device_id vt7505_of_match[] = { + { .compatible = "adi,max16545", .data = &max16545_data }, + { .compatible = "adi,max16550", .data = &max16550_data }, + { .compatible = "adi,vt7505", .data = &vt7505_data }, + { } +}; +MODULE_DEVICE_TABLE(of, vt7505_of_match); + +static struct i2c_driver vt7505_driver = { + .driver = { + .name = "vt7505", + .of_match_table = vt7505_of_match, + }, + .probe = vt7505_probe, + .id_table = vt7505_id, +}; +module_i2c_driver(vt7505_driver); + +MODULE_AUTHOR("Georgi Vlaev "); +MODULE_DESCRIPTION("PMBus driver for Analog Devices MAX16545/MAX16550 and Volterra VT7505"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("PMBUS"); diff --git a/drivers/hwmon/pmbus/xdpe1a2g7b.c b/drivers/hwmon/pmbus/xdpe1a2g7b.c --- a/drivers/hwmon/pmbus/xdpe1a2g7b.c +++ b/drivers/hwmon/pmbus/xdpe1a2g7b.c @@ -57,6 +57,13 @@ static int xdpe1a2g7b_identify(struct i2c_client *client, return 0; } +#if IS_ENABLED(CONFIG_SENSORS_XDPE1A2G7B_REGULATOR) +static const struct regulator_desc xdpe1a2g7b_reg_desc[] = { + PMBUS_REGULATOR("vout", 0), + PMBUS_REGULATOR("vout", 1), +}; +#endif + static struct pmbus_driver_info xdpe1a2g7b_info = { .pages = XDPE1A2G7B_PAGE_NUM, .identify = xdpe1a2g7b_identify, @@ -72,6 +79,10 @@ static struct pmbus_driver_info xdpe1a2g7b_info = { .func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | PMBUS_HAVE_STATUS_INPUT, +#if IS_ENABLED(CONFIG_SENSORS_XDPE1A2G7B_REGULATOR) + .num_regulators = XDPE1A2G7B_PAGE_NUM, + .reg_desc = xdpe1a2g7b_reg_desc, +#endif }; static int xdpe1a2g7b_probe(struct i2c_client *client) diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c --- a/drivers/hwmon/pwm-fan.c +++ b/drivers/hwmon/pwm-fan.c @@ -634,12 +634,8 @@ static int pwm_fan_probe(struct platform_device *pdev) if (tach->irq > 0) { ret = devm_request_irq(dev, tach->irq, pulse_handler, IRQF_NO_THREAD, pdev->name, tach); - if (ret) { - dev_err(dev, - "Failed to request interrupt: %d\n", - ret); + if (ret) return ret; - } } if (!ctx->pulses_per_revolution[i]) { diff --git a/drivers/hwmon/sht15.c b/drivers/hwmon/sht15.c --- a/drivers/hwmon/sht15.c +++ b/drivers/hwmon/sht15.c @@ -981,10 +981,8 @@ static int sht15_probe(struct platform_device *pdev) IRQF_TRIGGER_FALLING, "sht15 data", data); - if (ret) { - dev_err(&pdev->dev, "failed to get irq for data line\n"); + if (ret) goto err_release_reg; - } disable_irq_nosync(gpiod_to_irq(data->data)); ret = sht15_connection_reset(data); if (ret) diff --git a/drivers/hwmon/sht3x.c b/drivers/hwmon/sht3x.c --- a/drivers/hwmon/sht3x.c +++ b/drivers/hwmon/sht3x.c @@ -62,7 +62,7 @@ static const unsigned char sht3x_cmd_read_serial_number[] = { 0x37, 0x80 }; #define SHT3X_MAX_HUMIDITY 100000 enum sht3x_chips { - sht3x, + sht3x = 1, sts3x, }; @@ -940,8 +940,19 @@ static const struct i2c_device_id sht3x_ids[] = { MODULE_DEVICE_TABLE(i2c, sht3x_ids); +static const struct of_device_id sht3x_of_match[] = { + { .compatible = "sensirion,sht30", .data = (void *)(uintptr_t)sht3x }, + { .compatible = "sensirion,sts30", .data = (void *)(uintptr_t)sts3x }, + { } +}; + +MODULE_DEVICE_TABLE(of, sht3x_of_match); + static struct i2c_driver sht3x_i2c_driver = { - .driver.name = "sht3x", + .driver = { + .name = "sht3x", + .of_match_table = sht3x_of_match, + }, .probe = sht3x_probe, .id_table = sht3x_ids, }; diff --git a/drivers/hwmon/tmp102.c b/drivers/hwmon/tmp102.c --- a/drivers/hwmon/tmp102.c +++ b/drivers/hwmon/tmp102.c @@ -396,6 +396,8 @@ static DEFINE_SIMPLE_DEV_PM_OPS(tmp102_dev_pm_ops, tmp102_suspend, tmp102_resume static const struct i2c_device_id tmp102_id[] = { { .name = "tmp102" }, + { .name = "tmp110" }, + { .name = "tmp113" }, { } }; MODULE_DEVICE_TABLE(i2c, tmp102_id); diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c --- a/drivers/hwmon/tmp401.c +++ b/drivers/hwmon/tmp401.c @@ -695,7 +695,7 @@ static int tmp401_probe(struct i2c_client *client) info->type = hwmon_chip; info->config = data->chip_channel_config; - data->chip_channel_config[0] = HWMON_C_UPDATE_INTERVAL; + data->chip_channel_config[0] = HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL; info = &data->temp_info; info->type = hwmon_temp; diff --git a/drivers/hwmon/tvs-mpfs.c b/drivers/hwmon/tvs-mpfs.c new file mode 100644 --- /dev/null +++ b/drivers/hwmon/tvs-mpfs.c @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Author: Lars Randers + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MPFS_TVS_CTRL 0x08 +#define MPFS_TVS_OUTPUT0 0x24 +#define MPFS_TVS_OUTPUT1 0x28 + +#define MPFS_TVS_CTRL_TEMP_VALID BIT(19) +#define MPFS_TVS_CTRL_V2P5_VALID BIT(18) +#define MPFS_TVS_CTRL_V1P8_VALID BIT(17) +#define MPFS_TVS_CTRL_V1P05_VALID BIT(16) + +#define MPFS_TVS_CTRL_TEMP_ENABLE BIT(3) +#define MPFS_TVS_CTRL_V2P5_ENABLE BIT(2) +#define MPFS_TVS_CTRL_V1P8_ENABLE BIT(1) +#define MPFS_TVS_CTRL_V1P05_ENABLE BIT(0) +#define MPFS_TVS_CTRL_ENABLE_ALL GENMASK(3, 0) + +/* + * For all of these the value in millivolts is stored in 16 bits, with an upper + * sign bit and a lower 3 bits of decimal. These masks discard the sign bit and + * decimal places, because if Linux is running these voltages cannot be negative + * and so avoid having to convert to two's complement. + */ +#define MPFS_OUTPUT0_V1P8_MASK GENMASK(30, 19) +#define MPFS_OUTPUT0_V1P05_MASK GENMASK(14, 3) +#define MPFS_OUTPUT1_V2P5_MASK GENMASK(14, 3) + +/* + * The register map claims that the temperature is stored in bits 31:16, but + * application note "AN4682: PolarFire FPGA Temperature and Voltage Sensor" + * says that 31 is reserved. Temperature is in kelvin, so what's probably a + * sign bit has no value anyway. + */ +#define MPFS_OUTPUT1_TEMP_MASK GENMASK(30, 16) + +#define MPFS_TVS_INTERVAL_MASK GENMASK(15, 8) +#define MPFS_TVS_INTERVAL_OFFSET 8 +/* The interval register is in increments of 32 us */ +#define MPFS_TVS_INTERVAL_SCALE 32 +/* with 254 usable increments of 32 us available, 8 ms is the integer limit */ +#define MPFS_TVS_INTERVAL_MAX_MS 8 + +/* 273.1875 in 11.4 fixed-point notation */ +#define MPFS_TVS_K_TO_C 0x1113 + +enum mpfs_tvs_sensors { + SENSOR_V1P05 = 0, + SENSOR_V1P8, + SENSOR_V2P5, +}; + +static const char * const mpfs_tvs_voltage_labels[] = { "1P05", "1P8", "2P5" }; + +struct mpfs_tvs { + struct regmap *regmap; +}; + +static int mpfs_tvs_voltage_read(struct mpfs_tvs *data, u32 attr, + int channel, long *val) +{ + u32 tmp, control; + + if (attr != hwmon_in_input && attr != hwmon_in_enable) + return -EOPNOTSUPP; + + regmap_read(data->regmap, MPFS_TVS_CTRL, &control); + + switch (channel) { + case SENSOR_V2P5: + if (attr == hwmon_in_enable) { + *val = FIELD_GET(MPFS_TVS_CTRL_V2P5_ENABLE, control); + break; + } + + if (!(control & MPFS_TVS_CTRL_V2P5_VALID)) + return -ENODATA; + + regmap_read(data->regmap, MPFS_TVS_OUTPUT1, &tmp); + *val = FIELD_GET(MPFS_OUTPUT1_V2P5_MASK, tmp); + break; + case SENSOR_V1P8: + if (attr == hwmon_in_enable) { + *val = FIELD_GET(MPFS_TVS_CTRL_V1P8_ENABLE, control); + break; + } + + if (!(control & MPFS_TVS_CTRL_V1P8_VALID)) + return -ENODATA; + + regmap_read(data->regmap, MPFS_TVS_OUTPUT0, &tmp); + *val = FIELD_GET(MPFS_OUTPUT0_V1P8_MASK, tmp); + break; + case SENSOR_V1P05: + if (attr == hwmon_in_enable) { + *val = FIELD_GET(MPFS_TVS_CTRL_V1P05_ENABLE, control); + break; + } + + if (!(control & MPFS_TVS_CTRL_V1P05_VALID)) + return -ENODATA; + + regmap_read(data->regmap, MPFS_TVS_OUTPUT0, &tmp); + *val = FIELD_GET(MPFS_OUTPUT0_V1P05_MASK, tmp); + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int mpfs_tvs_voltage_write(struct mpfs_tvs *data, u32 attr, + int channel, long val) +{ + u32 tmp; + + if (attr != hwmon_in_enable) + return -EOPNOTSUPP; + + if (val > 1 || val < 0) + return -EINVAL; + + switch (channel) { + case SENSOR_V2P5: + tmp = FIELD_PREP(MPFS_TVS_CTRL_V2P5_ENABLE, val); + regmap_update_bits(data->regmap, MPFS_TVS_CTRL, + MPFS_TVS_CTRL_V2P5_ENABLE, tmp); + break; + case SENSOR_V1P8: + tmp = FIELD_PREP(MPFS_TVS_CTRL_V1P8_ENABLE, val); + regmap_update_bits(data->regmap, MPFS_TVS_CTRL, + MPFS_TVS_CTRL_V1P8_ENABLE, tmp); + break; + case SENSOR_V1P05: + tmp = FIELD_PREP(MPFS_TVS_CTRL_V1P05_ENABLE, val); + regmap_update_bits(data->regmap, MPFS_TVS_CTRL, + MPFS_TVS_CTRL_V1P05_ENABLE, tmp); + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int mpfs_tvs_temp_read(struct mpfs_tvs *data, u32 attr, long *val) +{ + u32 tmp, control; + + if (attr != hwmon_temp_input && attr != hwmon_temp_enable) + return -EOPNOTSUPP; + + regmap_read(data->regmap, MPFS_TVS_CTRL, &control); + + if (attr == hwmon_temp_enable) { + *val = FIELD_GET(MPFS_TVS_CTRL_TEMP_ENABLE, control); + return 0; + } + + if (!(control & MPFS_TVS_CTRL_TEMP_VALID)) + return -ENODATA; + + regmap_read(data->regmap, MPFS_TVS_OUTPUT1, &tmp); + *val = FIELD_GET(MPFS_OUTPUT1_TEMP_MASK, tmp); + *val -= MPFS_TVS_K_TO_C; + *val = (1000 * *val) >> 4; /* fixed point (11.4) to millidegrees */ + + return 0; +} + +static int mpfs_tvs_temp_write(struct mpfs_tvs *data, u32 attr, long val) +{ + u32 tmp; + + if (attr != hwmon_temp_enable) + return -EOPNOTSUPP; + + if (val > 1 || val < 0) + return -EINVAL; + + tmp = FIELD_PREP(MPFS_TVS_CTRL_TEMP_ENABLE, val); + regmap_update_bits(data->regmap, MPFS_TVS_CTRL, + MPFS_TVS_CTRL_TEMP_ENABLE, tmp); + + return 0; +} + +static int mpfs_tvs_interval_read(struct mpfs_tvs *data, u32 attr, long *val) +{ + u32 tmp; + + if (attr != hwmon_chip_update_interval) + return -EOPNOTSUPP; + + regmap_read(data->regmap, MPFS_TVS_CTRL, &tmp); + *val = FIELD_GET(MPFS_TVS_INTERVAL_MASK, tmp); + *val *= MPFS_TVS_INTERVAL_SCALE; + *val = roundup(*val, 1000); + *val /= 1000; + + return 0; +} + +static int mpfs_tvs_interval_write(struct mpfs_tvs *data, u32 attr, long val) +{ + long temp = val; + + if (attr != hwmon_chip_update_interval) + return -EOPNOTSUPP; + + temp = clamp(temp, 0, MPFS_TVS_INTERVAL_MAX_MS); + + temp *= 1000; + temp /= MPFS_TVS_INTERVAL_SCALE; + + temp <<= MPFS_TVS_INTERVAL_OFFSET; + regmap_update_bits(data->regmap, MPFS_TVS_CTRL, + MPFS_TVS_INTERVAL_MASK, temp); + + return 0; +} + +static umode_t mpfs_tvs_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + if (type == hwmon_chip && attr == hwmon_chip_update_interval) + return 0644; + + if (type == hwmon_temp) { + switch (attr) { + case hwmon_temp_enable: + return 0644; + case hwmon_temp_input: + case hwmon_temp_label: + return 0444; + default: + return 0; + } + } + + if (type == hwmon_in) { + switch (attr) { + case hwmon_in_enable: + return 0644; + case hwmon_in_input: + case hwmon_in_label: + return 0444; + default: + return 0; + } + } + + return 0; +} + +static int mpfs_tvs_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct mpfs_tvs *data = dev_get_drvdata(dev); + + switch (type) { + case hwmon_temp: + return mpfs_tvs_temp_read(data, attr, val); + case hwmon_in: + return mpfs_tvs_voltage_read(data, attr, channel, val); + case hwmon_chip: + return mpfs_tvs_interval_read(data, attr, val); + default: + return -EOPNOTSUPP; + } +} + +static int mpfs_tvs_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct mpfs_tvs *data = dev_get_drvdata(dev); + + switch (type) { + case hwmon_temp: + return mpfs_tvs_temp_write(data, attr, val); + case hwmon_in: + return mpfs_tvs_voltage_write(data, attr, channel, val); + case hwmon_chip: + return mpfs_tvs_interval_write(data, attr, val); + default: + return -EOPNOTSUPP; + } +} + +static int mpfs_tvs_read_labels(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, + const char **str) +{ + switch (type) { + case hwmon_temp: + *str = "Die Temp"; + return 0; + case hwmon_in: + *str = mpfs_tvs_voltage_labels[channel]; + return 0; + default: + return -EOPNOTSUPP; + } +} + +static const struct hwmon_ops mpfs_tvs_ops = { + .is_visible = mpfs_tvs_is_visible, + .read_string = mpfs_tvs_read_labels, + .read = mpfs_tvs_read, + .write = mpfs_tvs_write, +}; + +static const struct hwmon_channel_info *mpfs_tvs_info[] = { + HWMON_CHANNEL_INFO(chip, + HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_ENABLE), + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_ENABLE, + HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_ENABLE, + HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_ENABLE), + NULL +}; + +static const struct hwmon_chip_info mpfs_tvs_chip_info = { + .ops = &mpfs_tvs_ops, + .info = mpfs_tvs_info, +}; + +static int mpfs_tvs_probe(struct platform_device *pdev) +{ + struct device *hwmon_dev; + struct mpfs_tvs *data; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->regmap = device_node_to_regmap(pdev->dev.parent->of_node); + if (IS_ERR(data->regmap)) + return dev_err_probe(&pdev->dev, PTR_ERR(data->regmap), + "Failed to find syscon regmap\n"); + + /* + * It's an MMIO regmap with no resources, there's nothing that can fail + * and return an error + */ + regmap_write(data->regmap, MPFS_TVS_CTRL, MPFS_TVS_CTRL_ENABLE_ALL); + + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, "mpfs_tvs", + data, + &mpfs_tvs_chip_info, + NULL); + if (IS_ERR(hwmon_dev)) + return dev_err_probe(&pdev->dev, PTR_ERR(hwmon_dev), + "hwmon device registration failed.\n"); + + return 0; +} + +static struct platform_driver mpfs_tvs_driver = { + .probe = mpfs_tvs_probe, + .driver = { + .name = "mpfs-tvs", + }, +}; +module_platform_driver(mpfs_tvs_driver); + +MODULE_AUTHOR("Lars Randers "); +MODULE_DESCRIPTION("PolarFire SoC temperature & voltage sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/xgene-hwmon.c b/drivers/hwmon/xgene-hwmon.c --- a/drivers/hwmon/xgene-hwmon.c +++ b/drivers/hwmon/xgene-hwmon.c @@ -133,10 +133,6 @@ static int xgene_hwmon_pcc_rd(struct xgene_hwmon_dev *ctx, u32 *msg) init_completion(&ctx->rd_complete); ctx->resp_pending = true; - /* Write signature for subspace */ - WRITE_ONCE(generic_comm_base->signature, - cpu_to_le32(PCC_SIGNATURE | ctx->mbox_idx)); - /* Write to the shared command region */ WRITE_ONCE(generic_comm_base->command, cpu_to_le16(MSG_TYPE(msg[0]) | PCC_CMD_GENERATE_DB_INTR)); diff --git a/drivers/hwmon/yogafan.c b/drivers/hwmon/yogafan.c --- a/drivers/hwmon/yogafan.c +++ b/drivers/hwmon/yogafan.c @@ -53,6 +53,7 @@ struct yoga_fan_data { }; /* Specific configurations mapped via DMI */ + static const struct yogafan_config yoga_8bit_fans_cfg = { .multiplier = 100, .fan_count = 1, @@ -71,6 +72,24 @@ static const struct yogafan_config legion_16bit_dual_cfg = { .paths = { "\\_SB.PCI0.LPC0.EC0.FANS", "\\_SB.PCI0.LPC0.EC0.FA2S" } }; +static const struct yogafan_config loq_15iax9_8bit_dual_cfg = { + .multiplier = 100, + .fan_count = 2, + .paths = { "\\_SB.PC00.LPCB.EC0.FA1S", "\\_SB.PC00.LPCB.EC0.FA2S" } +}; + +static const struct yogafan_config xiaoxin_8bit_dual_cfg = { + .multiplier = 100, + .fan_count = 2, + .paths = { "\\_SB.PCI0.LPC0.EC0.FANS", "\\_SB.PCI0.LPC0.EC0.FA2S" } +}; + +static const struct yogafan_config yoga_pro_7_14iah10_cfg = { + .multiplier = 100, + .fan_count = 1, + .paths = { "\\_SB.PC00.LPCB.EC0.FANS", NULL } +}; + static void apply_rllag_filter(struct yoga_fan_data *data, int idx, long raw_rpm) { ktime_t now = ktime_get_boottime(); @@ -170,6 +189,54 @@ static const struct hwmon_chip_info yoga_fan_chip_info = { }; static const struct dmi_system_id yogafan_quirks[] = { + { + .ident = "Lenovo LOQ 15IAX9", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "LOQ 15IAX9"), + }, + .driver_data = (void *)&loq_15iax9_8bit_dual_cfg, + }, + { + .ident = "Lenovo XiaoXin Pro 13ARE 2020", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "XiaoXinPro-13ARE 2020"), + }, + .driver_data = (void *)&xiaoxin_8bit_dual_cfg, + }, + { + .ident = "Lenovo IdeaPad 3 15ALC6", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "IdeaPad 3 15ALC6"), + }, + .driver_data = (void *)&ideapad_8bit_fan0_cfg, + }, + { + .ident = "Lenovo Legion Pro 7 16AFR10H", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "Legion Pro 7 16AFR10H"), + }, + .driver_data = (void *)&xiaoxin_8bit_dual_cfg, + }, + { + .ident = "Lenovo Yoga Pro 7 14IAH10", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "Yoga Pro 7 14IAH10"), + }, + .driver_data = (void *)&yoga_pro_7_14iah10_cfg, + }, + { + .ident = "Lenovo Yoga 7 16ARP8", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "Yoga 7 16ARP8"), + }, + .driver_data = (void *)&xiaoxin_8bit_dual_cfg, + }, { .ident = "Lenovo Yoga", .matches = { diff --git a/include/linux/hwmon-sysfs.h b/include/linux/hwmon-sysfs.h --- a/include/linux/hwmon-sysfs.h +++ b/include/linux/hwmon-sysfs.h @@ -15,10 +15,10 @@ struct sensor_device_attribute{ int index; }; #define to_sensor_dev_attr(_dev_attr) \ - container_of(_dev_attr, struct sensor_device_attribute, dev_attr) + container_of_const(_dev_attr, struct sensor_device_attribute, dev_attr) -#define SENSOR_ATTR(_name, _mode, _show, _store, _index) \ - { .dev_attr = __ATTR(_name, _mode, _show, _store), \ +#define SENSOR_ATTR(_name, _mode, _show, _store, _index) \ + { .dev_attr = __DEVICE_ATTR(_name, _mode, _show, _store), \ .index = _index } #define SENSOR_ATTR_RO(_name, _func, _index) \ @@ -49,11 +49,11 @@ struct sensor_device_attribute_2 { u8 nr; }; #define to_sensor_dev_attr_2(_dev_attr) \ - container_of(_dev_attr, struct sensor_device_attribute_2, dev_attr) + container_of_const(_dev_attr, struct sensor_device_attribute_2, dev_attr) -#define SENSOR_ATTR_2(_name, _mode, _show, _store, _nr, _index) \ - { .dev_attr = __ATTR(_name, _mode, _show, _store), \ - .index = _index, \ +#define SENSOR_ATTR_2(_name, _mode, _show, _store, _nr, _index) \ + { .dev_attr = __DEVICE_ATTR(_name, _mode, _show, _store), \ + .index = _index, \ .nr = _nr } #define SENSOR_ATTR_2_RO(_name, _func, _nr, _index) \