Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit facd95b2 authored by Guenter Roeck's avatar Guenter Roeck Committed by David S. Miller
Browse files

net: dsa: mv88e6xxx: Add Hardware bridging support



Bridge support is similar for all chips supported by the mv88e6xxx code,
so add the code there.

Reviewed-by: default avatarAndrew Lunn <andrew@lunn.ch>
Tested-by: default avatarAndrew Lunn <andrew@lunn.ch>
Signed-off-by: default avatarGuenter Roeck <linux@roeck-us.net>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent b0019b70
Loading
Loading
Loading
Loading
+264 −7
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@
 */

#include <linux/delay.h>
#include <linux/if_bridge.h>
#include <linux/jiffies.h>
#include <linux/list.h>
#include <linux/module.h>
@@ -644,6 +645,31 @@ int mv88e6xxx_eeprom_busy_wait(struct dsa_switch *ds)
	return mv88e6xxx_wait(ds, REG_GLOBAL2, 0x14, 0x8000);
}

/* Must be called with SMI lock held */
static int _mv88e6xxx_wait(struct dsa_switch *ds, int reg, int offset, u16 mask)
{
	unsigned long timeout = jiffies + HZ / 10;

	while (time_before(jiffies, timeout)) {
		int ret;

		ret = _mv88e6xxx_reg_read(ds, reg, offset);
		if (ret < 0)
			return ret;
		if (!(ret & mask))
			return 0;

		usleep_range(1000, 2000);
	}
	return -ETIMEDOUT;
}

/* Must be called with SMI lock held */
static int _mv88e6xxx_atu_wait(struct dsa_switch *ds)
{
	return _mv88e6xxx_wait(ds, REG_GLOBAL, 0x0b, ATU_BUSY);
}

int mv88e6xxx_phy_read_indirect(struct dsa_switch *ds, int addr, int regnum)
{
	int ret;
@@ -717,10 +743,236 @@ int mv88e6xxx_set_eee(struct dsa_switch *ds, int port,
	return 0;
}

static int _mv88e6xxx_atu_cmd(struct dsa_switch *ds, int fid, u16 cmd)
{
	int ret;

	ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, 0x01, fid);
	if (ret < 0)
		return ret;

	ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, 0x0b, cmd);
	if (ret < 0)
		return ret;

	return _mv88e6xxx_atu_wait(ds);
}

static int _mv88e6xxx_flush_fid(struct dsa_switch *ds, int fid)
{
	int ret;

	ret = _mv88e6xxx_atu_wait(ds);
	if (ret < 0)
		return ret;

	return _mv88e6xxx_atu_cmd(ds, fid, ATU_CMD_FLUSH_NONSTATIC_FID);
}

static int mv88e6xxx_set_port_state(struct dsa_switch *ds, int port, u8 state)
{
	struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
	int reg, ret;
	u8 oldstate;

	mutex_lock(&ps->smi_mutex);

	reg = _mv88e6xxx_reg_read(ds, REG_PORT(port), 0x04);
	if (reg < 0)
		goto abort;

	oldstate = reg & PSTATE_MASK;
	if (oldstate != state) {
		/* Flush forwarding database if we're moving a port
		 * from Learning or Forwarding state to Disabled or
		 * Blocking or Listening state.
		 */
		if (oldstate >= PSTATE_LEARNING && state <= PSTATE_BLOCKING) {
			ret = _mv88e6xxx_flush_fid(ds, ps->fid[port]);
			if (ret)
				goto abort;
		}
		reg = (reg & ~PSTATE_MASK) | state;
		ret = _mv88e6xxx_reg_write(ds, REG_PORT(port), 0x04, reg);
	}

abort:
	mutex_unlock(&ps->smi_mutex);
	return ret;
}

/* Must be called with smi lock held */
static int _mv88e6xxx_update_port_config(struct dsa_switch *ds, int port)
{
	struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
	u8 fid = ps->fid[port];
	u16 reg = fid << 12;

	if (dsa_is_cpu_port(ds, port))
		reg |= ds->phys_port_mask;
	else
		reg |= (ps->bridge_mask[fid] |
		       (1 << dsa_upstream_port(ds))) & ~(1 << port);

	return _mv88e6xxx_reg_write(ds, REG_PORT(port), 0x06, reg);
}

/* Must be called with smi lock held */
static int _mv88e6xxx_update_bridge_config(struct dsa_switch *ds, int fid)
{
	struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
	int port;
	u32 mask;
	int ret;

	mask = ds->phys_port_mask;
	while (mask) {
		port = __ffs(mask);
		mask &= ~(1 << port);
		if (ps->fid[port] != fid)
			continue;

		ret = _mv88e6xxx_update_port_config(ds, port);
		if (ret)
			return ret;
	}

	return _mv88e6xxx_flush_fid(ds, fid);
}

/* Bridge handling functions */

int mv88e6xxx_join_bridge(struct dsa_switch *ds, int port, u32 br_port_mask)
{
	struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
	int ret = 0;
	u32 nmask;
	int fid;

	/* If the bridge group is not empty, join that group.
	 * Otherwise create a new group.
	 */
	fid = ps->fid[port];
	nmask = br_port_mask & ~(1 << port);
	if (nmask)
		fid = ps->fid[__ffs(nmask)];

	nmask = ps->bridge_mask[fid] | (1 << port);
	if (nmask != br_port_mask) {
		netdev_err(ds->ports[port],
			   "join: Bridge port mask mismatch fid=%d mask=0x%x expected 0x%x\n",
			   fid, br_port_mask, nmask);
		return -EINVAL;
	}

	mutex_lock(&ps->smi_mutex);

	ps->bridge_mask[fid] = br_port_mask;

	if (fid != ps->fid[port]) {
		ps->fid_mask |= 1 << ps->fid[port];
		ps->fid[port] = fid;
		ret = _mv88e6xxx_update_bridge_config(ds, fid);
	}

	mutex_unlock(&ps->smi_mutex);

	return ret;
}

int mv88e6xxx_leave_bridge(struct dsa_switch *ds, int port, u32 br_port_mask)
{
	struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
	u8 fid, newfid;
	int ret;

	fid = ps->fid[port];

	if (ps->bridge_mask[fid] != br_port_mask) {
		netdev_err(ds->ports[port],
			   "leave: Bridge port mask mismatch fid=%d mask=0x%x expected 0x%x\n",
			   fid, br_port_mask, ps->bridge_mask[fid]);
		return -EINVAL;
	}

	/* If the port was the last port of a bridge, we are done.
	 * Otherwise assign a new fid to the port, and fix up
	 * the bridge configuration.
	 */
	if (br_port_mask == (1 << port))
		return 0;

	mutex_lock(&ps->smi_mutex);

	newfid = __ffs(ps->fid_mask);
	ps->fid[port] = newfid;
	ps->fid_mask &= (1 << newfid);
	ps->bridge_mask[fid] &= ~(1 << port);
	ps->bridge_mask[newfid] = 1 << port;

	ret = _mv88e6xxx_update_bridge_config(ds, fid);
	if (!ret)
		ret = _mv88e6xxx_update_bridge_config(ds, newfid);

	mutex_unlock(&ps->smi_mutex);

	return ret;
}

int mv88e6xxx_port_stp_update(struct dsa_switch *ds, int port, u8 state)
{
	struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
	int stp_state;

	switch (state) {
	case BR_STATE_DISABLED:
		stp_state = PSTATE_DISABLED;
		break;
	case BR_STATE_BLOCKING:
	case BR_STATE_LISTENING:
		stp_state = PSTATE_BLOCKING;
		break;
	case BR_STATE_LEARNING:
		stp_state = PSTATE_LEARNING;
		break;
	case BR_STATE_FORWARDING:
	default:
		stp_state = PSTATE_FORWARDING;
		break;
	}

	netdev_dbg(ds->ports[port], "port state %d [%d]\n", state, stp_state);

	/* mv88e6xxx_port_stp_update may be called with softirqs disabled,
	 * so we can not update the port state directly but need to schedule it.
	 */
	ps->port_state[port] = stp_state;
	set_bit(port, &ps->port_state_update_mask);
	schedule_work(&ps->bridge_work);

	return 0;
}

static void mv88e6xxx_bridge_work(struct work_struct *work)
{
	struct mv88e6xxx_priv_state *ps;
	struct dsa_switch *ds;
	int port;

	ps = container_of(work, struct mv88e6xxx_priv_state, bridge_work);
	ds = ((struct dsa_switch *)ps) - 1;

	while (ps->port_state_update_mask) {
		port = __ffs(ps->port_state_update_mask);
		clear_bit(port, &ps->port_state_update_mask);
		mv88e6xxx_set_port_state(ds, port, ps->port_state[port]);
	}
}

int mv88e6xxx_setup_port_common(struct dsa_switch *ds, int port)
{
	struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
	int ret, reg;
	int ret, fid;

	mutex_lock(&ps->smi_mutex);

@@ -736,13 +988,14 @@ int mv88e6xxx_setup_port_common(struct dsa_switch *ds, int port)
	 * ports, and allow each of the 'real' ports to only talk to
	 * the upstream port.
	 */
	reg = (port & 0xf) << 12;
	if (dsa_is_cpu_port(ds, port))
		reg |= ds->phys_port_mask;
	else
		reg |= 1 << dsa_upstream_port(ds);
	fid = __ffs(ps->fid_mask);
	ps->fid[port] = fid;
	ps->fid_mask &= ~(1 << fid);

	if (!dsa_is_cpu_port(ds, port))
		ps->bridge_mask[fid] = 1 << port;

	ret = _mv88e6xxx_reg_write(ds, REG_PORT(port), 0x06, reg);
	ret = _mv88e6xxx_update_port_config(ds, port);
	if (ret)
		goto abort;

@@ -763,6 +1016,10 @@ int mv88e6xxx_setup_common(struct dsa_switch *ds)
	mutex_init(&ps->stats_mutex);
	mutex_init(&ps->phy_mutex);

	ps->fid_mask = (1 << DSA_MAX_PORTS) - 1;

	INIT_WORK(&ps->bridge_work, mv88e6xxx_bridge_work);

	return 0;
}

+28 −0
Original line number Diff line number Diff line
@@ -15,6 +15,20 @@
#define REG_GLOBAL		0x1b
#define REG_GLOBAL2		0x1c

/* ATU commands */

#define ATU_BUSY			0x8000

#define ATU_CMD_FLUSH_NONSTATIC_FID	(ATU_BUSY | 0x6000)

/* port states */

#define PSTATE_MASK		0x03
#define PSTATE_DISABLED		0x00
#define PSTATE_BLOCKING		0x01
#define PSTATE_LEARNING		0x02
#define PSTATE_FORWARDING	0x03

struct mv88e6xxx_priv_state {
	/* When using multi-chip addressing, this mutex protects
	 * access to the indirect access registers.  (In single-chip
@@ -49,6 +63,17 @@ struct mv88e6xxx_priv_state {
	struct mutex eeprom_mutex;

	int		id; /* switch product id */

	/* hw bridging */

	u32 fid_mask;
	u8 fid[DSA_MAX_PORTS];
	u16 bridge_mask[DSA_MAX_PORTS];

	unsigned long port_state_update_mask;
	u8 port_state[DSA_MAX_PORTS];

	struct work_struct bridge_work;
};

struct mv88e6xxx_hw_stat {
@@ -93,6 +118,9 @@ int mv88e6xxx_phy_write_indirect(struct dsa_switch *ds, int addr, int regnum,
int mv88e6xxx_get_eee(struct dsa_switch *ds, int port, struct ethtool_eee *e);
int mv88e6xxx_set_eee(struct dsa_switch *ds, int port,
		      struct phy_device *phydev, struct ethtool_eee *e);
int mv88e6xxx_join_bridge(struct dsa_switch *ds, int port, u32 br_port_mask);
int mv88e6xxx_leave_bridge(struct dsa_switch *ds, int port, u32 br_port_mask);
int mv88e6xxx_port_stp_update(struct dsa_switch *ds, int port, u8 state);

extern struct dsa_switch_driver mv88e6131_switch_driver;
extern struct dsa_switch_driver mv88e6123_61_65_switch_driver;