Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,27 @@
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 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
select PHY_COMMON_PROPS
help
This enables support for the Intel/MaxLinear GSW1xx family of 1GE
switches.
GSW120 4 port, 2 PHYs, RGMII & SGMII/2500Base-X
GSW125 4 port, 2 PHYs, RGMII & SGMII/2500Base-X, industrial temperature
GSW140 6 port, 4 PHYs, RGMII & SGMII/2500Base-X
GSW141 6 port, 4 PHYs, RGMII & SGMII
GSW145 6 port, 4 PHYs, RGMII & SGMII/2500Base-X, industrial temperature
GSW150 7 port, 5 PHYs, 1x GMII/RGMII, 1x RGMII
@@ -0,0 +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
@@ -0,0 +1,531 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Lantiq / Intel GSWIP switch driver for VRX200, xRX300 and xRX330 SoCs
*
* Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
* Copyright (C) 2017 - 2019 Hauke Mehrtens <hauke@hauke-m.de>
* Copyright (C) 2012 John Crispin <john@phrozen.org>
* Copyright (C) 2010 Lantiq Deutschland
*/
#include "lantiq_gswip.h"
#include "lantiq_pce.h"
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <dt-bindings/mips/lantiq_rcu_gphy.h>
#include <net/dsa.h>
struct xway_gphy_match_data {
char *fe_firmware_name;
char *ge_firmware_name;
};
static void gswip_xrx200_phylink_get_caps(struct dsa_switch *ds, int port,
struct phylink_config *config)
{
switch (port) {
case 0 ... 1:
phy_interface_set_rgmii(config->supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_MII,
config->supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_REVMII,
config->supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_RMII,
config->supported_interfaces);
break;
case 2 ... 4:
case 6:
__set_bit(PHY_INTERFACE_MODE_INTERNAL,
config->supported_interfaces);
break;
case 5:
phy_interface_set_rgmii(config->supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_INTERNAL,
config->supported_interfaces);
break;
}
config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
MAC_10 | MAC_100 | MAC_1000;
}
static void gswip_xrx300_phylink_get_caps(struct dsa_switch *ds, int port,
struct phylink_config *config)
{
switch (port) {
case 0:
phy_interface_set_rgmii(config->supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_GMII,
config->supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_RMII,
config->supported_interfaces);
break;
case 1 ... 4:
case 6:
__set_bit(PHY_INTERFACE_MODE_INTERNAL,
config->supported_interfaces);
break;
case 5:
phy_interface_set_rgmii(config->supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_INTERNAL,
config->supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_RMII,
config->supported_interfaces);
break;
}
config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
MAC_10 | MAC_100 | MAC_1000;
}
static const struct xway_gphy_match_data xrx200a1x_gphy_data = {
.fe_firmware_name = "lantiq/xrx200_phy22f_a14.bin",
.ge_firmware_name = "lantiq/xrx200_phy11g_a14.bin",
};
static const struct xway_gphy_match_data xrx200a2x_gphy_data = {
.fe_firmware_name = "lantiq/xrx200_phy22f_a22.bin",
.ge_firmware_name = "lantiq/xrx200_phy11g_a22.bin",
};
static const struct xway_gphy_match_data xrx300_gphy_data = {
.fe_firmware_name = "lantiq/xrx300_phy22f_a21.bin",
.ge_firmware_name = "lantiq/xrx300_phy11g_a21.bin",
};
static const struct of_device_id xway_gphy_match[] __maybe_unused = {
{ .compatible = "lantiq,xrx200-gphy-fw", .data = NULL },
{ .compatible = "lantiq,xrx200a1x-gphy-fw", .data = &xrx200a1x_gphy_data },
{ .compatible = "lantiq,xrx200a2x-gphy-fw", .data = &xrx200a2x_gphy_data },
{ .compatible = "lantiq,xrx300-gphy-fw", .data = &xrx300_gphy_data },
{ .compatible = "lantiq,xrx330-gphy-fw", .data = &xrx300_gphy_data },
{},
};
static int gswip_gphy_fw_load(struct gswip_priv *priv, struct gswip_gphy_fw *gphy_fw)
{
struct device *dev = priv->dev;
const struct firmware *fw;
void *fw_addr;
dma_addr_t dma_addr;
dma_addr_t dev_addr;
size_t size;
int ret;
ret = clk_prepare_enable(gphy_fw->clk_gate);
if (ret)
return ret;
reset_control_assert(gphy_fw->reset);
/* The vendor BSP uses a 200ms delay after asserting the reset line.
* Without this some users are observing that the PHY is not coming up
* on the MDIO bus.
*/
msleep(200);
ret = request_firmware(&fw, gphy_fw->fw_name, dev);
if (ret)
return dev_err_probe(dev, ret, "failed to load firmware: %s\n",
gphy_fw->fw_name);
/* GPHY cores need the firmware code in a persistent and contiguous
* memory area with a 16 kB boundary aligned start address.
*/
size = fw->size + XRX200_GPHY_FW_ALIGN;
fw_addr = dmam_alloc_coherent(dev, size, &dma_addr, GFP_KERNEL);
if (fw_addr) {
fw_addr = PTR_ALIGN(fw_addr, XRX200_GPHY_FW_ALIGN);
dev_addr = ALIGN(dma_addr, XRX200_GPHY_FW_ALIGN);
memcpy(fw_addr, fw->data, fw->size);
} else {
release_firmware(fw);
return -ENOMEM;
}
release_firmware(fw);
ret = regmap_write(priv->rcu_regmap, gphy_fw->fw_addr_offset, dev_addr);
if (ret)
return ret;
reset_control_deassert(gphy_fw->reset);
return ret;
}
static int gswip_gphy_fw_probe(struct gswip_priv *priv,
struct gswip_gphy_fw *gphy_fw,
struct device_node *gphy_fw_np, int i)
{
struct device *dev = priv->dev;
u32 gphy_mode;
int ret;
char gphyname[10];
snprintf(gphyname, sizeof(gphyname), "gphy%d", i);
gphy_fw->clk_gate = devm_clk_get(dev, gphyname);
if (IS_ERR(gphy_fw->clk_gate)) {
return dev_err_probe(dev, PTR_ERR(gphy_fw->clk_gate),
"Failed to lookup gate clock\n");
}
ret = of_property_read_u32(gphy_fw_np, "reg", &gphy_fw->fw_addr_offset);
if (ret)
return ret;
ret = of_property_read_u32(gphy_fw_np, "lantiq,gphy-mode", &gphy_mode);
/* Default to GE mode */
if (ret)
gphy_mode = GPHY_MODE_GE;
switch (gphy_mode) {
case GPHY_MODE_FE:
gphy_fw->fw_name = priv->gphy_fw_name_cfg->fe_firmware_name;
break;
case GPHY_MODE_GE:
gphy_fw->fw_name = priv->gphy_fw_name_cfg->ge_firmware_name;
break;
default:
return dev_err_probe(dev, -EINVAL, "Unknown GPHY mode %d\n",
gphy_mode);
}
gphy_fw->reset = of_reset_control_array_get_exclusive(gphy_fw_np);
if (IS_ERR(gphy_fw->reset))
return dev_err_probe(dev, PTR_ERR(gphy_fw->reset),
"Failed to lookup gphy reset\n");
return gswip_gphy_fw_load(priv, gphy_fw);
}
static void gswip_gphy_fw_remove(struct gswip_priv *priv,
struct gswip_gphy_fw *gphy_fw)
{
int ret;
/* check if the device was fully probed */
if (!gphy_fw->fw_name)
return;
ret = regmap_write(priv->rcu_regmap, gphy_fw->fw_addr_offset, 0);
if (ret)
dev_err(priv->dev, "can not reset GPHY FW pointer\n");
clk_disable_unprepare(gphy_fw->clk_gate);
reset_control_put(gphy_fw->reset);
}
static int gswip_gphy_fw_list(struct gswip_priv *priv,
struct device_node *gphy_fw_list_np, u32 version)
{
struct device *dev = priv->dev;
struct device_node *gphy_fw_np;
const struct of_device_id *match;
int err;
int i = 0;
/* The VRX200 rev 1.1 uses the GSWIP 2.0 and needs the older
* GPHY firmware. The VRX200 rev 1.2 uses the GSWIP 2.1 and also
* needs a different GPHY firmware.
*/
if (of_device_is_compatible(gphy_fw_list_np, "lantiq,xrx200-gphy-fw")) {
switch (version) {
case GSWIP_VERSION_2_0:
priv->gphy_fw_name_cfg = &xrx200a1x_gphy_data;
break;
case GSWIP_VERSION_2_1:
priv->gphy_fw_name_cfg = &xrx200a2x_gphy_data;
break;
default:
return dev_err_probe(dev, -ENOENT,
"unknown GSWIP version: 0x%x\n",
version);
}
}
match = of_match_node(xway_gphy_match, gphy_fw_list_np);
if (match && match->data)
priv->gphy_fw_name_cfg = match->data;
if (!priv->gphy_fw_name_cfg)
return dev_err_probe(dev, -ENOENT,
"GPHY compatible type not supported\n");
priv->num_gphy_fw = of_get_available_child_count(gphy_fw_list_np);
if (!priv->num_gphy_fw)
return -ENOENT;
priv->rcu_regmap = syscon_regmap_lookup_by_phandle(gphy_fw_list_np,
"lantiq,rcu");
if (IS_ERR(priv->rcu_regmap))
return PTR_ERR(priv->rcu_regmap);
priv->gphy_fw = devm_kmalloc_array(dev, priv->num_gphy_fw,
sizeof(*priv->gphy_fw),
GFP_KERNEL | __GFP_ZERO);
if (!priv->gphy_fw)
return -ENOMEM;
for_each_available_child_of_node(gphy_fw_list_np, gphy_fw_np) {
err = gswip_gphy_fw_probe(priv, &priv->gphy_fw[i],
gphy_fw_np, i);
if (err) {
of_node_put(gphy_fw_np);
goto remove_gphy;
}
i++;
}
/* The standalone PHY11G requires 300ms to be fully
* initialized and ready for any MDIO communication after being
* taken out of reset. For the SoC-internal GPHY variant there
* is no (known) documentation for the minimum time after a
* reset. Use the same value as for the standalone variant as
* some users have reported internal PHYs not being detected
* without any delay.
*/
msleep(300);
return 0;
remove_gphy:
for (i = 0; i < priv->num_gphy_fw; i++)
gswip_gphy_fw_remove(priv, &priv->gphy_fw[i]);
return err;
}
static const struct regmap_config sw_regmap_config = {
.name = "switch",
.reg_bits = 32,
.val_bits = 32,
.reg_shift = REGMAP_UPSHIFT(2),
.val_format_endian = REGMAP_ENDIAN_NATIVE,
.max_register = GSWIP_SDMA_PCTRLp(6),
};
static const struct regmap_config mdio_regmap_config = {
.name = "mdio",
.reg_bits = 32,
.val_bits = 32,
.reg_shift = REGMAP_UPSHIFT(2),
.val_format_endian = REGMAP_ENDIAN_NATIVE,
.max_register = GSWIP_MDIO_PHYp(0),
};
static const struct regmap_config mii_regmap_config = {
.name = "mii",
.reg_bits = 32,
.val_bits = 32,
.reg_shift = REGMAP_UPSHIFT(2),
.val_format_endian = REGMAP_ENDIAN_NATIVE,
.max_register = GSWIP_MII_CFGp(6),
};
static int gswip_probe(struct platform_device *pdev)
{
struct device_node *np, *gphy_fw_np;
__iomem void *gswip, *mdio, *mii;
struct device *dev = &pdev->dev;
struct gswip_priv *priv;
int err;
int i;
u32 version;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
gswip = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(gswip))
return PTR_ERR(gswip);
mdio = devm_platform_ioremap_resource(pdev, 1);
if (IS_ERR(mdio))
return PTR_ERR(mdio);
mii = devm_platform_ioremap_resource(pdev, 2);
if (IS_ERR(mii))
return PTR_ERR(mii);
priv->gswip = devm_regmap_init_mmio(dev, gswip, &sw_regmap_config);
if (IS_ERR(priv->gswip))
return PTR_ERR(priv->gswip);
priv->mdio = devm_regmap_init_mmio(dev, mdio, &mdio_regmap_config);
if (IS_ERR(priv->mdio))
return PTR_ERR(priv->mdio);
priv->mii = devm_regmap_init_mmio(dev, mii, &mii_regmap_config);
if (IS_ERR(priv->mii))
return PTR_ERR(priv->mii);
priv->hw_info = of_device_get_match_data(dev);
if (!priv->hw_info)
return -EINVAL;
priv->ds = devm_kzalloc(dev, sizeof(*priv->ds), GFP_KERNEL);
if (!priv->ds)
return -ENOMEM;
priv->dev = dev;
regmap_read(priv->gswip, GSWIP_VERSION, &version);
np = dev->of_node;
switch (version) {
case GSWIP_VERSION_2_0:
case GSWIP_VERSION_2_1:
if (!of_device_is_compatible(np, "lantiq,xrx200-gswip"))
return -EINVAL;
break;
case GSWIP_VERSION_2_2:
case GSWIP_VERSION_2_2_ETC:
if (!of_device_is_compatible(np, "lantiq,xrx300-gswip") &&
!of_device_is_compatible(np, "lantiq,xrx330-gswip"))
return -EINVAL;
break;
default:
return dev_err_probe(dev, -ENOENT,
"unknown GSWIP version: 0x%x\n", version);
}
/* bring up the mdio bus */
gphy_fw_np = of_get_compatible_child(dev->of_node, "lantiq,gphy-fw");
if (gphy_fw_np) {
err = gswip_gphy_fw_list(priv, gphy_fw_np, version);
of_node_put(gphy_fw_np);
if (err)
return dev_err_probe(dev, err,
"gphy fw probe failed\n");
}
err = gswip_probe_common(priv, version);
if (err)
goto gphy_fw_remove;
platform_set_drvdata(pdev, priv);
return 0;
gphy_fw_remove:
for (i = 0; i < priv->num_gphy_fw; i++)
gswip_gphy_fw_remove(priv, &priv->gphy_fw[i]);
return err;
}
static void gswip_remove(struct platform_device *pdev)
{
struct gswip_priv *priv = platform_get_drvdata(pdev);
int i;
if (!priv)
return;
dsa_unregister_switch(priv->ds);
for (i = 0; i < priv->num_gphy_fw; i++)
gswip_gphy_fw_remove(priv, &priv->gphy_fw[i]);
}
static void gswip_shutdown(struct platform_device *pdev)
{
struct gswip_priv *priv = platform_get_drvdata(pdev);
if (!priv)
return;
dsa_switch_shutdown(priv->ds);
platform_set_drvdata(pdev, NULL);
}
static const struct gswip_hw_info gswip_xrx200 = {
.max_ports = GSWIP_MAX_PORTS,
.allowed_cpu_ports = BIT(6),
.mii_cfg = {
[0] = GSWIP_MII_CFGp(0),
[1] = GSWIP_MII_CFGp(1),
[2 ... 4] = -1,
[5] = GSWIP_MII_CFGp(5),
[6] = -1,
},
.mii_pcdu = {
[0] = GSWIP_MII_PCDU0,
[1] = GSWIP_MII_PCDU1,
[2 ... 4] = -1,
[5] = GSWIP_MII_PCDU5,
[6] = -1,
},
.phylink_get_caps = gswip_xrx200_phylink_get_caps,
.pce_microcode = &gswip_pce_microcode,
.pce_microcode_size = ARRAY_SIZE(gswip_pce_microcode),
.tag_protocol = DSA_TAG_PROTO_GSWIP,
};
static const struct gswip_hw_info gswip_xrx300 = {
.max_ports = GSWIP_MAX_PORTS,
.allowed_cpu_ports = BIT(6),
.mii_cfg = {
[0] = GSWIP_MII_CFGp(0),
[1 ... 4] = -1,
[5] = GSWIP_MII_CFGp(5),
[6] = -1,
},
.mii_pcdu = {
[0] = GSWIP_MII_PCDU0,
[1 ... 4] = -1,
[5] = GSWIP_MII_PCDU5,
[6] = -1,
},
.phylink_get_caps = gswip_xrx300_phylink_get_caps,
.pce_microcode = &gswip_pce_microcode,
.pce_microcode_size = ARRAY_SIZE(gswip_pce_microcode),
.tag_protocol = DSA_TAG_PROTO_GSWIP,
};
static const struct of_device_id gswip_of_match[] = {
{ .compatible = "lantiq,xrx200-gswip", .data = &gswip_xrx200 },
{ .compatible = "lantiq,xrx300-gswip", .data = &gswip_xrx300 },
{ .compatible = "lantiq,xrx330-gswip", .data = &gswip_xrx300 },
{},
};
MODULE_DEVICE_TABLE(of, gswip_of_match);
static struct platform_driver gswip_driver = {
.probe = gswip_probe,
.remove = gswip_remove,
.shutdown = gswip_shutdown,
.driver = {
.name = "gswip",
.of_match_table = gswip_of_match,
},
};
module_platform_driver(gswip_driver);
MODULE_FIRMWARE("lantiq/xrx300_phy11g_a21.bin");
MODULE_FIRMWARE("lantiq/xrx300_phy22f_a21.bin");
MODULE_FIRMWARE("lantiq/xrx200_phy11g_a14.bin");
MODULE_FIRMWARE("lantiq/xrx200_phy11g_a22.bin");
MODULE_FIRMWARE("lantiq/xrx200_phy22f_a14.bin");
MODULE_FIRMWARE("lantiq/xrx200_phy22f_a22.bin");
MODULE_AUTHOR("Hauke Mehrtens <hauke@hauke-m.de>");
MODULE_DESCRIPTION("Lantiq / Intel GSWIP driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,302 @@
// SPDX-License-Identifier: GPL-2.0
#ifndef __LANTIQ_GSWIP_H
#define __LANTIQ_GSWIP_H
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/mutex.h>
#include <linux/phylink.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/swab.h>
#include <net/dsa.h>
/* GSWIP MDIO Registers */
#define GSWIP_MDIO_GLOB 0x00
#define GSWIP_MDIO_GLOB_ENABLE BIT(15)
#define GSWIP_MDIO_CTRL 0x08
#define GSWIP_MDIO_CTRL_BUSY BIT(12)
#define GSWIP_MDIO_CTRL_RD BIT(11)
#define GSWIP_MDIO_CTRL_WR BIT(10)
#define GSWIP_MDIO_CTRL_PHYAD_MASK 0x1f
#define GSWIP_MDIO_CTRL_PHYAD_SHIFT 5
#define GSWIP_MDIO_CTRL_REGAD_MASK 0x1f
#define GSWIP_MDIO_READ 0x09
#define GSWIP_MDIO_WRITE 0x0A
#define GSWIP_MDIO_MDC_CFG0 0x0B
#define GSWIP_MDIO_MDC_CFG1 0x0C
#define GSWIP_MDIO_PHYp(p) (0x15 - (p))
#define GSWIP_MDIO_PHY_LINK_MASK 0x6000
#define GSWIP_MDIO_PHY_LINK_AUTO 0x0000
#define GSWIP_MDIO_PHY_LINK_DOWN 0x4000
#define GSWIP_MDIO_PHY_LINK_UP 0x2000
#define GSWIP_MDIO_PHY_SPEED_MASK 0x1800
#define GSWIP_MDIO_PHY_SPEED_AUTO 0x1800
#define GSWIP_MDIO_PHY_SPEED_M10 0x0000
#define GSWIP_MDIO_PHY_SPEED_M100 0x0800
#define GSWIP_MDIO_PHY_SPEED_G1 0x1000
#define GSWIP_MDIO_PHY_FDUP_MASK 0x0600
#define GSWIP_MDIO_PHY_FDUP_AUTO 0x0000
#define GSWIP_MDIO_PHY_FDUP_EN 0x0200
#define GSWIP_MDIO_PHY_FDUP_DIS 0x0600
#define GSWIP_MDIO_PHY_FCONTX_MASK 0x0180
#define GSWIP_MDIO_PHY_FCONTX_AUTO 0x0000
#define GSWIP_MDIO_PHY_FCONTX_EN 0x0100
#define GSWIP_MDIO_PHY_FCONTX_DIS 0x0180
#define GSWIP_MDIO_PHY_FCONRX_MASK 0x0060
#define GSWIP_MDIO_PHY_FCONRX_AUTO 0x0000
#define GSWIP_MDIO_PHY_FCONRX_EN 0x0020
#define GSWIP_MDIO_PHY_FCONRX_DIS 0x0060
#define GSWIP_MDIO_PHY_ADDR_MASK 0x001f
#define GSWIP_MDIO_PHY_MASK (GSWIP_MDIO_PHY_ADDR_MASK | \
GSWIP_MDIO_PHY_FCONRX_MASK | \
GSWIP_MDIO_PHY_FCONTX_MASK | \
GSWIP_MDIO_PHY_LINK_MASK | \
GSWIP_MDIO_PHY_SPEED_MASK | \
GSWIP_MDIO_PHY_FDUP_MASK)
/* GSWIP MII Registers */
#define GSWIP_MII_CFGp(p) (0x2 * (p))
#define GSWIP_MII_CFG_RESET BIT(15)
#define GSWIP_MII_CFG_EN BIT(14)
#define GSWIP_MII_CFG_ISOLATE BIT(13)
#define GSWIP_MII_CFG_LDCLKDIS BIT(12)
#define GSWIP_MII_CFG_RGMII_IBS BIT(8)
#define GSWIP_MII_CFG_RMII_CLK BIT(7)
#define GSWIP_MII_CFG_MODE_MIIP 0x0
#define GSWIP_MII_CFG_MODE_MIIM 0x1
#define GSWIP_MII_CFG_MODE_RMIIP 0x2
#define GSWIP_MII_CFG_MODE_RMIIM 0x3
#define GSWIP_MII_CFG_MODE_RGMII 0x4
#define GSWIP_MII_CFG_MODE_GMII 0x9
#define GSWIP_MII_CFG_MODE_MASK 0xf
#define GSWIP_MII_CFG_RATE_M2P5 0x00
#define GSWIP_MII_CFG_RATE_M25 0x10
#define GSWIP_MII_CFG_RATE_M125 0x20
#define GSWIP_MII_CFG_RATE_M50 0x30
#define GSWIP_MII_CFG_RATE_AUTO 0x40
#define GSWIP_MII_CFG_RATE_MASK 0x70
#define GSWIP_MII_PCDU0 0x01
#define GSWIP_MII_PCDU1 0x03
#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
#define GSWIP_SWRES_R1 BIT(1) /* GSWIP Software reset */
#define GSWIP_SWRES_R0 BIT(0) /* GSWIP Hardware reset */
#define GSWIP_VERSION 0x013
#define GSWIP_VERSION_REV_MASK GENMASK(7, 0)
#define GSWIP_VERSION_MOD_MASK GENMASK(15, 8)
#define GSWIP_VERSION_REV(v) FIELD_GET(GSWIP_VERSION_REV_MASK, v)
#define GSWIP_VERSION_MOD(v) FIELD_GET(GSWIP_VERSION_MOD_MASK, v)
#define GSWIP_VERSION_2_0 0x100
#define GSWIP_VERSION_2_1 0x021
#define GSWIP_VERSION_2_2 0x122
#define GSWIP_VERSION_2_2_ETC 0x022
/* The hardware has the 'major/minor' version bytes in the wrong order
* preventing numerical comparisons. Swap the bytes of the 16-bit value
* to end up with REV being the most significant byte and MOD being the
* least significant byte, which then allows comparing it with the
* value stored in struct gswip_priv.
*/
#define GSWIP_VERSION_GE(priv, ver) ((priv)->version >= swab16(ver))
#define GSWIP_BM_RAM_VAL(x) (0x043 - (x))
#define GSWIP_BM_RAM_ADDR 0x044
#define GSWIP_BM_RAM_CTRL 0x045
#define GSWIP_BM_RAM_CTRL_BAS BIT(15)
#define GSWIP_BM_RAM_CTRL_OPMOD BIT(5)
#define GSWIP_BM_RAM_CTRL_ADDR_MASK GENMASK(4, 0)
#define GSWIP_BM_QUEUE_GCTRL 0x04A
#define GSWIP_BM_QUEUE_GCTRL_GL_MOD BIT(10)
/* buffer management Port Configuration Register */
#define GSWIP_BM_PCFGp(p) (0x080 + ((p) * 2))
#define GSWIP_BM_PCFG_CNTEN BIT(0) /* RMON Counter Enable */
#define GSWIP_BM_PCFG_IGCNT BIT(1) /* Ingres Special Tag RMON count */
/* buffer management Port Control Register */
#define GSWIP_BM_RMON_CTRLp(p) (0x81 + ((p) * 2))
#define GSWIP_BM_CTRL_RMON_RAM1_RES BIT(0) /* Software Reset for RMON RAM 1 */
#define GSWIP_BM_CTRL_RMON_RAM2_RES BIT(1) /* Software Reset for RMON RAM 2 */
/* PCE */
#define GSWIP_PCE_TBL_KEY(x) (0x447 - (x))
#define GSWIP_PCE_TBL_MASK 0x448
#define GSWIP_PCE_TBL_VAL(x) (0x44D - (x))
#define GSWIP_PCE_TBL_ADDR 0x44E
#define GSWIP_PCE_TBL_CTRL 0x44F
#define GSWIP_PCE_TBL_CTRL_BAS BIT(15)
#define GSWIP_PCE_TBL_CTRL_TYPE BIT(13)
#define GSWIP_PCE_TBL_CTRL_VLD BIT(12)
#define GSWIP_PCE_TBL_CTRL_KEYFORM BIT(11)
#define GSWIP_PCE_TBL_CTRL_GMAP_MASK GENMASK(10, 7)
#define GSWIP_PCE_TBL_CTRL_OPMOD_MASK GENMASK(6, 5)
#define GSWIP_PCE_TBL_CTRL_OPMOD_ADRD 0x00
#define GSWIP_PCE_TBL_CTRL_OPMOD_ADWR 0x20
#define GSWIP_PCE_TBL_CTRL_OPMOD_KSRD 0x40
#define GSWIP_PCE_TBL_CTRL_OPMOD_KSWR 0x60
#define GSWIP_PCE_TBL_CTRL_ADDR_MASK GENMASK(4, 0)
#define GSWIP_PCE_PMAP1 0x453 /* Monitoring port map */
#define GSWIP_PCE_PMAP2 0x454 /* Default Multicast port map */
#define GSWIP_PCE_PMAP3 0x455 /* Default Unknown Unicast port map */
#define GSWIP_PCE_GCTRL_0 0x456
#define GSWIP_PCE_GCTRL_0_MTFL BIT(0) /* MAC Table Flushing */
#define GSWIP_PCE_GCTRL_0_MC_VALID BIT(3)
#define GSWIP_PCE_GCTRL_0_VLAN BIT(14) /* VLAN aware Switching */
#define GSWIP_PCE_GCTRL_1 0x457
#define GSWIP_PCE_GCTRL_1_MAC_GLOCK BIT(2) /* MAC Address table lock */
#define GSWIP_PCE_GCTRL_1_MAC_GLOCK_MOD BIT(3) /* Mac address table lock forwarding mode */
#define GSWIP_PCE_PCTRL_0p(p) (0x480 + ((p) * 0xA))
#define GSWIP_PCE_PCTRL_0_TVM BIT(5) /* Transparent VLAN mode */
#define GSWIP_PCE_PCTRL_0_VREP BIT(6) /* VLAN Replace Mode */
#define GSWIP_PCE_PCTRL_0_INGRESS BIT(11) /* Accept special tag in ingress */
#define GSWIP_PCE_PCTRL_0_PSTATE_LISTEN 0x0
#define GSWIP_PCE_PCTRL_0_PSTATE_RX 0x1
#define GSWIP_PCE_PCTRL_0_PSTATE_TX 0x2
#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 */
#define GSWIP_PCE_VCTRL_VINR_ALL 0 /* Admit tagged and untagged packets */
#define GSWIP_PCE_VCTRL_VINR_TAGGED 1 /* Admit only tagged packets */
#define GSWIP_PCE_VCTRL_VINR_UNTAGGED 2 /* Admit only untagged packets */
#define GSWIP_PCE_VCTRL_VIMR BIT(3) /* VLAN Ingress Member violation rule */
#define GSWIP_PCE_VCTRL_VEMR BIT(4) /* VLAN Egress Member violation rule */
#define GSWIP_PCE_VCTRL_VSR BIT(5) /* VLAN Security */
#define GSWIP_PCE_VCTRL_VID0 BIT(6) /* Priority Tagged Rule */
#define GSWIP_PCE_DEFPVID(p) (0x486 + ((p) * 0xA))
#define GSWIP_MAC_FLEN 0x8C5
#define GSWIP_MAC_CTRL_0p(p) (0x903 + ((p) * 0xC))
#define GSWIP_MAC_CTRL_0_PADEN BIT(8)
#define GSWIP_MAC_CTRL_0_FCS_EN BIT(7)
#define GSWIP_MAC_CTRL_0_FCON_MASK 0x0070
#define GSWIP_MAC_CTRL_0_FCON_AUTO 0x0000
#define GSWIP_MAC_CTRL_0_FCON_RX 0x0010
#define GSWIP_MAC_CTRL_0_FCON_TX 0x0020
#define GSWIP_MAC_CTRL_0_FCON_RXTX 0x0030
#define GSWIP_MAC_CTRL_0_FCON_NONE 0x0040
#define GSWIP_MAC_CTRL_0_FDUP_MASK 0x000C
#define GSWIP_MAC_CTRL_0_FDUP_AUTO 0x0000
#define GSWIP_MAC_CTRL_0_FDUP_EN 0x0004
#define GSWIP_MAC_CTRL_0_FDUP_DIS 0x000C
#define GSWIP_MAC_CTRL_0_GMII_MASK 0x0003
#define GSWIP_MAC_CTRL_0_GMII_AUTO 0x0000
#define GSWIP_MAC_CTRL_0_GMII_MII 0x0001
#define GSWIP_MAC_CTRL_0_GMII_RGMII 0x0002
#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))
#define GSWIP_FDMA_PCTRL_EN BIT(0) /* FDMA Port Enable */
#define GSWIP_FDMA_PCTRL_STEN BIT(1) /* Special Tag Insertion Enable */
#define GSWIP_FDMA_PCTRL_VLANMOD_MASK GENMASK(4, 3) /* VLAN Modification Control */
#define GSWIP_FDMA_PCTRL_VLANMOD_SHIFT 3 /* VLAN Modification Control */
#define GSWIP_FDMA_PCTRL_VLANMOD_DIS (0x0 << GSWIP_FDMA_PCTRL_VLANMOD_SHIFT)
#define GSWIP_FDMA_PCTRL_VLANMOD_PRIO (0x1 << GSWIP_FDMA_PCTRL_VLANMOD_SHIFT)
#define GSWIP_FDMA_PCTRL_VLANMOD_ID (0x2 << GSWIP_FDMA_PCTRL_VLANMOD_SHIFT)
#define GSWIP_FDMA_PCTRL_VLANMOD_BOTH (0x3 << GSWIP_FDMA_PCTRL_VLANMOD_SHIFT)
/* Ethernet Switch Store DMA Port Control Register */
#define GSWIP_SDMA_PCTRLp(p) (0xBC0 + ((p) * 0x6))
#define GSWIP_SDMA_PCTRL_EN BIT(0) /* SDMA Port Enable */
#define GSWIP_SDMA_PCTRL_FCEN BIT(1) /* Flow Control Enable */
#define GSWIP_SDMA_PCTRL_PAUFWD BIT(3) /* Pause Frame Forwarding */
#define GSWIP_TABLE_ACTIVE_VLAN 0x01
#define GSWIP_TABLE_VLAN_MAPPING 0x02
#define GSWIP_TABLE_MAC_BRIDGE 0x0b
#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)
/* Maximum packet size supported by the switch. In theory this should be 10240,
* but long packets currently cause lock-ups with an MTU of over 2526. Medium
* packets are sometimes dropped (e.g. TCP over 2477, UDP over 2516-2519, ICMP
* over 2526), hence an MTU value of 2400 seems safe. This issue only affects
* packet reception. This is probably caused by the PPA engine, which is on the
* RX part of the device. Packet transmission works properly up to 10240.
*/
#define GSWIP_MAX_PACKET_LENGTH 2400
#define GSWIP_VLAN_UNAWARE_PVID 0
#define GSWIP_MAX_PORTS 7
struct gswip_pce_microcode {
u16 val_3;
u16 val_2;
u16 val_1;
u16 val_0;
};
struct gswip_hw_info {
int max_ports;
unsigned int allowed_cpu_ports;
s16 mii_cfg[GSWIP_MAX_PORTS];
s16 mii_pcdu[GSWIP_MAX_PORTS];
bool supports_2500m;
const struct gswip_pce_microcode (*pce_microcode)[];
size_t pce_microcode_size;
enum dsa_tag_protocol tag_protocol;
void (*phylink_get_caps)(struct dsa_switch *ds, int port,
struct phylink_config *config);
struct phylink_pcs *(*mac_select_pcs)(struct phylink_config *config,
phy_interface_t interface);
int (*port_setup)(struct dsa_switch *ds, int port);
};
struct gswip_gphy_fw {
struct clk *clk_gate;
struct reset_control *reset;
u32 fw_addr_offset;
char *fw_name;
};
struct gswip_vlan {
struct net_device *bridge;
u16 vid;
u8 fid;
};
struct gswip_priv {
struct regmap *gswip;
struct regmap *mdio;
struct regmap *mii;
const struct gswip_hw_info *hw_info;
const struct xway_gphy_match_data *gphy_fw_name_cfg;
struct dsa_switch *ds;
struct device *dev;
struct regmap *rcu_regmap;
struct gswip_vlan vlans[64];
int num_gphy_fw;
struct gswip_gphy_fw *gphy_fw;
struct mutex pce_table_lock;
u16 version;
};
int gswip_probe_common(struct gswip_priv *priv, u32 version);
#endif /* __LANTIQ_GSWIP_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,148 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* PCE microcode extracted from UGW 7.1.1 switch api
*
* Copyright (c) 2012, 2014, 2015 Lantiq Deutschland GmbH
* Copyright (C) 2012 John Crispin <john@phrozen.org>
* Copyright (C) 2017 - 2018 Hauke Mehrtens <hauke@hauke-m.de>
*/
#include "lantiq_gswip.h"
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_IPOFF, /*39*/
OUT_NONE = 63,
};
/* parser's microcode length type */
#define INSTR 0
#define IPV6 1
#define LENACCU 2
/* 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*/
};
#define MC_ENTRY(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 }
static const struct gswip_pce_microcode gswip_pce_microcode[] = {
/* value mask ns fields L type flags ipv4_len */
MC_ENTRY(0x88c3, 0xFFFF, 1, OUT_ITAG0, 4, INSTR, FLAG_ITAG, 0),
MC_ENTRY(0x8100, 0xFFFF, 2, OUT_VTAG0, 2, INSTR, FLAG_VLAN, 0),
MC_ENTRY(0x88A8, 0xFFFF, 1, OUT_VTAG0, 2, INSTR, FLAG_VLAN, 0),
MC_ENTRY(0x8100, 0xFFFF, 1, OUT_VTAG0, 2, INSTR, FLAG_VLAN, 0),
MC_ENTRY(0x8864, 0xFFFF, 17, OUT_ETHTYP, 1, INSTR, FLAG_NO, 0),
MC_ENTRY(0x0800, 0xFFFF, 21, OUT_ETHTYP, 1, INSTR, FLAG_NO, 0),
MC_ENTRY(0x86DD, 0xFFFF, 22, OUT_ETHTYP, 1, INSTR, FLAG_NO, 0),
MC_ENTRY(0x8863, 0xFFFF, 16, OUT_ETHTYP, 1, INSTR, FLAG_NO, 0),
MC_ENTRY(0x0000, 0xF800, 10, OUT_NONE, 0, INSTR, FLAG_NO, 0),
MC_ENTRY(0x0000, 0x0000, 40, OUT_ETHTYP, 1, INSTR, FLAG_NO, 0),
MC_ENTRY(0x0600, 0x0600, 40, OUT_ETHTYP, 1, INSTR, FLAG_NO, 0),
MC_ENTRY(0x0000, 0x0000, 12, OUT_NONE, 1, INSTR, FLAG_NO, 0),
MC_ENTRY(0xAAAA, 0xFFFF, 14, OUT_NONE, 1, INSTR, FLAG_NO, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_NO, 0),
MC_ENTRY(0x0300, 0xFF00, 41, OUT_NONE, 0, INSTR, FLAG_SNAP, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_NO, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_DIP7, 3, INSTR, FLAG_NO, 0),
MC_ENTRY(0x0000, 0x0000, 18, OUT_DIP7, 3, INSTR, FLAG_PPPOE, 0),
MC_ENTRY(0x0021, 0xFFFF, 21, OUT_NONE, 1, INSTR, FLAG_NO, 0),
MC_ENTRY(0x0057, 0xFFFF, 22, OUT_NONE, 1, INSTR, FLAG_NO, 0),
MC_ENTRY(0x0000, 0x0000, 40, OUT_NONE, 0, INSTR, FLAG_NO, 0),
MC_ENTRY(0x4000, 0xF000, 24, OUT_IP0, 4, INSTR, FLAG_IPV4, 1),
MC_ENTRY(0x6000, 0xF000, 27, OUT_IP0, 3, INSTR, FLAG_IPV6, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_NO, 0),
MC_ENTRY(0x0000, 0x0000, 25, OUT_IP3, 2, INSTR, FLAG_NO, 0),
MC_ENTRY(0x0000, 0x0000, 26, OUT_SIP0, 4, INSTR, FLAG_NO, 0),
MC_ENTRY(0x0000, 0x0000, 40, OUT_NONE, 0, LENACCU, FLAG_NO, 0),
MC_ENTRY(0x1100, 0xFF00, 39, OUT_PROT, 1, INSTR, FLAG_NO, 0),
MC_ENTRY(0x0600, 0xFF00, 39, OUT_PROT, 1, INSTR, FLAG_NO, 0),
MC_ENTRY(0x0000, 0xFF00, 33, OUT_IP3, 17, INSTR, FLAG_HOP, 0),
MC_ENTRY(0x2B00, 0xFF00, 33, OUT_IP3, 17, INSTR, FLAG_NN1, 0),
MC_ENTRY(0x3C00, 0xFF00, 33, OUT_IP3, 17, INSTR, FLAG_NN2, 0),
MC_ENTRY(0x0000, 0x0000, 39, OUT_PROT, 1, INSTR, FLAG_NO, 0),
MC_ENTRY(0x0000, 0x00E0, 35, OUT_NONE, 0, INSTR, FLAG_NO, 0),
MC_ENTRY(0x0000, 0x0000, 40, OUT_NONE, 0, INSTR, FLAG_NO, 0),
MC_ENTRY(0x0000, 0xFF00, 33, OUT_NONE, 0, IPV6, FLAG_HOP, 0),
MC_ENTRY(0x2B00, 0xFF00, 33, OUT_NONE, 0, IPV6, FLAG_NN1, 0),
MC_ENTRY(0x3C00, 0xFF00, 33, OUT_NONE, 0, IPV6, FLAG_NN2, 0),
MC_ENTRY(0x0000, 0x0000, 40, OUT_PROT, 1, IPV6, FLAG_NO, 0),
MC_ENTRY(0x0000, 0x0000, 40, OUT_SIP0, 16, INSTR, FLAG_NO, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_APP0, 4, INSTR, FLAG_IGMP, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_END, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_END, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_END, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_END, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_END, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_END, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_END, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_END, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_END, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_END, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_END, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_END, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_END, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_END, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_END, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_END, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_END, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_END, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_END, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_END, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_END, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_END, 0),
MC_ENTRY(0x0000, 0x0000, 41, OUT_NONE, 0, INSTR, FLAG_END, 0),
};
@@ -0,0 +1,940 @@
// 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/jiffies.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of_mdio.h>
#include <linux/phy/phy-common-props.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/workqueue.h>
#include <net/dsa.h>
#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;
struct delayed_work clear_raneg;
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);
cancel_delayed_work_sync(&priv->clear_raneg);
/* Assert SGMII shell reset (will also clear RANEG bit) */
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, phy_interface_t interface)
{
struct dsa_port *sgmii_port;
unsigned int pol;
int ret;
u16 val;
sgmii_port = dsa_to_port(priv->gswip.ds, GSW1XX_SGMII_PORT);
if (!sgmii_port)
return -EINVAL;
/* Assert and deassert SGMII shell reset */
ret = regmap_set_bits(priv->shell, GSW1XX_SHELL_RST_REQ,
GSW1XX_RST_REQ_SGMII_SHELL);
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);
ret = phy_get_manual_rx_polarity(of_fwnode_handle(sgmii_port->dn),
phy_modes(interface), &pol);
if (ret)
return ret;
/* RX lane seems to be inverted internally, so bit
* GSW1XX_SGMII_PHY_RX0_CFG2_INVERT needs to be set for normal
* (ie. non-inverted) operation matching the chips external pins as
* described in datasheets dated 2023-11-08, ie. pin B20 (RX0_P) being
* the positive signal and pin B21 (RX0_M) being the negative signal of
* the differential input pair.
*/
if (pol == PHY_POL_NORMAL)
val |= GSW1XX_SGMII_PHY_RX0_CFG2_INVERT;
ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_RX0_CFG2, val);
if (ret < 0)
return ret;
val = FIELD_PREP(GSW1XX_SGMII_PHY_TX0_CFG3_VBOOST_LEVEL,
GSW1XX_SGMII_PHY_TX0_CFG3_VBOOST_LEVEL_DEF);
ret = phy_get_manual_tx_polarity(of_fwnode_handle(sgmii_port->dn),
phy_modes(interface), &pol);
if (ret)
return ret;
if (pol == PHY_POL_INVERT)
val |= GSW1XX_SGMII_PHY_TX0_CFG3_INVERT;
ret = regmap_write(priv->sgmii, GSW1XX_SGMII_PHY_TX0_CFG3, val);
if (ret < 0)
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, interface);
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_clear_raneg(struct work_struct *work)
{
struct gsw1xx_priv *priv =
container_of(work, struct gsw1xx_priv, clear_raneg.work);
regmap_clear_bits(priv->sgmii, GSW1XX_SGMII_TBI_ANEGCTL,
GSW1XX_SGMII_TBI_ANEGCTL_RANEG);
}
static void gsw1xx_pcs_an_restart(struct phylink_pcs *pcs)
{
struct gsw1xx_priv *priv = pcs_to_gsw1xx(pcs);
cancel_delayed_work_sync(&priv->clear_raneg);
regmap_set_bits(priv->sgmii, GSW1XX_SGMII_TBI_ANEGCTL,
GSW1XX_SGMII_TBI_ANEGCTL_RANEG);
/* despite being documented as self-clearing, the RANEG bit
* sometimes remains set, preventing auto-negotiation from happening.
* MaxLinear advises to manually clear the bit after 10ms.
*/
schedule_delayed_work(&priv->clear_raneg, msecs_to_jiffies(10));
}
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_lpi_caps(struct phylink_config *config)
{
config->lpi_capabilities = MAC_100FD | MAC_1000FD;
config->lpi_timer_default = 20;
memcpy(config->lpi_interfaces, config->supported_interfaces,
sizeof(config->lpi_interfaces));
}
static void gsw1xx_phylink_get_caps(struct dsa_switch *ds, int port,
struct phylink_config *config)
{
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 ... 3: /* built-in PHYs */
__set_bit(PHY_INTERFACE_MODE_INTERNAL,
config->supported_interfaces);
break;
case 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: /* RGMII or RMII */
__set_bit(PHY_INTERFACE_MODE_RMII,
config->supported_interfaces);
phy_interface_set_rgmii(config->supported_interfaces);
break;
}
gsw1xx_phylink_get_lpi_caps(config);
}
static void gsw150_phylink_get_caps(struct dsa_switch *ds, int port,
struct phylink_config *config)
{
config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
MAC_10 | MAC_100 | MAC_1000;
switch (port) {
case 0 ... 4: /* built-in PHYs */
__set_bit(PHY_INTERFACE_MODE_INTERNAL,
config->supported_interfaces);
break;
case 5: /* GMII or RGMII */
__set_bit(PHY_INTERFACE_MODE_GMII,
config->supported_interfaces);
fallthrough;
case 6: /* RGMII */
phy_interface_set_rgmii(config->supported_interfaces);
break;
}
gsw1xx_phylink_get_lpi_caps(config);
}
static struct phylink_pcs *gsw1xx_phylink_mac_select_pcs(struct phylink_config *config,
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 int gsw1xx_rmii_slew_rate(const struct device_node *np, struct gsw1xx_priv *priv,
const char *prop, u16 mask)
{
u32 rate;
int ret;
ret = of_property_read_u32(np, prop, &rate);
/* Optional property */
if (ret == -EINVAL)
return 0;
if (ret < 0 || rate > 1) {
dev_err(&priv->mdio_dev->dev, "Invalid %s value\n", prop);
return (ret < 0) ? ret : -EINVAL;
}
return regmap_update_bits(priv->shell, GSW1XX_SHELL_RGMII_SLEW_CFG, mask, mask * rate);
}
static int gsw1xx_port_setup(struct dsa_switch *ds, int port)
{
struct dsa_port *dp = dsa_to_port(ds, port);
struct device_node *np = dp->dn;
struct gsw1xx_priv *gsw1xx_priv;
struct gswip_priv *gswip_priv;
if (dp->index != GSW1XX_MII_PORT)
return 0;
gswip_priv = ds->priv;
gsw1xx_priv = container_of(gswip_priv, struct gsw1xx_priv, gswip);
return gsw1xx_rmii_slew_rate(np, gsw1xx_priv,
"maxlinear,slew-rate-txc", RGMII_SLEW_CFG_DRV_TXC) ?:
gsw1xx_rmii_slew_rate(np, gsw1xx_priv,
"maxlinear,slew-rate-txd", RGMII_SLEW_CFG_DRV_TXD);
}
static struct regmap *gsw1xx_regmap_init(struct gsw1xx_priv *priv,
const char *name,
unsigned int reg_base,
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_serdes_pcs_init(struct gsw1xx_priv *priv)
{
/* do nothing if the chip doesn't have a SerDes PCS */
if (!priv->gswip.hw_info->mac_select_pcs)
return 0;
priv->pcs.ops = &gsw1xx_pcs_ops;
priv->pcs.poll = true;
__set_bit(PHY_INTERFACE_MODE_SGMII,
priv->pcs.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_1000BASEX,
priv->pcs.supported_interfaces);
if (priv->gswip.hw_info->supports_2500m)
__set_bit(PHY_INTERFACE_MODE_2500BASEX,
priv->pcs.supported_interfaces);
priv->tbi_interface = PHY_INTERFACE_MODE_NA;
/* assert SGMII reset to power down SGMII unit */
return regmap_set_bits(priv->shell, GSW1XX_SHELL_RST_REQ,
GSW1XX_RST_REQ_SGMII_SHELL);
}
static int gsw1xx_probe(struct mdio_device *mdiodev)
{
struct device *dev = &mdiodev->dev;
struct gsw1xx_priv *priv;
u32 version, val;
u8 shellver;
u16 pnum;
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);
ret = regmap_read(priv->shell, GSW1XX_SHELL_MANU_ID, &val);
if (ret < 0)
return ret;
/* validate chip ID */
if (FIELD_GET(GSW1XX_SHELL_MANU_ID_FIX1, val) != 1)
return -ENODEV;
if (FIELD_GET(GSW1XX_SHELL_MANU_ID_MANID, val) !=
GSW1XX_SHELL_MANU_ID_MANID_VAL)
return -ENODEV;
pnum = FIELD_GET(GSW1XX_SHELL_MANU_ID_PNUML, val);
ret = regmap_read(priv->shell, GSW1XX_SHELL_PNUM_ID, &val);
if (ret < 0)
return ret;
pnum |= FIELD_GET(GSW1XX_SHELL_PNUM_ID_PNUMM, val) << 4;
shellver = FIELD_GET(GSW1XX_SHELL_PNUM_ID_VER, val);
ret = gsw1xx_serdes_pcs_init(priv);
if (ret < 0)
return ret;
/* 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;
INIT_DELAYED_WORK(&priv->clear_raneg, gsw1xx_pcs_clear_raneg);
ret = gswip_probe_common(&priv->gswip, version);
if (ret)
return ret;
dev_info(dev, "standalone switch part number 0x%x v1.%u\n", pnum, shellver);
dev_set_drvdata(dev, &priv->gswip);
return 0;
}
static void gsw1xx_remove(struct mdio_device *mdiodev)
{
struct gswip_priv *priv = dev_get_drvdata(&mdiodev->dev);
struct gsw1xx_priv *gsw1xx_priv;
if (!priv)
return;
dsa_unregister_switch(priv->ds);
gsw1xx_priv = container_of(priv, struct gsw1xx_priv, gswip);
cancel_delayed_work_sync(&gsw1xx_priv->clear_raneg);
}
static void gsw1xx_shutdown(struct mdio_device *mdiodev)
{
struct gswip_priv *priv = dev_get_drvdata(&mdiodev->dev);
struct gsw1xx_priv *gsw1xx_priv;
if (!priv)
return;
dsa_switch_shutdown(priv->ds);
dev_set_drvdata(&mdiodev->dev, NULL);
gsw1xx_priv = container_of(priv, struct gsw1xx_priv, gswip);
cancel_delayed_work_sync(&gsw1xx_priv->clear_raneg);
}
static const struct gswip_hw_info gsw12x_data = {
.max_ports = GSW1XX_PORTS,
.allowed_cpu_ports = BIT(GSW1XX_MII_PORT) | BIT(GSW1XX_SGMII_PORT),
.mii_cfg = {
[0 ... GSW1XX_MII_PORT - 1] = -1,
[GSW1XX_MII_PORT] = GSWIP_MII_CFGp(0),
[GSW1XX_MII_PORT + 1 ... GSWIP_MAX_PORTS - 1] = -1,
},
.mii_pcdu = {
[0 ... GSW1XX_MII_PORT - 1] = -1,
[GSW1XX_MII_PORT] = GSWIP_MII_PCDU0,
[GSW1XX_MII_PORT + 1 ... GSWIP_MAX_PORTS - 1] = -1,
},
.mac_select_pcs = gsw1xx_phylink_mac_select_pcs,
.phylink_get_caps = &gsw1xx_phylink_get_caps,
.supports_2500m = true,
.port_setup = gsw1xx_port_setup,
.pce_microcode = &gsw1xx_pce_microcode,
.pce_microcode_size = ARRAY_SIZE(gsw1xx_pce_microcode),
.tag_protocol = DSA_TAG_PROTO_MXL_GSW1XX,
};
static const struct gswip_hw_info gsw140_data = {
.max_ports = GSW1XX_PORTS,
.allowed_cpu_ports = BIT(GSW1XX_MII_PORT) | BIT(GSW1XX_SGMII_PORT),
.mii_cfg = {
[0 ... GSW1XX_MII_PORT - 1] = -1,
[GSW1XX_MII_PORT] = GSWIP_MII_CFGp(0),
[GSW1XX_MII_PORT + 1 ... GSWIP_MAX_PORTS - 1] = -1,
},
.mii_pcdu = {
[0 ... GSW1XX_MII_PORT - 1] = -1,
[GSW1XX_MII_PORT] = GSWIP_MII_PCDU0,
[GSW1XX_MII_PORT + 1 ... GSWIP_MAX_PORTS - 1] = -1,
},
.mac_select_pcs = gsw1xx_phylink_mac_select_pcs,
.phylink_get_caps = &gsw1xx_phylink_get_caps,
.supports_2500m = true,
.port_setup = gsw1xx_port_setup,
.pce_microcode = &gsw1xx_pce_microcode,
.pce_microcode_size = ARRAY_SIZE(gsw1xx_pce_microcode),
.tag_protocol = DSA_TAG_PROTO_MXL_GSW1XX,
};
static const struct gswip_hw_info gsw141_data = {
.max_ports = GSW1XX_PORTS,
.allowed_cpu_ports = BIT(GSW1XX_MII_PORT) | BIT(GSW1XX_SGMII_PORT),
.mii_cfg = {
[0 ... GSW1XX_MII_PORT - 1] = -1,
[GSW1XX_MII_PORT] = GSWIP_MII_CFGp(0),
[GSW1XX_MII_PORT + 1 ... GSWIP_MAX_PORTS - 1] = -1,
},
.mii_pcdu = {
[0 ... GSW1XX_MII_PORT - 1] = -1,
[GSW1XX_MII_PORT] = GSWIP_MII_PCDU0,
[GSW1XX_MII_PORT + 1 ... GSWIP_MAX_PORTS - 1] = -1,
},
.mac_select_pcs = gsw1xx_phylink_mac_select_pcs,
.phylink_get_caps = gsw1xx_phylink_get_caps,
.port_setup = gsw1xx_port_setup,
.pce_microcode = &gsw1xx_pce_microcode,
.pce_microcode_size = ARRAY_SIZE(gsw1xx_pce_microcode),
.tag_protocol = DSA_TAG_PROTO_MXL_GSW1XX,
};
static const struct gswip_hw_info gsw150_data = {
.max_ports = GSW150_PORTS,
.allowed_cpu_ports = BIT(5) | BIT(6),
.mii_cfg = {
[0 ... 4] = -1,
[5] = 0,
[6] = 10,
},
.mii_pcdu = {
[0 ... 4] = -1,
[5] = 1,
[6] = 11,
},
.phylink_get_caps = gsw150_phylink_get_caps,
/* There is only a single RGMII_SLEW_CFG register in GSW150 and it is
* unknown if RGMII slew configuration affects both RGMII ports
* or only port 5. Use .port_setup which assumes it affects port 5
* for now.
*/
.port_setup = gsw1xx_port_setup,
.pce_microcode = &gsw1xx_pce_microcode,
.pce_microcode_size = ARRAY_SIZE(gsw1xx_pce_microcode),
.tag_protocol = DSA_TAG_PROTO_MXL_GSW1XX,
};
/*
* 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 = "intel,gsw150", .data = &gsw150_data },
{ .compatible = "lantiq,peb7084", .data = &gsw150_data },
{ .compatible = "maxlinear,gsw120", .data = &gsw12x_data },
{ .compatible = "maxlinear,gsw125", .data = &gsw12x_data },
{ .compatible = "maxlinear,gsw140", .data = &gsw140_data },
{ .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 Intel/MaxLinear GSW1xx Ethernet switch");
MODULE_LICENSE("GPL");
@@ -0,0 +1,139 @@
/* 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
#define GSW150_PORTS 7
/* 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)
#define GSW1XX_SHELL_MANU_ID 0x10
#define GSW1XX_SHELL_MANU_ID_PNUML GENMASK(15, 12)
#define GSW1XX_SHELL_MANU_ID_MANID GENMASK(11, 1)
#define GSW1XX_SHELL_MANU_ID_MANID_VAL 0x389
#define GSW1XX_SHELL_MANU_ID_FIX1 BIT(0)
#define GSW1XX_SHELL_PNUM_ID 0x11
#define GSW1XX_SHELL_PNUM_ID_VER GENMASK(15, 12)
#define GSW1XX_SHELL_PNUM_ID_PNUMM GENMASK(11, 0)
/* RGMII PAD Slew Control Register */
#define GSW1XX_SHELL_RGMII_SLEW_CFG 0x78
#define RGMII_SLEW_CFG_DRV_TXC BIT(2)
#define RGMII_SLEW_CFG_DRV_TXD BIT(3)
#define RGMII_SLEW_CFG_RX_2_5_V BIT(4)
#define RGMII_SLEW_CFG_TX_2_5_V BIT(5)
/* 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 */
@@ -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),
};