ufs: snapshot of UFS controller & PHY driver

This is a snapshot of UFS driver (host controller and PHY) taken as of
msm-4.4 commit <98b0bcfa> ("wil6210: fix memory access violation in
wil_memcpy_from/toio_32").

Snapshot is taken of following files:
	drivers/scsi/ufs/*
	drivers/phy/phy-qcom-ufs*
	drivers/phy/Makefile
	include/linux/phy/phy-qcom-ufs.h
	include/uapi/scsi/ufs/*
	include/trace/events/ufs.h
	Documentation/scsi/ufs.txt
	Documentation/devicetree/bindings/ufs/*

Fixed some of the minor compilation errrors after taking the snapshot.

Change-Id: Ia0c437de30f17f3f5e331b174522e9f6843599c9
Signed-off-by: Subhash Jadavani <subhashj@codeaurora.org>
This commit is contained in:
Subhash Jadavani 2017-04-01 00:35:51 -07:00
commit 9c8077087e
20 changed files with 1983 additions and 392 deletions

View file

@ -7,11 +7,12 @@ To bind UFS PHY with UFS host controller, the controller node should
contain a phandle reference to UFS PHY node.
Required properties:
- compatible : compatible list, contains one of the following:
- compatible : compatible list, contains one of the following
according to the relevant phy in use:
"qcom,ufs-phy-qmp-14nm"
"qcom,ufs-phy-qmp-v3"
"qcom,ufs-phy-qrbtc-sdm845"
according to the relevant phy in use.
"qcom,ufs-phy-qmp-v3-660"
- reg : should contain PHY register address space (mandatory),
- reg-names : indicates various resources passed to driver (via reg proptery) by name.
Required "reg-names" is "phy_mem".
@ -27,9 +28,6 @@ according to the relevant phy in use.
Optional properties:
- vdda-phy-max-microamp : specifies max. load that can be drawn from phy supply
- vdda-pll-max-microamp : specifies max. load that can be drawn from pll supply
- vddp-ref-clk-supply : phandle to UFS device ref_clk pad power supply
- vddp-ref-clk-max-microamp : specifies max. load that can be drawn from this supply
- vddp-ref-clk-always-on : specifies if this supply needs to be kept always on
- qcom,disable-lpm : disable various LPM mechanisms in UFS for platform compatibility
(limit link to PWM Gear-1, 1-lane slow mode; disable hibernate, and avoid suspend/resume)

View file

@ -13,6 +13,9 @@ Required properties:
- reg : <registers mapping>
first entry should contain UFS host controller register address space (mandatory),
second entry is the device ref. clock control register map (optional).
- reset : reset specifier pair consists of phandle for the reset provider
and reset lines used by this controller.
- reset-names : reset signal name strings sorted in the same order as the resets property.
Optional properties:
- phys : phandle to UFS PHY node
@ -52,6 +55,8 @@ Optional properties:
- lanes-per-direction: number of lanes available per direction - either 1 or 2.
Note that it is assume same number of lanes is used both directions at once.
If not specified, default is 2 lanes per direction.
- pinctrl-names, pinctrl-0, pinctrl-1,.. pinctrl-n: Refer to "Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt"
for these optional properties
- limit-tx-hs-gear : Specify the max. limit on the TX HS gear.
Valid range: 1-3. 1 => HS-G1, 2 => HS-G2, 3 => HS-G3
- limit-rx-hs-gear : Specify the max. limit on the RX HS gear. Refer "limit-tx-hs-gear" for expected values.
@ -89,6 +94,8 @@ Example:
clocks = <&core 0>, <&ref 0>, <&iface 0>;
clock-names = "core_clk", "ref_clk", "iface_clk";
freq-table-hz = <100000000 200000000>, <0 0>, <0 0>;
resets = <clock_gcc GCC_UFS_BCR>;
reset-names = "core_reset";
phys = <&ufsphy1>;
phy-names = "ufsphy";
rpm-level = <3>;
@ -146,6 +153,9 @@ enabled and functional in the driver:
- qcom,pm-qos-default-cpu: PM QoS voting is based on the cpu associated with each IO request by the block layer.
This defined the default cpu used for PM QoS voting in case a specific cpu value is not available.
- qcom,vddp-ref-clk-supply : reference clock to ufs device. Controlled by the host driver.
- qcom,vddp-ref-clk-max-microamp : specifies max. load that can be drawn for
ref-clk supply.
Example:
ufshc@0xfc598000 {
...

View file

@ -55,6 +55,7 @@ obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs.o
obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-14nm.o
obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-v3.o
obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qrbtc-sdm845.o
obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-v3-660.o
obj-$(CONFIG_PHY_TUSB1210) += phy-tusb1210.o
obj-$(CONFIG_PHY_BRCM_SATA) += phy-brcm-sata.o
obj-$(CONFIG_PHY_PISTACHIO_USB) += phy-pistachio-usb.o

View file

@ -152,6 +152,7 @@ struct ufs_qcom_phy {
* and writes to QSERDES_RX_SIGDET_CNTRL attribute
* @configure_lpm: pointer to a function that configures the phy
* for low power mode.
* @dbg_register_dump: pointer to a function that dumps phy registers for debug.
*/
struct ufs_qcom_phy_specific_ops {
int (*calibrate_phy)(struct ufs_qcom_phy *phy, bool is_rate_B);
@ -161,6 +162,7 @@ struct ufs_qcom_phy_specific_ops {
void (*ctrl_rx_linecfg)(struct ufs_qcom_phy *phy, bool ctrl);
void (*power_control)(struct ufs_qcom_phy *phy, bool val);
int (*configure_lpm)(struct ufs_qcom_phy *phy, bool enable);
void (*dbg_register_dump)(struct ufs_qcom_phy *phy);
};
struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy);
@ -184,5 +186,6 @@ int ufs_qcom_phy_calibrate(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);
void ufs_qcom_phy_dump_regs(struct ufs_qcom_phy *phy,
int offset, int len, char *prefix);
#endif

View file

@ -0,0 +1,260 @@
/*
* Copyright (c) 2013-2017, The 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-qmp-v3-660.h"
#define UFS_PHY_NAME "ufs_phy_qmp_v3_660"
static
int ufs_qcom_phy_qmp_v3_660_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
bool is_rate_B)
{
int err;
int tbl_size_A, tbl_size_B;
struct ufs_qcom_phy_calibration *tbl_A, *tbl_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;
tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
tbl_B = phy_cal_table_rate_B;
if ((major == 0x3) && (minor == 0x001) && (step == 0x001)) {
tbl_A = phy_cal_table_rate_A_3_1_1;
tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_3_1_1);
} else {
dev_err(ufs_qcom_phy->dev,
"%s: Unknown UFS-PHY version (major 0x%x minor 0x%x step 0x%x), no calibration values\n",
__func__, major, minor, step);
err = -ENODEV;
goto out;
}
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 int ufs_qcom_phy_qmp_v3_660_init(struct phy *generic_phy)
{
struct ufs_qcom_phy_qmp_v3_660 *phy = phy_get_drvdata(generic_phy);
struct ufs_qcom_phy *phy_common = &phy->common_cfg;
int err;
err = ufs_qcom_phy_init_clks(generic_phy, phy_common);
if (err) {
dev_err(phy_common->dev, "%s: ufs_qcom_phy_init_clks() failed %d\n",
__func__, err);
goto out;
}
err = ufs_qcom_phy_init_vregulators(generic_phy, phy_common);
if (err) {
dev_err(phy_common->dev, "%s: ufs_qcom_phy_init_vregulators() failed %d\n",
__func__, err);
goto out;
}
out:
return err;
}
static
void ufs_qcom_phy_qmp_v3_660_power_control(struct ufs_qcom_phy *phy,
bool power_ctrl)
{
if (!power_ctrl) {
/* apply analog power collapse */
writel_relaxed(0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
/*
* Make sure that PHY knows its analog rail is going to be
* powered OFF.
*/
mb();
} else {
/* bring PHY out of analog power collapse */
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();
}
}
static inline
void ufs_qcom_phy_qmp_v3_660_set_tx_lane_enable(struct ufs_qcom_phy *phy,
u32 val)
{
/*
* v3 PHY does not have TX_LANE_ENABLE register.
* Implement this function so as not to propagate error to caller.
*/
}
static
void ufs_qcom_phy_qmp_v3_660_ctrl_rx_linecfg(struct ufs_qcom_phy *phy,
bool ctrl)
{
u32 temp;
temp = readl_relaxed(phy->mmio + UFS_PHY_LINECFG_DISABLE);
if (ctrl) /* enable RX LineCfg */
temp &= ~UFS_PHY_RX_LINECFG_DISABLE_BIT;
else /* disable RX LineCfg */
temp |= UFS_PHY_RX_LINECFG_DISABLE_BIT;
writel_relaxed(temp, phy->mmio + UFS_PHY_LINECFG_DISABLE);
/* Make sure that RX LineCfg config applied before we return */
mb();
}
static inline void ufs_qcom_phy_qmp_v3_660_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_v3_660_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 void ufs_qcom_phy_qmp_v3_660_dbg_register_dump(
struct ufs_qcom_phy *phy)
{
ufs_qcom_phy_dump_regs(phy, COM_BASE, COM_SIZE,
"PHY QSERDES COM Registers ");
ufs_qcom_phy_dump_regs(phy, PHY_BASE, PHY_SIZE,
"PHY Registers ");
ufs_qcom_phy_dump_regs(phy, RX_BASE, RX_SIZE,
"PHY RX0 Registers ");
ufs_qcom_phy_dump_regs(phy, TX_BASE, TX_SIZE,
"PHY TX0 Registers ");
}
struct phy_ops ufs_qcom_phy_qmp_v3_660_phy_ops = {
.init = ufs_qcom_phy_qmp_v3_660_init,
.exit = ufs_qcom_phy_exit,
.power_on = ufs_qcom_phy_power_on,
.power_off = ufs_qcom_phy_power_off,
.owner = THIS_MODULE,
};
struct ufs_qcom_phy_specific_ops phy_v3_660_ops = {
.calibrate_phy = ufs_qcom_phy_qmp_v3_660_phy_calibrate,
.start_serdes = ufs_qcom_phy_qmp_v3_660_start_serdes,
.is_physical_coding_sublayer_ready =
ufs_qcom_phy_qmp_v3_660_is_pcs_ready,
.set_tx_lane_enable = ufs_qcom_phy_qmp_v3_660_set_tx_lane_enable,
.ctrl_rx_linecfg = ufs_qcom_phy_qmp_v3_660_ctrl_rx_linecfg,
.power_control = ufs_qcom_phy_qmp_v3_660_power_control,
.dbg_register_dump = ufs_qcom_phy_qmp_v3_660_dbg_register_dump,
};
static int ufs_qcom_phy_qmp_v3_660_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct phy *generic_phy;
struct ufs_qcom_phy_qmp_v3_660 *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_qmp_v3_660_phy_ops,
&phy_v3_660_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);
strlcpy(phy->common_cfg.name, UFS_PHY_NAME,
sizeof(phy->common_cfg.name));
out:
return err;
}
static int ufs_qcom_phy_qmp_v3_660_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct phy *generic_phy = to_phy(dev);
struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
int err = 0;
err = ufs_qcom_phy_remove(generic_phy, ufs_qcom_phy);
if (err)
dev_err(dev, "%s: ufs_qcom_phy_remove failed = %d\n",
__func__, err);
return err;
}
static const struct of_device_id ufs_qcom_phy_qmp_v3_660_of_match[] = {
{.compatible = "qcom,ufs-phy-qmp-v3-660"},
{},
};
MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_v3_660_of_match);
static struct platform_driver ufs_qcom_phy_qmp_v3_660_driver = {
.probe = ufs_qcom_phy_qmp_v3_660_probe,
.remove = ufs_qcom_phy_qmp_v3_660_remove,
.driver = {
.of_match_table = ufs_qcom_phy_qmp_v3_660_of_match,
.name = "ufs_qcom_phy_qmp_v3_660",
.owner = THIS_MODULE,
},
};
module_platform_driver(ufs_qcom_phy_qmp_v3_660_driver);
MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP v3 660");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,283 @@
/*
* Copyright (c) 2013-2017, The 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.
*
*/
#ifndef UFS_QCOM_PHY_QMP_V3_660_H_
#define UFS_QCOM_PHY_QMP_V3_660_H_
#include "phy-qcom-ufs-i.h"
/* QCOM UFS PHY control registers */
#define COM_BASE 0x000
#define COM_OFF(x) (COM_BASE + x)
#define COM_SIZE 0x1C0
#define TX_BASE 0x400
#define TX_OFF(x) (TX_BASE + x)
#define TX_SIZE 0x128
#define RX_BASE 0x600
#define RX_OFF(x) (RX_BASE + x)
#define RX_SIZE 0x1FC
#define PHY_BASE 0xC00
#define PHY_OFF(x) (PHY_BASE + x)
#define PHY_SIZE 0x1B4
/* UFS PHY QSERDES COM registers */
#define QSERDES_COM_ATB_SEL1 COM_OFF(0x00)
#define QSERDES_COM_ATB_SEL2 COM_OFF(0x04)
#define QSERDES_COM_FREQ_UPDATE COM_OFF(0x08)
#define QSERDES_COM_BG_TIMER COM_OFF(0x0C)
#define QSERDES_COM_SSC_EN_CENTER COM_OFF(0x10)
#define QSERDES_COM_SSC_ADJ_PER1 COM_OFF(0x14)
#define QSERDES_COM_SSC_ADJ_PER2 COM_OFF(0x18)
#define QSERDES_COM_SSC_PER1 COM_OFF(0x1C)
#define QSERDES_COM_SSC_PER2 COM_OFF(0x20)
#define QSERDES_COM_SSC_STEP_SIZE1 COM_OFF(0x24)
#define QSERDES_COM_SSC_STEP_SIZE2 COM_OFF(0x28)
#define QSERDES_COM_POST_DIV COM_OFF(0x2C)
#define QSERDES_COM_POST_DIV_MUX COM_OFF(0x30)
#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN COM_OFF(0x34)
#define QSERDES_COM_CLK_ENABLE1 COM_OFF(0x38)
#define QSERDES_COM_SYS_CLK_CTRL COM_OFF(0x3C)
#define QSERDES_COM_SYSCLK_BUF_ENABLE COM_OFF(0x40)
#define QSERDES_COM_PLL_EN COM_OFF(0x44)
#define QSERDES_COM_PLL_IVCO COM_OFF(0x48)
#define QSERDES_COM_LOCK_CMP1_MODE0 COM_OFF(0X4C)
#define QSERDES_COM_LOCK_CMP2_MODE0 COM_OFF(0X50)
#define QSERDES_COM_LOCK_CMP3_MODE0 COM_OFF(0X54)
#define QSERDES_COM_LOCK_CMP1_MODE1 COM_OFF(0X58)
#define QSERDES_COM_LOCK_CMP2_MODE1 COM_OFF(0X5C)
#define QSERDES_COM_LOCK_CMP3_MODE1 COM_OFF(0X60)
#define QSERDES_COM_CMD_RSVD0 COM_OFF(0x64)
#define QSERDES_COM_EP_CLOCK_DETECT_CTRL COM_OFF(0x68)
#define QSERDES_COM_SYSCLK_DET_COMP_STATUS COM_OFF(0x6C)
#define QSERDES_COM_BG_TRIM COM_OFF(0x70)
#define QSERDES_COM_CLK_EP_DIV COM_OFF(0x74)
#define QSERDES_COM_CP_CTRL_MODE0 COM_OFF(0x78)
#define QSERDES_COM_CP_CTRL_MODE1 COM_OFF(0x7C)
#define QSERDES_COM_CMN_RSVD1 COM_OFF(0x80)
#define QSERDES_COM_PLL_RCTRL_MODE0 COM_OFF(0x84)
#define QSERDES_COM_PLL_RCTRL_MODE1 COM_OFF(0x88)
#define QSERDES_COM_CMN_RSVD2 COM_OFF(0x8C)
#define QSERDES_COM_PLL_CCTRL_MODE0 COM_OFF(0x90)
#define QSERDES_COM_PLL_CCTRL_MODE1 COM_OFF(0x94)
#define QSERDES_COM_CMN_RSVD3 COM_OFF(0x98)
#define QSERDES_COM_PLL_CNTRL COM_OFF(0x9C)
#define QSERDES_COM_PHASE_SEL_CTRL COM_OFF(0xA0)
#define QSERDES_COM_PHASE_SEL_DC COM_OFF(0xA4)
#define QSERDES_COM_BIAS_EN_CTRL_BY_PSM COM_OFF(0xA8)
#define QSERDES_COM_SYSCLK_EN_SEL COM_OFF(0xAC)
#define QSERDES_COM_CML_SYSCLK_SEL COM_OFF(0xB0)
#define QSERDES_COM_RESETSM_CNTRL COM_OFF(0xB4)
#define QSERDES_COM_RESETSM_CNTRL2 COM_OFF(0xB8)
#define QSERDES_COM_RESTRIM_CTRL COM_OFF(0xBC)
#define QSERDES_COM_RESTRIM_CTRL2 COM_OFF(0xC0)
#define QSERDES_COM_LOCK_CMP_EN COM_OFF(0xC8)
#define QSERDES_COM_LOCK_CMP_CFG COM_OFF(0xCC)
#define QSERDES_COM_DEC_START_MODE0 COM_OFF(0xD0)
#define QSERDES_COM_DEC_START_MODE1 COM_OFF(0xD4)
#define QSERDES_COM_VCOCAL_DEADMAN_CTRL COM_OFF(0xD8)
#define QSERDES_COM_DIV_FRAC_START1_MODE0 COM_OFF(0xDC)
#define QSERDES_COM_DIV_FRAC_START2_MODE0 COM_OFF(0xE0)
#define QSERDES_COM_DIV_FRAC_START3_MODE0 COM_OFF(0xE4)
#define QSERDES_COM_DIV_FRAC_START1_MODE1 COM_OFF(0xE8)
#define QSERDES_COM_DIV_FRAC_START2_MODE1 COM_OFF(0xEC)
#define QSERDES_COM_DIV_FRAC_START3_MODE1 COM_OFF(0xF0)
#define QSERDES_COM_VCO_TUNE_MINVAL1 COM_OFF(0xF4)
#define QSERDES_COM_VCO_TUNE_MINVAL2 COM_OFF(0xF8)
#define QSERDES_COM_CMN_RSVD4 COM_OFF(0xFC)
#define QSERDES_COM_INTEGLOOP_INITVAL COM_OFF(0x100)
#define QSERDES_COM_INTEGLOOP_EN COM_OFF(0x104)
#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0 COM_OFF(0x108)
#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0 COM_OFF(0x10C)
#define QSERDES_COM_INTEGLOOP_GAIN0_MODE1 COM_OFF(0x110)
#define QSERDES_COM_INTEGLOOP_GAIN1_MODE1 COM_OFF(0x114)
#define QSERDES_COM_VCO_TUNE_MAXVAL1 COM_OFF(0x118)
#define QSERDES_COM_VCO_TUNE_MAXVAL2 COM_OFF(0x11C)
#define QSERDES_COM_RES_TRIM_CONTROL2 COM_OFF(0x120)
#define QSERDES_COM_VCO_TUNE_CTRL COM_OFF(0x124)
#define QSERDES_COM_VCO_TUNE_MAP COM_OFF(0x128)
#define QSERDES_COM_VCO_TUNE1_MODE0 COM_OFF(0x12C)
#define QSERDES_COM_VCO_TUNE2_MODE0 COM_OFF(0x130)
#define QSERDES_COM_VCO_TUNE1_MODE1 COM_OFF(0x134)
#define QSERDES_COM_VCO_TUNE2_MODE1 COM_OFF(0x138)
#define QSERDES_COM_VCO_TUNE_INITVAL1 COM_OFF(0x13C)
#define QSERDES_COM_VCO_TUNE_INITVAL2 COM_OFF(0x140)
#define QSERDES_COM_VCO_TUNE_TIMER1 COM_OFF(0x144)
#define QSERDES_COM_VCO_TUNE_TIMER2 COM_OFF(0x148)
#define QSERDES_COM_SAR COM_OFF(0x14C)
#define QSERDES_COM_SAR_CLK COM_OFF(0x150)
#define QSERDES_COM_SAR_CODE_OUT_STATUS COM_OFF(0x154)
#define QSERDES_COM_SAR_CODE_READY_STATUS COM_OFF(0x158)
#define QSERDES_COM_CMN_STATUS COM_OFF(0x15C)
#define QSERDES_COM_RESET_SM_STATUS COM_OFF(0x160)
#define QSERDES_COM_RESTRIM_CODE_STATUS COM_OFF(0x164)
#define QSERDES_COM_PLLCAL_CODE1_STATUS COM_OFF(0x168)
#define QSERDES_COM_PLLCAL_CODE2_STATUS COM_OFF(0x16C)
#define QSERDES_COM_BG_CTRL COM_OFF(0x170)
#define QSERDES_COM_CLK_SELECT COM_OFF(0x174)
#define QSERDES_COM_HSCLK_SEL COM_OFF(0x178)
#define QSERDES_COM_INTEGLOOP_BINCODE_STATUS COM_OFF(0x17C)
#define QSERDES_COM_PLL_ANALOG COM_OFF(0x180)
#define QSERDES_COM_CORECLK_DIV COM_OFF(0x184)
#define QSERDES_COM_SW_RESET COM_OFF(0x188)
#define QSERDES_COM_CORE_CLK_EN COM_OFF(0x18C)
#define QSERDES_COM_C_READY_STATUS COM_OFF(0x190)
#define QSERDES_COM_CMN_CONFIG COM_OFF(0x194)
#define QSERDES_COM_CMN_RATE_OVERRIDE COM_OFF(0x198)
#define QSERDES_COM_SVS_MODE_CLK_SEL COM_OFF(0x19C)
#define QSERDES_COM_DEBUG_BUS0 COM_OFF(0x1A0)
#define QSERDES_COM_DEBUG_BUS1 COM_OFF(0x1A4)
#define QSERDES_COM_DEBUG_BUS2 COM_OFF(0x1A8)
#define QSERDES_COM_DEBUG_BUS3 COM_OFF(0x1AC)
#define QSERDES_COM_DEBUG_BUS_SEL COM_OFF(0x1B0)
#define QSERDES_COM_CMN_MISC1 COM_OFF(0x1B4)
#define QSERDES_COM_CORECLK_DIV_MODE1 COM_OFF(0x1BC)
#define QSERDES_COM_CMN_RSVD5 COM_OFF(0x1C0)
/* UFS PHY registers */
#define UFS_PHY_PHY_START PHY_OFF(0x00)
#define UFS_PHY_POWER_DOWN_CONTROL PHY_OFF(0x04)
#define UFS_PHY_TX_LARGE_AMP_DRV_LVL PHY_OFF(0x34)
#define UFS_PHY_TX_SMALL_AMP_DRV_LVL PHY_OFF(0x3C)
#define UFS_PHY_RX_MIN_STALL_NOCONFIG_TIME_CAP PHY_OFF(0xCC)
#define UFS_PHY_LINECFG_DISABLE PHY_OFF(0x138)
#define UFS_PHY_RX_SYM_RESYNC_CTRL PHY_OFF(0x13C)
#define UFS_PHY_RX_SIGDET_CTRL2 PHY_OFF(0x148)
#define UFS_PHY_RX_PWM_GEAR_BAND PHY_OFF(0x154)
#define UFS_PHY_PCS_READY_STATUS PHY_OFF(0x168)
/* UFS PHY TX registers */
#define QSERDES_TX_HIGHZ_TRANSCEIVER_BIAS_DRVR_EN TX_OFF(0x68)
#define QSERDES_TX_LANE_MODE TX_OFF(0x94)
/* UFS PHY RX registers */
#define QSERDES_RX_UCDR_SVS_SO_GAIN_HALF RX_OFF(0x30)
#define QSERDES_RX_UCDR_SVS_SO_GAIN_QUARTER RX_OFF(0x34)
#define QSERDES_RX_UCDR_SVS_SO_GAIN_EIGHTH RX_OFF(0x38)
#define QSERDES_RX_UCDR_SVS_SO_GAIN RX_OFF(0x3C)
#define QSERDES_RX_UCDR_FASTLOCK_FO_GAIN RX_OFF(0x40)
#define QSERDES_RX_UCDR_SO_SATURATION_ENABLE RX_OFF(0x48)
#define QSERDES_RX_RX_TERM_BW RX_OFF(0x90)
#define QSERDES_RX_RX_EQ_GAIN1_LSB RX_OFF(0xC4)
#define QSERDES_RX_RX_EQ_GAIN1_MSB RX_OFF(0xC8)
#define QSERDES_RX_RX_EQ_GAIN2_LSB RX_OFF(0xCC)
#define QSERDES_RX_RX_EQ_GAIN2_MSB RX_OFF(0xD0)
#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2 RX_OFF(0xD8)
#define QSERDES_RX_SIGDET_CNTRL RX_OFF(0x114)
#define QSERDES_RX_SIGDET_LVL RX_OFF(0x118)
#define QSERDES_RX_SIGDET_DEGLITCH_CNTRL RX_OFF(0x11C)
#define QSERDES_RX_RX_INTERFACE_MODE RX_OFF(0x12C)
#define UFS_PHY_RX_LINECFG_DISABLE_BIT BIT(1)
/*
* This structure represents the v3 660 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_qmp_v3_660 {
struct ufs_qcom_phy common_cfg;
};
static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_3_1_1[] = {
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CMN_CONFIG, 0x0e),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL, 0x14),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CLK_SELECT, 0x30),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYS_CLK_CTRL, 0x02),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BG_TIMER, 0x0a),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_HSCLK_SEL, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV, 0x0a),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV_MODE1, 0x0a),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP_EN, 0x01),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_CTRL, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x20),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORE_CLK_EN, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP_CFG, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_TIMER1, 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_TIMER2, 0x3f),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x04),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SVS_MODE_CLK_SEL, 0x05),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START_MODE0, 0x82),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CP_CTRL_MODE0, 0x0b),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RCTRL_MODE0, 0x16),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CCTRL_MODE0, 0x28),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE1_MODE0, 0x28),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE2_MODE0, 0x02),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE0, 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE0, 0x0c),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP3_MODE0, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START_MODE1, 0x98),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1_MODE1, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2_MODE1, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3_MODE1, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CP_CTRL_MODE1, 0x0b),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RCTRL_MODE1, 0x16),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CCTRL_MODE1, 0x28),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE1, 0x80),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE1, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE1_MODE1, 0xd6),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE2_MODE1, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE1, 0x32),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE1, 0x0f),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP3_MODE1, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_HIGHZ_TRANSCEIVER_BIAS_DRVR_EN, 0x45),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE, 0x06),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_LVL, 0x24),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_CNTRL, 0x0F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_INTERFACE_MODE, 0x40),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x1E),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_UCDR_FASTLOCK_FO_GAIN, 0x0B),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_TERM_BW, 0x5B),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB, 0xFF),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB, 0x3F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB, 0xFF),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB, 0x3F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x0D),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IVCO, 0x0F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BG_TRIM, 0x0F),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_PWM_GEAR_BAND, 0x15),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_UCDR_SVS_SO_GAIN_HALF, 0x04),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_UCDR_SVS_SO_GAIN_QUARTER, 0x04),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_UCDR_SVS_SO_GAIN, 0x04),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_UCDR_SO_SATURATION_ENABLE, 0x4B),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_INITVAL1, 0xFF),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_INITVAL2, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL2, 0x6c),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_TX_LARGE_AMP_DRV_LVL, 0x0A),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_TX_SMALL_AMP_DRV_LVL, 0x02),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_MIN_STALL_NOCONFIG_TIME_CAP, 0x28),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SYM_RESYNC_CTRL, 0x03),
};
static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = {
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x44),
};
#endif

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2016, The Linux Foundation. All rights reserved.
* Copyright (c) 2013-2017, The 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
@ -23,12 +23,26 @@ int ufs_qcom_phy_qmp_v3_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
int err;
int tbl_size_A, tbl_size_B;
struct ufs_qcom_phy_calibration *tbl_A, *tbl_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;
tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
tbl_B = phy_cal_table_rate_B;
tbl_A = phy_cal_table_rate_A;
tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A);
if ((major == 0x3) && (minor == 0x000) && (step == 0x0000)) {
tbl_A = phy_cal_table_rate_A_3_0_0;
tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_3_0_0);
} else if ((major == 0x3) && (minor == 0x001) && (step == 0x0000)) {
tbl_A = phy_cal_table_rate_A_3_1_0;
tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_3_1_0);
} else {
dev_err(ufs_qcom_phy->dev,
"%s: Unknown UFS-PHY version (major 0x%x minor 0x%x step 0x%x), no calibration values\n",
__func__, major, minor, step);
err = -ENODEV;
goto out;
}
err = ufs_qcom_phy_calibrate(ufs_qcom_phy,
tbl_A, tbl_size_A,
@ -39,6 +53,8 @@ int ufs_qcom_phy_qmp_v3_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
dev_err(ufs_qcom_phy->dev,
"%s: ufs_qcom_phy_calibrate() failed %d\n",
__func__, err);
out:
return err;
}
@ -178,6 +194,22 @@ out:
return err;
}
static void ufs_qcom_phy_qmp_v3_dbg_register_dump(struct ufs_qcom_phy *phy)
{
ufs_qcom_phy_dump_regs(phy, COM_BASE, COM_SIZE,
"PHY QSERDES COM Registers ");
ufs_qcom_phy_dump_regs(phy, PHY_BASE, PHY_SIZE,
"PHY Registers ");
ufs_qcom_phy_dump_regs(phy, RX_BASE(0), RX_SIZE,
"PHY RX0 Registers ");
ufs_qcom_phy_dump_regs(phy, TX_BASE(0), TX_SIZE,
"PHY TX0 Registers ");
ufs_qcom_phy_dump_regs(phy, RX_BASE(1), RX_SIZE,
"PHY RX1 Registers ");
ufs_qcom_phy_dump_regs(phy, TX_BASE(1), TX_SIZE,
"PHY TX1 Registers ");
}
struct phy_ops ufs_qcom_phy_qmp_v3_phy_ops = {
.init = ufs_qcom_phy_qmp_v3_init,
.exit = ufs_qcom_phy_exit,
@ -194,6 +226,7 @@ struct ufs_qcom_phy_specific_ops phy_v3_ops = {
.ctrl_rx_linecfg = ufs_qcom_phy_qmp_v3_ctrl_rx_linecfg,
.power_control = ufs_qcom_phy_qmp_v3_power_control,
.configure_lpm = ufs_qcom_phy_qmp_v3_configure_lpm,
.dbg_register_dump = ufs_qcom_phy_qmp_v3_dbg_register_dump,
};
static int ufs_qcom_phy_qmp_v3_probe(struct platform_device *pdev)

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2016, The Linux Foundation. All rights reserved.
* Copyright (c) 2013-2017, The 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
@ -18,10 +18,18 @@
#include "phy-qcom-ufs-i.h"
/* QCOM UFS PHY control registers */
#define COM_OFF(x) (0x000 + x)
#define PHY_OFF(x) (0xC00 + x)
#define TX_OFF(n, x) (0x400 + (0x400 * n) + x)
#define RX_OFF(n, x) (0x600 + (0x400 * n) + x)
#define COM_BASE 0x000
#define COM_SIZE 0x18C
#define PHY_BASE 0xC00
#define PHY_SIZE 0x1DC
#define TX_BASE(n) (0x400 + (0x400 * n))
#define TX_SIZE 0x128
#define RX_BASE(n) (0x600 + (0x400 * n))
#define RX_SIZE 0x1FC
#define COM_OFF(x) (COM_BASE + x)
#define PHY_OFF(x) (PHY_BASE + x)
#define TX_OFF(n, x) (TX_BASE(n) + x)
#define RX_OFF(n, x) (RX_BASE(n) + x)
/* UFS PHY QSERDES COM registers */
#define QSERDES_COM_ATB_SEL1 COM_OFF(0x00)
@ -133,9 +141,13 @@
#define UFS_PHY_TX_SMALL_AMP_DRV_LVL PHY_OFF(0x34)
#define UFS_PHY_LINECFG_DISABLE PHY_OFF(0x130)
#define UFS_PHY_RX_SYM_RESYNC_CTRL PHY_OFF(0x134)
#define UFS_PHY_RX_MIN_HIBERN8_TIME PHY_OFF(0x138)
#define UFS_PHY_RX_SIGDET_CTRL1 PHY_OFF(0x13C)
#define UFS_PHY_RX_SIGDET_CTRL2 PHY_OFF(0x140)
#define UFS_PHY_RX_PWM_GEAR_BAND PHY_OFF(0x14C)
#define UFS_PHY_PCS_READY_STATUS PHY_OFF(0x160)
#define UFS_PHY_TX_MID_TERM_CTRL1 PHY_OFF(0x1BC)
#define UFS_PHY_MULTI_LANE_CTRL1 PHY_OFF(0x1C4)
/* UFS PHY TX registers */
#define QSERDES_TX0_TRANSCEIVER_BIAS_EN TX_OFF(0, 0x5C)
@ -143,6 +155,9 @@
#define QSERDES_TX0_LANE_MODE_2 TX_OFF(0, 0x90)
#define QSERDES_TX0_LANE_MODE_3 TX_OFF(0, 0x94)
#define QSERDES_TX1_LANE_MODE_1 TX_OFF(1, 0x8C)
/* UFS PHY RX registers */
#define QSERDES_RX0_UCDR_SVS_SO_GAIN_HALF RX_OFF(0, 0x24)
#define QSERDES_RX0_UCDR_SVS_SO_GAIN_QUARTER RX_OFF(0, 0x28)
@ -163,6 +178,22 @@
#define QSERDES_RX0_SIGDET_DEGLITCH_CNTRL RX_OFF(0, 0x10C)
#define QSERDES_RX0_RX_INTERFACE_MODE RX_OFF(0, 0x11C)
#define QSERDES_RX1_UCDR_SVS_SO_GAIN_HALF RX_OFF(1, 0x24)
#define QSERDES_RX1_UCDR_SVS_SO_GAIN_QUARTER RX_OFF(1, 0x28)
#define QSERDES_RX1_UCDR_SVS_SO_GAIN RX_OFF(1, 0x2C)
#define QSERDES_RX1_UCDR_FASTLOCK_FO_GAIN RX_OFF(1, 0x30)
#define QSERDES_RX1_UCDR_SO_SATURATION_AND_ENABLE RX_OFF(1, 0x34)
#define QSERDES_RX1_UCDR_FASTLOCK_COUNT_LOW RX_OFF(1, 0x3C)
#define QSERDES_RX1_UCDR_PI_CONTROLS RX_OFF(1, 0x44)
#define QSERDES_RX1_RX_TERM_BW RX_OFF(1, 0x7C)
#define QSERDES_RX1_RX_EQU_ADAPTOR_CNTRL2 RX_OFF(1, 0xD4)
#define QSERDES_RX1_RX_EQU_ADAPTOR_CNTRL3 RX_OFF(1, 0xD8)
#define QSERDES_RX1_RX_EQU_ADAPTOR_CNTRL4 RX_OFF(1, 0xDC)
#define QSERDES_RX1_SIGDET_CNTRL RX_OFF(1, 0x104)
#define QSERDES_RX1_SIGDET_LVL RX_OFF(1, 0x108)
#define QSERDES_RX1_SIGDET_DEGLITCH_CNTRL RX_OFF(1, 0x10C)
#define QSERDES_RX1_RX_INTERFACE_MODE RX_OFF(1, 0x11C)
#define UFS_PHY_RX_LINECFG_DISABLE_BIT BIT(1)
/*
@ -177,7 +208,7 @@ struct ufs_qcom_phy_qmp_v3 {
struct ufs_qcom_phy common_cfg;
};
static struct ufs_qcom_phy_calibration phy_cal_table_rate_A[] = {
static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_3_0_0[] = {
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CMN_CONFIG, 0x06),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL, 0xD5),
@ -234,6 +265,87 @@ static struct ufs_qcom_phy_calibration phy_cal_table_rate_A[] = {
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_TX_LARGE_AMP_DRV_LVL, 0x0A),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_TX_SMALL_AMP_DRV_LVL, 0x02),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SYM_RESYNC_CTRL, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_MIN_HIBERN8_TIME, 0x9A), /* 8 us */
};
static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_3_1_0[] = {
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CMN_CONFIG, 0x06),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL, 0xD5),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x20),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CLK_SELECT, 0x30),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYS_CLK_CTRL, 0x02),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x04),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BG_TIMER, 0x0A),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_HSCLK_SEL, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP_EN, 0x01),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_CTRL, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORE_CLK_EN, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x04),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SVS_MODE_CLK_SEL, 0x05),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IVCO, 0x0F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_INITVAL1, 0xFF),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_INITVAL2, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START_MODE0, 0x82),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CP_CTRL_MODE0, 0x06),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RCTRL_MODE0, 0x16),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CCTRL_MODE0, 0x36),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x3F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE1_MODE0, 0xDA),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE2_MODE0, 0x01),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE0, 0xFF),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE0, 0x0C),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START_MODE1, 0x98),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CP_CTRL_MODE1, 0x06),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RCTRL_MODE1, 0x16),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CCTRL_MODE1, 0x36),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE1, 0x3F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE1, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE1_MODE1, 0xC1),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE2_MODE1, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE1, 0x32),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE1, 0x0F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX0_LANE_MODE_1, 0x06),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_SIGDET_LVL, 0x24),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_SIGDET_CNTRL, 0x0F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_SIGDET_DEGLITCH_CNTRL, 0x1E),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_RX_INTERFACE_MODE, 0x40),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_UCDR_FASTLOCK_FO_GAIN, 0x0B),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_RX_TERM_BW, 0x5B),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_RX_EQU_ADAPTOR_CNTRL2, 0x06),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_RX_EQU_ADAPTOR_CNTRL3, 0x04),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_RX_EQU_ADAPTOR_CNTRL4, 0x1D),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_UCDR_SVS_SO_GAIN_HALF, 0x04),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_UCDR_SVS_SO_GAIN_QUARTER, 0x04),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_UCDR_SVS_SO_GAIN, 0x04),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_UCDR_SO_SATURATION_AND_ENABLE, 0x4B),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_UCDR_PI_CONTROLS, 0xF1),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_UCDR_FASTLOCK_COUNT_LOW, 0x80),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX1_LANE_MODE_1, 0x06),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_SIGDET_LVL, 0x24),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_SIGDET_CNTRL, 0x0F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_SIGDET_DEGLITCH_CNTRL, 0x1E),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_INTERFACE_MODE, 0x40),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_UCDR_FASTLOCK_FO_GAIN, 0x0B),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_TERM_BW, 0x5B),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_EQU_ADAPTOR_CNTRL2, 0x06),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_EQU_ADAPTOR_CNTRL3, 0x04),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_EQU_ADAPTOR_CNTRL4, 0x1D),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_UCDR_SVS_SO_GAIN_HALF, 0x04),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_UCDR_SVS_SO_GAIN_QUARTER, 0x04),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_UCDR_SVS_SO_GAIN, 0x04),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_UCDR_SO_SATURATION_AND_ENABLE, 0x4B),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_UCDR_PI_CONTROLS, 0xF1),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_UCDR_FASTLOCK_COUNT_LOW, 0x80),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_MULTI_LANE_CTRL1, 0x02),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL2, 0x6C),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_TX_LARGE_AMP_DRV_LVL, 0x0A),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_TX_SMALL_AMP_DRV_LVL, 0x02),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SYM_RESYNC_CTRL, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_TX_MID_TERM_CTRL1, 0x43),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL1, 0x0F),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_MIN_HIBERN8_TIME, 0x9A), /* 8 us */
};
static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = {

View file

@ -15,8 +15,8 @@
#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_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
@ -113,6 +113,14 @@ struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev,
goto out;
}
/*
* UFS PHY power management is managed by its parent (UFS host
* controller) hence set the no 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;
@ -191,27 +199,20 @@ ufs_qcom_phy_init_clks(struct phy *generic_phy,
struct ufs_qcom_phy *phy_common)
{
int err;
struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy);
err = ufs_qcom_phy_clk_get(generic_phy, "tx_iface_clk",
&phy_common->tx_iface_clk);
/*
* tx_iface_clk does not exist in newer version of ufs-phy HW,
* so don't return error if it is not found
*/
if (err)
dev_dbg(phy->dev, "%s: failed to get tx_iface_clk\n",
__func__);
__ufs_qcom_phy_clk_get(generic_phy, "tx_iface_clk",
&phy_common->tx_iface_clk, false);
err = ufs_qcom_phy_clk_get(generic_phy, "rx_iface_clk",
&phy_common->rx_iface_clk);
/*
* rx_iface_clk does not exist in newer version of ufs-phy HW,
* so don't return error if it is not found
*/
if (err)
dev_dbg(phy->dev, "%s: failed to get rx_iface_clk\n",
__func__);
__ufs_qcom_phy_clk_get(generic_phy, "rx_iface_clk",
&phy_common->rx_iface_clk, false);
err = ufs_qcom_phy_clk_get(generic_phy, "ref_clk_src",
&phy_common->ref_clk_src);
@ -246,7 +247,6 @@ ufs_qcom_phy_init_vregulators(struct phy *generic_phy,
struct ufs_qcom_phy *phy_common)
{
int err;
int vdda_phy_uV;
err = ufs_qcom_phy_init_vreg(generic_phy, &phy_common->vdda_pll,
"vdda-pll");
@ -258,10 +258,6 @@ ufs_qcom_phy_init_vregulators(struct phy *generic_phy,
if (err)
goto out;
vdda_phy_uV = regulator_get_voltage(phy_common->vdda_phy.reg);
phy_common->vdda_phy.max_uV = vdda_phy_uV;
phy_common->vdda_phy.min_uV = vdda_phy_uV;
/* vddp-ref-clk-* properties are optional */
__ufs_qcom_phy_init_vreg(generic_phy, &phy_common->vddp_ref_clk,
"vddp-ref-clk", true);
@ -279,6 +275,14 @@ static int __ufs_qcom_phy_init_vreg(struct phy *phy,
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 optional ? err : -ENODATA;
}
}
vreg->name = kstrdup(name, GFP_KERNEL);
if (!vreg->name) {
err = -ENOMEM;
@ -786,3 +790,21 @@ int ufs_qcom_phy_configure_lpm(struct phy *generic_phy, bool enable)
return ret;
}
EXPORT_SYMBOL(ufs_qcom_phy_configure_lpm);
void ufs_qcom_phy_dump_regs(struct ufs_qcom_phy *phy, int offset,
int len, char *prefix)
{
print_hex_dump(KERN_ERR, prefix,
len > 4 ? DUMP_PREFIX_OFFSET : DUMP_PREFIX_NONE,
16, 4, phy->mmio + offset, len, false);
}
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);

View file

@ -1,4 +1,4 @@
/* Copyright (c) 2013-2016, The Linux Foundation. All rights reserved.
/* Copyright (c) 2013-2017, The 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
@ -1412,8 +1412,10 @@ static ssize_t ufsdbg_reset_controller_write(struct file *filp,
struct ufs_hba *hba = filp->f_mapping->host->i_private;
unsigned long flags;
spin_lock_irqsave(hba->host->host_lock, flags);
pm_runtime_get_sync(hba->dev);
ufshcd_hold(hba, false);
spin_lock_irqsave(hba->host->host_lock, flags);
/*
* simulating a dummy error in order to "convince"
* eh_work to actually reset the controller
@ -1421,9 +1423,13 @@ static ssize_t ufsdbg_reset_controller_write(struct file *filp,
hba->saved_err |= INT_FATAL_ERRORS;
hba->silence_err_logs = true;
schedule_work(&hba->eh_work);
spin_unlock_irqrestore(hba->host->host_lock, flags);
flush_work(&hba->eh_work);
ufshcd_release(hba, false);
pm_runtime_put_sync(hba->dev);
return cnt;
}
@ -1471,8 +1477,8 @@ DEFINE_SIMPLE_ATTRIBUTE(ufsdbg_err_state,
void ufsdbg_add_debugfs(struct ufs_hba *hba)
{
if (!hba) {
dev_err(hba->dev, "%s: NULL hba, exiting", __func__);
goto err_no_root;
pr_err("%s: NULL hba, exiting", __func__);
return;
}
hba->debugfs_files.debugfs_root = debugfs_create_dir(dev_name(hba->dev),

View file

@ -67,6 +67,7 @@ static int ufs_qcom_dbg_testbus_en_read(void *data, u64 *attr_val)
static int ufs_qcom_dbg_testbus_en_set(void *data, u64 attr_id)
{
struct ufs_qcom_host *host = data;
int ret = 0;
if (!host)
return -EINVAL;
@ -76,7 +77,13 @@ static int ufs_qcom_dbg_testbus_en_set(void *data, u64 attr_id)
else
host->dbg_print_en &= ~UFS_QCOM_DBG_PRINT_TEST_BUS_EN;
return ufs_qcom_testbus_config(host);
pm_runtime_get_sync(host->hba->dev);
ufshcd_hold(host->hba, false);
ret = ufs_qcom_testbus_config(host);
ufshcd_release(host->hba, false);
pm_runtime_put_sync(host->hba->dev);
return ret;
}
DEFINE_SIMPLE_ATTRIBUTE(ufs_qcom_dbg_testbus_en_ops,
@ -142,7 +149,11 @@ static ssize_t ufs_qcom_dbg_testbus_cfg_write(struct file *file,
* Sanity check of the {major, minor} tuple is done in the
* config function
*/
pm_runtime_get_sync(host->hba->dev);
ufshcd_hold(host->hba, false);
ret = ufs_qcom_testbus_config(host);
ufshcd_release(host->hba, false);
pm_runtime_put_sync(host->hba->dev);
if (!ret)
dev_dbg(host->hba->dev,
"%s: New configuration: major=%d, minor=%d\n",

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014-2016, The Linux Foundation. All rights reserved.
* Copyright (c) 2014-2017, The 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
@ -14,6 +14,7 @@
#include <linux/io.h>
#include <linux/of.h>
#include <linux/blkdev.h>
#include <linux/spinlock.h>
#include <crypto/ice.h>
#include "ufs-qcom-ice.h"
@ -168,13 +169,23 @@ out:
static void ufs_qcom_ice_cfg_work(struct work_struct *work)
{
unsigned long flags;
struct ice_data_setting ice_set;
struct ufs_qcom_host *qcom_host =
container_of(work, struct ufs_qcom_host, ice_cfg_work);
struct request *req_pending = NULL;
if (!qcom_host->ice.vops->config_start || !qcom_host->req_pending)
if (!qcom_host->ice.vops->config_start)
return;
spin_lock_irqsave(&qcom_host->ice_work_lock, flags);
req_pending = qcom_host->req_pending;
if (!req_pending) {
spin_unlock_irqrestore(&qcom_host->ice_work_lock, flags);
return;
}
spin_unlock_irqrestore(&qcom_host->ice_work_lock, flags);
/*
* config_start is called again as previous attempt returned -EAGAIN,
* this call shall now take care of the necessary key setup.
@ -185,12 +196,17 @@ static void ufs_qcom_ice_cfg_work(struct work_struct *work)
qcom_host->ice.vops->config_start(qcom_host->ice.pdev,
qcom_host->req_pending, &ice_set, false);
spin_lock_irqsave(&qcom_host->ice_work_lock, flags);
qcom_host->req_pending = NULL;
spin_unlock_irqrestore(&qcom_host->ice_work_lock, flags);
/*
* Resume with requests processing. We assume config_start has been
* successful, but even if it wasn't we still must resume in order to
* allow for the request to be retried.
*/
ufshcd_scsi_unblock_requests(qcom_host->hba);
}
/**
@ -246,6 +262,7 @@ int ufs_qcom_ice_req_setup(struct ufs_qcom_host *qcom_host,
struct ice_data_setting ice_set;
char cmd_op = cmd->cmnd[0];
int err;
unsigned long flags;
if (!qcom_host->ice.pdev || !qcom_host->ice.vops) {
dev_dbg(qcom_host->hba->dev, "%s: ice device is not enabled\n",
@ -255,6 +272,10 @@ int ufs_qcom_ice_req_setup(struct ufs_qcom_host *qcom_host,
if (qcom_host->ice.vops->config_start) {
memset(&ice_set, 0, sizeof(ice_set));
spin_lock_irqsave(
&qcom_host->ice_work_lock, flags);
err = qcom_host->ice.vops->config_start(qcom_host->ice.pdev,
cmd->request, &ice_set, true);
if (err) {
@ -272,19 +293,41 @@ int ufs_qcom_ice_req_setup(struct ufs_qcom_host *qcom_host,
dev_dbg(qcom_host->hba->dev,
"%s: scheduling task for ice setup\n",
__func__);
qcom_host->req_pending = cmd->request;
if (schedule_work(&qcom_host->ice_cfg_work))
if (!qcom_host->req_pending) {
ufshcd_scsi_block_requests(
qcom_host->hba);
qcom_host->req_pending = cmd->request;
if (!schedule_work(
&qcom_host->ice_cfg_work)) {
qcom_host->req_pending = NULL;
spin_unlock_irqrestore(
&qcom_host->ice_work_lock,
flags);
ufshcd_scsi_unblock_requests(
qcom_host->hba);
return err;
}
}
} else {
dev_err(qcom_host->hba->dev,
"%s: error in ice_vops->config %d\n",
__func__, err);
if (err != -EBUSY)
dev_err(qcom_host->hba->dev,
"%s: error in ice_vops->config %d\n",
__func__, err);
}
spin_unlock_irqrestore(&qcom_host->ice_work_lock,
flags);
return err;
}
spin_unlock_irqrestore(&qcom_host->ice_work_lock, flags);
if (ufs_qcom_is_data_cmd(cmd_op, true))
*enable = !ice_set.encr_bypass;
else if (ufs_qcom_is_data_cmd(cmd_op, false))
@ -320,6 +363,7 @@ int ufs_qcom_ice_cfg_start(struct ufs_qcom_host *qcom_host,
unsigned int bypass = 0;
struct request *req;
char cmd_op;
unsigned long flags;
if (!qcom_host->ice.pdev || !qcom_host->ice.vops) {
dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
@ -339,7 +383,8 @@ int ufs_qcom_ice_cfg_start(struct ufs_qcom_host *qcom_host,
req = cmd->request;
if (req->bio)
lba = req->bio->bi_iter.bi_sector;
lba = (req->bio->bi_iter.bi_sector) >>
UFS_QCOM_ICE_TR_DATA_UNIT_4_KB;
slot = req->tag;
if (slot < 0 || slot > qcom_host->hba->nutrs) {
@ -348,8 +393,13 @@ int ufs_qcom_ice_cfg_start(struct ufs_qcom_host *qcom_host,
return -EINVAL;
}
memset(&ice_set, 0, sizeof(ice_set));
if (qcom_host->ice.vops->config_start) {
memset(&ice_set, 0, sizeof(ice_set));
spin_lock_irqsave(
&qcom_host->ice_work_lock, flags);
err = qcom_host->ice.vops->config_start(qcom_host->ice.pdev,
req, &ice_set, true);
if (err) {
@ -364,13 +414,44 @@ int ufs_qcom_ice_cfg_start(struct ufs_qcom_host *qcom_host,
* request processing.
*/
if (err == -EAGAIN) {
qcom_host->req_pending = req;
if (schedule_work(&qcom_host->ice_cfg_work))
dev_dbg(qcom_host->hba->dev,
"%s: scheduling task for ice setup\n",
__func__);
if (!qcom_host->req_pending) {
ufshcd_scsi_block_requests(
qcom_host->hba);
qcom_host->req_pending = cmd->request;
if (!schedule_work(
&qcom_host->ice_cfg_work)) {
qcom_host->req_pending = NULL;
spin_unlock_irqrestore(
&qcom_host->ice_work_lock,
flags);
ufshcd_scsi_unblock_requests(
qcom_host->hba);
return err;
}
}
} else {
if (err != -EBUSY)
dev_err(qcom_host->hba->dev,
"%s: error in ice_vops->config %d\n",
__func__, err);
}
goto out;
spin_unlock_irqrestore(
&qcom_host->ice_work_lock, flags);
return err;
}
spin_unlock_irqrestore(
&qcom_host->ice_work_lock, flags);
}
cmd_op = cmd->cmnd[0];
@ -390,6 +471,7 @@ int ufs_qcom_ice_cfg_start(struct ufs_qcom_host *qcom_host,
bypass = ice_set.decr_bypass ? UFS_QCOM_ICE_ENABLE_BYPASS :
UFS_QCOM_ICE_DISABLE_BYPASS;
/* Configure ICE index */
ctrl_info_val =
(ice_set.crypto_data.key_index &
@ -398,8 +480,7 @@ int ufs_qcom_ice_cfg_start(struct ufs_qcom_host *qcom_host,
/* Configure data unit size of transfer request */
ctrl_info_val |=
(UFS_QCOM_ICE_TR_DATA_UNIT_4_KB &
MASK_UFS_QCOM_ICE_CTRL_INFO_CDU)
UFS_QCOM_ICE_TR_DATA_UNIT_4_KB
<< OFFSET_UFS_QCOM_ICE_CTRL_INFO_CDU;
/* Configure ICE bypass mode */

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2016, Linux Foundation. All rights reserved.
* Copyright (c) 2013-2017, 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
@ -33,6 +33,9 @@
#include "ufs-qcom-ice.h"
#include "ufs-qcom-debugfs.h"
#define MAX_PROP_SIZE 32
#define VDDP_REF_CLK_MIN_UV 1200000
#define VDDP_REF_CLK_MAX_UV 1200000
/* TODO: further tuning for this parameter may be required */
#define UFS_QCOM_PM_QOS_UNVOTE_TIMEOUT_US (10000) /* microseconds */
@ -97,13 +100,10 @@ static int ufs_qcom_host_clk_get(struct device *dev,
int err = 0;
clk = devm_clk_get(dev, name);
if (IS_ERR(clk)) {
if (IS_ERR(clk))
err = PTR_ERR(clk);
dev_err(dev, "%s: failed to get %s err %d",
__func__, name, err);
} else {
else
*clk_out = clk;
}
return err;
}
@ -182,20 +182,29 @@ static int ufs_qcom_init_lane_clks(struct ufs_qcom_host *host)
err = ufs_qcom_host_clk_get(dev,
"rx_lane0_sync_clk", &host->rx_l0_sync_clk);
if (err)
if (err) {
dev_err(dev, "%s: failed to get rx_lane0_sync_clk, err %d",
__func__, err);
goto out;
}
err = ufs_qcom_host_clk_get(dev,
"tx_lane0_sync_clk", &host->tx_l0_sync_clk);
if (err)
if (err) {
dev_err(dev, "%s: failed to get tx_lane0_sync_clk, err %d",
__func__, err);
goto out;
}
/* In case of single lane per direction, don't read lane1 clocks */
if (host->hba->lanes_per_direction > 1) {
err = ufs_qcom_host_clk_get(dev, "rx_lane1_sync_clk",
&host->rx_l1_sync_clk);
if (err)
if (err) {
dev_err(dev, "%s: failed to get rx_lane1_sync_clk, err %d",
__func__, err);
goto out;
}
/* The tx lane1 clk could be muxed, hence keep this optional */
ufs_qcom_host_clk_get(dev, "tx_lane1_sync_clk",
@ -387,8 +396,9 @@ static int ufs_qcom_hce_enable_notify(struct ufs_hba *hba,
/**
* Returns zero for success and non-zero in case of a failure
*/
static int ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear,
u32 hs, u32 rate, bool update_link_startup_timer)
static int __ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear,
u32 hs, u32 rate, bool update_link_startup_timer,
bool is_pre_scale_up)
{
int ret = 0;
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
@ -435,8 +445,12 @@ static int ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear,
}
list_for_each_entry(clki, &hba->clk_list_head, list) {
if (!strcmp(clki->name, "core_clk"))
core_clk_rate = clk_get_rate(clki->clk);
if (!strcmp(clki->name, "core_clk")) {
if (is_pre_scale_up)
core_clk_rate = clki->max_freq;
else
core_clk_rate = clk_get_rate(clki->clk);
}
}
/* If frequency is smaller than 1MHz, set to 1MHz */
@ -533,6 +547,13 @@ out:
return ret;
}
static int ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear,
u32 hs, u32 rate, bool update_link_startup_timer)
{
return __ufs_qcom_cfg_timers(hba, gear, hs, rate,
update_link_startup_timer, false);
}
static int ufs_qcom_link_startup_pre_change(struct ufs_hba *hba)
{
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
@ -666,40 +687,105 @@ static int ufs_qcom_link_startup_notify(struct ufs_hba *hba,
return err;
}
static int ufs_qcom_config_vreg(struct device *dev,
struct ufs_vreg *vreg, bool on)
{
int ret = 0;
struct regulator *reg;
int min_uV, uA_load;
if (!vreg) {
WARN_ON(1);
ret = -EINVAL;
goto out;
}
reg = vreg->reg;
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__, vreg->name, ret);
goto out;
}
uA_load = on ? vreg->max_uA : 0;
ret = regulator_set_load(vreg->reg, uA_load);
if (ret)
goto out;
}
out:
return ret;
}
static int ufs_qcom_enable_vreg(struct device *dev, struct ufs_vreg *vreg)
{
int ret = 0;
if (vreg->enabled)
return ret;
ret = ufs_qcom_config_vreg(dev, vreg, true);
if (ret)
goto out;
ret = regulator_enable(vreg->reg);
if (ret)
goto out;
vreg->enabled = true;
out:
return ret;
}
static int ufs_qcom_disable_vreg(struct device *dev, struct ufs_vreg *vreg)
{
int ret = 0;
if (!vreg->enabled)
return ret;
ret = regulator_disable(vreg->reg);
if (ret)
goto out;
ret = ufs_qcom_config_vreg(dev, vreg, false);
if (ret)
goto out;
vreg->enabled = false;
out:
return ret;
}
static int ufs_qcom_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
{
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
struct phy *phy = host->generic_phy;
int ret = 0;
if (ufs_qcom_is_link_off(hba)) {
/*
* Disable the tx/rx lane symbol clocks before PHY is
* powered down as the PLL source should be disabled
* after downstream clocks are disabled.
*/
ufs_qcom_disable_lane_clks(host);
phy_power_off(phy);
ret = ufs_qcom_ice_suspend(host);
if (ret)
dev_err(hba->dev, "%s: failed ufs_qcom_ice_suspend %d\n",
__func__, ret);
/* Assert PHY soft reset */
ufs_qcom_assert_reset(hba);
goto out;
}
/*
* If UniPro link is not active, PHY ref_clk, main PHY analog power
* rail and low noise analog power rail for PLL can be switched off.
* If UniPro link is not active or OFF, PHY ref_clk, main PHY analog
* power rail and low noise analog power rail for PLL can be
* switched off.
*/
if (!ufs_qcom_is_link_active(hba)) {
ufs_qcom_disable_lane_clks(host);
phy_power_off(phy);
ufs_qcom_ice_suspend(host);
}
if (host->vddp_ref_clk && ufs_qcom_is_link_off(hba))
ret = ufs_qcom_disable_vreg(hba->dev,
host->vddp_ref_clk);
ufs_qcom_ice_suspend(host);
if (ufs_qcom_is_link_off(hba)) {
/* Assert PHY soft reset */
ufs_qcom_assert_reset(hba);
goto out;
}
}
/* Unvote PM QoS */
ufs_qcom_pm_qos_suspend(host);
@ -720,6 +806,11 @@ static int ufs_qcom_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
goto out;
}
if (host->vddp_ref_clk && (hba->rpm_lvl > UFS_PM_LVL_3 ||
hba->spm_lvl > UFS_PM_LVL_3))
ufs_qcom_enable_vreg(hba->dev,
host->vddp_ref_clk);
err = ufs_qcom_enable_lane_clks(host);
if (err)
goto out;
@ -739,7 +830,35 @@ out:
static int ufs_qcom_full_reset(struct ufs_hba *hba)
{
return -ENOTSUPP;
int ret = -ENOTSUPP;
if (!hba->core_reset) {
dev_err(hba->dev, "%s: failed, err = %d\n", __func__,
ret);
goto out;
}
ret = reset_control_assert(hba->core_reset);
if (ret) {
dev_err(hba->dev, "%s: core_reset assert failed, err = %d\n",
__func__, ret);
goto out;
}
/*
* The hardware requirement for delay between assert/deassert
* is at least 3-4 sleep clock (32.7KHz) cycles, which comes to
* ~125us (4/32768). To be on the safe side add 200us delay.
*/
usleep_range(200, 210);
ret = reset_control_deassert(hba->core_reset);
if (ret)
dev_err(hba->dev, "%s: core_reset deassert failed, err = %d\n",
__func__, ret);
out:
return ret;
}
#ifdef CONFIG_SCSI_UFS_QCOM_ICE
@ -757,7 +876,8 @@ static int ufs_qcom_crypto_req_setup(struct ufs_hba *hba,
/* Use request LBA as the DUN value */
if (req->bio)
*dun = req->bio->bi_iter.bi_sector;
*dun = (req->bio->bi_iter.bi_sector) >>
UFS_QCOM_ICE_TR_DATA_UNIT_4_KB;
ret = ufs_qcom_ice_req_setup(host, lrbp->cmd, cc_index, enable);
@ -978,7 +1098,7 @@ static void ufs_qcom_get_speed_mode(struct ufs_pa_layer_attr *p, char *result)
}
}
static int ufs_qcom_set_bus_vote(struct ufs_qcom_host *host, int vote)
static int __ufs_qcom_set_bus_vote(struct ufs_qcom_host *host, int vote)
{
int err = 0;
@ -1009,7 +1129,7 @@ static int ufs_qcom_update_bus_bw_vote(struct ufs_qcom_host *host)
vote = ufs_qcom_get_bus_vote(host, mode);
if (vote >= 0)
err = ufs_qcom_set_bus_vote(host, vote);
err = __ufs_qcom_set_bus_vote(host, vote);
else
err = vote;
@ -1020,6 +1140,35 @@ static int ufs_qcom_update_bus_bw_vote(struct ufs_qcom_host *host)
return err;
}
static int ufs_qcom_set_bus_vote(struct ufs_hba *hba, bool on)
{
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
int vote, err;
/*
* In case ufs_qcom_init() is not yet done, simply ignore.
* This ufs_qcom_set_bus_vote() shall be called from
* ufs_qcom_init() after init is done.
*/
if (!host)
return 0;
if (on) {
vote = host->bus_vote.saved_vote;
if (vote == host->bus_vote.min_bw_vote)
ufs_qcom_update_bus_bw_vote(host);
} else {
vote = host->bus_vote.min_bw_vote;
}
err = __ufs_qcom_set_bus_vote(host, vote);
if (err)
dev_err(hba->dev, "%s: set bus vote failed %d\n",
__func__, err);
return err;
}
static ssize_t
show_ufs_to_mem_max_bus_bw(struct device *dev, struct device_attribute *attr,
char *buf)
@ -1096,7 +1245,7 @@ static int ufs_qcom_update_bus_bw_vote(struct ufs_qcom_host *host)
return 0;
}
static int ufs_qcom_set_bus_vote(struct ufs_qcom_host *host, int vote)
static int ufs_qcom_set_bus_vote(struct ufs_hba *hba, bool on)
{
return 0;
}
@ -1373,7 +1522,6 @@ static int ufs_qcom_setup_clocks(struct ufs_hba *hba, bool on,
{
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
int err;
int vote = 0;
/*
* In case ufs_qcom_init() is not yet done, simply ignore.
@ -1398,9 +1546,6 @@ static int ufs_qcom_setup_clocks(struct ufs_hba *hba, bool on,
/* enable the device ref clock for HS mode*/
if (ufshcd_is_hs_mode(&hba->pwr_info))
ufs_qcom_dev_ref_clk_ctrl(host, true);
vote = host->bus_vote.saved_vote;
if (vote == host->bus_vote.min_bw_vote)
ufs_qcom_update_bus_bw_vote(host);
err = ufs_qcom_ice_resume(host);
if (err)
@ -1412,21 +1557,19 @@ static int ufs_qcom_setup_clocks(struct ufs_hba *hba, bool on,
/* M-PHY RMMI interface clocks can be turned off */
ufs_qcom_phy_disable_iface_clk(host->generic_phy);
if (!ufs_qcom_is_link_active(hba)) {
if (!is_gating_context)
/* turn off UFS local PHY ref_clk */
ufs_qcom_phy_disable_ref_clk(host->generic_phy);
/*
* If auto hibern8 is supported then the link will already
* be in hibern8 state and the ref clock can be gated.
*/
if (ufshcd_is_auto_hibern8_supported(hba) ||
!ufs_qcom_is_link_active(hba)) {
/* turn off UFS local PHY ref_clk */
ufs_qcom_phy_disable_ref_clk(host->generic_phy);
/* disable device ref_clk */
ufs_qcom_dev_ref_clk_ctrl(host, false);
}
vote = host->bus_vote.min_bw_vote;
}
err = ufs_qcom_set_bus_vote(host, vote);
if (err)
dev_err(hba->dev, "%s: set bus vote failed %d\n",
__func__, err);
out:
return err;
}
@ -1850,6 +1993,57 @@ static void ufs_qcom_save_host_ptr(struct ufs_hba *hba)
dev_err(hba->dev, "invalid host index %d\n", id);
}
static int ufs_qcom_parse_reg_info(struct ufs_qcom_host *host, char *name,
struct ufs_vreg **out_vreg)
{
int ret = 0;
char prop_name[MAX_PROP_SIZE];
struct ufs_vreg *vreg = NULL;
struct device *dev = host->hba->dev;
struct device_node *np = dev->of_node;
if (!np) {
dev_err(dev, "%s: non DT initialization\n", __func__);
goto out;
}
snprintf(prop_name, MAX_PROP_SIZE, "%s-supply", name);
if (!of_parse_phandle(np, prop_name, 0)) {
dev_info(dev, "%s: Unable to find %s regulator, assuming enabled\n",
__func__, prop_name);
ret = -ENODEV;
goto out;
}
vreg = devm_kzalloc(dev, sizeof(*vreg), GFP_KERNEL);
if (!vreg)
return -ENOMEM;
vreg->name = name;
snprintf(prop_name, MAX_PROP_SIZE, "%s-max-microamp", name);
ret = of_property_read_u32(np, prop_name, &vreg->max_uA);
if (ret) {
dev_err(dev, "%s: unable to find %s err %d\n",
__func__, prop_name, ret);
goto out;
}
vreg->reg = devm_regulator_get(dev, vreg->name);
if (IS_ERR(vreg->reg)) {
ret = PTR_ERR(vreg->reg);
dev_err(dev, "%s: %s get failed, err=%d\n",
__func__, vreg->name, ret);
}
vreg->min_uV = VDDP_REF_CLK_MIN_UV;
vreg->max_uV = VDDP_REF_CLK_MAX_UV;
out:
if (!ret)
*out_vreg = vreg;
return ret;
}
/**
* ufs_qcom_init - bind phy with controller
* @hba: host controller instance
@ -1877,14 +2071,9 @@ static int ufs_qcom_init(struct ufs_hba *hba)
/* Make a two way bind between the qcom host and the hba */
host->hba = hba;
ufshcd_set_variant(hba, host);
spin_lock_init(&host->ice_work_lock);
/*
* voting/devoting device ref_clk source is time consuming hence
* skip devoting it during aggressive clock gating. This clock
* will still be gated off during runtime suspend.
*/
hba->no_ref_clk_gating = true;
ufshcd_set_variant(hba, host);
err = ufs_qcom_ice_get_dev(host);
if (err == -EPROBE_DEFER) {
@ -1969,14 +2158,24 @@ static int ufs_qcom_init(struct ufs_hba *hba)
ufs_qcom_phy_save_controller_version(host->generic_phy,
host->hw_ver.major, host->hw_ver.minor, host->hw_ver.step);
err = ufs_qcom_parse_reg_info(host, "qcom,vddp-ref-clk",
&host->vddp_ref_clk);
phy_init(host->generic_phy);
err = phy_power_on(host->generic_phy);
if (err)
goto out_unregister_bus;
if (host->vddp_ref_clk) {
err = ufs_qcom_enable_vreg(dev, host->vddp_ref_clk);
if (err) {
dev_err(dev, "%s: failed enabling ref clk supply: %d\n",
__func__, err);
goto out_disable_phy;
}
}
err = ufs_qcom_init_lane_clks(host);
if (err)
goto out_disable_phy;
goto out_disable_vddp;
ufs_qcom_parse_lpm(host);
if (host->disable_lpm)
@ -1984,6 +2183,7 @@ static int ufs_qcom_init(struct ufs_hba *hba)
ufs_qcom_set_caps(hba);
ufs_qcom_advertise_quirks(hba);
ufs_qcom_set_bus_vote(hba, true);
ufs_qcom_setup_clocks(hba, true, false);
host->dbg_print_en |= UFS_QCOM_DEFAULT_DBG_PRINT_EN;
@ -1999,6 +2199,9 @@ static int ufs_qcom_init(struct ufs_hba *hba)
goto out;
out_disable_vddp:
if (host->vddp_ref_clk)
ufs_qcom_disable_vreg(dev, host->vddp_ref_clk);
out_disable_phy:
phy_power_off(host->generic_phy);
out_unregister_bus:
@ -2059,6 +2262,11 @@ static inline int ufs_qcom_configure_lpm(struct ufs_hba *hba, bool enable)
if (!ufs_qcom_cap_svs2(host))
goto out;
if (!((host->hw_ver.major == 0x3) &&
(host->hw_ver.minor == 0x0) &&
(host->hw_ver.step == 0x0)))
goto out;
/*
* The link should be put in hibern8 state before
* configuring the PHY to enter/exit SVS2 mode.
@ -2079,61 +2287,49 @@ out:
static int ufs_qcom_clk_scale_up_pre_change(struct ufs_hba *hba)
{
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
struct ufs_pa_layer_attr *attr = &host->dev_req_params;
int err = 0;
if (!ufs_qcom_cap_qunipro(host))
return 0;
goto out;
return ufs_qcom_configure_lpm(hba, false);
}
err = ufs_qcom_configure_lpm(hba, false);
if (err)
goto out;
static int ufs_qcom_clk_scale_up_post_change(struct ufs_hba *hba)
{
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
if (!ufs_qcom_cap_qunipro(host))
return 0;
if (attr)
__ufs_qcom_cfg_timers(hba, attr->gear_rx, attr->pwr_rx,
attr->hs_rate, false, true);
/* set unipro core clock cycles to 150 and clear clock divider */
return ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(hba, 150);
err = ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(hba, 150);
out:
return err;
}
static int ufs_qcom_clk_scale_down_pre_change(struct ufs_hba *hba)
{
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
u32 core_clk_ctrl_reg;
int err = 0;
if (!ufs_qcom_cap_qunipro(host))
goto out;
return 0;
err = ufs_qcom_configure_lpm(hba, true);
if (err)
goto out;
err = ufshcd_dme_get(hba,
UIC_ARG_MIB(DME_VS_CORE_CLK_CTRL),
&core_clk_ctrl_reg);
/* make sure CORE_CLK_DIV_EN is cleared */
if (!err &&
(core_clk_ctrl_reg & DME_VS_CORE_CLK_CTRL_CORE_CLK_DIV_EN_BIT)) {
core_clk_ctrl_reg &= ~DME_VS_CORE_CLK_CTRL_CORE_CLK_DIV_EN_BIT;
err = ufshcd_dme_set(hba,
UIC_ARG_MIB(DME_VS_CORE_CLK_CTRL),
core_clk_ctrl_reg);
}
out:
return err;
return ufs_qcom_configure_lpm(hba, true);
}
static int ufs_qcom_clk_scale_down_post_change(struct ufs_hba *hba)
{
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
struct ufs_pa_layer_attr *attr = &host->dev_req_params;
int err = 0;
if (!ufs_qcom_cap_qunipro(host))
return 0;
if (attr)
ufs_qcom_cfg_timers(hba, attr->gear_rx, attr->pwr_rx,
attr->hs_rate, false);
if (ufs_qcom_cap_svs2(host))
/*
* For SVS2 set unipro core clock cycles to 37 and
@ -2154,7 +2350,6 @@ static int ufs_qcom_clk_scale_notify(struct ufs_hba *hba,
bool scale_up, enum ufs_notify_change_status status)
{
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
struct ufs_pa_layer_attr *dev_req_params = &host->dev_req_params;
int err = 0;
switch (status) {
@ -2165,19 +2360,9 @@ static int ufs_qcom_clk_scale_notify(struct ufs_hba *hba,
err = ufs_qcom_clk_scale_down_pre_change(hba);
break;
case POST_CHANGE:
if (scale_up)
err = ufs_qcom_clk_scale_up_post_change(hba);
else
if (!scale_up)
err = ufs_qcom_clk_scale_down_post_change(hba);
if (err || !dev_req_params)
goto out;
ufs_qcom_cfg_timers(hba,
dev_req_params->gear_rx,
dev_req_params->pwr_rx,
dev_req_params->hs_rate,
false);
ufs_qcom_update_bus_bw_vote(host);
break;
default:
@ -2186,7 +2371,6 @@ static int ufs_qcom_clk_scale_notify(struct ufs_hba *hba,
break;
}
out:
return err;
}
@ -2277,17 +2461,21 @@ void ufs_qcom_print_hw_debug_reg_all(struct ufs_hba *hba, void *priv,
static void ufs_qcom_enable_test_bus(struct ufs_qcom_host *host)
{
if (host->dbg_print_en & UFS_QCOM_DBG_PRINT_TEST_BUS_EN)
if (host->dbg_print_en & UFS_QCOM_DBG_PRINT_TEST_BUS_EN) {
ufshcd_rmwl(host->hba, UFS_REG_TEST_BUS_EN,
UFS_REG_TEST_BUS_EN, REG_UFS_CFG1);
ufshcd_rmwl(host->hba, TEST_BUS_EN, TEST_BUS_EN, REG_UFS_CFG1);
else
} else {
ufshcd_rmwl(host->hba, UFS_REG_TEST_BUS_EN, 0, REG_UFS_CFG1);
ufshcd_rmwl(host->hba, TEST_BUS_EN, 0, REG_UFS_CFG1);
}
}
static void ufs_qcom_get_default_testbus_cfg(struct ufs_qcom_host *host)
{
/* provide a legal default configuration */
host->testbus.select_major = TSTBUS_UAWM;
host->testbus.select_minor = 1;
host->testbus.select_major = TSTBUS_UNIPRO;
host->testbus.select_minor = 37;
}
static bool ufs_qcom_testbus_cfg_is_ok(struct ufs_qcom_host *host)
@ -2304,7 +2492,7 @@ static bool ufs_qcom_testbus_cfg_is_ok(struct ufs_qcom_host *host)
* mappings of select_minor, since there is no harm in
* configuring a non-existent select_minor
*/
if (host->testbus.select_minor > 0x1F) {
if (host->testbus.select_minor > 0xFF) {
dev_err(host->hba->dev,
"%s: 0x%05X is not a legal testbus option\n",
__func__, host->testbus.select_minor);
@ -2314,6 +2502,11 @@ static bool ufs_qcom_testbus_cfg_is_ok(struct ufs_qcom_host *host)
return true;
}
/*
* The caller of this function must make sure that the controller
* is out of runtime suspend and appropriate clocks are enabled
* before accessing.
*/
int ufs_qcom_testbus_config(struct ufs_qcom_host *host)
{
int reg;
@ -2373,7 +2566,8 @@ int ufs_qcom_testbus_config(struct ufs_qcom_host *host)
break;
case TSTBUS_UNIPRO:
reg = UFS_UNIPRO_CFG;
offset = 1;
offset = 20;
mask = 0xFFF;
break;
/*
* No need for a default case, since
@ -2383,8 +2577,6 @@ int ufs_qcom_testbus_config(struct ufs_qcom_host *host)
}
mask <<= offset;
pm_runtime_get_sync(host->hba->dev);
ufshcd_hold(host->hba, false);
ufshcd_rmwl(host->hba, TEST_BUS_SEL,
(u32)host->testbus.select_major << 19,
REG_UFS_CFG1);
@ -2392,8 +2584,11 @@ int ufs_qcom_testbus_config(struct ufs_qcom_host *host)
(u32)host->testbus.select_minor << offset,
reg);
ufs_qcom_enable_test_bus(host);
ufshcd_release(host->hba, false);
pm_runtime_put_sync(host->hba->dev);
/*
* Make sure the test bus configuration is
* committed before returning.
*/
mb();
return 0;
}
@ -2403,15 +2598,47 @@ static void ufs_qcom_testbus_read(struct ufs_hba *hba)
ufs_qcom_dump_regs(hba, UFS_TEST_BUS, 1, "UFS_TEST_BUS ");
}
static void ufs_qcom_dump_dbg_regs(struct ufs_hba *hba)
static void ufs_qcom_print_unipro_testbus(struct ufs_hba *hba)
{
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
u32 *testbus = NULL;
int i, nminor = 256, testbus_len = nminor * sizeof(u32);
testbus = kmalloc(testbus_len, GFP_KERNEL);
if (!testbus)
return;
host->testbus.select_major = TSTBUS_UNIPRO;
for (i = 0; i < nminor; i++) {
host->testbus.select_minor = i;
ufs_qcom_testbus_config(host);
testbus[i] = ufshcd_readl(hba, UFS_TEST_BUS);
}
print_hex_dump(KERN_ERR, "UNIPRO_TEST_BUS ", DUMP_PREFIX_OFFSET,
16, 4, testbus, testbus_len, false);
kfree(testbus);
}
static void ufs_qcom_dump_dbg_regs(struct ufs_hba *hba, bool no_sleep)
{
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
struct phy *phy = host->generic_phy;
ufs_qcom_dump_regs(hba, REG_UFS_SYS1CLK_1US, 16,
"HCI Vendor Specific Registers ");
ufs_qcom_print_hw_debug_reg_all(hba, NULL, ufs_qcom_dump_regs_wrapper);
if (no_sleep)
return;
/* sleep a bit intermittently as we are dumping too much data */
usleep_range(1000, 1100);
ufs_qcom_testbus_read(hba);
usleep_range(1000, 1100);
ufs_qcom_print_unipro_testbus(hba);
usleep_range(1000, 1100);
ufs_qcom_phy_dbg_register_dump(phy);
usleep_range(1000, 1100);
ufs_qcom_ice_print_regs(host);
}
@ -2436,6 +2663,7 @@ static struct ufs_hba_variant_ops ufs_hba_qcom_vops = {
.full_reset = ufs_qcom_full_reset,
.update_sec_cfg = ufs_qcom_update_sec_cfg,
.get_scale_down_gear = ufs_qcom_get_scale_down_gear,
.set_bus_vote = ufs_qcom_set_bus_vote,
.dbg_register_dump = ufs_qcom_dump_dbg_regs,
#ifdef CONFIG_DEBUG_FS
.add_debugfs = ufs_qcom_dbg_add_debugfs,

View file

@ -1,4 +1,4 @@
/* Copyright (c) 2013-2016, The Linux Foundation. All rights reserved.
/* Copyright (c) 2013-2017, The 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
@ -100,6 +100,7 @@ enum {
#define QUNIPRO_SEL UFS_BIT(0)
#define TEST_BUS_EN BIT(18)
#define TEST_BUS_SEL GENMASK(22, 19)
#define UFS_REG_TEST_BUS_EN BIT(30)
/* bit definitions for REG_UFS_CFG2 register */
#define UAWM_HW_CGC_EN (1 << 0)
@ -369,8 +370,10 @@ struct ufs_qcom_host {
u32 dbg_print_en;
struct ufs_qcom_testbus testbus;
spinlock_t ice_work_lock;
struct work_struct ice_cfg_work;
struct request *req_pending;
struct ufs_vreg *vddp_ref_clk;
};
static inline u32

View file

@ -1,4 +1,4 @@
/* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
/* Copyright (c) 2013-2017, The 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
@ -689,13 +689,13 @@ static void scenario_free_end_io_fn(struct request *rq, int err)
__blk_put_request(test_iosched->req_q, test_rq->rq);
spin_unlock_irqrestore(&test_iosched->lock, flags);
test_iosched_free_test_req_data_buffer(test_rq);
kfree(test_rq);
if (err)
pr_err("%s: request %d completed, err=%d", __func__,
test_rq->req_id, err);
test_iosched_free_test_req_data_buffer(test_rq);
kfree(test_rq);
check_test_completion(test_iosched);
}
@ -984,14 +984,14 @@ static void long_test_free_end_io_fn(struct request *rq, int err)
return;
}
test_iosched_free_test_req_data_buffer(test_rq);
kfree(test_rq);
utd->completed_req_count++;
if (err)
pr_err("%s: request %d completed, err=%d", __func__,
test_rq->req_id, err);
test_iosched_free_test_req_data_buffer(test_rq);
kfree(test_rq);
utd->completed_req_count++;
check_test_completion(test_iosched);
}
@ -1007,7 +1007,7 @@ static void long_test_free_end_io_fn(struct request *rq, int err)
static int run_long_test(struct test_iosched *test_iosched)
{
int ret = 0;
int direction, num_bios_per_request;
int direction, num_bios_per_request = 1;
static unsigned int inserted_requests;
u32 sector, seed, num_bios, seq_sector_delta;
struct ufs_test_data *utd = test_iosched->blk_dev_test_data;
@ -1028,14 +1028,12 @@ static int run_long_test(struct test_iosched *test_iosched)
/* Set test parameters */
switch (test_iosched->test_info.testcase) {
case UFS_TEST_LONG_RANDOM_READ:
num_bios_per_request = 1;
utd->long_test_num_reqs = (utd->sector_range * SECTOR_SIZE) /
(LONG_RAND_TEST_REQ_RATIO * TEST_BIO_SIZE *
num_bios_per_request);
direction = READ;
break;
case UFS_TEST_LONG_RANDOM_WRITE:
num_bios_per_request = 1;
utd->long_test_num_reqs = (utd->sector_range * SECTOR_SIZE) /
(LONG_RAND_TEST_REQ_RATIO * TEST_BIO_SIZE *
num_bios_per_request);

View file

@ -40,6 +40,22 @@
#include "ufshcd.h"
#include "ufshcd-pltfrm.h"
static int ufshcd_parse_reset_info(struct ufs_hba *hba)
{
int ret = 0;
hba->core_reset = devm_reset_control_get(hba->dev,
"core_reset");
if (IS_ERR(hba->core_reset)) {
ret = PTR_ERR(hba->core_reset);
dev_err(hba->dev, "core_reset unavailable,err = %d\n",
ret);
hba->core_reset = NULL;
}
return ret;
}
static int ufshcd_parse_clock_info(struct ufs_hba *hba)
{
int ret = 0;
@ -297,6 +313,20 @@ static void ufshcd_parse_dev_ref_clk_freq(struct ufs_hba *hba)
hba->dev_ref_clk_freq = REF_CLK_FREQ_26_MHZ;
}
static int ufshcd_parse_pinctrl_info(struct ufs_hba *hba)
{
int ret = 0;
/* Try to obtain pinctrl handle */
hba->pctrl = devm_pinctrl_get(hba->dev);
if (IS_ERR(hba->pctrl)) {
ret = PTR_ERR(hba->pctrl);
hba->pctrl = NULL;
}
return ret;
}
#ifdef CONFIG_SMP
/**
* ufshcd_pltfrm_suspend - suspend power management function
@ -401,6 +431,20 @@ int ufshcd_pltfrm_init(struct platform_device *pdev,
goto dealloc_host;
}
err = ufshcd_parse_reset_info(hba);
if (err) {
dev_err(&pdev->dev, "%s: reset parse failed %d\n",
__func__, err);
goto dealloc_host;
}
err = ufshcd_parse_pinctrl_info(hba);
if (err) {
dev_dbg(&pdev->dev, "%s: unable to parse pinctrl data %d\n",
__func__, err);
/* let's not fail the probe */
}
ufshcd_parse_dev_ref_clk_freq(hba);
ufshcd_parse_pm_levels(hba);
ufshcd_parse_gear_limits(hba);

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@
*
* This code is based on drivers/scsi/ufs/ufshcd.h
* Copyright (C) 2011-2013 Samsung India Software Operations
* Copyright (c) 2013-2016, The Linux Foundation. All rights reserved.
* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
*
* Authors:
* Santosh Yaraganavi <santosh.sy@samsung.com>
@ -39,6 +39,7 @@
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/hrtimer.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
@ -55,6 +56,7 @@
#include <linux/clk.h>
#include <linux/completion.h>
#include <linux/regulator/consumer.h>
#include <linux/reset.h>
#include "unipro.h"
#include <asm/irq.h>
@ -309,6 +311,7 @@ struct ufs_pwr_mode_info {
* @update_sec_cfg: called to restore host controller secure configuration
* @get_scale_down_gear: called to get the minimum supported gear to
* scale down
* @set_bus_vote: called to vote for the required bus bandwidth
* @add_debugfs: used to add debugfs entries
* @remove_debugfs: used to remove debugfs entries
*/
@ -332,9 +335,10 @@ struct ufs_hba_variant_ops {
int (*suspend)(struct ufs_hba *, enum ufs_pm_op);
int (*resume)(struct ufs_hba *, enum ufs_pm_op);
int (*full_reset)(struct ufs_hba *);
void (*dbg_register_dump)(struct ufs_hba *hba);
void (*dbg_register_dump)(struct ufs_hba *hba, bool no_sleep);
int (*update_sec_cfg)(struct ufs_hba *hba, bool restore_sec_cfg);
u32 (*get_scale_down_gear)(struct ufs_hba *);
int (*set_bus_vote)(struct ufs_hba *, bool);
#ifdef CONFIG_DEBUG_FS
void (*add_debugfs)(struct ufs_hba *hba, struct dentry *root);
void (*remove_debugfs)(struct ufs_hba *hba);
@ -393,8 +397,9 @@ enum clk_gating_state {
/**
* struct ufs_clk_gating - UFS clock gating related info
* @gate_work: worker to turn off clocks after some delay as specified in
* delay_ms
* @gate_hrtimer: hrtimer to invoke @gate_work after some delay as
* specified in @delay_ms
* @gate_work: worker to turn off clocks
* @ungate_work: worker to turn on clocks that will be used in case of
* interrupt context
* @state: the current clocks state
@ -412,7 +417,8 @@ enum clk_gating_state {
* completion before gating clocks.
*/
struct ufs_clk_gating {
struct delayed_work gate_work;
struct hrtimer gate_hrtimer;
struct work_struct gate_work;
struct work_struct ungate_work;
enum clk_gating_state state;
unsigned long delay_ms;
@ -425,6 +431,7 @@ struct ufs_clk_gating {
struct device_attribute enable_attr;
bool is_enabled;
int active_reqs;
struct workqueue_struct *ungating_workq;
};
/* Hibern8 state */
@ -801,6 +808,7 @@ struct ufs_hba {
u32 saved_uic_err;
u32 saved_ce_err;
bool silence_err_logs;
bool force_host_reset;
/* Device management request data */
struct ufs_dev_cmd dev_cmd;
@ -882,17 +890,33 @@ struct ufs_hba {
enum bkops_status urgent_bkops_lvl;
bool is_urgent_bkops_lvl_checked;
struct rw_semaphore clk_scaling_lock;
/* sync b/w diff contexts */
struct rw_semaphore lock;
unsigned long shutdown_in_prog;
struct reset_control *core_reset;
/* If set, don't gate device ref_clk during clock gating */
bool no_ref_clk_gating;
int scsi_block_reqs_cnt;
bool full_init_linereset;
struct pinctrl *pctrl;
int latency_hist_enabled;
struct io_latency_state io_lat_s;
};
static inline void ufshcd_mark_shutdown_ongoing(struct ufs_hba *hba)
{
set_bit(0, &hba->shutdown_in_prog);
}
static inline bool ufshcd_is_shutdown_ongoing(struct ufs_hba *hba)
{
return !!(test_bit(0, &hba->shutdown_in_prog));
}
/* Returns true if clocks can be gated. Otherwise false */
static inline bool ufshcd_is_clkgating_allowed(struct ufs_hba *hba)
{
@ -1233,10 +1257,11 @@ static inline int ufshcd_vops_full_reset(struct ufs_hba *hba)
}
static inline void ufshcd_vops_dbg_register_dump(struct ufs_hba *hba)
static inline void ufshcd_vops_dbg_register_dump(struct ufs_hba *hba,
bool no_sleep)
{
if (hba->var && hba->var->vops && hba->var->vops->dbg_register_dump)
hba->var->vops->dbg_register_dump(hba);
hba->var->vops->dbg_register_dump(hba, no_sleep);
}
static inline int ufshcd_vops_update_sec_cfg(struct ufs_hba *hba,
@ -1255,6 +1280,13 @@ static inline u32 ufshcd_vops_get_scale_down_gear(struct ufs_hba *hba)
return UFS_HS_G1;
}
static inline int ufshcd_vops_set_bus_vote(struct ufs_hba *hba, bool on)
{
if (hba->var && hba->var->vops && hba->var->vops->set_bus_vote)
return hba->var->vops->set_bus_vote(hba, on);
return 0;
}
#ifdef CONFIG_DEBUG_FS
static inline void ufshcd_vops_add_debugfs(struct ufs_hba *hba,
struct dentry *root)

View file

@ -190,6 +190,7 @@ enum {
/* UECPA - Host UIC Error Code PHY Adapter Layer 38h */
#define UIC_PHY_ADAPTER_LAYER_ERROR UFS_BIT(31)
#define UIC_PHY_ADAPTER_LAYER_GENERIC_ERROR UFS_BIT(4)
#define UIC_PHY_ADAPTER_LAYER_ERROR_CODE_MASK 0x1F
#define UIC_PHY_ADAPTER_LAYER_LANE_ERR_MASK 0xF

View file

@ -58,5 +58,6 @@ void ufs_qcom_phy_save_controller_version(struct phy *phy,
u8 major, u16 minor, u16 step);
const char *ufs_qcom_phy_name(struct phy *phy);
int ufs_qcom_phy_configure_lpm(struct phy *generic_phy, bool enable);
void ufs_qcom_phy_dbg_register_dump(struct phy *generic_phy);
#endif /* PHY_QCOM_UFS_H_ */