| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Mediatek MT7628 Embedded Switch (ESW) DSA driver |
| * Copyright (C) 2026 Joris Vaisvila <joey@tinyisr.com> |
| * |
| * Portions derived from OpenWRT esw_rt3050 driver: |
| * Copyright (C) 2009-2015 John Crispin <blogic@openwrt.org> |
| * Copyright (C) 2009-2015 Felix Fietkau <nbd@nbd.name> |
| * Copyright (C) 2013-2015 Michael Lee <igvtee@gmail.com> |
| * Copyright (C) 2016 Vittorio Gambaletta <openwrt@vittgam.net> |
| */ |
| |
| #include <linux/platform_device.h> |
| #include <linux/etherdevice.h> |
| #include <linux/netdevice.h> |
| #include <linux/dsa/8021q.h> |
| #include <linux/if_bridge.h> |
| #include <linux/module.h> |
| #include <linux/mdio.h> |
| #include <linux/of.h> |
| #include <linux/of_mdio.h> |
| #include <linux/of_net.h> |
| #include <linux/kernel.h> |
| #include <linux/regmap.h> |
| #include <linux/reset.h> |
| #include <net/dsa.h> |
| |
| #define MT7628_ESW_REG_IMR 0x04 |
| #define MT7628_ESW_REG_FCT0 0x08 |
| #define MT7628_ESW_REG_PFC1 0x14 |
| #define MT7628_ESW_REG_PVIDC(port) (0x40 + 4 * ((port) / 2)) |
| #define MT7628_ESW_REG_VLANI(vlan) (0x50 + 4 * ((vlan) / 2)) |
| #define MT7628_ESW_REG_VMSC(vlan) (0x70 + 4 * ((vlan) / 4)) |
| #define MT7628_ESW_REG_VUB(vlan) (0x100 + 4 * ((vlan) / 4)) |
| #define MT7628_ESW_REG_SOCPC 0x8c |
| #define MT7628_ESW_REG_POC0 0x90 |
| #define MT7628_ESW_REG_POC2 0x98 |
| #define MT7628_ESW_REG_SGC 0x9c |
| #define MT7628_ESW_REG_PCR0 0xc0 |
| #define MT7628_ESW_REG_PCR1 0xc4 |
| #define MT7628_ESW_REG_FPA2 0xc8 |
| #define MT7628_ESW_REG_FCT2 0xcc |
| #define MT7628_ESW_REG_SGC2 0xe4 |
| |
| #define MT7628_ESW_FCT0_DROP_SET_TH GENMASK(7, 0) |
| #define MT7628_ESW_FCT0_DROP_RLS_TH GENMASK(15, 8) |
| #define MT7628_ESW_FCT0_FC_SET_TH GENMASK(23, 16) |
| #define MT7628_ESW_FCT0_FC_RLS_TH GENMASK(31, 24) |
| |
| #define MT7628_ESW_PFC1_EN_VLAN GENMASK(22, 16) |
| |
| #define MT7628_ESW_PVID_S 12 |
| #define MT7628_ESW_PVID_M GENMASK(11, 0) |
| #define MT7628_ESW_PVID_SHIFT(port) \ |
| (MT7628_ESW_PVID_S * ((port) % 2)) |
| #define MT7628_ESW_PVID_MASK(port) \ |
| (MT7628_ESW_PVID_M << MT7628_ESW_PVID_SHIFT(port)) |
| #define MT7628_ESW_PVID_PREP(port, pvid) \ |
| (((pvid) & MT7628_ESW_PVID_M) << MT7628_ESW_PVID_SHIFT(port)) |
| |
| #define MT7628_ESW_VID_S 12 |
| #define MT7628_ESW_VID_M GENMASK(11, 0) |
| #define MT7628_ESW_VID_SHIFT(vlan) \ |
| (MT7628_ESW_VID_S * ((vlan) % 2)) |
| #define MT7628_ESW_VID_MASK(vlan) \ |
| (MT7628_ESW_VID_M << MT7628_ESW_VID_SHIFT(vlan)) |
| #define MT7628_ESW_VID_PREP(vlan, vid) \ |
| (((vid) & MT7628_ESW_VID_M) << MT7628_ESW_VID_SHIFT(vlan)) |
| |
| #define MT7628_ESW_VMSC_S 8 |
| #define MT7628_ESW_VMSC_M GENMASK(7, 0) |
| #define MT7628_ESW_VMSC_SHIFT(vlan) \ |
| (MT7628_ESW_VMSC_S * ((vlan) % 4)) |
| #define MT7628_ESW_VMSC_MASK(vlan) \ |
| (MT7628_ESW_VMSC_M << MT7628_ESW_VMSC_SHIFT(vlan)) |
| #define MT7628_ESW_VMSC_PREP(vlan, vmsc) \ |
| (((vmsc) & MT7628_ESW_VMSC_M) << MT7628_ESW_VMSC_SHIFT(vlan)) |
| |
| #define MT7628_ESW_VUB_S 7 |
| #define MT7628_ESW_VUB_M GENMASK(6, 0) |
| #define MT7628_ESW_VUB_SHIFT(vlan) \ |
| (MT7628_ESW_VUB_S * ((vlan) % 4)) |
| #define MT7628_ESW_VUB_MASK(vlan) \ |
| (MT7628_ESW_VUB_M << MT7628_ESW_VUB_SHIFT(vlan)) |
| #define MT7628_ESW_VUB_PREP(vlan, vub) \ |
| (((vub) & MT7628_ESW_VUB_M) << MT7628_ESW_VUB_SHIFT(vlan)) |
| |
| #define MT7628_ESW_SOCPC_CRC_PADDING BIT(25) |
| #define MT7628_ESW_SOCPC_DISBC2CPU GENMASK(22, 16) |
| #define MT7628_ESW_SOCPC_DISMC2CPU GENMASK(14, 8) |
| #define MT7628_ESW_SOCPC_DISUN2CPU GENMASK(6, 0) |
| |
| #define MT7628_ESW_POC0_PORT_DISABLE GENMASK(29, 23) |
| |
| #define MT7628_ESW_POC2_PER_VLAN_UNTAG_EN BIT(15) |
| |
| #define MT7628_ESW_SGC_AGING_INTERVAL GENMASK(3, 0) |
| #define MT7628_ESW_BC_STORM_PROT GENMASK(5, 4) |
| #define MT7628_ESW_PKT_MAX_LEN GENMASK(7, 6) |
| #define MT7628_ESW_DIS_PKT_ABORT BIT(8) |
| #define MT7628_ESW_ADDRESS_HASH_ALG GENMASK(10, 9) |
| #define MT7628_ESW_DISABLE_TX_BACKOFF BIT(11) |
| #define MT7628_ESW_BP_JAM_CNT GENMASK(15, 12) |
| #define MT7628_ESW_DISMIIPORT_WASTX GENMASK(17, 16) |
| #define MT7628_ESW_BP_MODE GENMASK(19, 18) |
| #define MT7628_ESW_BISH_DIS BIT(20) |
| #define MT7628_ESW_BISH_TH GENMASK(22, 21) |
| #define MT7628_ESW_LED_FLASH_TIME GENMASK(24, 23) |
| #define MT7628_ESW_RMC_RULE GENMASK(26, 25) |
| #define MT7628_ESW_IP_MULT_RULE GENMASK(28, 27) |
| #define MT7628_ESW_LEN_ERR_CHK BIT(29) |
| #define MT7628_ESW_BKOFF_ALG BIT(30) |
| |
| #define MT7628_ESW_PCR0_WT_NWAY_DATA GENMASK(31, 16) |
| #define MT7628_ESW_PCR0_RD_PHY_CMD BIT(14) |
| #define MT7628_ESW_PCR0_WT_PHY_CMD BIT(13) |
| #define MT7628_ESW_PCR0_CPU_PHY_REG GENMASK(12, 8) |
| #define MT7628_ESW_PCR0_CPU_PHY_ADDR GENMASK(4, 0) |
| |
| #define MT7628_ESW_PCR1_RD_DATA GENMASK(31, 16) |
| #define MT7628_ESW_PCR1_RD_DONE BIT(1) |
| #define MT7628_ESW_PCR1_WT_DONE BIT(0) |
| |
| #define MT7628_ESW_FPA2_AP_EN BIT(29) |
| #define MT7628_ESW_FPA2_EXT_PHY_ADDR_BASE GENMASK(28, 24) |
| #define MT7628_ESW_FPA2_FORCE_RGMII_LINK1 BIT(13) |
| #define MT7628_ESW_FPA2_FORCE_RGMII_EN1 BIT(11) |
| |
| #define MT7628_ESW_FCT2_MUST_DROP_RLS_TH GENMASK(17, 13) |
| #define MT7628_ESW_FCT2_MUST_DROP_SET_TH GENMASK(12, 8) |
| #define MT7628_ESW_FCT2_MC_PER_PORT_TH GENMASK(5, 0) |
| |
| #define MT7628_ESW_SGC2_SPECIAL_TAG_EN BIT(23) |
| #define MT7628_ESW_SGC2_TX_CPU_TPID_BIT_MAP GENMASK(22, 16) |
| #define MT7628_ESW_SGC2_DOUBLE_TAG_EN GENMASK(6, 0) |
| |
| #define MT7628_ESW_PORTS_NOCPU GENMASK(5, 0) |
| #define MT7628_ESW_PORTS_CPU BIT(6) |
| #define MT7628_ESW_PORTS_ALL GENMASK(6, 0) |
| |
| #define MT7628_ESW_NUM_PORTS 7 |
| #define MT7628_NUM_VLANS 16 |
| |
| static const struct regmap_config mt7628_esw_regmap_cfg = { |
| .name = "mt7628-esw", |
| .reg_bits = 32, |
| .val_bits = 32, |
| .reg_stride = 4, |
| .fast_io = true, |
| .reg_format_endian = REGMAP_ENDIAN_LITTLE, |
| .val_format_endian = REGMAP_ENDIAN_LITTLE, |
| }; |
| |
| struct mt7628_vlan { |
| bool active; |
| u8 members; |
| u8 untag; |
| u16 vid; |
| }; |
| |
| struct mt7628_esw { |
| struct reset_control *rst_ephy; |
| struct reset_control *rst_esw; |
| struct regmap *regmap; |
| struct dsa_switch *ds; |
| u16 tag_8021q_pvid[MT7628_ESW_NUM_PORTS]; |
| struct mt7628_vlan vlans[MT7628_NUM_VLANS]; |
| struct device *dev; |
| }; |
| |
| static int mt7628_mii_read(struct mii_bus *bus, int port, int regnum) |
| { |
| struct mt7628_esw *esw = bus->priv; |
| int ret; |
| u32 val; |
| |
| /* |
| * RD_DONE bit is read to clear. Read PCR1 once to acknowledge any |
| * stale completion indicator before starting a new transaction. |
| */ |
| ret = regmap_read(esw->regmap, MT7628_ESW_REG_PCR1, &val); |
| if (ret) |
| goto out; |
| |
| ret = regmap_write(esw->regmap, MT7628_ESW_REG_PCR0, |
| FIELD_PREP(MT7628_ESW_PCR0_CPU_PHY_REG, |
| regnum) | |
| FIELD_PREP(MT7628_ESW_PCR0_CPU_PHY_ADDR, |
| port) | MT7628_ESW_PCR0_RD_PHY_CMD); |
| if (ret) |
| goto out; |
| |
| ret = regmap_read_poll_timeout(esw->regmap, MT7628_ESW_REG_PCR1, val, |
| (val & MT7628_ESW_PCR1_RD_DONE), 10, |
| 5000); |
| if (ret) |
| goto out; |
| |
| return FIELD_GET(MT7628_ESW_PCR1_RD_DATA, val); |
| |
| out: |
| dev_err(&bus->dev, "read failed. MDIO timeout?\n"); |
| return ret; |
| } |
| |
| static int mt7628_mii_write(struct mii_bus *bus, int port, int regnum, u16 dat) |
| { |
| struct mt7628_esw *esw = bus->priv; |
| u32 val; |
| int ret; |
| |
| /* |
| * WT_DONE bit is read to clear. Read PCR1 once to acknowledge any |
| * stale completion indicator before starting a new transaction. |
| */ |
| ret = regmap_read(esw->regmap, MT7628_ESW_REG_PCR1, &val); |
| if (ret) |
| goto out; |
| |
| ret = regmap_write(esw->regmap, MT7628_ESW_REG_PCR0, |
| FIELD_PREP(MT7628_ESW_PCR0_WT_NWAY_DATA, dat) | |
| FIELD_PREP(MT7628_ESW_PCR0_CPU_PHY_REG, |
| regnum) | |
| FIELD_PREP(MT7628_ESW_PCR0_CPU_PHY_ADDR, |
| port) | MT7628_ESW_PCR0_WT_PHY_CMD); |
| if (ret) |
| goto out; |
| |
| ret = regmap_read_poll_timeout(esw->regmap, MT7628_ESW_REG_PCR1, val, |
| (val & MT7628_ESW_PCR1_WT_DONE), 10, |
| 5000); |
| if (ret) |
| goto out; |
| |
| return 0; |
| |
| out: |
| dev_err(&bus->dev, "write failed. MDIO timeout?\n"); |
| return ret; |
| } |
| |
| static int mt7628_setup_internal_mdio(struct dsa_switch *ds) |
| { |
| struct mt7628_esw *esw = ds->priv; |
| struct device *dev = ds->dev; |
| struct mii_bus *bus; |
| |
| bus = devm_mdiobus_alloc(dev); |
| if (!bus) |
| return -ENOMEM; |
| |
| bus->name = "MT7628 internal MDIO bus"; |
| snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(dev)); |
| bus->priv = esw; |
| bus->read = mt7628_mii_read; |
| bus->write = mt7628_mii_write; |
| bus->parent = dev; |
| |
| ds->user_mii_bus = bus; |
| bus->phy_mask = ~ds->phys_mii_mask; |
| |
| return devm_mdiobus_register(dev, bus); |
| } |
| |
| static void mt7628_switch_init(struct dsa_switch *ds) |
| { |
| struct mt7628_esw *esw = ds->priv; |
| |
| regmap_write(esw->regmap, MT7628_ESW_REG_FCT0, |
| FIELD_PREP(MT7628_ESW_FCT0_DROP_SET_TH, 0x50) | |
| FIELD_PREP(MT7628_ESW_FCT0_DROP_RLS_TH, 0x78) | |
| FIELD_PREP(MT7628_ESW_FCT0_FC_SET_TH, 0xa0) | |
| FIELD_PREP(MT7628_ESW_FCT0_FC_RLS_TH, 0xc8)); |
| |
| regmap_write(esw->regmap, MT7628_ESW_REG_FCT2, |
| FIELD_PREP(MT7628_ESW_FCT2_MC_PER_PORT_TH, 0xc) | |
| FIELD_PREP(MT7628_ESW_FCT2_MUST_DROP_SET_TH, 0x10) | |
| FIELD_PREP(MT7628_ESW_FCT2_MUST_DROP_RLS_TH, 0x12)); |
| |
| /* |
| * general switch configuration: |
| * 300s aging interval |
| * broadcast storm prevention disabled |
| * max packet length 1536 bytes |
| * disable collision 16 packet abort and late collision abort |
| * use xor48 for address hashing |
| * disable tx backoff |
| * 10 packet back pressure jam |
| * disable was_transmit |
| * jam until BP condition released |
| * 30ms LED flash |
| * rmc tb fault to all ports |
| * unmatched IGMP as broadcast |
| */ |
| regmap_write(esw->regmap, MT7628_ESW_REG_SGC, |
| FIELD_PREP(MT7628_ESW_SGC_AGING_INTERVAL, 1) | |
| FIELD_PREP(MT7628_ESW_BC_STORM_PROT, 0) | |
| FIELD_PREP(MT7628_ESW_PKT_MAX_LEN, 0) | |
| MT7628_ESW_DIS_PKT_ABORT | |
| FIELD_PREP(MT7628_ESW_ADDRESS_HASH_ALG, 1) | |
| MT7628_ESW_DISABLE_TX_BACKOFF | |
| FIELD_PREP(MT7628_ESW_BP_JAM_CNT, 10) | |
| FIELD_PREP(MT7628_ESW_DISMIIPORT_WASTX, 0) | |
| FIELD_PREP(MT7628_ESW_BP_MODE, 0b10) | |
| FIELD_PREP(MT7628_ESW_LED_FLASH_TIME, 0) | |
| FIELD_PREP(MT7628_ESW_RMC_RULE, 0) | |
| FIELD_PREP(MT7628_ESW_IP_MULT_RULE, 0)); |
| |
| regmap_write(esw->regmap, MT7628_ESW_REG_SOCPC, |
| MT7628_ESW_SOCPC_CRC_PADDING | |
| FIELD_PREP(MT7628_ESW_SOCPC_DISUN2CPU, |
| MT7628_ESW_PORTS_CPU) | |
| FIELD_PREP(MT7628_ESW_SOCPC_DISMC2CPU, |
| MT7628_ESW_PORTS_CPU) | |
| FIELD_PREP(MT7628_ESW_SOCPC_DISBC2CPU, |
| MT7628_ESW_PORTS_CPU)); |
| |
| regmap_set_bits(esw->regmap, MT7628_ESW_REG_FPA2, |
| MT7628_ESW_FPA2_FORCE_RGMII_EN1 | |
| MT7628_ESW_FPA2_FORCE_RGMII_LINK1 | |
| MT7628_ESW_FPA2_AP_EN); |
| |
| regmap_update_bits(esw->regmap, MT7628_ESW_REG_FPA2, |
| MT7628_ESW_FPA2_EXT_PHY_ADDR_BASE, |
| FIELD_PREP(MT7628_ESW_FPA2_EXT_PHY_ADDR_BASE, 31)); |
| |
| /* disable all interrupts */ |
| regmap_write(esw->regmap, MT7628_ESW_REG_IMR, 0); |
| |
| /* enable MT7628 DSA tag on CPU port */ |
| regmap_write(esw->regmap, MT7628_ESW_REG_SGC2, |
| MT7628_ESW_SGC2_SPECIAL_TAG_EN | |
| FIELD_PREP(MT7628_ESW_SGC2_TX_CPU_TPID_BIT_MAP, |
| MT7628_ESW_PORTS_CPU)); |
| |
| /* |
| * Double tag feature allows switch to always append the port PVID VLAN tag |
| * regardless of if the incoming packet already has a VLAN tag. |
| * This is enabled to simulate VLAN unawareness. |
| */ |
| regmap_set_bits(esw->regmap, MT7628_ESW_REG_SGC2, |
| FIELD_PREP(MT7628_ESW_SGC2_DOUBLE_TAG_EN, |
| MT7628_ESW_PORTS_NOCPU)); |
| |
| regmap_set_bits(esw->regmap, MT7628_ESW_REG_POC2, |
| MT7628_ESW_POC2_PER_VLAN_UNTAG_EN); |
| |
| regmap_update_bits(esw->regmap, MT7628_ESW_REG_PFC1, |
| MT7628_ESW_PFC1_EN_VLAN, |
| FIELD_PREP(MT7628_ESW_PFC1_EN_VLAN, |
| MT7628_ESW_PORTS_ALL)); |
| } |
| |
| static void mt7628_esw_set_pvid(struct mt7628_esw *esw, unsigned int port, |
| unsigned int pvid) |
| { |
| regmap_update_bits(esw->regmap, MT7628_ESW_REG_PVIDC(port), |
| MT7628_ESW_PVID_MASK(port), |
| MT7628_ESW_PVID_PREP(port, pvid)); |
| } |
| |
| static void mt7628_esw_set_vlan_id(struct mt7628_esw *esw, unsigned int vlan, |
| unsigned int vid) |
| { |
| regmap_update_bits(esw->regmap, MT7628_ESW_REG_VLANI(vlan), |
| MT7628_ESW_VID_MASK(vlan), |
| MT7628_ESW_VID_PREP(vlan, vid)); |
| } |
| |
| static void mt7628_esw_set_vmsc(struct mt7628_esw *esw, unsigned int vlan, |
| unsigned int msc) |
| { |
| regmap_update_bits(esw->regmap, MT7628_ESW_REG_VMSC(vlan), |
| MT7628_ESW_VMSC_MASK(vlan), |
| MT7628_ESW_VMSC_PREP(vlan, msc)); |
| } |
| |
| static void mt7628_esw_set_vub(struct mt7628_esw *esw, unsigned int vlan, |
| unsigned int vub) |
| { |
| regmap_update_bits(esw->regmap, MT7628_ESW_REG_VUB(vlan), |
| MT7628_ESW_VUB_MASK(vlan), |
| MT7628_ESW_VUB_PREP(vlan, vub)); |
| } |
| |
| static void mt7628_vlan_sync(struct dsa_switch *ds) |
| { |
| struct mt7628_esw *esw = ds->priv; |
| int i; |
| |
| for (i = 0; i < MT7628_NUM_VLANS; i++) { |
| struct mt7628_vlan *vlan = &esw->vlans[i]; |
| |
| mt7628_esw_set_vmsc(esw, i, vlan->members); |
| mt7628_esw_set_vlan_id(esw, i, vlan->vid); |
| mt7628_esw_set_vub(esw, i, vlan->untag); |
| } |
| |
| for (i = 0; i < ds->num_ports; i++) |
| mt7628_esw_set_pvid(esw, i, esw->tag_8021q_pvid[i]); |
| } |
| |
| static int mt7628_setup(struct dsa_switch *ds) |
| { |
| struct mt7628_esw *esw = ds->priv; |
| int ret; |
| |
| ret = reset_control_reset(esw->rst_esw); |
| if (ret) |
| return ret; |
| usleep_range(1000, 2000); |
| |
| ret = reset_control_reset(esw->rst_ephy); |
| if (ret) |
| return ret; |
| usleep_range(1000, 2000); |
| /* |
| * all MMIO reads hang if esw is not out of reset |
| * ephy needs extra time to get out of reset or it ends up misconfigured |
| */ |
| |
| mt7628_switch_init(ds); |
| |
| ret = mt7628_setup_internal_mdio(ds); |
| if (ret) |
| return ret; |
| |
| rtnl_lock(); |
| ret = dsa_tag_8021q_register(ds, htons(ETH_P_8021Q)); |
| rtnl_unlock(); |
| |
| return ret; |
| } |
| |
| static int mt7628_port_enable(struct dsa_switch *ds, int port, |
| struct phy_device *phy) |
| { |
| struct mt7628_esw *esw = ds->priv; |
| |
| /* |
| * All switch ports are disabled by esw reset and enabled here by DSA. |
| */ |
| regmap_clear_bits(esw->regmap, MT7628_ESW_REG_POC0, |
| FIELD_PREP(MT7628_ESW_POC0_PORT_DISABLE, BIT(port))); |
| return 0; |
| } |
| |
| static void mt7628_port_disable(struct dsa_switch *ds, int port) |
| { |
| struct mt7628_esw *esw = ds->priv; |
| |
| regmap_set_bits(esw->regmap, MT7628_ESW_REG_POC0, |
| FIELD_PREP(MT7628_ESW_POC0_PORT_DISABLE, BIT(port))); |
| } |
| |
| static enum dsa_tag_protocol |
| mt7628_get_tag_proto(struct dsa_switch *ds, int port, enum dsa_tag_protocol mp) |
| { |
| return DSA_TAG_PROTO_MT7628; |
| } |
| |
| static void mt7628_phylink_get_caps(struct dsa_switch *ds, int port, |
| struct phylink_config *config) |
| { |
| switch (port) { |
| case 6: |
| config->mac_capabilities |= MAC_1000; |
| fallthrough; |
| case 0 ... 4: |
| config->mac_capabilities |= MAC_100 | MAC_10; |
| __set_bit(PHY_INTERFACE_MODE_INTERNAL, |
| config->supported_interfaces); |
| break; |
| default: |
| break; /* port 5 does not exist on MT7628 */ |
| } |
| } |
| |
| static int mt7628_dsa_8021q_vlan_add(struct dsa_switch *ds, int port, |
| u16 vid, u16 flags) |
| { |
| struct mt7628_esw *esw = ds->priv; |
| struct mt7628_vlan *vlan = NULL; |
| int i; |
| |
| for (i = 0; i < MT7628_NUM_VLANS; i++) { |
| struct mt7628_vlan *check_vlan = &esw->vlans[i]; |
| |
| if (!check_vlan->active && !vlan) |
| vlan = check_vlan; |
| |
| if (check_vlan->active && check_vlan->vid == vid) { |
| vlan = check_vlan; |
| break; |
| } |
| } |
| |
| if (!vlan) |
| return -ENOSPC; |
| |
| vlan->vid = vid; |
| vlan->active = true; |
| vlan->members |= BIT(port); |
| |
| if (flags & BRIDGE_VLAN_INFO_PVID) |
| esw->tag_8021q_pvid[port] = vid; |
| |
| if (flags & BRIDGE_VLAN_INFO_UNTAGGED) |
| vlan->untag |= BIT(port); |
| |
| mt7628_vlan_sync(ds); |
| return 0; |
| } |
| |
| static int mt7628_dsa_8021q_vlan_del(struct dsa_switch *ds, int port, u16 vid) |
| { |
| struct mt7628_esw *esw = ds->priv; |
| struct mt7628_vlan *vlan = NULL; |
| int i; |
| |
| for (i = 0; i < MT7628_NUM_VLANS; i++) { |
| struct mt7628_vlan *check_vlan = &esw->vlans[i]; |
| |
| if (!check_vlan->active || check_vlan->vid != vid) |
| continue; |
| vlan = check_vlan; |
| break; |
| } |
| if (!vlan) |
| return -ENOENT; |
| |
| if (esw->tag_8021q_pvid[port] == vid) |
| esw->tag_8021q_pvid[port] = 0; |
| |
| vlan->members &= ~BIT(port); |
| vlan->untag &= ~BIT(port); |
| |
| if (!vlan->members) { |
| vlan->active = false; |
| vlan->vid = 0; |
| } |
| |
| mt7628_vlan_sync(ds); |
| return 0; |
| } |
| |
| static void mt7628_teardown(struct dsa_switch *ds) |
| { |
| rtnl_lock(); |
| dsa_tag_8021q_unregister(ds); |
| rtnl_unlock(); |
| } |
| |
| static const struct dsa_switch_ops mt7628_switch_ops = { |
| .get_tag_protocol = mt7628_get_tag_proto, |
| .setup = mt7628_setup, |
| .teardown = mt7628_teardown, |
| .port_enable = mt7628_port_enable, |
| .port_disable = mt7628_port_disable, |
| .phylink_get_caps = mt7628_phylink_get_caps, |
| .tag_8021q_vlan_add = mt7628_dsa_8021q_vlan_add, |
| .tag_8021q_vlan_del = mt7628_dsa_8021q_vlan_del, |
| }; |
| |
| static int mt7628_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct mt7628_esw *esw; |
| struct dsa_switch *ds; |
| void __iomem *base; |
| |
| ds = devm_kzalloc(&pdev->dev, sizeof(*ds), GFP_KERNEL); |
| if (!ds) |
| return -ENOMEM; |
| |
| esw = devm_kzalloc(&pdev->dev, sizeof(*esw), GFP_KERNEL); |
| if (!esw) |
| return -ENOMEM; |
| |
| base = devm_platform_ioremap_resource(pdev, 0); |
| if (IS_ERR(base)) |
| return PTR_ERR(base); |
| |
| esw->regmap = devm_regmap_init_mmio(&pdev->dev, base, |
| &mt7628_esw_regmap_cfg); |
| if (IS_ERR(esw->regmap)) |
| return PTR_ERR(esw->regmap); |
| |
| esw->rst_ephy = devm_reset_control_get_exclusive(&pdev->dev, "ephy"); |
| if (IS_ERR(esw->rst_ephy)) |
| return dev_err_probe(dev, PTR_ERR(esw->rst_ephy), |
| "failed to get EPHY reset\n"); |
| |
| esw->rst_esw = devm_reset_control_get_exclusive(&pdev->dev, "esw"); |
| if (IS_ERR(esw->rst_esw)) |
| return dev_err_probe(dev, PTR_ERR(esw->rst_esw), |
| "failed to get ESW reset\n"); |
| |
| ds->dev = dev; |
| ds->num_ports = MT7628_ESW_NUM_PORTS; |
| ds->ops = &mt7628_switch_ops; |
| ds->priv = esw; |
| esw->ds = ds; |
| esw->dev = dev; |
| dev_set_drvdata(dev, esw); |
| |
| return dsa_register_switch(ds); |
| } |
| |
| static void mt7628_remove(struct platform_device *pdev) |
| { |
| struct mt7628_esw *esw = platform_get_drvdata(pdev); |
| |
| if (!esw) |
| return; |
| |
| dsa_unregister_switch(esw->ds); |
| } |
| |
| static void mt7628_shutdown(struct platform_device *pdev) |
| { |
| struct mt7628_esw *esw = platform_get_drvdata(pdev); |
| |
| if (!esw) |
| return; |
| |
| dsa_switch_shutdown(esw->ds); |
| dev_set_drvdata(&pdev->dev, NULL); |
| } |
| |
| static const struct of_device_id mt7628_of_match[] = { |
| { .compatible = "mediatek,mt7628-esw" }, |
| {} |
| }; |
| |
| MODULE_DEVICE_TABLE(of, mt7628_of_match); |
| |
| static struct platform_driver mt7628_driver = { |
| .driver = { |
| .name = "mt7628-esw", |
| .of_match_table = mt7628_of_match, |
| }, |
| .probe = mt7628_probe, |
| .remove = mt7628_remove, |
| .shutdown = mt7628_shutdown, |
| }; |
| |
| module_platform_driver(mt7628_driver); |
| |
| MODULE_AUTHOR("Joris Vaisvila <joey@tinyisr.com>"); |
| MODULE_DESCRIPTION("Driver for Mediatek MT7628 embedded switch"); |
| MODULE_LICENSE("GPL"); |