* [PATCH net-next v3 00/12] net: dsa: lantiq_gswip: Add support for MaxLinear GSW1xx switch family
@ 2025-10-26 23:43 Daniel Golle
2025-10-26 23:43 ` [PATCH net-next v3 01/12] net: dsa: lantiq_gswip: split into common and MMIO parts Daniel Golle
` (11 more replies)
0 siblings, 12 replies; 35+ messages in thread
From: Daniel Golle @ 2025-10-26 23:43 UTC (permalink / raw)
To: Hauke Mehrtens, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King,
netdev, devicetree, linux-kernel
Cc: Andreas Schirm, Lukas Stockmann, Alexander Sverdlin,
Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu,
Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng,
Livia M. Rosu, John Crispin
This patch series extends the existing lantiq_gswip DSA driver to support
the MaxLinear GSW1xx family of dedicated Ethernet switch ICs. These switches
are based on the same IP as the Lantiq/Intel GSWIP found in VR9 and xRX
MIPS router SoCs, but are connected via MDIO instead of memory-mapped I/O.
The series includes several improvements and refactoring to prepare for the
new hardware support.
The GSW1xx family includes several variants:
- GSW120: 4 ports, 2 PHYs, RGMII & SGMII/2500Base-X
- GSW125: 4 ports, 2 PHYs, RGMII & SGMII/2500Base-X, industrial temperature
- GSW140: 6 ports, 4 PHYs, RGMII & SGMII/2500Base-X
- GSW141: 6 ports, 4 PHYs, RGMII & SGMII
- GSW145: 6 ports, 4 PHYs, RGMII & SGMII/2500Base-X, industrial temperature
Key features implemented:
- MDIO-based register access using regmap
- Support for SGMII/1000Base-X/2500Base-X SerDes interfaces
- Configurable RGMII delays via device tree properties
- Configurable RMII clock direction
- Energy Efficient Ethernet (EEE) support
- enabling/disabling learning
Daniel Golle (12):
net: dsa: lantiq_gswip: split into common and MMIO parts
net: dsa: lantiq_gswip: support enable/disable learning
net: dsa: lantiq_gswip: support Energy Efficient Ethernet
net: dsa: lantiq_gswip: set link parameters also for CPU port
net: dsa: lantiq_gswip: define and use
GSWIP_TABLE_MAC_BRIDGE_VAL1_VALID
dt-bindings: net: dsa: lantiq,gswip: add support for MII delay
properties
net: dsa: lantiq_gswip: allow adjusting MII delays
dt-bindings: net: dsa: lantiq,gswip: add MaxLinear RMII refclk output
property
net: dsa: lantiq_gswip: add vendor property to setup MII refclk output
dt-bindings: net: dsa: lantiq,gswip: add support for MaxLinear GSW1xx
switches
net: dsa: add tagging driver for MaxLinear GSW1xx switch family
net: dsa: add driver for MaxLinear GSW1xx switch family
.../bindings/net/dsa/lantiq,gswip.yaml | 307 ++-
MAINTAINERS | 3 +-
drivers/net/dsa/lantiq/Kconfig | 18 +-
drivers/net/dsa/lantiq/Makefile | 2 +
drivers/net/dsa/lantiq/lantiq_gswip.c | 1617 +--------------
drivers/net/dsa/lantiq/lantiq_gswip.h | 20 +
drivers/net/dsa/lantiq/lantiq_gswip_common.c | 1748 +++++++++++++++++
drivers/net/dsa/lantiq/mxl-gsw1xx.c | 736 +++++++
drivers/net/dsa/lantiq/mxl-gsw1xx.h | 126 ++
drivers/net/dsa/lantiq/mxl-gsw1xx_pce.h | 154 ++
include/net/dsa.h | 2 +
net/dsa/Kconfig | 8 +
net/dsa/Makefile | 1 +
net/dsa/tag_mxl-gsw1xx.c | 141 ++
14 files changed, 3215 insertions(+), 1668 deletions(-)
create mode 100644 drivers/net/dsa/lantiq/lantiq_gswip_common.c
create mode 100644 drivers/net/dsa/lantiq/mxl-gsw1xx.c
create mode 100644 drivers/net/dsa/lantiq/mxl-gsw1xx.h
create mode 100644 drivers/net/dsa/lantiq/mxl-gsw1xx_pce.h
create mode 100644 net/dsa/tag_mxl-gsw1xx.c
--
2.51.1
^ permalink raw reply [flat|nested] 35+ messages in thread* [PATCH net-next v3 01/12] net: dsa: lantiq_gswip: split into common and MMIO parts 2025-10-26 23:43 [PATCH net-next v3 00/12] net: dsa: lantiq_gswip: Add support for MaxLinear GSW1xx switch family Daniel Golle @ 2025-10-26 23:43 ` Daniel Golle 2025-10-27 11:37 ` Sverdlin, Alexander 2025-10-27 22:22 ` Vladimir Oltean 2025-10-26 23:44 ` [PATCH net-next v3 02/12] net: dsa: lantiq_gswip: support enable/disable learning Daniel Golle ` (10 subsequent siblings) 11 siblings, 2 replies; 35+ messages in thread From: Daniel Golle @ 2025-10-26 23:43 UTC (permalink / raw) To: Hauke Mehrtens, Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel Cc: Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin Move all parts specific for the MMIO/SoC driver into a module of its own to prepare for supporting MDIO-connected switch ICs. Modify gswip_probe() functions by splitting it into a common function gswip_probe_common() which covers allocating, initializing and registering the DSA switch, while keeping transport-specific regmap initialization as well as PHY firmware loading in the new MMIO/SoC-specific gswip_probe() function. Signed-off-by: Daniel Golle <daniel@makrotopia.org> --- drivers/net/dsa/lantiq/Kconfig | 6 +- drivers/net/dsa/lantiq/Makefile | 1 + drivers/net/dsa/lantiq/lantiq_gswip.c | 1617 +---------------- drivers/net/dsa/lantiq/lantiq_gswip.h | 4 + drivers/net/dsa/lantiq/lantiq_gswip_common.c | 1622 ++++++++++++++++++ 5 files changed, 1658 insertions(+), 1592 deletions(-) create mode 100644 drivers/net/dsa/lantiq/lantiq_gswip_common.c diff --git a/drivers/net/dsa/lantiq/Kconfig b/drivers/net/dsa/lantiq/Kconfig index 3cfa16840cf5..78db82a47d09 100644 --- a/drivers/net/dsa/lantiq/Kconfig +++ b/drivers/net/dsa/lantiq/Kconfig @@ -1,8 +1,12 @@ +config NET_DSA_LANTIQ_COMMON + tristate + select REGMAP + config NET_DSA_LANTIQ_GSWIP tristate "Lantiq / Intel GSWIP" depends on HAS_IOMEM select NET_DSA_TAG_GSWIP - select REGMAP + select NET_DSA_LANTIQ_COMMON help This enables support for the Lantiq / Intel GSWIP 2.1 found in the xrx200 / VR9 SoC. diff --git a/drivers/net/dsa/lantiq/Makefile b/drivers/net/dsa/lantiq/Makefile index 849f85ebebd6..65ffa7bb71aa 100644 --- a/drivers/net/dsa/lantiq/Makefile +++ b/drivers/net/dsa/lantiq/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_NET_DSA_LANTIQ_GSWIP) += lantiq_gswip.o +obj-$(CONFIG_NET_DSA_LANTIQ_COMMON) += lantiq_gswip_common.o diff --git a/drivers/net/dsa/lantiq/lantiq_gswip.c b/drivers/net/dsa/lantiq/lantiq_gswip.c index 38f7f6352e8d..57dd063c0740 100644 --- a/drivers/net/dsa/lantiq/lantiq_gswip.c +++ b/drivers/net/dsa/lantiq/lantiq_gswip.c @@ -2,1226 +2,32 @@ /* * Lantiq / Intel GSWIP switch driver for VRX200, xRX300 and xRX330 SoCs * - * Copyright (C) 2010 Lantiq Deutschland - * Copyright (C) 2012 John Crispin <john@phrozen.org> - * Copyright (C) 2017 - 2019 Hauke Mehrtens <hauke@hauke-m.de> - * - * The VLAN and bridge model the GSWIP hardware uses does not directly - * matches the model DSA uses. - * - * The hardware has 64 possible table entries for bridges with one VLAN - * ID, one flow id and a list of ports for each bridge. All entries which - * match the same flow ID are combined in the mac learning table, they - * act as one global bridge. - * The hardware does not support VLAN filter on the port, but on the - * bridge, this driver converts the DSA model to the hardware. - * - * The CPU gets all the exception frames which do not match any forwarding - * rule and the CPU port is also added to all bridges. This makes it possible - * to handle all the special cases easily in software. - * At the initialization the driver allocates one bridge table entry for - * each switch port which is used when the port is used without an - * explicit bridge. This prevents the frames from being forwarded - * between all LAN ports by default. - */ - -#include "lantiq_gswip.h" -#include "lantiq_pce.h" - -#include <linux/delay.h> -#include <linux/etherdevice.h> -#include <linux/firmware.h> -#include <linux/if_bridge.h> -#include <linux/if_vlan.h> -#include <linux/iopoll.h> -#include <linux/mfd/syscon.h> -#include <linux/module.h> -#include <linux/of_mdio.h> -#include <linux/of_net.h> -#include <linux/of_platform.h> -#include <linux/phy.h> -#include <linux/phylink.h> -#include <dt-bindings/mips/lantiq_rcu_gphy.h> - -struct xway_gphy_match_data { - char *fe_firmware_name; - char *ge_firmware_name; -}; - -struct gswip_pce_table_entry { - u16 index; // PCE_TBL_ADDR.ADDR = pData->table_index - u16 table; // PCE_TBL_CTRL.ADDR = pData->table - u16 key[8]; - u16 val[5]; - u16 mask; - u8 gmap; - bool type; - bool valid; - bool key_mode; -}; - -struct gswip_rmon_cnt_desc { - unsigned int size; - unsigned int offset; - const char *name; -}; - -#define MIB_DESC(_size, _offset, _name) {.size = _size, .offset = _offset, .name = _name} - -static const struct gswip_rmon_cnt_desc gswip_rmon_cnt[] = { - /** Receive Packet Count (only packets that are accepted and not discarded). */ - MIB_DESC(1, 0x1F, "RxGoodPkts"), - MIB_DESC(1, 0x23, "RxUnicastPkts"), - MIB_DESC(1, 0x22, "RxMulticastPkts"), - MIB_DESC(1, 0x21, "RxFCSErrorPkts"), - MIB_DESC(1, 0x1D, "RxUnderSizeGoodPkts"), - MIB_DESC(1, 0x1E, "RxUnderSizeErrorPkts"), - MIB_DESC(1, 0x1B, "RxOversizeGoodPkts"), - MIB_DESC(1, 0x1C, "RxOversizeErrorPkts"), - MIB_DESC(1, 0x20, "RxGoodPausePkts"), - MIB_DESC(1, 0x1A, "RxAlignErrorPkts"), - MIB_DESC(1, 0x12, "Rx64BytePkts"), - MIB_DESC(1, 0x13, "Rx127BytePkts"), - MIB_DESC(1, 0x14, "Rx255BytePkts"), - MIB_DESC(1, 0x15, "Rx511BytePkts"), - MIB_DESC(1, 0x16, "Rx1023BytePkts"), - /** Receive Size 1024-1522 (or more, if configured) Packet Count. */ - MIB_DESC(1, 0x17, "RxMaxBytePkts"), - MIB_DESC(1, 0x18, "RxDroppedPkts"), - MIB_DESC(1, 0x19, "RxFilteredPkts"), - MIB_DESC(2, 0x24, "RxGoodBytes"), - MIB_DESC(2, 0x26, "RxBadBytes"), - MIB_DESC(1, 0x11, "TxAcmDroppedPkts"), - MIB_DESC(1, 0x0C, "TxGoodPkts"), - MIB_DESC(1, 0x06, "TxUnicastPkts"), - MIB_DESC(1, 0x07, "TxMulticastPkts"), - MIB_DESC(1, 0x00, "Tx64BytePkts"), - MIB_DESC(1, 0x01, "Tx127BytePkts"), - MIB_DESC(1, 0x02, "Tx255BytePkts"), - MIB_DESC(1, 0x03, "Tx511BytePkts"), - MIB_DESC(1, 0x04, "Tx1023BytePkts"), - /** Transmit Size 1024-1522 (or more, if configured) Packet Count. */ - MIB_DESC(1, 0x05, "TxMaxBytePkts"), - MIB_DESC(1, 0x08, "TxSingleCollCount"), - MIB_DESC(1, 0x09, "TxMultCollCount"), - MIB_DESC(1, 0x0A, "TxLateCollCount"), - MIB_DESC(1, 0x0B, "TxExcessCollCount"), - MIB_DESC(1, 0x0D, "TxPauseCount"), - MIB_DESC(1, 0x10, "TxDroppedPkts"), - MIB_DESC(2, 0x0E, "TxGoodBytes"), -}; - -static u32 gswip_switch_r_timeout(struct gswip_priv *priv, u32 offset, - u32 cleared) -{ - u32 val; - - return regmap_read_poll_timeout(priv->gswip, offset, val, - !(val & cleared), 20, 50000); -} - -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))) - return; - - reg_port = port + priv->hw_info->mii_port_reg_offset; - - regmap_write_bits(priv->mii, GSWIP_MII_CFGp(reg_port), mask, - set); -} - -static void gswip_mii_mask_pcdu(struct gswip_priv *priv, u32 mask, u32 set, - int port) -{ - int reg_port; - - /* MII_PCDU register only exists for MII ports */ - if (!(priv->hw_info->mii_ports & BIT(port))) - return; - - reg_port = port + priv->hw_info->mii_port_reg_offset; - - switch (reg_port) { - case 0: - regmap_write_bits(priv->mii, GSWIP_MII_PCDU0, mask, set); - break; - case 1: - regmap_write_bits(priv->mii, GSWIP_MII_PCDU1, mask, set); - break; - case 5: - regmap_write_bits(priv->mii, GSWIP_MII_PCDU5, mask, set); - break; - } -} - -static int gswip_mdio_poll(struct gswip_priv *priv) -{ - u32 ctrl; - - return regmap_read_poll_timeout(priv->mdio, GSWIP_MDIO_CTRL, ctrl, - !(ctrl & GSWIP_MDIO_CTRL_BUSY), 40, 4000); -} - -static int gswip_mdio_wr(struct mii_bus *bus, int addr, int reg, u16 val) -{ - struct gswip_priv *priv = bus->priv; - int err; - - err = gswip_mdio_poll(priv); - if (err) { - dev_err(&bus->dev, "waiting for MDIO bus busy timed out\n"); - return err; - } - - regmap_write(priv->mdio, GSWIP_MDIO_WRITE, val); - regmap_write(priv->mdio, GSWIP_MDIO_CTRL, - GSWIP_MDIO_CTRL_BUSY | GSWIP_MDIO_CTRL_WR | - ((addr & GSWIP_MDIO_CTRL_PHYAD_MASK) << GSWIP_MDIO_CTRL_PHYAD_SHIFT) | - (reg & GSWIP_MDIO_CTRL_REGAD_MASK)); - - return 0; -} - -static int gswip_mdio_rd(struct mii_bus *bus, int addr, int reg) -{ - struct gswip_priv *priv = bus->priv; - u32 val; - int err; - - err = gswip_mdio_poll(priv); - if (err) { - dev_err(&bus->dev, "waiting for MDIO bus busy timed out\n"); - return err; - } - - regmap_write(priv->mdio, GSWIP_MDIO_CTRL, - GSWIP_MDIO_CTRL_BUSY | GSWIP_MDIO_CTRL_RD | - ((addr & GSWIP_MDIO_CTRL_PHYAD_MASK) << GSWIP_MDIO_CTRL_PHYAD_SHIFT) | - (reg & GSWIP_MDIO_CTRL_REGAD_MASK)); - - err = gswip_mdio_poll(priv); - if (err) { - dev_err(&bus->dev, "waiting for MDIO bus busy timed out\n"); - return err; - } - - err = regmap_read(priv->mdio, GSWIP_MDIO_READ, &val); - if (err) - return err; - - return val; -} - -static int gswip_mdio(struct gswip_priv *priv) -{ - struct device_node *mdio_np, *switch_np = priv->dev->of_node; - struct device *dev = priv->dev; - struct mii_bus *bus; - int err = 0; - - mdio_np = of_get_compatible_child(switch_np, "lantiq,xrx200-mdio"); - if (!mdio_np) - mdio_np = of_get_child_by_name(switch_np, "mdio"); - - if (!of_device_is_available(mdio_np)) - goto out_put_node; - - bus = devm_mdiobus_alloc(dev); - if (!bus) { - err = -ENOMEM; - goto out_put_node; - } - - bus->priv = priv; - bus->read = gswip_mdio_rd; - bus->write = gswip_mdio_wr; - bus->name = "lantiq,xrx200-mdio"; - snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(priv->dev)); - bus->parent = priv->dev; - - err = devm_of_mdiobus_register(dev, bus, mdio_np); - -out_put_node: - of_node_put(mdio_np); - - return err; -} - -static int gswip_pce_table_entry_read(struct gswip_priv *priv, - struct gswip_pce_table_entry *tbl) -{ - int i; - int err; - u32 crtl; - u32 tmp; - u16 addr_mode = tbl->key_mode ? GSWIP_PCE_TBL_CTRL_OPMOD_KSRD : - GSWIP_PCE_TBL_CTRL_OPMOD_ADRD; - - mutex_lock(&priv->pce_table_lock); - - err = gswip_switch_r_timeout(priv, GSWIP_PCE_TBL_CTRL, - GSWIP_PCE_TBL_CTRL_BAS); - if (err) - goto out_unlock; - - regmap_write(priv->gswip, GSWIP_PCE_TBL_ADDR, tbl->index); - regmap_write_bits(priv->gswip, GSWIP_PCE_TBL_CTRL, - GSWIP_PCE_TBL_CTRL_ADDR_MASK | - GSWIP_PCE_TBL_CTRL_OPMOD_MASK | - GSWIP_PCE_TBL_CTRL_BAS, - tbl->table | addr_mode | GSWIP_PCE_TBL_CTRL_BAS); - - err = gswip_switch_r_timeout(priv, GSWIP_PCE_TBL_CTRL, - GSWIP_PCE_TBL_CTRL_BAS); - if (err) - goto out_unlock; - - for (i = 0; i < ARRAY_SIZE(tbl->key); i++) { - err = regmap_read(priv->gswip, GSWIP_PCE_TBL_KEY(i), &tmp); - if (err) - goto out_unlock; - tbl->key[i] = tmp; - } - for (i = 0; i < ARRAY_SIZE(tbl->val); i++) { - err = regmap_read(priv->gswip, GSWIP_PCE_TBL_VAL(i), &tmp); - if (err) - goto out_unlock; - tbl->val[i] = tmp; - } - - err = regmap_read(priv->gswip, GSWIP_PCE_TBL_MASK, &tmp); - if (err) - goto out_unlock; - - tbl->mask = tmp; - err = regmap_read(priv->gswip, GSWIP_PCE_TBL_CTRL, &crtl); - if (err) - goto out_unlock; - - tbl->type = !!(crtl & GSWIP_PCE_TBL_CTRL_TYPE); - tbl->valid = !!(crtl & GSWIP_PCE_TBL_CTRL_VLD); - tbl->gmap = (crtl & GSWIP_PCE_TBL_CTRL_GMAP_MASK) >> 7; - -out_unlock: - mutex_unlock(&priv->pce_table_lock); - - return err; -} - -static int gswip_pce_table_entry_write(struct gswip_priv *priv, - struct gswip_pce_table_entry *tbl) -{ - int i; - int err; - u32 crtl; - u16 addr_mode = tbl->key_mode ? GSWIP_PCE_TBL_CTRL_OPMOD_KSWR : - GSWIP_PCE_TBL_CTRL_OPMOD_ADWR; - - mutex_lock(&priv->pce_table_lock); - - err = gswip_switch_r_timeout(priv, GSWIP_PCE_TBL_CTRL, - GSWIP_PCE_TBL_CTRL_BAS); - if (err) { - mutex_unlock(&priv->pce_table_lock); - return err; - } - - regmap_write(priv->gswip, GSWIP_PCE_TBL_ADDR, tbl->index); - regmap_write_bits(priv->gswip, GSWIP_PCE_TBL_CTRL, - GSWIP_PCE_TBL_CTRL_ADDR_MASK | - GSWIP_PCE_TBL_CTRL_OPMOD_MASK, - tbl->table | addr_mode); - - for (i = 0; i < ARRAY_SIZE(tbl->key); i++) - regmap_write(priv->gswip, GSWIP_PCE_TBL_KEY(i), tbl->key[i]); - - for (i = 0; i < ARRAY_SIZE(tbl->val); i++) - regmap_write(priv->gswip, GSWIP_PCE_TBL_VAL(i), tbl->val[i]); - - regmap_write_bits(priv->gswip, GSWIP_PCE_TBL_CTRL, - GSWIP_PCE_TBL_CTRL_ADDR_MASK | - GSWIP_PCE_TBL_CTRL_OPMOD_MASK, - tbl->table | addr_mode); - - regmap_write(priv->gswip, GSWIP_PCE_TBL_MASK, tbl->mask); - - regmap_read(priv->gswip, GSWIP_PCE_TBL_CTRL, &crtl); - crtl &= ~(GSWIP_PCE_TBL_CTRL_TYPE | GSWIP_PCE_TBL_CTRL_VLD | - GSWIP_PCE_TBL_CTRL_GMAP_MASK); - if (tbl->type) - crtl |= GSWIP_PCE_TBL_CTRL_TYPE; - if (tbl->valid) - crtl |= GSWIP_PCE_TBL_CTRL_VLD; - crtl |= (tbl->gmap << 7) & GSWIP_PCE_TBL_CTRL_GMAP_MASK; - crtl |= GSWIP_PCE_TBL_CTRL_BAS; - regmap_write(priv->gswip, GSWIP_PCE_TBL_CTRL, crtl); - - err = gswip_switch_r_timeout(priv, GSWIP_PCE_TBL_CTRL, - GSWIP_PCE_TBL_CTRL_BAS); - - mutex_unlock(&priv->pce_table_lock); - - return err; -} - -/* Add the LAN port into a bridge with the CPU port by - * default. This prevents automatic forwarding of - * packages between the LAN ports when no explicit - * bridge is configured. - */ -static int gswip_add_single_port_br(struct gswip_priv *priv, int port, bool add) -{ - struct gswip_pce_table_entry vlan_active = {0,}; - struct gswip_pce_table_entry vlan_mapping = {0,}; - int err; - - vlan_active.index = port + 1; - vlan_active.table = GSWIP_TABLE_ACTIVE_VLAN; - vlan_active.key[0] = GSWIP_VLAN_UNAWARE_PVID; - vlan_active.val[0] = port + 1 /* fid */; - vlan_active.valid = add; - err = gswip_pce_table_entry_write(priv, &vlan_active); - if (err) { - dev_err(priv->dev, "failed to write active VLAN: %d\n", err); - return err; - } - - if (!add) - return 0; - - vlan_mapping.index = port + 1; - vlan_mapping.table = GSWIP_TABLE_VLAN_MAPPING; - vlan_mapping.val[0] = GSWIP_VLAN_UNAWARE_PVID; - vlan_mapping.val[1] = BIT(port) | dsa_cpu_ports(priv->ds); - vlan_mapping.val[2] = 0; - err = gswip_pce_table_entry_write(priv, &vlan_mapping); - if (err) { - dev_err(priv->dev, "failed to write VLAN mapping: %d\n", err); - return err; - } - - return 0; -} - -static int gswip_port_setup(struct dsa_switch *ds, int port) -{ - struct gswip_priv *priv = ds->priv; - int err; - - if (!dsa_is_cpu_port(ds, port)) { - err = gswip_add_single_port_br(priv, port, true); - if (err) - return err; - } - - return 0; -} - -static int gswip_port_enable(struct dsa_switch *ds, int port, - struct phy_device *phydev) -{ - struct gswip_priv *priv = ds->priv; - - if (!dsa_is_cpu_port(ds, port)) { - u32 mdio_phy = 0; - - if (phydev) - mdio_phy = phydev->mdio.addr & GSWIP_MDIO_PHY_ADDR_MASK; - - regmap_write_bits(priv->mdio, GSWIP_MDIO_PHYp(port), - GSWIP_MDIO_PHY_ADDR_MASK, - mdio_phy); - } - - /* RMON Counter Enable for port */ - regmap_write(priv->gswip, GSWIP_BM_PCFGp(port), GSWIP_BM_PCFG_CNTEN); - - /* enable port fetch/store dma & VLAN Modification */ - regmap_set_bits(priv->gswip, GSWIP_FDMA_PCTRLp(port), - GSWIP_FDMA_PCTRL_EN | GSWIP_FDMA_PCTRL_VLANMOD_BOTH); - regmap_set_bits(priv->gswip, GSWIP_SDMA_PCTRLp(port), - GSWIP_SDMA_PCTRL_EN); - - return 0; -} - -static void gswip_port_disable(struct dsa_switch *ds, int port) -{ - struct gswip_priv *priv = ds->priv; - - regmap_clear_bits(priv->gswip, GSWIP_FDMA_PCTRLp(port), - GSWIP_FDMA_PCTRL_EN); - regmap_clear_bits(priv->gswip, GSWIP_SDMA_PCTRLp(port), - GSWIP_SDMA_PCTRL_EN); -} - -static int gswip_pce_load_microcode(struct gswip_priv *priv) -{ - int i; - int err; - - regmap_write_bits(priv->gswip, GSWIP_PCE_TBL_CTRL, - GSWIP_PCE_TBL_CTRL_ADDR_MASK | - GSWIP_PCE_TBL_CTRL_OPMOD_MASK | - GSWIP_PCE_TBL_CTRL_OPMOD_ADWR, - GSWIP_PCE_TBL_CTRL_OPMOD_ADWR); - regmap_write(priv->gswip, GSWIP_PCE_TBL_MASK, 0); - - for (i = 0; i < priv->hw_info->pce_microcode_size; i++) { - regmap_write(priv->gswip, GSWIP_PCE_TBL_ADDR, i); - regmap_write(priv->gswip, GSWIP_PCE_TBL_VAL(0), - (*priv->hw_info->pce_microcode)[i].val_0); - regmap_write(priv->gswip, GSWIP_PCE_TBL_VAL(1), - (*priv->hw_info->pce_microcode)[i].val_1); - regmap_write(priv->gswip, GSWIP_PCE_TBL_VAL(2), - (*priv->hw_info->pce_microcode)[i].val_2); - regmap_write(priv->gswip, GSWIP_PCE_TBL_VAL(3), - (*priv->hw_info->pce_microcode)[i].val_3); - - /* start the table access: */ - regmap_set_bits(priv->gswip, GSWIP_PCE_TBL_CTRL, - GSWIP_PCE_TBL_CTRL_BAS); - err = gswip_switch_r_timeout(priv, GSWIP_PCE_TBL_CTRL, - GSWIP_PCE_TBL_CTRL_BAS); - if (err) - return err; - } - - /* tell the switch that the microcode is loaded */ - regmap_set_bits(priv->gswip, GSWIP_PCE_GCTRL_0, - GSWIP_PCE_GCTRL_0_MC_VALID); - - return 0; -} - -static void gswip_port_commit_pvid(struct gswip_priv *priv, int port) -{ - struct dsa_port *dp = dsa_to_port(priv->ds, port); - struct net_device *br = dsa_port_bridge_dev_get(dp); - u32 vinr; - int idx; - - if (!dsa_port_is_user(dp)) - return; - - if (br) { - u16 pvid = GSWIP_VLAN_UNAWARE_PVID; - - if (br_vlan_enabled(br)) - br_vlan_get_pvid(br, &pvid); - - /* VLAN-aware bridge ports with no PVID will use Active VLAN - * index 0. The expectation is that this drops all untagged and - * VID-0 tagged ingress traffic. - */ - idx = 0; - for (int i = priv->hw_info->max_ports; - i < ARRAY_SIZE(priv->vlans); i++) { - if (priv->vlans[i].bridge == br && - priv->vlans[i].vid == pvid) { - idx = i; - break; - } - } - } else { - /* The Active VLAN table index as configured by - * gswip_add_single_port_br() - */ - idx = port + 1; - } - - vinr = idx ? GSWIP_PCE_VCTRL_VINR_ALL : GSWIP_PCE_VCTRL_VINR_TAGGED; - regmap_write_bits(priv->gswip, GSWIP_PCE_VCTRL(port), - GSWIP_PCE_VCTRL_VINR, - FIELD_PREP(GSWIP_PCE_VCTRL_VINR, vinr)); - - /* Note that in GSWIP 2.2 VLAN mode the VID needs to be programmed - * directly instead of referencing the index in the Active VLAN Tablet. - * However, without the VLANMD bit (9) in PCE_GCTRL_1 (0x457) even - * GSWIP 2.2 and newer hardware maintain the GSWIP 2.1 behavior. - */ - regmap_write(priv->gswip, GSWIP_PCE_DEFPVID(port), idx); -} - -static int gswip_port_vlan_filtering(struct dsa_switch *ds, int port, - bool vlan_filtering, - struct netlink_ext_ack *extack) -{ - struct gswip_priv *priv = ds->priv; - - if (vlan_filtering) { - /* Use tag based VLAN */ - regmap_write_bits(priv->gswip, GSWIP_PCE_VCTRL(port), - GSWIP_PCE_VCTRL_VSR | - GSWIP_PCE_VCTRL_UVR | - GSWIP_PCE_VCTRL_VIMR | - GSWIP_PCE_VCTRL_VEMR | - GSWIP_PCE_VCTRL_VID0, - GSWIP_PCE_VCTRL_UVR | - GSWIP_PCE_VCTRL_VIMR | - GSWIP_PCE_VCTRL_VEMR | - GSWIP_PCE_VCTRL_VID0); - regmap_clear_bits(priv->gswip, GSWIP_PCE_PCTRL_0p(port), - GSWIP_PCE_PCTRL_0_TVM); - } else { - /* Use port based VLAN */ - regmap_write_bits(priv->gswip, GSWIP_PCE_VCTRL(port), - GSWIP_PCE_VCTRL_UVR | - GSWIP_PCE_VCTRL_VIMR | - GSWIP_PCE_VCTRL_VEMR | - GSWIP_PCE_VCTRL_VID0 | - GSWIP_PCE_VCTRL_VSR, - GSWIP_PCE_VCTRL_VSR); - regmap_set_bits(priv->gswip, GSWIP_PCE_PCTRL_0p(port), - GSWIP_PCE_PCTRL_0_TVM); - } - - gswip_port_commit_pvid(priv, port); - - return 0; -} - -static int gswip_setup(struct dsa_switch *ds) -{ - unsigned int cpu_ports = dsa_cpu_ports(ds); - struct gswip_priv *priv = ds->priv; - struct dsa_port *cpu_dp; - int err, i; - - regmap_write(priv->gswip, GSWIP_SWRES, GSWIP_SWRES_R0); - usleep_range(5000, 10000); - regmap_write(priv->gswip, GSWIP_SWRES, 0); - - /* disable port fetch/store dma on all ports */ - for (i = 0; i < priv->hw_info->max_ports; i++) { - gswip_port_disable(ds, i); - gswip_port_vlan_filtering(ds, i, false, NULL); - } - - /* enable Switch */ - regmap_set_bits(priv->mdio, GSWIP_MDIO_GLOB, GSWIP_MDIO_GLOB_ENABLE); - - err = gswip_pce_load_microcode(priv); - if (err) { - dev_err(priv->dev, "writing PCE microcode failed, %i\n", err); - return err; - } - - /* Default unknown Broadcast/Multicast/Unicast port maps */ - regmap_write(priv->gswip, GSWIP_PCE_PMAP1, cpu_ports); - regmap_write(priv->gswip, GSWIP_PCE_PMAP2, cpu_ports); - regmap_write(priv->gswip, GSWIP_PCE_PMAP3, cpu_ports); - - /* Deactivate MDIO PHY auto polling. Some PHYs as the AR8030 have an - * interoperability problem with this auto polling mechanism because - * their status registers think that the link is in a different state - * than it actually is. For the AR8030 it has the BMSR_ESTATEN bit set - * as well as ESTATUS_1000_TFULL and ESTATUS_1000_XFULL. This makes the - * auto polling state machine consider the link being negotiated with - * 1Gbit/s. Since the PHY itself is a Fast Ethernet RMII PHY this leads - * to the switch port being completely dead (RX and TX are both not - * working). - * Also with various other PHY / port combinations (PHY11G GPHY, PHY22F - * GPHY, external RGMII PEF7071/7072) any traffic would stop. Sometimes - * it would work fine for a few minutes to hours and then stop, on - * other device it would no traffic could be sent or received at all. - * Testing shows that when PHY auto polling is disabled these problems - * go away. - */ - regmap_write(priv->mdio, GSWIP_MDIO_MDC_CFG0, 0x0); - - /* Configure the MDIO Clock 2.5 MHz */ - regmap_write_bits(priv->mdio, GSWIP_MDIO_MDC_CFG1, 0xff, 0x09); - - /* bring up the mdio bus */ - err = gswip_mdio(priv); - if (err) { - dev_err(priv->dev, "mdio bus setup failed\n"); - return err; - } - - /* Disable the xMII interface and clear it's isolation bit */ - for (i = 0; i < priv->hw_info->max_ports; i++) - gswip_mii_mask_cfg(priv, - GSWIP_MII_CFG_EN | GSWIP_MII_CFG_ISOLATE, - 0, i); - - dsa_switch_for_each_cpu_port(cpu_dp, ds) { - /* enable special tag insertion on cpu port */ - regmap_set_bits(priv->gswip, GSWIP_FDMA_PCTRLp(cpu_dp->index), - GSWIP_FDMA_PCTRL_STEN); - - /* accept special tag in ingress direction */ - regmap_set_bits(priv->gswip, - GSWIP_PCE_PCTRL_0p(cpu_dp->index), - GSWIP_PCE_PCTRL_0_INGRESS); - } - - regmap_set_bits(priv->gswip, GSWIP_BM_QUEUE_GCTRL, - GSWIP_BM_QUEUE_GCTRL_GL_MOD); - - /* VLAN aware Switching */ - regmap_set_bits(priv->gswip, GSWIP_PCE_GCTRL_0, - GSWIP_PCE_GCTRL_0_VLAN); - - /* Flush MAC Table */ - regmap_set_bits(priv->gswip, GSWIP_PCE_GCTRL_0, - GSWIP_PCE_GCTRL_0_MTFL); - - err = gswip_switch_r_timeout(priv, GSWIP_PCE_GCTRL_0, - GSWIP_PCE_GCTRL_0_MTFL); - if (err) { - dev_err(priv->dev, "MAC flushing didn't finish\n"); - return err; - } - - ds->mtu_enforcement_ingress = true; - - return 0; -} - -static enum dsa_tag_protocol gswip_get_tag_protocol(struct dsa_switch *ds, - int port, - enum dsa_tag_protocol mp) -{ - struct gswip_priv *priv = ds->priv; - - return priv->hw_info->tag_protocol; -} - -static int gswip_vlan_active_create(struct gswip_priv *priv, - struct net_device *bridge, - int fid, u16 vid) -{ - struct gswip_pce_table_entry vlan_active = {0,}; - unsigned int max_ports = priv->hw_info->max_ports; - int idx = -1; - int err; - int i; - - /* Look for a free slot */ - for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) { - if (!priv->vlans[i].bridge) { - idx = i; - break; - } - } - - if (idx == -1) - return -ENOSPC; - - if (fid == -1) - fid = idx; - - vlan_active.index = idx; - vlan_active.table = GSWIP_TABLE_ACTIVE_VLAN; - vlan_active.key[0] = vid; - vlan_active.val[0] = fid; - vlan_active.valid = true; - - err = gswip_pce_table_entry_write(priv, &vlan_active); - if (err) { - dev_err(priv->dev, "failed to write active VLAN: %d\n", err); - return err; - } - - priv->vlans[idx].bridge = bridge; - priv->vlans[idx].vid = vid; - priv->vlans[idx].fid = fid; - - return idx; -} - -static int gswip_vlan_active_remove(struct gswip_priv *priv, int idx) -{ - struct gswip_pce_table_entry vlan_active = {0,}; - int err; - - vlan_active.index = idx; - vlan_active.table = GSWIP_TABLE_ACTIVE_VLAN; - vlan_active.valid = false; - err = gswip_pce_table_entry_write(priv, &vlan_active); - if (err) - dev_err(priv->dev, "failed to delete active VLAN: %d\n", err); - priv->vlans[idx].bridge = NULL; - - return err; -} - -static int gswip_vlan_add(struct gswip_priv *priv, struct net_device *bridge, - int port, u16 vid, bool untagged, bool pvid, - bool vlan_aware) -{ - struct gswip_pce_table_entry vlan_mapping = {0,}; - unsigned int max_ports = priv->hw_info->max_ports; - unsigned int cpu_ports = dsa_cpu_ports(priv->ds); - bool active_vlan_created = false; - int fid = -1, idx = -1; - int i, err; - - /* Check if there is already a page for this bridge */ - for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) { - if (priv->vlans[i].bridge == bridge) { - if (vlan_aware) { - if (fid != -1 && fid != priv->vlans[i].fid) - dev_err(priv->dev, "one bridge with multiple flow ids\n"); - fid = priv->vlans[i].fid; - } - if (priv->vlans[i].vid == vid) { - idx = i; - break; - } - } - } - - /* If this bridge is not programmed yet, add a Active VLAN table - * entry in a free slot and prepare the VLAN mapping table entry. - */ - if (idx == -1) { - idx = gswip_vlan_active_create(priv, bridge, fid, vid); - if (idx < 0) - return idx; - active_vlan_created = true; - - vlan_mapping.index = idx; - vlan_mapping.table = GSWIP_TABLE_VLAN_MAPPING; - } else { - /* Read the existing VLAN mapping entry from the switch */ - vlan_mapping.index = idx; - vlan_mapping.table = GSWIP_TABLE_VLAN_MAPPING; - err = gswip_pce_table_entry_read(priv, &vlan_mapping); - if (err) { - dev_err(priv->dev, "failed to read VLAN mapping: %d\n", - err); - return err; - } - } - - /* VLAN ID byte, maps to the VLAN ID of vlan active table */ - vlan_mapping.val[0] = vid; - /* Update the VLAN mapping entry and write it to the switch */ - vlan_mapping.val[1] |= cpu_ports; - vlan_mapping.val[1] |= BIT(port); - if (vlan_aware) - vlan_mapping.val[2] |= cpu_ports; - if (untagged) - vlan_mapping.val[2] &= ~BIT(port); - else - vlan_mapping.val[2] |= BIT(port); - err = gswip_pce_table_entry_write(priv, &vlan_mapping); - if (err) { - dev_err(priv->dev, "failed to write VLAN mapping: %d\n", err); - /* In case an Active VLAN was creaetd delete it again */ - if (active_vlan_created) - gswip_vlan_active_remove(priv, idx); - return err; - } - - gswip_port_commit_pvid(priv, port); - - return 0; -} - -static int gswip_vlan_remove(struct gswip_priv *priv, - struct net_device *bridge, int port, - u16 vid) -{ - struct gswip_pce_table_entry vlan_mapping = {0,}; - unsigned int max_ports = priv->hw_info->max_ports; - int idx = -1; - int i; - int err; - - /* Check if there is already a page for this bridge */ - for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) { - if (priv->vlans[i].bridge == bridge && - priv->vlans[i].vid == vid) { - idx = i; - break; - } - } - - if (idx == -1) { - dev_err(priv->dev, "Port %d cannot find VID %u of bridge %s\n", - port, vid, bridge ? bridge->name : "(null)"); - return -ENOENT; - } - - vlan_mapping.index = idx; - vlan_mapping.table = GSWIP_TABLE_VLAN_MAPPING; - err = gswip_pce_table_entry_read(priv, &vlan_mapping); - if (err) { - dev_err(priv->dev, "failed to read VLAN mapping: %d\n", err); - return err; - } - - vlan_mapping.val[1] &= ~BIT(port); - vlan_mapping.val[2] &= ~BIT(port); - err = gswip_pce_table_entry_write(priv, &vlan_mapping); - if (err) { - dev_err(priv->dev, "failed to write VLAN mapping: %d\n", err); - return err; - } - - /* In case all ports are removed from the bridge, remove the VLAN */ - if (!(vlan_mapping.val[1] & ~dsa_cpu_ports(priv->ds))) { - err = gswip_vlan_active_remove(priv, idx); - if (err) { - dev_err(priv->dev, "failed to write active VLAN: %d\n", - err); - return err; - } - } - - gswip_port_commit_pvid(priv, port); - - return 0; -} - -static int gswip_port_bridge_join(struct dsa_switch *ds, int port, - struct dsa_bridge bridge, - bool *tx_fwd_offload, - struct netlink_ext_ack *extack) -{ - struct net_device *br = bridge.dev; - struct gswip_priv *priv = ds->priv; - int err; - - /* Set up the VLAN for VLAN-unaware bridging for this port, and remove - * it from the "single-port bridge" through which it was operating as - * standalone. - */ - err = gswip_vlan_add(priv, br, port, GSWIP_VLAN_UNAWARE_PVID, - true, true, false); - if (err) - return err; - - return gswip_add_single_port_br(priv, port, false); -} - -static void gswip_port_bridge_leave(struct dsa_switch *ds, int port, - struct dsa_bridge bridge) -{ - struct net_device *br = bridge.dev; - struct gswip_priv *priv = ds->priv; - - /* Add the port back to the "single-port bridge", and remove it from - * the VLAN-unaware PVID created for this bridge. - */ - gswip_add_single_port_br(priv, port, true); - gswip_vlan_remove(priv, br, port, GSWIP_VLAN_UNAWARE_PVID); -} - -static int gswip_port_vlan_prepare(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_vlan *vlan, - struct netlink_ext_ack *extack) -{ - struct net_device *bridge = dsa_port_bridge_dev_get(dsa_to_port(ds, port)); - struct gswip_priv *priv = ds->priv; - unsigned int max_ports = priv->hw_info->max_ports; - int pos = max_ports; - int i, idx = -1; - - /* We only support VLAN filtering on bridges */ - if (!dsa_is_cpu_port(ds, port) && !bridge) - return -EOPNOTSUPP; - - /* Check if there is already a page for this VLAN */ - for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) { - if (priv->vlans[i].bridge == bridge && - priv->vlans[i].vid == vlan->vid) { - idx = i; - break; - } - } - - /* If this VLAN is not programmed yet, we have to reserve - * one entry in the VLAN table. Make sure we start at the - * next position round. - */ - if (idx == -1) { - /* Look for a free slot */ - for (; pos < ARRAY_SIZE(priv->vlans); pos++) { - if (!priv->vlans[pos].bridge) { - idx = pos; - pos++; - break; - } - } - - if (idx == -1) { - NL_SET_ERR_MSG_MOD(extack, "No slot in VLAN table"); - return -ENOSPC; - } - } - - return 0; -} - -static int gswip_port_vlan_add(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_vlan *vlan, - struct netlink_ext_ack *extack) -{ - struct net_device *bridge = dsa_port_bridge_dev_get(dsa_to_port(ds, port)); - struct gswip_priv *priv = ds->priv; - bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; - bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID; - int err; - - if (vlan->vid == GSWIP_VLAN_UNAWARE_PVID) - return 0; - - err = gswip_port_vlan_prepare(ds, port, vlan, extack); - if (err) - return err; - - /* We have to receive all packets on the CPU port and should not - * do any VLAN filtering here. This is also called with bridge - * NULL and then we do not know for which bridge to configure - * this. - */ - if (dsa_is_cpu_port(ds, port)) - return 0; - - return gswip_vlan_add(priv, bridge, port, vlan->vid, untagged, pvid, - true); -} - -static int gswip_port_vlan_del(struct dsa_switch *ds, int port, - const struct switchdev_obj_port_vlan *vlan) -{ - struct net_device *bridge = dsa_port_bridge_dev_get(dsa_to_port(ds, port)); - struct gswip_priv *priv = ds->priv; - - if (vlan->vid == GSWIP_VLAN_UNAWARE_PVID) - return 0; - - /* We have to receive all packets on the CPU port and should not - * do any VLAN filtering here. This is also called with bridge - * NULL and then we do not know for which bridge to configure - * this. - */ - if (dsa_is_cpu_port(ds, port)) - return 0; - - return gswip_vlan_remove(priv, bridge, port, vlan->vid); -} - -static void gswip_port_fast_age(struct dsa_switch *ds, int port) -{ - struct gswip_priv *priv = ds->priv; - struct gswip_pce_table_entry mac_bridge = {0,}; - int i; - int err; - - for (i = 0; i < 2048; i++) { - mac_bridge.table = GSWIP_TABLE_MAC_BRIDGE; - mac_bridge.index = i; - - err = gswip_pce_table_entry_read(priv, &mac_bridge); - if (err) { - dev_err(priv->dev, "failed to read mac bridge: %d\n", - err); - return; - } - - if (!mac_bridge.valid) - continue; - - if (mac_bridge.val[1] & GSWIP_TABLE_MAC_BRIDGE_VAL1_STATIC) - continue; - - if (port != FIELD_GET(GSWIP_TABLE_MAC_BRIDGE_VAL0_PORT, - mac_bridge.val[0])) - continue; - - mac_bridge.valid = false; - err = gswip_pce_table_entry_write(priv, &mac_bridge); - if (err) { - dev_err(priv->dev, "failed to write mac bridge: %d\n", - err); - return; - } - } -} - -static void gswip_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) -{ - struct gswip_priv *priv = ds->priv; - u32 stp_state; - - switch (state) { - case BR_STATE_DISABLED: - regmap_clear_bits(priv->gswip, GSWIP_SDMA_PCTRLp(port), - GSWIP_SDMA_PCTRL_EN); - return; - case BR_STATE_BLOCKING: - case BR_STATE_LISTENING: - stp_state = GSWIP_PCE_PCTRL_0_PSTATE_LISTEN; - break; - case BR_STATE_LEARNING: - stp_state = GSWIP_PCE_PCTRL_0_PSTATE_LEARNING; - break; - case BR_STATE_FORWARDING: - stp_state = GSWIP_PCE_PCTRL_0_PSTATE_FORWARDING; - break; - default: - dev_err(priv->dev, "invalid STP state: %d\n", state); - return; - } - - regmap_set_bits(priv->gswip, GSWIP_SDMA_PCTRLp(port), - GSWIP_SDMA_PCTRL_EN); - regmap_write_bits(priv->gswip, GSWIP_PCE_PCTRL_0p(port), - GSWIP_PCE_PCTRL_0_PSTATE_MASK, - stp_state); -} - -static int gswip_port_fdb(struct dsa_switch *ds, int port, - struct net_device *bridge, const unsigned char *addr, - u16 vid, bool add) -{ - struct gswip_priv *priv = ds->priv; - struct gswip_pce_table_entry mac_bridge = {0,}; - unsigned int max_ports = priv->hw_info->max_ports; - int fid = -1; - int i; - int err; - - for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) { - if (priv->vlans[i].bridge == bridge) { - fid = priv->vlans[i].fid; - break; - } - } - - if (fid == -1) { - dev_err(priv->dev, "no FID found for bridge %s\n", - bridge->name); - return -EINVAL; - } - - mac_bridge.table = GSWIP_TABLE_MAC_BRIDGE; - mac_bridge.key_mode = true; - mac_bridge.key[0] = addr[5] | (addr[4] << 8); - mac_bridge.key[1] = addr[3] | (addr[2] << 8); - mac_bridge.key[2] = addr[1] | (addr[0] << 8); - mac_bridge.key[3] = FIELD_PREP(GSWIP_TABLE_MAC_BRIDGE_KEY3_FID, fid); - mac_bridge.val[0] = add ? BIT(port) : 0; /* port map */ - mac_bridge.val[1] = GSWIP_TABLE_MAC_BRIDGE_VAL1_STATIC; - mac_bridge.valid = add; - - err = gswip_pce_table_entry_write(priv, &mac_bridge); - if (err) - dev_err(priv->dev, "failed to write mac bridge: %d\n", err); - - return err; -} - -static int gswip_port_fdb_add(struct dsa_switch *ds, int port, - const unsigned char *addr, u16 vid, - struct dsa_db db) -{ - if (db.type != DSA_DB_BRIDGE) - return -EOPNOTSUPP; - - return gswip_port_fdb(ds, port, db.bridge.dev, addr, vid, true); -} - -static int gswip_port_fdb_del(struct dsa_switch *ds, int port, - const unsigned char *addr, u16 vid, - struct dsa_db db) -{ - if (db.type != DSA_DB_BRIDGE) - return -EOPNOTSUPP; - - return gswip_port_fdb(ds, port, db.bridge.dev, addr, vid, false); -} - -static int gswip_port_fdb_dump(struct dsa_switch *ds, int port, - dsa_fdb_dump_cb_t *cb, void *data) -{ - struct gswip_priv *priv = ds->priv; - struct gswip_pce_table_entry mac_bridge = {0,}; - unsigned char addr[ETH_ALEN]; - int i; - int err; - - for (i = 0; i < 2048; i++) { - mac_bridge.table = GSWIP_TABLE_MAC_BRIDGE; - mac_bridge.index = i; - - err = gswip_pce_table_entry_read(priv, &mac_bridge); - if (err) { - dev_err(priv->dev, - "failed to read mac bridge entry %d: %d\n", - i, err); - return err; - } - - if (!mac_bridge.valid) - continue; - - addr[5] = mac_bridge.key[0] & 0xff; - addr[4] = (mac_bridge.key[0] >> 8) & 0xff; - addr[3] = mac_bridge.key[1] & 0xff; - addr[2] = (mac_bridge.key[1] >> 8) & 0xff; - addr[1] = mac_bridge.key[2] & 0xff; - addr[0] = (mac_bridge.key[2] >> 8) & 0xff; - if (mac_bridge.val[1] & GSWIP_TABLE_MAC_BRIDGE_VAL1_STATIC) { - if (mac_bridge.val[0] & BIT(port)) { - err = cb(addr, 0, true, data); - if (err) - return err; - } - } else { - if (port == FIELD_GET(GSWIP_TABLE_MAC_BRIDGE_VAL0_PORT, - mac_bridge.val[0])) { - err = cb(addr, 0, false, data); - if (err) - return err; - } - } - } - return 0; -} - -static int gswip_port_max_mtu(struct dsa_switch *ds, int port) -{ - /* Includes 8 bytes for special header. */ - return GSWIP_MAX_PACKET_LENGTH - VLAN_ETH_HLEN - ETH_FCS_LEN; -} + * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org> + * Copyright (C) 2017 - 2019 Hauke Mehrtens <hauke@hauke-m.de> + * Copyright (C) 2012 John Crispin <john@phrozen.org> + * Copyright (C) 2010 Lantiq Deutschland + */ -static int gswip_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu) -{ - struct gswip_priv *priv = ds->priv; +#include "lantiq_gswip.h" +#include "lantiq_pce.h" - /* CPU port always has maximum mtu of user ports, so use it to set - * switch frame size, including 8 byte special header. - */ - if (dsa_is_cpu_port(ds, port)) { - new_mtu += 8; - regmap_write(priv->gswip, GSWIP_MAC_FLEN, - VLAN_ETH_HLEN + new_mtu + ETH_FCS_LEN); - } +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <dt-bindings/mips/lantiq_rcu_gphy.h> - /* Enable MLEN for ports with non-standard MTUs, including the special - * header on the CPU port added above. - */ - if (new_mtu != ETH_DATA_LEN) - regmap_set_bits(priv->gswip, GSWIP_MAC_CTRL_2p(port), - GSWIP_MAC_CTRL_2_MLEN); - else - regmap_clear_bits(priv->gswip, GSWIP_MAC_CTRL_2p(port), - GSWIP_MAC_CTRL_2_MLEN); +#include <net/dsa.h> - return 0; -} +struct xway_gphy_match_data { + char *fe_firmware_name; + char *ge_firmware_name; +}; static void gswip_xrx200_phylink_get_caps(struct dsa_switch *ds, int port, struct phylink_config *config) @@ -1291,327 +97,6 @@ static void gswip_xrx300_phylink_get_caps(struct dsa_switch *ds, int port, MAC_10 | MAC_100 | MAC_1000; } -static void gswip_phylink_get_caps(struct dsa_switch *ds, int port, - struct phylink_config *config) -{ - struct gswip_priv *priv = ds->priv; - - priv->hw_info->phylink_get_caps(ds, port, config); -} - -static void gswip_port_set_link(struct gswip_priv *priv, int port, bool link) -{ - u32 mdio_phy; - - if (link) - mdio_phy = GSWIP_MDIO_PHY_LINK_UP; - else - mdio_phy = GSWIP_MDIO_PHY_LINK_DOWN; - - regmap_write_bits(priv->mdio, GSWIP_MDIO_PHYp(port), - GSWIP_MDIO_PHY_LINK_MASK, mdio_phy); -} - -static void gswip_port_set_speed(struct gswip_priv *priv, int port, int speed, - phy_interface_t interface) -{ - u32 mdio_phy = 0, mii_cfg = 0, mac_ctrl_0 = 0; - - switch (speed) { - case SPEED_10: - mdio_phy = GSWIP_MDIO_PHY_SPEED_M10; - - if (interface == PHY_INTERFACE_MODE_RMII) - mii_cfg = GSWIP_MII_CFG_RATE_M50; - else - mii_cfg = GSWIP_MII_CFG_RATE_M2P5; - - mac_ctrl_0 = GSWIP_MAC_CTRL_0_GMII_MII; - break; - - case SPEED_100: - mdio_phy = GSWIP_MDIO_PHY_SPEED_M100; - - if (interface == PHY_INTERFACE_MODE_RMII) - mii_cfg = GSWIP_MII_CFG_RATE_M50; - else - mii_cfg = GSWIP_MII_CFG_RATE_M25; - - mac_ctrl_0 = GSWIP_MAC_CTRL_0_GMII_MII; - break; - - case SPEED_1000: - mdio_phy = GSWIP_MDIO_PHY_SPEED_G1; - - mii_cfg = GSWIP_MII_CFG_RATE_M125; - - mac_ctrl_0 = GSWIP_MAC_CTRL_0_GMII_RGMII; - break; - } - - regmap_write_bits(priv->mdio, GSWIP_MDIO_PHYp(port), - GSWIP_MDIO_PHY_SPEED_MASK, mdio_phy); - gswip_mii_mask_cfg(priv, GSWIP_MII_CFG_RATE_MASK, mii_cfg, port); - regmap_write_bits(priv->gswip, GSWIP_MAC_CTRL_0p(port), - GSWIP_MAC_CTRL_0_GMII_MASK, mac_ctrl_0); -} - -static void gswip_port_set_duplex(struct gswip_priv *priv, int port, int duplex) -{ - u32 mac_ctrl_0, mdio_phy; - - if (duplex == DUPLEX_FULL) { - mac_ctrl_0 = GSWIP_MAC_CTRL_0_FDUP_EN; - mdio_phy = GSWIP_MDIO_PHY_FDUP_EN; - } else { - mac_ctrl_0 = GSWIP_MAC_CTRL_0_FDUP_DIS; - mdio_phy = GSWIP_MDIO_PHY_FDUP_DIS; - } - - regmap_write_bits(priv->gswip, GSWIP_MAC_CTRL_0p(port), - GSWIP_MAC_CTRL_0_FDUP_MASK, mac_ctrl_0); - regmap_write_bits(priv->mdio, GSWIP_MDIO_PHYp(port), - GSWIP_MDIO_PHY_FDUP_MASK, mdio_phy); -} - -static void gswip_port_set_pause(struct gswip_priv *priv, int port, - bool tx_pause, bool rx_pause) -{ - u32 mac_ctrl_0, mdio_phy; - - if (tx_pause && rx_pause) { - mac_ctrl_0 = GSWIP_MAC_CTRL_0_FCON_RXTX; - mdio_phy = GSWIP_MDIO_PHY_FCONTX_EN | - GSWIP_MDIO_PHY_FCONRX_EN; - } else if (tx_pause) { - mac_ctrl_0 = GSWIP_MAC_CTRL_0_FCON_TX; - mdio_phy = GSWIP_MDIO_PHY_FCONTX_EN | - GSWIP_MDIO_PHY_FCONRX_DIS; - } else if (rx_pause) { - mac_ctrl_0 = GSWIP_MAC_CTRL_0_FCON_RX; - mdio_phy = GSWIP_MDIO_PHY_FCONTX_DIS | - GSWIP_MDIO_PHY_FCONRX_EN; - } else { - mac_ctrl_0 = GSWIP_MAC_CTRL_0_FCON_NONE; - mdio_phy = GSWIP_MDIO_PHY_FCONTX_DIS | - GSWIP_MDIO_PHY_FCONRX_DIS; - } - - regmap_write_bits(priv->gswip, GSWIP_MAC_CTRL_0p(port), - GSWIP_MAC_CTRL_0_FCON_MASK, mac_ctrl_0); - regmap_write_bits(priv->mdio, GSWIP_MDIO_PHYp(port), - GSWIP_MDIO_PHY_FCONTX_MASK | GSWIP_MDIO_PHY_FCONRX_MASK, - mdio_phy); -} - -static void gswip_phylink_mac_config(struct phylink_config *config, - unsigned int mode, - const struct phylink_link_state *state) -{ - struct dsa_port *dp = dsa_phylink_to_port(config); - struct gswip_priv *priv = dp->ds->priv; - int port = dp->index; - u32 miicfg = 0; - - miicfg |= GSWIP_MII_CFG_LDCLKDIS; - - switch (state->interface) { - case PHY_INTERFACE_MODE_SGMII: - case PHY_INTERFACE_MODE_1000BASEX: - case PHY_INTERFACE_MODE_2500BASEX: - return; - case PHY_INTERFACE_MODE_MII: - case PHY_INTERFACE_MODE_INTERNAL: - miicfg |= GSWIP_MII_CFG_MODE_MIIM; - break; - case PHY_INTERFACE_MODE_REVMII: - miicfg |= GSWIP_MII_CFG_MODE_MIIP; - break; - case PHY_INTERFACE_MODE_RMII: - miicfg |= GSWIP_MII_CFG_MODE_RMIIM; - break; - case PHY_INTERFACE_MODE_RGMII: - case PHY_INTERFACE_MODE_RGMII_ID: - case PHY_INTERFACE_MODE_RGMII_RXID: - case PHY_INTERFACE_MODE_RGMII_TXID: - miicfg |= GSWIP_MII_CFG_MODE_RGMII; - break; - case PHY_INTERFACE_MODE_GMII: - miicfg |= GSWIP_MII_CFG_MODE_GMII; - break; - default: - dev_err(dp->ds->dev, - "Unsupported interface: %d\n", state->interface); - return; - } - - gswip_mii_mask_cfg(priv, - GSWIP_MII_CFG_MODE_MASK | GSWIP_MII_CFG_RMII_CLK | - GSWIP_MII_CFG_RGMII_IBS | GSWIP_MII_CFG_LDCLKDIS, - miicfg, port); - - switch (state->interface) { - case PHY_INTERFACE_MODE_RGMII_ID: - gswip_mii_mask_pcdu(priv, GSWIP_MII_PCDU_TXDLY_MASK | - GSWIP_MII_PCDU_RXDLY_MASK, 0, port); - break; - case PHY_INTERFACE_MODE_RGMII_RXID: - gswip_mii_mask_pcdu(priv, GSWIP_MII_PCDU_RXDLY_MASK, 0, port); - break; - case PHY_INTERFACE_MODE_RGMII_TXID: - gswip_mii_mask_pcdu(priv, GSWIP_MII_PCDU_TXDLY_MASK, 0, port); - break; - default: - break; - } -} - -static void gswip_phylink_mac_link_down(struct phylink_config *config, - unsigned int mode, - phy_interface_t interface) -{ - struct dsa_port *dp = dsa_phylink_to_port(config); - struct gswip_priv *priv = dp->ds->priv; - int port = dp->index; - - gswip_mii_mask_cfg(priv, GSWIP_MII_CFG_EN, 0, port); - - if (!dsa_port_is_cpu(dp)) - gswip_port_set_link(priv, port, false); -} - -static void gswip_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) -{ - struct dsa_port *dp = dsa_phylink_to_port(config); - struct gswip_priv *priv = dp->ds->priv; - int port = dp->index; - - if (!dsa_port_is_cpu(dp)) { - gswip_port_set_link(priv, port, true); - gswip_port_set_speed(priv, port, speed, interface); - gswip_port_set_duplex(priv, port, duplex); - gswip_port_set_pause(priv, port, tx_pause, rx_pause); - } - - gswip_mii_mask_cfg(priv, GSWIP_MII_CFG_EN, GSWIP_MII_CFG_EN, port); -} - -static void gswip_get_strings(struct dsa_switch *ds, int port, u32 stringset, - uint8_t *data) -{ - int i; - - if (stringset != ETH_SS_STATS) - return; - - for (i = 0; i < ARRAY_SIZE(gswip_rmon_cnt); i++) - ethtool_puts(&data, gswip_rmon_cnt[i].name); -} - -static u32 gswip_bcm_ram_entry_read(struct gswip_priv *priv, u32 table, - u32 index) -{ - u32 result, val; - int err; - - regmap_write(priv->gswip, GSWIP_BM_RAM_ADDR, index); - regmap_write_bits(priv->gswip, GSWIP_BM_RAM_CTRL, - GSWIP_BM_RAM_CTRL_ADDR_MASK | GSWIP_BM_RAM_CTRL_OPMOD | - GSWIP_BM_RAM_CTRL_BAS, - table | GSWIP_BM_RAM_CTRL_BAS); - - err = gswip_switch_r_timeout(priv, GSWIP_BM_RAM_CTRL, - GSWIP_BM_RAM_CTRL_BAS); - if (err) { - dev_err(priv->dev, "timeout while reading table: %u, index: %u\n", - table, index); - return 0; - } - - regmap_read(priv->gswip, GSWIP_BM_RAM_VAL(0), &result); - regmap_read(priv->gswip, GSWIP_BM_RAM_VAL(1), &val); - result |= val << 16; - - return result; -} - -static void gswip_get_ethtool_stats(struct dsa_switch *ds, int port, - uint64_t *data) -{ - struct gswip_priv *priv = ds->priv; - const struct gswip_rmon_cnt_desc *rmon_cnt; - int i; - u64 high; - - for (i = 0; i < ARRAY_SIZE(gswip_rmon_cnt); i++) { - rmon_cnt = &gswip_rmon_cnt[i]; - - data[i] = gswip_bcm_ram_entry_read(priv, port, - rmon_cnt->offset); - if (rmon_cnt->size == 2) { - high = gswip_bcm_ram_entry_read(priv, port, - rmon_cnt->offset + 1); - data[i] |= high << 32; - } - } -} - -static int gswip_get_sset_count(struct dsa_switch *ds, int port, int sset) -{ - if (sset != ETH_SS_STATS) - return 0; - - return ARRAY_SIZE(gswip_rmon_cnt); -} - -static struct phylink_pcs *gswip_phylink_mac_select_pcs(struct phylink_config *config, - phy_interface_t interface) -{ - struct dsa_port *dp = dsa_phylink_to_port(config); - struct gswip_priv *priv = dp->ds->priv; - - if (priv->hw_info->mac_select_pcs) - return priv->hw_info->mac_select_pcs(config, interface); - - return NULL; -} - -static const struct phylink_mac_ops gswip_phylink_mac_ops = { - .mac_config = gswip_phylink_mac_config, - .mac_link_down = gswip_phylink_mac_link_down, - .mac_link_up = gswip_phylink_mac_link_up, - .mac_select_pcs = gswip_phylink_mac_select_pcs, -}; - -static const struct dsa_switch_ops gswip_switch_ops = { - .get_tag_protocol = gswip_get_tag_protocol, - .setup = gswip_setup, - .port_setup = gswip_port_setup, - .port_enable = gswip_port_enable, - .port_disable = gswip_port_disable, - .port_bridge_join = gswip_port_bridge_join, - .port_bridge_leave = gswip_port_bridge_leave, - .port_fast_age = gswip_port_fast_age, - .port_vlan_filtering = gswip_port_vlan_filtering, - .port_vlan_add = gswip_port_vlan_add, - .port_vlan_del = gswip_port_vlan_del, - .port_stp_state_set = gswip_port_stp_state_set, - .port_fdb_add = gswip_port_fdb_add, - .port_fdb_del = gswip_port_fdb_del, - .port_fdb_dump = gswip_port_fdb_dump, - .port_change_mtu = gswip_port_change_mtu, - .port_max_mtu = gswip_port_max_mtu, - .phylink_get_caps = gswip_phylink_get_caps, - .get_strings = gswip_get_strings, - .get_ethtool_stats = gswip_get_ethtool_stats, - .get_sset_count = gswip_get_sset_count, -}; - static const struct xway_gphy_match_data xrx200a1x_gphy_data = { .fe_firmware_name = "lantiq/xrx200_phy22f_a14.bin", .ge_firmware_name = "lantiq/xrx200_phy11g_a14.bin", @@ -1832,30 +317,6 @@ static int gswip_gphy_fw_list(struct gswip_priv *priv, return err; } -static int gswip_validate_cpu_port(struct dsa_switch *ds) -{ - struct gswip_priv *priv = ds->priv; - struct dsa_port *cpu_dp; - int cpu_port = -1; - - dsa_switch_for_each_cpu_port(cpu_dp, ds) { - if (cpu_port != -1) - return dev_err_probe(ds->dev, -EINVAL, - "only a single CPU port is supported\n"); - - cpu_port = cpu_dp->index; - } - - if (cpu_port == -1) - return dev_err_probe(ds->dev, -EINVAL, "no CPU port defined\n"); - - if (BIT(cpu_port) & ~priv->hw_info->allowed_cpu_ports) - return dev_err_probe(ds->dev, -EINVAL, - "unsupported CPU port defined\n"); - - return 0; -} - static const struct regmap_config sw_regmap_config = { .name = "switch", .reg_bits = 32, @@ -1929,24 +390,9 @@ static int gswip_probe(struct platform_device *pdev) if (!priv->ds) return -ENOMEM; - priv->ds->dev = dev; - priv->ds->num_ports = priv->hw_info->max_ports; - priv->ds->priv = priv; - priv->ds->ops = &gswip_switch_ops; - priv->ds->phylink_mac_ops = &gswip_phylink_mac_ops; priv->dev = dev; - mutex_init(&priv->pce_table_lock); - regmap_read(priv->gswip, GSWIP_VERSION, &version); - /* The hardware has the 'major/minor' version bytes in the wrong order - * preventing numerical comparisons. Construct a 16-bit unsigned integer - * having the REV field as most significant byte and the MOD field as - * least significant byte. This is effectively swapping the two bytes of - * the version variable, but other than using swab16 it doesn't affect - * the source variable. - */ - priv->version = GSWIP_VERSION_REV(version) << 8 | - GSWIP_VERSION_MOD(version); + regmap_read(priv->gswip, GSWIP_VERSION, &version); np = dev->of_node; switch (version) { @@ -1976,25 +422,14 @@ static int gswip_probe(struct platform_device *pdev) "gphy fw probe failed\n"); } - err = dsa_register_switch(priv->ds); - if (err) { - dev_err_probe(dev, err, "dsa switch registration failed\n"); - goto gphy_fw_remove; - } - - err = gswip_validate_cpu_port(priv->ds); + err = gswip_probe_common(priv, version); if (err) - goto disable_switch; + goto gphy_fw_remove; platform_set_drvdata(pdev, priv); - dev_info(dev, "probed GSWIP version %lx mod %lx\n", - GSWIP_VERSION_REV(version), GSWIP_VERSION_MOD(version)); return 0; -disable_switch: - regmap_clear_bits(priv->mdio, GSWIP_MDIO_GLOB, GSWIP_MDIO_GLOB_ENABLE); - dsa_unregister_switch(priv->ds); gphy_fw_remove: for (i = 0; i < priv->num_gphy_fw; i++) gswip_gphy_fw_remove(priv, &priv->gphy_fw[i]); @@ -2010,7 +445,7 @@ static void gswip_remove(struct platform_device *pdev) return; /* disable the switch */ - regmap_clear_bits(priv->mdio, GSWIP_MDIO_GLOB, GSWIP_MDIO_GLOB_ENABLE); + gswip_disable_switch(priv); dsa_unregister_switch(priv->ds); diff --git a/drivers/net/dsa/lantiq/lantiq_gswip.h b/drivers/net/dsa/lantiq/lantiq_gswip.h index 24d759e06e15..d86290db19b4 100644 --- a/drivers/net/dsa/lantiq/lantiq_gswip.h +++ b/drivers/net/dsa/lantiq/lantiq_gswip.h @@ -278,4 +278,8 @@ struct gswip_priv { u16 version; }; +void gswip_disable_switch(struct gswip_priv *priv); + +int gswip_probe_common(struct gswip_priv *priv, u32 version); + #endif /* __LANTIQ_GSWIP_H */ diff --git a/drivers/net/dsa/lantiq/lantiq_gswip_common.c b/drivers/net/dsa/lantiq/lantiq_gswip_common.c new file mode 100644 index 000000000000..a0e361622acb --- /dev/null +++ b/drivers/net/dsa/lantiq/lantiq_gswip_common.c @@ -0,0 +1,1622 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Lantiq / Intel / MaxLinear GSWIP common function library + * + * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org> + * Copyright (C) 2023 - 2024 MaxLinear Inc. + * Copyright (C) 2022 Snap One, LLC. All rights reserved. + * Copyright (C) 2017 - 2019 Hauke Mehrtens <hauke@hauke-m.de> + * Copyright (C) 2012 John Crispin <john@phrozen.org> + * Copyright (C) 2010 Lantiq Deutschland + * + * The VLAN and bridge model the GSWIP hardware uses does not directly + * matches the model DSA uses. + * + * The hardware has 64 possible table entries for bridges with one VLAN + * ID, one flow id and a list of ports for each bridge. All entries which + * match the same flow ID are combined in the mac learning table, they + * act as one global bridge. + * The hardware does not support VLAN filter on the port, but on the + * bridge, this driver converts the DSA model to the hardware. + * + * The CPU gets all the exception frames which do not match any forwarding + * rule and the CPU port is also added to all bridges. This makes it possible + * to handle all the special cases easily in software. + * At the initialization the driver allocates one bridge table entry for + * each switch port which is used when the port is used without an + * explicit bridge. This prevents the frames from being forwarded + * between all LAN ports by default. + */ + +#include "lantiq_gswip.h" + +#include <linux/delay.h> +#include <linux/etherdevice.h> +#include <linux/if_bridge.h> +#include <linux/if_vlan.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/of_mdio.h> +#include <linux/of_net.h> +#include <linux/phy.h> +#include <linux/phylink.h> +#include <linux/regmap.h> +#include <net/dsa.h> + +struct gswip_pce_table_entry { + u16 index; // PCE_TBL_ADDR.ADDR = pData->table_index + u16 table; // PCE_TBL_CTRL.ADDR = pData->table + u16 key[8]; + u16 val[5]; + u16 mask; + u8 gmap; + bool type; + bool valid; + bool key_mode; +}; + +struct gswip_rmon_cnt_desc { + unsigned int size; + unsigned int offset; + const char *name; +}; + +#define MIB_DESC(_size, _offset, _name) {.size = _size, .offset = _offset, .name = _name} + +static const struct gswip_rmon_cnt_desc gswip_rmon_cnt[] = { + /** Receive Packet Count (only packets that are accepted and not discarded). */ + MIB_DESC(1, 0x1F, "RxGoodPkts"), + MIB_DESC(1, 0x23, "RxUnicastPkts"), + MIB_DESC(1, 0x22, "RxMulticastPkts"), + MIB_DESC(1, 0x21, "RxFCSErrorPkts"), + MIB_DESC(1, 0x1D, "RxUnderSizeGoodPkts"), + MIB_DESC(1, 0x1E, "RxUnderSizeErrorPkts"), + MIB_DESC(1, 0x1B, "RxOversizeGoodPkts"), + MIB_DESC(1, 0x1C, "RxOversizeErrorPkts"), + MIB_DESC(1, 0x20, "RxGoodPausePkts"), + MIB_DESC(1, 0x1A, "RxAlignErrorPkts"), + MIB_DESC(1, 0x12, "Rx64BytePkts"), + MIB_DESC(1, 0x13, "Rx127BytePkts"), + MIB_DESC(1, 0x14, "Rx255BytePkts"), + MIB_DESC(1, 0x15, "Rx511BytePkts"), + MIB_DESC(1, 0x16, "Rx1023BytePkts"), + /** Receive Size 1024-1522 (or more, if configured) Packet Count. */ + MIB_DESC(1, 0x17, "RxMaxBytePkts"), + MIB_DESC(1, 0x18, "RxDroppedPkts"), + MIB_DESC(1, 0x19, "RxFilteredPkts"), + MIB_DESC(2, 0x24, "RxGoodBytes"), + MIB_DESC(2, 0x26, "RxBadBytes"), + MIB_DESC(1, 0x11, "TxAcmDroppedPkts"), + MIB_DESC(1, 0x0C, "TxGoodPkts"), + MIB_DESC(1, 0x06, "TxUnicastPkts"), + MIB_DESC(1, 0x07, "TxMulticastPkts"), + MIB_DESC(1, 0x00, "Tx64BytePkts"), + MIB_DESC(1, 0x01, "Tx127BytePkts"), + MIB_DESC(1, 0x02, "Tx255BytePkts"), + MIB_DESC(1, 0x03, "Tx511BytePkts"), + MIB_DESC(1, 0x04, "Tx1023BytePkts"), + /** Transmit Size 1024-1522 (or more, if configured) Packet Count. */ + MIB_DESC(1, 0x05, "TxMaxBytePkts"), + MIB_DESC(1, 0x08, "TxSingleCollCount"), + MIB_DESC(1, 0x09, "TxMultCollCount"), + MIB_DESC(1, 0x0A, "TxLateCollCount"), + MIB_DESC(1, 0x0B, "TxExcessCollCount"), + MIB_DESC(1, 0x0D, "TxPauseCount"), + MIB_DESC(1, 0x10, "TxDroppedPkts"), + MIB_DESC(2, 0x0E, "TxGoodBytes"), +}; + +static u32 gswip_switch_r_timeout(struct gswip_priv *priv, u32 offset, + u32 cleared) +{ + u32 val; + + return regmap_read_poll_timeout(priv->gswip, offset, val, + !(val & cleared), 20, 50000); +} + +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))) + return; + + reg_port = port + priv->hw_info->mii_port_reg_offset; + + regmap_write_bits(priv->mii, GSWIP_MII_CFGp(reg_port), mask, + set); +} + +static void gswip_mii_mask_pcdu(struct gswip_priv *priv, u32 mask, u32 set, + int port) +{ + int reg_port; + + /* MII_PCDU register only exists for MII ports */ + if (!(priv->hw_info->mii_ports & BIT(port))) + return; + + reg_port = port + priv->hw_info->mii_port_reg_offset; + + switch (reg_port) { + case 0: + regmap_write_bits(priv->mii, GSWIP_MII_PCDU0, mask, set); + break; + case 1: + regmap_write_bits(priv->mii, GSWIP_MII_PCDU1, mask, set); + break; + case 5: + regmap_write_bits(priv->mii, GSWIP_MII_PCDU5, mask, set); + break; + } +} + +static int gswip_mdio_poll(struct gswip_priv *priv) +{ + u32 ctrl; + + return regmap_read_poll_timeout(priv->mdio, GSWIP_MDIO_CTRL, ctrl, + !(ctrl & GSWIP_MDIO_CTRL_BUSY), 40, 4000); +} + +static int gswip_mdio_wr(struct mii_bus *bus, int addr, int reg, u16 val) +{ + struct gswip_priv *priv = bus->priv; + int err; + + err = gswip_mdio_poll(priv); + if (err) { + dev_err(&bus->dev, "waiting for MDIO bus busy timed out\n"); + return err; + } + + regmap_write(priv->mdio, GSWIP_MDIO_WRITE, val); + regmap_write(priv->mdio, GSWIP_MDIO_CTRL, + GSWIP_MDIO_CTRL_BUSY | GSWIP_MDIO_CTRL_WR | + ((addr & GSWIP_MDIO_CTRL_PHYAD_MASK) << GSWIP_MDIO_CTRL_PHYAD_SHIFT) | + (reg & GSWIP_MDIO_CTRL_REGAD_MASK)); + + return 0; +} + +static int gswip_mdio_rd(struct mii_bus *bus, int addr, int reg) +{ + struct gswip_priv *priv = bus->priv; + u32 val; + int err; + + err = gswip_mdio_poll(priv); + if (err) { + dev_err(&bus->dev, "waiting for MDIO bus busy timed out\n"); + return err; + } + + regmap_write(priv->mdio, GSWIP_MDIO_CTRL, + GSWIP_MDIO_CTRL_BUSY | GSWIP_MDIO_CTRL_RD | + ((addr & GSWIP_MDIO_CTRL_PHYAD_MASK) << GSWIP_MDIO_CTRL_PHYAD_SHIFT) | + (reg & GSWIP_MDIO_CTRL_REGAD_MASK)); + + err = gswip_mdio_poll(priv); + if (err) { + dev_err(&bus->dev, "waiting for MDIO bus busy timed out\n"); + return err; + } + + err = regmap_read(priv->mdio, GSWIP_MDIO_READ, &val); + if (err) + return err; + + return val; +} + +static int gswip_mdio(struct gswip_priv *priv) +{ + struct device_node *mdio_np, *switch_np = priv->dev->of_node; + struct device *dev = priv->dev; + struct mii_bus *bus; + int err = 0; + + mdio_np = of_get_compatible_child(switch_np, "lantiq,xrx200-mdio"); + if (!mdio_np) + mdio_np = of_get_child_by_name(switch_np, "mdio"); + + if (!of_device_is_available(mdio_np)) + goto out_put_node; + + bus = devm_mdiobus_alloc(dev); + if (!bus) { + err = -ENOMEM; + goto out_put_node; + } + + bus->priv = priv; + bus->read = gswip_mdio_rd; + bus->write = gswip_mdio_wr; + bus->name = "lantiq,xrx200-mdio"; + snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(priv->dev)); + bus->parent = priv->dev; + + err = devm_of_mdiobus_register(dev, bus, mdio_np); + +out_put_node: + of_node_put(mdio_np); + + return err; +} + +static int gswip_pce_table_entry_read(struct gswip_priv *priv, + struct gswip_pce_table_entry *tbl) +{ + int i; + int err; + u32 crtl; + u32 tmp; + u16 addr_mode = tbl->key_mode ? GSWIP_PCE_TBL_CTRL_OPMOD_KSRD : + GSWIP_PCE_TBL_CTRL_OPMOD_ADRD; + + mutex_lock(&priv->pce_table_lock); + + err = gswip_switch_r_timeout(priv, GSWIP_PCE_TBL_CTRL, + GSWIP_PCE_TBL_CTRL_BAS); + if (err) + goto out_unlock; + + regmap_write(priv->gswip, GSWIP_PCE_TBL_ADDR, tbl->index); + regmap_write_bits(priv->gswip, GSWIP_PCE_TBL_CTRL, + GSWIP_PCE_TBL_CTRL_ADDR_MASK | + GSWIP_PCE_TBL_CTRL_OPMOD_MASK | + GSWIP_PCE_TBL_CTRL_BAS, + tbl->table | addr_mode | GSWIP_PCE_TBL_CTRL_BAS); + + err = gswip_switch_r_timeout(priv, GSWIP_PCE_TBL_CTRL, + GSWIP_PCE_TBL_CTRL_BAS); + if (err) + goto out_unlock; + + for (i = 0; i < ARRAY_SIZE(tbl->key); i++) { + err = regmap_read(priv->gswip, GSWIP_PCE_TBL_KEY(i), &tmp); + if (err) + goto out_unlock; + tbl->key[i] = tmp; + } + for (i = 0; i < ARRAY_SIZE(tbl->val); i++) { + err = regmap_read(priv->gswip, GSWIP_PCE_TBL_VAL(i), &tmp); + if (err) + goto out_unlock; + tbl->val[i] = tmp; + } + + err = regmap_read(priv->gswip, GSWIP_PCE_TBL_MASK, &tmp); + if (err) + goto out_unlock; + + tbl->mask = tmp; + err = regmap_read(priv->gswip, GSWIP_PCE_TBL_CTRL, &crtl); + if (err) + goto out_unlock; + + tbl->type = !!(crtl & GSWIP_PCE_TBL_CTRL_TYPE); + tbl->valid = !!(crtl & GSWIP_PCE_TBL_CTRL_VLD); + tbl->gmap = (crtl & GSWIP_PCE_TBL_CTRL_GMAP_MASK) >> 7; + +out_unlock: + mutex_unlock(&priv->pce_table_lock); + + return err; +} + +static int gswip_pce_table_entry_write(struct gswip_priv *priv, + struct gswip_pce_table_entry *tbl) +{ + int i; + int err; + u32 crtl; + u16 addr_mode = tbl->key_mode ? GSWIP_PCE_TBL_CTRL_OPMOD_KSWR : + GSWIP_PCE_TBL_CTRL_OPMOD_ADWR; + + mutex_lock(&priv->pce_table_lock); + + err = gswip_switch_r_timeout(priv, GSWIP_PCE_TBL_CTRL, + GSWIP_PCE_TBL_CTRL_BAS); + if (err) { + mutex_unlock(&priv->pce_table_lock); + return err; + } + + regmap_write(priv->gswip, GSWIP_PCE_TBL_ADDR, tbl->index); + regmap_write_bits(priv->gswip, GSWIP_PCE_TBL_CTRL, + GSWIP_PCE_TBL_CTRL_ADDR_MASK | + GSWIP_PCE_TBL_CTRL_OPMOD_MASK, + tbl->table | addr_mode); + + for (i = 0; i < ARRAY_SIZE(tbl->key); i++) + regmap_write(priv->gswip, GSWIP_PCE_TBL_KEY(i), tbl->key[i]); + + for (i = 0; i < ARRAY_SIZE(tbl->val); i++) + regmap_write(priv->gswip, GSWIP_PCE_TBL_VAL(i), tbl->val[i]); + + regmap_write_bits(priv->gswip, GSWIP_PCE_TBL_CTRL, + GSWIP_PCE_TBL_CTRL_ADDR_MASK | + GSWIP_PCE_TBL_CTRL_OPMOD_MASK, + tbl->table | addr_mode); + + regmap_write(priv->gswip, GSWIP_PCE_TBL_MASK, tbl->mask); + + regmap_read(priv->gswip, GSWIP_PCE_TBL_CTRL, &crtl); + crtl &= ~(GSWIP_PCE_TBL_CTRL_TYPE | GSWIP_PCE_TBL_CTRL_VLD | + GSWIP_PCE_TBL_CTRL_GMAP_MASK); + if (tbl->type) + crtl |= GSWIP_PCE_TBL_CTRL_TYPE; + if (tbl->valid) + crtl |= GSWIP_PCE_TBL_CTRL_VLD; + crtl |= (tbl->gmap << 7) & GSWIP_PCE_TBL_CTRL_GMAP_MASK; + crtl |= GSWIP_PCE_TBL_CTRL_BAS; + regmap_write(priv->gswip, GSWIP_PCE_TBL_CTRL, crtl); + + err = gswip_switch_r_timeout(priv, GSWIP_PCE_TBL_CTRL, + GSWIP_PCE_TBL_CTRL_BAS); + + mutex_unlock(&priv->pce_table_lock); + + return err; +} + +/* Add the LAN port into a bridge with the CPU port by + * default. This prevents automatic forwarding of + * packages between the LAN ports when no explicit + * bridge is configured. + */ +static int gswip_add_single_port_br(struct gswip_priv *priv, int port, bool add) +{ + struct gswip_pce_table_entry vlan_active = {0,}; + struct gswip_pce_table_entry vlan_mapping = {0,}; + int err; + + vlan_active.index = port + 1; + vlan_active.table = GSWIP_TABLE_ACTIVE_VLAN; + vlan_active.key[0] = GSWIP_VLAN_UNAWARE_PVID; + vlan_active.val[0] = port + 1 /* fid */; + vlan_active.valid = add; + err = gswip_pce_table_entry_write(priv, &vlan_active); + if (err) { + dev_err(priv->dev, "failed to write active VLAN: %d\n", err); + return err; + } + + if (!add) + return 0; + + vlan_mapping.index = port + 1; + vlan_mapping.table = GSWIP_TABLE_VLAN_MAPPING; + vlan_mapping.val[0] = GSWIP_VLAN_UNAWARE_PVID; + vlan_mapping.val[1] = BIT(port) | dsa_cpu_ports(priv->ds); + vlan_mapping.val[2] = 0; + err = gswip_pce_table_entry_write(priv, &vlan_mapping); + if (err) { + dev_err(priv->dev, "failed to write VLAN mapping: %d\n", err); + return err; + } + + return 0; +} + +static int gswip_port_setup(struct dsa_switch *ds, int port) +{ + struct gswip_priv *priv = ds->priv; + int err; + + if (!dsa_is_cpu_port(ds, port)) { + err = gswip_add_single_port_br(priv, port, true); + if (err) + return err; + } + + return 0; +} + +static int gswip_port_enable(struct dsa_switch *ds, int port, + struct phy_device *phydev) +{ + struct gswip_priv *priv = ds->priv; + + if (!dsa_is_cpu_port(ds, port)) { + u32 mdio_phy = 0; + + if (phydev) + mdio_phy = phydev->mdio.addr & GSWIP_MDIO_PHY_ADDR_MASK; + + regmap_write_bits(priv->mdio, GSWIP_MDIO_PHYp(port), + GSWIP_MDIO_PHY_ADDR_MASK, + mdio_phy); + } + + /* RMON Counter Enable for port */ + regmap_write(priv->gswip, GSWIP_BM_PCFGp(port), GSWIP_BM_PCFG_CNTEN); + + /* enable port fetch/store dma & VLAN Modification */ + regmap_set_bits(priv->gswip, GSWIP_FDMA_PCTRLp(port), + GSWIP_FDMA_PCTRL_EN | GSWIP_FDMA_PCTRL_VLANMOD_BOTH); + regmap_set_bits(priv->gswip, GSWIP_SDMA_PCTRLp(port), + GSWIP_SDMA_PCTRL_EN); + + return 0; +} + +static void gswip_port_disable(struct dsa_switch *ds, int port) +{ + struct gswip_priv *priv = ds->priv; + + regmap_clear_bits(priv->gswip, GSWIP_FDMA_PCTRLp(port), + GSWIP_FDMA_PCTRL_EN); + regmap_clear_bits(priv->gswip, GSWIP_SDMA_PCTRLp(port), + GSWIP_SDMA_PCTRL_EN); +} + +static int gswip_pce_load_microcode(struct gswip_priv *priv) +{ + int i; + int err; + + regmap_write_bits(priv->gswip, GSWIP_PCE_TBL_CTRL, + GSWIP_PCE_TBL_CTRL_ADDR_MASK | + GSWIP_PCE_TBL_CTRL_OPMOD_MASK | + GSWIP_PCE_TBL_CTRL_OPMOD_ADWR, + GSWIP_PCE_TBL_CTRL_OPMOD_ADWR); + regmap_write(priv->gswip, GSWIP_PCE_TBL_MASK, 0); + + for (i = 0; i < priv->hw_info->pce_microcode_size; i++) { + regmap_write(priv->gswip, GSWIP_PCE_TBL_ADDR, i); + regmap_write(priv->gswip, GSWIP_PCE_TBL_VAL(0), + (*priv->hw_info->pce_microcode)[i].val_0); + regmap_write(priv->gswip, GSWIP_PCE_TBL_VAL(1), + (*priv->hw_info->pce_microcode)[i].val_1); + regmap_write(priv->gswip, GSWIP_PCE_TBL_VAL(2), + (*priv->hw_info->pce_microcode)[i].val_2); + regmap_write(priv->gswip, GSWIP_PCE_TBL_VAL(3), + (*priv->hw_info->pce_microcode)[i].val_3); + + /* start the table access: */ + regmap_set_bits(priv->gswip, GSWIP_PCE_TBL_CTRL, + GSWIP_PCE_TBL_CTRL_BAS); + err = gswip_switch_r_timeout(priv, GSWIP_PCE_TBL_CTRL, + GSWIP_PCE_TBL_CTRL_BAS); + if (err) + return err; + } + + /* tell the switch that the microcode is loaded */ + regmap_set_bits(priv->gswip, GSWIP_PCE_GCTRL_0, + GSWIP_PCE_GCTRL_0_MC_VALID); + + return 0; +} + +static void gswip_port_commit_pvid(struct gswip_priv *priv, int port) +{ + struct dsa_port *dp = dsa_to_port(priv->ds, port); + struct net_device *br = dsa_port_bridge_dev_get(dp); + u32 vinr; + int idx; + + if (!dsa_port_is_user(dp)) + return; + + if (br) { + u16 pvid = GSWIP_VLAN_UNAWARE_PVID; + + if (br_vlan_enabled(br)) + br_vlan_get_pvid(br, &pvid); + + /* VLAN-aware bridge ports with no PVID will use Active VLAN + * index 0. The expectation is that this drops all untagged and + * VID-0 tagged ingress traffic. + */ + idx = 0; + for (int i = priv->hw_info->max_ports; + i < ARRAY_SIZE(priv->vlans); i++) { + if (priv->vlans[i].bridge == br && + priv->vlans[i].vid == pvid) { + idx = i; + break; + } + } + } else { + /* The Active VLAN table index as configured by + * gswip_add_single_port_br() + */ + idx = port + 1; + } + + vinr = idx ? GSWIP_PCE_VCTRL_VINR_ALL : GSWIP_PCE_VCTRL_VINR_TAGGED; + regmap_write_bits(priv->gswip, GSWIP_PCE_VCTRL(port), + GSWIP_PCE_VCTRL_VINR, + FIELD_PREP(GSWIP_PCE_VCTRL_VINR, vinr)); + + /* Note that in GSWIP 2.2 VLAN mode the VID needs to be programmed + * directly instead of referencing the index in the Active VLAN Tablet. + * However, without the VLANMD bit (9) in PCE_GCTRL_1 (0x457) even + * GSWIP 2.2 and newer hardware maintain the GSWIP 2.1 behavior. + */ + regmap_write(priv->gswip, GSWIP_PCE_DEFPVID(port), idx); +} + +static int gswip_port_vlan_filtering(struct dsa_switch *ds, int port, + bool vlan_filtering, + struct netlink_ext_ack *extack) +{ + struct gswip_priv *priv = ds->priv; + + if (vlan_filtering) { + /* Use tag based VLAN */ + regmap_write_bits(priv->gswip, GSWIP_PCE_VCTRL(port), + GSWIP_PCE_VCTRL_VSR | + GSWIP_PCE_VCTRL_UVR | + GSWIP_PCE_VCTRL_VIMR | + GSWIP_PCE_VCTRL_VEMR | + GSWIP_PCE_VCTRL_VID0, + GSWIP_PCE_VCTRL_UVR | + GSWIP_PCE_VCTRL_VIMR | + GSWIP_PCE_VCTRL_VEMR | + GSWIP_PCE_VCTRL_VID0); + regmap_clear_bits(priv->gswip, GSWIP_PCE_PCTRL_0p(port), + GSWIP_PCE_PCTRL_0_TVM); + } else { + /* Use port based VLAN */ + regmap_write_bits(priv->gswip, GSWIP_PCE_VCTRL(port), + GSWIP_PCE_VCTRL_UVR | + GSWIP_PCE_VCTRL_VIMR | + GSWIP_PCE_VCTRL_VEMR | + GSWIP_PCE_VCTRL_VID0 | + GSWIP_PCE_VCTRL_VSR, + GSWIP_PCE_VCTRL_VSR); + regmap_set_bits(priv->gswip, GSWIP_PCE_PCTRL_0p(port), + GSWIP_PCE_PCTRL_0_TVM); + } + + gswip_port_commit_pvid(priv, port); + + return 0; +} + +static int gswip_setup(struct dsa_switch *ds) +{ + unsigned int cpu_ports = dsa_cpu_ports(ds); + struct gswip_priv *priv = ds->priv; + struct dsa_port *cpu_dp; + int err, i; + + regmap_write(priv->gswip, GSWIP_SWRES, GSWIP_SWRES_R0); + usleep_range(5000, 10000); + regmap_write(priv->gswip, GSWIP_SWRES, 0); + + /* disable port fetch/store dma on all ports */ + for (i = 0; i < priv->hw_info->max_ports; i++) { + gswip_port_disable(ds, i); + gswip_port_vlan_filtering(ds, i, false, NULL); + } + + /* enable Switch */ + regmap_set_bits(priv->mdio, GSWIP_MDIO_GLOB, GSWIP_MDIO_GLOB_ENABLE); + + err = gswip_pce_load_microcode(priv); + if (err) { + dev_err(priv->dev, "writing PCE microcode failed, %i\n", err); + return err; + } + + /* Default unknown Broadcast/Multicast/Unicast port maps */ + regmap_write(priv->gswip, GSWIP_PCE_PMAP1, cpu_ports); + regmap_write(priv->gswip, GSWIP_PCE_PMAP2, cpu_ports); + regmap_write(priv->gswip, GSWIP_PCE_PMAP3, cpu_ports); + + /* Deactivate MDIO PHY auto polling. Some PHYs as the AR8030 have an + * interoperability problem with this auto polling mechanism because + * their status registers think that the link is in a different state + * than it actually is. For the AR8030 it has the BMSR_ESTATEN bit set + * as well as ESTATUS_1000_TFULL and ESTATUS_1000_XFULL. This makes the + * auto polling state machine consider the link being negotiated with + * 1Gbit/s. Since the PHY itself is a Fast Ethernet RMII PHY this leads + * to the switch port being completely dead (RX and TX are both not + * working). + * Also with various other PHY / port combinations (PHY11G GPHY, PHY22F + * GPHY, external RGMII PEF7071/7072) any traffic would stop. Sometimes + * it would work fine for a few minutes to hours and then stop, on + * other device it would no traffic could be sent or received at all. + * Testing shows that when PHY auto polling is disabled these problems + * go away. + */ + regmap_write(priv->mdio, GSWIP_MDIO_MDC_CFG0, 0x0); + + /* Configure the MDIO Clock 2.5 MHz */ + regmap_write_bits(priv->mdio, GSWIP_MDIO_MDC_CFG1, 0xff, 0x09); + + /* bring up the mdio bus */ + err = gswip_mdio(priv); + if (err) { + dev_err(priv->dev, "mdio bus setup failed\n"); + return err; + } + + /* Disable the xMII interface and clear it's isolation bit */ + for (i = 0; i < priv->hw_info->max_ports; i++) + gswip_mii_mask_cfg(priv, + GSWIP_MII_CFG_EN | GSWIP_MII_CFG_ISOLATE, + 0, i); + + dsa_switch_for_each_cpu_port(cpu_dp, ds) { + /* enable special tag insertion on cpu port */ + regmap_set_bits(priv->gswip, GSWIP_FDMA_PCTRLp(cpu_dp->index), + GSWIP_FDMA_PCTRL_STEN); + + /* accept special tag in ingress direction */ + regmap_set_bits(priv->gswip, + GSWIP_PCE_PCTRL_0p(cpu_dp->index), + GSWIP_PCE_PCTRL_0_INGRESS); + } + + regmap_set_bits(priv->gswip, GSWIP_BM_QUEUE_GCTRL, + GSWIP_BM_QUEUE_GCTRL_GL_MOD); + + /* VLAN aware Switching */ + regmap_set_bits(priv->gswip, GSWIP_PCE_GCTRL_0, + GSWIP_PCE_GCTRL_0_VLAN); + + /* Flush MAC Table */ + regmap_set_bits(priv->gswip, GSWIP_PCE_GCTRL_0, + GSWIP_PCE_GCTRL_0_MTFL); + + err = gswip_switch_r_timeout(priv, GSWIP_PCE_GCTRL_0, + GSWIP_PCE_GCTRL_0_MTFL); + if (err) { + dev_err(priv->dev, "MAC flushing didn't finish\n"); + return err; + } + + ds->mtu_enforcement_ingress = true; + + return 0; +} + +static enum dsa_tag_protocol gswip_get_tag_protocol(struct dsa_switch *ds, + int port, + enum dsa_tag_protocol mp) +{ + struct gswip_priv *priv = ds->priv; + + return priv->hw_info->tag_protocol; +} + +static int gswip_vlan_active_create(struct gswip_priv *priv, + struct net_device *bridge, + int fid, u16 vid) +{ + struct gswip_pce_table_entry vlan_active = {0,}; + unsigned int max_ports = priv->hw_info->max_ports; + int idx = -1; + int err; + int i; + + /* Look for a free slot */ + for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) { + if (!priv->vlans[i].bridge) { + idx = i; + break; + } + } + + if (idx == -1) + return -ENOSPC; + + if (fid == -1) + fid = idx; + + vlan_active.index = idx; + vlan_active.table = GSWIP_TABLE_ACTIVE_VLAN; + vlan_active.key[0] = vid; + vlan_active.val[0] = fid; + vlan_active.valid = true; + + err = gswip_pce_table_entry_write(priv, &vlan_active); + if (err) { + dev_err(priv->dev, "failed to write active VLAN: %d\n", err); + return err; + } + + priv->vlans[idx].bridge = bridge; + priv->vlans[idx].vid = vid; + priv->vlans[idx].fid = fid; + + return idx; +} + +static int gswip_vlan_active_remove(struct gswip_priv *priv, int idx) +{ + struct gswip_pce_table_entry vlan_active = {0,}; + int err; + + vlan_active.index = idx; + vlan_active.table = GSWIP_TABLE_ACTIVE_VLAN; + vlan_active.valid = false; + err = gswip_pce_table_entry_write(priv, &vlan_active); + if (err) + dev_err(priv->dev, "failed to delete active VLAN: %d\n", err); + priv->vlans[idx].bridge = NULL; + + return err; +} + +static int gswip_vlan_add(struct gswip_priv *priv, struct net_device *bridge, + int port, u16 vid, bool untagged, bool pvid, + bool vlan_aware) +{ + struct gswip_pce_table_entry vlan_mapping = {0,}; + unsigned int max_ports = priv->hw_info->max_ports; + unsigned int cpu_ports = dsa_cpu_ports(priv->ds); + bool active_vlan_created = false; + int fid = -1, idx = -1; + int i, err; + + /* Check if there is already a page for this bridge */ + for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) { + if (priv->vlans[i].bridge == bridge) { + if (vlan_aware) { + if (fid != -1 && fid != priv->vlans[i].fid) + dev_err(priv->dev, "one bridge with multiple flow ids\n"); + fid = priv->vlans[i].fid; + } + if (priv->vlans[i].vid == vid) { + idx = i; + break; + } + } + } + + /* If this bridge is not programmed yet, add a Active VLAN table + * entry in a free slot and prepare the VLAN mapping table entry. + */ + if (idx == -1) { + idx = gswip_vlan_active_create(priv, bridge, fid, vid); + if (idx < 0) + return idx; + active_vlan_created = true; + + vlan_mapping.index = idx; + vlan_mapping.table = GSWIP_TABLE_VLAN_MAPPING; + } else { + /* Read the existing VLAN mapping entry from the switch */ + vlan_mapping.index = idx; + vlan_mapping.table = GSWIP_TABLE_VLAN_MAPPING; + err = gswip_pce_table_entry_read(priv, &vlan_mapping); + if (err) { + dev_err(priv->dev, "failed to read VLAN mapping: %d\n", + err); + return err; + } + } + + /* VLAN ID byte, maps to the VLAN ID of vlan active table */ + vlan_mapping.val[0] = vid; + /* Update the VLAN mapping entry and write it to the switch */ + vlan_mapping.val[1] |= cpu_ports; + vlan_mapping.val[1] |= BIT(port); + if (vlan_aware) + vlan_mapping.val[2] |= cpu_ports; + if (untagged) + vlan_mapping.val[2] &= ~BIT(port); + else + vlan_mapping.val[2] |= BIT(port); + err = gswip_pce_table_entry_write(priv, &vlan_mapping); + if (err) { + dev_err(priv->dev, "failed to write VLAN mapping: %d\n", err); + /* In case an Active VLAN was creaetd delete it again */ + if (active_vlan_created) + gswip_vlan_active_remove(priv, idx); + return err; + } + + gswip_port_commit_pvid(priv, port); + + return 0; +} + +static int gswip_vlan_remove(struct gswip_priv *priv, + struct net_device *bridge, int port, + u16 vid) +{ + struct gswip_pce_table_entry vlan_mapping = {0,}; + unsigned int max_ports = priv->hw_info->max_ports; + int idx = -1; + int i; + int err; + + /* Check if there is already a page for this bridge */ + for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) { + if (priv->vlans[i].bridge == bridge && + priv->vlans[i].vid == vid) { + idx = i; + break; + } + } + + if (idx == -1) { + dev_err(priv->dev, "Port %d cannot find VID %u of bridge %s\n", + port, vid, bridge ? bridge->name : "(null)"); + return -ENOENT; + } + + vlan_mapping.index = idx; + vlan_mapping.table = GSWIP_TABLE_VLAN_MAPPING; + err = gswip_pce_table_entry_read(priv, &vlan_mapping); + if (err) { + dev_err(priv->dev, "failed to read VLAN mapping: %d\n", err); + return err; + } + + vlan_mapping.val[1] &= ~BIT(port); + vlan_mapping.val[2] &= ~BIT(port); + err = gswip_pce_table_entry_write(priv, &vlan_mapping); + if (err) { + dev_err(priv->dev, "failed to write VLAN mapping: %d\n", err); + return err; + } + + /* In case all ports are removed from the bridge, remove the VLAN */ + if (!(vlan_mapping.val[1] & ~dsa_cpu_ports(priv->ds))) { + err = gswip_vlan_active_remove(priv, idx); + if (err) { + dev_err(priv->dev, "failed to write active VLAN: %d\n", + err); + return err; + } + } + + gswip_port_commit_pvid(priv, port); + + return 0; +} + +static int gswip_port_bridge_join(struct dsa_switch *ds, int port, + struct dsa_bridge bridge, + bool *tx_fwd_offload, + struct netlink_ext_ack *extack) +{ + struct net_device *br = bridge.dev; + struct gswip_priv *priv = ds->priv; + int err; + + /* Set up the VLAN for VLAN-unaware bridging for this port, and remove + * it from the "single-port bridge" through which it was operating as + * standalone. + */ + err = gswip_vlan_add(priv, br, port, GSWIP_VLAN_UNAWARE_PVID, + true, true, false); + if (err) + return err; + + return gswip_add_single_port_br(priv, port, false); +} + +static void gswip_port_bridge_leave(struct dsa_switch *ds, int port, + struct dsa_bridge bridge) +{ + struct net_device *br = bridge.dev; + struct gswip_priv *priv = ds->priv; + + /* Add the port back to the "single-port bridge", and remove it from + * the VLAN-unaware PVID created for this bridge. + */ + gswip_add_single_port_br(priv, port, true); + gswip_vlan_remove(priv, br, port, GSWIP_VLAN_UNAWARE_PVID); +} + +static int gswip_port_vlan_prepare(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan, + struct netlink_ext_ack *extack) +{ + struct net_device *bridge = dsa_port_bridge_dev_get(dsa_to_port(ds, port)); + struct gswip_priv *priv = ds->priv; + unsigned int max_ports = priv->hw_info->max_ports; + int pos = max_ports; + int i, idx = -1; + + /* We only support VLAN filtering on bridges */ + if (!dsa_is_cpu_port(ds, port) && !bridge) + return -EOPNOTSUPP; + + /* Check if there is already a page for this VLAN */ + for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) { + if (priv->vlans[i].bridge == bridge && + priv->vlans[i].vid == vlan->vid) { + idx = i; + break; + } + } + + /* If this VLAN is not programmed yet, we have to reserve + * one entry in the VLAN table. Make sure we start at the + * next position round. + */ + if (idx == -1) { + /* Look for a free slot */ + for (; pos < ARRAY_SIZE(priv->vlans); pos++) { + if (!priv->vlans[pos].bridge) { + idx = pos; + pos++; + break; + } + } + + if (idx == -1) { + NL_SET_ERR_MSG_MOD(extack, "No slot in VLAN table"); + return -ENOSPC; + } + } + + return 0; +} + +static int gswip_port_vlan_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan, + struct netlink_ext_ack *extack) +{ + struct net_device *bridge = dsa_port_bridge_dev_get(dsa_to_port(ds, port)); + struct gswip_priv *priv = ds->priv; + bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; + bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID; + int err; + + if (vlan->vid == GSWIP_VLAN_UNAWARE_PVID) + return 0; + + err = gswip_port_vlan_prepare(ds, port, vlan, extack); + if (err) + return err; + + /* We have to receive all packets on the CPU port and should not + * do any VLAN filtering here. This is also called with bridge + * NULL and then we do not know for which bridge to configure + * this. + */ + if (dsa_is_cpu_port(ds, port)) + return 0; + + return gswip_vlan_add(priv, bridge, port, vlan->vid, untagged, pvid, + true); +} + +static int gswip_port_vlan_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + struct net_device *bridge = dsa_port_bridge_dev_get(dsa_to_port(ds, port)); + struct gswip_priv *priv = ds->priv; + + if (vlan->vid == GSWIP_VLAN_UNAWARE_PVID) + return 0; + + /* We have to receive all packets on the CPU port and should not + * do any VLAN filtering here. This is also called with bridge + * NULL and then we do not know for which bridge to configure + * this. + */ + if (dsa_is_cpu_port(ds, port)) + return 0; + + return gswip_vlan_remove(priv, bridge, port, vlan->vid); +} + +static void gswip_port_fast_age(struct dsa_switch *ds, int port) +{ + struct gswip_priv *priv = ds->priv; + struct gswip_pce_table_entry mac_bridge = {0,}; + int i; + int err; + + for (i = 0; i < 2048; i++) { + mac_bridge.table = GSWIP_TABLE_MAC_BRIDGE; + mac_bridge.index = i; + + err = gswip_pce_table_entry_read(priv, &mac_bridge); + if (err) { + dev_err(priv->dev, "failed to read mac bridge: %d\n", + err); + return; + } + + if (!mac_bridge.valid) + continue; + + if (mac_bridge.val[1] & GSWIP_TABLE_MAC_BRIDGE_VAL1_STATIC) + continue; + + if (port != FIELD_GET(GSWIP_TABLE_MAC_BRIDGE_VAL0_PORT, + mac_bridge.val[0])) + continue; + + mac_bridge.valid = false; + err = gswip_pce_table_entry_write(priv, &mac_bridge); + if (err) { + dev_err(priv->dev, "failed to write mac bridge: %d\n", + err); + return; + } + } +} + +static void gswip_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) +{ + struct gswip_priv *priv = ds->priv; + u32 stp_state; + + switch (state) { + case BR_STATE_DISABLED: + regmap_clear_bits(priv->gswip, GSWIP_SDMA_PCTRLp(port), + GSWIP_SDMA_PCTRL_EN); + return; + case BR_STATE_BLOCKING: + case BR_STATE_LISTENING: + stp_state = GSWIP_PCE_PCTRL_0_PSTATE_LISTEN; + break; + case BR_STATE_LEARNING: + stp_state = GSWIP_PCE_PCTRL_0_PSTATE_LEARNING; + break; + case BR_STATE_FORWARDING: + stp_state = GSWIP_PCE_PCTRL_0_PSTATE_FORWARDING; + break; + default: + dev_err(priv->dev, "invalid STP state: %d\n", state); + return; + } + + regmap_set_bits(priv->gswip, GSWIP_SDMA_PCTRLp(port), + GSWIP_SDMA_PCTRL_EN); + regmap_write_bits(priv->gswip, GSWIP_PCE_PCTRL_0p(port), + GSWIP_PCE_PCTRL_0_PSTATE_MASK, + stp_state); +} + +static int gswip_port_fdb(struct dsa_switch *ds, int port, + struct net_device *bridge, const unsigned char *addr, + u16 vid, bool add) +{ + struct gswip_priv *priv = ds->priv; + struct gswip_pce_table_entry mac_bridge = {0,}; + unsigned int max_ports = priv->hw_info->max_ports; + int fid = -1; + int i; + int err; + + for (i = max_ports; i < ARRAY_SIZE(priv->vlans); i++) { + if (priv->vlans[i].bridge == bridge) { + fid = priv->vlans[i].fid; + break; + } + } + + if (fid == -1) { + dev_err(priv->dev, "no FID found for bridge %s\n", + bridge->name); + return -EINVAL; + } + + mac_bridge.table = GSWIP_TABLE_MAC_BRIDGE; + mac_bridge.key_mode = true; + mac_bridge.key[0] = addr[5] | (addr[4] << 8); + mac_bridge.key[1] = addr[3] | (addr[2] << 8); + mac_bridge.key[2] = addr[1] | (addr[0] << 8); + mac_bridge.key[3] = FIELD_PREP(GSWIP_TABLE_MAC_BRIDGE_KEY3_FID, fid); + mac_bridge.val[0] = add ? BIT(port) : 0; /* port map */ + mac_bridge.val[1] = GSWIP_TABLE_MAC_BRIDGE_VAL1_STATIC; + mac_bridge.valid = add; + + err = gswip_pce_table_entry_write(priv, &mac_bridge); + if (err) + dev_err(priv->dev, "failed to write mac bridge: %d\n", err); + + return err; +} + +static int gswip_port_fdb_add(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid, + struct dsa_db db) +{ + if (db.type != DSA_DB_BRIDGE) + return -EOPNOTSUPP; + + return gswip_port_fdb(ds, port, db.bridge.dev, addr, vid, true); +} + +static int gswip_port_fdb_del(struct dsa_switch *ds, int port, + const unsigned char *addr, u16 vid, + struct dsa_db db) +{ + if (db.type != DSA_DB_BRIDGE) + return -EOPNOTSUPP; + + return gswip_port_fdb(ds, port, db.bridge.dev, addr, vid, false); +} + +static int gswip_port_fdb_dump(struct dsa_switch *ds, int port, + dsa_fdb_dump_cb_t *cb, void *data) +{ + struct gswip_priv *priv = ds->priv; + struct gswip_pce_table_entry mac_bridge = {0,}; + unsigned char addr[ETH_ALEN]; + int i; + int err; + + for (i = 0; i < 2048; i++) { + mac_bridge.table = GSWIP_TABLE_MAC_BRIDGE; + mac_bridge.index = i; + + err = gswip_pce_table_entry_read(priv, &mac_bridge); + if (err) { + dev_err(priv->dev, + "failed to read mac bridge entry %d: %d\n", + i, err); + return err; + } + + if (!mac_bridge.valid) + continue; + + addr[5] = mac_bridge.key[0] & 0xff; + addr[4] = (mac_bridge.key[0] >> 8) & 0xff; + addr[3] = mac_bridge.key[1] & 0xff; + addr[2] = (mac_bridge.key[1] >> 8) & 0xff; + addr[1] = mac_bridge.key[2] & 0xff; + addr[0] = (mac_bridge.key[2] >> 8) & 0xff; + if (mac_bridge.val[1] & GSWIP_TABLE_MAC_BRIDGE_VAL1_STATIC) { + if (mac_bridge.val[0] & BIT(port)) { + err = cb(addr, 0, true, data); + if (err) + return err; + } + } else { + if (port == FIELD_GET(GSWIP_TABLE_MAC_BRIDGE_VAL0_PORT, + mac_bridge.val[0])) { + err = cb(addr, 0, false, data); + if (err) + return err; + } + } + } + return 0; +} + +static int gswip_port_max_mtu(struct dsa_switch *ds, int port) +{ + /* Includes 8 bytes for special header. */ + return GSWIP_MAX_PACKET_LENGTH - VLAN_ETH_HLEN - ETH_FCS_LEN; +} + +static int gswip_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu) +{ + struct gswip_priv *priv = ds->priv; + + /* CPU port always has maximum mtu of user ports, so use it to set + * switch frame size, including 8 byte special header. + */ + if (dsa_is_cpu_port(ds, port)) { + new_mtu += 8; + regmap_write(priv->gswip, GSWIP_MAC_FLEN, + VLAN_ETH_HLEN + new_mtu + ETH_FCS_LEN); + } + + /* Enable MLEN for ports with non-standard MTUs, including the special + * header on the CPU port added above. + */ + if (new_mtu != ETH_DATA_LEN) + regmap_set_bits(priv->gswip, GSWIP_MAC_CTRL_2p(port), + GSWIP_MAC_CTRL_2_MLEN); + else + regmap_clear_bits(priv->gswip, GSWIP_MAC_CTRL_2p(port), + GSWIP_MAC_CTRL_2_MLEN); + + return 0; +} + +static void gswip_phylink_get_caps(struct dsa_switch *ds, int port, + struct phylink_config *config) +{ + struct gswip_priv *priv = ds->priv; + + priv->hw_info->phylink_get_caps(ds, port, config); +} + +static void gswip_port_set_link(struct gswip_priv *priv, int port, bool link) +{ + u32 mdio_phy; + + if (link) + mdio_phy = GSWIP_MDIO_PHY_LINK_UP; + else + mdio_phy = GSWIP_MDIO_PHY_LINK_DOWN; + + regmap_write_bits(priv->mdio, GSWIP_MDIO_PHYp(port), + GSWIP_MDIO_PHY_LINK_MASK, mdio_phy); +} + +static void gswip_port_set_speed(struct gswip_priv *priv, int port, int speed, + phy_interface_t interface) +{ + u32 mdio_phy = 0, mii_cfg = 0, mac_ctrl_0 = 0; + + switch (speed) { + case SPEED_10: + mdio_phy = GSWIP_MDIO_PHY_SPEED_M10; + + if (interface == PHY_INTERFACE_MODE_RMII) + mii_cfg = GSWIP_MII_CFG_RATE_M50; + else + mii_cfg = GSWIP_MII_CFG_RATE_M2P5; + + mac_ctrl_0 = GSWIP_MAC_CTRL_0_GMII_MII; + break; + + case SPEED_100: + mdio_phy = GSWIP_MDIO_PHY_SPEED_M100; + + if (interface == PHY_INTERFACE_MODE_RMII) + mii_cfg = GSWIP_MII_CFG_RATE_M50; + else + mii_cfg = GSWIP_MII_CFG_RATE_M25; + + mac_ctrl_0 = GSWIP_MAC_CTRL_0_GMII_MII; + break; + + case SPEED_1000: + mdio_phy = GSWIP_MDIO_PHY_SPEED_G1; + + mii_cfg = GSWIP_MII_CFG_RATE_M125; + + mac_ctrl_0 = GSWIP_MAC_CTRL_0_GMII_RGMII; + break; + } + + regmap_write_bits(priv->mdio, GSWIP_MDIO_PHYp(port), + GSWIP_MDIO_PHY_SPEED_MASK, mdio_phy); + gswip_mii_mask_cfg(priv, GSWIP_MII_CFG_RATE_MASK, mii_cfg, port); + regmap_write_bits(priv->gswip, GSWIP_MAC_CTRL_0p(port), + GSWIP_MAC_CTRL_0_GMII_MASK, mac_ctrl_0); +} + +static void gswip_port_set_duplex(struct gswip_priv *priv, int port, int duplex) +{ + u32 mac_ctrl_0, mdio_phy; + + if (duplex == DUPLEX_FULL) { + mac_ctrl_0 = GSWIP_MAC_CTRL_0_FDUP_EN; + mdio_phy = GSWIP_MDIO_PHY_FDUP_EN; + } else { + mac_ctrl_0 = GSWIP_MAC_CTRL_0_FDUP_DIS; + mdio_phy = GSWIP_MDIO_PHY_FDUP_DIS; + } + + regmap_write_bits(priv->gswip, GSWIP_MAC_CTRL_0p(port), + GSWIP_MAC_CTRL_0_FDUP_MASK, mac_ctrl_0); + regmap_write_bits(priv->mdio, GSWIP_MDIO_PHYp(port), + GSWIP_MDIO_PHY_FDUP_MASK, mdio_phy); +} + +static void gswip_port_set_pause(struct gswip_priv *priv, int port, + bool tx_pause, bool rx_pause) +{ + u32 mac_ctrl_0, mdio_phy; + + if (tx_pause && rx_pause) { + mac_ctrl_0 = GSWIP_MAC_CTRL_0_FCON_RXTX; + mdio_phy = GSWIP_MDIO_PHY_FCONTX_EN | + GSWIP_MDIO_PHY_FCONRX_EN; + } else if (tx_pause) { + mac_ctrl_0 = GSWIP_MAC_CTRL_0_FCON_TX; + mdio_phy = GSWIP_MDIO_PHY_FCONTX_EN | + GSWIP_MDIO_PHY_FCONRX_DIS; + } else if (rx_pause) { + mac_ctrl_0 = GSWIP_MAC_CTRL_0_FCON_RX; + mdio_phy = GSWIP_MDIO_PHY_FCONTX_DIS | + GSWIP_MDIO_PHY_FCONRX_EN; + } else { + mac_ctrl_0 = GSWIP_MAC_CTRL_0_FCON_NONE; + mdio_phy = GSWIP_MDIO_PHY_FCONTX_DIS | + GSWIP_MDIO_PHY_FCONRX_DIS; + } + + regmap_write_bits(priv->gswip, GSWIP_MAC_CTRL_0p(port), + GSWIP_MAC_CTRL_0_FCON_MASK, mac_ctrl_0); + regmap_write_bits(priv->mdio, GSWIP_MDIO_PHYp(port), + GSWIP_MDIO_PHY_FCONTX_MASK | GSWIP_MDIO_PHY_FCONRX_MASK, + mdio_phy); +} + +static void gswip_phylink_mac_config(struct phylink_config *config, + unsigned int mode, + const struct phylink_link_state *state) +{ + struct dsa_port *dp = dsa_phylink_to_port(config); + struct gswip_priv *priv = dp->ds->priv; + int port = dp->index; + u32 miicfg = 0; + + miicfg |= GSWIP_MII_CFG_LDCLKDIS; + + switch (state->interface) { + case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_1000BASEX: + case PHY_INTERFACE_MODE_2500BASEX: + return; + case PHY_INTERFACE_MODE_MII: + case PHY_INTERFACE_MODE_INTERNAL: + miicfg |= GSWIP_MII_CFG_MODE_MIIM; + break; + case PHY_INTERFACE_MODE_REVMII: + miicfg |= GSWIP_MII_CFG_MODE_MIIP; + break; + case PHY_INTERFACE_MODE_RMII: + miicfg |= GSWIP_MII_CFG_MODE_RMIIM; + break; + case PHY_INTERFACE_MODE_RGMII: + case PHY_INTERFACE_MODE_RGMII_ID: + case PHY_INTERFACE_MODE_RGMII_RXID: + case PHY_INTERFACE_MODE_RGMII_TXID: + miicfg |= GSWIP_MII_CFG_MODE_RGMII; + break; + case PHY_INTERFACE_MODE_GMII: + miicfg |= GSWIP_MII_CFG_MODE_GMII; + break; + default: + dev_err(dp->ds->dev, + "Unsupported interface: %d\n", state->interface); + return; + } + + gswip_mii_mask_cfg(priv, + GSWIP_MII_CFG_MODE_MASK | GSWIP_MII_CFG_RMII_CLK | + GSWIP_MII_CFG_RGMII_IBS | GSWIP_MII_CFG_LDCLKDIS, + miicfg, port); + + switch (state->interface) { + case PHY_INTERFACE_MODE_RGMII_ID: + gswip_mii_mask_pcdu(priv, GSWIP_MII_PCDU_TXDLY_MASK | + GSWIP_MII_PCDU_RXDLY_MASK, 0, port); + break; + case PHY_INTERFACE_MODE_RGMII_RXID: + gswip_mii_mask_pcdu(priv, GSWIP_MII_PCDU_RXDLY_MASK, 0, port); + break; + case PHY_INTERFACE_MODE_RGMII_TXID: + gswip_mii_mask_pcdu(priv, GSWIP_MII_PCDU_TXDLY_MASK, 0, port); + break; + default: + break; + } +} + +static void gswip_phylink_mac_link_down(struct phylink_config *config, + unsigned int mode, + phy_interface_t interface) +{ + struct dsa_port *dp = dsa_phylink_to_port(config); + struct gswip_priv *priv = dp->ds->priv; + int port = dp->index; + + gswip_mii_mask_cfg(priv, GSWIP_MII_CFG_EN, 0, port); + + if (!dsa_port_is_cpu(dp)) + gswip_port_set_link(priv, port, false); +} + +static void gswip_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) +{ + struct dsa_port *dp = dsa_phylink_to_port(config); + struct gswip_priv *priv = dp->ds->priv; + int port = dp->index; + + if (!dsa_port_is_cpu(dp)) { + gswip_port_set_link(priv, port, true); + gswip_port_set_speed(priv, port, speed, interface); + gswip_port_set_duplex(priv, port, duplex); + gswip_port_set_pause(priv, port, tx_pause, rx_pause); + } + + gswip_mii_mask_cfg(priv, GSWIP_MII_CFG_EN, GSWIP_MII_CFG_EN, port); +} + +static void gswip_get_strings(struct dsa_switch *ds, int port, u32 stringset, + uint8_t *data) +{ + int i; + + if (stringset != ETH_SS_STATS) + return; + + for (i = 0; i < ARRAY_SIZE(gswip_rmon_cnt); i++) + ethtool_puts(&data, gswip_rmon_cnt[i].name); +} + +static u32 gswip_bcm_ram_entry_read(struct gswip_priv *priv, u32 table, + u32 index) +{ + u32 result, val; + int err; + + regmap_write(priv->gswip, GSWIP_BM_RAM_ADDR, index); + regmap_write_bits(priv->gswip, GSWIP_BM_RAM_CTRL, + GSWIP_BM_RAM_CTRL_ADDR_MASK | GSWIP_BM_RAM_CTRL_OPMOD | + GSWIP_BM_RAM_CTRL_BAS, + table | GSWIP_BM_RAM_CTRL_BAS); + + err = gswip_switch_r_timeout(priv, GSWIP_BM_RAM_CTRL, + GSWIP_BM_RAM_CTRL_BAS); + if (err) { + dev_err(priv->dev, "timeout while reading table: %u, index: %u\n", + table, index); + return 0; + } + + regmap_read(priv->gswip, GSWIP_BM_RAM_VAL(0), &result); + regmap_read(priv->gswip, GSWIP_BM_RAM_VAL(1), &val); + result |= val << 16; + + return result; +} + +static void gswip_get_ethtool_stats(struct dsa_switch *ds, int port, + uint64_t *data) +{ + struct gswip_priv *priv = ds->priv; + const struct gswip_rmon_cnt_desc *rmon_cnt; + int i; + u64 high; + + for (i = 0; i < ARRAY_SIZE(gswip_rmon_cnt); i++) { + rmon_cnt = &gswip_rmon_cnt[i]; + + data[i] = gswip_bcm_ram_entry_read(priv, port, + rmon_cnt->offset); + if (rmon_cnt->size == 2) { + high = gswip_bcm_ram_entry_read(priv, port, + rmon_cnt->offset + 1); + data[i] |= high << 32; + } + } +} + +static int gswip_get_sset_count(struct dsa_switch *ds, int port, int sset) +{ + if (sset != ETH_SS_STATS) + return 0; + + return ARRAY_SIZE(gswip_rmon_cnt); +} + +static struct phylink_pcs *gswip_phylink_mac_select_pcs(struct phylink_config *config, + phy_interface_t interface) +{ + struct dsa_port *dp = dsa_phylink_to_port(config); + struct gswip_priv *priv = dp->ds->priv; + + if (priv->hw_info->mac_select_pcs) + return priv->hw_info->mac_select_pcs(config, interface); + + return NULL; +} + +static const struct phylink_mac_ops gswip_phylink_mac_ops = { + .mac_config = gswip_phylink_mac_config, + .mac_link_down = gswip_phylink_mac_link_down, + .mac_link_up = gswip_phylink_mac_link_up, + .mac_select_pcs = gswip_phylink_mac_select_pcs, +}; + +static const struct dsa_switch_ops gswip_switch_ops = { + .get_tag_protocol = gswip_get_tag_protocol, + .setup = gswip_setup, + .port_setup = gswip_port_setup, + .port_enable = gswip_port_enable, + .port_disable = gswip_port_disable, + .port_bridge_join = gswip_port_bridge_join, + .port_bridge_leave = gswip_port_bridge_leave, + .port_fast_age = gswip_port_fast_age, + .port_vlan_filtering = gswip_port_vlan_filtering, + .port_vlan_add = gswip_port_vlan_add, + .port_vlan_del = gswip_port_vlan_del, + .port_stp_state_set = gswip_port_stp_state_set, + .port_fdb_add = gswip_port_fdb_add, + .port_fdb_del = gswip_port_fdb_del, + .port_fdb_dump = gswip_port_fdb_dump, + .port_change_mtu = gswip_port_change_mtu, + .port_max_mtu = gswip_port_max_mtu, + .phylink_get_caps = gswip_phylink_get_caps, + .get_strings = gswip_get_strings, + .get_ethtool_stats = gswip_get_ethtool_stats, + .get_sset_count = gswip_get_sset_count, +}; + +void gswip_disable_switch(struct gswip_priv *priv) +{ + regmap_clear_bits(priv->mdio, GSWIP_MDIO_GLOB, GSWIP_MDIO_GLOB_ENABLE); +} +EXPORT_SYMBOL_GPL(gswip_disable_switch); + +static int gswip_validate_cpu_port(struct dsa_switch *ds) +{ + struct gswip_priv *priv = ds->priv; + struct dsa_port *cpu_dp; + int cpu_port = -1; + + dsa_switch_for_each_cpu_port(cpu_dp, ds) { + if (cpu_port != -1) + return dev_err_probe(ds->dev, -EINVAL, + "only a single CPU port is supported\n"); + + cpu_port = cpu_dp->index; + } + + if (cpu_port == -1) + return dev_err_probe(ds->dev, -EINVAL, "no CPU port defined\n"); + + if (BIT(cpu_port) & ~priv->hw_info->allowed_cpu_ports) + return dev_err_probe(ds->dev, -EINVAL, + "unsupported CPU port defined\n"); + + return 0; +} + +int gswip_probe_common(struct gswip_priv *priv, u32 version) +{ + int err; + + mutex_init(&priv->pce_table_lock); + + priv->ds = devm_kzalloc(priv->dev, sizeof(*priv->ds), GFP_KERNEL); + if (!priv->ds) + return -ENOMEM; + + priv->ds->dev = priv->dev; + priv->ds->num_ports = priv->hw_info->max_ports; + priv->ds->ops = &gswip_switch_ops; + priv->ds->phylink_mac_ops = &gswip_phylink_mac_ops; + priv->ds->priv = priv; + + /* The hardware has the 'major/minor' version bytes in the wrong order + * preventing numerical comparisons. Construct a 16-bit unsigned integer + * having the REV field as most significant byte and the MOD field as + * least significant byte. This is effectively swapping the two bytes of + * the version variable, but other than using swab16 it doesn't affect + * the source variable. + */ + priv->version = GSWIP_VERSION_REV(version) << 8 | + GSWIP_VERSION_MOD(version); + + err = dsa_register_switch(priv->ds); + if (err) + return dev_err_probe(priv->dev, err, "dsa switch registration failed\n"); + + err = gswip_validate_cpu_port(priv->ds); + if (err) + goto disable_switch; + + dev_info(priv->dev, "probed GSWIP version %lx mod %lx\n", + GSWIP_VERSION_REV(version), GSWIP_VERSION_MOD(version)); + + return 0; + +disable_switch: + gswip_disable_switch(priv); + dsa_unregister_switch(priv->ds); + + return err; +} +EXPORT_SYMBOL_GPL(gswip_probe_common); + +MODULE_AUTHOR("Hauke Mehrtens <hauke@hauke-m.de>"); +MODULE_AUTHOR("Daniel Golle <daniel@makrotopia.org>"); +MODULE_DESCRIPTION("Lantiq / Intel / MaxLinear GSWIP common functions"); +MODULE_LICENSE("GPL"); -- 2.51.1 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v3 01/12] net: dsa: lantiq_gswip: split into common and MMIO parts 2025-10-26 23:43 ` [PATCH net-next v3 01/12] net: dsa: lantiq_gswip: split into common and MMIO parts Daniel Golle @ 2025-10-27 11:37 ` Sverdlin, Alexander 2025-10-27 22:22 ` Vladimir Oltean 1 sibling, 0 replies; 35+ messages in thread From: Sverdlin, Alexander @ 2025-10-27 11:37 UTC (permalink / raw) To: daniel@makrotopia.org, devicetree@vger.kernel.org, netdev@vger.kernel.org, linux-kernel@vger.kernel.org Cc: andrew@lunn.ch, olteanv@gmail.com, robh@kernel.org, lxu@maxlinear.com, john@phrozen.org, davem@davemloft.net, yweng@maxlinear.com, bxu@maxlinear.com, edumazet@google.com, pabeni@redhat.com, conor+dt@kernel.org, fchan@maxlinear.com, ajayaraman@maxlinear.com, krzk+dt@kernel.org, hauke@hauke-m.de, horms@kernel.org, kuba@kernel.org, jpovazanec@maxlinear.com, linux@armlinux.org.uk Hi Daniel, On Sun, 2025-10-26 at 23:43 +0000, Daniel Golle wrote: > Move all parts specific for the MMIO/SoC driver into a module of its own > to prepare for supporting MDIO-connected switch ICs. > Modify gswip_probe() functions by splitting it into a common function > gswip_probe_common() which covers allocating, initializing and registering > the DSA switch, while keeping transport-specific regmap initialization as > well as PHY firmware loading in the new MMIO/SoC-specific gswip_probe() > function. > > Signed-off-by: Daniel Golle <daniel@makrotopia.org> thanks for the patch! Tested-by: Alexander Sverdlin <alexander.sverdlin@siemens.com> (with GSW145) > --- > drivers/net/dsa/lantiq/Kconfig | 6 +- > drivers/net/dsa/lantiq/Makefile | 1 + > drivers/net/dsa/lantiq/lantiq_gswip.c | 1617 +---------------- > drivers/net/dsa/lantiq/lantiq_gswip.h | 4 + > drivers/net/dsa/lantiq/lantiq_gswip_common.c | 1622 ++++++++++++++++++ > 5 files changed, 1658 insertions(+), 1592 deletions(-) > create mode 100644 drivers/net/dsa/lantiq/lantiq_gswip_common.c -- Alexander Sverdlin Siemens AG www.siemens.com ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v3 01/12] net: dsa: lantiq_gswip: split into common and MMIO parts 2025-10-26 23:43 ` [PATCH net-next v3 01/12] net: dsa: lantiq_gswip: split into common and MMIO parts Daniel Golle 2025-10-27 11:37 ` Sverdlin, Alexander @ 2025-10-27 22:22 ` Vladimir Oltean 1 sibling, 0 replies; 35+ messages in thread From: Vladimir Oltean @ 2025-10-27 22:22 UTC (permalink / raw) To: Daniel Golle Cc: Hauke Mehrtens, Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel, Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin On Sun, Oct 26, 2025 at 11:43:54PM +0000, Daniel Golle wrote: > Move all parts specific for the MMIO/SoC driver into a module of its own > to prepare for supporting MDIO-connected switch ICs. > Modify gswip_probe() functions by splitting it into a common function > gswip_probe_common() which covers allocating, initializing and registering > the DSA switch, while keeping transport-specific regmap initialization as > well as PHY firmware loading in the new MMIO/SoC-specific gswip_probe() > function. > > Signed-off-by: Daniel Golle <daniel@makrotopia.org> > --- Reviewed-by: Vladimir Oltean <olteanv@gmail.com> ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH net-next v3 02/12] net: dsa: lantiq_gswip: support enable/disable learning 2025-10-26 23:43 [PATCH net-next v3 00/12] net: dsa: lantiq_gswip: Add support for MaxLinear GSW1xx switch family Daniel Golle 2025-10-26 23:43 ` [PATCH net-next v3 01/12] net: dsa: lantiq_gswip: split into common and MMIO parts Daniel Golle @ 2025-10-26 23:44 ` Daniel Golle 2025-10-27 22:29 ` Vladimir Oltean 2025-10-26 23:44 ` [PATCH net-next v3 03/12] net: dsa: lantiq_gswip: support Energy Efficient Ethernet Daniel Golle ` (9 subsequent siblings) 11 siblings, 1 reply; 35+ messages in thread From: Daniel Golle @ 2025-10-26 23:44 UTC (permalink / raw) To: Hauke Mehrtens, Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel Cc: Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin Switch API 2.2 or later supports enabling or disabling learning on each port. Implement support for BR_LEARNING bridge flag and announce support for BR_LEARNING on GSWIP 2.2 or later. Signed-off-by: Daniel Golle <daniel@makrotopia.org> --- v2: initialize supported flags with 0 drivers/net/dsa/lantiq/lantiq_gswip.h | 3 ++ drivers/net/dsa/lantiq/lantiq_gswip_common.c | 43 ++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/drivers/net/dsa/lantiq/lantiq_gswip.h b/drivers/net/dsa/lantiq/lantiq_gswip.h index d86290db19b4..fb7d2c02bde9 100644 --- a/drivers/net/dsa/lantiq/lantiq_gswip.h +++ b/drivers/net/dsa/lantiq/lantiq_gswip.h @@ -157,6 +157,9 @@ #define GSWIP_PCE_PCTRL_0_PSTATE_LEARNING 0x3 #define GSWIP_PCE_PCTRL_0_PSTATE_FORWARDING 0x7 #define GSWIP_PCE_PCTRL_0_PSTATE_MASK GENMASK(2, 0) +/* Ethernet Switch PCE Port Control Register 3 */ +#define GSWIP_PCE_PCTRL_3p(p) (0x483 + ((p) * 0xA)) +#define GSWIP_PCE_PCTRL_3_LNDIS BIT(15) /* Learning Disable */ #define GSWIP_PCE_VCTRL(p) (0x485 + ((p) * 0xA)) #define GSWIP_PCE_VCTRL_UVR BIT(0) /* Unknown VLAN Rule */ #define GSWIP_PCE_VCTRL_VINR GENMASK(2, 1) /* VLAN Ingress Tag Rule */ diff --git a/drivers/net/dsa/lantiq/lantiq_gswip_common.c b/drivers/net/dsa/lantiq/lantiq_gswip_common.c index a0e361622acb..f130bf6642a7 100644 --- a/drivers/net/dsa/lantiq/lantiq_gswip_common.c +++ b/drivers/net/dsa/lantiq/lantiq_gswip_common.c @@ -403,6 +403,47 @@ static int gswip_add_single_port_br(struct gswip_priv *priv, int port, bool add) return 0; } +static int gswip_port_set_learning(struct gswip_priv *priv, int port, + bool enable) +{ + if (!GSWIP_VERSION_GE(priv, GSWIP_VERSION_2_2)) + return -EOPNOTSUPP; + + /* learning disable bit */ + return regmap_update_bits(priv->gswip, GSWIP_PCE_PCTRL_3p(port), + GSWIP_PCE_PCTRL_3_LNDIS, + enable ? 0 : GSWIP_PCE_PCTRL_3_LNDIS); +} + +static int gswip_port_pre_bridge_flags(struct dsa_switch *ds, int port, + struct switchdev_brport_flags flags, + struct netlink_ext_ack *extack) +{ + struct gswip_priv *priv = ds->priv; + unsigned long supported = 0; + + if (GSWIP_VERSION_GE(priv, GSWIP_VERSION_2_2)) + supported |= BR_LEARNING; + + if (flags.mask & ~supported) + return -EINVAL; + + return 0; +} + +static int gswip_port_bridge_flags(struct dsa_switch *ds, int port, + struct switchdev_brport_flags flags, + struct netlink_ext_ack *extack) +{ + struct gswip_priv *priv = ds->priv; + + if (flags.mask & BR_LEARNING) + return gswip_port_set_learning(priv, port, + !!(flags.val & BR_LEARNING)); + + return 0; +} + static int gswip_port_setup(struct dsa_switch *ds, int port) { struct gswip_priv *priv = ds->priv; @@ -1521,6 +1562,8 @@ static const struct dsa_switch_ops gswip_switch_ops = { .port_setup = gswip_port_setup, .port_enable = gswip_port_enable, .port_disable = gswip_port_disable, + .port_pre_bridge_flags = gswip_port_pre_bridge_flags, + .port_bridge_flags = gswip_port_bridge_flags, .port_bridge_join = gswip_port_bridge_join, .port_bridge_leave = gswip_port_bridge_leave, .port_fast_age = gswip_port_fast_age, -- 2.51.1 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v3 02/12] net: dsa: lantiq_gswip: support enable/disable learning 2025-10-26 23:44 ` [PATCH net-next v3 02/12] net: dsa: lantiq_gswip: support enable/disable learning Daniel Golle @ 2025-10-27 22:29 ` Vladimir Oltean 0 siblings, 0 replies; 35+ messages in thread From: Vladimir Oltean @ 2025-10-27 22:29 UTC (permalink / raw) To: Daniel Golle Cc: Hauke Mehrtens, Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel, Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin On Sun, Oct 26, 2025 at 11:44:03PM +0000, Daniel Golle wrote: > Switch API 2.2 or later supports enabling or disabling learning on each > port. Implement support for BR_LEARNING bridge flag and announce support > for BR_LEARNING on GSWIP 2.2 or later. > > Signed-off-by: Daniel Golle <daniel@makrotopia.org> > --- > v2: initialize supported flags with 0 Reviewed-by: Vladimir Oltean <olteanv@gmail.com> ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH net-next v3 03/12] net: dsa: lantiq_gswip: support Energy Efficient Ethernet 2025-10-26 23:43 [PATCH net-next v3 00/12] net: dsa: lantiq_gswip: Add support for MaxLinear GSW1xx switch family Daniel Golle 2025-10-26 23:43 ` [PATCH net-next v3 01/12] net: dsa: lantiq_gswip: split into common and MMIO parts Daniel Golle 2025-10-26 23:44 ` [PATCH net-next v3 02/12] net: dsa: lantiq_gswip: support enable/disable learning Daniel Golle @ 2025-10-26 23:44 ` Daniel Golle 2025-10-26 23:44 ` [PATCH net-next v3 04/12] net: dsa: lantiq_gswip: set link parameters also for CPU port Daniel Golle ` (8 subsequent siblings) 11 siblings, 0 replies; 35+ messages in thread From: Daniel Golle @ 2025-10-26 23:44 UTC (permalink / raw) To: Hauke Mehrtens, Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel Cc: Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin Introduce support for Energy Efficient Ethernet (EEE) on hardware version 2.2 or later. Signed-off-by: Daniel Golle <daniel@makrotopia.org> Reviewed-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk> --- drivers/net/dsa/lantiq/lantiq_gswip.h | 7 +++ drivers/net/dsa/lantiq/lantiq_gswip_common.c | 47 ++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/drivers/net/dsa/lantiq/lantiq_gswip.h b/drivers/net/dsa/lantiq/lantiq_gswip.h index fb7d2c02bde9..56de869fc472 100644 --- a/drivers/net/dsa/lantiq/lantiq_gswip.h +++ b/drivers/net/dsa/lantiq/lantiq_gswip.h @@ -2,6 +2,7 @@ #ifndef __LANTIQ_GSWIP_H #define __LANTIQ_GSWIP_H +#include <linux/bitfield.h> #include <linux/clk.h> #include <linux/mutex.h> #include <linux/phylink.h> @@ -193,6 +194,12 @@ #define GSWIP_MAC_CTRL_2p(p) (0x905 + ((p) * 0xC)) #define GSWIP_MAC_CTRL_2_LCHKL BIT(2) /* Frame Length Check Long Enable */ #define GSWIP_MAC_CTRL_2_MLEN BIT(3) /* Maximum Untagged Frame Lnegth */ +#define GSWIP_MAC_CTRL_4p(p) (0x907 + ((p) * 0xC)) +#define GSWIP_MAC_CTRL_4_LPIEN BIT(7) /* LPI Mode Enable */ +#define GSWIP_MAC_CTRL_4_GWAIT_MASK GENMASK(14, 8) /* LPI Wait Time 1G */ +#define GSWIP_MAC_CTRL_4_GWAIT(t) u16_encode_bits((t), GSWIP_MAC_CTRL_4_GWAIT_MASK) +#define GSWIP_MAC_CTRL_4_WAIT_MASK GENMASK(6, 0) /* LPI Wait Time 100M */ +#define GSWIP_MAC_CTRL_4_WAIT(t) u16_encode_bits((t), GSWIP_MAC_CTRL_4_WAIT_MASK) /* Ethernet Switch Fetch DMA Port Control Register */ #define GSWIP_FDMA_PCTRLp(p) (0xA80 + ((p) * 0x6)) diff --git a/drivers/net/dsa/lantiq/lantiq_gswip_common.c b/drivers/net/dsa/lantiq/lantiq_gswip_common.c index f130bf6642a7..092187603dea 100644 --- a/drivers/net/dsa/lantiq/lantiq_gswip_common.c +++ b/drivers/net/dsa/lantiq/lantiq_gswip_common.c @@ -1537,6 +1537,49 @@ static int gswip_get_sset_count(struct dsa_switch *ds, int port, int sset) return ARRAY_SIZE(gswip_rmon_cnt); } +static int gswip_set_mac_eee(struct dsa_switch *ds, int port, + struct ethtool_keee *e) +{ + if (e->tx_lpi_timer > 0x7f) + return -EINVAL; + + return 0; +} + +static void gswip_phylink_mac_disable_tx_lpi(struct phylink_config *config) +{ + struct dsa_port *dp = dsa_phylink_to_port(config); + struct gswip_priv *priv = dp->ds->priv; + + regmap_clear_bits(priv->gswip, GSWIP_MAC_CTRL_4p(dp->index), + GSWIP_MAC_CTRL_4_LPIEN); +} + +static int gswip_phylink_mac_enable_tx_lpi(struct phylink_config *config, + u32 timer, bool tx_clock_stop) +{ + struct dsa_port *dp = dsa_phylink_to_port(config); + struct gswip_priv *priv = dp->ds->priv; + + return regmap_update_bits(priv->gswip, GSWIP_MAC_CTRL_4p(dp->index), + GSWIP_MAC_CTRL_4_LPIEN | + GSWIP_MAC_CTRL_4_GWAIT_MASK | + GSWIP_MAC_CTRL_4_WAIT_MASK, + GSWIP_MAC_CTRL_4_LPIEN | + GSWIP_MAC_CTRL_4_GWAIT(timer) | + GSWIP_MAC_CTRL_4_WAIT(timer)); +} + +static bool gswip_support_eee(struct dsa_switch *ds, int port) +{ + struct gswip_priv *priv = ds->priv; + + if (GSWIP_VERSION_GE(priv, GSWIP_VERSION_2_2)) + return true; + + return false; +} + static struct phylink_pcs *gswip_phylink_mac_select_pcs(struct phylink_config *config, phy_interface_t interface) { @@ -1553,6 +1596,8 @@ static const struct phylink_mac_ops gswip_phylink_mac_ops = { .mac_config = gswip_phylink_mac_config, .mac_link_down = gswip_phylink_mac_link_down, .mac_link_up = gswip_phylink_mac_link_up, + .mac_disable_tx_lpi = gswip_phylink_mac_disable_tx_lpi, + .mac_enable_tx_lpi = gswip_phylink_mac_enable_tx_lpi, .mac_select_pcs = gswip_phylink_mac_select_pcs, }; @@ -1580,6 +1625,8 @@ static const struct dsa_switch_ops gswip_switch_ops = { .get_strings = gswip_get_strings, .get_ethtool_stats = gswip_get_ethtool_stats, .get_sset_count = gswip_get_sset_count, + .set_mac_eee = gswip_set_mac_eee, + .support_eee = gswip_support_eee, }; void gswip_disable_switch(struct gswip_priv *priv) -- 2.51.1 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v3 04/12] net: dsa: lantiq_gswip: set link parameters also for CPU port 2025-10-26 23:43 [PATCH net-next v3 00/12] net: dsa: lantiq_gswip: Add support for MaxLinear GSW1xx switch family Daniel Golle ` (2 preceding siblings ...) 2025-10-26 23:44 ` [PATCH net-next v3 03/12] net: dsa: lantiq_gswip: support Energy Efficient Ethernet Daniel Golle @ 2025-10-26 23:44 ` Daniel Golle 2025-10-27 8:03 ` Sverdlin, Alexander 2025-10-27 22:39 ` Vladimir Oltean 2025-10-26 23:44 ` [PATCH net-next v3 05/12] net: dsa: lantiq_gswip: define and use GSWIP_TABLE_MAC_BRIDGE_VAL1_VALID Daniel Golle ` (7 subsequent siblings) 11 siblings, 2 replies; 35+ messages in thread From: Daniel Golle @ 2025-10-26 23:44 UTC (permalink / raw) To: Hauke Mehrtens, Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel Cc: Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin On standalone switch ICs the link parameters of the CPU port need to be setup just like user ports. The destinction in the driver to not carry out link parameter setup for the CPU port does make sense for in-SoC switches on which the CPU port is internally connected to the SoC's Ethernet MAC. Set link parameters also for the CPU port unless it is an internal interface. Signed-off-by: Daniel Golle <daniel@makrotopia.org> --- drivers/net/dsa/lantiq/lantiq_gswip_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/dsa/lantiq/lantiq_gswip_common.c b/drivers/net/dsa/lantiq/lantiq_gswip_common.c index 092187603dea..0ac87eb23bb5 100644 --- a/drivers/net/dsa/lantiq/lantiq_gswip_common.c +++ b/drivers/net/dsa/lantiq/lantiq_gswip_common.c @@ -1459,7 +1459,7 @@ static void gswip_phylink_mac_link_up(struct phylink_config *config, struct gswip_priv *priv = dp->ds->priv; int port = dp->index; - if (!dsa_port_is_cpu(dp)) { + if (!dsa_port_is_cpu(dp) || interface != PHY_INTERFACE_MODE_INTERNAL) { gswip_port_set_link(priv, port, true); gswip_port_set_speed(priv, port, speed, interface); gswip_port_set_duplex(priv, port, duplex); -- 2.51.1 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v3 04/12] net: dsa: lantiq_gswip: set link parameters also for CPU port 2025-10-26 23:44 ` [PATCH net-next v3 04/12] net: dsa: lantiq_gswip: set link parameters also for CPU port Daniel Golle @ 2025-10-27 8:03 ` Sverdlin, Alexander 2025-10-27 22:39 ` Vladimir Oltean 1 sibling, 0 replies; 35+ messages in thread From: Sverdlin, Alexander @ 2025-10-27 8:03 UTC (permalink / raw) To: daniel@makrotopia.org, netdev@vger.kernel.org, linux-kernel@vger.kernel.org Cc: andrew@lunn.ch, olteanv@gmail.com, robh@kernel.org, lxu@maxlinear.com, john@phrozen.org, davem@davemloft.net, yweng@maxlinear.com, bxu@maxlinear.com, edumazet@google.com, pabeni@redhat.com, conor+dt@kernel.org, fchan@maxlinear.com, ajayaraman@maxlinear.com, krzk+dt@kernel.org, hauke@hauke-m.de, horms@kernel.org, kuba@kernel.org, jpovazanec@maxlinear.com, linux@armlinux.org.uk Hi Daniel, On Sun, 2025-10-26 at 23:44 +0000, Daniel Golle wrote: > On standalone switch ICs the link parameters of the CPU port need to > be setup just like user ports. The destinction in the driver to not > carry out link parameter setup for the CPU port does make sense for > in-SoC switches on which the CPU port is internally connected to the > SoC's Ethernet MAC. > Set link parameters also for the CPU port unless it is an internal > interface. > > Signed-off-by: Daniel Golle <daniel@makrotopia.org> thanks for the patch! Reviewed-by: Alexander Sverdlin <alexander.sverdlin@siemens.com> Tested-by: Alexander Sverdlin <alexander.sverdlin@siemens.com> (with GSW145) > --- > drivers/net/dsa/lantiq/lantiq_gswip_common.c | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) > > diff --git a/drivers/net/dsa/lantiq/lantiq_gswip_common.c b/drivers/net/dsa/lantiq/lantiq_gswip_common.c > index 092187603dea..0ac87eb23bb5 100644 > --- a/drivers/net/dsa/lantiq/lantiq_gswip_common.c > +++ b/drivers/net/dsa/lantiq/lantiq_gswip_common.c > @@ -1459,7 +1459,7 @@ static void gswip_phylink_mac_link_up(struct phylink_config *config, > struct gswip_priv *priv = dp->ds->priv; > int port = dp->index; > > - if (!dsa_port_is_cpu(dp)) { > + if (!dsa_port_is_cpu(dp) || interface != PHY_INTERFACE_MODE_INTERNAL) { > gswip_port_set_link(priv, port, true); > gswip_port_set_speed(priv, port, speed, interface); > gswip_port_set_duplex(priv, port, duplex); -- Alexander Sverdlin Siemens AG www.siemens.com ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v3 04/12] net: dsa: lantiq_gswip: set link parameters also for CPU port 2025-10-26 23:44 ` [PATCH net-next v3 04/12] net: dsa: lantiq_gswip: set link parameters also for CPU port Daniel Golle 2025-10-27 8:03 ` Sverdlin, Alexander @ 2025-10-27 22:39 ` Vladimir Oltean 1 sibling, 0 replies; 35+ messages in thread From: Vladimir Oltean @ 2025-10-27 22:39 UTC (permalink / raw) To: Daniel Golle Cc: Hauke Mehrtens, Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel, Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin On Sun, Oct 26, 2025 at 11:44:24PM +0000, Daniel Golle wrote: > On standalone switch ICs the link parameters of the CPU port need to > be setup just like user ports. The destinction in the driver to not > carry out link parameter setup for the CPU port does make sense for > in-SoC switches on which the CPU port is internally connected to the > SoC's Ethernet MAC. > Set link parameters also for the CPU port unless it is an internal > interface. > > Signed-off-by: Daniel Golle <daniel@makrotopia.org> > --- > drivers/net/dsa/lantiq/lantiq_gswip_common.c | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) > > diff --git a/drivers/net/dsa/lantiq/lantiq_gswip_common.c b/drivers/net/dsa/lantiq/lantiq_gswip_common.c > index 092187603dea..0ac87eb23bb5 100644 > --- a/drivers/net/dsa/lantiq/lantiq_gswip_common.c > +++ b/drivers/net/dsa/lantiq/lantiq_gswip_common.c > @@ -1459,7 +1459,7 @@ static void gswip_phylink_mac_link_up(struct phylink_config *config, > struct gswip_priv *priv = dp->ds->priv; > int port = dp->index; > > - if (!dsa_port_is_cpu(dp)) { > + if (!dsa_port_is_cpu(dp) || interface != PHY_INTERFACE_MODE_INTERNAL) { > gswip_port_set_link(priv, port, true); > gswip_port_set_speed(priv, port, speed, interface); > gswip_port_set_duplex(priv, port, duplex); > -- > 2.51.1 PHY_INTERFACE_MODE_INTERNAL has two meanings: an internal MAC-to-MAC connection or an internal PHY. In principle, your check excludes the link parameter setup for CPU ports with internal PHYs. I had to check that these don't exist, by looking at allowed_cpu_ports and seeing that only the MII ports can be CPU ports. So this is OK, although being a bit more explicit in the commit message would help, if you need to resend. Reviewed-by: Vladimir Oltean <olteanv@gmail.com> ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH net-next v3 05/12] net: dsa: lantiq_gswip: define and use GSWIP_TABLE_MAC_BRIDGE_VAL1_VALID 2025-10-26 23:43 [PATCH net-next v3 00/12] net: dsa: lantiq_gswip: Add support for MaxLinear GSW1xx switch family Daniel Golle ` (3 preceding siblings ...) 2025-10-26 23:44 ` [PATCH net-next v3 04/12] net: dsa: lantiq_gswip: set link parameters also for CPU port Daniel Golle @ 2025-10-26 23:44 ` Daniel Golle 2025-10-27 22:47 ` Vladimir Oltean 2025-10-26 23:45 ` [PATCH net-next v3 06/12] dt-bindings: net: dsa: lantiq,gswip: add support for MII delay properties Daniel Golle ` (6 subsequent siblings) 11 siblings, 1 reply; 35+ messages in thread From: Daniel Golle @ 2025-10-26 23:44 UTC (permalink / raw) To: Hauke Mehrtens, Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel Cc: Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin When adding FDB entries to the MAC bridge table it is needed to set an (undocumented) bit to mark the entry as valid. If this bit isn't set for entries in the MAC bridge table, then those entries won't be considered as valid MAC addresses. Signed-off-by: Daniel Golle <daniel@makrotopia.org> --- drivers/net/dsa/lantiq/lantiq_gswip.h | 1 + drivers/net/dsa/lantiq/lantiq_gswip_common.c | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/dsa/lantiq/lantiq_gswip.h b/drivers/net/dsa/lantiq/lantiq_gswip.h index 56de869fc472..42000954d842 100644 --- a/drivers/net/dsa/lantiq/lantiq_gswip.h +++ b/drivers/net/dsa/lantiq/lantiq_gswip.h @@ -224,6 +224,7 @@ #define GSWIP_TABLE_MAC_BRIDGE_KEY3_FID GENMASK(5, 0) /* Filtering identifier */ #define GSWIP_TABLE_MAC_BRIDGE_VAL0_PORT GENMASK(7, 4) /* Port on learned entries */ #define GSWIP_TABLE_MAC_BRIDGE_VAL1_STATIC BIT(0) /* Static, non-aging entry */ +#define GSWIP_TABLE_MAC_BRIDGE_VAL1_VALID BIT(1) /* Valid bit */ #define XRX200_GPHY_FW_ALIGN (16 * 1024) diff --git a/drivers/net/dsa/lantiq/lantiq_gswip_common.c b/drivers/net/dsa/lantiq/lantiq_gswip_common.c index 0ac87eb23bb5..94b187899db6 100644 --- a/drivers/net/dsa/lantiq/lantiq_gswip_common.c +++ b/drivers/net/dsa/lantiq/lantiq_gswip_common.c @@ -1149,7 +1149,8 @@ static int gswip_port_fdb(struct dsa_switch *ds, int port, mac_bridge.key[2] = addr[1] | (addr[0] << 8); mac_bridge.key[3] = FIELD_PREP(GSWIP_TABLE_MAC_BRIDGE_KEY3_FID, fid); mac_bridge.val[0] = add ? BIT(port) : 0; /* port map */ - mac_bridge.val[1] = GSWIP_TABLE_MAC_BRIDGE_VAL1_STATIC; + mac_bridge.val[1] = add ? (GSWIP_TABLE_MAC_BRIDGE_VAL1_STATIC | + GSWIP_TABLE_MAC_BRIDGE_VAL1_VALID) : 0; mac_bridge.valid = add; err = gswip_pce_table_entry_write(priv, &mac_bridge); -- 2.51.1 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v3 05/12] net: dsa: lantiq_gswip: define and use GSWIP_TABLE_MAC_BRIDGE_VAL1_VALID 2025-10-26 23:44 ` [PATCH net-next v3 05/12] net: dsa: lantiq_gswip: define and use GSWIP_TABLE_MAC_BRIDGE_VAL1_VALID Daniel Golle @ 2025-10-27 22:47 ` Vladimir Oltean 0 siblings, 0 replies; 35+ messages in thread From: Vladimir Oltean @ 2025-10-27 22:47 UTC (permalink / raw) To: Daniel Golle Cc: Hauke Mehrtens, Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel, Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin On Sun, Oct 26, 2025 at 11:44:50PM +0000, Daniel Golle wrote: > When adding FDB entries to the MAC bridge table it is needed to set an > (undocumented) bit to mark the entry as valid. If this bit isn't set for > entries in the MAC bridge table, then those entries won't be considered as > valid MAC addresses. Irrespective of GSWIP version? Does this issue have a user visible impact that would justify targeting stable kernels? My reading of the commit description is that the driver can never program static FDB entries? > > Signed-off-by: Daniel Golle <daniel@makrotopia.org> > --- > drivers/net/dsa/lantiq/lantiq_gswip.h | 1 + > drivers/net/dsa/lantiq/lantiq_gswip_common.c | 3 ++- > 2 files changed, 3 insertions(+), 1 deletion(-) > > diff --git a/drivers/net/dsa/lantiq/lantiq_gswip.h b/drivers/net/dsa/lantiq/lantiq_gswip.h > index 56de869fc472..42000954d842 100644 > --- a/drivers/net/dsa/lantiq/lantiq_gswip.h > +++ b/drivers/net/dsa/lantiq/lantiq_gswip.h > @@ -224,6 +224,7 @@ > #define GSWIP_TABLE_MAC_BRIDGE_KEY3_FID GENMASK(5, 0) /* Filtering identifier */ > #define GSWIP_TABLE_MAC_BRIDGE_VAL0_PORT GENMASK(7, 4) /* Port on learned entries */ > #define GSWIP_TABLE_MAC_BRIDGE_VAL1_STATIC BIT(0) /* Static, non-aging entry */ > +#define GSWIP_TABLE_MAC_BRIDGE_VAL1_VALID BIT(1) /* Valid bit */ > > #define XRX200_GPHY_FW_ALIGN (16 * 1024) > > diff --git a/drivers/net/dsa/lantiq/lantiq_gswip_common.c b/drivers/net/dsa/lantiq/lantiq_gswip_common.c > index 0ac87eb23bb5..94b187899db6 100644 > --- a/drivers/net/dsa/lantiq/lantiq_gswip_common.c > +++ b/drivers/net/dsa/lantiq/lantiq_gswip_common.c > @@ -1149,7 +1149,8 @@ static int gswip_port_fdb(struct dsa_switch *ds, int port, > mac_bridge.key[2] = addr[1] | (addr[0] << 8); > mac_bridge.key[3] = FIELD_PREP(GSWIP_TABLE_MAC_BRIDGE_KEY3_FID, fid); > mac_bridge.val[0] = add ? BIT(port) : 0; /* port map */ > - mac_bridge.val[1] = GSWIP_TABLE_MAC_BRIDGE_VAL1_STATIC; > + mac_bridge.val[1] = add ? (GSWIP_TABLE_MAC_BRIDGE_VAL1_STATIC | > + GSWIP_TABLE_MAC_BRIDGE_VAL1_VALID) : 0; > mac_bridge.valid = add; > > err = gswip_pce_table_entry_write(priv, &mac_bridge); > -- > 2.51.1 There is a second change, which is that GSWIP_TABLE_MAC_BRIDGE_VAL1_STATIC no longer gets set when "add=false". If it was deliberate according to the commit message (for example if it appears to not matter, the FDB entry is deleted anyway), it would have been fine, but nothing is said about it, so I have to wonder. ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH net-next v3 06/12] dt-bindings: net: dsa: lantiq,gswip: add support for MII delay properties 2025-10-26 23:43 [PATCH net-next v3 00/12] net: dsa: lantiq_gswip: Add support for MaxLinear GSW1xx switch family Daniel Golle ` (4 preceding siblings ...) 2025-10-26 23:44 ` [PATCH net-next v3 05/12] net: dsa: lantiq_gswip: define and use GSWIP_TABLE_MAC_BRIDGE_VAL1_VALID Daniel Golle @ 2025-10-26 23:45 ` Daniel Golle 2025-10-27 23:04 ` Vladimir Oltean 2025-10-26 23:45 ` [PATCH net-next v3 07/12] net: dsa: lantiq_gswip: allow adjusting MII delays Daniel Golle ` (5 subsequent siblings) 11 siblings, 1 reply; 35+ messages in thread From: Daniel Golle @ 2025-10-26 23:45 UTC (permalink / raw) To: Hauke Mehrtens, Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel Cc: Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin Add support for standard tx-internal-delay-ps and rx-internal-delay-ps properties on port nodes to allow fine-tuning of RGMII clock delays. The GSWIP switch hardware supports delay values in 500 picosecond increments from 0 to 3500 picoseconds, with a default of 2000 picoseconds for both TX and RX delays. This corresponds to the driver changes that allow adjusting MII delays using Device Tree properties instead of relying solely on the PHY interface mode. Signed-off-by: Daniel Golle <daniel@makrotopia.org> --- v3: * redefine ports node so properties are defined actually apply * RGMII port with 2ps delay is 'rgmii-id' mode .../bindings/net/dsa/lantiq,gswip.yaml | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml b/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml index f3154b19af78..b0227b80716c 100644 --- a/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml +++ b/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml @@ -6,8 +6,29 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# title: Lantiq GSWIP Ethernet switches -allOf: - - $ref: dsa.yaml#/$defs/ethernet-ports +$ref: dsa.yaml# + +patternProperties: + "^(ethernet-)?ports$": + type: object + patternProperties: + "^(ethernet-)?port@[0-6]$": + $ref: dsa-port.yaml# + unevaluatedProperties: false + + properties: + tx-internal-delay-ps: + enum: [0, 500, 1000, 1500, 2000, 2500, 3000, 3500] + default: 2000 + description: + RGMII TX Clock Delay defined in pico seconds. + The delay lines adjust the MII clock vs. data timing. + rx-internal-delay-ps: + enum: [0, 500, 1000, 1500, 2000, 2500, 3000, 3500] + default: 2000 + description: + RGMII RX Clock Delay defined in pico seconds. + The delay lines adjust the MII clock vs. data timing. maintainers: - Hauke Mehrtens <hauke@hauke-m.de> @@ -113,8 +134,10 @@ examples: port@0 { reg = <0>; label = "lan3"; - phy-mode = "rgmii"; + phy-mode = "rgmii-id"; phy-handle = <&phy0>; + tx-internal-delay-ps = <2000>; + rx-internal-delay-ps = <2000>; }; port@1 { -- 2.51.1 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v3 06/12] dt-bindings: net: dsa: lantiq,gswip: add support for MII delay properties 2025-10-26 23:45 ` [PATCH net-next v3 06/12] dt-bindings: net: dsa: lantiq,gswip: add support for MII delay properties Daniel Golle @ 2025-10-27 23:04 ` Vladimir Oltean 2025-10-27 23:41 ` Daniel Golle 0 siblings, 1 reply; 35+ messages in thread From: Vladimir Oltean @ 2025-10-27 23:04 UTC (permalink / raw) To: Daniel Golle Cc: Hauke Mehrtens, Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel, Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin On Sun, Oct 26, 2025 at 11:45:19PM +0000, Daniel Golle wrote: > Add support for standard tx-internal-delay-ps and rx-internal-delay-ps > properties on port nodes to allow fine-tuning of RGMII clock delays. > > The GSWIP switch hardware supports delay values in 500 picosecond > increments from 0 to 3500 picoseconds, with a default of 2000 > picoseconds for both TX and RX delays. > > This corresponds to the driver changes that allow adjusting MII delays > using Device Tree properties instead of relying solely on the PHY > interface mode. > > Signed-off-by: Daniel Golle <daniel@makrotopia.org> > --- > v3: > * redefine ports node so properties are defined actually apply > * RGMII port with 2ps delay is 'rgmii-id' mode > > .../bindings/net/dsa/lantiq,gswip.yaml | 29 +++++++++++++++++-- > 1 file changed, 26 insertions(+), 3 deletions(-) > > diff --git a/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml b/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml > index f3154b19af78..b0227b80716c 100644 > --- a/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml > +++ b/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml > @@ -6,8 +6,29 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# > > title: Lantiq GSWIP Ethernet switches > > -allOf: > - - $ref: dsa.yaml#/$defs/ethernet-ports > +$ref: dsa.yaml# > + > +patternProperties: > + "^(ethernet-)?ports$": > + type: object > + patternProperties: > + "^(ethernet-)?port@[0-6]$": > + $ref: dsa-port.yaml# > + unevaluatedProperties: false > + > + properties: > + tx-internal-delay-ps: > + enum: [0, 500, 1000, 1500, 2000, 2500, 3000, 3500] > + default: 2000 No. This is confusing and wrong. I looked at the driver implementation code, wanting to note that it has the potential of being a breaking change for device trees without the "tx-internal-delay-ps" and "rx-internal-delay-ps" properties. But then I saw that the driver implementation is subtly different. "tx-internal-delay-ps" defaults to 2000 only if "rx-internal-delay-ps" is set, and "rx-internal-delay-ps" defaults to 2000 only if "tx-internal-delay-ps" is set. So when implemented in this way, it won't cause the regressions I was concerned about, but it is misrepresented in the schema. Why overcomplicate this and just not set a default? Modify the RX clock skew if set, and the TX clock skew if set. > + description: > + RGMII TX Clock Delay defined in pico seconds. > + The delay lines adjust the MII clock vs. data timing. > + rx-internal-delay-ps: > + enum: [0, 500, 1000, 1500, 2000, 2500, 3000, 3500] > + default: 2000 > + description: > + RGMII RX Clock Delay defined in pico seconds. > + The delay lines adjust the MII clock vs. data timing. > > maintainers: > - Hauke Mehrtens <hauke@hauke-m.de> > @@ -113,8 +134,10 @@ examples: > port@0 { > reg = <0>; > label = "lan3"; > - phy-mode = "rgmii"; > + phy-mode = "rgmii-id"; > phy-handle = <&phy0>; > + tx-internal-delay-ps = <2000>; > + rx-internal-delay-ps = <2000>; > }; > > port@1 { > -- > 2.51.1 ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v3 06/12] dt-bindings: net: dsa: lantiq,gswip: add support for MII delay properties 2025-10-27 23:04 ` Vladimir Oltean @ 2025-10-27 23:41 ` Daniel Golle 2025-10-28 1:39 ` Vladimir Oltean 0 siblings, 1 reply; 35+ messages in thread From: Daniel Golle @ 2025-10-27 23:41 UTC (permalink / raw) To: Vladimir Oltean Cc: Hauke Mehrtens, Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel, Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin On Tue, Oct 28, 2025 at 01:04:39AM +0200, Vladimir Oltean wrote: > On Sun, Oct 26, 2025 at 11:45:19PM +0000, Daniel Golle wrote: > > Add support for standard tx-internal-delay-ps and rx-internal-delay-ps > > properties on port nodes to allow fine-tuning of RGMII clock delays. > > > > The GSWIP switch hardware supports delay values in 500 picosecond > > increments from 0 to 3500 picoseconds, with a default of 2000 > > picoseconds for both TX and RX delays. > > > > This corresponds to the driver changes that allow adjusting MII delays > > using Device Tree properties instead of relying solely on the PHY > > interface mode. > > > > Signed-off-by: Daniel Golle <daniel@makrotopia.org> > > --- > > v3: > > * redefine ports node so properties are defined actually apply > > * RGMII port with 2ps delay is 'rgmii-id' mode > > > > .../bindings/net/dsa/lantiq,gswip.yaml | 29 +++++++++++++++++-- > > 1 file changed, 26 insertions(+), 3 deletions(-) > > > > diff --git a/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml b/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml > > index f3154b19af78..b0227b80716c 100644 > > --- a/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml > > +++ b/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml > > @@ -6,8 +6,29 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# > > > > title: Lantiq GSWIP Ethernet switches > > > > -allOf: > > - - $ref: dsa.yaml#/$defs/ethernet-ports > > +$ref: dsa.yaml# > > + > > +patternProperties: > > + "^(ethernet-)?ports$": > > + type: object > > + patternProperties: > > + "^(ethernet-)?port@[0-6]$": > > + $ref: dsa-port.yaml# > > + unevaluatedProperties: false > > + > > + properties: > > + tx-internal-delay-ps: > > + enum: [0, 500, 1000, 1500, 2000, 2500, 3000, 3500] > > + default: 2000 > > No. This is confusing and wrong. I looked at the driver implementation > code, wanting to note that it has the potential of being a breaking > change for device trees without the "tx-internal-delay-ps" and > "rx-internal-delay-ps" properties. > > But then I saw that the driver implementation is subtly different. > "tx-internal-delay-ps" defaults to 2000 only if "rx-internal-delay-ps" is set, and > "rx-internal-delay-ps" defaults to 2000 only if "tx-internal-delay-ps" is set. > > So when implemented in this way, it won't cause the regressions I was > concerned about, but it is misrepresented in the schema. > > Why overcomplicate this and just not set a default? Modify the RX clock > skew if set, and the TX clock skew if set. The problem is that before adding support for both *-internal-delay-ps properties the internal delays would be set exclusively based on the interface mode -- and are inverted logic: ``` switch (state->interface) { case PHY_INTERFACE_MODE_RGMII_ID: gswip_mii_mask_pcdu(priv, GSWIP_MII_PCDU_TXDLY_MASK | GSWIP_MII_PCDU_RXDLY_MASK, 0, port); break; case PHY_INTERFACE_MODE_RGMII_RXID: gswip_mii_mask_pcdu(priv, GSWIP_MII_PCDU_RXDLY_MASK, 0, port); break; case PHY_INTERFACE_MODE_RGMII_TXID: gswip_mii_mask_pcdu(priv, GSWIP_MII_PCDU_TXDLY_MASK, 0, port); break; default: break; } ``` As you can see the delays are set to 0 in case of the interface mode being RGMII_ID (and the same for RGMII_RXID and RGMII_TXID respectively). This is probably the result of the delays being initialized to 2000ps by default, and if the **PHY connected to the switch port** is set to take care of the clk/data delay then the switch port RGMII interface doesn't have to do it. From my understanding this is a bit awkward as "internal delay" usually means the delay is taken care of by the PHY rather than by discrete parts of the board design. Here, however, it is *never* part of the board design and always handled by either the switch RGMII interface (MAC side) or the connected PHY. So in order to not break existing board device trees expecting this behavior I've decided to only fall-back to adjust the delay based on the interface mode in case both properties are missing. Please correct me if that's the wrong thing to do or if my understanding is flawed in any way. ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v3 06/12] dt-bindings: net: dsa: lantiq,gswip: add support for MII delay properties 2025-10-27 23:41 ` Daniel Golle @ 2025-10-28 1:39 ` Vladimir Oltean 2025-10-28 2:30 ` Daniel Golle 0 siblings, 1 reply; 35+ messages in thread From: Vladimir Oltean @ 2025-10-28 1:39 UTC (permalink / raw) To: Daniel Golle Cc: Hauke Mehrtens, Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel, Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin On Mon, Oct 27, 2025 at 11:41:10PM +0000, Daniel Golle wrote: > On Tue, Oct 28, 2025 at 01:04:39AM +0200, Vladimir Oltean wrote: > > On Sun, Oct 26, 2025 at 11:45:19PM +0000, Daniel Golle wrote: > > > Add support for standard tx-internal-delay-ps and rx-internal-delay-ps > > > properties on port nodes to allow fine-tuning of RGMII clock delays. > > > > > > The GSWIP switch hardware supports delay values in 500 picosecond > > > increments from 0 to 3500 picoseconds, with a default of 2000 > > > picoseconds for both TX and RX delays. > > > > > > This corresponds to the driver changes that allow adjusting MII delays > > > using Device Tree properties instead of relying solely on the PHY > > > interface mode. > > > > > > Signed-off-by: Daniel Golle <daniel@makrotopia.org> > > > --- > > > v3: > > > * redefine ports node so properties are defined actually apply > > > * RGMII port with 2ps delay is 'rgmii-id' mode > > > > > > .../bindings/net/dsa/lantiq,gswip.yaml | 29 +++++++++++++++++-- > > > 1 file changed, 26 insertions(+), 3 deletions(-) > > > > > > diff --git a/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml b/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml > > > index f3154b19af78..b0227b80716c 100644 > > > --- a/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml > > > +++ b/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml > > > @@ -6,8 +6,29 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# > > > > > > title: Lantiq GSWIP Ethernet switches > > > > > > -allOf: > > > - - $ref: dsa.yaml#/$defs/ethernet-ports > > > +$ref: dsa.yaml# > > > + > > > +patternProperties: > > > + "^(ethernet-)?ports$": > > > + type: object > > > + patternProperties: > > > + "^(ethernet-)?port@[0-6]$": > > > + $ref: dsa-port.yaml# > > > + unevaluatedProperties: false > > > + > > > + properties: > > > + tx-internal-delay-ps: > > > + enum: [0, 500, 1000, 1500, 2000, 2500, 3000, 3500] > > > + default: 2000 > > > > No. This is confusing and wrong. I looked at the driver implementation > > code, wanting to note that it has the potential of being a breaking > > change for device trees without the "tx-internal-delay-ps" and > > "rx-internal-delay-ps" properties. > > > > But then I saw that the driver implementation is subtly different. > > "tx-internal-delay-ps" defaults to 2000 only if "rx-internal-delay-ps" is set, and > > "rx-internal-delay-ps" defaults to 2000 only if "tx-internal-delay-ps" is set. > > > > So when implemented in this way, it won't cause the regressions I was > > concerned about, but it is misrepresented in the schema. > > > > Why overcomplicate this and just not set a default? Modify the RX clock > > skew if set, and the TX clock skew if set. > > The problem is that before adding support for both *-internal-delay-ps > properties the internal delays would be set exclusively based on the > interface mode -- and are inverted logic: > > ``` > switch (state->interface) { > case PHY_INTERFACE_MODE_RGMII_ID: > gswip_mii_mask_pcdu(priv, GSWIP_MII_PCDU_TXDLY_MASK | > GSWIP_MII_PCDU_RXDLY_MASK, 0, port); > break; > case PHY_INTERFACE_MODE_RGMII_RXID: > gswip_mii_mask_pcdu(priv, GSWIP_MII_PCDU_RXDLY_MASK, 0, port); > break; > case PHY_INTERFACE_MODE_RGMII_TXID: > gswip_mii_mask_pcdu(priv, GSWIP_MII_PCDU_TXDLY_MASK, 0, port); > break; > default: > break; > } > ``` > > As you can see the delays are set to 0 in case of the interface mode > being RGMII_ID (and the same for RGMII_RXID and RGMII_TXID > respectively). > > This is probably the result of the delays being initialized to 2000ps by > default, and if the **PHY connected to the switch port** is set to take > care of the clk/data delay then the switch port RGMII interface doesn't > have to do it. > > From my understanding this is a bit awkward as "internal delay" usually > means the delay is taken care of by the PHY rather than by discrete > parts of the board design. Here, however, it is *never* part of the > board design and always handled by either the switch RGMII interface > (MAC side) or the connected PHY. > > So in order to not break existing board device trees expecting this > behavior I've decided to only fall-back to adjust the delay based on the > interface mode in case both properties are missing. > > Please correct me if that's the wrong thing to do or if my understanding > is flawed in any way. Ok, I missed the fact that there's RGMII delay handling outside of gswip_mii_delay_setup() too (a bit bizarre). So "why overcomplicate this" has a good reason. You have legacy to maintain for xrx200, xrx300, xrx330 - essentially the same situation as documented in sja1105_parse_rgmii_delays(). But no legacy for the newly introduced switches though? I don't see why you'd opt them into this behaviour of applying MAC delays based on phy-mode. Also, the point still stands that your documented default delay value is incorrect. What happens in lack of one property depends on the presence of the other, and on the phy-mode. I think deleting the default value from the schema is much better than having wrong documentation for it. ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v3 06/12] dt-bindings: net: dsa: lantiq,gswip: add support for MII delay properties 2025-10-28 1:39 ` Vladimir Oltean @ 2025-10-28 2:30 ` Daniel Golle 0 siblings, 0 replies; 35+ messages in thread From: Daniel Golle @ 2025-10-28 2:30 UTC (permalink / raw) To: Vladimir Oltean Cc: Hauke Mehrtens, Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel, Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin On Tue, Oct 28, 2025 at 03:39:44AM +0200, Vladimir Oltean wrote: > On Mon, Oct 27, 2025 at 11:41:10PM +0000, Daniel Golle wrote: > > On Tue, Oct 28, 2025 at 01:04:39AM +0200, Vladimir Oltean wrote: > > > On Sun, Oct 26, 2025 at 11:45:19PM +0000, Daniel Golle wrote: > > > > Add support for standard tx-internal-delay-ps and rx-internal-delay-ps > > > > properties on port nodes to allow fine-tuning of RGMII clock delays. > > > > > > > > The GSWIP switch hardware supports delay values in 500 picosecond > > > > increments from 0 to 3500 picoseconds, with a default of 2000 > > > > picoseconds for both TX and RX delays. > > > > > > > > This corresponds to the driver changes that allow adjusting MII delays > > > > using Device Tree properties instead of relying solely on the PHY > > > > interface mode. > > > > > > > > Signed-off-by: Daniel Golle <daniel@makrotopia.org> > > > > --- > > > > v3: > > > > * redefine ports node so properties are defined actually apply > > > > * RGMII port with 2ps delay is 'rgmii-id' mode > > > > > > > > .../bindings/net/dsa/lantiq,gswip.yaml | 29 +++++++++++++++++-- > > > > 1 file changed, 26 insertions(+), 3 deletions(-) > > > > > > > > diff --git a/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml b/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml > > > > index f3154b19af78..b0227b80716c 100644 > > > > --- a/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml > > > > +++ b/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml > > > > @@ -6,8 +6,29 @@ $schema: http://devicetree.org/meta-schemas/core.yaml# > > > > > > > > title: Lantiq GSWIP Ethernet switches > > > > > > > > -allOf: > > > > - - $ref: dsa.yaml#/$defs/ethernet-ports > > > > +$ref: dsa.yaml# > > > > + > > > > +patternProperties: > > > > + "^(ethernet-)?ports$": > > > > + type: object > > > > + patternProperties: > > > > + "^(ethernet-)?port@[0-6]$": > > > > + $ref: dsa-port.yaml# > > > > + unevaluatedProperties: false > > > > + > > > > + properties: > > > > + tx-internal-delay-ps: > > > > + enum: [0, 500, 1000, 1500, 2000, 2500, 3000, 3500] > > > > + default: 2000 > > > > > > No. This is confusing and wrong. I looked at the driver implementation > > > code, wanting to note that it has the potential of being a breaking > > > change for device trees without the "tx-internal-delay-ps" and > > > "rx-internal-delay-ps" properties. > > > > > > But then I saw that the driver implementation is subtly different. > > > "tx-internal-delay-ps" defaults to 2000 only if "rx-internal-delay-ps" is set, and > > > "rx-internal-delay-ps" defaults to 2000 only if "tx-internal-delay-ps" is set. > > > > > > So when implemented in this way, it won't cause the regressions I was > > > concerned about, but it is misrepresented in the schema. > > > > > > Why overcomplicate this and just not set a default? Modify the RX clock > > > skew if set, and the TX clock skew if set. > > > > The problem is that before adding support for both *-internal-delay-ps > > properties the internal delays would be set exclusively based on the > > interface mode -- and are inverted logic: > > > > ``` > > switch (state->interface) { > > case PHY_INTERFACE_MODE_RGMII_ID: > > gswip_mii_mask_pcdu(priv, GSWIP_MII_PCDU_TXDLY_MASK | > > GSWIP_MII_PCDU_RXDLY_MASK, 0, port); > > break; > > case PHY_INTERFACE_MODE_RGMII_RXID: > > gswip_mii_mask_pcdu(priv, GSWIP_MII_PCDU_RXDLY_MASK, 0, port); > > break; > > case PHY_INTERFACE_MODE_RGMII_TXID: > > gswip_mii_mask_pcdu(priv, GSWIP_MII_PCDU_TXDLY_MASK, 0, port); > > break; > > default: > > break; > > } > > ``` > > > > As you can see the delays are set to 0 in case of the interface mode > > being RGMII_ID (and the same for RGMII_RXID and RGMII_TXID > > respectively). > > > > This is probably the result of the delays being initialized to 2000ps by > > default, and if the **PHY connected to the switch port** is set to take > > care of the clk/data delay then the switch port RGMII interface doesn't > > have to do it. > > > > From my understanding this is a bit awkward as "internal delay" usually > > means the delay is taken care of by the PHY rather than by discrete > > parts of the board design. Here, however, it is *never* part of the > > board design and always handled by either the switch RGMII interface > > (MAC side) or the connected PHY. > > > > So in order to not break existing board device trees expecting this > > behavior I've decided to only fall-back to adjust the delay based on the > > interface mode in case both properties are missing. > > > > Please correct me if that's the wrong thing to do or if my understanding > > is flawed in any way. > > Ok, I missed the fact that there's RGMII delay handling outside of > gswip_mii_delay_setup() too (a bit bizarre). > > So "why overcomplicate this" has a good reason. You have legacy to > maintain for xrx200, xrx300, xrx330 - essentially the same situation as > documented in sja1105_parse_rgmii_delays(). But no legacy for the newly > introduced switches though? I don't see why you'd opt them into this > behaviour of applying MAC delays based on phy-mode. The answer here is also simple: because it would make the common part of the driver to draw an artificial line between older in-SoC switches and newer standalone chips, though the RGMII interface itself is exactly the same and hence part of the lantiq_gswip_common library module. I know that this reason alone doesn't fly with DT maintainers, which is why I thought that allowing a modern (ie. using the {rx,tx}-internal-delay-ps properties) way to set clk vs. data delays for both new and old hardware would be the best. Do you think doing an opt-out of the new hardware from this behavior is worth persuing? (it would obviously make both the bindings as well as the driver itself more complex in order to achieve that) > > Also, the point still stands that your documented default delay value > is incorrect. What happens in lack of one property depends on the > presence of the other, and on the phy-mode. I think deleting the > default value from the schema is much better than having wrong > documentation for it. To me that sounds like a good compromise. The defaults are correct, but only if at least one of the two properties are present or if the reset value if kept in case of PHY_INTERFACE_MODE_RGMII (which doesn't zero either of the delays). I could also harmonize the situation in the driver by always starting with the default values selected in the legacy way (ie. based on the interface mode) and allow overriding that using the modern properties. Does that sound better than the current behavior? ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH net-next v3 07/12] net: dsa: lantiq_gswip: allow adjusting MII delays 2025-10-26 23:43 [PATCH net-next v3 00/12] net: dsa: lantiq_gswip: Add support for MaxLinear GSW1xx switch family Daniel Golle ` (5 preceding siblings ...) 2025-10-26 23:45 ` [PATCH net-next v3 06/12] dt-bindings: net: dsa: lantiq,gswip: add support for MII delay properties Daniel Golle @ 2025-10-26 23:45 ` Daniel Golle 2025-10-26 23:47 ` [PATCH net-next v3 08/12] dt-bindings: net: dsa: lantiq,gswip: add MaxLinear RMII refclk output property Daniel Golle ` (4 subsequent siblings) 11 siblings, 0 replies; 35+ messages in thread From: Daniel Golle @ 2025-10-26 23:45 UTC (permalink / raw) To: Hauke Mehrtens, Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel Cc: Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin Currently the MII clk vs. data delay is configured based on the PHY interface mode. In addition to that add support for setting up MII delays using the standard Device Tree properties 'tx-internal-delay-ps' and 'rx-internal-delay-ps' and only fall back to using the PHY interface mode in case both properties are unused. Signed-off-by: Daniel Golle <daniel@makrotopia.org> --- drivers/net/dsa/lantiq/lantiq_gswip.h | 4 +++ drivers/net/dsa/lantiq/lantiq_gswip_common.c | 31 ++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/drivers/net/dsa/lantiq/lantiq_gswip.h b/drivers/net/dsa/lantiq/lantiq_gswip.h index 42000954d842..0c32ec85e127 100644 --- a/drivers/net/dsa/lantiq/lantiq_gswip.h +++ b/drivers/net/dsa/lantiq/lantiq_gswip.h @@ -82,6 +82,10 @@ #define GSWIP_MII_PCDU5 0x05 #define GSWIP_MII_PCDU_TXDLY_MASK GENMASK(2, 0) #define GSWIP_MII_PCDU_RXDLY_MASK GENMASK(9, 7) +#define GSWIP_MII_PCDU_TXDLY(x) u16_encode_bits(((x) / 500), GSWIP_MII_PCDU_TXDLY_MASK) +#define GSWIP_MII_PCDU_RXDLY(x) u16_encode_bits(((x) / 500), GSWIP_MII_PCDU_RXDLY_MASK) +#define GSWIP_MII_PCDU_RXDLY_DEFAULT 2000 /* picoseconds */ +#define GSWIP_MII_PCDU_TXDLY_DEFAULT 2000 /* picoseconds */ /* GSWIP Core Registers */ #define GSWIP_SWRES 0x000 diff --git a/drivers/net/dsa/lantiq/lantiq_gswip_common.c b/drivers/net/dsa/lantiq/lantiq_gswip_common.c index 94b187899db6..60a83093cd10 100644 --- a/drivers/net/dsa/lantiq/lantiq_gswip_common.c +++ b/drivers/net/dsa/lantiq/lantiq_gswip_common.c @@ -622,6 +622,34 @@ static int gswip_port_vlan_filtering(struct dsa_switch *ds, int port, return 0; } +static bool 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; + bool used; + int ret; + + ret = of_property_read_u32(port_dn, "tx-internal-delay-ps", &tx_delay); + if (ret && ret != -EINVAL) + return ret; + used = !ret; + + ret = of_property_read_u32(port_dn, "rx-internal-delay-ps", &rx_delay); + if (ret && ret != -EINVAL) + return ret; + used |= !ret; + + if (used) + gswip_mii_mask_pcdu(priv, GSWIP_MII_PCDU_TXDLY_MASK | + GSWIP_MII_PCDU_RXDLY_MASK, + GSWIP_MII_PCDU_TXDLY(tx_delay) | + GSWIP_MII_PCDU_RXDLY(rx_delay), + dp->index); + + return used; +} + static int gswip_setup(struct dsa_switch *ds) { unsigned int cpu_ports = dsa_cpu_ports(ds); @@ -1419,6 +1447,9 @@ static void gswip_phylink_mac_config(struct phylink_config *config, GSWIP_MII_CFG_RGMII_IBS | GSWIP_MII_CFG_LDCLKDIS, miicfg, port); + if (gswip_mii_delay_setup(priv, dp)) + return; + switch (state->interface) { case PHY_INTERFACE_MODE_RGMII_ID: gswip_mii_mask_pcdu(priv, GSWIP_MII_PCDU_TXDLY_MASK | -- 2.51.1 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH net-next v3 08/12] dt-bindings: net: dsa: lantiq,gswip: add MaxLinear RMII refclk output property 2025-10-26 23:43 [PATCH net-next v3 00/12] net: dsa: lantiq_gswip: Add support for MaxLinear GSW1xx switch family Daniel Golle ` (6 preceding siblings ...) 2025-10-26 23:45 ` [PATCH net-next v3 07/12] net: dsa: lantiq_gswip: allow adjusting MII delays Daniel Golle @ 2025-10-26 23:47 ` Daniel Golle 2025-10-27 9:41 ` Sverdlin, Alexander 2025-10-26 23:47 ` [PATCH net-next v3 09/12] net: dsa: lantiq_gswip: add vendor property to setup MII refclk output Daniel Golle ` (3 subsequent siblings) 11 siblings, 1 reply; 35+ messages in thread From: Daniel Golle @ 2025-10-26 23:47 UTC (permalink / raw) To: Hauke Mehrtens, Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel Cc: Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin Add support for the maxlinear,rmii-refclk-out boolean property on port nodes to configure the RMII reference clock to be an output rather than an input. This property is only applicable for ports using RMII and allows the switch to provide the reference clock for RMII-connected PHYs instead of requiring an external clock source. Signed-off-by: Daniel Golle <daniel@makrotopia.org> --- Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml b/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml index b0227b80716c..dd3858bad8ca 100644 --- a/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml +++ b/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml @@ -29,6 +29,11 @@ patternProperties: description: RGMII RX Clock Delay defined in pico seconds. The delay lines adjust the MII clock vs. data timing. + maxlinear,rmii-refclk-out: + type: boolean + description: + Configure the RMII reference clock to be a clock output + rather than an input. Only applicable for RMII mode. maintainers: - Hauke Mehrtens <hauke@hauke-m.de> -- 2.51.1 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v3 08/12] dt-bindings: net: dsa: lantiq,gswip: add MaxLinear RMII refclk output property 2025-10-26 23:47 ` [PATCH net-next v3 08/12] dt-bindings: net: dsa: lantiq,gswip: add MaxLinear RMII refclk output property Daniel Golle @ 2025-10-27 9:41 ` Sverdlin, Alexander 0 siblings, 0 replies; 35+ messages in thread From: Sverdlin, Alexander @ 2025-10-27 9:41 UTC (permalink / raw) To: daniel@makrotopia.org, devicetree@vger.kernel.org, netdev@vger.kernel.org, linux-kernel@vger.kernel.org Cc: andrew@lunn.ch, olteanv@gmail.com, robh@kernel.org, lxu@maxlinear.com, john@phrozen.org, davem@davemloft.net, yweng@maxlinear.com, bxu@maxlinear.com, edumazet@google.com, pabeni@redhat.com, conor+dt@kernel.org, fchan@maxlinear.com, ajayaraman@maxlinear.com, krzk+dt@kernel.org, hauke@hauke-m.de, horms@kernel.org, kuba@kernel.org, jpovazanec@maxlinear.com, linux@armlinux.org.uk Hi Daniel, On Sun, 2025-10-26 at 23:47 +0000, Daniel Golle wrote: > Add support for the maxlinear,rmii-refclk-out boolean property on port > nodes to configure the RMII reference clock to be an output rather than > an input. > > This property is only applicable for ports using RMII and allows the > switch to provide the reference clock for RMII-connected PHYs instead > of requiring an external clock source. > > Signed-off-by: Daniel Golle <daniel@makrotopia.org> Reviewed-by: Alexander Sverdlin <alexander.sverdlin@siemens.com> > --- > Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml | 5 +++++ > 1 file changed, 5 insertions(+) > > diff --git a/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml b/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml > index b0227b80716c..dd3858bad8ca 100644 > --- a/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml > +++ b/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml > @@ -29,6 +29,11 @@ patternProperties: > description: > RGMII RX Clock Delay defined in pico seconds. > The delay lines adjust the MII clock vs. data timing. > + maxlinear,rmii-refclk-out: > + type: boolean > + description: > + Configure the RMII reference clock to be a clock output > + rather than an input. Only applicable for RMII mode. -- Alexander Sverdlin Siemens AG www.siemens.com ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH net-next v3 09/12] net: dsa: lantiq_gswip: add vendor property to setup MII refclk output 2025-10-26 23:43 [PATCH net-next v3 00/12] net: dsa: lantiq_gswip: Add support for MaxLinear GSW1xx switch family Daniel Golle ` (7 preceding siblings ...) 2025-10-26 23:47 ` [PATCH net-next v3 08/12] dt-bindings: net: dsa: lantiq,gswip: add MaxLinear RMII refclk output property Daniel Golle @ 2025-10-26 23:47 ` Daniel Golle 2025-10-27 11:39 ` Sverdlin, Alexander 2025-10-27 23:36 ` Vladimir Oltean 2025-10-26 23:48 ` [PATCH net-next v3 10/12] dt-bindings: net: dsa: lantiq,gswip: add support for MaxLinear GSW1xx switches Daniel Golle ` (2 subsequent siblings) 11 siblings, 2 replies; 35+ messages in thread From: Daniel Golle @ 2025-10-26 23:47 UTC (permalink / raw) To: Hauke Mehrtens, Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel Cc: Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin Read boolean Device Tree property "maxlinear,rmii-refclk-out" and switch the RMII reference clock to be a clock output rather than an input if it is set. Signed-off-by: Daniel Golle <daniel@makrotopia.org> --- drivers/net/dsa/lantiq/lantiq_gswip_common.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/dsa/lantiq/lantiq_gswip_common.c b/drivers/net/dsa/lantiq/lantiq_gswip_common.c index 60a83093cd10..bf38ecc13f76 100644 --- a/drivers/net/dsa/lantiq/lantiq_gswip_common.c +++ b/drivers/net/dsa/lantiq/lantiq_gswip_common.c @@ -1442,6 +1442,10 @@ static void gswip_phylink_mac_config(struct phylink_config *config, return; } + if (of_property_read_bool(dp->dn, "maxlinear,rmii-refclk-out") && + !(miicfg & GSWIP_MII_CFG_MODE_RGMII)) + miicfg |= GSWIP_MII_CFG_RMII_CLK; + gswip_mii_mask_cfg(priv, GSWIP_MII_CFG_MODE_MASK | GSWIP_MII_CFG_RMII_CLK | GSWIP_MII_CFG_RGMII_IBS | GSWIP_MII_CFG_LDCLKDIS, -- 2.51.1 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v3 09/12] net: dsa: lantiq_gswip: add vendor property to setup MII refclk output 2025-10-26 23:47 ` [PATCH net-next v3 09/12] net: dsa: lantiq_gswip: add vendor property to setup MII refclk output Daniel Golle @ 2025-10-27 11:39 ` Sverdlin, Alexander 2025-10-27 23:36 ` Vladimir Oltean 1 sibling, 0 replies; 35+ messages in thread From: Sverdlin, Alexander @ 2025-10-27 11:39 UTC (permalink / raw) To: daniel@makrotopia.org, devicetree@vger.kernel.org, netdev@vger.kernel.org, linux-kernel@vger.kernel.org Cc: andrew@lunn.ch, olteanv@gmail.com, robh@kernel.org, lxu@maxlinear.com, john@phrozen.org, davem@davemloft.net, yweng@maxlinear.com, bxu@maxlinear.com, edumazet@google.com, pabeni@redhat.com, conor+dt@kernel.org, fchan@maxlinear.com, ajayaraman@maxlinear.com, krzk+dt@kernel.org, hauke@hauke-m.de, horms@kernel.org, kuba@kernel.org, jpovazanec@maxlinear.com, linux@armlinux.org.uk Hi Daniel, On Sun, 2025-10-26 at 23:47 +0000, Daniel Golle wrote: > Read boolean Device Tree property "maxlinear,rmii-refclk-out" and switch > the RMII reference clock to be a clock output rather than an input if it > is set. > > Signed-off-by: Daniel Golle <daniel@makrotopia.org> thanks for the patch! Reviewed-by: Alexander Sverdlin <alexander.sverdlin@siemens.com> Tested-by: Alexander Sverdlin <alexander.sverdlin@siemens.com> (with GSW145) > --- > drivers/net/dsa/lantiq/lantiq_gswip_common.c | 4 ++++ > 1 file changed, 4 insertions(+) > > diff --git a/drivers/net/dsa/lantiq/lantiq_gswip_common.c b/drivers/net/dsa/lantiq/lantiq_gswip_common.c > index 60a83093cd10..bf38ecc13f76 100644 > --- a/drivers/net/dsa/lantiq/lantiq_gswip_common.c > +++ b/drivers/net/dsa/lantiq/lantiq_gswip_common.c > @@ -1442,6 +1442,10 @@ static void gswip_phylink_mac_config(struct phylink_config *config, > return; > } > > + if (of_property_read_bool(dp->dn, "maxlinear,rmii-refclk-out") && > + !(miicfg & GSWIP_MII_CFG_MODE_RGMII)) > + miicfg |= GSWIP_MII_CFG_RMII_CLK; > + -- Alexander Sverdlin Siemens AG www.siemens.com ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v3 09/12] net: dsa: lantiq_gswip: add vendor property to setup MII refclk output 2025-10-26 23:47 ` [PATCH net-next v3 09/12] net: dsa: lantiq_gswip: add vendor property to setup MII refclk output Daniel Golle 2025-10-27 11:39 ` Sverdlin, Alexander @ 2025-10-27 23:36 ` Vladimir Oltean 2025-10-27 23:48 ` Daniel Golle 1 sibling, 1 reply; 35+ messages in thread From: Vladimir Oltean @ 2025-10-27 23:36 UTC (permalink / raw) To: Daniel Golle Cc: Hauke Mehrtens, Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel, Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin On Sun, Oct 26, 2025 at 11:47:21PM +0000, Daniel Golle wrote: > Read boolean Device Tree property "maxlinear,rmii-refclk-out" and switch > the RMII reference clock to be a clock output rather than an input if it > is set. > > Signed-off-by: Daniel Golle <daniel@makrotopia.org> > --- > drivers/net/dsa/lantiq/lantiq_gswip_common.c | 4 ++++ > 1 file changed, 4 insertions(+) > > diff --git a/drivers/net/dsa/lantiq/lantiq_gswip_common.c b/drivers/net/dsa/lantiq/lantiq_gswip_common.c > index 60a83093cd10..bf38ecc13f76 100644 > --- a/drivers/net/dsa/lantiq/lantiq_gswip_common.c > +++ b/drivers/net/dsa/lantiq/lantiq_gswip_common.c > @@ -1442,6 +1442,10 @@ static void gswip_phylink_mac_config(struct phylink_config *config, > return; > } > > + if (of_property_read_bool(dp->dn, "maxlinear,rmii-refclk-out") && > + !(miicfg & GSWIP_MII_CFG_MODE_RGMII)) > + miicfg |= GSWIP_MII_CFG_RMII_CLK; > + What did you mean with the !(miicfg & GSWIP_MII_CFG_MODE_RGMII) test? If the schema says "Only applicable for RMII mode.", what's the purpose of this extra condition? For example, GSWIP_MII_CFG_MODE_GMII also has the "GSWIP_MII_CFG_MODE_RGMII" bit (0x4) unset. Does this have any significance? > gswip_mii_mask_cfg(priv, > GSWIP_MII_CFG_MODE_MASK | GSWIP_MII_CFG_RMII_CLK | > GSWIP_MII_CFG_RGMII_IBS | GSWIP_MII_CFG_LDCLKDIS, > -- > 2.51.1 ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v3 09/12] net: dsa: lantiq_gswip: add vendor property to setup MII refclk output 2025-10-27 23:36 ` Vladimir Oltean @ 2025-10-27 23:48 ` Daniel Golle 2025-10-28 1:44 ` Vladimir Oltean 0 siblings, 1 reply; 35+ messages in thread From: Daniel Golle @ 2025-10-27 23:48 UTC (permalink / raw) To: Vladimir Oltean Cc: Hauke Mehrtens, Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel, Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin On Tue, Oct 28, 2025 at 01:36:26AM +0200, Vladimir Oltean wrote: > On Sun, Oct 26, 2025 at 11:47:21PM +0000, Daniel Golle wrote: > > Read boolean Device Tree property "maxlinear,rmii-refclk-out" and switch > > the RMII reference clock to be a clock output rather than an input if it > > is set. > > > > Signed-off-by: Daniel Golle <daniel@makrotopia.org> > > --- > > drivers/net/dsa/lantiq/lantiq_gswip_common.c | 4 ++++ > > 1 file changed, 4 insertions(+) > > > > diff --git a/drivers/net/dsa/lantiq/lantiq_gswip_common.c b/drivers/net/dsa/lantiq/lantiq_gswip_common.c > > index 60a83093cd10..bf38ecc13f76 100644 > > --- a/drivers/net/dsa/lantiq/lantiq_gswip_common.c > > +++ b/drivers/net/dsa/lantiq/lantiq_gswip_common.c > > @@ -1442,6 +1442,10 @@ static void gswip_phylink_mac_config(struct phylink_config *config, > > return; > > } > > > > + if (of_property_read_bool(dp->dn, "maxlinear,rmii-refclk-out") && > > + !(miicfg & GSWIP_MII_CFG_MODE_RGMII)) > > + miicfg |= GSWIP_MII_CFG_RMII_CLK; > > + > > What did you mean with the !(miicfg & GSWIP_MII_CFG_MODE_RGMII) test? > If the schema says "Only applicable for RMII mode.", what's the purpose > of this extra condition? For example, GSWIP_MII_CFG_MODE_GMII also has > the "GSWIP_MII_CFG_MODE_RGMII" bit (0x4) unset. Does this have any significance? You are right, probably the best would be to test (if at all) that (miicfg == GSWIP_MII_CFG_MODE_RMIIM || miicfg == GSWIP_MII_CFG_MODE_RMIIP) and only in this case allow setting the GSWIP_MII_CFG_RMII_CLK bit. I forgot that there is older hardware which supports "full" MII, and MII MAC as well as MII PHY modes also shouldn't allow to set the GSWIP_MII_CFG_RMII_CLK bit to not end up with undefined behavior. ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v3 09/12] net: dsa: lantiq_gswip: add vendor property to setup MII refclk output 2025-10-27 23:48 ` Daniel Golle @ 2025-10-28 1:44 ` Vladimir Oltean 0 siblings, 0 replies; 35+ messages in thread From: Vladimir Oltean @ 2025-10-28 1:44 UTC (permalink / raw) To: Daniel Golle Cc: Hauke Mehrtens, Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel, Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin On Mon, Oct 27, 2025 at 11:48:26PM +0000, Daniel Golle wrote: > On Tue, Oct 28, 2025 at 01:36:26AM +0200, Vladimir Oltean wrote: > > On Sun, Oct 26, 2025 at 11:47:21PM +0000, Daniel Golle wrote: > > > Read boolean Device Tree property "maxlinear,rmii-refclk-out" and switch > > > the RMII reference clock to be a clock output rather than an input if it > > > is set. > > > > > > Signed-off-by: Daniel Golle <daniel@makrotopia.org> > > > --- > > > drivers/net/dsa/lantiq/lantiq_gswip_common.c | 4 ++++ > > > 1 file changed, 4 insertions(+) > > > > > > diff --git a/drivers/net/dsa/lantiq/lantiq_gswip_common.c b/drivers/net/dsa/lantiq/lantiq_gswip_common.c > > > index 60a83093cd10..bf38ecc13f76 100644 > > > --- a/drivers/net/dsa/lantiq/lantiq_gswip_common.c > > > +++ b/drivers/net/dsa/lantiq/lantiq_gswip_common.c > > > @@ -1442,6 +1442,10 @@ static void gswip_phylink_mac_config(struct phylink_config *config, > > > return; > > > } > > > > > > + if (of_property_read_bool(dp->dn, "maxlinear,rmii-refclk-out") && > > > + !(miicfg & GSWIP_MII_CFG_MODE_RGMII)) > > > + miicfg |= GSWIP_MII_CFG_RMII_CLK; > > > + > > > > What did you mean with the !(miicfg & GSWIP_MII_CFG_MODE_RGMII) test? > > If the schema says "Only applicable for RMII mode.", what's the purpose > > of this extra condition? For example, GSWIP_MII_CFG_MODE_GMII also has > > the "GSWIP_MII_CFG_MODE_RGMII" bit (0x4) unset. Does this have any significance? > > You are right, probably the best would be to test (if at all) that > (miicfg == GSWIP_MII_CFG_MODE_RMIIM || miicfg == > GSWIP_MII_CFG_MODE_RMIIP) and only in this case allow setting the > GSWIP_MII_CFG_RMII_CLK bit. > > I forgot that there is older hardware which supports "full" MII, and MII > MAC as well as MII PHY modes also shouldn't allow to set the > GSWIP_MII_CFG_RMII_CLK bit to not end up with undefined behavior. Yeah, actually you'd be looking at FIELD_GET(GSWIP_MII_CFG_MODE_MASK, miicfg) rather than miicfg directly. If the schema restricted "maxlinear,rmii-refclk-out" to be used only in combination with phy-mode = "rmii" and "rev-rmii", in theory that should be sufficient with no further driver checks. But some checks that at least make sense don't seem to hurt. ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH net-next v3 10/12] dt-bindings: net: dsa: lantiq,gswip: add support for MaxLinear GSW1xx switches 2025-10-26 23:43 [PATCH net-next v3 00/12] net: dsa: lantiq_gswip: Add support for MaxLinear GSW1xx switch family Daniel Golle ` (8 preceding siblings ...) 2025-10-26 23:47 ` [PATCH net-next v3 09/12] net: dsa: lantiq_gswip: add vendor property to setup MII refclk output Daniel Golle @ 2025-10-26 23:48 ` Daniel Golle 2025-10-28 0:09 ` Vladimir Oltean 2025-10-26 23:48 ` [PATCH net-next v3 11/12] net: dsa: add tagging driver for MaxLinear GSW1xx switch family Daniel Golle 2025-10-26 23:49 ` [PATCH net-next v3 12/12] net: dsa: add " Daniel Golle 11 siblings, 1 reply; 35+ messages in thread From: Daniel Golle @ 2025-10-26 23:48 UTC (permalink / raw) To: Hauke Mehrtens, Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel Cc: Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin Extend the Lantiq GSWIP device tree binding to also cover MaxLinear GSW1xx switches which are based on the same hardware IP but connected via MDIO instead of being memory-mapped. Add compatible strings for MaxLinear GSW120, GSW125, GSW140, GSW141, and GSW145 switches and adjust the schema to handle the different connection methods with conditional properties. Add MaxLinear GSW125 example showing MDIO-connected configuration. Signed-off-by: Daniel Golle <daniel@makrotopia.org> --- v3: * add maxlinear,rx-inverted and maxlinear,tx-inverted properties v2: * remove git conflict left-overs which somehow creeped in * indent example with 4 spaces instead of tabs .../bindings/net/dsa/lantiq,gswip.yaml | 275 +++++++++++++----- 1 file changed, 202 insertions(+), 73 deletions(-) diff --git a/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml b/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml index dd3858bad8ca..1148fdd0b6bc 100644 --- a/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml +++ b/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml @@ -4,7 +4,12 @@ $id: http://devicetree.org/schemas/net/dsa/lantiq,gswip.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Lantiq GSWIP Ethernet switches +title: Lantiq GSWIP and MaxLinear GSW1xx Ethernet switches + +description: + Lantiq GSWIP and MaxLinear GSW1xx switches share the same hardware IP. + Lantiq switches are embedded in SoCs and accessed via memory-mapped I/O, + while MaxLinear switches are standalone ICs connected via MDIO. $ref: dsa.yaml# @@ -34,6 +39,108 @@ patternProperties: description: Configure the RMII reference clock to be a clock output rather than an input. Only applicable for RMII mode. + maxlinear,rx-inverted: + type: boolean + description: + Enable RX polarity inversion for SerDes port. + maxlinear,tx-inverted: + type: boolean + description: + Enable TX polarity inversion for SerDes port. + +allOf: + - if: + properties: + compatible: + contains: + enum: + - lantiq,xrx200-gswip + - lantiq,xrx300-gswip + - lantiq,xrx330-gswip + then: + properties: + reg: + minItems: 3 + maxItems: 3 + description: Memory-mapped register regions (switch, mdio, mii) + reg-names: + items: + - const: switch + - const: mdio + - const: mii + mdio: + $ref: /schemas/net/mdio.yaml# + unevaluatedProperties: false + + properties: + compatible: + const: lantiq,xrx200-mdio + + required: + - compatible + gphy-fw: + type: object + properties: + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + compatible: + items: + - enum: + - lantiq,xrx200-gphy-fw + - lantiq,xrx300-gphy-fw + - lantiq,xrx330-gphy-fw + - const: lantiq,gphy-fw + + lantiq,rcu: + $ref: /schemas/types.yaml#/definitions/phandle + description: phandle to the RCU syscon + + patternProperties: + "^gphy@[0-9a-f]{1,2}$": + type: object + + additionalProperties: false + + properties: + reg: + minimum: 0 + maximum: 255 + description: + Offset of the GPHY firmware register in the RCU register + range + + resets: + items: + - description: GPHY reset line + + reset-names: + items: + - const: gphy + + required: + - reg + + required: + - compatible + - lantiq,rcu + + additionalProperties: false + required: + - reg-names + else: + properties: + reg: + maxItems: 1 + description: MDIO bus address + reg-names: false + gphy-fw: false + mdio: + $ref: /schemas/net/mdio.yaml# + unevaluatedProperties: false maintainers: - Hauke Mehrtens <hauke@hauke-m.de> @@ -44,78 +151,11 @@ properties: - lantiq,xrx200-gswip - lantiq,xrx300-gswip - lantiq,xrx330-gswip - - reg: - minItems: 3 - maxItems: 3 - - reg-names: - items: - - const: switch - - const: mdio - - const: mii - - mdio: - $ref: /schemas/net/mdio.yaml# - unevaluatedProperties: false - - properties: - compatible: - const: lantiq,xrx200-mdio - - required: - - compatible - - gphy-fw: - type: object - properties: - '#address-cells': - const: 1 - - '#size-cells': - const: 0 - - compatible: - items: - - enum: - - lantiq,xrx200-gphy-fw - - lantiq,xrx300-gphy-fw - - lantiq,xrx330-gphy-fw - - const: lantiq,gphy-fw - - lantiq,rcu: - $ref: /schemas/types.yaml#/definitions/phandle - description: phandle to the RCU syscon - - patternProperties: - "^gphy@[0-9a-f]{1,2}$": - type: object - - additionalProperties: false - - properties: - reg: - minimum: 0 - maximum: 255 - description: - Offset of the GPHY firmware register in the RCU register range - - resets: - items: - - description: GPHY reset line - - reset-names: - items: - - const: gphy - - required: - - reg - - required: - - compatible - - lantiq,rcu - - additionalProperties: false + - maxlinear,gsw120 + - maxlinear,gsw125 + - maxlinear,gsw140 + - maxlinear,gsw141 + - maxlinear,gsw145 required: - compatible @@ -130,6 +170,7 @@ examples: reg = <0xe108000 0x3100>, /* switch */ <0xe10b100 0xd8>, /* mdio */ <0xe10b1d8 0x130>; /* mii */ + reg-names = "switch", "mdio", "mii"; dsa,member = <0 0>; ports { @@ -228,3 +269,91 @@ examples: }; }; }; + + - | + #include <dt-bindings/leds/common.h> + + mdio { + #address-cells = <1>; + #size-cells = <0>; + + switch@1f { + compatible = "maxlinear,gsw125"; + reg = <0x1f>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + label = "lan0"; + phy-handle = <&switchphy0>; + phy-mode = "internal"; + }; + + port@1 { + reg = <1>; + label = "lan1"; + phy-handle = <&switchphy1>; + phy-mode = "internal"; + }; + + port@4 { + reg = <4>; + label = "wan"; + phy-mode = "1000base-x"; + maxlinear,rx-inverted; + managed = "in-band-status"; + }; + + port@5 { + reg = <5>; + phy-mode = "rgmii-id"; + tx-internal-delay-ps = <2000>; + rx-internal-delay-ps = <2000>; + ethernet = <ð0>; + + fixed-link { + speed = <1000>; + full-duplex; + }; + }; + }; + + mdio { + #address-cells = <1>; + #size-cells = <0>; + + switchphy0: switchphy@0 { + reg = <0>; + + leds { + #address-cells = <1>; + #size-cells = <0>; + + led@0 { + reg = <0>; + color = <LED_COLOR_ID_GREEN>; + function = LED_FUNCTION_LAN; + }; + }; + }; + + switchphy1: switchphy@1 { + reg = <1>; + + leds { + #address-cells = <1>; + #size-cells = <0>; + + led@0 { + reg = <0>; + color = <LED_COLOR_ID_GREEN>; + function = LED_FUNCTION_LAN; + }; + }; + }; + }; + }; + }; -- 2.51.1 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v3 10/12] dt-bindings: net: dsa: lantiq,gswip: add support for MaxLinear GSW1xx switches 2025-10-26 23:48 ` [PATCH net-next v3 10/12] dt-bindings: net: dsa: lantiq,gswip: add support for MaxLinear GSW1xx switches Daniel Golle @ 2025-10-28 0:09 ` Vladimir Oltean 2025-10-28 1:27 ` Daniel Golle 0 siblings, 1 reply; 35+ messages in thread From: Vladimir Oltean @ 2025-10-28 0:09 UTC (permalink / raw) To: Daniel Golle Cc: Hauke Mehrtens, Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel, Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin On Sun, Oct 26, 2025 at 11:48:06PM +0000, Daniel Golle wrote: > Extend the Lantiq GSWIP device tree binding to also cover MaxLinear > GSW1xx switches which are based on the same hardware IP but connected > via MDIO instead of being memory-mapped. > > Add compatible strings for MaxLinear GSW120, GSW125, GSW140, GSW141, > and GSW145 switches and adjust the schema to handle the different > connection methods with conditional properties. > > Add MaxLinear GSW125 example showing MDIO-connected configuration. > > Signed-off-by: Daniel Golle <daniel@makrotopia.org> > --- > v3: > * add maxlinear,rx-inverted and maxlinear,tx-inverted properties > > v2: > * remove git conflict left-overs which somehow creeped in > * indent example with 4 spaces instead of tabs > > .../bindings/net/dsa/lantiq,gswip.yaml | 275 +++++++++++++----- > 1 file changed, 202 insertions(+), 73 deletions(-) > > diff --git a/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml b/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml > index dd3858bad8ca..1148fdd0b6bc 100644 > --- a/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml > +++ b/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml > @@ -4,7 +4,12 @@ > $id: http://devicetree.org/schemas/net/dsa/lantiq,gswip.yaml# > $schema: http://devicetree.org/meta-schemas/core.yaml# > > -title: Lantiq GSWIP Ethernet switches > +title: Lantiq GSWIP and MaxLinear GSW1xx Ethernet switches > + > +description: > + Lantiq GSWIP and MaxLinear GSW1xx switches share the same hardware IP. > + Lantiq switches are embedded in SoCs and accessed via memory-mapped I/O, > + while MaxLinear switches are standalone ICs connected via MDIO. > > $ref: dsa.yaml# > > @@ -34,6 +39,108 @@ patternProperties: > description: > Configure the RMII reference clock to be a clock output > rather than an input. Only applicable for RMII mode. > + maxlinear,rx-inverted: > + type: boolean > + description: > + Enable RX polarity inversion for SerDes port. > + maxlinear,tx-inverted: > + type: boolean > + description: > + Enable TX polarity inversion for SerDes port. How urgently do you need these two properties? They are truly general, not vendor-specific, and while I wanted to add such support to the Synopsys XPCS, I started working on some generic variants. There's some cleanup and consolidation to do. "st,pcie-tx-pol-inv" and "st,sata-tx-pol-inv" are defined in .txt bindings but not implemented. Then we have "st,px_rx_pol_inv" and "mediatek,pnswap" which would also need deprecating and converted to the new formats. Where I left things was that I haven't decided if there's any value in defining the polarity per SerDes protocol (like Documentation/devicetree/bindings/phy/transmit-amplitude.yaml) or if a global value is fine. I.e. if the polarity is inverted for SATA, it's normal for PCIe, or something like that. The existence of the independent "st,pcie-tx-pol-inv" and "st,sata-tx-pol-inv" properties would suggest yes, but the lack of an implementation casts some doubt on that. Anyway, I do have some prototype patches that add something like this: phy: phy { #phy-cells = <1>; tx-p2p-microvolt = <915000>, <1100000>, <1200000>; tx-p2p-microvolt-names = "2500base-x", "usb-hs", "usb-ss"; /* RX polarity is inverted for usb-hs, normal for usb-ss */ rx-polarity = <PHY_POL_INVERT>, <PHY_POL_NORMAL>; rx-polarity-names = "usb-hs", "usb-ss"; /* TX polarity is normal for all modes */ tx-polarity = <PHY_POL_NORMAL>; tx-polarity-names = "default"; }; and a new drivers/phy/phy-common-props.c file (yes, outside of netdev) with two exported API functions: int phy_get_rx_polarity(struct fwnode_handle *fwnode, const char *mode_name); int phy_get_tx_polarity(struct fwnode_handle *fwnode, const char *mode_name); If you can split this up from the rest of the MDIO discrete switch introduction series, I can accelerate work on these common properties in the following weeks. > + > +allOf: > + - if: > + properties: > + compatible: > + contains: > + enum: > + - lantiq,xrx200-gswip > + - lantiq,xrx300-gswip > + - lantiq,xrx330-gswip > + then: > + properties: > + reg: > + minItems: 3 > + maxItems: 3 > + description: Memory-mapped register regions (switch, mdio, mii) > + reg-names: > + items: > + - const: switch > + - const: mdio > + - const: mii > + mdio: > + $ref: /schemas/net/mdio.yaml# > + unevaluatedProperties: false > + > + properties: > + compatible: > + const: lantiq,xrx200-mdio > + > + required: > + - compatible > + gphy-fw: > + type: object > + properties: > + '#address-cells': > + const: 1 > + > + '#size-cells': > + const: 0 > + > + compatible: > + items: > + - enum: > + - lantiq,xrx200-gphy-fw > + - lantiq,xrx300-gphy-fw > + - lantiq,xrx330-gphy-fw > + - const: lantiq,gphy-fw > + > + lantiq,rcu: > + $ref: /schemas/types.yaml#/definitions/phandle > + description: phandle to the RCU syscon > + > + patternProperties: > + "^gphy@[0-9a-f]{1,2}$": > + type: object > + > + additionalProperties: false > + > + properties: > + reg: > + minimum: 0 > + maximum: 255 > + description: > + Offset of the GPHY firmware register in the RCU register > + range > + > + resets: > + items: > + - description: GPHY reset line > + > + reset-names: > + items: > + - const: gphy > + > + required: > + - reg > + > + required: > + - compatible > + - lantiq,rcu > + > + additionalProperties: false > + required: > + - reg-names > + else: > + properties: > + reg: > + maxItems: 1 > + description: MDIO bus address > + reg-names: false > + gphy-fw: false If they're so different you could also define a separate schema for the discrete switches, if that helps. > + mdio: > + $ref: /schemas/net/mdio.yaml# > + unevaluatedProperties: false > > maintainers: > - Hauke Mehrtens <hauke@hauke-m.de> > @@ -44,78 +151,11 @@ properties: > - lantiq,xrx200-gswip > - lantiq,xrx300-gswip > - lantiq,xrx330-gswip > - > - reg: > - minItems: 3 > - maxItems: 3 > - > - reg-names: > - items: > - - const: switch > - - const: mdio > - - const: mii > - > - mdio: > - $ref: /schemas/net/mdio.yaml# > - unevaluatedProperties: false > - > - properties: > - compatible: > - const: lantiq,xrx200-mdio > - > - required: > - - compatible > - > - gphy-fw: > - type: object > - properties: > - '#address-cells': > - const: 1 > - > - '#size-cells': > - const: 0 > - > - compatible: > - items: > - - enum: > - - lantiq,xrx200-gphy-fw > - - lantiq,xrx300-gphy-fw > - - lantiq,xrx330-gphy-fw > - - const: lantiq,gphy-fw > - > - lantiq,rcu: > - $ref: /schemas/types.yaml#/definitions/phandle > - description: phandle to the RCU syscon > - > - patternProperties: > - "^gphy@[0-9a-f]{1,2}$": > - type: object > - > - additionalProperties: false > - > - properties: > - reg: > - minimum: 0 > - maximum: 255 > - description: > - Offset of the GPHY firmware register in the RCU register range > - > - resets: > - items: > - - description: GPHY reset line > - > - reset-names: > - items: > - - const: gphy > - > - required: > - - reg > - > - required: > - - compatible > - - lantiq,rcu > - > - additionalProperties: false > + - maxlinear,gsw120 > + - maxlinear,gsw125 > + - maxlinear,gsw140 > + - maxlinear,gsw141 > + - maxlinear,gsw145 > > required: > - compatible > @@ -130,6 +170,7 @@ examples: > reg = <0xe108000 0x3100>, /* switch */ > <0xe10b100 0xd8>, /* mdio */ > <0xe10b1d8 0x130>; /* mii */ > + reg-names = "switch", "mdio", "mii"; > dsa,member = <0 0>; > > ports { > @@ -228,3 +269,91 @@ examples: > }; > }; > }; > + > + - | > + #include <dt-bindings/leds/common.h> > + > + mdio { > + #address-cells = <1>; > + #size-cells = <0>; > + > + switch@1f { > + compatible = "maxlinear,gsw125"; > + reg = <0x1f>; > + > + ports { > + #address-cells = <1>; > + #size-cells = <0>; > + > + port@0 { > + reg = <0>; > + label = "lan0"; > + phy-handle = <&switchphy0>; > + phy-mode = "internal"; > + }; > + > + port@1 { > + reg = <1>; > + label = "lan1"; > + phy-handle = <&switchphy1>; > + phy-mode = "internal"; > + }; > + > + port@4 { > + reg = <4>; > + label = "wan"; > + phy-mode = "1000base-x"; > + maxlinear,rx-inverted; > + managed = "in-band-status"; > + }; > + > + port@5 { > + reg = <5>; > + phy-mode = "rgmii-id"; > + tx-internal-delay-ps = <2000>; > + rx-internal-delay-ps = <2000>; > + ethernet = <ð0>; > + > + fixed-link { > + speed = <1000>; > + full-duplex; > + }; > + }; > + }; > + > + mdio { > + #address-cells = <1>; > + #size-cells = <0>; > + > + switchphy0: switchphy@0 { > + reg = <0>; > + > + leds { > + #address-cells = <1>; > + #size-cells = <0>; > + > + led@0 { > + reg = <0>; > + color = <LED_COLOR_ID_GREEN>; > + function = LED_FUNCTION_LAN; > + }; > + }; > + }; > + > + switchphy1: switchphy@1 { > + reg = <1>; > + > + leds { > + #address-cells = <1>; > + #size-cells = <0>; > + > + led@0 { > + reg = <0>; > + color = <LED_COLOR_ID_GREEN>; > + function = LED_FUNCTION_LAN; > + }; > + }; > + }; > + }; > + }; > + }; > -- > 2.51.1 ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v3 10/12] dt-bindings: net: dsa: lantiq,gswip: add support for MaxLinear GSW1xx switches 2025-10-28 0:09 ` Vladimir Oltean @ 2025-10-28 1:27 ` Daniel Golle 0 siblings, 0 replies; 35+ messages in thread From: Daniel Golle @ 2025-10-28 1:27 UTC (permalink / raw) To: Vladimir Oltean Cc: Hauke Mehrtens, Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel, Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin On Tue, Oct 28, 2025 at 02:09:59AM +0200, Vladimir Oltean wrote: > On Sun, Oct 26, 2025 at 11:48:06PM +0000, Daniel Golle wrote: > > Extend the Lantiq GSWIP device tree binding to also cover MaxLinear > > GSW1xx switches which are based on the same hardware IP but connected > > via MDIO instead of being memory-mapped. > > > > Add compatible strings for MaxLinear GSW120, GSW125, GSW140, GSW141, > > and GSW145 switches and adjust the schema to handle the different > > connection methods with conditional properties. > > > > Add MaxLinear GSW125 example showing MDIO-connected configuration. > > > > Signed-off-by: Daniel Golle <daniel@makrotopia.org> > > --- > > v3: > > * add maxlinear,rx-inverted and maxlinear,tx-inverted properties > > > > v2: > > * remove git conflict left-overs which somehow creeped in > > * indent example with 4 spaces instead of tabs > > > > .../bindings/net/dsa/lantiq,gswip.yaml | 275 +++++++++++++----- > > 1 file changed, 202 insertions(+), 73 deletions(-) > > > > diff --git a/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml b/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml > > index dd3858bad8ca..1148fdd0b6bc 100644 > > --- a/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml > > +++ b/Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml > > @@ -4,7 +4,12 @@ > > $id: http://devicetree.org/schemas/net/dsa/lantiq,gswip.yaml# > > $schema: http://devicetree.org/meta-schemas/core.yaml# > > > > -title: Lantiq GSWIP Ethernet switches > > +title: Lantiq GSWIP and MaxLinear GSW1xx Ethernet switches > > + > > +description: > > + Lantiq GSWIP and MaxLinear GSW1xx switches share the same hardware IP. > > + Lantiq switches are embedded in SoCs and accessed via memory-mapped I/O, > > + while MaxLinear switches are standalone ICs connected via MDIO. > > > > $ref: dsa.yaml# > > > > @@ -34,6 +39,108 @@ patternProperties: > > description: > > Configure the RMII reference clock to be a clock output > > rather than an input. Only applicable for RMII mode. > > + maxlinear,rx-inverted: > > + type: boolean > > + description: > > + Enable RX polarity inversion for SerDes port. > > + maxlinear,tx-inverted: > > + type: boolean > > + description: > > + Enable TX polarity inversion for SerDes port. > > How urgently do you need these two properties? They are truly general, > not vendor-specific, and while I wanted to add such support to the > Synopsys XPCS, I started working on some generic variants. Inverting the RX inversion is required for the MaxLinear GSW145 demo board I got which got an MxL86111 PHY wired to the SGMII port of the switch. That's why I had to implement at least that in order to be able to test the SerDes port. > There's some cleanup and consolidation to do. "st,pcie-tx-pol-inv" and > "st,sata-tx-pol-inv" are defined in .txt bindings but not implemented. > Then we have "st,px_rx_pol_inv" and "mediatek,pnswap" which would also > need deprecating and converted to the new formats. Sounds like a good plan, I'm all for it :) > > Where I left things was that I haven't decided if there's any value in > defining the polarity per SerDes protocol (like > Documentation/devicetree/bindings/phy/transmit-amplitude.yaml) or if a > global value is fine. I.e. if the polarity is inverted for SATA, it's > normal for PCIe, or something like that. The existence of the independent > "st,pcie-tx-pol-inv" and "st,sata-tx-pol-inv" properties would suggest > yes, but the lack of an implementation casts some doubt on that. > > Anyway, I do have some prototype patches that add something like this: > > phy: phy { > #phy-cells = <1>; > tx-p2p-microvolt = <915000>, <1100000>, <1200000>; > tx-p2p-microvolt-names = "2500base-x", "usb-hs", "usb-ss"; > > /* RX polarity is inverted for usb-hs, normal for usb-ss */ > rx-polarity = <PHY_POL_INVERT>, <PHY_POL_NORMAL>; > rx-polarity-names = "usb-hs", "usb-ss"; > > /* TX polarity is normal for all modes */ > tx-polarity = <PHY_POL_NORMAL>; > tx-polarity-names = "default"; > }; > > and a new drivers/phy/phy-common-props.c file (yes, outside of netdev) > with two exported API functions: > > int phy_get_rx_polarity(struct fwnode_handle *fwnode, const char *mode_name); > int phy_get_tx_polarity(struct fwnode_handle *fwnode, const char *mode_name); > > If you can split this up from the rest of the MDIO discrete switch > introduction series, I can accelerate work on these common properties in > the following weeks. I can break out the SGMII polarity dt-bindings and functional patch and postpone it until generic properties to describe SerDes polarities are introduced. Also note that the SerDes PHY also got a bunch of other tunables which can make sense but aren't required on the demo board: * RX LOS Detector Enable * RX LOS Filter Count * RX LOS Threshold Level in mV * RX LOS Sensitivity Level * TX Amplitude Control * TX Vboost Enable * TX Vboost Level (0.844 V, 1.008 V, 1.156 V) * TX Remote Receiver Detection Request Enable * TX Preemphasis * ... Especially the voltage levels cry for being described in a generic way... ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH net-next v3 11/12] net: dsa: add tagging driver for MaxLinear GSW1xx switch family 2025-10-26 23:43 [PATCH net-next v3 00/12] net: dsa: lantiq_gswip: Add support for MaxLinear GSW1xx switch family Daniel Golle ` (9 preceding siblings ...) 2025-10-26 23:48 ` [PATCH net-next v3 10/12] dt-bindings: net: dsa: lantiq,gswip: add support for MaxLinear GSW1xx switches Daniel Golle @ 2025-10-26 23:48 ` Daniel Golle 2025-10-27 11:48 ` Sverdlin, Alexander 2025-10-28 0:28 ` Vladimir Oltean 2025-10-26 23:49 ` [PATCH net-next v3 12/12] net: dsa: add " Daniel Golle 11 siblings, 2 replies; 35+ messages in thread From: Daniel Golle @ 2025-10-26 23:48 UTC (permalink / raw) To: Hauke Mehrtens, Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel Cc: Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin Add support for a new DSA tagging protocol driver for the MaxLinear GSW1xx switch family. The GSW1xx switches use a proprietary 8-byte special tag inserted between the source MAC address and the EtherType field to indicate the source and destination ports for frames traversing the CPU port. Implement the tag handling logic to insert the special tag on transmit and parse it on receive. Signed-off-by: Daniel Golle <daniel@makrotopia.org> --- since RFC: * use dsa etype header macros instead of open coding them * maintain alphabetic order in Kconfig and Makefile MAINTAINERS | 3 +- include/net/dsa.h | 2 + net/dsa/Kconfig | 8 +++ net/dsa/Makefile | 1 + net/dsa/tag_mxl-gsw1xx.c | 141 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 net/dsa/tag_mxl-gsw1xx.c diff --git a/MAINTAINERS b/MAINTAINERS index d652f4f27756..4ddff0b0a547 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14038,7 +14038,7 @@ F: tools/testing/selftests/landlock/ K: landlock K: LANDLOCK -LANTIQ / INTEL Ethernet drivers +LANTIQ / MAXLINEAR / INTEL Ethernet DSA drivers M: Hauke Mehrtens <hauke@hauke-m.de> L: netdev@vger.kernel.org S: Maintained @@ -14046,6 +14046,7 @@ F: Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml F: drivers/net/dsa/lantiq/* F: drivers/net/ethernet/lantiq_xrx200.c F: net/dsa/tag_gswip.c +F: net/dsa/tag_mxl-gsw1xx.c LANTIQ MIPS ARCHITECTURE M: John Crispin <john@phrozen.org> diff --git a/include/net/dsa.h b/include/net/dsa.h index 67762fdaf3c7..2df2e2ead9a8 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -56,6 +56,7 @@ struct tc_action; #define DSA_TAG_PROTO_VSC73XX_8021Q_VALUE 28 #define DSA_TAG_PROTO_BRCM_LEGACY_FCS_VALUE 29 #define DSA_TAG_PROTO_YT921X_VALUE 30 +#define DSA_TAG_PROTO_MXL_GSW1XX_VALUE 31 enum dsa_tag_protocol { DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE, @@ -89,6 +90,7 @@ enum dsa_tag_protocol { DSA_TAG_PROTO_LAN937X = DSA_TAG_PROTO_LAN937X_VALUE, DSA_TAG_PROTO_VSC73XX_8021Q = DSA_TAG_PROTO_VSC73XX_8021Q_VALUE, DSA_TAG_PROTO_YT921X = DSA_TAG_PROTO_YT921X_VALUE, + DSA_TAG_PROTO_MXL_GSW1XX = DSA_TAG_PROTO_MXL_GSW1XX_VALUE, }; struct dsa_switch; diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig index 6b94028b1fcc..f86b30742122 100644 --- a/net/dsa/Kconfig +++ b/net/dsa/Kconfig @@ -104,6 +104,14 @@ config NET_DSA_TAG_MTK Say Y or M if you want to enable support for tagging frames for Mediatek switches. +config NET_DSA_TAG_MXL_GSW1XX + tristate "Tag driver for MaxLinear GSW1xx switches" + help + The GSW1xx family of switches supports an 8-byte special tag which + can be used on the CPU port of the switch. + Say Y or M if you want to enable support for tagging frames for + MaxLinear GSW1xx switches. + config NET_DSA_TAG_KSZ tristate "Tag driver for Microchip 8795/937x/9477/9893 families of switches" help diff --git a/net/dsa/Makefile b/net/dsa/Makefile index 4b011a1d5c87..42d173f5a701 100644 --- a/net/dsa/Makefile +++ b/net/dsa/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_NET_DSA_TAG_HELLCREEK) += tag_hellcreek.o obj-$(CONFIG_NET_DSA_TAG_KSZ) += tag_ksz.o obj-$(CONFIG_NET_DSA_TAG_LAN9303) += tag_lan9303.o obj-$(CONFIG_NET_DSA_TAG_MTK) += tag_mtk.o +obj-$(CONFIG_NET_DSA_TAG_MXL_GSW1XX) += tag_mxl-gsw1xx.o obj-$(CONFIG_NET_DSA_TAG_NONE) += tag_none.o obj-$(CONFIG_NET_DSA_TAG_OCELOT) += tag_ocelot.o obj-$(CONFIG_NET_DSA_TAG_OCELOT_8021Q) += tag_ocelot_8021q.o diff --git a/net/dsa/tag_mxl-gsw1xx.c b/net/dsa/tag_mxl-gsw1xx.c new file mode 100644 index 000000000000..9efec6deb494 --- /dev/null +++ b/net/dsa/tag_mxl-gsw1xx.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * DSA driver Special Tag support for MaxLinear GSW1xx switch chips + * + * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org> + * Copyright (C) 2023 - 2024 MaxLinear Inc. + */ + +#include <linux/bitops.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <net/dsa.h> + +#include "tag.h" + +/* To define the outgoing port and to discover the incoming port a special + * tag is used by the GSW1xx. + * + * Dest MAC Src MAC special TAG EtherType + * ...| 1 2 3 4 5 6 | 1 2 3 4 5 6 | 1 2 3 4 5 6 7 8 | 1 2 |... + * |<--------------->| + */ + +#define GSW1XX_TAG_NAME "gsw1xx" + +/* special tag in TX path header */ +#define GSW1XX_TX_HEADER_LEN 8 + +/* Byte 0 = Ethertype byte 1 -> 0x88 */ +/* Byte 1 = Ethertype byte 2 -> 0xC3*/ + +/* Byte 2 */ +#define GSW1XX_TX_PORT_MAP_EN BIT(7) +#define GSW1XX_TX_CLASS_EN BIT(6) +#define GSW1XX_TX_TIME_STAMP_EN BIT(5) +#define GSW1XX_TX_LRN_DIS BIT(4) +#define GSW1XX_TX_CLASS_SHIFT 0 +#define GSW1XX_TX_CLASS_MASK GENMASK(3, 0) + +/* Byte 3 */ +#define GSW1XX_TX_PORT_MAP_LOW_SHIFT 0 +#define GSW1XX_TX_PORT_MAP_LOW_MASK GENMASK(7, 0) + +/* Byte 4 */ +#define GSW1XX_TX_PORT_MAP_HIGH_SHIFT 0 +#define GSW1XX_TX_PORT_MAP_HIGH_MASK GENMASK(7, 0) + +#define GSW1XX_RX_HEADER_LEN 8 + +/* special tag in RX path header */ +/* Byte 4 */ +#define GSW1XX_RX_PORT_MAP_LOW_SHIFT 0 +#define GSW1XX_RX_PORT_MAP_LOW_MASK GENMASK(7, 0) + +/* Byte 5 */ +#define GSW1XX_RX_PORT_MAP_HIGH_SHIFT 0 +#define GSW1XX_RX_PORT_MAP_HIGH_MASK GENMASK(7, 0) + +static struct sk_buff *gsw1xx_tag_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + struct dsa_port *dp = dsa_user_to_port(dev); + u8 *gsw1xx_tag; + + /* provide additional space 'GSW1XX_TX_HEADER_LEN' bytes */ + skb_push(skb, GSW1XX_TX_HEADER_LEN); + + /* add space between MAC address and Ethertype */ + dsa_alloc_etype_header(skb, GSW1XX_TX_HEADER_LEN); + + /* special tag ingress */ + gsw1xx_tag = dsa_etype_header_pos_tx(skb); + gsw1xx_tag[0] = 0x88; + gsw1xx_tag[1] = 0xc3; + gsw1xx_tag[2] = GSW1XX_TX_PORT_MAP_EN | GSW1XX_TX_LRN_DIS; + gsw1xx_tag[3] = BIT(dp->index + GSW1XX_TX_PORT_MAP_LOW_SHIFT) & + GSW1XX_TX_PORT_MAP_LOW_MASK; + gsw1xx_tag[4] = 0; + gsw1xx_tag[5] = 0; + gsw1xx_tag[6] = 0; + gsw1xx_tag[7] = 0; + + return skb; +} + +static struct sk_buff *gsw1xx_tag_rcv(struct sk_buff *skb, + struct net_device *dev) +{ + int port; + u8 *gsw1xx_tag; + + if (unlikely(!pskb_may_pull(skb, GSW1XX_RX_HEADER_LEN))) { + dev_warn_ratelimited(&dev->dev, "Dropping packet, cannot pull SKB\n"); + return NULL; + } + + gsw1xx_tag = dsa_etype_header_pos_rx(skb); + + if (gsw1xx_tag[0] != 0x88 && gsw1xx_tag[1] != 0xc3) { + dev_warn_ratelimited(&dev->dev, "Dropping packet due to invalid special tag\n"); + dev_warn_ratelimited(&dev->dev, + "Tag: 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x\n", + gsw1xx_tag[0], gsw1xx_tag[1], gsw1xx_tag[2], gsw1xx_tag[3], + gsw1xx_tag[4], gsw1xx_tag[5], gsw1xx_tag[6], gsw1xx_tag[7]); + return NULL; + } + + /* Get source port information */ + port = (gsw1xx_tag[2] & GSW1XX_RX_PORT_MAP_LOW_MASK) >> GSW1XX_RX_PORT_MAP_LOW_SHIFT; + skb->dev = dsa_conduit_find_user(dev, 0, port); + if (!skb->dev) { + dev_warn_ratelimited(&dev->dev, "Dropping packet due to invalid source port\n"); + dev_warn_ratelimited(&dev->dev, + "Tag: 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x\n", + gsw1xx_tag[0], gsw1xx_tag[1], gsw1xx_tag[2], gsw1xx_tag[3], + gsw1xx_tag[4], gsw1xx_tag[5], gsw1xx_tag[6], gsw1xx_tag[7]); + return NULL; + } + + /* remove the GSW1xx special tag between MAC addresses and the current + * ethertype field. + */ + skb_pull_rcsum(skb, GSW1XX_RX_HEADER_LEN); + dsa_strip_etype_header(skb, GSW1XX_RX_HEADER_LEN); + + return skb; +} + +static const struct dsa_device_ops gsw1xx_netdev_ops = { + .name = GSW1XX_TAG_NAME, + .proto = DSA_TAG_PROTO_MXL_GSW1XX, + .xmit = gsw1xx_tag_xmit, + .rcv = gsw1xx_tag_rcv, + .needed_headroom = GSW1XX_RX_HEADER_LEN, +}; + +MODULE_DESCRIPTION("DSA tag driver for MaxLinear GSW1xx 8 byte protocol"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_MXL_GSW1XX, GSW1XX_TAG_NAME); + +module_dsa_tag_driver(gsw1xx_netdev_ops); -- 2.51.1 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v3 11/12] net: dsa: add tagging driver for MaxLinear GSW1xx switch family 2025-10-26 23:48 ` [PATCH net-next v3 11/12] net: dsa: add tagging driver for MaxLinear GSW1xx switch family Daniel Golle @ 2025-10-27 11:48 ` Sverdlin, Alexander 2025-10-28 0:28 ` Vladimir Oltean 1 sibling, 0 replies; 35+ messages in thread From: Sverdlin, Alexander @ 2025-10-27 11:48 UTC (permalink / raw) To: daniel@makrotopia.org, devicetree@vger.kernel.org, netdev@vger.kernel.org, linux-kernel@vger.kernel.org Cc: andrew@lunn.ch, olteanv@gmail.com, robh@kernel.org, lxu@maxlinear.com, john@phrozen.org, davem@davemloft.net, yweng@maxlinear.com, bxu@maxlinear.com, edumazet@google.com, pabeni@redhat.com, conor+dt@kernel.org, fchan@maxlinear.com, ajayaraman@maxlinear.com, krzk+dt@kernel.org, hauke@hauke-m.de, horms@kernel.org, kuba@kernel.org, jpovazanec@maxlinear.com, linux@armlinux.org.uk Hi Daniel, On Sun, 2025-10-26 at 23:48 +0000, Daniel Golle wrote: > Add support for a new DSA tagging protocol driver for the MaxLinear > GSW1xx switch family. The GSW1xx switches use a proprietary 8-byte > special tag inserted between the source MAC address and the EtherType > field to indicate the source and destination ports for frames > traversing the CPU port. > > Implement the tag handling logic to insert the special tag on transmit > and parse it on receive. > > Signed-off-by: Daniel Golle <daniel@makrotopia.org> thanks for the patch! Reviewed-by: Alexander Sverdlin <alexander.sverdlin@siemens.com> Tested-by: Alexander Sverdlin <alexander.sverdlin@siemens.com> (with GSW145) > --- > since RFC: > * use dsa etype header macros instead of open coding them > * maintain alphabetic order in Kconfig and Makefile > > MAINTAINERS | 3 +- > include/net/dsa.h | 2 + > net/dsa/Kconfig | 8 +++ > net/dsa/Makefile | 1 + > net/dsa/tag_mxl-gsw1xx.c | 141 +++++++++++++++++++++++++++++++++++++++ > 5 files changed, 154 insertions(+), 1 deletion(-) -- Alexander Sverdlin Siemens AG www.siemens.com ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v3 11/12] net: dsa: add tagging driver for MaxLinear GSW1xx switch family 2025-10-26 23:48 ` [PATCH net-next v3 11/12] net: dsa: add tagging driver for MaxLinear GSW1xx switch family Daniel Golle 2025-10-27 11:48 ` Sverdlin, Alexander @ 2025-10-28 0:28 ` Vladimir Oltean 2025-10-28 17:24 ` Daniel Golle 1 sibling, 1 reply; 35+ messages in thread From: Vladimir Oltean @ 2025-10-28 0:28 UTC (permalink / raw) To: Daniel Golle Cc: Hauke Mehrtens, Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel, Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin On Sun, Oct 26, 2025 at 11:48:23PM +0000, Daniel Golle wrote: > Add support for a new DSA tagging protocol driver for the MaxLinear > GSW1xx switch family. The GSW1xx switches use a proprietary 8-byte > special tag inserted between the source MAC address and the EtherType > field to indicate the source and destination ports for frames > traversing the CPU port. > > Implement the tag handling logic to insert the special tag on transmit > and parse it on receive. > > Signed-off-by: Daniel Golle <daniel@makrotopia.org> > --- > since RFC: > * use dsa etype header macros instead of open coding them > * maintain alphabetic order in Kconfig and Makefile > > MAINTAINERS | 3 +- > include/net/dsa.h | 2 + > net/dsa/Kconfig | 8 +++ > net/dsa/Makefile | 1 + > net/dsa/tag_mxl-gsw1xx.c | 141 +++++++++++++++++++++++++++++++++++++++ > 5 files changed, 154 insertions(+), 1 deletion(-) > create mode 100644 net/dsa/tag_mxl-gsw1xx.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index d652f4f27756..4ddff0b0a547 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -14038,7 +14038,7 @@ F: tools/testing/selftests/landlock/ > K: landlock > K: LANDLOCK > > -LANTIQ / INTEL Ethernet drivers > +LANTIQ / MAXLINEAR / INTEL Ethernet DSA drivers > M: Hauke Mehrtens <hauke@hauke-m.de> > L: netdev@vger.kernel.org > S: Maintained > @@ -14046,6 +14046,7 @@ F: Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml > F: drivers/net/dsa/lantiq/* > F: drivers/net/ethernet/lantiq_xrx200.c > F: net/dsa/tag_gswip.c > +F: net/dsa/tag_mxl-gsw1xx.c > > LANTIQ MIPS ARCHITECTURE > M: John Crispin <john@phrozen.org> > diff --git a/include/net/dsa.h b/include/net/dsa.h > index 67762fdaf3c7..2df2e2ead9a8 100644 > --- a/include/net/dsa.h > +++ b/include/net/dsa.h > @@ -56,6 +56,7 @@ struct tc_action; > #define DSA_TAG_PROTO_VSC73XX_8021Q_VALUE 28 > #define DSA_TAG_PROTO_BRCM_LEGACY_FCS_VALUE 29 > #define DSA_TAG_PROTO_YT921X_VALUE 30 > +#define DSA_TAG_PROTO_MXL_GSW1XX_VALUE 31 > > enum dsa_tag_protocol { > DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE, > @@ -89,6 +90,7 @@ enum dsa_tag_protocol { > DSA_TAG_PROTO_LAN937X = DSA_TAG_PROTO_LAN937X_VALUE, > DSA_TAG_PROTO_VSC73XX_8021Q = DSA_TAG_PROTO_VSC73XX_8021Q_VALUE, > DSA_TAG_PROTO_YT921X = DSA_TAG_PROTO_YT921X_VALUE, > + DSA_TAG_PROTO_MXL_GSW1XX = DSA_TAG_PROTO_MXL_GSW1XX_VALUE, > }; > > struct dsa_switch; > diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig > index 6b94028b1fcc..f86b30742122 100644 > --- a/net/dsa/Kconfig > +++ b/net/dsa/Kconfig > @@ -104,6 +104,14 @@ config NET_DSA_TAG_MTK > Say Y or M if you want to enable support for tagging frames for > Mediatek switches. > > +config NET_DSA_TAG_MXL_GSW1XX > + tristate "Tag driver for MaxLinear GSW1xx switches" > + help > + The GSW1xx family of switches supports an 8-byte special tag which > + can be used on the CPU port of the switch. > + Say Y or M if you want to enable support for tagging frames for > + MaxLinear GSW1xx switches. > + > config NET_DSA_TAG_KSZ > tristate "Tag driver for Microchip 8795/937x/9477/9893 families of switches" > help > diff --git a/net/dsa/Makefile b/net/dsa/Makefile > index 4b011a1d5c87..42d173f5a701 100644 > --- a/net/dsa/Makefile > +++ b/net/dsa/Makefile > @@ -28,6 +28,7 @@ obj-$(CONFIG_NET_DSA_TAG_HELLCREEK) += tag_hellcreek.o > obj-$(CONFIG_NET_DSA_TAG_KSZ) += tag_ksz.o > obj-$(CONFIG_NET_DSA_TAG_LAN9303) += tag_lan9303.o > obj-$(CONFIG_NET_DSA_TAG_MTK) += tag_mtk.o > +obj-$(CONFIG_NET_DSA_TAG_MXL_GSW1XX) += tag_mxl-gsw1xx.o > obj-$(CONFIG_NET_DSA_TAG_NONE) += tag_none.o > obj-$(CONFIG_NET_DSA_TAG_OCELOT) += tag_ocelot.o > obj-$(CONFIG_NET_DSA_TAG_OCELOT_8021Q) += tag_ocelot_8021q.o > diff --git a/net/dsa/tag_mxl-gsw1xx.c b/net/dsa/tag_mxl-gsw1xx.c > new file mode 100644 > index 000000000000..9efec6deb494 > --- /dev/null > +++ b/net/dsa/tag_mxl-gsw1xx.c > @@ -0,0 +1,141 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * DSA driver Special Tag support for MaxLinear GSW1xx switch chips > + * > + * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org> > + * Copyright (C) 2023 - 2024 MaxLinear Inc. > + */ > + > +#include <linux/bitops.h> > +#include <linux/etherdevice.h> > +#include <linux/skbuff.h> > +#include <net/dsa.h> > + > +#include "tag.h" > + > +/* To define the outgoing port and to discover the incoming port a special > + * tag is used by the GSW1xx. > + * > + * Dest MAC Src MAC special TAG EtherType > + * ...| 1 2 3 4 5 6 | 1 2 3 4 5 6 | 1 2 3 4 5 6 7 8 | 1 2 |... > + * |<--------------->| > + */ > + > +#define GSW1XX_TAG_NAME "gsw1xx" > + > +/* special tag in TX path header */ > +#define GSW1XX_TX_HEADER_LEN 8 > + > +/* Byte 0 = Ethertype byte 1 -> 0x88 */ > +/* Byte 1 = Ethertype byte 2 -> 0xC3*/ > + > +/* Byte 2 */ > +#define GSW1XX_TX_PORT_MAP_EN BIT(7) > +#define GSW1XX_TX_CLASS_EN BIT(6) > +#define GSW1XX_TX_TIME_STAMP_EN BIT(5) > +#define GSW1XX_TX_LRN_DIS BIT(4) > +#define GSW1XX_TX_CLASS_SHIFT 0 > +#define GSW1XX_TX_CLASS_MASK GENMASK(3, 0) Using FIELD_PREP() would eliminate these _SHIFT definitions and _MASK would also go away from the macro names. > + > +/* Byte 3 */ > +#define GSW1XX_TX_PORT_MAP_LOW_SHIFT 0 > +#define GSW1XX_TX_PORT_MAP_LOW_MASK GENMASK(7, 0) > + > +/* Byte 4 */ > +#define GSW1XX_TX_PORT_MAP_HIGH_SHIFT 0 > +#define GSW1XX_TX_PORT_MAP_HIGH_MASK GENMASK(7, 0) > + > +#define GSW1XX_RX_HEADER_LEN 8 Usually you use two separate macros when the lengths are not equal, and you set .needed_headroom to the largest value. > + > +/* special tag in RX path header */ > +/* Byte 4 */ > +#define GSW1XX_RX_PORT_MAP_LOW_SHIFT 0 > +#define GSW1XX_RX_PORT_MAP_LOW_MASK GENMASK(7, 0) > + > +/* Byte 5 */ > +#define GSW1XX_RX_PORT_MAP_HIGH_SHIFT 0 > +#define GSW1XX_RX_PORT_MAP_HIGH_MASK GENMASK(7, 0) > + > +static struct sk_buff *gsw1xx_tag_xmit(struct sk_buff *skb, > + struct net_device *dev) > +{ > + struct dsa_port *dp = dsa_user_to_port(dev); > + u8 *gsw1xx_tag; > + > + /* provide additional space 'GSW1XX_TX_HEADER_LEN' bytes */ > + skb_push(skb, GSW1XX_TX_HEADER_LEN); > + > + /* add space between MAC address and Ethertype */ > + dsa_alloc_etype_header(skb, GSW1XX_TX_HEADER_LEN); > + > + /* special tag ingress */ > + gsw1xx_tag = dsa_etype_header_pos_tx(skb); > + gsw1xx_tag[0] = 0x88; > + gsw1xx_tag[1] = 0xc3; Could you write this as a u16 pointer, to make it obvious to everyone it's an EtherType, and define the EtherType constant in include/uapi/linux/if_ether.h, to make it a bit more visible that it's in use? > + gsw1xx_tag[2] = GSW1XX_TX_PORT_MAP_EN | GSW1XX_TX_LRN_DIS; > + gsw1xx_tag[3] = BIT(dp->index + GSW1XX_TX_PORT_MAP_LOW_SHIFT) & > + GSW1XX_TX_PORT_MAP_LOW_MASK; > + gsw1xx_tag[4] = 0; > + gsw1xx_tag[5] = 0; > + gsw1xx_tag[6] = 0; > + gsw1xx_tag[7] = 0; > + > + return skb; > +} > + > +static struct sk_buff *gsw1xx_tag_rcv(struct sk_buff *skb, > + struct net_device *dev) > +{ > + int port; > + u8 *gsw1xx_tag; > + > + if (unlikely(!pskb_may_pull(skb, GSW1XX_RX_HEADER_LEN))) { > + dev_warn_ratelimited(&dev->dev, "Dropping packet, cannot pull SKB\n"); > + return NULL; > + } > + > + gsw1xx_tag = dsa_etype_header_pos_rx(skb); > + > + if (gsw1xx_tag[0] != 0x88 && gsw1xx_tag[1] != 0xc3) { > + dev_warn_ratelimited(&dev->dev, "Dropping packet due to invalid special tag\n"); > + dev_warn_ratelimited(&dev->dev, > + "Tag: 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x\n", > + gsw1xx_tag[0], gsw1xx_tag[1], gsw1xx_tag[2], gsw1xx_tag[3], > + gsw1xx_tag[4], gsw1xx_tag[5], gsw1xx_tag[6], gsw1xx_tag[7]); I think you could print the tag with %*ph, according to https://elixir.bootlin.com/linux/v6.17.5/source/lib/vsprintf.c#L2453 (needs testing) > + return NULL; > + } > + > + /* Get source port information */ > + port = (gsw1xx_tag[2] & GSW1XX_RX_PORT_MAP_LOW_MASK) >> GSW1XX_RX_PORT_MAP_LOW_SHIFT; > + skb->dev = dsa_conduit_find_user(dev, 0, port); > + if (!skb->dev) { > + dev_warn_ratelimited(&dev->dev, "Dropping packet due to invalid source port\n"); > + dev_warn_ratelimited(&dev->dev, > + "Tag: 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x\n", > + gsw1xx_tag[0], gsw1xx_tag[1], gsw1xx_tag[2], gsw1xx_tag[3], > + gsw1xx_tag[4], gsw1xx_tag[5], gsw1xx_tag[6], gsw1xx_tag[7]); > + return NULL; > + } > + > + /* remove the GSW1xx special tag between MAC addresses and the current > + * ethertype field. > + */ > + skb_pull_rcsum(skb, GSW1XX_RX_HEADER_LEN); > + dsa_strip_etype_header(skb, GSW1XX_RX_HEADER_LEN); You're not setting skb->offload_fwd_mark but you implement port_bridge_join() so you offload L2 switching. If a packet gets flooded from port A to the CPU and also to port B, don't you see that the software bridge also creates a packet copy that it sends to port B a second time? > + > + return skb; > +} > + > +static const struct dsa_device_ops gsw1xx_netdev_ops = { > + .name = GSW1XX_TAG_NAME, > + .proto = DSA_TAG_PROTO_MXL_GSW1XX, > + .xmit = gsw1xx_tag_xmit, > + .rcv = gsw1xx_tag_rcv, > + .needed_headroom = GSW1XX_RX_HEADER_LEN, > +}; > + > +MODULE_DESCRIPTION("DSA tag driver for MaxLinear GSW1xx 8 byte protocol"); > +MODULE_LICENSE("GPL"); > +MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_MXL_GSW1XX, GSW1XX_TAG_NAME); > + > +module_dsa_tag_driver(gsw1xx_netdev_ops); > -- > 2.51.1 ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v3 11/12] net: dsa: add tagging driver for MaxLinear GSW1xx switch family 2025-10-28 0:28 ` Vladimir Oltean @ 2025-10-28 17:24 ` Daniel Golle 0 siblings, 0 replies; 35+ messages in thread From: Daniel Golle @ 2025-10-28 17:24 UTC (permalink / raw) To: Vladimir Oltean Cc: Hauke Mehrtens, Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel, Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin On Tue, Oct 28, 2025 at 02:28:41AM +0200, Vladimir Oltean wrote: > On Sun, Oct 26, 2025 at 11:48:23PM +0000, Daniel Golle wrote: > > Add support for a new DSA tagging protocol driver for the MaxLinear > > GSW1xx switch family. The GSW1xx switches use a proprietary 8-byte > > special tag inserted between the source MAC address and the EtherType > > field to indicate the source and destination ports for frames > > traversing the CPU port. > > > > Implement the tag handling logic to insert the special tag on transmit > > and parse it on receive. > > [...] > > --- /dev/null > > +++ b/net/dsa/tag_mxl-gsw1xx.c > > [...] > > +#define GSW1XX_TX_CLASS_SHIFT 0 > > +#define GSW1XX_TX_CLASS_MASK GENMASK(3, 0) > > Using FIELD_PREP() would eliminate these _SHIFT definitions and _MASK > would also go away from the macro names. Ack, using FIELD_PREP() and FIELD_GET() does improve readability and I'll use that. > > > + > > +/* Byte 3 */ > > +#define GSW1XX_TX_PORT_MAP_LOW_SHIFT 0 > > +#define GSW1XX_TX_PORT_MAP_LOW_MASK GENMASK(7, 0) > > + > > +/* Byte 4 */ > > +#define GSW1XX_TX_PORT_MAP_HIGH_SHIFT 0 > > +#define GSW1XX_TX_PORT_MAP_HIGH_MASK GENMASK(7, 0) > > + > > +#define GSW1XX_RX_HEADER_LEN 8 > > Usually you use two separate macros when the lengths are not equal, and > you set .needed_headroom to the largest value. A single macro #define GSW1XX_HEADER_LEN 8 will do the trick as they are anyway equal, right? > > [...] > > + u8 *gsw1xx_tag; > > + > > + /* provide additional space 'GSW1XX_TX_HEADER_LEN' bytes */ > > + skb_push(skb, GSW1XX_TX_HEADER_LEN); > > + > > + /* add space between MAC address and Ethertype */ > > + dsa_alloc_etype_header(skb, GSW1XX_TX_HEADER_LEN); > > + > > + /* special tag ingress */ > > + gsw1xx_tag = dsa_etype_header_pos_tx(skb); > > + gsw1xx_tag[0] = 0x88; > > + gsw1xx_tag[1] = 0xc3; > > Could you write this as a u16 pointer, to make it obvious to everyone > it's an EtherType, and define the EtherType constant in > include/uapi/linux/if_ether.h, to make it a bit more visible that it's > in use? Defining the EtherType in the appropriate header makes sense (even though 0x88c3 is just the default and configuration of the chip allows to set it to anything else, or even have it omitted entirely). Using __be16 to access the tag fields will make the whole thing sensitive to endianess, which is a bit messy. I would prefer to keep using u8 type and some shifting and masking of the EtherType constant similar to how it is done in tag_dsa.c. Also note that the datasheet describes the special tag byte-by-byte, and there is even a 16-bit field which crosses word boundaries, GSW1XX_TX_PORT_MAP_LOW and GSW1XX_TX_PORT_MAP_HIGH (ie. it is obvious that this wasn't intended to be accessed as 16-bit words). So I'd rather make it easy to understand how the tag driver matches the datasheet instead of using __be16 just for the sake of the EtherType. I've implemented and tested using __be16 now, and it doesn't look very bad either, especially when skipping the PORT_MAP_HIGH/LOW part because on the actually produced chips there anyway aren't ever more than 6 ports, so one anyway always only accesses the LOW part of the portmap. If you like to use __be16 (like eg. the realtek taggers) I will proceed like that in v4. > > [...] > > + if (gsw1xx_tag[0] != 0x88 && gsw1xx_tag[1] != 0xc3) { > > + dev_warn_ratelimited(&dev->dev, "Dropping packet due to invalid special tag\n"); > > + dev_warn_ratelimited(&dev->dev, > > + "Tag: 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x\n", > > + gsw1xx_tag[0], gsw1xx_tag[1], gsw1xx_tag[2], gsw1xx_tag[3], > > + gsw1xx_tag[4], gsw1xx_tag[5], gsw1xx_tag[6], gsw1xx_tag[7]); > > I think you could print the tag with %*ph, according to > https://elixir.bootlin.com/linux/v6.17.5/source/lib/vsprintf.c#L2453 > (needs testing) I've tested that and it works fine (looks slightly different of course due to the missing '0x' prefix, but that doesn't matter for debugging) > > [...] > > + /* remove the GSW1xx special tag between MAC addresses and the current > > + * ethertype field. > > + */ > > + skb_pull_rcsum(skb, GSW1XX_RX_HEADER_LEN); > > + dsa_strip_etype_header(skb, GSW1XX_RX_HEADER_LEN); > > You're not setting skb->offload_fwd_mark but you implement > port_bridge_join() so you offload L2 switching. If a packet gets flooded > from port A to the CPU and also to port B, don't you see that the > software bridge also creates a packet copy that it sends to port B a > second time? No, the opposite is true. If I set dsa_default_offload_fwd_mark(skb); forwarding between the ports no longer works. It can well be that this is an existing flaw in the driver, as tag_gswip.c also doesn't set offload_fwd_mark. ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH net-next v3 12/12] net: dsa: add driver for MaxLinear GSW1xx switch family 2025-10-26 23:43 [PATCH net-next v3 00/12] net: dsa: lantiq_gswip: Add support for MaxLinear GSW1xx switch family Daniel Golle ` (10 preceding siblings ...) 2025-10-26 23:48 ` [PATCH net-next v3 11/12] net: dsa: add tagging driver for MaxLinear GSW1xx switch family Daniel Golle @ 2025-10-26 23:49 ` Daniel Golle 2025-10-28 1:24 ` Vladimir Oltean 11 siblings, 1 reply; 35+ messages in thread From: Daniel Golle @ 2025-10-26 23:49 UTC (permalink / raw) To: Hauke Mehrtens, Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel Cc: Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin Add driver for the MaxLinear GSW1xx family of Ethernet switch ICs which are based on the same IP as the Lantiq/Intel GSWIP found in the Lantiq VR9 and Intel GRX MIPS router SoCs. The main difference is that instead of using memory-mapped I/O to communicate with the host CPU these ICs are connected via MDIO (or SPI, which isn't supported by this driver). Implement the regmap API to access the switch registers over MDIO to allow reusing lantiq_gswip_common for all core functionality. The GSW1xx also comes with a SerDes port capable of 1000Base-X, SGMII and 2500Base-X, which can either be used to connect an external PHY or SFP cage, or as the CPU port. Support for the SerDes interface is implemented in this driver using the phylink_pcs interface. Signed-off-by: Daniel Golle <daniel@makrotopia.org> --- v3: * avoid disrupt link when calling .pcs_config() * sort functions and phylink_pcs_ops instance in same order as struct definition * always set bootstrap override bits and add explanatory comment * move definitions to separate header file, squash into a single patch * add custom properties for TX and RX inverted data on the SerDes interface v2: remove left-overs of 4k VLAN support (will be added in future) drivers/net/dsa/lantiq/Kconfig | 12 + drivers/net/dsa/lantiq/Makefile | 1 + drivers/net/dsa/lantiq/lantiq_gswip.h | 1 + drivers/net/dsa/lantiq/mxl-gsw1xx.c | 736 ++++++++++++++++++++++++ drivers/net/dsa/lantiq/mxl-gsw1xx.h | 126 ++++ drivers/net/dsa/lantiq/mxl-gsw1xx_pce.h | 154 +++++ 6 files changed, 1030 insertions(+) create mode 100644 drivers/net/dsa/lantiq/mxl-gsw1xx.c create mode 100644 drivers/net/dsa/lantiq/mxl-gsw1xx.h create mode 100644 drivers/net/dsa/lantiq/mxl-gsw1xx_pce.h diff --git a/drivers/net/dsa/lantiq/Kconfig b/drivers/net/dsa/lantiq/Kconfig index 78db82a47d09..4a9771be5d58 100644 --- a/drivers/net/dsa/lantiq/Kconfig +++ b/drivers/net/dsa/lantiq/Kconfig @@ -10,3 +10,15 @@ config NET_DSA_LANTIQ_GSWIP help This enables support for the Lantiq / Intel GSWIP 2.1 found in the xrx200 / VR9 SoC. + +config NET_DSA_MXL_GSW1XX + tristate "MaxLinear GSW1xx Ethernet switch support" + select NET_DSA_TAG_MXL_GSW1XX + select NET_DSA_LANTIQ_COMMON + help + This enables support for the 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 diff --git a/drivers/net/dsa/lantiq/Makefile b/drivers/net/dsa/lantiq/Makefile index 65ffa7bb71aa..85fce605310b 100644 --- a/drivers/net/dsa/lantiq/Makefile +++ b/drivers/net/dsa/lantiq/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_NET_DSA_LANTIQ_GSWIP) += lantiq_gswip.o obj-$(CONFIG_NET_DSA_LANTIQ_COMMON) += lantiq_gswip_common.o +obj-$(CONFIG_NET_DSA_MXL_GSW1XX) += mxl-gsw1xx.o diff --git a/drivers/net/dsa/lantiq/lantiq_gswip.h b/drivers/net/dsa/lantiq/lantiq_gswip.h index 0c32ec85e127..9c38e51a75e8 100644 --- a/drivers/net/dsa/lantiq/lantiq_gswip.h +++ b/drivers/net/dsa/lantiq/lantiq_gswip.h @@ -255,6 +255,7 @@ struct gswip_hw_info { unsigned int allowed_cpu_ports; unsigned int mii_ports; int mii_port_reg_offset; + bool supports_2500m; const struct gswip_pce_microcode (*pce_microcode)[]; size_t pce_microcode_size; enum dsa_tag_protocol tag_protocol; diff --git a/drivers/net/dsa/lantiq/mxl-gsw1xx.c b/drivers/net/dsa/lantiq/mxl-gsw1xx.c new file mode 100644 index 000000000000..d9744b19652e --- /dev/null +++ b/drivers/net/dsa/lantiq/mxl-gsw1xx.c @@ -0,0 +1,736 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* DSA Driver for MaxLinear GSW1xx switch devices + * + * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org> + * Copyright (C) 2023 - 2024 MaxLinear Inc. + * Copyright (C) 2022 Snap One, LLC. All rights reserved. + * Copyright (C) 2017 - 2019 Hauke Mehrtens <hauke@hauke-m.de> + * Copyright (C) 2012 John Crispin <john@phrozen.org> + * Copyright (C) 2010 Lantiq Deutschland + */ + +#include <linux/bits.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_mdio.h> +#include <linux/regmap.h> +#include <net/dsa.h> + +#include "lantiq_gswip.h" +#include "mxl-gsw1xx.h" +#include "mxl-gsw1xx_pce.h" + +struct gsw1xx_priv { + struct mdio_device *mdio_dev; + int smdio_badr; + struct regmap *sgmii; + struct regmap *gpio; + struct regmap *clk; + struct regmap *shell; + struct phylink_pcs pcs; + phy_interface_t tbi_interface; + struct gswip_priv gswip; +}; + +static int gsw1xx_config_smdio_badr(struct gsw1xx_priv *priv, + unsigned int reg) +{ + struct mii_bus *bus = priv->mdio_dev->bus; + int sw_addr = priv->mdio_dev->addr; + int smdio_badr = priv->smdio_badr; + int res; + + if (smdio_badr == GSW1XX_SMDIO_BADR_UNKNOWN || + reg - smdio_badr >= GSW1XX_SMDIO_BADR || + smdio_badr > reg) { + /* Configure the Switch Base Address */ + smdio_badr = reg & ~GENMASK(3, 0); + res = __mdiobus_write(bus, sw_addr, GSW1XX_SMDIO_BADR, smdio_badr); + if (res < 0) { + dev_err(&priv->mdio_dev->dev, + "%s: Error %d, configuring switch base\n", + __func__, res); + return res; + } + priv->smdio_badr = smdio_badr; + } + + return smdio_badr; +} + +static int gsw1xx_regmap_read(void *context, unsigned int reg, + unsigned int *val) +{ + struct gsw1xx_priv *priv = context; + struct mii_bus *bus = priv->mdio_dev->bus; + int sw_addr = priv->mdio_dev->addr; + int smdio_badr; + int res; + + smdio_badr = gsw1xx_config_smdio_badr(priv, reg); + if (smdio_badr < 0) + return smdio_badr; + + res = __mdiobus_read(bus, sw_addr, reg - smdio_badr); + if (res < 0) { + dev_err(&priv->mdio_dev->dev, "%s: Error %d reading 0x%x\n", + __func__, res, reg); + return res; + } + + *val = res; + + return 0; +} + +static int gsw1xx_regmap_write(void *context, unsigned int reg, + unsigned int val) +{ + struct gsw1xx_priv *priv = context; + struct mii_bus *bus = priv->mdio_dev->bus; + int sw_addr = priv->mdio_dev->addr; + int smdio_badr; + int res; + + smdio_badr = gsw1xx_config_smdio_badr(priv, reg); + if (smdio_badr < 0) + return smdio_badr; + + res = __mdiobus_write(bus, sw_addr, reg - smdio_badr, val); + if (res < 0) + dev_err(&priv->mdio_dev->dev, + "%s: Error %d, writing 0x%x:0x%x\n", __func__, res, reg, + val); + + return res; +} + +static const struct regmap_bus gsw1xx_regmap_bus = { + .reg_write = gsw1xx_regmap_write, + .reg_read = gsw1xx_regmap_read, +}; + +static void gsw1xx_mdio_regmap_lock(void *mdio_lock) +{ + mutex_lock_nested(mdio_lock, MDIO_MUTEX_NESTED); +} + +static void gsw1xx_mdio_regmap_unlock(void *mdio_lock) +{ + mutex_unlock(mdio_lock); +} + +static unsigned int gsw1xx_pcs_inband_caps(struct phylink_pcs *pcs, + phy_interface_t interface) +{ + return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE; +} + +static struct gsw1xx_priv *pcs_to_gsw1xx(struct phylink_pcs *pcs) +{ + return container_of(pcs, struct gsw1xx_priv, pcs); +} + +static int gsw1xx_pcs_enable(struct phylink_pcs *pcs) +{ + struct gsw1xx_priv *priv = pcs_to_gsw1xx(pcs); + + /* Deassert SGMII shell reset */ + return regmap_clear_bits(priv->shell, GSW1XX_SHELL_RST_REQ, + GSW1XX_RST_REQ_SGMII_SHELL); +} + +static void gsw1xx_pcs_disable(struct phylink_pcs *pcs) +{ + struct gsw1xx_priv *priv = pcs_to_gsw1xx(pcs); + + /* Assert SGMII shell reset */ + regmap_set_bits(priv->shell, GSW1XX_SHELL_RST_REQ, + GSW1XX_RST_REQ_SGMII_SHELL); + + priv->tbi_interface = PHY_INTERFACE_MODE_NA; +} + +static void gsw1xx_pcs_get_state(struct phylink_pcs *pcs, + unsigned int neg_mode, + struct phylink_link_state *state) +{ + struct gsw1xx_priv *priv = pcs_to_gsw1xx(pcs); + int ret; + u32 val; + + ret = regmap_read(priv->sgmii, GSW1XX_SGMII_TBI_TBISTAT, &val); + if (ret < 0) + return; + + state->link = !!(val & GSW1XX_SGMII_TBI_TBISTAT_LINK); + state->an_complete = !!(val & GSW1XX_SGMII_TBI_TBISTAT_AN_COMPLETE); + + ret = regmap_read(priv->sgmii, GSW1XX_SGMII_TBI_LPSTAT, &val); + if (ret < 0) + return; + + state->duplex = (val & GSW1XX_SGMII_TBI_LPSTAT_DUPLEX) ? + DUPLEX_FULL : DUPLEX_HALF; + if (val & GSW1XX_SGMII_TBI_LPSTAT_PAUSE_RX) + state->pause |= MLO_PAUSE_RX; + + if (val & GSW1XX_SGMII_TBI_LPSTAT_PAUSE_TX) + state->pause |= MLO_PAUSE_TX; + + switch (FIELD_GET(GSW1XX_SGMII_TBI_LPSTAT_SPEED, val)) { + case GSW1XX_SGMII_TBI_LPSTAT_SPEED_10: + state->speed = SPEED_10; + break; + case GSW1XX_SGMII_TBI_LPSTAT_SPEED_100: + state->speed = SPEED_100; + break; + case GSW1XX_SGMII_TBI_LPSTAT_SPEED_1000: + state->speed = SPEED_1000; + break; + case GSW1XX_SGMII_TBI_LPSTAT_SPEED_NOSGMII: + if (state->interface == PHY_INTERFACE_MODE_1000BASEX) + state->speed = SPEED_1000; + else if (state->interface == PHY_INTERFACE_MODE_2500BASEX) + state->speed = SPEED_2500; + else + state->speed = SPEED_UNKNOWN; + break; + } +} + +static int gsw1xx_pcs_phy_xaui_write(struct gsw1xx_priv *priv, u16 addr, + u16 data) +{ + int ret, val; + + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_D, data); + if (ret < 0) + return ret; + + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_A, addr); + if (ret < 0) + return ret; + + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_C, + GSW1XX_SGMII_PHY_WRITE | + GSW1XX_SGMII_PHY_RESET_N); + if (ret < 0) + return ret; + + return regmap_read_poll_timeout(priv->sgmii, GSW1XX_SGMII_PHY_C, + val, val & GSW1XX_SGMII_PHY_STATUS, + 1000, 100000); +} + +static int gsw1xx_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, + phy_interface_t interface, + const unsigned long *advertising, + bool permit_pause_to_mac) +{ + struct gsw1xx_priv *priv = pcs_to_gsw1xx(pcs); + bool sgmii_mac_mode = dsa_is_user_port(priv->gswip.ds, + GSW1XX_SGMII_PORT); + struct dsa_port *dp = dsa_to_port(priv->gswip.ds, + GSW1XX_SGMII_PORT); + u16 txaneg, anegctl, val, nco_ctrl; + bool reconf = false; + int ret; + + /* do not unnecessarily disrupt link and skip resetting the hardware in + * case the PCS has previously been successfully configured for this + * interface mode + */ + if (priv->tbi_interface == interface) + reconf = true; + + /* mark PCS configuration as incomplete */ + priv->tbi_interface = PHY_INTERFACE_MODE_NA; + + if (reconf) + goto skip_init_reset; + + /* Assert and deassert SGMII shell reset */ + ret = regmap_set_bits(priv->shell, GSW1XX_SHELL_RST_REQ, + GSW1XX_RST_REQ_SGMII_SHELL); + if (ret < 0) + return ret; + + ret = regmap_clear_bits(priv->shell, GSW1XX_SHELL_RST_REQ, + GSW1XX_RST_REQ_SGMII_SHELL); + if (ret < 0) + return ret; + + /* Hardware Bringup FSM Enable */ + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_HWBU_CTRL, + GSW1XX_SGMII_PHY_HWBU_CTRL_EN_HWBU_FSM | + GSW1XX_SGMII_PHY_HWBU_CTRL_HW_FSM_EN); + if (ret < 0) + return ret; + + /* Configure SGMII PHY Receiver */ + val = FIELD_PREP(GSW1XX_SGMII_PHY_RX0_CFG2_EQ, + GSW1XX_SGMII_PHY_RX0_CFG2_EQ_DEF) | + GSW1XX_SGMII_PHY_RX0_CFG2_LOS_EN | + GSW1XX_SGMII_PHY_RX0_CFG2_TERM_EN | + FIELD_PREP(GSW1XX_SGMII_PHY_RX0_CFG2_FILT_CNT, + GSW1XX_SGMII_PHY_RX0_CFG2_FILT_CNT_DEF); + + if (of_property_read_bool(dp->dn, "maxlinear,rx-inverted")) + val |= GSW1XX_SGMII_PHY_RX0_CFG2_INVERT; + + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_RX0_CFG2, val); + if (ret < 0) + return ret; + + val = FIELD_PREP(GSW1XX_SGMII_PHY_TX0_CFG3_VBOOST_LEVEL, + GSW1XX_SGMII_PHY_TX0_CFG3_VBOOST_LEVEL_DEF); + + if (of_property_read_bool(dp->dn, "maxlinear,tx-inverted")) + val |= GSW1XX_SGMII_PHY_TX0_CFG3_INVERT; + + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_TX0_CFG3, val); + if (ret < 0) + return ret; + + /* Reset and Release TBI */ + val = GSW1XX_SGMII_TBI_TBICTL_INITTBI | GSW1XX_SGMII_TBI_TBICTL_ENTBI | + GSW1XX_SGMII_TBI_TBICTL_CRSTRR | GSW1XX_SGMII_TBI_TBICTL_CRSOFF; + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_TBI_TBICTL, val); + if (ret < 0) + return ret; + val &= ~GSW1XX_SGMII_TBI_TBICTL_INITTBI; + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_TBI_TBICTL, val); + if (ret < 0) + return ret; + + /* Release Tx Data Buffers */ + ret = regmap_set_bits(priv->sgmii, GSW1XX_SGMII_PCS_TXB_CTL, + GSW1XX_SGMII_PCS_TXB_CTL_INIT_TX_TXB); + if (ret < 0) + return ret; + ret = regmap_clear_bits(priv->sgmii, GSW1XX_SGMII_PCS_TXB_CTL, + GSW1XX_SGMII_PCS_TXB_CTL_INIT_TX_TXB); + if (ret < 0) + return ret; + + /* Release Rx Data Buffers */ + ret = regmap_set_bits(priv->sgmii, GSW1XX_SGMII_PCS_RXB_CTL, + GSW1XX_SGMII_PCS_RXB_CTL_INIT_RX_RXB); + if (ret < 0) + return ret; + ret = regmap_clear_bits(priv->sgmii, GSW1XX_SGMII_PCS_RXB_CTL, + GSW1XX_SGMII_PCS_RXB_CTL_INIT_RX_RXB); + if (ret < 0) + return ret; + +skip_init_reset: + /* override bootstrap pin settings + * OVRANEG sets ANEG Mode, Enable ANEG and restart ANEG to be + * taken from bits ANMODE, ANEGEN, RANEG of the ANEGCTL register. + * OVERABL sets ability bits in tx_config_reg to be taken from + * the TXANEGH and TXANEGL registers. + */ + anegctl = GSW1XX_SGMII_TBI_ANEGCTL_OVRANEG | + GSW1XX_SGMII_TBI_ANEGCTL_OVRABL; + + switch (phylink_get_link_timer_ns(interface)) { + case 10000: + anegctl |= FIELD_PREP(GSW1XX_SGMII_TBI_ANEGCTL_LT, + GSW1XX_SGMII_TBI_ANEGCTL_LT_10US); + break; + case 1600000: + anegctl |= FIELD_PREP(GSW1XX_SGMII_TBI_ANEGCTL_LT, + GSW1XX_SGMII_TBI_ANEGCTL_LT_1_6MS); + break; + case 5000000: + anegctl |= FIELD_PREP(GSW1XX_SGMII_TBI_ANEGCTL_LT, + GSW1XX_SGMII_TBI_ANEGCTL_LT_5MS); + break; + case 10000000: + anegctl |= FIELD_PREP(GSW1XX_SGMII_TBI_ANEGCTL_LT, + GSW1XX_SGMII_TBI_ANEGCTL_LT_10MS); + break; + default: + return -EINVAL; + } + + if (neg_mode & PHYLINK_PCS_NEG_INBAND) + anegctl |= GSW1XX_SGMII_TBI_ANEGCTL_ANEGEN; + + if (interface == PHY_INTERFACE_MODE_SGMII) { + if (sgmii_mac_mode) { + anegctl |= FIELD_PREP(GSW1XX_SGMII_TBI_ANEGCTL_ANMODE, + GSW1XX_SGMII_TBI_ANEGCTL_ANMODE_SGMII_MAC); + txaneg = ADVERTISE_SGMII | ADVERTISE_LPACK; + } else { + /* lacking a defined reverse-SGMII interface mode this + * driver decides whether SGMII (MAC side) or SGMII (PHY side) + * is being used based on the port being a user port. + */ + anegctl |= FIELD_PREP(GSW1XX_SGMII_TBI_ANEGCTL_ANMODE, + GSW1XX_SGMII_TBI_ANEGCTL_ANMODE_SGMII_PHY); + txaneg = LPA_SGMII | LPA_SGMII_1000FULL; + } + } else if (interface == PHY_INTERFACE_MODE_1000BASEX || + interface == PHY_INTERFACE_MODE_2500BASEX) { + anegctl |= FIELD_PREP(GSW1XX_SGMII_TBI_ANEGCTL_ANMODE, + GSW1XX_SGMII_TBI_ANEGCTL_ANMODE_1000BASEX); + txaneg = phylink_mii_c22_pcs_encode_advertisement(interface, + advertising); + } else { + dev_err(priv->gswip.dev, "%s: wrong interface mode %s\n", + __func__, phy_modes(interface)); + return -EINVAL; + } + + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_TBI_TXANEGH, + FIELD_GET(GENMASK(15, 8), txaneg)); + if (ret < 0) + return ret; + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_TBI_TXANEGL, + FIELD_GET(GENMASK(7, 0), txaneg)); + if (ret < 0) + return ret; + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_TBI_ANEGCTL, anegctl); + if (ret < 0) + return ret; + + if (!reconf) { + /* setup SerDes clock speed */ + if (interface == PHY_INTERFACE_MODE_2500BASEX) + nco_ctrl = GSW1XX_SGMII_2G5 | GSW1XX_SGMII_2G5_NCO2; + else + nco_ctrl = GSW1XX_SGMII_1G | GSW1XX_SGMII_1G_NCO1; + + ret = regmap_update_bits(priv->clk, GSW1XX_CLK_NCO_CTRL, + GSW1XX_SGMII_HSP_MASK | + GSW1XX_SGMII_SEL, + nco_ctrl); + if (ret) + return ret; + + ret = gsw1xx_pcs_phy_xaui_write(priv, 0x30, 0x80); + if (ret) + return ret; + } + + /* PCS configuration has now been completed, set mode to prevent + * disrupting the link in case of future calls of this function for the + * same interface mode. + */ + priv->tbi_interface = interface; + + return 0; +} + +static void gsw1xx_pcs_an_restart(struct phylink_pcs *pcs) +{ + struct gsw1xx_priv *priv = pcs_to_gsw1xx(pcs); + + regmap_set_bits(priv->sgmii, GSW1XX_SGMII_TBI_ANEGCTL, + GSW1XX_SGMII_TBI_ANEGCTL_RANEG); +} + +static void gsw1xx_pcs_link_up(struct phylink_pcs *pcs, + unsigned int neg_mode, + phy_interface_t interface, int speed, + int duplex) +{ + struct gsw1xx_priv *priv = pcs_to_gsw1xx(pcs); + u16 lpstat; + + /* When in-band AN is enabled hardware will set lpstat */ + if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) + return; + + /* Force speed and duplex settings */ + if (interface == PHY_INTERFACE_MODE_SGMII) { + if (speed == SPEED_10) + lpstat = FIELD_PREP(GSW1XX_SGMII_TBI_LPSTAT_SPEED, + GSW1XX_SGMII_TBI_LPSTAT_SPEED_10); + else if (speed == SPEED_100) + lpstat = FIELD_PREP(GSW1XX_SGMII_TBI_LPSTAT_SPEED, + GSW1XX_SGMII_TBI_LPSTAT_SPEED_100); + else + lpstat = FIELD_PREP(GSW1XX_SGMII_TBI_LPSTAT_SPEED, + GSW1XX_SGMII_TBI_LPSTAT_SPEED_1000); + } else { + lpstat = FIELD_PREP(GSW1XX_SGMII_TBI_LPSTAT_SPEED, + GSW1XX_SGMII_TBI_LPSTAT_SPEED_NOSGMII); + } + + if (duplex == DUPLEX_FULL) + lpstat |= GSW1XX_SGMII_TBI_LPSTAT_DUPLEX; + + regmap_write(priv->sgmii, GSW1XX_SGMII_TBI_LPSTAT, lpstat); +} + +static const struct phylink_pcs_ops gsw1xx_pcs_ops = { + .pcs_inband_caps = gsw1xx_pcs_inband_caps, + .pcs_enable = gsw1xx_pcs_enable, + .pcs_disable = gsw1xx_pcs_disable, + .pcs_get_state = gsw1xx_pcs_get_state, + .pcs_config = gsw1xx_pcs_config, + .pcs_an_restart = gsw1xx_pcs_an_restart, + .pcs_link_up = gsw1xx_pcs_link_up, +}; + +static void gsw1xx_phylink_get_caps(struct dsa_switch *ds, int port, + struct phylink_config *config) +{ + struct gswip_priv *priv = ds->priv; + + config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | + MAC_10 | MAC_100 | MAC_1000; + + switch (port) { + case 0: + case 1: + case 2: + case 3: + __set_bit(PHY_INTERFACE_MODE_INTERNAL, + config->supported_interfaces); + break; + case 4: /* port 4: SGMII */ + __set_bit(PHY_INTERFACE_MODE_SGMII, + config->supported_interfaces); + __set_bit(PHY_INTERFACE_MODE_1000BASEX, + config->supported_interfaces); + if (priv->hw_info->supports_2500m) { + __set_bit(PHY_INTERFACE_MODE_2500BASEX, + config->supported_interfaces); + config->mac_capabilities |= MAC_2500FD; + } + return; /* no support for EEE on SGMII port */ + case 5: /* port 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)); +} + +static struct phylink_pcs *gsw1xx_phylink_mac_select_pcs(struct phylink_config *config, + phy_interface_t interface) +{ + struct dsa_port *dp = dsa_phylink_to_port(config); + struct gswip_priv *gswip_priv = dp->ds->priv; + struct gsw1xx_priv *gsw1xx_priv = container_of(gswip_priv, + struct gsw1xx_priv, + gswip); + + switch (dp->index) { + case GSW1XX_SGMII_PORT: + return &gsw1xx_priv->pcs; + default: + return NULL; + } +} + +static struct regmap *gsw1xx_regmap_init(struct gsw1xx_priv *priv, + const char *name, + unsigned int reg_base, + unsigned int max_register) +{ + const struct regmap_config config = { + .name = name, + .reg_bits = 16, + .val_bits = 16, + .reg_base = reg_base, + .max_register = max_register, + .lock = gsw1xx_mdio_regmap_lock, + .unlock = gsw1xx_mdio_regmap_unlock, + .lock_arg = &priv->mdio_dev->bus->mdio_lock, + }; + + return devm_regmap_init(&priv->mdio_dev->dev, &gsw1xx_regmap_bus, + priv, &config); +} + +static int gsw1xx_probe(struct mdio_device *mdiodev) +{ + struct device *dev = &mdiodev->dev; + struct gsw1xx_priv *priv; + u32 version; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->mdio_dev = mdiodev; + priv->smdio_badr = GSW1XX_SMDIO_BADR_UNKNOWN; + + priv->gswip.dev = dev; + priv->gswip.hw_info = of_device_get_match_data(dev); + if (!priv->gswip.hw_info) + return -EINVAL; + + priv->gswip.gswip = gsw1xx_regmap_init(priv, "switch", + GSW1XX_SWITCH_BASE, 0xfff); + if (IS_ERR(priv->gswip.gswip)) + return PTR_ERR(priv->gswip.gswip); + + priv->gswip.mdio = gsw1xx_regmap_init(priv, "mdio", GSW1XX_MMDIO_BASE, + 0xff); + if (IS_ERR(priv->gswip.mdio)) + return PTR_ERR(priv->gswip.mdio); + + priv->gswip.mii = gsw1xx_regmap_init(priv, "mii", GSW1XX_RGMII_BASE, + 0xff); + if (IS_ERR(priv->gswip.mii)) + return PTR_ERR(priv->gswip.mii); + + priv->sgmii = gsw1xx_regmap_init(priv, "sgmii", GSW1XX_SGMII_BASE, + 0xfff); + if (IS_ERR(priv->sgmii)) + return PTR_ERR(priv->sgmii); + + priv->gpio = gsw1xx_regmap_init(priv, "gpio", GSW1XX_GPIO_BASE, 0xff); + if (IS_ERR(priv->gpio)) + return PTR_ERR(priv->gpio); + + priv->clk = gsw1xx_regmap_init(priv, "clk", GSW1XX_CLK_BASE, 0xff); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + priv->shell = gsw1xx_regmap_init(priv, "shell", GSW1XX_SHELL_BASE, + 0xff); + 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; + + /* assert SGMII reset to power down SGMII unit */ + ret = regmap_set_bits(priv->shell, GSW1XX_SHELL_RST_REQ, + GSW1XX_RST_REQ_SGMII_SHELL); + if (ret < 0) + return ret; + + /* configure GPIO pin-mux for MMDIO in case of external PHY connected to + * SGMII or RGMII as slave interface + */ + regmap_set_bits(priv->gpio, GPIO_ALTSEL0, 3); + regmap_set_bits(priv->gpio, GPIO_ALTSEL1, 3); + + ret = regmap_read(priv->gswip.gswip, GSWIP_VERSION, &version); + if (ret) + return ret; + + ret = gswip_probe_common(&priv->gswip, version); + if (ret) + return ret; + + dev_set_drvdata(dev, &priv->gswip); + + return 0; +} + +static void gsw1xx_remove(struct mdio_device *mdiodev) +{ + struct gswip_priv *priv = dev_get_drvdata(&mdiodev->dev); + + if (!priv) + return; + + gswip_disable_switch(priv); + + dsa_unregister_switch(priv->ds); +} + +static void gsw1xx_shutdown(struct mdio_device *mdiodev) +{ + struct gswip_priv *priv = dev_get_drvdata(&mdiodev->dev); + + if (!priv) + return; + + dev_set_drvdata(&mdiodev->dev, NULL); + + gswip_disable_switch(priv); +} + +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, + .mac_select_pcs = gsw1xx_phylink_mac_select_pcs, + .phylink_get_caps = &gsw1xx_phylink_get_caps, + .supports_2500m = true, + .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 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, + .mac_select_pcs = gsw1xx_phylink_mac_select_pcs, + .phylink_get_caps = &gsw1xx_phylink_get_caps, + .supports_2500m = true, + .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 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, + .mac_select_pcs = gsw1xx_phylink_mac_select_pcs, + .phylink_get_caps = gsw1xx_phylink_get_caps, + .pce_microcode = &gsw1xx_pce_microcode, + .pce_microcode_size = ARRAY_SIZE(gsw1xx_pce_microcode), + .tag_protocol = DSA_TAG_PROTO_MXL_GSW1XX, +}; + +/* + * GSW125 is the industrial temperature version of GSW120. + * GSW145 is the industrial temperature version of GSW140. + */ +static const struct of_device_id gsw1xx_of_match[] = { + { .compatible = "maxlinear,gsw120", .data = &gsw12x_data }, + { .compatible = "maxlinear,gsw125", .data = &gsw12x_data }, + { .compatible = "maxlinear,gsw140", .data = &gsw140_data }, + { .compatible = "maxlinear,gsw141", .data = &gsw141_data }, + { .compatible = "maxlinear,gsw145", .data = &gsw140_data }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, gsw1xx_of_match); + +static struct mdio_driver gsw1xx_driver = { + .probe = gsw1xx_probe, + .remove = gsw1xx_remove, + .shutdown = gsw1xx_shutdown, + .mdiodrv.driver = { + .name = "mxl-gsw1xx", + .of_match_table = gsw1xx_of_match, + }, +}; + +mdio_module_driver(gsw1xx_driver); + +MODULE_AUTHOR("Daniel Golle <daniel@makrotopia.org>"); +MODULE_DESCRIPTION("Driver for 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 new file mode 100644 index 000000000000..38e03c048a26 --- /dev/null +++ b/drivers/net/dsa/lantiq/mxl-gsw1xx.h @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Register definitions for MaxLinear GSW1xx series switches + * + * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org> + * Copyright (C) 2023 - 2024 MaxLinear Inc. + */ +#ifndef __MXL_GSW1XX_H +#define __MXL_GSW1XX_H + +#include <linux/bitfield.h> + +#define GSW1XX_PORTS 6 +/* Port used for RGMII or optional RMII */ +#define GSW1XX_MII_PORT 5 +/* Port used for SGMII */ +#define GSW1XX_SGMII_PORT 4 + +#define GSW1XX_SYS_CLK_FREQ 340000000 + +/* SMDIO switch register base address */ +#define GSW1XX_SMDIO_BADR 0x1f +#define GSW1XX_SMDIO_BADR_UNKNOWN -1 + +/* GSW1XX SGMII PCS */ +#define GSW1XX_SGMII_BASE 0xd000 +#define GSW1XX_SGMII_PHY_HWBU_CTRL 0x009 +#define GSW1XX_SGMII_PHY_HWBU_CTRL_EN_HWBU_FSM BIT(0) +#define GSW1XX_SGMII_PHY_HWBU_CTRL_HW_FSM_EN BIT(3) +#define GSW1XX_SGMII_TBI_TXANEGH 0x300 +#define GSW1XX_SGMII_TBI_TXANEGL 0x301 +#define GSW1XX_SGMII_TBI_ANEGCTL 0x304 +#define GSW1XX_SGMII_TBI_ANEGCTL_LT GENMASK(1, 0) +#define GSW1XX_SGMII_TBI_ANEGCTL_LT_10US 0 +#define GSW1XX_SGMII_TBI_ANEGCTL_LT_1_6MS 1 +#define GSW1XX_SGMII_TBI_ANEGCTL_LT_5MS 2 +#define GSW1XX_SGMII_TBI_ANEGCTL_LT_10MS 3 +#define GSW1XX_SGMII_TBI_ANEGCTL_ANEGEN BIT(2) +#define GSW1XX_SGMII_TBI_ANEGCTL_RANEG BIT(3) +#define GSW1XX_SGMII_TBI_ANEGCTL_OVRABL BIT(4) +#define GSW1XX_SGMII_TBI_ANEGCTL_OVRANEG BIT(5) +#define GSW1XX_SGMII_TBI_ANEGCTL_ANMODE GENMASK(7, 6) +#define GSW1XX_SGMII_TBI_ANEGCTL_ANMODE_1000BASEX 1 +#define GSW1XX_SGMII_TBI_ANEGCTL_ANMODE_SGMII_PHY 2 +#define GSW1XX_SGMII_TBI_ANEGCTL_ANMODE_SGMII_MAC 3 +#define GSW1XX_SGMII_TBI_ANEGCTL_BCOMP BIT(15) + +#define GSW1XX_SGMII_TBI_TBICTL 0x305 +#define GSW1XX_SGMII_TBI_TBICTL_INITTBI BIT(0) +#define GSW1XX_SGMII_TBI_TBICTL_ENTBI BIT(1) +#define GSW1XX_SGMII_TBI_TBICTL_CRSTRR BIT(4) +#define GSW1XX_SGMII_TBI_TBICTL_CRSOFF BIT(5) +#define GSW1XX_SGMII_TBI_TBISTAT 0x309 +#define GSW1XX_SGMII_TBI_TBISTAT_LINK BIT(0) +#define GSW1XX_SGMII_TBI_TBISTAT_AN_COMPLETE BIT(1) +#define GSW1XX_SGMII_TBI_LPSTAT 0x30a +#define GSW1XX_SGMII_TBI_LPSTAT_DUPLEX BIT(0) +#define GSW1XX_SGMII_TBI_LPSTAT_PAUSE_RX BIT(1) +#define GSW1XX_SGMII_TBI_LPSTAT_PAUSE_TX BIT(2) +#define GSW1XX_SGMII_TBI_LPSTAT_SPEED GENMASK(6, 5) +#define GSW1XX_SGMII_TBI_LPSTAT_SPEED_10 0 +#define GSW1XX_SGMII_TBI_LPSTAT_SPEED_100 1 +#define GSW1XX_SGMII_TBI_LPSTAT_SPEED_1000 2 +#define GSW1XX_SGMII_TBI_LPSTAT_SPEED_NOSGMII 3 +#define GSW1XX_SGMII_PHY_D 0x100 +#define GSW1XX_SGMII_PHY_A 0x101 +#define GSW1XX_SGMII_PHY_C 0x102 +#define GSW1XX_SGMII_PHY_STATUS BIT(0) +#define GSW1XX_SGMII_PHY_READ BIT(4) +#define GSW1XX_SGMII_PHY_WRITE BIT(8) +#define GSW1XX_SGMII_PHY_RESET_N BIT(12) +#define GSW1XX_SGMII_PCS_RXB_CTL 0x401 +#define GSW1XX_SGMII_PCS_RXB_CTL_INIT_RX_RXB BIT(1) +#define GSW1XX_SGMII_PCS_TXB_CTL 0x404 +#define GSW1XX_SGMII_PCS_TXB_CTL_INIT_TX_TXB BIT(1) + +#define GSW1XX_SGMII_PHY_RX0_CFG2 0x004 +#define GSW1XX_SGMII_PHY_RX0_CFG2_EQ GENMASK(2, 0) +#define GSW1XX_SGMII_PHY_RX0_CFG2_EQ_DEF 2 +#define GSW1XX_SGMII_PHY_RX0_CFG2_INVERT BIT(3) +#define GSW1XX_SGMII_PHY_RX0_CFG2_LOS_EN BIT(4) +#define GSW1XX_SGMII_PHY_RX0_CFG2_TERM_EN BIT(5) +#define GSW1XX_SGMII_PHY_RX0_CFG2_FILT_CNT GENMASK(12, 6) +#define GSW1XX_SGMII_PHY_RX0_CFG2_FILT_CNT_DEF 20 + +#define GSW1XX_SGMII_PHY_TX0_CFG3 0x007 +#define GSW1XX_SGMII_PHY_TX0_CFG3_VBOOST_EN BIT(12) +#define GSW1XX_SGMII_PHY_TX0_CFG3_VBOOST_LEVEL GENMASK(11, 9) +#define GSW1XX_SGMII_PHY_TX0_CFG3_VBOOST_LEVEL_DEF 4 +#define GSW1XX_SGMII_PHY_TX0_CFG3_INVERT BIT(8) + +/* GSW1XX PDI Registers */ +#define GSW1XX_SWITCH_BASE 0xe000 + +/* GSW1XX MII Registers */ +#define GSW1XX_RGMII_BASE 0xf100 + +/* GSW1XX GPIO Registers */ +#define GSW1XX_GPIO_BASE 0xf300 +#define GPIO_ALTSEL0 0x83 +#define GPIO_ALTSEL0_EXTPHY_MUX_VAL 0x03c3 +#define GPIO_ALTSEL1 0x84 +#define GPIO_ALTSEL1_EXTPHY_MUX_VAL 0x003f + +/* MDIO bus controller */ +#define GSW1XX_MMDIO_BASE 0xf400 + +/* generic IC registers */ +#define GSW1XX_SHELL_BASE 0xfa00 +#define GSW1XX_SHELL_RST_REQ 0x01 +#define GSW1XX_RST_REQ_SGMII_SHELL BIT(5) +/* RGMII PAD Slew Control Register */ +#define GSW1XX_SHELL_RGMII_SLEW_CFG 0x78 +#define RGMII_SLEW_CFG_RX_2_5_V BIT(4) +#define RGMII_SLEW_CFG_TX_2_5_V BIT(5) + +/* SGMII clock related settings */ +#define GSW1XX_CLK_BASE 0xf900 +#define GSW1XX_CLK_NCO_CTRL 0x68 +#define GSW1XX_SGMII_HSP_MASK GENMASK(3, 2) +#define GSW1XX_SGMII_SEL BIT(1) +#define GSW1XX_SGMII_1G 0x0 +#define GSW1XX_SGMII_2G5 0xc +#define GSW1XX_SGMII_1G_NCO1 0x0 +#define GSW1XX_SGMII_2G5_NCO2 0x2 + +#endif /* __MXL_GSW1XX_H */ diff --git a/drivers/net/dsa/lantiq/mxl-gsw1xx_pce.h b/drivers/net/dsa/lantiq/mxl-gsw1xx_pce.h new file mode 100644 index 000000000000..eefcd411a340 --- /dev/null +++ b/drivers/net/dsa/lantiq/mxl-gsw1xx_pce.h @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * PCE microcode code update for driver for MaxLinear GSW1xx switch chips + * + * Copyright (C) 2023 - 2024 MaxLinear Inc. + * Copyright (C) 2022 Snap One, LLC. All rights reserved. + * Copyright (C) 2017 - 2019 Hauke Mehrtens <hauke@hauke-m.de> + * Copyright (C) 2012 John Crispin <john@phrozen.org> + * Copyright (C) 2010 Lantiq Deutschland + */ + +#include "lantiq_gswip.h" + +#define INSTR 0 +#define IPV6 1 +#define LENACCU 2 + +/* GSWIP_2.X */ +enum { + OUT_MAC0 = 0, + OUT_MAC1, + OUT_MAC2, + OUT_MAC3, + OUT_MAC4, + OUT_MAC5, + OUT_ETHTYP, + OUT_VTAG0, + OUT_VTAG1, + OUT_ITAG0, + OUT_ITAG1, /* 10 */ + OUT_ITAG2, + OUT_ITAG3, + OUT_IP0, + OUT_IP1, + OUT_IP2, + OUT_IP3, + OUT_SIP0, + OUT_SIP1, + OUT_SIP2, + OUT_SIP3, /* 20 */ + OUT_SIP4, + OUT_SIP5, + OUT_SIP6, + OUT_SIP7, + OUT_DIP0, + OUT_DIP1, + OUT_DIP2, + OUT_DIP3, + OUT_DIP4, + OUT_DIP5, /* 30 */ + OUT_DIP6, + OUT_DIP7, + OUT_SESID, + OUT_PROT, + OUT_APP0, + OUT_APP1, + OUT_IGMP0, + OUT_IGMP1, + OUT_STAG0 = 61, + OUT_STAG1 = 62, + OUT_NONE = 63, +}; + +/* parser's microcode flag type */ +enum { + FLAG_ITAG = 0, + FLAG_VLAN, + FLAG_SNAP, + FLAG_PPPOE, + FLAG_IPV6, + FLAG_IPV6FL, + FLAG_IPV4, + FLAG_IGMP, + FLAG_TU, + FLAG_HOP, + FLAG_NN1, /* 10 */ + FLAG_NN2, + FLAG_END, + FLAG_NO, /* 13 */ + FLAG_SVLAN, /* 14 */ +}; + +#define PCE_MC_M(val, msk, ns, out, len, type, flags, ipv4_len) \ + { (val), (msk), ((ns) << 10 | (out) << 4 | (len) >> 1),\ + ((len) & 1) << 15 | (type) << 13 | (flags) << 9 | (ipv4_len) << 8 } + +/* V22_2X (IPv6 issue fixed) */ +static const struct gswip_pce_microcode gsw1xx_pce_microcode[] = { + /* value mask ns fields L type flags ipv4_len */ + PCE_MC_M(0x88c3, 0xFFFF, 1, OUT_ITAG0, 4, INSTR, FLAG_ITAG, 0), + PCE_MC_M(0x8100, 0xFFFF, 4, OUT_STAG0, 2, INSTR, FLAG_SVLAN, 0), + PCE_MC_M(0x88A8, 0xFFFF, 4, OUT_STAG0, 2, INSTR, FLAG_SVLAN, 0), + PCE_MC_M(0x9100, 0xFFFF, 4, OUT_STAG0, 2, INSTR, FLAG_SVLAN, 0), + PCE_MC_M(0x8100, 0xFFFF, 5, OUT_VTAG0, 2, INSTR, FLAG_VLAN, 0), + PCE_MC_M(0x88A8, 0xFFFF, 6, OUT_VTAG0, 2, INSTR, FLAG_VLAN, 0), + PCE_MC_M(0x9100, 0xFFFF, 4, OUT_VTAG0, 2, INSTR, FLAG_VLAN, 0), + PCE_MC_M(0x8864, 0xFFFF, 20, OUT_ETHTYP, 1, INSTR, FLAG_NO, 0), + PCE_MC_M(0x0800, 0xFFFF, 24, OUT_ETHTYP, 1, INSTR, FLAG_NO, 0), + PCE_MC_M(0x86DD, 0xFFFF, 25, OUT_ETHTYP, 1, INSTR, FLAG_NO, 0), + PCE_MC_M(0x8863, 0xFFFF, 19, OUT_ETHTYP, 1, INSTR, FLAG_NO, 0), + PCE_MC_M(0x0000, 0xF800, 13, OUT_NONE, 0, INSTR, FLAG_NO, 0), + PCE_MC_M(0x0000, 0x0000, 44, OUT_ETHTYP, 1, INSTR, FLAG_NO, 0), + PCE_MC_M(0x0600, 0x0600, 44, OUT_ETHTYP, 1, INSTR, FLAG_NO, 0), + PCE_MC_M(0x0000, 0x0000, 15, OUT_NONE, 1, INSTR, FLAG_NO, 0), + PCE_MC_M(0xAAAA, 0xFFFF, 17, OUT_NONE, 1, INSTR, FLAG_NO, 0), + PCE_MC_M(0x0000, 0x0000, 45, OUT_NONE, 0, INSTR, FLAG_NO, 0), + PCE_MC_M(0x0300, 0xFF00, 45, OUT_NONE, 0, INSTR, FLAG_SNAP, 0), + PCE_MC_M(0x0000, 0x0000, 45, OUT_NONE, 0, INSTR, FLAG_NO, 0), + PCE_MC_M(0x0000, 0x0000, 45, OUT_DIP7, 3, INSTR, FLAG_NO, 0), + PCE_MC_M(0x0000, 0x0000, 21, OUT_DIP7, 3, INSTR, FLAG_PPPOE, 0), + PCE_MC_M(0x0021, 0xFFFF, 24, OUT_NONE, 1, INSTR, FLAG_NO, 0), + PCE_MC_M(0x0057, 0xFFFF, 25, OUT_NONE, 1, INSTR, FLAG_NO, 0), + PCE_MC_M(0x0000, 0x0000, 44, OUT_NONE, 0, INSTR, FLAG_NO, 0), + PCE_MC_M(0x4000, 0xF000, 27, OUT_IP0, 4, INSTR, FLAG_IPV4, 1), + PCE_MC_M(0x6000, 0xF000, 30, OUT_IP0, 3, INSTR, FLAG_IPV6, 0), + PCE_MC_M(0x0000, 0x0000, 45, OUT_NONE, 0, INSTR, FLAG_NO, 0), + PCE_MC_M(0x0000, 0x0000, 28, OUT_IP3, 2, INSTR, FLAG_NO, 0), + PCE_MC_M(0x0000, 0x0000, 29, OUT_SIP0, 4, INSTR, FLAG_NO, 0), + PCE_MC_M(0x0000, 0x0000, 44, OUT_NONE, 0, LENACCU, FLAG_NO, 0), + PCE_MC_M(0x1100, 0xFF00, 43, OUT_PROT, 1, INSTR, FLAG_NO, 0), + PCE_MC_M(0x0600, 0xFF00, 43, OUT_PROT, 1, INSTR, FLAG_NO, 0), + PCE_MC_M(0x0000, 0xFF00, 36, OUT_IP3, 17, INSTR, FLAG_HOP, 0), + PCE_MC_M(0x2B00, 0xFF00, 36, OUT_IP3, 17, INSTR, FLAG_NN1, 0), + PCE_MC_M(0x3C00, 0xFF00, 36, OUT_IP3, 17, INSTR, FLAG_NN2, 0), + PCE_MC_M(0x0000, 0x0000, 43, OUT_PROT, 1, INSTR, FLAG_NO, 0), + PCE_MC_M(0x0000, 0x00F0, 38, OUT_NONE, 0, INSTR, FLAG_NO, 0), + PCE_MC_M(0x0000, 0x0000, 44, OUT_NONE, 0, INSTR, FLAG_NO, 0), + PCE_MC_M(0x0000, 0xFF00, 36, OUT_NONE, 0, IPV6, FLAG_HOP, 0), + PCE_MC_M(0x2B00, 0xFF00, 36, OUT_NONE, 0, IPV6, FLAG_NN1, 0), + PCE_MC_M(0x3C00, 0xFF00, 36, OUT_NONE, 0, IPV6, FLAG_NN2, 0), + PCE_MC_M(0x0000, 0x00FC, 44, OUT_PROT, 0, IPV6, FLAG_NO, 0), + PCE_MC_M(0x0000, 0x0000, 44, OUT_NONE, 0, IPV6, FLAG_NO, 0), + PCE_MC_M(0x0000, 0x0000, 44, OUT_SIP0, 16, INSTR, FLAG_NO, 0), + PCE_MC_M(0x0000, 0x0000, 45, OUT_APP0, 4, INSTR, FLAG_IGMP, 0), + PCE_MC_M(0x0000, 0x0000, 45, OUT_NONE, 0, INSTR, FLAG_END, 0), + PCE_MC_M(0x0000, 0x0000, 45, OUT_NONE, 0, INSTR, FLAG_END, 0), + PCE_MC_M(0x0000, 0x0000, 45, OUT_NONE, 0, INSTR, FLAG_END, 0), + PCE_MC_M(0x0000, 0x0000, 45, OUT_NONE, 0, INSTR, FLAG_END, 0), + PCE_MC_M(0x0000, 0x0000, 45, OUT_NONE, 0, INSTR, FLAG_END, 0), + PCE_MC_M(0x0000, 0x0000, 45, OUT_NONE, 0, INSTR, FLAG_END, 0), + PCE_MC_M(0x0000, 0x0000, 45, OUT_NONE, 0, INSTR, FLAG_END, 0), + PCE_MC_M(0x0000, 0x0000, 45, OUT_NONE, 0, INSTR, FLAG_END, 0), + PCE_MC_M(0x0000, 0x0000, 45, OUT_NONE, 0, INSTR, FLAG_END, 0), + PCE_MC_M(0x0000, 0x0000, 45, OUT_NONE, 0, INSTR, FLAG_END, 0), + PCE_MC_M(0x0000, 0x0000, 45, OUT_NONE, 0, INSTR, FLAG_END, 0), + PCE_MC_M(0x0000, 0x0000, 45, OUT_NONE, 0, INSTR, FLAG_END, 0), + PCE_MC_M(0x0000, 0x0000, 45, OUT_NONE, 0, INSTR, FLAG_END, 0), + PCE_MC_M(0x0000, 0x0000, 45, OUT_NONE, 0, INSTR, FLAG_END, 0), + PCE_MC_M(0x0000, 0x0000, 45, OUT_NONE, 0, INSTR, FLAG_END, 0), + PCE_MC_M(0x0000, 0x0000, 45, OUT_NONE, 0, INSTR, FLAG_END, 0), + PCE_MC_M(0x0000, 0x0000, 45, OUT_NONE, 0, INSTR, FLAG_END, 0), + PCE_MC_M(0x0000, 0x0000, 45, OUT_NONE, 0, INSTR, FLAG_END, 0), + PCE_MC_M(0x0000, 0x0000, 45, OUT_NONE, 0, INSTR, FLAG_END, 0), +}; -- 2.51.1 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v3 12/12] net: dsa: add driver for MaxLinear GSW1xx switch family 2025-10-26 23:49 ` [PATCH net-next v3 12/12] net: dsa: add " Daniel Golle @ 2025-10-28 1:24 ` Vladimir Oltean 2025-10-28 2:14 ` Daniel Golle 0 siblings, 1 reply; 35+ messages in thread From: Vladimir Oltean @ 2025-10-28 1:24 UTC (permalink / raw) To: Daniel Golle Cc: Hauke Mehrtens, Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel, Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin On Sun, Oct 26, 2025 at 11:49:10PM +0000, Daniel Golle wrote: > Add driver for the MaxLinear GSW1xx family of Ethernet switch ICs which > are based on the same IP as the Lantiq/Intel GSWIP found in the Lantiq VR9 > and Intel GRX MIPS router SoCs. The main difference is that instead of > using memory-mapped I/O to communicate with the host CPU these ICs are > connected via MDIO (or SPI, which isn't supported by this driver). > Implement the regmap API to access the switch registers over MDIO to allow > reusing lantiq_gswip_common for all core functionality. > > The GSW1xx also comes with a SerDes port capable of 1000Base-X, SGMII and > 2500Base-X, which can either be used to connect an external PHY or SFP > cage, or as the CPU port. Support for the SerDes interface is implemented > in this driver using the phylink_pcs interface. I opened the GSW145 datasheet and it seems borderline in terms of what I'd suggest to implement via MFD, keeping the DSA driver to be just for the switch fabric, vs implementing everything in the DSA driver. Just to know what to expect in the future. Are there higher-spec'd switches with an embedded CPU, waiting to be supported by Linux? Linux running outside, but also potentially inside? Maybe you'll need full-fledged clock, pinmux, GPIO drivers, due to IPs reused in other parts? Interrupt controller support? The SGMII "PHY" block also seems distinct from the "PCS" block, more like a driver in drivers/phy/ would control. > + > +static int gsw1xx_pcs_phy_xaui_write(struct gsw1xx_priv *priv, u16 addr, > + u16 data) > +{ > + int ret, val; > + > + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_D, data); > + if (ret < 0) > + return ret; > + > + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_A, addr); > + if (ret < 0) > + return ret; > + > + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_C, > + GSW1XX_SGMII_PHY_WRITE | > + GSW1XX_SGMII_PHY_RESET_N); > + if (ret < 0) > + return ret; > + > + return regmap_read_poll_timeout(priv->sgmii, GSW1XX_SGMII_PHY_C, > + val, val & GSW1XX_SGMII_PHY_STATUS, > + 1000, 100000); > +} > + > +static int gsw1xx_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, > + phy_interface_t interface, > + const unsigned long *advertising, > + bool permit_pause_to_mac) > +{ > + struct gsw1xx_priv *priv = pcs_to_gsw1xx(pcs); > + bool sgmii_mac_mode = dsa_is_user_port(priv->gswip.ds, > + GSW1XX_SGMII_PORT); In lack of the phy-mode = "revsgmii" that you also mention, can we just assume that any port with phy-mode = "sgmii" is in "MAC mode"? > + struct dsa_port *dp = dsa_to_port(priv->gswip.ds, > + GSW1XX_SGMII_PORT); > + u16 txaneg, anegctl, val, nco_ctrl; > + bool reconf = false; > + int ret; > + > + /* do not unnecessarily disrupt link and skip resetting the hardware in > + * case the PCS has previously been successfully configured for this > + * interface mode > + */ > + if (priv->tbi_interface == interface) > + reconf = true; > + > + /* mark PCS configuration as incomplete */ > + priv->tbi_interface = PHY_INTERFACE_MODE_NA; > + > + if (reconf) > + goto skip_init_reset; > + > + /* Assert and deassert SGMII shell reset */ > + ret = regmap_set_bits(priv->shell, GSW1XX_SHELL_RST_REQ, > + GSW1XX_RST_REQ_SGMII_SHELL); > + if (ret < 0) > + return ret; > + > + ret = regmap_clear_bits(priv->shell, GSW1XX_SHELL_RST_REQ, > + GSW1XX_RST_REQ_SGMII_SHELL); > + if (ret < 0) > + return ret; > + > + /* Hardware Bringup FSM Enable */ > + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_HWBU_CTRL, > + GSW1XX_SGMII_PHY_HWBU_CTRL_EN_HWBU_FSM | > + GSW1XX_SGMII_PHY_HWBU_CTRL_HW_FSM_EN); > + if (ret < 0) > + return ret; > + > + /* Configure SGMII PHY Receiver */ > + val = FIELD_PREP(GSW1XX_SGMII_PHY_RX0_CFG2_EQ, > + GSW1XX_SGMII_PHY_RX0_CFG2_EQ_DEF) | > + GSW1XX_SGMII_PHY_RX0_CFG2_LOS_EN | > + GSW1XX_SGMII_PHY_RX0_CFG2_TERM_EN | > + FIELD_PREP(GSW1XX_SGMII_PHY_RX0_CFG2_FILT_CNT, > + GSW1XX_SGMII_PHY_RX0_CFG2_FILT_CNT_DEF); > + > + if (of_property_read_bool(dp->dn, "maxlinear,rx-inverted")) > + val |= GSW1XX_SGMII_PHY_RX0_CFG2_INVERT; > + > + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_RX0_CFG2, val); > + if (ret < 0) > + return ret; > + > + val = FIELD_PREP(GSW1XX_SGMII_PHY_TX0_CFG3_VBOOST_LEVEL, > + GSW1XX_SGMII_PHY_TX0_CFG3_VBOOST_LEVEL_DEF); > + > + if (of_property_read_bool(dp->dn, "maxlinear,tx-inverted")) > + val |= GSW1XX_SGMII_PHY_TX0_CFG3_INVERT; > + > + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_TX0_CFG3, val); > + if (ret < 0) > + return ret; > + > + /* Reset and Release TBI */ > + val = GSW1XX_SGMII_TBI_TBICTL_INITTBI | GSW1XX_SGMII_TBI_TBICTL_ENTBI | > + GSW1XX_SGMII_TBI_TBICTL_CRSTRR | GSW1XX_SGMII_TBI_TBICTL_CRSOFF; > + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_TBI_TBICTL, val); > + if (ret < 0) > + return ret; > + val &= ~GSW1XX_SGMII_TBI_TBICTL_INITTBI; > + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_TBI_TBICTL, val); > + if (ret < 0) > + return ret; > + > + /* Release Tx Data Buffers */ > + ret = regmap_set_bits(priv->sgmii, GSW1XX_SGMII_PCS_TXB_CTL, > + GSW1XX_SGMII_PCS_TXB_CTL_INIT_TX_TXB); > + if (ret < 0) > + return ret; > + ret = regmap_clear_bits(priv->sgmii, GSW1XX_SGMII_PCS_TXB_CTL, > + GSW1XX_SGMII_PCS_TXB_CTL_INIT_TX_TXB); > + if (ret < 0) > + return ret; > + > + /* Release Rx Data Buffers */ > + ret = regmap_set_bits(priv->sgmii, GSW1XX_SGMII_PCS_RXB_CTL, > + GSW1XX_SGMII_PCS_RXB_CTL_INIT_RX_RXB); > + if (ret < 0) > + return ret; > + ret = regmap_clear_bits(priv->sgmii, GSW1XX_SGMII_PCS_RXB_CTL, > + GSW1XX_SGMII_PCS_RXB_CTL_INIT_RX_RXB); > + if (ret < 0) > + return ret; > + > +skip_init_reset: > + /* override bootstrap pin settings > + * OVRANEG sets ANEG Mode, Enable ANEG and restart ANEG to be > + * taken from bits ANMODE, ANEGEN, RANEG of the ANEGCTL register. > + * OVERABL sets ability bits in tx_config_reg to be taken from > + * the TXANEGH and TXANEGL registers. > + */ > + anegctl = GSW1XX_SGMII_TBI_ANEGCTL_OVRANEG | > + GSW1XX_SGMII_TBI_ANEGCTL_OVRABL; > + > + switch (phylink_get_link_timer_ns(interface)) { > + case 10000: > + anegctl |= FIELD_PREP(GSW1XX_SGMII_TBI_ANEGCTL_LT, > + GSW1XX_SGMII_TBI_ANEGCTL_LT_10US); > + break; > + case 1600000: > + anegctl |= FIELD_PREP(GSW1XX_SGMII_TBI_ANEGCTL_LT, > + GSW1XX_SGMII_TBI_ANEGCTL_LT_1_6MS); > + break; > + case 5000000: > + anegctl |= FIELD_PREP(GSW1XX_SGMII_TBI_ANEGCTL_LT, > + GSW1XX_SGMII_TBI_ANEGCTL_LT_5MS); > + break; > + case 10000000: > + anegctl |= FIELD_PREP(GSW1XX_SGMII_TBI_ANEGCTL_LT, > + GSW1XX_SGMII_TBI_ANEGCTL_LT_10MS); > + break; > + default: > + return -EINVAL; > + } > + > + if (neg_mode & PHYLINK_PCS_NEG_INBAND) > + anegctl |= GSW1XX_SGMII_TBI_ANEGCTL_ANEGEN; > + > + if (interface == PHY_INTERFACE_MODE_SGMII) { > + if (sgmii_mac_mode) { > + anegctl |= FIELD_PREP(GSW1XX_SGMII_TBI_ANEGCTL_ANMODE, > + GSW1XX_SGMII_TBI_ANEGCTL_ANMODE_SGMII_MAC); > + txaneg = ADVERTISE_SGMII | ADVERTISE_LPACK; > + } else { > + /* lacking a defined reverse-SGMII interface mode this > + * driver decides whether SGMII (MAC side) or SGMII (PHY side) > + * is being used based on the port being a user port. > + */ > + anegctl |= FIELD_PREP(GSW1XX_SGMII_TBI_ANEGCTL_ANMODE, > + GSW1XX_SGMII_TBI_ANEGCTL_ANMODE_SGMII_PHY); > + txaneg = LPA_SGMII | LPA_SGMII_1000FULL; > + } > + } else if (interface == PHY_INTERFACE_MODE_1000BASEX || > + interface == PHY_INTERFACE_MODE_2500BASEX) { > + anegctl |= FIELD_PREP(GSW1XX_SGMII_TBI_ANEGCTL_ANMODE, > + GSW1XX_SGMII_TBI_ANEGCTL_ANMODE_1000BASEX); > + txaneg = phylink_mii_c22_pcs_encode_advertisement(interface, > + advertising); > + } else { > + dev_err(priv->gswip.dev, "%s: wrong interface mode %s\n", > + __func__, phy_modes(interface)); > + return -EINVAL; > + } > + > + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_TBI_TXANEGH, > + FIELD_GET(GENMASK(15, 8), txaneg)); > + if (ret < 0) > + return ret; > + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_TBI_TXANEGL, > + FIELD_GET(GENMASK(7, 0), txaneg)); > + if (ret < 0) > + return ret; > + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_TBI_ANEGCTL, anegctl); > + if (ret < 0) > + return ret; > + > + if (!reconf) { > + /* setup SerDes clock speed */ > + if (interface == PHY_INTERFACE_MODE_2500BASEX) > + nco_ctrl = GSW1XX_SGMII_2G5 | GSW1XX_SGMII_2G5_NCO2; > + else > + nco_ctrl = GSW1XX_SGMII_1G | GSW1XX_SGMII_1G_NCO1; > + > + ret = regmap_update_bits(priv->clk, GSW1XX_CLK_NCO_CTRL, > + GSW1XX_SGMII_HSP_MASK | > + GSW1XX_SGMII_SEL, > + nco_ctrl); > + if (ret) > + return ret; > + > + ret = gsw1xx_pcs_phy_xaui_write(priv, 0x30, 0x80); > + if (ret) > + return ret; > + } > + > + /* PCS configuration has now been completed, set mode to prevent > + * disrupting the link in case of future calls of this function for the > + * same interface mode. > + */ > + priv->tbi_interface = interface; > + > + return 0; > +} Can you split up this function in multiple smaller logical blocks? The control flow with "reconf" and "skip_init_reset" is a bit difficult to follow. I can't say I understood what's going on. Ideally gsw1xx_pcs_config() fits in one-two screen. > +static int gsw1xx_probe(struct mdio_device *mdiodev) > +{ > + struct device *dev = &mdiodev->dev; > + struct gsw1xx_priv *priv; > + u32 version; > + int ret; > + > + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + priv->mdio_dev = mdiodev; > + priv->smdio_badr = GSW1XX_SMDIO_BADR_UNKNOWN; > + > + priv->gswip.dev = dev; > + priv->gswip.hw_info = of_device_get_match_data(dev); > + if (!priv->gswip.hw_info) > + return -EINVAL; > + > + priv->gswip.gswip = gsw1xx_regmap_init(priv, "switch", > + GSW1XX_SWITCH_BASE, 0xfff); > + if (IS_ERR(priv->gswip.gswip)) > + return PTR_ERR(priv->gswip.gswip); > + > + priv->gswip.mdio = gsw1xx_regmap_init(priv, "mdio", GSW1XX_MMDIO_BASE, > + 0xff); > + if (IS_ERR(priv->gswip.mdio)) > + return PTR_ERR(priv->gswip.mdio); > + > + priv->gswip.mii = gsw1xx_regmap_init(priv, "mii", GSW1XX_RGMII_BASE, > + 0xff); > + if (IS_ERR(priv->gswip.mii)) > + return PTR_ERR(priv->gswip.mii); > + > + priv->sgmii = gsw1xx_regmap_init(priv, "sgmii", GSW1XX_SGMII_BASE, > + 0xfff); > + if (IS_ERR(priv->sgmii)) > + return PTR_ERR(priv->sgmii); > + > + priv->gpio = gsw1xx_regmap_init(priv, "gpio", GSW1XX_GPIO_BASE, 0xff); > + if (IS_ERR(priv->gpio)) > + return PTR_ERR(priv->gpio); > + > + priv->clk = gsw1xx_regmap_init(priv, "clk", GSW1XX_CLK_BASE, 0xff); > + if (IS_ERR(priv->clk)) > + return PTR_ERR(priv->clk); > + > + priv->shell = gsw1xx_regmap_init(priv, "shell", GSW1XX_SHELL_BASE, > + 0xff); > + 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; > + > + /* assert SGMII reset to power down SGMII unit */ > + ret = regmap_set_bits(priv->shell, GSW1XX_SHELL_RST_REQ, > + GSW1XX_RST_REQ_SGMII_SHELL); > + if (ret < 0) > + return ret; > + > + /* configure GPIO pin-mux for MMDIO in case of external PHY connected to Can you explain that MMDIO stands for MDIO master interface? On first sight it looks like a typo. > + * SGMII or RGMII as slave interface > + */ > + regmap_set_bits(priv->gpio, GPIO_ALTSEL0, 3); > + regmap_set_bits(priv->gpio, GPIO_ALTSEL1, 3); > + > + ret = regmap_read(priv->gswip.gswip, GSWIP_VERSION, &version); > + if (ret) > + return ret; > + > + ret = gswip_probe_common(&priv->gswip, version); > + if (ret) > + return ret; > + > + dev_set_drvdata(dev, &priv->gswip); > + > + return 0; > +} ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH net-next v3 12/12] net: dsa: add driver for MaxLinear GSW1xx switch family 2025-10-28 1:24 ` Vladimir Oltean @ 2025-10-28 2:14 ` Daniel Golle 0 siblings, 0 replies; 35+ messages in thread From: Daniel Golle @ 2025-10-28 2:14 UTC (permalink / raw) To: Vladimir Oltean Cc: Hauke Mehrtens, Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman, Russell King, netdev, devicetree, linux-kernel, Andreas Schirm, Lukas Stockmann, Alexander Sverdlin, Peter Christen, Avinash Jayaraman, Bing tao Xu, Liang Xu, Juraj Povazanec, Fanni (Fang-Yi) Chan, Benny (Ying-Tsan) Weng, Livia M. Rosu, John Crispin On Tue, Oct 28, 2025 at 03:24:30AM +0200, Vladimir Oltean wrote: > On Sun, Oct 26, 2025 at 11:49:10PM +0000, Daniel Golle wrote: > > Add driver for the MaxLinear GSW1xx family of Ethernet switch ICs which > > are based on the same IP as the Lantiq/Intel GSWIP found in the Lantiq VR9 > > and Intel GRX MIPS router SoCs. The main difference is that instead of > > using memory-mapped I/O to communicate with the host CPU these ICs are > > connected via MDIO (or SPI, which isn't supported by this driver). > > Implement the regmap API to access the switch registers over MDIO to allow > > reusing lantiq_gswip_common for all core functionality. > > > > The GSW1xx also comes with a SerDes port capable of 1000Base-X, SGMII and > > 2500Base-X, which can either be used to connect an external PHY or SFP > > cage, or as the CPU port. Support for the SerDes interface is implemented > > in this driver using the phylink_pcs interface. > > I opened the GSW145 datasheet and it seems borderline in terms of what > I'd suggest to implement via MFD, keeping the DSA driver to be just for > the switch fabric, vs implementing everything in the DSA driver. > > Just to know what to expect in the future. Are there higher-spec'd > switches with an embedded CPU, waiting to be supported by Linux? In terms of dedicated switches the short answer is "no". The Lantiq/Intel/MaxLinear GSWIP family afaik ends with GSWIP 3.0 which can be found in some of the PON SoCs build around the Intel Atom which are marketed by MaxLinear. Supporting also those is on my agenda once I get hold of the hardware. The switch IP found in those SoCs generally is still just an improved and extended PCE, just with more tables and more table entries to implement layer-3 features like PPPoE, NAT and flow-offloading all within the switch-part of the SoC. > Linux running outside, but also potentially inside? No. The GSW1xx switches and the switch IP found inside those SoCs are still basically programmable store-(modify-)and-forward ASICs rathern than general purpose processors with offloading paths. > Maybe you'll need full-fledged clock, pinmux, GPIO drivers, due to IPs > reused in other parts? Interrupt controller support? The SGMII "PHY" > block also seems distinct from the "PCS" block, more like a driver in > drivers/phy/ would control. I don't think we'll see those blocks in anything else than those dedicated switch ICs. Newer MaxLinear switches (with more than 1 Gbit/s TP ports) are completely different animals, they do run an RTOS on a general purpose CPU internally, and offer a complex API to be used by the host rather than allowing raw access to the internal registers. They can even be turned into a standalone web-managed switch, ie. the CPU is capable of providing a HTTP server, SNMP, ... It is, of course, possible that some parts of GSW1xx series may or may not have been reused, but as the RTOS running on those MxL862xx chips is proprietary and there is no documentation of the bare-metal hardware it is impossible for me to tell. What I can tell for sure that there isn't any external DRAM, so they won't ever run Linux for resource reasons. > > > + > > +static int gsw1xx_pcs_phy_xaui_write(struct gsw1xx_priv *priv, u16 addr, > > + u16 data) > > +{ > > + int ret, val; > > + > > + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_D, data); > > + if (ret < 0) > > + return ret; > > + > > + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_A, addr); > > + if (ret < 0) > > + return ret; > > + > > + ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_C, > > + GSW1XX_SGMII_PHY_WRITE | > > + GSW1XX_SGMII_PHY_RESET_N); > > + if (ret < 0) > > + return ret; > > + > > + return regmap_read_poll_timeout(priv->sgmii, GSW1XX_SGMII_PHY_C, > > + val, val & GSW1XX_SGMII_PHY_STATUS, > > + 1000, 100000); > > +} > > + > > +static int gsw1xx_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, > > + phy_interface_t interface, > > + const unsigned long *advertising, > > + bool permit_pause_to_mac) > > +{ > > + struct gsw1xx_priv *priv = pcs_to_gsw1xx(pcs); > > + bool sgmii_mac_mode = dsa_is_user_port(priv->gswip.ds, > > + GSW1XX_SGMII_PORT); > > In lack of the phy-mode = "revsgmii" that you also mention, can we just > assume that any port with phy-mode = "sgmii" is in "MAC mode"? That would result in SGMII generally not be useful for being used as interface mode for the CPU port, because in that case the switch would need to operate in "SGMII PHY mode". It is true, however, that in most cases it is likely possible to just use 1000Base-X or 2500Base-X instead of SGMII to connect the switch to the CPU. > > [...] > > Can you split up this function in multiple smaller logical blocks? > The control flow with "reconf" and "skip_init_reset" is a bit difficult > to follow. I can't say I understood what's going on. Ideally > gsw1xx_pcs_config() fits in one-two screen. I think breaking out the intial reset and flush into a seperate function makes sense. > > > +static int gsw1xx_probe(struct mdio_device *mdiodev) > > +{ > > [...] > > + /* configure GPIO pin-mux for MMDIO in case of external PHY connected to > > Can you explain that MMDIO stands for MDIO master interface? On first > sight it looks like a typo. Yes, MMDIO = Master MDIO (ie. to connect external PHYs to the switch in this case, but also to access the built-in TP PHYs). As opposed to the MDIO Slave module which is used to allow the CPU to access the switch registers. I'll change the comment to make it more clear. ^ permalink raw reply [flat|nested] 35+ messages in thread
end of thread, other threads:[~2025-10-28 17:24 UTC | newest] Thread overview: 35+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-10-26 23:43 [PATCH net-next v3 00/12] net: dsa: lantiq_gswip: Add support for MaxLinear GSW1xx switch family Daniel Golle 2025-10-26 23:43 ` [PATCH net-next v3 01/12] net: dsa: lantiq_gswip: split into common and MMIO parts Daniel Golle 2025-10-27 11:37 ` Sverdlin, Alexander 2025-10-27 22:22 ` Vladimir Oltean 2025-10-26 23:44 ` [PATCH net-next v3 02/12] net: dsa: lantiq_gswip: support enable/disable learning Daniel Golle 2025-10-27 22:29 ` Vladimir Oltean 2025-10-26 23:44 ` [PATCH net-next v3 03/12] net: dsa: lantiq_gswip: support Energy Efficient Ethernet Daniel Golle 2025-10-26 23:44 ` [PATCH net-next v3 04/12] net: dsa: lantiq_gswip: set link parameters also for CPU port Daniel Golle 2025-10-27 8:03 ` Sverdlin, Alexander 2025-10-27 22:39 ` Vladimir Oltean 2025-10-26 23:44 ` [PATCH net-next v3 05/12] net: dsa: lantiq_gswip: define and use GSWIP_TABLE_MAC_BRIDGE_VAL1_VALID Daniel Golle 2025-10-27 22:47 ` Vladimir Oltean 2025-10-26 23:45 ` [PATCH net-next v3 06/12] dt-bindings: net: dsa: lantiq,gswip: add support for MII delay properties Daniel Golle 2025-10-27 23:04 ` Vladimir Oltean 2025-10-27 23:41 ` Daniel Golle 2025-10-28 1:39 ` Vladimir Oltean 2025-10-28 2:30 ` Daniel Golle 2025-10-26 23:45 ` [PATCH net-next v3 07/12] net: dsa: lantiq_gswip: allow adjusting MII delays Daniel Golle 2025-10-26 23:47 ` [PATCH net-next v3 08/12] dt-bindings: net: dsa: lantiq,gswip: add MaxLinear RMII refclk output property Daniel Golle 2025-10-27 9:41 ` Sverdlin, Alexander 2025-10-26 23:47 ` [PATCH net-next v3 09/12] net: dsa: lantiq_gswip: add vendor property to setup MII refclk output Daniel Golle 2025-10-27 11:39 ` Sverdlin, Alexander 2025-10-27 23:36 ` Vladimir Oltean 2025-10-27 23:48 ` Daniel Golle 2025-10-28 1:44 ` Vladimir Oltean 2025-10-26 23:48 ` [PATCH net-next v3 10/12] dt-bindings: net: dsa: lantiq,gswip: add support for MaxLinear GSW1xx switches Daniel Golle 2025-10-28 0:09 ` Vladimir Oltean 2025-10-28 1:27 ` Daniel Golle 2025-10-26 23:48 ` [PATCH net-next v3 11/12] net: dsa: add tagging driver for MaxLinear GSW1xx switch family Daniel Golle 2025-10-27 11:48 ` Sverdlin, Alexander 2025-10-28 0:28 ` Vladimir Oltean 2025-10-28 17:24 ` Daniel Golle 2025-10-26 23:49 ` [PATCH net-next v3 12/12] net: dsa: add " Daniel Golle 2025-10-28 1:24 ` Vladimir Oltean 2025-10-28 2:14 ` Daniel Golle
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).