* [PATCH v7 0/4] hwmon: pmbus: add MPS MPQ8646 support
@ 2026-07-30 15:43 Vincent Jardin via B4 Relay
2026-07-30 15:43 ` [PATCH v7 1/4] hwmon: (pmbus/core) export pmbus_check_and_notify_faults() Vincent Jardin via B4 Relay
` (3 more replies)
0 siblings, 4 replies; 10+ messages in thread
From: Vincent Jardin via B4 Relay @ 2026-07-30 15:43 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet, Shuah Khan, Charles Hsu
Cc: Markus Elfring, linux-hwmon, linux-kernel, devicetree, linux-doc,
Vincent Jardin
Add support for the Monolithic Power Systems MPQ8646 step-down
converter as a PMBus device.
1/4 pmbus core: export pmbus_check_and_notify_faults() so a chip
driver can run the SMBALERT# path check and notify sequence
from a polling work item when the chip's SMBALERT# pin is
unavailable to the CPU.
2/4 dt-bindings: add the MPQ8646 compatible to the existing
mps,mpq8785 schema (same mps,vout-fb-divider-ratio-permille
property, same constraints).
3/4 the MPQ8646 driver: PMBus telemetry
4/4 the provisioning/NVM write entries, compiled out by default
thanks to SENSORS_MPQ8646_DEBUG_UNSAFE.
To: Guenter Roeck <linux@roeck-us.net>
To: Rob Herring <robh@kernel.org>
To: Krzysztof Kozlowski <krzk+dt@kernel.org>
To: Conor Dooley <conor+dt@kernel.org>
To: Jonathan Corbet <corbet@lwn.net>
To: Shuah Khan <skhan@linuxfoundation.org>
To: Charles Hsu <ythsu0511@gmail.com>
Cc: Markus Elfring <Markus.Elfring@web.de>
Cc: linux-hwmon@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: devicetree@vger.kernel.org
Cc: linux-doc@vger.kernel.org
Signed-off-by: Vincent Jardin <vjardin@free.fr>
Changes in v7:
- driver: named initializer for the i2c_device_id table (reported by
the Sashiko AI, confirmed by Guenter Roeck it is a real issue)
- add pmbus_lock in every debugfs accessor that touches, so reads cannot
race the pmbus core and no write can slip through while
clear_protection_last_force temporarily lowers WRITE_PROTECT
(reported by the Sashiko AI, confirmed by Guenter Roeck)
- propagate the i2c error codes from the debugfs write handlers instead
of returning success
- explicit Kconfig and documentation warnings, and add a WARNING banner
at probing of the device. (requested by Guenter Roeck)
- reword some commit logs (reported by Markus Elfring)
- Link to v6: https://lore.kernel.org/r/20260729-mpq8646_v0-v6-0-98848151cdb4@free.fr
Changes in v6:
- core: replace the pmbus_get_hwmon_device() accessor with an exported
pmbus_check_and_notify_faults() (suggested by Guenter Roeck on v5)
- driver: drop the bring-up probe/instrumentation debugfs entries
(challenged by Guenter Roeck on v4)
- new patch 4/4: move the write/provisioning debugfs entries
behind a new default-n SENSORS_MPQ8646_DEBUG_UNSAFE
- move the debugfs files into the client's pmbus debugfs directory
(pmbus_get_debugfs_dir()), mask using VOUT_MODE checks, MPQ8646_DEBUG()
using dev_dbg(), drop the 0xFF VOUT_MODE probe check and the
source-attribution comments (many inline reviews reported
by Guenter Roeck on v5)
- driver: drop the WRITE_PROTECT read shim: the HW implements the
register (reads back 0x00, unprotected), so the core handles write
protection as designed (reported by Guenter Roeck on v5)
- dt-bindings: fold the MPQ8646 into the existing mps,mpq8785.yaml
(suggested by Krzysztof Kozlowski on v5)
- scope locking, guard() and scoped_guard() (suggested by Markus
Elfring on v5)
- XXX TBD: should pmbus_check_and_notify_faults() also update
the pmbus registered thermal zones, or is that follow-up material ?
Currently, neither the SMBALERT# path nor this helper does it.
- Link to v5: https://lore.kernel.org/r/20260724-mpq8646_v0-v5-0-5915b5ae9aa2@free.fr
Changes in v5:
- driver: fix the hwmon channel indices used by the in*_alarm poll
notifications (thanks Sashiko AI, v4)
- driver: remove the shared debugfs root on module unload, by creating
it in module_init() and removing it in module_exit(). It also drops
the v4 per-probe root mutex and debugfs_lookup()
(Sashiko AI review of v4)
- doc: add Documentation/hwmon/mpq8646.rst to the toctree (kernel test robot)
- doc: fix a malformed table and drop the stale probe_page_write row
(removed since v3) and fix typo
- Link to v4: https://lore.kernel.org/r/20260723-mpq8646_v0-v4-0-b6323d5fc524@free.fr
Changes in v4 (all reported by the Sashiko AI review on v3):
- driver: register debugfs only after a successful pmbus_do_probe() --
the debugfs handlers take pmbus_lock() and rely on the pmbus clientdata
that pmbus_do_probe() installs (NULL-deref window in v3)
- driver: in remove(), unregister debugfs before cancel_delayed_work_sync()
so a debugfs write cannot re-arm the poll worker after it is cancelled
- driver: serialise the shared debugfs root create against concurrent
probes with a static mutex
- driver: move the remaining last_probe_* updates (clear_protection_last,
store_all, restore_all) inside mps_lock
- dt-bindings: describe the hardware, not the driver, in the description
- Link to v3: https://lore.kernel.org/r/20260723-mpq8646_v0-v3-0-1af1f3d38848@free.fr
Changes in v3 (all reported by the Sashiko AI review on v2):
- driver: alarm_poll_interval_ms is now a debugfs fops
- driver: pmbus_lock() around the async raw i2c accesses: the poll
worker and the CLEAR_LAST_FAULT force sequence, the nvmem snapshot read
keeps mps_lock (read-only, no chip-state change)
- driver: update last_probe_rc/last_probe_data under mps_lock to avoid
torn diagnostics
- driver: remove the dead PMBUS_PAGE swallow in write_byte
- driver: remove the probe_page_write debugfs hook
- Link to v2: https://lore.kernel.org/r/20260723-mpq8646_v0-v2-0-3c4cb71f23c0@free.fr
Changes in v2:
- driver: register debugfs only after the DT-property validation, to
avoid an early probe error to avoid dangling debugfs entries
- driver: do not schedule the alarm-poll worker when SMBALERT# (irq)
is wired, and do not re-arm it from the worker in that case
- driver: dput() the dentry returned by debugfs_lookup()
- driver: hold mps_lock around the nvmem snapshot reads
- driver: fix VID coefficients comment
- dt-bindings: fix typo of the commit message
- Link to v1: https://lore.kernel.org/r/20260723-mpq8646_v0-v1-0-14363c75916f@free.fr
---
Vincent Jardin (4):
hwmon: (pmbus/core) export pmbus_check_and_notify_faults()
dt-bindings: hwmon: pmbus: add MPS MPQ8646
hwmon: pmbus: add MPQ8646 driver
hwmon: pmbus: mpq8646: gate the writes
.../bindings/hwmon/pmbus/mps,mpq8785.yaml | 5 +-
Documentation/hwmon/index.rst | 1 +
Documentation/hwmon/mpq8646.rst | 300 +++++++
MAINTAINERS | 7 +
drivers/hwmon/pmbus/Kconfig | 31 +
drivers/hwmon/pmbus/Makefile | 1 +
drivers/hwmon/pmbus/mpq8646.c | 976 +++++++++++++++++++++
drivers/hwmon/pmbus/pmbus.h | 1 +
drivers/hwmon/pmbus/pmbus_core.c | 14 +-
9 files changed, 1332 insertions(+), 4 deletions(-)
---
base-commit: 248951ddc14de84de3910f9b13f51491a8cd91df
change-id: 20260723-mpq8646_v0-3383cb574d7a
Best regards,
--
Vincent Jardin <vjardin@free.fr>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v7 1/4] hwmon: (pmbus/core) export pmbus_check_and_notify_faults()
2026-07-30 15:43 [PATCH v7 0/4] hwmon: pmbus: add MPS MPQ8646 support Vincent Jardin via B4 Relay
@ 2026-07-30 15:43 ` Vincent Jardin via B4 Relay
2026-07-30 15:51 ` sashiko-bot
2026-07-30 15:44 ` [PATCH v7 2/4] dt-bindings: hwmon: pmbus: add MPS MPQ8646 Vincent Jardin via B4 Relay
` (2 subsequent siblings)
3 siblings, 1 reply; 10+ messages in thread
From: Vincent Jardin via B4 Relay @ 2026-07-30 15:43 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet, Shuah Khan, Charles Hsu
Cc: Markus Elfring, linux-hwmon, linux-kernel, devicetree, linux-doc,
Vincent Jardin
From: Vincent Jardin <vjardin@free.fr>
Factor of pmbus_fault_handler() into an exported helper so
drivers can notify sequence from a polling work item on boards
for which the chip's SMBALERT# pin is not wired to the
CPU. The interrupt handler becomes a thin wrapper.
Like the SMBALERT# path, the helper notifies and then clears the
latched faults unconditionally, so a polling caller inherits exactly
the interrupt semantics.
Signed-off-by: Vincent Jardin <vjardin@free.fr>
---
drivers/hwmon/pmbus/pmbus.h | 1 +
drivers/hwmon/pmbus/pmbus_core.c | 14 +++++++++++---
2 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h
index 23e3eda58870..1504d8b9e34c 100644
--- a/drivers/hwmon/pmbus/pmbus.h
+++ b/drivers/hwmon/pmbus/pmbus.h
@@ -560,6 +560,7 @@ int pmbus_write_byte_data(struct i2c_client *client, int page, u8 reg,
int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg,
u8 mask, u8 value);
void pmbus_clear_faults(struct i2c_client *client);
+void pmbus_check_and_notify_faults(struct i2c_client *client);
bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg);
bool pmbus_check_word_register(struct i2c_client *client, int page, int reg);
int pmbus_do_probe(struct i2c_client *client, struct pmbus_driver_info *info);
diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c
index 3143b9e0316c..c52052b30bc8 100644
--- a/drivers/hwmon/pmbus/pmbus_core.c
+++ b/drivers/hwmon/pmbus/pmbus_core.c
@@ -3437,10 +3437,9 @@ static int pmbus_write_smbalert_mask(struct i2c_client *client, u8 page, u8 reg,
return ret;
}
-static irqreturn_t pmbus_fault_handler(int irq, void *pdata)
+void pmbus_check_and_notify_faults(struct i2c_client *client)
{
- struct pmbus_data *data = pdata;
- struct i2c_client *client = to_i2c_client(data->dev);
+ struct pmbus_data *data = i2c_get_clientdata(client);
int i, status, event;
guard(pmbus_lock)(client);
@@ -3453,6 +3452,15 @@ static irqreturn_t pmbus_fault_handler(int irq, void *pdata)
}
pmbus_clear_faults(client);
+}
+EXPORT_SYMBOL_NS_GPL(pmbus_check_and_notify_faults, "PMBUS");
+
+static irqreturn_t pmbus_fault_handler(int irq, void *pdata)
+{
+ struct pmbus_data *data = pdata;
+ struct i2c_client *client = to_i2c_client(data->dev);
+
+ pmbus_check_and_notify_faults(client);
return IRQ_HANDLED;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v7 2/4] dt-bindings: hwmon: pmbus: add MPS MPQ8646
2026-07-30 15:43 [PATCH v7 0/4] hwmon: pmbus: add MPS MPQ8646 support Vincent Jardin via B4 Relay
2026-07-30 15:43 ` [PATCH v7 1/4] hwmon: (pmbus/core) export pmbus_check_and_notify_faults() Vincent Jardin via B4 Relay
@ 2026-07-30 15:44 ` Vincent Jardin via B4 Relay
2026-07-30 15:50 ` sashiko-bot
2026-07-30 15:44 ` [PATCH v7 3/4] hwmon: pmbus: add MPQ8646 driver Vincent Jardin via B4 Relay
2026-07-30 15:44 ` [PATCH v7 4/4] hwmon: pmbus: mpq8646: gate the writes Vincent Jardin via B4 Relay
3 siblings, 1 reply; 10+ messages in thread
From: Vincent Jardin via B4 Relay @ 2026-07-30 15:44 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet, Shuah Khan, Charles Hsu
Cc: Markus Elfring, linux-hwmon, linux-kernel, devicetree, linux-doc,
Vincent Jardin
From: Vincent Jardin <vjardin@free.fr>
The MPS MPQ8646 PMBus step-down converter uses the same
mps,vout-fb-divider-ratio-permille property with the same constraints
(maximum 2047, default 706) as the mps,mpq8785, so add its compatible
to the existing schema.
Signed-off-by: Vincent Jardin <vjardin@free.fr>
---
Documentation/devicetree/bindings/hwmon/pmbus/mps,mpq8785.yaml | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/mps,mpq8785.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/mps,mpq8785.yaml
index 90970a0433e9..d7a32fb3b7af 100644
--- a/Documentation/devicetree/bindings/hwmon/pmbus/mps,mpq8785.yaml
+++ b/Documentation/devicetree/bindings/hwmon/pmbus/mps,mpq8785.yaml
@@ -18,6 +18,7 @@ properties:
- mps,mpm3695
- mps,mpm3695-25
- mps,mpm82504
+ - mps,mpq8646
- mps,mpq8785
reg:
@@ -52,7 +53,9 @@ allOf:
- if:
properties:
compatible:
- const: mps,mpq8785
+ enum:
+ - mps,mpq8646
+ - mps,mpq8785
then:
properties:
mps,vout-fb-divider-ratio-permille:
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v7 3/4] hwmon: pmbus: add MPQ8646 driver
2026-07-30 15:43 [PATCH v7 0/4] hwmon: pmbus: add MPS MPQ8646 support Vincent Jardin via B4 Relay
2026-07-30 15:43 ` [PATCH v7 1/4] hwmon: (pmbus/core) export pmbus_check_and_notify_faults() Vincent Jardin via B4 Relay
2026-07-30 15:44 ` [PATCH v7 2/4] dt-bindings: hwmon: pmbus: add MPS MPQ8646 Vincent Jardin via B4 Relay
@ 2026-07-30 15:44 ` Vincent Jardin via B4 Relay
2026-07-30 16:03 ` sashiko-bot
2026-07-30 15:44 ` [PATCH v7 4/4] hwmon: pmbus: mpq8646: gate the writes Vincent Jardin via B4 Relay
3 siblings, 1 reply; 10+ messages in thread
From: Vincent Jardin via B4 Relay @ 2026-07-30 15:44 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet, Shuah Khan, Charles Hsu
Cc: Markus Elfring, linux-hwmon, linux-kernel, devicetree, linux-doc,
Vincent Jardin
From: Vincent Jardin <vjardin@free.fr>
Add a new driver for the MPS MPQ8646 that is a PMBus device.
Beyond basic PMBus telemetry, the driver adds:
- alarm acknowledge via inX_reset_history.
- STATUS_WORD MPS-extended bit decode and the NVM-backed
PROTECTION_LAST post-mortem, exposed as a read-only debugfs
decoder.
- In-driver alarm-poll fallback work item (thanks lm90) for
boards without SMBALERT
Signed-off-by: Vincent Jardin <vjardin@free.fr>
---
Documentation/hwmon/index.rst | 1 +
Documentation/hwmon/mpq8646.rst | 261 +++++++++++++++
MAINTAINERS | 7 +
drivers/hwmon/pmbus/Kconfig | 11 +
drivers/hwmon/pmbus/Makefile | 1 +
drivers/hwmon/pmbus/mpq8646.c | 688 ++++++++++++++++++++++++++++++++++++++++
6 files changed, 969 insertions(+)
diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 29130df44d12..be52f9f4dc70 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -201,6 +201,7 @@ Hardware Monitoring Kernel Drivers
mp5990
mp9941
mp9945
+ mpq8646
mpq8785
nct6683
nct6775
diff --git a/Documentation/hwmon/mpq8646.rst b/Documentation/hwmon/mpq8646.rst
new file mode 100644
index 000000000000..d3ede656ea15
--- /dev/null
+++ b/Documentation/hwmon/mpq8646.rst
@@ -0,0 +1,261 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+.. Copyright (c) 2026 Free Mobile - Vincent Jardin <vjardin@free.fr>
+
+Kernel driver mpq8646
+=====================
+
+Supported chips:
+
+ * MPS MPQ8646
+
+ Prefix: 'mpq8646'
+
+Author:
+ - Vincent Jardin <vjardin@free.fr>
+
+Chip-identity
+-------------
+
+Support the boards that are designed with the MPS MPQ8646 probed thanks
+to``MFR_MODEL`` register.
+
+This driver targets the MPQ8646 silicon specifically.
+
+Description
+-----------
+
+The MPQ8646 is a fully integrated, PMBus-compatible, high-frequency,
+synchronous buck converter. It offers a compact solution that
+achieves up high Amps output current per phase, with excellent load
+and line regulation over a wide input supply range. The chip
+operates at high efficiency over a wide output current load range.
+
+The PMBus interface provides converter configurations and key
+parameters monitoring.
+
+The device adopts MPS's proprietary multi-phase digital
+constant-on-time (MCOT) control, which provides fast transient
+response and eases loop stabilization. The MCOT scheme also allows
+multiple devices or channels to be connected in parallel with
+excellent current sharing and phase interleaving for high-current
+applications.
+
+Fully integrated protection features include over-current
+protection (OCP), over-voltage protection (OVP), under-voltage
+protection (UVP), and over-temperature protection (OTP).
+
+This device is compliant with:
+
+- PMBus rev 1.3 interface.
+
+The driver exports the following attributes via the 'sysfs' files
+for input voltage:
+
+- in1_input
+- in1_label
+- in1_max
+- in1_max_alarm
+- in1_min
+- in1_min_alarm
+- in1_crit
+- in1_crit_alarm
+
+The driver provides the following attributes for output voltage:
+
+- in2_input
+- in2_label
+- in2_alarm
+- in2_max
+- in2_max_alarm
+- in2_min
+- in2_min_alarm
+- in2_crit
+- in2_crit_alarm
+- in2_lcrit
+- in2_lcrit_alarm
+
+The driver provides the following attributes for output current:
+
+- curr1_input
+- curr1_label
+- curr1_max
+- curr1_max_alarm
+- curr1_crit
+- curr1_crit_alarm
+
+The driver provides the following attributes for temperature:
+
+- temp1_input
+- temp1_max
+- temp1_max_alarm
+- temp1_crit
+- temp1_crit_alarm
+
+Alarm acknowledgment
+---------------------
+
+The hwmon-class ``inX_alarm``/``currX_alarm``/``tempX_alarm`` files are
+read-only in pmbus_core. The driver exposes the standard
+``PMBUS_VIRT_RESET_*_HISTORY`` virtual-register channel for fault
+acknowledgment: writing ``1`` to ``inX_reset_history`` /
+``currX_reset_history`` / ``tempX_reset_history`` sends the chip a
+``CLEAR_FAULTS`` (0x03) Send-Byte, which clears the latched
+``STATUS_WORD`` / ``STATUS_VOUT`` / ``STATUS_IOUT`` /
+``STATUS_INPUT`` / ``STATUS_TEMPERATURE`` bits the chip is currently
+exposing.
+
+The MPS-specific NVM post-mortem register
+``PROTECTION_LAST`` (0xFB) is not cleared by this path; it can be read
+(decoded) via the ``protection_last`` debugfs entry described below.
+
+Regulator framework integration
+-------------------------------
+
+When ``CONFIG_REGULATOR=y`` is set, the chip is
+exposed under ``/sys/class/regulator/`` and accepts the standard
+regulator framework operations:
+
+- ``regulator_enable()`` / ``regulator_disable()`` -> ``OPERATION`` (0x01)
+- ``regulator_set_voltage()`` -> ``VOUT_COMMAND`` (0x21)
+- ``regulator_get_voltage()`` -> ``READ_VOUT`` (0x8B)
+- ``regulator_is_enabled()`` -> ``OPERATION`` bit-decode
+
+The single regulator descriptor is named ``"vout"`` (page 0). For a
+multi-page configuration the descriptor table can be extended in
+the driver.
+
+In-driver alarm-poll fallback
+-----------------------------
+
+On boards where the chip's ``SMBALERT#`` pin is unavailable to the
+SoC, ``pmbus_core::pmbus_irq_setup`` cannot deliver SMBALERT-driven
+``poll(POLLPRI)`` wakes or ``udev change@...`` events on the
+``inX_alarm`` files. The driver provides a ``delayed_work``-based
+polling fallback that
+periodically invokes the pmbus core fault check,
+``pmbus_check_and_notify_faults()``, which sends the notifications and
+then clears the latched faults, exactly like the ``SMBALERT#``
+interrupt path. The frequency of polling is tunable:
+
+::
+
+ /sys/kernel/debug/i2c/i2c-<bus>/<bus>-<addr>/alarm_poll_interval_ms
+
+The default is 1000 ms.
+
+Set it to 0 to disable. The worker self-suppresses
+when ``client->irq != 0`` (i.e. when DT supplies an
+``interrupts = <...>`` property on the regulator node), so adding
+``SMBALERT#`` wiring is a zero-driver-change uplift on a future
+board rev.
+
+MPS post-mortem (PROTECTION_LAST)
+---------------------------------
+
+The MPQ8646 silicon keeps a single 16-bit NVM-backed record of the
+last protection event in ``PROTECTION_LAST`` (0xFB). It survives
+chip power/reset. The following debugfs entries expose it:
+
+::
+
+ /sys/kernel/debug/i2c/i2c-<bus>/<bus>-<addr>/protection_last (RO)
+ /sys/kernel/debug/i2c/i2c-<bus>/<bus>-<addr>/status_decoded (RO)
+
+``protection_last`` decodes the 16-bit value based on the datasheet
+fault names (``INIT_FAULT``, ``NVM_CRC_ERROR``, ``NVM_FAULT``,
+``OC_PHASE_FAULT``, ``OTP_SELF_FAULT``, ``SWITCH_PRD_FAULT``,
+``VIN_OV_FAULT``, ``VOUT_OV_FAULT``, ``VOUT_UV_FAULT``,
+``OC_TOT_FAULT``, ``VIN_UVLO_FAULT``, ``DRMOS_OTP``).
+
+``status_decoded`` reads ``STATUS_WORD`` (0x79) and renders the
+16 bits with MPS-extension labels (bit12 = ``NVM_SUMMARY``,
+bit8 = ``WATCH_DOG``, bit0 = ``DRMOS_FAULT``) instead of the
+PMBus 1.3 spec generic names.
+
+NVMEM snapshot
+--------------
+
+When ``CONFIG_NVMEM=y`` is set, the chip's NVM-backed
+observability registers are exposed as a single 16-byte read-only
+``nvmem_device`` at ``/sys/bus/nvmem/devices/<i2c-name>/nvmem``.
+Layout (little-endian for 16-bit fields, zero-fill on per-entry read
+failure and for the reserved tail):
+
+::
+
+ offset 0..1 PROTECTION_LAST (0xFB) word
+ offset 2..3 MFR_RETRY_TIMES (0xF4) word
+ offset 4..5 MFR_CONFIG_ID (0xC0) word
+ offset 6..7 MFR_VBOOT_CFG (0xFC) word
+ offset 8 MFR_SILICON_REV (0xC3) byte
+ offset 9..15 reserved (zero)
+
+Suitable for single-``cat`` post-mortem capture by a fleet daemon.
+
+Diagnostics and introspection
+-----------------------------
+
+When ``CONFIG_DEBUG_FS=y`` is set, the driver adds a read-only
+decode surface to the client's pmbus debugfs directory,
+``/sys/kernel/debug/i2c/i2c-<bus>/<bus>-<addr>/``.
+The driver intentionally exposes no raw register poke/peek debugfs;
+use i2c-dev (``i2cget``/``i2cset``/``i2ctransfer``) for that.
+
+Identity / observability (read-only):
+
+========================== ================== =================================
+File PMBus / MFR cmd Description
+========================== ================== =================================
+``mfr_config_id`` 0xC0 (word) board / SKU NVM identifier
+``mfr_config_code_rev`` 0xC1 (word) NVM image revision (PART_RECOG +
+ config code rev fields)
+``mfr_silicon_rev`` 0xC3 (byte) die revision
+``mfr_retry_times`` 0xF4 (word) per-fault-class recovery-mode
+ configuration (NOT a retry
+ count). Four 4-bit fields
+ [15:12]=OTP, [11:8]=VOUT_OV,
+ [7:4]=VOUT_UV, [3:0]=OCP.
+ Each field: 0x0=latch-off,
+ 0x1..0xE=retry N times then
+ latch off, 0xF=hiccup
+ (retry indefinitely). The
+ chip exposes no in-NVM
+ retry-event counter; track
+ transitions externally if
+ needed (poll
+ ``protection_last`` or watch
+ ``inX_alarm`` / ``temp1_alarm``
+ ``poll(POLLPRI)`` wakes).
+``mfr_vboot_cfg`` 0xFC (word) ADDR/VBOOT latched at POR
+========================== ================== =================================
+
+Timing / UVLO knobs (read-only):
+
+========================== ================== =================================
+File PMBus cmd Description
+========================== ================== =================================
+``vin_on`` 0x35 (word) UVLO turn-on threshold
+``vin_off`` 0x36 (word) UVLO turn-off threshold
+``ton_delay`` 0x60 (word) soft-start delay
+``ton_rise`` 0x61 (word) soft-start ramp time
+``toff_delay`` 0x64 (word) soft-stop delay
+``toff_fall`` 0x65 (word) soft-stop ramp time
+========================== ================== =================================
+
+The only writable entry is ``alarm_poll_interval_ms`` (the alarm-poll
+worker cadence, see above), it is driver-local and never touches the
+chip.
+
+Devicetree
+----------
+
+The driver uses ``compatible = "mps,mpq8646"``. There are some few optional
+properties:
+
+- ``mps,vout-fb-divider-ratio-permille`` : it writes ``VOUT_SCALE_LOOP``
+ (0x29) at probe to compensate for an external resistor divider in
+ the VOUT feedback path. The valid range is 11-bit.
+- ``interrupts = <...>`` : if the board routes the chip's
+ ``SMBALERT#`` pin to a SoC GPIO, declaring it here lights up
+ ``pmbus_core::pmbus_irq_setup`` and disables the in-driver
+ alarm-poll fallback
diff --git a/MAINTAINERS b/MAINTAINERS
index 1ab8736850ea..88dc83d98896 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18328,6 +18328,13 @@ S: Maintained
F: Documentation/hwmon/mp9945.rst
F: drivers/hwmon/pmbus/mp9945.c
+MPS MPQ8646 PMBUS DRIVER
+M: Vincent Jardin <vjardin@free.fr>
+L: linux-hwmon@vger.kernel.org
+S: Maintained
+F: Documentation/hwmon/mpq8646.rst
+F: drivers/hwmon/pmbus/mpq8646.c
+
MR800 AVERMEDIA USB FM RADIO DRIVER
M: Alexey Klimov <alexey.klimov@linaro.org>
L: linux-media@vger.kernel.org
diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
index c8cda160b5f8..9f44e76b0b64 100644
--- a/drivers/hwmon/pmbus/Kconfig
+++ b/drivers/hwmon/pmbus/Kconfig
@@ -616,6 +616,17 @@ config SENSORS_MPQ8785
This driver can also be built as a module. If so, the module will
be called mpq8785.
+config SENSORS_MPQ8646
+ tristate "MPS MPQ8646"
+ depends on REGULATOR || !REGULATOR
+ depends on NVMEM || !NVMEM
+ help
+ If you say yes here you get hardware monitoring support for the
+ Monolithic Power Systems MPQ8646.
+
+ This driver can also be built as a module. If so, the module
+ will be called mpq8646.
+
config SENSORS_PIM4328
tristate "Flex PIM4328 and compatibles"
help
diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile
index ffc05f493213..6f8fda966600 100644
--- a/drivers/hwmon/pmbus/Makefile
+++ b/drivers/hwmon/pmbus/Makefile
@@ -60,6 +60,7 @@ obj-$(CONFIG_SENSORS_MP9941) += mp9941.o
obj-$(CONFIG_SENSORS_MP9945) += mp9945.o
obj-$(CONFIG_SENSORS_MPQ7932) += mpq7932.o
obj-$(CONFIG_SENSORS_MPQ8785) += mpq8785.o
+obj-$(CONFIG_SENSORS_MPQ8646) += mpq8646.o
obj-$(CONFIG_SENSORS_PLI1209BC) += pli1209bc.o
obj-$(CONFIG_SENSORS_PM6764TR) += pm6764tr.o
obj-$(CONFIG_SENSORS_PXE1610) += pxe1610.o
diff --git a/drivers/hwmon/pmbus/mpq8646.c b/drivers/hwmon/pmbus/mpq8646.c
new file mode 100644
index 000000000000..5133a2471873
--- /dev/null
+++ b/drivers/hwmon/pmbus/mpq8646.c
@@ -0,0 +1,688 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for MPS MPQ8646 step-down converter.
+ *
+ * Copyright (c) 2026 Free Mobile - Vincent Jardin <vjardin@free.fr>
+ */
+
+#include <linux/bitops.h>
+#include <linux/debugfs.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/nvmem-provider.h>
+#include <linux/of_device.h>
+#include <linux/pmbus.h>
+#include <linux/property.h>
+#include <linux/regulator/driver.h>
+#include <linux/seq_file.h>
+#include <linux/workqueue.h>
+#include "pmbus.h"
+
+/* Default cadence for the in-driver alarm-poll fallback (ms) */
+#define MPQ8646_ALARM_POLL_MS_DEFAULT 1000
+
+/* MPS vendor-extended command codes (NOT in PMBus 1.3 Part II) */
+#define MPS_PROTECTION_LAST 0xFB
+
+/* PMBus 1.3 timing / UVLO command codes */
+#define PMBUS_VIN_ON 0x35
+#define PMBUS_VIN_OFF 0x36
+#define PMBUS_TON_DELAY 0x60
+#define PMBUS_TON_RISE 0x61
+#define PMBUS_TOFF_DELAY 0x64
+#define PMBUS_TOFF_FALL 0x65
+
+/* MPS vendor-extended observability / identity registers */
+#define MPS_MFR_CONFIG_ID 0xC0
+#define MPS_MFR_CONFIG_CODE_REV 0xC1
+#define MPS_MFR_SILICON_REV 0xC3
+#define MPS_MFR_RETRY_TIMES 0xF4
+#define MPS_MFR_VBOOT_CFG 0xFC
+
+#define MPQ8646_DEBUG(client, fmt, ...) \
+ dev_dbg(&(client)->dev, fmt, ##__VA_ARGS__)
+
+/*
+ * Maximum legal value for VOUT_SCALE_LOOP (0x29) on the MPQ8646 silicon
+ * The chip uses an 11-bit VOUT feedback-divider scale register.
+ */
+#define MPQ8646_VOUT_SCALE_LOOP_MAX GENMASK(10, 0)
+
+/* Forward declaration */
+struct mpq8646_dbg_reg_ctx;
+
+/* Per-instance state */
+struct mpq8646_priv {
+ struct pmbus_driver_info info; /* must be first, container_of target */
+ struct i2c_client *client;
+
+ /* Serialises CLEAR_LAST_FAULT sequences and PROTECTION_LAST reads */
+ struct mutex mps_lock;
+
+ /* Set alarm_poll_interval_ms = 0 to disable. */
+ struct delayed_work alarm_poll_work;
+ u32 alarm_poll_interval_ms;
+
+#ifdef CONFIG_DEBUG_FS
+ /* the only debugfs file that can re-arm the poll worker */
+ struct dentry *dbg_poll;
+ struct mpq8646_dbg_reg_ctx *dbg_reg_ctx;
+#endif
+};
+
+static inline struct mpq8646_priv *mpq8646_priv_from_client(struct i2c_client *client)
+{
+ const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
+
+ return container_of(info, struct mpq8646_priv, info);
+}
+
+/*
+ * VID-mode m/b/R coefficients, same values as the mpq8785 driver for
+ * the MPS VID encoding. Per the PMBus Direct formula used by
+ * pmbus_core, X = (Y * 10^-R - b) / m, so m=64 / b=0 / R=1 yields
+ * 1.5625 mV per LSB.
+ */
+#define MPQ8646_VID_M 64
+#define MPQ8646_VID_B 0
+#define MPQ8646_VID_R 1
+
+static int mpq8646_identify(struct i2c_client *client,
+ struct pmbus_driver_info *info)
+{
+ int vout_mode;
+
+ vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE);
+ if (vout_mode < 0)
+ return vout_mode;
+
+ switch (vout_mode & PB_VOUT_MODE_MODE_MASK) {
+ case PB_VOUT_MODE_LINEAR:
+ info->format[PSC_VOLTAGE_OUT] = linear;
+ break;
+ case PB_VOUT_MODE_VID:
+ case PB_VOUT_MODE_DIRECT:
+ info->format[PSC_VOLTAGE_OUT] = direct;
+ info->m[PSC_VOLTAGE_OUT] = MPQ8646_VID_M;
+ info->b[PSC_VOLTAGE_OUT] = MPQ8646_VID_B;
+ info->R[PSC_VOLTAGE_OUT] = MPQ8646_VID_R;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ return 0;
+};
+
+static int mpq8646_read_byte_data(struct i2c_client *client, int page, int reg)
+{
+ int ret;
+
+ MPQ8646_DEBUG(client, "read_byte_data page=%d reg=0x%02x\n", page, reg);
+
+ switch (reg) {
+ case PMBUS_VOUT_MODE:
+ ret = pmbus_read_byte_data(client, page, reg);
+ MPQ8646_DEBUG(client, " VOUT_MODE raw=0x%02x ret=%d\n", ret, ret);
+ if (ret < 0)
+ return ret;
+
+ if ((ret & PB_VOUT_MODE_MODE_MASK) == PB_VOUT_MODE_VID)
+ return PB_VOUT_MODE_DIRECT;
+
+ return ret;
+ case PMBUS_STATUS_BYTE:
+ case PMBUS_STATUS_CML:
+ case PMBUS_STATUS_OTHER:
+ case PMBUS_STATUS_MFR_SPECIFIC:
+ case PMBUS_STATUS_FAN_12:
+ case PMBUS_STATUS_FAN_34:
+ return -ENXIO;
+ case PMBUS_MFR_LOCATION:
+ case PMBUS_MFR_DATE:
+ case PMBUS_MFR_SERIAL:
+ case PMBUS_IC_DEVICE_ID:
+ case PMBUS_IC_DEVICE_REV:
+ MPQ8646_DEBUG(client, " unsupported mfr-info reg 0x%02x -> ENXIO\n", reg);
+ return -ENXIO;
+ default:
+ return -ENODATA;
+ }
+}
+
+/*
+ * Reference: ltc2978.c::ltc2978_write_word_data which uses the
+ * same virtual-register channel for its real chip-side peak reset.
+ */
+static int mpq8646_write_word_data(struct i2c_client *client, int page,
+ int reg, u16 word)
+{
+ struct mpq8646_priv *priv = mpq8646_priv_from_client(client);
+ int rc;
+
+ switch (reg) {
+ case PMBUS_VIRT_RESET_VIN_HISTORY:
+ case PMBUS_VIRT_RESET_VOUT_HISTORY:
+ case PMBUS_VIRT_RESET_IOUT_HISTORY:
+ case PMBUS_VIRT_RESET_TEMP_HISTORY:
+ MPQ8646_DEBUG(client, "reset_history virt reg=0x%04x -> CLEAR_FAULTS\n",
+ reg);
+ rc = i2c_smbus_write_byte(priv->client, PMBUS_CLEAR_FAULTS);
+ if (rc < 0)
+ MPQ8646_DEBUG(client, " CLEAR_FAULTS rc=%d\n", rc);
+ return rc < 0 ? rc : 0;
+ default:
+ return -ENODATA; /* let pmbus_core do the direct write */
+ }
+}
+
+static int mpq8646_read_word_data(struct i2c_client *client, int page,
+ int phase, int reg)
+{
+ int rc;
+
+ MPQ8646_DEBUG(client, "read_word_data page=%d phase=%d reg=0x%02x\n",
+ page, phase, reg);
+
+ switch (reg) {
+ case PMBUS_READ_VIN:
+ case PMBUS_READ_VOUT:
+ case PMBUS_READ_IOUT:
+ case PMBUS_READ_TEMPERATURE_1:
+ case PMBUS_STATUS_WORD:
+ case PMBUS_VOUT_OV_FAULT_LIMIT:
+ case PMBUS_VOUT_OV_WARN_LIMIT:
+ case PMBUS_VOUT_UV_WARN_LIMIT:
+ case PMBUS_VOUT_UV_FAULT_LIMIT:
+ case PMBUS_IOUT_OC_FAULT_LIMIT:
+ case PMBUS_IOUT_OC_WARN_LIMIT:
+ case PMBUS_OT_FAULT_LIMIT:
+ case PMBUS_OT_WARN_LIMIT:
+ case PMBUS_VIN_OV_FAULT_LIMIT:
+ case PMBUS_VIN_OV_WARN_LIMIT:
+ case PMBUS_VIN_UV_WARN_LIMIT:
+ case PMBUS_VIN_UV_FAULT_LIMIT:
+ case PMBUS_MFR_VIN_MAX:
+ case PMBUS_MFR_VOUT_MAX:
+ case PMBUS_MFR_IOUT_MAX:
+ case PMBUS_MFR_MAX_TEMP_1:
+ break;
+ case PMBUS_VIRT_RESET_VIN_HISTORY:
+ case PMBUS_VIRT_RESET_VOUT_HISTORY:
+ case PMBUS_VIRT_RESET_IOUT_HISTORY:
+ case PMBUS_VIRT_RESET_TEMP_HISTORY:
+ return 0;
+ default:
+ return -ENODATA;
+ }
+
+ rc = i2c_smbus_read_word_data(client, reg);
+
+ MPQ8646_DEBUG(client, " reg=0x%02x rc=%d\n", reg, rc);
+ return rc;
+}
+
+static struct pmbus_driver_info mpq8646_info = {
+ .pages = 1,
+ .format[PSC_VOLTAGE_IN] = direct,
+ .format[PSC_CURRENT_OUT] = direct,
+ .format[PSC_TEMPERATURE] = direct,
+ .m[PSC_VOLTAGE_IN] = 4,
+ .b[PSC_VOLTAGE_IN] = 0,
+ .R[PSC_VOLTAGE_IN] = 1,
+ .m[PSC_CURRENT_OUT] = 16,
+ .b[PSC_CURRENT_OUT] = 0,
+ .R[PSC_CURRENT_OUT] = 0,
+ .m[PSC_TEMPERATURE] = 1,
+ .b[PSC_TEMPERATURE] = 0,
+ .R[PSC_TEMPERATURE] = 0,
+ .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT |
+ PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP |
+ PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_VOUT |
+ PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_TEMP,
+};
+
+#if IS_ENABLED(CONFIG_REGULATOR)
+static const struct regulator_desc mpq8646_reg_desc[] = {
+ PMBUS_REGULATOR("vout", 0),
+};
+#endif /* CONFIG_REGULATOR */
+
+#if IS_ENABLED(CONFIG_NVMEM)
+/*
+ * Expose using
+ * /sys/bus/nvmem/devices/<i2c-name>/nvmem
+ *
+ * Layout (16 bytes):
+ * offset 0..1 PROTECTION_LAST (0xFB) word, LE -- NVM
+ * offset 2..3 MFR_RETRY_TIMES (0xF4) word, LE -- NVM
+ * offset 4..5 MFR_CONFIG_ID (0xC0) word, LE -- NVM
+ * offset 6..7 MFR_VBOOT_CFG (0xFC) word, LE -- NVM
+ * offset 8 MFR_SILICON_REV (0xC3) byte -- NVM
+ * offset 9..15 reserved (zero-fill, leaves room for additions)
+ */
+#define MPQ8646_NVMEM_SIZE 16
+
+struct mpq8646_nvmem_entry {
+ unsigned int off;
+ u8 reg;
+ bool is_word;
+};
+
+static const struct mpq8646_nvmem_entry mpq8646_nvmem_map[] = {
+ { 0, MPS_PROTECTION_LAST, true },
+ { 2, MPS_MFR_RETRY_TIMES, true },
+ { 4, MPS_MFR_CONFIG_ID, true },
+ { 6, MPS_MFR_VBOOT_CFG, true },
+ { 8, MPS_MFR_SILICON_REV, false },
+};
+
+static int mpq8646_nvmem_read(void *data, unsigned int offset, void *val,
+ size_t bytes)
+{
+ struct mpq8646_priv *priv = data;
+ u8 *out = val;
+ size_t i;
+
+ if (offset >= MPQ8646_NVMEM_SIZE)
+ return -EINVAL;
+ if (offset + bytes > MPQ8646_NVMEM_SIZE)
+ bytes = MPQ8646_NVMEM_SIZE - offset;
+
+ memset(out, 0, bytes);
+
+ guard(mutex)(&priv->mps_lock);
+ for (i = 0; i < ARRAY_SIZE(mpq8646_nvmem_map); i++) {
+ const struct mpq8646_nvmem_entry *e = &mpq8646_nvmem_map[i];
+ unsigned int e_start = e->off;
+ unsigned int e_end = e_start + (e->is_word ? 2 : 1);
+ u8 raw[2];
+ int rc;
+ unsigned int j;
+
+ if (e_end <= offset || e_start >= offset + bytes)
+ continue; /* outside requested slice */
+
+ if (e->is_word)
+ rc = i2c_smbus_read_word_data(priv->client, e->reg);
+ else
+ rc = i2c_smbus_read_byte_data(priv->client, e->reg);
+ if (rc < 0)
+ continue; /* leave the zero-fill in place */
+
+ raw[0] = rc & 0xff;
+ raw[1] = (rc >> 8) & 0xff;
+
+ for (j = 0; j < e_end - e_start; j++) {
+ unsigned int abs = e_start + j;
+
+ if (abs >= offset && abs < offset + bytes)
+ out[abs - offset] = raw[j];
+ }
+ }
+ return 0;
+}
+#endif /* CONFIG_NVMEM */
+
+static const struct i2c_device_id mpq8646_id[] = {
+ { .name = "mpq8646" },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, mpq8646_id);
+
+static const struct of_device_id __maybe_unused mpq8646_of_match[] = {
+ { .compatible = "mps,mpq8646" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, mpq8646_of_match);
+
+static struct pmbus_platform_data mpq8646_no_pec_pdata = {
+ .flags = PMBUS_NO_CAPABILITY,
+};
+
+#ifdef CONFIG_DEBUG_FS
+/*
+ * Read-only decode cases in the client's pmbus debugfs directory:
+ * the MPS-specific STATUS_WORD and PROTECTION_LAST bit decode plus the
+ * identity/timing registers.
+ */
+
+struct mpq_status_bit {
+ u16 mask;
+ const char *name;
+};
+
+static const struct mpq_status_bit mpq8646_status_word_bits[] = {
+ { PB_STATUS_VOUT, "VOUT" },
+ { PB_STATUS_IOUT_POUT, "IOUT_POUT" },
+ { PB_STATUS_INPUT, "INPUT" },
+ { PB_STATUS_WORD_MFR, "NVM_SUMMARY" },
+ { PB_STATUS_POWER_GOOD_N, "POWER_GOOD#" },
+ { PB_STATUS_FANS, "FANS" },
+ { PB_STATUS_OTHER, "OTHER" },
+ { PB_STATUS_UNKNOWN, "WATCH_DOG" },
+ { PB_STATUS_BUSY, "BUSY" },
+ { PB_STATUS_OFF, "OFF" },
+ { PB_STATUS_VOUT_OV, "VOUT_OV_FAULT" },
+ { PB_STATUS_IOUT_OC, "IOUT_OC_FAULT" },
+ { PB_STATUS_VIN_UV, "VIN_UV_FAULT" },
+ { PB_STATUS_TEMPERATURE, "TEMP" },
+ { PB_STATUS_CML, "CML" },
+ { PB_STATUS_NONE_ABOVE, "DRMOS_FAULT" },
+ { /* sentinel */ }
+};
+
+/* PROTECTION_LAST (0xFB) bit names, it survives chip POR */
+static const struct mpq_status_bit mpq8646_protection_last_bits[] = {
+ { BIT(15), "INIT_FAULT" },
+ { BIT(14), "NVM_CRC_ERROR" },
+ { BIT(13), "NVM_FAULT" },
+ { BIT(12), "OC_PHASE_FAULT" },
+ { BIT(11), "OTP_SELF_FAULT" },
+ { BIT(9), "SWITCH_PRD_FAULT" },
+ { BIT(8), "VIN_OV_FAULT" },
+ { BIT(7), "VOUT_OV_FAULT" },
+ { BIT(6), "VOUT_UV_FAULT" },
+ { BIT(5), "OC_TOT_FAULT" },
+ { BIT(4), "VIN_UVLO_FAULT" },
+ { BIT(3), "DRMOS_OTP" },
+ { /* sentinel */ }
+};
+
+static void mpq8646_print_bits(struct seq_file *s, u16 v,
+ const struct mpq_status_bit *tab)
+{
+ const struct mpq_status_bit *t;
+ bool first = true;
+
+ seq_printf(s, "0x%04x", v);
+ if (!v) {
+ seq_puts(s, " [clean]\n");
+ return;
+ }
+ seq_puts(s, " [");
+ for (t = tab; t->mask; t++) {
+ if (v & t->mask) {
+ if (!first)
+ seq_putc(s, ' ');
+ seq_puts(s, t->name);
+ first = false;
+ }
+ }
+ seq_puts(s, "]\n");
+}
+
+static int mpq8646_dbg_status_decoded_show(struct seq_file *s, void *unused)
+{
+ struct mpq8646_priv *priv = s->private;
+ int rc;
+
+ scoped_guard(pmbus_lock, priv->client)
+ rc = i2c_smbus_read_word_data(priv->client, PMBUS_STATUS_WORD);
+ if (rc < 0) {
+ seq_printf(s, "ERROR: STATUS_WORD read failed (%d)\n", rc);
+ return 0;
+ }
+ seq_puts(s, "STATUS_WORD: ");
+ mpq8646_print_bits(s, (u16)rc, mpq8646_status_word_bits);
+ seq_puts(s, "(MPS extensions: bit12=NVM_SUMMARY, bit8=WATCH_DOG, bit0=DRMOS_FAULT)\n");
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_status_decoded);
+
+static int mpq8646_dbg_protection_last_show(struct seq_file *s, void *unused)
+{
+ struct mpq8646_priv *priv = s->private;
+ int rc;
+
+ guard(pmbus_lock)(priv->client);
+ scoped_guard(mutex, &priv->mps_lock)
+ rc = i2c_smbus_read_word_data(priv->client, MPS_PROTECTION_LAST);
+
+ if (rc < 0) {
+ seq_printf(s, "ERROR: PROTECTION_LAST read failed (%d)\n", rc);
+ return 0;
+ }
+ seq_puts(s, "PROTECTION_LAST: ");
+ mpq8646_print_bits(s, (u16)rc, mpq8646_protection_last_bits);
+ seq_puts(s, "(NVM-backed, survives chip POR)\n");
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_protection_last);
+
+struct mpq8646_dbg_reg {
+ u8 reg;
+ bool is_word;
+ const char *name;
+};
+
+static const struct mpq8646_dbg_reg mpq8646_dbg_regs[] = {
+ /* MPS vendor extensions (read-only identity / observability) */
+ { MPS_MFR_CONFIG_ID, true, "mfr_config_id" },
+ { MPS_MFR_CONFIG_CODE_REV, true, "mfr_config_code_rev" },
+ { MPS_MFR_SILICON_REV, false, "mfr_silicon_rev" },
+ { MPS_MFR_RETRY_TIMES, true, "mfr_retry_times" },
+ { MPS_MFR_VBOOT_CFG, true, "mfr_vboot_cfg" },
+ /* PMBus 1.3 standard timing / UVLO (read-only introspection) */
+ { PMBUS_VIN_ON, true, "vin_on" },
+ { PMBUS_VIN_OFF, true, "vin_off" },
+ { PMBUS_TON_DELAY, true, "ton_delay" },
+ { PMBUS_TON_RISE, true, "ton_rise" },
+ { PMBUS_TOFF_DELAY, true, "toff_delay" },
+ { PMBUS_TOFF_FALL, true, "toff_fall" },
+};
+
+struct mpq8646_dbg_reg_ctx {
+ struct mpq8646_priv *priv;
+ const struct mpq8646_dbg_reg *desc;
+};
+
+static int mpq8646_dbg_reg_show(struct seq_file *s, void *unused)
+{
+ struct mpq8646_dbg_reg_ctx *ctx = s->private;
+ int rc;
+
+ guard(pmbus_lock)(ctx->priv->client);
+ if (ctx->desc->is_word)
+ rc = i2c_smbus_read_word_data(ctx->priv->client,
+ ctx->desc->reg);
+ else
+ rc = i2c_smbus_read_byte_data(ctx->priv->client,
+ ctx->desc->reg);
+
+ if (rc < 0) {
+ seq_printf(s, "ERROR: reg 0x%02x (%s) read failed (%d)\n",
+ ctx->desc->reg, ctx->desc->name, rc);
+ return 0;
+ }
+ seq_printf(s, "0x%0*x\n", ctx->desc->is_word ? 4 : 2, rc);
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_reg);
+
+static int mpq8646_dbg_poll_interval_get(void *data, u64 *val)
+{
+ struct mpq8646_priv *priv = data;
+
+ *val = priv->alarm_poll_interval_ms;
+ return 0;
+}
+
+static int mpq8646_dbg_poll_interval_set(void *data, u64 val)
+{
+ struct mpq8646_priv *priv = data;
+ bool was_off = !priv->alarm_poll_interval_ms;
+
+ priv->alarm_poll_interval_ms = (u32)val;
+
+ if (val && was_off && !priv->client->irq)
+ schedule_delayed_work(&priv->alarm_poll_work,
+ msecs_to_jiffies((u32)val));
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_poll_interval_fops,
+ mpq8646_dbg_poll_interval_get,
+ mpq8646_dbg_poll_interval_set, "%llu\n");
+
+static void mpq8646_debugfs_register(struct mpq8646_priv *priv)
+{
+ struct dentry *root;
+ size_t i;
+
+ root = pmbus_get_debugfs_dir(priv->client);
+ if (!root)
+ return;
+
+ /* MPS extensions: status decode + NVM-backed PROTECTION_LAST */
+ debugfs_create_file("status_decoded", 0400, root, priv,
+ &mpq8646_dbg_status_decoded_fops);
+ debugfs_create_file("protection_last", 0400, root, priv,
+ &mpq8646_dbg_protection_last_fops);
+ priv->dbg_poll =
+ debugfs_create_file_unsafe("alarm_poll_interval_ms", 0600,
+ root, priv,
+ &mpq8646_dbg_poll_interval_fops);
+
+ priv->dbg_reg_ctx = devm_kcalloc(&priv->client->dev,
+ ARRAY_SIZE(mpq8646_dbg_regs),
+ sizeof(*priv->dbg_reg_ctx),
+ GFP_KERNEL);
+ if (!priv->dbg_reg_ctx)
+ return;
+ for (i = 0; i < ARRAY_SIZE(mpq8646_dbg_regs); i++) {
+ priv->dbg_reg_ctx[i].priv = priv;
+ priv->dbg_reg_ctx[i].desc = &mpq8646_dbg_regs[i];
+ debugfs_create_file(mpq8646_dbg_regs[i].name, 0400,
+ root, &priv->dbg_reg_ctx[i],
+ &mpq8646_dbg_reg_fops);
+ }
+}
+
+static void mpq8646_debugfs_unregister(struct mpq8646_priv *priv)
+{
+ debugfs_remove(priv->dbg_poll);
+}
+#else
+static inline void mpq8646_debugfs_register(struct mpq8646_priv *priv) {}
+static inline void mpq8646_debugfs_unregister(struct mpq8646_priv *priv) {}
+#endif /* CONFIG_DEBUG_FS */
+
+static void mpq8646_alarm_poll_work(struct work_struct *work)
+{
+ struct mpq8646_priv *priv = container_of(to_delayed_work(work),
+ struct mpq8646_priv,
+ alarm_poll_work);
+
+ if (priv->client->irq)
+ return; /* SMBALERT# wired; polling not needed */
+
+ if (!priv->alarm_poll_interval_ms)
+ return; /* polling disabled; don't re-arm */
+
+ pmbus_check_and_notify_faults(priv->client);
+
+ schedule_delayed_work(&priv->alarm_poll_work,
+ msecs_to_jiffies(priv->alarm_poll_interval_ms));
+}
+
+static int mpq8646_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct pmbus_driver_info *info;
+ struct mpq8646_priv *priv;
+ u32 voltage_scale;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ priv->client = client;
+ mutex_init(&priv->mps_lock);
+ memcpy(&priv->info, &mpq8646_info, sizeof(priv->info));
+ info = &priv->info;
+
+ info->identify = mpq8646_identify;
+ info->read_byte_data = mpq8646_read_byte_data;
+ info->read_word_data = mpq8646_read_word_data;
+ info->write_word_data = mpq8646_write_word_data;
+ dev->platform_data = &mpq8646_no_pec_pdata;
+
+#if IS_ENABLED(CONFIG_REGULATOR)
+ info->reg_desc = mpq8646_reg_desc;
+ info->num_regulators = ARRAY_SIZE(mpq8646_reg_desc);
+#endif
+
+ INIT_DELAYED_WORK(&priv->alarm_poll_work, mpq8646_alarm_poll_work);
+ priv->alarm_poll_interval_ms = MPQ8646_ALARM_POLL_MS_DEFAULT;
+
+ if (!device_property_read_u32(dev, "mps,vout-fb-divider-ratio-permille",
+ &voltage_scale)) {
+ if (voltage_scale > MPQ8646_VOUT_SCALE_LOOP_MAX)
+ return -EINVAL;
+
+ ret = i2c_smbus_write_word_data(client, PMBUS_VOUT_SCALE_LOOP,
+ voltage_scale);
+ if (ret)
+ return ret;
+ }
+
+ ret = pmbus_do_probe(client, info);
+ if (ret)
+ return ret;
+
+ mpq8646_debugfs_register(priv);
+
+ if (!client->irq)
+ schedule_delayed_work(&priv->alarm_poll_work,
+ msecs_to_jiffies(priv->alarm_poll_interval_ms));
+
+#if IS_ENABLED(CONFIG_NVMEM)
+ {
+ struct nvmem_config cfg = {
+ .dev = &client->dev,
+ .name = dev_name(&client->dev),
+ .owner = THIS_MODULE,
+ .read_only = true,
+ .root_only = true,
+ .word_size = 1,
+ .stride = 1,
+ .size = MPQ8646_NVMEM_SIZE,
+ .reg_read = mpq8646_nvmem_read,
+ .priv = priv,
+ };
+ struct nvmem_device *nv = devm_nvmem_register(&client->dev, &cfg);
+
+ if (IS_ERR(nv))
+ dev_warn(&client->dev,
+ "nvmem snapshot register failed (%pe)\n",
+ nv);
+ }
+#endif
+ return 0;
+};
+
+static void mpq8646_remove(struct i2c_client *client)
+{
+ struct mpq8646_priv *priv = mpq8646_priv_from_client(client);
+
+ mpq8646_debugfs_unregister(priv);
+ cancel_delayed_work_sync(&priv->alarm_poll_work);
+}
+
+static struct i2c_driver mpq8646_driver = {
+ .driver = {
+ .name = "mpq8646",
+ .of_match_table = of_match_ptr(mpq8646_of_match),
+ },
+ .probe = mpq8646_probe,
+ .remove = mpq8646_remove,
+ .id_table = mpq8646_id,
+};
+
+module_i2c_driver(mpq8646_driver);
+
+MODULE_AUTHOR("Vincent Jardin <vjardin@free.fr>");
+MODULE_DESCRIPTION("PMBus driver for MPS MPQ8646 (extended observability)");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("PMBUS");
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v7 4/4] hwmon: pmbus: mpq8646: gate the writes
2026-07-30 15:43 [PATCH v7 0/4] hwmon: pmbus: add MPS MPQ8646 support Vincent Jardin via B4 Relay
` (2 preceding siblings ...)
2026-07-30 15:44 ` [PATCH v7 3/4] hwmon: pmbus: add MPQ8646 driver Vincent Jardin via B4 Relay
@ 2026-07-30 15:44 ` Vincent Jardin via B4 Relay
2026-07-30 16:00 ` sashiko-bot
2026-07-30 18:40 ` Markus Elfring
3 siblings, 2 replies; 10+ messages in thread
From: Vincent Jardin via B4 Relay @ 2026-07-30 15:44 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet, Shuah Khan, Charles Hsu
Cc: Markus Elfring, linux-hwmon, linux-kernel, devicetree, linux-doc,
Vincent Jardin
From: Vincent Jardin <vjardin@free.fr>
The write cases of debugfs entries are provisioning and bench
helpers. By designs the MPQ8646 powers the CPU core rail,
so a wrong write can brown out the board or persist a bad
setpoint into finite-cycle NVM.
Said differently: some wrong register writes can and likely
will physically damage or destroy the chip and/or the board.
To be safe, it is disabled by default behind a
CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE and we print an explicit warning
banner at probe time when they are built in.
Signed-off-by: Vincent Jardin <vjardin@free.fr>
---
Documentation/hwmon/mpq8646.rst | 39 ++++++
drivers/hwmon/pmbus/Kconfig | 20 +++
drivers/hwmon/pmbus/mpq8646.c | 288 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 347 insertions(+)
diff --git a/Documentation/hwmon/mpq8646.rst b/Documentation/hwmon/mpq8646.rst
index d3ede656ea15..d606d957581b 100644
--- a/Documentation/hwmon/mpq8646.rst
+++ b/Documentation/hwmon/mpq8646.rst
@@ -246,6 +246,45 @@ The only writable entry is ``alarm_poll_interval_ms`` (the alarm-poll
worker cadence, see above), it is driver-local and never touches the
chip.
+Unsafe provisioning
+-------------------
+
+Available only when ``CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE=y``
+(``default n``, it is for boards bring up only). They WRITE to the regulator.
+On many designs the MPQ8646 powers the main CPU core rail, so a wrong write can
+brown out the board or persist a bad setpoint into finite-cycle NVM.
+WARNING: Some wrong register writes can and likely will physically damage or
+destroy the chip and/or the board. An explicit warning banner is printed at probe
+time when this option is enabled.
+
+=============================== =============== =========================================================
+File PMBus / MFR cmd Description
+=============================== =============== =========================================================
+``store_all`` 0x15 STORE_USER_ALL Send-Byte (commit RAM config to NVM)
+``restore_all`` 0x16 RESTORE_USER_ALL Send-Byte (revert RAM to last-NVM image)
+``clear_protection_last`` 0x08 CLEAR_LAST_FAULT Send-Byte
+``clear_protection_last_force`` -- gated CLEAR_LAST_FAULT, see below
+``on_off_config`` 0x02 (byte) PMBus vs CTRL-pin on/off source + active polarity
+``vout_margin_high`` 0x25 (word) margin-high VOUT setpoint
+``vout_margin_low`` 0x26 (word) margin-low VOUT setpoint
+``mfr_pmbus_lock`` 0xEE (word) programmable PMBus write-lock
+``mfr_product_rev_user`` 0xC2 (word) user-programmable product revision
+=============================== =============== =========================================================
+
+``clear_protection_last`` writes ``CLEAR_LAST_FAULT`` (0x08) Send-Byte.
+The chip silently no-ops unless ``MFR_CFG_EXT`` (0xF5) bit[6] is set.
+
+``clear_protection_last_force`` performs the unlock with the following
+six-step dancing:
+
+1. read ``WRITE_PROTECT`` (0x10) and ``MFR_CFG_EXT`` (0xF5) for restore
+2. clear ``WRITE_PROTECT`` if set
+3. set ``MFR_CFG_EXT`` bit[6] = 1, preserving other bits
+4. send ``CLEAR_LAST_FAULT`` (0x08)
+5. restore ``MFR_CFG_EXT`` (with retry to handle the chip's
+ undocumented post-NVM-write busy window)
+6. restore ``WRITE_PROTECT``
+
Devicetree
----------
diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
index 9f44e76b0b64..e50a54fc884d 100644
--- a/drivers/hwmon/pmbus/Kconfig
+++ b/drivers/hwmon/pmbus/Kconfig
@@ -627,6 +627,26 @@ config SENSORS_MPQ8646
This driver can also be built as a module. If so, the module
will be called mpq8646.
+config SENSORS_MPQ8646_DEBUG_UNSAFE
+ bool "MPQ8646 unsafe write/provisioning debugfs (DANGEROUS)"
+ depends on SENSORS_MPQ8646 && DEBUG_FS
+ default n
+ help
+ Expose additional WRITE-able debugfs files in the client's
+ pmbus debugfs directory,
+ /sys/kernel/debug/i2c/i2c-<bus>/<bus>-<addr>/
+
+ These are provisioning and bring-up aids. On many designs the
+ MPQ8646 powers the SoC core rail, so a wrong write can brown out
+ or permanently mis-provision the board.
+
+ WARNING: Wrong register writes can and likely will physically
+ damage or destroy the chip and/or the board.
+
+ It shall never be set in a production, shipping, or default configuration.
+
+ If unsure, say N.
+
config SENSORS_PIM4328
tristate "Flex PIM4328 and compatibles"
help
diff --git a/drivers/hwmon/pmbus/mpq8646.c b/drivers/hwmon/pmbus/mpq8646.c
index 5133a2471873..6ac82beb0981 100644
--- a/drivers/hwmon/pmbus/mpq8646.c
+++ b/drivers/hwmon/pmbus/mpq8646.c
@@ -7,6 +7,7 @@
#include <linux/bitops.h>
#include <linux/debugfs.h>
+#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/mutex.h>
@@ -23,8 +24,18 @@
#define MPQ8646_ALARM_POLL_MS_DEFAULT 1000
/* MPS vendor-extended command codes (NOT in PMBus 1.3 Part II) */
+#define MPS_CLEAR_LAST_FAULT 0x08
+#define MPS_MFR_CFG_EXT 0xF5
+#define MPS_MFR_CFG_EXT_CLR_LAST_EN BIT(6)
#define MPS_PROTECTION_LAST 0xFB
+/*
+ * PMBus 1.3 NVM commit / revert commands. MPS equivalent of
+ * STORE_ALL (15h) and RESTORE_ALL (16h).
+ */
+#define PMBUS_STORE_USER_ALL 0x15
+#define PMBUS_RESTORE_USER_ALL 0x16
+
/* PMBus 1.3 timing / UVLO command codes */
#define PMBUS_VIN_ON 0x35
#define PMBUS_VIN_OFF 0x36
@@ -36,10 +47,32 @@
/* MPS vendor-extended observability / identity registers */
#define MPS_MFR_CONFIG_ID 0xC0
#define MPS_MFR_CONFIG_CODE_REV 0xC1
+#define MPS_MFR_PRODUCT_REV_USER 0xC2
#define MPS_MFR_SILICON_REV 0xC3
#define MPS_MFR_RETRY_TIMES 0xF4
#define MPS_MFR_VBOOT_CFG 0xFC
+/*
+ * MPS_MFR_PMBUS_LOCK (EEh): 16-bit WORD whose low two bits gate
+ * subsequent PMBus writes
+ * bits[1:0] = 00 -- unlocked (POR default)
+ * 01 -- lock all writes EXCEPT VOUT_COMMAND (0x21)
+ * so the operator can still DVFS the rail
+ * 11 -- lock all writes
+ * A negative-going PG edge resets these bits to 00, the lock
+ * is operationally reversible without a full chip POR.
+ */
+#define MPS_MFR_PMBUS_LOCK 0xEE
+
+/*
+ * Retry parameters for the MFR_CFG_EXT gate-close write after
+ * CLEAR_LAST_FAULT. Bench-observed NVM-busy NACK window on this
+ * silicon is about 1 ms; the datasheet does not have information.
+ */
+#define MPQ8646_NVM_RETRY_MAX 5
+#define MPQ8646_NVM_RETRY_DELAY_US_MIN 2000
+#define MPQ8646_NVM_RETRY_DELAY_US_MAX 4000
+
#define MPQ8646_DEBUG(client, fmt, ...) \
dev_dbg(&(client)->dev, fmt, ##__VA_ARGS__)
@@ -501,6 +534,259 @@ static int mpq8646_dbg_reg_show(struct seq_file *s, void *unused)
}
DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_reg);
+#ifdef CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE
+/*
+ * Write/provisioning data, disabled by default: NVM commit and
+ * revert, the CLEAR_LAST_FAULT sequences and a small set of named
+ * writable registers.
+ */
+
+static void mpq8646_unsafe_banner(struct mpq8646_priv *priv)
+{
+ dev_warn(&priv->client->dev,
+ "**********************************************************\n"
+ "** WARNING WARNING WARNING WARNING WARNING WARNING **\n"
+ "** **\n"
+ "** The MPQ8646 provisioning debugfs writes are enabled. **\n"
+ "** Wrong register writes can and likely will physically **\n"
+ "** damage or destroy the chip and/or the board. **\n"
+ "** **\n"
+ "** If you see this message and you are not debugging **\n"
+ "** the kernel, report this immediately to your system **\n"
+ "** administrator! **\n"
+ "** **\n"
+ "** WARNING WARNING WARNING WARNING WARNING WARNING **\n"
+ "**********************************************************\n");
+}
+
+static int mpq8646_dbg_clear_protection_last(void *data, u64 val)
+{
+ struct mpq8646_priv *priv = data;
+ int rc;
+
+ if (!val)
+ return 0;
+
+ guard(pmbus_lock)(priv->client);
+ scoped_guard(mutex, &priv->mps_lock)
+ rc = i2c_smbus_write_byte(priv->client, MPS_CLEAR_LAST_FAULT);
+ if (rc < 0)
+ dev_warn(&priv->client->dev,
+ "clear_protection_last: CLEAR_LAST_FAULT write failed (%d)\n",
+ rc);
+ return rc;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_clear_protection_last_fops,
+ NULL, mpq8646_dbg_clear_protection_last, "%llu\n");
+
+static int mpq8646_dbg_clear_protection_last_force(void *data, u64 val)
+{
+ struct mpq8646_priv *priv = data;
+ int rc, ret;
+ int wp_orig, cfg_orig;
+
+ if (!val)
+ return 0;
+
+ guard(pmbus_lock)(priv->client);
+ guard(mutex)(&priv->mps_lock);
+
+ wp_orig = i2c_smbus_read_byte_data(priv->client, PMBUS_WRITE_PROTECT);
+ if (wp_orig < 0) {
+ dev_warn(&priv->client->dev,
+ "clear_protection_last_force: WRITE_PROTECT read failed (%d), aborting\n",
+ wp_orig);
+ return wp_orig;
+ }
+ cfg_orig = i2c_smbus_read_word_data(priv->client, MPS_MFR_CFG_EXT);
+ if (cfg_orig < 0) {
+ dev_warn(&priv->client->dev,
+ "clear_protection_last_force: MFR_CFG_EXT read failed (%d), aborting\n",
+ cfg_orig);
+ return cfg_orig;
+ }
+
+ if (wp_orig != 0) {
+ rc = i2c_smbus_write_byte_data(priv->client,
+ PMBUS_WRITE_PROTECT, 0);
+ if (rc < 0) {
+ dev_warn(&priv->client->dev,
+ "clear_protection_last_force: WP clear failed (%d), aborting\n",
+ rc);
+ return rc;
+ }
+ }
+
+ ret = i2c_smbus_write_word_data(priv->client, MPS_MFR_CFG_EXT,
+ (u16)cfg_orig | MPS_MFR_CFG_EXT_CLR_LAST_EN);
+ if (ret < 0) {
+ dev_warn(&priv->client->dev,
+ "clear_protection_last_force: gate open failed (%d)\n",
+ ret);
+ } else {
+ ret = i2c_smbus_write_byte(priv->client, MPS_CLEAR_LAST_FAULT);
+ if (ret < 0)
+ dev_warn(&priv->client->dev,
+ "clear_protection_last_force: CLEAR_LAST_FAULT failed (%d) even with gate open\n",
+ ret);
+
+ for (int attempt = 0; attempt < MPQ8646_NVM_RETRY_MAX; attempt++) {
+ rc = i2c_smbus_write_word_data(priv->client,
+ MPS_MFR_CFG_EXT,
+ (u16)cfg_orig);
+ if (rc >= 0)
+ break;
+ usleep_range(MPQ8646_NVM_RETRY_DELAY_US_MIN,
+ MPQ8646_NVM_RETRY_DELAY_US_MAX);
+ }
+ if (rc < 0) {
+ dev_warn(&priv->client->dev,
+ "clear_protection_last_force: MFR_CFG_EXT restore failed after retries (%d): gate may stay open until POR\n",
+ rc);
+ if (ret == 0)
+ ret = rc;
+ }
+ }
+
+ if (wp_orig != 0) {
+ rc = i2c_smbus_write_byte_data(priv->client,
+ PMBUS_WRITE_PROTECT,
+ (u8)wp_orig);
+ if (rc < 0) {
+ dev_warn(&priv->client->dev,
+ "clear_protection_last_force: WP restore failed (%d)\n",
+ rc);
+ if (ret == 0)
+ ret = rc;
+ }
+ }
+ return ret;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_clear_protection_last_force_fops,
+ NULL, mpq8646_dbg_clear_protection_last_force, "%llu\n");
+
+static int mpq8646_dbg_store_all(void *data, u64 val)
+{
+ struct mpq8646_priv *priv = data;
+ int rc;
+
+ if (!val)
+ return 0;
+
+ guard(pmbus_lock)(priv->client);
+ scoped_guard(mutex, &priv->mps_lock)
+ rc = i2c_smbus_write_byte(priv->client, PMBUS_STORE_USER_ALL);
+ if (rc < 0)
+ dev_warn(&priv->client->dev,
+ "store_all: STORE_USER_ALL (0x15) write failed (%d)\n",
+ rc);
+ return rc;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_store_all_fops,
+ NULL, mpq8646_dbg_store_all, "%llu\n");
+
+static int mpq8646_dbg_restore_all(void *data, u64 val)
+{
+ struct mpq8646_priv *priv = data;
+ int rc;
+
+ if (!val)
+ return 0;
+
+ guard(pmbus_lock)(priv->client);
+ scoped_guard(mutex, &priv->mps_lock)
+ rc = i2c_smbus_write_byte(priv->client, PMBUS_RESTORE_USER_ALL);
+ if (rc < 0)
+ dev_warn(&priv->client->dev,
+ "restore_all: RESTORE_USER_ALL (0x16) write failed (%d)\n",
+ rc);
+ return rc;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_restore_all_fops,
+ NULL, mpq8646_dbg_restore_all, "%llu\n");
+
+static const struct mpq8646_dbg_reg mpq8646_dbg_regs_unsafe[] = {
+ /* PMBus 1.3 control / margin */
+ { PMBUS_ON_OFF_CONFIG, false, "on_off_config" },
+ { PMBUS_VOUT_MARGIN_HIGH, true, "vout_margin_high" },
+ { PMBUS_VOUT_MARGIN_LOW, true, "vout_margin_low" },
+ /* MPS PMBus-level write-protect */
+ { MPS_MFR_PMBUS_LOCK, true, "mfr_pmbus_lock" },
+ /* MPS user-writable product revision */
+ { MPS_MFR_PRODUCT_REV_USER, true, "mfr_product_rev_user" },
+};
+
+static int mpq8646_dbg_reg_get(void *data, u64 *val)
+{
+ struct mpq8646_dbg_reg_ctx *ctx = data;
+ int rc;
+
+ guard(pmbus_lock)(ctx->priv->client);
+ if (ctx->desc->is_word)
+ rc = i2c_smbus_read_word_data(ctx->priv->client,
+ ctx->desc->reg);
+ else
+ rc = i2c_smbus_read_byte_data(ctx->priv->client,
+ ctx->desc->reg);
+ if (rc < 0)
+ return rc;
+ *val = rc;
+ return 0;
+}
+
+static int mpq8646_dbg_reg_set(void *data, u64 val)
+{
+ struct mpq8646_dbg_reg_ctx *ctx = data;
+ int rc;
+
+ guard(pmbus_lock)(ctx->priv->client);
+ if (ctx->desc->is_word)
+ rc = i2c_smbus_write_word_data(ctx->priv->client,
+ ctx->desc->reg, (u16)val);
+ else
+ rc = i2c_smbus_write_byte_data(ctx->priv->client,
+ ctx->desc->reg, (u8)val);
+ return rc < 0 ? rc : 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_reg_rw_fops,
+ mpq8646_dbg_reg_get, mpq8646_dbg_reg_set, "0x%llx\n");
+
+static void mpq8646_debugfs_register_unsafe(struct mpq8646_priv *priv,
+ struct dentry *root)
+{
+ struct mpq8646_dbg_reg_ctx *ctx;
+ size_t i;
+
+ mpq8646_unsafe_banner(priv);
+
+ debugfs_create_file_unsafe("clear_protection_last", 0200, root, priv,
+ &mpq8646_dbg_clear_protection_last_fops);
+ debugfs_create_file_unsafe("clear_protection_last_force", 0200, root,
+ priv,
+ &mpq8646_dbg_clear_protection_last_force_fops);
+ debugfs_create_file_unsafe("store_all", 0200, root, priv,
+ &mpq8646_dbg_store_all_fops);
+ debugfs_create_file_unsafe("restore_all", 0200, root, priv,
+ &mpq8646_dbg_restore_all_fops);
+
+ ctx = devm_kcalloc(&priv->client->dev,
+ ARRAY_SIZE(mpq8646_dbg_regs_unsafe),
+ sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return;
+ for (i = 0; i < ARRAY_SIZE(mpq8646_dbg_regs_unsafe); i++) {
+ ctx[i].priv = priv;
+ ctx[i].desc = &mpq8646_dbg_regs_unsafe[i];
+ debugfs_create_file_unsafe(mpq8646_dbg_regs_unsafe[i].name,
+ 0600, root, &ctx[i],
+ &mpq8646_dbg_reg_rw_fops);
+ }
+}
+#else
+static inline void mpq8646_debugfs_register_unsafe(struct mpq8646_priv *priv,
+ struct dentry *root) {}
+#endif /* CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE */
+
static int mpq8646_dbg_poll_interval_get(void *data, u64 *val)
{
struct mpq8646_priv *priv = data;
@@ -557,6 +843,8 @@ static void mpq8646_debugfs_register(struct mpq8646_priv *priv)
root, &priv->dbg_reg_ctx[i],
&mpq8646_dbg_reg_fops);
}
+
+ mpq8646_debugfs_register_unsafe(priv, root);
}
static void mpq8646_debugfs_unregister(struct mpq8646_priv *priv)
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v7 2/4] dt-bindings: hwmon: pmbus: add MPS MPQ8646
2026-07-30 15:44 ` [PATCH v7 2/4] dt-bindings: hwmon: pmbus: add MPS MPQ8646 Vincent Jardin via B4 Relay
@ 2026-07-30 15:50 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-07-30 15:50 UTC (permalink / raw)
To: Vincent Jardin; +Cc: linux-hwmon, devicetree, robh, conor+dt
> From: Vincent Jardin <vjardin@free.fr>
>
> The MPS MPQ8646 PMBus step-down converter uses the same
> mps,vout-fb-divider-ratio-permille property with the same constraints
> (maximum 2047, default 706) as the mps,mpq8785, so add its compatible
> to the existing schema.
>
> Signed-off-by: Vincent Jardin <vjardin@free.fr>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260730-mpq8646_v0-v7-0-e7c7ad768d5d@free.fr?part=2
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v7 1/4] hwmon: (pmbus/core) export pmbus_check_and_notify_faults()
2026-07-30 15:43 ` [PATCH v7 1/4] hwmon: (pmbus/core) export pmbus_check_and_notify_faults() Vincent Jardin via B4 Relay
@ 2026-07-30 15:51 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-07-30 15:51 UTC (permalink / raw)
To: Vincent Jardin; +Cc: robh, devicetree, linux-hwmon, conor+dt
> From: Vincent Jardin <vjardin@free.fr>
>
> Factor of pmbus_fault_handler() into an exported helper so
> drivers can notify sequence from a polling work item on boards
> for which the chip's SMBALERT# pin is not wired to the
> CPU. The interrupt handler becomes a thin wrapper.
>
> Like the SMBALERT# path, the helper notifies and then clears the
> latched faults unconditionally, so a polling caller inherits exactly
> the interrupt semantics.
>
> Signed-off-by: Vincent Jardin <vjardin@free.fr>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260730-mpq8646_v0-v7-0-e7c7ad768d5d@free.fr?part=1
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v7 4/4] hwmon: pmbus: mpq8646: gate the writes
2026-07-30 15:44 ` [PATCH v7 4/4] hwmon: pmbus: mpq8646: gate the writes Vincent Jardin via B4 Relay
@ 2026-07-30 16:00 ` sashiko-bot
2026-07-30 18:40 ` Markus Elfring
1 sibling, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-07-30 16:00 UTC (permalink / raw)
To: Vincent Jardin; +Cc: linux-hwmon, conor+dt, devicetree, robh
> From: Vincent Jardin <vjardin@free.fr>
>
> The write cases of debugfs entries are provisioning and bench
> helpers. By designs the MPQ8646 powers the CPU core rail,
> so a wrong write can brown out the board or persist a bad
> setpoint into finite-cycle NVM.
> Said differently: some wrong register writes can and likely
> will physically damage or destroy the chip and/or the board.
>
> To be safe, it is disabled by default behind a
> CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE and we print an explicit warning
> banner at probe time when they are built in.
>
> Signed-off-by: Vincent Jardin <vjardin@free.fr>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260730-mpq8646_v0-v7-0-e7c7ad768d5d@free.fr?part=4
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v7 3/4] hwmon: pmbus: add MPQ8646 driver
2026-07-30 15:44 ` [PATCH v7 3/4] hwmon: pmbus: add MPQ8646 driver Vincent Jardin via B4 Relay
@ 2026-07-30 16:03 ` sashiko-bot
0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-07-30 16:03 UTC (permalink / raw)
To: Vincent Jardin; +Cc: robh, linux-hwmon, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The `alarm_poll_interval_ms` variable is accessed and modified concurrently without any locking or atomic primitives, leading to a data race.
--
--- Patch [3]: [PATCH v7 3/4] hwmon: pmbus: add MPQ8646 driver ---
commit 013992e945508f7fa55c531019d24aec0d1d90cd
Author: Vincent Jardin <vjardin@free.fr>
hwmon: pmbus: add MPQ8646 driver
Add a new driver for the MPS MPQ8646 that is a PMBus device. Includes an
in-driver alarm-poll fallback for boards without SMBALERT.
> diff --git a/drivers/hwmon/pmbus/mpq8646.c b/drivers/hwmon/pmbus/mpq8646.c
> --- /dev/null
> +++ b/drivers/hwmon/pmbus/mpq8646.c
[ ... ]
> +static int mpq8646_dbg_poll_interval_set(void *data, u64 val)
> +{
> + struct mpq8646_priv *priv = data;
> + bool was_off = !priv->alarm_poll_interval_ms;
> +
> + priv->alarm_poll_interval_ms = (u32)val;
[Severity: Low]
Can this result in a data race when accessed concurrently by the polling worker?
If a user writes to the debugfs file while mpq8646_alarm_poll_work() is
running, the worker thread reads this same variable to determine the next
delay:
drivers/hwmon/pmbus/mpq8646.c:mpq8646_alarm_poll_work() {
...
schedule_delayed_work(&priv->alarm_poll_work,
msecs_to_jiffies(priv->alarm_poll_interval_ms));
}
Should these accesses be protected with WRITE_ONCE() and READ_ONCE() to
prevent KCSAN warnings and ensure safe concurrent access?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260730-mpq8646_v0-v7-0-e7c7ad768d5d@free.fr?part=3
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v7 4/4] hwmon: pmbus: mpq8646: gate the writes
2026-07-30 15:44 ` [PATCH v7 4/4] hwmon: pmbus: mpq8646: gate the writes Vincent Jardin via B4 Relay
2026-07-30 16:00 ` sashiko-bot
@ 2026-07-30 18:40 ` Markus Elfring
1 sibling, 0 replies; 10+ messages in thread
From: Markus Elfring @ 2026-07-30 18:40 UTC (permalink / raw)
To: Vincent Jardin, linux-hwmon, devicetree, Charles Hsu,
Conor Dooley, Günter Röck, Jonathan Corbet,
Krzysztof Kozlowski, Rob Herring, Shuah Khan
Cc: linux-doc, LKML
> The write cases of debugfs entries are provisioning and bench
> helpers. By designs the MPQ8646 powers the CPU core rail,
> so a wrong write can brown out the board or persist a bad
> setpoint into finite-cycle NVM.
> Said differently: some wrong register writes can and likely
> will physically damage or destroy the chip and/or the board.
>
> To be safe, it is disabled by default behind a
> CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE and we print an explicit warning
> banner at probe time when they are built in.
See also once more:
* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/process/submitting-patches.rst?h=v7.2-rc5#n94
* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/process/submitting-patches.rst?h=v7.2-rc5#n669
What did hinder you to take linked wording requirements better into account?
Regards,
Markus
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-07-30 18:40 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-30 15:43 [PATCH v7 0/4] hwmon: pmbus: add MPS MPQ8646 support Vincent Jardin via B4 Relay
2026-07-30 15:43 ` [PATCH v7 1/4] hwmon: (pmbus/core) export pmbus_check_and_notify_faults() Vincent Jardin via B4 Relay
2026-07-30 15:51 ` sashiko-bot
2026-07-30 15:44 ` [PATCH v7 2/4] dt-bindings: hwmon: pmbus: add MPS MPQ8646 Vincent Jardin via B4 Relay
2026-07-30 15:50 ` sashiko-bot
2026-07-30 15:44 ` [PATCH v7 3/4] hwmon: pmbus: add MPQ8646 driver Vincent Jardin via B4 Relay
2026-07-30 16:03 ` sashiko-bot
2026-07-30 15:44 ` [PATCH v7 4/4] hwmon: pmbus: mpq8646: gate the writes Vincent Jardin via B4 Relay
2026-07-30 16:00 ` sashiko-bot
2026-07-30 18:40 ` Markus Elfring
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox