This patch adds support to msm8996 pcie phy which supports 3 ports,
Port A, Port B and Port C.
Each port is independent and connected to a pcie host controller, there is
also a common block which is shared across all the 3 ports.
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@xxxxxxxxxx>
---
drivers/phy/Kconfig | 7 +
drivers/phy/Makefile | 1 +
drivers/phy/phy-qcom-msm8996-pcie.c | 492 ++++++++++++++++++++++++++++++++++++
3 files changed, 500 insertions(+)
create mode 100644 drivers/phy/phy-qcom-msm8996-pcie.c
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index 19bff3a..8ad621c 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -344,6 +344,13 @@ config PHY_QCOM_APQ8064_SATA
depends on OF
select GENERIC_PHY
+config PHY_QCOM_MSM8996_PCIE
+ tristate "Qualcomm MSM8996 PCIE SerDes/PHY driver"
+ depends on ARCH_QCOM
+ depends on HAS_IOMEM
+ depends on OF
+ select GENERIC_PHY
+
config PHY_QCOM_IPQ806X_SATA
tristate "Qualcomm IPQ806x SATA SerDes/PHY driver"
depends on ARCH_QCOM
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index 90ae198..273b9c5 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -38,6 +38,7 @@ phy-exynos-usb2-$(CONFIG_PHY_EXYNOS5250_USB2) += phy-exynos5250-usb2.o
phy-exynos-usb2-$(CONFIG_PHY_S5PV210_USB2) += phy-s5pv210-usb2.o
obj-$(CONFIG_PHY_EXYNOS5_USBDRD) += phy-exynos5-usbdrd.o
obj-$(CONFIG_PHY_QCOM_APQ8064_SATA) += phy-qcom-apq8064-sata.o
+obj-$(CONFIG_PHY_QCOM_MSM8996_PCIE) += phy-qcom-msm8996-pcie.o
obj-$(CONFIG_PHY_ROCKCHIP_USB) += phy-rockchip-usb.o
obj-$(CONFIG_PHY_ROCKCHIP_EMMC) += phy-rockchip-emmc.o
obj-$(CONFIG_PHY_ROCKCHIP_DP) += phy-rockchip-dp.o
diff --git a/drivers/phy/phy-qcom-msm8996-pcie.c b/drivers/phy/phy-qcom-msm8996-pcie.c
new file mode 100644
index 0000000..f7d0c73
--- /dev/null
+++ b/drivers/phy/phy-qcom-msm8996-pcie.c
@@ -0,0 +1,492 @@
+/*
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/time.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/phy/phy.h>
+
+#define QSERDES_COM_BG_TIMER 0x00c
+#define QSERDES_COM_SSC_EN_CENTER 0x010
+#define QSERDES_COM_SSC_ADJ_PER1 0x014
+#define QSERDES_COM_SSC_ADJ_PER2 0x018
+#define QSERDES_COM_SSC_PER1 0x01c
+#define QSERDES_COM_SSC_PER2 0x020
+#define QSERDES_COM_SSC_STEP_SIZE1 0x024
+#define QSERDES_COM_SSC_STEP_SIZE2 0x028
+#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN 0x034
+#define QSERDES_COM_CLK_ENABLE1 0x038
+#define QSERDES_COM_SYS_CLK_CTRL 0x03c
+#define QSERDES_COM_SYSCLK_BUF_ENABLE 0x040
+#define QSERDES_COM_PLL_IVCO 0x048
+#define QSERDES_COM_LOCK_CMP1_MODE0 0x04c
+#define QSERDES_COM_LOCK_CMP2_MODE0 0x050
+#define QSERDES_COM_LOCK_CMP3_MODE0 0x054
+#define QSERDES_COM_BG_TRIM 0x070
+#define QSERDES_COM_CLK_EP_DIV 0x074
+#define QSERDES_COM_CP_CTRL_MODE0 0x078
+#define QSERDES_COM_PLL_RCTRL_MODE0 0x084
+#define QSERDES_COM_PLL_CCTRL_MODE0 0x090
+#define QSERDES_COM_SYSCLK_EN_SEL 0x0ac
+#define QSERDES_COM_RESETSM_CNTRL 0x0b4
+#define QSERDES_COM_RESTRIM_CTRL 0x0bc
+#define QSERDES_COM_RESCODE_DIV_NUM 0x0c4
+#define QSERDES_COM_LOCK_CMP_EN 0x0c8
+#define QSERDES_COM_DEC_START_MODE0 0x0d0
+#define QSERDES_COM_DIV_FRAC_START1_MODE0 0x0dc
+#define QSERDES_COM_DIV_FRAC_START2_MODE0 0x0e0
+#define QSERDES_COM_DIV_FRAC_START3_MODE0 0x0e4
+#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0 0x108
+#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0 0x10c
+#define QSERDES_COM_VCO_TUNE_CTRL 0x124
+#define QSERDES_COM_VCO_TUNE_MAP 0x128
+#define QSERDES_COM_VCO_TUNE1_MODE0 0x12c
+#define QSERDES_COM_VCO_TUNE2_MODE0 0x130
+#define QSERDES_COM_VCO_TUNE_TIMER1 0x144
+#define QSERDES_COM_VCO_TUNE_TIMER2 0x148
+#define QSERDES_COM_BG_CTRL 0x170
+#define QSERDES_COM_CLK_SELECT 0x174
+#define QSERDES_COM_HSCLK_SEL 0x178
+#define QSERDES_COM_CORECLK_DIV 0x184
+#define QSERDES_COM_CORE_CLK_EN 0x18c
+#define QSERDES_COM_C_READY_STATUS 0x190
+#define QSERDES_COM_CMN_CONFIG 0x194
+#define QSERDES_COM_SVS_MODE_CLK_SEL 0x19c
+
+#define PCIE_N_SW_RESET(n) (PCS_PORT(n) + 0x00)
+#define PCIE_N_POWER_DOWN_CONTROL(n) (PCS_PORT(n) + 0x04)
+#define PCIE_N_START_CONTROL(n) (PCS_PORT(n) + 0x08)
+#define PCIE_N_TXDEEMPH_M6DB_V0(n) (PCS_PORT(n) + 0x24)
+#define PCIE_N_TXDEEMPH_M3P5DB_V0(n) (PCS_PORT(n) + 0x28)
+#define PCIE_N_ENDPOINT_REFCLK_DRIVE(n) (PCS_PORT(n) + 0x54)
+#define PCIE_N_RX_IDLE_DTCT_CNTRL(n) (PCS_PORT(n) + 0x58)
+#define PCIE_N_POWER_STATE_CONFIG1(n) (PCS_PORT(n) + 0x60)
+#define PCIE_N_POWER_STATE_CONFIG4(n) (PCS_PORT(n) + 0x6c)
+#define PCIE_N_PWRUP_RESET_DLY_TIME_AUXCLK(n) (PCS_PORT(n) + 0xa0)
+#define PCIE_N_LP_WAKEUP_DLY_TIME_AUXCLK(n) (PCS_PORT(n) + 0xa4)
+#define PCIE_N_PLL_LOCK_CHK_DLY_TIME(n) (PCS_PORT(n) + 0xa8)
+#define PCIE_N_PCS_STATUS(n) (PCS_PORT(n) + 0x174)
+#define PCIE_N_LP_WAKEUP_DLY_TIME_AUXCLK_MSB(n) (PCS_PORT(n) + 0x1a8)
+#define PCIE_N_OSC_DTCT_ACTIONS(n) (PCS_PORT(n) + 0x1ac)
+#define PCIE_N_SIGDET_CNTRL(n) (PCS_PORT(n) + 0x1b0)
+#define PCIE_N_L1SS_WAKEUP_DLY_TIME_AUXCLK_LSB(n) (PCS_PORT(n) + 0x1dc)
+#define PCIE_N_L1SS_WAKEUP_DLY_TIME_AUXCLK_MSB(n) (PCS_PORT(n) + 0x1e0)
+
+#define PCIE_COM_SW_RESET 0x400
+#define PCIE_COM_POWER_DOWN_CONTROL 0x404
+#define PCIE_COM_START_CONTROL 0x408
+#define PCIE_COM_PCS_READY_STATUS 0x448
+
+#define PCIE_LANE_TX_BASE 0x1000
+#define PCIE_LANE_RX_BASE 0x1200
+#define PCIE_LANE_PCS_BASE 0x1400
+
+#define TX(n) (PCIE_LANE_TX_BASE + n * 0x1000)
+#define RX(n) (PCIE_LANE_RX_BASE + n * 0x1000)
+#define PCS_PORT(n) (PCIE_LANE_PCS_BASE + n * 0x1000)
+
+#define QSERDES_TX_N_RES_CODE_LANE_OFFSET(n) (TX(n) + 0x4c)
+#define QSERDES_TX_N_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN(n) (TX(n) + 0x68)
+#define QSERDES_TX_N_LANE_MODE(n) (TX(n) + 0x94)
+#define QSERDES_TX_N_RCV_DETECT_LVL_2(n) (TX(n) + 0xac)
+
+#define QSERDES_RX_N_UCDR_SO_GAIN_HALF(n) (RX(n) + 0x010)
+#define QSERDES_RX_N_UCDR_SO_GAIN(n) (RX(n) + 0x01c)
+#define QSERDES_RX_N_UCDR_SO_SATURATION_AND_ENABLE(n) (RX(n) + 0x048)
+#define QSERDES_RX_N_RX_EQU_ADAPTOR_CNTRL2(n) (RX(n) + 0x0d8)
+#define QSERDES_RX_N_RX_EQU_ADAPTOR_CNTRL3(n) (RX(n) + 0x0dc)
+#define QSERDES_RX_N_RX_EQU_ADAPTOR_CNTRL4(n) (RX(n) + 0x0e0)
+#define QSERDES_RX_N_SIGDET_ENABLES(n) (RX(n) + 0x110)
+#define QSERDES_RX_N_SIGDET_DEGLITCH_CNTRL(n) (RX(n) + 0x11c)
+#define QSERDES_RX_N_SIGDET_LVL(n) (RX(n) + 0x118)
+#define QSERDES_RX_N_RX_BAND(n) (RX(n) + 0x120)
+
+#define REFCLK_STABILIZATION_DELAY_US_MIN 1000
+#define REFCLK_STABILIZATION_DELAY_US_MAX 1005
+#define PHY_READY_TIMEOUT_COUNT 10
+#define POWER_DOWN_DELAY_US_MIN 10
+#define POWER_DOWN_DELAY_US_MAX 11
+
+struct phy_msm8996_priv;
+
+struct phy_msm8996_desc {
+ struct phy *phy;
+ unsigned int index;
+ struct reset_control *phy_rstc;
+ struct phy_msm8996_priv *priv;
+};
+
+struct phy_msm8996_priv {
+ void __iomem *base;
+ struct clk *cfg_clk;
+ struct clk *aux_clk;
+ struct clk *ref_clk;
+ struct clk *ref_clk_src;
+ struct reset_control *phy_rstc, *phycom_rstc;
+ struct device *dev;
+ unsigned int nphys;
+ int init_count;
+ struct mutex phy_mutex;
+ struct phy_msm8996_desc **phys;
+};
+
+static struct phy *phy_msm8996_pcie_phy_xlate(struct device *dev,
+ struct of_phandle_args *args)
+{
+ struct phy_msm8996_priv *priv = dev_get_drvdata(dev);
+ int i;
+
+ if (WARN_ON(args->args[0] >= priv->nphys))
+ return ERR_PTR(-ENODEV);
+
+ for (i = 0; i < priv->nphys; i++) {
+ if (priv->phys[i]->index == args->args[0])
+ break;
+ }
+
+ if (i == priv->nphys)
+ return ERR_PTR(-ENODEV);
+
+ return priv->phys[i]->phy;
+}
+
+static int pcie_phy_is_ready(struct phy *phy)
+{
+ struct phy_msm8996_desc *phydesc = phy_get_drvdata(phy);
+ struct phy_msm8996_priv *priv = phydesc->priv;
+ void __iomem *base = priv->base;
+ int retries = 0;
+
+ do {
+ if ((readl_relaxed(base + PCIE_COM_PCS_READY_STATUS) & 0x1))
+ return 0;
+ retries++;
+ usleep_range(REFCLK_STABILIZATION_DELAY_US_MIN,
+ REFCLK_STABILIZATION_DELAY_US_MAX);
+ } while (retries < PHY_READY_TIMEOUT_COUNT);
+
+ dev_err(priv->dev, "PHY Failed to come up\n");
+
+ return -EBUSY;
+}
+
+static int qcom_msm8996_phy_common_power_off(struct phy *phy)
+{
+ struct phy_msm8996_desc *phydesc = phy_get_drvdata(phy);
+ struct phy_msm8996_priv *priv = phydesc->priv;
+ void __iomem *base = priv->base;
+
+ mutex_lock(&priv->phy_mutex);
+ if (--priv->init_count) {
+ mutex_unlock(&priv->phy_mutex);
+ return 0;
+ }
+
+ writel_relaxed(0x01, base + PCIE_COM_SW_RESET);
+ writel_relaxed(0x0, base + PCIE_COM_POWER_DOWN_CONTROL);
+
+ reset_control_assert(priv->phy_rstc);
+ reset_control_assert(priv->phycom_rstc);
+ clk_disable_unprepare(priv->cfg_clk);
+ clk_disable_unprepare(priv->aux_clk);
+ clk_disable_unprepare(priv->ref_clk);
+ clk_disable_unprepare(priv->ref_clk_src);
+
+ mutex_unlock(&priv->phy_mutex);
+
+ return 0;
+}
+
+static int qcom_msm8996_phy_common_power_on(struct phy *phy)
+{
+ struct phy_msm8996_desc *phydesc = phy_get_drvdata(phy);
+ struct phy_msm8996_priv *priv = phydesc->priv;
+ void __iomem *base = priv->base;
+ int ret;
+
+ mutex_lock(&priv->phy_mutex);
+ if (priv->init_count++) {
+ mutex_unlock(&priv->phy_mutex);
+ return 0;
+ }
+
+ clk_prepare_enable(priv->cfg_clk);
+ clk_prepare_enable(priv->aux_clk);
+ clk_prepare_enable(priv->ref_clk);
+ clk_prepare_enable(priv->ref_clk_src);
+
+ reset_control_deassert(priv->phy_rstc);
+ reset_control_deassert(priv->phycom_rstc);
+
+ writel_relaxed(0x01, base + PCIE_COM_POWER_DOWN_CONTROL);
+ writel_relaxed(0x1c, base + QSERDES_COM_BIAS_EN_CLKBUFLR_EN);
+ writel_relaxed(0x10, base + QSERDES_COM_CLK_ENABLE1);
+ writel_relaxed(0x33, base + QSERDES_COM_CLK_SELECT);
+ writel_relaxed(0x06, base + QSERDES_COM_CMN_CONFIG);
+ writel_relaxed(0x42, base + QSERDES_COM_LOCK_CMP_EN);
+ writel_relaxed(0x00, base + QSERDES_COM_VCO_TUNE_MAP);
+ writel_relaxed(0xff, base + QSERDES_COM_VCO_TUNE_TIMER1);
+ writel_relaxed(0x1f, base + QSERDES_COM_VCO_TUNE_TIMER2);
+ writel_relaxed(0x01, base + QSERDES_COM_HSCLK_SEL);
+ writel_relaxed(0x01, base + QSERDES_COM_SVS_MODE_CLK_SEL);
+ writel_relaxed(0x00, base + QSERDES_COM_CORE_CLK_EN);
+ writel_relaxed(0x0a, base + QSERDES_COM_CORECLK_DIV);
+ writel_relaxed(0x09, base + QSERDES_COM_BG_TIMER);
+ writel_relaxed(0x82, base + QSERDES_COM_DEC_START_MODE0);
+ writel_relaxed(0x03, base + QSERDES_COM_DIV_FRAC_START3_MODE0);
+ writel_relaxed(0x55, base + QSERDES_COM_DIV_FRAC_START2_MODE0);
+ writel_relaxed(0x55, base + QSERDES_COM_DIV_FRAC_START1_MODE0);
+ writel_relaxed(0x00, base + QSERDES_COM_LOCK_CMP3_MODE0);
+ writel_relaxed(0x1a, base + QSERDES_COM_LOCK_CMP2_MODE0);
+ writel_relaxed(0x0a, base + QSERDES_COM_LOCK_CMP1_MODE0);
+ writel_relaxed(0x33, base + QSERDES_COM_CLK_SELECT);
+ writel_relaxed(0x02, base + QSERDES_COM_SYS_CLK_CTRL);
+ writel_relaxed(0x1f, base + QSERDES_COM_SYSCLK_BUF_ENABLE);
+ writel_relaxed(0x04, base + QSERDES_COM_SYSCLK_EN_SEL);
+ writel_relaxed(0x0b, base + QSERDES_COM_CP_CTRL_MODE0);
+ writel_relaxed(0x16, base + QSERDES_COM_PLL_RCTRL_MODE0);
+ writel_relaxed(0x28, base + QSERDES_COM_PLL_CCTRL_MODE0);
+ writel_relaxed(0x00, base + QSERDES_COM_INTEGLOOP_GAIN1_MODE0);
+ writel_relaxed(0x80, base + QSERDES_COM_INTEGLOOP_GAIN0_MODE0);
+ writel_relaxed(0x01, base + QSERDES_COM_SSC_EN_CENTER);
+ writel_relaxed(0x31, base + QSERDES_COM_SSC_PER1);
+ writel_relaxed(0x01, base + QSERDES_COM_SSC_PER2);
+ writel_relaxed(0x02, base + QSERDES_COM_SSC_ADJ_PER1);
+ writel_relaxed(0x00, base + QSERDES_COM_SSC_ADJ_PER2);
+ writel_relaxed(0x2f, base + QSERDES_COM_SSC_STEP_SIZE1);
+ writel_relaxed(0x19, base + QSERDES_COM_SSC_STEP_SIZE2);
+ writel_relaxed(0x15, base + QSERDES_COM_RESCODE_DIV_NUM);
+ writel_relaxed(0x0f, base + QSERDES_COM_BG_TRIM);
+ writel_relaxed(0x0f, base + QSERDES_COM_PLL_IVCO);
+ writel_relaxed(0x19, base + QSERDES_COM_CLK_EP_DIV);
+ writel_relaxed(0x10, base + QSERDES_COM_CLK_ENABLE1);
+ writel_relaxed(0x00, base + QSERDES_COM_HSCLK_SEL);
+ writel_relaxed(0x40, base + QSERDES_COM_RESCODE_DIV_NUM);
+ writel_relaxed(0x00, base + PCIE_COM_SW_RESET);
+ writel_relaxed(0x03, base + PCIE_COM_START_CONTROL);
+
+ ret = pcie_phy_is_ready(phy);
+
+ mutex_unlock(&priv->phy_mutex);
+
+ return ret;
+}
+
+static int qcom_msm8996_pcie_phy_power_on(struct phy *phy)
+{
+ struct phy_msm8996_desc *phydesc = phy_get_drvdata(phy);
+ struct phy_msm8996_priv *priv = phydesc->priv;
+ void __iomem *base = priv->base;
+ int id = phydesc->index;
+ int err;
+
+ err = qcom_msm8996_phy_common_power_on(phy);
+ if (err) {
+ dev_err(priv->dev, "PCIE phy power on failed\n");
+ return err;
+ }
+
+ reset_control_deassert(phydesc->phy_rstc);
+
+ writel_relaxed(0x45, base +
+ QSERDES_TX_N_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN(id));
+ writel_relaxed(0x06, base + QSERDES_TX_N_LANE_MODE(id));
+ writel_relaxed(0x1c, base + QSERDES_RX_N_SIGDET_ENABLES(id));
+ writel_relaxed(0x17, base + QSERDES_RX_N_SIGDET_LVL(id));
+ writel_relaxed(0x01, base + QSERDES_RX_N_RX_EQU_ADAPTOR_CNTRL2(id));
+ writel_relaxed(0x00, base + QSERDES_RX_N_RX_EQU_ADAPTOR_CNTRL3(id));
+ writel_relaxed(0xdb, base + QSERDES_RX_N_RX_EQU_ADAPTOR_CNTRL4(id));
+ writel_relaxed(0x18, base + QSERDES_RX_N_RX_BAND(id));
+ writel_relaxed(0x04, base + QSERDES_RX_N_UCDR_SO_GAIN(id));
+ writel_relaxed(0x04, base + QSERDES_RX_N_UCDR_SO_GAIN_HALF(id));
+ writel_relaxed(0x4c, base + PCIE_N_RX_IDLE_DTCT_CNTRL(id));
+ writel_relaxed(0x00, base + PCIE_N_PWRUP_RESET_DLY_TIME_AUXCLK(id));
+ writel_relaxed(0x01, base + PCIE_N_LP_WAKEUP_DLY_TIME_AUXCLK(id));
+ writel_relaxed(0x05, base + PCIE_N_PLL_LOCK_CHK_DLY_TIME(id));
+ writel_relaxed(0x4b, base +
+ QSERDES_RX_N_UCDR_SO_SATURATION_AND_ENABLE(id));
+ writel_relaxed(0x14, base + QSERDES_RX_N_SIGDET_DEGLITCH_CNTRL(id));
+ writel_relaxed(0x05, base + PCIE_N_ENDPOINT_REFCLK_DRIVE(id));
+ writel_relaxed(0x02, base + PCIE_N_POWER_DOWN_CONTROL(id));
+ writel_relaxed(0x00, base + PCIE_N_POWER_STATE_CONFIG4(id));
+ writel_relaxed(0xa3, base + PCIE_N_POWER_STATE_CONFIG1(id));
+ writel_relaxed(0x19, base + QSERDES_RX_N_SIGDET_LVL(id));
+ writel_relaxed(0x0e, base + PCIE_N_TXDEEMPH_M3P5DB_V0(id));
+ writel_relaxed(0x03, base + PCIE_N_POWER_DOWN_CONTROL(id));
+
+ usleep_range(POWER_DOWN_DELAY_US_MIN, POWER_DOWN_DELAY_US_MAX);
+
+ writel_relaxed(0x00, base + PCIE_N_SW_RESET(id));
+ writel_relaxed(0x0a, base + PCIE_N_START_CONTROL(id));
+
+ return 0;
+}
+
+static int qcom_msm8996_pcie_phy_power_off(struct phy *phy)
+{
+ struct phy_msm8996_desc *phydesc = phy_get_drvdata(phy);
+ struct phy_msm8996_priv *priv = phydesc->priv;
+ void __iomem *base = priv->base;
+ int id = phydesc->index;
+ int err;
+
+ writel_relaxed(0x01, base + PCIE_N_SW_RESET(id));
+ writel_relaxed(0x0, base + PCIE_N_POWER_DOWN_CONTROL(id));
+
+ err = qcom_msm8996_phy_common_power_off(phy);
+ if (err < 0)
+ return err;
+
+ reset_control_assert(phydesc->phy_rstc);
+
+ return err;
+}
+
+static const struct phy_ops qcom_msm8996_pcie_phy_ops = {
+ .power_on = qcom_msm8996_pcie_phy_power_on,
+ .power_off = qcom_msm8996_pcie_phy_power_off,
+ .owner = THIS_MODULE,
+};
+
+
+static int qcom_msm8996_pcie_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *child;
+ struct phy *phy;
+ struct phy_provider *phy_provider;
+ struct phy_msm8996_priv *priv;
+ struct resource *res;
+ int ret;
+ u32 phy_id;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -EINVAL;
+
+ priv->base = devm_ioremap(dev, res->start, resource_size(res));
+ if (!priv->base)
+ return -ENOMEM;
+
+ priv->cfg_clk = devm_clk_get(dev, "cfg");
+ if (IS_ERR(priv->cfg_clk))
+ return PTR_ERR(priv->cfg_clk);
+
+ priv->aux_clk = devm_clk_get(dev, "aux");
+ if (IS_ERR(priv->aux_clk))
+ return PTR_ERR(priv->aux_clk);
+
+ priv->ref_clk = devm_clk_get(dev, "ref_clk");
+ if (IS_ERR(priv->ref_clk))
+ return PTR_ERR(priv->ref_clk);
+
+ priv->ref_clk_src = devm_clk_get(dev, "ref_clk_src");
+ if (IS_ERR(priv->ref_clk_src))
+ return PTR_ERR(priv->ref_clk_src);
+
+ priv->nphys = of_get_child_count(dev->of_node);
+ if (priv->nphys == 0)
+ return -ENODEV;
+
+ priv->phys = devm_kcalloc(dev, priv->nphys, sizeof(*priv->phys),
+ GFP_KERNEL);
+ if (!priv->phys)
+ return -ENOMEM;
+
+ priv->phy_rstc = reset_control_get(dev, "phy");
+ if (IS_ERR(priv->phy_rstc))
+ return PTR_ERR(priv->phy_rstc);
+
+ priv->phycom_rstc = reset_control_get(dev, "common");
+ if (IS_ERR(priv->phycom_rstc))
+ return PTR_ERR(priv->phycom_rstc);
+
+ mutex_init(&priv->phy_mutex);
+
+ dev_set_drvdata(dev, priv);
+
+ for_each_available_child_of_node(dev->of_node, child) {
+ struct phy_msm8996_desc *phy_desc;
+
+ if (of_property_read_u32(child, "reg", &phy_id)) {
+ dev_err(dev, "missing reg property in node %s\n",
+ child->name);
+ ret = -EINVAL;
+ goto put_child;
+ }
+
+ phy_desc = devm_kzalloc(dev, sizeof(*phy_desc), GFP_KERNEL);
+ if (!phy_desc) {
+ ret = -ENOMEM;
+ goto put_child;
+ }
+
+ phy = devm_phy_create(dev, NULL, &qcom_msm8996_pcie_phy_ops);
+ if (IS_ERR(phy)) {
+ dev_err(dev, "failed to create PHY %d\n", phy_id);
+ ret = PTR_ERR(phy);
+ goto put_child;
+ }
+
+ phy_desc->phy_rstc = of_reset_control_get(child, "phy");
+ if (IS_ERR(phy_desc->phy_rstc)) {
+ ret = PTR_ERR(phy_desc->phy_rstc);
+ goto put_child;
+ }
+
+ phy_desc->phy = phy;
+ phy_desc->priv = priv;
+ phy_desc->index = phy_id;
+ phy_set_drvdata(phy, phy_desc);
+ priv->phys[phy_id] = phy_desc;
+ }
+
+ phy_provider =
+ devm_of_phy_provider_register(dev, phy_msm8996_pcie_phy_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+
+put_child:
+ of_node_put(child);
+ return ret;
+}
+
+static const struct of_device_id qcom_msm8996_pcie_phy_of_match[] = {
+ { .compatible = "qcom,msm8996-pcie-phy" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, qcom_msm8996_pcie_phy_of_match);
+
+static struct platform_driver qcom_msm8996_pcie_phy_driver = {
+ .probe = qcom_msm8996_pcie_phy_probe,
+ .driver = {
+ .name = "qcom-msm8996-pcie-phy",
+ .of_match_table = qcom_msm8996_pcie_phy_of_match,
+ }
+};
+module_platform_driver(qcom_msm8996_pcie_phy_driver);
+
+MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@xxxxxx>");
+MODULE_DESCRIPTION("QCOM MSM899 PCIE PHY driver");
+MODULE_LICENSE("GPL v2");