From nobody Fri Jul 24 05:24:22 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 671864756AC; Thu, 23 Jul 2026 15:46:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784821572; cv=none; b=NGYmohftdnNwtiDhQWKCPo85V6FZkBKHu8baL8nv1b7Xor9u2ZhU15Zy1WOUQZghFzqrE8En0gS4SiT1DK55ZjuIAB5aB0TCjajViV0G8PiEyGfea9V6btzOUKUc/IeKtjxuX8T4C6k68HRwlUlIJJy6hzaElLz+ydXoCEV4kNU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784821572; c=relaxed/simple; bh=EClhRpruQEvlpGbCphJCmoLGR304M9HBeUainuax8Kk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=ebvTLxo+x/1Z1FnMyPBE/bnhNyYWhqqbIrFDe5jY2TotxXm2MsH+sqVfDLhHKGE36DY2UTu+KaLXtb/FI9bktE1jLGUqWkAFaabcZgXcjHABQW7XlS2WCs1Ik6meQcY7raL1nhMfnoTXEODhqIAbuNyIkzIxxouKIUrGaCj7Kzw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=ZwyIrDY1; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="ZwyIrDY1" Received: by smtp.kernel.org (Postfix) with ESMTPS id BC02AC2BCC9; Thu, 23 Jul 2026 15:46:10 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1784821570; bh=EClhRpruQEvlpGbCphJCmoLGR304M9HBeUainuax8Kk=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=ZwyIrDY1bRfCKW4oxKX5sddxcdBp2/s3MKkrdthreRwUr9P60VRMoBXF7FkjB41h5 RDU9fQ+bhLhNcjBcsxFGa51QCm7qS8gEEvXOuvCw1WG0A3ix3eiPu9YCX/uQbOpMeh BGyDzJfZrd7lyiOyatIkXqQtj2eYaA3N+N5EiNSJ+JRMET7VKMncRxaOkGAchOZRHZ HkKFXr96B+BosCkbPVQMXBq8Uoq/2wPlCJ/V9Ot9UwJFWuuz68CDpBABA96NK0sU9Z 0Q/V2XIQMecF7Em39OE2kdIcE+Zy/i8fxd/i9l+9s9V9xrGcP0ctn+UQZhwwscSD0E cnnf/peXnl1kw== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 99500C531F8; Thu, 23 Jul 2026 15:46:10 +0000 (UTC) From: Vincent Jardin via B4 Relay Date: Thu, 23 Jul 2026 17:46:05 +0200 Subject: [PATCH v2 1/3] hwmon: pmbus: event notification with alarms Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260723-mpq8646_v0-v2-1-3c4cb71f23c0@free.fr> References: <20260723-mpq8646_v0-v2-0-3c4cb71f23c0@free.fr> In-Reply-To: <20260723-mpq8646_v0-v2-0-3c4cb71f23c0@free.fr> To: Guenter Roeck , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Jonathan Corbet , Shuah Khan Cc: linux-hwmon@vger.kernel.org, linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, linux-doc@vger.kernel.org, Vincent Jardin X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784821569; l=2243; i=vjardin@free.fr; s=20260525; h=from:subject:message-id; bh=J9iIMMt2Lp2Xbzm5GXCYVVI1Koec6CMd7Yg+qpVdHaU=; b=0MHJ2fG9W4yTZSOsD5TrYqcJQIin0dQUHqNpzIB3lEDIt7IW6DItksPP0u/R3HuwAR392eCGw zIvOBGi3p1hCRXSx27CxO5bru/6u3f+YwaimukMq22YUs3bORXlQnPF X-Developer-Key: i=vjardin@free.fr; a=ed25519; pk=hppgLeFpGpKOi7LNwGEZ4jOYofJCoGd4Jf1ltAabiLw= X-Endpoint-Received: by B4 Relay for vjardin@free.fr/20260525 with auth_id=871 X-Original-From: Vincent Jardin Reply-To: vjardin@free.fr From: Vincent Jardin Let's add an accessor for the hwmon class device the pmbus core registered for an i2c_client. Export it with PMBUS so chip drivers can call hwmon_notify_event() on per-sensor *_alarm attributes from the driver's work items. The needs: for boards when the chip's SMBALERT# pin is not available for the CPU, pmbus_irq_setup() cannot deliver poll(POLLPRI) wakes or udev change@... events on the inX_alarm files. So the drivers can install a delayed_work-based polling fallback (like the lm90 driver) that periodically reads STATUS_WORD and calls hwmon_notify_event() on 0->1 transitions. Signed-off-by: Vincent Jardin --- drivers/hwmon/pmbus/pmbus.h | 1 + drivers/hwmon/pmbus/pmbus_core.c | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index 23e3eda58870..46ab056c0058 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -565,6 +565,7 @@ bool pmbus_check_word_register(struct i2c_client *clien= t, int page, int reg); int pmbus_do_probe(struct i2c_client *client, struct pmbus_driver_info *in= fo); const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client *client); +struct device *pmbus_get_hwmon_device(struct i2c_client *client); int pmbus_get_fan_rate_device(struct i2c_client *client, int page, int id, enum pmbus_fan_mode mode); int pmbus_get_fan_rate_cached(struct i2c_client *client, int page, int id, diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_c= ore.c index 3143b9e0316c..6ba00ad73297 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -695,6 +695,14 @@ const struct pmbus_driver_info *pmbus_get_driver_info(= struct i2c_client *client) } EXPORT_SYMBOL_NS_GPL(pmbus_get_driver_info, "PMBUS"); =20 +struct device *pmbus_get_hwmon_device(struct i2c_client *client) +{ + struct pmbus_data *data =3D i2c_get_clientdata(client); + + return data->hwmon_dev; +} +EXPORT_SYMBOL_NS_GPL(pmbus_get_hwmon_device, "PMBUS"); + static int pmbus_get_status(struct i2c_client *client, int page, int reg) { struct pmbus_data *data =3D i2c_get_clientdata(client); --=20 2.43.0 From nobody Fri Jul 24 05:24:22 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 67487477289; Thu, 23 Jul 2026 15:46:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784821572; cv=none; b=QfunASs/JHe1zHdnlCGxSFt1ItWjAJrjkGukWAGaEnGFCvORVjViMaLpaIGLZzyInaUKFvpNkcC4BG5hJrn/5syEpPTEJLdfZS+5tSzsJ2yB30dgUq9Ydi/hez6NcWudCXD9oHUUUtzXNNA14fDWKqFhtz/2Q+MQQPDJv4BQXr4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784821572; c=relaxed/simple; bh=Hlkt1la4nswj+5NkXwp/Yf8uwqOeZQ07zTTI9UXQiuA=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=RrgqvyoZaLk3jMDfG3AD9JP1gjolpx34itM4Y2AcSRCVNOexSsa6D52HaX6atZxvWjD60/z2wyrfXmgCuTh7omLPNkQLsUhq1zHrQADI45VmIPAXTtypDal91hKtElOJHWgdjJo5wX2jw7RaEaMTeJNQ1gvWZ0VYkbVD8EVg31o= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=QiCqkbcI; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="QiCqkbcI" Received: by smtp.kernel.org (Postfix) with ESMTPS id C437EC2BCF7; Thu, 23 Jul 2026 15:46:10 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1784821570; bh=Hlkt1la4nswj+5NkXwp/Yf8uwqOeZQ07zTTI9UXQiuA=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=QiCqkbcIjSq6gGH735zUflaW5V+2Xu84ubSqSPR8jutVWoiDtpGc+EzcaSlXHOo2q exAFelOewsmFc9DR3J+re1O8/ASlZTOaSFzCz56/J8i5urHBkTQ9C8sRciH491LEFH VP6e659NhFn4/sABu3KGzKiK/7KL8ye3KoZsePSevqqaXD+DhPIc2L6g3TLNnqo4MH sOfzDLZaoXachvtGV1miPdZylp4f4XGpGL8X7ukDoQQm9BSKR9snBMtiR8CQiaUrg/ JzfUp2SuHAHGqzjPho9IxZ+UNl9aLUAOzyN1/1AjRG4vNido21Y5OqGIDP1dbKZ09p PzMPP0cs+eRbQ== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id A9A6EC531F9; Thu, 23 Jul 2026 15:46:10 +0000 (UTC) From: Vincent Jardin via B4 Relay Date: Thu, 23 Jul 2026 17:46:06 +0200 Subject: [PATCH v2 2/3] dt-bindings: hwmon: pmbus: add MPS MPQ8646 binding Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260723-mpq8646_v0-v2-2-3c4cb71f23c0@free.fr> References: <20260723-mpq8646_v0-v2-0-3c4cb71f23c0@free.fr> In-Reply-To: <20260723-mpq8646_v0-v2-0-3c4cb71f23c0@free.fr> To: Guenter Roeck , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Jonathan Corbet , Shuah Khan Cc: linux-hwmon@vger.kernel.org, linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, linux-doc@vger.kernel.org, Vincent Jardin X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784821569; l=1981; i=vjardin@free.fr; s=20260525; h=from:subject:message-id; bh=0wKiuVAue/U1uDeAsZEu1pkRFNe5eYJ+NCPIIaUPwzg=; b=oC/RvoOk2MrEuXITMTkWXS3dmaVXYw1F5RF9umCD+evaPmx0wyHJ050zdOrexXH4E3HoIgC4U h93ilgcFCbnC9QQu9Rl+BlnwrBncHVlBYDo4RiVLEEd+5AWYQquXNAl X-Developer-Key: i=vjardin@free.fr; a=ed25519; pk=hppgLeFpGpKOi7LNwGEZ4jOYofJCoGd4Jf1ltAabiLw= X-Endpoint-Received: by B4 Relay for vjardin@free.fr/20260525 with auth_id=871 X-Original-From: Vincent Jardin Reply-To: vjardin@free.fr From: Vincent Jardin Add a device-tree binding for the Monolithic Power Systems MPQ8646 step-down converter as a PMBus device. The schema is inspired from the mpq8785e: same mps,vout-fb-divider-ratio-permille property (maximum: 2047), and const: mps,mpq8646. Signed-off-by: Vincent Jardin --- .../bindings/hwmon/pmbus/mps,mpq8646.yaml | 50 ++++++++++++++++++= ++++ 1 file changed, 50 insertions(+) diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/mps,mpq8646.yaml= b/Documentation/devicetree/bindings/hwmon/pmbus/mps,mpq8646.yaml new file mode 100644 index 000000000000..401a80325b4b --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/pmbus/mps,mpq8646.yaml @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +# Copyright (c) 2026 Free Mobile - Vincent Jardin +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/pmbus/mps,mpq8646.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Monolithic Power Systems MPQ8646 PMBus Step-Down Converter (extende= d) + +maintainers: + - Vincent Jardin + +description: | + PMBus driver for the MPS MPQ8646 step-down converter. + +properties: + compatible: + const: mps,mpq8646 + + reg: + maxItems: 1 + + mps,vout-fb-divider-ratio-permille: + description: + The feedback resistor divider ratio, expressed in permille + (Vfb / Vout * 1000). This value is written to the + PMBUS_VOUT_SCALE_LOOP register and is required for correct output + voltage presentation. + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 1 + maximum: 2047 + default: 706 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + regulator@10 { + compatible =3D "mps,mpq8646"; + reg =3D <0x10>; + }; + }; --=20 2.43.0 From nobody Fri Jul 24 05:24:22 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 63D59484251; Thu, 23 Jul 2026 15:46:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784821572; cv=none; b=mDSYwhHPZ0ShbqcCEI3oyWRB/UhQ3Yiys3YVPjl6iMtJEUlqpmhe+kA7+84N4/L0GF0QOKb1a9i+TvT1cXhM8tjG1HMf4oG1IDo+4JzieFE/0+LYySpKaVfmskuiDXaatYwcINrHcUBWRoL/yWT1GwBfpkA63NTEttk5DULtKAk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784821572; c=relaxed/simple; bh=/tipOe3lhtRfIZ+7ld22R0y8ZzZJqW9r+r+1smna6bM=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=SeJz/SMsLxJDV3mmED+Xr9AqgcbTyRRyFlSn4tnRU2uQYY10oI3rCiC3Ntg8sYFbOO6f6oUwi7J1Y/zc9uhbWAjNp0Nf+ieub8lQpxVdaWfOoLcTxvqE3PGF2Go1PwcjVkwvYaVV2fy9MFWDnJglShIjO4R1cUbniNkoK+d1CvU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=bWAr+gKd; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="bWAr+gKd" Received: by smtp.kernel.org (Postfix) with ESMTPS id D0CE6C2BCFA; Thu, 23 Jul 2026 15:46:10 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1784821570; bh=/tipOe3lhtRfIZ+7ld22R0y8ZzZJqW9r+r+1smna6bM=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=bWAr+gKdcrSM2pZTchn4zrfKD/kvpDFsyjIpywIHcHfNnZpX7eYBEdB0dV7juWd6D 0bh4tWJMzcGORgJGCJ4qtdijCTPRj1MdIaYSJh9Q5tqSc4K78nYvpTa86fyPY9Gd7L ERjA4heOw2JLUSj1y45Fdf8AsOzrTik/dsnf7uV/2ji2l7O9K9zCYTTa1QtnMIgNlI 7+KDjQxIS3suwvBrF0mPBJPkq7KWVLTTnQGSQlDaswxuuWyAjwh8yyQ8+W/e/d95KI U6VRIteKGxFZImwwFGQGKN7Pgc9Y2lbS4KJ2eOvD2qnSa+hdqd/Xp3NSG8na6c0/Bs Ags5IhZVFS1xg== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id BCCE9C531F7; Thu, 23 Jul 2026 15:46:10 +0000 (UTC) From: Vincent Jardin via B4 Relay Date: Thu, 23 Jul 2026 17:46:07 +0200 Subject: [PATCH v2 3/3] hwmon: pmbus: add MPQ8646 driver Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260723-mpq8646_v0-v2-3-3c4cb71f23c0@free.fr> References: <20260723-mpq8646_v0-v2-0-3c4cb71f23c0@free.fr> In-Reply-To: <20260723-mpq8646_v0-v2-0-3c4cb71f23c0@free.fr> To: Guenter Roeck , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Jonathan Corbet , Shuah Khan Cc: linux-hwmon@vger.kernel.org, linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, linux-doc@vger.kernel.org, Vincent Jardin X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784821569; l=53989; i=vjardin@free.fr; s=20260525; h=from:subject:message-id; bh=pC89f2lLFj8cF7zBD1kBQV41ubLULM9VAcCpXY6x6tI=; b=OQ9fCXDTB6UAb//TSyQ5J8pUUFm/XGNvjMsnRuZ66uklZKtYk8UhnnGk4nP8dNuHXLsNMkO69 pA2WSeQEYoiDKOnF3DqR/Ow9PibP2naVUF+X/Xu65MPyxLDngKRbCw7 X-Developer-Key: i=vjardin@free.fr; a=ed25519; pk=hppgLeFpGpKOi7LNwGEZ4jOYofJCoGd4Jf1ltAabiLw= X-Endpoint-Received: by B4 Relay for vjardin@free.fr/20260525 with auth_id=871 X-Original-From: Vincent Jardin Reply-To: vjardin@free.fr From: Vincent Jardin Add a new single-chip driver for the MPS MPQ8646 that is a PMBus device. Beyond basic PMBus telemetry, the driver adds: - MFR_CFG_EXT gate-close retry after CLEAR_LAST_FAULT. - alarm acknowledge via inX_reset_history. - STATUS_WORD MPS-extended bit decode + post-mortem clear. - In-driver alarm-poll fallback work item (thanks lm90) for boards without SMBALERT - on_off_config/vout_margin/mfr_pmbus_lock sysfs. Signed-off-by: Vincent Jardin --- Documentation/hwmon/mpq8646.rst | 314 ++++++++++ MAINTAINERS | 8 + drivers/hwmon/pmbus/Kconfig | 11 + drivers/hwmon/pmbus/Makefile | 1 + drivers/hwmon/pmbus/mpq8646.c | 1223 +++++++++++++++++++++++++++++++++++= ++++ 5 files changed, 1557 insertions(+) diff --git a/Documentation/hwmon/mpq8646.rst b/Documentation/hwmon/mpq8646.= rst new file mode 100644 index 000000000000..61ca2a5387da --- /dev/null +++ b/Documentation/hwmon/mpq8646.rst @@ -0,0 +1,314 @@ +.. SPDX-License-Identifier: GPL-2.0-only +.. Copyright (c) 2026 Free Mobile - Vincent Jardin + +Kernel driver mpq8646 +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Supported chips: + + * MPS MPQ8646 + + Prefix: 'mpq8646' + +Author: + - Vincent Jardin + +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, see the use of the +``clear_protection_last[_force]`` debugfs entries described below. + +Regulator framework integration +------------------------------- + +When ``CONFIG_REGULATOR=3Dy`` 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 (inspired from ``drivers/hwmon/lm90.c``) that +periodically reads ``STATUS_WORD`` and calls ``hwmon_notify_event()`` +on 0->1 transitions. The frequency of polling is tunable: + +:: + + /sys/kernel/debug/mpq8646/-/alarm_poll_interval_ms + +The default is 1000 ms. + +Set it to 0 to disable. The worker self-suppresses +when ``client->irq !=3D 0`` (i.e. when DT supplies an +``interrupts =3D <...>`` 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/mpq8646/-/protection_last (RO) + /sys/kernel/debug/mpq8646/-/clear_protection_last (WO) + /sys/kernel/debug/mpq8646/-/clear_protection_last_force (WO) + /sys/kernel/debug/mpq8646/-/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``). + +``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 dansing: + +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] =3D 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`` + +``status_decoded`` reads ``STATUS_WORD`` (0x79) and renders the +16 bits with MPS-extension labels (bit12 =3D ``NVM_SUMMARY``, +bit8 =3D ``WATCH_DOG``, bit0 =3D ``DRMOS_FAULT``) instead of the +PMBus 1.3 spec generic names. + +NVMEM snapshot +-------------- + +When ``CONFIG_NVMEM=3Dy`` 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//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=3Dy`` is set, the driver exposes the following +entries under ``/sys/kernel/debug/mpq8646/-/``. Most are diagno= stic +probes; the read/write entries (marked R/W) are direct accessors to +the underlying chip command. + +Identity / observability (read-only): + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D +File PMBus / MFR cmd Description +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D +``mfr_config_id`` 0xC0 (word) board / SKU NVM identifier +``mfr_config_code_rev`` 0xC1 (word) NVM image revision (PART_R= ECOG + + config code rev fields) +``mfr_silicon_rev`` 0xC3 (byte) die revision +``mfr_retry_times`` 0xF4 (word) per-fault-class recovery-m= ode + configuration (NOT a retry + count). Four 4-bit fields + [15:12]=3DOTP, [11:8]=3DVO= UT_OV, + [7:4]=3DVOUT_UV, [3:0]=3DO= CP. + Each field: 0x0=3Dlatch-of= f, + 0x1..0xE=3Dretry N times t= hen + latch off, 0xF=3Dhiccup + (retry indefinitely). The + chip exposes no in-NVM + retry-event counter; track + transitions externally if + needed (poll + ``protection_last`` or wat= ch + ``inX_alarm`` / ``temp1_al= arm`` + ``poll(POLLPRI)`` wakes). +``mfr_vboot_cfg`` 0xFC (word) ADDR/VBOOT latched at POR +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D + +Timing / UVLO knobs (read-only): + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D +File PMBus cmd Description +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D +``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 +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D + +Configuration (read/write): + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D +File PMBus / MFR cmd Description +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D +``on_off_config`` 0x02 (byte) PMBus vs CTRL-pin on/off s= ource + + active polarity +``vout_margin_high`` 0x25 (word) production margin-high VOU= T setpoint +``vout_margin_low`` 0x26 (word) production margin-low VOUT= setpoint +``mfr_pmbus_lock`` 0xEE (word) programmable PMBus write-l= ock + (independent of WRITE_PROT= ECT) +``mfr_product_rev_user`` 0xC2 (word) user-programmable product = revision +``alarm_poll_interval_ms`` -- alarm-poll worker cadence = (driver- + local, not a chip register) +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D + +NVM commit / revert (write-only): + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D +File PMBus cmd Description +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D +``store_all`` 0x15 STORE_USER_ALL Send-Byte (= commit RAM + config to chip NVM) +``restore_all`` 0x16 RESTORE_USER_ALL Send-Byte= (revert + RAM to last-NVM image) +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D + +Bring-up probes (write-only triggers, results in ``last_probe``): + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D +File Action +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D +``probe_smbus_rword `` i2c_smbus_read_word_data on +``probe_smbus_rbyte `` i2c_smbus_read_byte_data on +``probe_raw_xfer `` raw i2c_transfer read-word on +``probe_page_write `` write PMBUS_PAGE =3D +``probe_clear_faults`` send CLEAR_FAULTS (0x03) Send-Byte +``force_raw_xfer`` if 1, the read_word_data hook uses raw i2c_tr= ansfer +``delay_us`` udelay before each chip-driver hook call +``stats`` per-hook call / error counters +``last_probe`` result of the most recent probe_* operation +``reset_stats`` zero the stats counters +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D + +Devicetree +---------- + +The driver uses ``compatible =3D "mps,mpq8646"``. There are some few optio= nal +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 =3D <...>`` : 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..f4766a851df5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18328,6 +18328,14 @@ S: Maintained F: Documentation/hwmon/mp9945.rst F: drivers/hwmon/pmbus/mp9945.c =20 +MPS MPQ8646 PMBUS DRIVER +M: Vincent Jardin +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/hwmon/pmbus/mps,mpq8646.yaml +F: Documentation/hwmon/mpq8646.rst +F: drivers/hwmon/pmbus/mpq8646.c + MR800 AVERMEDIA USB FM RADIO DRIVER M: Alexey Klimov 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. =20 +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) +=3D mp9941.o obj-$(CONFIG_SENSORS_MP9945) +=3D mp9945.o obj-$(CONFIG_SENSORS_MPQ7932) +=3D mpq7932.o obj-$(CONFIG_SENSORS_MPQ8785) +=3D mpq8785.o +obj-$(CONFIG_SENSORS_MPQ8646) +=3D mpq8646.o obj-$(CONFIG_SENSORS_PLI1209BC) +=3D pli1209bc.o obj-$(CONFIG_SENSORS_PM6764TR) +=3D pm6764tr.o obj-$(CONFIG_SENSORS_PXE1610) +=3D pxe1610.o diff --git a/drivers/hwmon/pmbus/mpq8646.c b/drivers/hwmon/pmbus/mpq8646.c new file mode 100644 index 000000000000..b08b058aeee8 --- /dev/null +++ b/drivers/hwmon/pmbus/mpq8646.c @@ -0,0 +1,1223 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for MPS MPQ8646 step-down converter. + * + * Copyright (c) 2026 Free Mobile - Vincent Jardin + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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_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. pmbus.h does not provide + * named constants for these. 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 that pmbus.h doesn't expose */ +#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_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] =3D 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 + +/* + * VOUT_MODE invalid-value sentinel. PMBus chips return all-ones on + * unimplemented-register reads (a 0xFF byte is otherwise a legal + * encoding for VID mode 0b111, so we treat exactly 0xFF as + * "register absent" rather than "valid mode 7"). + */ +#define PB_VOUT_MODE_INVALID 0xFF + +/* + * VOUT_MODE byte layout per PMBus 1.3 Part II Table 2: + * [7:5] mode (0=3DLINEAR16, 1=3DVID, 2=3DDIRECT, ...) + * [4:0] mode-specific parameter (e.g. linear-format exponent) + * pmbus.h gives us PB_VOUT_MODE_MODE_MASK (0xE0) and + * PB_VOUT_MODE_LINEAR/VID/DIRECT as bit-aligned mask values; we + * still need an explicit shift to compare them after a right-shift + * to-the-LSB. + */ +#define PB_VOUT_MODE_MODE_SHIFT 5 + +#define MPQ8646_TRACE(fmt, ...) \ + pr_debug("mpq8646-trace: " 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; + + atomic_t read_byte_calls; + atomic_t read_word_calls; + atomic_t write_byte_calls; + atomic_t read_byte_err; + atomic_t read_word_err; + atomic_t clear_faults_swallowed; + atomic_t page_writes_swallowed; + + bool debug_force_raw_xfer; + unsigned int debug_delay_us; + + int last_probe_rc; + u16 last_probe_data; + + /* Serialises CLEAR_LAST_FAULT sequences and PROTECTION_LAST reads */ + struct mutex mps_lock; + + /* + * Adapted from lm90's alert_work pattern + * Set alarm_poll_interval_ms =3D 0 to disable. + */ + struct delayed_work alarm_poll_work; + struct device *hwmon_dev; + u16 last_status_word; + u32 alarm_poll_interval_ms; + +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs_root; + struct mpq8646_dbg_reg_ctx *dbg_reg_ctx; +#endif +}; + +struct mpq_status_bit { + u16 mask; + const char *name; +}; + +static const struct mpq_status_bit mpq8646_status_word_bits[] =3D { + { 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[] =3D { + { 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 inline struct mpq8646_priv *mpq8646_priv_from_client(struct i2c_cli= ent *client) +{ + const struct pmbus_driver_info *info =3D pmbus_get_driver_info(client); + + return container_of(info, struct mpq8646_priv, info); +} + +static int mpq8646_raw_xfer_rword(struct i2c_client *client, u8 reg) +{ + u8 cmd =3D reg; + __le16 data =3D 0; + struct i2c_msg msg[] =3D { + { + .addr =3D client->addr, + .flags =3D 0, + .len =3D sizeof(cmd), + .buf =3D &cmd, + }, + { + .addr =3D client->addr, + .flags =3D I2C_M_RD, + .len =3D sizeof(data), + .buf =3D (u8 *)&data, + }, + }; + int rc; + + rc =3D i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); + if (rc < 0) + return rc; + if (rc !=3D ARRAY_SIZE(msg)) + return -EIO; + return le16_to_cpu(data); +} + +/* + * 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 =3D (Y * 10^-R - b) / m, so m=3D64 / b=3D0 / R=3D1 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 =3D pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); + if (vout_mode < 0) + return vout_mode; + if (vout_mode =3D=3D PB_VOUT_MODE_INVALID) + return -ENODEV; + + switch ((vout_mode & PB_VOUT_MODE_MODE_MASK) >> PB_VOUT_MODE_MODE_SHIFT) { + case PB_VOUT_MODE_LINEAR >> PB_VOUT_MODE_MODE_SHIFT: + info->format[PSC_VOLTAGE_OUT] =3D linear; + break; + case PB_VOUT_MODE_VID >> PB_VOUT_MODE_MODE_SHIFT: + case PB_VOUT_MODE_DIRECT >> PB_VOUT_MODE_MODE_SHIFT: + info->format[PSC_VOLTAGE_OUT] =3D direct; + info->m[PSC_VOLTAGE_OUT] =3D MPQ8646_VID_M; + info->b[PSC_VOLTAGE_OUT] =3D MPQ8646_VID_B; + info->R[PSC_VOLTAGE_OUT] =3D MPQ8646_VID_R; + break; + default: + return -ENODEV; + } + + return 0; +}; + +static int mpq8646_read_byte_data(struct i2c_client *client, int page, int= reg) +{ + struct mpq8646_priv *priv =3D mpq8646_priv_from_client(client); + int ret; + + atomic_inc(&priv->read_byte_calls); + MPQ8646_TRACE("read_byte_data page=3D%d reg=3D0x%02x\n", page, reg); + + if (priv->debug_delay_us) + udelay(priv->debug_delay_us); + + switch (reg) { + case PMBUS_VOUT_MODE: + ret =3D pmbus_read_byte_data(client, page, reg); + MPQ8646_TRACE(" VOUT_MODE raw=3D0x%02x ret=3D%d\n", ret, ret); + if (ret < 0) { + atomic_inc(&priv->read_byte_err); + return ret; + } + + if ((ret >> 5) =3D=3D 1) + return PB_VOUT_MODE_DIRECT; + + return ret; + case PMBUS_WRITE_PROTECT: + MPQ8646_TRACE(" WRITE_PROTECT shimmed to 0 (framework path)\n"); + return 0; + 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_TRACE(" unsupported mfr-info reg 0x%02x -> ENXIO\n", reg); + return -ENXIO; + default: + return -ENODATA; + } +} + +static int mpq8646_write_byte(struct i2c_client *client, int page, u8 valu= e) +{ + struct mpq8646_priv *priv =3D mpq8646_priv_from_client(client); + + atomic_inc(&priv->write_byte_calls); + MPQ8646_TRACE("write_byte page=3D%d value=3D0x%02x\n", page, value); + + if (priv->debug_delay_us) + udelay(priv->debug_delay_us); + + if (value =3D=3D PMBUS_PAGE) { + atomic_inc(&priv->page_writes_swallowed); + return 0; + } + + return -ENODATA; /* let core do the standard direct write */ +} + +/* + * 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 =3D 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_TRACE("reset_history virt reg=3D0x%04x -> CLEAR_FAULTS\n", + reg); + rc =3D i2c_smbus_write_byte(priv->client, PMBUS_CLEAR_FAULTS); + if (rc < 0) + MPQ8646_TRACE(" CLEAR_FAULTS rc=3D%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) +{ + struct mpq8646_priv *priv =3D mpq8646_priv_from_client(client); + int rc; + + atomic_inc(&priv->read_word_calls); + MPQ8646_TRACE("read_word_data page=3D%d phase=3D%d reg=3D0x%02x%s\n", + page, phase, reg, + priv->debug_force_raw_xfer ? " [force-raw]" : ""); + + if (priv->debug_delay_us) + udelay(priv->debug_delay_us); + + 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; + } + + if (priv->debug_force_raw_xfer) + rc =3D mpq8646_raw_xfer_rword(client, reg); + else + rc =3D i2c_smbus_read_word_data(client, reg); + + if (rc < 0) + atomic_inc(&priv->read_word_err); + + MPQ8646_TRACE(" reg=3D0x%02x rc=3D%d\n", reg, rc); + return rc; +} + +static struct pmbus_driver_info mpq8646_info =3D { + .pages =3D 1, + .format[PSC_VOLTAGE_IN] =3D direct, + .format[PSC_CURRENT_OUT] =3D direct, + .format[PSC_TEMPERATURE] =3D direct, + .m[PSC_VOLTAGE_IN] =3D 4, + .b[PSC_VOLTAGE_IN] =3D 0, + .R[PSC_VOLTAGE_IN] =3D 1, + .m[PSC_CURRENT_OUT] =3D 16, + .b[PSC_CURRENT_OUT] =3D 0, + .R[PSC_CURRENT_OUT] =3D 0, + .m[PSC_TEMPERATURE] =3D 1, + .b[PSC_TEMPERATURE] =3D 0, + .R[PSC_TEMPERATURE] =3D 0, + .func[0] =3D 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[] =3D { + PMBUS_REGULATOR("vout", 0), +}; +#endif /* CONFIG_REGULATOR */ + +#if IS_ENABLED(CONFIG_NVMEM) +/* + * Expose using + * /sys/bus/nvmem/devices//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[] =3D { + { 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 =3D data; + u8 *out =3D val; + size_t i; + + if (offset >=3D MPQ8646_NVMEM_SIZE) + return -EINVAL; + if (offset + bytes > MPQ8646_NVMEM_SIZE) + bytes =3D MPQ8646_NVMEM_SIZE - offset; + + memset(out, 0, bytes); + + mutex_lock(&priv->mps_lock); + for (i =3D 0; i < ARRAY_SIZE(mpq8646_nvmem_map); i++) { + const struct mpq8646_nvmem_entry *e =3D &mpq8646_nvmem_map[i]; + unsigned int e_start =3D e->off; + unsigned int e_end =3D e_start + (e->is_word ? 2 : 1); + u8 raw[2]; + int rc; + unsigned int j; + + if (e_end <=3D offset || e_start >=3D offset + bytes) + continue; /* outside requested slice */ + + if (e->is_word) + rc =3D i2c_smbus_read_word_data(priv->client, e->reg); + else + rc =3D i2c_smbus_read_byte_data(priv->client, e->reg); + if (rc < 0) + continue; /* leave the zero-fill in place */ + + raw[0] =3D rc & 0xff; + raw[1] =3D (rc >> 8) & 0xff; + + for (j =3D 0; j < e_end - e_start; j++) { + unsigned int abs =3D e_start + j; + + if (abs >=3D offset && abs < offset + bytes) + out[abs - offset] =3D raw[j]; + } + } + mutex_unlock(&priv->mps_lock); + return 0; +} +#endif /* CONFIG_NVMEM */ + +static const struct i2c_device_id mpq8646_id[] =3D { + { "mpq8646", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, mpq8646_id); + +static const struct of_device_id __maybe_unused mpq8646_of_match[] =3D { + { .compatible =3D "mps,mpq8646" }, + {} +}; +MODULE_DEVICE_TABLE(of, mpq8646_of_match); + +static struct pmbus_platform_data mpq8646_no_pec_pdata =3D { + .flags =3D PMBUS_NO_CAPABILITY, +}; + +#ifdef CONFIG_DEBUG_FS +/* + * /sys/kernel/debug/mpq8646/-/ + * probe_smbus_rword echo call i2c_smbus_read_word_data + * probe_smbus_rbyte echo call i2c_smbus_read_byte_data + * probe_raw_xfer echo call i2c_transfer manually + * probe_page_write echo write PMBUS_PAGE =3D pg + * probe_clear_faults echo 1 send CLEAR_FAULTS Send-Byte + * force_raw_xfer 0|1 production read_word_data path + * delay_us udelay before each hook + * last_probe read-only "rc=3D data=3D0x" + * stats read-only counters + * reset_stats write-only zero counters + */ + +static int mpq8646_dbg_probe_smbus_rword(void *data, u64 val) +{ + struct mpq8646_priv *priv =3D data; + int rc; + + rc =3D i2c_smbus_read_word_data(priv->client, (u8)val); + priv->last_probe_rc =3D rc; + priv->last_probe_data =3D (rc < 0) ? 0 : (u16)rc; + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_probe_smbus_rword_fops, + NULL, mpq8646_dbg_probe_smbus_rword, "%llu\n"); + +static int mpq8646_dbg_probe_smbus_rbyte(void *data, u64 val) +{ + struct mpq8646_priv *priv =3D data; + int rc; + + rc =3D i2c_smbus_read_byte_data(priv->client, (u8)val); + priv->last_probe_rc =3D rc; + priv->last_probe_data =3D (rc < 0) ? 0 : (u8)rc; + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_probe_smbus_rbyte_fops, + NULL, mpq8646_dbg_probe_smbus_rbyte, "%llu\n"); + +static int mpq8646_dbg_probe_raw_xfer(void *data, u64 val) +{ + struct mpq8646_priv *priv =3D data; + int rc; + + rc =3D mpq8646_raw_xfer_rword(priv->client, (u8)val); + priv->last_probe_rc =3D rc; + priv->last_probe_data =3D (rc < 0) ? 0 : (u16)rc; + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_probe_raw_xfer_fops, + NULL, mpq8646_dbg_probe_raw_xfer, "%llu\n"); + +static int mpq8646_dbg_probe_page_write(void *data, u64 val) +{ + struct mpq8646_priv *priv =3D data; + int rc; + + rc =3D i2c_smbus_write_byte_data(priv->client, PMBUS_PAGE, (u8)val); + priv->last_probe_rc =3D rc; + priv->last_probe_data =3D 0; + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_probe_page_write_fops, + NULL, mpq8646_dbg_probe_page_write, "%llu\n"); + +static int mpq8646_dbg_probe_clear_faults(void *data, u64 val) +{ + struct mpq8646_priv *priv =3D data; + int rc; + + if (!val) + return 0; + rc =3D i2c_smbus_write_byte(priv->client, PMBUS_CLEAR_FAULTS); + priv->last_probe_rc =3D rc; + priv->last_probe_data =3D 0; + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_probe_clear_faults_fops, + NULL, mpq8646_dbg_probe_clear_faults, "%llu\n"); + +static int mpq8646_dbg_last_probe_show(struct seq_file *s, void *unused) +{ + struct mpq8646_priv *priv =3D s->private; + + seq_printf(s, "rc=3D%d data=3D0x%04x\n", + priv->last_probe_rc, priv->last_probe_data); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_last_probe); + +static int mpq8646_dbg_stats_show(struct seq_file *s, void *unused) +{ + struct mpq8646_priv *priv =3D s->private; + + seq_printf(s, + "read_byte_calls : %d\n" + "read_word_calls : %d\n" + "write_byte_calls : %d\n" + "read_byte_err : %d\n" + "read_word_err : %d\n" + "clear_faults_swallowed : %d\n" + "page_writes_swallowed : %d\n" + "force_raw_xfer : %d\n" + "delay_us : %u\n", + atomic_read(&priv->read_byte_calls), + atomic_read(&priv->read_word_calls), + atomic_read(&priv->write_byte_calls), + atomic_read(&priv->read_byte_err), + atomic_read(&priv->read_word_err), + atomic_read(&priv->clear_faults_swallowed), + atomic_read(&priv->page_writes_swallowed), + priv->debug_force_raw_xfer, + priv->debug_delay_us); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_stats); + +static int mpq8646_dbg_reset_stats(void *data, u64 val) +{ + struct mpq8646_priv *priv =3D data; + + if (!val) + return 0; + atomic_set(&priv->read_byte_calls, 0); + atomic_set(&priv->read_word_calls, 0); + atomic_set(&priv->write_byte_calls, 0); + atomic_set(&priv->read_byte_err, 0); + atomic_set(&priv->read_word_err, 0); + atomic_set(&priv->clear_faults_swallowed, 0); + atomic_set(&priv->page_writes_swallowed, 0); + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_reset_stats_fops, + NULL, mpq8646_dbg_reset_stats, "%llu\n"); + +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 =3D true; + + seq_printf(s, "0x%04x", v); + if (!v) { + seq_puts(s, " [clean]\n"); + return; + } + seq_puts(s, " ["); + for (t =3D tab; t->mask; t++) { + if (v & t->mask) { + if (!first) + seq_putc(s, ' '); + seq_puts(s, t->name); + first =3D false; + } + } + seq_puts(s, "]\n"); +} + +static int mpq8646_dbg_status_decoded_show(struct seq_file *s, void *unuse= d) +{ + struct mpq8646_priv *priv =3D s->private; + int rc; + + rc =3D 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=3DNVM_SUMMARY, bit8=3DWATCH_DOG, bit0= =3DDRMOS_FAULT)\n"); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_status_decoded); + +static int mpq8646_dbg_protection_last_show(struct seq_file *s, void *unus= ed) +{ + struct mpq8646_priv *priv =3D s->private; + int rc; + + mutex_lock(&priv->mps_lock); + rc =3D i2c_smbus_read_word_data(priv->client, MPS_PROTECTION_LAST); + mutex_unlock(&priv->mps_lock); + + 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; clear via clear_protection_l= ast[_force])\n"); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_protection_last); + +struct mpq8646_dbg_reg { + u8 reg; + bool is_word; + bool writable; + const char *name; +}; + +static const struct mpq8646_dbg_reg mpq8646_dbg_regs[] =3D { + /* MPS vendor extensions (read-only identity / observability) */ + { MPS_MFR_CONFIG_ID, true, false, "mfr_config_id" }, + { MPS_MFR_CONFIG_CODE_REV, true, false, "mfr_config_code_rev" }, + { MPS_MFR_SILICON_REV, false, false, "mfr_silicon_rev" }, + { MPS_MFR_RETRY_TIMES, true, false, "mfr_retry_times" }, + { MPS_MFR_VBOOT_CFG, true, false, "mfr_vboot_cfg" }, + /* MPS vendor extension -- user-writable product revision string */ + { MPS_MFR_PRODUCT_REV_USER, true, true, "mfr_product_rev_user" }, + /* PMBus 1.3 standard timing / UVLO (read-only introspection) */ + { PMBUS_VIN_ON, true, false, "vin_on" }, + { PMBUS_VIN_OFF, true, false, "vin_off" }, + { PMBUS_TON_DELAY, true, false, "ton_delay" }, + { PMBUS_TON_RISE, true, false, "ton_rise" }, + { PMBUS_TOFF_DELAY, true, false, "toff_delay" }, + { PMBUS_TOFF_FALL, true, false, "toff_fall" }, + /* PMBus 1.3 control / margin (read+write) */ + { PMBUS_ON_OFF_CONFIG, false, true, "on_off_config" }, + { PMBUS_VOUT_MARGIN_HIGH, true, true, "vout_margin_high" }, + { PMBUS_VOUT_MARGIN_LOW, true, true, "vout_margin_low" }, + /* MPS PMBus-level write-protect (read+write) */ + { MPS_MFR_PMBUS_LOCK, true, true, "mfr_pmbus_lock" }, +}; + +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 =3D s->private; + int rc; + + if (ctx->desc->is_word) + rc =3D i2c_smbus_read_word_data(ctx->priv->client, + ctx->desc->reg); + else + rc =3D 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_reg_get(void *data, u64 *val) +{ + struct mpq8646_dbg_reg_ctx *ctx =3D data; + int rc; + + if (ctx->desc->is_word) + rc =3D i2c_smbus_read_word_data(ctx->priv->client, + ctx->desc->reg); + else + rc =3D i2c_smbus_read_byte_data(ctx->priv->client, + ctx->desc->reg); + if (rc < 0) + return rc; + *val =3D rc; + return 0; +} + +static int mpq8646_dbg_reg_set(void *data, u64 val) +{ + struct mpq8646_dbg_reg_ctx *ctx =3D data; + int rc; + + if (ctx->desc->is_word) + rc =3D i2c_smbus_write_word_data(ctx->priv->client, + ctx->desc->reg, (u16)val); + else + rc =3D 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 int mpq8646_dbg_clear_protection_last(void *data, u64 val) +{ + struct mpq8646_priv *priv =3D data; + int rc; + + if (!val) + return 0; + + mutex_lock(&priv->mps_lock); + rc =3D i2c_smbus_write_byte(priv->client, MPS_CLEAR_LAST_FAULT); + mutex_unlock(&priv->mps_lock); + + priv->last_probe_rc =3D rc; + priv->last_probe_data =3D 0; + return 0; +} +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 =3D data; + int rc, last_rc; + int wp_orig, cfg_orig; + + if (!val) + return 0; + + mutex_lock(&priv->mps_lock); + + wp_orig =3D i2c_smbus_read_byte_data(priv->client, PMBUS_WRITE_PROTECT); + if (wp_orig < 0) { + priv->last_probe_rc =3D wp_orig; + dev_warn(&priv->client->dev, + "clear_protection_last_force: WRITE_PROTECT read failed (%d), aborting= \n", + wp_orig); + goto out; + } + cfg_orig =3D i2c_smbus_read_word_data(priv->client, MPS_MFR_CFG_EXT); + if (cfg_orig < 0) { + priv->last_probe_rc =3D cfg_orig; + dev_warn(&priv->client->dev, + "clear_protection_last_force: MFR_CFG_EXT read failed (%d), aborting\n= ", + cfg_orig); + goto out; + } + + if (wp_orig !=3D 0) { + rc =3D i2c_smbus_write_byte_data(priv->client, + PMBUS_WRITE_PROTECT, 0); + if (rc < 0) { + priv->last_probe_rc =3D rc; + dev_warn(&priv->client->dev, + "clear_protection_last_force: WP clear failed (%d), aborting\n", + rc); + goto out; + } + } + + rc =3D i2c_smbus_write_word_data(priv->client, MPS_MFR_CFG_EXT, + (u16)cfg_orig | MPS_MFR_CFG_EXT_CLR_LAST_EN); + if (rc < 0) { + priv->last_probe_rc =3D rc; + dev_warn(&priv->client->dev, + "clear_protection_last_force: gate open failed (%d)\n", + rc); + goto restore_wp; + } + + last_rc =3D i2c_smbus_write_byte(priv->client, MPS_CLEAR_LAST_FAULT); + priv->last_probe_rc =3D last_rc; + if (last_rc < 0) + dev_warn(&priv->client->dev, + "clear_protection_last_force: CLEAR_LAST_FAULT failed (%d) even with g= ate open\n", + last_rc); + + for (int attempt =3D 0; attempt < MPQ8646_NVM_RETRY_MAX; attempt++) { + rc =3D i2c_smbus_write_word_data(priv->client, MPS_MFR_CFG_EXT, + (u16)cfg_orig); + if (rc >=3D 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); + +restore_wp: + if (wp_orig !=3D 0) { + rc =3D 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); + } +out: + mutex_unlock(&priv->mps_lock); + return 0; +} +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 =3D data; + int rc; + + if (!val) + return 0; + + mutex_lock(&priv->mps_lock); + rc =3D i2c_smbus_write_byte(priv->client, PMBUS_STORE_USER_ALL); + mutex_unlock(&priv->mps_lock); + + priv->last_probe_rc =3D rc; + priv->last_probe_data =3D 0; + if (rc < 0) + dev_warn(&priv->client->dev, + "store_all: STORE_DEFAULT_ALL (0x11/0x15) write failed (%d)\n", + rc); + return 0; +} +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 =3D data; + int rc; + + if (!val) + return 0; + + mutex_lock(&priv->mps_lock); + rc =3D i2c_smbus_write_byte(priv->client, PMBUS_RESTORE_USER_ALL); + mutex_unlock(&priv->mps_lock); + + priv->last_probe_rc =3D rc; + priv->last_probe_data =3D 0; + if (rc < 0) + dev_warn(&priv->client->dev, + "restore_all: RESTORE_DEFAULT_ALL (0x12/0x16) write failed (%d)\n", + rc); + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_restore_all_fops, + NULL, mpq8646_dbg_restore_all, "%llu\n"); + +#define MPQ8646_DEBUGFS_DIRNAME_MAX 16 + +#define MPQ8646_DEBUGFS_ROOT_NAME "mpq8646" + +static void mpq8646_debugfs_register(struct mpq8646_priv *priv) +{ + struct dentry *root, *parent; + char name[MPQ8646_DEBUGFS_DIRNAME_MAX]; + bool parent_ref =3D false; + size_t i; + + parent =3D debugfs_lookup(MPQ8646_DEBUGFS_ROOT_NAME, NULL); + if (!parent) + parent =3D debugfs_create_dir(MPQ8646_DEBUGFS_ROOT_NAME, NULL); + else + parent_ref =3D true; + if (IS_ERR_OR_NULL(parent)) + return; + + snprintf(name, sizeof(name), "%d-%04x", + i2c_adapter_id(priv->client->adapter), priv->client->addr); + root =3D debugfs_create_dir(name, parent); + if (parent_ref) + dput(parent); + if (IS_ERR_OR_NULL(root)) + return; + + priv->debugfs_root =3D root; + + debugfs_create_file_unsafe("probe_smbus_rword", 0200, root, priv, + &mpq8646_dbg_probe_smbus_rword_fops); + debugfs_create_file_unsafe("probe_smbus_rbyte", 0200, root, priv, + &mpq8646_dbg_probe_smbus_rbyte_fops); + debugfs_create_file_unsafe("probe_raw_xfer", 0200, root, priv, + &mpq8646_dbg_probe_raw_xfer_fops); + debugfs_create_file_unsafe("probe_page_write", 0200, root, priv, + &mpq8646_dbg_probe_page_write_fops); + debugfs_create_file_unsafe("probe_clear_faults", 0200, root, priv, + &mpq8646_dbg_probe_clear_faults_fops); + debugfs_create_bool("force_raw_xfer", 0600, root, + &priv->debug_force_raw_xfer); + debugfs_create_u32("delay_us", 0600, root, &priv->debug_delay_us); + debugfs_create_file("last_probe", 0400, root, priv, + &mpq8646_dbg_last_probe_fops); + debugfs_create_file("stats", 0400, root, priv, + &mpq8646_dbg_stats_fops); + debugfs_create_file_unsafe("reset_stats", 0200, root, priv, + &mpq8646_dbg_reset_stats_fops); + /* 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); + 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, pri= v, + &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); + debugfs_create_u32("alarm_poll_interval_ms", 0600, root, + &priv->alarm_poll_interval_ms); + + priv->dbg_reg_ctx =3D 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 =3D 0; i < ARRAY_SIZE(mpq8646_dbg_regs); i++) { + priv->dbg_reg_ctx[i].priv =3D priv; + priv->dbg_reg_ctx[i].desc =3D &mpq8646_dbg_regs[i]; + if (mpq8646_dbg_regs[i].writable) + debugfs_create_file_unsafe(mpq8646_dbg_regs[i].name, + 0600, root, + &priv->dbg_reg_ctx[i], + &mpq8646_dbg_reg_rw_fops); + else + 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_recursive(priv->debugfs_root); +} +#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 const struct { + u16 mask; + enum hwmon_sensor_types type; + u32 attr; + int channel; +} mpq8646_alarm_map[] =3D { + { PB_STATUS_INPUT, hwmon_in, hwmon_in_alarm, 0 }, /* in1_alarm = (VIN) */ + { PB_STATUS_VOUT, hwmon_in, hwmon_in_alarm, 1 }, /* in2_alarm = (VOUT) */ + { PB_STATUS_IOUT_POUT, hwmon_curr, hwmon_curr_alarm, 0 }, /* curr1_alar= m */ + { PB_STATUS_TEMPERATURE, hwmon_temp, hwmon_temp_alarm, 0 }, /* temp1_alar= m */ +}; + +/* Adapted from lm90.c */ +static void mpq8646_alarm_poll_work(struct work_struct *work) +{ + struct mpq8646_priv *priv =3D container_of(to_delayed_work(work), + struct mpq8646_priv, + alarm_poll_work); + int rc; + u16 cur, newly_set; + size_t i; + + if (priv->client->irq) + return; /* SMBALERT# wired; polling not needed */ + + if (!priv->alarm_poll_interval_ms) + return; /* polling disabled; don't re-arm */ + + if (!priv->hwmon_dev) + goto rearm; /* hwmon not ready yet; try again next tick */ + + rc =3D i2c_smbus_read_word_data(priv->client, PMBUS_STATUS_WORD); + if (rc < 0) + goto rearm; + + cur =3D (u16)rc; + newly_set =3D cur & ~priv->last_status_word; + priv->last_status_word =3D cur; + + if (!newly_set) + goto rearm; + + for (i =3D 0; i < ARRAY_SIZE(mpq8646_alarm_map); i++) { + if (newly_set & mpq8646_alarm_map[i].mask) + hwmon_notify_event(priv->hwmon_dev, + mpq8646_alarm_map[i].type, + mpq8646_alarm_map[i].attr, + mpq8646_alarm_map[i].channel); + } + +rearm: + if (priv->alarm_poll_interval_ms) + 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 =3D &client->dev; + struct pmbus_driver_info *info; + struct mpq8646_priv *priv; + u32 voltage_scale; + int ret; + + priv =3D devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + priv->client =3D client; + mutex_init(&priv->mps_lock); + memcpy(&priv->info, &mpq8646_info, sizeof(priv->info)); + info =3D &priv->info; + + info->identify =3D mpq8646_identify; + info->read_byte_data =3D mpq8646_read_byte_data; + info->read_word_data =3D mpq8646_read_word_data; + info->write_byte =3D mpq8646_write_byte; + info->write_word_data =3D mpq8646_write_word_data; + dev->platform_data =3D &mpq8646_no_pec_pdata; + +#if IS_ENABLED(CONFIG_REGULATOR) + info->reg_desc =3D mpq8646_reg_desc; + info->num_regulators =3D ARRAY_SIZE(mpq8646_reg_desc); +#endif + + INIT_DELAYED_WORK(&priv->alarm_poll_work, mpq8646_alarm_poll_work); + priv->alarm_poll_interval_ms =3D 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 =3D i2c_smbus_write_word_data(client, PMBUS_VOUT_SCALE_LOOP, + voltage_scale); + if (ret) + return ret; + } + + mpq8646_debugfs_register(priv); + + ret =3D pmbus_do_probe(client, info); + if (ret) { + mpq8646_debugfs_unregister(priv); + return ret; + } + + priv->hwmon_dev =3D pmbus_get_hwmon_device(client); + 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 =3D { + .dev =3D &client->dev, + .name =3D dev_name(&client->dev), + .owner =3D THIS_MODULE, + .read_only =3D true, + .root_only =3D true, + .word_size =3D 1, + .stride =3D 1, + .size =3D MPQ8646_NVMEM_SIZE, + .reg_read =3D mpq8646_nvmem_read, + .priv =3D priv, + }; + struct nvmem_device *nv =3D 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 =3D mpq8646_priv_from_client(client); + + cancel_delayed_work_sync(&priv->alarm_poll_work); + mpq8646_debugfs_unregister(priv); +} + +static struct i2c_driver mpq8646_driver =3D { + .driver =3D { + .name =3D "mpq8646", + .of_match_table =3D of_match_ptr(mpq8646_of_match), + }, + .probe =3D mpq8646_probe, + .remove =3D mpq8646_remove, + .id_table =3D mpq8646_id, +}; + +module_i2c_driver(mpq8646_driver); + +MODULE_AUTHOR("Vincent Jardin "); +MODULE_DESCRIPTION("PMBus driver for MPS MPQ8646 (extended observability)"= ); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("PMBUS"); --=20 2.43.0