diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index f257023..fa48b8e 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -549,6 +549,8 @@ config XILINX_GMII2RGMII
 	  the Reduced Gigabit Media Independent Interface(RGMII) between
 	  Ethernet physical media devices and the Gigabit Ethernet controller.
 
+source "drivers/net/phy/backplane/Kconfig"
+
 endif # PHYLIB
 
 config MICREL_KS8995MA
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index dc9e53b..4849c16 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -104,3 +104,4 @@ obj-$(CONFIG_STE10XP)		+= ste10Xp.o
 obj-$(CONFIG_TERANETICS_PHY)	+= teranetics.o
 obj-$(CONFIG_VITESSE_PHY)	+= vitesse.o
 obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o
+obj-$(CONFIG_ETH_BACKPLANE)	+= backplane/
diff --git a/drivers/net/phy/backplane/Kconfig b/drivers/net/phy/backplane/Kconfig
new file mode 100644
index 0000000..9ec54b5
--- /dev/null
+++ b/drivers/net/phy/backplane/Kconfig
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
+config ETH_BACKPLANE
+	tristate "Ethernet Backplane support"
+	depends on OF_MDIO
+	help
+	  This module provides driver support for Ethernet Operation over
+	  Electrical Backplanes. It includes Backplane generic
+	  driver including support for Link Training (IEEE802.3ap/ba).
+	  Based on the link quality, a signal equalization is required.
+	  The standard specifies that a start-up algorithm should be in place
+	  in order to get the link up.
+
+config ETH_BACKPLANE_FIXED
+	tristate "Fixed: No Equalization algorithm"
+	depends on ETH_BACKPLANE
+	help
+	  This module provides a driver to setup fixed user configurable
+	  coefficient values for backplanes equalization. This means
+	  No Equalization algorithm is used to adapt the initial coefficients
+	  initially set by the user.
\ No newline at end of file
diff --git a/drivers/net/phy/backplane/Makefile b/drivers/net/phy/backplane/Makefile
new file mode 100644
index 0000000..ded6f2d
--- /dev/null
+++ b/drivers/net/phy/backplane/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
+#
+# Makefile for Ethernet Backplane driver
+#
+
+obj-$(CONFIG_ETH_BACKPLANE) += eth_backplane.o
+obj-$(CONFIG_ETH_BACKPLANE_FIXED) += eq_fixed.o
+
+eth_backplane-objs	:= backplane.o link_training.o
diff --git a/drivers/net/phy/backplane/backplane.c b/drivers/net/phy/backplane/backplane.c
new file mode 100644
index 0000000..d9a4770
--- /dev/null
+++ b/drivers/net/phy/backplane/backplane.c
@@ -0,0 +1,1557 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
+/* Backplane driver
+ *
+ * Copyright 2015 Freescale Semiconductor, Inc.
+ * Copyright 2018-2020 NXP
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/mdio.h>
+#include <linux/ethtool.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include <linux/netdevice.h>
+#include <linux/list.h>
+
+#include "backplane.h"
+#include "link_training.h"
+
+/* KR timeouts in milliseconds */
+#define KR_TIMEOUT_1				100
+#define KR_TIMEOUT_2				1000
+#define KR_AN_TIMEOUT				3000
+#define KR_LT_TIMEOUT				500
+
+/* KR timings in interations */
+#define KR_AN_WAIT_ITERATIONS			5
+#define KR_TRAIN_STEP_ITERATIONS		2
+#define CDR_LOCK_RETRY_COUNT			3
+
+/* AN register initialization */
+#define AN_CTRL_INIT				0x1200
+
+/* AN status register (Clause 45) (MMD 7): MDIO_STAT1 */
+#define AN_LINK_UP_MASK				0x04
+
+/* Backplane Ethernet status (Register 7.48) */
+#define AN_BP_ETH_STATUS			48
+
+/* AN masks: Backplane Ethernet status: register 7.48 */
+#define AN_MASK_10GBASE_KR			0x08
+
+/* AN advertisement 1 (Register 7.17) */
+#define AN_ADVERTISEMENT_1			17
+
+/* AN advertisement initialization for register 7.17 */
+#define AN_ADV1_KR_INIT_10G			0x85
+
+/* Backplane features */
+__ETHTOOL_DECLARE_LINK_MODE_MASK(backplane_features) __ro_after_init;
+EXPORT_SYMBOL(backplane_features);
+
+const int backplane_common_features_array[] = {
+	ETHTOOL_LINK_MODE_Backplane_BIT,
+	ETHTOOL_LINK_MODE_Autoneg_BIT,
+	ETHTOOL_LINK_MODE_MII_BIT,
+};
+
+const int backplane_protocol_features_array[] = {
+	ETHTOOL_LINK_MODE_10000baseKR_Full_BIT,
+};
+
+/* map string key to pointer data */
+struct spmap_node {
+	struct list_head entry;
+	const char *key;
+	void *pdata;
+};
+
+/* registered equalization algorithms info */
+static LIST_HEAD(eqalg_list);
+
+/* lanes attached to an equalization algorithm */
+static LIST_HEAD(lnalg_list);
+
+/* Backplane mutex between all KR PHY threads */
+static struct mutex backplane_lock;
+
+static int get_backplane_speed(phy_interface_t interface)
+{
+	switch (interface) {
+	case PHY_INTERFACE_MODE_10GKR:
+		return SPEED_10000;
+	default:
+		pr_err("%s: Unsupported backplane interface\n",
+		       BACKPLANE_DRIVER_NAME);
+		return SPEED_UNKNOWN;
+	}
+	return SPEED_UNKNOWN;
+}
+
+static enum ethtool_link_mode_bit_indices
+	get_backplane_supported_mode(phy_interface_t interface)
+{
+	switch (interface) {
+	case PHY_INTERFACE_MODE_10GKR:
+		return ETHTOOL_LINK_MODE_10000baseKR_Full_BIT;
+	default:
+		pr_err("%s: Unsupported backplane interface\n",
+		       BACKPLANE_DRIVER_NAME);
+		return ETHTOOL_LINK_MODE_Backplane_BIT;
+	}
+	return ETHTOOL_LINK_MODE_Backplane_BIT;
+}
+
+static int spmap_add(struct list_head *list, const char *key, void *pdata)
+{
+	struct spmap_node *node;
+
+	/* create a new entry with desired key */
+	node = kzalloc(sizeof(*node), GFP_KERNEL);
+	if (!node)
+		return -ENOMEM;
+
+	node->key = key;
+	node->pdata = pdata;
+
+	list_add(&node->entry, list);
+
+	return 0;
+}
+
+static const struct equalization_algorithm *eq_find(const char *key)
+{
+	struct spmap_node *eqalg, *eqalg_tmp;
+
+	if (!key)
+		return NULL;
+
+	/* search desired single key */
+	list_for_each_entry_safe(eqalg, eqalg_tmp, &eqalg_list, entry) {
+		if (strcmp(eqalg->key, key) == 0)
+			return (struct equalization_algorithm *)eqalg->pdata;
+	}
+	return NULL;
+}
+
+static void backplane_features_init(void)
+{
+	linkmode_set_bit_array(backplane_common_features_array,
+			       ARRAY_SIZE(backplane_common_features_array),
+			       backplane_features);
+
+	linkmode_set_bit_array(backplane_protocol_features_array,
+			       ARRAY_SIZE(backplane_protocol_features_array),
+			       backplane_features);
+}
+
+static u32 le_ioread32(void __iomem *reg)
+{
+	return ioread32(reg);
+}
+
+static void le_iowrite32(u32 value, void __iomem *reg)
+{
+	iowrite32(value, reg);
+}
+
+static u32 be_ioread32(void __iomem *reg)
+{
+	return ioread32be(reg);
+}
+
+static void be_iowrite32(u32 value, void __iomem *reg)
+{
+	iowrite32be(value, reg);
+}
+
+static void training_status_init(struct training_status *trst)
+{
+	trst->done_training = false;
+	trst->remote_tx_complete = false;
+	trst->remote_tx_running = false;
+	trst->sent_init = false;
+	trst->lp_rx_ready = 0;
+	trst->local_tx_running = false;
+}
+
+static void init_kr_lane(struct lane_device *lane, bool revert_default)
+{
+	if (revert_default)
+		backplane_default_kr_lane(lane);
+
+	training_status_init(&lane->krln.trst);
+	lane->krln.state = DETECTING_LP;
+	lane->krln.an_kr_detected = false;
+
+	lane->krln.ld_update = 0;
+	lane->krln.prev_ld_update = 0;
+	lane->krln.ld_last_nonhold_update = 0;
+	lane->krln.lp_status = 0;
+	lane->krln.lp_last_change_status = 0;
+	lane->krln.last_lp_update_status[C_M1] = 0;
+	lane->krln.last_lp_update_status[C_Z0] = 0;
+	lane->krln.last_lp_update_status[C_P1] = 0;
+	lane->krln.ld_status = 0;
+	lane->krln.move_back_prev = false;
+	lane->krln.move_back_cnt = 0;
+	lane->krln.move_back_lp_status = 0;
+
+	lt_init_ld(lane);
+}
+
+static void setup_supported_linkmode(struct phy_device *phydev)
+{
+	int i;
+
+	/* Clear all supported backplane protocols features
+	 * and setup only the currently configured protocol
+	 */
+	for (i = 0; i < ARRAY_SIZE(backplane_protocol_features_array); i++)
+		linkmode_clear_bit(backplane_protocol_features_array[i],
+				   phydev->supported);
+
+	linkmode_set_bit(get_backplane_supported_mode(phydev->interface),
+			 phydev->supported);
+}
+
+/* Read AN Link Status */
+static int is_an_link_up(struct phy_device *phydev)
+{
+	struct backplane_device *bpdev = phydev->priv;
+	int ret, val = 0;
+
+	mutex_lock(&bpdev->bpphy_lock);
+
+	/* Read twice because Link_Status is LL (Latched Low) bit */
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+	val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
+
+	mutex_unlock(&bpdev->bpphy_lock);
+
+	ret = (val & AN_LINK_UP_MASK) ? 1 : 0;
+
+	return ret;
+}
+
+static void start_kr_state_machine(struct lane_device *lane, u32 timeout)
+{
+	/* Check if equalization algorithm is installed */
+	if (!lane->krln.eq_alg)
+		return;
+
+	/* Check if link training is used */
+	if (!lane->krln.eq_alg->use_local_tx_training &&
+	    !lane->krln.eq_alg->use_remote_tx_training)
+		return;
+
+	queue_delayed_work(system_power_efficient_wq, &lane->krwk,
+			   msecs_to_jiffies(timeout));
+}
+
+static void stop_kr_state_machine(struct lane_device *lane)
+{
+	/* Check if equalization algorithm is installed */
+	if (!lane->krln.eq_alg)
+		return;
+
+	/* Check if link training is used */
+	if (!lane->krln.eq_alg->use_local_tx_training &&
+	    !lane->krln.eq_alg->use_remote_tx_training)
+		return;
+
+	cancel_delayed_work_sync(&lane->krwk);
+}
+
+static void setup_default_settings(struct lane_device *lane)
+{
+	if (lane->bpdev->bpkr.valid_eq_init)
+		lane->krln.def_kr = lane->bpdev->bpkr.def_kr;
+	else
+		lane->bpdev->drv.lane_ops->read_lane_kr(lane->reg_base,
+							&lane->krln.def_kr);
+
+	/* HW specific default settings */
+	if (lane->bpdev->drv.bp_ops.setup_default_settings)
+		lane->bpdev->drv.bp_ops.setup_default_settings(lane);
+}
+
+static void kr_reset_master_lane(struct lane_device *lane)
+{
+	const struct lane_ops *lane_ops = lane->bpdev->drv.lane_ops;
+	struct backplane_device *bpdev = lane->bpdev;
+
+	if (backplane_is_multi_lane(bpdev)) {
+		/* Reset only the Master Lane */
+		if (lane->idx == MASTER_LANE)
+			lane_ops->reset_lane(lane->reg_base, LANE_RX_TX);
+	} else {
+		lane_ops->reset_lane(lane->reg_base, LANE_RX_TX);
+	}
+}
+
+static void print_single_lane_trained(struct lane_device *lane)
+{
+	struct phy_device *phydev = lane->phydev;
+
+	phydev_info(phydev,
+		    "%s link trained, Tx equalization: C(-1)=0x%x, C(0)=0x%x, C(+1)=0x%x\n",
+		    phy_modes(phydev->interface),
+		    lane->krln.tuned_kr.preq, lane->krln.tuned_kr.mainq,
+		    lane->krln.tuned_kr.postq);
+}
+
+static void print_multi_lane_trained(struct lane_device *lane)
+{
+	struct backplane_device *bpdev = lane->bpdev;
+	struct phy_device *phydev = lane->phydev;
+	int i;
+
+	phydev_info(phydev,
+		    "%s link trained, Tx equalization:\n",
+		    phy_modes(phydev->interface));
+
+	for (i = 0; i < bpdev->num_lanes; i++)
+		phydev_info(phydev,
+			    "\t|- Lane %d: C(-1)=0x%x, C(0)=0x%x, C(+1)=0x%x\n",
+			    i + 1, bpdev->lane[i].krln.tuned_kr.preq,
+			    bpdev->lane[i].krln.tuned_kr.mainq,
+			    bpdev->lane[i].krln.tuned_kr.postq);
+}
+
+static void kr_link_trained(struct lane_device *lane)
+{
+	struct backplane_device *bpdev = lane->bpdev;
+
+	mutex_lock(&bpdev->trained_lock);
+	/* Setup lane state as TRAINED inside the phy trained lock
+	 * to avoid duplicated message printed on multi-lane PHYs
+	 */
+	lane->krln.state = TRAINED;
+
+	mutex_lock(&backplane_lock);
+
+	if (backplane_is_single_lane(bpdev))
+		print_single_lane_trained(lane);
+	else
+		if (backplane_are_all_lanes_trained(bpdev))
+			print_multi_lane_trained(lane);
+
+	mutex_unlock(&backplane_lock);
+	mutex_unlock(&bpdev->trained_lock);
+}
+
+static void kr_train_step(struct lane_device *lane)
+{
+	struct training_status *trst = &lane->krln.trst;
+	u32 lt_timeout = KR_LT_TIMEOUT;
+	u64 dead_line;
+	int i = 0;
+
+	/* Check if equalization algorithm is installed */
+	if (!lane->krln.eq_alg)
+		return;
+
+	/* Check if link training is used */
+	if (!lane->krln.eq_alg->use_local_tx_training &&
+	    !lane->krln.eq_alg->use_remote_tx_training)
+		return;
+
+	lt_start(lane);
+
+	while (i < KR_TRAIN_STEP_ITERATIONS) {
+		dead_line = jiffies + msecs_to_jiffies(lt_timeout);
+		while (time_before(jiffies, (unsigned long)dead_line)) {
+			/* check if the LT is already failed */
+			if (lt_is_training_failure(lane)) {
+				/* LT failed already, reset lane to avoid
+				 * it run into hanging, then start LT again.
+				 */
+				kr_reset_master_lane(lane);
+				lt_start(lane);
+			} else if (lt_is_frame_lock(lane)) {
+				break;
+			}
+			/* wait frame lock (without training_failure) */
+			usleep_range(100, 500);
+		}
+
+		if (!lt_is_frame_lock(lane)) {
+			i++;
+			continue;
+		}
+
+		/* the LT should be finished in 500ms, failed or OK. */
+		dead_line = jiffies + msecs_to_jiffies(lt_timeout);
+		while (time_before(jiffies, (unsigned long)dead_line)) {
+			/* check if the LT is already failed */
+			if (lt_is_training_failure(lane)) {
+				kr_reset_master_lane(lane);
+				break;
+			}
+
+			if (lane->krln.eq_alg->use_local_tx_training)
+				lt_train_local_tx(lane);
+
+			if (lane->krln.eq_alg->use_remote_tx_training)
+				lt_train_remote_tx(lane);
+
+			if (lane->krln.lt_error)
+				break;
+
+			if (trst->lp_rx_ready && trst->remote_tx_complete)
+				break;
+
+			usleep_range(100, 500);
+		}
+
+		i++;
+		/* check if LT Error occurred */
+		if (lane->krln.lt_error) {
+			init_kr_lane(lane, false);
+			continue;
+		} else {
+			break;
+		}
+	}
+
+	lt_stop(lane);
+
+	/* check if Link is successfully TRAINED */
+	if (lt_is_rx_trained(lane))
+		kr_link_trained(lane);
+	else
+		kr_reset_master_lane(lane);
+}
+
+static void an_init(struct lane_device *masterln)
+{
+	struct backplane_device *bpdev = masterln->bpdev;
+	struct phy_device *phydev = masterln->phydev;
+	struct lane_device *lane;
+	u32 init_an_adv1;
+	int i, err;
+
+	if (!backplane_is_mode_kr(phydev->interface))
+		return;
+
+	if (masterln->idx != MASTER_LANE)
+		return;
+
+	init_an_adv1 = backplane_get_an_adv1_init(phydev->interface);
+
+	/* setup AN init on each lane */
+	for (i = 0; i < bpdev->num_lanes; i++) {
+		lane = &bpdev->lane[i];
+		if (bpdev->drv.bp_ops.an_advertisement_init) {
+			bpdev->drv.bp_ops.an_advertisement_init(lane);
+		} else {
+			err = backplane_write_mmd(lane, MDIO_MMD_AN,
+						  AN_ADVERTISEMENT_1,
+						  init_an_adv1);
+			if (err)
+				phydev_err(phydev,
+					   "Setting AN register 0x%02x on lane %d failed with error code: 0x%08x\n",
+					   AN_ADVERTISEMENT_1, lane->idx, err);
+		}
+	}
+
+	udelay(1);
+
+	err = backplane_write_mmd(masterln, MDIO_MMD_AN, MDIO_CTRL1,
+				  AN_CTRL_INIT);
+	if (err)
+		phydev_err(phydev,
+			   "Setting AN register 0x%02x on Master Lane failed with error code: 0x%08x\n",
+			   MDIO_CTRL1, err);
+}
+
+static void an_request_restart(struct lane_device *lane)
+{
+	const struct lane_ops *lane_ops = lane->bpdev->drv.lane_ops;
+	struct backplane_device *bpdev = lane->bpdev;
+	struct phy_device *phydev = lane->phydev;
+	int i;
+
+	if (time_before(jiffies, (unsigned long)lane->krln.an_kr_timeout))
+		return;
+	if (!backplane_is_mode_kr(phydev->interface))
+		return;
+
+	for (i = 0; i < bpdev->num_lanes; i++) {
+		init_kr_lane(&bpdev->lane[i], true);
+		/* Reset the lane to recover from link down */
+		lane_ops->reset_lane(bpdev->lane[i].reg_base, LANE_RX_TX);
+		lt_reset(&bpdev->lane[i]);
+	}
+	/* AN init only for Master Lane */
+	an_init(&bpdev->lane[MASTER_LANE]);
+
+	lane->krln.an_kr_timeout = jiffies + msecs_to_jiffies(KR_AN_TIMEOUT);
+}
+
+static bool detect_lp(struct lane_device *lane)
+{
+	struct backplane_device *bpdev = lane->bpdev;
+	struct phy_device *phydev = lane->phydev;
+	struct lane_device *masterln;
+	bool start_train = false;
+	bool an_kr_link;
+	int an_state;
+	u32 an_mask;
+
+	if (bpdev->drv.bp_ops.is_an_link_detected) {
+		an_kr_link = bpdev->drv.bp_ops.is_an_link_detected(lane);
+	} else {
+		/* Check AN state only on Master Lane */
+		masterln = &bpdev->lane[MASTER_LANE];
+		an_mask = backplane_get_an_bp_eth_status_bit(phydev->interface);
+		an_state = backplane_read_mmd(masterln, MDIO_MMD_AN,
+					      AN_BP_ETH_STATUS);
+		an_kr_link = an_state & an_mask;
+	}
+
+	/* The link training occurs after auto-negotiation
+	 * has determined the link to be a Base-KR link.
+	 * This is indicated by asserting the corresponding bit.
+	 * This occurs before auto-negotiation can declare auto-negotiation
+	 * complete, as this requires the PCS to report a valid link.
+	 */
+	if (an_kr_link) {
+		/* AN acquired:
+		 * Train all lanes in order starting with Master Lane
+		 */
+		lane->krln.an_kr_detected = true;
+		lane->krln.an_kr_wait_count = 0;
+		start_train = true;
+	} else {
+		/* AN lost or not yet acquired */
+		if (lane->krln.an_kr_detected) {
+			/* AN acquired first time but now was lost */
+			if (!backplane_is_link_up(phydev)) {
+				/* Link is down: restart training */
+				lane->krln.an_kr_wait_count = 0;
+				an_request_restart(lane);
+			} else {
+				/* Link is up:
+				 * wait few iterations for AN to be acquired
+				 */
+				if (lane->krln.an_kr_wait_count >=
+							KR_AN_WAIT_ITERATIONS) {
+					lane->krln.an_kr_wait_count = 0;
+					an_request_restart(lane);
+				} else {
+					lane->krln.an_kr_wait_count++;
+				}
+			}
+		}
+		/* else: AN was not yet acquired first time
+		 * DO nothing, just wait AN to be acquired first time
+		 */
+	}
+
+	return start_train;
+}
+
+static void detect_hotplug(struct lane_device *lane)
+{
+	struct backplane_device *bpdev = lane->bpdev;
+	struct phy_device *phydev = lane->phydev;
+	int i;
+
+	if (lane->idx == MASTER_LANE) {
+		/* check if all lanes are trained
+		 * only if current lane is  Master Lane
+		 */
+		if (backplane_are_all_lanes_trained(bpdev)) {
+			phydev_info(phydev, "Detect hotplug, restart training\n");
+			for (i = 0; i < bpdev->num_lanes; i++) {
+				/* initializations on Detect hotplug / restart:
+				 * they must not be part of init_kr_lane
+				 */
+				bpdev->lane[i].krln.first_recv_init = false;
+			}
+			an_request_restart(lane);
+		}
+	}
+}
+
+static void bp_kr_state_machine(struct work_struct *work)
+{
+	struct delayed_work *dwork = to_delayed_work(work);
+	struct backplane_device *bpdev;
+	u32 kr_timeout = KR_TIMEOUT_1;
+	struct phy_device *phydev;
+	struct lane_device *lane;
+	bool start_train = false;
+
+	lane = container_of(dwork, struct lane_device, krwk);
+	if (!lane)
+		return;
+
+	bpdev = lane->bpdev;
+	phydev = lane->phydev;
+
+	if (!backplane_is_mode_kr(phydev->interface))
+		return;
+
+	/* Check if equalization algorithm is installed */
+	if (!lane->krln.eq_alg)
+		return;
+
+	/* Check if link training is used */
+	if (!lane->krln.eq_alg->use_local_tx_training &&
+	    !lane->krln.eq_alg->use_remote_tx_training)
+		return;
+
+	mutex_lock(&lane->lane_lock);
+	switch (lane->krln.state) {
+	case DETECTING_LP:
+		start_train = detect_lp(lane);
+		break;
+	case TRAINED:
+		kr_timeout = KR_TIMEOUT_2;
+		if (!backplane_is_link_up(phydev)) {
+			kr_timeout = KR_TIMEOUT_1;
+			detect_hotplug(lane);
+		}
+		break;
+	}
+
+	if (start_train)
+		kr_train_step(lane);
+
+	mutex_unlock(&lane->lane_lock);
+	start_kr_state_machine(lane, kr_timeout);
+}
+
+static void init_kr_state_machine(struct lane_device *lane)
+{
+	/* Check if equalization algorithm is installed */
+	if (!lane->krln.eq_alg)
+		return;
+
+	/* Check if link training is used */
+	if (!lane->krln.eq_alg->use_local_tx_training &&
+	    !lane->krln.eq_alg->use_remote_tx_training)
+		return;
+
+	INIT_DELAYED_WORK(&lane->krwk, bp_kr_state_machine);
+}
+
+/* backplane_get_current_taps
+ * convert coefficient taps from internal backplane driver to link training
+ */
+void backplane_get_current_taps(struct lane_device *lane, u32 *coef)
+{
+	coef[C_M1] = lane->krln.crt_kr.preq;
+	coef[C_Z0] = lane->krln.crt_kr.mainq;
+	coef[C_P1] = lane->krln.crt_kr.postq;
+}
+
+/* backplane_set_current_taps
+ * convert coefficient taps from link training to internal backplane driver
+ */
+void backplane_set_current_taps(struct lane_device *lane, u32 *coef)
+{
+	lane->krln.crt_kr.preq = coef[C_M1];
+	lane->krln.crt_kr.mainq = coef[C_Z0];
+	lane->krln.crt_kr.postq = coef[C_P1];
+}
+
+/* backplane_set_all_taps_to_max
+ * setup all coefficients to MAX values from IEEE802.3ap perspective
+ */
+void backplane_set_all_taps_to_max(struct lane_device *lane)
+{
+	lane->krln.crt_kr = lane->bpdev->bpkr.max_kr;
+}
+
+void backplane_tune_kr_lane(struct lane_device *lane, bool reset_lane)
+{
+	struct backplane_device *bpdev = lane->bpdev;
+	bool reset = false;
+
+	if (backplane_is_multi_lane(bpdev)) {
+		/* Reset only the Master Lane */
+		reset = (lane->idx == MASTER_LANE);
+	} else {
+		reset = true;
+	}
+
+	/* Do not reset the lane if this is how it was asked */
+	if (!reset_lane)
+		reset = false;
+
+	lane->bpdev->drv.lane_ops->tune_lane_kr(lane->reg_base,
+						&lane->krln.crt_kr, reset);
+	lane->krln.tuned_kr = lane->krln.crt_kr;
+}
+
+void backplane_default_kr_lane(struct lane_device *lane)
+{
+	lane->krln.crt_kr = lane->krln.def_kr;
+
+	backplane_tune_kr_lane(lane, true);
+}
+
+/* backplane_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();
+ */
+int backplane_write_mmd(struct lane_device *lane, int devad, u32 regnum,
+			u16 val)
+{
+	struct backplane_device *bpdev = lane->bpdev;
+	struct phy_device *phydev = lane->phydev;
+	int err;
+
+	mutex_lock(&bpdev->bpphy_lock);
+
+	err = phy_write_mmd(phydev, devad, regnum, val);
+	if (err)
+		phydev_err(phydev,
+			   "Writing PHY (%p) MMD = 0x%02x register = 0x%02x failed with error code: 0x%08x\n",
+			   phydev, devad, regnum, err);
+
+	mutex_unlock(&bpdev->bpphy_lock);
+
+	return err;
+}
+EXPORT_SYMBOL(backplane_write_mmd);
+
+/* backplane_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();
+ */
+int backplane_read_mmd(struct lane_device *lane, int devad, u32 regnum)
+{
+	struct backplane_device *bpdev = lane->bpdev;
+	struct phy_device *phydev = lane->phydev;
+	int ret;
+
+	mutex_lock(&bpdev->bpphy_lock);
+
+	ret = phy_read_mmd(phydev, devad, regnum);
+
+	mutex_unlock(&bpdev->bpphy_lock);
+
+	return ret;
+}
+EXPORT_SYMBOL(backplane_read_mmd);
+
+/* backplane_eq_register
+ *
+ * Registers an equalization algorithm with the specified key
+ *
+ * key: desired key on which eq algorithm must be registered
+ * eq_info: eq algorithm information to be registered
+ *
+ * Returns: Zero for success or error code in case of failure
+ */
+int backplane_eq_register(const char *key,
+			  const struct equalization_algorithm *eq_info)
+{
+	struct spmap_node *eqalg, *eqalg_tmp;
+
+	/* check if desired key already exists */
+	list_for_each_entry_safe(eqalg, eqalg_tmp, &eqalg_list, entry) {
+		if (strcmp(eqalg->key, key) == 0) {
+			pr_err("%s: Equalization algorithm registration failed: key '%s' already exists\n",
+			       BACKPLANE_DRIVER_NAME, key);
+			return -EEXIST;
+		}
+	}
+
+	spmap_add(&eqalg_list, key, (void *)eq_info);
+
+	return 0;
+}
+EXPORT_SYMBOL(backplane_eq_register);
+
+/* backplane_eq_unregister
+ *
+ * Unregisters all equalization algorithm for the specified key
+ *
+ * key: desired key for which all registered eq algorithms must be removed
+ *
+ * Returns: None
+ */
+void backplane_eq_unregister(const char *key)
+{
+	const struct equalization_algorithm *eq_alg;
+	struct spmap_node *node, *node_tmp;
+	struct lane_device *lane;
+
+	if (!key)
+		return;
+
+	/* search all keys in lanes list */
+	list_for_each_entry_safe(node, node_tmp, &lnalg_list, entry) {
+		if (strcmp(node->key, key) == 0) {
+			lane = (struct lane_device *)node->pdata;
+			eq_alg = lane->krln.eq_alg;
+			if (eq_alg->ops.destroy)
+				eq_alg->ops.destroy(lane->krln.eq_priv);
+			lane->krln.eq_alg = NULL;
+			lane->krln.eq_priv = NULL;
+			list_del_init(&node->entry);
+			kfree(node);
+		}
+	}
+
+	/* search single key in eq algorithms list */
+	list_for_each_entry_safe(node, node_tmp, &eqalg_list, entry) {
+		if (strcmp(node->key, key) == 0) {
+			list_del_init(&node->entry);
+			kfree(node);
+			break;
+		}
+	}
+}
+EXPORT_SYMBOL(backplane_eq_unregister);
+
+bool backplane_is_mode_kr(phy_interface_t interface)
+{
+	return (interface == PHY_INTERFACE_MODE_10GKR);
+}
+EXPORT_SYMBOL(backplane_is_mode_kr);
+
+bool backplane_is_valid_mode(phy_interface_t interface)
+{
+	return backplane_is_mode_kr(interface);
+}
+EXPORT_SYMBOL(backplane_is_valid_mode);
+
+u8 backplane_num_lanes(phy_interface_t interface)
+{
+	const char *bp_name;
+	char num_lanes;
+	int len;
+
+	if (!backplane_is_valid_mode(interface))
+		return 0;
+
+	bp_name = phy_modes(interface);
+	if (!bp_name)
+		return 0;
+	if (strcmp(bp_name, "unknown") == 0)
+		return 0;
+
+	len = strlen(bp_name);
+	if (len == 0)
+		return 0;
+	num_lanes = bp_name[len - 1];
+	if (num_lanes >= '0' && num_lanes <= '9')
+		return num_lanes - '0';
+
+	return 1;
+}
+EXPORT_SYMBOL(backplane_num_lanes);
+
+/* Backplane Ethernet Status Register Register 7.48 (an_bp_eth_status)
+ *	- AN_MASK_10GBASE_KR for 10GBase-KR
+ */
+u32 backplane_get_an_bp_eth_status_bit(phy_interface_t interface)
+{
+	switch (interface) {
+	case PHY_INTERFACE_MODE_10GKR:
+		return AN_MASK_10GBASE_KR;
+	/* add AN support for other backplane modes here */
+	default:
+		pr_err("%s: Unsupported backplane interface\n",
+		       BACKPLANE_DRIVER_NAME);
+		return 0;
+	}
+	return 0;
+}
+EXPORT_SYMBOL(backplane_get_an_bp_eth_status_bit);
+
+u32 backplane_get_an_adv1_init(phy_interface_t interface)
+{
+	switch (interface) {
+	case PHY_INTERFACE_MODE_10GKR:
+		return AN_ADV1_KR_INIT_10G;
+	/* add AN support for other backplane modes here */
+	default:
+		pr_err("%s: Unsupported backplane interface\n",
+		       BACKPLANE_DRIVER_NAME);
+		return 0;
+	}
+	return 0;
+}
+EXPORT_SYMBOL(backplane_get_an_adv1_init);
+
+void backplane_kr_lt_mmd_c45(struct backplane_kr *bpkr)
+{
+	lt_mmd_c45(bpkr);
+}
+EXPORT_SYMBOL(backplane_kr_lt_mmd_c45);
+
+void backplane_kr_lt_mmd_setup(struct backplane_kr *bpkr, int devad, u32 base)
+{
+	lt_mmd_setup(bpkr, devad, base);
+}
+EXPORT_SYMBOL(backplane_kr_lt_mmd_setup);
+
+bool backplane_is_single_lane(struct backplane_device *bpdev)
+{
+	return (bpdev->num_lanes == 1);
+}
+EXPORT_SYMBOL(backplane_is_single_lane);
+
+bool backplane_is_multi_lane(struct backplane_device *bpdev)
+{
+	return (bpdev->num_lanes > 1);
+}
+EXPORT_SYMBOL(backplane_is_multi_lane);
+
+/* backplane_is_cdr_lock
+ *
+ * Checks clock and data recovery bit: CDR Lock
+ *
+ * lane: desired lane to be verified
+ * retry: boolean value that specifies if to retry the check
+ *
+ * Returns: true if CDR_Lock bit is asserted or false otherwise
+ */
+bool backplane_is_cdr_lock(struct lane_device *lane, bool retry)
+{
+	const struct lane_ops *lane_ops = lane->bpdev->drv.lane_ops;
+	int i;
+
+	if (lane_ops->is_cdr_lock(lane->reg_base))
+		return true;
+
+	if (!retry)
+		return false;
+
+	/* Try RX_RESET: Allow for few retries */
+	for (i = 0; i < CDR_LOCK_RETRY_COUNT; i++) {
+		lane_ops->reset_lane(lane->reg_base, LANE_RX);
+		usleep_range(10, 50);
+
+		if (lane_ops->is_cdr_lock(lane->reg_base))
+			return true;
+	}
+	return false;
+}
+EXPORT_SYMBOL(backplane_is_cdr_lock);
+
+/* backplane_is_link_up
+ * Generic Link-up Status: use AN link-up
+ */
+int backplane_is_link_up(struct phy_device *phydev)
+{
+	return is_an_link_up(phydev);
+}
+EXPORT_SYMBOL(backplane_is_link_up);
+
+int backplane_get_lanes_trained_count(struct backplane_device *bpdev)
+{
+	int i, lanes_trained = 0;
+
+	for (i = 0; i < bpdev->num_lanes; i++) {
+		if (bpdev->lane[i].krln.state == TRAINED)
+			lanes_trained++;
+	}
+	return lanes_trained;
+}
+EXPORT_SYMBOL(backplane_get_lanes_trained_count);
+
+int backplane_are_all_lanes_trained(struct backplane_device *bpdev)
+{
+	int i;
+
+	for (i = 0; i < bpdev->num_lanes; i++) {
+		if (bpdev->lane[i].krln.state != TRAINED)
+			return 0;
+	}
+	return 1;
+}
+EXPORT_SYMBOL(backplane_are_all_lanes_trained);
+
+int backplane_create(struct phy_device *phydev)
+{
+	struct backplane_device *bpdev;
+	struct device_node *dev_node;
+
+	dev_node = phydev->mdio.dev.of_node;
+	if (!dev_node) {
+		phydev_err(phydev, "No associated device tree node\n");
+		return -EINVAL;
+	}
+
+	/* allocate memory for backplane info structure */
+	bpdev = devm_kzalloc(&phydev->mdio.dev, sizeof(*bpdev), GFP_KERNEL);
+	if (!bpdev)
+		return -ENOMEM;
+
+	bpdev->phydev = phydev;
+
+	/* save bpdev as phy private data pointer */
+	phydev->priv = bpdev;
+
+	return 0;
+}
+EXPORT_SYMBOL(backplane_create);
+
+/* backplane_parse_dt
+ * parses the device tree and saves backplane relevant data
+ * in backplane phy info structure
+ */
+int backplane_parse_dt(struct phy_device *phydev)
+{
+	struct backplane_device *bpdev = phydev->priv;
+	struct device_node *dev_node;
+	u32 eqinit[C_NO];
+	const char *eqa;
+	int proplen;
+	int ret;
+
+	if (!bpdev) {
+		phydev_err(phydev, "Backplane phy info is not allocated\n");
+		return -EINVAL;
+	}
+
+	dev_node = phydev->mdio.dev.of_node;
+	if (!dev_node) {
+		phydev_err(phydev, "No associated device tree node\n");
+		return -EINVAL;
+	}
+
+	if (!backplane_is_valid_mode(phydev->interface))
+		return -EINVAL;
+
+	ret = of_property_read_string(dev_node, "eq-algorithm", &eqa);
+	/* if eq-algorithm node is not found then use the default algorithm */
+	if (ret == 0)
+		bpdev->bpkr.eqa_name = eqa;
+	else
+		bpdev->bpkr.eqa_name = DEFAULT_EQ_ALGORITHM;
+
+	/* if eq-init node exists then use the DTS specified values
+	 * if eq-init node doesn't exist then use values already found in HW
+	 */
+	proplen = of_property_count_u32_elems(dev_node, "eq-init");
+	if (proplen > 0) {
+		/* There are 3 standard equalization coefficient taps */
+		if (proplen > C_NO)
+			proplen = C_NO;
+		ret = of_property_read_u32_array(dev_node, "eq-init",
+						 (u32 *)eqinit, proplen);
+		if (ret == 0) {
+			bpdev->bpkr.valid_eq_init = true;
+			bpdev->bpkr.def_kr.preq = eqinit[C_M1];
+			bpdev->bpkr.def_kr.mainq = eqinit[C_Z0];
+			bpdev->bpkr.def_kr.postq = eqinit[C_P1];
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(backplane_parse_dt);
+
+/* backplane_setup_memio
+ * Setup memory I/O access
+ */
+int backplane_setup_memio(struct phy_device *phydev)
+{
+	struct backplane_device *bpdev = phydev->priv;
+
+	if (!bpdev) {
+		phydev_err(phydev, "Backplane phy info is not allocated\n");
+		return -EINVAL;
+	}
+
+	/* setup ioread/iowrite according to endianness */
+	if (bpdev->drv.is_little_endian) {
+		bpdev->drv.io.read32 = le_ioread32;
+		bpdev->drv.io.write32 = le_iowrite32;
+	} else {
+		bpdev->drv.io.read32 = be_ioread32;
+		bpdev->drv.io.write32 = be_iowrite32;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(backplane_setup_memio);
+
+/* backplane_setup_mmd
+ * Setup default MMD (MDIO Managed Device)
+ */
+int backplane_setup_mmd(struct phy_device *phydev)
+{
+	struct backplane_device *bpdev = phydev->priv;
+
+	if (!bpdev) {
+		phydev_err(phydev, "Backplane phy info is not allocated\n");
+		return -EINVAL;
+	}
+
+	/* By default setup LT MMD Clause 45 */
+	backplane_kr_lt_mmd_c45(&bpdev->bpkr);
+
+	return 0;
+}
+EXPORT_SYMBOL(backplane_setup_mmd);
+
+/* backplane_setup_lanes
+ * Allocates lanes memory map and setup lanes relevant data
+ * Requires:
+ *	- backplane_driver#lane_ops
+ *		for lane access operations
+ *	- backplane_driver#lane_ops#memmap_size
+ *		for lane memory map allocation
+ *	- backplane_kr#equalizer
+ *		for specific Equalizer access
+ *	- backplane_kr#cx_def
+ *		for default coefficient setup
+ */
+int backplane_setup_lanes(struct phy_device *phydev)
+{
+	struct backplane_device *bpdev = phydev->priv;
+	const struct equalization_algorithm *eq_alg;
+	struct equalizer_driver eqdrv;
+	struct lane_device *lane;
+	int i;
+
+	if (!bpdev) {
+		phydev_err(phydev, "Backplane phy info is not allocated\n");
+		return -EINVAL;
+	}
+	if (!bpdev->drv.lane_ops) {
+		phydev_err(phydev, "Backplane lane ops is not set\n");
+		return -EINVAL;
+	}
+	if (!bpdev->bpkr.equalizer) {
+		phydev_err(phydev, "Backplane equalizer info is not set\n");
+		return -EINVAL;
+	}
+	if (bpdev->drv.lane_ops->memmap_size == 0) {
+		phydev_err(phydev, "Lane memory map size is zero\n");
+		return -EINVAL;
+	}
+
+	bpdev->num_lanes = backplane_num_lanes(phydev->interface);
+	if (bpdev->num_lanes > MAX_KR_LANES_PER_PHY) {
+		phydev_err(phydev, "Unsupported number of lanes per phy: %d\n",
+			   bpdev->num_lanes);
+		return -EINVAL;
+	}
+
+	if (backplane_is_mode_kr(phydev->interface)) {
+		if (bpdev->bpkr.valid_eq_init &&
+		    bpdev->bpkr.def_kr.preq == 0 &&
+		    bpdev->bpkr.def_kr.mainq == 0 &&
+		    bpdev->bpkr.def_kr.postq == 0)
+			phydev_warn(phydev,
+				    "All KR default values from DT are zero\n");
+	}
+
+	for (i = 0; i < bpdev->num_lanes; i++) {
+		lane = &bpdev->lane[i];
+
+		/* setup lane memory map size */
+		lane->memmap_size = bpdev->drv.lane_ops->memmap_size;
+
+		lane->reg_base = devm_ioremap(&phydev->mdio.dev,
+					      lane->lane_addr,
+					      lane->memmap_size);
+		if (!lane->reg_base) {
+			phydev_err(phydev, "Lane memory map allocation failed\n");
+			return -ENOMEM;
+		}
+
+		lane->idx = i;
+		lane->phydev = phydev;
+		lane->bpdev = bpdev;
+		lane->krln.an_kr_timeout =
+				jiffies + msecs_to_jiffies(KR_AN_TIMEOUT);
+
+		if (backplane_is_mode_kr(phydev->interface)) {
+			setup_default_settings(lane);
+
+			/* Find EQ Algorithm info */
+			eq_alg = eq_find(bpdev->bpkr.eqa_name);
+			if (!eq_alg) {
+				/* key for desired algorithm was not found */
+				phydev_err(phydev,
+					   "Equalization algorithm '%s' is not registered\n",
+					   bpdev->bpkr.eqa_name);
+				return -EINVAL;
+			}
+			if (!eq_alg->ops.create) {
+				phydev_err(phydev,
+					   "Equalization algorithm creation failed: create operation is not available\n");
+				return -EINVAL;
+			}
+			lane->krln.eq_alg = eq_alg;
+
+			/* Setup EQ Algorithm */
+			eqdrv.lane = lane;
+			eqdrv.phydev = lane->phydev;
+			eqdrv.reg_base = lane->reg_base;
+			eqdrv.equalizer = lane->bpdev->bpkr.equalizer;
+
+			/* Create EQ Algorithm */
+			lane->krln.eq_priv = eq_alg->ops.create(eqdrv);
+
+			/* register lane attached to an algorithm */
+			spmap_add(&lnalg_list, bpdev->bpkr.eqa_name, lane);
+
+			if (eq_alg->use_remote_tx_training) {
+				if (!eq_alg->ops.is_rx_ok)
+					phydev_warn(phydev,
+						    "Required operation for remote Tx training is missing: is_rx_ok\n");
+				if (!eq_alg->ops.is_eq_done)
+					phydev_warn(phydev,
+						    "Required operation for remote Tx training is missing: is_eq_done\n");
+				if (!eq_alg->ops.collect_statistics)
+					phydev_warn(phydev,
+						    "Required operation for remote Tx training is missing: collect_statistics\n");
+				if (!eq_alg->ops.generate_request)
+					phydev_warn(phydev,
+						    "Required operation for remote Tx training is missing: generate_request\n");
+			}
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(backplane_setup_lanes);
+
+/* backplane_initialize
+ * Initializes all PHY and lane mutexes and
+ * starts lane timers for running the algorithm
+ */
+int backplane_initialize(struct phy_device *phydev)
+{
+	struct backplane_device *bpdev = phydev->priv;
+	int i;
+
+	if (!bpdev) {
+		phydev_err(phydev, "Backplane phy info is not allocated\n");
+		return -EINVAL;
+	}
+
+	mutex_init(&bpdev->bpphy_lock);
+	mutex_init(&bpdev->trained_lock);
+
+	for (i = 0; i < bpdev->num_lanes; i++)
+		mutex_init(&bpdev->lane[i].lane_lock);
+
+	phydev->speed = get_backplane_speed(phydev->interface);
+	if (phydev->speed < 0) {
+		phydev_err(phydev, "Unsupported backplane mode\n");
+		return -EINVAL;
+	}
+
+	if (backplane_is_mode_kr(phydev->interface)) {
+		for (i = 0; i < bpdev->num_lanes; i++)
+			init_kr_state_machine(&bpdev->lane[i]);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(backplane_initialize);
+
+/* backplane_probe
+ *
+ * Probe function for backplane driver to provide generic device behavior
+ *
+ * phydev: backplane phy device
+ *	this is an internal phy block controlled by the software
+ *	which contains other component blocks like: PMA/PMD, PCS, AN
+ *
+ * Return: Zero for success or error code in case of failure
+ */
+int backplane_probe(struct phy_device *phydev)
+{
+	return backplane_create(phydev);
+}
+EXPORT_SYMBOL(backplane_probe);
+
+void backplane_remove(struct phy_device *phydev)
+{
+	struct backplane_device *bpdev = phydev->priv;
+
+	if (!bpdev) {
+		phydev_err(phydev, "Backplane phy info is not allocated\n");
+		return;
+	}
+
+	kfree(bpdev);
+	phydev->priv = NULL;
+}
+EXPORT_SYMBOL(backplane_remove);
+
+/* backplane_config_init
+ *
+ * Config_Init function for backplane driver to provide generic device behavior
+ *
+ * phydev: backplane phy device
+ *
+ * Return: Zero for success or error code in case of failure
+ */
+int backplane_config_init(struct phy_device *phydev)
+{
+	int ret;
+
+	ret = backplane_parse_dt(phydev);
+	if (ret)
+		return ret;
+
+	ret = backplane_setup_memio(phydev);
+	if (ret)
+		return ret;
+
+	ret = backplane_setup_mmd(phydev);
+	if (ret)
+		return ret;
+
+	ret = backplane_setup_lanes(phydev);
+	if (ret)
+		return ret;
+
+	ret = backplane_initialize(phydev);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+EXPORT_SYMBOL(backplane_config_init);
+
+int backplane_aneg_done(struct phy_device *phydev)
+{
+	struct backplane_device *bpdev = phydev->priv;
+
+	if (!phydev->mdio.dev.of_node) {
+		phydev_err(phydev, "No associated device tree node\n");
+		return -EINVAL;
+	}
+	if (!bpdev) {
+		phydev_err(phydev, "Backplane phy info is not allocated\n");
+		return -EINVAL;
+	}
+
+	bpdev->aneg_done = true;
+	phydev->state = PHY_RUNNING;
+
+	return 1;
+}
+EXPORT_SYMBOL(backplane_aneg_done);
+
+int backplane_config_aneg(struct phy_device *phydev)
+{
+	struct backplane_device *bpdev = phydev->priv;
+	struct equalization_ops ops;
+	struct lane_device *lane;
+	int i;
+
+	if (!phydev->mdio.dev.of_node) {
+		phydev_err(phydev, "No associated device tree node\n");
+		return -EINVAL;
+	}
+	if (!bpdev) {
+		phydev_err(phydev, "Backplane phy info is not allocated\n");
+		return -EINVAL;
+	}
+	if (backplane_get_lanes_trained_count(bpdev) > 0) {
+		phydev_err(phydev, "Incorrectly trained lanes detected\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < bpdev->num_lanes; i++) {
+		lane = &bpdev->lane[i];
+		if (lane->krln.eq_alg) {
+			ops = lane->krln.eq_alg->ops;
+			if (ops.dump_algorithm_context)
+				ops.dump_algorithm_context(lane->krln.eq_priv);
+		}
+	}
+
+	if (backplane_is_mode_kr(phydev->interface)) {
+		/* Warning:
+		 * Order of the operations below is important
+		 * otherwise the training may be failing
+		 * with error: 'link_training_failed'
+		 */
+
+		/* setup all lanes to default */
+		for (i = 0; i < bpdev->num_lanes; i++)
+			setup_default_settings(&bpdev->lane[i]);
+
+		/* Initialize all lanes and reset LT */
+		for (i = 0; i < bpdev->num_lanes; i++) {
+			init_kr_lane(&bpdev->lane[i], true);
+			lt_reset(&bpdev->lane[i]);
+		}
+	}
+
+	/* Warning:
+	 * speed and protocol setup operation
+	 * must be done just before AN and state machine start
+	 * otherwise if it is done earlier,
+	 * the error: 'REQ Timeout' will occur
+	 */
+	/* setup supported speed and protocol */
+	phydev->speed = get_backplane_speed(phydev->interface);
+	if (phydev->speed < 0) {
+		phydev_err(phydev, "Unsupported backplane mode\n");
+		return -EINVAL;
+	}
+
+	setup_supported_linkmode(phydev);
+	linkmode_copy(phydev->advertising, phydev->supported);
+	phydev->duplex = DUPLEX_FULL;
+
+	if (backplane_is_mode_kr(phydev->interface)) {
+		/* AN init only for Master Lane */
+		an_init(&bpdev->lane[MASTER_LANE]);
+		/* start state machine on all lanes */
+		for (i = 0; i < bpdev->num_lanes; i++)
+			start_kr_state_machine(&bpdev->lane[i], KR_TIMEOUT_1);
+	}
+
+	bpdev->aneg_config = true;
+
+	return 0;
+}
+EXPORT_SYMBOL(backplane_config_aneg);
+
+int backplane_suspend(struct phy_device *phydev)
+{
+	struct backplane_device *bpdev = phydev->priv;
+	int i;
+
+	if (!phydev->mdio.dev.of_node) {
+		phydev_err(phydev, "No associated device tree node\n");
+		return -EINVAL;
+	}
+	if (!bpdev) {
+		phydev_err(phydev, "Backplane phy info is not allocated\n");
+		return -EINVAL;
+	}
+
+	if (bpdev->aneg_config && !bpdev->phy_suspended) {
+		if (backplane_is_mode_kr(phydev->interface)) {
+			for (i = 0; i < bpdev->num_lanes; i++)
+				stop_kr_state_machine(&bpdev->lane[i]);
+		}
+		bpdev->phy_suspended = true;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(backplane_suspend);
+
+int backplane_resume(struct phy_device *phydev)
+{
+	struct backplane_device *bpdev = phydev->priv;
+	int i;
+
+	if (!phydev->mdio.dev.of_node) {
+		phydev_err(phydev, "No associated device tree node\n");
+		return -EINVAL;
+	}
+	if (!bpdev) {
+		phydev_err(phydev, "Backplane phy info is not allocated\n");
+		return -EINVAL;
+	}
+
+	if (bpdev->aneg_config && bpdev->phy_suspended) {
+		if (backplane_is_mode_kr(phydev->interface)) {
+			for (i = 0; i < bpdev->num_lanes; i++) {
+				init_kr_lane(&bpdev->lane[i], true);
+				start_kr_state_machine(&bpdev->lane[i],
+						       KR_TIMEOUT_1);
+			}
+		}
+		bpdev->phy_suspended = false;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(backplane_resume);
+
+int backplane_read_status(struct phy_device *phydev)
+{
+	struct backplane_device *bpdev = phydev->priv;
+
+	if (!phydev->mdio.dev.of_node) {
+		phydev_err(phydev, "No associated device tree node\n");
+		return -EINVAL;
+	}
+	if (!bpdev) {
+		phydev_err(phydev, "Backplane phy info is not allocated\n");
+		return -EINVAL;
+	}
+
+	/* Linkup method proposal for training stability:
+	 * Don't raise linkup until all lanes are trained
+	 * in order to prevent interface sending packets that may
+	 * interfere with the training packets
+	 */
+	if (backplane_is_link_up(phydev))
+		if (backplane_is_mode_kr(phydev->interface))
+			phydev->link = backplane_are_all_lanes_trained(bpdev);
+		else
+			phydev->link = 1;
+	else
+		phydev->link = 0;
+
+	return 0;
+}
+EXPORT_SYMBOL(backplane_read_status);
+
+int backplane_match_phy_device(struct phy_device *phydev)
+{
+	struct device_node *dev_node;
+
+	if (!phydev->is_c45)
+		return 0;
+
+	dev_node = phydev->mdio.dev.of_node;
+	if (!dev_node) {
+		phydev_err(phydev, "No associated device tree node\n");
+		return 0;
+	}
+
+	return 1;
+}
+EXPORT_SYMBOL(backplane_match_phy_device);
+
+static int __init backplane_module_init(void)
+{
+	pr_info("%s: Backplane driver version %s\n",
+		BACKPLANE_DRIVER_NAME, BACKPLANE_DRIVER_VERSION);
+
+	mutex_init(&backplane_lock);
+	backplane_features_init();
+
+	return 0;
+}
+
+static void __exit backplane_module_exit(void)
+{
+	pr_info("%s: Backplane driver version %s unloaded\n",
+		BACKPLANE_DRIVER_NAME, BACKPLANE_DRIVER_VERSION);
+}
+
+module_init(backplane_module_init);
+module_exit(backplane_module_exit);
+
+MODULE_DESCRIPTION("Backplane driver");
+MODULE_AUTHOR("Florinel Iordache <florinel.iordache@nxp.com>");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/phy/backplane/backplane.h b/drivers/net/phy/backplane/backplane.h
new file mode 100644
index 0000000..d142e83
--- /dev/null
+++ b/drivers/net/phy/backplane/backplane.h
@@ -0,0 +1,293 @@
+/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
+/* Backplane driver
+ *
+ * Copyright 2018-2020 NXP
+ */
+
+#ifndef __BACKPLANE_H
+#define __BACKPLANE_H
+
+#include <linux/phy.h>
+#include <linux/mutex.h>
+
+#include "equalization.h"
+
+/* Backplane Driver name */
+#define BACKPLANE_DRIVER_NAME			"backplane"
+
+/* Backplane Driver version */
+#define BACKPLANE_DRIVER_VERSION		"1.0.0"
+
+/* Maximum number of lanes per phy */
+#define MAX_KR_LANES_PER_PHY			1
+
+/* Lanes definitions */
+#define MASTER_LANE				0
+#define SINGLE_LANE				0
+
+/* Number of device specific kr coefficients (device specific extension) */
+#define DEVICE_KR_COEF_NO			6
+
+extern __ETHTOOL_DECLARE_LINK_MODE_MASK(backplane_features) __ro_after_init;
+
+#define BACKPLANE_FEATURES ((unsigned long *)&backplane_features)
+
+enum train_state {
+	DETECTING_LP,
+	TRAINED,
+};
+
+enum lane_req {
+	LANE_INVALID,
+	LANE_RX,
+	LANE_TX,
+	LANE_RX_TX
+};
+
+struct kr_coef {
+	u32 preq;
+	u32 mainq;
+	u32 postq;
+	/* device specific extension */
+	u32 dev_coef[DEVICE_KR_COEF_NO];
+};
+
+/* Endianness specific memory I/O */
+struct mem_io {
+	u32 (*read32)(void __iomem *addr);
+	void (*write32)(u32 value, void __iomem *addr);
+};
+
+/* Generic Lane operations */
+struct lane_ops {
+	/* device specific private extension */
+	const void *priv;
+	/* lane memory map size */
+	u32 memmap_size;
+	void (*reset_lane)(void __iomem *reg, enum lane_req req);
+	void (*tune_lane_kr)(void __iomem *reg, struct kr_coef *coef,
+			     bool reset);
+	void (*read_lane_kr)(void __iomem *reg, struct kr_coef *coef);
+	bool (*is_cdr_lock)(void __iomem *reg);
+};
+
+struct training_status {
+	bool done_training;
+	bool remote_tx_complete;
+	bool remote_tx_running;
+	bool sent_init;
+	bool lp_rx_ready;
+	bool local_tx_running;
+};
+
+struct backplane_device;
+
+/* Lane kr */
+struct lane_kr {
+	/* KR parameters (current, default, tuned) */
+	struct kr_coef crt_kr;
+	struct kr_coef def_kr;
+	struct kr_coef tuned_kr;
+	/* equalization */
+	const struct equalization_algorithm *eq_alg;
+	struct eq_data_priv *eq_priv;
+	/* training status */
+	struct training_status trst;
+	enum train_state state;
+	/* AN KR status */
+	bool an_kr_detected;
+	u32 an_kr_wait_count;
+	u64 an_kr_timeout;
+	/* KR LD/LP updates and status */
+	u32 ld_update;
+	u32 prev_ld_update;
+	/* last change (non-hold) update */
+	u32 ld_last_nonhold_update;
+	u32 ld_status;
+	u32 lp_status;
+	/* last change (non-zero) status */
+	u32 lp_last_change_status;
+	u32 last_lp_update_status[C_NO];
+	/* link training status */
+	bool lt_error;
+	u32 req_ld_update_init_count;
+	u32 repeat_request_count;
+	u64 init_handshake_time;
+	bool first_recv_init;
+	/* move lp back */
+	bool move_back_prev;
+	u32 move_back_cnt;
+	u32 move_back_lp_status;
+};
+
+/* Lane device */
+struct lane_device {
+	/* lane memory map: registers base address */
+	void __iomem *reg_base;
+	/* lane memory map size */
+	u32 memmap_size;
+	/* lane address */
+	u32 lane_addr;
+	/* lane relative index inside multi-lane PHY */
+	u8 idx;
+	/* device specific private extension */
+	void *priv;
+	/* phy device */
+	struct phy_device *phydev;
+	struct backplane_device *bpdev;
+	struct lane_kr krln;
+	struct delayed_work krwk;
+	/* mutex between multiple lanes */
+	struct mutex lane_lock;
+};
+
+/* KR LT MMD (MDIO Managed Device) */
+struct kr_lt_mmd {
+	int devad;
+	u32 control;
+	u32 status;
+	u32 lp_cu;
+	u32 lp_status;
+	u32 ld_cu;
+	u32 ld_status;
+};
+
+/* Backplane kr */
+struct backplane_kr {
+	struct kr_coef min_kr;
+	struct kr_coef max_kr;
+	struct kr_coef def_kr;
+	/* defaults for eq kr are initialized from DT: eq-init */
+	bool valid_eq_init;
+	/* defaults for eq params are initialized from DT: eq-params */
+	bool valid_eq_params;
+	/* EQ algorithm name */
+	const char *eqa_name;
+	const struct equalizer_device *equalizer;
+	struct kr_lt_mmd ltmmd;
+};
+
+/* Backplane device specific callbacks */
+struct backplane_ops {
+	/* AN register ops */
+	void (*an_advertisement_init)(struct lane_device *lane);
+	bool (*is_an_link_detected)(struct lane_device *lane);
+	/* default settings ops */
+	void (*setup_default_settings)(struct lane_device *lane);
+	/* LT coefficients validation ops */
+	int (*lt_validation)(struct lane_device *lane, u32 *ld_coef);
+};
+
+/* Backplane driver */
+struct backplane_driver {
+	/* serdes base address */
+	u32 base_addr;
+	/* serdes memory map size */
+	u32 memmap_size;
+	/* serdes endianness */
+	bool is_little_endian;
+	/* memory I/O */
+	struct mem_io io;
+	/* backplane ops */
+	struct backplane_ops bp_ops;
+	/* lane ops */
+	const struct lane_ops *lane_ops;
+	/* device specific private extension */
+	void *priv;
+};
+
+/* Backplane device */
+struct backplane_device {
+	struct phy_device *phydev;
+	u8 num_lanes;
+	bool aneg_config;
+	bool aneg_done;
+	bool phy_suspended;
+	/* backplane management functions */
+	struct backplane_driver drv;
+	/* backplane kr */
+	struct backplane_kr bpkr;
+	/* kr lanes array: valid elements = num_lanes */
+	struct lane_device lane[MAX_KR_LANES_PER_PHY];
+	/* device specific private extension */
+	void *priv;
+	/* bpphy mutexes */
+	struct mutex bpphy_lock;
+	/* mutex between multiple lanes training */
+	struct mutex trained_lock;
+};
+
+bool backplane_is_mode_kr(phy_interface_t interface);
+
+bool backplane_is_valid_mode(phy_interface_t interface);
+
+u8 backplane_num_lanes(phy_interface_t interface);
+
+u32 backplane_get_an_bp_eth_status_bit(phy_interface_t interface);
+
+u32 backplane_get_an_adv1_init(phy_interface_t interface);
+
+bool backplane_is_single_lane(struct backplane_device *bpdev);
+
+bool backplane_is_multi_lane(struct backplane_device *bpdev);
+
+int backplane_are_all_lanes_trained(struct backplane_device *bpdev);
+
+int backplane_get_lanes_trained_count(struct backplane_device *bpdev);
+
+int backplane_is_link_up(struct phy_device *phydev);
+
+void backplane_kr_lt_mmd_c45(struct backplane_kr *bpkr);
+
+void backplane_kr_lt_mmd_setup(struct backplane_kr *bpkr, int devad, u32 base);
+
+int backplane_read_mmd(struct lane_device *lane, int devad, u32 regnum);
+
+int backplane_write_mmd(struct lane_device *lane, int devad, u32 regnum,
+			u16 val);
+
+void backplane_default_kr_lane(struct lane_device *lane);
+
+void backplane_get_current_taps(struct lane_device *lane, u32 *coef);
+
+void backplane_set_current_taps(struct lane_device *lane, u32 *coef);
+
+void backplane_set_all_taps_to_max(struct lane_device *lane);
+
+void backplane_tune_kr_lane(struct lane_device *lane, bool reset_lane);
+
+/* generic main operations to be used on probe callback */
+
+int backplane_create(struct phy_device *phydev);
+
+int backplane_parse_dt(struct phy_device *phydev);
+
+int backplane_setup_memio(struct phy_device *phydev);
+
+int backplane_setup_mmd(struct phy_device *phydev);
+
+int backplane_setup_lanes(struct phy_device *phydev);
+
+int backplane_initialize(struct phy_device *phydev);
+
+/* predefined phy_driver callback functions */
+
+int backplane_probe(struct phy_device *phydev);
+
+void backplane_remove(struct phy_device *phydev);
+
+int backplane_config_init(struct phy_device *phydev);
+
+int backplane_aneg_done(struct phy_device *phydev);
+
+int backplane_config_aneg(struct phy_device *phydev);
+
+int backplane_suspend(struct phy_device *phydev);
+
+int backplane_resume(struct phy_device *phydev);
+
+int backplane_read_status(struct phy_device *phydev);
+
+int backplane_match_phy_device(struct phy_device *phydev);
+
+#endif /* __BACKPLANE_H */
diff --git a/drivers/net/phy/backplane/eq_fixed.c b/drivers/net/phy/backplane/eq_fixed.c
new file mode 100644
index 0000000..827450e
--- /dev/null
+++ b/drivers/net/phy/backplane/eq_fixed.c
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
+/* Fixed: No Equalization algorithm
+ *
+ * Copyright 2019-2020 NXP
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include "equalization.h"
+
+#define ALGORITHM_NAME		"backplane_fixed"
+#define ALGORITHM_DESCR		"Fixed Equalization"
+#define ALGORITHM_VERSION	"1.0.0"
+
+/* Fixed Algorithm API */
+
+/* Create Fixed Equalization Algorithm */
+static struct eq_data_priv *create(struct equalizer_driver eqdrv)
+{
+	return NULL;
+}
+
+static const struct equalization_algorithm eq_alg = {
+	.name = ALGORITHM_NAME,
+	.descr = ALGORITHM_DESCR,
+	.version = ALGORITHM_VERSION,
+	.use_local_tx_training = false,
+	.use_remote_tx_training = false,
+	.ops = {
+		.create = create,
+		.destroy = NULL,
+		.is_rx_ok = NULL,
+		.is_eq_done = NULL,
+		.collect_statistics = NULL,
+		.generate_request = NULL,
+		.process_bad_state = NULL,
+		.dump_algorithm_context = NULL,
+	}
+};
+
+static const char * const alg_keys[] = {
+	DEFAULT_EQ_ALGORITHM,
+	"bypass",
+};
+
+static int __init fixed_init(void)
+{
+	int i, err;
+
+	pr_info("%s: %s algorithm version %s\n",
+		ALGORITHM_NAME, ALGORITHM_DESCR, ALGORITHM_VERSION);
+
+	/* register Fixed algorithm: */
+	for (i = 0; i < ARRAY_SIZE(alg_keys); i++) {
+		err = backplane_eq_register(alg_keys[i], &eq_alg);
+		if (err) {
+			pr_err("%s: '%s' equalization algorithm registration failed\n",
+			       ALGORITHM_NAME, alg_keys[i]);
+		}
+	}
+
+	return 0;
+}
+
+static void __exit fixed_exit(void)
+{
+	int i;
+
+	/* unregister Fixed algorithm: */
+	for (i = 0; i < ARRAY_SIZE(alg_keys); i++)
+		backplane_eq_unregister(alg_keys[i]);
+
+	pr_info("%s: %s algorithm version %s unloaded\n",
+		ALGORITHM_NAME, ALGORITHM_DESCR, ALGORITHM_VERSION);
+}
+
+module_init(fixed_init);
+module_exit(fixed_exit);
+
+MODULE_DESCRIPTION("Fixed Equalization Algorithm");
+MODULE_AUTHOR("Florinel Iordache <florinel.iordache@nxp.com>");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/phy/backplane/equalization.h b/drivers/net/phy/backplane/equalization.h
new file mode 100644
index 0000000..767f0159
--- /dev/null
+++ b/drivers/net/phy/backplane/equalization.h
@@ -0,0 +1,275 @@
+/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
+/* Equalization interface
+ * for Equalization and Link Training (IEEE802.3ap/ba)
+ *
+ * Copyright 2019-2020 NXP
+ */
+
+#ifndef __EQUALIZATION_H
+#define __EQUALIZATION_H
+
+#include <linux/phy.h>
+
+/* Default equalization algorithm */
+#define DEFAULT_EQ_ALGORITHM			"fixed"
+
+struct lane_device;
+struct equalizer_driver;
+
+/* EQ Algorithms Interface used by Link Training
+ * to call equalization algorithms callbacks
+ */
+
+/* Equalization private data
+ * specifically defined by each algorithm to be used internally
+ */
+struct eq_data_priv;
+
+/* Equalization Algorithm operations */
+struct equalization_ops {
+	/* Mandatory operations: */
+	struct eq_data_priv *(*create)(struct equalizer_driver eqdrv);
+	void (*destroy)(struct eq_data_priv *priv);
+	/* Required operations for remote Tx link training: */
+	bool (*is_rx_ok)(struct eq_data_priv *priv);
+	bool (*is_eq_done)(struct eq_data_priv *priv);
+	bool (*collect_statistics)(struct eq_data_priv *priv);
+	void (*generate_request)(struct eq_data_priv *priv);
+	/* Optional operations: */
+	void (*process_bad_state)(struct eq_data_priv *priv);
+	void (*dump_algorithm_context)(struct eq_data_priv *priv);
+};
+
+/* Equalization Algorithm description data */
+struct equalization_algorithm {
+	const char *name;
+	const char *descr;
+	const char *version;
+	bool use_local_tx_training;
+	bool use_remote_tx_training;
+	struct equalization_ops ops;
+};
+
+/* Equalizer Interface for EQ Algorithms:
+ * Used by equalization algorithms to collect equalizer statistics
+ * required to take correct decisions for tuning equalization parameters
+ */
+
+/* Equalizer counters type
+ *
+ * Equalizer Binning Counters for Data Dependent Edge Statistics:
+ *
+ *   Bin(s) = (# late edges - # early edges)
+ *            Prior/Next Edge at T -/+ #UI (Unit Interval)
+ *   Bin_1: 1UI wide pulses: Prior Edge at T - 1UI
+ *      final edges on short pulses:
+ *      - contains the scoring of final edges on pulses that are 1UI long
+ *      - represents the difference between the number of short pulse late edges
+ *        and the number of short pulse early edges
+ *   Bin_2: 2UI wide pulses: Prior Edge at T - 2UI
+ *   Bin_3: 3UI (or >=3UI) wide pulses: Prior Edge at T - 3UI (or T - >=3UI)
+ *   Bin_4: 4UI (or >=4UI) wide pulses: Prior Edge at T - 4UI (or T - >=4UI)
+ *   Bin_Med: >=5UI and <=7UI wide pulses:
+ *      Prior Edge in between T - >=5UI and T - <=7UI
+ *      final edges on medium pulses:
+ *      - contains the scoring of final edges on pulses between 5UI and 7UI long
+ *   Bin_Long: >=8UI wide pulses: Prior Edge at T - >=8UI
+ *      final edges on long pulses:
+ *      - contains the scoring of final edges on pulses longer than 7UI long
+ *      - represents the difference between the number of long pulse late edges
+ *        and the number of long pulse early edges
+ *   Bin_M1: 1UI wide pulses: Next Edge at T + 1UI
+ *      initial edges on short pulses following non-single bits:
+ *      - contains the scoring of initial edges on pulses that are 1UI long
+ *        following non-single bits
+ *      - the next edge is 1UI away and prior edge is more than 1UI away
+ *   Bin_M2: 2UI wide pulses: Next Edge at T + 2UI
+ *   Bin_M3: 3UI (or >=3UI) wide pulses: Next Edge at T + 3UI (or T + >=3UI)
+ *   Bin_M4: 4UI (or >=4UI) wide pulses: Next Edge at T + 4UI (or T + >=4UI)
+ *   Bin_MMed: >=5UI and <=7UI wide pulses:
+ *      Next Edge in between T + >=5UI and T + <=7UI
+ *      initial edges on medium pulses following non-single bits:
+ *      - contains the scoring of initial edges on pulses between 5UI and 7UI
+ *      following non-single bits
+ *   Bin_MLong: >=8UI wide pulses: Next Edge at T + >=8UI
+ *      initial edges on long pulses following non-single bits:
+ *      - contains the scoring of initial edges on pulses longer than 7UI long
+ *      - represents the difference between the number of long pulse late edges
+ *        and the number of long pulse early edges
+ *
+ *   Bin_Offset = [(# late rising edges + # early falling edges) -
+ *                 (# early rising edges + # late falling edges)]
+ *      - contains the transition information for the difference between
+ *        all bits that are narrower than expected and
+ *        all bits that are wider than expected
+ *
+ *   Bin_Avg: Low Pass Filter of Running Disparity
+ *      - Bin_Avg provides a time weighted, filtered average of disparity which
+ *        indicates the BLW potential of recently received data
+ *        New Bin_Avg = Bin_Avg - Bin_Avg/8 + block_disparity
+ *                      where block_disparity = (#of ones - #of zeros)
+ *
+ *   Bin_BLW: Bin Baseline Wander
+ *      - BinBLW accumulates the correlation between Bin_Avg and Bin_Offset
+ *      - Low frequency deficiency (LFD) causes BLW effect
+ *        New Bin_BLW = Bin_BLW + Bin_Avg, for Bin_Offset > 0
+ *                    = Bin_BLW - Bin_Avg, for Bin_Offset < 0
+ *                    = Bin_BLW,           for Bin_Offset = 0
+ *
+ * Equalizer gains:
+ *   GAIN_LF: Low-frequency gain of the equalizer amplifier
+ *   GAIN_MF: Middle-frequency gain of the equalizer amplifier
+ *   GAIN_HF: High-frequency gain of the equalizer amplifier
+ *
+ * Equalizer status:
+ *   EQOFFSET: equalization offset status
+ *      Binary coded status of RX Adaptive Equalization offset controls of lane
+ */
+enum eqc_type {
+	EQC_BIN_1,
+	EQC_BIN_2,
+	EQC_BIN_3,
+	EQC_BIN_4,
+	EQC_BIN_MED,
+	EQC_BIN_LONG,
+	EQC_BIN_M1,
+	EQC_BIN_M2,
+	EQC_BIN_M3,
+	EQC_BIN_M4,
+	EQC_BIN_MMED,
+	EQC_BIN_MLONG,
+	EQC_BIN_OFFSET,
+	EQC_BIN_AVG,
+	EQC_BIN_BLW,
+	EQC_GAIN_LF,
+	EQC_GAIN_MF,
+	EQC_GAIN_HF,
+	EQC_EQOFFSET,
+};
+
+/* Equalizer counters range */
+struct eqc_range {
+	s16 min;
+	s16 max;
+	s16 mid_low;
+	s16 mid_high;
+};
+
+/* Equalizer counters collection operations */
+struct equalizer_ops {
+	int (*collect_counters)(void *reg, enum eqc_type type, s16 *counters,
+				u8 size);
+	int (*collect_multiple_counters)(void *reg, enum eqc_type type[],
+					 u8 type_no, s16 *counters, u8 size);
+	struct eqc_range *(*get_counter_range)(enum eqc_type type);
+};
+
+/* Equalizer device and operations */
+struct equalizer_device {
+	const char *name;
+	const char *version;
+	struct equalizer_ops ops;
+};
+
+/* Equalization driver */
+struct equalizer_driver {
+	struct phy_device *phydev;
+	/* lane info used as parameter for link training API */
+	struct lane_device *lane;
+	/* lane reg base used as parameter for equalizer ops */
+	void *reg_base;
+	const struct equalizer_device *equalizer;
+};
+
+/* Link Training Interface used by EQ Algorithms
+ * to interact with IEEE802.3ap/ba standards
+ */
+
+/* update request type
+ * Identifies the LP update request type according to IEEE802.3ap-2007
+ * which must be sent to LP to request coefficients update
+ *
+ * HOLD: Request LP to Hold all coefficients update
+ * INC: Request LP to Increment the specified coefficient
+ * DEC: Request LP to Decrement the specified coefficient
+ * INIT: Request LP to Initialize all coefficients
+ * PRESET: Request LP to set all coefficients to Preset
+ * INVALID: Invalid request type: should not be used as LP request
+ */
+enum req_type {
+	REQ_HOLD,
+	REQ_INC,
+	REQ_DEC,
+	REQ_INIT,
+	REQ_PRESET,
+	REQ_INVALID
+};
+
+/* coefficient field
+ * Identifies the coefficient field on which must take a desired action
+ * according to IEEE802.3ap-2007
+ *
+ * coefficients:
+ *  M1: C(-1): Pre-cursor
+ *  Z0: C(0):  Main cursor
+ *  P1: C(+1): Post-cursor
+ *  NO: Number of coefficients (this is not a valid coefficient field)
+ */
+enum coef_field {
+	C_M1,
+	C_Z0,
+	C_P1,
+	C_NO
+};
+
+/* coefficient status
+ * Specifies the coefficient status according to IEEE802.3ap-2007:
+ * 72.6.10.2.5 Coefficient update process
+ *
+ * NOTUPDATED: Coefficient is not updated
+ * UPDATED: Coefficient is updated
+ * MIN: Coefficient has reached the minimum threshold
+ * MAX: Coefficient has reached the maximum threshold
+ * INVALID: Invalid coefficient status
+ */
+enum coef_status {
+	COEF_NOTUPDATED,
+	COEF_UPDATED,
+	COEF_MIN,
+	COEF_MAX,
+	COEF_INVALID
+};
+
+void lt_lp_update(struct lane_device *lane, u32 update);
+
+u32 lt_encode_request(u32 base_update, enum req_type req,
+		      enum coef_field field);
+
+u32 lt_encode_startup_request(enum req_type req);
+
+enum req_type lt_decode_coef_update(u32 update, enum coef_field field);
+
+bool lt_is_update_of_type(u32 update, enum req_type type);
+
+bool lt_is_lp_at_startup(struct lane_device *lane, enum req_type type);
+
+enum coef_status lt_get_lp_coef_status(struct lane_device *lane,
+				       enum coef_field field);
+
+void lt_move_lp_back(struct lane_device *lane);
+
+void lt_set_error(struct lane_device *lane, bool err);
+
+/* Backplane Driver Interface for EQ Algorithms:
+ * Used by equalization algorithms to interact
+ * with backplane driver during equalization
+ */
+
+/* equalization algorithm registration */
+int backplane_eq_register(const char *key,
+			  const struct equalization_algorithm *eq_info);
+void backplane_eq_unregister(const char *key);
+
+bool backplane_is_cdr_lock(struct lane_device *lane, bool retry);
+
+#endif /* __EQUALIZATION_H */
diff --git a/drivers/net/phy/backplane/link_training.c b/drivers/net/phy/backplane/link_training.c
new file mode 100644
index 0000000..3aa26f9
--- /dev/null
+++ b/drivers/net/phy/backplane/link_training.c
@@ -0,0 +1,1529 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
+/* Link Training (IEEE802.3ap/ba)
+ * Ethernet Operation over Electrical Backplanes
+ *
+ * Copyright 2019-2020 NXP
+ */
+
+#include <linux/mdio.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+
+#include "link_training.h"
+
+/* KR LP/LD Coefficients */
+#define PRESET_MASK				0x2000
+#define INIT_MASK				0x1000
+#define COP1_MASK				0x30
+#define COP1_SHIFT				4
+#define COZ0_MASK				0xc
+#define COZ0_SHIFT				2
+#define COM1_MASK				0x3
+#define COM1_SHIFT				0
+#define ALL_COEF_MASK		(COP1_MASK | COZ0_MASK | COM1_MASK)
+#define LD_ALL_MASK		(PRESET_MASK | INIT_MASK | ALL_COEF_MASK)
+
+/* KR LP Status Report */
+#define LP_STATUS_ALL_COEF_UPDATED		0x15
+
+/* KR LP/LD Status Report:
+ * RX_READY_MASK - Receiver Ready
+ * 0b - The LP/LD receiver is requesting that training continue
+ * 1b - The LP/LD receiver has determined that training is complete
+ * and is prepared to receive data.
+ */
+#define RX_READY_MASK				0x8000
+
+/* Increment/Decrement Requests */
+#define HOLD					0
+#define INCREMENT				1
+#define DECREMENT				2
+#define RESERVED				3
+
+/* Increment/Decrement Steps */
+#define STEP_INCREMENT_P1			-1
+#define STEP_INCREMENT_Z0			1
+#define STEP_INCREMENT_M1			-1
+
+/* KR PMD Control defines */
+#define TRAIN_EN				0x3
+#define TRAIN_DISABLE				0x1
+#define PMD_RESET				0x1
+
+/* KR PMD Status defines */
+#define PMD_STATUS_TRAIN_FAIL			0x8
+#define PMD_STATUS_SUP_STAT			0x4
+#define PMD_STATUS_FRAME_LOCK			0x2
+#define PMD_STATUS_RX_STAT			0x1
+
+/* KR PMD control register (Register 1.150) */
+#define KR_PMD_BASE_OFFSET			150
+
+/* Link training KR PMD registers offsets (relative to base) */
+#define OFFSET_KR_PMD_CTRL			0x0
+#define OFFSET_KR_PMD_STATUS			0x1
+#define OFFSET_KR_LP_CU				0x2
+#define OFFSET_KR_LP_STATUS			0x3
+#define OFFSET_KR_LD_CU				0x4
+#define OFFSET_KR_LD_STATUS			0x5
+
+/* Timeouts */
+#define TIMEOUT_MOVE_BACK_PREV			6
+#define TIMEOUT_REPEAT_REQUEST			10
+
+/* Training for Remote Tx */
+
+static u32 get_mask_for_req(enum req_type req)
+{
+	u32 cmd = HOLD;
+
+	switch (req) {
+	case REQ_HOLD:
+		cmd = HOLD;
+		break;
+	case REQ_INC:
+		cmd = INCREMENT;
+		break;
+	case REQ_DEC:
+		cmd = DECREMENT;
+		break;
+	case REQ_INIT:
+		cmd = INIT_MASK;
+		break;
+	case REQ_PRESET:
+		cmd = PRESET_MASK;
+		break;
+	case REQ_INVALID:
+		cmd = RESERVED;
+		break;
+	default:
+		cmd = HOLD;
+		break;
+	}
+	return cmd;
+}
+
+static enum req_type get_req_for_mask(u32 cmd)
+{
+	enum req_type req = REQ_HOLD;
+
+	switch (cmd) {
+	case HOLD:
+		req = REQ_HOLD;
+		break;
+	case INCREMENT:
+		req = REQ_INC;
+		break;
+	case DECREMENT:
+		req = REQ_DEC;
+		break;
+	case INIT_MASK:
+		req = REQ_INIT;
+		break;
+	case PRESET_MASK:
+		req = REQ_PRESET;
+		break;
+	case RESERVED:
+		req = REQ_INVALID;
+		break;
+	default:
+		req = REQ_HOLD;
+		break;
+	}
+	return req;
+}
+
+/* ld_coef_status
+ * 72.6.10.2.5 Coefficient update process
+ * Once the updated, maximum, or minimum state is reported it continues
+ * to be reported until a hold request is received,
+ * after which the status reverts to not_updated.
+ */
+static void ld_coef_status(struct lane_device *lane)
+{
+	backplane_write_mmd(lane, lane->bpdev->bpkr.ltmmd.devad,
+			    lane->bpdev->bpkr.ltmmd.ld_status,
+			    lane->krln.ld_status);
+}
+
+/* ld_coef_update
+ * LD sends to LP the specified request for coefficients update
+ */
+static void ld_coef_update(struct lane_device *lane)
+{
+	backplane_write_mmd(lane, lane->bpdev->bpkr.ltmmd.devad,
+			    lane->bpdev->bpkr.ltmmd.ld_cu,
+			    lane->krln.ld_update);
+}
+
+/* get_lp_lcs
+ * get LP lcs (last change status)
+ * returns the last LP change (non-zero) status:
+ * meaning the last LP status resulted from a change request
+ * 72.6.10.2.5 Coefficient update process
+ * Once the updated, maximum, or minimum state is reported it continues
+ * to be reported until a hold request is received,
+ * after which the status reverts to not_updated.
+ */
+static u32 get_lp_lcs(struct lane_device *lane)
+{
+	return lane->krln.lp_last_change_status;
+}
+
+static bool is_all_status(u32 status, enum coef_status cs)
+{
+	return ((status & ALL_COEF_MASK) ==
+		(cs << COP1_SHIFT | cs << COZ0_SHIFT | cs << COM1_SHIFT));
+}
+
+/* Training for Local Tx */
+
+static void initialize(struct lane_device *lane)
+{
+	backplane_default_kr_lane(lane);
+
+	lane->krln.ld_status &= ~ALL_COEF_MASK;
+	lane->krln.ld_status |= COEF_UPDATED << COP1_SHIFT |
+				COEF_UPDATED << COZ0_SHIFT |
+				COEF_UPDATED << COM1_SHIFT;
+
+	ld_coef_status(lane);
+}
+
+/* preset
+ * Preset as defined by: IEEE 802.3, sub-clause 72.6.10.2.3.1
+ * Setup all coefficients to MAX values from IEEE802.3 perspective
+ */
+static void preset(struct lane_device *lane)
+{
+	backplane_set_all_taps_to_max(lane);
+
+	backplane_tune_kr_lane(lane, true);
+
+	lane->krln.ld_status &= ~ALL_COEF_MASK;
+	lane->krln.ld_status |= COEF_MAX << COP1_SHIFT |
+				COEF_MAX << COZ0_SHIFT |
+				COEF_MAX << COM1_SHIFT;
+
+	ld_coef_status(lane);
+}
+
+static bool is_rx_ready(u32 status)
+{
+	return ((status & RX_READY_MASK) != 0);
+}
+
+/* is_ld_valid
+ * LD coefficient values have hardware restrictions
+ * Check if all ld coefficients are in range
+ */
+static int is_ld_valid(struct lane_device *lane, u32 *ld_coef)
+{
+	struct backplane_kr *bpkr = &lane->bpdev->bpkr;
+	u32 mainq = ld_coef[C_Z0];
+	u32 postq = ld_coef[C_P1];
+	u32 preq = ld_coef[C_M1];
+
+	/* Basic HW restrictions: */
+
+	/* 1. tx_preq <= MIN_C(-1) */
+	if (preq > bpkr->min_kr.preq)
+		return -ERANGE;
+	/* 2. tx_ratio_post1q <= MIN_C(+1) */
+	if (postq > bpkr->min_kr.postq)
+		return -ERANGE;
+	/* 3. MIN_C(0) <= tx_mainq <= MAX_C(0) */
+	if (mainq < bpkr->min_kr.mainq)
+		return -ERANGE;
+	if (mainq > bpkr->max_kr.mainq)
+		return -ERANGE;
+	/* 4. tx_ratio_post1q >= tx_preq */
+	if (postq < preq)
+		return -ERANGE;
+
+	/* Additional HW restrictions:
+	 * LT custom HW validation: Device specific HW restrictions
+	 */
+	if (lane->bpdev->drv.bp_ops.lt_validation)
+		return lane->bpdev->drv.bp_ops.lt_validation(lane, ld_coef);
+
+	return 0;
+}
+
+static bool update_ld_status(struct lane_device *lane, enum coef_field field,
+			     enum coef_status cs)
+{
+	u32 ld_cs = cs;
+	u32 mask, val;
+
+	if (cs == COEF_INVALID)
+		return false;
+
+	switch (field) {
+	case C_P1:
+		mask = COP1_MASK;
+		val = ld_cs << COP1_SHIFT;
+		break;
+	case C_Z0:
+		mask = COZ0_MASK;
+		val = ld_cs << COZ0_SHIFT;
+		break;
+	case C_M1:
+		mask = COM1_MASK;
+		val = ld_cs << COM1_SHIFT;
+		break;
+	default:
+		return false;
+	}
+
+	lane->krln.ld_status &= ~mask;
+	lane->krln.ld_status |= val;
+
+	return true;
+}
+
+static enum coef_status inc_dec(struct lane_device *lane,
+				enum coef_field field, int request)
+{
+	u32 ld_coef[C_NO], step[C_NO], ld_limit[C_NO];
+	int err;
+
+	backplane_get_current_taps(lane, ld_coef);
+
+	step[C_M1] = STEP_INCREMENT_M1;
+	step[C_Z0] = STEP_INCREMENT_Z0;
+	step[C_P1] = STEP_INCREMENT_P1;
+
+	/* 72.6.10.2.5 Coefficient update process
+	 * Upon execution of a received increment or decrement request,
+	 * the status is reported as updated, maximum, or minimum.
+	 */
+	switch (request) {
+	case INCREMENT:
+		ld_limit[C_M1] = lane->bpdev->bpkr.max_kr.preq;
+		ld_limit[C_Z0] = lane->bpdev->bpkr.max_kr.mainq;
+		ld_limit[C_P1] = lane->bpdev->bpkr.max_kr.postq;
+		if (ld_coef[field] != ld_limit[field])
+			ld_coef[field] += step[field];
+		else
+			return COEF_MAX;
+		break;
+	case DECREMENT:
+		ld_limit[C_M1] = lane->bpdev->bpkr.min_kr.preq;
+		ld_limit[C_Z0] = lane->bpdev->bpkr.min_kr.mainq;
+		ld_limit[C_P1] = lane->bpdev->bpkr.min_kr.postq;
+		if (ld_coef[field] != ld_limit[field])
+			ld_coef[field] -= step[field];
+		else
+			return COEF_MIN;
+		break;
+	default:
+		break;
+	}
+
+	err = is_ld_valid(lane, ld_coef);
+	if (!err) {
+		/* accept new ld coefficients */
+		backplane_set_current_taps(lane, ld_coef);
+		backplane_tune_kr_lane(lane, false);
+	} else {
+		if (request == DECREMENT)
+			return COEF_MIN;
+		if (request == INCREMENT)
+			return COEF_MAX;
+	}
+
+	/* UPDATED */
+	return COEF_UPDATED;
+}
+
+static void check_request(struct lane_device *lane, int request)
+{
+	enum coef_status cu = COEF_INVALID;
+	int cop1_req, coz0_req, com1_req;
+	int old_status;
+
+	cop1_req = (request & COP1_MASK) >> COP1_SHIFT;
+	coz0_req = (request & COZ0_MASK) >> COZ0_SHIFT;
+	com1_req = (request & COM1_MASK) >> COM1_SHIFT;
+
+	/* IEEE802.3-2008, 72.6.10.2.5
+	 * Ensure we only act on INCREMENT/DECREMENT when we are in NOT UPDATED
+	 *
+	 * 72.6.10.2.5 Coefficient update process
+	 * An increment or decrement request will only be acted upon when
+	 * the state of the tap is not_updated.
+	 */
+	old_status = lane->krln.ld_status;
+
+	if (cop1_req && !(lane->krln.ld_status & COP1_MASK)) {
+		cu = inc_dec(lane, C_P1, cop1_req);
+		update_ld_status(lane, C_P1, cu);
+	}
+
+	if (coz0_req && !(lane->krln.ld_status & COZ0_MASK)) {
+		cu = inc_dec(lane, C_Z0, coz0_req);
+		update_ld_status(lane, C_Z0, cu);
+	}
+
+	if (com1_req && !(lane->krln.ld_status & COM1_MASK)) {
+		cu = inc_dec(lane, C_M1, com1_req);
+		update_ld_status(lane, C_M1, cu);
+	}
+
+	if (old_status != lane->krln.ld_status)
+		ld_coef_status(lane);
+}
+
+static void training_complete(struct lane_device *lane)
+{
+	struct training_status *trst = &lane->krln.trst;
+
+	/* update training status */
+	trst->remote_tx_complete = true;
+	trst->remote_tx_running = false;
+
+	/* report LD status */
+	lane->krln.ld_status |= RX_READY_MASK;
+	ld_coef_status(lane);
+
+	/* update PMD status and tell LP we are ready */
+	backplane_write_mmd(lane, lane->bpdev->bpkr.ltmmd.devad,
+			    lane->bpdev->bpkr.ltmmd.status,
+			    PMD_STATUS_RX_STAT);
+}
+
+/* Link Training general API */
+
+/* Setup standard KR LT MMD (MDIO Managed Device) for Clause 45
+ * 45.2.1.76 10GBASE-KR PMD control register (Register 1.150)
+ */
+void lt_mmd_c45(struct backplane_kr *bpkr)
+{
+	lt_mmd_setup(bpkr, MDIO_MMD_PMAPMD, KR_PMD_BASE_OFFSET);
+}
+
+/* Setup KR LT MMD (MDIO Managed Device)
+ * IEEE Std 802.3ap-2007: Table 45.3 PMA/PMD registers
+ */
+void lt_mmd_setup(struct backplane_kr *bpkr, int devad, u32 base)
+{
+	bpkr->ltmmd.devad = devad;
+	bpkr->ltmmd.control = base + OFFSET_KR_PMD_CTRL;
+	bpkr->ltmmd.status = base + OFFSET_KR_PMD_STATUS;
+	bpkr->ltmmd.lp_cu = base + OFFSET_KR_LP_CU;
+	bpkr->ltmmd.lp_status = base + OFFSET_KR_LP_STATUS;
+	bpkr->ltmmd.ld_cu = base + OFFSET_KR_LD_CU;
+	bpkr->ltmmd.ld_status = base + OFFSET_KR_LD_STATUS;
+}
+
+/* lt_is_lp_rx_ready
+ * Reports if LP Receiver is ready
+ * false: The LP receiver is requesting that training continue
+ * true: The LP receiver has determined that training is complete
+ * and is prepared to receive data.
+ */
+bool lt_is_lp_rx_ready(struct lane_device *lane)
+{
+	struct kr_lt_mmd *ltmmd = &lane->bpdev->bpkr.ltmmd;
+
+	/* Read LP Status */
+	lane->krln.lp_status = backplane_read_mmd(lane,
+						  ltmmd->devad,
+						  ltmmd->lp_status);
+	return is_rx_ready(lane->krln.lp_status);
+}
+
+/* lt_is_ld_rx_ready
+ * Reports if LD Receiver is ready
+ * false: The LD receiver is requesting that training continue
+ * true: The LD receiver has determined that training is complete
+ * and is prepared to receive data.
+ */
+bool lt_is_ld_rx_ready(struct lane_device *lane)
+{
+	return is_rx_ready(lane->krln.ld_status);
+}
+
+void lt_start(struct lane_device *lane)
+{
+	backplane_write_mmd(lane, lane->bpdev->bpkr.ltmmd.devad,
+			    lane->bpdev->bpkr.ltmmd.control,
+			    TRAIN_EN);
+}
+
+void lt_stop(struct lane_device *lane)
+{
+	backplane_write_mmd(lane, lane->bpdev->bpkr.ltmmd.devad,
+			    lane->bpdev->bpkr.ltmmd.control,
+			    TRAIN_DISABLE);
+}
+
+void lt_reset(struct lane_device *lane)
+{
+	backplane_write_mmd(lane, lane->bpdev->bpkr.ltmmd.devad, MDIO_CTRL1,
+			    PMD_RESET);
+	backplane_write_mmd(lane, lane->bpdev->bpkr.ltmmd.devad,
+			    lane->bpdev->bpkr.ltmmd.control,
+			    TRAIN_DISABLE);
+	backplane_write_mmd(lane, lane->bpdev->bpkr.ltmmd.devad,
+			    lane->bpdev->bpkr.ltmmd.ld_cu, 0);
+	backplane_write_mmd(lane, lane->bpdev->bpkr.ltmmd.devad,
+			    lane->bpdev->bpkr.ltmmd.ld_status, 0);
+	backplane_write_mmd(lane, lane->bpdev->bpkr.ltmmd.devad,
+			    lane->bpdev->bpkr.ltmmd.status, 0);
+	backplane_write_mmd(lane, lane->bpdev->bpkr.ltmmd.devad,
+			    lane->bpdev->bpkr.ltmmd.lp_cu, 0);
+	backplane_write_mmd(lane, lane->bpdev->bpkr.ltmmd.devad,
+			    lane->bpdev->bpkr.ltmmd.lp_status, 0);
+}
+
+/* lt_is_rx_trained
+ * IEEE Std 802.3ap-2007: Table 72.3 MDIO/PMD status variable mapping
+ * PMD status variable: rx_trained
+ */
+bool lt_is_rx_trained(struct lane_device *lane)
+{
+	struct phy_device *phydev = lane->phydev;
+	int timeout = 100;
+	int val;
+
+	val = backplane_read_mmd(lane, lane->bpdev->bpkr.ltmmd.devad,
+				 lane->bpdev->bpkr.ltmmd.status);
+
+	if ((val & PMD_STATUS_RX_STAT) && !(val & PMD_STATUS_TRAIN_FAIL)) {
+		while (timeout--) {
+			if (backplane_is_link_up(phydev))
+				return true;
+
+			usleep_range(100, 500);
+		}
+	}
+	return false;
+}
+
+/* lt_is_training_failure
+ * IEEE Std 802.3ap-2007: Table 72.3 MDIO/PMD status variable mapping
+ * PMD status variable: PMD_fault
+ */
+bool lt_is_training_failure(struct lane_device *lane)
+{
+	struct kr_lt_mmd *ltmmd = &lane->bpdev->bpkr.ltmmd;
+	int lt_state;
+
+	lt_state = backplane_read_mmd(lane, ltmmd->devad, ltmmd->status);
+
+	/* according to spec: 8023ap-2007.pdf
+	 * training_failure
+	 * Boolean variable that is set to TRUE when the training state machine
+	 * has timed out due to expiration of the max_wait_timer while in the
+	 * SEND_TRAINING, TRAIN_LOCAL, or
+	 * TRAIN_REMOTE states and is set to FALSE otherwise.
+	 */
+	if (lt_state & PMD_STATUS_TRAIN_FAIL)
+		return true;
+
+	return false;
+}
+
+/* lt_is_frame_lock
+ * IEEE Std 802.3ap-2007: Table 72.3 MDIO/PMD status variable mapping
+ * PMD status variable: frame_lock
+ */
+bool lt_is_frame_lock(struct lane_device *lane)
+{
+	struct kr_lt_mmd *ltmmd = &lane->bpdev->bpkr.ltmmd;
+	int lt_state;
+
+	lt_state = backplane_read_mmd(lane, ltmmd->devad, ltmmd->status);
+
+	if ((lt_state & PMD_STATUS_SUP_STAT) &&
+	    (lt_state & PMD_STATUS_FRAME_LOCK))
+		return true;
+
+	return false;
+}
+
+/* Training for Remote Tx
+ * This is the main routine for Remote Tx training
+ */
+void lt_train_remote_tx(struct lane_device *lane)
+{
+	const struct equalization_algorithm *eq_alg = lane->krln.eq_alg;
+	struct training_status *trst = &lane->krln.trst;
+	u32 prev_req_cp1, prev_req_cz0, prev_req_cm1;
+	u32 status_cp1, status_cz0, status_cm1;
+	u32 prev_req_init, prev_req_preset;
+	u64 lp_resp_time;
+
+	/* Check stop condition for Remote Tx training */
+	if (trst->remote_tx_complete)
+		return;
+
+	/* Check if equalization algorithm is installed */
+	if (!eq_alg)
+		return;
+
+	/* Check that all required callback operations are installed */
+	if (!eq_alg->ops.collect_statistics ||
+	    !eq_alg->ops.is_rx_ok ||
+	    !eq_alg->ops.generate_request ||
+	    !eq_alg->ops.is_eq_done)
+		return;
+
+	/* Start new Remote Tx training step */
+	trst->remote_tx_running = true;
+
+	/* Store current state as previous state */
+	lane->krln.prev_ld_update = lane->krln.ld_update;
+	if ((lane->krln.prev_ld_update & ALL_COEF_MASK) != HOLD)
+		lane->krln.ld_last_nonhold_update = lane->krln.prev_ld_update;
+
+	prev_req_init = lane->krln.prev_ld_update & INIT_MASK;
+	prev_req_preset = lane->krln.prev_ld_update & PRESET_MASK;
+	prev_req_cp1 = (lane->krln.prev_ld_update & COP1_MASK) >> COP1_SHIFT;
+	prev_req_cz0 = (lane->krln.prev_ld_update & COZ0_MASK) >> COZ0_SHIFT;
+	prev_req_cm1 = (lane->krln.prev_ld_update & COM1_MASK) >> COM1_SHIFT;
+
+	/* Training Done condition */
+	if (eq_alg->ops.is_eq_done(lane->krln.eq_priv))
+		trst->done_training = true;
+
+	/* Check if Training is Done */
+	if (trst->done_training) {
+		training_complete(lane);
+		return;
+	}
+
+	/* Read LP Status */
+	lane->krln.lp_status =
+		backplane_read_mmd(lane, lane->bpdev->bpkr.ltmmd.devad,
+				   lane->bpdev->bpkr.ltmmd.lp_status);
+
+	if ((lane->krln.lp_status & ALL_COEF_MASK) != 0)
+		lane->krln.lp_last_change_status = lane->krln.lp_status;
+
+	status_cp1 = (lane->krln.lp_status & COP1_MASK) >> COP1_SHIFT;
+	status_cz0 = (lane->krln.lp_status & COZ0_MASK) >> COZ0_SHIFT;
+	status_cm1 = (lane->krln.lp_status & COM1_MASK) >> COM1_SHIFT;
+
+	if (status_cp1 == COEF_UPDATED || status_cp1 == COEF_MIN ||
+	    status_cp1 == COEF_MAX)
+		lane->krln.last_lp_update_status[C_P1] = status_cp1;
+	if (status_cz0 == COEF_UPDATED || status_cz0 == COEF_MIN ||
+	    status_cz0 == COEF_MAX)
+		lane->krln.last_lp_update_status[C_Z0] = status_cz0;
+	if (status_cm1 == COEF_UPDATED || status_cm1 == COEF_MIN ||
+	    status_cm1 == COEF_MAX)
+		lane->krln.last_lp_update_status[C_M1] = status_cm1;
+
+	/* IEEE802.3-2008, 72.6.10.2.3.2
+	 * we send initialize to the other side to ensure default settings
+	 * for the LP. Naturally, we should do this only once.
+	 */
+	if (!trst->sent_init) {
+		/* All status MUST be NOTUPDATED for INIT to be executed
+		 * otherwise send HOLD first
+		 */
+		if (status_cp1 == COEF_NOTUPDATED &&
+		    status_cz0 == COEF_NOTUPDATED &&
+		    status_cm1 == COEF_NOTUPDATED) {
+			trst->sent_init = true;
+			lane->krln.ld_update = INIT_MASK;
+			lane->krln.req_ld_update_init_count = 1;
+			lane->krln.init_handshake_time =
+						jiffies_to_msecs(jiffies);
+		} else {
+			/* send HOLD before sending subsequent Init requests
+			 * this is not the very first Init sent
+			 */
+			lane->krln.ld_update = HOLD;
+		}
+		ld_coef_update(lane);
+		return;
+	}
+	/* continue to sent init request until LP responds to init */
+	if (prev_req_init) {
+		if (lane->krln.lp_status == 0) {
+			/* nothing to do here for now
+			 * perhaps the partner board LP has not yet started
+			 * so continue to send INIT requests
+			 * this will happen in the next condition anyway
+			 */
+		}
+		/* 72.6.10.2.3.2 Initialize
+		 * The initialize control shall only be initially sent when all
+		 * coefficient status fields indicate not_updated,
+		 * and will then continue to be sent
+		 * until no coefficient status field indicates not_updated.
+		 */
+		if (status_cp1 == COEF_NOTUPDATED ||
+		    status_cz0 == COEF_NOTUPDATED ||
+		    status_cm1 == COEF_NOTUPDATED) {
+			lane->krln.ld_update = INIT_MASK;
+			ld_coef_update(lane);
+			lane->krln.req_ld_update_init_count++;
+		} else {
+			/* IEEE802.3-2008, 72.6.10.2.3.2
+			 * We may clear INITIALIZE when no coefficients
+			 * show NOT UPDATED.
+			 */
+			/* v1: lane->krln.ld_update &= ~INIT_MASK; */
+			/* better send request: HOLD ALL
+			 * should be equivalent since only INIT is set now
+			 */
+			lane->krln.ld_update = HOLD;
+
+			lp_resp_time = jiffies_to_msecs(jiffies) -
+					       lane->krln.init_handshake_time;
+			if (!lane->krln.first_recv_init) {
+				/* Init handshake not done yet,
+				 * but will be soon
+				 */
+				lane->krln.req_ld_update_init_count = 1;
+				lp_resp_time = 0;
+			}
+			ld_coef_update(lane);
+		}
+		return;
+	}
+
+	/* 72.6.10.2.3.1 Preset
+	 * The preset control shall only be initially sent when all coefficient
+	 * status fields indicate not_updated,
+	 * and will then continue to be sent until the status for all
+	 * coefficients indicates updated or maximum
+	 */
+	/* IEEE802.3-2008, 72.6.10.2.3.1
+	 * We may clear PRESET when all coefficients show UPDATED or MAX.
+	 */
+	/* check if previous request was preset */
+	if (prev_req_preset) {
+		if ((status_cp1 == COEF_UPDATED || status_cp1 == COEF_MAX) &&
+		    (status_cz0 == COEF_UPDATED || status_cz0 == COEF_MAX) &&
+		    (status_cm1 == COEF_UPDATED || status_cm1 == COEF_MAX)) {
+			lane->krln.ld_update &= ~PRESET_MASK;
+		} else {
+			/* All status MUST be NOTUPDATED for INIT to be executed
+			 * otherwise send HOLD first
+			 */
+			if (status_cp1 == COEF_NOTUPDATED &&
+			    status_cz0 == COEF_NOTUPDATED &&
+			    status_cm1 == COEF_NOTUPDATED) {
+				lane->krln.ld_update = PRESET_MASK;
+			} else {
+				/* send HOLD before sending subsequent
+				 * Preset requests
+				 */
+				lane->krln.ld_update = HOLD;
+			}
+			ld_coef_update(lane);
+			return;
+		}
+	}
+
+	/* IEEE802.3-2008, 72.6.10.2.3.3
+	 * We only request coefficient updates when no PRESET/INITIALIZE is
+	 * pending. We also only request coefficient updates when the
+	 * corresponding status is NOT UPDATED and nothing is pending.
+	 */
+	if (lane->krln.ld_update & (PRESET_MASK | INIT_MASK))
+		return;
+
+	/* continue to move back to previous request until LP responds to it
+	 * Move back to previous C(-1), C(0), C(+1) and HOLD
+	 */
+	if (lane->krln.move_back_prev) {
+		/* can exit from here only with: DONE Training */
+		if (lane->krln.move_back_cnt == TIMEOUT_MOVE_BACK_PREV) {
+			trst->done_training = true;
+			training_complete(lane);
+			return;
+		}
+		lane->krln.move_back_cnt++;
+
+		if (status_cp1 == COEF_UPDATED)
+			lane->krln.move_back_lp_status |=
+						(COEF_UPDATED << COP1_SHIFT);
+		if (status_cz0 == COEF_UPDATED)
+			lane->krln.move_back_lp_status |=
+						(COEF_UPDATED << COZ0_SHIFT);
+		if (status_cm1 == COEF_UPDATED)
+			lane->krln.move_back_lp_status |=
+						(COEF_UPDATED << COM1_SHIFT);
+
+		if ((lane->krln.move_back_lp_status & ALL_COEF_MASK) ==
+						LP_STATUS_ALL_COEF_UPDATED) {
+			trst->done_training = true;
+			training_complete(lane);
+			return;
+		}
+
+		/* Move back to previous C(-1), C(0), C(+1) */
+		lane->krln.ld_update = lane->krln.prev_ld_update;
+		ld_coef_update(lane);
+		return;
+	}
+
+	/* 72.6.10.2.5 Coefficient update process
+	 * Once the updated, maximum, or minimum state is reported it continues
+	 * to be reported until a hold request is received,
+	 * after which the status reverts to not_updated.
+	 */
+
+	/* IEEE802.3-2008, 72.6.10.2.3.3
+	 * We set coefficient requests to HOLD when we get the information
+	 * about any updates On clearing our prior response, we also update
+	 * our internal status.
+	 */
+
+	/* send a Hold if want to send another INC same as previous
+	 * and received status: NOTUPDATED
+	 * 1. Continue to send previous REQ until receive status UPDATED
+	 * 2. Continue to send HOLD until receive status NOTUPDATED
+	 */
+
+	/* 3. LP can remain stuck ~42 ms in reset Rx lane: so we should wait
+	 * around ~50 ms and only after that issue Timeout error message
+	 */
+
+	switch (prev_req_cp1) {
+	case HOLD:
+		/* previous request was: HOLD */
+		if (status_cp1 == COEF_NOTUPDATED) {
+			/* All good here:
+			 * continue to check the other coefficient requests
+			 * and if all are good then proceed to
+			 * generate coefficient tuning requests
+			 */
+		} else {
+			/* Continue to send the same request: (2.)
+			 * Continue to send HOLD until receive status NOTUPDATED
+			 */
+			if (lane->krln.repeat_request_count >=
+						TIMEOUT_REPEAT_REQUEST) {
+				phydev_err(lane->phydev,
+					   "REQ Timeout: Repeating C(+1) HOLD request without LP response timeout !\n");
+				/* Hold Request Timeout:
+				 * continue to send HOLD until LP responds
+				 * with NOTUPDATED
+				 */
+				lane->krln.repeat_request_count = 0;
+			} else {
+				/* Allow LP some time to respond
+				 * and repeat request
+				 */
+				msleep(20);
+				/* Allow LP more time to respond,
+				 * as the last chance, on the last time
+				 * before issuing timeout error: (3.)
+				 */
+				if (lane->krln.repeat_request_count ==
+						TIMEOUT_REPEAT_REQUEST - 1)
+					msleep(30);
+				lane->krln.repeat_request_count++;
+			}
+			ld_coef_update(lane);
+			return;
+		}
+		break;
+	case INCREMENT:
+	case DECREMENT:
+		/* previous request was: INC/DEC */
+		if (status_cp1 == COEF_NOTUPDATED) {
+			/* Continue to send the same request: (1.)
+			 * Continue to send previous REQ
+			 * until receive status UPDATED
+			 */
+			if (lane->krln.repeat_request_count >=
+						TIMEOUT_REPEAT_REQUEST) {
+				if (prev_req_cp1 == INCREMENT)
+					phydev_err(lane->phydev,
+						   "REQ Timeout: Repeating C(+1) INC request without LP response timeout !\n");
+				else
+					phydev_err(lane->phydev,
+						   "REQ Timeout: Repeating C(+1) DEC request without LP response timeout !\n");
+				/* Request Timeout:
+				 * just continue: proceed again to
+				 * generate coefficient tuning requests
+				 */
+			} else {
+				/* Allow LP some time to respond
+				 * and repeat request
+				 */
+				msleep(20);
+				/* Allow LP more time to respond,
+				 * as the last chance,
+				 * on the last time before
+				 * issuing timeout error: (3.)
+				 */
+				if (lane->krln.repeat_request_count ==
+						TIMEOUT_REPEAT_REQUEST - 1)
+					msleep(30);
+				lane->krln.repeat_request_count++;
+				ld_coef_update(lane);
+				return;
+			}
+		} else {
+			/* All good here:
+			 * LP responded to this Request
+			 * Sent HOLD for this coefficient
+			 * before asking another request
+			 * continue to check the other coefficient requests
+			 */
+			lane->krln.ld_update &= ~COP1_MASK;
+		}
+		break;
+	default:
+		/* previous request was: RESERVED: do nothing */
+		break;
+	}
+
+	switch (prev_req_cz0) {
+	case HOLD:
+		/* previous request was: HOLD */
+		if (status_cz0 == COEF_NOTUPDATED) {
+			/* All good here:
+			 * continue to check the other coefficient requests
+			 * and if all are good then proceed to
+			 * generate coefficient tuning requests
+			 */
+		} else {
+			/* Continue to send the same request: (2.)
+			 * Continue to send HOLD until receive status NOTUPDATED
+			 */
+			if (lane->krln.repeat_request_count >=
+						TIMEOUT_REPEAT_REQUEST) {
+				phydev_err(lane->phydev,
+					   "REQ Timeout: Repeating C(0) HOLD request without LP response timeout !\n");
+				/* Hold Request Timeout:
+				 * continue to send HOLD until LP responds
+				 * with NOTUPDATED
+				 */
+				lane->krln.repeat_request_count = 0;
+			} else {
+				/* Allow LP some time to respond
+				 * and repeat request
+				 */
+				msleep(20);
+				/* Allow LP more time to respond,
+				 * as the last chance,
+				 * on the last time before issuing
+				 * timeout error: (3.)
+				 */
+				if (lane->krln.repeat_request_count ==
+						TIMEOUT_REPEAT_REQUEST - 1)
+					msleep(30);
+				lane->krln.repeat_request_count++;
+			}
+			ld_coef_update(lane);
+			return;
+		}
+		break;
+	case INCREMENT:
+	case DECREMENT:
+		/* previous request was: INC/DEC */
+		if (status_cz0 == COEF_NOTUPDATED) {
+			/* Continue to send the same request: (1.)
+			 * Continue to send previous REQ until receive
+			 * status UPDATED
+			 */
+			if (lane->krln.repeat_request_count >=
+						TIMEOUT_REPEAT_REQUEST) {
+				if (prev_req_cz0 == INCREMENT)
+					phydev_err(lane->phydev,
+						   "REQ Timeout: Repeating C(0) INC request without LP response timeout !\n");
+				else
+					phydev_err(lane->phydev,
+						   "REQ Timeout: Repeating C(0) DEC request without LP response timeout !\n");
+				/* Request Timeout:
+				 * just continue: proceed again to
+				 * generate coefficient tuning requests
+				 */
+			} else {
+				/* Allow LP some time to respond
+				 * and repeat request
+				 */
+				msleep(20);
+				/* Allow LP more time to respond, as the last
+				 * chance, on the last time before issuing
+				 * timeout error: (3.)
+				 */
+				if (lane->krln.repeat_request_count ==
+						TIMEOUT_REPEAT_REQUEST - 1)
+					msleep(30);
+				lane->krln.repeat_request_count++;
+				ld_coef_update(lane);
+				return;
+			}
+		} else {
+			/* All good here:
+			 * LP responded to this Request
+			 * Sent HOLD for this coefficient
+			 * before asking another request
+			 * continue to check the other coefficient requests
+			 */
+			lane->krln.ld_update &= ~COZ0_MASK;
+		}
+		break;
+	default:
+		/* previous request was: RESERVED: do nothing */
+		break;
+	}
+
+	switch (prev_req_cm1) {
+	case HOLD:
+		/* previous request was: HOLD */
+		if (status_cm1 == COEF_NOTUPDATED) {
+			/* All good here:
+			 * continue to check the other coefficient requests
+			 * and if all are good then proceed to
+			 * generate coefficient tuning requests
+			 */
+		} else {
+			/* Continue to send the same request: (2.)
+			 * Continue to send HOLD until receive status
+			 * NOTUPDATED
+			 */
+			if (lane->krln.repeat_request_count >=
+						TIMEOUT_REPEAT_REQUEST) {
+				phydev_err(lane->phydev,
+					   "REQ Timeout: Repeating C(-1) HOLD request without LP response timeout !\n");
+				/* Hold Request Timeout:
+				 * continue to send HOLD until
+				 * LP responds with NOTUPDATED
+				 */
+				lane->krln.repeat_request_count = 0;
+			} else {
+				/* Allow LP some time to respond
+				 * and repeat request
+				 */
+				msleep(20);
+				/* Allow LP more time to respond,
+				 * as the last chance,
+				 * on the last time
+				 * before issuing timeout error: (3.)
+				 */
+				if (lane->krln.repeat_request_count ==
+						TIMEOUT_REPEAT_REQUEST - 1)
+					msleep(30);
+				lane->krln.repeat_request_count++;
+			}
+			ld_coef_update(lane);
+			return;
+		}
+		break;
+	case INCREMENT:
+	case DECREMENT:
+		/* previous request was: INC/DEC */
+		if (status_cm1 == COEF_NOTUPDATED) {
+			/* Continue to send the same request: (1.)
+			 * Continue to send previous REQ until receive status
+			 * UPDATED
+			 */
+			if (lane->krln.repeat_request_count >=
+						TIMEOUT_REPEAT_REQUEST) {
+				if (prev_req_cm1 == INCREMENT)
+					phydev_err(lane->phydev,
+						   "REQ Timeout: Repeating C(-1) INC request without LP response timeout !\n");
+				else
+					phydev_err(lane->phydev,
+						   "REQ Timeout: Repeating C(-1) DEC request without LP response timeout !\n");
+				/* Request Timeout:
+				 * just continue: proceed again to
+				 * generate coefficient tuning requests
+				 */
+			} else {
+				/* Allow LP some time to respond and repeat
+				 * request
+				 */
+				msleep(20);
+				/* Allow LP more time to respond, as the last
+				 * chance, on the last time before issuing
+				 * timeout error: (3.)
+				 */
+				if (lane->krln.repeat_request_count ==
+						TIMEOUT_REPEAT_REQUEST - 1)
+					msleep(30);
+				lane->krln.repeat_request_count++;
+				ld_coef_update(lane);
+				return;
+			}
+		} else {
+			/* All good here:
+			 * LP responded to this Request
+			 * Sent HOLD for this coefficient
+			 * before asking another request
+			 * continue to check the other coefficient requests
+			 */
+			lane->krln.ld_update &= ~COM1_MASK;
+		}
+		break;
+	default:
+		/* previous request was: RESERVED: do nothing */
+		break;
+	}
+
+	/* Reset repeat request counter:
+	 * must be after all prev_req verifications above
+	 */
+	lane->krln.repeat_request_count = 0;
+
+	if (lane->krln.prev_ld_update != lane->krln.ld_update) {
+		ld_coef_update(lane);
+		/* Redo these status checks and updates until we have no more
+		 * changes, to speed up the overall process.
+		 */
+		return;
+	}
+
+	/* Do nothing if we have pending request. */
+	if (prev_req_cp1 || prev_req_cz0 || prev_req_cm1)
+		return;
+	else if (lane->krln.lp_status & ALL_COEF_MASK)
+		/* No pending request but LP status was not reverted to
+		 * not updated.
+		 */
+		return;
+
+	/* Initialize status for the current step */
+	lane->krln.lt_error = false;
+
+	/* if CDR_LOCK = 0: Statistics are invalid */
+	if (!backplane_is_cdr_lock(lane, true)) {
+		if (eq_alg->ops.process_bad_state)
+			eq_alg->ops.process_bad_state(lane->krln.eq_priv);
+		return;
+	}
+
+	/* collect bit edge statistics */
+	if (!eq_alg->ops.collect_statistics(lane->krln.eq_priv))
+		return;
+
+	/* if CDR_LOCK = 0: Statistics are invalid */
+	if (!backplane_is_cdr_lock(lane, true)) {
+		if (eq_alg->ops.process_bad_state)
+			eq_alg->ops.process_bad_state(lane->krln.eq_priv);
+		return;
+	}
+
+	/* Check Rx */
+	if (!eq_alg->ops.is_rx_ok(lane->krln.eq_priv)) {
+		if (eq_alg->ops.process_bad_state)
+			eq_alg->ops.process_bad_state(lane->krln.eq_priv);
+		return;
+	}
+	eq_alg->ops.generate_request(lane->krln.eq_priv);
+
+	/* All C are in Hold and both Bins are stopped:
+	 * So the Training is done
+	 */
+	if (eq_alg->ops.is_eq_done(lane->krln.eq_priv)) {
+		trst->done_training = true;
+		training_complete(lane);
+	}
+}
+
+/* Training for Local Tx
+ * Initialize LD (Local Device)
+ */
+void lt_init_ld(struct lane_device *lane)
+{
+	/* report initial ld status to lp */
+	lane->krln.ld_status = 0;
+	ld_coef_status(lane);
+}
+
+/* Training for Local Tx
+ * This is the main routine for Local Tx training
+ */
+void lt_train_local_tx(struct lane_device *lane)
+{
+	struct training_status *trst = &lane->krln.trst;
+	int request, old_ld_status;
+
+	/* Check stop condition for Local Tx training */
+	trst->lp_rx_ready = lt_is_lp_rx_ready(lane);
+	if (trst->lp_rx_ready) {
+		/* LP receiver is ready
+		 * As soon as the LP shows ready,
+		 * no need to do any more updates.
+		 */
+		lane->krln.ld_status &= ~ALL_COEF_MASK;
+		ld_coef_status(lane);
+
+		trst->local_tx_running = false;
+		return;
+	}
+
+	/* Start new Local Tx training step */
+	trst->local_tx_running = true;
+
+	/* get request from LP */
+	request = backplane_read_mmd(lane, lane->bpdev->bpkr.ltmmd.devad,
+				     lane->bpdev->bpkr.ltmmd.lp_cu) &
+					LD_ALL_MASK;
+
+	old_ld_status = lane->krln.ld_status;
+
+	/* IEEE802.3-2008, 72.6.10.2.5
+	 * Ensure we always go to NOT UDPATED for status reporting in
+	 * response to HOLD requests.
+	 * IEEE802.3-2008, 72.6.10.2.3.1/2
+	 * ... but only if PRESET/INITIALIZE are not active to ensure
+	 * we keep status until they are released.
+	 *
+	 * 72.6.10.2.5 Coefficient update process
+	 * Once the updated, maximum, or minimum state is reported it continues
+	 * to be reported until a hold request is received,
+	 * after which the status reverts to not_updated.
+	 */
+	if (!(request & (PRESET_MASK | INIT_MASK))) {
+		/* Reset status on HOLD request */
+		if (!(request & COP1_MASK))
+			lane->krln.ld_status &= ~COP1_MASK;
+
+		if (!(request & COZ0_MASK))
+			lane->krln.ld_status &= ~COZ0_MASK;
+
+		if (!(request & COM1_MASK))
+			lane->krln.ld_status &= ~COM1_MASK;
+
+		ld_coef_status(lane);
+	}
+
+	/* 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 (!(lane->krln.ld_status & ALL_COEF_MASK)) {
+			if (request & PRESET_MASK)
+				preset(lane);
+
+			if (request & INIT_MASK) {
+				if (!lane->krln.first_recv_init) {
+					lane->krln.first_recv_init = true;
+					/* Init requests must be counted
+					 * from initial handshake
+					 */
+					lane->krln.req_ld_update_init_count = 1;
+					lane->krln.init_handshake_time =
+						jiffies_to_msecs(jiffies);
+				}
+				initialize(lane);
+			}
+		} else {
+			/* Inform the partner about current ld status
+			 * which should be: ALL UPDATED for INIT  and
+			 * ALL MAX for PRESET
+			 */
+			ld_coef_status(lane);
+		}
+	}
+
+	/* check if LP Coefficient are not in HOLD */
+	if (request & ALL_COEF_MASK)
+		check_request(lane, request & ALL_COEF_MASK);
+
+	/* Make sure the partner is always informed about the current ld status
+	 * this will ensure avoidance of several training issues and errors:
+	 *   'link_training_failed'
+	 *   'Repeating request without LP response'
+	 */
+	ld_coef_status(lane);
+}
+
+/* Training for Remote Tx API */
+
+/* lt_lp_update
+ *
+ * Sends to LP the specified request for coefficients update
+ *
+ * lane: desired lane for which to send lp update
+ * update: desired update request to be sent to LP
+ *
+ * Returns: None
+ */
+void lt_lp_update(struct lane_device *lane, u32 update)
+{
+	lane->krln.ld_update = update;
+	ld_coef_update(lane);
+}
+EXPORT_SYMBOL(lt_lp_update);
+
+/* lt_encode_request
+ *
+ * Encodes a request in the update word
+ * and adds it to other bit requests already existent in the update word
+ *
+ * base_update: base update word used to add a new desired request
+ * req: desired request type to be encoded
+ * field: the field for which the request must be encoded
+ *
+ * Returns: the encoded update word
+ */
+u32 lt_encode_request(u32 base_update, enum req_type req,
+		      enum coef_field field)
+{
+	u32 new_cmd = base_update;
+	u32 cmd;
+
+	if (req >= REQ_INIT)
+		return RESERVED;
+
+	cmd = get_mask_for_req(req);
+
+	switch (field) {
+	case C_P1:
+		new_cmd |= (cmd << COP1_SHIFT);
+		break;
+	case C_Z0:
+		new_cmd |= (cmd << COZ0_SHIFT);
+		break;
+	case C_M1:
+		new_cmd |= (cmd << COM1_SHIFT);
+		break;
+	default:
+		return RESERVED;
+	}
+	return new_cmd;
+}
+EXPORT_SYMBOL(lt_encode_request);
+
+/* lt_encode_startup_request
+ *
+ * Encodes a startup request in the update word
+ *
+ * req: desired startup request type to be encoded
+ *
+ * Returns: the encoded update word
+ */
+u32 lt_encode_startup_request(enum req_type req)
+{
+	if (req == REQ_HOLD || req == REQ_INIT || req == REQ_PRESET)
+		return get_mask_for_req(req);
+
+	return RESERVED;
+}
+EXPORT_SYMBOL(lt_encode_startup_request);
+
+/* lt_decode_coef_update
+ *
+ * Decodes a request update for the specified field
+ *
+ * update: update word to be decoded
+ * field: desired field for which to decode the update
+ *
+ * Returns: the decoded request type
+ */
+enum req_type lt_decode_coef_update(u32 update, enum coef_field field)
+{
+	u32 cmd = HOLD;
+
+	switch (field) {
+	case C_P1:
+		cmd = (update & COP1_MASK) >> COP1_SHIFT;
+		break;
+	case C_Z0:
+		cmd = (update & COZ0_MASK) >> COZ0_SHIFT;
+		break;
+	case C_M1:
+		cmd = (update & COM1_MASK) >> COM1_SHIFT;
+		break;
+	default:
+		return REQ_INVALID;
+	}
+
+	return get_req_for_mask(cmd);
+}
+EXPORT_SYMBOL(lt_decode_coef_update);
+
+/* lt_is_update_of_type
+ *
+ * Checks if a request update is according to the specified type
+ * by checking the specific request bit in update word
+ *
+ * update: desired update word to be verified
+ * type: desired type to check against
+ *
+ * Returns: true if update is according to asked type or false otherwise
+ */
+bool lt_is_update_of_type(u32 update, enum req_type type)
+{
+	u32 mask = HOLD;
+
+	switch (type) {
+	case REQ_HOLD:
+		return (update == HOLD);
+	case REQ_INC:
+		mask |= (INCREMENT << COP1_SHIFT);
+		mask |= (INCREMENT << COZ0_SHIFT);
+		mask |= (INCREMENT << COM1_SHIFT);
+		return ((update & mask) != 0);
+	case REQ_DEC:
+		mask |= (DECREMENT << COP1_SHIFT);
+		mask |= (DECREMENT << COZ0_SHIFT);
+		mask |= (DECREMENT << COM1_SHIFT);
+		return ((update & mask) != 0);
+	case REQ_INIT:
+		return ((update & INIT_MASK) != 0);
+	case REQ_PRESET:
+		return ((update & PRESET_MASK) != 0);
+	default:
+		return false;
+	}
+	return false;
+}
+EXPORT_SYMBOL(lt_is_update_of_type);
+
+/* lt_is_lp_at_startup
+ *
+ * Checks if LP status is still at startup status: INIT or PRESET
+ *
+ * lane: desired lane to be verified
+ * req: request type to check startup status
+ *	it makes sense only for INIT or PRESET requests
+ *
+ * Returns: true if LP status is still at startup status or false otherwise
+ */
+bool lt_is_lp_at_startup(struct lane_device *lane, enum req_type type)
+{
+	u32 lp_lcs = get_lp_lcs(lane);
+	u32 lp_st = lane->krln.lp_status;
+	bool lp_startup;
+
+	/* LP status still at Init/Preset:
+	 * IF now LP status is Init/Preset
+	 * OR (now LP status is NOTUPDATED
+	 * AND the last nonzero LP status was Init/Preset)
+	 */
+	switch (type) {
+	case REQ_INIT:
+		if (is_all_status(lp_st, COEF_UPDATED))
+			lp_startup = true;
+		else
+			lp_startup = is_all_status(lp_st, COEF_NOTUPDATED) &&
+					is_all_status(lp_lcs, COEF_UPDATED);
+		break;
+	case REQ_PRESET:
+		/* LP status still at Preset
+		 * if now LP status is Preset
+		 * OR now LP status is NOTUPDATED
+		 *    AND the last nonzero LP status was Preset
+		 */
+		if (is_all_status(lp_st, COEF_MAX) ||
+		    is_all_status(lp_st, COEF_UPDATED))
+			lp_startup = true;
+		else
+			lp_startup = is_all_status(lp_st, COEF_NOTUPDATED) &&
+					(is_all_status(lp_lcs, COEF_MAX) ||
+					 is_all_status(lp_lcs, COEF_UPDATED));
+		break;
+	default:
+		return false;
+	}
+
+	return lp_startup;
+}
+EXPORT_SYMBOL(lt_is_lp_at_startup);
+
+/* lt_get_lp_coef_status
+ *
+ * Determines the last LP coefficient status
+ * according to IEEE802.3ap-2007:
+ * 72.6.10.2.5 Coefficient update process
+ *
+ * lane: desired lane to be verified
+ * field: coefficient field to be verified
+ *
+ * Returns: the last LP coefficient status
+ */
+enum coef_status lt_get_lp_coef_status(struct lane_device *lane,
+				       enum coef_field field)
+{
+	return lane->krln.last_lp_update_status[field];
+}
+EXPORT_SYMBOL(lt_get_lp_coef_status);
+
+/* lt_set_error
+ *
+ * Sets or resets the LT (Link Training) Error flag
+ * This is used to signal to the generic kr training step procedure
+ * that an LT error state has occurred
+ * and link training cannot be successfully finished
+ *
+ * lane: desired lane to set lt error
+ * err: boolean value that specifies if set or reset the error flag
+ *
+ * Returns: None
+ */
+void lt_set_error(struct lane_device *lane, bool err)
+{
+	lane->krln.lt_error = err;
+}
+EXPORT_SYMBOL(lt_set_error);
+
+/* lt_move_lp_back
+ * Request LP to move back to previous coefficients setup and HOLD
+ * The procedure for sending this request is based on reverting the
+ * latest change request (non-hold update) for all coefficients
+ * This procedure should be used to exit from bad states like not CDR_Lock
+ *
+ * lane: desired lane for which to send lp update
+ *
+ * Returns: None
+ */
+void lt_move_lp_back(struct lane_device *lane)
+{
+	u32 prev_req_cp1 = (lane->krln.ld_last_nonhold_update & COP1_MASK) >>
+				COP1_SHIFT;
+	u32 prev_req_cz0 = (lane->krln.ld_last_nonhold_update & COZ0_MASK) >>
+				COZ0_SHIFT;
+	u32 prev_req_cm1 = (lane->krln.ld_last_nonhold_update & COM1_MASK) >>
+				COM1_SHIFT;
+	u32 temp;
+
+	/* Move back to previous C(-1), C(0), C(+1) and HOLD */
+	temp = HOLD;
+	switch (prev_req_cp1) {
+	case INCREMENT:
+		temp |= DECREMENT << COP1_SHIFT;
+		break;
+	case DECREMENT:
+		temp |= INCREMENT << COP1_SHIFT;
+		break;
+	}
+	switch (prev_req_cz0) {
+	case INCREMENT:
+		temp |= DECREMENT << COZ0_SHIFT;
+		break;
+	case DECREMENT:
+		temp |= INCREMENT << COZ0_SHIFT;
+		break;
+	}
+	switch (prev_req_cm1) {
+	case INCREMENT:
+		temp |= DECREMENT << COM1_SHIFT;
+		break;
+	case DECREMENT:
+		temp |= INCREMENT << COM1_SHIFT;
+		break;
+	}
+
+	lane->krln.ld_update = temp;
+	ld_coef_update(lane);
+
+	/* start the procedure for sending request to move LP back
+	 * to previous setup until LP responds to it
+	 */
+	lane->krln.move_back_prev = true;
+	lane->krln.move_back_cnt = 0;
+	lane->krln.move_back_lp_status = 0;
+	if (prev_req_cp1 == HOLD)
+		lane->krln.move_back_lp_status |= (COEF_UPDATED << COP1_SHIFT);
+	if (prev_req_cz0 == HOLD)
+		lane->krln.move_back_lp_status |= (COEF_UPDATED << COZ0_SHIFT);
+	if (prev_req_cm1 == HOLD)
+		lane->krln.move_back_lp_status |= (COEF_UPDATED << COM1_SHIFT);
+}
+EXPORT_SYMBOL(lt_move_lp_back);
diff --git a/drivers/net/phy/backplane/link_training.h b/drivers/net/phy/backplane/link_training.h
new file mode 100644
index 0000000..fae5f35
--- /dev/null
+++ b/drivers/net/phy/backplane/link_training.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
+/* Link Training (IEEE802.3ap/ba)
+ *
+ * Copyright 2019-2020 NXP
+ */
+
+#ifndef __LINK_TRAINING_H
+#define __LINK_TRAINING_H
+
+#include "backplane.h"
+
+/* Link Training interface with backplane driver */
+
+void lt_start(struct lane_device *lane);
+void lt_stop(struct lane_device *lane);
+void lt_reset(struct lane_device *lane);
+void lt_init_ld(struct lane_device *lane);
+
+void lt_mmd_c45(struct backplane_kr *bpkr);
+void lt_mmd_setup(struct backplane_kr *bpkr, int devad, u32 base);
+
+bool lt_is_rx_trained(struct lane_device *lane);
+bool lt_is_training_failure(struct lane_device *lane);
+bool lt_is_frame_lock(struct lane_device *lane);
+
+bool lt_is_lp_rx_ready(struct lane_device *lane);
+bool lt_is_ld_rx_ready(struct lane_device *lane);
+
+void lt_train_remote_tx(struct lane_device *lane);
+void lt_train_local_tx(struct lane_device *lane);
+
+#endif /* __LINK_TRAINING_H */
