mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net-next v3 0/2] net: pse-pd: Add LTC4266 PSE controller driver
@ 2026-09-27 21:29 Kyle Swenson
  2026-09-27 21:29 ` [PATCH net-next v3 1/2] dt-bindings: net: pse-pd: Add bindings for LTC4266 PSE Controller Kyle Swenson
  2026-09-27 21:29 ` [PATCH net-next v3 2/2] net: pse-pd: Add LTC4266 PSE controller driver Kyle Swenson
  0 siblings, 2 replies; 5+ messages in thread
From: Kyle Swenson @ 2026-09-27 21:29 UTC (permalink / raw)
  To: o.rempel, kory.maincent, andrew+netdev, davem, edumazet, kuba,
	pabeni, robh, krzk+dt, conor+dt
  Cc: Kyle Swenson, netdev, devicetree, linux-kernel,
	Roland Kovács, David Nyström

Add support for an older PSE controller that supports powering Type 1
and Type 2 PDs.

The chip has four individually controllable power channels, each with
it own detection, classification and current-limiting abilities.  The
driver declares the static power budgeting strategy.

---
RFC v2 -> v3:
    - The admin power limit is now configurable before the port is
      powered and is independent from the class limit.  However, when a
      PD is powered, the current limit is set by the minimum between the PD's
      class and the admin power limit, to maintain compliance with the IEEE
      spec.
    - The LTC4266 doesn't support reading a port's voltage (or current)
      until that port is completely and successfully powered.  Since we
      don't have a good way to measure the port voltage, we'll first try to
      use the vpwr-supply voltage if available during probe, and fall back to
      50V (the Type 2 PSE minimum) if the regulator isn't available (ENODEV or
      EPROBE_DEFER) or doesn't have a readable voltage for the port.  This is
      the voltage the LTC4266 driver will return back to pse core when a
      port's power good bit is not set.
    - Report the hardware admin state from the "Power Enabled" bit instead
      of the "Power Good" bit.  The latter is set when the port is fully
      powered (after inrush) so a port in the process of powering up looked
      disabled to the core and had power domain budget allocated to it twice.
    - Added a Maintainers entry for the driver.
    - Avoid precision loss in pi_get_actual_pw.
    - Demote a dev_err in a common path on device disconnection to
      dev_dbg.
    - Add an err: label in map_event that'll clear all the event
      registers via the INTCLR push-button to prevent a possible
      interrupt storm.
    - The supply and over-temperature faults were too hard to test, but
      they will result in a disconnection event for one or more
      channels.  Replace those faults with a combination of a "power good"
      change and a port status check to send a disconnect event to the pse
      core.
    - Bail out of probe early if we don't have the required IRQ.
    - Replace the driver's .remove with a devm action registered before
      the controller and the IRQ to ensure the IRQ is freed before this
      driver is removed.
    - Drop reset-gpios from the binding, since the driver doesn't
      support it at the moment (because my hardware doesn't have it
      correctly wired)
    - Expand the description for sense-resistor-micro-ohms to indicate
      the property is required by the chip for accurate current limiting.

Link to RFC v2: https://lore.kernel.org/all/20260820142429.2285172-1-kyle.swenson@est.tech/

Kyle Swenson (2):
  dt-bindings: net: pse-pd: Add bindings for LTC4266 PSE Controller
  net: pse-pd: Add LTC4266 PSE controller driver

 .../bindings/net/pse-pd/lltc,ltc4266.yaml     |  180 +++
 MAINTAINERS                                   |    7 +
 drivers/net/pse-pd/Kconfig                    |   11 +
 drivers/net/pse-pd/Makefile                   |    1 +
 drivers/net/pse-pd/ltc4266.c                  | 1386 +++++++++++++++++
 5 files changed, 1585 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/net/pse-pd/lltc,ltc4266.yaml
 create mode 100644 drivers/net/pse-pd/ltc4266.c

-- 
2.55.0

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH net-next v3 1/2] dt-bindings: net: pse-pd: Add bindings for LTC4266 PSE Controller
  2026-09-27 21:29 [PATCH net-next v3 0/2] net: pse-pd: Add LTC4266 PSE controller driver Kyle Swenson
@ 2026-09-27 21:29 ` Kyle Swenson
  2026-09-30 21:31   ` netdev-bot+sashiko
  2026-09-27 21:29 ` [PATCH net-next v3 2/2] net: pse-pd: Add LTC4266 PSE controller driver Kyle Swenson
  1 sibling, 1 reply; 5+ messages in thread
From: Kyle Swenson @ 2026-09-27 21:29 UTC (permalink / raw)
  To: o.rempel, kory.maincent, andrew+netdev, davem, edumazet, kuba,
	pabeni, robh, krzk+dt, conor+dt
  Cc: Kyle Swenson, netdev, devicetree, linux-kernel,
	Roland Kovács, David Nyström

Add the LTC4266 Power Sourcing Equipment controller device tree bindings
documentation.

Signed-off-by: Kyle Swenson <kyle.swenson@est.tech>
---
 .../bindings/net/pse-pd/lltc,ltc4266.yaml     | 180 ++++++++++++++++++
 1 file changed, 180 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/net/pse-pd/lltc,ltc4266.yaml

diff --git a/Documentation/devicetree/bindings/net/pse-pd/lltc,ltc4266.yaml b/Documentation/devicetree/bindings/net/pse-pd/lltc,ltc4266.yaml
new file mode 100644
index 000000000000..e11c9d601eee
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/pse-pd/lltc,ltc4266.yaml
@@ -0,0 +1,180 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/pse-pd/lltc,ltc4266.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Linear Technology LTC4266 Power Sourcing Equipment controller
+
+maintainers:
+  - Kyle Swenson <kyle.swenson@est.tech>
+
+allOf:
+  - $ref: pse-controller.yaml#
+
+properties:
+  compatible:
+    enum:
+      - lltc,ltc4266
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  channels:
+    type: object
+    additionalProperties: false
+    description:
+      Defines the 4 physical delivery channels on the controller that can be
+      referenced by PSE PIs through their "pairsets" property. The actual port
+      matrix mapping is created when PSE PIs reference these channels in their
+      pairsets.
+
+    properties:
+      '#address-cells':
+        const: 1
+
+      '#size-cells':
+        const: 0
+
+    patternProperties:
+      '^channel@[0-3]$':
+        type: object
+        additionalProperties: false
+
+        properties:
+          reg:
+            maxItems: 1
+
+          sense-resistor-micro-ohms:
+            description: Sense resistor connected to the channel's MOSFET, used
+              for current measurement and for overcurrent detection. The I_CUT
+              and I_LIM register encodings depend on which value is fitted, so
+              a wrong value programs the wrong thresholds.
+            enum: [250000, 500000]
+
+        required:
+          - reg
+          - sense-resistor-micro-ohms
+
+    required:
+      - '#address-cells'
+      - '#size-cells'
+
+  pse-pis:
+    type: object
+    additionalProperties: false
+
+    properties:
+      '#address-cells':
+        const: 1
+
+      '#size-cells':
+        const: 0
+
+    patternProperties:
+      '^pse-pi@[0-3]$':
+        type: object
+        additionalProperties: true
+        properties:
+          pairsets:
+            description: The LTC4266 delivers power to a PI over a single
+              pairset, driven by one of the controller's four channels. There
+              is no 4-pair mode spreading a PI over two channels, so exactly
+              one channel phandle is expected.
+            maxItems: 1
+          pairset-names:
+            maxItems: 1
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - channels
+  - pse-pis
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    i2c {
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      ethernet-pse@2f {
+        compatible = "lltc,ltc4266";
+        reg = <0x2f>;
+        interrupts = <8 IRQ_TYPE_LEVEL_LOW>;
+        interrupt-parent = <&gpio>;
+
+        channels {
+          #address-cells = <1>;
+          #size-cells = <0>;
+
+          phys0: channel@0 {
+            reg = <0>;
+            sense-resistor-micro-ohms = <500000>;
+          };
+
+          phys1: channel@1 {
+            reg = <1>;
+            sense-resistor-micro-ohms = <500000>;
+          };
+
+          phys2: channel@2 {
+            reg = <2>;
+            sense-resistor-micro-ohms = <500000>;
+          };
+
+          phys3: channel@3 {
+            reg = <3>;
+            sense-resistor-micro-ohms = <500000>;
+          };
+        };
+
+        pse-pis {
+          #address-cells = <1>;
+          #size-cells = <0>;
+
+          pse_pi0: pse-pi@0 {
+            reg = <0>;
+            #pse-cells = <0>;
+            pairset-names = "alternative-a";
+            pairsets = <&phys0>;
+            polarity-supported = "MDI";
+            vpwr-supply = <&vreg_pse>;
+          };
+
+          pse_pi1: pse-pi@1 {
+            reg = <1>;
+            #pse-cells = <0>;
+            pairset-names = "alternative-a";
+            pairsets = <&phys1>;
+            polarity-supported = "MDI";
+            vpwr-supply = <&vreg_pse>;
+          };
+
+          pse_pi2: pse-pi@2 {
+            reg = <2>;
+            #pse-cells = <0>;
+            pairset-names = "alternative-a";
+            pairsets = <&phys2>;
+            polarity-supported = "MDI";
+            vpwr-supply = <&vreg_pse>;
+          };
+
+          pse_pi3: pse-pi@3 {
+            reg = <3>;
+            #pse-cells = <0>;
+            pairset-names = "alternative-a";
+            pairsets = <&phys3>;
+            polarity-supported = "MDI";
+            vpwr-supply = <&vreg_pse>;
+          };
+        };
+      };
+    };
-- 
2.55.0

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH net-next v3 2/2] net: pse-pd: Add LTC4266 PSE controller driver
  2026-09-27 21:29 [PATCH net-next v3 0/2] net: pse-pd: Add LTC4266 PSE controller driver Kyle Swenson
  2026-09-27 21:29 ` [PATCH net-next v3 1/2] dt-bindings: net: pse-pd: Add bindings for LTC4266 PSE Controller Kyle Swenson
@ 2026-09-27 21:29 ` Kyle Swenson
  2026-09-30 21:31   ` netdev-bot+sashiko
  1 sibling, 1 reply; 5+ messages in thread
From: Kyle Swenson @ 2026-09-27 21:29 UTC (permalink / raw)
  To: o.rempel, kory.maincent, andrew+netdev, davem, edumazet, kuba,
	pabeni, robh, krzk+dt, conor+dt
  Cc: Kyle Swenson, netdev, devicetree, linux-kernel,
	Roland Kovács, David Nyström

Add a new driver for the Linear Technology LTC4266 I2C Power Sourcing
Equipment controller and integrate with the PSE controller core.  The
LTC4266 supports Type 1 and Type 2 Powered Devices.

Signed-off-by: Kyle Swenson <kyle.swenson@est.tech>
---
 MAINTAINERS                  |    7 +
 drivers/net/pse-pd/Kconfig   |   11 +
 drivers/net/pse-pd/Makefile  |    1 +
 drivers/net/pse-pd/ltc4266.c | 1386 ++++++++++++++++++++++++++++++++++
 4 files changed, 1405 insertions(+)
 create mode 100644 drivers/net/pse-pd/ltc4266.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 6de1ff058db6..3f7226d04983 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15531,10 +15531,17 @@ L:	linux-leds@vger.kernel.org
 S:	Maintained
 W:	https://ez.analog.com/linux-software-drivers
 F:	Documentation/devicetree/bindings/leds/adi,ltc3220.yaml
 F:	drivers/leds/leds-ltc3220.c
 
+LTC4266 PSE CONTROLLER DRIVER
+M:	Kyle Swenson <kyle.swenson@est.tech>
+L:	netdev@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/net/pse-pd/lltc,ltc4266.yaml
+F:	drivers/net/pse-pd/ltc4266.c
+
 LTC4282 HARDWARE MONITOR DRIVER
 M:	Nuno Sa <nuno.sa@analog.com>
 L:	linux-hwmon@vger.kernel.org
 S:	Supported
 F:	Documentation/devicetree/bindings/hwmon/adi,ltc4282.yaml
diff --git a/drivers/net/pse-pd/Kconfig b/drivers/net/pse-pd/Kconfig
index a0f2ae668c67..844c1b237df7 100644
--- a/drivers/net/pse-pd/Kconfig
+++ b/drivers/net/pse-pd/Kconfig
@@ -46,10 +46,21 @@ config PSE_REGULATOR
 	help
 	  This module provides support for simple regulator based Ethernet Power
 	  Sourcing Equipment without automatic classification support. For
 	  example for basic implementation of PoDL (802.3bu) specification.
 
+config PSE_LTC4266
+	tristate "LTC4266 PSE controller"
+	depends on I2C
+	select REGMAP_I2C
+	help
+	  This module provides support for LTC4266 regulator based Ethernet
+	  Power Sourcing Equipment.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ltc4266.
+
 config PSE_PD692X0
 	tristate "PD692X0 PSE controller"
 	depends on I2C
 	select FW_LOADER
 	select FW_UPLOAD
diff --git a/drivers/net/pse-pd/Makefile b/drivers/net/pse-pd/Makefile
index 9cca5900fe34..c3fe4efcf1de 100644
--- a/drivers/net/pse-pd/Makefile
+++ b/drivers/net/pse-pd/Makefile
@@ -1,10 +1,11 @@
 # SPDX-License-Identifier: GPL-2.0-only
 # Makefile for Linux PSE drivers
 
 obj-$(CONFIG_PSE_CONTROLLER) += pse_core.o
 
+obj-$(CONFIG_PSE_LTC4266) += ltc4266.o
 obj-$(CONFIG_PSE_REALTEK_MCU) += realtek-pse-mcu-core.o
 obj-$(CONFIG_PSE_REALTEK_MCU_I2C) += realtek-pse-mcu-i2c.o
 obj-$(CONFIG_PSE_REALTEK_MCU_UART) += realtek-pse-mcu-uart.o
 obj-$(CONFIG_PSE_REGULATOR) += pse_regulator.o
 obj-$(CONFIG_PSE_PD692X0) += pd692x0.o
diff --git a/drivers/net/pse-pd/ltc4266.c b/drivers/net/pse-pd/ltc4266.c
new file mode 100644
index 000000000000..37dea467811a
--- /dev/null
+++ b/drivers/net/pse-pd/ltc4266.c
@@ -0,0 +1,1386 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Linear LTC4266 PoE PSE Controller
+ *
+ * Original work:
+ *    Copyright 2019 Cradlepoint Technology, Inc.
+ *    Cradlepoint Technology, Inc.  <source@cradlepoint.com>
+ *
+ * Re-written in 2026:
+ *    Copyright 2026 Ericsson Software Technology
+ *    Kyle Swenson <kyle.swenson@est.tech>
+ *
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/ethtool.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pse-pd/pse.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+#define LTC4266_MAX_PORTS			4
+
+/* The minimum and maximum here depend on the resolution of the I_CUT field,
+ * which is 18.75mA.  To get 1000mW with a 50V port voltage, we need 20mA; but
+ * with 18.75 mA steps we end up with a current limit of 37.5 mA resulting in
+ * an actual power limit of 1875mW.
+ */
+#define LTC4266_PW_LIMIT_MAX			30000
+#define LTC4266_PW_LIMIT_MIN			1000
+
+/* Nominal PI voltage reported when the port is not delivering power and no
+ * "vpwr-supply" is described in the device tree: V_Port_PSE min for a Type 2
+ * PSE. IEEE 802.3-2022 Table 33-11 item 1 specifies the POWER_ON state output
+ * voltage as 50.0 V to 57.0 V for a Type 2 PSE, 44.0 V to 57.0 V for Type 1.
+ */
+#define LTC4266_VPORT_NOMINAL_UV		50000000
+
+/* Register definitions */
+#define LTC4266_REG_INTSTAT			0x00
+#define LTC4266_REG_INTMASK			0x01
+#define LTC4266_REG_PWREVN_COR			0x03
+#define LTC4266_REG_DETEVN_COR			0x05
+#define LTC4266_REG_FLTEVN_COR			0x07
+#define LTC4266_REG_TSEVN_COR			0x09
+#define LTC4266_REG_SUPEVN_COR			0x0B
+#define LTC4266_REG_STAT(_p)			(0x0C + (_p))
+#define LTC4266_REG_STATPWR			0x10
+#define LTC4266_REG_OPMD			0x12
+#define LTC4266_REG_DISENA			0x13 /* Disconnect detect enable */
+#define LTC4266_REG_MCONF			0x17
+#define LTC4266_REG_DETPB			0x18 /*PB means "push button" */
+#define LTC4266_REG_PWRPB			0x19
+#define LTC4266_REG_RSTPB			0x1A
+#define LTC4266_REG_ID				0x1B
+#define LTC4266_REG_TLIM12			0x1E
+#define LTC4266_REG_TLIM34			0x1F
+#define LTC4266_REG_IPLSB(_p)			(0x30 | ((_p) << 2))
+#define LTC4266_REG_VPLSB(_p)			(LTC4266_REG_IPLSB(_p) + 2)
+#define LTC4266_REG_HPEN			0x44
+#define LTC4266_REG_HPMD(_p)			(0x46 + (5 * (_p)))
+#define LTC4266_REG_ICUT_HP(_p)			(LTC4266_REG_HPMD(_p) + 1)
+#define LTC4266_REG_ILIM(_p)			(LTC4266_REG_HPMD(_p) + 2)
+
+/* Register field definitions */
+
+/* LTC4266_REG_INTSTAT and LTC4266_REG_INTMASK */
+#define LTC4266_INT_TSTART			BIT(6)
+#define LTC4266_INT_TCUT			BIT(5)
+#define LTC4266_INT_CLASS			BIT(4)
+#define LTC4266_INT_DETECT			BIT(3)
+#define LTC4266_INT_DIS				BIT(2)
+#define LTC4266_INT_PWRGD			BIT(1)
+
+/* Per-port event bits within the CoR event registers. Every per-port event
+ * register splits its 8 bits into a per-port low nibble and a per-port high
+ * nibble:
+ * pwrevn (03h): LO = power on/off change, HI = power good change
+ * detevn (05h): LO = detection complete, HI = classification complete
+ * fltevn (07h): LO = tCUT overcurrent,   HI = tDIS DC disconnect
+ * tsevn  (09h): LO = tSTART overcurrent, HI = tLIM current-limit timeout
+ */
+#define LTC4266_EVN_HI(_p)			BIT((_p) + 4)	/* ports 0-3 */
+#define LTC4266_EVN_LO(_p)			BIT(_p)		/* ports 0-3 */
+
+/* statp<n> (0Ch-0Fh) detection result, and the "Signature Good" value */
+#define LTC4266_PORT_CLASS(_stat)		FIELD_GET(GENMASK(6, 4), (_stat))
+#define LTC4266_PORT_DETECT(_stat)		FIELD_GET(GENMASK(2, 0), (_stat))
+#define LTC4266_DETECT_GOOD			0x4
+
+/* LTC4266_REG_STATPWR */
+#define LTC4266_STATPWR_PG(_p)			BIT((_p) + 4)
+#define LTC4266_STATPWR_PE(_p)			BIT(_p)
+
+/* LTC4266_REG_OPMD
+ * There are three other operation modes possible this
+ * driver doesn't support and so aren't defined.  The one supported mode,
+ * OPMD_SEMI, means that a port will continuously detect and classify devices,
+ * but will not power the device until instructed to do so.
+ */
+#define LTC4266_OPMD_SEMI			2
+#define LTC4266_TWO_BIT_WORD_OFFSET(_v, _p)	((_v) << ((_p) * 2))
+#define LTC4266_TWO_BIT_WORD_MASK(_p)		LTC4266_TWO_BIT_WORD_OFFSET(0x03, (_p))
+
+/* LTC4266_REG_MCONF */
+#define LTC4266_MCONF_INTERRUPT_ENABLE		BIT(7)
+/* Only report a detect event when the result changes, not every cycle */
+#define LTC4266_MCONF_DETCHG			BIT(6)
+
+/* LTC4266_REG_DETPB */
+#define LTC4266_DETPB_CLASS_ENABLE(_p)		BIT((_p) + 4)
+#define LTC4266_DETPB_DETECT_ENABLE(_p)		BIT(_p)
+
+/* LTC4266_REG_RSTPB */
+#define LTC4266_RSTPB_INTCLR			BIT(7)
+#define LTC4266_RSTPB_PINCLR			BIT(6)
+#define LTC4266_RSTPB_RSTALL			BIT(4)
+#define LTC4266_RSTPB_RSTPORTS			GENMASK(3, 0)
+
+/* LTC4266_REG_ID */
+#define LTC4266_ID				0x64
+
+/* LTC4266_REG_TLIM* */
+#define LTC4266_TLIM_VALUE			0x01
+
+/* Current-sense scaling, in nA per LSB, dependent on the sense resistor. */
+#define LTC4266_IP_NA_PER_LSB_RSENSE_025	122070	/* 122.07 uA/LSB */
+#define LTC4266_IP_NA_PER_LSB_RSENSE_050	61035	/* 61.035 uA/LSB */
+
+/* Voltage-sense scaling: 5.835 mV == 5835 uV per LSB. */
+#define LTC4266_VP_UV_PER_LSB			5835
+
+/* LTC4266_REG_HPEN, enable "High Power" mode (Type 2, Class 4) */
+#define LTC4266_HPEN(_p)			BIT(_p)
+
+/* LTC4266_REG_HPMD */
+#define LTC4266_HPMD_PONGEN			0x01
+
+/* LTC4266_REG_ICUT_HP.
+ * Set if the sense resistor specified in DT is 0.25 Ohm to have accurately
+ * scaled ICUT thresholds.
+ */
+#define LTC4266_ICUT_RSENSE_025_OHM		BIT(7)
+
+/* To keep the ICUT resolution at a constant 18.75 mA, for the 0.25 Ohm sense
+ * case we should also set this ICUT_RANGE
+ */
+#define LTC4266_ICUT_RANGE			BIT(6)
+
+/* I_CUT is programmed in a 6-bit field; each step is 18.75 mA (18750 uA). */
+#define LTC4266_ICUT_STEP_UA			18750
+#define LTC4266_ICUT_MASK			GENMASK(5, 0)
+
+/* Cap I_CUT at the suggested value for a Type 2 PD at 638mA */
+#define LTC4266_ICUT_MAX_MA			638
+#define LTC4266_ICUT_MAX_STEPS			34
+
+/* Recommended lim<n> settings from datasheet Table 5.
+ *
+ *	I_LIM (mA)	RSENSE = 0.5 Ohm	RSENSE = 0.25 Ohm
+ *	425 (Type 1)	0x00			0x80
+ *	850 (Type 2)	0x40			0xC0
+ */
+#define LTC4266_ILIM_TYPE1_RSENSE_050		0x00
+#define LTC4266_ILIM_TYPE1_RSENSE_025		0x80
+#define LTC4266_ILIM_TYPE2_RSENSE_050		0x40
+#define LTC4266_ILIM_TYPE2_RSENSE_025		0xC0
+
+enum {
+	LTC4266_READ_CURRENT = 0,
+	LTC4266_READ_VOLTAGE = 2
+};
+
+/* Map LTC4266 Classification result to PD class.  Note for a PD that has a
+ * valid detect signature, but doesn't produce a classification signature is
+ * still a valid PD.  The LTC4266 indicates this with 0x06 in the statp<n>
+ * register and calls it "Class 0".  This is a different state than when
+ * statp<n> indicates 0, which means classification isn't complete.  This maps
+ * the result to either an errno or classification value suitable for use up
+ * the stack.
+ */
+static const int ltc4266_class_map[] = {
+	-EAGAIN, /* Classification is incomplete */
+	1,
+	2,
+	3,
+	4,
+	-EINVAL,
+	0,
+	-ERANGE
+};
+
+/* Map a PD Class to I_CUT thresholds from the LTC4266 datasheet Table 2 */
+static const int ltc4266_class_to_icut[] = {
+	375,
+	112,
+	206,
+	375,
+	638
+};
+
+/* Per-class power budget at the PSE PI in mW, indexed by class (0-4).
+ * Classes 0-3 are the P_Class values from Table 33-7 in the IEEE802.3
+ * standard, which Note 1 defines as the minimum power a PSE must supply at the
+ * PI for that class (the PD input power maxima are in Table 33-18). Class 4 is
+ * P_Type, which Table 33-11 item 12 defines as I_Cable x V_Port_PSE min:
+ * 0.600 A x 50.0 V for a Type 2 PSE, with I_Cable from Table 33-1.
+ *
+ * Class 0 and Class 3 have the same value because a device without a classification
+ * signature (Class 0) has to be assumed to consume up to the maximum power for
+ * a Type 1 PD (Class 3).
+ */
+static const int ltc4266_class_pw[] = {
+	15400,	/* Class 0 */
+	4000,	/* Class 1 */
+	7000,	/* Class 2 */
+	15400,	/* Class 3 */
+	30000,	/* Class 4 (Type 2) */
+};
+
+enum sense_resistor {
+	LTC4266_RSENSE_500, /* Rsense 0.5 Ohm */
+	LTC4266_RSENSE_250 /* Rsense 0.25 Ohm */
+};
+
+struct ltc4266;
+
+/**
+ * struct ltc4266_port - per-PSE-PI context
+ *
+ * @ltc4266: the controller owning this port
+ * @chan: index of the LTC4266 delivery channel backing this PI, established
+ *	  from the PI pairset phandle by ltc4266_map_pis().  All register
+ *	  addressing uses this member.
+ * @rsense: sense resistor on @chan, used to scale current readings and
+ *	    to pick the I_CUT and I_LIM encodings.
+ * @pw_limit: Admin-configured power limit in mW, always valid: it defaults to
+ *	      LTC4266_PW_LIMIT_MAX, the most this controller can deliver, until
+ *	      pi_set_pw_limit() lowers it.
+ * @vpwr_uv: nominal PI voltage in uV, read once from the PI's "vpwr-supply" at
+ *	     probe, or LTC4266_VPORT_NOMINAL_UV when the PI does not describe
+ *	     one. Reported while the port is not delivering power, when the
+ *	     controller has nothing to measure.
+ */
+struct ltc4266_port {
+	struct ltc4266 *ltc4266;
+	u8 chan;
+	enum sense_resistor rsense;
+	int pw_limit;
+	int vpwr_uv;
+};
+
+/**
+ * struct ltc4266 - LTC4266 controller context
+ *
+ * @client: the I2C client
+ * @regmap: register map of @client
+ * @ports: table of PSE PI contexts, indexed by PSE PI id. A NULL entry is a PI
+ *	   that is not described in the device tree and therefore has no
+ *	   channel mapped.
+ * @dev: the underlying device
+ * @np: device node of @dev
+ * @pcdev: the PSE controller registered with the PSE core
+ */
+struct ltc4266 {
+	struct i2c_client *client;
+	struct regmap *regmap;
+	struct ltc4266_port *ports[LTC4266_MAX_PORTS];
+	struct device *dev;
+	struct device_node *np;
+	struct pse_controller_dev pcdev;
+};
+
+static struct ltc4266_port *ltc4266_pi_port(struct pse_controller_dev *pcdev,
+					    int id)
+{
+	struct ltc4266 *ltc4266 = container_of(pcdev, struct ltc4266, pcdev);
+
+	return ltc4266->ports[id];
+}
+
+static int ltc4266_read_iv(struct ltc4266_port *port, u8 iv)
+{
+	struct ltc4266 *ltc4266 = port->ltc4266;
+	unsigned int lsb, msb;
+	unsigned int statpwr;
+	int lsb_reg;
+	int result;
+	u64 ivbits;
+
+	result = regmap_read(ltc4266->regmap, LTC4266_REG_STATPWR, &statpwr);
+	if (result < 0)
+		return result;
+
+	/* LTC4266 IV readings are only meaningful while the port is delivering
+	 * power. When the PG (power good) bit is not set, the port is
+	 * delivering nothing, so report 0 rather than an error: returning an
+	 * errno here would abort pse_ethtool_get_status() and fail the whole
+	 * "ethtool --show-pse" query for an otherwise perfectly readable port.
+	 * Callers that must not report 0 (ltc4266_port_voltage_uv()) substitute
+	 * a nominal value.
+	 */
+	if (!(statpwr & LTC4266_STATPWR_PG(port->chan)))
+		return 0;
+
+	/* Since iv is either 0 (to read current) or 2 to (read voltage), we
+	 * can get the LSB reg by adding
+	 */
+	lsb_reg = LTC4266_REG_IPLSB(port->chan) + iv;
+
+	result = regmap_read(ltc4266->regmap, lsb_reg, &lsb);
+	if (result < 0)
+		return result;
+
+	result = regmap_read(ltc4266->regmap, lsb_reg + 1, &msb);
+	if (result < 0)
+		return result;
+
+	ivbits = (msb << 8) | lsb;
+
+	if (iv == LTC4266_READ_CURRENT)
+		if (port->rsense == LTC4266_RSENSE_250)
+			result = DIV_ROUND_CLOSEST_ULL(ivbits * LTC4266_IP_NA_PER_LSB_RSENSE_025,
+						       1000);
+		else
+			result = DIV_ROUND_CLOSEST_ULL(ivbits * LTC4266_IP_NA_PER_LSB_RSENSE_050,
+						       1000);
+	else
+		result = ivbits * LTC4266_VP_UV_PER_LSB;
+
+	return result;
+}
+
+/* Voltage at the PI in uV, if the PI is currently powering a device;
+ * otherwise, return the nominal voltage
+ */
+static int ltc4266_port_voltage_uv(struct ltc4266_port *port)
+{
+	int uv = ltc4266_read_iv(port, LTC4266_READ_VOLTAGE);
+
+	if (uv < 0)
+		return uv;
+
+	return uv ? uv : port->vpwr_uv;
+}
+
+/**
+ * ltc4266_port_set_ilim - Set the active current limit (ILIM) for a port
+ * @port: the port to configure
+ * @class: the detected PD class (0-4)
+ *
+ * Given the PD class, configure the active current limit for a particular
+ * channel.  The LTC4266 will actively enforce this current limit using the
+ * sense resistor for the channel. The values written are from the LTC4266
+ * Datasheet, Table 5, and correspond to 425mA for Type I PDs, and 850mA for
+ * Type 2 PDs (per the datasheet requirement for IEEE compliance).
+ *
+ * IEEE Std 802.3-2022, Table 33-11 specifies ILIM parameter ranges:
+ * - For Type 1 PSE operation (PD Classes 0-3):
+ * The minimum ILIM is 0.400A. This driver uses 425mA. This value fits
+ * within typical Type 1 ILIM specifications (e.g., 0.400A min to
+ * around 0.440A-0.500A max for the programmed steady-state limit).
+ *
+ * - For Type 2 PSE operation (typically PD Class 4):
+ * The minimum ILIM is 1.14 * ICable (or ~1.05 * IPort_max from other
+ * interpretations, e.g., ~0.630A to ~0.684A). This driver uses 850mA.
+ * This value meets the minimum requirement and is a supported operational
+ * current limit for high power modes in the LTC4266.
+ *
+ * The overall PSE current output must not exceed the time-dependent PSE
+ * upperbound template, IPSEUT(t), described in IEEE Std 802.3-2022,
+ * Equation (33-6). The programmed ILIM values (425mA/850mA) serve as the
+ * long-term current limit (Ilimmin segment of IPSEUT(t)) and are well
+ * within the higher short-term current allowances of that template (e.g., 1.75A).
+ *
+ * Returns: 0 on success or a negative errno.
+ */
+static int ltc4266_port_set_ilim(struct ltc4266_port *port, int class)
+{
+	bool rsense_250 = port->rsense == LTC4266_RSENSE_250;
+	u8 ilim;
+
+	if (class > 4 || class < 0)
+		return -EINVAL;
+
+	if (class < 4)
+		ilim = rsense_250 ? LTC4266_ILIM_TYPE1_RSENSE_025 :
+				    LTC4266_ILIM_TYPE1_RSENSE_050;
+	else
+		ilim = rsense_250 ? LTC4266_ILIM_TYPE2_RSENSE_025 :
+				    LTC4266_ILIM_TYPE2_RSENSE_050;
+
+	return regmap_write(port->ltc4266->regmap,
+			    LTC4266_REG_ILIM(port->chan), ilim);
+}
+
+static int ltc4266_port_set_icut(struct ltc4266_port *port, int icut)
+{
+	u8 val;
+
+	if (icut > LTC4266_ICUT_MAX_MA)
+		return -ERANGE;
+
+	val = min(DIV_ROUND_UP(icut * 1000, LTC4266_ICUT_STEP_UA),
+		  LTC4266_ICUT_MAX_STEPS) & LTC4266_ICUT_MASK;
+
+	if (port->rsense == LTC4266_RSENSE_250)
+		val |= LTC4266_ICUT_RSENSE_025_OHM | LTC4266_ICUT_RANGE;
+
+	return regmap_write(port->ltc4266->regmap,
+			    LTC4266_REG_ICUT_HP(port->chan), val);
+}
+
+/**
+ * ltc4266_pw_limit_to_icut - Convert an admin power limit to an I_CUT threshold
+ * @port: the port to convert for
+ * @max_mw: the power limit in mW
+ * @class: the detected PD class (0-4)
+ *
+ * The LTC4266 only enforces a current threshold, so a power limit has to be
+ * divided by the port voltage.
+ *
+ * Lowering I_CUT below the class threshold is legitimate: IEEE 802.3-2022
+ * 33.2.7.10 defines P_Class as either the class power or "PSE allocated power
+ * ... added to the channel power loss", and an administrative limit is that
+ * allocated power.
+ *
+ * Raising it above the class threshold is not. Table 33-11 item 7 bounds I_CUT
+ * at I_LIM, and ltc4266_port_set_ilim() picks I_LIM from the class: 425 mA for
+ * Type 1, 850 mA for Type 2. An administrative limit of LTC4266_PW_LIMIT_MAX
+ * asks for 600 mA at the nominal port voltage, which would exceed I_LIM on a
+ * Type 1 class. Capping at the class threshold from datasheet Table 2 keeps
+ * I_CUT under I_LIM for every class; the LTC4266_ICUT_MAX_MA check in
+ * ltc4266_port_set_icut() only covers Type 2.
+ *
+ * Return: the threshold in mA, or a negative errno if the voltage read failed.
+ */
+static int ltc4266_pw_limit_to_icut(struct ltc4266_port *port, int max_mw,
+				    int class)
+{
+	int uv = ltc4266_port_voltage_uv(port);
+	int icut_mA;
+
+	if (uv < 0)
+		return uv;
+
+	/* 30000 mW x 1000000 overflows an int */
+	icut_mA = DIV_ROUND_UP_ULL((u64)max_mw * 1000000, uv);
+
+	return min(ltc4266_class_to_icut[class], icut_mA);
+}
+
+static int ltc4266_port_delivering(struct ltc4266_port *port)
+{
+	unsigned int result;
+	int ret;
+
+	ret = regmap_read(port->ltc4266->regmap, LTC4266_REG_STATPWR, &result);
+	if (ret < 0)
+		return ret;
+
+	return !!((result & LTC4266_STATPWR_PG(port->chan)) &&
+		  (result & LTC4266_STATPWR_PE(port->chan)));
+}
+
+static int ltc4266_port_init(struct ltc4266_port *port)
+{
+	struct ltc4266 *ltc4266 = port->ltc4266;
+	u8 chan = port->chan;
+	u8 tlim_shift;
+	u8 tlim_mask;
+	u8 tlim_reg;
+	int ret;
+
+	/* Reset the port */
+	ret = regmap_write(ltc4266->regmap, LTC4266_REG_RSTPB, BIT(chan));
+	if (ret < 0)
+		return ret;
+
+	/* Set Semi-auto mode */
+	ret = regmap_update_bits(port->ltc4266->regmap, LTC4266_REG_OPMD,
+				 LTC4266_TWO_BIT_WORD_MASK(port->chan),
+				 LTC4266_TWO_BIT_WORD_OFFSET(LTC4266_OPMD_SEMI,
+							     port->chan));
+	if (ret < 0)
+		return ret;
+
+	/* Enable high power mode on the port (for Type 2 PD support) */
+	ret = regmap_update_bits(ltc4266->regmap, LTC4266_REG_HPEN,
+				 LTC4266_HPEN(chan), LTC4266_HPEN(chan));
+	if (ret < 0)
+		return ret;
+
+	/* Enable 2-event classification (IEEE 802.3-2022, Clause 33), which the
+	 * datasheet refers to as "Ping-Pong" classification.
+	 */
+	ret = regmap_update_bits(ltc4266->regmap, LTC4266_REG_HPMD(chan),
+				 LTC4266_HPMD_PONGEN, LTC4266_HPMD_PONGEN);
+	if (ret < 0)
+		return ret;
+
+	if (chan <= 1)
+		tlim_reg = LTC4266_REG_TLIM12;
+	else
+		tlim_reg = LTC4266_REG_TLIM34;
+
+	/* Each tlim register packs two ports: the even port in the low nibble
+	 * and the odd port in the high nibble. Shift both the value and the
+	 * mask into the correct nibble.
+	 */
+	if (chan & BIT(0))
+		tlim_shift = 4;
+	else
+		tlim_shift = 0;
+
+	tlim_mask = GENMASK(3, 0) << tlim_shift;
+
+	ret = regmap_update_bits(ltc4266->regmap, tlim_reg,
+				 tlim_mask, LTC4266_TLIM_VALUE << tlim_shift);
+	if (ret < 0)
+		return ret;
+
+	/* Enable disconnect detect. */
+	ret = regmap_update_bits(ltc4266->regmap, LTC4266_REG_DISENA,
+				 BIT(chan), BIT(chan));
+	if (ret < 0)
+		return ret;
+
+	/* Enable detection (low nibble), classification (high nibble) on the port */
+	ret = regmap_write(ltc4266->regmap, LTC4266_REG_DETPB,
+			   LTC4266_DETPB_CLASS_ENABLE(chan) |
+			   LTC4266_DETPB_DETECT_ENABLE(chan));
+
+	if (ret < 0)
+		return ret;
+
+	dev_dbg(ltc4266->dev, "Channel %d has been initialized\n", chan);
+	return 0;
+}
+
+/* Read the port's classification result and return a class 0-4 or an error if
+ * the result isn't valid.
+ */
+static int ltc4266_port_get_class(struct ltc4266_port *port)
+{
+	struct ltc4266 *ltc4266 = port->ltc4266;
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(ltc4266->regmap, LTC4266_REG_STAT(port->chan), &val);
+	if (ret < 0) {
+		dev_warn(ltc4266->dev, "Failed to read status register, err=%d\n", ret);
+		return ret;
+	}
+
+	/* Can't have a valid classification result if we've not yet had a good
+	 * detection result.
+	 */
+	if (LTC4266_PORT_DETECT(val) != LTC4266_DETECT_GOOD)
+		return -EINVAL;
+
+	ret = ltc4266_class_map[LTC4266_PORT_CLASS(val)];
+	return ret;
+}
+
+/* Maximum power the classified PD is allowed. It does not depend on the port
+ * being powered, but it does depend on the port having a PD attached and being
+ * enabled to the point of running classification and detection cycles.
+ */
+static int ltc4266_port_max_pw(struct ltc4266_port *port)
+{
+	int class = ltc4266_port_get_class(port);
+
+	if (class < 0)
+		return class;
+
+	return ltc4266_class_pw[class];
+}
+
+static int ltc4266_pi_get_pw_status(struct pse_controller_dev *pcdev, int id,
+				    struct pse_pw_status *pw_status)
+{
+	struct ltc4266_port *port = ltc4266_pi_port(pcdev, id);
+	int ret;
+
+	ret = ltc4266_port_delivering(port);
+	if (ret < 0)
+		return ret;
+
+	if (ret)
+		pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;
+	else
+		pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_SEARCHING;
+
+	return 0;
+}
+
+/* With the static budget evaluation strategy the PSE core calls this only
+ * after a PD has been classified and the power budget has been allocated.
+ * Program the current limit for the classified PD, reduced if an admin power
+ * limit asks for less than the class is entitled to, and apply power.
+ */
+static int ltc4266_pi_enable(struct pse_controller_dev *pcdev, int id)
+{
+	struct ltc4266_port *port = ltc4266_pi_port(pcdev, id);
+	int class, icut, ret;
+
+	class = ltc4266_port_get_class(port);
+	if (class < 0)
+		return class;
+
+	ret = ltc4266_port_set_ilim(port, class);
+	if (ret < 0)
+		return ret;
+
+	/* Derive the threshold from the admin limit against the class in front of
+	 * us now, since a different PD may have been plugged in since the limit
+	 * was set.
+	 */
+	icut = ltc4266_pw_limit_to_icut(port, port->pw_limit, class);
+	if (icut < 0)
+		return icut;
+
+	ret = ltc4266_port_set_icut(port, icut);
+	if (ret < 0)
+		return ret;
+
+	/* Apply power to the port. */
+	return regmap_write(port->ltc4266->regmap, LTC4266_REG_PWRPB,
+			    BIT(port->chan));
+}
+
+static int ltc4266_pi_disable(struct pse_controller_dev *pcdev, int id)
+{
+	struct ltc4266_port *port = ltc4266_pi_port(pcdev, id);
+
+	/* Resetting the port (RSTPB, issued at the start of ltc4266_port_init)
+	 * removes power, disables detection and classification, and clears the
+	 * port's status register. Re-init the port so that detection and
+	 * classification can happen again.
+	 */
+	return ltc4266_port_init(port);
+}
+
+static int ltc4266_pi_get_voltage(struct pse_controller_dev *pcdev, int id)
+{
+	return ltc4266_port_voltage_uv(ltc4266_pi_port(pcdev, id));
+}
+
+static int ltc4266_pi_get_admin_state(struct pse_controller_dev *pcdev, int id,
+				      struct pse_admin_state *admin_state)
+{
+	struct ltc4266_port *port = ltc4266_pi_port(pcdev, id);
+	unsigned int val;
+	int ret;
+
+	/* The PSE core uses this as the hardware admin state: whether power has
+	 * been commanded on, not whether it has finished coming up.
+	 * pse_pi_is_hw_enabled() decides from it which software-enabled PIs
+	 * still need power delivery attempted, so report the power-enable
+	 * nibble (peN), which the controller sets as soon as it applies power to
+	 * the port. The power-good nibble only sets once OUT has pulled down to
+	 * VEE, up to a tSTART later, and a port reported as not enabled for that
+	 * whole ramp is one the core will allocate power domain budget for a
+	 * second time. Actual delivery is reported by pi_get_pw_status().
+	 */
+	ret = regmap_read(port->ltc4266->regmap, LTC4266_REG_STATPWR, &val);
+	if (ret < 0)
+		return ret;
+
+	if (val & LTC4266_STATPWR_PE(port->chan))
+		admin_state->c33_admin_state =
+			ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
+	else
+		admin_state->c33_admin_state =
+			ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
+
+	return 0;
+}
+
+static int ltc4266_pi_get_pw_class(struct pse_controller_dev *pcdev, int id)
+{
+	int ret = ltc4266_port_get_class(ltc4266_pi_port(pcdev, id));
+
+	/* ltc4266_port_get_class will return either the class, or an errno.
+	 * Returning an errno from this function will mean the ethtool command
+	 * will abort with the error and not emit later useful information.
+	 * Additionally, returning 0 to indicate no classification signature
+	 * will cause the ethtool output to omit the "Power Class" attribute
+	 * entirely. Since the "Power Class" is expected to be returned here,
+	 * and class 0 and class 3 are equivalent in terms of power allocation,
+	 * we'll return 3.
+	 */
+
+	if (ret == 0)
+		ret = 3;
+	if (ret < 0)
+		ret = 0;
+	return ret;
+}
+
+/* Get the power requested by the PD before enabling the port: its
+ * classification power.
+ */
+static int ltc4266_pi_get_pw_req(struct pse_controller_dev *pcdev, int id)
+{
+	return ltc4266_port_max_pw(ltc4266_pi_port(pcdev, id));
+}
+
+static int ltc4266_pi_get_actual_pw(struct pse_controller_dev *pcdev, int id)
+{
+	struct ltc4266_port *port = ltc4266_pi_port(pcdev, id);
+	int uA, uV;
+
+	uA = ltc4266_read_iv(port, LTC4266_READ_CURRENT);
+	if (uA < 0)
+		return uA;
+
+	uV = ltc4266_read_iv(port, LTC4266_READ_VOLTAGE);
+	if (uV < 0)
+		return uV;
+
+	/* mW = uA * uV / 1000000000 */
+	return DIV_ROUND_CLOSEST_ULL((u64)uA * uV, 1000000000);
+}
+
+/* The range of administrative power limits this controller supports. It does
+ * not depend on the detected class: the class only bounds the I_CUT threshold
+ * that ltc4266_pw_limit_to_icut() programs.
+ */
+static int ltc4266_pi_get_pw_limit_ranges(struct pse_controller_dev *pcdev, int id,
+					  struct pse_pw_limit_ranges *pw_limit_ranges)
+{
+	struct ethtool_c33_pse_pw_limit_range *c33_pw_limit_ranges;
+
+	c33_pw_limit_ranges = kzalloc_obj(*c33_pw_limit_ranges);
+	if (!c33_pw_limit_ranges)
+		return -ENOMEM;
+
+	c33_pw_limit_ranges[0].min = LTC4266_PW_LIMIT_MIN;
+	c33_pw_limit_ranges[0].max = LTC4266_PW_LIMIT_MAX;
+
+	pw_limit_ranges->c33_pw_limit_ranges = c33_pw_limit_ranges;
+
+	/* Return the number of ranges */
+	return 1;
+}
+
+/* Store the administrative power limit. The limit is independent of any PD in
+ * front of us, so accept it whether or not the port has classified anything;
+ * program I_CUT immediately when it has, so a change on a live port takes
+ * effect, and leave it to ltc4266_pi_enable() otherwise.
+ */
+static int ltc4266_pi_set_pw_limit(struct pse_controller_dev *pcdev,
+				   int id, int max_mw)
+{
+	struct ltc4266_port *port = ltc4266_pi_port(pcdev, id);
+	int class;
+	int icut;
+	int ret;
+
+	if (max_mw < LTC4266_PW_LIMIT_MIN || max_mw > LTC4266_PW_LIMIT_MAX) {
+		dev_err(port->ltc4266->dev, "power limit %d is out of range [%d, %d]\n",
+			max_mw, LTC4266_PW_LIMIT_MIN, LTC4266_PW_LIMIT_MAX);
+		return -ERANGE;
+	}
+
+	class = ltc4266_port_get_class(port);
+	if (class >= 0) {
+		icut = ltc4266_pw_limit_to_icut(port, max_mw, class);
+		if (icut < 0)
+			return icut;
+
+		ret = ltc4266_port_set_icut(port, icut);
+		if (ret < 0)
+			return ret;
+	}
+
+	port->pw_limit = max_mw;
+
+	return 0;
+}
+
+static int ltc4266_pi_get_pw_limit(struct pse_controller_dev *pcdev, int id)
+{
+	return ltc4266_pi_port(pcdev, id)->pw_limit;
+}
+
+/* Description of one delivery channel parsed out of the "channels" node. Used
+ * locally only during ltc4266_setup_pi_matrix()
+ */
+struct ltc4266_chan_desc {
+	struct device_node *np;
+	enum sense_resistor rsense;
+};
+
+static int ltc4266_get_of_channels(struct ltc4266 *ltc4266,
+				   struct ltc4266_chan_desc *chans)
+{
+	struct device_node *channels_node __free(device_node) =
+		of_get_child_by_name(ltc4266->np, "channels");
+	u32 chan_id, sense;
+	int ret;
+
+	if (!channels_node)
+		return dev_err_probe(ltc4266->dev, -EINVAL,
+				     "missing \"channels\" node\n");
+
+	for_each_child_of_node_scoped(channels_node, chan_node) {
+		if (!of_node_name_eq(chan_node, "channel"))
+			continue;
+
+		ret = of_property_read_u32(chan_node, "reg", &chan_id);
+		if (ret)
+			return dev_err_probe(ltc4266->dev, ret,
+					     "missing reg property in node %pOF\n",
+					     chan_node);
+
+		if (chan_id >= LTC4266_MAX_PORTS)
+			return dev_err_probe(ltc4266->dev, -EINVAL,
+					     "channel id %u is out of range in node %pOF\n",
+					     chan_id, chan_node);
+
+		if (chans[chan_id].np)
+			return dev_err_probe(ltc4266->dev, -EINVAL,
+					     "channel id %u is already used, please check the reg property in node %pOF\n",
+					     chan_id, chan_node);
+
+		ret = of_property_read_u32(chan_node, "sense-resistor-micro-ohms", &sense);
+		if (ret)
+			return dev_err_probe(ltc4266->dev, ret,
+					     "missing sense-resistor-micro-ohms property in node %pOF\n",
+					     chan_node);
+
+		if (sense == 250000)
+			chans[chan_id].rsense = LTC4266_RSENSE_250;
+		else if (sense == 500000)
+			chans[chan_id].rsense = LTC4266_RSENSE_500;
+		else
+			return dev_err_probe(ltc4266->dev, -EINVAL,
+					     "invalid sense resistor value %u in node %pOF\n",
+					     sense, chan_node);
+
+		chans[chan_id].np = of_node_get(chan_node);
+	}
+
+	return 0;
+}
+
+/* Find the channel a PI pairset phandle points at. */
+static int ltc4266_match_channel(const struct pse_pi_pairset *pairset,
+				 struct ltc4266_chan_desc *chans)
+{
+	int i;
+
+	for (i = 0; i < LTC4266_MAX_PORTS; i++)
+		if (pairset->np == chans[i].np)
+			return i;
+
+	return -ENODEV;
+}
+
+/*
+ * Nominal voltage of the rail feeding a PSE PI, taken from its "vpwr-supply".
+ * This is intended to work around the limitation on the LTC4266 where the port
+ * voltage is not valid until _after_ the port is powered.  Since the PSE core
+ * expects to read the port voltage when the port is not powered we need
+ * something non-zero to return to the core.
+ *
+ * In the event the regulator isn't present, or if the regulator doesn't have a
+ * nominal voltage available, we'll fall back and use 50V, which is the minimum
+ * allowed for a Type 2 PSE.
+ *
+ * Finally, we read this regulator voltage here instead of during
+ * ltc4266_pi_get_voltage to avoid deadlock.
+ *
+ * Return: the nominal voltage in uV, or LTC4266_VPORT_NOMINAL_UV if the PI
+ * describes no supply, its supply has not registered yet, or its voltage
+ * cannot be determined.
+ */
+static int ltc4266_pi_nominal_uv(struct ltc4266 *ltc4266, struct device_node *np)
+{
+	struct regulator *vpwr;
+	int uv;
+
+	vpwr = of_regulator_get_optional(ltc4266->dev, np, "vpwr");
+	if (IS_ERR(vpwr)) {
+		/* -ENODEV means the PI describes no vpwr-supply at all, which
+		 * is the case the fallback exists for. -EPROBE_DEFER means the
+		 * rail _is_ described but has not registered yet, so the
+		 * fallback is wrong for it. Asking for a probe retry is not an
+		 * option from here: we run from setup_pi_matrix(), and
+		 * pse_controller_register() unwinds neither its notification
+		 * fifo nor its pse_pi array when that fails, so every retry
+		 * would leak. Warn instead so the assumed voltage is visible.
+		 */
+		if (PTR_ERR(vpwr) == -EPROBE_DEFER)
+			dev_warn(ltc4266->dev,
+				 "%pOF: vpwr-supply is not registered yet, assuming %d uV\n",
+				 np, LTC4266_VPORT_NOMINAL_UV);
+
+		return LTC4266_VPORT_NOMINAL_UV;
+	}
+
+	uv = regulator_get_voltage(vpwr);
+	regulator_put(vpwr);
+
+	if (uv <= 0)
+		return LTC4266_VPORT_NOMINAL_UV;
+
+	return uv;
+}
+
+/* Build a port context for every PSE PI described in the device tree, bound to
+ * the channel its pairset references. A PI with no node is left NULL; the PSE
+ * core does not register a regulator for it and so never calls back for it.
+ */
+static int ltc4266_map_pis(struct ltc4266 *ltc4266,
+			   struct ltc4266_chan_desc *chans)
+{
+	struct pse_controller_dev *pcdev = &ltc4266->pcdev;
+	struct ltc4266_port *port;
+	int i, j, chan, uv;
+
+	for (i = 0; i < LTC4266_MAX_PORTS; i++) {
+		struct pse_pi *pi = &pcdev->pi[i];
+
+		if (!pi->np)
+			continue;
+
+		if (!pi->pairset[0].np)
+			return dev_err_probe(ltc4266->dev, -EINVAL,
+					     "%pOF has no pairsets\n", pi->np);
+
+		/* The LTC4266 delivers over a single pairset per channel, so
+		 * there is no 4-pair mode to spread a PI over two channels.
+		 */
+		if (pi->pairset[1].np)
+			return dev_err_probe(ltc4266->dev, -EOPNOTSUPP,
+					     "%pOF: 4-pair PSE PIs are not supported\n",
+					     pi->np);
+
+		chan = ltc4266_match_channel(&pi->pairset[0], chans);
+		if (chan < 0)
+			return dev_err_probe(ltc4266->dev, chan,
+					     "%pOF: pairset %pOF is not a channel of this controller\n",
+					     pi->np, pi->pairset[0].np);
+
+		for (j = 0; j < i; j++)
+			if (ltc4266->ports[j] && ltc4266->ports[j]->chan == chan)
+				return dev_err_probe(ltc4266->dev, -EINVAL,
+						     "%pOF: channel %d is already used by %pOF\n",
+						     pi->np, chan, pcdev->pi[j].np);
+
+		uv = ltc4266_pi_nominal_uv(ltc4266, pi->np);
+
+		port = devm_kzalloc(ltc4266->dev, sizeof(*port), GFP_KERNEL);
+		if (!port)
+			return -ENOMEM;
+
+		port->ltc4266 = ltc4266;
+		port->chan = chan;
+		port->rsense = chans[chan].rsense;
+		port->vpwr_uv = uv;
+		port->pw_limit = LTC4266_PW_LIMIT_MAX;
+		ltc4266->ports[i] = port;
+
+		dev_dbg(ltc4266->dev, "PI %d is backed by channel %d, nominal %d uV\n",
+			i, chan, uv);
+	}
+
+	return 0;
+}
+
+static int ltc4266_setup_pi_matrix(struct pse_controller_dev *pcdev)
+{
+	struct ltc4266 *ltc4266 = container_of(pcdev, struct ltc4266, pcdev);
+	struct ltc4266_chan_desc chans[LTC4266_MAX_PORTS] = { };
+	int i, ret;
+
+	if (pcdev->no_of_pse_pi)
+		return dev_err_probe(ltc4266->dev, -EINVAL,
+				     "a \"pse-pis\" node is required\n");
+
+	ret = ltc4266_get_of_channels(ltc4266, chans);
+	if (!ret)
+		ret = ltc4266_map_pis(ltc4266, chans);
+
+	for (i = 0; i < LTC4266_MAX_PORTS; i++)
+		of_node_put(chans[i].np);
+
+	if (ret)
+		return ret;
+
+	for (i = 0; i < LTC4266_MAX_PORTS; i++) {
+		if (!ltc4266->ports[i])
+			continue;
+
+		ret = ltc4266_port_init(ltc4266->ports[i]);
+		if (ret < 0)
+			return dev_err_probe(ltc4266->dev, ret,
+					     "Failed to initialize PI %d\n", i);
+	}
+
+	return 0;
+}
+
+static const struct pse_controller_ops ltc4266_ops = {
+	.setup_pi_matrix = ltc4266_setup_pi_matrix,
+	.pi_get_admin_state = ltc4266_pi_get_admin_state,
+	.pi_get_pw_status = ltc4266_pi_get_pw_status,
+	.pi_get_pw_class = ltc4266_pi_get_pw_class,
+	.pi_get_actual_pw = ltc4266_pi_get_actual_pw,
+	.pi_enable = ltc4266_pi_enable,
+	.pi_disable = ltc4266_pi_disable,
+	.pi_get_voltage = ltc4266_pi_get_voltage,
+	.pi_get_pw_limit = ltc4266_pi_get_pw_limit,
+	.pi_set_pw_limit = ltc4266_pi_set_pw_limit,
+	.pi_get_pw_limit_ranges = ltc4266_pi_get_pw_limit_ranges,
+	.pi_get_pw_req = ltc4266_pi_get_pw_req,
+};
+
+#define LTC4266_INTERRUPT_SOURCES	(LTC4266_INT_TSTART | LTC4266_INT_TCUT | \
+					 LTC4266_INT_CLASS | LTC4266_INT_DETECT | \
+					 LTC4266_INT_DIS | LTC4266_INT_PWRGD)
+
+static int ltc4266_enable_interrupts(struct ltc4266 *ltc4266)
+{
+	/* Unmask interrupts */
+	return regmap_write(ltc4266->regmap, LTC4266_REG_INTMASK,
+		     LTC4266_INTERRUPT_SOURCES);
+}
+
+static int ltc4266_disable_interrupts(struct ltc4266 *ltc4266)
+{
+	int ret;
+
+	ret = regmap_write(ltc4266->regmap, LTC4266_REG_INTMASK, 0x00);
+	if (ret < 0)
+		return ret;
+
+	/* Reset the (SMBus Alert) interrupt pin */
+	return regmap_write(ltc4266->regmap, LTC4266_REG_RSTPB, LTC4266_RSTPB_PINCLR);
+}
+
+static int ltc4266_map_event(int irq, struct pse_controller_dev *pcdev,
+			     unsigned long *notifs, unsigned long *notifs_mask)
+{
+	struct ltc4266 *ltc4266 = container_of(pcdev, struct ltc4266, pcdev);
+	unsigned int detevn = 0, fltevn = 0, tsevn = 0, pwrevn = 0;
+	unsigned int statpwr = 0;
+	unsigned int intstat;
+	int ret;
+	int i;
+
+	ret = ltc4266_disable_interrupts(ltc4266);
+	if (ret < 0)
+		goto err;
+
+	ret = regmap_read(ltc4266->regmap, LTC4266_REG_INTSTAT, &intstat);
+	if (ret < 0)
+		goto err;
+
+	if (!intstat)
+		goto done;
+
+	if (intstat & LTC4266_INT_PWRGD) {
+		ret = regmap_read(ltc4266->regmap, LTC4266_REG_PWREVN_COR, &pwrevn);
+		if (ret < 0) {
+			dev_err(&ltc4266->client->dev, "Failed to read pwrevn, err=%d\n", ret);
+			goto err;
+		}
+
+		ret = regmap_read(ltc4266->regmap, LTC4266_REG_STATPWR, &statpwr);
+		if (ret < 0) {
+			dev_err(&ltc4266->client->dev, "Failed to read statpwr, err=%d\n", ret);
+			goto err;
+		}
+	}
+
+	if (intstat & (LTC4266_INT_DIS | LTC4266_INT_TCUT)) {
+		ret = regmap_read(ltc4266->regmap, LTC4266_REG_FLTEVN_COR, &fltevn);
+		if (ret < 0) {
+			dev_err(&ltc4266->client->dev, "Failed to read fltevn err=%d\n", ret);
+			goto err;
+		}
+	}
+
+	if (intstat & (LTC4266_INT_TSTART | LTC4266_INT_TCUT)) {
+		ret = regmap_read(ltc4266->regmap, LTC4266_REG_TSEVN_COR, &tsevn);
+		if (ret < 0) {
+			dev_err(&ltc4266->client->dev, "Failed to read tsevn, err=%d\n", ret);
+			goto err;
+		}
+	}
+
+	if (intstat & (LTC4266_INT_CLASS | LTC4266_INT_DETECT)) {
+		ret = regmap_read(ltc4266->regmap, LTC4266_REG_DETEVN_COR, &detevn);
+		if (ret < 0) {
+			dev_err(&ltc4266->client->dev, "Failed to read detevn, err=%d\n", ret);
+			goto err;
+		}
+	}
+
+	/* The event registers are indexed by delivery channel while notifs[] is
+	 * indexed by PSE PI id, so walk the PIs and use each one's channel to
+	 * select the event bits.
+	 */
+	for (i = 0; i < LTC4266_MAX_PORTS; i++) {
+		struct ltc4266_port *port = ltc4266->ports[i];
+		unsigned int fault;
+		bool pg_lost;
+		u8 chan;
+
+		if (!port)
+			continue;
+
+		chan = port->chan;
+
+		fault = (tsevn | fltevn) &
+			(LTC4266_EVN_HI(chan) | LTC4266_EVN_LO(chan));
+
+		/* The controller also drops a port without reporting any
+		 * per-port fault event, for instance on an over-temperature
+		 * shutdown or when it finds a failed external MOSFET. Both of
+		 * those clear the port's detection and classification enables,
+		 * so the port stays dark until it is re-initialised. A power
+		 * good change that leaves statpwr[pg] clear catches them, and
+		 * ignores the rising edge of a port that has just been powered
+		 * on.
+		 */
+		pg_lost = (pwrevn & LTC4266_EVN_HI(chan)) &&
+			  !(statpwr & LTC4266_STATPWR_PG(chan));
+
+		if (fault || pg_lost) {
+			/* The port has gone down; if we see this, then we don't
+			 * care if any of the remaining events are set.  If the
+			 * device did disconnect briefly, it'll redetect and
+			 * reclassify accordingly
+			 */
+			notifs[i] |= ETHTOOL_C33_PSE_EVENT_DISCONNECTION;
+			*notifs_mask |= BIT(i);
+
+			/* Report over-current if the port went down for any
+			 * overcurrent reason: tSTART (tsevn low nibble, startup
+			 * inrush), tLIM (tsevn high nibble, current-limit
+			 * timeout) or tCUT (fltevn low nibble, I_CUT timeout).
+			 */
+			if ((tsevn & (LTC4266_EVN_LO(chan) | LTC4266_EVN_HI(chan))) ||
+			    (fltevn & LTC4266_EVN_LO(chan)))
+				notifs[i] |= ETHTOOL_PSE_EVENT_OVER_CURRENT;
+
+			dev_dbg(&ltc4266->client->dev,
+				"tsevn=0x%02X fltevn=0x%02X pwrevn=0x%02X statpwr=0x%02X\n",
+				tsevn, fltevn, pwrevn, statpwr);
+			continue;
+		}
+		if (detevn & LTC4266_EVN_LO(chan)) {
+			/* Read the detect result, and if it isn't detect good,
+			 * call it a disconnect
+			 */
+			unsigned int detval;
+
+			ret = regmap_read(ltc4266->regmap, LTC4266_REG_STAT(chan), &detval);
+			if (ret < 0) {
+				dev_warn(ltc4266->dev, "Failed to read status register, err=%d\n",
+					 ret);
+				continue;
+			}
+
+			if (LTC4266_PORT_DETECT(detval) != LTC4266_DETECT_GOOD) {
+				notifs[i] |= ETHTOOL_C33_PSE_EVENT_DISCONNECTION;
+				*notifs_mask |= BIT(i);
+				continue;
+			}
+		}
+
+		if (detevn & LTC4266_EVN_HI(chan)) {
+			int class = ltc4266_port_get_class(port);
+
+			if (class >= 0) {
+				notifs[i] |= ETHTOOL_C33_PSE_EVENT_CLASSIFICATION;
+				*notifs_mask |= BIT(i);
+			}
+		}
+	}
+
+done:
+	return ltc4266_enable_interrupts(ltc4266);
+
+err:
+	/* (Attempt to) clear any remaining event registers that we might've
+	 * missed in the event a previous read has failed.
+	 */
+	ret = regmap_write(ltc4266->regmap, LTC4266_REG_RSTPB, LTC4266_RSTPB_INTCLR);
+	if (ret)
+		dev_warn(&ltc4266->client->dev, "Failed to clear pending interrupts, err=%d\n",
+			 ret);
+
+	return ltc4266_enable_interrupts(ltc4266);
+}
+
+static const struct regmap_config ltc4266_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = 0x5F,
+};
+
+static void ltc4266_teardown(void *data)
+{
+	struct ltc4266 *ltc4266 = data;
+
+	ltc4266_disable_interrupts(ltc4266);
+
+	/* Prevent the chip from asserting interrupts */
+	regmap_update_bits(ltc4266->regmap, LTC4266_REG_MCONF,
+			   LTC4266_MCONF_INTERRUPT_ENABLE, 0);
+
+	/* Reset all the ports and do not re-init: a port reset removes power and
+	 * clears the port's detection and classification enables, leaving it in
+	 * semi-auto mode, which never powers a port without a host request.
+	 */
+	regmap_write(ltc4266->regmap, LTC4266_REG_RSTPB, LTC4266_RSTPB_RSTPORTS);
+}
+
+static int ltc4266_probe(struct i2c_client *client)
+{
+	struct ltc4266 *ltc4266;
+	struct regmap *regmap;
+	unsigned int id_reg;
+	int ret;
+
+	struct pse_irq_desc irq_desc = {
+		.name = "ltc4266-irq",
+		.map_event = ltc4266_map_event,
+	};
+
+	/* We need IRQ for static power budgeting and if don't have it, fail
+	 * probe early
+	 */
+	if (!client->irq)
+		return dev_err_probe(&client->dev, -EINVAL,
+				     "Interrupt is required for power budget management\n");
+
+	regmap = devm_regmap_init_i2c(client, &ltc4266_regmap_config);
+	if (IS_ERR(regmap))
+		return dev_err_probe(&client->dev, PTR_ERR(regmap),
+				     "Failed to allocate regmap\n");
+
+	/* Confirm we are talking to an LTC4266: the id register (0x1B) should
+	 * read back its documented reset value of 0x64.
+	 */
+	ret = regmap_read(regmap, LTC4266_REG_ID, &id_reg);
+	if (ret < 0)
+		return dev_err_probe(&client->dev, ret, "Failed to read ID register\n");
+
+	if (id_reg != LTC4266_ID)
+		return dev_err_probe(&client->dev, -ENODEV,
+				     "Expected an ID of 0x64, saw 0x%02X\n", id_reg);
+
+	/* Reset the chip */
+	ret = regmap_write(regmap, LTC4266_REG_RSTPB, LTC4266_RSTPB_INTCLR | LTC4266_RSTPB_RSTALL);
+	if (ret < 0)
+		return dev_err_probe(&client->dev, ret, "Failed to reset\n");
+
+	/* LTC4266 requires approximately 10 ms after reset to be stable; if it
+	 * isn't, then there is typically an undervoltage lockout/something pretty bad
+	 * going on. We give it 50 ms here so we don't need to poll the chip and use I2C bandwidth
+	 */
+	msleep(50);
+
+	/* Let's make sure the chip came out of reset (if not, the chip is probably
+	 * either (no longer?) present, in thermal shutdown, or watchdogged....either
+	 * way, there's nothing we can do in software to fix it)
+	 */
+	ret = regmap_read(regmap, LTC4266_REG_ID, &id_reg);
+	if (ret < 0)
+		return dev_err_probe(&client->dev, ret,
+				     "Failed to re-read ID register after reset\n");
+
+	if (id_reg != LTC4266_ID)
+		return dev_err_probe(&client->dev, -ENODEV,
+				     "Failed to re-read device ID after reset 0x%02X\n",
+				     id_reg);
+
+	ltc4266 = devm_kzalloc(&client->dev, sizeof(struct ltc4266), GFP_KERNEL);
+	if (!ltc4266)
+		return -ENOMEM;
+
+	ltc4266->client = client;
+	ltc4266->regmap = regmap;
+	ltc4266->np = client->dev.of_node;
+	ltc4266->dev = &client->dev;
+
+	/* After reset, the LTC4266 will interrupt with a (single) supply fault.
+	 * Clear it here and discard the result
+	 */
+	regmap_read(ltc4266->regmap, LTC4266_REG_SUPEVN_COR, &id_reg);
+
+	ret = ltc4266_disable_interrupts(ltc4266);
+	if (ret)
+		return dev_err_probe(&client->dev, ret,
+				     "Failed to disable interrupts\n");
+
+	/* Registered before the controller and the IRQ so devres ordering runs
+	 * it after free_irq() and pse_controller_unregister(), and so a failed
+	 * probe cannot leave ports running detection with no driver bound.
+	 */
+	ret = devm_add_action_or_reset(&client->dev, ltc4266_teardown, ltc4266);
+	if (ret)
+		return ret;
+
+	ltc4266->pcdev.owner = THIS_MODULE;
+	ltc4266->pcdev.ops = &ltc4266_ops;
+	ltc4266->pcdev.dev = &client->dev;
+	ltc4266->pcdev.types = ETHTOOL_PSE_C33;
+	ltc4266->pcdev.nr_lines = LTC4266_MAX_PORTS;
+	ltc4266->pcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_STATIC;
+
+	ret = devm_pse_controller_register(ltc4266->dev, &ltc4266->pcdev);
+	if (ret)
+		return dev_err_probe(&client->dev, ret,
+				     "Failed to register PSE controller\n");
+
+	/* Enable the interrupt pin, and only report detect events on
+	 * change (detchg) so idle ports continuously re-running
+	 * detection in semi-auto mode don't flood the host with a
+	 * detect event every cycle.
+	 */
+	ret = regmap_update_bits(ltc4266->regmap, LTC4266_REG_MCONF,
+				 LTC4266_MCONF_INTERRUPT_ENABLE | LTC4266_MCONF_DETCHG,
+				 LTC4266_MCONF_INTERRUPT_ENABLE | LTC4266_MCONF_DETCHG);
+	if (ret)
+		return dev_err_probe(&client->dev, ret,
+				     "Failed to configure interrupts\n");
+
+	ret = devm_pse_irq_helper(&ltc4266->pcdev, client->irq,
+				  0, &irq_desc);
+	if (ret)
+		return dev_err_probe(&client->dev, ret,
+				     "Failed to register PSE IRQ\n");
+
+	/* Unmask the chip interrupt sources now the handler is ready. */
+	ret = ltc4266_enable_interrupts(ltc4266);
+	if (ret)
+		return dev_err_probe(&client->dev, ret,
+				     "Failed to enable interrupts\n");
+
+	return 0;
+}
+
+static const struct i2c_device_id ltc4266_id[] = {
+	{.name = "ltc4266"},
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ltc4266_id);
+
+static const struct of_device_id ltc4266_of_match[] = {
+	{ .compatible = "lltc,ltc4266" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ltc4266_of_match);
+
+static struct i2c_driver ltc4266_driver = {
+	.driver		= {
+		.name	= "ltc4266",
+		.of_match_table = ltc4266_of_match,
+	},
+	.probe		= ltc4266_probe,
+	.id_table	= ltc4266_id,
+};
+module_i2c_driver(ltc4266_driver);
+
+MODULE_AUTHOR("Kyle Swenson <kyle.swenson@est.tech>");
+MODULE_DESCRIPTION("LTC4266 PoE PSE Controller Driver");
+MODULE_LICENSE("GPL");
-- 
2.55.0

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH net-next v3 1/2] dt-bindings: net: pse-pd: Add bindings for LTC4266 PSE Controller
  2026-09-27 21:29 ` [PATCH net-next v3 1/2] dt-bindings: net: pse-pd: Add bindings for LTC4266 PSE Controller Kyle Swenson
@ 2026-09-30 21:31   ` netdev-bot+sashiko
  0 siblings, 0 replies; 5+ messages in thread
From: netdev-bot+sashiko @ 2026-09-30 21:31 UTC (permalink / raw)
  To: kyle.swenson
  Cc: o.rempel, kory.maincent, andrew+netdev, davem, edumazet, kuba,
	pabeni, robh, krzk+dt, conor+dt, netdev, devicetree,
	linux-kernel, roland.kovacs, david.nystrom

Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.

Low priority / nit picks for your consideration:

- [Low] The binding does not describe the LTC4266's board-level control
  and supply resources.
- [Low] The commit message for "dt-bindings: net: pse-pd: Add bindings
  for LTC4266 PSE Controller" is one sentence ("Add the LTC4266 Power…
- [Low] The `pse-pi@[0-3]` subschema describes `pairsets` as "exactly one
  channel phandle is expected", and the `channels` description says the…
- [Low] `interrupts` is in the top-level `required` list of
  lltc,ltc4266.yaml (line 94).

Pre-existing issues:
- [Low] This problem wasn't introduced by this patch, but the base pse-
  controller.yaml (which lltc,ltc4266.yaml inherits) and the LTC4266…

--

dt-bindings: net: pse-pd: Add bindings for LTC4266 PSE Controller

This commit adds a YAML device tree binding for the Linear Technology
LTC4266 PSE controller. The binding describes the chip's four delivery
channels, each with a sense resistor value, and PSE PIs that each map to
one channel.

> dt-bindings: net: pse-pd: Add bindings for LTC4266 PSE Controller
>
> Add the LTC4266 Power Sourcing Equipment controller device tree bindings
> documentation.

[Severity: Low]
This isn't a bug, but the subject says "binding" twice.
Documentation/devicetree/bindings/submitting-patches.rst asks submitters
to avoid that:

  Repeating "binding" again should also be avoided, so for a new device it
  is often enough for example: "dt-bindings: iio: adc: Add ROHM BD79100G"

Could the body also describe the hardware, a 4-port 802.3at PSE on I2C?
Could it also explain the ABI choices that differ from the sibling PSE
bindings? These are:

- a new required per-channel sense-resistor-micro-ohms property with a
  fixed 250000/500000 enum
- channels and pse-pis are both required (ti,tps23881.yaml requires
  neither)
- each PI is limited to a single pairset

> diff --git a/Documentation/devicetree/bindings/net/pse-pd/lltc,ltc4266.yaml b/Documentation/devicetree/bindings/net/pse-pd/lltc,ltc4266.yaml
> new file mode 100644
> index 0000000000000..e11c9d601eee9
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/pse-pd/lltc,ltc4266.yaml
> @@ -0,0 +1,180 @@

[ ... ]

> +properties:
> +  compatible:
> +    enum:
> +      - lltc,ltc4266
> +
> +  reg:
> +    maxItems: 1
> +
> +  interrupts:
> +    maxItems: 1

[Severity: Low]
Should this list also have reset-gpios for the chip's active-low RESET
input? Should it have supply properties for the VDD and VEE rails?

The schema ends with unevaluatedProperties: false. A board DT that routes
RESET to a SoC GPIO, or names its VDD/VEE regulators, would fail
validation. writing-bindings.rst says:

  DO attempt to make bindings complete even if a driver doesn't support
  some features.

The sibling ti,tps23881.yaml in the same directory documents its reset
line:

  reset-gpios:
    maxItems: 1

The driver patch in this series ("net: pse-pd: Add LTC4266 PSE controller
driver") only resets the chip in software, through the RSTPB register in
ltc4266_probe():

  ret = regmap_write(regmap, LTC4266_REG_RSTPB, LTC4266_RSTPB_INTCLR | LTC4266_RSTPB_RSTALL);

Suppose a board's RESET GPIO is asserted by default. Won't the chip stay
in reset, so the ID read in probe fails? Nothing in the DT would let the
driver deassert the line.

[ ... ]

> +    patternProperties:
> +      '^pse-pi@[0-3]$':
> +        type: object
> +        additionalProperties: true
> +        properties:
> +          pairsets:
> +            description: The LTC4266 delivers power to a PI over a single
> +              pairset, driven by one of the controller's four channels. There
> +              is no 4-pair mode spreading a PI over two channels, so exactly
> +              one channel phandle is expected.
> +            maxItems: 1
> +          pairset-names:
> +            maxItems: 1

[Severity: Low]
The description says exactly one channel phandle is expected. Should
pairsets and pairset-names also be marked required in this subschema?

The only required entries inherited from pse-controller.yaml are reg and
#pse-cells. The PSE core's of_load_pse_pis() accepts a pse-pi that has no
pairsets. It takes the -ENOENT branch and leaves pairset[0].np NULL:

  } else if (ret != -ENOENT) {

The driver patch in this series then rejects that case in
ltc4266_map_pis():

  if (!pi->pairset[0].np)
          return dev_err_probe(ltc4266->dev, -EINVAL,
                               "%pOF has no pairsets\n", pi->np);

So there is no NULL dereference. However, a DT with a PI that has no
channel mapping passes dtbs_check and then makes pse_controller_register()
fail. That takes down all four ports.

Would adding this to the pse-pi subschema close the gap?

  required:
    - pairsets
    - pairset-names

[Severity: Low]
This is a pre-existing issue and was not introduced by this patch.
Neither this subschema nor the base pse-controller.yaml requires pairsets
and pairset-names to appear together, for example with dependentRequired.

of_load_pse_pi_pairsets() in drivers/net/pse-pd/pse_core.c checks this at
runtime:

  ret = of_property_count_strings(node, "pairset-names");
  if (ret != npairsets) {
          pr_err(...);
          return -EINVAL;
  }

So a node with pairsets = <&phys0>; and no pairset-names passes schema
validation and then fails controller registration.

ti,tps23881.yaml and skyworks,si3474.yaml have the same gap. Would this be
better fixed once in pse-controller.yaml?

> +
> +required:
> +  - compatible
> +  - reg
> +  - interrupts
> +  - channels
> +  - pse-pis

[Severity: Low]
Does interrupts need to be required? The requirement seems to come from
the driver rather than the hardware. Without an IRQ, the driver patch in
this series fails probe in ltc4266_probe():

  if (!client->irq)
          return dev_err_probe(&client->dev, -EINVAL,
                               "Interrupt is required for power budget management\n");

On the LTC4266, INT is an open-drain output that summarises the event and
status registers. Those registers can be read over I2C, so the chip can
also be managed by polling. writing-bindings.rst says:

  - DON'T refer to Linux or "device driver" in bindings. Bindings should
    be based on what the hardware has, not what an OS and driver
    currently support.

A board that leaves INT unconnected is valid hardware, but it would fail
dtbs_check. For comparison, skyworks,si3474.yaml in the same directory
does not require interrupts, while ti,tps23881.yaml does.

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260927212929.593366-1-kyle.swenson%40est.tech

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH net-next v3 2/2] net: pse-pd: Add LTC4266 PSE controller driver
  2026-09-27 21:29 ` [PATCH net-next v3 2/2] net: pse-pd: Add LTC4266 PSE controller driver Kyle Swenson
@ 2026-09-30 21:31   ` netdev-bot+sashiko
  0 siblings, 0 replies; 5+ messages in thread
From: netdev-bot+sashiko @ 2026-09-30 21:31 UTC (permalink / raw)
  To: kyle.swenson
  Cc: o.rempel, kory.maincent, andrew+netdev, davem, edumazet, kuba,
	pabeni, robh, krzk+dt, conor+dt, netdev, devicetree,
	linux-kernel, roland.kovacs, david.nystrom

Thank you for your contribution! Sashiko AI review found 9 potential
issue(s) to consider.

Critical: 0 · High: 1 (2) · Medium: 6 (3) · Low: 2

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [High] If a PI's vpwr-supply is described in DT but not yet registered
  when the LTC4266 probes, ltc4266_pi_nominal_uv() carries on (warn plus…
- [Medium] ltc4266_pi_get_pw_class() reports the wrong power class to
  userspace.
- [Medium] ltc4266_pi_nominal_uv() swallows -EPROBE_DEFER from
  of_regulator_get_optional().
- [Medium] ltc4266_map_event() loses hardware events on every error exit.
- [Medium] ltc4266_pi_set_pw_limit() treats every negative
  ltc4266_port_get_class() result as 'not classified', including…
- [Medium] ltc4266_map_event() reports
  ETHTOOL_C33_PSE_EVENT_CLASSIFICATION whenever the detevn high nibble…
- [Medium] ltc4266_pi_disable() is carried out by ltc4266_port_init(),
  whose first write (RSTPB = BIT(chan)) removes power from the port.
- [Low] The LTC4266 binding sets maxItems: 1 for pse-pi 'pairsets' but
  does not list it as required, and pse-controller.yaml requires only…
- [Low] ltc4266_regmap_config sets only reg_bits, val_bits and
  max_register (0x5F), with no precious_reg or readable_reg callbacks.

Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but the new driver
  makes it easy to hit.
- [High] This problem wasn't introduced by this patch, but the new driver
  opts into the affected path.
- [Medium] This problem wasn't introduced by this patch, but: when
  ops->setup_pi_matrix() fails, pse_controller_register() returns…
- [Medium] This problem wasn't introduced by this patch, but:
  pse_controller_unregister() calls pse_release_pis() (kfree(pcdev->pi))…
- [Medium] This problem wasn't introduced by this patch, but:
  pse_control_get_internal() calls try_module_get(pcdev->owner) and…

--

net: pse-pd: Add LTC4266 PSE controller driver

This commit adds a new I2C driver for the Linear Technology LTC4266 PoE PSE
controller and hooks it into the PSE controller core. The driver runs the
chip in semi-auto mode and maps PSE PIs to delivery channels from the
device tree. It uses the chip interrupt together with the static power
budget evaluation strategy.

> diff --git a/drivers/net/pse-pd/ltc4266.c b/drivers/net/pse-pd/ltc4266.c
> new file mode 100644
> index 0000000000000..37dea467811a4
> --- /dev/null
> +++ b/drivers/net/pse-pd/ltc4266.c
> @@ -0,0 +1,1386 @@

[ ... ]

> +static int ltc4266_pi_disable(struct pse_controller_dev *pcdev, int id)
> +{
> +	struct ltc4266_port *port = ltc4266_pi_port(pcdev, id);
> +
> +	/* Resetting the port (RSTPB, issued at the start of ltc4266_port_init)
> +	 * removes power, disables detection and classification, and clears the
> +	 * port's status register. Re-init the port so that detection and
> +	 * classification can happen again.
> +	 */
> +	return ltc4266_port_init(port);
> +}

[Severity: Medium]
What happens if one of the writes after the RSTPB reset in
ltc4266_port_init() fails?

The first write in ltc4266_port_init() has already removed power from the
port. A later failure in the OPMD, HPEN, HPMD, TLIM, DISENA or DETPB write
is still returned as a failed disable, and _pse_pi_disable() then skips
the budget release:

drivers/net/pse-pd/pse_core.c:_pse_pi_disable() {
    ...
	ret = ops->pi_disable(pcdev, id);
	if (ret)
		return ret;

	pse_pi_deallocate_pw_budget(&pcdev->pi[id]);
    ...
}

Would the core then keep budget allocated and admin_state_enabled set for
a port that has no power? DETPB was never written, so detection stays off.
No later classification interrupt would arrive to resync the state.

[ ... ]

> +static int ltc4266_pi_get_admin_state(struct pse_controller_dev *pcdev, int id,
> +				      struct pse_admin_state *admin_state)
> +{

[ ... ]

> +	ret = regmap_read(port->ltc4266->regmap, LTC4266_REG_STATPWR, &val);
> +	if (ret < 0)
> +		return ret;

[Severity: Medium]
This isn't a bug introduced by this patch, but an I2C error returned here
reaches a module reference leak that already exists in the PSE core.
pse_control_get_internal() calls try_module_get(pcdev->owner) and then
does this:

drivers/net/pse-pd/pse_core.c:pse_control_get_internal() {
    ...
	ret = pse_pi_is_hw_enabled(pcdev, index);
	if (ret < 0)
		goto free_psec;
    ...
}

That jump skips the put_module label. If an I2C error happens while a PHY
is acquiring its PSE control, does the ltc4266 module stay pinned?

[ ... ]

> +static int ltc4266_pi_get_pw_class(struct pse_controller_dev *pcdev, int id)
> +{
> +	int ret = ltc4266_port_get_class(ltc4266_pi_port(pcdev, id));
> +

[ ... ]

> +	if (ret == 0)
> +		ret = 3;
> +	if (ret < 0)
> +		ret = 0;
> +	return ret;
> +}

[Severity: Medium]
Does this report the wrong power class to userspace?

A PD that has a valid detection signature but no class signature (statp
class code 0x6, which is class 0 in ltc4266_class_map[]) is reported as
class 3.

Every negative result from ltc4266_port_get_class() becomes 0. That
covers regmap I2C errors, -EAGAIN (classification not finished), -EINVAL
(detection not good) and -ERANGE.

pse_ethtool_get_status() stores the value in status->c33_pw_class, and
pse_fill_reply() only emits it when it is greater than zero:

net/ethtool/pse-pd.c:pse_fill_reply() {
    ...
	if (st->c33_pw_class > 0 &&
	    nla_put_u32(skb, ETHTOOL_A_C33_PSE_PW_CLASS,
    ...
}

So ethtool --show-pse would show class 3 for a class 0 PD, and an I2C
failure would look the same as no PD at all. Other drivers, such as
tps23881_pi_get_pw_class(), return the raw class and propagate errors.

ltc4266_class_pw[] and pi_get_pw_req already treat class 0 like class 3
for power budgeting. Could this callback report the real class instead?
If the rewrite stays, could the commit message mention it?

[ ... ]

> +static int ltc4266_pi_set_pw_limit(struct pse_controller_dev *pcdev,
> +				   int id, int max_mw)
> +{

[ ... ]

> +	class = ltc4266_port_get_class(port);
> +	if (class >= 0) {
> +		icut = ltc4266_pw_limit_to_icut(port, max_mw, class);
> +		if (icut < 0)
> +			return icut;
> +
> +		ret = ltc4266_port_set_icut(port, icut);
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	port->pw_limit = max_mw;
> +
> +	return 0;

[Severity: Medium]
Should a regmap_read() failure inside ltc4266_port_get_class() be treated
the same as "not classified" here?

On a powered port, a transient I2C error skips ltc4266_port_set_icut(),
but port->pw_limit is still updated and 0 is returned.
pse_ethtool_set_pw_limit() has already updated the power budget and only
rolls it back on an error return. The core budget and the reported limit
would then show the new value while the hardware keeps enforcing the old
I_CUT.

[ ... ]

> +static int ltc4266_pi_nominal_uv(struct ltc4266 *ltc4266, struct device_node *np)
> +{
> +	struct regulator *vpwr;
> +	int uv;
> +
> +	vpwr = of_regulator_get_optional(ltc4266->dev, np, "vpwr");
> +	if (IS_ERR(vpwr)) {
> +		/* -ENODEV means the PI describes no vpwr-supply at all, which
> +		 * is the case the fallback exists for. -EPROBE_DEFER means the
> +		 * rail _is_ described but has not registered yet, so the
> +		 * fallback is wrong for it. Asking for a probe retry is not an
> +		 * option from here: we run from setup_pi_matrix(), and
> +		 * pse_controller_register() unwinds neither its notification
> +		 * fifo nor its pse_pi array when that fails, so every retry
> +		 * would leak. Warn instead so the assumed voltage is visible.
> +		 */
> +		if (PTR_ERR(vpwr) == -EPROBE_DEFER)
> +			dev_warn(ltc4266->dev,
> +				 "%pOF: vpwr-supply is not registered yet, assuming %d uV\n",
> +				 np, LTC4266_VPORT_NOMINAL_UV);
> +
> +		return LTC4266_VPORT_NOMINAL_UV;
> +	}

[Severity: Medium]
Does swallowing -EPROBE_DEFER here actually avoid the leak described in
the comment?

ltc4266_setup_pi_matrix() still has many other error returns that go
through the same unwind gap in pse_controller_register(): DT validation in
ltc4266_get_of_channels() and ltc4266_map_pis(), -ENOMEM, and I2C errors
from ltc4266_port_init().

drivers/net/pse-pd/pse_core.c:pse_controller_register() {
    ...
	if (pcdev->ops->setup_pi_matrix) {
		ret = pcdev->ops->setup_pi_matrix(pcdev);
		if (ret)
			return ret;
	}
    ...
}

In the deferred case, the 50 V nominal voltage stays for the whole life
of the binding. ltc4266_pw_limit_to_icut() uses it whenever an admin limit
is set on an unpowered port.

The deferral can happen in practice. fw_devlink only creates
SYNC_STATE_ONLY links for suppliers referenced from child nodes, such as
the pse-pi nodes, so the supply does not hold back this probe.

Could the unwind in pse_controller_register() be fixed in a prerequisite
patch? Alternatively, the supplies could be resolved before
devm_pse_controller_register() so that probe can return -EPROBE_DEFER.

[Severity: High]
Continuing with a deferred vpwr-supply also has a functional effect. Can
the PI end up with no PSE power domain for good?

If the supply is not registered yet, regulator_resolve_supply() returns
-EPROBE_DEFER and regulator_register() leaves rdev->supply NULL for the PI
regulator. pse_register_pw_ds() runs only once, from
pse_controller_register():

drivers/net/pse-pd/pse_core.c:pse_register_pw_ds() {
    ...
		if (!rdev || !rdev->supply)
			continue;
    ...
}

So pi->pw_d stays NULL even after the supply resolves, and
pse_pw_d_is_sw_pw_control() returns false for this PI. Two things follow.

First, pse_isr() never calls pse_set_config_isr(). The CLASSIFICATION and
DISCONNECTION events from ltc4266_map_event() then never power the port
or re-arm it.

Second, on admin enable pse_pi_enable() calls ltc4266_pi_enable()
directly. That fails in ltc4266_port_get_class() with -EINVAL or -EAGAIN
when no PD has been classified.

The chip runs in semi-auto mode and never powers a port without a PWRPB
write. Would a PD that is plugged in later stay unpowered until the
driver is rebound?

This only affects a supply that is described but deferred. When
vpwr-supply is missing entirely, the dummy regulator is used and a power
domain is still created.

[ ... ]

> +		if (!pi->pairset[0].np)
> +			return dev_err_probe(ltc4266->dev, -EINVAL,
> +					     "%pOF has no pairsets\n", pi->np);

[Severity: Low]
The lltc,ltc4266.yaml binding gives pairsets maxItems: 1 for each pse-pi
node but does not list it as required. pse-controller.yaml only requires
reg and #pse-cells, and of_load_pse_pis() also accepts a PI without
pairsets.

Should the binding list pairsets as required? Otherwise a DT that passes
dt_binding_check fails probe here.

[ ... ]

> +	ret = ltc4266_get_of_channels(ltc4266, chans);
> +	if (!ret)
> +		ret = ltc4266_map_pis(ltc4266, chans);
> +
> +	for (i = 0; i < LTC4266_MAX_PORTS; i++)
> +		of_node_put(chans[i].np);
> +
> +	if (ret)
> +		return ret;

[Severity: Medium]
This is a pre-existing issue, but every error return from
ltc4266_setup_pi_matrix() runs into it.

When setup_pi_matrix() fails, pse_controller_register() returns without
calling kfifo_free(&pcdev->ntf_fifo) or pse_release_pis(), and
devm_pse_controller_register() only calls devres_free(). Would each
failed probe leak the pcdev->pi array and the of_node references on the
PI and pairset nodes?

[ ... ]

> +		if (detevn & LTC4266_EVN_HI(chan)) {
> +			int class = ltc4266_port_get_class(port);
> +
> +			if (class >= 0) {
> +				notifs[i] |= ETHTOOL_C33_PSE_EVENT_CLASSIFICATION;
> +				*notifs_mask |= BIT(i);
> +			}
> +		}

[Severity: Medium]
Should this check whether the port is already powered before reporting a
classification event?

For an admin-enabled PI, pse_set_config_isr() calls
_pse_pi_delivery_power_sw_pw_ctrl() on every CLASSIFICATION event.
pse_pi_allocate_pw_budget_static_prio() does not check for an existing
allocation: it requests budget again and overwrites pi->pw_allocated_mW.
ltc4266_pi_enable() then rewrites ILIM, ICUT and PWRPB on the live port.

Here is one way this could happen. pse_pi_enable() powers the port while
holding pcdev->lock, and a class event is latched at the same time. The
IRQ thread waits for pcdev->lock and then reads the stale DETEVN for a
port that is now powered. A classification that completes between the
DETEVN_COR read and the PWRPB write in the ISR would have the same
effect.

Could this leak the first budget allocation and shut off lower-priority
ports for no reason? It depends on whether the chip raises a new class
event for an unchanged result while MCONF.detchg is set.

> +	}
> +
> +done:
> +	return ltc4266_enable_interrupts(ltc4266);
> +
> +err:
> +	/* (Attempt to) clear any remaining event registers that we might've
> +	 * missed in the event a previous read has failed.
> +	 */
> +	ret = regmap_write(ltc4266->regmap, LTC4266_REG_RSTPB, LTC4266_RSTPB_INTCLR);
> +	if (ret)
> +		dev_warn(&ltc4266->client->dev, "Failed to clear pending interrupts, err=%d\n",
> +			 ret);
> +
> +	return ltc4266_enable_interrupts(ltc4266);
> +}

[Severity: Medium]
Can this error path lose hardware events?

PWREVN_COR, FLTEVN_COR, TSEVN_COR and DETEVN_COR clear on read. When a
later read fails, the latches read before it (for example a tCUT bit in
fltevn or a PG-lost bit in pwrevn) have already been cleared, and their
values are thrown away.

The RSTPB_INTCLR write then clears the latched events on every channel,
including ones that were never read. ltc4266_enable_interrupts() normally
returns 0, so pse_isr() sees success with an empty mask and does nothing.

The reverse case is also a problem. If the INTMASK write in
ltc4266_enable_interrupts() fails after decoding, pse_isr() throws away
the notifications because of the nonzero return:

drivers/net/pse-pd/pse_core.c:pse_isr() {
    ...
	ret = desc->map_event(irq, pcdev, h->notifs, h->notifs_mask);
	if (ret || bitmap_empty(h->notifs_mask, pcdev->nr_lines)) {
		mutex_unlock(&pcdev->lock);
		return IRQ_NONE;
    ...
}

INTMASK also stays at 0 from the ltc4266_disable_interrupts() call at
entry, so no further interrupts would arrive.

With detchg set, a lost classification is not raised again. A lost fault
or PG-loss leaves the core thinking the port is delivering, with its
budget still allocated.

Is there any way for the driver to resync after one I2C error in the IRQ
thread?

> +
> +static const struct regmap_config ltc4266_regmap_config = {
> +	.reg_bits = 8,
> +	.val_bits = 8,
> +	.max_register = 0x5F,
> +};

[Severity: Low]
Should a precious_reg callback mark the clear-on-read event registers
(0x03, 0x05, 0x07, 0x09, 0x0B) as precious? Without it, a regmap debugfs
registers dump reads them and clears pending detect, class and fault
latches before the IRQ handler sees them. A readable_reg callback would
also stop the dump from reading the reserved addresses in the gaps of the
register map.

[ ... ]

> +	ltc4266->pcdev.owner = THIS_MODULE;
> +	ltc4266->pcdev.ops = &ltc4266_ops;
> +	ltc4266->pcdev.dev = &client->dev;
> +	ltc4266->pcdev.types = ETHTOOL_PSE_C33;
> +	ltc4266->pcdev.nr_lines = LTC4266_MAX_PORTS;
> +	ltc4266->pcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_STATIC;

[Severity: High]
This isn't a bug introduced by this patch, but choosing the static budget
strategy exposes a lock ordering problem that already exists in the PSE
core.

pse_isr() holds pcdev->lock and ends up taking the supply regulator lock:

pse_isr()
  pse_set_config_isr()
    _pse_pi_delivery_power_sw_pw_ctrl()
      pse_pi_allocate_pw_budget_static_prio()
        regulator_request_power_budget(pw_d->supply)
          regulator_lock(rdev)

An admin enable running at the same time takes the locks in the opposite
order:

regulator_enable(psec->ps)
  regulator_lock_dependent()    (PI rdev, then its supply rdev)
    _regulator_enable()
      pse_pi_is_enabled() / pse_pi_enable()
        mutex_lock(&pcdev->lock)

Can a classification interrupt on one PI, racing with an admin enable of
another PI on the same supply, deadlock both the IRQ thread and the
ethtool task? The ww_mutex scheme cannot break a cycle that includes the
plain pcdev->lock mutex.

> +
> +	ret = devm_pse_controller_register(ltc4266->dev, &ltc4266->pcdev);
> +	if (ret)
> +		return dev_err_probe(&client->dev, ret,
> +				     "Failed to register PSE controller\n");

[Severity: High]
This is a pre-existing issue in the PSE core, but boards with more than
one LTC4266 are likely to hit it.

devm_pse_alloc_pw_d() allocates the power domain with
devm_kzalloc(pcdev->dev) and publishes it in the global pse_pw_d_map.
pse_register_pw_ds() then gives the same object to any other controller
whose PI supply matches:

drivers/net/pse-pd/pse_core.c:pse_register_pw_ds() {
    ...
			if (regulator_is_equal(pw_d->supply, rdev->supply)) {
				present = true;
				pcdev->pi[i].pw_d = pw_d;
    ...
}

Consider all PIs pointing at one shared supply, as in the binding
example, or sharing the dummy regulator when vpwr-supply is omitted.
Unbinding the first controller frees the memory through devres, but the
xarray entry and the other controller's pointers remain.

Would the second controller's pse_flush_pw_ds() then read pw_d->id and
call kref_put_mutex() on freed memory? pse_isr()->
pse_pw_d_is_sw_pw_control() would use the same dangling pointer.

[Severity: Medium]
This is a pre-existing issue, but the teardown order in
pse_controller_unregister() looks unsafe:

drivers/net/pse-pd/pse_core.c:pse_controller_unregister() {
    ...
	pse_flush_pw_ds(pcdev);
	pse_release_pis(pcdev);
	if (pcdev->irq)
		disable_irq(pcdev->irq);
	cancel_work_sync(&pcdev->ntf_work);
    ...
}

pse_send_ntf_worker() takes a psec reference with
pse_control_find_by_id() and drops it with pse_control_put(). If that is
the last reference, __pse_control_release() reads
psec->pcdev->pi[psec->id].admin_state_enabled after pse_release_pis() has
freed the array.

Could the work be cancelled before the PI array is released?

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260927212929.593366-1-kyle.swenson%40est.tech

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-09-30 21:31 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-27 21:29 [PATCH net-next v3 0/2] net: pse-pd: Add LTC4266 PSE controller driver Kyle Swenson
2026-09-27 21:29 ` [PATCH net-next v3 1/2] dt-bindings: net: pse-pd: Add bindings for LTC4266 PSE Controller Kyle Swenson
2026-09-30 21:31   ` netdev-bot+sashiko
2026-09-27 21:29 ` [PATCH net-next v3 2/2] net: pse-pd: Add LTC4266 PSE controller driver Kyle Swenson
2026-09-30 21:31   ` netdev-bot+sashiko

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

all inboxes | Powered by JetHome®