Files
omarchy-pkgs/pkgbuilds/linux-omarchy-bore/0740-hwmon-updates.patch
T

7420 lines
222 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 <linux/platform_device.h>
#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 <linux/sched.h>
#include <linux/slab.h>
#include <linux/util_macros.h>
+#include <linux/pwm.h>
+#include <linux/cleanup.h>
+#include <linux/math64.h>
/* 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 <linux/workqueue.h>
#include <linux/err.h>
#include <linux/bits.h>
+#include <asm/barrier.h>
/* 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 <savicaleksa83@gmail.com>
*/
@@ -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 <savicaleksa83@gmail.com>");
-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 <linux/irq.h>
#include <linux/module.h>
#include <linux/regulator/consumer.h>
+#include <linux/mod_devicetable.h>
+#include <linux/property.h>
#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 <luyulin@eswincomputing.com>
+ * Huan He <hehuan1@eswincomputing.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/polynomial.h>
+#include <linux/reset.h>
+#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 <luyulin@eswincomputing.com>");
+MODULE_AUTHOR("Huan He <hehuan1@eswincomputing.com>");
+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 <linux/completion.h>
+#include <linux/hwmon.h>
+#include <linux/kernel.h>
+#include <linux/time.h>
+
+/* 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 <linux/err.h>
#include <linux/sysfs.h>
#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
#include <linux/util_macros.h>
#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 <linux/bits.h>
#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
-#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, &regval);
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 <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/debugfs.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/seq_file.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+
+#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 <andy.chung@amd.com>");
+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 <linux/bits.h>
#include <linux/device.h>
#include <linux/err.h>
+#include <linux/fwnode.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/interrupt.h>
@@ -108,7 +109,7 @@
#include <linux/hwmon.h>
#include <linux/kstrtox.h>
#include <linux/module.h>
-#include <linux/of.h>
+#include <linux/property.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
@@ -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-<bus>/<bus>-<addr>/
+
+ 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 = &ltc2978_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 <linux/errno.h>
#include <linux/i2c.h>
+#include <linux/math64.h>
#include <linux/module.h>
#include <linux/string.h>
#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 <linux/bitfield.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#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 <wenswang@yeah.net");
+MODULE_DESCRIPTION("PMBus driver for MPS MPQ82D00 device");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("PMBUS");
diff --git a/drivers/hwmon/pmbus/mpq8646.c b/drivers/hwmon/pmbus/mpq8646.c
new file mode 100644
--- /dev/null
+++ b/drivers/hwmon/pmbus/mpq8646.c
@@ -0,0 +1,976 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for MPS MPQ8646 step-down converter.
+ *
+ * Copyright (c) 2026 Free Mobile - Vincent Jardin <vjardin@free.fr>
+ */
+
+#include <linux/bitops.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/nvmem-provider.h>
+#include <linux/of_device.h>
+#include <linux/pmbus.h>
+#include <linux/property.h>
+#include <linux/regulator/driver.h>
+#include <linux/seq_file.h>
+#include <linux/workqueue.h>
+#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/<i2c-name>/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 <vjardin@free.fr>");
+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 <ziming.zhu@silergycorp.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/math64.h>
+
+#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 <ziming.zhu@silergycorp.com>");
+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 <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pmbus.h>
+
+#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 <gvlaev@juniper.net>");
+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 <lranders@mail.dk>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/err.h>
+#include <linux/freezer.h>
+#include <linux/hwmon.h>
+#include <linux/io.h>
+#include <linux/kthread.h>
+#include <linux/math.h>
+#include <linux/mfd/syscon.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#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 <lranders@mail.dk>");
+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) \