diff options
Diffstat (limited to 'drivers/net/dsa')
26 files changed, 2396 insertions, 231 deletions
diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig index 7eb301fd987d..39fb8ead16b5 100644 --- a/drivers/net/dsa/Kconfig +++ b/drivers/net/dsa/Kconfig @@ -74,6 +74,8 @@ source "drivers/net/dsa/microchip/Kconfig" source "drivers/net/dsa/mv88e6xxx/Kconfig" +source "drivers/net/dsa/mxl862xx/Kconfig" + source "drivers/net/dsa/ocelot/Kconfig" source "drivers/net/dsa/qca/Kconfig" @@ -158,6 +160,7 @@ config NET_DSA_VITESSE_VSC73XX_PLATFORM config NET_DSA_YT921X tristate "Motorcomm YT9215 ethernet switch chip support" select NET_DSA_TAG_YT921X + select NET_IEEE8021Q_HELPERS if DCB help This enables support for the Motorcomm YT9215 ethernet switch chip. diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile index 16de4ba3fa38..f5a463b87ec2 100644 --- a/drivers/net/dsa/Makefile +++ b/drivers/net/dsa/Makefile @@ -20,6 +20,7 @@ obj-y += hirschmann/ obj-y += lantiq/ obj-y += microchip/ obj-y += mv88e6xxx/ +obj-y += mxl862xx/ obj-y += ocelot/ obj-y += qca/ obj-y += realtek/ diff --git a/drivers/net/dsa/dsa_loop.c b/drivers/net/dsa/dsa_loop.c index 4a416f2717ba..b41254b3ac42 100644 --- a/drivers/net/dsa/dsa_loop.c +++ b/drivers/net/dsa/dsa_loop.c @@ -395,6 +395,12 @@ static struct mdio_driver dsa_loop_drv = { .shutdown = dsa_loop_drv_shutdown, }; +static int dsa_loop_bus_match(struct device *dev, + const struct device_driver *drv) +{ + return drv == &dsa_loop_drv.mdiodrv.driver; +} + static void dsa_loop_phydevs_unregister(void) { for (int i = 0; i < NUM_FIXED_PHYS; i++) { @@ -428,7 +434,7 @@ static int __init dsa_loop_create_switch_mdiodev(void) if (IS_ERR(switch_mdiodev)) goto out; - strscpy(switch_mdiodev->modalias, "dsa-loop"); + switch_mdiodev->bus_match = dsa_loop_bus_match; switch_mdiodev->dev.platform_data = &dsa_loop_pdata; ret = mdio_device_register(switch_mdiodev); diff --git a/drivers/net/dsa/lantiq/Kconfig b/drivers/net/dsa/lantiq/Kconfig index 4a9771be5d58..98efeef2661b 100644 --- a/drivers/net/dsa/lantiq/Kconfig +++ b/drivers/net/dsa/lantiq/Kconfig @@ -15,10 +15,13 @@ config NET_DSA_MXL_GSW1XX tristate "MaxLinear GSW1xx Ethernet switch support" select NET_DSA_TAG_MXL_GSW1XX select NET_DSA_LANTIQ_COMMON + select PHY_COMMON_PROPS help - This enables support for the MaxLinear GSW1xx family of 1GE switches + This enables support for the Intel/MaxLinear GSW1xx family of 1GE + switches. GSW120 4 port, 2 PHYs, RGMII & SGMII/2500Base-X GSW125 4 port, 2 PHYs, RGMII & SGMII/2500Base-X, industrial temperature GSW140 6 port, 4 PHYs, RGMII & SGMII/2500Base-X GSW141 6 port, 4 PHYs, RGMII & SGMII GSW145 6 port, 4 PHYs, RGMII & SGMII/2500Base-X, industrial temperature + GSW150 7 port, 5 PHYs, 1x GMII/RGMII, 1x RGMII diff --git a/drivers/net/dsa/lantiq/lantiq_gswip.c b/drivers/net/dsa/lantiq/lantiq_gswip.c index b094001a7c80..4d699d8c16f9 100644 --- a/drivers/net/dsa/lantiq/lantiq_gswip.c +++ b/drivers/net/dsa/lantiq/lantiq_gswip.c @@ -33,8 +33,7 @@ static void gswip_xrx200_phylink_get_caps(struct dsa_switch *ds, int port, struct phylink_config *config) { switch (port) { - case 0: - case 1: + case 0 ... 1: phy_interface_set_rgmii(config->supported_interfaces); __set_bit(PHY_INTERFACE_MODE_MII, config->supported_interfaces); @@ -44,9 +43,7 @@ static void gswip_xrx200_phylink_get_caps(struct dsa_switch *ds, int port, config->supported_interfaces); break; - case 2: - case 3: - case 4: + case 2 ... 4: case 6: __set_bit(PHY_INTERFACE_MODE_INTERNAL, config->supported_interfaces); @@ -75,10 +72,7 @@ static void gswip_xrx300_phylink_get_caps(struct dsa_switch *ds, int port, config->supported_interfaces); break; - case 1: - case 2: - case 3: - case 4: + case 1 ... 4: case 6: __set_bit(PHY_INTERFACE_MODE_INTERNAL, config->supported_interfaces); @@ -463,10 +457,22 @@ static void gswip_shutdown(struct platform_device *pdev) } static const struct gswip_hw_info gswip_xrx200 = { - .max_ports = 7, + .max_ports = GSWIP_MAX_PORTS, .allowed_cpu_ports = BIT(6), - .mii_ports = BIT(0) | BIT(1) | BIT(5), - .mii_port_reg_offset = 0, + .mii_cfg = { + [0] = GSWIP_MII_CFGp(0), + [1] = GSWIP_MII_CFGp(1), + [2 ... 4] = -1, + [5] = GSWIP_MII_CFGp(5), + [6] = -1, + }, + .mii_pcdu = { + [0] = GSWIP_MII_PCDU0, + [1] = GSWIP_MII_PCDU1, + [2 ... 4] = -1, + [5] = GSWIP_MII_PCDU5, + [6] = -1, + }, .phylink_get_caps = gswip_xrx200_phylink_get_caps, .pce_microcode = &gswip_pce_microcode, .pce_microcode_size = ARRAY_SIZE(gswip_pce_microcode), @@ -474,10 +480,20 @@ static const struct gswip_hw_info gswip_xrx200 = { }; static const struct gswip_hw_info gswip_xrx300 = { - .max_ports = 7, + .max_ports = GSWIP_MAX_PORTS, .allowed_cpu_ports = BIT(6), - .mii_ports = BIT(0) | BIT(5), - .mii_port_reg_offset = 0, + .mii_cfg = { + [0] = GSWIP_MII_CFGp(0), + [1 ... 4] = -1, + [5] = GSWIP_MII_CFGp(5), + [6] = -1, + }, + .mii_pcdu = { + [0] = GSWIP_MII_PCDU0, + [1 ... 4] = -1, + [5] = GSWIP_MII_PCDU5, + [6] = -1, + }, .phylink_get_caps = gswip_xrx300_phylink_get_caps, .pce_microcode = &gswip_pce_microcode, .pce_microcode_size = ARRAY_SIZE(gswip_pce_microcode), diff --git a/drivers/net/dsa/lantiq/lantiq_gswip.h b/drivers/net/dsa/lantiq/lantiq_gswip.h index 2e0f2afbadbb..bc3686faad0d 100644 --- a/drivers/net/dsa/lantiq/lantiq_gswip.h +++ b/drivers/net/dsa/lantiq/lantiq_gswip.h @@ -243,6 +243,8 @@ #define GSWIP_VLAN_UNAWARE_PVID 0 +#define GSWIP_MAX_PORTS 7 + struct gswip_pce_microcode { u16 val_3; u16 val_2; @@ -253,8 +255,8 @@ struct gswip_pce_microcode { struct gswip_hw_info { int max_ports; unsigned int allowed_cpu_ports; - unsigned int mii_ports; - int mii_port_reg_offset; + s16 mii_cfg[GSWIP_MAX_PORTS]; + s16 mii_pcdu[GSWIP_MAX_PORTS]; bool supports_2500m; const struct gswip_pce_microcode (*pce_microcode)[]; size_t pce_microcode_size; @@ -263,6 +265,7 @@ struct gswip_hw_info { struct phylink_config *config); struct phylink_pcs *(*mac_select_pcs)(struct phylink_config *config, phy_interface_t interface); + int (*port_setup)(struct dsa_switch *ds, int port); }; struct gswip_gphy_fw { diff --git a/drivers/net/dsa/lantiq/lantiq_gswip_common.c b/drivers/net/dsa/lantiq/lantiq_gswip_common.c index e790f2ef7588..0e8eedf64d3a 100644 --- a/drivers/net/dsa/lantiq/lantiq_gswip_common.c +++ b/drivers/net/dsa/lantiq/lantiq_gswip_common.c @@ -118,15 +118,11 @@ static u32 gswip_switch_r_timeout(struct gswip_priv *priv, u32 offset, static void gswip_mii_mask_cfg(struct gswip_priv *priv, u32 mask, u32 set, int port) { - int reg_port; - /* MII_CFG register only exists for MII ports */ - if (!(priv->hw_info->mii_ports & BIT(port))) + if (priv->hw_info->mii_cfg[port] == -1) return; - reg_port = port + priv->hw_info->mii_port_reg_offset; - - regmap_write_bits(priv->mii, GSWIP_MII_CFGp(reg_port), mask, + regmap_write_bits(priv->mii, priv->hw_info->mii_cfg[port], mask, set); } @@ -425,6 +421,12 @@ static int gswip_port_setup(struct dsa_switch *ds, int port) struct gswip_priv *priv = ds->priv; int err; + if (priv->hw_info->port_setup) { + err = priv->hw_info->port_setup(ds, port); + if (err) + return err; + } + if (!dsa_is_cpu_port(ds, port)) { err = gswip_add_single_port_br(priv, port, true); if (err) @@ -604,28 +606,13 @@ static void gswip_mii_delay_setup(struct gswip_priv *priv, struct dsa_port *dp, u32 tx_delay = GSWIP_MII_PCDU_TXDLY_DEFAULT; u32 rx_delay = GSWIP_MII_PCDU_RXDLY_DEFAULT; struct device_node *port_dn = dp->dn; - u16 mii_pcdu_reg; /* As MII_PCDU registers only exist for MII ports, silently return * unless the port is an MII port */ - if (!(priv->hw_info->mii_ports & BIT(dp->index))) + if (priv->hw_info->mii_pcdu[dp->index] == -1) return; - switch (dp->index + priv->hw_info->mii_port_reg_offset) { - case 0: - mii_pcdu_reg = GSWIP_MII_PCDU0; - break; - case 1: - mii_pcdu_reg = GSWIP_MII_PCDU1; - break; - case 5: - mii_pcdu_reg = GSWIP_MII_PCDU5; - break; - default: - return; - } - /* legacy code to set default delays according to the interface mode */ switch (interface) { case PHY_INTERFACE_MODE_RGMII_ID: @@ -646,7 +633,7 @@ static void gswip_mii_delay_setup(struct gswip_priv *priv, struct dsa_port *dp, of_property_read_u32(port_dn, "rx-internal-delay-ps", &rx_delay); of_property_read_u32(port_dn, "tx-internal-delay-ps", &tx_delay); - regmap_write_bits(priv->mii, mii_pcdu_reg, + regmap_write_bits(priv->mii, priv->hw_info->mii_pcdu[dp->index], GSWIP_MII_PCDU_TXDLY_MASK | GSWIP_MII_PCDU_RXDLY_MASK, GSWIP_MII_PCDU_TXDLY(tx_delay) | diff --git a/drivers/net/dsa/lantiq/mxl-gsw1xx.c b/drivers/net/dsa/lantiq/mxl-gsw1xx.c index f8ff8a604bf5..a1104b2f92a9 100644 --- a/drivers/net/dsa/lantiq/mxl-gsw1xx.c +++ b/drivers/net/dsa/lantiq/mxl-gsw1xx.c @@ -15,6 +15,8 @@ #include <linux/module.h> #include <linux/of_device.h> #include <linux/of_mdio.h> +#include <linux/phy/phy-common-props.h> +#include <linux/property.h> #include <linux/regmap.h> #include <linux/workqueue.h> #include <net/dsa.h> @@ -229,11 +231,17 @@ static int gsw1xx_pcs_phy_xaui_write(struct gsw1xx_priv *priv, u16 addr, 1000, 100000); } -static int gsw1xx_pcs_reset(struct gsw1xx_priv *priv) +static int gsw1xx_pcs_reset(struct gsw1xx_priv *priv, phy_interface_t interface) { + struct dsa_port *sgmii_port; + unsigned int pol; int ret; u16 val; + sgmii_port = dsa_to_port(priv->gswip.ds, GSW1XX_SGMII_PORT); + if (!sgmii_port) + return -EINVAL; + /* Assert and deassert SGMII shell reset */ ret = regmap_set_bits(priv->shell, GSW1XX_SHELL_RST_REQ, GSW1XX_RST_REQ_SGMII_SHELL); @@ -260,15 +268,20 @@ static int gsw1xx_pcs_reset(struct gsw1xx_priv *priv) FIELD_PREP(GSW1XX_SGMII_PHY_RX0_CFG2_FILT_CNT, GSW1XX_SGMII_PHY_RX0_CFG2_FILT_CNT_DEF); + ret = phy_get_manual_rx_polarity(of_fwnode_handle(sgmii_port->dn), + phy_modes(interface), &pol); + if (ret) + return ret; + /* RX lane seems to be inverted internally, so bit * GSW1XX_SGMII_PHY_RX0_CFG2_INVERT needs to be set for normal - * (ie. non-inverted) operation. - * - * TODO: Take care of inverted RX pair once generic property is - * available + * (ie. non-inverted) operation matching the chips external pins as + * described in datasheets dated 2023-11-08, ie. pin B20 (RX0_P) being + * the positive signal and pin B21 (RX0_M) being the negative signal of + * the differential input pair. */ - - val |= GSW1XX_SGMII_PHY_RX0_CFG2_INVERT; + if (pol == PHY_POL_NORMAL) + val |= GSW1XX_SGMII_PHY_RX0_CFG2_INVERT; ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_RX0_CFG2, val); if (ret < 0) @@ -277,9 +290,13 @@ static int gsw1xx_pcs_reset(struct gsw1xx_priv *priv) val = FIELD_PREP(GSW1XX_SGMII_PHY_TX0_CFG3_VBOOST_LEVEL, GSW1XX_SGMII_PHY_TX0_CFG3_VBOOST_LEVEL_DEF); - /* TODO: Take care of inverted TX pair once generic property is - * available - */ + ret = phy_get_manual_tx_polarity(of_fwnode_handle(sgmii_port->dn), + phy_modes(interface), &pol); + if (ret) + return ret; + + if (pol == PHY_POL_INVERT) + val |= GSW1XX_SGMII_PHY_TX0_CFG3_INVERT; ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_TX0_CFG3, val); if (ret < 0) @@ -336,7 +353,7 @@ static int gsw1xx_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, priv->tbi_interface = PHY_INTERFACE_MODE_NA; if (!reconf) - ret = gsw1xx_pcs_reset(priv); + ret = gsw1xx_pcs_reset(priv, interface); if (ret) return ret; @@ -502,6 +519,14 @@ static const struct phylink_pcs_ops gsw1xx_pcs_ops = { .pcs_link_up = gsw1xx_pcs_link_up, }; +static void gsw1xx_phylink_get_lpi_caps(struct phylink_config *config) +{ + config->lpi_capabilities = MAC_100FD | MAC_1000FD; + config->lpi_timer_default = 20; + memcpy(config->lpi_interfaces, config->supported_interfaces, + sizeof(config->lpi_interfaces)); +} + static void gsw1xx_phylink_get_caps(struct dsa_switch *ds, int port, struct phylink_config *config) { @@ -511,14 +536,12 @@ static void gsw1xx_phylink_get_caps(struct dsa_switch *ds, int port, MAC_10 | MAC_100 | MAC_1000; switch (port) { - case 0: - case 1: - case 2: - case 3: + case 0 ... 3: /* built-in PHYs */ __set_bit(PHY_INTERFACE_MODE_INTERNAL, config->supported_interfaces); break; - case 4: /* port 4: SGMII */ + + case 4: /* SGMII */ __set_bit(PHY_INTERFACE_MODE_SGMII, config->supported_interfaces); __set_bit(PHY_INTERFACE_MODE_1000BASEX, @@ -529,17 +552,40 @@ static void gsw1xx_phylink_get_caps(struct dsa_switch *ds, int port, config->mac_capabilities |= MAC_2500FD; } return; /* no support for EEE on SGMII port */ - case 5: /* port 5: RGMII or RMII */ + + case 5: /* RGMII or RMII */ __set_bit(PHY_INTERFACE_MODE_RMII, config->supported_interfaces); phy_interface_set_rgmii(config->supported_interfaces); break; } - config->lpi_capabilities = MAC_100FD | MAC_1000FD; - config->lpi_timer_default = 20; - memcpy(config->lpi_interfaces, config->supported_interfaces, - sizeof(config->lpi_interfaces)); + gsw1xx_phylink_get_lpi_caps(config); +} + +static void gsw150_phylink_get_caps(struct dsa_switch *ds, int port, + struct phylink_config *config) +{ + config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | + MAC_10 | MAC_100 | MAC_1000; + + switch (port) { + case 0 ... 4: /* built-in PHYs */ + __set_bit(PHY_INTERFACE_MODE_INTERNAL, + config->supported_interfaces); + break; + + case 5: /* GMII or RGMII */ + __set_bit(PHY_INTERFACE_MODE_GMII, + config->supported_interfaces); + fallthrough; + + case 6: /* RGMII */ + phy_interface_set_rgmii(config->supported_interfaces); + break; + } + + gsw1xx_phylink_get_lpi_caps(config); } static struct phylink_pcs *gsw1xx_phylink_mac_select_pcs(struct phylink_config *config, @@ -559,6 +605,43 @@ static struct phylink_pcs *gsw1xx_phylink_mac_select_pcs(struct phylink_config * } } +static int gsw1xx_rmii_slew_rate(const struct device_node *np, struct gsw1xx_priv *priv, + const char *prop, u16 mask) +{ + u32 rate; + int ret; + + ret = of_property_read_u32(np, prop, &rate); + /* Optional property */ + if (ret == -EINVAL) + return 0; + if (ret < 0 || rate > 1) { + dev_err(&priv->mdio_dev->dev, "Invalid %s value\n", prop); + return (ret < 0) ? ret : -EINVAL; + } + + return regmap_update_bits(priv->shell, GSW1XX_SHELL_RGMII_SLEW_CFG, mask, mask * rate); +} + +static int gsw1xx_port_setup(struct dsa_switch *ds, int port) +{ + struct dsa_port *dp = dsa_to_port(ds, port); + struct device_node *np = dp->dn; + struct gsw1xx_priv *gsw1xx_priv; + struct gswip_priv *gswip_priv; + + if (dp->index != GSW1XX_MII_PORT) + return 0; + + gswip_priv = ds->priv; + gsw1xx_priv = container_of(gswip_priv, struct gsw1xx_priv, gswip); + + return gsw1xx_rmii_slew_rate(np, gsw1xx_priv, + "maxlinear,slew-rate-txc", RGMII_SLEW_CFG_DRV_TXC) ?: + gsw1xx_rmii_slew_rate(np, gsw1xx_priv, + "maxlinear,slew-rate-txd", RGMII_SLEW_CFG_DRV_TXD); +} + static struct regmap *gsw1xx_regmap_init(struct gsw1xx_priv *priv, const char *name, unsigned int reg_base, @@ -579,11 +662,35 @@ static struct regmap *gsw1xx_regmap_init(struct gsw1xx_priv *priv, priv, &config); } +static int gsw1xx_serdes_pcs_init(struct gsw1xx_priv *priv) +{ + /* do nothing if the chip doesn't have a SerDes PCS */ + if (!priv->gswip.hw_info->mac_select_pcs) + return 0; + + priv->pcs.ops = &gsw1xx_pcs_ops; + priv->pcs.poll = true; + __set_bit(PHY_INTERFACE_MODE_SGMII, + priv->pcs.supported_interfaces); + __set_bit(PHY_INTERFACE_MODE_1000BASEX, + priv->pcs.supported_interfaces); + if (priv->gswip.hw_info->supports_2500m) + __set_bit(PHY_INTERFACE_MODE_2500BASEX, + priv->pcs.supported_interfaces); + priv->tbi_interface = PHY_INTERFACE_MODE_NA; + + /* assert SGMII reset to power down SGMII unit */ + return regmap_set_bits(priv->shell, GSW1XX_SHELL_RST_REQ, + GSW1XX_RST_REQ_SGMII_SHELL); +} + static int gsw1xx_probe(struct mdio_device *mdiodev) { struct device *dev = &mdiodev->dev; struct gsw1xx_priv *priv; - u32 version; + u32 version, val; + u8 shellver; + u16 pnum; int ret; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); @@ -631,20 +738,28 @@ static int gsw1xx_probe(struct mdio_device *mdiodev) if (IS_ERR(priv->shell)) return PTR_ERR(priv->shell); - priv->pcs.ops = &gsw1xx_pcs_ops; - priv->pcs.poll = true; - __set_bit(PHY_INTERFACE_MODE_SGMII, - priv->pcs.supported_interfaces); - __set_bit(PHY_INTERFACE_MODE_1000BASEX, - priv->pcs.supported_interfaces); - if (priv->gswip.hw_info->supports_2500m) - __set_bit(PHY_INTERFACE_MODE_2500BASEX, - priv->pcs.supported_interfaces); - priv->tbi_interface = PHY_INTERFACE_MODE_NA; + ret = regmap_read(priv->shell, GSW1XX_SHELL_MANU_ID, &val); + if (ret < 0) + return ret; - /* assert SGMII reset to power down SGMII unit */ - ret = regmap_set_bits(priv->shell, GSW1XX_SHELL_RST_REQ, - GSW1XX_RST_REQ_SGMII_SHELL); + /* validate chip ID */ + if (FIELD_GET(GSW1XX_SHELL_MANU_ID_FIX1, val) != 1) + return -ENODEV; + + if (FIELD_GET(GSW1XX_SHELL_MANU_ID_MANID, val) != + GSW1XX_SHELL_MANU_ID_MANID_VAL) + return -ENODEV; + + pnum = FIELD_GET(GSW1XX_SHELL_MANU_ID_PNUML, val); + + ret = regmap_read(priv->shell, GSW1XX_SHELL_PNUM_ID, &val); + if (ret < 0) + return ret; + + pnum |= FIELD_GET(GSW1XX_SHELL_PNUM_ID_PNUMM, val) << 4; + shellver = FIELD_GET(GSW1XX_SHELL_PNUM_ID_VER, val); + + ret = gsw1xx_serdes_pcs_init(priv); if (ret < 0) return ret; @@ -664,6 +779,8 @@ static int gsw1xx_probe(struct mdio_device *mdiodev) if (ret) return ret; + dev_info(dev, "standalone switch part number 0x%x v1.%u\n", pnum, shellver); + dev_set_drvdata(dev, &priv->gswip); return 0; @@ -702,11 +819,20 @@ static void gsw1xx_shutdown(struct mdio_device *mdiodev) static const struct gswip_hw_info gsw12x_data = { .max_ports = GSW1XX_PORTS, .allowed_cpu_ports = BIT(GSW1XX_MII_PORT) | BIT(GSW1XX_SGMII_PORT), - .mii_ports = BIT(GSW1XX_MII_PORT), - .mii_port_reg_offset = -GSW1XX_MII_PORT, + .mii_cfg = { + [0 ... GSW1XX_MII_PORT - 1] = -1, + [GSW1XX_MII_PORT] = GSWIP_MII_CFGp(0), + [GSW1XX_MII_PORT + 1 ... GSWIP_MAX_PORTS - 1] = -1, + }, + .mii_pcdu = { + [0 ... GSW1XX_MII_PORT - 1] = -1, + [GSW1XX_MII_PORT] = GSWIP_MII_PCDU0, + [GSW1XX_MII_PORT + 1 ... GSWIP_MAX_PORTS - 1] = -1, + }, .mac_select_pcs = gsw1xx_phylink_mac_select_pcs, .phylink_get_caps = &gsw1xx_phylink_get_caps, .supports_2500m = true, + .port_setup = gsw1xx_port_setup, .pce_microcode = &gsw1xx_pce_microcode, .pce_microcode_size = ARRAY_SIZE(gsw1xx_pce_microcode), .tag_protocol = DSA_TAG_PROTO_MXL_GSW1XX, @@ -715,11 +841,20 @@ static const struct gswip_hw_info gsw12x_data = { static const struct gswip_hw_info gsw140_data = { .max_ports = GSW1XX_PORTS, .allowed_cpu_ports = BIT(GSW1XX_MII_PORT) | BIT(GSW1XX_SGMII_PORT), - .mii_ports = BIT(GSW1XX_MII_PORT), - .mii_port_reg_offset = -GSW1XX_MII_PORT, + .mii_cfg = { + [0 ... GSW1XX_MII_PORT - 1] = -1, + [GSW1XX_MII_PORT] = GSWIP_MII_CFGp(0), + [GSW1XX_MII_PORT + 1 ... GSWIP_MAX_PORTS - 1] = -1, + }, + .mii_pcdu = { + [0 ... GSW1XX_MII_PORT - 1] = -1, + [GSW1XX_MII_PORT] = GSWIP_MII_PCDU0, + [GSW1XX_MII_PORT + 1 ... GSWIP_MAX_PORTS - 1] = -1, + }, .mac_select_pcs = gsw1xx_phylink_mac_select_pcs, .phylink_get_caps = &gsw1xx_phylink_get_caps, .supports_2500m = true, + .port_setup = gsw1xx_port_setup, .pce_microcode = &gsw1xx_pce_microcode, .pce_microcode_size = ARRAY_SIZE(gsw1xx_pce_microcode), .tag_protocol = DSA_TAG_PROTO_MXL_GSW1XX, @@ -728,10 +863,44 @@ static const struct gswip_hw_info gsw140_data = { static const struct gswip_hw_info gsw141_data = { .max_ports = GSW1XX_PORTS, .allowed_cpu_ports = BIT(GSW1XX_MII_PORT) | BIT(GSW1XX_SGMII_PORT), - .mii_ports = BIT(GSW1XX_MII_PORT), - .mii_port_reg_offset = -GSW1XX_MII_PORT, + .mii_cfg = { + [0 ... GSW1XX_MII_PORT - 1] = -1, + [GSW1XX_MII_PORT] = GSWIP_MII_CFGp(0), + [GSW1XX_MII_PORT + 1 ... GSWIP_MAX_PORTS - 1] = -1, + }, + .mii_pcdu = { + [0 ... GSW1XX_MII_PORT - 1] = -1, + [GSW1XX_MII_PORT] = GSWIP_MII_PCDU0, + [GSW1XX_MII_PORT + 1 ... GSWIP_MAX_PORTS - 1] = -1, + }, .mac_select_pcs = gsw1xx_phylink_mac_select_pcs, .phylink_get_caps = gsw1xx_phylink_get_caps, + .port_setup = gsw1xx_port_setup, + .pce_microcode = &gsw1xx_pce_microcode, + .pce_microcode_size = ARRAY_SIZE(gsw1xx_pce_microcode), + .tag_protocol = DSA_TAG_PROTO_MXL_GSW1XX, +}; + +static const struct gswip_hw_info gsw150_data = { + .max_ports = GSW150_PORTS, + .allowed_cpu_ports = BIT(5) | BIT(6), + .mii_cfg = { + [0 ... 4] = -1, + [5] = 0, + [6] = 10, + }, + .mii_pcdu = { + [0 ... 4] = -1, + [5] = 1, + [6] = 11, + }, + .phylink_get_caps = gsw150_phylink_get_caps, + /* There is only a single RGMII_SLEW_CFG register in GSW150 and it is + * unknown if RGMII slew configuration affects both RGMII ports + * or only port 5. Use .port_setup which assumes it affects port 5 + * for now. + */ + .port_setup = gsw1xx_port_setup, .pce_microcode = &gsw1xx_pce_microcode, .pce_microcode_size = ARRAY_SIZE(gsw1xx_pce_microcode), .tag_protocol = DSA_TAG_PROTO_MXL_GSW1XX, @@ -742,6 +911,8 @@ static const struct gswip_hw_info gsw141_data = { * GSW145 is the industrial temperature version of GSW140. */ static const struct of_device_id gsw1xx_of_match[] = { + { .compatible = "intel,gsw150", .data = &gsw150_data }, + { .compatible = "lantiq,peb7084", .data = &gsw150_data }, { .compatible = "maxlinear,gsw120", .data = &gsw12x_data }, { .compatible = "maxlinear,gsw125", .data = &gsw12x_data }, { .compatible = "maxlinear,gsw140", .data = &gsw140_data }, @@ -765,5 +936,5 @@ static struct mdio_driver gsw1xx_driver = { mdio_module_driver(gsw1xx_driver); MODULE_AUTHOR("Daniel Golle <daniel@makrotopia.org>"); -MODULE_DESCRIPTION("Driver for MaxLinear GSW1xx ethernet switch"); +MODULE_DESCRIPTION("Driver for Intel/MaxLinear GSW1xx Ethernet switch"); MODULE_LICENSE("GPL"); diff --git a/drivers/net/dsa/lantiq/mxl-gsw1xx.h b/drivers/net/dsa/lantiq/mxl-gsw1xx.h index 38e03c048a26..caa8f1008587 100644 --- a/drivers/net/dsa/lantiq/mxl-gsw1xx.h +++ b/drivers/net/dsa/lantiq/mxl-gsw1xx.h @@ -10,6 +10,8 @@ #include <linux/bitfield.h> #define GSW1XX_PORTS 6 +#define GSW150_PORTS 7 + /* Port used for RGMII or optional RMII */ #define GSW1XX_MII_PORT 5 /* Port used for SGMII */ @@ -108,8 +110,19 @@ #define GSW1XX_SHELL_BASE 0xfa00 #define GSW1XX_SHELL_RST_REQ 0x01 #define GSW1XX_RST_REQ_SGMII_SHELL BIT(5) +#define GSW1XX_SHELL_MANU_ID 0x10 +#define GSW1XX_SHELL_MANU_ID_PNUML GENMASK(15, 12) +#define GSW1XX_SHELL_MANU_ID_MANID GENMASK(11, 1) +#define GSW1XX_SHELL_MANU_ID_MANID_VAL 0x389 +#define GSW1XX_SHELL_MANU_ID_FIX1 BIT(0) +#define GSW1XX_SHELL_PNUM_ID 0x11 +#define GSW1XX_SHELL_PNUM_ID_VER GENMASK(15, 12) +#define GSW1XX_SHELL_PNUM_ID_PNUMM GENMASK(11, 0) + /* RGMII PAD Slew Control Register */ #define GSW1XX_SHELL_RGMII_SLEW_CFG 0x78 +#define RGMII_SLEW_CFG_DRV_TXC BIT(2) +#define RGMII_SLEW_CFG_DRV_TXD BIT(3) #define RGMII_SLEW_CFG_RX_2_5_V BIT(4) #define RGMII_SLEW_CFG_TX_2_5_V BIT(5) diff --git a/drivers/net/dsa/microchip/ksz_common.c b/drivers/net/dsa/microchip/ksz_common.c index 0c10351fe5eb..e5fa1f5fc09b 100644 --- a/drivers/net/dsa/microchip/ksz_common.c +++ b/drivers/net/dsa/microchip/ksz_common.c @@ -569,6 +569,12 @@ static const u16 ksz8463_regs[] = { [S_START_CTRL] = 0x01, [S_BROADCAST_CTRL] = 0x06, [S_MULTICAST_CTRL] = 0x04, + [PTP_CLK_CTRL] = 0x0600, + [PTP_RTC_NANOSEC] = 0x0604, + [PTP_RTC_SEC] = 0x0608, + [PTP_RTC_SUB_NANOSEC] = 0x060C, + [PTP_SUBNANOSEC_RATE] = 0x0610, + [PTP_MSG_CONF1] = 0x0620, }; static const u32 ksz8463_masks[] = { @@ -803,6 +809,12 @@ static const u16 ksz9477_regs[] = { [REG_SW_PME_CTRL] = 0x0006, [REG_PORT_PME_STATUS] = 0x0013, [REG_PORT_PME_CTRL] = 0x0017, + [PTP_CLK_CTRL] = 0x0500, + [PTP_RTC_SUB_NANOSEC] = 0x0502, + [PTP_RTC_NANOSEC] = 0x0504, + [PTP_RTC_SEC] = 0x0508, + [PTP_SUBNANOSEC_RATE] = 0x050C, + [PTP_MSG_CONF1] = 0x0514, }; static const u32 ksz9477_masks[] = { @@ -2905,7 +2917,6 @@ static int ksz_irq_common_setup(struct ksz_device *dev, struct ksz_irq *kirq) int ret, n; kirq->dev = dev; - kirq->masked = ~0; kirq->domain = irq_domain_create_simple(dev_fwnode(dev->dev), kirq->nirqs, 0, &ksz_irq_domain_ops, kirq); @@ -2935,6 +2946,7 @@ static int ksz_girq_setup(struct ksz_device *dev) girq->nirqs = dev->info->port_cnt; girq->reg_mask = REG_SW_PORT_INT_MASK__1; girq->reg_status = REG_SW_PORT_INT_STATUS__1; + girq->masked = ~0; snprintf(girq->name, sizeof(girq->name), "global_port_irq"); girq->irq_num = dev->irq; @@ -2949,6 +2961,7 @@ static int ksz_pirq_setup(struct ksz_device *dev, u8 p) pirq->nirqs = dev->info->port_nirqs; pirq->reg_mask = dev->dev_ops->get_port_addr(p, REG_PORT_INT_MASK); pirq->reg_status = dev->dev_ops->get_port_addr(p, REG_PORT_INT_STATUS); + pirq->masked = ~0; snprintf(pirq->name, sizeof(pirq->name), "port_irq-%d", p); pirq->irq_num = irq_find_mapping(dev->girq.domain, p); diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index c65188cd3c0a..929aff4c55de 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -108,6 +108,7 @@ struct ksz_irq { int irq_num; char name[16]; struct ksz_device *dev; + u16 irq0_offset; }; struct ksz_ptp_irq { @@ -270,6 +271,12 @@ enum ksz_regs { REG_SW_PME_CTRL, REG_PORT_PME_STATUS, REG_PORT_PME_CTRL, + PTP_CLK_CTRL, + PTP_RTC_NANOSEC, + PTP_RTC_SEC, + PTP_RTC_SUB_NANOSEC, + PTP_SUBNANOSEC_RATE, + PTP_MSG_CONF1, }; enum ksz_masks { diff --git a/drivers/net/dsa/microchip/ksz_ptp.c b/drivers/net/dsa/microchip/ksz_ptp.c index 997e4a76d0a6..4a2cc57a628f 100644 --- a/drivers/net/dsa/microchip/ksz_ptp.c +++ b/drivers/net/dsa/microchip/ksz_ptp.c @@ -263,6 +263,7 @@ static int ksz_ptp_enable_mode(struct ksz_device *dev) { struct ksz_tagger_data *tagger_data = ksz_tagger_data(dev->ds); struct ksz_ptp_data *ptp_data = &dev->ptp_data; + const u16 *regs = dev->info->regs; struct ksz_port *prt; struct dsa_port *dp; bool tag_en = false; @@ -283,7 +284,7 @@ static int ksz_ptp_enable_mode(struct ksz_device *dev) tagger_data->hwtstamp_set_state(dev->ds, tag_en); - return ksz_rmw16(dev, REG_PTP_MSG_CONF1, PTP_ENABLE, + return ksz_rmw16(dev, regs[PTP_MSG_CONF1], PTP_ENABLE, tag_en ? PTP_ENABLE : 0); } @@ -335,6 +336,7 @@ static int ksz_set_hwtstamp_config(struct ksz_device *dev, struct ksz_port *prt, struct kernel_hwtstamp_config *config) { + const u16 *regs = dev->info->regs; int ret; if (config->flags) @@ -353,7 +355,7 @@ static int ksz_set_hwtstamp_config(struct ksz_device *dev, prt->ptpmsg_irq[KSZ_PDRES_MSG].ts_en = false; prt->hwts_tx_en = true; - ret = ksz_rmw16(dev, REG_PTP_MSG_CONF1, PTP_1STEP, PTP_1STEP); + ret = ksz_rmw16(dev, regs[PTP_MSG_CONF1], PTP_1STEP, PTP_1STEP); if (ret) return ret; @@ -367,7 +369,7 @@ static int ksz_set_hwtstamp_config(struct ksz_device *dev, prt->ptpmsg_irq[KSZ_PDRES_MSG].ts_en = true; prt->hwts_tx_en = true; - ret = ksz_rmw16(dev, REG_PTP_MSG_CONF1, PTP_1STEP, 0); + ret = ksz_rmw16(dev, regs[PTP_MSG_CONF1], PTP_1STEP, 0); if (ret) return ret; @@ -585,25 +587,26 @@ void ksz_port_deferred_xmit(struct kthread_work *work) static int _ksz_ptp_gettime(struct ksz_device *dev, struct timespec64 *ts) { + const u16 *regs = dev->info->regs; u32 nanoseconds; u32 seconds; u8 phase; int ret; /* Copy current PTP clock into shadow registers and read */ - ret = ksz_rmw16(dev, REG_PTP_CLK_CTRL, PTP_READ_TIME, PTP_READ_TIME); + ret = ksz_rmw16(dev, regs[PTP_CLK_CTRL], PTP_READ_TIME, PTP_READ_TIME); if (ret) return ret; - ret = ksz_read8(dev, REG_PTP_RTC_SUB_NANOSEC__2, &phase); + ret = ksz_read8(dev, regs[PTP_RTC_SUB_NANOSEC], &phase); if (ret) return ret; - ret = ksz_read32(dev, REG_PTP_RTC_NANOSEC, &nanoseconds); + ret = ksz_read32(dev, regs[PTP_RTC_NANOSEC], &nanoseconds); if (ret) return ret; - ret = ksz_read32(dev, REG_PTP_RTC_SEC, &seconds); + ret = ksz_read32(dev, regs[PTP_RTC_SEC], &seconds); if (ret) return ret; @@ -676,24 +679,25 @@ static int ksz_ptp_settime(struct ptp_clock_info *ptp, { struct ksz_ptp_data *ptp_data = ptp_caps_to_data(ptp); struct ksz_device *dev = ptp_data_to_ksz_dev(ptp_data); + const u16 *regs = dev->info->regs; int ret; mutex_lock(&ptp_data->lock); /* Write to shadow registers and Load PTP clock */ - ret = ksz_write16(dev, REG_PTP_RTC_SUB_NANOSEC__2, PTP_RTC_0NS); + ret = ksz_write16(dev, regs[PTP_RTC_SUB_NANOSEC], PTP_RTC_0NS); if (ret) goto unlock; - ret = ksz_write32(dev, REG_PTP_RTC_NANOSEC, ts->tv_nsec); + ret = ksz_write32(dev, regs[PTP_RTC_NANOSEC], ts->tv_nsec); if (ret) goto unlock; - ret = ksz_write32(dev, REG_PTP_RTC_SEC, ts->tv_sec); + ret = ksz_write32(dev, regs[PTP_RTC_SEC], ts->tv_sec); if (ret) goto unlock; - ret = ksz_rmw16(dev, REG_PTP_CLK_CTRL, PTP_LOAD_TIME, PTP_LOAD_TIME); + ret = ksz_rmw16(dev, regs[PTP_CLK_CTRL], PTP_LOAD_TIME, PTP_LOAD_TIME); if (ret) goto unlock; @@ -723,6 +727,7 @@ static int ksz_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) { struct ksz_ptp_data *ptp_data = ptp_caps_to_data(ptp); struct ksz_device *dev = ptp_data_to_ksz_dev(ptp_data); + const u16 *regs = dev->info->regs; u64 base, adj; bool negative; u32 data32; @@ -739,16 +744,16 @@ static int ksz_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) if (!negative) data32 |= PTP_RATE_DIR; - ret = ksz_write32(dev, REG_PTP_SUBNANOSEC_RATE, data32); + ret = ksz_write32(dev, regs[PTP_SUBNANOSEC_RATE], data32); if (ret) goto unlock; - ret = ksz_rmw16(dev, REG_PTP_CLK_CTRL, PTP_CLK_ADJ_ENABLE, + ret = ksz_rmw16(dev, regs[PTP_CLK_CTRL], PTP_CLK_ADJ_ENABLE, PTP_CLK_ADJ_ENABLE); if (ret) goto unlock; } else { - ret = ksz_rmw16(dev, REG_PTP_CLK_CTRL, PTP_CLK_ADJ_ENABLE, 0); + ret = ksz_rmw16(dev, regs[PTP_CLK_CTRL], PTP_CLK_ADJ_ENABLE, 0); if (ret) goto unlock; } @@ -763,6 +768,7 @@ static int ksz_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) struct ksz_ptp_data *ptp_data = ptp_caps_to_data(ptp); struct ksz_device *dev = ptp_data_to_ksz_dev(ptp_data); struct timespec64 delta64 = ns_to_timespec64(delta); + const u16 *regs = dev->info->regs; s32 sec, nsec; u16 data16; int ret; @@ -774,15 +780,15 @@ static int ksz_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) */ sec = div_s64_rem(delta, NSEC_PER_SEC, &nsec); - ret = ksz_write32(dev, REG_PTP_RTC_NANOSEC, abs(nsec)); + ret = ksz_write32(dev, regs[PTP_RTC_NANOSEC], abs(nsec)); if (ret) goto unlock; - ret = ksz_write32(dev, REG_PTP_RTC_SEC, abs(sec)); + ret = ksz_write32(dev, regs[PTP_RTC_SEC], abs(sec)); if (ret) goto unlock; - ret = ksz_read16(dev, REG_PTP_CLK_CTRL, &data16); + ret = ksz_read16(dev, regs[PTP_CLK_CTRL], &data16); if (ret) goto unlock; @@ -794,7 +800,7 @@ static int ksz_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) else data16 |= PTP_STEP_DIR; - ret = ksz_write16(dev, REG_PTP_CLK_CTRL, data16); + ret = ksz_write16(dev, regs[PTP_CLK_CTRL], data16); if (ret) goto unlock; @@ -882,9 +888,10 @@ out: static int ksz_ptp_start_clock(struct ksz_device *dev) { struct ksz_ptp_data *ptp_data = &dev->ptp_data; + const u16 *regs = dev->info->regs; int ret; - ret = ksz_rmw16(dev, REG_PTP_CLK_CTRL, PTP_CLK_ENABLE, PTP_CLK_ENABLE); + ret = ksz_rmw16(dev, regs[PTP_CLK_CTRL], PTP_CLK_ENABLE, PTP_CLK_ENABLE); if (ret) return ret; @@ -897,6 +904,7 @@ static int ksz_ptp_start_clock(struct ksz_device *dev) int ksz_ptp_clock_register(struct dsa_switch *ds) { struct ksz_device *dev = ds->priv; + const u16 *regs = dev->info->regs; struct ksz_ptp_data *ptp_data; int ret; u8 i; @@ -936,7 +944,7 @@ int ksz_ptp_clock_register(struct dsa_switch *ds) /* Currently only P2P mode is supported. When 802_1AS bit is set, it * forwards all PTP packets to host port and none to other ports. */ - ret = ksz_rmw16(dev, REG_PTP_MSG_CONF1, PTP_TC_P2P | PTP_802_1AS, + ret = ksz_rmw16(dev, regs[PTP_MSG_CONF1], PTP_TC_P2P | PTP_802_1AS, PTP_TC_P2P | PTP_802_1AS); if (ret) return ret; @@ -959,6 +967,11 @@ void ksz_ptp_clock_unregister(struct dsa_switch *ds) ptp_clock_unregister(ptp_data->clock); } +static int ksz_read_ts(struct ksz_port *port, u16 reg, u32 *ts) +{ + return ksz_read32(port->ksz_dev, reg, ts); +} + static irqreturn_t ksz_ptp_msg_thread_fn(int irq, void *dev_id) { struct ksz_ptp_irq *ptpmsg_irq = dev_id; @@ -972,7 +985,7 @@ static irqreturn_t ksz_ptp_msg_thread_fn(int irq, void *dev_id) dev = port->ksz_dev; if (ptpmsg_irq->ts_en) { - ret = ksz_read32(dev, ptpmsg_irq->ts_reg, &tstamp_raw); + ret = ksz_read_ts(port, ptpmsg_irq->ts_reg, &tstamp_raw); if (ret) return IRQ_NONE; @@ -1008,7 +1021,7 @@ static irqreturn_t ksz_ptp_irq_thread_fn(int irq, void *dev_id) return IRQ_NONE; for (n = 0; n < ptpirq->nirqs; ++n) { - if (data & BIT(n + KSZ_PTP_INT_START)) { + if (data & BIT(n + ptpirq->irq0_offset)) { sub_irq = irq_find_mapping(ptpirq->domain, n); handle_nested_irq(sub_irq); ++nhandled; @@ -1023,14 +1036,14 @@ static void ksz_ptp_irq_mask(struct irq_data *d) { struct ksz_irq *kirq = irq_data_get_irq_chip_data(d); - kirq->masked &= ~BIT(d->hwirq + KSZ_PTP_INT_START); + kirq->masked &= ~BIT(d->hwirq + kirq->irq0_offset); } static void ksz_ptp_irq_unmask(struct irq_data *d) { struct ksz_irq *kirq = irq_data_get_irq_chip_data(d); - kirq->masked |= BIT(d->hwirq + KSZ_PTP_INT_START); + kirq->masked |= BIT(d->hwirq + kirq->irq0_offset); } static void ksz_ptp_irq_bus_lock(struct irq_data *d) @@ -1126,6 +1139,8 @@ int ksz_ptp_irq_setup(struct dsa_switch *ds, u8 p) ptpirq->reg_mask = ops->get_port_addr(p, REG_PTP_PORT_TX_INT_ENABLE__2); ptpirq->reg_status = ops->get_port_addr(p, REG_PTP_PORT_TX_INT_STATUS__2); + ptpirq->irq0_offset = KSZ_PTP_INT_START; + snprintf(ptpirq->name, sizeof(ptpirq->name), "ptp-irq-%d", p); init_completion(&port->tstamp_msg_comp); diff --git a/drivers/net/dsa/microchip/ksz_ptp_reg.h b/drivers/net/dsa/microchip/ksz_ptp_reg.h index d71e85510cda..eab9aecb7fa8 100644 --- a/drivers/net/dsa/microchip/ksz_ptp_reg.h +++ b/drivers/net/dsa/microchip/ksz_ptp_reg.h @@ -15,8 +15,7 @@ #define LED_SRC_PTP_GPIO_2 BIT(2) /* 5 - PTP Clock */ -#define REG_PTP_CLK_CTRL 0x0500 - +/* REG_PTP_CLK_CTRL */ #define PTP_STEP_ADJ BIT(6) #define PTP_STEP_DIR BIT(5) #define PTP_READ_TIME BIT(4) @@ -25,17 +24,11 @@ #define PTP_CLK_ENABLE BIT(1) #define PTP_CLK_RESET BIT(0) -#define REG_PTP_RTC_SUB_NANOSEC__2 0x0502 - +/* REG_PTP_RTC_SUB_NANOSEC */ #define PTP_RTC_SUB_NANOSEC_M 0x0007 #define PTP_RTC_0NS 0x00 -#define REG_PTP_RTC_NANOSEC 0x0504 - -#define REG_PTP_RTC_SEC 0x0508 - -#define REG_PTP_SUBNANOSEC_RATE 0x050C - +/* REG_PTP_SUBNANOSEC_RATE */ #define PTP_SUBNANOSEC_M 0x3FFFFFFF #define PTP_RATE_DIR BIT(31) #define PTP_TMP_RATE_ENABLE BIT(30) @@ -46,8 +39,7 @@ #define REG_PTP_RATE_DURATION_H 0x0510 #define REG_PTP_RATE_DURATION_L 0x0512 -#define REG_PTP_MSG_CONF1 0x0514 - +/* REG_PTP_MSG_CONF1 */ #define PTP_802_1AS BIT(7) #define PTP_ENABLE BIT(6) #define PTP_ETH_ENABLE BIT(5) diff --git a/drivers/net/dsa/mt7530-mdio.c b/drivers/net/dsa/mt7530-mdio.c index 0286a6cecb6f..11ea924a9f35 100644 --- a/drivers/net/dsa/mt7530-mdio.c +++ b/drivers/net/dsa/mt7530-mdio.c @@ -113,8 +113,8 @@ mt7531_create_sgmii(struct mt7530_priv *priv) ret = PTR_ERR(regmap); break; } - pcs = mtk_pcs_lynxi_create(priv->dev, regmap, - MT7531_PHYA_CTRL_SIGNAL3, 0); + pcs = mtk_pcs_lynxi_create(priv->dev, NULL, regmap, + MT7531_PHYA_CTRL_SIGNAL3); if (!pcs) { ret = -ENXIO; break; diff --git a/drivers/net/dsa/mxl862xx/Kconfig b/drivers/net/dsa/mxl862xx/Kconfig new file mode 100644 index 000000000000..4db7bab21a71 --- /dev/null +++ b/drivers/net/dsa/mxl862xx/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only +config NET_DSA_MXL862 + tristate "MaxLinear MxL862xx" + depends on NET_DSA + select MAXLINEAR_GPHY + select NET_DSA_TAG_MXL_862XX + help + This enables support for the MaxLinear MxL862xx switch family. + These switches have two 10GE SerDes interfaces, one typically + used as CPU port. + - MxL86282 has eight 2.5 Gigabit PHYs + - MxL86252 has five 2.5 Gigabit PHYs diff --git a/drivers/net/dsa/mxl862xx/Makefile b/drivers/net/dsa/mxl862xx/Makefile new file mode 100644 index 000000000000..d23dd3cd511d --- /dev/null +++ b/drivers/net/dsa/mxl862xx/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_NET_DSA_MXL862) += mxl862xx_dsa.o +mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-api.h b/drivers/net/dsa/mxl862xx/mxl862xx-api.h new file mode 100644 index 000000000000..a9f599dbca25 --- /dev/null +++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h @@ -0,0 +1,675 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef __MXL862XX_API_H +#define __MXL862XX_API_H + +#include <linux/if_ether.h> + +/** + * struct mdio_relay_data - relayed access to the switch internal MDIO bus + * @data: data to be read or written + * @phy: PHY index + * @mmd: MMD device + * @reg: register index + */ +struct mdio_relay_data { + __le16 data; + u8 phy; + u8 mmd; + __le16 reg; +} __packed; + +/** + * struct mxl862xx_register_mod - Register access parameter to directly + * modify internal registers + * @addr: Register address offset for modification + * @data: Value to write to the register address + * @mask: Mask of bits to be modified (1 to modify, 0 to ignore) + * + * Used for direct register modification operations. + */ +struct mxl862xx_register_mod { + __le16 addr; + __le16 data; + __le16 mask; +} __packed; + +/** + * enum mxl862xx_mac_clear_type - MAC table clear type + * @MXL862XX_MAC_CLEAR_PHY_PORT: clear dynamic entries based on port_id + * @MXL862XX_MAC_CLEAR_DYNAMIC: clear all dynamic entries + */ +enum mxl862xx_mac_clear_type { + MXL862XX_MAC_CLEAR_PHY_PORT = 0, + MXL862XX_MAC_CLEAR_DYNAMIC, +}; + +/** + * struct mxl862xx_mac_table_clear - MAC table clear + * @type: see &enum mxl862xx_mac_clear_type + * @port_id: physical port id + */ +struct mxl862xx_mac_table_clear { + u8 type; + u8 port_id; +} __packed; + +/** + * enum mxl862xx_age_timer - Aging Timer Value. + * @MXL862XX_AGETIMER_1_SEC: 1 second aging time + * @MXL862XX_AGETIMER_10_SEC: 10 seconds aging time + * @MXL862XX_AGETIMER_300_SEC: 300 seconds aging time + * @MXL862XX_AGETIMER_1_HOUR: 1 hour aging time + * @MXL862XX_AGETIMER_1_DAY: 24 hours aging time + * @MXL862XX_AGETIMER_CUSTOM: Custom aging time in seconds + */ +enum mxl862xx_age_timer { + MXL862XX_AGETIMER_1_SEC = 1, + MXL862XX_AGETIMER_10_SEC, + MXL862XX_AGETIMER_300_SEC, + MXL862XX_AGETIMER_1_HOUR, + MXL862XX_AGETIMER_1_DAY, + MXL862XX_AGETIMER_CUSTOM, +}; + +/** + * struct mxl862xx_bridge_alloc - Bridge Allocation + * @bridge_id: If the bridge allocation is successful, a valid ID will be + * returned in this field. Otherwise, INVALID_HANDLE is + * returned. For bridge free, this field should contain a + * valid ID returned by the bridge allocation. ID 0 is not + * used for historic reasons. + * + * Used by MXL862XX_BRIDGE_ALLOC and MXL862XX_BRIDGE_FREE. + */ +struct mxl862xx_bridge_alloc { + __le16 bridge_id; +}; + +/** + * enum mxl862xx_bridge_config_mask - Bridge configuration mask + * @MXL862XX_BRIDGE_CONFIG_MASK_MAC_LEARNING_LIMIT: + * Mask for mac_learning_limit_enable and mac_learning_limit. + * @MXL862XX_BRIDGE_CONFIG_MASK_MAC_LEARNED_COUNT: + * Mask for mac_learning_count + * @MXL862XX_BRIDGE_CONFIG_MASK_MAC_DISCARD_COUNT: + * Mask for learning_discard_event + * @MXL862XX_BRIDGE_CONFIG_MASK_SUB_METER: + * Mask for sub_metering_enable and traffic_sub_meter_id + * @MXL862XX_BRIDGE_CONFIG_MASK_FORWARDING_MODE: + * Mask for forward_broadcast, forward_unknown_multicast_ip, + * forward_unknown_multicast_non_ip and forward_unknown_unicast. + * @MXL862XX_BRIDGE_CONFIG_MASK_ALL: Enable all + * @MXL862XX_BRIDGE_CONFIG_MASK_FORCE: Bypass any check for debug purpose + */ +enum mxl862xx_bridge_config_mask { + MXL862XX_BRIDGE_CONFIG_MASK_MAC_LEARNING_LIMIT = BIT(0), + MXL862XX_BRIDGE_CONFIG_MASK_MAC_LEARNED_COUNT = BIT(1), + MXL862XX_BRIDGE_CONFIG_MASK_MAC_DISCARD_COUNT = BIT(2), + MXL862XX_BRIDGE_CONFIG_MASK_SUB_METER = BIT(3), + MXL862XX_BRIDGE_CONFIG_MASK_FORWARDING_MODE = BIT(4), + MXL862XX_BRIDGE_CONFIG_MASK_ALL = 0x7FFFFFFF, + MXL862XX_BRIDGE_CONFIG_MASK_FORCE = BIT(31) +}; + +/** + * enum mxl862xx_bridge_port_egress_meter - Meters for egress traffic type + * @MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST: + * Index of broadcast traffic meter + * @MXL862XX_BRIDGE_PORT_EGRESS_METER_MULTICAST: + * Index of known multicast traffic meter + * @MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_IP: + * Index of unknown multicast IP traffic meter + * @MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_NON_IP: + * Index of unknown multicast non-IP traffic meter + * @MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_UC: + * Index of unknown unicast traffic meter + * @MXL862XX_BRIDGE_PORT_EGRESS_METER_OTHERS: + * Index of traffic meter for other types + * @MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX: Number of index + */ +enum mxl862xx_bridge_port_egress_meter { + MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST = 0, + MXL862XX_BRIDGE_PORT_EGRESS_METER_MULTICAST, + MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_IP, + MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_NON_IP, + MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_UC, + MXL862XX_BRIDGE_PORT_EGRESS_METER_OTHERS, + MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX, +}; + +/** + * enum mxl862xx_bridge_forward_mode - Bridge forwarding type of packet + * @MXL862XX_BRIDGE_FORWARD_FLOOD: Packet is flooded to port members of + * ingress bridge port + * @MXL862XX_BRIDGE_FORWARD_DISCARD: Packet is discarded + */ +enum mxl862xx_bridge_forward_mode { + MXL862XX_BRIDGE_FORWARD_FLOOD = 0, + MXL862XX_BRIDGE_FORWARD_DISCARD, +}; + +/** + * struct mxl862xx_bridge_config - Bridge Configuration + * @bridge_id: Bridge ID (FID) + * @mask: See &enum mxl862xx_bridge_config_mask + * @mac_learning_limit_enable: Enable MAC learning limitation + * @mac_learning_limit: Max number of MAC addresses that can be learned in + * this bridge (all bridge ports) + * @mac_learning_count: Number of MAC addresses learned from this bridge + * @learning_discard_event: Number of learning discard events due to + * hardware resource not available + * @sub_metering_enable: Traffic metering on type of traffic (such as + * broadcast, multicast, unknown unicast, etc) applies + * @traffic_sub_meter_id: Meter for bridge process with specific type (such + * as broadcast, multicast, unknown unicast, etc) + * @forward_broadcast: Forwarding mode of broadcast traffic. See + * &enum mxl862xx_bridge_forward_mode + * @forward_unknown_multicast_ip: Forwarding mode of unknown multicast IP + * traffic. + * See &enum mxl862xx_bridge_forward_mode + * @forward_unknown_multicast_non_ip: Forwarding mode of unknown multicast + * non-IP traffic. + * See &enum mxl862xx_bridge_forward_mode + * @forward_unknown_unicast: Forwarding mode of unknown unicast traffic. See + * &enum mxl862xx_bridge_forward_mode + */ +struct mxl862xx_bridge_config { + __le16 bridge_id; + __le32 mask; /* enum mxl862xx_bridge_config_mask */ + u8 mac_learning_limit_enable; + __le16 mac_learning_limit; + __le16 mac_learning_count; + __le32 learning_discard_event; + u8 sub_metering_enable[MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX]; + __le16 traffic_sub_meter_id[MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX]; + __le32 forward_broadcast; /* enum mxl862xx_bridge_forward_mode */ + __le32 forward_unknown_multicast_ip; /* enum mxl862xx_bridge_forward_mode */ + __le32 forward_unknown_multicast_non_ip; /* enum mxl862xx_bridge_forward_mode */ + __le32 forward_unknown_unicast; /* enum mxl862xx_bridge_forward_mode */ +} __packed; + +/** + * struct mxl862xx_bridge_port_alloc - Bridge Port Allocation + * @bridge_port_id: If the bridge port allocation is successful, a valid ID + * will be returned in this field. Otherwise, INVALID_HANDLE + * is returned. For bridge port free, this field should + * contain a valid ID returned by the bridge port allocation. + * + * Used by MXL862XX_BRIDGE_PORT_ALLOC and MXL862XX_BRIDGE_PORT_FREE. + */ +struct mxl862xx_bridge_port_alloc { + __le16 bridge_port_id; +}; + +/** + * enum mxl862xx_bridge_port_config_mask - Bridge Port configuration mask + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID: + * Mask for bridge_id + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_VLAN: + * Mask for ingress_extended_vlan_enable, + * ingress_extended_vlan_block_id and ingress_extended_vlan_block_size + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN: + * Mask for egress_extended_vlan_enable, egress_extended_vlan_block_id + * and egress_extended_vlan_block_size + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_MARKING: + * Mask for ingress_marking_mode + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_REMARKING: + * Mask for egress_remarking_mode + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_METER: + * Mask for ingress_metering_enable and ingress_traffic_meter_id + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_SUB_METER: + * Mask for egress_sub_metering_enable and egress_traffic_sub_meter_id + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_CTP_MAPPING: + * Mask for dest_logical_port_id, pmapper_enable, dest_sub_if_id_group, + * pmapper_mapping_mode, pmapper_id_valid and pmapper + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP: + * Mask for bridge_port_map + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_DEST_IP_LOOKUP: + * Mask for mc_dest_ip_lookup_disable + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_IP_LOOKUP: + * Mask for mc_src_ip_lookup_enable + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_DEST_MAC_LOOKUP: + * Mask for dest_mac_lookup_disable + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING: + * Mask for src_mac_learning_disable + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MAC_SPOOFING: + * Mask for mac_spoofing_detect_enable + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_PORT_LOCK: + * Mask for port_lock_enable + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MAC_LEARNING_LIMIT: + * Mask for mac_learning_limit_enable and mac_learning_limit + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_MAC_LEARNED_COUNT: + * Mask for mac_learning_count + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_VLAN_FILTER: + * Mask for ingress_vlan_filter_enable, ingress_vlan_filter_block_id + * and ingress_vlan_filter_block_size + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN_FILTER1: + * Mask for bypass_egress_vlan_filter1, egress_vlan_filter1enable, + * egress_vlan_filter1block_id and egress_vlan_filter1block_size + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN_FILTER2: + * Mask for egress_vlan_filter2enable, egress_vlan_filter2block_id and + * egress_vlan_filter2block_size + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING: + * Mask for vlan_tag_selection, vlan_src_mac_priority_enable, + * vlan_src_mac_dei_enable, vlan_src_mac_vid_enable, + * vlan_dst_mac_priority_enable, vlan_dst_mac_dei_enable and + * vlan_dst_mac_vid_enable + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MULTICAST_LOOKUP: + * Mask for vlan_multicast_priority_enable, + * vlan_multicast_dei_enable and vlan_multicast_vid_enable + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_LOOP_VIOLATION_COUNTER: + * Mask for loop_violation_count + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_ALL: Enable all + * @MXL862XX_BRIDGE_PORT_CONFIG_MASK_FORCE: Bypass any check for debug purpose + */ +enum mxl862xx_bridge_port_config_mask { + MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID = BIT(0), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_VLAN = BIT(1), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN = BIT(2), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_MARKING = BIT(3), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_REMARKING = BIT(4), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_METER = BIT(5), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_SUB_METER = BIT(6), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_CTP_MAPPING = BIT(7), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP = BIT(8), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_DEST_IP_LOOKUP = BIT(9), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_IP_LOOKUP = BIT(10), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_DEST_MAC_LOOKUP = BIT(11), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING = BIT(12), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MAC_SPOOFING = BIT(13), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_PORT_LOCK = BIT(14), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MAC_LEARNING_LIMIT = BIT(15), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MAC_LEARNED_COUNT = BIT(16), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_INGRESS_VLAN_FILTER = BIT(17), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN_FILTER1 = BIT(18), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_VLAN_FILTER2 = BIT(19), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING = BIT(20), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MULTICAST_LOOKUP = BIT(21), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_LOOP_VIOLATION_COUNTER = BIT(22), + MXL862XX_BRIDGE_PORT_CONFIG_MASK_ALL = 0x7FFFFFFF, + MXL862XX_BRIDGE_PORT_CONFIG_MASK_FORCE = BIT(31) +}; + +/** + * enum mxl862xx_color_marking_mode - Color Marking Mode + * @MXL862XX_MARKING_ALL_GREEN: mark packets (except critical) to green + * @MXL862XX_MARKING_INTERNAL_MARKING: do not change color and priority + * @MXL862XX_MARKING_DEI: DEI mark mode + * @MXL862XX_MARKING_PCP_8P0D: PCP 8P0D mark mode + * @MXL862XX_MARKING_PCP_7P1D: PCP 7P1D mark mode + * @MXL862XX_MARKING_PCP_6P2D: PCP 6P2D mark mode + * @MXL862XX_MARKING_PCP_5P3D: PCP 5P3D mark mode + * @MXL862XX_MARKING_DSCP_AF: DSCP AF class + */ +enum mxl862xx_color_marking_mode { + MXL862XX_MARKING_ALL_GREEN = 0, + MXL862XX_MARKING_INTERNAL_MARKING, + MXL862XX_MARKING_DEI, + MXL862XX_MARKING_PCP_8P0D, + MXL862XX_MARKING_PCP_7P1D, + MXL862XX_MARKING_PCP_6P2D, + MXL862XX_MARKING_PCP_5P3D, + MXL862XX_MARKING_DSCP_AF, +}; + +/** + * enum mxl862xx_color_remarking_mode - Color Remarking Mode + * @MXL862XX_REMARKING_NONE: values from last process stage + * @MXL862XX_REMARKING_DEI: DEI mark mode + * @MXL862XX_REMARKING_PCP_8P0D: PCP 8P0D mark mode + * @MXL862XX_REMARKING_PCP_7P1D: PCP 7P1D mark mode + * @MXL862XX_REMARKING_PCP_6P2D: PCP 6P2D mark mode + * @MXL862XX_REMARKING_PCP_5P3D: PCP 5P3D mark mode + * @MXL862XX_REMARKING_DSCP_AF: DSCP AF class + */ +enum mxl862xx_color_remarking_mode { + MXL862XX_REMARKING_NONE = 0, + MXL862XX_REMARKING_DEI = 2, + MXL862XX_REMARKING_PCP_8P0D, + MXL862XX_REMARKING_PCP_7P1D, + MXL862XX_REMARKING_PCP_6P2D, + MXL862XX_REMARKING_PCP_5P3D, + MXL862XX_REMARKING_DSCP_AF, +}; + +/** + * enum mxl862xx_pmapper_mapping_mode - P-mapper Mapping Mode + * @MXL862XX_PMAPPER_MAPPING_PCP: Use PCP for VLAN tagged packets to derive + * sub interface ID group + * @MXL862XX_PMAPPER_MAPPING_LAG: Use LAG Index for Pmapper access + * regardless of IP and VLAN packet + * @MXL862XX_PMAPPER_MAPPING_DSCP: Use DSCP for VLAN tagged IP packets to + * derive sub interface ID group + */ +enum mxl862xx_pmapper_mapping_mode { + MXL862XX_PMAPPER_MAPPING_PCP = 0, + MXL862XX_PMAPPER_MAPPING_LAG, + MXL862XX_PMAPPER_MAPPING_DSCP, +}; + +/** + * struct mxl862xx_pmapper - P-mapper Configuration + * @pmapper_id: Index of P-mapper (0-31) + * @dest_sub_if_id_group: Sub interface ID group. Entry 0 is for non-IP and + * non-VLAN tagged packets. + * Entries 1-8 are PCP mapping entries for VLAN tagged + * packets. + * Entries 9-72 are DSCP or LAG mapping entries. + * + * Used by CTP port config and bridge port config. In case of LAG, it is + * user's responsibility to provide the mapped entries in given P-mapper + * table. In other modes the entries are auto mapped from input packet. + */ +struct mxl862xx_pmapper { + __le16 pmapper_id; + u8 dest_sub_if_id_group[73]; +} __packed; + +/** + * struct mxl862xx_bridge_port_config - Bridge Port Configuration + * @bridge_port_id: Bridge Port ID allocated by bridge port allocation + * @mask: See &enum mxl862xx_bridge_port_config_mask + * @bridge_id: Bridge ID (FID) to which this bridge port is associated + * @ingress_extended_vlan_enable: Enable extended VLAN processing for + * ingress traffic + * @ingress_extended_vlan_block_id: Extended VLAN block allocated for + * ingress traffic + * @ingress_extended_vlan_block_size: Extended VLAN block size for ingress + * traffic + * @egress_extended_vlan_enable: Enable extended VLAN processing for egress + * traffic + * @egress_extended_vlan_block_id: Extended VLAN block allocated for egress + * traffic + * @egress_extended_vlan_block_size: Extended VLAN block size for egress + * traffic + * @ingress_marking_mode: Ingress color marking mode. See + * &enum mxl862xx_color_marking_mode + * @egress_remarking_mode: Color remarking for egress traffic. See + * &enum mxl862xx_color_remarking_mode + * @ingress_metering_enable: Traffic metering on ingress traffic applies + * @ingress_traffic_meter_id: Meter for ingress Bridge Port process + * @egress_sub_metering_enable: Traffic metering on various types of egress + * traffic + * @egress_traffic_sub_meter_id: Meter for egress Bridge Port process with + * specific type + * @dest_logical_port_id: Destination logical port + * @pmapper_enable: Enable P-mapper + * @dest_sub_if_id_group: Destination sub interface ID group when + * pmapper_enable is false + * @pmapper_mapping_mode: P-mapper mapping mode. See + * &enum mxl862xx_pmapper_mapping_mode + * @pmapper_id_valid: When true, P-mapper is re-used; when false, + * allocation is handled by API + * @pmapper: P-mapper configuration used when pmapper_enable is true + * @bridge_port_map: Port map defining broadcast domain. Each bit + * represents one bridge port. Bridge port ID is + * index * 16 + bit offset. + * @mc_dest_ip_lookup_disable: Disable multicast IP destination table + * lookup + * @mc_src_ip_lookup_enable: Enable multicast IP source table lookup + * @dest_mac_lookup_disable: Disable destination MAC lookup; packet treated + * as unknown + * @src_mac_learning_disable: Disable source MAC address learning + * @mac_spoofing_detect_enable: Enable MAC spoofing detection + * @port_lock_enable: Enable port locking + * @mac_learning_limit_enable: Enable MAC learning limitation + * @mac_learning_limit: Maximum number of MAC addresses that can be learned + * from this bridge port + * @loop_violation_count: Number of loop violation events from this bridge + * port + * @mac_learning_count: Number of MAC addresses learned from this bridge + * port + * @ingress_vlan_filter_enable: Enable ingress VLAN filter + * @ingress_vlan_filter_block_id: VLAN filter block of ingress traffic + * @ingress_vlan_filter_block_size: VLAN filter block size for ingress + * traffic + * @bypass_egress_vlan_filter1: For ingress traffic, bypass VLAN filter 1 + * at egress bridge port processing + * @egress_vlan_filter1enable: Enable egress VLAN filter 1 + * @egress_vlan_filter1block_id: VLAN filter block 1 of egress traffic + * @egress_vlan_filter1block_size: VLAN filter block 1 size + * @egress_vlan_filter2enable: Enable egress VLAN filter 2 + * @egress_vlan_filter2block_id: VLAN filter block 2 of egress traffic + * @egress_vlan_filter2block_size: VLAN filter block 2 size + * @vlan_tag_selection: VLAN tag selection for MAC address/multicast + * learning, lookup and filtering. + * 0 - Intermediate outer VLAN tag is used. + * 1 - Original outer VLAN tag is used. + * @vlan_src_mac_priority_enable: Enable VLAN Priority field for source MAC + * learning and filtering + * @vlan_src_mac_dei_enable: Enable VLAN DEI/CFI field for source MAC + * learning and filtering + * @vlan_src_mac_vid_enable: Enable VLAN ID field for source MAC learning + * and filtering + * @vlan_dst_mac_priority_enable: Enable VLAN Priority field for destination + * MAC lookup and filtering + * @vlan_dst_mac_dei_enable: Enable VLAN CFI/DEI field for destination MAC + * lookup and filtering + * @vlan_dst_mac_vid_enable: Enable VLAN ID field for destination MAC lookup + * and filtering + * @vlan_multicast_priority_enable: Enable VLAN Priority field for IP + * multicast lookup + * @vlan_multicast_dei_enable: Enable VLAN CFI/DEI field for IP multicast + * lookup + * @vlan_multicast_vid_enable: Enable VLAN ID field for IP multicast lookup + */ +struct mxl862xx_bridge_port_config { + __le16 bridge_port_id; + __le32 mask; /* enum mxl862xx_bridge_port_config_mask */ + __le16 bridge_id; + u8 ingress_extended_vlan_enable; + __le16 ingress_extended_vlan_block_id; + __le16 ingress_extended_vlan_block_size; + u8 egress_extended_vlan_enable; + __le16 egress_extended_vlan_block_id; + __le16 egress_extended_vlan_block_size; + __le32 ingress_marking_mode; /* enum mxl862xx_color_marking_mode */ + __le32 egress_remarking_mode; /* enum mxl862xx_color_remarking_mode */ + u8 ingress_metering_enable; + __le16 ingress_traffic_meter_id; + u8 egress_sub_metering_enable[MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX]; + __le16 egress_traffic_sub_meter_id[MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX]; + u8 dest_logical_port_id; + u8 pmapper_enable; + __le16 dest_sub_if_id_group; + __le32 pmapper_mapping_mode; /* enum mxl862xx_pmapper_mapping_mode */ + u8 pmapper_id_valid; + struct mxl862xx_pmapper pmapper; + __le16 bridge_port_map[8]; + u8 mc_dest_ip_lookup_disable; + u8 mc_src_ip_lookup_enable; + u8 dest_mac_lookup_disable; + u8 src_mac_learning_disable; + u8 mac_spoofing_detect_enable; + u8 port_lock_enable; + u8 mac_learning_limit_enable; + __le16 mac_learning_limit; + __le16 loop_violation_count; + __le16 mac_learning_count; + u8 ingress_vlan_filter_enable; + __le16 ingress_vlan_filter_block_id; + __le16 ingress_vlan_filter_block_size; + u8 bypass_egress_vlan_filter1; + u8 egress_vlan_filter1enable; + __le16 egress_vlan_filter1block_id; + __le16 egress_vlan_filter1block_size; + u8 egress_vlan_filter2enable; + __le16 egress_vlan_filter2block_id; + __le16 egress_vlan_filter2block_size; + u8 vlan_tag_selection; + u8 vlan_src_mac_priority_enable; + u8 vlan_src_mac_dei_enable; + u8 vlan_src_mac_vid_enable; + u8 vlan_dst_mac_priority_enable; + u8 vlan_dst_mac_dei_enable; + u8 vlan_dst_mac_vid_enable; + u8 vlan_multicast_priority_enable; + u8 vlan_multicast_dei_enable; + u8 vlan_multicast_vid_enable; +} __packed; + +/** + * struct mxl862xx_cfg - Global Switch configuration Attributes + * @mac_table_age_timer: See &enum mxl862xx_age_timer + * @age_timer: Custom MAC table aging timer in seconds + * @max_packet_len: Maximum Ethernet packet length + * @learning_limit_action: Automatic MAC address table learning limitation + * consecutive action + * @mac_locking_action: Accept or discard MAC port locking violation + * packets + * @mac_spoofing_action: Accept or discard MAC spoofing and port MAC locking + * violation packets + * @pause_mac_mode_src: Pause frame MAC source address mode + * @pause_mac_src: Pause frame MAC source address + */ +struct mxl862xx_cfg { + __le32 mac_table_age_timer; /* enum mxl862xx_age_timer */ + __le32 age_timer; + __le16 max_packet_len; + u8 learning_limit_action; + u8 mac_locking_action; + u8 mac_spoofing_action; + u8 pause_mac_mode_src; + u8 pause_mac_src[ETH_ALEN]; +} __packed; + +/** + * enum mxl862xx_ss_sp_tag_mask - Special tag valid field indicator bits + * @MXL862XX_SS_SP_TAG_MASK_RX: valid RX special tag mode + * @MXL862XX_SS_SP_TAG_MASK_TX: valid TX special tag mode + * @MXL862XX_SS_SP_TAG_MASK_RX_PEN: valid RX special tag info over preamble + * @MXL862XX_SS_SP_TAG_MASK_TX_PEN: valid TX special tag info over preamble + */ +enum mxl862xx_ss_sp_tag_mask { + MXL862XX_SS_SP_TAG_MASK_RX = BIT(0), + MXL862XX_SS_SP_TAG_MASK_TX = BIT(1), + MXL862XX_SS_SP_TAG_MASK_RX_PEN = BIT(2), + MXL862XX_SS_SP_TAG_MASK_TX_PEN = BIT(3), +}; + +/** + * enum mxl862xx_ss_sp_tag_rx - RX special tag mode + * @MXL862XX_SS_SP_TAG_RX_NO_TAG_NO_INSERT: packet does NOT have special + * tag and special tag is NOT inserted + * @MXL862XX_SS_SP_TAG_RX_NO_TAG_INSERT: packet does NOT have special tag + * and special tag is inserted + * @MXL862XX_SS_SP_TAG_RX_TAG_NO_INSERT: packet has special tag and special + * tag is NOT inserted + */ +enum mxl862xx_ss_sp_tag_rx { + MXL862XX_SS_SP_TAG_RX_NO_TAG_NO_INSERT = 0, + MXL862XX_SS_SP_TAG_RX_NO_TAG_INSERT = 1, + MXL862XX_SS_SP_TAG_RX_TAG_NO_INSERT = 2, +}; + +/** + * enum mxl862xx_ss_sp_tag_tx - TX special tag mode + * @MXL862XX_SS_SP_TAG_TX_NO_TAG_NO_REMOVE: packet does NOT have special + * tag and special tag is NOT removed + * @MXL862XX_SS_SP_TAG_TX_TAG_REPLACE: packet has special tag and special + * tag is replaced + * @MXL862XX_SS_SP_TAG_TX_TAG_NO_REMOVE: packet has special tag and special + * tag is NOT removed + * @MXL862XX_SS_SP_TAG_TX_TAG_REMOVE: packet has special tag and special + * tag is removed + */ +enum mxl862xx_ss_sp_tag_tx { + MXL862XX_SS_SP_TAG_TX_NO_TAG_NO_REMOVE = 0, + MXL862XX_SS_SP_TAG_TX_TAG_REPLACE = 1, + MXL862XX_SS_SP_TAG_TX_TAG_NO_REMOVE = 2, + MXL862XX_SS_SP_TAG_TX_TAG_REMOVE = 3, +}; + +/** + * enum mxl862xx_ss_sp_tag_rx_pen - RX special tag info over preamble + * @MXL862XX_SS_SP_TAG_RX_PEN_ALL_0: special tag info inserted from byte 2 + * to 7 are all 0 + * @MXL862XX_SS_SP_TAG_RX_PEN_BYTE_5_IS_16: special tag byte 5 is 16, other + * bytes from 2 to 7 are 0 + * @MXL862XX_SS_SP_TAG_RX_PEN_BYTE_5_FROM_PREAMBLE: special tag byte 5 is + * from preamble field, others + * are 0 + * @MXL862XX_SS_SP_TAG_RX_PEN_BYTE_2_TO_7_FROM_PREAMBLE: special tag byte 2 + * to 7 are from preamble + * field + */ +enum mxl862xx_ss_sp_tag_rx_pen { + MXL862XX_SS_SP_TAG_RX_PEN_ALL_0 = 0, + MXL862XX_SS_SP_TAG_RX_PEN_BYTE_5_IS_16 = 1, + MXL862XX_SS_SP_TAG_RX_PEN_BYTE_5_FROM_PREAMBLE = 2, + MXL862XX_SS_SP_TAG_RX_PEN_BYTE_2_TO_7_FROM_PREAMBLE = 3, +}; + +/** + * struct mxl862xx_ss_sp_tag - Special tag port settings + * @pid: port ID (1~16) + * @mask: See &enum mxl862xx_ss_sp_tag_mask + * @rx: See &enum mxl862xx_ss_sp_tag_rx + * @tx: See &enum mxl862xx_ss_sp_tag_tx + * @rx_pen: See &enum mxl862xx_ss_sp_tag_rx_pen + * @tx_pen: TX special tag info over preamble + * 0 - disabled + * 1 - enabled + */ +struct mxl862xx_ss_sp_tag { + u8 pid; + u8 mask; /* enum mxl862xx_ss_sp_tag_mask */ + u8 rx; /* enum mxl862xx_ss_sp_tag_rx */ + u8 tx; /* enum mxl862xx_ss_sp_tag_tx */ + u8 rx_pen; /* enum mxl862xx_ss_sp_tag_rx_pen */ + u8 tx_pen; /* boolean */ +} __packed; + +/** + * enum mxl862xx_logical_port_mode - Logical port mode + * @MXL862XX_LOGICAL_PORT_8BIT_WLAN: WLAN with 8-bit station ID + * @MXL862XX_LOGICAL_PORT_9BIT_WLAN: WLAN with 9-bit station ID + * @MXL862XX_LOGICAL_PORT_ETHERNET: Ethernet port + * @MXL862XX_LOGICAL_PORT_OTHER: Others + */ +enum mxl862xx_logical_port_mode { + MXL862XX_LOGICAL_PORT_8BIT_WLAN = 0, + MXL862XX_LOGICAL_PORT_9BIT_WLAN, + MXL862XX_LOGICAL_PORT_ETHERNET, + MXL862XX_LOGICAL_PORT_OTHER = 0xFF, +}; + +/** + * struct mxl862xx_ctp_port_assignment - CTP Port Assignment/association + * with logical port + * @logical_port_id: Logical Port Id. The valid range is hardware dependent + * @first_ctp_port_id: First CTP (Connectivity Termination Port) ID mapped + * to above logical port ID + * @number_of_ctp_port: Total number of CTP Ports mapped above logical port + * ID + * @mode: Logical port mode to define sub interface ID format. See + * &enum mxl862xx_logical_port_mode + * @bridge_port_id: Bridge Port ID (not FID). For allocation, each CTP + * allocated is mapped to the Bridge Port given by this field. + * The Bridge Port will be configured to use first CTP as + * egress CTP. + */ +struct mxl862xx_ctp_port_assignment { + u8 logical_port_id; + __le16 first_ctp_port_id; + __le16 number_of_ctp_port; + __le32 mode; /* enum mxl862xx_logical_port_mode */ + __le16 bridge_port_id; +} __packed; + +/** + * struct mxl862xx_sys_fw_image_version - Firmware version information + * @iv_major: firmware major version + * @iv_minor: firmware minor version + * @iv_revision: firmware revision + * @iv_build_num: firmware build number + */ +struct mxl862xx_sys_fw_image_version { + u8 iv_major; + u8 iv_minor; + __le16 iv_revision; + __le32 iv_build_num; +} __packed; + +#endif /* __MXL862XX_API_H */ diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h new file mode 100644 index 000000000000..f6852ade64e7 --- /dev/null +++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef __MXL862XX_CMD_H +#define __MXL862XX_CMD_H + +#define MXL862XX_MMD_DEV 30 +#define MXL862XX_MMD_REG_CTRL 0 +#define MXL862XX_MMD_REG_LEN_RET 1 +#define MXL862XX_MMD_REG_DATA_FIRST 2 +#define MXL862XX_MMD_REG_DATA_LAST 95 +#define MXL862XX_MMD_REG_DATA_MAX_SIZE \ + (MXL862XX_MMD_REG_DATA_LAST - MXL862XX_MMD_REG_DATA_FIRST + 1) + +#define MXL862XX_COMMON_MAGIC 0x100 +#define MXL862XX_BRDG_MAGIC 0x300 +#define MXL862XX_BRDGPORT_MAGIC 0x400 +#define MXL862XX_CTP_MAGIC 0x500 +#define MXL862XX_SWMAC_MAGIC 0xa00 +#define MXL862XX_SS_MAGIC 0x1600 +#define GPY_GPY2XX_MAGIC 0x1800 +#define SYS_MISC_MAGIC 0x1900 + +#define MXL862XX_COMMON_CFGGET (MXL862XX_COMMON_MAGIC + 0x9) +#define MXL862XX_COMMON_REGISTERMOD (MXL862XX_COMMON_MAGIC + 0x11) + +#define MXL862XX_BRIDGE_ALLOC (MXL862XX_BRDG_MAGIC + 0x1) +#define MXL862XX_BRIDGE_CONFIGSET (MXL862XX_BRDG_MAGIC + 0x2) +#define MXL862XX_BRIDGE_CONFIGGET (MXL862XX_BRDG_MAGIC + 0x3) +#define MXL862XX_BRIDGE_FREE (MXL862XX_BRDG_MAGIC + 0x4) + +#define MXL862XX_BRIDGEPORT_ALLOC (MXL862XX_BRDGPORT_MAGIC + 0x1) +#define MXL862XX_BRIDGEPORT_CONFIGSET (MXL862XX_BRDGPORT_MAGIC + 0x2) +#define MXL862XX_BRIDGEPORT_CONFIGGET (MXL862XX_BRDGPORT_MAGIC + 0x3) +#define MXL862XX_BRIDGEPORT_FREE (MXL862XX_BRDGPORT_MAGIC + 0x4) + +#define MXL862XX_CTP_PORTASSIGNMENTSET (MXL862XX_CTP_MAGIC + 0x3) + +#define MXL862XX_MAC_TABLECLEARCOND (MXL862XX_SWMAC_MAGIC + 0x8) + +#define MXL862XX_SS_SPTAG_SET (MXL862XX_SS_MAGIC + 0x02) + +#define INT_GPHY_READ (GPY_GPY2XX_MAGIC + 0x01) +#define INT_GPHY_WRITE (GPY_GPY2XX_MAGIC + 0x02) + +#define SYS_MISC_FW_VERSION (SYS_MISC_MAGIC + 0x02) + +#define MMD_API_MAXIMUM_ID 0x7fff + +#endif /* __MXL862XX_CMD_H */ diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.c b/drivers/net/dsa/mxl862xx/mxl862xx-host.c new file mode 100644 index 000000000000..8c55497a0ce8 --- /dev/null +++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Based upon the MaxLinear SDK driver + * + * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org> + * Copyright (C) 2025 John Crispin <john@phrozen.org> + * Copyright (C) 2024 MaxLinear Inc. + */ + +#include <linux/bits.h> +#include <linux/iopoll.h> +#include <linux/limits.h> +#include <net/dsa.h> +#include "mxl862xx.h" +#include "mxl862xx-host.h" + +#define CTRL_BUSY_MASK BIT(15) + +#define MXL862XX_MMD_REG_CTRL 0 +#define MXL862XX_MMD_REG_LEN_RET 1 +#define MXL862XX_MMD_REG_DATA_FIRST 2 +#define MXL862XX_MMD_REG_DATA_LAST 95 +#define MXL862XX_MMD_REG_DATA_MAX_SIZE \ + (MXL862XX_MMD_REG_DATA_LAST - MXL862XX_MMD_REG_DATA_FIRST + 1) + +#define MMD_API_SET_DATA_0 2 +#define MMD_API_GET_DATA_0 5 +#define MMD_API_RST_DATA 8 + +#define MXL862XX_SWITCH_RESET 0x9907 + +static int mxl862xx_reg_read(struct mxl862xx_priv *priv, u32 addr) +{ + return __mdiodev_c45_read(priv->mdiodev, MDIO_MMD_VEND1, addr); +} + +static int mxl862xx_reg_write(struct mxl862xx_priv *priv, u32 addr, u16 data) +{ + return __mdiodev_c45_write(priv->mdiodev, MDIO_MMD_VEND1, addr, data); +} + +static int mxl862xx_ctrl_read(struct mxl862xx_priv *priv) +{ + return mxl862xx_reg_read(priv, MXL862XX_MMD_REG_CTRL); +} + +static int mxl862xx_busy_wait(struct mxl862xx_priv *priv) +{ + int val; + + return readx_poll_timeout(mxl862xx_ctrl_read, priv, val, + !(val & CTRL_BUSY_MASK), 15, 500000); +} + +static int mxl862xx_set_data(struct mxl862xx_priv *priv, u16 words) +{ + int ret; + u16 cmd; + + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET, + MXL862XX_MMD_REG_DATA_MAX_SIZE * sizeof(u16)); + if (ret < 0) + return ret; + + cmd = words / MXL862XX_MMD_REG_DATA_MAX_SIZE - 1; + if (!(cmd < 2)) + return -EINVAL; + + cmd += MMD_API_SET_DATA_0; + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL, + cmd | CTRL_BUSY_MASK); + if (ret < 0) + return ret; + + return mxl862xx_busy_wait(priv); +} + +static int mxl862xx_get_data(struct mxl862xx_priv *priv, u16 words) +{ + int ret; + u16 cmd; + + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET, + MXL862XX_MMD_REG_DATA_MAX_SIZE * sizeof(u16)); + if (ret < 0) + return ret; + + cmd = words / MXL862XX_MMD_REG_DATA_MAX_SIZE; + if (!(cmd > 0 && cmd < 3)) + return -EINVAL; + + cmd += MMD_API_GET_DATA_0; + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL, + cmd | CTRL_BUSY_MASK); + if (ret < 0) + return ret; + + return mxl862xx_busy_wait(priv); +} + +static int mxl862xx_firmware_return(int ret) +{ + /* Only 16-bit values are valid. */ + if (WARN_ON(ret & GENMASK(31, 16))) + return -EINVAL; + + /* Interpret value as signed 16-bit integer. */ + return (s16)ret; +} + +static int mxl862xx_send_cmd(struct mxl862xx_priv *priv, u16 cmd, u16 size, + bool quiet) +{ + int ret; + + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET, size); + if (ret) + return ret; + + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL, + cmd | CTRL_BUSY_MASK); + if (ret) + return ret; + + ret = mxl862xx_busy_wait(priv); + if (ret) + return ret; + + ret = mxl862xx_reg_read(priv, MXL862XX_MMD_REG_LEN_RET); + if (ret < 0) + return ret; + + /* handle errors returned by the firmware as -EIO + * The firmware is based on Zephyr OS and uses the errors as + * defined in errno.h of Zephyr OS. See + * https://github.com/zephyrproject-rtos/zephyr/blob/v3.7.0/lib/libc/minimal/include/errno.h + */ + ret = mxl862xx_firmware_return(ret); + if (ret < 0) { + if (!quiet) + dev_err(&priv->mdiodev->dev, + "CMD %04x returned error %d\n", cmd, ret); + return -EIO; + } + + return ret; +} + +int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *_data, + u16 size, bool read, bool quiet) +{ + __le16 *data = _data; + int ret, cmd_ret; + u16 max, i; + + dev_dbg(&priv->mdiodev->dev, "CMD %04x DATA %*ph\n", cmd, size, data); + + mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED); + + max = (size + 1) / 2; + + ret = mxl862xx_busy_wait(priv); + if (ret < 0) + goto out; + + for (i = 0; i < max; i++) { + u16 off = i % MXL862XX_MMD_REG_DATA_MAX_SIZE; + + if (i && off == 0) { + /* Send command to set data when every + * MXL862XX_MMD_REG_DATA_MAX_SIZE of WORDs are written. + */ + ret = mxl862xx_set_data(priv, i); + if (ret < 0) + goto out; + } + + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_DATA_FIRST + off, + le16_to_cpu(data[i])); + if (ret < 0) + goto out; + } + + ret = mxl862xx_send_cmd(priv, cmd, size, quiet); + if (ret < 0 || !read) + goto out; + + /* store result of mxl862xx_send_cmd() */ + cmd_ret = ret; + + for (i = 0; i < max; i++) { + u16 off = i % MXL862XX_MMD_REG_DATA_MAX_SIZE; + + if (i && off == 0) { + /* Send command to fetch next batch of data when every + * MXL862XX_MMD_REG_DATA_MAX_SIZE of WORDs are read. + */ + ret = mxl862xx_get_data(priv, i); + if (ret < 0) + goto out; + } + + ret = mxl862xx_reg_read(priv, MXL862XX_MMD_REG_DATA_FIRST + off); + if (ret < 0) + goto out; + + if ((i * 2 + 1) == size) { + /* Special handling for last BYTE if it's not WORD + * aligned to avoid writing beyond the allocated data + * structure. + */ + *(uint8_t *)&data[i] = ret & 0xff; + } else { + data[i] = cpu_to_le16((u16)ret); + } + } + + /* on success return the result of the mxl862xx_send_cmd() */ + ret = cmd_ret; + + dev_dbg(&priv->mdiodev->dev, "RET %d DATA %*ph\n", ret, size, data); + +out: + mutex_unlock(&priv->mdiodev->bus->mdio_lock); + + return ret; +} + +int mxl862xx_reset(struct mxl862xx_priv *priv) +{ + int ret; + + mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED); + + /* Software reset */ + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_LEN_RET, 0); + if (ret) + goto out; + + ret = mxl862xx_reg_write(priv, MXL862XX_MMD_REG_CTRL, MXL862XX_SWITCH_RESET); +out: + mutex_unlock(&priv->mdiodev->bus->mdio_lock); + + return ret; +} diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.h b/drivers/net/dsa/mxl862xx/mxl862xx-host.h new file mode 100644 index 000000000000..7cc496f6be5c --- /dev/null +++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef __MXL862XX_HOST_H +#define __MXL862XX_HOST_H + +#include "mxl862xx.h" + +int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *data, u16 size, + bool read, bool quiet); +int mxl862xx_reset(struct mxl862xx_priv *priv); + +#endif /* __MXL862XX_HOST_H */ diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c new file mode 100644 index 000000000000..b1e2094b5816 --- /dev/null +++ b/drivers/net/dsa/mxl862xx/mxl862xx.c @@ -0,0 +1,476 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for MaxLinear MxL862xx switch family + * + * Copyright (C) 2024 MaxLinear Inc. + * Copyright (C) 2025 John Crispin <john@phrozen.org> + * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org> + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/of_device.h> +#include <linux/of_mdio.h> +#include <linux/phy.h> +#include <linux/phylink.h> +#include <net/dsa.h> + +#include "mxl862xx.h" +#include "mxl862xx-api.h" +#include "mxl862xx-cmd.h" +#include "mxl862xx-host.h" + +#define MXL862XX_API_WRITE(dev, cmd, data) \ + mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), false, false) +#define MXL862XX_API_READ(dev, cmd, data) \ + mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, false) +#define MXL862XX_API_READ_QUIET(dev, cmd, data) \ + mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, true) + +#define MXL862XX_SDMA_PCTRLP(p) (0xbc0 + ((p) * 0x6)) +#define MXL862XX_SDMA_PCTRL_EN BIT(0) + +#define MXL862XX_FDMA_PCTRLP(p) (0xa80 + ((p) * 0x6)) +#define MXL862XX_FDMA_PCTRL_EN BIT(0) + +#define MXL862XX_READY_TIMEOUT_MS 10000 +#define MXL862XX_READY_POLL_MS 100 + +static enum dsa_tag_protocol mxl862xx_get_tag_protocol(struct dsa_switch *ds, + int port, + enum dsa_tag_protocol m) +{ + return DSA_TAG_PROTO_MXL862; +} + +/* PHY access via firmware relay */ +static int mxl862xx_phy_read_mmd(struct mxl862xx_priv *priv, int port, + int devadd, int reg) +{ + struct mdio_relay_data param = { + .phy = port, + .mmd = devadd, + .reg = cpu_to_le16(reg), + }; + int ret; + + ret = MXL862XX_API_READ(priv, INT_GPHY_READ, param); + if (ret) + return ret; + + return le16_to_cpu(param.data); +} + +static int mxl862xx_phy_write_mmd(struct mxl862xx_priv *priv, int port, + int devadd, int reg, u16 data) +{ + struct mdio_relay_data param = { + .phy = port, + .mmd = devadd, + .reg = cpu_to_le16(reg), + .data = cpu_to_le16(data), + }; + + return MXL862XX_API_WRITE(priv, INT_GPHY_WRITE, param); +} + +static int mxl862xx_phy_read_mii_bus(struct mii_bus *bus, int port, int regnum) +{ + return mxl862xx_phy_read_mmd(bus->priv, port, 0, regnum); +} + +static int mxl862xx_phy_write_mii_bus(struct mii_bus *bus, int port, + int regnum, u16 val) +{ + return mxl862xx_phy_write_mmd(bus->priv, port, 0, regnum, val); +} + +static int mxl862xx_phy_read_c45_mii_bus(struct mii_bus *bus, int port, + int devadd, int regnum) +{ + return mxl862xx_phy_read_mmd(bus->priv, port, devadd, regnum); +} + +static int mxl862xx_phy_write_c45_mii_bus(struct mii_bus *bus, int port, + int devadd, int regnum, u16 val) +{ + return mxl862xx_phy_write_mmd(bus->priv, port, devadd, regnum, val); +} + +static int mxl862xx_wait_ready(struct dsa_switch *ds) +{ + struct mxl862xx_sys_fw_image_version ver = {}; + unsigned long start = jiffies, timeout; + struct mxl862xx_priv *priv = ds->priv; + struct mxl862xx_cfg cfg = {}; + int ret; + + timeout = start + msecs_to_jiffies(MXL862XX_READY_TIMEOUT_MS); + msleep(2000); /* it always takes at least 2 seconds */ + do { + ret = MXL862XX_API_READ_QUIET(priv, SYS_MISC_FW_VERSION, ver); + if (ret || !ver.iv_major) + goto not_ready_yet; + + /* being able to perform CFGGET indicates that + * the firmware is ready + */ + ret = MXL862XX_API_READ_QUIET(priv, + MXL862XX_COMMON_CFGGET, + cfg); + if (ret) + goto not_ready_yet; + + dev_info(ds->dev, "switch ready after %ums, firmware %u.%u.%u (build %u)\n", + jiffies_to_msecs(jiffies - start), + ver.iv_major, ver.iv_minor, + le16_to_cpu(ver.iv_revision), + le32_to_cpu(ver.iv_build_num)); + return 0; + +not_ready_yet: + msleep(MXL862XX_READY_POLL_MS); + } while (time_before(jiffies, timeout)); + + dev_err(ds->dev, "switch not responding after reset\n"); + return -ETIMEDOUT; +} + +static int mxl862xx_setup_mdio(struct dsa_switch *ds) +{ + struct mxl862xx_priv *priv = ds->priv; + struct device *dev = ds->dev; + struct device_node *mdio_np; + struct mii_bus *bus; + int ret; + + bus = devm_mdiobus_alloc(dev); + if (!bus) + return -ENOMEM; + + bus->priv = priv; + ds->user_mii_bus = bus; + bus->name = KBUILD_MODNAME "-mii"; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(dev)); + bus->read_c45 = mxl862xx_phy_read_c45_mii_bus; + bus->write_c45 = mxl862xx_phy_write_c45_mii_bus; + bus->read = mxl862xx_phy_read_mii_bus; + bus->write = mxl862xx_phy_write_mii_bus; + bus->parent = dev; + bus->phy_mask = ~ds->phys_mii_mask; + + mdio_np = of_get_child_by_name(dev->of_node, "mdio"); + if (!mdio_np) + return -ENODEV; + + ret = devm_of_mdiobus_register(dev, bus, mdio_np); + of_node_put(mdio_np); + + return ret; +} + +static int mxl862xx_setup(struct dsa_switch *ds) +{ + struct mxl862xx_priv *priv = ds->priv; + int ret; + + ret = mxl862xx_reset(priv); + if (ret) + return ret; + + ret = mxl862xx_wait_ready(ds); + if (ret) + return ret; + + return mxl862xx_setup_mdio(ds); +} + +static int mxl862xx_port_state(struct dsa_switch *ds, int port, bool enable) +{ + struct mxl862xx_register_mod sdma = { + .addr = cpu_to_le16(MXL862XX_SDMA_PCTRLP(port)), + .data = cpu_to_le16(enable ? MXL862XX_SDMA_PCTRL_EN : 0), + .mask = cpu_to_le16(MXL862XX_SDMA_PCTRL_EN), + }; + struct mxl862xx_register_mod fdma = { + .addr = cpu_to_le16(MXL862XX_FDMA_PCTRLP(port)), + .data = cpu_to_le16(enable ? MXL862XX_FDMA_PCTRL_EN : 0), + .mask = cpu_to_le16(MXL862XX_FDMA_PCTRL_EN), + }; + int ret; + + ret = MXL862XX_API_WRITE(ds->priv, MXL862XX_COMMON_REGISTERMOD, sdma); + if (ret) + return ret; + + return MXL862XX_API_WRITE(ds->priv, MXL862XX_COMMON_REGISTERMOD, fdma); +} + +static int mxl862xx_port_enable(struct dsa_switch *ds, int port, + struct phy_device *phydev) +{ + return mxl862xx_port_state(ds, port, true); +} + +static void mxl862xx_port_disable(struct dsa_switch *ds, int port) +{ + if (mxl862xx_port_state(ds, port, false)) + dev_err(ds->dev, "failed to disable port %d\n", port); +} + +static void mxl862xx_port_fast_age(struct dsa_switch *ds, int port) +{ + struct mxl862xx_mac_table_clear param = { + .type = MXL862XX_MAC_CLEAR_PHY_PORT, + .port_id = port, + }; + + if (MXL862XX_API_WRITE(ds->priv, MXL862XX_MAC_TABLECLEARCOND, param)) + dev_err(ds->dev, "failed to clear fdb on port %d\n", port); +} + +static int mxl862xx_configure_ctp_port(struct dsa_switch *ds, int port, + u16 first_ctp_port_id, + u16 number_of_ctp_ports) +{ + struct mxl862xx_ctp_port_assignment ctp_assign = { + .logical_port_id = port, + .first_ctp_port_id = cpu_to_le16(first_ctp_port_id), + .number_of_ctp_port = cpu_to_le16(number_of_ctp_ports), + .mode = cpu_to_le32(MXL862XX_LOGICAL_PORT_ETHERNET), + }; + + return MXL862XX_API_WRITE(ds->priv, MXL862XX_CTP_PORTASSIGNMENTSET, + ctp_assign); +} + +static int mxl862xx_configure_sp_tag_proto(struct dsa_switch *ds, int port, + bool enable) +{ + struct mxl862xx_ss_sp_tag tag = { + .pid = port, + .mask = MXL862XX_SS_SP_TAG_MASK_RX | MXL862XX_SS_SP_TAG_MASK_TX, + .rx = enable ? MXL862XX_SS_SP_TAG_RX_TAG_NO_INSERT : + MXL862XX_SS_SP_TAG_RX_NO_TAG_INSERT, + .tx = enable ? MXL862XX_SS_SP_TAG_TX_TAG_NO_REMOVE : + MXL862XX_SS_SP_TAG_TX_TAG_REMOVE, + }; + + return MXL862XX_API_WRITE(ds->priv, MXL862XX_SS_SPTAG_SET, tag); +} + +static int mxl862xx_setup_cpu_bridge(struct dsa_switch *ds, int port) +{ + struct mxl862xx_bridge_port_config br_port_cfg = {}; + struct mxl862xx_priv *priv = ds->priv; + u16 bridge_port_map = 0; + struct dsa_port *dp; + + /* CPU port bridge setup */ + br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP | + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING | + MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING); + + br_port_cfg.bridge_port_id = cpu_to_le16(port); + br_port_cfg.src_mac_learning_disable = false; + br_port_cfg.vlan_src_mac_vid_enable = true; + br_port_cfg.vlan_dst_mac_vid_enable = true; + + /* include all assigned user ports in the CPU portmap */ + dsa_switch_for_each_user_port(dp, ds) { + /* it's safe to rely on cpu_dp being valid for user ports */ + if (dp->cpu_dp->index != port) + continue; + + bridge_port_map |= BIT(dp->index); + } + br_port_cfg.bridge_port_map[0] |= cpu_to_le16(bridge_port_map); + + return MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET, br_port_cfg); +} + +static int mxl862xx_add_single_port_bridge(struct dsa_switch *ds, int port) +{ + struct mxl862xx_bridge_port_config br_port_cfg = {}; + struct dsa_port *dp = dsa_to_port(ds, port); + struct mxl862xx_bridge_alloc br_alloc = {}; + int ret; + + ret = MXL862XX_API_READ(ds->priv, MXL862XX_BRIDGE_ALLOC, br_alloc); + if (ret) { + dev_err(ds->dev, "failed to allocate a bridge for port %d\n", port); + return ret; + } + + br_port_cfg.bridge_id = br_alloc.bridge_id; + br_port_cfg.bridge_port_id = cpu_to_le16(port); + br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID | + MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP | + MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING | + MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING); + br_port_cfg.src_mac_learning_disable = true; + br_port_cfg.vlan_src_mac_vid_enable = false; + br_port_cfg.vlan_dst_mac_vid_enable = false; + /* As this function is only called for user ports it is safe to rely on + * cpu_dp being valid + */ + br_port_cfg.bridge_port_map[0] = cpu_to_le16(BIT(dp->cpu_dp->index)); + + return MXL862XX_API_WRITE(ds->priv, MXL862XX_BRIDGEPORT_CONFIGSET, br_port_cfg); +} + +static int mxl862xx_port_setup(struct dsa_switch *ds, int port) +{ + struct dsa_port *dp = dsa_to_port(ds, port); + bool is_cpu_port = dsa_port_is_cpu(dp); + int ret; + + /* disable port and flush MAC entries */ + ret = mxl862xx_port_state(ds, port, false); + if (ret) + return ret; + + mxl862xx_port_fast_age(ds, port); + + /* skip setup for unused and DSA ports */ + if (dsa_port_is_unused(dp) || + dsa_port_is_dsa(dp)) + return 0; + + /* configure tag protocol */ + ret = mxl862xx_configure_sp_tag_proto(ds, port, is_cpu_port); + if (ret) + return ret; + + /* assign CTP port IDs */ + ret = mxl862xx_configure_ctp_port(ds, port, port, + is_cpu_port ? 32 - port : 1); + if (ret) + return ret; + + if (is_cpu_port) + /* assign user ports to CPU port bridge */ + return mxl862xx_setup_cpu_bridge(ds, port); + + /* setup single-port bridge for user ports */ + return mxl862xx_add_single_port_bridge(ds, port); +} + +static void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port, + struct phylink_config *config) +{ + config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | MAC_10 | + MAC_100 | MAC_1000 | MAC_2500FD; + + __set_bit(PHY_INTERFACE_MODE_INTERNAL, + config->supported_interfaces); +} + +static const struct dsa_switch_ops mxl862xx_switch_ops = { + .get_tag_protocol = mxl862xx_get_tag_protocol, + .setup = mxl862xx_setup, + .port_setup = mxl862xx_port_setup, + .phylink_get_caps = mxl862xx_phylink_get_caps, + .port_enable = mxl862xx_port_enable, + .port_disable = mxl862xx_port_disable, + .port_fast_age = mxl862xx_port_fast_age, +}; + +static void mxl862xx_phylink_mac_config(struct phylink_config *config, + unsigned int mode, + const struct phylink_link_state *state) +{ +} + +static void mxl862xx_phylink_mac_link_down(struct phylink_config *config, + unsigned int mode, + phy_interface_t interface) +{ +} + +static void mxl862xx_phylink_mac_link_up(struct phylink_config *config, + struct phy_device *phydev, + unsigned int mode, + phy_interface_t interface, + int speed, int duplex, + bool tx_pause, bool rx_pause) +{ +} + +static const struct phylink_mac_ops mxl862xx_phylink_mac_ops = { + .mac_config = mxl862xx_phylink_mac_config, + .mac_link_down = mxl862xx_phylink_mac_link_down, + .mac_link_up = mxl862xx_phylink_mac_link_up, +}; + +static int mxl862xx_probe(struct mdio_device *mdiodev) +{ + struct device *dev = &mdiodev->dev; + struct mxl862xx_priv *priv; + struct dsa_switch *ds; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->mdiodev = mdiodev; + + ds = devm_kzalloc(dev, sizeof(*ds), GFP_KERNEL); + if (!ds) + return -ENOMEM; + + priv->ds = ds; + ds->dev = dev; + ds->priv = priv; + ds->ops = &mxl862xx_switch_ops; + ds->phylink_mac_ops = &mxl862xx_phylink_mac_ops; + ds->num_ports = MXL862XX_MAX_PORTS; + + dev_set_drvdata(dev, ds); + + return dsa_register_switch(ds); +} + +static void mxl862xx_remove(struct mdio_device *mdiodev) +{ + struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev); + + if (!ds) + return; + + dsa_unregister_switch(ds); +} + +static void mxl862xx_shutdown(struct mdio_device *mdiodev) +{ + struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev); + + if (!ds) + return; + + dsa_switch_shutdown(ds); + + dev_set_drvdata(&mdiodev->dev, NULL); +} + +static const struct of_device_id mxl862xx_of_match[] = { + { .compatible = "maxlinear,mxl86282" }, + { .compatible = "maxlinear,mxl86252" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mxl862xx_of_match); + +static struct mdio_driver mxl862xx_driver = { + .probe = mxl862xx_probe, + .remove = mxl862xx_remove, + .shutdown = mxl862xx_shutdown, + .mdiodrv.driver = { + .name = "mxl862xx", + .of_match_table = mxl862xx_of_match, + }, +}; + +mdio_module_driver(mxl862xx_driver); + +MODULE_DESCRIPTION("Driver for MaxLinear MxL862xx switch family"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx/mxl862xx.h new file mode 100644 index 000000000000..bfeb436942d5 --- /dev/null +++ b/drivers/net/dsa/mxl862xx/mxl862xx.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef __MXL862XX_H +#define __MXL862XX_H + +#include <linux/mdio.h> +#include <net/dsa.h> + +#define MXL862XX_MAX_PORTS 17 + +struct mxl862xx_priv { + struct dsa_switch *ds; + struct mdio_device *mdiodev; +}; + +#endif /* __MXL862XX_H */ diff --git a/drivers/net/dsa/ocelot/felix.c b/drivers/net/dsa/ocelot/felix.c index 9e5ede932b42..5d34eb82e639 100644 --- a/drivers/net/dsa/ocelot/felix.c +++ b/drivers/net/dsa/ocelot/felix.c @@ -2003,11 +2003,11 @@ static int felix_cls_flower_stats(struct dsa_switch *ds, int port, } static int felix_port_policer_add(struct dsa_switch *ds, int port, - struct dsa_mall_policer_tc_entry *policer) + const struct flow_action_police *policer) { struct ocelot *ocelot = ds->priv; struct ocelot_policer pol = { - .rate = div_u64(policer->rate_bytes_per_sec, 1000) * 8, + .rate = div_u64(policer->rate_bytes_ps, 1000) * 8, .burst = policer->burst, }; diff --git a/drivers/net/dsa/sja1105/sja1105_main.c b/drivers/net/dsa/sja1105/sja1105_main.c index aa2145cf29a6..71a817c07a90 100644 --- a/drivers/net/dsa/sja1105/sja1105_main.c +++ b/drivers/net/dsa/sja1105/sja1105_main.c @@ -1260,7 +1260,9 @@ static int sja1105_set_port_speed(struct sja1105_private *priv, int port, int speed_mbps) { struct sja1105_mac_config_entry *mac; + struct device *dev = priv->ds->dev; u64 speed; + int rc; /* 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. @@ -1305,26 +1307,6 @@ static int sja1105_set_port_speed(struct sja1105_private *priv, int port, */ mac[port].speed = speed; - return 0; -} - -/* Write the MAC Configuration Table entry and, if necessary, the CGU settings, - * after a link speedchange for this port. - */ -static int sja1105_set_port_config(struct sja1105_private *priv, int port) -{ - struct sja1105_mac_config_entry *mac; - struct device *dev = priv->ds->dev; - int rc; - - /* 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. - */ - mac = priv->static_config.tables[BLK_IDX_MAC_CONFIG].entries; - /* Write to the dynamic reconfiguration tables */ rc = sja1105_dynamic_config_write(priv, BLK_IDX_MAC_CONFIG, port, &mac[port], true); @@ -1380,9 +1362,7 @@ static void sja1105_mac_link_up(struct phylink_config *config, struct sja1105_private *priv = dp->ds->priv; int port = dp->index; - if (!sja1105_set_port_speed(priv, port, speed)) - sja1105_set_port_config(priv, port); - + sja1105_set_port_speed(priv, port, speed); sja1105_inhibit_tx(priv, BIT(port), false); } @@ -2280,14 +2260,12 @@ int sja1105_static_config_reload(struct sja1105_private *priv, { struct ptp_system_timestamp ptp_sts_before; struct ptp_system_timestamp ptp_sts_after; - u16 bmcr[SJA1105_MAX_NUM_PORTS] = {0}; - u64 mac_speed[SJA1105_MAX_NUM_PORTS]; struct sja1105_mac_config_entry *mac; struct dsa_switch *ds = priv->ds; + struct dsa_port *dp; s64 t1, t2, t3, t4; - s64 t12, t34; - int rc, i; - s64 now; + s64 t12, t34, now; + int rc; mutex_lock(&priv->fdb_lock); mutex_lock(&priv->mgmt_lock); @@ -2299,13 +2277,9 @@ int sja1105_static_config_reload(struct sja1105_private *priv, * switch wants to see in the static config in order to allow us to * change it through the dynamic interface later. */ - for (i = 0; i < ds->num_ports; i++) { - mac_speed[i] = mac[i].speed; - mac[i].speed = priv->info->port_speed[SJA1105_SPEED_AUTO]; - - if (priv->pcs[i]) - bmcr[i] = mdiobus_c45_read(priv->mdio_pcs, i, - MDIO_MMD_VEND2, MDIO_CTRL1); + dsa_switch_for_each_available_port(dp, ds) { + phylink_replay_link_begin(dp->pl); + mac[dp->index].speed = priv->info->port_speed[SJA1105_SPEED_AUTO]; } /* No PTP operations can run right now */ @@ -2359,44 +2333,8 @@ int sja1105_static_config_reload(struct sja1105_private *priv, goto out; } - for (i = 0; i < ds->num_ports; i++) { - struct phylink_pcs *pcs = priv->pcs[i]; - unsigned int neg_mode; - - mac[i].speed = mac_speed[i]; - rc = sja1105_set_port_config(priv, i); - if (rc < 0) - goto out; - - if (!pcs) - continue; - - if (bmcr[i] & BMCR_ANENABLE) - neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED; - else - neg_mode = PHYLINK_PCS_NEG_OUTBAND; - - rc = pcs->ops->pcs_config(pcs, neg_mode, priv->phy_mode[i], - NULL, true); - if (rc < 0) - goto out; - - if (neg_mode == PHYLINK_PCS_NEG_OUTBAND) { - int speed = SPEED_UNKNOWN; - - if (priv->phy_mode[i] == PHY_INTERFACE_MODE_2500BASEX) - speed = SPEED_2500; - else if (bmcr[i] & BMCR_SPEED1000) - speed = SPEED_1000; - else if (bmcr[i] & BMCR_SPEED100) - speed = SPEED_100; - else - speed = SPEED_10; - - pcs->ops->pcs_link_up(pcs, neg_mode, priv->phy_mode[i], - speed, DUPLEX_FULL); - } - } + dsa_switch_for_each_available_port(dp, ds) + phylink_replay_link_end(dp->pl); rc = sja1105_reload_cbs(priv); if (rc < 0) @@ -2903,7 +2841,7 @@ static void sja1105_mirror_del(struct dsa_switch *ds, int port, } static int sja1105_port_policer_add(struct dsa_switch *ds, int port, - struct dsa_mall_policer_tc_entry *policer) + const struct flow_action_police *policer) { struct sja1105_l2_policing_entry *policing; struct sja1105_private *priv = ds->priv; @@ -2914,7 +2852,7 @@ static int sja1105_port_policer_add(struct dsa_switch *ds, int port, * the value of RATE bytes divided by 64, up to a maximum of SMAX * bytes. */ - policing[port].rate = div_u64(512 * policer->rate_bytes_per_sec, + policing[port].rate = div_u64(512 * policer->rate_bytes_ps, 1000000); policing[port].smax = policer->burst; diff --git a/drivers/net/dsa/yt921x.c b/drivers/net/dsa/yt921x.c index 7b8c1549a0fb..904613f4694a 100644 --- a/drivers/net/dsa/yt921x.c +++ b/drivers/net/dsa/yt921x.c @@ -8,6 +8,7 @@ * Copyright (c) 2025 David Yang */ +#include <linux/dcbnl.h> #include <linux/etherdevice.h> #include <linux/if_bridge.h> #include <linux/if_hsr.h> @@ -18,8 +19,11 @@ #include <linux/of.h> #include <linux/of_mdio.h> #include <linux/of_net.h> +#include <linux/sort.h> #include <net/dsa.h> +#include <net/dscp.h> +#include <net/ieee8021q.h> #include "yt921x.h" @@ -1118,6 +1122,188 @@ yt921x_dsa_port_mirror_add(struct dsa_switch *ds, int port, return res; } +static int yt921x_lag_hash(struct yt921x_priv *priv, u32 ctrl, bool unique_lag, + struct netlink_ext_ack *extack) +{ + u32 val; + int res; + + /* Hash Mode is global. Make sure the same Hash Mode is set to all the + * 2 possible lags. + * If we are the unique LAG we can set whatever hash mode we want. + * To change hash mode it's needed to remove all LAG and change the mode + * with the latest. + */ + if (unique_lag) { + res = yt921x_reg_write(priv, YT921X_LAG_HASH, ctrl); + if (res) + return res; + } else { + res = yt921x_reg_read(priv, YT921X_LAG_HASH, &val); + if (res) + return res; + + if (val != ctrl) { + NL_SET_ERR_MSG_MOD(extack, + "Mismatched Hash Mode across different lags is not supported"); + return -EOPNOTSUPP; + } + } + + return 0; +} + +static int yt921x_lag_set(struct yt921x_priv *priv, u8 index, u16 ports_mask) +{ + unsigned long targets_mask = ports_mask; + unsigned int cnt; + u32 ctrl; + int port; + int res; + + cnt = 0; + for_each_set_bit(port, &targets_mask, YT921X_PORT_NUM) { + ctrl = YT921X_LAG_MEMBER_PORT(port); + res = yt921x_reg_write(priv, YT921X_LAG_MEMBERnm(index, cnt), + ctrl); + if (res) + return res; + + cnt++; + } + + ctrl = YT921X_LAG_GROUP_PORTS(ports_mask) | + YT921X_LAG_GROUP_MEMBER_NUM(cnt); + return yt921x_reg_write(priv, YT921X_LAG_GROUPn(index), ctrl); +} + +static int +yt921x_dsa_port_lag_leave(struct dsa_switch *ds, int port, struct dsa_lag lag) +{ + struct yt921x_priv *priv = to_yt921x_priv(ds); + struct dsa_port *dp; + u32 ctrl; + int res; + + if (!lag.id) + return -EINVAL; + + ctrl = 0; + dsa_lag_foreach_port(dp, ds->dst, &lag) + ctrl |= BIT(dp->index); + + mutex_lock(&priv->reg_lock); + res = yt921x_lag_set(priv, lag.id - 1, ctrl); + mutex_unlock(&priv->reg_lock); + + return res; +} + +static int +yt921x_dsa_port_lag_check(struct dsa_switch *ds, struct dsa_lag lag, + struct netdev_lag_upper_info *info, + struct netlink_ext_ack *extack) +{ + unsigned int members; + struct dsa_port *dp; + + if (!lag.id) + return -EINVAL; + + members = 0; + dsa_lag_foreach_port(dp, ds->dst, &lag) + /* Includes the port joining the LAG */ + members++; + + if (members > YT921X_LAG_PORT_NUM) { + NL_SET_ERR_MSG_MOD(extack, + "Cannot offload more than 4 LAG ports"); + return -EOPNOTSUPP; + } + + if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH) { + NL_SET_ERR_MSG_MOD(extack, + "Can only offload LAG using hash TX type"); + return -EOPNOTSUPP; + } + + if (info->hash_type != NETDEV_LAG_HASH_L2 && + info->hash_type != NETDEV_LAG_HASH_L23 && + info->hash_type != NETDEV_LAG_HASH_L34) { + NL_SET_ERR_MSG_MOD(extack, + "Can only offload L2 or L2+L3 or L3+L4 TX hash"); + return -EOPNOTSUPP; + } + + return 0; +} + +static int +yt921x_dsa_port_lag_join(struct dsa_switch *ds, int port, struct dsa_lag lag, + struct netdev_lag_upper_info *info, + struct netlink_ext_ack *extack) +{ + struct yt921x_priv *priv = to_yt921x_priv(ds); + struct dsa_port *dp; + bool unique_lag; + unsigned int i; + u32 ctrl; + int res; + + res = yt921x_dsa_port_lag_check(ds, lag, info, extack); + if (res) + return res; + + ctrl = 0; + switch (info->hash_type) { + case NETDEV_LAG_HASH_L34: + ctrl |= YT921X_LAG_HASH_IP_DST; + ctrl |= YT921X_LAG_HASH_IP_SRC; + ctrl |= YT921X_LAG_HASH_IP_PROTO; + + ctrl |= YT921X_LAG_HASH_L4_DPORT; + ctrl |= YT921X_LAG_HASH_L4_SPORT; + break; + case NETDEV_LAG_HASH_L23: + ctrl |= YT921X_LAG_HASH_MAC_DA; + ctrl |= YT921X_LAG_HASH_MAC_SA; + + ctrl |= YT921X_LAG_HASH_IP_DST; + ctrl |= YT921X_LAG_HASH_IP_SRC; + ctrl |= YT921X_LAG_HASH_IP_PROTO; + break; + case NETDEV_LAG_HASH_L2: + ctrl |= YT921X_LAG_HASH_MAC_DA; + ctrl |= YT921X_LAG_HASH_MAC_SA; + break; + default: + return -EOPNOTSUPP; + } + + /* Check if we are the unique configured LAG */ + unique_lag = true; + dsa_lags_foreach_id(i, ds->dst) + if (i != lag.id && dsa_lag_by_id(ds->dst, i)) { + unique_lag = false; + break; + } + + mutex_lock(&priv->reg_lock); + do { + res = yt921x_lag_hash(priv, ctrl, unique_lag, extack); + if (res) + break; + + ctrl = 0; + dsa_lag_foreach_port(dp, ds->dst, &lag) + ctrl |= BIT(dp->index); + res = yt921x_lag_set(priv, lag.id - 1, ctrl); + } while (0); + mutex_unlock(&priv->reg_lock); + + return res; +} + static int yt921x_fdb_wait(struct yt921x_priv *priv, u32 *valp) { struct device *dev = to_device(priv); @@ -1588,6 +1774,21 @@ yt921x_dsa_port_mdb_add(struct dsa_switch *ds, int port, } static int +yt921x_vlan_aware_set(struct yt921x_priv *priv, int port, bool vlan_aware) +{ + u32 ctrl; + + /* Abuse SVLAN for PCP parsing without polluting the FDB - it just works + * despite YT921X_VLAN_CTRL_SVLAN_EN never being set + */ + if (!vlan_aware) + ctrl = YT921X_PORT_IGR_TPIDn_STAG(0); + else + ctrl = YT921X_PORT_IGR_TPIDn_CTAG(0); + return yt921x_reg_write(priv, YT921X_PORTn_IGR_TPID(port), ctrl); +} + +static int yt921x_port_set_pvid(struct yt921x_priv *priv, int port, u16 vid) { u32 mask; @@ -1636,14 +1837,7 @@ yt921x_vlan_filtering(struct yt921x_priv *priv, int port, bool vlan_filtering) if (res) return res; - /* Turn on / off VLAN awareness */ - mask = YT921X_PORT_IGR_TPIDn_CTAG_M; - if (!vlan_filtering) - ctrl = 0; - else - ctrl = YT921X_PORT_IGR_TPIDn_CTAG(0); - res = yt921x_reg_update_bits(priv, YT921X_PORTn_IGR_TPID(port), - mask, ctrl); + res = yt921x_vlan_aware_set(priv, port, vlan_filtering); if (res) return res; @@ -1840,8 +2034,7 @@ static int yt921x_userport_standalone(struct yt921x_priv *priv, int port) return res; /* Turn off VLAN awareness */ - mask = YT921X_PORT_IGR_TPIDn_CTAG_M; - res = yt921x_reg_clear_bits(priv, YT921X_PORTn_IGR_TPID(port), mask); + res = yt921x_vlan_aware_set(priv, port, false); if (res) return res; @@ -2210,6 +2403,122 @@ yt921x_dsa_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) port, res); } +static int __maybe_unused +yt921x_dsa_port_get_default_prio(struct dsa_switch *ds, int port) +{ + struct yt921x_priv *priv = to_yt921x_priv(ds); + u32 val; + int res; + + mutex_lock(&priv->reg_lock); + res = yt921x_reg_read(priv, YT921X_PORTn_QOS(port), &val); + mutex_unlock(&priv->reg_lock); + + if (res) + return res; + + return FIELD_GET(YT921X_PORT_QOS_PRIO_M, val); +} + +static int __maybe_unused +yt921x_dsa_port_set_default_prio(struct dsa_switch *ds, int port, u8 prio) +{ + struct yt921x_priv *priv = to_yt921x_priv(ds); + u32 mask; + u32 ctrl; + int res; + + if (prio >= YT921X_PRIO_NUM) + return -EINVAL; + + mutex_lock(&priv->reg_lock); + mask = YT921X_PORT_QOS_PRIO_M | YT921X_PORT_QOS_PRIO_EN; + ctrl = YT921X_PORT_QOS_PRIO(prio) | YT921X_PORT_QOS_PRIO_EN; + res = yt921x_reg_update_bits(priv, YT921X_PORTn_QOS(port), mask, ctrl); + mutex_unlock(&priv->reg_lock); + + return res; +} + +static int __maybe_unused appprios_cmp(const void *a, const void *b) +{ + return ((const u8 *)b)[1] - ((const u8 *)a)[1]; +} + +static int __maybe_unused +yt921x_dsa_port_get_apptrust(struct dsa_switch *ds, int port, u8 *sel, + int *nselp) +{ + struct yt921x_priv *priv = to_yt921x_priv(ds); + u8 appprios[2][2] = {}; + int nsel; + u32 val; + int res; + + mutex_lock(&priv->reg_lock); + res = yt921x_reg_read(priv, YT921X_PORTn_PRIO_ORD(port), &val); + mutex_unlock(&priv->reg_lock); + + if (res) + return res; + + appprios[0][0] = IEEE_8021QAZ_APP_SEL_DSCP; + appprios[0][1] = (val >> (3 * YT921X_APP_SEL_DSCP)) & 7; + appprios[1][0] = DCB_APP_SEL_PCP; + appprios[1][1] = (val >> (3 * YT921X_APP_SEL_CVLAN_PCP)) & 7; + sort(appprios, ARRAY_SIZE(appprios), sizeof(appprios[0]), appprios_cmp, + NULL); + + nsel = 0; + for (int i = 0; i < ARRAY_SIZE(appprios) && appprios[i][1]; i++) { + sel[nsel] = appprios[i][0]; + nsel++; + } + *nselp = nsel; + + return 0; +} + +static int __maybe_unused +yt921x_dsa_port_set_apptrust(struct dsa_switch *ds, int port, const u8 *sel, + int nsel) +{ + struct yt921x_priv *priv = to_yt921x_priv(ds); + struct device *dev = to_device(priv); + u32 ctrl; + int res; + + if (nsel > YT921X_APP_SEL_NUM) + return -EINVAL; + + ctrl = 0; + for (int i = 0; i < nsel; i++) { + switch (sel[i]) { + case IEEE_8021QAZ_APP_SEL_DSCP: + ctrl |= YT921X_PORT_PRIO_ORD_APPm(YT921X_APP_SEL_DSCP, + 7 - i); + break; + case DCB_APP_SEL_PCP: + ctrl |= YT921X_PORT_PRIO_ORD_APPm(YT921X_APP_SEL_CVLAN_PCP, + 7 - i); + ctrl |= YT921X_PORT_PRIO_ORD_APPm(YT921X_APP_SEL_SVLAN_PCP, + 7 - i); + break; + default: + dev_err(dev, + "Invalid apptrust selector (at %d-th). Supported: dscp, pcp\n", + i + 1); + return -EOPNOTSUPP; + } + } + + mutex_lock(&priv->reg_lock); + res = yt921x_reg_write(priv, YT921X_PORTn_PRIO_ORD(port), ctrl); + mutex_unlock(&priv->reg_lock); + + return res; +} + static int yt921x_port_down(struct yt921x_priv *priv, int port) { u32 mask; @@ -2534,6 +2843,13 @@ static int yt921x_port_setup(struct yt921x_priv *priv, int port) if (res) return res; + /* Clear prio order (even if DCB is not enabled) to avoid unsolicited + * priorities + */ + res = yt921x_reg_write(priv, YT921X_PORTn_PRIO_ORD(port), 0); + if (res) + return res; + if (dsa_is_cpu_port(ds, port)) { /* Egress of CPU port is supposed to be completely controlled * via tagging, so set to oneway isolated (drop all packets @@ -2577,6 +2893,66 @@ static int yt921x_dsa_port_setup(struct dsa_switch *ds, int port) return res; } +/* Not "port" - DSCP mapping is global */ +static int __maybe_unused +yt921x_dsa_port_get_dscp_prio(struct dsa_switch *ds, int port, u8 dscp) +{ + struct yt921x_priv *priv = to_yt921x_priv(ds); + u32 val; + int res; + + mutex_lock(&priv->reg_lock); + res = yt921x_reg_read(priv, YT921X_IPM_DSCPn(dscp), &val); + mutex_unlock(&priv->reg_lock); + + if (res) + return res; + + return FIELD_GET(YT921X_IPM_PRIO_M, val); +} + +static int __maybe_unused +yt921x_dsa_port_del_dscp_prio(struct dsa_switch *ds, int port, u8 dscp, u8 prio) +{ + struct yt921x_priv *priv = to_yt921x_priv(ds); + u32 val; + int res; + + mutex_lock(&priv->reg_lock); + /* During a "dcb app replace" command, the new app table entry will be + * added first, then the old one will be deleted. But the hardware only + * supports one QoS class per DSCP value (duh), so if we blindly delete + * the app table entry for this DSCP value, we end up deleting the + * entry with the new priority. Avoid that by checking whether user + * space wants to delete the priority which is currently configured, or + * something else which is no longer current. + */ + res = yt921x_reg_read(priv, YT921X_IPM_DSCPn(dscp), &val); + if (!res && FIELD_GET(YT921X_IPM_PRIO_M, val) == prio) + res = yt921x_reg_write(priv, YT921X_IPM_DSCPn(dscp), + YT921X_IPM_PRIO(IEEE8021Q_TT_BK)); + mutex_unlock(&priv->reg_lock); + + return res; +} + +static int __maybe_unused +yt921x_dsa_port_add_dscp_prio(struct dsa_switch *ds, int port, u8 dscp, u8 prio) +{ + struct yt921x_priv *priv = to_yt921x_priv(ds); + int res; + + if (prio >= YT921X_PRIO_NUM) + return -EINVAL; + + mutex_lock(&priv->reg_lock); + res = yt921x_reg_write(priv, YT921X_IPM_DSCPn(dscp), + YT921X_IPM_PRIO(prio)); + mutex_unlock(&priv->reg_lock); + + return res; +} + static int yt921x_edata_wait(struct yt921x_priv *priv, u32 *valp) { u32 val = YT921X_EDATA_DATA_IDLE; @@ -2716,7 +3092,7 @@ static int yt921x_chip_reset(struct yt921x_priv *priv) return 0; } -static int yt921x_chip_setup(struct yt921x_priv *priv) +static int yt921x_chip_setup_dsa(struct yt921x_priv *priv) { struct dsa_switch *ds = &priv->ds; unsigned long cpu_ports_mask; @@ -2734,16 +3110,6 @@ static int yt921x_chip_setup(struct yt921x_priv *priv) if (res) return res; - /* Enable and clear MIB */ - res = yt921x_reg_set_bits(priv, YT921X_FUNC, YT921X_FUNC_MIB); - if (res) - return res; - - ctrl = YT921X_MIB_CTRL_CLEAN | YT921X_MIB_CTRL_ALL_PORT; - res = yt921x_reg_write(priv, YT921X_MIB_CTRL, ctrl); - if (res) - return res; - /* Setup software switch */ ctrl = YT921X_CPU_COPY_TO_EXT_CPU; res = yt921x_reg_write(priv, YT921X_CPU_COPY, ctrl); @@ -2796,6 +3162,76 @@ static int yt921x_chip_setup(struct yt921x_priv *priv) if (res) return res; + return 0; +} + +static int __maybe_unused yt921x_chip_setup_qos(struct yt921x_priv *priv) +{ + u32 ctrl; + int res; + + /* DSCP to internal priorities */ + for (u8 dscp = 0; dscp < DSCP_MAX; dscp++) { + int prio = ietf_dscp_to_ieee8021q_tt(dscp); + + if (prio < 0) + return prio; + + res = yt921x_reg_write(priv, YT921X_IPM_DSCPn(dscp), + YT921X_IPM_PRIO(prio)); + if (res) + return res; + } + + /* 802.1Q QoS to internal priorities */ + for (u8 pcp = 0; pcp < 8; pcp++) + for (u8 dei = 0; dei < 2; dei++) { + ctrl = YT921X_IPM_PRIO(pcp); + if (dei) + /* "Red" almost means drop, so it's not that + * useful. Note that tc police does not support + * Three-Color very well + */ + ctrl |= YT921X_IPM_COLOR_YELLOW; + + for (u8 svlan = 0; svlan < 2; svlan++) { + u32 reg = YT921X_IPM_PCPn(svlan, dei, pcp); + + res = yt921x_reg_write(priv, reg, ctrl); + if (res) + return res; + } + } + + return 0; +} + +static int yt921x_chip_setup(struct yt921x_priv *priv) +{ + u32 ctrl; + int res; + + ctrl = YT921X_FUNC_MIB; + res = yt921x_reg_set_bits(priv, YT921X_FUNC, ctrl); + if (res) + return res; + + res = yt921x_chip_setup_dsa(priv); + if (res) + return res; + +#if IS_ENABLED(CONFIG_DCB) + res = yt921x_chip_setup_qos(priv); + if (res) + return res; +#endif + + /* Clear MIB */ + ctrl = YT921X_MIB_CTRL_CLEAN | YT921X_MIB_CTRL_ALL_PORT; + res = yt921x_reg_write(priv, YT921X_MIB_CTRL, ctrl); + if (res) + return res; + /* Miscellaneous */ res = yt921x_reg_set_bits(priv, YT921X_SENSOR, YT921X_SENSOR_TEMP); if (res) @@ -2881,6 +3317,9 @@ static const struct dsa_switch_ops yt921x_dsa_switch_ops = { /* mirror */ .port_mirror_del = yt921x_dsa_port_mirror_del, .port_mirror_add = yt921x_dsa_port_mirror_add, + /* lag */ + .port_lag_leave = yt921x_dsa_port_lag_leave, + .port_lag_join = yt921x_dsa_port_lag_join, /* fdb */ .port_fdb_dump = yt921x_dsa_port_fdb_dump, .port_fast_age = yt921x_dsa_port_fast_age, @@ -2902,10 +3341,23 @@ static const struct dsa_switch_ops yt921x_dsa_switch_ops = { .port_mst_state_set = yt921x_dsa_port_mst_state_set, .vlan_msti_set = yt921x_dsa_vlan_msti_set, .port_stp_state_set = yt921x_dsa_port_stp_state_set, +#if IS_ENABLED(CONFIG_DCB) + /* dcb */ + .port_get_default_prio = yt921x_dsa_port_get_default_prio, + .port_set_default_prio = yt921x_dsa_port_set_default_prio, + .port_get_apptrust = yt921x_dsa_port_get_apptrust, + .port_set_apptrust = yt921x_dsa_port_set_apptrust, +#endif /* port */ .get_tag_protocol = yt921x_dsa_get_tag_protocol, .phylink_get_caps = yt921x_dsa_phylink_get_caps, .port_setup = yt921x_dsa_port_setup, +#if IS_ENABLED(CONFIG_DCB) + /* dscp */ + .port_get_dscp_prio = yt921x_dsa_port_get_dscp_prio, + .port_del_dscp_prio = yt921x_dsa_port_del_dscp_prio, + .port_add_dscp_prio = yt921x_dsa_port_add_dscp_prio, +#endif /* chip */ .setup = yt921x_dsa_setup, }; @@ -2972,11 +3424,13 @@ static int yt921x_mdio_probe(struct mdio_device *mdiodev) ds = &priv->ds; ds->dev = dev; ds->assisted_learning_on_cpu_port = true; + ds->dscp_prio_mapping_is_global = true; ds->priv = priv; ds->ops = &yt921x_dsa_switch_ops; ds->ageing_time_min = 1 * 5000; ds->ageing_time_max = U16_MAX * 5000; ds->phylink_mac_ops = &yt921x_phylink_mac_ops; + ds->num_lag_ids = YT921X_LAG_NUM; ds->num_ports = YT921X_PORT_NUM; mdiodev_set_drvdata(mdiodev, priv); diff --git a/drivers/net/dsa/yt921x.h b/drivers/net/dsa/yt921x.h index 61bb0ab3b09a..3f129b8d403f 100644 --- a/drivers/net/dsa/yt921x.h +++ b/drivers/net/dsa/yt921x.h @@ -269,6 +269,38 @@ #define YT921X_TPID_EGRn(x) (0x100300 + 4 * (x)) /* [0, 3] */ #define YT921X_TPID_EGR_TPID_M GENMASK(15, 0) +#define YT921X_IPM_DSCPn(n) (0x180000 + 4 * (n)) /* Internal Priority Map */ +#define YT921X_IPM_PCPn(map, dei, pcp) (0x180100 + 4 * (16 * (map) + 8 * (dei) + (pcp))) +#define YT921X_IPM_PRIO_M GENMASK(4, 2) +#define YT921X_IPM_PRIO(x) FIELD_PREP(YT921X_IPM_PRIO_M, (x)) +#define YT921X_IPM_COLOR_M GENMASK(1, 0) +#define YT921X_IPM_COLOR(x) FIELD_PREP(YT921X_IPM_COLOR_M, (x)) +#define YT921X_IPM_COLOR_GREEN YT921X_IPM_COLOR(0) +#define YT921X_IPM_COLOR_YELLOW YT921X_IPM_COLOR(1) +#define YT921X_IPM_COLOR_RED YT921X_IPM_COLOR(2) +#define YT921X_PORTn_QOS(port) (0x180180 + 4 * (port)) +#define YT921X_PORT_QOS_CVLAN_PRIO_MAP_ID BIT(5) +#define YT921X_PORT_QOS_SVLAN_PRIO_MAP_ID BIT(4) +#define YT921X_PORT_QOS_PRIO_M GENMASK(3, 1) +#define YT921X_PORT_QOS_PRIO(x) FIELD_PREP(YT921X_PORT_QOS_PRIO_M, (x)) +#define YT921X_PORT_QOS_PRIO_EN BIT(0) +#define YT921X_PORTn_PRIO_ORD(port) (0x180200 + 4 * (port)) +#define YT921X_PORT_PRIO_ORD_APPm_M(m) GENMASK(3 * (m) + 2, 3 * (m)) +#define YT921X_PORT_PRIO_ORD_APPm(m, x) ((x) << (3 * (m))) /* 0: disabled, except PORT_QOS_PRIO */ + +enum yt921x_app_selector { + YT921X_APP_SEL_MAC_SA, + YT921X_APP_SEL_MAC_DA, + YT921X_APP_SEL_VID, + YT921X_APP_SEL_ACL, + YT921X_APP_SEL_DSCP, + YT921X_APP_SEL_CVLAN_PCP, + YT921X_APP_SEL_SVLAN_PCP, + /* The physical port, i.e. YT921X_PORT_QOS_PRIO */ + YT921X_APP_SEL_PORT, + YT921X_APP_SEL_NUM +}; + #define YT921X_VLAN_IGR_FILTER 0x180280 #define YT921X_VLAN_IGR_FILTER_PORTn_BYPASS_IGMP(port) BIT((port) + 11) #define YT921X_VLAN_IGR_FILTER_PORTn(port) BIT(port) @@ -337,7 +369,7 @@ #define YT921X_FDB_OUT0 0x1804b0 #define YT921X_FDB_IO0_ADDR_HI4_M GENMASK(31, 0) #define YT921X_FDB_OUT1 0x1804b4 -#define YT921X_FDB_IO1_EGR_INT_PRI_EN BIT(31) +#define YT921X_FDB_IO1_EGR_PRIO_EN BIT(31) #define YT921X_FDB_IO1_STATUS_M GENMASK(30, 28) #define YT921X_FDB_IO1_STATUS(x) FIELD_PREP(YT921X_FDB_IO1_STATUS_M, (x)) #define YT921X_FDB_IO1_STATUS_INVALID YT921X_FDB_IO1_STATUS(0) @@ -356,9 +388,9 @@ #define YT921X_FDB_IO2_EGR_PORTS(x) FIELD_PREP(YT921X_FDB_IO2_EGR_PORTS_M, (x)) #define YT921X_FDB_IO2_EGR_DROP BIT(17) #define YT921X_FDB_IO2_COPY_TO_CPU BIT(16) -#define YT921X_FDB_IO2_IGR_INT_PRI_EN BIT(15) -#define YT921X_FDB_IO2_INT_PRI_M GENMASK(14, 12) -#define YT921X_FDB_IO2_INT_PRI(x) FIELD_PREP(YT921X_FDB_IO2_INT_PRI_M, (x)) +#define YT921X_FDB_IO2_IGR_PRIO_EN BIT(15) +#define YT921X_FDB_IO2_PRIO_M GENMASK(14, 12) +#define YT921X_FDB_IO2_PRIO(x) FIELD_PREP(YT921X_FDB_IO2_PRIO_M, (x)) #define YT921X_FDB_IO2_NEW_VID_M GENMASK(11, 0) #define YT921X_FDB_IO2_NEW_VID(x) FIELD_PREP(YT921X_FDB_IO2_NEW_VID_M, (x)) #define YT921X_FILTER_UNK_UCAST 0x180508 @@ -370,6 +402,14 @@ #define YT921X_FILTER_PORTn(port) BIT(port) #define YT921X_VLAN_EGR_FILTER 0x180598 #define YT921X_VLAN_EGR_FILTER_PORTn(port) BIT(port) +#define YT921X_LAG_GROUPn(n) (0x1805a8 + 4 * (n)) +#define YT921X_LAG_GROUP_PORTS_M GENMASK(13, 3) +#define YT921X_LAG_GROUP_PORTS(x) FIELD_PREP(YT921X_LAG_GROUP_PORTS_M, (x)) +#define YT921X_LAG_GROUP_MEMBER_NUM_M GENMASK(2, 0) +#define YT921X_LAG_GROUP_MEMBER_NUM(x) FIELD_PREP(YT921X_LAG_GROUP_MEMBER_NUM_M, (x)) +#define YT921X_LAG_MEMBERnm(n, m) (0x1805b0 + 4 * (4 * (n) + (m))) +#define YT921X_LAG_MEMBER_PORT_M GENMASK(3, 0) +#define YT921X_LAG_MEMBER_PORT(x) FIELD_PREP(YT921X_LAG_MEMBER_PORT_M, (x)) #define YT921X_CPU_COPY 0x180690 #define YT921X_CPU_COPY_FORCE_INT_PORT BIT(2) #define YT921X_CPU_COPY_TO_INT_CPU BIT(1) @@ -398,8 +438,9 @@ #define YT921X_VLAN_CTRL_FID_M GENMASK_ULL(34, 23) #define YT921X_VLAN_CTRL_FID(x) FIELD_PREP(YT921X_VLAN_CTRL_FID_M, (x)) #define YT921X_VLAN_CTRL_LEARN_DIS BIT_ULL(22) -#define YT921X_VLAN_CTRL_INT_PRI_EN BIT_ULL(21) -#define YT921X_VLAN_CTRL_INT_PRI_M GENMASK_ULL(20, 18) +#define YT921X_VLAN_CTRL_PRIO_EN BIT_ULL(21) +#define YT921X_VLAN_CTRL_PRIO_M GENMASK_ULL(20, 18) +#define YT921X_VLAN_CTRL_PRIO(x) FIELD_PREP(YT921X_VLAN_CTRL_PRIO_M, (x)) #define YT921X_VLAN_CTRL_PORTS_M GENMASK_ULL(17, 7) #define YT921X_VLAN_CTRL_PORTS(x) FIELD_PREP(YT921X_VLAN_CTRL_PORTS_M, (x)) #define YT921X_VLAN_CTRL_PORTn(port) BIT_ULL((port) + 7) @@ -414,16 +455,25 @@ #define YT921X_PORT_IGR_TPIDn_STAG(x) BIT((x) + 4) #define YT921X_PORT_IGR_TPIDn_CTAG_M GENMASK(3, 0) #define YT921X_PORT_IGR_TPIDn_CTAG(x) BIT(x) +#define YT921X_LAG_HASH 0x210090 +#define YT921X_LAG_HASH_L4_SPORT BIT(7) +#define YT921X_LAG_HASH_L4_DPORT BIT(6) +#define YT921X_LAG_HASH_IP_PROTO BIT(5) +#define YT921X_LAG_HASH_IP_SRC BIT(4) +#define YT921X_LAG_HASH_IP_DST BIT(3) +#define YT921X_LAG_HASH_MAC_SA BIT(2) +#define YT921X_LAG_HASH_MAC_DA BIT(1) +#define YT921X_LAG_HASH_SRC_PORT BIT(0) #define YT921X_PORTn_VLAN_CTRL(port) (0x230010 + 4 * (port)) -#define YT921X_PORT_VLAN_CTRL_SVLAN_PRI_EN BIT(31) -#define YT921X_PORT_VLAN_CTRL_CVLAN_PRI_EN BIT(30) +#define YT921X_PORT_VLAN_CTRL_SVLAN_PRIO_EN BIT(31) +#define YT921X_PORT_VLAN_CTRL_CVLAN_PRIO_EN BIT(30) #define YT921X_PORT_VLAN_CTRL_SVID_M GENMASK(29, 18) #define YT921X_PORT_VLAN_CTRL_SVID(x) FIELD_PREP(YT921X_PORT_VLAN_CTRL_SVID_M, (x)) #define YT921X_PORT_VLAN_CTRL_CVID_M GENMASK(17, 6) #define YT921X_PORT_VLAN_CTRL_CVID(x) FIELD_PREP(YT921X_PORT_VLAN_CTRL_CVID_M, (x)) -#define YT921X_PORT_VLAN_CTRL_SVLAN_PRI_M GENMASK(5, 3) -#define YT921X_PORT_VLAN_CTRL_CVLAN_PRI_M GENMASK(2, 0) +#define YT921X_PORT_VLAN_CTRL_SVLAN_PRIO_M GENMASK(5, 3) +#define YT921X_PORT_VLAN_CTRL_CVLAN_PRIO_M GENMASK(2, 0) #define YT921X_PORTn_VLAN_CTRL1(port) (0x230080 + 4 * (port)) #define YT921X_PORT_VLAN_CTRL1_VLAN_RANGE_EN BIT(8) #define YT921X_PORT_VLAN_CTRL1_VLAN_RANGE_PROFILE_ID_M GENMASK(7, 4) @@ -458,6 +508,11 @@ enum yt921x_fdb_entry_status { #define YT921X_MSTI_NUM 16 +#define YT921X_LAG_NUM 2 +#define YT921X_LAG_PORT_NUM 4 + +#define YT921X_PRIO_NUM 8 + #define YT9215_MAJOR 0x9002 #define YT9218_MAJOR 0x9001 |
