2015-01-15 21:32:35 +07:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2013-2015, 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 "phy-qcom-ufs-i.h"
|
|
|
|
|
|
|
|
#define MAX_PROP_NAME 32
|
|
|
|
#define VDDA_PHY_MIN_UV 1000000
|
|
|
|
#define VDDA_PHY_MAX_UV 1000000
|
|
|
|
#define VDDA_PLL_MIN_UV 1800000
|
|
|
|
#define VDDA_PLL_MAX_UV 1800000
|
|
|
|
#define VDDP_REF_CLK_MIN_UV 1200000
|
|
|
|
#define VDDP_REF_CLK_MAX_UV 1200000
|
|
|
|
|
|
|
|
int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
|
|
|
|
struct ufs_qcom_phy_calibration *tbl_A,
|
|
|
|
int tbl_size_A,
|
|
|
|
struct ufs_qcom_phy_calibration *tbl_B,
|
|
|
|
int tbl_size_B, bool is_rate_B)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (!tbl_A) {
|
|
|
|
dev_err(ufs_qcom_phy->dev, "%s: tbl_A is NULL", __func__);
|
|
|
|
ret = EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < tbl_size_A; i++)
|
|
|
|
writel_relaxed(tbl_A[i].cfg_value,
|
|
|
|
ufs_qcom_phy->mmio + tbl_A[i].reg_offset);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* In case we would like to work in rate B, we need
|
|
|
|
* to override a registers that were configured in rate A table
|
|
|
|
* with registers of rate B table.
|
|
|
|
* table.
|
|
|
|
*/
|
|
|
|
if (is_rate_B) {
|
|
|
|
if (!tbl_B) {
|
|
|
|
dev_err(ufs_qcom_phy->dev, "%s: tbl_B is NULL",
|
|
|
|
__func__);
|
|
|
|
ret = EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < tbl_size_B; i++)
|
|
|
|
writel_relaxed(tbl_B[i].cfg_value,
|
|
|
|
ufs_qcom_phy->mmio + tbl_B[i].reg_offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* flush buffered writes */
|
|
|
|
mb();
|
|
|
|
|
|
|
|
out:
|
|
|
|
return ret;
|
|
|
|
}
|
2015-03-23 10:54:50 +07:00
|
|
|
EXPORT_SYMBOL_GPL(ufs_qcom_phy_calibrate);
|
2015-01-15 21:32:35 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* This assumes the embedded phy structure inside generic_phy is of type
|
|
|
|
* struct ufs_qcom_phy. In order to function properly it's crucial
|
|
|
|
* to keep the embedded struct "struct ufs_qcom_phy common_cfg"
|
|
|
|
* as the first inside generic_phy.
|
|
|
|
*/
|
|
|
|
struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy)
|
|
|
|
{
|
|
|
|
return (struct ufs_qcom_phy *)phy_get_drvdata(generic_phy);
|
|
|
|
}
|
2015-03-23 10:54:50 +07:00
|
|
|
EXPORT_SYMBOL_GPL(get_ufs_qcom_phy);
|
2015-01-15 21:32:35 +07:00
|
|
|
|
|
|
|
static
|
|
|
|
int ufs_qcom_phy_base_init(struct platform_device *pdev,
|
|
|
|
struct ufs_qcom_phy *phy_common)
|
|
|
|
{
|
|
|
|
struct device *dev = &pdev->dev;
|
|
|
|
struct resource *res;
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_mem");
|
|
|
|
phy_common->mmio = devm_ioremap_resource(dev, res);
|
|
|
|
if (IS_ERR((void const *)phy_common->mmio)) {
|
|
|
|
err = PTR_ERR((void const *)phy_common->mmio);
|
|
|
|
phy_common->mmio = NULL;
|
|
|
|
dev_err(dev, "%s: ioremap for phy_mem resource failed %d\n",
|
|
|
|
__func__, err);
|
2015-03-23 11:08:18 +07:00
|
|
|
return err;
|
2015-01-15 21:32:35 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* "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);
|
2015-03-23 11:08:18 +07:00
|
|
|
if (IS_ERR((void const *)phy_common->dev_ref_clk_ctrl_mmio))
|
2015-01-15 21:32:35 +07:00
|
|
|
phy_common->dev_ref_clk_ctrl_mmio = NULL;
|
|
|
|
|
2015-03-23 11:08:18 +07:00
|
|
|
return 0;
|
2015-01-15 21:32:35 +07:00
|
|
|
}
|
|
|
|
|
2016-11-08 17:07:46 +07:00
|
|
|
struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev,
|
|
|
|
struct ufs_qcom_phy *common_cfg,
|
|
|
|
const struct phy_ops *ufs_qcom_phy_gen_ops,
|
|
|
|
struct ufs_qcom_phy_specific_ops *phy_spec_ops)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
struct device *dev = &pdev->dev;
|
|
|
|
struct phy *generic_phy = NULL;
|
|
|
|
struct phy_provider *phy_provider;
|
|
|
|
|
|
|
|
err = ufs_qcom_phy_base_init(pdev, common_cfg);
|
|
|
|
if (err) {
|
|
|
|
dev_err(dev, "%s: phy base init failed %d\n", __func__, err);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
|
|
|
|
if (IS_ERR(phy_provider)) {
|
|
|
|
err = PTR_ERR(phy_provider);
|
|
|
|
dev_err(dev, "%s: failed to register phy %d\n", __func__, err);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
generic_phy = devm_phy_create(dev, NULL, ufs_qcom_phy_gen_ops);
|
|
|
|
if (IS_ERR(generic_phy)) {
|
|
|
|
err = PTR_ERR(generic_phy);
|
|
|
|
dev_err(dev, "%s: failed to create phy %d\n", __func__, err);
|
|
|
|
generic_phy = NULL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
common_cfg->phy_spec_ops = phy_spec_ops;
|
|
|
|
common_cfg->dev = dev;
|
|
|
|
|
|
|
|
out:
|
|
|
|
return generic_phy;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(ufs_qcom_phy_generic_probe);
|
|
|
|
|
phy: ufs-qcom: Refactor all init steps into phy_poweron
The phy code was using implicit sequencing between the PHY driver
and the UFS driver to implement certain hardware requirements.
Specifically, the PHY reset register in the UFS controller needs
to be deasserted before serdes start occurs in the PHY.
Before this change, the code was doing this by utilizing the two
phy callbacks, phy_init() and phy_poweron(), as "init step 1" and
"init step 2", where the UFS driver would deassert reset between
these two steps.
This makes it challenging to power off the regulators in suspend,
as regulators are initialized in init, not in poweron(), but only
poweroff() is called during suspend, not exit().
For UFS, move the actual firing up of the PHY to phy_poweron() and
phy_poweroff() callbacks, rather than init()/exit(). UFS calls
phy_poweroff() during suspend, so now all clocks and regulators for
the phy can be powered down during suspend.
QMP is a little tricky because the PHY is also shared with PCIe and
USB3, which have their own definitions for init() and poweron(). Rename
the meaty functions to _enable() and _disable() to disentangle from the
PHY core names, and then create two different ops structures: one for
UFS and one for the other PHY types.
In phy-qcom-ufs, remove the 'is_powered_on' and 'is_started' guards,
as the generic PHY code does the reference counting. The
14/20nm-specific init functions get collapsed into the generic power_on()
function, with the addition of a calibrate() callback specific to 14/20nm.
Signed-off-by: Evan Green <evgreen@chromium.org>
Reviewed-by: Stephen Boyd <swboyd@chromium.org>
Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
2019-03-22 00:18:00 +07:00
|
|
|
static int ufs_qcom_phy_get_reset(struct ufs_qcom_phy *phy_common)
|
2019-03-22 00:17:59 +07:00
|
|
|
{
|
|
|
|
struct reset_control *reset;
|
|
|
|
|
|
|
|
if (phy_common->ufs_reset)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
reset = devm_reset_control_get_exclusive_by_index(phy_common->dev, 0);
|
|
|
|
if (IS_ERR(reset))
|
|
|
|
return PTR_ERR(reset);
|
|
|
|
|
|
|
|
phy_common->ufs_reset = reset;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-11-08 17:07:42 +07:00
|
|
|
static int __ufs_qcom_phy_clk_get(struct device *dev,
|
2015-01-15 21:32:35 +07:00
|
|
|
const char *name, struct clk **clk_out, bool err_print)
|
|
|
|
{
|
|
|
|
struct clk *clk;
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
clk = devm_clk_get(dev, name);
|
|
|
|
if (IS_ERR(clk)) {
|
|
|
|
err = PTR_ERR(clk);
|
|
|
|
if (err_print)
|
|
|
|
dev_err(dev, "failed to get %s err %d", name, err);
|
|
|
|
} else {
|
|
|
|
*clk_out = clk;
|
|
|
|
}
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2016-11-08 17:07:42 +07:00
|
|
|
static int ufs_qcom_phy_clk_get(struct device *dev,
|
2015-01-15 21:32:35 +07:00
|
|
|
const char *name, struct clk **clk_out)
|
|
|
|
{
|
2016-11-08 17:07:42 +07:00
|
|
|
return __ufs_qcom_phy_clk_get(dev, name, clk_out, true);
|
2015-01-15 21:32:35 +07:00
|
|
|
}
|
|
|
|
|
2016-11-08 17:07:42 +07:00
|
|
|
int ufs_qcom_phy_init_clks(struct ufs_qcom_phy *phy_common)
|
2015-01-15 21:32:35 +07:00
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
2016-11-08 17:07:44 +07:00
|
|
|
if (of_device_is_compatible(phy_common->dev->of_node,
|
|
|
|
"qcom,msm8996-ufs-phy-qmp-14nm"))
|
|
|
|
goto skip_txrx_clk;
|
|
|
|
|
2016-11-08 17:07:42 +07:00
|
|
|
err = ufs_qcom_phy_clk_get(phy_common->dev, "tx_iface_clk",
|
2015-01-15 21:32:35 +07:00
|
|
|
&phy_common->tx_iface_clk);
|
|
|
|
if (err)
|
|
|
|
goto out;
|
|
|
|
|
2016-11-08 17:07:42 +07:00
|
|
|
err = ufs_qcom_phy_clk_get(phy_common->dev, "rx_iface_clk",
|
2015-01-15 21:32:35 +07:00
|
|
|
&phy_common->rx_iface_clk);
|
|
|
|
if (err)
|
|
|
|
goto out;
|
|
|
|
|
2017-01-27 15:10:19 +07:00
|
|
|
skip_txrx_clk:
|
2016-11-08 17:07:42 +07:00
|
|
|
err = ufs_qcom_phy_clk_get(phy_common->dev, "ref_clk_src",
|
2015-01-15 21:32:35 +07:00
|
|
|
&phy_common->ref_clk_src);
|
|
|
|
if (err)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* "ref_clk_parent" is optional hence don't abort init if it's not
|
|
|
|
* found.
|
|
|
|
*/
|
2016-11-08 17:07:42 +07:00
|
|
|
__ufs_qcom_phy_clk_get(phy_common->dev, "ref_clk_parent",
|
2015-01-15 21:32:35 +07:00
|
|
|
&phy_common->ref_clk_parent, false);
|
|
|
|
|
2016-11-08 17:07:42 +07:00
|
|
|
err = ufs_qcom_phy_clk_get(phy_common->dev, "ref_clk",
|
2015-01-15 21:32:35 +07:00
|
|
|
&phy_common->ref_clk);
|
|
|
|
|
|
|
|
out:
|
|
|
|
return err;
|
|
|
|
}
|
2015-03-23 10:54:50 +07:00
|
|
|
EXPORT_SYMBOL_GPL(ufs_qcom_phy_init_clks);
|
2015-01-15 21:32:35 +07:00
|
|
|
|
2017-01-23 04:17:47 +07:00
|
|
|
static int ufs_qcom_phy_init_vreg(struct device *dev,
|
|
|
|
struct ufs_qcom_phy_vreg *vreg,
|
|
|
|
const char *name)
|
2015-01-15 21:32:35 +07:00
|
|
|
{
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
char prop_name[MAX_PROP_NAME];
|
|
|
|
|
2017-01-23 04:17:46 +07:00
|
|
|
vreg->name = name;
|
2015-01-15 21:32:35 +07:00
|
|
|
vreg->reg = devm_regulator_get(dev, name);
|
|
|
|
if (IS_ERR(vreg->reg)) {
|
|
|
|
err = PTR_ERR(vreg->reg);
|
2017-01-23 04:17:47 +07:00
|
|
|
dev_err(dev, "failed to get %s, %d\n", name, err);
|
2015-01-15 21:32:35 +07:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dev->of_node) {
|
|
|
|
snprintf(prop_name, MAX_PROP_NAME, "%s-max-microamp", name);
|
|
|
|
err = of_property_read_u32(dev->of_node,
|
|
|
|
prop_name, &vreg->max_uA);
|
|
|
|
if (err && err != -EINVAL) {
|
|
|
|
dev_err(dev, "%s: failed to read %s\n",
|
|
|
|
__func__, prop_name);
|
|
|
|
goto out;
|
|
|
|
} else if (err == -EINVAL || !vreg->max_uA) {
|
|
|
|
if (regulator_count_voltages(vreg->reg) > 0) {
|
|
|
|
dev_err(dev, "%s: %s is mandatory\n",
|
|
|
|
__func__, prop_name);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
err = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!strcmp(name, "vdda-pll")) {
|
|
|
|
vreg->max_uV = VDDA_PLL_MAX_UV;
|
|
|
|
vreg->min_uV = VDDA_PLL_MIN_UV;
|
|
|
|
} else if (!strcmp(name, "vdda-phy")) {
|
|
|
|
vreg->max_uV = VDDA_PHY_MAX_UV;
|
|
|
|
vreg->min_uV = VDDA_PHY_MIN_UV;
|
|
|
|
} else if (!strcmp(name, "vddp-ref-clk")) {
|
|
|
|
vreg->max_uV = VDDP_REF_CLK_MAX_UV;
|
|
|
|
vreg->min_uV = VDDP_REF_CLK_MIN_UV;
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2016-11-08 17:07:46 +07:00
|
|
|
int ufs_qcom_phy_init_vregulators(struct ufs_qcom_phy *phy_common)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = ufs_qcom_phy_init_vreg(phy_common->dev, &phy_common->vdda_pll,
|
|
|
|
"vdda-pll");
|
|
|
|
if (err)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
err = ufs_qcom_phy_init_vreg(phy_common->dev, &phy_common->vdda_phy,
|
|
|
|
"vdda-phy");
|
|
|
|
|
|
|
|
if (err)
|
|
|
|
goto out;
|
|
|
|
|
2017-01-23 04:17:47 +07:00
|
|
|
err = ufs_qcom_phy_init_vreg(phy_common->dev, &phy_common->vddp_ref_clk,
|
|
|
|
"vddp-ref-clk");
|
|
|
|
|
2016-11-08 17:07:46 +07:00
|
|
|
out:
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(ufs_qcom_phy_init_vregulators);
|
|
|
|
|
2016-11-08 17:07:42 +07:00
|
|
|
static int ufs_qcom_phy_cfg_vreg(struct device *dev,
|
2015-01-15 21:32:35 +07:00
|
|
|
struct ufs_qcom_phy_vreg *vreg, bool on)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
struct regulator *reg = vreg->reg;
|
|
|
|
const char *name = vreg->name;
|
|
|
|
int min_uV;
|
|
|
|
int uA_load;
|
|
|
|
|
|
|
|
if (regulator_count_voltages(reg) > 0) {
|
|
|
|
min_uV = on ? vreg->min_uV : 0;
|
|
|
|
ret = regulator_set_voltage(reg, min_uV, vreg->max_uV);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(dev, "%s: %s set voltage failed, err=%d\n",
|
|
|
|
__func__, name, ret);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
uA_load = on ? vreg->max_uA : 0;
|
2015-03-10 09:44:41 +07:00
|
|
|
ret = regulator_set_load(reg, uA_load);
|
2015-01-15 21:32:35 +07:00
|
|
|
if (ret >= 0) {
|
|
|
|
/*
|
2015-03-10 09:44:41 +07:00
|
|
|
* regulator_set_load() returns new regulator
|
2015-01-15 21:32:35 +07:00
|
|
|
* mode upon success.
|
|
|
|
*/
|
|
|
|
ret = 0;
|
|
|
|
} else {
|
|
|
|
dev_err(dev, "%s: %s set optimum mode(uA_load=%d) failed, err=%d\n",
|
|
|
|
__func__, name, uA_load, ret);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
out:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-11-08 17:07:42 +07:00
|
|
|
static int ufs_qcom_phy_enable_vreg(struct device *dev,
|
2015-01-15 21:32:35 +07:00
|
|
|
struct ufs_qcom_phy_vreg *vreg)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (!vreg || vreg->enabled)
|
|
|
|
goto out;
|
|
|
|
|
2016-11-08 17:07:42 +07:00
|
|
|
ret = ufs_qcom_phy_cfg_vreg(dev, vreg, true);
|
2015-01-15 21:32:35 +07:00
|
|
|
if (ret) {
|
|
|
|
dev_err(dev, "%s: ufs_qcom_phy_cfg_vreg() failed, err=%d\n",
|
|
|
|
__func__, ret);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = regulator_enable(vreg->reg);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(dev, "%s: enable failed, err=%d\n",
|
|
|
|
__func__, ret);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
vreg->enabled = true;
|
|
|
|
out:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-11-08 17:07:48 +07:00
|
|
|
static int ufs_qcom_phy_enable_ref_clk(struct ufs_qcom_phy *phy)
|
2015-01-15 21:32:35 +07:00
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (phy->is_ref_clk_enabled)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* reference clock is propagated in a daisy-chained manner from
|
|
|
|
* source to phy, so ungate them at each stage.
|
|
|
|
*/
|
|
|
|
ret = clk_prepare_enable(phy->ref_clk_src);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(phy->dev, "%s: ref_clk_src enable failed %d\n",
|
|
|
|
__func__, ret);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* "ref_clk_parent" is optional clock hence make sure that clk reference
|
|
|
|
* is available before trying to enable the clock.
|
|
|
|
*/
|
|
|
|
if (phy->ref_clk_parent) {
|
|
|
|
ret = clk_prepare_enable(phy->ref_clk_parent);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(phy->dev, "%s: ref_clk_parent enable failed %d\n",
|
|
|
|
__func__, ret);
|
|
|
|
goto out_disable_src;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
phy->is_ref_clk_enabled = true;
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
out_disable_parent:
|
|
|
|
if (phy->ref_clk_parent)
|
|
|
|
clk_disable_unprepare(phy->ref_clk_parent);
|
|
|
|
out_disable_src:
|
|
|
|
clk_disable_unprepare(phy->ref_clk_src);
|
|
|
|
out:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-11-08 17:07:42 +07:00
|
|
|
static int ufs_qcom_phy_disable_vreg(struct device *dev,
|
2015-01-15 21:32:35 +07:00
|
|
|
struct ufs_qcom_phy_vreg *vreg)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
|
2017-01-23 04:17:48 +07:00
|
|
|
if (!vreg || !vreg->enabled)
|
2015-01-15 21:32:35 +07:00
|
|
|
goto out;
|
|
|
|
|
|
|
|
ret = regulator_disable(vreg->reg);
|
|
|
|
|
|
|
|
if (!ret) {
|
|
|
|
/* ignore errors on applying disable config */
|
2016-11-08 17:07:42 +07:00
|
|
|
ufs_qcom_phy_cfg_vreg(dev, vreg, false);
|
2015-01-15 21:32:35 +07:00
|
|
|
vreg->enabled = false;
|
|
|
|
} else {
|
|
|
|
dev_err(dev, "%s: %s disable failed, err=%d\n",
|
|
|
|
__func__, vreg->name, ret);
|
|
|
|
}
|
|
|
|
out:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-11-08 17:07:48 +07:00
|
|
|
static void ufs_qcom_phy_disable_ref_clk(struct ufs_qcom_phy *phy)
|
2015-01-15 21:32:35 +07:00
|
|
|
{
|
|
|
|
if (phy->is_ref_clk_enabled) {
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
if (phy->ref_clk_parent)
|
|
|
|
clk_disable_unprepare(phy->ref_clk_parent);
|
|
|
|
clk_disable_unprepare(phy->ref_clk_src);
|
|
|
|
phy->is_ref_clk_enabled = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Turn ON M-PHY RMMI interface clocks */
|
2016-11-08 17:07:48 +07:00
|
|
|
static int ufs_qcom_phy_enable_iface_clk(struct ufs_qcom_phy *phy)
|
2015-01-15 21:32:35 +07:00
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (phy->is_iface_clk_enabled)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
ret = clk_prepare_enable(phy->tx_iface_clk);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(phy->dev, "%s: tx_iface_clk enable failed %d\n",
|
|
|
|
__func__, ret);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
ret = clk_prepare_enable(phy->rx_iface_clk);
|
|
|
|
if (ret) {
|
|
|
|
clk_disable_unprepare(phy->tx_iface_clk);
|
|
|
|
dev_err(phy->dev, "%s: rx_iface_clk enable failed %d. disabling also tx_iface_clk\n",
|
|
|
|
__func__, ret);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
phy->is_iface_clk_enabled = true;
|
|
|
|
|
|
|
|
out:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Turn OFF M-PHY RMMI interface clocks */
|
2019-03-19 22:27:38 +07:00
|
|
|
static void ufs_qcom_phy_disable_iface_clk(struct ufs_qcom_phy *phy)
|
2015-01-15 21:32:35 +07:00
|
|
|
{
|
|
|
|
if (phy->is_iface_clk_enabled) {
|
|
|
|
clk_disable_unprepare(phy->tx_iface_clk);
|
|
|
|
clk_disable_unprepare(phy->rx_iface_clk);
|
|
|
|
phy->is_iface_clk_enabled = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-12 13:19:36 +07:00
|
|
|
static int ufs_qcom_phy_start_serdes(struct ufs_qcom_phy *ufs_qcom_phy)
|
2015-01-15 21:32:35 +07:00
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (!ufs_qcom_phy->phy_spec_ops->start_serdes) {
|
|
|
|
dev_err(ufs_qcom_phy->dev, "%s: start_serdes() callback is not supported\n",
|
|
|
|
__func__);
|
|
|
|
ret = -ENOTSUPP;
|
|
|
|
} else {
|
|
|
|
ufs_qcom_phy->phy_spec_ops->start_serdes(ufs_qcom_phy);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ufs_qcom_phy_set_tx_lane_enable(struct phy *generic_phy, u32 tx_lanes)
|
|
|
|
{
|
|
|
|
struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (!ufs_qcom_phy->phy_spec_ops->set_tx_lane_enable) {
|
|
|
|
dev_err(ufs_qcom_phy->dev, "%s: set_tx_lane_enable() callback is not supported\n",
|
|
|
|
__func__);
|
|
|
|
ret = -ENOTSUPP;
|
|
|
|
} else {
|
|
|
|
ufs_qcom_phy->phy_spec_ops->set_tx_lane_enable(ufs_qcom_phy,
|
|
|
|
tx_lanes);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
2015-09-02 15:32:17 +07:00
|
|
|
EXPORT_SYMBOL_GPL(ufs_qcom_phy_set_tx_lane_enable);
|
2015-01-15 21:32:35 +07:00
|
|
|
|
|
|
|
void ufs_qcom_phy_save_controller_version(struct phy *generic_phy,
|
|
|
|
u8 major, u16 minor, u16 step)
|
|
|
|
{
|
|
|
|
struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
|
|
|
|
|
|
|
|
ufs_qcom_phy->host_ctrl_rev_major = major;
|
|
|
|
ufs_qcom_phy->host_ctrl_rev_minor = minor;
|
|
|
|
ufs_qcom_phy->host_ctrl_rev_step = step;
|
|
|
|
}
|
2015-09-02 15:32:17 +07:00
|
|
|
EXPORT_SYMBOL_GPL(ufs_qcom_phy_save_controller_version);
|
2015-01-15 21:32:35 +07:00
|
|
|
|
2017-10-12 13:19:36 +07:00
|
|
|
static int ufs_qcom_phy_is_pcs_ready(struct ufs_qcom_phy *ufs_qcom_phy)
|
2015-01-15 21:32:35 +07:00
|
|
|
{
|
|
|
|
if (!ufs_qcom_phy->phy_spec_ops->is_physical_coding_sublayer_ready) {
|
|
|
|
dev_err(ufs_qcom_phy->dev, "%s: is_physical_coding_sublayer_ready() callback is not supported\n",
|
|
|
|
__func__);
|
|
|
|
return -ENOTSUPP;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ufs_qcom_phy->phy_spec_ops->
|
|
|
|
is_physical_coding_sublayer_ready(ufs_qcom_phy);
|
|
|
|
}
|
|
|
|
|
|
|
|
int ufs_qcom_phy_power_on(struct phy *generic_phy)
|
|
|
|
{
|
|
|
|
struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
|
|
|
|
struct device *dev = phy_common->dev;
|
phy: ufs-qcom: Refactor all init steps into phy_poweron
The phy code was using implicit sequencing between the PHY driver
and the UFS driver to implement certain hardware requirements.
Specifically, the PHY reset register in the UFS controller needs
to be deasserted before serdes start occurs in the PHY.
Before this change, the code was doing this by utilizing the two
phy callbacks, phy_init() and phy_poweron(), as "init step 1" and
"init step 2", where the UFS driver would deassert reset between
these two steps.
This makes it challenging to power off the regulators in suspend,
as regulators are initialized in init, not in poweron(), but only
poweroff() is called during suspend, not exit().
For UFS, move the actual firing up of the PHY to phy_poweron() and
phy_poweroff() callbacks, rather than init()/exit(). UFS calls
phy_poweroff() during suspend, so now all clocks and regulators for
the phy can be powered down during suspend.
QMP is a little tricky because the PHY is also shared with PCIe and
USB3, which have their own definitions for init() and poweron(). Rename
the meaty functions to _enable() and _disable() to disentangle from the
PHY core names, and then create two different ops structures: one for
UFS and one for the other PHY types.
In phy-qcom-ufs, remove the 'is_powered_on' and 'is_started' guards,
as the generic PHY code does the reference counting. The
14/20nm-specific init functions get collapsed into the generic power_on()
function, with the addition of a calibrate() callback specific to 14/20nm.
Signed-off-by: Evan Green <evgreen@chromium.org>
Reviewed-by: Stephen Boyd <swboyd@chromium.org>
Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
2019-03-22 00:18:00 +07:00
|
|
|
bool is_rate_B = false;
|
2015-01-15 21:32:35 +07:00
|
|
|
int err;
|
|
|
|
|
phy: ufs-qcom: Refactor all init steps into phy_poweron
The phy code was using implicit sequencing between the PHY driver
and the UFS driver to implement certain hardware requirements.
Specifically, the PHY reset register in the UFS controller needs
to be deasserted before serdes start occurs in the PHY.
Before this change, the code was doing this by utilizing the two
phy callbacks, phy_init() and phy_poweron(), as "init step 1" and
"init step 2", where the UFS driver would deassert reset between
these two steps.
This makes it challenging to power off the regulators in suspend,
as regulators are initialized in init, not in poweron(), but only
poweroff() is called during suspend, not exit().
For UFS, move the actual firing up of the PHY to phy_poweron() and
phy_poweroff() callbacks, rather than init()/exit(). UFS calls
phy_poweroff() during suspend, so now all clocks and regulators for
the phy can be powered down during suspend.
QMP is a little tricky because the PHY is also shared with PCIe and
USB3, which have their own definitions for init() and poweron(). Rename
the meaty functions to _enable() and _disable() to disentangle from the
PHY core names, and then create two different ops structures: one for
UFS and one for the other PHY types.
In phy-qcom-ufs, remove the 'is_powered_on' and 'is_started' guards,
as the generic PHY code does the reference counting. The
14/20nm-specific init functions get collapsed into the generic power_on()
function, with the addition of a calibrate() callback specific to 14/20nm.
Signed-off-by: Evan Green <evgreen@chromium.org>
Reviewed-by: Stephen Boyd <swboyd@chromium.org>
Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
2019-03-22 00:18:00 +07:00
|
|
|
err = ufs_qcom_phy_get_reset(phy_common);
|
|
|
|
if (err)
|
|
|
|
return err;
|
2016-11-08 17:07:49 +07:00
|
|
|
|
phy: ufs-qcom: Refactor all init steps into phy_poweron
The phy code was using implicit sequencing between the PHY driver
and the UFS driver to implement certain hardware requirements.
Specifically, the PHY reset register in the UFS controller needs
to be deasserted before serdes start occurs in the PHY.
Before this change, the code was doing this by utilizing the two
phy callbacks, phy_init() and phy_poweron(), as "init step 1" and
"init step 2", where the UFS driver would deassert reset between
these two steps.
This makes it challenging to power off the regulators in suspend,
as regulators are initialized in init, not in poweron(), but only
poweroff() is called during suspend, not exit().
For UFS, move the actual firing up of the PHY to phy_poweron() and
phy_poweroff() callbacks, rather than init()/exit(). UFS calls
phy_poweroff() during suspend, so now all clocks and regulators for
the phy can be powered down during suspend.
QMP is a little tricky because the PHY is also shared with PCIe and
USB3, which have their own definitions for init() and poweron(). Rename
the meaty functions to _enable() and _disable() to disentangle from the
PHY core names, and then create two different ops structures: one for
UFS and one for the other PHY types.
In phy-qcom-ufs, remove the 'is_powered_on' and 'is_started' guards,
as the generic PHY code does the reference counting. The
14/20nm-specific init functions get collapsed into the generic power_on()
function, with the addition of a calibrate() callback specific to 14/20nm.
Signed-off-by: Evan Green <evgreen@chromium.org>
Reviewed-by: Stephen Boyd <swboyd@chromium.org>
Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
2019-03-22 00:18:00 +07:00
|
|
|
err = reset_control_assert(phy_common->ufs_reset);
|
|
|
|
if (err)
|
|
|
|
return err;
|
2017-10-12 13:19:36 +07:00
|
|
|
|
phy: ufs-qcom: Refactor all init steps into phy_poweron
The phy code was using implicit sequencing between the PHY driver
and the UFS driver to implement certain hardware requirements.
Specifically, the PHY reset register in the UFS controller needs
to be deasserted before serdes start occurs in the PHY.
Before this change, the code was doing this by utilizing the two
phy callbacks, phy_init() and phy_poweron(), as "init step 1" and
"init step 2", where the UFS driver would deassert reset between
these two steps.
This makes it challenging to power off the regulators in suspend,
as regulators are initialized in init, not in poweron(), but only
poweroff() is called during suspend, not exit().
For UFS, move the actual firing up of the PHY to phy_poweron() and
phy_poweroff() callbacks, rather than init()/exit(). UFS calls
phy_poweroff() during suspend, so now all clocks and regulators for
the phy can be powered down during suspend.
QMP is a little tricky because the PHY is also shared with PCIe and
USB3, which have their own definitions for init() and poweron(). Rename
the meaty functions to _enable() and _disable() to disentangle from the
PHY core names, and then create two different ops structures: one for
UFS and one for the other PHY types.
In phy-qcom-ufs, remove the 'is_powered_on' and 'is_started' guards,
as the generic PHY code does the reference counting. The
14/20nm-specific init functions get collapsed into the generic power_on()
function, with the addition of a calibrate() callback specific to 14/20nm.
Signed-off-by: Evan Green <evgreen@chromium.org>
Reviewed-by: Stephen Boyd <swboyd@chromium.org>
Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
2019-03-22 00:18:00 +07:00
|
|
|
if (phy_common->mode == PHY_MODE_UFS_HS_B)
|
|
|
|
is_rate_B = true;
|
2017-10-12 13:19:36 +07:00
|
|
|
|
phy: ufs-qcom: Refactor all init steps into phy_poweron
The phy code was using implicit sequencing between the PHY driver
and the UFS driver to implement certain hardware requirements.
Specifically, the PHY reset register in the UFS controller needs
to be deasserted before serdes start occurs in the PHY.
Before this change, the code was doing this by utilizing the two
phy callbacks, phy_init() and phy_poweron(), as "init step 1" and
"init step 2", where the UFS driver would deassert reset between
these two steps.
This makes it challenging to power off the regulators in suspend,
as regulators are initialized in init, not in poweron(), but only
poweroff() is called during suspend, not exit().
For UFS, move the actual firing up of the PHY to phy_poweron() and
phy_poweroff() callbacks, rather than init()/exit(). UFS calls
phy_poweroff() during suspend, so now all clocks and regulators for
the phy can be powered down during suspend.
QMP is a little tricky because the PHY is also shared with PCIe and
USB3, which have their own definitions for init() and poweron(). Rename
the meaty functions to _enable() and _disable() to disentangle from the
PHY core names, and then create two different ops structures: one for
UFS and one for the other PHY types.
In phy-qcom-ufs, remove the 'is_powered_on' and 'is_started' guards,
as the generic PHY code does the reference counting. The
14/20nm-specific init functions get collapsed into the generic power_on()
function, with the addition of a calibrate() callback specific to 14/20nm.
Signed-off-by: Evan Green <evgreen@chromium.org>
Reviewed-by: Stephen Boyd <swboyd@chromium.org>
Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
2019-03-22 00:18:00 +07:00
|
|
|
err = phy_common->phy_spec_ops->calibrate(phy_common, is_rate_B);
|
|
|
|
if (err)
|
|
|
|
return err;
|
2016-11-08 17:07:49 +07:00
|
|
|
|
2019-03-22 00:17:59 +07:00
|
|
|
err = reset_control_deassert(phy_common->ufs_reset);
|
|
|
|
if (err) {
|
|
|
|
dev_err(dev, "Failed to assert UFS PHY reset");
|
|
|
|
return err;
|
2017-10-12 13:19:36 +07:00
|
|
|
}
|
|
|
|
|
phy: ufs-qcom: Refactor all init steps into phy_poweron
The phy code was using implicit sequencing between the PHY driver
and the UFS driver to implement certain hardware requirements.
Specifically, the PHY reset register in the UFS controller needs
to be deasserted before serdes start occurs in the PHY.
Before this change, the code was doing this by utilizing the two
phy callbacks, phy_init() and phy_poweron(), as "init step 1" and
"init step 2", where the UFS driver would deassert reset between
these two steps.
This makes it challenging to power off the regulators in suspend,
as regulators are initialized in init, not in poweron(), but only
poweroff() is called during suspend, not exit().
For UFS, move the actual firing up of the PHY to phy_poweron() and
phy_poweroff() callbacks, rather than init()/exit(). UFS calls
phy_poweroff() during suspend, so now all clocks and regulators for
the phy can be powered down during suspend.
QMP is a little tricky because the PHY is also shared with PCIe and
USB3, which have their own definitions for init() and poweron(). Rename
the meaty functions to _enable() and _disable() to disentangle from the
PHY core names, and then create two different ops structures: one for
UFS and one for the other PHY types.
In phy-qcom-ufs, remove the 'is_powered_on' and 'is_started' guards,
as the generic PHY code does the reference counting. The
14/20nm-specific init functions get collapsed into the generic power_on()
function, with the addition of a calibrate() callback specific to 14/20nm.
Signed-off-by: Evan Green <evgreen@chromium.org>
Reviewed-by: Stephen Boyd <swboyd@chromium.org>
Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
2019-03-22 00:18:00 +07:00
|
|
|
err = ufs_qcom_phy_start_serdes(phy_common);
|
|
|
|
if (err)
|
|
|
|
return err;
|
2017-10-12 13:19:36 +07:00
|
|
|
|
phy: ufs-qcom: Refactor all init steps into phy_poweron
The phy code was using implicit sequencing between the PHY driver
and the UFS driver to implement certain hardware requirements.
Specifically, the PHY reset register in the UFS controller needs
to be deasserted before serdes start occurs in the PHY.
Before this change, the code was doing this by utilizing the two
phy callbacks, phy_init() and phy_poweron(), as "init step 1" and
"init step 2", where the UFS driver would deassert reset between
these two steps.
This makes it challenging to power off the regulators in suspend,
as regulators are initialized in init, not in poweron(), but only
poweroff() is called during suspend, not exit().
For UFS, move the actual firing up of the PHY to phy_poweron() and
phy_poweroff() callbacks, rather than init()/exit(). UFS calls
phy_poweroff() during suspend, so now all clocks and regulators for
the phy can be powered down during suspend.
QMP is a little tricky because the PHY is also shared with PCIe and
USB3, which have their own definitions for init() and poweron(). Rename
the meaty functions to _enable() and _disable() to disentangle from the
PHY core names, and then create two different ops structures: one for
UFS and one for the other PHY types.
In phy-qcom-ufs, remove the 'is_powered_on' and 'is_started' guards,
as the generic PHY code does the reference counting. The
14/20nm-specific init functions get collapsed into the generic power_on()
function, with the addition of a calibrate() callback specific to 14/20nm.
Signed-off-by: Evan Green <evgreen@chromium.org>
Reviewed-by: Stephen Boyd <swboyd@chromium.org>
Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
2019-03-22 00:18:00 +07:00
|
|
|
err = ufs_qcom_phy_is_pcs_ready(phy_common);
|
|
|
|
if (err)
|
|
|
|
return err;
|
2017-10-12 13:19:36 +07:00
|
|
|
|
2016-11-08 17:07:42 +07:00
|
|
|
err = ufs_qcom_phy_enable_vreg(dev, &phy_common->vdda_phy);
|
2015-01-15 21:32:35 +07:00
|
|
|
if (err) {
|
|
|
|
dev_err(dev, "%s enable vdda_phy failed, err=%d\n",
|
|
|
|
__func__, err);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
phy_common->phy_spec_ops->power_control(phy_common, true);
|
|
|
|
|
|
|
|
/* vdda_pll also enables ref clock LDOs so enable it first */
|
2016-11-08 17:07:42 +07:00
|
|
|
err = ufs_qcom_phy_enable_vreg(dev, &phy_common->vdda_pll);
|
2015-01-15 21:32:35 +07:00
|
|
|
if (err) {
|
|
|
|
dev_err(dev, "%s enable vdda_pll failed, err=%d\n",
|
|
|
|
__func__, err);
|
|
|
|
goto out_disable_phy;
|
|
|
|
}
|
|
|
|
|
2016-11-08 17:07:48 +07:00
|
|
|
err = ufs_qcom_phy_enable_iface_clk(phy_common);
|
2015-01-15 21:32:35 +07:00
|
|
|
if (err) {
|
2016-11-08 17:07:48 +07:00
|
|
|
dev_err(dev, "%s enable phy iface clock failed, err=%d\n",
|
2015-01-15 21:32:35 +07:00
|
|
|
__func__, err);
|
|
|
|
goto out_disable_pll;
|
|
|
|
}
|
|
|
|
|
2016-11-08 17:07:48 +07:00
|
|
|
err = ufs_qcom_phy_enable_ref_clk(phy_common);
|
|
|
|
if (err) {
|
|
|
|
dev_err(dev, "%s enable phy ref clock failed, err=%d\n",
|
|
|
|
__func__, err);
|
|
|
|
goto out_disable_iface_clk;
|
|
|
|
}
|
|
|
|
|
2015-01-15 21:32:35 +07:00
|
|
|
/* enable device PHY ref_clk pad rail */
|
|
|
|
if (phy_common->vddp_ref_clk.reg) {
|
2016-11-08 17:07:42 +07:00
|
|
|
err = ufs_qcom_phy_enable_vreg(dev,
|
2015-01-15 21:32:35 +07:00
|
|
|
&phy_common->vddp_ref_clk);
|
|
|
|
if (err) {
|
|
|
|
dev_err(dev, "%s enable vddp_ref_clk failed, err=%d\n",
|
|
|
|
__func__, err);
|
|
|
|
goto out_disable_ref_clk;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
out_disable_ref_clk:
|
2016-11-08 17:07:48 +07:00
|
|
|
ufs_qcom_phy_disable_ref_clk(phy_common);
|
|
|
|
out_disable_iface_clk:
|
|
|
|
ufs_qcom_phy_disable_iface_clk(phy_common);
|
2015-01-15 21:32:35 +07:00
|
|
|
out_disable_pll:
|
2016-11-08 17:07:42 +07:00
|
|
|
ufs_qcom_phy_disable_vreg(dev, &phy_common->vdda_pll);
|
2015-01-15 21:32:35 +07:00
|
|
|
out_disable_phy:
|
2016-11-08 17:07:42 +07:00
|
|
|
ufs_qcom_phy_disable_vreg(dev, &phy_common->vdda_phy);
|
2015-01-15 21:32:35 +07:00
|
|
|
out:
|
|
|
|
return err;
|
|
|
|
}
|
2015-03-23 10:54:50 +07:00
|
|
|
EXPORT_SYMBOL_GPL(ufs_qcom_phy_power_on);
|
2015-01-15 21:32:35 +07:00
|
|
|
|
|
|
|
int ufs_qcom_phy_power_off(struct phy *generic_phy)
|
|
|
|
{
|
|
|
|
struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
|
|
|
|
|
|
|
|
phy_common->phy_spec_ops->power_control(phy_common, false);
|
|
|
|
|
|
|
|
if (phy_common->vddp_ref_clk.reg)
|
2016-11-08 17:07:42 +07:00
|
|
|
ufs_qcom_phy_disable_vreg(phy_common->dev,
|
2015-01-15 21:32:35 +07:00
|
|
|
&phy_common->vddp_ref_clk);
|
2016-11-08 17:07:48 +07:00
|
|
|
ufs_qcom_phy_disable_ref_clk(phy_common);
|
|
|
|
ufs_qcom_phy_disable_iface_clk(phy_common);
|
2015-01-15 21:32:35 +07:00
|
|
|
|
2016-11-08 17:07:42 +07:00
|
|
|
ufs_qcom_phy_disable_vreg(phy_common->dev, &phy_common->vdda_pll);
|
|
|
|
ufs_qcom_phy_disable_vreg(phy_common->dev, &phy_common->vdda_phy);
|
2019-03-22 00:17:59 +07:00
|
|
|
reset_control_assert(phy_common->ufs_reset);
|
2015-01-15 21:32:35 +07:00
|
|
|
return 0;
|
|
|
|
}
|
2015-03-23 10:54:50 +07:00
|
|
|
EXPORT_SYMBOL_GPL(ufs_qcom_phy_power_off);
|
2018-01-10 23:35:43 +07:00
|
|
|
|
|
|
|
MODULE_AUTHOR("Yaniv Gardi <ygardi@codeaurora.org>");
|
|
|
|
MODULE_AUTHOR("Vivek Gautam <vivek.gautam@codeaurora.org>");
|
|
|
|
MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY");
|
|
|
|
MODULE_LICENSE("GPL v2");
|