mirror of
https://github.com/torvalds/linux.git
synced 2026-05-30 01:53:29 +02:00
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:
commit
380e6f3c7b
|
|
@ -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 = <ð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;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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 */
|
||||
|
|
|
|||
1737
drivers/net/dsa/lantiq/lantiq_gswip_common.c
Normal file
1737
drivers/net/dsa/lantiq/lantiq_gswip_common.c
Normal file
File diff suppressed because it is too large
Load Diff
733
drivers/net/dsa/lantiq/mxl-gsw1xx.c
Normal file
733
drivers/net/dsa/lantiq/mxl-gsw1xx.c
Normal 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");
|
||||
126
drivers/net/dsa/lantiq/mxl-gsw1xx.h
Normal file
126
drivers/net/dsa/lantiq/mxl-gsw1xx.h
Normal 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 */
|
||||
154
drivers/net/dsa/lantiq/mxl-gsw1xx_pce.h
Normal file
154
drivers/net/dsa/lantiq/mxl-gsw1xx_pce.h
Normal 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),
|
||||
};
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
116
net/dsa/tag_mxl-gsw1xx.c
Normal 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);
|
||||
Loading…
Reference in New Issue
Block a user