845 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			845 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // 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_REG_PW_STATUS	0x10
 | |
| #define TPS23881_REG_OP_MODE	0x12
 | |
| #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_NBITACC	BIT(5)
 | |
| #define TPS23881_REG_PW_EN	0x19
 | |
| #define TPS23881_REG_PORT_MAP	0x26
 | |
| #define TPS23881_REG_PORT_POWER	0x29
 | |
| #define TPS23881_REG_POEPLUS	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_SRAM_CTRL	0x60
 | |
| #define TPS23881_REG_SRAM_DATA	0x61
 | |
| 
 | |
| struct tps23881_port_desc {
 | |
| 	u8 chan[2];
 | |
| 	bool is_4p;
 | |
| };
 | |
| 
 | |
| 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);
 | |
| }
 | |
| 
 | |
| 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;
 | |
| 
 | |
| 	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));
 | |
| 
 | |
| 	if (priv->port[id].is_4p) {
 | |
| 		chan = priv->port[id].chan[1];
 | |
| 		if (chan < 4)
 | |
| 			val |= BIT(chan);
 | |
| 		else
 | |
| 			val |= BIT(chan + 4);
 | |
| 	}
 | |
| 
 | |
| 	ret = i2c_smbus_write_word_data(client, TPS23881_REG_PW_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;
 | |
| 
 | |
| 	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 + 4));
 | |
| 	else
 | |
| 		val = (u16)(ret | BIT(chan + 8));
 | |
| 
 | |
| 	if (priv->port[id].is_4p) {
 | |
| 		chan = priv->port[id].chan[1];
 | |
| 		if (chan < 4)
 | |
| 			val |= BIT(chan + 4);
 | |
| 		else
 | |
| 			val |= BIT(chan + 8);
 | |
| 	}
 | |
| 
 | |
| 	ret = i2c_smbus_write_word_data(client, TPS23881_REG_PW_EN, val);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int tps23881_pi_is_enabled(struct pse_controller_dev *pcdev, int id)
 | |
| {
 | |
| 	struct tps23881_priv *priv = to_tps23881_priv(pcdev);
 | |
| 	struct i2c_client *client = priv->client;
 | |
| 	bool enabled;
 | |
| 	u8 chan;
 | |
| 	int ret;
 | |
| 
 | |
| 	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)
 | |
| 		enabled = ret & BIT(chan);
 | |
| 	else
 | |
| 		enabled = ret & BIT(chan + 4);
 | |
| 
 | |
| 	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));
 | |
| 	}
 | |
| 
 | |
| 	/* Return enabled status only if both channel are on this state */
 | |
| 	return enabled;
 | |
| }
 | |
| 
 | |
| static int tps23881_ethtool_get_status(struct pse_controller_dev *pcdev,
 | |
| 				       unsigned long id,
 | |
| 				       struct netlink_ext_ack *extack,
 | |
| 				       struct pse_control_status *status)
 | |
| {
 | |
| 	struct tps23881_priv *priv = to_tps23881_priv(pcdev);
 | |
| 	struct i2c_client *client = priv->client;
 | |
| 	bool enabled, delivering;
 | |
| 	u8 chan;
 | |
| 	int ret;
 | |
| 
 | |
| 	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) {
 | |
| 		enabled = ret & BIT(chan);
 | |
| 		delivering = ret & BIT(chan + 4);
 | |
| 	} else {
 | |
| 		enabled = ret & BIT(chan + 4);
 | |
| 		delivering = ret & BIT(chan + 8);
 | |
| 	}
 | |
| 
 | |
| 	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));
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* 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;
 | |
| 	else
 | |
| 		status->c33_admin_state = ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /* 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, ret;
 | |
| 
 | |
| 	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;
 | |
| 
 | |
| 		/* 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 */
 | |
| 	ret = i2c_smbus_write_word_data(client, TPS23881_REG_PORT_MAP, val);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 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);
 | |
| 	}
 | |
| 	ret = i2c_smbus_write_word_data(client, TPS23881_REG_DET_CLA_EN, val);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 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;
 | |
| 
 | |
| 	ret = tps23881_set_ports_conf(priv, port_matrix);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 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 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,
 | |
| };
 | |
| 
 | |
| static const char fw_parity_name[] = "ti/tps23881/tps23881-parity-14.bin";
 | |
| static const char fw_sram_name[] = "ti/tps23881/tps23881-sram-14.bin";
 | |
| 
 | |
| 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)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = tps23881_flash_sram_fw_part(client, fw_parity_name,
 | |
| 					  tps23881_fw_parity_conf);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	ret = tps23881_flash_sram_fw_part(client, 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;
 | |
| }
 | |
| 
 | |
| static int tps23881_i2c_probe(struct i2c_client *client)
 | |
| {
 | |
| 	struct device *dev = &client->dev;
 | |
| 	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;
 | |
| 	}
 | |
| 
 | |
| 	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 proceedure.
 | |
| 		 */
 | |
| 		msleep(50);
 | |
| 	}
 | |
| 
 | |
| 	ret = i2c_smbus_read_byte_data(client, TPS23881_REG_DEVID);
 | |
| 	if (ret < 0)
 | |
| 		return ret;
 | |
| 
 | |
| 	if (FIELD_GET(TPS23881_REG_DEVID_MASK, ret) != TPS23881_DEVICE_ID) {
 | |
| 		dev_err(dev, "Wrong device ID\n");
 | |
| 		return -ENXIO;
 | |
| 	}
 | |
| 
 | |
| 	ret = tps23881_flash_sram_fw(client);
 | |
| 	if (ret < 0)
 | |
| 		return ret;
 | |
| 
 | |
| 	ret = i2c_smbus_read_byte_data(client, TPS23881_REG_FWREV);
 | |
| 	if (ret < 0)
 | |
| 		return ret;
 | |
| 
 | |
| 	dev_info(&client->dev, "Firmware revision 0x%x\n", ret);
 | |
| 
 | |
| 	/* 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;
 | |
| 	ret = devm_pse_controller_register(dev, &priv->pcdev);
 | |
| 	if (ret) {
 | |
| 		return dev_err_probe(dev, ret,
 | |
| 				     "failed to register PSE controller\n");
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static const struct i2c_device_id tps23881_id[] = {
 | |
| 	{ "tps23881" },
 | |
| 	{ }
 | |
| };
 | |
| MODULE_DEVICE_TABLE(i2c, tps23881_id);
 | |
| 
 | |
| static const struct of_device_id tps23881_of_match[] = {
 | |
| 	{ .compatible = "ti,tps23881", },
 | |
| 	{ },
 | |
| };
 | |
| 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");
 |