Devicetree
 help / color / mirror / Atom feed
* [PATCH RESEND v2 2/2] i2c: cadence: Add support for Axiado AX3000
From: Swark Yang @ 2026-06-12  1:37 UTC (permalink / raw)
  To: Michal Simek, Andi Shyti, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: linux-arm-kernel, linux-i2c, devicetree, linux-kernel, openbmc,
	Swark Yang
In-Reply-To: <20260611-axiado-ax3000-cadence-i2c-support-v2-0-cfdad0534afa@axiado.com>

The Axiado AX3000 SoC integrates a Cadence I2C controller
that supports SMBus Quick commands.

Introduce the "axiado,ax3000-i2c" compatible string and
add a new quirk CDNS_I2C_QUIRK_SMBUS_QUICK to enable
this functionality. This allows the controller to support
I2C_FUNC_SMBUS_QUICK, enabling features such as bus scanning
via quick write commands.

Signed-off-by: Swark Yang <syang@axiado.com>
---
 drivers/i2c/busses/i2c-cadence.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/drivers/i2c/busses/i2c-cadence.c b/drivers/i2c/busses/i2c-cadence.c
index 0fb728ade92e..8079c045f936 100644
--- a/drivers/i2c/busses/i2c-cadence.c
+++ b/drivers/i2c/busses/i2c-cadence.c
@@ -128,6 +128,7 @@
 #define CDNS_I2C_TIMEOUT_MAX	0xFF
 
 #define CDNS_I2C_BROKEN_HOLD_BIT	BIT(0)
+#define CDNS_I2C_QUIRKS_ENABLE_SMBUS_QUICK_CFG BIT(1)
 #define CDNS_I2C_POLL_US	100000
 #define CDNS_I2C_POLL_US_ATOMIC	10
 #define CDNS_I2C_TIMEOUT_US	500000
@@ -1175,10 +1176,14 @@ static int cdns_i2c_master_xfer_atomic(struct i2c_adapter *adap, struct i2c_msg
  */
 static u32 cdns_i2c_func(struct i2c_adapter *adap)
 {
+	struct cdns_i2c *id = adap->algo_data;
 	u32 func = I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR |
 			(I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK) |
 			I2C_FUNC_SMBUS_BLOCK_DATA;
 
+	if (id->quirks & CDNS_I2C_QUIRKS_ENABLE_SMBUS_QUICK_CFG)
+		func |= I2C_FUNC_SMBUS_QUICK;
+
 #if IS_ENABLED(CONFIG_I2C_SLAVE)
 	func |= I2C_FUNC_SLAVE;
 #endif
@@ -1442,9 +1447,14 @@ static const struct cdns_platform_data r1p10_i2c_def = {
 	.quirks = CDNS_I2C_BROKEN_HOLD_BIT,
 };
 
+static const struct cdns_platform_data ax3000_i2c_def = {
+	.quirks = CDNS_I2C_QUIRKS_ENABLE_SMBUS_QUICK_CFG,
+};
+
 static const struct of_device_id cdns_i2c_of_match[] = {
 	{ .compatible = "cdns,i2c-r1p10", .data = &r1p10_i2c_def },
 	{ .compatible = "cdns,i2c-r1p14",},
+	{ .compatible = "axiado,ax3000-i2c", .data = &ax3000_i2c_def },
 	{ /* end of table */ }
 };
 MODULE_DEVICE_TABLE(of, cdns_i2c_of_match);

-- 
2.34.1


^ permalink raw reply related

* [PATCH RESEND v2 1/2] dt-bindings: i2c: cadence: Add Axiado AX3000
From: Swark Yang @ 2026-06-12  1:37 UTC (permalink / raw)
  To: Michal Simek, Andi Shyti, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: linux-arm-kernel, linux-i2c, devicetree, linux-kernel, openbmc,
	Swark Yang, Conor Dooley
In-Reply-To: <20260611-axiado-ax3000-cadence-i2c-support-v2-0-cfdad0534afa@axiado.com>

The Axiado AX3000 SoC integrates the Cadence I2C controller.
Add a specific compatible string "axiado,ax3000-i2c" to support
its hardware features, including SMBus Quick command capability.

Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Swark Yang <syang@axiado.com>
---
 Documentation/devicetree/bindings/i2c/cdns,i2c-r1p10.yaml | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/Documentation/devicetree/bindings/i2c/cdns,i2c-r1p10.yaml b/Documentation/devicetree/bindings/i2c/cdns,i2c-r1p10.yaml
index 9f1d35ce1fe8..de2110376a3f 100644
--- a/Documentation/devicetree/bindings/i2c/cdns,i2c-r1p10.yaml
+++ b/Documentation/devicetree/bindings/i2c/cdns,i2c-r1p10.yaml
@@ -14,9 +14,13 @@ allOf:
 
 properties:
   compatible:
-    enum:
-      - cdns,i2c-r1p10 # cadence i2c controller version 1.0
-      - cdns,i2c-r1p14 # cadence i2c controller version 1.4
+    oneOf:
+      - items:
+          - const: axiado,ax3000-i2c
+          - const: cdns,i2c-r1p14
+      - enum:
+          - cdns,i2c-r1p10 # cadence i2c controller version 1.0
+          - cdns,i2c-r1p14 # cadence i2c controller version 1.4
 
   reg:
     maxItems: 1

-- 
2.34.1


^ permalink raw reply related

* [PATCH RESEND v2 0/2] i2c: cadence: Add support for Axiado AX3000
From: Swark Yang @ 2026-06-12  1:37 UTC (permalink / raw)
  To: Michal Simek, Andi Shyti, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley
  Cc: linux-arm-kernel, linux-i2c, devicetree, linux-kernel, openbmc,
	Swark Yang, Conor Dooley

This patch series adds support for the Cadence I2C controller
integrated into the Axiado AX3000 SoC and enables SMBus Quick
command functionality.

The Axiado AX3000 utilizes the Cadence I2C IP core (version r1p14).
While it is largely compatible with the existing i2c-cadence
driver logic, the AX3000 hardware specifically supports SMBus Quick
commands. This feature is currently disabled by default in the
i2c-cadence driver (masked out from I2C_FUNC_SMBUS_EMUL).

To enable this functionality, this series introduces a new
platform-specific quirk (CDNS_I2C_QUIRK_SMBUS_QUICK) and uses driver
match data for the "axiado,ax3000-i2c" compatible string. This allows
tools like 'i2cdetect' to properly scan the bus using quick write
commands.

The DT binding update follows the recommended fallback structure,
referencing the 'cdns,i2c-r1p14' fallback to ensure compatibility with
older kernels while allowing the new quirk to be enabled on AX3000.

Changes in RESEND:
- Resending as the original submission seems to have slipped through the cracks.
- No code changes since the original submission.
- Collected Conor Dooley's Acked-by for Patch 1.

Patch breakdown:

Patch 1: dt-bindings: i2c: cadence: Add Axiado AX3000
Patch 2: i2c: cadence: Add support for Axiado AX3000

These patches are expected to go via the I2C subsystem tree.

Feedback is welcome.

Signed-off-by: Swark Yang <syang@axiado.com>
---
Changes in v2:
- EDITME: describe what is new in this series revision.
- EDITME: use bulletpoints and terse descriptions.
- Link to v1: https://lore.kernel.org/r/20260504-axiado-ax3000-cadence-i2c-support-v1-0-97ed2fdc0b7b@axiado.com

---
Swark Yang (2):
      dt-bindings: i2c: cadence: Add Axiado AX3000
      i2c: cadence: Add support for Axiado AX3000

 Documentation/devicetree/bindings/i2c/cdns,i2c-r1p10.yaml | 10 +++++++---
 drivers/i2c/busses/i2c-cadence.c                          | 10 ++++++++++
 2 files changed, 17 insertions(+), 3 deletions(-)
---
base-commit: 63804fed149a6750ffd28610c5c1c98cce6bd377
change-id: 20260111-axiado-ax3000-cadence-i2c-support-53ec117bb074

Best regards,
-- 
Swark Yang <syang@axiado.com>


^ permalink raw reply

* Re: [PATCH v2 1/2] dt-bindings: PCI: qcom: Document the Hawi PCIe Controller
From: Matthew Leung @ 2026-06-12  1:17 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Bjorn Helgaas, Lorenzo Pieralisi, Krzysztof Wilczyński,
	Manivannan Sadhasivam, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Bjorn Andersson, linux-arm-msm, linux-pci,
	devicetree, linux-kernel
In-Reply-To: <2tenqkmwcr2gshtjwh44pvban4gtlzcgrm3iibkfrs4zh6vphb@h5losfunylr6>

On Sun, Jun 07, 2026 at 11:01:10PM +0300, Dmitry Baryshkov wrote:
> On Fri, May 29, 2026 at 01:10:08AM +0000, Matthew Leung wrote:
> > Add a dedicated schema for the PCIe controllers found on the Hawi
> > platform.
> > 
> > Signed-off-by: Matthew Leung <matthew.leung@oss.qualcomm.com>
> > ---
> >  .../devicetree/bindings/pci/qcom,hawi-pcie.yaml    | 204 +++++++++++++++++++++
> >  1 file changed, 204 insertions(+)
> > 
> > +
> > +examples:
> > +  - |
> > +    #include <dt-bindings/clock/qcom,hawi-gcc.h>
> > +    #include <dt-bindings/gpio/gpio.h>
> > +    #include <dt-bindings/interconnect/qcom,icc.h>
> > +    #include <dt-bindings/interconnect/qcom,hawi-rpmh.h>
> 
> Stop referencing clocks and interconnect header files. Replace used nocs
> with ephemeral values.
> 
> > +    #include <dt-bindings/interrupt-controller/arm-gic.h>
> > +
> > +    soc {
> > +        #address-cells = <2>;
> > +        #size-cells = <2>;
> 
> Not looking around should be a sin. Take a look at other Qualcomm PCIe
> bindings. Compare them to yours. Then fix yours to follow.
> 
> Hint: the extra soc node is useless. This is just an example, so use the
> default, 1 cells for address and size.

Thank you for the feedback. This new binding follows the examples set in
the qcom,pcie-sm8x50 bindings and retains the same formatting (extra soc
node and header references).

I understand the example can be simplified with your suggestions but
want additional confirmation that these will be the convention for new
bindings going forward.

> 
> > +
> > +        pcie@1c00000 {
> > +            compatible = "qcom,hawi-pcie";
> > +            reg = <0 0x01c00000 0 0x3000>,
> > +                  <0 0x40000000 0 0xf1d>,
> > +                  <0 0x40000f20 0 0xa8>,
> > +                  <0 0x40001000 0 0x1000>,
> > +                  <0 0x40100000 0 0x100000>;
> > +            reg-names = "parf", "dbi", "elbi", "atu", "config";
> > +            ranges = <0x01000000 0x0 0x00000000 0x0 0x40200000 0x0 0x100000>,
> > +                     <0x02000000 0x0 0x40300000 0x0 0x40300000 0x0 0x3d00000>;
> > +
> > +            bus-range = <0x00 0xff>;
> > +            device_type = "pci";
> > +            linux,pci-domain = <0>;
> > +            num-lanes = <2>;
> > +
> > +            #address-cells = <3>;
> > +            #size-cells = <2>;
> > +
> > +            clocks = <&gcc GCC_PCIE_0_AUX_CLK>,
> 
> <&gcc_pcie_0_aux_clk>, etc.
> 
> > +                     <&gcc GCC_PCIE_0_CFG_AHB_CLK>,
> > +                     <&gcc GCC_PCIE_0_MSTR_AXI_CLK>,
> > +                     <&gcc GCC_PCIE_0_SLV_AXI_CLK>,
> > +                     <&gcc GCC_PCIE_0_SLV_Q2A_AXI_CLK>,
> > +                     <&gcc GCC_AGGRE_NOC_PCIE_AXI_CLK>,
> > +                     <&gcc GCC_CNOC_PCIE_SF_AXI_CLK>;
> 
> -- 
> With best wishes
> Dmitry

^ permalink raw reply

* Re: [PATCH v7 2/2] ARM: dts: aspeed: ventura2: Add Meta ventura2 BMC
From: Kyle Hsieh @ 2026-06-12  1:19 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Joel Stanley,
	Andrew Jeffery, devicetree, linux-arm-kernel, linux-aspeed,
	linux-kernel
In-Reply-To: <843dc0ff-a504-4237-b0f4-d92be07e2465@lunn.ch>

On Thu, Jun 11, 2026 at 11:57 PM Andrew Lunn <andrew@lunn.ch> wrote:
>
> > +     /* Marvell 88E6393X EEPROM */
> > +     eeprom@50 {
> > +             compatible = "atmel,24c64";
> > +             reg = <0x50>;
> > +     };
>
> How is this on both a host I2C bus, and the switches I2C bus? Are you
> using multi-master? Is there a GPIO to hold the switch in reset while
> the host access the EEPROM?
>
>       Andrew
Hi Andrew,

Thanks for taking a look at this.

To answer your questions: No, we are not using multi-master.
The EEPROM is physically isolated by a hardware I2C multiplexer.
By default, the mux connects the EEPROM directly to the Marvell switch
for its routine operation and configuration loading. The BMC's I2C bus is
physically disconnected from the EEPROM during this time.

The BMC only gains access to this EEPROM for out-of-band firmware updates.
When an update is required, the BMC or CPLD asserts a reset to the switch,
toggles the I2C mux to route the EEPROM to the BMC, performs the flash,
and then restores the original routing before releasing the switch from reset.

Therefore, there is no runtime concurrent access or multi-master scenario
between the host and the switch.

Would you recommend adding a comment in the device tree to clarify this
hardware isolation, or is this explanation sufficient?

Best regards,
Kyle Hsieh

^ permalink raw reply

* Re: [PATCH v4 0/2] pinctrl: ultrarisc: add DP1000 pinctrl support
From: Jia Wang @ 2026-06-12  1:05 UTC (permalink / raw)
  To: Linus Walleij
  Cc: wangjia, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Bartosz Golaszewski, linux-gpio, devicetree, linux-kernel,
	Conor Dooley
In-Reply-To: <CAD++jL=m6++zOfq2gP4-y4N7U6nx1buVM5KMoxWJ_9PM6RVV8A@mail.gmail.com>

On 2026-06-11 14:24 +0200, Linus Walleij wrote:
> On Wed, Jun 10, 2026 at 7:29 AM Jia Wang via B4 Relay
> <devnull+wangjia.ultrarisc.com@kernel.org> wrote:
> 
> > This series adds the devicetree schema and the pinctrl driver for the
> > DP1000 controller using generic pinctrl bindings.
> >
> > Compared with v1, this series is narrowed down to the pinctrl binding
> > and driver only. v1 patches 1, 2, 3, 5, 7, 8, and 9 (vendor prefix,
> > CPU/SoC bindings, DTS files, and defconfig update) are not included in
> > v2 and will be sent separately.
> 
> This is a very good looking driver so I applied the patches!
> 
> If there are any more snags they can certainly be fixed in-tree.
>

Hi Linus,

Thanks for applying the series!
 
> Yours,
> Linus Walleij
>
 
Best regards,
Jia Wang



^ permalink raw reply

* Re: [PATCH v2 3/3] regulator: qcom-refgen: add support for the IPQ9650 SoC
From: Dmitry Baryshkov @ 2026-06-12  0:56 UTC (permalink / raw)
  To: Kathiravan Thirumoorthy
  Cc: Liam Girdwood, Mark Brown, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Konrad Dybcio, linux-arm-msm, linux-kernel,
	devicetree
In-Reply-To: <20260611-ipq9650_refgen-v2-3-d96a91d5b99e@oss.qualcomm.com>

On Thu, Jun 11, 2026 at 02:33:18PM +0530, Kathiravan Thirumoorthy wrote:
> IPQ9650 SoC has 2 REFGEN blocks providing the reference current to the
> PCIe and USB, UNIPHY PHYs. For the other SoCs, clocks for this block is
> enabled on power up but that's not the case for IPQ9650 and we have to
> enable those clocks explicitly to bring up the PHYs properly.
> 
> Signed-off-by: Kathiravan Thirumoorthy <kathiravan.thirumoorthy@oss.qualcomm.com>
> ---
>  drivers/regulator/qcom-refgen-regulator.c | 89 +++++++++++++++++++++++++++++--
>  1 file changed, 85 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/regulator/qcom-refgen-regulator.c b/drivers/regulator/qcom-refgen-regulator.c
> index 6a3795469927..e8821f159ff1 100644
> --- a/drivers/regulator/qcom-refgen-regulator.c
> +++ b/drivers/regulator/qcom-refgen-regulator.c
> @@ -3,6 +3,7 @@
>  // Copyright (c) 2023, Linaro Limited
>  
>  #include <linux/bitfield.h>
> +#include <linux/clk.h>
>  #include <linux/module.h>
>  #include <linux/of.h>
>  #include <linux/platform_device.h>
> @@ -16,6 +17,10 @@
>   #define REFGEN_BIAS_EN_ENABLE		0x7
>   #define REFGEN_BIAS_EN_DISABLE		0x6
>  
> +#define REFGEN_REG_REFGEN_STATUS	0xC

Lowercase hex, please.

With that fixed:


Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>



> +#define REFGEN_STATUS_OUT_MASK		BIT(3)
> + #define REFGEN_STATUS_OUT_ENABLE	0x8
> +

-- 
With best wishes
Dmitry

^ permalink raw reply

* Re: [PATCH v2 00/10] phy: qcom: qmp-pcie: Add PCIe PHY support for Hawi
From: Matthew Leung @ 2026-06-12  0:39 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-arm-msm, linux-phy, devicetree, linux-kernel,
	Krzysztof Kozlowski
In-Reply-To: <egojnbup5igcre6ccegojsdrvtokwfoccqhwmfxkoy5ukvuxvj@ailtbrgrgocs>

On Mon, Jun 08, 2026 at 12:25:03AM +0300, Dmitry Baryshkov wrote:
> On Thu, Jun 04, 2026 at 01:32:54AM +0000, Matthew Leung wrote:
> > This series adds QMP PCIe PHY support for the Qualcomm Hawi SoC. The Hawi
> > platform features two PCIe PHY configurations: Gen3 x2 and Gen4 x1.
> > 
> > The Gen3 x2 PHY uses v10 register definitions, while the Gen4 x1 PHY uses
> > v10.60 register definitions.
> > 
> > The series adds:
> > - device tree bindings (patch 1)
> > - v10 register offset headers (patches 2-5)
> > - v10.60 register offset headers (patches 6-9)
> > - driver support with PHY initialization tables for both configurations
> >   (patch 10)
> > 
> > Overlap:
> > The series has overlap with "phy: qcom: Introduce USB support for Hawi"
> > by Ronak Raheja (see link [1]). Both patch series introduce a subset of
> > v10 registers (this series for PCIe and Ronak's for USB). I have
> > coordinated with Ronak regarding the overlap, and we can update the
> > series to resolve any overlap based on the order of merging.
> > 
> > Link: https://lore.kernel.org/all/20260508213234.4643-1-ronak.raheja@oss.qualcomm.com/ [1]
> > 
> > Signed-off-by: Matthew Leung <matthew.leung@oss.qualcomm.com>
> > ---
> > Changes in v2:
> > - Rebased onto v7.1-rc6
> > - Patch 1: no change (Reviewed-by carried forward)
> > - Patch 9: rename QPHY_PCIE_V10_60_PCS_PCS_TX_RX_CONFIG to
> >   QPHY_PCIE_V10_60_PCS_TX_RX_CONFIG to be consistent with the
> >   naming convention used in previous pcs-pcie headers
> > - Patch 10: update usage of renamed macro
> > - Link to v1: https://patch.msgid.link/20260508-hawi-phy-pcie-v1-0-237b894353fc@oss.qualcomm.com
> > 
> > To: Vinod Koul <vkoul@kernel.org>
> > To: Neil Armstrong <neil.armstrong@linaro.org>
> > To: Rob Herring <robh@kernel.org>
> > To: Krzysztof Kozlowski <krzk+dt@kernel.org>
> > To: Conor Dooley <conor+dt@kernel.org>
> > Cc: linux-arm-msm@vger.kernel.org
> > Cc: linux-phy@lists.infradead.org
> > Cc: devicetree@vger.kernel.org
> > Cc: linux-kernel@vger.kernel.org
> > 
> > ---
> > Matthew Leung (10):
> >       dt-bindings: phy: qcom,sc8280xp-qmp-pcie-phy: Add Hawi compatibles
> >       phy: qcom-qmp: qserdes-com: Add v10 register offsets
> >       phy: qcom-qmp: qserdes-txrx: Add v10 register offsets
> >       phy: qcom-qmp: pcs: Add v10 register offsets
> >       phy: qcom-qmp: pcs-pcie: Add v10 register offsets
> 
> Squash these 4 patches.
> 
> >       phy: qcom-qmp: qserdes-com: Add v10.60 register offsets
> >       phy: qcom-qmp: qserdes-txrx: Add v10.60 register offsets
> >       phy: qcom-qmp: pcs: Add v10.60 register offsets
> >       phy: qcom-qmp: pcs-pcie: Add v10.60 register offsets
> 
> And these 4

Will do.

> 
> >       phy: qcom: qmp-pcie: Add QMP PCIe PHY support for Hawi
> > 
> >  .../bindings/phy/qcom,sc8280xp-qmp-pcie-phy.yaml   |   6 +
> >  drivers/phy/qualcomm/phy-qcom-qmp-pcie.c           | 382 +++++++++++++++++++++
> >  drivers/phy/qualcomm/phy-qcom-qmp-pcs-pcie-v10.h   |  18 +
> >  .../phy/qualcomm/phy-qcom-qmp-pcs-pcie-v10_60.h    |  26 ++
> >  drivers/phy/qualcomm/phy-qcom-qmp-pcs-v10.h        |  22 ++
> >  drivers/phy/qualcomm/phy-qcom-qmp-pcs-v10_60.h     |  23 ++
> >  .../phy/qualcomm/phy-qcom-qmp-qserdes-com-v10.h    |  49 +++
> >  .../phy/qualcomm/phy-qcom-qmp-qserdes-com-v10_60.h |  55 +++
> >  .../phy/qualcomm/phy-qcom-qmp-qserdes-txrx-v10.h   |  47 +++
> >  .../qualcomm/phy-qcom-qmp-qserdes-txrx-v10_60.h    | 109 ++++++
> >  drivers/phy/qualcomm/phy-qcom-qmp.h                |  10 +
> >  11 files changed, 747 insertions(+)
> > ---
> > base-commit: e43ffb69e0438cddd72aaa30898b4dc446f664f8
> > change-id: 20260506-hawi-phy-pcie-283933b4113e
> > 
> > Best regards,
> > --  
> > Matthew Leung <matthew.leung@oss.qualcomm.com>
> > 
> > 
> > -- 
> > linux-phy mailing list
> > linux-phy@lists.infradead.org
> > https://lists.infradead.org/mailman/listinfo/linux-phy
> 
> -- 
> With best wishes
> Dmitry

^ permalink raw reply

* Re: [PATCH v2 10/10] phy: qcom: qmp-pcie: Add QMP PCIe PHY support for Hawi
From: Matthew Leung @ 2026-06-12  0:37 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Vinod Koul, Neil Armstrong, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-arm-msm, linux-phy, devicetree, linux-kernel
In-Reply-To: <h6fbnsqg3gcobkc6chzehy2ew5hczidzqulr3xh6d6u5kazjhd@ygsd26b3neyo>

On Mon, Jun 08, 2026 at 12:29:54AM +0300, Dmitry Baryshkov wrote:
> On Thu, Jun 04, 2026 at 01:33:04AM +0000, Matthew Leung wrote:
> > Add the QMP PCIe PHY support for the Gen3 x2 and Gen4 x1 PHY found on
> > the Hawi platform.
> > 
> > Signed-off-by: Matthew Leung <matthew.leung@oss.qualcomm.com>
> > ---
> >  drivers/phy/qualcomm/phy-qcom-qmp-pcie.c | 380 +++++++++++++++++++++++++++++++
> >  1 file changed, 380 insertions(+)
> > 
> >  	u16 rx2;
> >  	u16 txz;
> >  	u16 rxz;
> > +	u16 txrx;
> 
> Can we do what we did for QHP and reuse tx instead of introducing a
> separate txrx offset and data pointer, etc.

I see what you're saying. Yes, that method should work here as well. I
will update in the next version.

> 
> >  	u16 txrxz;
> >  	u16 ln_shrd;
> >  };
> 
> -- 
> With best wishes
> Dmitry

^ permalink raw reply

* Re: [PATCH v3 1/2] dt-bindings: spi: nuvoton,ma35d1-qspi: Add Nuvoton MA35D1 QSPI
From: Chi-Wen Weng @ 2026-06-12  0:33 UTC (permalink / raw)
  To: Conor Dooley
  Cc: broonie, robh, krzk+dt, conor+dt, linux-arm-kernel, linux-spi,
	devicetree, linux-kernel, cwweng
In-Reply-To: <20260611-decoy-glamorous-81903a5fd1f9@spud>

Hi Conor,

Thanks for the review.

I will add a default value for num-cs in v4:

   num-cs:
     maximum: 2
     default: 2

The controller has two native chip selects and the driver currently uses
that hardware default.

Best regards,
Chi-Wen

Conor Dooley 於 2026/6/12 上午 01:34 寫道:
> On Thu, Jun 11, 2026 at 05:12:45PM +0800, Chi-Wen Weng wrote:
>> From: Chi-Wen Weng <cwweng@nuvoton.com>
>>
>> Add a devicetree binding for the Quad SPI controller found in
>> Nuvoton MA35D1 SoCs.
>>
>> The controller supports SPI memory devices such as SPI NOR and SPI NAND
>> flashes. It has one register range, one clock input and one reset line,
>> and supports up to two chip selects.
>>
>> Signed-off-by: Chi-Wen Weng <cwweng@nuvoton.com>
>> ---
>>   .../bindings/spi/nuvoton,ma35d1-qspi.yaml     | 62 +++++++++++++++++++
>>   1 file changed, 62 insertions(+)
>>   create mode 100644 Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml b/Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml
>> new file mode 100644
>> index 000000000000..d3b36e612eb0
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/spi/nuvoton,ma35d1-qspi.yaml
>> @@ -0,0 +1,62 @@
>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/spi/nuvoton,ma35d1-qspi.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Nuvoton MA35D1 Quad SPI Controller
>> +
>> +maintainers:
>> +  - Chi-Wen Weng <cwweng@nuvoton.com>
>> +
>> +allOf:
>> +  - $ref: /schemas/spi/spi-controller.yaml#
>> +
>> +properties:
>> +  compatible:
>> +    const: nuvoton,ma35d1-qspi
>> +
>> +  reg:
>> +    maxItems: 1
>> +
>> +  interrupts:
>> +    maxItems: 1
>> +
>> +  clocks:
>> +    maxItems: 1
>> +
>> +  resets:
>> +    maxItems: 1
>> +
>> +  num-cs:
>> +    maximum: 2
> Missing a default of 2, unless you make the property required.
> FWIW, your driver doesn't appear to read this value.
>
> pw-bot: changes-requested
>
> Cheers,
> Conor.
>
>> +
>> +required:
>> +  - compatible
>> +  - reg
>> +  - clocks
>> +  - resets
>> +
>> +unevaluatedProperties: false
>> +
>> +examples:
>> +  - |
>> +    #include <dt-bindings/interrupt-controller/arm-gic.h>
>> +    #include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
>> +    #include <dt-bindings/reset/nuvoton,ma35d1-reset.h>
>> +
>> +    soc {
>> +        #address-cells = <2>;
>> +        #size-cells = <2>;
>> +
>> +        spi@40680000 {
>> +            compatible = "nuvoton,ma35d1-qspi";
>> +            reg = <0 0x40680000 0 0x100>;
>> +            interrupts = <GIC_SPI 57 IRQ_TYPE_LEVEL_HIGH>;
>> +            clocks = <&clk QSPI0_GATE>;
>> +            resets = <&sys MA35D1_RESET_QSPI0>;
>> +            #address-cells = <1>;
>> +            #size-cells = <0>;
>> +        };
>> +    };
>> +
>> -- 
>> 2.25.1
>>

^ permalink raw reply

* Re: [PATCH v2 1/4] soc: qcom: rpmh: Allow non-child devices to issue write commands
From: Dmitry Baryshkov @ 2026-06-12  0:27 UTC (permalink / raw)
  To: Konrad Dybcio
  Cc: Fenglin Wu, linux-arm-msm, Bjorn Andersson, Konrad Dybcio,
	Linus Walleij, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Bartosz Golaszewski, David Collins, Subbaraman Narayanamurthy,
	Kamal Wadhwa, Maulik Shah, kernel, linux-kernel, linux-gpio,
	devicetree
In-Reply-To: <4edaf745-d24f-4ce0-9605-e3971f067b68@oss.qualcomm.com>

On Thu, Jun 11, 2026 at 12:36:43PM +0200, Konrad Dybcio wrote:
> On 6/9/26 3:28 AM, Fenglin Wu wrote:
> > 
> > On 6/8/2026 5:21 AM, Dmitry Baryshkov wrote:
> >> On Thu, Jun 04, 2026 at 10:02:43AM +0800, Fenglin Wu wrote:
> >>> On 6/2/2026 3:29 PM, Fenglin Wu wrote:
> >>>> On 6/1/2026 9:37 PM, Dmitry Baryshkov wrote:
> >>>>> On Thu, May 28, 2026 at 06:05:35PM -0700, Fenglin Wu wrote:
> >>>>>> Currently, the RPMH driver only allows child devices of the RPMH
> >>>>>> controller to issue commands, as it assumes dev->parent points to the
> >>>>>> RSC device.
> >>>>>>
> >>>>>> There is a possibility that certain devices which are not children of
> >>>>>> the RPMH controller want to send commands for special control at the
> >>>>>> RPMH side. For example, in PMH0101 PMICs, there are bidirectional
> >>>>>> level shifter (LS) peripherals, and each LS works with a pair of PMIC
> >>>>>> GPIOs. The control of the LS, which is combined with the GPIO
> >>>>>> configuration, is handled by RPMH firmware for sharing the resource
> >>>>>> between different subsystems. From a hardware point of view, the LS
> >>>>>> functionality is tied to a pair of PMIC GPIOs, so its control is more
> >>>>>> suitable to be added in the pinctrl-spmi-gpio driver by adding the
> >>>>>> level-shifter function. However, the pinctrl-spmi-gpio device is a
> >>>>>> child device of the SPMI controller, not the RPMH controller.
> >>>>> This replicates the story of the PMIC regulators. There are two drivers,
> >>>>> one SPMI and one RPMh. Why don't we add a separate, RPMh-based GPIO
> >>>>> driver targeting only those paired GPIOs (and we don't even need to
> >>>>> represent them as a pair, it might be just one pin).
> >>>> Thanks for the suggestion.
> >>>>
> >>>> I agree that adding a separate, RPMh-based GPIO driver would be more
> >>>> straightforward from RPMh control perspective. It makes the new device
> >>>> as a child of the RSC device then it can naturally use the APIs for RPMh
> >>>> commands. The main challenge here is, we need to make the level-shifter
> >>>> mutually exclusive with other GPIO functions when the GPIO pairs are
> >>>> used in level-shifter function, which means we need to write SPMI
> >>>> commands to disable the associated GPIO modules. I am not sure if AOP
> >>>> already handles this; as far as I know, AOP only manages the
> >>>> BIDIR_LVL_SHIFTER module registers. Let me double check on this
> >>>> internally, if the GPIO modules could be controlled along
> >>>> with BIDIR_LVL_SHIFTER module registers at AOP side, and get back.
> >>>>
> >>> I checked on this internally, AOP only handles BIDIR_LVL_SHIFTER module
> >>> registers, it doesn't disable the associated GPIO modules. Also, I still
> >>> have no idea how could we make the "level-shifter" function to be mutually
> >>> exclusive with other GPIO functions after moved it into a separate driver.
> >>> Do you have further suggestions?
> >> So, for my understanding, we still need to write SPMI registers to
> >> configure the pins and only then AOP can handle the level shifter?
> >>
> >> I was thinking of using gpio-reserved-ranges to prevent those GPIOs from
> >> being used by the normal SPMI driver.
> > 
> > More background: "level-shifter" module is actually an independent hardware which is not part of the GPIO module. However, they are sharing the physical pins. Which means, from PMIC chip perspective, these pins can be configured to either a GPIO function or the "level-shifter" function. So in PMIC base dtsi file, for example, pmh0101.dtsi, these pins should not be restricted in the GPIO nodes in "gpio-reserved-ranges".
> > 
> > Also, we need to make the GPIO modules are disabled when the "level-shifter" is enabled, to ensure that the "level-shifter" circuitry is not impacted by the GPIO modules internal circuitry. So it is supposed to write GPIO EN_CTL register (offset 0x46) to 0 through SPMI bus when the "level-shifter" is enabled.
> > 
> > That's why we have the requirement to access both RPMh and SPMI bus in the same driver.
> 
> I was thinking about other ways to solve it.. maybe someting like:
> 
> &pmh0101_gpios {
> 	pmh0101_ls_pins1_2: foo-bar {
> 		pins = "gpio1", "gpio2";
> 		// appropriate pinctrl config
> 	};
> };
> 
> &rpmh_rsc {
> 	// should this be a gpio controller? a mux provider?
> 	// is there another class that would better suit this?
> 	rpmh_level_shifter: rpmh-foo-bar {
> 		pinctrl-0 = <&>;
> 		pinctrl-names = "default";
> 	};
> };
> 
> // but where would it make sense to describe?
> // fixed-regulator or something akin to that?
> &some_consumer {
> 	someclass = <&rpmh_level_shifter 1>;
> };
> 
> i.e. the "rpmh level shifter" driver would consume a reference to the
> pins, configure them as necessary (just like any other pinctrl consumer)
> upon request

SGTM.

> 
> Konrad

-- 
With best wishes
Dmitry

^ permalink raw reply

* Re: [PATCH 1/2] dt-bindings: phy: qcom,usb-hs-phy: add qcom,vendor-init-seq
From: Dmitry Baryshkov @ 2026-06-12  0:25 UTC (permalink / raw)
  To: Konrad Dybcio
  Cc: github.com, me, linux-phy, devicetree, linux-arm-msm, Vinod Koul,
	Neil Armstrong, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Bjorn Andersson
In-Reply-To: <0f6ea4d2-3865-492e-ac6b-b008843f8d56@oss.qualcomm.com>

On Thu, Jun 11, 2026 at 12:39:45PM +0200, Konrad Dybcio wrote:
> On 6/4/26 1:02 AM, Dmitry Baryshkov wrote:
> > On Wed, Jun 03, 2026 at 06:09:18PM +0200, me@herrie.org wrote:
> >> On 2026-06-03 15:57, Dmitry Baryshkov wrote:
> >>> On Wed, Jun 03, 2026 at 07:48:08AM +0200, Herman van Hazendonk wrote:
> >>>> Add an optional "qcom,vendor-init-seq" property carrying raw ULPI
> >>>> (address, value) pairs that are written after PHY reset.
> >>>>
> >>>> Unlike the existing "qcom,init-seq" property, the address field is
> >>>> NOT offset by ULPI_EXT_VENDOR_SPECIFIC, so the new property can
> >>>> reach the standard ULPI vendor register range (0x30-0x3f). MSM8x60-
> >>>> class hardware needs this range to programme pre-emphasis, HS driver
> >>>> slope and CDR auto-reset bits the legacy msm_otg driver used to set
> >>>> via platform data.
> >>>
> >>> Are those register writes specific to the device or to the whole
> >>> platform? In the latter case please extend the driver to write them.
> >>
> >> Looking at every MSM8x60 reference kernel I could find (Qualcomm's own
> >> msm8x60 board, HP TouchPad / APQ8060, and some HTC/Saumsung MSM8660
> >> devices), the writes split into two groups:
> >>
> >> Platform-level (same across all MSM8x60 hardware):
> >>  - reg 0x36 bits 1+2: CDR auto-reset disabled, SE1 gating disabled
> >>  - reg 0x32 bits [5:4]: pre-emphasis at 20%
> >>
> >> Board-specific:
> >>  - reg 0x32 bits [3:0]: HS driver slope — HP TouchPad uses 5, HTC
> >>    devices use 1. This clearly depends on board layout (trace length,
> >>    connector loading, etc.).
> >>
> >> So the platform-level writes should move unconditionally into the driver
> >> behind a match-data flag for the MSM8x60-class compatible, and only the
> >> HS driver slope value belongs in DT.
> > 
> > Looks like it. Please hardcode the value for your platform in the driver
> > (with the comment), meanwhile we can try looking up the actual values.
> 
> Do we have the values for a MTP/QRD (or whatever they used to be called
> back then..), like we would usually put in there?

As far as I can understand msm-3.0 and msm-3.4 most of the boards were
writing 0 here (although it might have been unexpected). None of the
board files set the hsdrvslope value (which means 0).

Please correct me if I'm wrong. I see that for tenderloin kernels change
that to 0x5, but I can't find a sensible commit message.

I could not find the documentation for vendor ULPI registers for those
chips, so I don't think we can identify, how to make sense of those
values. In such a case and having different board-specific values, we
don't have a better option than having a qcom,hsdrvslope (or similarly
named) property in DT.

-- 
With best wishes
Dmitry

^ permalink raw reply

* Re: [PATCH] dt-bindings: mailbox: qcom: Add IPCC support for Maili Platform
From: Jassi Brar @ 2026-06-11 23:37 UTC (permalink / raw)
  To: Chunkai Deng
  Cc: Bjorn Andersson, Manivannan Sadhasivam, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, yijie.yang, Jingyi Wang,
	Vishnu Santhosh, Deepak Kumar Singh, linux-arm-msm, linux-kernel,
	devicetree
In-Reply-To: <20260526-mailbox-qcom-maili-v1-1-1b689a42f33e@oss.qualcomm.com>

On Mon, May 25, 2026 at 10:38 PM Chunkai Deng
<chunkai.deng@oss.qualcomm.com> wrote:
>
> Document the Inter-Processor Communication Controller on the Qualcomm
> Maili Platform, which will be used to route interrupts across various
> subsystems found on the SoC.
>
> Signed-off-by: Chunkai Deng <chunkai.deng@oss.qualcomm.com>
> ---
>  Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml | 1 +
>  1 file changed, 1 insertion(+)
>
> diff --git a/Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml b/Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml
> index f5c584cf2146..4b69a281971a 100644
> --- a/Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml
> +++ b/Documentation/devicetree/bindings/mailbox/qcom-ipcc.yaml
> @@ -27,6 +27,7 @@ properties:
>            - qcom,eliza-ipcc
>            - qcom,glymur-ipcc
>            - qcom,kaanapali-ipcc
> +          - qcom,maili-ipcc
>            - qcom,milos-ipcc
>            - qcom,qcs8300-ipcc
>            - qcom,qdu1000-ipcc
>
Applied to mailbox/for-next
Thanks
Jassi

^ permalink raw reply

* Re: [PATCH v1 2/2] ASoC: qcom: sc8280xp: add Shikra EVK machine variants
From: Dmitry Baryshkov @ 2026-06-11 23:16 UTC (permalink / raw)
  To: Ajay Kumar Nandam
  Cc: Srinivas Kandagatla, Liam Girdwood, Mark Brown, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, linux-sound, linux-arm-msm,
	devicetree, linux-kernel, Mohammad Rafi Shaik
In-Reply-To: <20260611112946.954172-3-ajay.nandam@oss.qualcomm.com>

On Thu, Jun 11, 2026 at 04:59:46PM +0530, Ajay Kumar Nandam wrote:
> Add machine-driver support for Qualcomm Shikra EVK variants by matching
> dedicated compatible strings and applying board-specific audio behavior.
> 
> Shikra platforms are available as CQM, CQS, and IQS variants with
> different audio components and processing architectures:
> 
> - CQM/CQS use an I2S-based path with WSA885x amplifiers and
>   PM4125 + Rouleur codec components.
> - CQM runs in DSP-bypass mode, where the complete audio pipeline runs
>   on CPU only and no DSP is involved.
> - CQS uses modem-DSP based audio processing.
> - IQS uses a third-party MAX98091 codec with modem-DSP support.
> 
> Introduce variant-specific handling so codec controls, DAPM widgets, and
> clock programming match the board design and avoid invalid clock setup on
> variants where it is not applicable.

To me it looks like several independent changes being squashed into one
commit.

> 
> Co-developed-by: Mohammad Rafi Shaik <mohammad.rafi.shaik@oss.qualcomm.com>
> Signed-off-by: Mohammad Rafi Shaik <mohammad.rafi.shaik@oss.qualcomm.com>
> Signed-off-by: Ajay Kumar Nandam <ajay.nandam@oss.qualcomm.com>
> ---
>  sound/soc/qcom/common.c   |  2 ++
>  sound/soc/qcom/sc8280xp.c | 58 +++++++++++++++++++++++++++++++++++++--
>  sound/soc/qcom/sdw.c      |  3 ++
>  3 files changed, 61 insertions(+), 2 deletions(-)
> 
> diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c
> index f42c98ded..32d6c09b2 100644
> --- a/sound/soc/qcom/common.c
> +++ b/sound/soc/qcom/common.c
> @@ -3,6 +3,7 @@
>  // Copyright (c) 2018, The Linux Foundation. All rights reserved.
>  
>  #include <dt-bindings/sound/qcom,q6afe.h>
> +#include <dt-bindings/sound/qcom,qaif.h>
>  #include <linux/module.h>
>  #include <sound/jack.h>
>  #include <linux/input-event-codes.h>
> @@ -430,6 +431,7 @@ int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd,
>  	}
>  
>  	switch (cpu_dai->id) {
> +	case QAIF_CDC_DMA_RX0:
>  	case TX_CODEC_DMA_TX_0:
>  	case TX_CODEC_DMA_TX_1:
>  	case TX_CODEC_DMA_TX_2:
> diff --git a/sound/soc/qcom/sc8280xp.c b/sound/soc/qcom/sc8280xp.c
> index e5d23e244..4c985f81c 100644
> --- a/sound/soc/qcom/sc8280xp.c
> +++ b/sound/soc/qcom/sc8280xp.c
> @@ -37,6 +37,25 @@ static struct snd_soc_dapm_widget sc8280xp_dapm_widgets[] = {
>  	SND_SOC_DAPM_SPK("DP7 Jack", NULL),
>  };
>  
> +static struct snd_soc_dapm_widget shikra_cqm_dapm_widgets[] = {
> +	SND_SOC_DAPM_HP("Headphone Jack", NULL),
> +	SND_SOC_DAPM_MIC("Mic Jack", NULL),
> +};

No speakers?

> +
> +static const struct snd_soc_dapm_widget shikra_iqs_dapm_widgets[] = {
> +	SND_SOC_DAPM_HP("Headphone", NULL),
> +	SND_SOC_DAPM_MIC("Headset Mic", NULL),
> +	SND_SOC_DAPM_MIC("Int Mic", NULL),
> +	SND_SOC_DAPM_SPK("Speaker", NULL),
> +};
> +
> +static const struct snd_kcontrol_new shikra_iqs_controls[] = {
> +	SOC_DAPM_PIN_SWITCH("Headset Mic"),
> +	SOC_DAPM_PIN_SWITCH("Headphone"),
> +	SOC_DAPM_PIN_SWITCH("Int Mic"),
> +	SOC_DAPM_PIN_SWITCH("Speaker"),
> +};

No controls for CQM/CQS variants? Why? Or rather why do we need
kcontrols for IQS?

> +
>  struct snd_soc_common {
>  	const char *driver_name;
>  	const struct snd_soc_dapm_widget *dapm_widgets;
> @@ -49,6 +68,7 @@ struct snd_soc_common {
>  	bool codec_sysclk_set;
>  	bool mi2s_mclk_enable;
>  	bool mi2s_bclk_enable;
> +	bool dsp_bypass;
>  };
>  
>  struct sc8280xp_snd_data {
> @@ -219,6 +239,10 @@ static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream,
>  	int mclk_freq = sc8280xp_get_mclk_freq(params);
>  	int bclk_freq = sc8280xp_get_bclk_freq(params);
>  
> +    /* Skip DSP configuration when operating in CPU-only (bypass) mode */
> +	if (data->snd_soc_common_priv->dsp_bypass)
> +		return 0;
> +

How are e.g. MI2S signals configured / routed?

>  	switch (cpu_dai->id) {
>  	case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX:
>  	case QUINARY_MI2S_RX ... QUINARY_MI2S_TX:
> @@ -239,7 +263,7 @@ static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream,
>  					       SND_SOC_CLOCK_IN);
>  
>  		if (data->snd_soc_common_priv->codec_sysclk_set)
> -			snd_soc_dai_set_sysclk(cpu_dai,
> +			snd_soc_dai_set_sysclk(codec_dai,
>  					       0, mclk_freq,
>  					       SND_SOC_CLOCK_IN);
>  		break;
> @@ -284,7 +308,7 @@ static void sc8280xp_add_be_ops(struct snd_soc_card *card)
>  	int i;
>  
>  	for_each_card_prelinks(card, i, link) {
> -		if (link->no_pcm == 1) {
> +		if (link->no_pcm == 1 || link->num_codecs > 0) {
>  			link->init = sc8280xp_snd_init;
>  			link->be_hw_params_fixup = sc8280xp_be_hw_params_fixup;
>  			link->ops = &sc8280xp_be_ops;
> @@ -375,6 +399,33 @@ static struct snd_soc_common sc8280xp_priv_data = {
>  	.num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
>  };
>  
> +static const struct snd_soc_common shikra_cqm_priv_data = {
> +	.driver_name = "shikra",
> +	.dapm_widgets = shikra_cqm_dapm_widgets,
> +	.num_dapm_widgets = ARRAY_SIZE(shikra_cqm_dapm_widgets),
> +	.dsp_bypass = true,
> +};
> +
> +static const struct snd_soc_common shikra_cqs_priv_data = {
> +	.driver_name = "shikra",
> +	.dapm_widgets = shikra_cqm_dapm_widgets,
> +	.num_dapm_widgets = ARRAY_SIZE(shikra_cqm_dapm_widgets),
> +	.mi2s_bclk_enable = true,
> +	.codec_sysclk_set = true,
> +};
> +
> +static const struct snd_soc_common shikra_iqs_priv_data = {
> +	.driver_name = "shikra",
> +	.dapm_widgets = shikra_iqs_dapm_widgets,
> +	.num_dapm_widgets = ARRAY_SIZE(shikra_iqs_dapm_widgets),
> +	.controls = shikra_iqs_controls,
> +	.num_controls = ARRAY_SIZE(shikra_iqs_controls),
> +	.codec_dai_fmt = SND_SOC_DAIFMT_CBP_CFP |
> +			 SND_SOC_DAIFMT_NB_NF |
> +			 SND_SOC_DAIFMT_I2S,
> +	.codec_sysclk_set = true,
> +	.mi2s_bclk_enable = true,
> +};
> +
>  static struct snd_soc_common sm8450_priv_data = {
>  	.driver_name = "sm8450",
>  	.dapm_widgets = sc8280xp_dapm_widgets,
> @@ -408,6 +459,9 @@ static const struct of_device_id snd_sc8280xp_dt_match[] = {
>  	{.compatible = "qcom,qcs9075-sndcard", .data = &qcs9100_priv_data},
>  	{.compatible = "qcom,qcs9100-sndcard", .data = &qcs9100_priv_data},
>  	{.compatible = "qcom,sc8280xp-sndcard", .data = &sc8280xp_priv_data},
> +	{.compatible = "qcom,shikra-cqm-sndcard", .data = &shikra_cqm_priv_data},
> +	{.compatible = "qcom,shikra-cqs-sndcard", .data = &shikra_cqs_priv_data},
> +	{.compatible = "qcom,shikra-iqs-sndcard", .data = &shikra_iqs_priv_data},
>  	{.compatible = "qcom,sm8450-sndcard", .data = &sm8450_priv_data},
>  	{.compatible = "qcom,sm8550-sndcard", .data = &sm8550_priv_data},
>  	{.compatible = "qcom,sm8650-sndcard", .data = &sm8650_priv_data},
> diff --git a/sound/soc/qcom/sdw.c b/sound/soc/qcom/sdw.c
> index 6576b47a4..0be743cec 100644
> --- a/sound/soc/qcom/sdw.c
> +++ b/sound/soc/qcom/sdw.c
> @@ -4,6 +4,7 @@
>  
>  #include <dt-bindings/sound/qcom,lpass.h>
>  #include <dt-bindings/sound/qcom,q6afe.h>
> +#include <dt-bindings/sound/qcom,qaif.h>
>  #include <linux/module.h>
>  #include <sound/soc.h>
>  #include "sdw.h"
> @@ -41,6 +42,8 @@ static bool qcom_snd_is_sdw_dai(int id)
>  	switch (id) {
>  	case LPASS_CDC_DMA_TX3:
>  	case LPASS_CDC_DMA_RX0:
> +	case QAIF_CDC_DMA_VA_TX0:
> +	case QAIF_CDC_DMA_RX0:
>  		return true;
>  	default:
>  		break;
> -- 
> 2.34.1

-- 
With best wishes
Dmitry

^ permalink raw reply

* Re: [PATCH v5] arm64: dts: qcom: glymur: add coresight nodes
From: Jie Gan @ 2026-06-11 23:11 UTC (permalink / raw)
  To: Konrad Dybcio, Bjorn Andersson, Konrad Dybcio, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Tingwei Zhang
  Cc: linux-arm-msm, devicetree, linux-kernel
In-Reply-To: <3c2fdcf8-957b-4b28-85b7-3a50ab8ee485@oss.qualcomm.com>



On 6/11/2026 5:28 PM, Konrad Dybcio wrote:
> On 5/19/26 3:14 PM, Jie Gan wrote:
>> Add CoreSight nodes to enable trace paths like TPDM->ETF/STM->ETF.
>> These devices are part of the AOSS, CDSP, QDSS, PCIe5, TraceNoc and
>> some small subsystems, such as GCC, IPCC, PMU and so on.
>>
>> Delete cti_wpss DT node on Mahua since this device will cause NoC issue
>> on Mahua device.
> 
> Another good explanation is that it simply doesn't exist there!
> 
>> Signed-off-by: Jie Gan <jie.gan@oss.qualcomm.com>
>> ---
> 
> [...]
> 
> 
>> +	tpdm-cdsp-llm {
> 
> Nodes without a unit address should be sorted alphabetically
> 
>> +		compatible = "qcom,coresight-static-tpdm";
>> +		qcom,cmb-element-bits = <32>;
> 
> Do these other TPDMs not have the "DSB element" as the driver
> calls it?

Depends on the design of the TPDM.

tpdm-cdsp-llm here only supports CMB mode.

There are three types of TPDM:
CMB only
DSB only
support both

> 
>> +
>> +		out-ports {
>> +			port {
>> +				cdsp_llm_tpdm_out: endpoint {
>> +					remote-endpoint = <&cdsp_tpda_in1>;
>> +				};
>> +			};
>> +		};
>> +	};
>> +
>> +	tpdm-cdsp-llm2 {
>> +		compatible = "qcom,coresight-static-tpdm";
>> +		qcom,cmb-element-bits = <32>;
>> +
>> +		out-ports {
>> +			port {
>> +				cdsp_llm2_tpdm_out: endpoint {
>> +					remote-endpoint = <&cdsp_tpda_in2>;
>> +				};
>> +			};
>> +		};
>> +	};
>> +
>> +	tpdm-cdsp-cmsr {
>> +		compatible = "qcom,coresight-static-tpdm";
>> +
>> +		qcom,cmb-element-bits = <32>;
>> +		qcom,dsb-element-bits = <32>;
> 
> Some nodes have a \n between compatible and other properties, some don't
> Please unify the style (fwiw I prefer the one with a \n)

I will take care of it in the future.

Thanks,
Jie


> 
>> base-commit: e98d21c170b01ddef366f023bbfcf6b31509fa83
> 
> Please rebase, doesn't apply on next-20260610
> 
> Konrad


^ permalink raw reply

* Re: [PATCH] arm64: dts: qcom: glymur: fix QUP serial engine IRQs
From: Dmitry Baryshkov @ 2026-06-11 23:04 UTC (permalink / raw)
  To: Bjorn Andersson
  Cc: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Pankaj Patil, Taniya Das,
	Manaf Meethalavalappu Pallikunhi, Jyothi Kumar Seerapu,
	Jishnu Prakash, Maulik Shah, Sibi Sankar, Kamal Wadhwa,
	linux-arm-msm, devicetree, linux-kernel
In-Reply-To: <20260611-glymur-geni-irqs-v1-1-269428faeb6d@oss.qualcomm.com>

On Thu, Jun 11, 2026 at 05:22:37PM +0000, Bjorn Andersson wrote:
> The Geni serial-engine interrupts from QUP wrapper 0 all fall in ESPI
> INTIDs space. While some of the i2c instances has gotten their
> interrupt specifiers corrected, even the other functions on the same
> serial-engines are wrong.
> 
> Ensure that all the serial engine interrupts for QUP wrapper 0 matches
> the datasheet.
> 
> Assisted-by: Codex:GPT-5.5
> Fixes: 41b6e8db400c ("arm64: dts: qcom: Introduce Glymur base dtsi")
> Signed-off-by: Bjorn Andersson <bjorn.andersson@oss.qualcomm.com>
> ---
>  arch/arm64/boot/dts/qcom/glymur.dtsi | 26 +++++++++++++-------------
>  1 file changed, 13 insertions(+), 13 deletions(-)

What about the SPI / I2C controllers which are a part of qupv3_1?


-- 
With best wishes
Dmitry

^ permalink raw reply

* Re: [PATCH v3 2/8] soc: qcom: Add support for QMI TMD cooling devices
From: Dmitry Baryshkov @ 2026-06-11 22:50 UTC (permalink / raw)
  To: Gaurav Kohli
  Cc: Bjorn Andersson, Mathieu Poirier, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Daniel Lezcano, Amit Kucheria,
	Manivannan Sadhasivam, Konrad Dybcio, Kees Cook,
	Gustavo A. R. Silva, cros-qcom-dts-watchers, linux-arm-msm,
	linux-remoteproc, devicetree, linux-kernel, linux-pm,
	linux-hardening, Manaf Meethalavalappu Pallikunhi, Casey Connolly
In-Reply-To: <c5d085d6-822f-46e3-81f4-ef794b8040bd@oss.qualcomm.com>

On Thu, Jun 11, 2026 at 04:23:40PM +0530, Gaurav Kohli wrote:
> 
> 
> On 6/9/2026 5:00 PM, Dmitry Baryshkov wrote:
> > On Tue, Jun 09, 2026 at 03:52:57PM +0530, Gaurav Kohli wrote:
> > > From: Casey Connolly <casey.connolly@linaro.org>
> > > 
> > > Add a Qualcomm QMI Thermal Mitigation Device (TMD) to support thermal
> > > cooling devices backed by remote subsystems.
> > > 
> > > On several Qualcomm platforms, remote processors (for example modem and
> > > CDSP) expose thermal mitigation controls through the TMD QMI service.
> > > Client drivers need a way to discover that service, map DT thermal
> > > mitigation endpoints to cooling devices, and forward cooling state
> > > updates to the remote subsystem.
> > > 
> > > Co-developed-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
> > > Signed-off-by: Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
> > > Signed-off-by: Casey Connolly <casey.connolly@linaro.org>
> > > Signed-off-by: Daniel Lezcano <daniel.lezcano@oss.qualcomm.com>
> > 
> > Wrong SoB chain.
> > 
> 
> Thanks for review, Ack.
> 
> > > ---
> > >   MAINTAINERS                      |   6 +
> > >   drivers/soc/qcom/Kconfig         |  10 +
> > >   drivers/soc/qcom/Makefile        |   1 +
> > >   drivers/soc/qcom/qmi_tmd.c       | 604 +++++++++++++++++++++++++++++++++++++++
> > >   include/linux/soc/qcom/qmi.h     |   1 +
> > >   include/linux/soc/qcom/qmi_tmd.h |  23 ++
> > >   6 files changed, 645 insertions(+)
> > > 
> > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > index 57656ec0e9d5..3d60702a655a 100644
> > > --- a/MAINTAINERS
> > > +++ b/MAINTAINERS
> > > @@ -22286,6 +22286,12 @@ F:	Documentation/devicetree/bindings/net/qcom,ipq9574-ppe.yaml
> > >   F:	Documentation/networking/device_drivers/ethernet/qualcomm/ppe/ppe.rst
> > >   F:	drivers/net/ethernet/qualcomm/ppe/
> > > +QUALCOMM QMI (REMOTEPROC THERMAL MITIGATION) TMD
> > > +M:	Gaurav Kohli <gaurav.kohli@oss.qualcomm.com>
> > > +L:	linux-arm-msm@vger.kernel.org
> > > +L:	linux-pm@vger.kernel.org
> > > +F:	drivers/soc/qcom/qmi_tmd.c
> > > +
> > >   QUALCOMM QSEECOM DRIVER
> > >   M:	Maximilian Luz <luzmaximilian@gmail.com>
> > >   L:	linux-arm-msm@vger.kernel.org
> > > diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
> > > index 2caadbbcf830..a292ce57fd4a 100644
> > > --- a/drivers/soc/qcom/Kconfig
> > > +++ b/drivers/soc/qcom/Kconfig
> > > @@ -128,6 +128,16 @@ config QCOM_QMI_HELPERS
> > >   	tristate
> > >   	depends on NET
> > > +config QCOM_QMI_TMD
> > > +	bool "Qualcomm QMI TMD library" if COMPILE_TEST
> > > +	depends on ARCH_QCOM
> > > +	select QCOM_QMI_HELPERS
> > > +	help
> > > +	  This enables the QMI-based Thermal Mitigation Device (TMD) library
> > > +	  for Qualcomm remote subsystems. The library manages TMD messaging and
> > > +	  handles QMI communication with remote processors (modem, CDSP) to
> > > +	  exchange mitigation state and apply thermal mitigation requests.
> > > +
> > >   config QCOM_RAMP_CTRL
> > >   	tristate "Qualcomm Ramp Controller driver"
> > >   	depends on ARCH_QCOM || COMPILE_TEST
> > > diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
> > > index b7f1d2a57367..4544e61c74e7 100644
> > > --- a/drivers/soc/qcom/Makefile
> > > +++ b/drivers/soc/qcom/Makefile
> > > @@ -14,6 +14,7 @@ obj-$(CONFIG_QCOM_PMIC_GLINK)	+= pmic_glink.o
> > >   obj-$(CONFIG_QCOM_PMIC_GLINK)	+= pmic_glink_altmode.o
> > >   obj-$(CONFIG_QCOM_PMIC_PDCHARGER_ULOG)	+= pmic_pdcharger_ulog.o
> > >   CFLAGS_pmic_pdcharger_ulog.o	:=  -I$(src)
> > > +obj-$(CONFIG_QCOM_QMI_TMD) += qmi_tmd.o
> > >   obj-$(CONFIG_QCOM_QMI_HELPERS)	+= qmi_helpers.o
> > >   qmi_helpers-y	+= qmi_encdec.o qmi_interface.o
> > >   obj-$(CONFIG_QCOM_RAMP_CTRL)	+= ramp_controller.o
> > > diff --git a/drivers/soc/qcom/qmi_tmd.c b/drivers/soc/qcom/qmi_tmd.c
> > > new file mode 100644
> > > index 000000000000..9d88ae48c864
> > > --- /dev/null
> > > +++ b/drivers/soc/qcom/qmi_tmd.c
> > > @@ -0,0 +1,604 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
> > > + * Copyright (c) 2025, Linaro Limited
> > > + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> > > + *
> > > + * QMI Thermal Mitigation Device (TMD) library.
> > > + * This library provides cooling device support for remote subsystems
> > > + * (modem and CDSP) running the TMD service via QMI.
> > 
> > Why are you limiting it to these DSPs only? I quickly checked, my X13s
> > (sc8280xp) also has one on the ADSP.
> > 
> 
> Adsp is low power dsp, not prone to thermal issues and no TMD service is up
> and running. So we are defining for Cdsp and Modem only.

Again, if there is no TMD service, what do I observe on my laptop? Or
what do I see on other devices (checked on SM8350, which I had at hand)?

# qrtr-lookup  |grep Thermal.mitigation
       24       1        0    0    22 Thermal mitigation device service
       24       1        1    5     9 Thermal mitigation device service
       24       1       67   10     9 Thermal mitigation device service
# echo stop > /sys/class/remoteproc/
# cat /sys/class/remoteproc/remoteproc*/name
adsp
modem
slpi
cdsp
# echo stop > /sys/class/remoteproc/remoteproc0/state
[   68.353571] ucsi_glink.pmic_glink_ucsi pmic_glink.ucsi.0: failed to send UCSI write request: -5
[   68.354150] qcom_q6v5_pas 3000000.remoteproc: Handover signaled, but it already happened
[   68.503057] remoteproc remoteproc0: stopped remote processor adsp
# qrtr-lookup | grep Thermal.mitigation
       24       1        0    0    22 Thermal mitigation device service
       24       1       67   10     9 Thermal mitigation device service

So, obviously, there is a TMD service running on the ADSP.

> > > +
> > > +static int qmi_tmd_get_cur_state(struct thermal_cooling_device *cdev,
> > > +				 unsigned long *state)
> > > +{
> > > +	struct qmi_tmd *tmd = cdev->devdata;
> > > +
> > > +	*state = tmd->cur_state;
> > 
> > Mutex protection?
> > 
> 
> This callback only reads the cached local state and does not perform any QMI
> transaction, that's why not using lock here.

And what is protecting the cached state from the concurrent
modification, ending up with the read of incorrect value?

> 
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static int qmi_tmd_set_cur_state(struct thermal_cooling_device *cdev,
> > > +				 unsigned long state)
> > > +{
> > > +	struct qmi_tmd *tmd = cdev->devdata;
> > > +	int ret;
> > > +
> > > +	if (state > tmd->max_state)
> > > +		return -EINVAL;
> > > +
> > > +	if (tmd->cur_state == state)
> > > +		return 0;
> > 
> > Hmm, again, mutex protection for the cur_state? Or is it provided by the
> > thermal core?
> > 
> 
> Yes, it is already protected by cdev->lock.

Comment.

> 
> > > +
> > > +	ret = qmi_tmd_send_state_request(tmd, state);
> > > +	if (!ret)
> > > +		tmd->cur_state = state;
> > > +
> > > +	return ret;
> > > +}
> > > +
> > > +static const struct thermal_cooling_device_ops qmi_tmd_cooling_ops = {
> > > +	.get_max_state = qmi_tmd_get_max_state,
> > > +	.get_cur_state = qmi_tmd_get_cur_state,
> > > +	.set_cur_state = qmi_tmd_set_cur_state,
> > > +};
> > > +
> > > +static int qmi_tmd_register(struct qmi_tmd_client *qmi_tmd_cli,
> > > +			    const char *label, u8 max_state)
> > > +{
> > > +	struct device *dev = qmi_tmd_cli->dev;
> > > +	struct qmi_tmd *tmd;
> > > +	int index;
> > > +
> > > +	for (index = 0; index < qmi_tmd_cli->num_tmds; index++) {
> > > +		tmd = &qmi_tmd_cli->tmds[index];
> > > +
> > > +		if (!strncasecmp(tmd->name, label,
> > > +				 QMI_TMD_DEV_ID_LEN_MAX + 1))
> > > +			goto found;
> > > +	}
> > > +
> > > +	dev_dbg(qmi_tmd_cli->dev,
> > > +		"TMD '%s' available in firmware but not specified in DT\n",
> > > +		label);
> > 
> > If we can read them from the firmware, why do you need to specify them
> > in DT?
> > 
> 
> We need DT, because cooling-device binding is index-based as per new thermal
> framework changes:
> https://lore.kernel.org/all/20260526140802.1059293-12-daniel.lezcano@oss.qualcomm.com/
> 
> And also the firmware list is name-based and ordering is not consistent
> across platforms/firmware. DT provides the explicit set and order of TMDs
> that thermal framework should bind. Firmware may also expose
> additional/virtual TMD's that are not used for binding with thermal zone.

I've asked this elsewhere. It's not clear how different TMDs are used.
There are comments by other reviewers regarding the tmd-names property.


-- 
With best wishes
Dmitry

^ permalink raw reply

* Re: [PATCH v6 5/5] iio: adc: versal-sysmon: add oversampling support
From: sashiko-bot @ 2026-06-11 22:40 UTC (permalink / raw)
  To: Salih Erim; +Cc: robh, devicetree, conor+dt
In-Reply-To: <20260611222738.2035062-6-salih.erim@amd.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Inconsistent IIO UAPI and incorrect attribute scoping for oversampling ratio.
- [Medium] Software cache of the oversampling ratio is out of sync with hardware state.
--

commit 274d65ae0591be4c40c4e7a8107c4b94a62ac67c
Author: Salih Erim <salih.erim@amd.com>

iio: adc: versal-sysmon: add oversampling support
    
Add support for reading and writing the oversampling ratio through
the IIO oversampling_ratio attribute. The hardware supports averaging
2, 4, 8, or 16 samples, plus a ratio of 1 (no averaging).

> diff --git a/drivers/iio/adc/versal-sysmon-core.c b/drivers/iio/adc/versal-sysmon-core.c
> index 20fd3a87d448e..fa8f0dc868af2 100644
> --- a/drivers/iio/adc/versal-sysmon-core.c
> +++ b/drivers/iio/adc/versal-sysmon-core.c

[ ... ]

> @@ -857,7 +996,11 @@ static int sysmon_parse_fw(struct iio_dev *indio_dev, struct device *dev,
>  			.address = SYSMON_TEMP_SAT_BASE +
>  				   (reg - 1) * SYSMON_REG_STRIDE,
>  			.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
> -			.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
> +			.info_mask_shared_by_type =
> +				BIT(IIO_CHAN_INFO_SCALE) |
> +				BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
> +			.info_mask_shared_by_type_available =
> +				BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
>  			.datasheet_name = label,

[Severity: Medium]
Does this inconsistently expose the oversampling attribute?

The commit adds BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) to
info_mask_shared_by_type for the dynamic satellite temperature channels here,
but it appears to be omitted from the statically defined core temperature
channels (temp_channels_no_events and temp_channels_with_events).

Because of IIO's attribute merging behavior, if no satellite channels are
defined in the Device Tree, the in_temp_oversampling_ratio attribute might
not be created at all.

Furthermore, if satellite channels are present, the attribute is created and
appears to apply to all in_temp channels, including the core sensor. However,
the hardware register configured (SYSMON_CONFIG_TEMP_SAT_OSR) seems to
indicate oversampling only applies to the satellite sensors.

Could this create an inconsistent UAPI that either hides the feature when no
satellites are present, or misleadingly claims it applies to the core
temperature sensor?

[ ... ]

> @@ -904,6 +1047,8 @@ int sysmon_core_probe(struct device *dev, struct regmap *regmap)
>  
>  	sysmon = iio_priv(indio_dev);
>  	sysmon->regmap = regmap;
> +	sysmon->temp_oversampling = 1;
> +	sysmon->supply_oversampling = 1;

[Severity: Medium]
Are these software cache variables out of sync with the actual hardware state?

The driver initializes the software cache to 1 (no averaging) here, but it
does not appear to read the actual hardware configuration from the
SYSMON_CONFIG register, nor does it explicitly write this default state of 1
to the hardware.

If the device was previously configured by a bootloader, firmware, or a prior
kernel to use a different oversampling ratio, will the sysfs attribute
incorrectly report 1 while the hardware actively averages multiple samples?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260611222738.2035062-1-salih.erim@amd.com?part=5

^ permalink raw reply

* Re: [PATCH v6 4/5] iio: adc: versal-sysmon: add threshold event support
From: sashiko-bot @ 2026-06-11 22:40 UTC (permalink / raw)
  To: Salih Erim; +Cc: robh, conor+dt, devicetree
In-Reply-To: <20260611222738.2035062-5-salih.erim@amd.com>

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] Integer overflow and lack of bounds checking on user-supplied sysfs threshold values
- [High] Kernel panic due to sleeping in atomic context when I2C regmap backend is paired with an interrupt
- [High] Resource leak: Hardware interrupts are not quiesced on driver unbind, causing unhandled interrupt storms
- [Medium] Race condition between interrupt masking state updates and the delayed worker
- [Medium] ABI Violation: `sysmon_read_event_config` exposes transient hardware masking state instead of persistent software configuration
--

commit 4bd81e58f57baddd1a0af6e02ac6cb6917070ee7
Author: Salih Erim <salih.erim@amd.com>

iio: adc: versal-sysmon: add threshold event support

This commit adds threshold event support for temperature and supply voltage
channels in the Versal Sysmon driver. It introduces hysteresis alarm mode
for temperature and configurable threshold limits.

diff --git a/drivers/iio/adc/versal-sysmon-core.c b/drivers/iio/adc/versal-sysmon-core.c
--- a/drivers/iio/adc/versal-sysmon-core.c
+++ b/drivers/iio/adc/versal-sysmon-core.c
@@ -105,6 +232,289 @@ static int sysmon_read_raw(struct iio_dev *indio_dev,
[ ... ]
> +static void sysmon_millicelsius_to_q8p7(u32 *raw_data, int val)
> +{
> +	*raw_data = (val << SYSMON_FRACTIONAL_SHIFT) / (int)MILLI;
> +}

[Severity: High]
Does this bitwise shift overflow for large values of val?

If a user writes a very large threshold, val << SYSMON_FRACTIONAL_SHIFT
could overflow. Because there is no bounds checking or 16-bit masking, the
negative value might be sign-extended and write a negative number to a 32-bit
hardware register, potentially corrupting adjacent reserved fields.

[ ... ]

> +static void sysmon_supply_processedtoraw(int val, u32 reg_val, u32 *raw_data)
> +{
> +	int exponent = FIELD_GET(SYSMON_MODE_MASK, reg_val);
> +	int format = FIELD_GET(SYSMON_FMT_MASK, reg_val);
> +	int scale, tmp;
> +
> +	scale = BIT(SYSMON_SUPPLY_MANTISSA_BITS - exponent);
> +	tmp = (val * scale) / (int)MILLI;

[Severity: High]
Can val * scale result in an arithmetic overflow here?

If the user supplies a large valid input (e.g. 100,000), multiplying by scale
(which can be up to 65,536) could overflow a 32-bit signed integer. This
would yield a negative result, which is then clamped to 0 below. This would
accidentally set the hardware threshold to 0 and trigger an immediate false
alarm.

[ ... ]

> +static int sysmon_read_event_config(struct iio_dev *indio_dev,
> +				    const struct iio_chan_spec *chan,
> +				    enum iio_event_type type,
> +				    enum iio_event_direction dir)
> +{
> +	u32 alarm_event_mask = sysmon_get_event_mask(chan->address);
> +	struct sysmon *sysmon = iio_priv(indio_dev);
> +	unsigned int imr;
> +	int config_value;
> +	int ret;
> +
> +	ret = regmap_read(sysmon->regmap, SYSMON_IMR, &imr);
> +	if (ret)
> +		return ret;
> +
> +	/* IMR bits are 1=masked, invert to get 1=enabled */
> +	imr = ~imr;
> +
> +	switch (chan->type) {
> +	case IIO_VOLTAGE:
> +		config_value = sysmon_read_alarm_config(sysmon, chan->address);
> +		if (config_value < 0)
> +			return config_value;
> +		return config_value && (imr & alarm_event_mask);
> +
> +	case IIO_TEMP:
> +		return !!(imr & alarm_event_mask);

[Severity: Medium]
Does returning the raw hardware IMR state violate the IIO ABI?

During a temperature alarm, the interrupt handler masks the event in hardware,
and a delayed worker polls before unmasking it. If userspace reads the enable
attribute during this polling window, this function returns 0 (disabled)
because the hardware IMR bit is set, even if the event is administratively
configured as enabled.

Should this return the persistent logical state stored in sysmon->temp_mask
instead?

[ ... ]

> +static int sysmon_write_event_config(struct iio_dev *indio_dev,
> +				     const struct iio_chan_spec *chan,
> +				     enum iio_event_type type,
> +				     enum iio_event_direction dir,
> +				     bool state)
> +{
> +	u32 offset = SYSMON_ALARM_OFFSET(chan->address);
> +	u32 ier = sysmon_get_event_mask(chan->address);
> +	struct sysmon *sysmon = iio_priv(indio_dev);
> +	unsigned int alarm_config;
> +	int ret;
> +
> +	guard(mutex)(&sysmon->lock);
> +
> +	switch (chan->type) {
[ ... ]
> +	case IIO_TEMP:
> +		if (state) {
> +			ret = regmap_write(sysmon->regmap, SYSMON_IER, ier);
> +			if (ret)
> +				return ret;
> +
> +			scoped_guard(spinlock_irq, &sysmon->irq_lock)
> +				sysmon->temp_mask &= ~ier;
> +		} else {
> +			ret = regmap_write(sysmon->regmap, SYSMON_IDR, ier);
> +			if (ret)
> +				return ret;
> +
> +			scoped_guard(spinlock_irq, &sysmon->irq_lock)
> +				sysmon->temp_mask |= ier;
> +		}

[Severity: Medium]
Is there a race condition between writing the hardware register and updating
the software tracking mask?

Because the hardware register (SYSMON_IDR) is written outside of the
sysmon->irq_lock, a window exists where the hardware is disabled but
sysmon->temp_mask has not yet been updated.

If the sysmon_unmask_worker executes during this window, it will read the
stale sysmon->temp_mask (which still indicates the event is enabled):

drivers/iio/adc/versal-sysmon-core.c:sysmon_unmask_temp() {
    ...
    unmask &= ~sysmon->temp_mask;
    regmap_write(sysmon->regmap, SYSMON_IER, unmask);
}

This would cause the worker to mistakenly unmask the interrupt in hardware,
while the software subsequently considers it disabled.

[ ... ]

> +static irqreturn_t sysmon_iio_irq(int irq, void *data)
> +{
> +	struct iio_dev *indio_dev = data;
> +	struct sysmon *sysmon = iio_priv(indio_dev);
> +	unsigned int isr, imr;
> +
> +	guard(spinlock)(&sysmon->irq_lock);
> +
> +	if (regmap_read(sysmon->regmap, SYSMON_ISR, &isr) ||
> +	    regmap_read(sysmon->regmap, SYSMON_IMR, &imr))
> +		return IRQ_NONE;

[ ... ]

> +static int sysmon_init_interrupt(struct sysmon *sysmon,
> +				 struct device *dev,
> +				 struct iio_dev *indio_dev,
> +				 int irq)
> +{
> +	unsigned int imr;
> +	int ret;
> +
> +	/* Events not supported without IRQ (e.g. I2C path) */
> +	if (!irq)
> +		return 0;
> +
> +	ret = devm_delayed_work_autocancel(dev, &sysmon->sysmon_unmask_work,
> +					   sysmon_unmask_worker);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_read(sysmon->regmap, SYSMON_IMR, &imr);
> +	if (ret)
> +		return ret;
> +	sysmon->temp_mask = imr & SYSMON_TEMP_INTR_MASK;
> +
> +	return devm_request_irq(dev, irq, sysmon_iio_irq, 0, "sysmon-irq", indio_dev);
> +}

[Severity: High]
Could this cause a scheduling while atomic panic if an I2C device node
specifies an interrupt?

The hardirq handler sysmon_iio_irq() acquires a spinlock and then performs a
regmap_read(). If the DT provides an interrupts property for the I2C backend,
the driver registers the hardirq handler because of the check for irq > 0.

Since I2C regmap transactions can sleep, calling regmap_read() inside the
spinlock-protected hardirq handler will crash the system.

Should there be a check for !regmap_might_sleep(sysmon->regmap) before
registering the interrupt handler?

[ ... ]

> @@ -256,6 +908,7 @@ int sysmon_core_probe(struct device *dev, struct regmap *regmap)
>  	ret = devm_mutex_init(dev, &sysmon->lock);
>  	if (ret)
>  		return ret;
> +	spin_lock_init(&sysmon->irq_lock);
>  
>  	/* Disable all interrupts and clear pending status */
>  	ret = regmap_write(sysmon->regmap, SYSMON_IDR, SYSMON_INTR_ALL_MASK);
> @@ -265,13 +918,40 @@ int sysmon_core_probe(struct device *dev, struct regmap *regmap)
>  	if (ret)
>  		return ret;
>  
> +	irq = fwnode_irq_get(dev_fwnode(dev), 0);
> +	if (irq == -EPROBE_DEFER)
> +		return dev_err_probe(dev, irq, "failed to get IRQ\n");
> +
>  	indio_dev->name = "versal-sysmon";
>  	indio_dev->modes = INDIO_DIRECT_MODE;
>  
> -	ret = sysmon_parse_fw(indio_dev, dev);
> +	ret = sysmon_parse_fw(indio_dev, dev, irq);
>  	if (ret)
>  		return ret;
>  
> +	if (irq > 0) {
> +		/* Set hysteresis mode for both temperature channels */
> +		ret = regmap_set_bits(sysmon->regmap, SYSMON_TEMP_EV_CFG,
> +				      SYSMON_OT_HYST_MASK |
> +				      SYSMON_TEMP_HYST_MASK);
> +		if (ret)
> +			return ret;
> +
> +		/* Initialize cached hysteresis from hardware registers */
> +		ret = sysmon_init_hysteresis(sysmon, SYSMON_ADDR_TEMP_EVENT,
> +					     &sysmon->temp_hysteresis);
> +		if (ret)
> +			return ret;
> +		ret = sysmon_init_hysteresis(sysmon, SYSMON_ADDR_OT_EVENT,
> +					     &sysmon->ot_hysteresis);
> +		if (ret)
> +			return ret;
> +
> +		ret = sysmon_init_interrupt(sysmon, dev, indio_dev, irq);
> +		if (ret)
> +			return ret;
> +	}
> +
>  	return devm_iio_device_register(dev, indio_dev);
>  }

[Severity: High]
Are hardware interrupts left enabled if the driver is unbound?

While interrupts are quiesced during sysmon_core_probe(), there appears to
be no cleanup callback registered (like a devm_add_action_or_reset) to mask
the interrupts on driver removal.

If userspace enables threshold alarms and the driver is then unbound, the
devm infrastructure will free the IRQ handler, but the device's hardware
interrupts will remain active. This could result in an unhandled interrupt
storm that disables the shared IRQ line.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260611222738.2035062-1-salih.erim@amd.com?part=4

^ permalink raw reply

* Re: [PATCH v4 0/4] IPQ5018: Add and enable GEPHY RX and TX clocks
From: patchwork-bot+netdevbpf @ 2026-06-11 22:40 UTC (permalink / raw)
  To: George Moussalem
  Cc: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, robh,
	krzk+dt, conor+dt, f.fainelli, andersson, konradybcio, netdev,
	devicetree, linux-kernel, konrad.dybcio, linux-arm-msm,
	conor.dooley, dmitry.baryshkov
In-Reply-To: <20260608-ipq5018-gephy-clocks-v4-0-fb2ccd56894b@outlook.com>

Hello:

This series was applied to netdev/net-next.git (main)
by Jakub Kicinski <kuba@kernel.org>:

On Mon, 08 Jun 2026 09:09:15 +0400 you wrote:
> Greetings,
> 
> This patch series addresses a missing hardware description issue for the
> Qualcomm IPQ5018 Internal Ethernet PHY, where the data paths fail to
> function correctly unless their dedicated RX and TX clocks are
> explicitly enabled.
> 
> [...]

Here is the summary with links:
  - [v4,1/4] dt-bindings: net: ethernet-phy: increase max clock count to two
    https://git.kernel.org/netdev/net-next/c/19e00bdfbb1b
  - [v4,2/4] dt-bindings: net: qca,ar803x: Add clocks for IPQ5018 PHY
    https://git.kernel.org/netdev/net-next/c/fea4ae4b5b50
  - [v4,3/4] arm64: qcom: ipq5018: Add GEPHY RX and TX clocks
    (no matching commit)
  - [v4,4/4] net: phy: at803x: add RX and TX clock management for IPQ5018 PHY
    https://git.kernel.org/netdev/net-next/c/cf6077e4903f

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* Re: [PATCH v6 3/5] iio: adc: versal-sysmon: add I2C driver
From: sashiko-bot @ 2026-06-11 22:37 UTC (permalink / raw)
  To: Salih Erim; +Cc: devicetree, robh, conor+dt
In-Reply-To: <20260611222738.2035062-4-salih.erim@amd.com>

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The driver unconditionally registers an interrupt handler based on the Device Tree, which causes a panic on I2C systems because the I2C regmap backend cannot sleep in atomic context.
--

commit adf35a1c0d89c3413cd1a863d9f01cfad15d4e03
Author: Salih Erim <salih.erim@amd.com>

iio: adc: versal-sysmon: add I2C driver

This commit adds an I2C transport driver for the Versal SysMon block. It
enables external masters to read voltage and temperature measurements through
the same register map used by the MMIO path, though events are not supported.

> diff --git a/drivers/iio/adc/versal-sysmon-i2c.c b/drivers/iio/adc/versal-sysmon-i2c.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/iio/adc/versal-sysmon-i2c.c

[ ... ]

> +static int sysmon_i2c_probe(struct i2c_client *client)
> +{
> +	struct device *dev = &client->dev;
> +	struct regmap *regmap;
> +
> +	regmap = devm_regmap_init(dev, NULL, client, &sysmon_i2c_regmap_config);
> +	if (IS_ERR(regmap))
> +		return PTR_ERR(regmap);
> +
> +	/* I2C has no IRQ connection; events are not supported */
> +	return sysmon_core_probe(dev, regmap);

[Severity: High]
If a misconfigured Device Tree provides an interrupts property for the I2C
node, will this unconditionally register a hardirq handler?

Looking at sysmon_core_probe() in versal-sysmon-core.c, it parses the IRQ
directly from the fwnode and sets up the interrupt if found:

    irq = fwnode_irq_get(dev_fwnode(dev), 0);
    ...
    if (irq > 0) {
        ...
        ret = sysmon_init_interrupt(sysmon, dev, indio_dev, irq);

If that interrupt fires, sysmon_iio_irq() attempts to take a spinlock and
perform a regmap_read():

    guard(spinlock)(&sysmon->irq_lock);

    if (regmap_read(sysmon->regmap, SYSMON_ISR, &isr) ||

Since the I2C regmap backend requires sleeping to perform I2C transfers,
won't executing this over I2C in a hardirq context cause a "scheduling while
atomic" panic?

Does the I2C transport need a mechanism to explicitly instruct the core probe
to bypass IRQ parsing?

> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260611222738.2035062-1-salih.erim@amd.com?part=3

^ permalink raw reply

* Re: [PATCH] arm64: dts: socfpga: agilex5: Add per-channel interrupts to gmac0
From: Dinh Nguyen @ 2026-06-11 22:35 UTC (permalink / raw)
  To: muhammad.nazim.amirul.nazle.asmade
  Cc: robh, krzk+dt, conor+dt, devicetree, linux-kernel
In-Reply-To: <20260609131641.28476-1-muhammad.nazim.amirul.nazle.asmade@altera.com>



On 6/9/26 22:16, muhammad.nazim.amirul.nazle.asmade@altera.com wrote:
> From: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
> 
> Extend the gmac0 interrupt list to support 8 TX and 8 RX per-channel
> interrupts in addition to the combined macirq, enabling per-channel
> interrupt handling for improved DMA performance.
> 
> Signed-off-by: Nazim Amirul <muhammad.nazim.amirul.nazle.asmade@altera.com>
> ---
>   .../arm64/boot/dts/intel/socfpga_agilex5.dtsi | 36 +++++++++++++++++--
>   1 file changed, 34 insertions(+), 2 deletions(-)
> 
> diff --git a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
> index b06c6d5d60ee..c936f8db1bd0 100644
> --- a/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
> +++ b/arch/arm64/boot/dts/intel/socfpga_agilex5.dtsi
> @@ -557,8 +557,40 @@ gmac0: ethernet@10810000 {
>   			compatible = "altr,socfpga-stmmac-agilex5",
>   				     "snps,dwxgmac-2.10";
>   			reg = <0x10810000 0x3500>;
> -			interrupts = <GIC_SPI 190 IRQ_TYPE_LEVEL_HIGH>;
> -			interrupt-names = "macirq";
> +			interrupts = <GIC_SPI 190 IRQ_TYPE_LEVEL_HIGH>,
> +				     <GIC_SPI 191 IRQ_TYPE_LEVEL_HIGH>,
> +				     <GIC_SPI 192 IRQ_TYPE_LEVEL_HIGH>,
> +				     <GIC_SPI 193 IRQ_TYPE_LEVEL_HIGH>,
> +				     <GIC_SPI 194 IRQ_TYPE_LEVEL_HIGH>,
> +				     <GIC_SPI 195 IRQ_TYPE_LEVEL_HIGH>,
> +				     <GIC_SPI 196 IRQ_TYPE_LEVEL_HIGH>,
> +				     <GIC_SPI 197 IRQ_TYPE_LEVEL_HIGH>,
> +				     <GIC_SPI 198 IRQ_TYPE_LEVEL_HIGH>,
> +				     <GIC_SPI 199 IRQ_TYPE_LEVEL_HIGH>,
> +				     <GIC_SPI 200 IRQ_TYPE_LEVEL_HIGH>,
> +				     <GIC_SPI 201 IRQ_TYPE_LEVEL_HIGH>,
> +				     <GIC_SPI 202 IRQ_TYPE_LEVEL_HIGH>,
> +				     <GIC_SPI 203 IRQ_TYPE_LEVEL_HIGH>,
> +				     <GIC_SPI 204 IRQ_TYPE_LEVEL_HIGH>,
> +				     <GIC_SPI 205 IRQ_TYPE_LEVEL_HIGH>,
> +				     <GIC_SPI 206 IRQ_TYPE_LEVEL_HIGH>;
> +			interrupt-names = "macirq",
> +					  "tx-queue-0",
> +					  "tx-queue-1",
> +					  "tx-queue-2",
> +					  "tx-queue-3",
> +					  "tx-queue-4",
> +					  "tx-queue-5",
> +					  "tx-queue-6",
> +					  "tx-queue-7",
> +					  "rx-queue-0",
> +					  "rx-queue-1",
> +					  "rx-queue-2",
> +					  "rx-queue-3",
> +					  "rx-queue-4",
> +					  "rx-queue-5",
> +					  "rx-queue-6",
> +					  "rx-queue-7";
>   			resets = <&rst EMAC0_RESET>, <&rst EMAC0_OCP_RESET>;
>   			reset-names = "stmmaceth", "ahb";
>   			clocks = <&clkmgr AGILEX5_EMAC0_CLK>,

Applied!

Thanks,
Dinh


^ permalink raw reply

* [PATCH v6 4/5] iio: adc: versal-sysmon: add threshold event support
From: Salih Erim @ 2026-06-11 22:27 UTC (permalink / raw)
  To: jic23, andy
  Cc: dlechner, nuno.sa, robh, krzk+dt, conor+dt, conall.ogriofa,
	michal.simek, linux, erimsalih, linux-iio, devicetree,
	linux-kernel, Salih Erim
In-Reply-To: <20260611222738.2035062-1-salih.erim@amd.com>

Add threshold event support for temperature and supply voltage
channels.

Temperature events:
  - Rising threshold with configurable value
  - Over-temperature (OT) alarm with separate threshold
  - Per-channel hysteresis as a millicelsius value
  - Event direction is IIO_EV_DIR_RISING (hysteresis mode)

Supply voltage events:
  - Rising/falling threshold per supply channel
  - Per-channel alarm enable via alarm configuration registers

The hardware supports both window and hysteresis alarm modes for
temperature. This driver uses hysteresis mode, where the upper
threshold triggers the alarm and the lower threshold clears it
(re-arm point). The hardware has a single ISR bit per temperature
channel with no indication of which threshold was crossed, so
hysteresis mode is the natural fit. The lower threshold register
is computed internally as (upper - hysteresis).

Hysteresis is stored in the driver as a millicelsius value,
initialized from the hardware registers at probe. Writing the
rising threshold or hysteresis recomputes the lower register.
ALARM_CONFIG is hard-coded to hysteresis mode during init.

The interrupt handler masks active threshold interrupts (which are
level-sensitive) and schedules a delayed worker to poll for condition
clear before unmasking. When no hardware IRQ is available, event
channels are not created and interrupt init is skipped, since the
I2C regmap backend cannot be called from atomic context.

When disabling a supply channel alarm, the group interrupt remains
active if any other channel in the same alarm group still has an
alarm enabled.

Signed-off-by: Salih Erim <salih.erim@amd.com>
---
Changes in v6:
  - Remove types.h from header (not needed at any stage) (Andy)
  - Macro brace on separate line for SYSMON_CHAN_TEMP_EVENT (Andy)
  - switch(chan->type) in all event functions instead of cascading
    if statements (Andy)
  - switch(info) in read/write_event_value for nested
    dispatch (Andy)
  - Reversed xmas tree in sysmon_update_temp_lower and
    sysmon_init_hysteresis (Andy)
  - scoped_guard(spinlock_irq) with error check in
    sysmon_unmask_worker (Andy)
  - Combined regmap_read error check with || in
    sysmon_iio_irq (Andy)
  - Join devm_request_irq on one line (Andy)
  - Fix fwnode_irq_get() to propagate only -EPROBE_DEFER;
    treating all negatives as fatal broke probe on I2C nodes
    without interrupts property

Changes in v5:
  - clamp() instead of clamp_t() (Andy)
  - regmap_assign_bits() instead of separate set/clear (Andy)
  - Remove unneeded parentheses (2 places) (Andy)
  - for_each_set_bit on single line (Andy)
  - regmap_clear_bits() instead of regmap_update_bits() (Andy)
  - Simplify unmask XOR to ~status & masked_temp (Andy)
  - Add comment explaining unmask &= ~temp_mask logic (Andy)
  - Split container_of across two lines (Andy)
  - Move ISR write after !isr check to avoid writing 0 (Andy)
  - unsigned int for init_hysteresis address param (Andy)
  - Add comment explaining error check policy in worker/IRQ (Andy)
  - Nested size_add() for overflow-safe allocation (Andy)
  - Propagate negative from fwnode_irq_get() for
    EPROBE_DEFER (Andy)
  - Pass irq instead of has_irq to sysmon_parse_fw (Andy)

Changes in v4:
  - Merge event channels into static temp array; two arrays
    (with/without events) selected by has_irq (Jonathan)
  - Event-only channels have no info_mask; their addresses are
    logical identifiers, not readable registers
  - Drop RAW for voltage events, keep PROCESSED only (Jonathan)
  - Drop scan_type from event channel macro (Jonathan)
  - Blank lines between call+error-check blocks (Jonathan)
  - Fit under 80 chars on one line where possible (Jonathan)
  - default case returns -EINVAL instead of break (Jonathan)
  - sysmon_handle_event: return early in each case (Jonathan)
  - guard(spinlock) in sysmon_iio_irq, return IRQ_NONE/IRQ_HANDLED
    directly (Jonathan)
  - Take irq_lock in write_event_config for temp_mask updates to
    synchronize with unmask worker (Sashiko)

Changes in v3:
  - IWYU: add new includes, group iio headers with blank line (Andy)
  - Reduce casts in millicelsius_to_q8p7, consistent style with
    q8p7_to_millicelsius (Andy)
  - Use clamp_t with typed constants, remove tmp & U16_MAX (Andy)
  - Use !! to return 0/1 from read_alarm_config (Andy)
  - Use regmap_set_bits/clear_bits in write_alarm_config (Andy)
  - Add comment explaining spinlock is safe (I2C never reaches
    event code path) (Andy)
  - Add comment explaining IMR negation logic (Andy)
  - Split read_event_value/write_event_value parameters logically
    across lines (Andy)
  - Move mask/shift after regmap_read error check (Andy)
  - Remove redundant else in read_event_value and
    write_event_value (Andy)
  - Use named constant for hysteresis bit, if-else not ternary
    (Andy)
  - Loop variable declared in for() scope (Andy)
  - Add error checks in sysmon_handle_event (Andy)
  - Use IRQ_RETVAL() macro (Andy)
  - Use devm_delayed_work_autocancel instead of manual INIT +
    devm_add_action (Andy)
  - Use FIELD_GET/FIELD_PREP for hysteresis register bits
    (Jonathan)
  - Split OT vs TEMP handling with FIELD_GET (Jonathan)
  - Rework hysteresis: store as millicelsius value, hardcode
    ALARM_CONFIG to hysteresis mode, compute lower threshold
    from (upper - hysteresis), initialize from HW at probe
    (Jonathan)
  - Remove falling threshold for temperature; single event
    spec per channel with IIO_EV_DIR_RISING (Jonathan)
  - Push IIO_EV_DIR_RISING events for temperature,
    IIO_EV_DIR_EITHER for voltage (Jonathan)

Changes in v2:
  - Reverse Christmas Tree variable ordering in all functions
  - Named constants for hysteresis bits: SYSMON_OT_HYST_BIT,
    SYSMON_TEMP_HYST_BIT instead of magic 0x1/0x2
  - SYSMON_ALARM_BITS_PER_REG replaces magic number 32
  - SYSMON_ALARM_OFFSET() helper macro deduplicates alarm register
    offset computation
  - BIT() macro for shift expressions in conversion functions
  - Hysteresis input validated to single-bit range (0 or 1)
  - Event channels only created when irq > 0 (I2C safety)
  - Group alarm interrupt stays active while any channel in the
    group has an alarm enabled
  - write_event_value returns -EINVAL for unhandled types
  - IRQ_NONE returned for spurious interrupts
  - Q8.7 write path uses multiplication instead of left-shift
    to avoid undefined behavior with negative temperatures
  - (u16) mask prevents garbage in reserved register bits
  - regmap_write return values checked for IER/IDR writes
  - devm cleanup ordering: cancel_work before request_irq
 drivers/iio/adc/versal-sysmon-core.c | 700 ++++++++++++++++++++++++++-
 drivers/iio/adc/versal-sysmon.h      |  44 ++
 2 files changed, 734 insertions(+), 10 deletions(-)

diff --git a/drivers/iio/adc/versal-sysmon-core.c b/drivers/iio/adc/versal-sysmon-core.c
index c875d156dbe..20fd3a87d44 100644
--- a/drivers/iio/adc/versal-sysmon-core.c
+++ b/drivers/iio/adc/versal-sysmon-core.c
@@ -11,7 +11,9 @@
 #include <linux/bitops.h>
 #include <linux/cleanup.h>
 #include <linux/device.h>
+#include <linux/devm-helpers.h>
 #include <linux/err.h>
+#include <linux/interrupt.h>
 #include <linux/module.h>
 #include <linux/property.h>
 #include <linux/regmap.h>
@@ -19,10 +21,19 @@
 #include <linux/sysfs.h>
 #include <linux/units.h>
 
+#include <linux/iio/events.h>
 #include <linux/iio/iio.h>
 
 #include "versal-sysmon.h"
 
+/* OT and TEMP hysteresis mode bits in SYSMON_TEMP_EV_CFG */
+#define SYSMON_OT_HYST_MASK		BIT(0)
+#define SYSMON_TEMP_HYST_MASK		BIT(1)
+
+/* Compute alarm register offset from a channel address */
+#define SYSMON_ALARM_OFFSET(addr) \
+	(SYSMON_ALARM_REG + ((addr) / SYSMON_ALARM_BITS_PER_REG) * SYSMON_REG_STRIDE)
+
 #define SYSMON_CHAN_TEMP(_chan, _address, _name)		\
 {								\
 	.type = IIO_TEMP,					\
@@ -34,14 +45,87 @@
 	.datasheet_name = _name,				\
 }
 
+#define SYSMON_CHAN_TEMP_EVENT(_chan, _address, _name, _events)	\
+{								\
+	.type = IIO_TEMP,					\
+	.indexed = 1,						\
+	.address = _address,					\
+	.channel = _chan,					\
+	.event_spec = _events,					\
+	.num_event_specs = ARRAY_SIZE(_events),			\
+	.datasheet_name = _name,				\
+}
+
+enum sysmon_alarm_bit {
+	SYSMON_BIT_ALARM0 = 0,
+	SYSMON_BIT_ALARM1 = 1,
+	SYSMON_BIT_ALARM2 = 2,
+	SYSMON_BIT_ALARM3 = 3,
+	SYSMON_BIT_ALARM4 = 4,
+	SYSMON_BIT_OT = 8,
+	SYSMON_BIT_TEMP = 9,
+};
+
+/* Temperature event specification: rising threshold + hysteresis only */
+static const struct iio_event_spec sysmon_temp_events[] = {
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_RISING,
+		.mask_separate = BIT(IIO_EV_INFO_ENABLE) |
+				 BIT(IIO_EV_INFO_VALUE) |
+				 BIT(IIO_EV_INFO_HYSTERESIS),
+	},
+};
+
+/* Supply event specifications */
+static const struct iio_event_spec sysmon_supply_events[] = {
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_RISING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE),
+	},
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_FALLING,
+		.mask_separate = BIT(IIO_EV_INFO_VALUE),
+	},
+	{
+		.type = IIO_EV_TYPE_THRESH,
+		.dir = IIO_EV_DIR_EITHER,
+		.mask_separate = BIT(IIO_EV_INFO_ENABLE),
+	},
+};
+
 /* Static temperature channels (always present) */
-static const struct iio_chan_spec temp_channels[] = {
+static const struct iio_chan_spec temp_channels_no_events[] = {
 	SYSMON_CHAN_TEMP(0, SYSMON_TEMP_MAX, "temp"),
 	SYSMON_CHAN_TEMP(1, SYSMON_TEMP_MIN, "min"),
 	SYSMON_CHAN_TEMP(2, SYSMON_TEMP_MAX_MAX, "max_max"),
 	SYSMON_CHAN_TEMP(3, SYSMON_TEMP_MIN_MIN, "min_min"),
 };
 
+/* Static temperature channels with event support (when IRQ available) */
+static const struct iio_chan_spec temp_channels_with_events[] = {
+	SYSMON_CHAN_TEMP(0, SYSMON_TEMP_MAX, "temp"),
+	SYSMON_CHAN_TEMP(1, SYSMON_TEMP_MIN, "min"),
+	SYSMON_CHAN_TEMP(2, SYSMON_TEMP_MAX_MAX, "max_max"),
+	SYSMON_CHAN_TEMP(3, SYSMON_TEMP_MIN_MIN, "min_min"),
+	SYSMON_CHAN_TEMP_EVENT(4, SYSMON_ADDR_TEMP_EVENT, "temp",
+			      sysmon_temp_events),
+	SYSMON_CHAN_TEMP_EVENT(5, SYSMON_ADDR_OT_EVENT, "ot",
+			      sysmon_temp_events),
+};
+
+static void sysmon_q8p7_to_millicelsius(s16 raw_data, int *val)
+{
+	*val = (raw_data * (int)MILLI) >> SYSMON_FRACTIONAL_SHIFT;
+}
+
+static void sysmon_millicelsius_to_q8p7(u32 *raw_data, int val)
+{
+	*raw_data = (val << SYSMON_FRACTIONAL_SHIFT) / (int)MILLI;
+}
+
 static void sysmon_supply_rawtoprocessed(int raw_data, int *val)
 {
 	int mantissa, format, exponent;
@@ -59,6 +143,49 @@ static void sysmon_supply_rawtoprocessed(int raw_data, int *val)
 	*val = (mantissa * (int)MILLI) >> exponent;
 }
 
+static void sysmon_supply_processedtoraw(int val, u32 reg_val, u32 *raw_data)
+{
+	int exponent = FIELD_GET(SYSMON_MODE_MASK, reg_val);
+	int format = FIELD_GET(SYSMON_FMT_MASK, reg_val);
+	int scale, tmp;
+
+	scale = BIT(SYSMON_SUPPLY_MANTISSA_BITS - exponent);
+	tmp = (val * scale) / (int)MILLI;
+
+	if (format)
+		tmp = clamp(tmp, S16_MIN, S16_MAX);
+	else
+		tmp = clamp(tmp, 0, U16_MAX);
+
+	*raw_data = (u16)tmp;
+}
+
+static int sysmon_temp_thresh_offset(int address,
+				     enum iio_event_direction dir)
+{
+	switch (address) {
+	case SYSMON_ADDR_TEMP_EVENT:
+		return (dir == IIO_EV_DIR_RISING) ? SYSMON_TEMP_TH_UP :
+						    SYSMON_TEMP_TH_LOW;
+	case SYSMON_ADDR_OT_EVENT:
+		return (dir == IIO_EV_DIR_RISING) ? SYSMON_OT_TH_UP :
+						    SYSMON_OT_TH_LOW;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int sysmon_supply_thresh_offset(int address,
+				       enum iio_event_direction dir)
+{
+	if (dir == IIO_EV_DIR_RISING)
+		return (address * SYSMON_REG_STRIDE) + SYSMON_SUPPLY_TH_UP;
+	if (dir == IIO_EV_DIR_FALLING)
+		return (address * SYSMON_REG_STRIDE) + SYSMON_SUPPLY_TH_LOW;
+
+	return -EINVAL;
+}
+
 static int sysmon_read_raw(struct iio_dev *indio_dev,
 			   struct iio_chan_spec const *chan,
 			   int *val, int *val2, long mask)
@@ -105,6 +232,289 @@ static int sysmon_read_raw(struct iio_dev *indio_dev,
 	}
 }
 
+static int sysmon_get_event_mask(unsigned long address)
+{
+	if (address == SYSMON_ADDR_TEMP_EVENT)
+		return BIT(SYSMON_BIT_TEMP);
+	if (address == SYSMON_ADDR_OT_EVENT)
+		return BIT(SYSMON_BIT_OT);
+
+	return BIT(address / SYSMON_ALARM_BITS_PER_REG);
+}
+
+static int sysmon_read_alarm_config(struct sysmon *sysmon,
+				    unsigned long address)
+{
+	u32 shift = address % SYSMON_ALARM_BITS_PER_REG;
+	u32 offset = SYSMON_ALARM_OFFSET(address);
+	unsigned int reg_val;
+	int ret;
+
+	ret = regmap_read(sysmon->regmap, offset, &reg_val);
+	if (ret)
+		return ret;
+
+	return !!(reg_val & BIT(shift));
+}
+
+static int sysmon_write_alarm_config(struct sysmon *sysmon,
+				     unsigned long address, bool enable)
+{
+	u32 shift = address % SYSMON_ALARM_BITS_PER_REG;
+	u32 offset = SYSMON_ALARM_OFFSET(address);
+
+	return regmap_assign_bits(sysmon->regmap, offset, BIT(shift), enable);
+}
+
+static int sysmon_read_event_config(struct iio_dev *indio_dev,
+				    const struct iio_chan_spec *chan,
+				    enum iio_event_type type,
+				    enum iio_event_direction dir)
+{
+	u32 alarm_event_mask = sysmon_get_event_mask(chan->address);
+	struct sysmon *sysmon = iio_priv(indio_dev);
+	unsigned int imr;
+	int config_value;
+	int ret;
+
+	ret = regmap_read(sysmon->regmap, SYSMON_IMR, &imr);
+	if (ret)
+		return ret;
+
+	/* IMR bits are 1=masked, invert to get 1=enabled */
+	imr = ~imr;
+
+	switch (chan->type) {
+	case IIO_VOLTAGE:
+		config_value = sysmon_read_alarm_config(sysmon, chan->address);
+		if (config_value < 0)
+			return config_value;
+		return config_value && (imr & alarm_event_mask);
+
+	case IIO_TEMP:
+		return !!(imr & alarm_event_mask);
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static int sysmon_write_event_config(struct iio_dev *indio_dev,
+				     const struct iio_chan_spec *chan,
+				     enum iio_event_type type,
+				     enum iio_event_direction dir,
+				     bool state)
+{
+	u32 offset = SYSMON_ALARM_OFFSET(chan->address);
+	u32 ier = sysmon_get_event_mask(chan->address);
+	struct sysmon *sysmon = iio_priv(indio_dev);
+	unsigned int alarm_config;
+	int ret;
+
+	guard(mutex)(&sysmon->lock);
+
+	switch (chan->type) {
+	case IIO_VOLTAGE:
+		ret = sysmon_write_alarm_config(sysmon, chan->address, state);
+		if (ret)
+			return ret;
+
+		ret = regmap_read(sysmon->regmap, offset, &alarm_config);
+		if (ret)
+			return ret;
+
+		if (alarm_config)
+			return regmap_write(sysmon->regmap, SYSMON_IER, ier);
+
+		return regmap_write(sysmon->regmap, SYSMON_IDR, ier);
+
+	case IIO_TEMP:
+		if (state) {
+			ret = regmap_write(sysmon->regmap, SYSMON_IER, ier);
+			if (ret)
+				return ret;
+
+			scoped_guard(spinlock_irq, &sysmon->irq_lock)
+				sysmon->temp_mask &= ~ier;
+		} else {
+			ret = regmap_write(sysmon->regmap, SYSMON_IDR, ier);
+			if (ret)
+				return ret;
+
+			scoped_guard(spinlock_irq, &sysmon->irq_lock)
+				sysmon->temp_mask |= ier;
+		}
+		return 0;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+/*
+ * Recompute the lower threshold register from upper threshold and
+ * cached hysteresis. Called when either upper threshold or hysteresis
+ * is written.
+ */
+static int sysmon_update_temp_lower(struct sysmon *sysmon, int address)
+{
+	int upper_mc, lower_mc, hysteresis;
+	int upper_off, lower_off, ret;
+	unsigned int upper_reg;
+	u32 raw_val;
+
+	upper_off = sysmon_temp_thresh_offset(address, IIO_EV_DIR_RISING);
+	if (upper_off < 0)
+		return upper_off;
+	lower_off = sysmon_temp_thresh_offset(address, IIO_EV_DIR_FALLING);
+	if (lower_off < 0)
+		return lower_off;
+
+	if (address == SYSMON_ADDR_OT_EVENT)
+		hysteresis = sysmon->ot_hysteresis;
+	else
+		hysteresis = sysmon->temp_hysteresis;
+
+	ret = regmap_read(sysmon->regmap, upper_off, &upper_reg);
+	if (ret)
+		return ret;
+
+	sysmon_q8p7_to_millicelsius(upper_reg, &upper_mc);
+
+	lower_mc = upper_mc - hysteresis;
+	sysmon_millicelsius_to_q8p7(&raw_val, lower_mc);
+
+	return regmap_write(sysmon->regmap, lower_off, raw_val);
+}
+
+static int sysmon_read_event_value(struct iio_dev *indio_dev,
+				   const struct iio_chan_spec *chan,
+				   enum iio_event_type type,
+				   enum iio_event_direction dir,
+				   enum iio_event_info info,
+				   int *val, int *val2)
+{
+	struct sysmon *sysmon = iio_priv(indio_dev);
+	unsigned int reg_val;
+	int offset;
+	int ret;
+
+	guard(mutex)(&sysmon->lock);
+
+	switch (chan->type) {
+	case IIO_TEMP:
+		switch (info) {
+		case IIO_EV_INFO_VALUE:
+			/* Only rising threshold is exposed */
+			offset = sysmon_temp_thresh_offset(chan->address,
+							   IIO_EV_DIR_RISING);
+			if (offset < 0)
+				return offset;
+
+			ret = regmap_read(sysmon->regmap, offset, &reg_val);
+			if (ret)
+				return ret;
+
+			sysmon_q8p7_to_millicelsius(reg_val, val);
+
+			return IIO_VAL_INT;
+
+		case IIO_EV_INFO_HYSTERESIS:
+			if (chan->address == SYSMON_ADDR_OT_EVENT)
+				*val = sysmon->ot_hysteresis;
+			else
+				*val = sysmon->temp_hysteresis;
+			return IIO_VAL_INT;
+
+		default:
+			return -EINVAL;
+		}
+
+	case IIO_VOLTAGE:
+		offset = sysmon_supply_thresh_offset(chan->address, dir);
+		if (offset < 0)
+			return offset;
+
+		ret = regmap_read(sysmon->regmap, offset, &reg_val);
+		if (ret)
+			return ret;
+
+		sysmon_supply_rawtoprocessed(reg_val, val);
+
+		return IIO_VAL_INT;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static int sysmon_write_event_value(struct iio_dev *indio_dev,
+				    const struct iio_chan_spec *chan,
+				    enum iio_event_type type,
+				    enum iio_event_direction dir,
+				    enum iio_event_info info,
+				    int val, int val2)
+{
+	struct sysmon *sysmon = iio_priv(indio_dev);
+	unsigned int reg_val;
+	u32 raw_val;
+	int offset;
+	int ret;
+
+	guard(mutex)(&sysmon->lock);
+
+	switch (chan->type) {
+	case IIO_TEMP:
+		switch (info) {
+		case IIO_EV_INFO_VALUE:
+			/* Only rising threshold is exposed */
+			offset = sysmon_temp_thresh_offset(chan->address,
+							   IIO_EV_DIR_RISING);
+			if (offset < 0)
+				return offset;
+
+			sysmon_millicelsius_to_q8p7(&raw_val, val);
+
+			ret = regmap_write(sysmon->regmap, offset, raw_val);
+			if (ret)
+				return ret;
+
+			/* Recompute lower = upper - hysteresis */
+			return sysmon_update_temp_lower(sysmon, chan->address);
+
+		case IIO_EV_INFO_HYSTERESIS:
+			if (val < 0)
+				return -EINVAL;
+
+			if (chan->address == SYSMON_ADDR_OT_EVENT)
+				sysmon->ot_hysteresis = val;
+			else
+				sysmon->temp_hysteresis = val;
+
+			return sysmon_update_temp_lower(sysmon, chan->address);
+
+		default:
+			return -EINVAL;
+		}
+
+	case IIO_VOLTAGE:
+		offset = sysmon_supply_thresh_offset(chan->address, dir);
+		if (offset < 0)
+			return offset;
+
+		ret = regmap_read(sysmon->regmap, offset, &reg_val);
+		if (ret)
+			return ret;
+
+		sysmon_supply_processedtoraw(val, reg_val, &raw_val);
+
+		return regmap_write(sysmon->regmap, offset, raw_val);
+
+	default:
+		return -EINVAL;
+	}
+}
+
 static int sysmon_read_label(struct iio_dev *indio_dev,
 			     struct iio_chan_spec const *chan,
 			     char *label)
@@ -118,24 +528,253 @@ static int sysmon_read_label(struct iio_dev *indio_dev,
 static const struct iio_info sysmon_iio_info = {
 	.read_raw = sysmon_read_raw,
 	.read_label = sysmon_read_label,
+	.read_event_config = sysmon_read_event_config,
+	.write_event_config = sysmon_write_event_config,
+	.read_event_value = sysmon_read_event_value,
+	.write_event_value = sysmon_write_event_value,
 };
 
+static void sysmon_push_event(struct iio_dev *indio_dev, u32 address)
+{
+	const struct iio_chan_spec *chan;
+	enum iio_event_direction dir;
+
+	for (unsigned int i = 0; i < indio_dev->num_channels; i++) {
+		if (indio_dev->channels[i].address != address)
+			continue;
+
+		chan = &indio_dev->channels[i];
+		/* Temp uses hysteresis mode (rising only), voltage uses window */
+		dir = (chan->type == IIO_TEMP) ? IIO_EV_DIR_RISING :
+						 IIO_EV_DIR_EITHER;
+		iio_push_event(indio_dev,
+			       IIO_UNMOD_EVENT_CODE(chan->type,
+						    chan->channel,
+						    IIO_EV_TYPE_THRESH,
+						    dir),
+			       iio_get_time_ns(indio_dev));
+	}
+}
+
+static int sysmon_handle_event(struct iio_dev *indio_dev, u32 event)
+{
+	u32 alarm_flag_offset = SYSMON_ALARM_FLAG + event * SYSMON_REG_STRIDE;
+	u32 alarm_reg_offset = SYSMON_ALARM_REG + event * SYSMON_REG_STRIDE;
+	struct sysmon *sysmon = iio_priv(indio_dev);
+	unsigned long alarm_flag_reg;
+	unsigned int reg_val;
+	u32 address, bit;
+	int ret;
+
+	switch (event) {
+	case SYSMON_BIT_TEMP:
+		sysmon_push_event(indio_dev, SYSMON_ADDR_TEMP_EVENT);
+
+		ret = regmap_write(sysmon->regmap, SYSMON_IDR, BIT(SYSMON_BIT_TEMP));
+		if (ret)
+			return ret;
+
+		sysmon->masked_temp |= BIT(SYSMON_BIT_TEMP);
+		return 0;
+
+	case SYSMON_BIT_OT:
+		sysmon_push_event(indio_dev, SYSMON_ADDR_OT_EVENT);
+
+		ret = regmap_write(sysmon->regmap, SYSMON_IDR, BIT(SYSMON_BIT_OT));
+		if (ret)
+			return ret;
+
+		sysmon->masked_temp |= BIT(SYSMON_BIT_OT);
+		return 0;
+
+	case SYSMON_BIT_ALARM0:
+	case SYSMON_BIT_ALARM1:
+	case SYSMON_BIT_ALARM2:
+	case SYSMON_BIT_ALARM3:
+	case SYSMON_BIT_ALARM4:
+		ret = regmap_read(sysmon->regmap, alarm_flag_offset, &reg_val);
+		if (ret)
+			return ret;
+
+		alarm_flag_reg = reg_val;
+
+		for_each_set_bit(bit, &alarm_flag_reg, SYSMON_ALARM_BITS_PER_REG) {
+			address = bit + SYSMON_ALARM_BITS_PER_REG * event;
+			sysmon_push_event(indio_dev, address);
+			ret = regmap_clear_bits(sysmon->regmap, alarm_reg_offset, BIT(bit));
+			if (ret)
+				return ret;
+		}
+
+		return regmap_write(sysmon->regmap, alarm_flag_offset, alarm_flag_reg);
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static void sysmon_handle_events(struct iio_dev *indio_dev,
+				 unsigned long events)
+{
+	unsigned int bit;
+
+	for_each_set_bit(bit, &events, SYSMON_NO_OF_EVENTS)
+		sysmon_handle_event(indio_dev, bit);
+}
+
+static void sysmon_unmask_temp(struct sysmon *sysmon, unsigned int isr)
+{
+	unsigned int unmask, status;
+
+	status = isr & SYSMON_TEMP_INTR_MASK;
+
+	unmask = ~status & sysmon->masked_temp;
+	sysmon->masked_temp &= status;
+
+	/* Only unmask if not administratively disabled by userspace */
+	unmask &= ~sysmon->temp_mask;
+
+	regmap_write(sysmon->regmap, SYSMON_IER, unmask);
+}
+
+/*
+ * Versal threshold interrupts are level-sensitive. Active threshold
+ * interrupts are masked in the handler and polled via delayed work
+ * until the condition clears, then unmasked.
+ */
+static void sysmon_unmask_worker(struct work_struct *work)
+{
+	struct sysmon *sysmon =
+		container_of(work, struct sysmon, sysmon_unmask_work.work);
+	unsigned int isr;
+
+	/*
+	 * If the ISR read fails, skip processing to avoid acting
+	 * on undefined data.
+	 */
+	scoped_guard(spinlock_irq, &sysmon->irq_lock) {
+		if (regmap_read(sysmon->regmap, SYSMON_ISR, &isr))
+			break;
+		regmap_write(sysmon->regmap, SYSMON_ISR, isr);
+		sysmon_unmask_temp(sysmon, isr);
+	}
+
+	if (sysmon->masked_temp)
+		schedule_delayed_work(&sysmon->sysmon_unmask_work,
+				      msecs_to_jiffies(SYSMON_UNMASK_WORK_DELAY_MS));
+	else
+		regmap_write(sysmon->regmap, SYSMON_STATUS_RESET, 1);
+}
+
+static irqreturn_t sysmon_iio_irq(int irq, void *data)
+{
+	struct iio_dev *indio_dev = data;
+	struct sysmon *sysmon = iio_priv(indio_dev);
+	unsigned int isr, imr;
+
+	guard(spinlock)(&sysmon->irq_lock);
+
+	if (regmap_read(sysmon->regmap, SYSMON_ISR, &isr) ||
+	    regmap_read(sysmon->regmap, SYSMON_IMR, &imr))
+		return IRQ_NONE;
+
+	isr &= ~imr;
+	if (!isr)
+		return IRQ_NONE;
+
+	regmap_write(sysmon->regmap, SYSMON_ISR, isr);
+
+	sysmon_handle_events(indio_dev, isr);
+	schedule_delayed_work(&sysmon->sysmon_unmask_work,
+			      msecs_to_jiffies(SYSMON_UNMASK_WORK_DELAY_MS));
+
+	return IRQ_HANDLED;
+}
+
+static int sysmon_init_interrupt(struct sysmon *sysmon,
+				 struct device *dev,
+				 struct iio_dev *indio_dev,
+				 int irq)
+{
+	unsigned int imr;
+	int ret;
+
+	/* Events not supported without IRQ (e.g. I2C path) */
+	if (!irq)
+		return 0;
+
+	ret = devm_delayed_work_autocancel(dev, &sysmon->sysmon_unmask_work,
+					   sysmon_unmask_worker);
+	if (ret)
+		return ret;
+
+	ret = regmap_read(sysmon->regmap, SYSMON_IMR, &imr);
+	if (ret)
+		return ret;
+	sysmon->temp_mask = imr & SYSMON_TEMP_INTR_MASK;
+
+	return devm_request_irq(dev, irq, sysmon_iio_irq, 0, "sysmon-irq", indio_dev);
+}
+
+/*
+ * Initialize the cached hysteresis for a temperature channel from the
+ * current hardware threshold registers: hysteresis = upper - lower.
+ */
+static int sysmon_init_hysteresis(struct sysmon *sysmon, unsigned int address,
+				  int *hysteresis)
+{
+	unsigned int upper_reg, lower_reg;
+	int upper_off, lower_off;
+	int upper_mc, lower_mc;
+	int ret;
+
+	upper_off = sysmon_temp_thresh_offset(address, IIO_EV_DIR_RISING);
+	if (upper_off < 0)
+		return upper_off;
+	lower_off = sysmon_temp_thresh_offset(address, IIO_EV_DIR_FALLING);
+	if (lower_off < 0)
+		return lower_off;
+
+	ret = regmap_read(sysmon->regmap, upper_off, &upper_reg);
+	if (ret)
+		return ret;
+
+	ret = regmap_read(sysmon->regmap, lower_off, &lower_reg);
+	if (ret)
+		return ret;
+
+	sysmon_q8p7_to_millicelsius(upper_reg, &upper_mc);
+	sysmon_q8p7_to_millicelsius(lower_reg, &lower_mc);
+	*hysteresis = upper_mc - lower_mc;
+
+	return 0;
+}
+
 /**
  * sysmon_parse_fw() - Parse firmware nodes and configure IIO channels.
  * @indio_dev: IIO device instance
  * @dev: Parent device
+ * @irq: IRQ number (positive enables event channels, 0 disables)
  *
  * Reads voltage-channels and temperature-channels container nodes from
  * firmware and builds the IIO channel array. Static temperature channels
- * are prepended, followed by supply and satellite channels from DT.
+ * and event channels are prepended, followed by supply and satellite
+ * channels from DT.
+ *
+ * Event channels and per-channel event specs are only added when the
+ * device has an IRQ. I2C devices have no interrupt line, and the I2C
+ * regmap cannot be called from atomic context, so events are not
+ * supported on that path.
  *
  * Return: 0 on success, negative errno on failure.
  */
-static int sysmon_parse_fw(struct iio_dev *indio_dev, struct device *dev)
+static int sysmon_parse_fw(struct iio_dev *indio_dev, struct device *dev,
+			   int irq)
 {
-	unsigned int num_chan, idx, temp_chan_idx, volt_chan_idx;
-	unsigned int num_supply, num_temp;
+	unsigned int num_chan, num_static, idx, temp_chan_idx, volt_chan_idx;
+	const struct iio_chan_spec *temp_chans;
 	struct iio_chan_spec *sysmon_channels;
+	unsigned int num_supply, num_temp;
 	const char *label;
 	u32 reg;
 	int ret;
@@ -148,14 +787,22 @@ static int sysmon_parse_fw(struct iio_dev *indio_dev, struct device *dev)
 		device_get_named_child_node(dev, "temperature-channels");
 	num_temp = fwnode_get_child_node_count(temp_node);
 
-	num_chan = size_add(num_temp, size_add(ARRAY_SIZE(temp_channels), num_supply));
+	if (irq > 0) {
+		temp_chans = temp_channels_with_events;
+		num_static = ARRAY_SIZE(temp_channels_with_events);
+	} else {
+		temp_chans = temp_channels_no_events;
+		num_static = ARRAY_SIZE(temp_channels_no_events);
+	}
+
+	num_chan = size_add(num_temp, size_add(num_static, num_supply));
 	sysmon_channels = devm_kcalloc(dev, num_chan, sizeof(*sysmon_channels), GFP_KERNEL);
 	if (!sysmon_channels)
 		return -ENOMEM;
 
-	/* Static temperature channels first */
-	memcpy(sysmon_channels, temp_channels, sizeof(temp_channels));
-	idx = ARRAY_SIZE(temp_channels);
+	/* Static temperature channels (with or without events) */
+	memcpy(sysmon_channels, temp_chans, num_static * sizeof(*temp_chans));
+	idx = num_static;
 
 	/* Supply channels from DT */
 	fwnode_for_each_child_node_scoped(supply_node, child) {
@@ -179,6 +826,10 @@ static int sysmon_parse_fw(struct iio_dev *indio_dev, struct device *dev)
 			.indexed = 1,
 			.address = reg,
 			.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+			.event_spec = irq > 0 ?
+				sysmon_supply_events : NULL,
+			.num_event_specs = irq > 0 ?
+				ARRAY_SIZE(sysmon_supply_events) : 0,
 			.datasheet_name = label,
 		};
 	}
@@ -244,6 +895,7 @@ int sysmon_core_probe(struct device *dev, struct regmap *regmap)
 {
 	struct iio_dev *indio_dev;
 	struct sysmon *sysmon;
+	int irq;
 	int ret;
 
 	indio_dev = devm_iio_device_alloc(dev, sizeof(*sysmon));
@@ -256,6 +908,7 @@ int sysmon_core_probe(struct device *dev, struct regmap *regmap)
 	ret = devm_mutex_init(dev, &sysmon->lock);
 	if (ret)
 		return ret;
+	spin_lock_init(&sysmon->irq_lock);
 
 	/* Disable all interrupts and clear pending status */
 	ret = regmap_write(sysmon->regmap, SYSMON_IDR, SYSMON_INTR_ALL_MASK);
@@ -265,13 +918,40 @@ int sysmon_core_probe(struct device *dev, struct regmap *regmap)
 	if (ret)
 		return ret;
 
+	irq = fwnode_irq_get(dev_fwnode(dev), 0);
+	if (irq == -EPROBE_DEFER)
+		return dev_err_probe(dev, irq, "failed to get IRQ\n");
+
 	indio_dev->name = "versal-sysmon";
 	indio_dev->modes = INDIO_DIRECT_MODE;
 
-	ret = sysmon_parse_fw(indio_dev, dev);
+	ret = sysmon_parse_fw(indio_dev, dev, irq);
 	if (ret)
 		return ret;
 
+	if (irq > 0) {
+		/* Set hysteresis mode for both temperature channels */
+		ret = regmap_set_bits(sysmon->regmap, SYSMON_TEMP_EV_CFG,
+				      SYSMON_OT_HYST_MASK |
+				      SYSMON_TEMP_HYST_MASK);
+		if (ret)
+			return ret;
+
+		/* Initialize cached hysteresis from hardware registers */
+		ret = sysmon_init_hysteresis(sysmon, SYSMON_ADDR_TEMP_EVENT,
+					     &sysmon->temp_hysteresis);
+		if (ret)
+			return ret;
+		ret = sysmon_init_hysteresis(sysmon, SYSMON_ADDR_OT_EVENT,
+					     &sysmon->ot_hysteresis);
+		if (ret)
+			return ret;
+
+		ret = sysmon_init_interrupt(sysmon, dev, indio_dev, irq);
+		if (ret)
+			return ret;
+	}
+
 	return devm_iio_device_register(dev, indio_dev);
 }
 EXPORT_SYMBOL_GPL(sysmon_core_probe);
diff --git a/drivers/iio/adc/versal-sysmon.h b/drivers/iio/adc/versal-sysmon.h
index 0c0c89f6e0c..2c6cea64ddf 100644
--- a/drivers/iio/adc/versal-sysmon.h
+++ b/drivers/iio/adc/versal-sysmon.h
@@ -11,6 +11,8 @@
 
 #include <linux/bits.h>
 #include <linux/mutex.h>
+#include <linux/spinlock_types.h>
+#include <linux/workqueue.h>
 
 struct device;
 struct regmap;
@@ -18,12 +20,24 @@ struct regmap;
 /* Register offsets (sorted by address) */
 #define SYSMON_NPI_LOCK			0x000C
 #define SYSMON_ISR			0x0044
+#define SYSMON_IMR			0x0048
+#define SYSMON_IER			0x004C
 #define SYSMON_IDR			0x0050
 #define SYSMON_TEMP_MAX			0x1030
 #define SYSMON_TEMP_MIN			0x1034
 #define SYSMON_SUPPLY_BASE		0x1040
+#define SYSMON_ALARM_FLAG		0x1018
+#define SYSMON_ALARM_REG		0x1940
+#define SYSMON_TEMP_TH_LOW		0x1970
+#define SYSMON_TEMP_TH_UP		0x1974
+#define SYSMON_OT_TH_LOW		0x1978
+#define SYSMON_OT_TH_UP		0x197C
+#define SYSMON_SUPPLY_TH_LOW		0x1980
+#define SYSMON_SUPPLY_TH_UP		0x1C80
+#define SYSMON_TEMP_EV_CFG		0x1F84
 #define SYSMON_TEMP_MIN_MIN		0x1F8C
 #define SYSMON_TEMP_MAX_MAX		0x1F90
+#define SYSMON_STATUS_RESET		0x1F94
 #define SYSMON_TEMP_SAT_BASE		0x1FAC
 #define SYSMON_MAX_REG			0x24C0
 
@@ -35,8 +49,12 @@ struct regmap;
 
 #define SYSMON_SUPPLY_IDX_MAX		159
 #define SYSMON_TEMP_SAT_MAX		64
+#define SYSMON_NO_OF_EVENTS		32
 #define SYSMON_INTR_ALL_MASK		GENMASK(31, 0)
 
+/* ISR/IMR temperature and OT alarm mask (bits 9:8) */
+#define SYSMON_TEMP_INTR_MASK		GENMASK(9, 8)
+
 /* Supply voltage conversion register fields */
 #define SYSMON_MANTISSA_MASK		GENMASK(15, 0)
 #define SYSMON_FMT_MASK			BIT(16)
@@ -46,11 +64,26 @@ struct regmap;
 #define SYSMON_FRACTIONAL_SHIFT		7U
 #define SYSMON_SUPPLY_MANTISSA_BITS	16
 
+/* Event address IDs for temp event channels */
+#define SYSMON_ADDR_TEMP_EVENT		160
+#define SYSMON_ADDR_OT_EVENT		161
+
+/* Bits per alarm register */
+#define SYSMON_ALARM_BITS_PER_REG	32
+
+#define SYSMON_UNMASK_WORK_DELAY_MS	500
+
 /**
  * struct sysmon - Driver data for Versal SysMon
  * @regmap: register map for hardware access
  * @lock: protects read-modify-write sequences on threshold registers
  *        and cached state that spans multiple regmap calls
+ * @irq_lock: protects interrupt mask register updates (MMIO path only)
+ * @masked_temp: currently masked temperature alarm bits
+ * @temp_mask: temperature interrupt configuration mask
+ * @temp_hysteresis: cached DEVICE_TEMP hysteresis in millicelsius
+ * @ot_hysteresis: cached OT hysteresis in millicelsius
+ * @sysmon_unmask_work: re-enables events after alarm condition clears
  */
 struct sysmon {
 	struct regmap *regmap;
@@ -60,6 +93,17 @@ struct sysmon {
 	 * that spans multiple regmap calls.
 	 */
 	struct mutex lock;
+	/*
+	 * Protects interrupt mask register updates.  Only used on the
+	 * MMIO path (fast_io regmap); I2C has no IRQ and never reaches
+	 * the event code that takes this lock.
+	 */
+	spinlock_t irq_lock;
+	unsigned int masked_temp;
+	unsigned int temp_mask;
+	int temp_hysteresis;
+	int ot_hysteresis;
+	struct delayed_work sysmon_unmask_work;
 };
 
 int sysmon_core_probe(struct device *dev, struct regmap *regmap);
-- 
2.48.1


^ permalink raw reply related

* [PATCH v6 5/5] iio: adc: versal-sysmon: add oversampling support
From: Salih Erim @ 2026-06-11 22:27 UTC (permalink / raw)
  To: jic23, andy
  Cc: dlechner, nuno.sa, robh, krzk+dt, conor+dt, conall.ogriofa,
	michal.simek, linux, erimsalih, linux-iio, devicetree,
	linux-kernel, Salih Erim
In-Reply-To: <20260611222738.2035062-1-salih.erim@amd.com>

Add support for reading and writing the oversampling ratio through
the IIO oversampling_ratio attribute. The hardware supports averaging
2, 4, 8, or 16 samples, plus a ratio of 1 (no averaging).

Temperature and supply channels share oversampling configuration at
the type level (all temperature channels share one ratio, all supply
channels share another), exposed through info_mask_shared_by_type.

The hardware encoding uses sample_count / 2 in a 4-bit field within
the CONFIG register. Per-channel averaging enable registers must also
be updated to activate or deactivate averaging.

Signed-off-by: Salih Erim <salih.erim@amd.com>
---
Changes in v6:
  - Fix FIELD_PREP indentation in sysmon_osr_write (Andy)
  - unsigned int for loop index in sysmon_write_raw (Andy)

Changes in v5:
  - Remove unneeded parentheses in i * SYSMON_REG_STRIDE (Andy)
  - Use struct regmap *map local variable in
    sysmon_set_avg_enable (Andy)
  - switch instead of redundant if/if on channel_type (Andy)
  - Add CONFIG register readback fence after oversampling update
    to prevent NoC bus hang from posted writes (found during
    hardware stress testing)

Changes in v4:
  - Return directly from sysmon_set_avg_enable calls, remove
    else after early returns, drop unreachable return 0 (Jonathan)
  - Rename mask defines to SYSMON_CONFIG_SUPPLY_OSR and
    SYSMON_CONFIG_TEMP_SAT_OSR (Jonathan)
  - Drop "bits X:Y" from GENMASK comments (Jonathan)
  - Blank lines after if (ret) return ret blocks (Jonathan)
  - Move oversampling read inside guard(mutex) scope

Changes in v3:
  - No changes

Changes in v2:
  - EN_AVG per-channel bitmask registers written with all-ones
    instead of boolean 1 when oversampling is enabled
  - EN_AVG write errors propagated to userspace
  - Oversampling limited to satellite temp and supply channels;
    static temp channels do not participate
  - Oversampling exposes actual sample counts (1,2,4,8,16) to
    userspace with internal HW register translation
  - write_raw_get_fmt returns IIO_VAL_INT for oversampling ratio
  - HW encoding documented (sample_count/2, not log2)
  - oversampling_avail is const int[] (type match fix)
 drivers/iio/adc/versal-sysmon-core.c | 147 ++++++++++++++++++++++++++-
 drivers/iio/adc/versal-sysmon.h      |  17 ++++
 2 files changed, 163 insertions(+), 1 deletion(-)

diff --git a/drivers/iio/adc/versal-sysmon-core.c b/drivers/iio/adc/versal-sysmon-core.c
index 20fd3a87d44..fa8f0dc868a 100644
--- a/drivers/iio/adc/versal-sysmon-core.c
+++ b/drivers/iio/adc/versal-sysmon-core.c
@@ -26,6 +26,12 @@
 
 #include "versal-sysmon.h"
 
+/*
+ * Oversampling ratio values exposed to userspace via IIO.
+ * Actual number of samples averaged: 1=none, 2=2x, 4=4x, 8=8x, 16=16x.
+ */
+static const int sysmon_oversampling_avail[] = { 1, 2, 4, 8, 16 };
+
 /* OT and TEMP hysteresis mode bits in SYSMON_TEMP_EV_CFG */
 #define SYSMON_OT_HYST_MASK		BIT(0)
 #define SYSMON_TEMP_HYST_MASK		BIT(1)
@@ -196,6 +202,12 @@ static int sysmon_read_raw(struct iio_dev *indio_dev,
 
 	guard(mutex)(&sysmon->lock);
 
+	if (mask == IIO_CHAN_INFO_OVERSAMPLING_RATIO) {
+		*val = (chan->type == IIO_TEMP) ? sysmon->temp_oversampling :
+						 sysmon->supply_oversampling;
+		return IIO_VAL_INT;
+	}
+
 	switch (chan->type) {
 	case IIO_TEMP:
 		if (mask == IIO_CHAN_INFO_SCALE) {
@@ -515,6 +527,126 @@ static int sysmon_write_event_value(struct iio_dev *indio_dev,
 	}
 }
 
+static int sysmon_set_avg_enable(struct sysmon *sysmon,
+				 u32 base, u32 count, u32 val)
+{
+	struct regmap *map = sysmon->regmap;
+	int ret;
+
+	for (unsigned int i = 0; i < count; i++) {
+		ret = regmap_write(map, base + i * SYSMON_REG_STRIDE, val);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int sysmon_osr_write(struct sysmon *sysmon, int channel_type, int val)
+{
+	/*
+	 * HW register encoding is sample_count / 2:
+	 * 0=none, 1=2x, 2=4x, 4=8x, 8=16x (not log2-based).
+	 */
+	int hw_val = val >> 1;
+	unsigned int readback;
+	int ret;
+
+	switch (channel_type) {
+	case IIO_TEMP:
+		ret = regmap_update_bits(sysmon->regmap, SYSMON_CONFIG,
+					SYSMON_CONFIG_TEMP_SAT_OSR,
+					FIELD_PREP(SYSMON_CONFIG_TEMP_SAT_OSR, hw_val));
+		if (ret)
+			return ret;
+
+		/*
+		 * Readback fence: the SysMon CONFIG register resides in the
+		 * PMC domain behind the NoC. A posted write may not reach the
+		 * hardware before the next MMIO access. Reading the register
+		 * back forces the interconnect to complete the write, preventing
+		 * a bus hang on the subsequent access.
+		 */
+		regmap_read(sysmon->regmap, SYSMON_CONFIG, &readback);
+
+		return sysmon_set_avg_enable(sysmon, SYSMON_TEMP_EN_AVG_BASE,
+					     SYSMON_TEMP_EN_AVG_COUNT,
+					     hw_val ? ~0U : 0);
+	case IIO_VOLTAGE:
+		ret = regmap_update_bits(sysmon->regmap, SYSMON_CONFIG,
+					SYSMON_CONFIG_SUPPLY_OSR,
+					FIELD_PREP(SYSMON_CONFIG_SUPPLY_OSR, hw_val));
+		if (ret)
+			return ret;
+
+		/* Readback fence -- see above */
+		regmap_read(sysmon->regmap, SYSMON_CONFIG, &readback);
+
+		return sysmon_set_avg_enable(sysmon, SYSMON_SUPPLY_EN_AVG_BASE,
+					     SYSMON_SUPPLY_EN_AVG_COUNT,
+					     hw_val ? ~0U : 0);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int sysmon_write_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan,
+			    int val, int val2, long mask)
+{
+	struct sysmon *sysmon = iio_priv(indio_dev);
+	unsigned int i;
+	int ret;
+
+	if (mask != IIO_CHAN_INFO_OVERSAMPLING_RATIO)
+		return -EINVAL;
+
+	for (i = 0; i < ARRAY_SIZE(sysmon_oversampling_avail); i++) {
+		if (val == sysmon_oversampling_avail[i])
+			break;
+	}
+	if (i == ARRAY_SIZE(sysmon_oversampling_avail))
+		return -EINVAL;
+
+	guard(mutex)(&sysmon->lock);
+
+	ret = sysmon_osr_write(sysmon, chan->type, val);
+	if (ret)
+		return ret;
+
+	if (chan->type == IIO_TEMP)
+		sysmon->temp_oversampling = val;
+	else
+		sysmon->supply_oversampling = val;
+
+	return 0;
+}
+
+static int sysmon_write_raw_get_fmt(struct iio_dev *indio_dev,
+				    struct iio_chan_spec const *chan,
+				    long mask)
+{
+	if (mask == IIO_CHAN_INFO_OVERSAMPLING_RATIO)
+		return IIO_VAL_INT;
+
+	return -EINVAL;
+}
+
+static int sysmon_read_avail(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     const int **vals, int *type,
+			     int *length, long mask)
+{
+	if (mask != IIO_CHAN_INFO_OVERSAMPLING_RATIO)
+		return -EINVAL;
+
+	*vals = sysmon_oversampling_avail;
+	*type = IIO_VAL_INT;
+	*length = ARRAY_SIZE(sysmon_oversampling_avail);
+
+	return IIO_AVAIL_LIST;
+}
+
 static int sysmon_read_label(struct iio_dev *indio_dev,
 			     struct iio_chan_spec const *chan,
 			     char *label)
@@ -527,6 +659,9 @@ static int sysmon_read_label(struct iio_dev *indio_dev,
 
 static const struct iio_info sysmon_iio_info = {
 	.read_raw = sysmon_read_raw,
+	.write_raw = sysmon_write_raw,
+	.write_raw_get_fmt = sysmon_write_raw_get_fmt,
+	.read_avail = sysmon_read_avail,
 	.read_label = sysmon_read_label,
 	.read_event_config = sysmon_read_event_config,
 	.write_event_config = sysmon_write_event_config,
@@ -826,6 +961,10 @@ static int sysmon_parse_fw(struct iio_dev *indio_dev, struct device *dev,
 			.indexed = 1,
 			.address = reg,
 			.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+			.info_mask_shared_by_type =
+				BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+			.info_mask_shared_by_type_available =
+				BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
 			.event_spec = irq > 0 ?
 				sysmon_supply_events : NULL,
 			.num_event_specs = irq > 0 ?
@@ -857,7 +996,11 @@ static int sysmon_parse_fw(struct iio_dev *indio_dev, struct device *dev,
 			.address = SYSMON_TEMP_SAT_BASE +
 				   (reg - 1) * SYSMON_REG_STRIDE,
 			.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
-			.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
+			.info_mask_shared_by_type =
+				BIT(IIO_CHAN_INFO_SCALE) |
+				BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
+			.info_mask_shared_by_type_available =
+				BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),
 			.datasheet_name = label,
 		};
 	}
@@ -904,6 +1047,8 @@ int sysmon_core_probe(struct device *dev, struct regmap *regmap)
 
 	sysmon = iio_priv(indio_dev);
 	sysmon->regmap = regmap;
+	sysmon->temp_oversampling = 1;
+	sysmon->supply_oversampling = 1;
 
 	ret = devm_mutex_init(dev, &sysmon->lock);
 	if (ret)
diff --git a/drivers/iio/adc/versal-sysmon.h b/drivers/iio/adc/versal-sysmon.h
index 2c6cea64ddf..c6b9e496c1c 100644
--- a/drivers/iio/adc/versal-sysmon.h
+++ b/drivers/iio/adc/versal-sysmon.h
@@ -23,11 +23,13 @@ struct regmap;
 #define SYSMON_IMR			0x0048
 #define SYSMON_IER			0x004C
 #define SYSMON_IDR			0x0050
+#define SYSMON_CONFIG			0x0100
 #define SYSMON_TEMP_MAX			0x1030
 #define SYSMON_TEMP_MIN			0x1034
 #define SYSMON_SUPPLY_BASE		0x1040
 #define SYSMON_ALARM_FLAG		0x1018
 #define SYSMON_ALARM_REG		0x1940
+#define SYSMON_SUPPLY_EN_AVG_BASE	0x1958
 #define SYSMON_TEMP_TH_LOW		0x1970
 #define SYSMON_TEMP_TH_UP		0x1974
 #define SYSMON_OT_TH_LOW		0x1978
@@ -39,6 +41,7 @@ struct regmap;
 #define SYSMON_TEMP_MAX_MAX		0x1F90
 #define SYSMON_STATUS_RESET		0x1F94
 #define SYSMON_TEMP_SAT_BASE		0x1FAC
+#define SYSMON_TEMP_EN_AVG_BASE		0x24B4
 #define SYSMON_MAX_REG			0x24C0
 
 /* NPI unlock value written to SYSMON_NPI_LOCK */
@@ -55,6 +58,16 @@ struct regmap;
 /* ISR/IMR temperature and OT alarm mask (bits 9:8) */
 #define SYSMON_TEMP_INTR_MASK		GENMASK(9, 8)
 
+/* SYSMON_CONFIG: supply oversampling ratio */
+#define SYSMON_CONFIG_SUPPLY_OSR	GENMASK(17, 14)
+
+/* SYSMON_CONFIG: temperature satellite oversampling ratio */
+#define SYSMON_CONFIG_TEMP_SAT_OSR	GENMASK(27, 24)
+
+/* Per-channel averaging enable register counts */
+#define SYSMON_SUPPLY_EN_AVG_COUNT	5
+#define SYSMON_TEMP_EN_AVG_COUNT	2
+
 /* Supply voltage conversion register fields */
 #define SYSMON_MANTISSA_MASK		GENMASK(15, 0)
 #define SYSMON_FMT_MASK			BIT(16)
@@ -84,6 +97,8 @@ struct regmap;
  * @temp_hysteresis: cached DEVICE_TEMP hysteresis in millicelsius
  * @ot_hysteresis: cached OT hysteresis in millicelsius
  * @sysmon_unmask_work: re-enables events after alarm condition clears
+ * @temp_oversampling: current temp oversampling ratio
+ * @supply_oversampling: current supply oversampling ratio
  */
 struct sysmon {
 	struct regmap *regmap;
@@ -104,6 +119,8 @@ struct sysmon {
 	int temp_hysteresis;
 	int ot_hysteresis;
 	struct delayed_work sysmon_unmask_work;
+	unsigned int temp_oversampling;
+	unsigned int supply_oversampling;
 };
 
 int sysmon_core_probe(struct device *dev, struct regmap *regmap);
-- 
2.48.1


^ permalink raw reply related

* [PATCH v6 3/5] iio: adc: versal-sysmon: add I2C driver
From: Salih Erim @ 2026-06-11 22:27 UTC (permalink / raw)
  To: jic23, andy
  Cc: dlechner, nuno.sa, robh, krzk+dt, conor+dt, conall.ogriofa,
	michal.simek, linux, erimsalih, linux-iio, devicetree,
	linux-kernel, Salih Erim
In-Reply-To: <20260611222738.2035062-1-salih.erim@amd.com>

Add an I2C transport driver for the Versal SysMon block. The SysMon
provides an I2C slave interface that allows an external master to
read voltage and temperature measurements through the same register
map used by the MMIO path.

The I2C command frame is an 8-byte structure containing a 4-byte data
payload, a 2-byte register offset, and a 1-byte instruction field.
Read operations send the frame with a read instruction, then receive
a 4-byte response containing the register value.

Events are not supported on the I2C path because there is no
interrupt line and the I2C regmap backend cannot be called from
atomic context.

Co-developed-by: Conall O'Griofa <conall.ogriofa@amd.com>
Signed-off-by: Conall O'Griofa <conall.ogriofa@amd.com>
Signed-off-by: Salih Erim <salih.erim@amd.com>
---
Changes in v6:
  - Add types.h include (IWYU) (Andy)
  - Add local struct device *dev, join devm_regmap_init on
    one line (Andy)

Changes in v5:
  - Add err.h, mod_devicetable.h includes (IWYU) (Andy)

Changes in v4:
  - Replace enum with defines for I2C frame offsets (Jonathan)
  - Use get_unaligned_le32() for read data reassembly (Jonathan)
  - Use put_unaligned_le32/le16() for write data and register offset
    packing (Jonathan)
  - Named initializer in i2c_device_id (Jonathan)
  - Drop bitfield.h, add unaligned.h (FIELD_GET/FIELD_PREP replaced
    by unaligned accessors)

Changes in v3:
  - IWYU: fix includes (Andy)
  - Enum: assign all values explicitly for HW-mapped fields (Andy)
  - Remove sysmon_i2c wrapper struct, pass i2c_client directly
    (Andy)
  - Use sizeof() for I2C buffer lengths instead of defines (Andy)
  - Use = { } instead of = { 0 } for initializers (Andy)
  - Use single compatible xlnx,versal-sysmon (Krzysztof)
  - Adapt to core_probe interface change: irq moved to core,
    remove irq parameter from bus driver (Jonathan)

Changes in v2:
  - New patch (I2C was deferred to Series B in v1)
  - Uses regmap API with custom I2C read/write callbacks
  - Shares core module with MMIO driver via sysmon_core_probe()
  - No event support (I2C has no interrupt line)
  - Separate VERSAL_SYSMON_I2C Kconfig symbol
  - Reverse Christmas Tree variable ordering in read/write functions
 drivers/iio/adc/Kconfig             |  13 +++
 drivers/iio/adc/Makefile            |   1 +
 drivers/iio/adc/versal-sysmon-i2c.c | 128 ++++++++++++++++++++++++++++
 3 files changed, 142 insertions(+)
 create mode 100644 drivers/iio/adc/versal-sysmon-i2c.c

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index c7f19057484..8f9fc9de74a 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1963,6 +1963,19 @@ config VERSAL_SYSMON
 	  To compile this driver as a module, choose M here: the module
 	  will be called versal-sysmon.
 
+config VERSAL_SYSMON_I2C
+	tristate "AMD Versal SysMon I2C driver"
+	depends on I2C
+	select VERSAL_SYSMON_CORE
+	help
+	  Say yes here to have support for the AMD/Xilinx Versal System
+	  Monitor (SysMon) via I2C interface. This driver enables voltage
+	  and temperature monitoring when the Versal chip has SysMon
+	  configured with I2C access.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called versal-sysmon-i2c.
+
 config VF610_ADC
 	tristate "Freescale vf610 ADC driver"
 	depends on HAS_IOMEM
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index d7696b1b157..5abb611fe46 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -169,6 +169,7 @@ obj-$(CONFIG_TWL4030_MADC) += twl4030-madc.o
 obj-$(CONFIG_TWL6030_GPADC) += twl6030-gpadc.o
 obj-$(CONFIG_VERSAL_SYSMON_CORE) += versal-sysmon-core.o
 obj-$(CONFIG_VERSAL_SYSMON) += versal-sysmon.o
+obj-$(CONFIG_VERSAL_SYSMON_I2C) += versal-sysmon-i2c.o
 obj-$(CONFIG_VF610_ADC) += vf610_adc.o
 obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o
 obj-$(CONFIG_XILINX_AMS) += xilinx-ams.o
diff --git a/drivers/iio/adc/versal-sysmon-i2c.c b/drivers/iio/adc/versal-sysmon-i2c.c
new file mode 100644
index 00000000000..1bc969597b4
--- /dev/null
+++ b/drivers/iio/adc/versal-sysmon-i2c.c
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AMD Versal SysMon I2C driver
+ *
+ * Copyright (C) 2023 - 2026, Advanced Micro Devices, Inc.
+ */
+
+#include <linux/bits.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+
+#include "versal-sysmon.h"
+
+#define SYSMON_I2C_INSTR_READ	BIT(2)
+#define SYSMON_I2C_INSTR_WRITE	BIT(3)
+
+/*
+ * I2C command frame layout (8 bytes):
+ *   [0..3] data payload (little-endian u32)
+ *   [4..5] register offset >> 2 (little-endian u16)
+ *   [6]    instruction (read/write)
+ *   [7]    reserved
+ */
+#define SYSMON_I2C_DATA_OFS	0
+#define SYSMON_I2C_REG_OFS	4
+#define SYSMON_I2C_INSTR_OFS	6
+
+static int sysmon_i2c_reg_read(void *context, unsigned int reg,
+			       unsigned int *val)
+{
+	struct i2c_client *client = context;
+	u8 write_buf[8] = { };
+	u8 read_buf[4];
+	int ret;
+
+	put_unaligned_le16(reg >> 2, &write_buf[SYSMON_I2C_REG_OFS]);
+	write_buf[SYSMON_I2C_INSTR_OFS] = SYSMON_I2C_INSTR_READ;
+
+	ret = i2c_master_send(client, write_buf, sizeof(write_buf));
+	if (ret < 0)
+		return ret;
+	if (ret != sizeof(write_buf))
+		return -EIO;
+
+	ret = i2c_master_recv(client, read_buf, sizeof(read_buf));
+	if (ret < 0)
+		return ret;
+	if (ret != sizeof(read_buf))
+		return -EIO;
+
+	*val = get_unaligned_le32(read_buf);
+
+	return 0;
+}
+
+static int sysmon_i2c_reg_write(void *context, unsigned int reg,
+				unsigned int val)
+{
+	struct i2c_client *client = context;
+	u8 write_buf[8] = { };
+	int ret;
+
+	put_unaligned_le32(val, &write_buf[SYSMON_I2C_DATA_OFS]);
+	put_unaligned_le16(reg >> 2, &write_buf[SYSMON_I2C_REG_OFS]);
+	write_buf[SYSMON_I2C_INSTR_OFS] = SYSMON_I2C_INSTR_WRITE;
+
+	ret = i2c_master_send(client, write_buf, sizeof(write_buf));
+	if (ret < 0)
+		return ret;
+	if (ret != sizeof(write_buf))
+		return -EIO;
+
+	return 0;
+}
+
+static const struct regmap_config sysmon_i2c_regmap_config = {
+	.reg_bits = 32,
+	.val_bits = 32,
+	.reg_stride = SYSMON_REG_STRIDE,
+	.max_register = SYSMON_MAX_REG,
+	.reg_read = sysmon_i2c_reg_read,
+	.reg_write = sysmon_i2c_reg_write,
+};
+
+static int sysmon_i2c_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct regmap *regmap;
+
+	regmap = devm_regmap_init(dev, NULL, client, &sysmon_i2c_regmap_config);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	/* I2C has no IRQ connection; events are not supported */
+	return sysmon_core_probe(dev, regmap);
+}
+
+static const struct of_device_id sysmon_i2c_of_match_table[] = {
+	{ .compatible = "xlnx,versal-sysmon" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, sysmon_i2c_of_match_table);
+
+static const struct i2c_device_id sysmon_i2c_id_table[] = {
+	{ .name = "versal-sysmon" },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, sysmon_i2c_id_table);
+
+static struct i2c_driver sysmon_i2c_driver = {
+	.probe = sysmon_i2c_probe,
+	.driver = {
+		.name = "versal-sysmon-i2c",
+		.of_match_table = sysmon_i2c_of_match_table,
+	},
+	.id_table = sysmon_i2c_id_table,
+};
+module_i2c_driver(sysmon_i2c_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("AMD Versal SysMon I2C Driver");
+MODULE_AUTHOR("Conall O'Griofa <conall.ogriofa@amd.com>");
+MODULE_AUTHOR("Salih Erim <salih.erim@amd.com>");
-- 
2.48.1


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox