diff --git a/drivers/phy/qualcomm/phy-qcom-ufs-i.h b/drivers/phy/qualcomm/phy-qcom-ufs-i.h index 282afa4b5561..84e3aa525a4a 100644 --- a/drivers/phy/qualcomm/phy-qcom-ufs-i.h +++ b/drivers/phy/qualcomm/phy-qcom-ufs-i.h @@ -66,11 +66,16 @@ struct ufs_qcom_phy { struct clk *ref_clk_src; struct clk *ref_clk_parent; struct clk *ref_clk; + struct clk *ref_aux_clk; bool is_ref_clk_enabled; bool is_dev_ref_clk_enabled; struct ufs_qcom_phy_vreg vdda_pll; struct ufs_qcom_phy_vreg vdda_phy; struct ufs_qcom_phy_vreg vddp_ref_clk; + + /* Number of lanes available (1 or 2) for Rx/Tx */ + u32 lanes_per_direction; + unsigned int quirks; /* diff --git a/drivers/phy/qualcomm/phy-qcom-ufs.c b/drivers/phy/qualcomm/phy-qcom-ufs.c index 9194cf470509..fbf9a2d81b7c 100644 --- a/drivers/phy/qualcomm/phy-qcom-ufs.c +++ b/drivers/phy/qualcomm/phy-qcom-ufs.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2013-2015, Linux Foundation. All rights reserved. + * Copyright (c) 2013-2019, Linux Foundation. All rights reserved. */ #include "phy-qcom-ufs-i.h" @@ -13,6 +13,8 @@ #define VDDP_REF_CLK_MIN_UV 1200000 #define VDDP_REF_CLK_MAX_UV 1200000 +#define UFS_PHY_DEFAULT_LANES_PER_DIRECTION 1 + int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy, struct ufs_qcom_phy_calibration *tbl_A, int tbl_size_A, @@ -89,13 +91,6 @@ int ufs_qcom_phy_base_init(struct platform_device *pdev, return err; } - /* "dev_ref_clk_ctrl_mem" is optional resource */ - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - "dev_ref_clk_ctrl_mem"); - phy_common->dev_ref_clk_ctrl_mmio = devm_ioremap_resource(dev, res); - if (IS_ERR((void const *)phy_common->dev_ref_clk_ctrl_mmio)) - phy_common->dev_ref_clk_ctrl_mmio = NULL; - return 0; } @@ -130,6 +125,19 @@ struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev, goto out; } + if (of_property_read_u32(dev->of_node, "lanes-per-direction", + &common_cfg->lanes_per_direction)) + common_cfg->lanes_per_direction = + UFS_PHY_DEFAULT_LANES_PER_DIRECTION; + + /* + * UFS PHY power management is managed by its parent (UFS host + * controller) hence set the no runtime PM callbacks flag + * on UFS PHY device to avoid any accidental attempt to call the + * PM callbacks for PHY device. + */ + pm_runtime_no_callbacks(&generic_phy->dev); + common_cfg->phy_spec_ops = phy_spec_ops; common_cfg->dev = dev; @@ -184,16 +192,19 @@ int ufs_qcom_phy_init_clks(struct ufs_qcom_phy *phy_common) if (of_device_is_compatible(phy_common->dev->of_node, "qcom,msm8996-ufs-phy-qmp-14nm")) goto skip_txrx_clk; + /* + * tx_iface_clk does not exist in newer version of ufs-phy HW, + * so don't return error if it is not found + */ + __ufs_qcom_phy_clk_get(phy_common->dev, "tx_iface_clk", + &phy_common->tx_iface_clk, false); - err = ufs_qcom_phy_clk_get(phy_common->dev, "tx_iface_clk", - &phy_common->tx_iface_clk); - if (err) - goto out; - - err = ufs_qcom_phy_clk_get(phy_common->dev, "rx_iface_clk", - &phy_common->rx_iface_clk); - if (err) - goto out; + /* + * rx_iface_clk does not exist in newer version of ufs-phy HW, + * so don't return error if it is not found + */ + __ufs_qcom_phy_clk_get(phy_common->dev, "rx_iface_clk", + &phy_common->rx_iface_clk, false); skip_txrx_clk: err = ufs_qcom_phy_clk_get(phy_common->dev, "ref_clk_src", @@ -208,9 +219,19 @@ skip_txrx_clk: __ufs_qcom_phy_clk_get(phy_common->dev, "ref_clk_parent", &phy_common->ref_clk_parent, false); - err = ufs_qcom_phy_clk_get(phy_common->dev, "ref_clk", - &phy_common->ref_clk); + /* + * Some platforms may not have the ON/OFF control for reference clock, + * hence this clock may be optional. + */ + __ufs_qcom_phy_clk_get(phy_common->dev, "ref_clk", + &phy_common->ref_clk, false); + /* + * "ref_aux_clk" is optional and only supported by certain + * phy versions, don't abort init if it's not found. + */ + __ufs_qcom_phy_clk_get(phy_common->dev, "ref_aux_clk", + &phy_common->ref_aux_clk, false); out: return err; } @@ -224,6 +245,14 @@ static int ufs_qcom_phy_init_vreg(struct device *dev, char prop_name[MAX_PROP_NAME]; + if (dev->of_node) { + snprintf(prop_name, MAX_PROP_NAME, "%s-supply", name); + if (!of_parse_phandle(dev->of_node, prop_name, 0)) { + dev_dbg(dev, "No vreg data found for %s\n", prop_name); + return -ENODATA; + } + } + vreg->name = name; vreg->reg = devm_regulator_get(dev, name); if (IS_ERR(vreg->reg)) { @@ -280,7 +309,7 @@ int ufs_qcom_phy_init_vregulators(struct ufs_qcom_phy *phy_common) if (err) goto out; - err = ufs_qcom_phy_init_vreg(phy_common->dev, &phy_common->vddp_ref_clk, + ufs_qcom_phy_init_vreg(phy_common->dev, &phy_common->vddp_ref_clk, "vddp-ref-clk"); out: @@ -381,16 +410,39 @@ static int ufs_qcom_phy_enable_ref_clk(struct ufs_qcom_phy *phy) } } - ret = clk_prepare_enable(phy->ref_clk); - if (ret) { - dev_err(phy->dev, "%s: ref_clk enable failed %d\n", - __func__, ret); - goto out_disable_parent; + /* + * "ref_clk" is optional clock hence make sure that clk reference + * is available before trying to enable the clock. + */ + if (phy->ref_clk) { + ret = clk_prepare_enable(phy->ref_clk); + if (ret) { + dev_err(phy->dev, "%s: ref_clk enable failed %d\n", + __func__, ret); + goto out_disable_parent; + } + } + + /* + * "ref_aux_clk" is optional clock and only supported by certain + * phy versions, hence make sure that clk reference is available + * before trying to enable the clock. + */ + if (phy->ref_aux_clk) { + ret = clk_prepare_enable(phy->ref_aux_clk); + if (ret) { + dev_err(phy->dev, "%s: ref_aux_clk enable failed %d\n", + __func__, ret); + goto out_disable_ref; + } } phy->is_ref_clk_enabled = true; goto out; +out_disable_ref: + if (phy->ref_clk) + clk_disable_unprepare(phy->ref_clk); out_disable_parent: if (phy->ref_clk_parent) clk_disable_unprepare(phy->ref_clk_parent); @@ -425,7 +477,21 @@ out: static void ufs_qcom_phy_disable_ref_clk(struct ufs_qcom_phy *phy) { if (phy->is_ref_clk_enabled) { - clk_disable_unprepare(phy->ref_clk); + /* + * "ref_aux_clk" is optional clock and only supported by + * certain phy versions, hence make sure that clk reference + * is available before trying to disable the clock. + */ + if (phy->ref_aux_clk) + clk_disable_unprepare(phy->ref_aux_clk); + + /* + * "ref_clk" is optional clock hence make sure that clk + * reference is available before trying to disable the clock. + */ + if (phy->ref_clk) + clk_disable_unprepare(phy->ref_clk); + /* * "ref_clk_parent" is optional clock hence make sure that clk * reference is available before trying to disable the clock. @@ -445,6 +511,9 @@ static int ufs_qcom_phy_enable_iface_clk(struct ufs_qcom_phy *phy) if (phy->is_iface_clk_enabled) goto out; + if (!phy->tx_iface_clk) + goto out; + ret = clk_prepare_enable(phy->tx_iface_clk); if (ret) { dev_err(phy->dev, "%s: tx_iface_clk enable failed %d\n", @@ -467,6 +536,9 @@ out: /* Turn OFF M-PHY RMMI interface clocks */ static void ufs_qcom_phy_disable_iface_clk(struct ufs_qcom_phy *phy) { + if (!phy->tx_iface_clk) + return; + if (phy->is_iface_clk_enabled) { clk_disable_unprepare(phy->tx_iface_clk); clk_disable_unprepare(phy->rx_iface_clk);