[PATCH net-next 7/9] net: phy: enable qoriq backplane support
From: Florinel Iordache
Date: Thu Mar 26 2020 - 09:52:43 EST
Enable backplane support for qoriq family of devices
Signed-off-by: Florinel Iordache <florinel.iordache@xxxxxxx>
---
drivers/net/phy/backplane/Kconfig | 11 +-
drivers/net/phy/backplane/Makefile | 2 +
drivers/net/phy/backplane/qoriq_backplane.c | 442 ++++++++++++++++++++++
drivers/net/phy/backplane/qoriq_backplane.h | 33 ++
drivers/net/phy/backplane/qoriq_serdes_10g.c | 470 +++++++++++++++++++++++
drivers/net/phy/backplane/qoriq_serdes_28g.c | 533 +++++++++++++++++++++++++++
6 files changed, 1490 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
create mode 100644 drivers/net/phy/backplane/qoriq_serdes_28g.c
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..d8f95ac 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 qoriq_serdes_28g.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..9a2cd4e
--- /dev/null
+++ b/drivers/net/phy/backplane/qoriq_backplane.c
@@ -0,0 +1,442 @@
+// 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
+
+/* AN registers initialization */
+#define KR_AN_AD1_INIT_10G 0x85
+#define KR_AN_AD1_INIT_40G 0x105
+
+/* AN masks: Backplane Ethernet status (Register 7.48) */
+#define AN_MASK_10GBASE_KR 0x08
+#define AN_MASK_40GBASE_KR4 0x20
+
+/* 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_28G,
+ SERDES_INVAL
+};
+
+/* Backplane Ethernet Status Register (an_bp_eth_status)
+ * chapter: 45.2.7.12 Backplane Ethernet status (Register 7.48)
+ * - bit AN_MASK_10GBASE_KR for 10GBase-KR
+ * - bit AN_MASK_40GBASE_KR4 for 40GBase-KR4
+ */
+static u32 get_an_bp_eth_status_bit(phy_interface_t mode)
+{
+ u32 an_mask = 0;
+
+ switch (mode) {
+ case PHY_INTERFACE_MODE_10GKR:
+ an_mask = AN_MASK_10GBASE_KR;
+ break;
+ case PHY_INTERFACE_MODE_40GKR4:
+ an_mask = AN_MASK_40GBASE_KR4;
+ break;
+ /* add AN support for other backplane modes here */
+ default:
+ an_mask = 0;
+ break;
+ }
+ return an_mask;
+}
+
+static u32 get_an_ad_ability_1_init(phy_interface_t mode)
+{
+ u32 init_value = 0;
+
+ switch (mode) {
+ case PHY_INTERFACE_MODE_10GKR:
+ init_value = KR_AN_AD1_INIT_10G;
+ break;
+ case PHY_INTERFACE_MODE_40GKR4:
+ init_value = KR_AN_AD1_INIT_40G;
+ break;
+ /* add AN support for other backplane modes here */
+ default:
+ init_value = 0;
+ break;
+ }
+ return init_value;
+}
+
+/* qoriq_backplane_probe
+ *
+ * Probe function for QorIQ backplane driver to provide QorIQ device specific
+ * behavior
+ *
+ * bpphy: 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 *bpphy)
+{
+ static bool one_time_action = true;
+
+ if (one_time_action) {
+ one_time_action = false;
+ pr_info("%s: QorIQ Backplane driver version %s\n",
+ QORIQ_BACKPLANE_DRIVER_NAME,
+ QORIQ_BACKPLANE_DRIVER_VERSION);
+ }
+
+ /* call generic driver probe */
+ return backplane_probe(bpphy);
+}
+
+/* qoriq_backplane_config_init
+ *
+ * Config_Init function for QorIQ devices to provide QorIQ specific behavior
+ *
+ * bpphy: backplane phy device
+ *
+ * Return: Zero for success or error code in case of failure
+ */
+static int qoriq_backplane_config_init(struct phy_device *bpphy)
+{
+ struct backplane_phy_info *bp_phy = bpphy->priv;
+ struct device_node *bpphy_node, *serdes_node, *lane_node;
+ struct resource res;
+ const char *serdes_comp;
+ const struct lane_io_ops *lane_ops = NULL;
+ struct qoriq_lane_ops *qoriq_lane = NULL;
+ const struct equalizer_info *equalizer = NULL;
+ int comp_no, i, ret;
+ int serdes_type = SERDES_INVAL;
+ u32 eqparams[EQ_PARAMS_NO];
+ int proplen;
+
+ bpphy_node = bpphy->mdio.dev.of_node;
+ if (!bpphy_node) {
+ bpdev_err(bpphy, "No associated device tree node\n");
+ return -EINVAL;
+ }
+
+ if (!bp_phy) {
+ bpdev_err(bpphy, "Backplane phy info is not allocated\n");
+ return -EINVAL;
+ }
+
+ if (!backplane_is_valid_mode(bpphy->interface))
+ return -EINVAL;
+
+ bp_phy->bp_mode = bpphy->interface;
+ bp_phy->num_lanes = backplane_num_lanes(bpphy->interface);
+
+ proplen = of_property_count_u32_elems(bpphy_node, "lane-handle");
+ if (proplen < bp_phy->num_lanes) {
+ bpdev_err(bpphy, "Unspecified lane handles\n");
+ return -EINVAL;
+ }
+ serdes_node = NULL;
+ for (i = 0; i < bp_phy->num_lanes; i++) {
+ lane_node = of_parse_phandle(bpphy_node, "lane-handle", i);
+ if (!lane_node) {
+ bpdev_err(bpphy, "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) {
+ bpdev_err(bpphy,
+ "could not obtain lane memory map for index=%d, ret = %d\n",
+ i, ret);
+ return ret;
+ }
+ /* setup lane address */
+ bp_phy->krln[i].lane_addr = res.start;
+
+ of_node_put(lane_node);
+ }
+ if (!serdes_node) {
+ bpdev_err(bpphy, "serdes node not found\n");
+ return -EINVAL;
+ }
+ bp_phy->bp_dev.is_little_endian = of_property_read_bool(serdes_node,
+ "little-endian");
+
+ ret = of_address_to_resource(serdes_node, 0, &res);
+ if (ret) {
+ bpdev_err(bpphy,
+ "could not obtain serdes memory map, ret = %d\n",
+ ret);
+ return ret;
+ }
+ bp_phy->bp_dev.base_addr = res.start;
+ bp_phy->bp_dev.memmap_size = res.end - res.start + 1;
+
+ 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_type = SERDES_10G;
+ break;
+ } else if (!strcasecmp(serdes_comp, "serdes-28g")) {
+ serdes_type = SERDES_28G;
+ break;
+ }
+ }
+ }
+
+ if (serdes_type == SERDES_INVAL) {
+ bpdev_err(bpphy, "Unknown serdes-type\n");
+ return 0;
+ }
+
+ /* call generic driver parse DT */
+ ret = backplane_parse_dt(bpphy);
+ if (ret)
+ return ret;
+
+ /* call generic driver setup mdio */
+ ret = backplane_setup_mdio(bpphy);
+ if (ret)
+ return ret;
+
+ /* override default mdio setup and initialize lane ops */
+ switch (serdes_type) {
+ case SERDES_10G:
+ qoriq_setup_mdio_10g(&bp_phy->bp_dev);
+ lane_ops = qoriq_get_lane_ops_10g();
+ qoriq_setup_mem_io_10g(bp_phy->bp_dev.io);
+ equalizer = qoriq_get_equalizer_info_10g();
+ break;
+ case SERDES_28G:
+ qoriq_setup_mdio_28g(&bp_phy->bp_dev);
+ lane_ops = qoriq_get_lane_ops_28g();
+ qoriq_setup_mem_io_28g(bp_phy->bp_dev.io);
+ equalizer = qoriq_get_equalizer_info_28g();
+ break;
+ default:
+ bpdev_err(bpphy, "Serdes type not supported\n");
+ return -EINVAL;
+ }
+ if (!lane_ops) {
+ bpdev_err(bpphy, "Lane ops not available\n");
+ return -EINVAL;
+ }
+ if (!equalizer) {
+ bpdev_err(bpphy, "Equalizer not available\n");
+ return -EINVAL;
+ }
+
+ bp_phy->bp_dev.lane_ops = lane_ops;
+ bp_phy->bp_dev.equalizer = equalizer;
+ qoriq_lane = (struct qoriq_lane_ops *)lane_ops->priv;
+
+ /* install AN bp_eth_status decoding callback */
+ bp_phy->bp_dev.mdio.get_an_bp_eth_status_bit = get_an_bp_eth_status_bit;
+ bp_phy->bp_dev.mdio.get_an_ad_ability_1_init = get_an_ad_ability_1_init;
+
+ if (backplane_is_mode_kr(bp_phy->bp_mode)) {
+ /* setup coefficient limits */
+ bp_phy->bp_dev.cm_min = PRE_COEF_MIN;
+ bp_phy->bp_dev.cm_max = PRE_COEF_MAX;
+ bp_phy->bp_dev.cz_min = ZERO_COEF_MIN;
+ bp_phy->bp_dev.cz_max = ZERO_COEF_MAX;
+ bp_phy->bp_dev.cp_min = POST_COEF_MIN;
+ bp_phy->bp_dev.cp_max = POST_COEF_MAX;
+ bp_phy->bp_dev.sum_ratio_numer = COEF_SUM_RATIO_NUMERATOR;
+ bp_phy->bp_dev.sum_ratio_denom = COEF_SUM_RATIO_DENOMINATOR;
+ }
+
+ /* 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(bpphy_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(bpphy_node, "eq-params",
+ (u32 *)eqparams, proplen);
+ if (ret == 0) {
+ bp_phy->bp_dev.ampr_def_dt = true;
+ bp_phy->bp_dev.amp_red_def = eqparams[0];
+ }
+ }
+
+ /* call generic driver setup lanes */
+ ret = backplane_setup_lanes(bpphy);
+ if (ret)
+ return ret;
+
+ /* call generic driver initialize
+ * start the lane timers used to run the algorithm
+ */
+ ret = backplane_initialize(bpphy);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int qoriq_backplane_match_phy_device(struct phy_device *bpphy)
+{
+ struct device_node *bpphy_node, *serdes_node, *lane_node;
+ const char *serdes_comp;
+ int comp_no, i, ret;
+ int serdes_type = SERDES_INVAL;
+
+ if (!bpphy->mdio.dev.of_node)
+ return 0;
+
+ if (!bpphy->is_c45)
+ return 0;
+
+ bpphy_node = bpphy->mdio.dev.of_node;
+ if (!bpphy_node) {
+ bpdev_err(bpphy, "No associated device tree node\n");
+ return 0;
+ }
+
+ /* Get Master lane node */
+ lane_node = of_parse_phandle(bpphy_node, "lane-handle", 0);
+ if (!lane_node)
+ return 0;
+ serdes_node = lane_node->parent;
+ of_node_put(lane_node);
+ if (!serdes_node)
+ return 0;
+
+ 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_type = SERDES_10G;
+ break;
+ } else if (!strcasecmp(serdes_comp, "serdes-28g")) {
+ serdes_type = SERDES_28G;
+ break;
+ }
+ }
+ }
+
+ if (serdes_type == SERDES_INVAL) {
+ bpdev_err(bpphy, "Unknown serdes-type\n");
+ return 0;
+ }
+
+ switch (serdes_type) {
+ 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(bpphy->c45_ids.device_ids); i++) {
+ if (!(bpphy->c45_ids.devices_in_package & (1 << i)))
+ continue;
+
+ if ((PCS_PHY_DEVICE_ID & PCS_PHY_DEVICE_ID_MASK) ==
+ (bpphy->c45_ids.device_ids[i] &
+ PCS_PHY_DEVICE_ID_MASK))
+ return 1;
+ }
+ break;
+ case SERDES_28G:
+ /* WORKAROUND:
+ * Required for LX2 devices
+ * where PHY ID cannot be verified in PCS
+ * because PCS Device Identifier Upper and Lower registers are
+ * hidden and always return 0 when they are read:
+ * 2 02 Device_ID0 RO Bits 15:0 0
+ * val = phy_read_mmd(bpphy, MDIO_MMD_PCS, 0x2);
+ * 3 03 Device_ID1 RO Bits 31:16 0
+ * val = phy_read_mmd(bpphy, MDIO_MMD_PCS, 0x3);
+ *
+ * To be removed: After the issue will be fixed on LX2 devices
+ */
+
+ /* On LX devices we cannot verify PHY ID
+ * phy id because registers are hidden
+ * so we are happy only with preliminary verifications
+ * already made: mdio.dev.of_node, is_c45
+ * and lane-handle with valid serdes parent
+ * because we already filtered other undesired devices:
+ * non clause 45
+ */
+ return 1;
+ default:
+ bpdev_err(bpphy, "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@xxxxxxx>");
+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..bbfdfb5
--- /dev/null
+++ b/drivers/net/phy/backplane/qoriq_backplane.h
@@ -0,0 +1,33 @@
+/* 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
+
+struct qoriq_lane_ops {
+ u32 (*read_tecr0)(void *reg);
+ u32 (*read_tecr1)(void *reg);
+};
+
+const struct lane_io_ops *qoriq_get_lane_ops_10g(void);
+const struct lane_io_ops *qoriq_get_lane_ops_28g(void);
+
+const struct equalizer_info *qoriq_get_equalizer_info_10g(void);
+const struct equalizer_info *qoriq_get_equalizer_info_28g(void);
+
+void qoriq_setup_mem_io_10g(struct mem_io_ops io);
+void qoriq_setup_mem_io_28g(struct mem_io_ops io);
+
+void qoriq_setup_mdio_10g(struct backplane_dev_info *bp_dev);
+void qoriq_setup_mdio_28g(struct backplane_dev_info *bp_dev);
+
+#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..8ee7308
--- /dev/null
+++ b/drivers/net/phy/backplane/qoriq_serdes_10g.c
@@ -0,0 +1,470 @@
+// 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
+
+#define AN_AD_ABILITY_0 0x10
+#define AN_AD_ABILITY_1 0x11
+#define AN_BP_ETH_STATUS_OFFSET 0x30
+#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_ops io_ops;
+
+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_ops.write32(io_ops.read32(®_base->gcr0) & ~GCR0_TRST_MASK,
+ ®_base->gcr0);
+ }
+ /* reset Rx lane: send reset request */
+ if (ln_req | LANE_RX) {
+ io_ops.write32(io_ops.read32(®_base->gcr0) & ~GCR0_RRST_MASK,
+ ®_base->gcr0);
+ }
+ /* unreset the lane */
+ if (ln_req != LANE_INVALID) {
+ udelay(1);
+ io_ops.write32(io_ops.read32(®_base->gcr0) | GCR0_RESET_MASK,
+ ®_base->gcr0);
+ udelay(1);
+ }
+}
+
+static u32 read_tecr0(void __iomem *reg)
+{
+ struct qoriq_lane_regs __iomem *reg_base = reg;
+
+ return io_ops.read32(®_base->tecr0);
+}
+
+static u32 read_tecr1(void __iomem *reg)
+{
+ return 0;
+}
+
+static void read_tecr_params(void __iomem *reg, struct lane_kr_params *params)
+{
+ struct qoriq_lane_regs __iomem *reg_base = reg;
+ u32 val;
+
+ val = io_ops.read32(®_base->tecr0);
+
+ params->ratio_preq = (val & RATIO_PREQ_MASK) >> RATIO_PREQ_SHIFT;
+ params->ratio_pstq = (val & RATIO_PST1Q_MASK) >> RATIO_PST1Q_SHIFT;
+ params->adpt_eq = (val & ADPT_EQ_MASK) >> ADPT_EQ_SHIFT;
+ params->amp_red = (val & AMP_RED_MASK) >> AMP_RED_SHIFT;
+}
+
+static void setup_tecr(void __iomem *reg, struct lane_kr_params *params,
+ bool reset)
+{
+ struct qoriq_lane_regs __iomem *reg_base = reg;
+ u32 val;
+
+ val = TECR0_INIT |
+ params->adpt_eq << ADPT_EQ_SHIFT |
+ params->ratio_preq << RATIO_PREQ_SHIFT |
+ params->ratio_pstq << RATIO_PST1Q_SHIFT |
+ params->amp_red << AMP_RED_SHIFT;
+
+ if (reset) {
+ /* reset the lane */
+ io_ops.write32(io_ops.read32(®_base->gcr0) &
+ ~GCR0_RESET_MASK, ®_base->gcr0);
+ udelay(1);
+ }
+
+ io_ops.write32(val, ®_base->tecr0);
+ udelay(1);
+
+ if (reset) {
+ /* unreset the lane */
+ io_ops.write32(io_ops.read32(®_base->gcr0) | GCR0_RESET_MASK,
+ ®_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)
+{
+ u32 rx_eq_snp;
+ struct qoriq_lane_regs __iomem *reg_base = reg;
+ int timeout;
+ int i;
+
+ for (i = 0; i < size; i++) {
+ /* wait RECR1_CTL_SNP_DONE_MASK has cleared */
+ timeout = 100;
+ while (io_ops.read32(®_base->recr1) &
+ RECR1_CTL_SNP_DONE_MASK) {
+ udelay(1);
+ timeout--;
+ if (timeout == 0)
+ break;
+ }
+
+ /* start snapshot */
+ io_ops.write32((io_ops.read32(®_base->gcr1) |
+ GCR1_CTL_SNP_START_MASK), ®_base->gcr1);
+
+ /* wait for SNP done */
+ timeout = 100;
+ while (!(io_ops.read32(®_base->recr1) &
+ RECR1_CTL_SNP_DONE_MASK)) {
+ udelay(1);
+ timeout--;
+ if (timeout == 0)
+ break;
+ }
+
+ /* read and save the snapshot */
+ rx_eq_snp = io_ops.read32(®_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_ops.write32((io_ops.read32(®_base->gcr1) &
+ ~GCR1_CTL_SNP_START_MASK), ®_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)
+{
+ int bin_snapshot;
+ u32 bin_sel;
+ int i, timeout;
+ struct qoriq_lane_regs __iomem *reg_base = reg;
+
+ /* 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_ops.read32(®_base->recr1) &
+ RECR1_SNP_DONE_MASK)) {
+ udelay(1);
+ timeout--;
+ if (timeout == 0)
+ break;
+ }
+
+ /* set TCSR1[CDR_SEL] */
+ io_ops.write32((io_ops.read32(®_base->tcsr1) &
+ ~CDR_SEL_MASK) | bin_sel, ®_base->tcsr1);
+
+ /* start snapshot */
+ io_ops.write32(io_ops.read32(®_base->gcr1) |
+ GCR1_SNP_START_MASK, ®_base->gcr1);
+
+ /* wait for SNP done */
+ timeout = 100;
+ while (!(io_ops.read32(®_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_ops.read32(®_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_ops.write32(io_ops.read32(®_base->gcr1) &
+ ~GCR1_SNP_START_MASK, ®_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_ops.read32(®_base->tcsr3) & TCSR3_CDR_LCK_MASK)
+ return true;
+
+ return false;
+}
+
+static const struct qoriq_lane_ops qoriq_lane = {
+ .read_tecr0 = read_tecr0,
+ .read_tecr1 = read_tecr1,
+};
+
+static const struct lane_io_ops lane_ops = {
+ .priv = &qoriq_lane,
+ .memmap_size = MEMORY_MAP_SIZE,
+ .reset_lane = reset_lane,
+ .tune_lane_kr = setup_tecr,
+ .read_lane_kr = read_tecr_params,
+ .is_cdr_lock = is_cdr_lock_bit,
+};
+
+const struct lane_io_ops *qoriq_get_lane_ops_10g(void)
+{
+ return &lane_ops;
+}
+
+static const struct equalizer_info 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_info *qoriq_get_equalizer_info_10g(void)
+{
+ return &equalizer;
+}
+
+void qoriq_setup_mem_io_10g(struct mem_io_ops io)
+{
+ io_ops = io;
+}
+
+void qoriq_setup_mdio_10g(struct backplane_dev_info *bp_dev)
+{
+ /* IEEE802.3 Clause 45 register spaces */
+
+ /* KR PMD registers */
+ backplane_setup_kr_lt_mmd(bp_dev, MDIO_MMD_PMAPMD, KR_PMD_BASE_OFFSET);
+
+ /* KX/KR AN registers: IEEE802.3 Clause 45 MMD 7 */
+ bp_dev->mdio.an_ad_ability_0 = AN_AD_ABILITY_0;
+ bp_dev->mdio.an_ad_ability_1 = AN_AD_ABILITY_1;
+ bp_dev->mdio.an_bp_eth_status = AN_BP_ETH_STATUS_OFFSET;
+}
diff --git a/drivers/net/phy/backplane/qoriq_serdes_28g.c b/drivers/net/phy/backplane/qoriq_serdes_28g.c
new file mode 100644
index 0000000..5d847a4
--- /dev/null
+++ b/drivers/net/phy/backplane/qoriq_serdes_28g.c
@@ -0,0 +1,533 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
+/* QorIQ Backplane driver for SerDes 28G
+ *
+ * Copyright 2018-2020 NXP
+ */
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+
+#include "qoriq_backplane.h"
+
+#define EQUALIZER_NAME "qoriq_serdes_28g"
+#define EQUALIZER_VERSION "1.0.0"
+
+#define BIN_1_SEL 0x00000000
+#define BIN_2_SEL 0x00001000
+#define BIN_3_SEL 0x00002000
+#define BIN_4_SEL 0x00003000
+#define BIN_OFFSET_SEL 0x00004000
+#define BIN_BLW_SEL 0x00008000
+#define BIN_AVG_SEL 0x00009000
+#define BIN_M1_SEL 0x0000c000
+#define BIN_LONG_SEL 0x0000d000
+#define CDR_SEL_MASK 0x0000f000
+
+#define RATIO_PREQ_SHIFT 16
+#define RATIO_PST1Q_SHIFT 8
+#define AMP_RED_SHIFT 0
+#define ADPT_EQ_SHIFT 24
+
+#define RATIO_PREQ_MASK 0x000f0000
+#define RATIO_PST1Q_MASK 0x00001f00
+#define ADPT_EQ_MASK 0x3f000000
+#define AMP_RED_MASK 0x0000003f
+
+#define TECR0_INIT 0x20808000
+
+#define RESET_REQ_MASK 0x80000000
+
+#define TCSR0_SD_STAT_OBS_EN_MASK 0x80000000
+#define RECR3_SNP_START_MASK 0x80000000
+#define RECR3_SNP_DONE_MASK 0x40000000
+
+#define RECR4_SNP_DATA_MASK 0x000001ff
+#define RECR4_SNP_DATA_SHIFT 0
+#define RECR4_EQ_SNPBIN_SIGN_MASK 0x100
+
+#define RECR3_GAINK2_MASK 0x1f000000
+#define RECR3_GAINK2_SHIFT 24
+
+#define RECR3_GAINK3_MASK 0x001f0000
+#define RECR3_GAINK3_SHIFT 16
+
+#define RECR4_EQ_OFFSET_MASK 0x003f0000
+#define RECR4_EQ_OFFSET_SHIFT 16
+
+#define RRSTCTL_CDR_LOCK_MASK 0x00001000
+
+#define AN_AD_ABILITY_0 0x02
+#define AN_AD_ABILITY_1 0x03
+#define AN_BP_ETH_STATUS_OFFSET 0x0F
+#define KR_PMD_BASE_OFFSET 0x100
+
+/* 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 0x1F
+#define EQ_GAINK_MAX 0x0
+#define EQ_GAINK_MIDRANGE_LOW 0x1E
+#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 0x100
+
+struct qoriq_lane_regs {
+ u32 gcr0; /* 0x00: General Control Register 0 */
+ u32 res_04[7]; /* 0x04: Reserved */
+ u32 trstctl; /* 0x20: TX Reset Control Register */
+ u32 tgcr0; /* 0x24: TX General Control Register 0 */
+ u32 tgcr1; /* 0x28: TX General Control Register 1 */
+ u32 tgcr2; /* 0x2C: TX General Control Register 2 */
+ u32 tecr0; /* 0x30: Transmit Equalization Control Register 0 */
+ u32 tecr1; /* 0x34: Transmit Equalization Control Register 1 */
+ u32 res_38[2]; /* 0x38: Reserved */
+ u32 rrstctl; /* 0x40: RX Reset Control Register */
+ u32 rgcr0; /* 0x44: RX General Control Register 0 */
+ u32 rxgcr1; /* 0x48: RX General Control Register 1 */
+ u32 res_4c; /* 0x4C: Reserved */
+ u32 recr0; /* 0x50: RX Equalization Register 0 */
+ u32 recr1; /* 0x54: RX Equalization Register 1 */
+ u32 recr2; /* 0x58: RX Equalization Register 2 */
+ u32 recr3; /* 0x5C: RX Equalization Register 3 */
+ u32 recr4; /* 0x60: RX Equalization Register 4 */
+ u32 res_64; /* 0x64: Reserved */
+ u32 rccr0; /* 0x68: RX Calibration Register 0 */
+ u32 rccr1; /* 0x6C: RX Calibration Register 1 */
+ u32 rcpcr0; /* 0x70: RX Clock Path Register 0 */
+ u32 rsccr0; /* 0x74: RX Sampler Calibration Control Register 0 */
+ u32 rsccr1; /* 0x78: RX Sampler Calibration Control Register 1 */
+ u32 res_7c; /* 0x7C: Reserved */
+ u32 ttlcr0; /* 0x80: Transition Tracking Loop Register 0 */
+ u32 ttlcr1; /* 0x84: Transition Tracking Loop Register 1 */
+ u32 ttlcr2; /* 0x88: Transition Tracking Loop Register 2 */
+ u32 ttlcr3; /* 0x8C: Transition Tracking Loop Register 3 */
+ u32 res_90[4]; /* 0x90: Reserved */
+ u32 tcsr0; /* 0xA0: Test Control/Status Register 0 */
+ u32 tcsr1; /* 0xA4: Test Control/Status Register 1 */
+ u32 tcsr2; /* 0xA8: Test Control/Status Register 2 */
+ u32 tcsr3; /* 0xAC: Test Control/Status Register 3 */
+ u32 tcsr4; /* 0xB0: Test Control/Status Register 4 */
+ u32 res_b4[3]; /* 0xB4: Reserved */
+ u32 rxcb0; /* 0xC0: RX Control Block Register 0 */
+ u32 rxcb1; /* 0xC4: RX Control Block Register 1 */
+ u32 res_c8[2]; /* 0xC8: Reserved */
+ u32 rxss0; /* 0xD0: RX Speed Switch Register 0 */
+ u32 rxss1; /* 0xD4: RX Speed Switch Register 1 */
+ u32 rxss2; /* 0xD8: RX Speed Switch Register 2 */
+ u32 res_dc; /* 0xDC: Reserved */
+ u32 txcb0; /* 0xE0: TX Control Block Register 0 */
+ u32 txcb1; /* 0xE4: TX Control Block Register 1 */
+ u32 res_e8[2]; /* 0xE8: Reserved */
+ u32 txss0; /* 0xF0: TX Speed Switch Register 0 */
+ u32 txss1; /* 0xF4: TX Speed Switch Register 1 */
+ u32 txss2; /* 0xF8: TX Speed Switch Register 2 */
+ u32 res_fc; /* 0xFC: Reserved */
+};
+
+static struct mem_io_ops io_ops;
+
+static void reset_lane(void __iomem *reg, enum lane_req ln_req)
+{
+ struct qoriq_lane_regs __iomem *reg_base = reg;
+ u32 val;
+ u64 timeout;
+
+ /* reset Tx lane: send reset request */
+ if (ln_req | LANE_TX) {
+ io_ops.write32(io_ops.read32(®_base->trstctl) |
+ RESET_REQ_MASK, ®_base->trstctl);
+ udelay(1);
+ timeout = 10;
+ while (timeout--) {
+ val = io_ops.read32(®_base->trstctl);
+ if (!(val & RESET_REQ_MASK))
+ break;
+ usleep_range(5, 20);
+ }
+ }
+
+ /* reset Rx lane: send reset request */
+ if (ln_req | LANE_RX) {
+ io_ops.write32(io_ops.read32(®_base->rrstctl) |
+ RESET_REQ_MASK, ®_base->rrstctl);
+ udelay(1);
+ timeout = 10;
+ while (timeout--) {
+ val = io_ops.read32(®_base->rrstctl);
+ if (!(val & RESET_REQ_MASK))
+ break;
+ usleep_range(5, 20);
+ }
+ }
+
+ /* wait for a while after reset */
+ if (ln_req != LANE_INVALID) {
+ timeout = jiffies + 10;
+ while (time_before(jiffies, (unsigned long)timeout)) {
+ schedule();
+ usleep_range(5, 20);
+ }
+ }
+}
+
+static u32 read_tecr0(void __iomem *reg)
+{
+ struct qoriq_lane_regs __iomem *reg_base = reg;
+
+ return io_ops.read32(®_base->tecr0);
+}
+
+static u32 read_tecr1(void __iomem *reg)
+{
+ struct qoriq_lane_regs __iomem *reg_base = reg;
+
+ return io_ops.read32(®_base->tecr1);
+}
+
+static void read_tecr_params(void __iomem *reg, struct lane_kr_params *params)
+{
+ struct qoriq_lane_regs __iomem *reg_base = reg;
+ u32 val;
+
+ val = io_ops.read32(®_base->tecr0);
+ params->ratio_preq = (val & RATIO_PREQ_MASK) >> RATIO_PREQ_SHIFT;
+ params->ratio_pstq = (val & RATIO_PST1Q_MASK) >> RATIO_PST1Q_SHIFT;
+ params->amp_red = (val & AMP_RED_MASK) >> AMP_RED_SHIFT;
+
+ val = io_ops.read32(®_base->tecr1);
+ params->adpt_eq = (val & ADPT_EQ_MASK) >> ADPT_EQ_SHIFT;
+}
+
+static void setup_tecr(void __iomem *reg, struct lane_kr_params *params,
+ bool reset)
+{
+ struct qoriq_lane_regs __iomem *reg_base = reg;
+ u32 val;
+
+ /* reset lanes */
+ if (reset)
+ reset_lane(reg, LANE_RX_TX);
+
+ val = TECR0_INIT |
+ params->ratio_preq << RATIO_PREQ_SHIFT |
+ params->ratio_pstq << RATIO_PST1Q_SHIFT |
+ params->amp_red << AMP_RED_SHIFT;
+ io_ops.write32(val, ®_base->tecr0);
+
+ val = params->adpt_eq << ADPT_EQ_SHIFT;
+ io_ops.write32(val, ®_base->tecr1);
+
+ 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)
+{
+ u32 recr3, recr4;
+ struct qoriq_lane_regs __iomem *reg_base = reg;
+ int timeout;
+ int i;
+
+ /* Enable observation of SerDes status on all status registers */
+ io_ops.write32(io_ops.read32(®_base->tcsr0) |
+ TCSR0_SD_STAT_OBS_EN_MASK, ®_base->tcsr0);
+
+ for (i = 0; i < size; i++) {
+ /* wait RECR3_SNP_DONE_MASK has cleared */
+ timeout = 100;
+ while (io_ops.read32(®_base->recr3) & RECR3_SNP_DONE_MASK) {
+ udelay(1);
+ timeout--;
+ if (timeout == 0)
+ break;
+ }
+
+ /* start snapshot */
+ io_ops.write32((io_ops.read32(®_base->recr3) |
+ RECR3_SNP_START_MASK), ®_base->recr3);
+
+ /* wait for SNP done */
+ timeout = 100;
+ while (!(io_ops.read32(®_base->recr3) &
+ RECR3_SNP_DONE_MASK)) {
+ udelay(1);
+ timeout--;
+ if (timeout == 0)
+ break;
+ }
+
+ /* read and save the snapshot */
+ recr3 = io_ops.read32(®_base->recr3);
+ recr4 = io_ops.read32(®_base->recr4);
+
+ if (gaink2)
+ gaink2[i] = (u8)((recr3 & RECR3_GAINK2_MASK) >>
+ RECR3_GAINK2_SHIFT);
+ if (gaink3)
+ gaink3[i] = (u8)((recr3 & RECR3_GAINK3_MASK) >>
+ RECR3_GAINK3_SHIFT);
+ if (osestat)
+ osestat[i] = (u8)((recr4 & RECR4_EQ_OFFSET_MASK) >>
+ RECR4_EQ_OFFSET_SHIFT);
+
+ /* terminate the snapshot by setting GCR1[REQ_CTL_SNP] */
+ io_ops.write32((io_ops.read32(®_base->recr3) &
+ ~RECR3_SNP_START_MASK), ®_base->recr3);
+ }
+ 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)
+{
+ int bin_snapshot;
+ u32 bin_sel;
+ int i, timeout;
+ struct qoriq_lane_regs __iomem *reg_base = reg;
+
+ /* calculate RECR4[EQ_BIN_DATA_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_4:
+ bin_sel = BIN_4_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;
+ }
+
+ /* Enable observation of SerDes status on all status registers */
+ io_ops.write32(io_ops.read32(®_base->tcsr0) |
+ TCSR0_SD_STAT_OBS_EN_MASK, ®_base->tcsr0);
+
+ for (i = 0; i < bin_size; i++) {
+ /* wait RECR3_SNP_DONE_MASK has cleared */
+ timeout = 100;
+ while ((io_ops.read32(®_base->recr3) &
+ RECR3_SNP_DONE_MASK)) {
+ udelay(1);
+ timeout--;
+ if (timeout == 0)
+ break;
+ }
+
+ /* set RECR4[EQ_BIN_DATA_SEL] */
+ io_ops.write32((io_ops.read32(®_base->recr4) &
+ ~CDR_SEL_MASK) | bin_sel, ®_base->recr4);
+
+ /* start snapshot */
+ io_ops.write32(io_ops.read32(®_base->recr3) |
+ RECR3_SNP_START_MASK, ®_base->recr3);
+
+ /* wait for SNP done */
+ timeout = 100;
+ while (!(io_ops.read32(®_base->recr3) &
+ RECR3_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_ops.read32(®_base->recr4) &
+ RECR4_SNP_DATA_MASK) >> RECR4_SNP_DATA_SHIFT;
+ if (bin_snapshot & RECR4_EQ_SNPBIN_SIGN_MASK) {
+ /* 2's complement 9 bit long negative number */
+ bin_snapshot &= ~RECR4_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_ops.write32(io_ops.read32(®_base->recr3) &
+ ~RECR3_SNP_START_MASK, ®_base->recr3);
+ }
+ 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_4:
+ 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_ops.read32(®_base->rrstctl) & RRSTCTL_CDR_LOCK_MASK)
+ return true;
+
+ return false;
+}
+
+static const struct qoriq_lane_ops qoriq_lane = {
+ .read_tecr0 = read_tecr0,
+ .read_tecr1 = read_tecr1,
+};
+
+static const struct lane_io_ops lane_ops = {
+ .priv = &qoriq_lane,
+ .memmap_size = MEMORY_MAP_SIZE,
+ .reset_lane = reset_lane,
+ .tune_lane_kr = setup_tecr,
+ .read_lane_kr = read_tecr_params,
+ .is_cdr_lock = is_cdr_lock_bit,
+};
+
+const struct lane_io_ops *qoriq_get_lane_ops_28g(void)
+{
+ return &lane_ops;
+}
+
+static const struct equalizer_info 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_info *qoriq_get_equalizer_info_28g(void)
+{
+ return &equalizer;
+}
+
+void qoriq_setup_mem_io_28g(struct mem_io_ops io)
+{
+ io_ops = io;
+}
+
+void qoriq_setup_mdio_28g(struct backplane_dev_info *bp_dev)
+{
+ /* Auto-Negotiation and Link Training Core Registers:
+ * IEEE802.3 Clauses 72, 73, 92
+ */
+
+ /* Link Training Control and Status Registers: page 1: 0x100 */
+ backplane_setup_kr_lt_mmd(bp_dev, MDIO_MMD_AN, KR_PMD_BASE_OFFSET);
+
+ /* Auto-Negotiation Control and Status Registers: page 0 */
+ bp_dev->mdio.an_ad_ability_0 = AN_AD_ABILITY_0;
+ bp_dev->mdio.an_ad_ability_1 = AN_AD_ABILITY_1;
+ bp_dev->mdio.an_bp_eth_status = AN_BP_ETH_STATUS_OFFSET;
+}
--
1.9.1