[PATCH v4 2/2] phy: zynqmp: Add phy driver for xilinx zynqmp phy core

From: Anurag Kumar Vulisha
Date: Wed Sep 12 2018 - 12:23:26 EST


ZynqMP SoC has a Gigabit Transceiver with four lanes. All the high speed
peripherals such as USB, SATA, PCIE, Display Port and Ethernet SGMII can
rely on any of the four GT lanes for PHY layer. This patch adds driver
for that ZynqMP GT core.

Signed-off-by: Anurag Kumar Vulisha <anurag.kumar.vulisha@xxxxxxxxxx>
---
Changes in v4:
1. Moved include/dt-bindings/phy/phy.h into patch 1 as suggested by
"Rob Herring"

Changes in v3:
1. Corrected the Documentation as suggested by "Vivek Gautam"

Changes in v2:
1. Fixed the compilation error when compiled phy-zynqmp.c as a module
2. Added CONFIG_PM macro in phy-zynqmp.c driver
---
drivers/phy/Kconfig | 8 +
drivers/phy/Makefile | 1 +
drivers/phy/phy-zynqmp.c | 1581 ++++++++++++++++++++++++++++++++++++++++
include/linux/phy/phy-zynqmp.h | 52 ++
4 files changed, 1642 insertions(+)
create mode 100644 drivers/phy/phy-zynqmp.c
create mode 100644 include/linux/phy/phy-zynqmp.h

diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index 5c8d452..14cf3330 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -40,6 +40,14 @@ config PHY_XGENE
help
This option enables support for APM X-Gene SoC multi-purpose PHY.

+config PHY_XILINX_ZYNQMP
+ tristate "Xilinx ZynqMP PHY driver"
+ depends on ARCH_ZYNQMP
+ select GENERIC_PHY
+ help
+ Enable this to support ZynqMP High Speed Gigabit Transceiver
+ that is part of ZynqMP SoC.
+
source "drivers/phy/allwinner/Kconfig"
source "drivers/phy/amlogic/Kconfig"
source "drivers/phy/broadcom/Kconfig"
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index 84e3bd9..f2a8d27 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_GENERIC_PHY) += phy-core.o
obj-$(CONFIG_PHY_LPC18XX_USB_OTG) += phy-lpc18xx-usb-otg.o
obj-$(CONFIG_PHY_XGENE) += phy-xgene.o
obj-$(CONFIG_PHY_PISTACHIO_USB) += phy-pistachio-usb.o
+obj-$(CONFIG_PHY_XILINX_ZYNQMP) += phy-zynqmp.o
obj-$(CONFIG_ARCH_SUNXI) += allwinner/
obj-$(CONFIG_ARCH_MESON) += amlogic/
obj-$(CONFIG_LANTIQ) += lantiq/
diff --git a/drivers/phy/phy-zynqmp.c b/drivers/phy/phy-zynqmp.c
new file mode 100644
index 0000000..497862d
--- /dev/null
+++ b/drivers/phy/phy-zynqmp.c
@@ -0,0 +1,1581 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * phy-zynqmp.c - PHY driver for Xilinx ZynqMP GT.
+ *
+ * Copyright (C) 2018 Xilinx Inc.
+ *
+ * Author: Anurag Kumar Vulisha <anuragku@xxxxxxxxxx>
+ *
+ * This driver is tested for USB, SATA and Display Port currently.
+ * Other controllers PCIe and SGMII should also work but that is
+ * experimental as of now.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/of_address.h>
+#include <linux/phy/phy.h>
+#include <linux/phy/phy-zynqmp.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <dt-bindings/phy/phy.h>
+#include <linux/reset.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+
+/* Inter Connect Matrix parameters */
+#define ICM_CFG0 0x10010
+#define ICM_CFG1 0x10014
+#define ICM_CFG0_L0_MASK 0x07
+#define ICM_CFG0_L1_MASK 0x70
+#define ICM_CFG1_L2_MASK 0x07
+#define ICM_CFG2_L3_MASK 0x70
+#define ICM_CFG_SHIFT 4
+
+/* Inter Connect Matrix allowed protocols */
+#define ICM_PROTOCOL_PD 0x0
+#define ICM_PROTOCOL_PCIE 0x1
+#define ICM_PROTOCOL_SATA 0x2
+#define ICM_PROTOCOL_USB 0x3
+#define ICM_PROTOCOL_DP 0x4
+#define ICM_PROTOCOL_SGMII 0x5
+
+/* Test Mode common reset control parameters */
+#define TM_CMN_RST 0x10018
+#define TM_CMN_RST_EN 0x1
+#define TM_CMN_RST_SET 0x2
+#define TM_CMN_RST_MASK 0x3
+
+/* Refclk selection parameters */
+#define PLL_REF_SEL0 0x10000
+#define PLL_REF_OFFSET 0x4
+#define PLL_FREQ_MASK 0x1F
+#define PLL_STATUS_READ_OFFSET 0x4000
+#define PLL_STATUS_LOCKED 0x10
+
+/* PLL SSC step size offsets */
+#define L0_L0_REF_CLK_SEL 0x2860
+#define L0_PLL_SS_STEPS_0_LSB 0x2368
+#define L0_PLL_SS_STEPS_1_MSB 0x236C
+#define L0_PLL_SS_STEP_SIZE_0_LSB 0x2370
+#define L0_PLL_SS_STEP_SIZE_1 0x2374
+#define L0_PLL_SS_STEP_SIZE_2 0x2378
+#define L0_PLL_SS_STEP_SIZE_3_MSB 0x237C
+#define L0_PLL_STATUS_READ_1 0x23E4
+
+/* SSC step size parameters */
+#define STEP_SIZE_OFFSET 0x4000
+#define STEP_SIZE_0_MASK 0xFF
+#define STEP_SIZE_1_MASK 0xFF
+#define STEP_SIZE_2_MASK 0xFF
+#define STEP_SIZE_3_MASK 0x3
+#define STEP_SIZE_SHIFT 8
+#define FORCE_STEP_SIZE 0x10
+#define FORCE_STEPS 0x20
+#define STEPS_OFFSET 0x4000
+#define STEPS_0_MASK 0xFF
+#define STEPS_1_MASK 0x07
+
+/* BG calibration parameters */
+#define BGCAL_REF_SEL 0x10028
+#define BGCAL_REF_VALUE 0x0C
+
+/* Calibration digital logic parameters */
+#define L3_TM_CALIB_DIG19 0xEC4C
+#define L3_CALIB_DONE_STATUS 0xEF14
+#define L3_TM_CALIB_DIG18 0xEC48
+#define L3_TM_CALIB_DIG19_NSW 0x07
+#define L3_TM_CALIB_DIG18_NSW 0xE0
+#define L3_TM_OVERRIDE_NSW_CODE 0x20
+#define L3_CALIB_DONE 0x02
+#define L3_NSW_SHIFT 5
+#define L3_NSW_PIPE_SHIFT 4
+#define L3_NSW_CALIB_SHIFT 3
+
+/* DN Resistor calibration code parameters */
+#define L0_TXPMA_ST_3 0x0B0C
+#define L0_DN_CALIB_CODE 0x3F
+
+/* PLL Test Mode register parameters */
+#define L0_TM_PLL_DIG_37 0x2094
+#define L0_TM_PLL_DIG_37_OFFSET 0x4000
+#define L0_TM_COARSE_CODE_LIMIT 0x10
+
+/* PCS control parameters */
+#define L0_TM_DIG_6 0x106C
+#define L0_TX_DIG_61 0x00F4
+#define L0_TM_DIG_6_OFFSET 0x4000
+#define L0_TX_DIG_61_OFFSET 0x4000
+#define L0_TM_DIS_DESCRAMBLE_DECODER 0x0F
+#define L0_TM_DISABLE_SCRAMBLE_ENCODER 0x0F
+
+/* TX De-emphasis parameters */
+#define L0_TX_ANA_TM_18 0x0048
+#define L0_TX_ANA_TM_118 0x01D8
+#define L0_TX_ANA_TM_18_OFFSET 0x4000
+#define L0_TX_ANA_TM_118_OFFSET 0x4000
+#define L0_TX_ANA_TM_118_FORCE_17_0 BIT(0)
+
+/* PMA control parameters */
+#define L0_TXPMD_TM_45 0x0CB4
+#define L0_TXPMD_TM_48 0x0CC0
+#define L0_TXPMD_TM_45_OFFSET 0x4000
+#define L0_TXPMD_TM_48_OFFSET 0x4000
+#define L0_TXPMD_TM_45_OVER_DP_MAIN BIT(0)
+#define L0_TXPMD_TM_45_ENABLE_DP_MAIN BIT(1)
+#define L0_TXPMD_TM_45_OVER_DP_POST1 BIT(2)
+#define L0_TXPMD_TM_45_ENABLE_DP_POST1 BIT(3)
+#define L0_TXPMD_TM_45_OVER_DP_POST2 BIT(4)
+#define L0_TXPMD_TM_45_ENABLE_DP_POST2 BIT(5)
+
+/* Bus width parameters */
+#define TX_PROT_BUS_WIDTH 0x10040
+#define RX_PROT_BUS_WIDTH 0x10044
+#define PROT_BUS_WIDTH_10 0x0
+#define PROT_BUS_WIDTH_20 0x1
+#define PROT_BUS_WIDTH_40 0x2
+#define PROT_BUS_WIDTH_SHIFT 2
+
+/* Max number of GT lanes */
+#define MAX_LANES 4
+
+/* Max Allowed refclk frequencies */
+#define MAX_REFCLK 13
+
+/* Lane CLK sharing mask */
+#define LANE_CLK_SHARE_MASK 0x8F
+
+/* SIOU SATA control register */
+#define SATA_CONTROL_OFFSET 0x0100
+
+/* Total number of controllers */
+#define CONTROLLERS_PER_LANE 5
+
+/* USB pipe control parameters */
+#define PIPE_CLK_OFFSET 0x7c
+#define PIPE_POWER_OFFSET 0x80
+#define PIPE_CLK_ON 1
+#define PIPE_CLK_OFF 0
+#define PIPE_POWER_ON 1
+#define PIPE_POWER_OFF 0
+
+/* Protocol Type Pparameters */
+#define XPSGTR_TYPE_USB0 0 /* USB controller 0 */
+#define XPSGTR_TYPE_USB1 1 /* USB controller 1 */
+#define XPSGTR_TYPE_SATA_0 2 /* SATA controller lane 0 */
+#define XPSGTR_TYPE_SATA_1 3 /* SATA controller lane 1 */
+#define XPSGTR_TYPE_PCIE_0 4 /* PCIe controller lane 0 */
+#define XPSGTR_TYPE_PCIE_1 5 /* PCIe controller lane 1 */
+#define XPSGTR_TYPE_PCIE_2 6 /* PCIe controller lane 2 */
+#define XPSGTR_TYPE_PCIE_3 7 /* PCIe controller lane 3 */
+#define XPSGTR_TYPE_DP_0 8 /* Display Port controller lane 0 */
+#define XPSGTR_TYPE_DP_1 9 /* Display Port controller lane 1 */
+#define XPSGTR_TYPE_SGMII0 10 /* Ethernet SGMII controller 0 */
+#define XPSGTR_TYPE_SGMII1 11 /* Ethernet SGMII controller 1 */
+#define XPSGTR_TYPE_SGMII2 12 /* Ethernet SGMII controller 2 */
+#define XPSGTR_TYPE_SGMII3 13 /* Ethernet SGMII controller 3 */
+
+/* Timeout values */
+#define RST_TIMEOUT_MS 1000
+#define TIMEOUT_US 1000
+
+/*
+ * This table holds the valid combinations of controllers and
+ * lanes(Interconnect Matrix).
+ */
+static unsigned int icm_matrix[MAX_LANES][CONTROLLERS_PER_LANE] = {
+ { XPSGTR_TYPE_PCIE_0, XPSGTR_TYPE_SATA_0, XPSGTR_TYPE_USB0,
+ XPSGTR_TYPE_DP_1, XPSGTR_TYPE_SGMII0 },
+ { XPSGTR_TYPE_PCIE_1, XPSGTR_TYPE_SATA_1, XPSGTR_TYPE_USB0,
+ XPSGTR_TYPE_DP_0, XPSGTR_TYPE_SGMII1 },
+ { XPSGTR_TYPE_PCIE_2, XPSGTR_TYPE_SATA_0, XPSGTR_TYPE_USB0,
+ XPSGTR_TYPE_DP_1, XPSGTR_TYPE_SGMII2 },
+ { XPSGTR_TYPE_PCIE_3, XPSGTR_TYPE_SATA_1, XPSGTR_TYPE_USB1,
+ XPSGTR_TYPE_DP_0, XPSGTR_TYPE_SGMII3 }
+};
+
+/* Allowed PLL reference clock frequencies */
+enum pll_frequencies {
+ REF_19_2M = 0,
+ REF_20M,
+ REF_24M,
+ REF_26M,
+ REF_27M,
+ REF_38_4M,
+ REF_40M,
+ REF_52M,
+ REF_100M,
+ REF_108M,
+ REF_125M,
+ REF_135M,
+ REF_150M,
+};
+
+/**
+ * struct xpsgtr_phy - representation of a lane
+ * @phy: pointer to the kernel PHY device
+ * @type: controller which uses this lane
+ * @lane: lane number
+ * @protocol: protocol in which the lane operates
+ * @ref_clk: enum of allowed ref clock rates for this lane PLL
+ * @pll_lock: PLL status
+ * @skip_phy_init: skip phy_init() if true
+ * @data: pointer to hold private data
+ * @refclk_rate: PLL reference clock frequency
+ * @share_laneclk: lane number of the clock to be shared
+ */
+struct xpsgtr_phy {
+ struct phy *phy;
+ u8 type;
+ u8 lane;
+ u8 protocol;
+ enum pll_frequencies ref_clk;
+ bool pll_lock;
+ bool skip_phy_init;
+ void *data;
+ u32 refclk_rate;
+ u32 share_laneclk;
+};
+
+/**
+ * struct xpsgtr_ssc - structure to hold SSC settings for a lane
+ * @refclk_rate: PLL reference clock frequency
+ * @pll_ref_clk: value to be written to register for corresponding ref clk rate
+ * @steps: number of steps of SSC (Spread Spectrum Clock)
+ * @step_size: step size of each step
+ */
+struct xpsgtr_ssc {
+ u32 refclk_rate;
+ u8 pll_ref_clk;
+ u32 steps;
+ u32 step_size;
+};
+
+/* lookup table to hold all settings needed for a ref clock frequency */
+static struct xpsgtr_ssc ssc_lookup[MAX_REFCLK] = {
+ {19200000, 0x05, 608, 264020},
+ {20000000, 0x06, 634, 243454},
+ {24000000, 0x07, 760, 168973},
+ {26000000, 0x08, 824, 143860},
+ {27000000, 0x09, 856, 86551},
+ {38400000, 0x0A, 1218, 65896},
+ {40000000, 0x0B, 634, 243454},
+ {52000000, 0x0C, 824, 143860},
+ {100000000, 0x0D, 1058, 87533},
+ {108000000, 0x0E, 856, 86551},
+ {125000000, 0x0F, 992, 119497},
+ {135000000, 0x10, 1070, 55393},
+ {150000000, 0x11, 792, 187091}
+};
+
+/**
+ * struct xpsgtr_dev - representation of a ZynMP GT device
+ * @dev: pointer to device
+ * @serdes: serdes base address
+ * @siou: siou base address
+ * @gtr_mutex: mutex for locking
+ * @phys: pointer to all the lanes
+ * @tx_term_fix: fix for GT issue
+ * @saved_icm_cfg0: stored value of ICM CFG0 register
+ * @saved_icm_cfg1: stored value of ICM CFG1 register
+ * @sata_rst: a reset control for SATA
+ * @dp_rst: a reset control for DP
+ * @usb0_crst: a reset control for usb0 core
+ * @usb1_crst: a reset control for usb1 core
+ * @usb0_hibrst: a reset control for usb0 hibernation module
+ * @usb1_hibrst: a reset control for usb1 hibernation module
+ * @usb0_apbrst: a reset control for usb0 apb bus
+ * @usb1_apbrst: a reset control for usb1 apb bus
+ * @gem0_rst: a reset control for gem0
+ * @gem1_rst: a reset control for gem1
+ * @gem2_rst: a reset control for gem2
+ * @gem3_rst: a reset control for gem3
+ */
+struct xpsgtr_dev {
+ struct device *dev;
+ void __iomem *serdes;
+ void __iomem *siou;
+ struct mutex gtr_mutex; /* mutex for locking */
+ struct xpsgtr_phy **phys;
+ bool tx_term_fix;
+ unsigned int saved_icm_cfg0;
+ unsigned int saved_icm_cfg1;
+ struct reset_control *sata_rst;
+ struct reset_control *dp_rst;
+ struct reset_control *usb0_crst;
+ struct reset_control *usb1_crst;
+ struct reset_control *usb0_hibrst;
+ struct reset_control *usb1_hibrst;
+ struct reset_control *usb0_apbrst;
+ struct reset_control *usb1_apbrst;
+ struct reset_control *gem0_rst;
+ struct reset_control *gem1_rst;
+ struct reset_control *gem2_rst;
+ struct reset_control *gem3_rst;
+};
+
+/**
+ * xpsgtr_override_deemph - override PIPE TX de-emphasis
+ * @phy: pointer to phy
+ * @plvl: pre-emphasis level
+ * @vlvl: voltage swing level
+ *
+ * Return: None
+ */
+void xpsgtr_override_deemph(struct phy *phy, u8 plvl, u8 vlvl)
+{
+ struct xpsgtr_phy *gtr_phy = phy_get_drvdata(phy);
+ struct xpsgtr_dev *gtr_dev = gtr_phy->data;
+ static u8 pe[4][4] = { { 0x2, 0x2, 0x2, 0x2 },
+ { 0x1, 0x1, 0x1, 0xFF },
+ { 0x0, 0x0, 0xFF, 0xFF },
+ { 0xFF, 0xFF, 0xFF, 0xFF } };
+
+ writel(pe[plvl][vlvl],
+ gtr_dev->serdes + gtr_phy->lane * L0_TX_ANA_TM_18_OFFSET +
+ L0_TX_ANA_TM_18);
+}
+EXPORT_SYMBOL_GPL(xpsgtr_override_deemph);
+
+/**
+ * xpsgtr_margining_factor - adjust margining factor value
+ * @phy: pointer to phy
+ * @plvl: pre-emphasis level
+ * @vlvl: voltage swing level
+ *
+ * Return: None
+ */
+void xpsgtr_margining_factor(struct phy *phy, u8 plvl, u8 vlvl)
+{
+ struct xpsgtr_phy *gtr_phy = phy_get_drvdata(phy);
+ struct xpsgtr_dev *gtr_dev = gtr_phy->data;
+ static u8 vs[4][4] = { { 0x2a, 0x27, 0x24, 0x20 },
+ { 0x27, 0x23, 0x20, 0xFF },
+ { 0x24, 0x20, 0xFF, 0xFF },
+ { 0xFF, 0xFF, 0xFF, 0xFF } };
+
+ writel(vs[plvl][vlvl],
+ gtr_dev->serdes + gtr_phy->lane * L0_TXPMD_TM_48_OFFSET +
+ L0_TXPMD_TM_48);
+}
+EXPORT_SYMBOL_GPL(xpsgtr_margining_factor);
+
+/**
+ * xpsgtr_configure_pll - configures SSC settings for a lane
+ * @gtr_phy: pointer to lane
+ *
+ * Return: None
+ */
+static void xpsgtr_configure_pll(struct xpsgtr_phy *gtr_phy)
+{
+ struct xpsgtr_dev *gtr_dev = gtr_phy->data;
+ u32 reg;
+ u32 offset;
+ u32 steps;
+ u32 size;
+ u8 pll_ref_clk;
+
+ steps = ssc_lookup[gtr_phy->ref_clk].steps;
+ size = ssc_lookup[gtr_phy->ref_clk].step_size;
+ pll_ref_clk = ssc_lookup[gtr_phy->ref_clk].pll_ref_clk;
+
+ offset = gtr_phy->lane * PLL_REF_OFFSET + PLL_REF_SEL0;
+ reg = readl(gtr_dev->serdes + offset);
+ reg = (reg & ~PLL_FREQ_MASK) | pll_ref_clk;
+ writel(reg, gtr_dev->serdes + offset);
+
+ /* Enable lane clock sharing, if required */
+ if (gtr_phy->share_laneclk != gtr_phy->lane) {
+ /* Lane3 Ref Clock Selection Register */
+ offset = gtr_phy->lane * PLL_REF_OFFSET + L0_L0_REF_CLK_SEL;
+ reg = readl(gtr_dev->serdes + offset);
+ reg = (reg & ~LANE_CLK_SHARE_MASK) |
+ (1 << gtr_phy->share_laneclk);
+ writel(reg, gtr_dev->serdes + offset);
+ }
+
+ /* SSC step size [7:0] */
+ offset = gtr_phy->lane * STEP_SIZE_OFFSET + L0_PLL_SS_STEP_SIZE_0_LSB;
+ reg = readl(gtr_dev->serdes + offset);
+ reg = (reg & ~STEP_SIZE_0_MASK) |
+ (size & STEP_SIZE_0_MASK);
+ writel(reg, gtr_dev->serdes + offset);
+
+ /* SSC step size [15:8] */
+ size = size >> STEP_SIZE_SHIFT;
+ offset = gtr_phy->lane * STEP_SIZE_OFFSET + L0_PLL_SS_STEP_SIZE_1;
+ reg = readl(gtr_dev->serdes + offset);
+ reg = (reg & ~STEP_SIZE_1_MASK) |
+ (size & STEP_SIZE_1_MASK);
+ writel(reg, gtr_dev->serdes + offset);
+
+ /* SSC step size [23:16] */
+ size = size >> STEP_SIZE_SHIFT;
+ offset = gtr_phy->lane * STEP_SIZE_OFFSET + L0_PLL_SS_STEP_SIZE_2;
+ reg = readl(gtr_dev->serdes + offset);
+ reg = (reg & ~STEP_SIZE_2_MASK) |
+ (size & STEP_SIZE_2_MASK);
+ writel(reg, gtr_dev->serdes + offset);
+
+ /* SSC steps [7:0] */
+ offset = gtr_phy->lane * STEPS_OFFSET + L0_PLL_SS_STEPS_0_LSB;
+ reg = readl(gtr_dev->serdes + offset);
+ reg = (reg & ~STEPS_0_MASK) |
+ (steps & STEPS_0_MASK);
+ writel(reg, gtr_dev->serdes + offset);
+
+ /* SSC steps [10:8] */
+ steps = steps >> STEP_SIZE_SHIFT;
+ offset = gtr_phy->lane * STEPS_OFFSET + L0_PLL_SS_STEPS_1_MSB;
+ reg = readl(gtr_dev->serdes + offset);
+ reg = (reg & ~STEPS_1_MASK) |
+ (steps & STEPS_1_MASK);
+ writel(reg, gtr_dev->serdes + offset);
+
+ /* SSC step size [24:25] */
+ size = size >> STEP_SIZE_SHIFT;
+ offset = gtr_phy->lane * STEP_SIZE_OFFSET + L0_PLL_SS_STEP_SIZE_3_MSB;
+ reg = readl(gtr_dev->serdes + offset);
+ reg = (reg & ~STEP_SIZE_3_MASK) |
+ (size & STEP_SIZE_3_MASK);
+ reg |= FORCE_STEP_SIZE | FORCE_STEPS;
+ writel(reg, gtr_dev->serdes + offset);
+}
+
+/**
+ * xpsgtr_lane_setprotocol - sets required protocol in ICM registers
+ * @gtr_phy: pointer to lane
+ *
+ * Return: None
+ */
+static void xpsgtr_lane_setprotocol(struct xpsgtr_phy *gtr_phy)
+{
+ struct xpsgtr_dev *gtr_dev = gtr_phy->data;
+ u32 reg;
+ u8 protocol = gtr_phy->protocol;
+
+ switch (gtr_phy->lane) {
+ case 0:
+ reg = readl(gtr_dev->serdes + ICM_CFG0);
+ reg = (reg & ~ICM_CFG0_L0_MASK) | protocol;
+ writel(reg, gtr_dev->serdes + ICM_CFG0);
+ break;
+ case 1:
+ reg = readl(gtr_dev->serdes + ICM_CFG0);
+ reg = (reg & ~ICM_CFG0_L1_MASK) | (protocol << ICM_CFG_SHIFT);
+ writel(reg, gtr_dev->serdes + ICM_CFG0);
+ break;
+ case 2:
+ reg = readl(gtr_dev->serdes + ICM_CFG1);
+ reg = (reg & ~ICM_CFG0_L0_MASK) | protocol;
+ writel(reg, gtr_dev->serdes + ICM_CFG1);
+ break;
+ case 3:
+ reg = readl(gtr_dev->serdes + ICM_CFG1);
+ reg = (reg & ~ICM_CFG0_L1_MASK) | (protocol << ICM_CFG_SHIFT);
+ writel(reg, gtr_dev->serdes + ICM_CFG1);
+ break;
+ default:
+ /* We already checked 0 <= lane <= 3 */
+ break;
+ }
+}
+
+/**
+ * xpsgtr_get_ssc - gets the required ssc settings based on clk rate
+ * @gtr_phy: pointer to lane
+ *
+ * Return: 0 on success or error on failure
+ */
+static int xpsgtr_get_ssc(struct xpsgtr_phy *gtr_phy)
+{
+ u32 i;
+
+ /*
+ * Assign the required spread spectrum(SSC) settings
+ * from lane refernce clk rate
+ */
+ for (i = 0 ; i < ARRAY_SIZE(ssc_lookup); i++) {
+ if (gtr_phy->refclk_rate == ssc_lookup[i].refclk_rate) {
+ gtr_phy->ref_clk = i;
+ return 0;
+ }
+ }
+
+ /* Did not get valid ssc settings*/
+ return -EINVAL;
+}
+
+/**
+ * xpsgtr_configure_lane - configures SSC settings for a lane
+ * @gtr_phy: pointer to lane
+ *
+ * Return: 0 on success or error on failure
+ */
+static int xpsgtr_configure_lane(struct xpsgtr_phy *gtr_phy)
+{
+ switch (gtr_phy->type) {
+ case XPSGTR_TYPE_USB0:
+ case XPSGTR_TYPE_USB1:
+ gtr_phy->protocol = ICM_PROTOCOL_USB;
+ break;
+ case XPSGTR_TYPE_SATA_0:
+ case XPSGTR_TYPE_SATA_1:
+ gtr_phy->protocol = ICM_PROTOCOL_SATA;
+ break;
+ case XPSGTR_TYPE_DP_0:
+ case XPSGTR_TYPE_DP_1:
+ gtr_phy->protocol = ICM_PROTOCOL_DP;
+ break;
+ case XPSGTR_TYPE_PCIE_0:
+ case XPSGTR_TYPE_PCIE_1:
+ case XPSGTR_TYPE_PCIE_2:
+ case XPSGTR_TYPE_PCIE_3:
+ gtr_phy->protocol = ICM_PROTOCOL_PCIE;
+ break;
+ case XPSGTR_TYPE_SGMII0:
+ case XPSGTR_TYPE_SGMII1:
+ case XPSGTR_TYPE_SGMII2:
+ case XPSGTR_TYPE_SGMII3:
+ gtr_phy->protocol = ICM_PROTOCOL_SGMII;
+ break;
+ default:
+ gtr_phy->protocol = ICM_PROTOCOL_PD;
+ break;
+ }
+
+ /* Get SSC settinsg for refernce clk rate */
+ if (xpsgtr_get_ssc(gtr_phy) < 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+/**
+ * xpsgtr_config_usbpipe - configures the PIPE3 signals for USB
+ * @gtr_phy: pointer to gtr phy device
+ *
+ * Return: None
+ */
+static void xpsgtr_config_usbpipe(struct xpsgtr_phy *gtr_phy)
+{
+ struct phy *phy = gtr_phy->phy;
+ struct xpsgtr_dev *gtr_dev = gtr_phy->data;
+ void __iomem *regs = dev_get_platdata(&phy->dev);
+
+ if (regs) {
+ /* Set PIPE power present signal */
+ writel(PIPE_POWER_ON, regs + PIPE_POWER_OFFSET);
+
+ /* Clear PIPE CLK signal */
+ writel(PIPE_CLK_OFF, regs + PIPE_CLK_OFFSET);
+ } else {
+ dev_info(gtr_dev->dev,
+ "%s: No valid Platform_data found\n", __func__);
+ }
+}
+
+/**
+ * xpsgtr_reset_assert - asserts reset using reset framework
+ * @rstc: pointer to reset_control
+ *
+ * Return: 0 on success or error on failure
+ */
+static int xpsgtr_reset_assert(struct reset_control *rstc)
+{
+ unsigned long loop_time = msecs_to_jiffies(RST_TIMEOUT_MS);
+ unsigned long timeout;
+
+ reset_control_assert(rstc);
+
+ /* wait until reset is asserted or timeout */
+ timeout = jiffies + loop_time;
+
+ while (!time_after_eq(jiffies, timeout)) {
+ if (reset_control_status(rstc) > 0)
+ return 0;
+
+ cpu_relax();
+ }
+
+ return -ETIMEDOUT;
+}
+
+/**
+ * xpsgtr_reset_release - de-asserts reset using reset framework
+ * @rstc: pointer to reset_control
+ *
+ * Return: 0 on success or error on failure
+ */
+static int xpsgtr_reset_release(struct reset_control *rstc)
+{
+ unsigned long loop_time = msecs_to_jiffies(RST_TIMEOUT_MS);
+ unsigned long timeout;
+
+ reset_control_deassert(rstc);
+
+ /* wait until reset is de-asserted or timeout */
+ timeout = jiffies + loop_time;
+ while (!time_after_eq(jiffies, timeout)) {
+ if (!reset_control_status(rstc))
+ return 0;
+
+ cpu_relax();
+ }
+
+ return -ETIMEDOUT;
+}
+
+/**
+ * xpsgtr_controller_reset - puts controller in reset
+ * @gtr_phy: pointer to lane
+ *
+ * Return: 0 on success or error on failure
+ */
+static int xpsgtr_controller_reset(struct xpsgtr_phy *gtr_phy)
+{
+ struct xpsgtr_dev *gtr_dev = gtr_phy->data;
+ int ret;
+
+ switch (gtr_phy->type) {
+ case XPSGTR_TYPE_USB0:
+ ret = xpsgtr_reset_assert(gtr_dev->usb0_crst);
+ if (ret != 0)
+ break;
+
+ ret = xpsgtr_reset_assert(gtr_dev->usb0_hibrst);
+ if (ret != 0)
+ break;
+
+ ret = xpsgtr_reset_assert(gtr_dev->usb0_apbrst);
+ break;
+ case XPSGTR_TYPE_USB1:
+ ret = xpsgtr_reset_assert(gtr_dev->usb1_crst);
+ if (ret != 0)
+ break;
+
+ ret = xpsgtr_reset_assert(gtr_dev->usb1_hibrst);
+ if (ret != 0)
+ break;
+
+ ret = xpsgtr_reset_assert(gtr_dev->usb1_apbrst);
+ break;
+ case XPSGTR_TYPE_SATA_0:
+ case XPSGTR_TYPE_SATA_1:
+ ret = xpsgtr_reset_assert(gtr_dev->sata_rst);
+ break;
+ case XPSGTR_TYPE_DP_0:
+ case XPSGTR_TYPE_DP_1:
+ ret = xpsgtr_reset_assert(gtr_dev->dp_rst);
+ break;
+ case XPSGTR_TYPE_SGMII0:
+ ret = xpsgtr_reset_assert(gtr_dev->gem0_rst);
+ break;
+ case XPSGTR_TYPE_SGMII1:
+ ret = xpsgtr_reset_assert(gtr_dev->gem1_rst);
+ break;
+ case XPSGTR_TYPE_SGMII2:
+ ret = xpsgtr_reset_assert(gtr_dev->gem2_rst);
+ break;
+ case XPSGTR_TYPE_SGMII3:
+ ret = xpsgtr_reset_assert(gtr_dev->gem3_rst);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * xpsgtr_controller_release_reset - releases controller from reset
+ * @gtr_phy: pointer to lane
+ *
+ * Return: 0 on success or error on failure
+ */
+static int xpsgtr_controller_release_reset(struct xpsgtr_phy *gtr_phy)
+{
+ struct xpsgtr_dev *gtr_dev = gtr_phy->data;
+ int ret;
+
+ switch (gtr_phy->type) {
+ case XPSGTR_TYPE_USB0:
+ ret = xpsgtr_reset_release(gtr_dev->usb0_apbrst);
+ if (ret != 0)
+ break;
+
+ /* Config PIPE3 signals after releasing APB reset */
+ xpsgtr_config_usbpipe(gtr_phy);
+
+ ret = xpsgtr_reset_release(gtr_dev->usb0_crst);
+ if (ret != 0)
+ break;
+
+ ret = xpsgtr_reset_release(gtr_dev->usb0_hibrst);
+ break;
+ case XPSGTR_TYPE_USB1:
+ ret = xpsgtr_reset_release(gtr_dev->usb1_apbrst);
+ if (ret != 0)
+ break;
+
+ /* Config PIPE3 signals after releasing APB reset */
+ xpsgtr_config_usbpipe(gtr_phy);
+
+ ret = xpsgtr_reset_release(gtr_dev->usb1_crst);
+ if (ret != 0)
+ break;
+
+ ret = xpsgtr_reset_release(gtr_dev->usb1_hibrst);
+ break;
+ case XPSGTR_TYPE_SATA_0:
+ case XPSGTR_TYPE_SATA_1:
+ ret = xpsgtr_reset_release(gtr_dev->sata_rst);
+ break;
+ case XPSGTR_TYPE_DP_0:
+ case XPSGTR_TYPE_DP_1:
+ ret = xpsgtr_reset_release(gtr_dev->dp_rst);
+ break;
+ case XPSGTR_TYPE_SGMII0:
+ ret = xpsgtr_reset_release(gtr_dev->gem0_rst);
+ break;
+ case XPSGTR_TYPE_SGMII1:
+ ret = xpsgtr_reset_release(gtr_dev->gem1_rst);
+ break;
+ case XPSGTR_TYPE_SGMII2:
+ ret = xpsgtr_reset_release(gtr_dev->gem2_rst);
+ break;
+ case XPSGTR_TYPE_SGMII3:
+ ret = xpsgtr_reset_release(gtr_dev->gem3_rst);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * xpsgtr_usb_rst_assert - assert USB core reset
+ * @phy: pointer to phy
+ *
+ * Return: 0 on success or error on failure
+ */
+int xpsgtr_usb_crst_assert(struct phy *phy)
+{
+ struct xpsgtr_phy *gtr_phy = phy_get_drvdata(phy);
+ struct xpsgtr_dev *gtr_dev = gtr_phy->data;
+ int ret;
+
+ switch (gtr_phy->type) {
+ case XPSGTR_TYPE_USB0:
+ ret = xpsgtr_reset_assert(gtr_dev->usb0_crst);
+ break;
+ case XPSGTR_TYPE_USB1:
+ ret = xpsgtr_reset_assert(gtr_dev->usb1_crst);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(xpsgtr_usb_crst_assert);
+
+/**
+ * xpsgtr_usb_rst_release - release USB core reset
+ * @phy: pointer to phy
+ *
+ * Return: 0 on success or error on failure
+ */
+int xpsgtr_usb_crst_release(struct phy *phy)
+{
+ struct xpsgtr_phy *gtr_phy = phy_get_drvdata(phy);
+ struct xpsgtr_dev *gtr_dev = gtr_phy->data;
+ int ret;
+
+ switch (gtr_phy->type) {
+ case XPSGTR_TYPE_USB0:
+ ret = xpsgtr_reset_release(gtr_dev->usb0_crst);
+ break;
+ case XPSGTR_TYPE_USB1:
+ ret = xpsgtr_reset_release(gtr_dev->usb1_crst);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(xpsgtr_usb_crst_release);
+
+/**
+ * xpsgtr_wait_pll_lock - Waits until PLL is locked or timedout
+ * @phy: pointer to phy
+ *
+ * Return: 0 on success or error on failure
+ */
+int xpsgtr_wait_pll_lock(struct phy *phy)
+{
+ struct xpsgtr_phy *gtr_phy = phy_get_drvdata(phy);
+ struct xpsgtr_dev *gtr_dev = gtr_phy->data;
+ u32 offset, reg;
+ u32 timeout = TIMEOUT_US;
+ int ret = 0;
+
+ /* Check pll is locked */
+ offset = gtr_phy->lane * PLL_STATUS_READ_OFFSET + L0_PLL_STATUS_READ_1;
+ dev_dbg(gtr_dev->dev, "Waiting for PLL lock...\n");
+
+ do {
+ reg = readl(gtr_dev->serdes + offset);
+ if ((reg & PLL_STATUS_LOCKED) == PLL_STATUS_LOCKED)
+ break;
+
+ if (!--timeout) {
+ dev_err(gtr_dev->dev, "PLL lock time out\n");
+ ret = -ETIMEDOUT;
+ break;
+ }
+
+ udelay(1);
+ } while (timeout > 0);
+
+ if (ret == 0)
+ gtr_phy->pll_lock = true;
+
+ dev_info(gtr_dev->dev, "Lane:%d type:%d protocol:%d pll_locked:%s\n",
+ gtr_phy->lane, gtr_phy->type, gtr_phy->protocol,
+ gtr_phy->pll_lock ? "yes" : "no");
+ return ret;
+}
+EXPORT_SYMBOL_GPL(xpsgtr_wait_pll_lock);
+
+/**
+ * xpsgtr_set_txwidth - This function sets the tx bus width of the lane
+ * @gtr_phy: pointer to lane
+ * @width: tx bus width size
+ *
+ * Return: None
+ */
+static void xpsgtr_set_txwidth(struct xpsgtr_phy *gtr_phy, u32 width)
+{
+ struct xpsgtr_dev *gtr_dev = gtr_phy->data;
+
+ writel(gtr_phy->lane * PROT_BUS_WIDTH_SHIFT >> width,
+ gtr_dev->serdes + TX_PROT_BUS_WIDTH);
+}
+
+/**
+ * xpsgtr_set_rxwidth - This function sets the rx bus width of the lane
+ * @gtr_phy: pointer to lane
+ * @width: rx bus width size
+ *
+ * Return: None
+ */
+static void xpsgtr_set_rxwidth(struct xpsgtr_phy *gtr_phy, u32 width)
+{
+ struct xpsgtr_dev *gtr_dev = gtr_phy->data;
+
+ writel(gtr_phy->lane * PROT_BUS_WIDTH_SHIFT >> width,
+ gtr_dev->serdes + RX_PROT_BUS_WIDTH);
+}
+
+/**
+ * xpsgtr_bypass_scramenc - This bypasses scrambler and 8b/10b encoder feature
+ * @gtr_phy: pointer to lane
+ *
+ * Return: None
+ */
+static void xpsgtr_bypass_scramenc(struct xpsgtr_phy *gtr_phy)
+{
+ u32 offset;
+ struct xpsgtr_dev *gtr_dev = gtr_phy->data;
+
+ /* bypass Scrambler and 8b/10b Encoder */
+ offset = gtr_phy->lane * L0_TX_DIG_61_OFFSET + L0_TX_DIG_61;
+ writel(L0_TM_DISABLE_SCRAMBLE_ENCODER, gtr_dev->serdes + offset);
+}
+
+/**
+ * xpsgtr_bypass_descramdec - bypasses descrambler and 8b/10b encoder feature
+ * @gtr_phy: pointer to lane
+ *
+ * Return: None
+ */
+static void xpsgtr_bypass_descramdec(struct xpsgtr_phy *gtr_phy)
+{
+ u32 offset;
+ struct xpsgtr_dev *gtr_dev = gtr_phy->data;
+
+ /* bypass Descrambler and 8b/10b decoder */
+ offset = gtr_phy->lane * L0_TM_DIG_6_OFFSET + L0_TM_DIG_6;
+ writel(L0_TM_DIS_DESCRAMBLE_DECODER, gtr_dev->serdes + offset);
+}
+
+/**
+ * xpsgtr_misc_sgmii - miscellaneous settings for SGMII
+ * @gtr_phy: pointer to lane
+ *
+ * Return: None
+ */
+static void xpsgtr_misc_sgmii(struct xpsgtr_phy *gtr_phy)
+{
+ /* Set SGMII protocol tx bus width 10 bits */
+ xpsgtr_set_txwidth(gtr_phy, PROT_BUS_WIDTH_10);
+
+ /* Set SGMII protocol rx bus width 10 bits */
+ xpsgtr_set_rxwidth(gtr_phy, PROT_BUS_WIDTH_10);
+
+ /* bypass Descrambler and 8b/10b decoder */
+ xpsgtr_bypass_descramdec(gtr_phy);
+
+ /* bypass Scrambler and 8b/10b Encoder */
+ xpsgtr_bypass_scramenc(gtr_phy);
+}
+
+/**
+ * xpsgtr_misc_sata - miscellaneous settings for SATA
+ * @gtr_phy: pointer to lane
+ *
+ * Return: None
+ */
+static void xpsgtr_misc_sata(struct xpsgtr_phy *gtr_phy)
+{
+ struct xpsgtr_dev *gtr_dev = gtr_phy->data;
+
+ /* bypass Descrambler and 8b/10b decoder */
+ xpsgtr_bypass_descramdec(gtr_phy);
+
+ /* bypass Scrambler and 8b/10b Encoder */
+ xpsgtr_bypass_scramenc(gtr_phy);
+
+ writel(gtr_phy->lane, gtr_dev->siou + SATA_CONTROL_OFFSET);
+}
+
+/**
+ * xpsgtr_phyinit_required - check if phy_init for the lane can be skipped
+ * @gtr_phy: pointer to the phy lane
+ *
+ * Return: true if phy_init can be skipped or false
+ */
+static bool xpsgtr_phyinit_required(struct xpsgtr_phy *gtr_phy)
+{
+ /*
+ * As USB may save the snapshot of the states during hibernation, doing
+ * phy_init() will put the USB controller into reset, resulting in the
+ * losing of the saved snapshot. So try to avoid phy_init() for USB
+ * except when gtr_phy->skip_phy_init is false (this happens when FPD is
+ * shutdown during suspend or when gt lane is changed from current one)
+ */
+ if (gtr_phy->protocol == ICM_PROTOCOL_USB && gtr_phy->skip_phy_init)
+ return true;
+ else
+ return false;
+}
+
+/**
+ * xpsgtr_phy_init - initializes a lane
+ * @phy: pointer to kernel PHY device
+ *
+ * Return: 0 on success or error on failure
+ */
+static int xpsgtr_phy_init(struct phy *phy)
+{
+ struct xpsgtr_phy *gtr_phy = phy_get_drvdata(phy);
+ struct xpsgtr_dev *gtr_dev = gtr_phy->data;
+ int ret = 0;
+ u32 offset;
+ u32 reg;
+ u32 nsw;
+ u32 timeout = TIMEOUT_US;
+
+ mutex_lock(&gtr_dev->gtr_mutex);
+
+ /* Check if phy_init() is required */
+ if (xpsgtr_phyinit_required(gtr_phy))
+ goto out;
+
+ /* Put controller in reset */
+ ret = xpsgtr_controller_reset(gtr_phy);
+ if (ret != 0) {
+ dev_err(gtr_dev->dev, "Failed to assert reset\n");
+ goto out;
+ }
+
+ /*
+ * There is a functional issue in the GT. The TX termination resistance
+ * can be out of spec due to a issue in the calibration logic. Below is
+ * the workaround to fix it. This below is required for XCZU9EG silicon.
+ */
+ if (gtr_dev->tx_term_fix) {
+ /* Enabling Test Mode control for CMN Rest */
+ reg = readl(gtr_dev->serdes + TM_CMN_RST);
+ reg = (reg & ~TM_CMN_RST_MASK) | TM_CMN_RST_SET;
+ writel(reg, gtr_dev->serdes + TM_CMN_RST);
+
+ /* Set Test Mode reset */
+ reg = readl(gtr_dev->serdes + TM_CMN_RST);
+ reg = (reg & ~TM_CMN_RST_MASK) | TM_CMN_RST_EN;
+ writel(reg, gtr_dev->serdes + TM_CMN_RST);
+
+ writel(0x00, gtr_dev->serdes + L3_TM_CALIB_DIG18);
+ writel(L3_TM_OVERRIDE_NSW_CODE, gtr_dev->serdes +
+ L3_TM_CALIB_DIG19);
+
+ /* As a part of work around sequence for PMOS calibration fix,
+ * we need to configure any lane ICM_CFG to valid protocol. This
+ * will deassert the CMN_Resetn signal.
+ */
+ xpsgtr_lane_setprotocol(gtr_phy);
+
+ /* Clear Test Mode reset */
+ reg = readl(gtr_dev->serdes + TM_CMN_RST);
+ reg = (reg & ~TM_CMN_RST_MASK) | TM_CMN_RST_SET;
+ writel(reg, gtr_dev->serdes + TM_CMN_RST);
+
+ dev_dbg(gtr_dev->dev, "calibrating...\n");
+
+ do {
+ reg = readl(gtr_dev->serdes + L3_CALIB_DONE_STATUS);
+ if ((reg & L3_CALIB_DONE) == L3_CALIB_DONE)
+ break;
+
+ if (!--timeout) {
+ dev_err(gtr_dev->dev, "calibration time out\n");
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+ udelay(1);
+ } while (timeout > 0);
+
+ dev_dbg(gtr_dev->dev, "calibration done\n");
+
+ /* Reading NMOS Register Code */
+ nsw = readl(gtr_dev->serdes + L0_TXPMA_ST_3);
+
+ /* Set Test Mode reset */
+ reg = readl(gtr_dev->serdes + TM_CMN_RST);
+ reg = (reg & ~TM_CMN_RST_MASK) | TM_CMN_RST_EN;
+ writel(reg, gtr_dev->serdes + TM_CMN_RST);
+
+ nsw = nsw & L0_DN_CALIB_CODE;
+
+ /* Writing NMOS register values back [5:3] */
+ reg = nsw >> L3_NSW_CALIB_SHIFT;
+ writel(reg, gtr_dev->serdes + L3_TM_CALIB_DIG19);
+
+ /* Writing NMOS register value [2:0] */
+ reg = ((nsw & L3_TM_CALIB_DIG19_NSW) << L3_NSW_SHIFT) |
+ (1 << L3_NSW_PIPE_SHIFT);
+ writel(reg, gtr_dev->serdes + L3_TM_CALIB_DIG18);
+
+ /* Clear Test Mode reset */
+ reg = readl(gtr_dev->serdes + TM_CMN_RST);
+ reg = (reg & ~TM_CMN_RST_MASK) | TM_CMN_RST_SET;
+ writel(reg, gtr_dev->serdes + TM_CMN_RST);
+
+ gtr_dev->tx_term_fix = false;
+ }
+
+ /* Enable coarse code saturation limiting logic */
+ offset = gtr_phy->lane * L0_TM_PLL_DIG_37_OFFSET + L0_TM_PLL_DIG_37;
+ writel(L0_TM_COARSE_CODE_LIMIT, gtr_dev->serdes + offset);
+
+ xpsgtr_configure_pll(gtr_phy);
+ xpsgtr_lane_setprotocol(gtr_phy);
+
+ if (gtr_phy->protocol == ICM_PROTOCOL_SATA)
+ xpsgtr_misc_sata(gtr_phy);
+
+ if (gtr_phy->protocol == ICM_PROTOCOL_SGMII)
+ xpsgtr_misc_sgmii(gtr_phy);
+
+ /* Bring controller out of reset */
+ ret = xpsgtr_controller_release_reset(gtr_phy);
+ if (ret != 0) {
+ dev_err(gtr_dev->dev, "Failed to release reset\n");
+ goto out;
+ }
+
+ /* Wait till pll is locked for all protocols except DP. For DP
+ * pll locking function will be called from driver.
+ */
+ if (gtr_phy->protocol != ICM_PROTOCOL_DP) {
+ ret = xpsgtr_wait_pll_lock(phy);
+ if (ret != 0)
+ goto out;
+ } else {
+ offset = gtr_phy->lane * L0_TXPMD_TM_45_OFFSET + L0_TXPMD_TM_45;
+ reg = L0_TXPMD_TM_45_OVER_DP_MAIN |
+ L0_TXPMD_TM_45_ENABLE_DP_MAIN |
+ L0_TXPMD_TM_45_OVER_DP_POST1 |
+ L0_TXPMD_TM_45_OVER_DP_POST2 |
+ L0_TXPMD_TM_45_ENABLE_DP_POST2;
+ writel(reg, gtr_dev->serdes + offset);
+ offset = gtr_phy->lane * L0_TX_ANA_TM_118_OFFSET +
+ L0_TX_ANA_TM_118;
+ writel(L0_TX_ANA_TM_118_FORCE_17_0,
+ gtr_dev->serdes + offset);
+ }
+
+out:
+ mutex_unlock(&gtr_dev->gtr_mutex);
+ return ret;
+}
+
+/**
+ * xpsgtr_set_lanetype - derives lane type from dts arguments
+ * @gtr_phy: pointer to lane
+ * @controller: type of controller
+ * @instance_num: instance number of the controller in case multilane controller
+ *
+ * Return: 0 on success or error on failure
+ */
+static int xpsgtr_set_lanetype(struct xpsgtr_phy *gtr_phy, u8 controller,
+ u8 instance_num)
+{
+ switch (controller) {
+ case PHY_TYPE_SATA:
+ if (!instance_num)
+ gtr_phy->type = XPSGTR_TYPE_SATA_0;
+ else if (instance_num == 1)
+ gtr_phy->type = XPSGTR_TYPE_SATA_1;
+ else
+ return -EINVAL;
+ break;
+ case PHY_TYPE_USB3:
+ if (!instance_num)
+ gtr_phy->type = XPSGTR_TYPE_USB0;
+ else if (instance_num == 1)
+ gtr_phy->type = XPSGTR_TYPE_USB1;
+ else
+ return -EINVAL;
+ break;
+ case PHY_TYPE_DP:
+ if (!instance_num)
+ gtr_phy->type = XPSGTR_TYPE_DP_0;
+ else if (instance_num == 1)
+ gtr_phy->type = XPSGTR_TYPE_DP_1;
+ else
+ return -EINVAL;
+ break;
+ case PHY_TYPE_PCIE:
+ if (!instance_num)
+ gtr_phy->type = XPSGTR_TYPE_PCIE_0;
+ else if (instance_num == 1)
+ gtr_phy->type = XPSGTR_TYPE_PCIE_1;
+ else if (instance_num == 2)
+ gtr_phy->type = XPSGTR_TYPE_PCIE_2;
+ else if (instance_num == 3)
+ gtr_phy->type = XPSGTR_TYPE_PCIE_3;
+ else
+ return -EINVAL;
+ break;
+ case PHY_TYPE_SGMII:
+ if (!instance_num)
+ gtr_phy->type = XPSGTR_TYPE_SGMII0;
+ else if (instance_num == 1)
+ gtr_phy->type = XPSGTR_TYPE_SGMII1;
+ else if (instance_num == 2)
+ gtr_phy->type = XPSGTR_TYPE_SGMII2;
+ else if (instance_num == 3)
+ gtr_phy->type = XPSGTR_TYPE_SGMII3;
+ else
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * xpsgtr_xlate - provides a PHY specific to a controller
+ * @dev: pointer to device
+ * @args: arguments from dts
+ *
+ * Return: pointer to kernel PHY device or error on failure
+ */
+static struct phy *xpsgtr_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct xpsgtr_dev *gtr_dev = dev_get_drvdata(dev);
+ struct xpsgtr_phy *gtr_phy = NULL;
+ struct device_node *phynode = args->np;
+ int index;
+ int i;
+ u8 controller;
+ u8 instance_num;
+
+ if (args->args_count != 4) {
+ dev_err(dev, "Invalid number of cells in 'phy' property\n");
+ return ERR_PTR(-EINVAL);
+ }
+ if (!of_device_is_available(phynode)) {
+ dev_warn(dev, "requested PHY is disabled\n");
+ return ERR_PTR(-ENODEV);
+ }
+ for (index = 0; index < of_get_child_count(dev->of_node); index++) {
+ if (phynode == gtr_dev->phys[index]->phy->dev.of_node) {
+ gtr_phy = gtr_dev->phys[index];
+ break;
+ }
+ }
+ if (!gtr_phy) {
+ dev_err(dev, "failed to find appropriate phy\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ /* get type of controller from phys */
+ controller = args->args[0];
+
+ /* get controller instance number */
+ instance_num = args->args[1];
+
+ /* Check if lane sharing is required */
+ gtr_phy->share_laneclk = args->args[2];
+
+ /* get the required clk rate for controller from phys */
+ gtr_phy->refclk_rate = args->args[3];
+
+ /* derive lane type */
+ if (xpsgtr_set_lanetype(gtr_phy, controller, instance_num) < 0) {
+ dev_err(gtr_dev->dev, "Invalid lane type\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ /* configures SSC settings for a lane */
+ if (xpsgtr_configure_lane(gtr_phy) < 0) {
+ dev_err(gtr_dev->dev, "Invalid clock rate: %d\n",
+ gtr_phy->refclk_rate);
+ return ERR_PTR(-EINVAL);
+ }
+
+ /*
+ * Check Interconnect Matrix is obeyed i.e, given lane type
+ * is allowed to operate on the lane.
+ */
+ for (i = 0; i < CONTROLLERS_PER_LANE; i++) {
+ if (icm_matrix[index][i] == gtr_phy->type)
+ return gtr_phy->phy;
+ }
+
+ /* Should not reach here */
+ return ERR_PTR(-EINVAL);
+}
+
+/**
+ * xpsgtr_phy_exit - clears previous initialized variables
+ * @phy: pointer to kernel PHY device
+ *
+ * Return: 0 on success or error value on failure
+ */
+static int xpsgtr_phy_exit(struct phy *phy)
+{
+ struct xpsgtr_phy *gtr_phy = phy_get_drvdata(phy);
+
+ if (!gtr_phy)
+ return -EINVAL;
+
+ /* As we are exiting, clear skip_phy_init flag */
+ gtr_phy->skip_phy_init = false;
+
+ return 0;
+}
+
+static struct phy_ops xpsgtr_phyops = {
+ .init = xpsgtr_phy_init,
+ .exit = xpsgtr_phy_exit,
+ .owner = THIS_MODULE,
+};
+
+/*
+ * xpsgtr_get_resets - Gets reset signals based on reset-names property
+ * @gtr_dev: pointer to structure which stores reset information
+ *
+ * Return: 0 on success or error value on failure
+ */
+static int xpsgtr_get_resets(struct xpsgtr_dev *gtr_dev)
+{
+ char *name;
+ struct reset_control *rst_temp;
+
+ gtr_dev->sata_rst = devm_reset_control_get(gtr_dev->dev, "sata_rst");
+ if (IS_ERR(gtr_dev->sata_rst)) {
+ name = "sata_rst";
+ rst_temp = gtr_dev->sata_rst;
+ goto error;
+ }
+
+ gtr_dev->dp_rst = devm_reset_control_get(gtr_dev->dev, "dp_rst");
+ if (IS_ERR(gtr_dev->dp_rst)) {
+ name = "dp_rst";
+ rst_temp = gtr_dev->dp_rst;
+ goto error;
+ }
+
+ gtr_dev->usb0_crst = devm_reset_control_get(gtr_dev->dev, "usb0_crst");
+ if (IS_ERR(gtr_dev->usb0_crst)) {
+ name = "usb0_crst";
+ rst_temp = gtr_dev->usb0_crst;
+ goto error;
+ }
+
+ gtr_dev->usb1_crst = devm_reset_control_get(gtr_dev->dev, "usb1_crst");
+ if (IS_ERR(gtr_dev->usb1_crst)) {
+ name = "usb1_crst";
+ rst_temp = gtr_dev->usb1_crst;
+ goto error;
+ }
+
+ gtr_dev->usb0_hibrst = devm_reset_control_get(gtr_dev->dev,
+ "usb0_hibrst");
+ if (IS_ERR(gtr_dev->usb0_hibrst)) {
+ name = "usb0_hibrst";
+ rst_temp = gtr_dev->usb0_hibrst;
+ goto error;
+ }
+
+ gtr_dev->usb1_hibrst = devm_reset_control_get(gtr_dev->dev,
+ "usb1_hibrst");
+ if (IS_ERR(gtr_dev->usb1_hibrst)) {
+ name = "usb1_hibrst";
+ rst_temp = gtr_dev->usb1_hibrst;
+ goto error;
+ }
+
+ gtr_dev->usb0_apbrst = devm_reset_control_get(gtr_dev->dev,
+ "usb0_apbrst");
+ if (IS_ERR(gtr_dev->usb0_apbrst)) {
+ name = "usb0_apbrst";
+ rst_temp = gtr_dev->usb0_apbrst;
+ goto error;
+ }
+
+ gtr_dev->usb1_apbrst = devm_reset_control_get(gtr_dev->dev,
+ "usb1_apbrst");
+ if (IS_ERR(gtr_dev->usb1_apbrst)) {
+ name = "usb1_apbrst";
+ rst_temp = gtr_dev->usb1_apbrst;
+ goto error;
+ }
+
+ gtr_dev->gem0_rst = devm_reset_control_get(gtr_dev->dev, "gem0_rst");
+ if (IS_ERR(gtr_dev->gem0_rst)) {
+ name = "gem0_rst";
+ rst_temp = gtr_dev->gem0_rst;
+ goto error;
+ }
+
+ gtr_dev->gem1_rst = devm_reset_control_get(gtr_dev->dev, "gem1_rst");
+ if (IS_ERR(gtr_dev->gem1_rst)) {
+ name = "gem1_rst";
+ rst_temp = gtr_dev->gem1_rst;
+ goto error;
+ }
+
+ gtr_dev->gem2_rst = devm_reset_control_get(gtr_dev->dev, "gem2_rst");
+ if (IS_ERR(gtr_dev->gem2_rst)) {
+ name = "gem2_rst";
+ rst_temp = gtr_dev->gem2_rst;
+ goto error;
+ }
+
+ gtr_dev->gem3_rst = devm_reset_control_get(gtr_dev->dev, "gem3_rst");
+ if (IS_ERR(gtr_dev->gem3_rst)) {
+ name = "gem3_rst";
+ rst_temp = gtr_dev->gem3_rst;
+ goto error;
+ }
+
+ return 0;
+error:
+ dev_err(gtr_dev->dev, "failed to get %s reset signal\n", name);
+ return PTR_ERR(rst_temp);
+}
+
+/**
+ * xpsgtr_probe - The device probe function for driver initialization.
+ * @pdev: pointer to the platform device structure.
+ *
+ * Return: 0 for success and error value on failure
+ */
+static int xpsgtr_probe(struct platform_device *pdev)
+{
+ struct device_node *child, *np = pdev->dev.of_node;
+ struct xpsgtr_dev *gtr_dev;
+ struct phy_provider *provider;
+ struct phy *phy;
+ struct resource *res;
+ int lanecount, port = 0, index = 0;
+ int err;
+
+ gtr_dev = devm_kzalloc(&pdev->dev, sizeof(*gtr_dev), GFP_KERNEL);
+ if (!gtr_dev)
+ return -ENOMEM;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "serdes");
+ gtr_dev->serdes = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(gtr_dev->serdes))
+ return PTR_ERR(gtr_dev->serdes);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "siou");
+ gtr_dev->siou = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(gtr_dev->siou))
+ return PTR_ERR(gtr_dev->siou);
+
+ lanecount = of_get_child_count(np);
+ if (lanecount > MAX_LANES || lanecount == 0)
+ return -EINVAL;
+
+ gtr_dev->phys = devm_kzalloc(&pdev->dev, sizeof(phy) * lanecount,
+ GFP_KERNEL);
+ if (!gtr_dev->phys)
+ return -ENOMEM;
+
+ gtr_dev->dev = &pdev->dev;
+ platform_set_drvdata(pdev, gtr_dev);
+ mutex_init(&gtr_dev->gtr_mutex);
+
+ if (of_device_is_compatible(np, "xlnx,zynqmp-psgtr"))
+ gtr_dev->tx_term_fix =
+ of_property_read_bool(np, "xlnx,tx-termination-fix");
+
+ err = xpsgtr_get_resets(gtr_dev);
+ if (err) {
+ dev_err(&pdev->dev, "failed to get resets: %d\n", err);
+ return err;
+ }
+
+ for_each_child_of_node(np, child) {
+ struct xpsgtr_phy *gtr_phy;
+
+ gtr_phy = devm_kzalloc(&pdev->dev, sizeof(*gtr_phy),
+ GFP_KERNEL);
+ if (!gtr_phy)
+ return -ENOMEM;
+
+ /* Assign lane number to gtr_phy instance */
+ gtr_phy->lane = index;
+
+ /* Disable lane sharing as default */
+ gtr_phy->share_laneclk = -1;
+
+ gtr_dev->phys[port] = gtr_phy;
+ phy = devm_phy_create(&pdev->dev, child, &xpsgtr_phyops);
+ if (IS_ERR(phy)) {
+ dev_err(&pdev->dev, "failed to create PHY\n");
+ return PTR_ERR(phy);
+ }
+ gtr_dev->phys[port]->phy = phy;
+ phy_set_drvdata(phy, gtr_dev->phys[port]);
+ gtr_phy->data = gtr_dev;
+ port++;
+ index++;
+ }
+ provider = devm_of_phy_provider_register(&pdev->dev, xpsgtr_xlate);
+ if (IS_ERR(provider)) {
+ dev_err(&pdev->dev, "registering provider failed\n");
+ return PTR_ERR(provider);
+ }
+ return 0;
+}
+
+#ifdef CONFIG_PM
+/**
+ * xpsgtr_suspend - The function for driver suspend.
+ * @dev: pointer to the device structure.
+ *
+ * Return: 0 for success and error value on failure
+ */
+static int xpsgtr_suspend(struct device *dev)
+{
+ struct xpsgtr_dev *gtr_dev = dev_get_drvdata(dev);
+
+ if (!gtr_dev)
+ return -EINVAL;
+
+ /* Save the snapshot ICM_CFG registers */
+ gtr_dev->saved_icm_cfg0 = readl(gtr_dev->serdes + ICM_CFG0);
+ gtr_dev->saved_icm_cfg1 = readl(gtr_dev->serdes + ICM_CFG1);
+
+ return 0;
+}
+
+/**
+ * xpsgtr_resume - The function for driver resume.
+ * @dev: pointer to the device structure.
+ *
+ * Return: 0 for success and error value on failure
+ */
+static int xpsgtr_resume(struct device *dev)
+{
+ unsigned int icm_cfg0, icm_cfg1, index;
+ bool skip_phy_init;
+ struct xpsgtr_phy *gtr_phy;
+ struct xpsgtr_dev *gtr_dev = dev_get_drvdata(dev);
+
+ if (!gtr_dev)
+ return -EINVAL;
+
+ icm_cfg0 = readl(gtr_dev->serdes + ICM_CFG0);
+ icm_cfg1 = readl(gtr_dev->serdes + ICM_CFG1);
+
+ /* Return if no gt lanes got configured before suspend */
+ if (!gtr_dev->saved_icm_cfg0 && !gtr_dev->saved_icm_cfg1)
+ return 0;
+
+ /* Check if the ICM configurations changed after suspend */
+ if (icm_cfg0 == gtr_dev->saved_icm_cfg0 &&
+ icm_cfg1 == gtr_dev->saved_icm_cfg1)
+ skip_phy_init = true;
+ else
+ skip_phy_init = false;
+
+ /* This below updates the skip_phy_init for all gtr_phy instances */
+ for (index = 0; index < of_get_child_count(dev->of_node); index++) {
+ gtr_phy = gtr_dev->phys[index];
+ gtr_phy->skip_phy_init = skip_phy_init;
+ }
+
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+/* device PM ops */
+static const struct dev_pm_ops xpsgtr_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(xpsgtr_suspend, xpsgtr_resume)
+};
+
+/* Match table for of_platform binding */
+static const struct of_device_id xpsgtr_of_match[] = {
+ { .compatible = "xlnx,zynqmp-psgtr", },
+ { .compatible = "xlnx,zynqmp-psgtr-v1.1", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, xpsgtr_of_match);
+
+static struct platform_driver xpsgtr_driver = {
+ .probe = xpsgtr_probe,
+ .driver = {
+ .name = "xilinx-psgtr",
+ .of_match_table = xpsgtr_of_match,
+ .pm = &xpsgtr_pm_ops,
+ },
+};
+
+module_platform_driver(xpsgtr_driver);
+
+MODULE_AUTHOR("Xilinx Inc.");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Xilinx ZynqMP High speed Gigabit Transceiver");
diff --git a/include/linux/phy/phy-zynqmp.h b/include/linux/phy/phy-zynqmp.h
new file mode 100644
index 0000000..8dfd73f
--- /dev/null
+++ b/include/linux/phy/phy-zynqmp.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Xilinx ZynqMP PHY header
+ *
+ * Copyright (C) 2018 Xilinx, Inc.
+ *
+ * Author: Anurag Kumar Vulisha <anuragku@xxxxxxxxxx>
+ * Author: Hyun Woo Kwon <hyunk@xxxxxxxxxx>
+ *
+ */
+
+#ifndef _PHY_ZYNQMP_H_
+#define _PHY_ZYNQMP_H_
+
+#include <linux/phy/phy.h>
+
+#if IS_ENABLED(CONFIG_PHY_XILINX_ZYNQMP)
+void xpsgtr_override_deemph(struct phy *phy, u8 plvl, u8 vlvl);
+void xpsgtr_margining_factor(struct phy *phy, u8 plvl, u8 vlvl);
+int xpsgtr_wait_pll_lock(struct phy *phy);
+int xpsgtr_usb_crst_assert(struct phy *phy);
+int xpsgtr_usb_crst_release(struct phy *phy);
+#else
+
+static inline int xpsgtr_override_deemph(struct phy *base, u8 plvl, u8 vlvl)
+{
+ return -ENODEV;
+}
+
+static inline int xpsgtr_margining_factor(struct phy *base, u8 plvl, u8 vlvl)
+{
+ return -ENODEV;
+}
+
+extern inline int xpsgtr_wait_pll_lock(struct phy *phy)
+{
+ return -ENODEV;
+}
+
+extern inline int xpsgtr_usb_crst_assert(struct phy *phy)
+{
+ return -ENODEV;
+}
+
+extern inline int xpsgtr_usb_crst_release(struct phy *phy)
+{
+ return -ENODEV;
+}
+
+#endif
+
+#endif /* _PHY_ZYNQMP_H_ */
--
2.1.1