diff mbox series

[net-next,v3,5/7] net: phy: enable qoriq backplane support

Message ID 1592832924-31733-6-git-send-email-florinel.iordache@nxp.com
State New
Headers show
Series net: ethernet backplane support on DPAA1 | expand

Commit Message

Florinel Iordache June 22, 2020, 1:35 p.m. UTC
Enable backplane support for qoriq family of devices

Signed-off-by: Florinel Iordache <florinel.iordache@nxp.com>
---
 drivers/net/phy/backplane/Kconfig            |  11 +-
 drivers/net/phy/backplane/Makefile           |   2 +
 drivers/net/phy/backplane/qoriq_backplane.c  | 473 ++++++++++++++++++++++++++
 drivers/net/phy/backplane/qoriq_backplane.h  |  42 +++
 drivers/net/phy/backplane/qoriq_serdes_10g.c | 486 +++++++++++++++++++++++++++
 5 files changed, 1013 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/phy/backplane/qoriq_backplane.c
 create mode 100644 drivers/net/phy/backplane/qoriq_backplane.h
 create mode 100644 drivers/net/phy/backplane/qoriq_serdes_10g.c
diff mbox series

Patch

diff --git a/drivers/net/phy/backplane/Kconfig b/drivers/net/phy/backplane/Kconfig
index 9ec54b5..3e20a78 100644
--- a/drivers/net/phy/backplane/Kconfig
+++ b/drivers/net/phy/backplane/Kconfig
@@ -17,4 +17,13 @@  config ETH_BACKPLANE_FIXED
 	  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
+	  initially set by the user.
+
+config ETH_BACKPLANE_QORIQ
+	tristate "QorIQ Ethernet Backplane driver"
+	depends on ETH_BACKPLANE
+	help
+	  This module provides a driver for Ethernet Operation over
+	  Electrical Backplanes enabled for QorIQ family of devices.
+	  This driver is using the services provided by the generic
+	  backplane and link training modules.
\ No newline at end of file
diff --git a/drivers/net/phy/backplane/Makefile b/drivers/net/phy/backplane/Makefile
index ded6f2d..3ae3d8b 100644
--- a/drivers/net/phy/backplane/Makefile
+++ b/drivers/net/phy/backplane/Makefile
@@ -5,5 +5,7 @@ 
 
 obj-$(CONFIG_ETH_BACKPLANE) += eth_backplane.o
 obj-$(CONFIG_ETH_BACKPLANE_FIXED) += eq_fixed.o
+obj-$(CONFIG_ETH_BACKPLANE_QORIQ) += eth_backplane_qoriq.o
 
 eth_backplane-objs	:= backplane.o link_training.o
+eth_backplane_qoriq-objs	:= qoriq_backplane.o qoriq_serdes_10g.o
diff --git a/drivers/net/phy/backplane/qoriq_backplane.c b/drivers/net/phy/backplane/qoriq_backplane.c
new file mode 100644
index 0000000..eb22e02
--- /dev/null
+++ b/drivers/net/phy/backplane/qoriq_backplane.c
@@ -0,0 +1,473 @@ 
+// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
+/* QorIQ 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/io.h>
+#include <linux/netdevice.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+
+#include "qoriq_backplane.h"
+
+/* QorIQ Backplane Driver name */
+#define QORIQ_BACKPLANE_DRIVER_NAME		"backplane_qoriq"
+
+/* QorIQ Backplane Driver version */
+#define QORIQ_BACKPLANE_DRIVER_VERSION		"1.0.0"
+
+/* PCS Device Identifier */
+#define PCS_PHY_DEVICE_ID			0x0083e400
+#define PCS_PHY_DEVICE_ID_MASK			0xffffffff
+
+/* Max/Min coefficients range values */
+#define PRE_COEF_MAX				0x0
+#define PRE_COEF_MIN				0x8
+#define POST_COEF_MAX				0x0
+#define POST_COEF_MIN				0x10
+#define ZERO_COEF_MIN				0x1A
+#define ZERO_COEF_MAX				0x30
+
+/* Coefficients sum ratio: (their sum divided by their difference) */
+#define COEF_SUM_RATIO_NUMERATOR		17
+#define COEF_SUM_RATIO_DENOMINATOR		4
+
+/* Number of equalization custom parameters */
+#define EQ_PARAMS_NO				1
+
+/* Serdes types supported by QorIQ devices */
+enum serdes_type {
+	SERDES_10G,
+	SERDES_INVAL
+};
+
+static void an_advertisement_init(struct lane_device *lane)
+{
+	struct backplane_device *bpdev = lane->bpdev;
+	struct phy_device *phydev = lane->phydev;
+	struct qoriq_driver *qoriq_drv;
+	u32 init_an_adv1;
+	int err;
+
+	qoriq_drv = (struct qoriq_driver *)bpdev->drv.priv;
+	init_an_adv1 = backplane_get_an_adv1_init(phydev->interface);
+
+	err = backplane_write_mmd(lane, MDIO_MMD_AN, qoriq_drv->an_adv1,
+				  init_an_adv1);
+	if (err)
+		phydev_err(phydev,
+			   "Setting AN register 0x%02x on lane %d failed with error code: 0x%08x\n",
+			   qoriq_drv->an_adv1, lane->idx, err);
+}
+
+static bool is_an_link_detected(struct lane_device *lane)
+{
+	struct backplane_device *bpdev = lane->bpdev;
+	struct phy_device *phydev = lane->phydev;
+	struct qoriq_driver *qoriq_drv;
+	struct lane_device *masterln;
+	u32 an_bp_eth_status;
+	int an_state;
+	u32 an_mask;
+
+	qoriq_drv = (struct qoriq_driver *)bpdev->drv.priv;
+	an_bp_eth_status = qoriq_drv->an_bp_eth_status;
+
+	/* Check AN state only on Master Lane */
+	masterln = &bpdev->lane[MASTER_LANE];
+
+	/* The link training occurs after auto-negotiation
+	 * has determined the link to be a Base-KR link.
+	 * This is indicated by asserting the corresponding
+	 * technology bit within the BP_ETH_STATUS register.
+	 * Note that this occurs before auto-negotiation can declare
+	 * auto-negotiation complete,
+	 * as this requires the PCS to report a valid link.
+	 */
+	an_mask = backplane_get_an_bp_eth_status_bit(phydev->interface);
+	an_state = backplane_read_mmd(masterln, MDIO_MMD_AN, an_bp_eth_status);
+
+	return (an_state & an_mask);
+}
+
+static void qoriq_setup_default_settings(struct lane_device *lane)
+{
+	const struct lane_ops *lane_ops = lane->bpdev->drv.lane_ops;
+	struct qoriq_lane_ops *qoriq_lane_ops;
+	u32 def_amp_red;
+
+	qoriq_lane_ops = (struct qoriq_lane_ops *)lane_ops->priv;
+
+	if (lane->bpdev->bpkr.valid_eq_params)
+		def_amp_red = lane->bpdev->bpkr.def_kr.dev_coef[IDX_AMP_RED];
+	else
+		def_amp_red = qoriq_lane_ops->read_amp_red(lane->reg_base);
+
+	lane->krln.def_kr.dev_coef[IDX_AMP_RED] = def_amp_red;
+}
+
+/* LT HW restrictions:
+ * Section 5.3.1 10GBaseKR Transmit Adaptive Equalization Control
+ * additional restrictions set down by 802.3 specification Clause 72,
+ * specifically 72.7.1.11 Transmitter output waveform requirements
+ *
+ * Maintaining the following relationships limit transmit equalization
+ * to reasonable levels compliant with the KR specification
+ */
+static int lt_qoriq_validation(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];
+
+	/* Additional HW restrictions:
+	 * 1. MIN_C(0) <= tx_preq + tx_mainq + tx_ratio_post1q <= MAX_C(0)
+	 */
+	if ((preq + postq + mainq) < bpkr->min_kr.mainq)
+		return -ERANGE;
+	if ((preq + postq + mainq) > bpkr->max_kr.mainq)
+		return -ERANGE;
+
+	/* 2.
+	 * ( tx_mainq + tx_preq + tx_ratio_post1q ) /
+	 * ( tx_mainq - tx_preq - tx_ratio_post1q ) <
+	 * coef_sum_ratio_numerator / coef_sum_ratio_denominator
+	 */
+	if (((mainq + preq + postq) * COEF_SUM_RATIO_DENOMINATOR) >=
+	    ((mainq - preq - postq) * COEF_SUM_RATIO_NUMERATOR))
+		return -ERANGE;
+
+	return 0;
+}
+
+enum serdes_type get_serdes_type(struct device_node *serdes_node)
+{
+	enum serdes_type serdes = SERDES_INVAL;
+	const char *serdes_comp;
+	int comp_no, i, ret;
+
+	comp_no = of_property_count_strings(serdes_node, "compatible");
+	for (i = 0; i < comp_no; i++) {
+		ret = of_property_read_string_index(serdes_node, "compatible",
+						    i, &serdes_comp);
+		if (ret == 0) {
+			if (!strcasecmp(serdes_comp, "serdes-10g")) {
+				serdes = SERDES_10G;
+				break;
+			}
+		}
+	}
+
+	return serdes;
+}
+
+/* install QorIQ specific backplane callbacks:
+ * for AN start/decoding, hw specific defaults and lt validation
+ */
+static const struct backplane_ops qoriq_ops = {
+	.an_advertisement_init = an_advertisement_init,
+	.is_an_link_detected = is_an_link_detected,
+	.setup_default_settings = qoriq_setup_default_settings,
+	.lt_validation = lt_qoriq_validation,
+};
+
+/* qoriq_backplane_probe
+ *
+ * Probe function for QorIQ backplane driver to provide QorIQ device specific
+ * 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
+ */
+static int qoriq_backplane_probe(struct phy_device *phydev)
+{
+	pr_info_once("%s: QorIQ Backplane driver version %s\n",
+		     QORIQ_BACKPLANE_DRIVER_NAME,
+		     QORIQ_BACKPLANE_DRIVER_VERSION);
+
+	/* call generic driver probe */
+	return backplane_probe(phydev);
+}
+
+/* qoriq_backplane_config_init
+ *
+ * Config_Init function for QorIQ devices to provide QorIQ specific behavior
+ *
+ * phydev: backplane phy device
+ *
+ * Return: Zero for success or error code in case of failure
+ */
+static int qoriq_backplane_config_init(struct phy_device *phydev)
+{
+	struct device_node *dev_node, *serdes_node, *lane_node;
+	const struct equalizer_device *qoriq_equalizer = NULL;
+	struct backplane_device *bpdev = phydev->priv;
+	struct qoriq_lane_ops *qoriq_lane_ops = NULL;
+	const struct qoriq_driver *qoriq_drv = NULL;
+	const struct lane_ops *lane_ops = NULL;
+	enum serdes_type serdes = SERDES_INVAL;
+	u32 eqparams[EQ_PARAMS_NO];
+	struct resource res;
+	int proplen;
+	int i, ret;
+
+	dev_node = phydev->mdio.dev.of_node;
+	if (!dev_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_is_valid_mode(phydev->interface))
+		return -EINVAL;
+
+	/* call generic driver parse DT */
+	ret = backplane_parse_dt(phydev);
+	if (ret)
+		return ret;
+
+	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;
+	}
+
+	proplen = of_property_count_u32_elems(dev_node, "lane-handle");
+	if (proplen < bpdev->num_lanes) {
+		phydev_err(phydev, "Unspecified lane handles\n");
+		return -EINVAL;
+	}
+	serdes_node = NULL;
+	for (i = 0; i < bpdev->num_lanes; i++) {
+		lane_node = of_parse_phandle(dev_node, "lane-handle", i);
+		if (!lane_node) {
+			phydev_err(phydev, "parse lane-handle failed\n");
+			return -EINVAL;
+		}
+		if (i == 0)
+			serdes_node = lane_node->parent;
+		ret = of_address_to_resource(lane_node, 0, &res);
+		if (ret) {
+			phydev_err(phydev,
+				   "could not obtain lane memory map for index=%d, ret = %d\n",
+				   i, ret);
+			return ret;
+		}
+		/* setup lane address */
+		bpdev->lane[i].lane_addr = res.start;
+
+		of_node_put(lane_node);
+	}
+	if (!serdes_node) {
+		phydev_err(phydev, "serdes node not found\n");
+		return -EINVAL;
+	}
+	bpdev->drv.is_little_endian = of_property_read_bool(serdes_node,
+							    "little-endian");
+
+	ret = of_address_to_resource(serdes_node, 0, &res);
+	if (ret) {
+		phydev_err(phydev,
+			   "could not obtain serdes memory map, ret = %d\n",
+			   ret);
+		return ret;
+	}
+	bpdev->drv.base_addr = res.start;
+	bpdev->drv.memmap_size = res.end - res.start + 1;
+
+	serdes = get_serdes_type(serdes_node);
+	if (serdes == SERDES_INVAL) {
+		phydev_err(phydev, "Unknown serdes-type\n");
+		return 0;
+	}
+
+	/* if eq-params node exists then use the DTS specified values
+	 * if eq-params node doesn't exist then use values already found in HW
+	 * eq-params is a custom node and variable in size
+	 */
+	proplen = of_property_count_u32_elems(dev_node, "eq-params");
+	if (proplen > 0) {
+		/* we use only 1 custom coefficient tap: amp_red */
+		if (proplen > EQ_PARAMS_NO)
+			proplen = EQ_PARAMS_NO;
+		ret = of_property_read_u32_array(dev_node, "eq-params",
+						 (u32 *)eqparams, proplen);
+		if (ret == 0) {
+			bpdev->bpkr.valid_eq_params = true;
+			bpdev->bpkr.def_kr.dev_coef[IDX_AMP_RED] =
+							eqparams[IDX_AMP_RED];
+		}
+	}
+
+	/* call generic driver setup memio after reading serdes endianness */
+	ret = backplane_setup_memio(phydev);
+	if (ret)
+		return ret;
+
+	/* call generic driver setup mmd */
+	ret = backplane_setup_mmd(phydev);
+	if (ret)
+		return ret;
+
+	/* override default mdio setup and get qoriq specific info */
+	switch (serdes) {
+	case SERDES_10G:
+		lane_ops = qoriq_get_lane_ops_10g();
+		qoriq_drv = get_qoriq_driver_10g();
+		qoriq_setup_mem_io_10g(bpdev->drv.io);
+		qoriq_equalizer = qoriq_get_equalizer_10g();
+		break;
+	default:
+		phydev_err(phydev, "Serdes type not supported\n");
+		return -EINVAL;
+	}
+	if (!lane_ops) {
+		phydev_err(phydev, "Lane ops not available\n");
+		return -EINVAL;
+	}
+	if (!qoriq_drv) {
+		phydev_err(phydev, "Qoriq driver not available\n");
+		return -EINVAL;
+	}
+	if (!qoriq_equalizer) {
+		phydev_err(phydev, "Qoriq Equalizer not available\n");
+		return -EINVAL;
+	}
+
+	/* setup ops and equalizer */
+	bpdev->drv.lane_ops = lane_ops;
+	bpdev->drv.bp_ops = qoriq_ops;
+	bpdev->drv.priv = (void *)qoriq_drv;
+	bpdev->bpkr.equalizer = qoriq_equalizer;
+	qoriq_lane_ops = (struct qoriq_lane_ops *)lane_ops->priv;
+
+	if (!qoriq_lane_ops) {
+		phydev_err(phydev, "QorIQ lane ops not available\n");
+		return -EINVAL;
+	}
+
+	/* setup KR LT MMD registers space */
+	backplane_kr_lt_mmd_setup(&bpdev->bpkr, qoriq_drv->kr_lt_devad,
+				  qoriq_drv->kr_lt_base);
+
+	if (backplane_is_mode_kr(phydev->interface)) {
+		/* setup kr coefficients limits */
+		bpdev->bpkr.min_kr.preq = PRE_COEF_MIN;
+		bpdev->bpkr.max_kr.preq = PRE_COEF_MAX;
+		bpdev->bpkr.min_kr.mainq = ZERO_COEF_MIN;
+		bpdev->bpkr.max_kr.mainq = ZERO_COEF_MAX;
+		bpdev->bpkr.min_kr.postq = POST_COEF_MIN;
+		bpdev->bpkr.max_kr.postq = POST_COEF_MAX;
+	}
+
+	/* call generic driver setup lanes */
+	ret = backplane_setup_lanes(phydev);
+	if (ret)
+		return ret;
+
+	/* call generic driver initialize
+	 * start the lane timers used to run the algorithm
+	 */
+	ret = backplane_initialize(phydev);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int qoriq_backplane_match_phy_device(struct phy_device *phydev)
+{
+	struct device_node *dev_node, *serdes_node, *lane_node;
+	enum serdes_type serdes = SERDES_INVAL;
+	int i;
+
+	if (!phydev->mdio.dev.of_node)
+		return 0;
+
+	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;
+	}
+
+	/* Get Master lane node */
+	lane_node = of_parse_phandle(dev_node, "lane-handle", 0);
+	if (!lane_node)
+		return 0;
+	serdes_node = lane_node->parent;
+	of_node_put(lane_node);
+	if (!serdes_node)
+		return 0;
+
+	serdes = get_serdes_type(serdes_node);
+
+	switch (serdes) {
+	case SERDES_10G:
+		/* On LS devices we must find the c45 device with correct PHY ID
+		 * Implementation similar with the one existent in phy_device:
+		 * @function: phy_bus_match
+		 */
+		for (i = 1; i < ARRAY_SIZE(phydev->c45_ids.device_ids); i++) {
+			if (!(phydev->c45_ids.devices_in_package & (1 << i)))
+				continue;
+
+			if ((PCS_PHY_DEVICE_ID & PCS_PHY_DEVICE_ID_MASK) ==
+			    (phydev->c45_ids.device_ids[i] &
+			     PCS_PHY_DEVICE_ID_MASK))
+				return 1;
+		}
+		break;
+	default:
+		phydev_err(phydev, "Unknown serdes-type\n");
+		return 0;
+	}
+	return 0;
+}
+
+static struct phy_driver qoriq_backplane_driver[] = {
+	{
+	.phy_id		= PCS_PHY_DEVICE_ID,
+	.name		= QORIQ_BACKPLANE_DRIVER_NAME,
+	.phy_id_mask	= PCS_PHY_DEVICE_ID_MASK,
+	.features       = BACKPLANE_FEATURES,
+	.probe          = qoriq_backplane_probe,
+	.remove         = backplane_remove,
+	.config_init    = qoriq_backplane_config_init,
+	.aneg_done      = backplane_aneg_done,
+	.config_aneg	= backplane_config_aneg,
+	.read_status	= backplane_read_status,
+	.suspend	= backplane_suspend,
+	.resume		= backplane_resume,
+	.match_phy_device = qoriq_backplane_match_phy_device,
+	},
+};
+
+module_phy_driver(qoriq_backplane_driver);
+
+static struct mdio_device_id __maybe_unused qoriq_backplane_tbl[] = {
+	{ PCS_PHY_DEVICE_ID, PCS_PHY_DEVICE_ID_MASK },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, qoriq_backplane_tbl);
+
+MODULE_DESCRIPTION("QorIQ Backplane driver");
+MODULE_AUTHOR("Florinel Iordache <florinel.iordache@nxp.com>");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/phy/backplane/qoriq_backplane.h b/drivers/net/phy/backplane/qoriq_backplane.h
new file mode 100644
index 0000000..09a3600
--- /dev/null
+++ b/drivers/net/phy/backplane/qoriq_backplane.h
@@ -0,0 +1,42 @@ 
+/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
+/* QorIQ Backplane driver
+ *
+ * Copyright 2018-2020 NXP
+ */
+
+#ifndef __QORIQ_BACKPLANE_H
+#define __QORIQ_BACKPLANE_H
+
+#include "backplane.h"
+
+/* Bins thresholds */
+#define QORIQ_BIN_M1_THRESHOLD			3
+#define QORIQ_BIN_LONG_THRESHOLD		2
+
+/* Index of custom parameter: AMP_RED (amplitude reduction) */
+#define IDX_AMP_RED				0
+
+struct qoriq_lane_ops {
+	u32 (*read_tecr0)(void __iomem *reg);
+	u32 (*read_tecr1)(void __iomem *reg);
+	u32 (*read_amp_red)(void __iomem *reg);
+};
+
+struct qoriq_driver {
+	/* KR LT MMD registers */
+	int kr_lt_devad;
+	u32 kr_lt_base;
+	/* KR AN MMD registers */
+	u32 an_adv1;
+	u32 an_bp_eth_status;
+};
+
+const struct lane_ops *qoriq_get_lane_ops_10g(void);
+
+const struct equalizer_device *qoriq_get_equalizer_10g(void);
+
+const struct qoriq_driver *get_qoriq_driver_10g(void);
+
+void qoriq_setup_mem_io_10g(struct mem_io memio);
+
+#endif /* __QORIQ_BACKPLANE_H */
diff --git a/drivers/net/phy/backplane/qoriq_serdes_10g.c b/drivers/net/phy/backplane/qoriq_serdes_10g.c
new file mode 100644
index 0000000..e4e5991
--- /dev/null
+++ b/drivers/net/phy/backplane/qoriq_serdes_10g.c
@@ -0,0 +1,486 @@ 
+// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
+/* QorIQ Backplane driver for SerDes 10G
+ *
+ * Copyright 2018-2020 NXP
+ */
+
+#include <linux/io.h>
+#include <linux/delay.h>
+
+#include "qoriq_backplane.h"
+
+#define EQUALIZER_NAME				"qoriq_serdes_10g"
+#define EQUALIZER_VERSION			"1.0.0"
+
+#define BIN_1_SEL				0x00000000
+#define BIN_2_SEL				0x00010000
+#define BIN_3_SEL				0x00020000
+#define BIN_OFFSET_SEL				0x00030000
+#define BIN_BLW_SEL				0x00040000
+#define BIN_AVG_SEL				0x00050000
+#define BIN_M1_SEL				0x00060000
+#define BIN_LONG_SEL				0x00070000
+#define CDR_SEL_MASK				0x00070000
+
+#define RATIO_PREQ_SHIFT			22
+#define RATIO_PST1Q_SHIFT			16
+#define ADPT_EQ_SHIFT				8
+#define AMP_RED_SHIFT				0
+
+#define RATIO_PREQ_MASK				0x03c00000
+#define RATIO_PST1Q_MASK			0x001f0000
+#define ADPT_EQ_MASK				0x00003f00
+#define AMP_RED_MASK				0x0000003f
+
+#define TECR0_INIT				0x24200000
+
+#define GCR0_RESET_MASK				0x00600000
+#define GCR0_TRST_MASK				0x00200000
+#define GCR0_RRST_MASK				0x00400000
+
+#define GCR1_SNP_START_MASK			0x00000040
+#define GCR1_CTL_SNP_START_MASK			0x00002000
+
+#define RECR1_CTL_SNP_DONE_MASK			0x00000002
+#define RECR1_SNP_DONE_MASK			0x00000004
+#define TCSR1_SNP_DATA_MASK			0x00007fc0
+#define TCSR1_SNP_DATA_SHIFT			6
+#define TCSR1_EQ_SNPBIN_SIGN_MASK		0x100
+
+#define TCSR3_CDR_LCK_MASK			0x08000000
+
+#define RECR1_GAINK2_MASK			0x0f000000
+#define RECR1_GAINK2_SHIFT			24
+
+#define RECR1_GAINK3_MASK			0x000f0000
+#define RECR1_GAINK3_SHIFT			16
+
+#define RECR1_EQ_OFFSET_MASK			0x00001f80
+#define RECR1_EQ_OFFSET_SHIFT			7
+
+/* AN advertisement register 7.17 */
+#define AN_AD_ABILITY_1				0x11
+
+/* Backplane Ethernet status (Register 7.48) */
+#define AN_BP_ETH_STATUS_OFFSET			0x30
+
+/* KR PMD control register (Register 1.150) */
+#define KR_PMD_BASE_OFFSET			0x96
+
+/* Bin snapshots thresholds range */
+#define EQ_BIN_MIN				-256
+#define EQ_BIN_MAX				255
+/* Bin snapshots average thresholds range */
+#define EQ_BIN_SNP_AV_THR_LOW			-150
+#define EQ_BIN_SNP_AV_THR_HIGH			150
+
+#define EQ_GAINK_MIN				0xF
+#define EQ_GAINK_MAX				0x0
+#define EQ_GAINK_MIDRANGE_LOW			0xE
+#define EQ_GAINK_MIDRANGE_HIGH			0x1
+
+#define EQ_OFFSET_MIN				0
+#define EQ_OFFSET_MAX				0x3F
+#define EQ_OFFSET_MIDRANGE_LOW			0x10
+#define EQ_OFFSET_MIDRANGE_HIGH			0x2F
+
+#define MEMORY_MAP_SIZE				0x40
+
+struct qoriq_lane_regs {
+	u32 gcr0;	/* 0x00: General Control Register 0 */
+	u32 gcr1;	/* 0x04: General Control Register 1 */
+	u32 gcr2;	/* 0x08: General Control Register 2 */
+	u32 res_0c;	/* 0x0C: Reserved */
+	u32 recr0;	/* 0x10: Receive Equalization Control Register 0 */
+	u32 recr1;	/* 0x14: Receive Equalization Control Register 1 */
+	u32 tecr0;	/* 0x18: Transmit Equalization Control Register 0 */
+	u32 res_1c;	/* 0x1C: Reserved */
+	u32 tlcr0;	/* 0x20: TTL Control Register 0 */
+	u32 tlcr1;	/* 0x24: TTL Control Register 1 */
+	u32 tlcr2;	/* 0x28: TTL Control Register 2 */
+	u32 tlcr3;	/* 0x2C: TTL Control Register 3 */
+	u32 tcsr0;	/* 0x30: Test Control/Status Register 0 */
+	u32 tcsr1;	/* 0x34: Test Control/Status Register 1 */
+	u32 tcsr2;	/* 0x38: Test Control/Status Register 2 */
+	u32 tcsr3;	/* 0x3C: Test Control/Status Register 3 */
+};
+
+static struct mem_io io;
+
+static void reset_lane(void __iomem *reg, enum lane_req ln_req)
+{
+	struct qoriq_lane_regs __iomem *reg_base = reg;
+
+	/* reset Tx lane: send reset request */
+	if (ln_req | LANE_TX) {
+		io.write32(io.read32(&reg_base->gcr0) & ~GCR0_TRST_MASK,
+			   &reg_base->gcr0);
+	}
+	/* reset Rx lane: send reset request */
+	if (ln_req | LANE_RX) {
+		io.write32(io.read32(&reg_base->gcr0) & ~GCR0_RRST_MASK,
+			   &reg_base->gcr0);
+	}
+	/* unreset the lane */
+	if (ln_req != LANE_INVALID) {
+		udelay(1);
+		io.write32(io.read32(&reg_base->gcr0) | GCR0_RESET_MASK,
+			   &reg_base->gcr0);
+		udelay(1);
+	}
+}
+
+static u32 read_tecr0(void __iomem *reg)
+{
+	struct qoriq_lane_regs __iomem *reg_base = reg;
+
+	return io.read32(&reg_base->tecr0);
+}
+
+static u32 read_tecr1(void __iomem *reg)
+{
+	return 0;
+}
+
+static u32 read_amp_red(void __iomem *reg)
+{
+	struct qoriq_lane_regs __iomem *reg_base = reg;
+	u32 val, amp_red;
+
+	val = io.read32(&reg_base->tecr0);
+	amp_red = (val & AMP_RED_MASK) >> AMP_RED_SHIFT;
+
+	return amp_red;
+}
+
+static void read_kr_coef(void __iomem *reg, struct kr_coef *coef)
+{
+	struct qoriq_lane_regs __iomem *reg_base = reg;
+	u32 val;
+
+	val = io.read32(&reg_base->tecr0);
+
+	coef->preq = (val & RATIO_PREQ_MASK) >> RATIO_PREQ_SHIFT;
+	coef->postq = (val & RATIO_PST1Q_MASK) >> RATIO_PST1Q_SHIFT;
+	coef->mainq = (val & ADPT_EQ_MASK) >> ADPT_EQ_SHIFT;
+	coef->dev_coef[IDX_AMP_RED] = (val & AMP_RED_MASK) >> AMP_RED_SHIFT;
+}
+
+static void tune_tecr(void __iomem *reg, struct kr_coef *coef, bool reset)
+{
+	struct qoriq_lane_regs __iomem *reg_base = reg;
+	u32 val;
+
+	val = TECR0_INIT |
+		coef->mainq << ADPT_EQ_SHIFT |
+		coef->preq << RATIO_PREQ_SHIFT |
+		coef->postq << RATIO_PST1Q_SHIFT |
+		coef->dev_coef[IDX_AMP_RED] << AMP_RED_SHIFT;
+
+	if (reset) {
+		/* reset the lane */
+		io.write32(io.read32(&reg_base->gcr0) &
+			   ~GCR0_RESET_MASK, &reg_base->gcr0);
+		udelay(1);
+	}
+
+	io.write32(val, &reg_base->tecr0);
+	udelay(1);
+
+	if (reset) {
+		/* unreset the lane */
+		io.write32(io.read32(&reg_base->gcr0) | GCR0_RESET_MASK,
+			   &reg_base->gcr0);
+		udelay(1);
+	}
+}
+
+/* collect_gains
+ *
+ * reg: serdes registers memory map
+ * gaink2: High-frequency gain of the equalizer amplifier
+ *         the high-frequency gain of the equalizer amplifier is increased by
+ *         decrementing the value of eq_gaink2 by one
+ * gaink3: Middle-frequency gain of the equalizer amplifier
+ *         the mid-frequency gain of the equalizer amplifier is increased by
+ *         decrementing the value of eq_gaink3 by one
+ * osestat: equalization offset status
+ *          the equalizer offset is reduced by decrementing the value of osestat
+ * size: size of snapshots data collection
+ */
+static int collect_gains(void __iomem *reg, s16 *gaink2, s16 *gaink3,
+			 s16 *osestat, u8 size)
+{
+	struct qoriq_lane_regs __iomem *reg_base = reg;
+	u32 rx_eq_snp;
+	int timeout;
+	int i;
+
+	for (i = 0; i < size; i++) {
+		/* wait RECR1_CTL_SNP_DONE_MASK has cleared */
+		timeout = 100;
+		while (io.read32(&reg_base->recr1) &
+		       RECR1_CTL_SNP_DONE_MASK) {
+			udelay(1);
+			timeout--;
+			if (timeout == 0)
+				break;
+		}
+
+		/* start snapshot */
+		io.write32((io.read32(&reg_base->gcr1) |
+			    GCR1_CTL_SNP_START_MASK), &reg_base->gcr1);
+
+		/* wait for SNP done */
+		timeout = 100;
+		while (!(io.read32(&reg_base->recr1) &
+		       RECR1_CTL_SNP_DONE_MASK)) {
+			udelay(1);
+			timeout--;
+			if (timeout == 0)
+				break;
+		}
+
+		/* read and save the snapshot */
+		rx_eq_snp = io.read32(&reg_base->recr1);
+
+		if (gaink2)
+			gaink2[i] = (u8)((rx_eq_snp & RECR1_GAINK2_MASK) >>
+					 RECR1_GAINK2_SHIFT);
+		if (gaink3)
+			gaink3[i] = (u8)((rx_eq_snp & RECR1_GAINK3_MASK) >>
+					 RECR1_GAINK3_SHIFT);
+		if (osestat)
+			osestat[i] = (u8)((rx_eq_snp & RECR1_EQ_OFFSET_MASK) >>
+					  RECR1_EQ_OFFSET_SHIFT);
+
+		/* terminate the snapshot by setting GCR1[REQ_CTL_SNP] */
+		io.write32((io.read32(&reg_base->gcr1) &
+			   ~GCR1_CTL_SNP_START_MASK), &reg_base->gcr1);
+	}
+	return i;
+}
+
+static int collect_eq_status(void __iomem *reg, enum eqc_type type[],
+			     u8 type_no, s16 *counters, u8 size)
+{
+	s16 *gaink2 = NULL, *gaink3 = NULL, *osestat = NULL;
+	u8 i;
+
+	for (i = 0; i < type_no; i++) {
+		switch (type[i]) {
+		case EQC_GAIN_HF:
+			gaink2 = counters;
+			break;
+		case EQC_GAIN_MF:
+			gaink3 = counters + size;
+			break;
+		case EQC_EQOFFSET:
+			osestat = counters + 2 * size;
+			break;
+		default:
+			/* invalid type */
+			break;
+		}
+	}
+
+	return collect_gains(reg, gaink2, gaink3, osestat, size);
+}
+
+static int collect_bin_snapshots(void __iomem *reg, enum eqc_type type,
+				 s16 *bin_counters, u8 bin_size)
+{
+	struct qoriq_lane_regs __iomem *reg_base = reg;
+	int bin_snapshot;
+	int i, timeout;
+	u32 bin_sel;
+
+	/* calculate TCSR1[CDR_SEL] */
+	switch (type) {
+	case EQC_BIN_1:
+		bin_sel = BIN_1_SEL;
+		break;
+	case EQC_BIN_2:
+		bin_sel = BIN_2_SEL;
+		break;
+	case EQC_BIN_3:
+		bin_sel = BIN_3_SEL;
+		break;
+	case EQC_BIN_LONG:
+		bin_sel = BIN_LONG_SEL;
+		break;
+	case EQC_BIN_M1:
+		bin_sel = BIN_M1_SEL;
+		break;
+	case EQC_BIN_OFFSET:
+		bin_sel = BIN_OFFSET_SEL;
+		break;
+	case EQC_BIN_AVG:
+		bin_sel = BIN_AVG_SEL;
+		break;
+	case EQC_BIN_BLW:
+		bin_sel = BIN_BLW_SEL;
+		break;
+	default:
+		/* invalid bin type */
+		return 0;
+	}
+
+	for (i = 0; i < bin_size; i++) {
+		/* wait RECR1_SNP_DONE_MASK has cleared */
+		timeout = 100;
+		while ((io.read32(&reg_base->recr1) &
+			RECR1_SNP_DONE_MASK)) {
+			udelay(1);
+			timeout--;
+			if (timeout == 0)
+				break;
+		}
+
+		/* set TCSR1[CDR_SEL] */
+		io.write32((io.read32(&reg_base->tcsr1) &
+			    ~CDR_SEL_MASK) | bin_sel, &reg_base->tcsr1);
+
+		/* start snapshot */
+		io.write32(io.read32(&reg_base->gcr1) |
+			   GCR1_SNP_START_MASK, &reg_base->gcr1);
+
+		/* wait for SNP done */
+		timeout = 100;
+		while (!(io.read32(&reg_base->recr1) &
+			 RECR1_SNP_DONE_MASK)) {
+			udelay(1);
+			timeout--;
+			if (timeout == 0)
+				break;
+		}
+
+		/* read and save the snapshot:
+		 * 2's complement 9 bit long value (-256 to 255)
+		 */
+		bin_snapshot = (io.read32(&reg_base->tcsr1) &
+				TCSR1_SNP_DATA_MASK) >> TCSR1_SNP_DATA_SHIFT;
+		if (bin_snapshot & TCSR1_EQ_SNPBIN_SIGN_MASK) {
+			/* 2's complement 9 bit long negative number */
+			bin_snapshot &= ~TCSR1_EQ_SNPBIN_SIGN_MASK;
+			bin_snapshot -= 256;
+		}
+
+		/* save collected Bin snapshot */
+		bin_counters[i] = (s16)bin_snapshot;
+
+		/* terminate the snapshot by setting GCR1[REQ_CTL_SNP] */
+		io.write32(io.read32(&reg_base->gcr1) &
+			   ~GCR1_SNP_START_MASK, &reg_base->gcr1);
+	}
+	return i;
+}
+
+static struct eqc_range bin_range = {
+	.min = EQ_BIN_MIN,
+	.max = EQ_BIN_MAX,
+	.mid_low = EQ_BIN_SNP_AV_THR_LOW,
+	.mid_high = EQ_BIN_SNP_AV_THR_HIGH,
+};
+
+static struct eqc_range gaink_range = {
+	.min = EQ_GAINK_MIN,
+	.max = EQ_GAINK_MAX,
+	.mid_low = EQ_GAINK_MIDRANGE_LOW,
+	.mid_high = EQ_GAINK_MIDRANGE_HIGH,
+};
+
+static struct eqc_range osestat_range = {
+	.min = EQ_OFFSET_MIN,
+	.max = EQ_OFFSET_MAX,
+	.mid_low = EQ_OFFSET_MIDRANGE_LOW,
+	.mid_high = EQ_OFFSET_MIDRANGE_HIGH,
+};
+
+static struct eqc_range *get_counter_range(enum eqc_type type)
+{
+	switch (type) {
+	case EQC_BIN_1:
+	case EQC_BIN_2:
+	case EQC_BIN_3:
+	case EQC_BIN_LONG:
+	case EQC_BIN_M1:
+	case EQC_BIN_OFFSET:
+	case EQC_BIN_AVG:
+	case EQC_BIN_BLW:
+		return &bin_range;
+	case EQC_GAIN_HF:
+	case EQC_GAIN_MF:
+		return &gaink_range;
+	case EQC_EQOFFSET:
+		return &osestat_range;
+	default:
+		/* invalid counter type */
+		return NULL;
+	}
+	return NULL;
+}
+
+static bool is_cdr_lock_bit(void __iomem *reg)
+{
+	struct qoriq_lane_regs __iomem *reg_base = reg;
+
+	if (io.read32(&reg_base->tcsr3) & TCSR3_CDR_LCK_MASK)
+		return true;
+
+	return false;
+}
+
+static const struct qoriq_lane_ops qoriq_lane_ops_10g = {
+	.read_tecr0 = read_tecr0,
+	.read_tecr1 = read_tecr1,
+	.read_amp_red = read_amp_red,
+};
+
+static const struct lane_ops lane_ops_10g = {
+	.priv = &qoriq_lane_ops_10g,
+	.memmap_size = MEMORY_MAP_SIZE,
+	.reset_lane = reset_lane,
+	.tune_lane_kr = tune_tecr,
+	.read_lane_kr = read_kr_coef,
+	.is_cdr_lock = is_cdr_lock_bit,
+};
+
+const struct lane_ops *qoriq_get_lane_ops_10g(void)
+{
+	return &lane_ops_10g;
+}
+
+static const struct equalizer_device qoriq_equalizer = {
+	.name = EQUALIZER_NAME,
+	.version = EQUALIZER_VERSION,
+	.ops = {
+		.collect_counters = collect_bin_snapshots,
+		.collect_multiple_counters = collect_eq_status,
+		.get_counter_range = get_counter_range,
+	},
+};
+
+const struct equalizer_device *qoriq_get_equalizer_10g(void)
+{
+	return &qoriq_equalizer;
+}
+
+static const struct qoriq_driver qoriq_drv = {
+	/* KR PMD registers */
+	.kr_lt_devad = MDIO_MMD_PMAPMD,
+	.kr_lt_base = KR_PMD_BASE_OFFSET,
+	/* KR AN registers: IEEE802.3 Clause 45 MMD 7 */
+	.an_adv1 = AN_AD_ABILITY_1,
+	.an_bp_eth_status = AN_BP_ETH_STATUS_OFFSET,
+};
+
+const struct qoriq_driver *get_qoriq_driver_10g(void)
+{
+	return &qoriq_drv;
+}
+
+void qoriq_setup_mem_io_10g(struct mem_io memio)
+{
+	io = memio;
+}