diff options
Diffstat (limited to 'drivers/net/pse-pd')
-rw-r--r-- | drivers/net/pse-pd/pd692x0.c | 179 | ||||
-rw-r--r-- | drivers/net/pse-pd/pse_core.c | 101 | ||||
-rw-r--r-- | drivers/net/pse-pd/pse_regulator.c | 23 | ||||
-rw-r--r-- | drivers/net/pse-pd/tps23881.c | 449 |
4 files changed, 580 insertions, 172 deletions
diff --git a/drivers/net/pse-pd/pd692x0.c b/drivers/net/pse-pd/pd692x0.c index 7cfc36cadb57..7d60a714ca53 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; @@ -517,21 +492,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 +605,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 +647,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 +714,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; - ret = pd692x0_pi_get_pw_ranges(status); + 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; + + 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 +1073,17 @@ 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 bb509d973e91..4602e26eb8c8 100644 --- a/drivers/net/pse-pd/pse_core.c +++ b/drivers/net/pse-pd/pse_core.c @@ -6,6 +6,7 @@ // #include <linux/device.h> +#include <linux/ethtool.h> #include <linux/of.h> #include <linux/pse-pd/pse.h> #include <linux/regulator/driver.h> @@ -210,16 +211,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; @@ -413,6 +423,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)) { @@ -443,6 +454,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 +763,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); @@ -868,6 +942,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/drivers/net/pse-pd/pse_regulator.c b/drivers/net/pse-pd/pse_regulator.c index 64ab36974fe0..6ce6773fff31 100644 --- a/drivers/net/pse-pd/pse_regulator.c +++ b/drivers/net/pse-pd/pse_regulator.c @@ -52,17 +52,19 @@ pse_reg_pi_disable(struct pse_controller_dev *pcdev, int id) } static int -pse_reg_pi_is_enabled(struct pse_controller_dev *pcdev, int id) +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); - return regulator_is_enabled(priv->ps); + admin_state->podl_admin_state = priv->admin_state; + + return 0; } 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_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,20 +74,19 @@ 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 8797ca1a8a21..5e9dda2c0eac 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 { @@ -53,36 +65,123 @@ 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_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); struct i2c_client *client = priv->client; u8 chan; u16 val; - int ret; if (id >= TPS23881_MAX_CHANS) 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)); } - 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) @@ -97,32 +196,67 @@ 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)); } ret = i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val); if (ret) return ret; - return 0; + /* 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)); + } + + 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 tps23881_pi_is_enabled(struct pse_controller_dev *pcdev, int id) +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; u8 chan; + u16 val; int ret; ret = i2c_smbus_read_word_data(client, TPS23881_REG_PW_STATUS); @@ -130,32 +264,35 @@ 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 */ - return enabled; + 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_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_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 enabled, delivering; + bool delivering; u8 chan; + u16 val; int ret; ret = i2c_smbus_read_word_data(client, TPS23881_REG_PW_STATUS); @@ -163,40 +300,197 @@ 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, 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, 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; + pw_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; - 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; } +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, @@ -480,7 +774,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; @@ -491,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); @@ -511,11 +808,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 +857,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 +883,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) @@ -626,8 +911,14 @@ 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, - .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, + .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"; |