[U-Boot] [PATCH 09/51] phy: Support Marvell 88X2242

Mario Six mario.six at gdsys.cc
Fri Jul 14 12:54:55 UTC 2017


From: Dirk Eibach <dirk.eibach at gdsys.cc>

Implement support for the Marvell Alaska X 88X2242P Integrated Dual-port
and Quad-port Multi-speed Ethernet Transceivers.

Signed-off-by: Dirk Eibach <dirk.eibach at gdsys.cc>
Signed-off-by: Mario Six <mario.six at gdsys.cc>
---

 drivers/net/phy/Kconfig   |  67 ++++
 drivers/net/phy/Makefile  |   1 +
 drivers/net/phy/marvell.c |   1 -
 drivers/net/phy/mv88x2.c  | 846 ++++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/phy/mv88x2.h  |  12 +
 drivers/net/phy/phy.c     |   3 +
 include/phy.h             |   1 +
 7 files changed, 930 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/phy/mv88x2.c
 create mode 100644 drivers/net/phy/mv88x2.h

diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 0230852244..bb21be197f 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -55,6 +55,73 @@ config PHY_LXT
 config PHY_MARVELL
 	bool "Marvell Ethernet PHYs support"
 
+
+if PHY_MARVELL
+
+config BOARD_M88E1510_CONFIG
+	bool "M88E1510 board-specific callback"
+
+endif
+
+config PHY_MV88X2
+	depends on PHYLIB_10G
+	bool "Marvell 88X2 PHYs support"
+
+if PHY_MV88X2
+
+config BOARD_MV88X2_CONFIG
+	bool "MV88X2 board-specific callback"
+
+config MV88X2_DEBUG_REGS
+	bool "Debug register printing"
+
+config MV88X2_LINE_10GBASE_R
+	bool "Enable Line 10GBASE-R support"
+
+config MV88X2_HOST_10GBASE_X2
+	bool "Enable Host 10GBASE-X2 support"
+
+config MV88X2_X2_DISPARITY
+	bool "Enable 10GBASE-X2 disparity"
+
+config MV88X2_LED0_BLINK
+	int "LED0 blink behavior"
+	range 0 11
+	default 0
+
+config MV88X2_LED0_SOLID
+	int "LED0 solid behavior"
+	range 0 7
+	default 0
+
+config MV88X2_LED1_BLINK
+	int "LED1 blink behavior"
+	range 0 11
+	default 0
+
+config MV88X2_LED1_SOLID
+	int "LED1 solid behavior"
+	range 0 7
+	default 0
+
+config MV88X2_HOST_LANE_MUX_2_PORT
+	hex "Host side lane muxing (2 ports)"
+	default 0x0
+
+config MV88X2_HOST_LANE_MUX_4_PORT
+	hex "Host side lane muxing (4 ports)"
+	default 0x0
+
+config MV88X2_SFI_POL
+	hex "SFI polarity"
+	default 0x0
+
+config MV88X2_XFI_POL
+	hex "XFI polarity"
+	default 0x0
+
+endif
+
 config PHY_MICREL
 	bool "Micrel Ethernet PHYs support"
 	help
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 88c00a5cd3..631797a1f0 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_PHY_ET1011C) += et1011c.o
 obj-$(CONFIG_PHY_LXT) += lxt.o
 obj-$(CONFIG_PHY_MARVELL) += marvell.o
 obj-$(CONFIG_PHY_MICREL) += micrel.o
+obj-$(CONFIG_PHY_MV88X2) += mv88x2.o
 obj-$(CONFIG_PHY_NATSEMI) += natsemi.o
 obj-$(CONFIG_PHY_REALTEK) += realtek.o
 obj-$(CONFIG_PHY_SMSC) += smsc.o
diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c
index 8eddf70c68..66107a8af3 100644
--- a/drivers/net/phy/marvell.c
+++ b/drivers/net/phy/marvell.c
@@ -720,6 +720,5 @@ int phy_marvell_init(void)
 	phy_register(&M88E1510_driver);
 	phy_register(&M88E1518_driver);
 	phy_register(&M88E1680_driver);
-
 	return 0;
 }
diff --git a/drivers/net/phy/mv88x2.c b/drivers/net/phy/mv88x2.c
new file mode 100644
index 0000000000..9d04197e47
--- /dev/null
+++ b/drivers/net/phy/mv88x2.c
@@ -0,0 +1,846 @@
+/*
+ * Driver for Marvell 88X2 PHYs
+ * Support for Revision A devices is not included.
+ *
+ * (C) Copyright 2016
+ * Dirk Eibach,  Guntermann & Drunck GmbH, eibach at gdsys.de
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <config.h>
+#include <common.h>
+#include <miiphy.h>
+#include <phy.h>
+#include <console.h>
+
+#include "mv88x2.h"
+
+enum {
+	MGD_2140M_A1	= 0x1b,		/* 88X2140M_A1 */
+
+	MGD_22x2M	= 0x31,		/* 88X22x2M */
+	MGD_2242_B0	= 0x311,	/* 88X2242_B0 */
+	MGD_2242M_A0	= 0x312,	/* 88X2242M_A0 (MGD_22x2M + rev=2) */
+	MGD_2242M_B0	= 0x313,	/* 88X2242M_B0 (MGD_22x2M + rev=3) */
+	MGD_2222_B0	= 0x315,	/* 88X2222_B0 (MGD_22x2M + rev=5) */
+	MGD_2222M_B0	= 0x317,	/* 88X2222M_B0 (MGD_22x2M + rev=7) */
+
+	MGD_22x2P	= 0x19,		/* 88X22x2P */
+	MGD_2242P_A0	= 0x191,	/* 88X2242P_A0 (MGD_22x2P + rev=1) */
+	MGD_2222P_A0	= 0x199,	/* 88X2222P_A0 (MGD_22x2P + rev=9) */
+};
+
+enum {
+	MGD_PCS_SPEED_40GBASE_R4	= 0x70,
+	MGD_PCS_SPEED_10GBASE_R		= 0x71,
+	MGD_PCS_SPEED_10GBASE_X2	= 0x72,
+	MGD_PCS_SPEED_10GBASE_X4	= 0x73,
+	MGD_PCS_SPEED_10GBASE_W		= 0x74, /* line side only */
+	MGD_PCS_SPEED_20GBASE_R2	= 0x75,
+	MGD_PCS_SPEED_20GBASE_X4	= 0x76,
+	MGD_PCS_SPEED_2500BASE_X	= 0X76,
+	MGD_PCS_SPEED_20GBASE_R8	= 0x77, /* host side only */
+	MGD_PCS_SPEED_40GBASE_R8	= 0x77, /* host side only */
+	MGD_PCS_SPEED_1000BASE_AN_OFF	= 0x7A,
+	MGD_PCS_SPEED_1000BASE_AN_ON	= 0x7B,
+	MGD_PCS_SPEED_SGMII_MAC_AN_OFF	= 0x7C,
+	MGD_PCS_SPEED_SGMII_MAC_AN_ON	= 0x7D,
+	MGD_PCS_SPEED_SGMII_LINE_AN_OFF	= 0x7E,
+	MGD_PCS_SPEED_SGMII_LINE_AN_ON	= 0x7F,
+	MGD_PCS_SPEED_UNKNOWN		= 0x78, /* temp set reserved */
+};
+
+struct debug_register {
+	int port;
+	int devad;
+	int regnum;
+} debug_registers[] = {
+	/* PCS Registers */
+	{ 0, 31, 0xf002 },
+	/* line side */
+	{ 0, 3, 0xf074 }, /* pwr mgmt TX state control*/
+	{ 0, 3, 0x0001 }, /* PCS status*/
+	{ 0, 3, 0xf002 }, /* PCS port configuration*/
+	{ 0, 3, 0xf010 }, /* Packet generator/CRC checker*/
+	{ 0, 3, 0x3021 }, /* PCS status2 (block lock/High BER)*/
+	/* host side */
+	{ 0, 4, 0xf074 }, /* Power management control*/
+	{ 0, 4, 0x1018 }, /* XAUI/RXAUI alignment and sync*/
+	{ 0, 4, 0x3032 }, /* XAUI/RXAUI block lock per lane*/
+	{ 0, 4, 0x3034 }, /* XAUI/RXAUI alignment*/
+	{ 0, 4, 0x0001 }, /* PCS status*/
+	{ 0, 4, 0xf002 }, /* PCS port configuration*/
+	{ 0, 4, 0xf010 }, /* Packet generator/CRC checker*/
+	{ 0, 4, 0x3021 }, /* PCS status2 (block lock/High BER)*/
+	/* DSP Registers */
+	/* line side */
+	{ 0, 30, 0xb841 },
+	/* port 0 */
+	{ 0, 30, 0xb111 },
+	{ 0, 30, 0xb116 }, /* sfi tx settings*/
+	{ 0, 30, 0xb117 }, /* sfi tx settings*/
+	{ 0, 30, 0xb042 }, /* dsp status*/
+	{ 0, 30, 0xb132 }, /* pga*/
+	{ 0, 30, 0xb000 },
+	{ 0, 30, 0xb001 },
+	{ 0, 30, 0xb1be }, /* snr*/
+	{ 0, 30, 0xb1f1 },
+	{ 0, 30, 0xb1d3 },
+	{ 0, 30, 0xb065 },
+	{ 0, 30, 0xb1bb },
+	{ 0, 30, 0xb1bc },
+	{ 0, 30, 0xb1bf },
+	{ 0, 30, 0xb1b3 },
+	/* port 1 */
+	{ 0, 30, 0xb311 },
+	{ 0, 30, 0xb316 },
+	{ 0, 30, 0xb317 },
+	{ 0, 30, 0xb242 },
+	{ 0, 30, 0xb332 },
+	{ 0, 30, 0xb200 },
+	{ 0, 30, 0xb201 },
+	{ 0, 30, 0xb3be },
+	{ 0, 30, 0xb3f1 },
+	{ 0, 30, 0xb3d3 },
+	{ 0, 30, 0xb265 },
+	{ 0, 30, 0xb3bb },
+	{ 0, 30, 0xb3bc },
+	{ 0, 30, 0xb3bf },
+	{ 0, 30, 0xb3b3 },
+	/* port 2 */
+	{ 0, 30, 0xb511 },
+	{ 0, 30, 0xb516 },
+	{ 0, 30, 0xb517 },
+	{ 0, 30, 0xb442 },
+	{ 0, 30, 0xb532 },
+	{ 0, 30, 0xb400 },
+	{ 0, 30, 0xb401 },
+	{ 0, 30, 0xb5be },
+	{ 0, 30, 0xb5f1 },
+	{ 0, 30, 0xb5d3 },
+	{ 0, 30, 0xb465 },
+	{ 0, 30, 0xb5bb },
+	{ 0, 30, 0xb5bc },
+	{ 0, 30, 0xb5bf },
+	{ 0, 30, 0xb5b3 },
+	/* port 3 */
+	{ 0, 30, 0xb711 },
+	{ 0, 30, 0xb716 },
+	{ 0, 30, 0xb717 },
+	{ 0, 30, 0xb642 },
+	{ 0, 30, 0xb732 },
+	{ 0, 30, 0xb600 },
+	{ 0, 30, 0xb601 },
+	{ 0, 30, 0xb7be },
+	{ 0, 30, 0xb7f1 },
+	{ 0, 30, 0xb7d3 },
+	{ 0, 30, 0xb665 },
+	{ 0, 30, 0xb7bb },
+	{ 0, 30, 0xb7bc },
+	{ 0, 30, 0xb7bf },
+	{ 0, 30, 0xb7b3 },
+	{ 0, 30, 0xb753 },
+	{ 0, 30, 0xb780 },
+	{ 0, 30, 0xb7d4 },
+	{ 0, 30, 0xb7d6 },
+	{ 0, 30, 0xb7ea },
+	/* host side */
+	{ 0, 30, 0x9041 },
+	/* port 0 */
+	{ 0, 30, 0x8111 },
+	{ 0, 30, 0x8116 },
+	{ 0, 30, 0x8117 },
+	{ 0, 30, 0x8042 },
+	{ 0, 30, 0x8132 },
+	{ 0, 30, 0x81d1 },
+	/* port 1 */
+	{ 0, 30, 0x8311 },
+	{ 0, 30, 0x8316 },
+	{ 0, 30, 0x8317 },
+	{ 0, 30, 0x8242 },
+	{ 0, 30, 0x8332 },
+	{ 0, 30, 0x83d1 },
+	/* port 2 */
+	{ 0, 30, 0x8511 },
+	{ 0, 30, 0x8516 },
+	{ 0, 30, 0x8517 },
+	{ 0, 30, 0x8442 },
+	{ 0, 30, 0x8532 },
+	{ 0, 30, 0x85d1 },
+	/* port 3 */
+	{ 0, 30, 0x8711 },
+	{ 0, 30, 0x8716 },
+	{ 0, 30, 0x8717 },
+	{ 0, 30, 0x8642 },
+	{ 0, 30, 0x8732 },
+	{ 0, 30, 0x87d1 },
+};
+
+static const char *get_name(u16 device_id)
+{
+	switch (device_id) {
+	case MGD_2140M_A1:
+		return "MV88X2140M_A1";
+	case MGD_2242_B0:
+		return "MV88X2242_B0";
+	case MGD_2242M_A0:
+		return "MV88X2242M_A0";
+	case MGD_2242M_B0:
+		return "MV88X2242M_B0";
+	case MGD_2222_B0:
+		return "MV88X2222_B0";
+	case MGD_2222M_B0:
+		return "MV88X2222M_B0";
+	case MGD_2242P_A0:
+		return "MV88X2242P_A0";
+	case MGD_2222P_A0:
+		return "MV88X2222P_A0";
+	}
+
+	return "";
+}
+
+static int get_num_ports(u16 device_id)
+{
+	int res = -1;
+
+	switch (device_id) {
+	case MGD_2140M_A1:
+	case MGD_2242M_A0:
+	case MGD_2242M_B0:
+	case MGD_2242_B0:
+	case MGD_2242P_A0:
+		res = 4;
+		break;
+
+	case MGD_2222P_A0:
+	case MGD_2222_B0:
+	case MGD_2222M_B0:
+		res = 2;
+		break;
+	};
+
+	return res;
+}
+
+/*
+ * 88X2 takes 4 consecutive addresses on the mdio bus
+ * this provides access to these port instances
+ */
+static int m88x2_read(struct phy_device *phydev, int port, int devad,
+		      int regnum)
+{
+	struct mii_dev *bus = phydev->bus;
+
+	return bus->read(bus, phydev->addr + port, devad, regnum);
+}
+
+static int m88x2_write(struct phy_device *phydev, int port, int devad,
+		       int regnum, u16 val)
+{
+	struct mii_dev *bus = phydev->bus;
+
+	return bus->write(bus, phydev->addr + port, devad, regnum, val);
+}
+
+static int m88x2_write_mask(struct phy_device *phydev, int port, int devad,
+			    int regnum, u16 mask, u16 val)
+{
+	u16 reg;
+	struct mii_dev *bus = phydev->bus;
+
+	reg = bus->read(bus, phydev->addr + port, devad, regnum);
+	reg &= ~mask;
+	reg |= val;
+
+	return bus->write(bus, phydev->addr + port, devad, regnum, reg);
+}
+
+static int m88x2_write_all(struct phy_device *phydev, int devad, int regnum,
+			   u16 val)
+{
+	unsigned int k;
+
+	for (k = 0; k < 4; ++k) {
+		int res = m88x2_write(phydev, k, devad, regnum, val);
+
+		if (res < 0)
+			return res;
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_MV88X2_DEBUG_REGS
+static void m88x2_dump_debug(struct phy_device *phydev)
+{
+	unsigned int k;
+
+	for (k = 0; k < ARRAY_SIZE(debug_registers); ++k) {
+		int val;
+		struct debug_register *reg = &debug_registers[k];
+
+		val = m88x2_read(phydev, reg->port, reg->devad, reg->regnum);
+		printf("%2x.%04x %04x\n", reg->devad, reg->regnum, val);
+	}
+}
+#endif
+
+/* 88X2 has a weird way of mixing revision and device id encoding */
+static int get_device_id(struct phy_device *phydev)
+{
+	u16 id2 = phy_read(phydev, 1, 3);
+	u16 device_id_raw = id2 & 0x3FF;
+	u16 device_id_base = device_id_raw >> 4;
+	u16 revision = device_id_raw & 0xF;
+	int res = -1;
+
+	switch (device_id_base) {
+	case MGD_22x2M:
+		switch (revision) {
+		case 1:
+			res = MGD_2242_B0;
+			break;
+		case 2:
+			res = MGD_2242M_A0;
+			break;
+		case 3:
+			res = MGD_2242M_B0;
+			break;
+		case 5:
+			res = MGD_2222_B0;
+			break;
+		case 7:
+			res = MGD_2222M_B0;
+			break;
+		}
+		break;
+	case MGD_22x2P:
+		switch (revision) {
+		case 1:
+			res = MGD_2242P_A0;
+			break;
+		case 9:
+			res = MGD_2222P_A0;
+			break;
+		}
+		break;
+	}
+
+	return res;
+}
+
+static int configure_host_lanes(struct phy_device *phydev, int num_ports)
+{
+	printf("       setup host lane mux for %d ports\n", num_ports);
+
+#ifdef CONFIG_MV88X2_HOST_LANE_MUX_4_PORT
+	if (num_ports == 4) {
+		m88x2_write(phydev, 0, 31, 0xf402,
+			    CONFIG_MV88X2_HOST_LANE_MUX_4_PORT);
+	}
+#endif
+
+#ifdef CONFIG_MV88X2_HOST_LANE_MUX_2_PORT
+	if (num_ports == 2)
+		m88x2_write(phydev, 0, 31, 0xf402,
+			    CONFIG_MV88X2_HOST_LANE_MUX_2_PORT);
+#endif
+
+	m88x2_write_mask(phydev, 0, 31, 0xf404, 0x8000, 0x8000);
+
+	m88x2_write_all(phydev, 31, 0xf003, 0x0080);
+
+	return 0;
+}
+
+static int configure_options(struct phy_device *phydev)
+{
+	struct mv88x2_config_data data;
+
+	printf("       setup board specific options\n");
+
+	memset(&data, 0, sizeof(struct mv88x2_config_data));
+
+	data.led0_blink = CONFIG_MV88X2_LED0_BLINK;
+	data.led0_solid = CONFIG_MV88X2_LED0_SOLID;
+	data.led1_blink = CONFIG_MV88X2_LED1_BLINK;
+	data.led1_solid = CONFIG_MV88X2_LED1_SOLID;
+	data.sfi_pol = CONFIG_MV88X2_SFI_POL;
+	data.xfi_pol = CONFIG_MV88X2_XFI_POL;
+
+#ifdef CONFIG_BOARD_MV88X2_CONFIG
+	board_mv88x2_config(&data);
+#endif
+
+	if (data.sfi_pol)
+		m88x2_write(phydev, 0, 31, 0xf407, data.sfi_pol);
+
+	m88x2_write(phydev, 0, 31, 0xf406, data.xfi_pol);
+
+#ifdef CONFIG_MV88X2_X2_DISPARITY
+	m88x2_write_all(phydev, 4, 0x9000, 0x0002);
+#endif
+
+	m88x2_write_all(phydev, 31, 0xf020, data.led0_blink << 8 |
+					    data.led0_solid << 4);
+
+	m88x2_write_all(phydev, 31, 0xf021, data.led1_blink << 8 |
+					    data.led1_solid << 4);
+	return 0;
+}
+
+static int port_power_up(struct phy_device *phydev, unsigned int device_id,
+			 unsigned int port)
+{
+	printf("       power up port %u\n", port);
+
+	/* power up all ports in config reg */
+	m88x2_write(phydev, 0, 31, 0xf403, 0x0000);
+
+	if (device_id == MGD_2242M_B0 ||
+	    device_id == MGD_2222_B0 ||
+	    device_id == MGD_2222M_B0 ||
+	    device_id == MGD_2242_B0) {
+		m88x2_write(phydev, 0, 31, 0xf400, 0xba98);
+		m88x2_write(phydev, 0, 31, 0xf401, 0xba98);
+	}
+
+	/* power up primary ports */
+	m88x2_write(phydev, port, 3, 0, 0x2040);
+	m88x2_write(phydev, port, 4, 0, 0x2040);
+
+	/* port reset */
+	m88x2_write(phydev, port, 31, 0xf003, 0x8080);
+
+	/* power up line side */
+	m88x2_write_mask(phydev, port, 31, 0xf003, BIT(14), 0);
+	/* power up host side */
+	m88x2_write_mask(phydev, port, 31, 0xf003, BIT(6), 0);
+
+	/* SFI */
+	m88x2_write_mask(phydev, port, 1, 0, BIT(11), 0);
+#ifdef CONFIG_MV88X2_LINE_10GBASE_R
+	m88x2_write_mask(phydev, port, 3, 0, BIT(11), 0);
+#endif
+
+	/* XFI */
+#if defined(CONFIG_MV88X2_HOST_10GBASE_R)
+	m88x2_write_mask(phydev, port, 4, 0, BIT(11), 0);
+#elif defined(CONFIG_MV88X2_HOST_10GBASE_X2) || \
+	defined(CONFIG_MV88X2_HOST_10GBASE_X4)
+	m88x2_write_mask(phydev, port, 4, 0x1000, BIT(11), 0);
+#endif
+
+	return 0;
+}
+
+#if defined(CONFIG_MV88X2_LINE_10GBASE_R) && \
+	defined(CONFIG_MV88X2_HOST_10GBASE_X2)
+/* initialization sequence from Marvell MGD driver Release 2.4 */
+static int init_port_mode_2242m_b0_10gr_10gx2(struct phy_device *phydev,
+					      unsigned int port)
+{
+	printf("       init_port_mode_2242m_b0_10gr_10gx2 port %u\n", port);
+
+	m88x2_write(phydev, 0, 30, 0xb841, 0x0000);
+	m88x2_write(phydev, 0, 30, 0x9041, 0x03aa);
+	udelay(100);
+
+	m88x2_write_mask(phydev, 0, 30, 0xb040 + port * 0x200, 0x4000, 0x4000);
+
+	m88x2_write_mask(phydev, 0, 30, 0x8040 + port * 0x400, 0x4000, 0x4000);
+	udelay(100);
+
+	/* 10GR-10GX2 */
+	m88x2_write(phydev, port, 3, 0xf002,
+		    (0x80 << 8) | MGD_PCS_SPEED_10GBASE_R);
+	m88x2_write(phydev, port, 4, 0xf002,
+		    (0x80 << 8) | MGD_PCS_SPEED_10GBASE_X2);
+
+	m88x2_write(phydev, 0, 30, 0xb108 + port * 0x200, 0xf8d0);
+
+	m88x2_write_mask(phydev, 0, 30, 0xb1e7 + port * 0x200, 0xc000, 0x0000);
+
+	m88x2_write_mask(phydev, 0, 30, 0xb1e8 + port * 0x200, 0x7f00, 0x2000);
+
+	m88x2_write_mask(phydev, 0, 30, 0xb042 + port * 0x200, 0x00f0, 0x00b0);
+
+	m88x2_write(phydev, 0, 30, 0xb1a2 + port * 0x200, 0x00b0);
+	m88x2_write(phydev, 0, 30, 0xb19c + port * 0x200, 0x00a0);
+
+	m88x2_write_mask(phydev, 0, 30, 0xb1b5 + port * 0x200, 0x1000, 0x0000);
+
+	m88x2_write_mask(phydev, 0, 30, 0xb1b4 + port * 0x200, 0x1000, 0x1000);
+
+	m88x2_write_mask(phydev, 0, 30, 0xb181 + port * 0x200, 0xff00, 0x3300);
+
+	m88x2_write(phydev, 0, 30, 0x8141 + port * 0x400, 0x8f1a);
+	m88x2_write(phydev, 0, 30, 0x8131 + port * 0x400, 0x8f1a);
+	m88x2_write(phydev, 0, 30, 0x8077 + port * 0x400, 0x820a);
+
+	m88x2_write_mask(phydev, 0, 30, 0x80a0 + port * 0x400, 0x01ff, 0x0100);
+
+	m88x2_write_mask(phydev, 0, 30, 0x8085 + port * 0x400, 0x3f00, 0x0700);
+
+	m88x2_write_mask(phydev, 0, 30, 0x807b + port * 0x400, 0x0fff, 0x02b2);
+
+	m88x2_write_mask(phydev, 0, 30, 0x80b0 + port * 0x400, 0x0770, 0x0220);
+
+	m88x2_write_mask(phydev, 0, 30, 0x80b1 + port * 0x400, 0x0c30, 0x0820);
+
+	m88x2_write_mask(phydev, 0, 30, 0x8093 + port * 0x400, 0x7f00, 0x4600);
+
+	m88x2_write_mask(phydev, 0, 30, 0x809f + port * 0x400, 0x007f, 0x0079);
+
+	m88x2_write(phydev, 0, 30, 0x805c + port * 0x400, 0x4759);
+	m88x2_write(phydev, 0, 30, 0x805d + port * 0x400, 0x5900);
+	m88x2_write(phydev, port, 31, 0xf016, 0x0010);
+	/* CQ: 00006634 03/21/2016 */
+	m88x2_write_mask(phydev, 0, 30, 0xb153 + port * 0x200, 0x0070, 0x0070);
+	m88x2_write(phydev, port, 31, 0xf003, 0x8080);
+
+	return 0;
+}
+
+/* initialization sequence from Marvell MGD driver Release 2.4 */
+static int init_mode_2242m_b0_10gr_10gx2(struct phy_device *phydev,
+					 int num_ports)
+{
+	ulong mv88x2cfg = getenv_hex("mv88x2cfg", 0xe7);
+
+	printf("       init_mode_2242m_b0_10gr_10gx2\n");
+
+	/* Chip Hardware Reset */
+	m88x2_write(phydev, 0, 31, 0xf404, 0x4000);
+	udelay(100);
+
+	configure_host_lanes(phydev, num_ports);
+	if (mv88x2cfg & BIT(7))
+		configure_options(phydev);
+
+	/* set pcs speed mode */
+	m88x2_write_all(phydev, 31, 0xf002, 0x7172);
+
+	/* Couple Lane Writing Line Side */
+	m88x2_write(phydev, 0, 30, 0xb841, 0xe000);
+	/* Couple Lane Writing Host Side */
+	m88x2_write(phydev, 0, 30, 0x9041, 0x03fe);
+
+	/* LINE: Analog BW and ETA TxPLL */
+	m88x2_write(phydev, 0, 30, 0xb108, 0xf8d0);
+
+	m88x2_write_mask(phydev, 0, 30, 0xb1e7, 0xc000, 0x0000);
+
+	/* Hack pga table index for testing boost adjustment */
+	m88x2_write_mask(phydev, 0, 30, 0xb1e8, 0x7f00, 0x2000);
+
+	/* Enable ramping detection */
+	m88x2_write_mask(phydev, 0, 30, 0xb042, 0x00f0, 0x00b0);
+
+	/* Initial gain */
+	m88x2_write(phydev, 0, 30, 0xb1a2, 0x00b0);
+	/* Initial FFE */
+	m88x2_write(phydev, 0, 30, 0xb19c, 0x00a0);
+
+	/* freeze FFE main tap adaptation */
+	m88x2_write_mask(phydev, 0, 30, 0xb1b5, 0x1000, 0x0000);
+	m88x2_write_mask(phydev, 0, 30, 0xb1b4, 0x1000, 0x1000);
+
+	/* lower FFE mu */
+	m88x2_write_mask(phydev, 0, 30, 0xb181, 0xff00, 0x3300);
+
+	/* Host: Analog BW and ETA TxPLL */
+	m88x2_write(phydev, 0, 30, 0x8141, 0x8f1a);
+	/* Host: Analog BW and ETA RxPll */
+	m88x2_write(phydev, 0, 30, 0x8131, 0x8f1a);
+	/* Change DFE threshold */
+	m88x2_write(phydev, 0, 30, 0x8077, 0x820a);
+
+	/* 3/24/2015 updates */
+	/* Force BLW gain to zero */
+	m88x2_write_mask(phydev, 0, 30, 0x80a0, 0x01ff, 0x0100);
+
+	/* Constrain C0 min */
+	m88x2_write_mask(phydev, 0, 30, 0x8085, 0x3f00, 0x0700);
+
+	/* change C0 min for best veo search */
+	m88x2_write_mask(phydev, 0, 30, 0x807b, 0x0fff, 0x02b2);
+
+	/* Force EQR/EQC value */
+	m88x2_write_mask(phydev, 0, 30, 0x80b0, 0x0770, 0x0220);
+	m88x2_write_mask(phydev, 0, 30, 0x80b1, 0x0c30, 0x0820);
+
+	/* peakdet threshold change before and after startup */
+	m88x2_write_mask(phydev, 0, 30, 0x8093, 0x7f00, 0x4600);
+	m88x2_write_mask(phydev, 0, 30, 0x809f, 0x007f, 0x0079);
+
+	/* kpkf for 6G */
+	m88x2_write(phydev, 0, 30, 0x805c, 0x4759);
+	m88x2_write(phydev, 0, 30, 0x805d, 0x5900);
+
+	/* enable sfp tx_fault, rx_los and mod_abs functions */
+	m88x2_write_all(phydev, 31, 0xf014, 0x0777);
+
+	/* enable sfp tx_disable function */
+	m88x2_write_all(phydev, 31, 0xf016, 0x0010);
+
+	/* enable sfp tx_fault, rx_los and mod_abs interrupts*/
+	m88x2_write_all(phydev, 31, 0xf010, 0x0007);
+
+	/* CQ: 00006634 03/21/2016 */
+	m88x2_write_mask(phydev, 0, 30, 0xb153, 0x0070, 0x0070);
+	m88x2_write_mask(phydev, 0, 30, 0xb353, 0x0070, 0x0070);
+	m88x2_write_mask(phydev, 0, 30, 0xb553, 0x0070, 0x0070);
+	m88x2_write_mask(phydev, 0, 30, 0xb753, 0x0070, 0x0070);
+
+	/* PCS Reset */
+	m88x2_write_all(phydev, 31, 0xf003, 0x8080);
+
+	udelay(2000);
+
+	/* release FFE main tap adaptation */
+	m88x2_write_mask(phydev, 0, 30, 0xb1b5, 0x1000, 0x1000);
+	m88x2_write_mask(phydev, 0, 30, 0xb1b4, 0x1000, 0x1000);
+
+	return 0;
+}
+#endif
+
+static int disable_tx_rx_reset(struct phy_device *phydev, unsigned int port)
+{
+	printf("       %s port %u\n", __func__, port);
+
+	m88x2_write_mask(phydev, port, 3, 0xf074, 0x2000, 0);
+
+	return 0;
+}
+
+static int m88x2_config(struct phy_device *phydev)
+{
+	unsigned int k;
+	int device_id;
+	int num_ports;
+	int sfi_pol;
+
+	/*
+	 * mv88x2cfg environment variable encoding, default: 0xe7
+	 * bit 0: execute init_mode()
+	 * bit 1: execute init_port_mode()
+	 * bit 2: execute port_power_up()
+	 * bit 3: initialize sfi_pol, xfi_pol and disparity before
+	 *        port_power_up()
+	 * bit 4: initialize sfi_pol, xfi_pol and disparity after
+	 *        port_power_up()
+	 * bit 5: initialize lane_mux
+	 * bit 6: disable tx/rx reset mechanism
+	 * bit 7: initialize sfi_pol, xfi_pol and disparity ASAP()
+	 */
+	ulong mv88x2cfg = getenv_hex("mv88x2cfg", 0xe7);
+
+	device_id = get_device_id(phydev);
+	if (device_id < 0)
+		return -1;
+
+	num_ports = get_num_ports(device_id);
+	if (num_ports < 0)
+		return -1;
+
+	printf("%s (%d ports, %02x on %s):\n", get_name(device_id), num_ports,
+	       phydev->addr, phydev->bus->name);
+
+#ifdef CONFIG_MV88X2_LINE_10GBASE_R
+#ifdef CONFIG_MV88X2_HOST_10GBASE_X2
+	if (mv88x2cfg & BIT(0))
+		init_mode_2242m_b0_10gr_10gx2(phydev, num_ports);
+#endif
+#endif
+
+	if (mv88x2cfg & BIT(3))
+		configure_options(phydev);
+
+	for (k = 0; k < 4; ++k) {
+		/* on devices with 2 ports, ports 0 and 2 are active */
+		if ((k % 2) && (num_ports == 2))
+			continue;
+
+#ifdef CONFIG_MV88X2_LINE_10GBASE_R
+#ifdef CONFIG_MV88X2_HOST_10GBASE_X2
+		if (mv88x2cfg & BIT(1))
+			init_port_mode_2242m_b0_10gr_10gx2(phydev, k);
+#endif
+#endif
+
+		if (mv88x2cfg & BIT(6))
+			disable_tx_rx_reset(phydev, k);
+
+		if (mv88x2cfg & BIT(2))
+			port_power_up(phydev, device_id, k);
+	}
+
+	if (mv88x2cfg & BIT(4))
+		configure_options(phydev);
+
+	sfi_pol = m88x2_read(phydev, 0, 31, 0xf407);
+
+	/* Marvells magic SFI input polarity inversion fix */
+	if (sfi_pol & 0x0f00) {
+		puts("       ");
+		puts("apply Marvells magic SFI input polarity inversion fix\n");
+		m88x2_write(phydev, 0, 30, 0xb800, 0);
+		m88x2_write_mask(phydev, 0, 30, 0xb133, 0x0020, 0x0020);
+		m88x2_write_mask(phydev, 0, 30, 0xb333, 0x0020, 0x0020);
+		m88x2_write_mask(phydev, 0, 30, 0xb533, 0x0020, 0x0020);
+		m88x2_write_mask(phydev, 0, 30, 0xb733, 0x0020, 0x0020);
+	}
+
+	return 0;
+};
+
+static int m88x2_startup(struct phy_device *phydev)
+{
+#ifdef CONFIG_MV88X2_DEBUG_REGS
+	m88x2_dump_debug(phydev);
+#endif
+
+	return gen10g_startup(phydev);
+}
+
+static struct phy_driver M88X2_driver = {
+	.name = "Marvell 88X2",
+	.uid = 0x01410f10,
+	.mask = 0xffffff0,
+	.features = PHY_10G_FEATURES,
+	.config = &m88x2_config,
+	.startup = &m88x2_startup,
+	.shutdown = &gen10g_shutdown,
+};
+
+int phy_m88x2_init(void)
+{
+	phy_register(&M88X2_driver);
+
+	return 0;
+}
+
+static int sfi_test(struct phy_device *phydev, int portmask)
+{
+	int port;
+
+	/* PCS Reset */
+	m88x2_write_all(phydev, 31, 0xf003, 0x8080);
+
+	mdelay(2000);
+
+	for (port = 0; port < 4; ++port) {
+		if (!(portmask & BIT(port)))
+			continue;
+		/* pseudo random byte with crc */
+		m88x2_write(phydev, port, 3, 0xf011, 0x00a0);
+		/* continuously generate packets */
+		m88x2_write(phydev, port, 3, 0xf017, 0xffff);
+		/* enable generator and checker */
+		m88x2_write(phydev, port, 3, 0xf010, 0x0003);
+		udelay(200);
+		/* clear counters */
+		m88x2_write(phydev, port, 3, 0xf010, 0x0043);
+	}
+
+	mdelay(4000);
+
+	for (port = 0; port < 4; ++port) {
+		unsigned long long rxcnt, errcnt;
+
+		if (!(portmask & BIT(port)))
+			continue;
+
+		rxcnt = m88x2_read(phydev, port, 3, 0xf021);
+		rxcnt |= (unsigned long long)m88x2_read(phydev, port, 3,
+							0xf022) << 16;
+		rxcnt |= (unsigned long long)m88x2_read(phydev, port, 3,
+							0xf023) << 32;
+
+		errcnt = m88x2_read(phydev, port, 3, 0xf027);
+		errcnt |= (unsigned long long)m88x2_read(phydev, port, 3,
+							 0xf028) << 16;
+		errcnt |= (unsigned long long)m88x2_read(phydev, port, 3,
+							 0xf029) << 32;
+
+		if (errcnt > 1)
+			printf("\033[49;1;31m");
+
+		printf("%d: %llu\033[0m/%llu ", port, errcnt, rxcnt);
+	}
+
+	printf("\n");
+
+	for (port = 0; port < 4; ++port) {
+		if (!(portmask & BIT(port)))
+			continue;
+
+		/* disable generator */
+		m88x2_write(phydev, port, 3, 0xf010, 0x0001);
+	}
+
+	return 0;
+}
+
+static int do_mv88x2(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
+{
+	char op[2];
+	int portmask = 0xf;
+	int pos = argc - 1;
+	struct mii_dev *bus;
+	struct phy_device *phydev = NULL;
+
+	if (argc < 2)
+		return CMD_RET_USAGE;
+
+	/*
+	 * We use the last specified parameters, unless new ones are
+	 * entered.
+	 */
+	op[0] = argv[1][0];
+
+	bus = mdio_get_current_dev();
+
+	switch (op[0]) {
+	case 's':
+		if (pos > 3)
+			portmask = simple_strtoul(argv[pos--], NULL, 16);
+		break;
+	}
+
+	if (pos > 2)
+		bus = miiphy_get_dev_by_name(argv[pos - 1]);
+	if (!bus)
+		return -1;
+
+	if (pos > 1) {
+		unsigned int addr = simple_strtoul(argv[pos], NULL, 16);
+
+		if (addr > 31)
+			return -1;
+		phydev = phy_find_by_mask_c45(bus, BIT(addr),
+					      PHY_INTERFACE_MODE_XGMII);
+	}
+	if (!phydev)
+		return -1;
+
+	switch (op[0]) {
+	case 's':
+		sfi_test(phydev, portmask);
+		break;
+	}
+
+	return 0;
+}
+
+/***************************************************/
+
+U_BOOT_CMD(
+	mv88x2,	6,	0,	do_mv88x2,
+	"MV88X2 utility commands",
+	"sfitest <busname> <addr> [<portmask>] - use PHY packet generator/checker to test SFI ports at <portmask>\n"
+);
diff --git a/drivers/net/phy/mv88x2.h b/drivers/net/phy/mv88x2.h
new file mode 100644
index 0000000000..6cd2073cae
--- /dev/null
+++ b/drivers/net/phy/mv88x2.h
@@ -0,0 +1,12 @@
+struct mv88x2_config_data {
+	ulong sfi_pol;
+	ulong xfi_pol;
+	u16 led0_blink;
+	u16 led0_solid;
+	u16 led1_blink;
+	u16 led1_solid;
+};
+
+#ifdef CONFIG_BOARD_MV88X2_CONFIG
+int board_mv88x2_config(struct mv88x2_config_data *data);
+#endif
diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index a16bb6ca89..f39ab39061 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -493,6 +493,9 @@ int phy_init(void)
 #ifdef CONFIG_PHY_MICREL
 	phy_micrel_init();
 #endif
+#ifdef CONFIG_PHY_MV88X2
+	phy_m88x2_init();
+#endif
 #ifdef CONFIG_PHY_NATSEMI
 	phy_natsemi_init();
 #endif
diff --git a/include/phy.h b/include/phy.h
index 75a9ae3314..1c94f511a7 100644
--- a/include/phy.h
+++ b/include/phy.h
@@ -275,6 +275,7 @@ int phy_et1011c_init(void);
 int phy_lxt_init(void);
 int phy_marvell_init(void);
 int phy_micrel_init(void);
+int phy_m88x2_init(void);
 int phy_natsemi_init(void);
 int phy_realtek_init(void);
 int phy_smsc_init(void);
-- 
2.11.0



More information about the U-Boot mailing list