Merge branch 'net-dsa-lantiq_gswip-add-support-for-maxlinear-gsw1xx-switch-family'

Daniel Golle says:

====================
net: dsa: lantiq_gswip: Add support for MaxLinear GSW1xx switch family

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 which are currently supported by the
lantiq_gswip driver, but they are dedicated ICs connected via MDIO
rather than built-in components of a SoC accessible via memory-mapped
I/O.

The series includes several improvements and refactoring to implement
support for GSW1xx switch ICs by reusing the existing lantiq_gswip
driver.

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
====================

Link: https://patch.msgid.link/cover.1762170107.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2025-11-06 14:16:20 -08:00
commit 380e6f3c7b
15 changed files with 3088 additions and 1588 deletions

View File

@ -4,10 +4,14 @@
$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
allOf:
- $ref: dsa.yaml#/$defs/ethernet-ports
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#
maintainers:
- Hauke Mehrtens <hauke@hauke-m.de>
@ -18,9 +22,14 @@ properties:
- lantiq,xrx200-gswip
- lantiq,xrx300-gswip
- lantiq,xrx330-gswip
- maxlinear,gsw120
- maxlinear,gsw125
- maxlinear,gsw140
- maxlinear,gsw141
- maxlinear,gsw145
reg:
minItems: 3
minItems: 1
maxItems: 3
reg-names:
@ -37,9 +46,6 @@ properties:
compatible:
const: lantiq,xrx200-mdio
required:
- compatible
gphy-fw:
type: object
properties:
@ -91,10 +97,63 @@ properties:
additionalProperties: false
patternProperties:
"^(ethernet-)?ports$":
type: object
patternProperties:
"^(ethernet-)?port@[0-6]$":
$ref: dsa-port.yaml#
unevaluatedProperties: false
properties:
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.
tx-internal-delay-ps:
enum: [0, 500, 1000, 1500, 2000, 2500, 3000, 3500]
description:
RGMII TX Clock Delay defined in pico seconds.
The delay lines adjust the MII clock vs. data timing.
If this property is not present the delay is determined by
the interface mode.
rx-internal-delay-ps:
enum: [0, 500, 1000, 1500, 2000, 2500, 3000, 3500]
description:
RGMII RX Clock Delay defined in pico seconds.
The delay lines adjust the MII clock vs. data timing.
If this property is not present the delay is determined by
the interface mode.
required:
- compatible
- reg
allOf:
- if:
properties:
compatible:
contains:
enum:
- lantiq,xrx200-gswip
- lantiq,xrx300-gswip
- lantiq,xrx330-gswip
then:
properties:
reg:
minItems: 3
maxItems: 3
mdio:
required:
- compatible
else:
properties:
reg:
maxItems: 1
reg-names: false
gphy-fw: false
unevaluatedProperties: false
examples:
@ -113,8 +172,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 {
@ -200,3 +261,90 @@ 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";
managed = "in-band-status";
};
port@5 {
reg = <5>;
phy-mode = "rgmii-id";
tx-internal-delay-ps = <2000>;
rx-internal-delay-ps = <2000>;
ethernet = <&eth0>;
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;
};
};
};
};
};
};

View File

@ -14053,7 +14053,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
@ -14061,6 +14061,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>

View File

@ -1,8 +1,24 @@
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.
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

View File

@ -1 +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

File diff suppressed because it is too large Load Diff

View File

@ -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>
@ -81,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
@ -157,6 +162,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 */
@ -190,6 +198,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))
@ -214,6 +228,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)
@ -240,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;
@ -278,4 +294,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 */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,733 @@
// 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_reset(struct gsw1xx_priv *priv)
{
int ret;
u16 val;
/* 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);
/* TODO: Take care of inverted RX pair once generic property is
* available
*/
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);
/* TODO: Take care of inverted TX pair once generic property is
* available
*/
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;
return regmap_clear_bits(priv->sgmii, GSW1XX_SGMII_PCS_RXB_CTL,
GSW1XX_SGMII_PCS_RXB_CTL_INIT_RX_RXB);
}
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);
u16 txaneg, anegctl, nco_ctrl;
bool reconf = false;
int ret = 0;
/* 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)
ret = gsw1xx_pcs_reset(priv);
if (ret)
return ret;
/* 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;
txaneg = phylink_mii_c22_pcs_encode_advertisement(interface, advertising);
if (interface == PHY_INTERFACE_MODE_SGMII) {
/* lacking a defined reverse-SGMII interface mode this
* driver only supports SGMII (MAC side) for now
*/
anegctl |= FIELD_PREP(GSW1XX_SGMII_TBI_ANEGCTL_ANMODE,
GSW1XX_SGMII_TBI_ANEGCTL_ANMODE_SGMII_MAC);
txaneg |= ADVERTISE_LPACK;
} 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);
} 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, store 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");

View File

@ -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 */

View File

@ -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),
};

View File

@ -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;

View File

@ -92,6 +92,7 @@
#define ETH_P_ETHERCAT 0x88A4 /* EtherCAT */
#define ETH_P_8021AD 0x88A8 /* 802.1ad Service VLAN */
#define ETH_P_802_EX1 0x88B5 /* 802.1 Local Experimental 1. */
#define ETH_P_MXLGSW 0x88C3 /* MaxLinear GSW DSA [ NOT AN OFFICIALLY REGISTERED ID ] */
#define ETH_P_PREAUTH 0x88C7 /* 802.11 Preauthentication */
#define ETH_P_TIPC 0x88CA /* TIPC */
#define ETH_P_LLDP 0x88CC /* Link Layer Discovery Protocol */

View File

@ -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

View File

@ -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

116
net/dsa/tag_mxl-gsw1xx.c Normal file
View File

@ -0,0 +1,116 @@
// 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 header length (RX and TX) */
#define GSW1XX_HEADER_LEN 8
/* Word 0 = Ethertype -> 0x88C3 */
/* Word 1 */
#define GSW1XX_TX_PORT_MAP GENMASK(7, 0)
#define GSW1XX_TX_PORT_MAP_EN BIT(15)
#define GSW1XX_TX_CLASS_EN BIT(14)
#define GSW1XX_TX_TIME_STAMP_EN BIT(13)
#define GSW1XX_TX_LRN_DIS BIT(12)
#define GSW1XX_TX_CLASS GENMASK(11, 8)
/* special tag in RX path header */
/* Word 2 */
#define GSW1XX_RX_PORT_MAP GENMASK(15, 8)
static struct sk_buff *gsw1xx_tag_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct dsa_port *dp = dsa_user_to_port(dev);
__be16 *gsw1xx_tag;
/* provide additional space 'GSW1XX_HEADER_LEN' bytes */
skb_push(skb, GSW1XX_HEADER_LEN);
/* add space between MAC address and Ethertype */
dsa_alloc_etype_header(skb, GSW1XX_HEADER_LEN);
/* special tag ingress */
gsw1xx_tag = dsa_etype_header_pos_tx(skb);
gsw1xx_tag[0] = htons(ETH_P_MXLGSW);
gsw1xx_tag[1] = htons(GSW1XX_TX_PORT_MAP_EN | GSW1XX_TX_LRN_DIS |
FIELD_PREP(GSW1XX_TX_PORT_MAP, BIT(dp->index)));
gsw1xx_tag[2] = 0;
gsw1xx_tag[3] = 0;
return skb;
}
static struct sk_buff *gsw1xx_tag_rcv(struct sk_buff *skb,
struct net_device *dev)
{
int port;
__be16 *gsw1xx_tag;
if (unlikely(!pskb_may_pull(skb, GSW1XX_HEADER_LEN))) {
dev_warn_ratelimited(&dev->dev, "Dropping packet, cannot pull SKB\n");
return NULL;
}
gsw1xx_tag = dsa_etype_header_pos_rx(skb);
if (unlikely(ntohs(gsw1xx_tag[0]) != ETH_P_MXLGSW)) {
dev_warn_ratelimited(&dev->dev, "Dropping packet due to invalid special tag\n");
dev_warn_ratelimited(&dev->dev, "Tag: %8ph\n", gsw1xx_tag);
return NULL;
}
/* Get source port information */
port = FIELD_GET(GSW1XX_RX_PORT_MAP, ntohs(gsw1xx_tag[1]));
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: %8ph\n", gsw1xx_tag);
return NULL;
}
/* remove the GSW1xx special tag between MAC addresses and the current
* ethertype field.
*/
skb_pull_rcsum(skb, GSW1XX_HEADER_LEN);
dsa_strip_etype_header(skb, GSW1XX_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_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);