* [PATCH RFC] hwmon: Add Support for PMIC5000 Power Management ICs
@ 2026-08-19 13:37 Stephen Horvath
2026-08-19 13:51 ` sashiko-bot
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Stephen Horvath @ 2026-08-19 13:37 UTC (permalink / raw)
To: Guenter Roeck
Cc: Andi Shyti, linux-hwmon, linux-i2c, linux-kernel, Stephen Horvath,
Stephen Horvath
Add support for sensors present on PMIC5000 (JEDEC JESD301) compliant
power management ICs. These chips are commonly found on DDR5 memory
modules.
Tested against PMIC5100 chips, but only features present in the
PMIC5000 (JESD301-1) specification have been implemented.
Signed-off-by: Stephen Horvath <linux@stevetech.au>
---
Hi maintainers, I wrote this over the past couple days and I'm looking
for feedback.
Here's a few concerns I have:
- I'm not too experienced with kernel development.
- I don't think there's a good way to auto-detect PMIC5000 chips
standalone, would it be reasonable to instantiate it from
spd5118?
- I'm unsure what to do with temperatures below 85°C, I'd rather not
return 85 as that would make it seem hotter than it is, but
returning -EOPNOTSUPP seems wrong.
- Toggling between current and power seems like it's a bit of a hack,
is that okay, or should I just pick one and drop the other?
- Does this belong in HWMON, or should it be part of the regulator
subsystem?
---
.../devicetree/bindings/trivial-devices.yaml | 2 +
Documentation/hwmon/index.rst | 1 +
Documentation/hwmon/pmic5000.rst | 103 +++
drivers/hwmon/Kconfig | 12 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/pmic5000.c | 896 +++++++++++++++++++++
6 files changed, 1015 insertions(+)
diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml
index 2de8eb09cb7d..86e76be7d41b 100644
--- a/Documentation/devicetree/bindings/trivial-devices.yaml
+++ b/Documentation/devicetree/bindings/trivial-devices.yaml
@@ -194,6 +194,8 @@ properties:
- isil,isl29030
# Intersil ISL76682 Ambient Light Sensor
- isil,isl76682
+ # JEDEC JESD301 (PMIC5000) Power Management IC
+ - jedec,pmic5000
# JEDEC JESD300 (SPD5118) Hub and Serial Presence Detect
- jedec,spd5118
# Kandou KB9002 PCIe 5.0 retimer
diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index d979e6d6e9f2..4bb518d1927c 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -226,6 +226,7 @@ Hardware Monitoring Kernel Drivers
peci-cputemp
peci-dimmtemp
pmbus
+ pmic5000
powerz
powr1220
prom21-xhci
diff --git a/Documentation/hwmon/pmic5000.rst b/Documentation/hwmon/pmic5000.rst
new file mode 100644
index 000000000000..e7d864088ba5
--- /dev/null
+++ b/Documentation/hwmon/pmic5000.rst
@@ -0,0 +1,103 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Kernel driver pmic5000
+=====================
+
+Supported chips:
+
+ * PMIC5000 (JEDEC JESD301) compliant power management chips
+
+ JEDEC standard download:
+ https://www.jedec.org/standards-documents/docs/jesd301-1a03
+ (account required)
+
+
+ Prefix: 'pmic5000'
+
+Author:
+ Stephen Horvath <linux@stevetech.au>
+
+
+Description
+-----------
+
+This driver implements support for PMIC5000 (JEDEC JESD301) compliant power
+management chips, which are used on many DDR5 memory modules.
+
+The driver does not auto-detect PMIC5000 compliant chips, and must be
+instantiated manually or via device tree. The address of these chips are
+usually between (inclusive) 0x48 and 0x4F.
+
+
+Hardware monitoring sysfs entries
+---------------------------------
+
+======================= ==================================
+in0_enable Whether SWA is enabled (RO)
+in0_input SWA Voltage (RO)
+in0_min Minimum SWA Voltage (RO)
+in0_max Maximum SWA Voltage (RO)
+in0_min_alarm SWA Voltage low alarm
+in0_max_alarm SWA Voltage high alarm
+
+in1_enable Whether SWB is enabled (RO)
+in1_input SWB Voltage (RO)
+in1_min Minimum SWB Voltage (RO)
+in1_max Maximum SWB Voltage (RO)
+in1_min_alarm SWB Voltage low alarm
+in1_max_alarm SWB Voltage high alarm
+
+in2_enable Whether SWC is enabled (RO)
+in2_input SWC Voltage (RO)
+in2_min Minimum SWC Voltage (RO)
+in2_max Maximum SWC Voltage (RO)
+in2_min_alarm SWC Voltage low alarm
+in2_max_alarm SWC Voltage high alarm
+
+in3_enable Whether SWD is enabled (RO)
+in3_input SWD Voltage (RO)
+in3_min Minimum SWD Voltage (RO)
+in3_max Maximum SWD Voltage (RO)
+in3_min_alarm SWD Voltage low alarm
+in3_max_alarm SWD Voltage high alarm
+
+in5_input VIN_Bulk Voltage (RO)
+in5_max Maximum VIN_Bulk Voltage (RO)
+in5_max_alarm VIN_Bulk Voltage high alarm
+
+in6_input VIN_Mgmt Voltage (RO)
+in6_max Maximum VIN_Mgmt Voltage (RO)
+in6_max_alarm VIN_Mgmt Voltage high alarm
+
+in7_input VBias Voltage (RO)
+in8_input VOUT_1.8V Voltage (RO)
+in9_input VOUT_1.0V Voltage (RO)
+
+temp1_input Temperature (RO)
+temp1_max Maximum temperature (RO)
+temp1_max_alarm Temperature high alarm
+
+power1_input SWA Wattage (RO)
+power2_input SWB Wattage (RO)
+power3_input SWC Wattage (RO)
+power4_input SWD Wattage (RO)
+
+curr1_input SWA Current (RO)
+curr1_max Maximum SWA Current (RO)
+curr1_max_alarm SWA Current high alarm
+
+curr2_input SWB Current (RO)
+curr2_max Maximum SWB Current (RO)
+curr2_max_alarm SWB Current high alarm
+
+curr3_input SWC Current (RO)
+curr3_max Maximum SWC Current (RO)
+curr3_max_alarm SWC Current high alarm
+
+curr4_input SWD Current (RO)
+curr4_max Maximum SWD Current (RO)
+curr4_max_alarm SWD Current high alarm
+
+update_interval Update interval in milliseconds (RW), Allowed values: 1, 2, 4, 8
+
+======================= ==================================
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 81a9a1d40eec..5ed5f9499c43 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -950,6 +950,18 @@ config SENSORS_JC42
This driver can also be built as a module. If so, the module
will be called jc42.
+config SENSORS_PMIC5000
+ tristate "PMIC5000 Compliant Sensors"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for PMIC5000 (JEDEC JESD301)
+ compliant sensors. Such sensors are found on DDR5 memory
+ modules.
+
+ This driver can also be built as a module. If so, the module
+ will be called pmic5000.
+
config SENSORS_POLARFIRE_SOC_TVS
tristate "PolarFire SoC (MPFS) temperature and voltage sensor"
depends on POLARFIRE_SOC_SYSCONS || COMPILE_TEST
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 0cad7e21634c..ac519123796f 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -199,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_PMIC5000) += pmic5000.o
obj-$(CONFIG_SENSORS_POLARFIRE_SOC_TVS) += tvs-mpfs.o
obj-$(CONFIG_SENSORS_POWERZ) += powerz.o
obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o
diff --git a/drivers/hwmon/pmic5000.c b/drivers/hwmon/pmic5000.c
new file mode 100644
index 000000000000..d3f3126dd9c8
--- /dev/null
+++ b/drivers/hwmon/pmic5000.c
@@ -0,0 +1,896 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for Jedec PMIC5000 compliant sensors
+ *
+ * Copyright (c) 2026 Stephen Horvath
+ *
+ * Inspired by spd5118.c.
+ *
+ * PMIC5000 compliant sensors are typically used on DDR5 memory modules.
+ */
+
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+
+/* PMIC5000 registers. */
+// clang-format off
+#define PMIC5000_REG_OVER_VOLT_IN 0x08
+#define PMIC5000_REG_OVER_CURRENT 0x09
+ #define PMIC5000_HIGH_TEMP BIT(7)
+#define PMIC5000_REG_OVER_VOLTAGE 0x0A
+#define PMIC5000_REG_UNDER_VOLTAGE 0x0B
+#define PMIC5000_REG_SWA_POWER 0x0C
+#define PMIC5000_REG_SWB_POWER 0x0D
+#define PMIC5000_REG_SWC_POWER 0x0E
+#define PMIC5000_REG_SWD_POWER 0x0F
+#define PMIC5000_REG_OUTPUT_SELECT 0x1A
+ #define PMIC5000_OUTPUT_SELECT BIT(1)
+#define PMIC5000_REG_THRES_AND_SEL 0x1B
+ #define PMIC5000_VIN_MGMT_THRESH BIT(5)
+ #define PMIC5000_CURR_OR_PWR BIT(6)
+ #define PMIC5000_VIN_BULK_THRESH BIT(7)
+#define PMIC5000_REG_SWA_CURR_WARN 0x1C
+#define PMIC5000_REG_SWB_CURR_WARN 0x1D
+#define PMIC5000_REG_SWC_CURR_WARN 0x1E
+#define PMIC5000_REG_SWD_CURR_WARN 0x1F
+#define PMIC5000_REG_SWA_VOLT_SET 0x21
+#define PMIC5000_REG_SWA_THRESH 0x22
+#define PMIC5000_REG_SWB_VOLT_SET 0x23
+#define PMIC5000_REG_SWB_THRESH 0x24
+#define PMIC5000_REG_SWC_VOLT_SET 0x25
+#define PMIC5000_REG_SWC_THRESH 0x26
+#define PMIC5000_REG_SWD_VOLT_SET 0x27
+#define PMIC5000_REG_SWD_THRESH 0x28
+#define PMIC5000_REG_SW_VOLT_RANGE 0x2B
+ #define PMIC5000_SWD_RANGE BIT(0)
+ #define PMIC5000_SWC_RANGE BIT(3)
+ #define PMIC5000_SWB_RANGE BIT(4)
+ #define PMIC5000_SWA_RANGE BIT(5)
+#define PMIC5000_REG_REGULATOR_CONTROL 0x2F
+ #define PMIC5000_SWD_CONTROL BIT(3)
+ #define PMIC5000_SWC_CONTROL BIT(4)
+ #define PMIC5000_SWB_CONTROL BIT(5)
+ #define PMIC5000_SWA_CONTROL BIT(6)
+#define PMIC5000_REG_ADC_CONFIG 0x30
+ #define PMIC5000_ADC_SELECT_MASK GENMASK(6, 3)
+ #define PMIC5000_ADC_ENABLE BIT(7)
+#define PMIC5000_REG_ADC_VOLTAGE 0x31
+#define PMIC5000_REG_TEMPERATURE 0x33
+#define PMIC5000_REG_REVISION 0x3B
+#define PMIC5000_REG_VENDOR 0x3C
+
+
+/* 125 mA multiplier */
+#define PMIC5000_CURR_UNIT 125
+/* 125 mW multiplier */
+#define PMIC5000_POWER_UNIT (125 * MILLIWATT_PER_WATT)
+/* mV multipliers */
+#define PMIC5000_VOLT_UNIT 15
+#define PMIC5000_VINBULK_UNIT 70
+#define PMIC5000_VBIAS_UNIT 25
+// clang-format on
+
+struct pmic5000_data {
+ struct regmap *regmap;
+ struct mutex mode_lock;
+ struct mutex adc_lock;
+};
+
+static const char *const pmic5000_power_labels[] = { "SWA", "SWB", "SWC",
+ "SWD" };
+
+static const char *const pmic5000_voltage_labels[] = {
+ "SWA", "SWB", "SWC", "SWD", NULL,
+ "VIN_Bulk", "VIN_Mgmt", "VBias", "VOUT_1.8V", "VOUT_1.0V"
+};
+
+/* hwmon */
+
+static int pmic5000_check_regulator_enabled(struct regmap *regmap, int channel)
+{
+ u32 regval;
+ int err;
+
+ err = regmap_read(regmap, PMIC5000_REG_REGULATOR_CONTROL, ®val);
+ if (err)
+ return err;
+ switch (channel) {
+ case 0:
+ return !!(regval & PMIC5000_SWA_CONTROL);
+ case 1:
+ return !!(regval & PMIC5000_SWB_CONTROL);
+ case 2:
+ return !!(regval & PMIC5000_SWC_CONTROL);
+ case 3:
+ return !!(regval & PMIC5000_SWD_CONTROL);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int pmic5000_read_temp(struct regmap *regmap, u32 attr, int channel,
+ long *val)
+{
+ int err;
+ u32 regval;
+
+ if (channel != 0)
+ return -EOPNOTSUPP;
+
+ switch (attr) {
+ case hwmon_temp_input: {
+ err = regmap_read(regmap, PMIC5000_REG_TEMPERATURE, ®val);
+ if (err)
+ return err;
+ regval >>= 5;
+ /* Below 85°C */
+ if (regval == 0)
+ return -EOPNOTSUPP;
+ /* 0b001 = 85°C, 0b010 = 95°C, etc. */
+ *val = (75 + regval * 10) * MILLIDEGREE_PER_DEGREE;
+ return 0;
+ }
+ case hwmon_temp_max: {
+ err = regmap_read(regmap, PMIC5000_REG_THRES_AND_SEL, ®val);
+ if (err)
+ return err;
+ regval &= 0x07;
+ /* Reserved */
+ if (regval == 0 || regval == 0x7)
+ return -EOPNOTSUPP;
+ *val = (75 + regval * 10) * MILLIDEGREE_PER_DEGREE;
+ return 0;
+ }
+ case hwmon_temp_max_alarm: {
+ err = regmap_read(regmap, PMIC5000_REG_OVER_CURRENT, ®val);
+ if (err)
+ return err;
+ *val = !!(regval & PMIC5000_HIGH_TEMP);
+ return 0;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int pmic5000_read_curr(struct pmic5000_data *data, u32 attr, int channel,
+ long *val)
+{
+ struct regmap *regmap = data->regmap;
+ int reg, err;
+ int shift = 0;
+ u32 regval;
+
+ if (attr == hwmon_curr_input) {
+ mutex_lock(&data->mode_lock);
+ /* Select power measurements */
+ err = regmap_update_bits(regmap, PMIC5000_REG_THRES_AND_SEL,
+ PMIC5000_CURR_OR_PWR, 0);
+ if (err)
+ goto error;
+
+ switch (channel) {
+ case 0:
+ reg = PMIC5000_REG_SWA_POWER;
+ break;
+ case 1:
+ reg = PMIC5000_REG_SWB_POWER;
+ break;
+ case 2:
+ reg = PMIC5000_REG_SWC_POWER;
+ break;
+ case 3:
+ reg = PMIC5000_REG_SWD_POWER;
+ break;
+ default:
+ err = -EOPNOTSUPP;
+ goto error;
+ }
+ } else if (attr == hwmon_curr_max) {
+ shift = 2;
+ switch (channel) {
+ case 0:
+ reg = PMIC5000_REG_SWA_CURR_WARN;
+ break;
+ case 1:
+ reg = PMIC5000_REG_SWB_CURR_WARN;
+ break;
+ case 2:
+ reg = PMIC5000_REG_SWC_CURR_WARN;
+ break;
+ case 3:
+ reg = PMIC5000_REG_SWD_CURR_WARN;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ } else if (attr == hwmon_curr_max_alarm && channel >= 0 &&
+ channel <= 3) {
+ err = regmap_read(regmap, PMIC5000_REG_OVER_CURRENT, ®val);
+ if (err)
+ return err;
+ *val = regval >> (3 - channel) & 0x01;
+ return 0;
+ } else {
+ return -EOPNOTSUPP;
+ }
+
+ err = regmap_read(regmap, reg, ®val);
+ if (err)
+ goto error;
+
+ if (attr == hwmon_curr_input)
+ mutex_unlock(&data->mode_lock);
+
+ *val = regval * PMIC5000_CURR_UNIT >> shift;
+ return 0;
+
+error:
+ if (attr == hwmon_curr_input)
+ mutex_unlock(&data->mode_lock);
+ return err;
+}
+
+static int pmic5000_read_power(struct pmic5000_data *data, u32 attr,
+ int channel, long *val)
+{
+ struct regmap *regmap = data->regmap;
+ int reg, err;
+ u32 regval;
+
+ if (attr != hwmon_power_input)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&data->mode_lock);
+ /* Select power measurements */
+ err = regmap_update_bits(regmap, PMIC5000_REG_THRES_AND_SEL,
+ PMIC5000_CURR_OR_PWR, PMIC5000_CURR_OR_PWR);
+ if (err)
+ goto error;
+
+ switch (channel) {
+ case 0:
+ reg = PMIC5000_REG_SWA_POWER;
+ break;
+ case 1:
+ reg = PMIC5000_REG_SWB_POWER;
+ break;
+ case 2:
+ reg = PMIC5000_REG_SWC_POWER;
+ break;
+ case 3:
+ reg = PMIC5000_REG_SWD_POWER;
+ break;
+ default:
+ err = -EOPNOTSUPP;
+ goto error;
+ }
+
+ err = regmap_read(regmap, reg, ®val);
+ if (err)
+ goto error;
+
+ mutex_unlock(&data->mode_lock);
+
+ *val = regval * PMIC5000_POWER_UNIT;
+ return 0;
+
+error:
+ mutex_unlock(&data->mode_lock);
+ return err;
+}
+
+static int pmic5000_read_volt_thresholds(struct regmap *regmap, u32 attr,
+ int channel, long *val)
+{
+ int err;
+ u32 set_reg, thresh_reg, range_bit;
+ u32 set_regval, thresh_regval, range_regval;
+ u32 volt_set;
+ int base_volts[2];
+
+ switch (channel) {
+ case 0:
+ set_reg = PMIC5000_REG_SWA_VOLT_SET;
+ thresh_reg = PMIC5000_REG_SWA_THRESH;
+ range_bit = PMIC5000_SWA_RANGE;
+ base_volts[0] = 800;
+ base_volts[1] = 600;
+ break;
+ case 1:
+ set_reg = PMIC5000_REG_SWB_VOLT_SET;
+ thresh_reg = PMIC5000_REG_SWB_THRESH;
+ range_bit = PMIC5000_SWB_RANGE;
+ base_volts[0] = 800;
+ base_volts[1] = 600;
+ break;
+ case 2:
+ set_reg = PMIC5000_REG_SWC_VOLT_SET;
+ thresh_reg = PMIC5000_REG_SWC_THRESH;
+ range_bit = PMIC5000_SWC_RANGE;
+ base_volts[0] = 800;
+ base_volts[1] = 600;
+ break;
+ case 3:
+ set_reg = PMIC5000_REG_SWD_VOLT_SET;
+ thresh_reg = PMIC5000_REG_SWD_THRESH;
+ range_bit = PMIC5000_SWD_RANGE;
+ base_volts[0] = 1500;
+ base_volts[1] = 2200;
+ break;
+ case 5:
+ case 6: {
+ err = regmap_read(regmap, PMIC5000_REG_THRES_AND_SEL,
+ &thresh_regval);
+ if (err)
+ return err;
+ if (channel == 5) {
+ if (thresh_regval & PMIC5000_VIN_BULK_THRESH)
+ *val = 14500;
+ else
+ *val = 16000;
+ return 0;
+ } else {
+ if (thresh_regval & PMIC5000_VIN_MGMT_THRESH)
+ *val = 3800;
+ else
+ *val = 3700;
+ return 0;
+ }
+ }
+
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ err = regmap_read(regmap, set_reg, &set_regval);
+ if (err)
+ return err;
+ err = regmap_read(regmap, thresh_reg, &thresh_regval);
+ if (err)
+ return err;
+ err = regmap_read(regmap, PMIC5000_REG_SW_VOLT_RANGE, &range_regval);
+ if (err)
+ return err;
+
+ volt_set = range_regval & range_bit ? base_volts[1] : base_volts[0];
+ volt_set += (set_regval >> 1) * 5;
+
+ switch (attr) {
+ case hwmon_in_min:
+ /* 10%, 12.5%, Reserved, Reserved */
+ const int min_permilles[4] = { 100, 125, PERMILLE, PERMILLE };
+ *val = volt_set - (min_permilles[(thresh_regval >> 2) & 0x03] *
+ volt_set / PERMILLE);
+ return 0;
+ case hwmon_in_max:
+ /* 7.5%, 10%, 12.5%, Reserved */
+ const int max_permilles[4] = { 75, 100, 125, PERMILLE };
+ *val = volt_set + (max_permilles[(thresh_regval >> 4) & 0x03] *
+ volt_set / PERMILLE);
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int pmic5000_read_adc_alarms(struct regmap *regmap, u32 attr,
+ int channel, long *val)
+{
+ int err;
+ u32 regval;
+
+ if (channel >= 0 && channel <= 3) {
+ switch (attr) {
+ case hwmon_in_min_alarm: {
+ err = regmap_read(regmap, PMIC5000_REG_UNDER_VOLTAGE,
+ ®val);
+ if (err)
+ return err;
+ *val = regval >> (3 - channel) & 0x01;
+ return 0;
+ }
+ case hwmon_in_max_alarm: {
+ err = regmap_read(regmap, PMIC5000_REG_OVER_VOLTAGE,
+ ®val);
+ if (err)
+ return err;
+ *val = regval >> (7 - channel) & 0x01;
+ return 0;
+ }
+ }
+ } else if (channel == 5 || channel == 6) {
+ switch (attr) {
+ case hwmon_in_max_alarm: {
+ err = regmap_read(regmap, PMIC5000_REG_OVER_VOLT_IN,
+ ®val);
+ if (err)
+ return err;
+ *val = regval >> (channel - 4) & 0x01;
+ return 0;
+ }
+ }
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static int pmic5000_read_adc(struct pmic5000_data *data, u32 attr, int channel,
+ long *val)
+{
+ struct regmap *regmap = data->regmap;
+ int err, mult;
+ u32 regval;
+
+ switch (attr) {
+ case hwmon_in_enable:
+ err = pmic5000_check_regulator_enabled(regmap, channel);
+ if (err < 0)
+ return err;
+ *val = err;
+ return 0;
+ case hwmon_in_input:
+ break;
+ case hwmon_in_min:
+ case hwmon_in_max:
+ return pmic5000_read_volt_thresholds(regmap, attr, channel,
+ val);
+ case hwmon_in_min_alarm:
+ case hwmon_in_max_alarm:
+ return pmic5000_read_adc_alarms(regmap, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ /* Channel 4 is reserved */
+ if (channel < 0 || channel > 9 || channel == 4)
+ return -EOPNOTSUPP;
+
+ switch (channel) {
+ case 5:
+ mult = PMIC5000_VINBULK_UNIT;
+ break;
+ case 7:
+ mult = PMIC5000_VBIAS_UNIT;
+ break;
+ default:
+ mult = PMIC5000_VOLT_UNIT;
+ break;
+ }
+
+ mutex_lock(&data->adc_lock);
+
+ err = regmap_update_bits(regmap, PMIC5000_REG_ADC_CONFIG,
+ PMIC5000_ADC_SELECT_MASK, channel << 3);
+ if (err)
+ goto error;
+
+ /*
+ * The host shall wait minimum of 9 ms delay after the input selection
+ * for ADC readout and the actual readout
+ *
+ * msleep may sleep for up to 20ms, which is fine.
+ */
+ msleep(9);
+
+ err = regmap_read(regmap, PMIC5000_REG_ADC_VOLTAGE, ®val);
+ if (err)
+ goto error;
+
+ mutex_unlock(&data->adc_lock);
+
+ *val = regval * mult;
+ return 0;
+
+error:
+ mutex_unlock(&data->adc_lock);
+ return err;
+}
+
+static int pmic5000_read_interval(struct regmap *regmap, u32 attr, long *val)
+{
+ unsigned int regval;
+ int err;
+
+ if (attr != hwmon_chip_update_interval)
+ return -EOPNOTSUPP;
+
+ err = regmap_read(regmap, PMIC5000_REG_ADC_CONFIG, ®val);
+ if (err < 0)
+ return err;
+ *val = 1 << (regval & 0x03);
+ return 0;
+}
+
+static int pmic5000_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct pmic5000_data *data = dev_get_drvdata(dev);
+ struct regmap *regmap = data->regmap;
+
+ switch (type) {
+ case hwmon_chip:
+ return pmic5000_read_interval(regmap, attr, val);
+ case hwmon_temp:
+ return pmic5000_read_temp(regmap, attr, channel, val);
+ case hwmon_in:
+ return pmic5000_read_adc(data, attr, channel, val);
+ case hwmon_curr:
+ return pmic5000_read_curr(data, attr, channel, val);
+ case hwmon_power:
+ return pmic5000_read_power(data, attr, channel, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int pmic5000_read_string(struct device *dev,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel, const char **str)
+{
+ if (type == hwmon_curr && attr == hwmon_curr_label) {
+ if (channel < 0 || channel > 3)
+ return -EOPNOTSUPP;
+ *str = pmic5000_power_labels[channel];
+ } else if (type == hwmon_power && attr == hwmon_power_label) {
+ if (channel < 0 || channel > 3)
+ return -EOPNOTSUPP;
+ *str = pmic5000_power_labels[channel];
+ } else if (type == hwmon_in && attr == hwmon_in_label) {
+ if (channel < 0 || channel > 9 || channel == 4)
+ return -EOPNOTSUPP;
+ *str = pmic5000_voltage_labels[channel];
+ } else {
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int pmic5000_write_interval(struct pmic5000_data *data, long val)
+{
+ struct regmap *regmap = data->regmap;
+ u32 regval;
+ int err;
+
+ switch (val) {
+ case 1:
+ regval = 0;
+ break;
+ case 2:
+ regval = 1;
+ break;
+ case 4:
+ regval = 2;
+ break;
+ case 8:
+ regval = 3;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mutex_lock(&data->adc_lock);
+ err = regmap_update_bits(regmap, PMIC5000_REG_ADC_CONFIG, 0x03, regval);
+ mutex_unlock(&data->adc_lock);
+ return err;
+}
+
+static int pmic5000_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ struct pmic5000_data *data = dev_get_drvdata(dev);
+
+ switch (type) {
+ case hwmon_chip:
+ return pmic5000_write_interval(data, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static umode_t pmic5000_is_visible(const void *data,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel)
+{
+ int ret;
+ struct pmic5000_data *pmic_data = (struct pmic5000_data *)data;
+ struct regmap *regmap = pmic_data->regmap;
+
+ switch (type) {
+ case hwmon_chip:
+ if (attr == hwmon_chip_update_interval)
+ return 0644;
+ break;
+ case hwmon_temp:
+ return 0444;
+ case hwmon_in:
+ if (channel == 4)
+ return 0;
+ if (channel >= 0 && channel <= 3 && (attr != hwmon_in_enable)) {
+ ret = pmic5000_check_regulator_enabled(regmap, channel);
+ if (!ret || ret < 0)
+ return 0;
+ }
+ return 0444;
+ case hwmon_power:
+ if (channel >= 0 && channel <= 3) {
+ ret = pmic5000_check_regulator_enabled(regmap, channel);
+ if (!ret || ret < 0)
+ return 0;
+ }
+ return 0444;
+ case hwmon_curr:
+ if (channel >= 0 && channel <= 3) {
+ ret = pmic5000_check_regulator_enabled(regmap, channel);
+ if (!ret || ret < 0)
+ return 0;
+ }
+ return 0444;
+ default:
+ break;
+ }
+ return 0444;
+}
+
+/*
+ * Bank and vendor id are 8-bit fields with seven data bits and odd parity.
+ * Vendor IDs 0 and 0x7f are invalid.
+ * See Jedec standard JEP106BJ for details and a list of assigned vendor IDs.
+ */
+static bool pmic5000_vendor_valid(u8 bank, u8 id)
+{
+ if (parity8(bank) == 0 || parity8(id) == 0)
+ return false;
+
+ id &= 0x7f;
+ return id && id != 0x7f;
+}
+
+static const struct hwmon_channel_info *pmic5000_info[] = {
+ HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL),
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_ALARM),
+ HWMON_CHANNEL_INFO(
+ in,
+ HWMON_I_ENABLE | HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX |
+ HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM | HWMON_I_LABEL,
+ HWMON_I_ENABLE | HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX |
+ HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM | HWMON_I_LABEL,
+ HWMON_I_ENABLE | HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX |
+ HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM | HWMON_I_LABEL,
+ HWMON_I_ENABLE | HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX |
+ HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM | HWMON_I_LABEL,
+ HWMON_I_INPUT,
+ HWMON_I_INPUT | HWMON_I_MAX | HWMON_I_MAX_ALARM | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_MAX | HWMON_I_MAX_ALARM | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL),
+ HWMON_CHANNEL_INFO(
+ curr,
+ HWMON_C_INPUT | HWMON_C_MAX | HWMON_C_MAX_ALARM | HWMON_C_LABEL,
+ HWMON_C_INPUT | HWMON_C_MAX | HWMON_C_MAX_ALARM | HWMON_C_LABEL,
+ HWMON_C_INPUT | HWMON_C_MAX | HWMON_C_MAX_ALARM | HWMON_C_LABEL,
+ HWMON_C_INPUT | HWMON_C_MAX | HWMON_C_MAX_ALARM |
+ HWMON_C_LABEL),
+ HWMON_CHANNEL_INFO(power, HWMON_P_INPUT | HWMON_P_LABEL,
+ HWMON_P_INPUT | HWMON_P_LABEL,
+ HWMON_P_INPUT | HWMON_P_LABEL,
+ HWMON_P_INPUT | HWMON_P_LABEL),
+ NULL
+};
+
+static const struct hwmon_ops pmic5000_hwmon_ops = {
+ .is_visible = pmic5000_is_visible,
+ .read = pmic5000_read,
+ .read_string = pmic5000_read_string,
+ .write = pmic5000_write,
+};
+
+static const struct hwmon_chip_info pmic5000_chip_info = {
+ .ops = &pmic5000_hwmon_ops,
+ .info = pmic5000_info,
+};
+
+/* regmap */
+
+static bool pmic5000_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case PMIC5000_REG_OUTPUT_SELECT:
+ case PMIC5000_REG_THRES_AND_SEL:
+ case PMIC5000_REG_ADC_CONFIG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool pmic5000_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case PMIC5000_REG_OVER_VOLT_IN:
+ case PMIC5000_REG_OVER_CURRENT:
+ case PMIC5000_REG_OVER_VOLTAGE:
+ case PMIC5000_REG_UNDER_VOLTAGE:
+ case PMIC5000_REG_SWA_POWER:
+ case PMIC5000_REG_SWB_POWER:
+ case PMIC5000_REG_SWC_POWER:
+ case PMIC5000_REG_SWD_POWER:
+ case PMIC5000_REG_ADC_VOLTAGE:
+ case PMIC5000_REG_TEMPERATURE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config pmic5000_regmap8_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0x3f,
+ .writeable_reg = pmic5000_writeable_reg,
+ .volatile_reg = pmic5000_volatile_reg,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+static int pmic5000_suspend(struct device *dev)
+{
+ struct pmic5000_data *data = dev_get_drvdata(dev);
+ struct regmap *regmap = data->regmap;
+ u32 regval;
+ int err;
+
+ /*
+ * Make sure the configuration register in the regmap cache is current
+ * before bypassing it.
+ */
+ err = regmap_read(regmap, PMIC5000_REG_ADC_CONFIG, ®val);
+ if (err < 0)
+ return err;
+
+ regcache_cache_bypass(regmap, true);
+ regmap_update_bits(regmap, PMIC5000_REG_ADC_CONFIG, PMIC5000_ADC_ENABLE,
+ 0);
+ regcache_cache_bypass(regmap, false);
+
+ regcache_cache_only(regmap, true);
+ regcache_mark_dirty(regmap);
+
+ return 0;
+}
+
+static int pmic5000_resume(struct device *dev)
+{
+ struct pmic5000_data *data = dev_get_drvdata(dev);
+ struct regmap *regmap = data->regmap;
+
+ regcache_cache_only(regmap, false);
+ return regcache_sync(regmap);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(pmic5000_pm_ops, pmic5000_suspend,
+ pmic5000_resume);
+
+static int pmic5000_common_probe(struct device *dev, struct regmap *regmap)
+{
+ unsigned int revision, vendor, bank;
+ struct pmic5000_data *data;
+ struct device *hwmon_dev;
+ int err;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ err = regmap_read(regmap, PMIC5000_REG_REVISION, &revision);
+ if (err)
+ return err;
+
+ err = regmap_read(regmap, PMIC5000_REG_VENDOR, &bank);
+ if (err)
+ return err;
+ err = regmap_read(regmap, PMIC5000_REG_VENDOR + 1, &vendor);
+ if (err)
+ return err;
+ if (!pmic5000_vendor_valid(bank, vendor))
+ return -ENODEV;
+
+ data->regmap = regmap;
+ mutex_init(&data->mode_lock);
+ mutex_init(&data->adc_lock);
+ dev_set_drvdata(dev, data);
+
+ hwmon_dev = devm_hwmon_device_register_with_info(
+ dev, "pmic5000", data, &pmic5000_chip_info, NULL);
+ if (IS_ERR(hwmon_dev))
+ return PTR_ERR(hwmon_dev);
+
+ dev_info(dev, "DDR5 PMIC sensor: vendor 0x%02x:0x%02x revision %d.%d\n",
+ bank & 0x7f, vendor, ((revision >> 4) & 0x03) + 1,
+ ((revision >> 1) & 0x07) + 1);
+
+ /* Enable individual measurements and enable ADC */
+ err = regmap_update_bits(regmap, PMIC5000_REG_OUTPUT_SELECT,
+ PMIC5000_OUTPUT_SELECT,
+ PMIC5000_OUTPUT_SELECT);
+ if (err)
+ return err;
+ err = regmap_update_bits(regmap, PMIC5000_REG_ADC_CONFIG,
+ PMIC5000_ADC_ENABLE, PMIC5000_ADC_ENABLE);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+/* I2C */
+
+static int pmic5000_i2c_init(struct i2c_client *client)
+{
+ struct i2c_adapter *adapter = client->adapter;
+
+ /*
+ * Register accesses are 8-bit, so require byte-data transactions only.
+ * Requiring WORD_DATA here rejects otherwise valid adapters.
+ */
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+ return -ENODEV;
+
+ return 0;
+}
+
+static int pmic5000_i2c_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct regmap *regmap;
+ int err;
+
+ err = pmic5000_i2c_init(client);
+ if (err)
+ return dev_err_probe(dev, err, "I2C capability check failed\n");
+
+ regmap = devm_regmap_init_i2c(client, &pmic5000_regmap8_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap),
+ "regmap init failed\n");
+
+ return pmic5000_common_probe(dev, regmap);
+}
+
+static const struct i2c_device_id pmic5000_i2c_id[] = { { .name = "pmic5000" },
+ {} };
+MODULE_DEVICE_TABLE(i2c, pmic5000_i2c_id);
+
+static const struct of_device_id pmic5000_of_ids[] = {
+ {
+ .compatible = "jedec,pmic5000",
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, pmic5000_of_ids);
+
+static struct i2c_driver pmic5000_i2c_driver = {
+ .class = I2C_CLASS_HWMON,
+ .driver = {
+ .name = "pmic5000",
+ .of_match_table = pmic5000_of_ids,
+ .pm = pm_sleep_ptr(&pmic5000_pm_ops),
+ },
+ .probe = pmic5000_i2c_probe,
+ .id_table = pmic5000_i2c_id,
+};
+
+module_i2c_driver(pmic5000_i2c_driver);
+
+MODULE_AUTHOR("Stephen Horvath <linux@stevetech.au>");
+MODULE_DESCRIPTION("PMIC5000 driver");
+MODULE_LICENSE("GPL");
---
base-commit: 75f2c0b3690702c90863c2e138cb5520670845ea
change-id: 20260819-pmic5000-3840f8a9ade1
Best regards,
--
Stephen Horvath <linux@stevetech.au>
^ permalink raw reply related [flat|nested] 7+ messages in thread* Re: [PATCH RFC] hwmon: Add Support for PMIC5000 Power Management ICs
2026-08-19 13:37 [PATCH RFC] hwmon: Add Support for PMIC5000 Power Management ICs Stephen Horvath
@ 2026-08-19 13:51 ` sashiko-bot
2026-08-20 15:34 ` Guenter Roeck
2026-08-23 20:18 ` kernel test robot
2 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-08-19 13:51 UTC (permalink / raw)
To: Stephen Horvath; +Cc: linux-hwmon
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Medium] Race condition: sysfs registration before hardware initialization
- [Medium] Incorrect error code for out-of-range values
- [Medium] hwmon update_interval attribute violates ABI units (returns raw values instead of milliseconds)
- [Medium] Architectural layering violation: PMIC directly registered as hwmon I2C driver
- [Low] C11 syntax error due to declaration directly after a label
--
--- Patch [1]: [PATCH RFC] hwmon: Add Support for PMIC5000 Power Management ICs ---
commit 4dd6cacf5a9ff70a4083c91afc8539b2aae42832
Author: Stephen Horvath <linux@stevetech.au>
hwmon: Add Support for PMIC5000 Power Management ICs
Add support for sensors present on PMIC5000 (JEDEC JESD301) compliant
power management ICs. These chips are commonly found on DDR5 memory
modules.
> diff --git a/drivers/hwmon/pmic5000.c b/drivers/hwmon/pmic5000.c
> index 0000000000000..d3f3126dd9c80
> --- /dev/null
> +++ b/drivers/hwmon/pmic5000.c
[ ... ]
> +static int pmic5000_read_temp(struct regmap *regmap, u32 attr, int channel,
> + long *val)
> +{
> + int err;
> + u32 regval;
> +
> + if (channel != 0)
> + return -EOPNOTSUPP;
> +
> + switch (attr) {
> + case hwmon_temp_input: {
> + err = regmap_read(regmap, PMIC5000_REG_TEMPERATURE, ®val);
> + if (err)
> + return err;
> + regval >>= 5;
> + /* Below 85°C */
> + if (regval == 0)
> + return -EOPNOTSUPP;
[Severity: Medium]
Since returning -EOPNOTSUPP causes userspace tools to treat the attribute
as permanently unsupported by the chip, should this return -ENODATA instead
when the temperature is temporarily out of range?
> + /* 0b001 = 85°C, 0b010 = 95°C, etc. */
> + *val = (75 + regval * 10) * MILLIDEGREE_PER_DEGREE;
> + return 0;
> + }
[ ... ]
> +static int pmic5000_read_volt_thresholds(struct regmap *regmap, u32 attr,
> + int channel, long *val)
> +{
[ ... ]
> + volt_set = range_regval & range_bit ? base_volts[1] : base_volts[0];
> + volt_set += (set_regval >> 1) * 5;
> +
> + switch (attr) {
> + case hwmon_in_min:
> + /* 10%, 12.5%, Reserved, Reserved */
> + const int min_permilles[4] = { 100, 125, PERMILLE, PERMILLE };
[Severity: Low]
Can declaring a variable immediately following a case label cause a build
failure? The C11 standard expects a statement after a label, rather than
a declaration. Would wrapping the contents of this case block in braces
resolve this syntax error?
> + *val = volt_set - (min_permilles[(thresh_regval >> 2) & 0x03] *
> + volt_set / PERMILLE);
> + return 0;
> + case hwmon_in_max:
> + /* 7.5%, 10%, 12.5%, Reserved */
> + const int max_permilles[4] = { 75, 100, 125, PERMILLE };
> + *val = volt_set + (max_permilles[(thresh_regval >> 4) & 0x03] *
> + volt_set / PERMILLE);
> + return 0;
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
[ ... ]
> +static int pmic5000_read_interval(struct regmap *regmap, u32 attr, long *val)
> +{
> + unsigned int regval;
> + int err;
> +
> + if (attr != hwmon_chip_update_interval)
> + return -EOPNOTSUPP;
> +
> + err = regmap_read(regmap, PMIC5000_REG_ADC_CONFIG, ®val);
> + if (err < 0)
> + return err;
> + *val = 1 << (regval & 0x03);
> + return 0;
> +}
[Severity: Medium]
Is this exposing raw values like 1, 2, 4, or 8 instead of milliseconds?
The hwmon ABI specifies that update_interval should be reported and set
in milliseconds. Do these values need to be translated to match the ABI
requirements?
[ ... ]
> +static int pmic5000_common_probe(struct device *dev, struct regmap *regmap)
> +{
> + unsigned int revision, vendor, bank;
> + struct pmic5000_data *data;
> + struct device *hwmon_dev;
> + int err;
[ ... ]
> + hwmon_dev = devm_hwmon_device_register_with_info(
> + dev, "pmic5000", data, &pmic5000_chip_info, NULL);
> + if (IS_ERR(hwmon_dev))
> + return PTR_ERR(hwmon_dev);
> +
> + dev_info(dev, "DDR5 PMIC sensor: vendor 0x%02x:0x%02x revision %d.%d\n",
> + bank & 0x7f, vendor, ((revision >> 4) & 0x03) + 1,
> + ((revision >> 1) & 0x07) + 1);
> +
> + /* Enable individual measurements and enable ADC */
> + err = regmap_update_bits(regmap, PMIC5000_REG_OUTPUT_SELECT,
> + PMIC5000_OUTPUT_SELECT,
> + PMIC5000_OUTPUT_SELECT);
> + if (err)
> + return err;
> + err = regmap_update_bits(regmap, PMIC5000_REG_ADC_CONFIG,
> + PMIC5000_ADC_ENABLE, PMIC5000_ADC_ENABLE);
[Severity: Medium]
Does this expose the sysfs files before the ADC is actually enabled? If
userspace polling tools access the sysfs attributes right after
devm_hwmon_device_register_with_info() completes, could it trigger reads on
uninitialized hardware? Should the hardware initialization happen before
registration?
[ ... ]
> +static struct i2c_driver pmic5000_i2c_driver = {
> + .class = I2C_CLASS_HWMON,
> + .driver = {
> + .name = "pmic5000",
> + .of_match_table = pmic5000_of_ids,
> + .pm = pm_sleep_ptr(&pmic5000_pm_ops),
> + },
> + .probe = pmic5000_i2c_probe,
> + .id_table = pmic5000_i2c_id,
> +};
> +
> +module_i2c_driver(pmic5000_i2c_driver);
[Severity: Medium]
Is this driver placed in the correct subsystem? For PMIC chips where hardware
monitoring isn't the primary function, the hwmon subsystem guidelines state
the core driver should reside in drivers/mfd or drivers/regulator, and
instantiate the hwmon functionality as an auxiliary device. Does binding
directly to the I2C device here prevent regulator drivers from attaching to
this PMIC later?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260819-pmic5000-v1-1-e5e0ddcf0863@stevetech.au?part=1
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [PATCH RFC] hwmon: Add Support for PMIC5000 Power Management ICs
2026-08-19 13:37 [PATCH RFC] hwmon: Add Support for PMIC5000 Power Management ICs Stephen Horvath
2026-08-19 13:51 ` sashiko-bot
@ 2026-08-20 15:34 ` Guenter Roeck
2026-08-21 3:27 ` Stephen Horvath
2026-08-23 20:18 ` kernel test robot
2 siblings, 1 reply; 7+ messages in thread
From: Guenter Roeck @ 2026-08-20 15:34 UTC (permalink / raw)
To: Stephen Horvath
Cc: Andi Shyti, linux-hwmon, linux-i2c, linux-kernel, Stephen Horvath
On Wed, Aug 19, 2026 at 11:37:45PM +1000, Stephen Horvath wrote:
> Add support for sensors present on PMIC5000 (JEDEC JESD301) compliant
> power management ICs. These chips are commonly found on DDR5 memory
> modules.
>
> Tested against PMIC5100 chips, but only features present in the
> PMIC5000 (JESD301-1) specification have been implemented.
>
> Signed-off-by: Stephen Horvath <linux@stevetech.au>
> ---
> Hi maintainers, I wrote this over the past couple days and I'm looking
> for feedback.
>
> Here's a few concerns I have:
> - I'm not too experienced with kernel development.
> - I don't think there's a good way to auto-detect PMIC5000 chips
> standalone, would it be reasonable to instantiate it from
> spd5118?
> - I'm unsure what to do with temperatures below 85°C, I'd rather not
> return 85 as that would make it seem hotter than it is, but
> returning -EOPNOTSUPP seems wrong.
> - Toggling between current and power seems like it's a bit of a hack,
> is that okay, or should I just pick one and drop the other?
> - Does this belong in HWMON, or should it be part of the regulator
> subsystem?
It depends on its functionality. Is this just for monitoring, or are the voltages
controllable ?
> ---
> .../devicetree/bindings/trivial-devices.yaml | 2 +
> Documentation/hwmon/index.rst | 1 +
> Documentation/hwmon/pmic5000.rst | 103 +++
> drivers/hwmon/Kconfig | 12 +
> drivers/hwmon/Makefile | 1 +
> drivers/hwmon/pmic5000.c | 896 +++++++++++++++++++++
> 6 files changed, 1015 insertions(+)
>
>
> ---
> base-commit: 75f2c0b3690702c90863c2e138cb5520670845ea
> change-id: 20260819-pmic5000-3840f8a9ade1
>
> Best regards,
> --
> Stephen Horvath <linux@stevetech.au>
>
> diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml
> index 2de8eb09cb7d..86e76be7d41b 100644
> --- a/Documentation/devicetree/bindings/trivial-devices.yaml
> +++ b/Documentation/devicetree/bindings/trivial-devices.yaml
> @@ -194,6 +194,8 @@ properties:
> - isil,isl29030
> # Intersil ISL76682 Ambient Light Sensor
> - isil,isl76682
> + # JEDEC JESD301 (PMIC5000) Power Management IC
> + - jedec,pmic5000
> # JEDEC JESD300 (SPD5118) Hub and Serial Presence Detect
> - jedec,spd5118
> # Kandou KB9002 PCIe 5.0 retimer
> diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
> index d979e6d6e9f2..4bb518d1927c 100644
> --- a/Documentation/hwmon/index.rst
> +++ b/Documentation/hwmon/index.rst
> @@ -226,6 +226,7 @@ Hardware Monitoring Kernel Drivers
> peci-cputemp
> peci-dimmtemp
> pmbus
> + pmic5000
> powerz
> powr1220
> prom21-xhci
> diff --git a/Documentation/hwmon/pmic5000.rst b/Documentation/hwmon/pmic5000.rst
> new file mode 100644
> index 000000000000..e7d864088ba5
> --- /dev/null
> +++ b/Documentation/hwmon/pmic5000.rst
> @@ -0,0 +1,103 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +
> +Kernel driver pmic5000
> +=====================
> +
> +Supported chips:
> +
> + * PMIC5000 (JEDEC JESD301) compliant power management chips
> +
> + JEDEC standard download:
> + https://www.jedec.org/standards-documents/docs/jesd301-1a03
> + (account required)
> +
> +
> + Prefix: 'pmic5000'
> +
> +Author:
> + Stephen Horvath <linux@stevetech.au>
> +
> +
> +Description
> +-----------
> +
> +This driver implements support for PMIC5000 (JEDEC JESD301) compliant power
> +management chips, which are used on many DDR5 memory modules.
> +
> +The driver does not auto-detect PMIC5000 compliant chips, and must be
> +instantiated manually or via device tree. The address of these chips are
> +usually between (inclusive) 0x48 and 0x4F.
> +
> +
> +Hardware monitoring sysfs entries
> +---------------------------------
> +
> +======================= ==================================
> +in0_enable Whether SWA is enabled (RO)
Does that enable the voltage or its monitoriong ? The enable attribute is
only to enable monitoring, not to enable the voltage. If this is a
controllable voltage, the driver should be a regulator driver, as Sashiko
suggested.
> +in0_input SWA Voltage (RO)
> +in0_min Minimum SWA Voltage (RO)
> +in0_max Maximum SWA Voltage (RO)
> +in0_min_alarm SWA Voltage low alarm
> +in0_max_alarm SWA Voltage high alarm
> +
> +in1_enable Whether SWB is enabled (RO)
> +in1_input SWB Voltage (RO)
> +in1_min Minimum SWB Voltage (RO)
> +in1_max Maximum SWB Voltage (RO)
> +in1_min_alarm SWB Voltage low alarm
> +in1_max_alarm SWB Voltage high alarm
> +
> +in2_enable Whether SWC is enabled (RO)
> +in2_input SWC Voltage (RO)
> +in2_min Minimum SWC Voltage (RO)
> +in2_max Maximum SWC Voltage (RO)
> +in2_min_alarm SWC Voltage low alarm
> +in2_max_alarm SWC Voltage high alarm
> +
> +in3_enable Whether SWD is enabled (RO)
> +in3_input SWD Voltage (RO)
> +in3_min Minimum SWD Voltage (RO)
> +in3_max Maximum SWD Voltage (RO)
> +in3_min_alarm SWD Voltage low alarm
> +in3_max_alarm SWD Voltage high alarm
> +
> +in5_input VIN_Bulk Voltage (RO)
> +in5_max Maximum VIN_Bulk Voltage (RO)
> +in5_max_alarm VIN_Bulk Voltage high alarm
> +
> +in6_input VIN_Mgmt Voltage (RO)
> +in6_max Maximum VIN_Mgmt Voltage (RO)
> +in6_max_alarm VIN_Mgmt Voltage high alarm
> +
> +in7_input VBias Voltage (RO)
> +in8_input VOUT_1.8V Voltage (RO)
> +in9_input VOUT_1.0V Voltage (RO)
> +
> +temp1_input Temperature (RO)
> +temp1_max Maximum temperature (RO)
> +temp1_max_alarm Temperature high alarm
> +
> +power1_input SWA Wattage (RO)
> +power2_input SWB Wattage (RO)
> +power3_input SWC Wattage (RO)
> +power4_input SWD Wattage (RO)
> +
> +curr1_input SWA Current (RO)
> +curr1_max Maximum SWA Current (RO)
> +curr1_max_alarm SWA Current high alarm
> +
> +curr2_input SWB Current (RO)
> +curr2_max Maximum SWB Current (RO)
> +curr2_max_alarm SWB Current high alarm
> +
> +curr3_input SWC Current (RO)
> +curr3_max Maximum SWC Current (RO)
> +curr3_max_alarm SWC Current high alarm
> +
> +curr4_input SWD Current (RO)
> +curr4_max Maximum SWD Current (RO)
> +curr4_max_alarm SWD Current high alarm
> +
> +update_interval Update interval in milliseconds (RW), Allowed values: 1, 2, 4, 8
> +
> +======================= ==================================
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 81a9a1d40eec..5ed5f9499c43 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -950,6 +950,18 @@ config SENSORS_JC42
> This driver can also be built as a module. If so, the module
> will be called jc42.
>
> +config SENSORS_PMIC5000
> + tristate "PMIC5000 Compliant Sensors"
> + depends on I2C
> + select REGMAP_I2C
> + help
> + If you say yes here you get support for PMIC5000 (JEDEC JESD301)
> + compliant sensors. Such sensors are found on DDR5 memory
> + modules.
> +
> + This driver can also be built as a module. If so, the module
> + will be called pmic5000.
> +
> config SENSORS_POLARFIRE_SOC_TVS
> tristate "PolarFire SoC (MPFS) temperature and voltage sensor"
> depends on POLARFIRE_SOC_SYSCONS || COMPILE_TEST
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 0cad7e21634c..ac519123796f 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -199,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_PMIC5000) += pmic5000.o
> obj-$(CONFIG_SENSORS_POLARFIRE_SOC_TVS) += tvs-mpfs.o
> obj-$(CONFIG_SENSORS_POWERZ) += powerz.o
> obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o
> diff --git a/drivers/hwmon/pmic5000.c b/drivers/hwmon/pmic5000.c
> new file mode 100644
> index 000000000000..d3f3126dd9c8
> --- /dev/null
> +++ b/drivers/hwmon/pmic5000.c
> @@ -0,0 +1,896 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Driver for Jedec PMIC5000 compliant sensors
> + *
> + * Copyright (c) 2026 Stephen Horvath
> + *
> + * Inspired by spd5118.c.
> + *
> + * PMIC5000 compliant sensors are typically used on DDR5 memory modules.
> + */
> +
> +#include <linux/bitops.h>
> +#include <linux/bits.h>
> +#include <linux/err.h>
> +#include <linux/hwmon.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/pm.h>
> +#include <linux/regmap.h>
> +#include <linux/units.h>
> +
> +/* PMIC5000 registers. */
> +// clang-format off
No way. Are you serious ?
> +#define PMIC5000_REG_OVER_VOLT_IN 0x08
> +#define PMIC5000_REG_OVER_CURRENT 0x09
> + #define PMIC5000_HIGH_TEMP BIT(7)
> +#define PMIC5000_REG_OVER_VOLTAGE 0x0A
> +#define PMIC5000_REG_UNDER_VOLTAGE 0x0B
> +#define PMIC5000_REG_SWA_POWER 0x0C
> +#define PMIC5000_REG_SWB_POWER 0x0D
> +#define PMIC5000_REG_SWC_POWER 0x0E
> +#define PMIC5000_REG_SWD_POWER 0x0F
> +#define PMIC5000_REG_OUTPUT_SELECT 0x1A
> + #define PMIC5000_OUTPUT_SELECT BIT(1)
> +#define PMIC5000_REG_THRES_AND_SEL 0x1B
> + #define PMIC5000_VIN_MGMT_THRESH BIT(5)
> + #define PMIC5000_CURR_OR_PWR BIT(6)
> + #define PMIC5000_VIN_BULK_THRESH BIT(7)
> +#define PMIC5000_REG_SWA_CURR_WARN 0x1C
> +#define PMIC5000_REG_SWB_CURR_WARN 0x1D
> +#define PMIC5000_REG_SWC_CURR_WARN 0x1E
> +#define PMIC5000_REG_SWD_CURR_WARN 0x1F
> +#define PMIC5000_REG_SWA_VOLT_SET 0x21
> +#define PMIC5000_REG_SWA_THRESH 0x22
> +#define PMIC5000_REG_SWB_VOLT_SET 0x23
> +#define PMIC5000_REG_SWB_THRESH 0x24
> +#define PMIC5000_REG_SWC_VOLT_SET 0x25
> +#define PMIC5000_REG_SWC_THRESH 0x26
> +#define PMIC5000_REG_SWD_VOLT_SET 0x27
> +#define PMIC5000_REG_SWD_THRESH 0x28
> +#define PMIC5000_REG_SW_VOLT_RANGE 0x2B
> + #define PMIC5000_SWD_RANGE BIT(0)
> + #define PMIC5000_SWC_RANGE BIT(3)
> + #define PMIC5000_SWB_RANGE BIT(4)
> + #define PMIC5000_SWA_RANGE BIT(5)
> +#define PMIC5000_REG_REGULATOR_CONTROL 0x2F
> + #define PMIC5000_SWD_CONTROL BIT(3)
> + #define PMIC5000_SWC_CONTROL BIT(4)
> + #define PMIC5000_SWB_CONTROL BIT(5)
> + #define PMIC5000_SWA_CONTROL BIT(6)
> +#define PMIC5000_REG_ADC_CONFIG 0x30
> + #define PMIC5000_ADC_SELECT_MASK GENMASK(6, 3)
> + #define PMIC5000_ADC_ENABLE BIT(7)
> +#define PMIC5000_REG_ADC_VOLTAGE 0x31
> +#define PMIC5000_REG_TEMPERATURE 0x33
> +#define PMIC5000_REG_REVISION 0x3B
> +#define PMIC5000_REG_VENDOR 0x3C
> +
> +
Please run checkpatch --strict on your patches and fix what it reports.
> +/* 125 mA multiplier */
> +#define PMIC5000_CURR_UNIT 125
> +/* 125 mW multiplier */
> +#define PMIC5000_POWER_UNIT (125 * MILLIWATT_PER_WATT)
> +/* mV multipliers */
> +#define PMIC5000_VOLT_UNIT 15
> +#define PMIC5000_VINBULK_UNIT 70
> +#define PMIC5000_VBIAS_UNIT 25
> +// clang-format on
> +
> +struct pmic5000_data {
> + struct regmap *regmap;
> + struct mutex mode_lock;
> + struct mutex adc_lock;
Explain why those are needed on top of the hwmon subsystem lock.
> +};
> +
> +static const char *const pmic5000_power_labels[] = { "SWA", "SWB", "SWC",
> + "SWD" };
> +
> +static const char *const pmic5000_voltage_labels[] = {
> + "SWA", "SWB", "SWC", "SWD", NULL,
> + "VIN_Bulk", "VIN_Mgmt", "VBias", "VOUT_1.8V", "VOUT_1.0V"
> +};
> +
> +/* hwmon */
> +
> +static int pmic5000_check_regulator_enabled(struct regmap *regmap, int channel)
Pretty clear indication that this should be a regulator driver.
> +{
> + u32 regval;
> + int err;
> +
> + err = regmap_read(regmap, PMIC5000_REG_REGULATOR_CONTROL, ®val);
> + if (err)
> + return err;
> + switch (channel) {
> + case 0:
> + return !!(regval & PMIC5000_SWA_CONTROL);
> + case 1:
> + return !!(regval & PMIC5000_SWB_CONTROL);
> + case 2:
> + return !!(regval & PMIC5000_SWC_CONTROL);
> + case 3:
> + return !!(regval & PMIC5000_SWD_CONTROL);
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static int pmic5000_read_temp(struct regmap *regmap, u32 attr, int channel,
> + long *val)
> +{
> + int err;
> + u32 regval;
> +
> + if (channel != 0)
> + return -EOPNOTSUPP;
Unnecessary check.
> +
> + switch (attr) {
> + case hwmon_temp_input: {
> + err = regmap_read(regmap, PMIC5000_REG_TEMPERATURE, ®val);
> + if (err)
> + return err;
> + regval >>= 5;
> + /* Below 85°C */
> + if (regval == 0)
> + return -EOPNOTSUPP;
> + /* 0b001 = 85°C, 0b010 = 95°C, etc. */
> + *val = (75 + regval * 10) * MILLIDEGREE_PER_DEGREE;
> + return 0;
> + }
> + case hwmon_temp_max: {
> + err = regmap_read(regmap, PMIC5000_REG_THRES_AND_SEL, ®val);
> + if (err)
> + return err;
> + regval &= 0x07;
> + /* Reserved */
> + if (regval == 0 || regval == 0x7)
> + return -EOPNOTSUPP;
> + *val = (75 + regval * 10) * MILLIDEGREE_PER_DEGREE;
> + return 0;
> + }
> + case hwmon_temp_max_alarm: {
> + err = regmap_read(regmap, PMIC5000_REG_OVER_CURRENT, ®val);
> + if (err)
> + return err;
> + *val = !!(regval & PMIC5000_HIGH_TEMP);
> + return 0;
> + }
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static int pmic5000_read_curr(struct pmic5000_data *data, u32 attr, int channel,
> + long *val)
> +{
> + struct regmap *regmap = data->regmap;
> + int reg, err;
> + int shift = 0;
> + u32 regval;
> +
> + if (attr == hwmon_curr_input) {
> + mutex_lock(&data->mode_lock);
I don't see why this is needed on top of the hwmon subsystem lock.
Same for other usage of this lock.
> + /* Select power measurements */
> + err = regmap_update_bits(regmap, PMIC5000_REG_THRES_AND_SEL,
> + PMIC5000_CURR_OR_PWR, 0);
> + if (err)
> + goto error;
> +
> + switch (channel) {
> + case 0:
> + reg = PMIC5000_REG_SWA_POWER;
> + break;
> + case 1:
> + reg = PMIC5000_REG_SWB_POWER;
> + break;
> + case 2:
> + reg = PMIC5000_REG_SWC_POWER;
> + break;
> + case 3:
> + reg = PMIC5000_REG_SWD_POWER;
> + break;
> + default:
> + err = -EOPNOTSUPP;
> + goto error;
> + }
> + } else if (attr == hwmon_curr_max) {
> + shift = 2;
> + switch (channel) {
> + case 0:
> + reg = PMIC5000_REG_SWA_CURR_WARN;
> + break;
> + case 1:
> + reg = PMIC5000_REG_SWB_CURR_WARN;
> + break;
> + case 2:
> + reg = PMIC5000_REG_SWC_CURR_WARN;
> + break;
> + case 3:
> + reg = PMIC5000_REG_SWD_CURR_WARN;
> + break;
> + default:
> + return -EOPNOTSUPP;
> + }
> + } else if (attr == hwmon_curr_max_alarm && channel >= 0 &&
> + channel <= 3) {
Unnecessary channel checks (at least if the is_visible function does its job
and the info data is correct).
> + err = regmap_read(regmap, PMIC5000_REG_OVER_CURRENT, ®val);
> + if (err)
> + return err;
> + *val = regval >> (3 - channel) & 0x01;
> + return 0;
> + } else {
> + return -EOPNOTSUPP;
> + }
> +
> + err = regmap_read(regmap, reg, ®val);
> + if (err)
> + goto error;
> +
> + if (attr == hwmon_curr_input)
> + mutex_unlock(&data->mode_lock);
> +
> + *val = regval * PMIC5000_CURR_UNIT >> shift;
> + return 0;
> +
> +error:
> + if (attr == hwmon_curr_input)
> + mutex_unlock(&data->mode_lock);
> + return err;
> +}
> +
> +static int pmic5000_read_power(struct pmic5000_data *data, u32 attr,
> + int channel, long *val)
> +{
> + struct regmap *regmap = data->regmap;
> + int reg, err;
> + u32 regval;
> +
> + if (attr != hwmon_power_input)
> + return -EOPNOTSUPP;
> +
> + mutex_lock(&data->mode_lock);
> + /* Select power measurements */
> + err = regmap_update_bits(regmap, PMIC5000_REG_THRES_AND_SEL,
> + PMIC5000_CURR_OR_PWR, PMIC5000_CURR_OR_PWR);
> + if (err)
> + goto error;
> +
> + switch (channel) {
> + case 0:
> + reg = PMIC5000_REG_SWA_POWER;
> + break;
> + case 1:
> + reg = PMIC5000_REG_SWB_POWER;
> + break;
> + case 2:
> + reg = PMIC5000_REG_SWC_POWER;
> + break;
> + case 3:
> + reg = PMIC5000_REG_SWD_POWER;
> + break;
> + default:
> + err = -EOPNOTSUPP;
> + goto error;
> + }
> +
> + err = regmap_read(regmap, reg, ®val);
> + if (err)
> + goto error;
> +
> + mutex_unlock(&data->mode_lock);
> +
> + *val = regval * PMIC5000_POWER_UNIT;
> + return 0;
> +
> +error:
> + mutex_unlock(&data->mode_lock);
> + return err;
> +}
> +
> +static int pmic5000_read_volt_thresholds(struct regmap *regmap, u32 attr,
> + int channel, long *val)
> +{
> + int err;
> + u32 set_reg, thresh_reg, range_bit;
> + u32 set_regval, thresh_regval, range_regval;
> + u32 volt_set;
> + int base_volts[2];
> +
> + switch (channel) {
> + case 0:
> + set_reg = PMIC5000_REG_SWA_VOLT_SET;
> + thresh_reg = PMIC5000_REG_SWA_THRESH;
> + range_bit = PMIC5000_SWA_RANGE;
> + base_volts[0] = 800;
> + base_volts[1] = 600;
> + break;
> + case 1:
> + set_reg = PMIC5000_REG_SWB_VOLT_SET;
> + thresh_reg = PMIC5000_REG_SWB_THRESH;
> + range_bit = PMIC5000_SWB_RANGE;
> + base_volts[0] = 800;
> + base_volts[1] = 600;
> + break;
> + case 2:
> + set_reg = PMIC5000_REG_SWC_VOLT_SET;
> + thresh_reg = PMIC5000_REG_SWC_THRESH;
> + range_bit = PMIC5000_SWC_RANGE;
> + base_volts[0] = 800;
> + base_volts[1] = 600;
> + break;
> + case 3:
> + set_reg = PMIC5000_REG_SWD_VOLT_SET;
> + thresh_reg = PMIC5000_REG_SWD_THRESH;
> + range_bit = PMIC5000_SWD_RANGE;
> + base_volts[0] = 1500;
> + base_volts[1] = 2200;
> + break;
> + case 5:
> + case 6: {
Why "{" ?
> + err = regmap_read(regmap, PMIC5000_REG_THRES_AND_SEL,
> + &thresh_regval);
> + if (err)
> + return err;
> + if (channel == 5) {
> + if (thresh_regval & PMIC5000_VIN_BULK_THRESH)
> + *val = 14500;
> + else
> + *val = 16000;
> + return 0;
> + } else {
> + if (thresh_regval & PMIC5000_VIN_MGMT_THRESH)
> + *val = 3800;
> + else
> + *val = 3700;
> + return 0;
> + }
> + }
> +
> + default:
> + return -EOPNOTSUPP;
> + }
> +
> + err = regmap_read(regmap, set_reg, &set_regval);
> + if (err)
> + return err;
> + err = regmap_read(regmap, thresh_reg, &thresh_regval);
> + if (err)
> + return err;
> + err = regmap_read(regmap, PMIC5000_REG_SW_VOLT_RANGE, &range_regval);
> + if (err)
> + return err;
> +
> + volt_set = range_regval & range_bit ? base_volts[1] : base_volts[0];
> + volt_set += (set_regval >> 1) * 5;
> +
> + switch (attr) {
> + case hwmon_in_min:
> + /* 10%, 12.5%, Reserved, Reserved */
> + const int min_permilles[4] = { 100, 125, PERMILLE, PERMILLE };
> + *val = volt_set - (min_permilles[(thresh_regval >> 2) & 0x03] *
> + volt_set / PERMILLE);
> + return 0;
> + case hwmon_in_max:
> + /* 7.5%, 10%, 12.5%, Reserved */
> + const int max_permilles[4] = { 75, 100, 125, PERMILLE };
> + *val = volt_set + (max_permilles[(thresh_regval >> 4) & 0x03] *
> + volt_set / PERMILLE);
> + return 0;
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static int pmic5000_read_adc_alarms(struct regmap *regmap, u32 attr,
> + int channel, long *val)
> +{
> + int err;
> + u32 regval;
> +
> + if (channel >= 0 && channel <= 3) {
channel is always >= 0.
> + switch (attr) {
> + case hwmon_in_min_alarm: {
> + err = regmap_read(regmap, PMIC5000_REG_UNDER_VOLTAGE,
> + ®val);
> + if (err)
> + return err;
> + *val = regval >> (3 - channel) & 0x01;
> + return 0;
> + }
> + case hwmon_in_max_alarm: {
> + err = regmap_read(regmap, PMIC5000_REG_OVER_VOLTAGE,
> + ®val);
> + if (err)
> + return err;
> + *val = regval >> (7 - channel) & 0x01;
> + return 0;
> + }
> + }
Alignment.
> + } else if (channel == 5 || channel == 6) {
> + switch (attr) {
> + case hwmon_in_max_alarm: {
> + err = regmap_read(regmap, PMIC5000_REG_OVER_VOLT_IN,
> + ®val);
> + if (err)
> + return err;
> + *val = regval >> (channel - 4) & 0x01;
> + return 0;
> + }
> + }
> + }
> +
> + return -EOPNOTSUPP;
> +}
> +
> +static int pmic5000_read_adc(struct pmic5000_data *data, u32 attr, int channel,
> + long *val)
> +{
> + struct regmap *regmap = data->regmap;
> + int err, mult;
> + u32 regval;
> +
> + switch (attr) {
> + case hwmon_in_enable:
> + err = pmic5000_check_regulator_enabled(regmap, channel);
> + if (err < 0)
> + return err;
> + *val = err;
> + return 0;
The enable attribute is supposed to enable or disable monitoring.
It is not supposed to report regulator status.
> + case hwmon_in_input:
> + break;
> + case hwmon_in_min:
> + case hwmon_in_max:
> + return pmic5000_read_volt_thresholds(regmap, attr, channel,
> + val);
> + case hwmon_in_min_alarm:
> + case hwmon_in_max_alarm:
> + return pmic5000_read_adc_alarms(regmap, attr, channel, val);
> + default:
> + return -EOPNOTSUPP;
> + }
> +
> + /* Channel 4 is reserved */
> + if (channel < 0 || channel > 9 || channel == 4)
> + return -EOPNOTSUPP;
Then it should not be enabled by the is_visible function.
Also, channel is never < 0 or > 9.
> +
> + switch (channel) {
+ case 5:
> + mult = PMIC5000_VINBULK_UNIT;
> + break;
> + case 7:
> + mult = PMIC5000_VBIAS_UNIT;
> + break;
> + default:
> + mult = PMIC5000_VOLT_UNIT;
> + break;
> + }
> +
> + mutex_lock(&data->adc_lock);
> +
> + err = regmap_update_bits(regmap, PMIC5000_REG_ADC_CONFIG,
> + PMIC5000_ADC_SELECT_MASK, channel << 3);
> + if (err)
> + goto error;
> +
> + /*
> + * The host shall wait minimum of 9 ms delay after the input selection
> + * for ADC readout and the actual readout
> + *
> + * msleep may sleep for up to 20ms, which is fine.
No, it isn't fine.
Also, why wait if the channel was not changed ?
> + */
> + msleep(9);
> +
> + err = regmap_read(regmap, PMIC5000_REG_ADC_VOLTAGE, ®val);
> + if (err)
> + goto error;
> +
> + mutex_unlock(&data->adc_lock);
> +
> + *val = regval * mult;
> + return 0;
> +
> +error:
> + mutex_unlock(&data->adc_lock);
> + return err;
> +}
> +
> +static int pmic5000_read_interval(struct regmap *regmap, u32 attr, long *val)
> +{
> + unsigned int regval;
> + int err;
> +
> + if (attr != hwmon_chip_update_interval)
> + return -EOPNOTSUPP;
> +
> + err = regmap_read(regmap, PMIC5000_REG_ADC_CONFIG, ®val);
> + if (err < 0)
> + return err;
> + *val = 1 << (regval & 0x03);
BIT()
> + return 0;
> +}
> +
> +static int pmic5000_read(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, long *val)
> +{
> + struct pmic5000_data *data = dev_get_drvdata(dev);
> + struct regmap *regmap = data->regmap;
> +
> + switch (type) {
> + case hwmon_chip:
> + return pmic5000_read_interval(regmap, attr, val);
> + case hwmon_temp:
> + return pmic5000_read_temp(regmap, attr, channel, val);
> + case hwmon_in:
> + return pmic5000_read_adc(data, attr, channel, val);
> + case hwmon_curr:
> + return pmic5000_read_curr(data, attr, channel, val);
> + case hwmon_power:
> + return pmic5000_read_power(data, attr, channel, val);
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static int pmic5000_read_string(struct device *dev,
> + enum hwmon_sensor_types type, u32 attr,
> + int channel, const char **str)
> +{
> + if (type == hwmon_curr && attr == hwmon_curr_label) {
> + if (channel < 0 || channel > 3)
> + return -EOPNOTSUPP;
Again, channel is never < 0, and unless is_visible and/or the configuration data is wrong
it should never be > 3.
> + *str = pmic5000_power_labels[channel];
> + } else if (type == hwmon_power && attr == hwmon_power_label) {
> + if (channel < 0 || channel > 3)
> + return -EOPNOTSUPP;
> + *str = pmic5000_power_labels[channel];
> + } else if (type == hwmon_in && attr == hwmon_in_label) {
> + if (channel < 0 || channel > 9 || channel == 4)
Many more unencessary channel checks.
> + return -EOPNOTSUPP;
> + *str = pmic5000_voltage_labels[channel];
> + } else {
> + return -EOPNOTSUPP;
> + }
> +
> + return 0;
> +}
> +
> +static int pmic5000_write_interval(struct pmic5000_data *data, long val)
> +{
> + struct regmap *regmap = data->regmap;
> + u32 regval;
> + int err;
> +
> + switch (val) {
> + case 1:
> + regval = 0;
> + break;
> + case 2:
> + regval = 1;
> + break;
> + case 4:
> + regval = 2;
> + break;
> + case 8:
> + regval = 3;
> + break;
> + default:
> + return -EINVAL;
> + }
find_closest() would be more appropriate here. We don't usually expect users to know valid
update intervals.
> +
> + mutex_lock(&data->adc_lock);
> + err = regmap_update_bits(regmap, PMIC5000_REG_ADC_CONFIG, 0x03, regval);
> + mutex_unlock(&data->adc_lock);
Another unnecessary lock.
> + return err;
> +}
> +
> +static int pmic5000_write(struct device *dev, enum hwmon_sensor_types type,
> + u32 attr, int channel, long val)
> +{
> + struct pmic5000_data *data = dev_get_drvdata(dev);
> +
> + switch (type) {
> + case hwmon_chip:
> + return pmic5000_write_interval(data, val);
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static umode_t pmic5000_is_visible(const void *data,
> + enum hwmon_sensor_types type, u32 attr,
> + int channel)
> +{
> + int ret;
> + struct pmic5000_data *pmic_data = (struct pmic5000_data *)data;
> + struct regmap *regmap = pmic_data->regmap;
> +
> + switch (type) {
> + case hwmon_chip:
> + if (attr == hwmon_chip_update_interval)
> + return 0644;
> + break;
> + case hwmon_temp:
> + return 0444;
> + case hwmon_in:
> + if (channel == 4)
> + return 0;
> + if (channel >= 0 && channel <= 3 && (attr != hwmon_in_enable)) {
> + ret = pmic5000_check_regulator_enabled(regmap, channel);
> + if (!ret || ret < 0)
> + return 0;
> + }
> + return 0444;
> + case hwmon_power:
> + if (channel >= 0 && channel <= 3) {
> + ret = pmic5000_check_regulator_enabled(regmap, channel);
> + if (!ret || ret < 0)
> + return 0;
> + }
> + return 0444;
> + case hwmon_curr:
> + if (channel >= 0 && channel <= 3) {
> + ret = pmic5000_check_regulator_enabled(regmap, channel);
> + if (!ret || ret < 0)
> + return 0;
> + }
> + return 0444;
> + default:
> + break;
> + }
> + return 0444;
> +}
> +
> +/*
> + * Bank and vendor id are 8-bit fields with seven data bits and odd parity.
> + * Vendor IDs 0 and 0x7f are invalid.
> + * See Jedec standard JEP106BJ for details and a list of assigned vendor IDs.
> + */
> +static bool pmic5000_vendor_valid(u8 bank, u8 id)
> +{
> + if (parity8(bank) == 0 || parity8(id) == 0)
> + return false;
> +
> + id &= 0x7f;
> + return id && id != 0x7f;
> +}
> +
> +static const struct hwmon_channel_info *pmic5000_info[] = {
> + HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL),
> + HWMON_CHANNEL_INFO(temp,
> + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_ALARM),
> + HWMON_CHANNEL_INFO(
> + in,
> + HWMON_I_ENABLE | HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX |
> + HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM | HWMON_I_LABEL,
> + HWMON_I_ENABLE | HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX |
> + HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM | HWMON_I_LABEL,
> + HWMON_I_ENABLE | HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX |
> + HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM | HWMON_I_LABEL,
> + HWMON_I_ENABLE | HWMON_I_INPUT | HWMON_I_MIN | HWMON_I_MAX |
> + HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM | HWMON_I_LABEL,
> + HWMON_I_INPUT,
> + HWMON_I_INPUT | HWMON_I_MAX | HWMON_I_MAX_ALARM | HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_MAX | HWMON_I_MAX_ALARM | HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL,
> + HWMON_I_INPUT | HWMON_I_LABEL),
> + HWMON_CHANNEL_INFO(
> + curr,
> + HWMON_C_INPUT | HWMON_C_MAX | HWMON_C_MAX_ALARM | HWMON_C_LABEL,
> + HWMON_C_INPUT | HWMON_C_MAX | HWMON_C_MAX_ALARM | HWMON_C_LABEL,
> + HWMON_C_INPUT | HWMON_C_MAX | HWMON_C_MAX_ALARM | HWMON_C_LABEL,
> + HWMON_C_INPUT | HWMON_C_MAX | HWMON_C_MAX_ALARM |
> + HWMON_C_LABEL),
> + HWMON_CHANNEL_INFO(power, HWMON_P_INPUT | HWMON_P_LABEL,
> + HWMON_P_INPUT | HWMON_P_LABEL,
> + HWMON_P_INPUT | HWMON_P_LABEL,
> + HWMON_P_INPUT | HWMON_P_LABEL),
> + NULL
> +};
> +
> +static const struct hwmon_ops pmic5000_hwmon_ops = {
> + .is_visible = pmic5000_is_visible,
> + .read = pmic5000_read,
> + .read_string = pmic5000_read_string,
> + .write = pmic5000_write,
> +};
> +
> +static const struct hwmon_chip_info pmic5000_chip_info = {
> + .ops = &pmic5000_hwmon_ops,
> + .info = pmic5000_info,
> +};
> +
> +/* regmap */
> +
> +static bool pmic5000_writeable_reg(struct device *dev, unsigned int reg)
> +{
> + switch (reg) {
> + case PMIC5000_REG_OUTPUT_SELECT:
> + case PMIC5000_REG_THRES_AND_SEL:
> + case PMIC5000_REG_ADC_CONFIG:
> + return true;
> + default:
> + return false;
> + }
> +}
> +
> +static bool pmic5000_volatile_reg(struct device *dev, unsigned int reg)
> +{
> + switch (reg) {
> + case PMIC5000_REG_OVER_VOLT_IN:
> + case PMIC5000_REG_OVER_CURRENT:
> + case PMIC5000_REG_OVER_VOLTAGE:
> + case PMIC5000_REG_UNDER_VOLTAGE:
> + case PMIC5000_REG_SWA_POWER:
> + case PMIC5000_REG_SWB_POWER:
> + case PMIC5000_REG_SWC_POWER:
> + case PMIC5000_REG_SWD_POWER:
> + case PMIC5000_REG_ADC_VOLTAGE:
> + case PMIC5000_REG_TEMPERATURE:
> + return true;
> + default:
> + return false;
> + }
> +}
> +
> +static const struct regmap_config pmic5000_regmap8_config = {
> + .reg_bits = 8,
> + .val_bits = 8,
> + .max_register = 0x3f,
> + .writeable_reg = pmic5000_writeable_reg,
> + .volatile_reg = pmic5000_volatile_reg,
> + .cache_type = REGCACHE_MAPLE,
> +};
> +
> +static int pmic5000_suspend(struct device *dev)
> +{
> + struct pmic5000_data *data = dev_get_drvdata(dev);
> + struct regmap *regmap = data->regmap;
> + u32 regval;
> + int err;
> +
> + /*
> + * Make sure the configuration register in the regmap cache is current
> + * before bypassing it.
> + */
> + err = regmap_read(regmap, PMIC5000_REG_ADC_CONFIG, ®val);
> + if (err < 0)
> + return err;
> +
> + regcache_cache_bypass(regmap, true);
> + regmap_update_bits(regmap, PMIC5000_REG_ADC_CONFIG, PMIC5000_ADC_ENABLE,
> + 0);
> + regcache_cache_bypass(regmap, false);
> +
> + regcache_cache_only(regmap, true);
> + regcache_mark_dirty(regmap);
> +
> + return 0;
> +}
> +
> +static int pmic5000_resume(struct device *dev)
> +{
> + struct pmic5000_data *data = dev_get_drvdata(dev);
> + struct regmap *regmap = data->regmap;
> +
> + regcache_cache_only(regmap, false);
> + return regcache_sync(regmap);
> +}
> +
> +static DEFINE_SIMPLE_DEV_PM_OPS(pmic5000_pm_ops, pmic5000_suspend,
> + pmic5000_resume);
> +
> +static int pmic5000_common_probe(struct device *dev, struct regmap *regmap)
Why pmic5000_common_probe() ? There is only one caller.
> +{
> + unsigned int revision, vendor, bank;
> + struct pmic5000_data *data;
> + struct device *hwmon_dev;
> + int err;
> +
> + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
> + if (!data)
> + return -ENOMEM;
> +
> + err = regmap_read(regmap, PMIC5000_REG_REVISION, &revision);
> + if (err)
> + return err;
> +
> + err = regmap_read(regmap, PMIC5000_REG_VENDOR, &bank);
> + if (err)
> + return err;
> + err = regmap_read(regmap, PMIC5000_REG_VENDOR + 1, &vendor);
> + if (err)
> + return err;
> + if (!pmic5000_vendor_valid(bank, vendor))
> + return -ENODEV;
> +
> + data->regmap = regmap;
> + mutex_init(&data->mode_lock);
> + mutex_init(&data->adc_lock);
> + dev_set_drvdata(dev, data);
> +
> + hwmon_dev = devm_hwmon_device_register_with_info(
> + dev, "pmic5000", data, &pmic5000_chip_info, NULL);
> + if (IS_ERR(hwmon_dev))
> + return PTR_ERR(hwmon_dev);
> +
> + dev_info(dev, "DDR5 PMIC sensor: vendor 0x%02x:0x%02x revision %d.%d\n",
> + bank & 0x7f, vendor, ((revision >> 4) & 0x03) + 1,
> + ((revision >> 1) & 0x07) + 1);
> +
> + /* Enable individual measurements and enable ADC */
> + err = regmap_update_bits(regmap, PMIC5000_REG_OUTPUT_SELECT,
> + PMIC5000_OUTPUT_SELECT,
> + PMIC5000_OUTPUT_SELECT);
> + if (err)
> + return err;
> + err = regmap_update_bits(regmap, PMIC5000_REG_ADC_CONFIG,
> + PMIC5000_ADC_ENABLE, PMIC5000_ADC_ENABLE);
> + if (err)
> + return err;
> +
> + return 0;
> +}
> +
> +/* I2C */
> +
> +static int pmic5000_i2c_init(struct i2c_client *client)
> +{
> + struct i2c_adapter *adapter = client->adapter;
> +
> + /*
> + * Register accesses are 8-bit, so require byte-data transactions only.
> + * Requiring WORD_DATA here rejects otherwise valid adapters.
> + */
> + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
> + return -ENODEV;
> +
> + return 0;
> +}
> +
> +static int pmic5000_i2c_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct regmap *regmap;
> + int err;
> +
> + err = pmic5000_i2c_init(client);
> + if (err)
> + return dev_err_probe(dev, err, "I2C capability check failed\n");
> +
> + regmap = devm_regmap_init_i2c(client, &pmic5000_regmap8_config);
> + if (IS_ERR(regmap))
> + return dev_err_probe(dev, PTR_ERR(regmap),
> + "regmap init failed\n");
> +
> + return pmic5000_common_probe(dev, regmap);
> +}
> +
> +static const struct i2c_device_id pmic5000_i2c_id[] = { { .name = "pmic5000" },
> + {} };
> +MODULE_DEVICE_TABLE(i2c, pmic5000_i2c_id);
> +
> +static const struct of_device_id pmic5000_of_ids[] = {
> + {
> + .compatible = "jedec,pmic5000",
jedec,pmic5000 is not documented.
> + },
> + {}
> +};
> +MODULE_DEVICE_TABLE(of, pmic5000_of_ids);
> +
> +static struct i2c_driver pmic5000_i2c_driver = {
> + .class = I2C_CLASS_HWMON,
> + .driver = {
> + .name = "pmic5000",
> + .of_match_table = pmic5000_of_ids,
> + .pm = pm_sleep_ptr(&pmic5000_pm_ops),
> + },
> + .probe = pmic5000_i2c_probe,
> + .id_table = pmic5000_i2c_id,
> +};
> +
> +module_i2c_driver(pmic5000_i2c_driver);
> +
> +MODULE_AUTHOR("Stephen Horvath <linux@stevetech.au>");
> +MODULE_DESCRIPTION("PMIC5000 driver");
> +MODULE_LICENSE("GPL");
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [PATCH RFC] hwmon: Add Support for PMIC5000 Power Management ICs
2026-08-20 15:34 ` Guenter Roeck
@ 2026-08-21 3:27 ` Stephen Horvath
2026-08-21 3:52 ` Guenter Roeck
0 siblings, 1 reply; 7+ messages in thread
From: Stephen Horvath @ 2026-08-21 3:27 UTC (permalink / raw)
To: Guenter Roeck
Cc: Andi Shyti, linux-hwmon, linux-i2c, linux-kernel, Stephen Horvath
Hi Guenter,
Thanks for your review!
On 21/8/26 01:34, Guenter Roeck wrote:
>> - Does this belong in HWMON, or should it be part of the regulator
>> subsystem?
>
> It depends on its functionality. Is this just for monitoring, or are the voltages
> controllable ?
This driver is just for monitoring. They could be controllable, but I
haven't implemented that as changing the voltage of active RAM modules
doesn't seem safe (without retraining and so on).
>> +in0_enable Whether SWA is enabled (RO)
>
> Does that enable the voltage or its monitoriong ? The enable attribute is
> only to enable monitoring, not to enable the voltage. If this is a
> controllable voltage, the driver should be a regulator driver, as Sashiko
> suggested.
It just queries whether that output is enabled. I'll remove it though as
the disabled channels are hidden by is_visible anyway.
>> +/* PMIC5000 registers. */
>> +// clang-format off
>
> No way. Are you serious ?
clang-format would break the alignment of the #defines; and there's a
few other drivers with "// clang-format" in them, so I assumed it would
be okay (though none are hwmon). I'll remove it next revision.
>> +
>> +
>
> Please run checkpatch --strict on your patches and fix what it reports.
Will do!
>> +struct pmic5000_data {
>> + struct regmap *regmap;
>> + struct mutex mode_lock;
>> + struct mutex adc_lock;
>
> Explain why those are needed on top of the hwmon subsystem lock.
I didn't know the subsystem lock existed, I'll remove them.
>> + if (channel != 0)
>> + return -EOPNOTSUPP;
>
> Unnecessary check.
Sure.
>
> Unnecessary channel checks (at least if the is_visible function does its job
> and the info data is correct).
>
Okay.
>> + case 6: {
>
> Why "{" ?
No reason, I'll remove it. I had previously declared something there.
>> + if (channel >= 0 && channel <= 3) {
>
> channel is always >= 0.
I wasn't too sure, thanks for that!
>> + /*
>> + * The host shall wait minimum of 9 ms delay after the input selection
>> + * for ADC readout and the actual readout
>> + *
>> + * msleep may sleep for up to 20ms, which is fine.
>
> No, it isn't fine.
Fair enough, I'll change it to fsleep.
> Also, why wait if the channel was not changed ?
Good point.
>> + *val = 1 << (regval & 0x03);
>
> BIT()
Thanks!
>> +static int pmic5000_write_interval(struct pmic5000_data *data, long val)
>> +{
>> + struct regmap *regmap = data->regmap;
>> + u32 regval;
>> + int err;
>> +
>> + switch (val) {
>> + case 1:
>> + regval = 0;
>> + break;
>> + case 2:
>> + regval = 1;
>> + break;
>> + case 4:
>> + regval = 2;
>> + break;
>> + case 8:
>> + regval = 3;
>> + break;
>> + default:
>> + return -EINVAL;
>> + }
>
> find_closest() would be more appropriate here. We don't usually expect users to know valid
> update intervals.
Thanks!
>> +static int pmic5000_common_probe(struct device *dev, struct regmap *regmap)
>
> Why pmic5000_common_probe() ? There is only one caller.
Good point, I'll move it.
>> +static const struct of_device_id pmic5000_of_ids[] = {
>> + {
>> + .compatible = "jedec,pmic5000",
>
> jedec,pmic5000 is not documented.
Is there some centralised database for device tree identifiers, or does
it just need to be added to trivial-devices.yaml or something?
Thanks again for your review!
Steve
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [PATCH RFC] hwmon: Add Support for PMIC5000 Power Management ICs
2026-08-21 3:27 ` Stephen Horvath
@ 2026-08-21 3:52 ` Guenter Roeck
0 siblings, 0 replies; 7+ messages in thread
From: Guenter Roeck @ 2026-08-21 3:52 UTC (permalink / raw)
To: Stephen Horvath
Cc: Andi Shyti, linux-hwmon, linux-i2c, linux-kernel, Stephen Horvath
On 8/20/26 20:27, Stephen Horvath wrote:
...
>>> +static const struct of_device_id pmic5000_of_ids[] = {
>>> + {
>>> + .compatible = "jedec,pmic5000",
>>
>> jedec,pmic5000 is not documented.
>
> Is there some centralised database for device tree identifiers, or does
> it just need to be added to trivial-devices.yaml or something?
You'll need a separate patch adding the property to trivial-devices.yaml.
Guenter
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH RFC] hwmon: Add Support for PMIC5000 Power Management ICs
2026-08-19 13:37 [PATCH RFC] hwmon: Add Support for PMIC5000 Power Management ICs Stephen Horvath
2026-08-19 13:51 ` sashiko-bot
2026-08-20 15:34 ` Guenter Roeck
@ 2026-08-23 20:18 ` kernel test robot
2 siblings, 0 replies; 7+ messages in thread
From: kernel test robot @ 2026-08-23 20:18 UTC (permalink / raw)
To: Stephen Horvath; +Cc: oe-kbuild-all
Hi Stephen,
[This is a private test report for your RFC patch.]
kernel test robot noticed the following build warnings:
[auto build test WARNING on 75f2c0b3690702c90863c2e138cb5520670845ea]
url: https://github.com/intel-lab-lkp/linux/commits/Stephen-Horvath/hwmon-Add-Support-for-PMIC5000-Power-Management-ICs/20260819-233745
base: 75f2c0b3690702c90863c2e138cb5520670845ea
patch link: https://lore.kernel.org/r/20260819-pmic5000-v1-1-e5e0ddcf0863%40stevetech.au
patch subject: [PATCH RFC] hwmon: Add Support for PMIC5000 Power Management ICs
compiler: clang version 22.1.8 (https://github.com/llvm/llvm-project ca7933e47d3a3451d81e72ac174dcb5aa28b59d1)
docutils: docutils (Docutils 0.21.2, Python 3.13.5, on linux)
reproduce: (https://download.01.org/0day-ci/archive/20260823/202608232257.ltZnDbDc-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202608232257.ltZnDbDc-lkp@intel.com/
All warnings (new ones prefixed by >>):
Runtime Survivability
===================== [docutils]
>> Documentation/hwmon/pmic5000.rst:4: WARNING: Title underline too short.
vim +4 Documentation/hwmon/pmic5000.rst
2
3 Kernel driver pmic5000
> 4 =====================
5
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH RFC] hwmon: Add Support for PMIC5000 Power Management ICs
@ 2026-08-23 11:45 kernel test robot
0 siblings, 0 replies; 7+ messages in thread
From: kernel test robot @ 2026-08-23 11:45 UTC (permalink / raw)
To: oe-kbuild; +Cc: lkp, Dan Carpenter
BCC: lkp@intel.com
CC: oe-kbuild-all@lists.linux.dev
In-Reply-To: <20260819-pmic5000-v1-1-e5e0ddcf0863@stevetech.au>
References: <20260819-pmic5000-v1-1-e5e0ddcf0863@stevetech.au>
TO: Stephen Horvath <linux@stevetech.au>
Hi Stephen,
[This is a private test report for your RFC patch.]
kernel test robot noticed the following build warnings:
[auto build test WARNING on 75f2c0b3690702c90863c2e138cb5520670845ea]
url: https://github.com/intel-lab-lkp/linux/commits/Stephen-Horvath/hwmon-Add-Support-for-PMIC5000-Power-Management-ICs/20260819-233745
base: 75f2c0b3690702c90863c2e138cb5520670845ea
patch link: https://lore.kernel.org/r/20260819-pmic5000-v1-1-e5e0ddcf0863%40stevetech.au
patch subject: [PATCH RFC] hwmon: Add Support for PMIC5000 Power Management ICs
:::::: branch date: 2 days ago
:::::: commit date: 2 days ago
config: riscv-randconfig-r071-20260823 (https://download.01.org/0day-ci/archive/20260823/202608231513.PfEj0vse-lkp@intel.com/config)
compiler: riscv64-linux-gcc (GCC) 12.5.0
smatch: v0.5.0-9187-g5189e3fb
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Reported-by: Dan Carpenter <error27@gmail.com>
| Closes: https://lore.kernel.org/r/202608231513.PfEj0vse-lkp@intel.com/
New smatch warnings:
drivers/hwmon/pmic5000.c:370 pmic5000_read_volt_thresholds() warn: statement has no effect 'const'
Old smatch warnings:
drivers/hwmon/pmic5000.c:376 pmic5000_read_volt_thresholds() warn: statement has no effect 'const'
vim +/const +370 drivers/hwmon/pmic5000.c
a0d30d2a53a818 Stephen Horvath 2026-08-19 290
a0d30d2a53a818 Stephen Horvath 2026-08-19 291 static int pmic5000_read_volt_thresholds(struct regmap *regmap, u32 attr,
a0d30d2a53a818 Stephen Horvath 2026-08-19 292 int channel, long *val)
a0d30d2a53a818 Stephen Horvath 2026-08-19 293 {
a0d30d2a53a818 Stephen Horvath 2026-08-19 294 int err;
a0d30d2a53a818 Stephen Horvath 2026-08-19 295 u32 set_reg, thresh_reg, range_bit;
a0d30d2a53a818 Stephen Horvath 2026-08-19 296 u32 set_regval, thresh_regval, range_regval;
a0d30d2a53a818 Stephen Horvath 2026-08-19 297 u32 volt_set;
a0d30d2a53a818 Stephen Horvath 2026-08-19 298 int base_volts[2];
a0d30d2a53a818 Stephen Horvath 2026-08-19 299
a0d30d2a53a818 Stephen Horvath 2026-08-19 300 switch (channel) {
a0d30d2a53a818 Stephen Horvath 2026-08-19 301 case 0:
a0d30d2a53a818 Stephen Horvath 2026-08-19 302 set_reg = PMIC5000_REG_SWA_VOLT_SET;
a0d30d2a53a818 Stephen Horvath 2026-08-19 303 thresh_reg = PMIC5000_REG_SWA_THRESH;
a0d30d2a53a818 Stephen Horvath 2026-08-19 304 range_bit = PMIC5000_SWA_RANGE;
a0d30d2a53a818 Stephen Horvath 2026-08-19 305 base_volts[0] = 800;
a0d30d2a53a818 Stephen Horvath 2026-08-19 306 base_volts[1] = 600;
a0d30d2a53a818 Stephen Horvath 2026-08-19 307 break;
a0d30d2a53a818 Stephen Horvath 2026-08-19 308 case 1:
a0d30d2a53a818 Stephen Horvath 2026-08-19 309 set_reg = PMIC5000_REG_SWB_VOLT_SET;
a0d30d2a53a818 Stephen Horvath 2026-08-19 310 thresh_reg = PMIC5000_REG_SWB_THRESH;
a0d30d2a53a818 Stephen Horvath 2026-08-19 311 range_bit = PMIC5000_SWB_RANGE;
a0d30d2a53a818 Stephen Horvath 2026-08-19 312 base_volts[0] = 800;
a0d30d2a53a818 Stephen Horvath 2026-08-19 313 base_volts[1] = 600;
a0d30d2a53a818 Stephen Horvath 2026-08-19 314 break;
a0d30d2a53a818 Stephen Horvath 2026-08-19 315 case 2:
a0d30d2a53a818 Stephen Horvath 2026-08-19 316 set_reg = PMIC5000_REG_SWC_VOLT_SET;
a0d30d2a53a818 Stephen Horvath 2026-08-19 317 thresh_reg = PMIC5000_REG_SWC_THRESH;
a0d30d2a53a818 Stephen Horvath 2026-08-19 318 range_bit = PMIC5000_SWC_RANGE;
a0d30d2a53a818 Stephen Horvath 2026-08-19 319 base_volts[0] = 800;
a0d30d2a53a818 Stephen Horvath 2026-08-19 320 base_volts[1] = 600;
a0d30d2a53a818 Stephen Horvath 2026-08-19 321 break;
a0d30d2a53a818 Stephen Horvath 2026-08-19 322 case 3:
a0d30d2a53a818 Stephen Horvath 2026-08-19 323 set_reg = PMIC5000_REG_SWD_VOLT_SET;
a0d30d2a53a818 Stephen Horvath 2026-08-19 324 thresh_reg = PMIC5000_REG_SWD_THRESH;
a0d30d2a53a818 Stephen Horvath 2026-08-19 325 range_bit = PMIC5000_SWD_RANGE;
a0d30d2a53a818 Stephen Horvath 2026-08-19 326 base_volts[0] = 1500;
a0d30d2a53a818 Stephen Horvath 2026-08-19 327 base_volts[1] = 2200;
a0d30d2a53a818 Stephen Horvath 2026-08-19 328 break;
a0d30d2a53a818 Stephen Horvath 2026-08-19 329 case 5:
a0d30d2a53a818 Stephen Horvath 2026-08-19 330 case 6: {
a0d30d2a53a818 Stephen Horvath 2026-08-19 331 err = regmap_read(regmap, PMIC5000_REG_THRES_AND_SEL,
a0d30d2a53a818 Stephen Horvath 2026-08-19 332 &thresh_regval);
a0d30d2a53a818 Stephen Horvath 2026-08-19 333 if (err)
a0d30d2a53a818 Stephen Horvath 2026-08-19 334 return err;
a0d30d2a53a818 Stephen Horvath 2026-08-19 335 if (channel == 5) {
a0d30d2a53a818 Stephen Horvath 2026-08-19 336 if (thresh_regval & PMIC5000_VIN_BULK_THRESH)
a0d30d2a53a818 Stephen Horvath 2026-08-19 337 *val = 14500;
a0d30d2a53a818 Stephen Horvath 2026-08-19 338 else
a0d30d2a53a818 Stephen Horvath 2026-08-19 339 *val = 16000;
a0d30d2a53a818 Stephen Horvath 2026-08-19 340 return 0;
a0d30d2a53a818 Stephen Horvath 2026-08-19 341 } else {
a0d30d2a53a818 Stephen Horvath 2026-08-19 342 if (thresh_regval & PMIC5000_VIN_MGMT_THRESH)
a0d30d2a53a818 Stephen Horvath 2026-08-19 343 *val = 3800;
a0d30d2a53a818 Stephen Horvath 2026-08-19 344 else
a0d30d2a53a818 Stephen Horvath 2026-08-19 345 *val = 3700;
a0d30d2a53a818 Stephen Horvath 2026-08-19 346 return 0;
a0d30d2a53a818 Stephen Horvath 2026-08-19 347 }
a0d30d2a53a818 Stephen Horvath 2026-08-19 348 }
a0d30d2a53a818 Stephen Horvath 2026-08-19 349
a0d30d2a53a818 Stephen Horvath 2026-08-19 350 default:
a0d30d2a53a818 Stephen Horvath 2026-08-19 351 return -EOPNOTSUPP;
a0d30d2a53a818 Stephen Horvath 2026-08-19 352 }
a0d30d2a53a818 Stephen Horvath 2026-08-19 353
a0d30d2a53a818 Stephen Horvath 2026-08-19 354 err = regmap_read(regmap, set_reg, &set_regval);
a0d30d2a53a818 Stephen Horvath 2026-08-19 355 if (err)
a0d30d2a53a818 Stephen Horvath 2026-08-19 356 return err;
a0d30d2a53a818 Stephen Horvath 2026-08-19 357 err = regmap_read(regmap, thresh_reg, &thresh_regval);
a0d30d2a53a818 Stephen Horvath 2026-08-19 358 if (err)
a0d30d2a53a818 Stephen Horvath 2026-08-19 359 return err;
a0d30d2a53a818 Stephen Horvath 2026-08-19 360 err = regmap_read(regmap, PMIC5000_REG_SW_VOLT_RANGE, &range_regval);
a0d30d2a53a818 Stephen Horvath 2026-08-19 361 if (err)
a0d30d2a53a818 Stephen Horvath 2026-08-19 362 return err;
a0d30d2a53a818 Stephen Horvath 2026-08-19 363
a0d30d2a53a818 Stephen Horvath 2026-08-19 364 volt_set = range_regval & range_bit ? base_volts[1] : base_volts[0];
a0d30d2a53a818 Stephen Horvath 2026-08-19 365 volt_set += (set_regval >> 1) * 5;
a0d30d2a53a818 Stephen Horvath 2026-08-19 366
a0d30d2a53a818 Stephen Horvath 2026-08-19 367 switch (attr) {
a0d30d2a53a818 Stephen Horvath 2026-08-19 368 case hwmon_in_min:
a0d30d2a53a818 Stephen Horvath 2026-08-19 369 /* 10%, 12.5%, Reserved, Reserved */
a0d30d2a53a818 Stephen Horvath 2026-08-19 @370 const int min_permilles[4] = { 100, 125, PERMILLE, PERMILLE };
a0d30d2a53a818 Stephen Horvath 2026-08-19 371 *val = volt_set - (min_permilles[(thresh_regval >> 2) & 0x03] *
a0d30d2a53a818 Stephen Horvath 2026-08-19 372 volt_set / PERMILLE);
a0d30d2a53a818 Stephen Horvath 2026-08-19 373 return 0;
a0d30d2a53a818 Stephen Horvath 2026-08-19 374 case hwmon_in_max:
a0d30d2a53a818 Stephen Horvath 2026-08-19 375 /* 7.5%, 10%, 12.5%, Reserved */
a0d30d2a53a818 Stephen Horvath 2026-08-19 376 const int max_permilles[4] = { 75, 100, 125, PERMILLE };
a0d30d2a53a818 Stephen Horvath 2026-08-19 377 *val = volt_set + (max_permilles[(thresh_regval >> 4) & 0x03] *
a0d30d2a53a818 Stephen Horvath 2026-08-19 378 volt_set / PERMILLE);
a0d30d2a53a818 Stephen Horvath 2026-08-19 379 return 0;
a0d30d2a53a818 Stephen Horvath 2026-08-19 380 default:
a0d30d2a53a818 Stephen Horvath 2026-08-19 381 return -EOPNOTSUPP;
a0d30d2a53a818 Stephen Horvath 2026-08-19 382 }
a0d30d2a53a818 Stephen Horvath 2026-08-19 383 }
a0d30d2a53a818 Stephen Horvath 2026-08-19 384
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-08-23 20:19 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-19 13:37 [PATCH RFC] hwmon: Add Support for PMIC5000 Power Management ICs Stephen Horvath
2026-08-19 13:51 ` sashiko-bot
2026-08-20 15:34 ` Guenter Roeck
2026-08-21 3:27 ` Stephen Horvath
2026-08-21 3:52 ` Guenter Roeck
2026-08-23 20:18 ` kernel test robot
-- strict thread matches above, loose matches on Subject: below --
2026-08-23 11:45 kernel test robot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.