* [PATCH v7 0/4] hwmon: pmbus: add MPS MPQ8646 support
@ 2026-07-30 15:43 ` Vincent Jardin
0 siblings, 0 replies; 15+ 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] 15+ messages in thread* [PATCH v7 0/4] hwmon: pmbus: add MPS MPQ8646 support @ 2026-07-30 15:43 ` Vincent Jardin 0 siblings, 0 replies; 15+ messages in thread From: Vincent Jardin @ 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] 15+ messages in thread
* [PATCH v7 1/4] hwmon: (pmbus/core) export pmbus_check_and_notify_faults() 2026-07-30 15:43 ` Vincent Jardin @ 2026-07-30 15:43 ` Vincent Jardin -1 siblings, 0 replies; 15+ 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] 15+ messages in thread
* [PATCH v7 1/4] hwmon: (pmbus/core) export pmbus_check_and_notify_faults() @ 2026-07-30 15:43 ` Vincent Jardin 0 siblings, 0 replies; 15+ messages in thread From: Vincent Jardin @ 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 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] 15+ messages in thread
* Re: [PATCH v7 1/4] hwmon: (pmbus/core) export pmbus_check_and_notify_faults() 2026-07-30 15:43 ` Vincent Jardin (?) @ 2026-07-30 15:51 ` sashiko-bot -1 siblings, 0 replies; 15+ 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] 15+ messages in thread
* [PATCH v7 2/4] dt-bindings: hwmon: pmbus: add MPS MPQ8646 2026-07-30 15:43 ` Vincent Jardin @ 2026-07-30 15:44 ` Vincent Jardin -1 siblings, 0 replies; 15+ 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] 15+ messages in thread
* [PATCH v7 2/4] dt-bindings: hwmon: pmbus: add MPS MPQ8646 @ 2026-07-30 15:44 ` Vincent Jardin 0 siblings, 0 replies; 15+ messages in thread From: Vincent Jardin @ 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 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] 15+ messages in thread
* Re: [PATCH v7 2/4] dt-bindings: hwmon: pmbus: add MPS MPQ8646 2026-07-30 15:44 ` Vincent Jardin (?) @ 2026-07-30 15:50 ` sashiko-bot -1 siblings, 0 replies; 15+ 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] 15+ messages in thread
* [PATCH v7 3/4] hwmon: pmbus: add MPQ8646 driver 2026-07-30 15:43 ` Vincent Jardin @ 2026-07-30 15:44 ` Vincent Jardin -1 siblings, 0 replies; 15+ 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] 15+ messages in thread
* [PATCH v7 3/4] hwmon: pmbus: add MPQ8646 driver @ 2026-07-30 15:44 ` Vincent Jardin 0 siblings, 0 replies; 15+ messages in thread From: Vincent Jardin @ 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 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] 15+ messages in thread
* Re: [PATCH v7 3/4] hwmon: pmbus: add MPQ8646 driver 2026-07-30 15:44 ` Vincent Jardin (?) @ 2026-07-30 16:03 ` sashiko-bot -1 siblings, 0 replies; 15+ 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] 15+ messages in thread
* [PATCH v7 4/4] hwmon: pmbus: mpq8646: gate the writes 2026-07-30 15:43 ` Vincent Jardin @ 2026-07-30 15:44 ` Vincent Jardin -1 siblings, 0 replies; 15+ 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] 15+ messages in thread
* [PATCH v7 4/4] hwmon: pmbus: mpq8646: gate the writes @ 2026-07-30 15:44 ` Vincent Jardin 0 siblings, 0 replies; 15+ messages in thread From: Vincent Jardin @ 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 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] 15+ messages in thread
* Re: [PATCH v7 4/4] hwmon: pmbus: mpq8646: gate the writes 2026-07-30 15:44 ` Vincent Jardin (?) @ 2026-07-30 16:00 ` sashiko-bot -1 siblings, 0 replies; 15+ 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] 15+ messages in thread
* Re: [PATCH v7 4/4] hwmon: pmbus: mpq8646: gate the writes 2026-07-30 15:44 ` Vincent Jardin (?) (?) @ 2026-07-30 18:40 ` Markus Elfring -1 siblings, 0 replies; 15+ 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] 15+ messages in thread
end of thread, other threads:[~2026-07-30 18:40 UTC | newest] Thread overview: 15+ 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 ` Vincent Jardin 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:43 ` Vincent Jardin 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:44 ` Vincent Jardin 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 ` Vincent Jardin 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 15:44 ` Vincent Jardin 2026-07-30 16:00 ` sashiko-bot 2026-07-30 18:40 ` Markus Elfring
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.