aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/layerscape/patches-4.14/709-mdio-phy-support-layerscape.patch
diff options
context:
space:
mode:
Diffstat (limited to 'target/linux/layerscape/patches-4.14/709-mdio-phy-support-layerscape.patch')
-rw-r--r--target/linux/layerscape/patches-4.14/709-mdio-phy-support-layerscape.patch3456
1 files changed, 2895 insertions, 561 deletions
diff --git a/target/linux/layerscape/patches-4.14/709-mdio-phy-support-layerscape.patch b/target/linux/layerscape/patches-4.14/709-mdio-phy-support-layerscape.patch
index eeb4baaba7..b544114f3e 100644
--- a/target/linux/layerscape/patches-4.14/709-mdio-phy-support-layerscape.patch
+++ b/target/linux/layerscape/patches-4.14/709-mdio-phy-support-layerscape.patch
@@ -1,28 +1,63 @@
-From 8eb578a8c1eb55715a40f02790e43aba4a528c38 Mon Sep 17 00:00:00 2001
+From 83fe1ecb8ac6e0544ae74bf5a63806dcac768201 Mon Sep 17 00:00:00 2001
From: Biwen Li <biwen.li@nxp.com>
-Date: Tue, 30 Oct 2018 18:26:51 +0800
-Subject: [PATCH 15/40] mdio-phy: support layerscae
+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: Biwen Li <biwen.li@nxp.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 | 6 +
- drivers/net/phy/Makefile | 1 +
- drivers/net/phy/fsl_backplane.c | 1358 +++++++++++++++++++++++++++++++
- drivers/net/phy/swphy.c | 1 +
- include/linux/phy.h | 3 +
- 5 files changed, 1369 insertions(+)
+ drivers/net/phy/Kconfig | 33 +
+ drivers/net/phy/Makefile | 5 +
+ drivers/net/phy/aquantia.c | 328 +++-
+ 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, 3526 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
-@@ -90,6 +90,12 @@ config MDIO_BUS_MUX_MMIOREG
+@@ -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
@@ -35,23 +70,515 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
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
-@@ -45,6 +45,7 @@ obj-$(CONFIG_MDIO_BUS_MUX_BCM_IPROC) +=
+@@ -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,200 @@
+
+ #define PHY_AQUANTIA_FEATURES (SUPPORTED_10000baseT_Full | \
+ SUPPORTED_1000baseT_Full | \
++ SUPPORTED_2500baseX_Full | \
+ SUPPORTED_100baseT_Full | \
++ SUPPORTED_Pause | \
++ SUPPORTED_Asym_Pause | \
+ 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
++
++#define MDIO_AN_LPA_PAUSE 0x20
++#define MDIO_AN_LPA_ASYM_PAUSE 0x10
++#define MDIO_AN_ADV_PAUSE 0x20
++#define MDIO_AN_ADV_ASYM_PAUSE 0x10
++
++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;
++ }
++
++ /* advertise flow control */
++ oldadv = aquantia_read_reg(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE);
++ if (oldadv < 0)
++ return oldadv;
++
++ adv = oldadv & ~(MDIO_AN_ADV_PAUSE | MDIO_AN_ADV_ASYM_PAUSE);
++ if (advertise & ADVERTISED_Pause)
++ adv |= MDIO_AN_ADV_PAUSE;
++ if (advertise & ADVERTISED_Asym_Pause)
++ adv |= MDIO_AN_ADV_ASYM_PAUSE;
++
++ if (adv != oldadv) {
++ err = aquantia_write_reg(phydev, MDIO_MMD_AN,
++ MDIO_AN_ADVERTISE, adv);
++ 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 +237,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 +266,145 @@ 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;
++
++ /* flow control advertisement */
++ adv = aquantia_read_reg(phydev, MDIO_MMD_AN, MDIO_AN_ADVERTISE);
++ if (adv & MDIO_AN_ADV_PAUSE)
++ phydev->advertising |= ADVERTISED_Pause;
++ else
++ phydev->advertising &= ~ADVERTISED_Pause;
++ if (adv & MDIO_AN_ADV_ASYM_PAUSE)
++ phydev->advertising |= ADVERTISED_Asym_Pause;
++ else
++ phydev->advertising &= ~ADVERTISED_Asym_Pause;
++
++ 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;
+
++ reg = aquantia_read_reg(phydev, MDIO_MMD_AN, MDIO_AN_LPA);
++ phydev->pause = reg & MDIO_AN_LPA_PAUSE ? 1 : 0;
++ phydev->asym_pause = reg & MDIO_AN_LPA_ASYM_PAUSE ? 1 : 0;
++
++ 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,1358 @@
-+/* Freescale backplane driver.
+@@ -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.
+ */
@@ -70,126 +597,130 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
++#include <linux/netdevice.h>
+
-+/* XFI PCS Device Identifier */
-+#define FSL_PCS_PHY_ID 0x0083e400
++#include "fsl_backplane.h"
+
-+/* Freescale KR PMD registers */
-+#define FSL_KR_PMD_CTRL 0x96
-+#define FSL_KR_PMD_STATUS 0x97
-+#define FSL_KR_LP_CU 0x98
-+#define FSL_KR_LP_STATUS 0x99
-+#define FSL_KR_LD_CU 0x9a
-+#define FSL_KR_LD_STATUS 0x9b
+
-+/* Freescale 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 Device Identifier */
++#define PCS_PHY_DEVICE_ID 0x0083e400
++#define PCS_PHY_DEVICE_ID_MASK 0xffffffff
+
-+#define FSL_KR_RX_LINK_STAT_MASK 0x1000
-+#define FSL_XFI_PCS_10GR_SR1 0x20
++/* 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
+
-+/* Freescale KX PCS mode register */
-+#define FSL_PCS_IF_MODE 0x8014
++/* 10G Short cables setup: up to 30 cm cable */
++//#define RATIO_PREQ_10G 0x3
++//#define RATIO_PST1Q_10G 0xa
++//#define RATIO_EQ_10G 0x29
+
-+/* Freescale KX PCS mode register init value */
-+#define IF_MODE_INIT 0x8
++/* 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
+
-+/* Freescale KX/KR AN registers */
-+#define FSL_AN_AD1 0x11
-+#define FSL_AN_BP_STAT 0x30
++/* 40G Short cables setup: up to 30 cm cable */
++//#define RATIO_PREQ_40G 0x1
++//#define RATIO_PST1Q_40G 0x3
++//#define RATIO_EQ_40G 0x29
+
-+/* Freescale KX/KR AN registers defines */
-+#define AN_CTRL_INIT 0x1200
-+#define KX_AN_AD1_INIT 0x25
-+#define KR_AN_AD1_INIT 0x85
-+#define AN_LNK_UP_MASK 0x4
-+#define KR_AN_MASK 0x8
-+#define TRAIN_FAIL 0x8
++/* LX2 2x40G default RCW setup */
++//#define RATIO_PREQ_40G 0x0
++//#define RATIO_PST1Q_40G 0x3
++//#define RATIO_EQ_40G 0x30
+
-+/* C(-1) */
-+#define BIN_M1 0
-+/* C(1) */
-+#define BIN_LONG 1
-+#define BIN_M1_SEL 6
-+#define BIN_Long_SEL 7
-+#define CDR_SEL_MASK 0x00070000
-+#define BIN_SNAPSHOT_NUM 5
-+#define BIN_M1_THRESHOLD 3
-+#define BIN_LONG_THRESHOLD 2
-+
-+#define PRE_COE_SHIFT 22
-+#define POST_COE_SHIFT 16
-+#define ZERO_COE_SHIFT 8
-+
-+#define PRE_COE_MAX 0x0
-+#define PRE_COE_MIN 0x8
++/* 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
+
-+#define TECR0_INIT 0x24200000
-+#define RATIO_PREQ 0x3
-+#define RATIO_PST1Q 0xd
-+#define RATIO_EQ 0x20
++/* 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
+
-+#define GCR0_RESET_MASK 0x600000
-+#define GCR1_SNP_START_MASK 0x00000040
-+#define GCR1_CTL_SNP_START_MASK 0x00002000
-+#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
++/* PCS Link up */
++#define XFI_PCS_SR1 0x20
++#define KR_RX_LINK_STAT_MASK 0x1000
+
-+#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
++/* KX PCS mode register */
++#define KX_PCS_IF_MODE 0x8014
+
-+#define RECR1_GAINK2_MASK 0x0f000000
-+#define RECR1_GAINK2_SHIFT 24
-+#define RECR1_GAINK3_MASK 0x000f0000
-+#define RECR1_GAINK3_SHIFT 16
-+#define RECR1_OFFSET_MASK 0x00003f80
-+#define RECR1_OFFSET_SHIFT 7
-+#define RECR1_BLW_MASK 0x00000f80
-+#define RECR1_BLW_SHIFT 7
-+#define EYE_CTRL_SHIFT 12
-+#define BASE_WAND_SHIFT 10
++/* 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
+
-+#define INCREMENT 1
-+#define DECREMENT 2
++/* XGKR Increment/Decrement Requests */
++#define INCREMENT 1
++#define DECREMENT 2
+#define TIMEOUT_LONG 3
-+#define TIMEOUT_M1 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 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
++#define FORCE_INC_COP1_NUMBER 0
++#define FORCE_INC_COM1_NUMBER 1
+#endif
+
-+#define VAL_INVALID 0xff
++/* 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};
@@ -199,9 +730,16 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+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,
@@ -221,25 +759,6 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+ TRAINED,
+};
+
-+struct per_lane_ctrl_status {
-+ __be32 gcr0; /* 0x.000 - General Control Register 0 */
-+ __be32 gcr1; /* 0x.004 - General Control Register 1 */
-+ __be32 gcr2; /* 0x.008 - General Control Register 2 */
-+ __be32 resv1; /* 0x.00C - Reserved */
-+ __be32 recr0; /* 0x.010 - Receive Equalization Control Register 0 */
-+ __be32 recr1; /* 0x.014 - Receive Equalization Control Register 1 */
-+ __be32 tecr0; /* 0x.018 - Transmit Equalization Control Register 0 */
-+ __be32 resv2; /* 0x.01C - Reserved */
-+ __be32 tlcr0; /* 0x.020 - TTL Control Register 0 */
-+ __be32 tlcr1; /* 0x.024 - TTL Control Register 1 */
-+ __be32 tlcr2; /* 0x.028 - TTL Control Register 2 */
-+ __be32 tlcr3; /* 0x.02C - TTL Control Register 3 */
-+ __be32 tcsr0; /* 0x.030 - Test Control/Status Register 0 */
-+ __be32 tcsr1; /* 0x.034 - Test Control/Status Register 1 */
-+ __be32 tcsr2; /* 0x.038 - Test Control/Status Register 2 */
-+ __be32 tcsr3; /* 0x.03C - Test Control/Status Register 3 */
-+};
-+
+struct tx_condition {
+ bool bin_m1_late_early;
+ bool bin_long_late_early;
@@ -255,19 +774,152 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+#endif
+};
+
-+struct fsl_xgkr_inst {
-+ void *reg_base;
++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;
@@ -284,88 +936,59 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+#endif
+}
+
-+void tune_tecr0(struct fsl_xgkr_inst *inst)
++void tune_tecr(struct xgkr_params *xgkr)
+{
-+ struct per_lane_ctrl_status *reg_base = inst->reg_base;
-+ u32 val;
-+
-+ val = TECR0_INIT |
-+ inst->adpt_eq << ZERO_COE_SHIFT |
-+ inst->ratio_preq << PRE_COE_SHIFT |
-+ inst->ratio_pst1q << POST_COE_SHIFT;
++ 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);
+
-+ /* reset the lane */
-+ iowrite32(ioread32(&reg_base->gcr0) & ~GCR0_RESET_MASK,
-+ &reg_base->gcr0);
-+ udelay(1);
-+ iowrite32(val, &reg_base->tecr0);
-+ udelay(1);
-+ /* unreset the lane */
-+ iowrite32(ioread32(&reg_base->gcr0) | GCR0_RESET_MASK,
-+ &reg_base->gcr0);
-+ udelay(1);
++ 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 phy_device *phydev)
++static void start_lt(struct xgkr_params *xgkr)
+{
-+ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, FSL_KR_PMD_CTRL, TRAIN_EN);
++ xgkr_phy_write_mmd(xgkr, lt_MDIO_MMD, lt_KR_PMD_CTRL, TRAIN_EN);
+}
+
-+static void stop_lt(struct phy_device *phydev)
++static void stop_lt(struct xgkr_params *xgkr)
+{
-+ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, FSL_KR_PMD_CTRL, TRAIN_DISABLE);
++ xgkr_phy_write_mmd(xgkr, lt_MDIO_MMD, lt_KR_PMD_CTRL, TRAIN_DISABLE);
+}
+
-+static void reset_gcr0(struct fsl_xgkr_inst *inst)
++static void reset_lt(struct xgkr_params *xgkr)
+{
-+ struct per_lane_ctrl_status *reg_base = inst->reg_base;
-+
-+ iowrite32(ioread32(&reg_base->gcr0) & ~GCR0_RESET_MASK,
-+ &reg_base->gcr0);
-+ udelay(1);
-+ iowrite32(ioread32(&reg_base->gcr0) | GCR0_RESET_MASK,
-+ &reg_base->gcr0);
-+ udelay(1);
++ 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);
++
+}
+
-+void lane_set_1gkx(void *reg)
++static void ld_coe_status(struct xgkr_params *xgkr)
+{
-+ struct per_lane_ctrl_status *reg_base = reg;
-+ u32 val;
-+
-+ /* reset the lane */
-+ iowrite32(ioread32(&reg_base->gcr0) & ~GCR0_RESET_MASK,
-+ &reg_base->gcr0);
-+ udelay(1);
-+
-+ /* set gcr1 for 1GKX */
-+ val = ioread32(&reg_base->gcr1);
-+ val &= ~(GCR1_REIDL_TH_MASK | GCR1_REIDL_EX_SEL_MASK |
-+ GCR1_REIDL_ET_MAS_MASK);
-+ iowrite32(val, &reg_base->gcr1);
-+ udelay(1);
-+
-+ /* set tecr0 for 1GKX */
-+ val = ioread32(&reg_base->tecr0);
-+ val &= ~TECR0_AMP_RED_MASK;
-+ iowrite32(val, &reg_base->tecr0);
-+ udelay(1);
-+
-+ /* unreset the lane */
-+ iowrite32(ioread32(&reg_base->gcr0) | GCR0_RESET_MASK,
-+ &reg_base->gcr0);
-+ udelay(1);
++ xgkr_phy_write_mmd(xgkr, lt_MDIO_MMD,
++ lt_KR_LD_STATUS, xgkr->ld_status);
+}
+
-+static void reset_lt(struct phy_device *phydev)
++static void ld_coe_update(struct xgkr_params *xgkr)
+{
-+ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1, PMD_RESET);
-+ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, FSL_KR_PMD_CTRL, TRAIN_DISABLE);
-+ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, FSL_KR_LD_CU, 0);
-+ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, FSL_KR_LD_STATUS, 0);
-+ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, FSL_KR_PMD_STATUS, 0);
-+ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, FSL_KR_LP_CU, 0);
-+ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, FSL_KR_LP_STATUS, 0);
++ 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)
@@ -374,191 +997,110 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+ msecs_to_jiffies(XGKR_TIMEOUT));
+}
+
-+static void start_xgkr_an(struct phy_device *phydev)
++static void start_xgkr_an(struct xgkr_params *xgkr)
+{
-+ struct fsl_xgkr_inst *inst;
++ struct phy_device *phydev = xgkr->phydev;
++ struct xgkr_phy_data *xgkr_inst = phydev->priv;
++ int i;
++ int err;
+
-+ reset_lt(phydev);
-+ phy_write_mmd(phydev, MDIO_MMD_AN, FSL_AN_AD1, KR_AN_AD1_INIT);
-+ phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1, AN_CTRL_INIT);
++ switch (xgkr_inst->bp_mode)
++ {
++ case PHY_BACKPLANE_1000BASE_KX:
++ dev_err(&phydev->mdio.dev, "Wrong call path for 1000Base-KX \n");
++ break;
+
-+ inst = phydev->priv;
++ 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;
+
-+ /* start state machine*/
-+ start_xgkr_state_machine(&inst->xgkr_wk);
++ 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, FSL_PCS_IF_MODE, IF_MODE_INIT);
-+ phy_write_mmd(phydev, MDIO_MMD_AN, FSL_AN_AD1, KX_AN_AD1_INIT);
++ 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 ld_coe_status(struct fsl_xgkr_inst *inst)
++static void reset_tecr(struct xgkr_params *xgkr)
+{
-+ phy_write_mmd(inst->phydev, MDIO_MMD_PMAPMD,
-+ FSL_KR_LD_STATUS, inst->ld_status);
-+}
++ struct phy_device *phydev = xgkr->phydev;
++ struct xgkr_phy_data *xgkr_inst = phydev->priv;
+
-+static void ld_coe_update(struct fsl_xgkr_inst *inst)
-+{
-+ dev_dbg(&inst->phydev->mdio.dev, "sending request: %x\n", inst->ld_update);
-+ phy_write_mmd(inst->phydev, MDIO_MMD_PMAPMD,
-+ FSL_KR_LD_CU, inst->ld_update);
-+}
++ switch (xgkr_inst->bp_mode)
++ {
++ case PHY_BACKPLANE_1000BASE_KX:
++ dev_err(&phydev->mdio.dev, "Wrong call path for 1000Base-KX \n");
++ break;
+
-+static void init_inst(struct fsl_xgkr_inst *inst, int reset)
-+{
-+ if (reset) {
-+ inst->ratio_preq = RATIO_PREQ;
-+ inst->ratio_pst1q = RATIO_PST1Q;
-+ inst->adpt_eq = RATIO_EQ;
-+ tune_tecr0(inst);
++ 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;
+ }
+
-+ tx_condition_init(&inst->tx_c);
-+ inst->state = DETECTING_LP;
-+ inst->ld_status &= RX_READY_MASK;
-+ ld_coe_status(inst);
-+ inst->ld_update = 0;
-+ inst->ld_status &= ~RX_READY_MASK;
-+ ld_coe_status(inst);
++ tune_tecr(xgkr);
+}
+
-+#ifdef NEW_ALGORITHM_TRAIN_TX
-+static int get_median_gaink2(u32 *reg)
++static void init_xgkr(struct xgkr_params *xgkr, int reset)
+{
-+ 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 (ioread32(&reg_base->recr1) &
-+ RECR1_CTL_SNP_DONE_MASK) {
-+ udelay(1);
-+ timeout--;
-+ if (timeout == 0)
-+ break;
-+ }
-+
-+ /* start snap shot */
-+ iowrite32((ioread32(&reg_base->gcr1) |
-+ GCR1_CTL_SNP_START_MASK),
-+ &reg_base->gcr1);
-+
-+ /* wait for SNP done */
-+ timeout = 100;
-+ while (!(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 = ioread32(&reg_base->recr1);
-+ gaink2_snap_shot[i] = (rx_eq_snp & RECR1_GAINK2_MASK) >>
-+ RECR1_GAINK2_SHIFT;
++ if (reset)
++ reset_tecr(xgkr);
+
-+ /* terminate the snap shot by setting GCR1[REQ_CTL_SNP] */
-+ iowrite32((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;
-+ }
-+ }
++ tx_condition_init(&xgkr->tx_c);
++ xgkr->state = DETECTING_LP;
+
-+ gaink2_snap_shot[pos] = gaink2_snap_shot[i];
-+ gaink2_snap_shot[i] = tmp;
-+ }
++ xgkr->ld_status &= RX_READY_MASK;
++ ld_coe_status(xgkr);
++ xgkr->ld_update = 0;
++ xgkr->ld_status &= ~RX_READY_MASK;
++ ld_coe_status(xgkr);
+
-+ return gaink2_snap_shot[2];
+}
-+#endif
+
-+static bool is_bin_early(int bin_sel, void *reg)
++static void initialize(struct xgkr_params *xgkr)
+{
-+ 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 ((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) {
-+ iowrite32((ioread32(&reg_base->tcsr1) &
-+ ~CDR_SEL_MASK) | BIN_M1_SEL,
-+ &reg_base->tcsr1);
-+ } else {
-+ iowrite32((ioread32(&reg_base->tcsr1) &
-+ ~CDR_SEL_MASK) | BIN_Long_SEL,
-+ &reg_base->tcsr1);
-+ }
-+
-+ /* start snap shot */
-+ iowrite32(ioread32(&reg_base->gcr1) | GCR1_SNP_START_MASK,
-+ &reg_base->gcr1);
-+
-+ /* wait for SNP done */
-+ timeout = 100;
-+ while (!(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] = (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] */
-+ iowrite32(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;
-+ }
++ reset_tecr(xgkr);
+
-+ return early;
++ 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_tx(struct fsl_xgkr_inst *inst)
++static void train_remote_tx(struct xgkr_params *xgkr)
+{
-+ struct phy_device *phydev = inst->phydev;
-+ struct tx_condition *tx_c = &inst->tx_c;
++ 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;
@@ -571,11 +1113,13 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+recheck:
+ if (tx_c->bin_long_stop && tx_c->bin_m1_stop) {
+ tx_c->tx_complete = true;
-+ inst->ld_status |= RX_READY_MASK;
-+ ld_coe_status(inst);
++ xgkr->ld_status |= RX_READY_MASK;
++ ld_coe_status(xgkr);
++
+ /* tell LP we are ready */
-+ phy_write_mmd(phydev, MDIO_MMD_PMAPMD,
-+ FSL_KR_PMD_STATUS, RX_STAT);
++ xgkr_phy_write_mmd(xgkr, lt_MDIO_MMD,
++ lt_KR_PMD_STATUS, RX_STAT);
++
+ return;
+ }
+
@@ -583,13 +1127,14 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+ * we can clear up the appropriate update request so that the
+ * subsequent code may easily issue new update requests if needed.
+ */
-+ lp_status = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, FSL_KR_LP_STATUS) &
++ 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 = inst->ld_update;
++ 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;
@@ -603,7 +1148,7 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+ 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)) {
-+ inst->ld_update &= ~PRESET_MASK;
++ xgkr->ld_update &= ~PRESET_MASK;
+ }
+ }
+
@@ -614,7 +1159,7 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+ if (status_cop1 != COE_NOTUPDATED &&
+ status_coz != COE_NOTUPDATED &&
+ status_com1 != COE_NOTUPDATED) {
-+ inst->ld_update &= ~INIT_MASK;
++ xgkr->ld_update &= ~INIT_MASK;
+ }
+ }
+
@@ -624,7 +1169,7 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+ */
+ if (!tx_c->sent_init) {
+ if (!lp_status && !(old_ld_update & (LD_ALL_MASK))) {
-+ inst->ld_update = INIT_MASK;
++ xgkr->ld_update = INIT_MASK;
+ tx_c->sent_init = true;
+ }
+ }
@@ -636,7 +1181,7 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+ */
+ if (status_cop1 != COE_NOTUPDATED) {
+ if (req_cop1) {
-+ inst->ld_update &= ~COP1_MASK;
++ xgkr->ld_update &= ~COP1_MASK;
+#ifdef NEW_ALGORITHM_TRAIN_TX
+ if (tx_c->post_inc) {
+ if (req_cop1 == INCREMENT &&
@@ -648,19 +1193,19 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+ tx_c->post_inc -= 1;
+ }
+
-+ ld_coe_update(inst);
++ ld_coe_update(xgkr);
+ goto recheck;
+ }
+#endif
+ if ((req_cop1 == DECREMENT && status_cop1 == COE_MIN) ||
+ (req_cop1 == INCREMENT && status_cop1 == COE_MAX)) {
-+ dev_dbg(&inst->phydev->mdio.dev, "COP1 hit limit %s",
++ 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(inst);
++ ld_coe_update(xgkr);
+ goto recheck;
+ }
+ }
@@ -669,12 +1214,12 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+
+ if (status_coz != COE_NOTUPDATED) {
+ if (req_coz)
-+ inst->ld_update &= ~COZ_MASK;
++ xgkr->ld_update &= ~COZ_MASK;
+ }
+
+ if (status_com1 != COE_NOTUPDATED) {
+ if (req_com1) {
-+ inst->ld_update &= ~COM1_MASK;
++ xgkr->ld_update &= ~COM1_MASK;
+#ifdef NEW_ALGORITHM_TRAIN_TX
+ if (tx_c->pre_inc) {
+ if (req_com1 == INCREMENT &&
@@ -683,28 +1228,28 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+ else
+ tx_c->pre_inc -= 1;
+
-+ ld_coe_update(inst);
++ 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(&inst->phydev->mdio.dev, "COM1 hit limit %s",
++ 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(inst);
++ ld_coe_update(xgkr);
+ goto recheck;
+ }
+ }
+ }
+ }
+
-+ if (old_ld_update != inst->ld_update) {
-+ ld_coe_update(inst);
++ 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.
+ */
@@ -721,15 +1266,15 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+ return;
+
+#ifdef NEW_ALGORITHM_TRAIN_TX
-+ if (!(inst->ld_update & (PRESET_MASK | INIT_MASK))) {
++ if (!(xgkr->ld_update & (PRESET_MASK | INIT_MASK))) {
+ if (tx_c->pre_inc) {
-+ inst->ld_update = INCREMENT << COM1_SHIFT;
-+ ld_coe_update(inst);
++ xgkr->ld_update = INCREMENT << COM1_SHIFT;
++ ld_coe_update(xgkr);
+ return;
+ }
+
+ if (status_cop1 != COE_MAX) {
-+ median_gaink2 = get_median_gaink2(inst->reg_base);
++ median_gaink2 = xgkr->srds->get_median_gaink2(xgkr->reg_base);
+ if (median_gaink2 == 0xf) {
+ tx_c->post_inc = 1;
+ } else {
@@ -746,16 +1291,16 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+ }
+
+ if (tx_c->post_inc) {
-+ inst->ld_update = INCREMENT << COP1_SHIFT;
-+ ld_coe_update(inst);
++ xgkr->ld_update = INCREMENT << COP1_SHIFT;
++ ld_coe_update(xgkr);
+ return;
+ }
+ }
+#endif
+
+ /* snapshot and select bin */
-+ bin_m1_early = is_bin_early(BIN_M1, inst->reg_base);
-+ bin_long_early = is_bin_early(BIN_LONG, inst->reg_base);
++ 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;
@@ -773,39 +1318,39 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+ * pending. We also only request coefficient updates when the
+ * corresponding status is NOT UPDATED and nothing is pending.
+ */
-+ if (!(inst->ld_update & (PRESET_MASK | INIT_MASK))) {
++ if (!(xgkr->ld_update & (PRESET_MASK | INIT_MASK))) {
+ if (!tx_c->bin_long_stop) {
+ /* BinM1 correction means changing COM1 */
-+ if (!status_com1 && !(inst->ld_update & COM1_MASK)) {
++ 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;
-+ inst->ld_update = temp;
-+ ld_coe_update(inst);
++ 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 && !(inst->ld_update & COP1_MASK)) {
++ 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;
-+ inst->ld_update = temp;
++ xgkr->ld_update = temp;
+ } else {
+ /* request decrement c(1) */
+ temp = DECREMENT << COP1_SHIFT;
-+ inst->ld_update = temp;
++ xgkr->ld_update = temp;
+ }
+
-+ ld_coe_update(inst);
++ ld_coe_update(xgkr);
+ tx_c->bin_long_late_early = bin_long_early;
+ }
+ /* We try to finish BinLong before we do BinM1 */
@@ -814,19 +1359,19 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+
+ if (!tx_c->bin_m1_stop) {
+ /* BinM1 correction means changing COM1 */
-+ if (!status_com1 && !(inst->ld_update & COM1_MASK)) {
++ 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;
-+ inst->ld_update = temp;
++ xgkr->ld_update = temp;
+ } else {
+ /* request decrement c(-1) */
+ temp = DECREMENT << COM1_SHIFT;
-+ inst->ld_update = temp;
++ xgkr->ld_update = temp;
+ }
+
-+ ld_coe_update(inst);
++ ld_coe_update(xgkr);
+ tx_c->bin_m1_late_early = bin_m1_early;
+ }
+ }
@@ -835,20 +1380,26 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+
+static int is_link_up(struct phy_device *phydev)
+{
-+ int val;
++ struct xgkr_phy_data *xgkr_inst = phydev->priv;
++ int val = 0;
++
++ mutex_lock(&xgkr_inst->phy_lock);
+
-+ phy_read_mmd(phydev, MDIO_MMD_PCS, FSL_XFI_PCS_10GR_SR1);
-+ val = phy_read_mmd(phydev, MDIO_MMD_PCS, FSL_XFI_PCS_10GR_SR1);
++ val = phy_read_mmd(phydev, MDIO_MMD_PCS, XFI_PCS_SR1);
+
-+ return (val & FSL_KR_RX_LINK_STAT_MASK) ? 1 : 0;
++ mutex_unlock(&xgkr_inst->phy_lock);
++
++ return (val & KR_RX_LINK_STAT_MASK) ? 1 : 0;
+}
+
-+static int is_link_training_fail(struct phy_device *phydev)
++static int is_link_training_fail(struct xgkr_params *xgkr)
+{
++ struct phy_device *phydev = xgkr->phydev;
+ int val;
+ int timeout = 100;
+
-+ val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, FSL_KR_PMD_STATUS);
++ 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--) {
@@ -862,18 +1413,18 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+ return 1;
+}
+
-+static int check_rx(struct phy_device *phydev)
++static int check_rx(struct xgkr_params *xgkr)
+{
-+ return phy_read_mmd(phydev, MDIO_MMD_PMAPMD, FSL_KR_LP_STATUS) &
++ 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 fsl_xgkr_inst *inst)
++static int is_ld_valid(struct xgkr_params *xgkr)
+{
-+ u32 ratio_pst1q = inst->ratio_pst1q;
-+ u32 adpt_eq = inst->adpt_eq;
-+ u32 ratio_preq = inst->ratio_preq;
++ 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;
@@ -909,15 +1460,15 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+ }
+}
+
-+static int inc_dec(struct fsl_xgkr_inst *inst, int field, int request)
++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] = inst->ratio_pst1q;
-+ ld_coe[1] = inst->adpt_eq;
-+ ld_coe[2] = inst->ratio_preq;
++ ld_coe[0] = xgkr->ratio_pst1q;
++ ld_coe[1] = xgkr->adpt_eq;
++ ld_coe[2] = xgkr->ratio_preq;
+
-+ /* Information specific to the Freescale SerDes for 10GBase-KR:
++ /* 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
@@ -935,7 +1486,7 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+ ld_coe[field] += step[field];
+ else
+ /* MAX */
-+ return 2;
++ return COE_MAX;
+ break;
+ case DECREMENT:
+ ld_limit[0] = POST_COE_MIN;
@@ -945,76 +1496,83 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+ ld_coe[field] -= step[field];
+ else
+ /* MIN */
-+ return 1;
++ return COE_MIN;
+ break;
+ default:
+ break;
+ }
+
-+ if (is_ld_valid(inst)) {
++ if (is_ld_valid(xgkr)) {
+ /* accept new ld */
-+ inst->ratio_pst1q = ld_coe[0];
-+ inst->adpt_eq = ld_coe[1];
-+ inst->ratio_preq = ld_coe[2];
++ 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(&inst->phydev->mdio.dev,
++ dev_dbg(&xgkr->phydev->mdio.dev,
+ "preq skipped value: %d\n", ld_coe[2]);
-+ return 0;
++ /* NOT UPDATED */
++ return COE_NOTUPDATED;
+ }
+
+ if (!is_value_allowed((const u32 *)&pst1q_table, ld_coe[0])) {
-+ dev_dbg(&inst->phydev->mdio.dev,
++ dev_dbg(&xgkr->phydev->mdio.dev,
+ "pst1q skipped value: %d\n", ld_coe[0]);
-+ return 0;
++ /* NOT UPDATED */
++ return COE_NOTUPDATED;
+ }
+
-+ tune_tecr0(inst);
++ tune_tecr(xgkr);
+ } else {
+ if (request == DECREMENT)
+ /* MIN */
-+ return 1;
++ return COE_MIN;
+ if (request == INCREMENT)
+ /* MAX */
-+ return 2;
++ return COE_MAX;
+ }
+
-+ return 0;
++ /* UPDATED */
++ return COE_UPDATED;
+}
+
-+static void min_max_updated(struct fsl_xgkr_inst *inst, int field, int new_ld)
++static void min_max_updated(struct xgkr_params *xgkr, int field, enum coe_update cs)
+{
-+ u32 ld_coe[] = {COE_UPDATED, COE_MIN, COE_MAX};
+ u32 mask, val;
++ u32 ld_cs = cs;
++
++ if (cs == COE_INV)
++ return;
+
+ switch (field) {
+ case COE_COP1:
+ mask = COP1_MASK;
-+ val = ld_coe[new_ld] << COP1_SHIFT;
++ val = ld_cs << COP1_SHIFT;
+ break;
+ case COE_COZ:
+ mask = COZ_MASK;
-+ val = ld_coe[new_ld] << COZ_SHIFT;
++ val = ld_cs << COZ_SHIFT;
+ break;
+ case COE_COM:
+ mask = COM1_MASK;
-+ val = ld_coe[new_ld] << COM1_SHIFT;
++ val = ld_cs << COM1_SHIFT;
+ break;
+ default:
+ return;
+ }
+
-+ inst->ld_status &= ~mask;
-+ inst->ld_status |= val;
++ xgkr->ld_status &= ~mask;
++ xgkr->ld_status |= val;
+}
+
-+static void check_request(struct fsl_xgkr_inst *inst, int request)
++static void check_request(struct xgkr_params *xgkr, int request)
+{
+ int cop1_req, coz_req, com_req;
-+ int old_status, new_ld_sta;
++ int old_status;
++ enum coe_update cu;
+
+ cop1_req = (request & COP1_MASK) >> COP1_SHIFT;
+ coz_req = (request & COZ_MASK) >> COZ_SHIFT;
@@ -1023,65 +1581,51 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+ /* IEEE802.3-2008, 72.6.10.2.5
+ * Ensure we only act on INCREMENT/DECREMENT when we are in NOT UPDATED
+ */
-+ old_status = inst->ld_status;
++ old_status = xgkr->ld_status;
+
-+ if (cop1_req && !(inst->ld_status & COP1_MASK)) {
-+ new_ld_sta = inc_dec(inst, COE_COP1, cop1_req);
-+ min_max_updated(inst, COE_COP1, new_ld_sta);
++ 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 && !(inst->ld_status & COZ_MASK)) {
-+ new_ld_sta = inc_dec(inst, COE_COZ, coz_req);
-+ min_max_updated(inst, COE_COZ, new_ld_sta);
++ 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 && !(inst->ld_status & COM1_MASK)) {
-+ new_ld_sta = inc_dec(inst, COE_COM, com_req);
-+ min_max_updated(inst, COE_COM, new_ld_sta);
++ 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 != inst->ld_status)
-+ ld_coe_status(inst);
++ if (old_status != xgkr->ld_status)
++ ld_coe_status(xgkr);
+}
+
-+static void preset(struct fsl_xgkr_inst *inst)
++static void preset(struct xgkr_params *xgkr)
+{
+ /* These are all MAX values from the IEEE802.3 perspective. */
-+ inst->ratio_pst1q = POST_COE_MAX;
-+ inst->adpt_eq = ZERO_COE_MAX;
-+ inst->ratio_preq = PRE_COE_MAX;
++ xgkr->ratio_pst1q = POST_COE_MAX;
++ xgkr->adpt_eq = ZERO_COE_MAX;
++ xgkr->ratio_preq = PRE_COE_MAX;
+
-+ tune_tecr0(inst);
-+ inst->ld_status &= ~(COP1_MASK | COZ_MASK | COM1_MASK);
-+ inst->ld_status |= COE_MAX << COP1_SHIFT |
++ 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(inst);
-+}
-+
-+static void initialize(struct fsl_xgkr_inst *inst)
-+{
-+ inst->ratio_preq = RATIO_PREQ;
-+ inst->ratio_pst1q = RATIO_PST1Q;
-+ inst->adpt_eq = RATIO_EQ;
-+
-+ tune_tecr0(inst);
-+ inst->ld_status &= ~(COP1_MASK | COZ_MASK | COM1_MASK);
-+ inst->ld_status |= COE_UPDATED << COP1_SHIFT |
-+ COE_UPDATED << COZ_SHIFT |
-+ COE_UPDATED << COM1_SHIFT;
-+ ld_coe_status(inst);
++ ld_coe_status(xgkr);
+}
+
-+static void train_rx(struct fsl_xgkr_inst *inst)
++static void train_local_tx(struct xgkr_params *xgkr)
+{
-+ struct phy_device *phydev = inst->phydev;
+ int request, old_ld_status;
+
+ /* get request from LP */
-+ request = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, FSL_KR_LP_CU) &
++ request = xgkr_phy_read_mmd(xgkr, lt_MDIO_MMD, lt_KR_LP_CU) &
+ (LD_ALL_MASK);
-+ old_ld_status = inst->ld_status;
++
++ 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
@@ -1092,69 +1636,87 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+ */
+ if (!(request & (PRESET_MASK | INIT_MASK))) {
+ if (!(request & COP1_MASK))
-+ inst->ld_status &= ~COP1_MASK;
++ xgkr->ld_status &= ~COP1_MASK;
+
+ if (!(request & COZ_MASK))
-+ inst->ld_status &= ~COZ_MASK;
++ xgkr->ld_status &= ~COZ_MASK;
+
+ if (!(request & COM1_MASK))
-+ inst->ld_status &= ~COM1_MASK;
++ xgkr->ld_status &= ~COM1_MASK;
+
-+ if (old_ld_status != inst->ld_status)
-+ ld_coe_status(inst);
++ 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(phydev)) {
++ if (check_rx(xgkr)) {
+ /* LP receiver is ready */
-+ if (inst->ld_status & (COP1_MASK | COZ_MASK | COM1_MASK)) {
-+ inst->ld_status &= ~(COP1_MASK | COZ_MASK | COM1_MASK);
-+ ld_coe_status(inst);
++ 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 (!(inst->ld_status &
++ if (!(xgkr->ld_status &
+ (COP1_MASK | COZ_MASK | COM1_MASK))) {
+ if (request & PRESET_MASK)
-+ preset(inst);
++ preset(xgkr);
+
+ if (request & INIT_MASK)
-+ initialize(inst);
++ initialize(xgkr);
+ }
+ }
+
+ /* LP Coefficient are not in HOLD */
+ if (request & REQUEST_MASK)
-+ check_request(inst, request & REQUEST_MASK);
++ check_request(xgkr, request & REQUEST_MASK);
+ }
+}
+
-+static void xgkr_start_train(struct phy_device *phydev)
++static void xgkr_start_train(struct xgkr_params *xgkr)
+{
-+ struct fsl_xgkr_inst *inst = phydev->priv;
-+ struct tx_condition *tx_c = &inst->tx_c;
-+ int val = 0, i;
++ 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 rx_ok, tx_ok;
-+
-+ init_inst(inst, 0);
-+ start_lt(phydev);
++ 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(500);
++
++ dead_line = jiffies + msecs_to_jiffies(lt_timeout);
++
+ while (time_before(jiffies, dead_line)) {
-+ val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD,
-+ FSL_KR_PMD_STATUS);
++
++ 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.
+ */
-+ reset_gcr0(inst);
-+ start_lt(phydev);
++ 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;
@@ -1168,105 +1730,293 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+ }
+
+ /* init process */
-+ rx_ok = false;
-+ tx_ok = false;
++ lp_rx_ready = false;
++ tx_training_complete = false;
+ /* the LT should be finished in 500ms, failed or OK. */
-+ dead_line = jiffies + msecs_to_jiffies(500);
++ dead_line = jiffies + msecs_to_jiffies(lt_timeout);
+
+ while (time_before(jiffies, dead_line)) {
+ /* check if the LT is already failed */
-+ lt_state = phy_read_mmd(phydev, MDIO_MMD_PMAPMD,
-+ FSL_KR_PMD_STATUS);
++
++ lt_state = xgkr_phy_read_mmd(xgkr, lt_MDIO_MMD,
++ lt_KR_PMD_STATUS);
++
+ if (lt_state & TRAIN_FAIL) {
-+ reset_gcr0(inst);
++
++ 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;
+ }
+
-+ rx_ok = check_rx(phydev);
-+ tx_ok = tx_c->tx_complete;
++ lp_rx_ready = check_rx(xgkr);
++ tx_training_complete = tx_c->tx_complete;
+
-+ if (rx_ok && tx_ok)
++ if (lp_rx_ready && tx_training_complete)
+ break;
+
-+ if (!rx_ok)
-+ train_rx(inst);
++ if (!lp_rx_ready)
++ train_local_tx(xgkr);
+
-+ if (!tx_ok)
-+ train_tx(inst);
++ if (!tx_training_complete)
++ train_remote_tx(xgkr);
+
+ usleep_range(100, 500);
+ }
+
+ i++;
+ /* check LT result */
-+ if (is_link_training_fail(phydev)) {
-+ init_inst(inst, 0);
++ if (is_link_training_fail(xgkr)) {
++ init_xgkr(xgkr, 0);
+ continue;
+ } else {
-+ stop_lt(phydev);
-+ inst->state = TRAINED;
++ 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 fsl_xgkr_inst *inst = container_of(dwork,
-+ struct fsl_xgkr_inst,
-+ xgkr_wk);
-+ struct phy_device *phydev = inst->phydev;
++ 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 needs_train = false;
++ bool start_train = false;
++ bool all_lanes_trained = false;
++ int i;
+
-+ mutex_lock(&phydev->lock);
++ if (!xgkr_inst->aneg_done) {
++ start_xgkr_state_machine(&xgkr->xgkr_wk);
++ return;
++ }
+
-+ switch (inst->state) {
++ mutex_lock(&phydev->lock);
++
++ switch (xgkr->state) {
+ case DETECTING_LP:
-+ phy_read_mmd(phydev, MDIO_MMD_AN, FSL_AN_BP_STAT);
-+ an_state = phy_read_mmd(phydev, MDIO_MMD_AN, FSL_AN_BP_STAT);
-+ if ((an_state & KR_AN_MASK))
-+ needs_train = true;
++
++ 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)) {
-+ dev_info(&phydev->mdio.dev,
-+ "Detect hotplug, restart training\n");
-+ init_inst(inst, 1);
-+ start_xgkr_an(phydev);
-+ inst->state = 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:
++ 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 (needs_train)
-+ xgkr_start_train(phydev);
++ if (start_train) {
++ xgkr_start_train(xgkr);
++ }
+
+ mutex_unlock(&phydev->lock);
-+ queue_delayed_work(system_power_efficient_wq, &inst->xgkr_wk,
-+ msecs_to_jiffies(XGKR_TIMEOUT));
++ start_xgkr_state_machine(&xgkr->xgkr_wk);
+}
+
+static int fsl_backplane_probe(struct phy_device *phydev)
+{
-+ struct fsl_xgkr_inst *xgkr_inst;
++ 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;
++ int ret, i, phy_lanes;
+ int bp_mode;
-+ u32 lane[2];
++ 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 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;
@@ -1278,6 +2028,20 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+ 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");
@@ -1286,25 +2050,43 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+
+ of_node_put(lane_node);
+ ret = of_property_read_u32_array(phy_node, "fsl,lane-reg",
-+ (u32 *)&lane, 2);
++ (u32 *)lane_base_addr, phy_lanes);
+ if (ret) {
+ dev_err(&phydev->mdio.dev, "could not get fsl,lane-reg\n");
+ return -EINVAL;
+ }
+
-+ phydev->priv = devm_ioremap_nocache(&phydev->mdio.dev,
-+ res_lane.start + lane[0],
-+ lane[1]);
-+ if (!phydev->priv) {
-+ dev_err(&phydev->mdio.dev, "ioremap_nocache failed\n");
-+ return -ENOMEM;
++ 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 (bp_mode == PHY_BACKPLANE_1000BASE_KX) {
-+ phydev->speed = SPEED_1000;
-+ /* configure the lane for 1000BASE-KX */
-+ lane_set_1gkx(phydev->priv);
-+ return 0;
++ 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,
@@ -1312,13 +2094,46 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+ if (!xgkr_inst)
+ return -ENOMEM;
+
-+ xgkr_inst->reg_base = phydev->priv;
-+ xgkr_inst->phydev = phydev;
++ 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;
+
-+ if (bp_mode == PHY_BACKPLANE_10GBASE_KR) {
++ 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_wk, xgkr_state_machine);
++ 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;
@@ -1326,17 +2141,56 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+
+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)
+{
-+ if (phydev->speed == SPEED_10000) {
-+ phydev->supported |= SUPPORTED_10000baseKR_Full;
-+ start_xgkr_an(phydev);
-+ } else if (phydev->speed == SPEED_1000) {
++ 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;
@@ -1347,29 +2201,48 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+
+static int fsl_backplane_suspend(struct phy_device *phydev)
+{
-+ if (phydev->speed == SPEED_10000) {
-+ struct fsl_xgkr_inst *xgkr_inst = phydev->priv;
++ int i;
+
-+ cancel_delayed_work_sync(&xgkr_inst->xgkr_wk);
++ 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)
+{
-+ if (phydev->speed == SPEED_10000) {
-+ struct fsl_xgkr_inst *xgkr_inst = phydev->priv;
++ struct xgkr_phy_data *xgkr_inst = phydev->priv;
++ int i;
+
-+ init_inst(xgkr_inst, 1);
-+ queue_delayed_work(system_power_efficient_wq,
-+ &xgkr_inst->xgkr_wk,
-+ msecs_to_jiffies(XGKR_TIMEOUT));
++ 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
@@ -1378,11 +2251,82 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+ 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 = FSL_PCS_PHY_ID,
++ .phy_id = PCS_PHY_DEVICE_ID,
+ .name = "Freescale Backplane",
-+ .phy_id_mask = 0xffffffff,
++ .phy_id_mask = PCS_PHY_DEVICE_ID_MASK,
+ .features = SUPPORTED_Backplane | SUPPORTED_Autoneg |
+ SUPPORTED_MII,
+ .probe = fsl_backplane_probe,
@@ -1391,13 +2335,14 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+ .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[] = {
-+ { FSL_PCS_PHY_ID, 0xffffffff },
++ { PCS_PHY_DEVICE_ID, PCS_PHY_DEVICE_ID_MASK },
+ { }
+};
+
@@ -1406,6 +2351,1395 @@ Signed-off-by: Biwen Li <biwen.li@nxp.com>
+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[]