 7ace30aeb6
			
		
	
	7ace30aeb6
	
	
	
		
			
			Backport upstream code split patch for qca8k needed for ipq40xx target to correctly implement a DSA driver. Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
		
			
				
	
	
		
			705 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			705 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| From 2e5bd96eea86a246b4de3bf756f7a11b43e6187d Mon Sep 17 00:00:00 2001
 | |
| From: Christian Marangi <ansuelsmth@gmail.com>
 | |
| Date: Wed, 27 Jul 2022 13:35:19 +0200
 | |
| Subject: [PATCH 10/14] net: dsa: qca8k: move port FDB/MDB function to common
 | |
|  code
 | |
| 
 | |
| The same port FDB/MDB function are used by drivers based on qca8k family
 | |
| switch. Move them to common code to make them accessible also by other
 | |
| drivers.
 | |
| Also drop bulk read/write functions and make them static
 | |
| 
 | |
| Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
 | |
| Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
 | |
| Signed-off-by: Jakub Kicinski <kuba@kernel.org>
 | |
| ---
 | |
|  drivers/net/dsa/qca/qca8k-8xxx.c   | 306 -----------------------------
 | |
|  drivers/net/dsa/qca/qca8k-common.c | 297 +++++++++++++++++++++++++++-
 | |
|  drivers/net/dsa/qca/qca8k.h        |  25 ++-
 | |
|  3 files changed, 317 insertions(+), 311 deletions(-)
 | |
| 
 | |
| --- a/drivers/net/dsa/qca/qca8k-8xxx.c
 | |
| +++ b/drivers/net/dsa/qca/qca8k-8xxx.c
 | |
| @@ -442,217 +442,6 @@ static struct regmap_config qca8k_regmap
 | |
|  };
 | |
|  
 | |
|  static int
 | |
| -qca8k_fdb_read(struct qca8k_priv *priv, struct qca8k_fdb *fdb)
 | |
| -{
 | |
| -	u32 reg[3];
 | |
| -	int ret;
 | |
| -
 | |
| -	/* load the ARL table into an array */
 | |
| -	ret = qca8k_bulk_read(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg));
 | |
| -	if (ret)
 | |
| -		return ret;
 | |
| -
 | |
| -	/* vid - 83:72 */
 | |
| -	fdb->vid = FIELD_GET(QCA8K_ATU_VID_MASK, reg[2]);
 | |
| -	/* aging - 67:64 */
 | |
| -	fdb->aging = FIELD_GET(QCA8K_ATU_STATUS_MASK, reg[2]);
 | |
| -	/* portmask - 54:48 */
 | |
| -	fdb->port_mask = FIELD_GET(QCA8K_ATU_PORT_MASK, reg[1]);
 | |
| -	/* mac - 47:0 */
 | |
| -	fdb->mac[0] = FIELD_GET(QCA8K_ATU_ADDR0_MASK, reg[1]);
 | |
| -	fdb->mac[1] = FIELD_GET(QCA8K_ATU_ADDR1_MASK, reg[1]);
 | |
| -	fdb->mac[2] = FIELD_GET(QCA8K_ATU_ADDR2_MASK, reg[0]);
 | |
| -	fdb->mac[3] = FIELD_GET(QCA8K_ATU_ADDR3_MASK, reg[0]);
 | |
| -	fdb->mac[4] = FIELD_GET(QCA8K_ATU_ADDR4_MASK, reg[0]);
 | |
| -	fdb->mac[5] = FIELD_GET(QCA8K_ATU_ADDR5_MASK, reg[0]);
 | |
| -
 | |
| -	return 0;
 | |
| -}
 | |
| -
 | |
| -static void
 | |
| -qca8k_fdb_write(struct qca8k_priv *priv, u16 vid, u8 port_mask, const u8 *mac,
 | |
| -		u8 aging)
 | |
| -{
 | |
| -	u32 reg[3] = { 0 };
 | |
| -
 | |
| -	/* vid - 83:72 */
 | |
| -	reg[2] = FIELD_PREP(QCA8K_ATU_VID_MASK, vid);
 | |
| -	/* aging - 67:64 */
 | |
| -	reg[2] |= FIELD_PREP(QCA8K_ATU_STATUS_MASK, aging);
 | |
| -	/* portmask - 54:48 */
 | |
| -	reg[1] = FIELD_PREP(QCA8K_ATU_PORT_MASK, port_mask);
 | |
| -	/* mac - 47:0 */
 | |
| -	reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR0_MASK, mac[0]);
 | |
| -	reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR1_MASK, mac[1]);
 | |
| -	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR2_MASK, mac[2]);
 | |
| -	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR3_MASK, mac[3]);
 | |
| -	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR4_MASK, mac[4]);
 | |
| -	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR5_MASK, mac[5]);
 | |
| -
 | |
| -	/* load the array into the ARL table */
 | |
| -	qca8k_bulk_write(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg));
 | |
| -}
 | |
| -
 | |
| -static int
 | |
| -qca8k_fdb_access(struct qca8k_priv *priv, enum qca8k_fdb_cmd cmd, int port)
 | |
| -{
 | |
| -	u32 reg;
 | |
| -	int ret;
 | |
| -
 | |
| -	/* Set the command and FDB index */
 | |
| -	reg = QCA8K_ATU_FUNC_BUSY;
 | |
| -	reg |= cmd;
 | |
| -	if (port >= 0) {
 | |
| -		reg |= QCA8K_ATU_FUNC_PORT_EN;
 | |
| -		reg |= FIELD_PREP(QCA8K_ATU_FUNC_PORT_MASK, port);
 | |
| -	}
 | |
| -
 | |
| -	/* Write the function register triggering the table access */
 | |
| -	ret = qca8k_write(priv, QCA8K_REG_ATU_FUNC, reg);
 | |
| -	if (ret)
 | |
| -		return ret;
 | |
| -
 | |
| -	/* wait for completion */
 | |
| -	ret = qca8k_busy_wait(priv, QCA8K_REG_ATU_FUNC, QCA8K_ATU_FUNC_BUSY);
 | |
| -	if (ret)
 | |
| -		return ret;
 | |
| -
 | |
| -	/* Check for table full violation when adding an entry */
 | |
| -	if (cmd == QCA8K_FDB_LOAD) {
 | |
| -		ret = qca8k_read(priv, QCA8K_REG_ATU_FUNC, ®);
 | |
| -		if (ret < 0)
 | |
| -			return ret;
 | |
| -		if (reg & QCA8K_ATU_FUNC_FULL)
 | |
| -			return -1;
 | |
| -	}
 | |
| -
 | |
| -	return 0;
 | |
| -}
 | |
| -
 | |
| -static int
 | |
| -qca8k_fdb_next(struct qca8k_priv *priv, struct qca8k_fdb *fdb, int port)
 | |
| -{
 | |
| -	int ret;
 | |
| -
 | |
| -	qca8k_fdb_write(priv, fdb->vid, fdb->port_mask, fdb->mac, fdb->aging);
 | |
| -	ret = qca8k_fdb_access(priv, QCA8K_FDB_NEXT, port);
 | |
| -	if (ret < 0)
 | |
| -		return ret;
 | |
| -
 | |
| -	return qca8k_fdb_read(priv, fdb);
 | |
| -}
 | |
| -
 | |
| -static int
 | |
| -qca8k_fdb_add(struct qca8k_priv *priv, const u8 *mac, u16 port_mask,
 | |
| -	      u16 vid, u8 aging)
 | |
| -{
 | |
| -	int ret;
 | |
| -
 | |
| -	mutex_lock(&priv->reg_mutex);
 | |
| -	qca8k_fdb_write(priv, vid, port_mask, mac, aging);
 | |
| -	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
 | |
| -	mutex_unlock(&priv->reg_mutex);
 | |
| -
 | |
| -	return ret;
 | |
| -}
 | |
| -
 | |
| -static int
 | |
| -qca8k_fdb_del(struct qca8k_priv *priv, const u8 *mac, u16 port_mask, u16 vid)
 | |
| -{
 | |
| -	int ret;
 | |
| -
 | |
| -	mutex_lock(&priv->reg_mutex);
 | |
| -	qca8k_fdb_write(priv, vid, port_mask, mac, 0);
 | |
| -	ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
 | |
| -	mutex_unlock(&priv->reg_mutex);
 | |
| -
 | |
| -	return ret;
 | |
| -}
 | |
| -
 | |
| -static void
 | |
| -qca8k_fdb_flush(struct qca8k_priv *priv)
 | |
| -{
 | |
| -	mutex_lock(&priv->reg_mutex);
 | |
| -	qca8k_fdb_access(priv, QCA8K_FDB_FLUSH, -1);
 | |
| -	mutex_unlock(&priv->reg_mutex);
 | |
| -}
 | |
| -
 | |
| -static int
 | |
| -qca8k_fdb_search_and_insert(struct qca8k_priv *priv, u8 port_mask,
 | |
| -			    const u8 *mac, u16 vid)
 | |
| -{
 | |
| -	struct qca8k_fdb fdb = { 0 };
 | |
| -	int ret;
 | |
| -
 | |
| -	mutex_lock(&priv->reg_mutex);
 | |
| -
 | |
| -	qca8k_fdb_write(priv, vid, 0, mac, 0);
 | |
| -	ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1);
 | |
| -	if (ret < 0)
 | |
| -		goto exit;
 | |
| -
 | |
| -	ret = qca8k_fdb_read(priv, &fdb);
 | |
| -	if (ret < 0)
 | |
| -		goto exit;
 | |
| -
 | |
| -	/* Rule exist. Delete first */
 | |
| -	if (!fdb.aging) {
 | |
| -		ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
 | |
| -		if (ret)
 | |
| -			goto exit;
 | |
| -	}
 | |
| -
 | |
| -	/* Add port to fdb portmask */
 | |
| -	fdb.port_mask |= port_mask;
 | |
| -
 | |
| -	qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging);
 | |
| -	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
 | |
| -
 | |
| -exit:
 | |
| -	mutex_unlock(&priv->reg_mutex);
 | |
| -	return ret;
 | |
| -}
 | |
| -
 | |
| -static int
 | |
| -qca8k_fdb_search_and_del(struct qca8k_priv *priv, u8 port_mask,
 | |
| -			 const u8 *mac, u16 vid)
 | |
| -{
 | |
| -	struct qca8k_fdb fdb = { 0 };
 | |
| -	int ret;
 | |
| -
 | |
| -	mutex_lock(&priv->reg_mutex);
 | |
| -
 | |
| -	qca8k_fdb_write(priv, vid, 0, mac, 0);
 | |
| -	ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1);
 | |
| -	if (ret < 0)
 | |
| -		goto exit;
 | |
| -
 | |
| -	/* Rule doesn't exist. Why delete? */
 | |
| -	if (!fdb.aging) {
 | |
| -		ret = -EINVAL;
 | |
| -		goto exit;
 | |
| -	}
 | |
| -
 | |
| -	ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
 | |
| -	if (ret)
 | |
| -		goto exit;
 | |
| -
 | |
| -	/* Only port in the rule is this port. Don't re insert */
 | |
| -	if (fdb.port_mask == port_mask)
 | |
| -		goto exit;
 | |
| -
 | |
| -	/* Remove port from port mask */
 | |
| -	fdb.port_mask &= ~port_mask;
 | |
| -
 | |
| -	qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging);
 | |
| -	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
 | |
| -
 | |
| -exit:
 | |
| -	mutex_unlock(&priv->reg_mutex);
 | |
| -	return ret;
 | |
| -}
 | |
| -
 | |
| -static int
 | |
|  qca8k_vlan_access(struct qca8k_priv *priv, enum qca8k_vlan_cmd cmd, u16 vid)
 | |
|  {
 | |
|  	u32 reg;
 | |
| @@ -2048,97 +1837,6 @@ exit:
 | |
|  	return ret;
 | |
|  }
 | |
|  
 | |
| -static void
 | |
| -qca8k_port_fast_age(struct dsa_switch *ds, int port)
 | |
| -{
 | |
| -	struct qca8k_priv *priv = ds->priv;
 | |
| -
 | |
| -	mutex_lock(&priv->reg_mutex);
 | |
| -	qca8k_fdb_access(priv, QCA8K_FDB_FLUSH_PORT, port);
 | |
| -	mutex_unlock(&priv->reg_mutex);
 | |
| -}
 | |
| -
 | |
| -static int
 | |
| -qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr,
 | |
| -		      u16 port_mask, u16 vid)
 | |
| -{
 | |
| -	/* Set the vid to the port vlan id if no vid is set */
 | |
| -	if (!vid)
 | |
| -		vid = QCA8K_PORT_VID_DEF;
 | |
| -
 | |
| -	return qca8k_fdb_add(priv, addr, port_mask, vid,
 | |
| -			     QCA8K_ATU_STATUS_STATIC);
 | |
| -}
 | |
| -
 | |
| -static int
 | |
| -qca8k_port_fdb_add(struct dsa_switch *ds, int port,
 | |
| -		   const unsigned char *addr, u16 vid)
 | |
| -{
 | |
| -	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
 | |
| -	u16 port_mask = BIT(port);
 | |
| -
 | |
| -	return qca8k_port_fdb_insert(priv, addr, port_mask, vid);
 | |
| -}
 | |
| -
 | |
| -static int
 | |
| -qca8k_port_fdb_del(struct dsa_switch *ds, int port,
 | |
| -		   const unsigned char *addr, u16 vid)
 | |
| -{
 | |
| -	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
 | |
| -	u16 port_mask = BIT(port);
 | |
| -
 | |
| -	if (!vid)
 | |
| -		vid = QCA8K_PORT_VID_DEF;
 | |
| -
 | |
| -	return qca8k_fdb_del(priv, addr, port_mask, vid);
 | |
| -}
 | |
| -
 | |
| -static int
 | |
| -qca8k_port_fdb_dump(struct dsa_switch *ds, int port,
 | |
| -		    dsa_fdb_dump_cb_t *cb, void *data)
 | |
| -{
 | |
| -	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
 | |
| -	struct qca8k_fdb _fdb = { 0 };
 | |
| -	int cnt = QCA8K_NUM_FDB_RECORDS;
 | |
| -	bool is_static;
 | |
| -	int ret = 0;
 | |
| -
 | |
| -	mutex_lock(&priv->reg_mutex);
 | |
| -	while (cnt-- && !qca8k_fdb_next(priv, &_fdb, port)) {
 | |
| -		if (!_fdb.aging)
 | |
| -			break;
 | |
| -		is_static = (_fdb.aging == QCA8K_ATU_STATUS_STATIC);
 | |
| -		ret = cb(_fdb.mac, _fdb.vid, is_static, data);
 | |
| -		if (ret)
 | |
| -			break;
 | |
| -	}
 | |
| -	mutex_unlock(&priv->reg_mutex);
 | |
| -
 | |
| -	return 0;
 | |
| -}
 | |
| -
 | |
| -static int
 | |
| -qca8k_port_mdb_add(struct dsa_switch *ds, int port,
 | |
| -		   const struct switchdev_obj_port_mdb *mdb)
 | |
| -{
 | |
| -	struct qca8k_priv *priv = ds->priv;
 | |
| -	const u8 *addr = mdb->addr;
 | |
| -	u16 vid = mdb->vid;
 | |
| -
 | |
| -	return qca8k_fdb_search_and_insert(priv, BIT(port), addr, vid);
 | |
| -}
 | |
| -
 | |
| -static int
 | |
| -qca8k_port_mdb_del(struct dsa_switch *ds, int port,
 | |
| -		   const struct switchdev_obj_port_mdb *mdb)
 | |
| -{
 | |
| -	struct qca8k_priv *priv = ds->priv;
 | |
| -	const u8 *addr = mdb->addr;
 | |
| -	u16 vid = mdb->vid;
 | |
| -
 | |
| -	return qca8k_fdb_search_and_del(priv, BIT(port), addr, vid);
 | |
| -}
 | |
| -
 | |
|  static int
 | |
|  qca8k_port_mirror_add(struct dsa_switch *ds, int port,
 | |
|  		      struct dsa_mall_mirror_tc_entry *mirror,
 | |
| --- a/drivers/net/dsa/qca/qca8k-common.c
 | |
| +++ b/drivers/net/dsa/qca/qca8k-common.c
 | |
| @@ -103,7 +103,7 @@ const struct regmap_access_table qca8k_r
 | |
|  };
 | |
|  
 | |
|  /* TODO: remove these extra ops when we can support regmap bulk read/write */
 | |
| -int qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
 | |
| +static int qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
 | |
|  {
 | |
|  	int i, count = len / sizeof(u32), ret;
 | |
|  
 | |
| @@ -121,7 +121,7 @@ int qca8k_bulk_read(struct qca8k_priv *p
 | |
|  }
 | |
|  
 | |
|  /* TODO: remove these extra ops when we can support regmap bulk read/write */
 | |
| -int qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
 | |
| +static int qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
 | |
|  {
 | |
|  	int i, count = len / sizeof(u32), ret;
 | |
|  	u32 tmp;
 | |
| @@ -149,6 +149,211 @@ int qca8k_busy_wait(struct qca8k_priv *p
 | |
|  				       QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC);
 | |
|  }
 | |
|  
 | |
| +static int qca8k_fdb_read(struct qca8k_priv *priv, struct qca8k_fdb *fdb)
 | |
| +{
 | |
| +	u32 reg[3];
 | |
| +	int ret;
 | |
| +
 | |
| +	/* load the ARL table into an array */
 | |
| +	ret = qca8k_bulk_read(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg));
 | |
| +	if (ret)
 | |
| +		return ret;
 | |
| +
 | |
| +	/* vid - 83:72 */
 | |
| +	fdb->vid = FIELD_GET(QCA8K_ATU_VID_MASK, reg[2]);
 | |
| +	/* aging - 67:64 */
 | |
| +	fdb->aging = FIELD_GET(QCA8K_ATU_STATUS_MASK, reg[2]);
 | |
| +	/* portmask - 54:48 */
 | |
| +	fdb->port_mask = FIELD_GET(QCA8K_ATU_PORT_MASK, reg[1]);
 | |
| +	/* mac - 47:0 */
 | |
| +	fdb->mac[0] = FIELD_GET(QCA8K_ATU_ADDR0_MASK, reg[1]);
 | |
| +	fdb->mac[1] = FIELD_GET(QCA8K_ATU_ADDR1_MASK, reg[1]);
 | |
| +	fdb->mac[2] = FIELD_GET(QCA8K_ATU_ADDR2_MASK, reg[0]);
 | |
| +	fdb->mac[3] = FIELD_GET(QCA8K_ATU_ADDR3_MASK, reg[0]);
 | |
| +	fdb->mac[4] = FIELD_GET(QCA8K_ATU_ADDR4_MASK, reg[0]);
 | |
| +	fdb->mac[5] = FIELD_GET(QCA8K_ATU_ADDR5_MASK, reg[0]);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static void qca8k_fdb_write(struct qca8k_priv *priv, u16 vid, u8 port_mask,
 | |
| +			    const u8 *mac, u8 aging)
 | |
| +{
 | |
| +	u32 reg[3] = { 0 };
 | |
| +
 | |
| +	/* vid - 83:72 */
 | |
| +	reg[2] = FIELD_PREP(QCA8K_ATU_VID_MASK, vid);
 | |
| +	/* aging - 67:64 */
 | |
| +	reg[2] |= FIELD_PREP(QCA8K_ATU_STATUS_MASK, aging);
 | |
| +	/* portmask - 54:48 */
 | |
| +	reg[1] = FIELD_PREP(QCA8K_ATU_PORT_MASK, port_mask);
 | |
| +	/* mac - 47:0 */
 | |
| +	reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR0_MASK, mac[0]);
 | |
| +	reg[1] |= FIELD_PREP(QCA8K_ATU_ADDR1_MASK, mac[1]);
 | |
| +	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR2_MASK, mac[2]);
 | |
| +	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR3_MASK, mac[3]);
 | |
| +	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR4_MASK, mac[4]);
 | |
| +	reg[0] |= FIELD_PREP(QCA8K_ATU_ADDR5_MASK, mac[5]);
 | |
| +
 | |
| +	/* load the array into the ARL table */
 | |
| +	qca8k_bulk_write(priv, QCA8K_REG_ATU_DATA0, reg, sizeof(reg));
 | |
| +}
 | |
| +
 | |
| +static int qca8k_fdb_access(struct qca8k_priv *priv, enum qca8k_fdb_cmd cmd,
 | |
| +			    int port)
 | |
| +{
 | |
| +	u32 reg;
 | |
| +	int ret;
 | |
| +
 | |
| +	/* Set the command and FDB index */
 | |
| +	reg = QCA8K_ATU_FUNC_BUSY;
 | |
| +	reg |= cmd;
 | |
| +	if (port >= 0) {
 | |
| +		reg |= QCA8K_ATU_FUNC_PORT_EN;
 | |
| +		reg |= FIELD_PREP(QCA8K_ATU_FUNC_PORT_MASK, port);
 | |
| +	}
 | |
| +
 | |
| +	/* Write the function register triggering the table access */
 | |
| +	ret = qca8k_write(priv, QCA8K_REG_ATU_FUNC, reg);
 | |
| +	if (ret)
 | |
| +		return ret;
 | |
| +
 | |
| +	/* wait for completion */
 | |
| +	ret = qca8k_busy_wait(priv, QCA8K_REG_ATU_FUNC, QCA8K_ATU_FUNC_BUSY);
 | |
| +	if (ret)
 | |
| +		return ret;
 | |
| +
 | |
| +	/* Check for table full violation when adding an entry */
 | |
| +	if (cmd == QCA8K_FDB_LOAD) {
 | |
| +		ret = qca8k_read(priv, QCA8K_REG_ATU_FUNC, ®);
 | |
| +		if (ret < 0)
 | |
| +			return ret;
 | |
| +		if (reg & QCA8K_ATU_FUNC_FULL)
 | |
| +			return -1;
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static int qca8k_fdb_next(struct qca8k_priv *priv, struct qca8k_fdb *fdb,
 | |
| +			  int port)
 | |
| +{
 | |
| +	int ret;
 | |
| +
 | |
| +	qca8k_fdb_write(priv, fdb->vid, fdb->port_mask, fdb->mac, fdb->aging);
 | |
| +	ret = qca8k_fdb_access(priv, QCA8K_FDB_NEXT, port);
 | |
| +	if (ret < 0)
 | |
| +		return ret;
 | |
| +
 | |
| +	return qca8k_fdb_read(priv, fdb);
 | |
| +}
 | |
| +
 | |
| +static int qca8k_fdb_add(struct qca8k_priv *priv, const u8 *mac,
 | |
| +			 u16 port_mask, u16 vid, u8 aging)
 | |
| +{
 | |
| +	int ret;
 | |
| +
 | |
| +	mutex_lock(&priv->reg_mutex);
 | |
| +	qca8k_fdb_write(priv, vid, port_mask, mac, aging);
 | |
| +	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
 | |
| +	mutex_unlock(&priv->reg_mutex);
 | |
| +
 | |
| +	return ret;
 | |
| +}
 | |
| +
 | |
| +static int qca8k_fdb_del(struct qca8k_priv *priv, const u8 *mac,
 | |
| +			 u16 port_mask, u16 vid)
 | |
| +{
 | |
| +	int ret;
 | |
| +
 | |
| +	mutex_lock(&priv->reg_mutex);
 | |
| +	qca8k_fdb_write(priv, vid, port_mask, mac, 0);
 | |
| +	ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
 | |
| +	mutex_unlock(&priv->reg_mutex);
 | |
| +
 | |
| +	return ret;
 | |
| +}
 | |
| +
 | |
| +void qca8k_fdb_flush(struct qca8k_priv *priv)
 | |
| +{
 | |
| +	mutex_lock(&priv->reg_mutex);
 | |
| +	qca8k_fdb_access(priv, QCA8K_FDB_FLUSH, -1);
 | |
| +	mutex_unlock(&priv->reg_mutex);
 | |
| +}
 | |
| +
 | |
| +static int qca8k_fdb_search_and_insert(struct qca8k_priv *priv, u8 port_mask,
 | |
| +				       const u8 *mac, u16 vid)
 | |
| +{
 | |
| +	struct qca8k_fdb fdb = { 0 };
 | |
| +	int ret;
 | |
| +
 | |
| +	mutex_lock(&priv->reg_mutex);
 | |
| +
 | |
| +	qca8k_fdb_write(priv, vid, 0, mac, 0);
 | |
| +	ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1);
 | |
| +	if (ret < 0)
 | |
| +		goto exit;
 | |
| +
 | |
| +	ret = qca8k_fdb_read(priv, &fdb);
 | |
| +	if (ret < 0)
 | |
| +		goto exit;
 | |
| +
 | |
| +	/* Rule exist. Delete first */
 | |
| +	if (!fdb.aging) {
 | |
| +		ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
 | |
| +		if (ret)
 | |
| +			goto exit;
 | |
| +	}
 | |
| +
 | |
| +	/* Add port to fdb portmask */
 | |
| +	fdb.port_mask |= port_mask;
 | |
| +
 | |
| +	qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging);
 | |
| +	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
 | |
| +
 | |
| +exit:
 | |
| +	mutex_unlock(&priv->reg_mutex);
 | |
| +	return ret;
 | |
| +}
 | |
| +
 | |
| +static int qca8k_fdb_search_and_del(struct qca8k_priv *priv, u8 port_mask,
 | |
| +				    const u8 *mac, u16 vid)
 | |
| +{
 | |
| +	struct qca8k_fdb fdb = { 0 };
 | |
| +	int ret;
 | |
| +
 | |
| +	mutex_lock(&priv->reg_mutex);
 | |
| +
 | |
| +	qca8k_fdb_write(priv, vid, 0, mac, 0);
 | |
| +	ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1);
 | |
| +	if (ret < 0)
 | |
| +		goto exit;
 | |
| +
 | |
| +	/* Rule doesn't exist. Why delete? */
 | |
| +	if (!fdb.aging) {
 | |
| +		ret = -EINVAL;
 | |
| +		goto exit;
 | |
| +	}
 | |
| +
 | |
| +	ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
 | |
| +	if (ret)
 | |
| +		goto exit;
 | |
| +
 | |
| +	/* Only port in the rule is this port. Don't re insert */
 | |
| +	if (fdb.port_mask == port_mask)
 | |
| +		goto exit;
 | |
| +
 | |
| +	/* Remove port from port mask */
 | |
| +	fdb.port_mask &= ~port_mask;
 | |
| +
 | |
| +	qca8k_fdb_write(priv, vid, fdb.port_mask, mac, fdb.aging);
 | |
| +	ret = qca8k_fdb_access(priv, QCA8K_FDB_LOAD, -1);
 | |
| +
 | |
| +exit:
 | |
| +	mutex_unlock(&priv->reg_mutex);
 | |
| +	return ret;
 | |
| +}
 | |
| +
 | |
|  int qca8k_mib_init(struct qca8k_priv *priv)
 | |
|  {
 | |
|  	int ret;
 | |
| @@ -368,6 +573,15 @@ void qca8k_port_bridge_leave(struct dsa_
 | |
|  		  QCA8K_PORT_LOOKUP_MEMBER, BIT(cpu_port));
 | |
|  }
 | |
|  
 | |
| +void qca8k_port_fast_age(struct dsa_switch *ds, int port)
 | |
| +{
 | |
| +	struct qca8k_priv *priv = ds->priv;
 | |
| +
 | |
| +	mutex_lock(&priv->reg_mutex);
 | |
| +	qca8k_fdb_access(priv, QCA8K_FDB_FLUSH_PORT, port);
 | |
| +	mutex_unlock(&priv->reg_mutex);
 | |
| +}
 | |
| +
 | |
|  int qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
 | |
|  {
 | |
|  	struct qca8k_priv *priv = ds->priv;
 | |
| @@ -452,3 +666,78 @@ int qca8k_port_max_mtu(struct dsa_switch
 | |
|  {
 | |
|  	return QCA8K_MAX_MTU;
 | |
|  }
 | |
| +
 | |
| +int qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr,
 | |
| +			  u16 port_mask, u16 vid)
 | |
| +{
 | |
| +	/* Set the vid to the port vlan id if no vid is set */
 | |
| +	if (!vid)
 | |
| +		vid = QCA8K_PORT_VID_DEF;
 | |
| +
 | |
| +	return qca8k_fdb_add(priv, addr, port_mask, vid,
 | |
| +			     QCA8K_ATU_STATUS_STATIC);
 | |
| +}
 | |
| +
 | |
| +int qca8k_port_fdb_add(struct dsa_switch *ds, int port,
 | |
| +		       const unsigned char *addr, u16 vid)
 | |
| +{
 | |
| +	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
 | |
| +	u16 port_mask = BIT(port);
 | |
| +
 | |
| +	return qca8k_port_fdb_insert(priv, addr, port_mask, vid);
 | |
| +}
 | |
| +
 | |
| +int qca8k_port_fdb_del(struct dsa_switch *ds, int port,
 | |
| +		       const unsigned char *addr, u16 vid)
 | |
| +{
 | |
| +	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
 | |
| +	u16 port_mask = BIT(port);
 | |
| +
 | |
| +	if (!vid)
 | |
| +		vid = QCA8K_PORT_VID_DEF;
 | |
| +
 | |
| +	return qca8k_fdb_del(priv, addr, port_mask, vid);
 | |
| +}
 | |
| +
 | |
| +int qca8k_port_fdb_dump(struct dsa_switch *ds, int port,
 | |
| +			dsa_fdb_dump_cb_t *cb, void *data)
 | |
| +{
 | |
| +	struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
 | |
| +	struct qca8k_fdb _fdb = { 0 };
 | |
| +	int cnt = QCA8K_NUM_FDB_RECORDS;
 | |
| +	bool is_static;
 | |
| +	int ret = 0;
 | |
| +
 | |
| +	mutex_lock(&priv->reg_mutex);
 | |
| +	while (cnt-- && !qca8k_fdb_next(priv, &_fdb, port)) {
 | |
| +		if (!_fdb.aging)
 | |
| +			break;
 | |
| +		is_static = (_fdb.aging == QCA8K_ATU_STATUS_STATIC);
 | |
| +		ret = cb(_fdb.mac, _fdb.vid, is_static, data);
 | |
| +		if (ret)
 | |
| +			break;
 | |
| +	}
 | |
| +	mutex_unlock(&priv->reg_mutex);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +int qca8k_port_mdb_add(struct dsa_switch *ds, int port,
 | |
| +		       const struct switchdev_obj_port_mdb *mdb)
 | |
| +{
 | |
| +	struct qca8k_priv *priv = ds->priv;
 | |
| +	const u8 *addr = mdb->addr;
 | |
| +	u16 vid = mdb->vid;
 | |
| +
 | |
| +	return qca8k_fdb_search_and_insert(priv, BIT(port), addr, vid);
 | |
| +}
 | |
| +
 | |
| +int qca8k_port_mdb_del(struct dsa_switch *ds, int port,
 | |
| +		       const struct switchdev_obj_port_mdb *mdb)
 | |
| +{
 | |
| +	struct qca8k_priv *priv = ds->priv;
 | |
| +	const u8 *addr = mdb->addr;
 | |
| +	u16 vid = mdb->vid;
 | |
| +
 | |
| +	return qca8k_fdb_search_and_del(priv, BIT(port), addr, vid);
 | |
| +}
 | |
| --- a/drivers/net/dsa/qca/qca8k.h
 | |
| +++ b/drivers/net/dsa/qca/qca8k.h
 | |
| @@ -430,11 +430,9 @@ int qca8k_read(struct qca8k_priv *priv,
 | |
|  int qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val);
 | |
|  int qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val);
 | |
|  
 | |
| -int qca8k_bulk_read(struct qca8k_priv *priv, u32 reg, u32 *val, int len);
 | |
| -int qca8k_bulk_write(struct qca8k_priv *priv, u32 reg, u32 *val, int len);
 | |
| -
 | |
|  /* Common ops function */
 | |
|  int qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask);
 | |
| +void qca8k_fdb_flush(struct qca8k_priv *priv);
 | |
|  
 | |
|  /* Common ethtool stats function */
 | |
|  void qca8k_get_strings(struct dsa_switch *ds, int port, u32 stringset, uint8_t *data);
 | |
| @@ -463,6 +461,23 @@ int qca8k_port_change_mtu(struct dsa_swi
 | |
|  int qca8k_port_max_mtu(struct dsa_switch *ds, int port);
 | |
|  
 | |
|  /* Common fast age function */
 | |
| +void qca8k_port_fast_age(struct dsa_switch *ds, int port);
 | |
|  int qca8k_set_ageing_time(struct dsa_switch *ds, unsigned int msecs);
 | |
|  
 | |
| +/* Common FDB function */
 | |
| +int qca8k_port_fdb_insert(struct qca8k_priv *priv, const u8 *addr,
 | |
| +			  u16 port_mask, u16 vid);
 | |
| +int qca8k_port_fdb_add(struct dsa_switch *ds, int port,
 | |
| +		       const unsigned char *addr, u16 vid);
 | |
| +int qca8k_port_fdb_del(struct dsa_switch *ds, int port,
 | |
| +		       const unsigned char *addr, u16 vid);
 | |
| +int qca8k_port_fdb_dump(struct dsa_switch *ds, int port,
 | |
| +			dsa_fdb_dump_cb_t *cb, void *data);
 | |
| +
 | |
| +/* Common MDB function */
 | |
| +int qca8k_port_mdb_add(struct dsa_switch *ds, int port,
 | |
| +		       const struct switchdev_obj_port_mdb *mdb);
 | |
| +int qca8k_port_mdb_del(struct dsa_switch *ds, int port,
 | |
| +		       const struct switchdev_obj_port_mdb *mdb);
 | |
| +
 | |
|  #endif /* __QCA8K_H */
 |