7420 lines
222 KiB
Diff
7420 lines
222 KiB
Diff
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, ®val);
|
||
|
||
if (ret)
|
||
@@ -384,6 +388,10 @@ static const struct hwmon_channel_info * const k10temp_info[] = {
|
||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||
HWMON_T_INPUT | HWMON_T_LABEL,
|
||
+ HWMON_T_INPUT | HWMON_T_LABEL,
|
||
+ HWMON_T_INPUT | HWMON_T_LABEL,
|
||
+ HWMON_T_INPUT | HWMON_T_LABEL,
|
||
+ HWMON_T_INPUT | HWMON_T_LABEL,
|
||
HWMON_T_INPUT | HWMON_T_LABEL),
|
||
NULL
|
||
};
|
||
@@ -515,6 +523,10 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
||
}
|
||
} else if (boot_cpu_data.x86 == 0x1a) {
|
||
switch (boot_cpu_data.x86_model) {
|
||
+ case 0x00 ... 0x2f: /* Zen5 Turin */
|
||
+ data->ccd_offset = 0x1F0;
|
||
+ k10temp_get_ccd_support(data, 16);
|
||
+ break;
|
||
case 0x40 ... 0x4f: /* Zen5 Ryzen Desktop */
|
||
data->ccd_offset = 0x308;
|
||
k10temp_get_ccd_support(data, 8);
|
||
diff --git a/drivers/hwmon/kb9002.c b/drivers/hwmon/kb9002.c
|
||
new file mode 100644
|
||
--- /dev/null
|
||
+++ b/drivers/hwmon/kb9002.c
|
||
@@ -0,0 +1,460 @@
|
||
+// SPDX-License-Identifier: GPL-2.0-or-later
|
||
+/*
|
||
+ * Kandou KB9002 PCIe 5.0 retimer hwmon driver.
|
||
+ *
|
||
+ * The retimer exposes a system management bus (SMBus 3.0 with PEC)
|
||
+ * target for firmware-managed status registers. This driver assumes
|
||
+ * the chip is strapped to SMBus mode and exports the aggregated
|
||
+ * maximum die temperature as hwmon temp1_input (millidegrees Celsius)
|
||
+ * plus the firmware version and boot status under debugfs.
|
||
+ *
|
||
+ * The raw-I2C path (kb9002_i2c_read/write, used only at probe to switch
|
||
+ * the host interface) carries the 32-bit register address and data
|
||
+ * big-endian. The SMBus path (kb9002_fw_read/kb9002_smbus_hw_read)
|
||
+ * carries the register address and returned data little-endian.
|
||
+ *
|
||
+ * Datasheet: Kandou KB9002 PCIe retimer (KA-015171-PD).
|
||
+ */
|
||
+
|
||
+#include <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 = <c2978_id[0]; strlen(id->name); id++) {
|
||
@@ -637,16 +633,17 @@ static int ltc2978_get_id(struct i2c_client *client)
|
||
u8 buf[I2C_SMBUS_BLOCK_MAX];
|
||
int ret;
|
||
|
||
- ret = i2c_smbus_read_i2c_block_data(client, PMBUS_IC_DEVICE_ID,
|
||
- sizeof(buf), buf);
|
||
+ ret = pmbus_read_smbus_i2c_block_data(client, PMBUS_IC_DEVICE_ID,
|
||
+ buf);
|
||
if (ret < 0)
|
||
return ret;
|
||
|
||
- if (!strncmp(buf + 1, "LT7170", 6) ||
|
||
- !strncmp(buf + 1, "LT7170-1", 8))
|
||
+ if (ret < 6)
|
||
+ return -ENODEV;
|
||
+
|
||
+ if (!strncmp(buf, "LT7170", 6))
|
||
return lt7170;
|
||
- if (!strncmp(buf + 1, "LT7171", 6) ||
|
||
- !strncmp(buf + 1, "LT7171-1", 8))
|
||
+ if (!strncmp(buf, "LT7171", 6))
|
||
return lt7171;
|
||
|
||
return -ENODEV;
|
||
diff --git a/drivers/hwmon/pmbus/max20830.c b/drivers/hwmon/pmbus/max20830.c
|
||
--- a/drivers/hwmon/pmbus/max20830.c
|
||
+++ b/drivers/hwmon/pmbus/max20830.c
|
||
@@ -7,12 +7,69 @@
|
||
|
||
#include <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) \
|