linux/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c

173 lines
4.3 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
*/
#include "phy-qcom-ufs-qmp-14nm.h"
#define UFS_PHY_NAME "ufs_phy_qmp_14nm"
#define UFS_PHY_VDDA_PHY_UV (925000)
static
int ufs_qcom_phy_qmp_14nm_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
bool is_rate_B)
{
int tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A);
int tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
int err;
err = ufs_qcom_phy_calibrate(ufs_qcom_phy, phy_cal_table_rate_A,
tbl_size_A, phy_cal_table_rate_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);
return err;
}
static
void ufs_qcom_phy_qmp_14nm_advertise_quirks(struct ufs_qcom_phy *phy_common)
{
phy_common->quirks =
UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
}
static
phy: core: rework phy_set_mode to accept phy mode and submode Currently the attempt to add support for Ethernet interface mode PHY (MII/GMII/RGMII) will lead to the necessity of extending enum phy_mode and duplicate there values from phy_interface_t enum (or introduce more PHY callbacks) [1]. Both approaches are ineffective and would lead to fast bloating of enum phy_mode or struct phy_ops in the process of adding more PHYs for different subsystems which will make them unmaintainable. As discussed in [1] the solution could be to introduce dual level PHYs mode configuration - PHY mode and PHY submode. The PHY mode will define generic PHY type (subsystem - PCIE/ETHERNET/USB_) while the PHY submode - subsystem specific interface mode. The last is usually already defined in corresponding subsystem headers (phy_interface_t for Ethernet, enum usb_device_speed for USB). This patch is cumulative change which refactors PHY framework code to support dual level PHYs mode configuration - PHY mode and PHY submode. It extends .set_mode() callback to support additional parameter "int submode" and converts all corresponding PHY drivers to support new .set_mode() callback declaration. The new extended PHY API int phy_set_mode_ext(struct phy *phy, enum phy_mode mode, int submode) is introduced to support dual level PHYs mode configuration and existing phy_set_mode() API is converted to macros, so PHY framework consumers do not need to be changed (~21 matches). [1] http://lkml.kernel.org/r/d63588f6-9ab0-848a-5ad4-8073143bd95d@ti.com Signed-off-by: Grygorii Strashko <grygorii.strashko@ti.com> Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
2018-11-20 09:24:20 +08:00
int ufs_qcom_phy_qmp_14nm_set_mode(struct phy *generic_phy,
enum phy_mode mode, int submode)
{
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;
}
static
void ufs_qcom_phy_qmp_14nm_power_control(struct ufs_qcom_phy *phy, bool val)
{
writel_relaxed(val ? 0x1 : 0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
/*
* Before any transactions involving PHY, ensure PHY knows
* that it's analog rail is powered ON (or OFF).
*/
mb();
}
static inline
void ufs_qcom_phy_qmp_14nm_set_tx_lane_enable(struct ufs_qcom_phy *phy, u32 val)
{
/*
* 14nm PHY does not have TX_LANE_ENABLE register.
* Implement this function so as not to propagate error to caller.
*/
}
static inline void ufs_qcom_phy_qmp_14nm_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);
/* Ensure register value is committed */
mb();
}
static int ufs_qcom_phy_qmp_14nm_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;
}
static const struct phy_ops ufs_qcom_phy_qmp_14nm_phy_ops = {
.power_on = ufs_qcom_phy_power_on,
.power_off = ufs_qcom_phy_power_off,
.set_mode = ufs_qcom_phy_qmp_14nm_set_mode,
.owner = THIS_MODULE,
};
static struct ufs_qcom_phy_specific_ops phy_14nm_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-22 01:18:00 +08:00
.calibrate = ufs_qcom_phy_qmp_14nm_phy_calibrate,
.start_serdes = ufs_qcom_phy_qmp_14nm_start_serdes,
.is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_14nm_is_pcs_ready,
.set_tx_lane_enable = ufs_qcom_phy_qmp_14nm_set_tx_lane_enable,
.power_control = ufs_qcom_phy_qmp_14nm_power_control,
};
static int ufs_qcom_phy_qmp_14nm_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct phy *generic_phy;
struct ufs_qcom_phy_qmp_14nm *phy;
struct ufs_qcom_phy *phy_common;
int err = 0;
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
if (!phy) {
err = -ENOMEM;
goto out;
}
phy_common = &phy->common_cfg;
generic_phy = ufs_qcom_phy_generic_probe(pdev, phy_common,
&ufs_qcom_phy_qmp_14nm_phy_ops, &phy_14nm_ops);
if (!generic_phy) {
err = -EIO;
goto out;
}
err = ufs_qcom_phy_init_clks(phy_common);
if (err)
goto out;
err = ufs_qcom_phy_init_vregulators(phy_common);
if (err)
goto out;
phy_common->vdda_phy.max_uV = UFS_PHY_VDDA_PHY_UV;
phy_common->vdda_phy.min_uV = UFS_PHY_VDDA_PHY_UV;
ufs_qcom_phy_qmp_14nm_advertise_quirks(phy_common);
phy_set_drvdata(generic_phy, phy);
strlcpy(phy_common->name, UFS_PHY_NAME, sizeof(phy_common->name));
out:
return err;
}
static const struct of_device_id ufs_qcom_phy_qmp_14nm_of_match[] = {
{.compatible = "qcom,ufs-phy-qmp-14nm"},
{.compatible = "qcom,msm8996-ufs-phy-qmp-14nm"},
{},
};
MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_14nm_of_match);
static struct platform_driver ufs_qcom_phy_qmp_14nm_driver = {
.probe = ufs_qcom_phy_qmp_14nm_probe,
.driver = {
.of_match_table = ufs_qcom_phy_qmp_14nm_of_match,
.name = "ufs_qcom_phy_qmp_14nm",
},
};
module_platform_driver(ufs_qcom_phy_qmp_14nm_driver);
MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP 14nm");
MODULE_LICENSE("GPL v2");