[PATCH 7/8] net: add MV88E61xx DSA driver

Tim Harvey tharvey at gateworks.com
Thu May 12 02:20:02 CEST 2022


Add a DSA driver for the MV88E61xx compatible GbE Ethernet switches.

Cc: Marek Behún <marek.behun at nic.cz>
Cc: Vladimir Oltean <vladimir.oltean at nxp.com>
Signed-off-by: Tim Harvey <tharvey at gateworks.com>
---
v2:
 - rebase on v2022.07-rc1 (use ofnode_get_phy_node)
 - remove unused commented out fields from struct
 - remove unused PORT_MASK macro
 - remove phy from priv struct name
 - refactor code from original drivers/net/phy/mv88e61xx with
   suggestions from review to consolidate some functions
   into mv88e61xx_dsa_port_enable
 - remove unecessary skiping of disabling of CPU port
 - remove unecessary dev_set_parent_priv
 - remove unnecessary static init flag
 - replace debug with a dev_warn if switch device-id unsupported
 - remove unnecessary xmit/recv functions as we rely on the fact that
   only a single port is active instead of mangling packets
---
 drivers/net/Kconfig     |   7 +
 drivers/net/Makefile    |   1 +
 drivers/net/mv88e61xx.c | 843 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 851 insertions(+)
 create mode 100644 drivers/net/mv88e61xx.c

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 7fe0e00649cf..edb1a0898986 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -433,6 +433,13 @@ config LPC32XX_ETH
 	depends on ARCH_LPC32XX
 	default y
 
+config MV88E61XX
+	bool "Marvell MV88E61xx GbE switch DSA driver"
+	depends on DM_DSA && DM_MDIO
+	help
+	  This driver implements a DSA switch driver for the MV88E61xx family
+	  of GbE switches using the MDIO interface
+
 config MVGBE
 	bool "Marvell Orion5x/Kirkwood network interface support"
 	depends on ARCH_KIRKWOOD || ARCH_ORION5X
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 69fb3bbbf7cb..36b4c279430a 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -59,6 +59,7 @@ obj-$(CONFIG_MEDIATEK_ETH) += mtk_eth.o
 obj-$(CONFIG_MPC8XX_FEC) += mpc8xx_fec.o
 obj-$(CONFIG_MT7620_ETH) += mt7620-eth.o
 obj-$(CONFIG_MT7628_ETH) += mt7628-eth.o
+obj-$(CONFIG_MV88E61XX) += mv88e61xx.o
 obj-$(CONFIG_MVGBE) += mvgbe.o
 obj-$(CONFIG_MVMDIO) += mvmdio.o
 obj-$(CONFIG_MVNETA) += mvneta.o
diff --git a/drivers/net/mv88e61xx.c b/drivers/net/mv88e61xx.c
new file mode 100644
index 000000000000..514835bf03b9
--- /dev/null
+++ b/drivers/net/mv88e61xx.c
@@ -0,0 +1,843 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2022
+ * Gateworks Corporation <www.gateworks.com>
+ * Tim Harvey <tharvey at gateworks.com>
+ *
+ * (C) Copyright 2015
+ * Elecsys Corporation <www.elecsyscorp.com>
+ * Kevin Smith <kevin.smith at elecsyscorp.com>
+ *
+ * Original driver:
+ * (C) Copyright 2009
+ * Marvell Semiconductor <www.marvell.com>
+ * Prafulla Wadaskar <prafulla at marvell.com>
+ */
+
+/*
+ * DSA driver for mv88e61xx ethernet switches.
+ *
+ * This driver configures the mv88e61xx for basic use as a DSA switch.
+ *
+ * This driver was adapted from drivers/net/phy/mv88e61xx and tested
+ * on the mv88e6176 via an SGMII interface.
+ */
+
+#include <bitfield.h>
+#include <common.h>
+#include <miiphy.h>
+#include <dm/device.h>
+#include <dm/device_compat.h>
+#include <dm/device-internal.h>
+#include <dm/lists.h>
+#include <dm/of_extra.h>
+#include <linux/delay.h>
+#include <net/dsa.h>
+
+/* Device addresses */
+#define DEVADDR_PHY(p)			(p)
+#define DEVADDR_SERDES			0x0F
+
+/* SMI indirection registers for multichip addressing mode */
+#define SMI_CMD_REG			0x00
+#define SMI_DATA_REG			0x01
+
+/* Global registers */
+#define GLOBAL1_STATUS			0x00
+#define GLOBAL1_CTRL			0x04
+#define GLOBAL1_MON_CTRL		0x1A
+
+/* Global 2 registers */
+#define GLOBAL2_REG_PHY_CMD		0x18
+#define GLOBAL2_REG_PHY_DATA		0x19
+#define GLOBAL2_REG_SCRATCH		0x1A
+
+/* Port registers */
+#define PORT_REG_STATUS			0x00
+#define PORT_REG_PHYS_CTRL		0x01
+#define PORT_REG_SWITCH_ID		0x03
+#define PORT_REG_CTRL			0x04
+#define PORT_REG_VLAN_MAP		0x06
+#define PORT_REG_VLAN_ID		0x07
+#define PORT_REG_LED_CTRL		0x16
+
+/* Phy registers */
+#define PHY_REG_CTRL1			0x10
+#define PHY_REG_STATUS1			0x11
+#define PHY_REG_PAGE			0x16
+
+/* Serdes registers */
+#define SERDES_REG_CTRL_1		0x10
+
+/* Phy page numbers */
+#define PHY_PAGE_COPPER			0
+#define PHY_PAGE_SERDES			1
+
+/* Register fields */
+#define GLOBAL1_CTRL_SWRESET		BIT(15)
+
+#define GLOBAL1_MON_CTRL_CPUDEST_SHIFT	4
+#define GLOBAL1_MON_CTRL_CPUDEST_WIDTH	4
+
+#define PORT_REG_STATUS_SPEED_SHIFT	8
+#define PORT_REG_STATUS_SPEED_10	0
+#define PORT_REG_STATUS_SPEED_100	1
+#define PORT_REG_STATUS_SPEED_1000	2
+
+#define PORT_REG_STATUS_CMODE_MASK		0xF
+#define PORT_REG_STATUS_CMODE_100BASE_X		0x8
+#define PORT_REG_STATUS_CMODE_1000BASE_X	0x9
+#define PORT_REG_STATUS_CMODE_SGMII		0xa
+
+#define PORT_REG_PHYS_CTRL_RGMII_DELAY_RXCLK	BIT(15)
+#define PORT_REG_PHYS_CTRL_RGMII_DELAY_TXCLK	BIT(14)
+#define PORT_REG_PHYS_CTRL_PCS_AN_EN	BIT(10)
+#define PORT_REG_PHYS_CTRL_PCS_AN_RST	BIT(9)
+#define PORT_REG_PHYS_CTRL_FC_VALUE	BIT(7)
+#define PORT_REG_PHYS_CTRL_FC_FORCE	BIT(6)
+#define PORT_REG_PHYS_CTRL_LINK_VALUE	BIT(5)
+#define PORT_REG_PHYS_CTRL_LINK_FORCE	BIT(4)
+#define PORT_REG_PHYS_CTRL_DUPLEX_VALUE	BIT(3)
+#define PORT_REG_PHYS_CTRL_DUPLEX_FORCE	BIT(2)
+#define PORT_REG_PHYS_CTRL_SPD1000	BIT(1)
+#define PORT_REG_PHYS_CTRL_SPD100	BIT(0)
+#define PORT_REG_PHYS_CTRL_SPD_MASK	(BIT(1) | BIT(0))
+
+#define PORT_REG_CTRL_PSTATE_SHIFT	0
+#define PORT_REG_CTRL_PSTATE_WIDTH	2
+
+#define PORT_REG_VLAN_ID_DEF_VID_SHIFT	0
+#define PORT_REG_VLAN_ID_DEF_VID_WIDTH	12
+
+#define PORT_REG_VLAN_MAP_TABLE_SHIFT	0
+#define PORT_REG_VLAN_MAP_TABLE_WIDTH	11
+
+#define SERDES_REG_CTRL_1_FORCE_LINK	BIT(10)
+
+/* Field values */
+#define PORT_REG_CTRL_PSTATE_DISABLED	0
+#define PORT_REG_CTRL_PSTATE_FORWARD	3
+
+#define PHY_REG_CTRL1_ENERGY_DET_OFF	0
+#define PHY_REG_CTRL1_ENERGY_DET_SENSE_PULSE	1
+#define PHY_REG_CTRL1_ENERGY_DET_SENSE_ONLY	2
+#define PHY_REG_CTRL1_ENERGY_DET_SENSE_XMIT	3
+
+/* PHY Status Register */
+#define PHY_REG_STATUS1_SPEED		0xc000
+#define PHY_REG_STATUS1_GBIT		0x8000
+#define PHY_REG_STATUS1_100		0x4000
+#define PHY_REG_STATUS1_DUPLEX		0x2000
+#define PHY_REG_STATUS1_SPDDONE		0x0800
+#define PHY_REG_STATUS1_LINK		0x0400
+#define PHY_REG_STATUS1_ENERGY		0x0010
+
+/*
+ * Macros for building commands for indirect addressing modes.  These are valid
+ * for both the indirect multichip addressing mode and the PHY indirection
+ * required for the writes to any PHY register.
+ */
+#define SMI_BUSY			BIT(15)
+#define SMI_CMD_CLAUSE_22		BIT(12)
+#define SMI_CMD_CLAUSE_22_OP_READ	(2 << 10)
+#define SMI_CMD_CLAUSE_22_OP_WRITE	(1 << 10)
+
+#define SMI_CMD_READ			(SMI_BUSY | SMI_CMD_CLAUSE_22 | \
+					 SMI_CMD_CLAUSE_22_OP_READ)
+#define SMI_CMD_WRITE			(SMI_BUSY | SMI_CMD_CLAUSE_22 | \
+					 SMI_CMD_CLAUSE_22_OP_WRITE)
+
+#define SMI_CMD_ADDR_SHIFT		5
+#define SMI_CMD_ADDR_WIDTH		5
+#define SMI_CMD_REG_SHIFT		0
+#define SMI_CMD_REG_WIDTH		5
+
+/* Defines for Scratch and Misc Registers */
+#define SCRATCH_UPDATE			BIT(15)
+#define SCRATCH_ADDR_SHIFT		8
+#define SCRATCH_ADDR_WIDTH		7
+
+/* Scratch registers */
+#define SCRATCH_ADDR_GPIO0DIR		0x62 /* GPIO[7:0] direction 1=input */
+#define SCRATCH_ADDR_GPIO1DIR		0x63 /* GPIO[14:8] direction 1=input */
+#define SCRATCH_ADDR_GPIO0DATA		0x64 /* GPIO[7:0] data */
+#define SCRATCH_ADDR_GPIO1DATA		0x65 /* GPIO[14:8] data */
+#define SCRATCH_ADDR_GPIO0CTRL		0x68 /* GPIO[1:0] control */
+#define SCRATCH_ADDR_GPIO1CTRL		0x69 /* GPIO[3:2] control */
+
+/* ID register values for different switch models */
+#define PORT_SWITCH_ID_6020		0x0200
+#define PORT_SWITCH_ID_6070		0x0700
+#define PORT_SWITCH_ID_6071		0x0710
+#define PORT_SWITCH_ID_6096		0x0980
+#define PORT_SWITCH_ID_6097		0x0990
+#define PORT_SWITCH_ID_6172		0x1720
+#define PORT_SWITCH_ID_6176		0x1760
+#define PORT_SWITCH_ID_6220		0x2200
+#define PORT_SWITCH_ID_6240		0x2400
+#define PORT_SWITCH_ID_6250		0x2500
+#define PORT_SWITCH_ID_6352		0x3520
+
+struct mv88e61xx_priv {
+	int smi_addr;
+	int id;
+	int port_count;		/* Number of switch ports */
+	int port_reg_base;	/* Base of the switch port registers */
+	u8 global1;	/* Offset of Switch Global 1 registers */
+	u8 global2;	/* Offset of Switch Global 2 registers */
+	u8 phy_ctrl1_en_det_shift; /* 'EDet' bit field offset */
+	u8 phy_ctrl1_en_det_width; /* Width of 'EDet' bit field */
+	u8 phy_ctrl1_en_det_ctrl;  /* 'EDet' control value */
+};
+
+static inline int smi_cmd(int cmd, int addr, int reg)
+{
+	cmd = bitfield_replace(cmd, SMI_CMD_ADDR_SHIFT, SMI_CMD_ADDR_WIDTH,
+			       addr);
+	cmd = bitfield_replace(cmd, SMI_CMD_REG_SHIFT, SMI_CMD_REG_WIDTH, reg);
+	return cmd;
+}
+
+static inline int smi_cmd_read(int addr, int reg)
+{
+	return smi_cmd(SMI_CMD_READ, addr, reg);
+}
+
+static inline int smi_cmd_write(int addr, int reg)
+{
+	return smi_cmd(SMI_CMD_WRITE, addr, reg);
+}
+
+/* Wait for the current SMI indirect command to complete */
+static int mv88e61xx_smi_wait(struct udevice *dev, int smi_addr)
+{
+	int val;
+	u32 timeout = 100;
+
+	do {
+		val = dm_mdio_read(dev->parent, smi_addr, MDIO_DEVAD_NONE, SMI_CMD_REG);
+		if (val >= 0 && (val & SMI_BUSY) == 0)
+			return 0;
+
+		mdelay(1);
+	} while (--timeout);
+
+	dev_err(dev, "SMI busy timeout\n");
+	return -ETIMEDOUT;
+}
+
+/*
+ * The mv88e61xx has three types of addresses: the smi bus address, the device
+ * address, and the register address.  The smi bus address distinguishes it on
+ * the smi bus from other PHYs or switches.  The device address determines
+ * which on-chip register set you are reading/writing (the various PHYs, their
+ * associated ports, or global configuration registers).  The register address
+ * is the offset of the register you are reading/writing.
+ *
+ * When the mv88e61xx is hardware configured to have address zero, it behaves in
+ * single-chip addressing mode, where it responds to all SMI addresses, using
+ * the smi address as its device address.  This obviously only works when this
+ * is the only chip on the SMI bus.  This allows the driver to access device
+ * registers without using indirection.  When the chip is configured to a
+ * non-zero address, it only responds to that SMI address and requires indirect
+ * writes to access the different device addresses.
+ */
+static int mv88e61xx_reg_read(struct udevice *dev, int addr, int reg)
+{
+	struct mv88e61xx_priv *priv = dev_get_priv(dev);
+	int smi_addr = priv->smi_addr;
+	int res;
+
+	/* In single-chip mode, the device can be addressed directly */
+	if (smi_addr == 0)
+		return dm_mdio_read(dev->parent, addr, MDIO_DEVAD_NONE, reg);
+
+	/* Wait for the bus to become free */
+	res = mv88e61xx_smi_wait(dev, smi_addr);
+	if (res < 0)
+		return res;
+
+	/* Issue the read command */
+	res = dm_mdio_write(dev->parent, smi_addr, MDIO_DEVAD_NONE, SMI_CMD_REG,
+			    smi_cmd_read(addr, reg));
+	if (res < 0)
+		return res;
+
+	/* Wait for the read command to complete */
+	res = mv88e61xx_smi_wait(dev, smi_addr);
+	if (res < 0)
+		return res;
+
+	/* Read the data */
+	res = dm_mdio_read(dev->parent, smi_addr, MDIO_DEVAD_NONE, SMI_DATA_REG);
+	if (res < 0)
+		return res;
+
+	return bitfield_extract(res, 0, 16);
+}
+
+/* See the comment above mv88e61xx_reg_read */
+static int mv88e61xx_reg_write(struct udevice *dev, int addr, int reg, u16 val)
+{
+	struct mv88e61xx_priv *priv = dev_get_priv(dev);
+	int smi_addr = priv->smi_addr;
+	int res;
+
+	/* In single-chip mode, the device can be addressed directly */
+	if (smi_addr == 0)
+		return dm_mdio_write(dev->parent, addr, MDIO_DEVAD_NONE, reg, val);
+
+	/* Wait for the bus to become free */
+	res = mv88e61xx_smi_wait(dev, smi_addr);
+	if (res < 0)
+		return res;
+
+	/* Set the data to write */
+	res = dm_mdio_write(dev->parent, smi_addr, MDIO_DEVAD_NONE,
+			    SMI_DATA_REG, val);
+	if (res < 0)
+		return res;
+
+	/* Issue the write command */
+	res = dm_mdio_write(dev->parent, smi_addr, MDIO_DEVAD_NONE, SMI_CMD_REG,
+			    smi_cmd_write(addr, reg));
+	if (res < 0)
+		return res;
+
+	/* Wait for the write command to complete */
+	res = mv88e61xx_smi_wait(dev, smi_addr);
+	if (res < 0)
+		return res;
+
+	return 0;
+}
+
+static int mv88e61xx_phy_wait(struct udevice *dev)
+{
+	struct mv88e61xx_priv *priv = dev_get_priv(dev);
+	int val;
+	u32 timeout = 100;
+
+	do {
+		val = mv88e61xx_reg_read(dev, priv->global2, GLOBAL2_REG_PHY_CMD);
+		if (val >= 0 && (val & SMI_BUSY) == 0)
+			return 0;
+
+		mdelay(1);
+	} while (--timeout);
+
+	return -ETIMEDOUT;
+}
+
+static int mv88e61xx_phy_read_indirect(struct udevice *dev, int phyad, int devad, int reg)
+{
+	struct mv88e61xx_priv *priv = dev_get_priv(dev);
+	int res;
+
+	/* Issue command to read */
+	res = mv88e61xx_reg_write(dev, priv->global2,
+				  GLOBAL2_REG_PHY_CMD,
+				  smi_cmd_read(phyad, reg));
+
+	/* Wait for data to be read */
+	res = mv88e61xx_phy_wait(dev);
+	if (res < 0)
+		return res;
+
+	/* Read retrieved data */
+	return mv88e61xx_reg_read(dev, priv->global2,
+				  GLOBAL2_REG_PHY_DATA);
+}
+
+static int mv88e61xx_phy_write_indirect(struct udevice *dev, int phyad,
+					int devad, int reg, u16 data)
+{
+	struct mv88e61xx_priv *priv = dev_get_priv(dev);
+	int res;
+
+	/* Set the data to write */
+	res = mv88e61xx_reg_write(dev, priv->global2,
+				  GLOBAL2_REG_PHY_DATA, data);
+	if (res < 0)
+		return res;
+	/* Issue the write command */
+	res = mv88e61xx_reg_write(dev, priv->global2,
+				  GLOBAL2_REG_PHY_CMD,
+				  smi_cmd_write(phyad, reg));
+	if (res < 0)
+		return res;
+
+	/* Wait for command to complete */
+	return mv88e61xx_phy_wait(dev);
+}
+
+/* Wrapper function to make calls to phy_read_indirect simpler */
+static int mv88e61xx_phy_read(struct udevice *dev, int phy, int reg)
+{
+	return mv88e61xx_phy_read_indirect(dev, DEVADDR_PHY(phy),
+					   MDIO_DEVAD_NONE, reg);
+}
+
+/* Wrapper function to make calls to phy_write_indirect simpler */
+static int mv88e61xx_phy_write(struct udevice *dev, int phy, int reg, u16 val)
+{
+	return mv88e61xx_phy_write_indirect(dev, DEVADDR_PHY(phy),
+					    MDIO_DEVAD_NONE, reg, val);
+}
+
+static int mv88e61xx_port_read(struct udevice *dev, u8 port, u8 reg)
+{
+	struct mv88e61xx_priv *priv = dev_get_priv(dev);
+
+	return mv88e61xx_reg_read(dev, priv->port_reg_base + port, reg);
+}
+
+static int mv88e61xx_port_write(struct udevice *dev, u8 port, u8 reg, u16 val)
+{
+	struct mv88e61xx_priv *priv = dev_get_priv(dev);
+
+	return mv88e61xx_reg_write(dev, priv->port_reg_base + port, reg, val);
+}
+
+static int mv88e61xx_set_page(struct udevice *dev, u8 phy, u8 page)
+{
+	return mv88e61xx_phy_write(dev, phy, PHY_REG_PAGE, page);
+}
+
+static int mv88e61xx_get_switch_id(struct udevice *dev)
+{
+	int res;
+
+	res = mv88e61xx_port_read(dev, 0, PORT_REG_SWITCH_ID);
+	if (res < 0)
+		return res;
+	return res & 0xfff0;
+}
+
+static bool mv88e61xx_6352_family(struct udevice *dev)
+{
+	struct mv88e61xx_priv *priv = dev_get_priv(dev);
+
+	switch (priv->id) {
+	case PORT_SWITCH_ID_6172:
+	case PORT_SWITCH_ID_6176:
+	case PORT_SWITCH_ID_6240:
+	case PORT_SWITCH_ID_6352:
+		return true;
+	}
+	return false;
+}
+
+static int mv88e61xx_get_cmode(struct udevice *dev, u8 port)
+{
+	int res;
+
+	res = mv88e61xx_port_read(dev, port, PORT_REG_STATUS);
+	if (res < 0)
+		return res;
+	return res & PORT_REG_STATUS_CMODE_MASK;
+}
+
+static int mv88e61xx_switch_reset(struct udevice *dev)
+{
+	struct mv88e61xx_priv *priv = dev_get_priv(dev);
+	int time;
+	int val;
+	u8 port;
+
+	/* Disable all ports */
+	for (port = 0; port < priv->port_count; port++) {
+		val = mv88e61xx_port_read(dev, port, PORT_REG_CTRL);
+		if (val < 0)
+			return val;
+		val = bitfield_replace(val, PORT_REG_CTRL_PSTATE_SHIFT,
+				       PORT_REG_CTRL_PSTATE_WIDTH,
+				       PORT_REG_CTRL_PSTATE_DISABLED);
+		val = mv88e61xx_port_write(dev, port, PORT_REG_CTRL, val);
+		if (val < 0)
+			return val;
+	}
+
+	/* Wait 2 ms for queues to drain */
+	udelay(2000);
+
+	/* Reset switch */
+	val = mv88e61xx_reg_read(dev, priv->global1, GLOBAL1_CTRL);
+	if (val < 0)
+		return val;
+	val |= GLOBAL1_CTRL_SWRESET;
+	val = mv88e61xx_reg_write(dev, priv->global1, GLOBAL1_CTRL, val);
+	if (val < 0)
+		return val;
+
+	/* Wait up to 1 second for switch reset complete */
+	for (time = 1000; time; time--) {
+		val = mv88e61xx_reg_read(dev, priv->global1, GLOBAL1_CTRL);
+		if (val >= 0 && ((val & GLOBAL1_CTRL_SWRESET) == 0))
+			break;
+		udelay(1000);
+	}
+	if (!time)
+		return -ETIMEDOUT;
+
+	return 0;
+}
+
+static int mv88e61xx_serdes_init(struct udevice *dev)
+{
+	int val;
+
+	val = mv88e61xx_set_page(dev, DEVADDR_SERDES, PHY_PAGE_SERDES);
+	if (val < 0)
+		return val;
+
+	/* Power up serdes module */
+	val = mv88e61xx_phy_read(dev, DEVADDR_SERDES, MII_BMCR);
+	if (val < 0)
+		return val;
+	val &= ~(BMCR_PDOWN);
+	val = mv88e61xx_phy_write(dev, DEVADDR_SERDES, MII_BMCR, val);
+	if (val < 0)
+		return val;
+
+	return 0;
+}
+
+static int mv88e61xx_fixed_port_setup(struct udevice *dev, u8 port)
+{
+	struct dsa_pdata *dsa_pdata = dev_get_uclass_plat(dev);
+	struct mv88e61xx_priv *priv = dev_get_priv(dev);
+	int val;
+
+	val = mv88e61xx_port_read(dev, port, PORT_REG_PHYS_CTRL);
+	if (val < 0)
+		return val;
+
+	val &= ~(PORT_REG_PHYS_CTRL_SPD_MASK |
+		 PORT_REG_PHYS_CTRL_FC_VALUE |
+		 PORT_REG_PHYS_CTRL_FC_FORCE);
+	val |= PORT_REG_PHYS_CTRL_FC_FORCE |
+	       PORT_REG_PHYS_CTRL_DUPLEX_VALUE |
+	       PORT_REG_PHYS_CTRL_DUPLEX_FORCE;
+
+	if (priv->id == PORT_SWITCH_ID_6071) {
+		val |= PORT_REG_PHYS_CTRL_SPD100;
+	} else {
+		val |= PORT_REG_PHYS_CTRL_PCS_AN_EN |
+		       PORT_REG_PHYS_CTRL_PCS_AN_RST |
+		       PORT_REG_PHYS_CTRL_SPD1000;
+	}
+
+	if (port == dsa_pdata->cpu_port)
+		val |= PORT_REG_PHYS_CTRL_LINK_VALUE |
+		       PORT_REG_PHYS_CTRL_LINK_FORCE;
+
+	return mv88e61xx_port_write(dev, port, PORT_REG_PHYS_CTRL, val);
+}
+
+/*
+ * This function is used to pre-configure the required register
+ * offsets, so that the indirect register access to the PHY registers
+ * is possible. This is necessary to be able to read the PHY ID
+ * while driver probing or in get_phy_id(). The globalN register
+ * offsets must be initialized correctly for a detected switch,
+ * otherwise detection of the PHY ID won't work!
+ */
+static int mv88e61xx_priv_reg_offs_pre_init(struct udevice *dev)
+{
+	struct mv88e61xx_priv *priv = dev_get_priv(dev);
+
+	/*
+	 * Initial 'port_reg_base' value must be an offset of existing
+	 * port register, then reading the ID should succeed. First, try
+	 * to read via port registers with device address 0x10 (88E6096
+	 * and compatible switches).
+	 */
+	priv->port_reg_base = 0x10;
+	priv->id = mv88e61xx_get_switch_id(dev);
+	if (priv->id != 0xfff0) {
+		priv->global1 = 0x1B;
+		priv->global2 = 0x1C;
+		return 0;
+	}
+
+	/*
+	 * Now try via port registers with device address 0x08
+	 * (88E6020 and compatible switches).
+	 */
+	priv->port_reg_base = 0x08;
+	priv->id = mv88e61xx_get_switch_id(dev);
+	if (priv->id != 0xfff0) {
+		priv->global1 = 0x0F;
+		priv->global2 = 0x07;
+		return 0;
+	}
+
+	dev_warn(dev, "%s Unknown ID 0x%x\n", __func__, priv->id);
+
+	return -ENODEV;
+}
+
+static int mv88e61xx_mdio_read(struct udevice *dev, int addr, int devad, int reg)
+{
+	return mv88e61xx_phy_read_indirect(dev->parent, DEVADDR_PHY(addr),
+					   MDIO_DEVAD_NONE, reg);
+}
+
+static int mv88e61xx_mdio_write(struct udevice *dev, int addr, int devad,
+				int reg, u16 val)
+{
+	return mv88e61xx_phy_write_indirect(dev->parent, DEVADDR_PHY(addr),
+					   MDIO_DEVAD_NONE, reg, val);
+}
+
+static const struct mdio_ops mv88e61xx_mdio_ops = {
+	.read = mv88e61xx_mdio_read,
+	.write = mv88e61xx_mdio_write,
+};
+
+static int mv88e61xx_mdio_bind(struct udevice *dev)
+{
+	char name[32];
+	static int num_devices;
+
+	sprintf(name, "mv88e61xx-mdio-%d", num_devices++);
+	device_set_name(dev, name);
+
+	return 0;
+}
+
+U_BOOT_DRIVER(mv88e61xx_mdio) = {
+	.name		= "mv88e61xx_mdio",
+	.id		= UCLASS_MDIO,
+	.ops		= &mv88e61xx_mdio_ops,
+	.bind		= mv88e61xx_mdio_bind,
+	.priv_auto	= sizeof(struct mv88e61xx_priv),
+	.plat_auto	= sizeof(struct mdio_perdev_priv),
+};
+
+static int mv88e61xx_dsa_port_enable(struct udevice *dev, int port, struct phy_device *phy)
+{
+	struct dsa_pdata *dsa_pdata = dev_get_uclass_plat(dev);
+	struct mv88e61xx_priv *priv = dev_get_priv(dev);
+	int val, ret;
+
+	dev_dbg(dev, "%s P%d phy:0x%08x %s\n", __func__, port,
+		phy->phy_id, phy_string_for_interface(phy->interface));
+
+	/*
+	 * Enable energy-detect sensing on PHY, used to determine when a PHY
+	 * port is physically connected
+	 */
+	val = mv88e61xx_phy_read(dev, port, PHY_REG_CTRL1);
+	if (val < 0)
+		return val;
+	val = bitfield_replace(val, priv->phy_ctrl1_en_det_shift,
+			       priv->phy_ctrl1_en_det_width,
+			       priv->phy_ctrl1_en_det_ctrl);
+	val = mv88e61xx_phy_write(dev, port, PHY_REG_CTRL1, val);
+	if (val < 0)
+		return val;
+
+	/* enable port */
+	val = mv88e61xx_port_read(dev, port, PORT_REG_CTRL);
+	if (val < 0)
+		return val;
+	val = bitfield_replace(val, PORT_REG_CTRL_PSTATE_SHIFT,
+			       PORT_REG_CTRL_PSTATE_WIDTH,
+			       PORT_REG_CTRL_PSTATE_FORWARD);
+	val = mv88e61xx_port_write(dev, port, PORT_REG_CTRL, val);
+	if (val < 0)
+		return val;
+
+	/* configure RGMII delays for fixed link */
+	if (phy->phy_id == PHY_FIXED_ID) {
+		/* Physical Control register: Table 62 */
+		val = mv88e61xx_port_read(dev, port, PORT_REG_PHYS_CTRL);
+		val &= ~(PORT_REG_PHYS_CTRL_RGMII_DELAY_RXCLK ||
+			 PORT_REG_PHYS_CTRL_RGMII_DELAY_TXCLK);
+		if (phy->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+		    phy->interface == PHY_INTERFACE_MODE_RGMII_RXID)
+			val |= PORT_REG_PHYS_CTRL_RGMII_DELAY_RXCLK;
+		if (phy->interface == PHY_INTERFACE_MODE_RGMII_ID ||
+		    phy->interface == PHY_INTERFACE_MODE_RGMII_TXID)
+			val |= PORT_REG_PHYS_CTRL_RGMII_DELAY_TXCLK;
+		val |= BIT(5) | BIT(4); /* Force link */
+		ret = mv88e61xx_port_write(dev, port, PORT_REG_PHYS_CTRL, val);
+		if (ret < 0)
+			return ret;
+
+		ret = mv88e61xx_fixed_port_setup(dev, port);
+		if (ret < 0)
+			return ret;
+	}
+
+	if (port == dsa_pdata->cpu_port) {
+		/* Set CPUDest for cpu port */
+		val = mv88e61xx_reg_read(dev, priv->global1, GLOBAL1_MON_CTRL);
+		if (val < 0)
+			return val;
+		val = bitfield_replace(val, GLOBAL1_MON_CTRL_CPUDEST_SHIFT,
+				       GLOBAL1_MON_CTRL_CPUDEST_WIDTH, port);
+		val = mv88e61xx_reg_write(dev, priv->global1, GLOBAL1_MON_CTRL, val);
+		if (val < 0)
+			return val;
+
+		if (mv88e61xx_6352_family(dev)) {
+			/* initialize serdes */
+			val = mv88e61xx_get_cmode(dev, dsa_pdata->cpu_port);
+			if (val < 0)
+				return val;
+			if (val == PORT_REG_STATUS_CMODE_100BASE_X ||
+			    val == PORT_REG_STATUS_CMODE_1000BASE_X ||
+			    val == PORT_REG_STATUS_CMODE_SGMII) {
+				val = mv88e61xx_serdes_init(dev);
+				if (val < 0)
+					return val;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static void mv88e61xx_dsa_port_disable(struct udevice *dev, int port, struct phy_device *phy)
+{
+	int val;
+
+	dev_dbg(dev, "%s P%d phy:0x%x\n", __func__, port, phy->phy_id);
+
+	val = mv88e61xx_port_read(dev, port, PORT_REG_CTRL);
+	if (val < 0)
+		return;
+	val = bitfield_replace(val, PORT_REG_CTRL_PSTATE_SHIFT,
+			       PORT_REG_CTRL_PSTATE_WIDTH,
+			       PORT_REG_CTRL_PSTATE_DISABLED);
+	val = mv88e61xx_port_write(dev, port, PORT_REG_CTRL, val);
+	if (val < 0)
+		return;
+}
+
+static const struct dsa_ops mv88e61xx_dsa_ops = {
+	.port_enable = mv88e61xx_dsa_port_enable,
+	.port_disable = mv88e61xx_dsa_port_disable,
+};
+
+/* bind and probe the switch mdios */
+static int mv88e61xx_dsa_probe_mdio(struct udevice *dev)
+{
+	struct udevice *pdev;
+	ofnode node, mdios;
+	const char *name;
+	int ret;
+
+	/* bind phy ports of mdios child node to mv88e61xx_mdio device */
+	mdios = dev_read_subnode(dev, "mdios");
+	if (ofnode_valid(mdios)) {
+		ofnode_for_each_subnode(node, mdios) {
+			name = ofnode_get_name(node);
+			ret = device_bind_driver_to_node(dev,
+							 "mv88e61xx_mdio",
+							 name, node, &pdev);
+			if (ret) {
+				dev_err(dev, "failed to bind %s: %d\n", name, ret);
+				continue;
+			}
+
+			/* need to probe it as there is no compatible to do so */
+			ret = uclass_get_device_by_ofnode(UCLASS_MDIO, node, &pdev);
+			if (ret) {
+				dev_err(dev, "failed to probe %s: %d\n", name, ret);
+				continue;
+			}
+		}
+	}
+
+	/* bind the mdios node to the parent mdio driver */
+	name = ofnode_get_name(mdios);
+	ret = device_bind_driver_to_node(dev,
+					 "mv88e61xx_mdio",
+					 name, mdios, &pdev);
+	if (ret)
+		dev_err(dev, "failed to bind %s: %d\n", name, ret);
+
+	/* need to probe it as there is no compatible to do so */
+	ret = uclass_get_device_by_ofnode(UCLASS_MDIO, mdios, &pdev);
+	if (ret)
+		dev_err(dev, "failed to probe %s: %d\n", name, ret);
+
+	return ret;
+}
+
+static int mv88e61xx_dsa_probe(struct udevice *dev)
+{
+	struct mv88e61xx_priv *priv = dev_get_priv(dev);
+	struct udevice *master = dsa_get_master(dev);
+	int ret;
+
+	if (!master)
+		return -ENODEV;
+
+	dev_dbg(dev, "%s master:%s\n", __func__, master->name);
+
+	/* probe internal mdio bus */
+	ret = mv88e61xx_dsa_probe_mdio(dev);
+	if (ret)
+		return ret;
+
+	ret = mv88e61xx_priv_reg_offs_pre_init(dev);
+	if (ret)
+		return ret;
+
+	dev_dbg(dev, "ID=0x%x PORT_BASE=0x%02x GLOBAL1=0x%02x GLOBAL2=0x%02x\n",
+		priv->id, priv->port_reg_base, priv->global1, priv->global2);
+	switch (priv->id) {
+	case PORT_SWITCH_ID_6096:
+	case PORT_SWITCH_ID_6097:
+	case PORT_SWITCH_ID_6172:
+	case PORT_SWITCH_ID_6176:
+	case PORT_SWITCH_ID_6240:
+	case PORT_SWITCH_ID_6352:
+		priv->port_count = 11;
+		priv->phy_ctrl1_en_det_shift = 8;
+		priv->phy_ctrl1_en_det_width = 2;
+		priv->phy_ctrl1_en_det_ctrl =
+			PHY_REG_CTRL1_ENERGY_DET_SENSE_XMIT;
+		break;
+	case PORT_SWITCH_ID_6020:
+	case PORT_SWITCH_ID_6070:
+	case PORT_SWITCH_ID_6071:
+	case PORT_SWITCH_ID_6220:
+	case PORT_SWITCH_ID_6250:
+		priv->port_count = 7;
+		priv->phy_ctrl1_en_det_shift = 14;
+		priv->phy_ctrl1_en_det_width = 1;
+		priv->phy_ctrl1_en_det_ctrl =
+			PHY_REG_CTRL1_ENERGY_DET_SENSE_PULSE;
+		break;
+	default:
+		return -ENODEV;
+	}
+
+	ret = mv88e61xx_switch_reset(dev);
+	if (ret < 0)
+		return ret;
+
+	dsa_set_tagging(dev, 0, 0);
+
+	return 0;
+}
+
+static const struct udevice_id mv88e61xx_ids[] = {
+	{ .compatible = "marvell,mv88e6085" },
+	{ }
+};
+
+U_BOOT_DRIVER(mv88e61xx) = {
+	.name		= "mv88e61xx",
+	.id		= UCLASS_DSA,
+	.of_match	= mv88e61xx_ids,
+	.probe		= mv88e61xx_dsa_probe,
+	.ops		= &mv88e61xx_dsa_ops,
+	.priv_auto	= sizeof(struct mv88e61xx_priv),
+};
-- 
2.17.1



More information about the U-Boot mailing list