| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Driver for the TI TPS23881 PoE PSE Controller driver (I2C bus) | |
| * | |
| * Copyright (c) 2023 Bootlin, Kory Maincent <kory.maincent@bootlin.com> | |
| */ | |
| #include <linux/bitfield.h> | |
| #include <linux/delay.h> | |
| #include <linux/firmware.h> | |
| #include <linux/gpio/consumer.h> | |
| #include <linux/i2c.h> | |
| #include <linux/module.h> | |
| #include <linux/of.h> | |
| #include <linux/platform_device.h> | |
| #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_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_CHAN1_CLASS 0x4c | |
| #define TPS23881_REG_SRAM_CTRL 0x60 | |
| #define TPS23881_REG_SRAM_DATA 0x61 | |
| #define TPS23881_UV_STEP 3662 | |
| #define TPS23881_NA_STEP 89500 | |
| #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 { | |
| struct i2c_client *client; | |
| struct pse_controller_dev pcdev; | |
| struct device_node *np; | |
| struct tps23881_port_desc port[TPS23881_MAX_CHANS]; | |
| }; | |
| 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]; | |
| 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]; | |
| 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; | |
| } | |
| static int tps23881_pi_disable(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]; | |
| 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]; | |
| 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_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); | |
| 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 */ | |
| 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) | |
| pw_status->c33_pw_status = | |
| ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING; | |
| else | |
| pw_status->c33_pw_status = | |
| ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED; | |
| return 0; | |
| } | |
| static int 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_obj(*c33_pw_limit_ranges); | |
| 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, | |
| struct device_node *chan_node[TPS23881_MAX_CHANS]) | |
| { | |
| struct device_node *channels_node, *node; | |
| int i, ret; | |
| if (!priv->np) | |
| return -EINVAL; | |
| channels_node = of_find_node_by_name(priv->np, "channels"); | |
| if (!channels_node) | |
| return -EINVAL; | |
| for_each_child_of_node(channels_node, node) { | |
| u32 chan_id; | |
| if (!of_node_name_eq(node, "channel")) | |
| continue; | |
| ret = of_property_read_u32(node, "reg", &chan_id); | |
| if (ret) { | |
| ret = -EINVAL; | |
| goto out; | |
| } | |
| if (chan_id >= TPS23881_MAX_CHANS || chan_node[chan_id]) { | |
| dev_err(&priv->client->dev, | |
| "wrong number of port (%d)\n", chan_id); | |
| ret = -EINVAL; | |
| goto out; | |
| } | |
| of_node_get(node); | |
| chan_node[chan_id] = node; | |
| } | |
| of_node_put(channels_node); | |
| return 0; | |
| out: | |
| for (i = 0; i < TPS23881_MAX_CHANS; i++) { | |
| of_node_put(chan_node[i]); | |
| chan_node[i] = NULL; | |
| } | |
| of_node_put(node); | |
| of_node_put(channels_node); | |
| return ret; | |
| } | |
| struct tps23881_port_matrix { | |
| u8 pi_id; | |
| u8 lgcl_chan[2]; | |
| u8 hw_chan[2]; | |
| bool is_4p; | |
| bool exist; | |
| }; | |
| static int | |
| tps23881_match_channel(const struct pse_pi_pairset *pairset, | |
| struct device_node *chan_node[TPS23881_MAX_CHANS]) | |
| { | |
| int i; | |
| /* Look on every channels */ | |
| for (i = 0; i < TPS23881_MAX_CHANS; i++) { | |
| if (pairset->np == chan_node[i]) | |
| return i; | |
| } | |
| return -ENODEV; | |
| } | |
| static bool | |
| tps23881_is_chan_free(struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS], | |
| int chan) | |
| { | |
| int i; | |
| for (i = 0; i < TPS23881_MAX_CHANS; i++) { | |
| if (port_matrix[i].exist && | |
| (port_matrix[i].hw_chan[0] == chan || | |
| port_matrix[i].hw_chan[1] == chan)) | |
| return false; | |
| } | |
| return true; | |
| } | |
| /* Fill port matrix with the matching channels */ | |
| static int | |
| tps23881_match_port_matrix(struct pse_pi *pi, int pi_id, | |
| struct device_node *chan_node[TPS23881_MAX_CHANS], | |
| struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS]) | |
| { | |
| int ret; | |
| if (!pi->pairset[0].np) | |
| return 0; | |
| ret = tps23881_match_channel(&pi->pairset[0], chan_node); | |
| if (ret < 0) | |
| return ret; | |
| if (!tps23881_is_chan_free(port_matrix, ret)) { | |
| pr_err("tps23881: channel %d already used\n", ret); | |
| return -ENODEV; | |
| } | |
| port_matrix[pi_id].hw_chan[0] = ret; | |
| port_matrix[pi_id].exist = true; | |
| if (!pi->pairset[1].np) | |
| return 0; | |
| ret = tps23881_match_channel(&pi->pairset[1], chan_node); | |
| if (ret < 0) | |
| return ret; | |
| if (!tps23881_is_chan_free(port_matrix, ret)) { | |
| pr_err("tps23881: channel %d already used\n", ret); | |
| return -ENODEV; | |
| } | |
| if (port_matrix[pi_id].hw_chan[0] / 4 != ret / 4) { | |
| pr_err("tps23881: 4-pair PSE can only be set within the same 4 ports group"); | |
| return -ENODEV; | |
| } | |
| port_matrix[pi_id].hw_chan[1] = ret; | |
| port_matrix[pi_id].is_4p = true; | |
| return 0; | |
| } | |
| static int | |
| tps23881_get_unused_chan(struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS], | |
| int port_cnt) | |
| { | |
| bool used; | |
| int i, j; | |
| for (i = 0; i < TPS23881_MAX_CHANS; i++) { | |
| used = false; | |
| for (j = 0; j < port_cnt; j++) { | |
| if (port_matrix[j].hw_chan[0] == i) { | |
| used = true; | |
| break; | |
| } | |
| if (port_matrix[j].is_4p && | |
| port_matrix[j].hw_chan[1] == i) { | |
| used = true; | |
| break; | |
| } | |
| } | |
| if (!used) | |
| return i; | |
| } | |
| return -ENODEV; | |
| } | |
| /* Sort the port matrix to following particular hardware ports matrix | |
| * specification of the tps23881. The device has two 4-ports groups and | |
| * each 4-pair powered device has to be configured to use two consecutive | |
| * logical channel in each 4 ports group (1 and 2 or 3 and 4). Also the | |
| * hardware matrix has to be fully configured even with unused chan to be | |
| * valid. | |
| */ | |
| static int | |
| tps23881_sort_port_matrix(struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS]) | |
| { | |
| struct tps23881_port_matrix tmp_port_matrix[TPS23881_MAX_CHANS] = {0}; | |
| int i, ret, port_cnt = 0, cnt_4ch_grp1 = 0, cnt_4ch_grp2 = 4; | |
| /* Configure 4p port matrix */ | |
| for (i = 0; i < TPS23881_MAX_CHANS; i++) { | |
| int *cnt; | |
| if (!port_matrix[i].exist || !port_matrix[i].is_4p) | |
| continue; | |
| if (port_matrix[i].hw_chan[0] < 4) | |
| cnt = &cnt_4ch_grp1; | |
| else | |
| cnt = &cnt_4ch_grp2; | |
| tmp_port_matrix[port_cnt].exist = true; | |
| tmp_port_matrix[port_cnt].is_4p = true; | |
| tmp_port_matrix[port_cnt].pi_id = i; | |
| tmp_port_matrix[port_cnt].hw_chan[0] = port_matrix[i].hw_chan[0]; | |
| tmp_port_matrix[port_cnt].hw_chan[1] = port_matrix[i].hw_chan[1]; | |
| /* 4-pair ports have to be configured with consecutive | |
| * logical channels 0 and 1, 2 and 3. | |
| */ | |
| tmp_port_matrix[port_cnt].lgcl_chan[0] = (*cnt)++; | |
| tmp_port_matrix[port_cnt].lgcl_chan[1] = (*cnt)++; | |
| port_cnt++; | |
| } | |
| /* Configure 2p port matrix */ | |
| for (i = 0; i < TPS23881_MAX_CHANS; i++) { | |
| int *cnt; | |
| if (!port_matrix[i].exist || port_matrix[i].is_4p) | |
| continue; | |
| if (port_matrix[i].hw_chan[0] < 4) | |
| cnt = &cnt_4ch_grp1; | |
| else | |
| cnt = &cnt_4ch_grp2; | |
| tmp_port_matrix[port_cnt].exist = true; | |
| tmp_port_matrix[port_cnt].pi_id = i; | |
| tmp_port_matrix[port_cnt].lgcl_chan[0] = (*cnt)++; | |
| tmp_port_matrix[port_cnt].hw_chan[0] = port_matrix[i].hw_chan[0]; | |
| port_cnt++; | |
| } | |
| /* Complete the rest of the first 4 port group matrix even if | |
| * channels are unused | |
| */ | |
| while (cnt_4ch_grp1 < 4) { | |
| ret = tps23881_get_unused_chan(tmp_port_matrix, port_cnt); | |
| if (ret < 0) { | |
| pr_err("tps23881: port matrix issue, no chan available\n"); | |
| return ret; | |
| } | |
| if (port_cnt >= TPS23881_MAX_CHANS) { | |
| pr_err("tps23881: wrong number of channels\n"); | |
| return -ENODEV; | |
| } | |
| tmp_port_matrix[port_cnt].lgcl_chan[0] = cnt_4ch_grp1; | |
| tmp_port_matrix[port_cnt].hw_chan[0] = ret; | |
| cnt_4ch_grp1++; | |
| port_cnt++; | |
| } | |
| /* Complete the rest of the second 4 port group matrix even if | |
| * channels are unused | |
| */ | |
| while (cnt_4ch_grp2 < 8) { | |
| ret = tps23881_get_unused_chan(tmp_port_matrix, port_cnt); | |
| if (ret < 0) { | |
| pr_err("tps23881: port matrix issue, no chan available\n"); | |
| return -ENODEV; | |
| } | |
| if (port_cnt >= TPS23881_MAX_CHANS) { | |
| pr_err("tps23881: wrong number of channels\n"); | |
| return -ENODEV; | |
| } | |
| tmp_port_matrix[port_cnt].lgcl_chan[0] = cnt_4ch_grp2; | |
| tmp_port_matrix[port_cnt].hw_chan[0] = ret; | |
| cnt_4ch_grp2++; | |
| port_cnt++; | |
| } | |
| memcpy(port_matrix, tmp_port_matrix, sizeof(tmp_port_matrix)); | |
| return port_cnt; | |
| } | |
| /* Write port matrix to the hardware port matrix and the software port | |
| * matrix. | |
| */ | |
| static int | |
| tps23881_write_port_matrix(struct tps23881_priv *priv, | |
| struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS], | |
| int port_cnt) | |
| { | |
| struct i2c_client *client = priv->client; | |
| u8 pi_id, lgcl_chan, hw_chan; | |
| u16 val = 0; | |
| int i; | |
| for (i = 0; i < port_cnt; i++) { | |
| pi_id = port_matrix[i].pi_id; | |
| lgcl_chan = port_matrix[i].lgcl_chan[0]; | |
| hw_chan = port_matrix[i].hw_chan[0] % 4; | |
| /* Set software port matrix for existing ports */ | |
| 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); | |
| if (!port_matrix[i].is_4p) | |
| continue; | |
| lgcl_chan = port_matrix[i].lgcl_chan[1]; | |
| hw_chan = port_matrix[i].hw_chan[1] % 4; | |
| /* Set software port matrix for existing ports */ | |
| if (port_matrix[i].exist) { | |
| priv->port[pi_id].is_4p = true; | |
| priv->port[pi_id].chan[1] = lgcl_chan; | |
| } | |
| /* Set hardware port matrix for all ports */ | |
| val |= hw_chan << (lgcl_chan * 2); | |
| } | |
| /* Write hardware ports matrix */ | |
| return i2c_smbus_write_word_data(client, TPS23881_REG_PORT_MAP, val); | |
| } | |
| static int | |
| tps23881_set_ports_conf(struct tps23881_priv *priv, | |
| struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS]) | |
| { | |
| struct i2c_client *client = priv->client; | |
| int i, ret; | |
| u16 val; | |
| /* Set operating mode */ | |
| ret = i2c_smbus_write_word_data(client, TPS23881_REG_OP_MODE, | |
| TPS23881_OP_MODE_SEMIAUTO); | |
| if (ret) | |
| return ret; | |
| /* Disable DC disconnect */ | |
| ret = i2c_smbus_write_word_data(client, TPS23881_REG_DIS_EN, 0x0); | |
| if (ret) | |
| return ret; | |
| /* Set port power allocation */ | |
| val = 0; | |
| for (i = 0; i < TPS23881_MAX_CHANS; i++) { | |
| if (!port_matrix[i].exist) | |
| continue; | |
| if (port_matrix[i].is_4p) | |
| val |= 0xf << ((port_matrix[i].lgcl_chan[0] / 2) * 4); | |
| else | |
| val |= 0x3 << ((port_matrix[i].lgcl_chan[0] / 2) * 4); | |
| } | |
| ret = i2c_smbus_write_word_data(client, TPS23881_REG_PORT_POWER, val); | |
| if (ret) | |
| return ret; | |
| /* Enable detection and classification */ | |
| val = 0; | |
| for (i = 0; i < TPS23881_MAX_CHANS; i++) { | |
| if (!port_matrix[i].exist) | |
| continue; | |
| val |= BIT(port_matrix[i].lgcl_chan[0]) | | |
| BIT(port_matrix[i].lgcl_chan[0] + 4); | |
| if (port_matrix[i].is_4p) | |
| val |= BIT(port_matrix[i].lgcl_chan[1]) | | |
| BIT(port_matrix[i].lgcl_chan[1] + 4); | |
| } | |
| return i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val); | |
| } | |
| static int | |
| tps23881_set_ports_matrix(struct tps23881_priv *priv, | |
| struct device_node *chan_node[TPS23881_MAX_CHANS]) | |
| { | |
| struct tps23881_port_matrix port_matrix[TPS23881_MAX_CHANS] = {0}; | |
| int i, ret; | |
| /* Update with values for every PSE PIs */ | |
| for (i = 0; i < TPS23881_MAX_CHANS; i++) { | |
| ret = tps23881_match_port_matrix(&priv->pcdev.pi[i], i, | |
| chan_node, port_matrix); | |
| if (ret) | |
| return ret; | |
| } | |
| ret = tps23881_sort_port_matrix(port_matrix); | |
| if (ret < 0) | |
| return ret; | |
| ret = tps23881_write_port_matrix(priv, port_matrix, ret); | |
| if (ret) | |
| return ret; | |
| return tps23881_set_ports_conf(priv, port_matrix); | |
| } | |
| static int tps23881_setup_pi_matrix(struct pse_controller_dev *pcdev) | |
| { | |
| struct device_node *chan_node[TPS23881_MAX_CHANS] = {NULL}; | |
| struct tps23881_priv *priv = to_tps23881_priv(pcdev); | |
| int ret, i; | |
| ret = tps23881_get_of_channels(priv, chan_node); | |
| if (ret < 0) { | |
| dev_warn(&priv->client->dev, | |
| "Unable to parse port-matrix, default matrix will be used\n"); | |
| return 0; | |
| } | |
| ret = tps23881_set_ports_matrix(priv, chan_node); | |
| for (i = 0; i < TPS23881_MAX_CHANS; i++) | |
| of_node_put(chan_node[i]); | |
| 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_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, | |
| }; | |
| struct tps23881_info { | |
| u8 dev_id; /* device ID and silicon revision */ | |
| const char *fw_parity_name; /* parity code firmware file name */ | |
| const char *fw_sram_name; /* SRAM code firmware file name */ | |
| }; | |
| enum tps23881_model { | |
| TPS23881, | |
| TPS23881B, | |
| }; | |
| static const struct tps23881_info tps23881_info[] = { | |
| [TPS23881] = { | |
| .dev_id = 0x22, | |
| .fw_parity_name = "ti/tps23881/tps23881-parity-14.bin", | |
| .fw_sram_name = "ti/tps23881/tps23881-sram-14.bin", | |
| }, | |
| [TPS23881B] = { | |
| .dev_id = 0x24, | |
| /* skip SRAM load, ROM provides Clause 145 hardware-level support */ | |
| }, | |
| }; | |
| struct tps23881_fw_conf { | |
| u8 reg; | |
| u8 val; | |
| }; | |
| static const struct tps23881_fw_conf tps23881_fw_parity_conf[] = { | |
| {.reg = 0x60, .val = 0x01}, | |
| {.reg = 0x62, .val = 0x00}, | |
| {.reg = 0x63, .val = 0x80}, | |
| {.reg = 0x60, .val = 0xC4}, | |
| {.reg = 0x1D, .val = 0xBC}, | |
| {.reg = 0xD7, .val = 0x02}, | |
| {.reg = 0x91, .val = 0x00}, | |
| {.reg = 0x90, .val = 0x00}, | |
| {.reg = 0xD7, .val = 0x00}, | |
| {.reg = 0x1D, .val = 0x00}, | |
| { /* sentinel */ } | |
| }; | |
| static const struct tps23881_fw_conf tps23881_fw_sram_conf[] = { | |
| {.reg = 0x60, .val = 0xC5}, | |
| {.reg = 0x62, .val = 0x00}, | |
| {.reg = 0x63, .val = 0x80}, | |
| {.reg = 0x60, .val = 0xC0}, | |
| {.reg = 0x1D, .val = 0xBC}, | |
| {.reg = 0xD7, .val = 0x02}, | |
| {.reg = 0x91, .val = 0x00}, | |
| {.reg = 0x90, .val = 0x00}, | |
| {.reg = 0xD7, .val = 0x00}, | |
| {.reg = 0x1D, .val = 0x00}, | |
| { /* sentinel */ } | |
| }; | |
| static int tps23881_flash_sram_fw_part(struct i2c_client *client, | |
| const char *fw_name, | |
| const struct tps23881_fw_conf *fw_conf) | |
| { | |
| const struct firmware *fw = NULL; | |
| int i, ret; | |
| ret = request_firmware(&fw, fw_name, &client->dev); | |
| if (ret) | |
| return ret; | |
| dev_dbg(&client->dev, "Flashing %s\n", fw_name); | |
| /* Prepare device for RAM download */ | |
| while (fw_conf->reg) { | |
| ret = i2c_smbus_write_byte_data(client, fw_conf->reg, | |
| fw_conf->val); | |
| if (ret) | |
| goto out; | |
| fw_conf++; | |
| } | |
| /* Flash the firmware file */ | |
| for (i = 0; i < fw->size; i++) { | |
| ret = i2c_smbus_write_byte_data(client, | |
| TPS23881_REG_SRAM_DATA, | |
| fw->data[i]); | |
| if (ret) | |
| goto out; | |
| } | |
| out: | |
| release_firmware(fw); | |
| return ret; | |
| } | |
| static int tps23881_flash_sram_fw(struct i2c_client *client, | |
| const struct tps23881_info *info) | |
| { | |
| int ret; | |
| ret = tps23881_flash_sram_fw_part(client, info->fw_parity_name, | |
| tps23881_fw_parity_conf); | |
| if (ret) | |
| return ret; | |
| ret = tps23881_flash_sram_fw_part(client, info->fw_sram_name, | |
| tps23881_fw_sram_conf); | |
| if (ret) | |
| return ret; | |
| ret = i2c_smbus_write_byte_data(client, TPS23881_REG_SRAM_CTRL, 0x18); | |
| if (ret) | |
| return ret; | |
| mdelay(12); | |
| 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; | |
| const struct tps23881_info *info; | |
| struct tps23881_priv *priv; | |
| struct gpio_desc *reset; | |
| int ret; | |
| u8 val; | |
| if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { | |
| dev_err(dev, "i2c check functionality failed\n"); | |
| return -ENXIO; | |
| } | |
| info = i2c_get_match_data(client); | |
| if (!info) | |
| return -EINVAL; | |
| priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
| if (!priv) | |
| return -ENOMEM; | |
| reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); | |
| if (IS_ERR(reset)) | |
| return dev_err_probe(&client->dev, PTR_ERR(reset), "Failed to get reset GPIO\n"); | |
| if (reset) { | |
| /* TPS23880 datasheet (Rev G) indicates minimum reset pulse is 5us */ | |
| usleep_range(5, 10); | |
| gpiod_set_value_cansleep(reset, 0); /* De-assert reset */ | |
| /* TPS23880 datasheet indicates the minimum time after power on reset | |
| * should be 20ms, but the document describing how to load SRAM ("How | |
| * to Load TPS2388x SRAM and Parity Code over I2C" (Rev E)) | |
| * indicates we should delay that programming by at least 50ms. So | |
| * we'll wait the entire 50ms here to ensure we're safe to go to the | |
| * SRAM loading procedure. | |
| */ | |
| msleep(50); | |
| } | |
| ret = i2c_smbus_read_byte_data(client, TPS23881_REG_DEVID); | |
| if (ret < 0) | |
| return ret; | |
| if (ret != info->dev_id) { | |
| dev_err(dev, "Wrong device ID\n"); | |
| return -ENXIO; | |
| } | |
| if (info->fw_sram_name) { | |
| ret = tps23881_flash_sram_fw(client, info); | |
| if (ret < 0) | |
| return ret; | |
| } | |
| ret = i2c_smbus_read_byte_data(client, TPS23881_REG_FWREV); | |
| if (ret < 0) | |
| return ret; | |
| if (ret == 0xFF) { | |
| dev_err(&client->dev, "Device entered safe mode\n"); | |
| return -ENXIO; | |
| } | |
| dev_info(&client->dev, "Firmware revision 0x%x%s\n", ret, | |
| ret == 0x00 ? " (ROM firmware)" : ""); | |
| /* Set configuration B, 16 bit access on a single device address */ | |
| ret = i2c_smbus_read_byte_data(client, TPS23881_REG_GEN_MASK); | |
| if (ret < 0) | |
| return ret; | |
| val = ret | TPS23881_REG_NBITACC; | |
| ret = i2c_smbus_write_byte_data(client, TPS23881_REG_GEN_MASK, val); | |
| if (ret) | |
| return ret; | |
| priv->client = client; | |
| i2c_set_clientdata(client, priv); | |
| priv->np = dev->of_node; | |
| priv->pcdev.owner = THIS_MODULE; | |
| priv->pcdev.ops = &tps23881_ops; | |
| 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; | |
| } | |
| static const struct i2c_device_id tps23881_id[] = { | |
| { .name = "tps23881", .driver_data = (kernel_ulong_t)&tps23881_info[TPS23881] }, | |
| { .name = "tps23881b", .driver_data = (kernel_ulong_t)&tps23881_info[TPS23881B] }, | |
| { } | |
| }; | |
| MODULE_DEVICE_TABLE(i2c, tps23881_id); | |
| static const struct of_device_id tps23881_of_match[] = { | |
| { | |
| .compatible = "ti,tps23881", | |
| .data = &tps23881_info[TPS23881] | |
| }, | |
| { | |
| .compatible = "ti,tps23881b", | |
| .data = &tps23881_info[TPS23881B] | |
| }, | |
| { }, | |
| }; | |
| MODULE_DEVICE_TABLE(of, tps23881_of_match); | |
| static struct i2c_driver tps23881_driver = { | |
| .probe = tps23881_i2c_probe, | |
| .id_table = tps23881_id, | |
| .driver = { | |
| .name = "tps23881", | |
| .of_match_table = tps23881_of_match, | |
| }, | |
| }; | |
| module_i2c_driver(tps23881_driver); | |
| MODULE_AUTHOR("Kory Maincent <kory.maincent@bootlin.com>"); | |
| MODULE_DESCRIPTION("TI TPS23881 PoE PSE Controller driver"); | |
| MODULE_LICENSE("GPL"); |