diff mbox

[2/2] phy: msm8996-pcie-phy: Add support to msm8996 pcie phy

Message ID 1473245733-17260-3-git-send-email-srinivas.kandagatla@linaro.org
State New
Headers show

Commit Message

Srinivas Kandagatla Sept. 7, 2016, 10:55 a.m. UTC
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@linaro.org>

---
 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

-- 
2.7.4

--
To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

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@st.com>");
+MODULE_DESCRIPTION("QCOM MSM899 PCIE PHY driver");
+MODULE_LICENSE("GPL v2");