From c24cbb648c5bde8312dbd5498a4b8c12b2692205 Mon Sep 17 00:00:00 2001
From: Biwen Li <biwen.li@nxp.com>
Date: Wed, 17 Apr 2019 18:58:45 +0800
Subject: [PATCH] mdio-phy: support layerscape

This is an integrated patch of mdio-phy for layerscape

Signed-off-by: Bhaskar Upadhaya <Bhaskar.Upadhaya@nxp.com>
Signed-off-by: Biwen Li <biwen.li@nxp.com>
Signed-off-by: Camelia Groza <camelia.groza@nxp.com>
Signed-off-by: Constantin Tudor <constantin.tudor@nxp.com>
Signed-off-by: costi <constantin.tudor@freescale.com>
Signed-off-by: Florin Chiculita <florinlaurentiu.chiculita@nxp.com>
Signed-off-by: Florinel Iordache <florinel.iordache@nxp.com>
Signed-off-by: Ioana Ciornei <ioana.ciornei@nxp.com>
Signed-off-by: Ioana Radulescu <ruxandra.radulescu@nxp.com>
Signed-off-by: Madalin Bucur <madalin.bucur@freescale.com>
Signed-off-by: Pankaj Bansal <pankaj.bansal@nxp.com>
Signed-off-by: Shaohui Xie <Shaohui.Xie@freescale.com>
Signed-off-by: Valentin Catalin Neacsu <valentin-catalin.neacsu@nxp.com>
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
 drivers/net/phy/Kconfig                    |   33 +
 drivers/net/phy/Makefile                   |    5 +
 drivers/net/phy/aquantia.c                 |  286 ++++-
 drivers/net/phy/at803x.c                   |   21 +
 drivers/net/phy/fsl_backplane.c            | 1780 ++++++++++++++++++++++++++++
 drivers/net/phy/fsl_backplane.h            |   41 +
 drivers/net/phy/fsl_backplane_serdes_10g.c |  281 +++++
 drivers/net/phy/fsl_backplane_serdes_28g.c |  336 ++++++
 drivers/net/phy/inphi.c                    |  594 ++++++++++
 drivers/net/phy/mdio-mux-multiplexer.c     |  122 ++
 drivers/net/phy/swphy.c                    |    1 +
 include/linux/phy.h                        |    3 +
 12 files changed, 3484 insertions(+), 19 deletions(-)
 create mode 100644 drivers/net/phy/fsl_backplane.c
 create mode 100644 drivers/net/phy/fsl_backplane.h
 create mode 100644 drivers/net/phy/fsl_backplane_serdes_10g.c
 create mode 100644 drivers/net/phy/fsl_backplane_serdes_28g.c
 create mode 100644 drivers/net/phy/inphi.c
 create mode 100644 drivers/net/phy/mdio-mux-multiplexer.c

--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -87,9 +87,27 @@ config MDIO_BUS_MUX_MMIOREG
 
 	  Currently, only 8/16/32 bits registers are supported.
 
+config MDIO_BUS_MUX_MULTIPLEXER
+	tristate "MDIO bus multiplexer using kernel multiplexer subsystem"
+	depends on OF
+	select MULTIPLEXER
+	select MDIO_BUS_MUX
+	help
+	  This module provides a driver for MDIO bus multiplexer
+	  that is controlled via the kernel multiplexer subsystem. The
+	  bus multiplexer connects one of several child MDIO busses to
+	  a parent bus.  Child bus selection is under the control of
+	  the kernel multiplexer subsystem.
+
 config MDIO_CAVIUM
 	tristate
 
+config MDIO_FSL_BACKPLANE
+	tristate "Support for backplane on Freescale XFI interface"
+	depends on OF_MDIO
+	help
+	  This module provides a driver for Freescale XFI's backplane.
+
 config MDIO_GPIO
 	tristate "GPIO lib-based bitbanged MDIO buses"
 	depends on MDIO_BITBANG && GPIOLIB
@@ -303,6 +321,16 @@ config AT803X_PHY
 	---help---
 	  Currently supports the AT8030 and AT8035 model
 
+config AT803X_PHY_SMART_EEE
+	depends on AT803X_PHY
+	default n
+	tristate "SmartEEE feature for AT803X PHYs"
+	---help---
+	  Enables the Atheros SmartEEE feature (not IEEE 802.3az). When 2 PHYs
+	  which support this feature are connected back-to-back, they may
+	  negotiate a low-power sleep mode autonomously, without the Ethernet
+	  controller's knowledge.  May cause packet loss.
+
 config BCM63XX_PHY
 	tristate "Broadcom 63xx SOCs internal PHY"
 	depends on BCM63XX
@@ -385,6 +413,11 @@ config ICPLUS_PHY
 	---help---
 	  Currently supports the IP175C and IP1001 PHYs.
 
+config INPHI_PHY
+	tristate "Inphi CDR 10G/25G Ethernet PHY"
+	---help---
+	  Currently supports the IN112525_S03 part @ 25G
+
 config INTEL_XWAY_PHY
 	tristate "Intel XWAY PHYs"
 	---help---
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -44,7 +44,11 @@ obj-$(CONFIG_MDIO_BUS_MUX)	+= mdio-mux.o
 obj-$(CONFIG_MDIO_BUS_MUX_BCM_IPROC)	+= mdio-mux-bcm-iproc.o
 obj-$(CONFIG_MDIO_BUS_MUX_GPIO)	+= mdio-mux-gpio.o
 obj-$(CONFIG_MDIO_BUS_MUX_MMIOREG) += mdio-mux-mmioreg.o
+obj-$(CONFIG_MDIO_BUS_MUX_MULTIPLEXER) += mdio-mux-multiplexer.o
 obj-$(CONFIG_MDIO_CAVIUM)	+= mdio-cavium.o
+obj-$(CONFIG_MDIO_FSL_BACKPLANE) += fsl_backplane.o
+obj-$(CONFIG_MDIO_FSL_BACKPLANE) += fsl_backplane_serdes_10g.o
+obj-$(CONFIG_MDIO_FSL_BACKPLANE) += fsl_backplane_serdes_28g.o
 obj-$(CONFIG_MDIO_GPIO)		+= mdio-gpio.o
 obj-$(CONFIG_MDIO_HISI_FEMAC)	+= mdio-hisi-femac.o
 obj-$(CONFIG_MDIO_I2C)		+= mdio-i2c.o
@@ -75,6 +79,7 @@ obj-$(CONFIG_DP83848_PHY)	+= dp83848.o
 obj-$(CONFIG_DP83867_PHY)	+= dp83867.o
 obj-$(CONFIG_FIXED_PHY)		+= fixed_phy.o
 obj-$(CONFIG_ICPLUS_PHY)	+= icplus.o
+obj-$(CONFIG_INPHI_PHY)	+= inphi.o
 obj-$(CONFIG_INTEL_XWAY_PHY)	+= intel-xway.o
 obj-$(CONFIG_LSI_ET1011C_PHY)	+= et1011c.o
 obj-$(CONFIG_LXT_PHY)		+= lxt.o
--- a/drivers/net/phy/aquantia.c
+++ b/drivers/net/phy/aquantia.c
@@ -4,6 +4,7 @@
  * Author: Shaohui Xie <Shaohui.Xie@freescale.com>
  *
  * Copyright 2015 Freescale Semiconductor, Inc.
+ * Copyright 2018 NXP
  *
  * This file is licensed under the terms of the GNU General Public License
  * version 2.  This program is licensed "as is" without any warranty of any
@@ -27,15 +28,174 @@
 
 #define PHY_AQUANTIA_FEATURES	(SUPPORTED_10000baseT_Full | \
 				 SUPPORTED_1000baseT_Full | \
+				 SUPPORTED_2500baseX_Full | \
 				 SUPPORTED_100baseT_Full | \
 				 PHY_DEFAULT_FEATURES)
 
+#define MDIO_PMA_CTRL1_AQ_SPEED10	0
+#define MDIO_PMA_CTRL1_AQ_SPEED2500	0x2058
+#define MDIO_PMA_CTRL1_AQ_SPEED5000	0x205c
+#define MDIO_PMA_CTRL2_AQ_2500BT       0x30
+#define MDIO_PMA_CTRL2_AQ_5000BT       0x31
+#define MDIO_PMA_CTRL2_AQ_TYPE_MASK    0x3F
+
+#define MDIO_AN_VENDOR_PROV_CTRL       0xc400
+#define MDIO_AN_RECV_LP_STATUS         0xe820
+
+static int aquantia_write_reg(struct phy_device *phydev, int devad,
+			      u32 regnum, u16 val)
+{
+	u32 addr = MII_ADDR_C45 | (devad << 16) | (regnum & 0xffff);
+
+	return mdiobus_write(phydev->mdio.bus, phydev->mdio.addr, addr, val);
+}
+
+static int aquantia_read_reg(struct phy_device *phydev, int devad, u32 regnum)
+{
+	u32 addr = MII_ADDR_C45 | (devad << 16) | (regnum & 0xffff);
+
+	return mdiobus_read(phydev->mdio.bus, phydev->mdio.addr, addr);
+}
+
+static int aquantia_pma_setup_forced(struct phy_device *phydev)
+{
+	int ctrl1, ctrl2, ret;
+
+	/* Half duplex is not supported */
+	if (phydev->duplex != DUPLEX_FULL)
+		return -EINVAL;
+
+	ctrl1 = aquantia_read_reg(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1);
+	if (ctrl1 < 0)
+		return ctrl1;
+
+	ctrl2 = aquantia_read_reg(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL2);
+	if (ctrl2 < 0)
+		return ctrl2;
+
+	ctrl1 &= ~MDIO_CTRL1_SPEEDSEL;
+	ctrl2 &= ~(MDIO_PMA_CTRL2_AQ_TYPE_MASK);
+
+	switch (phydev->speed) {
+	case SPEED_10:
+		ctrl2 |= MDIO_PMA_CTRL2_10BT;
+		break;
+	case SPEED_100:
+		ctrl1 |= MDIO_PMA_CTRL1_SPEED100;
+		ctrl2 |= MDIO_PMA_CTRL2_100BTX;
+		break;
+	case SPEED_1000:
+		ctrl1 |= MDIO_PMA_CTRL1_SPEED1000;
+		/* Assume 1000base-T */
+		ctrl2 |= MDIO_PMA_CTRL2_1000BT;
+		break;
+	case SPEED_10000:
+		ctrl1 |= MDIO_CTRL1_SPEED10G;
+		/* Assume 10Gbase-T */
+		ctrl2 |= MDIO_PMA_CTRL2_10GBT;
+		break;
+	case SPEED_2500:
+		ctrl1 |= MDIO_PMA_CTRL1_AQ_SPEED2500;
+		ctrl2 |= MDIO_PMA_CTRL2_AQ_2500BT;
+		break;
+	case SPEED_5000:
+		ctrl1 |= MDIO_PMA_CTRL1_AQ_SPEED5000;
+		ctrl2 |= MDIO_PMA_CTRL2_AQ_5000BT;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	ret = aquantia_write_reg(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1, ctrl1);
+	if (ret < 0)
+		return ret;
+
+	return aquantia_write_reg(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL2, ctrl2);
+}
+
+static int aquantia_aneg(struct phy_device *phydev, bool control)
+{
+	int reg = aquantia_read_reg(phydev, MDIO_MMD_AN, MDIO_CTRL1);
+
+	if (reg < 0)
+		return reg;
+
+	if (control)
+		reg |= MDIO_AN_CTRL1_ENABLE | MDIO_AN_CTRL1_RESTART;
+	else
+		reg &= ~(MDIO_AN_CTRL1_ENABLE | MDIO_AN_CTRL1_RESTART);
+
+	return aquantia_write_reg(phydev, MDIO_MMD_AN, MDIO_CTRL1, reg);
+}
+
+static int aquantia_config_advert(struct phy_device *phydev)
+{
+	u32 advertise;
+	int oldadv, adv, oldadv1, adv1;
+	int err, changed = 0;
+
+	/* Only allow advertising what this PHY supports */
+	phydev->advertising &= phydev->supported;
+	advertise = phydev->advertising;
+
+	/* Setup standard advertisement */
+	oldadv = aquantia_read_reg(phydev, MDIO_MMD_AN,
+				   MDIO_AN_10GBT_CTRL);
+	if (oldadv < 0)
+		return oldadv;
+
+	/* Aquantia vendor specific advertisments */
+	oldadv1 = aquantia_read_reg(phydev, MDIO_MMD_AN,
+				    MDIO_AN_VENDOR_PROV_CTRL);
+	if (oldadv1 < 0)
+		return oldadv1;
+
+	adv  = 0;
+	adv1 = 0;
+
+	/*100BaseT_full is supported by default*/
+
+	if (advertise & ADVERTISED_1000baseT_Full)
+		adv1 |= 0x8000;
+	if (advertise & ADVERTISED_10000baseT_Full)
+		adv |= 0x1000;
+	if (advertise &  ADVERTISED_2500baseX_Full)
+		adv1 |= 0x400;
+
+	if (adv != oldadv) {
+		err = aquantia_write_reg(phydev, MDIO_MMD_AN,
+					 MDIO_AN_10GBT_CTRL, adv);
+		if (err < 0)
+			return err;
+		changed = 1;
+	}
+	if (adv1 != oldadv1) {
+		err = aquantia_write_reg(phydev, MDIO_MMD_AN,
+					 MDIO_AN_VENDOR_PROV_CTRL, adv1);
+		if (err < 0)
+			return err;
+		changed = 1;
+	}
+
+	return changed;
+}
+
 static int aquantia_config_aneg(struct phy_device *phydev)
 {
+	int ret = 0;
+
 	phydev->supported = PHY_AQUANTIA_FEATURES;
-	phydev->advertising = phydev->supported;
+	if (phydev->autoneg == AUTONEG_DISABLE) {
+		aquantia_pma_setup_forced(phydev);
+		return aquantia_aneg(phydev, false);
+	}
 
-	return 0;
+	ret = aquantia_config_advert(phydev);
+	if (ret > 0)
+		/* restart autoneg */
+		return aquantia_aneg(phydev, true);
+
+	return ret;
 }
 
 static int aquantia_aneg_done(struct phy_device *phydev)
@@ -51,25 +211,26 @@ static int aquantia_config_intr(struct p
 	int err;
 
 	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
-		err = phy_write_mmd(phydev, MDIO_MMD_AN, 0xd401, 1);
+		err = aquantia_write_reg(phydev, MDIO_MMD_AN, 0xd401, 1);
 		if (err < 0)
 			return err;
 
-		err = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xff00, 1);
+		err = aquantia_write_reg(phydev, MDIO_MMD_VEND1, 0xff00, 1);
 		if (err < 0)
 			return err;
 
-		err = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xff01, 0x1001);
+		err = aquantia_write_reg(phydev, MDIO_MMD_VEND1,
+					 0xff01, 0x1001);
 	} else {
-		err = phy_write_mmd(phydev, MDIO_MMD_AN, 0xd401, 0);
+		err = aquantia_write_reg(phydev, MDIO_MMD_AN, 0xd401, 0);
 		if (err < 0)
 			return err;
 
-		err = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xff00, 0);
+		err = aquantia_write_reg(phydev, MDIO_MMD_VEND1, 0xff00, 0);
 		if (err < 0)
 			return err;
 
-		err = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xff01, 0);
+		err = aquantia_write_reg(phydev, MDIO_MMD_VEND1, 0xff01, 0);
 	}
 
 	return err;
@@ -79,42 +240,129 @@ static int aquantia_ack_interrupt(struct
 {
 	int reg;
 
-	reg = phy_read_mmd(phydev, MDIO_MMD_AN, 0xcc01);
+	reg = aquantia_read_reg(phydev, MDIO_MMD_AN, 0xcc01);
 	return (reg < 0) ? reg : 0;
 }
 
+static int aquantia_read_advert(struct phy_device *phydev)
+{
+	int adv, adv1;
+
+	/* Setup standard advertisement */
+	adv = aquantia_read_reg(phydev, MDIO_MMD_AN,
+				MDIO_AN_10GBT_CTRL);
+
+	/* Aquantia vendor specific advertisments */
+	adv1 = aquantia_read_reg(phydev, MDIO_MMD_AN,
+				 MDIO_AN_VENDOR_PROV_CTRL);
+
+	/*100BaseT_full is supported by default*/
+	phydev->advertising |= ADVERTISED_100baseT_Full;
+
+	if (adv & 0x1000)
+		phydev->advertising |= ADVERTISED_10000baseT_Full;
+	else
+		phydev->advertising &= ~ADVERTISED_10000baseT_Full;
+	if (adv1 & 0x8000)
+		phydev->advertising |= ADVERTISED_1000baseT_Full;
+	else
+		phydev->advertising &= ~ADVERTISED_1000baseT_Full;
+	if (adv1 & 0x400)
+		phydev->advertising |= ADVERTISED_2500baseX_Full;
+	else
+		phydev->advertising &= ~ADVERTISED_2500baseX_Full;
+	return 0;
+}
+
+static int aquantia_read_lp_advert(struct phy_device *phydev)
+{
+	int adv, adv1;
+
+	/* Read standard link partner advertisement */
+	adv = aquantia_read_reg(phydev, MDIO_MMD_AN,
+				MDIO_STAT1);
+
+	if (adv & 0x1)
+		phydev->lp_advertising |= ADVERTISED_Autoneg |
+					  ADVERTISED_100baseT_Full;
+	else
+		phydev->lp_advertising &= ~(ADVERTISED_Autoneg |
+					    ADVERTISED_100baseT_Full);
+
+	/* Read standard link partner advertisement */
+	adv = aquantia_read_reg(phydev, MDIO_MMD_AN,
+				MDIO_AN_10GBT_STAT);
+
+	/* Aquantia link partner advertisments */
+	adv1 = aquantia_read_reg(phydev, MDIO_MMD_AN,
+				 MDIO_AN_RECV_LP_STATUS);
+
+	if (adv & 0x800)
+		phydev->lp_advertising |= ADVERTISED_10000baseT_Full;
+	else
+		phydev->lp_advertising &= ~ADVERTISED_10000baseT_Full;
+	if (adv1 & 0x8000)
+		phydev->lp_advertising |= ADVERTISED_1000baseT_Full;
+	else
+		phydev->lp_advertising &= ~ADVERTISED_1000baseT_Full;
+	if (adv1 & 0x400)
+		phydev->lp_advertising |= ADVERTISED_2500baseX_Full;
+	else
+		phydev->lp_advertising &= ~ADVERTISED_2500baseX_Full;
+
+	return 0;
+}
+
 static int aquantia_read_status(struct phy_device *phydev)
 {
 	int reg;
 
-	reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
-	reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+	/* Read the link status twice; the bit is latching low */
+	reg = aquantia_read_reg(phydev, MDIO_MMD_AN, MDIO_STAT1);
+	reg = aquantia_read_reg(phydev, MDIO_MMD_AN, MDIO_STAT1);
+
 	if (reg & MDIO_STAT1_LSTATUS)
 		phydev->link = 1;
 	else
 		phydev->link = 0;
 
-	reg = phy_read_mmd(phydev, MDIO_MMD_AN, 0xc800);
 	mdelay(10);
-	reg = phy_read_mmd(phydev, MDIO_MMD_AN, 0xc800);
+	reg = aquantia_read_reg(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1);
+
+	if ((reg & MDIO_CTRL1_SPEEDSELEXT) == MDIO_CTRL1_SPEEDSELEXT)
+		reg &= MDIO_CTRL1_SPEEDSEL;
+	else
+		reg &= MDIO_CTRL1_SPEEDSELEXT;
 
 	switch (reg) {
-	case 0x9:
+	case MDIO_PMA_CTRL1_AQ_SPEED5000:
+		phydev->speed = SPEED_5000;
+		break;
+	case MDIO_PMA_CTRL1_AQ_SPEED2500:
 		phydev->speed = SPEED_2500;
 		break;
-	case 0x5:
-		phydev->speed = SPEED_1000;
+	case MDIO_PMA_CTRL1_AQ_SPEED10:
+		phydev->speed = SPEED_10;
 		break;
-	case 0x3:
+	case MDIO_PMA_CTRL1_SPEED100:
 		phydev->speed = SPEED_100;
 		break;
-	case 0x7:
-	default:
+	case MDIO_PMA_CTRL1_SPEED1000:
+		phydev->speed = SPEED_1000;
+		break;
+	case MDIO_CTRL1_SPEED10G:
 		phydev->speed = SPEED_10000;
 		break;
+	default:
+		phydev->speed = SPEED_UNKNOWN;
+		break;
 	}
+
 	phydev->duplex = DUPLEX_FULL;
 
+	aquantia_read_advert(phydev);
+	aquantia_read_lp_advert(phydev);
+
 	return 0;
 }
 
--- a/drivers/net/phy/at803x.c
+++ b/drivers/net/phy/at803x.c
@@ -68,6 +68,8 @@
 #define AT803X_DEBUG_REG_5			0x05
 #define AT803X_DEBUG_TX_CLK_DLY_EN		BIT(8)
 
+#define AT803X_LPI_EN				BIT(8)
+
 #define ATH8030_PHY_ID 0x004dd076
 #define ATH8031_PHY_ID 0x004dd074
 #define ATH8032_PHY_ID 0x004dd023
@@ -290,6 +292,19 @@ static void at803x_disable_smarteee(stru
 	phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0);
 }
 
+static void at803x_enable_smart_eee(struct phy_device *phydev, int on)
+{
+	int value;
+
+	/* 5.1.11 Smart_eee control3 */
+	value = phy_read_mmd(phydev, MDIO_MMD_PCS, 0x805D);
+	if (on)
+		value |= AT803X_LPI_EN;
+	else
+		value &= ~AT803X_LPI_EN;
+	phy_write_mmd(phydev, MDIO_MMD_PCS, 0x805D, value);
+}
+
 static int at803x_config_init(struct phy_device *phydev)
 {
 	struct at803x_platform_data *pdata;
@@ -320,6 +335,12 @@ static int at803x_config_init(struct phy
 	if (ret < 0)
 		return ret;
 
+#ifdef CONFIG_AT803X_PHY_SMART_EEE
+	at803x_enable_smart_eee(phydev, 1);
+#else
+	at803x_enable_smart_eee(phydev, 0);
+#endif
+
 	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID ||
 			phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) {
 		ret = at803x_enable_rx_delay(phydev);
--- /dev/null
+++ b/drivers/net/phy/fsl_backplane.c
@@ -0,0 +1,1780 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *  DPAA backplane driver.
+ *   Author: Shaohui Xie <Shaohui.Xie@freescale.com>
+ *           Florinel Iordache <florinel.iordache@nxp.com>
+ *
+ * Copyright 2015 Freescale Semiconductor, Inc.
+ * Copyright 2018 NXP
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/mdio.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include <linux/netdevice.h>
+
+#include "fsl_backplane.h"
+
+
+/* PCS Device Identifier */
+#define PCS_PHY_DEVICE_ID			0x0083e400
+#define PCS_PHY_DEVICE_ID_MASK		0xffffffff
+
+/* 10G Long cables setup: 1 m to 2 m cables */
+#define RATIO_PREQ_10G				0x3
+#define RATIO_PST1Q_10G				0xd
+#define RATIO_EQ_10G				0x20
+
+/* 10G Short cables setup: up to 30 cm cable */
+//#define RATIO_PREQ_10G				0x3
+//#define RATIO_PST1Q_10G				0xa
+//#define RATIO_EQ_10G				0x29
+
+/* 40G Long cables setup: 1 m to 2 m cables */
+#define RATIO_PREQ_40G				0x2
+#define RATIO_PST1Q_40G				0xd
+#define RATIO_EQ_40G				0x20
+
+/* 40G Short cables setup: up to 30 cm cable */
+//#define RATIO_PREQ_40G				0x1
+//#define RATIO_PST1Q_40G				0x3
+//#define RATIO_EQ_40G				0x29
+
+/* LX2 2x40G default RCW setup */
+//#define RATIO_PREQ_40G				0x0
+//#define RATIO_PST1Q_40G				0x3
+//#define RATIO_EQ_40G				0x30
+
+/* Max/Min coefficient values */
+#define PRE_COE_MAX					0x0
+#define PRE_COE_MIN					0x8
+#define POST_COE_MAX				0x0
+#define POST_COE_MIN				0x10
+#define ZERO_COE_MAX				0x30
+#define ZERO_COE_MIN				0x0
+
+/* KR PMD defines */
+#define PMD_RESET					0x1
+#define PMD_STATUS_SUP_STAT			0x4
+#define PMD_STATUS_FRAME_LOCK		0x2
+#define TRAIN_EN					0x3
+#define TRAIN_DISABLE				0x1
+#define RX_STAT						0x1
+
+/* PCS Link up */
+#define XFI_PCS_SR1             	0x20
+#define KR_RX_LINK_STAT_MASK		0x1000
+
+/* KX PCS mode register */
+#define KX_PCS_IF_MODE				0x8014
+
+/* KX PCS mode register init value */
+#define KX_IF_MODE_INIT				0x8
+
+/* KX/KR AN registers */
+#define AN_CTRL_INIT				0x1200
+#define KX_AN_AD1_INIT				0x25
+#define KR_AN_AD1_INIT_10G			0x85
+#define KR_AN_AD1_INIT_40G			0x105
+#define AN_LNK_UP_MASK				0x4
+#define KR_AN_MASK_10G				0x8
+#define KR_AN_MASK_40G				0x20
+#define TRAIN_FAIL					0x8
+#define KR_AN_40G_MDIO_OFFSET		4
+
+/* XGKR Timeouts */
+#define XGKR_TIMEOUT				1050
+#define XGKR_DENY_RT_INTERVAL		3000
+#define XGKR_AN_WAIT_ITERATIONS 	5
+
+/* XGKR Increment/Decrement Requests */
+#define INCREMENT					1
+#define DECREMENT					2
+#define TIMEOUT_LONG				3
+#define TIMEOUT_M1					3
+
+/* XGKR Masks */
+#define RX_READY_MASK				0x8000
+#define PRESET_MASK					0x2000
+#define INIT_MASK					0x1000
+#define COP1_MASK					0x30
+#define COP1_SHIFT					4
+#define COZ_MASK					0xc
+#define COZ_SHIFT					2
+#define COM1_MASK					0x3
+#define COM1_SHIFT					0
+#define REQUEST_MASK				0x3f
+#define LD_ALL_MASK			(PRESET_MASK | INIT_MASK | \
+					COP1_MASK | COZ_MASK | COM1_MASK)
+
+/* Lanes definitions */
+#define MASTER_LANE					0
+#define SINGLE_LANE					0
+#define MAX_PHY_LANES_NO			4
+
+/* Invalid value */
+#define VAL_INVALID 				0xff
+
+/* New XGKR Training Algorithm */
+#define NEW_ALGORITHM_TRAIN_TX
+
+#ifdef	NEW_ALGORITHM_TRAIN_TX
+#define	FORCE_INC_COP1_NUMBER		0
+#define	FORCE_INC_COM1_NUMBER		1
+#endif
+
+/* Link_Training_Registers offsets */
+static int lt_MDIO_MMD = 0;
+static u32 lt_KR_PMD_CTRL = 0;
+static u32 lt_KR_PMD_STATUS = 0;
+static u32 lt_KR_LP_CU = 0;
+static u32 lt_KR_LP_STATUS = 0;
+static u32 lt_KR_LD_CU = 0;
+static u32 lt_KR_LD_STATUS = 0;
+
+/* KX/KR AN registers offsets */
+static u32 g_an_AD1 = 0;
+static u32 g_an_BP_STAT = 0;
+
+static const u32 preq_table[] = {0x0, 0x1, 0x3, 0x5,
+				 0x7, 0x9, 0xb, 0xc, VAL_INVALID};
+static const u32 pst1q_table[] = {0x0, 0x1, 0x3, 0x5, 0x7,
+				  0x9, 0xb, 0xd, 0xf, 0x10, VAL_INVALID};
+
+enum backplane_mode {
+	PHY_BACKPLANE_1000BASE_KX,
+	PHY_BACKPLANE_10GBASE_KR,
+	PHY_BACKPLANE_40GBASE_KR,
+	PHY_BACKPLANE_INVAL
+};
+
+enum serdes_type {
+	SERDES_10G,
+	SERDES_28G,
+	SERDES_INVAL
+};
+
+enum coe_filed {
+	COE_COP1,
+	COE_COZ,
+	COE_COM
+};
+
+enum coe_update {
+	COE_NOTUPDATED,
+	COE_UPDATED,
+	COE_MIN,
+	COE_MAX,
+	COE_INV
+};
+
+enum train_state {
+	DETECTING_LP,
+	TRAINED,
+};
+
+struct tx_condition {
+	bool bin_m1_late_early;
+	bool bin_long_late_early;
+	bool bin_m1_stop;
+	bool bin_long_stop;
+	bool tx_complete;
+	bool sent_init;
+	int m1_min_max_cnt;
+	int long_min_max_cnt;
+#ifdef	NEW_ALGORITHM_TRAIN_TX
+	int pre_inc;
+	int post_inc;
+#endif
+};
+
+struct xgkr_params {
+	void *reg_base;		/* lane memory map: registers base address */
+	int idx;			/* lane relative index inside a multi-lane PHY */
+	struct phy_device *phydev;
+	struct serdes_access *srds;
+	struct tx_condition tx_c;
+	struct delayed_work xgkr_wk;
+	enum train_state state;
+	int an_wait_count;
+	unsigned long rt_time;
+	u32 ld_update;
+	u32 ld_status;
+	u32 ratio_preq;
+	u32 ratio_pst1q;
+	u32 adpt_eq;
+	u32 tuned_ratio_preq;
+	u32 tuned_ratio_pst1q;
+	u32 tuned_adpt_eq;
+};
+
+struct xgkr_phy_data {
+	int bp_mode;
+	u32 phy_lanes;
+	struct mutex phy_lock;
+	bool aneg_done;
+	struct xgkr_params xgkr[MAX_PHY_LANES_NO];
+};
+
+static void setup_an_lt_ls(void)
+{
+	/* KR PMD registers */
+	lt_MDIO_MMD = MDIO_MMD_PMAPMD;
+	lt_KR_PMD_CTRL = 0x96;
+	lt_KR_PMD_STATUS = 0x97;
+	lt_KR_LP_CU = 0x98;
+	lt_KR_LP_STATUS = 0x99;
+	lt_KR_LD_CU = 0x9a;
+	lt_KR_LD_STATUS = 0x9b;
+
+	/* KX/KR AN registers */
+	g_an_AD1 = 0x11;
+	g_an_BP_STAT = 0x30;
+}
+
+static void setup_an_lt_lx(void)
+{
+	/* Auto-Negotiation and Link Training Core Registers page 1: 256 = 0x100 */
+	lt_MDIO_MMD = MDIO_MMD_AN;
+	lt_KR_PMD_CTRL = 0x100;
+	lt_KR_PMD_STATUS = 0x101;
+	lt_KR_LP_CU = 0x102;
+	lt_KR_LP_STATUS = 0x103;
+	lt_KR_LD_CU = 0x104;
+	lt_KR_LD_STATUS = 0x105;
+
+	/* KX/KR AN registers */
+	g_an_AD1 = 0x03;
+	g_an_BP_STAT = 0x0F;
+}
+
+static u32 le_ioread32(u32 *reg)
+{
+	return ioread32(reg);
+}
+
+static void le_iowrite32(u32 value, u32 *reg)
+{
+	iowrite32(value, reg);
+}
+
+static u32 be_ioread32(u32 *reg)
+{
+	return ioread32be(reg);
+}
+
+static void be_iowrite32(u32 value, u32 *reg)
+{
+	iowrite32be(value, reg);
+}
+
+/**
+ * xgkr_phy_write_mmd - Wrapper function for phy_write_mmd
+ * for writing a register on an MMD on a given PHY.
+ *
+ * Same rules as for phy_write_mmd();
+ */
+static int xgkr_phy_write_mmd(struct xgkr_params *xgkr, int devad, u32 regnum, u16 val)
+{
+	struct phy_device *phydev = xgkr->phydev;
+	struct xgkr_phy_data *xgkr_inst = phydev->priv;
+	int mdio_addr = phydev->mdio.addr;
+	int err;
+
+	mutex_lock(&xgkr_inst->phy_lock);
+
+	if (xgkr_inst->bp_mode == PHY_BACKPLANE_40GBASE_KR && devad == MDIO_MMD_AN) {
+		//40G AN: prepare mdio address for writing phydev AN registers for 40G on respective lane
+		phydev->mdio.addr = KR_AN_40G_MDIO_OFFSET + xgkr->idx;
+	}
+
+	err = phy_write_mmd(phydev, devad, regnum, val);
+	if (err)
+		dev_err(&phydev->mdio.dev, "Writing PHY (%p) MMD = 0x%02x register = 0x%02x failed with error code: 0x%08x \n", phydev, devad, regnum, err);
+
+	if (xgkr_inst->bp_mode == PHY_BACKPLANE_40GBASE_KR && devad == MDIO_MMD_AN) {
+		//40G AN: restore mdio address
+		phydev->mdio.addr = mdio_addr;
+	}
+
+	mutex_unlock(&xgkr_inst->phy_lock);
+
+	return err;
+}
+
+/**
+ * xgkr_phy_read_mmd - Wrapper function for phy_read_mmd
+ * for reading a register from an MMD on a given PHY.
+ *
+ * Same rules as for phy_read_mmd();
+ */
+static int xgkr_phy_read_mmd(struct xgkr_params *xgkr, int devad, u32 regnum)
+{
+	struct phy_device *phydev = xgkr->phydev;
+	struct xgkr_phy_data *xgkr_inst = phydev->priv;
+	int mdio_addr = phydev->mdio.addr;
+	int ret;
+
+	mutex_lock(&xgkr_inst->phy_lock);
+
+	if (xgkr_inst->bp_mode == PHY_BACKPLANE_40GBASE_KR && devad == MDIO_MMD_AN) {
+		//40G AN: prepare mdio address for reading phydev AN registers for 40G on respective lane
+		phydev->mdio.addr = KR_AN_40G_MDIO_OFFSET + xgkr->idx;
+	}
+
+	ret = phy_read_mmd(phydev, devad, regnum);
+
+	if (xgkr_inst->bp_mode == PHY_BACKPLANE_40GBASE_KR && devad == MDIO_MMD_AN) {
+		//40G AN: restore mdio address
+		phydev->mdio.addr = mdio_addr;
+	}
+
+	mutex_unlock(&xgkr_inst->phy_lock);
+
+	return ret;
+}
+
+static void tx_condition_init(struct tx_condition *tx_c)
+{
+	tx_c->bin_m1_late_early = true;
+	tx_c->bin_long_late_early = false;
+	tx_c->bin_m1_stop = false;
+	tx_c->bin_long_stop = false;
+	tx_c->tx_complete = false;
+	tx_c->sent_init = false;
+	tx_c->m1_min_max_cnt = 0;
+	tx_c->long_min_max_cnt = 0;
+#ifdef	NEW_ALGORITHM_TRAIN_TX
+	tx_c->pre_inc = FORCE_INC_COM1_NUMBER;
+	tx_c->post_inc = FORCE_INC_COP1_NUMBER;
+#endif
+}
+
+void tune_tecr(struct xgkr_params *xgkr)
+{
+	struct phy_device *phydev = xgkr->phydev;
+	struct xgkr_phy_data *xgkr_inst = phydev->priv;
+	bool reset = false;
+	
+	if (xgkr_inst->bp_mode == PHY_BACKPLANE_40GBASE_KR) {
+		/* Reset only the Master Lane */
+		reset = (xgkr->idx == MASTER_LANE);
+	} else {
+		reset = true;
+	}
+	
+	xgkr->srds->tune_tecr(xgkr->reg_base, xgkr->ratio_preq, xgkr->ratio_pst1q, xgkr->adpt_eq, reset);
+
+	xgkr->tuned_ratio_preq = xgkr->ratio_preq;
+	xgkr->tuned_ratio_pst1q = xgkr->ratio_pst1q;
+	xgkr->tuned_adpt_eq = xgkr->adpt_eq;
+}
+
+static void start_lt(struct xgkr_params *xgkr)
+{
+	xgkr_phy_write_mmd(xgkr, lt_MDIO_MMD, lt_KR_PMD_CTRL, TRAIN_EN);
+}
+
+static void stop_lt(struct xgkr_params *xgkr)
+{
+	xgkr_phy_write_mmd(xgkr, lt_MDIO_MMD, lt_KR_PMD_CTRL, TRAIN_DISABLE);
+}
+
+static void reset_lt(struct xgkr_params *xgkr)
+{
+	xgkr_phy_write_mmd(xgkr, lt_MDIO_MMD, MDIO_CTRL1, PMD_RESET);
+	xgkr_phy_write_mmd(xgkr, lt_MDIO_MMD, lt_KR_PMD_CTRL, TRAIN_DISABLE);
+	xgkr_phy_write_mmd(xgkr, lt_MDIO_MMD, lt_KR_LD_CU, 0);
+	xgkr_phy_write_mmd(xgkr, lt_MDIO_MMD, lt_KR_LD_STATUS, 0);
+	xgkr_phy_write_mmd(xgkr, lt_MDIO_MMD, lt_KR_PMD_STATUS, 0);
+	xgkr_phy_write_mmd(xgkr, lt_MDIO_MMD, lt_KR_LP_CU, 0);
+	xgkr_phy_write_mmd(xgkr, lt_MDIO_MMD, lt_KR_LP_STATUS, 0);
+	
+}
+
+static void ld_coe_status(struct xgkr_params *xgkr)
+{
+	xgkr_phy_write_mmd(xgkr, lt_MDIO_MMD,
+		      lt_KR_LD_STATUS, xgkr->ld_status);
+}
+
+static void ld_coe_update(struct xgkr_params *xgkr)
+{
+	dev_dbg(&xgkr->phydev->mdio.dev, "sending request: %x\n", xgkr->ld_update);
+	xgkr_phy_write_mmd(xgkr, lt_MDIO_MMD,
+		      lt_KR_LD_CU, xgkr->ld_update);
+}
+
+static void start_xgkr_state_machine(struct delayed_work *work)
+{
+	queue_delayed_work(system_power_efficient_wq, work,
+			   msecs_to_jiffies(XGKR_TIMEOUT));
+}
+
+static void start_xgkr_an(struct xgkr_params *xgkr)
+{
+	struct phy_device *phydev = xgkr->phydev;
+	struct xgkr_phy_data *xgkr_inst = phydev->priv;
+	int i;
+	int err;
+
+	switch (xgkr_inst->bp_mode)
+	{
+	case PHY_BACKPLANE_1000BASE_KX:
+		dev_err(&phydev->mdio.dev, "Wrong call path for 1000Base-KX \n");
+		break;
+
+	case PHY_BACKPLANE_10GBASE_KR:
+		err = xgkr_phy_write_mmd(xgkr, MDIO_MMD_AN, g_an_AD1, KR_AN_AD1_INIT_10G);
+		if (err)
+			dev_err(&phydev->mdio.dev, "Setting AN register 0x%02x failed with error code: 0x%08x \n", g_an_AD1, err);
+		udelay(1);
+		err = xgkr_phy_write_mmd(xgkr, MDIO_MMD_AN, MDIO_CTRL1, AN_CTRL_INIT);
+		if (err)
+			dev_err(&phydev->mdio.dev, "Setting AN register 0x%02x failed with error code: 0x%08x \n", MDIO_CTRL1, err);
+		break;
+
+	case PHY_BACKPLANE_40GBASE_KR:
+		if (xgkr->idx == MASTER_LANE) {
+			for (i = 0; i < xgkr_inst->phy_lanes; i++) {
+				err = xgkr_phy_write_mmd(&xgkr_inst->xgkr[i], MDIO_MMD_AN, g_an_AD1, KR_AN_AD1_INIT_40G);
+				if (err)
+					dev_err(&phydev->mdio.dev, "Setting AN register 0x%02x on lane %d failed with error code: 0x%08x \n", g_an_AD1, xgkr_inst->xgkr[i].idx, err);
+			}
+			udelay(1);
+			err = xgkr_phy_write_mmd(xgkr, MDIO_MMD_AN, MDIO_CTRL1, AN_CTRL_INIT);
+			if (err)
+				dev_err(&phydev->mdio.dev, "Setting AN register 0x%02x on Master Lane failed with error code: 0x%08x \n", MDIO_CTRL1, err);
+		}
+		break;
+	}
+}
+
+static void start_1gkx_an(struct phy_device *phydev)
+{
+	phy_write_mmd(phydev, MDIO_MMD_PCS, KX_PCS_IF_MODE, KX_IF_MODE_INIT);
+	phy_write_mmd(phydev, MDIO_MMD_AN, g_an_AD1, KX_AN_AD1_INIT);
+	phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+	phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1, AN_CTRL_INIT);
+}
+
+static void reset_tecr(struct xgkr_params *xgkr)
+{
+	struct phy_device *phydev = xgkr->phydev;
+	struct xgkr_phy_data *xgkr_inst = phydev->priv;
+
+	switch (xgkr_inst->bp_mode)
+	{
+	case PHY_BACKPLANE_1000BASE_KX:
+		dev_err(&phydev->mdio.dev, "Wrong call path for 1000Base-KX \n");
+		break;
+
+	case PHY_BACKPLANE_10GBASE_KR:
+		xgkr->ratio_preq = RATIO_PREQ_10G;
+		xgkr->ratio_pst1q = RATIO_PST1Q_10G;
+		xgkr->adpt_eq = RATIO_EQ_10G;
+		break;
+
+	case PHY_BACKPLANE_40GBASE_KR:
+		xgkr->ratio_preq = RATIO_PREQ_40G;
+		xgkr->ratio_pst1q = RATIO_PST1Q_40G;
+		xgkr->adpt_eq = RATIO_EQ_40G;
+		break;
+	}
+
+	tune_tecr(xgkr);
+}
+
+static void init_xgkr(struct xgkr_params *xgkr, int reset)
+{
+	if (reset)
+		reset_tecr(xgkr);
+
+	tx_condition_init(&xgkr->tx_c);
+	xgkr->state = DETECTING_LP;
+
+	xgkr->ld_status &= RX_READY_MASK;
+	ld_coe_status(xgkr);
+	xgkr->ld_update = 0;
+	xgkr->ld_status &= ~RX_READY_MASK;
+	ld_coe_status(xgkr);
+
+}
+
+static void initialize(struct xgkr_params *xgkr)
+{
+	reset_tecr(xgkr);
+
+	xgkr->ld_status &= ~(COP1_MASK | COZ_MASK | COM1_MASK);
+	xgkr->ld_status |= COE_UPDATED << COP1_SHIFT |
+			   COE_UPDATED << COZ_SHIFT |
+			   COE_UPDATED << COM1_SHIFT;
+	ld_coe_status(xgkr);
+}
+
+static void train_remote_tx(struct xgkr_params *xgkr)
+{
+	struct tx_condition *tx_c = &xgkr->tx_c;
+	bool bin_m1_early, bin_long_early;
+	u32 lp_status, old_ld_update;
+	u32 status_cop1, status_coz, status_com1;
+	u32 req_cop1, req_coz, req_com1, req_preset, req_init;
+	u32 temp;
+#ifdef	NEW_ALGORITHM_TRAIN_TX
+	u32 median_gaink2;
+#endif
+
+recheck:
+	if (tx_c->bin_long_stop && tx_c->bin_m1_stop) {
+		tx_c->tx_complete = true;
+		xgkr->ld_status |= RX_READY_MASK;
+		ld_coe_status(xgkr);
+
+		/* tell LP we are ready */
+		xgkr_phy_write_mmd(xgkr, lt_MDIO_MMD,
+			      lt_KR_PMD_STATUS, RX_STAT);
+
+		return;
+	}
+
+	/* We start by checking the current LP status. If we got any responses,
+	 * we can clear up the appropriate update request so that the
+	 * subsequent code may easily issue new update requests if needed.
+	 */
+	lp_status = xgkr_phy_read_mmd(xgkr, lt_MDIO_MMD, lt_KR_LP_STATUS) &
+				 REQUEST_MASK;
+
+	status_cop1 = (lp_status & COP1_MASK) >> COP1_SHIFT;
+	status_coz = (lp_status & COZ_MASK) >> COZ_SHIFT;
+	status_com1 = (lp_status & COM1_MASK) >> COM1_SHIFT;
+
+	old_ld_update = xgkr->ld_update;
+	req_cop1 = (old_ld_update & COP1_MASK) >> COP1_SHIFT;
+	req_coz = (old_ld_update & COZ_MASK) >> COZ_SHIFT;
+	req_com1 = (old_ld_update & COM1_MASK) >> COM1_SHIFT;
+	req_preset = old_ld_update & PRESET_MASK;
+	req_init = old_ld_update & INIT_MASK;
+
+	/* IEEE802.3-2008, 72.6.10.2.3.1
+	 * We may clear PRESET when all coefficients show UPDATED or MAX.
+	 */
+	if (req_preset) {
+		if ((status_cop1 == COE_UPDATED || status_cop1 == COE_MAX) &&
+		    (status_coz == COE_UPDATED || status_coz == COE_MAX) &&
+		    (status_com1 == COE_UPDATED || status_com1 == COE_MAX)) {
+			xgkr->ld_update &= ~PRESET_MASK;
+		}
+	}
+
+	/* IEEE802.3-2008, 72.6.10.2.3.2
+	 * We may clear INITIALIZE when no coefficients show NOT UPDATED.
+	 */
+	if (req_init) {
+		if (status_cop1 != COE_NOTUPDATED &&
+		    status_coz != COE_NOTUPDATED &&
+		    status_com1 != COE_NOTUPDATED) {
+			xgkr->ld_update &= ~INIT_MASK;
+		}
+	}
+
+	/* IEEE802.3-2008, 72.6.10.2.3.2
+	 * we send initialize to the other side to ensure default settings
+	 * for the LP. Naturally, we should do this only once.
+	 */
+	if (!tx_c->sent_init) {
+		if (!lp_status && !(old_ld_update & (LD_ALL_MASK))) {
+			xgkr->ld_update = INIT_MASK;
+			tx_c->sent_init = true;
+		}
+	}
+
+	/* IEEE802.3-2008, 72.6.10.2.3.3
+	 * We set coefficient requests to HOLD when we get the information
+	 * about any updates On clearing our prior response, we also update
+	 * our internal status.
+	 */
+	if (status_cop1 != COE_NOTUPDATED) {
+		if (req_cop1) {
+			xgkr->ld_update &= ~COP1_MASK;
+#ifdef	NEW_ALGORITHM_TRAIN_TX
+			if (tx_c->post_inc) {
+				if (req_cop1 == INCREMENT &&
+				    status_cop1 == COE_MAX) {
+					tx_c->post_inc = 0;
+					tx_c->bin_long_stop = true;
+					tx_c->bin_m1_stop = true;
+				} else {
+					tx_c->post_inc -= 1;
+				}
+
+				ld_coe_update(xgkr);
+				goto recheck;
+			}
+#endif
+			if ((req_cop1 == DECREMENT && status_cop1 == COE_MIN) ||
+			    (req_cop1 == INCREMENT && status_cop1 == COE_MAX)) {
+				dev_dbg(&xgkr->phydev->mdio.dev, "COP1 hit limit %s",
+					(status_cop1 == COE_MIN) ?
+					"DEC MIN" : "INC MAX");
+				tx_c->long_min_max_cnt++;
+				if (tx_c->long_min_max_cnt >= TIMEOUT_LONG) {
+					tx_c->bin_long_stop = true;
+					ld_coe_update(xgkr);
+					goto recheck;
+				}
+			}
+		}
+	}
+
+	if (status_coz != COE_NOTUPDATED) {
+		if (req_coz)
+			xgkr->ld_update &= ~COZ_MASK;
+	}
+
+	if (status_com1 != COE_NOTUPDATED) {
+		if (req_com1) {
+			xgkr->ld_update &= ~COM1_MASK;
+#ifdef	NEW_ALGORITHM_TRAIN_TX
+			if (tx_c->pre_inc) {
+				if (req_com1 == INCREMENT &&
+				    status_com1 == COE_MAX)
+					tx_c->pre_inc = 0;
+				else
+					tx_c->pre_inc -= 1;
+
+				ld_coe_update(xgkr);
+				goto recheck;
+			}
+#endif
+			/* Stop If we have reached the limit for a parameter. */
+			if ((req_com1 == DECREMENT && status_com1 == COE_MIN) ||
+			    (req_com1 == INCREMENT && status_com1 == COE_MAX)) {
+				dev_dbg(&xgkr->phydev->mdio.dev, "COM1 hit limit %s",
+					(status_com1 == COE_MIN) ?
+					"DEC MIN" : "INC MAX");
+				tx_c->m1_min_max_cnt++;
+				if (tx_c->m1_min_max_cnt >= TIMEOUT_M1) {
+					tx_c->bin_m1_stop = true;
+					ld_coe_update(xgkr);
+					goto recheck;
+				}
+			}
+		}
+	}
+
+	if (old_ld_update != xgkr->ld_update) {
+		ld_coe_update(xgkr);
+		/* Redo these status checks and updates until we have no more
+		 * changes, to speed up the overall process.
+		 */
+		goto recheck;
+	}
+
+	/* Do nothing if we have pending request. */
+	if ((req_coz || req_com1 || req_cop1))
+		return;
+	else if (lp_status)
+		/* No pending request but LP status was not reverted to
+		 * not updated.
+		 */
+		return;
+
+#ifdef	NEW_ALGORITHM_TRAIN_TX
+	if (!(xgkr->ld_update & (PRESET_MASK | INIT_MASK))) {
+		if (tx_c->pre_inc) {
+			xgkr->ld_update = INCREMENT << COM1_SHIFT;
+			ld_coe_update(xgkr);
+			return;
+		}
+
+		if (status_cop1 != COE_MAX) {
+			median_gaink2 = xgkr->srds->get_median_gaink2(xgkr->reg_base);
+			if (median_gaink2 == 0xf) {
+				tx_c->post_inc = 1;
+			} else {
+				/* Gaink2 median lower than "F" */
+				tx_c->bin_m1_stop = true;
+				tx_c->bin_long_stop = true;
+				goto recheck;
+			}
+		} else {
+			/* C1 MAX */
+			tx_c->bin_m1_stop = true;
+			tx_c->bin_long_stop = true;
+			goto recheck;
+		}
+
+		if (tx_c->post_inc) {
+			xgkr->ld_update = INCREMENT << COP1_SHIFT;
+			ld_coe_update(xgkr);
+			return;
+		}
+	}
+#endif
+
+	/* snapshot and select bin */
+	bin_m1_early = xgkr->srds->is_bin_early(BIN_M1, xgkr->reg_base);
+	bin_long_early = xgkr->srds->is_bin_early(BIN_LONG, xgkr->reg_base);
+
+	if (!tx_c->bin_m1_stop && !tx_c->bin_m1_late_early && bin_m1_early) {
+		tx_c->bin_m1_stop = true;
+		goto recheck;
+	}
+
+	if (!tx_c->bin_long_stop &&
+	    tx_c->bin_long_late_early && !bin_long_early) {
+		tx_c->bin_long_stop = true;
+		goto recheck;
+	}
+
+	/* IEEE802.3-2008, 72.6.10.2.3.3
+	 * We only request coefficient updates when no PRESET/INITIALIZE is
+	 * pending. We also only request coefficient updates when the
+	 * corresponding status is NOT UPDATED and nothing is pending.
+	 */
+	if (!(xgkr->ld_update & (PRESET_MASK | INIT_MASK))) {
+		if (!tx_c->bin_long_stop) {
+			/* BinM1 correction means changing COM1 */
+			if (!status_com1 && !(xgkr->ld_update & COM1_MASK)) {
+				/* Avoid BinM1Late by requesting an
+				 * immediate decrement.
+				 */
+				if (!bin_m1_early) {
+					/* request decrement c(-1) */
+					temp = DECREMENT << COM1_SHIFT;
+					xgkr->ld_update = temp;
+					ld_coe_update(xgkr);
+					tx_c->bin_m1_late_early = bin_m1_early;
+					return;
+				}
+			}
+
+			/* BinLong correction means changing COP1 */
+			if (!status_cop1 && !(xgkr->ld_update & COP1_MASK)) {
+				/* Locate BinLong transition point (if any)
+				 * while avoiding BinM1Late.
+				 */
+				if (bin_long_early) {
+					/* request increment c(1) */
+					temp = INCREMENT << COP1_SHIFT;
+					xgkr->ld_update = temp;
+				} else {
+					/* request decrement c(1) */
+					temp = DECREMENT << COP1_SHIFT;
+					xgkr->ld_update = temp;
+				}
+
+				ld_coe_update(xgkr);
+				tx_c->bin_long_late_early = bin_long_early;
+			}
+			/* We try to finish BinLong before we do BinM1 */
+			return;
+		}
+
+		if (!tx_c->bin_m1_stop) {
+			/* BinM1 correction means changing COM1 */
+			if (!status_com1 && !(xgkr->ld_update & COM1_MASK)) {
+				/* Locate BinM1 transition point (if any) */
+				if (bin_m1_early) {
+					/* request increment c(-1) */
+					temp = INCREMENT << COM1_SHIFT;
+					xgkr->ld_update = temp;
+				} else {
+					/* request decrement c(-1) */
+					temp = DECREMENT << COM1_SHIFT;
+					xgkr->ld_update = temp;
+				}
+
+				ld_coe_update(xgkr);
+				tx_c->bin_m1_late_early = bin_m1_early;
+			}
+		}
+	}
+}
+
+static int is_link_up(struct phy_device *phydev)
+{
+	struct xgkr_phy_data *xgkr_inst = phydev->priv;
+	int val = 0;
+	
+	mutex_lock(&xgkr_inst->phy_lock);
+
+	val = phy_read_mmd(phydev, MDIO_MMD_PCS, XFI_PCS_SR1);
+
+	mutex_unlock(&xgkr_inst->phy_lock);
+
+	return (val & KR_RX_LINK_STAT_MASK) ? 1 : 0;
+}
+
+static int is_link_training_fail(struct xgkr_params *xgkr)
+{
+	struct phy_device *phydev = xgkr->phydev;
+	int val;
+	int timeout = 100;
+
+	val = xgkr_phy_read_mmd(xgkr, lt_MDIO_MMD, lt_KR_PMD_STATUS);
+
+	if (!(val & TRAIN_FAIL) && (val & RX_STAT)) {
+		/* check LNK_STAT for sure */
+		while (timeout--) {
+			if (is_link_up(phydev))
+				return 0;
+
+			usleep_range(100, 500);
+		}
+	}
+
+	return 1;
+}
+
+static int check_rx(struct xgkr_params *xgkr)
+{
+	return xgkr_phy_read_mmd(xgkr, lt_MDIO_MMD, lt_KR_LP_STATUS) &
+			    RX_READY_MASK;
+}
+
+/* Coefficient values have hardware restrictions */
+static int is_ld_valid(struct xgkr_params *xgkr)
+{
+	u32 ratio_pst1q = xgkr->ratio_pst1q;
+	u32 adpt_eq = xgkr->adpt_eq;
+	u32 ratio_preq = xgkr->ratio_preq;
+
+	if ((ratio_pst1q + adpt_eq + ratio_preq) > 48)
+		return 0;
+
+	if (((ratio_pst1q + adpt_eq + ratio_preq) * 4) >=
+	    ((adpt_eq - ratio_pst1q - ratio_preq) * 17))
+		return 0;
+
+	if (ratio_preq > ratio_pst1q)
+		return 0;
+
+	if (ratio_preq > 8)
+		return 0;
+
+	if (adpt_eq < 26)
+		return 0;
+
+	if (ratio_pst1q > 16)
+		return 0;
+
+	return 1;
+}
+
+static int is_value_allowed(const u32 *val_table, u32 val)
+{
+	int i;
+
+	for (i = 0;; i++) {
+		if (*(val_table + i) == VAL_INVALID)
+			return 0;
+		if (*(val_table + i) == val)
+			return 1;
+	}
+}
+
+static enum coe_update inc_dec(struct xgkr_params *xgkr, int field, int request)
+{
+	u32 ld_limit[3], ld_coe[3], step[3];
+
+	ld_coe[0] = xgkr->ratio_pst1q;
+	ld_coe[1] = xgkr->adpt_eq;
+	ld_coe[2] = xgkr->ratio_preq;
+
+	/* Information specific to the SerDes for 10GBase-KR:
+	 * Incrementing C(+1) means *decrementing* RATIO_PST1Q
+	 * Incrementing C(0) means incrementing ADPT_EQ
+	 * Incrementing C(-1) means *decrementing* RATIO_PREQ
+	 */
+	step[0] = -1;
+	step[1] = 1;
+	step[2] = -1;
+
+	switch (request) {
+	case INCREMENT:
+		ld_limit[0] = POST_COE_MAX;
+		ld_limit[1] = ZERO_COE_MAX;
+		ld_limit[2] = PRE_COE_MAX;
+		if (ld_coe[field] != ld_limit[field])
+			ld_coe[field] += step[field];
+		else
+			/* MAX */
+			return COE_MAX;
+		break;
+	case DECREMENT:
+		ld_limit[0] = POST_COE_MIN;
+		ld_limit[1] = ZERO_COE_MIN;
+		ld_limit[2] = PRE_COE_MIN;
+		if (ld_coe[field] != ld_limit[field])
+			ld_coe[field] -= step[field];
+		else
+			/* MIN */
+			return COE_MIN;
+		break;
+	default:
+		break;
+	}
+
+	if (is_ld_valid(xgkr)) {
+		/* accept new ld */
+		xgkr->ratio_pst1q = ld_coe[0];
+		xgkr->adpt_eq = ld_coe[1];
+		xgkr->ratio_preq = ld_coe[2];
+		/* only some values for preq and pst1q can be used.
+		 * for preq: 0x0, 0x1, 0x3, 0x5, 0x7, 0x9, 0xb, 0xc.
+		 * for pst1q: 0x0, 0x1, 0x3, 0x5, 0x7, 0x9, 0xb, 0xd, 0xf, 0x10.
+		 */
+		if (!is_value_allowed((const u32 *)&preq_table, ld_coe[2])) {
+			dev_dbg(&xgkr->phydev->mdio.dev,
+				"preq skipped value: %d\n", ld_coe[2]);
+			/* NOT UPDATED */
+			return COE_NOTUPDATED;
+		}
+
+		if (!is_value_allowed((const u32 *)&pst1q_table, ld_coe[0])) {
+			dev_dbg(&xgkr->phydev->mdio.dev,
+				"pst1q skipped value: %d\n", ld_coe[0]);
+			/* NOT UPDATED */
+			return COE_NOTUPDATED;
+		}
+
+		tune_tecr(xgkr);
+	} else {
+		if (request == DECREMENT)
+			/* MIN */
+			return COE_MIN;
+		if (request == INCREMENT)
+			/* MAX */
+			return COE_MAX;
+	}
+
+	/* UPDATED */
+	return COE_UPDATED;
+}
+
+static void min_max_updated(struct xgkr_params *xgkr, int field, enum coe_update cs)
+{
+	u32 mask, val;
+	u32 ld_cs = cs;
+
+	if (cs == COE_INV)
+		return;
+
+	switch (field) {
+	case COE_COP1:
+		mask = COP1_MASK;
+		val = ld_cs << COP1_SHIFT;
+		break;
+	case COE_COZ:
+		mask = COZ_MASK;
+		val = ld_cs << COZ_SHIFT;
+		break;
+	case COE_COM:
+		mask = COM1_MASK;
+		val = ld_cs << COM1_SHIFT;
+		break;
+	default:
+		return;
+	}
+
+	xgkr->ld_status &= ~mask;
+	xgkr->ld_status |= val;
+}
+
+static void check_request(struct xgkr_params *xgkr, int request)
+{
+	int cop1_req, coz_req, com_req;
+	int old_status;
+	enum coe_update cu;
+
+	cop1_req = (request & COP1_MASK) >> COP1_SHIFT;
+	coz_req = (request & COZ_MASK) >> COZ_SHIFT;
+	com_req = (request & COM1_MASK) >> COM1_SHIFT;
+
+	/* IEEE802.3-2008, 72.6.10.2.5
+	 * Ensure we only act on INCREMENT/DECREMENT when we are in NOT UPDATED
+	 */
+	old_status = xgkr->ld_status;
+
+	if (cop1_req && !(xgkr->ld_status & COP1_MASK)) {
+		cu = inc_dec(xgkr, COE_COP1, cop1_req);
+		min_max_updated(xgkr, COE_COP1, cu);
+	}
+
+	if (coz_req && !(xgkr->ld_status & COZ_MASK)) {
+		cu = inc_dec(xgkr, COE_COZ, coz_req);
+		min_max_updated(xgkr, COE_COZ, cu);
+	}
+
+	if (com_req && !(xgkr->ld_status & COM1_MASK)) {
+		cu = inc_dec(xgkr, COE_COM, com_req);
+		min_max_updated(xgkr, COE_COM, cu);
+	}
+
+	if (old_status != xgkr->ld_status)
+		ld_coe_status(xgkr);
+}
+
+static void preset(struct xgkr_params *xgkr)
+{
+	/* These are all MAX values from the IEEE802.3 perspective. */
+	xgkr->ratio_pst1q = POST_COE_MAX;
+	xgkr->adpt_eq = ZERO_COE_MAX;
+	xgkr->ratio_preq = PRE_COE_MAX;
+
+	tune_tecr(xgkr);
+	xgkr->ld_status &= ~(COP1_MASK | COZ_MASK | COM1_MASK);
+	xgkr->ld_status |= COE_MAX << COP1_SHIFT |
+			   COE_MAX << COZ_SHIFT |
+			   COE_MAX << COM1_SHIFT;
+	ld_coe_status(xgkr);
+}
+
+static void train_local_tx(struct xgkr_params *xgkr)
+{
+	int request, old_ld_status;
+
+	/* get request from LP */
+	request = xgkr_phy_read_mmd(xgkr, lt_MDIO_MMD, lt_KR_LP_CU) &
+			      (LD_ALL_MASK);
+
+	old_ld_status = xgkr->ld_status;
+
+	/* IEEE802.3-2008, 72.6.10.2.5
+	 * Ensure we always go to NOT UDPATED for status reporting in
+	 * response to HOLD requests.
+	 * IEEE802.3-2008, 72.6.10.2.3.1/2
+	 * ... but only if PRESET/INITIALIZE are not active to ensure
+	 * we keep status until they are released.
+	 */
+	if (!(request & (PRESET_MASK | INIT_MASK))) {
+		if (!(request & COP1_MASK))
+			xgkr->ld_status &= ~COP1_MASK;
+
+		if (!(request & COZ_MASK))
+			xgkr->ld_status &= ~COZ_MASK;
+
+		if (!(request & COM1_MASK))
+			xgkr->ld_status &= ~COM1_MASK;
+
+		if (old_ld_status != xgkr->ld_status)
+			ld_coe_status(xgkr);
+	}
+
+	/* As soon as the LP shows ready, no need to do any more updates. */
+	if (check_rx(xgkr)) {
+		/* LP receiver is ready */
+		if (xgkr->ld_status & (COP1_MASK | COZ_MASK | COM1_MASK)) {
+			xgkr->ld_status &= ~(COP1_MASK | COZ_MASK | COM1_MASK);
+			ld_coe_status(xgkr);
+		}
+	} else {
+		/* IEEE802.3-2008, 72.6.10.2.3.1/2
+		 * only act on PRESET/INITIALIZE if all status is NOT UPDATED.
+		 */
+		if (request & (PRESET_MASK | INIT_MASK)) {
+			if (!(xgkr->ld_status &
+			      (COP1_MASK | COZ_MASK | COM1_MASK))) {
+				if (request & PRESET_MASK)
+					preset(xgkr);
+
+				if (request & INIT_MASK)
+					initialize(xgkr);
+			}
+		}
+
+		/* LP Coefficient are not in HOLD */
+		if (request & REQUEST_MASK)
+			check_request(xgkr, request & REQUEST_MASK);
+	}
+}
+
+static void xgkr_start_train(struct xgkr_params *xgkr)
+{
+	struct phy_device *phydev = xgkr->phydev;
+	struct xgkr_phy_data *xgkr_inst = phydev->priv;
+	struct tx_condition *tx_c = &xgkr->tx_c;
+	int val = 0, i, j;
+	int lt_state;
+	unsigned long dead_line;
+	int lp_rx_ready, tx_training_complete;
+	u32 lt_timeout = 500;
+
+	init_xgkr(xgkr, 0);
+	
+	start_lt(xgkr);
+	
+	if (xgkr_inst->bp_mode == PHY_BACKPLANE_40GBASE_KR) {
+		lt_timeout = 2000;
+	}
+
+	for (i = 0; i < 2;) {
+		
+		dead_line = jiffies + msecs_to_jiffies(lt_timeout);
+		
+		while (time_before(jiffies, dead_line)) {
+
+			val = xgkr_phy_read_mmd(xgkr, lt_MDIO_MMD,
+					   lt_KR_PMD_STATUS);
+
+			if (val & TRAIN_FAIL) {
+				/* LT failed already, reset lane to avoid
+				 * it run into hanging, then start LT again.
+				 */
+				if (xgkr_inst->bp_mode == PHY_BACKPLANE_40GBASE_KR) {
+					/* Reset only the Master Lane */
+					if (xgkr->idx == MASTER_LANE)
+						xgkr->srds->reset_lane(xgkr->reg_base);
+				} else {
+					xgkr->srds->reset_lane(xgkr->reg_base);
+				}
+				
+				start_lt(xgkr);
+			} else if ((val & PMD_STATUS_SUP_STAT) &&
+				   (val & PMD_STATUS_FRAME_LOCK))
+				break;
+			usleep_range(100, 500);
+		}
+
+		if (!((val & PMD_STATUS_FRAME_LOCK) &&
+		      (val & PMD_STATUS_SUP_STAT))) {
+			i++;
+			continue;
+		}
+
+		/* init process */
+		lp_rx_ready = false;
+		tx_training_complete = false;
+		/* the LT should be finished in 500ms, failed or OK. */
+		dead_line = jiffies + msecs_to_jiffies(lt_timeout);
+
+		while (time_before(jiffies, dead_line)) {
+			/* check if the LT is already failed */
+
+			lt_state = xgkr_phy_read_mmd(xgkr, lt_MDIO_MMD,
+						lt_KR_PMD_STATUS);
+
+			if (lt_state & TRAIN_FAIL) {
+				
+				if (xgkr_inst->bp_mode == PHY_BACKPLANE_40GBASE_KR) {
+					/* Reset only the Master Lane */
+					if (xgkr->idx == MASTER_LANE)
+						xgkr->srds->reset_lane(xgkr->reg_base);
+				} else {
+					xgkr->srds->reset_lane(xgkr->reg_base);
+				}
+				
+				break;
+			}
+
+			lp_rx_ready = check_rx(xgkr);
+			tx_training_complete = tx_c->tx_complete;
+
+			if (lp_rx_ready && tx_training_complete)
+				break;
+
+			if (!lp_rx_ready)
+				train_local_tx(xgkr);
+
+			if (!tx_training_complete)
+				train_remote_tx(xgkr);
+
+			usleep_range(100, 500);
+		}
+
+		i++;
+		/* check LT result */
+		if (is_link_training_fail(xgkr)) {
+			init_xgkr(xgkr, 0);
+			continue;
+		} else {
+			stop_lt(xgkr);
+			xgkr->state = TRAINED;
+			
+			switch (xgkr_inst->bp_mode)
+			{
+			case PHY_BACKPLANE_10GBASE_KR:
+				if (phydev->attached_dev == NULL)
+					dev_info(&phydev->mdio.dev, "10GBase-KR link trained (Tx equalization: RATIO_PREQ = 0x%x, RATIO_PST1Q = 0x%x, ADPT_EQ = 0x%x)\n",
+							xgkr->tuned_ratio_preq, xgkr->tuned_ratio_pst1q, xgkr->tuned_adpt_eq);
+				else
+					dev_info(&phydev->mdio.dev, "%s %s: 10GBase-KR link trained (Tx equalization: RATIO_PREQ = 0x%x, RATIO_PST1Q = 0x%x, ADPT_EQ = 0x%x)\n",
+							dev_driver_string(phydev->attached_dev->dev.parent), 
+							dev_name(phydev->attached_dev->dev.parent),
+							xgkr->tuned_ratio_preq, xgkr->tuned_ratio_pst1q, xgkr->tuned_adpt_eq);
+				break;
+				
+			case PHY_BACKPLANE_40GBASE_KR:
+				if (xgkr->idx == xgkr_inst->phy_lanes - 1) {
+					if (phydev->attached_dev == NULL)
+						dev_info(&phydev->mdio.dev, "40GBase-KR link trained at lanes Tx equalization:\n");
+					else
+						dev_info(&phydev->mdio.dev, "%s %s: 40GBase-KR link trained at lanes Tx equalization:\n",
+								dev_driver_string(phydev->attached_dev->dev.parent), 
+								dev_name(phydev->attached_dev->dev.parent));
+
+					for (j = 0; j < xgkr_inst->phy_lanes; j++) {
+						if (phydev->attached_dev == NULL)
+							dev_info(&phydev->mdio.dev, "40GBase-KR Lane %d: RATIO_PREQ = 0x%x, RATIO_PST1Q = 0x%x, ADPT_EQ = 0x%x\n",
+									j, xgkr_inst->xgkr[j].tuned_ratio_preq, xgkr_inst->xgkr[j].tuned_ratio_pst1q, xgkr_inst->xgkr[j].tuned_adpt_eq);
+						else
+							dev_info(&phydev->mdio.dev, "%s %s: 40GBase-KR Lane %d: RATIO_PREQ = 0x%x, RATIO_PST1Q = 0x%x, ADPT_EQ = 0x%x\n",
+									dev_driver_string(phydev->attached_dev->dev.parent),
+									dev_name(phydev->attached_dev->dev.parent),
+									j, xgkr_inst->xgkr[j].tuned_ratio_preq, xgkr_inst->xgkr[j].tuned_ratio_pst1q, xgkr_inst->xgkr[j].tuned_adpt_eq);
+					}
+				}
+				break;
+			}
+
+			break;
+		}
+	}
+}
+
+static void xgkr_request_restart_an(struct xgkr_params *xgkr)
+{
+	struct phy_device *phydev = xgkr->phydev;
+	struct xgkr_phy_data *xgkr_inst = phydev->priv;
+	int i;
+
+	if (time_before(jiffies, xgkr->rt_time))
+		return;
+	
+	switch (xgkr_inst->bp_mode)
+	{
+	case PHY_BACKPLANE_1000BASE_KX:
+		dev_err(&phydev->mdio.dev, "Wrong call path for 1000Base-KX \n");
+		break;
+
+	case PHY_BACKPLANE_10GBASE_KR:
+		init_xgkr(xgkr, 0);  
+		reset_lt(xgkr);
+		xgkr->state = DETECTING_LP;
+		start_xgkr_an(xgkr);
+		start_xgkr_state_machine(&xgkr->xgkr_wk);
+		break;
+
+	case PHY_BACKPLANE_40GBASE_KR:
+		for (i = 0; i < xgkr_inst->phy_lanes; i++) {
+			init_xgkr(&xgkr_inst->xgkr[i], 0);
+			reset_lt(&xgkr_inst->xgkr[i]);
+			xgkr_inst->xgkr[i].state = DETECTING_LP;
+		}
+		//Start AN only for Master Lane
+		start_xgkr_an(&xgkr_inst->xgkr[MASTER_LANE]);
+		//start state machine
+		for (i = 0; i < xgkr_inst->phy_lanes; i++) {
+			start_xgkr_state_machine(&xgkr_inst->xgkr[i].xgkr_wk);
+		}
+		break;
+	}
+	
+	xgkr->rt_time = jiffies + msecs_to_jiffies(XGKR_DENY_RT_INTERVAL);
+}
+
+static void xgkr_state_machine(struct work_struct *work)
+{
+	struct delayed_work *dwork = to_delayed_work(work);
+	struct xgkr_params *xgkr = container_of(dwork,
+						  struct xgkr_params, xgkr_wk);
+	struct phy_device *phydev = xgkr->phydev;
+	struct xgkr_phy_data *xgkr_inst = phydev->priv;
+	int an_state;
+	bool start_train = false;
+	bool all_lanes_trained = false;
+	int i;
+
+	if (!xgkr_inst->aneg_done) {
+		start_xgkr_state_machine(&xgkr->xgkr_wk);
+		return;
+	}
+
+	mutex_lock(&phydev->lock);
+	
+	switch (xgkr->state) {
+	case DETECTING_LP:
+
+		switch (xgkr_inst->bp_mode)
+		{
+		case PHY_BACKPLANE_1000BASE_KX:
+			dev_err(&phydev->mdio.dev, "Wrong call path for 1000Base-KX \n");
+			break;
+
+		case PHY_BACKPLANE_10GBASE_KR:
+			an_state = xgkr_phy_read_mmd(xgkr, MDIO_MMD_AN, g_an_BP_STAT);
+			if (an_state & KR_AN_MASK_10G) {
+				//AN acquired: Train the lane
+				xgkr->an_wait_count = 0;
+				start_train = true;
+			} else {
+				//AN lost or not yet acquired
+				if (!is_link_up(phydev)) {
+					//Link is down: restart training
+					xgkr->an_wait_count = 0;
+					xgkr_request_restart_an(xgkr);
+				} else {
+					//Link is up: wait few iterations for AN to be acquired
+					if (xgkr->an_wait_count >= XGKR_AN_WAIT_ITERATIONS) {
+						xgkr->an_wait_count = 0;
+						xgkr_request_restart_an(xgkr);
+					} else {
+						xgkr->an_wait_count++;
+					}
+				}
+			}
+			break;
+
+		case PHY_BACKPLANE_40GBASE_KR:
+			//Check AN state only on Master Lane
+			an_state = xgkr_phy_read_mmd(&xgkr_inst->xgkr[MASTER_LANE], MDIO_MMD_AN, g_an_BP_STAT);
+			if (an_state & KR_AN_MASK_40G) {
+				//AN acquired: Train all lanes in order starting with Master Lane
+				xgkr->an_wait_count = 0;
+				if (xgkr->idx == MASTER_LANE) {
+					start_train = true;
+				}
+				else if (xgkr_inst->xgkr[xgkr->idx - 1].state == TRAINED) {
+					start_train = true;
+				}
+			} else {
+				//AN lost or not yet acquired
+				if (!is_link_up(phydev)) {
+					//Link is down: restart training
+					xgkr->an_wait_count = 0;
+					xgkr_request_restart_an(xgkr);
+				} else {
+					//Link is up: wait few iterations for AN to be acquired
+					if (xgkr->an_wait_count >= XGKR_AN_WAIT_ITERATIONS) {
+						xgkr->an_wait_count = 0;
+						xgkr_request_restart_an(xgkr);
+					} else {
+						xgkr->an_wait_count++;
+					}
+				}
+			}
+			break;
+		}
+		break;
+
+	case TRAINED:
+		if (!is_link_up(phydev)) {
+			switch (xgkr_inst->bp_mode)
+			{
+			case PHY_BACKPLANE_1000BASE_KX:
+				dev_err(&phydev->mdio.dev, "Wrong call path for 1000Base-KX \n");
+				break;
+
+			case PHY_BACKPLANE_10GBASE_KR:
+				dev_info(&phydev->mdio.dev, "Detect hotplug, restart training\n");
+				xgkr_request_restart_an(xgkr);
+				break;
+
+			case PHY_BACKPLANE_40GBASE_KR:
+				if (xgkr->idx == MASTER_LANE) {
+					//check if all lanes are trained only on Master Lane
+					all_lanes_trained = true;
+					for (i = 0; i < xgkr_inst->phy_lanes; i++) {
+						if (xgkr_inst->xgkr[i].state != TRAINED) {
+							all_lanes_trained = false;
+							break;
+						}
+					}
+					if (all_lanes_trained) {
+						dev_info(&phydev->mdio.dev, "Detect hotplug, restart training\n");
+						xgkr_request_restart_an(xgkr);
+					}
+				}
+				break;
+			}
+		}
+		break;
+	}
+
+	if (start_train) {
+		xgkr_start_train(xgkr);
+	}
+
+	mutex_unlock(&phydev->lock);
+	start_xgkr_state_machine(&xgkr->xgkr_wk);
+}
+
+static int fsl_backplane_probe(struct phy_device *phydev)
+{
+	struct xgkr_phy_data *xgkr_inst;
+	struct device_node *phy_node, *lane_node;
+	struct resource res_lane;
+	struct serdes_access *srds = NULL;
+	int serdes_type;
+	const char *st;
+	const char *bm;
+	int ret, i, phy_lanes;
+	int bp_mode;
+	u32 lane_base_addr[MAX_PHY_LANES_NO], lane_memmap_size;
+
+	phy_node = phydev->mdio.dev.of_node;
+	if (!phy_node) {
+		dev_err(&phydev->mdio.dev, "No associated device tree node\n");
+		return -EINVAL;
+	}
+
+	bp_mode = of_property_read_string(phy_node, "backplane-mode", &bm);
+	if (bp_mode < 0)
+		return -EINVAL;
+
+	phy_lanes = 1;
+	if (!strcasecmp(bm, "1000base-kx")) {
+		bp_mode = PHY_BACKPLANE_1000BASE_KX;
+	} else if (!strcasecmp(bm, "10gbase-kr")) {
+		bp_mode = PHY_BACKPLANE_10GBASE_KR;
+	} else if (!strcasecmp(bm, "40gbase-kr")) {
+		bp_mode = PHY_BACKPLANE_40GBASE_KR;
+		phy_lanes = 4;
+	} else {
+		dev_err(&phydev->mdio.dev, "Unknown backplane-mode\n");
+		return -EINVAL;
+	}
+
+	lane_node = of_parse_phandle(phy_node, "fsl,lane-handle", 0);
+	if (!lane_node) {
+		dev_err(&phydev->mdio.dev, "parse fsl,lane-handle failed\n");
+		return -EINVAL;
+	}
+
+	ret = of_property_read_string(lane_node, "compatible", &st);
+	if (ret < 0) {
+		//assume SERDES-10G if compatible property is not specified
+		serdes_type = SERDES_10G;
+	}
+	else if (!strcasecmp(st, "fsl,serdes-10g")) {
+		serdes_type = SERDES_10G;
+	} else if (!strcasecmp(st, "fsl,serdes-28g")) {
+		serdes_type = SERDES_28G;
+	} else {
+		dev_err(&phydev->mdio.dev, "Unknown serdes-type\n");
+		return -EINVAL;
+	}
+
+	ret = of_address_to_resource(lane_node, 0, &res_lane);
+	if (ret) {
+		dev_err(&phydev->mdio.dev, "could not obtain memory map\n");
+		return ret;
+	}
+
+	of_node_put(lane_node);
+	ret = of_property_read_u32_array(phy_node, "fsl,lane-reg",
+					 (u32 *)lane_base_addr, phy_lanes);
+	if (ret) {
+		dev_err(&phydev->mdio.dev, "could not get fsl,lane-reg\n");
+		return -EINVAL;
+	}
+
+	switch (serdes_type)
+	{
+	case SERDES_10G:
+		setup_an_lt_ls();
+		srds = setup_serdes_access_10g();
+		break;
+
+	case SERDES_28G:
+		setup_an_lt_lx();
+		srds = setup_serdes_access_28g();
+		break;
+
+	default:
+		dev_err(&phydev->mdio.dev, "Unsupported serdes-type\n");
+		return -EINVAL;
+	}
+
+	if (!srds) {
+		dev_err(&phydev->mdio.dev, "Unsupported serdes-type\n");
+		return -EINVAL;
+	}
+
+	srds->serdes_type = serdes_type;
+	srds->is_little_endian = of_property_read_bool(lane_node, "little-endian");
+
+	if (srds->is_little_endian) {
+		srds->ioread32 = le_ioread32;
+		srds->iowrite32 = le_iowrite32;
+	} else {
+		srds->ioread32 = be_ioread32;
+		srds->iowrite32 = be_iowrite32;
+	}
+
+	xgkr_inst = devm_kzalloc(&phydev->mdio.dev,
+				 sizeof(*xgkr_inst), GFP_KERNEL);
+	if (!xgkr_inst)
+		return -ENOMEM;
+
+	xgkr_inst->phy_lanes = phy_lanes;
+	xgkr_inst->bp_mode = bp_mode;
+	mutex_init(&xgkr_inst->phy_lock);
+
+	lane_memmap_size = srds->get_lane_memmap_size();
+	
+	for (i = 0; i < phy_lanes; i++) {
+		xgkr_inst->xgkr[i].idx = i;
+		xgkr_inst->xgkr[i].phydev = phydev;
+		xgkr_inst->xgkr[i].srds = srds;
+		xgkr_inst->xgkr[i].reg_base = devm_ioremap_nocache(&phydev->mdio.dev,
+						    res_lane.start + lane_base_addr[i],
+						    lane_memmap_size);
+		if (!xgkr_inst->xgkr[i].reg_base) {
+			dev_err(&phydev->mdio.dev, "ioremap_nocache failed\n");
+			return -ENOMEM;
+		}
+		xgkr_inst->xgkr[i].rt_time = jiffies + msecs_to_jiffies(XGKR_DENY_RT_INTERVAL);
+	}
+
+	phydev->priv = xgkr_inst;
+
+	switch (bp_mode)
+	{
+	case PHY_BACKPLANE_1000BASE_KX:
+		phydev->speed = SPEED_1000;
+		/* configure the lane for 1000BASE-KX */
+		srds->lane_set_1gkx(xgkr_inst->xgkr[SINGLE_LANE].reg_base);
+		break;
+
+	case PHY_BACKPLANE_10GBASE_KR:
+		phydev->speed = SPEED_10000;
+		INIT_DELAYED_WORK(&xgkr_inst->xgkr[SINGLE_LANE].xgkr_wk, xgkr_state_machine);
+		break;
+
+	case PHY_BACKPLANE_40GBASE_KR:
+		phydev->speed = SPEED_40000;
+		for (i = 0; i < phy_lanes; i++)
+			INIT_DELAYED_WORK(&xgkr_inst->xgkr[i].xgkr_wk, xgkr_state_machine);
+		break;
+	}
+
+	return 0;
+}
+
+static int fsl_backplane_aneg_done(struct phy_device *phydev)
+{
+	struct xgkr_phy_data *xgkr_inst = phydev->priv;
+
+	if (!phydev->mdio.dev.of_node) {
+		dev_err(&phydev->mdio.dev, "No associated device tree node\n");
+		return -EINVAL;
+	}
+	
+	xgkr_inst->aneg_done = true;
+
+	return 1;
+}
+
+static int fsl_backplane_config_aneg(struct phy_device *phydev)
+{
+	struct xgkr_phy_data *xgkr_inst = phydev->priv;
+	int i;
+
+	if (!phydev->mdio.dev.of_node) {
+		dev_err(&phydev->mdio.dev, "No associated device tree node\n");
+		return -EINVAL;
+	}
+
+	switch (phydev->speed)
+	{
+	case SPEED_1000:
+		phydev->supported |= SUPPORTED_1000baseKX_Full;
+		start_1gkx_an(phydev);
+		break;
+
+	case SPEED_10000:
+		phydev->supported |= SUPPORTED_10000baseKR_Full;
+		reset_lt(&xgkr_inst->xgkr[SINGLE_LANE]);
+		start_xgkr_an(&xgkr_inst->xgkr[SINGLE_LANE]);
+		/* start state machine*/
+		start_xgkr_state_machine(&xgkr_inst->xgkr[SINGLE_LANE].xgkr_wk);
+		break;
+
+	case SPEED_40000:
+		phydev->supported |= SUPPORTED_40000baseKR4_Full;
+		for (i = 0; i < xgkr_inst->phy_lanes; i++) {
+			reset_lt(&xgkr_inst->xgkr[i]);
+		}
+		//Start AN only for Master Lane
+		start_xgkr_an(&xgkr_inst->xgkr[MASTER_LANE]);
+		/* start state machine*/
+		for (i = 0; i < xgkr_inst->phy_lanes; i++) {
+			start_xgkr_state_machine(&xgkr_inst->xgkr[i].xgkr_wk);
+		}
+		
+		break;
+	}
+
+	phydev->advertising = phydev->supported;
+	phydev->duplex = 1;
+
+	return 0;
+}
+
+static int fsl_backplane_suspend(struct phy_device *phydev)
+{
+	int i;
+
+	if (!phydev->mdio.dev.of_node) {
+		dev_err(&phydev->mdio.dev, "No associated device tree node\n");
+		return -EINVAL;
+	}
+
+	if (phydev->speed == SPEED_10000 || phydev->speed == SPEED_40000) {
+		struct xgkr_phy_data *xgkr_inst = phydev->priv;
+
+		for (i = 0; i < xgkr_inst->phy_lanes; i++)
+			cancel_delayed_work_sync(&xgkr_inst->xgkr[i].xgkr_wk);
+	}
+	return 0;
+}
+
+static int fsl_backplane_resume(struct phy_device *phydev)
+{
+	struct xgkr_phy_data *xgkr_inst = phydev->priv;
+	int i;
+
+	if (!phydev->mdio.dev.of_node) {
+		dev_err(&phydev->mdio.dev, "No associated device tree node\n");
+		return -EINVAL;
+	}
+
+	if (phydev->speed == SPEED_10000 || phydev->speed == SPEED_40000) {
+		for (i = 0; i < xgkr_inst->phy_lanes; i++) {
+			init_xgkr(&xgkr_inst->xgkr[i], 1);
+			start_xgkr_state_machine(&xgkr_inst->xgkr[i].xgkr_wk);
+		}
+	}
+	return 0;
+}
+
+static int fsl_backplane_read_status(struct phy_device *phydev)
+{
+	if (!phydev->mdio.dev.of_node) {
+		dev_err(&phydev->mdio.dev, "No associated device tree node\n");
+		return -EINVAL;
+	}
+
+	if (is_link_up(phydev))
+		phydev->link = 1;
+	else
+		phydev->link = 0;
+
+	return 0;
+}
+
+static int fsl_backplane_match_phy_device(struct phy_device *phydev)
+{
+	struct device_node *phy_node, *lane_node;
+	const char *st;
+	int serdes_type, i, ret;
+	const int num_ids = ARRAY_SIZE(phydev->c45_ids.device_ids);
+
+	if (!phydev->mdio.dev.of_node) {
+		return 0;
+	}
+
+	//	 WORKAROUND:
+	// Required for LX2 devices
+	// where PHY ID cannot be verified in PCS
+	// because PCS Device Identifier Upper and Lower registers are hidden
+	// and always return 0 when they are read:
+	// 2  02 	Device_ID0  RO 		Bits 15:0 	0
+	// val = phy_read_mmd(phydev, MDIO_MMD_PCS, 0x2);
+	// 3  03 	Device_ID1  RO 		Bits 31:16 	0
+	// val = phy_read_mmd(phydev, MDIO_MMD_PCS, 0x3);
+	//
+	// To be removed: After the issue will be fixed on LX2 devices
+
+	if (!phydev->is_c45)
+		return 0;
+
+	phy_node = phydev->mdio.dev.of_node;
+
+	lane_node = of_parse_phandle(phy_node, "fsl,lane-handle", 0);
+	if (!lane_node) {
+		dev_err(&phydev->mdio.dev, "parse fsl,lane-handle failed\n");
+		return 0;
+	}
+
+	ret = of_property_read_string(lane_node, "compatible", &st);
+	if (ret < 0) {
+		//assume SERDES-10G if compatible property is not specified
+		serdes_type = SERDES_10G;
+	}
+	else if (!strcasecmp(st, "fsl,serdes-10g")) {
+		serdes_type = SERDES_10G;
+	} else if (!strcasecmp(st, "fsl,serdes-28g")) {
+		serdes_type = SERDES_28G;
+	} else {
+		dev_err(&phydev->mdio.dev, "Unknown serdes-type\n");
+		return 0;
+	}
+
+	if (serdes_type == SERDES_10G) {
+		//On LS devices we must find the c45 device with correct PHY ID
+		//Implementation similar with the one existent in phy_device: @function: phy_bus_match
+		for (i = 1; i < num_ids; i++) {
+			if (!(phydev->c45_ids.devices_in_package & (1 << i)))
+				continue;
+
+			if ((PCS_PHY_DEVICE_ID & PCS_PHY_DEVICE_ID_MASK) ==
+				(phydev->c45_ids.device_ids[i] & PCS_PHY_DEVICE_ID_MASK))
+			{
+				return 1;
+			}
+		}
+		return 0;
+	}
+
+	//On LX devices we cannot verify PHY ID
+	//so we are happy only with preliminary verifications already made: mdio.dev.of_node and is_c45
+	//because we already filtered other undesired devices: non clause 45
+
+	return 1;
+}
+
+static struct phy_driver fsl_backplane_driver[] = {
+	{
+	.phy_id		= PCS_PHY_DEVICE_ID,
+	.name		= "Freescale Backplane",
+	.phy_id_mask	= PCS_PHY_DEVICE_ID_MASK,
+	.features	= SUPPORTED_Backplane | SUPPORTED_Autoneg |
+			  SUPPORTED_MII,
+	.probe          = fsl_backplane_probe,
+	.aneg_done      = fsl_backplane_aneg_done,
+	.config_aneg	= fsl_backplane_config_aneg,
+	.read_status	= fsl_backplane_read_status,
+	.suspend	= fsl_backplane_suspend,
+	.resume		= fsl_backplane_resume,
+	.match_phy_device = fsl_backplane_match_phy_device,
+	},
+};
+
+module_phy_driver(fsl_backplane_driver);
+
+static struct mdio_device_id __maybe_unused freescale_tbl[] = {
+	{ PCS_PHY_DEVICE_ID, PCS_PHY_DEVICE_ID_MASK },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, freescale_tbl);
+
+MODULE_DESCRIPTION("Freescale Backplane driver");
+MODULE_AUTHOR("Shaohui Xie <Shaohui.Xie@freescale.com>");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/drivers/net/phy/fsl_backplane.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ *  DPAA backplane driver.
+ *   Author: Florinel Iordache <florinel.iordache@nxp.com>
+ *
+ * Copyright 2018 NXP
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#ifndef FSL_BACKPLANE_H
+#define FSL_BACKPLANE_H
+
+/* C(-1) */
+#define BIN_M1						0
+/* C(1) */
+#define BIN_LONG					1
+
+#define BIN_SNAPSHOT_NUM			5
+#define BIN_M1_THRESHOLD			3
+#define BIN_LONG_THRESHOLD			2
+
+struct serdes_access {
+
+	int serdes_type;
+	bool is_little_endian;
+	u32 (*ioread32)(u32 *reg);
+	void (*iowrite32)(u32 value, u32 *reg);
+	u32 (*get_lane_memmap_size)(void);
+	void (*tune_tecr)(void *reg, u32 ratio_preq, u32 ratio_pst1q, u32 adpt_eq, bool reset);
+	void (*reset_lane)(void *reg);
+	void (*lane_set_1gkx)(void *reg);
+	int (*get_median_gaink2)(u32 *reg);
+	bool (*is_bin_early)(int bin_sel, void *reg);
+};
+
+struct serdes_access* setup_serdes_access_10g(void);
+struct serdes_access* setup_serdes_access_28g(void);
+
+
+#endif //FSL_BACKPLANE_H
--- /dev/null
+++ b/drivers/net/phy/fsl_backplane_serdes_10g.c
@@ -0,0 +1,281 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *  DPAA backplane driver for SerDes 10G.
+ *   Author: Florinel Iordache <florinel.iordache@nxp.com>
+ *
+ * Copyright 2018 NXP
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/io.h>
+#include <linux/delay.h>
+
+#include "fsl_backplane.h"
+
+#define BIN_M1_SEL					6
+#define BIN_Long_SEL				7
+#define CDR_SEL_MASK				0x00070000
+
+#define PRE_COE_SHIFT				22
+#define POST_COE_SHIFT				16
+#define ZERO_COE_SHIFT				8
+
+#define TECR0_INIT					0x24200000
+
+#define GCR0_RESET_MASK				0x00600000
+
+#define GCR1_SNP_START_MASK			0x00000040
+#define GCR1_CTL_SNP_START_MASK		0x00002000
+
+#define RECR1_CTL_SNP_DONE_MASK		0x00000002
+#define RECR1_SNP_DONE_MASK			0x00000004
+#define TCSR1_SNP_DATA_MASK			0x0000ffc0
+#define TCSR1_SNP_DATA_SHIFT		6
+#define TCSR1_EQ_SNPBIN_SIGN_MASK	0x100
+
+#define RECR1_GAINK2_MASK			0x0f000000
+#define RECR1_GAINK2_SHIFT			24
+
+/* Required only for 1000BASE KX */
+#define GCR1_REIDL_TH_MASK			0x00700000
+#define GCR1_REIDL_EX_SEL_MASK		0x000c0000
+#define GCR1_REIDL_ET_MAS_MASK		0x00004000
+#define TECR0_AMP_RED_MASK			0x0000003f
+
+struct per_lane_ctrl_status {
+	u32 gcr0;	/* 0x.000 - General Control Register 0 */
+	u32 gcr1;	/* 0x.004 - General Control Register 1 */
+	u32 gcr2;	/* 0x.008 - General Control Register 2 */
+	u32 resv1;	/* 0x.00C - Reserved */
+	u32 recr0;	/* 0x.010 - Receive Equalization Control Register 0 */
+	u32 recr1;	/* 0x.014 - Receive Equalization Control Register 1 */
+	u32 tecr0;	/* 0x.018 - Transmit Equalization Control Register 0 */
+	u32 resv2;	/* 0x.01C - Reserved */
+	u32 tlcr0;	/* 0x.020 - TTL Control Register 0 */
+	u32 tlcr1;	/* 0x.024 - TTL Control Register 1 */
+	u32 tlcr2;	/* 0x.028 - TTL Control Register 2 */
+	u32 tlcr3;	/* 0x.02C - TTL Control Register 3 */
+	u32 tcsr0;	/* 0x.030 - Test Control/Status Register 0 */
+	u32 tcsr1;	/* 0x.034 - Test Control/Status Register 1 */
+	u32 tcsr2;	/* 0x.038 - Test Control/Status Register 2 */
+	u32 tcsr3;	/* 0x.03C - Test Control/Status Register 3 */
+};
+
+static struct serdes_access srds;
+
+static u32 get_lane_memmap_size(void)
+{
+	return 0x40;
+}
+
+static void reset_lane(void *reg)
+{
+	struct per_lane_ctrl_status *reg_base = reg;
+
+	/* reset the lane */
+	srds.iowrite32(srds.ioread32(&reg_base->gcr0) & ~GCR0_RESET_MASK,
+		    &reg_base->gcr0);
+	udelay(1);
+	
+	/* unreset the lane */
+	srds.iowrite32(srds.ioread32(&reg_base->gcr0) | GCR0_RESET_MASK,
+		    &reg_base->gcr0);
+	udelay(1);
+}
+
+static void tune_tecr(void *reg, u32 ratio_preq, u32 ratio_pst1q, u32 adpt_eq, bool reset)
+{
+	struct per_lane_ctrl_status *reg_base = reg;
+	u32 val;
+
+	val = TECR0_INIT |
+		adpt_eq << ZERO_COE_SHIFT |
+		ratio_preq << PRE_COE_SHIFT |
+		ratio_pst1q << POST_COE_SHIFT;
+
+	if (reset) {
+		/* reset the lane */
+		srds.iowrite32(srds.ioread32(&reg_base->gcr0) & ~GCR0_RESET_MASK,
+				&reg_base->gcr0);
+		udelay(1);
+	}
+	
+	srds.iowrite32(val, &reg_base->tecr0);
+	udelay(1);
+	
+	if (reset) {
+		/* unreset the lane */
+		srds.iowrite32(srds.ioread32(&reg_base->gcr0) | GCR0_RESET_MASK,
+				&reg_base->gcr0);
+		udelay(1);
+	}
+}
+
+static void lane_set_1gkx(void *reg)
+{
+	struct per_lane_ctrl_status *reg_base = reg;
+	u32 val;
+
+	/* reset the lane */
+	srds.iowrite32(srds.ioread32(&reg_base->gcr0) & ~GCR0_RESET_MASK,
+		    &reg_base->gcr0);
+	udelay(1);
+
+	/* set gcr1 for 1GKX */
+	val = srds.ioread32(&reg_base->gcr1);
+	val &= ~(GCR1_REIDL_TH_MASK | GCR1_REIDL_EX_SEL_MASK |
+		 GCR1_REIDL_ET_MAS_MASK);
+	srds.iowrite32(val, &reg_base->gcr1);
+	udelay(1);
+
+	/* set tecr0 for 1GKX */
+	val = srds.ioread32(&reg_base->tecr0);
+	val &= ~TECR0_AMP_RED_MASK;
+	srds.iowrite32(val, &reg_base->tecr0);
+	udelay(1);
+
+	/* unreset the lane */
+	srds.iowrite32(srds.ioread32(&reg_base->gcr0) | GCR0_RESET_MASK,
+		    &reg_base->gcr0);
+	udelay(1);
+}
+
+static int get_median_gaink2(u32 *reg)
+{
+	int gaink2_snap_shot[BIN_SNAPSHOT_NUM];
+	u32 rx_eq_snp;
+	struct per_lane_ctrl_status *reg_base;
+	int timeout;
+	int i, j, tmp, pos;
+
+	reg_base = (struct per_lane_ctrl_status *)reg;
+
+	for (i = 0; i < BIN_SNAPSHOT_NUM; i++) {
+		/* wait RECR1_CTL_SNP_DONE_MASK has cleared */
+		timeout = 100;
+		while (srds.ioread32(&reg_base->recr1) &
+		       RECR1_CTL_SNP_DONE_MASK) {
+			udelay(1);
+			timeout--;
+			if (timeout == 0)
+				break;
+		}
+
+		/* start snap shot */
+		srds.iowrite32((srds.ioread32(&reg_base->gcr1) |
+			    GCR1_CTL_SNP_START_MASK),
+			    &reg_base->gcr1);
+
+		/* wait for SNP done */
+		timeout = 100;
+		while (!(srds.ioread32(&reg_base->recr1) &
+		       RECR1_CTL_SNP_DONE_MASK)) {
+			udelay(1);
+			timeout--;
+			if (timeout == 0)
+				break;
+		}
+
+		/* read and save the snap shot */
+		rx_eq_snp = srds.ioread32(&reg_base->recr1);
+		gaink2_snap_shot[i] = (rx_eq_snp & RECR1_GAINK2_MASK) >>
+					RECR1_GAINK2_SHIFT;
+
+		/* terminate the snap shot by setting GCR1[REQ_CTL_SNP] */
+		srds.iowrite32((srds.ioread32(&reg_base->gcr1) &
+			    ~GCR1_CTL_SNP_START_MASK),
+			    &reg_base->gcr1);
+	}
+
+	/* get median of the 5 snap shot */
+	for (i = 0; i < BIN_SNAPSHOT_NUM - 1; i++) {
+		tmp = gaink2_snap_shot[i];
+		pos = i;
+		for (j = i + 1; j < BIN_SNAPSHOT_NUM; j++) {
+			if (gaink2_snap_shot[j] < tmp) {
+				tmp = gaink2_snap_shot[j];
+				pos = j;
+			}
+		}
+
+		gaink2_snap_shot[pos] = gaink2_snap_shot[i];
+		gaink2_snap_shot[i] = tmp;
+	}
+
+	return gaink2_snap_shot[2];
+}
+
+static bool is_bin_early(int bin_sel, void *reg)
+{
+	bool early = false;
+	int bin_snap_shot[BIN_SNAPSHOT_NUM];
+	int i, negative_count = 0;
+	struct per_lane_ctrl_status *reg_base = reg;
+	int timeout;
+
+	for (i = 0; i < BIN_SNAPSHOT_NUM; i++) {
+		/* wait RECR1_SNP_DONE_MASK has cleared */
+		timeout = 100;
+		while ((srds.ioread32(&reg_base->recr1) & RECR1_SNP_DONE_MASK)) {
+			udelay(1);
+			timeout--;
+			if (timeout == 0)
+				break;
+		}
+
+		/* set TCSR1[CDR_SEL] to BinM1/BinLong */
+		if (bin_sel == BIN_M1) {
+			srds.iowrite32((srds.ioread32(&reg_base->tcsr1) &
+				    ~CDR_SEL_MASK) | BIN_M1_SEL,
+				    &reg_base->tcsr1);
+		} else {
+			srds.iowrite32((srds.ioread32(&reg_base->tcsr1) &
+				    ~CDR_SEL_MASK) | BIN_Long_SEL,
+				    &reg_base->tcsr1);
+		}
+
+		/* start snap shot */
+		srds.iowrite32(srds.ioread32(&reg_base->gcr1) | GCR1_SNP_START_MASK,
+			    &reg_base->gcr1);
+
+		/* wait for SNP done */
+		timeout = 100;
+		while (!(srds.ioread32(&reg_base->recr1) & RECR1_SNP_DONE_MASK)) {
+			udelay(1);
+			timeout--;
+			if (timeout == 0)
+				break;
+		}
+
+		/* read and save the snap shot */
+		bin_snap_shot[i] = (srds.ioread32(&reg_base->tcsr1) &
+				TCSR1_SNP_DATA_MASK) >> TCSR1_SNP_DATA_SHIFT;
+		if (bin_snap_shot[i] & TCSR1_EQ_SNPBIN_SIGN_MASK)
+			negative_count++;
+
+		/* terminate the snap shot by setting GCR1[REQ_CTL_SNP] */
+		srds.iowrite32(srds.ioread32(&reg_base->gcr1) & ~GCR1_SNP_START_MASK,
+			    &reg_base->gcr1);
+	}
+
+	if (((bin_sel == BIN_M1) && (negative_count > BIN_M1_THRESHOLD)) ||
+	    ((bin_sel == BIN_LONG && (negative_count > BIN_LONG_THRESHOLD)))) {
+		early = true;
+	}
+
+	return early;
+}
+
+struct serdes_access* setup_serdes_access_10g(void)
+{
+	srds.get_lane_memmap_size = get_lane_memmap_size;
+	srds.tune_tecr = tune_tecr;
+	srds.reset_lane = reset_lane;
+	srds.lane_set_1gkx = lane_set_1gkx;
+	srds.get_median_gaink2 = get_median_gaink2;
+	srds.is_bin_early = is_bin_early;
+
+	return &srds;
+}
+
--- /dev/null
+++ b/drivers/net/phy/fsl_backplane_serdes_28g.c
@@ -0,0 +1,336 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *  DPAA backplane driver for SerDes 28G.
+ *   Author: Florinel Iordache <florinel.iordache@nxp.com>
+ *
+ * Copyright 2018 NXP
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+
+#include "fsl_backplane.h"
+
+#define BIN_M1_SEL					0x0000c000
+#define BIN_Long_SEL				0x0000d000
+#define CDR_SEL_MASK				0x0000f000
+
+#define PRE_COE_SHIFT				16
+#define POST_COE_SHIFT				8
+#define ZERO_COE_SHIFT				24
+
+#define TECR0_INIT					0x20808000
+
+#define RESET_REQ_MASK				0x80000000
+
+#define RECR3_SNP_START_MASK		0x80000000
+#define RECR3_SNP_DONE_MASK			0x40000000
+
+#define RECR4_SNP_DATA_MASK			0x000003ff
+#define RECR4_SNP_DATA_SHIFT		0
+#define RECR4_EQ_SNPBIN_SIGN_MASK	0x200
+
+#define RECR3_GAINK2_MASK			0x1f000000
+#define RECR3_GAINK2_SHIFT			24
+
+/* Required only for 1000BASE KX */
+#define GCR1_REIDL_TH_MASK			0x00700000
+#define GCR1_REIDL_EX_SEL_MASK		0x000c0000
+#define GCR1_REIDL_ET_MAS_MASK		0x04000000
+#define TECR0_AMP_RED_MASK			0x0000003f
+
+struct per_lane_ctrl_status {
+	u32 gcr0;	/* 0x.000 - General Control Register 0 */
+	u32 resv1;	/* 0x.004 - Reserved */
+	u32 resv2;	/* 0x.008 - Reserved */
+	u32 resv3;	/* 0x.00C - Reserved */
+	u32 resv4;	/* 0x.010 - Reserved */
+	u32 resv5;	/* 0x.014 - Reserved */
+	u32 resv6;	/* 0x.018 - Reserved */
+	u32 resv7;	/* 0x.01C - Reserved */
+	u32 trstctl;	/* 0x.020 - TX Reset Control Register */
+	u32 tgcr0;	/* 0x.024 - TX General Control Register 0 */
+	u32 tgcr1;	/* 0x.028 - TX General Control Register 1 */
+	u32 tgcr2;	/* 0x.02C - TX General Control Register 2 */
+	u32 tecr0;	/* 0x.030 - Transmit Equalization Control Register 0 */
+	u32 tecr1;	/* 0x.034 - Transmit Equalization Control Register 1 */
+	u32 resv8;	/* 0x.038 - Reserved */
+	u32 resv9;	/* 0x.03C - Reserved */
+	u32 rrstctl;	/* 0x.040 - RX Reset Control Register */
+	u32 rgcr0;	/* 0x.044 - RX General Control Register 0 */
+	u32 rxgcr1; 	/* 0x.048 - RX General Control Register 1 */
+	u32 resv10;	/* 0x.04C - Reserved */
+	u32 recr0;	/* 0x.050 - RX Equalization Register 0 */
+	u32 recr1;	/* 0x.054 - RX Equalization Register 1 */
+	u32 recr2;	/* 0x.058 - RX Equalization Register 2 */
+	u32 recr3;	/* 0x.05C - RX Equalization Register 3 */
+	u32 recr4;	/* 0x.060 - RX Equalization Register 4 */
+	u32 resv11;	/* 0x.064 - Reserved */
+	u32 rccr0;	/* 0x.068 - RX Calibration Register 0 */
+	u32 rccr1;	/* 0x.06C - RX Calibration Register 1 */
+	u32 rcpcr0;	/* 0x.070 - RX Clock Path Register 0 */
+	u32 rsccr0;	/* 0x.074 - RX Sampler Calibration Control Register 0 */
+	u32 rsccr1;	/* 0x.078 - RX Sampler Calibration Control Register 1 */
+	u32 resv12;	/* 0x.07C - Reserved */
+	u32 ttlcr0;	/* 0x.080 - Transition Tracking Loop Register 0 */
+	u32 ttlcr1;	/* 0x.084 - Transition Tracking Loop Register 1 */
+	u32 ttlcr2;	/* 0x.088 - Transition Tracking Loop Register 2 */
+	u32 ttlcr3;	/* 0x.08C - Transition Tracking Loop Register 3 */
+	u32 resv13;	/* 0x.090 - Reserved */
+	u32 resv14;	/* 0x.094 - Reserved */
+	u32 resv15;	/* 0x.098 - Reserved */
+	u32 resv16;	/* 0x.09C - Reserved */
+	u32 tcsr0;	/* 0x.0A0 - Test Control/Status Register 0 */
+	u32 tcsr1;	/* 0x.0A4 - Test Control/Status Register 1 */
+	u32 tcsr2;	/* 0x.0A8 - Test Control/Status Register 2 */
+	u32 tcsr3;	/* 0x.0AC - Test Control/Status Register 3 */
+	u32 tcsr4;	/* 0x.0B0 - Test Control/Status Register 4 */
+	u32 resv17;	/* 0x.0B4 - Reserved */
+	u32 resv18;	/* 0x.0B8 - Reserved */
+	u32 resv19;	/* 0x.0BC - Reserved */
+	u32 rxcb0;	/* 0x.0C0 - RX Control Block Register 0 */
+	u32 rxcb1;	/* 0x.0C4 - RX Control Block Register 1 */
+	u32 resv20;	/* 0x.0C8 - Reserved */
+	u32 resv21;	/* 0x.0CC - Reserved */
+	u32 rxss0;	/* 0x.0D0 - RX Speed Switch Register 0 */
+	u32 rxss1;	/* 0x.0D4 - RX Speed Switch Register 1 */
+	u32 rxss2;	/* 0x.0D8 - RX Speed Switch Register 2 */
+	u32 resv22;	/* 0x.0DC - Reserved */
+	u32 txcb0;	/* 0x.0E0 - TX Control Block Register 0 */
+	u32 txcb1;	/* 0x.0E4 - TX Control Block Register 1 */
+	u32 resv23;	/* 0x.0E8 - Reserved */
+	u32 resv24;	/* 0x.0EC - Reserved */
+	u32 txss0;	/* 0x.0F0 - TX Speed Switch Register 0 */
+	u32 txss1;	/* 0x.0F4 - TX Speed Switch Register 1 */
+	u32 txss2;	/* 0x.0F8 - TX Speed Switch Register 2 */
+	u32 resv25;	/* 0x.0FC - Reserved */
+};
+
+static struct serdes_access srds;
+
+static u32 get_lane_memmap_size(void)
+{
+	return 0x100;
+}
+
+static void reset_lane(void *reg)
+{
+	struct per_lane_ctrl_status *reg_base = reg;
+	u32 val;
+	unsigned long timeout;
+
+	/* reset Tx lane: send reset request */
+	srds.iowrite32(srds.ioread32(&reg_base->trstctl) | RESET_REQ_MASK,
+		    &reg_base->trstctl);
+	udelay(1);
+	timeout = 10;
+	while (timeout--) {
+		val = srds.ioread32(&reg_base->trstctl);
+		if (!(val & RESET_REQ_MASK))
+			break;
+		usleep_range(5, 20);
+	}
+	
+	/* reset Rx lane: send reset request */
+	srds.iowrite32(srds.ioread32(&reg_base->rrstctl) | RESET_REQ_MASK,
+		    &reg_base->rrstctl);
+	udelay(1);
+	timeout = 10;
+	while (timeout--) {
+		val = srds.ioread32(&reg_base->rrstctl);
+		if (!(val & RESET_REQ_MASK))
+			break;
+		usleep_range(5, 20);
+	}
+	
+	/* wait for a while after reset */
+	timeout = jiffies + 10;
+	while (time_before(jiffies, timeout)) {
+		schedule();
+		usleep_range(5, 20);
+	}
+}
+
+static void tune_tecr(void *reg, u32 ratio_preq, u32 ratio_pst1q, u32 adpt_eq, bool reset)
+{
+	struct per_lane_ctrl_status *reg_base = reg;
+	u32 val;
+
+	if (reset) {
+		/* reset lanes */
+		reset_lane(reg);
+	}
+	
+	val = TECR0_INIT |
+		ratio_preq << PRE_COE_SHIFT |
+		ratio_pst1q << POST_COE_SHIFT;
+	srds.iowrite32(val, &reg_base->tecr0);
+
+	val = adpt_eq << ZERO_COE_SHIFT;
+	srds.iowrite32(val, &reg_base->tecr1);
+	
+	udelay(1);
+}
+
+static void lane_set_1gkx(void *reg)
+{
+	struct per_lane_ctrl_status *reg_base = reg;
+	u32 val;
+
+	/* reset lanes */
+	reset_lane(reg);
+
+	/* set gcr1 for 1GKX */
+	val = srds.ioread32(&reg_base->rxgcr1);
+	val &= ~(GCR1_REIDL_TH_MASK | GCR1_REIDL_EX_SEL_MASK |
+		 GCR1_REIDL_ET_MAS_MASK);
+	srds.iowrite32(val, &reg_base->rxgcr1);
+	udelay(1);
+
+	/* set tecr0 for 1GKX */
+	val = srds.ioread32(&reg_base->tecr0);
+	val &= ~TECR0_AMP_RED_MASK;
+	srds.iowrite32(val, &reg_base->tecr0);
+	udelay(1);
+}
+
+static int get_median_gaink2(u32 *reg)
+{
+	int gaink2_snap_shot[BIN_SNAPSHOT_NUM];
+	u32 rx_eq_snp;
+	struct per_lane_ctrl_status *reg_base;
+	int timeout;
+	int i, j, tmp, pos;
+
+	reg_base = (struct per_lane_ctrl_status *)reg;
+
+	for (i = 0; i < BIN_SNAPSHOT_NUM; i++) {
+		/* wait RECR3_SNP_DONE_MASK has cleared */
+		timeout = 100;
+		while (srds.ioread32(&reg_base->recr3) &
+				RECR3_SNP_DONE_MASK) {
+			udelay(1);
+			timeout--;
+			if (timeout == 0)
+				break;
+		}
+
+		/* start snap shot */
+		srds.iowrite32((srds.ioread32(&reg_base->recr3) |
+			    RECR3_SNP_START_MASK),
+			    &reg_base->recr3);
+
+		/* wait for SNP done */
+		timeout = 100;
+		while (!(srds.ioread32(&reg_base->recr3) &
+				RECR3_SNP_DONE_MASK)) {
+			udelay(1);
+			timeout--;
+			if (timeout == 0)
+				break;
+		}
+
+		/* read and save the snap shot */
+		rx_eq_snp = srds.ioread32(&reg_base->recr3);
+		gaink2_snap_shot[i] = (rx_eq_snp & RECR3_GAINK2_MASK) >>
+					RECR3_GAINK2_SHIFT;
+
+		/* terminate the snap shot by setting GCR1[REQ_CTL_SNP] */
+		srds.iowrite32((srds.ioread32(&reg_base->recr3) &
+			    ~RECR3_SNP_START_MASK),
+			    &reg_base->recr3);
+	}
+
+	/* get median of the 5 snap shot */
+	for (i = 0; i < BIN_SNAPSHOT_NUM - 1; i++) {
+		tmp = gaink2_snap_shot[i];
+		pos = i;
+		for (j = i + 1; j < BIN_SNAPSHOT_NUM; j++) {
+			if (gaink2_snap_shot[j] < tmp) {
+				tmp = gaink2_snap_shot[j];
+				pos = j;
+			}
+		}
+
+		gaink2_snap_shot[pos] = gaink2_snap_shot[i];
+		gaink2_snap_shot[i] = tmp;
+	}
+
+	return gaink2_snap_shot[2];
+}
+
+static bool is_bin_early(int bin_sel, void *reg)
+{
+	bool early = false;
+	int bin_snap_shot[BIN_SNAPSHOT_NUM];
+	int i, negative_count = 0;
+	struct per_lane_ctrl_status *reg_base = reg;
+	int timeout;
+
+	for (i = 0; i < BIN_SNAPSHOT_NUM; i++) {
+		/* wait RECR3_SNP_DONE_MASK has cleared */
+		timeout = 100;
+		while ((srds.ioread32(&reg_base->recr3) & RECR3_SNP_DONE_MASK)) {
+			udelay(1);
+			timeout--;
+			if (timeout == 0)
+				break;
+		}
+
+		/* set TCSR1[CDR_SEL] to BinM1/BinLong */
+		if (bin_sel == BIN_M1) {
+			srds.iowrite32((srds.ioread32(&reg_base->recr4) &
+				    ~CDR_SEL_MASK) | BIN_M1_SEL,
+				    &reg_base->recr4);
+		} else {
+			srds.iowrite32((srds.ioread32(&reg_base->recr4) &
+				    ~CDR_SEL_MASK) | BIN_Long_SEL,
+				    &reg_base->recr4);
+		}
+
+		/* start snap shot */
+		srds.iowrite32(srds.ioread32(&reg_base->recr3) | RECR3_SNP_START_MASK,
+			    &reg_base->recr3);
+
+		/* wait for SNP done */
+		timeout = 100;
+		while (!(srds.ioread32(&reg_base->recr3) & RECR3_SNP_DONE_MASK)) {
+			udelay(1);
+			timeout--;
+			if (timeout == 0)
+				break;
+		}
+
+		/* read and save the snap shot */
+		bin_snap_shot[i] = (srds.ioread32(&reg_base->recr4) &
+				RECR4_SNP_DATA_MASK) >> RECR4_SNP_DATA_SHIFT;
+		if (bin_snap_shot[i] & RECR4_EQ_SNPBIN_SIGN_MASK)
+			negative_count++;
+
+		/* terminate the snap shot by setting GCR1[REQ_CTL_SNP] */
+		srds.iowrite32(srds.ioread32(&reg_base->recr3) & ~RECR3_SNP_START_MASK,
+			    &reg_base->recr3);
+	}
+
+	if (((bin_sel == BIN_M1) && (negative_count > BIN_M1_THRESHOLD)) ||
+	    ((bin_sel == BIN_LONG && (negative_count > BIN_LONG_THRESHOLD)))) {
+		early = true;
+	}
+
+	return early;
+}
+
+struct serdes_access* setup_serdes_access_28g(void)
+{
+	srds.get_lane_memmap_size = get_lane_memmap_size;
+	srds.tune_tecr = tune_tecr;
+	srds.reset_lane = reset_lane;
+	srds.lane_set_1gkx = lane_set_1gkx;
+	srds.get_median_gaink2 = get_median_gaink2;
+	srds.is_bin_early = is_bin_early;
+
+	return &srds;
+}
--- /dev/null
+++ b/drivers/net/phy/inphi.c
@@ -0,0 +1,594 @@
+/*
+ * Copyright 2018 NXP
+ * Copyright 2018 INPHI
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Inphi is a registered trademark of Inphi Corporation
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/mdio.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/of_irq.h>
+#include <linux/workqueue.h>
+#include <linux/i2c.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#define PHY_ID_IN112525  0x02107440
+
+#define INPHI_S03_DEVICE_ID_MSB 0x2
+#define INPHI_S03_DEVICE_ID_LSB 0x3
+
+#define ALL_LANES		4
+#define INPHI_POLL_DELAY	2500
+
+#define PHYCTRL_REG1	0x0012
+#define PHYCTRL_REG2	0x0014
+#define PHYCTRL_REG3	0x0120
+#define PHYCTRL_REG4	0x0121
+#define PHYCTRL_REG5	0x0180
+#define PHYCTRL_REG6	0x0580
+#define PHYCTRL_REG7	0x05C4
+#define PHYCTRL_REG8	0x01C8
+#define PHYCTRL_REG9	0x0521
+
+#define PHYSTAT_REG1	0x0021
+#define PHYSTAT_REG2	0x0022
+#define PHYSTAT_REG3	0x0123
+
+#define PHYMISC_REG1	0x0025
+#define PHYMISC_REG2	0x002c
+#define PHYMISC_REG3	0x00b3
+#define PHYMISC_REG4	0x0181
+#define PHYMISC_REG5	0x019D
+#define PHYMISC_REG6	0x0198
+#define PHYMISC_REG7	0x0199
+#define PHYMISC_REG8	0x0581
+#define PHYMISC_REG9	0x0598
+#define PHYMISC_REG10	0x059c
+#define PHYMISC_REG20	0x01B0
+#define PHYMISC_REG21	0x01BC
+#define PHYMISC_REG22	0x01C0
+
+#define RX_VCO_CODE_OFFSET	5
+#define VCO_CODE		390
+
+int vco_codes[ALL_LANES] = {
+	VCO_CODE,
+	VCO_CODE,
+	VCO_CODE,
+	VCO_CODE
+};
+
+static void mykmod_work_handler(struct work_struct *w);
+
+static struct workqueue_struct *wq;
+static DECLARE_DELAYED_WORK(mykmod_work, mykmod_work_handler);
+static unsigned long onesec;
+struct phy_device *inphi_phydev;
+
+static int mdio_wr(u32 regnum, u16 val)
+{
+	regnum = MII_ADDR_C45 | (MDIO_MMD_VEND1 << 16) | (regnum & 0xffff);
+
+	return mdiobus_write(inphi_phydev->mdio.bus, inphi_phydev->mdio.addr,
+				regnum, val);
+}
+
+static int mdio_rd(u32 regnum)
+{
+	regnum = MII_ADDR_C45 | (MDIO_MMD_VEND1 << 16) | (regnum & 0xffff);
+
+	return mdiobus_read(inphi_phydev->mdio.bus, inphi_phydev->mdio.addr,
+				regnum);
+}
+
+
+int bit_test(int value, int bit_field)
+{
+	int result;
+	int bit_mask = (1 << bit_field);
+
+	result = ((value & bit_mask) == bit_mask);
+	return result;
+}
+
+int tx_pll_lock_test(int lane)
+{
+	int i, val, locked = 1;
+
+	if (lane == ALL_LANES) {
+		for (i = 0; i < ALL_LANES; i++) {
+			val = mdio_rd(i * 0x100 + PHYSTAT_REG3);
+			locked = locked & bit_test(val, 15);
+		}
+	} else {
+		val = mdio_rd(lane * 0x100 + PHYSTAT_REG3);
+		locked = locked & bit_test(val, 15);
+	}
+
+	return locked;
+}
+
+void rx_reset_assert(int lane)
+{
+	int mask, val;
+
+	if (lane == ALL_LANES) {
+		val = mdio_rd(PHYMISC_REG2);
+		mask = (1 << 15);
+		mdio_wr(PHYMISC_REG2, val + mask);
+	} else {
+		val = mdio_rd(lane * 0x100 + PHYCTRL_REG8);
+		mask = (1 << 6);
+		mdio_wr(lane * 0x100 + PHYCTRL_REG8, val + mask);
+	}
+}
+
+void rx_reset_de_assert(int lane)
+{
+	int mask, val;
+
+	if (lane == ALL_LANES) {
+		val = mdio_rd(PHYMISC_REG2);
+		mask = 0xffff - (1 << 15);
+		mdio_wr(PHYMISC_REG2, val & mask);
+	} else {
+		val = mdio_rd(lane * 0x100 + PHYCTRL_REG8);
+		mask = 0xffff - (1 << 6);
+		mdio_wr(lane * 0x100 + PHYCTRL_REG8, val & mask);
+	}
+}
+
+void rx_powerdown_assert(int lane)
+{
+	int mask, val;
+
+	val = mdio_rd(lane * 0x100 + PHYCTRL_REG8);
+	mask = (1 << 5);
+	mdio_wr(lane * 0x100 + PHYCTRL_REG8, val + mask);
+}
+
+void rx_powerdown_de_assert(int lane)
+{
+	int mask, val;
+
+	val = mdio_rd(lane * 0x100 + PHYCTRL_REG8);
+	mask = 0xffff - (1 << 5);
+	mdio_wr(lane * 0x100 + PHYCTRL_REG8, val & mask);
+}
+
+void tx_pll_assert(int lane)
+{
+	int val, recal;
+
+	if (lane == ALL_LANES) {
+		val = mdio_rd(PHYMISC_REG2);
+		recal = (1 << 12);
+		mdio_wr(PHYMISC_REG2, val | recal);
+	} else {
+		val = mdio_rd(lane * 0x100 + PHYCTRL_REG4);
+		recal = (1 << 15);
+		mdio_wr(lane * 0x100 + PHYCTRL_REG4, val | recal);
+	}
+}
+
+void tx_pll_de_assert(int lane)
+{
+	int recal, val;
+
+	if (lane == ALL_LANES) {
+		val = mdio_rd(PHYMISC_REG2);
+		recal = 0xefff;
+		mdio_wr(PHYMISC_REG2, val & recal);
+	} else {
+		val = mdio_rd(lane * 0x100 + PHYCTRL_REG4);
+		recal = 0x7fff;
+		mdio_wr(lane * 0x100 + PHYCTRL_REG4, val & recal);
+	}
+}
+
+void tx_core_assert(int lane)
+{
+	int recal, val, val2, core_reset;
+
+	if (lane == 4) {
+		val = mdio_rd(PHYMISC_REG2);
+		recal = 1 << 10;
+		mdio_wr(PHYMISC_REG2, val | recal);
+	} else {
+		val2 = mdio_rd(PHYMISC_REG3);
+		core_reset = (1 << (lane + 8));
+		mdio_wr(PHYMISC_REG3, val2 | core_reset);
+	}
+}
+
+void lol_disable(int lane)
+{
+	int val, mask;
+
+	val = mdio_rd(PHYMISC_REG3);
+	mask = 1 << (lane + 4);
+	mdio_wr(PHYMISC_REG3, val | mask);
+}
+
+void tx_core_de_assert(int lane)
+{
+	int val, recal, val2, core_reset;
+
+	if (lane == ALL_LANES) {
+		val = mdio_rd(PHYMISC_REG2);
+		recal = 0xffff - (1 << 10);
+		mdio_wr(PHYMISC_REG2, val & recal);
+	} else {
+		val2 = mdio_rd(PHYMISC_REG3);
+		core_reset = 0xffff - (1 << (lane + 8));
+		mdio_wr(PHYMISC_REG3, val2 & core_reset);
+	}
+}
+
+void tx_restart(int lane)
+{
+	tx_core_assert(lane);
+	tx_pll_assert(lane);
+	tx_pll_de_assert(lane);
+	usleep_range(1500, 1600);
+	tx_core_de_assert(lane);
+}
+
+void disable_lane(int lane)
+{
+	rx_reset_assert(lane);
+	rx_powerdown_assert(lane);
+	tx_core_assert(lane);
+	lol_disable(lane);
+}
+
+void toggle_reset(int lane)
+{
+	int reg, val, orig;
+
+	if (lane == ALL_LANES) {
+		mdio_wr(PHYMISC_REG2, 0x8000);
+		udelay(100);
+		mdio_wr(PHYMISC_REG2, 0x0000);
+	} else {
+		reg = lane * 0x100 + PHYCTRL_REG8;
+		val = (1 << 6);
+		orig = mdio_rd(reg);
+		mdio_wr(reg, orig + val);
+		udelay(100);
+		mdio_wr(reg, orig);
+	}
+}
+
+int az_complete_test(int lane)
+{
+	int success = 1, value;
+
+	if (lane == 0 || lane == ALL_LANES) {
+		value = mdio_rd(PHYCTRL_REG5);
+		success = success & bit_test(value, 2);
+	}
+	if (lane == 1 || lane == ALL_LANES) {
+		value = mdio_rd(PHYCTRL_REG5 + 0x100);
+		success = success & bit_test(value, 2);
+	}
+	if (lane == 2 || lane == ALL_LANES) {
+		value = mdio_rd(PHYCTRL_REG5 + 0x200);
+		success = success & bit_test(value, 2);
+	}
+	if (lane == 3 || lane == ALL_LANES) {
+		value = mdio_rd(PHYCTRL_REG5 + 0x300);
+		success = success & bit_test(value, 2);
+	}
+
+	return success;
+}
+
+void save_az_offsets(int lane)
+{
+	int i;
+
+#define AZ_OFFSET_LANE_UPDATE(reg, lane) \
+	mdio_wr((reg) + (lane) * 0x100,  \
+		(mdio_rd((reg) + (lane) * 0x100) >> 8))
+
+	if (lane == ALL_LANES) {
+		for (i = 0; i < ALL_LANES; i++) {
+			AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20, i);
+			AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20 + 1, i);
+			AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20 + 2, i);
+			AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20 + 3, i);
+			AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21, i);
+			AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21 + 1, i);
+			AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21 + 2, i);
+			AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21 + 3, i);
+			AZ_OFFSET_LANE_UPDATE(PHYMISC_REG22, i);
+		}
+	} else {
+		AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20, lane);
+		AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20 + 1, lane);
+		AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20 + 2, lane);
+		AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20 + 3, lane);
+		AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21, lane);
+		AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21 + 1, lane);
+		AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21 + 2, lane);
+		AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21 + 3, lane);
+		AZ_OFFSET_LANE_UPDATE(PHYMISC_REG22, lane);
+	}
+
+	mdio_wr(PHYCTRL_REG7, 0x0001);
+}
+
+void save_vco_codes(int lane)
+{
+	int i;
+
+	if (lane == ALL_LANES) {
+		for (i = 0; i < ALL_LANES; i++) {
+			vco_codes[i] = mdio_rd(PHYMISC_REG5 + i * 0x100);
+			mdio_wr(PHYMISC_REG5 + i * 0x100,
+				vco_codes[i] + RX_VCO_CODE_OFFSET);
+		}
+	} else {
+		vco_codes[lane] = mdio_rd(PHYMISC_REG5 + lane * 0x100);
+		mdio_wr(PHYMISC_REG5 + lane * 0x100,
+			vco_codes[lane] + RX_VCO_CODE_OFFSET);
+	}
+}
+
+int inphi_lane_recovery(int lane)
+{
+	int i, value, az_pass;
+
+	switch (lane) {
+	case 0:
+	case 1:
+	case 2:
+	case 3:
+		rx_reset_assert(lane);
+		mdelay(20);
+		break;
+	case ALL_LANES:
+		mdio_wr(PHYMISC_REG2, 0x9C00);
+		mdelay(20);
+		do {
+			value = mdio_rd(PHYMISC_REG2);
+			udelay(10);
+		} while (!bit_test(value, 4));
+		break;
+	default:
+		dev_err(&inphi_phydev->mdio.dev,
+			"Incorrect usage of APIs in %s driver\n",
+			inphi_phydev->drv->name);
+		break;
+	}
+
+	if (lane == ALL_LANES) {
+		for (i = 0; i < ALL_LANES; i++)
+			mdio_wr(PHYMISC_REG7 + i * 0x100, VCO_CODE);
+	} else {
+		mdio_wr(PHYMISC_REG7 + lane * 0x100, VCO_CODE);
+	}
+
+	if (lane == ALL_LANES)
+		for (i = 0; i < ALL_LANES; i++)
+			mdio_wr(PHYCTRL_REG5 + i * 0x100, 0x0418);
+	else
+		mdio_wr(PHYCTRL_REG5 + lane * 0x100, 0x0418);
+
+	mdio_wr(PHYCTRL_REG7,   0x0000);
+
+	rx_reset_de_assert(lane);
+
+	if (lane == ALL_LANES) {
+		for (i = 0; i < ALL_LANES; i++) {
+			mdio_wr(PHYCTRL_REG5 + i * 0x100, 0x0410);
+			mdio_wr(PHYCTRL_REG5 + i * 0x100, 0x0412);
+		}
+	} else {
+		mdio_wr(PHYCTRL_REG5 + lane * 0x100, 0x0410);
+		mdio_wr(PHYCTRL_REG5 + lane * 0x100, 0x0412);
+	}
+
+	for (i = 0; i < 64; i++) {
+		mdelay(100);
+		az_pass = az_complete_test(lane);
+		if (az_pass) {
+			save_az_offsets(lane);
+			break;
+		}
+	}
+
+	if (!az_pass) {
+		pr_info("in112525: AZ calibration fail @ lane=%d\n", lane);
+		return -1;
+	}
+
+	if (lane == ALL_LANES) {
+		mdio_wr(PHYMISC_REG8, 0x0002);
+		mdio_wr(PHYMISC_REG9, 0x2028);
+		mdio_wr(PHYCTRL_REG6, 0x0010);
+		usleep_range(1000, 1200);
+		mdio_wr(PHYCTRL_REG6, 0x0110);
+		mdelay(30);
+		mdio_wr(PHYMISC_REG9, 0x3020);
+	} else {
+		mdio_wr(PHYMISC_REG4 + lane * 0x100, 0x0002);
+		mdio_wr(PHYMISC_REG6 + lane * 0x100, 0x2028);
+		mdio_wr(PHYCTRL_REG5 + lane * 0x100, 0x0010);
+		usleep_range(1000, 1200);
+		mdio_wr(PHYCTRL_REG5 + lane * 0x100, 0x0110);
+		mdelay(30);
+		mdio_wr(PHYMISC_REG6 + lane * 0x100, 0x3020);
+	}
+
+	if (lane == ALL_LANES) {
+		mdio_wr(PHYMISC_REG2, 0x1C00);
+		mdio_wr(PHYMISC_REG2, 0x0C00);
+	} else {
+		tx_restart(lane);
+		mdelay(11);
+	}
+
+	if (lane == ALL_LANES) {
+		if (bit_test(mdio_rd(PHYMISC_REG2), 6) == 0)
+			return -1;
+	} else {
+		if (tx_pll_lock_test(lane) == 0)
+			return -1;
+	}
+
+	save_vco_codes(lane);
+
+	if (lane == ALL_LANES) {
+		mdio_wr(PHYMISC_REG2, 0x0400);
+		mdio_wr(PHYMISC_REG2, 0x0000);
+		value = mdio_rd(PHYCTRL_REG1);
+		value = value & 0xffbf;
+		mdio_wr(PHYCTRL_REG2, value);
+	} else {
+		tx_core_de_assert(lane);
+	}
+
+	if (lane == ALL_LANES) {
+		mdio_wr(PHYMISC_REG1, 0x8000);
+		mdio_wr(PHYMISC_REG1, 0x0000);
+	}
+	mdio_rd(PHYMISC_REG1);
+	mdio_rd(PHYMISC_REG1);
+	usleep_range(1000, 1200);
+	mdio_rd(PHYSTAT_REG1);
+	mdio_rd(PHYSTAT_REG2);
+
+	return 0;
+}
+
+static void mykmod_work_handler(struct work_struct *w)
+{
+	int all_lanes_lock, lane0_lock, lane1_lock, lane2_lock, lane3_lock;
+
+	lane0_lock = bit_test(mdio_rd(0x123), 15);
+	lane1_lock = bit_test(mdio_rd(0x223), 15);
+	lane2_lock = bit_test(mdio_rd(0x323), 15);
+	lane3_lock = bit_test(mdio_rd(0x423), 15);
+
+	/* check if the chip had any successful lane lock from the previous
+	 * stage (e.g. u-boot)
+	 */
+	all_lanes_lock = lane0_lock | lane1_lock | lane2_lock | lane3_lock;
+
+	if (!all_lanes_lock) {
+		/* start fresh */
+		inphi_lane_recovery(ALL_LANES);
+	} else {
+		if (!lane0_lock)
+			inphi_lane_recovery(0);
+		if (!lane1_lock)
+			inphi_lane_recovery(1);
+		if (!lane2_lock)
+			inphi_lane_recovery(2);
+		if (!lane3_lock)
+			inphi_lane_recovery(3);
+	}
+
+	queue_delayed_work(wq, &mykmod_work, onesec);
+}
+
+int inphi_probe(struct phy_device *phydev)
+{
+	int phy_id = 0, id_lsb = 0, id_msb = 0;
+
+	/* setup the inphi_phydev ptr for mdio_rd/mdio_wr APIs */
+	inphi_phydev = phydev;
+
+	/* Read device id from phy registers */
+	id_lsb = mdio_rd(INPHI_S03_DEVICE_ID_MSB);
+	if (id_lsb < 0)
+		return -ENXIO;
+
+	phy_id = id_lsb << 16;
+
+	id_msb = mdio_rd(INPHI_S03_DEVICE_ID_LSB);
+	if (id_msb < 0)
+		return -ENXIO;
+
+	phy_id |= id_msb;
+
+	/* Make sure the device tree binding matched the driver with the
+	 * right device.
+	 */
+	if (phy_id != phydev->drv->phy_id) {
+		dev_err(&phydev->mdio.dev,
+			"Error matching phy with %s driver\n",
+			phydev->drv->name);
+		return -ENODEV;
+	}
+
+	/* update the local phydev pointer, used inside all APIs */
+	inphi_phydev = phydev;
+	onesec = msecs_to_jiffies(INPHI_POLL_DELAY);
+
+	wq = create_singlethread_workqueue("inphi_kmod");
+	if (wq) {
+		queue_delayed_work(wq, &mykmod_work, onesec);
+	} else {
+		dev_err(&phydev->mdio.dev,
+			"Error creating kernel workqueue for %s driver\n",
+			phydev->drv->name);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static struct phy_driver inphi_driver[] = {
+{
+	.phy_id		= PHY_ID_IN112525,
+	.phy_id_mask	= 0x0ff0fff0,
+	.name		= "Inphi 112525_S03",
+	.features	= PHY_GBIT_FEATURES,
+	.probe		= &inphi_probe,
+},
+};
+
+module_phy_driver(inphi_driver);
+
+static struct mdio_device_id __maybe_unused inphi_tbl[] = {
+	{ PHY_ID_IN112525, 0x0ff0fff0},
+	{},
+};
+
+MODULE_DEVICE_TABLE(mdio, inphi_tbl);
--- /dev/null
+++ b/drivers/net/phy/mdio-mux-multiplexer.c
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* MDIO bus multiplexer using kernel multiplexer subsystem
+ *
+ * Copyright 2019 NXP
+ */
+
+#include <linux/platform_device.h>
+#include <linux/mdio-mux.h>
+#include <linux/module.h>
+#include <linux/mux/consumer.h>
+
+struct mdio_mux_multiplexer_state {
+	struct mux_control *muxc;
+	bool do_deselect;
+	void *mux_handle;
+};
+
+/**
+ * mdio_mux_multiplexer_switch_fn - This function is called by the mdio-mux
+ *                                  layer when it thinks the mdio bus
+ *                                  multiplexer needs to switch.
+ * @current_child:  current value of the mux register.
+ * @desired_child: value of the 'reg' property of the target child MDIO node.
+ * @data: Private data used by this switch_fn passed to mdio_mux_init function
+ *        via mdio_mux_init(.., .., .., .., data, ..).
+ *
+ * The first time this function is called, current_child == -1.
+ * If current_child == desired_child, then the mux is already set to the
+ * correct bus.
+ */
+static int mdio_mux_multiplexer_switch_fn(int current_child, int desired_child,
+					  void *data)
+{
+	struct platform_device *pdev;
+	struct mdio_mux_multiplexer_state *s;
+	int ret = 0;
+
+	pdev = (struct platform_device *)data;
+	s = platform_get_drvdata(pdev);
+
+	if (!(current_child ^ desired_child))
+		return 0;
+
+	if (s->do_deselect)
+		ret = mux_control_deselect(s->muxc);
+	if (ret) {
+		dev_err(&pdev->dev, "mux_control_deselect failed in %s: %d\n",
+			__func__, ret);
+		return ret;
+	}
+
+	ret =  mux_control_select(s->muxc, desired_child);
+	if (!ret) {
+		dev_dbg(&pdev->dev, "%s %d -> %d\n", __func__, current_child,
+			desired_child);
+		s->do_deselect = true;
+	} else {
+		s->do_deselect = false;
+	}
+
+	return ret;
+}
+
+static int mdio_mux_multiplexer_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct mdio_mux_multiplexer_state *s;
+	int ret = 0;
+
+	s = devm_kzalloc(&pdev->dev, sizeof(*s), GFP_KERNEL);
+	if (!s)
+		return -ENOMEM;
+
+	s->muxc = devm_mux_control_get(dev, NULL);
+	if (IS_ERR(s->muxc)) {
+		ret = PTR_ERR(s->muxc);
+		if (ret != -EPROBE_DEFER)
+			dev_err(&pdev->dev, "Failed to get mux: %d\n", ret);
+		return ret;
+	}
+
+	platform_set_drvdata(pdev, s);
+
+	ret = mdio_mux_init(&pdev->dev, pdev->dev.of_node,
+			    mdio_mux_multiplexer_switch_fn, &s->mux_handle,
+			    pdev, NULL);
+
+	return ret;
+}
+
+static int mdio_mux_multiplexer_remove(struct platform_device *pdev)
+{
+	struct mdio_mux_multiplexer_state *s = platform_get_drvdata(pdev);
+
+	mdio_mux_uninit(s->mux_handle);
+
+	if (s->do_deselect)
+		mux_control_deselect(s->muxc);
+
+	return 0;
+}
+
+static const struct of_device_id mdio_mux_multiplexer_match[] = {
+	{ .compatible = "mdio-mux-multiplexer", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, mdio_mux_multiplexer_match);
+
+static struct platform_driver mdio_mux_multiplexer_driver = {
+	.driver = {
+		.name		= "mdio-mux-multiplexer",
+		.of_match_table	= mdio_mux_multiplexer_match,
+	},
+	.probe		= mdio_mux_multiplexer_probe,
+	.remove		= mdio_mux_multiplexer_remove,
+};
+
+module_platform_driver(mdio_mux_multiplexer_driver);
+
+MODULE_DESCRIPTION("MDIO bus multiplexer using kernel multiplexer subsystem");
+MODULE_AUTHOR("Pankaj Bansal <pankaj.bansal@nxp.com>");
+MODULE_LICENSE("GPL");
--- a/drivers/net/phy/swphy.c
+++ b/drivers/net/phy/swphy.c
@@ -77,6 +77,7 @@ static const struct swmii_regs duplex[]
 static int swphy_decode_speed(int speed)
 {
 	switch (speed) {
+	case 10000:
 	case 1000:
 		return SWMII_SPEED_1000;
 	case 100:
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -87,6 +87,7 @@ typedef enum {
 	PHY_INTERFACE_MODE_XAUI,
 	/* 10GBASE-KR, XFI, SFI - single lane 10G Serdes */
 	PHY_INTERFACE_MODE_10GKR,
+	PHY_INTERFACE_MODE_2500SGMII,
 	PHY_INTERFACE_MODE_MAX,
 } phy_interface_t;
 
@@ -159,6 +160,8 @@ static inline const char *phy_modes(phy_
 		return "xaui";
 	case PHY_INTERFACE_MODE_10GKR:
 		return "10gbase-kr";
+	case PHY_INTERFACE_MODE_2500SGMII:
+		return "sgmii-2500";
 	default:
 		return "unknown";
 	}
