]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
kernel: net: pse-pd: backport PSE v6.13-v6.19 21810/head
authorCarlo Szelinsky <github@szelinsky.de>
Sat, 31 Jan 2026 12:19:00 +0000 (13:19 +0100)
committerHauke Mehrtens <hauke@hauke-m.de>
Sat, 7 Feb 2026 23:19:43 +0000 (00:19 +0100)
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@szelinsky.de>
Link: https://github.com/openwrt/openwrt/pull/21810
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
30 files changed:
target/linux/generic/backport-6.12/626-01-v6.13-net-pse-pd-Add-power-limit-check.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-02-v6.13-net-pse-pd-tps23881-Simplify-function-returns.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-03-v6.13-net-pse-pd-tps23881-Use-helpers-to-calculate-bit-offset.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-04-v6.13-net-pse-pd-tps23881-Add-missing-configuration-register.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-05-v6.13-net-pse-pd-Split-ethtool_get_status-into-multiple-callbacks.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-06-v6.13-net-pse-pd-Remove-is_enabled-callback-from-drivers.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-07-v6.13-net-pse-pd-tps23881-Add-power-limit-and-measurement-features.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-08-v6.13-net-pse-pd-Fix-missing-PI-of_node-description.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-09-v6.13-net-pse-pd-Clean-ethtool-header-of-PSE-structures.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-10-v6.17-net-pse-pd-Introduce-attached_phydev-to-pse-control.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-11-v6.17-net-pse-pd-Add-support-for-reporting-events.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-12-v6.17-net-pse-pd-tps23881-Add-support-for-PSE-events.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-13-v6.17-net-pse-pd-Add-support-for-PSE-power-domains.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-14-v6.17-net-pse-pd-ethtool-Add-support-for-power-domains-index.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-15-v6.17-net-pse-pd-Add-helper-to-report-hw-enable-status.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-16-v6.17-net-pse-pd-Add-support-for-budget-evaluation-strategies.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-17-v6.17-net-pse-pd-pd692x0-Add-PSE-PI-priority-feature.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-18-v6.17-net-pse-pd-pd692x0-Add-controller-and-manager-power.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-19-v6.17-net-pse-pd-tps23881-Add-static-port-priority-feature.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-20-v6.17-net-pse-pd-pd692x0-reduce-stack-usage.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-21-v6.18-net-pse-pd-pd692x0-Fix-power-budget-leak.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-22-v6.18-net-pse-pd-pd692x0-Skip-power-budget-when-undefined.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-23-v6.18-net-pse-pd-Add-Si3474-PSE-controller-driver.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-24-v6.19-net-pse-pd-tps23881-Fix-current-measurement-scaling.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-25-v6.19-net-pse-pd-pd692x0-Replace-__free-macro.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-26-v6.19-net-pse-pd-pd692x0-Separate-configuration-parsing.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-27-v6.19-net-pse-pd-pd692x0-Preserve-PSE-configuration.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/626-28-v6.19-net-pse-pd-tps23881-Add-support-for-TPS23881B.patch [new file with mode: 0644]
target/linux/generic/backport-6.12/627-01-v6.17-net-pse-pd-Add-ethtool_netlink_generated-header.patch [new file with mode: 0644]
target/linux/realtek/patches-6.12/700-dsa-mdio-increase-max-ports-for-rtl839x-rtl931x.patch

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 (file)
index 0000000..fbcc3ec
--- /dev/null
@@ -0,0 +1,41 @@
+From 6e56a6d47a7fad705a1a1d088237b0858c01a770 Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent@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@pengutronix.de>
+Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..fc6d532
--- /dev/null
@@ -0,0 +1,112 @@
+From 0b567519d1152de52b29b2da2c47aa0f39a46266 Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent@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@lunn.ch>
+Acked-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Reviewed-by: Kyle Swenson <kyle.swenson@est.tech>
+Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..3b390ea
--- /dev/null
@@ -0,0 +1,190 @@
+From 4c2bab507eb7edc8e497e91b9b7f05d76d7e32bb Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent@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@pengutronix.de>
+Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..c00dd90
--- /dev/null
@@ -0,0 +1,63 @@
+From f3cb3c7bea0c08e821d8e9dfd2f96acd1db7c24e Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent@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@pengutronix.de>
+Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..71a2379
--- /dev/null
@@ -0,0 +1,714 @@
+From 3e9dbfec499807767d03592ebdf19d9c15fd495b Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent@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@bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..dd7a20e
--- /dev/null
@@ -0,0 +1,184 @@
+From 4640a1f0d8f2246f34d6e74330d7e7d2cf75605b Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent@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@pengutronix.de>
+Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..de3cc76
--- /dev/null
@@ -0,0 +1,337 @@
+From 7f076ce3f17334964590c2cce49a02c0851c099a Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent@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@pengutronix.de>
+Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..3aa46cd
--- /dev/null
@@ -0,0 +1,28 @@
+From 10276f3e1c7e7f5de9f0bba58f8a849cb195253d Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent@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@pengutronix.de>
+Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..7d33812
--- /dev/null
@@ -0,0 +1,92 @@
+From 5385f1e1923ca8131eb143567d509b101a344e06 Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent@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@bootlin.com>
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..b00c0e1
--- /dev/null
@@ -0,0 +1,171 @@
+From fa2f0454174c2f33005f5a6e6f70c7160a15b2a1 Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@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@bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-1-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..ec99fca
--- /dev/null
@@ -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@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@bootlin.com>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-2-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..8afcb3f
--- /dev/null
@@ -0,0 +1,252 @@
+From f5e7aecaa4efcd4c85477b6a62f94fea668031db Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@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@bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-3-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..a02bbbc
--- /dev/null
@@ -0,0 +1,218 @@
+From 50f8b341d26826aa5fdccb8f497fbff2500934b3 Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@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@bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-4-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..1124882
--- /dev/null
@@ -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@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@bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-5-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..ed51289
--- /dev/null
@@ -0,0 +1,74 @@
+From c394e757dedd9cf947f9ac470d615d28fd2b07d1 Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@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@bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-6-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..c95d8f0
--- /dev/null
@@ -0,0 +1,1104 @@
+From ffef61d6d27374542f1bce4452200d9bdd2e1edd Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@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@bootlin.com>
+Acked-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-7-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..6e1f2d6
--- /dev/null
@@ -0,0 +1,326 @@
+From 359754013e6a7fc81af6735ebbfedd4a01999f68 Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@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@bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-9-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..a93dca0
--- /dev/null
@@ -0,0 +1,71 @@
+From 24a4e3a05dd0eadd0c9585c411880e5dcb6be97f Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@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@bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-10-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..739233a
--- /dev/null
@@ -0,0 +1,392 @@
+From 56cfc97635e9164395c9242f72746454347155ab Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@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@bootlin.com>
+Reviewed-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-12-78a1a645e2ee@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..d5e1aaf
--- /dev/null
@@ -0,0 +1,55 @@
+From d12b3dc106090b358fb67b7c0c717a0884327ddf Mon Sep 17 00:00:00 2001
+From: Arnd Bergmann <arnd@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@arndb.de>
+Reviewed-by: Kory Maincent <kory.maincent@bootlin.com>
+Link: https://patch.msgid.link/20250709153210.1920125-1-arnd@kernel.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..af23745
--- /dev/null
@@ -0,0 +1,121 @@
+From 1c67f9c54cdc70627e3f6472b89cd3d895df974c Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent@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@bootlin.com>
+Link: https://patch.msgid.link/20250820132708.837255-1-kory.maincent@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..98da7b5
--- /dev/null
@@ -0,0 +1,37 @@
+From 7ef353879f714602b43f98662069f4fb86536761 Mon Sep 17 00:00:00 2001
+From: Kory Maincent <kory.maincent@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@bootlin.com>
+Acked-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Link: https://patch.msgid.link/20250820133321.841054-1-kory.maincent@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..2f4087f
--- /dev/null
@@ -0,0 +1,636 @@
+From a2317231df4b22e6634fe3d8645e7cef848acf49 Mon Sep 17 00:00:00 2001
+From: Piotr Kubik <piotr.kubik@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@adtran.com>
+Reviewed-by: Kory Maincent <kory.maincent@bootlin.com>
+Link: https://patch.msgid.link/9b72c8cd-c8d3-4053-9c80-671b9481d166@adtran.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Carlo Szelinsky <github@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@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 (file)
index 0000000..5150da3
--- /dev/null
@@ -0,0 +1,31 @@
+From 2c95a756e0cfc19af6d0b32b0c6cf3bada334998 Mon Sep 17 00:00:00 2001
+From: Thomas Wismer <thomas.wismer@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@scs.ch>
+Acked-by: Kory Maincent <kory.maincent@bootlin.com>
+Link: https://patch.msgid.link/20251006204029.7169-2-thomas@wismer.xyz
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..fafa5af
--- /dev/null
@@ -0,0 +1,57 @@
+From f197902cd21ae833850679b216bb62c0d056bbb3 Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@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@bootlin.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/20251013-feature_pd692x0_reboot_keep_conf-v2-1-68ab082a93dd@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..e88bbd0
--- /dev/null
@@ -0,0 +1,291 @@
+From 6fa1f8b64a47edd7d8420d8fd1008507aee2853e Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@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@bootlin.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/20251013-feature_pd692x0_reboot_keep_conf-v2-2-68ab082a93dd@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..4ae835e
--- /dev/null
@@ -0,0 +1,111 @@
+From 8f3d044b34fe99b894046edb84605456195cabc0 Mon Sep 17 00:00:00 2001
+From: "Kory Maincent (Dent Project)" <kory.maincent@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@bootlin.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Link: https://patch.msgid.link/20251013-feature_pd692x0_reboot_keep_conf-v2-3-68ab082a93dd@bootlin.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..2c188db
--- /dev/null
@@ -0,0 +1,170 @@
+From 4d07797faaa19aa8e80e10a04ca1a72c643ef5cf Mon Sep 17 00:00:00 2001
+From: Thomas Wismer <thomas.wismer@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@scs.ch>
+Reviewed-by: Kory Maincent <kory.maincent@bootlin.com>
+Acked-by: Oleksij Rempel <o.rempel@pengutronix.de>
+Link: https://patch.msgid.link/20251029212312.108749-2-thomas@wismer.xyz
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Carlo Szelinsky <github@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 (file)
index 0000000..863bd2e
--- /dev/null
@@ -0,0 +1,80 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: OpenWrt PSE-PD Backport <openwrt@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@openwrt.org>
+Signed-off-by: Carlo Szelinsky <github@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 */
index 3431715c632afea8a9aa4ffff9b8b3822b101512..ca95d3c6d33cbe444b5c3678433e6275c58fccfb 100644 (file)
@@ -24,7 +24,7 @@ Signed-off-by: Markus Stockhausen <markus.stockhausen@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"))