diff options
Diffstat (limited to 'drivers/net/pse-pd/tps23881.c')
-rw-r--r-- | drivers/net/pse-pd/tps23881.c | 842 |
1 files changed, 761 insertions, 81 deletions
diff --git a/drivers/net/pse-pd/tps23881.c b/drivers/net/pse-pd/tps23881.c index 5c4e88be46ee..63f8f43062bc 100644 --- a/drivers/net/pse-pd/tps23881.c +++ b/drivers/net/pse-pd/tps23881.c @@ -16,29 +16,61 @@ #include <linux/pse-pd/pse.h> #define TPS23881_MAX_CHANS 8 - +#define TPS23881_MAX_IRQ_RETRIES 10 + +#define TPS23881_REG_IT 0x0 +#define TPS23881_REG_IT_MASK 0x1 +#define TPS23881_REG_IT_DISF BIT(2) +#define TPS23881_REG_IT_DETC BIT(3) +#define TPS23881_REG_IT_CLASC BIT(4) +#define TPS23881_REG_IT_IFAULT BIT(5) +#define TPS23881_REG_IT_SUPF BIT(7) +#define TPS23881_REG_DET_EVENT 0x5 +#define TPS23881_REG_FAULT 0x7 +#define TPS23881_REG_SUPF_EVENT 0xb +#define TPS23881_REG_TSD BIT(7) +#define TPS23881_REG_DISC 0xc #define TPS23881_REG_PW_STATUS 0x10 #define TPS23881_REG_OP_MODE 0x12 +#define TPS23881_REG_DISC_EN 0x13 #define TPS23881_OP_MODE_SEMIAUTO 0xaaaa #define TPS23881_REG_DIS_EN 0x13 #define TPS23881_REG_DET_CLA_EN 0x14 #define TPS23881_REG_GEN_MASK 0x17 +#define TPS23881_REG_CLCHE BIT(2) +#define TPS23881_REG_DECHE BIT(3) #define TPS23881_REG_NBITACC BIT(5) +#define TPS23881_REG_INTEN BIT(7) #define TPS23881_REG_PW_EN 0x19 +#define TPS23881_REG_RESET 0x1a +#define TPS23881_REG_CLRAIN BIT(7) +#define TPS23881_REG_2PAIR_POL1 0x1e #define TPS23881_REG_PORT_MAP 0x26 #define TPS23881_REG_PORT_POWER 0x29 -#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; + bool exist; }; struct tps23881_priv { @@ -53,6 +85,103 @@ 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); @@ -64,28 +193,30 @@ static int tps23881_pi_enable(struct pse_controller_dev *pcdev, int id) if (id >= TPS23881_MAX_CHANS) return -ERANGE; - ret = i2c_smbus_read_word_data(client, TPS23881_REG_PW_STATUS); - if (ret < 0) - return ret; - chan = priv->port[id].chan[0]; - if (chan < 4) - val = (u16)(ret | BIT(chan)); - else - val = (u16)(ret | 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; + /* Enable DC disconnect*/ + chan = priv->port[id].chan[0]; + ret = i2c_smbus_read_word_data(client, TPS23881_REG_DISC_EN); + if (ret < 0) + return ret; + + val = tps23881_set_val(ret, chan, 0, BIT(chan % 4), BIT(chan % 4)); + ret = i2c_smbus_write_word_data(client, TPS23881_REG_DISC_EN, val); + if (ret) + return ret; + return 0; } @@ -100,37 +231,79 @@ static int tps23881_pi_disable(struct pse_controller_dev *pcdev, int id) if (id >= TPS23881_MAX_CHANS) return -ERANGE; - ret = i2c_smbus_read_word_data(client, TPS23881_REG_PW_STATUS); + chan = priv->port[id].chan[0]; + 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]; + 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; + + /* 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); + + /* Disable DC disconnect*/ + chan = priv->port[id].chan[0]; + ret = i2c_smbus_read_word_data(client, TPS23881_REG_DISC_EN); + if (ret < 0) + return ret; + + val = tps23881_set_val(ret, chan, 0, 0, BIT(chan % 4)); + ret = i2c_smbus_write_word_data(client, TPS23881_REG_DISC_EN, val); + if (ret) + return ret; + + /* Enable detection and classification */ + ret = i2c_smbus_read_word_data(client, TPS23881_REG_DET_CLA_EN); if (ret < 0) return ret; chan = priv->port[id].chan[0]; - if (chan < 4) - val = (u16)(ret | BIT(chan + 4)); - else - val = (u16)(ret | BIT(chan + 8)); + 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]; - if (chan < 4) - val |= BIT(chan + 4); - else - val |= BIT(chan + 8); + 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_PW_EN, val); + ret = i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val); if (ret) return ret; - return 0; + /* 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); @@ -138,32 +311,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); @@ -171,40 +347,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; - 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; } +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, @@ -488,7 +821,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; @@ -496,8 +829,13 @@ tps23881_write_port_matrix(struct tps23881_priv *priv, hw_chan = port_matrix[i].hw_chan[0] % 4; /* Set software port matrix for existing ports */ - if (port_matrix[i].exist) + if (port_matrix[i].exist) { priv->port[pi_id].chan[0] = lgcl_chan; + priv->port[pi_id].exist = true; + } + + /* Initialize power policy internal value */ + priv->port[pi_id].pw_pol = -1; /* Set hardware port matrix for all ports */ val |= hw_chan << (lgcl_chan * 2); @@ -519,11 +857,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 @@ -572,11 +906,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 @@ -602,11 +932,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) @@ -630,12 +956,60 @@ static int tps23881_setup_pi_matrix(struct pse_controller_dev *pcdev) return ret; } +static int tps23881_power_class_table[] = { + -ERANGE, + 4000, + 7000, + 15500, + 30000, + 15500, + 15500, + -ERANGE, + 45000, + 60000, + 75000, + 90000, + 15500, + 45000, + -ERANGE, + -ERANGE, +}; + +static int tps23881_pi_get_pw_req(struct pse_controller_dev *pcdev, int id) +{ + struct tps23881_priv *priv = to_tps23881_priv(pcdev); + struct i2c_client *client = priv->client; + u8 reg, chan; + int ret; + u16 val; + + /* For a 4-pair the classification need 5ms to be completed */ + if (priv->port[id].is_4p) + mdelay(5); + + chan = priv->port[id].chan[0]; + reg = TPS23881_REG_DISC + (chan % 4); + ret = i2c_smbus_read_word_data(client, reg); + if (ret < 0) + return ret; + + val = tps23881_calc_val(ret, chan, 4, 0xf); + return tps23881_power_class_table[val]; +} + static const struct pse_controller_ops tps23881_ops = { .setup_pi_matrix = tps23881_setup_pi_matrix, .pi_enable = tps23881_pi_enable, .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, + .pi_get_pw_req = tps23881_pi_get_pw_req, }; static const char fw_parity_name[] = "ti/tps23881/tps23881-parity-14.bin"; @@ -734,6 +1108,307 @@ static int tps23881_flash_sram_fw(struct i2c_client *client) return 0; } +/* Convert interrupt events to 0xff to be aligned with the chan + * number. + */ +static u8 tps23881_irq_export_chans_helper(u16 reg_val, u8 field_offset) +{ + u8 val; + + val = (reg_val >> (4 + field_offset) & 0xf0) | + (reg_val >> field_offset & 0x0f); + + return val; +} + +/* Convert chan number to port number */ +static void tps23881_set_notifs_helper(struct tps23881_priv *priv, + u8 chans, + unsigned long *notifs, + unsigned long *notifs_mask, + enum ethtool_pse_event event) +{ + u8 chan; + int i; + + if (!chans) + return; + + for (i = 0; i < TPS23881_MAX_CHANS; i++) { + if (!priv->port[i].exist) + continue; + /* No need to look at the 2nd channel in case of PoE4 as + * both registers are set. + */ + chan = priv->port[i].chan[0]; + + if (BIT(chan) & chans) { + *notifs_mask |= BIT(i); + notifs[i] |= event; + } + } +} + +static void tps23881_irq_event_over_temp(struct tps23881_priv *priv, + u16 reg_val, + unsigned long *notifs, + unsigned long *notifs_mask) +{ + int i; + + if (reg_val & TPS23881_REG_TSD) { + for (i = 0; i < TPS23881_MAX_CHANS; i++) { + if (!priv->port[i].exist) + continue; + + *notifs_mask |= BIT(i); + notifs[i] |= ETHTOOL_PSE_EVENT_OVER_TEMP; + } + } +} + +static int tps23881_irq_event_over_current(struct tps23881_priv *priv, + u16 reg_val, + unsigned long *notifs, + unsigned long *notifs_mask) +{ + int i, ret; + u8 chans; + + chans = tps23881_irq_export_chans_helper(reg_val, 0); + if (!chans) + return 0; + + tps23881_set_notifs_helper(priv, chans, notifs, notifs_mask, + ETHTOOL_PSE_EVENT_OVER_CURRENT | + ETHTOOL_C33_PSE_EVENT_DISCONNECTION); + + /* Over Current event resets the power limit registers so we need + * to configured it again. + */ + for_each_set_bit(i, notifs_mask, priv->pcdev.nr_lines) { + if (priv->port[i].pw_pol < 0) + continue; + + ret = tps23881_pi_enable_manual_pol(priv, i); + if (ret < 0) + return ret; + + /* Set power policy */ + ret = tps23881_pi_set_pw_pol_limit(priv, i, + priv->port[i].pw_pol, + priv->port[i].is_4p); + if (ret < 0) + return ret; + } + + return 0; +} + +static void tps23881_irq_event_disconnection(struct tps23881_priv *priv, + u16 reg_val, + unsigned long *notifs, + unsigned long *notifs_mask) +{ + u8 chans; + + chans = tps23881_irq_export_chans_helper(reg_val, 4); + if (chans) + tps23881_set_notifs_helper(priv, chans, notifs, notifs_mask, + ETHTOOL_C33_PSE_EVENT_DISCONNECTION); +} + +static int tps23881_irq_event_detection(struct tps23881_priv *priv, + u16 reg_val, + unsigned long *notifs, + unsigned long *notifs_mask) +{ + enum ethtool_pse_event event; + int reg, ret, i, val; + unsigned long chans; + + chans = tps23881_irq_export_chans_helper(reg_val, 0); + for_each_set_bit(i, &chans, TPS23881_MAX_CHANS) { + reg = TPS23881_REG_DISC + (i % 4); + ret = i2c_smbus_read_word_data(priv->client, reg); + if (ret < 0) + return ret; + + val = tps23881_calc_val(ret, i, 0, 0xf); + /* If detection valid */ + if (val == 0x4) + event = ETHTOOL_C33_PSE_EVENT_DETECTION; + else + event = ETHTOOL_C33_PSE_EVENT_DISCONNECTION; + + tps23881_set_notifs_helper(priv, BIT(i), notifs, + notifs_mask, event); + } + + return 0; +} + +static int tps23881_irq_event_classification(struct tps23881_priv *priv, + u16 reg_val, + unsigned long *notifs, + unsigned long *notifs_mask) +{ + int reg, ret, val, i; + unsigned long chans; + + chans = tps23881_irq_export_chans_helper(reg_val, 4); + for_each_set_bit(i, &chans, TPS23881_MAX_CHANS) { + reg = TPS23881_REG_DISC + (i % 4); + ret = i2c_smbus_read_word_data(priv->client, reg); + if (ret < 0) + return ret; + + val = tps23881_calc_val(ret, i, 4, 0xf); + /* Do not report classification event for unknown class */ + if (!val || val == 0x8 || val == 0xf) + continue; + + tps23881_set_notifs_helper(priv, BIT(i), notifs, + notifs_mask, + ETHTOOL_C33_PSE_EVENT_CLASSIFICATION); + } + + return 0; +} + +static int tps23881_irq_event_handler(struct tps23881_priv *priv, u16 reg, + unsigned long *notifs, + unsigned long *notifs_mask) +{ + struct i2c_client *client = priv->client; + int ret, val; + + /* The Supply event bit is repeated twice so we only need to read + * the one from the first byte. + */ + if (reg & TPS23881_REG_IT_SUPF) { + ret = i2c_smbus_read_word_data(client, TPS23881_REG_SUPF_EVENT); + if (ret < 0) + return ret; + tps23881_irq_event_over_temp(priv, ret, notifs, notifs_mask); + } + + if (reg & (TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_IFAULT << 8 | + TPS23881_REG_IT_DISF | TPS23881_REG_IT_DISF << 8)) { + ret = i2c_smbus_read_word_data(client, TPS23881_REG_FAULT); + if (ret < 0) + return ret; + ret = tps23881_irq_event_over_current(priv, ret, notifs, + notifs_mask); + if (ret) + return ret; + + tps23881_irq_event_disconnection(priv, ret, notifs, notifs_mask); + } + + if (reg & (TPS23881_REG_IT_DETC | TPS23881_REG_IT_DETC << 8 | + TPS23881_REG_IT_CLASC | TPS23881_REG_IT_CLASC << 8)) { + ret = i2c_smbus_read_word_data(client, TPS23881_REG_DET_EVENT); + if (ret < 0) + return ret; + + val = ret; + ret = tps23881_irq_event_detection(priv, val, notifs, + notifs_mask); + if (ret) + return ret; + + ret = tps23881_irq_event_classification(priv, val, notifs, + notifs_mask); + if (ret) + return ret; + } + return 0; +} + +static int tps23881_irq_handler(int irq, struct pse_controller_dev *pcdev, + unsigned long *notifs, + unsigned long *notifs_mask) +{ + struct tps23881_priv *priv = to_tps23881_priv(pcdev); + struct i2c_client *client = priv->client; + int ret, it_mask, retry; + + /* Get interruption mask */ + ret = i2c_smbus_read_word_data(client, TPS23881_REG_IT_MASK); + if (ret < 0) + return ret; + it_mask = ret; + + /* Read interrupt register until it frees the interruption pin. */ + retry = 0; + while (true) { + if (retry > TPS23881_MAX_IRQ_RETRIES) { + dev_err(&client->dev, "interrupt never freed"); + return -ETIMEDOUT; + } + + ret = i2c_smbus_read_word_data(client, TPS23881_REG_IT); + if (ret < 0) + return ret; + + /* No more relevant interruption */ + if (!(ret & it_mask)) + return 0; + + ret = tps23881_irq_event_handler(priv, (u16)ret, notifs, + notifs_mask); + if (ret) + return ret; + + retry++; + } + return 0; +} + +static int tps23881_setup_irq(struct tps23881_priv *priv, int irq) +{ + struct i2c_client *client = priv->client; + struct pse_irq_desc irq_desc = { + .name = "tps23881-irq", + .map_event = tps23881_irq_handler, + }; + int ret; + u16 val; + + if (!irq) { + dev_err(&client->dev, "interrupt is missing"); + return -EINVAL; + } + + val = TPS23881_REG_IT_IFAULT | TPS23881_REG_IT_SUPF | + TPS23881_REG_IT_DETC | TPS23881_REG_IT_CLASC | + TPS23881_REG_IT_DISF; + val |= val << 8; + ret = i2c_smbus_write_word_data(client, TPS23881_REG_IT_MASK, val); + if (ret) + return ret; + + ret = i2c_smbus_read_word_data(client, TPS23881_REG_GEN_MASK); + if (ret < 0) + return ret; + + val = TPS23881_REG_INTEN | TPS23881_REG_CLCHE | TPS23881_REG_DECHE; + val |= val << 8; + val |= (u16)ret; + ret = i2c_smbus_write_word_data(client, TPS23881_REG_GEN_MASK, val); + if (ret < 0) + return ret; + + /* Reset interrupts registers */ + ret = i2c_smbus_write_word_data(client, TPS23881_REG_RESET, + TPS23881_REG_CLRAIN); + if (ret < 0) + return ret; + + return devm_pse_irq_helper(&priv->pcdev, irq, 0, &irq_desc); +} + static int tps23881_i2c_probe(struct i2c_client *client) { struct device *dev = &client->dev; @@ -808,12 +1483,17 @@ static int tps23881_i2c_probe(struct i2c_client *client) priv->pcdev.dev = dev; priv->pcdev.types = ETHTOOL_PSE_C33; priv->pcdev.nr_lines = TPS23881_MAX_CHANS; + priv->pcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_STATIC; ret = devm_pse_controller_register(dev, &priv->pcdev); if (ret) { return dev_err_probe(dev, ret, "failed to register PSE controller\n"); } + ret = tps23881_setup_irq(priv, client->irq); + if (ret) + return ret; + return ret; } |