Support for VLAN add, del, prepare and filtering operations. The VLAN aware is a global setting. Mixed vlan filterings are not supported. vlan_filtering_is_global is made as true in lan937x_setup function. Signed-off-by: Prasanna Vengateshan <prasanna.vengateshan@xxxxxxxxxxxxx> --- drivers/net/dsa/microchip/lan937x_main.c | 186 +++++++++++++++++++++++ 1 file changed, 186 insertions(+) diff --git a/drivers/net/dsa/microchip/lan937x_main.c b/drivers/net/dsa/microchip/lan937x_main.c index 8849ed17ff3e..a58a48de9475 100644 --- a/drivers/net/dsa/microchip/lan937x_main.c +++ b/drivers/net/dsa/microchip/lan937x_main.c @@ -17,6 +17,14 @@ #include "ksz_common.h" #include "lan937x_dev.h" +static int lan937x_wait_vlan_ctrl_ready(struct ksz_device *dev) +{ + unsigned int val; + + return regmap_read_poll_timeout(dev->regmap[0], REG_SW_VLAN_CTRL, val, + !(val & VLAN_START), 10, 1000); +} + static u8 lan937x_get_fid(u16 vid) { if (vid > ALU_FID_SIZE) @@ -25,6 +33,97 @@ static u8 lan937x_get_fid(u16 vid) return vid; } +static int lan937x_get_vlan_table(struct ksz_device *dev, u16 vid, + struct lan937x_vlan *vlan_entry) +{ + u32 data; + int ret; + + mutex_lock(&dev->vlan_mutex); + + ret = ksz_write16(dev, REG_SW_VLAN_ENTRY_INDEX__2, vid & VLAN_INDEX_M); + if (ret < 0) + goto exit; + + ret = ksz_write8(dev, REG_SW_VLAN_CTRL, VLAN_READ | VLAN_START); + if (ret < 0) + goto exit; + + /* wait to be cleared */ + ret = lan937x_wait_vlan_ctrl_ready(dev); + if (ret < 0) + goto exit; + + ret = ksz_read32(dev, REG_SW_VLAN_ENTRY__4, &data); + if (ret < 0) + goto exit; + + vlan_entry->valid = !!(data & VLAN_VALID); + vlan_entry->fid = data & VLAN_FID_M; + + ret = ksz_read32(dev, REG_SW_VLAN_ENTRY_UNTAG__4, + &vlan_entry->untag_prtmap); + if (ret < 0) + goto exit; + + ret = ksz_read32(dev, REG_SW_VLAN_ENTRY_PORTS__4, + &vlan_entry->fwd_map); + if (ret < 0) + goto exit; + + ret = ksz_write8(dev, REG_SW_VLAN_CTRL, 0); + if (ret < 0) + goto exit; + +exit: + mutex_unlock(&dev->vlan_mutex); + + return ret; +} + +static int lan937x_set_vlan_table(struct ksz_device *dev, u16 vid, + struct lan937x_vlan *vlan_entry) +{ + u32 data; + int ret; + + mutex_lock(&dev->vlan_mutex); + + data = vlan_entry->valid ? VLAN_VALID : 0; + data |= vlan_entry->fid; + + ret = ksz_write32(dev, REG_SW_VLAN_ENTRY__4, data); + if (ret < 0) + goto exit; + + ret = ksz_write32(dev, REG_SW_VLAN_ENTRY_UNTAG__4, + vlan_entry->untag_prtmap); + if (ret < 0) + goto exit; + + ret = ksz_write32(dev, REG_SW_VLAN_ENTRY_PORTS__4, vlan_entry->fwd_map); + if (ret < 0) + goto exit; + + ret = ksz_write16(dev, REG_SW_VLAN_ENTRY_INDEX__2, vid & VLAN_INDEX_M); + if (ret < 0) + goto exit; + + ret = ksz_write8(dev, REG_SW_VLAN_CTRL, VLAN_START | VLAN_WRITE); + if (ret < 0) + goto exit; + + /* wait to be cleared */ + ret = lan937x_wait_vlan_ctrl_ready(dev); + if (ret < 0) + goto exit; + +exit: + mutex_unlock(&dev->vlan_mutex); + + return ret; +} + static int lan937x_read_table(struct ksz_device *dev, u32 *table) { int ret; @@ -162,6 +261,90 @@ static void lan937x_port_stp_state_set(struct dsa_switch *ds, int port, ksz_update_port_member(dev, port); } +static int lan937x_port_vlan_filtering(struct dsa_switch *ds, int port, + bool flag, + struct netlink_ext_ack *extack) +{ + struct ksz_device *dev = ds->priv; + + /* enable/disable VLAN mode, once enabled, look up process starts + * and then forwarding and discarding are done based on port + * membership of the VLAN table + */ + return lan937x_cfg(dev, REG_SW_LUE_CTRL_0, SW_VLAN_ENABLE, flag); +} + +static int lan937x_port_vlan_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan, + struct netlink_ext_ack *extack) +{ + bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; + struct ksz_device *dev = ds->priv; + struct lan937x_vlan vlan_entry; + int ret; + + ret = lan937x_get_vlan_table(dev, vlan->vid, &vlan_entry); + if (ret < 0) { + NL_SET_ERR_MSG_MOD(extack, "Failed to get vlan table"); + return ret; + } + + vlan_entry.fid = lan937x_get_fid(vlan->vid); + vlan_entry.valid = true; + + /* set/clear switch port when updating vlan table registers */ + if (untagged) + vlan_entry.untag_prtmap |= BIT(port); + else + vlan_entry.untag_prtmap &= ~BIT(port); + + vlan_entry.fwd_map |= BIT(port); + + ret = lan937x_set_vlan_table(dev, vlan->vid, &vlan_entry); + if (ret < 0) { + NL_SET_ERR_MSG_MOD(extack, "Failed to set vlan table"); + return ret; + } + + /* change PVID */ + if (vlan->flags & BRIDGE_VLAN_INFO_PVID) { + ret = lan937x_pwrite16(dev, port, REG_PORT_DEFAULT_VID, + vlan->vid); + if (ret < 0) { + NL_SET_ERR_MSG_MOD(extack, "Failed to set pvid"); + return ret; + } + } + + return 0; +} + +static int lan937x_port_vlan_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + struct ksz_device *dev = ds->priv; + struct lan937x_vlan vlan_entry; + int ret; + + ret = lan937x_get_vlan_table(dev, vlan->vid, &vlan_entry); + if (ret < 0) { + dev_err(dev->dev, "Failed to get vlan table\n"); + return ret; + } + + /* clear port fwd map & untag entries*/ + vlan_entry.fwd_map &= ~BIT(port); + vlan_entry.untag_prtmap &= ~BIT(port); + + ret = lan937x_set_vlan_table(dev, vlan->vid, &vlan_entry); + if (ret < 0) { + dev_err(dev->dev, "Failed to set vlan table\n"); + return ret; + } + + return 0; +} + static int lan937x_port_fdb_add(struct dsa_switch *ds, int port, const unsigned char *addr, u16 vid) { @@ -1096,6 +1279,9 @@ const struct dsa_switch_ops lan937x_switch_ops = { .port_bridge_leave = ksz_port_bridge_leave, .port_stp_state_set = lan937x_port_stp_state_set, .port_fast_age = ksz_port_fast_age, + .port_vlan_filtering = lan937x_port_vlan_filtering, + .port_vlan_add = lan937x_port_vlan_add, + .port_vlan_del = lan937x_port_vlan_del, .port_fdb_dump = lan937x_port_fdb_dump, .port_fdb_add = lan937x_port_fdb_add, .port_fdb_del = lan937x_port_fdb_del, -- 2.30.2