Add hardware monitoring driver for JEDEC PMIC50x0 compliant I2C DDR5
PMICs.
The driver provides monitoring for voltage, current, power, and
temperature across multiple channels, along with comprehensive error
reporting.
Signed-off-by: Almog Ben Shaul <almogbs@amazon.com>
Tested-by: Almog Ben Shaul <almogbs@amazon.com>
---
Documentation/hwmon/index.rst | 1 +
Documentation/hwmon/pmic50x0.rst | 113 +++++
MAINTAINERS | 6 +
drivers/hwmon/Kconfig | 10 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/pmic50x0.c | 839 +++++++++++++++++++++++++++++++
6 files changed, 970 insertions(+)
create mode 100644 Documentation/hwmon/pmic50x0.rst
create mode 100644 drivers/hwmon/pmic50x0.c
diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 85d7a686883e..a08ef61c9cda 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -210,6 +210,7 @@ Hardware Monitoring Kernel Drivers
peci-cputemp
peci-dimmtemp
pmbus
+ pmic50x0
powerz
powr1220
pt5161l
diff --git a/Documentation/hwmon/pmic50x0.rst b/Documentation/hwmon/pmic50x0.rst
new file mode 100644
index 000000000000..2a0a6be2a3b1
--- /dev/null
+++ b/Documentation/hwmon/pmic50x0.rst
@@ -0,0 +1,113 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Kernel driver pmic50x0
+======================
+
+Supported chips:
+
+ * JEDEC PMIC50X0 (JESD301) compliant DDR5 PMICs
+
+ JEDEC standard download:
+ https://www.jedec.org/standards-documents/docs/jesd301-1a03
+
+ Prefix: 'pmic50x0'
+
+ Addresses scanned: ~
+
+Author:
+ Almog Ben Shaul <almogbs@amazon.com>
+
+
+Description
+-----------
+
+This driver implements support for hardware monitoring of JEDEC PMIC50X0
+compliant DDR5 Power Management ICs. These devices are I2C-based power
+management controllers designed specifically for DDR5 memory modules.
+
+The driver provides monitoring for:
+
+ * Voltage measurements across 4 switch nodes (A, B, C, D)
+ * Current measurements for each switch node
+ * Power consumption per switch node and total power
+ * PMIC die temperature
+ * Comprehensive error status reporting
+
+The PMIC50X0 specification defines a standard interface for DDR5 power
+management, including telemetry and error reporting capabilities.
+
+
+Usage Notes
+-----------
+
+Error monitoring is performed via a delayed work queue that polls error
+registers at a configurable interval (default 1000ms). The polling interval
+can be adjusted via the module parameter ``error_polling_ms``.
+
+
+Hardware monitoring sysfs entries
+---------------------------------
+
+======================= ========================================================
+temp1_input PMIC die temperature in millidegrees Celsius
+
+in0_input Switch Node A output voltage in millivolts
+in1_input Switch Node B output voltage in millivolts
+in2_input Switch Node C output voltage in millivolts
+in3_input Switch Node D output voltage in millivolts
+
+curr1_input Switch Node A output current in milliamperes
+curr2_input Switch Node B output current in milliamperes
+curr3_input Switch Node C output current in milliamperes
+curr4_input Switch Node D output current in milliamperes
+
+power1_input Switch Node A power consumption in microwatts
+power2_input Switch Node B power consumption in microwatts
+power3_input Switch Node C power consumption in microwatts
+power4_input Switch Node D power consumption in microwatts
+power5_input Total power consumption (sum of all nodes) in microwatts
+======================= ========================================================
+
+
+Error Status Counters
+---------------------
+
+The driver maintains counters for various error conditions. Each counter
+increments when the corresponding error condition is detected during polling.
+All error attributes are read-only and return the number of times the error
+has been detected since driver load or counter reset.
+
+====================================== =========================================
+err_global_log_vin_bulk_over_vol VIN_Bulk input over-voltage error count
+err_global_log_crit_temp Critical temperature error count
+err_global_log_buck_ov_or_uv Buck converter over/under-voltage count
+err_vin_bulk_input_over_vol_stat VIN_Bulk over-voltage status count
+err_vin_mgmt_input_over_vol_stat VIN_Mgmt over-voltage status count
+err_vin_bulk_input_pow_good_stat VIN_Bulk power good status count
+err_vin_mgmt_to_vin_bulk_stat VIN_Mgmt to VIN_Bulk switchover count
+err_swa_out_pow_good_stat Switch Node A power good status count
+err_swb_out_pow_good_stat Switch Node B power good status count
+err_swc_out_pow_good_stat Switch Node C power good status count
+err_swd_out_pow_good_stat Switch Node D power good status count
+err_swa_out_over_vol_stat Switch Node A over-voltage count
+err_swb_out_over_vol_stat Switch Node B over-voltage count
+err_swc_out_over_vol_stat Switch Node C over-voltage count
+err_swd_out_over_vol_stat Switch Node D over-voltage count
+err_swa_out_under_vol_lockout_stat Switch Node A under-voltage lockout count
+err_swb_out_under_vol_lockout_stat Switch Node B under-voltage lockout count
+err_swc_out_under_vol_lockout_stat Switch Node C under-voltage lockout count
+err_swd_out_under_vol_lockout_stat Switch Node D under-voltage lockout count
+err_swa_high_out_curr_consump_stat Switch Node A high current warning count
+err_swb_high_out_curr_consump_stat Switch Node B high current warning count
+err_swc_high_out_curr_consump_stat Switch Node C high current warning count
+err_swd_high_out_curr_consump_stat Switch Node D high current warning count
+err_swa_out_curr_limiter_warn_stat Switch Node A current limiter count
+err_swb_out_curr_limiter_warn_stat Switch Node B current limiter count
+err_swc_out_curr_limiter_warn_stat Switch Node C current limiter count
+err_swd_out_curr_limiter_warn_stat Switch Node D current limiter count
+err_crit_temp_shutdown_stat Critical temperature shutdown count
+err_pmic_high_temp_warn_stat High temperature warning count
+err_vout_1v_out_power_good_stat VOUT_1.0V LDO power good status count
+err_vout_1_8v_out_power_good_stat VOUT_1.8V LDO power good status count
+err_vbias_power_good_stat VBias power good status count
+====================================== =========================================
diff --git a/MAINTAINERS b/MAINTAINERS
index ebc2f1bc0ade..f179bccb992b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13515,6 +13515,12 @@ S: Maintained
F: arch/x86/include/asm/jailhouse_para.h
F: arch/x86/kernel/jailhouse.c
+JEDEC PMIC50X0 HARDWARE MONITOR DRIVER
+M: Almog Ben Shaul <almogbs@amazon.com>
+L: linux-hwmon@vger.kernel.org
+S: Maintained
+F: drivers/hwmon/pmic50x0.c
+
JFS FILESYSTEM
M: Dave Kleikamp <shaggy@kernel.org>
L: jfs-discussion@lists.sourceforge.net
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 157678b821fc..7100866ca444 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1905,6 +1905,16 @@ config SENSORS_PWM_FAN
This driver can also be built as a module. If so, the module
will be called pwm-fan.
+config SENSORS_PMIC50X0
+ tristate "JEDEC PMIC50x0 compliant DDR5 PMICs"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for JEDEC PMIC50x0 compliant
+ DDR5 PMIC sensor chips.
+ This driver can also be built as a module. If so, the module
+ will be called pmic50x0.
+
config SENSORS_QNAP_MCU_HWMON
tristate "QNAP MCU hardware monitoring"
depends on MFD_QNAP_MCU
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index eade8e3b1bde..f831aacc5791 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -197,6 +197,7 @@ obj-$(CONFIG_SENSORS_POWERZ) += powerz.o
obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o
obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o
obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o
+obj-$(CONFIG_SENSORS_PMIC50X0) += pmic50x0.o
obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON) += qnap-mcu-hwmon.o
obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o
obj-$(CONFIG_SENSORS_SA67MCU) += sa67mcu-hwmon.o
diff --git a/drivers/hwmon/pmic50x0.c b/drivers/hwmon/pmic50x0.c
new file mode 100644
index 000000000000..14a336d64a5b
--- /dev/null
+++ b/drivers/hwmon/pmic50x0.c
@@ -0,0 +1,839 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Hardware monitoring driver for JEDEC PMIC50x0 compliant DDR5 PMICs
+ *
+ * Specification: https://www.jedec.org/standards-documents/docs/jesd301-1a03
+ *
+ * Copyright (C) 2026 Almog Ben Shaul <almogbs@amazon.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/panic_notifier.h>
+#include <linux/regmap.h>
+
+/* PMIC50X0 Register Mapping */
+#define PMIC50X0_REG_CURR_POW 0x0C
+#define PMIC50X0_REG_NODE_A_SUM_SELECT 0x1A
+#define PMIC50X0_REG_CURR_POW_SELECT 0x1B
+#define PMIC50X0_REG_VOL_NODE_SELECT 0x30
+#define PMIC50X0_REG_VOL 0x31
+#define PMIC50X0_REG_TEMP 0x33
+#define PMIC50X0_MAX_REG_ADDR 0xFF
+
+/* PMIC50X0 Error Registers & Bits Mapping */
+#define PMIC50X0_ERR_REG0_ADDR 0x04
+#define PMIC50X0_ERR_REG1_ADDR 0x08
+#define PMIC50X0_ERR_REG2_ADDR 0x09
+#define PMIC50X0_ERR_REG3_ADDR 0x0A
+#define PMIC50X0_ERR_REG4_ADDR 0x0B
+#define PMIC50X0_ERR_REG5_ADDR 0x33
+
+#define PMIC50X0_ERR_REG0_SBIT 4
+#define PMIC50X0_ERR_REG1_SBIT 0
+#define PMIC50X0_ERR_REG2_SBIT 0
+#define PMIC50X0_ERR_REG3_SBIT 4
+#define PMIC50X0_ERR_REG4_SBIT 0
+#define PMIC50X0_ERR_REG5_SBIT 2
+
+#define PMIC50X0_ERR_REG0_EBIT 6
+#define PMIC50X0_ERR_REG1_EBIT 7
+#define PMIC50X0_ERR_REG2_EBIT 7
+#define PMIC50X0_ERR_REG3_EBIT 7
+#define PMIC50X0_ERR_REG4_EBIT 7
+#define PMIC50X0_ERR_REG5_EBIT 2
+
+#define ERR_REG_NUM_BITS(bit) (PMIC50X0_ERR_REG##bit##_EBIT - PMIC50X0_ERR_REG##bit##_SBIT + 1)
+#define PMIC50X0_ERR_REG0_NUM_BITS ERR_REG_NUM_BITS(0)
+#define PMIC50X0_ERR_REG1_NUM_BITS ERR_REG_NUM_BITS(1)
+#define PMIC50X0_ERR_REG2_NUM_BITS ERR_REG_NUM_BITS(2)
+#define PMIC50X0_ERR_REG3_NUM_BITS ERR_REG_NUM_BITS(3)
+#define PMIC50X0_ERR_REG4_NUM_BITS ERR_REG_NUM_BITS(4)
+#define PMIC50X0_ERR_REG5_NUM_BITS ERR_REG_NUM_BITS(5)
+
+/* PMIC50X0 Masks and offsets etc. */
+#define PMIC50X0_VOL_SELECT_MASK GENMASK(6, 3)
+#define PMIC50X0_VOL_MASK GENMASK(7, 0)
+#define PMIC50X0_TEMP_MASK GENMASK(7, 5)
+#define PMIC50X0_CURR_POW_A_MASK GENMASK(7, 0)
+#define PMIC50X0_CURR_POW_SUM_MASK GENMASK(7, 0)
+#define PMIC50X0_CURR_POW_BCD_MASK GENMASK(5, 0)
+#define PMIC50X0_CURR_POW_SELECT BIT(6)
+#define PMIC50X0_CURR_POW_A_SUM_SELECT BIT(1)
+#define PMIC50X0_VOL_OFFSET 3
+#define PMIC50X0_CURR_POW_SELECT_OFFSET 6
+#define PMIC50X0_CURR_POW_A_SUM_OFFSET 1
+#define PMIC50X0_SELECT_POWER_A 0
+#define PMIC50X0_SELECT_POWER_SUM BIT(PMIC50X0_CURR_POW_A_SUM_OFFSET)
+#define PMIC50X0_VOL_SELECT_DELAY 9
+
+/* PMIC50X0 consts etc. */
+#define PMIC50X0_DRIVER_NAME "pmic50x0"
+#define PMIC50X0_REG_BITS 8
+#define PMIC50X0_VAL_BITS 8
+#define PMIC50X0_VOL_FACTOR 15
+#define PMIC50X0_TEMP_FACTOR 10
+#define PMIC50X0_TEMP_BASE 75
+#define PMIC50X0_CURR_POWER_FACTOR 125
+#define PMIC50X0_ERR_POLL_INTERVAL_MSEC 1000
+#define PMIC50X0_VOLTAGE_CHANNELS 4
+#define PMIC50X0_PASS0_UNLOCK_VAL 0x73
+#define PMIC50X0_PASS1_UNLOCK_VAL 0x94
+#define PMIC50X0_PROT_UNLOCK_VAL 0x40
+#define PMIC50X0_PROT_LOCK_VAL 0x0
+
+#define MILLIDEGREE_PER_DEGREE 1000
+#define PMIC50X0_DEV_ATTR(name, index) \
+ static SENSOR_DEVICE_ATTR(name, 0444, pmic50x0_err_show, 0, (index))
+
+enum pmic50x0_curr_pow_node {
+ PMIC50X0_CURR_POW_NODE_A,
+ PMIC50X0_CURR_POW_NODE_B,
+ PMIC50X0_CURR_POW_NODE_C,
+ PMIC50X0_CURR_POW_NODE_D,
+ PMIC50X0_CURR_POW_SUM
+};
+
+enum pmic50x0_select_current_power {
+ PMIC50X0_SELECT_CURRENT,
+ PMIC50X0_SELECT_POWER,
+};
+
+enum pmic50x0_error_list {
+ PMIC50X0_ERR_GLOBAL_LOG_VIN_BULK_OVER_VOL,
+ PMIC50X0_ERR_GLOBAL_LOG_CRIT_TEMP,
+ PMIC50X0_ERR_GLOBAL_LOG_BUCK_OV_OR_UV,
+ PMIC50X0_ERR_VIN_BULK_INPUT_OVER_VOL_STAT,
+ PMIC50X0_ERR_VIN_MGMT_INPUT_OVER_VOL_STAT,
+ PMIC50X0_ERR_SWD_OUT_POW_GOOD_STAT,
+ PMIC50X0_ERR_SWC_OUT_POW_GOOD_STAT,
+ PMIC50X0_ERR_SWB_OUT_POW_GOOD_STAT,
+ PMIC50X0_ERR_SWA_OUT_POW_GOOD_STAT,
+ PMIC50X0_ERR_CRIT_TEMP_SHUTDOWN_STAT,
+ PMIC50X0_ERR_VIN_BULK_INPUT_POW_GOOD_STAT,
+ PMIC50X0_ERR_SWD_HIGH_OUT_CURR_CONSUMP_STAT,
+ PMIC50X0_ERR_SWC_HIGH_OUT_CURR_CONSUMP_STAT,
+ PMIC50X0_ERR_SWB_HIGH_OUT_CURR_CONSUMP_STAT,
+ PMIC50X0_ERR_SWA_HIGH_OUT_CURR_CONSUMP_STAT,
+ PMIC50X0_ERR_VIN_MGMT_TO_VIN_BULK_STAT,
+ PMIC50X0_ERR_VOUT_1_8V_OUT_POWER_GOOD_STAT,
+ PMIC50X0_ERR_VBIAS_POWER_GOOD_STAT,
+ PMIC50X0_ERR_PMIC_HIGH_TEMP_WARN_STAT,
+ PMIC50X0_ERR_SWD_OUT_OVER_VOL_STAT,
+ PMIC50X0_ERR_SWC_OUT_OVER_VOL_STAT,
+ PMIC50X0_ERR_SWB_OUT_OVER_VOL_STAT,
+ PMIC50X0_ERR_SWA_OUT_OVER_VOL_STAT,
+ PMIC50X0_ERR_SWD_OUT_UNDER_VOL_LOCKOUT_STAT,
+ PMIC50X0_ERR_SWC_OUT_UNDER_VOL_LOCKOUT_STAT,
+ PMIC50X0_ERR_SWB_OUT_UNDER_VOL_LOCKOUT_STAT,
+ PMIC50X0_ERR_SWA_OUT_UNDER_VOL_LOCKOUT_STAT,
+ PMIC50X0_ERR_SWD_OUT_CURR_LIMITER_WARN_STAT,
+ PMIC50X0_ERR_SWC_OUT_CURR_LIMITER_WARN_STAT,
+ PMIC50X0_ERR_SWB_OUT_CURR_LIMITER_WARN_STAT,
+ PMIC50X0_ERR_SWA_OUT_CURR_LIMITER_WARN_STAT,
+ PMIC50X0_ERR_VOUT_1V_OUT_POWER_GOOD_STAT,
+ PMIC50X0_ERR_MAX
+};
+
+struct pmic50x0_error {
+ u8 reg;
+ u8 bit;
+};
+
+static const struct pmic50x0_error pmic50x0_error_reg[PMIC50X0_ERR_MAX] = {
+ [PMIC50X0_ERR_GLOBAL_LOG_VIN_BULK_OVER_VOL] = {.reg = 0, .bit = 4},
+ [PMIC50X0_ERR_GLOBAL_LOG_CRIT_TEMP] = {.reg = 0, .bit = 5},
+ [PMIC50X0_ERR_GLOBAL_LOG_BUCK_OV_OR_UV] = {.reg = 0, .bit = 6},
+ [PMIC50X0_ERR_VIN_BULK_INPUT_OVER_VOL_STAT] = {.reg = 1, .bit = 0},
+ [PMIC50X0_ERR_VIN_MGMT_INPUT_OVER_VOL_STAT] = {.reg = 1, .bit = 1},
+ [PMIC50X0_ERR_SWD_OUT_POW_GOOD_STAT] = {.reg = 1, .bit = 2},
+ [PMIC50X0_ERR_SWC_OUT_POW_GOOD_STAT] = {.reg = 1, .bit = 3},
+ [PMIC50X0_ERR_SWB_OUT_POW_GOOD_STAT] = {.reg = 1, .bit = 4},
+ [PMIC50X0_ERR_SWA_OUT_POW_GOOD_STAT] = {.reg = 1, .bit = 5},
+ [PMIC50X0_ERR_CRIT_TEMP_SHUTDOWN_STAT] = {.reg = 1, .bit = 6},
+ [PMIC50X0_ERR_VIN_BULK_INPUT_POW_GOOD_STAT] = {.reg = 1, .bit = 7},
+ [PMIC50X0_ERR_SWD_HIGH_OUT_CURR_CONSUMP_STAT] = {.reg = 2, .bit = 0},
+ [PMIC50X0_ERR_SWC_HIGH_OUT_CURR_CONSUMP_STAT] = {.reg = 2, .bit = 1},
+ [PMIC50X0_ERR_SWB_HIGH_OUT_CURR_CONSUMP_STAT] = {.reg = 2, .bit = 2},
+ [PMIC50X0_ERR_SWA_HIGH_OUT_CURR_CONSUMP_STAT] = {.reg = 2, .bit = 3},
+ [PMIC50X0_ERR_VIN_MGMT_TO_VIN_BULK_STAT] = {.reg = 2, .bit = 4},
+ [PMIC50X0_ERR_VOUT_1_8V_OUT_POWER_GOOD_STAT] = {.reg = 2, .bit = 5},
+ [PMIC50X0_ERR_VBIAS_POWER_GOOD_STAT] = {.reg = 2, .bit = 6},
+ [PMIC50X0_ERR_PMIC_HIGH_TEMP_WARN_STAT] = {.reg = 2, .bit = 7},
+ [PMIC50X0_ERR_SWD_OUT_OVER_VOL_STAT] = {.reg = 3, .bit = 4},
+ [PMIC50X0_ERR_SWC_OUT_OVER_VOL_STAT] = {.reg = 3, .bit = 5},
+ [PMIC50X0_ERR_SWB_OUT_OVER_VOL_STAT] = {.reg = 3, .bit = 6},
+ [PMIC50X0_ERR_SWA_OUT_OVER_VOL_STAT] = {.reg = 3, .bit = 7},
+ [PMIC50X0_ERR_SWD_OUT_UNDER_VOL_LOCKOUT_STAT] = {.reg = 4, .bit = 0},
+ [PMIC50X0_ERR_SWC_OUT_UNDER_VOL_LOCKOUT_STAT] = {.reg = 4, .bit = 1},
+ [PMIC50X0_ERR_SWB_OUT_UNDER_VOL_LOCKOUT_STAT] = {.reg = 4, .bit = 2},
+ [PMIC50X0_ERR_SWA_OUT_UNDER_VOL_LOCKOUT_STAT] = {.reg = 4, .bit = 3},
+ [PMIC50X0_ERR_SWD_OUT_CURR_LIMITER_WARN_STAT] = {.reg = 4, .bit = 4},
+ [PMIC50X0_ERR_SWC_OUT_CURR_LIMITER_WARN_STAT] = {.reg = 4, .bit = 5},
+ [PMIC50X0_ERR_SWB_OUT_CURR_LIMITER_WARN_STAT] = {.reg = 4, .bit = 6},
+ [PMIC50X0_ERR_SWA_OUT_CURR_LIMITER_WARN_STAT] = {.reg = 4, .bit = 7},
+ [PMIC50X0_ERR_VOUT_1V_OUT_POWER_GOOD_STAT] = {.reg = 5, .bit = 2},
+};
+
+static const char *pmic50x0_reg0_err_msgs[PMIC50X0_ERR_REG0_NUM_BITS] = {
+ "Global Error Log History for Critical Temperature",
+ "Global Error Log History for VIN_Bulk Over",
+ "Global Error Log History for Buck Regulator Output O/U Voltage",
+};
+
+static const char *pmic50x0_reg1_err_msgs[PMIC50X0_ERR_REG1_NUM_BITS] = {
+ "VIN_Bulk Input Supply Over Voltage Status",
+ "VIN_Mgmt Input Supply Over Voltage Status",
+ "Switch Node D Output Power Good Status",
+ "Switch Node C Output Power Good Status",
+ "Switch Node B Output Power Good Status",
+ "Switch Node A Output Power Good Status",
+ "Critical Temperature Shutdown Status",
+ "VIN_Bulk Input Power Good Status"
+};
+
+static const char *pmic50x0_reg2_err_msgs[PMIC50X0_ERR_REG2_NUM_BITS] = {
+ "Switch Node D High Output Current Consumption Warning Status",
+ "Switch Node C High Output Current Consumption Warning Status",
+ "Switch Node B High Output Current Consumption Warning Status",
+ "Switch Node A High Output Current Consumption Warning Status",
+ "VIN_Mgmt to VIN_Bulk Input Supply Automatic Switchover Status",
+ "VOUT_1.8V LDO Output Power Good Status",
+ "VBias Power Good Status",
+ "PMIC High Temperature Warning Status"
+};
+
+static const char *pmic50x0_reg3_err_msgs[PMIC50X0_ERR_REG3_NUM_BITS] = {
+ "Switch Node D Output Over Voltage Status",
+ "Switch Node C Output Over Voltage Status",
+ "Switch Node B Output Over Voltage Status",
+ "Switch Node A Output Over Voltage Status"
+};
+
+static const char *pmic50x0_reg4_err_msgs[PMIC50X0_ERR_REG4_NUM_BITS] = {
+ "Switch Node D Output Under Voltage Lockout Status",
+ "Switch Node C Output Under Voltage Lockout Status",
+ "Switch Node B Output Under Voltage Lockout Status",
+ "Switch Node A Output Under Voltage Lockout Status",
+ "Switch Node D Output Current Limiter Warning Status",
+ "Switch Node C Output Current Limiter Warning Status",
+ "Switch Node B Output Current Limiter Warning Status",
+ "Switch Node A Output Current Limiter Warning Status"
+};
+
+static const char *pmic50x0_reg5_err_msgs[PMIC50X0_ERR_REG5_NUM_BITS] = {
+ "VOUT_1.0V LDO Output Power Good"
+};
+
+struct pmic50x0_err_reg {
+ unsigned int addr;
+ unsigned int *counters;
+ const char **err_msgs;
+ u8 sbit;
+ u8 ebit;
+ u8 size;
+ u8 err_active;
+};
+
+/* Main driver struct */
+struct pmic50x0 {
+ struct device *dev;
+ struct regmap *regmap;
+ struct delayed_work work;
+ struct pmic50x0_err_reg *err_regs;
+ struct notifier_block panic_notifier;
+ long last_voltage[PMIC50X0_VOLTAGE_CHANNELS];
+ /* Mutex for reading the voltage registers */
+ struct mutex voltage_mutex;
+ /* Mutex for reading the current and power registers */
+ struct mutex curr_power_mutex;
+
+};
+
+static unsigned int error_polling_ms = PMIC50X0_ERR_POLL_INTERVAL_MSEC;
+module_param(error_polling_ms, uint, 0644);
+MODULE_PARM_DESC(error_polling_ms, "PMIC error polling interval in msec, default = 1000");
+
+static ssize_t pmic50x0_err_show(struct device *dev,
+ struct device_attribute *da, char *buf)
+{
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
+ struct pmic50x0 *pmic50x0 = dev_get_drvdata(dev);
+ unsigned int idx = attr->index;
+ struct pmic50x0_error err = pmic50x0_error_reg[idx];
+ u8 reg, bit;
+
+ reg = err.reg;
+ bit = err.bit - pmic50x0->err_regs[reg].sbit;
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", pmic50x0->err_regs[reg].counters[bit]);
+}
+
+/* Error Sysfs group */
+PMIC50X0_DEV_ATTR(err_global_log_vin_bulk_over_vol, PMIC50X0_ERR_GLOBAL_LOG_VIN_BULK_OVER_VOL);
+PMIC50X0_DEV_ATTR(err_global_log_crit_temp, PMIC50X0_ERR_GLOBAL_LOG_CRIT_TEMP);
+PMIC50X0_DEV_ATTR(err_global_log_buck_ov_or_uv, PMIC50X0_ERR_GLOBAL_LOG_BUCK_OV_OR_UV);
+PMIC50X0_DEV_ATTR(err_vin_bulk_input_over_vol_stat, PMIC50X0_ERR_VIN_BULK_INPUT_OVER_VOL_STAT);
+PMIC50X0_DEV_ATTR(err_vin_mgmt_input_over_vol_stat, PMIC50X0_ERR_VIN_MGMT_INPUT_OVER_VOL_STAT);
+PMIC50X0_DEV_ATTR(err_swd_out_pow_good_stat, PMIC50X0_ERR_SWD_OUT_POW_GOOD_STAT);
+PMIC50X0_DEV_ATTR(err_swc_out_pow_good_stat, PMIC50X0_ERR_SWC_OUT_POW_GOOD_STAT);
+PMIC50X0_DEV_ATTR(err_swb_out_pow_good_stat, PMIC50X0_ERR_SWB_OUT_POW_GOOD_STAT);
+PMIC50X0_DEV_ATTR(err_swa_out_pow_good_stat, PMIC50X0_ERR_SWA_OUT_POW_GOOD_STAT);
+PMIC50X0_DEV_ATTR(err_crit_temp_shutdown_stat, PMIC50X0_ERR_CRIT_TEMP_SHUTDOWN_STAT);
+PMIC50X0_DEV_ATTR(err_vin_bulk_input_pow_good_stat, PMIC50X0_ERR_VIN_BULK_INPUT_POW_GOOD_STAT);
+PMIC50X0_DEV_ATTR(err_swd_high_out_curr_consump_stat, PMIC50X0_ERR_SWD_HIGH_OUT_CURR_CONSUMP_STAT);
+PMIC50X0_DEV_ATTR(err_swc_high_out_curr_consump_stat, PMIC50X0_ERR_SWC_HIGH_OUT_CURR_CONSUMP_STAT);
+PMIC50X0_DEV_ATTR(err_swb_high_out_curr_consump_stat, PMIC50X0_ERR_SWB_HIGH_OUT_CURR_CONSUMP_STAT);
+PMIC50X0_DEV_ATTR(err_swa_high_out_curr_consump_stat, PMIC50X0_ERR_SWA_HIGH_OUT_CURR_CONSUMP_STAT);
+PMIC50X0_DEV_ATTR(err_vin_mgmt_to_vin_bulk_stat, PMIC50X0_ERR_VIN_MGMT_TO_VIN_BULK_STAT);
+PMIC50X0_DEV_ATTR(err_vout_1_8v_out_power_good_stat, PMIC50X0_ERR_VOUT_1_8V_OUT_POWER_GOOD_STAT);
+PMIC50X0_DEV_ATTR(err_vbias_power_good_stat, PMIC50X0_ERR_VBIAS_POWER_GOOD_STAT);
+PMIC50X0_DEV_ATTR(err_pmic_high_temp_warn_stat, PMIC50X0_ERR_PMIC_HIGH_TEMP_WARN_STAT);
+PMIC50X0_DEV_ATTR(err_swd_out_over_vol_stat, PMIC50X0_ERR_SWD_OUT_OVER_VOL_STAT);
+PMIC50X0_DEV_ATTR(err_swc_out_over_vol_stat, PMIC50X0_ERR_SWC_OUT_OVER_VOL_STAT);
+PMIC50X0_DEV_ATTR(err_swb_out_over_vol_stat, PMIC50X0_ERR_SWB_OUT_OVER_VOL_STAT);
+PMIC50X0_DEV_ATTR(err_swa_out_over_vol_stat, PMIC50X0_ERR_SWA_OUT_OVER_VOL_STAT);
+PMIC50X0_DEV_ATTR(err_swd_out_under_vol_lockout_stat, PMIC50X0_ERR_SWD_OUT_UNDER_VOL_LOCKOUT_STAT);
+PMIC50X0_DEV_ATTR(err_swc_out_under_vol_lockout_stat, PMIC50X0_ERR_SWC_OUT_UNDER_VOL_LOCKOUT_STAT);
+PMIC50X0_DEV_ATTR(err_swb_out_under_vol_lockout_stat, PMIC50X0_ERR_SWB_OUT_UNDER_VOL_LOCKOUT_STAT);
+PMIC50X0_DEV_ATTR(err_swa_out_under_vol_lockout_stat, PMIC50X0_ERR_SWA_OUT_UNDER_VOL_LOCKOUT_STAT);
+PMIC50X0_DEV_ATTR(err_swd_out_curr_limiter_warn_stat, PMIC50X0_ERR_SWD_OUT_CURR_LIMITER_WARN_STAT);
+PMIC50X0_DEV_ATTR(err_swc_out_curr_limiter_warn_stat, PMIC50X0_ERR_SWC_OUT_CURR_LIMITER_WARN_STAT);
+PMIC50X0_DEV_ATTR(err_swb_out_curr_limiter_warn_stat, PMIC50X0_ERR_SWB_OUT_CURR_LIMITER_WARN_STAT);
+PMIC50X0_DEV_ATTR(err_swa_out_curr_limiter_warn_stat, PMIC50X0_ERR_SWA_OUT_CURR_LIMITER_WARN_STAT);
+PMIC50X0_DEV_ATTR(err_vout_1v_out_power_good_stat, PMIC50X0_ERR_VOUT_1V_OUT_POWER_GOOD_STAT);
+
+static struct attribute *pmic50x0_err_attrs[] = {
+ &sensor_dev_attr_err_global_log_vin_bulk_over_vol.dev_attr.attr,
+ &sensor_dev_attr_err_global_log_crit_temp.dev_attr.attr,
+ &sensor_dev_attr_err_global_log_buck_ov_or_uv.dev_attr.attr,
+ &sensor_dev_attr_err_vin_bulk_input_over_vol_stat.dev_attr.attr,
+ &sensor_dev_attr_err_vin_mgmt_input_over_vol_stat.dev_attr.attr,
+ &sensor_dev_attr_err_swd_out_pow_good_stat.dev_attr.attr,
+ &sensor_dev_attr_err_swc_out_pow_good_stat.dev_attr.attr,
+ &sensor_dev_attr_err_swb_out_pow_good_stat.dev_attr.attr,
+ &sensor_dev_attr_err_swa_out_pow_good_stat.dev_attr.attr,
+ &sensor_dev_attr_err_crit_temp_shutdown_stat.dev_attr.attr,
+ &sensor_dev_attr_err_vin_bulk_input_pow_good_stat.dev_attr.attr,
+ &sensor_dev_attr_err_swd_high_out_curr_consump_stat.dev_attr.attr,
+ &sensor_dev_attr_err_swc_high_out_curr_consump_stat.dev_attr.attr,
+ &sensor_dev_attr_err_swb_high_out_curr_consump_stat.dev_attr.attr,
+ &sensor_dev_attr_err_swa_high_out_curr_consump_stat.dev_attr.attr,
+ &sensor_dev_attr_err_vin_mgmt_to_vin_bulk_stat.dev_attr.attr,
+ &sensor_dev_attr_err_vout_1_8v_out_power_good_stat.dev_attr.attr,
+ &sensor_dev_attr_err_vbias_power_good_stat.dev_attr.attr,
+ &sensor_dev_attr_err_pmic_high_temp_warn_stat.dev_attr.attr,
+ &sensor_dev_attr_err_swd_out_over_vol_stat.dev_attr.attr,
+ &sensor_dev_attr_err_swc_out_over_vol_stat.dev_attr.attr,
+ &sensor_dev_attr_err_swb_out_over_vol_stat.dev_attr.attr,
+ &sensor_dev_attr_err_swa_out_over_vol_stat.dev_attr.attr,
+ &sensor_dev_attr_err_swd_out_under_vol_lockout_stat.dev_attr.attr,
+ &sensor_dev_attr_err_swc_out_under_vol_lockout_stat.dev_attr.attr,
+ &sensor_dev_attr_err_swb_out_under_vol_lockout_stat.dev_attr.attr,
+ &sensor_dev_attr_err_swa_out_under_vol_lockout_stat.dev_attr.attr,
+ &sensor_dev_attr_err_swd_out_curr_limiter_warn_stat.dev_attr.attr,
+ &sensor_dev_attr_err_swc_out_curr_limiter_warn_stat.dev_attr.attr,
+ &sensor_dev_attr_err_swb_out_curr_limiter_warn_stat.dev_attr.attr,
+ &sensor_dev_attr_err_swa_out_curr_limiter_warn_stat.dev_attr.attr,
+ &sensor_dev_attr_err_vout_1v_out_power_good_stat.dev_attr.attr,
+ NULL,
+};
+
+ATTRIBUTE_GROUPS(pmic50x0_err);
+
+static struct pmic50x0_err_reg pmic50x0_err_regs[] = {
+ {
+ .addr = PMIC50X0_ERR_REG0_ADDR,
+ .err_msgs = pmic50x0_reg0_err_msgs,
+ .sbit = PMIC50X0_ERR_REG0_SBIT,
+ .ebit = PMIC50X0_ERR_REG0_EBIT,
+ .size = PMIC50X0_ERR_REG0_NUM_BITS,
+ },
+ {
+ .addr = PMIC50X0_ERR_REG1_ADDR,
+ .err_msgs = pmic50x0_reg1_err_msgs,
+ .sbit = PMIC50X0_ERR_REG1_SBIT,
+ .ebit = PMIC50X0_ERR_REG1_EBIT,
+ .size = PMIC50X0_ERR_REG1_NUM_BITS,
+ },
+ {
+ .addr = PMIC50X0_ERR_REG2_ADDR,
+ .err_msgs = pmic50x0_reg2_err_msgs,
+ .sbit = PMIC50X0_ERR_REG2_SBIT,
+ .ebit = PMIC50X0_ERR_REG2_EBIT,
+ .size = PMIC50X0_ERR_REG2_NUM_BITS,
+ },
+ {
+ .addr = PMIC50X0_ERR_REG3_ADDR,
+ .err_msgs = pmic50x0_reg3_err_msgs,
+ .sbit = PMIC50X0_ERR_REG3_SBIT,
+ .ebit = PMIC50X0_ERR_REG3_EBIT,
+ .size = PMIC50X0_ERR_REG3_NUM_BITS,
+ },
+ {
+ .addr = PMIC50X0_ERR_REG4_ADDR,
+ .err_msgs = pmic50x0_reg4_err_msgs,
+ .sbit = PMIC50X0_ERR_REG4_SBIT,
+ .ebit = PMIC50X0_ERR_REG4_EBIT,
+ .size = PMIC50X0_ERR_REG4_NUM_BITS,
+ },
+ {
+ .addr = PMIC50X0_ERR_REG5_ADDR,
+ .err_msgs = pmic50x0_reg5_err_msgs,
+ .sbit = PMIC50X0_ERR_REG5_SBIT,
+ .ebit = PMIC50X0_ERR_REG5_EBIT,
+ .size = PMIC50X0_ERR_REG5_NUM_BITS,
+ },
+};
+
+static int pmic50x0_temp_read(struct device *dev, long *val)
+{
+ struct pmic50x0 *pmic50x0 = dev_get_drvdata(dev);
+ unsigned int regval;
+ long temp;
+ int err;
+
+ err = regmap_read(pmic50x0->regmap, PMIC50X0_REG_TEMP, ®val);
+ if (err < 0)
+ return err;
+
+ temp = FIELD_GET(PMIC50X0_TEMP_MASK, regval) * PMIC50X0_TEMP_FACTOR;
+ *val = MILLIDEGREE_PER_DEGREE * (PMIC50X0_TEMP_BASE + temp);
+
+ return 0;
+}
+
+static int pmic50x0_in_read(struct device *dev, long *val, int channel)
+{
+ struct pmic50x0 *pmic50x0 = dev_get_drvdata(dev);
+ unsigned int regval;
+ int err;
+
+ mutex_lock(&pmic50x0->voltage_mutex);
+
+ /* Select the node */
+ err = regmap_update_bits(pmic50x0->regmap, PMIC50X0_REG_VOL_NODE_SELECT,
+ PMIC50X0_VOL_SELECT_MASK, channel << PMIC50X0_VOL_OFFSET);
+ if (err < 0)
+ goto ret_unlock;
+
+ /* The spec requires 9ms delay between the node selection and the reading */
+ msleep(PMIC50X0_VOL_SELECT_DELAY);
+
+ /* Read the voltage register after selecting the node */
+ err = regmap_read(pmic50x0->regmap, PMIC50X0_REG_VOL, ®val);
+ if (err < 0)
+ goto ret_unlock;
+
+ *val = FIELD_GET(PMIC50X0_VOL_MASK, regval) * PMIC50X0_VOL_FACTOR;
+ pmic50x0->last_voltage[channel] = *val;
+
+ret_unlock:
+ mutex_unlock(&pmic50x0->voltage_mutex);
+
+ return err;
+}
+
+static int pmic50x0_curr_power_read(struct device *dev, long *val, int channel,
+ enum pmic50x0_select_current_power node)
+{
+ struct pmic50x0 *pmic50x0 = dev_get_drvdata(dev);
+ unsigned int regval, reg;
+ long mask;
+ int err;
+
+ mutex_lock(&pmic50x0->curr_power_mutex);
+
+ /* Select power/current mode */
+ err = regmap_update_bits(pmic50x0->regmap, PMIC50X0_REG_CURR_POW_SELECT,
+ PMIC50X0_CURR_POW_SELECT, node << PMIC50X0_CURR_POW_SELECT_OFFSET);
+ if (err < 0)
+ goto ret_unlock;
+
+ switch (channel) {
+ case PMIC50X0_CURR_POW_NODE_A:
+ mask = PMIC50X0_CURR_POW_A_MASK;
+ reg = PMIC50X0_REG_CURR_POW;
+
+ /* Select node A */
+ err = regmap_update_bits(pmic50x0->regmap, PMIC50X0_REG_NODE_A_SUM_SELECT,
+ PMIC50X0_CURR_POW_A_SUM_SELECT, PMIC50X0_SELECT_POWER_A);
+ if (err < 0)
+ goto ret_unlock;
+ break;
+ case PMIC50X0_CURR_POW_NODE_B:
+ case PMIC50X0_CURR_POW_NODE_C:
+ case PMIC50X0_CURR_POW_NODE_D:
+ mask = PMIC50X0_CURR_POW_BCD_MASK;
+ reg = PMIC50X0_REG_CURR_POW + channel;
+ break;
+ case PMIC50X0_CURR_POW_SUM:
+ mask = PMIC50X0_CURR_POW_SUM_MASK;
+ reg = PMIC50X0_REG_CURR_POW;
+
+ /* Select the sum of A,B,C and D nodes */
+ err = regmap_update_bits(pmic50x0->regmap, PMIC50X0_REG_NODE_A_SUM_SELECT,
+ PMIC50X0_CURR_POW_A_SUM_SELECT, PMIC50X0_SELECT_POWER_SUM);
+ if (err < 0)
+ goto ret_unlock;
+ break;
+ default:
+ err = -EOPNOTSUPP;
+ goto ret_unlock;
+ }
+
+ err = regmap_read(pmic50x0->regmap, reg, ®val);
+ if (err < 0)
+ goto ret_unlock;
+
+ *val = (regval & mask) * PMIC50X0_CURR_POWER_FACTOR;
+
+ret_unlock:
+ mutex_unlock(&pmic50x0->curr_power_mutex);
+
+ return err;
+}
+
+static int pmic50x0_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+ long *val)
+{
+ switch (type) {
+ case hwmon_temp:
+ if (attr != hwmon_temp_input)
+ return -EOPNOTSUPP;
+ return pmic50x0_temp_read(dev, val);
+ case hwmon_in:
+ if (attr != hwmon_in_input)
+ return -EOPNOTSUPP;
+ return pmic50x0_in_read(dev, val, channel);
+ case hwmon_curr:
+ if (attr != hwmon_curr_input)
+ return -EOPNOTSUPP;
+ return pmic50x0_curr_power_read(dev, val, channel, PMIC50X0_SELECT_CURRENT);
+ case hwmon_power:
+ if (attr != hwmon_power_input)
+ return -EOPNOTSUPP;
+ return pmic50x0_curr_power_read(dev, val, channel, PMIC50X0_SELECT_POWER);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static umode_t pmic50x0_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr,
+ int channel)
+{
+ switch (type) {
+ case hwmon_temp:
+ return (attr == hwmon_temp_input) ? 0444 : 0;
+ case hwmon_in:
+ return (attr == hwmon_in_input) ? 0444 : 0;
+ case hwmon_curr:
+ return (attr == hwmon_curr_input) ? 0444 : 0;
+ case hwmon_power:
+ return (attr == hwmon_power_input) ? 0444 : 0;
+ default:
+ return 0;
+ }
+}
+
+static const u32 pmic50x0_temp_config[] = {
+ HWMON_T_INPUT,
+ 0
+};
+
+static const struct hwmon_channel_info pmic50x0_temp = {
+ .type = hwmon_temp,
+ .config = pmic50x0_temp_config,
+};
+
+static const u32 pmic50x0_in_config[] = {
+ HWMON_I_INPUT,
+ HWMON_I_INPUT,
+ HWMON_I_INPUT,
+ HWMON_I_INPUT,
+ 0
+};
+
+static const struct hwmon_channel_info pmic50x0_in = {
+ .type = hwmon_in,
+ .config = pmic50x0_in_config,
+};
+
+static const u32 pmic50x0_curr_config[] = {
+ HWMON_C_INPUT,
+ HWMON_C_INPUT,
+ HWMON_C_INPUT,
+ HWMON_C_INPUT,
+ 0
+};
+
+static const struct hwmon_channel_info pmic50x0_curr = {
+ .type = hwmon_curr,
+ .config = pmic50x0_curr_config,
+};
+
+static const u32 pmic50x0_power_config[] = {
+ HWMON_P_INPUT,
+ HWMON_P_INPUT,
+ HWMON_P_INPUT,
+ HWMON_P_INPUT,
+ HWMON_P_INPUT,
+ 0
+};
+
+static const struct hwmon_channel_info pmic50x0_power = {
+ .type = hwmon_power,
+ .config = pmic50x0_power_config,
+};
+
+static const struct hwmon_channel_info *pmic50x0_info[] = {
+ &pmic50x0_temp,
+ &pmic50x0_in,
+ &pmic50x0_curr,
+ &pmic50x0_power,
+ NULL
+};
+
+static const struct hwmon_ops pmic50x0_ops = {
+ .is_visible = pmic50x0_is_visible,
+ .read = pmic50x0_read,
+};
+
+static const struct hwmon_chip_info pmic50x0_chip_info = {
+ .ops = &pmic50x0_ops,
+ .info = pmic50x0_info
+};
+
+static const struct regmap_config pmic50x0_regmap_config = {
+ .reg_bits = PMIC50X0_REG_BITS,
+ .val_bits = PMIC50X0_VAL_BITS,
+ .max_register = PMIC50X0_MAX_REG_ADDR
+};
+
+static void pmic50x0_update_last_volt(struct pmic50x0 *pmic50x0)
+{
+ struct device *dev = pmic50x0->dev;
+ int i, err;
+
+ for (i = 0; i < PMIC50X0_VOLTAGE_CHANNELS; i++) {
+ err = pmic50x0_in_read(dev, &pmic50x0->last_voltage[i], i);
+ if (err < 0)
+ dev_err(dev, "Failed to read voltage (%d)\n", err);
+ }
+}
+
+static void pmic50x0_work_callback(struct work_struct *work)
+{
+ struct delayed_work *delayed_work = to_delayed_work(work);
+ struct pmic50x0 *pmic50x0 = container_of(delayed_work, struct pmic50x0, work);
+ struct pmic50x0_err_reg *reg;
+ u8 bit, sbit, ebit, i, idx;
+ unsigned int err_reg;
+ int err;
+
+ pmic50x0_update_last_volt(pmic50x0);
+
+ for (i = 0; i < ARRAY_SIZE(pmic50x0_err_regs); i++) {
+ reg = &pmic50x0->err_regs[i];
+ err = regmap_read(pmic50x0->regmap, reg->addr, &err_reg);
+ if (err < 0) {
+ dev_err(pmic50x0->dev, "Could not read Error Register at %x\n", reg->addr);
+ continue;
+ }
+
+ /* Checks if error bits changed since last polling interval */
+ if (err_reg == reg->err_active)
+ continue;
+
+ sbit = reg->sbit;
+ ebit = reg->ebit;
+
+ for (bit = sbit; bit <= ebit; bit++) {
+ idx = bit - sbit;
+
+ if (err_reg & BIT(bit)) {
+ /* Continue if error is not new */
+ if (reg->err_active & BIT(bit))
+ continue;
+
+ /* Mark the error bit as active */
+ reg->err_active |= BIT(bit);
+ reg->counters[idx]++;
+
+ dev_err(pmic50x0->dev, "%s (cnt=%u)\n", reg->err_msgs[idx],
+ reg->counters[idx]);
+ } else if (reg->err_active & BIT(bit)) {
+ /* Error cleared since last polling interval. */
+ dev_info(pmic50x0->dev, "Error (%s) cleared\n", reg->err_msgs[idx]);
+
+ reg->err_active &= ~BIT(bit);
+ }
+ }
+ }
+
+ schedule_delayed_work(&pmic50x0->work, msecs_to_jiffies(error_polling_ms));
+}
+
+static void pmic50x0_cancel_work(void *work)
+{
+ cancel_delayed_work_sync(work);
+}
+
+static int pmic50x0_work_init(struct device *dev)
+{
+ struct pmic50x0 *pmic50x0 = dev_get_drvdata(dev);
+
+ INIT_DELAYED_WORK(&pmic50x0->work, pmic50x0_work_callback);
+ schedule_delayed_work(&pmic50x0->work, msecs_to_jiffies(error_polling_ms));
+
+ return devm_add_action_or_reset(dev, pmic50x0_cancel_work, &pmic50x0->work);
+}
+
+static int pmic50x0_error_init(struct device *dev)
+{
+ struct pmic50x0 *pmic50x0 = dev_get_drvdata(dev);
+ int reg;
+
+ pmic50x0->err_regs = devm_kcalloc(dev, ARRAY_SIZE(pmic50x0_err_regs),
+ sizeof(*pmic50x0->err_regs), GFP_KERNEL);
+ if (!pmic50x0->err_regs)
+ return -ENOMEM;
+
+ for (reg = 0; reg < ARRAY_SIZE(pmic50x0_err_regs); reg++) {
+ pmic50x0->err_regs[reg] = pmic50x0_err_regs[reg];
+ pmic50x0->err_regs[reg].counters =
+ devm_kcalloc(dev, pmic50x0->err_regs[reg].size,
+ sizeof(*pmic50x0->err_regs[reg].counters), GFP_KERNEL);
+ if (!pmic50x0->err_regs[reg].counters)
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void pmic50x0_mutexes_destroy(void *arg)
+{
+ struct pmic50x0 *pmic50x0 = arg;
+
+ mutex_destroy(&pmic50x0->curr_power_mutex);
+ mutex_destroy(&pmic50x0->voltage_mutex);
+}
+
+static int pmic50x0_mutexes_init(struct pmic50x0 *pmic50x0)
+{
+ mutex_init(&pmic50x0->voltage_mutex);
+ mutex_init(&pmic50x0->curr_power_mutex);
+
+ return devm_add_action_or_reset(pmic50x0->dev, pmic50x0_mutexes_destroy, pmic50x0);
+}
+
+static int pmic50x0_panic_callback(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct pmic50x0 *pmic50x0 = container_of(nb, struct pmic50x0, panic_notifier);
+
+ dev_emerg(pmic50x0->dev, "volt(mV): A=%ld, B=%ld, C=%ld, D=%ld\n",
+ pmic50x0->last_voltage[0], pmic50x0->last_voltage[1],
+ pmic50x0->last_voltage[2], pmic50x0->last_voltage[3]);
+
+ return NOTIFY_DONE;
+}
+
+static void pmic50x0_panic_notifier_unregister(void *data)
+{
+ struct pmic50x0 *pmic50x0 = data;
+
+ atomic_notifier_chain_unregister(&panic_notifier_list, &pmic50x0->panic_notifier);
+}
+
+static int pmic50x0_panic_notifier_register(struct pmic50x0 *pmic50x0)
+{
+ struct notifier_block *panic_notifier = &pmic50x0->panic_notifier;
+ struct device *dev = pmic50x0->dev;
+ int ret;
+
+ panic_notifier->notifier_call = pmic50x0_panic_callback;
+ panic_notifier->priority = 0;
+
+ ret = atomic_notifier_chain_register(&panic_notifier_list, panic_notifier);
+ if (ret) {
+ dev_err(dev, "failed to register panic notifier (%d)\n", ret);
+ return ret;
+ }
+
+ return devm_add_action_or_reset(dev, pmic50x0_panic_notifier_unregister, pmic50x0);
+}
+
+static int pmic50x0_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct pmic50x0 *pmic50x0;
+ struct device *hwmon_dev;
+ int err;
+
+ pmic50x0 = devm_kzalloc(dev, sizeof(*pmic50x0), GFP_KERNEL);
+ if (!pmic50x0)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, pmic50x0);
+
+ pmic50x0->dev = dev;
+ pmic50x0->regmap = devm_regmap_init_i2c(client, &pmic50x0_regmap_config);
+ if (IS_ERR(pmic50x0->regmap)) {
+ dev_err(dev, "init regmap failed!\n");
+ return PTR_ERR(pmic50x0->regmap);
+ }
+
+ err = pmic50x0_error_init(dev);
+ if (err < 0)
+ return err;
+
+ err = pmic50x0_mutexes_init(pmic50x0);
+ if (err < 0)
+ return err;
+
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, pmic50x0,
+ &pmic50x0_chip_info, pmic50x0_err_groups);
+ if (IS_ERR(hwmon_dev))
+ return PTR_ERR(hwmon_dev);
+
+ err = pmic50x0_panic_notifier_register(pmic50x0);
+ if (err < 0)
+ return err;
+
+ return pmic50x0_work_init(dev);
+}
+
+static const struct i2c_device_id pmic50x0_ids[] = {
+ { PMIC50X0_DRIVER_NAME },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, pmic50x0_ids);
+
+static const struct of_device_id pmic50x0_of_match[] = {
+ { .compatible = "jedec,pmic50x0" },
+ { },
+};
+
+MODULE_DEVICE_TABLE(of, pmic50x0_of_match);
+
+static struct i2c_driver pmic50x0_driver = {
+ .class = I2C_CLASS_HWMON,
+ .driver = {
+ .name = PMIC50X0_DRIVER_NAME,
+ .of_match_table = pmic50x0_of_match,
+ },
+ .probe = pmic50x0_probe,
+ .id_table = pmic50x0_ids,
+};
+
+module_i2c_driver(pmic50x0_driver);
+
+MODULE_AUTHOR("Almog Ben Shaul <almogbs@amazon.com>");
+MODULE_DESCRIPTION("JEDEC PMIC50x0 Hardware Monitoring Driver");
+MODULE_LICENSE("GPL");
--
2.47.3
On Wed, Jan 21, 2026 at 03:19:47PM +0000, Almog Ben Shaul wrote:
> Add hardware monitoring driver for JEDEC PMIC50x0 compliant I2C DDR5
> PMICs.
>
> The driver provides monitoring for voltage, current, power, and
> temperature across multiple channels, along with comprehensive error
> reporting.
>
> Signed-off-by: Almog Ben Shaul <almogbs@amazon.com>
> Tested-by: Almog Ben Shaul <almogbs@amazon.com>
Drop Tested-by:.
Please provide a register map (generated using i2dump or similar)
for the chip.
> ---
> Documentation/hwmon/index.rst | 1 +
> Documentation/hwmon/pmic50x0.rst | 113 +++++
> MAINTAINERS | 6 +
> drivers/hwmon/Kconfig | 10 +
> drivers/hwmon/Makefile | 1 +
> drivers/hwmon/pmic50x0.c | 839 +++++++++++++++++++++++++++++++
> 6 files changed, 970 insertions(+)
> create mode 100644 Documentation/hwmon/pmic50x0.rst
> create mode 100644 drivers/hwmon/pmic50x0.c
>
> diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
> index 85d7a686883e..a08ef61c9cda 100644
> --- a/Documentation/hwmon/index.rst
> +++ b/Documentation/hwmon/index.rst
> @@ -210,6 +210,7 @@ Hardware Monitoring Kernel Drivers
> peci-cputemp
> peci-dimmtemp
> pmbus
> + pmic50x0
> powerz
> powr1220
> pt5161l
> diff --git a/Documentation/hwmon/pmic50x0.rst b/Documentation/hwmon/pmic50x0.rst
> new file mode 100644
> index 000000000000..2a0a6be2a3b1
> --- /dev/null
> +++ b/Documentation/hwmon/pmic50x0.rst
> @@ -0,0 +1,113 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +
> +Kernel driver pmic50x0
> +======================
> +
> +Supported chips:
> +
> + * JEDEC PMIC50X0 (JESD301) compliant DDR5 PMICs
> +
> + JEDEC standard download:
> + https://www.jedec.org/standards-documents/docs/jesd301-1a03
> +
> + Prefix: 'pmic50x0'
> +
> + Addresses scanned: ~
> +
> +Author:
> + Almog Ben Shaul <almogbs@amazon.com>
> +
> +
> +Description
> +-----------
> +
> +This driver implements support for hardware monitoring of JEDEC PMIC50X0
It is really PMIC5000/PMIC5010, not PMIC50X0. PMIC50[2-9]0 are not covered.
Please be explicit.
> +compliant DDR5 Power Management ICs. These devices are I2C-based power
> +management controllers designed specifically for DDR5 memory modules.
> +
> +The driver provides monitoring for:
> +
> + * Voltage measurements across 4 switch nodes (A, B, C, D)
> + * Current measurements for each switch node
> + * Power consumption per switch node and total power
> + * PMIC die temperature
> + * Comprehensive error status reporting
> +
> +The PMIC50X0 specification defines a standard interface for DDR5 power
> +management, including telemetry and error reporting capabilities.
> +
> +
> +Usage Notes
> +-----------
> +
> +Error monitoring is performed via a delayed work queue that polls error
> +registers at a configurable interval (default 1000ms). The polling interval
> +can be adjusted via the module parameter ``error_polling_ms``.
> +
This is a no-go. Please no such polling in hardware monitoring drivers.
More on that below.
> +
> +Hardware monitoring sysfs entries
> +---------------------------------
> +
> +======================= ========================================================
> +temp1_input PMIC die temperature in millidegrees Celsius
> +
> +in0_input Switch Node A output voltage in millivolts
> +in1_input Switch Node B output voltage in millivolts
> +in2_input Switch Node C output voltage in millivolts
> +in3_input Switch Node D output voltage in millivolts
> +
> +curr1_input Switch Node A output current in milliamperes
> +curr2_input Switch Node B output current in milliamperes
> +curr3_input Switch Node C output current in milliamperes
> +curr4_input Switch Node D output current in milliamperes
> +
> +power1_input Switch Node A power consumption in microwatts
> +power2_input Switch Node B power consumption in microwatts
> +power3_input Switch Node C power consumption in microwatts
> +power4_input Switch Node D power consumption in microwatts
> +power5_input Total power consumption (sum of all nodes) in microwatts
> +======================= ========================================================
> +
> +
> +Error Status Counters
> +---------------------
> +
> +The driver maintains counters for various error conditions. Each counter
> +increments when the corresponding error condition is detected during polling.
> +All error attributes are read-only and return the number of times the error
> +has been detected since driver load or counter reset.
> +
> +====================================== =========================================
> +err_global_log_vin_bulk_over_vol VIN_Bulk input over-voltage error count
> +err_global_log_crit_temp Critical temperature error count
> +err_global_log_buck_ov_or_uv Buck converter over/under-voltage count
> +err_vin_bulk_input_over_vol_stat VIN_Bulk over-voltage status count
> +err_vin_mgmt_input_over_vol_stat VIN_Mgmt over-voltage status count
> +err_vin_bulk_input_pow_good_stat VIN_Bulk power good status count
> +err_vin_mgmt_to_vin_bulk_stat VIN_Mgmt to VIN_Bulk switchover count
> +err_swa_out_pow_good_stat Switch Node A power good status count
> +err_swb_out_pow_good_stat Switch Node B power good status count
> +err_swc_out_pow_good_stat Switch Node C power good status count
> +err_swd_out_pow_good_stat Switch Node D power good status count
> +err_swa_out_over_vol_stat Switch Node A over-voltage count
> +err_swb_out_over_vol_stat Switch Node B over-voltage count
> +err_swc_out_over_vol_stat Switch Node C over-voltage count
> +err_swd_out_over_vol_stat Switch Node D over-voltage count
> +err_swa_out_under_vol_lockout_stat Switch Node A under-voltage lockout count
> +err_swb_out_under_vol_lockout_stat Switch Node B under-voltage lockout count
> +err_swc_out_under_vol_lockout_stat Switch Node C under-voltage lockout count
> +err_swd_out_under_vol_lockout_stat Switch Node D under-voltage lockout count
> +err_swa_high_out_curr_consump_stat Switch Node A high current warning count
> +err_swb_high_out_curr_consump_stat Switch Node B high current warning count
> +err_swc_high_out_curr_consump_stat Switch Node C high current warning count
> +err_swd_high_out_curr_consump_stat Switch Node D high current warning count
> +err_swa_out_curr_limiter_warn_stat Switch Node A current limiter count
> +err_swb_out_curr_limiter_warn_stat Switch Node B current limiter count
> +err_swc_out_curr_limiter_warn_stat Switch Node C current limiter count
> +err_swd_out_curr_limiter_warn_stat Switch Node D current limiter count
> +err_crit_temp_shutdown_stat Critical temperature shutdown count
> +err_pmic_high_temp_warn_stat High temperature warning count
> +err_vout_1v_out_power_good_stat VOUT_1.0V LDO power good status count
> +err_vout_1_8v_out_power_good_stat VOUT_1.8V LDO power good status count
> +err_vbias_power_good_stat VBias power good status count
Implement in debugfs.
> +====================================== =========================================
> diff --git a/MAINTAINERS b/MAINTAINERS
> index ebc2f1bc0ade..f179bccb992b 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13515,6 +13515,12 @@ S: Maintained
> F: arch/x86/include/asm/jailhouse_para.h
> F: arch/x86/kernel/jailhouse.c
>
> +JEDEC PMIC50X0 HARDWARE MONITOR DRIVER
> +M: Almog Ben Shaul <almogbs@amazon.com>
> +L: linux-hwmon@vger.kernel.org
> +S: Maintained
> +F: drivers/hwmon/pmic50x0.c
> +
> JFS FILESYSTEM
> M: Dave Kleikamp <shaggy@kernel.org>
> L: jfs-discussion@lists.sourceforge.net
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 157678b821fc..7100866ca444 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -1905,6 +1905,16 @@ config SENSORS_PWM_FAN
> This driver can also be built as a module. If so, the module
> will be called pwm-fan.
>
> +config SENSORS_PMIC50X0
> + tristate "JEDEC PMIC50x0 compliant DDR5 PMICs"
> + depends on I2C
> + select REGMAP_I2C
> + help
> + If you say yes here you get support for JEDEC PMIC50x0 compliant
> + DDR5 PMIC sensor chips.
> + This driver can also be built as a module. If so, the module
> + will be called pmic50x0.
> +
> config SENSORS_QNAP_MCU_HWMON
> tristate "QNAP MCU hardware monitoring"
> depends on MFD_QNAP_MCU
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index eade8e3b1bde..f831aacc5791 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -197,6 +197,7 @@ obj-$(CONFIG_SENSORS_POWERZ) += powerz.o
> obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o
> obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o
> obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o
> +obj-$(CONFIG_SENSORS_PMIC50X0) += pmic50x0.o
> obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON) += qnap-mcu-hwmon.o
> obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o
> obj-$(CONFIG_SENSORS_SA67MCU) += sa67mcu-hwmon.o
> diff --git a/drivers/hwmon/pmic50x0.c b/drivers/hwmon/pmic50x0.c
> new file mode 100644
> index 000000000000..14a336d64a5b
> --- /dev/null
> +++ b/drivers/hwmon/pmic50x0.c
> @@ -0,0 +1,839 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Hardware monitoring driver for JEDEC PMIC50x0 compliant DDR5 PMICs
> + *
> + * Specification: https://www.jedec.org/standards-documents/docs/jesd301-1a03
> + *
> + * Copyright (C) 2026 Almog Ben Shaul <almogbs@amazon.com>
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/panic_notifier.h>
> +#include <linux/regmap.h>
> +
> +/* PMIC50X0 Register Mapping */
> +#define PMIC50X0_REG_CURR_POW 0x0C
> +#define PMIC50X0_REG_NODE_A_SUM_SELECT 0x1A
> +#define PMIC50X0_REG_CURR_POW_SELECT 0x1B
> +#define PMIC50X0_REG_VOL_NODE_SELECT 0x30
> +#define PMIC50X0_REG_VOL 0x31
> +#define PMIC50X0_REG_TEMP 0x33
> +#define PMIC50X0_MAX_REG_ADDR 0xFF
> +
> +/* PMIC50X0 Error Registers & Bits Mapping */
> +#define PMIC50X0_ERR_REG0_ADDR 0x04
> +#define PMIC50X0_ERR_REG1_ADDR 0x08
> +#define PMIC50X0_ERR_REG2_ADDR 0x09
> +#define PMIC50X0_ERR_REG3_ADDR 0x0A
> +#define PMIC50X0_ERR_REG4_ADDR 0x0B
> +#define PMIC50X0_ERR_REG5_ADDR 0x33
> +
> +#define PMIC50X0_ERR_REG0_SBIT 4
> +#define PMIC50X0_ERR_REG1_SBIT 0
> +#define PMIC50X0_ERR_REG2_SBIT 0
> +#define PMIC50X0_ERR_REG3_SBIT 4
> +#define PMIC50X0_ERR_REG4_SBIT 0
> +#define PMIC50X0_ERR_REG5_SBIT 2
> +
> +#define PMIC50X0_ERR_REG0_EBIT 6
> +#define PMIC50X0_ERR_REG1_EBIT 7
> +#define PMIC50X0_ERR_REG2_EBIT 7
> +#define PMIC50X0_ERR_REG3_EBIT 7
> +#define PMIC50X0_ERR_REG4_EBIT 7
> +#define PMIC50X0_ERR_REG5_EBIT 2
> +
> +#define ERR_REG_NUM_BITS(bit) (PMIC50X0_ERR_REG##bit##_EBIT - PMIC50X0_ERR_REG##bit##_SBIT + 1)
> +#define PMIC50X0_ERR_REG0_NUM_BITS ERR_REG_NUM_BITS(0)
> +#define PMIC50X0_ERR_REG1_NUM_BITS ERR_REG_NUM_BITS(1)
> +#define PMIC50X0_ERR_REG2_NUM_BITS ERR_REG_NUM_BITS(2)
> +#define PMIC50X0_ERR_REG3_NUM_BITS ERR_REG_NUM_BITS(3)
> +#define PMIC50X0_ERR_REG4_NUM_BITS ERR_REG_NUM_BITS(4)
> +#define PMIC50X0_ERR_REG5_NUM_BITS ERR_REG_NUM_BITS(5)
> +
> +/* PMIC50X0 Masks and offsets etc. */
> +#define PMIC50X0_VOL_SELECT_MASK GENMASK(6, 3)
> +#define PMIC50X0_VOL_MASK GENMASK(7, 0)
> +#define PMIC50X0_TEMP_MASK GENMASK(7, 5)
> +#define PMIC50X0_CURR_POW_A_MASK GENMASK(7, 0)
> +#define PMIC50X0_CURR_POW_SUM_MASK GENMASK(7, 0)
> +#define PMIC50X0_CURR_POW_BCD_MASK GENMASK(5, 0)
> +#define PMIC50X0_CURR_POW_SELECT BIT(6)
> +#define PMIC50X0_CURR_POW_A_SUM_SELECT BIT(1)
> +#define PMIC50X0_VOL_OFFSET 3
> +#define PMIC50X0_CURR_POW_SELECT_OFFSET 6
> +#define PMIC50X0_CURR_POW_A_SUM_OFFSET 1
> +#define PMIC50X0_SELECT_POWER_A 0
> +#define PMIC50X0_SELECT_POWER_SUM BIT(PMIC50X0_CURR_POW_A_SUM_OFFSET)
> +#define PMIC50X0_VOL_SELECT_DELAY 9
> +
> +/* PMIC50X0 consts etc. */
> +#define PMIC50X0_DRIVER_NAME "pmic50x0"
> +#define PMIC50X0_REG_BITS 8
> +#define PMIC50X0_VAL_BITS 8
> +#define PMIC50X0_VOL_FACTOR 15
> +#define PMIC50X0_TEMP_FACTOR 10
> +#define PMIC50X0_TEMP_BASE 75
> +#define PMIC50X0_CURR_POWER_FACTOR 125
> +#define PMIC50X0_ERR_POLL_INTERVAL_MSEC 1000
> +#define PMIC50X0_VOLTAGE_CHANNELS 4
> +#define PMIC50X0_PASS0_UNLOCK_VAL 0x73
> +#define PMIC50X0_PASS1_UNLOCK_VAL 0x94
> +#define PMIC50X0_PROT_UNLOCK_VAL 0x40
> +#define PMIC50X0_PROT_LOCK_VAL 0x0
> +
Please only provide definitions if actually used.
> +#define MILLIDEGREE_PER_DEGREE 1000
MILLIDEGREE_PER_DEGREE is defined in include/linux/units.h. Use it.
> +#define PMIC50X0_DEV_ATTR(name, index) \
> + static SENSOR_DEVICE_ATTR(name, 0444, pmic50x0_err_show, 0, (index))
> +
> +enum pmic50x0_curr_pow_node {
> + PMIC50X0_CURR_POW_NODE_A,
> + PMIC50X0_CURR_POW_NODE_B,
> + PMIC50X0_CURR_POW_NODE_C,
> + PMIC50X0_CURR_POW_NODE_D,
> + PMIC50X0_CURR_POW_SUM
> +};
> +
> +enum pmic50x0_select_current_power {
> + PMIC50X0_SELECT_CURRENT,
> + PMIC50X0_SELECT_POWER,
> +};
> +
> +enum pmic50x0_error_list {
> + PMIC50X0_ERR_GLOBAL_LOG_VIN_BULK_OVER_VOL,
> + PMIC50X0_ERR_GLOBAL_LOG_CRIT_TEMP,
> + PMIC50X0_ERR_GLOBAL_LOG_BUCK_OV_OR_UV,
> + PMIC50X0_ERR_VIN_BULK_INPUT_OVER_VOL_STAT,
> + PMIC50X0_ERR_VIN_MGMT_INPUT_OVER_VOL_STAT,
> + PMIC50X0_ERR_SWD_OUT_POW_GOOD_STAT,
> + PMIC50X0_ERR_SWC_OUT_POW_GOOD_STAT,
> + PMIC50X0_ERR_SWB_OUT_POW_GOOD_STAT,
> + PMIC50X0_ERR_SWA_OUT_POW_GOOD_STAT,
> + PMIC50X0_ERR_CRIT_TEMP_SHUTDOWN_STAT,
> + PMIC50X0_ERR_VIN_BULK_INPUT_POW_GOOD_STAT,
> + PMIC50X0_ERR_SWD_HIGH_OUT_CURR_CONSUMP_STAT,
> + PMIC50X0_ERR_SWC_HIGH_OUT_CURR_CONSUMP_STAT,
> + PMIC50X0_ERR_SWB_HIGH_OUT_CURR_CONSUMP_STAT,
> + PMIC50X0_ERR_SWA_HIGH_OUT_CURR_CONSUMP_STAT,
> + PMIC50X0_ERR_VIN_MGMT_TO_VIN_BULK_STAT,
> + PMIC50X0_ERR_VOUT_1_8V_OUT_POWER_GOOD_STAT,
> + PMIC50X0_ERR_VBIAS_POWER_GOOD_STAT,
> + PMIC50X0_ERR_PMIC_HIGH_TEMP_WARN_STAT,
> + PMIC50X0_ERR_SWD_OUT_OVER_VOL_STAT,
> + PMIC50X0_ERR_SWC_OUT_OVER_VOL_STAT,
> + PMIC50X0_ERR_SWB_OUT_OVER_VOL_STAT,
> + PMIC50X0_ERR_SWA_OUT_OVER_VOL_STAT,
> + PMIC50X0_ERR_SWD_OUT_UNDER_VOL_LOCKOUT_STAT,
> + PMIC50X0_ERR_SWC_OUT_UNDER_VOL_LOCKOUT_STAT,
> + PMIC50X0_ERR_SWB_OUT_UNDER_VOL_LOCKOUT_STAT,
> + PMIC50X0_ERR_SWA_OUT_UNDER_VOL_LOCKOUT_STAT,
> + PMIC50X0_ERR_SWD_OUT_CURR_LIMITER_WARN_STAT,
> + PMIC50X0_ERR_SWC_OUT_CURR_LIMITER_WARN_STAT,
> + PMIC50X0_ERR_SWB_OUT_CURR_LIMITER_WARN_STAT,
> + PMIC50X0_ERR_SWA_OUT_CURR_LIMITER_WARN_STAT,
> + PMIC50X0_ERR_VOUT_1V_OUT_POWER_GOOD_STAT,
> + PMIC50X0_ERR_MAX
> +};
> +
> +struct pmic50x0_error {
> + u8 reg;
> + u8 bit;
> +};
> +
> +static const struct pmic50x0_error pmic50x0_error_reg[PMIC50X0_ERR_MAX] = {
> + [PMIC50X0_ERR_GLOBAL_LOG_VIN_BULK_OVER_VOL] = {.reg = 0, .bit = 4},
> + [PMIC50X0_ERR_GLOBAL_LOG_CRIT_TEMP] = {.reg = 0, .bit = 5},
> + [PMIC50X0_ERR_GLOBAL_LOG_BUCK_OV_OR_UV] = {.reg = 0, .bit = 6},
> + [PMIC50X0_ERR_VIN_BULK_INPUT_OVER_VOL_STAT] = {.reg = 1, .bit = 0},
> + [PMIC50X0_ERR_VIN_MGMT_INPUT_OVER_VOL_STAT] = {.reg = 1, .bit = 1},
> + [PMIC50X0_ERR_SWD_OUT_POW_GOOD_STAT] = {.reg = 1, .bit = 2},
> + [PMIC50X0_ERR_SWC_OUT_POW_GOOD_STAT] = {.reg = 1, .bit = 3},
> + [PMIC50X0_ERR_SWB_OUT_POW_GOOD_STAT] = {.reg = 1, .bit = 4},
> + [PMIC50X0_ERR_SWA_OUT_POW_GOOD_STAT] = {.reg = 1, .bit = 5},
> + [PMIC50X0_ERR_CRIT_TEMP_SHUTDOWN_STAT] = {.reg = 1, .bit = 6},
> + [PMIC50X0_ERR_VIN_BULK_INPUT_POW_GOOD_STAT] = {.reg = 1, .bit = 7},
> + [PMIC50X0_ERR_SWD_HIGH_OUT_CURR_CONSUMP_STAT] = {.reg = 2, .bit = 0},
> + [PMIC50X0_ERR_SWC_HIGH_OUT_CURR_CONSUMP_STAT] = {.reg = 2, .bit = 1},
> + [PMIC50X0_ERR_SWB_HIGH_OUT_CURR_CONSUMP_STAT] = {.reg = 2, .bit = 2},
> + [PMIC50X0_ERR_SWA_HIGH_OUT_CURR_CONSUMP_STAT] = {.reg = 2, .bit = 3},
> + [PMIC50X0_ERR_VIN_MGMT_TO_VIN_BULK_STAT] = {.reg = 2, .bit = 4},
> + [PMIC50X0_ERR_VOUT_1_8V_OUT_POWER_GOOD_STAT] = {.reg = 2, .bit = 5},
> + [PMIC50X0_ERR_VBIAS_POWER_GOOD_STAT] = {.reg = 2, .bit = 6},
> + [PMIC50X0_ERR_PMIC_HIGH_TEMP_WARN_STAT] = {.reg = 2, .bit = 7},
> + [PMIC50X0_ERR_SWD_OUT_OVER_VOL_STAT] = {.reg = 3, .bit = 4},
> + [PMIC50X0_ERR_SWC_OUT_OVER_VOL_STAT] = {.reg = 3, .bit = 5},
> + [PMIC50X0_ERR_SWB_OUT_OVER_VOL_STAT] = {.reg = 3, .bit = 6},
> + [PMIC50X0_ERR_SWA_OUT_OVER_VOL_STAT] = {.reg = 3, .bit = 7},
> + [PMIC50X0_ERR_SWD_OUT_UNDER_VOL_LOCKOUT_STAT] = {.reg = 4, .bit = 0},
> + [PMIC50X0_ERR_SWC_OUT_UNDER_VOL_LOCKOUT_STAT] = {.reg = 4, .bit = 1},
> + [PMIC50X0_ERR_SWB_OUT_UNDER_VOL_LOCKOUT_STAT] = {.reg = 4, .bit = 2},
> + [PMIC50X0_ERR_SWA_OUT_UNDER_VOL_LOCKOUT_STAT] = {.reg = 4, .bit = 3},
> + [PMIC50X0_ERR_SWD_OUT_CURR_LIMITER_WARN_STAT] = {.reg = 4, .bit = 4},
> + [PMIC50X0_ERR_SWC_OUT_CURR_LIMITER_WARN_STAT] = {.reg = 4, .bit = 5},
> + [PMIC50X0_ERR_SWB_OUT_CURR_LIMITER_WARN_STAT] = {.reg = 4, .bit = 6},
> + [PMIC50X0_ERR_SWA_OUT_CURR_LIMITER_WARN_STAT] = {.reg = 4, .bit = 7},
> + [PMIC50X0_ERR_VOUT_1V_OUT_POWER_GOOD_STAT] = {.reg = 5, .bit = 2},
> +};
> +
> +static const char *pmic50x0_reg0_err_msgs[PMIC50X0_ERR_REG0_NUM_BITS] = {
> + "Global Error Log History for Critical Temperature",
> + "Global Error Log History for VIN_Bulk Over",
> + "Global Error Log History for Buck Regulator Output O/U Voltage",
> +};
> +
> +static const char *pmic50x0_reg1_err_msgs[PMIC50X0_ERR_REG1_NUM_BITS] = {
> + "VIN_Bulk Input Supply Over Voltage Status",
> + "VIN_Mgmt Input Supply Over Voltage Status",
> + "Switch Node D Output Power Good Status",
> + "Switch Node C Output Power Good Status",
> + "Switch Node B Output Power Good Status",
> + "Switch Node A Output Power Good Status",
> + "Critical Temperature Shutdown Status",
> + "VIN_Bulk Input Power Good Status"
> +};
> +
> +static const char *pmic50x0_reg2_err_msgs[PMIC50X0_ERR_REG2_NUM_BITS] = {
> + "Switch Node D High Output Current Consumption Warning Status",
> + "Switch Node C High Output Current Consumption Warning Status",
> + "Switch Node B High Output Current Consumption Warning Status",
> + "Switch Node A High Output Current Consumption Warning Status",
> + "VIN_Mgmt to VIN_Bulk Input Supply Automatic Switchover Status",
> + "VOUT_1.8V LDO Output Power Good Status",
> + "VBias Power Good Status",
> + "PMIC High Temperature Warning Status"
> +};
> +
> +static const char *pmic50x0_reg3_err_msgs[PMIC50X0_ERR_REG3_NUM_BITS] = {
> + "Switch Node D Output Over Voltage Status",
> + "Switch Node C Output Over Voltage Status",
> + "Switch Node B Output Over Voltage Status",
> + "Switch Node A Output Over Voltage Status"
> +};
> +
> +static const char *pmic50x0_reg4_err_msgs[PMIC50X0_ERR_REG4_NUM_BITS] = {
> + "Switch Node D Output Under Voltage Lockout Status",
> + "Switch Node C Output Under Voltage Lockout Status",
> + "Switch Node B Output Under Voltage Lockout Status",
> + "Switch Node A Output Under Voltage Lockout Status",
> + "Switch Node D Output Current Limiter Warning Status",
> + "Switch Node C Output Current Limiter Warning Status",
> + "Switch Node B Output Current Limiter Warning Status",
> + "Switch Node A Output Current Limiter Warning Status"
> +};
> +
> +static const char *pmic50x0_reg5_err_msgs[PMIC50X0_ERR_REG5_NUM_BITS] = {
> + "VOUT_1.0V LDO Output Power Good"
> +};
> +
> +struct pmic50x0_err_reg {
> + unsigned int addr;
> + unsigned int *counters;
> + const char **err_msgs;
> + u8 sbit;
> + u8 ebit;
> + u8 size;
> + u8 err_active;
> +};
> +
> +/* Main driver struct */
> +struct pmic50x0 {
> + struct device *dev;
> + struct regmap *regmap;
> + struct delayed_work work;
> + struct pmic50x0_err_reg *err_regs;
> + struct notifier_block panic_notifier;
> + long last_voltage[PMIC50X0_VOLTAGE_CHANNELS];
> + /* Mutex for reading the voltage registers */
> + struct mutex voltage_mutex;
> + /* Mutex for reading the current and power registers */
> + struct mutex curr_power_mutex;
The hwmon core already implements locking.
> +
> +};
> +
> +static unsigned int error_polling_ms = PMIC50X0_ERR_POLL_INTERVAL_MSEC;
> +module_param(error_polling_ms, uint, 0644);
> +MODULE_PARM_DESC(error_polling_ms, "PMIC error polling interval in msec, default = 1000");
This is a no-go. If some kind of error attribute/register polling is wanted,
implement it from userspace. For the kernel it is unnecessary burden and costly
for everyone not wanting/needing it.
> +
> +static ssize_t pmic50x0_err_show(struct device *dev,
> + struct device_attribute *da, char *buf)
> +{
> + struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
> + struct pmic50x0 *pmic50x0 = dev_get_drvdata(dev);
> + unsigned int idx = attr->index;
> + struct pmic50x0_error err = pmic50x0_error_reg[idx];
> + u8 reg, bit;
> +
> + reg = err.reg;
> + bit = err.bit - pmic50x0->err_regs[reg].sbit;
> +
> + return scnprintf(buf, PAGE_SIZE, "%u\n", pmic50x0->err_regs[reg].counters[bit]);
> +}
> +
> +/* Error Sysfs group */
> +PMIC50X0_DEV_ATTR(err_global_log_vin_bulk_over_vol, PMIC50X0_ERR_GLOBAL_LOG_VIN_BULK_OVER_VOL);
> +PMIC50X0_DEV_ATTR(err_global_log_crit_temp, PMIC50X0_ERR_GLOBAL_LOG_CRIT_TEMP);
> +PMIC50X0_DEV_ATTR(err_global_log_buck_ov_or_uv, PMIC50X0_ERR_GLOBAL_LOG_BUCK_OV_OR_UV);
> +PMIC50X0_DEV_ATTR(err_vin_bulk_input_over_vol_stat, PMIC50X0_ERR_VIN_BULK_INPUT_OVER_VOL_STAT);
> +PMIC50X0_DEV_ATTR(err_vin_mgmt_input_over_vol_stat, PMIC50X0_ERR_VIN_MGMT_INPUT_OVER_VOL_STAT);
> +PMIC50X0_DEV_ATTR(err_swd_out_pow_good_stat, PMIC50X0_ERR_SWD_OUT_POW_GOOD_STAT);
> +PMIC50X0_DEV_ATTR(err_swc_out_pow_good_stat, PMIC50X0_ERR_SWC_OUT_POW_GOOD_STAT);
> +PMIC50X0_DEV_ATTR(err_swb_out_pow_good_stat, PMIC50X0_ERR_SWB_OUT_POW_GOOD_STAT);
> +PMIC50X0_DEV_ATTR(err_swa_out_pow_good_stat, PMIC50X0_ERR_SWA_OUT_POW_GOOD_STAT);
> +PMIC50X0_DEV_ATTR(err_crit_temp_shutdown_stat, PMIC50X0_ERR_CRIT_TEMP_SHUTDOWN_STAT);
> +PMIC50X0_DEV_ATTR(err_vin_bulk_input_pow_good_stat, PMIC50X0_ERR_VIN_BULK_INPUT_POW_GOOD_STAT);
> +PMIC50X0_DEV_ATTR(err_swd_high_out_curr_consump_stat, PMIC50X0_ERR_SWD_HIGH_OUT_CURR_CONSUMP_STAT);
> +PMIC50X0_DEV_ATTR(err_swc_high_out_curr_consump_stat, PMIC50X0_ERR_SWC_HIGH_OUT_CURR_CONSUMP_STAT);
> +PMIC50X0_DEV_ATTR(err_swb_high_out_curr_consump_stat, PMIC50X0_ERR_SWB_HIGH_OUT_CURR_CONSUMP_STAT);
> +PMIC50X0_DEV_ATTR(err_swa_high_out_curr_consump_stat, PMIC50X0_ERR_SWA_HIGH_OUT_CURR_CONSUMP_STAT);
> +PMIC50X0_DEV_ATTR(err_vin_mgmt_to_vin_bulk_stat, PMIC50X0_ERR_VIN_MGMT_TO_VIN_BULK_STAT);
> +PMIC50X0_DEV_ATTR(err_vout_1_8v_out_power_good_stat, PMIC50X0_ERR_VOUT_1_8V_OUT_POWER_GOOD_STAT);
> +PMIC50X0_DEV_ATTR(err_vbias_power_good_stat, PMIC50X0_ERR_VBIAS_POWER_GOOD_STAT);
> +PMIC50X0_DEV_ATTR(err_pmic_high_temp_warn_stat, PMIC50X0_ERR_PMIC_HIGH_TEMP_WARN_STAT);
> +PMIC50X0_DEV_ATTR(err_swd_out_over_vol_stat, PMIC50X0_ERR_SWD_OUT_OVER_VOL_STAT);
> +PMIC50X0_DEV_ATTR(err_swc_out_over_vol_stat, PMIC50X0_ERR_SWC_OUT_OVER_VOL_STAT);
> +PMIC50X0_DEV_ATTR(err_swb_out_over_vol_stat, PMIC50X0_ERR_SWB_OUT_OVER_VOL_STAT);
> +PMIC50X0_DEV_ATTR(err_swa_out_over_vol_stat, PMIC50X0_ERR_SWA_OUT_OVER_VOL_STAT);
> +PMIC50X0_DEV_ATTR(err_swd_out_under_vol_lockout_stat, PMIC50X0_ERR_SWD_OUT_UNDER_VOL_LOCKOUT_STAT);
> +PMIC50X0_DEV_ATTR(err_swc_out_under_vol_lockout_stat, PMIC50X0_ERR_SWC_OUT_UNDER_VOL_LOCKOUT_STAT);
> +PMIC50X0_DEV_ATTR(err_swb_out_under_vol_lockout_stat, PMIC50X0_ERR_SWB_OUT_UNDER_VOL_LOCKOUT_STAT);
> +PMIC50X0_DEV_ATTR(err_swa_out_under_vol_lockout_stat, PMIC50X0_ERR_SWA_OUT_UNDER_VOL_LOCKOUT_STAT);
> +PMIC50X0_DEV_ATTR(err_swd_out_curr_limiter_warn_stat, PMIC50X0_ERR_SWD_OUT_CURR_LIMITER_WARN_STAT);
> +PMIC50X0_DEV_ATTR(err_swc_out_curr_limiter_warn_stat, PMIC50X0_ERR_SWC_OUT_CURR_LIMITER_WARN_STAT);
> +PMIC50X0_DEV_ATTR(err_swb_out_curr_limiter_warn_stat, PMIC50X0_ERR_SWB_OUT_CURR_LIMITER_WARN_STAT);
> +PMIC50X0_DEV_ATTR(err_swa_out_curr_limiter_warn_stat, PMIC50X0_ERR_SWA_OUT_CURR_LIMITER_WARN_STAT);
> +PMIC50X0_DEV_ATTR(err_vout_1v_out_power_good_stat, PMIC50X0_ERR_VOUT_1V_OUT_POWER_GOOD_STAT);
> +
> +static struct attribute *pmic50x0_err_attrs[] = {
> + &sensor_dev_attr_err_global_log_vin_bulk_over_vol.dev_attr.attr,
> + &sensor_dev_attr_err_global_log_crit_temp.dev_attr.attr,
> + &sensor_dev_attr_err_global_log_buck_ov_or_uv.dev_attr.attr,
> + &sensor_dev_attr_err_vin_bulk_input_over_vol_stat.dev_attr.attr,
> + &sensor_dev_attr_err_vin_mgmt_input_over_vol_stat.dev_attr.attr,
> + &sensor_dev_attr_err_swd_out_pow_good_stat.dev_attr.attr,
> + &sensor_dev_attr_err_swc_out_pow_good_stat.dev_attr.attr,
> + &sensor_dev_attr_err_swb_out_pow_good_stat.dev_attr.attr,
> + &sensor_dev_attr_err_swa_out_pow_good_stat.dev_attr.attr,
> + &sensor_dev_attr_err_crit_temp_shutdown_stat.dev_attr.attr,
> + &sensor_dev_attr_err_vin_bulk_input_pow_good_stat.dev_attr.attr,
> + &sensor_dev_attr_err_swd_high_out_curr_consump_stat.dev_attr.attr,
> + &sensor_dev_attr_err_swc_high_out_curr_consump_stat.dev_attr.attr,
> + &sensor_dev_attr_err_swb_high_out_curr_consump_stat.dev_attr.attr,
> + &sensor_dev_attr_err_swa_high_out_curr_consump_stat.dev_attr.attr,
> + &sensor_dev_attr_err_vin_mgmt_to_vin_bulk_stat.dev_attr.attr,
> + &sensor_dev_attr_err_vout_1_8v_out_power_good_stat.dev_attr.attr,
> + &sensor_dev_attr_err_vbias_power_good_stat.dev_attr.attr,
> + &sensor_dev_attr_err_pmic_high_temp_warn_stat.dev_attr.attr,
> + &sensor_dev_attr_err_swd_out_over_vol_stat.dev_attr.attr,
> + &sensor_dev_attr_err_swc_out_over_vol_stat.dev_attr.attr,
> + &sensor_dev_attr_err_swb_out_over_vol_stat.dev_attr.attr,
> + &sensor_dev_attr_err_swa_out_over_vol_stat.dev_attr.attr,
> + &sensor_dev_attr_err_swd_out_under_vol_lockout_stat.dev_attr.attr,
> + &sensor_dev_attr_err_swc_out_under_vol_lockout_stat.dev_attr.attr,
> + &sensor_dev_attr_err_swb_out_under_vol_lockout_stat.dev_attr.attr,
> + &sensor_dev_attr_err_swa_out_under_vol_lockout_stat.dev_attr.attr,
> + &sensor_dev_attr_err_swd_out_curr_limiter_warn_stat.dev_attr.attr,
> + &sensor_dev_attr_err_swc_out_curr_limiter_warn_stat.dev_attr.attr,
> + &sensor_dev_attr_err_swb_out_curr_limiter_warn_stat.dev_attr.attr,
> + &sensor_dev_attr_err_swa_out_curr_limiter_warn_stat.dev_attr.attr,
> + &sensor_dev_attr_err_vout_1v_out_power_good_stat.dev_attr.attr,
> + NULL,
> +};
> +
> +ATTRIBUTE_GROUPS(pmic50x0_err);
Not acceptable as hardware monitoring attributes. Follow the ABI;
it supports various alarm attributes. For those not covered by
the ABI, implement using debugfs.
> +
> +static struct pmic50x0_err_reg pmic50x0_err_regs[] = {
> + {
> + .addr = PMIC50X0_ERR_REG0_ADDR,
> + .err_msgs = pmic50x0_reg0_err_msgs,
> + .sbit = PMIC50X0_ERR_REG0_SBIT,
> + .ebit = PMIC50X0_ERR_REG0_EBIT,
> + .size = PMIC50X0_ERR_REG0_NUM_BITS,
> + },
> + {
> + .addr = PMIC50X0_ERR_REG1_ADDR,
> + .err_msgs = pmic50x0_reg1_err_msgs,
> + .sbit = PMIC50X0_ERR_REG1_SBIT,
> + .ebit = PMIC50X0_ERR_REG1_EBIT,
> + .size = PMIC50X0_ERR_REG1_NUM_BITS,
> + },
> + {
> + .addr = PMIC50X0_ERR_REG2_ADDR,
> + .err_msgs = pmic50x0_reg2_err_msgs,
> + .sbit = PMIC50X0_ERR_REG2_SBIT,
> + .ebit = PMIC50X0_ERR_REG2_EBIT,
> + .size = PMIC50X0_ERR_REG2_NUM_BITS,
> + },
> + {
> + .addr = PMIC50X0_ERR_REG3_ADDR,
> + .err_msgs = pmic50x0_reg3_err_msgs,
> + .sbit = PMIC50X0_ERR_REG3_SBIT,
> + .ebit = PMIC50X0_ERR_REG3_EBIT,
> + .size = PMIC50X0_ERR_REG3_NUM_BITS,
> + },
> + {
> + .addr = PMIC50X0_ERR_REG4_ADDR,
> + .err_msgs = pmic50x0_reg4_err_msgs,
> + .sbit = PMIC50X0_ERR_REG4_SBIT,
> + .ebit = PMIC50X0_ERR_REG4_EBIT,
> + .size = PMIC50X0_ERR_REG4_NUM_BITS,
> + },
> + {
> + .addr = PMIC50X0_ERR_REG5_ADDR,
> + .err_msgs = pmic50x0_reg5_err_msgs,
> + .sbit = PMIC50X0_ERR_REG5_SBIT,
> + .ebit = PMIC50X0_ERR_REG5_EBIT,
> + .size = PMIC50X0_ERR_REG5_NUM_BITS,
> + },
> +};
> +
> +static int pmic50x0_temp_read(struct device *dev, long *val)
> +{
> + struct pmic50x0 *pmic50x0 = dev_get_drvdata(dev);
> + unsigned int regval;
> + long temp;
> + int err;
> +
> + err = regmap_read(pmic50x0->regmap, PMIC50X0_REG_TEMP, ®val);
> + if (err < 0)
> + return err;
> +
> + temp = FIELD_GET(PMIC50X0_TEMP_MASK, regval) * PMIC50X0_TEMP_FACTOR;
> + *val = MILLIDEGREE_PER_DEGREE * (PMIC50X0_TEMP_BASE + temp);
> +
> + return 0;
> +}
> +
> +static int pmic50x0_in_read(struct device *dev, long *val, int channel)
> +{
> + struct pmic50x0 *pmic50x0 = dev_get_drvdata(dev);
> + unsigned int regval;
> + int err;
> +
> + mutex_lock(&pmic50x0->voltage_mutex);
> +
> + /* Select the node */
> + err = regmap_update_bits(pmic50x0->regmap, PMIC50X0_REG_VOL_NODE_SELECT,
> + PMIC50X0_VOL_SELECT_MASK, channel << PMIC50X0_VOL_OFFSET);
> + if (err < 0)
> + goto ret_unlock;
> +
> + /* The spec requires 9ms delay between the node selection and the reading */
> + msleep(PMIC50X0_VOL_SELECT_DELAY);
Use usleep_range() or better fsleep().
> +
> + /* Read the voltage register after selecting the node */
> + err = regmap_read(pmic50x0->regmap, PMIC50X0_REG_VOL, ®val);
> + if (err < 0)
> + goto ret_unlock;
> +
> + *val = FIELD_GET(PMIC50X0_VOL_MASK, regval) * PMIC50X0_VOL_FACTOR;
> + pmic50x0->last_voltage[channel] = *val;
> +
> +ret_unlock:
> + mutex_unlock(&pmic50x0->voltage_mutex);
> +
> + return err;
> +}
> +
> +static int pmic50x0_curr_power_read(struct device *dev, long *val, int channel,
> + enum pmic50x0_select_current_power node)
> +{
> + struct pmic50x0 *pmic50x0 = dev_get_drvdata(dev);
> + unsigned int regval, reg;
> + long mask;
> + int err;
> +
> + mutex_lock(&pmic50x0->curr_power_mutex);
> +
> + /* Select power/current mode */
> + err = regmap_update_bits(pmic50x0->regmap, PMIC50X0_REG_CURR_POW_SELECT,
> + PMIC50X0_CURR_POW_SELECT, node << PMIC50X0_CURR_POW_SELECT_OFFSET);
> + if (err < 0)
> + goto ret_unlock;
> +
> + switch (channel) {
> + case PMIC50X0_CURR_POW_NODE_A:
> + mask = PMIC50X0_CURR_POW_A_MASK;
> + reg = PMIC50X0_REG_CURR_POW;
> +
> + /* Select node A */
> + err = regmap_update_bits(pmic50x0->regmap, PMIC50X0_REG_NODE_A_SUM_SELECT,
> + PMIC50X0_CURR_POW_A_SUM_SELECT, PMIC50X0_SELECT_POWER_A);
> + if (err < 0)
> + goto ret_unlock;
> + break;
> + case PMIC50X0_CURR_POW_NODE_B:
> + case PMIC50X0_CURR_POW_NODE_C:
> + case PMIC50X0_CURR_POW_NODE_D:
> + mask = PMIC50X0_CURR_POW_BCD_MASK;
> + reg = PMIC50X0_REG_CURR_POW + channel;
> + break;
> + case PMIC50X0_CURR_POW_SUM:
> + mask = PMIC50X0_CURR_POW_SUM_MASK;
> + reg = PMIC50X0_REG_CURR_POW;
> +
> + /* Select the sum of A,B,C and D nodes */
> + err = regmap_update_bits(pmic50x0->regmap, PMIC50X0_REG_NODE_A_SUM_SELECT,
> + PMIC50X0_CURR_POW_A_SUM_SELECT, PMIC50X0_SELECT_POWER_SUM);
> + if (err < 0)
> + goto ret_unlock;
> + break;
> + default:
> + err = -EOPNOTSUPP;
> + goto ret_unlock;
> + }
> +
> + err = regmap_read(pmic50x0->regmap, reg, ®val);
> + if (err < 0)
> + goto ret_unlock;
> +
> + *val = (regval & mask) * PMIC50X0_CURR_POWER_FACTOR;
Power is reported in uW per ABI. The registers report power in steps of 125mW.
PMIC50X0_CURR_POWER_FACTOR is 125, so this calculation seems off by a factor
of 1,000 for power readings.
> +
> +ret_unlock:
> + mutex_unlock(&pmic50x0->curr_power_mutex);
> +
> + return err;
> +}
> +
> +static int pmic50x0_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
> + long *val)
> +{
> + switch (type) {
> + case hwmon_temp:
> + if (attr != hwmon_temp_input)
> + return -EOPNOTSUPP;
> + return pmic50x0_temp_read(dev, val);
> + case hwmon_in:
> + if (attr != hwmon_in_input)
> + return -EOPNOTSUPP;
> + return pmic50x0_in_read(dev, val, channel);
> + case hwmon_curr:
> + if (attr != hwmon_curr_input)
> + return -EOPNOTSUPP;
> + return pmic50x0_curr_power_read(dev, val, channel, PMIC50X0_SELECT_CURRENT);
> + case hwmon_power:
> + if (attr != hwmon_power_input)
> + return -EOPNOTSUPP;
> + return pmic50x0_curr_power_read(dev, val, channel, PMIC50X0_SELECT_POWER);
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static umode_t pmic50x0_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr,
> + int channel)
> +{
> + switch (type) {
> + case hwmon_temp:
> + return (attr == hwmon_temp_input) ? 0444 : 0;
> + case hwmon_in:
> + return (attr == hwmon_in_input) ? 0444 : 0;
> + case hwmon_curr:
> + return (attr == hwmon_curr_input) ? 0444 : 0;
> + case hwmon_power:
> + return (attr == hwmon_power_input) ? 0444 : 0;
> + default:
> + return 0;
> + }
> +}
> +
> +static const u32 pmic50x0_temp_config[] = {
> + HWMON_T_INPUT,
> + 0
> +};
> +
> +static const struct hwmon_channel_info pmic50x0_temp = {
> + .type = hwmon_temp,
> + .config = pmic50x0_temp_config,
> +};
> +
> +static const u32 pmic50x0_in_config[] = {
> + HWMON_I_INPUT,
> + HWMON_I_INPUT,
> + HWMON_I_INPUT,
> + HWMON_I_INPUT,
The chips support measuring a total of 9 different voltages.
Why only support SWA..SWD ?
> + 0
> +};
> +
> +static const struct hwmon_channel_info pmic50x0_in = {
> + .type = hwmon_in,
> + .config = pmic50x0_in_config,
> +};
> +
> +static const u32 pmic50x0_curr_config[] = {
> + HWMON_C_INPUT,
> + HWMON_C_INPUT,
> + HWMON_C_INPUT,
> + HWMON_C_INPUT,
> + 0
> +};
> +
> +static const struct hwmon_channel_info pmic50x0_curr = {
> + .type = hwmon_curr,
> + .config = pmic50x0_curr_config,
> +};
> +
> +static const u32 pmic50x0_power_config[] = {
> + HWMON_P_INPUT,
> + HWMON_P_INPUT,
> + HWMON_P_INPUT,
> + HWMON_P_INPUT,
> + HWMON_P_INPUT,
> + 0
> +};
> +
> +static const struct hwmon_channel_info pmic50x0_power = {
> + .type = hwmon_power,
> + .config = pmic50x0_power_config,
> +};
> +
> +static const struct hwmon_channel_info *pmic50x0_info[] = {
> + &pmic50x0_temp,
> + &pmic50x0_in,
> + &pmic50x0_curr,
> + &pmic50x0_power,
> + NULL
> +};
Use the HWMON_CHANNEL_INFO() macro.
Why are limits not supported ?
> +
> +static const struct hwmon_ops pmic50x0_ops = {
> + .is_visible = pmic50x0_is_visible,
> + .read = pmic50x0_read,
> +};
> +
> +static const struct hwmon_chip_info pmic50x0_chip_info = {
> + .ops = &pmic50x0_ops,
> + .info = pmic50x0_info
> +};
> +
> +static const struct regmap_config pmic50x0_regmap_config = {
> + .reg_bits = PMIC50X0_REG_BITS,
> + .val_bits = PMIC50X0_VAL_BITS,
> + .max_register = PMIC50X0_MAX_REG_ADDR
> +};
> +
> +static void pmic50x0_update_last_volt(struct pmic50x0 *pmic50x0)
> +{
> + struct device *dev = pmic50x0->dev;
> + int i, err;
> +
> + for (i = 0; i < PMIC50X0_VOLTAGE_CHANNELS; i++) {
> + err = pmic50x0_in_read(dev, &pmic50x0->last_voltage[i], i);
> + if (err < 0)
> + dev_err(dev, "Failed to read voltage (%d)\n", err);
> + }
> +}
Not acceptable. Implement history like this from userspace.
> +
> +static void pmic50x0_work_callback(struct work_struct *work)
> +{
> + struct delayed_work *delayed_work = to_delayed_work(work);
> + struct pmic50x0 *pmic50x0 = container_of(delayed_work, struct pmic50x0, work);
> + struct pmic50x0_err_reg *reg;
> + u8 bit, sbit, ebit, i, idx;
> + unsigned int err_reg;
> + int err;
> +
> + pmic50x0_update_last_volt(pmic50x0);
> +
> + for (i = 0; i < ARRAY_SIZE(pmic50x0_err_regs); i++) {
> + reg = &pmic50x0->err_regs[i];
> + err = regmap_read(pmic50x0->regmap, reg->addr, &err_reg);
> + if (err < 0) {
> + dev_err(pmic50x0->dev, "Could not read Error Register at %x\n", reg->addr);
> + continue;
> + }
> +
> + /* Checks if error bits changed since last polling interval */
> + if (err_reg == reg->err_active)
> + continue;
> +
> + sbit = reg->sbit;
> + ebit = reg->ebit;
> +
> + for (bit = sbit; bit <= ebit; bit++) {
> + idx = bit - sbit;
> +
> + if (err_reg & BIT(bit)) {
> + /* Continue if error is not new */
> + if (reg->err_active & BIT(bit))
> + continue;
> +
> + /* Mark the error bit as active */
> + reg->err_active |= BIT(bit);
> + reg->counters[idx]++;
> +
> + dev_err(pmic50x0->dev, "%s (cnt=%u)\n", reg->err_msgs[idx],
> + reg->counters[idx]);
> + } else if (reg->err_active & BIT(bit)) {
> + /* Error cleared since last polling interval. */
> + dev_info(pmic50x0->dev, "Error (%s) cleared\n", reg->err_msgs[idx]);
> +
> + reg->err_active &= ~BIT(bit);
> + }
> + }
> + }
> +
> + schedule_delayed_work(&pmic50x0->work, msecs_to_jiffies(error_polling_ms));
> +}
> +
> +static void pmic50x0_cancel_work(void *work)
> +{
> + cancel_delayed_work_sync(work);
> +}
> +
> +static int pmic50x0_work_init(struct device *dev)
> +{
> + struct pmic50x0 *pmic50x0 = dev_get_drvdata(dev);
> +
> + INIT_DELAYED_WORK(&pmic50x0->work, pmic50x0_work_callback);
> + schedule_delayed_work(&pmic50x0->work, msecs_to_jiffies(error_polling_ms));
> +
> + return devm_add_action_or_reset(dev, pmic50x0_cancel_work, &pmic50x0->work);
> +}
As said before, this is unacceptable. Implement such tracking from userspace.
> +
> +static int pmic50x0_error_init(struct device *dev)
> +{
> + struct pmic50x0 *pmic50x0 = dev_get_drvdata(dev);
> + int reg;
> +
> + pmic50x0->err_regs = devm_kcalloc(dev, ARRAY_SIZE(pmic50x0_err_regs),
> + sizeof(*pmic50x0->err_regs), GFP_KERNEL);
> + if (!pmic50x0->err_regs)
> + return -ENOMEM;
> +
> + for (reg = 0; reg < ARRAY_SIZE(pmic50x0_err_regs); reg++) {
> + pmic50x0->err_regs[reg] = pmic50x0_err_regs[reg];
> + pmic50x0->err_regs[reg].counters =
> + devm_kcalloc(dev, pmic50x0->err_regs[reg].size,
> + sizeof(*pmic50x0->err_regs[reg].counters), GFP_KERNEL);
> + if (!pmic50x0->err_regs[reg].counters)
> + return -ENOMEM;
> + }
> +
> + return 0;
> +}
> +
> +static void pmic50x0_mutexes_destroy(void *arg)
> +{
> + struct pmic50x0 *pmic50x0 = arg;
> +
> + mutex_destroy(&pmic50x0->curr_power_mutex);
> + mutex_destroy(&pmic50x0->voltage_mutex);
> +}
> +
> +static int pmic50x0_mutexes_init(struct pmic50x0 *pmic50x0)
> +{
> + mutex_init(&pmic50x0->voltage_mutex);
> + mutex_init(&pmic50x0->curr_power_mutex);
> +
> + return devm_add_action_or_reset(pmic50x0->dev, pmic50x0_mutexes_destroy, pmic50x0);
> +}
Those mutexes are unnecessary.
> +
> +static int pmic50x0_panic_callback(struct notifier_block *nb,
> + unsigned long action, void *data)
> +{
> + struct pmic50x0 *pmic50x0 = container_of(nb, struct pmic50x0, panic_notifier);
> +
> + dev_emerg(pmic50x0->dev, "volt(mV): A=%ld, B=%ld, C=%ld, D=%ld\n",
> + pmic50x0->last_voltage[0], pmic50x0->last_voltage[1],
> + pmic50x0->last_voltage[2], pmic50x0->last_voltage[3]);
> +
> + return NOTIFY_DONE;
> +}
This is a no-go. If you want to keep track of historic voltages, do it from userspace,
and I don't want to see such panic notifiers in hardware monitoring drivers.
> +
> +static void pmic50x0_panic_notifier_unregister(void *data)
> +{
> + struct pmic50x0 *pmic50x0 = data;
> +
> + atomic_notifier_chain_unregister(&panic_notifier_list, &pmic50x0->panic_notifier);
> +}
> +
> +static int pmic50x0_panic_notifier_register(struct pmic50x0 *pmic50x0)
> +{
> + struct notifier_block *panic_notifier = &pmic50x0->panic_notifier;
> + struct device *dev = pmic50x0->dev;
> + int ret;
> +
> + panic_notifier->notifier_call = pmic50x0_panic_callback;
> + panic_notifier->priority = 0;
> +
> + ret = atomic_notifier_chain_register(&panic_notifier_list, panic_notifier);
> + if (ret) {
> + dev_err(dev, "failed to register panic notifier (%d)\n", ret);
> + return ret;
> + }
> +
> + return devm_add_action_or_reset(dev, pmic50x0_panic_notifier_unregister, pmic50x0);
> +}
> +
> +static int pmic50x0_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct pmic50x0 *pmic50x0;
> + struct device *hwmon_dev;
> + int err;
> +
> + pmic50x0 = devm_kzalloc(dev, sizeof(*pmic50x0), GFP_KERNEL);
> + if (!pmic50x0)
> + return -ENOMEM;
> +
> + dev_set_drvdata(dev, pmic50x0);
> +
> + pmic50x0->dev = dev;
> + pmic50x0->regmap = devm_regmap_init_i2c(client, &pmic50x0_regmap_config);
> + if (IS_ERR(pmic50x0->regmap)) {
> + dev_err(dev, "init regmap failed!\n");
> + return PTR_ERR(pmic50x0->regmap);
Use dev_err_probe().
> + }
> +
> + err = pmic50x0_error_init(dev);
> + if (err < 0)
> + return err;
> +
> + err = pmic50x0_mutexes_init(pmic50x0);
> + if (err < 0)
> + return err;
> +
> + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, pmic50x0,
> + &pmic50x0_chip_info, pmic50x0_err_groups);
> + if (IS_ERR(hwmon_dev))
> + return PTR_ERR(hwmon_dev);
> +
> + err = pmic50x0_panic_notifier_register(pmic50x0);
> + if (err < 0)
> + return err;
> +
> + return pmic50x0_work_init(dev);
> +}
> +
> +static const struct i2c_device_id pmic50x0_ids[] = {
> + { PMIC50X0_DRIVER_NAME },
> + { }
> +};
> +
> +MODULE_DEVICE_TABLE(i2c, pmic50x0_ids);
> +
> +static const struct of_device_id pmic50x0_of_match[] = {
> + { .compatible = "jedec,pmic50x0" },
> + { },
> +};
> +
> +MODULE_DEVICE_TABLE(of, pmic50x0_of_match);
> +
> +static struct i2c_driver pmic50x0_driver = {
> + .class = I2C_CLASS_HWMON,
There is no detect function, so this is pointless.
> + .driver = {
> + .name = PMIC50X0_DRIVER_NAME,
> + .of_match_table = pmic50x0_of_match,
> + },
> + .probe = pmic50x0_probe,
> + .id_table = pmic50x0_ids,
> +};
> +
> +module_i2c_driver(pmic50x0_driver);
> +
> +MODULE_AUTHOR("Almog Ben Shaul <almogbs@amazon.com>");
> +MODULE_DESCRIPTION("JEDEC PMIC50x0 Hardware Monitoring Driver");
> +MODULE_LICENSE("GPL");
On Wed, Jan 21, 2026 at 03:19:47PM +0000, Almog Ben Shaul wrote:
> Add hardware monitoring driver for JEDEC PMIC50x0 compliant I2C DDR5
> PMICs.
>
> The driver provides monitoring for voltage, current, power, and
> temperature across multiple channels, along with comprehensive error
> reporting.
>
> Signed-off-by: Almog Ben Shaul <almogbs@amazon.com>
> Tested-by: Almog Ben Shaul <almogbs@amazon.com>
Drop. You cannot send it untested, read submitting patches. The tag is
for OTHER people to test, not create impression that all code is
untested but here you did some testing.
> ---
...
> +
> +static int pmic50x0_panic_callback(struct notifier_block *nb,
> + unsigned long action, void *data)
> +{
> + struct pmic50x0 *pmic50x0 = container_of(nb, struct pmic50x0, panic_notifier);
> +
> + dev_emerg(pmic50x0->dev, "volt(mV): A=%ld, B=%ld, C=%ld, D=%ld\n",
> + pmic50x0->last_voltage[0], pmic50x0->last_voltage[1],
> + pmic50x0->last_voltage[2], pmic50x0->last_voltage[3]);
You did not Cc any maintainers, so maybe one of your 5 CCs from Amazon
can explain me why exactly random hwmon driver should receive panic
callbacks? Do you see such pattern in hwmon?
> +
> + return NOTIFY_DONE;
> +}
> +
> +static void pmic50x0_panic_notifier_unregister(void *data)
> +{
> + struct pmic50x0 *pmic50x0 = data;
> +
> + atomic_notifier_chain_unregister(&panic_notifier_list, &pmic50x0->panic_notifier);
> +}
> +
> +static int pmic50x0_panic_notifier_register(struct pmic50x0 *pmic50x0)
> +{
> + struct notifier_block *panic_notifier = &pmic50x0->panic_notifier;
> + struct device *dev = pmic50x0->dev;
> + int ret;
> +
> + panic_notifier->notifier_call = pmic50x0_panic_callback;
> + panic_notifier->priority = 0;
> +
> + ret = atomic_notifier_chain_register(&panic_notifier_list, panic_notifier);
> + if (ret) {
> + dev_err(dev, "failed to register panic notifier (%d)\n", ret);
> + return ret;
> + }
> +
> + return devm_add_action_or_reset(dev, pmic50x0_panic_notifier_unregister, pmic50x0);
> +}
Best regards,
Krzysztof
© 2016 - 2026 Red Hat, Inc.