[openwrt/openwrt] kernel: net: pse-pd: backport PSE v6.13-v6.19

LEDE Commits lede-commits at lists.infradead.org
Sat Feb 7 15:21:11 PST 2026


hauke pushed a commit to openwrt/openwrt.git, branch main:
https://git.openwrt.org/528c9259a70f3a94595cd860b97913fbe913ed39

commit 528c9259a70f3a94595cd860b97913fbe913ed39
Author: Carlo Szelinsky <github at szelinsky.de>
AuthorDate: Sat Jan 31 13:19:00 2026 +0100

    kernel: net: pse-pd: backport PSE v6.13-v6.19
    
    Backport the PSE-PD (Power Sourcing Equipment - Powered Device)
    framework updates from Linux 6.13 through 6.19. This brings modern
    PoE (Power over Ethernet) controller support to OpenWrt, enabling
    userspace control of PSE hardware via ethtool.
    
    Key features:
    - Enhanced ethtool integration for PSE status and configuration
    - Power domain support with budget evaluation strategies
    - PSE event reporting via netlink
    - Port priority management for power budget allocation
    - New Si3474 PSE controller driver
    
    Backported commits:
    
    v6.13 core framework and TPS23881 improvements:
    - 6e56a6d47a7f net: pse-pd: Add power limit check
    - 0b567519d115 net: pse-pd: tps23881: Simplify function returns
    - 4c2bab507eb7 net: pse-pd: tps23881: Use helpers to calculate bit offset
    - f3cb3c7bea0c net: pse-pd: tps23881: Add missing configuration register
    - 3e9dbfec4998 net: pse-pd: Split ethtool_get_status
      into multiple callbacks
    - 4640a1f0d8f2 net: pse-pd: Remove is_enabled callback from drivers
    - 7f076ce3f173 net: pse-pd: tps23881: Add power limit
      and measurement features
    - 10276f3e1c7e net: pse-pd: Fix missing PI of_node description
    - 5385f1e1923c net: pse-pd: Clean ethtool header of PSE structures
    
    v6.17 power domains and event support:
    - fa2f0454174c net: pse-pd: Introduce attached_phydev to pse control
    - fc0e6db30941 net: pse-pd: Add support for reporting events
    - f5e7aecaa4ef net: pse-pd: tps23881: Add support for PSE events
    - 50f8b341d268 net: pse-pd: Add support for PSE power domains
    - 1176978ed851 net: ethtool: Add support for power domains index
    - c394e757dedd net: pse-pd: Add helper to report hw enable status
    - ffef61d6d273 net: pse-pd: Add support for budget evaluation strategies
    - 359754013e6a net: pse-pd: pd692x0: Add PSE PI priority feature
    - 24a4e3a05dd0 net: pse-pd: pd692x0: Add controller and manager power
    - 56cfc97635e9 net: pse-pd: tps23881: Add static port priority feature
    - d12b3dc10609 net: pse-pd: pd692x0: reduce stack usage
    
    v6.18 Si3474 driver and fixes:
    - 1c67f9c54cdc net: pse-pd: pd692x0: Fix power budget leak
    - 7ef353879f71 net: pse-pd: pd692x0: Skip power budget when undefined
    - a2317231df4b net: pse-pd: Add Si3474 PSE controller driver
    
    v6.19 maintenance and TPS23881B support:
    - 2c95a756e0cf net: pse-pd: tps23881: Fix current measurement scaling
    - f197902cd21a net: pse-pd: pd692x0: Replace __free macro
    - 6fa1f8b64a47 net: pse-pd: pd692x0: Separate configuration parsing
    - 8f3d044b34fe net: pse-pd: pd692x0: Preserve PSE configuration
    - 4d07797faaa1 net: pse-pd: tps23881: Add support for TPS23881B
    
    Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
    Link: https://github.com/openwrt/openwrt/pull/21810
    Signed-off-by: Hauke Mehrtens <hauke at hauke-m.de>
---
 ...01-v6.13-net-pse-pd-Add-power-limit-check.patch |   41 +
 ...pse-pd-tps23881-Simplify-function-returns.patch |  112 ++
 ...23881-Use-helpers-to-calculate-bit-offset.patch |  190 ++++
 ...s23881-Add-missing-configuration-register.patch |   63 ++
 ...thtool_get_status-into-multiple-callbacks.patch |  714 +++++++++++++
 ...d-Remove-is_enabled-callback-from-drivers.patch |  184 ++++
 ...-Add-power-limit-and-measurement-features.patch |  337 ++++++
 ...pse-pd-Fix-missing-PI-of_node-description.patch |   28 +
 ...pd-Clean-ethtool-header-of-PSE-structures.patch |   92 ++
 ...-Introduce-attached_phydev-to-pse-control.patch |  171 +++
 ...t-pse-pd-Add-support-for-reporting-events.patch |  363 +++++++
 ...se-pd-tps23881-Add-support-for-PSE-events.patch |  252 +++++
 ...-pse-pd-Add-support-for-PSE-power-domains.patch |  218 ++++
 ...htool-Add-support-for-power-domains-index.patch |   78 ++
 ...-pd-Add-helper-to-report-hw-enable-status.patch |   74 ++
 ...-support-for-budget-evaluation-strategies.patch | 1104 ++++++++++++++++++++
 ...se-pd-pd692x0-Add-PSE-PI-priority-feature.patch |  326 ++++++
 ...-pd692x0-Add-controller-and-manager-power.patch |   71 ++
 ...tps23881-Add-static-port-priority-feature.patch |  392 +++++++
 ....17-net-pse-pd-pd692x0-reduce-stack-usage.patch |   55 +
 ...-net-pse-pd-pd692x0-Fix-power-budget-leak.patch |  121 +++
 ...-pd692x0-Skip-power-budget-when-undefined.patch |   37 +
 ...t-pse-pd-Add-Si3474-PSE-controller-driver.patch |  636 +++++++++++
 ...-tps23881-Fix-current-measurement-scaling.patch |   31 +
 ...9-net-pse-pd-pd692x0-Replace-__free-macro.patch |   57 +
 ...pd-pd692x0-Separate-configuration-parsing.patch |  291 ++++++
 ...pse-pd-pd692x0-Preserve-PSE-configuration.patch |  111 ++
 ...pse-pd-tps23881-Add-support-for-TPS23881B.patch |  170 +++
 ...e-pd-Add-ethtool_netlink_generated-header.patch |   80 ++
 ...io-increase-max-ports-for-rtl839x-rtl931x.patch |    2 +-
 30 files changed, 6400 insertions(+), 1 deletion(-)

diff --git a/target/linux/generic/backport-6.12/626-01-v6.13-net-pse-pd-Add-power-limit-check.patch b/target/linux/generic/backport-6.12/626-01-v6.13-net-pse-pd-Add-power-limit-check.patch
new file mode 100644
index 0000000000..fbcc3ec3e1
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-01-v6.13-net-pse-pd-Add-power-limit-check.patch
@@ -0,0 +1,41 @@
+From 6e56a6d47a7fad705a1a1d088237b0858c01a770 Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent at bootlin.com>
+Date: Fri, 10 Jan 2025 10:40:22 +0100
+Subject: [PATCH] net: pse-pd: Add power limit check
+
+Checking only the current limit is not sufficient. According to the
+standard, voltage can reach up to 57V and current up to 1.92A, which
+exceeds the power limit described in the standard (99.9W). Add a power
+limit check to prevent this.
+
+Acked-by: Oleksij Rempel <o.rempel at pengutronix.de>
+Signed-off-by: Kory Maincent <kory.maincent at bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni at redhat.com>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/pse_core.c | 3 +++
+ include/linux/pse-pd/pse.h    | 2 ++
+ 2 files changed, 5 insertions(+)
+--- a/drivers/net/pse-pd/pse_core.c
++++ b/drivers/net/pse-pd/pse_core.c
+@@ -868,6 +868,9 @@ int pse_ethtool_set_pw_limit(struct pse_
+ 	int uV, uA, ret;
+ 	s64 tmp_64;
+ 
++	if (pw_limit > MAX_PI_PW)
++		return -ERANGE;
++
+ 	ret = regulator_get_voltage(psec->ps);
+ 	if (!ret) {
+ 		NL_SET_ERR_MSG(extack,
+--- a/include/linux/pse-pd/pse.h
++++ b/include/linux/pse-pd/pse.h
+@@ -11,6 +11,8 @@
+ 
+ /* Maximum current in uA according to IEEE 802.3-2022 Table 145-1 */
+ #define MAX_PI_CURRENT 1920000
++/* Maximum power in mW according to IEEE 802.3-2022 Table 145-16 */
++#define MAX_PI_PW 99900
+ 
+ struct phy_device;
+ struct pse_controller_dev;
diff --git a/target/linux/generic/backport-6.12/626-02-v6.13-net-pse-pd-tps23881-Simplify-function-returns.patch b/target/linux/generic/backport-6.12/626-02-v6.13-net-pse-pd-tps23881-Simplify-function-returns.patch
new file mode 100644
index 0000000000..fc6d532298
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-02-v6.13-net-pse-pd-tps23881-Simplify-function-returns.patch
@@ -0,0 +1,112 @@
+From 0b567519d1152de52b29b2da2c47aa0f39a46266 Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent at bootlin.com>
+Date: Fri, 10 Jan 2025 10:40:23 +0100
+Subject: [PATCH] net: pse-pd: tps23881: Simplify function returns by removing
+ redundant checks
+
+Cleaned up several functions in tps23881 by removing redundant checks on
+return values at the end of functions. These check has been removed, and
+the return statement now directly returns the function result, reducing
+the code's complexity and making it more concise.
+
+Reviewed-by: Andrew Lunn <andrew at lunn.ch>
+Acked-by: Oleksij Rempel <o.rempel at pengutronix.de>
+Reviewed-by: Kyle Swenson <kyle.swenson at est.tech>
+Signed-off-by: Kory Maincent <kory.maincent at bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni at redhat.com>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/tps23881.c | 34 ++++++----------------------------
+ 1 file changed, 6 insertions(+), 28 deletions(-)
+--- a/drivers/net/pse-pd/tps23881.c
++++ b/drivers/net/pse-pd/tps23881.c
+@@ -59,7 +59,6 @@ static int tps23881_pi_enable(struct pse
+ 	struct i2c_client *client = priv->client;
+ 	u8 chan;
+ 	u16 val;
+-	int ret;
+ 
+ 	if (id >= TPS23881_MAX_CHANS)
+ 		return -ERANGE;
+@@ -78,11 +77,7 @@ static int tps23881_pi_enable(struct pse
+ 			val |= BIT(chan + 4);
+ 	}
+ 
+-	ret = i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
+-	if (ret)
+-		return ret;
+-
+-	return 0;
++	return i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
+ }
+ 
+ static int tps23881_pi_disable(struct pse_controller_dev *pcdev, int id)
+@@ -91,7 +86,6 @@ static int tps23881_pi_disable(struct ps
+ 	struct i2c_client *client = priv->client;
+ 	u8 chan;
+ 	u16 val;
+-	int ret;
+ 
+ 	if (id >= TPS23881_MAX_CHANS)
+ 		return -ERANGE;
+@@ -110,11 +104,7 @@ static int tps23881_pi_disable(struct ps
+ 			val |= BIT(chan + 8);
+ 	}
+ 
+-	ret = i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
+-	if (ret)
+-		return ret;
+-
+-	return 0;
++	return i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
+ }
+ 
+ static int tps23881_pi_is_enabled(struct pse_controller_dev *pcdev, int id)
+@@ -480,7 +470,7 @@ tps23881_write_port_matrix(struct tps238
+ 	struct i2c_client *client = priv->client;
+ 	u8 pi_id, lgcl_chan, hw_chan;
+ 	u16 val = 0;
+-	int i, ret;
++	int i;
+ 
+ 	for (i = 0; i < port_cnt; i++) {
+ 		pi_id = port_matrix[i].pi_id;
+@@ -511,11 +501,7 @@ tps23881_write_port_matrix(struct tps238
+ 	}
+ 
+ 	/* Write hardware ports matrix */
+-	ret = i2c_smbus_write_word_data(client, TPS23881_REG_PORT_MAP, val);
+-	if (ret)
+-		return ret;
+-
+-	return 0;
++	return i2c_smbus_write_word_data(client, TPS23881_REG_PORT_MAP, val);
+ }
+ 
+ static int
+@@ -564,11 +550,7 @@ tps23881_set_ports_conf(struct tps23881_
+ 			val |= BIT(port_matrix[i].lgcl_chan[1]) |
+ 			       BIT(port_matrix[i].lgcl_chan[1] + 4);
+ 	}
+-	ret = i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val);
+-	if (ret)
+-		return ret;
+-
+-	return 0;
++	return i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val);
+ }
+ 
+ static int
+@@ -594,11 +576,7 @@ tps23881_set_ports_matrix(struct tps2388
+ 	if (ret)
+ 		return ret;
+ 
+-	ret = tps23881_set_ports_conf(priv, port_matrix);
+-	if (ret)
+-		return ret;
+-
+-	return 0;
++	return tps23881_set_ports_conf(priv, port_matrix);
+ }
+ 
+ static int tps23881_setup_pi_matrix(struct pse_controller_dev *pcdev)
diff --git a/target/linux/generic/backport-6.12/626-03-v6.13-net-pse-pd-tps23881-Use-helpers-to-calculate-bit-offset.patch b/target/linux/generic/backport-6.12/626-03-v6.13-net-pse-pd-tps23881-Use-helpers-to-calculate-bit-offset.patch
new file mode 100644
index 0000000000..3b390ea565
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-03-v6.13-net-pse-pd-tps23881-Use-helpers-to-calculate-bit-offset.patch
@@ -0,0 +1,190 @@
+From 4c2bab507eb7edc8e497e91b9b7f05d76d7e32bb Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent at bootlin.com>
+Date: Fri, 10 Jan 2025 10:40:24 +0100
+Subject: [PATCH] net: pse-pd: tps23881: Use helpers to calculate bit offset
+ for a channel
+
+This driver frequently follows a pattern where two registers are read or
+written in a single operation, followed by calculating the bit offset for
+a specific channel.
+
+Introduce helpers to streamline this process and reduce code redundancy,
+making the codebase cleaner and more maintainable.
+
+Acked-by: Oleksij Rempel <o.rempel at pengutronix.de>
+Signed-off-by: Kory Maincent <kory.maincent at bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni at redhat.com>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/tps23881.c | 107 ++++++++++++++++++++++------------
+ 1 file changed, 69 insertions(+), 38 deletions(-)
+--- a/drivers/net/pse-pd/tps23881.c
++++ b/drivers/net/pse-pd/tps23881.c
+@@ -53,6 +53,55 @@ static struct tps23881_priv *to_tps23881
+ 	return container_of(pcdev, struct tps23881_priv, pcdev);
+ }
+ 
++/*
++ * Helper to extract a value from a u16 register value, which is made of two
++ * u8 registers. The function calculates the bit offset based on the channel
++ * and extracts the relevant bits using a provided field mask.
++ *
++ * @param reg_val: The u16 register value (composed of two u8 registers).
++ * @param chan: The channel number (0-7).
++ * @param field_offset: The base bit offset to apply (e.g., 0 or 4).
++ * @param field_mask: The mask to apply to extract the required bits.
++ * @return: The extracted value for the specific channel.
++ */
++static u16 tps23881_calc_val(u16 reg_val, u8 chan, u8 field_offset,
++			     u16 field_mask)
++{
++	if (chan >= 4)
++		reg_val >>= 8;
++
++	return (reg_val >> field_offset) & field_mask;
++}
++
++/*
++ * Helper to combine individual channel values into a u16 register value.
++ * The function sets the value for a specific channel in the appropriate
++ * position.
++ *
++ * @param reg_val: The current u16 register value.
++ * @param chan: The channel number (0-7).
++ * @param field_offset: The base bit offset to apply (e.g., 0 or 4).
++ * @param field_mask: The mask to apply for the field (e.g., 0x0F).
++ * @param field_val: The value to set for the specific channel (masked by
++ *                   field_mask).
++ * @return: The updated u16 register value with the channel value set.
++ */
++static u16 tps23881_set_val(u16 reg_val, u8 chan, u8 field_offset,
++			    u16 field_mask, u16 field_val)
++{
++	field_val &= field_mask;
++
++	if (chan < 4) {
++		reg_val &= ~(field_mask << field_offset);
++		reg_val |= (field_val << field_offset);
++	} else {
++		reg_val &= ~(field_mask << (field_offset + 8));
++		reg_val |= (field_val << (field_offset + 8));
++	}
++
++	return reg_val;
++}
++
+ static int tps23881_pi_enable(struct pse_controller_dev *pcdev, int id)
+ {
+ 	struct tps23881_priv *priv = to_tps23881_priv(pcdev);
+@@ -64,17 +113,12 @@ static int tps23881_pi_enable(struct pse
+ 		return -ERANGE;
+ 
+ 	chan = priv->port[id].chan[0];
+-	if (chan < 4)
+-		val = BIT(chan);
+-	else
+-		val = BIT(chan + 4);
++	val = tps23881_set_val(0, chan, 0, BIT(chan % 4), BIT(chan % 4));
+ 
+ 	if (priv->port[id].is_4p) {
+ 		chan = priv->port[id].chan[1];
+-		if (chan < 4)
+-			val |= BIT(chan);
+-		else
+-			val |= BIT(chan + 4);
++		val = tps23881_set_val(val, chan, 0, BIT(chan % 4),
++				       BIT(chan % 4));
+ 	}
+ 
+ 	return i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
+@@ -91,17 +135,12 @@ static int tps23881_pi_disable(struct ps
+ 		return -ERANGE;
+ 
+ 	chan = priv->port[id].chan[0];
+-	if (chan < 4)
+-		val = BIT(chan + 4);
+-	else
+-		val = BIT(chan + 8);
++	val = tps23881_set_val(0, chan, 4, BIT(chan % 4), BIT(chan % 4));
+ 
+ 	if (priv->port[id].is_4p) {
+ 		chan = priv->port[id].chan[1];
+-		if (chan < 4)
+-			val |= BIT(chan + 4);
+-		else
+-			val |= BIT(chan + 8);
++		val = tps23881_set_val(val, chan, 4, BIT(chan % 4),
++				       BIT(chan % 4));
+ 	}
+ 
+ 	return i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
+@@ -113,6 +152,7 @@ static int tps23881_pi_is_enabled(struct
+ 	struct i2c_client *client = priv->client;
+ 	bool enabled;
+ 	u8 chan;
++	u16 val;
+ 	int ret;
+ 
+ 	ret = i2c_smbus_read_word_data(client, TPS23881_REG_PW_STATUS);
+@@ -120,17 +160,13 @@ static int tps23881_pi_is_enabled(struct
+ 		return ret;
+ 
+ 	chan = priv->port[id].chan[0];
+-	if (chan < 4)
+-		enabled = ret & BIT(chan);
+-	else
+-		enabled = ret & BIT(chan + 4);
++	val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4));
++	enabled = !!(val);
+ 
+ 	if (priv->port[id].is_4p) {
+ 		chan = priv->port[id].chan[1];
+-		if (chan < 4)
+-			enabled &= !!(ret & BIT(chan));
+-		else
+-			enabled &= !!(ret & BIT(chan + 4));
++		val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4));
++		enabled &= !!(val);
+ 	}
+ 
+ 	/* Return enabled status only if both channel are on this state */
+@@ -146,6 +182,7 @@ static int tps23881_ethtool_get_status(s
+ 	struct i2c_client *client = priv->client;
+ 	bool enabled, delivering;
+ 	u8 chan;
++	u16 val;
+ 	int ret;
+ 
+ 	ret = i2c_smbus_read_word_data(client, TPS23881_REG_PW_STATUS);
+@@ -153,23 +190,17 @@ static int tps23881_ethtool_get_status(s
+ 		return ret;
+ 
+ 	chan = priv->port[id].chan[0];
+-	if (chan < 4) {
+-		enabled = ret & BIT(chan);
+-		delivering = ret & BIT(chan + 4);
+-	} else {
+-		enabled = ret & BIT(chan + 4);
+-		delivering = ret & BIT(chan + 8);
+-	}
++	val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4));
++	enabled = !!(val);
++	val = tps23881_calc_val(ret, chan, 4, BIT(chan % 4));
++	delivering = !!(val);
+ 
+ 	if (priv->port[id].is_4p) {
+ 		chan = priv->port[id].chan[1];
+-		if (chan < 4) {
+-			enabled &= !!(ret & BIT(chan));
+-			delivering &= !!(ret & BIT(chan + 4));
+-		} else {
+-			enabled &= !!(ret & BIT(chan + 4));
+-			delivering &= !!(ret & BIT(chan + 8));
+-		}
++		val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4));
++		enabled &= !!(val);
++		val = tps23881_calc_val(ret, chan, 4, BIT(chan % 4));
++		delivering &= !!(val);
+ 	}
+ 
+ 	/* Return delivering status only if both channel are on this state */
diff --git a/target/linux/generic/backport-6.12/626-04-v6.13-net-pse-pd-tps23881-Add-missing-configuration-register.patch b/target/linux/generic/backport-6.12/626-04-v6.13-net-pse-pd-tps23881-Add-missing-configuration-register.patch
new file mode 100644
index 0000000000..c00dd9050f
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-04-v6.13-net-pse-pd-tps23881-Add-missing-configuration-register.patch
@@ -0,0 +1,63 @@
+From f3cb3c7bea0c08e821d8e9dfd2f96acd1db7c24e Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent at bootlin.com>
+Date: Fri, 10 Jan 2025 10:40:25 +0100
+Subject: [PATCH] net: pse-pd: tps23881: Add missing configuration register
+ after disable
+
+When setting the PWOFF register, the controller resets multiple
+configuration registers. This patch ensures these registers are
+reconfigured as needed following a disable operation.
+
+Acked-by: Oleksij Rempel <o.rempel at pengutronix.de>
+Signed-off-by: Kory Maincent <kory.maincent at bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni at redhat.com>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/tps23881.c | 30 +++++++++++++++++++++++++++++-
+ 1 file changed, 29 insertions(+), 1 deletion(-)
+--- a/drivers/net/pse-pd/tps23881.c
++++ b/drivers/net/pse-pd/tps23881.c
+@@ -130,6 +130,7 @@ static int tps23881_pi_disable(struct ps
+ 	struct i2c_client *client = priv->client;
+ 	u8 chan;
+ 	u16 val;
++	int ret;
+ 
+ 	if (id >= TPS23881_MAX_CHANS)
+ 		return -ERANGE;
+@@ -143,7 +144,34 @@ static int tps23881_pi_disable(struct ps
+ 				       BIT(chan % 4));
+ 	}
+ 
+-	return i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
++	ret = i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
++	if (ret)
++		return ret;
++
++	/* PWOFF command resets lots of register which need to be
++	 * configured again. According to the datasheet "It may take upwards
++	 * of 5ms after PWOFFn command for all register values to be updated"
++	 */
++	mdelay(5);
++
++	/* Enable detection and classification */
++	ret = i2c_smbus_read_word_data(client, TPS23881_REG_DET_CLA_EN);
++	if (ret < 0)
++		return ret;
++
++	chan = priv->port[id].chan[0];
++	val = tps23881_set_val(ret, chan, 0, BIT(chan % 4), BIT(chan % 4));
++	val = tps23881_set_val(val, chan, 4, BIT(chan % 4), BIT(chan % 4));
++
++	if (priv->port[id].is_4p) {
++		chan = priv->port[id].chan[1];
++		val = tps23881_set_val(ret, chan, 0, BIT(chan % 4),
++				       BIT(chan % 4));
++		val = tps23881_set_val(val, chan, 4, BIT(chan % 4),
++				       BIT(chan % 4));
++	}
++
++	return i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val);
+ }
+ 
+ static int tps23881_pi_is_enabled(struct pse_controller_dev *pcdev, int id)
diff --git a/target/linux/generic/backport-6.12/626-05-v6.13-net-pse-pd-Split-ethtool_get_status-into-multiple-callbacks.patch b/target/linux/generic/backport-6.12/626-05-v6.13-net-pse-pd-Split-ethtool_get_status-into-multiple-callbacks.patch
new file mode 100644
index 0000000000..71a2379e3b
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-05-v6.13-net-pse-pd-Split-ethtool_get_status-into-multiple-callbacks.patch
@@ -0,0 +1,714 @@
+From 3e9dbfec499807767d03592ebdf19d9c15fd495b Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent at bootlin.com>
+Date: Fri, 10 Jan 2025 10:40:27 +0100
+Subject: [PATCH] net: pse-pd: Split ethtool_get_status into multiple callbacks
+
+The ethtool_get_status callback currently handles all status and PSE
+information within a single function. This approach has two key
+drawbacks:
+
+1. If the core requires some information for purposes other than
+   ethtool_get_status, redundant code will be needed to fetch the same
+   data from the driver (like is_enabled).
+
+2. Drivers currently have access to all information passed to ethtool.
+   New variables will soon be added to ethtool status, such as PSE ID,
+   power domain IDs, and budget evaluation strategies, which are meant
+   to be managed solely by the core. Drivers should not have the ability
+   to modify these variables.
+
+To resolve these issues, ethtool_get_status has been split into multiple
+callbacks, with each handling a specific piece of information required
+by ethtool or the core.
+
+Signed-off-by: Kory Maincent <kory.maincent at bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni at redhat.com>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/pd692x0.c       | 153 ++++++++++++++++++++---------
+ drivers/net/pse-pd/pse_core.c      |  83 ++++++++++++++--
+ drivers/net/pse-pd/pse_regulator.c |  26 +++--
+ drivers/net/pse-pd/tps23881.c      |  60 ++++++++---
+ include/linux/pse-pd/pse.h         |  87 +++++++++++++---
+ net/ethtool/pse-pd.c               |   8 +-
+ 6 files changed, 323 insertions(+), 94 deletions(-)
+
+--- a/drivers/net/pse-pd/pd692x0.c
++++ b/drivers/net/pse-pd/pd692x0.c
+@@ -517,21 +517,38 @@ pd692x0_pse_ext_state_map[] = {
+ 	{ /* sentinel */ }
+ };
+ 
+-static void
+-pd692x0_get_ext_state(struct ethtool_c33_pse_ext_state_info *c33_ext_state_info,
+-		      u32 status_code)
++static int
++pd692x0_pi_get_ext_state(struct pse_controller_dev *pcdev, int id,
++			 struct pse_ext_state_info *ext_state_info)
+ {
++	struct ethtool_c33_pse_ext_state_info *c33_ext_state_info;
+ 	const struct pd692x0_pse_ext_state_mapping *ext_state_map;
++	struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
++	struct pd692x0_msg msg, buf = {0};
++	int ret;
++
++	ret = pd692x0_fw_unavailable(priv);
++	if (ret)
++		return ret;
+ 
++	msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_STATUS];
++	msg.sub[2] = id;
++	ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
++	if (ret < 0)
++		return ret;
++
++	c33_ext_state_info = &ext_state_info->c33_ext_state_info;
+ 	ext_state_map = pd692x0_pse_ext_state_map;
+ 	while (ext_state_map->status_code) {
+-		if (ext_state_map->status_code == status_code) {
++		if (ext_state_map->status_code == buf.sub[0]) {
+ 			c33_ext_state_info->c33_pse_ext_state = ext_state_map->pse_ext_state;
+ 			c33_ext_state_info->__c33_pse_ext_substate = ext_state_map->pse_ext_substate;
+-			return;
++			return  0;
+ 		}
+ 		ext_state_map++;
+ 	}
++
++	return 0;
+ }
+ 
+ struct pd692x0_class_pw {
+@@ -613,35 +630,36 @@ static int pd692x0_pi_set_pw_from_table(
+ }
+ 
+ static int
+-pd692x0_pi_get_pw_ranges(struct pse_control_status *st)
++pd692x0_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;
+ 	const struct pd692x0_class_pw *pw_table;
+ 	int i;
+ 
+ 	pw_table = pd692x0_class_pw_table;
+-	st->c33_pw_limit_ranges = kcalloc(PD692X0_CLASS_PW_TABLE_SIZE,
+-					  sizeof(struct ethtool_c33_pse_pw_limit_range),
+-					  GFP_KERNEL);
+-	if (!st->c33_pw_limit_ranges)
++	c33_pw_limit_ranges = kcalloc(PD692X0_CLASS_PW_TABLE_SIZE,
++				      sizeof(*c33_pw_limit_ranges),
++				      GFP_KERNEL);
++	if (!c33_pw_limit_ranges)
+ 		return -ENOMEM;
+ 
+ 	for (i = 0; i < PD692X0_CLASS_PW_TABLE_SIZE; i++, pw_table++) {
+-		st->c33_pw_limit_ranges[i].min = pw_table->class_pw;
+-		st->c33_pw_limit_ranges[i].max = pw_table->class_pw + pw_table->max_added_class_pw;
++		c33_pw_limit_ranges[i].min = pw_table->class_pw;
++		c33_pw_limit_ranges[i].max = pw_table->class_pw +
++					     pw_table->max_added_class_pw;
+ 	}
+ 
+-	st->c33_pw_limit_nb_ranges = i;
+-	return 0;
++	pw_limit_ranges->c33_pw_limit_ranges = c33_pw_limit_ranges;
++	return i;
+ }
+ 
+-static int pd692x0_ethtool_get_status(struct pse_controller_dev *pcdev,
+-				      unsigned long id,
+-				      struct netlink_ext_ack *extack,
+-				      struct pse_control_status *status)
++static int
++pd692x0_pi_get_admin_state(struct pse_controller_dev *pcdev, int id,
++			   struct pse_admin_state *admin_state)
+ {
+ 	struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+ 	struct pd692x0_msg msg, buf = {0};
+-	u32 class;
+ 	int ret;
+ 
+ 	ret = pd692x0_fw_unavailable(priv);
+@@ -654,39 +672,65 @@ static int pd692x0_ethtool_get_status(st
+ 	if (ret < 0)
+ 		return ret;
+ 
+-	/* Compare Port Status (Communication Protocol Document par. 7.1) */
+-	if ((buf.sub[0] & 0xf0) == 0x80 || (buf.sub[0] & 0xf0) == 0x90)
+-		status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;
+-	else if (buf.sub[0] == 0x1b || buf.sub[0] == 0x22)
+-		status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_SEARCHING;
+-	else if (buf.sub[0] == 0x12)
+-		status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_FAULT;
+-	else
+-		status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED;
+-
+ 	if (buf.sub[1])
+-		status->c33_admin_state = ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
++		admin_state->c33_admin_state =
++			ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
+ 	else
+-		status->c33_admin_state = ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
++		admin_state->c33_admin_state =
++			ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
+ 
+-	priv->admin_state[id] = status->c33_admin_state;
++	priv->admin_state[id] = admin_state->c33_admin_state;
+ 
+-	pd692x0_get_ext_state(&status->c33_ext_state_info, buf.sub[0]);
+-	status->c33_actual_pw = (buf.data[0] << 4 | buf.data[1]) * 100;
++	return 0;
++}
+ 
+-	msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_PARAM];
++static int
++pd692x0_pi_get_pw_status(struct pse_controller_dev *pcdev, int id,
++			 struct pse_pw_status *pw_status)
++{
++	struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
++	struct pd692x0_msg msg, buf = {0};
++	int ret;
++
++	ret = pd692x0_fw_unavailable(priv);
++	if (ret)
++		return ret;
++
++	msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_STATUS];
+ 	msg.sub[2] = id;
+-	memset(&buf, 0, sizeof(buf));
+ 	ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
+ 	if (ret < 0)
+ 		return ret;
+ 
+-	ret = pd692x0_pi_get_pw_from_table(buf.data[0], buf.data[1]);
+-	if (ret < 0)
++	/* Compare Port Status (Communication Protocol Document par. 7.1) */
++	if ((buf.sub[0] & 0xf0) == 0x80 || (buf.sub[0] & 0xf0) == 0x90)
++		pw_status->c33_pw_status =
++			ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;
++	else if (buf.sub[0] == 0x1b || buf.sub[0] == 0x22)
++		pw_status->c33_pw_status =
++			ETHTOOL_C33_PSE_PW_D_STATUS_SEARCHING;
++	else if (buf.sub[0] == 0x12)
++		pw_status->c33_pw_status =
++			ETHTOOL_C33_PSE_PW_D_STATUS_FAULT;
++	else
++		pw_status->c33_pw_status =
++			ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED;
++
++	return 0;
++}
++
++static int
++pd692x0_pi_get_pw_class(struct pse_controller_dev *pcdev, int id)
++{
++	struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
++	struct pd692x0_msg msg, buf = {0};
++	u32 class;
++	int ret;
++
++	ret = pd692x0_fw_unavailable(priv);
++	if (ret)
+ 		return ret;
+-	status->c33_avail_pw_limit = ret;
+ 
+-	memset(&buf, 0, sizeof(buf));
+ 	msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_CLASS];
+ 	msg.sub[2] = id;
+ 	ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
+@@ -695,13 +739,29 @@ static int pd692x0_ethtool_get_status(st
+ 
+ 	class = buf.data[3] >> 4;
+ 	if (class <= 8)
+-		status->c33_pw_class = class;
++		return class;
++
++	return 0;
++}
++
++static int
++pd692x0_pi_get_actual_pw(struct pse_controller_dev *pcdev, int id)
++{
++	struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
++	struct pd692x0_msg msg, buf = {0};
++	int ret;
++
++	ret = pd692x0_fw_unavailable(priv);
++	if (ret)
++		return ret;
+ 
+-	ret = pd692x0_pi_get_pw_ranges(status);
++	msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_STATUS];
++	msg.sub[2] = id;
++	ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
+ 	if (ret < 0)
+ 		return ret;
+ 
+-	return 0;
++	return (buf.data[0] << 4 | buf.data[1]) * 100;
+ }
+ 
+ static struct pd692x0_msg_ver pd692x0_get_sw_version(struct pd692x0_priv *priv)
+@@ -1038,13 +1098,18 @@ static int pd692x0_pi_set_pw_limit(struc
+ 
+ static const struct pse_controller_ops pd692x0_ops = {
+ 	.setup_pi_matrix = pd692x0_setup_pi_matrix,
+-	.ethtool_get_status = pd692x0_ethtool_get_status,
++	.pi_get_admin_state = pd692x0_pi_get_admin_state,
++	.pi_get_pw_status = pd692x0_pi_get_pw_status,
++	.pi_get_ext_state = pd692x0_pi_get_ext_state,
++	.pi_get_pw_class = pd692x0_pi_get_pw_class,
++	.pi_get_actual_pw = pd692x0_pi_get_actual_pw,
+ 	.pi_enable = pd692x0_pi_enable,
+ 	.pi_disable = pd692x0_pi_disable,
+ 	.pi_is_enabled = pd692x0_pi_is_enabled,
+ 	.pi_get_voltage = pd692x0_pi_get_voltage,
+ 	.pi_get_pw_limit = pd692x0_pi_get_pw_limit,
+ 	.pi_set_pw_limit = pd692x0_pi_set_pw_limit,
++	.pi_get_pw_limit_ranges = pd692x0_pi_get_pw_limit_ranges,
+ };
+ 
+ #define PD692X0_FW_LINE_MAX_SZ 0xff
+--- a/drivers/net/pse-pd/pse_core.c
++++ b/drivers/net/pse-pd/pse_core.c
+@@ -443,6 +443,13 @@ int pse_controller_register(struct pse_c
+ 	if (!pcdev->nr_lines)
+ 		pcdev->nr_lines = 1;
+ 
++	if (!pcdev->ops->pi_get_admin_state ||
++	    !pcdev->ops->pi_get_pw_status) {
++		dev_err(pcdev->dev,
++			"Mandatory status report callbacks are missing");
++		return -EINVAL;
++	}
++
+ 	ret = of_load_pse_pis(pcdev);
+ 	if (ret)
+ 		return ret;
+@@ -745,25 +752,81 @@ EXPORT_SYMBOL_GPL(of_pse_control_get);
+  */
+ int pse_ethtool_get_status(struct pse_control *psec,
+ 			   struct netlink_ext_ack *extack,
+-			   struct pse_control_status *status)
++			   struct ethtool_pse_control_status *status)
+ {
++	struct pse_admin_state admin_state = {0};
++	struct pse_pw_status pw_status = {0};
+ 	const struct pse_controller_ops *ops;
+ 	struct pse_controller_dev *pcdev;
+-	int err;
++	int ret;
+ 
+ 	pcdev = psec->pcdev;
+ 	ops = pcdev->ops;
+-	if (!ops->ethtool_get_status) {
+-		NL_SET_ERR_MSG(extack,
+-			       "PSE driver does not support status report");
+-		return -EOPNOTSUPP;
++	mutex_lock(&pcdev->lock);
++	ret = ops->pi_get_admin_state(pcdev, psec->id, &admin_state);
++	if (ret)
++		goto out;
++	status->podl_admin_state = admin_state.podl_admin_state;
++	status->c33_admin_state = admin_state.c33_admin_state;
++
++	ret = ops->pi_get_pw_status(pcdev, psec->id, &pw_status);
++	if (ret)
++		goto out;
++	status->podl_pw_status = pw_status.podl_pw_status;
++	status->c33_pw_status = pw_status.c33_pw_status;
++
++	if (ops->pi_get_ext_state) {
++		struct pse_ext_state_info ext_state_info = {0};
++
++		ret = ops->pi_get_ext_state(pcdev, psec->id,
++					    &ext_state_info);
++		if (ret)
++			goto out;
++
++		memcpy(&status->c33_ext_state_info,
++		       &ext_state_info.c33_ext_state_info,
++		       sizeof(status->c33_ext_state_info));
+ 	}
+ 
+-	mutex_lock(&pcdev->lock);
+-	err = ops->ethtool_get_status(pcdev, psec->id, extack, status);
+-	mutex_unlock(&pcdev->lock);
++	if (ops->pi_get_pw_class) {
++		ret = ops->pi_get_pw_class(pcdev, psec->id);
++		if (ret < 0)
++			goto out;
++
++		status->c33_pw_class = ret;
++	}
++
++	if (ops->pi_get_actual_pw) {
++		ret = ops->pi_get_actual_pw(pcdev, psec->id);
++		if (ret < 0)
++			goto out;
++
++		status->c33_actual_pw = ret;
++	}
++
++	if (ops->pi_get_pw_limit) {
++		ret = ops->pi_get_pw_limit(pcdev, psec->id);
++		if (ret < 0)
++			goto out;
++
++		status->c33_avail_pw_limit = ret;
++	}
++
++	if (ops->pi_get_pw_limit_ranges) {
++		struct pse_pw_limit_ranges pw_limit_ranges = {0};
+ 
+-	return err;
++		ret = ops->pi_get_pw_limit_ranges(pcdev, psec->id,
++						  &pw_limit_ranges);
++		if (ret < 0)
++			goto out;
++
++		status->c33_pw_limit_ranges =
++			pw_limit_ranges.c33_pw_limit_ranges;
++		status->c33_pw_limit_nb_ranges = ret;
++	}
++out:
++	mutex_unlock(&psec->pcdev->lock);
++	return ret;
+ }
+ EXPORT_SYMBOL_GPL(pse_ethtool_get_status);
+ 
+--- a/drivers/net/pse-pd/pse_regulator.c
++++ b/drivers/net/pse-pd/pse_regulator.c
+@@ -60,9 +60,19 @@ pse_reg_pi_is_enabled(struct pse_control
+ }
+ 
+ static int
+-pse_reg_ethtool_get_status(struct pse_controller_dev *pcdev, unsigned long id,
+-			   struct netlink_ext_ack *extack,
+-			   struct pse_control_status *status)
++pse_reg_pi_get_admin_state(struct pse_controller_dev *pcdev, int id,
++			   struct pse_admin_state *admin_state)
++{
++	struct pse_reg_priv *priv = to_pse_reg(pcdev);
++
++	admin_state->podl_admin_state = priv->admin_state;
++
++	return 0;
++}
++
++static int
++pse_reg_pi_get_pw_status(struct pse_controller_dev *pcdev, int id,
++			 struct pse_pw_status *pw_status)
+ {
+ 	struct pse_reg_priv *priv = to_pse_reg(pcdev);
+ 	int ret;
+@@ -72,18 +82,18 @@ pse_reg_ethtool_get_status(struct pse_co
+ 		return ret;
+ 
+ 	if (!ret)
+-		status->podl_pw_status = ETHTOOL_PODL_PSE_PW_D_STATUS_DISABLED;
++		pw_status->podl_pw_status =
++			ETHTOOL_PODL_PSE_PW_D_STATUS_DISABLED;
+ 	else
+-		status->podl_pw_status =
++		pw_status->podl_pw_status =
+ 			ETHTOOL_PODL_PSE_PW_D_STATUS_DELIVERING;
+ 
+-	status->podl_admin_state = priv->admin_state;
+-
+ 	return 0;
+ }
+ 
+ static const struct pse_controller_ops pse_reg_ops = {
+-	.ethtool_get_status = pse_reg_ethtool_get_status,
++	.pi_get_admin_state = pse_reg_pi_get_admin_state,
++	.pi_get_pw_status = pse_reg_pi_get_pw_status,
+ 	.pi_enable = pse_reg_pi_enable,
+ 	.pi_is_enabled = pse_reg_pi_is_enabled,
+ 	.pi_disable = pse_reg_pi_disable,
+--- a/drivers/net/pse-pd/tps23881.c
++++ b/drivers/net/pse-pd/tps23881.c
+@@ -201,14 +201,13 @@ static int tps23881_pi_is_enabled(struct
+ 	return enabled;
+ }
+ 
+-static int tps23881_ethtool_get_status(struct pse_controller_dev *pcdev,
+-				       unsigned long id,
+-				       struct netlink_ext_ack *extack,
+-				       struct pse_control_status *status)
++static int
++tps23881_pi_get_admin_state(struct pse_controller_dev *pcdev, int id,
++			    struct pse_admin_state *admin_state)
+ {
+ 	struct tps23881_priv *priv = to_tps23881_priv(pcdev);
+ 	struct i2c_client *client = priv->client;
+-	bool enabled, delivering;
++	bool enabled;
+ 	u8 chan;
+ 	u16 val;
+ 	int ret;
+@@ -220,28 +219,56 @@ static int tps23881_ethtool_get_status(s
+ 	chan = priv->port[id].chan[0];
+ 	val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4));
+ 	enabled = !!(val);
+-	val = tps23881_calc_val(ret, chan, 4, BIT(chan % 4));
+-	delivering = !!(val);
+ 
+ 	if (priv->port[id].is_4p) {
+ 		chan = priv->port[id].chan[1];
+ 		val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4));
+ 		enabled &= !!(val);
++	}
++
++	/* Return enabled status only if both channel are on this state */
++	if (enabled)
++		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
++tps23881_pi_get_pw_status(struct pse_controller_dev *pcdev, int id,
++			  struct pse_pw_status *pw_status)
++{
++	struct tps23881_priv *priv = to_tps23881_priv(pcdev);
++	struct i2c_client *client = priv->client;
++	bool delivering;
++	u8 chan;
++	u16 val;
++	int ret;
++
++	ret = i2c_smbus_read_word_data(client, TPS23881_REG_PW_STATUS);
++	if (ret < 0)
++		return ret;
++
++	chan = priv->port[id].chan[0];
++	val = tps23881_calc_val(ret, chan, 4, BIT(chan % 4));
++	delivering = !!(val);
++
++	if (priv->port[id].is_4p) {
++		chan = priv->port[id].chan[1];
+ 		val = tps23881_calc_val(ret, chan, 4, BIT(chan % 4));
+ 		delivering &= !!(val);
+ 	}
+ 
+ 	/* Return delivering status only if both channel are on this state */
+ 	if (delivering)
+-		status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;
+-	else
+-		status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED;
+-
+-	/* Return enabled status only if both channel are on this state */
+-	if (enabled)
+-		status->c33_admin_state = ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
++		pw_status->c33_pw_status =
++			ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;
+ 	else
+-		status->c33_admin_state = ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
++		pw_status->c33_pw_status =
++			ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED;
+ 
+ 	return 0;
+ }
+@@ -664,7 +691,8 @@ static const struct pse_controller_ops t
+ 	.pi_enable = tps23881_pi_enable,
+ 	.pi_disable = tps23881_pi_disable,
+ 	.pi_is_enabled = tps23881_pi_is_enabled,
+-	.ethtool_get_status = tps23881_ethtool_get_status,
++	.pi_get_admin_state = tps23881_pi_get_admin_state,
++	.pi_get_pw_status = tps23881_pi_get_pw_status,
+ };
+ 
+ static const char fw_parity_name[] = "ti/tps23881/tps23881-parity-14.bin";
+--- a/include/linux/pse-pd/pse.h
++++ b/include/linux/pse-pd/pse.h
+@@ -31,7 +31,52 @@ struct pse_control_config {
+ };
+ 
+ /**
+- * struct pse_control_status - PSE control/channel status.
++ * struct pse_admin_state - PSE operational state
++ *
++ * @podl_admin_state: operational state of the PoDL PSE
++ *	functions. IEEE 802.3-2018 30.15.1.1.2 aPoDLPSEAdminState
++ * @c33_admin_state: operational state of the PSE
++ *	functions. IEEE 802.3-2022 30.9.1.1.2 aPSEAdminState
++ */
++struct pse_admin_state {
++	enum ethtool_podl_pse_admin_state podl_admin_state;
++	enum ethtool_c33_pse_admin_state c33_admin_state;
++};
++
++/**
++ * struct pse_pw_status - PSE power detection status
++ *
++ * @podl_pw_status: power detection status of the PoDL PSE.
++ *	IEEE 802.3-2018 30.15.1.1.3 aPoDLPSEPowerDetectionStatus:
++ * @c33_pw_status: power detection status of the PSE.
++ *	IEEE 802.3-2022 30.9.1.1.5 aPSEPowerDetectionStatus:
++ */
++struct pse_pw_status {
++	enum ethtool_podl_pse_pw_d_status podl_pw_status;
++	enum ethtool_c33_pse_pw_d_status c33_pw_status;
++};
++
++/**
++ * struct pse_ext_state_info - PSE extended state information
++ *
++ * @c33_ext_state_info: extended state information of the PSE
++ */
++struct pse_ext_state_info {
++	struct ethtool_c33_pse_ext_state_info c33_ext_state_info;
++};
++
++/**
++ * struct pse_pw_limit_ranges - PSE power limit configuration range
++ *
++ * @c33_pw_limit_ranges: supported power limit configuration range. The driver
++ *			 is in charge of the memory allocation.
++ */
++struct pse_pw_limit_ranges {
++	struct ethtool_c33_pse_pw_limit_range *c33_pw_limit_ranges;
++};
++
++/**
++ * struct ethtool_pse_control_status - PSE control/channel status.
+  *
+  * @podl_admin_state: operational state of the PoDL PSE
+  *	functions. IEEE 802.3-2018 30.15.1.1.2 aPoDLPSEAdminState
+@@ -49,11 +94,11 @@ struct pse_control_config {
+  * @c33_avail_pw_limit: available power limit of the PSE in mW
+  *	IEEE 802.3-2022 145.2.5.4 pse_avail_pwr
+  * @c33_pw_limit_ranges: supported power limit configuration range. The driver
+- *	is in charge of the memory allocation.
++ *	is in charge of the memory allocation
+  * @c33_pw_limit_nb_ranges: number of supported power limit configuration
+  *	ranges
+  */
+-struct pse_control_status {
++struct ethtool_pse_control_status {
+ 	enum ethtool_podl_pse_admin_state podl_admin_state;
+ 	enum ethtool_podl_pse_pw_d_status podl_pw_status;
+ 	enum ethtool_c33_pse_admin_state c33_admin_state;
+@@ -69,22 +114,37 @@ struct pse_control_status {
+ /**
+  * struct pse_controller_ops - PSE controller driver callbacks
+  *
+- * @ethtool_get_status: get PSE control status for ethtool interface
+  * @setup_pi_matrix: setup PI matrix of the PSE controller
++ * @pi_get_admin_state: Get the operational state of the PSE PI. This ops
++ *			is mandatory.
++ * @pi_get_pw_status: Get the power detection status of the PSE PI. This
++ *		      ops is mandatory.
++ * @pi_get_ext_state: Get the extended state of the PSE PI.
++ * @pi_get_pw_class: Get the power class of the PSE PI.
++ * @pi_get_actual_pw: Get actual power of the PSE PI in mW.
+  * @pi_is_enabled: Return 1 if the PSE PI is enabled, 0 if not.
+  *		   May also return negative errno.
+  * @pi_enable: Configure the PSE PI as enabled.
+  * @pi_disable: Configure the PSE PI as disabled.
+  * @pi_get_voltage: Return voltage similarly to get_voltage regulator
+- *		    callback.
+- * @pi_get_pw_limit: Get the configured power limit of the PSE PI.
+- * @pi_set_pw_limit: Configure the power limit of the PSE PI.
++ *		    callback in uV.
++ * @pi_get_pw_limit: Get the configured power limit of the PSE PI in mW.
++ * @pi_set_pw_limit: Configure the power limit of the PSE PI in mW.
++ * @pi_get_pw_limit_ranges: Get the supported power limit configuration
++ *			    range. The driver is in charge of the memory
++ *			    allocation and should return the number of
++ *			    ranges.
+  */
+ struct pse_controller_ops {
+-	int (*ethtool_get_status)(struct pse_controller_dev *pcdev,
+-		unsigned long id, struct netlink_ext_ack *extack,
+-		struct pse_control_status *status);
+ 	int (*setup_pi_matrix)(struct pse_controller_dev *pcdev);
++	int (*pi_get_admin_state)(struct pse_controller_dev *pcdev, int id,
++				  struct pse_admin_state *admin_state);
++	int (*pi_get_pw_status)(struct pse_controller_dev *pcdev, int id,
++				struct pse_pw_status *pw_status);
++	int (*pi_get_ext_state)(struct pse_controller_dev *pcdev, int id,
++				struct pse_ext_state_info *ext_state_info);
++	int (*pi_get_pw_class)(struct pse_controller_dev *pcdev, int id);
++	int (*pi_get_actual_pw)(struct pse_controller_dev *pcdev, int id);
+ 	int (*pi_is_enabled)(struct pse_controller_dev *pcdev, int id);
+ 	int (*pi_enable)(struct pse_controller_dev *pcdev, int id);
+ 	int (*pi_disable)(struct pse_controller_dev *pcdev, int id);
+@@ -93,12 +153,15 @@ struct pse_controller_ops {
+ 			       int id);
+ 	int (*pi_set_pw_limit)(struct pse_controller_dev *pcdev,
+ 			       int id, int max_mW);
++	int (*pi_get_pw_limit_ranges)(struct pse_controller_dev *pcdev, int id,
++				      struct pse_pw_limit_ranges *pw_limit_ranges);
+ };
+ 
+ struct module;
+ struct device_node;
+ struct of_phandle_args;
+ struct pse_control;
++struct ethtool_pse_control_status;
+ 
+ /* PSE PI pairset pinout can either be Alternative A or Alternative B */
+ enum pse_pi_pairset_pinout {
+@@ -175,7 +238,7 @@ void pse_control_put(struct pse_control
+ 
+ int pse_ethtool_get_status(struct pse_control *psec,
+ 			   struct netlink_ext_ack *extack,
+-			   struct pse_control_status *status);
++			   struct ethtool_pse_control_status *status);
+ int pse_ethtool_set_config(struct pse_control *psec,
+ 			   struct netlink_ext_ack *extack,
+ 			   const struct pse_control_config *config);
+@@ -201,7 +264,7 @@ static inline void pse_control_put(struc
+ 
+ static inline int pse_ethtool_get_status(struct pse_control *psec,
+ 					 struct netlink_ext_ack *extack,
+-					 struct pse_control_status *status)
++					 struct ethtool_pse_control_status *status)
+ {
+ 	return -EOPNOTSUPP;
+ }
+--- a/net/ethtool/pse-pd.c
++++ b/net/ethtool/pse-pd.c
+@@ -19,7 +19,7 @@ struct pse_req_info {
+ 
+ struct pse_reply_data {
+ 	struct ethnl_reply_data	base;
+-	struct pse_control_status status;
++	struct ethtool_pse_control_status status;
+ };
+ 
+ #define PSE_REPDATA(__reply_base) \
+@@ -80,7 +80,7 @@ static int pse_reply_size(const struct e
+ 			  const struct ethnl_reply_data *reply_base)
+ {
+ 	const struct pse_reply_data *data = PSE_REPDATA(reply_base);
+-	const struct pse_control_status *st = &data->status;
++	const struct ethtool_pse_control_status *st = &data->status;
+ 	int len = 0;
+ 
+ 	if (st->podl_admin_state > 0)
+@@ -114,7 +114,7 @@ static int pse_reply_size(const struct e
+ }
+ 
+ static int pse_put_pw_limit_ranges(struct sk_buff *skb,
+-				   const struct pse_control_status *st)
++				   const struct ethtool_pse_control_status *st)
+ {
+ 	const struct ethtool_c33_pse_pw_limit_range *pw_limit_ranges;
+ 	int i;
+@@ -146,7 +146,7 @@ static int pse_fill_reply(struct sk_buff
+ 			  const struct ethnl_reply_data *reply_base)
+ {
+ 	const struct pse_reply_data *data = PSE_REPDATA(reply_base);
+-	const struct pse_control_status *st = &data->status;
++	const struct ethtool_pse_control_status *st = &data->status;
+ 
+ 	if (st->podl_admin_state > 0 &&
+ 	    nla_put_u32(skb, ETHTOOL_A_PODL_PSE_ADMIN_STATE,
diff --git a/target/linux/generic/backport-6.12/626-06-v6.13-net-pse-pd-Remove-is_enabled-callback-from-drivers.patch b/target/linux/generic/backport-6.12/626-06-v6.13-net-pse-pd-Remove-is_enabled-callback-from-drivers.patch
new file mode 100644
index 0000000000..dd7a20efaa
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-06-v6.13-net-pse-pd-Remove-is_enabled-callback-from-drivers.patch
@@ -0,0 +1,184 @@
+From 4640a1f0d8f2246f34d6e74330d7e7d2cf75605b Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent at bootlin.com>
+Date: Fri, 10 Jan 2025 10:40:28 +0100
+Subject: [PATCH] net: pse-pd: Remove is_enabled callback from drivers
+
+The is_enabled callback is now redundant as the admin_state can be obtained
+directly from the driver and provides the same information.
+
+To simplify functionality, the core will handle this internally, making
+the is_enabled callback unnecessary at the driver level. Remove the
+callback from all drivers.
+
+Acked-by: Oleksij Rempel <o.rempel at pengutronix.de>
+Signed-off-by: Kory Maincent <kory.maincent at bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni at redhat.com>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/pd692x0.c       | 26 --------------------------
+ drivers/net/pse-pd/pse_core.c      | 13 +++++++++++--
+ drivers/net/pse-pd/pse_regulator.c |  9 ---------
+ drivers/net/pse-pd/tps23881.c      | 28 ----------------------------
+ include/linux/pse-pd/pse.h         |  3 ---
+ 5 files changed, 11 insertions(+), 68 deletions(-)
+
+--- a/drivers/net/pse-pd/pd692x0.c
++++ b/drivers/net/pse-pd/pd692x0.c
+@@ -431,31 +431,6 @@ static int pd692x0_pi_disable(struct pse
+ 	return 0;
+ }
+ 
+-static int pd692x0_pi_is_enabled(struct pse_controller_dev *pcdev, int id)
+-{
+-	struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+-	struct pd692x0_msg msg, buf = {0};
+-	int ret;
+-
+-	ret = pd692x0_fw_unavailable(priv);
+-	if (ret)
+-		return ret;
+-
+-	msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_STATUS];
+-	msg.sub[2] = id;
+-	ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
+-	if (ret < 0)
+-		return ret;
+-
+-	if (buf.sub[1]) {
+-		priv->admin_state[id] = ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
+-		return 1;
+-	} else {
+-		priv->admin_state[id] = ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
+-		return 0;
+-	}
+-}
+-
+ struct pd692x0_pse_ext_state_mapping {
+ 	u32 status_code;
+ 	enum ethtool_c33_pse_ext_state pse_ext_state;
+@@ -1105,7 +1080,6 @@ static const struct pse_controller_ops p
+ 	.pi_get_actual_pw = pd692x0_pi_get_actual_pw,
+ 	.pi_enable = pd692x0_pi_enable,
+ 	.pi_disable = pd692x0_pi_disable,
+-	.pi_is_enabled = pd692x0_pi_is_enabled,
+ 	.pi_get_voltage = pd692x0_pi_get_voltage,
+ 	.pi_get_pw_limit = pd692x0_pi_get_pw_limit,
+ 	.pi_set_pw_limit = pd692x0_pi_set_pw_limit,
+--- a/drivers/net/pse-pd/pse_core.c
++++ b/drivers/net/pse-pd/pse_core.c
+@@ -210,16 +210,25 @@ out:
+ static int pse_pi_is_enabled(struct regulator_dev *rdev)
+ {
+ 	struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
++	struct pse_admin_state admin_state = {0};
+ 	const struct pse_controller_ops *ops;
+ 	int id, ret;
+ 
+ 	ops = pcdev->ops;
+-	if (!ops->pi_is_enabled)
++	if (!ops->pi_get_admin_state)
+ 		return -EOPNOTSUPP;
+ 
+ 	id = rdev_get_id(rdev);
+ 	mutex_lock(&pcdev->lock);
+-	ret = ops->pi_is_enabled(pcdev, id);
++	ret = ops->pi_get_admin_state(pcdev, id, &admin_state);
++	if (ret)
++		goto out;
++
++	if (admin_state.podl_admin_state == ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED ||
++	    admin_state.c33_admin_state == ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED)
++		ret = 1;
++
++out:
+ 	mutex_unlock(&pcdev->lock);
+ 
+ 	return ret;
+--- a/drivers/net/pse-pd/pse_regulator.c
++++ b/drivers/net/pse-pd/pse_regulator.c
+@@ -52,14 +52,6 @@ pse_reg_pi_disable(struct pse_controller
+ }
+ 
+ static int
+-pse_reg_pi_is_enabled(struct pse_controller_dev *pcdev, int id)
+-{
+-	struct pse_reg_priv *priv = to_pse_reg(pcdev);
+-
+-	return regulator_is_enabled(priv->ps);
+-}
+-
+-static int
+ pse_reg_pi_get_admin_state(struct pse_controller_dev *pcdev, int id,
+ 			   struct pse_admin_state *admin_state)
+ {
+@@ -95,7 +87,6 @@ static const struct pse_controller_ops p
+ 	.pi_get_admin_state = pse_reg_pi_get_admin_state,
+ 	.pi_get_pw_status = pse_reg_pi_get_pw_status,
+ 	.pi_enable = pse_reg_pi_enable,
+-	.pi_is_enabled = pse_reg_pi_is_enabled,
+ 	.pi_disable = pse_reg_pi_disable,
+ };
+ 
+--- a/drivers/net/pse-pd/tps23881.c
++++ b/drivers/net/pse-pd/tps23881.c
+@@ -174,33 +174,6 @@ static int tps23881_pi_disable(struct ps
+ 	return i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val);
+ }
+ 
+-static int tps23881_pi_is_enabled(struct pse_controller_dev *pcdev, int id)
+-{
+-	struct tps23881_priv *priv = to_tps23881_priv(pcdev);
+-	struct i2c_client *client = priv->client;
+-	bool enabled;
+-	u8 chan;
+-	u16 val;
+-	int ret;
+-
+-	ret = i2c_smbus_read_word_data(client, TPS23881_REG_PW_STATUS);
+-	if (ret < 0)
+-		return ret;
+-
+-	chan = priv->port[id].chan[0];
+-	val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4));
+-	enabled = !!(val);
+-
+-	if (priv->port[id].is_4p) {
+-		chan = priv->port[id].chan[1];
+-		val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4));
+-		enabled &= !!(val);
+-	}
+-
+-	/* Return enabled status only if both channel are on this state */
+-	return enabled;
+-}
+-
+ static int
+ tps23881_pi_get_admin_state(struct pse_controller_dev *pcdev, int id,
+ 			    struct pse_admin_state *admin_state)
+@@ -690,7 +663,6 @@ static const struct pse_controller_ops t
+ 	.setup_pi_matrix = tps23881_setup_pi_matrix,
+ 	.pi_enable = tps23881_pi_enable,
+ 	.pi_disable = tps23881_pi_disable,
+-	.pi_is_enabled = tps23881_pi_is_enabled,
+ 	.pi_get_admin_state = tps23881_pi_get_admin_state,
+ 	.pi_get_pw_status = tps23881_pi_get_pw_status,
+ };
+--- a/include/linux/pse-pd/pse.h
++++ b/include/linux/pse-pd/pse.h
+@@ -122,8 +122,6 @@ struct ethtool_pse_control_status {
+  * @pi_get_ext_state: Get the extended state of the PSE PI.
+  * @pi_get_pw_class: Get the power class of the PSE PI.
+  * @pi_get_actual_pw: Get actual power of the PSE PI in mW.
+- * @pi_is_enabled: Return 1 if the PSE PI is enabled, 0 if not.
+- *		   May also return negative errno.
+  * @pi_enable: Configure the PSE PI as enabled.
+  * @pi_disable: Configure the PSE PI as disabled.
+  * @pi_get_voltage: Return voltage similarly to get_voltage regulator
+@@ -145,7 +143,6 @@ struct pse_controller_ops {
+ 				struct pse_ext_state_info *ext_state_info);
+ 	int (*pi_get_pw_class)(struct pse_controller_dev *pcdev, int id);
+ 	int (*pi_get_actual_pw)(struct pse_controller_dev *pcdev, int id);
+-	int (*pi_is_enabled)(struct pse_controller_dev *pcdev, int id);
+ 	int (*pi_enable)(struct pse_controller_dev *pcdev, int id);
+ 	int (*pi_disable)(struct pse_controller_dev *pcdev, int id);
+ 	int (*pi_get_voltage)(struct pse_controller_dev *pcdev, int id);
diff --git a/target/linux/generic/backport-6.12/626-07-v6.13-net-pse-pd-tps23881-Add-power-limit-and-measurement-features.patch b/target/linux/generic/backport-6.12/626-07-v6.13-net-pse-pd-tps23881-Add-power-limit-and-measurement-features.patch
new file mode 100644
index 0000000000..de3cc76b4f
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-07-v6.13-net-pse-pd-tps23881-Add-power-limit-and-measurement-features.patch
@@ -0,0 +1,337 @@
+From 7f076ce3f17334964590c2cce49a02c0851c099a Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent at bootlin.com>
+Date: Fri, 10 Jan 2025 10:40:29 +0100
+Subject: [PATCH] net: pse-pd: tps23881: Add support for power limit and
+ measurement features
+
+Expand PSE callbacks to support the newly introduced
+pi_get/set_pw_limit() and pi_get_voltage() functions. These callbacks
+allow for power limit configuration in the TPS23881 controller.
+
+Additionally, the patch includes the pi_get_pw_class() the
+pi_get_actual_pw(), and the pi_get_pw_limit_ranges') callbacks providing
+more comprehensive PoE status reporting.
+
+Acked-by: Oleksij Rempel <o.rempel at pengutronix.de>
+Signed-off-by: Kory Maincent <kory.maincent at bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni at redhat.com>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/tps23881.c | 258 +++++++++++++++++++++++++++++++++-
+ 1 file changed, 256 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/pse-pd/tps23881.c
++++ b/drivers/net/pse-pd/tps23881.c
+@@ -25,20 +25,32 @@
+ #define TPS23881_REG_GEN_MASK	0x17
+ #define TPS23881_REG_NBITACC	BIT(5)
+ #define TPS23881_REG_PW_EN	0x19
++#define TPS23881_REG_2PAIR_POL1	0x1e
+ #define TPS23881_REG_PORT_MAP	0x26
+ #define TPS23881_REG_PORT_POWER	0x29
+-#define TPS23881_REG_POEPLUS	0x40
++#define TPS23881_REG_4PAIR_POL1	0x2a
++#define TPS23881_REG_INPUT_V	0x2e
++#define TPS23881_REG_CHAN1_A	0x30
++#define TPS23881_REG_CHAN1_V	0x32
++#define TPS23881_REG_FOLDBACK	0x40
+ #define TPS23881_REG_TPON	BIT(0)
+ #define TPS23881_REG_FWREV	0x41
+ #define TPS23881_REG_DEVID	0x43
+ #define TPS23881_REG_DEVID_MASK	0xF0
+ #define TPS23881_DEVICE_ID	0x02
++#define TPS23881_REG_CHAN1_CLASS	0x4c
+ #define TPS23881_REG_SRAM_CTRL	0x60
+ #define TPS23881_REG_SRAM_DATA	0x61
+ 
++#define TPS23881_UV_STEP	3662
++#define TPS23881_NA_STEP	70190
++#define TPS23881_MW_STEP	500
++#define TPS23881_MIN_PI_PW_LIMIT_MW	2000
++
+ struct tps23881_port_desc {
+ 	u8 chan[2];
+ 	bool is_4p;
++	int pw_pol;
+ };
+ 
+ struct tps23881_priv {
+@@ -102,6 +114,54 @@ static u16 tps23881_set_val(u16 reg_val,
+ 	return reg_val;
+ }
+ 
++static int
++tps23881_pi_set_pw_pol_limit(struct tps23881_priv *priv, int id, u8 pw_pol,
++			     bool is_4p)
++{
++	struct i2c_client *client = priv->client;
++	int ret, reg;
++	u16 val;
++	u8 chan;
++
++	chan = priv->port[id].chan[0];
++	if (!is_4p) {
++		reg = TPS23881_REG_2PAIR_POL1 + (chan % 4);
++	} else {
++		/* One chan is enough to configure the 4p PI power limit */
++		if ((chan % 4) < 2)
++			reg = TPS23881_REG_4PAIR_POL1;
++		else
++			reg = TPS23881_REG_4PAIR_POL1 + 1;
++	}
++
++	ret = i2c_smbus_read_word_data(client, reg);
++	if (ret < 0)
++		return ret;
++
++	val = tps23881_set_val(ret, chan, 0, 0xff, pw_pol);
++	return i2c_smbus_write_word_data(client, reg, val);
++}
++
++static int tps23881_pi_enable_manual_pol(struct tps23881_priv *priv, int id)
++{
++	struct i2c_client *client = priv->client;
++	int ret;
++	u8 chan;
++	u16 val;
++
++	ret = i2c_smbus_read_byte_data(client, TPS23881_REG_FOLDBACK);
++	if (ret < 0)
++		return ret;
++
++	/* No need to test if the chan is PoE4 as setting either bit for a
++	 * 4P configured port disables the automatic configuration on both
++	 * channels.
++	 */
++	chan = priv->port[id].chan[0];
++	val = tps23881_set_val(ret, chan, 0, BIT(chan % 4), BIT(chan % 4));
++	return i2c_smbus_write_byte_data(client, TPS23881_REG_FOLDBACK, val);
++}
++
+ static int tps23881_pi_enable(struct pse_controller_dev *pcdev, int id)
+ {
+ 	struct tps23881_priv *priv = to_tps23881_priv(pcdev);
+@@ -171,7 +231,21 @@ static int tps23881_pi_disable(struct ps
+ 				       BIT(chan % 4));
+ 	}
+ 
+-	return i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val);
++	ret = i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val);
++	if (ret)
++		return ret;
++
++	/* No power policy */
++	if (priv->port[id].pw_pol < 0)
++		return 0;
++
++	ret = tps23881_pi_enable_manual_pol(priv, id);
++	if (ret < 0)
++		return ret;
++
++	/* Set power policy */
++	return tps23881_pi_set_pw_pol_limit(priv, id, priv->port[id].pw_pol,
++					    priv->port[id].is_4p);
+ }
+ 
+ static int
+@@ -246,6 +320,177 @@ tps23881_pi_get_pw_status(struct pse_con
+ 	return 0;
+ }
+ 
++static int tps23881_pi_get_voltage(struct pse_controller_dev *pcdev, int id)
++{
++	struct tps23881_priv *priv = to_tps23881_priv(pcdev);
++	struct i2c_client *client = priv->client;
++	int ret;
++	u64 uV;
++
++	ret = i2c_smbus_read_word_data(client, TPS23881_REG_INPUT_V);
++	if (ret < 0)
++		return ret;
++
++	uV = ret & 0x3fff;
++	uV *= TPS23881_UV_STEP;
++
++	return (int)uV;
++}
++
++static int
++tps23881_pi_get_chan_current(struct tps23881_priv *priv, u8 chan)
++{
++	struct i2c_client *client = priv->client;
++	int reg, ret;
++	u64 tmp_64;
++
++	/* Registers 0x30 to 0x3d */
++	reg = TPS23881_REG_CHAN1_A + (chan % 4) * 4 + (chan >= 4);
++	ret = i2c_smbus_read_word_data(client, reg);
++	if (ret < 0)
++		return ret;
++
++	tmp_64 = ret & 0x3fff;
++	tmp_64 *= TPS23881_NA_STEP;
++	/* uA = nA / 1000 */
++	tmp_64 = DIV_ROUND_CLOSEST_ULL(tmp_64, 1000);
++	return (int)tmp_64;
++}
++
++static int tps23881_pi_get_pw_class(struct pse_controller_dev *pcdev,
++				    int id)
++{
++	struct tps23881_priv *priv = to_tps23881_priv(pcdev);
++	struct i2c_client *client = priv->client;
++	int ret, reg;
++	u8 chan;
++
++	chan = priv->port[id].chan[0];
++	reg = TPS23881_REG_CHAN1_CLASS + (chan % 4);
++	ret = i2c_smbus_read_word_data(client, reg);
++	if (ret < 0)
++		return ret;
++
++	return tps23881_calc_val(ret, chan, 4, 0x0f);
++}
++
++static int
++tps23881_pi_get_actual_pw(struct pse_controller_dev *pcdev, int id)
++{
++	struct tps23881_priv *priv = to_tps23881_priv(pcdev);
++	int ret, uV, uA;
++	u64 tmp_64;
++	u8 chan;
++
++	ret = tps23881_pi_get_voltage(&priv->pcdev, id);
++	if (ret < 0)
++		return ret;
++	uV = ret;
++
++	chan = priv->port[id].chan[0];
++	ret = tps23881_pi_get_chan_current(priv, chan);
++	if (ret < 0)
++		return ret;
++	uA = ret;
++
++	if (priv->port[id].is_4p) {
++		chan = priv->port[id].chan[1];
++		ret = tps23881_pi_get_chan_current(priv, chan);
++		if (ret < 0)
++			return ret;
++		uA += ret;
++	}
++
++	tmp_64 = uV;
++	tmp_64 *= uA;
++	/* mW = uV * uA / 1000000000 */
++	return DIV_ROUND_CLOSEST_ULL(tmp_64, 1000000000);
++}
++
++static int
++tps23881_pi_get_pw_limit_chan(struct tps23881_priv *priv, u8 chan)
++{
++	struct i2c_client *client = priv->client;
++	int ret, reg;
++	u16 val;
++
++	reg = TPS23881_REG_2PAIR_POL1 + (chan % 4);
++	ret = i2c_smbus_read_word_data(client, reg);
++	if (ret < 0)
++		return ret;
++
++	val = tps23881_calc_val(ret, chan, 0, 0xff);
++	return val * TPS23881_MW_STEP;
++}
++
++static int tps23881_pi_get_pw_limit(struct pse_controller_dev *pcdev, int id)
++{
++	struct tps23881_priv *priv = to_tps23881_priv(pcdev);
++	int ret, mW;
++	u8 chan;
++
++	chan = priv->port[id].chan[0];
++	ret = tps23881_pi_get_pw_limit_chan(priv, chan);
++	if (ret < 0)
++		return ret;
++
++	mW = ret;
++	if (priv->port[id].is_4p) {
++		chan = priv->port[id].chan[1];
++		ret = tps23881_pi_get_pw_limit_chan(priv, chan);
++		if (ret < 0)
++			return ret;
++		mW += ret;
++	}
++
++	return mW;
++}
++
++static int tps23881_pi_set_pw_limit(struct pse_controller_dev *pcdev,
++				    int id, int max_mW)
++{
++	struct tps23881_priv *priv = to_tps23881_priv(pcdev);
++	u8 pw_pol;
++	int ret;
++
++	if (max_mW < TPS23881_MIN_PI_PW_LIMIT_MW || MAX_PI_PW < max_mW) {
++		dev_err(&priv->client->dev,
++			"power limit %d out of ranges [%d,%d]",
++			max_mW, TPS23881_MIN_PI_PW_LIMIT_MW, MAX_PI_PW);
++		return -ERANGE;
++	}
++
++	ret = tps23881_pi_enable_manual_pol(priv, id);
++	if (ret < 0)
++		return ret;
++
++	pw_pol = DIV_ROUND_CLOSEST_ULL(max_mW, TPS23881_MW_STEP);
++
++	/* Save power policy to reconfigure it after a disabled call */
++	priv->port[id].pw_pol = pw_pol;
++	return tps23881_pi_set_pw_pol_limit(priv, id, pw_pol,
++					    priv->port[id].is_4p);
++}
++
++static int
++tps23881_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(sizeof(*c33_pw_limit_ranges),
++				      GFP_KERNEL);
++	if (!c33_pw_limit_ranges)
++		return -ENOMEM;
++
++	c33_pw_limit_ranges->min = TPS23881_MIN_PI_PW_LIMIT_MW;
++	c33_pw_limit_ranges->max = MAX_PI_PW;
++	pw_limit_ranges->c33_pw_limit_ranges = c33_pw_limit_ranges;
++
++	/* Return the number of ranges */
++	return 1;
++}
++
+ /* Parse managers subnode into a array of device node */
+ static int
+ tps23881_get_of_channels(struct tps23881_priv *priv,
+@@ -540,6 +785,9 @@ tps23881_write_port_matrix(struct tps238
+ 		if (port_matrix[i].exist)
+ 			priv->port[pi_id].chan[0] = lgcl_chan;
+ 
++		/* Initialize power policy internal value */
++		priv->port[pi_id].pw_pol = -1;
++
+ 		/* Set hardware port matrix for all ports */
+ 		val |= hw_chan << (lgcl_chan * 2);
+ 
+@@ -665,6 +913,12 @@ static const struct pse_controller_ops t
+ 	.pi_disable = tps23881_pi_disable,
+ 	.pi_get_admin_state = tps23881_pi_get_admin_state,
+ 	.pi_get_pw_status = tps23881_pi_get_pw_status,
++	.pi_get_pw_class = tps23881_pi_get_pw_class,
++	.pi_get_actual_pw = tps23881_pi_get_actual_pw,
++	.pi_get_voltage = tps23881_pi_get_voltage,
++	.pi_get_pw_limit = tps23881_pi_get_pw_limit,
++	.pi_set_pw_limit = tps23881_pi_set_pw_limit,
++	.pi_get_pw_limit_ranges = tps23881_pi_get_pw_limit_ranges,
+ };
+ 
+ static const char fw_parity_name[] = "ti/tps23881/tps23881-parity-14.bin";
diff --git a/target/linux/generic/backport-6.12/626-08-v6.13-net-pse-pd-Fix-missing-PI-of_node-description.patch b/target/linux/generic/backport-6.12/626-08-v6.13-net-pse-pd-Fix-missing-PI-of_node-description.patch
new file mode 100644
index 0000000000..3aa46cde42
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-08-v6.13-net-pse-pd-Fix-missing-PI-of_node-description.patch
@@ -0,0 +1,28 @@
+From 10276f3e1c7e7f5de9f0bba58f8a849cb195253d Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent at bootlin.com>
+Date: Fri, 10 Jan 2025 10:40:30 +0100
+Subject: [PATCH] net: pse-pd: Fix missing PI of_node description
+
+The PI of_node was not assigned in the regulator_config structure, leading
+to failures in resolving the correct supply when different power supplies
+are assigned to multiple PIs of a PSE controller. This fix ensures that the
+of_node is properly set in the regulator_config, allowing accurate supply
+resolution for each PI.
+
+Acked-by: Oleksij Rempel <o.rempel at pengutronix.de>
+Signed-off-by: Kory Maincent <kory.maincent at bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni at redhat.com>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/pse_core.c | 1 +
+ 1 file changed, 1 insertion(+)
+--- a/drivers/net/pse-pd/pse_core.c
++++ b/drivers/net/pse-pd/pse_core.c
+@@ -422,6 +422,7 @@ devm_pse_pi_regulator_register(struct ps
+ 	rconfig.dev = pcdev->dev;
+ 	rconfig.driver_data = pcdev;
+ 	rconfig.init_data = rinit_data;
++	rconfig.of_node = pcdev->pi[id].np;
+ 
+ 	rdev = devm_regulator_register(pcdev->dev, rdesc, &rconfig);
+ 	if (IS_ERR(rdev)) {
diff --git a/target/linux/generic/backport-6.12/626-09-v6.13-net-pse-pd-Clean-ethtool-header-of-PSE-structures.patch b/target/linux/generic/backport-6.12/626-09-v6.13-net-pse-pd-Clean-ethtool-header-of-PSE-structures.patch
new file mode 100644
index 0000000000..7d338125a0
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-09-v6.13-net-pse-pd-Clean-ethtool-header-of-PSE-structures.patch
@@ -0,0 +1,92 @@
+From 5385f1e1923ca8131eb143567d509b101a344e06 Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent at bootlin.com>
+Date: Fri, 10 Jan 2025 10:40:31 +0100
+Subject: [PATCH] net: pse-pd: Clean ethtool header of PSE structures
+
+Remove PSE-specific structures from the ethtool header to improve code
+modularity, maintain independent headers, and reduce incremental build
+time.
+
+Signed-off-by: Kory Maincent <kory.maincent at bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni at redhat.com>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/pse_core.c |  1 +
+ include/linux/ethtool.h       | 20 --------------------
+ include/linux/pse-pd/pse.h    | 22 +++++++++++++++++++++-
+ 3 files changed, 22 insertions(+), 21 deletions(-)
+--- a/drivers/net/pse-pd/pse_core.c
++++ b/drivers/net/pse-pd/pse_core.c
+@@ -6,6 +6,7 @@
+ //
+ 
+ #include <linux/device.h>
++#include <linux/ethtool.h>
+ #include <linux/of.h>
+ #include <linux/pse-pd/pse.h>
+ #include <linux/regulator/driver.h>
+--- a/include/linux/ethtool.h
++++ b/include/linux/ethtool.h
+@@ -1322,24 +1322,4 @@ struct ethtool_forced_speed_map {
+ 
+ void
+ ethtool_forced_speed_maps_init(struct ethtool_forced_speed_map *maps, u32 size);
+-
+-/* C33 PSE extended state and substate. */
+-struct ethtool_c33_pse_ext_state_info {
+-	enum ethtool_c33_pse_ext_state c33_pse_ext_state;
+-	union {
+-		enum ethtool_c33_pse_ext_substate_error_condition error_condition;
+-		enum ethtool_c33_pse_ext_substate_mr_pse_enable mr_pse_enable;
+-		enum ethtool_c33_pse_ext_substate_option_detect_ted option_detect_ted;
+-		enum ethtool_c33_pse_ext_substate_option_vport_lim option_vport_lim;
+-		enum ethtool_c33_pse_ext_substate_ovld_detected ovld_detected;
+-		enum ethtool_c33_pse_ext_substate_power_not_available power_not_available;
+-		enum ethtool_c33_pse_ext_substate_short_detected short_detected;
+-		u32 __c33_pse_ext_substate;
+-	};
+-};
+-
+-struct ethtool_c33_pse_pw_limit_range {
+-	u32 min;
+-	u32 max;
+-};
+ #endif /* _LINUX_ETHTOOL_H */
+--- a/include/linux/pse-pd/pse.h
++++ b/include/linux/pse-pd/pse.h
+@@ -5,7 +5,6 @@
+ #ifndef _LINUX_PSE_CONTROLLER_H
+ #define _LINUX_PSE_CONTROLLER_H
+ 
+-#include <linux/ethtool.h>
+ #include <linux/list.h>
+ #include <uapi/linux/ethtool.h>
+ 
+@@ -16,6 +15,27 @@
+ 
+ struct phy_device;
+ struct pse_controller_dev;
++struct netlink_ext_ack;
++
++/* C33 PSE extended state and substate. */
++struct ethtool_c33_pse_ext_state_info {
++	enum ethtool_c33_pse_ext_state c33_pse_ext_state;
++	union {
++		enum ethtool_c33_pse_ext_substate_error_condition error_condition;
++		enum ethtool_c33_pse_ext_substate_mr_pse_enable mr_pse_enable;
++		enum ethtool_c33_pse_ext_substate_option_detect_ted option_detect_ted;
++		enum ethtool_c33_pse_ext_substate_option_vport_lim option_vport_lim;
++		enum ethtool_c33_pse_ext_substate_ovld_detected ovld_detected;
++		enum ethtool_c33_pse_ext_substate_power_not_available power_not_available;
++		enum ethtool_c33_pse_ext_substate_short_detected short_detected;
++		u32 __c33_pse_ext_substate;
++	};
++};
++
++struct ethtool_c33_pse_pw_limit_range {
++	u32 min;
++	u32 max;
++};
+ 
+ /**
+  * struct pse_control_config - PSE control/channel configuration.
diff --git a/target/linux/generic/backport-6.12/626-10-v6.17-net-pse-pd-Introduce-attached_phydev-to-pse-control.patch b/target/linux/generic/backport-6.12/626-10-v6.17-net-pse-pd-Introduce-attached_phydev-to-pse-control.patch
new file mode 100644
index 0000000000..b00c0e1475
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-10-v6.17-net-pse-pd-Introduce-attached_phydev-to-pse-control.patch
@@ -0,0 +1,171 @@
+From fa2f0454174c2f33005f5a6e6f70c7160a15b2a1 Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent at bootlin.com>
+Date: Tue, 17 Jun 2025 14:12:00 +0200
+Subject: [PATCH] net: pse-pd: Introduce attached_phydev to pse control
+
+In preparation for reporting PSE events via ethtool notifications,
+introduce an attached_phydev field in the pse_control structure.
+This field stores the phy_device associated with the PSE PI,
+ensuring that notifications are sent to the correct network
+interface.
+
+The attached_phydev pointer is directly tied to the PHY lifecycle. It
+is set when the PHY is registered and cleared when the PHY is removed.
+There is no need to use a refcount, as doing so could interfere with
+the PHY removal process.
+
+Signed-off-by: Kory Maincent (Dent Project) <kory.maincent at bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel at pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-1-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba at kernel.org>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/mdio/fwnode_mdio.c | 26 ++++++++++++++------------
+ drivers/net/pse-pd/pse_core.c  | 11 ++++++++---
+ include/linux/pse-pd/pse.h     |  6 ++++--
+ 3 files changed, 26 insertions(+), 17 deletions(-)
+--- a/drivers/net/mdio/fwnode_mdio.c
++++ b/drivers/net/mdio/fwnode_mdio.c
+@@ -18,7 +18,8 @@ MODULE_LICENSE("GPL");
+ MODULE_DESCRIPTION("FWNODE MDIO bus (Ethernet PHY) accessors");
+ 
+ static struct pse_control *
+-fwnode_find_pse_control(struct fwnode_handle *fwnode)
++fwnode_find_pse_control(struct fwnode_handle *fwnode,
++			struct phy_device *phydev)
+ {
+ 	struct pse_control *psec;
+ 	struct device_node *np;
+@@ -30,7 +31,7 @@ fwnode_find_pse_control(struct fwnode_ha
+ 	if (!np)
+ 		return NULL;
+ 
+-	psec = of_pse_control_get(np);
++	psec = of_pse_control_get(np, phydev);
+ 	if (PTR_ERR(psec) == -ENOENT)
+ 		return NULL;
+ 
+@@ -128,15 +129,9 @@ int fwnode_mdiobus_register_phy(struct m
+ 	u32 phy_id;
+ 	int rc;
+ 
+-	psec = fwnode_find_pse_control(child);
+-	if (IS_ERR(psec))
+-		return PTR_ERR(psec);
+-
+ 	mii_ts = fwnode_find_mii_timestamper(child);
+-	if (IS_ERR(mii_ts)) {
+-		rc = PTR_ERR(mii_ts);
+-		goto clean_pse;
+-	}
++	if (IS_ERR(mii_ts))
++		return PTR_ERR(mii_ts);
+ 
+ 	is_c45 = fwnode_device_is_compatible(child, "ethernet-phy-ieee802.3-c45");
+ 	if (is_c45 || fwnode_get_phy_id(child, &phy_id))
+@@ -169,6 +164,12 @@ int fwnode_mdiobus_register_phy(struct m
+ 			goto clean_phy;
+ 	}
+ 
++	psec = fwnode_find_pse_control(child, phy);
++	if (IS_ERR(psec)) {
++		rc = PTR_ERR(psec);
++		goto unregister_phy;
++	}
++
+ 	phy->psec = psec;
+ 
+ 	/* phy->mii_ts may already be defined by the PHY driver. A
+@@ -180,12 +181,13 @@ int fwnode_mdiobus_register_phy(struct m
+ 
+ 	return 0;
+ 
++unregister_phy:
++	if (is_acpi_node(child) || is_of_node(child))
++		phy_device_remove(phy);
+ clean_phy:
+ 	phy_device_free(phy);
+ clean_mii_ts:
+ 	unregister_mii_timestamper(mii_ts);
+-clean_pse:
+-	pse_control_put(psec);
+ 
+ 	return rc;
+ }
+--- a/drivers/net/pse-pd/pse_core.c
++++ b/drivers/net/pse-pd/pse_core.c
+@@ -23,6 +23,7 @@ static LIST_HEAD(pse_controller_list);
+  * @list: list entry for the pcdev's PSE controller list
+  * @id: ID of the PSE line in the PSE controller device
+  * @refcnt: Number of gets of this pse_control
++ * @attached_phydev: PHY device pointer attached by the PSE control
+  */
+ struct pse_control {
+ 	struct pse_controller_dev *pcdev;
+@@ -30,6 +31,7 @@ struct pse_control {
+ 	struct list_head list;
+ 	unsigned int id;
+ 	struct kref refcnt;
++	struct phy_device *attached_phydev;
+ };
+ 
+ static int of_load_single_pse_pi_pairset(struct device_node *node,
+@@ -599,7 +601,8 @@ void pse_control_put(struct pse_control
+ EXPORT_SYMBOL_GPL(pse_control_put);
+ 
+ static struct pse_control *
+-pse_control_get_internal(struct pse_controller_dev *pcdev, unsigned int index)
++pse_control_get_internal(struct pse_controller_dev *pcdev, unsigned int index,
++			 struct phy_device *phydev)
+ {
+ 	struct pse_control *psec;
+ 	int ret;
+@@ -638,6 +641,7 @@ pse_control_get_internal(struct pse_cont
+ 	psec->pcdev = pcdev;
+ 	list_add(&psec->list, &pcdev->pse_control_head);
+ 	psec->id = index;
++	psec->attached_phydev = phydev;
+ 	kref_init(&psec->refcnt);
+ 
+ 	return psec;
+@@ -693,7 +697,8 @@ static int psec_id_xlate(struct pse_cont
+ 	return pse_spec->args[0];
+ }
+ 
+-struct pse_control *of_pse_control_get(struct device_node *node)
++struct pse_control *of_pse_control_get(struct device_node *node,
++				       struct phy_device *phydev)
+ {
+ 	struct pse_controller_dev *r, *pcdev;
+ 	struct of_phandle_args args;
+@@ -743,7 +748,7 @@ struct pse_control *of_pse_control_get(s
+ 	}
+ 
+ 	/* pse_list_mutex also protects the pcdev's pse_control list */
+-	psec = pse_control_get_internal(pcdev, psec_id);
++	psec = pse_control_get_internal(pcdev, psec_id, phydev);
+ 
+ out:
+ 	mutex_unlock(&pse_list_mutex);
+--- a/include/linux/pse-pd/pse.h
++++ b/include/linux/pse-pd/pse.h
+@@ -250,7 +250,8 @@ struct device;
+ int devm_pse_controller_register(struct device *dev,
+ 				 struct pse_controller_dev *pcdev);
+ 
+-struct pse_control *of_pse_control_get(struct device_node *node);
++struct pse_control *of_pse_control_get(struct device_node *node,
++				       struct phy_device *phydev);
+ void pse_control_put(struct pse_control *psec);
+ 
+ int pse_ethtool_get_status(struct pse_control *psec,
+@@ -270,7 +271,8 @@ bool pse_has_c33(struct pse_control *pse
+ 
+ #else
+ 
+-static inline struct pse_control *of_pse_control_get(struct device_node *node)
++static inline struct pse_control *of_pse_control_get(struct device_node *node,
++						     struct phy_device *phydev)
+ {
+ 	return ERR_PTR(-ENOENT);
+ }
diff --git a/target/linux/generic/backport-6.12/626-11-v6.17-net-pse-pd-Add-support-for-reporting-events.patch b/target/linux/generic/backport-6.12/626-11-v6.17-net-pse-pd-Add-support-for-reporting-events.patch
new file mode 100644
index 0000000000..ec99fcab82
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-11-v6.17-net-pse-pd-Add-support-for-reporting-events.patch
@@ -0,0 +1,363 @@
+# ADAPTED FOR OPENWRT 6.12.67 - Documentation changes removed
+# Original commit: fc0e6db30941
+From fc0e6db30941a66e284b8516b82356f97f31061d Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent at bootlin.com>
+Date: Tue, 17 Jun 2025 14:12:01 +0200
+Subject: [PATCH] net: pse-pd: Add support for reporting events
+
+Add support for devm_pse_irq_helper() to register PSE interrupts and report
+events such as over-current or over-temperature conditions. This follows a
+similar approach to the regulator API but also sends notifications using a
+dedicated PSE ethtool netlink socket.
+
+Signed-off-by: Kory Maincent (Dent Project) <kory.maincent at bootlin.com>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-2-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba at kernel.org>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ Documentation/netlink/specs/ethtool.yaml      |  34 ++++
+ Documentation/networking/ethtool-netlink.rst  |  19 ++
+ drivers/net/pse-pd/pse_core.c                 | 179 ++++++++++++++++++
+ include/linux/ethtool_netlink.h               |   7 +
+ include/linux/pse-pd/pse.h                    |  20 ++
+ .../uapi/linux/ethtool_netlink_generated.h    |  19 ++
+ net/ethtool/pse-pd.c                          |  39 ++++
+ 7 files changed, 317 insertions(+)
+
+--- a/drivers/net/pse-pd/pse_core.c
++++ b/drivers/net/pse-pd/pse_core.c
+@@ -7,10 +7,14 @@
+ 
+ #include <linux/device.h>
+ #include <linux/ethtool.h>
++#include <linux/ethtool_netlink.h>
+ #include <linux/of.h>
++#include <linux/phy.h>
+ #include <linux/pse-pd/pse.h>
+ #include <linux/regulator/driver.h>
+ #include <linux/regulator/machine.h>
++#include <linux/rtnetlink.h>
++#include <net/net_trackers.h>
+ 
+ static DEFINE_MUTEX(pse_list_mutex);
+ static LIST_HEAD(pse_controller_list);
+@@ -210,6 +214,48 @@ out:
+ 	return ret;
+ }
+ 
++/**
++ * pse_control_find_net_by_id - Find net attached to the pse control id
++ * @pcdev: a pointer to the PSE
++ * @id: index of the PSE control
++ *
++ * Return: pse_control pointer or NULL. The device returned has had a
++ *	   reference added and the pointer is safe until the user calls
++ *	   pse_control_put() to indicate they have finished with it.
++ */
++static struct pse_control *
++pse_control_find_by_id(struct pse_controller_dev *pcdev, int id)
++{
++	struct pse_control *psec;
++
++	mutex_lock(&pse_list_mutex);
++	list_for_each_entry(psec, &pcdev->pse_control_head, list) {
++		if (psec->id == id) {
++			kref_get(&psec->refcnt);
++			mutex_unlock(&pse_list_mutex);
++			return psec;
++		}
++	}
++	mutex_unlock(&pse_list_mutex);
++	return NULL;
++}
++
++/**
++ * pse_control_get_netdev - Return netdev associated to a PSE control
++ * @psec: PSE control pointer
++ *
++ * Return: netdev pointer or NULL
++ */
++static struct net_device *pse_control_get_netdev(struct pse_control *psec)
++{
++	ASSERT_RTNL();
++
++	if (!psec || !psec->attached_phydev)
++		return NULL;
++
++	return psec->attached_phydev->attached_dev;
++}
++
+ static int pse_pi_is_enabled(struct regulator_dev *rdev)
+ {
+ 	struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
+@@ -559,6 +605,139 @@ int devm_pse_controller_register(struct
+ }
+ EXPORT_SYMBOL_GPL(devm_pse_controller_register);
+ 
++struct pse_irq {
++	struct pse_controller_dev *pcdev;
++	struct pse_irq_desc desc;
++	unsigned long *notifs;
++};
++
++/**
++ * pse_to_regulator_notifs - Convert PSE notifications to Regulator
++ *			     notifications
++ * @notifs: PSE notifications
++ *
++ * Return: Regulator notifications
++ */
++static unsigned long pse_to_regulator_notifs(unsigned long notifs)
++{
++	unsigned long rnotifs = 0;
++
++	if (notifs & ETHTOOL_PSE_EVENT_OVER_CURRENT)
++		rnotifs |= REGULATOR_EVENT_OVER_CURRENT;
++	if (notifs & ETHTOOL_PSE_EVENT_OVER_TEMP)
++		rnotifs |= REGULATOR_EVENT_OVER_TEMP;
++
++	return rnotifs;
++}
++
++/**
++ * pse_isr - IRQ handler for PSE
++ * @irq: irq number
++ * @data: pointer to user interrupt structure
++ *
++ * Return: irqreturn_t - status of IRQ
++ */
++static irqreturn_t pse_isr(int irq, void *data)
++{
++	struct pse_controller_dev *pcdev;
++	unsigned long notifs_mask = 0;
++	struct pse_irq_desc *desc;
++	struct pse_irq *h = data;
++	int ret, i;
++
++	desc = &h->desc;
++	pcdev = h->pcdev;
++
++	/* Clear notifs mask */
++	memset(h->notifs, 0, pcdev->nr_lines * sizeof(*h->notifs));
++	mutex_lock(&pcdev->lock);
++	ret = desc->map_event(irq, pcdev, h->notifs, &notifs_mask);
++	mutex_unlock(&pcdev->lock);
++	if (ret || !notifs_mask)
++		return IRQ_NONE;
++
++	for_each_set_bit(i, &notifs_mask, pcdev->nr_lines) {
++		unsigned long notifs, rnotifs;
++		struct net_device *netdev;
++		struct pse_control *psec;
++
++		/* Do nothing PI not described */
++		if (!pcdev->pi[i].rdev)
++			continue;
++
++		notifs = h->notifs[i];
++		dev_dbg(h->pcdev->dev,
++			"Sending PSE notification EVT 0x%lx\n", notifs);
++
++		psec = pse_control_find_by_id(pcdev, i);
++		rtnl_lock();
++		netdev = pse_control_get_netdev(psec);
++		if (netdev)
++			ethnl_pse_send_ntf(netdev, notifs);
++		rtnl_unlock();
++		pse_control_put(psec);
++
++		rnotifs = pse_to_regulator_notifs(notifs);
++		regulator_notifier_call_chain(pcdev->pi[i].rdev, rnotifs,
++					      NULL);
++	}
++
++	return IRQ_HANDLED;
++}
++
++/**
++ * devm_pse_irq_helper - Register IRQ based PSE event notifier
++ * @pcdev: a pointer to the PSE
++ * @irq: the irq value to be passed to request_irq
++ * @irq_flags: the flags to be passed to request_irq
++ * @d: PSE interrupt description
++ *
++ * Return: 0 on success and errno on failure
++ */
++int devm_pse_irq_helper(struct pse_controller_dev *pcdev, int irq,
++			int irq_flags, const struct pse_irq_desc *d)
++{
++	struct device *dev = pcdev->dev;
++	size_t irq_name_len;
++	struct pse_irq *h;
++	char *irq_name;
++	int ret;
++
++	if (!d || !d->map_event || !d->name)
++		return -EINVAL;
++
++	h = devm_kzalloc(dev, sizeof(*h), GFP_KERNEL);
++	if (!h)
++		return -ENOMEM;
++
++	h->pcdev = pcdev;
++	h->desc = *d;
++
++	/* IRQ name len is pcdev dev name + 5 char + irq desc name + 1 */
++	irq_name_len = strlen(dev_name(pcdev->dev)) + 5 + strlen(d->name) + 1;
++	irq_name = devm_kzalloc(dev, irq_name_len, GFP_KERNEL);
++	if (!irq_name)
++		return -ENOMEM;
++
++	snprintf(irq_name, irq_name_len, "pse-%s:%s", dev_name(pcdev->dev),
++		 d->name);
++
++	h->notifs = devm_kcalloc(dev, pcdev->nr_lines,
++				 sizeof(*h->notifs), GFP_KERNEL);
++	if (!h->notifs)
++		return -ENOMEM;
++
++	ret = devm_request_threaded_irq(dev, irq, NULL, pse_isr,
++					IRQF_ONESHOT | irq_flags,
++					irq_name, h);
++	if (ret)
++		dev_err(pcdev->dev, "Failed to request IRQ %d\n", irq);
++
++	pcdev->irq = irq;
++	return ret;
++}
++EXPORT_SYMBOL_GPL(devm_pse_irq_helper);
++
+ /* PSE control section */
+ 
+ static void __pse_control_release(struct kref *kref)
+--- a/include/linux/ethtool_netlink.h
++++ b/include/linux/ethtool_netlink.h
+@@ -43,6 +43,8 @@ void ethtool_aggregate_rmon_stats(struct
+ 				  struct ethtool_rmon_stats *rmon_stats);
+ bool ethtool_dev_mm_supported(struct net_device *dev);
+ 
++void ethnl_pse_send_ntf(struct net_device *netdev, unsigned long notif);
++
+ #else
+ static inline int ethnl_cable_test_alloc(struct phy_device *phydev, u8 cmd)
+ {
+@@ -120,6 +122,11 @@ static inline bool ethtool_dev_mm_suppor
+ 	return false;
+ }
+ 
++static inline void ethnl_pse_send_ntf(struct phy_device *phydev,
++				      unsigned long notif)
++{
++}
++
+ #endif /* IS_ENABLED(CONFIG_ETHTOOL_NETLINK) */
+ 
+ static inline int ethnl_cable_test_result(struct phy_device *phydev, u8 pair,
+--- a/include/linux/pse-pd/pse.h
++++ b/include/linux/pse-pd/pse.h
+@@ -7,12 +7,15 @@
+ 
+ #include <linux/list.h>
+ #include <uapi/linux/ethtool.h>
++#include <uapi/linux/ethtool_netlink_generated.h>
++#include <linux/regulator/driver.h>
+ 
+ /* Maximum current in uA according to IEEE 802.3-2022 Table 145-1 */
+ #define MAX_PI_CURRENT 1920000
+ /* Maximum power in mW according to IEEE 802.3-2022 Table 145-16 */
+ #define MAX_PI_PW 99900
+ 
++struct net_device;
+ struct phy_device;
+ struct pse_controller_dev;
+ struct netlink_ext_ack;
+@@ -38,6 +41,19 @@ struct ethtool_c33_pse_pw_limit_range {
+ };
+ 
+ /**
++ * struct pse_irq_desc - notification sender description for IRQ based events.
++ *
++ * @name: the visible name for the IRQ
++ * @map_event: driver callback to map IRQ status into PSE devices with events.
++ */
++struct pse_irq_desc {
++	const char *name;
++	int (*map_event)(int irq, struct pse_controller_dev *pcdev,
++			 unsigned long *notifs,
++			 unsigned long *notifs_mask);
++};
++
++/**
+  * struct pse_control_config - PSE control/channel configuration.
+  *
+  * @podl_admin_control: set PoDL PSE admin control as described in
+@@ -228,6 +244,7 @@ struct pse_pi {
+  * @types: types of the PSE controller
+  * @pi: table of PSE PIs described in this controller device
+  * @no_of_pse_pi: flag set if the pse_pis devicetree node is not used
++ * @irq: PSE interrupt
+  */
+ struct pse_controller_dev {
+ 	const struct pse_controller_ops *ops;
+@@ -241,6 +258,7 @@ struct pse_controller_dev {
+ 	enum ethtool_pse_types types;
+ 	struct pse_pi *pi;
+ 	bool no_of_pse_pi;
++	int irq;
+ };
+ 
+ #if IS_ENABLED(CONFIG_PSE_CONTROLLER)
+@@ -249,6 +267,8 @@ void pse_controller_unregister(struct ps
+ struct device;
+ int devm_pse_controller_register(struct device *dev,
+ 				 struct pse_controller_dev *pcdev);
++int devm_pse_irq_helper(struct pse_controller_dev *pcdev, int irq,
++			int irq_flags, const struct pse_irq_desc *d);
+ 
+ struct pse_control *of_pse_control_get(struct device_node *node,
+ 				       struct phy_device *phydev);
+--- a/net/ethtool/pse-pd.c
++++ b/net/ethtool/pse-pd.c
+@@ -315,3 +315,42 @@ const struct ethnl_request_ops ethnl_pse
+ 	.set			= ethnl_set_pse,
+ 	/* PSE has no notification */
+ };
++
++void ethnl_pse_send_ntf(struct net_device *netdev, unsigned long notifs)
++{
++	void *reply_payload;
++	struct sk_buff *skb;
++	int reply_len;
++	int ret;
++
++	ASSERT_RTNL();
++
++	if (!netdev || !notifs)
++		return;
++
++	reply_len = ethnl_reply_header_size() +
++		    nla_total_size(sizeof(u32)); /* _PSE_NTF_EVENTS */
++
++	skb = genlmsg_new(reply_len, GFP_KERNEL);
++	if (!skb)
++		return;
++
++	reply_payload = ethnl_bcastmsg_put(skb, ETHTOOL_MSG_PSE_NTF);
++	if (!reply_payload)
++		goto err_skb;
++
++	ret = ethnl_fill_reply_header(skb, netdev, ETHTOOL_A_PSE_NTF_HEADER);
++	if (ret < 0)
++		goto err_skb;
++
++	if (nla_put_uint(skb, ETHTOOL_A_PSE_NTF_EVENTS, notifs))
++		goto err_skb;
++
++	genlmsg_end(skb, reply_payload);
++	ethnl_multicast(skb, netdev);
++	return;
++
++err_skb:
++	nlmsg_free(skb);
++}
++EXPORT_SYMBOL_GPL(ethnl_pse_send_ntf);
diff --git a/target/linux/generic/backport-6.12/626-12-v6.17-net-pse-pd-tps23881-Add-support-for-PSE-events.patch b/target/linux/generic/backport-6.12/626-12-v6.17-net-pse-pd-tps23881-Add-support-for-PSE-events.patch
new file mode 100644
index 0000000000..8afcb3f870
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-12-v6.17-net-pse-pd-tps23881-Add-support-for-PSE-events.patch
@@ -0,0 +1,252 @@
+From f5e7aecaa4efcd4c85477b6a62f94fea668031db Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent at bootlin.com>
+Date: Tue, 17 Jun 2025 14:12:02 +0200
+Subject: [PATCH] net: pse-pd: tps23881: Add support for PSE events and
+ interrupts
+
+Add support for PSE event reporting through interrupts. Set up the newly
+introduced devm_pse_irq_helper helper to register the interrupt. Events are
+reported for over-current and over-temperature conditions.
+
+Signed-off-by: Kory Maincent (Dent Project) <kory.maincent at bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel at pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-3-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba at kernel.org>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/tps23881.c | 189 +++++++++++++++++++++++++++++++++-
+ 1 file changed, 187 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/pse-pd/tps23881.c
++++ b/drivers/net/pse-pd/tps23881.c
+@@ -16,7 +16,15 @@
+ #include <linux/pse-pd/pse.h>
+ 
+ #define TPS23881_MAX_CHANS 8
++#define TPS23881_MAX_IRQ_RETRIES 10
+ 
++#define TPS23881_REG_IT		0x0
++#define TPS23881_REG_IT_MASK	0x1
++#define TPS23881_REG_IT_IFAULT	BIT(5)
++#define TPS23881_REG_IT_SUPF	BIT(7)
++#define TPS23881_REG_FAULT	0x7
++#define TPS23881_REG_SUPF_EVENT	0xb
++#define TPS23881_REG_TSD	BIT(7)
+ #define TPS23881_REG_PW_STATUS	0x10
+ #define TPS23881_REG_OP_MODE	0x12
+ #define TPS23881_OP_MODE_SEMIAUTO	0xaaaa
+@@ -24,6 +32,7 @@
+ #define TPS23881_REG_DET_CLA_EN	0x14
+ #define TPS23881_REG_GEN_MASK	0x17
+ #define TPS23881_REG_NBITACC	BIT(5)
++#define TPS23881_REG_INTEN	BIT(7)
+ #define TPS23881_REG_PW_EN	0x19
+ #define TPS23881_REG_2PAIR_POL1	0x1e
+ #define TPS23881_REG_PORT_MAP	0x26
+@@ -51,6 +60,7 @@ struct tps23881_port_desc {
+ 	u8 chan[2];
+ 	bool is_4p;
+ 	int pw_pol;
++	bool exist;
+ };
+ 
+ struct tps23881_priv {
+@@ -782,8 +792,10 @@ tps23881_write_port_matrix(struct tps238
+ 		hw_chan = port_matrix[i].hw_chan[0] % 4;
+ 
+ 		/* Set software port matrix for existing ports */
+-		if (port_matrix[i].exist)
++		if (port_matrix[i].exist) {
+ 			priv->port[pi_id].chan[0] = lgcl_chan;
++			priv->port[pi_id].exist = true;
++		}
+ 
+ 		/* Initialize power policy internal value */
+ 		priv->port[pi_id].pw_pol = -1;
+@@ -1017,6 +1029,173 @@ static int tps23881_flash_sram_fw(struct
+ 	return 0;
+ }
+ 
++/* Convert interrupt events to 0xff to be aligned with the chan
++ * number.
++ */
++static u8 tps23881_irq_export_chans_helper(u16 reg_val, u8 field_offset)
++{
++	u8 val;
++
++	val = (reg_val >> (4 + field_offset) & 0xf0) |
++	      (reg_val >> field_offset & 0x0f);
++
++	return val;
++}
++
++/* Convert chan number to port number */
++static void tps23881_set_notifs_helper(struct tps23881_priv *priv,
++				       u8 chans,
++				       unsigned long *notifs,
++				       unsigned long *notifs_mask,
++				       enum ethtool_pse_event event)
++{
++	u8 chan;
++	int i;
++
++	if (!chans)
++		return;
++
++	for (i = 0; i < TPS23881_MAX_CHANS; i++) {
++		if (!priv->port[i].exist)
++			continue;
++		/* No need to look at the 2nd channel in case of PoE4 as
++		 * both registers are set.
++		 */
++		chan = priv->port[i].chan[0];
++
++		if (BIT(chan) & chans) {
++			*notifs_mask |= BIT(i);
++			notifs[i] |= event;
++		}
++	}
++}
++
++static void tps23881_irq_event_over_temp(struct tps23881_priv *priv,
++					 u16 reg_val,
++					 unsigned long *notifs,
++					 unsigned long *notifs_mask)
++{
++	int i;
++
++	if (reg_val & TPS23881_REG_TSD) {
++		for (i = 0; i < TPS23881_MAX_CHANS; i++) {
++			if (!priv->port[i].exist)
++				continue;
++
++			*notifs_mask |= BIT(i);
++			notifs[i] |= ETHTOOL_PSE_EVENT_OVER_TEMP;
++		}
++	}
++}
++
++static void tps23881_irq_event_over_current(struct tps23881_priv *priv,
++					    u16 reg_val,
++					    unsigned long *notifs,
++					    unsigned long *notifs_mask)
++{
++	u8 chans;
++
++	chans = tps23881_irq_export_chans_helper(reg_val, 0);
++	if (chans)
++		tps23881_set_notifs_helper(priv, chans, notifs, notifs_mask,
++					   ETHTOOL_PSE_EVENT_OVER_CURRENT);
++}
++
++static int tps23881_irq_event_handler(struct tps23881_priv *priv, u16 reg,
++				      unsigned long *notifs,
++				      unsigned long *notifs_mask)
++{
++	struct i2c_client *client = priv->client;
++	int ret;
++
++	/* The Supply event bit is repeated twice so we only need to read
++	 * the one from the first byte.
++	 */
++	if (reg & TPS23881_REG_IT_SUPF) {
++		ret = i2c_smbus_read_word_data(client, TPS23881_REG_SUPF_EVENT);
++		if (ret < 0)
++			return ret;
++		tps23881_irq_event_over_temp(priv, ret, notifs, notifs_mask);
++	}
++
++	if (reg & (TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_IFAULT << 8)) {
++		ret = i2c_smbus_read_word_data(client, TPS23881_REG_FAULT);
++		if (ret < 0)
++			return ret;
++		tps23881_irq_event_over_current(priv, ret, notifs, notifs_mask);
++	}
++
++	return 0;
++}
++
++static int tps23881_irq_handler(int irq, struct pse_controller_dev *pcdev,
++				unsigned long *notifs,
++				unsigned long *notifs_mask)
++{
++	struct tps23881_priv *priv = to_tps23881_priv(pcdev);
++	struct i2c_client *client = priv->client;
++	int ret, it_mask, retry;
++
++	/* Get interruption mask */
++	ret = i2c_smbus_read_word_data(client, TPS23881_REG_IT_MASK);
++	if (ret < 0)
++		return ret;
++	it_mask = ret;
++
++	/* Read interrupt register until it frees the interruption pin. */
++	retry = 0;
++	while (true) {
++		if (retry > TPS23881_MAX_IRQ_RETRIES) {
++			dev_err(&client->dev, "interrupt never freed");
++			return -ETIMEDOUT;
++		}
++
++		ret = i2c_smbus_read_word_data(client, TPS23881_REG_IT);
++		if (ret < 0)
++			return ret;
++
++		/* No more relevant interruption */
++		if (!(ret & it_mask))
++			return 0;
++
++		ret = tps23881_irq_event_handler(priv, (u16)ret, notifs,
++						 notifs_mask);
++		if (ret)
++			return ret;
++
++		retry++;
++	}
++	return 0;
++}
++
++static int tps23881_setup_irq(struct tps23881_priv *priv, int irq)
++{
++	struct i2c_client *client = priv->client;
++	struct pse_irq_desc irq_desc = {
++		.name = "tps23881-irq",
++		.map_event = tps23881_irq_handler,
++	};
++	int ret;
++	u16 val;
++
++	val = TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_SUPF;
++	val |= val << 8;
++	ret = i2c_smbus_write_word_data(client, TPS23881_REG_IT_MASK, val);
++	if (ret)
++		return ret;
++
++	ret = i2c_smbus_read_word_data(client, TPS23881_REG_GEN_MASK);
++	if (ret < 0)
++		return ret;
++
++	val = (u16)(ret | TPS23881_REG_INTEN | TPS23881_REG_INTEN << 8);
++	ret = i2c_smbus_write_word_data(client, TPS23881_REG_GEN_MASK, val);
++	if (ret < 0)
++		return ret;
++
++	return devm_pse_irq_helper(&priv->pcdev, irq, 0, &irq_desc);
++}
++
+ static int tps23881_i2c_probe(struct i2c_client *client)
+ {
+ 	struct device *dev = &client->dev;
+@@ -1097,6 +1276,12 @@ static int tps23881_i2c_probe(struct i2c
+ 				     "failed to register PSE controller\n");
+ 	}
+ 
++	if (client->irq) {
++		ret = tps23881_setup_irq(priv, client->irq);
++		if (ret)
++			return ret;
++	}
++
+ 	return ret;
+ }
+ 
diff --git a/target/linux/generic/backport-6.12/626-13-v6.17-net-pse-pd-Add-support-for-PSE-power-domains.patch b/target/linux/generic/backport-6.12/626-13-v6.17-net-pse-pd-Add-support-for-PSE-power-domains.patch
new file mode 100644
index 0000000000..a02bbbc28b
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-13-v6.17-net-pse-pd-Add-support-for-PSE-power-domains.patch
@@ -0,0 +1,218 @@
+From 50f8b341d26826aa5fdccb8f497fbff2500934b3 Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent at bootlin.com>
+Date: Tue, 17 Jun 2025 14:12:03 +0200
+Subject: [PATCH] net: pse-pd: Add support for PSE power domains
+
+Introduce PSE power domain support as groundwork for upcoming port
+priority features. Multiple PSE PIs can now be grouped under a single
+PSE power domain, enabling future enhancements like defining available
+power budgets, port priority modes, and disconnection policies. This
+setup will allow the system to assess whether activating a port would
+exceed the available power budget, preventing over-budget states
+proactively.
+
+Signed-off-by: Kory Maincent (Dent Project) <kory.maincent at bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel at pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-4-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba at kernel.org>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/pse_core.c | 140 ++++++++++++++++++++++++++++++++++
+ include/linux/pse-pd/pse.h    |   2 +
+ 2 files changed, 142 insertions(+)
+--- a/drivers/net/pse-pd/pse_core.c
++++ b/drivers/net/pse-pd/pse_core.c
+@@ -16,8 +16,12 @@
+ #include <linux/rtnetlink.h>
+ #include <net/net_trackers.h>
+ 
++#define PSE_PW_D_LIMIT INT_MAX
++
+ static DEFINE_MUTEX(pse_list_mutex);
+ static LIST_HEAD(pse_controller_list);
++static DEFINE_XARRAY_ALLOC(pse_pw_d_map);
++static DEFINE_MUTEX(pse_pw_d_mutex);
+ 
+ /**
+  * struct pse_control - a PSE control
+@@ -38,6 +42,18 @@ struct pse_control {
+ 	struct phy_device *attached_phydev;
+ };
+ 
++/**
++ * struct pse_power_domain - a PSE power domain
++ * @id: ID of the power domain
++ * @supply: Power supply the Power Domain
++ * @refcnt: Number of gets of this pse_power_domain
++ */
++struct pse_power_domain {
++	int id;
++	struct regulator *supply;
++	struct kref refcnt;
++};
++
+ static int of_load_single_pse_pi_pairset(struct device_node *node,
+ 					 struct pse_pi *pi,
+ 					 int pairset_num)
+@@ -485,6 +501,125 @@ devm_pse_pi_regulator_register(struct ps
+ 	return 0;
+ }
+ 
++static void __pse_pw_d_release(struct kref *kref)
++{
++	struct pse_power_domain *pw_d = container_of(kref,
++						     struct pse_power_domain,
++						     refcnt);
++
++	regulator_put(pw_d->supply);
++	xa_erase(&pse_pw_d_map, pw_d->id);
++	mutex_unlock(&pse_pw_d_mutex);
++}
++
++/**
++ * pse_flush_pw_ds - flush all PSE power domains of a PSE
++ * @pcdev: a pointer to the initialized PSE controller device
++ */
++static void pse_flush_pw_ds(struct pse_controller_dev *pcdev)
++{
++	struct pse_power_domain *pw_d;
++	int i;
++
++	for (i = 0; i < pcdev->nr_lines; i++) {
++		if (!pcdev->pi[i].pw_d)
++			continue;
++
++		pw_d = xa_load(&pse_pw_d_map, pcdev->pi[i].pw_d->id);
++		if (!pw_d)
++			continue;
++
++		kref_put_mutex(&pw_d->refcnt, __pse_pw_d_release,
++			       &pse_pw_d_mutex);
++	}
++}
++
++/**
++ * devm_pse_alloc_pw_d - allocate a new PSE power domain for a device
++ * @dev: device that is registering this PSE power domain
++ *
++ * Return: Pointer to the newly allocated PSE power domain or error pointers
++ */
++static struct pse_power_domain *devm_pse_alloc_pw_d(struct device *dev)
++{
++	struct pse_power_domain *pw_d;
++	int index, ret;
++
++	pw_d = devm_kzalloc(dev, sizeof(*pw_d), GFP_KERNEL);
++	if (!pw_d)
++		return ERR_PTR(-ENOMEM);
++
++	ret = xa_alloc(&pse_pw_d_map, &index, pw_d, XA_LIMIT(1, PSE_PW_D_LIMIT),
++		       GFP_KERNEL);
++	if (ret)
++		return ERR_PTR(ret);
++
++	kref_init(&pw_d->refcnt);
++	pw_d->id = index;
++	return pw_d;
++}
++
++/**
++ * pse_register_pw_ds - register the PSE power domains for a PSE
++ * @pcdev: a pointer to the PSE controller device
++ *
++ * Return: 0 on success and failure value on error
++ */
++static int pse_register_pw_ds(struct pse_controller_dev *pcdev)
++{
++	int i, ret = 0;
++
++	mutex_lock(&pse_pw_d_mutex);
++	for (i = 0; i < pcdev->nr_lines; i++) {
++		struct regulator_dev *rdev = pcdev->pi[i].rdev;
++		struct pse_power_domain *pw_d;
++		struct regulator *supply;
++		bool present = false;
++		unsigned long index;
++
++		/* No regulator or regulator parent supply registered.
++		 * We need a regulator parent to register a PSE power domain
++		 */
++		if (!rdev || !rdev->supply)
++			continue;
++
++		xa_for_each(&pse_pw_d_map, index, pw_d) {
++			/* Power supply already registered as a PSE power
++			 * domain.
++			 */
++			if (regulator_is_equal(pw_d->supply, rdev->supply)) {
++				present = true;
++				pcdev->pi[i].pw_d = pw_d;
++				break;
++			}
++		}
++		if (present) {
++			kref_get(&pw_d->refcnt);
++			continue;
++		}
++
++		pw_d = devm_pse_alloc_pw_d(pcdev->dev);
++		if (IS_ERR(pw_d)) {
++			ret = PTR_ERR(pw_d);
++			goto out;
++		}
++
++		supply = regulator_get(&rdev->dev, rdev->supply_name);
++		if (IS_ERR(supply)) {
++			xa_erase(&pse_pw_d_map, pw_d->id);
++			ret = PTR_ERR(supply);
++			goto out;
++		}
++
++		pw_d->supply = supply;
++		pcdev->pi[i].pw_d = pw_d;
++	}
++
++out:
++	mutex_unlock(&pse_pw_d_mutex);
++	return ret;
++}
++
+ /**
+  * pse_controller_register - register a PSE controller device
+  * @pcdev: a pointer to the initialized PSE controller device
+@@ -544,6 +679,10 @@ int pse_controller_register(struct pse_c
+ 			return ret;
+ 	}
+ 
++	ret = pse_register_pw_ds(pcdev);
++	if (ret)
++		return ret;
++
+ 	mutex_lock(&pse_list_mutex);
+ 	list_add(&pcdev->list, &pse_controller_list);
+ 	mutex_unlock(&pse_list_mutex);
+@@ -558,6 +697,7 @@ EXPORT_SYMBOL_GPL(pse_controller_registe
+  */
+ void pse_controller_unregister(struct pse_controller_dev *pcdev)
+ {
++	pse_flush_pw_ds(pcdev);
+ 	pse_release_pis(pcdev);
+ 	mutex_lock(&pse_list_mutex);
+ 	list_del(&pcdev->list);
+--- a/include/linux/pse-pd/pse.h
++++ b/include/linux/pse-pd/pse.h
+@@ -222,12 +222,14 @@ struct pse_pi_pairset {
+  * @np: device node pointer of the PSE PI node
+  * @rdev: regulator represented by the PSE PI
+  * @admin_state_enabled: PI enabled state
++ * @pw_d: Power domain of the PSE PI
+  */
+ struct pse_pi {
+ 	struct pse_pi_pairset pairset[2];
+ 	struct device_node *np;
+ 	struct regulator_dev *rdev;
+ 	bool admin_state_enabled;
++	struct pse_power_domain *pw_d;
+ };
+ 
+ /**
diff --git a/target/linux/generic/backport-6.12/626-14-v6.17-net-pse-pd-ethtool-Add-support-for-power-domains-index.patch b/target/linux/generic/backport-6.12/626-14-v6.17-net-pse-pd-ethtool-Add-support-for-power-domains-index.patch
new file mode 100644
index 0000000000..1124882a6a
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-14-v6.17-net-pse-pd-ethtool-Add-support-for-power-domains-index.patch
@@ -0,0 +1,78 @@
+# ADAPTED FOR OPENWRT 6.12.67 - Documentation changes removed
+# Original commit: 1176978ed851
+From 1176978ed851952652ddea3685e2f71a0e5d61ff Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent at bootlin.com>
+Date: Tue, 17 Jun 2025 14:12:04 +0200
+Subject: [PATCH] net: ethtool: Add support for new power domains index
+ description
+
+Report the index of the newly introduced PSE power domain to the user,
+enabling improved management of the power budget for PSE devices.
+
+Signed-off-by: Kory Maincent (Dent Project) <kory.maincent at bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel at pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-5-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba at kernel.org>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ Documentation/netlink/specs/ethtool.yaml       | 5 +++++
+ Documentation/networking/ethtool-netlink.rst   | 4 ++++
+ drivers/net/pse-pd/pse_core.c                  | 3 +++
+ include/linux/pse-pd/pse.h                     | 2 ++
+ include/uapi/linux/ethtool_netlink_generated.h | 1 +
+ net/ethtool/pse-pd.c                           | 7 +++++++
+ 6 files changed, 22 insertions(+)
+
+--- a/drivers/net/pse-pd/pse_core.c
++++ b/drivers/net/pse-pd/pse_core.c
+@@ -1098,6 +1098,9 @@ int pse_ethtool_get_status(struct pse_co
+ 	pcdev = psec->pcdev;
+ 	ops = pcdev->ops;
+ 	mutex_lock(&pcdev->lock);
++	if (pcdev->pi[psec->id].pw_d)
++		status->pw_d_id = pcdev->pi[psec->id].pw_d->id;
++
+ 	ret = ops->pi_get_admin_state(pcdev, psec->id, &admin_state);
+ 	if (ret)
+ 		goto out;
+--- a/include/linux/pse-pd/pse.h
++++ b/include/linux/pse-pd/pse.h
+@@ -114,6 +114,7 @@ struct pse_pw_limit_ranges {
+ /**
+  * struct ethtool_pse_control_status - PSE control/channel status.
+  *
++ * @pw_d_id: PSE power domain index.
+  * @podl_admin_state: operational state of the PoDL PSE
+  *	functions. IEEE 802.3-2018 30.15.1.1.2 aPoDLPSEAdminState
+  * @podl_pw_status: power detection status of the PoDL PSE.
+@@ -135,6 +136,7 @@ struct pse_pw_limit_ranges {
+  *	ranges
+  */
+ struct ethtool_pse_control_status {
++	u32 pw_d_id;
+ 	enum ethtool_podl_pse_admin_state podl_admin_state;
+ 	enum ethtool_podl_pse_pw_d_status podl_pw_status;
+ 	enum ethtool_c33_pse_admin_state c33_admin_state;
+--- a/net/ethtool/pse-pd.c
++++ b/net/ethtool/pse-pd.c
+@@ -83,6 +83,8 @@ static int pse_reply_size(const struct e
+ 	const struct ethtool_pse_control_status *st = &data->status;
+ 	int len = 0;
+ 
++	if (st->pw_d_id)
++		len += nla_total_size(sizeof(u32)); /* _PSE_PW_D_ID */
+ 	if (st->podl_admin_state > 0)
+ 		len += nla_total_size(sizeof(u32)); /* _PODL_PSE_ADMIN_STATE */
+ 	if (st->podl_pw_status > 0)
+@@ -148,6 +150,11 @@ static int pse_fill_reply(struct sk_buff
+ 	const struct pse_reply_data *data = PSE_REPDATA(reply_base);
+ 	const struct ethtool_pse_control_status *st = &data->status;
+ 
++	if (st->pw_d_id &&
++	    nla_put_u32(skb, ETHTOOL_A_PSE_PW_D_ID,
++			st->pw_d_id))
++		return -EMSGSIZE;
++
+ 	if (st->podl_admin_state > 0 &&
+ 	    nla_put_u32(skb, ETHTOOL_A_PODL_PSE_ADMIN_STATE,
+ 			st->podl_admin_state))
diff --git a/target/linux/generic/backport-6.12/626-15-v6.17-net-pse-pd-Add-helper-to-report-hw-enable-status.patch b/target/linux/generic/backport-6.12/626-15-v6.17-net-pse-pd-Add-helper-to-report-hw-enable-status.patch
new file mode 100644
index 0000000000..ed51289816
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-15-v6.17-net-pse-pd-Add-helper-to-report-hw-enable-status.patch
@@ -0,0 +1,74 @@
+From c394e757dedd9cf947f9ac470d615d28fd2b07d1 Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent at bootlin.com>
+Date: Tue, 17 Jun 2025 14:12:05 +0200
+Subject: [PATCH] net: pse-pd: Add helper to report hardware enable status of
+ the PI
+
+Refactor code by introducing a helper function to retrieve the hardware
+enabled state of the PI, avoiding redundant implementations in the
+future.
+
+Signed-off-by: Kory Maincent (Dent Project) <kory.maincent at bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel at pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-6-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba at kernel.org>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/pse_core.c | 36 +++++++++++++++++++++++++----------
+ 1 file changed, 26 insertions(+), 10 deletions(-)
+
+--- a/drivers/net/pse-pd/pse_core.c
++++ b/drivers/net/pse-pd/pse_core.c
+@@ -272,10 +272,34 @@ static struct net_device *pse_control_ge
+ 	return psec->attached_phydev->attached_dev;
+ }
+ 
++/**
++ * pse_pi_is_hw_enabled - Is PI enabled at the hardware level
++ * @pcdev: a pointer to the PSE controller device
++ * @id: Index of the PI
++ *
++ * Return: 1 if the PI is enabled at the hardware level, 0 if not, and
++ *	   a failure value on error
++ */
++static int pse_pi_is_hw_enabled(struct pse_controller_dev *pcdev, int id)
++{
++	struct pse_admin_state admin_state = {0};
++	int ret;
++
++	ret = pcdev->ops->pi_get_admin_state(pcdev, id, &admin_state);
++	if (ret < 0)
++		return ret;
++
++	/* PI is well enabled at the hardware level */
++	if (admin_state.podl_admin_state == ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED ||
++	    admin_state.c33_admin_state == ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED)
++		return 1;
++
++	return 0;
++}
++
+ static int pse_pi_is_enabled(struct regulator_dev *rdev)
+ {
+ 	struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
+-	struct pse_admin_state admin_state = {0};
+ 	const struct pse_controller_ops *ops;
+ 	int id, ret;
+ 
+@@ -285,15 +309,7 @@ static int pse_pi_is_enabled(struct regu
+ 
+ 	id = rdev_get_id(rdev);
+ 	mutex_lock(&pcdev->lock);
+-	ret = ops->pi_get_admin_state(pcdev, id, &admin_state);
+-	if (ret)
+-		goto out;
+-
+-	if (admin_state.podl_admin_state == ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED ||
+-	    admin_state.c33_admin_state == ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED)
+-		ret = 1;
+-
+-out:
++	ret = pse_pi_is_hw_enabled(pcdev, id);
+ 	mutex_unlock(&pcdev->lock);
+ 
+ 	return ret;
diff --git a/target/linux/generic/backport-6.12/626-16-v6.17-net-pse-pd-Add-support-for-budget-evaluation-strategies.patch b/target/linux/generic/backport-6.12/626-16-v6.17-net-pse-pd-Add-support-for-budget-evaluation-strategies.patch
new file mode 100644
index 0000000000..c95d8f0fee
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-16-v6.17-net-pse-pd-Add-support-for-budget-evaluation-strategies.patch
@@ -0,0 +1,1104 @@
+From ffef61d6d27374542f1bce4452200d9bdd2e1edd Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent at bootlin.com>
+Date: Tue, 17 Jun 2025 14:12:06 +0200
+Subject: [PATCH] net: pse-pd: Add support for budget evaluation strategies
+
+ADAPTED FOR OPENWRT 6.12.67 - Documentation and ethtool_netlink_generated.h changes removed
+
+This patch introduces the ability to configure the PSE PI budget evaluation
+strategies. Budget evaluation strategies is utilized by PSE controllers to
+determine which ports to turn off first in scenarios such as power budget
+exceedance.
+
+The pis_prio_max value is used to define the maximum priority level
+supported by the controller. Both the current priority and the maximum
+priority are exposed to the user through the pse_ethtool_get_status call.
+
+This patch add support for two mode of budget evaluation strategies.
+1. Static Method:
+   This method involves distributing power based on PD classification.
+
+2. Dynamic Method:
+   Budget evaluation strategy based on the current consumption per ports
+   compared to the total power budget.
+
+Signed-off-by: Kory Maincent (Dent Project) <kory.maincent at bootlin.com>
+Acked-by: Oleksij Rempel <o.rempel at pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-7-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba at kernel.org>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/pse_core.c | 729 +++++++++++++++++++++++++++++++++-
+ include/linux/pse-pd/pse.h    |  76 ++++
+ 2 files changed, 791 insertions(+), 14 deletions(-)
+
+--- a/drivers/net/pse-pd/pse_core.c
++++ b/drivers/net/pse-pd/pse_core.c
+@@ -47,11 +47,14 @@ struct pse_control {
+  * @id: ID of the power domain
+  * @supply: Power supply the Power Domain
+  * @refcnt: Number of gets of this pse_power_domain
++ * @budget_eval_strategy: Current power budget evaluation strategy of the
++ *			  power domain
+  */
+ struct pse_power_domain {
+ 	int id;
+ 	struct regulator *supply;
+ 	struct kref refcnt;
++	u32 budget_eval_strategy;
+ };
+ 
+ static int of_load_single_pse_pi_pairset(struct device_node *node,
+@@ -297,6 +300,115 @@ static int pse_pi_is_hw_enabled(struct p
+ 	return 0;
+ }
+ 
++/**
++ * pse_pi_is_admin_enable_pending - Check if PI is in admin enable pending state
++ *				    which mean the power is not yet being
++ *				    delivered
++ * @pcdev: a pointer to the PSE controller device
++ * @id: Index of the PI
++ *
++ * Detects if a PI is enabled in software with a PD detected, but the hardware
++ * admin state hasn't been applied yet.
++ *
++ * This function is used in the power delivery and retry mechanisms to determine
++ * which PIs need to have power delivery attempted again.
++ *
++ * Return: true if the PI has admin enable flag set in software but not yet
++ *	   reflected in the hardware admin state, false otherwise.
++ */
++static bool
++pse_pi_is_admin_enable_pending(struct pse_controller_dev *pcdev, int id)
++{
++	int ret;
++
++	/* PI not enabled or nothing is plugged */
++	if (!pcdev->pi[id].admin_state_enabled ||
++	    !pcdev->pi[id].isr_pd_detected)
++		return false;
++
++	ret = pse_pi_is_hw_enabled(pcdev, id);
++	/* PSE PI is already enabled at hardware level */
++	if (ret == 1)
++		return false;
++
++	return true;
++}
++
++static int _pse_pi_delivery_power_sw_pw_ctrl(struct pse_controller_dev *pcdev,
++					     int id,
++					     struct netlink_ext_ack *extack);
++
++/**
++ * pse_pw_d_retry_power_delivery - Retry power delivery for pending ports in a
++ *				   PSE power domain
++ * @pcdev: a pointer to the PSE controller device
++ * @pw_d: a pointer to the PSE power domain
++ *
++ * Scans all ports in the specified power domain and attempts to enable power
++ * delivery to any ports that have admin enable state set but don't yet have
++ * hardware power enabled. Used when there are changes in connection status,
++ * admin state, or priority that might allow previously unpowered ports to
++ * receive power, especially in over-budget conditions.
++ */
++static void pse_pw_d_retry_power_delivery(struct pse_controller_dev *pcdev,
++					  struct pse_power_domain *pw_d)
++{
++	int i, ret = 0;
++
++	for (i = 0; i < pcdev->nr_lines; i++) {
++		int prio_max = pcdev->nr_lines;
++		struct netlink_ext_ack extack;
++
++		if (pcdev->pi[i].pw_d != pw_d)
++			continue;
++
++		if (!pse_pi_is_admin_enable_pending(pcdev, i))
++			continue;
++
++		/* Do not try to enable PI with a lower prio (higher value)
++		 * than one which already can't be enabled.
++		 */
++		if (pcdev->pi[i].prio > prio_max)
++			continue;
++
++		ret = _pse_pi_delivery_power_sw_pw_ctrl(pcdev, i, &extack);
++		if (ret == -ERANGE)
++			prio_max = pcdev->pi[i].prio;
++	}
++}
++
++/**
++ * pse_pw_d_is_sw_pw_control - Determine if power control is software managed
++ * @pcdev: a pointer to the PSE controller device
++ * @pw_d: a pointer to the PSE power domain
++ *
++ * This function determines whether the power control for a specific power
++ * domain is managed by software in the interrupt handler rather than directly
++ * by hardware.
++ *
++ * Software power control is active in the following cases:
++ * - When the budget evaluation strategy is set to static
++ * - When the budget evaluation strategy is disabled but the PSE controller
++ *   has an interrupt handler that can report if a Powered Device is connected
++ *
++ * Return: true if the power control of the power domain is managed by software,
++ *         false otherwise
++ */
++static bool pse_pw_d_is_sw_pw_control(struct pse_controller_dev *pcdev,
++				      struct pse_power_domain *pw_d)
++{
++	if (!pw_d)
++		return false;
++
++	if (pw_d->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_STATIC)
++		return true;
++	if (pw_d->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_DISABLED &&
++	    pcdev->ops->pi_enable && pcdev->irq)
++		return true;
++
++	return false;
++}
++
+ static int pse_pi_is_enabled(struct regulator_dev *rdev)
+ {
+ 	struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
+@@ -309,17 +421,252 @@ static int pse_pi_is_enabled(struct regu
+ 
+ 	id = rdev_get_id(rdev);
+ 	mutex_lock(&pcdev->lock);
++	if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d)) {
++		ret = pcdev->pi[id].admin_state_enabled;
++		goto out;
++	}
++
+ 	ret = pse_pi_is_hw_enabled(pcdev, id);
++
++out:
+ 	mutex_unlock(&pcdev->lock);
+ 
+ 	return ret;
+ }
+ 
++/**
++ * pse_pi_deallocate_pw_budget - Deallocate power budget of the PI
++ * @pi: a pointer to the PSE PI
++ */
++static void pse_pi_deallocate_pw_budget(struct pse_pi *pi)
++{
++	if (!pi->pw_d || !pi->pw_allocated_mW)
++		return;
++
++	regulator_free_power_budget(pi->pw_d->supply, pi->pw_allocated_mW);
++	pi->pw_allocated_mW = 0;
++}
++
++/**
++ * _pse_pi_disable - Call disable operation. Assumes the PSE lock has been
++ *		     acquired.
++ * @pcdev: a pointer to the PSE
++ * @id: index of the PSE control
++ *
++ * Return: 0 on success and failure value on error
++ */
++static int _pse_pi_disable(struct pse_controller_dev *pcdev, int id)
++{
++	const struct pse_controller_ops *ops = pcdev->ops;
++	int ret;
++
++	if (!ops->pi_disable)
++		return -EOPNOTSUPP;
++
++	ret = ops->pi_disable(pcdev, id);
++	if (ret)
++		return ret;
++
++	pse_pi_deallocate_pw_budget(&pcdev->pi[id]);
++
++	if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d))
++		pse_pw_d_retry_power_delivery(pcdev, pcdev->pi[id].pw_d);
++
++	return 0;
++}
++
++/**
++ * pse_disable_pi_pol - Disable a PI on a power budget policy
++ * @pcdev: a pointer to the PSE
++ * @id: index of the PSE PI
++ *
++ * Return: 0 on success and failure value on error
++ */
++static int pse_disable_pi_pol(struct pse_controller_dev *pcdev, int id)
++{
++	unsigned long notifs = ETHTOOL_PSE_EVENT_OVER_BUDGET;
++	struct pse_ntf ntf = {};
++	int ret;
++
++	dev_dbg(pcdev->dev, "Disabling PI %d to free power budget\n", id);
++
++	ret = _pse_pi_disable(pcdev, id);
++	if (ret)
++		notifs |= ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR;
++
++	ntf.notifs = notifs;
++	ntf.id = id;
++	kfifo_in_spinlocked(&pcdev->ntf_fifo, &ntf, 1, &pcdev->ntf_fifo_lock);
++	schedule_work(&pcdev->ntf_work);
++
++	return ret;
++}
++
++/**
++ * pse_disable_pi_prio - Disable all PIs of a given priority inside a PSE
++ *			 power domain
++ * @pcdev: a pointer to the PSE
++ * @pw_d: a pointer to the PSE power domain
++ * @prio: priority
++ *
++ * Return: 0 on success and failure value on error
++ */
++static int pse_disable_pi_prio(struct pse_controller_dev *pcdev,
++			       struct pse_power_domain *pw_d,
++			       int prio)
++{
++	int i;
++
++	for (i = 0; i < pcdev->nr_lines; i++) {
++		int ret;
++
++		if (pcdev->pi[i].prio != prio ||
++		    pcdev->pi[i].pw_d != pw_d ||
++		    pse_pi_is_hw_enabled(pcdev, i) <= 0)
++			continue;
++
++		ret = pse_disable_pi_pol(pcdev, i);
++		if (ret)
++			return ret;
++	}
++
++	return 0;
++}
++
++/**
++ * pse_pi_allocate_pw_budget_static_prio - Allocate power budget for the PI
++ *					   when the budget eval strategy is
++ *					   static
++ * @pcdev: a pointer to the PSE
++ * @id: index of the PSE control
++ * @pw_req: power requested in mW
++ * @extack: extack for error reporting
++ *
++ * Allocates power using static budget evaluation strategy, where allocation
++ * is based on PD classification. When insufficient budget is available,
++ * lower-priority ports (higher priority numbers) are turned off first.
++ *
++ * Return: 0 on success and failure value on error
++ */
++static int
++pse_pi_allocate_pw_budget_static_prio(struct pse_controller_dev *pcdev, int id,
++				      int pw_req, struct netlink_ext_ack *extack)
++{
++	struct pse_pi *pi = &pcdev->pi[id];
++	int ret, _prio;
++
++	_prio = pcdev->nr_lines;
++	while (regulator_request_power_budget(pi->pw_d->supply, pw_req) == -ERANGE) {
++		if (_prio <= pi->prio) {
++			NL_SET_ERR_MSG_FMT(extack,
++					   "PI %d: not enough power budget available",
++					   id);
++			return -ERANGE;
++		}
++
++		ret = pse_disable_pi_prio(pcdev, pi->pw_d, _prio);
++		if (ret < 0)
++			return ret;
++
++		_prio--;
++	}
++
++	pi->pw_allocated_mW = pw_req;
++	return 0;
++}
++
++/**
++ * pse_pi_allocate_pw_budget - Allocate power budget for the PI
++ * @pcdev: a pointer to the PSE
++ * @id: index of the PSE control
++ * @pw_req: power requested in mW
++ * @extack: extack for error reporting
++ *
++ * Return: 0 on success and failure value on error
++ */
++static int pse_pi_allocate_pw_budget(struct pse_controller_dev *pcdev, int id,
++				     int pw_req, struct netlink_ext_ack *extack)
++{
++	struct pse_pi *pi = &pcdev->pi[id];
++
++	if (!pi->pw_d)
++		return 0;
++
++	/* PSE_BUDGET_EVAL_STRAT_STATIC */
++	if (pi->pw_d->budget_eval_strategy == PSE_BUDGET_EVAL_STRAT_STATIC)
++		return pse_pi_allocate_pw_budget_static_prio(pcdev, id, pw_req,
++							     extack);
++
++	return 0;
++}
++
++/**
++ * _pse_pi_delivery_power_sw_pw_ctrl - Enable PSE PI in case of software power
++ *				       control. Assumes the PSE lock has been
++ *				       acquired.
++ * @pcdev: a pointer to the PSE
++ * @id: index of the PSE control
++ * @extack: extack for error reporting
++ *
++ * Return: 0 on success and failure value on error
++ */
++static int _pse_pi_delivery_power_sw_pw_ctrl(struct pse_controller_dev *pcdev,
++					     int id,
++					     struct netlink_ext_ack *extack)
++{
++	const struct pse_controller_ops *ops = pcdev->ops;
++	struct pse_pi *pi = &pcdev->pi[id];
++	int ret, pw_req;
++
++	if (!ops->pi_get_pw_req) {
++		/* No power allocation management */
++		ret = ops->pi_enable(pcdev, id);
++		if (ret)
++			NL_SET_ERR_MSG_FMT(extack,
++					   "PI %d: enable error %d",
++					   id, ret);
++		return ret;
++	}
++
++	ret = ops->pi_get_pw_req(pcdev, id);
++	if (ret < 0)
++		return ret;
++
++	pw_req = ret;
++
++	/* Compare requested power with port power limit and use the lowest
++	 * one.
++	 */
++	if (ops->pi_get_pw_limit) {
++		ret = ops->pi_get_pw_limit(pcdev, id);
++		if (ret < 0)
++			return ret;
++
++		if (ret < pw_req)
++			pw_req = ret;
++	}
++
++	ret = pse_pi_allocate_pw_budget(pcdev, id, pw_req, extack);
++	if (ret)
++		return ret;
++
++	ret = ops->pi_enable(pcdev, id);
++	if (ret) {
++		pse_pi_deallocate_pw_budget(pi);
++		NL_SET_ERR_MSG_FMT(extack,
++				   "PI %d: enable error %d",
++				   id, ret);
++		return ret;
++	}
++
++	return 0;
++}
++
+ static int pse_pi_enable(struct regulator_dev *rdev)
+ {
+ 	struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
+ 	const struct pse_controller_ops *ops;
+-	int id, ret;
++	int id, ret = 0;
+ 
+ 	ops = pcdev->ops;
+ 	if (!ops->pi_enable)
+@@ -327,6 +674,23 @@ static int pse_pi_enable(struct regulato
+ 
+ 	id = rdev_get_id(rdev);
+ 	mutex_lock(&pcdev->lock);
++	if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d)) {
++		/* Manage enabled status by software.
++		 * Real enable process will happen if a port is connected.
++		 */
++		if (pcdev->pi[id].isr_pd_detected) {
++			struct netlink_ext_ack extack;
++
++			ret = _pse_pi_delivery_power_sw_pw_ctrl(pcdev, id, &extack);
++		}
++		if (!ret || ret == -ERANGE) {
++			pcdev->pi[id].admin_state_enabled = 1;
++			ret = 0;
++		}
++		mutex_unlock(&pcdev->lock);
++		return ret;
++	}
++
+ 	ret = ops->pi_enable(pcdev, id);
+ 	if (!ret)
+ 		pcdev->pi[id].admin_state_enabled = 1;
+@@ -338,21 +702,18 @@ static int pse_pi_enable(struct regulato
+ static int pse_pi_disable(struct regulator_dev *rdev)
+ {
+ 	struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
+-	const struct pse_controller_ops *ops;
++	struct pse_pi *pi;
+ 	int id, ret;
+ 
+-	ops = pcdev->ops;
+-	if (!ops->pi_disable)
+-		return -EOPNOTSUPP;
+-
+ 	id = rdev_get_id(rdev);
++	pi = &pcdev->pi[id];
+ 	mutex_lock(&pcdev->lock);
+-	ret = ops->pi_disable(pcdev, id);
++	ret = _pse_pi_disable(pcdev, id);
+ 	if (!ret)
+-		pcdev->pi[id].admin_state_enabled = 0;
+-	mutex_unlock(&pcdev->lock);
++		pi->admin_state_enabled = 0;
+ 
+-	return ret;
++	mutex_unlock(&pcdev->lock);
++	return 0;
+ }
+ 
+ static int _pse_pi_get_voltage(struct regulator_dev *rdev)
+@@ -628,6 +989,11 @@ static int pse_register_pw_ds(struct pse
+ 		}
+ 
+ 		pw_d->supply = supply;
++		if (pcdev->supp_budget_eval_strategies)
++			pw_d->budget_eval_strategy = pcdev->supp_budget_eval_strategies;
++		else
++			pw_d->budget_eval_strategy = PSE_BUDGET_EVAL_STRAT_DISABLED;
++		kref_init(&pw_d->refcnt);
+ 		pcdev->pi[i].pw_d = pw_d;
+ 	}
+ 
+@@ -637,6 +1003,34 @@ out:
+ }
+ 
+ /**
++ * pse_send_ntf_worker - Worker to send PSE notifications
++ * @work: work object
++ *
++ * Manage and send PSE netlink notifications using a workqueue to avoid
++ * deadlock between pcdev_lock and pse_list_mutex.
++ */
++static void pse_send_ntf_worker(struct work_struct *work)
++{
++	struct pse_controller_dev *pcdev;
++	struct pse_ntf ntf;
++
++	pcdev = container_of(work, struct pse_controller_dev, ntf_work);
++
++	while (kfifo_out(&pcdev->ntf_fifo, &ntf, 1)) {
++		struct net_device *netdev;
++		struct pse_control *psec;
++
++		psec = pse_control_find_by_id(pcdev, ntf.id);
++		rtnl_lock();
++		netdev = pse_control_get_netdev(psec);
++		if (netdev)
++			ethnl_pse_send_ntf(netdev, ntf.notifs);
++		rtnl_unlock();
++		pse_control_put(psec);
++	}
++}
++
++/**
+  * pse_controller_register - register a PSE controller device
+  * @pcdev: a pointer to the initialized PSE controller device
+  *
+@@ -649,6 +1043,13 @@ int pse_controller_register(struct pse_c
+ 
+ 	mutex_init(&pcdev->lock);
+ 	INIT_LIST_HEAD(&pcdev->pse_control_head);
++	spin_lock_init(&pcdev->ntf_fifo_lock);
++	ret = kfifo_alloc(&pcdev->ntf_fifo, pcdev->nr_lines, GFP_KERNEL);
++	if (ret) {
++		dev_err(pcdev->dev, "failed to allocate kfifo notifications\n");
++		return ret;
++	}
++	INIT_WORK(&pcdev->ntf_work, pse_send_ntf_worker);
+ 
+ 	if (!pcdev->nr_lines)
+ 		pcdev->nr_lines = 1;
+@@ -715,6 +1116,10 @@ void pse_controller_unregister(struct ps
+ {
+ 	pse_flush_pw_ds(pcdev);
+ 	pse_release_pis(pcdev);
++	if (pcdev->irq)
++		disable_irq(pcdev->irq);
++	cancel_work_sync(&pcdev->ntf_work);
++	kfifo_free(&pcdev->ntf_fifo);
+ 	mutex_lock(&pse_list_mutex);
+ 	list_del(&pcdev->list);
+ 	mutex_unlock(&pse_list_mutex);
+@@ -787,6 +1192,52 @@ static unsigned long pse_to_regulator_no
+ }
+ 
+ /**
++ * pse_set_config_isr - Set PSE control config according to the PSE
++ *			notifications
++ * @pcdev: a pointer to the PSE
++ * @id: index of the PSE control
++ * @notifs: PSE event notifications
++ *
++ * Return: 0 on success and failure value on error
++ */
++static int pse_set_config_isr(struct pse_controller_dev *pcdev, int id,
++			      unsigned long notifs)
++{
++	int ret = 0;
++
++	if (notifs & PSE_BUDGET_EVAL_STRAT_DYNAMIC)
++		return 0;
++
++	if ((notifs & ETHTOOL_C33_PSE_EVENT_DISCONNECTION) &&
++	    ((notifs & ETHTOOL_C33_PSE_EVENT_DETECTION) ||
++	     (notifs & ETHTOOL_C33_PSE_EVENT_CLASSIFICATION))) {
++		dev_dbg(pcdev->dev,
++			"PI %d: error, connection and disconnection reported simultaneously",
++			id);
++		return -EINVAL;
++	}
++
++	if (notifs & ETHTOOL_C33_PSE_EVENT_CLASSIFICATION) {
++		struct netlink_ext_ack extack;
++
++		pcdev->pi[id].isr_pd_detected = true;
++		if (pcdev->pi[id].admin_state_enabled) {
++			ret = _pse_pi_delivery_power_sw_pw_ctrl(pcdev, id,
++								&extack);
++			if (ret == -ERANGE)
++				ret = 0;
++		}
++	} else if (notifs & ETHTOOL_C33_PSE_EVENT_DISCONNECTION) {
++		if (pcdev->pi[id].admin_state_enabled &&
++		    pcdev->pi[id].isr_pd_detected)
++			ret = _pse_pi_disable(pcdev, id);
++		pcdev->pi[id].isr_pd_detected = false;
++	}
++
++	return ret;
++}
++
++/**
+  * pse_isr - IRQ handler for PSE
+  * @irq: irq number
+  * @data: pointer to user interrupt structure
+@@ -808,36 +1259,42 @@ static irqreturn_t pse_isr(int irq, void
+ 	memset(h->notifs, 0, pcdev->nr_lines * sizeof(*h->notifs));
+ 	mutex_lock(&pcdev->lock);
+ 	ret = desc->map_event(irq, pcdev, h->notifs, &notifs_mask);
+-	mutex_unlock(&pcdev->lock);
+-	if (ret || !notifs_mask)
++	if (ret || !notifs_mask) {
++		mutex_unlock(&pcdev->lock);
+ 		return IRQ_NONE;
++	}
+ 
+ 	for_each_set_bit(i, &notifs_mask, pcdev->nr_lines) {
+ 		unsigned long notifs, rnotifs;
+-		struct net_device *netdev;
+-		struct pse_control *psec;
++		struct pse_ntf ntf = {};
+ 
+ 		/* Do nothing PI not described */
+ 		if (!pcdev->pi[i].rdev)
+ 			continue;
+ 
+ 		notifs = h->notifs[i];
++		if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[i].pw_d)) {
++			ret = pse_set_config_isr(pcdev, i, notifs);
++			if (ret)
++				notifs |= ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR;
++		}
++
+ 		dev_dbg(h->pcdev->dev,
+ 			"Sending PSE notification EVT 0x%lx\n", notifs);
+ 
+-		psec = pse_control_find_by_id(pcdev, i);
+-		rtnl_lock();
+-		netdev = pse_control_get_netdev(psec);
+-		if (netdev)
+-			ethnl_pse_send_ntf(netdev, notifs);
+-		rtnl_unlock();
+-		pse_control_put(psec);
++		ntf.notifs = notifs;
++		ntf.id = i;
++		kfifo_in_spinlocked(&pcdev->ntf_fifo, &ntf, 1,
++				    &pcdev->ntf_fifo_lock);
++		schedule_work(&pcdev->ntf_work);
+ 
+ 		rnotifs = pse_to_regulator_notifs(notifs);
+ 		regulator_notifier_call_chain(pcdev->pi[i].rdev, rnotifs,
+ 					      NULL);
+ 	}
+ 
++	mutex_unlock(&pcdev->lock);
++
+ 	return IRQ_HANDLED;
+ }
+ 
+@@ -960,6 +1417,20 @@ pse_control_get_internal(struct pse_cont
+ 		goto free_psec;
+ 	}
+ 
++	if (!pcdev->ops->pi_get_admin_state) {
++		ret = -EOPNOTSUPP;
++		goto free_psec;
++	}
++
++	/* Initialize admin_state_enabled before the regulator_get. This
++	 * aims to have the right value reported in the first is_enabled
++	 * call in case of control managed by software.
++	 */
++	ret = pse_pi_is_hw_enabled(pcdev, index);
++	if (ret < 0)
++		goto free_psec;
++
++	pcdev->pi[index].admin_state_enabled = ret;
+ 	psec->ps = devm_regulator_get_exclusive(pcdev->dev,
+ 						rdev_get_name(pcdev->pi[index].rdev));
+ 	if (IS_ERR(psec->ps)) {
+@@ -967,12 +1438,6 @@ pse_control_get_internal(struct pse_cont
+ 		goto put_module;
+ 	}
+ 
+-	ret = regulator_is_enabled(psec->ps);
+-	if (ret < 0)
+-		goto regulator_put;
+-
+-	pcdev->pi[index].admin_state_enabled = ret;
+-
+ 	psec->pcdev = pcdev;
+ 	list_add(&psec->list, &pcdev->pse_control_head);
+ 	psec->id = index;
+@@ -981,8 +1446,6 @@ pse_control_get_internal(struct pse_cont
+ 
+ 	return psec;
+ 
+-regulator_put:
+-	devm_regulator_put(psec->ps);
+ put_module:
+ 	module_put(pcdev->owner);
+ free_psec:
+@@ -1094,6 +1557,35 @@ out:
+ EXPORT_SYMBOL_GPL(of_pse_control_get);
+ 
+ /**
++ * pse_get_sw_admin_state - Convert the software admin state to c33 or podl
++ *			    admin state value used in the standard
++ * @psec: PSE control pointer
++ * @admin_state: a pointer to the admin_state structure
++ */
++static void pse_get_sw_admin_state(struct pse_control *psec,
++				   struct pse_admin_state *admin_state)
++{
++	struct pse_pi *pi = &psec->pcdev->pi[psec->id];
++
++	if (pse_has_podl(psec)) {
++		if (pi->admin_state_enabled)
++			admin_state->podl_admin_state =
++				ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED;
++		else
++			admin_state->podl_admin_state =
++				ETHTOOL_PODL_PSE_ADMIN_STATE_DISABLED;
++	}
++	if (pse_has_c33(psec)) {
++		if (pi->admin_state_enabled)
++			admin_state->c33_admin_state =
++				ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
++		else
++			admin_state->c33_admin_state =
++				ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
++	}
++}
++
++/**
+  * pse_ethtool_get_status - get status of PSE control
+  * @psec: PSE control pointer
+  * @extack: extack for reporting useful error messages
+@@ -1109,19 +1601,46 @@ int pse_ethtool_get_status(struct pse_co
+ 	struct pse_pw_status pw_status = {0};
+ 	const struct pse_controller_ops *ops;
+ 	struct pse_controller_dev *pcdev;
++	struct pse_pi *pi;
+ 	int ret;
+ 
+ 	pcdev = psec->pcdev;
+ 	ops = pcdev->ops;
++
++	pi = &pcdev->pi[psec->id];
+ 	mutex_lock(&pcdev->lock);
+-	if (pcdev->pi[psec->id].pw_d)
+-		status->pw_d_id = pcdev->pi[psec->id].pw_d->id;
++	if (pi->pw_d) {
++		status->pw_d_id = pi->pw_d->id;
++		if (pse_pw_d_is_sw_pw_control(pcdev, pi->pw_d)) {
++			pse_get_sw_admin_state(psec, &admin_state);
++		} else {
++			ret = ops->pi_get_admin_state(pcdev, psec->id,
++						      &admin_state);
++			if (ret)
++				goto out;
++		}
++		status->podl_admin_state = admin_state.podl_admin_state;
++		status->c33_admin_state = admin_state.c33_admin_state;
+ 
+-	ret = ops->pi_get_admin_state(pcdev, psec->id, &admin_state);
+-	if (ret)
+-		goto out;
+-	status->podl_admin_state = admin_state.podl_admin_state;
+-	status->c33_admin_state = admin_state.c33_admin_state;
++		switch (pi->pw_d->budget_eval_strategy) {
++		case PSE_BUDGET_EVAL_STRAT_STATIC:
++			status->prio_max = pcdev->nr_lines - 1;
++			status->prio = pi->prio;
++			break;
++		case PSE_BUDGET_EVAL_STRAT_DYNAMIC:
++			status->prio_max = pcdev->pis_prio_max;
++			if (ops->pi_get_prio) {
++				ret = ops->pi_get_prio(pcdev, psec->id);
++				if (ret < 0)
++					goto out;
++
++				status->prio = ret;
++			}
++			break;
++		default:
++			break;
++		}
++	}
+ 
+ 	ret = ops->pi_get_pw_status(pcdev, psec->id, &pw_status);
+ 	if (ret)
+@@ -1271,6 +1790,52 @@ int pse_ethtool_set_config(struct pse_co
+ EXPORT_SYMBOL_GPL(pse_ethtool_set_config);
+ 
+ /**
++ * pse_pi_update_pw_budget - Update PSE power budget allocated with new
++ *			     power in mW
++ * @pcdev: a pointer to the PSE controller device
++ * @id: index of the PSE PI
++ * @pw_req: power requested
++ * @extack: extack for reporting useful error messages
++ *
++ * Return: Previous power allocated on success and failure value on error
++ */
++static int pse_pi_update_pw_budget(struct pse_controller_dev *pcdev, int id,
++				   const unsigned int pw_req,
++				   struct netlink_ext_ack *extack)
++{
++	struct pse_pi *pi = &pcdev->pi[id];
++	int previous_pw_allocated;
++	int pw_diff, ret = 0;
++
++	/* We don't want pw_allocated_mW value change in the middle of an
++	 * power budget update
++	 */
++	mutex_lock(&pcdev->lock);
++	previous_pw_allocated = pi->pw_allocated_mW;
++	pw_diff = pw_req - previous_pw_allocated;
++	if (!pw_diff) {
++		goto out;
++	} else if (pw_diff > 0) {
++		ret = regulator_request_power_budget(pi->pw_d->supply, pw_diff);
++		if (ret) {
++			NL_SET_ERR_MSG_FMT(extack,
++					   "PI %d: not enough power budget available",
++					   id);
++			goto out;
++		}
++
++	} else {
++		regulator_free_power_budget(pi->pw_d->supply, -pw_diff);
++	}
++	pi->pw_allocated_mW = pw_req;
++	ret = previous_pw_allocated;
++
++out:
++	mutex_unlock(&pcdev->lock);
++	return ret;
++}
++
++/**
+  * pse_ethtool_set_pw_limit - set PSE control power limit
+  * @psec: PSE control pointer
+  * @extack: extack for reporting useful error messages
+@@ -1282,7 +1847,7 @@ int pse_ethtool_set_pw_limit(struct pse_
+ 			     struct netlink_ext_ack *extack,
+ 			     const unsigned int pw_limit)
+ {
+-	int uV, uA, ret;
++	int uV, uA, ret, previous_pw_allocated = 0;
+ 	s64 tmp_64;
+ 
+ 	if (pw_limit > MAX_PI_PW)
+@@ -1306,10 +1871,100 @@ int pse_ethtool_set_pw_limit(struct pse_
+ 	/* uA = mW * 1000000000 / uV */
+ 	uA = DIV_ROUND_CLOSEST_ULL(tmp_64, uV);
+ 
+-	return regulator_set_current_limit(psec->ps, 0, uA);
++	/* Update power budget only in software power control case and
++	 * if a Power Device is powered.
++	 */
++	if (pse_pw_d_is_sw_pw_control(psec->pcdev,
++				      psec->pcdev->pi[psec->id].pw_d) &&
++	    psec->pcdev->pi[psec->id].admin_state_enabled &&
++	    psec->pcdev->pi[psec->id].isr_pd_detected) {
++		ret = pse_pi_update_pw_budget(psec->pcdev, psec->id,
++					      pw_limit, extack);
++		if (ret < 0)
++			return ret;
++		previous_pw_allocated = ret;
++	}
++
++	ret = regulator_set_current_limit(psec->ps, 0, uA);
++	if (ret < 0 && previous_pw_allocated) {
++		pse_pi_update_pw_budget(psec->pcdev, psec->id,
++					previous_pw_allocated, extack);
++	}
++
++	return ret;
+ }
+ EXPORT_SYMBOL_GPL(pse_ethtool_set_pw_limit);
+ 
++/**
++ * pse_ethtool_set_prio - Set PSE PI priority according to the budget
++ *			  evaluation strategy
++ * @psec: PSE control pointer
++ * @extack: extack for reporting useful error messages
++ * @prio: priovity value
++ *
++ * Return: 0 on success and failure value on error
++ */
++int pse_ethtool_set_prio(struct pse_control *psec,
++			 struct netlink_ext_ack *extack,
++			 unsigned int prio)
++{
++	struct pse_controller_dev *pcdev = psec->pcdev;
++	const struct pse_controller_ops *ops;
++	int ret = 0;
++
++	if (!pcdev->pi[psec->id].pw_d) {
++		NL_SET_ERR_MSG(extack, "no power domain attached");
++		return -EOPNOTSUPP;
++	}
++
++	/* We don't want priority change in the middle of an
++	 * enable/disable call or a priority mode change
++	 */
++	mutex_lock(&pcdev->lock);
++	switch (pcdev->pi[psec->id].pw_d->budget_eval_strategy) {
++	case PSE_BUDGET_EVAL_STRAT_STATIC:
++		if (prio >= pcdev->nr_lines) {
++			NL_SET_ERR_MSG_FMT(extack,
++					   "priority %d exceed priority max %d",
++					   prio, pcdev->nr_lines);
++			ret = -ERANGE;
++			goto out;
++		}
++
++		pcdev->pi[psec->id].prio = prio;
++		pse_pw_d_retry_power_delivery(pcdev, pcdev->pi[psec->id].pw_d);
++		break;
++
++	case PSE_BUDGET_EVAL_STRAT_DYNAMIC:
++		ops = psec->pcdev->ops;
++		if (!ops->pi_set_prio) {
++			NL_SET_ERR_MSG(extack,
++				       "pse driver does not support setting port priority");
++			ret = -EOPNOTSUPP;
++			goto out;
++		}
++
++		if (prio > pcdev->pis_prio_max) {
++			NL_SET_ERR_MSG_FMT(extack,
++					   "priority %d exceed priority max %d",
++					   prio, pcdev->pis_prio_max);
++			ret = -ERANGE;
++			goto out;
++		}
++
++		ret = ops->pi_set_prio(pcdev, psec->id, prio);
++		break;
++
++	default:
++		ret = -EOPNOTSUPP;
++	}
++
++out:
++	mutex_unlock(&pcdev->lock);
++	return ret;
++}
++EXPORT_SYMBOL_GPL(pse_ethtool_set_prio);
++
+ bool pse_has_podl(struct pse_control *psec)
+ {
+ 	return psec->pcdev->types & ETHTOOL_PSE_PODL;
+--- a/include/linux/pse-pd/pse.h
++++ b/include/linux/pse-pd/pse.h
+@@ -6,6 +6,8 @@
+ #define _LINUX_PSE_CONTROLLER_H
+ 
+ #include <linux/list.h>
++#include <linux/netlink.h>
++#include <linux/kfifo.h>
+ #include <uapi/linux/ethtool.h>
+ #include <uapi/linux/ethtool_netlink_generated.h>
+ #include <linux/regulator/driver.h>
+@@ -134,6 +136,9 @@ struct pse_pw_limit_ranges {
+  *	is in charge of the memory allocation
+  * @c33_pw_limit_nb_ranges: number of supported power limit configuration
+  *	ranges
++ * @prio_max: max priority allowed for the c33_prio variable value.
++ * @prio: priority of the PSE. Managed by PSE core in case of static budget
++ *	evaluation strategy.
+  */
+ struct ethtool_pse_control_status {
+ 	u32 pw_d_id;
+@@ -147,6 +152,8 @@ struct ethtool_pse_control_status {
+ 	u32 c33_avail_pw_limit;
+ 	struct ethtool_c33_pse_pw_limit_range *c33_pw_limit_ranges;
+ 	u32 c33_pw_limit_nb_ranges;
++	u32 prio_max;
++	u32 prio;
+ };
+ 
+ /**
+@@ -170,6 +177,11 @@ struct ethtool_pse_control_status {
+  *			    range. The driver is in charge of the memory
+  *			    allocation and should return the number of
+  *			    ranges.
++ * @pi_get_prio: Get the PSE PI priority.
++ * @pi_set_prio: Configure the PSE PI priority.
++ * @pi_get_pw_req: Get the power requested by a PD before enabling the PSE PI.
++ *		   This is only relevant when an interrupt is registered using
++ *		   devm_pse_irq_helper helper.
+  */
+ struct pse_controller_ops {
+ 	int (*setup_pi_matrix)(struct pse_controller_dev *pcdev);
+@@ -190,6 +202,10 @@ struct pse_controller_ops {
+ 			       int id, int max_mW);
+ 	int (*pi_get_pw_limit_ranges)(struct pse_controller_dev *pcdev, int id,
+ 				      struct pse_pw_limit_ranges *pw_limit_ranges);
++	int (*pi_get_prio)(struct pse_controller_dev *pcdev, int id);
++	int (*pi_set_prio)(struct pse_controller_dev *pcdev, int id,
++			   unsigned int prio);
++	int (*pi_get_pw_req)(struct pse_controller_dev *pcdev, int id);
+ };
+ 
+ struct module;
+@@ -225,6 +241,13 @@ struct pse_pi_pairset {
+  * @rdev: regulator represented by the PSE PI
+  * @admin_state_enabled: PI enabled state
+  * @pw_d: Power domain of the PSE PI
++ * @prio: Priority of the PSE PI. Used in static budget evaluation strategy
++ * @isr_pd_detected: PSE PI detection status managed by the interruption
++ *		     handler. This variable is relevant when the power enabled
++ *		     management is managed in software like the static
++ *		     budget evaluation strategy.
++ * @pw_allocated_mW: Power allocated to a PSE PI to manage power budget in
++ *		     static budget evaluation strategy.
+  */
+ struct pse_pi {
+ 	struct pse_pi_pairset pairset[2];
+@@ -232,6 +255,20 @@ struct pse_pi {
+ 	struct regulator_dev *rdev;
+ 	bool admin_state_enabled;
+ 	struct pse_power_domain *pw_d;
++	int prio;
++	bool isr_pd_detected;
++	int pw_allocated_mW;
++};
++
++/**
++ * struct pse_ntf - PSE notification element
++ *
++ * @id: ID of the PSE control
++ * @notifs: PSE notifications to be reported
++ */
++struct pse_ntf {
++	int id;
++	unsigned long notifs;
+ };
+ 
+ /**
+@@ -249,6 +286,12 @@ struct pse_pi {
+  * @pi: table of PSE PIs described in this controller device
+  * @no_of_pse_pi: flag set if the pse_pis devicetree node is not used
+  * @irq: PSE interrupt
++ * @pis_prio_max: Maximum value allowed for the PSE PIs priority
++ * @supp_budget_eval_strategies: budget evaluation strategies supported
++ *				 by the PSE
++ * @ntf_work: workqueue for PSE notification management
++ * @ntf_fifo: PSE notifications FIFO
++ * @ntf_fifo_lock: protect @ntf_fifo writer
+  */
+ struct pse_controller_dev {
+ 	const struct pse_controller_ops *ops;
+@@ -263,6 +306,29 @@ struct pse_controller_dev {
+ 	struct pse_pi *pi;
+ 	bool no_of_pse_pi;
+ 	int irq;
++	unsigned int pis_prio_max;
++	u32 supp_budget_eval_strategies;
++	struct work_struct ntf_work;
++	DECLARE_KFIFO_PTR(ntf_fifo, struct pse_ntf);
++	spinlock_t ntf_fifo_lock; /* Protect @ntf_fifo writer */
++};
++
++/**
++ * enum pse_budget_eval_strategies - PSE budget evaluation strategies.
++ * @PSE_BUDGET_EVAL_STRAT_DISABLED: Budget evaluation strategy disabled.
++ * @PSE_BUDGET_EVAL_STRAT_STATIC: PSE static budget evaluation strategy.
++ *	Budget evaluation strategy based on the power requested during PD
++ *	classification. This strategy is managed by the PSE core.
++ * @PSE_BUDGET_EVAL_STRAT_DYNAMIC: PSE dynamic budget evaluation
++ *	strategy. Budget evaluation strategy based on the current consumption
++ *	per ports compared to the total	power budget. This mode is managed by
++ *	the PSE controller.
++ */
++
++enum pse_budget_eval_strategies {
++	PSE_BUDGET_EVAL_STRAT_DISABLED	= 1 << 0,
++	PSE_BUDGET_EVAL_STRAT_STATIC	= 1 << 1,
++	PSE_BUDGET_EVAL_STRAT_DYNAMIC	= 1 << 2,
+ };
+ 
+ #if IS_ENABLED(CONFIG_PSE_CONTROLLER)
+@@ -287,6 +353,9 @@ int pse_ethtool_set_config(struct pse_co
+ int pse_ethtool_set_pw_limit(struct pse_control *psec,
+ 			     struct netlink_ext_ack *extack,
+ 			     const unsigned int pw_limit);
++int pse_ethtool_set_prio(struct pse_control *psec,
++			 struct netlink_ext_ack *extack,
++			 unsigned int prio);
+ int pse_ethtool_get_pw_limit(struct pse_control *psec,
+ 			     struct netlink_ext_ack *extack);
+ 
+@@ -331,6 +400,13 @@ static inline int pse_ethtool_get_pw_lim
+ {
+ 	return -EOPNOTSUPP;
+ }
++
++static inline int pse_ethtool_set_prio(struct pse_control *psec,
++				       struct netlink_ext_ack *extack,
++				       unsigned int prio)
++{
++	return -EOPNOTSUPP;
++}
+ 
+ static inline bool pse_has_podl(struct pse_control *psec)
+ {
diff --git a/target/linux/generic/backport-6.12/626-17-v6.17-net-pse-pd-pd692x0-Add-PSE-PI-priority-feature.patch b/target/linux/generic/backport-6.12/626-17-v6.17-net-pse-pd-pd692x0-Add-PSE-PI-priority-feature.patch
new file mode 100644
index 0000000000..6e1f2d63c9
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-17-v6.17-net-pse-pd-pd692x0-Add-PSE-PI-priority-feature.patch
@@ -0,0 +1,326 @@
+From 359754013e6a7fc81af6735ebbfedd4a01999f68 Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent at bootlin.com>
+Date: Tue, 17 Jun 2025 14:12:08 +0200
+Subject: [PATCH] net: pse-pd: pd692x0: Add support for PSE PI priority feature
+
+This patch extends the PSE callbacks by adding support for the newly
+introduced pi_set_prio() callback, enabling the configuration of PSE PI
+priorities. The current port priority is now also included in the status
+information returned to users.
+
+Signed-off-by: Kory Maincent (Dent Project) <kory.maincent at bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel at pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-9-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba at kernel.org>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/pd692x0.c | 205 +++++++++++++++++++++++++++++++++++
+ 1 file changed, 205 insertions(+)
+
+--- a/drivers/net/pse-pd/pd692x0.c
++++ b/drivers/net/pse-pd/pd692x0.c
+@@ -12,6 +12,8 @@
+ #include <linux/of.h>
+ #include <linux/platform_device.h>
+ #include <linux/pse-pd/pse.h>
++#include <linux/regulator/driver.h>
++#include <linux/regulator/machine.h>
+ 
+ #define PD692X0_PSE_NAME "pd692x0_pse"
+ 
+@@ -76,6 +78,8 @@ enum {
+ 	PD692X0_MSG_GET_PORT_CLASS,
+ 	PD692X0_MSG_GET_PORT_MEAS,
+ 	PD692X0_MSG_GET_PORT_PARAM,
++	PD692X0_MSG_GET_POWER_BANK,
++	PD692X0_MSG_SET_POWER_BANK,
+ 
+ 	/* add new message above here */
+ 	PD692X0_MSG_CNT
+@@ -95,6 +99,8 @@ struct pd692x0_priv {
+ 	unsigned long last_cmd_key_time;
+ 
+ 	enum ethtool_c33_pse_admin_state admin_state[PD692X0_MAX_PIS];
++	struct regulator_dev *manager_reg[PD692X0_MAX_MANAGERS];
++	int manager_pw_budget[PD692X0_MAX_MANAGERS];
+ };
+ 
+ /* Template list of communication messages. The non-null bytes defined here
+@@ -170,6 +176,16 @@ static const struct pd692x0_msg pd692x0_
+ 		.data = {0x4e, 0x4e, 0x4e, 0x4e,
+ 			 0x4e, 0x4e, 0x4e, 0x4e},
+ 	},
++	[PD692X0_MSG_GET_POWER_BANK] = {
++		.key = PD692X0_KEY_REQ,
++		.sub = {0x07, 0x0b, 0x57},
++		.data = {   0, 0x4e, 0x4e, 0x4e,
++			 0x4e, 0x4e, 0x4e, 0x4e},
++	},
++	[PD692X0_MSG_SET_POWER_BANK] = {
++		.key = PD692X0_KEY_CMD,
++		.sub = {0x07, 0x0b, 0x57},
++	},
+ };
+ 
+ static u8 pd692x0_build_msg(struct pd692x0_msg *msg, u8 echo)
+@@ -739,6 +755,29 @@ pd692x0_pi_get_actual_pw(struct pse_cont
+ 	return (buf.data[0] << 4 | buf.data[1]) * 100;
+ }
+ 
++static int
++pd692x0_pi_get_prio(struct pse_controller_dev *pcdev, int id)
++{
++	struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
++	struct pd692x0_msg msg, buf = {0};
++	int ret;
++
++	ret = pd692x0_fw_unavailable(priv);
++	if (ret)
++		return ret;
++
++	msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_PARAM];
++	msg.sub[2] = id;
++	ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
++	if (ret < 0)
++		return ret;
++	if (!buf.data[2] || buf.data[2] > pcdev->pis_prio_max + 1)
++		return -ERANGE;
++
++	/* PSE core priority start at 0 */
++	return buf.data[2] - 1;
++}
++
+ static struct pd692x0_msg_ver pd692x0_get_sw_version(struct pd692x0_priv *priv)
+ {
+ 	struct device *dev = &priv->client->dev;
+@@ -766,6 +805,7 @@ static struct pd692x0_msg_ver pd692x0_ge
+ 
+ struct pd692x0_manager {
+ 	struct device_node *port_node[PD692X0_MAX_MANAGER_PORTS];
++	struct device_node *node;
+ 	int nports;
+ };
+ 
+@@ -857,6 +897,8 @@ pd692x0_of_get_managers(struct pd692x0_p
+ 		if (ret)
+ 			goto out;
+ 
++		of_node_get(node);
++		manager[manager_id].node = node;
+ 		nmanagers++;
+ 	}
+ 
+@@ -869,6 +911,8 @@ out:
+ 			of_node_put(manager[i].port_node[j]);
+ 			manager[i].port_node[j] = NULL;
+ 		}
++		of_node_put(manager[i].node);
++		manager[i].node = NULL;
+ 	}
+ 
+ 	of_node_put(node);
+@@ -876,6 +920,130 @@ out:
+ 	return ret;
+ }
+ 
++static const struct regulator_ops dummy_ops;
++
++static struct regulator_dev *
++pd692x0_register_manager_regulator(struct device *dev, char *reg_name,
++				   struct device_node *node)
++{
++	struct regulator_init_data *rinit_data;
++	struct regulator_config rconfig = {0};
++	struct regulator_desc *rdesc;
++	struct regulator_dev *rdev;
++
++	rinit_data = devm_kzalloc(dev, sizeof(*rinit_data),
++				  GFP_KERNEL);
++	if (!rinit_data)
++		return ERR_PTR(-ENOMEM);
++
++	rdesc = devm_kzalloc(dev, sizeof(*rdesc), GFP_KERNEL);
++	if (!rdesc)
++		return ERR_PTR(-ENOMEM);
++
++	rdesc->name = reg_name;
++	rdesc->type = REGULATOR_VOLTAGE;
++	rdesc->ops = &dummy_ops;
++	rdesc->owner = THIS_MODULE;
++
++	rinit_data->supply_regulator = "vmain";
++
++	rconfig.dev = dev;
++	rconfig.init_data = rinit_data;
++	rconfig.of_node = node;
++
++	rdev = devm_regulator_register(dev, rdesc, &rconfig);
++	if (IS_ERR(rdev)) {
++		dev_err_probe(dev, PTR_ERR(rdev),
++			      "Failed to register regulator\n");
++		return rdev;
++	}
++
++	return rdev;
++}
++
++static int
++pd692x0_register_managers_regulator(struct pd692x0_priv *priv,
++				    const struct pd692x0_manager *manager,
++				    int nmanagers)
++{
++	struct device *dev = &priv->client->dev;
++	size_t reg_name_len;
++	int i;
++
++	/* Each regulator name len is dev name + 12 char +
++	 * int max digit number (10) + 1
++	 */
++	reg_name_len = strlen(dev_name(dev)) + 23;
++
++	for (i = 0; i < nmanagers; i++) {
++		struct regulator_dev *rdev;
++		char *reg_name;
++
++		reg_name = devm_kzalloc(dev, reg_name_len, GFP_KERNEL);
++		if (!reg_name)
++			return -ENOMEM;
++		snprintf(reg_name, 26, "pse-%s-manager%d", dev_name(dev), i);
++		rdev = pd692x0_register_manager_regulator(dev, reg_name,
++							  manager[i].node);
++		if (IS_ERR(rdev))
++			return PTR_ERR(rdev);
++
++		priv->manager_reg[i] = rdev;
++	}
++
++	return 0;
++}
++
++static int
++pd692x0_conf_manager_power_budget(struct pd692x0_priv *priv, int id, int pw)
++{
++	struct pd692x0_msg msg, buf;
++	int ret, pw_mW = pw / 1000;
++
++	msg = pd692x0_msg_template_list[PD692X0_MSG_GET_POWER_BANK];
++	msg.data[0] = id;
++	ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
++	if (ret < 0)
++		return ret;
++
++	msg = pd692x0_msg_template_list[PD692X0_MSG_SET_POWER_BANK];
++	msg.data[0] = id;
++	msg.data[1] = pw_mW >> 8;
++	msg.data[2] = pw_mW & 0xff;
++	msg.data[3] = buf.sub[2];
++	msg.data[4] = buf.data[0];
++	msg.data[5] = buf.data[1];
++	msg.data[6] = buf.data[2];
++	msg.data[7] = buf.data[3];
++	return pd692x0_sendrecv_msg(priv, &msg, &buf);
++}
++
++static int
++pd692x0_configure_managers(struct pd692x0_priv *priv, int nmanagers)
++{
++	int i, ret;
++
++	for (i = 0; i < nmanagers; i++) {
++		struct regulator *supply = priv->manager_reg[i]->supply;
++		int pw_budget;
++
++		pw_budget = regulator_get_unclaimed_power_budget(supply);
++		/* Max power budget per manager */
++		if (pw_budget > 6000000)
++			pw_budget = 6000000;
++		ret = regulator_request_power_budget(supply, pw_budget);
++		if (ret < 0)
++			return ret;
++
++		priv->manager_pw_budget[i] = pw_budget;
++		ret = pd692x0_conf_manager_power_budget(priv, i, pw_budget);
++		if (ret < 0)
++			return ret;
++	}
++
++	return 0;
++}
++
+ static int
+ pd692x0_set_port_matrix(const struct pse_pi_pairset *pairset,
+ 			const struct pd692x0_manager *manager,
+@@ -998,6 +1166,14 @@ static int pd692x0_setup_pi_matrix(struc
+ 		return ret;
+ 
+ 	nmanagers = ret;
++	ret = pd692x0_register_managers_regulator(priv, manager, nmanagers);
++	if (ret)
++		goto out;
++
++	ret = pd692x0_configure_managers(priv, nmanagers);
++	if (ret)
++		goto out;
++
+ 	ret = pd692x0_set_ports_matrix(priv, manager, nmanagers, port_matrix);
+ 	if (ret)
+ 		goto out;
+@@ -1008,8 +1184,14 @@ static int pd692x0_setup_pi_matrix(struc
+ 
+ out:
+ 	for (i = 0; i < nmanagers; i++) {
++		struct regulator *supply = priv->manager_reg[i]->supply;
++
++		regulator_free_power_budget(supply,
++					    priv->manager_pw_budget[i]);
++
+ 		for (j = 0; j < manager[i].nports; j++)
+ 			of_node_put(manager[i].port_node[j]);
++		of_node_put(manager[i].node);
+ 	}
+ 	return ret;
+ }
+@@ -1071,6 +1253,25 @@ static int pd692x0_pi_set_pw_limit(struc
+ 	return pd692x0_sendrecv_msg(priv, &msg, &buf);
+ }
+ 
++static int pd692x0_pi_set_prio(struct pse_controller_dev *pcdev, int id,
++			       unsigned int prio)
++{
++	struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
++	struct pd692x0_msg msg, buf = {0};
++	int ret;
++
++	ret = pd692x0_fw_unavailable(priv);
++	if (ret)
++		return ret;
++
++	msg = pd692x0_msg_template_list[PD692X0_MSG_SET_PORT_PARAM];
++	msg.sub[2] = id;
++	/* Controller priority from 1 to 3 */
++	msg.data[4] = prio + 1;
++
++	return pd692x0_sendrecv_msg(priv, &msg, &buf);
++}
++
+ static const struct pse_controller_ops pd692x0_ops = {
+ 	.setup_pi_matrix = pd692x0_setup_pi_matrix,
+ 	.pi_get_admin_state = pd692x0_pi_get_admin_state,
+@@ -1084,6 +1285,8 @@ static const struct pse_controller_ops p
+ 	.pi_get_pw_limit = pd692x0_pi_get_pw_limit,
+ 	.pi_set_pw_limit = pd692x0_pi_set_pw_limit,
+ 	.pi_get_pw_limit_ranges = pd692x0_pi_get_pw_limit_ranges,
++	.pi_get_prio = pd692x0_pi_get_prio,
++	.pi_set_prio = pd692x0_pi_set_prio,
+ };
+ 
+ #define PD692X0_FW_LINE_MAX_SZ 0xff
+@@ -1500,6 +1703,8 @@ static int pd692x0_i2c_probe(struct i2c_
+ 	priv->pcdev.ops = &pd692x0_ops;
+ 	priv->pcdev.dev = dev;
+ 	priv->pcdev.types = ETHTOOL_PSE_C33;
++	priv->pcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_DYNAMIC;
++	priv->pcdev.pis_prio_max = 2;
+ 	ret = devm_pse_controller_register(dev, &priv->pcdev);
+ 	if (ret)
+ 		return dev_err_probe(dev, ret,
diff --git a/target/linux/generic/backport-6.12/626-18-v6.17-net-pse-pd-pd692x0-Add-controller-and-manager-power.patch b/target/linux/generic/backport-6.12/626-18-v6.17-net-pse-pd-pd692x0-Add-controller-and-manager-power.patch
new file mode 100644
index 0000000000..a93dca0999
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-18-v6.17-net-pse-pd-pd692x0-Add-controller-and-manager-power.patch
@@ -0,0 +1,71 @@
+From 24a4e3a05dd0eadd0c9585c411880e5dcb6be97f Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent at bootlin.com>
+Date: Tue, 17 Jun 2025 14:12:09 +0200
+Subject: [PATCH] net: pse-pd: pd692x0: Add support for controller and manager
+ power supplies
+
+Add support for managing the VDD and VDDA power supplies for the PD692x0
+PSE controller, as well as the VAUX5 and VAUX3P3 power supplies for the
+PD6920x PSE managers.
+
+Signed-off-by: Kory Maincent (Dent Project) <kory.maincent at bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel at pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-10-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba at kernel.org>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/pd692x0.c | 20 ++++++++++++++++++++
+ 1 file changed, 20 insertions(+)
+
+--- a/drivers/net/pse-pd/pd692x0.c
++++ b/drivers/net/pse-pd/pd692x0.c
+@@ -976,8 +976,10 @@ pd692x0_register_managers_regulator(stru
+ 	reg_name_len = strlen(dev_name(dev)) + 23;
+ 
+ 	for (i = 0; i < nmanagers; i++) {
++		static const char * const regulators[] = { "vaux5", "vaux3p3" };
+ 		struct regulator_dev *rdev;
+ 		char *reg_name;
++		int ret;
+ 
+ 		reg_name = devm_kzalloc(dev, reg_name_len, GFP_KERNEL);
+ 		if (!reg_name)
+@@ -988,6 +990,17 @@ pd692x0_register_managers_regulator(stru
+ 		if (IS_ERR(rdev))
+ 			return PTR_ERR(rdev);
+ 
++		/* VMAIN is described as main supply for the manager.
++		 * Add other VAUX power supplies and link them to the
++		 * virtual device rdev->dev.
++		 */
++		ret = devm_regulator_bulk_get_enable(&rdev->dev,
++						     ARRAY_SIZE(regulators),
++						     regulators);
++		if (ret)
++			return dev_err_probe(&rdev->dev, ret,
++					     "Failed to enable regulators\n");
++
+ 		priv->manager_reg[i] = rdev;
+ 	}
+ 
+@@ -1640,6 +1653,7 @@ static const struct fw_upload_ops pd692x
+ 
+ static int pd692x0_i2c_probe(struct i2c_client *client)
+ {
++	static const char * const regulators[] = { "vdd", "vdda" };
+ 	struct pd692x0_msg msg, buf = {0}, zero = {0};
+ 	struct device *dev = &client->dev;
+ 	struct pd692x0_msg_ver ver;
+@@ -1647,6 +1661,12 @@ static int pd692x0_i2c_probe(struct i2c_
+ 	struct fw_upload *fwl;
+ 	int ret;
+ 
++	ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulators),
++					     regulators);
++	if (ret)
++		return dev_err_probe(dev, ret,
++				     "Failed to enable regulators\n");
++
+ 	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ 		dev_err(dev, "i2c check functionality failed\n");
+ 		return -ENXIO;
diff --git a/target/linux/generic/backport-6.12/626-19-v6.17-net-pse-pd-tps23881-Add-static-port-priority-feature.patch b/target/linux/generic/backport-6.12/626-19-v6.17-net-pse-pd-tps23881-Add-static-port-priority-feature.patch
new file mode 100644
index 0000000000..739233a12e
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-19-v6.17-net-pse-pd-tps23881-Add-static-port-priority-feature.patch
@@ -0,0 +1,392 @@
+From 56cfc97635e9164395c9242f72746454347155ab Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent at bootlin.com>
+Date: Tue, 17 Jun 2025 14:12:11 +0200
+Subject: [PATCH] net: pse-pd: tps23881: Add support for static port priority
+ feature
+
+This patch enhances PSE callbacks by introducing support for the static
+port priority feature. It extends interrupt management to handle and report
+detection, classification, and disconnection events. Additionally, it
+introduces the pi_get_pw_req() callback, which provides information about
+the power requested by the Powered Devices.
+
+Interrupt support is essential for the proper functioning of the TPS23881
+controller. Without it, after a power-on (PWON), the controller will
+no longer perform detection and classification. This could lead to
+potential hazards, such as connecting a non-PoE device after a PoE device,
+which might result in magic smoke.
+
+Signed-off-by: Kory Maincent (Dent Project) <kory.maincent at bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel at pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-12-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba at kernel.org>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/tps23881.c | 244 +++++++++++++++++++++++++++++++---
+ 1 file changed, 228 insertions(+), 16 deletions(-)
+
+--- a/drivers/net/pse-pd/tps23881.c
++++ b/drivers/net/pse-pd/tps23881.c
+@@ -20,20 +20,30 @@
+ 
+ #define TPS23881_REG_IT		0x0
+ #define TPS23881_REG_IT_MASK	0x1
++#define TPS23881_REG_IT_DISF	BIT(2)
++#define TPS23881_REG_IT_DETC	BIT(3)
++#define TPS23881_REG_IT_CLASC	BIT(4)
+ #define TPS23881_REG_IT_IFAULT	BIT(5)
+ #define TPS23881_REG_IT_SUPF	BIT(7)
++#define TPS23881_REG_DET_EVENT	0x5
+ #define TPS23881_REG_FAULT	0x7
+ #define TPS23881_REG_SUPF_EVENT	0xb
+ #define TPS23881_REG_TSD	BIT(7)
++#define TPS23881_REG_DISC	0xc
+ #define TPS23881_REG_PW_STATUS	0x10
+ #define TPS23881_REG_OP_MODE	0x12
++#define TPS23881_REG_DISC_EN	0x13
+ #define TPS23881_OP_MODE_SEMIAUTO	0xaaaa
+ #define TPS23881_REG_DIS_EN	0x13
+ #define TPS23881_REG_DET_CLA_EN	0x14
+ #define TPS23881_REG_GEN_MASK	0x17
++#define TPS23881_REG_CLCHE	BIT(2)
++#define TPS23881_REG_DECHE	BIT(3)
+ #define TPS23881_REG_NBITACC	BIT(5)
+ #define TPS23881_REG_INTEN	BIT(7)
+ #define TPS23881_REG_PW_EN	0x19
++#define TPS23881_REG_RESET	0x1a
++#define TPS23881_REG_CLRAIN	BIT(7)
+ #define TPS23881_REG_2PAIR_POL1	0x1e
+ #define TPS23881_REG_PORT_MAP	0x26
+ #define TPS23881_REG_PORT_POWER	0x29
+@@ -178,6 +188,7 @@ static int tps23881_pi_enable(struct pse
+ 	struct i2c_client *client = priv->client;
+ 	u8 chan;
+ 	u16 val;
++	int ret;
+ 
+ 	if (id >= TPS23881_MAX_CHANS)
+ 		return -ERANGE;
+@@ -191,7 +202,22 @@ static int tps23881_pi_enable(struct pse
+ 				       BIT(chan % 4));
+ 	}
+ 
+-	return i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
++	ret = i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
++	if (ret)
++		return ret;
++
++	/* Enable DC disconnect*/
++	chan = priv->port[id].chan[0];
++	ret = i2c_smbus_read_word_data(client, TPS23881_REG_DISC_EN);
++	if (ret < 0)
++		return ret;
++
++	val = tps23881_set_val(ret, chan, 0, BIT(chan % 4), BIT(chan % 4));
++	ret = i2c_smbus_write_word_data(client, TPS23881_REG_DISC_EN, val);
++	if (ret)
++		return ret;
++
++	return 0;
+ }
+ 
+ static int tps23881_pi_disable(struct pse_controller_dev *pcdev, int id)
+@@ -224,6 +250,17 @@ static int tps23881_pi_disable(struct ps
+ 	 */
+ 	mdelay(5);
+ 
++	/* Disable DC disconnect*/
++	chan = priv->port[id].chan[0];
++	ret = i2c_smbus_read_word_data(client, TPS23881_REG_DISC_EN);
++	if (ret < 0)
++		return ret;
++
++	val = tps23881_set_val(ret, chan, 0, 0, BIT(chan % 4));
++	ret = i2c_smbus_write_word_data(client, TPS23881_REG_DISC_EN, val);
++	if (ret)
++		return ret;
++
+ 	/* Enable detection and classification */
+ 	ret = i2c_smbus_read_word_data(client, TPS23881_REG_DET_CLA_EN);
+ 	if (ret < 0)
+@@ -919,6 +956,47 @@ static int tps23881_setup_pi_matrix(stru
+ 	return ret;
+ }
+ 
++static int tps23881_power_class_table[] = {
++	-ERANGE,
++	4000,
++	7000,
++	15500,
++	30000,
++	15500,
++	15500,
++	-ERANGE,
++	45000,
++	60000,
++	75000,
++	90000,
++	15500,
++	45000,
++	-ERANGE,
++	-ERANGE,
++};
++
++static int tps23881_pi_get_pw_req(struct pse_controller_dev *pcdev, int id)
++{
++	struct tps23881_priv *priv = to_tps23881_priv(pcdev);
++	struct i2c_client *client = priv->client;
++	u8 reg, chan;
++	int ret;
++	u16 val;
++
++	/* For a 4-pair the classification need 5ms to be completed */
++	if (priv->port[id].is_4p)
++		mdelay(5);
++
++	chan = priv->port[id].chan[0];
++	reg = TPS23881_REG_DISC + (chan % 4);
++	ret = i2c_smbus_read_word_data(client, reg);
++	if (ret < 0)
++		return ret;
++
++	val = tps23881_calc_val(ret, chan, 4, 0xf);
++	return tps23881_power_class_table[val];
++}
++
+ static const struct pse_controller_ops tps23881_ops = {
+ 	.setup_pi_matrix = tps23881_setup_pi_matrix,
+ 	.pi_enable = tps23881_pi_enable,
+@@ -931,6 +1009,7 @@ static const struct pse_controller_ops t
+ 	.pi_get_pw_limit = tps23881_pi_get_pw_limit,
+ 	.pi_set_pw_limit = tps23881_pi_set_pw_limit,
+ 	.pi_get_pw_limit_ranges = tps23881_pi_get_pw_limit_ranges,
++	.pi_get_pw_req = tps23881_pi_get_pw_req,
+ };
+ 
+ static const char fw_parity_name[] = "ti/tps23881/tps23881-parity-14.bin";
+@@ -1088,17 +1167,113 @@ static void tps23881_irq_event_over_temp
+ 	}
+ }
+ 
+-static void tps23881_irq_event_over_current(struct tps23881_priv *priv,
+-					    u16 reg_val,
+-					    unsigned long *notifs,
+-					    unsigned long *notifs_mask)
++static int tps23881_irq_event_over_current(struct tps23881_priv *priv,
++					   u16 reg_val,
++					   unsigned long *notifs,
++					   unsigned long *notifs_mask)
+ {
++	int i, ret;
+ 	u8 chans;
+ 
+ 	chans = tps23881_irq_export_chans_helper(reg_val, 0);
++	if (!chans)
++		return 0;
++
++	tps23881_set_notifs_helper(priv, chans, notifs, notifs_mask,
++				   ETHTOOL_PSE_EVENT_OVER_CURRENT |
++				   ETHTOOL_C33_PSE_EVENT_DISCONNECTION);
++
++	/* Over Current event resets the power limit registers so we need
++	 * to configured it again.
++	 */
++	for_each_set_bit(i, notifs_mask, priv->pcdev.nr_lines) {
++		if (priv->port[i].pw_pol < 0)
++			continue;
++
++		ret = tps23881_pi_enable_manual_pol(priv, i);
++		if (ret < 0)
++			return ret;
++
++		/* Set power policy */
++		ret = tps23881_pi_set_pw_pol_limit(priv, i,
++						   priv->port[i].pw_pol,
++						   priv->port[i].is_4p);
++		if (ret < 0)
++			return ret;
++	}
++
++	return 0;
++}
++
++static void tps23881_irq_event_disconnection(struct tps23881_priv *priv,
++					     u16 reg_val,
++					     unsigned long *notifs,
++					     unsigned long *notifs_mask)
++{
++	u8 chans;
++
++	chans = tps23881_irq_export_chans_helper(reg_val, 4);
+ 	if (chans)
+ 		tps23881_set_notifs_helper(priv, chans, notifs, notifs_mask,
+-					   ETHTOOL_PSE_EVENT_OVER_CURRENT);
++					   ETHTOOL_C33_PSE_EVENT_DISCONNECTION);
++}
++
++static int tps23881_irq_event_detection(struct tps23881_priv *priv,
++					u16 reg_val,
++					unsigned long *notifs,
++					unsigned long *notifs_mask)
++{
++	enum ethtool_pse_event event;
++	int reg, ret, i, val;
++	unsigned long chans;
++
++	chans = tps23881_irq_export_chans_helper(reg_val, 0);
++	for_each_set_bit(i, &chans, TPS23881_MAX_CHANS) {
++		reg = TPS23881_REG_DISC + (i % 4);
++		ret = i2c_smbus_read_word_data(priv->client, reg);
++		if (ret < 0)
++			return ret;
++
++		val = tps23881_calc_val(ret, i, 0, 0xf);
++		/* If detection valid */
++		if (val == 0x4)
++			event = ETHTOOL_C33_PSE_EVENT_DETECTION;
++		else
++			event = ETHTOOL_C33_PSE_EVENT_DISCONNECTION;
++
++		tps23881_set_notifs_helper(priv, BIT(i), notifs,
++					   notifs_mask, event);
++	}
++
++	return 0;
++}
++
++static int tps23881_irq_event_classification(struct tps23881_priv *priv,
++					     u16 reg_val,
++					     unsigned long *notifs,
++					     unsigned long *notifs_mask)
++{
++	int reg, ret, val, i;
++	unsigned long chans;
++
++	chans = tps23881_irq_export_chans_helper(reg_val, 4);
++	for_each_set_bit(i, &chans, TPS23881_MAX_CHANS) {
++		reg = TPS23881_REG_DISC + (i % 4);
++		ret = i2c_smbus_read_word_data(priv->client, reg);
++		if (ret < 0)
++			return ret;
++
++		val = tps23881_calc_val(ret, i, 4, 0xf);
++		/* Do not report classification event for unknown class */
++		if (!val || val == 0x8 || val == 0xf)
++			continue;
++
++		tps23881_set_notifs_helper(priv, BIT(i), notifs,
++					   notifs_mask,
++					   ETHTOOL_C33_PSE_EVENT_CLASSIFICATION);
++	}
++
++	return 0;
+ }
+ 
+ static int tps23881_irq_event_handler(struct tps23881_priv *priv, u16 reg,
+@@ -1106,7 +1281,7 @@ static int tps23881_irq_event_handler(st
+ 				      unsigned long *notifs_mask)
+ {
+ 	struct i2c_client *client = priv->client;
+-	int ret;
++	int ret, val;
+ 
+ 	/* The Supply event bit is repeated twice so we only need to read
+ 	 * the one from the first byte.
+@@ -1118,13 +1293,36 @@ static int tps23881_irq_event_handler(st
+ 		tps23881_irq_event_over_temp(priv, ret, notifs, notifs_mask);
+ 	}
+ 
+-	if (reg & (TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_IFAULT << 8)) {
++	if (reg & (TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_IFAULT << 8 |
++		   TPS23881_REG_IT_DISF | TPS23881_REG_IT_DISF << 8)) {
+ 		ret = i2c_smbus_read_word_data(client, TPS23881_REG_FAULT);
+ 		if (ret < 0)
+ 			return ret;
+-		tps23881_irq_event_over_current(priv, ret, notifs, notifs_mask);
++		ret = tps23881_irq_event_over_current(priv, ret, notifs,
++						      notifs_mask);
++		if (ret)
++			return ret;
++
++		tps23881_irq_event_disconnection(priv, ret, notifs, notifs_mask);
+ 	}
+ 
++	if (reg & (TPS23881_REG_IT_DETC | TPS23881_REG_IT_DETC << 8 |
++		   TPS23881_REG_IT_CLASC | TPS23881_REG_IT_CLASC << 8)) {
++		ret = i2c_smbus_read_word_data(client, TPS23881_REG_DET_EVENT);
++		if (ret < 0)
++			return ret;
++
++		val = ret;
++		ret = tps23881_irq_event_detection(priv, val, notifs,
++						   notifs_mask);
++		if (ret)
++			return ret;
++
++		ret = tps23881_irq_event_classification(priv, val, notifs,
++							notifs_mask);
++		if (ret)
++			return ret;
++	}
+ 	return 0;
+ }
+ 
+@@ -1178,7 +1376,14 @@ static int tps23881_setup_irq(struct tps
+ 	int ret;
+ 	u16 val;
+ 
+-	val = TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_SUPF;
++	if (!irq) {
++		dev_err(&client->dev, "interrupt is missing");
++		return -EINVAL;
++	}
++
++	val = TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_SUPF |
++	      TPS23881_REG_IT_DETC | TPS23881_REG_IT_CLASC |
++	      TPS23881_REG_IT_DISF;
+ 	val |= val << 8;
+ 	ret = i2c_smbus_write_word_data(client, TPS23881_REG_IT_MASK, val);
+ 	if (ret)
+@@ -1188,11 +1393,19 @@ static int tps23881_setup_irq(struct tps
+ 	if (ret < 0)
+ 		return ret;
+ 
+-	val = (u16)(ret | TPS23881_REG_INTEN | TPS23881_REG_INTEN << 8);
++	val = TPS23881_REG_INTEN | TPS23881_REG_CLCHE | TPS23881_REG_DECHE;
++	val |= val << 8;
++	val |= (u16)ret;
+ 	ret = i2c_smbus_write_word_data(client, TPS23881_REG_GEN_MASK, val);
+ 	if (ret < 0)
+ 		return ret;
+ 
++	/* Reset interrupts registers */
++	ret = i2c_smbus_write_word_data(client, TPS23881_REG_RESET,
++					TPS23881_REG_CLRAIN);
++	if (ret < 0)
++		return ret;
++
+ 	return devm_pse_irq_helper(&priv->pcdev, irq, 0, &irq_desc);
+ }
+ 
+@@ -1270,17 +1483,16 @@ static int tps23881_i2c_probe(struct i2c
+ 	priv->pcdev.dev = dev;
+ 	priv->pcdev.types = ETHTOOL_PSE_C33;
+ 	priv->pcdev.nr_lines = TPS23881_MAX_CHANS;
++	priv->pcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_STATIC;
+ 	ret = devm_pse_controller_register(dev, &priv->pcdev);
+ 	if (ret) {
+ 		return dev_err_probe(dev, ret,
+ 				     "failed to register PSE controller\n");
+ 	}
+ 
+-	if (client->irq) {
+-		ret = tps23881_setup_irq(priv, client->irq);
+-		if (ret)
+-			return ret;
+-	}
++	ret = tps23881_setup_irq(priv, client->irq);
++	if (ret)
++		return ret;
+ 
+ 	return ret;
+ }
diff --git a/target/linux/generic/backport-6.12/626-20-v6.17-net-pse-pd-pd692x0-reduce-stack-usage.patch b/target/linux/generic/backport-6.12/626-20-v6.17-net-pse-pd-pd692x0-reduce-stack-usage.patch
new file mode 100644
index 0000000000..d5e1aaf9a3
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-20-v6.17-net-pse-pd-pd692x0-reduce-stack-usage.patch
@@ -0,0 +1,55 @@
+From d12b3dc106090b358fb67b7c0c717a0884327ddf Mon Sep 17 00:00:00 2001
+From: Arnd Bergmann <arnd at arndb.de>
+Date: Wed, 9 Jul 2025 17:32:04 +0200
+Subject: [PATCH] net: pse-pd: pd692x0: reduce stack usage in
+ pd692x0_setup_pi_matrix
+
+The pd692x0_manager array in this function is really too big to fit on the
+stack, though this never triggered a warning until a recent patch made
+it slightly bigger:
+
+drivers/net/pse-pd/pd692x0.c: In function 'pd692x0_setup_pi_matrix':
+drivers/net/pse-pd/pd692x0.c:1210:1: error: the frame size of 1584 bytes is larger than 1536 bytes [-Werror=frame-larger-than=]
+
+Change the function to dynamically allocate the array here.
+
+Fixes: 359754013e6a ("net: pse-pd: pd692x0: Add support for PSE PI priority feature")
+Signed-off-by: Arnd Bergmann <arnd at arndb.de>
+Reviewed-by: Kory Maincent <kory.maincent at bootlin.com>
+Link: https://patch.msgid.link/20250709153210.1920125-1-arnd@kernel.org
+Signed-off-by: Jakub Kicinski <kuba at kernel.org>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/pd692x0.c | 8 ++++++--
+ 1 file changed, 6 insertions(+), 2 deletions(-)
+--- a/drivers/net/pse-pd/pd692x0.c
++++ b/drivers/net/pse-pd/pd692x0.c
+@@ -860,7 +860,7 @@ out:
+ 
+ static int
+ pd692x0_of_get_managers(struct pd692x0_priv *priv,
+-			struct pd692x0_manager manager[PD692X0_MAX_MANAGERS])
++			struct pd692x0_manager *manager)
+ {
+ 	struct device_node *managers_node, *node;
+ 	int ret, nmanagers, i, j;
+@@ -1164,7 +1164,7 @@ pd692x0_write_ports_matrix(struct pd692x
+ 
+ static int pd692x0_setup_pi_matrix(struct pse_controller_dev *pcdev)
+ {
+-	struct pd692x0_manager manager[PD692X0_MAX_MANAGERS] = {0};
++	struct pd692x0_manager *manager __free(kfree) = NULL;
+ 	struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+ 	struct pd692x0_matrix port_matrix[PD692X0_MAX_PIS];
+ 	int ret, i, j, nmanagers;
+@@ -1174,6 +1174,10 @@ static int pd692x0_setup_pi_matrix(struc
+ 	    priv->fw_state != PD692X0_FW_COMPLETE)
+ 		return 0;
+ 
++	manager = kcalloc(PD692X0_MAX_MANAGERS, sizeof(*manager), GFP_KERNEL);
++	if (!manager)
++		return -ENOMEM;
++
+ 	ret = pd692x0_of_get_managers(priv, manager);
+ 	if (ret < 0)
+ 		return ret;
diff --git a/target/linux/generic/backport-6.12/626-21-v6.18-net-pse-pd-pd692x0-Fix-power-budget-leak.patch b/target/linux/generic/backport-6.12/626-21-v6.18-net-pse-pd-pd692x0-Fix-power-budget-leak.patch
new file mode 100644
index 0000000000..af23745947
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-21-v6.18-net-pse-pd-pd692x0-Fix-power-budget-leak.patch
@@ -0,0 +1,121 @@
+From 1c67f9c54cdc70627e3f6472b89cd3d895df974c Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent at bootlin.com>
+Date: Wed, 20 Aug 2025 15:27:07 +0200
+Subject: [PATCH] net: pse-pd: pd692x0: Fix power budget leak in manager setup
+ error path
+
+Fix a resource leak where manager power budgets were freed on both
+success and error paths during manager setup. Power budgets should
+only be freed on error paths after regulator registration or during
+driver removal.
+
+Refactor cleanup logic by extracting OF node cleanup and power budget
+freeing into separate helper functions for better maintainability.
+
+Fixes: 359754013e6a ("net: pse-pd: pd692x0: Add support for PSE PI priority feature")
+Signed-off-by: Kory Maincent <kory.maincent at bootlin.com>
+Link: https://patch.msgid.link/20250820132708.837255-1-kory.maincent@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba at kernel.org>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/pd692x0.c | 59 +++++++++++++++++++++++++++---------
+ 1 file changed, 44 insertions(+), 15 deletions(-)
+
+--- a/drivers/net/pse-pd/pd692x0.c
++++ b/drivers/net/pse-pd/pd692x0.c
+@@ -1162,12 +1162,44 @@ pd692x0_write_ports_matrix(struct pd692x
+ 	return 0;
+ }
+ 
++static void pd692x0_of_put_managers(struct pd692x0_priv *priv,
++				    struct pd692x0_manager *manager,
++				    int nmanagers)
++{
++	int i, j;
++
++	for (i = 0; i < nmanagers; i++) {
++		for (j = 0; j < manager[i].nports; j++)
++			of_node_put(manager[i].port_node[j]);
++		of_node_put(manager[i].node);
++	}
++}
++
++static void pd692x0_managers_free_pw_budget(struct pd692x0_priv *priv)
++{
++	int i;
++
++	for (i = 0; i < PD692X0_MAX_MANAGERS; i++) {
++		struct regulator *supply;
++
++		if (!priv->manager_reg[i] || !priv->manager_pw_budget[i])
++			continue;
++
++		supply = priv->manager_reg[i]->supply;
++		if (!supply)
++			continue;
++
++		regulator_free_power_budget(supply,
++					    priv->manager_pw_budget[i]);
++	}
++}
++
+ static int pd692x0_setup_pi_matrix(struct pse_controller_dev *pcdev)
+ {
+ 	struct pd692x0_manager *manager __free(kfree) = NULL;
+ 	struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+ 	struct pd692x0_matrix port_matrix[PD692X0_MAX_PIS];
+-	int ret, i, j, nmanagers;
++	int ret, nmanagers;
+ 
+ 	/* Should we flash the port matrix */
+ 	if (priv->fw_state != PD692X0_FW_OK &&
+@@ -1185,31 +1217,27 @@ static int pd692x0_setup_pi_matrix(struc
+ 	nmanagers = ret;
+ 	ret = pd692x0_register_managers_regulator(priv, manager, nmanagers);
+ 	if (ret)
+-		goto out;
++		goto err_of_managers;
+ 
+ 	ret = pd692x0_configure_managers(priv, nmanagers);
+ 	if (ret)
+-		goto out;
++		goto err_of_managers;
+ 
+ 	ret = pd692x0_set_ports_matrix(priv, manager, nmanagers, port_matrix);
+ 	if (ret)
+-		goto out;
++		goto err_managers_req_pw;
+ 
+ 	ret = pd692x0_write_ports_matrix(priv, port_matrix);
+ 	if (ret)
+-		goto out;
+-
+-out:
+-	for (i = 0; i < nmanagers; i++) {
+-		struct regulator *supply = priv->manager_reg[i]->supply;
++		goto err_managers_req_pw;
+ 
+-		regulator_free_power_budget(supply,
+-					    priv->manager_pw_budget[i]);
++	pd692x0_of_put_managers(priv, manager, nmanagers);
++	return 0;
+ 
+-		for (j = 0; j < manager[i].nports; j++)
+-			of_node_put(manager[i].port_node[j]);
+-		of_node_put(manager[i].node);
+-	}
++err_managers_req_pw:
++	pd692x0_managers_free_pw_budget(priv);
++err_of_managers:
++	pd692x0_of_put_managers(priv, manager, nmanagers);
+ 	return ret;
+ }
+ 
+@@ -1748,6 +1776,7 @@ static void pd692x0_i2c_remove(struct i2
+ {
+ 	struct pd692x0_priv *priv = i2c_get_clientdata(client);
+ 
++	pd692x0_managers_free_pw_budget(priv);
+ 	firmware_upload_unregister(priv->fwl);
+ }
+ 
diff --git a/target/linux/generic/backport-6.12/626-22-v6.18-net-pse-pd-pd692x0-Skip-power-budget-when-undefined.patch b/target/linux/generic/backport-6.12/626-22-v6.18-net-pse-pd-pd692x0-Skip-power-budget-when-undefined.patch
new file mode 100644
index 0000000000..98da7b5080
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-22-v6.18-net-pse-pd-pd692x0-Skip-power-budget-when-undefined.patch
@@ -0,0 +1,37 @@
+From 7ef353879f714602b43f98662069f4fb86536761 Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent at bootlin.com>
+Date: Wed, 20 Aug 2025 15:33:21 +0200
+Subject: [PATCH] net: pse-pd: pd692x0: Skip power budget configuration when
+ undefined
+
+If the power supply's power budget is not defined in the device tree,
+the current code still requests power and configures the PSE manager
+with a 0W power limit, which is undesirable behavior.
+
+Skip power budget configuration entirely when the budget is zero,
+avoiding unnecessary power requests and preventing invalid 0W limits
+from being set on the PSE manager.
+
+Fixes: 359754013e6a ("net: pse-pd: pd692x0: Add support for PSE PI priority feature")
+Signed-off-by: Kory Maincent <kory.maincent at bootlin.com>
+Acked-by: Oleksij Rempel <o.rempel at pengutronix.de>
+Link: https://patch.msgid.link/20250820133321.841054-1-kory.maincent@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba at kernel.org>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/pd692x0.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+--- a/drivers/net/pse-pd/pd692x0.c
++++ b/drivers/net/pse-pd/pd692x0.c
+@@ -1041,6 +1041,10 @@ pd692x0_configure_managers(struct pd692x
+ 		int pw_budget;
+ 
+ 		pw_budget = regulator_get_unclaimed_power_budget(supply);
++		if (!pw_budget)
++			/* Do nothing if no power budget */
++			continue;
++
+ 		/* Max power budget per manager */
+ 		if (pw_budget > 6000000)
+ 			pw_budget = 6000000;
diff --git a/target/linux/generic/backport-6.12/626-23-v6.18-net-pse-pd-Add-Si3474-PSE-controller-driver.patch b/target/linux/generic/backport-6.12/626-23-v6.18-net-pse-pd-Add-Si3474-PSE-controller-driver.patch
new file mode 100644
index 0000000000..2f4087facc
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-23-v6.18-net-pse-pd-Add-Si3474-PSE-controller-driver.patch
@@ -0,0 +1,636 @@
+From a2317231df4b22e6634fe3d8645e7cef848acf49 Mon Sep 17 00:00:00 2001
+From: Piotr Kubik <piotr.kubik at adtran.com>
+Date: Tue, 26 Aug 2025 14:41:58 +0000
+Subject: [PATCH] net: pse-pd: Add Si3474 PSE controller driver
+
+Add a driver for the Skyworks Si3474 I2C Power Sourcing Equipment
+controller.
+
+Driver supports basic features of Si3474 IC:
+- get port status,
+- get port power,
+- get port voltage,
+- enable/disable port power.
+
+Only 4p configurations are supported at this moment.
+
+Signed-off-by: Piotr Kubik <piotr.kubik at adtran.com>
+Reviewed-by: Kory Maincent <kory.maincent at bootlin.com>
+Link: https://patch.msgid.link/9b72c8cd-c8d3-4053-9c80-671b9481d166@adtran.com
+Signed-off-by: Paolo Abeni <pabeni at redhat.com>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/Kconfig  |  11 +
+ drivers/net/pse-pd/Makefile |   1 +
+ drivers/net/pse-pd/si3474.c | 578 ++++++++++++++++++++++++++++++++++++
+ 3 files changed, 590 insertions(+)
+ create mode 100644 drivers/net/pse-pd/si3474.c
+--- a/drivers/net/pse-pd/Kconfig
++++ b/drivers/net/pse-pd/Kconfig
+@@ -32,6 +32,17 @@ config PSE_PD692X0
+ 	  To compile this driver as a module, choose M here: the
+ 	  module will be called pd692x0.
+ 
++config PSE_SI3474
++	tristate "Si3474 PSE controller"
++	depends on I2C
++	help
++	  This module provides support for Si3474 regulator based Ethernet
++	  Power Sourcing Equipment.
++	  Only 4-pair PSE configurations are supported.
++
++	  To compile this driver as a module, choose M here: the
++	  module will be called si3474.
++
+ config PSE_TPS23881
+ 	tristate "TPS23881 PSE controller"
+ 	depends on I2C
+--- a/drivers/net/pse-pd/Makefile
++++ b/drivers/net/pse-pd/Makefile
+@@ -5,4 +5,5 @@ obj-$(CONFIG_PSE_CONTROLLER) += pse_core
+ 
+ obj-$(CONFIG_PSE_REGULATOR) += pse_regulator.o
+ obj-$(CONFIG_PSE_PD692X0) += pd692x0.o
++obj-$(CONFIG_PSE_SI3474) += si3474.o
+ obj-$(CONFIG_PSE_TPS23881) += tps23881.o
+--- /dev/null
++++ b/drivers/net/pse-pd/si3474.c
+@@ -0,0 +1,578 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Driver for the Skyworks Si3474 PoE PSE Controller
++ *
++ * Chip Architecture & Terminology:
++ *
++ * The Si3474 is a single-chip PoE PSE controller managing 8 physical power
++ * delivery channels. Internally, it's structured into two logical "Quads".
++ *
++ * Quad 0: Manages physical channels ('ports' in datasheet) 0, 1, 2, 3
++ * Quad 1: Manages physical channels ('ports' in datasheet) 4, 5, 6, 7
++ *
++ * Each Quad is accessed via a separate I2C address. The base address range is
++ * set by hardware pins A1-A4, and the specific address selects Quad 0 (usually
++ * the lower/even address) or Quad 1 (usually the higher/odd address).
++ * See datasheet Table 2.2 for the address mapping.
++ *
++ * While the Quads manage channel-specific operations, the Si3474 package has
++ * several resources shared across the entire chip:
++ * - Single RESETb input pin.
++ * - Single INTb output pin (signals interrupts from *either* Quad).
++ * - Single OSS input pin (Emergency Shutdown).
++ * - Global I2C Address (0x7F) used for firmware updates.
++ * - Global status monitoring (Temperature, VDD/VPWR Undervoltage Lockout).
++ *
++ * Driver Architecture:
++ *
++ * To handle the mix of per-Quad access and shared resources correctly, this
++ * driver treats the entire Si3474 package as one logical device. The driver
++ * instance associated with the primary I2C address (Quad 0) takes ownership.
++ * It discovers and manages the I2C client for the secondary address (Quad 1).
++ * This primary instance handles shared resources like IRQ management and
++ * registers a single PSE controller device representing all logical PIs.
++ * Internal functions route I2C commands to the appropriate Quad's i2c_client
++ * based on the target channel or PI.
++ *
++ * Terminology Mapping:
++ *
++ * - "PI" (Power Interface): Refers to the logical PSE port as defined by
++ * IEEE 802.3 (typically corresponds to an RJ45 connector). This is the
++ * `id` (0-7) used in the pse_controller_ops.
++ * - "Channel": Refers to one of the 8 physical power control paths within
++ * the Si3474 chip itself (hardware channels 0-7). This terminology is
++ * used internally within the driver to avoid confusion with 'ports'.
++ * - "Quad": One of the two internal 4-channel management units within the
++ * Si3474, each accessed via its own I2C address.
++ *
++ * Relationship:
++ * - A 2-Pair PoE PI uses 1 Channel.
++ * - A 4-Pair PoE PI uses 2 Channels.
++ *
++ * ASCII Schematic:
++ *
++ * +-----------------------------------------------------+
++ * |                    Si3474 Chip                      |
++ * |                                                     |
++ * | +---------------------+     +---------------------+ |
++ * | |      Quad 0         |     |      Quad 1         | |
++ * | | Channels 0, 1, 2, 3 |     | Channels 4, 5, 6, 7 | |
++ * | +----------^----------+     +-------^-------------+ |
++ * | I2C Addr 0 |                        | I2C Addr 1    |
++ * |            +------------------------+               |
++ * | (Primary Driver Instance) (Managed by Primary)      |
++ * |                                                     |
++ * | Shared Resources (affect whole chip):               |
++ * |  - Single INTb Output -> Handled by Primary         |
++ * |  - Single RESETb Input                              |
++ * |  - Single OSS Input   -> Handled by Primary         |
++ * |  - Global I2C Addr (0x7F) for Firmware Update       |
++ * |  - Global Status (Temp, VDD/VPWR UVLO)              |
++ * +-----------------------------------------------------+
++ *        |   |   |   |        |   |   |   |
++ *        Ch0 Ch1 Ch2 Ch3      Ch4 Ch5 Ch6 Ch7  (Physical Channels)
++ *
++ * Example Mapping (Logical PI to Physical Channel(s)):
++ * * 2-Pair Mode (8 PIs):
++ * PI 0 -> Ch 0
++ * PI 1 -> Ch 1
++ * ...
++ * PI 7 -> Ch 7
++ * * 4-Pair Mode (4 PIs):
++ * PI 0 -> Ch 0 + Ch 1  (Managed via Quad 0 Addr)
++ * PI 1 -> Ch 2 + Ch 3  (Managed via Quad 0 Addr)
++ * PI 2 -> Ch 4 + Ch 5  (Managed via Quad 1 Addr)
++ * PI 3 -> Ch 6 + Ch 7  (Managed via Quad 1 Addr)
++ * (Note: Actual mapping depends on Device Tree and PORT_REMAP config)
++ */
++
++#include <linux/i2c.h>
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/platform_device.h>
++#include <linux/pse-pd/pse.h>
++
++#define SI3474_MAX_CHANS 8
++
++#define MANUFACTURER_ID 0x08
++#define IC_ID 0x05
++#define SI3474_DEVICE_ID (MANUFACTURER_ID << 3 | IC_ID)
++
++/* Misc registers */
++#define VENDOR_IC_ID_REG 0x1B
++#define TEMPERATURE_REG 0x2C
++#define FIRMWARE_REVISION_REG 0x41
++#define CHIP_REVISION_REG 0x43
++
++/* Main status registers */
++#define POWER_STATUS_REG 0x10
++#define PORT_MODE_REG 0x12
++#define DETECT_CLASS_ENABLE_REG 0x14
++
++/* PORTn Current */
++#define PORT1_CURRENT_LSB_REG 0x30
++
++/* PORTn Current [mA], return in [nA] */
++/* 1000 * ((PORTn_CURRENT_MSB << 8) + PORTn_CURRENT_LSB) / 16384 */
++#define SI3474_NA_STEP (1000 * 1000 * 1000 / 16384)
++
++/* VPWR Voltage */
++#define VPWR_LSB_REG 0x2E
++#define VPWR_MSB_REG 0x2F
++
++/* PORTn Voltage */
++#define PORT1_VOLTAGE_LSB_REG 0x32
++
++/* VPWR Voltage [V], return in [uV] */
++/* 60 * (( VPWR_MSB << 8) + VPWR_LSB) / 16384 */
++#define SI3474_UV_STEP (1000 * 1000 * 60 / 16384)
++
++/* Helper macros */
++#define CHAN_IDX(chan) ((chan) % 4)
++#define CHAN_BIT(chan) BIT(CHAN_IDX(chan))
++#define CHAN_UPPER_BIT(chan) BIT(CHAN_IDX(chan) + 4)
++
++#define CHAN_MASK(chan) (0x03U << (2 * CHAN_IDX(chan)))
++#define CHAN_REG(base, chan) ((base) + (CHAN_IDX(chan) * 4))
++
++struct si3474_pi_desc {
++	u8 chan[2];
++	bool is_4p;
++};
++
++struct si3474_priv {
++	struct i2c_client *client[2];
++	struct pse_controller_dev pcdev;
++	struct device_node *np;
++	struct si3474_pi_desc pi[SI3474_MAX_CHANS];
++};
++
++static struct si3474_priv *to_si3474_priv(struct pse_controller_dev *pcdev)
++{
++	return container_of(pcdev, struct si3474_priv, pcdev);
++}
++
++static void si3474_get_channels(struct si3474_priv *priv, int id,
++				u8 *chan0, u8 *chan1)
++{
++	*chan0 = priv->pi[id].chan[0];
++	*chan1 = priv->pi[id].chan[1];
++}
++
++static struct i2c_client *si3474_get_chan_client(struct si3474_priv *priv,
++						 u8 chan)
++{
++	return (chan < 4) ? priv->client[0] : priv->client[1];
++}
++
++static int si3474_pi_get_admin_state(struct pse_controller_dev *pcdev, int id,
++				     struct pse_admin_state *admin_state)
++{
++	struct si3474_priv *priv = to_si3474_priv(pcdev);
++	struct i2c_client *client;
++	bool is_enabled;
++	u8 chan0, chan1;
++	s32 ret;
++
++	si3474_get_channels(priv, id, &chan0, &chan1);
++	client = si3474_get_chan_client(priv, chan0);
++
++	ret = i2c_smbus_read_byte_data(client, PORT_MODE_REG);
++	if (ret < 0) {
++		admin_state->c33_admin_state =
++			ETHTOOL_C33_PSE_ADMIN_STATE_UNKNOWN;
++		return ret;
++	}
++
++	is_enabled = ret & (CHAN_MASK(chan0) | CHAN_MASK(chan1));
++
++	if (is_enabled)
++		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 si3474_pi_get_pw_status(struct pse_controller_dev *pcdev, int id,
++				   struct pse_pw_status *pw_status)
++{
++	struct si3474_priv *priv = to_si3474_priv(pcdev);
++	struct i2c_client *client;
++	bool delivering;
++	u8 chan0, chan1;
++	s32 ret;
++
++	si3474_get_channels(priv, id, &chan0, &chan1);
++	client = si3474_get_chan_client(priv, chan0);
++
++	ret = i2c_smbus_read_byte_data(client, POWER_STATUS_REG);
++	if (ret < 0) {
++		pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_UNKNOWN;
++		return ret;
++	}
++
++	delivering = ret & (CHAN_UPPER_BIT(chan0) | CHAN_UPPER_BIT(chan1));
++
++	if (delivering)
++		pw_status->c33_pw_status =
++			ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;
++	else
++		pw_status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED;
++
++	return 0;
++}
++
++static int si3474_get_of_channels(struct si3474_priv *priv)
++{
++	struct pse_pi *pi;
++	u32 chan_id;
++	u8 pi_no;
++	s32 ret;
++
++	for (pi_no = 0; pi_no < SI3474_MAX_CHANS; pi_no++) {
++		pi = &priv->pcdev.pi[pi_no];
++		bool pairset_found = false;
++		u8 pairset_no;
++
++		for (pairset_no = 0; pairset_no < 2; pairset_no++) {
++			if (!pi->pairset[pairset_no].np)
++				continue;
++
++			pairset_found = true;
++
++			ret = of_property_read_u32(pi->pairset[pairset_no].np,
++						   "reg", &chan_id);
++			if (ret) {
++				dev_err(&priv->client[0]->dev,
++					"Failed to read channel reg property\n");
++				return ret;
++			}
++			if (chan_id > SI3474_MAX_CHANS) {
++				dev_err(&priv->client[0]->dev,
++					"Incorrect channel number: %d\n", chan_id);
++				return -EINVAL;
++			}
++
++			priv->pi[pi_no].chan[pairset_no] = chan_id;
++			/* Mark as 4-pair if second pairset is present */
++			priv->pi[pi_no].is_4p = (pairset_no == 1);
++		}
++
++		if (pairset_found && !priv->pi[pi_no].is_4p) {
++			dev_err(&priv->client[0]->dev,
++				"Second pairset is missing for PI %pOF, only 4p configs are supported\n",
++				pi->np);
++			return -EINVAL;
++		}
++	}
++
++	return 0;
++}
++
++static int si3474_setup_pi_matrix(struct pse_controller_dev *pcdev)
++{
++	struct si3474_priv *priv = to_si3474_priv(pcdev);
++	s32 ret;
++
++	ret = si3474_get_of_channels(priv);
++	if (ret < 0)
++		dev_warn(&priv->client[0]->dev,
++			 "Unable to parse DT PSE power interface matrix\n");
++
++	return ret;
++}
++
++static int si3474_pi_enable(struct pse_controller_dev *pcdev, int id)
++{
++	struct si3474_priv *priv = to_si3474_priv(pcdev);
++	struct i2c_client *client;
++	u8 chan0, chan1;
++	s32 ret;
++	u8 val;
++
++	si3474_get_channels(priv, id, &chan0, &chan1);
++	client = si3474_get_chan_client(priv, chan0);
++
++	/* Release PI from shutdown */
++	ret = i2c_smbus_read_byte_data(client, PORT_MODE_REG);
++	if (ret < 0)
++		return ret;
++
++	val = (u8)ret;
++	val |= CHAN_MASK(chan0);
++	val |= CHAN_MASK(chan1);
++
++	ret = i2c_smbus_write_byte_data(client, PORT_MODE_REG, val);
++	if (ret)
++		return ret;
++
++	/* DETECT_CLASS_ENABLE must be set when using AUTO mode,
++	 * otherwise PI does not power up - datasheet section 2.10.2
++	 */
++	val = CHAN_BIT(chan0) | CHAN_UPPER_BIT(chan0) |
++	      CHAN_BIT(chan1) | CHAN_UPPER_BIT(chan1);
++
++	ret = i2c_smbus_write_byte_data(client, DETECT_CLASS_ENABLE_REG, val);
++	if (ret)
++		return ret;
++
++	return 0;
++}
++
++static int si3474_pi_disable(struct pse_controller_dev *pcdev, int id)
++{
++	struct si3474_priv *priv = to_si3474_priv(pcdev);
++	struct i2c_client *client;
++	u8 chan0, chan1;
++	s32 ret;
++	u8 val;
++
++	si3474_get_channels(priv, id, &chan0, &chan1);
++	client = si3474_get_chan_client(priv, chan0);
++
++	/* Set PI in shutdown mode */
++	ret = i2c_smbus_read_byte_data(client, PORT_MODE_REG);
++	if (ret < 0)
++		return ret;
++
++	val = (u8)ret;
++	val &= ~CHAN_MASK(chan0);
++	val &= ~CHAN_MASK(chan1);
++
++	ret = i2c_smbus_write_byte_data(client, PORT_MODE_REG, val);
++	if (ret)
++		return ret;
++
++	return 0;
++}
++
++static int si3474_pi_get_chan_current(struct si3474_priv *priv, u8 chan)
++{
++	struct i2c_client *client;
++	u64 tmp_64;
++	s32 ret;
++	u8 reg;
++
++	client = si3474_get_chan_client(priv, chan);
++
++	/* Registers 0x30 to 0x3d */
++	reg = CHAN_REG(PORT1_CURRENT_LSB_REG, chan);
++
++	ret = i2c_smbus_read_word_data(client, reg);
++	if (ret < 0)
++		return ret;
++
++	tmp_64 = ret * SI3474_NA_STEP;
++
++	/* uA = nA / 1000 */
++	tmp_64 = DIV_ROUND_CLOSEST_ULL(tmp_64, 1000);
++	return (int)tmp_64;
++}
++
++static int si3474_pi_get_chan_voltage(struct si3474_priv *priv, u8 chan)
++{
++	struct i2c_client *client;
++	s32 ret;
++	u32 val;
++	u8 reg;
++
++	client = si3474_get_chan_client(priv, chan);
++
++	/* Registers 0x32 to 0x3f */
++	reg = CHAN_REG(PORT1_VOLTAGE_LSB_REG, chan);
++
++	ret = i2c_smbus_read_word_data(client, reg);
++	if (ret < 0)
++		return ret;
++
++	val = ret * SI3474_UV_STEP;
++
++	return (int)val;
++}
++
++static int si3474_pi_get_voltage(struct pse_controller_dev *pcdev, int id)
++{
++	struct si3474_priv *priv = to_si3474_priv(pcdev);
++	struct i2c_client *client;
++	u8 chan0, chan1;
++	s32 ret;
++
++	si3474_get_channels(priv, id, &chan0, &chan1);
++	client = si3474_get_chan_client(priv, chan0);
++
++	/* Check which channels are enabled*/
++	ret = i2c_smbus_read_byte_data(client, POWER_STATUS_REG);
++	if (ret < 0)
++		return ret;
++
++	/* Take voltage from the first enabled channel */
++	if (ret & CHAN_BIT(chan0))
++		ret = si3474_pi_get_chan_voltage(priv, chan0);
++	else if (ret & CHAN_BIT(chan1))
++		ret = si3474_pi_get_chan_voltage(priv, chan1);
++	else
++		/* 'should' be no voltage in this case */
++		return 0;
++
++	return ret;
++}
++
++static int si3474_pi_get_actual_pw(struct pse_controller_dev *pcdev, int id)
++{
++	struct si3474_priv *priv = to_si3474_priv(pcdev);
++	u8 chan0, chan1;
++	u32 uV, uA;
++	u64 tmp_64;
++	s32 ret;
++
++	ret = si3474_pi_get_voltage(&priv->pcdev, id);
++
++	/* Do not read currents if voltage is 0 */
++	if (ret <= 0)
++		return ret;
++	uV = ret;
++
++	si3474_get_channels(priv, id, &chan0, &chan1);
++
++	ret = si3474_pi_get_chan_current(priv, chan0);
++	if (ret < 0)
++		return ret;
++	uA = ret;
++
++	ret = si3474_pi_get_chan_current(priv, chan1);
++	if (ret < 0)
++		return ret;
++	uA += ret;
++
++	tmp_64 = uV;
++	tmp_64 *= uA;
++	/* mW = uV * uA / 1000000000 */
++	return DIV_ROUND_CLOSEST_ULL(tmp_64, 1000000000);
++}
++
++static const struct pse_controller_ops si3474_ops = {
++	.setup_pi_matrix = si3474_setup_pi_matrix,
++	.pi_enable = si3474_pi_enable,
++	.pi_disable = si3474_pi_disable,
++	.pi_get_actual_pw = si3474_pi_get_actual_pw,
++	.pi_get_voltage = si3474_pi_get_voltage,
++	.pi_get_admin_state = si3474_pi_get_admin_state,
++	.pi_get_pw_status = si3474_pi_get_pw_status,
++};
++
++static void si3474_ancillary_i2c_remove(void *data)
++{
++	struct i2c_client *client = data;
++
++	i2c_unregister_device(client);
++}
++
++static int si3474_i2c_probe(struct i2c_client *client)
++{
++	struct device *dev = &client->dev;
++	struct si3474_priv *priv;
++	u8 fw_version;
++	s32 ret;
++
++	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
++		dev_err(dev, "i2c check functionality failed\n");
++		return -ENXIO;
++	}
++
++	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
++	if (!priv)
++		return -ENOMEM;
++
++	ret = i2c_smbus_read_byte_data(client, VENDOR_IC_ID_REG);
++	if (ret < 0)
++		return ret;
++
++	if (ret != SI3474_DEVICE_ID) {
++		dev_err(dev, "Wrong device ID: 0x%x\n", ret);
++		return -ENXIO;
++	}
++
++	ret = i2c_smbus_read_byte_data(client, FIRMWARE_REVISION_REG);
++	if (ret < 0)
++		return ret;
++	fw_version = ret;
++
++	ret = i2c_smbus_read_byte_data(client, CHIP_REVISION_REG);
++	if (ret < 0)
++		return ret;
++
++	dev_dbg(dev, "Chip revision: 0x%x, firmware version: 0x%x\n",
++		ret, fw_version);
++
++	priv->client[0] = client;
++	i2c_set_clientdata(client, priv);
++
++	priv->client[1] = i2c_new_ancillary_device(priv->client[0], "secondary",
++						   priv->client[0]->addr + 1);
++	if (IS_ERR(priv->client[1]))
++		return PTR_ERR(priv->client[1]);
++
++	ret = devm_add_action_or_reset(dev, si3474_ancillary_i2c_remove, priv->client[1]);
++	if (ret < 0) {
++		dev_err(&priv->client[1]->dev, "Cannot register remove callback\n");
++		return ret;
++	}
++
++	ret = i2c_smbus_read_byte_data(priv->client[1], VENDOR_IC_ID_REG);
++	if (ret < 0) {
++		dev_err(&priv->client[1]->dev, "Cannot access secondary PSE controller\n");
++		return ret;
++	}
++
++	if (ret != SI3474_DEVICE_ID) {
++		dev_err(&priv->client[1]->dev,
++			"Wrong device ID for secondary PSE controller: 0x%x\n", ret);
++		return -ENXIO;
++	}
++
++	priv->np = dev->of_node;
++	priv->pcdev.owner = THIS_MODULE;
++	priv->pcdev.ops = &si3474_ops;
++	priv->pcdev.dev = dev;
++	priv->pcdev.types = ETHTOOL_PSE_C33;
++	priv->pcdev.nr_lines = SI3474_MAX_CHANS;
++
++	ret = devm_pse_controller_register(dev, &priv->pcdev);
++	if (ret) {
++		dev_err(dev, "Failed to register PSE controller: 0x%x\n", ret);
++		return ret;
++	}
++
++	return 0;
++}
++
++static const struct i2c_device_id si3474_id[] = {
++	{ "si3474" },
++	{}
++};
++MODULE_DEVICE_TABLE(i2c, si3474_id);
++
++static const struct of_device_id si3474_of_match[] = {
++	{
++		.compatible = "skyworks,si3474",
++	},
++	{},
++};
++MODULE_DEVICE_TABLE(of, si3474_of_match);
++
++static struct i2c_driver si3474_driver = {
++	.probe = si3474_i2c_probe,
++	.id_table = si3474_id,
++	.driver = {
++		.name = "si3474",
++		.of_match_table = si3474_of_match,
++	},
++};
++module_i2c_driver(si3474_driver);
++
++MODULE_AUTHOR("Piotr Kubik <piotr.kubik at adtran.com>");
++MODULE_DESCRIPTION("Skyworks Si3474 PoE PSE Controller driver");
++MODULE_LICENSE("GPL");
diff --git a/target/linux/generic/backport-6.12/626-24-v6.19-net-pse-pd-tps23881-Fix-current-measurement-scaling.patch b/target/linux/generic/backport-6.12/626-24-v6.19-net-pse-pd-tps23881-Fix-current-measurement-scaling.patch
new file mode 100644
index 0000000000..5150da365f
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-24-v6.19-net-pse-pd-tps23881-Fix-current-measurement-scaling.patch
@@ -0,0 +1,31 @@
+From 2c95a756e0cfc19af6d0b32b0c6cf3bada334998 Mon Sep 17 00:00:00 2001
+From: Thomas Wismer <thomas.wismer at scs.ch>
+Date: Mon, 6 Oct 2025 22:40:29 +0200
+Subject: [PATCH] net: pse-pd: tps23881: Fix current measurement scaling
+
+The TPS23881 improves on the TPS23880 with current sense resistors reduced
+from 255 mOhm to 200 mOhm. This has a direct impact on the scaling of the
+current measurement. However, the latest TPS23881 data sheet from May 2023
+still shows the scaling of the TPS23880 model.
+
+Fixes: 7f076ce3f1733 ("net: pse-pd: tps23881: Add support for power limit and measurement features")
+Signed-off-by: Thomas Wismer <thomas.wismer at scs.ch>
+Acked-by: Kory Maincent <kory.maincent at bootlin.com>
+Link: https://patch.msgid.link/20251006204029.7169-2-thomas@wismer.xyz
+Signed-off-by: Jakub Kicinski <kuba at kernel.org>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/tps23881.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/pse-pd/tps23881.c
++++ b/drivers/net/pse-pd/tps23881.c
+@@ -62,7 +62,7 @@
+ #define TPS23881_REG_SRAM_DATA	0x61
+ 
+ #define TPS23881_UV_STEP	3662
+-#define TPS23881_NA_STEP	70190
++#define TPS23881_NA_STEP	89500
+ #define TPS23881_MW_STEP	500
+ #define TPS23881_MIN_PI_PW_LIMIT_MW	2000
+ 
diff --git a/target/linux/generic/backport-6.12/626-25-v6.19-net-pse-pd-pd692x0-Replace-__free-macro.patch b/target/linux/generic/backport-6.12/626-25-v6.19-net-pse-pd-pd692x0-Replace-__free-macro.patch
new file mode 100644
index 0000000000..fafa5af2d1
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-25-v6.19-net-pse-pd-pd692x0-Replace-__free-macro.patch
@@ -0,0 +1,57 @@
+From f197902cd21ae833850679b216bb62c0d056bbb3 Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent at bootlin.com>
+Date: Mon, 13 Oct 2025 16:05:31 +0200
+Subject: [PATCH] net: pse-pd: pd692x0: Replace __free macro with explicit
+ kfree calls
+
+Replace __free(kfree) with explicit kfree() calls to follow the net
+subsystem policy of avoiding automatic cleanup macros as described in
+the documentation.
+
+Signed-off-by: Kory Maincent <kory.maincent at bootlin.com>
+Reviewed-by: Simon Horman <horms at kernel.org>
+Link: https://patch.msgid.link/20251013-feature_pd692x0_reboot_keep_conf-v2-1-68ab082a93dd@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba at kernel.org>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/pd692x0.c | 7 +++++--
+ 1 file changed, 5 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/pse-pd/pd692x0.c
++++ b/drivers/net/pse-pd/pd692x0.c
+@@ -1200,9 +1200,9 @@ static void pd692x0_managers_free_pw_bud
+ 
+ static int pd692x0_setup_pi_matrix(struct pse_controller_dev *pcdev)
+ {
+-	struct pd692x0_manager *manager __free(kfree) = NULL;
+ 	struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+ 	struct pd692x0_matrix port_matrix[PD692X0_MAX_PIS];
++	struct pd692x0_manager *manager;
+ 	int ret, nmanagers;
+ 
+ 	/* Should we flash the port matrix */
+@@ -1216,7 +1216,7 @@ static int pd692x0_setup_pi_matrix(struc
+ 
+ 	ret = pd692x0_of_get_managers(priv, manager);
+ 	if (ret < 0)
+-		return ret;
++		goto err_free_manager;
+ 
+ 	nmanagers = ret;
+ 	ret = pd692x0_register_managers_regulator(priv, manager, nmanagers);
+@@ -1236,12 +1236,15 @@ static int pd692x0_setup_pi_matrix(struc
+ 		goto err_managers_req_pw;
+ 
+ 	pd692x0_of_put_managers(priv, manager, nmanagers);
++	kfree(manager);
+ 	return 0;
+ 
+ err_managers_req_pw:
+ 	pd692x0_managers_free_pw_budget(priv);
+ err_of_managers:
+ 	pd692x0_of_put_managers(priv, manager, nmanagers);
++err_free_manager:
++	kfree(manager);
+ 	return ret;
+ }
+ 
diff --git a/target/linux/generic/backport-6.12/626-26-v6.19-net-pse-pd-pd692x0-Separate-configuration-parsing.patch b/target/linux/generic/backport-6.12/626-26-v6.19-net-pse-pd-pd692x0-Separate-configuration-parsing.patch
new file mode 100644
index 0000000000..e88bbd03bf
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-26-v6.19-net-pse-pd-pd692x0-Separate-configuration-parsing.patch
@@ -0,0 +1,291 @@
+From 6fa1f8b64a47edd7d8420d8fd1008507aee2853e Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent at bootlin.com>
+Date: Mon, 13 Oct 2025 16:05:32 +0200
+Subject: [PATCH] net: pse-pd: pd692x0: Separate configuration parsing from
+ hardware setup
+
+Cache the port matrix configuration in driver private data to enable
+PSE controller reconfiguration. This refactoring separates device tree
+parsing from hardware configuration application, allowing settings to be
+reapplied without reparsing the device tree.
+
+This refactoring is a prerequisite for preserving PSE configuration
+across reboots to prevent power disruption to connected devices.
+
+Signed-off-by: Kory Maincent <kory.maincent at bootlin.com>
+Reviewed-by: Simon Horman <horms at kernel.org>
+Link: https://patch.msgid.link/20251013-feature_pd692x0_reboot_keep_conf-v2-2-68ab082a93dd@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba at kernel.org>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/pd692x0.c | 115 +++++++++++++++++++++++------------
+ 1 file changed, 76 insertions(+), 39 deletions(-)
+
+--- a/drivers/net/pse-pd/pd692x0.c
++++ b/drivers/net/pse-pd/pd692x0.c
+@@ -85,6 +85,11 @@ enum {
+ 	PD692X0_MSG_CNT
+ };
+ 
++struct pd692x0_matrix {
++	u8 hw_port_a;
++	u8 hw_port_b;
++};
++
+ struct pd692x0_priv {
+ 	struct i2c_client *client;
+ 	struct pse_controller_dev pcdev;
+@@ -101,6 +106,8 @@ struct pd692x0_priv {
+ 	enum ethtool_c33_pse_admin_state admin_state[PD692X0_MAX_PIS];
+ 	struct regulator_dev *manager_reg[PD692X0_MAX_MANAGERS];
+ 	int manager_pw_budget[PD692X0_MAX_MANAGERS];
++	int nmanagers;
++	struct pd692x0_matrix *port_matrix;
+ };
+ 
+ /* Template list of communication messages. The non-null bytes defined here
+@@ -809,11 +816,6 @@ struct pd692x0_manager {
+ 	int nports;
+ };
+ 
+-struct pd692x0_matrix {
+-	u8 hw_port_a;
+-	u8 hw_port_b;
+-};
+-
+ static int
+ pd692x0_of_get_ports_manager(struct pd692x0_priv *priv,
+ 			     struct pd692x0_manager *manager,
+@@ -903,7 +905,8 @@ pd692x0_of_get_managers(struct pd692x0_p
+ 	}
+ 
+ 	of_node_put(managers_node);
+-	return nmanagers;
++	priv->nmanagers = nmanagers;
++	return 0;
+ 
+ out:
+ 	for (i = 0; i < nmanagers; i++) {
+@@ -963,8 +966,7 @@ pd692x0_register_manager_regulator(struc
+ 
+ static int
+ pd692x0_register_managers_regulator(struct pd692x0_priv *priv,
+-				    const struct pd692x0_manager *manager,
+-				    int nmanagers)
++				    const struct pd692x0_manager *manager)
+ {
+ 	struct device *dev = &priv->client->dev;
+ 	size_t reg_name_len;
+@@ -975,7 +977,7 @@ pd692x0_register_managers_regulator(stru
+ 	 */
+ 	reg_name_len = strlen(dev_name(dev)) + 23;
+ 
+-	for (i = 0; i < nmanagers; i++) {
++	for (i = 0; i < priv->nmanagers; i++) {
+ 		static const char * const regulators[] = { "vaux5", "vaux3p3" };
+ 		struct regulator_dev *rdev;
+ 		char *reg_name;
+@@ -1008,10 +1010,14 @@ pd692x0_register_managers_regulator(stru
+ }
+ 
+ static int
+-pd692x0_conf_manager_power_budget(struct pd692x0_priv *priv, int id, int pw)
++pd692x0_conf_manager_power_budget(struct pd692x0_priv *priv, int id)
+ {
+ 	struct pd692x0_msg msg, buf;
+-	int ret, pw_mW = pw / 1000;
++	int ret, pw_mW;
++
++	pw_mW = priv->manager_pw_budget[id] / 1000;
++	if (!pw_mW)
++		return 0;
+ 
+ 	msg = pd692x0_msg_template_list[PD692X0_MSG_GET_POWER_BANK];
+ 	msg.data[0] = id;
+@@ -1032,11 +1038,11 @@ pd692x0_conf_manager_power_budget(struct
+ }
+ 
+ static int
+-pd692x0_configure_managers(struct pd692x0_priv *priv, int nmanagers)
++pd692x0_req_managers_pw_budget(struct pd692x0_priv *priv)
+ {
+ 	int i, ret;
+ 
+-	for (i = 0; i < nmanagers; i++) {
++	for (i = 0; i < priv->nmanagers; i++) {
+ 		struct regulator *supply = priv->manager_reg[i]->supply;
+ 		int pw_budget;
+ 
+@@ -1053,7 +1059,18 @@ pd692x0_configure_managers(struct pd692x
+ 			return ret;
+ 
+ 		priv->manager_pw_budget[i] = pw_budget;
+-		ret = pd692x0_conf_manager_power_budget(priv, i, pw_budget);
++	}
++
++	return 0;
++}
++
++static int
++pd692x0_configure_managers(struct pd692x0_priv *priv)
++{
++	int i, ret;
++
++	for (i = 0; i < priv->nmanagers; i++) {
++		ret = pd692x0_conf_manager_power_budget(priv, i);
+ 		if (ret < 0)
+ 			return ret;
+ 	}
+@@ -1101,10 +1118,9 @@ pd692x0_set_port_matrix(const struct pse
+ 
+ static int
+ pd692x0_set_ports_matrix(struct pd692x0_priv *priv,
+-			 const struct pd692x0_manager *manager,
+-			 int nmanagers,
+-			 struct pd692x0_matrix port_matrix[PD692X0_MAX_PIS])
++			 const struct pd692x0_manager *manager)
+ {
++	struct pd692x0_matrix *port_matrix = priv->port_matrix;
+ 	struct pse_controller_dev *pcdev = &priv->pcdev;
+ 	int i, ret;
+ 
+@@ -1117,7 +1133,7 @@ pd692x0_set_ports_matrix(struct pd692x0_
+ 	/* Update with values for every PSE PIs */
+ 	for (i = 0; i < pcdev->nr_lines; i++) {
+ 		ret = pd692x0_set_port_matrix(&pcdev->pi[i].pairset[0],
+-					      manager, nmanagers,
++					      manager, priv->nmanagers,
+ 					      &port_matrix[i]);
+ 		if (ret) {
+ 			dev_err(&priv->client->dev,
+@@ -1126,7 +1142,7 @@ pd692x0_set_ports_matrix(struct pd692x0_
+ 		}
+ 
+ 		ret = pd692x0_set_port_matrix(&pcdev->pi[i].pairset[1],
+-					      manager, nmanagers,
++					      manager, priv->nmanagers,
+ 					      &port_matrix[i]);
+ 		if (ret) {
+ 			dev_err(&priv->client->dev,
+@@ -1139,9 +1155,9 @@ pd692x0_set_ports_matrix(struct pd692x0_
+ }
+ 
+ static int
+-pd692x0_write_ports_matrix(struct pd692x0_priv *priv,
+-			   const struct pd692x0_matrix port_matrix[PD692X0_MAX_PIS])
++pd692x0_write_ports_matrix(struct pd692x0_priv *priv)
+ {
++	struct pd692x0_matrix *port_matrix = priv->port_matrix;
+ 	struct pd692x0_msg msg, buf;
+ 	int ret, i;
+ 
+@@ -1166,13 +1182,32 @@ pd692x0_write_ports_matrix(struct pd692x
+ 	return 0;
+ }
+ 
++static int pd692x0_hw_conf_init(struct pd692x0_priv *priv)
++{
++	int ret;
++
++	/* Is PD692x0 ready to be configured? */
++	if (priv->fw_state != PD692X0_FW_OK &&
++	    priv->fw_state != PD692X0_FW_COMPLETE)
++		return 0;
++
++	ret = pd692x0_configure_managers(priv);
++	if (ret)
++		return ret;
++
++	ret = pd692x0_write_ports_matrix(priv);
++	if (ret)
++		return ret;
++
++	return 0;
++}
++
+ static void pd692x0_of_put_managers(struct pd692x0_priv *priv,
+-				    struct pd692x0_manager *manager,
+-				    int nmanagers)
++				    struct pd692x0_manager *manager)
+ {
+ 	int i, j;
+ 
+-	for (i = 0; i < nmanagers; i++) {
++	for (i = 0; i < priv->nmanagers; i++) {
+ 		for (j = 0; j < manager[i].nports; j++)
+ 			of_node_put(manager[i].port_node[j]);
+ 		of_node_put(manager[i].node);
+@@ -1201,48 +1236,50 @@ static void pd692x0_managers_free_pw_bud
+ static int pd692x0_setup_pi_matrix(struct pse_controller_dev *pcdev)
+ {
+ 	struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+-	struct pd692x0_matrix port_matrix[PD692X0_MAX_PIS];
++	struct pd692x0_matrix *port_matrix;
+ 	struct pd692x0_manager *manager;
+-	int ret, nmanagers;
+-
+-	/* Should we flash the port matrix */
+-	if (priv->fw_state != PD692X0_FW_OK &&
+-	    priv->fw_state != PD692X0_FW_COMPLETE)
+-		return 0;
++	int ret;
+ 
+ 	manager = kcalloc(PD692X0_MAX_MANAGERS, sizeof(*manager), GFP_KERNEL);
+ 	if (!manager)
+ 		return -ENOMEM;
+ 
++	port_matrix = devm_kcalloc(&priv->client->dev, PD692X0_MAX_PIS,
++				   sizeof(*port_matrix), GFP_KERNEL);
++	if (!port_matrix) {
++		ret = -ENOMEM;
++		goto err_free_manager;
++	}
++	priv->port_matrix = port_matrix;
++
+ 	ret = pd692x0_of_get_managers(priv, manager);
+ 	if (ret < 0)
+ 		goto err_free_manager;
+ 
+-	nmanagers = ret;
+-	ret = pd692x0_register_managers_regulator(priv, manager, nmanagers);
++	ret = pd692x0_register_managers_regulator(priv, manager);
+ 	if (ret)
+ 		goto err_of_managers;
+ 
+-	ret = pd692x0_configure_managers(priv, nmanagers);
++	ret = pd692x0_req_managers_pw_budget(priv);
+ 	if (ret)
+ 		goto err_of_managers;
+ 
+-	ret = pd692x0_set_ports_matrix(priv, manager, nmanagers, port_matrix);
++	ret = pd692x0_set_ports_matrix(priv, manager);
+ 	if (ret)
+ 		goto err_managers_req_pw;
+ 
+-	ret = pd692x0_write_ports_matrix(priv, port_matrix);
++	ret = pd692x0_hw_conf_init(priv);
+ 	if (ret)
+ 		goto err_managers_req_pw;
+ 
+-	pd692x0_of_put_managers(priv, manager, nmanagers);
++	pd692x0_of_put_managers(priv, manager);
+ 	kfree(manager);
+ 	return 0;
+ 
+ err_managers_req_pw:
+ 	pd692x0_managers_free_pw_budget(priv);
+ err_of_managers:
+-	pd692x0_of_put_managers(priv, manager, nmanagers);
++	pd692x0_of_put_managers(priv, manager);
+ err_free_manager:
+ 	kfree(manager);
+ 	return ret;
+@@ -1647,7 +1684,7 @@ static enum fw_upload_err pd692x0_fw_pol
+ 		return FW_UPLOAD_ERR_FW_INVALID;
+ 	}
+ 
+-	ret = pd692x0_setup_pi_matrix(&priv->pcdev);
++	ret = pd692x0_hw_conf_init(priv);
+ 	if (ret < 0) {
+ 		dev_err(&client->dev, "Error configuring ports matrix (%pe)\n",
+ 			ERR_PTR(ret));
diff --git a/target/linux/generic/backport-6.12/626-27-v6.19-net-pse-pd-pd692x0-Preserve-PSE-configuration.patch b/target/linux/generic/backport-6.12/626-27-v6.19-net-pse-pd-pd692x0-Preserve-PSE-configuration.patch
new file mode 100644
index 0000000000..4ae835e932
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-27-v6.19-net-pse-pd-pd692x0-Preserve-PSE-configuration.patch
@@ -0,0 +1,111 @@
+From 8f3d044b34fe99b894046edb84605456195cabc0 Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent at bootlin.com>
+Date: Mon, 13 Oct 2025 16:05:33 +0200
+Subject: [PATCH] net: pse-pd: pd692x0: Preserve PSE configuration across
+ reboots
+
+Detect when PSE hardware is already configured (user byte == 42) and
+skip hardware initialization to prevent power interruption to connected
+devices during system reboots.
+
+Previously, the driver would always reconfigure the PSE hardware on
+probe, causing a port matrix reflash that resulted in temporary power
+loss to all connected devices. This change maintains power continuity
+by preserving existing configuration when the PSE has been previously
+initialized.
+
+Signed-off-by: Kory Maincent <kory.maincent at bootlin.com>
+Reviewed-by: Simon Horman <horms at kernel.org>
+Link: https://patch.msgid.link/20251013-feature_pd692x0_reboot_keep_conf-v2-3-68ab082a93dd@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba at kernel.org>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/pd692x0.c | 35 ++++++++++++++++++++++++++++++++---
+ 1 file changed, 32 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/pse-pd/pd692x0.c
++++ b/drivers/net/pse-pd/pd692x0.c
+@@ -30,6 +30,8 @@
+ #define PD692X0_FW_MIN_VER	5
+ #define PD692X0_FW_PATCH_VER	5
+ 
++#define PD692X0_USER_BYTE	42
++
+ enum pd692x0_fw_state {
+ 	PD692X0_FW_UNKNOWN,
+ 	PD692X0_FW_OK,
+@@ -80,6 +82,7 @@ enum {
+ 	PD692X0_MSG_GET_PORT_PARAM,
+ 	PD692X0_MSG_GET_POWER_BANK,
+ 	PD692X0_MSG_SET_POWER_BANK,
++	PD692X0_MSG_SET_USER_BYTE,
+ 
+ 	/* add new message above here */
+ 	PD692X0_MSG_CNT
+@@ -103,6 +106,7 @@ struct pd692x0_priv {
+ 	bool last_cmd_key;
+ 	unsigned long last_cmd_key_time;
+ 
++	bool cfg_saved;
+ 	enum ethtool_c33_pse_admin_state admin_state[PD692X0_MAX_PIS];
+ 	struct regulator_dev *manager_reg[PD692X0_MAX_MANAGERS];
+ 	int manager_pw_budget[PD692X0_MAX_MANAGERS];
+@@ -193,6 +197,12 @@ static const struct pd692x0_msg pd692x0_
+ 		.key = PD692X0_KEY_CMD,
+ 		.sub = {0x07, 0x0b, 0x57},
+ 	},
++	[PD692X0_MSG_SET_USER_BYTE] = {
++		.key = PD692X0_KEY_PRG,
++		.sub = {0x41, PD692X0_USER_BYTE},
++		.data = {0x4e, 0x4e, 0x4e, 0x4e,
++			 0x4e, 0x4e, 0x4e, 0x4e},
++	},
+ };
+ 
+ static u8 pd692x0_build_msg(struct pd692x0_msg *msg, u8 echo)
+@@ -1233,6 +1243,15 @@ static void pd692x0_managers_free_pw_bud
+ 	}
+ }
+ 
++static int
++pd692x0_save_user_byte(struct pd692x0_priv *priv)
++{
++	struct pd692x0_msg msg, buf;
++
++	msg = pd692x0_msg_template_list[PD692X0_MSG_SET_USER_BYTE];
++	return pd692x0_sendrecv_msg(priv, &msg, &buf);
++}
++
+ static int pd692x0_setup_pi_matrix(struct pse_controller_dev *pcdev)
+ {
+ 	struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
+@@ -1268,9 +1287,16 @@ static int pd692x0_setup_pi_matrix(struc
+ 	if (ret)
+ 		goto err_managers_req_pw;
+ 
+-	ret = pd692x0_hw_conf_init(priv);
+-	if (ret)
+-		goto err_managers_req_pw;
++	/* Do not init the conf if it is already saved */
++	if (!priv->cfg_saved) {
++		ret = pd692x0_hw_conf_init(priv);
++		if (ret)
++			goto err_managers_req_pw;
++
++		ret = pd692x0_save_user_byte(priv);
++		if (ret)
++			goto err_managers_req_pw;
++	}
+ 
+ 	pd692x0_of_put_managers(priv, manager);
+ 	kfree(manager);
+@@ -1793,6 +1819,9 @@ static int pd692x0_i2c_probe(struct i2c_
+ 		}
+ 	}
+ 
++	if (buf.data[2] == PD692X0_USER_BYTE)
++		priv->cfg_saved = true;
++
+ 	priv->np = dev->of_node;
+ 	priv->pcdev.nr_lines = PD692X0_MAX_PIS;
+ 	priv->pcdev.owner = THIS_MODULE;
diff --git a/target/linux/generic/backport-6.12/626-28-v6.19-net-pse-pd-tps23881-Add-support-for-TPS23881B.patch b/target/linux/generic/backport-6.12/626-28-v6.19-net-pse-pd-tps23881-Add-support-for-TPS23881B.patch
new file mode 100644
index 0000000000..2c188db7a3
--- /dev/null
+++ b/target/linux/generic/backport-6.12/626-28-v6.19-net-pse-pd-tps23881-Add-support-for-TPS23881B.patch
@@ -0,0 +1,170 @@
+From 4d07797faaa19aa8e80e10a04ca1a72c643ef5cf Mon Sep 17 00:00:00 2001
+From: Thomas Wismer <thomas.wismer at scs.ch>
+Date: Wed, 29 Oct 2025 22:23:09 +0100
+Subject: [PATCH] net: pse-pd: tps23881: Add support for TPS23881B
+
+The TPS23881B uses different firmware than the TPS23881. Trying to load the
+TPS23881 firmware on a TPS23881B device fails and must be omitted.
+
+The TPS23881B ships with a more recent ROM firmware. Moreover, no updated
+firmware has been released yet and so the firmware loading step must be
+skipped. As of today, the TPS23881B is intended to use its ROM firmware.
+
+Signed-off-by: Thomas Wismer <thomas.wismer at scs.ch>
+Reviewed-by: Kory Maincent <kory.maincent at bootlin.com>
+Acked-by: Oleksij Rempel <o.rempel at pengutronix.de>
+Link: https://patch.msgid.link/20251029212312.108749-2-thomas@wismer.xyz
+Signed-off-by: Jakub Kicinski <kuba at kernel.org>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ drivers/net/pse-pd/tps23881.c | 69 +++++++++++++++++++++++++++--------
+ 1 file changed, 54 insertions(+), 15 deletions(-)
+
+--- a/drivers/net/pse-pd/tps23881.c
++++ b/drivers/net/pse-pd/tps23881.c
+@@ -55,8 +55,6 @@
+ #define TPS23881_REG_TPON	BIT(0)
+ #define TPS23881_REG_FWREV	0x41
+ #define TPS23881_REG_DEVID	0x43
+-#define TPS23881_REG_DEVID_MASK	0xF0
+-#define TPS23881_DEVICE_ID	0x02
+ #define TPS23881_REG_CHAN1_CLASS	0x4c
+ #define TPS23881_REG_SRAM_CTRL	0x60
+ #define TPS23881_REG_SRAM_DATA	0x61
+@@ -1012,8 +1010,28 @@ static const struct pse_controller_ops t
+ 	.pi_get_pw_req = tps23881_pi_get_pw_req,
+ };
+ 
+-static const char fw_parity_name[] = "ti/tps23881/tps23881-parity-14.bin";
+-static const char fw_sram_name[] = "ti/tps23881/tps23881-sram-14.bin";
++struct tps23881_info {
++	u8 dev_id;	/* device ID and silicon revision */
++	const char *fw_parity_name;	/* parity code firmware file name */
++	const char *fw_sram_name;	/* SRAM code firmware file name */
++};
++
++enum tps23881_model {
++	TPS23881,
++	TPS23881B,
++};
++
++static const struct tps23881_info tps23881_info[] = {
++	[TPS23881] = {
++		.dev_id = 0x22,
++		.fw_parity_name = "ti/tps23881/tps23881-parity-14.bin",
++		.fw_sram_name = "ti/tps23881/tps23881-sram-14.bin",
++	},
++	[TPS23881B] = {
++		.dev_id = 0x24,
++		/* skip SRAM load, ROM provides Clause 145 hardware-level support */
++	},
++};
+ 
+ struct tps23881_fw_conf {
+ 	u8 reg;
+@@ -1085,16 +1103,17 @@ out:
+ 	return ret;
+ }
+ 
+-static int tps23881_flash_sram_fw(struct i2c_client *client)
++static int tps23881_flash_sram_fw(struct i2c_client *client,
++				  const struct tps23881_info *info)
+ {
+ 	int ret;
+ 
+-	ret = tps23881_flash_sram_fw_part(client, fw_parity_name,
++	ret = tps23881_flash_sram_fw_part(client, info->fw_parity_name,
+ 					  tps23881_fw_parity_conf);
+ 	if (ret)
+ 		return ret;
+ 
+-	ret = tps23881_flash_sram_fw_part(client, fw_sram_name,
++	ret = tps23881_flash_sram_fw_part(client, info->fw_sram_name,
+ 					  tps23881_fw_sram_conf);
+ 	if (ret)
+ 		return ret;
+@@ -1412,6 +1431,7 @@ static int tps23881_setup_irq(struct tps
+ static int tps23881_i2c_probe(struct i2c_client *client)
+ {
+ 	struct device *dev = &client->dev;
++	const struct tps23881_info *info;
+ 	struct tps23881_priv *priv;
+ 	struct gpio_desc *reset;
+ 	int ret;
+@@ -1422,6 +1442,10 @@ static int tps23881_i2c_probe(struct i2c
+ 		return -ENXIO;
+ 	}
+ 
++	info = i2c_get_match_data(client);
++	if (!info)
++		return -EINVAL;
++
+ 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ 	if (!priv)
+ 		return -ENOMEM;
+@@ -1440,7 +1464,7 @@ static int tps23881_i2c_probe(struct i2c
+ 		 * to Load TPS2388x SRAM and Parity Code over I2C" (Rev E))
+ 		 * indicates we should delay that programming by at least 50ms. So
+ 		 * we'll wait the entire 50ms here to ensure we're safe to go to the
+-		 * SRAM loading proceedure.
++		 * SRAM loading procedure.
+ 		 */
+ 		msleep(50);
+ 	}
+@@ -1449,20 +1473,27 @@ static int tps23881_i2c_probe(struct i2c
+ 	if (ret < 0)
+ 		return ret;
+ 
+-	if (FIELD_GET(TPS23881_REG_DEVID_MASK, ret) != TPS23881_DEVICE_ID) {
++	if (ret != info->dev_id) {
+ 		dev_err(dev, "Wrong device ID\n");
+ 		return -ENXIO;
+ 	}
+ 
+-	ret = tps23881_flash_sram_fw(client);
+-	if (ret < 0)
+-		return ret;
++	if (info->fw_sram_name) {
++		ret = tps23881_flash_sram_fw(client, info);
++		if (ret < 0)
++			return ret;
++	}
+ 
+ 	ret = i2c_smbus_read_byte_data(client, TPS23881_REG_FWREV);
+ 	if (ret < 0)
+ 		return ret;
+ 
+-	dev_info(&client->dev, "Firmware revision 0x%x\n", ret);
++	if (ret == 0xFF) {
++		dev_err(&client->dev, "Device entered safe mode\n");
++		return -ENXIO;
++	}
++	dev_info(&client->dev, "Firmware revision 0x%x%s\n", ret,
++		 ret == 0x00 ? " (ROM firmware)" : "");
+ 
+ 	/* Set configuration B, 16 bit access on a single device address */
+ 	ret = i2c_smbus_read_byte_data(client, TPS23881_REG_GEN_MASK);
+@@ -1498,13 +1529,21 @@ static int tps23881_i2c_probe(struct i2c
+ }
+ 
+ static const struct i2c_device_id tps23881_id[] = {
+-	{ "tps23881" },
++	{ "tps23881", .driver_data = (kernel_ulong_t)&tps23881_info[TPS23881] },
++	{ "tps23881b", .driver_data = (kernel_ulong_t)&tps23881_info[TPS23881B] },
+ 	{ }
+ };
+ MODULE_DEVICE_TABLE(i2c, tps23881_id);
+ 
+ static const struct of_device_id tps23881_of_match[] = {
+-	{ .compatible = "ti,tps23881", },
++	{
++		.compatible = "ti,tps23881",
++		.data = &tps23881_info[TPS23881]
++	},
++	{
++		.compatible = "ti,tps23881b",
++		.data = &tps23881_info[TPS23881B]
++	},
+ 	{ },
+ };
+ MODULE_DEVICE_TABLE(of, tps23881_of_match);
diff --git a/target/linux/generic/backport-6.12/627-01-v6.17-net-pse-pd-Add-ethtool_netlink_generated-header.patch b/target/linux/generic/backport-6.12/627-01-v6.17-net-pse-pd-Add-ethtool_netlink_generated-header.patch
new file mode 100644
index 0000000000..863bd2e56c
--- /dev/null
+++ b/target/linux/generic/backport-6.12/627-01-v6.17-net-pse-pd-Add-ethtool_netlink_generated-header.patch
@@ -0,0 +1,80 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: OpenWrt PSE-PD Backport <openwrt at openwrt.org>
+Date: Fri, 31 Jan 2025 00:00:00 +0000
+Subject: [PATCH] net: pse-pd: Add ethtool netlink definitions for PSE events
+
+GENERATED PATCH - OpenWrt PSE-PD Backport
+
+This patch:
+1. Creates include/uapi/linux/ethtool_netlink_generated.h with PSE event
+   definitions (in mainline 6.17+, this file is auto-generated)
+2. Adds ETHTOOL_MSG_PSE_NTF to the kernel message types enum
+
+These definitions are required by PSE event reporting patches.
+
+Signed-off-by: OpenWrt PSE-PD Backport <openwrt at openwrt.org>
+Signed-off-by: Carlo Szelinsky <github at szelinsky.de>
+---
+ include/uapi/linux/ethtool_netlink.h           |  1 +
+ include/uapi/linux/ethtool_netlink_generated.h | 45 ++++++++++++++++++
+ 2 files changed, 46 insertions(+)
+ create mode 100644 include/uapi/linux/ethtool_netlink_generated.h
+
+--- a/include/uapi/linux/ethtool_netlink.h
++++ b/include/uapi/linux/ethtool_netlink.h
+@@ -115,6 +115,7 @@ enum {
+ 	ETHTOOL_MSG_PHY_GET_REPLY,
+ 	ETHTOOL_MSG_PHY_NTF,
+ 
++	ETHTOOL_MSG_PSE_NTF,
+ 	/* add new constants above here */
+ 	__ETHTOOL_MSG_KERNEL_CNT,
+ 	ETHTOOL_MSG_KERNEL_MAX = __ETHTOOL_MSG_KERNEL_CNT - 1
+--- /dev/null
++++ b/include/uapi/linux/ethtool_netlink_generated.h
+@@ -0,0 +1,45 @@
++/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
++/*
++ * Minimal PSE ethtool netlink definitions - OpenWrt backport for 6.12.67
++ * In mainline 6.17+, this file is auto-generated from:
++ *   Documentation/netlink/specs/ethtool.yaml
++ *
++ * GENERATED PATCH - OpenWrt PSE-PD Backport
++ */
++
++#ifndef _UAPI_LINUX_ETHTOOL_NETLINK_GENERATED_H
++#define _UAPI_LINUX_ETHTOOL_NETLINK_GENERATED_H
++
++/**
++ * enum ethtool_pse_event - PSE event list for the PSE controller
++ * @ETHTOOL_PSE_EVENT_OVER_CURRENT: PSE output current is too high
++ * @ETHTOOL_PSE_EVENT_OVER_TEMP: PSE in over temperature state
++ * @ETHTOOL_C33_PSE_EVENT_DETECTION: detection process occur on the PSE
++ * @ETHTOOL_C33_PSE_EVENT_CLASSIFICATION: classification process occur
++ * @ETHTOOL_C33_PSE_EVENT_DISCONNECTION: PD has been disconnected
++ * @ETHTOOL_PSE_EVENT_OVER_BUDGET: PSE turned off due to over budget
++ * @ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR: PSE power control error
++ */
++enum ethtool_pse_event {
++	ETHTOOL_PSE_EVENT_OVER_CURRENT = 1,
++	ETHTOOL_PSE_EVENT_OVER_TEMP = 2,
++	ETHTOOL_C33_PSE_EVENT_DETECTION = 4,
++	ETHTOOL_C33_PSE_EVENT_CLASSIFICATION = 8,
++	ETHTOOL_C33_PSE_EVENT_DISCONNECTION = 16,
++	ETHTOOL_PSE_EVENT_OVER_BUDGET = 32,
++	ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR = 64,
++};
++
++/* PSE notification attributes */
++enum {
++	ETHTOOL_A_PSE_NTF_HEADER = 1,
++	ETHTOOL_A_PSE_NTF_EVENTS,
++
++	__ETHTOOL_A_PSE_NTF_CNT,
++	ETHTOOL_A_PSE_NTF_MAX = (__ETHTOOL_A_PSE_NTF_CNT - 1)
++};
++
++/* PSE power domain ID attribute - value 14 in the ETHTOOL_A_PSE_* enum */
++#define ETHTOOL_A_PSE_PW_D_ID 14
++
++#endif /* _UAPI_LINUX_ETHTOOL_NETLINK_GENERATED_H */
diff --git a/target/linux/realtek/patches-6.12/700-dsa-mdio-increase-max-ports-for-rtl839x-rtl931x.patch b/target/linux/realtek/patches-6.12/700-dsa-mdio-increase-max-ports-for-rtl839x-rtl931x.patch
index 3431715c63..ca95d3c6d3 100644
--- a/target/linux/realtek/patches-6.12/700-dsa-mdio-increase-max-ports-for-rtl839x-rtl931x.patch
+++ b/target/linux/realtek/patches-6.12/700-dsa-mdio-increase-max-ports-for-rtl839x-rtl931x.patch
@@ -24,7 +24,7 @@ Signed-off-by: Markus Stockhausen <markus.stockhausen at gmx.de>
 
 --- a/drivers/net/mdio/fwnode_mdio.c
 +++ b/drivers/net/mdio/fwnode_mdio.c
-@@ -89,7 +89,7 @@ int fwnode_mdiobus_phy_device_register(s
+@@ -90,7 +90,7 @@ int fwnode_mdiobus_phy_device_register(s
  	}
  
  	if (fwnode_property_read_bool(child, "broken-turn-around"))




More information about the lede-commits mailing list