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"); -- 2.7.4 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html