diff options
Diffstat (limited to 'drivers/net/mscc_eswitch/felix_switch.c')
| -rw-r--r-- | drivers/net/mscc_eswitch/felix_switch.c | 414 | 
1 files changed, 414 insertions, 0 deletions
| diff --git a/drivers/net/mscc_eswitch/felix_switch.c b/drivers/net/mscc_eswitch/felix_switch.c new file mode 100644 index 00000000000..f20e84e0f10 --- /dev/null +++ b/drivers/net/mscc_eswitch/felix_switch.c @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause +/* + * Felix (VSC9959) Ethernet switch driver + * Copyright 2018-2021 NXP Semiconductors + */ + +/* + * This driver is used for the Ethernet switch integrated into NXP LS1028A. + * Felix switch is derived from Microsemi Ocelot but there are several NXP + * adaptations that makes the two U-Boot drivers largely incompatible. + * + * Felix on LS1028A has 4 front panel ports and two internal ports, connected + * to ENETC interfaces.  We're using one of the ENETC interfaces to push traffic + * into the switch.  Injection/extraction headers are used to identify + * egress/ingress ports in the switch for Tx/Rx. + */ + +#include <dm/device_compat.h> +#include <linux/delay.h> +#include <net/dsa.h> +#include <asm/io.h> +#include <miiphy.h> +#include <pci.h> + +/* defines especially around PCS are reused from enetc */ +#include "../fsl_enetc.h" + +#define PCI_DEVICE_ID_FELIX_ETHSW	0xEEF0 + +/* Felix has in fact 6 ports, but we don't use the last internal one */ +#define FELIX_PORT_COUNT		5 +/* Front panel port mask */ +#define FELIX_FP_PORT_MASK		0xf + +/* Register map for BAR4 */ +#define FELIX_SYS			0x010000 +#define FELIX_ES0			0x040000 +#define FELIX_IS1			0x050000 +#define FELIX_IS2			0x060000 +#define FELIX_GMII(port)		(0x100000 + (port) * 0x10000) +#define FELIX_QSYS			0x200000 + +#define FELIX_SYS_SYSTEM		(FELIX_SYS + 0x00000E00) +#define  FELIX_SYS_SYSTEM_EN		BIT(0) +#define FELIX_SYS_RAM_CTRL		(FELIX_SYS + 0x00000F24) +#define  FELIX_SYS_RAM_CTRL_INIT	BIT(1) +#define FELIX_SYS_SYSTEM_PORT_MODE(a)	(FELIX_SYS_SYSTEM + 0xC + (a) * 4) +#define  FELIX_SYS_SYSTEM_PORT_MODE_CPU	0x0000001e + +#define FELIX_ES0_TCAM_CTRL		(FELIX_ES0 + 0x000003C0) +#define  FELIX_ES0_TCAM_CTRL_EN		BIT(0) +#define FELIX_IS1_TCAM_CTRL		(FELIX_IS1 + 0x000003C0) +#define  FELIX_IS1_TCAM_CTRL_EN		BIT(0) +#define FELIX_IS2_TCAM_CTRL		(FELIX_IS2 + 0x000003C0) +#define  FELIX_IS2_TCAM_CTRL_EN		BIT(0) + +#define FELIX_GMII_CLOCK_CFG(port)	(FELIX_GMII(port) + 0x00000000) +#define  FELIX_GMII_CLOCK_CFG_LINK_1G	1 +#define  FELIX_GMII_CLOCK_CFG_LINK_100M	2 +#define  FELIX_GMII_CLOCK_CFG_LINK_10M	3 +#define FELIX_GMII_MAC_ENA_CFG(port)	(FELIX_GMII(port) + 0x0000001C) +#define  FELIX_GMII_MAX_ENA_CFG_TX	BIT(0) +#define  FELIX_GMII_MAX_ENA_CFG_RX	BIT(4) +#define FELIX_GMII_MAC_IFG_CFG(port)	(FELIX_GMII(port) + 0x0000001C + 0x14) +#define  FELIX_GMII_MAC_IFG_CFG_DEF	0x515 + +#define FELIX_QSYS_SYSTEM		(FELIX_QSYS + 0x0000F460) +#define FELIX_QSYS_SYSTEM_SW_PORT_MODE(a)	\ +					(FELIX_QSYS_SYSTEM + 0x20 + (a) * 4) +#define  FELIX_QSYS_SYSTEM_SW_PORT_ENA		BIT(14) +#define  FELIX_QSYS_SYSTEM_SW_PORT_LOSSY	BIT(9) +#define  FELIX_QSYS_SYSTEM_SW_PORT_SCH(a)	(((a) & 0x3800) << 11) +#define FELIX_QSYS_SYSTEM_EXT_CPU_CFG	(FELIX_QSYS_SYSTEM + 0x80) +#define  FELIX_QSYS_SYSTEM_EXT_CPU_PORT(a)	(((a) & 0xf) << 8 | 0xff) + +/* internal MDIO in BAR0 */ +#define FELIX_PM_IMDIO_BASE		0x8030 + +/* Serdes block on LS1028A */ +#define FELIX_SERDES_BASE		0x1ea0000L +#define FELIX_SERDES_LNATECR0(lane)	(FELIX_SERDES_BASE + 0x818 + \ +					 (lane) * 0x40) +#define  FELIX_SERDES_LNATECR0_ADPT_EQ	0x00003000 +#define FELIX_SERDES_SGMIICR1(lane)	(FELIX_SERDES_BASE + 0x1804 + \ +					 (lane) * 0x10) +#define  FELIX_SERDES_SGMIICR1_SGPCS	BIT(11) +#define  FELIX_SERDES_SGMIICR1_MDEV(a)	(((a) & 0x1f) << 27) + +#define FELIX_PCS_CTRL			0 +#define  FELIX_PCS_CTRL_RST		BIT(15) + +/* + * The long prefix format used here contains two dummy MAC addresses, a magic + * value in place of a VLAN tag followed by the extraction/injection header and + * the original L2 frame.  Out of all this we only use the port ID. + */ +#define FELIX_DSA_TAG_LEN		sizeof(struct felix_dsa_tag) +#define FELIX_DSA_TAG_MAGIC		0x0a008088 +#define FELIX_DSA_TAG_INJ_PORT		7 +#define  FELIX_DSA_TAG_INJ_PORT_SET(a)	(0x1 << ((a) & FELIX_FP_PORT_MASK)) +#define FELIX_DSA_TAG_EXT_PORT		10 +#define  FELIX_DSA_TAG_EXT_PORT_GET(a)	((a) >> 3) + +struct felix_dsa_tag { +	uchar d_mac[6]; +	uchar s_mac[6]; +	u32   magic; +	uchar meta[16]; +}; + +struct felix_priv { +	void *regs_base; +	void *imdio_base; +	struct mii_dev imdio; +}; + +/* MDIO wrappers, we're using these to drive internal MDIO to get to serdes */ +static int felix_mdio_read(struct mii_dev *bus, int addr, int devad, int reg) +{ +	struct enetc_mdio_priv priv; + +	priv.regs_base = bus->priv; +	return enetc_mdio_read_priv(&priv, addr, devad, reg); +} + +static int felix_mdio_write(struct mii_dev *bus, int addr, int devad, int reg, +			    u16 val) +{ +	struct enetc_mdio_priv priv; + +	priv.regs_base = bus->priv; +	return enetc_mdio_write_priv(&priv, addr, devad, reg, val); +} + +/* set up serdes for SGMII */ +static void felix_init_sgmii(struct mii_dev *imdio, int pidx, bool an) +{ +	u16 reg; + +	/* set up PCS lane address */ +	out_le32(FELIX_SERDES_SGMIICR1(pidx), FELIX_SERDES_SGMIICR1_SGPCS | +		 FELIX_SERDES_SGMIICR1_MDEV(pidx)); + +	/* +	 * Set to SGMII mode, for 1Gbps enable AN, for 2.5Gbps set fixed speed. +	 * Although fixed speed is 1Gbps, we could be running at 2.5Gbps based +	 * on PLL configuration.  Setting 1G for 2.5G here is counter intuitive +	 * but intentional. +	 */ +	reg = ENETC_PCS_IF_MODE_SGMII; +	reg |= an ? ENETC_PCS_IF_MODE_SGMII_AN : ENETC_PCS_IF_MODE_SPEED_1G; +	felix_mdio_write(imdio, pidx, MDIO_DEVAD_NONE, +			 ENETC_PCS_IF_MODE, reg); + +	/* Dev ability - SGMII */ +	felix_mdio_write(imdio, pidx, MDIO_DEVAD_NONE, +			 ENETC_PCS_DEV_ABILITY, ENETC_PCS_DEV_ABILITY_SGMII); + +	/* Adjust link timer for SGMII */ +	felix_mdio_write(imdio, pidx, MDIO_DEVAD_NONE, +			 ENETC_PCS_LINK_TIMER1, ENETC_PCS_LINK_TIMER1_VAL); +	felix_mdio_write(imdio, pidx, MDIO_DEVAD_NONE, +			 ENETC_PCS_LINK_TIMER2, ENETC_PCS_LINK_TIMER2_VAL); + +	reg = ENETC_PCS_CR_DEF_VAL; +	reg |= an ? ENETC_PCS_CR_RESET_AN : ENETC_PCS_CR_RST; +	/* restart PCS AN */ +	felix_mdio_write(imdio, pidx, MDIO_DEVAD_NONE, +			 ENETC_PCS_CR, reg); +} + +/* set up MAC and serdes for (Q)SXGMII */ +static int felix_init_sxgmii(struct mii_dev *imdio, int pidx) +{ +	int timeout = 1000; + +	/* set up transit equalization control on serdes lane */ +	out_le32(FELIX_SERDES_LNATECR0(1), FELIX_SERDES_LNATECR0_ADPT_EQ); + +	/*reset lane */ +	felix_mdio_write(imdio, pidx, MDIO_MMD_PCS, FELIX_PCS_CTRL, +			 FELIX_PCS_CTRL_RST); +	while (felix_mdio_read(imdio, pidx, MDIO_MMD_PCS, +			       FELIX_PCS_CTRL) & FELIX_PCS_CTRL_RST && +			--timeout) { +		mdelay(10); +	} +	if (felix_mdio_read(imdio, pidx, MDIO_MMD_PCS, +			    FELIX_PCS_CTRL) & FELIX_PCS_CTRL_RST) +		return -ETIME; + +	/* Dev ability - SXGMII */ +	felix_mdio_write(imdio, pidx, ENETC_PCS_DEVAD_REPL, +			 ENETC_PCS_DEV_ABILITY, ENETC_PCS_DEV_ABILITY_SXGMII); + +	/* Restart PCS AN */ +	felix_mdio_write(imdio, pidx, ENETC_PCS_DEVAD_REPL, ENETC_PCS_CR, +			 ENETC_PCS_CR_RST | ENETC_PCS_CR_RESET_AN); +	felix_mdio_write(imdio, pidx, ENETC_PCS_DEVAD_REPL, +			 ENETC_PCS_REPL_LINK_TIMER_1, +			 ENETC_PCS_REPL_LINK_TIMER_1_DEF); +	felix_mdio_write(imdio, pidx, ENETC_PCS_DEVAD_REPL, +			 ENETC_PCS_REPL_LINK_TIMER_2, +			 ENETC_PCS_REPL_LINK_TIMER_2_DEF); + +	return 0; +} + +/* Apply protocol specific configuration to MAC, serdes as needed */ +static void felix_start_pcs(struct udevice *dev, int port, +			    struct phy_device *phy, struct mii_dev *imdio) +{ +	bool autoneg = true; + +	if (phy->phy_id == PHY_FIXED_ID || +	    phy->interface == PHY_INTERFACE_MODE_SGMII_2500) +		autoneg = false; + +	switch (phy->interface) { +	case PHY_INTERFACE_MODE_SGMII: +	case PHY_INTERFACE_MODE_SGMII_2500: +	case PHY_INTERFACE_MODE_QSGMII: +		felix_init_sgmii(imdio, port, autoneg); +		break; +	case PHY_INTERFACE_MODE_XGMII: +	case PHY_INTERFACE_MODE_XFI: +	case PHY_INTERFACE_MODE_USXGMII: +		if (felix_init_sxgmii(imdio, port)) +			dev_err(dev, "PCS reset timeout on port %d\n", port); +		break; +	default: +		break; +	} +} + +void felix_init(struct udevice *dev) +{ +	struct dsa_pdata *pdata = dev_get_uclass_plat(dev); +	struct felix_priv *priv = dev_get_priv(dev); +	void *base = priv->regs_base; +	int timeout = 100; + +	/* Init core memories */ +	out_le32(base + FELIX_SYS_RAM_CTRL, FELIX_SYS_RAM_CTRL_INIT); +	while (in_le32(base + FELIX_SYS_RAM_CTRL) & FELIX_SYS_RAM_CTRL_INIT && +	       --timeout) +		udelay(10); +	if (in_le32(base + FELIX_SYS_RAM_CTRL) & FELIX_SYS_RAM_CTRL_INIT) +		dev_err(dev, "Timeout waiting for switch memories\n"); + +	/* Start switch core, set up ES0, IS1, IS2 */ +	out_le32(base + FELIX_SYS_SYSTEM, FELIX_SYS_SYSTEM_EN); +	out_le32(base + FELIX_ES0_TCAM_CTRL, FELIX_ES0_TCAM_CTRL_EN); +	out_le32(base + FELIX_IS1_TCAM_CTRL, FELIX_IS1_TCAM_CTRL_EN); +	out_le32(base + FELIX_IS2_TCAM_CTRL, FELIX_IS2_TCAM_CTRL_EN); +	udelay(20); + +	priv->imdio.read = felix_mdio_read; +	priv->imdio.write = felix_mdio_write; +	priv->imdio.priv = priv->imdio_base + FELIX_PM_IMDIO_BASE; +	strncpy(priv->imdio.name, dev->name, MDIO_NAME_LEN); + +	/* set up CPU port */ +	out_le32(base + FELIX_QSYS_SYSTEM_EXT_CPU_CFG, +		 FELIX_QSYS_SYSTEM_EXT_CPU_PORT(pdata->cpu_port)); +	out_le32(base + FELIX_SYS_SYSTEM_PORT_MODE(pdata->cpu_port), +		 FELIX_SYS_SYSTEM_PORT_MODE_CPU); +} + +/* + * Probe Felix: + * - enable the PCI function + * - map BAR 4 + * - init switch core and port registers + */ +static int felix_probe(struct udevice *dev) +{ +	struct felix_priv *priv = dev_get_priv(dev); + +	if (ofnode_valid(dev_ofnode(dev)) && +	    !ofnode_is_available(dev_ofnode(dev))) { +		dev_dbg(dev, "switch disabled\n"); +		return -ENODEV; +	} + +	priv->imdio_base = dm_pci_map_bar(dev, PCI_BASE_ADDRESS_0, 0); +	if (!priv->imdio_base) { +		dev_err(dev, "failed to map BAR0\n"); +		return -EINVAL; +	} + +	priv->regs_base = dm_pci_map_bar(dev, PCI_BASE_ADDRESS_4, 0); +	if (!priv->regs_base) { +		dev_err(dev, "failed to map BAR4\n"); +		return -EINVAL; +	} + +	/* register internal MDIO for debug */ +	if (!miiphy_get_dev_by_name(dev->name)) { +		struct mii_dev *mii_bus; + +		mii_bus = mdio_alloc(); +		mii_bus->read = felix_mdio_read; +		mii_bus->write = felix_mdio_write; +		mii_bus->priv = priv->imdio_base + FELIX_PM_IMDIO_BASE; +		strncpy(mii_bus->name, dev->name, MDIO_NAME_LEN); +		mdio_register(mii_bus); +	} + +	dm_pci_clrset_config16(dev, PCI_COMMAND, 0, PCI_COMMAND_MEMORY); + +	dsa_set_tagging(dev, FELIX_DSA_TAG_LEN, 0); + +	/* set up registers */ +	felix_init(dev); + +	return 0; +} + +static int felix_port_enable(struct udevice *dev, int port, +			     struct phy_device *phy) +{ +	int supported = PHY_GBIT_FEATURES | SUPPORTED_2500baseX_Full; +	struct felix_priv *priv = dev_get_priv(dev); +	void *base = priv->regs_base; + +	/* Set up MAC registers */ +	out_le32(base + FELIX_GMII_CLOCK_CFG(port), +		 FELIX_GMII_CLOCK_CFG_LINK_1G); + +	out_le32(base + FELIX_GMII_MAC_IFG_CFG(port), +		 FELIX_GMII_MAC_IFG_CFG_DEF); + +	out_le32(base + FELIX_GMII_MAC_ENA_CFG(port), +		 FELIX_GMII_MAX_ENA_CFG_TX | FELIX_GMII_MAX_ENA_CFG_RX); + +	out_le32(base + FELIX_QSYS_SYSTEM_SW_PORT_MODE(port), +		 FELIX_QSYS_SYSTEM_SW_PORT_ENA | +		 FELIX_QSYS_SYSTEM_SW_PORT_LOSSY | +		 FELIX_QSYS_SYSTEM_SW_PORT_SCH(1)); + +	felix_start_pcs(dev, port, phy, &priv->imdio); + +	phy->supported &= supported; +	phy->advertising &= supported; +	phy_config(phy); + +	phy_startup(phy); + +	return 0; +} + +static void felix_port_disable(struct udevice *dev, int pidx, +			       struct phy_device *phy) +{ +	struct felix_priv *priv = dev_get_priv(dev); +	void *base = priv->regs_base; + +	out_le32(base + FELIX_GMII_MAC_ENA_CFG(pidx), 0); + +	out_le32(base + FELIX_QSYS_SYSTEM_SW_PORT_MODE(pidx), +		 FELIX_QSYS_SYSTEM_SW_PORT_LOSSY | +		 FELIX_QSYS_SYSTEM_SW_PORT_SCH(1)); + +	/* +	 * we don't call phy_shutdown here to avoid waiting next time we use +	 * the port, but the downside is that remote side will think we're +	 * actively processing traffic although we are not. +	 */ +} + +static int felix_xmit(struct udevice *dev, int pidx, void *packet, int length) +{ +	struct felix_dsa_tag *tag = packet; + +	tag->magic = FELIX_DSA_TAG_MAGIC; +	tag->meta[FELIX_DSA_TAG_INJ_PORT] = FELIX_DSA_TAG_INJ_PORT_SET(pidx); + +	return 0; +} + +static int felix_rcv(struct udevice *dev, int *pidx, void *packet, int length) +{ +	struct felix_dsa_tag *tag = packet; + +	if (tag->magic != FELIX_DSA_TAG_MAGIC) +		return -EINVAL; + +	*pidx = FELIX_DSA_TAG_EXT_PORT_GET(tag->meta[FELIX_DSA_TAG_EXT_PORT]); + +	return 0; +} + +static const struct dsa_ops felix_dsa_ops = { +	.port_enable	= felix_port_enable, +	.port_disable	= felix_port_disable, +	.xmit		= felix_xmit, +	.rcv		= felix_rcv, +}; + +U_BOOT_DRIVER(felix_ethsw) = { +	.name		= "felix-switch", +	.id		= UCLASS_DSA, +	.probe		= felix_probe, +	.ops		= &felix_dsa_ops, +	.priv_auto	= sizeof(struct felix_priv), +}; + +static struct pci_device_id felix_ethsw_ids[] = { +	{ PCI_DEVICE(PCI_VENDOR_ID_FREESCALE, PCI_DEVICE_ID_FELIX_ETHSW) }, +	{} +}; + +U_BOOT_PCI_DEVICE(felix_ethsw, felix_ethsw_ids); | 
