From: Almog Ben Shaul <almogbs@amazon.com>
To: <linux-hwmon@vger.kernel.org>
Cc: <devicetree@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
<linux-doc@vger.kernel.org>, <itamark@amazon.com>,
<talel@amazon.com>, <farbere@amazon.com>, <ayalstei@amazon.com>,
<dwmw@amazon.com>, <almogbs@amazon.com>
Subject: [PATCH 2/2] hwmon: Add JEDEC PMIC50x0 driver
Date: Wed, 21 Jan 2026 15:19:47 +0000 [thread overview]
Message-ID: <20260121151947.37719-3-almogbs@amazon.com> (raw)
In-Reply-To: <20260121151947.37719-1-almogbs@amazon.com>
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
next prev parent reply other threads:[~2026-01-21 15:20 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-01-21 15:19 [PATCH 0/2] hwmon: Add JEDEC PMIC50x0 driver for DDR5 PMICs Almog Ben Shaul
2026-01-21 15:19 ` [PATCH 1/2] dt-bindings: trivial-devices: Add jedec,pmic50x0 Almog Ben Shaul
2026-01-22 8:10 ` Krzysztof Kozlowski
2026-01-27 17:12 ` Guenter Roeck
2026-01-21 15:19 ` Almog Ben Shaul [this message]
2026-01-22 8:13 ` [PATCH 2/2] hwmon: Add JEDEC PMIC50x0 driver Krzysztof Kozlowski
2026-01-27 0:08 ` Guenter Roeck
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260121151947.37719-3-almogbs@amazon.com \
--to=almogbs@amazon.com \
--cc=ayalstei@amazon.com \
--cc=devicetree@vger.kernel.org \
--cc=dwmw@amazon.com \
--cc=farbere@amazon.com \
--cc=itamark@amazon.com \
--cc=linux-doc@vger.kernel.org \
--cc=linux-hwmon@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=talel@amazon.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox