diff options
Diffstat (limited to 'drivers/net/dsa')
29 files changed, 6506 insertions, 408 deletions
diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig index 71bb3aebded4..c6c5ecdbcaef 100644 --- a/drivers/net/dsa/Kconfig +++ b/drivers/net/dsa/Kconfig @@ -41,7 +41,7 @@ config NET_DSA_MT7530 config NET_DSA_MV88E6060 tristate "Marvell 88E6060 ethernet switch chip support" - depends on NET_DSA && NET_DSA_LEGACY + depends on NET_DSA select NET_DSA_TAG_TRAILER ---help--- This enables support for the Marvell 88E6060 ethernet switch @@ -51,6 +51,8 @@ source "drivers/net/dsa/microchip/Kconfig" source "drivers/net/dsa/mv88e6xxx/Kconfig" +source "drivers/net/dsa/sja1105/Kconfig" + config NET_DSA_QCA8K tristate "Qualcomm Atheros QCA8K Ethernet switch family support" depends on NET_DSA diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile index 82e5d794c41f..fefb6aaa82ba 100644 --- a/drivers/net/dsa/Makefile +++ b/drivers/net/dsa/Makefile @@ -18,3 +18,4 @@ obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX) += vitesse-vsc73xx.o obj-y += b53/ obj-y += microchip/ obj-y += mv88e6xxx/ +obj-y += sja1105/ diff --git a/drivers/net/dsa/b53/b53_common.c b/drivers/net/dsa/b53/b53_common.c index 0852e5e08177..c8040ecf4425 100644 --- a/drivers/net/dsa/b53/b53_common.c +++ b/drivers/net/dsa/b53/b53_common.c @@ -428,7 +428,6 @@ static void b53_enable_vlan(struct b53_device *dev, bool enable, b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt); dev->vlan_enabled = enable; - dev->vlan_filtering_enabled = enable_filtering; } static int b53_set_jumbo(struct b53_device *dev, bool enable, bool allow_10_100) @@ -665,7 +664,7 @@ int b53_configure_vlan(struct dsa_switch *ds) b53_do_vlan_op(dev, VTA_CMD_CLEAR); } - b53_enable_vlan(dev, false, dev->vlan_filtering_enabled); + b53_enable_vlan(dev, false, ds->vlan_filtering); b53_for_each_port(dev, i) b53_write16(dev, B53_VLAN_PAGE, @@ -966,6 +965,13 @@ static int b53_setup(struct dsa_switch *ds) b53_disable_port(ds, port); } + /* Let DSA handle the case were multiple bridges span the same switch + * device and different VLAN awareness settings are requested, which + * would be breaking filtering semantics for any of the other bridge + * devices. (not hardware supported) + */ + ds->vlan_filtering_is_global = true; + return ret; } @@ -1275,35 +1281,17 @@ EXPORT_SYMBOL(b53_phylink_mac_link_up); int b53_vlan_filtering(struct dsa_switch *ds, int port, bool vlan_filtering) { struct b53_device *dev = ds->priv; - struct net_device *bridge_dev; - unsigned int i; u16 pvid, new_pvid; - /* Handle the case were multiple bridges span the same switch device - * and one of them has a different setting than what is being requested - * which would be breaking filtering semantics for any of the other - * bridge devices. - */ - b53_for_each_port(dev, i) { - bridge_dev = dsa_to_port(ds, i)->bridge_dev; - if (bridge_dev && - bridge_dev != dsa_to_port(ds, port)->bridge_dev && - br_vlan_enabled(bridge_dev) != vlan_filtering) { - netdev_err(bridge_dev, - "VLAN filtering is global to the switch!\n"); - return -EINVAL; - } - } - b53_read16(dev, B53_VLAN_PAGE, B53_VLAN_PORT_DEF_TAG(port), &pvid); new_pvid = pvid; - if (dev->vlan_filtering_enabled && !vlan_filtering) { + if (!vlan_filtering) { /* Filtering is currently enabled, use the default PVID since * the bridge does not expect tagging anymore */ dev->ports[port].pvid = pvid; new_pvid = b53_default_pvid(dev); - } else if (!dev->vlan_filtering_enabled && vlan_filtering) { + } else { /* Filtering is currently disabled, restore the previous PVID */ new_pvid = dev->ports[port].pvid; } @@ -1329,7 +1317,7 @@ int b53_vlan_prepare(struct dsa_switch *ds, int port, if (vlan->vid_end > dev->num_vlans) return -ERANGE; - b53_enable_vlan(dev, true, dev->vlan_filtering_enabled); + b53_enable_vlan(dev, true, ds->vlan_filtering); return 0; } diff --git a/drivers/net/dsa/b53/b53_priv.h b/drivers/net/dsa/b53/b53_priv.h index e3441dcf2d21..f25bc80c4ffc 100644 --- a/drivers/net/dsa/b53/b53_priv.h +++ b/drivers/net/dsa/b53/b53_priv.h @@ -139,7 +139,6 @@ struct b53_device { unsigned int num_vlans; struct b53_vlan *vlans; bool vlan_enabled; - bool vlan_filtering_enabled; unsigned int num_ports; struct b53_port *ports; }; diff --git a/drivers/net/dsa/bcm_sf2.c b/drivers/net/dsa/bcm_sf2.c index c8e3f05e1d72..4ccb3239f5f7 100644 --- a/drivers/net/dsa/bcm_sf2.c +++ b/drivers/net/dsa/bcm_sf2.c @@ -1188,10 +1188,11 @@ static int bcm_sf2_sw_probe(struct platform_device *pdev) if (ret) goto out_mdio; - pr_info("Starfighter 2 top: %x.%02x, core: %x.%02x base: 0x%p, IRQs: %d, %d\n", - priv->hw_params.top_rev >> 8, priv->hw_params.top_rev & 0xff, - priv->hw_params.core_rev >> 8, priv->hw_params.core_rev & 0xff, - priv->core, priv->irq0, priv->irq1); + dev_info(&pdev->dev, + "Starfighter 2 top: %x.%02x, core: %x.%02x, IRQs: %d, %d\n", + priv->hw_params.top_rev >> 8, priv->hw_params.top_rev & 0xff, + priv->hw_params.core_rev >> 8, priv->hw_params.core_rev & 0xff, + priv->irq0, priv->irq1); return 0; diff --git a/drivers/net/dsa/bcm_sf2_cfp.c b/drivers/net/dsa/bcm_sf2_cfp.c index e6234d209787..4212bc4a5f31 100644 --- a/drivers/net/dsa/bcm_sf2_cfp.c +++ b/drivers/net/dsa/bcm_sf2_cfp.c @@ -886,6 +886,9 @@ static int bcm_sf2_cfp_rule_set(struct dsa_switch *ds, int port, fs->m_ext.data[1])) return -EINVAL; + if (fs->location != RX_CLS_LOC_ANY && fs->location >= CFP_NUM_RULES) + return -EINVAL; + if (fs->location != RX_CLS_LOC_ANY && test_bit(fs->location, priv->cfp.used)) return -EBUSY; @@ -974,6 +977,9 @@ static int bcm_sf2_cfp_rule_del(struct bcm_sf2_priv *priv, int port, u32 loc) struct cfp_rule *rule; int ret; + if (loc >= CFP_NUM_RULES) + return -EINVAL; + /* Refuse deleting unused rules, and those that are not unique since * that could leave IPv6 rules with one of the chained rule in the * table. diff --git a/drivers/net/dsa/lantiq_gswip.c b/drivers/net/dsa/lantiq_gswip.c index d8328866908c..4e64835deac2 100644 --- a/drivers/net/dsa/lantiq_gswip.c +++ b/drivers/net/dsa/lantiq_gswip.c @@ -4,7 +4,25 @@ * * Copyright (C) 2010 Lantiq Deutschland * Copyright (C) 2012 John Crispin <john@phrozen.org> - * Copyright (C) 2017 - 2018 Hauke Mehrtens <hauke@hauke-m.de> + * Copyright (C) 2017 - 2019 Hauke Mehrtens <hauke@hauke-m.de> + * + * The VLAN and bridge model the GSWIP hardware uses does not directly + * matches the model DSA uses. + * + * The hardware has 64 possible table entries for bridges with one VLAN + * ID, one flow id and a list of ports for each bridge. All entries which + * match the same flow ID are combined in the mac learning table, they + * act as one global bridge. + * The hardware does not support VLAN filter on the port, but on the + * bridge, this driver converts the DSA model to the hardware. + * + * The CPU gets all the exception frames which do not match any forwarding + * rule and the CPU port is also added to all bridges. This makes it possible + * to handle all the special cases easily in software. + * At the initialization the driver allocates one bridge table entry for + * each switch port which is used when the port is used without an + * explicit bridge. This prevents the frames from being forwarded + * between all LAN ports by default. */ #include <linux/clk.h> @@ -148,19 +166,29 @@ #define GSWIP_PCE_PMAP2 0x454 /* Default Multicast port map */ #define GSWIP_PCE_PMAP3 0x455 /* Default Unknown Unicast port map */ #define GSWIP_PCE_GCTRL_0 0x456 +#define GSWIP_PCE_GCTRL_0_MTFL BIT(0) /* MAC Table Flushing */ #define GSWIP_PCE_GCTRL_0_MC_VALID BIT(3) #define GSWIP_PCE_GCTRL_0_VLAN BIT(14) /* VLAN aware Switching */ #define GSWIP_PCE_GCTRL_1 0x457 #define GSWIP_PCE_GCTRL_1_MAC_GLOCK BIT(2) /* MAC Address table lock */ #define GSWIP_PCE_GCTRL_1_MAC_GLOCK_MOD BIT(3) /* Mac address table lock forwarding mode */ #define GSWIP_PCE_PCTRL_0p(p) (0x480 + ((p) * 0xA)) -#define GSWIP_PCE_PCTRL_0_INGRESS BIT(11) +#define GSWIP_PCE_PCTRL_0_TVM BIT(5) /* Transparent VLAN mode */ +#define GSWIP_PCE_PCTRL_0_VREP BIT(6) /* VLAN Replace Mode */ +#define GSWIP_PCE_PCTRL_0_INGRESS BIT(11) /* Accept special tag in ingress */ #define GSWIP_PCE_PCTRL_0_PSTATE_LISTEN 0x0 #define GSWIP_PCE_PCTRL_0_PSTATE_RX 0x1 #define GSWIP_PCE_PCTRL_0_PSTATE_TX 0x2 #define GSWIP_PCE_PCTRL_0_PSTATE_LEARNING 0x3 #define GSWIP_PCE_PCTRL_0_PSTATE_FORWARDING 0x7 #define GSWIP_PCE_PCTRL_0_PSTATE_MASK GENMASK(2, 0) +#define GSWIP_PCE_VCTRL(p) (0x485 + ((p) * 0xA)) +#define GSWIP_PCE_VCTRL_UVR BIT(0) /* Unknown VLAN Rule */ +#define GSWIP_PCE_VCTRL_VIMR BIT(3) /* VLAN Ingress Member violation rule */ +#define GSWIP_PCE_VCTRL_VEMR BIT(4) /* VLAN Egress Member violation rule */ +#define GSWIP_PCE_VCTRL_VSR BIT(5) /* VLAN Security */ +#define GSWIP_PCE_VCTRL_VID0 BIT(6) /* Priority Tagged Rule */ +#define GSWIP_PCE_DEFPVID(p) (0x486 + ((p) * 0xA)) #define GSWIP_MAC_FLEN 0x8C5 #define GSWIP_MAC_CTRL_2p(p) (0x905 + ((p) * 0xC)) @@ -183,6 +211,11 @@ #define GSWIP_SDMA_PCTRL_FCEN BIT(1) /* Flow Control Enable */ #define GSWIP_SDMA_PCTRL_PAUFWD BIT(1) /* Pause Frame Forwarding */ +#define GSWIP_TABLE_ACTIVE_VLAN 0x01 +#define GSWIP_TABLE_VLAN_MAPPING 0x02 +#define GSWIP_TABLE_MAC_BRIDGE 0x0b +#define GSWIP_TABLE_MAC_BRIDGE_STATIC 0x01 /* Static not, aging entry */ + #define XRX200_GPHY_FW_ALIGN (16 * 1024) struct gswip_hw_info { @@ -202,6 +235,12 @@ struct gswip_gphy_fw { char *fw_name; }; +struct gswip_vlan { + struct net_device *bridge; + u16 vid; + u8 fid; +}; + struct gswip_priv { __iomem void *gswip; __iomem void *mdio; @@ -211,8 +250,22 @@ struct gswip_priv { struct dsa_switch *ds; struct device *dev; struct regmap *rcu_regmap; + struct gswip_vlan vlans[64]; int num_gphy_fw; struct gswip_gphy_fw *gphy_fw; + u32 port_vlan_filter; +}; + +struct gswip_pce_table_entry { + u16 index; // PCE_TBL_ADDR.ADDR = pData->table_index + u16 table; // PCE_TBL_CTRL.ADDR = pData->table + u16 key[8]; + u16 val[5]; + u16 mask; + u8 gmap; + bool type; + bool valid; + bool key_mode; }; struct gswip_rmon_cnt_desc { @@ -447,10 +500,153 @@ static int gswip_mdio(struct gswip_priv *priv, struct device_node *mdio_np) return of_mdiobus_register(ds->slave_mii_bus, mdio_np); } +static int gswip_pce_table_entry_read(struct gswip_priv *priv, + struct gswip_pce_table_entry *tbl) +{ + int i; + int err; + u16 crtl; + u16 addr_mode = tbl->key_mode ? GSWIP_PCE_TBL_CTRL_OPMOD_KSRD : + GSWIP_PCE_TBL_CTRL_OPMOD_ADRD; + + err = gswip_switch_r_timeout(priv, GSWIP_PCE_TBL_CTRL, + GSWIP_PCE_TBL_CTRL_BAS); + if (err) + return err; + + gswip_switch_w(priv, tbl->index, GSWIP_PCE_TBL_ADDR); + gswip_switch_mask(priv, GSWIP_PCE_TBL_CTRL_ADDR_MASK | + GSWIP_PCE_TBL_CTRL_OPMOD_MASK, + tbl->table | addr_mode | GSWIP_PCE_TBL_CTRL_BAS, + GSWIP_PCE_TBL_CTRL); + + err = gswip_switch_r_timeout(priv, GSWIP_PCE_TBL_CTRL, + GSWIP_PCE_TBL_CTRL_BAS); + if (err) + return err; + + for (i = 0; i < ARRAY_SIZE(tbl->key); i++) + tbl->key[i] = gswip_switch_r(priv, GSWIP_PCE_TBL_KEY(i)); + + for (i = 0; i < ARRAY_SIZE(tbl->val); i++) + tbl->val[i] = gswip_switch_r(priv, GSWIP_PCE_TBL_VAL(i)); + + tbl->mask = gswip_switch_r(priv, GSWIP_PCE_TBL_MASK); + + crtl = gswip_switch_r(priv, GSWIP_PCE_TBL_CTRL); + + tbl->type = !!(crtl & GSWIP_PCE_TBL_CTRL_TYPE); + tbl->valid = !!(crtl & GSWIP_PCE_TBL_CTRL_VLD); + tbl->gmap = (crtl & GSWIP_PCE_TBL_CTRL_GMAP_MASK) >> 7; + + return 0; +} + +static int gswip_pce_table_entry_write(struct gswip_priv *priv, + struct gswip_pce_table_entry *tbl) +{ + int i; + int err; + u16 crtl; + u16 addr_mode = tbl->key_mode ? GSWIP_PCE_TBL_CTRL_OPMOD_KSWR : + GSWIP_PCE_TBL_CTRL_OPMOD_ADWR; + + err = gswip_switch_r_timeout(priv, GSWIP_PCE_TBL_CTRL, + GSWIP_PCE_TBL_CTRL_BAS); + if (err) + return err; + + gswip_switch_w(priv, tbl->index, GSWIP_PCE_TBL_ADDR); + gswip_switch_mask(priv, GSWIP_PCE_TBL_CTRL_ADDR_MASK | + GSWIP_PCE_TBL_CTRL_OPMOD_MASK, + tbl->table | addr_mode, + GSWIP_PCE_TBL_CTRL); + + for (i = 0; i < ARRAY_SIZE(tbl->key); i++) + gswip_switch_w(priv, tbl->key[i], GSWIP_PCE_TBL_KEY(i)); + + for (i = 0; i < ARRAY_SIZE(tbl->val); i++) + gswip_switch_w(priv, tbl->val[i], GSWIP_PCE_TBL_VAL(i)); + + gswip_switch_mask(priv, GSWIP_PCE_TBL_CTRL_ADDR_MASK | + GSWIP_PCE_TBL_CTRL_OPMOD_MASK, + tbl->table | addr_mode, + GSWIP_PCE_TBL_CTRL); + + gswip_switch_w(priv, tbl->mask, GSWIP_PCE_TBL_MASK); + + crtl = gswip_switch_r(priv, GSWIP_PCE_TBL_CTRL); + crtl &= ~(GSWIP_PCE_TBL_CTRL_TYPE | GSWIP_PCE_TBL_CTRL_VLD | + GSWIP_PCE_TBL_CTRL_GMAP_MASK); + if (tbl->type) + crtl |= GSWIP_PCE_TBL_CTRL_TYPE; + if (tbl->valid) + crtl |= GSWIP_PCE_TBL_CTRL_VLD; + crtl |= (tbl->gmap << 7) & GSWIP_PCE_TBL_CTRL_GMAP_MASK; + crtl |= GSWIP_PCE_TBL_CTRL_BAS; + gswip_switch_w(priv, crtl, GSWIP_PCE_TBL_CTRL); + + return gswip_switch_r_timeout(priv, GSWIP_PCE_TBL_CTRL, + GSWIP_PCE_TBL_CTRL_BAS); +} + +/* Add the LAN port into a bridge with the CPU port by + * default. This prevents automatic forwarding of + * packages between the LAN ports when no explicit + * bridge is configured. + */ +static int gswip_add_single_port_br(struct gswip_priv *priv, int port, bool add) +{ + struct gswip_pce_table_entry vlan_active = {0,}; + struct gswip_pce_table_entry vlan_mapping = {0,}; + unsigned int cpu_port = priv->hw_info->cpu_port; + unsigned int max_ports = priv->hw_info->max_ports; + int err; + + if (port >= max_ports) { + dev_err(priv->dev, "single port for %i supported\n", port); + return -EIO; + } + + vlan_active.index = port + 1; + vlan_active.table = GSWIP_TABLE_ACTIVE_VLAN; + vlan_active.key[0] = 0; /* vid */ + vlan_active.val[0] = port + 1 /* fid */; + vlan_active.valid = add; + err = gswip_pce_table_entry_write(priv, &vlan_active); + if (err) { + dev_err(priv->dev, "failed to write active VLAN: %d\n", err); + return err; + } + + if (!add) + return 0; + + vlan_mapping.index = port + 1; + vlan_mapping.table = GSWIP_TABLE_VLAN_MAPPING; + vlan_mapping.val[0] = 0 /* vid */; + vlan_mapping.val[1] = BIT(port) | BIT(cpu_port); + vlan_mapping.val[2] = 0; + err = gswip_pce_table_entry_write(priv, &vlan_mapping); + if (err) { + dev_err(priv->dev, "failed to write VLAN mapping: %d\n", err); + return err; + } + + return 0; +} + static int gswip_port_enable(struct dsa_switch *ds, int port, struct phy_device *phydev) { struct gswip_priv *priv = ds->priv; + int err; + + if (!dsa_is_cpu_port(ds, port)) { + err = gswip_add_single_port_br(priv, port, true); + if (err) + return err; + } /* RMON Counter Enable for port */ gswip_switch_w(priv, GSWIP_BM_PCFG_CNTEN, GSWIP_BM_PCFGp(port)); @@ -461,8 +657,6 @@ static int gswip_port_enable(struct dsa_switch *ds, int port, GSWIP_FDMA_PCTRLp(port)); gswip_switch_mask(priv, 0, GSWIP_SDMA_PCTRL_EN, GSWIP_SDMA_PCTRLp(port)); - gswip_switch_mask(priv, 0, GSWIP_PCE_PCTRL_0_INGRESS, - GSWIP_PCE_PCTRL_0p(port)); if (!dsa_is_cpu_port(ds, port)) { u32 macconf = GSWIP_MDIO_PHY_LINK_AUTO | @@ -535,6 +729,39 @@ static int gswip_pce_load_microcode(struct gswip_priv *priv) return 0; } +static int gswip_port_vlan_filtering(struct dsa_switch *ds, int port, + bool vlan_filtering) +{ + struct gswip_priv *priv = ds->priv; + struct net_device *bridge = dsa_to_port(ds, port)->bridge_dev; + + /* Do not allow changing the VLAN filtering options while in bridge */ + if (!!(priv->port_vlan_filter & BIT(port)) != vlan_filtering && bridge) + return -EIO; + + if (vlan_filtering) { + /* Use port based VLAN tag */ + gswip_switch_mask(priv, + GSWIP_PCE_VCTRL_VSR, + GSWIP_PCE_VCTRL_UVR | GSWIP_PCE_VCTRL_VIMR | + GSWIP_PCE_VCTRL_VEMR, + GSWIP_PCE_VCTRL(port)); + gswip_switch_mask(priv, GSWIP_PCE_PCTRL_0_TVM, 0, + GSWIP_PCE_PCTRL_0p(port)); + } else { + /* Use port based VLAN tag */ + gswip_switch_mask(priv, + GSWIP_PCE_VCTRL_UVR | GSWIP_PCE_VCTRL_VIMR | + GSWIP_PCE_VCTRL_VEMR, + GSWIP_PCE_VCTRL_VSR, + GSWIP_PCE_VCTRL(port)); + gswip_switch_mask(priv, 0, GSWIP_PCE_PCTRL_0_TVM, + GSWIP_PCE_PCTRL_0p(port)); + } + + return 0; +} + static int gswip_setup(struct dsa_switch *ds) { struct gswip_priv *priv = ds->priv; @@ -547,8 +774,10 @@ static int gswip_setup(struct dsa_switch *ds) gswip_switch_w(priv, 0, GSWIP_SWRES); /* disable port fetch/store dma on all ports */ - for (i = 0; i < priv->hw_info->max_ports; i++) + for (i = 0; i < priv->hw_info->max_ports; i++) { gswip_port_disable(ds, i); + gswip_port_vlan_filtering(ds, i, false); + } /* enable Switch */ gswip_mdio_mask(priv, 0, GSWIP_MDIO_GLOB_ENABLE, GSWIP_MDIO_GLOB); @@ -578,6 +807,10 @@ static int gswip_setup(struct dsa_switch *ds) gswip_switch_mask(priv, 0, GSWIP_FDMA_PCTRL_STEN, GSWIP_FDMA_PCTRLp(cpu_port)); + /* accept special tag in ingress direction */ + gswip_switch_mask(priv, 0, GSWIP_PCE_PCTRL_0_INGRESS, + GSWIP_PCE_PCTRL_0p(cpu_port)); + gswip_switch_mask(priv, 0, GSWIP_MAC_CTRL_2_MLEN, GSWIP_MAC_CTRL_2p(cpu_port)); gswip_switch_w(priv, VLAN_ETH_FRAME_LEN + 8, GSWIP_MAC_FLEN); @@ -587,10 +820,15 @@ static int gswip_setup(struct dsa_switch *ds) /* VLAN aware Switching */ gswip_switch_mask(priv, 0, GSWIP_PCE_GCTRL_0_VLAN, GSWIP_PCE_GCTRL_0); - /* Mac Address Table Lock */ - gswip_switch_mask(priv, 0, GSWIP_PCE_GCTRL_1_MAC_GLOCK | - GSWIP_PCE_GCTRL_1_MAC_GLOCK_MOD, - GSWIP_PCE_GCTRL_1); + /* Flush MAC Table */ + gswip_switch_mask(priv, 0, GSWIP_PCE_GCTRL_0_MTFL, GSWIP_PCE_GCTRL_0); + + err = gswip_switch_r_timeout(priv, GSWIP_PCE_GCTRL_0, + GSWIP_PCE_GCTRL_0_MTFL); + if (err) { + dev_err(priv->dev, "MAC flushing didn't finish\n"); + return err; + } gswip_port_enable(ds, cpu_port, NULL); return 0; @@ -602,6 +840,551 @@ static enum dsa_tag_protocol gswip_get_tag_protocol(struct dsa_switch *ds, return DSA_TAG_PROTO_GSWIP; } +static int gswip_vlan_active_create(struct gswip_priv *priv, + struct net_device *bridge, + int fid, u16 vid) +{ + struct gswip_pce_table_entry vlan_active = {0,}; + unsigned int max_ports = priv->hw_info->max_ports; + int idx = -1; + int err; + int i; + + /* Look for a free slot */ + for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) { + if (!priv->vlans[i].bridge) { + idx = i; + break; + } + } + + if (idx == -1) + return -ENOSPC; + + if (fid == -1) + fid = idx; + + vlan_active.index = idx; + vlan_active.table = GSWIP_TABLE_ACTIVE_VLAN; + vlan_active.key[0] = vid; + vlan_active.val[0] = fid; + vlan_active.valid = true; + + err = gswip_pce_table_entry_write(priv, &vlan_active); + if (err) { + dev_err(priv->dev, "failed to write active VLAN: %d\n", err); + return err; + } + + priv->vlans[idx].bridge = bridge; + priv->vlans[idx].vid = vid; + priv->vlans[idx].fid = fid; + + return idx; +} + +static int gswip_vlan_active_remove(struct gswip_priv *priv, int idx) +{ + struct gswip_pce_table_entry vlan_active = {0,}; + int err; + + vlan_active.index = idx; + vlan_active.table = GSWIP_TABLE_ACTIVE_VLAN; + vlan_active.valid = false; + err = gswip_pce_table_entry_write(priv, &vlan_active); + if (err) + dev_err(priv->dev, "failed to delete active VLAN: %d\n", err); + priv->vlans[idx].bridge = NULL; + + return err; +} + +static int gswip_vlan_add_unaware(struct gswip_priv *priv, + struct net_device *bridge, int port) +{ + struct gswip_pce_table_entry vlan_mapping = {0,}; + unsigned int max_ports = priv->hw_info->max_ports; + unsigned int cpu_port = priv->hw_info->cpu_port; + bool active_vlan_created = false; + int idx = -1; + int i; + int err; + + /* Check if there is already a page for this bridge */ + for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) { + if (priv->vlans[i].bridge == bridge) { + idx = i; + break; + } + } + + /* If this bridge is not programmed yet, add a Active VLAN table + * entry in a free slot and prepare the VLAN mapping table entry. + */ + if (idx == -1) { + idx = gswip_vlan_active_create(priv, bridge, -1, 0); + if (idx < 0) + return idx; + active_vlan_created = true; + + vlan_mapping.index = idx; + vlan_mapping.table = GSWIP_TABLE_VLAN_MAPPING; + /* VLAN ID byte, maps to the VLAN ID of vlan active table */ + vlan_mapping.val[0] = 0; + } else { + /* Read the existing VLAN mapping entry from the switch */ + vlan_mapping.index = idx; + vlan_mapping.table = GSWIP_TABLE_VLAN_MAPPING; + err = gswip_pce_table_entry_read(priv, &vlan_mapping); + if (err) { + dev_err(priv->dev, "failed to read VLAN mapping: %d\n", + err); + return err; + } + } + + /* Update the VLAN mapping entry and write it to the switch */ + vlan_mapping.val[1] |= BIT(cpu_port); + vlan_mapping.val[1] |= BIT(port); + err = gswip_pce_table_entry_write(priv, &vlan_mapping); + if (err) { + dev_err(priv->dev, "failed to write VLAN mapping: %d\n", err); + /* In case an Active VLAN was creaetd delete it again */ + if (active_vlan_created) + gswip_vlan_active_remove(priv, idx); + return err; + } + + gswip_switch_w(priv, 0, GSWIP_PCE_DEFPVID(port)); + return 0; +} + +static int gswip_vlan_add_aware(struct gswip_priv *priv, + struct net_device *bridge, int port, + u16 vid, bool untagged, + bool pvid) +{ + struct gswip_pce_table_entry vlan_mapping = {0,}; + unsigned int max_ports = priv->hw_info->max_ports; + unsigned int cpu_port = priv->hw_info->cpu_port; + bool active_vlan_created = false; + int idx = -1; + int fid = -1; + int i; + int err; + + /* Check if there is already a page for this bridge */ + for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) { + if (priv->vlans[i].bridge == bridge) { + if (fid != -1 && fid != priv->vlans[i].fid) + dev_err(priv->dev, "one bridge with multiple flow ids\n"); + fid = priv->vlans[i].fid; + if (priv->vlans[i].vid == vid) { + idx = i; + break; + } + } + } + + /* If this bridge is not programmed yet, add a Active VLAN table + * entry in a free slot and prepare the VLAN mapping table entry. + */ + if (idx == -1) { + idx = gswip_vlan_active_create(priv, bridge, fid, vid); + if (idx < 0) + return idx; + active_vlan_created = true; + + vlan_mapping.index = idx; + vlan_mapping.table = GSWIP_TABLE_VLAN_MAPPING; + /* VLAN ID byte, maps to the VLAN ID of vlan active table */ + vlan_mapping.val[0] = vid; + } else { + /* Read the existing VLAN mapping entry from the switch */ + vlan_mapping.index = idx; + vlan_mapping.table = GSWIP_TABLE_VLAN_MAPPING; + err = gswip_pce_table_entry_read(priv, &vlan_mapping); + if (err) { + dev_err(priv->dev, "failed to read VLAN mapping: %d\n", + err); + return err; + } + } + + vlan_mapping.val[0] = vid; + /* Update the VLAN mapping entry and write it to the switch */ + vlan_mapping.val[1] |= BIT(cpu_port); + vlan_mapping.val[2] |= BIT(cpu_port); + vlan_mapping.val[1] |= BIT(port); + if (untagged) + vlan_mapping.val[2] &= ~BIT(port); + else + vlan_mapping.val[2] |= BIT(port); + err = gswip_pce_table_entry_write(priv, &vlan_mapping); + if (err) { + dev_err(priv->dev, "failed to write VLAN mapping: %d\n", err); + /* In case an Active VLAN was creaetd delete it again */ + if (active_vlan_created) + gswip_vlan_active_remove(priv, idx); + return err; + } + + if (pvid) + gswip_switch_w(priv, idx, GSWIP_PCE_DEFPVID(port)); + + return 0; +} + +static int gswip_vlan_remove(struct gswip_priv *priv, + struct net_device *bridge, int port, + u16 vid, bool pvid, bool vlan_aware) +{ + struct gswip_pce_table_entry vlan_mapping = {0,}; + unsigned int max_ports = priv->hw_info->max_ports; + unsigned int cpu_port = priv->hw_info->cpu_port; + int idx = -1; + int i; + int err; + + /* Check if there is already a page for this bridge */ + for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) { + if (priv->vlans[i].bridge == bridge && + (!vlan_aware || priv->vlans[i].vid == vid)) { + idx = i; + break; + } + } + + if (idx == -1) { + dev_err(priv->dev, "bridge to leave does not exists\n"); + return -ENOENT; + } + + vlan_mapping.index = idx; + vlan_mapping.table = GSWIP_TABLE_VLAN_MAPPING; + err = gswip_pce_table_entry_read(priv, &vlan_mapping); + if (err) { + dev_err(priv->dev, "failed to read VLAN mapping: %d\n", err); + return err; + } + + vlan_mapping.val[1] &= ~BIT(port); + vlan_mapping.val[2] &= ~BIT(port); + err = gswip_pce_table_entry_write(priv, &vlan_mapping); + if (err) { + dev_err(priv->dev, "failed to write VLAN mapping: %d\n", err); + return err; + } + + /* In case all ports are removed from the bridge, remove the VLAN */ + if ((vlan_mapping.val[1] & ~BIT(cpu_port)) == 0) { + err = gswip_vlan_active_remove(priv, idx); + if (err) { + dev_err(priv->dev, "failed to write active VLAN: %d\n", + err); + return err; + } + } + + /* GSWIP 2.2 (GRX300) and later program here the VID directly. */ + if (pvid) + gswip_switch_w(priv, 0, GSWIP_PCE_DEFPVID(port)); + + return 0; +} + +static int gswip_port_bridge_join(struct dsa_switch *ds, int port, + struct net_device *bridge) +{ + struct gswip_priv *priv = ds->priv; + int err; + + /* When the bridge uses VLAN filtering we have to configure VLAN + * specific bridges. No bridge is configured here. + */ + if (!br_vlan_enabled(bridge)) { + err = gswip_vlan_add_unaware(priv, bridge, port); + if (err) + return err; + priv->port_vlan_filter &= ~BIT(port); + } else { + priv->port_vlan_filter |= BIT(port); + } + return gswip_add_single_port_br(priv, port, false); +} + +static void gswip_port_bridge_leave(struct dsa_switch *ds, int port, + struct net_device *bridge) +{ + struct gswip_priv *priv = ds->priv; + + gswip_add_single_port_br(priv, port, true); + + /* When the bridge uses VLAN filtering we have to configure VLAN + * specific bridges. No bridge is configured here. + */ + if (!br_vlan_enabled(bridge)) + gswip_vlan_remove(priv, bridge, port, 0, true, false); +} + +static int gswip_port_vlan_prepare(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + struct gswip_priv *priv = ds->priv; + struct net_device *bridge = dsa_to_port(ds, port)->bridge_dev; + unsigned int max_ports = priv->hw_info->max_ports; + u16 vid; + int i; + int pos = max_ports; + + /* We only support VLAN filtering on bridges */ + if (!dsa_is_cpu_port(ds, port) && !bridge) + return -EOPNOTSUPP; + + for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) { + int idx = -1; + + /* Check if there is already a page for this VLAN */ + for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) { + if (priv->vlans[i].bridge == bridge && + priv->vlans[i].vid == vid) { + idx = i; + break; + } + } + + /* If this VLAN is not programmed yet, we have to reserve + * one entry in the VLAN table. Make sure we start at the + * next position round. + */ + if (idx == -1) { + /* Look for a free slot */ + for (; pos < ARRAY_SIZE(priv->vlans); pos++) { + if (!priv->vlans[pos].bridge) { + idx = pos; + pos++; + break; + } + } + + if (idx == -1) + return -ENOSPC; + } + } + + return 0; +} + +static void gswip_port_vlan_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + struct gswip_priv *priv = ds->priv; + struct net_device *bridge = dsa_to_port(ds, port)->bridge_dev; + bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; + bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID; + u16 vid; + + /* We have to receive all packets on the CPU port and should not + * do any VLAN filtering here. This is also called with bridge + * NULL and then we do not know for which bridge to configure + * this. + */ + if (dsa_is_cpu_port(ds, port)) + return; + + for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) + gswip_vlan_add_aware(priv, bridge, port, vid, untagged, pvid); +} + +static int gswip_port_vlan_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + struct gswip_priv *priv = ds->priv; + struct net_device *bridge = dsa_to_port(ds, port)->bridge_dev; + bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID; + u16 vid; + int err; + + /* We have to receive all packets on the CPU port and should not + * do any VLAN filtering here. This is also called with bridge + * NULL and then we do not know for which bridge to configure + * this. + */ + if (dsa_is_cpu_port(ds, port)) + return 0; + + for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) { + err = gswip_vlan_remove(priv, bridge, port, vid, pvid, true); + if (err) + return err; + } + + return 0; +} + +static void gswip_port_fast_age(struct dsa_switch *ds, int port) +{ + struct gswip_priv *priv = ds->priv; + struct gswip_pce_table_entry mac_bridge = {0,}; + int i; + int err; + + for (i = 0; i < 2048; i++) { + mac_bridge.table = GSWIP_TABLE_MAC_BRIDGE; + mac_bridge.index = i; + + err = gswip_pce_table_entry_read(priv, &mac_bridge); + if (err) { + dev_err(priv->dev, "failed to read mac bridge: %d\n", + err); + return; + } + + if (!mac_bridge.valid) + continue; + + if (mac_bridge.val[1] & GSWIP_TABLE_MAC_BRIDGE_STATIC) + continue; + + if (((mac_bridge.val[0] & GENMASK(7, 4)) >> 4) != port) + continue; + + mac_bridge.valid = false; + err = gswip_pce_table_entry_write(priv, &mac_bridge); + if (err) { + dev_err(priv->dev, "failed to write mac bridge: %d\n", + err); + return; + } + } +} + +static void gswip_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) +{ + struct gswip_priv *priv = ds->priv; + u32 stp_state; + + switch (state) { + case BR_STATE_DISABLED: + gswip_switch_mask(priv, GSWIP_SDMA_PCTRL_EN, 0, + GSWIP_SDMA_PCTRLp(port)); + return; + case BR_STATE_BLOCKING: + case BR_STATE_LISTENING: + stp_state = GSWIP_PCE_PCTRL_0_PSTATE_LISTEN; + break; + case BR_STATE_LEARNING: + stp_state = GSWIP_PCE_PCTRL_0_PSTATE_LEARNING; + break; + case BR_STATE_FORWARDING: + stp_state = GSWIP_PCE_PCTRL_0_PSTATE_FORWARDING; + break; + default: + dev_err(priv->dev, "invalid STP state: %d\n", state); + return; + } + + gswip_switch_mask(priv, 0, GSWIP_SDMA_PCTRL_EN, + GSWIP_SDMA_PCTRLp(port)); + gswip_switch_mask(priv, GSWIP_PCE_PCTRL_0_PSTATE_MASK, stp_state, + GSWIP_PCE_PCTRL_0p(port)); +} + +static int gswip_port_fdb(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid, bool add) +{ + struct gswip_priv *priv = ds->priv; + struct net_device *bridge = dsa_to_port(ds, port)->bridge_dev; + struct gswip_pce_table_entry mac_bridge = {0,}; + unsigned int cpu_port = priv->hw_info->cpu_port; + int fid = -1; + int i; + int err; + + if (!bridge) + return -EINVAL; + + for (i = cpu_port; i < ARRAY_SIZE(priv->vlans); i++) { + if (priv->vlans[i].bridge == bridge) { + fid = priv->vlans[i].fid; + break; + } + } + + if (fid == -1) { + dev_err(priv->dev, "Port not part of a bridge\n"); + return -EINVAL; + } + + mac_bridge.table = GSWIP_TABLE_MAC_BRIDGE; + mac_bridge.key_mode = true; + mac_bridge.key[0] = addr[5] | (addr[4] << 8); + mac_bridge.key[1] = addr[3] | (addr[2] << 8); + mac_bridge.key[2] = addr[1] | (addr[0] << 8); + mac_bridge.key[3] = fid; + mac_bridge.val[0] = add ? BIT(port) : 0; /* port map */ + mac_bridge.val[1] = GSWIP_TABLE_MAC_BRIDGE_STATIC; + mac_bridge.valid = add; + + err = gswip_pce_table_entry_write(priv, &mac_bridge); + if (err) + dev_err(priv->dev, "failed to write mac bridge: %d\n", err); + + return err; +} + +static int gswip_port_fdb_add(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid) +{ + return gswip_port_fdb(ds, port, addr, vid, true); +} + +static int gswip_port_fdb_del(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid) +{ + return gswip_port_fdb(ds, port, addr, vid, false); +} + +static int gswip_port_fdb_dump(struct dsa_switch *ds, int port, + dsa_fdb_dump_cb_t *cb, void *data) +{ + struct gswip_priv *priv = ds->priv; + struct gswip_pce_table_entry mac_bridge = {0,}; + unsigned char addr[6]; + int i; + int err; + + for (i = 0; i < 2048; i++) { + mac_bridge.table = GSWIP_TABLE_MAC_BRIDGE; + mac_bridge.index = i; + + err = gswip_pce_table_entry_read(priv, &mac_bridge); + if (err) { + dev_err(priv->dev, "failed to write mac bridge: %d\n", + err); + return err; + } + + if (!mac_bridge.valid) + continue; + + addr[5] = mac_bridge.key[0] & 0xff; + addr[4] = (mac_bridge.key[0] >> 8) & 0xff; + addr[3] = mac_bridge.key[1] & 0xff; + addr[2] = (mac_bridge.key[1] >> 8) & 0xff; + addr[1] = mac_bridge.key[2] & 0xff; + addr[0] = (mac_bridge.key[2] >> 8) & 0xff; + if (mac_bridge.val[1] & GSWIP_TABLE_MAC_BRIDGE_STATIC) { + if (mac_bridge.val[0] & BIT(port)) + cb(addr, 0, true, data); + } else { + if (((mac_bridge.val[0] & GENMASK(7, 4)) >> 4) == port) + cb(addr, 0, false, data); + } + } + return 0; +} + static void gswip_phylink_validate(struct dsa_switch *ds, int port, unsigned long *supported, struct phylink_link_state *state) @@ -809,6 +1592,17 @@ static const struct dsa_switch_ops gswip_switch_ops = { .setup = gswip_setup, .port_enable = gswip_port_enable, .port_disable = gswip_port_disable, + .port_bridge_join = gswip_port_bridge_join, + .port_bridge_leave = gswip_port_bridge_leave, + .port_fast_age = gswip_port_fast_age, + .port_vlan_filtering = gswip_port_vlan_filtering, + .port_vlan_prepare = gswip_port_vlan_prepare, + .port_vlan_add = gswip_port_vlan_add, + .port_vlan_del = gswip_port_vlan_del, + .port_stp_state_set = gswip_port_stp_state_set, + .port_fdb_add = gswip_port_fdb_add, + .port_fdb_del = gswip_port_fdb_del, + .port_fdb_dump = gswip_port_fdb_dump, .phylink_validate = gswip_phylink_validate, .phylink_mac_config = gswip_phylink_mac_config, .phylink_mac_link_down = gswip_phylink_mac_link_down, diff --git a/drivers/net/dsa/microchip/ksz9477.c b/drivers/net/dsa/microchip/ksz9477.c index f16e1d7d8615..c026d15721f6 100644 --- a/drivers/net/dsa/microchip/ksz9477.c +++ b/drivers/net/dsa/microchip/ksz9477.c @@ -1144,6 +1144,7 @@ static phy_interface_t ksz9477_get_interface(struct ksz_device *dev, int port) interface = PHY_INTERFACE_MODE_GMII; if (gbit) break; + /* fall through */ case 0: interface = PHY_INTERFACE_MODE_MII; break; diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c index 7357b4fc0185..8d531c5f21f3 100644 --- a/drivers/net/dsa/mt7530.c +++ b/drivers/net/dsa/mt7530.c @@ -828,11 +828,9 @@ mt7530_port_set_vlan_unaware(struct dsa_switch *ds, int port) 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) { + dsa_port_is_vlan_filtering(&ds->ports[i])) { all_user_ports_removed = false; break; } @@ -891,8 +889,8 @@ mt7530_port_bridge_leave(struct dsa_switch *ds, int port, * And the other port's port matrix cannot be broken when the * other port is still a VLAN-aware port. */ - if (!priv->ports[i].vlan_filtering && - dsa_is_user_port(ds, i) && i != port) { + if (dsa_is_user_port(ds, i) && i != port && + !dsa_port_is_vlan_filtering(&ds->ports[i])) { if (dsa_to_port(ds, i)->bridge_dev != bridge) continue; if (priv->ports[i].enable) @@ -910,8 +908,6 @@ 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); } @@ -1013,10 +1009,6 @@ 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 @@ -1025,6 +1017,8 @@ mt7530_port_vlan_filtering(struct dsa_switch *ds, int port, */ mt7530_port_set_vlan_aware(ds, port); mt7530_port_set_vlan_aware(ds, MT7530_CPU_PORT); + } else { + mt7530_port_set_vlan_unaware(ds, port); } return 0; @@ -1139,7 +1133,7 @@ mt7530_port_vlan_add(struct dsa_switch *ds, int port, /* The port is kept as VLAN-unaware if bridge with vlan_filtering not * being set. */ - if (!priv->ports[port].vlan_filtering) + if (!dsa_port_is_vlan_filtering(&ds->ports[port])) return; mutex_lock(&priv->reg_mutex); @@ -1170,7 +1164,7 @@ mt7530_port_vlan_del(struct dsa_switch *ds, int port, /* The port is kept as VLAN-unaware if bridge with vlan_filtering not * being set. */ - if (!priv->ports[port].vlan_filtering) + if (!dsa_port_is_vlan_filtering(&ds->ports[port])) return 0; mutex_lock(&priv->reg_mutex); diff --git a/drivers/net/dsa/mt7530.h b/drivers/net/dsa/mt7530.h index a95ed958df5b..1eec7bdc283a 100644 --- a/drivers/net/dsa/mt7530.h +++ b/drivers/net/dsa/mt7530.h @@ -410,7 +410,6 @@ struct mt7530_port { bool enable; u32 pm; u16 pvid; - bool vlan_filtering; }; /* struct mt7530_priv - This is the main data structure for holding the state diff --git a/drivers/net/dsa/mv88e6060.c b/drivers/net/dsa/mv88e6060.c index 0b3e51f248c2..2a2489b5196d 100644 --- a/drivers/net/dsa/mv88e6060.c +++ b/drivers/net/dsa/mv88e6060.c @@ -1,11 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * net/dsa/mv88e6060.c - Driver for Marvell 88e6060 switch chips * Copyright (c) 2008-2009 Marvell Semiconductor - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #include <linux/delay.h> @@ -18,40 +14,16 @@ #include <net/dsa.h> #include "mv88e6060.h" -static int reg_read(struct dsa_switch *ds, int addr, int reg) +static int reg_read(struct mv88e6060_priv *priv, int addr, int reg) { - struct mv88e6060_priv *priv = ds->priv; - return mdiobus_read_nested(priv->bus, priv->sw_addr + addr, reg); } -#define REG_READ(addr, reg) \ - ({ \ - int __ret; \ - \ - __ret = reg_read(ds, addr, reg); \ - if (__ret < 0) \ - return __ret; \ - __ret; \ - }) - - -static int reg_write(struct dsa_switch *ds, int addr, int reg, u16 val) +static int reg_write(struct mv88e6060_priv *priv, int addr, int reg, u16 val) { - struct mv88e6060_priv *priv = ds->priv; - return mdiobus_write_nested(priv->bus, priv->sw_addr + addr, reg, val); } -#define REG_WRITE(addr, reg, val) \ - ({ \ - int __ret; \ - \ - __ret = reg_write(ds, addr, reg, val); \ - if (__ret < 0) \ - return __ret; \ - }) - static const char *mv88e6060_get_name(struct mii_bus *bus, int sw_addr) { int ret; @@ -76,28 +48,7 @@ static enum dsa_tag_protocol mv88e6060_get_tag_protocol(struct dsa_switch *ds, return DSA_TAG_PROTO_TRAILER; } -static const char *mv88e6060_drv_probe(struct device *dsa_dev, - struct device *host_dev, int sw_addr, - void **_priv) -{ - struct mii_bus *bus = dsa_host_dev_to_mii_bus(host_dev); - struct mv88e6060_priv *priv; - const char *name; - - name = mv88e6060_get_name(bus, sw_addr); - if (name) { - priv = devm_kzalloc(dsa_dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return NULL; - *_priv = priv; - priv->bus = bus; - priv->sw_addr = sw_addr; - } - - return name; -} - -static int mv88e6060_switch_reset(struct dsa_switch *ds) +static int mv88e6060_switch_reset(struct mv88e6060_priv *priv) { int i; int ret; @@ -105,23 +56,32 @@ static int mv88e6060_switch_reset(struct dsa_switch *ds) /* Set all ports to the disabled state. */ for (i = 0; i < MV88E6060_PORTS; i++) { - ret = REG_READ(REG_PORT(i), PORT_CONTROL); - REG_WRITE(REG_PORT(i), PORT_CONTROL, - ret & ~PORT_CONTROL_STATE_MASK); + ret = reg_read(priv, REG_PORT(i), PORT_CONTROL); + if (ret < 0) + return ret; + ret = reg_write(priv, REG_PORT(i), PORT_CONTROL, + ret & ~PORT_CONTROL_STATE_MASK); + if (ret) + return ret; } /* Wait for transmit queues to drain. */ usleep_range(2000, 4000); /* Reset the switch. */ - REG_WRITE(REG_GLOBAL, GLOBAL_ATU_CONTROL, - GLOBAL_ATU_CONTROL_SWRESET | - GLOBAL_ATU_CONTROL_LEARNDIS); + ret = reg_write(priv, REG_GLOBAL, GLOBAL_ATU_CONTROL, + GLOBAL_ATU_CONTROL_SWRESET | + GLOBAL_ATU_CONTROL_LEARNDIS); + if (ret) + return ret; /* Wait up to one second for reset to complete. */ timeout = jiffies + 1 * HZ; while (time_before(jiffies, timeout)) { - ret = REG_READ(REG_GLOBAL, GLOBAL_STATUS); + ret = reg_read(priv, REG_GLOBAL, GLOBAL_STATUS); + if (ret < 0) + return ret; + if (ret & GLOBAL_STATUS_INIT_READY) break; @@ -133,61 +93,69 @@ static int mv88e6060_switch_reset(struct dsa_switch *ds) return 0; } -static int mv88e6060_setup_global(struct dsa_switch *ds) +static int mv88e6060_setup_global(struct mv88e6060_priv *priv) { + int ret; + /* Disable discarding of frames with excessive collisions, * set the maximum frame size to 1536 bytes, and mask all * interrupt sources. */ - REG_WRITE(REG_GLOBAL, GLOBAL_CONTROL, GLOBAL_CONTROL_MAX_FRAME_1536); + ret = reg_write(priv, REG_GLOBAL, GLOBAL_CONTROL, + GLOBAL_CONTROL_MAX_FRAME_1536); + if (ret) + return ret; /* Disable automatic address learning. */ - REG_WRITE(REG_GLOBAL, GLOBAL_ATU_CONTROL, - GLOBAL_ATU_CONTROL_LEARNDIS); - - return 0; + return reg_write(priv, REG_GLOBAL, GLOBAL_ATU_CONTROL, + GLOBAL_ATU_CONTROL_LEARNDIS); } -static int mv88e6060_setup_port(struct dsa_switch *ds, int p) +static int mv88e6060_setup_port(struct mv88e6060_priv *priv, int p) { int addr = REG_PORT(p); + int ret; /* Do not force flow control, disable Ingress and Egress * Header tagging, disable VLAN tunneling, and set the port * state to Forwarding. Additionally, if this is the CPU * port, enable Ingress and Egress Trailer tagging mode. */ - REG_WRITE(addr, PORT_CONTROL, - dsa_is_cpu_port(ds, p) ? + ret = reg_write(priv, addr, PORT_CONTROL, + dsa_is_cpu_port(priv->ds, p) ? PORT_CONTROL_TRAILER | PORT_CONTROL_INGRESS_MODE | PORT_CONTROL_STATE_FORWARDING : PORT_CONTROL_STATE_FORWARDING); + if (ret) + return ret; /* Port based VLAN map: give each port its own address * database, allow the CPU port to talk to each of the 'real' * ports, and allow each of the 'real' ports to only talk to * the CPU port. */ - REG_WRITE(addr, PORT_VLAN_MAP, - ((p & 0xf) << PORT_VLAN_MAP_DBNUM_SHIFT) | - (dsa_is_cpu_port(ds, p) ? dsa_user_ports(ds) : - BIT(dsa_to_port(ds, p)->cpu_dp->index))); + ret = reg_write(priv, addr, PORT_VLAN_MAP, + ((p & 0xf) << PORT_VLAN_MAP_DBNUM_SHIFT) | + (dsa_is_cpu_port(priv->ds, p) ? + dsa_user_ports(priv->ds) : + BIT(dsa_to_port(priv->ds, p)->cpu_dp->index))); + if (ret) + return ret; /* Port Association Vector: when learning source addresses * of packets, add the address to the address database using * a port bitmap that has only the bit for this port set and * the other bits clear. */ - REG_WRITE(addr, PORT_ASSOC_VECTOR, BIT(p)); - - return 0; + return reg_write(priv, addr, PORT_ASSOC_VECTOR, BIT(p)); } -static int mv88e6060_setup_addr(struct dsa_switch *ds) +static int mv88e6060_setup_addr(struct mv88e6060_priv *priv) { u8 addr[ETH_ALEN]; + int ret; u16 val; eth_random_addr(addr); @@ -199,34 +167,43 @@ static int mv88e6060_setup_addr(struct dsa_switch *ds) */ val &= 0xfeff; - REG_WRITE(REG_GLOBAL, GLOBAL_MAC_01, val); - REG_WRITE(REG_GLOBAL, GLOBAL_MAC_23, (addr[2] << 8) | addr[3]); - REG_WRITE(REG_GLOBAL, GLOBAL_MAC_45, (addr[4] << 8) | addr[5]); + ret = reg_write(priv, REG_GLOBAL, GLOBAL_MAC_01, val); + if (ret) + return ret; + + ret = reg_write(priv, REG_GLOBAL, GLOBAL_MAC_23, + (addr[2] << 8) | addr[3]); + if (ret) + return ret; - return 0; + return reg_write(priv, REG_GLOBAL, GLOBAL_MAC_45, + (addr[4] << 8) | addr[5]); } static int mv88e6060_setup(struct dsa_switch *ds) { + struct mv88e6060_priv *priv = ds->priv; int ret; int i; - ret = mv88e6060_switch_reset(ds); + priv->ds = ds; + + ret = mv88e6060_switch_reset(priv); if (ret < 0) return ret; /* @@@ initialise atu */ - ret = mv88e6060_setup_global(ds); + ret = mv88e6060_setup_global(priv); if (ret < 0) return ret; - ret = mv88e6060_setup_addr(ds); + ret = mv88e6060_setup_addr(priv); if (ret < 0) return ret; for (i = 0; i < MV88E6060_PORTS; i++) { - ret = mv88e6060_setup_port(ds, i); + ret = mv88e6060_setup_port(priv, i); if (ret < 0) return ret; } @@ -243,51 +220,93 @@ static int mv88e6060_port_to_phy_addr(int port) static int mv88e6060_phy_read(struct dsa_switch *ds, int port, int regnum) { + struct mv88e6060_priv *priv = ds->priv; int addr; addr = mv88e6060_port_to_phy_addr(port); if (addr == -1) return 0xffff; - return reg_read(ds, addr, regnum); + return reg_read(priv, addr, regnum); } static int mv88e6060_phy_write(struct dsa_switch *ds, int port, int regnum, u16 val) { + struct mv88e6060_priv *priv = ds->priv; int addr; addr = mv88e6060_port_to_phy_addr(port); if (addr == -1) return 0xffff; - return reg_write(ds, addr, regnum, val); + return reg_write(priv, addr, regnum, val); } static const struct dsa_switch_ops mv88e6060_switch_ops = { .get_tag_protocol = mv88e6060_get_tag_protocol, - .probe = mv88e6060_drv_probe, .setup = mv88e6060_setup, .phy_read = mv88e6060_phy_read, .phy_write = mv88e6060_phy_write, }; -static struct dsa_switch_driver mv88e6060_switch_drv = { - .ops = &mv88e6060_switch_ops, -}; - -static int __init mv88e6060_init(void) +static int mv88e6060_probe(struct mdio_device *mdiodev) { - register_switch_driver(&mv88e6060_switch_drv); - return 0; + struct device *dev = &mdiodev->dev; + struct mv88e6060_priv *priv; + struct dsa_switch *ds; + const char *name; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->bus = mdiodev->bus; + priv->sw_addr = mdiodev->addr; + + name = mv88e6060_get_name(priv->bus, priv->sw_addr); + if (!name) + return -ENODEV; + + dev_info(dev, "switch %s detected\n", name); + + ds = dsa_switch_alloc(dev, MV88E6060_PORTS); + if (!ds) + return -ENOMEM; + + ds->priv = priv; + ds->dev = dev; + ds->ops = &mv88e6060_switch_ops; + + dev_set_drvdata(dev, ds); + + return dsa_register_switch(ds); } -module_init(mv88e6060_init); -static void __exit mv88e6060_cleanup(void) +static void mv88e6060_remove(struct mdio_device *mdiodev) { - unregister_switch_driver(&mv88e6060_switch_drv); + struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev); + + dsa_unregister_switch(ds); } -module_exit(mv88e6060_cleanup); + +static const struct of_device_id mv88e6060_of_match[] = { + { + .compatible = "marvell,mv88e6060", + }, + { /* sentinel */ }, +}; + +static struct mdio_driver mv88e6060_driver = { + .probe = mv88e6060_probe, + .remove = mv88e6060_remove, + .mdiodrv.driver = { + .name = "mv88e6060", + .of_match_table = mv88e6060_of_match, + }, +}; + +mdio_module_driver(mv88e6060_driver); MODULE_AUTHOR("Lennert Buytenhek <buytenh@wantstofly.org>"); MODULE_DESCRIPTION("Driver for Marvell 88E6060 ethernet switch chip"); diff --git a/drivers/net/dsa/mv88e6060.h b/drivers/net/dsa/mv88e6060.h index 10249bd16292..c0e7a0f2fb6a 100644 --- a/drivers/net/dsa/mv88e6060.h +++ b/drivers/net/dsa/mv88e6060.h @@ -117,6 +117,7 @@ struct mv88e6060_priv { */ struct mii_bus *bus; int sw_addr; + struct dsa_switch *ds; }; #endif diff --git a/drivers/net/dsa/mv88e6xxx/Makefile b/drivers/net/dsa/mv88e6xxx/Makefile index 50de304abe2f..e85755dde90b 100644 --- a/drivers/net/dsa/mv88e6xxx/Makefile +++ b/drivers/net/dsa/mv88e6xxx/Makefile @@ -12,3 +12,4 @@ mv88e6xxx-objs += phy.o mv88e6xxx-objs += port.o mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_PTP) += ptp.o mv88e6xxx-objs += serdes.o +mv88e6xxx-objs += smi.o diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c index f4e2db44ad91..28414db979b0 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c @@ -43,6 +43,7 @@ #include "port.h" #include "ptp.h" #include "serdes.h" +#include "smi.h" static void assert_reg_lock(struct mv88e6xxx_chip *chip) { @@ -52,149 +53,6 @@ static void assert_reg_lock(struct mv88e6xxx_chip *chip) } } -/* The switch ADDR[4:1] configuration pins define the chip SMI device address - * (ADDR[0] is always zero, thus only even SMI addresses can be strapped). - * - * When ADDR is all zero, the chip uses Single-chip Addressing Mode, assuming it - * is the only device connected to the SMI master. In this mode it responds to - * all 32 possible SMI addresses, and thus maps directly the internal devices. - * - * When ADDR is non-zero, the chip uses Multi-chip Addressing Mode, allowing - * multiple devices to share the SMI interface. In this mode it responds to only - * 2 registers, used to indirectly access the internal SMI devices. - */ - -static int mv88e6xxx_smi_read(struct mv88e6xxx_chip *chip, - int addr, int reg, u16 *val) -{ - if (!chip->smi_ops) - return -EOPNOTSUPP; - - return chip->smi_ops->read(chip, addr, reg, val); -} - -static int mv88e6xxx_smi_write(struct mv88e6xxx_chip *chip, - int addr, int reg, u16 val) -{ - if (!chip->smi_ops) - return -EOPNOTSUPP; - - return chip->smi_ops->write(chip, addr, reg, val); -} - -static int mv88e6xxx_smi_single_chip_read(struct mv88e6xxx_chip *chip, - int addr, int reg, u16 *val) -{ - int ret; - - ret = mdiobus_read_nested(chip->bus, addr, reg); - if (ret < 0) - return ret; - - *val = ret & 0xffff; - - return 0; -} - -static int mv88e6xxx_smi_single_chip_write(struct mv88e6xxx_chip *chip, - int addr, int reg, u16 val) -{ - int ret; - - ret = mdiobus_write_nested(chip->bus, addr, reg, val); - if (ret < 0) - return ret; - - return 0; -} - -static const struct mv88e6xxx_bus_ops mv88e6xxx_smi_single_chip_ops = { - .read = mv88e6xxx_smi_single_chip_read, - .write = mv88e6xxx_smi_single_chip_write, -}; - -static int mv88e6xxx_smi_multi_chip_wait(struct mv88e6xxx_chip *chip) -{ - int ret; - int i; - - for (i = 0; i < 16; i++) { - ret = mdiobus_read_nested(chip->bus, chip->sw_addr, SMI_CMD); - if (ret < 0) - return ret; - - if ((ret & SMI_CMD_BUSY) == 0) - return 0; - } - - return -ETIMEDOUT; -} - -static int mv88e6xxx_smi_multi_chip_read(struct mv88e6xxx_chip *chip, - int addr, int reg, u16 *val) -{ - int ret; - - /* Wait for the bus to become free. */ - ret = mv88e6xxx_smi_multi_chip_wait(chip); - if (ret < 0) - return ret; - - /* Transmit the read command. */ - ret = mdiobus_write_nested(chip->bus, chip->sw_addr, SMI_CMD, - SMI_CMD_OP_22_READ | (addr << 5) | reg); - if (ret < 0) - return ret; - - /* Wait for the read command to complete. */ - ret = mv88e6xxx_smi_multi_chip_wait(chip); - if (ret < 0) - return ret; - - /* Read the data. */ - ret = mdiobus_read_nested(chip->bus, chip->sw_addr, SMI_DATA); - if (ret < 0) - return ret; - - *val = ret & 0xffff; - - return 0; -} - -static int mv88e6xxx_smi_multi_chip_write(struct mv88e6xxx_chip *chip, - int addr, int reg, u16 val) -{ - int ret; - - /* Wait for the bus to become free. */ - ret = mv88e6xxx_smi_multi_chip_wait(chip); - if (ret < 0) - return ret; - - /* Transmit the data to write. */ - ret = mdiobus_write_nested(chip->bus, chip->sw_addr, SMI_DATA, val); - if (ret < 0) - return ret; - - /* Transmit the write command. */ - ret = mdiobus_write_nested(chip->bus, chip->sw_addr, SMI_CMD, - SMI_CMD_OP_22_WRITE | (addr << 5) | reg); - if (ret < 0) - return ret; - - /* Wait for the write command to complete. */ - ret = mv88e6xxx_smi_multi_chip_wait(chip); - if (ret < 0) - return ret; - - return 0; -} - -static const struct mv88e6xxx_bus_ops mv88e6xxx_smi_multi_chip_ops = { - .read = mv88e6xxx_smi_multi_chip_read, - .write = mv88e6xxx_smi_multi_chip_write, -}; - int mv88e6xxx_read(struct mv88e6xxx_chip *chip, int addr, int reg, u16 *val) { int err; @@ -553,11 +411,28 @@ int mv88e6xxx_port_setup_mac(struct mv88e6xxx_chip *chip, int port, int link, int speed, int duplex, int pause, phy_interface_t mode) { + struct phylink_link_state state; int err; if (!chip->info->ops->port_set_link) return 0; + if (!chip->info->ops->port_link_state) + return 0; + + err = chip->info->ops->port_link_state(chip, port, &state); + if (err) + return err; + + /* Has anything actually changed? We don't expect the + * interface mode to change without one of the other + * parameters also changing + */ + if (state.link == link && + state.speed == speed && + state.duplex == duplex) + return 0; + /* Port's MAC control must not be changed unless the link is down */ err = chip->info->ops->port_set_link(chip, port, 0); if (err) @@ -2411,6 +2286,9 @@ static void mv88e6xxx_port_disable(struct dsa_switch *ds, int port) mutex_lock(&chip->reg_lock); + if (mv88e6xxx_port_set_state(chip, port, BR_STATE_DISABLED)) + dev_err(chip->dev, "failed to disable port\n"); + if (chip->info->ops->serdes_irq_free) chip->info->ops->serdes_irq_free(chip, port); @@ -2579,8 +2457,18 @@ static int mv88e6xxx_setup(struct dsa_switch *ds) /* Setup Switch Port Registers */ for (i = 0; i < mv88e6xxx_num_ports(chip); i++) { - if (dsa_is_unused_port(ds, i)) + if (dsa_is_unused_port(ds, i)) { + err = mv88e6xxx_port_set_state(chip, i, + BR_STATE_DISABLED); + if (err) + goto unlock; + + err = mv88e6xxx_serdes_power(chip, i, false); + if (err) + goto unlock; + continue; + } err = mv88e6xxx_setup_port(chip, i); if (err) @@ -4615,30 +4503,6 @@ static struct mv88e6xxx_chip *mv88e6xxx_alloc_chip(struct device *dev) return chip; } -static int mv88e6xxx_smi_init(struct mv88e6xxx_chip *chip, - struct mii_bus *bus, int sw_addr) -{ - if (sw_addr == 0) - chip->smi_ops = &mv88e6xxx_smi_single_chip_ops; - else if (chip->info->multi_chip) - chip->smi_ops = &mv88e6xxx_smi_multi_chip_ops; - else - return -EINVAL; - - chip->bus = bus; - chip->sw_addr = sw_addr; - - return 0; -} - -static void mv88e6xxx_ports_cmode_init(struct mv88e6xxx_chip *chip) -{ - int i; - - for (i = 0; i < mv88e6xxx_num_ports(chip); i++) - chip->ports[i].cmode = MV88E6XXX_PORT_STS_CMODE_INVALID; -} - static enum dsa_tag_protocol mv88e6xxx_get_tag_protocol(struct dsa_switch *ds, int port) { @@ -4647,58 +4511,6 @@ static enum dsa_tag_protocol mv88e6xxx_get_tag_protocol(struct dsa_switch *ds, return chip->info->tag_protocol; } -#if IS_ENABLED(CONFIG_NET_DSA_LEGACY) -static const char *mv88e6xxx_drv_probe(struct device *dsa_dev, - struct device *host_dev, int sw_addr, - void **priv) -{ - struct mv88e6xxx_chip *chip; - struct mii_bus *bus; - int err; - - bus = dsa_host_dev_to_mii_bus(host_dev); - if (!bus) - return NULL; - - chip = mv88e6xxx_alloc_chip(dsa_dev); - if (!chip) - return NULL; - - /* Legacy SMI probing will only support chips similar to 88E6085 */ - chip->info = &mv88e6xxx_table[MV88E6085]; - - err = mv88e6xxx_smi_init(chip, bus, sw_addr); - if (err) - goto free; - - err = mv88e6xxx_detect(chip); - if (err) - goto free; - - mv88e6xxx_ports_cmode_init(chip); - - mutex_lock(&chip->reg_lock); - err = mv88e6xxx_switch_reset(chip); - mutex_unlock(&chip->reg_lock); - if (err) - goto free; - - mv88e6xxx_phy_init(chip); - - err = mv88e6xxx_mdios_register(chip, NULL); - if (err) - goto free; - - *priv = chip; - - return chip->info->name; -free: - devm_kfree(dsa_dev, chip); - - return NULL; -} -#endif - static int mv88e6xxx_port_mdb_prepare(struct dsa_switch *ds, int port, const struct switchdev_obj_port_mdb *mdb) { @@ -4753,9 +4565,6 @@ static int mv88e6xxx_port_egress_floods(struct dsa_switch *ds, int port, } static const struct dsa_switch_ops mv88e6xxx_switch_ops = { -#if IS_ENABLED(CONFIG_NET_DSA_LEGACY) - .probe = mv88e6xxx_drv_probe, -#endif .get_tag_protocol = mv88e6xxx_get_tag_protocol, .setup = mv88e6xxx_setup, .adjust_link = mv88e6xxx_adjust_link, @@ -4801,10 +4610,6 @@ static const struct dsa_switch_ops mv88e6xxx_switch_ops = { .get_ts_info = mv88e6xxx_get_ts_info, }; -static struct dsa_switch_driver mv88e6xxx_switch_drv = { - .ops = &mv88e6xxx_switch_ops, -}; - static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip) { struct device *dev = chip->dev; @@ -4915,7 +4720,6 @@ static int mv88e6xxx_probe(struct mdio_device *mdiodev) if (err) goto out; - mv88e6xxx_ports_cmode_init(chip); mv88e6xxx_phy_init(chip); if (chip->info->ops->get_eeprom) { @@ -4932,12 +4736,17 @@ static int mv88e6xxx_probe(struct mdio_device *mdiodev) if (err) goto out; - chip->irq = of_irq_get(np, 0); - if (chip->irq == -EPROBE_DEFER) { - err = chip->irq; - goto out; + if (np) { + chip->irq = of_irq_get(np, 0); + if (chip->irq == -EPROBE_DEFER) { + err = chip->irq; + goto out; + } } + if (pdata) + chip->irq = pdata->irq; + /* Has to be performed before the MDIO bus is created, because * the PHYs will link their interrupts to these interrupt * controllers @@ -5047,19 +4856,7 @@ static struct mdio_driver mv88e6xxx_driver = { }, }; -static int __init mv88e6xxx_init(void) -{ - register_switch_driver(&mv88e6xxx_switch_drv); - return mdio_driver_register(&mv88e6xxx_driver); -} -module_init(mv88e6xxx_init); - -static void __exit mv88e6xxx_cleanup(void) -{ - mdio_driver_unregister(&mv88e6xxx_driver); - unregister_switch_driver(&mv88e6xxx_switch_drv); -} -module_exit(mv88e6xxx_cleanup); +mdio_module_driver(mv88e6xxx_driver); MODULE_AUTHOR("Lennert Buytenhek <buytenh@wantstofly.org>"); MODULE_DESCRIPTION("Driver for Marvell 88E6XXX ethernet switch chips"); diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h index 19c07dff0440..faa3fa889f19 100644 --- a/drivers/net/dsa/mv88e6xxx/chip.h +++ b/drivers/net/dsa/mv88e6xxx/chip.h @@ -21,17 +21,6 @@ #include <linux/timecounter.h> #include <net/dsa.h> -#define SMI_CMD 0x00 -#define SMI_CMD_BUSY BIT(15) -#define SMI_CMD_CLAUSE_22 BIT(12) -#define SMI_CMD_OP_22_WRITE ((1 << 10) | SMI_CMD_BUSY | SMI_CMD_CLAUSE_22) -#define SMI_CMD_OP_22_READ ((2 << 10) | SMI_CMD_BUSY | SMI_CMD_CLAUSE_22) -#define SMI_CMD_OP_45_WRITE_ADDR ((0 << 10) | SMI_CMD_BUSY) -#define SMI_CMD_OP_45_WRITE_DATA ((1 << 10) | SMI_CMD_BUSY) -#define SMI_CMD_OP_45_READ_DATA ((2 << 10) | SMI_CMD_BUSY) -#define SMI_CMD_OP_45_READ_DATA_INC ((3 << 10) | SMI_CMD_BUSY) -#define SMI_DATA 0x01 - #define MV88E6XXX_N_FID 4096 /* PVT limits for 4-bit port and 5-bit switch */ diff --git a/drivers/net/dsa/mv88e6xxx/port.h b/drivers/net/dsa/mv88e6xxx/port.h index c7bed263a0f4..39c85e98fb92 100644 --- a/drivers/net/dsa/mv88e6xxx/port.h +++ b/drivers/net/dsa/mv88e6xxx/port.h @@ -52,7 +52,6 @@ #define MV88E6185_PORT_STS_CMODE_1000BASE_X 0x0005 #define MV88E6185_PORT_STS_CMODE_PHY 0x0006 #define MV88E6185_PORT_STS_CMODE_DISABLED 0x0007 -#define MV88E6XXX_PORT_STS_CMODE_INVALID 0xff /* Offset 0x01: MAC (or PCS or Physical) Control Register */ #define MV88E6XXX_PORT_MAC_CTL 0x01 diff --git a/drivers/net/dsa/mv88e6xxx/smi.c b/drivers/net/dsa/mv88e6xxx/smi.c new file mode 100644 index 000000000000..96f7d2685bdc --- /dev/null +++ b/drivers/net/dsa/mv88e6xxx/smi.c @@ -0,0 +1,158 @@ +/* + * Marvell 88E6xxx System Management Interface (SMI) support + * + * Copyright (c) 2008 Marvell Semiconductor + * + * Copyright (c) 2019 Vivien Didelot <vivien.didelot@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include "chip.h" +#include "smi.h" + +/* The switch ADDR[4:1] configuration pins define the chip SMI device address + * (ADDR[0] is always zero, thus only even SMI addresses can be strapped). + * + * When ADDR is all zero, the chip uses Single-chip Addressing Mode, assuming it + * is the only device connected to the SMI master. In this mode it responds to + * all 32 possible SMI addresses, and thus maps directly the internal devices. + * + * When ADDR is non-zero, the chip uses Multi-chip Addressing Mode, allowing + * multiple devices to share the SMI interface. In this mode it responds to only + * 2 registers, used to indirectly access the internal SMI devices. + */ + +static int mv88e6xxx_smi_direct_read(struct mv88e6xxx_chip *chip, + int dev, int reg, u16 *data) +{ + int ret; + + ret = mdiobus_read_nested(chip->bus, dev, reg); + if (ret < 0) + return ret; + + *data = ret & 0xffff; + + return 0; +} + +static int mv88e6xxx_smi_direct_write(struct mv88e6xxx_chip *chip, + int dev, int reg, u16 data) +{ + int ret; + + ret = mdiobus_write_nested(chip->bus, dev, reg, data); + if (ret < 0) + return ret; + + return 0; +} + +static int mv88e6xxx_smi_direct_wait(struct mv88e6xxx_chip *chip, + int dev, int reg, int bit, int val) +{ + u16 data; + int err; + int i; + + for (i = 0; i < 16; i++) { + err = mv88e6xxx_smi_direct_read(chip, dev, reg, &data); + if (err) + return err; + + if (!!(data >> bit) == !!val) + return 0; + } + + return -ETIMEDOUT; +} + +static const struct mv88e6xxx_bus_ops mv88e6xxx_smi_direct_ops = { + .read = mv88e6xxx_smi_direct_read, + .write = mv88e6xxx_smi_direct_write, +}; + +/* Offset 0x00: SMI Command Register + * Offset 0x01: SMI Data Register + */ + +static int mv88e6xxx_smi_indirect_read(struct mv88e6xxx_chip *chip, + int dev, int reg, u16 *data) +{ + int err; + + err = mv88e6xxx_smi_direct_wait(chip, chip->sw_addr, + MV88E6XXX_SMI_CMD, 15, 0); + if (err) + return err; + + err = mv88e6xxx_smi_direct_write(chip, chip->sw_addr, + MV88E6XXX_SMI_CMD, + MV88E6XXX_SMI_CMD_BUSY | + MV88E6XXX_SMI_CMD_MODE_22 | + MV88E6XXX_SMI_CMD_OP_22_READ | + (dev << 5) | reg); + if (err) + return err; + + err = mv88e6xxx_smi_direct_wait(chip, chip->sw_addr, + MV88E6XXX_SMI_CMD, 15, 0); + if (err) + return err; + + return mv88e6xxx_smi_direct_read(chip, chip->sw_addr, + MV88E6XXX_SMI_DATA, data); +} + +static int mv88e6xxx_smi_indirect_write(struct mv88e6xxx_chip *chip, + int dev, int reg, u16 data) +{ + int err; + + err = mv88e6xxx_smi_direct_wait(chip, chip->sw_addr, + MV88E6XXX_SMI_CMD, 15, 0); + if (err) + return err; + + err = mv88e6xxx_smi_direct_write(chip, chip->sw_addr, + MV88E6XXX_SMI_DATA, data); + if (err) + return err; + + err = mv88e6xxx_smi_direct_write(chip, chip->sw_addr, + MV88E6XXX_SMI_CMD, + MV88E6XXX_SMI_CMD_BUSY | + MV88E6XXX_SMI_CMD_MODE_22 | + MV88E6XXX_SMI_CMD_OP_22_WRITE | + (dev << 5) | reg); + if (err) + return err; + + return mv88e6xxx_smi_direct_wait(chip, chip->sw_addr, + MV88E6XXX_SMI_CMD, 15, 0); +} + +static const struct mv88e6xxx_bus_ops mv88e6xxx_smi_indirect_ops = { + .read = mv88e6xxx_smi_indirect_read, + .write = mv88e6xxx_smi_indirect_write, +}; + +int mv88e6xxx_smi_init(struct mv88e6xxx_chip *chip, + struct mii_bus *bus, int sw_addr) +{ + if (sw_addr == 0) + chip->smi_ops = &mv88e6xxx_smi_direct_ops; + else if (chip->info->multi_chip) + chip->smi_ops = &mv88e6xxx_smi_indirect_ops; + else + return -EINVAL; + + chip->bus = bus; + chip->sw_addr = sw_addr; + + return 0; +} diff --git a/drivers/net/dsa/mv88e6xxx/smi.h b/drivers/net/dsa/mv88e6xxx/smi.h new file mode 100644 index 000000000000..35e6403b65dc --- /dev/null +++ b/drivers/net/dsa/mv88e6xxx/smi.h @@ -0,0 +1,59 @@ +/* + * Marvell 88E6xxx System Management Interface (SMI) support + * + * Copyright (c) 2008 Marvell Semiconductor + * + * Copyright (c) 2019 Vivien Didelot <vivien.didelot@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef _MV88E6XXX_SMI_H +#define _MV88E6XXX_SMI_H + +#include "chip.h" + +/* Offset 0x00: SMI Command Register */ +#define MV88E6XXX_SMI_CMD 0x00 +#define MV88E6XXX_SMI_CMD_BUSY 0x8000 +#define MV88E6XXX_SMI_CMD_MODE_MASK 0x1000 +#define MV88E6XXX_SMI_CMD_MODE_45 0x0000 +#define MV88E6XXX_SMI_CMD_MODE_22 0x1000 +#define MV88E6XXX_SMI_CMD_OP_MASK 0x0c00 +#define MV88E6XXX_SMI_CMD_OP_22_WRITE 0x0400 +#define MV88E6XXX_SMI_CMD_OP_22_READ 0x0800 +#define MV88E6XXX_SMI_CMD_OP_45_WRITE_ADDR 0x0000 +#define MV88E6XXX_SMI_CMD_OP_45_WRITE_DATA 0x0400 +#define MV88E6XXX_SMI_CMD_OP_45_READ_DATA 0x0800 +#define MV88E6XXX_SMI_CMD_OP_45_READ_DATA_INC 0x0c00 +#define MV88E6XXX_SMI_CMD_DEV_ADDR_MASK 0x003e +#define MV88E6XXX_SMI_CMD_REG_ADDR_MASK 0x001f + +/* Offset 0x01: SMI Data Register */ +#define MV88E6XXX_SMI_DATA 0x01 + +int mv88e6xxx_smi_init(struct mv88e6xxx_chip *chip, + struct mii_bus *bus, int sw_addr); + +static inline int mv88e6xxx_smi_read(struct mv88e6xxx_chip *chip, + int dev, int reg, u16 *data) +{ + if (chip->smi_ops && chip->smi_ops->read) + return chip->smi_ops->read(chip, dev, reg, data); + + return -EOPNOTSUPP; +} + +static inline int mv88e6xxx_smi_write(struct mv88e6xxx_chip *chip, + int dev, int reg, u16 data) +{ + if (chip->smi_ops && chip->smi_ops->write) + return chip->smi_ops->write(chip, dev, reg, data); + + return -EOPNOTSUPP; +} + +#endif /* _MV88E6XXX_SMI_H */ diff --git a/drivers/net/dsa/sja1105/Kconfig b/drivers/net/dsa/sja1105/Kconfig new file mode 100644 index 000000000000..757751a89819 --- /dev/null +++ b/drivers/net/dsa/sja1105/Kconfig @@ -0,0 +1,17 @@ +config NET_DSA_SJA1105 +tristate "NXP SJA1105 Ethernet switch family support" + depends on NET_DSA && SPI + select NET_DSA_TAG_SJA1105 + select PACKING + select CRC32 + help + This is the driver for the NXP SJA1105 automotive Ethernet switch + family. These are 5-port devices and are managed over an SPI + interface. Probing is handled based on OF bindings and so is the + linkage to phylib. The driver supports the following revisions: + - SJA1105E (Gen. 1, No TT-Ethernet) + - SJA1105T (Gen. 1, TT-Ethernet) + - SJA1105P (Gen. 2, No SGMII, No TT-Ethernet) + - SJA1105Q (Gen. 2, No SGMII, TT-Ethernet) + - SJA1105R (Gen. 2, SGMII, No TT-Ethernet) + - SJA1105S (Gen. 2, SGMII, TT-Ethernet) diff --git a/drivers/net/dsa/sja1105/Makefile b/drivers/net/dsa/sja1105/Makefile new file mode 100644 index 000000000000..1c2b55fec959 --- /dev/null +++ b/drivers/net/dsa/sja1105/Makefile @@ -0,0 +1,9 @@ +obj-$(CONFIG_NET_DSA_SJA1105) += sja1105.o + +sja1105-objs := \ + sja1105_spi.o \ + sja1105_main.o \ + sja1105_ethtool.o \ + sja1105_clocking.o \ + sja1105_static_config.o \ + sja1105_dynamic_config.o \ diff --git a/drivers/net/dsa/sja1105/sja1105.h b/drivers/net/dsa/sja1105/sja1105.h new file mode 100644 index 000000000000..b043bfc408f2 --- /dev/null +++ b/drivers/net/dsa/sja1105/sja1105.h @@ -0,0 +1,159 @@ +/* SPDX-License-Identifier: GPL-2.0 + * Copyright (c) 2018, Sensor-Technik Wiedemann GmbH + * Copyright (c) 2018-2019, Vladimir Oltean <olteanv@gmail.com> + */ +#ifndef _SJA1105_H +#define _SJA1105_H + +#include <linux/dsa/sja1105.h> +#include <net/dsa.h> +#include <linux/mutex.h> +#include "sja1105_static_config.h" + +#define SJA1105_NUM_PORTS 5 +#define SJA1105_NUM_TC 8 +#define SJA1105ET_FDB_BIN_SIZE 4 +/* The hardware value is in multiples of 10 ms. + * The passed parameter is in multiples of 1 ms. + */ +#define SJA1105_AGEING_TIME_MS(ms) ((ms) / 10) + +/* Keeps the different addresses between E/T and P/Q/R/S */ +struct sja1105_regs { + u64 device_id; + u64 prod_id; + u64 status; + u64 port_control; + u64 rgu; + u64 config; + u64 rmii_pll1; + u64 pad_mii_tx[SJA1105_NUM_PORTS]; + u64 cgu_idiv[SJA1105_NUM_PORTS]; + u64 rgmii_pad_mii_tx[SJA1105_NUM_PORTS]; + u64 mii_tx_clk[SJA1105_NUM_PORTS]; + u64 mii_rx_clk[SJA1105_NUM_PORTS]; + u64 mii_ext_tx_clk[SJA1105_NUM_PORTS]; + u64 mii_ext_rx_clk[SJA1105_NUM_PORTS]; + u64 rgmii_tx_clk[SJA1105_NUM_PORTS]; + u64 rmii_ref_clk[SJA1105_NUM_PORTS]; + u64 rmii_ext_tx_clk[SJA1105_NUM_PORTS]; + u64 mac[SJA1105_NUM_PORTS]; + u64 mac_hl1[SJA1105_NUM_PORTS]; + u64 mac_hl2[SJA1105_NUM_PORTS]; + u64 qlevel[SJA1105_NUM_PORTS]; +}; + +struct sja1105_info { + u64 device_id; + /* Needed for distinction between P and R, and between Q and S + * (since the parts with/without SGMII share the same + * switch core and device_id) + */ + u64 part_no; + const struct sja1105_dynamic_table_ops *dyn_ops; + const struct sja1105_table_ops *static_ops; + const struct sja1105_regs *regs; + int (*reset_cmd)(const void *ctx, const void *data); + int (*setup_rgmii_delay)(const void *ctx, int port); + const char *name; +}; + +struct sja1105_private { + struct sja1105_static_config static_config; + bool rgmii_rx_delay[SJA1105_NUM_PORTS]; + bool rgmii_tx_delay[SJA1105_NUM_PORTS]; + const struct sja1105_info *info; + struct gpio_desc *reset_gpio; + struct spi_device *spidev; + struct dsa_switch *ds; + struct sja1105_port ports[SJA1105_NUM_PORTS]; + /* Serializes transmission of management frames so that + * the switch doesn't confuse them with one another. + */ + struct mutex mgmt_lock; +}; + +#include "sja1105_dynamic_config.h" + +struct sja1105_spi_message { + u64 access; + u64 read_count; + u64 address; +}; + +typedef enum { + SPI_READ = 0, + SPI_WRITE = 1, +} sja1105_spi_rw_mode_t; + +/* From sja1105_spi.c */ +int sja1105_spi_send_packed_buf(const struct sja1105_private *priv, + sja1105_spi_rw_mode_t rw, u64 reg_addr, + void *packed_buf, size_t size_bytes); +int sja1105_spi_send_int(const struct sja1105_private *priv, + sja1105_spi_rw_mode_t rw, u64 reg_addr, + u64 *value, u64 size_bytes); +int sja1105_spi_send_long_packed_buf(const struct sja1105_private *priv, + sja1105_spi_rw_mode_t rw, u64 base_addr, + void *packed_buf, u64 buf_len); +int sja1105_static_config_upload(struct sja1105_private *priv); + +extern struct sja1105_info sja1105e_info; +extern struct sja1105_info sja1105t_info; +extern struct sja1105_info sja1105p_info; +extern struct sja1105_info sja1105q_info; +extern struct sja1105_info sja1105r_info; +extern struct sja1105_info sja1105s_info; + +/* From sja1105_clocking.c */ + +typedef enum { + XMII_MAC = 0, + XMII_PHY = 1, +} sja1105_mii_role_t; + +typedef enum { + XMII_MODE_MII = 0, + XMII_MODE_RMII = 1, + XMII_MODE_RGMII = 2, +} sja1105_phy_interface_t; + +typedef enum { + SJA1105_SPEED_10MBPS = 3, + SJA1105_SPEED_100MBPS = 2, + SJA1105_SPEED_1000MBPS = 1, + SJA1105_SPEED_AUTO = 0, +} sja1105_speed_t; + +int sja1105_clocking_setup_port(struct sja1105_private *priv, int port); +int sja1105_clocking_setup(struct sja1105_private *priv); + +/* From sja1105_ethtool.c */ +void sja1105_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *data); +void sja1105_get_strings(struct dsa_switch *ds, int port, + u32 stringset, u8 *data); +int sja1105_get_sset_count(struct dsa_switch *ds, int port, int sset); + +/* From sja1105_dynamic_config.c */ +int sja1105_dynamic_config_read(struct sja1105_private *priv, + enum sja1105_blk_idx blk_idx, + int index, void *entry); +int sja1105_dynamic_config_write(struct sja1105_private *priv, + enum sja1105_blk_idx blk_idx, + int index, void *entry, bool keep); + +u8 sja1105_fdb_hash(struct sja1105_private *priv, const u8 *addr, u16 vid); + +/* Common implementations for the static and dynamic configs */ +size_t sja1105_l2_forwarding_entry_packing(void *buf, void *entry_ptr, + enum packing_op op); +size_t sja1105pqrs_l2_lookup_entry_packing(void *buf, void *entry_ptr, + enum packing_op op); +size_t sja1105et_l2_lookup_entry_packing(void *buf, void *entry_ptr, + enum packing_op op); +size_t sja1105_vlan_lookup_entry_packing(void *buf, void *entry_ptr, + enum packing_op op); +size_t sja1105pqrs_mac_config_entry_packing(void *buf, void *entry_ptr, + enum packing_op op); + +#endif diff --git a/drivers/net/dsa/sja1105/sja1105_clocking.c b/drivers/net/dsa/sja1105/sja1105_clocking.c new file mode 100644 index 000000000000..94bfe0ee50a8 --- /dev/null +++ b/drivers/net/dsa/sja1105/sja1105_clocking.c @@ -0,0 +1,601 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* Copyright (c) 2016-2018, NXP Semiconductors + * Copyright (c) 2018-2019, Vladimir Oltean <olteanv@gmail.com> + */ +#include <linux/packing.h> +#include "sja1105.h" + +#define SJA1105_SIZE_CGU_CMD 4 + +struct sja1105_cfg_pad_mii_tx { + u64 d32_os; + u64 d32_ipud; + u64 d10_os; + u64 d10_ipud; + u64 ctrl_os; + u64 ctrl_ipud; + u64 clk_os; + u64 clk_ih; + u64 clk_ipud; +}; + +/* UM10944 Table 82. + * IDIV_0_C to IDIV_4_C control registers + * (addr. 10000Bh to 10000Fh) + */ +struct sja1105_cgu_idiv { + u64 clksrc; + u64 autoblock; + u64 idiv; + u64 pd; +}; + +/* PLL_1_C control register + * + * SJA1105 E/T: UM10944 Table 81 (address 10000Ah) + * SJA1105 P/Q/R/S: UM11040 Table 116 (address 10000Ah) + */ +struct sja1105_cgu_pll_ctrl { + u64 pllclksrc; + u64 msel; + u64 autoblock; + u64 psel; + u64 direct; + u64 fbsel; + u64 bypass; + u64 pd; +}; + +enum { + CLKSRC_MII0_TX_CLK = 0x00, + CLKSRC_MII0_RX_CLK = 0x01, + CLKSRC_MII1_TX_CLK = 0x02, + CLKSRC_MII1_RX_CLK = 0x03, + CLKSRC_MII2_TX_CLK = 0x04, + CLKSRC_MII2_RX_CLK = 0x05, + CLKSRC_MII3_TX_CLK = 0x06, + CLKSRC_MII3_RX_CLK = 0x07, + CLKSRC_MII4_TX_CLK = 0x08, + CLKSRC_MII4_RX_CLK = 0x09, + CLKSRC_PLL0 = 0x0B, + CLKSRC_PLL1 = 0x0E, + CLKSRC_IDIV0 = 0x11, + CLKSRC_IDIV1 = 0x12, + CLKSRC_IDIV2 = 0x13, + CLKSRC_IDIV3 = 0x14, + CLKSRC_IDIV4 = 0x15, +}; + +/* UM10944 Table 83. + * MIIx clock control registers 1 to 30 + * (addresses 100013h to 100035h) + */ +struct sja1105_cgu_mii_ctrl { + u64 clksrc; + u64 autoblock; + u64 pd; +}; + +static void sja1105_cgu_idiv_packing(void *buf, struct sja1105_cgu_idiv *idiv, + enum packing_op op) +{ + const int size = 4; + + sja1105_packing(buf, &idiv->clksrc, 28, 24, size, op); + sja1105_packing(buf, &idiv->autoblock, 11, 11, size, op); + sja1105_packing(buf, &idiv->idiv, 5, 2, size, op); + sja1105_packing(buf, &idiv->pd, 0, 0, size, op); +} + +static int sja1105_cgu_idiv_config(struct sja1105_private *priv, int port, + bool enabled, int factor) +{ + const struct sja1105_regs *regs = priv->info->regs; + struct device *dev = priv->ds->dev; + struct sja1105_cgu_idiv idiv; + u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0}; + + if (enabled && factor != 1 && factor != 10) { + dev_err(dev, "idiv factor must be 1 or 10\n"); + return -ERANGE; + } + + /* Payload for packed_buf */ + idiv.clksrc = 0x0A; /* 25MHz */ + idiv.autoblock = 1; /* Block clk automatically */ + idiv.idiv = factor - 1; /* Divide by 1 or 10 */ + idiv.pd = enabled ? 0 : 1; /* Power down? */ + sja1105_cgu_idiv_packing(packed_buf, &idiv, PACK); + + return sja1105_spi_send_packed_buf(priv, SPI_WRITE, + regs->cgu_idiv[port], packed_buf, + SJA1105_SIZE_CGU_CMD); +} + +static void +sja1105_cgu_mii_control_packing(void *buf, struct sja1105_cgu_mii_ctrl *cmd, + enum packing_op op) +{ + const int size = 4; + + sja1105_packing(buf, &cmd->clksrc, 28, 24, size, op); + sja1105_packing(buf, &cmd->autoblock, 11, 11, size, op); + sja1105_packing(buf, &cmd->pd, 0, 0, size, op); +} + +static int sja1105_cgu_mii_tx_clk_config(struct sja1105_private *priv, + int port, sja1105_mii_role_t role) +{ + const struct sja1105_regs *regs = priv->info->regs; + struct sja1105_cgu_mii_ctrl mii_tx_clk; + const int mac_clk_sources[] = { + CLKSRC_MII0_TX_CLK, + CLKSRC_MII1_TX_CLK, + CLKSRC_MII2_TX_CLK, + CLKSRC_MII3_TX_CLK, + CLKSRC_MII4_TX_CLK, + }; + const int phy_clk_sources[] = { + CLKSRC_IDIV0, + CLKSRC_IDIV1, + CLKSRC_IDIV2, + CLKSRC_IDIV3, + CLKSRC_IDIV4, + }; + u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0}; + int clksrc; + + if (role == XMII_MAC) + clksrc = mac_clk_sources[port]; + else + clksrc = phy_clk_sources[port]; + + /* Payload for packed_buf */ + mii_tx_clk.clksrc = clksrc; + mii_tx_clk.autoblock = 1; /* Autoblock clk while changing clksrc */ + mii_tx_clk.pd = 0; /* Power Down off => enabled */ + sja1105_cgu_mii_control_packing(packed_buf, &mii_tx_clk, PACK); + + return sja1105_spi_send_packed_buf(priv, SPI_WRITE, + regs->mii_tx_clk[port], packed_buf, + SJA1105_SIZE_CGU_CMD); +} + +static int +sja1105_cgu_mii_rx_clk_config(struct sja1105_private *priv, int port) +{ + const struct sja1105_regs *regs = priv->info->regs; + struct sja1105_cgu_mii_ctrl mii_rx_clk; + u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0}; + const int clk_sources[] = { + CLKSRC_MII0_RX_CLK, + CLKSRC_MII1_RX_CLK, + CLKSRC_MII2_RX_CLK, + CLKSRC_MII3_RX_CLK, + CLKSRC_MII4_RX_CLK, + }; + + /* Payload for packed_buf */ + mii_rx_clk.clksrc = clk_sources[port]; + mii_rx_clk.autoblock = 1; /* Autoblock clk while changing clksrc */ + mii_rx_clk.pd = 0; /* Power Down off => enabled */ + sja1105_cgu_mii_control_packing(packed_buf, &mii_rx_clk, PACK); + + return sja1105_spi_send_packed_buf(priv, SPI_WRITE, + regs->mii_rx_clk[port], packed_buf, + SJA1105_SIZE_CGU_CMD); +} + +static int +sja1105_cgu_mii_ext_tx_clk_config(struct sja1105_private *priv, int port) +{ + const struct sja1105_regs *regs = priv->info->regs; + struct sja1105_cgu_mii_ctrl mii_ext_tx_clk; + u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0}; + const int clk_sources[] = { + CLKSRC_IDIV0, + CLKSRC_IDIV1, + CLKSRC_IDIV2, + CLKSRC_IDIV3, + CLKSRC_IDIV4, + }; + + /* Payload for packed_buf */ + mii_ext_tx_clk.clksrc = clk_sources[port]; + mii_ext_tx_clk.autoblock = 1; /* Autoblock clk while changing clksrc */ + mii_ext_tx_clk.pd = 0; /* Power Down off => enabled */ + sja1105_cgu_mii_control_packing(packed_buf, &mii_ext_tx_clk, PACK); + + return sja1105_spi_send_packed_buf(priv, SPI_WRITE, + regs->mii_ext_tx_clk[port], + packed_buf, SJA1105_SIZE_CGU_CMD); +} + +static int +sja1105_cgu_mii_ext_rx_clk_config(struct sja1105_private *priv, int port) +{ + const struct sja1105_regs *regs = priv->info->regs; + struct sja1105_cgu_mii_ctrl mii_ext_rx_clk; + u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0}; + const int clk_sources[] = { + CLKSRC_IDIV0, + CLKSRC_IDIV1, + CLKSRC_IDIV2, + CLKSRC_IDIV3, + CLKSRC_IDIV4, + }; + + /* Payload for packed_buf */ + mii_ext_rx_clk.clksrc = clk_sources[port]; + mii_ext_rx_clk.autoblock = 1; /* Autoblock clk while changing clksrc */ + mii_ext_rx_clk.pd = 0; /* Power Down off => enabled */ + sja1105_cgu_mii_control_packing(packed_buf, &mii_ext_rx_clk, PACK); + + return sja1105_spi_send_packed_buf(priv, SPI_WRITE, + regs->mii_ext_rx_clk[port], + packed_buf, SJA1105_SIZE_CGU_CMD); +} + +static int sja1105_mii_clocking_setup(struct sja1105_private *priv, int port, + sja1105_mii_role_t role) +{ + struct device *dev = priv->ds->dev; + int rc; + + dev_dbg(dev, "Configuring MII-%s clocking\n", + (role == XMII_MAC) ? "MAC" : "PHY"); + /* If role is MAC, disable IDIV + * If role is PHY, enable IDIV and configure for 1/1 divider + */ + rc = sja1105_cgu_idiv_config(priv, port, (role == XMII_PHY), 1); + if (rc < 0) + return rc; + + /* Configure CLKSRC of MII_TX_CLK_n + * * If role is MAC, select TX_CLK_n + * * If role is PHY, select IDIV_n + */ + rc = sja1105_cgu_mii_tx_clk_config(priv, port, role); + if (rc < 0) + return rc; + + /* Configure CLKSRC of MII_RX_CLK_n + * Select RX_CLK_n + */ + rc = sja1105_cgu_mii_rx_clk_config(priv, port); + if (rc < 0) + return rc; + + if (role == XMII_PHY) { + /* Per MII spec, the PHY (which is us) drives the TX_CLK pin */ + + /* Configure CLKSRC of EXT_TX_CLK_n + * Select IDIV_n + */ + rc = sja1105_cgu_mii_ext_tx_clk_config(priv, port); + if (rc < 0) + return rc; + + /* Configure CLKSRC of EXT_RX_CLK_n + * Select IDIV_n + */ + rc = sja1105_cgu_mii_ext_rx_clk_config(priv, port); + if (rc < 0) + return rc; + } + return 0; +} + +static void +sja1105_cgu_pll_control_packing(void *buf, struct sja1105_cgu_pll_ctrl *cmd, + enum packing_op op) +{ + const int size = 4; + + sja1105_packing(buf, &cmd->pllclksrc, 28, 24, size, op); + sja1105_packing(buf, &cmd->msel, 23, 16, size, op); + sja1105_packing(buf, &cmd->autoblock, 11, 11, size, op); + sja1105_packing(buf, &cmd->psel, 9, 8, size, op); + sja1105_packing(buf, &cmd->direct, 7, 7, size, op); + sja1105_packing(buf, &cmd->fbsel, 6, 6, size, op); + sja1105_packing(buf, &cmd->bypass, 1, 1, size, op); + sja1105_packing(buf, &cmd->pd, 0, 0, size, op); +} + +static int sja1105_cgu_rgmii_tx_clk_config(struct sja1105_private *priv, + int port, sja1105_speed_t speed) +{ + const struct sja1105_regs *regs = priv->info->regs; + struct sja1105_cgu_mii_ctrl txc; + u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0}; + int clksrc; + + if (speed == SJA1105_SPEED_1000MBPS) { + clksrc = CLKSRC_PLL0; + } else { + int clk_sources[] = {CLKSRC_IDIV0, CLKSRC_IDIV1, CLKSRC_IDIV2, + CLKSRC_IDIV3, CLKSRC_IDIV4}; + clksrc = clk_sources[port]; + } + + /* RGMII: 125MHz for 1000, 25MHz for 100, 2.5MHz for 10 */ + txc.clksrc = clksrc; + /* Autoblock clk while changing clksrc */ + txc.autoblock = 1; + /* Power Down off => enabled */ + txc.pd = 0; + sja1105_cgu_mii_control_packing(packed_buf, &txc, PACK); + + return sja1105_spi_send_packed_buf(priv, SPI_WRITE, + regs->rgmii_tx_clk[port], + packed_buf, SJA1105_SIZE_CGU_CMD); +} + +/* AGU */ +static void +sja1105_cfg_pad_mii_tx_packing(void *buf, struct sja1105_cfg_pad_mii_tx *cmd, + enum packing_op op) +{ + const int size = 4; + + sja1105_packing(buf, &cmd->d32_os, 28, 27, size, op); + sja1105_packing(buf, &cmd->d32_ipud, 25, 24, size, op); + sja1105_packing(buf, &cmd->d10_os, 20, 19, size, op); + sja1105_packing(buf, &cmd->d10_ipud, 17, 16, size, op); + sja1105_packing(buf, &cmd->ctrl_os, 12, 11, size, op); + sja1105_packing(buf, &cmd->ctrl_ipud, 9, 8, size, op); + sja1105_packing(buf, &cmd->clk_os, 4, 3, size, op); + sja1105_packing(buf, &cmd->clk_ih, 2, 2, size, op); + sja1105_packing(buf, &cmd->clk_ipud, 1, 0, size, op); +} + +static int sja1105_rgmii_cfg_pad_tx_config(struct sja1105_private *priv, + int port) +{ + const struct sja1105_regs *regs = priv->info->regs; + struct sja1105_cfg_pad_mii_tx pad_mii_tx; + u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0}; + + /* Payload */ + pad_mii_tx.d32_os = 3; /* TXD[3:2] output stage: */ + /* high noise/high speed */ + pad_mii_tx.d10_os = 3; /* TXD[1:0] output stage: */ + /* high noise/high speed */ + pad_mii_tx.d32_ipud = 2; /* TXD[3:2] input stage: */ + /* plain input (default) */ + pad_mii_tx.d10_ipud = 2; /* TXD[1:0] input stage: */ + /* plain input (default) */ + pad_mii_tx.ctrl_os = 3; /* TX_CTL / TX_ER output stage */ + pad_mii_tx.ctrl_ipud = 2; /* TX_CTL / TX_ER input stage (default) */ + pad_mii_tx.clk_os = 3; /* TX_CLK output stage */ + pad_mii_tx.clk_ih = 0; /* TX_CLK input hysteresis (default) */ + pad_mii_tx.clk_ipud = 2; /* TX_CLK input stage (default) */ + sja1105_cfg_pad_mii_tx_packing(packed_buf, &pad_mii_tx, PACK); + + return sja1105_spi_send_packed_buf(priv, SPI_WRITE, + regs->rgmii_pad_mii_tx[port], + packed_buf, SJA1105_SIZE_CGU_CMD); +} + +static int sja1105_rgmii_clocking_setup(struct sja1105_private *priv, int port) +{ + struct device *dev = priv->ds->dev; + struct sja1105_mac_config_entry *mac; + sja1105_speed_t speed; + int rc; + + mac = priv->static_config.tables[BLK_IDX_MAC_CONFIG].entries; + speed = mac[port].speed; + + dev_dbg(dev, "Configuring port %d RGMII at speed %dMbps\n", + port, speed); + + switch (speed) { + case SJA1105_SPEED_1000MBPS: + /* 1000Mbps, IDIV disabled (125 MHz) */ + rc = sja1105_cgu_idiv_config(priv, port, false, 1); + break; + case SJA1105_SPEED_100MBPS: + /* 100Mbps, IDIV enabled, divide by 1 (25 MHz) */ + rc = sja1105_cgu_idiv_config(priv, port, true, 1); + break; + case SJA1105_SPEED_10MBPS: + /* 10Mbps, IDIV enabled, divide by 10 (2.5 MHz) */ + rc = sja1105_cgu_idiv_config(priv, port, true, 10); + break; + case SJA1105_SPEED_AUTO: + /* Skip CGU configuration if there is no speed available + * (e.g. link is not established yet) + */ + dev_dbg(dev, "Speed not available, skipping CGU config\n"); + return 0; + default: + rc = -EINVAL; + } + + if (rc < 0) { + dev_err(dev, "Failed to configure idiv\n"); + return rc; + } + rc = sja1105_cgu_rgmii_tx_clk_config(priv, port, speed); + if (rc < 0) { + dev_err(dev, "Failed to configure RGMII Tx clock\n"); + return rc; + } + rc = sja1105_rgmii_cfg_pad_tx_config(priv, port); + if (rc < 0) { + dev_err(dev, "Failed to configure Tx pad registers\n"); + return rc; + } + if (!priv->info->setup_rgmii_delay) + return 0; + + return priv->info->setup_rgmii_delay(priv, port); +} + +static int sja1105_cgu_rmii_ref_clk_config(struct sja1105_private *priv, + int port) +{ + const struct sja1105_regs *regs = priv->info->regs; + struct sja1105_cgu_mii_ctrl ref_clk; + u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0}; + const int clk_sources[] = { + CLKSRC_MII0_TX_CLK, + CLKSRC_MII1_TX_CLK, + CLKSRC_MII2_TX_CLK, + CLKSRC_MII3_TX_CLK, + CLKSRC_MII4_TX_CLK, + }; + + /* Payload for packed_buf */ + ref_clk.clksrc = clk_sources[port]; + ref_clk.autoblock = 1; /* Autoblock clk while changing clksrc */ + ref_clk.pd = 0; /* Power Down off => enabled */ + sja1105_cgu_mii_control_packing(packed_buf, &ref_clk, PACK); + + return sja1105_spi_send_packed_buf(priv, SPI_WRITE, + regs->rmii_ref_clk[port], + packed_buf, SJA1105_SIZE_CGU_CMD); +} + +static int +sja1105_cgu_rmii_ext_tx_clk_config(struct sja1105_private *priv, int port) +{ + const struct sja1105_regs *regs = priv->info->regs; + struct sja1105_cgu_mii_ctrl ext_tx_clk; + u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0}; + + /* Payload for packed_buf */ + ext_tx_clk.clksrc = CLKSRC_PLL1; + ext_tx_clk.autoblock = 1; /* Autoblock clk while changing clksrc */ + ext_tx_clk.pd = 0; /* Power Down off => enabled */ + sja1105_cgu_mii_control_packing(packed_buf, &ext_tx_clk, PACK); + + return sja1105_spi_send_packed_buf(priv, SPI_WRITE, + regs->rmii_ext_tx_clk[port], + packed_buf, SJA1105_SIZE_CGU_CMD); +} + +static int sja1105_cgu_rmii_pll_config(struct sja1105_private *priv) +{ + const struct sja1105_regs *regs = priv->info->regs; + u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0}; + struct sja1105_cgu_pll_ctrl pll = {0}; + struct device *dev = priv->ds->dev; + int rc; + + /* PLL1 must be enabled and output 50 Mhz. + * This is done by writing first 0x0A010941 to + * the PLL_1_C register and then deasserting + * power down (PD) 0x0A010940. + */ + + /* Step 1: PLL1 setup for 50Mhz */ + pll.pllclksrc = 0xA; + pll.msel = 0x1; + pll.autoblock = 0x1; + pll.psel = 0x1; + pll.direct = 0x0; + pll.fbsel = 0x1; + pll.bypass = 0x0; + pll.pd = 0x1; + + sja1105_cgu_pll_control_packing(packed_buf, &pll, PACK); + rc = sja1105_spi_send_packed_buf(priv, SPI_WRITE, regs->rmii_pll1, + packed_buf, SJA1105_SIZE_CGU_CMD); + if (rc < 0) { + dev_err(dev, "failed to configure PLL1 for 50MHz\n"); + return rc; + } + + /* Step 2: Enable PLL1 */ + pll.pd = 0x0; + + sja1105_cgu_pll_control_packing(packed_buf, &pll, PACK); + rc = sja1105_spi_send_packed_buf(priv, SPI_WRITE, regs->rmii_pll1, + packed_buf, SJA1105_SIZE_CGU_CMD); + if (rc < 0) { + dev_err(dev, "failed to enable PLL1\n"); + return rc; + } + return rc; +} + +static int sja1105_rmii_clocking_setup(struct sja1105_private *priv, int port, + sja1105_mii_role_t role) +{ + struct device *dev = priv->ds->dev; + int rc; + + dev_dbg(dev, "Configuring RMII-%s clocking\n", + (role == XMII_MAC) ? "MAC" : "PHY"); + /* AH1601.pdf chapter 2.5.1. Sources */ + if (role == XMII_MAC) { + /* Configure and enable PLL1 for 50Mhz output */ + rc = sja1105_cgu_rmii_pll_config(priv); + if (rc < 0) + return rc; + } + /* Disable IDIV for this port */ + rc = sja1105_cgu_idiv_config(priv, port, false, 1); + if (rc < 0) + return rc; + /* Source to sink mappings */ + rc = sja1105_cgu_rmii_ref_clk_config(priv, port); + if (rc < 0) + return rc; + if (role == XMII_MAC) { + rc = sja1105_cgu_rmii_ext_tx_clk_config(priv, port); + if (rc < 0) + return rc; + } + return 0; +} + +int sja1105_clocking_setup_port(struct sja1105_private *priv, int port) +{ + struct sja1105_xmii_params_entry *mii; + struct device *dev = priv->ds->dev; + sja1105_phy_interface_t phy_mode; + sja1105_mii_role_t role; + int rc; + + mii = priv->static_config.tables[BLK_IDX_XMII_PARAMS].entries; + + /* RGMII etc */ + phy_mode = mii->xmii_mode[port]; + /* MAC or PHY, for applicable types (not RGMII) */ + role = mii->phy_mac[port]; + + switch (phy_mode) { + case XMII_MODE_MII: + rc = sja1105_mii_clocking_setup(priv, port, role); + break; + case XMII_MODE_RMII: + rc = sja1105_rmii_clocking_setup(priv, port, role); + break; + case XMII_MODE_RGMII: + rc = sja1105_rgmii_clocking_setup(priv, port); + break; + default: + dev_err(dev, "Invalid interface mode specified: %d\n", + phy_mode); + return -EINVAL; + } + if (rc) + dev_err(dev, "Clocking setup for port %d failed: %d\n", + port, rc); + return rc; +} + +int sja1105_clocking_setup(struct sja1105_private *priv) +{ + int port, rc; + + for (port = 0; port < SJA1105_NUM_PORTS; port++) { + rc = sja1105_clocking_setup_port(priv, port); + if (rc < 0) + return rc; + } + return 0; +} diff --git a/drivers/net/dsa/sja1105/sja1105_dynamic_config.c b/drivers/net/dsa/sja1105/sja1105_dynamic_config.c new file mode 100644 index 000000000000..e73ab28bf632 --- /dev/null +++ b/drivers/net/dsa/sja1105/sja1105_dynamic_config.c @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2018-2019, Vladimir Oltean <olteanv@gmail.com> + */ +#include "sja1105.h" + +#define SJA1105_SIZE_DYN_CMD 4 + +#define SJA1105ET_SIZE_MAC_CONFIG_DYN_ENTRY \ + SJA1105_SIZE_DYN_CMD + +#define SJA1105ET_SIZE_L2_LOOKUP_DYN_CMD \ + (SJA1105_SIZE_DYN_CMD + SJA1105ET_SIZE_L2_LOOKUP_ENTRY) + +#define SJA1105PQRS_SIZE_L2_LOOKUP_DYN_CMD \ + (SJA1105_SIZE_DYN_CMD + SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY) + +#define SJA1105_SIZE_VLAN_LOOKUP_DYN_CMD \ + (SJA1105_SIZE_DYN_CMD + 4 + SJA1105_SIZE_VLAN_LOOKUP_ENTRY) + +#define SJA1105_SIZE_L2_FORWARDING_DYN_CMD \ + (SJA1105_SIZE_DYN_CMD + SJA1105_SIZE_L2_FORWARDING_ENTRY) + +#define SJA1105ET_SIZE_MAC_CONFIG_DYN_CMD \ + (SJA1105_SIZE_DYN_CMD + SJA1105ET_SIZE_MAC_CONFIG_DYN_ENTRY) + +#define SJA1105PQRS_SIZE_MAC_CONFIG_DYN_CMD \ + (SJA1105_SIZE_DYN_CMD + SJA1105PQRS_SIZE_MAC_CONFIG_ENTRY) + +#define SJA1105ET_SIZE_L2_LOOKUP_PARAMS_DYN_CMD \ + SJA1105_SIZE_DYN_CMD + +#define SJA1105ET_SIZE_GENERAL_PARAMS_DYN_CMD \ + SJA1105_SIZE_DYN_CMD + +#define SJA1105_MAX_DYN_CMD_SIZE \ + SJA1105PQRS_SIZE_MAC_CONFIG_DYN_CMD + +static void +sja1105pqrs_l2_lookup_cmd_packing(void *buf, struct sja1105_dyn_cmd *cmd, + enum packing_op op) +{ + u8 *p = buf + SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY; + const int size = SJA1105_SIZE_DYN_CMD; + + sja1105_packing(p, &cmd->valid, 31, 31, size, op); + sja1105_packing(p, &cmd->rdwrset, 30, 30, size, op); + sja1105_packing(p, &cmd->errors, 29, 29, size, op); + sja1105_packing(p, &cmd->valident, 27, 27, size, op); + /* Hack - The hardware takes the 'index' field within + * struct sja1105_l2_lookup_entry as the index on which this command + * will operate. However it will ignore everything else, so 'index' + * is logically part of command but physically part of entry. + * Populate the 'index' entry field from within the command callback, + * such that our API doesn't need to ask for a full-blown entry + * structure when e.g. a delete is requested. + */ + sja1105_packing(buf, &cmd->index, 29, 20, + SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY, op); + /* TODO hostcmd */ +} + +static void +sja1105et_l2_lookup_cmd_packing(void *buf, struct sja1105_dyn_cmd *cmd, + enum packing_op op) +{ + u8 *p = buf + SJA1105ET_SIZE_L2_LOOKUP_ENTRY; + const int size = SJA1105_SIZE_DYN_CMD; + + sja1105_packing(p, &cmd->valid, 31, 31, size, op); + sja1105_packing(p, &cmd->rdwrset, 30, 30, size, op); + sja1105_packing(p, &cmd->errors, 29, 29, size, op); + sja1105_packing(p, &cmd->valident, 27, 27, size, op); + /* Hack - see comments above. */ + sja1105_packing(buf, &cmd->index, 29, 20, + SJA1105ET_SIZE_L2_LOOKUP_ENTRY, op); +} + +static void +sja1105et_mgmt_route_cmd_packing(void *buf, struct sja1105_dyn_cmd *cmd, + enum packing_op op) +{ + u8 *p = buf + SJA1105ET_SIZE_L2_LOOKUP_ENTRY; + u64 mgmtroute = 1; + + sja1105et_l2_lookup_cmd_packing(buf, cmd, op); + if (op == PACK) + sja1105_pack(p, &mgmtroute, 26, 26, SJA1105_SIZE_DYN_CMD); +} + +static size_t sja1105et_mgmt_route_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + struct sja1105_mgmt_entry *entry = entry_ptr; + const size_t size = SJA1105ET_SIZE_L2_LOOKUP_ENTRY; + + /* UM10944: To specify if a PTP egress timestamp shall be captured on + * each port upon transmission of the frame, the LSB of VLANID in the + * ENTRY field provided by the host must be set. + * Bit 1 of VLANID then specifies the register where the timestamp for + * this port is stored in. + */ + sja1105_packing(buf, &entry->tsreg, 85, 85, size, op); + sja1105_packing(buf, &entry->takets, 84, 84, size, op); + sja1105_packing(buf, &entry->macaddr, 83, 36, size, op); + sja1105_packing(buf, &entry->destports, 35, 31, size, op); + sja1105_packing(buf, &entry->enfport, 30, 30, size, op); + return size; +} + +/* In E/T, entry is at addresses 0x27-0x28. There is a 4 byte gap at 0x29, + * and command is at 0x2a. Similarly in P/Q/R/S there is a 1 register gap + * between entry (0x2d, 0x2e) and command (0x30). + */ +static void +sja1105_vlan_lookup_cmd_packing(void *buf, struct sja1105_dyn_cmd *cmd, + enum packing_op op) +{ + u8 *p = buf + SJA1105_SIZE_VLAN_LOOKUP_ENTRY + 4; + const int size = SJA1105_SIZE_DYN_CMD; + + sja1105_packing(p, &cmd->valid, 31, 31, size, op); + sja1105_packing(p, &cmd->rdwrset, 30, 30, size, op); + sja1105_packing(p, &cmd->valident, 27, 27, size, op); + /* Hack - see comments above, applied for 'vlanid' field of + * struct sja1105_vlan_lookup_entry. + */ + sja1105_packing(buf, &cmd->index, 38, 27, + SJA1105_SIZE_VLAN_LOOKUP_ENTRY, op); +} + +static void +sja1105_l2_forwarding_cmd_packing(void *buf, struct sja1105_dyn_cmd *cmd, + enum packing_op op) +{ + u8 *p = buf + SJA1105_SIZE_L2_FORWARDING_ENTRY; + const int size = SJA1105_SIZE_DYN_CMD; + + sja1105_packing(p, &cmd->valid, 31, 31, size, op); + sja1105_packing(p, &cmd->errors, 30, 30, size, op); + sja1105_packing(p, &cmd->rdwrset, 29, 29, size, op); + sja1105_packing(p, &cmd->index, 4, 0, size, op); +} + +static void +sja1105et_mac_config_cmd_packing(void *buf, struct sja1105_dyn_cmd *cmd, + enum packing_op op) +{ + const int size = SJA1105_SIZE_DYN_CMD; + /* Yup, user manual definitions are reversed */ + u8 *reg1 = buf + 4; + + sja1105_packing(reg1, &cmd->valid, 31, 31, size, op); + sja1105_packing(reg1, &cmd->index, 26, 24, size, op); +} + +static size_t sja1105et_mac_config_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const int size = SJA1105ET_SIZE_MAC_CONFIG_DYN_ENTRY; + struct sja1105_mac_config_entry *entry = entry_ptr; + /* Yup, user manual definitions are reversed */ + u8 *reg1 = buf + 4; + u8 *reg2 = buf; + + sja1105_packing(reg1, &entry->speed, 30, 29, size, op); + sja1105_packing(reg1, &entry->drpdtag, 23, 23, size, op); + sja1105_packing(reg1, &entry->drpuntag, 22, 22, size, op); + sja1105_packing(reg1, &entry->retag, 21, 21, size, op); + sja1105_packing(reg1, &entry->dyn_learn, 20, 20, size, op); + sja1105_packing(reg1, &entry->egress, 19, 19, size, op); + sja1105_packing(reg1, &entry->ingress, 18, 18, size, op); + sja1105_packing(reg1, &entry->ing_mirr, 17, 17, size, op); + sja1105_packing(reg1, &entry->egr_mirr, 16, 16, size, op); + sja1105_packing(reg1, &entry->vlanprio, 14, 12, size, op); + sja1105_packing(reg1, &entry->vlanid, 11, 0, size, op); + sja1105_packing(reg2, &entry->tp_delin, 31, 16, size, op); + sja1105_packing(reg2, &entry->tp_delout, 15, 0, size, op); + /* MAC configuration table entries which can't be reconfigured: + * top, base, enabled, ifg, maxage, drpnona664 + */ + /* Bogus return value, not used anywhere */ + return 0; +} + +static void +sja1105pqrs_mac_config_cmd_packing(void *buf, struct sja1105_dyn_cmd *cmd, + enum packing_op op) +{ + const int size = SJA1105ET_SIZE_MAC_CONFIG_DYN_ENTRY; + u8 *p = buf + SJA1105PQRS_SIZE_MAC_CONFIG_ENTRY; + + sja1105_packing(p, &cmd->valid, 31, 31, size, op); + sja1105_packing(p, &cmd->errors, 30, 30, size, op); + sja1105_packing(p, &cmd->rdwrset, 29, 29, size, op); + sja1105_packing(p, &cmd->index, 2, 0, size, op); +} + +static void +sja1105et_l2_lookup_params_cmd_packing(void *buf, struct sja1105_dyn_cmd *cmd, + enum packing_op op) +{ + sja1105_packing(buf, &cmd->valid, 31, 31, + SJA1105ET_SIZE_L2_LOOKUP_PARAMS_DYN_CMD, op); +} + +static size_t +sja1105et_l2_lookup_params_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + struct sja1105_l2_lookup_params_entry *entry = entry_ptr; + + sja1105_packing(buf, &entry->poly, 7, 0, + SJA1105ET_SIZE_L2_LOOKUP_PARAMS_DYN_CMD, op); + /* Bogus return value, not used anywhere */ + return 0; +} + +static void +sja1105et_general_params_cmd_packing(void *buf, struct sja1105_dyn_cmd *cmd, + enum packing_op op) +{ + const int size = SJA1105ET_SIZE_GENERAL_PARAMS_DYN_CMD; + + sja1105_packing(buf, &cmd->valid, 31, 31, size, op); + sja1105_packing(buf, &cmd->errors, 30, 30, size, op); +} + +static size_t +sja1105et_general_params_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + struct sja1105_general_params_entry *entry = entry_ptr; + const int size = SJA1105ET_SIZE_GENERAL_PARAMS_DYN_CMD; + + sja1105_packing(buf, &entry->mirr_port, 2, 0, size, op); + /* Bogus return value, not used anywhere */ + return 0; +} + +#define OP_READ BIT(0) +#define OP_WRITE BIT(1) +#define OP_DEL BIT(2) + +/* SJA1105E/T: First generation */ +struct sja1105_dynamic_table_ops sja1105et_dyn_ops[BLK_IDX_MAX_DYN] = { + [BLK_IDX_L2_LOOKUP] = { + .entry_packing = sja1105et_l2_lookup_entry_packing, + .cmd_packing = sja1105et_l2_lookup_cmd_packing, + .access = (OP_READ | OP_WRITE | OP_DEL), + .max_entry_count = SJA1105_MAX_L2_LOOKUP_COUNT, + .packed_size = SJA1105ET_SIZE_L2_LOOKUP_DYN_CMD, + .addr = 0x20, + }, + [BLK_IDX_MGMT_ROUTE] = { + .entry_packing = sja1105et_mgmt_route_entry_packing, + .cmd_packing = sja1105et_mgmt_route_cmd_packing, + .access = (OP_READ | OP_WRITE), + .max_entry_count = SJA1105_NUM_PORTS, + .packed_size = SJA1105ET_SIZE_L2_LOOKUP_DYN_CMD, + .addr = 0x20, + }, + [BLK_IDX_L2_POLICING] = {0}, + [BLK_IDX_VLAN_LOOKUP] = { + .entry_packing = sja1105_vlan_lookup_entry_packing, + .cmd_packing = sja1105_vlan_lookup_cmd_packing, + .access = (OP_WRITE | OP_DEL), + .max_entry_count = SJA1105_MAX_VLAN_LOOKUP_COUNT, + .packed_size = SJA1105_SIZE_VLAN_LOOKUP_DYN_CMD, + .addr = 0x27, + }, + [BLK_IDX_L2_FORWARDING] = { + .entry_packing = sja1105_l2_forwarding_entry_packing, + .cmd_packing = sja1105_l2_forwarding_cmd_packing, + .max_entry_count = SJA1105_MAX_L2_FORWARDING_COUNT, + .access = OP_WRITE, + .packed_size = SJA1105_SIZE_L2_FORWARDING_DYN_CMD, + .addr = 0x24, + }, + [BLK_IDX_MAC_CONFIG] = { + .entry_packing = sja1105et_mac_config_entry_packing, + .cmd_packing = sja1105et_mac_config_cmd_packing, + .max_entry_count = SJA1105_MAX_MAC_CONFIG_COUNT, + .access = OP_WRITE, + .packed_size = SJA1105ET_SIZE_MAC_CONFIG_DYN_CMD, + .addr = 0x36, + }, + [BLK_IDX_L2_LOOKUP_PARAMS] = { + .entry_packing = sja1105et_l2_lookup_params_entry_packing, + .cmd_packing = sja1105et_l2_lookup_params_cmd_packing, + .max_entry_count = SJA1105_MAX_L2_LOOKUP_PARAMS_COUNT, + .access = OP_WRITE, + .packed_size = SJA1105ET_SIZE_L2_LOOKUP_PARAMS_DYN_CMD, + .addr = 0x38, + }, + [BLK_IDX_L2_FORWARDING_PARAMS] = {0}, + [BLK_IDX_GENERAL_PARAMS] = { + .entry_packing = sja1105et_general_params_entry_packing, + .cmd_packing = sja1105et_general_params_cmd_packing, + .max_entry_count = SJA1105_MAX_GENERAL_PARAMS_COUNT, + .access = OP_WRITE, + .packed_size = SJA1105ET_SIZE_GENERAL_PARAMS_DYN_CMD, + .addr = 0x34, + }, + [BLK_IDX_XMII_PARAMS] = {0}, +}; + +/* SJA1105P/Q/R/S: Second generation: TODO */ +struct sja1105_dynamic_table_ops sja1105pqrs_dyn_ops[BLK_IDX_MAX_DYN] = { + [BLK_IDX_L2_LOOKUP] = { + .entry_packing = sja1105pqrs_l2_lookup_entry_packing, + .cmd_packing = sja1105pqrs_l2_lookup_cmd_packing, + .access = (OP_READ | OP_WRITE | OP_DEL), + .max_entry_count = SJA1105_MAX_L2_LOOKUP_COUNT, + .packed_size = SJA1105ET_SIZE_L2_LOOKUP_DYN_CMD, + .addr = 0x24, + }, + [BLK_IDX_L2_POLICING] = {0}, + [BLK_IDX_VLAN_LOOKUP] = { + .entry_packing = sja1105_vlan_lookup_entry_packing, + .cmd_packing = sja1105_vlan_lookup_cmd_packing, + .access = (OP_READ | OP_WRITE | OP_DEL), + .max_entry_count = SJA1105_MAX_VLAN_LOOKUP_COUNT, + .packed_size = SJA1105_SIZE_VLAN_LOOKUP_DYN_CMD, + .addr = 0x2D, + }, + [BLK_IDX_L2_FORWARDING] = { + .entry_packing = sja1105_l2_forwarding_entry_packing, + .cmd_packing = sja1105_l2_forwarding_cmd_packing, + .max_entry_count = SJA1105_MAX_L2_FORWARDING_COUNT, + .access = OP_WRITE, + .packed_size = SJA1105_SIZE_L2_FORWARDING_DYN_CMD, + .addr = 0x2A, + }, + [BLK_IDX_MAC_CONFIG] = { + .entry_packing = sja1105pqrs_mac_config_entry_packing, + .cmd_packing = sja1105pqrs_mac_config_cmd_packing, + .max_entry_count = SJA1105_MAX_MAC_CONFIG_COUNT, + .access = (OP_READ | OP_WRITE), + .packed_size = SJA1105PQRS_SIZE_MAC_CONFIG_DYN_CMD, + .addr = 0x4B, + }, + [BLK_IDX_L2_LOOKUP_PARAMS] = { + .entry_packing = sja1105et_l2_lookup_params_entry_packing, + .cmd_packing = sja1105et_l2_lookup_params_cmd_packing, + .max_entry_count = SJA1105_MAX_L2_LOOKUP_PARAMS_COUNT, + .access = (OP_READ | OP_WRITE), + .packed_size = SJA1105ET_SIZE_L2_LOOKUP_PARAMS_DYN_CMD, + .addr = 0x38, + }, + [BLK_IDX_L2_FORWARDING_PARAMS] = {0}, + [BLK_IDX_GENERAL_PARAMS] = { + .entry_packing = sja1105et_general_params_entry_packing, + .cmd_packing = sja1105et_general_params_cmd_packing, + .max_entry_count = SJA1105_MAX_GENERAL_PARAMS_COUNT, + .access = OP_WRITE, + .packed_size = SJA1105ET_SIZE_GENERAL_PARAMS_DYN_CMD, + .addr = 0x34, + }, + [BLK_IDX_XMII_PARAMS] = {0}, +}; + +int sja1105_dynamic_config_read(struct sja1105_private *priv, + enum sja1105_blk_idx blk_idx, + int index, void *entry) +{ + const struct sja1105_dynamic_table_ops *ops; + struct sja1105_dyn_cmd cmd = {0}; + /* SPI payload buffer */ + u8 packed_buf[SJA1105_MAX_DYN_CMD_SIZE] = {0}; + int retries = 3; + int rc; + + if (blk_idx >= BLK_IDX_MAX_DYN) + return -ERANGE; + + ops = &priv->info->dyn_ops[blk_idx]; + + if (index >= ops->max_entry_count) + return -ERANGE; + if (!(ops->access & OP_READ)) + return -EOPNOTSUPP; + if (ops->packed_size > SJA1105_MAX_DYN_CMD_SIZE) + return -ERANGE; + if (!ops->cmd_packing) + return -EOPNOTSUPP; + if (!ops->entry_packing) + return -EOPNOTSUPP; + + cmd.valid = true; /* Trigger action on table entry */ + cmd.rdwrset = SPI_READ; /* Action is read */ + cmd.index = index; + ops->cmd_packing(packed_buf, &cmd, PACK); + + /* Send SPI write operation: read config table entry */ + rc = sja1105_spi_send_packed_buf(priv, SPI_WRITE, ops->addr, + packed_buf, ops->packed_size); + if (rc < 0) + return rc; + + /* Loop until we have confirmation that hardware has finished + * processing the command and has cleared the VALID field + */ + do { + memset(packed_buf, 0, ops->packed_size); + + /* Retrieve the read operation's result */ + rc = sja1105_spi_send_packed_buf(priv, SPI_READ, ops->addr, + packed_buf, ops->packed_size); + if (rc < 0) + return rc; + + cmd = (struct sja1105_dyn_cmd) {0}; + ops->cmd_packing(packed_buf, &cmd, UNPACK); + /* UM10944: [valident] will always be found cleared + * during a read access with MGMTROUTE set. + * So don't error out in that case. + */ + if (!cmd.valident && blk_idx != BLK_IDX_MGMT_ROUTE) + return -EINVAL; + cpu_relax(); + } while (cmd.valid && --retries); + + if (cmd.valid) + return -ETIMEDOUT; + + /* Don't dereference possibly NULL pointer - maybe caller + * only wanted to see whether the entry existed or not. + */ + if (entry) + ops->entry_packing(packed_buf, entry, UNPACK); + return 0; +} + +int sja1105_dynamic_config_write(struct sja1105_private *priv, + enum sja1105_blk_idx blk_idx, + int index, void *entry, bool keep) +{ + const struct sja1105_dynamic_table_ops *ops; + struct sja1105_dyn_cmd cmd = {0}; + /* SPI payload buffer */ + u8 packed_buf[SJA1105_MAX_DYN_CMD_SIZE] = {0}; + int rc; + + if (blk_idx >= BLK_IDX_MAX_DYN) + return -ERANGE; + + ops = &priv->info->dyn_ops[blk_idx]; + + if (index >= ops->max_entry_count) + return -ERANGE; + if (!(ops->access & OP_WRITE)) + return -EOPNOTSUPP; + if (!keep && !(ops->access & OP_DEL)) + return -EOPNOTSUPP; + if (ops->packed_size > SJA1105_MAX_DYN_CMD_SIZE) + return -ERANGE; + + cmd.valident = keep; /* If false, deletes entry */ + cmd.valid = true; /* Trigger action on table entry */ + cmd.rdwrset = SPI_WRITE; /* Action is write */ + cmd.index = index; + + if (!ops->cmd_packing) + return -EOPNOTSUPP; + ops->cmd_packing(packed_buf, &cmd, PACK); + + if (!ops->entry_packing) + return -EOPNOTSUPP; + /* Don't dereference potentially NULL pointer if just + * deleting a table entry is what was requested. For cases + * where 'index' field is physically part of entry structure, + * and needed here, we deal with that in the cmd_packing callback. + */ + if (keep) + ops->entry_packing(packed_buf, entry, PACK); + + /* Send SPI write operation: read config table entry */ + rc = sja1105_spi_send_packed_buf(priv, SPI_WRITE, ops->addr, + packed_buf, ops->packed_size); + if (rc < 0) + return rc; + + cmd = (struct sja1105_dyn_cmd) {0}; + ops->cmd_packing(packed_buf, &cmd, UNPACK); + if (cmd.errors) + return -EINVAL; + + return 0; +} + +static u8 sja1105_crc8_add(u8 crc, u8 byte, u8 poly) +{ + int i; + + for (i = 0; i < 8; i++) { + if ((crc ^ byte) & (1 << 7)) { + crc <<= 1; + crc ^= poly; + } else { + crc <<= 1; + } + byte <<= 1; + } + return crc; +} + +/* CRC8 algorithm with non-reversed input, non-reversed output, + * no input xor and no output xor. Code customized for receiving + * the SJA1105 E/T FDB keys (vlanid, macaddr) as input. CRC polynomial + * is also received as argument in the Koopman notation that the switch + * hardware stores it in. + */ +u8 sja1105_fdb_hash(struct sja1105_private *priv, const u8 *addr, u16 vid) +{ + struct sja1105_l2_lookup_params_entry *l2_lookup_params = + priv->static_config.tables[BLK_IDX_L2_LOOKUP_PARAMS].entries; + u64 poly_koopman = l2_lookup_params->poly; + /* Convert polynomial from Koopman to 'normal' notation */ + u8 poly = (u8)(1 + (poly_koopman << 1)); + u64 vlanid = l2_lookup_params->shared_learn ? 0 : vid; + u64 input = (vlanid << 48) | ether_addr_to_u64(addr); + u8 crc = 0; /* seed */ + int i; + + /* Mask the eight bytes starting from MSB one at a time */ + for (i = 56; i >= 0; i -= 8) { + u8 byte = (input & (0xffull << i)) >> i; + + crc = sja1105_crc8_add(crc, byte, poly); + } + return crc; +} diff --git a/drivers/net/dsa/sja1105/sja1105_dynamic_config.h b/drivers/net/dsa/sja1105/sja1105_dynamic_config.h new file mode 100644 index 000000000000..77be59546a55 --- /dev/null +++ b/drivers/net/dsa/sja1105/sja1105_dynamic_config.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0 + * Copyright (c) 2019, Vladimir Oltean <olteanv@gmail.com> + */ +#ifndef _SJA1105_DYNAMIC_CONFIG_H +#define _SJA1105_DYNAMIC_CONFIG_H + +#include "sja1105.h" +#include <linux/packing.h> + +struct sja1105_dyn_cmd { + u64 valid; + u64 rdwrset; + u64 errors; + u64 valident; + u64 index; +}; + +struct sja1105_dynamic_table_ops { + /* This returns size_t just to keep same prototype as the + * static config ops, of which we are reusing some functions. + */ + size_t (*entry_packing)(void *buf, void *entry_ptr, enum packing_op op); + void (*cmd_packing)(void *buf, struct sja1105_dyn_cmd *cmd, + enum packing_op op); + size_t max_entry_count; + size_t packed_size; + u64 addr; + u8 access; +}; + +struct sja1105_mgmt_entry { + u64 tsreg; + u64 takets; + u64 macaddr; + u64 destports; + u64 enfport; + u64 index; +}; + +extern struct sja1105_dynamic_table_ops sja1105et_dyn_ops[BLK_IDX_MAX_DYN]; +extern struct sja1105_dynamic_table_ops sja1105pqrs_dyn_ops[BLK_IDX_MAX_DYN]; + +#endif diff --git a/drivers/net/dsa/sja1105/sja1105_ethtool.c b/drivers/net/dsa/sja1105/sja1105_ethtool.c new file mode 100644 index 000000000000..ab581a28cd41 --- /dev/null +++ b/drivers/net/dsa/sja1105/sja1105_ethtool.c @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2018-2019, Vladimir Oltean <olteanv@gmail.com> + */ +#include "sja1105.h" + +#define SJA1105_SIZE_MAC_AREA (0x02 * 4) +#define SJA1105_SIZE_HL1_AREA (0x10 * 4) +#define SJA1105_SIZE_HL2_AREA (0x4 * 4) +#define SJA1105_SIZE_QLEVEL_AREA (0x8 * 4) /* 0x4 to 0xB */ + +struct sja1105_port_status_mac { + u64 n_runt; + u64 n_soferr; + u64 n_alignerr; + u64 n_miierr; + u64 typeerr; + u64 sizeerr; + u64 tctimeout; + u64 priorerr; + u64 nomaster; + u64 memov; + u64 memerr; + u64 invtyp; + u64 intcyov; + u64 domerr; + u64 pcfbagdrop; + u64 spcprior; + u64 ageprior; + u64 portdrop; + u64 lendrop; + u64 bagdrop; + u64 policeerr; + u64 drpnona664err; + u64 spcerr; + u64 agedrp; +}; + +struct sja1105_port_status_hl1 { + u64 n_n664err; + u64 n_vlanerr; + u64 n_unreleased; + u64 n_sizeerr; + u64 n_crcerr; + u64 n_vlnotfound; + u64 n_ctpolerr; + u64 n_polerr; + u64 n_rxfrmsh; + u64 n_rxfrm; + u64 n_rxbytesh; + u64 n_rxbyte; + u64 n_txfrmsh; + u64 n_txfrm; + u64 n_txbytesh; + u64 n_txbyte; +}; + +struct sja1105_port_status_hl2 { + u64 n_qfull; + u64 n_part_drop; + u64 n_egr_disabled; + u64 n_not_reach; + u64 qlevel_hwm[8]; /* Only for P/Q/R/S */ + u64 qlevel[8]; /* Only for P/Q/R/S */ +}; + +struct sja1105_port_status { + struct sja1105_port_status_mac mac; + struct sja1105_port_status_hl1 hl1; + struct sja1105_port_status_hl2 hl2; +}; + +static void +sja1105_port_status_mac_unpack(void *buf, + struct sja1105_port_status_mac *status) +{ + /* Make pointer arithmetic work on 4 bytes */ + u32 *p = buf; + + sja1105_unpack(p + 0x0, &status->n_runt, 31, 24, 4); + sja1105_unpack(p + 0x0, &status->n_soferr, 23, 16, 4); + sja1105_unpack(p + 0x0, &status->n_alignerr, 15, 8, 4); + sja1105_unpack(p + 0x0, &status->n_miierr, 7, 0, 4); + sja1105_unpack(p + 0x1, &status->typeerr, 27, 27, 4); + sja1105_unpack(p + 0x1, &status->sizeerr, 26, 26, 4); + sja1105_unpack(p + 0x1, &status->tctimeout, 25, 25, 4); + sja1105_unpack(p + 0x1, &status->priorerr, 24, 24, 4); + sja1105_unpack(p + 0x1, &status->nomaster, 23, 23, 4); + sja1105_unpack(p + 0x1, &status->memov, 22, 22, 4); + sja1105_unpack(p + 0x1, &status->memerr, 21, 21, 4); + sja1105_unpack(p + 0x1, &status->invtyp, 19, 19, 4); + sja1105_unpack(p + 0x1, &status->intcyov, 18, 18, 4); + sja1105_unpack(p + 0x1, &status->domerr, 17, 17, 4); + sja1105_unpack(p + 0x1, &status->pcfbagdrop, 16, 16, 4); + sja1105_unpack(p + 0x1, &status->spcprior, 15, 12, 4); + sja1105_unpack(p + 0x1, &status->ageprior, 11, 8, 4); + sja1105_unpack(p + 0x1, &status->portdrop, 6, 6, 4); + sja1105_unpack(p + 0x1, &status->lendrop, 5, 5, 4); + sja1105_unpack(p + 0x1, &status->bagdrop, 4, 4, 4); + sja1105_unpack(p + 0x1, &status->policeerr, 3, 3, 4); + sja1105_unpack(p + 0x1, &status->drpnona664err, 2, 2, 4); + sja1105_unpack(p + 0x1, &status->spcerr, 1, 1, 4); + sja1105_unpack(p + 0x1, &status->agedrp, 0, 0, 4); +} + +static void +sja1105_port_status_hl1_unpack(void *buf, + struct sja1105_port_status_hl1 *status) +{ + /* Make pointer arithmetic work on 4 bytes */ + u32 *p = buf; + + sja1105_unpack(p + 0xF, &status->n_n664err, 31, 0, 4); + sja1105_unpack(p + 0xE, &status->n_vlanerr, 31, 0, 4); + sja1105_unpack(p + 0xD, &status->n_unreleased, 31, 0, 4); + sja1105_unpack(p + 0xC, &status->n_sizeerr, 31, 0, 4); + sja1105_unpack(p + 0xB, &status->n_crcerr, 31, 0, 4); + sja1105_unpack(p + 0xA, &status->n_vlnotfound, 31, 0, 4); + sja1105_unpack(p + 0x9, &status->n_ctpolerr, 31, 0, 4); + sja1105_unpack(p + 0x8, &status->n_polerr, 31, 0, 4); + sja1105_unpack(p + 0x7, &status->n_rxfrmsh, 31, 0, 4); + sja1105_unpack(p + 0x6, &status->n_rxfrm, 31, 0, 4); + sja1105_unpack(p + 0x5, &status->n_rxbytesh, 31, 0, 4); + sja1105_unpack(p + 0x4, &status->n_rxbyte, 31, 0, 4); + sja1105_unpack(p + 0x3, &status->n_txfrmsh, 31, 0, 4); + sja1105_unpack(p + 0x2, &status->n_txfrm, 31, 0, 4); + sja1105_unpack(p + 0x1, &status->n_txbytesh, 31, 0, 4); + sja1105_unpack(p + 0x0, &status->n_txbyte, 31, 0, 4); + status->n_rxfrm += status->n_rxfrmsh << 32; + status->n_rxbyte += status->n_rxbytesh << 32; + status->n_txfrm += status->n_txfrmsh << 32; + status->n_txbyte += status->n_txbytesh << 32; +} + +static void +sja1105_port_status_hl2_unpack(void *buf, + struct sja1105_port_status_hl2 *status) +{ + /* Make pointer arithmetic work on 4 bytes */ + u32 *p = buf; + + sja1105_unpack(p + 0x3, &status->n_qfull, 31, 0, 4); + sja1105_unpack(p + 0x2, &status->n_part_drop, 31, 0, 4); + sja1105_unpack(p + 0x1, &status->n_egr_disabled, 31, 0, 4); + sja1105_unpack(p + 0x0, &status->n_not_reach, 31, 0, 4); +} + +static void +sja1105pqrs_port_status_qlevel_unpack(void *buf, + struct sja1105_port_status_hl2 *status) +{ + /* Make pointer arithmetic work on 4 bytes */ + u32 *p = buf; + int i; + + for (i = 0; i < 8; i++) { + sja1105_unpack(p + i, &status->qlevel_hwm[i], 24, 16, 4); + sja1105_unpack(p + i, &status->qlevel[i], 8, 0, 4); + } +} + +static int sja1105_port_status_get_mac(struct sja1105_private *priv, + struct sja1105_port_status_mac *status, + int port) +{ + const struct sja1105_regs *regs = priv->info->regs; + u8 packed_buf[SJA1105_SIZE_MAC_AREA] = {0}; + int rc; + + /* MAC area */ + rc = sja1105_spi_send_packed_buf(priv, SPI_READ, regs->mac[port], + packed_buf, SJA1105_SIZE_MAC_AREA); + if (rc < 0) + return rc; + + sja1105_port_status_mac_unpack(packed_buf, status); + + return 0; +} + +static int sja1105_port_status_get_hl1(struct sja1105_private *priv, + struct sja1105_port_status_hl1 *status, + int port) +{ + const struct sja1105_regs *regs = priv->info->regs; + u8 packed_buf[SJA1105_SIZE_HL1_AREA] = {0}; + int rc; + + rc = sja1105_spi_send_packed_buf(priv, SPI_READ, regs->mac_hl1[port], + packed_buf, SJA1105_SIZE_HL1_AREA); + if (rc < 0) + return rc; + + sja1105_port_status_hl1_unpack(packed_buf, status); + + return 0; +} + +static int sja1105_port_status_get_hl2(struct sja1105_private *priv, + struct sja1105_port_status_hl2 *status, + int port) +{ + const struct sja1105_regs *regs = priv->info->regs; + u8 packed_buf[SJA1105_SIZE_QLEVEL_AREA] = {0}; + int rc; + + rc = sja1105_spi_send_packed_buf(priv, SPI_READ, regs->mac_hl2[port], + packed_buf, SJA1105_SIZE_HL2_AREA); + if (rc < 0) + return rc; + + sja1105_port_status_hl2_unpack(packed_buf, status); + + /* Code below is strictly P/Q/R/S specific. */ + if (priv->info->device_id == SJA1105E_DEVICE_ID || + priv->info->device_id == SJA1105T_DEVICE_ID) + return 0; + + rc = sja1105_spi_send_packed_buf(priv, SPI_READ, regs->qlevel[port], + packed_buf, SJA1105_SIZE_QLEVEL_AREA); + if (rc < 0) + return rc; + + sja1105pqrs_port_status_qlevel_unpack(packed_buf, status); + + return 0; +} + +static int sja1105_port_status_get(struct sja1105_private *priv, + struct sja1105_port_status *status, + int port) +{ + int rc; + + rc = sja1105_port_status_get_mac(priv, &status->mac, port); + if (rc < 0) + return rc; + rc = sja1105_port_status_get_hl1(priv, &status->hl1, port); + if (rc < 0) + return rc; + rc = sja1105_port_status_get_hl2(priv, &status->hl2, port); + if (rc < 0) + return rc; + + return 0; +} + +static char sja1105_port_stats[][ETH_GSTRING_LEN] = { + /* MAC-Level Diagnostic Counters */ + "n_runt", + "n_soferr", + "n_alignerr", + "n_miierr", + /* MAC-Level Diagnostic Flags */ + "typeerr", + "sizeerr", + "tctimeout", + "priorerr", + "nomaster", + "memov", + "memerr", + "invtyp", + "intcyov", + "domerr", + "pcfbagdrop", + "spcprior", + "ageprior", + "portdrop", + "lendrop", + "bagdrop", + "policeerr", + "drpnona664err", + "spcerr", + "agedrp", + /* High-Level Diagnostic Counters */ + "n_n664err", + "n_vlanerr", + "n_unreleased", + "n_sizeerr", + "n_crcerr", + "n_vlnotfound", + "n_ctpolerr", + "n_polerr", + "n_rxfrm", + "n_rxbyte", + "n_txfrm", + "n_txbyte", + "n_qfull", + "n_part_drop", + "n_egr_disabled", + "n_not_reach", +}; + +static char sja1105pqrs_extra_port_stats[][ETH_GSTRING_LEN] = { + /* Queue Levels */ + "qlevel_hwm_0", + "qlevel_hwm_1", + "qlevel_hwm_2", + "qlevel_hwm_3", + "qlevel_hwm_4", + "qlevel_hwm_5", + "qlevel_hwm_6", + "qlevel_hwm_7", + "qlevel_0", + "qlevel_1", + "qlevel_2", + "qlevel_3", + "qlevel_4", + "qlevel_5", + "qlevel_6", + "qlevel_7", +}; + +void sja1105_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *data) +{ + struct sja1105_private *priv = ds->priv; + struct sja1105_port_status status; + int rc, i, k = 0; + + memset(&status, 0, sizeof(status)); + + rc = sja1105_port_status_get(priv, &status, port); + if (rc < 0) { + dev_err(ds->dev, "Failed to read port %d counters: %d\n", + port, rc); + return; + } + memset(data, 0, ARRAY_SIZE(sja1105_port_stats) * sizeof(u64)); + data[k++] = status.mac.n_runt; + data[k++] = status.mac.n_soferr; + data[k++] = status.mac.n_alignerr; + data[k++] = status.mac.n_miierr; + data[k++] = status.mac.typeerr; + data[k++] = status.mac.sizeerr; + data[k++] = status.mac.tctimeout; + data[k++] = status.mac.priorerr; + data[k++] = status.mac.nomaster; + data[k++] = status.mac.memov; + data[k++] = status.mac.memerr; + data[k++] = status.mac.invtyp; + data[k++] = status.mac.intcyov; + data[k++] = status.mac.domerr; + data[k++] = status.mac.pcfbagdrop; + data[k++] = status.mac.spcprior; + data[k++] = status.mac.ageprior; + data[k++] = status.mac.portdrop; + data[k++] = status.mac.lendrop; + data[k++] = status.mac.bagdrop; + data[k++] = status.mac.policeerr; + data[k++] = status.mac.drpnona664err; + data[k++] = status.mac.spcerr; + data[k++] = status.mac.agedrp; + data[k++] = status.hl1.n_n664err; + data[k++] = status.hl1.n_vlanerr; + data[k++] = status.hl1.n_unreleased; + data[k++] = status.hl1.n_sizeerr; + data[k++] = status.hl1.n_crcerr; + data[k++] = status.hl1.n_vlnotfound; + data[k++] = status.hl1.n_ctpolerr; + data[k++] = status.hl1.n_polerr; + data[k++] = status.hl1.n_rxfrm; + data[k++] = status.hl1.n_rxbyte; + data[k++] = status.hl1.n_txfrm; + data[k++] = status.hl1.n_txbyte; + data[k++] = status.hl2.n_qfull; + data[k++] = status.hl2.n_part_drop; + data[k++] = status.hl2.n_egr_disabled; + data[k++] = status.hl2.n_not_reach; + + if (priv->info->device_id == SJA1105E_DEVICE_ID || + priv->info->device_id == SJA1105T_DEVICE_ID) + return; + + memset(data + k, 0, ARRAY_SIZE(sja1105pqrs_extra_port_stats) * + sizeof(u64)); + for (i = 0; i < 8; i++) { + data[k++] = status.hl2.qlevel_hwm[i]; + data[k++] = status.hl2.qlevel[i]; + } +} + +void sja1105_get_strings(struct dsa_switch *ds, int port, + u32 stringset, u8 *data) +{ + struct sja1105_private *priv = ds->priv; + u8 *p = data; + int i; + + switch (stringset) { + case ETH_SS_STATS: + for (i = 0; i < ARRAY_SIZE(sja1105_port_stats); i++) { + strlcpy(p, sja1105_port_stats[i], ETH_GSTRING_LEN); + p += ETH_GSTRING_LEN; + } + if (priv->info->device_id == SJA1105E_DEVICE_ID || + priv->info->device_id == SJA1105T_DEVICE_ID) + return; + for (i = 0; i < ARRAY_SIZE(sja1105pqrs_extra_port_stats); i++) { + strlcpy(p, sja1105pqrs_extra_port_stats[i], + ETH_GSTRING_LEN); + p += ETH_GSTRING_LEN; + } + break; + } +} + +int sja1105_get_sset_count(struct dsa_switch *ds, int port, int sset) +{ + int count = ARRAY_SIZE(sja1105_port_stats); + struct sja1105_private *priv = ds->priv; + + if (sset != ETH_SS_STATS) + return -EOPNOTSUPP; + + if (priv->info->device_id == SJA1105PR_DEVICE_ID || + priv->info->device_id == SJA1105QS_DEVICE_ID) + count += ARRAY_SIZE(sja1105pqrs_extra_port_stats); + + return count; +} diff --git a/drivers/net/dsa/sja1105/sja1105_main.c b/drivers/net/dsa/sja1105/sja1105_main.c new file mode 100644 index 000000000000..0663b78a2f6c --- /dev/null +++ b/drivers/net/dsa/sja1105/sja1105_main.c @@ -0,0 +1,1679 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2018, Sensor-Technik Wiedemann GmbH + * Copyright (c) 2018-2019, Vladimir Oltean <olteanv@gmail.com> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/printk.h> +#include <linux/spi/spi.h> +#include <linux/errno.h> +#include <linux/gpio/consumer.h> +#include <linux/phylink.h> +#include <linux/of.h> +#include <linux/of_net.h> +#include <linux/of_mdio.h> +#include <linux/of_device.h> +#include <linux/netdev_features.h> +#include <linux/netdevice.h> +#include <linux/if_bridge.h> +#include <linux/if_ether.h> +#include <linux/dsa/8021q.h> +#include "sja1105.h" + +static void sja1105_hw_reset(struct gpio_desc *gpio, unsigned int pulse_len, + unsigned int startup_delay) +{ + gpiod_set_value_cansleep(gpio, 1); + /* Wait for minimum reset pulse length */ + msleep(pulse_len); + gpiod_set_value_cansleep(gpio, 0); + /* Wait until chip is ready after reset */ + msleep(startup_delay); +} + +static void +sja1105_port_allow_traffic(struct sja1105_l2_forwarding_entry *l2_fwd, + int from, int to, bool allow) +{ + if (allow) { + l2_fwd[from].bc_domain |= BIT(to); + l2_fwd[from].reach_port |= BIT(to); + l2_fwd[from].fl_domain |= BIT(to); + } else { + l2_fwd[from].bc_domain &= ~BIT(to); + l2_fwd[from].reach_port &= ~BIT(to); + l2_fwd[from].fl_domain &= ~BIT(to); + } +} + +/* Structure used to temporarily transport device tree + * settings into sja1105_setup + */ +struct sja1105_dt_port { + phy_interface_t phy_mode; + sja1105_mii_role_t role; +}; + +static int sja1105_init_mac_settings(struct sja1105_private *priv) +{ + struct sja1105_mac_config_entry default_mac = { + /* Enable all 8 priority queues on egress. + * Every queue i holds top[i] - base[i] frames. + * Sum of top[i] - base[i] is 511 (max hardware limit). + */ + .top = {0x3F, 0x7F, 0xBF, 0xFF, 0x13F, 0x17F, 0x1BF, 0x1FF}, + .base = {0x0, 0x40, 0x80, 0xC0, 0x100, 0x140, 0x180, 0x1C0}, + .enabled = {true, true, true, true, true, true, true, true}, + /* Keep standard IFG of 12 bytes on egress. */ + .ifg = 0, + /* Always put the MAC speed in automatic mode, where it can be + * retrieved from the PHY object through phylib and + * sja1105_adjust_port_config. + */ + .speed = SJA1105_SPEED_AUTO, + /* No static correction for 1-step 1588 events */ + .tp_delin = 0, + .tp_delout = 0, + /* Disable aging for critical TTEthernet traffic */ + .maxage = 0xFF, + /* Internal VLAN (pvid) to apply to untagged ingress */ + .vlanprio = 0, + .vlanid = 0, + .ing_mirr = false, + .egr_mirr = false, + /* Don't drop traffic with other EtherType than ETH_P_IP */ + .drpnona664 = false, + /* Don't drop double-tagged traffic */ + .drpdtag = false, + /* Don't drop untagged traffic */ + .drpuntag = false, + /* Don't retag 802.1p (VID 0) traffic with the pvid */ + .retag = false, + /* Disable learning and I/O on user ports by default - + * STP will enable it. + */ + .dyn_learn = false, + .egress = false, + .ingress = false, + }; + struct sja1105_mac_config_entry *mac; + struct sja1105_table *table; + int i; + + table = &priv->static_config.tables[BLK_IDX_MAC_CONFIG]; + + /* Discard previous MAC Configuration Table */ + if (table->entry_count) { + kfree(table->entries); + table->entry_count = 0; + } + + table->entries = kcalloc(SJA1105_NUM_PORTS, + table->ops->unpacked_entry_size, GFP_KERNEL); + if (!table->entries) + return -ENOMEM; + + /* Override table based on phylib DT bindings */ + table->entry_count = SJA1105_NUM_PORTS; + + mac = table->entries; + + for (i = 0; i < SJA1105_NUM_PORTS; i++) { + mac[i] = default_mac; + if (i == dsa_upstream_port(priv->ds, i)) { + /* STP doesn't get called for CPU port, so we need to + * set the I/O parameters statically. + */ + mac[i].dyn_learn = true; + mac[i].ingress = true; + mac[i].egress = true; + } + } + + return 0; +} + +static int sja1105_init_mii_settings(struct sja1105_private *priv, + struct sja1105_dt_port *ports) +{ + struct device *dev = &priv->spidev->dev; + struct sja1105_xmii_params_entry *mii; + struct sja1105_table *table; + int i; + + table = &priv->static_config.tables[BLK_IDX_XMII_PARAMS]; + + /* Discard previous xMII Mode Parameters Table */ + if (table->entry_count) { + kfree(table->entries); + table->entry_count = 0; + } + + table->entries = kcalloc(SJA1105_MAX_XMII_PARAMS_COUNT, + table->ops->unpacked_entry_size, GFP_KERNEL); + if (!table->entries) + return -ENOMEM; + + /* Override table based on phylib DT bindings */ + table->entry_count = SJA1105_MAX_XMII_PARAMS_COUNT; + + mii = table->entries; + + for (i = 0; i < SJA1105_NUM_PORTS; i++) { + switch (ports[i].phy_mode) { + case PHY_INTERFACE_MODE_MII: + mii->xmii_mode[i] = XMII_MODE_MII; + break; + case PHY_INTERFACE_MODE_RMII: + mii->xmii_mode[i] = XMII_MODE_RMII; + break; + case PHY_INTERFACE_MODE_RGMII: + case PHY_INTERFACE_MODE_RGMII_ID: + case PHY_INTERFACE_MODE_RGMII_RXID: + case PHY_INTERFACE_MODE_RGMII_TXID: + mii->xmii_mode[i] = XMII_MODE_RGMII; + break; + default: + dev_err(dev, "Unsupported PHY mode %s!\n", + phy_modes(ports[i].phy_mode)); + } + + mii->phy_mac[i] = ports[i].role; + } + return 0; +} + +static int sja1105_init_static_fdb(struct sja1105_private *priv) +{ + struct sja1105_table *table; + + table = &priv->static_config.tables[BLK_IDX_L2_LOOKUP]; + + /* We only populate the FDB table through dynamic + * L2 Address Lookup entries + */ + if (table->entry_count) { + kfree(table->entries); + table->entry_count = 0; + } + return 0; +} + +static int sja1105_init_l2_lookup_params(struct sja1105_private *priv) +{ + struct sja1105_table *table; + struct sja1105_l2_lookup_params_entry default_l2_lookup_params = { + /* Learned FDB entries are forgotten after 300 seconds */ + .maxage = SJA1105_AGEING_TIME_MS(300000), + /* All entries within a FDB bin are available for learning */ + .dyn_tbsz = SJA1105ET_FDB_BIN_SIZE, + /* 2^8 + 2^5 + 2^3 + 2^2 + 2^1 + 1 in Koopman notation */ + .poly = 0x97, + /* This selects between Independent VLAN Learning (IVL) and + * Shared VLAN Learning (SVL) + */ + .shared_learn = false, + /* Don't discard management traffic based on ENFPORT - + * we don't perform SMAC port enforcement anyway, so + * what we are setting here doesn't matter. + */ + .no_enf_hostprt = false, + /* Don't learn SMAC for mac_fltres1 and mac_fltres0. + * Maybe correlate with no_linklocal_learn from bridge driver? + */ + .no_mgmt_learn = true, + }; + + table = &priv->static_config.tables[BLK_IDX_L2_LOOKUP_PARAMS]; + + if (table->entry_count) { + kfree(table->entries); + table->entry_count = 0; + } + + table->entries = kcalloc(SJA1105_MAX_L2_LOOKUP_PARAMS_COUNT, + table->ops->unpacked_entry_size, GFP_KERNEL); + if (!table->entries) + return -ENOMEM; + + table->entry_count = SJA1105_MAX_L2_LOOKUP_PARAMS_COUNT; + + /* This table only has a single entry */ + ((struct sja1105_l2_lookup_params_entry *)table->entries)[0] = + default_l2_lookup_params; + + return 0; +} + +static int sja1105_init_static_vlan(struct sja1105_private *priv) +{ + struct sja1105_table *table; + struct sja1105_vlan_lookup_entry pvid = { + .ving_mirr = 0, + .vegr_mirr = 0, + .vmemb_port = 0, + .vlan_bc = 0, + .tag_port = 0, + .vlanid = 0, + }; + int i; + + table = &priv->static_config.tables[BLK_IDX_VLAN_LOOKUP]; + + /* The static VLAN table will only contain the initial pvid of 0. + * All other VLANs are to be configured through dynamic entries, + * and kept in the static configuration table as backing memory. + * The pvid of 0 is sufficient to pass traffic while the ports are + * standalone and when vlan_filtering is disabled. When filtering + * gets enabled, the switchdev core sets up the VLAN ID 1 and sets + * it as the new pvid. Actually 'pvid 1' still comes up in 'bridge + * vlan' even when vlan_filtering is off, but it has no effect. + */ + if (table->entry_count) { + kfree(table->entries); + table->entry_count = 0; + } + + table->entries = kcalloc(1, table->ops->unpacked_entry_size, + GFP_KERNEL); + if (!table->entries) + return -ENOMEM; + + table->entry_count = 1; + + /* VLAN ID 0: all DT-defined ports are members; no restrictions on + * forwarding; always transmit priority-tagged frames as untagged. + */ + for (i = 0; i < SJA1105_NUM_PORTS; i++) { + pvid.vmemb_port |= BIT(i); + pvid.vlan_bc |= BIT(i); + pvid.tag_port &= ~BIT(i); + } + + ((struct sja1105_vlan_lookup_entry *)table->entries)[0] = pvid; + return 0; +} + +static int sja1105_init_l2_forwarding(struct sja1105_private *priv) +{ + struct sja1105_l2_forwarding_entry *l2fwd; + struct sja1105_table *table; + int i, j; + + table = &priv->static_config.tables[BLK_IDX_L2_FORWARDING]; + + if (table->entry_count) { + kfree(table->entries); + table->entry_count = 0; + } + + table->entries = kcalloc(SJA1105_MAX_L2_FORWARDING_COUNT, + table->ops->unpacked_entry_size, GFP_KERNEL); + if (!table->entries) + return -ENOMEM; + + table->entry_count = SJA1105_MAX_L2_FORWARDING_COUNT; + + l2fwd = table->entries; + + /* First 5 entries define the forwarding rules */ + for (i = 0; i < SJA1105_NUM_PORTS; i++) { + unsigned int upstream = dsa_upstream_port(priv->ds, i); + + for (j = 0; j < SJA1105_NUM_TC; j++) + l2fwd[i].vlan_pmap[j] = j; + + if (i == upstream) + continue; + + sja1105_port_allow_traffic(l2fwd, i, upstream, true); + sja1105_port_allow_traffic(l2fwd, upstream, i, true); + } + /* Next 8 entries define VLAN PCP mapping from ingress to egress. + * Create a one-to-one mapping. + */ + for (i = 0; i < SJA1105_NUM_TC; i++) + for (j = 0; j < SJA1105_NUM_PORTS; j++) + l2fwd[SJA1105_NUM_PORTS + i].vlan_pmap[j] = i; + + return 0; +} + +static int sja1105_init_l2_forwarding_params(struct sja1105_private *priv) +{ + struct sja1105_l2_forwarding_params_entry default_l2fwd_params = { + /* Disallow dynamic reconfiguration of vlan_pmap */ + .max_dynp = 0, + /* Use a single memory partition for all ingress queues */ + .part_spc = { SJA1105_MAX_FRAME_MEMORY, 0, 0, 0, 0, 0, 0, 0 }, + }; + struct sja1105_table *table; + + table = &priv->static_config.tables[BLK_IDX_L2_FORWARDING_PARAMS]; + + if (table->entry_count) { + kfree(table->entries); + table->entry_count = 0; + } + + table->entries = kcalloc(SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT, + table->ops->unpacked_entry_size, GFP_KERNEL); + if (!table->entries) + return -ENOMEM; + + table->entry_count = SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT; + + /* This table only has a single entry */ + ((struct sja1105_l2_forwarding_params_entry *)table->entries)[0] = + default_l2fwd_params; + + return 0; +} + +static int sja1105_init_general_params(struct sja1105_private *priv) +{ + struct sja1105_general_params_entry default_general_params = { + /* Disallow dynamic changing of the mirror port */ + .mirr_ptacu = 0, + .switchid = priv->ds->index, + /* Priority queue for link-local frames trapped to CPU */ + .hostprio = 0, + .mac_fltres1 = SJA1105_LINKLOCAL_FILTER_A, + .mac_flt1 = SJA1105_LINKLOCAL_FILTER_A_MASK, + .incl_srcpt1 = true, + .send_meta1 = false, + .mac_fltres0 = SJA1105_LINKLOCAL_FILTER_B, + .mac_flt0 = SJA1105_LINKLOCAL_FILTER_B_MASK, + .incl_srcpt0 = true, + .send_meta0 = false, + /* The destination for traffic matching mac_fltres1 and + * mac_fltres0 on all ports except host_port. Such traffic + * receieved on host_port itself would be dropped, except + * by installing a temporary 'management route' + */ + .host_port = dsa_upstream_port(priv->ds, 0), + /* Same as host port */ + .mirr_port = dsa_upstream_port(priv->ds, 0), + /* Link-local traffic received on casc_port will be forwarded + * to host_port without embedding the source port and device ID + * info in the destination MAC address (presumably because it + * is a cascaded port and a downstream SJA switch already did + * that). Default to an invalid port (to disable the feature) + * and overwrite this if we find any DSA (cascaded) ports. + */ + .casc_port = SJA1105_NUM_PORTS, + /* No TTEthernet */ + .vllupformat = 0, + .vlmarker = 0, + .vlmask = 0, + /* Only update correctionField for 1-step PTP (L2 transport) */ + .ignore2stf = 0, + /* Forcefully disable VLAN filtering by telling + * the switch that VLAN has a different EtherType. + */ + .tpid = ETH_P_SJA1105, + .tpid2 = ETH_P_SJA1105, + }; + struct sja1105_table *table; + int i, k = 0; + + for (i = 0; i < SJA1105_NUM_PORTS; i++) { + if (dsa_is_dsa_port(priv->ds, i)) + default_general_params.casc_port = i; + else if (dsa_is_user_port(priv->ds, i)) + priv->ports[i].mgmt_slot = k++; + } + + table = &priv->static_config.tables[BLK_IDX_GENERAL_PARAMS]; + + if (table->entry_count) { + kfree(table->entries); + table->entry_count = 0; + } + + table->entries = kcalloc(SJA1105_MAX_GENERAL_PARAMS_COUNT, + table->ops->unpacked_entry_size, GFP_KERNEL); + if (!table->entries) + return -ENOMEM; + + table->entry_count = SJA1105_MAX_GENERAL_PARAMS_COUNT; + + /* This table only has a single entry */ + ((struct sja1105_general_params_entry *)table->entries)[0] = + default_general_params; + + return 0; +} + +#define SJA1105_RATE_MBPS(speed) (((speed) * 64000) / 1000) + +static inline void +sja1105_setup_policer(struct sja1105_l2_policing_entry *policing, + int index) +{ + policing[index].sharindx = index; + policing[index].smax = 65535; /* Burst size in bytes */ + policing[index].rate = SJA1105_RATE_MBPS(1000); + policing[index].maxlen = ETH_FRAME_LEN + VLAN_HLEN + ETH_FCS_LEN; + policing[index].partition = 0; +} + +static int sja1105_init_l2_policing(struct sja1105_private *priv) +{ + struct sja1105_l2_policing_entry *policing; + struct sja1105_table *table; + int i, j, k; + + table = &priv->static_config.tables[BLK_IDX_L2_POLICING]; + + /* Discard previous L2 Policing Table */ + if (table->entry_count) { + kfree(table->entries); + table->entry_count = 0; + } + + table->entries = kcalloc(SJA1105_MAX_L2_POLICING_COUNT, + table->ops->unpacked_entry_size, GFP_KERNEL); + if (!table->entries) + return -ENOMEM; + + table->entry_count = SJA1105_MAX_L2_POLICING_COUNT; + + policing = table->entries; + + /* k sweeps through all unicast policers (0-39). + * bcast sweeps through policers 40-44. + */ + for (i = 0, k = 0; i < SJA1105_NUM_PORTS; i++) { + int bcast = (SJA1105_NUM_PORTS * SJA1105_NUM_TC) + i; + + for (j = 0; j < SJA1105_NUM_TC; j++, k++) + sja1105_setup_policer(policing, k); + + /* Set up this port's policer for broadcast traffic */ + sja1105_setup_policer(policing, bcast); + } + return 0; +} + +static int sja1105_static_config_load(struct sja1105_private *priv, + struct sja1105_dt_port *ports) +{ + int rc; + + sja1105_static_config_free(&priv->static_config); + rc = sja1105_static_config_init(&priv->static_config, + priv->info->static_ops, + priv->info->device_id); + if (rc) + return rc; + + /* Build static configuration */ + rc = sja1105_init_mac_settings(priv); + if (rc < 0) + return rc; + rc = sja1105_init_mii_settings(priv, ports); + if (rc < 0) + return rc; + rc = sja1105_init_static_fdb(priv); + if (rc < 0) + return rc; + rc = sja1105_init_static_vlan(priv); + if (rc < 0) + return rc; + rc = sja1105_init_l2_lookup_params(priv); + if (rc < 0) + return rc; + rc = sja1105_init_l2_forwarding(priv); + if (rc < 0) + return rc; + rc = sja1105_init_l2_forwarding_params(priv); + if (rc < 0) + return rc; + rc = sja1105_init_l2_policing(priv); + if (rc < 0) + return rc; + rc = sja1105_init_general_params(priv); + if (rc < 0) + return rc; + + /* Send initial configuration to hardware via SPI */ + return sja1105_static_config_upload(priv); +} + +static int sja1105_parse_rgmii_delays(struct sja1105_private *priv, + const struct sja1105_dt_port *ports) +{ + int i; + + for (i = 0; i < SJA1105_NUM_PORTS; i++) { + if (ports->role == XMII_MAC) + continue; + + if (ports->phy_mode == PHY_INTERFACE_MODE_RGMII_RXID || + ports->phy_mode == PHY_INTERFACE_MODE_RGMII_ID) + priv->rgmii_rx_delay[i] = true; + + if (ports->phy_mode == PHY_INTERFACE_MODE_RGMII_TXID || + ports->phy_mode == PHY_INTERFACE_MODE_RGMII_ID) + priv->rgmii_tx_delay[i] = true; + + if ((priv->rgmii_rx_delay[i] || priv->rgmii_tx_delay[i]) && + !priv->info->setup_rgmii_delay) + return -EINVAL; + } + return 0; +} + +static int sja1105_parse_ports_node(struct sja1105_private *priv, + struct sja1105_dt_port *ports, + struct device_node *ports_node) +{ + struct device *dev = &priv->spidev->dev; + struct device_node *child; + + for_each_child_of_node(ports_node, child) { + struct device_node *phy_node; + int phy_mode; + u32 index; + + /* Get switch port number from DT */ + if (of_property_read_u32(child, "reg", &index) < 0) { + dev_err(dev, "Port number not defined in device tree " + "(property \"reg\")\n"); + return -ENODEV; + } + + /* Get PHY mode from DT */ + phy_mode = of_get_phy_mode(child); + if (phy_mode < 0) { + dev_err(dev, "Failed to read phy-mode or " + "phy-interface-type property for port %d\n", + index); + return -ENODEV; + } + ports[index].phy_mode = phy_mode; + + phy_node = of_parse_phandle(child, "phy-handle", 0); + if (!phy_node) { + if (!of_phy_is_fixed_link(child)) { + dev_err(dev, "phy-handle or fixed-link " + "properties missing!\n"); + return -ENODEV; + } + /* phy-handle is missing, but fixed-link isn't. + * So it's a fixed link. Default to PHY role. + */ + ports[index].role = XMII_PHY; + } else { + /* phy-handle present => put port in MAC role */ + ports[index].role = XMII_MAC; + of_node_put(phy_node); + } + + /* The MAC/PHY role can be overridden with explicit bindings */ + if (of_property_read_bool(child, "sja1105,role-mac")) + ports[index].role = XMII_MAC; + else if (of_property_read_bool(child, "sja1105,role-phy")) + ports[index].role = XMII_PHY; + } + + return 0; +} + +static int sja1105_parse_dt(struct sja1105_private *priv, + struct sja1105_dt_port *ports) +{ + struct device *dev = &priv->spidev->dev; + struct device_node *switch_node = dev->of_node; + struct device_node *ports_node; + int rc; + + ports_node = of_get_child_by_name(switch_node, "ports"); + if (!ports_node) { + dev_err(dev, "Incorrect bindings: absent \"ports\" node\n"); + return -ENODEV; + } + + rc = sja1105_parse_ports_node(priv, ports, ports_node); + of_node_put(ports_node); + + return rc; +} + +/* Convert back and forth MAC speed from Mbps to SJA1105 encoding */ +static int sja1105_speed[] = { + [SJA1105_SPEED_AUTO] = 0, + [SJA1105_SPEED_10MBPS] = 10, + [SJA1105_SPEED_100MBPS] = 100, + [SJA1105_SPEED_1000MBPS] = 1000, +}; + +static sja1105_speed_t sja1105_get_speed_cfg(unsigned int speed_mbps) +{ + int i; + + for (i = SJA1105_SPEED_AUTO; i <= SJA1105_SPEED_1000MBPS; i++) + if (sja1105_speed[i] == speed_mbps) + return i; + return -EINVAL; +} + +/* Set link speed and enable/disable traffic I/O in the MAC configuration + * for a specific port. + * + * @speed_mbps: If 0, leave the speed unchanged, else adapt MAC to PHY speed. + * @enabled: Manage Rx and Tx settings for this port. If false, overrides the + * settings from the STP state, but not persistently (does not + * overwrite the static MAC info for this port). + */ +static int sja1105_adjust_port_config(struct sja1105_private *priv, int port, + int speed_mbps, bool enabled) +{ + struct sja1105_mac_config_entry dyn_mac; + struct sja1105_xmii_params_entry *mii; + struct sja1105_mac_config_entry *mac; + struct device *dev = priv->ds->dev; + sja1105_phy_interface_t phy_mode; + sja1105_speed_t speed; + int rc; + + mii = priv->static_config.tables[BLK_IDX_XMII_PARAMS].entries; + mac = priv->static_config.tables[BLK_IDX_MAC_CONFIG].entries; + + speed = sja1105_get_speed_cfg(speed_mbps); + if (speed_mbps && speed < 0) { + dev_err(dev, "Invalid speed %iMbps\n", speed_mbps); + return -EINVAL; + } + + /* If requested, overwrite SJA1105_SPEED_AUTO from the static MAC + * configuration table, since this will be used for the clocking setup, + * and we no longer need to store it in the static config (already told + * hardware we want auto during upload phase). + */ + if (speed_mbps) + mac[port].speed = speed; + else + mac[port].speed = SJA1105_SPEED_AUTO; + + /* On P/Q/R/S, one can read from the device via the MAC reconfiguration + * tables. On E/T, MAC reconfig tables are not readable, only writable. + * We have to *know* what the MAC looks like. For the sake of keeping + * the code common, we'll use the static configuration tables as a + * reasonable approximation for both E/T and P/Q/R/S. + */ + dyn_mac = mac[port]; + dyn_mac.ingress = enabled && mac[port].ingress; + dyn_mac.egress = enabled && mac[port].egress; + + /* Write to the dynamic reconfiguration tables */ + rc = sja1105_dynamic_config_write(priv, BLK_IDX_MAC_CONFIG, + port, &dyn_mac, true); + if (rc < 0) { + dev_err(dev, "Failed to write MAC config: %d\n", rc); + return rc; + } + + /* Reconfigure the PLLs for the RGMII interfaces (required 125 MHz at + * gigabit, 25 MHz at 100 Mbps and 2.5 MHz at 10 Mbps). For MII and + * RMII no change of the clock setup is required. Actually, changing + * the clock setup does interrupt the clock signal for a certain time + * which causes trouble for all PHYs relying on this signal. + */ + if (!enabled) + return 0; + + phy_mode = mii->xmii_mode[port]; + if (phy_mode != XMII_MODE_RGMII) + return 0; + + return sja1105_clocking_setup_port(priv, port); +} + +static void sja1105_adjust_link(struct dsa_switch *ds, int port, + struct phy_device *phydev) +{ + struct sja1105_private *priv = ds->priv; + + if (!phydev->link) + sja1105_adjust_port_config(priv, port, 0, false); + else + sja1105_adjust_port_config(priv, port, phydev->speed, true); +} + +static void sja1105_phylink_validate(struct dsa_switch *ds, int port, + unsigned long *supported, + struct phylink_link_state *state) +{ + /* Construct a new mask which exhaustively contains all link features + * supported by the MAC, and then apply that (logical AND) to what will + * be sent to the PHY for "marketing". + */ + __ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, }; + struct sja1105_private *priv = ds->priv; + struct sja1105_xmii_params_entry *mii; + + mii = priv->static_config.tables[BLK_IDX_XMII_PARAMS].entries; + + /* The MAC does not support pause frames, and also doesn't + * support half-duplex traffic modes. + */ + phylink_set(mask, Autoneg); + phylink_set(mask, MII); + phylink_set(mask, 10baseT_Full); + phylink_set(mask, 100baseT_Full); + if (mii->xmii_mode[port] == XMII_MODE_RGMII) + phylink_set(mask, 1000baseT_Full); + + bitmap_and(supported, supported, mask, __ETHTOOL_LINK_MODE_MASK_NBITS); + bitmap_and(state->advertising, state->advertising, mask, + __ETHTOOL_LINK_MODE_MASK_NBITS); +} + +/* First-generation switches have a 4-way set associative TCAM that + * holds the FDB entries. An FDB index spans from 0 to 1023 and is comprised of + * a "bin" (grouping of 4 entries) and a "way" (an entry within a bin). + * For the placement of a newly learnt FDB entry, the switch selects the bin + * based on a hash function, and the way within that bin incrementally. + */ +static inline int sja1105et_fdb_index(int bin, int way) +{ + return bin * SJA1105ET_FDB_BIN_SIZE + way; +} + +static int sja1105_is_fdb_entry_in_bin(struct sja1105_private *priv, int bin, + const u8 *addr, u16 vid, + struct sja1105_l2_lookup_entry *match, + int *last_unused) +{ + int way; + + for (way = 0; way < SJA1105ET_FDB_BIN_SIZE; way++) { + struct sja1105_l2_lookup_entry l2_lookup = {0}; + int index = sja1105et_fdb_index(bin, way); + + /* Skip unused entries, optionally marking them + * into the return value + */ + if (sja1105_dynamic_config_read(priv, BLK_IDX_L2_LOOKUP, + index, &l2_lookup)) { + if (last_unused) + *last_unused = way; + continue; + } + + if (l2_lookup.macaddr == ether_addr_to_u64(addr) && + l2_lookup.vlanid == vid) { + if (match) + *match = l2_lookup; + return way; + } + } + /* Return an invalid entry index if not found */ + return -1; +} + +static int sja1105_fdb_add(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid) +{ + struct sja1105_l2_lookup_entry l2_lookup = {0}; + struct sja1105_private *priv = ds->priv; + struct device *dev = ds->dev; + int last_unused = -1; + int bin, way; + + bin = sja1105_fdb_hash(priv, addr, vid); + + way = sja1105_is_fdb_entry_in_bin(priv, bin, addr, vid, + &l2_lookup, &last_unused); + if (way >= 0) { + /* We have an FDB entry. Is our port in the destination + * mask? If yes, we need to do nothing. If not, we need + * to rewrite the entry by adding this port to it. + */ + if (l2_lookup.destports & BIT(port)) + return 0; + l2_lookup.destports |= BIT(port); + } else { + int index = sja1105et_fdb_index(bin, way); + + /* We don't have an FDB entry. We construct a new one and + * try to find a place for it within the FDB table. + */ + l2_lookup.macaddr = ether_addr_to_u64(addr); + l2_lookup.destports = BIT(port); + l2_lookup.vlanid = vid; + + if (last_unused >= 0) { + way = last_unused; + } else { + /* Bin is full, need to evict somebody. + * Choose victim at random. If you get these messages + * often, you may need to consider changing the + * distribution function: + * static_config[BLK_IDX_L2_LOOKUP_PARAMS].entries->poly + */ + get_random_bytes(&way, sizeof(u8)); + way %= SJA1105ET_FDB_BIN_SIZE; + dev_warn(dev, "Warning, FDB bin %d full while adding entry for %pM. Evicting entry %u.\n", + bin, addr, way); + /* Evict entry */ + sja1105_dynamic_config_write(priv, BLK_IDX_L2_LOOKUP, + index, NULL, false); + } + } + l2_lookup.index = sja1105et_fdb_index(bin, way); + + return sja1105_dynamic_config_write(priv, BLK_IDX_L2_LOOKUP, + l2_lookup.index, &l2_lookup, + true); +} + +static int sja1105_fdb_del(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid) +{ + struct sja1105_l2_lookup_entry l2_lookup = {0}; + struct sja1105_private *priv = ds->priv; + int index, bin, way; + bool keep; + + bin = sja1105_fdb_hash(priv, addr, vid); + way = sja1105_is_fdb_entry_in_bin(priv, bin, addr, vid, + &l2_lookup, NULL); + if (way < 0) + return 0; + index = sja1105et_fdb_index(bin, way); + + /* We have an FDB entry. Is our port in the destination mask? If yes, + * we need to remove it. If the resulting port mask becomes empty, we + * need to completely evict the FDB entry. + * Otherwise we just write it back. + */ + if (l2_lookup.destports & BIT(port)) + l2_lookup.destports &= ~BIT(port); + if (l2_lookup.destports) + keep = true; + else + keep = false; + + return sja1105_dynamic_config_write(priv, BLK_IDX_L2_LOOKUP, + index, &l2_lookup, keep); +} + +static int sja1105_fdb_dump(struct dsa_switch *ds, int port, + dsa_fdb_dump_cb_t *cb, void *data) +{ + struct sja1105_private *priv = ds->priv; + struct device *dev = ds->dev; + int i; + + for (i = 0; i < SJA1105_MAX_L2_LOOKUP_COUNT; i++) { + struct sja1105_l2_lookup_entry l2_lookup = {0}; + u8 macaddr[ETH_ALEN]; + int rc; + + rc = sja1105_dynamic_config_read(priv, BLK_IDX_L2_LOOKUP, + i, &l2_lookup); + /* No fdb entry at i, not an issue */ + if (rc == -EINVAL) + continue; + if (rc) { + dev_err(dev, "Failed to dump FDB: %d\n", rc); + return rc; + } + + /* FDB dump callback is per port. This means we have to + * disregard a valid entry if it's not for this port, even if + * only to revisit it later. This is inefficient because the + * 1024-sized FDB table needs to be traversed 4 times through + * SPI during a 'bridge fdb show' command. + */ + if (!(l2_lookup.destports & BIT(port))) + continue; + u64_to_ether_addr(l2_lookup.macaddr, macaddr); + cb(macaddr, l2_lookup.vlanid, false, data); + } + return 0; +} + +/* This callback needs to be present */ +static int sja1105_mdb_prepare(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_mdb *mdb) +{ + return 0; +} + +static void sja1105_mdb_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_mdb *mdb) +{ + sja1105_fdb_add(ds, port, mdb->addr, mdb->vid); +} + +static int sja1105_mdb_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_mdb *mdb) +{ + return sja1105_fdb_del(ds, port, mdb->addr, mdb->vid); +} + +static int sja1105_bridge_member(struct dsa_switch *ds, int port, + struct net_device *br, bool member) +{ + struct sja1105_l2_forwarding_entry *l2_fwd; + struct sja1105_private *priv = ds->priv; + int i, rc; + + l2_fwd = priv->static_config.tables[BLK_IDX_L2_FORWARDING].entries; + + for (i = 0; i < SJA1105_NUM_PORTS; i++) { + /* Add this port to the forwarding matrix of the + * other ports in the same bridge, and viceversa. + */ + if (!dsa_is_user_port(ds, i)) + continue; + /* For the ports already under the bridge, only one thing needs + * to be done, and that is to add this port to their + * reachability domain. So we can perform the SPI write for + * them immediately. However, for this port itself (the one + * that is new to the bridge), we need to add all other ports + * to its reachability domain. So we do that incrementally in + * this loop, and perform the SPI write only at the end, once + * the domain contains all other bridge ports. + */ + if (i == port) + continue; + if (dsa_to_port(ds, i)->bridge_dev != br) + continue; + sja1105_port_allow_traffic(l2_fwd, i, port, member); + sja1105_port_allow_traffic(l2_fwd, port, i, member); + + rc = sja1105_dynamic_config_write(priv, BLK_IDX_L2_FORWARDING, + i, &l2_fwd[i], true); + if (rc < 0) + return rc; + } + + return sja1105_dynamic_config_write(priv, BLK_IDX_L2_FORWARDING, + port, &l2_fwd[port], true); +} + +static void sja1105_bridge_stp_state_set(struct dsa_switch *ds, int port, + u8 state) +{ + struct sja1105_private *priv = ds->priv; + struct sja1105_mac_config_entry *mac; + + mac = priv->static_config.tables[BLK_IDX_MAC_CONFIG].entries; + + switch (state) { + case BR_STATE_DISABLED: + case BR_STATE_BLOCKING: + /* From UM10944 description of DRPDTAG (why put this there?): + * "Management traffic flows to the port regardless of the state + * of the INGRESS flag". So BPDUs are still be allowed to pass. + * At the moment no difference between DISABLED and BLOCKING. + */ + mac[port].ingress = false; + mac[port].egress = false; + mac[port].dyn_learn = false; + break; + case BR_STATE_LISTENING: + mac[port].ingress = true; + mac[port].egress = false; + mac[port].dyn_learn = false; + break; + case BR_STATE_LEARNING: + mac[port].ingress = true; + mac[port].egress = false; + mac[port].dyn_learn = true; + break; + case BR_STATE_FORWARDING: + mac[port].ingress = true; + mac[port].egress = true; + mac[port].dyn_learn = true; + break; + default: + dev_err(ds->dev, "invalid STP state: %d\n", state); + return; + } + + sja1105_dynamic_config_write(priv, BLK_IDX_MAC_CONFIG, port, + &mac[port], true); +} + +static int sja1105_bridge_join(struct dsa_switch *ds, int port, + struct net_device *br) +{ + return sja1105_bridge_member(ds, port, br, true); +} + +static void sja1105_bridge_leave(struct dsa_switch *ds, int port, + struct net_device *br) +{ + sja1105_bridge_member(ds, port, br, false); +} + +static u8 sja1105_stp_state_get(struct sja1105_private *priv, int port) +{ + struct sja1105_mac_config_entry *mac; + + mac = priv->static_config.tables[BLK_IDX_MAC_CONFIG].entries; + + if (!mac[port].ingress && !mac[port].egress && !mac[port].dyn_learn) + return BR_STATE_BLOCKING; + if (mac[port].ingress && !mac[port].egress && !mac[port].dyn_learn) + return BR_STATE_LISTENING; + if (mac[port].ingress && !mac[port].egress && mac[port].dyn_learn) + return BR_STATE_LEARNING; + if (mac[port].ingress && mac[port].egress && mac[port].dyn_learn) + return BR_STATE_FORWARDING; + /* This is really an error condition if the MAC was in none of the STP + * states above. But treating the port as disabled does nothing, which + * is adequate, and it also resets the MAC to a known state later on. + */ + return BR_STATE_DISABLED; +} + +/* For situations where we need to change a setting at runtime that is only + * available through the static configuration, resetting the switch in order + * to upload the new static config is unavoidable. Back up the settings we + * modify at runtime (currently only MAC) and restore them after uploading, + * such that this operation is relatively seamless. + */ +static int sja1105_static_config_reload(struct sja1105_private *priv) +{ + struct sja1105_mac_config_entry *mac; + int speed_mbps[SJA1105_NUM_PORTS]; + u8 stp_state[SJA1105_NUM_PORTS]; + int rc, i; + + mac = priv->static_config.tables[BLK_IDX_MAC_CONFIG].entries; + + /* Back up settings changed by sja1105_adjust_port_config and + * sja1105_bridge_stp_state_set and restore their defaults. + */ + for (i = 0; i < SJA1105_NUM_PORTS; i++) { + speed_mbps[i] = sja1105_speed[mac[i].speed]; + mac[i].speed = SJA1105_SPEED_AUTO; + if (i == dsa_upstream_port(priv->ds, i)) { + mac[i].ingress = true; + mac[i].egress = true; + mac[i].dyn_learn = true; + } else { + stp_state[i] = sja1105_stp_state_get(priv, i); + mac[i].ingress = false; + mac[i].egress = false; + mac[i].dyn_learn = false; + } + } + + /* Reset switch and send updated static configuration */ + rc = sja1105_static_config_upload(priv); + if (rc < 0) + goto out; + + /* Configure the CGU (PLLs) for MII and RMII PHYs. + * For these interfaces there is no dynamic configuration + * needed, since PLLs have same settings at all speeds. + */ + rc = sja1105_clocking_setup(priv); + if (rc < 0) + goto out; + + for (i = 0; i < SJA1105_NUM_PORTS; i++) { + bool enabled = (speed_mbps[i] != 0); + + if (i != dsa_upstream_port(priv->ds, i)) + sja1105_bridge_stp_state_set(priv->ds, i, stp_state[i]); + + rc = sja1105_adjust_port_config(priv, i, speed_mbps[i], + enabled); + if (rc < 0) + goto out; + } +out: + return rc; +} + +/* The TPID setting belongs to the General Parameters table, + * which can only be partially reconfigured at runtime (and not the TPID). + * So a switch reset is required. + */ +static int sja1105_change_tpid(struct sja1105_private *priv, + u16 tpid, u16 tpid2) +{ + struct sja1105_general_params_entry *general_params; + struct sja1105_table *table; + + table = &priv->static_config.tables[BLK_IDX_GENERAL_PARAMS]; + general_params = table->entries; + general_params->tpid = tpid; + general_params->tpid2 = tpid2; + return sja1105_static_config_reload(priv); +} + +static int sja1105_pvid_apply(struct sja1105_private *priv, int port, u16 pvid) +{ + struct sja1105_mac_config_entry *mac; + + mac = priv->static_config.tables[BLK_IDX_MAC_CONFIG].entries; + + mac[port].vlanid = pvid; + + return sja1105_dynamic_config_write(priv, BLK_IDX_MAC_CONFIG, port, + &mac[port], true); +} + +static int sja1105_is_vlan_configured(struct sja1105_private *priv, u16 vid) +{ + struct sja1105_vlan_lookup_entry *vlan; + int count, i; + + vlan = priv->static_config.tables[BLK_IDX_VLAN_LOOKUP].entries; + count = priv->static_config.tables[BLK_IDX_VLAN_LOOKUP].entry_count; + + for (i = 0; i < count; i++) + if (vlan[i].vlanid == vid) + return i; + + /* Return an invalid entry index if not found */ + return -1; +} + +static int sja1105_vlan_apply(struct sja1105_private *priv, int port, u16 vid, + bool enabled, bool untagged) +{ + struct sja1105_vlan_lookup_entry *vlan; + struct sja1105_table *table; + bool keep = true; + int match, rc; + + table = &priv->static_config.tables[BLK_IDX_VLAN_LOOKUP]; + + match = sja1105_is_vlan_configured(priv, vid); + if (match < 0) { + /* Can't delete a missing entry. */ + if (!enabled) + return 0; + rc = sja1105_table_resize(table, table->entry_count + 1); + if (rc) + return rc; + match = table->entry_count - 1; + } + /* Assign pointer after the resize (it's new memory) */ + vlan = table->entries; + vlan[match].vlanid = vid; + if (enabled) { + vlan[match].vlan_bc |= BIT(port); + vlan[match].vmemb_port |= BIT(port); + } else { + vlan[match].vlan_bc &= ~BIT(port); + vlan[match].vmemb_port &= ~BIT(port); + } + /* Also unset tag_port if removing this VLAN was requested, + * just so we don't have a confusing bitmap (no practical purpose). + */ + if (untagged || !enabled) + vlan[match].tag_port &= ~BIT(port); + else + vlan[match].tag_port |= BIT(port); + /* If there's no port left as member of this VLAN, + * it's time for it to go. + */ + if (!vlan[match].vmemb_port) + keep = false; + + dev_dbg(priv->ds->dev, + "%s: port %d, vid %llu, broadcast domain 0x%llx, " + "port members 0x%llx, tagged ports 0x%llx, keep %d\n", + __func__, port, vlan[match].vlanid, vlan[match].vlan_bc, + vlan[match].vmemb_port, vlan[match].tag_port, keep); + + rc = sja1105_dynamic_config_write(priv, BLK_IDX_VLAN_LOOKUP, vid, + &vlan[match], keep); + if (rc < 0) + return rc; + + if (!keep) + return sja1105_table_delete_entry(table, match); + + return 0; +} + +static int sja1105_setup_8021q_tagging(struct dsa_switch *ds, bool enabled) +{ + int rc, i; + + for (i = 0; i < SJA1105_NUM_PORTS; i++) { + rc = dsa_port_setup_8021q_tagging(ds, i, enabled); + if (rc < 0) { + dev_err(ds->dev, "Failed to setup VLAN tagging for port %d: %d\n", + i, rc); + return rc; + } + } + dev_info(ds->dev, "%s switch tagging\n", + enabled ? "Enabled" : "Disabled"); + return 0; +} + +static enum dsa_tag_protocol +sja1105_get_tag_protocol(struct dsa_switch *ds, int port) +{ + return DSA_TAG_PROTO_SJA1105; +} + +/* This callback needs to be present */ +static int sja1105_vlan_prepare(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + return 0; +} + +static int sja1105_vlan_filtering(struct dsa_switch *ds, int port, bool enabled) +{ + struct sja1105_private *priv = ds->priv; + int rc; + + if (enabled) + /* Enable VLAN filtering. */ + rc = sja1105_change_tpid(priv, ETH_P_8021Q, ETH_P_8021AD); + else + /* Disable VLAN filtering. */ + rc = sja1105_change_tpid(priv, ETH_P_SJA1105, ETH_P_SJA1105); + if (rc) + dev_err(ds->dev, "Failed to change VLAN Ethertype\n"); + + /* Switch port identification based on 802.1Q is only passable + * if we are not under a vlan_filtering bridge. So make sure + * the two configurations are mutually exclusive. + */ + return sja1105_setup_8021q_tagging(ds, !enabled); +} + +static void sja1105_vlan_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + struct sja1105_private *priv = ds->priv; + u16 vid; + int rc; + + for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) { + rc = sja1105_vlan_apply(priv, port, vid, true, vlan->flags & + BRIDGE_VLAN_INFO_UNTAGGED); + if (rc < 0) { + dev_err(ds->dev, "Failed to add VLAN %d to port %d: %d\n", + vid, port, rc); + return; + } + if (vlan->flags & BRIDGE_VLAN_INFO_PVID) { + rc = sja1105_pvid_apply(ds->priv, port, vid); + if (rc < 0) { + dev_err(ds->dev, "Failed to set pvid %d on port %d: %d\n", + vid, port, rc); + return; + } + } + } +} + +static int sja1105_vlan_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + struct sja1105_private *priv = ds->priv; + u16 vid; + int rc; + + for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++) { + rc = sja1105_vlan_apply(priv, port, vid, false, vlan->flags & + BRIDGE_VLAN_INFO_UNTAGGED); + if (rc < 0) { + dev_err(ds->dev, "Failed to remove VLAN %d from port %d: %d\n", + vid, port, rc); + return rc; + } + } + return 0; +} + +/* The programming model for the SJA1105 switch is "all-at-once" via static + * configuration tables. Some of these can be dynamically modified at runtime, + * but not the xMII mode parameters table. + * Furthermode, some PHYs may not have crystals for generating their clocks + * (e.g. RMII). Instead, their 50MHz clock is supplied via the SJA1105 port's + * ref_clk pin. So port clocking needs to be initialized early, before + * connecting to PHYs is attempted, otherwise they won't respond through MDIO. + * Setting correct PHY link speed does not matter now. + * But dsa_slave_phy_setup is called later than sja1105_setup, so the PHY + * bindings are not yet parsed by DSA core. We need to parse early so that we + * can populate the xMII mode parameters table. + */ +static int sja1105_setup(struct dsa_switch *ds) +{ + struct sja1105_dt_port ports[SJA1105_NUM_PORTS]; + struct sja1105_private *priv = ds->priv; + int rc; + + rc = sja1105_parse_dt(priv, ports); + if (rc < 0) { + dev_err(ds->dev, "Failed to parse DT: %d\n", rc); + return rc; + } + + /* Error out early if internal delays are required through DT + * and we can't apply them. + */ + rc = sja1105_parse_rgmii_delays(priv, ports); + if (rc < 0) { + dev_err(ds->dev, "RGMII delay not supported\n"); + return rc; + } + + /* Create and send configuration down to device */ + rc = sja1105_static_config_load(priv, ports); + if (rc < 0) { + dev_err(ds->dev, "Failed to load static config: %d\n", rc); + return rc; + } + /* Configure the CGU (PHY link modes and speeds) */ + rc = sja1105_clocking_setup(priv); + if (rc < 0) { + dev_err(ds->dev, "Failed to configure MII clocking: %d\n", rc); + return rc; + } + /* On SJA1105, VLAN filtering per se is always enabled in hardware. + * The only thing we can do to disable it is lie about what the 802.1Q + * EtherType is. + * So it will still try to apply VLAN filtering, but all ingress + * traffic (except frames received with EtherType of ETH_P_SJA1105) + * will be internally tagged with a distorted VLAN header where the + * TPID is ETH_P_SJA1105, and the VLAN ID is the port pvid. + */ + ds->vlan_filtering_is_global = true; + + /* The DSA/switchdev model brings up switch ports in standalone mode by + * default, and that means vlan_filtering is 0 since they're not under + * a bridge, so it's safe to set up switch tagging at this time. + */ + return sja1105_setup_8021q_tagging(ds, true); +} + +static int sja1105_mgmt_xmit(struct dsa_switch *ds, int port, int slot, + struct sk_buff *skb) +{ + struct sja1105_mgmt_entry mgmt_route = {0}; + struct sja1105_private *priv = ds->priv; + struct ethhdr *hdr; + int timeout = 10; + int rc; + + hdr = eth_hdr(skb); + + mgmt_route.macaddr = ether_addr_to_u64(hdr->h_dest); + mgmt_route.destports = BIT(port); + mgmt_route.enfport = 1; + + rc = sja1105_dynamic_config_write(priv, BLK_IDX_MGMT_ROUTE, + slot, &mgmt_route, true); + if (rc < 0) { + kfree_skb(skb); + return rc; + } + + /* Transfer skb to the host port. */ + dsa_enqueue_skb(skb, ds->ports[port].slave); + + /* Wait until the switch has processed the frame */ + do { + rc = sja1105_dynamic_config_read(priv, BLK_IDX_MGMT_ROUTE, + slot, &mgmt_route); + if (rc < 0) { + dev_err_ratelimited(priv->ds->dev, + "failed to poll for mgmt route\n"); + continue; + } + + /* UM10944: The ENFPORT flag of the respective entry is + * cleared when a match is found. The host can use this + * flag as an acknowledgment. + */ + cpu_relax(); + } while (mgmt_route.enfport && --timeout); + + if (!timeout) { + /* Clean up the management route so that a follow-up + * frame may not match on it by mistake. + */ + sja1105_dynamic_config_write(priv, BLK_IDX_MGMT_ROUTE, + slot, &mgmt_route, false); + dev_err_ratelimited(priv->ds->dev, "xmit timed out\n"); + } + + return NETDEV_TX_OK; +} + +/* Deferred work is unfortunately necessary because setting up the management + * route cannot be done from atomit context (SPI transfer takes a sleepable + * lock on the bus) + */ +static netdev_tx_t sja1105_port_deferred_xmit(struct dsa_switch *ds, int port, + struct sk_buff *skb) +{ + struct sja1105_private *priv = ds->priv; + struct sja1105_port *sp = &priv->ports[port]; + int slot = sp->mgmt_slot; + + /* The tragic fact about the switch having 4x2 slots for installing + * management routes is that all of them except one are actually + * useless. + * If 2 slots are simultaneously configured for two BPDUs sent to the + * same (multicast) DMAC but on different egress ports, the switch + * would confuse them and redirect first frame it receives on the CPU + * port towards the port configured on the numerically first slot + * (therefore wrong port), then second received frame on second slot + * (also wrong port). + * So for all practical purposes, there needs to be a lock that + * prevents that from happening. The slot used here is utterly useless + * (could have simply been 0 just as fine), but we are doing it + * nonetheless, in case a smarter idea ever comes up in the future. + */ + mutex_lock(&priv->mgmt_lock); + + sja1105_mgmt_xmit(ds, port, slot, skb); + + mutex_unlock(&priv->mgmt_lock); + return NETDEV_TX_OK; +} + +/* The MAXAGE setting belongs to the L2 Forwarding Parameters table, + * which cannot be reconfigured at runtime. So a switch reset is required. + */ +static int sja1105_set_ageing_time(struct dsa_switch *ds, + unsigned int ageing_time) +{ + struct sja1105_l2_lookup_params_entry *l2_lookup_params; + struct sja1105_private *priv = ds->priv; + struct sja1105_table *table; + unsigned int maxage; + + table = &priv->static_config.tables[BLK_IDX_L2_LOOKUP_PARAMS]; + l2_lookup_params = table->entries; + + maxage = SJA1105_AGEING_TIME_MS(ageing_time); + + if (l2_lookup_params->maxage == maxage) + return 0; + + l2_lookup_params->maxage = maxage; + + return sja1105_static_config_reload(priv); +} + +static const struct dsa_switch_ops sja1105_switch_ops = { + .get_tag_protocol = sja1105_get_tag_protocol, + .setup = sja1105_setup, + .adjust_link = sja1105_adjust_link, + .set_ageing_time = sja1105_set_ageing_time, + .phylink_validate = sja1105_phylink_validate, + .get_strings = sja1105_get_strings, + .get_ethtool_stats = sja1105_get_ethtool_stats, + .get_sset_count = sja1105_get_sset_count, + .port_fdb_dump = sja1105_fdb_dump, + .port_fdb_add = sja1105_fdb_add, + .port_fdb_del = sja1105_fdb_del, + .port_bridge_join = sja1105_bridge_join, + .port_bridge_leave = sja1105_bridge_leave, + .port_stp_state_set = sja1105_bridge_stp_state_set, + .port_vlan_prepare = sja1105_vlan_prepare, + .port_vlan_filtering = sja1105_vlan_filtering, + .port_vlan_add = sja1105_vlan_add, + .port_vlan_del = sja1105_vlan_del, + .port_mdb_prepare = sja1105_mdb_prepare, + .port_mdb_add = sja1105_mdb_add, + .port_mdb_del = sja1105_mdb_del, + .port_deferred_xmit = sja1105_port_deferred_xmit, +}; + +static int sja1105_check_device_id(struct sja1105_private *priv) +{ + const struct sja1105_regs *regs = priv->info->regs; + u8 prod_id[SJA1105_SIZE_DEVICE_ID] = {0}; + struct device *dev = &priv->spidev->dev; + u64 device_id; + u64 part_no; + int rc; + + rc = sja1105_spi_send_int(priv, SPI_READ, regs->device_id, + &device_id, SJA1105_SIZE_DEVICE_ID); + if (rc < 0) + return rc; + + if (device_id != priv->info->device_id) { + dev_err(dev, "Expected device ID 0x%llx but read 0x%llx\n", + priv->info->device_id, device_id); + return -ENODEV; + } + + rc = sja1105_spi_send_packed_buf(priv, SPI_READ, regs->prod_id, + prod_id, SJA1105_SIZE_DEVICE_ID); + if (rc < 0) + return rc; + + sja1105_unpack(prod_id, &part_no, 19, 4, SJA1105_SIZE_DEVICE_ID); + + if (part_no != priv->info->part_no) { + dev_err(dev, "Expected part number 0x%llx but read 0x%llx\n", + priv->info->part_no, part_no); + return -ENODEV; + } + + return 0; +} + +static int sja1105_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct sja1105_private *priv; + struct dsa_switch *ds; + int rc, i; + + if (!dev->of_node) { + dev_err(dev, "No DTS bindings for SJA1105 driver\n"); + return -EINVAL; + } + + priv = devm_kzalloc(dev, sizeof(struct sja1105_private), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* Configure the optional reset pin and bring up switch */ + priv->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(priv->reset_gpio)) + dev_dbg(dev, "reset-gpios not defined, ignoring\n"); + else + sja1105_hw_reset(priv->reset_gpio, 1, 1); + + /* Populate our driver private structure (priv) based on + * the device tree node that was probed (spi) + */ + priv->spidev = spi; + spi_set_drvdata(spi, priv); + + /* Configure the SPI bus */ + spi->bits_per_word = 8; + rc = spi_setup(spi); + if (rc < 0) { + dev_err(dev, "Could not init SPI\n"); + return rc; + } + + priv->info = of_device_get_match_data(dev); + + /* Detect hardware device */ + rc = sja1105_check_device_id(priv); + if (rc < 0) { + dev_err(dev, "Device ID check failed: %d\n", rc); + return rc; + } + + dev_info(dev, "Probed switch chip: %s\n", priv->info->name); + + ds = dsa_switch_alloc(dev, SJA1105_NUM_PORTS); + if (!ds) + return -ENOMEM; + + ds->ops = &sja1105_switch_ops; + ds->priv = priv; + priv->ds = ds; + + /* Connections between dsa_port and sja1105_port */ + for (i = 0; i < SJA1105_NUM_PORTS; i++) { + struct sja1105_port *sp = &priv->ports[i]; + + ds->ports[i].priv = sp; + sp->dp = &ds->ports[i]; + } + mutex_init(&priv->mgmt_lock); + + return dsa_register_switch(priv->ds); +} + +static int sja1105_remove(struct spi_device *spi) +{ + struct sja1105_private *priv = spi_get_drvdata(spi); + + dsa_unregister_switch(priv->ds); + sja1105_static_config_free(&priv->static_config); + return 0; +} + +static const struct of_device_id sja1105_dt_ids[] = { + { .compatible = "nxp,sja1105e", .data = &sja1105e_info }, + { .compatible = "nxp,sja1105t", .data = &sja1105t_info }, + { .compatible = "nxp,sja1105p", .data = &sja1105p_info }, + { .compatible = "nxp,sja1105q", .data = &sja1105q_info }, + { .compatible = "nxp,sja1105r", .data = &sja1105r_info }, + { .compatible = "nxp,sja1105s", .data = &sja1105s_info }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, sja1105_dt_ids); + +static struct spi_driver sja1105_driver = { + .driver = { + .name = "sja1105", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(sja1105_dt_ids), + }, + .probe = sja1105_probe, + .remove = sja1105_remove, +}; + +module_spi_driver(sja1105_driver); + +MODULE_AUTHOR("Vladimir Oltean <olteanv@gmail.com>"); +MODULE_AUTHOR("Georg Waibel <georg.waibel@sensor-technik.de>"); +MODULE_DESCRIPTION("SJA1105 Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/dsa/sja1105/sja1105_spi.c b/drivers/net/dsa/sja1105/sja1105_spi.c new file mode 100644 index 000000000000..2eb70b8acfc3 --- /dev/null +++ b/drivers/net/dsa/sja1105/sja1105_spi.c @@ -0,0 +1,591 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* Copyright (c) 2016-2018, NXP Semiconductors + * Copyright (c) 2018, Sensor-Technik Wiedemann GmbH + * Copyright (c) 2018-2019, Vladimir Oltean <olteanv@gmail.com> + */ +#include <linux/spi/spi.h> +#include <linux/packing.h> +#include "sja1105.h" + +#define SJA1105_SIZE_PORT_CTRL 4 +#define SJA1105_SIZE_RESET_CMD 4 +#define SJA1105_SIZE_SPI_MSG_HEADER 4 +#define SJA1105_SIZE_SPI_MSG_MAXLEN (64 * 4) +#define SJA1105_SIZE_SPI_TRANSFER_MAX \ + (SJA1105_SIZE_SPI_MSG_HEADER + SJA1105_SIZE_SPI_MSG_MAXLEN) + +static int sja1105_spi_transfer(const struct sja1105_private *priv, + const void *tx, void *rx, int size) +{ + struct spi_device *spi = priv->spidev; + struct spi_transfer transfer = { + .tx_buf = tx, + .rx_buf = rx, + .len = size, + }; + struct spi_message msg; + int rc; + + if (size > SJA1105_SIZE_SPI_TRANSFER_MAX) { + dev_err(&spi->dev, "SPI message (%d) longer than max of %d\n", + size, SJA1105_SIZE_SPI_TRANSFER_MAX); + return -EMSGSIZE; + } + + spi_message_init(&msg); + spi_message_add_tail(&transfer, &msg); + + rc = spi_sync(spi, &msg); + if (rc < 0) { + dev_err(&spi->dev, "SPI transfer failed: %d\n", rc); + return rc; + } + + return rc; +} + +static void +sja1105_spi_message_pack(void *buf, const struct sja1105_spi_message *msg) +{ + const int size = SJA1105_SIZE_SPI_MSG_HEADER; + + memset(buf, 0, size); + + sja1105_pack(buf, &msg->access, 31, 31, size); + sja1105_pack(buf, &msg->read_count, 30, 25, size); + sja1105_pack(buf, &msg->address, 24, 4, size); +} + +/* If @rw is: + * - SPI_WRITE: creates and sends an SPI write message at absolute + * address reg_addr, taking size_bytes from *packed_buf + * - SPI_READ: creates and sends an SPI read message from absolute + * address reg_addr, writing size_bytes into *packed_buf + * + * This function should only be called if it is priorly known that + * @size_bytes is smaller than SIZE_SPI_MSG_MAXLEN. Larger packed buffers + * are chunked in smaller pieces by sja1105_spi_send_long_packed_buf below. + */ +int sja1105_spi_send_packed_buf(const struct sja1105_private *priv, + sja1105_spi_rw_mode_t rw, u64 reg_addr, + void *packed_buf, size_t size_bytes) +{ + u8 tx_buf[SJA1105_SIZE_SPI_TRANSFER_MAX] = {0}; + u8 rx_buf[SJA1105_SIZE_SPI_TRANSFER_MAX] = {0}; + const int msg_len = size_bytes + SJA1105_SIZE_SPI_MSG_HEADER; + struct sja1105_spi_message msg = {0}; + int rc; + + if (msg_len > SJA1105_SIZE_SPI_TRANSFER_MAX) + return -ERANGE; + + msg.access = rw; + msg.address = reg_addr; + if (rw == SPI_READ) + msg.read_count = size_bytes / 4; + + sja1105_spi_message_pack(tx_buf, &msg); + + if (rw == SPI_WRITE) + memcpy(tx_buf + SJA1105_SIZE_SPI_MSG_HEADER, + packed_buf, size_bytes); + + rc = sja1105_spi_transfer(priv, tx_buf, rx_buf, msg_len); + if (rc < 0) + return rc; + + if (rw == SPI_READ) + memcpy(packed_buf, rx_buf + SJA1105_SIZE_SPI_MSG_HEADER, + size_bytes); + + return 0; +} + +/* If @rw is: + * - SPI_WRITE: creates and sends an SPI write message at absolute + * address reg_addr, taking size_bytes from *packed_buf + * - SPI_READ: creates and sends an SPI read message from absolute + * address reg_addr, writing size_bytes into *packed_buf + * + * The u64 *value is unpacked, meaning that it's stored in the native + * CPU endianness and directly usable by software running on the core. + * + * This is a wrapper around sja1105_spi_send_packed_buf(). + */ +int sja1105_spi_send_int(const struct sja1105_private *priv, + sja1105_spi_rw_mode_t rw, u64 reg_addr, + u64 *value, u64 size_bytes) +{ + u8 packed_buf[SJA1105_SIZE_SPI_MSG_MAXLEN]; + int rc; + + if (size_bytes > SJA1105_SIZE_SPI_MSG_MAXLEN) + return -ERANGE; + + if (rw == SPI_WRITE) + sja1105_pack(packed_buf, value, 8 * size_bytes - 1, 0, + size_bytes); + + rc = sja1105_spi_send_packed_buf(priv, rw, reg_addr, packed_buf, + size_bytes); + + if (rw == SPI_READ) + sja1105_unpack(packed_buf, value, 8 * size_bytes - 1, 0, + size_bytes); + + return rc; +} + +/* Should be used if a @packed_buf larger than SJA1105_SIZE_SPI_MSG_MAXLEN + * must be sent/received. Splitting the buffer into chunks and assembling + * those into SPI messages is done automatically by this function. + */ +int sja1105_spi_send_long_packed_buf(const struct sja1105_private *priv, + sja1105_spi_rw_mode_t rw, u64 base_addr, + void *packed_buf, u64 buf_len) +{ + struct chunk { + void *buf_ptr; + int len; + u64 spi_address; + } chunk; + int distance_to_end; + int rc; + + /* Initialize chunk */ + chunk.buf_ptr = packed_buf; + chunk.spi_address = base_addr; + chunk.len = min_t(int, buf_len, SJA1105_SIZE_SPI_MSG_MAXLEN); + + while (chunk.len) { + rc = sja1105_spi_send_packed_buf(priv, rw, chunk.spi_address, + chunk.buf_ptr, chunk.len); + if (rc < 0) + return rc; + + chunk.buf_ptr += chunk.len; + chunk.spi_address += chunk.len / 4; + distance_to_end = (uintptr_t)(packed_buf + buf_len - + chunk.buf_ptr); + chunk.len = min(distance_to_end, SJA1105_SIZE_SPI_MSG_MAXLEN); + } + + return 0; +} + +/* Back-ported structure from UM11040 Table 112. + * Reset control register (addr. 100440h) + * In the SJA1105 E/T, only warm_rst and cold_rst are + * supported (exposed in UM10944 as rst_ctrl), but the bit + * offsets of warm_rst and cold_rst are actually reversed. + */ +struct sja1105_reset_cmd { + u64 switch_rst; + u64 cfg_rst; + u64 car_rst; + u64 otp_rst; + u64 warm_rst; + u64 cold_rst; + u64 por_rst; +}; + +static void +sja1105et_reset_cmd_pack(void *buf, const struct sja1105_reset_cmd *reset) +{ + const int size = SJA1105_SIZE_RESET_CMD; + + memset(buf, 0, size); + + sja1105_pack(buf, &reset->cold_rst, 3, 3, size); + sja1105_pack(buf, &reset->warm_rst, 2, 2, size); +} + +static void +sja1105pqrs_reset_cmd_pack(void *buf, const struct sja1105_reset_cmd *reset) +{ + const int size = SJA1105_SIZE_RESET_CMD; + + memset(buf, 0, size); + + sja1105_pack(buf, &reset->switch_rst, 8, 8, size); + sja1105_pack(buf, &reset->cfg_rst, 7, 7, size); + sja1105_pack(buf, &reset->car_rst, 5, 5, size); + sja1105_pack(buf, &reset->otp_rst, 4, 4, size); + sja1105_pack(buf, &reset->warm_rst, 3, 3, size); + sja1105_pack(buf, &reset->cold_rst, 2, 2, size); + sja1105_pack(buf, &reset->por_rst, 1, 1, size); +} + +static int sja1105et_reset_cmd(const void *ctx, const void *data) +{ + const struct sja1105_private *priv = ctx; + const struct sja1105_reset_cmd *reset = data; + const struct sja1105_regs *regs = priv->info->regs; + struct device *dev = priv->ds->dev; + u8 packed_buf[SJA1105_SIZE_RESET_CMD]; + + if (reset->switch_rst || + reset->cfg_rst || + reset->car_rst || + reset->otp_rst || + reset->por_rst) { + dev_err(dev, "Only warm and cold reset is supported " + "for SJA1105 E/T!\n"); + return -EINVAL; + } + + if (reset->warm_rst) + dev_dbg(dev, "Warm reset requested\n"); + if (reset->cold_rst) + dev_dbg(dev, "Cold reset requested\n"); + + sja1105et_reset_cmd_pack(packed_buf, reset); + + return sja1105_spi_send_packed_buf(priv, SPI_WRITE, regs->rgu, + packed_buf, SJA1105_SIZE_RESET_CMD); +} + +static int sja1105pqrs_reset_cmd(const void *ctx, const void *data) +{ + const struct sja1105_private *priv = ctx; + const struct sja1105_reset_cmd *reset = data; + const struct sja1105_regs *regs = priv->info->regs; + struct device *dev = priv->ds->dev; + u8 packed_buf[SJA1105_SIZE_RESET_CMD]; + + if (reset->switch_rst) + dev_dbg(dev, "Main reset for all functional modules requested\n"); + if (reset->cfg_rst) + dev_dbg(dev, "Chip configuration reset requested\n"); + if (reset->car_rst) + dev_dbg(dev, "Clock and reset control logic reset requested\n"); + if (reset->otp_rst) + dev_dbg(dev, "OTP read cycle for reading product " + "config settings requested\n"); + if (reset->warm_rst) + dev_dbg(dev, "Warm reset requested\n"); + if (reset->cold_rst) + dev_dbg(dev, "Cold reset requested\n"); + if (reset->por_rst) + dev_dbg(dev, "Power-on reset requested\n"); + + sja1105pqrs_reset_cmd_pack(packed_buf, reset); + + return sja1105_spi_send_packed_buf(priv, SPI_WRITE, regs->rgu, + packed_buf, SJA1105_SIZE_RESET_CMD); +} + +static int sja1105_cold_reset(const struct sja1105_private *priv) +{ + struct sja1105_reset_cmd reset = {0}; + + reset.cold_rst = 1; + return priv->info->reset_cmd(priv, &reset); +} + +static int sja1105_inhibit_tx(const struct sja1105_private *priv, + const unsigned long *port_bitmap) +{ + const struct sja1105_regs *regs = priv->info->regs; + u64 inhibit_cmd; + int port, rc; + + rc = sja1105_spi_send_int(priv, SPI_READ, regs->port_control, + &inhibit_cmd, SJA1105_SIZE_PORT_CTRL); + if (rc < 0) + return rc; + + for_each_set_bit(port, port_bitmap, SJA1105_NUM_PORTS) + inhibit_cmd |= BIT(port); + + return sja1105_spi_send_int(priv, SPI_WRITE, regs->port_control, + &inhibit_cmd, SJA1105_SIZE_PORT_CTRL); +} + +struct sja1105_status { + u64 configs; + u64 crcchkl; + u64 ids; + u64 crcchkg; +}; + +/* This is not reading the entire General Status area, which is also + * divergent between E/T and P/Q/R/S, but only the relevant bits for + * ensuring that the static config upload procedure was successful. + */ +static void sja1105_status_unpack(void *buf, struct sja1105_status *status) +{ + /* So that addition translates to 4 bytes */ + u32 *p = buf; + + /* device_id is missing from the buffer, but we don't + * want to diverge from the manual definition of the + * register addresses, so we'll back off one step with + * the register pointer, and never access p[0]. + */ + p--; + sja1105_unpack(p + 0x1, &status->configs, 31, 31, 4); + sja1105_unpack(p + 0x1, &status->crcchkl, 30, 30, 4); + sja1105_unpack(p + 0x1, &status->ids, 29, 29, 4); + sja1105_unpack(p + 0x1, &status->crcchkg, 28, 28, 4); +} + +static int sja1105_status_get(struct sja1105_private *priv, + struct sja1105_status *status) +{ + const struct sja1105_regs *regs = priv->info->regs; + u8 packed_buf[4]; + int rc; + + rc = sja1105_spi_send_packed_buf(priv, SPI_READ, + regs->status, + packed_buf, 4); + if (rc < 0) + return rc; + + sja1105_status_unpack(packed_buf, status); + + return 0; +} + +/* Not const because unpacking priv->static_config into buffers and preparing + * for upload requires the recalculation of table CRCs and updating the + * structures with these. + */ +static int +static_config_buf_prepare_for_upload(struct sja1105_private *priv, + void *config_buf, int buf_len) +{ + struct sja1105_static_config *config = &priv->static_config; + struct sja1105_table_header final_header; + sja1105_config_valid_t valid; + char *final_header_ptr; + int crc_len; + + valid = sja1105_static_config_check_valid(config); + if (valid != SJA1105_CONFIG_OK) { + dev_err(&priv->spidev->dev, + sja1105_static_config_error_msg[valid]); + return -EINVAL; + } + + /* Write Device ID and config tables to config_buf */ + sja1105_static_config_pack(config_buf, config); + /* Recalculate CRC of the last header (right now 0xDEADBEEF). + * Don't include the CRC field itself. + */ + crc_len = buf_len - 4; + /* Read the whole table header */ + final_header_ptr = config_buf + buf_len - SJA1105_SIZE_TABLE_HEADER; + sja1105_table_header_packing(final_header_ptr, &final_header, UNPACK); + /* Modify */ + final_header.crc = sja1105_crc32(config_buf, crc_len); + /* Rewrite */ + sja1105_table_header_packing(final_header_ptr, &final_header, PACK); + + return 0; +} + +#define RETRIES 10 + +int sja1105_static_config_upload(struct sja1105_private *priv) +{ + unsigned long port_bitmap = GENMASK_ULL(SJA1105_NUM_PORTS - 1, 0); + struct sja1105_static_config *config = &priv->static_config; + const struct sja1105_regs *regs = priv->info->regs; + struct device *dev = &priv->spidev->dev; + struct sja1105_status status; + int rc, retries = RETRIES; + u8 *config_buf; + int buf_len; + + buf_len = sja1105_static_config_get_length(config); + config_buf = kcalloc(buf_len, sizeof(char), GFP_KERNEL); + if (!config_buf) + return -ENOMEM; + + rc = static_config_buf_prepare_for_upload(priv, config_buf, buf_len); + if (rc < 0) { + dev_err(dev, "Invalid config, cannot upload\n"); + return -EINVAL; + } + /* Prevent PHY jabbering during switch reset by inhibiting + * Tx on all ports and waiting for current packet to drain. + * Otherwise, the PHY will see an unterminated Ethernet packet. + */ + rc = sja1105_inhibit_tx(priv, &port_bitmap); + if (rc < 0) { + dev_err(dev, "Failed to inhibit Tx on ports\n"); + return -ENXIO; + } + /* Wait for an eventual egress packet to finish transmission + * (reach IFG). It is guaranteed that a second one will not + * follow, and that switch cold reset is thus safe + */ + usleep_range(500, 1000); + do { + /* Put the SJA1105 in programming mode */ + rc = sja1105_cold_reset(priv); + if (rc < 0) { + dev_err(dev, "Failed to reset switch, retrying...\n"); + continue; + } + /* Wait for the switch to come out of reset */ + usleep_range(1000, 5000); + /* Upload the static config to the device */ + rc = sja1105_spi_send_long_packed_buf(priv, SPI_WRITE, + regs->config, + config_buf, buf_len); + if (rc < 0) { + dev_err(dev, "Failed to upload config, retrying...\n"); + continue; + } + /* Check that SJA1105 responded well to the config upload */ + rc = sja1105_status_get(priv, &status); + if (rc < 0) + continue; + + if (status.ids == 1) { + dev_err(dev, "Mismatch between hardware and static config " + "device id. Wrote 0x%llx, wants 0x%llx\n", + config->device_id, priv->info->device_id); + continue; + } + if (status.crcchkl == 1) { + dev_err(dev, "Switch reported invalid local CRC on " + "the uploaded config, retrying...\n"); + continue; + } + if (status.crcchkg == 1) { + dev_err(dev, "Switch reported invalid global CRC on " + "the uploaded config, retrying...\n"); + continue; + } + if (status.configs == 0) { + dev_err(dev, "Switch reported that configuration is " + "invalid, retrying...\n"); + continue; + } + /* Success! */ + break; + } while (--retries); + + if (!retries) { + rc = -EIO; + dev_err(dev, "Failed to upload config to device, giving up\n"); + goto out; + } else if (retries != RETRIES) { + dev_info(dev, "Succeeded after %d tried\n", RETRIES - retries); + } + + dev_info(dev, "Reset switch and programmed static config\n"); +out: + kfree(config_buf); + return rc; +} + +static struct sja1105_regs sja1105et_regs = { + .device_id = 0x0, + .prod_id = 0x100BC3, + .status = 0x1, + .port_control = 0x11, + .config = 0x020000, + .rgu = 0x100440, + .pad_mii_tx = {0x100800, 0x100802, 0x100804, 0x100806, 0x100808}, + .rmii_pll1 = 0x10000A, + .cgu_idiv = {0x10000B, 0x10000C, 0x10000D, 0x10000E, 0x10000F}, + /* UM10944.pdf, Table 86, ACU Register overview */ + .rgmii_pad_mii_tx = {0x100800, 0x100802, 0x100804, 0x100806, 0x100808}, + .mac = {0x200, 0x202, 0x204, 0x206, 0x208}, + .mac_hl1 = {0x400, 0x410, 0x420, 0x430, 0x440}, + .mac_hl2 = {0x600, 0x610, 0x620, 0x630, 0x640}, + /* UM10944.pdf, Table 78, CGU Register overview */ + .mii_tx_clk = {0x100013, 0x10001A, 0x100021, 0x100028, 0x10002F}, + .mii_rx_clk = {0x100014, 0x10001B, 0x100022, 0x100029, 0x100030}, + .mii_ext_tx_clk = {0x100018, 0x10001F, 0x100026, 0x10002D, 0x100034}, + .mii_ext_rx_clk = {0x100019, 0x100020, 0x100027, 0x10002E, 0x100035}, + .rgmii_tx_clk = {0x100016, 0x10001D, 0x100024, 0x10002B, 0x100032}, + .rmii_ref_clk = {0x100015, 0x10001C, 0x100023, 0x10002A, 0x100031}, + .rmii_ext_tx_clk = {0x100018, 0x10001F, 0x100026, 0x10002D, 0x100034}, +}; + +static struct sja1105_regs sja1105pqrs_regs = { + .device_id = 0x0, + .prod_id = 0x100BC3, + .status = 0x1, + .port_control = 0x12, + .config = 0x020000, + .rgu = 0x100440, + .pad_mii_tx = {0x100800, 0x100802, 0x100804, 0x100806, 0x100808}, + .rmii_pll1 = 0x10000A, + .cgu_idiv = {0x10000B, 0x10000C, 0x10000D, 0x10000E, 0x10000F}, + /* UM10944.pdf, Table 86, ACU Register overview */ + .rgmii_pad_mii_tx = {0x100800, 0x100802, 0x100804, 0x100806, 0x100808}, + .mac = {0x200, 0x202, 0x204, 0x206, 0x208}, + .mac_hl1 = {0x400, 0x410, 0x420, 0x430, 0x440}, + .mac_hl2 = {0x600, 0x610, 0x620, 0x630, 0x640}, + /* UM11040.pdf, Table 114 */ + .mii_tx_clk = {0x100013, 0x100019, 0x10001F, 0x100025, 0x10002B}, + .mii_rx_clk = {0x100014, 0x10001A, 0x100020, 0x100026, 0x10002C}, + .mii_ext_tx_clk = {0x100017, 0x10001D, 0x100023, 0x100029, 0x10002F}, + .mii_ext_rx_clk = {0x100018, 0x10001E, 0x100024, 0x10002A, 0x100030}, + .rgmii_tx_clk = {0x100016, 0x10001C, 0x100022, 0x100028, 0x10002E}, + .rmii_ref_clk = {0x100015, 0x10001B, 0x100021, 0x100027, 0x10002D}, + .rmii_ext_tx_clk = {0x100017, 0x10001D, 0x100023, 0x100029, 0x10002F}, + .qlevel = {0x604, 0x614, 0x624, 0x634, 0x644}, +}; + +struct sja1105_info sja1105e_info = { + .device_id = SJA1105E_DEVICE_ID, + .part_no = SJA1105ET_PART_NO, + .static_ops = sja1105e_table_ops, + .dyn_ops = sja1105et_dyn_ops, + .reset_cmd = sja1105et_reset_cmd, + .regs = &sja1105et_regs, + .name = "SJA1105E", +}; +struct sja1105_info sja1105t_info = { + .device_id = SJA1105T_DEVICE_ID, + .part_no = SJA1105ET_PART_NO, + .static_ops = sja1105t_table_ops, + .dyn_ops = sja1105et_dyn_ops, + .reset_cmd = sja1105et_reset_cmd, + .regs = &sja1105et_regs, + .name = "SJA1105T", +}; +struct sja1105_info sja1105p_info = { + .device_id = SJA1105PR_DEVICE_ID, + .part_no = SJA1105P_PART_NO, + .static_ops = sja1105p_table_ops, + .dyn_ops = sja1105pqrs_dyn_ops, + .reset_cmd = sja1105pqrs_reset_cmd, + .regs = &sja1105pqrs_regs, + .name = "SJA1105P", +}; +struct sja1105_info sja1105q_info = { + .device_id = SJA1105QS_DEVICE_ID, + .part_no = SJA1105Q_PART_NO, + .static_ops = sja1105q_table_ops, + .dyn_ops = sja1105pqrs_dyn_ops, + .reset_cmd = sja1105pqrs_reset_cmd, + .regs = &sja1105pqrs_regs, + .name = "SJA1105Q", +}; +struct sja1105_info sja1105r_info = { + .device_id = SJA1105PR_DEVICE_ID, + .part_no = SJA1105R_PART_NO, + .static_ops = sja1105r_table_ops, + .dyn_ops = sja1105pqrs_dyn_ops, + .reset_cmd = sja1105pqrs_reset_cmd, + .regs = &sja1105pqrs_regs, + .name = "SJA1105R", +}; +struct sja1105_info sja1105s_info = { + .device_id = SJA1105QS_DEVICE_ID, + .part_no = SJA1105S_PART_NO, + .static_ops = sja1105s_table_ops, + .dyn_ops = sja1105pqrs_dyn_ops, + .regs = &sja1105pqrs_regs, + .reset_cmd = sja1105pqrs_reset_cmd, + .name = "SJA1105S", +}; diff --git a/drivers/net/dsa/sja1105/sja1105_static_config.c b/drivers/net/dsa/sja1105/sja1105_static_config.c new file mode 100644 index 000000000000..b3c992b0abb0 --- /dev/null +++ b/drivers/net/dsa/sja1105/sja1105_static_config.c @@ -0,0 +1,987 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* Copyright (c) 2016-2018, NXP Semiconductors + * Copyright (c) 2018-2019, Vladimir Oltean <olteanv@gmail.com> + */ +#include "sja1105_static_config.h" +#include <linux/crc32.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/errno.h> + +/* Convenience wrappers over the generic packing functions. These take into + * account the SJA1105 memory layout quirks and provide some level of + * programmer protection against incorrect API use. The errors are not expected + * to occur durring runtime, therefore printing and swallowing them here is + * appropriate instead of clutterring up higher-level code. + */ +void sja1105_pack(void *buf, const u64 *val, int start, int end, size_t len) +{ + int rc = packing(buf, (u64 *)val, start, end, len, + PACK, QUIRK_LSW32_IS_FIRST); + + if (likely(!rc)) + return; + + if (rc == -EINVAL) { + pr_err("Start bit (%d) expected to be larger than end (%d)\n", + start, end); + } else if (rc == -ERANGE) { + if ((start - end + 1) > 64) + pr_err("Field %d-%d too large for 64 bits!\n", + start, end); + else + pr_err("Cannot store %llx inside bits %d-%d (would truncate)\n", + *val, start, end); + } + dump_stack(); +} + +void sja1105_unpack(const void *buf, u64 *val, int start, int end, size_t len) +{ + int rc = packing((void *)buf, val, start, end, len, + UNPACK, QUIRK_LSW32_IS_FIRST); + + if (likely(!rc)) + return; + + if (rc == -EINVAL) + pr_err("Start bit (%d) expected to be larger than end (%d)\n", + start, end); + else if (rc == -ERANGE) + pr_err("Field %d-%d too large for 64 bits!\n", + start, end); + dump_stack(); +} + +void sja1105_packing(void *buf, u64 *val, int start, int end, + size_t len, enum packing_op op) +{ + int rc = packing(buf, val, start, end, len, op, QUIRK_LSW32_IS_FIRST); + + if (likely(!rc)) + return; + + if (rc == -EINVAL) { + pr_err("Start bit (%d) expected to be larger than end (%d)\n", + start, end); + } else if (rc == -ERANGE) { + if ((start - end + 1) > 64) + pr_err("Field %d-%d too large for 64 bits!\n", + start, end); + else + pr_err("Cannot store %llx inside bits %d-%d (would truncate)\n", + *val, start, end); + } + dump_stack(); +} + +/* Little-endian Ethernet CRC32 of data packed as big-endian u32 words */ +u32 sja1105_crc32(const void *buf, size_t len) +{ + unsigned int i; + u64 word; + u32 crc; + + /* seed */ + crc = ~0; + for (i = 0; i < len; i += 4) { + sja1105_unpack((void *)buf + i, &word, 31, 0, 4); + crc = crc32_le(crc, (u8 *)&word, 4); + } + return ~crc; +} + +static size_t sja1105et_general_params_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105ET_SIZE_GENERAL_PARAMS_ENTRY; + struct sja1105_general_params_entry *entry = entry_ptr; + + sja1105_packing(buf, &entry->vllupformat, 319, 319, size, op); + sja1105_packing(buf, &entry->mirr_ptacu, 318, 318, size, op); + sja1105_packing(buf, &entry->switchid, 317, 315, size, op); + sja1105_packing(buf, &entry->hostprio, 314, 312, size, op); + sja1105_packing(buf, &entry->mac_fltres1, 311, 264, size, op); + sja1105_packing(buf, &entry->mac_fltres0, 263, 216, size, op); + sja1105_packing(buf, &entry->mac_flt1, 215, 168, size, op); + sja1105_packing(buf, &entry->mac_flt0, 167, 120, size, op); + sja1105_packing(buf, &entry->incl_srcpt1, 119, 119, size, op); + sja1105_packing(buf, &entry->incl_srcpt0, 118, 118, size, op); + sja1105_packing(buf, &entry->send_meta1, 117, 117, size, op); + sja1105_packing(buf, &entry->send_meta0, 116, 116, size, op); + sja1105_packing(buf, &entry->casc_port, 115, 113, size, op); + sja1105_packing(buf, &entry->host_port, 112, 110, size, op); + sja1105_packing(buf, &entry->mirr_port, 109, 107, size, op); + sja1105_packing(buf, &entry->vlmarker, 106, 75, size, op); + sja1105_packing(buf, &entry->vlmask, 74, 43, size, op); + sja1105_packing(buf, &entry->tpid, 42, 27, size, op); + sja1105_packing(buf, &entry->ignore2stf, 26, 26, size, op); + sja1105_packing(buf, &entry->tpid2, 25, 10, size, op); + return size; +} + +static size_t +sja1105pqrs_general_params_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105PQRS_SIZE_GENERAL_PARAMS_ENTRY; + struct sja1105_general_params_entry *entry = entry_ptr; + + sja1105_packing(buf, &entry->vllupformat, 351, 351, size, op); + sja1105_packing(buf, &entry->mirr_ptacu, 350, 350, size, op); + sja1105_packing(buf, &entry->switchid, 349, 347, size, op); + sja1105_packing(buf, &entry->hostprio, 346, 344, size, op); + sja1105_packing(buf, &entry->mac_fltres1, 343, 296, size, op); + sja1105_packing(buf, &entry->mac_fltres0, 295, 248, size, op); + sja1105_packing(buf, &entry->mac_flt1, 247, 200, size, op); + sja1105_packing(buf, &entry->mac_flt0, 199, 152, size, op); + sja1105_packing(buf, &entry->incl_srcpt1, 151, 151, size, op); + sja1105_packing(buf, &entry->incl_srcpt0, 150, 150, size, op); + sja1105_packing(buf, &entry->send_meta1, 149, 149, size, op); + sja1105_packing(buf, &entry->send_meta0, 148, 148, size, op); + sja1105_packing(buf, &entry->casc_port, 147, 145, size, op); + sja1105_packing(buf, &entry->host_port, 144, 142, size, op); + sja1105_packing(buf, &entry->mirr_port, 141, 139, size, op); + sja1105_packing(buf, &entry->vlmarker, 138, 107, size, op); + sja1105_packing(buf, &entry->vlmask, 106, 75, size, op); + sja1105_packing(buf, &entry->tpid, 74, 59, size, op); + sja1105_packing(buf, &entry->ignore2stf, 58, 58, size, op); + sja1105_packing(buf, &entry->tpid2, 57, 42, size, op); + sja1105_packing(buf, &entry->queue_ts, 41, 41, size, op); + sja1105_packing(buf, &entry->egrmirrvid, 40, 29, size, op); + sja1105_packing(buf, &entry->egrmirrpcp, 28, 26, size, op); + sja1105_packing(buf, &entry->egrmirrdei, 25, 25, size, op); + sja1105_packing(buf, &entry->replay_port, 24, 22, size, op); + return size; +} + +static size_t +sja1105_l2_forwarding_params_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY; + struct sja1105_l2_forwarding_params_entry *entry = entry_ptr; + int offset, i; + + sja1105_packing(buf, &entry->max_dynp, 95, 93, size, op); + for (i = 0, offset = 13; i < 8; i++, offset += 10) + sja1105_packing(buf, &entry->part_spc[i], + offset + 9, offset + 0, size, op); + return size; +} + +size_t sja1105_l2_forwarding_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105_SIZE_L2_FORWARDING_ENTRY; + struct sja1105_l2_forwarding_entry *entry = entry_ptr; + int offset, i; + + sja1105_packing(buf, &entry->bc_domain, 63, 59, size, op); + sja1105_packing(buf, &entry->reach_port, 58, 54, size, op); + sja1105_packing(buf, &entry->fl_domain, 53, 49, size, op); + for (i = 0, offset = 25; i < 8; i++, offset += 3) + sja1105_packing(buf, &entry->vlan_pmap[i], + offset + 2, offset + 0, size, op); + return size; +} + +static size_t +sja1105et_l2_lookup_params_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105ET_SIZE_L2_LOOKUP_PARAMS_ENTRY; + struct sja1105_l2_lookup_params_entry *entry = entry_ptr; + + sja1105_packing(buf, &entry->maxage, 31, 17, size, op); + sja1105_packing(buf, &entry->dyn_tbsz, 16, 14, size, op); + sja1105_packing(buf, &entry->poly, 13, 6, size, op); + sja1105_packing(buf, &entry->shared_learn, 5, 5, size, op); + sja1105_packing(buf, &entry->no_enf_hostprt, 4, 4, size, op); + sja1105_packing(buf, &entry->no_mgmt_learn, 3, 3, size, op); + return size; +} + +static size_t +sja1105pqrs_l2_lookup_params_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105PQRS_SIZE_L2_LOOKUP_PARAMS_ENTRY; + struct sja1105_l2_lookup_params_entry *entry = entry_ptr; + + sja1105_packing(buf, &entry->maxage, 57, 43, size, op); + sja1105_packing(buf, &entry->shared_learn, 27, 27, size, op); + sja1105_packing(buf, &entry->no_enf_hostprt, 26, 26, size, op); + sja1105_packing(buf, &entry->no_mgmt_learn, 25, 25, size, op); + return size; +} + +size_t sja1105et_l2_lookup_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105ET_SIZE_L2_LOOKUP_ENTRY; + struct sja1105_l2_lookup_entry *entry = entry_ptr; + + sja1105_packing(buf, &entry->vlanid, 95, 84, size, op); + sja1105_packing(buf, &entry->macaddr, 83, 36, size, op); + sja1105_packing(buf, &entry->destports, 35, 31, size, op); + sja1105_packing(buf, &entry->enfport, 30, 30, size, op); + sja1105_packing(buf, &entry->index, 29, 20, size, op); + return size; +} + +size_t sja1105pqrs_l2_lookup_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY; + struct sja1105_l2_lookup_entry *entry = entry_ptr; + + /* These are static L2 lookup entries, so the structure + * should match UM11040 Table 16/17 definitions when + * LOCKEDS is 1. + */ + sja1105_packing(buf, &entry->vlanid, 81, 70, size, op); + sja1105_packing(buf, &entry->macaddr, 69, 22, size, op); + sja1105_packing(buf, &entry->destports, 21, 17, size, op); + sja1105_packing(buf, &entry->enfport, 16, 16, size, op); + sja1105_packing(buf, &entry->index, 15, 6, size, op); + return size; +} + +static size_t sja1105_l2_policing_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105_SIZE_L2_POLICING_ENTRY; + struct sja1105_l2_policing_entry *entry = entry_ptr; + + sja1105_packing(buf, &entry->sharindx, 63, 58, size, op); + sja1105_packing(buf, &entry->smax, 57, 42, size, op); + sja1105_packing(buf, &entry->rate, 41, 26, size, op); + sja1105_packing(buf, &entry->maxlen, 25, 15, size, op); + sja1105_packing(buf, &entry->partition, 14, 12, size, op); + return size; +} + +static size_t sja1105et_mac_config_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105ET_SIZE_MAC_CONFIG_ENTRY; + struct sja1105_mac_config_entry *entry = entry_ptr; + int offset, i; + + for (i = 0, offset = 72; i < 8; i++, offset += 19) { + sja1105_packing(buf, &entry->enabled[i], + offset + 0, offset + 0, size, op); + sja1105_packing(buf, &entry->base[i], + offset + 9, offset + 1, size, op); + sja1105_packing(buf, &entry->top[i], + offset + 18, offset + 10, size, op); + } + sja1105_packing(buf, &entry->ifg, 71, 67, size, op); + sja1105_packing(buf, &entry->speed, 66, 65, size, op); + sja1105_packing(buf, &entry->tp_delin, 64, 49, size, op); + sja1105_packing(buf, &entry->tp_delout, 48, 33, size, op); + sja1105_packing(buf, &entry->maxage, 32, 25, size, op); + sja1105_packing(buf, &entry->vlanprio, 24, 22, size, op); + sja1105_packing(buf, &entry->vlanid, 21, 10, size, op); + sja1105_packing(buf, &entry->ing_mirr, 9, 9, size, op); + sja1105_packing(buf, &entry->egr_mirr, 8, 8, size, op); + sja1105_packing(buf, &entry->drpnona664, 7, 7, size, op); + sja1105_packing(buf, &entry->drpdtag, 6, 6, size, op); + sja1105_packing(buf, &entry->drpuntag, 5, 5, size, op); + sja1105_packing(buf, &entry->retag, 4, 4, size, op); + sja1105_packing(buf, &entry->dyn_learn, 3, 3, size, op); + sja1105_packing(buf, &entry->egress, 2, 2, size, op); + sja1105_packing(buf, &entry->ingress, 1, 1, size, op); + return size; +} + +size_t sja1105pqrs_mac_config_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105PQRS_SIZE_MAC_CONFIG_ENTRY; + struct sja1105_mac_config_entry *entry = entry_ptr; + int offset, i; + + for (i = 0, offset = 104; i < 8; i++, offset += 19) { + sja1105_packing(buf, &entry->enabled[i], + offset + 0, offset + 0, size, op); + sja1105_packing(buf, &entry->base[i], + offset + 9, offset + 1, size, op); + sja1105_packing(buf, &entry->top[i], + offset + 18, offset + 10, size, op); + } + sja1105_packing(buf, &entry->ifg, 103, 99, size, op); + sja1105_packing(buf, &entry->speed, 98, 97, size, op); + sja1105_packing(buf, &entry->tp_delin, 96, 81, size, op); + sja1105_packing(buf, &entry->tp_delout, 80, 65, size, op); + sja1105_packing(buf, &entry->maxage, 64, 57, size, op); + sja1105_packing(buf, &entry->vlanprio, 56, 54, size, op); + sja1105_packing(buf, &entry->vlanid, 53, 42, size, op); + sja1105_packing(buf, &entry->ing_mirr, 41, 41, size, op); + sja1105_packing(buf, &entry->egr_mirr, 40, 40, size, op); + sja1105_packing(buf, &entry->drpnona664, 39, 39, size, op); + sja1105_packing(buf, &entry->drpdtag, 38, 38, size, op); + sja1105_packing(buf, &entry->drpuntag, 35, 35, size, op); + sja1105_packing(buf, &entry->retag, 34, 34, size, op); + sja1105_packing(buf, &entry->dyn_learn, 33, 33, size, op); + sja1105_packing(buf, &entry->egress, 32, 32, size, op); + sja1105_packing(buf, &entry->ingress, 31, 31, size, op); + return size; +} + +size_t sja1105_vlan_lookup_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105_SIZE_VLAN_LOOKUP_ENTRY; + struct sja1105_vlan_lookup_entry *entry = entry_ptr; + + sja1105_packing(buf, &entry->ving_mirr, 63, 59, size, op); + sja1105_packing(buf, &entry->vegr_mirr, 58, 54, size, op); + sja1105_packing(buf, &entry->vmemb_port, 53, 49, size, op); + sja1105_packing(buf, &entry->vlan_bc, 48, 44, size, op); + sja1105_packing(buf, &entry->tag_port, 43, 39, size, op); + sja1105_packing(buf, &entry->vlanid, 38, 27, size, op); + return size; +} + +static size_t sja1105_xmii_params_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105_SIZE_XMII_PARAMS_ENTRY; + struct sja1105_xmii_params_entry *entry = entry_ptr; + int offset, i; + + for (i = 0, offset = 17; i < 5; i++, offset += 3) { + sja1105_packing(buf, &entry->xmii_mode[i], + offset + 1, offset + 0, size, op); + sja1105_packing(buf, &entry->phy_mac[i], + offset + 2, offset + 2, size, op); + } + return size; +} + +size_t sja1105_table_header_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105_SIZE_TABLE_HEADER; + struct sja1105_table_header *entry = entry_ptr; + + sja1105_packing(buf, &entry->block_id, 31, 24, size, op); + sja1105_packing(buf, &entry->len, 55, 32, size, op); + sja1105_packing(buf, &entry->crc, 95, 64, size, op); + return size; +} + +/* WARNING: the *hdr pointer is really non-const, because it is + * modifying the CRC of the header for a 2-stage packing operation + */ +void +sja1105_table_header_pack_with_crc(void *buf, struct sja1105_table_header *hdr) +{ + /* First pack the table as-is, then calculate the CRC, and + * finally put the proper CRC into the packed buffer + */ + memset(buf, 0, SJA1105_SIZE_TABLE_HEADER); + sja1105_table_header_packing(buf, hdr, PACK); + hdr->crc = sja1105_crc32(buf, SJA1105_SIZE_TABLE_HEADER - 4); + sja1105_pack(buf + SJA1105_SIZE_TABLE_HEADER - 4, &hdr->crc, 31, 0, 4); +} + +static void sja1105_table_write_crc(u8 *table_start, u8 *crc_ptr) +{ + u64 computed_crc; + int len_bytes; + + len_bytes = (uintptr_t)(crc_ptr - table_start); + computed_crc = sja1105_crc32(table_start, len_bytes); + sja1105_pack(crc_ptr, &computed_crc, 31, 0, 4); +} + +/* The block IDs that the switches support are unfortunately sparse, so keep a + * mapping table to "block indices" and translate back and forth so that we + * don't waste useless memory in struct sja1105_static_config. + * Also, since the block id comes from essentially untrusted input (unpacking + * the static config from userspace) it has to be sanitized (range-checked) + * before blindly indexing kernel memory with the blk_idx. + */ +static u64 blk_id_map[BLK_IDX_MAX] = { + [BLK_IDX_L2_LOOKUP] = BLKID_L2_LOOKUP, + [BLK_IDX_L2_POLICING] = BLKID_L2_POLICING, + [BLK_IDX_VLAN_LOOKUP] = BLKID_VLAN_LOOKUP, + [BLK_IDX_L2_FORWARDING] = BLKID_L2_FORWARDING, + [BLK_IDX_MAC_CONFIG] = BLKID_MAC_CONFIG, + [BLK_IDX_L2_LOOKUP_PARAMS] = BLKID_L2_LOOKUP_PARAMS, + [BLK_IDX_L2_FORWARDING_PARAMS] = BLKID_L2_FORWARDING_PARAMS, + [BLK_IDX_GENERAL_PARAMS] = BLKID_GENERAL_PARAMS, + [BLK_IDX_XMII_PARAMS] = BLKID_XMII_PARAMS, +}; + +const char *sja1105_static_config_error_msg[] = { + [SJA1105_CONFIG_OK] = "", + [SJA1105_MISSING_L2_POLICING_TABLE] = + "l2-policing-table needs to have at least one entry", + [SJA1105_MISSING_L2_FORWARDING_TABLE] = + "l2-forwarding-table is either missing or incomplete", + [SJA1105_MISSING_L2_FORWARDING_PARAMS_TABLE] = + "l2-forwarding-parameters-table is missing", + [SJA1105_MISSING_GENERAL_PARAMS_TABLE] = + "general-parameters-table is missing", + [SJA1105_MISSING_VLAN_TABLE] = + "vlan-lookup-table needs to have at least the default untagged VLAN", + [SJA1105_MISSING_XMII_TABLE] = + "xmii-table is missing", + [SJA1105_MISSING_MAC_TABLE] = + "mac-configuration-table needs to contain an entry for each port", + [SJA1105_OVERCOMMITTED_FRAME_MEMORY] = + "Not allowed to overcommit frame memory. L2 memory partitions " + "and VL memory partitions share the same space. The sum of all " + "16 memory partitions is not allowed to be larger than 929 " + "128-byte blocks (or 910 with retagging). Please adjust " + "l2-forwarding-parameters-table.part_spc and/or " + "vl-forwarding-parameters-table.partspc.", +}; + +sja1105_config_valid_t +static_config_check_memory_size(const struct sja1105_table *tables) +{ + const struct sja1105_l2_forwarding_params_entry *l2_fwd_params; + int i, mem = 0; + + l2_fwd_params = tables[BLK_IDX_L2_FORWARDING_PARAMS].entries; + + for (i = 0; i < 8; i++) + mem += l2_fwd_params->part_spc[i]; + + if (mem > SJA1105_MAX_FRAME_MEMORY) + return SJA1105_OVERCOMMITTED_FRAME_MEMORY; + + return SJA1105_CONFIG_OK; +} + +sja1105_config_valid_t +sja1105_static_config_check_valid(const struct sja1105_static_config *config) +{ + const struct sja1105_table *tables = config->tables; +#define IS_FULL(blk_idx) \ + (tables[blk_idx].entry_count == tables[blk_idx].ops->max_entry_count) + + if (tables[BLK_IDX_L2_POLICING].entry_count == 0) + return SJA1105_MISSING_L2_POLICING_TABLE; + + if (tables[BLK_IDX_VLAN_LOOKUP].entry_count == 0) + return SJA1105_MISSING_VLAN_TABLE; + + if (!IS_FULL(BLK_IDX_L2_FORWARDING)) + return SJA1105_MISSING_L2_FORWARDING_TABLE; + + if (!IS_FULL(BLK_IDX_MAC_CONFIG)) + return SJA1105_MISSING_MAC_TABLE; + + if (!IS_FULL(BLK_IDX_L2_FORWARDING_PARAMS)) + return SJA1105_MISSING_L2_FORWARDING_PARAMS_TABLE; + + if (!IS_FULL(BLK_IDX_GENERAL_PARAMS)) + return SJA1105_MISSING_GENERAL_PARAMS_TABLE; + + if (!IS_FULL(BLK_IDX_XMII_PARAMS)) + return SJA1105_MISSING_XMII_TABLE; + + return static_config_check_memory_size(tables); +#undef IS_FULL +} + +void +sja1105_static_config_pack(void *buf, struct sja1105_static_config *config) +{ + struct sja1105_table_header header = {0}; + enum sja1105_blk_idx i; + char *p = buf; + int j; + + sja1105_pack(p, &config->device_id, 31, 0, 4); + p += SJA1105_SIZE_DEVICE_ID; + + for (i = 0; i < BLK_IDX_MAX; i++) { + const struct sja1105_table *table; + char *table_start; + + table = &config->tables[i]; + if (!table->entry_count) + continue; + + header.block_id = blk_id_map[i]; + header.len = table->entry_count * + table->ops->packed_entry_size / 4; + sja1105_table_header_pack_with_crc(p, &header); + p += SJA1105_SIZE_TABLE_HEADER; + table_start = p; + for (j = 0; j < table->entry_count; j++) { + u8 *entry_ptr = table->entries; + + entry_ptr += j * table->ops->unpacked_entry_size; + memset(p, 0, table->ops->packed_entry_size); + table->ops->packing(p, entry_ptr, PACK); + p += table->ops->packed_entry_size; + } + sja1105_table_write_crc(table_start, p); + p += 4; + } + /* Final header: + * Block ID does not matter + * Length of 0 marks that header is final + * CRC will be replaced on-the-fly on "config upload" + */ + header.block_id = 0; + header.len = 0; + header.crc = 0xDEADBEEF; + memset(p, 0, SJA1105_SIZE_TABLE_HEADER); + sja1105_table_header_packing(p, &header, PACK); +} + +size_t +sja1105_static_config_get_length(const struct sja1105_static_config *config) +{ + unsigned int sum; + unsigned int header_count; + enum sja1105_blk_idx i; + + /* Ending header */ + header_count = 1; + sum = SJA1105_SIZE_DEVICE_ID; + + /* Tables (headers and entries) */ + for (i = 0; i < BLK_IDX_MAX; i++) { + const struct sja1105_table *table; + + table = &config->tables[i]; + if (table->entry_count) + header_count++; + + sum += table->ops->packed_entry_size * table->entry_count; + } + /* Headers have an additional CRC at the end */ + sum += header_count * (SJA1105_SIZE_TABLE_HEADER + 4); + /* Last header does not have an extra CRC because there is no data */ + sum -= 4; + + return sum; +} + +/* Compatibility matrices */ + +/* SJA1105E: First generation, no TTEthernet */ +struct sja1105_table_ops sja1105e_table_ops[BLK_IDX_MAX] = { + [BLK_IDX_L2_LOOKUP] = { + .packing = sja1105et_l2_lookup_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_lookup_entry), + .packed_entry_size = SJA1105ET_SIZE_L2_LOOKUP_ENTRY, + .max_entry_count = SJA1105_MAX_L2_LOOKUP_COUNT, + }, + [BLK_IDX_L2_POLICING] = { + .packing = sja1105_l2_policing_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_policing_entry), + .packed_entry_size = SJA1105_SIZE_L2_POLICING_ENTRY, + .max_entry_count = SJA1105_MAX_L2_POLICING_COUNT, + }, + [BLK_IDX_VLAN_LOOKUP] = { + .packing = sja1105_vlan_lookup_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_vlan_lookup_entry), + .packed_entry_size = SJA1105_SIZE_VLAN_LOOKUP_ENTRY, + .max_entry_count = SJA1105_MAX_VLAN_LOOKUP_COUNT, + }, + [BLK_IDX_L2_FORWARDING] = { + .packing = sja1105_l2_forwarding_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_forwarding_entry), + .packed_entry_size = SJA1105_SIZE_L2_FORWARDING_ENTRY, + .max_entry_count = SJA1105_MAX_L2_FORWARDING_COUNT, + }, + [BLK_IDX_MAC_CONFIG] = { + .packing = sja1105et_mac_config_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_mac_config_entry), + .packed_entry_size = SJA1105ET_SIZE_MAC_CONFIG_ENTRY, + .max_entry_count = SJA1105_MAX_MAC_CONFIG_COUNT, + }, + [BLK_IDX_L2_LOOKUP_PARAMS] = { + .packing = sja1105et_l2_lookup_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_lookup_params_entry), + .packed_entry_size = SJA1105ET_SIZE_L2_LOOKUP_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_L2_LOOKUP_PARAMS_COUNT, + }, + [BLK_IDX_L2_FORWARDING_PARAMS] = { + .packing = sja1105_l2_forwarding_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_forwarding_params_entry), + .packed_entry_size = SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT, + }, + [BLK_IDX_GENERAL_PARAMS] = { + .packing = sja1105et_general_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_general_params_entry), + .packed_entry_size = SJA1105ET_SIZE_GENERAL_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_GENERAL_PARAMS_COUNT, + }, + [BLK_IDX_XMII_PARAMS] = { + .packing = sja1105_xmii_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_xmii_params_entry), + .packed_entry_size = SJA1105_SIZE_XMII_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_XMII_PARAMS_COUNT, + }, +}; + +/* SJA1105T: First generation, TTEthernet */ +struct sja1105_table_ops sja1105t_table_ops[BLK_IDX_MAX] = { + [BLK_IDX_L2_LOOKUP] = { + .packing = sja1105et_l2_lookup_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_lookup_entry), + .packed_entry_size = SJA1105ET_SIZE_L2_LOOKUP_ENTRY, + .max_entry_count = SJA1105_MAX_L2_LOOKUP_COUNT, + }, + [BLK_IDX_L2_POLICING] = { + .packing = sja1105_l2_policing_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_policing_entry), + .packed_entry_size = SJA1105_SIZE_L2_POLICING_ENTRY, + .max_entry_count = SJA1105_MAX_L2_POLICING_COUNT, + }, + [BLK_IDX_VLAN_LOOKUP] = { + .packing = sja1105_vlan_lookup_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_vlan_lookup_entry), + .packed_entry_size = SJA1105_SIZE_VLAN_LOOKUP_ENTRY, + .max_entry_count = SJA1105_MAX_VLAN_LOOKUP_COUNT, + }, + [BLK_IDX_L2_FORWARDING] = { + .packing = sja1105_l2_forwarding_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_forwarding_entry), + .packed_entry_size = SJA1105_SIZE_L2_FORWARDING_ENTRY, + .max_entry_count = SJA1105_MAX_L2_FORWARDING_COUNT, + }, + [BLK_IDX_MAC_CONFIG] = { + .packing = sja1105et_mac_config_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_mac_config_entry), + .packed_entry_size = SJA1105ET_SIZE_MAC_CONFIG_ENTRY, + .max_entry_count = SJA1105_MAX_MAC_CONFIG_COUNT, + }, + [BLK_IDX_L2_LOOKUP_PARAMS] = { + .packing = sja1105et_l2_lookup_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_lookup_params_entry), + .packed_entry_size = SJA1105ET_SIZE_L2_LOOKUP_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_L2_LOOKUP_PARAMS_COUNT, + }, + [BLK_IDX_L2_FORWARDING_PARAMS] = { + .packing = sja1105_l2_forwarding_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_forwarding_params_entry), + .packed_entry_size = SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT, + }, + [BLK_IDX_GENERAL_PARAMS] = { + .packing = sja1105et_general_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_general_params_entry), + .packed_entry_size = SJA1105ET_SIZE_GENERAL_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_GENERAL_PARAMS_COUNT, + }, + [BLK_IDX_XMII_PARAMS] = { + .packing = sja1105_xmii_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_xmii_params_entry), + .packed_entry_size = SJA1105_SIZE_XMII_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_XMII_PARAMS_COUNT, + }, +}; + +/* SJA1105P: Second generation, no TTEthernet, no SGMII */ +struct sja1105_table_ops sja1105p_table_ops[BLK_IDX_MAX] = { + [BLK_IDX_L2_LOOKUP] = { + .packing = sja1105pqrs_l2_lookup_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_lookup_entry), + .packed_entry_size = SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY, + .max_entry_count = SJA1105_MAX_L2_LOOKUP_COUNT, + }, + [BLK_IDX_L2_POLICING] = { + .packing = sja1105_l2_policing_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_policing_entry), + .packed_entry_size = SJA1105_SIZE_L2_POLICING_ENTRY, + .max_entry_count = SJA1105_MAX_L2_POLICING_COUNT, + }, + [BLK_IDX_VLAN_LOOKUP] = { + .packing = sja1105_vlan_lookup_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_vlan_lookup_entry), + .packed_entry_size = SJA1105_SIZE_VLAN_LOOKUP_ENTRY, + .max_entry_count = SJA1105_MAX_VLAN_LOOKUP_COUNT, + }, + [BLK_IDX_L2_FORWARDING] = { + .packing = sja1105_l2_forwarding_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_forwarding_entry), + .packed_entry_size = SJA1105_SIZE_L2_FORWARDING_ENTRY, + .max_entry_count = SJA1105_MAX_L2_FORWARDING_COUNT, + }, + [BLK_IDX_MAC_CONFIG] = { + .packing = sja1105pqrs_mac_config_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_mac_config_entry), + .packed_entry_size = SJA1105PQRS_SIZE_MAC_CONFIG_ENTRY, + .max_entry_count = SJA1105_MAX_MAC_CONFIG_COUNT, + }, + [BLK_IDX_L2_LOOKUP_PARAMS] = { + .packing = sja1105pqrs_l2_lookup_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_lookup_params_entry), + .packed_entry_size = SJA1105PQRS_SIZE_L2_LOOKUP_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_L2_LOOKUP_PARAMS_COUNT, + }, + [BLK_IDX_L2_FORWARDING_PARAMS] = { + .packing = sja1105_l2_forwarding_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_forwarding_params_entry), + .packed_entry_size = SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT, + }, + [BLK_IDX_GENERAL_PARAMS] = { + .packing = sja1105pqrs_general_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_general_params_entry), + .packed_entry_size = SJA1105PQRS_SIZE_GENERAL_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_GENERAL_PARAMS_COUNT, + }, + [BLK_IDX_XMII_PARAMS] = { + .packing = sja1105_xmii_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_xmii_params_entry), + .packed_entry_size = SJA1105_SIZE_XMII_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_XMII_PARAMS_COUNT, + }, +}; + +/* SJA1105Q: Second generation, TTEthernet, no SGMII */ +struct sja1105_table_ops sja1105q_table_ops[BLK_IDX_MAX] = { + [BLK_IDX_L2_LOOKUP] = { + .packing = sja1105pqrs_l2_lookup_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_lookup_entry), + .packed_entry_size = SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY, + .max_entry_count = SJA1105_MAX_L2_LOOKUP_COUNT, + }, + [BLK_IDX_L2_POLICING] = { + .packing = sja1105_l2_policing_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_policing_entry), + .packed_entry_size = SJA1105_SIZE_L2_POLICING_ENTRY, + .max_entry_count = SJA1105_MAX_L2_POLICING_COUNT, + }, + [BLK_IDX_VLAN_LOOKUP] = { + .packing = sja1105_vlan_lookup_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_vlan_lookup_entry), + .packed_entry_size = SJA1105_SIZE_VLAN_LOOKUP_ENTRY, + .max_entry_count = SJA1105_MAX_VLAN_LOOKUP_COUNT, + }, + [BLK_IDX_L2_FORWARDING] = { + .packing = sja1105_l2_forwarding_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_forwarding_entry), + .packed_entry_size = SJA1105_SIZE_L2_FORWARDING_ENTRY, + .max_entry_count = SJA1105_MAX_L2_FORWARDING_COUNT, + }, + [BLK_IDX_MAC_CONFIG] = { + .packing = sja1105pqrs_mac_config_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_mac_config_entry), + .packed_entry_size = SJA1105PQRS_SIZE_MAC_CONFIG_ENTRY, + .max_entry_count = SJA1105_MAX_MAC_CONFIG_COUNT, + }, + [BLK_IDX_L2_LOOKUP_PARAMS] = { + .packing = sja1105pqrs_l2_lookup_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_lookup_params_entry), + .packed_entry_size = SJA1105PQRS_SIZE_L2_LOOKUP_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_L2_LOOKUP_PARAMS_COUNT, + }, + [BLK_IDX_L2_FORWARDING_PARAMS] = { + .packing = sja1105_l2_forwarding_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_forwarding_params_entry), + .packed_entry_size = SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT, + }, + [BLK_IDX_GENERAL_PARAMS] = { + .packing = sja1105pqrs_general_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_general_params_entry), + .packed_entry_size = SJA1105PQRS_SIZE_GENERAL_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_GENERAL_PARAMS_COUNT, + }, + [BLK_IDX_XMII_PARAMS] = { + .packing = sja1105_xmii_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_xmii_params_entry), + .packed_entry_size = SJA1105_SIZE_XMII_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_XMII_PARAMS_COUNT, + }, +}; + +/* SJA1105R: Second generation, no TTEthernet, SGMII */ +struct sja1105_table_ops sja1105r_table_ops[BLK_IDX_MAX] = { + [BLK_IDX_L2_LOOKUP] = { + .packing = sja1105pqrs_l2_lookup_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_lookup_entry), + .packed_entry_size = SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY, + .max_entry_count = SJA1105_MAX_L2_LOOKUP_COUNT, + }, + [BLK_IDX_L2_POLICING] = { + .packing = sja1105_l2_policing_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_policing_entry), + .packed_entry_size = SJA1105_SIZE_L2_POLICING_ENTRY, + .max_entry_count = SJA1105_MAX_L2_POLICING_COUNT, + }, + [BLK_IDX_VLAN_LOOKUP] = { + .packing = sja1105_vlan_lookup_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_vlan_lookup_entry), + .packed_entry_size = SJA1105_SIZE_VLAN_LOOKUP_ENTRY, + .max_entry_count = SJA1105_MAX_VLAN_LOOKUP_COUNT, + }, + [BLK_IDX_L2_FORWARDING] = { + .packing = sja1105_l2_forwarding_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_forwarding_entry), + .packed_entry_size = SJA1105_SIZE_L2_FORWARDING_ENTRY, + .max_entry_count = SJA1105_MAX_L2_FORWARDING_COUNT, + }, + [BLK_IDX_MAC_CONFIG] = { + .packing = sja1105pqrs_mac_config_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_mac_config_entry), + .packed_entry_size = SJA1105PQRS_SIZE_MAC_CONFIG_ENTRY, + .max_entry_count = SJA1105_MAX_MAC_CONFIG_COUNT, + }, + [BLK_IDX_L2_LOOKUP_PARAMS] = { + .packing = sja1105pqrs_l2_lookup_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_lookup_params_entry), + .packed_entry_size = SJA1105PQRS_SIZE_L2_LOOKUP_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_L2_LOOKUP_PARAMS_COUNT, + }, + [BLK_IDX_L2_FORWARDING_PARAMS] = { + .packing = sja1105_l2_forwarding_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_forwarding_params_entry), + .packed_entry_size = SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT, + }, + [BLK_IDX_GENERAL_PARAMS] = { + .packing = sja1105pqrs_general_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_general_params_entry), + .packed_entry_size = SJA1105PQRS_SIZE_GENERAL_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_GENERAL_PARAMS_COUNT, + }, + [BLK_IDX_XMII_PARAMS] = { + .packing = sja1105_xmii_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_xmii_params_entry), + .packed_entry_size = SJA1105_SIZE_XMII_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_XMII_PARAMS_COUNT, + }, +}; + +/* SJA1105S: Second generation, TTEthernet, SGMII */ +struct sja1105_table_ops sja1105s_table_ops[BLK_IDX_MAX] = { + [BLK_IDX_L2_LOOKUP] = { + .packing = sja1105pqrs_l2_lookup_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_lookup_entry), + .packed_entry_size = SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY, + .max_entry_count = SJA1105_MAX_L2_LOOKUP_COUNT, + }, + [BLK_IDX_L2_POLICING] = { + .packing = sja1105_l2_policing_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_policing_entry), + .packed_entry_size = SJA1105_SIZE_L2_POLICING_ENTRY, + .max_entry_count = SJA1105_MAX_L2_POLICING_COUNT, + }, + [BLK_IDX_VLAN_LOOKUP] = { + .packing = sja1105_vlan_lookup_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_vlan_lookup_entry), + .packed_entry_size = SJA1105_SIZE_VLAN_LOOKUP_ENTRY, + .max_entry_count = SJA1105_MAX_VLAN_LOOKUP_COUNT, + }, + [BLK_IDX_L2_FORWARDING] = { + .packing = sja1105_l2_forwarding_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_forwarding_entry), + .packed_entry_size = SJA1105_SIZE_L2_FORWARDING_ENTRY, + .max_entry_count = SJA1105_MAX_L2_FORWARDING_COUNT, + }, + [BLK_IDX_MAC_CONFIG] = { + .packing = sja1105pqrs_mac_config_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_mac_config_entry), + .packed_entry_size = SJA1105PQRS_SIZE_MAC_CONFIG_ENTRY, + .max_entry_count = SJA1105_MAX_MAC_CONFIG_COUNT, + }, + [BLK_IDX_L2_LOOKUP_PARAMS] = { + .packing = sja1105pqrs_l2_lookup_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_lookup_params_entry), + .packed_entry_size = SJA1105PQRS_SIZE_L2_LOOKUP_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_L2_LOOKUP_PARAMS_COUNT, + }, + [BLK_IDX_L2_FORWARDING_PARAMS] = { + .packing = sja1105_l2_forwarding_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_forwarding_params_entry), + .packed_entry_size = SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT, + }, + [BLK_IDX_GENERAL_PARAMS] = { + .packing = sja1105pqrs_general_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_general_params_entry), + .packed_entry_size = SJA1105PQRS_SIZE_GENERAL_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_GENERAL_PARAMS_COUNT, + }, + [BLK_IDX_XMII_PARAMS] = { + .packing = sja1105_xmii_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_xmii_params_entry), + .packed_entry_size = SJA1105_SIZE_XMII_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_XMII_PARAMS_COUNT, + }, +}; + +int sja1105_static_config_init(struct sja1105_static_config *config, + const struct sja1105_table_ops *static_ops, + u64 device_id) +{ + enum sja1105_blk_idx i; + + *config = (struct sja1105_static_config) {0}; + + /* Transfer static_ops array from priv into per-table ops + * for handier access + */ + for (i = 0; i < BLK_IDX_MAX; i++) + config->tables[i].ops = &static_ops[i]; + + config->device_id = device_id; + return 0; +} + +void sja1105_static_config_free(struct sja1105_static_config *config) +{ + enum sja1105_blk_idx i; + + for (i = 0; i < BLK_IDX_MAX; i++) { + if (config->tables[i].entry_count) { + kfree(config->tables[i].entries); + config->tables[i].entry_count = 0; + } + } +} + +int sja1105_table_delete_entry(struct sja1105_table *table, int i) +{ + size_t entry_size = table->ops->unpacked_entry_size; + u8 *entries = table->entries; + + if (i > table->entry_count) + return -ERANGE; + + memmove(entries + i * entry_size, entries + (i + 1) * entry_size, + (table->entry_count - i) * entry_size); + + table->entry_count--; + + return 0; +} + +/* No pointers to table->entries should be kept when this is called. */ +int sja1105_table_resize(struct sja1105_table *table, size_t new_count) +{ + size_t entry_size = table->ops->unpacked_entry_size; + void *new_entries, *old_entries = table->entries; + + if (new_count > table->ops->max_entry_count) + return -ERANGE; + + new_entries = kcalloc(new_count, entry_size, GFP_KERNEL); + if (!new_entries) + return -ENOMEM; + + memcpy(new_entries, old_entries, min(new_count, table->entry_count) * + entry_size); + + table->entries = new_entries; + table->entry_count = new_count; + kfree(old_entries); + return 0; +} diff --git a/drivers/net/dsa/sja1105/sja1105_static_config.h b/drivers/net/dsa/sja1105/sja1105_static_config.h new file mode 100644 index 000000000000..069ca8fd059c --- /dev/null +++ b/drivers/net/dsa/sja1105/sja1105_static_config.h @@ -0,0 +1,253 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright (c) 2016-2018, NXP Semiconductors + * Copyright (c) 2018-2019, Vladimir Oltean <olteanv@gmail.com> + */ +#ifndef _SJA1105_STATIC_CONFIG_H +#define _SJA1105_STATIC_CONFIG_H + +#include <linux/packing.h> +#include <linux/types.h> +#include <asm/types.h> + +#define SJA1105_SIZE_DEVICE_ID 4 +#define SJA1105_SIZE_TABLE_HEADER 12 +#define SJA1105_SIZE_L2_POLICING_ENTRY 8 +#define SJA1105_SIZE_VLAN_LOOKUP_ENTRY 8 +#define SJA1105_SIZE_L2_FORWARDING_ENTRY 8 +#define SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY 12 +#define SJA1105_SIZE_XMII_PARAMS_ENTRY 4 +#define SJA1105ET_SIZE_L2_LOOKUP_ENTRY 12 +#define SJA1105ET_SIZE_MAC_CONFIG_ENTRY 28 +#define SJA1105ET_SIZE_L2_LOOKUP_PARAMS_ENTRY 4 +#define SJA1105ET_SIZE_GENERAL_PARAMS_ENTRY 40 +#define SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY 20 +#define SJA1105PQRS_SIZE_MAC_CONFIG_ENTRY 32 +#define SJA1105PQRS_SIZE_L2_LOOKUP_PARAMS_ENTRY 16 +#define SJA1105PQRS_SIZE_GENERAL_PARAMS_ENTRY 44 + +/* UM10944.pdf Page 11, Table 2. Configuration Blocks */ +enum { + BLKID_L2_LOOKUP = 0x05, + BLKID_L2_POLICING = 0x06, + BLKID_VLAN_LOOKUP = 0x07, + BLKID_L2_FORWARDING = 0x08, + BLKID_MAC_CONFIG = 0x09, + BLKID_L2_LOOKUP_PARAMS = 0x0D, + BLKID_L2_FORWARDING_PARAMS = 0x0E, + BLKID_GENERAL_PARAMS = 0x11, + BLKID_XMII_PARAMS = 0x4E, +}; + +enum sja1105_blk_idx { + BLK_IDX_L2_LOOKUP = 0, + BLK_IDX_L2_POLICING, + BLK_IDX_VLAN_LOOKUP, + BLK_IDX_L2_FORWARDING, + BLK_IDX_MAC_CONFIG, + BLK_IDX_L2_LOOKUP_PARAMS, + BLK_IDX_L2_FORWARDING_PARAMS, + BLK_IDX_GENERAL_PARAMS, + BLK_IDX_XMII_PARAMS, + BLK_IDX_MAX, + /* Fake block indices that are only valid for dynamic access */ + BLK_IDX_MGMT_ROUTE, + BLK_IDX_MAX_DYN, + BLK_IDX_INVAL = -1, +}; + +#define SJA1105_MAX_L2_LOOKUP_COUNT 1024 +#define SJA1105_MAX_L2_POLICING_COUNT 45 +#define SJA1105_MAX_VLAN_LOOKUP_COUNT 4096 +#define SJA1105_MAX_L2_FORWARDING_COUNT 13 +#define SJA1105_MAX_MAC_CONFIG_COUNT 5 +#define SJA1105_MAX_L2_LOOKUP_PARAMS_COUNT 1 +#define SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT 1 +#define SJA1105_MAX_GENERAL_PARAMS_COUNT 1 +#define SJA1105_MAX_XMII_PARAMS_COUNT 1 + +#define SJA1105_MAX_FRAME_MEMORY 929 + +#define SJA1105E_DEVICE_ID 0x9C00000Cull +#define SJA1105T_DEVICE_ID 0x9E00030Eull +#define SJA1105PR_DEVICE_ID 0xAF00030Eull +#define SJA1105QS_DEVICE_ID 0xAE00030Eull + +#define SJA1105ET_PART_NO 0x9A83 +#define SJA1105P_PART_NO 0x9A84 +#define SJA1105Q_PART_NO 0x9A85 +#define SJA1105R_PART_NO 0x9A86 +#define SJA1105S_PART_NO 0x9A87 + +struct sja1105_general_params_entry { + u64 vllupformat; + u64 mirr_ptacu; + u64 switchid; + u64 hostprio; + u64 mac_fltres1; + u64 mac_fltres0; + u64 mac_flt1; + u64 mac_flt0; + u64 incl_srcpt1; + u64 incl_srcpt0; + u64 send_meta1; + u64 send_meta0; + u64 casc_port; + u64 host_port; + u64 mirr_port; + u64 vlmarker; + u64 vlmask; + u64 tpid; + u64 ignore2stf; + u64 tpid2; + /* P/Q/R/S only */ + u64 queue_ts; + u64 egrmirrvid; + u64 egrmirrpcp; + u64 egrmirrdei; + u64 replay_port; +}; + +struct sja1105_vlan_lookup_entry { + u64 ving_mirr; + u64 vegr_mirr; + u64 vmemb_port; + u64 vlan_bc; + u64 tag_port; + u64 vlanid; +}; + +struct sja1105_l2_lookup_entry { + u64 vlanid; + u64 macaddr; + u64 destports; + u64 enfport; + u64 index; +}; + +struct sja1105_l2_lookup_params_entry { + u64 maxage; /* Shared */ + u64 dyn_tbsz; /* E/T only */ + u64 poly; /* E/T only */ + u64 shared_learn; /* Shared */ + u64 no_enf_hostprt; /* Shared */ + u64 no_mgmt_learn; /* Shared */ +}; + +struct sja1105_l2_forwarding_entry { + u64 bc_domain; + u64 reach_port; + u64 fl_domain; + u64 vlan_pmap[8]; +}; + +struct sja1105_l2_forwarding_params_entry { + u64 max_dynp; + u64 part_spc[8]; +}; + +struct sja1105_l2_policing_entry { + u64 sharindx; + u64 smax; + u64 rate; + u64 maxlen; + u64 partition; +}; + +struct sja1105_mac_config_entry { + u64 top[8]; + u64 base[8]; + u64 enabled[8]; + u64 ifg; + u64 speed; + u64 tp_delin; + u64 tp_delout; + u64 maxage; + u64 vlanprio; + u64 vlanid; + u64 ing_mirr; + u64 egr_mirr; + u64 drpnona664; + u64 drpdtag; + u64 drpuntag; + u64 retag; + u64 dyn_learn; + u64 egress; + u64 ingress; +}; + +struct sja1105_xmii_params_entry { + u64 phy_mac[5]; + u64 xmii_mode[5]; +}; + +struct sja1105_table_header { + u64 block_id; + u64 len; + u64 crc; +}; + +struct sja1105_table_ops { + size_t (*packing)(void *buf, void *entry_ptr, enum packing_op op); + size_t unpacked_entry_size; + size_t packed_entry_size; + size_t max_entry_count; +}; + +struct sja1105_table { + const struct sja1105_table_ops *ops; + size_t entry_count; + void *entries; +}; + +struct sja1105_static_config { + u64 device_id; + struct sja1105_table tables[BLK_IDX_MAX]; +}; + +extern struct sja1105_table_ops sja1105e_table_ops[BLK_IDX_MAX]; +extern struct sja1105_table_ops sja1105t_table_ops[BLK_IDX_MAX]; +extern struct sja1105_table_ops sja1105p_table_ops[BLK_IDX_MAX]; +extern struct sja1105_table_ops sja1105q_table_ops[BLK_IDX_MAX]; +extern struct sja1105_table_ops sja1105r_table_ops[BLK_IDX_MAX]; +extern struct sja1105_table_ops sja1105s_table_ops[BLK_IDX_MAX]; + +size_t sja1105_table_header_packing(void *buf, void *hdr, enum packing_op op); +void +sja1105_table_header_pack_with_crc(void *buf, struct sja1105_table_header *hdr); +size_t +sja1105_static_config_get_length(const struct sja1105_static_config *config); + +typedef enum { + SJA1105_CONFIG_OK = 0, + SJA1105_MISSING_L2_POLICING_TABLE, + SJA1105_MISSING_L2_FORWARDING_TABLE, + SJA1105_MISSING_L2_FORWARDING_PARAMS_TABLE, + SJA1105_MISSING_GENERAL_PARAMS_TABLE, + SJA1105_MISSING_VLAN_TABLE, + SJA1105_MISSING_XMII_TABLE, + SJA1105_MISSING_MAC_TABLE, + SJA1105_OVERCOMMITTED_FRAME_MEMORY, +} sja1105_config_valid_t; + +extern const char *sja1105_static_config_error_msg[]; + +sja1105_config_valid_t +sja1105_static_config_check_valid(const struct sja1105_static_config *config); +void +sja1105_static_config_pack(void *buf, struct sja1105_static_config *config); +int sja1105_static_config_init(struct sja1105_static_config *config, + const struct sja1105_table_ops *static_ops, + u64 device_id); +void sja1105_static_config_free(struct sja1105_static_config *config); + +int sja1105_table_delete_entry(struct sja1105_table *table, int i); +int sja1105_table_resize(struct sja1105_table *table, size_t new_count); + +u32 sja1105_crc32(const void *buf, size_t len); + +void sja1105_pack(void *buf, const u64 *val, int start, int end, size_t len); +void sja1105_unpack(const void *buf, u64 *val, int start, int end, size_t len); +void sja1105_packing(void *buf, u64 *val, int start, int end, + size_t len, enum packing_op op); + +#endif |