Skip to content

Commit

Permalink
Merge branch 'dsa-MT7530-vlan'
Browse files Browse the repository at this point in the history
Sean Wang says:

====================
add VLAN support to DSA MT7530

Changes sicne v2:
update to the latest code base from net-next and fix up all building
errors with -Werror.

Changes since v1:
- fix up the typo
- prefer ordering declarations longest to shortest
- update that vlan_prepare callback should not change any state
- use lower case letter for function naming

The patchset extends DSA MT7530 to VLAN support through filling required
callbacks in patch 1 and merging the special tag with VLAN tag in patch 2
for allowing that the hardware can handle these packets with VID from the
CPU port.
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
David S. Miller committed Dec 15, 2017
2 parents 5c13e07 + 423d129 commit be17bbe
Show file tree
Hide file tree
Showing 4 changed files with 400 additions and 16 deletions.
7 changes: 7 additions & 0 deletions MAINTAINERS
Original file line number Diff line number Diff line change
Expand Up @@ -8728,6 +8728,13 @@ L: netdev@vger.kernel.org
S: Maintained
F: drivers/net/ethernet/mediatek/

MEDIATEK SWITCH DRIVER
M: Sean Wang <sean.wang@mediatek.com>
L: netdev@vger.kernel.org
S: Maintained
F: drivers/net/dsa/mt7530.*
F: net/dsa/tag_mtk.c

MEDIATEK JPEG DRIVER
M: Rick Chang <rick.chang@mediatek.com>
M: Bin Liu <bin.liu@mediatek.com>
Expand Down
288 changes: 287 additions & 1 deletion drivers/net/dsa/mt7530.c
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,69 @@ mt7530_port_bridge_join(struct dsa_switch *ds, int port,
return 0;
}

static void
mt7530_port_set_vlan_unaware(struct dsa_switch *ds, int port)
{
struct mt7530_priv *priv = ds->priv;
bool all_user_ports_removed = true;
int i;

/* When a port is removed from the bridge, the port would be set up
* back to the default as is at initial boot which is a VLAN-unaware
* port.
*/
mt7530_rmw(priv, MT7530_PCR_P(port), PCR_PORT_VLAN_MASK,
MT7530_PORT_MATRIX_MODE);
mt7530_rmw(priv, MT7530_PVC_P(port), VLAN_ATTR_MASK,
VLAN_ATTR(MT7530_VLAN_TRANSPARENT));

priv->ports[port].vlan_filtering = false;

for (i = 0; i < MT7530_NUM_PORTS; i++) {
if (dsa_is_user_port(ds, i) &&
priv->ports[i].vlan_filtering) {
all_user_ports_removed = false;
break;
}
}

/* CPU port also does the same thing until all user ports belonging to
* the CPU port get out of VLAN filtering mode.
*/
if (all_user_ports_removed) {
mt7530_write(priv, MT7530_PCR_P(MT7530_CPU_PORT),
PCR_MATRIX(dsa_user_ports(priv->ds)));
mt7530_write(priv, MT7530_PVC_P(MT7530_CPU_PORT),
PORT_SPEC_TAG);
}
}

static void
mt7530_port_set_vlan_aware(struct dsa_switch *ds, int port)
{
struct mt7530_priv *priv = ds->priv;

/* The real fabric path would be decided on the membership in the
* entry of VLAN table. PCR_MATRIX set up here with ALL_MEMBERS
* means potential VLAN can be consisting of certain subset of all
* ports.
*/
mt7530_rmw(priv, MT7530_PCR_P(port),
PCR_MATRIX_MASK, PCR_MATRIX(MT7530_ALL_MEMBERS));

/* Trapped into security mode allows packet forwarding through VLAN
* table lookup.
*/
mt7530_rmw(priv, MT7530_PCR_P(port), PCR_PORT_VLAN_MASK,
MT7530_PORT_SECURITY_MODE);

/* Set the port as a user port which is to be able to recognize VID
* from incoming packets before fetching entry within the VLAN table.
*/
mt7530_rmw(priv, MT7530_PVC_P(port), VLAN_ATTR_MASK,
VLAN_ATTR(MT7530_VLAN_USER));
}

static void
mt7530_port_bridge_leave(struct dsa_switch *ds, int port,
struct net_device *bridge)
Expand All @@ -817,8 +880,11 @@ mt7530_port_bridge_leave(struct dsa_switch *ds, int port,
/* Remove this port from the port matrix of the other ports
* in the same bridge. If the port is disabled, port matrix
* is kept and not being setup until the port becomes enabled.
* And the other port's port matrix cannot be broken when the
* other port is still a VLAN-aware port.
*/
if (dsa_is_user_port(ds, i) && i != port) {
if (!priv->ports[i].vlan_filtering &&
dsa_is_user_port(ds, i) && i != port) {
if (dsa_to_port(ds, i)->bridge_dev != bridge)
continue;
if (priv->ports[i].enable)
Expand All @@ -836,6 +902,8 @@ mt7530_port_bridge_leave(struct dsa_switch *ds, int port,
PCR_MATRIX(BIT(MT7530_CPU_PORT)));
priv->ports[port].pm = PCR_MATRIX(BIT(MT7530_CPU_PORT));

mt7530_port_set_vlan_unaware(ds, port);

mutex_unlock(&priv->reg_mutex);
}

Expand Down Expand Up @@ -906,6 +974,220 @@ mt7530_port_fdb_dump(struct dsa_switch *ds, int port,
return 0;
}

static int
mt7530_vlan_cmd(struct mt7530_priv *priv, enum mt7530_vlan_cmd cmd, u16 vid)
{
struct mt7530_dummy_poll p;
u32 val;
int ret;

val = VTCR_BUSY | VTCR_FUNC(cmd) | vid;
mt7530_write(priv, MT7530_VTCR, val);

INIT_MT7530_DUMMY_POLL(&p, priv, MT7530_VTCR);
ret = readx_poll_timeout(_mt7530_read, &p, val,
!(val & VTCR_BUSY), 20, 20000);
if (ret < 0) {
dev_err(priv->dev, "poll timeout\n");
return ret;
}

val = mt7530_read(priv, MT7530_VTCR);
if (val & VTCR_INVALID) {
dev_err(priv->dev, "read VTCR invalid\n");
return -EINVAL;
}

return 0;
}

static int
mt7530_port_vlan_filtering(struct dsa_switch *ds, int port,
bool vlan_filtering)
{
struct mt7530_priv *priv = ds->priv;

priv->ports[port].vlan_filtering = vlan_filtering;

if (vlan_filtering) {
/* The port is being kept as VLAN-unaware port when bridge is
* set up with vlan_filtering not being set, Otherwise, the
* port and the corresponding CPU port is required the setup
* for becoming a VLAN-aware port.
*/
mt7530_port_set_vlan_aware(ds, port);
mt7530_port_set_vlan_aware(ds, MT7530_CPU_PORT);
}

return 0;
}

static int
mt7530_port_vlan_prepare(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan)
{
/* nothing needed */

return 0;
}

static void
mt7530_hw_vlan_add(struct mt7530_priv *priv,
struct mt7530_hw_vlan_entry *entry)
{
u8 new_members;
u32 val;

new_members = entry->old_members | BIT(entry->port) |
BIT(MT7530_CPU_PORT);

/* Validate the entry with independent learning, create egress tag per
* VLAN and joining the port as one of the port members.
*/
val = IVL_MAC | VTAG_EN | PORT_MEM(new_members) | VLAN_VALID;
mt7530_write(priv, MT7530_VAWD1, val);

/* Decide whether adding tag or not for those outgoing packets from the
* port inside the VLAN.
*/
val = entry->untagged ? MT7530_VLAN_EGRESS_UNTAG :
MT7530_VLAN_EGRESS_TAG;
mt7530_rmw(priv, MT7530_VAWD2,
ETAG_CTRL_P_MASK(entry->port),
ETAG_CTRL_P(entry->port, val));

/* CPU port is always taken as a tagged port for serving more than one
* VLANs across and also being applied with egress type stack mode for
* that VLAN tags would be appended after hardware special tag used as
* DSA tag.
*/
mt7530_rmw(priv, MT7530_VAWD2,
ETAG_CTRL_P_MASK(MT7530_CPU_PORT),
ETAG_CTRL_P(MT7530_CPU_PORT,
MT7530_VLAN_EGRESS_STACK));
}

static void
mt7530_hw_vlan_del(struct mt7530_priv *priv,
struct mt7530_hw_vlan_entry *entry)
{
u8 new_members;
u32 val;

new_members = entry->old_members & ~BIT(entry->port);

val = mt7530_read(priv, MT7530_VAWD1);
if (!(val & VLAN_VALID)) {
dev_err(priv->dev,
"Cannot be deleted due to invalid entry\n");
return;
}

/* If certain member apart from CPU port is still alive in the VLAN,
* the entry would be kept valid. Otherwise, the entry is got to be
* disabled.
*/
if (new_members && new_members != BIT(MT7530_CPU_PORT)) {
val = IVL_MAC | VTAG_EN | PORT_MEM(new_members) |
VLAN_VALID;
mt7530_write(priv, MT7530_VAWD1, val);
} else {
mt7530_write(priv, MT7530_VAWD1, 0);
mt7530_write(priv, MT7530_VAWD2, 0);
}
}

static void
mt7530_hw_vlan_update(struct mt7530_priv *priv, u16 vid,
struct mt7530_hw_vlan_entry *entry,
mt7530_vlan_op vlan_op)
{
u32 val;

/* Fetch entry */
mt7530_vlan_cmd(priv, MT7530_VTCR_RD_VID, vid);

val = mt7530_read(priv, MT7530_VAWD1);

entry->old_members = (val >> PORT_MEM_SHFT) & PORT_MEM_MASK;

/* Manipulate entry */
vlan_op(priv, entry);

/* Flush result to hardware */
mt7530_vlan_cmd(priv, MT7530_VTCR_WR_VID, vid);
}

static void
mt7530_port_vlan_add(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan)
{
bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
struct mt7530_hw_vlan_entry new_entry;
struct mt7530_priv *priv = ds->priv;
u16 vid;

/* The port is kept as VLAN-unaware if bridge with vlan_filtering not
* being set.
*/
if (!priv->ports[port].vlan_filtering)
return;

mutex_lock(&priv->reg_mutex);

for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) {
mt7530_hw_vlan_entry_init(&new_entry, port, untagged);
mt7530_hw_vlan_update(priv, vid, &new_entry,
mt7530_hw_vlan_add);
}

if (pvid) {
mt7530_rmw(priv, MT7530_PPBV1_P(port), G0_PORT_VID_MASK,
G0_PORT_VID(vlan->vid_end));
priv->ports[port].pvid = vlan->vid_end;
}

mutex_unlock(&priv->reg_mutex);
}

static int
mt7530_port_vlan_del(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan)
{
struct mt7530_hw_vlan_entry target_entry;
struct mt7530_priv *priv = ds->priv;
u16 vid, pvid;

/* The port is kept as VLAN-unaware if bridge with vlan_filtering not
* being set.
*/
if (!priv->ports[port].vlan_filtering)
return 0;

mutex_lock(&priv->reg_mutex);

pvid = priv->ports[port].pvid;
for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) {
mt7530_hw_vlan_entry_init(&target_entry, port, 0);
mt7530_hw_vlan_update(priv, vid, &target_entry,
mt7530_hw_vlan_del);

/* PVID is being restored to the default whenever the PVID port
* is being removed from the VLAN.
*/
if (pvid == vid)
pvid = G0_PORT_VID_DEF;
}

mt7530_rmw(priv, MT7530_PPBV1_P(port), G0_PORT_VID_MASK, pvid);
priv->ports[port].pvid = pvid;

mutex_unlock(&priv->reg_mutex);

return 0;
}

static enum dsa_tag_protocol
mtk_get_tag_protocol(struct dsa_switch *ds, int port)
{
Expand Down Expand Up @@ -1035,6 +1317,10 @@ static const struct dsa_switch_ops mt7530_switch_ops = {
.port_fdb_add = mt7530_port_fdb_add,
.port_fdb_del = mt7530_port_fdb_del,
.port_fdb_dump = mt7530_port_fdb_dump,
.port_vlan_filtering = mt7530_port_vlan_filtering,
.port_vlan_prepare = mt7530_port_vlan_prepare,
.port_vlan_add = mt7530_port_vlan_add,
.port_vlan_del = mt7530_port_vlan_del,
};

static int
Expand Down
Loading

0 comments on commit be17bbe

Please sign in to comment.