2019-05-29 14:17:58 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2015-01-15 14:32:36 +00:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "phy-qcom-ufs-qmp-20nm.h"
|
|
|
|
|
|
|
|
#define UFS_PHY_NAME "ufs_phy_qmp_20nm"
|
|
|
|
|
|
|
|
static
|
|
|
|
int ufs_qcom_phy_qmp_20nm_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
|
|
|
|
bool is_rate_B)
|
|
|
|
{
|
|
|
|
struct ufs_qcom_phy_calibration *tbl_A, *tbl_B;
|
|
|
|
int tbl_size_A, tbl_size_B;
|
|
|
|
u8 major = ufs_qcom_phy->host_ctrl_rev_major;
|
|
|
|
u16 minor = ufs_qcom_phy->host_ctrl_rev_minor;
|
|
|
|
u16 step = ufs_qcom_phy->host_ctrl_rev_step;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
if ((major == 0x1) && (minor == 0x002) && (step == 0x0000)) {
|
|
|
|
tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_2_0);
|
|
|
|
tbl_A = phy_cal_table_rate_A_1_2_0;
|
|
|
|
} else if ((major == 0x1) && (minor == 0x003) && (step == 0x0000)) {
|
|
|
|
tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_3_0);
|
|
|
|
tbl_A = phy_cal_table_rate_A_1_3_0;
|
|
|
|
} else {
|
|
|
|
dev_err(ufs_qcom_phy->dev, "%s: Unknown UFS-PHY version, no calibration values\n",
|
|
|
|
__func__);
|
|
|
|
err = -ENODEV;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
|
|
|
|
tbl_B = phy_cal_table_rate_B;
|
|
|
|
|
|
|
|
err = ufs_qcom_phy_calibrate(ufs_qcom_phy, tbl_A, tbl_size_A,
|
|
|
|
tbl_B, tbl_size_B, is_rate_B);
|
|
|
|
|
|
|
|
if (err)
|
|
|
|
dev_err(ufs_qcom_phy->dev, "%s: ufs_qcom_phy_calibrate() failed %d\n",
|
|
|
|
__func__, err);
|
|
|
|
|
|
|
|
out:
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static
|
|
|
|
void ufs_qcom_phy_qmp_20nm_advertise_quirks(struct ufs_qcom_phy *phy_common)
|
|
|
|
{
|
|
|
|
phy_common->quirks =
|
|
|
|
UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
|
|
|
|
}
|
|
|
|
|
2017-10-12 06:19:34 +00:00
|
|
|
static
|
2018-11-20 01:24:20 +00:00
|
|
|
int ufs_qcom_phy_qmp_20nm_set_mode(struct phy *generic_phy,
|
|
|
|
enum phy_mode mode, int submode)
|
2017-10-12 06:19:34 +00:00
|
|
|
{
|
|
|
|
struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
|
|
|
|
|
|
|
|
phy_common->mode = PHY_MODE_INVALID;
|
|
|
|
|
|
|
|
if (mode > 0)
|
|
|
|
phy_common->mode = mode;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-01-15 14:32:36 +00:00
|
|
|
static
|
|
|
|
void ufs_qcom_phy_qmp_20nm_power_control(struct ufs_qcom_phy *phy, bool val)
|
|
|
|
{
|
|
|
|
bool hibern8_exit_after_pwr_collapse = phy->quirks &
|
|
|
|
UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
|
|
|
|
|
|
|
|
if (val) {
|
|
|
|
writel_relaxed(0x1, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
|
|
|
|
/*
|
|
|
|
* Before any transactions involving PHY, ensure PHY knows
|
|
|
|
* that it's analog rail is powered ON.
|
|
|
|
*/
|
|
|
|
mb();
|
|
|
|
|
|
|
|
if (hibern8_exit_after_pwr_collapse) {
|
|
|
|
/*
|
|
|
|
* Give atleast 1us delay after restoring PHY analog
|
|
|
|
* power.
|
|
|
|
*/
|
|
|
|
usleep_range(1, 2);
|
|
|
|
writel_relaxed(0x0A, phy->mmio +
|
|
|
|
QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
|
|
|
|
writel_relaxed(0x08, phy->mmio +
|
|
|
|
QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
|
|
|
|
/*
|
|
|
|
* Make sure workaround is deactivated before proceeding
|
|
|
|
* with normal PHY operations.
|
|
|
|
*/
|
|
|
|
mb();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (hibern8_exit_after_pwr_collapse) {
|
|
|
|
writel_relaxed(0x0A, phy->mmio +
|
|
|
|
QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
|
|
|
|
writel_relaxed(0x02, phy->mmio +
|
|
|
|
QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
|
|
|
|
/*
|
|
|
|
* Make sure that above workaround is activated before
|
|
|
|
* PHY analog power collapse.
|
|
|
|
*/
|
|
|
|
mb();
|
|
|
|
}
|
|
|
|
|
|
|
|
writel_relaxed(0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
|
|
|
|
/*
|
|
|
|
* ensure that PHY knows its PHY analog rail is going
|
|
|
|
* to be powered down
|
|
|
|
*/
|
|
|
|
mb();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static
|
|
|
|
void ufs_qcom_phy_qmp_20nm_set_tx_lane_enable(struct ufs_qcom_phy *phy, u32 val)
|
|
|
|
{
|
|
|
|
writel_relaxed(val & UFS_PHY_TX_LANE_ENABLE_MASK,
|
|
|
|
phy->mmio + UFS_PHY_TX_LANE_ENABLE);
|
|
|
|
mb();
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void ufs_qcom_phy_qmp_20nm_start_serdes(struct ufs_qcom_phy *phy)
|
|
|
|
{
|
|
|
|
u32 tmp;
|
|
|
|
|
|
|
|
tmp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START);
|
|
|
|
tmp &= ~MASK_SERDES_START;
|
|
|
|
tmp |= (1 << OFFSET_SERDES_START);
|
|
|
|
writel_relaxed(tmp, phy->mmio + UFS_PHY_PHY_START);
|
|
|
|
mb();
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ufs_qcom_phy_qmp_20nm_is_pcs_ready(struct ufs_qcom_phy *phy_common)
|
|
|
|
{
|
|
|
|
int err = 0;
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
err = readl_poll_timeout(phy_common->mmio + UFS_PHY_PCS_READY_STATUS,
|
|
|
|
val, (val & MASK_PCS_READY), 10, 1000000);
|
|
|
|
if (err)
|
|
|
|
dev_err(phy_common->dev, "%s: poll for pcs failed err = %d\n",
|
|
|
|
__func__, err);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2015-07-15 07:33:51 +00:00
|
|
|
static const struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = {
|
2015-01-15 14:32:36 +00:00
|
|
|
.power_on = ufs_qcom_phy_power_on,
|
|
|
|
.power_off = ufs_qcom_phy_power_off,
|
2017-10-12 06:19:34 +00:00
|
|
|
.set_mode = ufs_qcom_phy_qmp_20nm_set_mode,
|
2015-01-15 14:32:36 +00:00
|
|
|
.owner = THIS_MODULE,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct ufs_qcom_phy_specific_ops phy_20nm_ops = {
|
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-21 17:18:00 +00:00
|
|
|
.calibrate = ufs_qcom_phy_qmp_20nm_phy_calibrate,
|
2015-01-15 14:32:36 +00:00
|
|
|
.start_serdes = ufs_qcom_phy_qmp_20nm_start_serdes,
|
|
|
|
.is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_20nm_is_pcs_ready,
|
|
|
|
.set_tx_lane_enable = ufs_qcom_phy_qmp_20nm_set_tx_lane_enable,
|
|
|
|
.power_control = ufs_qcom_phy_qmp_20nm_power_control,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int ufs_qcom_phy_qmp_20nm_probe(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct device *dev = &pdev->dev;
|
|
|
|
struct phy *generic_phy;
|
|
|
|
struct ufs_qcom_phy_qmp_20nm *phy;
|
2016-11-08 10:07:47 +00:00
|
|
|
struct ufs_qcom_phy *phy_common;
|
2015-01-15 14:32:36 +00:00
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
|
|
|
|
if (!phy) {
|
|
|
|
err = -ENOMEM;
|
|
|
|
goto out;
|
|
|
|
}
|
2016-11-08 10:07:47 +00:00
|
|
|
phy_common = &phy->common_cfg;
|
2015-01-15 14:32:36 +00:00
|
|
|
|
2016-11-08 10:07:47 +00:00
|
|
|
generic_phy = ufs_qcom_phy_generic_probe(pdev, phy_common,
|
2015-01-15 14:32:36 +00:00
|
|
|
&ufs_qcom_phy_qmp_20nm_phy_ops, &phy_20nm_ops);
|
|
|
|
|
|
|
|
if (!generic_phy) {
|
|
|
|
err = -EIO;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2016-11-08 10:07:47 +00:00
|
|
|
err = ufs_qcom_phy_init_clks(phy_common);
|
2017-01-22 21:17:49 +00:00
|
|
|
if (err)
|
2016-11-08 10:07:47 +00:00
|
|
|
goto out;
|
|
|
|
|
|
|
|
err = ufs_qcom_phy_init_vregulators(phy_common);
|
2017-01-22 21:17:49 +00:00
|
|
|
if (err)
|
2016-11-08 10:07:47 +00:00
|
|
|
goto out;
|
|
|
|
|
|
|
|
ufs_qcom_phy_qmp_20nm_advertise_quirks(phy_common);
|
|
|
|
|
2015-01-15 14:32:36 +00:00
|
|
|
phy_set_drvdata(generic_phy, phy);
|
|
|
|
|
2016-11-08 10:07:47 +00:00
|
|
|
strlcpy(phy_common->name, UFS_PHY_NAME, sizeof(phy_common->name));
|
2015-01-15 14:32:36 +00:00
|
|
|
|
|
|
|
out:
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct of_device_id ufs_qcom_phy_qmp_20nm_of_match[] = {
|
|
|
|
{.compatible = "qcom,ufs-phy-qmp-20nm"},
|
|
|
|
{},
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_20nm_of_match);
|
|
|
|
|
|
|
|
static struct platform_driver ufs_qcom_phy_qmp_20nm_driver = {
|
|
|
|
.probe = ufs_qcom_phy_qmp_20nm_probe,
|
|
|
|
.driver = {
|
|
|
|
.of_match_table = ufs_qcom_phy_qmp_20nm_of_match,
|
|
|
|
.name = "ufs_qcom_phy_qmp_20nm",
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
module_platform_driver(ufs_qcom_phy_qmp_20nm_driver);
|
|
|
|
|
|
|
|
MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP 20nm");
|
|
|
|
MODULE_LICENSE("GPL v2");
|