From 514dcf78afe6b99fba5e885be295356856dd424b Mon Sep 17 00:00:00 2001 From: Kory Maincent Date: Fri, 10 Jan 2025 10:40:20 +0100 Subject: [PATCH 01/12] net: pse-pd: Remove unused pse_ethtool_get_pw_limit function declaration Removed the unused pse_ethtool_get_pw_limit() function declaration from pse.h. This function was declared but never implemented or used, making the declaration unnecessary. Reviewed-by: Kalesh AP Reviewed-by: Andrew Lunn Acked-by: Oleksij Rempel Reviewed-by: Kyle Swenson Signed-off-by: Kory Maincent Signed-off-by: Paolo Abeni --- include/linux/pse-pd/pse.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/include/linux/pse-pd/pse.h b/include/linux/pse-pd/pse.h index 591a53e082e65..85a08c3492566 100644 --- a/include/linux/pse-pd/pse.h +++ b/include/linux/pse-pd/pse.h @@ -184,8 +184,6 @@ int pse_ethtool_set_config(struct pse_control *psec, int pse_ethtool_set_pw_limit(struct pse_control *psec, struct netlink_ext_ack *extack, const unsigned int pw_limit); -int pse_ethtool_get_pw_limit(struct pse_control *psec, - struct netlink_ext_ack *extack); bool pse_has_podl(struct pse_control *psec); bool pse_has_c33(struct pse_control *psec); @@ -222,12 +220,6 @@ static inline int pse_ethtool_set_pw_limit(struct pse_control *psec, return -EOPNOTSUPP; } -static inline int pse_ethtool_get_pw_limit(struct pse_control *psec, - struct netlink_ext_ack *extack) -{ - return -EOPNOTSUPP; -} - static inline bool pse_has_podl(struct pse_control *psec) { return false; From 675d0e3cacc3ae7c29294a5f6a820187f862ad8b Mon Sep 17 00:00:00 2001 From: Kory Maincent Date: Fri, 10 Jan 2025 10:40:21 +0100 Subject: [PATCH 02/12] net: pse-pd: Avoid setting max_uA in regulator constraints Setting the max_uA constraint in the regulator API imposes a current limit during the regulator registration process. This behavior conflicts with preserving the maximum PI power budget configuration across reboots. Instead, compare the desired current limit to MAX_PI_CURRENT in the pse_pi_set_current_limit() function to ensure proper handling of the power budget. Acked-by: Oleksij Rempel Signed-off-by: Kory Maincent Signed-off-by: Paolo Abeni --- drivers/net/pse-pd/pse_core.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c index 2906ce173f66c..9fee4dd53515a 100644 --- a/drivers/net/pse-pd/pse_core.c +++ b/drivers/net/pse-pd/pse_core.c @@ -357,6 +357,9 @@ static int pse_pi_set_current_limit(struct regulator_dev *rdev, int min_uA, if (!ops->pi_set_current_limit) return -EOPNOTSUPP; + if (max_uA > MAX_PI_CURRENT) + return -ERANGE; + id = rdev_get_id(rdev); mutex_lock(&pcdev->lock); ret = ops->pi_set_current_limit(pcdev, id, max_uA); @@ -403,11 +406,9 @@ devm_pse_pi_regulator_register(struct pse_controller_dev *pcdev, rinit_data->constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS; - if (pcdev->ops->pi_set_current_limit) { + if (pcdev->ops->pi_set_current_limit) rinit_data->constraints.valid_ops_mask |= REGULATOR_CHANGE_CURRENT; - rinit_data->constraints.max_uA = MAX_PI_CURRENT; - } rinit_data->supply_regulator = "vpwr"; From 6e56a6d47a7fad705a1a1d088237b0858c01a770 Mon Sep 17 00:00:00 2001 From: Kory Maincent Date: Fri, 10 Jan 2025 10:40:22 +0100 Subject: [PATCH 03/12] 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 --- drivers/net/pse-pd/pse_core.c | 3 +++ include/linux/pse-pd/pse.h | 2 ++ 2 files changed, 5 insertions(+) diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c index 9fee4dd53515a..432b6c2c04f83 100644 --- a/drivers/net/pse-pd/pse_core.c +++ b/drivers/net/pse-pd/pse_core.c @@ -877,6 +877,9 @@ int pse_ethtool_set_pw_limit(struct pse_control *psec, 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, diff --git a/include/linux/pse-pd/pse.h b/include/linux/pse-pd/pse.h index 85a08c3492566..bc5addccbf32e 100644 --- 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; From 0b567519d1152de52b29b2da2c47aa0f39a46266 Mon Sep 17 00:00:00 2001 From: Kory Maincent Date: Fri, 10 Jan 2025 10:40:23 +0100 Subject: [PATCH 04/12] 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 --- drivers/net/pse-pd/tps23881.c | 34 ++++++---------------------------- 1 file changed, 6 insertions(+), 28 deletions(-) diff --git a/drivers/net/pse-pd/tps23881.c b/drivers/net/pse-pd/tps23881.c index 8797ca1a8a219..a3507520ff872 100644 --- 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_controller_dev *pcdev, int id) 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_controller_dev *pcdev, int id) 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 pse_controller_dev *pcdev, int id) 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 pse_controller_dev *pcdev, int id) 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 tps23881_priv *priv, 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 tps23881_priv *priv, } /* 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_priv *priv, 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 tps23881_priv *priv, 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) From 4c2bab507eb7edc8e497e91b9b7f05d76d7e32bb Mon Sep 17 00:00:00 2001 From: Kory Maincent Date: Fri, 10 Jan 2025 10:40:24 +0100 Subject: [PATCH 05/12] 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 --- drivers/net/pse-pd/tps23881.c | 107 ++++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 38 deletions(-) diff --git a/drivers/net/pse-pd/tps23881.c b/drivers/net/pse-pd/tps23881.c index a3507520ff872..4a75206b2de67 100644 --- a/drivers/net/pse-pd/tps23881.c +++ b/drivers/net/pse-pd/tps23881.c @@ -53,6 +53,55 @@ static struct tps23881_priv *to_tps23881_priv(struct pse_controller_dev *pcdev) 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_controller_dev *pcdev, int id) 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 pse_controller_dev *pcdev, int id) 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 pse_controller_dev *pcdev, int id) 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 pse_controller_dev *pcdev, int id) 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(struct pse_controller_dev *pcdev, 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(struct pse_controller_dev *pcdev, 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 */ From f3cb3c7bea0c08e821d8e9dfd2f96acd1db7c24e Mon Sep 17 00:00:00 2001 From: Kory Maincent Date: Fri, 10 Jan 2025 10:40:25 +0100 Subject: [PATCH 06/12] 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 --- drivers/net/pse-pd/tps23881.c | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/drivers/net/pse-pd/tps23881.c b/drivers/net/pse-pd/tps23881.c index 4a75206b2de67..b87c391ae0f57 100644 --- a/drivers/net/pse-pd/tps23881.c +++ b/drivers/net/pse-pd/tps23881.c @@ -130,6 +130,7 @@ static int tps23881_pi_disable(struct pse_controller_dev *pcdev, int id) 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 pse_controller_dev *pcdev, int id) 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) From e0a5e2bba38aa61a900934b45d6e846e0a6d7524 Mon Sep 17 00:00:00 2001 From: Kory Maincent Date: Fri, 10 Jan 2025 10:40:26 +0100 Subject: [PATCH 07/12] net: pse-pd: Use power limit at driver side instead of current limit The regulator framework uses current limits, but the PSE standard and known PSE controllers rely on power limits. Instead of converting current to power within each driver, perform the conversion in the PSE core. This avoids redundancy in driver implementation and aligns better with the standard, simplifying driver development. Remove at the same time the _pse_ethtool_get_status() function which is not needed anymore. Acked-by: Oleksij Rempel Signed-off-by: Kory Maincent Signed-off-by: Paolo Abeni --- drivers/net/pse-pd/pd692x0.c | 45 ++++------------- drivers/net/pse-pd/pse_core.c | 91 ++++++++++++++++------------------- include/linux/pse-pd/pse.h | 16 +++--- 3 files changed, 57 insertions(+), 95 deletions(-) diff --git a/drivers/net/pse-pd/pd692x0.c b/drivers/net/pse-pd/pd692x0.c index 0af7db80b2f88..9f00538f7e450 100644 --- a/drivers/net/pse-pd/pd692x0.c +++ b/drivers/net/pse-pd/pd692x0.c @@ -999,13 +999,12 @@ static int pd692x0_pi_get_voltage(struct pse_controller_dev *pcdev, int id) return (buf.sub[0] << 8 | buf.sub[1]) * 100000; } -static int pd692x0_pi_get_current_limit(struct pse_controller_dev *pcdev, - int id) +static int pd692x0_pi_get_pw_limit(struct pse_controller_dev *pcdev, + int id) { struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); struct pd692x0_msg msg, buf = {0}; - int mW, uV, uA, ret; - s64 tmp_64; + int ret; msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_PARAM]; msg.sub[2] = id; @@ -1013,48 +1012,24 @@ static int pd692x0_pi_get_current_limit(struct pse_controller_dev *pcdev, if (ret < 0) return ret; - ret = pd692x0_pi_get_pw_from_table(buf.data[2], buf.data[3]); - if (ret < 0) - return ret; - mW = ret; - - ret = pd692x0_pi_get_voltage(pcdev, id); - if (ret < 0) - return ret; - uV = ret; - - tmp_64 = mW; - tmp_64 *= 1000000000ull; - /* uA = mW * 1000000000 / uV */ - uA = DIV_ROUND_CLOSEST_ULL(tmp_64, uV); - return uA; + return pd692x0_pi_get_pw_from_table(buf.data[2], buf.data[3]); } -static int pd692x0_pi_set_current_limit(struct pse_controller_dev *pcdev, - int id, int max_uA) +static int pd692x0_pi_set_pw_limit(struct pse_controller_dev *pcdev, + int id, int max_mW) { struct pd692x0_priv *priv = to_pd692x0_priv(pcdev); struct device *dev = &priv->client->dev; struct pd692x0_msg msg, buf = {0}; - int uV, ret, mW; - s64 tmp_64; + int ret; ret = pd692x0_fw_unavailable(priv); if (ret) return ret; - ret = pd692x0_pi_get_voltage(pcdev, id); - if (ret < 0) - return ret; - uV = ret; - msg = pd692x0_msg_template_list[PD692X0_MSG_SET_PORT_PARAM]; msg.sub[2] = id; - tmp_64 = uV; - tmp_64 *= max_uA; - /* mW = uV * uA / 1000000000 */ - mW = DIV_ROUND_CLOSEST_ULL(tmp_64, 1000000000); - ret = pd692x0_pi_set_pw_from_table(dev, &msg, mW); + ret = pd692x0_pi_set_pw_from_table(dev, &msg, max_mW); if (ret) return ret; @@ -1068,8 +1043,8 @@ static const struct pse_controller_ops pd692x0_ops = { .pi_disable = pd692x0_pi_disable, .pi_is_enabled = pd692x0_pi_is_enabled, .pi_get_voltage = pd692x0_pi_get_voltage, - .pi_get_current_limit = pd692x0_pi_get_current_limit, - .pi_set_current_limit = pd692x0_pi_set_current_limit, + .pi_get_pw_limit = pd692x0_pi_get_pw_limit, + .pi_set_pw_limit = pd692x0_pi_set_pw_limit, }; #define PD692X0_FW_LINE_MAX_SZ 0xff diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c index 432b6c2c04f83..ae819bfed1b10 100644 --- a/drivers/net/pse-pd/pse_core.c +++ b/drivers/net/pse-pd/pse_core.c @@ -291,33 +291,25 @@ static int pse_pi_get_voltage(struct regulator_dev *rdev) return ret; } -static int _pse_ethtool_get_status(struct pse_controller_dev *pcdev, - int id, - struct netlink_ext_ack *extack, - struct pse_control_status *status); - static int pse_pi_get_current_limit(struct regulator_dev *rdev) { struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev); const struct pse_controller_ops *ops; - struct netlink_ext_ack extack = {}; - struct pse_control_status st = {}; - int id, uV, ret; + int id, uV, mW, ret; s64 tmp_64; ops = pcdev->ops; id = rdev_get_id(rdev); + if (!ops->pi_get_pw_limit || !ops->pi_get_voltage) + return -EOPNOTSUPP; + mutex_lock(&pcdev->lock); - if (ops->pi_get_current_limit) { - ret = ops->pi_get_current_limit(pcdev, id); + ret = ops->pi_get_pw_limit(pcdev, id); + if (ret < 0) goto out; - } + mW = ret; - /* If pi_get_current_limit() callback not populated get voltage - * from pi_get_voltage() and power limit from ethtool_get_status() - * to calculate current limit. - */ - ret = _pse_pi_get_voltage(rdev); + ret = pse_pi_get_voltage(rdev); if (!ret) { dev_err(pcdev->dev, "Voltage null\n"); ret = -ERANGE; @@ -327,16 +319,7 @@ static int pse_pi_get_current_limit(struct regulator_dev *rdev) goto out; uV = ret; - ret = _pse_ethtool_get_status(pcdev, id, &extack, &st); - if (ret) - goto out; - - if (!st.c33_avail_pw_limit) { - ret = -ENODATA; - goto out; - } - - tmp_64 = st.c33_avail_pw_limit; + tmp_64 = mW; tmp_64 *= 1000000000ull; /* uA = mW * 1000000000 / uV */ ret = DIV_ROUND_CLOSEST_ULL(tmp_64, uV); @@ -351,10 +334,11 @@ static int pse_pi_set_current_limit(struct regulator_dev *rdev, int min_uA, { struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev); const struct pse_controller_ops *ops; - int id, ret; + int id, mW, ret; + s64 tmp_64; ops = pcdev->ops; - if (!ops->pi_set_current_limit) + if (!ops->pi_set_pw_limit || !ops->pi_get_voltage) return -EOPNOTSUPP; if (max_uA > MAX_PI_CURRENT) @@ -362,7 +346,21 @@ static int pse_pi_set_current_limit(struct regulator_dev *rdev, int min_uA, id = rdev_get_id(rdev); mutex_lock(&pcdev->lock); - ret = ops->pi_set_current_limit(pcdev, id, max_uA); + ret = pse_pi_get_voltage(rdev); + if (!ret) { + dev_err(pcdev->dev, "Voltage null\n"); + ret = -ERANGE; + goto out; + } + if (ret < 0) + goto out; + + tmp_64 = ret; + tmp_64 *= max_uA; + /* mW = uA * uV / 1000000000 */ + mW = DIV_ROUND_CLOSEST_ULL(tmp_64, 1000000000); + ret = ops->pi_set_pw_limit(pcdev, id, mW); +out: mutex_unlock(&pcdev->lock); return ret; @@ -406,7 +404,7 @@ devm_pse_pi_regulator_register(struct pse_controller_dev *pcdev, rinit_data->constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS; - if (pcdev->ops->pi_set_current_limit) + if (pcdev->ops->pi_set_pw_limit) rinit_data->constraints.valid_ops_mask |= REGULATOR_CHANGE_CURRENT; @@ -737,23 +735,6 @@ struct pse_control *of_pse_control_get(struct device_node *node) } EXPORT_SYMBOL_GPL(of_pse_control_get); -static int _pse_ethtool_get_status(struct pse_controller_dev *pcdev, - int id, - struct netlink_ext_ack *extack, - struct pse_control_status *status) -{ - const struct pse_controller_ops *ops; - - ops = pcdev->ops; - if (!ops->ethtool_get_status) { - NL_SET_ERR_MSG(extack, - "PSE driver does not support status report"); - return -EOPNOTSUPP; - } - - return ops->ethtool_get_status(pcdev, id, extack, status); -} - /** * pse_ethtool_get_status - get status of PSE control * @psec: PSE control pointer @@ -766,11 +747,21 @@ int pse_ethtool_get_status(struct pse_control *psec, struct netlink_ext_ack *extack, struct pse_control_status *status) { + const struct pse_controller_ops *ops; + struct pse_controller_dev *pcdev; int err; - mutex_lock(&psec->pcdev->lock); - err = _pse_ethtool_get_status(psec->pcdev, psec->id, extack, status); - mutex_unlock(&psec->pcdev->lock); + 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); + err = ops->ethtool_get_status(pcdev, psec->id, extack, status); + mutex_unlock(&pcdev->lock); return err; } diff --git a/include/linux/pse-pd/pse.h b/include/linux/pse-pd/pse.h index bc5addccbf32e..a721651cd1e0b 100644 --- a/include/linux/pse-pd/pse.h +++ b/include/linux/pse-pd/pse.h @@ -77,12 +77,8 @@ struct pse_control_status { * @pi_disable: Configure the PSE PI as disabled. * @pi_get_voltage: Return voltage similarly to get_voltage regulator * callback. - * @pi_get_current_limit: Get the configured current limit similarly to - * get_current_limit regulator callback. - * @pi_set_current_limit: Configure the current limit similarly to - * set_current_limit regulator callback. - * Should not return an error in case of MAX_PI_CURRENT - * current value set. + * @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. */ struct pse_controller_ops { int (*ethtool_get_status)(struct pse_controller_dev *pcdev, @@ -93,10 +89,10 @@ struct pse_controller_ops { 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); - int (*pi_get_current_limit)(struct pse_controller_dev *pcdev, - int id); - int (*pi_set_current_limit)(struct pse_controller_dev *pcdev, - int id, int max_uA); + int (*pi_get_pw_limit)(struct pse_controller_dev *pcdev, + int id); + int (*pi_set_pw_limit)(struct pse_controller_dev *pcdev, + int id, int max_mW); }; struct module; From 3e9dbfec499807767d03592ebdf19d9c15fd495b Mon Sep 17 00:00:00 2001 From: Kory Maincent Date: Fri, 10 Jan 2025 10:40:27 +0100 Subject: [PATCH 08/12] 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 --- 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(-) diff --git a/drivers/net/pse-pd/pd692x0.c b/drivers/net/pse-pd/pd692x0.c index 9f00538f7e450..da5d09ed628a3 100644 --- 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(struct device *dev, } 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(struct pse_controller_dev *pcdev, 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(struct pse_controller_dev *pcdev, 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(struct pse_controller_dev *pcdev, 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 diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c index ae819bfed1b10..5f2a9f36e4ed4 100644 --- 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_controller_dev *pcdev) 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; - return err; + 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}; + + 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); diff --git a/drivers/net/pse-pd/pse_regulator.c b/drivers/net/pse-pd/pse_regulator.c index 64ab36974fe04..86360056b2f5e 100644 --- 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_controller_dev *pcdev, int id) } 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_controller_dev *pcdev, unsigned long id, 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, diff --git a/drivers/net/pse-pd/tps23881.c b/drivers/net/pse-pd/tps23881.c index b87c391ae0f57..f735b6917f8bb 100644 --- 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 pse_controller_dev *pcdev, int id) 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(struct pse_controller_dev *pcdev, 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 tps23881_ops = { .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"; diff --git a/include/linux/pse-pd/pse.h b/include/linux/pse-pd/pse.h index a721651cd1e0b..3c544aff58b9b 100644 --- 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 *psec); 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); @@ -199,7 +262,7 @@ static inline void pse_control_put(struct pse_control *psec) 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; } diff --git a/net/ethtool/pse-pd.c b/net/ethtool/pse-pd.c index a0705edca22a1..2819e2ba6be2d 100644 --- 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 ethnl_req_info *req_base, 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 ethnl_req_info *req_base, } 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 *skb, 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, From 4640a1f0d8f2246f34d6e74330d7e7d2cf75605b Mon Sep 17 00:00:00 2001 From: Kory Maincent Date: Fri, 10 Jan 2025 10:40:28 +0100 Subject: [PATCH 09/12] 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 --- 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(-) diff --git a/drivers/net/pse-pd/pd692x0.c b/drivers/net/pse-pd/pd692x0.c index da5d09ed628a3..fc9e23927b3b7 100644 --- 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_controller_dev *pcdev, int id) 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 pd692x0_ops = { .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, diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c index 5f2a9f36e4ed4..887a477197a6d 100644 --- a/drivers/net/pse-pd/pse_core.c +++ b/drivers/net/pse-pd/pse_core.c @@ -210,16 +210,25 @@ static int of_load_pse_pis(struct pse_controller_dev *pcdev) 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; diff --git a/drivers/net/pse-pd/pse_regulator.c b/drivers/net/pse-pd/pse_regulator.c index 86360056b2f5e..6ce6773fff310 100644 --- a/drivers/net/pse-pd/pse_regulator.c +++ b/drivers/net/pse-pd/pse_regulator.c @@ -51,14 +51,6 @@ pse_reg_pi_disable(struct pse_controller_dev *pcdev, int id) return 0; } -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 pse_reg_ops = { .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, }; diff --git a/drivers/net/pse-pd/tps23881.c b/drivers/net/pse-pd/tps23881.c index f735b6917f8bb..340d70ee37fe3 100644 --- a/drivers/net/pse-pd/tps23881.c +++ b/drivers/net/pse-pd/tps23881.c @@ -174,33 +174,6 @@ static int tps23881_pi_disable(struct pse_controller_dev *pcdev, int id) 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 tps23881_ops = { .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, }; diff --git a/include/linux/pse-pd/pse.h b/include/linux/pse-pd/pse.h index 3c544aff58b9b..b5ae3dcee550a 100644 --- 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); From 7f076ce3f17334964590c2cce49a02c0851c099a Mon Sep 17 00:00:00 2001 From: Kory Maincent Date: Fri, 10 Jan 2025 10:40:29 +0100 Subject: [PATCH 10/12] 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 --- drivers/net/pse-pd/tps23881.c | 258 +++++++++++++++++++++++++++++++++- 1 file changed, 256 insertions(+), 2 deletions(-) diff --git a/drivers/net/pse-pd/tps23881.c b/drivers/net/pse-pd/tps23881.c index 340d70ee37fe3..5e9dda2c0eac7 100644 --- 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, u8 chan, u8 field_offset, 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 pse_controller_dev *pcdev, int id) 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_controller_dev *pcdev, int id, 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 tps23881_priv *priv, 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 tps23881_ops = { .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"; From 10276f3e1c7e7f5de9f0bba58f8a849cb195253d Mon Sep 17 00:00:00 2001 From: Kory Maincent Date: Fri, 10 Jan 2025 10:40:30 +0100 Subject: [PATCH 11/12] 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 --- drivers/net/pse-pd/pse_core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c index 887a477197a6d..b0272616a861d 100644 --- 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 pse_controller_dev *pcdev, 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)) { From 5385f1e1923ca8131eb143567d509b101a344e06 Mon Sep 17 00:00:00 2001 From: Kory Maincent Date: Fri, 10 Jan 2025 10:40:31 +0100 Subject: [PATCH 12/12] 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 --- 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(-) diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c index b0272616a861d..4f2a54afc4d09 100644 --- 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 diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h index 4bf70cfec8268..20a86bd5f4e34 100644 --- a/include/linux/ethtool.h +++ b/include/linux/ethtool.h @@ -1326,24 +1326,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 */ diff --git a/include/linux/pse-pd/pse.h b/include/linux/pse-pd/pse.h index b5ae3dcee550a..c773eeb92d041 100644 --- 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.