phy: qcom: Add fundamental UFS PHY drivers

Add fundamental UFS PHY drivers to support Pineapple pre-sil.

Change-Id: Ie90db61aee04c7d57632b41df69341b75350f635
Signed-off-by: Can Guo <quic_cang@quicinc.com>
This commit is contained in:
Can Guo 2022-04-06 01:01:28 -07:00
parent 250faa59b7
commit fd375a80ad
7 changed files with 1433 additions and 0 deletions

View File

@ -48,6 +48,30 @@ config PHY_QCOM_QMP
Enable this to support the QMP PHY transceiver that is used
with controllers such as PCIe, UFS, and USB on Qualcomm chips.
config PHY_QCOM_UFS
tristate "Qualcomm Technologies, Inc. UFS PHY driver"
depends on OF && ARCH_QCOM
select GENERIC_PHY
help
Enables support for UFS PHY on Qualcomm Technologies, Inc.
chipsets.
This driver together with the UFS PHY controller specific
driver enables the UFS PHY support for Qualcomm Technologies, Inc.
chips.
if PHY_QCOM_UFS
config PHY_QCOM_UFS_QRBTC_SDM845
tristate "Qualcomm Technologies, Inc. UFS Presil Phy Driver"
depends on PHY_QCOM_UFS && REGULATOR_STUB
help
Enable this to support UFS PHY on pre-silicon platforms.
The support is restricted to HS-G1 only on both Tx and Rx.
No low power mode support is present.
Doesn't restrict number of lanes.
endif
config PHY_QCOM_QUSB2
tristate "Qualcomm QUSB2 PHY Driver"
depends on OF && (ARCH_QCOM || COMPILE_TEST)

View File

@ -5,6 +5,8 @@ obj-$(CONFIG_PHY_QCOM_IPQ4019_USB) += phy-qcom-ipq4019-usb.o
obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o
obj-$(CONFIG_PHY_QCOM_PCIE2) += phy-qcom-pcie2.o
obj-$(CONFIG_PHY_QCOM_QMP) += phy-qcom-qmp.o
obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs.o
obj-$(CONFIG_PHY_QCOM_UFS_QRBTC_SDM845) += phy-qcom-ufs-qrbtc-sdm845.o
obj-$(CONFIG_PHY_QCOM_QUSB2) += phy-qcom-qusb2.o
obj-$(CONFIG_PHY_QCOM_USB_HS) += phy-qcom-usb-hs.o
obj-$(CONFIG_PHY_QCOM_USB_HSIC) += phy-qcom-usb-hsic.o

View File

@ -0,0 +1,165 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2013-2015, 2019-2021, Linux Foundation. All rights reserved.
*/
#ifndef UFS_QCOM_PHY_I_H_
#define UFS_QCOM_PHY_I_H_
#include <linux/module.h>
#include <linux/clk.h>
#include <linux/phy/phy.h>
#include <linux/regulator/consumer.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
#include <linux/phy/phy-qcom-ufs.h>
#define UFS_QCOM_PHY_CAL_ENTRY(reg, val) \
{ \
.reg_offset = reg, \
.cfg_value = val, \
}
#define UFS_QCOM_PHY_NAME_LEN 30
enum {
MASK_SERDES_START = 0x1,
MASK_PCS_READY = 0x1,
};
enum {
OFFSET_SERDES_START = 0x0,
};
enum ufs_qcom_phy_submode {
UFS_QCOM_PHY_SUBMODE_NON_G4,
UFS_QCOM_PHY_SUBMODE_G4,
UFS_QCOM_PHY_SUBMODE_G5,
};
struct ufs_qcom_phy_stored_attributes {
u32 att;
u32 value;
};
struct ufs_qcom_phy_calibration {
u32 reg_offset;
u32 cfg_value;
};
struct ufs_qcom_phy_vreg {
const char *name;
struct regulator *reg;
int max_uA;
int min_uV;
int max_uV;
bool enabled;
};
struct ufs_qcom_phy {
struct list_head list;
struct device *dev;
void __iomem *mmio;
void __iomem *dev_ref_clk_ctrl_mmio;
struct clk *tx_iface_clk;
struct clk *rx_iface_clk;
bool is_iface_clk_enabled;
struct clk *ref_clk_src;
struct clk *ref_clk_parent;
struct clk *ref_clk;
struct clk *ref_aux_clk;
struct clk *qref_clk;
struct clk *rx_sym0_mux_clk;
struct clk *rx_sym1_mux_clk;
struct clk *tx_sym0_mux_clk;
struct clk *rx_sym0_phy_clk;
struct clk *rx_sym1_phy_clk;
struct clk *tx_sym0_phy_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;
struct ufs_qcom_phy_vreg vdd_phy_gdsc;
struct ufs_qcom_phy_vreg vdda_qref;
/* Number of lanes available (1 or 2) for Rx/Tx */
u32 lanes_per_direction;
unsigned int quirks;
/**
* If UFS link is put into Hibern8 and if UFS PHY analog hardware is
* power collapsed (by clearing UFS_PHY_POWER_DOWN_CONTROL), Hibern8
* exit might fail even after powering on UFS PHY analog hardware.
* Enabling this quirk will help to solve above issue by doing
* custom PHY settings just before PHY analog power collapse.
*/
#define UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE BIT(0)
u8 host_ctrl_rev_major;
u16 host_ctrl_rev_minor;
u16 host_ctrl_rev_step;
char name[UFS_QCOM_PHY_NAME_LEN];
struct ufs_qcom_phy_calibration *cached_regs;
int cached_regs_table_size;
struct ufs_qcom_phy_specific_ops *phy_spec_ops;
enum phy_mode mode;
int submode;
struct reset_control *ufs_reset;
};
/**
* struct ufs_qcom_phy_specific_ops - set of pointers to functions which have a
* specific implementation per phy. Each UFS phy, should implement
* those functions according to its spec and requirements
* @start_serdes: pointer to a function that starts the serdes
* @is_physical_coding_sublayer_ready: pointer to a function that
* checks pcs readiness. returns 0 for success and non-zero for error.
* @set_tx_lane_enable: pointer to a function that enable tx lanes
* @power_control: pointer to a function that controls analog rail of phy
* and writes to QSERDES_RX_SIGDET_CNTRL attribute
* @ctrl_rx_linecfg: pointer to a function that controls the enable/disable of
* Rx line config
* @dbg_register_dump: pointer to a function that dumps phy registers for debug.
*/
struct ufs_qcom_phy_specific_ops {
int (*calibrate)(struct ufs_qcom_phy *ufs_qcom_phy, bool is_rate_B,
bool is_g4);
void (*start_serdes)(struct ufs_qcom_phy *phy);
int (*is_physical_coding_sublayer_ready)(struct ufs_qcom_phy *phy);
void (*set_tx_lane_enable)(struct ufs_qcom_phy *phy, u32 val);
void (*power_control)(struct ufs_qcom_phy *phy, bool val);
void (*ctrl_rx_linecfg)(struct ufs_qcom_phy *phy, bool ctrl);
void (*dbg_register_dump)(struct ufs_qcom_phy *phy);
};
struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy);
int ufs_qcom_phy_power_on(struct phy *generic_phy);
int ufs_qcom_phy_power_off(struct phy *generic_phy);
int ufs_qcom_phy_init_clks(struct ufs_qcom_phy *phy_common);
int ufs_qcom_phy_init_vregulators(struct ufs_qcom_phy *phy_common);
int ufs_qcom_phy_remove(struct phy *generic_phy,
struct ufs_qcom_phy *ufs_qcom_phy);
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 ufs_qcom_phy_get_reset(struct ufs_qcom_phy *phy_common);
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);
void ufs_qcom_phy_write_tbl(struct ufs_qcom_phy *ufs_qcom_phy,
struct ufs_qcom_phy_calibration *tbl,
int tbl_size);
int ufs_qcom_phy_dump_regs(struct ufs_qcom_phy *phy,
int offset, int len, char *prefix);
#endif

View File

@ -0,0 +1,186 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016, 2019-2021, Linux Foundation. All rights reserved.
* 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-qrbtc-sdm845.h"
#define UFS_PHY_NAME "ufs_phy_qrbtc_sdm845"
static
int ufs_qcom_phy_qrbtc_sdm845_phy_calibrate(struct phy *generic_phy)
{
int err;
int tbl_size_A, tbl_size_B;
struct ufs_qcom_phy_calibration *tbl_A, *tbl_B;
struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
bool is_rate_B;
tbl_A = phy_cal_table_rate_A;
tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A);
tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
tbl_B = phy_cal_table_rate_B;
is_rate_B = (ufs_qcom_phy->mode == PHY_MODE_UFS_HS_B) ? true : false;
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);
return err;
}
static int
ufs_qcom_phy_qrbtc_sdm845_is_pcs_ready(struct ufs_qcom_phy *phy_common)
{
int err = 0;
u32 val;
/*
* The value we are polling for is 0x3D which represents the
* following masks:
* RESET_SM field: 0x5
* RESTRIMDONE bit: BIT(3)
* PLLLOCK bit: BIT(4)
* READY bit: BIT(5)
*/
#define QSERDES_COM_RESET_SM_REG_POLL_VAL 0x3D
err = readl_poll_timeout(phy_common->mmio + QSERDES_COM_RESET_SM,
val, (val == QSERDES_COM_RESET_SM_REG_POLL_VAL), 10, 1000000);
if (err)
dev_err(phy_common->dev, "%s: poll for pcs failed err = %d\n",
__func__, err);
return err;
}
static void ufs_qcom_phy_qrbtc_sdm845_start_serdes(struct ufs_qcom_phy *phy)
{
u32 temp;
writel_relaxed(0x01, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
temp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START);
temp |= 0x1;
writel_relaxed(temp, phy->mmio + UFS_PHY_PHY_START);
/* Ensure register value is committed */
mb();
}
static int ufs_qcom_phy_qrbtc_sdm845_init(struct phy *generic_phy)
{
struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
int ret;
ret = ufs_qcom_phy_get_reset(phy_common);
if (ret)
dev_err(phy_common->dev, "Failed to get reset control\n", ret);
return ret;
}
static int ufs_qcom_phy_qrbtc_sdm845_exit(struct phy *generic_phy)
{
return 0;
}
static
int ufs_qcom_phy_qrbtc_sdm845_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;
phy_common->submode = submode;
return 0;
}
static const struct phy_ops ufs_qcom_phy_qrbtc_sdm845_phy_ops = {
.init = ufs_qcom_phy_qrbtc_sdm845_init,
.exit = ufs_qcom_phy_qrbtc_sdm845_exit,
.set_mode = ufs_qcom_phy_qrbtc_sdm845_set_mode,
.calibrate = ufs_qcom_phy_qrbtc_sdm845_phy_calibrate,
.owner = THIS_MODULE,
};
static struct ufs_qcom_phy_specific_ops phy_qrbtc_sdm845_ops = {
.start_serdes = ufs_qcom_phy_qrbtc_sdm845_start_serdes,
.is_physical_coding_sublayer_ready =
ufs_qcom_phy_qrbtc_sdm845_is_pcs_ready,
};
static int ufs_qcom_phy_qrbtc_sdm845_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct phy *generic_phy;
struct ufs_qcom_phy_qrbtc_sdm845 *phy;
int err = 0;
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
if (!phy) {
err = -ENOMEM;
goto out;
}
generic_phy = ufs_qcom_phy_generic_probe(pdev, &phy->common_cfg,
&ufs_qcom_phy_qrbtc_sdm845_phy_ops, &phy_qrbtc_sdm845_ops);
if (!generic_phy) {
dev_err(dev, "%s: ufs_qcom_phy_generic_probe() failed\n",
__func__);
err = -EIO;
goto out;
}
phy_set_drvdata(generic_phy, phy);
strscpy(phy->common_cfg.name, UFS_PHY_NAME,
sizeof(phy->common_cfg.name));
out:
return err;
}
static const struct of_device_id ufs_qcom_phy_qrbtc_sdm845_of_match[] = {
{.compatible = "qcom,ufs-phy-qrbtc-sdm845"},
{},
};
MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qrbtc_sdm845_of_match);
static struct platform_driver ufs_qcom_phy_qrbtc_sdm845_driver = {
.probe = ufs_qcom_phy_qrbtc_sdm845_probe,
.driver = {
.of_match_table = ufs_qcom_phy_qrbtc_sdm845_of_match,
.name = "ufs_qcom_phy_qrbtc_sdm845",
},
};
module_platform_driver(ufs_qcom_phy_qrbtc_sdm845_driver);
MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QRBTC SDM845");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,182 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2016 - 2021, Linux Foundation. All rights reserved.
* 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.
*
*/
#ifndef UFS_QCOM_PHY_QRBTC_SDM845_H_
#define UFS_QCOM_PHY_QRBTC_SDM845_H_
#include "phy-qcom-ufs-i.h"
/* QCOM UFS PHY control registers */
#define COM_OFF(x) (0x000 + x)
#define TX_OFF(n, x) (0x400 + (0x400 * n) + x)
#define RX_OFF(n, x) (0x600 + (0x400 * n) + x)
#define PHY_OFF(x) (0xC00 + x)
#define PHY_USR(x) (x)
/* UFS PHY PLL block registers */
#define QSERDES_COM_SYS_CLK_CTRL COM_OFF(0x00)
#define QSERDES_COM_PLL_VCOTAIL_EN COM_OFF(0x04)
#define QSERDES_COM_PLL_CNTRL COM_OFF(0x14)
#define QSERDES_COM_PLL_IP_SETI COM_OFF(0x18)
#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN COM_OFF(0x20)
#define QSERDES_COM_PLL_CP_SETI COM_OFF(0x24)
#define QSERDES_COM_PLL_IP_SETP COM_OFF(0x28)
#define QSERDES_COM_PLL_CP_SETP COM_OFF(0x2C)
#define QSERDES_COM_SYSCLK_EN_SEL COM_OFF(0x38)
#define QSERDES_COM_RES_CODE_TXBAND COM_OFF(0x3C)
#define QSERDES_COM_RESETSM_CNTRL COM_OFF(0x40)
#define QSERDES_COM_PLLLOCK_CMP1 COM_OFF(0x44)
#define QSERDES_COM_PLLLOCK_CMP2 COM_OFF(0x48)
#define QSERDES_COM_PLLLOCK_CMP3 COM_OFF(0x4C)
#define QSERDES_COM_PLLLOCK_CMP_EN COM_OFF(0x50)
#define QSERDES_COM_DEC_START1 COM_OFF(0x64)
#define QSERDES_COM_DIV_FRAC_START1 COM_OFF(0x98)
#define QSERDES_COM_DIV_FRAC_START2 COM_OFF(0x9C)
#define QSERDES_COM_DIV_FRAC_START3 COM_OFF(0xA0)
#define QSERDES_COM_DEC_START2 COM_OFF(0xA4)
#define QSERDES_COM_PLL_RXTXEPCLK_EN COM_OFF(0xA8)
#define QSERDES_COM_PLL_CRCTRL COM_OFF(0xAC)
#define QSERDES_COM_PLL_CLKEPDIV COM_OFF(0xB0)
#define QSERDES_COM_RESET_SM COM_OFF(0xBC)
/* TX LANE n (0, 1) registers */
#define QSERDES_TX_CLKBUF_ENABLE(n) TX_OFF(n, 0x4)
/* RX LANE n (0, 1) registers */
#define QSERDES_RX_CDR_CONTROL(n) RX_OFF(n, 0x0)
#define QSERDES_RX_RX_IQ_RXDET_EN(n) RX_OFF(n, 0x28)
#define QSERDES_RX_SIGDET_CNTRL(n) RX_OFF(n, 0x34)
#define QSERDES_RX_RX_BAND(n) RX_OFF(n, 0x38)
#define QSERDES_RX_CDR_CONTROL_HALF(n) RX_OFF(n, 0x98)
#define QSERDES_RX_CDR_CONTROL_QUARTER(n) RX_OFF(n, 0x9C)
#define QSERDES_RX_PWM_CNTRL1(n) RX_OFF(n, 0x80)
#define QSERDES_RX_PWM_CNTRL2(n) RX_OFF(n, 0x84)
#define QSERDES_RX_PWM_NDIV(n) RX_OFF(n, 0x88)
#define QSERDES_RX_SIGDET_CNTRL2(n) RX_OFF(n, 0x8C)
#define QSERDES_RX_UFS_CNTRL(n) RX_OFF(n, 0x90)
/* UFS PHY registers */
#define UFS_PHY_PHY_START PHY_OFF(0x00)
#define UFS_PHY_POWER_DOWN_CONTROL PHY_OFF(0x04)
#define UFS_PHY_TIMER_20US_CORECLK_STEPS_MSB PHY_OFF(0x08)
#define UFS_PHY_TIMER_20US_CORECLK_STEPS_LSB PHY_OFF(0x0C)
#define UFS_PHY_RX_SYM_RESYNC_CTRL PHY_OFF(0x134)
#define UFS_PHY_MULTI_LANE_CTRL1 PHY_OFF(0x1C4)
/* QRBTC V2 USER REGISTERS */
#define U11_UFS_RESET_REG_OFFSET PHY_USR(0x4)
#define U11_QRBTC_CONTROL_OFFSET PHY_USR(0x18)
#define U11_QRBTC_TX_CLK_CTRL PHY_USR(0x20)
static struct ufs_qcom_phy_calibration phy_cal_table_rate_A[] = {
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_PHY_START, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SYM_RESYNC_CTRL, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_TIMER_20US_CORECLK_STEPS_MSB, 0x0F),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_TIMER_20US_CORECLK_STEPS_LSB, 0x00),
/* QSERDES Common */
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYS_CLK_CTRL, 0x16),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RES_CODE_TXBAND, 0xC0),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_VCOTAIL_EN, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CNTRL, 0x24),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CLKEPDIV, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x10),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RXTXEPCLK_EN, 0x13),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CRCTRL, 0x43),
/* QSERDES TX */
/* Enable large amplitude setting */
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_CLKBUF_ENABLE(0), 0x29),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_CLKBUF_ENABLE(1), 0x29),
/* QSERDES RX0 */
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_PWM_CNTRL1(0), 0x08),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_PWM_CNTRL2(0), 0x40),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_PWM_NDIV(0), 0x30),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL(0), 0x40),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(0), 0x0C),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(0), 0x12),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_CNTRL(0), 0xC0),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_CNTRL2(0), 0x07),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_BAND(0), 0x06),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_UFS_CNTRL(0), 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_IQ_RXDET_EN(0), 0xF3),
/* QSERDES RX1 */
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_PWM_CNTRL1(1), 0x08),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_PWM_CNTRL2(1), 0x40),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_PWM_NDIV(1), 0x30),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL(1), 0x40),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(1), 0x0C),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(1), 0x12),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_CNTRL(1), 0xC0),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_CNTRL2(1), 0x07),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_BAND(1), 0x06),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_UFS_CNTRL(1), 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_IQ_RXDET_EN(1), 0xF3),
/* QSERDES PLL Settings - Series A */
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x82),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x80),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0x80),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x10),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0xFF),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x19),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x07),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x0F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x07),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x01),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_MULTI_LANE_CTRL1, 0x02),
};
static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = {
/* QSERDES PLL Settings - Series B */
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x98),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x80),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0x80),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x10),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0x65),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x1E),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x07),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x0F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x07),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x01),
};
/*
* This structure represents the qrbtc-sdm845 specific phy.
* common_cfg MUST remain the first field in this structure
* in case extra fields are added. This way, when calling
* get_ufs_qcom_phy() of generic phy, we can extract the
* common phy structure (struct ufs_qcom_phy) out of it
* regardless of the relevant specific phy.
*/
struct ufs_qcom_phy_qrbtc_sdm845 {
struct ufs_qcom_phy common_cfg;
};
#endif

View File

@ -0,0 +1,856 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2013-2021, Linux Foundation. All rights reserved.
*/
#include "phy-qcom-ufs-i.h"
#define MAX_PROP_NAME 32
#define VDDA_PHY_MIN_UV 800000
#define VDDA_PHY_MAX_UV 925000
#define VDDA_PLL_MIN_UV 1200000
#define VDDA_PLL_MAX_UV 1800000
#define VDDP_REF_CLK_MIN_UV 1200000
#define VDDP_REF_CLK_MAX_UV 1200000
#define VDDA_QREF_MIN_UV 880000
#define VDDA_QREF_MAX_UV 912000
#define UFS_PHY_DEFAULT_LANES_PER_DIRECTION 1
static int ufs_qcom_phy_start_serdes(struct ufs_qcom_phy *ufs_qcom_phy);
static int ufs_qcom_phy_is_pcs_ready(struct ufs_qcom_phy *ufs_qcom_phy);
void ufs_qcom_phy_write_tbl(struct ufs_qcom_phy *ufs_qcom_phy,
struct ufs_qcom_phy_calibration *tbl,
int tbl_size)
{
int i;
for (i = 0; i < tbl_size; i++)
writel_relaxed(tbl[i].cfg_value,
ufs_qcom_phy->mmio + tbl[i].reg_offset);
}
EXPORT_SYMBOL(ufs_qcom_phy_write_tbl);
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)
{
struct device *dev = ufs_qcom_phy->dev;
int ret = 0;
ret = reset_control_assert(ufs_qcom_phy->ufs_reset);
if (ret) {
dev_err(dev, "Failed to assert UFS PHY reset %d\n", ret);
goto out;
}
if (!tbl_A) {
dev_err(dev, "%s: tbl_A is NULL\n", __func__);
ret = EINVAL;
goto out;
}
ufs_qcom_phy_write_tbl(ufs_qcom_phy, tbl_A, tbl_size_A);
/*
* 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(dev, "%s: tbl_B is NULL\n",
__func__);
ret = EINVAL;
goto out;
}
ufs_qcom_phy_write_tbl(ufs_qcom_phy, tbl_B, tbl_size_B);
}
/* flush buffered writes */
mb();
ret = reset_control_deassert(ufs_qcom_phy->ufs_reset);
if (ret)
dev_err(dev, "Failed to deassert UFS PHY reset %d\n", ret);
ret = ufs_qcom_phy_start_serdes(ufs_qcom_phy);
if (ret)
goto out;
ret = ufs_qcom_phy_is_pcs_ready(ufs_qcom_phy);
out:
return ret;
}
EXPORT_SYMBOL(ufs_qcom_phy_calibrate);
/*
* 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);
}
EXPORT_SYMBOL(get_ufs_qcom_phy);
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);
return err;
}
return 0;
}
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;
}
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;
out:
return generic_phy;
}
EXPORT_SYMBOL(ufs_qcom_phy_generic_probe);
int ufs_qcom_phy_get_reset(struct ufs_qcom_phy *phy_common)
{
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;
}
EXPORT_SYMBOL(ufs_qcom_phy_get_reset);
static int __ufs_qcom_phy_clk_get(struct device *dev,
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 get %s, %d\n", name, err);
} else {
*clk_out = clk;
}
return err;
}
static int ufs_qcom_phy_clk_get(struct device *dev,
const char *name, struct clk **clk_out)
{
return __ufs_qcom_phy_clk_get(dev, name, clk_out, true);
}
int ufs_qcom_phy_init_clks(struct ufs_qcom_phy *phy_common)
{
int err;
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);
/*
* 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",
&phy_common->ref_clk_src);
if (err)
goto out;
/*
* "ref_clk_parent" is optional hence don't abort init if it's not
* found.
*/
__ufs_qcom_phy_clk_get(phy_common->dev, "ref_clk_parent",
&phy_common->ref_clk_parent, false);
/*
* 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);
/*
* "qref_clk_signal" is optional. It is needed for certain platforms.
* No need to abort if it's not present.
*/
__ufs_qcom_phy_clk_get(phy_common->dev, "qref_clk",
&phy_common->qref_clk, false);
__ufs_qcom_phy_clk_get(phy_common->dev, "rx_sym0_mux_clk",
&phy_common->rx_sym0_mux_clk, false);
__ufs_qcom_phy_clk_get(phy_common->dev, "rx_sym1_mux_clk",
&phy_common->rx_sym1_mux_clk, false);
__ufs_qcom_phy_clk_get(phy_common->dev, "tx_sym0_mux_clk",
&phy_common->tx_sym0_mux_clk, false);
__ufs_qcom_phy_clk_get(phy_common->dev, "rx_sym0_phy_clk",
&phy_common->rx_sym0_phy_clk, false);
__ufs_qcom_phy_clk_get(phy_common->dev, "rx_sym1_phy_clk",
&phy_common->rx_sym1_phy_clk, false);
__ufs_qcom_phy_clk_get(phy_common->dev, "tx_sym0_phy_clk",
&phy_common->tx_sym0_phy_clk, false);
out:
return err;
}
EXPORT_SYMBOL(ufs_qcom_phy_init_clks);
static int ufs_qcom_phy_init_vreg(struct device *dev,
struct ufs_qcom_phy_vreg *vreg,
const char *name)
{
int err = 0;
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)) {
err = PTR_ERR(vreg->reg);
dev_err(dev, "failed to get %s, %d\n", name, err);
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 (!vreg->max_uA) {
err = 0;
} else 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;
} else if (!strcmp(name, "vdda-qref")) {
vreg->max_uV = VDDA_QREF_MAX_UV;
vreg->min_uV = VDDA_QREF_MIN_UV;
}
out:
return err;
}
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;
ufs_qcom_phy_init_vreg(phy_common->dev, &phy_common->vddp_ref_clk,
"vddp-ref-clk");
ufs_qcom_phy_init_vreg(phy_common->dev, &phy_common->vdd_phy_gdsc,
"vdd-phy-gdsc");
ufs_qcom_phy_init_vreg(phy_common->dev, &phy_common->vdda_qref,
"vdda-qref");
out:
return err;
}
EXPORT_SYMBOL(ufs_qcom_phy_init_vregulators);
static int ufs_qcom_phy_cfg_vreg(struct device *dev,
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;
ret = regulator_set_load(reg, uA_load);
if (ret >= 0) {
/*
* regulator_set_load() returns new regulator
* 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;
}
static int ufs_qcom_phy_enable_vreg(struct device *dev,
struct ufs_qcom_phy_vreg *vreg)
{
int ret = 0;
if (!vreg || vreg->enabled)
goto out;
ret = ufs_qcom_phy_cfg_vreg(dev, vreg, true);
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;
}
static int ufs_qcom_phy_enable_ref_clk(struct ufs_qcom_phy *phy)
{
int ret = 0;
if (phy->is_ref_clk_enabled)
goto out;
/* qref clk signal is optional */
if (phy->qref_clk)
clk_prepare_enable(phy->qref_clk);
/*
* 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;
}
}
/*
* "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);
out_disable_src:
clk_disable_unprepare(phy->ref_clk_src);
out:
return ret;
}
static int ufs_qcom_phy_disable_vreg(struct device *dev,
struct ufs_qcom_phy_vreg *vreg)
{
int ret = 0;
if (!vreg || !vreg->enabled)
goto out;
ret = regulator_disable(vreg->reg);
if (!ret) {
/* ignore errors on applying disable config */
ufs_qcom_phy_cfg_vreg(dev, vreg, false);
vreg->enabled = false;
} else {
dev_err(dev, "%s: %s disable failed, err=%d\n",
__func__, vreg->name, ret);
}
out:
return ret;
}
static void ufs_qcom_phy_disable_ref_clk(struct ufs_qcom_phy *phy)
{
if (phy->is_ref_clk_enabled) {
/*
* "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.
*/
if (phy->ref_clk_parent)
clk_disable_unprepare(phy->ref_clk_parent);
clk_disable_unprepare(phy->ref_clk_src);
/* qref clk signal is optional */
if (phy->qref_clk)
clk_disable_unprepare(phy->qref_clk);
phy->is_ref_clk_enabled = false;
}
}
/* Turn ON M-PHY RMMI interface clocks */
static int ufs_qcom_phy_enable_iface_clk(struct ufs_qcom_phy *phy)
{
int ret = 0;
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",
__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 */
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);
phy->is_iface_clk_enabled = false;
}
}
static int ufs_qcom_phy_start_serdes(struct ufs_qcom_phy *ufs_qcom_phy)
{
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 = -EOPNOTSUPP;
} else {
ufs_qcom_phy->phy_spec_ops->start_serdes(ufs_qcom_phy);
}
return ret;
}
void 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);
if (ufs_qcom_phy->phy_spec_ops->set_tx_lane_enable)
ufs_qcom_phy->phy_spec_ops->set_tx_lane_enable(ufs_qcom_phy,
tx_lanes);
}
EXPORT_SYMBOL(ufs_qcom_phy_set_tx_lane_enable);
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;
}
EXPORT_SYMBOL(ufs_qcom_phy_save_controller_version);
void ufs_qcom_phy_set_src_clk_h8_enter(struct phy *generic_phy)
{
struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
if (!ufs_qcom_phy->rx_sym0_mux_clk || !ufs_qcom_phy->rx_sym1_mux_clk ||
!ufs_qcom_phy->tx_sym0_mux_clk || !ufs_qcom_phy->ref_clk_src) {
dev_err(ufs_qcom_phy->dev, "%s: null clock\n", __func__);
return;
}
/*
* Before entering hibernate, select xo as source of symbol
* clocks according to the UFS Host Controller Hardware
* Programming Guide's "Hibernate enter with power collapse".
*/
clk_set_parent(ufs_qcom_phy->rx_sym0_mux_clk, ufs_qcom_phy->ref_clk_src);
clk_set_parent(ufs_qcom_phy->rx_sym1_mux_clk, ufs_qcom_phy->ref_clk_src);
clk_set_parent(ufs_qcom_phy->tx_sym0_mux_clk, ufs_qcom_phy->ref_clk_src);
}
EXPORT_SYMBOL(ufs_qcom_phy_set_src_clk_h8_enter);
void ufs_qcom_phy_set_src_clk_h8_exit(struct phy *generic_phy)
{
struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
if (!ufs_qcom_phy->rx_sym0_mux_clk ||
!ufs_qcom_phy->rx_sym1_mux_clk ||
!ufs_qcom_phy->tx_sym0_mux_clk ||
!ufs_qcom_phy->rx_sym0_phy_clk ||
!ufs_qcom_phy->rx_sym1_phy_clk ||
!ufs_qcom_phy->tx_sym0_phy_clk) {
dev_err(ufs_qcom_phy->dev, "%s: null clock\n", __func__);
return;
}
/*
* Refer to the UFS Host Controller Hardware Programming Guide's
* section "Hibernate exit from power collapse". Select phy clocks
* as source of the PHY symbol clocks.
*/
clk_set_parent(ufs_qcom_phy->rx_sym0_mux_clk, ufs_qcom_phy->rx_sym0_phy_clk);
clk_set_parent(ufs_qcom_phy->rx_sym1_mux_clk, ufs_qcom_phy->rx_sym1_phy_clk);
clk_set_parent(ufs_qcom_phy->tx_sym0_mux_clk, ufs_qcom_phy->tx_sym0_phy_clk);
}
EXPORT_SYMBOL(ufs_qcom_phy_set_src_clk_h8_exit);
static int ufs_qcom_phy_is_pcs_ready(struct ufs_qcom_phy *ufs_qcom_phy)
{
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 -EOPNOTSUPP;
}
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;
int err;
if (phy_common->vdd_phy_gdsc.reg) {
err = ufs_qcom_phy_enable_vreg(dev, &phy_common->vdd_phy_gdsc);
if (err) {
dev_err(dev, "%s enable phy_gdsc failed, err=%d\n",
__func__, err);
goto out;
}
}
if (phy_common->vdda_qref.reg) {
err = ufs_qcom_phy_enable_vreg(dev, &phy_common->vdda_qref);
if (err) {
dev_err(dev, "%s enable vdda_qref failed, err=%d\n",
__func__, err);
goto out;
}
}
err = ufs_qcom_phy_enable_vreg(dev, &phy_common->vdda_phy);
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 */
err = ufs_qcom_phy_enable_vreg(dev, &phy_common->vdda_pll);
if (err) {
dev_err(dev, "%s enable vdda_pll failed, err=%d\n",
__func__, err);
goto out_disable_phy;
}
err = ufs_qcom_phy_enable_iface_clk(phy_common);
if (err) {
dev_err(dev, "%s enable phy iface clock failed, err=%d\n",
__func__, err);
goto out_disable_pll;
}
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;
}
/* enable device PHY ref_clk pad rail */
if (phy_common->vddp_ref_clk.reg) {
err = ufs_qcom_phy_enable_vreg(dev,
&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:
ufs_qcom_phy_disable_ref_clk(phy_common);
out_disable_iface_clk:
ufs_qcom_phy_disable_iface_clk(phy_common);
out_disable_pll:
ufs_qcom_phy_disable_vreg(dev, &phy_common->vdda_pll);
out_disable_phy:
ufs_qcom_phy_disable_vreg(dev, &phy_common->vdda_phy);
out:
return err;
}
EXPORT_SYMBOL(ufs_qcom_phy_power_on);
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)
ufs_qcom_phy_disable_vreg(phy_common->dev,
&phy_common->vddp_ref_clk);
ufs_qcom_phy_disable_ref_clk(phy_common);
ufs_qcom_phy_disable_iface_clk(phy_common);
ufs_qcom_phy_disable_vreg(phy_common->dev, &phy_common->vdda_pll);
ufs_qcom_phy_disable_vreg(phy_common->dev, &phy_common->vdda_phy);
if (phy_common->vdda_qref.reg)
ufs_qcom_phy_disable_vreg(phy_common->dev, &phy_common->vdda_qref);
return 0;
}
EXPORT_SYMBOL(ufs_qcom_phy_power_off);
void ufs_qcom_phy_ctrl_rx_linecfg(struct phy *generic_phy, bool ctrl)
{
struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
if (ufs_qcom_phy->phy_spec_ops->ctrl_rx_linecfg)
ufs_qcom_phy->phy_spec_ops->ctrl_rx_linecfg(ufs_qcom_phy, ctrl);
}
EXPORT_SYMBOL(ufs_qcom_phy_ctrl_rx_linecfg);
int ufs_qcom_phy_dump_regs(struct ufs_qcom_phy *phy, int offset,
int len, char *prefix)
{
u32 *regs;
size_t pos;
if (offset % 4 != 0 || len % 4 != 0) /* keep readl happy */
return -EINVAL;
regs = kzalloc(len, GFP_KERNEL);
if (!regs)
return -ENOMEM;
for (pos = 0; pos < len; pos += 4)
regs[pos / 4] = readl_relaxed(phy->mmio + offset + pos);
print_hex_dump(KERN_ERR, prefix,
len > 4 ? DUMP_PREFIX_OFFSET : DUMP_PREFIX_NONE,
16, 4, regs, len, false);
kfree(regs);
return 0;
}
EXPORT_SYMBOL(ufs_qcom_phy_dump_regs);
void ufs_qcom_phy_dbg_register_dump(struct phy *generic_phy)
{
struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
if (ufs_qcom_phy->phy_spec_ops->dbg_register_dump)
ufs_qcom_phy->phy_spec_ops->dbg_register_dump(ufs_qcom_phy);
}
EXPORT_SYMBOL(ufs_qcom_phy_dbg_register_dump);
MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,18 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2013-2021, Linux Foundation. All rights reserved.
*/
#ifndef PHY_QCOM_UFS_H_
#define PHY_QCOM_UFS_H_
#include "phy.h"
void ufs_qcom_phy_ctrl_rx_linecfg(struct phy *generic_phy, bool ctrl);
void ufs_qcom_phy_set_tx_lane_enable(struct phy *generic_phy, u32 tx_lanes);
void ufs_qcom_phy_dbg_register_dump(struct phy *generic_phy);
void ufs_qcom_phy_set_src_clk_h8_enter(struct phy *generic_phy);
void ufs_qcom_phy_set_src_clk_h8_exit(struct phy *generic_phy);
#endif /* PHY_QCOM_UFS_H_ */