diff options
Diffstat (limited to 'drivers/net/pse-pd/tps23881.c')
-rw-r--r-- | drivers/net/pse-pd/tps23881.c | 449 |
1 files changed, 370 insertions, 79 deletions
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"; |