summaryrefslogtreecommitdiff
path: root/drivers/net/dsa
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/dsa')
-rw-r--r--drivers/net/dsa/Kconfig3
-rw-r--r--drivers/net/dsa/Makefile1
-rw-r--r--drivers/net/dsa/dsa_loop.c8
-rw-r--r--drivers/net/dsa/lantiq/Kconfig5
-rw-r--r--drivers/net/dsa/lantiq/lantiq_gswip.c46
-rw-r--r--drivers/net/dsa/lantiq/lantiq_gswip.h7
-rw-r--r--drivers/net/dsa/lantiq/lantiq_gswip_common.c33
-rw-r--r--drivers/net/dsa/lantiq/mxl-gsw1xx.c255
-rw-r--r--drivers/net/dsa/lantiq/mxl-gsw1xx.h13
-rw-r--r--drivers/net/dsa/microchip/ksz_common.c15
-rw-r--r--drivers/net/dsa/microchip/ksz_common.h7
-rw-r--r--drivers/net/dsa/microchip/ksz_ptp.c63
-rw-r--r--drivers/net/dsa/microchip/ksz_ptp_reg.h16
-rw-r--r--drivers/net/dsa/mt7530-mdio.c4
-rw-r--r--drivers/net/dsa/mxl862xx/Kconfig12
-rw-r--r--drivers/net/dsa/mxl862xx/Makefile3
-rw-r--r--drivers/net/dsa/mxl862xx/mxl862xx-api.h675
-rw-r--r--drivers/net/dsa/mxl862xx/mxl862xx-cmd.h49
-rw-r--r--drivers/net/dsa/mxl862xx/mxl862xx-host.c245
-rw-r--r--drivers/net/dsa/mxl862xx/mxl862xx-host.h12
-rw-r--r--drivers/net/dsa/mxl862xx/mxl862xx.c476
-rw-r--r--drivers/net/dsa/mxl862xx/mxl862xx.h16
-rw-r--r--drivers/net/dsa/ocelot/felix.c4
-rw-r--r--drivers/net/dsa/sja1105/sja1105_main.c88
-rw-r--r--drivers/net/dsa/yt921x.c496
-rw-r--r--drivers/net/dsa/yt921x.h75
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