From: Carlo Szelinsky Date: Sat, 31 Jan 2026 12:19:00 +0000 (+0100) Subject: kernel: net: pse-pd: backport PSE v6.13-v6.19 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=528c9259a70f3a94595cd860b97913fbe913ed39;p=thirdparty%2Fopenwrt.git kernel: net: pse-pd: backport PSE v6.13-v6.19 Backport the PSE-PD (Power Sourcing Equipment - Powered Device) framework updates from Linux 6.13 through 6.19. This brings modern PoE (Power over Ethernet) controller support to OpenWrt, enabling userspace control of PSE hardware via ethtool. Key features: - Enhanced ethtool integration for PSE status and configuration - Power domain support with budget evaluation strategies - PSE event reporting via netlink - Port priority management for power budget allocation - New Si3474 PSE controller driver Backported commits: v6.13 core framework and TPS23881 improvements: - 6e56a6d47a7f net: pse-pd: Add power limit check - 0b567519d115 net: pse-pd: tps23881: Simplify function returns - 4c2bab507eb7 net: pse-pd: tps23881: Use helpers to calculate bit offset - f3cb3c7bea0c net: pse-pd: tps23881: Add missing configuration register - 3e9dbfec4998 net: pse-pd: Split ethtool_get_status into multiple callbacks - 4640a1f0d8f2 net: pse-pd: Remove is_enabled callback from drivers - 7f076ce3f173 net: pse-pd: tps23881: Add power limit and measurement features - 10276f3e1c7e net: pse-pd: Fix missing PI of_node description - 5385f1e1923c net: pse-pd: Clean ethtool header of PSE structures v6.17 power domains and event support: - fa2f0454174c net: pse-pd: Introduce attached_phydev to pse control - fc0e6db30941 net: pse-pd: Add support for reporting events - f5e7aecaa4ef net: pse-pd: tps23881: Add support for PSE events - 50f8b341d268 net: pse-pd: Add support for PSE power domains - 1176978ed851 net: ethtool: Add support for power domains index - c394e757dedd net: pse-pd: Add helper to report hw enable status - ffef61d6d273 net: pse-pd: Add support for budget evaluation strategies - 359754013e6a net: pse-pd: pd692x0: Add PSE PI priority feature - 24a4e3a05dd0 net: pse-pd: pd692x0: Add controller and manager power - 56cfc97635e9 net: pse-pd: tps23881: Add static port priority feature - d12b3dc10609 net: pse-pd: pd692x0: reduce stack usage v6.18 Si3474 driver and fixes: - 1c67f9c54cdc net: pse-pd: pd692x0: Fix power budget leak - 7ef353879f71 net: pse-pd: pd692x0: Skip power budget when undefined - a2317231df4b net: pse-pd: Add Si3474 PSE controller driver v6.19 maintenance and TPS23881B support: - 2c95a756e0cf net: pse-pd: tps23881: Fix current measurement scaling - f197902cd21a net: pse-pd: pd692x0: Replace __free macro - 6fa1f8b64a47 net: pse-pd: pd692x0: Separate configuration parsing - 8f3d044b34fe net: pse-pd: pd692x0: Preserve PSE configuration - 4d07797faaa1 net: pse-pd: tps23881: Add support for TPS23881B Signed-off-by: Carlo Szelinsky Link: https://github.com/openwrt/openwrt/pull/21810 Signed-off-by: Hauke Mehrtens --- diff --git a/target/linux/generic/backport-6.12/626-01-v6.13-net-pse-pd-Add-power-limit-check.patch b/target/linux/generic/backport-6.12/626-01-v6.13-net-pse-pd-Add-power-limit-check.patch new file mode 100644 index 00000000000..fbcc3ec3e1d --- /dev/null +++ b/target/linux/generic/backport-6.12/626-01-v6.13-net-pse-pd-Add-power-limit-check.patch @@ -0,0 +1,41 @@ +From 6e56a6d47a7fad705a1a1d088237b0858c01a770 Mon Sep 17 00:00:00 2001 +From: Kory Maincent +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 +Signed-off-by: Kory Maincent +Signed-off-by: Paolo Abeni +Signed-off-by: Carlo Szelinsky +--- + drivers/net/pse-pd/pse_core.c | 3 +++ + include/linux/pse-pd/pse.h | 2 ++ + 2 files changed, 5 insertions(+) +--- a/drivers/net/pse-pd/pse_core.c ++++ b/drivers/net/pse-pd/pse_core.c +@@ -868,6 +868,9 @@ int pse_ethtool_set_pw_limit(struct pse_ + int uV, uA, ret; + s64 tmp_64; + ++ if (pw_limit > MAX_PI_PW) ++ return -ERANGE; ++ + ret = regulator_get_voltage(psec->ps); + if (!ret) { + NL_SET_ERR_MSG(extack, +--- a/include/linux/pse-pd/pse.h ++++ b/include/linux/pse-pd/pse.h +@@ -11,6 +11,8 @@ + + /* Maximum current in uA according to IEEE 802.3-2022 Table 145-1 */ + #define MAX_PI_CURRENT 1920000 ++/* Maximum power in mW according to IEEE 802.3-2022 Table 145-16 */ ++#define MAX_PI_PW 99900 + + struct phy_device; + struct pse_controller_dev; diff --git a/target/linux/generic/backport-6.12/626-02-v6.13-net-pse-pd-tps23881-Simplify-function-returns.patch b/target/linux/generic/backport-6.12/626-02-v6.13-net-pse-pd-tps23881-Simplify-function-returns.patch new file mode 100644 index 00000000000..fc6d5322981 --- /dev/null +++ b/target/linux/generic/backport-6.12/626-02-v6.13-net-pse-pd-tps23881-Simplify-function-returns.patch @@ -0,0 +1,112 @@ +From 0b567519d1152de52b29b2da2c47aa0f39a46266 Mon Sep 17 00:00:00 2001 +From: Kory Maincent +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 +Acked-by: Oleksij Rempel +Reviewed-by: Kyle Swenson +Signed-off-by: Kory Maincent +Signed-off-by: Paolo Abeni +Signed-off-by: Carlo Szelinsky +--- + drivers/net/pse-pd/tps23881.c | 34 ++++++---------------------------- + 1 file changed, 6 insertions(+), 28 deletions(-) +--- a/drivers/net/pse-pd/tps23881.c ++++ b/drivers/net/pse-pd/tps23881.c +@@ -59,7 +59,6 @@ static int tps23881_pi_enable(struct pse + struct i2c_client *client = priv->client; + u8 chan; + u16 val; +- int ret; + + if (id >= TPS23881_MAX_CHANS) + return -ERANGE; +@@ -78,11 +77,7 @@ static int tps23881_pi_enable(struct pse + val |= BIT(chan + 4); + } + +- ret = i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val); +- if (ret) +- return ret; +- +- return 0; ++ return i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val); + } + + static int tps23881_pi_disable(struct pse_controller_dev *pcdev, int id) +@@ -91,7 +86,6 @@ static int tps23881_pi_disable(struct ps + struct i2c_client *client = priv->client; + u8 chan; + u16 val; +- int ret; + + if (id >= TPS23881_MAX_CHANS) + return -ERANGE; +@@ -110,11 +104,7 @@ static int tps23881_pi_disable(struct ps + val |= BIT(chan + 8); + } + +- ret = i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val); +- if (ret) +- return ret; +- +- return 0; ++ return i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val); + } + + static int tps23881_pi_is_enabled(struct pse_controller_dev *pcdev, int id) +@@ -480,7 +470,7 @@ tps23881_write_port_matrix(struct tps238 + struct i2c_client *client = priv->client; + u8 pi_id, lgcl_chan, hw_chan; + u16 val = 0; +- int i, ret; ++ int i; + + for (i = 0; i < port_cnt; i++) { + pi_id = port_matrix[i].pi_id; +@@ -511,11 +501,7 @@ tps23881_write_port_matrix(struct tps238 + } + + /* Write hardware ports matrix */ +- ret = i2c_smbus_write_word_data(client, TPS23881_REG_PORT_MAP, val); +- if (ret) +- return ret; +- +- return 0; ++ return i2c_smbus_write_word_data(client, TPS23881_REG_PORT_MAP, val); + } + + static int +@@ -564,11 +550,7 @@ tps23881_set_ports_conf(struct tps23881_ + val |= BIT(port_matrix[i].lgcl_chan[1]) | + BIT(port_matrix[i].lgcl_chan[1] + 4); + } +- ret = i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val); +- if (ret) +- return ret; +- +- return 0; ++ return i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val); + } + + static int +@@ -594,11 +576,7 @@ tps23881_set_ports_matrix(struct tps2388 + if (ret) + return ret; + +- ret = tps23881_set_ports_conf(priv, port_matrix); +- if (ret) +- return ret; +- +- return 0; ++ return tps23881_set_ports_conf(priv, port_matrix); + } + + static int tps23881_setup_pi_matrix(struct pse_controller_dev *pcdev) diff --git a/target/linux/generic/backport-6.12/626-03-v6.13-net-pse-pd-tps23881-Use-helpers-to-calculate-bit-offset.patch b/target/linux/generic/backport-6.12/626-03-v6.13-net-pse-pd-tps23881-Use-helpers-to-calculate-bit-offset.patch new file mode 100644 index 00000000000..3b390ea5651 --- /dev/null +++ b/target/linux/generic/backport-6.12/626-03-v6.13-net-pse-pd-tps23881-Use-helpers-to-calculate-bit-offset.patch @@ -0,0 +1,190 @@ +From 4c2bab507eb7edc8e497e91b9b7f05d76d7e32bb Mon Sep 17 00:00:00 2001 +From: Kory Maincent +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 +Signed-off-by: Kory Maincent +Signed-off-by: Paolo Abeni +Signed-off-by: Carlo Szelinsky +--- + drivers/net/pse-pd/tps23881.c | 107 ++++++++++++++++++++++------------ + 1 file changed, 69 insertions(+), 38 deletions(-) +--- a/drivers/net/pse-pd/tps23881.c ++++ b/drivers/net/pse-pd/tps23881.c +@@ -53,6 +53,55 @@ static struct tps23881_priv *to_tps23881 + return container_of(pcdev, struct tps23881_priv, pcdev); + } + ++/* ++ * Helper to extract a value from a u16 register value, which is made of two ++ * u8 registers. The function calculates the bit offset based on the channel ++ * and extracts the relevant bits using a provided field mask. ++ * ++ * @param reg_val: The u16 register value (composed of two u8 registers). ++ * @param chan: The channel number (0-7). ++ * @param field_offset: The base bit offset to apply (e.g., 0 or 4). ++ * @param field_mask: The mask to apply to extract the required bits. ++ * @return: The extracted value for the specific channel. ++ */ ++static u16 tps23881_calc_val(u16 reg_val, u8 chan, u8 field_offset, ++ u16 field_mask) ++{ ++ if (chan >= 4) ++ reg_val >>= 8; ++ ++ return (reg_val >> field_offset) & field_mask; ++} ++ ++/* ++ * Helper to combine individual channel values into a u16 register value. ++ * The function sets the value for a specific channel in the appropriate ++ * position. ++ * ++ * @param reg_val: The current u16 register value. ++ * @param chan: The channel number (0-7). ++ * @param field_offset: The base bit offset to apply (e.g., 0 or 4). ++ * @param field_mask: The mask to apply for the field (e.g., 0x0F). ++ * @param field_val: The value to set for the specific channel (masked by ++ * field_mask). ++ * @return: The updated u16 register value with the channel value set. ++ */ ++static u16 tps23881_set_val(u16 reg_val, u8 chan, u8 field_offset, ++ u16 field_mask, u16 field_val) ++{ ++ field_val &= field_mask; ++ ++ if (chan < 4) { ++ reg_val &= ~(field_mask << field_offset); ++ reg_val |= (field_val << field_offset); ++ } else { ++ reg_val &= ~(field_mask << (field_offset + 8)); ++ reg_val |= (field_val << (field_offset + 8)); ++ } ++ ++ return reg_val; ++} ++ + static int tps23881_pi_enable(struct pse_controller_dev *pcdev, int id) + { + struct tps23881_priv *priv = to_tps23881_priv(pcdev); +@@ -64,17 +113,12 @@ static int tps23881_pi_enable(struct pse + return -ERANGE; + + chan = priv->port[id].chan[0]; +- if (chan < 4) +- val = BIT(chan); +- else +- val = BIT(chan + 4); ++ val = tps23881_set_val(0, chan, 0, BIT(chan % 4), BIT(chan % 4)); + + if (priv->port[id].is_4p) { + chan = priv->port[id].chan[1]; +- if (chan < 4) +- val |= BIT(chan); +- else +- val |= BIT(chan + 4); ++ val = tps23881_set_val(val, chan, 0, BIT(chan % 4), ++ BIT(chan % 4)); + } + + return i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val); +@@ -91,17 +135,12 @@ static int tps23881_pi_disable(struct ps + return -ERANGE; + + chan = priv->port[id].chan[0]; +- if (chan < 4) +- val = BIT(chan + 4); +- else +- val = BIT(chan + 8); ++ val = tps23881_set_val(0, chan, 4, BIT(chan % 4), BIT(chan % 4)); + + if (priv->port[id].is_4p) { + chan = priv->port[id].chan[1]; +- if (chan < 4) +- val |= BIT(chan + 4); +- else +- val |= BIT(chan + 8); ++ val = tps23881_set_val(val, chan, 4, BIT(chan % 4), ++ BIT(chan % 4)); + } + + return i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val); +@@ -113,6 +152,7 @@ static int tps23881_pi_is_enabled(struct + struct i2c_client *client = priv->client; + bool enabled; + u8 chan; ++ u16 val; + int ret; + + ret = i2c_smbus_read_word_data(client, TPS23881_REG_PW_STATUS); +@@ -120,17 +160,13 @@ static int tps23881_pi_is_enabled(struct + return ret; + + chan = priv->port[id].chan[0]; +- if (chan < 4) +- enabled = ret & BIT(chan); +- else +- enabled = ret & BIT(chan + 4); ++ val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4)); ++ enabled = !!(val); + + if (priv->port[id].is_4p) { + chan = priv->port[id].chan[1]; +- if (chan < 4) +- enabled &= !!(ret & BIT(chan)); +- else +- enabled &= !!(ret & BIT(chan + 4)); ++ val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4)); ++ enabled &= !!(val); + } + + /* Return enabled status only if both channel are on this state */ +@@ -146,6 +182,7 @@ static int tps23881_ethtool_get_status(s + struct i2c_client *client = priv->client; + bool enabled, delivering; + u8 chan; ++ u16 val; + int ret; + + ret = i2c_smbus_read_word_data(client, TPS23881_REG_PW_STATUS); +@@ -153,23 +190,17 @@ static int tps23881_ethtool_get_status(s + return ret; + + chan = priv->port[id].chan[0]; +- if (chan < 4) { +- enabled = ret & BIT(chan); +- delivering = ret & BIT(chan + 4); +- } else { +- enabled = ret & BIT(chan + 4); +- delivering = ret & BIT(chan + 8); +- } ++ val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4)); ++ enabled = !!(val); ++ val = tps23881_calc_val(ret, chan, 4, BIT(chan % 4)); ++ delivering = !!(val); + + if (priv->port[id].is_4p) { + chan = priv->port[id].chan[1]; +- if (chan < 4) { +- enabled &= !!(ret & BIT(chan)); +- delivering &= !!(ret & BIT(chan + 4)); +- } else { +- enabled &= !!(ret & BIT(chan + 4)); +- delivering &= !!(ret & BIT(chan + 8)); +- } ++ val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4)); ++ enabled &= !!(val); ++ val = tps23881_calc_val(ret, chan, 4, BIT(chan % 4)); ++ delivering &= !!(val); + } + + /* Return delivering status only if both channel are on this state */ diff --git a/target/linux/generic/backport-6.12/626-04-v6.13-net-pse-pd-tps23881-Add-missing-configuration-register.patch b/target/linux/generic/backport-6.12/626-04-v6.13-net-pse-pd-tps23881-Add-missing-configuration-register.patch new file mode 100644 index 00000000000..c00dd9050ff --- /dev/null +++ b/target/linux/generic/backport-6.12/626-04-v6.13-net-pse-pd-tps23881-Add-missing-configuration-register.patch @@ -0,0 +1,63 @@ +From f3cb3c7bea0c08e821d8e9dfd2f96acd1db7c24e Mon Sep 17 00:00:00 2001 +From: Kory Maincent +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 +Signed-off-by: Kory Maincent +Signed-off-by: Paolo Abeni +Signed-off-by: Carlo Szelinsky +--- + drivers/net/pse-pd/tps23881.c | 30 +++++++++++++++++++++++++++++- + 1 file changed, 29 insertions(+), 1 deletion(-) +--- a/drivers/net/pse-pd/tps23881.c ++++ b/drivers/net/pse-pd/tps23881.c +@@ -130,6 +130,7 @@ static int tps23881_pi_disable(struct ps + struct i2c_client *client = priv->client; + u8 chan; + u16 val; ++ int ret; + + if (id >= TPS23881_MAX_CHANS) + return -ERANGE; +@@ -143,7 +144,34 @@ static int tps23881_pi_disable(struct ps + BIT(chan % 4)); + } + +- return i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val); ++ ret = i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val); ++ if (ret) ++ return ret; ++ ++ /* PWOFF command resets lots of register which need to be ++ * configured again. According to the datasheet "It may take upwards ++ * of 5ms after PWOFFn command for all register values to be updated" ++ */ ++ mdelay(5); ++ ++ /* Enable detection and classification */ ++ ret = i2c_smbus_read_word_data(client, TPS23881_REG_DET_CLA_EN); ++ if (ret < 0) ++ return ret; ++ ++ chan = priv->port[id].chan[0]; ++ val = tps23881_set_val(ret, chan, 0, BIT(chan % 4), BIT(chan % 4)); ++ val = tps23881_set_val(val, chan, 4, BIT(chan % 4), BIT(chan % 4)); ++ ++ if (priv->port[id].is_4p) { ++ chan = priv->port[id].chan[1]; ++ val = tps23881_set_val(ret, chan, 0, BIT(chan % 4), ++ BIT(chan % 4)); ++ val = tps23881_set_val(val, chan, 4, BIT(chan % 4), ++ BIT(chan % 4)); ++ } ++ ++ return i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val); + } + + static int tps23881_pi_is_enabled(struct pse_controller_dev *pcdev, int id) diff --git a/target/linux/generic/backport-6.12/626-05-v6.13-net-pse-pd-Split-ethtool_get_status-into-multiple-callbacks.patch b/target/linux/generic/backport-6.12/626-05-v6.13-net-pse-pd-Split-ethtool_get_status-into-multiple-callbacks.patch new file mode 100644 index 00000000000..71a2379e3b4 --- /dev/null +++ b/target/linux/generic/backport-6.12/626-05-v6.13-net-pse-pd-Split-ethtool_get_status-into-multiple-callbacks.patch @@ -0,0 +1,714 @@ +From 3e9dbfec499807767d03592ebdf19d9c15fd495b Mon Sep 17 00:00:00 2001 +From: Kory Maincent +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 +Signed-off-by: Paolo Abeni +Signed-off-by: Carlo Szelinsky +--- + drivers/net/pse-pd/pd692x0.c | 153 ++++++++++++++++++++--------- + drivers/net/pse-pd/pse_core.c | 83 ++++++++++++++-- + drivers/net/pse-pd/pse_regulator.c | 26 +++-- + drivers/net/pse-pd/tps23881.c | 60 ++++++++--- + include/linux/pse-pd/pse.h | 87 +++++++++++++--- + net/ethtool/pse-pd.c | 8 +- + 6 files changed, 323 insertions(+), 94 deletions(-) + +--- a/drivers/net/pse-pd/pd692x0.c ++++ b/drivers/net/pse-pd/pd692x0.c +@@ -517,21 +517,38 @@ pd692x0_pse_ext_state_map[] = { + { /* sentinel */ } + }; + +-static void +-pd692x0_get_ext_state(struct ethtool_c33_pse_ext_state_info *c33_ext_state_info, +- u32 status_code) ++static int ++pd692x0_pi_get_ext_state(struct pse_controller_dev *pcdev, int id, ++ struct pse_ext_state_info *ext_state_info) + { ++ struct ethtool_c33_pse_ext_state_info *c33_ext_state_info; + const struct pd692x0_pse_ext_state_mapping *ext_state_map; ++ struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); ++ struct pd692x0_msg msg, buf = {0}; ++ int ret; ++ ++ ret = pd692x0_fw_unavailable(priv); ++ if (ret) ++ return ret; + ++ msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_STATUS]; ++ msg.sub[2] = id; ++ ret = pd692x0_sendrecv_msg(priv, &msg, &buf); ++ if (ret < 0) ++ return ret; ++ ++ c33_ext_state_info = &ext_state_info->c33_ext_state_info; + ext_state_map = pd692x0_pse_ext_state_map; + while (ext_state_map->status_code) { +- if (ext_state_map->status_code == status_code) { ++ if (ext_state_map->status_code == buf.sub[0]) { + c33_ext_state_info->c33_pse_ext_state = ext_state_map->pse_ext_state; + c33_ext_state_info->__c33_pse_ext_substate = ext_state_map->pse_ext_substate; +- return; ++ return 0; + } + ext_state_map++; + } ++ ++ return 0; + } + + struct pd692x0_class_pw { +@@ -613,35 +630,36 @@ static int pd692x0_pi_set_pw_from_table( + } + + static int +-pd692x0_pi_get_pw_ranges(struct pse_control_status *st) ++pd692x0_pi_get_pw_limit_ranges(struct pse_controller_dev *pcdev, int id, ++ struct pse_pw_limit_ranges *pw_limit_ranges) + { ++ struct ethtool_c33_pse_pw_limit_range *c33_pw_limit_ranges; + const struct pd692x0_class_pw *pw_table; + int i; + + pw_table = pd692x0_class_pw_table; +- st->c33_pw_limit_ranges = kcalloc(PD692X0_CLASS_PW_TABLE_SIZE, +- sizeof(struct ethtool_c33_pse_pw_limit_range), +- GFP_KERNEL); +- if (!st->c33_pw_limit_ranges) ++ c33_pw_limit_ranges = kcalloc(PD692X0_CLASS_PW_TABLE_SIZE, ++ sizeof(*c33_pw_limit_ranges), ++ GFP_KERNEL); ++ if (!c33_pw_limit_ranges) + return -ENOMEM; + + for (i = 0; i < PD692X0_CLASS_PW_TABLE_SIZE; i++, pw_table++) { +- st->c33_pw_limit_ranges[i].min = pw_table->class_pw; +- st->c33_pw_limit_ranges[i].max = pw_table->class_pw + pw_table->max_added_class_pw; ++ c33_pw_limit_ranges[i].min = pw_table->class_pw; ++ c33_pw_limit_ranges[i].max = pw_table->class_pw + ++ pw_table->max_added_class_pw; + } + +- st->c33_pw_limit_nb_ranges = i; +- return 0; ++ pw_limit_ranges->c33_pw_limit_ranges = c33_pw_limit_ranges; ++ return i; + } + +-static int pd692x0_ethtool_get_status(struct pse_controller_dev *pcdev, +- unsigned long id, +- struct netlink_ext_ack *extack, +- struct pse_control_status *status) ++static int ++pd692x0_pi_get_admin_state(struct pse_controller_dev *pcdev, int id, ++ struct pse_admin_state *admin_state) + { + struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); + struct pd692x0_msg msg, buf = {0}; +- u32 class; + int ret; + + ret = pd692x0_fw_unavailable(priv); +@@ -654,39 +672,65 @@ static int pd692x0_ethtool_get_status(st + if (ret < 0) + return ret; + +- /* Compare Port Status (Communication Protocol Document par. 7.1) */ +- if ((buf.sub[0] & 0xf0) == 0x80 || (buf.sub[0] & 0xf0) == 0x90) +- status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING; +- else if (buf.sub[0] == 0x1b || buf.sub[0] == 0x22) +- status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_SEARCHING; +- else if (buf.sub[0] == 0x12) +- status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_FAULT; +- else +- status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED; +- + if (buf.sub[1]) +- status->c33_admin_state = ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED; ++ admin_state->c33_admin_state = ++ ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED; + else +- status->c33_admin_state = ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED; ++ admin_state->c33_admin_state = ++ ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED; + +- priv->admin_state[id] = status->c33_admin_state; ++ priv->admin_state[id] = admin_state->c33_admin_state; + +- pd692x0_get_ext_state(&status->c33_ext_state_info, buf.sub[0]); +- status->c33_actual_pw = (buf.data[0] << 4 | buf.data[1]) * 100; ++ return 0; ++} + +- msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_PARAM]; ++static int ++pd692x0_pi_get_pw_status(struct pse_controller_dev *pcdev, int id, ++ struct pse_pw_status *pw_status) ++{ ++ struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); ++ struct pd692x0_msg msg, buf = {0}; ++ int ret; ++ ++ ret = pd692x0_fw_unavailable(priv); ++ if (ret) ++ return ret; ++ ++ msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_STATUS]; + msg.sub[2] = id; +- memset(&buf, 0, sizeof(buf)); + ret = pd692x0_sendrecv_msg(priv, &msg, &buf); + if (ret < 0) + return ret; + +- ret = pd692x0_pi_get_pw_from_table(buf.data[0], buf.data[1]); +- if (ret < 0) ++ /* Compare Port Status (Communication Protocol Document par. 7.1) */ ++ if ((buf.sub[0] & 0xf0) == 0x80 || (buf.sub[0] & 0xf0) == 0x90) ++ pw_status->c33_pw_status = ++ ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING; ++ else if (buf.sub[0] == 0x1b || buf.sub[0] == 0x22) ++ pw_status->c33_pw_status = ++ ETHTOOL_C33_PSE_PW_D_STATUS_SEARCHING; ++ else if (buf.sub[0] == 0x12) ++ pw_status->c33_pw_status = ++ ETHTOOL_C33_PSE_PW_D_STATUS_FAULT; ++ else ++ pw_status->c33_pw_status = ++ ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED; ++ ++ return 0; ++} ++ ++static int ++pd692x0_pi_get_pw_class(struct pse_controller_dev *pcdev, int id) ++{ ++ struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); ++ struct pd692x0_msg msg, buf = {0}; ++ u32 class; ++ int ret; ++ ++ ret = pd692x0_fw_unavailable(priv); ++ if (ret) + return ret; +- status->c33_avail_pw_limit = ret; + +- memset(&buf, 0, sizeof(buf)); + msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_CLASS]; + msg.sub[2] = id; + ret = pd692x0_sendrecv_msg(priv, &msg, &buf); +@@ -695,13 +739,29 @@ static int pd692x0_ethtool_get_status(st + + class = buf.data[3] >> 4; + if (class <= 8) +- status->c33_pw_class = class; ++ return class; ++ ++ return 0; ++} ++ ++static int ++pd692x0_pi_get_actual_pw(struct pse_controller_dev *pcdev, int id) ++{ ++ struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); ++ struct pd692x0_msg msg, buf = {0}; ++ int ret; ++ ++ ret = pd692x0_fw_unavailable(priv); ++ if (ret) ++ return ret; + +- ret = pd692x0_pi_get_pw_ranges(status); ++ msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_STATUS]; ++ msg.sub[2] = id; ++ ret = pd692x0_sendrecv_msg(priv, &msg, &buf); + if (ret < 0) + return ret; + +- return 0; ++ return (buf.data[0] << 4 | buf.data[1]) * 100; + } + + static struct pd692x0_msg_ver pd692x0_get_sw_version(struct pd692x0_priv *priv) +@@ -1038,13 +1098,18 @@ static int pd692x0_pi_set_pw_limit(struc + + static const struct pse_controller_ops pd692x0_ops = { + .setup_pi_matrix = pd692x0_setup_pi_matrix, +- .ethtool_get_status = pd692x0_ethtool_get_status, ++ .pi_get_admin_state = pd692x0_pi_get_admin_state, ++ .pi_get_pw_status = pd692x0_pi_get_pw_status, ++ .pi_get_ext_state = pd692x0_pi_get_ext_state, ++ .pi_get_pw_class = pd692x0_pi_get_pw_class, ++ .pi_get_actual_pw = pd692x0_pi_get_actual_pw, + .pi_enable = pd692x0_pi_enable, + .pi_disable = pd692x0_pi_disable, + .pi_is_enabled = pd692x0_pi_is_enabled, + .pi_get_voltage = pd692x0_pi_get_voltage, + .pi_get_pw_limit = pd692x0_pi_get_pw_limit, + .pi_set_pw_limit = pd692x0_pi_set_pw_limit, ++ .pi_get_pw_limit_ranges = pd692x0_pi_get_pw_limit_ranges, + }; + + #define PD692X0_FW_LINE_MAX_SZ 0xff +--- a/drivers/net/pse-pd/pse_core.c ++++ b/drivers/net/pse-pd/pse_core.c +@@ -443,6 +443,13 @@ int pse_controller_register(struct pse_c + if (!pcdev->nr_lines) + pcdev->nr_lines = 1; + ++ if (!pcdev->ops->pi_get_admin_state || ++ !pcdev->ops->pi_get_pw_status) { ++ dev_err(pcdev->dev, ++ "Mandatory status report callbacks are missing"); ++ return -EINVAL; ++ } ++ + ret = of_load_pse_pis(pcdev); + if (ret) + return ret; +@@ -745,25 +752,81 @@ EXPORT_SYMBOL_GPL(of_pse_control_get); + */ + int pse_ethtool_get_status(struct pse_control *psec, + struct netlink_ext_ack *extack, +- struct pse_control_status *status) ++ struct ethtool_pse_control_status *status) + { ++ struct pse_admin_state admin_state = {0}; ++ struct pse_pw_status pw_status = {0}; + const struct pse_controller_ops *ops; + struct pse_controller_dev *pcdev; +- int err; ++ int ret; + + pcdev = psec->pcdev; + ops = pcdev->ops; +- if (!ops->ethtool_get_status) { +- NL_SET_ERR_MSG(extack, +- "PSE driver does not support status report"); +- return -EOPNOTSUPP; ++ mutex_lock(&pcdev->lock); ++ ret = ops->pi_get_admin_state(pcdev, psec->id, &admin_state); ++ if (ret) ++ goto out; ++ status->podl_admin_state = admin_state.podl_admin_state; ++ status->c33_admin_state = admin_state.c33_admin_state; ++ ++ ret = ops->pi_get_pw_status(pcdev, psec->id, &pw_status); ++ if (ret) ++ goto out; ++ status->podl_pw_status = pw_status.podl_pw_status; ++ status->c33_pw_status = pw_status.c33_pw_status; ++ ++ if (ops->pi_get_ext_state) { ++ struct pse_ext_state_info ext_state_info = {0}; ++ ++ ret = ops->pi_get_ext_state(pcdev, psec->id, ++ &ext_state_info); ++ if (ret) ++ goto out; ++ ++ memcpy(&status->c33_ext_state_info, ++ &ext_state_info.c33_ext_state_info, ++ sizeof(status->c33_ext_state_info)); + } + +- mutex_lock(&pcdev->lock); +- err = ops->ethtool_get_status(pcdev, psec->id, extack, status); +- mutex_unlock(&pcdev->lock); ++ if (ops->pi_get_pw_class) { ++ ret = ops->pi_get_pw_class(pcdev, psec->id); ++ if (ret < 0) ++ goto out; ++ ++ status->c33_pw_class = ret; ++ } ++ ++ if (ops->pi_get_actual_pw) { ++ ret = ops->pi_get_actual_pw(pcdev, psec->id); ++ if (ret < 0) ++ goto out; ++ ++ status->c33_actual_pw = ret; ++ } ++ ++ if (ops->pi_get_pw_limit) { ++ ret = ops->pi_get_pw_limit(pcdev, psec->id); ++ if (ret < 0) ++ goto out; ++ ++ status->c33_avail_pw_limit = ret; ++ } ++ ++ if (ops->pi_get_pw_limit_ranges) { ++ struct pse_pw_limit_ranges pw_limit_ranges = {0}; + +- return err; ++ ret = ops->pi_get_pw_limit_ranges(pcdev, psec->id, ++ &pw_limit_ranges); ++ if (ret < 0) ++ goto out; ++ ++ status->c33_pw_limit_ranges = ++ pw_limit_ranges.c33_pw_limit_ranges; ++ status->c33_pw_limit_nb_ranges = ret; ++ } ++out: ++ mutex_unlock(&psec->pcdev->lock); ++ return ret; + } + EXPORT_SYMBOL_GPL(pse_ethtool_get_status); + +--- a/drivers/net/pse-pd/pse_regulator.c ++++ b/drivers/net/pse-pd/pse_regulator.c +@@ -60,9 +60,19 @@ pse_reg_pi_is_enabled(struct pse_control + } + + static int +-pse_reg_ethtool_get_status(struct pse_controller_dev *pcdev, unsigned long id, +- struct netlink_ext_ack *extack, +- struct pse_control_status *status) ++pse_reg_pi_get_admin_state(struct pse_controller_dev *pcdev, int id, ++ struct pse_admin_state *admin_state) ++{ ++ struct pse_reg_priv *priv = to_pse_reg(pcdev); ++ ++ admin_state->podl_admin_state = priv->admin_state; ++ ++ return 0; ++} ++ ++static int ++pse_reg_pi_get_pw_status(struct pse_controller_dev *pcdev, int id, ++ struct pse_pw_status *pw_status) + { + struct pse_reg_priv *priv = to_pse_reg(pcdev); + int ret; +@@ -72,18 +82,18 @@ pse_reg_ethtool_get_status(struct pse_co + return ret; + + if (!ret) +- status->podl_pw_status = ETHTOOL_PODL_PSE_PW_D_STATUS_DISABLED; ++ pw_status->podl_pw_status = ++ ETHTOOL_PODL_PSE_PW_D_STATUS_DISABLED; + else +- status->podl_pw_status = ++ pw_status->podl_pw_status = + ETHTOOL_PODL_PSE_PW_D_STATUS_DELIVERING; + +- status->podl_admin_state = priv->admin_state; +- + return 0; + } + + static const struct pse_controller_ops pse_reg_ops = { +- .ethtool_get_status = pse_reg_ethtool_get_status, ++ .pi_get_admin_state = pse_reg_pi_get_admin_state, ++ .pi_get_pw_status = pse_reg_pi_get_pw_status, + .pi_enable = pse_reg_pi_enable, + .pi_is_enabled = pse_reg_pi_is_enabled, + .pi_disable = pse_reg_pi_disable, +--- a/drivers/net/pse-pd/tps23881.c ++++ b/drivers/net/pse-pd/tps23881.c +@@ -201,14 +201,13 @@ static int tps23881_pi_is_enabled(struct + return enabled; + } + +-static int tps23881_ethtool_get_status(struct pse_controller_dev *pcdev, +- unsigned long id, +- struct netlink_ext_ack *extack, +- struct pse_control_status *status) ++static int ++tps23881_pi_get_admin_state(struct pse_controller_dev *pcdev, int id, ++ struct pse_admin_state *admin_state) + { + struct tps23881_priv *priv = to_tps23881_priv(pcdev); + struct i2c_client *client = priv->client; +- bool enabled, delivering; ++ bool enabled; + u8 chan; + u16 val; + int ret; +@@ -220,28 +219,56 @@ static int tps23881_ethtool_get_status(s + chan = priv->port[id].chan[0]; + val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4)); + enabled = !!(val); +- val = tps23881_calc_val(ret, chan, 4, BIT(chan % 4)); +- delivering = !!(val); + + if (priv->port[id].is_4p) { + chan = priv->port[id].chan[1]; + val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4)); + enabled &= !!(val); ++ } ++ ++ /* Return enabled status only if both channel are on this state */ ++ if (enabled) ++ admin_state->c33_admin_state = ++ ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED; ++ else ++ admin_state->c33_admin_state = ++ ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED; ++ ++ return 0; ++} ++ ++static int ++tps23881_pi_get_pw_status(struct pse_controller_dev *pcdev, int id, ++ struct pse_pw_status *pw_status) ++{ ++ struct tps23881_priv *priv = to_tps23881_priv(pcdev); ++ struct i2c_client *client = priv->client; ++ bool delivering; ++ u8 chan; ++ u16 val; ++ int ret; ++ ++ ret = i2c_smbus_read_word_data(client, TPS23881_REG_PW_STATUS); ++ if (ret < 0) ++ return ret; ++ ++ chan = priv->port[id].chan[0]; ++ val = tps23881_calc_val(ret, chan, 4, BIT(chan % 4)); ++ delivering = !!(val); ++ ++ if (priv->port[id].is_4p) { ++ chan = priv->port[id].chan[1]; + val = tps23881_calc_val(ret, chan, 4, BIT(chan % 4)); + delivering &= !!(val); + } + + /* Return delivering status only if both channel are on this state */ + if (delivering) +- status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING; +- else +- status->c33_pw_status = ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED; +- +- /* Return enabled status only if both channel are on this state */ +- if (enabled) +- status->c33_admin_state = ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED; ++ pw_status->c33_pw_status = ++ ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING; + else +- status->c33_admin_state = ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED; ++ pw_status->c33_pw_status = ++ ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED; + + return 0; + } +@@ -664,7 +691,8 @@ static const struct pse_controller_ops t + .pi_enable = tps23881_pi_enable, + .pi_disable = tps23881_pi_disable, + .pi_is_enabled = tps23881_pi_is_enabled, +- .ethtool_get_status = tps23881_ethtool_get_status, ++ .pi_get_admin_state = tps23881_pi_get_admin_state, ++ .pi_get_pw_status = tps23881_pi_get_pw_status, + }; + + static const char fw_parity_name[] = "ti/tps23881/tps23881-parity-14.bin"; +--- a/include/linux/pse-pd/pse.h ++++ b/include/linux/pse-pd/pse.h +@@ -31,7 +31,52 @@ struct pse_control_config { + }; + + /** +- * struct pse_control_status - PSE control/channel status. ++ * struct pse_admin_state - PSE operational state ++ * ++ * @podl_admin_state: operational state of the PoDL PSE ++ * functions. IEEE 802.3-2018 30.15.1.1.2 aPoDLPSEAdminState ++ * @c33_admin_state: operational state of the PSE ++ * functions. IEEE 802.3-2022 30.9.1.1.2 aPSEAdminState ++ */ ++struct pse_admin_state { ++ enum ethtool_podl_pse_admin_state podl_admin_state; ++ enum ethtool_c33_pse_admin_state c33_admin_state; ++}; ++ ++/** ++ * struct pse_pw_status - PSE power detection status ++ * ++ * @podl_pw_status: power detection status of the PoDL PSE. ++ * IEEE 802.3-2018 30.15.1.1.3 aPoDLPSEPowerDetectionStatus: ++ * @c33_pw_status: power detection status of the PSE. ++ * IEEE 802.3-2022 30.9.1.1.5 aPSEPowerDetectionStatus: ++ */ ++struct pse_pw_status { ++ enum ethtool_podl_pse_pw_d_status podl_pw_status; ++ enum ethtool_c33_pse_pw_d_status c33_pw_status; ++}; ++ ++/** ++ * struct pse_ext_state_info - PSE extended state information ++ * ++ * @c33_ext_state_info: extended state information of the PSE ++ */ ++struct pse_ext_state_info { ++ struct ethtool_c33_pse_ext_state_info c33_ext_state_info; ++}; ++ ++/** ++ * struct pse_pw_limit_ranges - PSE power limit configuration range ++ * ++ * @c33_pw_limit_ranges: supported power limit configuration range. The driver ++ * is in charge of the memory allocation. ++ */ ++struct pse_pw_limit_ranges { ++ struct ethtool_c33_pse_pw_limit_range *c33_pw_limit_ranges; ++}; ++ ++/** ++ * struct ethtool_pse_control_status - PSE control/channel status. + * + * @podl_admin_state: operational state of the PoDL PSE + * functions. IEEE 802.3-2018 30.15.1.1.2 aPoDLPSEAdminState +@@ -49,11 +94,11 @@ struct pse_control_config { + * @c33_avail_pw_limit: available power limit of the PSE in mW + * IEEE 802.3-2022 145.2.5.4 pse_avail_pwr + * @c33_pw_limit_ranges: supported power limit configuration range. The driver +- * is in charge of the memory allocation. ++ * is in charge of the memory allocation + * @c33_pw_limit_nb_ranges: number of supported power limit configuration + * ranges + */ +-struct pse_control_status { ++struct ethtool_pse_control_status { + enum ethtool_podl_pse_admin_state podl_admin_state; + enum ethtool_podl_pse_pw_d_status podl_pw_status; + enum ethtool_c33_pse_admin_state c33_admin_state; +@@ -69,22 +114,37 @@ struct pse_control_status { + /** + * struct pse_controller_ops - PSE controller driver callbacks + * +- * @ethtool_get_status: get PSE control status for ethtool interface + * @setup_pi_matrix: setup PI matrix of the PSE controller ++ * @pi_get_admin_state: Get the operational state of the PSE PI. This ops ++ * is mandatory. ++ * @pi_get_pw_status: Get the power detection status of the PSE PI. This ++ * ops is mandatory. ++ * @pi_get_ext_state: Get the extended state of the PSE PI. ++ * @pi_get_pw_class: Get the power class of the PSE PI. ++ * @pi_get_actual_pw: Get actual power of the PSE PI in mW. + * @pi_is_enabled: Return 1 if the PSE PI is enabled, 0 if not. + * May also return negative errno. + * @pi_enable: Configure the PSE PI as enabled. + * @pi_disable: Configure the PSE PI as disabled. + * @pi_get_voltage: Return voltage similarly to get_voltage regulator +- * callback. +- * @pi_get_pw_limit: Get the configured power limit of the PSE PI. +- * @pi_set_pw_limit: Configure the power limit of the PSE PI. ++ * callback in uV. ++ * @pi_get_pw_limit: Get the configured power limit of the PSE PI in mW. ++ * @pi_set_pw_limit: Configure the power limit of the PSE PI in mW. ++ * @pi_get_pw_limit_ranges: Get the supported power limit configuration ++ * range. The driver is in charge of the memory ++ * allocation and should return the number of ++ * ranges. + */ + struct pse_controller_ops { +- int (*ethtool_get_status)(struct pse_controller_dev *pcdev, +- unsigned long id, struct netlink_ext_ack *extack, +- struct pse_control_status *status); + int (*setup_pi_matrix)(struct pse_controller_dev *pcdev); ++ int (*pi_get_admin_state)(struct pse_controller_dev *pcdev, int id, ++ struct pse_admin_state *admin_state); ++ int (*pi_get_pw_status)(struct pse_controller_dev *pcdev, int id, ++ struct pse_pw_status *pw_status); ++ int (*pi_get_ext_state)(struct pse_controller_dev *pcdev, int id, ++ struct pse_ext_state_info *ext_state_info); ++ int (*pi_get_pw_class)(struct pse_controller_dev *pcdev, int id); ++ int (*pi_get_actual_pw)(struct pse_controller_dev *pcdev, int id); + int (*pi_is_enabled)(struct pse_controller_dev *pcdev, int id); + int (*pi_enable)(struct pse_controller_dev *pcdev, int id); + int (*pi_disable)(struct pse_controller_dev *pcdev, int id); +@@ -93,12 +153,15 @@ struct pse_controller_ops { + int id); + int (*pi_set_pw_limit)(struct pse_controller_dev *pcdev, + int id, int max_mW); ++ int (*pi_get_pw_limit_ranges)(struct pse_controller_dev *pcdev, int id, ++ struct pse_pw_limit_ranges *pw_limit_ranges); + }; + + struct module; + struct device_node; + struct of_phandle_args; + struct pse_control; ++struct ethtool_pse_control_status; + + /* PSE PI pairset pinout can either be Alternative A or Alternative B */ + enum pse_pi_pairset_pinout { +@@ -175,7 +238,7 @@ void pse_control_put(struct pse_control + + int pse_ethtool_get_status(struct pse_control *psec, + struct netlink_ext_ack *extack, +- struct pse_control_status *status); ++ struct ethtool_pse_control_status *status); + int pse_ethtool_set_config(struct pse_control *psec, + struct netlink_ext_ack *extack, + const struct pse_control_config *config); +@@ -201,7 +264,7 @@ static inline void pse_control_put(struc + + static inline int pse_ethtool_get_status(struct pse_control *psec, + struct netlink_ext_ack *extack, +- struct pse_control_status *status) ++ struct ethtool_pse_control_status *status) + { + return -EOPNOTSUPP; + } +--- a/net/ethtool/pse-pd.c ++++ b/net/ethtool/pse-pd.c +@@ -19,7 +19,7 @@ struct pse_req_info { + + struct pse_reply_data { + struct ethnl_reply_data base; +- struct pse_control_status status; ++ struct ethtool_pse_control_status status; + }; + + #define PSE_REPDATA(__reply_base) \ +@@ -80,7 +80,7 @@ static int pse_reply_size(const struct e + const struct ethnl_reply_data *reply_base) + { + const struct pse_reply_data *data = PSE_REPDATA(reply_base); +- const struct pse_control_status *st = &data->status; ++ const struct ethtool_pse_control_status *st = &data->status; + int len = 0; + + if (st->podl_admin_state > 0) +@@ -114,7 +114,7 @@ static int pse_reply_size(const struct e + } + + static int pse_put_pw_limit_ranges(struct sk_buff *skb, +- const struct pse_control_status *st) ++ const struct ethtool_pse_control_status *st) + { + const struct ethtool_c33_pse_pw_limit_range *pw_limit_ranges; + int i; +@@ -146,7 +146,7 @@ static int pse_fill_reply(struct sk_buff + const struct ethnl_reply_data *reply_base) + { + const struct pse_reply_data *data = PSE_REPDATA(reply_base); +- const struct pse_control_status *st = &data->status; ++ const struct ethtool_pse_control_status *st = &data->status; + + if (st->podl_admin_state > 0 && + nla_put_u32(skb, ETHTOOL_A_PODL_PSE_ADMIN_STATE, diff --git a/target/linux/generic/backport-6.12/626-06-v6.13-net-pse-pd-Remove-is_enabled-callback-from-drivers.patch b/target/linux/generic/backport-6.12/626-06-v6.13-net-pse-pd-Remove-is_enabled-callback-from-drivers.patch new file mode 100644 index 00000000000..dd7a20efaa9 --- /dev/null +++ b/target/linux/generic/backport-6.12/626-06-v6.13-net-pse-pd-Remove-is_enabled-callback-from-drivers.patch @@ -0,0 +1,184 @@ +From 4640a1f0d8f2246f34d6e74330d7e7d2cf75605b Mon Sep 17 00:00:00 2001 +From: Kory Maincent +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 +Signed-off-by: Kory Maincent +Signed-off-by: Paolo Abeni +Signed-off-by: Carlo Szelinsky +--- + drivers/net/pse-pd/pd692x0.c | 26 -------------------------- + drivers/net/pse-pd/pse_core.c | 13 +++++++++++-- + drivers/net/pse-pd/pse_regulator.c | 9 --------- + drivers/net/pse-pd/tps23881.c | 28 ---------------------------- + include/linux/pse-pd/pse.h | 3 --- + 5 files changed, 11 insertions(+), 68 deletions(-) + +--- a/drivers/net/pse-pd/pd692x0.c ++++ b/drivers/net/pse-pd/pd692x0.c +@@ -431,31 +431,6 @@ static int pd692x0_pi_disable(struct pse + return 0; + } + +-static int pd692x0_pi_is_enabled(struct pse_controller_dev *pcdev, int id) +-{ +- struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); +- struct pd692x0_msg msg, buf = {0}; +- int ret; +- +- ret = pd692x0_fw_unavailable(priv); +- if (ret) +- return ret; +- +- msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_STATUS]; +- msg.sub[2] = id; +- ret = pd692x0_sendrecv_msg(priv, &msg, &buf); +- if (ret < 0) +- return ret; +- +- if (buf.sub[1]) { +- priv->admin_state[id] = ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED; +- return 1; +- } else { +- priv->admin_state[id] = ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED; +- return 0; +- } +-} +- + struct pd692x0_pse_ext_state_mapping { + u32 status_code; + enum ethtool_c33_pse_ext_state pse_ext_state; +@@ -1105,7 +1080,6 @@ static const struct pse_controller_ops p + .pi_get_actual_pw = pd692x0_pi_get_actual_pw, + .pi_enable = pd692x0_pi_enable, + .pi_disable = pd692x0_pi_disable, +- .pi_is_enabled = pd692x0_pi_is_enabled, + .pi_get_voltage = pd692x0_pi_get_voltage, + .pi_get_pw_limit = pd692x0_pi_get_pw_limit, + .pi_set_pw_limit = pd692x0_pi_set_pw_limit, +--- a/drivers/net/pse-pd/pse_core.c ++++ b/drivers/net/pse-pd/pse_core.c +@@ -210,16 +210,25 @@ out: + static int pse_pi_is_enabled(struct regulator_dev *rdev) + { + struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev); ++ struct pse_admin_state admin_state = {0}; + const struct pse_controller_ops *ops; + int id, ret; + + ops = pcdev->ops; +- if (!ops->pi_is_enabled) ++ if (!ops->pi_get_admin_state) + return -EOPNOTSUPP; + + id = rdev_get_id(rdev); + mutex_lock(&pcdev->lock); +- ret = ops->pi_is_enabled(pcdev, id); ++ ret = ops->pi_get_admin_state(pcdev, id, &admin_state); ++ if (ret) ++ goto out; ++ ++ if (admin_state.podl_admin_state == ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED || ++ admin_state.c33_admin_state == ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED) ++ ret = 1; ++ ++out: + mutex_unlock(&pcdev->lock); + + return ret; +--- a/drivers/net/pse-pd/pse_regulator.c ++++ b/drivers/net/pse-pd/pse_regulator.c +@@ -52,14 +52,6 @@ pse_reg_pi_disable(struct pse_controller + } + + static int +-pse_reg_pi_is_enabled(struct pse_controller_dev *pcdev, int id) +-{ +- struct pse_reg_priv *priv = to_pse_reg(pcdev); +- +- return regulator_is_enabled(priv->ps); +-} +- +-static int + pse_reg_pi_get_admin_state(struct pse_controller_dev *pcdev, int id, + struct pse_admin_state *admin_state) + { +@@ -95,7 +87,6 @@ static const struct pse_controller_ops p + .pi_get_admin_state = pse_reg_pi_get_admin_state, + .pi_get_pw_status = pse_reg_pi_get_pw_status, + .pi_enable = pse_reg_pi_enable, +- .pi_is_enabled = pse_reg_pi_is_enabled, + .pi_disable = pse_reg_pi_disable, + }; + +--- a/drivers/net/pse-pd/tps23881.c ++++ b/drivers/net/pse-pd/tps23881.c +@@ -174,33 +174,6 @@ static int tps23881_pi_disable(struct ps + return i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val); + } + +-static int tps23881_pi_is_enabled(struct pse_controller_dev *pcdev, int id) +-{ +- struct tps23881_priv *priv = to_tps23881_priv(pcdev); +- struct i2c_client *client = priv->client; +- bool enabled; +- u8 chan; +- u16 val; +- int ret; +- +- ret = i2c_smbus_read_word_data(client, TPS23881_REG_PW_STATUS); +- if (ret < 0) +- return ret; +- +- chan = priv->port[id].chan[0]; +- val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4)); +- enabled = !!(val); +- +- if (priv->port[id].is_4p) { +- chan = priv->port[id].chan[1]; +- val = tps23881_calc_val(ret, chan, 0, BIT(chan % 4)); +- enabled &= !!(val); +- } +- +- /* Return enabled status only if both channel are on this state */ +- return enabled; +-} +- + static int + tps23881_pi_get_admin_state(struct pse_controller_dev *pcdev, int id, + struct pse_admin_state *admin_state) +@@ -690,7 +663,6 @@ static const struct pse_controller_ops t + .setup_pi_matrix = tps23881_setup_pi_matrix, + .pi_enable = tps23881_pi_enable, + .pi_disable = tps23881_pi_disable, +- .pi_is_enabled = tps23881_pi_is_enabled, + .pi_get_admin_state = tps23881_pi_get_admin_state, + .pi_get_pw_status = tps23881_pi_get_pw_status, + }; +--- a/include/linux/pse-pd/pse.h ++++ b/include/linux/pse-pd/pse.h +@@ -122,8 +122,6 @@ struct ethtool_pse_control_status { + * @pi_get_ext_state: Get the extended state of the PSE PI. + * @pi_get_pw_class: Get the power class of the PSE PI. + * @pi_get_actual_pw: Get actual power of the PSE PI in mW. +- * @pi_is_enabled: Return 1 if the PSE PI is enabled, 0 if not. +- * May also return negative errno. + * @pi_enable: Configure the PSE PI as enabled. + * @pi_disable: Configure the PSE PI as disabled. + * @pi_get_voltage: Return voltage similarly to get_voltage regulator +@@ -145,7 +143,6 @@ struct pse_controller_ops { + struct pse_ext_state_info *ext_state_info); + int (*pi_get_pw_class)(struct pse_controller_dev *pcdev, int id); + int (*pi_get_actual_pw)(struct pse_controller_dev *pcdev, int id); +- int (*pi_is_enabled)(struct pse_controller_dev *pcdev, int id); + int (*pi_enable)(struct pse_controller_dev *pcdev, int id); + int (*pi_disable)(struct pse_controller_dev *pcdev, int id); + int (*pi_get_voltage)(struct pse_controller_dev *pcdev, int id); diff --git a/target/linux/generic/backport-6.12/626-07-v6.13-net-pse-pd-tps23881-Add-power-limit-and-measurement-features.patch b/target/linux/generic/backport-6.12/626-07-v6.13-net-pse-pd-tps23881-Add-power-limit-and-measurement-features.patch new file mode 100644 index 00000000000..de3cc76b4f6 --- /dev/null +++ b/target/linux/generic/backport-6.12/626-07-v6.13-net-pse-pd-tps23881-Add-power-limit-and-measurement-features.patch @@ -0,0 +1,337 @@ +From 7f076ce3f17334964590c2cce49a02c0851c099a Mon Sep 17 00:00:00 2001 +From: Kory Maincent +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 +Signed-off-by: Kory Maincent +Signed-off-by: Paolo Abeni +Signed-off-by: Carlo Szelinsky +--- + drivers/net/pse-pd/tps23881.c | 258 +++++++++++++++++++++++++++++++++- + 1 file changed, 256 insertions(+), 2 deletions(-) + +--- a/drivers/net/pse-pd/tps23881.c ++++ b/drivers/net/pse-pd/tps23881.c +@@ -25,20 +25,32 @@ + #define TPS23881_REG_GEN_MASK 0x17 + #define TPS23881_REG_NBITACC BIT(5) + #define TPS23881_REG_PW_EN 0x19 ++#define TPS23881_REG_2PAIR_POL1 0x1e + #define TPS23881_REG_PORT_MAP 0x26 + #define TPS23881_REG_PORT_POWER 0x29 +-#define TPS23881_REG_POEPLUS 0x40 ++#define TPS23881_REG_4PAIR_POL1 0x2a ++#define TPS23881_REG_INPUT_V 0x2e ++#define TPS23881_REG_CHAN1_A 0x30 ++#define TPS23881_REG_CHAN1_V 0x32 ++#define TPS23881_REG_FOLDBACK 0x40 + #define TPS23881_REG_TPON BIT(0) + #define TPS23881_REG_FWREV 0x41 + #define TPS23881_REG_DEVID 0x43 + #define TPS23881_REG_DEVID_MASK 0xF0 + #define TPS23881_DEVICE_ID 0x02 ++#define TPS23881_REG_CHAN1_CLASS 0x4c + #define TPS23881_REG_SRAM_CTRL 0x60 + #define TPS23881_REG_SRAM_DATA 0x61 + ++#define TPS23881_UV_STEP 3662 ++#define TPS23881_NA_STEP 70190 ++#define TPS23881_MW_STEP 500 ++#define TPS23881_MIN_PI_PW_LIMIT_MW 2000 ++ + struct tps23881_port_desc { + u8 chan[2]; + bool is_4p; ++ int pw_pol; + }; + + struct tps23881_priv { +@@ -102,6 +114,54 @@ static u16 tps23881_set_val(u16 reg_val, + return reg_val; + } + ++static int ++tps23881_pi_set_pw_pol_limit(struct tps23881_priv *priv, int id, u8 pw_pol, ++ bool is_4p) ++{ ++ struct i2c_client *client = priv->client; ++ int ret, reg; ++ u16 val; ++ u8 chan; ++ ++ chan = priv->port[id].chan[0]; ++ if (!is_4p) { ++ reg = TPS23881_REG_2PAIR_POL1 + (chan % 4); ++ } else { ++ /* One chan is enough to configure the 4p PI power limit */ ++ if ((chan % 4) < 2) ++ reg = TPS23881_REG_4PAIR_POL1; ++ else ++ reg = TPS23881_REG_4PAIR_POL1 + 1; ++ } ++ ++ ret = i2c_smbus_read_word_data(client, reg); ++ if (ret < 0) ++ return ret; ++ ++ val = tps23881_set_val(ret, chan, 0, 0xff, pw_pol); ++ return i2c_smbus_write_word_data(client, reg, val); ++} ++ ++static int tps23881_pi_enable_manual_pol(struct tps23881_priv *priv, int id) ++{ ++ struct i2c_client *client = priv->client; ++ int ret; ++ u8 chan; ++ u16 val; ++ ++ ret = i2c_smbus_read_byte_data(client, TPS23881_REG_FOLDBACK); ++ if (ret < 0) ++ return ret; ++ ++ /* No need to test if the chan is PoE4 as setting either bit for a ++ * 4P configured port disables the automatic configuration on both ++ * channels. ++ */ ++ chan = priv->port[id].chan[0]; ++ val = tps23881_set_val(ret, chan, 0, BIT(chan % 4), BIT(chan % 4)); ++ return i2c_smbus_write_byte_data(client, TPS23881_REG_FOLDBACK, val); ++} ++ + static int tps23881_pi_enable(struct pse_controller_dev *pcdev, int id) + { + struct tps23881_priv *priv = to_tps23881_priv(pcdev); +@@ -171,7 +231,21 @@ static int tps23881_pi_disable(struct ps + BIT(chan % 4)); + } + +- return i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val); ++ ret = i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val); ++ if (ret) ++ return ret; ++ ++ /* No power policy */ ++ if (priv->port[id].pw_pol < 0) ++ return 0; ++ ++ ret = tps23881_pi_enable_manual_pol(priv, id); ++ if (ret < 0) ++ return ret; ++ ++ /* Set power policy */ ++ return tps23881_pi_set_pw_pol_limit(priv, id, priv->port[id].pw_pol, ++ priv->port[id].is_4p); + } + + static int +@@ -246,6 +320,177 @@ tps23881_pi_get_pw_status(struct pse_con + return 0; + } + ++static int tps23881_pi_get_voltage(struct pse_controller_dev *pcdev, int id) ++{ ++ struct tps23881_priv *priv = to_tps23881_priv(pcdev); ++ struct i2c_client *client = priv->client; ++ int ret; ++ u64 uV; ++ ++ ret = i2c_smbus_read_word_data(client, TPS23881_REG_INPUT_V); ++ if (ret < 0) ++ return ret; ++ ++ uV = ret & 0x3fff; ++ uV *= TPS23881_UV_STEP; ++ ++ return (int)uV; ++} ++ ++static int ++tps23881_pi_get_chan_current(struct tps23881_priv *priv, u8 chan) ++{ ++ struct i2c_client *client = priv->client; ++ int reg, ret; ++ u64 tmp_64; ++ ++ /* Registers 0x30 to 0x3d */ ++ reg = TPS23881_REG_CHAN1_A + (chan % 4) * 4 + (chan >= 4); ++ ret = i2c_smbus_read_word_data(client, reg); ++ if (ret < 0) ++ return ret; ++ ++ tmp_64 = ret & 0x3fff; ++ tmp_64 *= TPS23881_NA_STEP; ++ /* uA = nA / 1000 */ ++ tmp_64 = DIV_ROUND_CLOSEST_ULL(tmp_64, 1000); ++ return (int)tmp_64; ++} ++ ++static int tps23881_pi_get_pw_class(struct pse_controller_dev *pcdev, ++ int id) ++{ ++ struct tps23881_priv *priv = to_tps23881_priv(pcdev); ++ struct i2c_client *client = priv->client; ++ int ret, reg; ++ u8 chan; ++ ++ chan = priv->port[id].chan[0]; ++ reg = TPS23881_REG_CHAN1_CLASS + (chan % 4); ++ ret = i2c_smbus_read_word_data(client, reg); ++ if (ret < 0) ++ return ret; ++ ++ return tps23881_calc_val(ret, chan, 4, 0x0f); ++} ++ ++static int ++tps23881_pi_get_actual_pw(struct pse_controller_dev *pcdev, int id) ++{ ++ struct tps23881_priv *priv = to_tps23881_priv(pcdev); ++ int ret, uV, uA; ++ u64 tmp_64; ++ u8 chan; ++ ++ ret = tps23881_pi_get_voltage(&priv->pcdev, id); ++ if (ret < 0) ++ return ret; ++ uV = ret; ++ ++ chan = priv->port[id].chan[0]; ++ ret = tps23881_pi_get_chan_current(priv, chan); ++ if (ret < 0) ++ return ret; ++ uA = ret; ++ ++ if (priv->port[id].is_4p) { ++ chan = priv->port[id].chan[1]; ++ ret = tps23881_pi_get_chan_current(priv, chan); ++ if (ret < 0) ++ return ret; ++ uA += ret; ++ } ++ ++ tmp_64 = uV; ++ tmp_64 *= uA; ++ /* mW = uV * uA / 1000000000 */ ++ return DIV_ROUND_CLOSEST_ULL(tmp_64, 1000000000); ++} ++ ++static int ++tps23881_pi_get_pw_limit_chan(struct tps23881_priv *priv, u8 chan) ++{ ++ struct i2c_client *client = priv->client; ++ int ret, reg; ++ u16 val; ++ ++ reg = TPS23881_REG_2PAIR_POL1 + (chan % 4); ++ ret = i2c_smbus_read_word_data(client, reg); ++ if (ret < 0) ++ return ret; ++ ++ val = tps23881_calc_val(ret, chan, 0, 0xff); ++ return val * TPS23881_MW_STEP; ++} ++ ++static int tps23881_pi_get_pw_limit(struct pse_controller_dev *pcdev, int id) ++{ ++ struct tps23881_priv *priv = to_tps23881_priv(pcdev); ++ int ret, mW; ++ u8 chan; ++ ++ chan = priv->port[id].chan[0]; ++ ret = tps23881_pi_get_pw_limit_chan(priv, chan); ++ if (ret < 0) ++ return ret; ++ ++ mW = ret; ++ if (priv->port[id].is_4p) { ++ chan = priv->port[id].chan[1]; ++ ret = tps23881_pi_get_pw_limit_chan(priv, chan); ++ if (ret < 0) ++ return ret; ++ mW += ret; ++ } ++ ++ return mW; ++} ++ ++static int tps23881_pi_set_pw_limit(struct pse_controller_dev *pcdev, ++ int id, int max_mW) ++{ ++ struct tps23881_priv *priv = to_tps23881_priv(pcdev); ++ u8 pw_pol; ++ int ret; ++ ++ if (max_mW < TPS23881_MIN_PI_PW_LIMIT_MW || MAX_PI_PW < max_mW) { ++ dev_err(&priv->client->dev, ++ "power limit %d out of ranges [%d,%d]", ++ max_mW, TPS23881_MIN_PI_PW_LIMIT_MW, MAX_PI_PW); ++ return -ERANGE; ++ } ++ ++ ret = tps23881_pi_enable_manual_pol(priv, id); ++ if (ret < 0) ++ return ret; ++ ++ pw_pol = DIV_ROUND_CLOSEST_ULL(max_mW, TPS23881_MW_STEP); ++ ++ /* Save power policy to reconfigure it after a disabled call */ ++ priv->port[id].pw_pol = pw_pol; ++ return tps23881_pi_set_pw_pol_limit(priv, id, pw_pol, ++ priv->port[id].is_4p); ++} ++ ++static int ++tps23881_pi_get_pw_limit_ranges(struct pse_controller_dev *pcdev, int id, ++ struct pse_pw_limit_ranges *pw_limit_ranges) ++{ ++ struct ethtool_c33_pse_pw_limit_range *c33_pw_limit_ranges; ++ ++ c33_pw_limit_ranges = kzalloc(sizeof(*c33_pw_limit_ranges), ++ GFP_KERNEL); ++ if (!c33_pw_limit_ranges) ++ return -ENOMEM; ++ ++ c33_pw_limit_ranges->min = TPS23881_MIN_PI_PW_LIMIT_MW; ++ c33_pw_limit_ranges->max = MAX_PI_PW; ++ pw_limit_ranges->c33_pw_limit_ranges = c33_pw_limit_ranges; ++ ++ /* Return the number of ranges */ ++ return 1; ++} ++ + /* Parse managers subnode into a array of device node */ + static int + tps23881_get_of_channels(struct tps23881_priv *priv, +@@ -540,6 +785,9 @@ tps23881_write_port_matrix(struct tps238 + if (port_matrix[i].exist) + priv->port[pi_id].chan[0] = lgcl_chan; + ++ /* Initialize power policy internal value */ ++ priv->port[pi_id].pw_pol = -1; ++ + /* Set hardware port matrix for all ports */ + val |= hw_chan << (lgcl_chan * 2); + +@@ -665,6 +913,12 @@ static const struct pse_controller_ops t + .pi_disable = tps23881_pi_disable, + .pi_get_admin_state = tps23881_pi_get_admin_state, + .pi_get_pw_status = tps23881_pi_get_pw_status, ++ .pi_get_pw_class = tps23881_pi_get_pw_class, ++ .pi_get_actual_pw = tps23881_pi_get_actual_pw, ++ .pi_get_voltage = tps23881_pi_get_voltage, ++ .pi_get_pw_limit = tps23881_pi_get_pw_limit, ++ .pi_set_pw_limit = tps23881_pi_set_pw_limit, ++ .pi_get_pw_limit_ranges = tps23881_pi_get_pw_limit_ranges, + }; + + static const char fw_parity_name[] = "ti/tps23881/tps23881-parity-14.bin"; diff --git a/target/linux/generic/backport-6.12/626-08-v6.13-net-pse-pd-Fix-missing-PI-of_node-description.patch b/target/linux/generic/backport-6.12/626-08-v6.13-net-pse-pd-Fix-missing-PI-of_node-description.patch new file mode 100644 index 00000000000..3aa46cde42c --- /dev/null +++ b/target/linux/generic/backport-6.12/626-08-v6.13-net-pse-pd-Fix-missing-PI-of_node-description.patch @@ -0,0 +1,28 @@ +From 10276f3e1c7e7f5de9f0bba58f8a849cb195253d Mon Sep 17 00:00:00 2001 +From: Kory Maincent +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 +Signed-off-by: Kory Maincent +Signed-off-by: Paolo Abeni +Signed-off-by: Carlo Szelinsky +--- + drivers/net/pse-pd/pse_core.c | 1 + + 1 file changed, 1 insertion(+) +--- a/drivers/net/pse-pd/pse_core.c ++++ b/drivers/net/pse-pd/pse_core.c +@@ -422,6 +422,7 @@ devm_pse_pi_regulator_register(struct ps + rconfig.dev = pcdev->dev; + rconfig.driver_data = pcdev; + rconfig.init_data = rinit_data; ++ rconfig.of_node = pcdev->pi[id].np; + + rdev = devm_regulator_register(pcdev->dev, rdesc, &rconfig); + if (IS_ERR(rdev)) { diff --git a/target/linux/generic/backport-6.12/626-09-v6.13-net-pse-pd-Clean-ethtool-header-of-PSE-structures.patch b/target/linux/generic/backport-6.12/626-09-v6.13-net-pse-pd-Clean-ethtool-header-of-PSE-structures.patch new file mode 100644 index 00000000000..7d338125a03 --- /dev/null +++ b/target/linux/generic/backport-6.12/626-09-v6.13-net-pse-pd-Clean-ethtool-header-of-PSE-structures.patch @@ -0,0 +1,92 @@ +From 5385f1e1923ca8131eb143567d509b101a344e06 Mon Sep 17 00:00:00 2001 +From: Kory Maincent +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 +Signed-off-by: Paolo Abeni +Signed-off-by: Carlo Szelinsky +--- + 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 ++#include + #include + #include + #include +--- 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 + #include + #include + +@@ -16,6 +15,27 @@ + + struct phy_device; + struct pse_controller_dev; ++struct netlink_ext_ack; ++ ++/* C33 PSE extended state and substate. */ ++struct ethtool_c33_pse_ext_state_info { ++ enum ethtool_c33_pse_ext_state c33_pse_ext_state; ++ union { ++ enum ethtool_c33_pse_ext_substate_error_condition error_condition; ++ enum ethtool_c33_pse_ext_substate_mr_pse_enable mr_pse_enable; ++ enum ethtool_c33_pse_ext_substate_option_detect_ted option_detect_ted; ++ enum ethtool_c33_pse_ext_substate_option_vport_lim option_vport_lim; ++ enum ethtool_c33_pse_ext_substate_ovld_detected ovld_detected; ++ enum ethtool_c33_pse_ext_substate_power_not_available power_not_available; ++ enum ethtool_c33_pse_ext_substate_short_detected short_detected; ++ u32 __c33_pse_ext_substate; ++ }; ++}; ++ ++struct ethtool_c33_pse_pw_limit_range { ++ u32 min; ++ u32 max; ++}; + + /** + * struct pse_control_config - PSE control/channel configuration. diff --git a/target/linux/generic/backport-6.12/626-10-v6.17-net-pse-pd-Introduce-attached_phydev-to-pse-control.patch b/target/linux/generic/backport-6.12/626-10-v6.17-net-pse-pd-Introduce-attached_phydev-to-pse-control.patch new file mode 100644 index 00000000000..b00c0e14759 --- /dev/null +++ b/target/linux/generic/backport-6.12/626-10-v6.17-net-pse-pd-Introduce-attached_phydev-to-pse-control.patch @@ -0,0 +1,171 @@ +From fa2f0454174c2f33005f5a6e6f70c7160a15b2a1 Mon Sep 17 00:00:00 2001 +From: "Kory Maincent (Dent Project)" +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) +Reviewed-by: Oleksij Rempel +Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-1-78a1a645e2ee@bootlin.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Carlo Szelinsky +--- + drivers/net/mdio/fwnode_mdio.c | 26 ++++++++++++++------------ + drivers/net/pse-pd/pse_core.c | 11 ++++++++--- + include/linux/pse-pd/pse.h | 6 ++++-- + 3 files changed, 26 insertions(+), 17 deletions(-) +--- a/drivers/net/mdio/fwnode_mdio.c ++++ b/drivers/net/mdio/fwnode_mdio.c +@@ -18,7 +18,8 @@ MODULE_LICENSE("GPL"); + MODULE_DESCRIPTION("FWNODE MDIO bus (Ethernet PHY) accessors"); + + static struct pse_control * +-fwnode_find_pse_control(struct fwnode_handle *fwnode) ++fwnode_find_pse_control(struct fwnode_handle *fwnode, ++ struct phy_device *phydev) + { + struct pse_control *psec; + struct device_node *np; +@@ -30,7 +31,7 @@ fwnode_find_pse_control(struct fwnode_ha + if (!np) + return NULL; + +- psec = of_pse_control_get(np); ++ psec = of_pse_control_get(np, phydev); + if (PTR_ERR(psec) == -ENOENT) + return NULL; + +@@ -128,15 +129,9 @@ int fwnode_mdiobus_register_phy(struct m + u32 phy_id; + int rc; + +- psec = fwnode_find_pse_control(child); +- if (IS_ERR(psec)) +- return PTR_ERR(psec); +- + mii_ts = fwnode_find_mii_timestamper(child); +- if (IS_ERR(mii_ts)) { +- rc = PTR_ERR(mii_ts); +- goto clean_pse; +- } ++ if (IS_ERR(mii_ts)) ++ return PTR_ERR(mii_ts); + + is_c45 = fwnode_device_is_compatible(child, "ethernet-phy-ieee802.3-c45"); + if (is_c45 || fwnode_get_phy_id(child, &phy_id)) +@@ -169,6 +164,12 @@ int fwnode_mdiobus_register_phy(struct m + goto clean_phy; + } + ++ psec = fwnode_find_pse_control(child, phy); ++ if (IS_ERR(psec)) { ++ rc = PTR_ERR(psec); ++ goto unregister_phy; ++ } ++ + phy->psec = psec; + + /* phy->mii_ts may already be defined by the PHY driver. A +@@ -180,12 +181,13 @@ int fwnode_mdiobus_register_phy(struct m + + return 0; + ++unregister_phy: ++ if (is_acpi_node(child) || is_of_node(child)) ++ phy_device_remove(phy); + clean_phy: + phy_device_free(phy); + clean_mii_ts: + unregister_mii_timestamper(mii_ts); +-clean_pse: +- pse_control_put(psec); + + return rc; + } +--- a/drivers/net/pse-pd/pse_core.c ++++ b/drivers/net/pse-pd/pse_core.c +@@ -23,6 +23,7 @@ static LIST_HEAD(pse_controller_list); + * @list: list entry for the pcdev's PSE controller list + * @id: ID of the PSE line in the PSE controller device + * @refcnt: Number of gets of this pse_control ++ * @attached_phydev: PHY device pointer attached by the PSE control + */ + struct pse_control { + struct pse_controller_dev *pcdev; +@@ -30,6 +31,7 @@ struct pse_control { + struct list_head list; + unsigned int id; + struct kref refcnt; ++ struct phy_device *attached_phydev; + }; + + static int of_load_single_pse_pi_pairset(struct device_node *node, +@@ -599,7 +601,8 @@ void pse_control_put(struct pse_control + EXPORT_SYMBOL_GPL(pse_control_put); + + static struct pse_control * +-pse_control_get_internal(struct pse_controller_dev *pcdev, unsigned int index) ++pse_control_get_internal(struct pse_controller_dev *pcdev, unsigned int index, ++ struct phy_device *phydev) + { + struct pse_control *psec; + int ret; +@@ -638,6 +641,7 @@ pse_control_get_internal(struct pse_cont + psec->pcdev = pcdev; + list_add(&psec->list, &pcdev->pse_control_head); + psec->id = index; ++ psec->attached_phydev = phydev; + kref_init(&psec->refcnt); + + return psec; +@@ -693,7 +697,8 @@ static int psec_id_xlate(struct pse_cont + return pse_spec->args[0]; + } + +-struct pse_control *of_pse_control_get(struct device_node *node) ++struct pse_control *of_pse_control_get(struct device_node *node, ++ struct phy_device *phydev) + { + struct pse_controller_dev *r, *pcdev; + struct of_phandle_args args; +@@ -743,7 +748,7 @@ struct pse_control *of_pse_control_get(s + } + + /* pse_list_mutex also protects the pcdev's pse_control list */ +- psec = pse_control_get_internal(pcdev, psec_id); ++ psec = pse_control_get_internal(pcdev, psec_id, phydev); + + out: + mutex_unlock(&pse_list_mutex); +--- a/include/linux/pse-pd/pse.h ++++ b/include/linux/pse-pd/pse.h +@@ -250,7 +250,8 @@ struct device; + int devm_pse_controller_register(struct device *dev, + struct pse_controller_dev *pcdev); + +-struct pse_control *of_pse_control_get(struct device_node *node); ++struct pse_control *of_pse_control_get(struct device_node *node, ++ struct phy_device *phydev); + void pse_control_put(struct pse_control *psec); + + int pse_ethtool_get_status(struct pse_control *psec, +@@ -270,7 +271,8 @@ bool pse_has_c33(struct pse_control *pse + + #else + +-static inline struct pse_control *of_pse_control_get(struct device_node *node) ++static inline struct pse_control *of_pse_control_get(struct device_node *node, ++ struct phy_device *phydev) + { + return ERR_PTR(-ENOENT); + } diff --git a/target/linux/generic/backport-6.12/626-11-v6.17-net-pse-pd-Add-support-for-reporting-events.patch b/target/linux/generic/backport-6.12/626-11-v6.17-net-pse-pd-Add-support-for-reporting-events.patch new file mode 100644 index 00000000000..ec99fcab821 --- /dev/null +++ b/target/linux/generic/backport-6.12/626-11-v6.17-net-pse-pd-Add-support-for-reporting-events.patch @@ -0,0 +1,363 @@ +# ADAPTED FOR OPENWRT 6.12.67 - Documentation changes removed +# Original commit: fc0e6db30941 +From fc0e6db30941a66e284b8516b82356f97f31061d Mon Sep 17 00:00:00 2001 +From: "Kory Maincent (Dent Project)" +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) +Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-2-78a1a645e2ee@bootlin.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Carlo Szelinsky +--- + 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 + #include ++#include + #include ++#include + #include + #include + #include ++#include ++#include + + 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, ¬ifs_mask); ++ mutex_unlock(&pcdev->lock); ++ if (ret || !notifs_mask) ++ return IRQ_NONE; ++ ++ for_each_set_bit(i, ¬ifs_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 + #include ++#include ++#include + + /* Maximum current in uA according to IEEE 802.3-2022 Table 145-1 */ + #define MAX_PI_CURRENT 1920000 + /* Maximum power in mW according to IEEE 802.3-2022 Table 145-16 */ + #define MAX_PI_PW 99900 + ++struct net_device; + struct phy_device; + struct pse_controller_dev; + struct netlink_ext_ack; +@@ -38,6 +41,19 @@ struct ethtool_c33_pse_pw_limit_range { + }; + + /** ++ * struct pse_irq_desc - notification sender description for IRQ based events. ++ * ++ * @name: the visible name for the IRQ ++ * @map_event: driver callback to map IRQ status into PSE devices with events. ++ */ ++struct pse_irq_desc { ++ const char *name; ++ int (*map_event)(int irq, struct pse_controller_dev *pcdev, ++ unsigned long *notifs, ++ unsigned long *notifs_mask); ++}; ++ ++/** + * struct pse_control_config - PSE control/channel configuration. + * + * @podl_admin_control: set PoDL PSE admin control as described in +@@ -228,6 +244,7 @@ struct pse_pi { + * @types: types of the PSE controller + * @pi: table of PSE PIs described in this controller device + * @no_of_pse_pi: flag set if the pse_pis devicetree node is not used ++ * @irq: PSE interrupt + */ + struct pse_controller_dev { + const struct pse_controller_ops *ops; +@@ -241,6 +258,7 @@ struct pse_controller_dev { + enum ethtool_pse_types types; + struct pse_pi *pi; + bool no_of_pse_pi; ++ int irq; + }; + + #if IS_ENABLED(CONFIG_PSE_CONTROLLER) +@@ -249,6 +267,8 @@ void pse_controller_unregister(struct ps + struct device; + int devm_pse_controller_register(struct device *dev, + struct pse_controller_dev *pcdev); ++int devm_pse_irq_helper(struct pse_controller_dev *pcdev, int irq, ++ int irq_flags, const struct pse_irq_desc *d); + + struct pse_control *of_pse_control_get(struct device_node *node, + struct phy_device *phydev); +--- a/net/ethtool/pse-pd.c ++++ b/net/ethtool/pse-pd.c +@@ -315,3 +315,42 @@ const struct ethnl_request_ops ethnl_pse + .set = ethnl_set_pse, + /* PSE has no notification */ + }; ++ ++void ethnl_pse_send_ntf(struct net_device *netdev, unsigned long notifs) ++{ ++ void *reply_payload; ++ struct sk_buff *skb; ++ int reply_len; ++ int ret; ++ ++ ASSERT_RTNL(); ++ ++ if (!netdev || !notifs) ++ return; ++ ++ reply_len = ethnl_reply_header_size() + ++ nla_total_size(sizeof(u32)); /* _PSE_NTF_EVENTS */ ++ ++ skb = genlmsg_new(reply_len, GFP_KERNEL); ++ if (!skb) ++ return; ++ ++ reply_payload = ethnl_bcastmsg_put(skb, ETHTOOL_MSG_PSE_NTF); ++ if (!reply_payload) ++ goto err_skb; ++ ++ ret = ethnl_fill_reply_header(skb, netdev, ETHTOOL_A_PSE_NTF_HEADER); ++ if (ret < 0) ++ goto err_skb; ++ ++ if (nla_put_uint(skb, ETHTOOL_A_PSE_NTF_EVENTS, notifs)) ++ goto err_skb; ++ ++ genlmsg_end(skb, reply_payload); ++ ethnl_multicast(skb, netdev); ++ return; ++ ++err_skb: ++ nlmsg_free(skb); ++} ++EXPORT_SYMBOL_GPL(ethnl_pse_send_ntf); diff --git a/target/linux/generic/backport-6.12/626-12-v6.17-net-pse-pd-tps23881-Add-support-for-PSE-events.patch b/target/linux/generic/backport-6.12/626-12-v6.17-net-pse-pd-tps23881-Add-support-for-PSE-events.patch new file mode 100644 index 00000000000..8afcb3f8700 --- /dev/null +++ b/target/linux/generic/backport-6.12/626-12-v6.17-net-pse-pd-tps23881-Add-support-for-PSE-events.patch @@ -0,0 +1,252 @@ +From f5e7aecaa4efcd4c85477b6a62f94fea668031db Mon Sep 17 00:00:00 2001 +From: "Kory Maincent (Dent Project)" +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) +Reviewed-by: Oleksij Rempel +Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-3-78a1a645e2ee@bootlin.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Carlo Szelinsky +--- + 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 + + #define TPS23881_MAX_CHANS 8 ++#define TPS23881_MAX_IRQ_RETRIES 10 + ++#define TPS23881_REG_IT 0x0 ++#define TPS23881_REG_IT_MASK 0x1 ++#define TPS23881_REG_IT_IFAULT BIT(5) ++#define TPS23881_REG_IT_SUPF BIT(7) ++#define TPS23881_REG_FAULT 0x7 ++#define TPS23881_REG_SUPF_EVENT 0xb ++#define TPS23881_REG_TSD BIT(7) + #define TPS23881_REG_PW_STATUS 0x10 + #define TPS23881_REG_OP_MODE 0x12 + #define TPS23881_OP_MODE_SEMIAUTO 0xaaaa +@@ -24,6 +32,7 @@ + #define TPS23881_REG_DET_CLA_EN 0x14 + #define TPS23881_REG_GEN_MASK 0x17 + #define TPS23881_REG_NBITACC BIT(5) ++#define TPS23881_REG_INTEN BIT(7) + #define TPS23881_REG_PW_EN 0x19 + #define TPS23881_REG_2PAIR_POL1 0x1e + #define TPS23881_REG_PORT_MAP 0x26 +@@ -51,6 +60,7 @@ struct tps23881_port_desc { + u8 chan[2]; + bool is_4p; + int pw_pol; ++ bool exist; + }; + + struct tps23881_priv { +@@ -782,8 +792,10 @@ tps23881_write_port_matrix(struct tps238 + hw_chan = port_matrix[i].hw_chan[0] % 4; + + /* Set software port matrix for existing ports */ +- if (port_matrix[i].exist) ++ if (port_matrix[i].exist) { + priv->port[pi_id].chan[0] = lgcl_chan; ++ priv->port[pi_id].exist = true; ++ } + + /* Initialize power policy internal value */ + priv->port[pi_id].pw_pol = -1; +@@ -1017,6 +1029,173 @@ static int tps23881_flash_sram_fw(struct + return 0; + } + ++/* Convert interrupt events to 0xff to be aligned with the chan ++ * number. ++ */ ++static u8 tps23881_irq_export_chans_helper(u16 reg_val, u8 field_offset) ++{ ++ u8 val; ++ ++ val = (reg_val >> (4 + field_offset) & 0xf0) | ++ (reg_val >> field_offset & 0x0f); ++ ++ return val; ++} ++ ++/* Convert chan number to port number */ ++static void tps23881_set_notifs_helper(struct tps23881_priv *priv, ++ u8 chans, ++ unsigned long *notifs, ++ unsigned long *notifs_mask, ++ enum ethtool_pse_event event) ++{ ++ u8 chan; ++ int i; ++ ++ if (!chans) ++ return; ++ ++ for (i = 0; i < TPS23881_MAX_CHANS; i++) { ++ if (!priv->port[i].exist) ++ continue; ++ /* No need to look at the 2nd channel in case of PoE4 as ++ * both registers are set. ++ */ ++ chan = priv->port[i].chan[0]; ++ ++ if (BIT(chan) & chans) { ++ *notifs_mask |= BIT(i); ++ notifs[i] |= event; ++ } ++ } ++} ++ ++static void tps23881_irq_event_over_temp(struct tps23881_priv *priv, ++ u16 reg_val, ++ unsigned long *notifs, ++ unsigned long *notifs_mask) ++{ ++ int i; ++ ++ if (reg_val & TPS23881_REG_TSD) { ++ for (i = 0; i < TPS23881_MAX_CHANS; i++) { ++ if (!priv->port[i].exist) ++ continue; ++ ++ *notifs_mask |= BIT(i); ++ notifs[i] |= ETHTOOL_PSE_EVENT_OVER_TEMP; ++ } ++ } ++} ++ ++static void tps23881_irq_event_over_current(struct tps23881_priv *priv, ++ u16 reg_val, ++ unsigned long *notifs, ++ unsigned long *notifs_mask) ++{ ++ u8 chans; ++ ++ chans = tps23881_irq_export_chans_helper(reg_val, 0); ++ if (chans) ++ tps23881_set_notifs_helper(priv, chans, notifs, notifs_mask, ++ ETHTOOL_PSE_EVENT_OVER_CURRENT); ++} ++ ++static int tps23881_irq_event_handler(struct tps23881_priv *priv, u16 reg, ++ unsigned long *notifs, ++ unsigned long *notifs_mask) ++{ ++ struct i2c_client *client = priv->client; ++ int ret; ++ ++ /* The Supply event bit is repeated twice so we only need to read ++ * the one from the first byte. ++ */ ++ if (reg & TPS23881_REG_IT_SUPF) { ++ ret = i2c_smbus_read_word_data(client, TPS23881_REG_SUPF_EVENT); ++ if (ret < 0) ++ return ret; ++ tps23881_irq_event_over_temp(priv, ret, notifs, notifs_mask); ++ } ++ ++ if (reg & (TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_IFAULT << 8)) { ++ ret = i2c_smbus_read_word_data(client, TPS23881_REG_FAULT); ++ if (ret < 0) ++ return ret; ++ tps23881_irq_event_over_current(priv, ret, notifs, notifs_mask); ++ } ++ ++ return 0; ++} ++ ++static int tps23881_irq_handler(int irq, struct pse_controller_dev *pcdev, ++ unsigned long *notifs, ++ unsigned long *notifs_mask) ++{ ++ struct tps23881_priv *priv = to_tps23881_priv(pcdev); ++ struct i2c_client *client = priv->client; ++ int ret, it_mask, retry; ++ ++ /* Get interruption mask */ ++ ret = i2c_smbus_read_word_data(client, TPS23881_REG_IT_MASK); ++ if (ret < 0) ++ return ret; ++ it_mask = ret; ++ ++ /* Read interrupt register until it frees the interruption pin. */ ++ retry = 0; ++ while (true) { ++ if (retry > TPS23881_MAX_IRQ_RETRIES) { ++ dev_err(&client->dev, "interrupt never freed"); ++ return -ETIMEDOUT; ++ } ++ ++ ret = i2c_smbus_read_word_data(client, TPS23881_REG_IT); ++ if (ret < 0) ++ return ret; ++ ++ /* No more relevant interruption */ ++ if (!(ret & it_mask)) ++ return 0; ++ ++ ret = tps23881_irq_event_handler(priv, (u16)ret, notifs, ++ notifs_mask); ++ if (ret) ++ return ret; ++ ++ retry++; ++ } ++ return 0; ++} ++ ++static int tps23881_setup_irq(struct tps23881_priv *priv, int irq) ++{ ++ struct i2c_client *client = priv->client; ++ struct pse_irq_desc irq_desc = { ++ .name = "tps23881-irq", ++ .map_event = tps23881_irq_handler, ++ }; ++ int ret; ++ u16 val; ++ ++ val = TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_SUPF; ++ val |= val << 8; ++ ret = i2c_smbus_write_word_data(client, TPS23881_REG_IT_MASK, val); ++ if (ret) ++ return ret; ++ ++ ret = i2c_smbus_read_word_data(client, TPS23881_REG_GEN_MASK); ++ if (ret < 0) ++ return ret; ++ ++ val = (u16)(ret | TPS23881_REG_INTEN | TPS23881_REG_INTEN << 8); ++ ret = i2c_smbus_write_word_data(client, TPS23881_REG_GEN_MASK, val); ++ if (ret < 0) ++ return ret; ++ ++ return devm_pse_irq_helper(&priv->pcdev, irq, 0, &irq_desc); ++} ++ + static int tps23881_i2c_probe(struct i2c_client *client) + { + struct device *dev = &client->dev; +@@ -1097,6 +1276,12 @@ static int tps23881_i2c_probe(struct i2c + "failed to register PSE controller\n"); + } + ++ if (client->irq) { ++ ret = tps23881_setup_irq(priv, client->irq); ++ if (ret) ++ return ret; ++ } ++ + return ret; + } + diff --git a/target/linux/generic/backport-6.12/626-13-v6.17-net-pse-pd-Add-support-for-PSE-power-domains.patch b/target/linux/generic/backport-6.12/626-13-v6.17-net-pse-pd-Add-support-for-PSE-power-domains.patch new file mode 100644 index 00000000000..a02bbbc28b1 --- /dev/null +++ b/target/linux/generic/backport-6.12/626-13-v6.17-net-pse-pd-Add-support-for-PSE-power-domains.patch @@ -0,0 +1,218 @@ +From 50f8b341d26826aa5fdccb8f497fbff2500934b3 Mon Sep 17 00:00:00 2001 +From: "Kory Maincent (Dent Project)" +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) +Reviewed-by: Oleksij Rempel +Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-4-78a1a645e2ee@bootlin.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Carlo Szelinsky +--- + 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 + #include + ++#define PSE_PW_D_LIMIT INT_MAX ++ + static DEFINE_MUTEX(pse_list_mutex); + static LIST_HEAD(pse_controller_list); ++static DEFINE_XARRAY_ALLOC(pse_pw_d_map); ++static DEFINE_MUTEX(pse_pw_d_mutex); + + /** + * struct pse_control - a PSE control +@@ -38,6 +42,18 @@ struct pse_control { + struct phy_device *attached_phydev; + }; + ++/** ++ * struct pse_power_domain - a PSE power domain ++ * @id: ID of the power domain ++ * @supply: Power supply the Power Domain ++ * @refcnt: Number of gets of this pse_power_domain ++ */ ++struct pse_power_domain { ++ int id; ++ struct regulator *supply; ++ struct kref refcnt; ++}; ++ + static int of_load_single_pse_pi_pairset(struct device_node *node, + struct pse_pi *pi, + int pairset_num) +@@ -485,6 +501,125 @@ devm_pse_pi_regulator_register(struct ps + return 0; + } + ++static void __pse_pw_d_release(struct kref *kref) ++{ ++ struct pse_power_domain *pw_d = container_of(kref, ++ struct pse_power_domain, ++ refcnt); ++ ++ regulator_put(pw_d->supply); ++ xa_erase(&pse_pw_d_map, pw_d->id); ++ mutex_unlock(&pse_pw_d_mutex); ++} ++ ++/** ++ * pse_flush_pw_ds - flush all PSE power domains of a PSE ++ * @pcdev: a pointer to the initialized PSE controller device ++ */ ++static void pse_flush_pw_ds(struct pse_controller_dev *pcdev) ++{ ++ struct pse_power_domain *pw_d; ++ int i; ++ ++ for (i = 0; i < pcdev->nr_lines; i++) { ++ if (!pcdev->pi[i].pw_d) ++ continue; ++ ++ pw_d = xa_load(&pse_pw_d_map, pcdev->pi[i].pw_d->id); ++ if (!pw_d) ++ continue; ++ ++ kref_put_mutex(&pw_d->refcnt, __pse_pw_d_release, ++ &pse_pw_d_mutex); ++ } ++} ++ ++/** ++ * devm_pse_alloc_pw_d - allocate a new PSE power domain for a device ++ * @dev: device that is registering this PSE power domain ++ * ++ * Return: Pointer to the newly allocated PSE power domain or error pointers ++ */ ++static struct pse_power_domain *devm_pse_alloc_pw_d(struct device *dev) ++{ ++ struct pse_power_domain *pw_d; ++ int index, ret; ++ ++ pw_d = devm_kzalloc(dev, sizeof(*pw_d), GFP_KERNEL); ++ if (!pw_d) ++ return ERR_PTR(-ENOMEM); ++ ++ ret = xa_alloc(&pse_pw_d_map, &index, pw_d, XA_LIMIT(1, PSE_PW_D_LIMIT), ++ GFP_KERNEL); ++ if (ret) ++ return ERR_PTR(ret); ++ ++ kref_init(&pw_d->refcnt); ++ pw_d->id = index; ++ return pw_d; ++} ++ ++/** ++ * pse_register_pw_ds - register the PSE power domains for a PSE ++ * @pcdev: a pointer to the PSE controller device ++ * ++ * Return: 0 on success and failure value on error ++ */ ++static int pse_register_pw_ds(struct pse_controller_dev *pcdev) ++{ ++ int i, ret = 0; ++ ++ mutex_lock(&pse_pw_d_mutex); ++ for (i = 0; i < pcdev->nr_lines; i++) { ++ struct regulator_dev *rdev = pcdev->pi[i].rdev; ++ struct pse_power_domain *pw_d; ++ struct regulator *supply; ++ bool present = false; ++ unsigned long index; ++ ++ /* No regulator or regulator parent supply registered. ++ * We need a regulator parent to register a PSE power domain ++ */ ++ if (!rdev || !rdev->supply) ++ continue; ++ ++ xa_for_each(&pse_pw_d_map, index, pw_d) { ++ /* Power supply already registered as a PSE power ++ * domain. ++ */ ++ if (regulator_is_equal(pw_d->supply, rdev->supply)) { ++ present = true; ++ pcdev->pi[i].pw_d = pw_d; ++ break; ++ } ++ } ++ if (present) { ++ kref_get(&pw_d->refcnt); ++ continue; ++ } ++ ++ pw_d = devm_pse_alloc_pw_d(pcdev->dev); ++ if (IS_ERR(pw_d)) { ++ ret = PTR_ERR(pw_d); ++ goto out; ++ } ++ ++ supply = regulator_get(&rdev->dev, rdev->supply_name); ++ if (IS_ERR(supply)) { ++ xa_erase(&pse_pw_d_map, pw_d->id); ++ ret = PTR_ERR(supply); ++ goto out; ++ } ++ ++ pw_d->supply = supply; ++ pcdev->pi[i].pw_d = pw_d; ++ } ++ ++out: ++ mutex_unlock(&pse_pw_d_mutex); ++ return ret; ++} ++ + /** + * pse_controller_register - register a PSE controller device + * @pcdev: a pointer to the initialized PSE controller device +@@ -544,6 +679,10 @@ int pse_controller_register(struct pse_c + return ret; + } + ++ ret = pse_register_pw_ds(pcdev); ++ if (ret) ++ return ret; ++ + mutex_lock(&pse_list_mutex); + list_add(&pcdev->list, &pse_controller_list); + mutex_unlock(&pse_list_mutex); +@@ -558,6 +697,7 @@ EXPORT_SYMBOL_GPL(pse_controller_registe + */ + void pse_controller_unregister(struct pse_controller_dev *pcdev) + { ++ pse_flush_pw_ds(pcdev); + pse_release_pis(pcdev); + mutex_lock(&pse_list_mutex); + list_del(&pcdev->list); +--- a/include/linux/pse-pd/pse.h ++++ b/include/linux/pse-pd/pse.h +@@ -222,12 +222,14 @@ struct pse_pi_pairset { + * @np: device node pointer of the PSE PI node + * @rdev: regulator represented by the PSE PI + * @admin_state_enabled: PI enabled state ++ * @pw_d: Power domain of the PSE PI + */ + struct pse_pi { + struct pse_pi_pairset pairset[2]; + struct device_node *np; + struct regulator_dev *rdev; + bool admin_state_enabled; ++ struct pse_power_domain *pw_d; + }; + + /** diff --git a/target/linux/generic/backport-6.12/626-14-v6.17-net-pse-pd-ethtool-Add-support-for-power-domains-index.patch b/target/linux/generic/backport-6.12/626-14-v6.17-net-pse-pd-ethtool-Add-support-for-power-domains-index.patch new file mode 100644 index 00000000000..1124882a6a3 --- /dev/null +++ b/target/linux/generic/backport-6.12/626-14-v6.17-net-pse-pd-ethtool-Add-support-for-power-domains-index.patch @@ -0,0 +1,78 @@ +# ADAPTED FOR OPENWRT 6.12.67 - Documentation changes removed +# Original commit: 1176978ed851 +From 1176978ed851952652ddea3685e2f71a0e5d61ff Mon Sep 17 00:00:00 2001 +From: "Kory Maincent (Dent Project)" +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) +Reviewed-by: Oleksij Rempel +Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-5-78a1a645e2ee@bootlin.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Carlo Szelinsky +--- + Documentation/netlink/specs/ethtool.yaml | 5 +++++ + Documentation/networking/ethtool-netlink.rst | 4 ++++ + drivers/net/pse-pd/pse_core.c | 3 +++ + include/linux/pse-pd/pse.h | 2 ++ + include/uapi/linux/ethtool_netlink_generated.h | 1 + + net/ethtool/pse-pd.c | 7 +++++++ + 6 files changed, 22 insertions(+) + +--- a/drivers/net/pse-pd/pse_core.c ++++ b/drivers/net/pse-pd/pse_core.c +@@ -1098,6 +1098,9 @@ int pse_ethtool_get_status(struct pse_co + pcdev = psec->pcdev; + ops = pcdev->ops; + mutex_lock(&pcdev->lock); ++ if (pcdev->pi[psec->id].pw_d) ++ status->pw_d_id = pcdev->pi[psec->id].pw_d->id; ++ + ret = ops->pi_get_admin_state(pcdev, psec->id, &admin_state); + if (ret) + goto out; +--- a/include/linux/pse-pd/pse.h ++++ b/include/linux/pse-pd/pse.h +@@ -114,6 +114,7 @@ struct pse_pw_limit_ranges { + /** + * struct ethtool_pse_control_status - PSE control/channel status. + * ++ * @pw_d_id: PSE power domain index. + * @podl_admin_state: operational state of the PoDL PSE + * functions. IEEE 802.3-2018 30.15.1.1.2 aPoDLPSEAdminState + * @podl_pw_status: power detection status of the PoDL PSE. +@@ -135,6 +136,7 @@ struct pse_pw_limit_ranges { + * ranges + */ + struct ethtool_pse_control_status { ++ u32 pw_d_id; + enum ethtool_podl_pse_admin_state podl_admin_state; + enum ethtool_podl_pse_pw_d_status podl_pw_status; + enum ethtool_c33_pse_admin_state c33_admin_state; +--- a/net/ethtool/pse-pd.c ++++ b/net/ethtool/pse-pd.c +@@ -83,6 +83,8 @@ static int pse_reply_size(const struct e + const struct ethtool_pse_control_status *st = &data->status; + int len = 0; + ++ if (st->pw_d_id) ++ len += nla_total_size(sizeof(u32)); /* _PSE_PW_D_ID */ + if (st->podl_admin_state > 0) + len += nla_total_size(sizeof(u32)); /* _PODL_PSE_ADMIN_STATE */ + if (st->podl_pw_status > 0) +@@ -148,6 +150,11 @@ static int pse_fill_reply(struct sk_buff + const struct pse_reply_data *data = PSE_REPDATA(reply_base); + const struct ethtool_pse_control_status *st = &data->status; + ++ if (st->pw_d_id && ++ nla_put_u32(skb, ETHTOOL_A_PSE_PW_D_ID, ++ st->pw_d_id)) ++ return -EMSGSIZE; ++ + if (st->podl_admin_state > 0 && + nla_put_u32(skb, ETHTOOL_A_PODL_PSE_ADMIN_STATE, + st->podl_admin_state)) diff --git a/target/linux/generic/backport-6.12/626-15-v6.17-net-pse-pd-Add-helper-to-report-hw-enable-status.patch b/target/linux/generic/backport-6.12/626-15-v6.17-net-pse-pd-Add-helper-to-report-hw-enable-status.patch new file mode 100644 index 00000000000..ed512898162 --- /dev/null +++ b/target/linux/generic/backport-6.12/626-15-v6.17-net-pse-pd-Add-helper-to-report-hw-enable-status.patch @@ -0,0 +1,74 @@ +From c394e757dedd9cf947f9ac470d615d28fd2b07d1 Mon Sep 17 00:00:00 2001 +From: "Kory Maincent (Dent Project)" +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) +Reviewed-by: Oleksij Rempel +Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-6-78a1a645e2ee@bootlin.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Carlo Szelinsky +--- + drivers/net/pse-pd/pse_core.c | 36 +++++++++++++++++++++++++---------- + 1 file changed, 26 insertions(+), 10 deletions(-) + +--- a/drivers/net/pse-pd/pse_core.c ++++ b/drivers/net/pse-pd/pse_core.c +@@ -272,10 +272,34 @@ static struct net_device *pse_control_ge + return psec->attached_phydev->attached_dev; + } + ++/** ++ * pse_pi_is_hw_enabled - Is PI enabled at the hardware level ++ * @pcdev: a pointer to the PSE controller device ++ * @id: Index of the PI ++ * ++ * Return: 1 if the PI is enabled at the hardware level, 0 if not, and ++ * a failure value on error ++ */ ++static int pse_pi_is_hw_enabled(struct pse_controller_dev *pcdev, int id) ++{ ++ struct pse_admin_state admin_state = {0}; ++ int ret; ++ ++ ret = pcdev->ops->pi_get_admin_state(pcdev, id, &admin_state); ++ if (ret < 0) ++ return ret; ++ ++ /* PI is well enabled at the hardware level */ ++ if (admin_state.podl_admin_state == ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED || ++ admin_state.c33_admin_state == ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED) ++ return 1; ++ ++ return 0; ++} ++ + static int pse_pi_is_enabled(struct regulator_dev *rdev) + { + struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev); +- struct pse_admin_state admin_state = {0}; + const struct pse_controller_ops *ops; + int id, ret; + +@@ -285,15 +309,7 @@ static int pse_pi_is_enabled(struct regu + + id = rdev_get_id(rdev); + mutex_lock(&pcdev->lock); +- ret = ops->pi_get_admin_state(pcdev, id, &admin_state); +- if (ret) +- goto out; +- +- if (admin_state.podl_admin_state == ETHTOOL_PODL_PSE_ADMIN_STATE_ENABLED || +- admin_state.c33_admin_state == ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED) +- ret = 1; +- +-out: ++ ret = pse_pi_is_hw_enabled(pcdev, id); + mutex_unlock(&pcdev->lock); + + return ret; diff --git a/target/linux/generic/backport-6.12/626-16-v6.17-net-pse-pd-Add-support-for-budget-evaluation-strategies.patch b/target/linux/generic/backport-6.12/626-16-v6.17-net-pse-pd-Add-support-for-budget-evaluation-strategies.patch new file mode 100644 index 00000000000..c95d8f0fee3 --- /dev/null +++ b/target/linux/generic/backport-6.12/626-16-v6.17-net-pse-pd-Add-support-for-budget-evaluation-strategies.patch @@ -0,0 +1,1104 @@ +From ffef61d6d27374542f1bce4452200d9bdd2e1edd Mon Sep 17 00:00:00 2001 +From: "Kory Maincent (Dent Project)" +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) +Acked-by: Oleksij Rempel +Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-7-78a1a645e2ee@bootlin.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Carlo Szelinsky +--- + 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, ¬ifs_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, ¬ifs_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 ++#include ++#include + #include + #include + #include +@@ -134,6 +136,9 @@ struct pse_pw_limit_ranges { + * is in charge of the memory allocation + * @c33_pw_limit_nb_ranges: number of supported power limit configuration + * ranges ++ * @prio_max: max priority allowed for the c33_prio variable value. ++ * @prio: priority of the PSE. Managed by PSE core in case of static budget ++ * evaluation strategy. + */ + struct ethtool_pse_control_status { + u32 pw_d_id; +@@ -147,6 +152,8 @@ struct ethtool_pse_control_status { + u32 c33_avail_pw_limit; + struct ethtool_c33_pse_pw_limit_range *c33_pw_limit_ranges; + u32 c33_pw_limit_nb_ranges; ++ u32 prio_max; ++ u32 prio; + }; + + /** +@@ -170,6 +177,11 @@ struct ethtool_pse_control_status { + * range. The driver is in charge of the memory + * allocation and should return the number of + * ranges. ++ * @pi_get_prio: Get the PSE PI priority. ++ * @pi_set_prio: Configure the PSE PI priority. ++ * @pi_get_pw_req: Get the power requested by a PD before enabling the PSE PI. ++ * This is only relevant when an interrupt is registered using ++ * devm_pse_irq_helper helper. + */ + struct pse_controller_ops { + int (*setup_pi_matrix)(struct pse_controller_dev *pcdev); +@@ -190,6 +202,10 @@ struct pse_controller_ops { + int id, int max_mW); + int (*pi_get_pw_limit_ranges)(struct pse_controller_dev *pcdev, int id, + struct pse_pw_limit_ranges *pw_limit_ranges); ++ int (*pi_get_prio)(struct pse_controller_dev *pcdev, int id); ++ int (*pi_set_prio)(struct pse_controller_dev *pcdev, int id, ++ unsigned int prio); ++ int (*pi_get_pw_req)(struct pse_controller_dev *pcdev, int id); + }; + + struct module; +@@ -225,6 +241,13 @@ struct pse_pi_pairset { + * @rdev: regulator represented by the PSE PI + * @admin_state_enabled: PI enabled state + * @pw_d: Power domain of the PSE PI ++ * @prio: Priority of the PSE PI. Used in static budget evaluation strategy ++ * @isr_pd_detected: PSE PI detection status managed by the interruption ++ * handler. This variable is relevant when the power enabled ++ * management is managed in software like the static ++ * budget evaluation strategy. ++ * @pw_allocated_mW: Power allocated to a PSE PI to manage power budget in ++ * static budget evaluation strategy. + */ + struct pse_pi { + struct pse_pi_pairset pairset[2]; +@@ -232,6 +255,20 @@ struct pse_pi { + struct regulator_dev *rdev; + bool admin_state_enabled; + struct pse_power_domain *pw_d; ++ int prio; ++ bool isr_pd_detected; ++ int pw_allocated_mW; ++}; ++ ++/** ++ * struct pse_ntf - PSE notification element ++ * ++ * @id: ID of the PSE control ++ * @notifs: PSE notifications to be reported ++ */ ++struct pse_ntf { ++ int id; ++ unsigned long notifs; + }; + + /** +@@ -249,6 +286,12 @@ struct pse_pi { + * @pi: table of PSE PIs described in this controller device + * @no_of_pse_pi: flag set if the pse_pis devicetree node is not used + * @irq: PSE interrupt ++ * @pis_prio_max: Maximum value allowed for the PSE PIs priority ++ * @supp_budget_eval_strategies: budget evaluation strategies supported ++ * by the PSE ++ * @ntf_work: workqueue for PSE notification management ++ * @ntf_fifo: PSE notifications FIFO ++ * @ntf_fifo_lock: protect @ntf_fifo writer + */ + struct pse_controller_dev { + const struct pse_controller_ops *ops; +@@ -263,6 +306,29 @@ struct pse_controller_dev { + struct pse_pi *pi; + bool no_of_pse_pi; + int irq; ++ unsigned int pis_prio_max; ++ u32 supp_budget_eval_strategies; ++ struct work_struct ntf_work; ++ DECLARE_KFIFO_PTR(ntf_fifo, struct pse_ntf); ++ spinlock_t ntf_fifo_lock; /* Protect @ntf_fifo writer */ ++}; ++ ++/** ++ * enum pse_budget_eval_strategies - PSE budget evaluation strategies. ++ * @PSE_BUDGET_EVAL_STRAT_DISABLED: Budget evaluation strategy disabled. ++ * @PSE_BUDGET_EVAL_STRAT_STATIC: PSE static budget evaluation strategy. ++ * Budget evaluation strategy based on the power requested during PD ++ * classification. This strategy is managed by the PSE core. ++ * @PSE_BUDGET_EVAL_STRAT_DYNAMIC: PSE dynamic budget evaluation ++ * strategy. Budget evaluation strategy based on the current consumption ++ * per ports compared to the total power budget. This mode is managed by ++ * the PSE controller. ++ */ ++ ++enum pse_budget_eval_strategies { ++ PSE_BUDGET_EVAL_STRAT_DISABLED = 1 << 0, ++ PSE_BUDGET_EVAL_STRAT_STATIC = 1 << 1, ++ PSE_BUDGET_EVAL_STRAT_DYNAMIC = 1 << 2, + }; + + #if IS_ENABLED(CONFIG_PSE_CONTROLLER) +@@ -287,6 +353,9 @@ int pse_ethtool_set_config(struct pse_co + int pse_ethtool_set_pw_limit(struct pse_control *psec, + struct netlink_ext_ack *extack, + const unsigned int pw_limit); ++int pse_ethtool_set_prio(struct pse_control *psec, ++ struct netlink_ext_ack *extack, ++ unsigned int prio); + int pse_ethtool_get_pw_limit(struct pse_control *psec, + struct netlink_ext_ack *extack); + +@@ -331,6 +400,13 @@ static inline int pse_ethtool_get_pw_lim + { + return -EOPNOTSUPP; + } ++ ++static inline int pse_ethtool_set_prio(struct pse_control *psec, ++ struct netlink_ext_ack *extack, ++ unsigned int prio) ++{ ++ return -EOPNOTSUPP; ++} + + static inline bool pse_has_podl(struct pse_control *psec) + { diff --git a/target/linux/generic/backport-6.12/626-17-v6.17-net-pse-pd-pd692x0-Add-PSE-PI-priority-feature.patch b/target/linux/generic/backport-6.12/626-17-v6.17-net-pse-pd-pd692x0-Add-PSE-PI-priority-feature.patch new file mode 100644 index 00000000000..6e1f2d63c9a --- /dev/null +++ b/target/linux/generic/backport-6.12/626-17-v6.17-net-pse-pd-pd692x0-Add-PSE-PI-priority-feature.patch @@ -0,0 +1,326 @@ +From 359754013e6a7fc81af6735ebbfedd4a01999f68 Mon Sep 17 00:00:00 2001 +From: "Kory Maincent (Dent Project)" +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) +Reviewed-by: Oleksij Rempel +Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-9-78a1a645e2ee@bootlin.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Carlo Szelinsky +--- + 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 + #include + #include ++#include ++#include + + #define PD692X0_PSE_NAME "pd692x0_pse" + +@@ -76,6 +78,8 @@ enum { + PD692X0_MSG_GET_PORT_CLASS, + PD692X0_MSG_GET_PORT_MEAS, + PD692X0_MSG_GET_PORT_PARAM, ++ PD692X0_MSG_GET_POWER_BANK, ++ PD692X0_MSG_SET_POWER_BANK, + + /* add new message above here */ + PD692X0_MSG_CNT +@@ -95,6 +99,8 @@ struct pd692x0_priv { + unsigned long last_cmd_key_time; + + enum ethtool_c33_pse_admin_state admin_state[PD692X0_MAX_PIS]; ++ struct regulator_dev *manager_reg[PD692X0_MAX_MANAGERS]; ++ int manager_pw_budget[PD692X0_MAX_MANAGERS]; + }; + + /* Template list of communication messages. The non-null bytes defined here +@@ -170,6 +176,16 @@ static const struct pd692x0_msg pd692x0_ + .data = {0x4e, 0x4e, 0x4e, 0x4e, + 0x4e, 0x4e, 0x4e, 0x4e}, + }, ++ [PD692X0_MSG_GET_POWER_BANK] = { ++ .key = PD692X0_KEY_REQ, ++ .sub = {0x07, 0x0b, 0x57}, ++ .data = { 0, 0x4e, 0x4e, 0x4e, ++ 0x4e, 0x4e, 0x4e, 0x4e}, ++ }, ++ [PD692X0_MSG_SET_POWER_BANK] = { ++ .key = PD692X0_KEY_CMD, ++ .sub = {0x07, 0x0b, 0x57}, ++ }, + }; + + static u8 pd692x0_build_msg(struct pd692x0_msg *msg, u8 echo) +@@ -739,6 +755,29 @@ pd692x0_pi_get_actual_pw(struct pse_cont + return (buf.data[0] << 4 | buf.data[1]) * 100; + } + ++static int ++pd692x0_pi_get_prio(struct pse_controller_dev *pcdev, int id) ++{ ++ struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); ++ struct pd692x0_msg msg, buf = {0}; ++ int ret; ++ ++ ret = pd692x0_fw_unavailable(priv); ++ if (ret) ++ return ret; ++ ++ msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_PARAM]; ++ msg.sub[2] = id; ++ ret = pd692x0_sendrecv_msg(priv, &msg, &buf); ++ if (ret < 0) ++ return ret; ++ if (!buf.data[2] || buf.data[2] > pcdev->pis_prio_max + 1) ++ return -ERANGE; ++ ++ /* PSE core priority start at 0 */ ++ return buf.data[2] - 1; ++} ++ + static struct pd692x0_msg_ver pd692x0_get_sw_version(struct pd692x0_priv *priv) + { + struct device *dev = &priv->client->dev; +@@ -766,6 +805,7 @@ static struct pd692x0_msg_ver pd692x0_ge + + struct pd692x0_manager { + struct device_node *port_node[PD692X0_MAX_MANAGER_PORTS]; ++ struct device_node *node; + int nports; + }; + +@@ -857,6 +897,8 @@ pd692x0_of_get_managers(struct pd692x0_p + if (ret) + goto out; + ++ of_node_get(node); ++ manager[manager_id].node = node; + nmanagers++; + } + +@@ -869,6 +911,8 @@ out: + of_node_put(manager[i].port_node[j]); + manager[i].port_node[j] = NULL; + } ++ of_node_put(manager[i].node); ++ manager[i].node = NULL; + } + + of_node_put(node); +@@ -876,6 +920,130 @@ out: + return ret; + } + ++static const struct regulator_ops dummy_ops; ++ ++static struct regulator_dev * ++pd692x0_register_manager_regulator(struct device *dev, char *reg_name, ++ struct device_node *node) ++{ ++ struct regulator_init_data *rinit_data; ++ struct regulator_config rconfig = {0}; ++ struct regulator_desc *rdesc; ++ struct regulator_dev *rdev; ++ ++ rinit_data = devm_kzalloc(dev, sizeof(*rinit_data), ++ GFP_KERNEL); ++ if (!rinit_data) ++ return ERR_PTR(-ENOMEM); ++ ++ rdesc = devm_kzalloc(dev, sizeof(*rdesc), GFP_KERNEL); ++ if (!rdesc) ++ return ERR_PTR(-ENOMEM); ++ ++ rdesc->name = reg_name; ++ rdesc->type = REGULATOR_VOLTAGE; ++ rdesc->ops = &dummy_ops; ++ rdesc->owner = THIS_MODULE; ++ ++ rinit_data->supply_regulator = "vmain"; ++ ++ rconfig.dev = dev; ++ rconfig.init_data = rinit_data; ++ rconfig.of_node = node; ++ ++ rdev = devm_regulator_register(dev, rdesc, &rconfig); ++ if (IS_ERR(rdev)) { ++ dev_err_probe(dev, PTR_ERR(rdev), ++ "Failed to register regulator\n"); ++ return rdev; ++ } ++ ++ return rdev; ++} ++ ++static int ++pd692x0_register_managers_regulator(struct pd692x0_priv *priv, ++ const struct pd692x0_manager *manager, ++ int nmanagers) ++{ ++ struct device *dev = &priv->client->dev; ++ size_t reg_name_len; ++ int i; ++ ++ /* Each regulator name len is dev name + 12 char + ++ * int max digit number (10) + 1 ++ */ ++ reg_name_len = strlen(dev_name(dev)) + 23; ++ ++ for (i = 0; i < nmanagers; i++) { ++ struct regulator_dev *rdev; ++ char *reg_name; ++ ++ reg_name = devm_kzalloc(dev, reg_name_len, GFP_KERNEL); ++ if (!reg_name) ++ return -ENOMEM; ++ snprintf(reg_name, 26, "pse-%s-manager%d", dev_name(dev), i); ++ rdev = pd692x0_register_manager_regulator(dev, reg_name, ++ manager[i].node); ++ if (IS_ERR(rdev)) ++ return PTR_ERR(rdev); ++ ++ priv->manager_reg[i] = rdev; ++ } ++ ++ return 0; ++} ++ ++static int ++pd692x0_conf_manager_power_budget(struct pd692x0_priv *priv, int id, int pw) ++{ ++ struct pd692x0_msg msg, buf; ++ int ret, pw_mW = pw / 1000; ++ ++ msg = pd692x0_msg_template_list[PD692X0_MSG_GET_POWER_BANK]; ++ msg.data[0] = id; ++ ret = pd692x0_sendrecv_msg(priv, &msg, &buf); ++ if (ret < 0) ++ return ret; ++ ++ msg = pd692x0_msg_template_list[PD692X0_MSG_SET_POWER_BANK]; ++ msg.data[0] = id; ++ msg.data[1] = pw_mW >> 8; ++ msg.data[2] = pw_mW & 0xff; ++ msg.data[3] = buf.sub[2]; ++ msg.data[4] = buf.data[0]; ++ msg.data[5] = buf.data[1]; ++ msg.data[6] = buf.data[2]; ++ msg.data[7] = buf.data[3]; ++ return pd692x0_sendrecv_msg(priv, &msg, &buf); ++} ++ ++static int ++pd692x0_configure_managers(struct pd692x0_priv *priv, int nmanagers) ++{ ++ int i, ret; ++ ++ for (i = 0; i < nmanagers; i++) { ++ struct regulator *supply = priv->manager_reg[i]->supply; ++ int pw_budget; ++ ++ pw_budget = regulator_get_unclaimed_power_budget(supply); ++ /* Max power budget per manager */ ++ if (pw_budget > 6000000) ++ pw_budget = 6000000; ++ ret = regulator_request_power_budget(supply, pw_budget); ++ if (ret < 0) ++ return ret; ++ ++ priv->manager_pw_budget[i] = pw_budget; ++ ret = pd692x0_conf_manager_power_budget(priv, i, pw_budget); ++ if (ret < 0) ++ return ret; ++ } ++ ++ return 0; ++} ++ + static int + pd692x0_set_port_matrix(const struct pse_pi_pairset *pairset, + const struct pd692x0_manager *manager, +@@ -998,6 +1166,14 @@ static int pd692x0_setup_pi_matrix(struc + return ret; + + nmanagers = ret; ++ ret = pd692x0_register_managers_regulator(priv, manager, nmanagers); ++ if (ret) ++ goto out; ++ ++ ret = pd692x0_configure_managers(priv, nmanagers); ++ if (ret) ++ goto out; ++ + ret = pd692x0_set_ports_matrix(priv, manager, nmanagers, port_matrix); + if (ret) + goto out; +@@ -1008,8 +1184,14 @@ static int pd692x0_setup_pi_matrix(struc + + out: + for (i = 0; i < nmanagers; i++) { ++ struct regulator *supply = priv->manager_reg[i]->supply; ++ ++ regulator_free_power_budget(supply, ++ priv->manager_pw_budget[i]); ++ + for (j = 0; j < manager[i].nports; j++) + of_node_put(manager[i].port_node[j]); ++ of_node_put(manager[i].node); + } + return ret; + } +@@ -1071,6 +1253,25 @@ static int pd692x0_pi_set_pw_limit(struc + return pd692x0_sendrecv_msg(priv, &msg, &buf); + } + ++static int pd692x0_pi_set_prio(struct pse_controller_dev *pcdev, int id, ++ unsigned int prio) ++{ ++ struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); ++ struct pd692x0_msg msg, buf = {0}; ++ int ret; ++ ++ ret = pd692x0_fw_unavailable(priv); ++ if (ret) ++ return ret; ++ ++ msg = pd692x0_msg_template_list[PD692X0_MSG_SET_PORT_PARAM]; ++ msg.sub[2] = id; ++ /* Controller priority from 1 to 3 */ ++ msg.data[4] = prio + 1; ++ ++ return pd692x0_sendrecv_msg(priv, &msg, &buf); ++} ++ + static const struct pse_controller_ops pd692x0_ops = { + .setup_pi_matrix = pd692x0_setup_pi_matrix, + .pi_get_admin_state = pd692x0_pi_get_admin_state, +@@ -1084,6 +1285,8 @@ static const struct pse_controller_ops p + .pi_get_pw_limit = pd692x0_pi_get_pw_limit, + .pi_set_pw_limit = pd692x0_pi_set_pw_limit, + .pi_get_pw_limit_ranges = pd692x0_pi_get_pw_limit_ranges, ++ .pi_get_prio = pd692x0_pi_get_prio, ++ .pi_set_prio = pd692x0_pi_set_prio, + }; + + #define PD692X0_FW_LINE_MAX_SZ 0xff +@@ -1500,6 +1703,8 @@ static int pd692x0_i2c_probe(struct i2c_ + priv->pcdev.ops = &pd692x0_ops; + priv->pcdev.dev = dev; + priv->pcdev.types = ETHTOOL_PSE_C33; ++ priv->pcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_DYNAMIC; ++ priv->pcdev.pis_prio_max = 2; + ret = devm_pse_controller_register(dev, &priv->pcdev); + if (ret) + return dev_err_probe(dev, ret, diff --git a/target/linux/generic/backport-6.12/626-18-v6.17-net-pse-pd-pd692x0-Add-controller-and-manager-power.patch b/target/linux/generic/backport-6.12/626-18-v6.17-net-pse-pd-pd692x0-Add-controller-and-manager-power.patch new file mode 100644 index 00000000000..a93dca09995 --- /dev/null +++ b/target/linux/generic/backport-6.12/626-18-v6.17-net-pse-pd-pd692x0-Add-controller-and-manager-power.patch @@ -0,0 +1,71 @@ +From 24a4e3a05dd0eadd0c9585c411880e5dcb6be97f Mon Sep 17 00:00:00 2001 +From: "Kory Maincent (Dent Project)" +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) +Reviewed-by: Oleksij Rempel +Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-10-78a1a645e2ee@bootlin.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Carlo Szelinsky +--- + drivers/net/pse-pd/pd692x0.c | 20 ++++++++++++++++++++ + 1 file changed, 20 insertions(+) + +--- a/drivers/net/pse-pd/pd692x0.c ++++ b/drivers/net/pse-pd/pd692x0.c +@@ -976,8 +976,10 @@ pd692x0_register_managers_regulator(stru + reg_name_len = strlen(dev_name(dev)) + 23; + + for (i = 0; i < nmanagers; i++) { ++ static const char * const regulators[] = { "vaux5", "vaux3p3" }; + struct regulator_dev *rdev; + char *reg_name; ++ int ret; + + reg_name = devm_kzalloc(dev, reg_name_len, GFP_KERNEL); + if (!reg_name) +@@ -988,6 +990,17 @@ pd692x0_register_managers_regulator(stru + if (IS_ERR(rdev)) + return PTR_ERR(rdev); + ++ /* VMAIN is described as main supply for the manager. ++ * Add other VAUX power supplies and link them to the ++ * virtual device rdev->dev. ++ */ ++ ret = devm_regulator_bulk_get_enable(&rdev->dev, ++ ARRAY_SIZE(regulators), ++ regulators); ++ if (ret) ++ return dev_err_probe(&rdev->dev, ret, ++ "Failed to enable regulators\n"); ++ + priv->manager_reg[i] = rdev; + } + +@@ -1640,6 +1653,7 @@ static const struct fw_upload_ops pd692x + + static int pd692x0_i2c_probe(struct i2c_client *client) + { ++ static const char * const regulators[] = { "vdd", "vdda" }; + struct pd692x0_msg msg, buf = {0}, zero = {0}; + struct device *dev = &client->dev; + struct pd692x0_msg_ver ver; +@@ -1647,6 +1661,12 @@ static int pd692x0_i2c_probe(struct i2c_ + struct fw_upload *fwl; + int ret; + ++ ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulators), ++ regulators); ++ if (ret) ++ return dev_err_probe(dev, ret, ++ "Failed to enable regulators\n"); ++ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(dev, "i2c check functionality failed\n"); + return -ENXIO; diff --git a/target/linux/generic/backport-6.12/626-19-v6.17-net-pse-pd-tps23881-Add-static-port-priority-feature.patch b/target/linux/generic/backport-6.12/626-19-v6.17-net-pse-pd-tps23881-Add-static-port-priority-feature.patch new file mode 100644 index 00000000000..739233a12e3 --- /dev/null +++ b/target/linux/generic/backport-6.12/626-19-v6.17-net-pse-pd-tps23881-Add-static-port-priority-feature.patch @@ -0,0 +1,392 @@ +From 56cfc97635e9164395c9242f72746454347155ab Mon Sep 17 00:00:00 2001 +From: "Kory Maincent (Dent Project)" +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) +Reviewed-by: Oleksij Rempel +Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-12-78a1a645e2ee@bootlin.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Carlo Szelinsky +--- + drivers/net/pse-pd/tps23881.c | 244 +++++++++++++++++++++++++++++++--- + 1 file changed, 228 insertions(+), 16 deletions(-) + +--- a/drivers/net/pse-pd/tps23881.c ++++ b/drivers/net/pse-pd/tps23881.c +@@ -20,20 +20,30 @@ + + #define TPS23881_REG_IT 0x0 + #define TPS23881_REG_IT_MASK 0x1 ++#define TPS23881_REG_IT_DISF BIT(2) ++#define TPS23881_REG_IT_DETC BIT(3) ++#define TPS23881_REG_IT_CLASC BIT(4) + #define TPS23881_REG_IT_IFAULT BIT(5) + #define TPS23881_REG_IT_SUPF BIT(7) ++#define TPS23881_REG_DET_EVENT 0x5 + #define TPS23881_REG_FAULT 0x7 + #define TPS23881_REG_SUPF_EVENT 0xb + #define TPS23881_REG_TSD BIT(7) ++#define TPS23881_REG_DISC 0xc + #define TPS23881_REG_PW_STATUS 0x10 + #define TPS23881_REG_OP_MODE 0x12 ++#define TPS23881_REG_DISC_EN 0x13 + #define TPS23881_OP_MODE_SEMIAUTO 0xaaaa + #define TPS23881_REG_DIS_EN 0x13 + #define TPS23881_REG_DET_CLA_EN 0x14 + #define TPS23881_REG_GEN_MASK 0x17 ++#define TPS23881_REG_CLCHE BIT(2) ++#define TPS23881_REG_DECHE BIT(3) + #define TPS23881_REG_NBITACC BIT(5) + #define TPS23881_REG_INTEN BIT(7) + #define TPS23881_REG_PW_EN 0x19 ++#define TPS23881_REG_RESET 0x1a ++#define TPS23881_REG_CLRAIN BIT(7) + #define TPS23881_REG_2PAIR_POL1 0x1e + #define TPS23881_REG_PORT_MAP 0x26 + #define TPS23881_REG_PORT_POWER 0x29 +@@ -178,6 +188,7 @@ static int tps23881_pi_enable(struct pse + struct i2c_client *client = priv->client; + u8 chan; + u16 val; ++ int ret; + + if (id >= TPS23881_MAX_CHANS) + return -ERANGE; +@@ -191,7 +202,22 @@ static int tps23881_pi_enable(struct pse + BIT(chan % 4)); + } + +- return i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val); ++ ret = i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val); ++ if (ret) ++ return ret; ++ ++ /* Enable DC disconnect*/ ++ chan = priv->port[id].chan[0]; ++ ret = i2c_smbus_read_word_data(client, TPS23881_REG_DISC_EN); ++ if (ret < 0) ++ return ret; ++ ++ val = tps23881_set_val(ret, chan, 0, BIT(chan % 4), BIT(chan % 4)); ++ ret = i2c_smbus_write_word_data(client, TPS23881_REG_DISC_EN, val); ++ if (ret) ++ return ret; ++ ++ return 0; + } + + static int tps23881_pi_disable(struct pse_controller_dev *pcdev, int id) +@@ -224,6 +250,17 @@ static int tps23881_pi_disable(struct ps + */ + mdelay(5); + ++ /* Disable DC disconnect*/ ++ chan = priv->port[id].chan[0]; ++ ret = i2c_smbus_read_word_data(client, TPS23881_REG_DISC_EN); ++ if (ret < 0) ++ return ret; ++ ++ val = tps23881_set_val(ret, chan, 0, 0, BIT(chan % 4)); ++ ret = i2c_smbus_write_word_data(client, TPS23881_REG_DISC_EN, val); ++ if (ret) ++ return ret; ++ + /* Enable detection and classification */ + ret = i2c_smbus_read_word_data(client, TPS23881_REG_DET_CLA_EN); + if (ret < 0) +@@ -919,6 +956,47 @@ static int tps23881_setup_pi_matrix(stru + return ret; + } + ++static int tps23881_power_class_table[] = { ++ -ERANGE, ++ 4000, ++ 7000, ++ 15500, ++ 30000, ++ 15500, ++ 15500, ++ -ERANGE, ++ 45000, ++ 60000, ++ 75000, ++ 90000, ++ 15500, ++ 45000, ++ -ERANGE, ++ -ERANGE, ++}; ++ ++static int tps23881_pi_get_pw_req(struct pse_controller_dev *pcdev, int id) ++{ ++ struct tps23881_priv *priv = to_tps23881_priv(pcdev); ++ struct i2c_client *client = priv->client; ++ u8 reg, chan; ++ int ret; ++ u16 val; ++ ++ /* For a 4-pair the classification need 5ms to be completed */ ++ if (priv->port[id].is_4p) ++ mdelay(5); ++ ++ chan = priv->port[id].chan[0]; ++ reg = TPS23881_REG_DISC + (chan % 4); ++ ret = i2c_smbus_read_word_data(client, reg); ++ if (ret < 0) ++ return ret; ++ ++ val = tps23881_calc_val(ret, chan, 4, 0xf); ++ return tps23881_power_class_table[val]; ++} ++ + static const struct pse_controller_ops tps23881_ops = { + .setup_pi_matrix = tps23881_setup_pi_matrix, + .pi_enable = tps23881_pi_enable, +@@ -931,6 +1009,7 @@ static const struct pse_controller_ops t + .pi_get_pw_limit = tps23881_pi_get_pw_limit, + .pi_set_pw_limit = tps23881_pi_set_pw_limit, + .pi_get_pw_limit_ranges = tps23881_pi_get_pw_limit_ranges, ++ .pi_get_pw_req = tps23881_pi_get_pw_req, + }; + + static const char fw_parity_name[] = "ti/tps23881/tps23881-parity-14.bin"; +@@ -1088,17 +1167,113 @@ static void tps23881_irq_event_over_temp + } + } + +-static void tps23881_irq_event_over_current(struct tps23881_priv *priv, +- u16 reg_val, +- unsigned long *notifs, +- unsigned long *notifs_mask) ++static int tps23881_irq_event_over_current(struct tps23881_priv *priv, ++ u16 reg_val, ++ unsigned long *notifs, ++ unsigned long *notifs_mask) + { ++ int i, ret; + u8 chans; + + chans = tps23881_irq_export_chans_helper(reg_val, 0); ++ if (!chans) ++ return 0; ++ ++ tps23881_set_notifs_helper(priv, chans, notifs, notifs_mask, ++ ETHTOOL_PSE_EVENT_OVER_CURRENT | ++ ETHTOOL_C33_PSE_EVENT_DISCONNECTION); ++ ++ /* Over Current event resets the power limit registers so we need ++ * to configured it again. ++ */ ++ for_each_set_bit(i, notifs_mask, priv->pcdev.nr_lines) { ++ if (priv->port[i].pw_pol < 0) ++ continue; ++ ++ ret = tps23881_pi_enable_manual_pol(priv, i); ++ if (ret < 0) ++ return ret; ++ ++ /* Set power policy */ ++ ret = tps23881_pi_set_pw_pol_limit(priv, i, ++ priv->port[i].pw_pol, ++ priv->port[i].is_4p); ++ if (ret < 0) ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void tps23881_irq_event_disconnection(struct tps23881_priv *priv, ++ u16 reg_val, ++ unsigned long *notifs, ++ unsigned long *notifs_mask) ++{ ++ u8 chans; ++ ++ chans = tps23881_irq_export_chans_helper(reg_val, 4); + if (chans) + tps23881_set_notifs_helper(priv, chans, notifs, notifs_mask, +- ETHTOOL_PSE_EVENT_OVER_CURRENT); ++ ETHTOOL_C33_PSE_EVENT_DISCONNECTION); ++} ++ ++static int tps23881_irq_event_detection(struct tps23881_priv *priv, ++ u16 reg_val, ++ unsigned long *notifs, ++ unsigned long *notifs_mask) ++{ ++ enum ethtool_pse_event event; ++ int reg, ret, i, val; ++ unsigned long chans; ++ ++ chans = tps23881_irq_export_chans_helper(reg_val, 0); ++ for_each_set_bit(i, &chans, TPS23881_MAX_CHANS) { ++ reg = TPS23881_REG_DISC + (i % 4); ++ ret = i2c_smbus_read_word_data(priv->client, reg); ++ if (ret < 0) ++ return ret; ++ ++ val = tps23881_calc_val(ret, i, 0, 0xf); ++ /* If detection valid */ ++ if (val == 0x4) ++ event = ETHTOOL_C33_PSE_EVENT_DETECTION; ++ else ++ event = ETHTOOL_C33_PSE_EVENT_DISCONNECTION; ++ ++ tps23881_set_notifs_helper(priv, BIT(i), notifs, ++ notifs_mask, event); ++ } ++ ++ return 0; ++} ++ ++static int tps23881_irq_event_classification(struct tps23881_priv *priv, ++ u16 reg_val, ++ unsigned long *notifs, ++ unsigned long *notifs_mask) ++{ ++ int reg, ret, val, i; ++ unsigned long chans; ++ ++ chans = tps23881_irq_export_chans_helper(reg_val, 4); ++ for_each_set_bit(i, &chans, TPS23881_MAX_CHANS) { ++ reg = TPS23881_REG_DISC + (i % 4); ++ ret = i2c_smbus_read_word_data(priv->client, reg); ++ if (ret < 0) ++ return ret; ++ ++ val = tps23881_calc_val(ret, i, 4, 0xf); ++ /* Do not report classification event for unknown class */ ++ if (!val || val == 0x8 || val == 0xf) ++ continue; ++ ++ tps23881_set_notifs_helper(priv, BIT(i), notifs, ++ notifs_mask, ++ ETHTOOL_C33_PSE_EVENT_CLASSIFICATION); ++ } ++ ++ return 0; + } + + static int tps23881_irq_event_handler(struct tps23881_priv *priv, u16 reg, +@@ -1106,7 +1281,7 @@ static int tps23881_irq_event_handler(st + unsigned long *notifs_mask) + { + struct i2c_client *client = priv->client; +- int ret; ++ int ret, val; + + /* The Supply event bit is repeated twice so we only need to read + * the one from the first byte. +@@ -1118,13 +1293,36 @@ static int tps23881_irq_event_handler(st + tps23881_irq_event_over_temp(priv, ret, notifs, notifs_mask); + } + +- if (reg & (TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_IFAULT << 8)) { ++ if (reg & (TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_IFAULT << 8 | ++ TPS23881_REG_IT_DISF | TPS23881_REG_IT_DISF << 8)) { + ret = i2c_smbus_read_word_data(client, TPS23881_REG_FAULT); + if (ret < 0) + return ret; +- tps23881_irq_event_over_current(priv, ret, notifs, notifs_mask); ++ ret = tps23881_irq_event_over_current(priv, ret, notifs, ++ notifs_mask); ++ if (ret) ++ return ret; ++ ++ tps23881_irq_event_disconnection(priv, ret, notifs, notifs_mask); + } + ++ if (reg & (TPS23881_REG_IT_DETC | TPS23881_REG_IT_DETC << 8 | ++ TPS23881_REG_IT_CLASC | TPS23881_REG_IT_CLASC << 8)) { ++ ret = i2c_smbus_read_word_data(client, TPS23881_REG_DET_EVENT); ++ if (ret < 0) ++ return ret; ++ ++ val = ret; ++ ret = tps23881_irq_event_detection(priv, val, notifs, ++ notifs_mask); ++ if (ret) ++ return ret; ++ ++ ret = tps23881_irq_event_classification(priv, val, notifs, ++ notifs_mask); ++ if (ret) ++ return ret; ++ } + return 0; + } + +@@ -1178,7 +1376,14 @@ static int tps23881_setup_irq(struct tps + int ret; + u16 val; + +- val = TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_SUPF; ++ if (!irq) { ++ dev_err(&client->dev, "interrupt is missing"); ++ return -EINVAL; ++ } ++ ++ val = TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_SUPF | ++ TPS23881_REG_IT_DETC | TPS23881_REG_IT_CLASC | ++ TPS23881_REG_IT_DISF; + val |= val << 8; + ret = i2c_smbus_write_word_data(client, TPS23881_REG_IT_MASK, val); + if (ret) +@@ -1188,11 +1393,19 @@ static int tps23881_setup_irq(struct tps + if (ret < 0) + return ret; + +- val = (u16)(ret | TPS23881_REG_INTEN | TPS23881_REG_INTEN << 8); ++ val = TPS23881_REG_INTEN | TPS23881_REG_CLCHE | TPS23881_REG_DECHE; ++ val |= val << 8; ++ val |= (u16)ret; + ret = i2c_smbus_write_word_data(client, TPS23881_REG_GEN_MASK, val); + if (ret < 0) + return ret; + ++ /* Reset interrupts registers */ ++ ret = i2c_smbus_write_word_data(client, TPS23881_REG_RESET, ++ TPS23881_REG_CLRAIN); ++ if (ret < 0) ++ return ret; ++ + return devm_pse_irq_helper(&priv->pcdev, irq, 0, &irq_desc); + } + +@@ -1270,17 +1483,16 @@ static int tps23881_i2c_probe(struct i2c + priv->pcdev.dev = dev; + priv->pcdev.types = ETHTOOL_PSE_C33; + priv->pcdev.nr_lines = TPS23881_MAX_CHANS; ++ priv->pcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_STATIC; + ret = devm_pse_controller_register(dev, &priv->pcdev); + if (ret) { + return dev_err_probe(dev, ret, + "failed to register PSE controller\n"); + } + +- if (client->irq) { +- ret = tps23881_setup_irq(priv, client->irq); +- if (ret) +- return ret; +- } ++ ret = tps23881_setup_irq(priv, client->irq); ++ if (ret) ++ return ret; + + return ret; + } diff --git a/target/linux/generic/backport-6.12/626-20-v6.17-net-pse-pd-pd692x0-reduce-stack-usage.patch b/target/linux/generic/backport-6.12/626-20-v6.17-net-pse-pd-pd692x0-reduce-stack-usage.patch new file mode 100644 index 00000000000..d5e1aaf9a32 --- /dev/null +++ b/target/linux/generic/backport-6.12/626-20-v6.17-net-pse-pd-pd692x0-reduce-stack-usage.patch @@ -0,0 +1,55 @@ +From d12b3dc106090b358fb67b7c0c717a0884327ddf Mon Sep 17 00:00:00 2001 +From: Arnd Bergmann +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 +Reviewed-by: Kory Maincent +Link: https://patch.msgid.link/20250709153210.1920125-1-arnd@kernel.org +Signed-off-by: Jakub Kicinski +Signed-off-by: Carlo Szelinsky +--- + drivers/net/pse-pd/pd692x0.c | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) +--- a/drivers/net/pse-pd/pd692x0.c ++++ b/drivers/net/pse-pd/pd692x0.c +@@ -860,7 +860,7 @@ out: + + static int + pd692x0_of_get_managers(struct pd692x0_priv *priv, +- struct pd692x0_manager manager[PD692X0_MAX_MANAGERS]) ++ struct pd692x0_manager *manager) + { + struct device_node *managers_node, *node; + int ret, nmanagers, i, j; +@@ -1164,7 +1164,7 @@ pd692x0_write_ports_matrix(struct pd692x + + static int pd692x0_setup_pi_matrix(struct pse_controller_dev *pcdev) + { +- struct pd692x0_manager manager[PD692X0_MAX_MANAGERS] = {0}; ++ struct pd692x0_manager *manager __free(kfree) = NULL; + struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); + struct pd692x0_matrix port_matrix[PD692X0_MAX_PIS]; + int ret, i, j, nmanagers; +@@ -1174,6 +1174,10 @@ static int pd692x0_setup_pi_matrix(struc + priv->fw_state != PD692X0_FW_COMPLETE) + return 0; + ++ manager = kcalloc(PD692X0_MAX_MANAGERS, sizeof(*manager), GFP_KERNEL); ++ if (!manager) ++ return -ENOMEM; ++ + ret = pd692x0_of_get_managers(priv, manager); + if (ret < 0) + return ret; diff --git a/target/linux/generic/backport-6.12/626-21-v6.18-net-pse-pd-pd692x0-Fix-power-budget-leak.patch b/target/linux/generic/backport-6.12/626-21-v6.18-net-pse-pd-pd692x0-Fix-power-budget-leak.patch new file mode 100644 index 00000000000..af237459470 --- /dev/null +++ b/target/linux/generic/backport-6.12/626-21-v6.18-net-pse-pd-pd692x0-Fix-power-budget-leak.patch @@ -0,0 +1,121 @@ +From 1c67f9c54cdc70627e3f6472b89cd3d895df974c Mon Sep 17 00:00:00 2001 +From: Kory Maincent +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 +Link: https://patch.msgid.link/20250820132708.837255-1-kory.maincent@bootlin.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Carlo Szelinsky +--- + drivers/net/pse-pd/pd692x0.c | 59 +++++++++++++++++++++++++++--------- + 1 file changed, 44 insertions(+), 15 deletions(-) + +--- a/drivers/net/pse-pd/pd692x0.c ++++ b/drivers/net/pse-pd/pd692x0.c +@@ -1162,12 +1162,44 @@ pd692x0_write_ports_matrix(struct pd692x + return 0; + } + ++static void pd692x0_of_put_managers(struct pd692x0_priv *priv, ++ struct pd692x0_manager *manager, ++ int nmanagers) ++{ ++ int i, j; ++ ++ for (i = 0; i < nmanagers; i++) { ++ for (j = 0; j < manager[i].nports; j++) ++ of_node_put(manager[i].port_node[j]); ++ of_node_put(manager[i].node); ++ } ++} ++ ++static void pd692x0_managers_free_pw_budget(struct pd692x0_priv *priv) ++{ ++ int i; ++ ++ for (i = 0; i < PD692X0_MAX_MANAGERS; i++) { ++ struct regulator *supply; ++ ++ if (!priv->manager_reg[i] || !priv->manager_pw_budget[i]) ++ continue; ++ ++ supply = priv->manager_reg[i]->supply; ++ if (!supply) ++ continue; ++ ++ regulator_free_power_budget(supply, ++ priv->manager_pw_budget[i]); ++ } ++} ++ + static int pd692x0_setup_pi_matrix(struct pse_controller_dev *pcdev) + { + struct pd692x0_manager *manager __free(kfree) = NULL; + struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); + struct pd692x0_matrix port_matrix[PD692X0_MAX_PIS]; +- int ret, i, j, nmanagers; ++ int ret, nmanagers; + + /* Should we flash the port matrix */ + if (priv->fw_state != PD692X0_FW_OK && +@@ -1185,31 +1217,27 @@ static int pd692x0_setup_pi_matrix(struc + nmanagers = ret; + ret = pd692x0_register_managers_regulator(priv, manager, nmanagers); + if (ret) +- goto out; ++ goto err_of_managers; + + ret = pd692x0_configure_managers(priv, nmanagers); + if (ret) +- goto out; ++ goto err_of_managers; + + ret = pd692x0_set_ports_matrix(priv, manager, nmanagers, port_matrix); + if (ret) +- goto out; ++ goto err_managers_req_pw; + + ret = pd692x0_write_ports_matrix(priv, port_matrix); + if (ret) +- goto out; +- +-out: +- for (i = 0; i < nmanagers; i++) { +- struct regulator *supply = priv->manager_reg[i]->supply; ++ goto err_managers_req_pw; + +- regulator_free_power_budget(supply, +- priv->manager_pw_budget[i]); ++ pd692x0_of_put_managers(priv, manager, nmanagers); ++ return 0; + +- for (j = 0; j < manager[i].nports; j++) +- of_node_put(manager[i].port_node[j]); +- of_node_put(manager[i].node); +- } ++err_managers_req_pw: ++ pd692x0_managers_free_pw_budget(priv); ++err_of_managers: ++ pd692x0_of_put_managers(priv, manager, nmanagers); + return ret; + } + +@@ -1748,6 +1776,7 @@ static void pd692x0_i2c_remove(struct i2 + { + struct pd692x0_priv *priv = i2c_get_clientdata(client); + ++ pd692x0_managers_free_pw_budget(priv); + firmware_upload_unregister(priv->fwl); + } + diff --git a/target/linux/generic/backport-6.12/626-22-v6.18-net-pse-pd-pd692x0-Skip-power-budget-when-undefined.patch b/target/linux/generic/backport-6.12/626-22-v6.18-net-pse-pd-pd692x0-Skip-power-budget-when-undefined.patch new file mode 100644 index 00000000000..98da7b5080e --- /dev/null +++ b/target/linux/generic/backport-6.12/626-22-v6.18-net-pse-pd-pd692x0-Skip-power-budget-when-undefined.patch @@ -0,0 +1,37 @@ +From 7ef353879f714602b43f98662069f4fb86536761 Mon Sep 17 00:00:00 2001 +From: Kory Maincent +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 +Acked-by: Oleksij Rempel +Link: https://patch.msgid.link/20250820133321.841054-1-kory.maincent@bootlin.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Carlo Szelinsky +--- + drivers/net/pse-pd/pd692x0.c | 4 ++++ + 1 file changed, 4 insertions(+) + +--- a/drivers/net/pse-pd/pd692x0.c ++++ b/drivers/net/pse-pd/pd692x0.c +@@ -1041,6 +1041,10 @@ pd692x0_configure_managers(struct pd692x + int pw_budget; + + pw_budget = regulator_get_unclaimed_power_budget(supply); ++ if (!pw_budget) ++ /* Do nothing if no power budget */ ++ continue; ++ + /* Max power budget per manager */ + if (pw_budget > 6000000) + pw_budget = 6000000; diff --git a/target/linux/generic/backport-6.12/626-23-v6.18-net-pse-pd-Add-Si3474-PSE-controller-driver.patch b/target/linux/generic/backport-6.12/626-23-v6.18-net-pse-pd-Add-Si3474-PSE-controller-driver.patch new file mode 100644 index 00000000000..2f4087facca --- /dev/null +++ b/target/linux/generic/backport-6.12/626-23-v6.18-net-pse-pd-Add-Si3474-PSE-controller-driver.patch @@ -0,0 +1,636 @@ +From a2317231df4b22e6634fe3d8645e7cef848acf49 Mon Sep 17 00:00:00 2001 +From: Piotr Kubik +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 +Reviewed-by: Kory Maincent +Link: https://patch.msgid.link/9b72c8cd-c8d3-4053-9c80-671b9481d166@adtran.com +Signed-off-by: Paolo Abeni +Signed-off-by: Carlo Szelinsky +--- + 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 ++#include ++#include ++#include ++#include ++ ++#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 "); ++MODULE_DESCRIPTION("Skyworks Si3474 PoE PSE Controller driver"); ++MODULE_LICENSE("GPL"); diff --git a/target/linux/generic/backport-6.12/626-24-v6.19-net-pse-pd-tps23881-Fix-current-measurement-scaling.patch b/target/linux/generic/backport-6.12/626-24-v6.19-net-pse-pd-tps23881-Fix-current-measurement-scaling.patch new file mode 100644 index 00000000000..5150da365f7 --- /dev/null +++ b/target/linux/generic/backport-6.12/626-24-v6.19-net-pse-pd-tps23881-Fix-current-measurement-scaling.patch @@ -0,0 +1,31 @@ +From 2c95a756e0cfc19af6d0b32b0c6cf3bada334998 Mon Sep 17 00:00:00 2001 +From: Thomas Wismer +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 +Acked-by: Kory Maincent +Link: https://patch.msgid.link/20251006204029.7169-2-thomas@wismer.xyz +Signed-off-by: Jakub Kicinski +Signed-off-by: Carlo Szelinsky +--- + drivers/net/pse-pd/tps23881.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/net/pse-pd/tps23881.c ++++ b/drivers/net/pse-pd/tps23881.c +@@ -62,7 +62,7 @@ + #define TPS23881_REG_SRAM_DATA 0x61 + + #define TPS23881_UV_STEP 3662 +-#define TPS23881_NA_STEP 70190 ++#define TPS23881_NA_STEP 89500 + #define TPS23881_MW_STEP 500 + #define TPS23881_MIN_PI_PW_LIMIT_MW 2000 + diff --git a/target/linux/generic/backport-6.12/626-25-v6.19-net-pse-pd-pd692x0-Replace-__free-macro.patch b/target/linux/generic/backport-6.12/626-25-v6.19-net-pse-pd-pd692x0-Replace-__free-macro.patch new file mode 100644 index 00000000000..fafa5af2d1e --- /dev/null +++ b/target/linux/generic/backport-6.12/626-25-v6.19-net-pse-pd-pd692x0-Replace-__free-macro.patch @@ -0,0 +1,57 @@ +From f197902cd21ae833850679b216bb62c0d056bbb3 Mon Sep 17 00:00:00 2001 +From: "Kory Maincent (Dent Project)" +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 +Reviewed-by: Simon Horman +Link: https://patch.msgid.link/20251013-feature_pd692x0_reboot_keep_conf-v2-1-68ab082a93dd@bootlin.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Carlo Szelinsky +--- + drivers/net/pse-pd/pd692x0.c | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +--- a/drivers/net/pse-pd/pd692x0.c ++++ b/drivers/net/pse-pd/pd692x0.c +@@ -1200,9 +1200,9 @@ static void pd692x0_managers_free_pw_bud + + static int pd692x0_setup_pi_matrix(struct pse_controller_dev *pcdev) + { +- struct pd692x0_manager *manager __free(kfree) = NULL; + struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); + struct pd692x0_matrix port_matrix[PD692X0_MAX_PIS]; ++ struct pd692x0_manager *manager; + int ret, nmanagers; + + /* Should we flash the port matrix */ +@@ -1216,7 +1216,7 @@ static int pd692x0_setup_pi_matrix(struc + + ret = pd692x0_of_get_managers(priv, manager); + if (ret < 0) +- return ret; ++ goto err_free_manager; + + nmanagers = ret; + ret = pd692x0_register_managers_regulator(priv, manager, nmanagers); +@@ -1236,12 +1236,15 @@ static int pd692x0_setup_pi_matrix(struc + goto err_managers_req_pw; + + pd692x0_of_put_managers(priv, manager, nmanagers); ++ kfree(manager); + return 0; + + err_managers_req_pw: + pd692x0_managers_free_pw_budget(priv); + err_of_managers: + pd692x0_of_put_managers(priv, manager, nmanagers); ++err_free_manager: ++ kfree(manager); + return ret; + } + diff --git a/target/linux/generic/backport-6.12/626-26-v6.19-net-pse-pd-pd692x0-Separate-configuration-parsing.patch b/target/linux/generic/backport-6.12/626-26-v6.19-net-pse-pd-pd692x0-Separate-configuration-parsing.patch new file mode 100644 index 00000000000..e88bbd03bfa --- /dev/null +++ b/target/linux/generic/backport-6.12/626-26-v6.19-net-pse-pd-pd692x0-Separate-configuration-parsing.patch @@ -0,0 +1,291 @@ +From 6fa1f8b64a47edd7d8420d8fd1008507aee2853e Mon Sep 17 00:00:00 2001 +From: "Kory Maincent (Dent Project)" +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 +Reviewed-by: Simon Horman +Link: https://patch.msgid.link/20251013-feature_pd692x0_reboot_keep_conf-v2-2-68ab082a93dd@bootlin.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Carlo Szelinsky +--- + drivers/net/pse-pd/pd692x0.c | 115 +++++++++++++++++++++++------------ + 1 file changed, 76 insertions(+), 39 deletions(-) + +--- a/drivers/net/pse-pd/pd692x0.c ++++ b/drivers/net/pse-pd/pd692x0.c +@@ -85,6 +85,11 @@ enum { + PD692X0_MSG_CNT + }; + ++struct pd692x0_matrix { ++ u8 hw_port_a; ++ u8 hw_port_b; ++}; ++ + struct pd692x0_priv { + struct i2c_client *client; + struct pse_controller_dev pcdev; +@@ -101,6 +106,8 @@ struct pd692x0_priv { + enum ethtool_c33_pse_admin_state admin_state[PD692X0_MAX_PIS]; + struct regulator_dev *manager_reg[PD692X0_MAX_MANAGERS]; + int manager_pw_budget[PD692X0_MAX_MANAGERS]; ++ int nmanagers; ++ struct pd692x0_matrix *port_matrix; + }; + + /* Template list of communication messages. The non-null bytes defined here +@@ -809,11 +816,6 @@ struct pd692x0_manager { + int nports; + }; + +-struct pd692x0_matrix { +- u8 hw_port_a; +- u8 hw_port_b; +-}; +- + static int + pd692x0_of_get_ports_manager(struct pd692x0_priv *priv, + struct pd692x0_manager *manager, +@@ -903,7 +905,8 @@ pd692x0_of_get_managers(struct pd692x0_p + } + + of_node_put(managers_node); +- return nmanagers; ++ priv->nmanagers = nmanagers; ++ return 0; + + out: + for (i = 0; i < nmanagers; i++) { +@@ -963,8 +966,7 @@ pd692x0_register_manager_regulator(struc + + static int + pd692x0_register_managers_regulator(struct pd692x0_priv *priv, +- const struct pd692x0_manager *manager, +- int nmanagers) ++ const struct pd692x0_manager *manager) + { + struct device *dev = &priv->client->dev; + size_t reg_name_len; +@@ -975,7 +977,7 @@ pd692x0_register_managers_regulator(stru + */ + reg_name_len = strlen(dev_name(dev)) + 23; + +- for (i = 0; i < nmanagers; i++) { ++ for (i = 0; i < priv->nmanagers; i++) { + static const char * const regulators[] = { "vaux5", "vaux3p3" }; + struct regulator_dev *rdev; + char *reg_name; +@@ -1008,10 +1010,14 @@ pd692x0_register_managers_regulator(stru + } + + static int +-pd692x0_conf_manager_power_budget(struct pd692x0_priv *priv, int id, int pw) ++pd692x0_conf_manager_power_budget(struct pd692x0_priv *priv, int id) + { + struct pd692x0_msg msg, buf; +- int ret, pw_mW = pw / 1000; ++ int ret, pw_mW; ++ ++ pw_mW = priv->manager_pw_budget[id] / 1000; ++ if (!pw_mW) ++ return 0; + + msg = pd692x0_msg_template_list[PD692X0_MSG_GET_POWER_BANK]; + msg.data[0] = id; +@@ -1032,11 +1038,11 @@ pd692x0_conf_manager_power_budget(struct + } + + static int +-pd692x0_configure_managers(struct pd692x0_priv *priv, int nmanagers) ++pd692x0_req_managers_pw_budget(struct pd692x0_priv *priv) + { + int i, ret; + +- for (i = 0; i < nmanagers; i++) { ++ for (i = 0; i < priv->nmanagers; i++) { + struct regulator *supply = priv->manager_reg[i]->supply; + int pw_budget; + +@@ -1053,7 +1059,18 @@ pd692x0_configure_managers(struct pd692x + return ret; + + priv->manager_pw_budget[i] = pw_budget; +- ret = pd692x0_conf_manager_power_budget(priv, i, pw_budget); ++ } ++ ++ return 0; ++} ++ ++static int ++pd692x0_configure_managers(struct pd692x0_priv *priv) ++{ ++ int i, ret; ++ ++ for (i = 0; i < priv->nmanagers; i++) { ++ ret = pd692x0_conf_manager_power_budget(priv, i); + if (ret < 0) + return ret; + } +@@ -1101,10 +1118,9 @@ pd692x0_set_port_matrix(const struct pse + + static int + pd692x0_set_ports_matrix(struct pd692x0_priv *priv, +- const struct pd692x0_manager *manager, +- int nmanagers, +- struct pd692x0_matrix port_matrix[PD692X0_MAX_PIS]) ++ const struct pd692x0_manager *manager) + { ++ struct pd692x0_matrix *port_matrix = priv->port_matrix; + struct pse_controller_dev *pcdev = &priv->pcdev; + int i, ret; + +@@ -1117,7 +1133,7 @@ pd692x0_set_ports_matrix(struct pd692x0_ + /* Update with values for every PSE PIs */ + for (i = 0; i < pcdev->nr_lines; i++) { + ret = pd692x0_set_port_matrix(&pcdev->pi[i].pairset[0], +- manager, nmanagers, ++ manager, priv->nmanagers, + &port_matrix[i]); + if (ret) { + dev_err(&priv->client->dev, +@@ -1126,7 +1142,7 @@ pd692x0_set_ports_matrix(struct pd692x0_ + } + + ret = pd692x0_set_port_matrix(&pcdev->pi[i].pairset[1], +- manager, nmanagers, ++ manager, priv->nmanagers, + &port_matrix[i]); + if (ret) { + dev_err(&priv->client->dev, +@@ -1139,9 +1155,9 @@ pd692x0_set_ports_matrix(struct pd692x0_ + } + + static int +-pd692x0_write_ports_matrix(struct pd692x0_priv *priv, +- const struct pd692x0_matrix port_matrix[PD692X0_MAX_PIS]) ++pd692x0_write_ports_matrix(struct pd692x0_priv *priv) + { ++ struct pd692x0_matrix *port_matrix = priv->port_matrix; + struct pd692x0_msg msg, buf; + int ret, i; + +@@ -1166,13 +1182,32 @@ pd692x0_write_ports_matrix(struct pd692x + return 0; + } + ++static int pd692x0_hw_conf_init(struct pd692x0_priv *priv) ++{ ++ int ret; ++ ++ /* Is PD692x0 ready to be configured? */ ++ if (priv->fw_state != PD692X0_FW_OK && ++ priv->fw_state != PD692X0_FW_COMPLETE) ++ return 0; ++ ++ ret = pd692x0_configure_managers(priv); ++ if (ret) ++ return ret; ++ ++ ret = pd692x0_write_ports_matrix(priv); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ + static void pd692x0_of_put_managers(struct pd692x0_priv *priv, +- struct pd692x0_manager *manager, +- int nmanagers) ++ struct pd692x0_manager *manager) + { + int i, j; + +- for (i = 0; i < nmanagers; i++) { ++ for (i = 0; i < priv->nmanagers; i++) { + for (j = 0; j < manager[i].nports; j++) + of_node_put(manager[i].port_node[j]); + of_node_put(manager[i].node); +@@ -1201,48 +1236,50 @@ static void pd692x0_managers_free_pw_bud + static int pd692x0_setup_pi_matrix(struct pse_controller_dev *pcdev) + { + struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); +- struct pd692x0_matrix port_matrix[PD692X0_MAX_PIS]; ++ struct pd692x0_matrix *port_matrix; + struct pd692x0_manager *manager; +- int ret, nmanagers; +- +- /* Should we flash the port matrix */ +- if (priv->fw_state != PD692X0_FW_OK && +- priv->fw_state != PD692X0_FW_COMPLETE) +- return 0; ++ int ret; + + manager = kcalloc(PD692X0_MAX_MANAGERS, sizeof(*manager), GFP_KERNEL); + if (!manager) + return -ENOMEM; + ++ port_matrix = devm_kcalloc(&priv->client->dev, PD692X0_MAX_PIS, ++ sizeof(*port_matrix), GFP_KERNEL); ++ if (!port_matrix) { ++ ret = -ENOMEM; ++ goto err_free_manager; ++ } ++ priv->port_matrix = port_matrix; ++ + ret = pd692x0_of_get_managers(priv, manager); + if (ret < 0) + goto err_free_manager; + +- nmanagers = ret; +- ret = pd692x0_register_managers_regulator(priv, manager, nmanagers); ++ ret = pd692x0_register_managers_regulator(priv, manager); + if (ret) + goto err_of_managers; + +- ret = pd692x0_configure_managers(priv, nmanagers); ++ ret = pd692x0_req_managers_pw_budget(priv); + if (ret) + goto err_of_managers; + +- ret = pd692x0_set_ports_matrix(priv, manager, nmanagers, port_matrix); ++ ret = pd692x0_set_ports_matrix(priv, manager); + if (ret) + goto err_managers_req_pw; + +- ret = pd692x0_write_ports_matrix(priv, port_matrix); ++ ret = pd692x0_hw_conf_init(priv); + if (ret) + goto err_managers_req_pw; + +- pd692x0_of_put_managers(priv, manager, nmanagers); ++ pd692x0_of_put_managers(priv, manager); + kfree(manager); + return 0; + + err_managers_req_pw: + pd692x0_managers_free_pw_budget(priv); + err_of_managers: +- pd692x0_of_put_managers(priv, manager, nmanagers); ++ pd692x0_of_put_managers(priv, manager); + err_free_manager: + kfree(manager); + return ret; +@@ -1647,7 +1684,7 @@ static enum fw_upload_err pd692x0_fw_pol + return FW_UPLOAD_ERR_FW_INVALID; + } + +- ret = pd692x0_setup_pi_matrix(&priv->pcdev); ++ ret = pd692x0_hw_conf_init(priv); + if (ret < 0) { + dev_err(&client->dev, "Error configuring ports matrix (%pe)\n", + ERR_PTR(ret)); diff --git a/target/linux/generic/backport-6.12/626-27-v6.19-net-pse-pd-pd692x0-Preserve-PSE-configuration.patch b/target/linux/generic/backport-6.12/626-27-v6.19-net-pse-pd-pd692x0-Preserve-PSE-configuration.patch new file mode 100644 index 00000000000..4ae835e9322 --- /dev/null +++ b/target/linux/generic/backport-6.12/626-27-v6.19-net-pse-pd-pd692x0-Preserve-PSE-configuration.patch @@ -0,0 +1,111 @@ +From 8f3d044b34fe99b894046edb84605456195cabc0 Mon Sep 17 00:00:00 2001 +From: "Kory Maincent (Dent Project)" +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 +Reviewed-by: Simon Horman +Link: https://patch.msgid.link/20251013-feature_pd692x0_reboot_keep_conf-v2-3-68ab082a93dd@bootlin.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Carlo Szelinsky +--- + drivers/net/pse-pd/pd692x0.c | 35 ++++++++++++++++++++++++++++++++--- + 1 file changed, 32 insertions(+), 3 deletions(-) + +--- a/drivers/net/pse-pd/pd692x0.c ++++ b/drivers/net/pse-pd/pd692x0.c +@@ -30,6 +30,8 @@ + #define PD692X0_FW_MIN_VER 5 + #define PD692X0_FW_PATCH_VER 5 + ++#define PD692X0_USER_BYTE 42 ++ + enum pd692x0_fw_state { + PD692X0_FW_UNKNOWN, + PD692X0_FW_OK, +@@ -80,6 +82,7 @@ enum { + PD692X0_MSG_GET_PORT_PARAM, + PD692X0_MSG_GET_POWER_BANK, + PD692X0_MSG_SET_POWER_BANK, ++ PD692X0_MSG_SET_USER_BYTE, + + /* add new message above here */ + PD692X0_MSG_CNT +@@ -103,6 +106,7 @@ struct pd692x0_priv { + bool last_cmd_key; + unsigned long last_cmd_key_time; + ++ bool cfg_saved; + enum ethtool_c33_pse_admin_state admin_state[PD692X0_MAX_PIS]; + struct regulator_dev *manager_reg[PD692X0_MAX_MANAGERS]; + int manager_pw_budget[PD692X0_MAX_MANAGERS]; +@@ -193,6 +197,12 @@ static const struct pd692x0_msg pd692x0_ + .key = PD692X0_KEY_CMD, + .sub = {0x07, 0x0b, 0x57}, + }, ++ [PD692X0_MSG_SET_USER_BYTE] = { ++ .key = PD692X0_KEY_PRG, ++ .sub = {0x41, PD692X0_USER_BYTE}, ++ .data = {0x4e, 0x4e, 0x4e, 0x4e, ++ 0x4e, 0x4e, 0x4e, 0x4e}, ++ }, + }; + + static u8 pd692x0_build_msg(struct pd692x0_msg *msg, u8 echo) +@@ -1233,6 +1243,15 @@ static void pd692x0_managers_free_pw_bud + } + } + ++static int ++pd692x0_save_user_byte(struct pd692x0_priv *priv) ++{ ++ struct pd692x0_msg msg, buf; ++ ++ msg = pd692x0_msg_template_list[PD692X0_MSG_SET_USER_BYTE]; ++ return pd692x0_sendrecv_msg(priv, &msg, &buf); ++} ++ + static int pd692x0_setup_pi_matrix(struct pse_controller_dev *pcdev) + { + struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); +@@ -1268,9 +1287,16 @@ static int pd692x0_setup_pi_matrix(struc + if (ret) + goto err_managers_req_pw; + +- ret = pd692x0_hw_conf_init(priv); +- if (ret) +- goto err_managers_req_pw; ++ /* Do not init the conf if it is already saved */ ++ if (!priv->cfg_saved) { ++ ret = pd692x0_hw_conf_init(priv); ++ if (ret) ++ goto err_managers_req_pw; ++ ++ ret = pd692x0_save_user_byte(priv); ++ if (ret) ++ goto err_managers_req_pw; ++ } + + pd692x0_of_put_managers(priv, manager); + kfree(manager); +@@ -1793,6 +1819,9 @@ static int pd692x0_i2c_probe(struct i2c_ + } + } + ++ if (buf.data[2] == PD692X0_USER_BYTE) ++ priv->cfg_saved = true; ++ + priv->np = dev->of_node; + priv->pcdev.nr_lines = PD692X0_MAX_PIS; + priv->pcdev.owner = THIS_MODULE; diff --git a/target/linux/generic/backport-6.12/626-28-v6.19-net-pse-pd-tps23881-Add-support-for-TPS23881B.patch b/target/linux/generic/backport-6.12/626-28-v6.19-net-pse-pd-tps23881-Add-support-for-TPS23881B.patch new file mode 100644 index 00000000000..2c188db7a37 --- /dev/null +++ b/target/linux/generic/backport-6.12/626-28-v6.19-net-pse-pd-tps23881-Add-support-for-TPS23881B.patch @@ -0,0 +1,170 @@ +From 4d07797faaa19aa8e80e10a04ca1a72c643ef5cf Mon Sep 17 00:00:00 2001 +From: Thomas Wismer +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 +Reviewed-by: Kory Maincent +Acked-by: Oleksij Rempel +Link: https://patch.msgid.link/20251029212312.108749-2-thomas@wismer.xyz +Signed-off-by: Jakub Kicinski +Signed-off-by: Carlo Szelinsky +--- + drivers/net/pse-pd/tps23881.c | 69 +++++++++++++++++++++++++++-------- + 1 file changed, 54 insertions(+), 15 deletions(-) + +--- a/drivers/net/pse-pd/tps23881.c ++++ b/drivers/net/pse-pd/tps23881.c +@@ -55,8 +55,6 @@ + #define TPS23881_REG_TPON BIT(0) + #define TPS23881_REG_FWREV 0x41 + #define TPS23881_REG_DEVID 0x43 +-#define TPS23881_REG_DEVID_MASK 0xF0 +-#define TPS23881_DEVICE_ID 0x02 + #define TPS23881_REG_CHAN1_CLASS 0x4c + #define TPS23881_REG_SRAM_CTRL 0x60 + #define TPS23881_REG_SRAM_DATA 0x61 +@@ -1012,8 +1010,28 @@ static const struct pse_controller_ops t + .pi_get_pw_req = tps23881_pi_get_pw_req, + }; + +-static const char fw_parity_name[] = "ti/tps23881/tps23881-parity-14.bin"; +-static const char fw_sram_name[] = "ti/tps23881/tps23881-sram-14.bin"; ++struct tps23881_info { ++ u8 dev_id; /* device ID and silicon revision */ ++ const char *fw_parity_name; /* parity code firmware file name */ ++ const char *fw_sram_name; /* SRAM code firmware file name */ ++}; ++ ++enum tps23881_model { ++ TPS23881, ++ TPS23881B, ++}; ++ ++static const struct tps23881_info tps23881_info[] = { ++ [TPS23881] = { ++ .dev_id = 0x22, ++ .fw_parity_name = "ti/tps23881/tps23881-parity-14.bin", ++ .fw_sram_name = "ti/tps23881/tps23881-sram-14.bin", ++ }, ++ [TPS23881B] = { ++ .dev_id = 0x24, ++ /* skip SRAM load, ROM provides Clause 145 hardware-level support */ ++ }, ++}; + + struct tps23881_fw_conf { + u8 reg; +@@ -1085,16 +1103,17 @@ out: + return ret; + } + +-static int tps23881_flash_sram_fw(struct i2c_client *client) ++static int tps23881_flash_sram_fw(struct i2c_client *client, ++ const struct tps23881_info *info) + { + int ret; + +- ret = tps23881_flash_sram_fw_part(client, fw_parity_name, ++ ret = tps23881_flash_sram_fw_part(client, info->fw_parity_name, + tps23881_fw_parity_conf); + if (ret) + return ret; + +- ret = tps23881_flash_sram_fw_part(client, fw_sram_name, ++ ret = tps23881_flash_sram_fw_part(client, info->fw_sram_name, + tps23881_fw_sram_conf); + if (ret) + return ret; +@@ -1412,6 +1431,7 @@ static int tps23881_setup_irq(struct tps + static int tps23881_i2c_probe(struct i2c_client *client) + { + struct device *dev = &client->dev; ++ const struct tps23881_info *info; + struct tps23881_priv *priv; + struct gpio_desc *reset; + int ret; +@@ -1422,6 +1442,10 @@ static int tps23881_i2c_probe(struct i2c + return -ENXIO; + } + ++ info = i2c_get_match_data(client); ++ if (!info) ++ return -EINVAL; ++ + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; +@@ -1440,7 +1464,7 @@ static int tps23881_i2c_probe(struct i2c + * to Load TPS2388x SRAM and Parity Code over I2C" (Rev E)) + * indicates we should delay that programming by at least 50ms. So + * we'll wait the entire 50ms here to ensure we're safe to go to the +- * SRAM loading proceedure. ++ * SRAM loading procedure. + */ + msleep(50); + } +@@ -1449,20 +1473,27 @@ static int tps23881_i2c_probe(struct i2c + if (ret < 0) + return ret; + +- if (FIELD_GET(TPS23881_REG_DEVID_MASK, ret) != TPS23881_DEVICE_ID) { ++ if (ret != info->dev_id) { + dev_err(dev, "Wrong device ID\n"); + return -ENXIO; + } + +- ret = tps23881_flash_sram_fw(client); +- if (ret < 0) +- return ret; ++ if (info->fw_sram_name) { ++ ret = tps23881_flash_sram_fw(client, info); ++ if (ret < 0) ++ return ret; ++ } + + ret = i2c_smbus_read_byte_data(client, TPS23881_REG_FWREV); + if (ret < 0) + return ret; + +- dev_info(&client->dev, "Firmware revision 0x%x\n", ret); ++ if (ret == 0xFF) { ++ dev_err(&client->dev, "Device entered safe mode\n"); ++ return -ENXIO; ++ } ++ dev_info(&client->dev, "Firmware revision 0x%x%s\n", ret, ++ ret == 0x00 ? " (ROM firmware)" : ""); + + /* Set configuration B, 16 bit access on a single device address */ + ret = i2c_smbus_read_byte_data(client, TPS23881_REG_GEN_MASK); +@@ -1498,13 +1529,21 @@ static int tps23881_i2c_probe(struct i2c + } + + static const struct i2c_device_id tps23881_id[] = { +- { "tps23881" }, ++ { "tps23881", .driver_data = (kernel_ulong_t)&tps23881_info[TPS23881] }, ++ { "tps23881b", .driver_data = (kernel_ulong_t)&tps23881_info[TPS23881B] }, + { } + }; + MODULE_DEVICE_TABLE(i2c, tps23881_id); + + static const struct of_device_id tps23881_of_match[] = { +- { .compatible = "ti,tps23881", }, ++ { ++ .compatible = "ti,tps23881", ++ .data = &tps23881_info[TPS23881] ++ }, ++ { ++ .compatible = "ti,tps23881b", ++ .data = &tps23881_info[TPS23881B] ++ }, + { }, + }; + MODULE_DEVICE_TABLE(of, tps23881_of_match); diff --git a/target/linux/generic/backport-6.12/627-01-v6.17-net-pse-pd-Add-ethtool_netlink_generated-header.patch b/target/linux/generic/backport-6.12/627-01-v6.17-net-pse-pd-Add-ethtool_netlink_generated-header.patch new file mode 100644 index 00000000000..863bd2e56cf --- /dev/null +++ b/target/linux/generic/backport-6.12/627-01-v6.17-net-pse-pd-Add-ethtool_netlink_generated-header.patch @@ -0,0 +1,80 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: OpenWrt PSE-PD Backport +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 +Signed-off-by: Carlo Szelinsky +--- + include/uapi/linux/ethtool_netlink.h | 1 + + include/uapi/linux/ethtool_netlink_generated.h | 45 ++++++++++++++++++ + 2 files changed, 46 insertions(+) + create mode 100644 include/uapi/linux/ethtool_netlink_generated.h + +--- a/include/uapi/linux/ethtool_netlink.h ++++ b/include/uapi/linux/ethtool_netlink.h +@@ -115,6 +115,7 @@ enum { + ETHTOOL_MSG_PHY_GET_REPLY, + ETHTOOL_MSG_PHY_NTF, + ++ ETHTOOL_MSG_PSE_NTF, + /* add new constants above here */ + __ETHTOOL_MSG_KERNEL_CNT, + ETHTOOL_MSG_KERNEL_MAX = __ETHTOOL_MSG_KERNEL_CNT - 1 +--- /dev/null ++++ b/include/uapi/linux/ethtool_netlink_generated.h +@@ -0,0 +1,45 @@ ++/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ ++/* ++ * Minimal PSE ethtool netlink definitions - OpenWrt backport for 6.12.67 ++ * In mainline 6.17+, this file is auto-generated from: ++ * Documentation/netlink/specs/ethtool.yaml ++ * ++ * GENERATED PATCH - OpenWrt PSE-PD Backport ++ */ ++ ++#ifndef _UAPI_LINUX_ETHTOOL_NETLINK_GENERATED_H ++#define _UAPI_LINUX_ETHTOOL_NETLINK_GENERATED_H ++ ++/** ++ * enum ethtool_pse_event - PSE event list for the PSE controller ++ * @ETHTOOL_PSE_EVENT_OVER_CURRENT: PSE output current is too high ++ * @ETHTOOL_PSE_EVENT_OVER_TEMP: PSE in over temperature state ++ * @ETHTOOL_C33_PSE_EVENT_DETECTION: detection process occur on the PSE ++ * @ETHTOOL_C33_PSE_EVENT_CLASSIFICATION: classification process occur ++ * @ETHTOOL_C33_PSE_EVENT_DISCONNECTION: PD has been disconnected ++ * @ETHTOOL_PSE_EVENT_OVER_BUDGET: PSE turned off due to over budget ++ * @ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR: PSE power control error ++ */ ++enum ethtool_pse_event { ++ ETHTOOL_PSE_EVENT_OVER_CURRENT = 1, ++ ETHTOOL_PSE_EVENT_OVER_TEMP = 2, ++ ETHTOOL_C33_PSE_EVENT_DETECTION = 4, ++ ETHTOOL_C33_PSE_EVENT_CLASSIFICATION = 8, ++ ETHTOOL_C33_PSE_EVENT_DISCONNECTION = 16, ++ ETHTOOL_PSE_EVENT_OVER_BUDGET = 32, ++ ETHTOOL_PSE_EVENT_SW_PW_CONTROL_ERROR = 64, ++}; ++ ++/* PSE notification attributes */ ++enum { ++ ETHTOOL_A_PSE_NTF_HEADER = 1, ++ ETHTOOL_A_PSE_NTF_EVENTS, ++ ++ __ETHTOOL_A_PSE_NTF_CNT, ++ ETHTOOL_A_PSE_NTF_MAX = (__ETHTOOL_A_PSE_NTF_CNT - 1) ++}; ++ ++/* PSE power domain ID attribute - value 14 in the ETHTOOL_A_PSE_* enum */ ++#define ETHTOOL_A_PSE_PW_D_ID 14 ++ ++#endif /* _UAPI_LINUX_ETHTOOL_NETLINK_GENERATED_H */ diff --git a/target/linux/realtek/patches-6.12/700-dsa-mdio-increase-max-ports-for-rtl839x-rtl931x.patch b/target/linux/realtek/patches-6.12/700-dsa-mdio-increase-max-ports-for-rtl839x-rtl931x.patch index 3431715c632..ca95d3c6d33 100644 --- a/target/linux/realtek/patches-6.12/700-dsa-mdio-increase-max-ports-for-rtl839x-rtl931x.patch +++ b/target/linux/realtek/patches-6.12/700-dsa-mdio-increase-max-ports-for-rtl839x-rtl931x.patch @@ -24,7 +24,7 @@ Signed-off-by: Markus Stockhausen --- 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"))