ufs: snapshot of UFS driver

This is a snapshot of UFS driver (host controller and PHY) taken as of
msm-4.4 commit <33ec44fb> ("cfg80211: Advertise extended capabilities per
interface type to userspace").

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: Ica7946230a37329560ad1e4667436e584228934f
Signed-off-by: Subhash Jadavani <subhashj@codeaurora.org>
This commit is contained in:
Subhash Jadavani 2016-08-11 11:35:26 -07:00 • committed by Kyle Yan
commit cce6fbcbcc
39 changed files with 11530 additions and 1035 deletions

View file

@ -8,7 +8,8 @@ contain a phandle reference to UFS PHY node.
Required properties:
- compatible : compatible list, contains "qcom,ufs-phy-qmp-20nm"
or "qcom,ufs-phy-qmp-14nm" according to the relevant phy in use.
or "qcom,ufs-phy-qmp-14nm" or "qcom,ufs-phy-qmp-v3"
or "qcom,ufs-phy-qrbtc-v2" according to the relevant phy in use.
- 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,6 +28,8 @@ Optional properties:
- 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)
Example:

View file

@ -4,13 +4,15 @@ UFSHC nodes are defined to describe on-chip UFS host controllers.
Each UFS controller instance should have its own node.
Required properties:
- compatible : must contain "jedec,ufs-1.1" or "jedec,ufs-2.0", may
also list one or more of the following:
- compatible : must contain "jedec,ufs-1.1", may also list one or more
of the following:
"qcom,msm8994-ufshc"
"qcom,msm8996-ufshc"
"qcom,ufshc"
- interrupts : <interrupt mapping for UFS host controller IRQ>
- 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).
Optional properties:
- phys : phandle to UFS PHY node
@ -38,9 +40,18 @@ Optional properties:
defined or a value in the array is "0" then it is assumed
that the frequency is set by the parent clock or a
fixed rate clock source.
-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.
- rpm-level : UFS Runtime power management level. Following PM levels are suppported:
0 - Both UFS device and Link in active state (Highest power consumption)
1 - UFS device in active state but Link in Hibern8 state
2 - UFS device in Sleep state but Link in active state
3 - UFS device in Sleep state and Link in hibern8 state (default PM level)
4 - UFS device in Power-down state and Link in Hibern8 state
5 - UFS device in Power-down state and Link in OFF state (Lowest power consumption)
- spm-level : UFS System power management level. Allowed PM levels are same as rpm-level.
- ufs-qcom-crypto : phandle to UFS-QCOM ICE (Inline Cryptographic Engine) node
- 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.
Note: If above properties are not defined it can be assumed that the supply
regulators or clocks are always on.
@ -48,9 +59,10 @@ regulators or clocks are always on.
Example:
ufshc@0xfc598000 {
compatible = "jedec,ufs-1.1";
reg = <0xfc598000 0x800>;
reg = <0xfc598000 0x800>, <0xfd512074 0x4>;
interrupts = <0 28 0>;
ufs-qcom-crypto = <&ufs_ice>;
vdd-hba-supply = <&xxx_reg0>;
vdd-hba-fixed-regulator;
vcc-supply = <&xxx_reg1>;
@ -66,4 +78,105 @@ Example:
freq-table-hz = <100000000 200000000>, <0 0>, <0 0>;
phys = <&ufsphy1>;
phy-names = "ufsphy";
rpm-level = <3>;
spm-level = <5>;
};
==== MSM UFS platform driver properties =====
* For UFS host controller in MSM platform following clocks are required -
Controller clock source -
"core_clk_src", max-clock-frequency-hz = 200MHz
Controller System clock branch:
"core_clk" - Controller core clock
AHB/AXI interface clocks:
"iface_clk" - AHB interface clock
"bus_clk" - AXI bus master clock
PHY to controller symbol synchronization clocks:
"rx_lane0_sync_clk" - RX Lane 0
"rx_lane1_sync_clk" - RX Lane 1
"tx_lane0_sync_clk" - TX Lane 0
"tx_lane1_sync_clk" - TX Lane 1
Optional reference clock input to UFS device
"ref_clk", max-clock-frequency-hz = 19.2MHz
* Following bus parameters are required -
- qcom,msm-bus,name
- qcom,msm-bus,num-cases
- qcom,msm-bus,num-paths
- qcom,msm-bus,vectors-KBps
For the above four properties please refer to
Documentation/devicetree/bindings/arm/msm/msm_bus.txt
Note: The instantaneous bandwidth (IB) value in the vectors-KBps field should
be zero as UFS data transfer path doesn't have latency requirements and
voting for aggregated bandwidth (AB) should take care of providing
optimum throughput requested.
- qcom,bus-vector-names: specifies string IDs for the corresponding
bus vectors in the same order as qcom,msm-bus,vectors-KBps property.
* The following parameters are optional, but required in order for PM QoS to be
enabled and functional in the driver:
- qcom,pm-qos-cpu-groups: arrays of unsigned integers representing the cpu groups.
The number of values in the array defines the number of cpu-groups.
Each value is a bit-mask defining the cpus that take part in that cpu group.
i.e. if bit N is set, then cpuN is a part of the cpu group. So basically,
a cpu group corelated to a cpu cluster.
A PM QoS request object is maintained for each cpu-group.
- qcom,pm-qos-cpu-group-latency-us: array of values used for PM QoS voting, one for each cpu-group defined.
the number of values must match the number of values defined in
qcom,pm-qos-cpu-mask property.
- 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.
Example:
ufshc@0xfc598000 {
...
qcom,msm-bus,name = "ufs1";
qcom,msm-bus,num-cases = <22>;
qcom,msm-bus,num-paths = <2>;
qcom,msm-bus,vectors-KBps =
<95 512 0 0>, <1 650 0 0>, /* No vote */
<95 512 922 0>, <1 650 1000 0>, /* PWM G1 */
<95 512 1844 0>, <1 650 1000 0>, /* PWM G2 */
<95 512 3688 0>, <1 650 1000 0>, /* PWM G3 */
<95 512 7376 0>, <1 650 1000 0>, /* PWM G4 */
<95 512 1844 0>, <1 650 1000 0>, /* PWM G1 L2 */
<95 512 3688 0>, <1 650 1000 0>, /* PWM G2 L2 */
<95 512 7376 0>, <1 650 1000 0>, /* PWM G3 L2 */
<95 512 14752 0>, <1 650 1000 0>, /* PWM G4 L2 */
<95 512 127796 0>, <1 650 1000 0>, /* HS G1 RA */
<95 512 255591 0>, <1 650 1000 0>, /* HS G2 RA */
<95 512 511181 0>, <1 650 1000 0>, /* HS G3 RA */
<95 512 255591 0>, <1 650 1000 0>, /* HS G1 RA L2 */
<95 512 511181 0>, <1 650 1000 0>, /* HS G2 RA L2 */
<95 512 1022362 0>, <1 650 1000 0>, /* HS G3 RA L2 */
<95 512 149422 0>, <1 650 1000 0>, /* HS G1 RB */
<95 512 298189 0>, <1 650 1000 0>, /* HS G2 RB */
<95 512 596378 0>, <1 650 1000 0>, /* HS G3 RB */
<95 512 298189 0>, <1 650 1000 0>, /* HS G1 RB L2 */
<95 512 596378 0>, <1 650 1000 0>, /* HS G2 RB L2 */
<95 512 1192756 0>, <1 650 1000 0>, /* HS G3 RB L2 */
<95 512 4096000 0>, <1 650 1000 0>; /* Max. bandwidth */
qcom,bus-vector-names = "MIN",
"PWM_G1_L1", "PWM_G2_L1", "PWM_G3_L1", "PWM_G4_L1",
"PWM_G1_L2", "PWM_G2_L2", "PWM_G3_L2", "PWM_G4_L2",
"HS_RA_G1_L1", "HS_RA_G2_L1", "HS_RA_G3_L1",
"HS_RA_G1_L2", "HS_RA_G2_L2", "HS_RA_G3_L2",
"HS_RB_G1_L1", "HS_RB_G2_L1", "HS_RB_G3_L1",
"HS_RB_G1_L2", "HS_RB_G2_L2", "HS_RB_G3_L2",
"MAX";
qcom,pm-qos-cpu-groups = <0x03 0x0C>; /* group0: cpu0, cpu1, group1: cpu2, cpu3 */
qcom,pm-qos-cpu-group-latency-us = <200 300>; /* group0: 200us, group1: 300us */
qcom,pm-qos-default-cpu = <0>;
};

View file

@ -50,6 +50,8 @@ obj-$(CONFIG_PHY_STIH41X_USB) += phy-stih41x-usb.o
obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs.o
obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-20nm.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-v2.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

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
* Copyright (c) 2013-2016, 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
@ -91,6 +91,7 @@ struct ufs_qcom_phy {
struct clk *ref_clk_src;
struct clk *ref_clk_parent;
struct clk *ref_clk;
struct clk *ref_aux_clk;
bool is_ref_clk_enabled;
bool is_dev_ref_clk_enabled;
struct ufs_qcom_phy_vreg vdda_pll;
@ -107,6 +108,23 @@ struct ufs_qcom_phy {
*/
#define UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE BIT(0)
/*
* On some UFS PHY HW revisions, UFS PHY power up calibration sequence
* cannot have SVS mode configuration otherwise calibration result
* cannot be used in HS-G3. So there are additional register writes must
* be done after the PHY is initialized but before the controller
* requests hibernate exit.
*/
#define UFS_QCOM_PHY_QUIRK_SVS_MODE BIT(1)
/*
* On some UFS PHY HW revisions, UFS PHY power up calibration sequence
* requires manual VCO tuning code and its better to rely on the VCO
* tuning code programmed by boot loader. Enable this quirk to enable
* programming the manually tuned VCO code.
*/
#define UFS_QCOM_PHY_QUIRK_VCO_MANUAL_TUNING BIT(2)
u8 host_ctrl_rev_major;
u16 host_ctrl_rev_minor;
u16 host_ctrl_rev_step;
@ -116,6 +134,7 @@ struct ufs_qcom_phy {
int cached_regs_table_size;
bool is_powered_on;
struct ufs_qcom_phy_specific_ops *phy_spec_ops;
u32 vco_tune1_mode1;
};
/**
@ -127,15 +146,21 @@ struct ufs_qcom_phy {
* @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
* @ctrl_rx_linecfg: pointer to a function that controls the Host Rx LineCfg
* state.
* @power_control: pointer to a function that controls analog rail of phy
* and writes to QSERDES_RX_SIGDET_CNTRL attribute
* @configure_lpm: pointer to a function that configures the phy
* for low power mode.
*/
struct ufs_qcom_phy_specific_ops {
int (*calibrate_phy)(struct ufs_qcom_phy *phy, bool is_rate_B);
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 (*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);
};
struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy);
@ -156,4 +181,8 @@ 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);
#endif

View file

@ -15,19 +15,49 @@
#include "phy-qcom-ufs-qmp-14nm.h"
#define UFS_PHY_NAME "ufs_phy_qmp_14nm"
#define UFS_PHY_VDDA_PHY_UV (925000)
static
int ufs_qcom_phy_qmp_14nm_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
bool is_rate_B)
{
int tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A);
int tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
int err;
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;
err = ufs_qcom_phy_calibrate(ufs_qcom_phy, phy_cal_table_rate_A,
tbl_size_A, phy_cal_table_rate_B, tbl_size_B, is_rate_B);
tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
tbl_B = phy_cal_table_rate_B;
if ((major == 0x2) && (minor == 0x000) && (step == 0x0000)) {
tbl_A = phy_cal_table_rate_A_2_0_0;
tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_2_0_0);
} else if ((major == 0x2) && (minor == 0x001) && (step == 0x0000)) {
tbl_A = phy_cal_table_rate_A_2_1_0;
tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_2_1_0);
} else if ((major == 0x2) && (minor == 0x002) && (step == 0x0000)) {
tbl_A = phy_cal_table_rate_A_2_2_0;
tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_2_2_0);
tbl_B = phy_cal_table_rate_B_2_2_0;
tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B_2_2_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,
tbl_B, tbl_size_B,
is_rate_B);
if (ufs_qcom_phy->quirks & UFS_QCOM_PHY_QUIRK_VCO_MANUAL_TUNING)
writel_relaxed(ufs_qcom_phy->vco_tune1_mode1,
ufs_qcom_phy->mmio + QSERDES_COM_VCO_TUNE1_MODE1);
out:
if (err)
dev_err(ufs_qcom_phy->dev,
"%s: ufs_qcom_phy_calibrate() failed %d\n",
@ -38,8 +68,15 @@ int ufs_qcom_phy_qmp_14nm_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
static
void ufs_qcom_phy_qmp_14nm_advertise_quirks(struct ufs_qcom_phy *phy_common)
{
phy_common->quirks =
UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
u8 major = phy_common->host_ctrl_rev_major;
u16 minor = phy_common->host_ctrl_rev_minor;
u16 step = phy_common->host_ctrl_rev_step;
if ((major == 0x2) && (minor == 0x000) && (step == 0x0000))
phy_common->quirks =
UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE |
UFS_QCOM_PHY_QUIRK_SVS_MODE |
UFS_QCOM_PHY_QUIRK_VCO_MANUAL_TUNING;
}
static int ufs_qcom_phy_qmp_14nm_init(struct phy *generic_phy)
@ -61,24 +98,66 @@ static int ufs_qcom_phy_qmp_14nm_init(struct phy *generic_phy)
__func__, err);
goto out;
}
phy_common->vdda_phy.max_uV = UFS_PHY_VDDA_PHY_UV;
phy_common->vdda_phy.min_uV = UFS_PHY_VDDA_PHY_UV;
ufs_qcom_phy_qmp_14nm_advertise_quirks(phy_common);
if (phy_common->quirks & UFS_QCOM_PHY_QUIRK_VCO_MANUAL_TUNING) {
phy_common->vco_tune1_mode1 = readl_relaxed(phy_common->mmio +
QSERDES_COM_VCO_TUNE1_MODE1);
dev_info(phy_common->dev, "%s: vco_tune1_mode1 0x%x\n",
__func__, phy_common->vco_tune1_mode1);
}
out:
return err;
}
static
void ufs_qcom_phy_qmp_14nm_power_control(struct ufs_qcom_phy *phy, bool val)
void ufs_qcom_phy_qmp_14nm_power_control(struct ufs_qcom_phy *phy,
bool power_ctrl)
{
writel_relaxed(val ? 0x1 : 0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
/*
* Before any transactions involving PHY, ensure PHY knows
* that it's analog rail is powered ON (or OFF).
*/
mb();
bool is_workaround_req = false;
if (phy->quirks &
UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE)
is_workaround_req = true;
if (!power_ctrl) {
/* apply PHY analog power collapse */
if (is_workaround_req) {
/* assert common reset before analog power collapse */
writel_relaxed(0x1, phy->mmio + QSERDES_COM_SW_RESET);
/*
* make sure that reset is propogated before analog
* power collapse
*/
mb();
}
/* 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();
if (is_workaround_req) {
/*
* de-assert common reset after coming out of analog
* power collapse
*/
writel_relaxed(0x0, phy->mmio + QSERDES_COM_SW_RESET);
/* make common reset is de-asserted before proceeding */
mb();
}
}
}
static inline
@ -90,6 +169,23 @@ void ufs_qcom_phy_qmp_14nm_set_tx_lane_enable(struct ufs_qcom_phy *phy, u32 val)
*/
}
static
void ufs_qcom_phy_qmp_14nm_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_14nm_start_serdes(struct ufs_qcom_phy *phy)
{
u32 tmp;
@ -109,9 +205,24 @@ static int ufs_qcom_phy_qmp_14nm_is_pcs_ready(struct ufs_qcom_phy *phy_common)
err = readl_poll_timeout(phy_common->mmio + UFS_PHY_PCS_READY_STATUS,
val, (val & MASK_PCS_READY), 10, 1000000);
if (err)
if (err) {
dev_err(phy_common->dev, "%s: poll for pcs failed err = %d\n",
__func__, err);
goto out;
}
if (phy_common->quirks & UFS_QCOM_PHY_QUIRK_SVS_MODE) {
int i;
for (i = 0; i < ARRAY_SIZE(phy_svs_mode_config_2_0_0); i++)
writel_relaxed(phy_svs_mode_config_2_0_0[i].cfg_value,
(phy_common->mmio +
phy_svs_mode_config_2_0_0[i].reg_offset));
/* apply above configuration immediately */
mb();
}
out:
return err;
}
@ -128,6 +239,7 @@ static struct ufs_qcom_phy_specific_ops phy_14nm_ops = {
.start_serdes = ufs_qcom_phy_qmp_14nm_start_serdes,
.is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_14nm_is_pcs_ready,
.set_tx_lane_enable = ufs_qcom_phy_qmp_14nm_set_tx_lane_enable,
.ctrl_rx_linecfg = ufs_qcom_phy_qmp_14nm_ctrl_rx_linecfg,
.power_control = ufs_qcom_phy_qmp_14nm_power_control,
};
@ -140,6 +252,7 @@ static int ufs_qcom_phy_qmp_14nm_probe(struct platform_device *pdev)
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
if (!phy) {
dev_err(dev, "%s: failed to allocate phy\n", __func__);
err = -ENOMEM;
goto out;
}

View file

@ -27,12 +27,14 @@
#define QSERDES_COM_BG_TIMER COM_OFF(0x0C)
#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN COM_OFF(0x34)
#define QSERDES_COM_SYS_CLK_CTRL COM_OFF(0x3C)
#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_BG_TRIM COM_OFF(0x70)
#define QSERDES_COM_CP_CTRL_MODE0 COM_OFF(0x78)
#define QSERDES_COM_CP_CTRL_MODE1 COM_OFF(0x7C)
#define QSERDES_COM_PLL_RCTRL_MODE0 COM_OFF(0x84)
@ -41,6 +43,7 @@
#define QSERDES_COM_PLL_CCTRL_MODE1 COM_OFF(0x94)
#define QSERDES_COM_SYSCLK_EN_SEL COM_OFF(0xAC)
#define QSERDES_COM_RESETSM_CNTRL COM_OFF(0xB4)
#define QSERDES_COM_RESCODE_DIV_NUM COM_OFF(0xC4)
#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)
@ -61,19 +64,35 @@
#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_CLK_SELECT COM_OFF(0x174)
#define QSERDES_COM_HSCLK_SEL COM_OFF(0x178)
#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_CMN_CONFIG COM_OFF(0x194)
#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_MISC2 COM_OFF(0x1B8)
#define QSERDES_COM_CORECLK_DIV_MODE1 COM_OFF(0x1BC)
/* 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 */
@ -81,7 +100,12 @@
#define QSERDES_TX_LANE_MODE TX_OFF(0, 0x94)
/* UFS PHY RX registers */
#define QSERDES_RX_UCDR_SVS_SO_GAIN_HALF RX_OFF(0, 0x30)
#define QSERDES_RX_UCDR_SVS_SO_GAIN_QUARTER RX_OFF(0, 0x34)
#define QSERDES_RX_UCDR_SVS_SO_GAIN_EIGHTH RX_OFF(0, 0x38)
#define QSERDES_RX_UCDR_SVS_SO_GAIN RX_OFF(0, 0x3C)
#define QSERDES_RX_UCDR_FASTLOCK_FO_GAIN RX_OFF(0, 0x40)
#define QSERDES_RX_UCDR_SO_SATURATION_ENABLE RX_OFF(0, 0x48)
#define QSERDES_RX_RX_TERM_BW RX_OFF(0, 0x90)
#define QSERDES_RX_RX_EQ_GAIN1_LSB RX_OFF(0, 0xC4)
#define QSERDES_RX_RX_EQ_GAIN1_MSB RX_OFF(0, 0xC8)
@ -93,6 +117,8 @@
#define QSERDES_RX_SIGDET_DEGLITCH_CNTRL RX_OFF(0, 0x11C)
#define QSERDES_RX_RX_INTERFACE_MODE RX_OFF(0, 0x12C)
#define UFS_PHY_RX_LINECFG_DISABLE_BIT BIT(1)
/*
* This structure represents the 14nm specific phy.
* common_cfg MUST remain the first field in this structure
@ -105,12 +131,102 @@ struct ufs_qcom_phy_qmp_14nm {
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_2_0_0[] = {
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, 0xd7),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL, 0x17),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CLK_SELECT, 0x30),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYS_CLK_CTRL, 0x06),
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, 0x05),
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, 0x1C),
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, 0x14),
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, 0x3F),
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_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_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, 0x00),
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(QSERDES_COM_RESCODE_DIV_NUM, 0x15),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CMN_MISC2, 0x1F),
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(UFS_PHY_RX_SIGDET_CTRL2, 0x6c),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_TX_LARGE_AMP_DRV_LVL, 0x12),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_TX_SMALL_AMP_DRV_LVL, 0x06),
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),
/*
* UFS_PHY_RX_PWM_GEAR_BAND configuration is changed after the power up
* sequence so make sure that this register gets set to power on reset
* value. This is required in case power up sequence is initiated after
* this register value got changed to value other than power on reset
* value.
*/
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_PWM_GEAR_BAND, 0x55),
};
/*
* For 2.1.0 revision, SVS mode configuration can be part of PHY power
* up sequence itself.
*/
static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_2_1_0[] = {
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, 0x05),
@ -155,23 +271,133 @@ static struct ufs_qcom_phy_calibration phy_cal_table_rate_A[] = {
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, 0x02),
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, 0x02),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_INTERFACE_MODE, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x18),
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, 0x0F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x0E),
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_COM_RESCODE_DIV_NUM, 0x15),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CMN_MISC2, 0x1F),
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(UFS_PHY_RX_SIGDET_CTRL2, 0x6c),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_TX_LARGE_AMP_DRV_LVL, 0x12),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_TX_SMALL_AMP_DRV_LVL, 0x06),
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_A_2_2_0[] = {
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_COM_RESCODE_DIV_NUM, 0x40),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CMN_MISC2, 0x63),
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, 0x54),
};
static struct ufs_qcom_phy_calibration phy_cal_table_rate_B_2_2_0[] = {
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x44),
};
/*
* For 2.0.0 revision, apply this SVS mode configuration after PHY power
* up sequence is completed.
*/
static struct ufs_qcom_phy_calibration phy_svs_mode_config_2_0_0[] = {
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_INTERFACE_MODE, 0x40),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_PWM_GEAR_BAND, 0x15),
};
#endif

View file

@ -147,6 +147,23 @@ void ufs_qcom_phy_qmp_20nm_set_tx_lane_enable(struct ufs_qcom_phy *phy, u32 val)
mb();
}
static
void ufs_qcom_phy_qmp_20nm_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_20nm_start_serdes(struct ufs_qcom_phy *phy)
{
u32 tmp;
@ -171,7 +188,7 @@ static int ufs_qcom_phy_qmp_20nm_is_pcs_ready(struct ufs_qcom_phy *phy_common)
return err;
}
static const struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = {
struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = {
.init = ufs_qcom_phy_qmp_20nm_init,
.exit = ufs_qcom_phy_exit,
.power_on = ufs_qcom_phy_power_on,
@ -179,11 +196,12 @@ static const struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = {
.owner = THIS_MODULE,
};
static struct ufs_qcom_phy_specific_ops phy_20nm_ops = {
struct ufs_qcom_phy_specific_ops phy_20nm_ops = {
.calibrate_phy = ufs_qcom_phy_qmp_20nm_phy_calibrate,
.start_serdes = ufs_qcom_phy_qmp_20nm_start_serdes,
.is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_20nm_is_pcs_ready,
.set_tx_lane_enable = ufs_qcom_phy_qmp_20nm_set_tx_lane_enable,
.ctrl_rx_linecfg = ufs_qcom_phy_qmp_20nm_ctrl_rx_linecfg,
.power_control = ufs_qcom_phy_qmp_20nm_power_control,
};
@ -196,6 +214,7 @@ static int ufs_qcom_phy_qmp_20nm_probe(struct platform_device *pdev)
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
if (!phy) {
dev_err(dev, "%s: failed to allocate phy\n", __func__);
err = -ENOMEM;
goto out;
}

View file

@ -101,6 +101,7 @@
#define UFS_PHY_RX_MIN_SAVE_CONFIG_TIME_CAPABILITY PHY_OFF(0xE8)
#define UFS_PHY_RX_PWM_BURST_CLOSURE_LENGTH_CAPABILITY PHY_OFF(0xFC)
#define UFS_PHY_RX_MIN_ACTIVATETIME_CAPABILITY PHY_OFF(0x100)
#define UFS_PHY_LINECFG_DISABLE PHY_OFF(0x134)
#define UFS_PHY_RX_SIGDET_CTRL3 PHY_OFF(0x14c)
#define UFS_PHY_RMMI_ATTR_CTRL PHY_OFF(0x160)
#define UFS_PHY_RMMI_RX_CFGUPDT_L1 (1 << 7)
@ -118,6 +119,7 @@
#define UFS_PHY_PCS_READY_STATUS PHY_OFF(0x174)
#define UFS_PHY_TX_LANE_ENABLE_MASK 0x3
#define UFS_PHY_RX_LINECFG_DISABLE_BIT BIT(1)
/*
* This structure represents the 20nm specific phy.

View file

@ -0,0 +1,265 @@
/*
* Copyright (c) 2013-2016, 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.h"
#define UFS_PHY_NAME "ufs_phy_qmp_v3"
static
int ufs_qcom_phy_qmp_v3_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;
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);
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_qmp_v3_init(struct phy *generic_phy)
{
struct ufs_qcom_phy_qmp_v3 *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_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_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_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_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_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);
goto out;
}
out:
return err;
}
static
int ufs_qcom_phy_qmp_v3_configure_lpm(struct ufs_qcom_phy *ufs_qcom_phy,
bool enable)
{
int err = 0;
int tbl_size;
struct ufs_qcom_phy_calibration *tbl = NULL;
/* The default low power mode configuration is SVS2 */
if (enable) {
tbl_size = ARRAY_SIZE(phy_cal_table_svs2_enable);
tbl = phy_cal_table_svs2_enable;
} else {
tbl_size = ARRAY_SIZE(phy_cal_table_svs2_disable);
tbl = phy_cal_table_svs2_disable;
}
if (!tbl) {
dev_err(ufs_qcom_phy->dev, "%s: tbl for SVS2 %s is NULL",
__func__, enable ? "enable" : "disable");
err = -EINVAL;
goto out;
}
ufs_qcom_phy_write_tbl(ufs_qcom_phy, tbl, tbl_size);
/* flush buffered writes */
mb();
out:
return err;
}
struct phy_ops ufs_qcom_phy_qmp_v3_phy_ops = {
.init = ufs_qcom_phy_qmp_v3_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_ops = {
.calibrate_phy = ufs_qcom_phy_qmp_v3_phy_calibrate,
.start_serdes = ufs_qcom_phy_qmp_v3_start_serdes,
.is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_v3_is_pcs_ready,
.set_tx_lane_enable = ufs_qcom_phy_qmp_v3_set_tx_lane_enable,
.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,
};
static int ufs_qcom_phy_qmp_v3_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct phy *generic_phy;
struct ufs_qcom_phy_qmp_v3 *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_phy_ops, &phy_v3_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_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_of_match[] = {
{.compatible = "qcom,ufs-phy-qmp-v3"},
{},
};
MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_v3_of_match);
static struct platform_driver ufs_qcom_phy_qmp_v3_driver = {
.probe = ufs_qcom_phy_qmp_v3_probe,
.remove = ufs_qcom_phy_qmp_v3_remove,
.driver = {
.of_match_table = ufs_qcom_phy_qmp_v3_of_match,
.name = "ufs_qcom_phy_qmp_v3",
.owner = THIS_MODULE,
},
};
module_platform_driver(ufs_qcom_phy_qmp_v3_driver);
MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP v3");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,275 @@
/*
* Copyright (c) 2013-2016, 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_H_
#define UFS_QCOM_PHY_QMP_V3_H_
#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)
/* 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_CMN_IETRIM COM_OFF(0x4C)
#define QSERDES_COM_CMN_IPTRIM COM_OFF(0x50)
#define QSERDES_COM_EP_CLOCK_DETECT_CTR COM_OFF(0x54)
#define QSERDES_COM_SYSCLK_DET_COMP_STATUS COM_OFF(0x58)
#define QSERDES_COM_CLK_EP_DIV COM_OFF(0x5C)
#define QSERDES_COM_CP_CTRL_MODE0 COM_OFF(0x60)
#define QSERDES_COM_CP_CTRL_MODE1 COM_OFF(0x64)
#define QSERDES_COM_PLL_RCTRL_MODE0 COM_OFF(0x68)
#define QSERDES_COM_PLL_RCTRL_MODE1 COM_OFF(0x6C)
#define QSERDES_COM_PLL_CCTRL_MODE0 COM_OFF(0x70)
#define QSERDES_COM_PLL_CCTRL_MODE1 COM_OFF(0x74)
#define QSERDES_COM_PLL_CNTRL COM_OFF(0x78)
#define SERDES_COM_BIAS_EN_CTRL_BY_PSM COM_OFF(0x7C)
#define QSERDES_COM_SYSCLK_EN_SEL COM_OFF(0x80)
#define QSERDES_COM_CML_SYSCLK_SEL COM_OFF(0x84)
#define QSERDES_COM_RESETSM_CNTRL COM_OFF(0x88)
#define QSERDES_COM_RESETSM_CNTRL2 COM_OFF(0x8C)
#define QSERDES_COM_LOCK_CMP_EN COM_OFF(0x90)
#define QSERDES_COM_LOCK_CMP_CFG COM_OFF(0x94)
#define QSERDES_COM_LOCK_CMP1_MODE0 COM_OFF(0x98)
#define QSERDES_COM_LOCK_CMP2_MODE0 COM_OFF(0x9C)
#define QSERDES_COM_LOCK_CMP3_MODE0 COM_OFF(0xA0)
#define QSERDES_COM_LOCK_CMP1_MODE1 COM_OFF(0xA4)
#define QSERDES_COM_LOCK_CMP2_MODE1 COM_OFF(0xA8)
#define QSERDES_COM_LOCK_CMP3_MODE1 COM_OFF(0xAC)
#define QSERDES_COM_DEC_START_MODE0 COM_OFF(0xB0)
#define QSERDES_COM_DEC_START_MODE1 COM_OFF(0xB4)
#define QSERDES_COM_DIV_FRAC_START1_MODE0 COM_OFF(0xB8)
#define QSERDES_COM_DIV_FRAC_START2_MODE0 COM_OFF(0xBC)
#define QSERDES_COM_DIV_FRAC_START3_MODE0 COM_OFF(0xC0)
#define QSERDES_COM_DIV_FRAC_START1_MODE1 COM_OFF(0xC4)
#define QSERDES_COM_DIV_FRAC_START2_MODE1 COM_OFF(0xC8)
#define QSERDES_COM_DIV_FRAC_START3_MODE1 COM_OFF(0xCC)
#define QSERDES_COM_INTEGLOOP_INITVAL COM_OFF(0xD0)
#define QSERDES_COM_INTEGLOOP_EN COM_OFF(0xD4)
#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0 COM_OFF(0xD8)
#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0 COM_OFF(0xDC)
#define QSERDES_COM_INTEGLOOP_GAIN0_MODE1 COM_OFF(0xE0)
#define QSERDES_COM_INTEGLOOP_GAIN1_MODE1 COM_OFF(0xE4)
#define QSERDES_COM_VCOCAL_DEADMAN_CTRL COM_OFF(0xE8)
#define QSERDES_COM_VCO_TUNE_CTRL COM_OFF(0xEC)
#define QSERDES_COM_VCO_TUNE_MAP COM_OFF(0xF0)
#define QSERDES_COM_VCO_TUNE1_MODE0 COM_OFF(0xF4)
#define QSERDES_COM_VCO_TUNE2_MODE0 COM_OFF(0xF8)
#define QSERDES_COM_VCO_TUNE1_MODE1 COM_OFF(0xFC)
#define QSERDES_COM_VCO_TUNE2_MODE1 COM_OFF(0x100)
#define QSERDES_COM_VCO_TUNE_INITVAL1 COM_OFF(0x104)
#define QSERDES_COM_VCO_TUNE_INITVAL2 COM_OFF(0x108)
#define QSERDES_COM_VCO_TUNE_MINVAL1 COM_OFF(0x10C)
#define QSERDES_COM_VCO_TUNE_MINVAL2 COM_OFF(0x110)
#define QSERDES_COM_VCO_TUNE_MAXVAL1 COM_OFF(0x114)
#define QSERDES_COM_VCO_TUNE_MAXVAL2 COM_OFF(0x118)
#define QSERDES_COM_VCO_TUNE_TIMER1 COM_OFF(0x11C)
#define QSERDES_COM_VCO_TUNE_TIMER2 COM_OFF(0x120)
#define QSERDES_COM_CMN_STATUS COM_OFF(0x124)
#define QSERDES_COM_RESET_SM_STATUS COM_OFF(0x128)
#define QSERDES_COM_RESTRIM_CODE_STATUS COM_OFF(0x12C)
#define QSERDES_COM_PLLCAL_CODE1_STATUS COM_OFF(0x130)
#define QSERDES_COM_PLLCAL_CODE2_STATUS COM_OFF(0x134)
#define QSERDES_COM_CLK_SELECT COM_OFF(0x138)
#define QSERDES_COM_HSCLK_SEL COM_OFF(0x13C)
#define QSERDES_COM_INTEGLOOP_BINCODE_STATUS COM_OFF(0x140)
#define QSERDES_COM_PLL_ANALOG COM_OFF(0x144)
#define QSERDES_COM_CORECLK_DIV_MODE0 COM_OFF(0x148)
#define QSERDES_COM_CORECLK_DIV_MODE1 COM_OFF(0x14C)
#define QSERDES_COM_SW_RESET COM_OFF(0x150)
#define QSERDES_COM_CORE_CLK_EN COM_OFF(0x154)
#define QSERDES_COM_C_READY_STATUS COM_OFF(0x158)
#define QSERDES_COM_CMN_CONFIG COM_OFF(0x15C)
#define QSERDES_COM_CMN_RATE_OVERRIDE COM_OFF(0x160)
#define QSERDES_COM_SVS_MODE_CLK_SEL COM_OFF(0x164)
#define QSERDES_COM_DEBUG_BUS0 COM_OFF(0x168)
#define QSERDES_COM_DEBUG_BUS1 COM_OFF(0x16C)
#define QSERDES_COM_DEBUG_BUS2 COM_OFF(0x170)
#define QSERDES_COM_DEBUG_BUS3 COM_OFF(0x174)
#define QSERDES_COM_DEBUG_BUS_SEL COM_OFF(0x178)
#define QSERDES_COM_CMN_MISC1 COM_OFF(0x17C)
#define QSERDES_COM_CMN_MISC2 COM_OFF(0x180)
#define QSERDES_COM_CMN_MODE COM_OFF(0x184)
#define QSERDES_COM_CMN_VREG_SEL COM_OFF(0x188)
/* 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_TX_LARGE_AMP_DRV_LVL PHY_OFF(0x2C)
#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_SIGDET_CTRL2 PHY_OFF(0x140)
#define UFS_PHY_RX_PWM_GEAR_BAND PHY_OFF(0x14C)
#define UFS_PHY_PCS_READY_STATUS PHY_OFF(0x160)
/* UFS PHY TX registers */
#define QSERDES_TX0_TRANSCEIVER_BIAS_EN TX_OFF(0, 0x5C)
#define QSERDES_TX0_LANE_MODE_1 TX_OFF(0, 0x8C)
#define QSERDES_TX0_LANE_MODE_2 TX_OFF(0, 0x90)
#define QSERDES_TX0_LANE_MODE_3 TX_OFF(0, 0x94)
/* 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)
#define QSERDES_RX0_UCDR_SVS_SO_GAIN RX_OFF(0, 0x2C)
#define QSERDES_RX0_UCDR_FASTLOCK_FO_GAIN RX_OFF(0, 0x30)
#define QSERDES_RX0_UCDR_SO_SATURATION_AND_ENABLE RX_OFF(0, 0x34)
#define QSERDES_RX0_UCDR_FASTLOCK_COUNT_LOW RX_OFF(0, 0x3C)
#define QSERDES_RX0_UCDR_PI_CONTROLS RX_OFF(0, 0x44)
#define QSERDES_RX0_RX_TERM_BW RX_OFF(0, 0x7C)
#define QSERDES_RX0_RX_EQ_GAIN2_LSB RX_OFF(0, 0xC8)
#define QSERDES_RX0_RX_EQ_GAIN2_MSB RX_OFF(0, 0xCC)
#define QSERDES_RX0_RX_EQU_ADAPTOR_CNTRL1 RX_OFF(0, 0xD0)
#define QSERDES_RX0_RX_EQU_ADAPTOR_CNTRL2 RX_OFF(0, 0xD4)
#define QSERDES_RX0_RX_EQU_ADAPTOR_CNTRL3 RX_OFF(0, 0xD8)
#define QSERDES_RX0_RX_EQU_ADAPTOR_CNTRL4 RX_OFF(0, 0xDC)
#define QSERDES_RX0_SIGDET_CNTRL RX_OFF(0, 0x104)
#define QSERDES_RX0_SIGDET_LVL RX_OFF(0, 0x108)
#define QSERDES_RX0_SIGDET_DEGLITCH_CNTRL RX_OFF(0, 0x10C)
#define QSERDES_RX0_RX_INTERFACE_MODE RX_OFF(0, 0x11C)
#define UFS_PHY_RX_LINECFG_DISABLE_BIT BIT(1)
/*
* This structure represents the v3 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 {
struct ufs_qcom_phy common_cfg;
};
static struct ufs_qcom_phy_calibration phy_cal_table_rate_A[] = {
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_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, 0x08),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RCTRL_MODE0, 0x16),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CCTRL_MODE0, 0x34),
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, 0xCB),
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, 0x08),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RCTRL_MODE1, 0x16),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CCTRL_MODE1, 0x34),
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, 0xB2),
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(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),
};
static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = {
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x44),
};
static struct ufs_qcom_phy_calibration phy_cal_table_svs2_enable[] = {
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV_MODE0, 0x14),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV_MODE1, 0x14),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SVS_MODE_CLK_SEL, 0x0a),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x7e),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE0, 0x7f),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE0, 0x06),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE1, 0x7e),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE1, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE1, 0x99),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE1, 0x07),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_TIMER_20US_CORECLK_STEPS_MSB, 0x0b),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_TIMER_20US_CORECLK_STEPS_LSB, 0x66),
};
static struct ufs_qcom_phy_calibration phy_cal_table_svs2_disable[] = {
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV_MODE0, 0x0a),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV_MODE1, 0x0a),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SVS_MODE_CLK_SEL, 0x05),
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_LOCK_CMP1_MODE0, 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE0, 0x0c),
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_LOCK_CMP1_MODE1, 0x32),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE1, 0x0f),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_TIMER_20US_CORECLK_STEPS_MSB, 0x16),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_TIMER_20US_CORECLK_STEPS_LSB, 0xcc),
};
#endif

View file

@ -0,0 +1,205 @@
/*
* Copyright (c) 2013-2016, 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-qrbtc-v2.h"
#define UFS_PHY_NAME "ufs_phy_qrbtc_v2"
static
int ufs_qcom_phy_qrbtc_v2_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
bool is_rate_B)
{
int err;
int tbl_size_A;
struct ufs_qcom_phy_calibration *tbl_A;
struct ufs_qcom_phy_qrbtc_v2 *qrbtc_phy = container_of(ufs_qcom_phy,
struct ufs_qcom_phy_qrbtc_v2, common_cfg);
writel_relaxed(0x15f, qrbtc_phy->u11_regs + U11_UFS_RESET_REG_OFFSET);
/* 50ms are required to stabilize the reset */
usleep_range(50000, 50100);
writel_relaxed(0x0, qrbtc_phy->u11_regs + U11_UFS_RESET_REG_OFFSET);
/* Set R3PC REF CLK */
writel_relaxed(0x80, qrbtc_phy->u11_regs + U11_QRBTC_CONTROL_OFFSET);
tbl_A = phy_cal_table_rate_A;
tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A);
err = ufs_qcom_phy_calibrate(ufs_qcom_phy,
tbl_A, tbl_size_A,
NULL, 0,
false);
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_v2_is_pcs_ready(struct ufs_qcom_phy *phy_common)
{
int err = 0;
u32 val;
struct ufs_qcom_phy_qrbtc_v2 *qrbtc_phy = container_of(phy_common,
struct ufs_qcom_phy_qrbtc_v2, common_cfg);
/*
* 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);
writel_relaxed(0x100, qrbtc_phy->u11_regs + U11_QRBTC_TX_CLK_CTRL);
return err;
}
static void ufs_qcom_phy_qrbtc_v2_start_serdes(struct ufs_qcom_phy *phy)
{
u32 temp;
writel_relaxed(0x01, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL_OFFSET);
temp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START_OFFSET);
temp |= 0x1;
writel_relaxed(temp, phy->mmio + UFS_PHY_PHY_START_OFFSET);
/* Ensure register value is committed */
mb();
}
static int ufs_qcom_phy_qrbtc_v2_init(struct phy *generic_phy)
{
return 0;
}
struct phy_ops ufs_qcom_phy_qrbtc_v2_phy_ops = {
.init = ufs_qcom_phy_qrbtc_v2_init,
.exit = ufs_qcom_phy_exit,
.owner = THIS_MODULE,
};
struct ufs_qcom_phy_specific_ops phy_qrbtc_v2_ops = {
.calibrate_phy = ufs_qcom_phy_qrbtc_v2_phy_calibrate,
.start_serdes = ufs_qcom_phy_qrbtc_v2_start_serdes,
.is_physical_coding_sublayer_ready =
ufs_qcom_phy_qrbtc_v2_is_pcs_ready,
};
static int ufs_qcom_phy_qrbtc_v2_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct phy *generic_phy;
struct ufs_qcom_phy_qrbtc_v2 *phy;
struct resource *res;
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_v2_phy_ops, &phy_qrbtc_v2_ops);
if (!generic_phy) {
dev_err(dev, "%s: ufs_qcom_phy_generic_probe() failed\n",
__func__);
err = -EIO;
goto out;
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "u11_user");
if (!res) {
dev_err(dev, "%s: u11_user resource not found\n", __func__);
err = -EINVAL;
goto out;
}
phy->u11_regs = devm_ioremap_resource(dev, res);
if (IS_ERR_OR_NULL(phy->u11_regs)) {
if (IS_ERR(phy->u11_regs)) {
err = PTR_ERR(phy->u11_regs);
phy->u11_regs = NULL;
dev_err(dev, "%s: ioremap for phy_mem resource failed %d\n",
__func__, err);
} else {
dev_err(dev, "%s: ioremap for phy_mem resource failed\n",
__func__);
err = -ENOMEM;
}
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_qrbtc_v2_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_qrbtc_v2_of_match[] = {
{.compatible = "qcom,ufs-phy-qrbtc-v2"},
{},
};
MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qrbtc_v2_of_match);
static struct platform_driver ufs_qcom_phy_qrbtc_v2_driver = {
.probe = ufs_qcom_phy_qrbtc_v2_probe,
.remove = ufs_qcom_phy_qrbtc_v2_remove,
.driver = {
.of_match_table = ufs_qcom_phy_qrbtc_v2_of_match,
.name = "ufs_qcom_phy_qrbtc_v2",
.owner = THIS_MODULE,
},
};
module_platform_driver(ufs_qcom_phy_qrbtc_v2_driver);
MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QRBTC V2");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,116 @@
/*
* Copyright (c) 2013-2016, 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_QRBTC_V2_H_
#define UFS_QCOM_PHY_QRBTC_V2_H_
#include "phy-qcom-ufs-i.h"
/* QCOM UFS PHY control registers */
#define COM_OFF(x) (0x000 + x)
#define PHY_OFF(x) (0x700 + x)
#define PHY_USR(x) (x)
#define UFS_PHY_PHY_START_OFFSET PHY_OFF(0x00)
#define UFS_PHY_POWER_DOWN_CONTROL_OFFSET PHY_OFF(0x04)
#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN_OFFSET COM_OFF(0x20)
#define QSERDES_COM_SYSCLK_EN_SEL COM_OFF(0x38)
#define QSERDES_COM_SYS_CLK_CTRL COM_OFF(0x00)
#define QSERDES_COM_RES_CODE_TXBAND COM_OFF(0x3C)
#define QSERDES_COM_PLL_VCOTAIL_EN COM_OFF(0x04)
#define QSERDES_COM_PLL_CNTRL COM_OFF(0x14)
#define QSERDES_COM_PLL_CLKEPDIV COM_OFF(0xB0)
#define QSERDES_COM_RESETSM_CNTRL COM_OFF(0x40)
#define QSERDES_COM_PLL_RXTXEPCLK_EN COM_OFF(0xA8)
#define QSERDES_COM_PLL_CRCTRL COM_OFF(0xAC)
#define QSERDES_COM_DEC_START1 COM_OFF(0x64)
#define QSERDES_COM_DEC_START2 COM_OFF(0xA4)
#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_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_PLL_IP_SETI COM_OFF(0x18)
#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_RESET_SM COM_OFF(0xBC)
#define QSERDES_COM_PWM_CNTRL1 COM_OFF(0x280)
#define QSERDES_COM_PWM_CNTRL2 COM_OFF(0x284)
#define QSERDES_COM_PWM_NDIV COM_OFF(0x288)
#define QSERDES_COM_CDR_CONTROL COM_OFF(0x200)
#define QSERDES_COM_CDR_CONTROL_HALF COM_OFF(0x298)
#define QSERDES_COM_CDR_CONTROL_QUARTER COM_OFF(0x29C)
#define QSERDES_COM_SIGDET_CNTRL COM_OFF(0x234)
#define QSERDES_COM_SIGDET_CNTRL2 COM_OFF(0x28C)
#define QSERDES_COM_UFS_CNTRL COM_OFF(0x290)
/* 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_OFFSET, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN_OFFSET, 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, 0x88),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CLKEPDIV, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x30),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RXTXEPCLK_EN, 0x10),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CRCTRL, 0x94),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x98),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x02),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x8C),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0xAE),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x1F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0xF7),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x13),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x01),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x01),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x3B),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x0A),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x04),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PWM_CNTRL1, 0xCF),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PWM_CNTRL2, 0x61),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PWM_NDIV, 0x4F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CDR_CONTROL, 0xF2),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CDR_CONTROL_HALF, 0x2A),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CDR_CONTROL_QUARTER, 0x2A),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SIGDET_CNTRL, 0xC0),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SIGDET_CNTRL2, 0x07),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_UFS_CNTRL, 0x18),
};
/*
* This structure represents the qrbtc-v2 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_v2 {
struct ufs_qcom_phy common_cfg;
void __iomem *u11_regs;
};
#endif

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
* Copyright (c) 2013-2016, 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
@ -17,7 +17,7 @@
#define MAX_PROP_NAME 32
#define VDDA_PHY_MIN_UV 1000000
#define VDDA_PHY_MAX_UV 1000000
#define VDDA_PLL_MIN_UV 1800000
#define VDDA_PLL_MIN_UV 1200000
#define VDDA_PLL_MAX_UV 1800000
#define VDDP_REF_CLK_MIN_UV 1200000
#define VDDP_REF_CLK_MAX_UV 1200000
@ -29,13 +29,24 @@ static int ufs_qcom_phy_init_vreg(struct phy *, struct ufs_qcom_phy_vreg *,
static int ufs_qcom_phy_base_init(struct platform_device *pdev,
struct ufs_qcom_phy *phy_common);
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)
{
int i;
int ret = 0;
if (!tbl_A) {
@ -44,9 +55,7 @@ int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
goto out;
}
for (i = 0; i < tbl_size_A; i++)
writel_relaxed(tbl_A[i].cfg_value,
ufs_qcom_phy->mmio + tbl_A[i].reg_offset);
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
@ -62,9 +71,7 @@ int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
goto out;
}
for (i = 0; i < tbl_size_B; i++)
writel_relaxed(tbl_B[i].cfg_value,
ufs_qcom_phy->mmio + tbl_B[i].reg_offset);
ufs_qcom_phy_write_tbl(ufs_qcom_phy, tbl_B, tbl_size_B);
}
/* flush buffered writes */
@ -135,23 +142,21 @@ int ufs_qcom_phy_base_init(struct platform_device *pdev,
int err = 0;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_mem");
if (!res) {
dev_err(dev, "%s: phy_mem resource not found\n", __func__);
err = -ENOMEM;
goto out;
}
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;
}
/* "dev_ref_clk_ctrl_mem" is optional resource */
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"dev_ref_clk_ctrl_mem");
phy_common->dev_ref_clk_ctrl_mmio = devm_ioremap_resource(dev, res);
if (IS_ERR((void const *)phy_common->dev_ref_clk_ctrl_mmio))
phy_common->dev_ref_clk_ctrl_mmio = NULL;
return 0;
out:
return err;
}
static int __ufs_qcom_phy_clk_get(struct phy *phy,
@ -186,16 +191,27 @@ 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)
goto out;
dev_dbg(phy->dev, "%s: failed to get tx_iface_clk\n",
__func__);
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)
goto out;
dev_dbg(phy->dev, "%s: failed to get rx_iface_clk\n",
__func__);
err = ufs_qcom_phy_clk_get(generic_phy, "ref_clk_src",
&phy_common->ref_clk_src);
@ -211,7 +227,15 @@ ufs_qcom_phy_init_clks(struct phy *generic_phy,
err = ufs_qcom_phy_clk_get(generic_phy, "ref_clk",
&phy_common->ref_clk);
if (err)
goto out;
/*
* "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(generic_phy, "ref_aux_clk",
&phy_common->ref_aux_clk, false);
out:
return err;
}
@ -222,6 +246,7 @@ 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");
@ -230,10 +255,13 @@ ufs_qcom_phy_init_vregulators(struct phy *generic_phy,
err = ufs_qcom_phy_init_vreg(generic_phy, &phy_common->vdda_phy,
"vdda-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);
@ -421,9 +449,26 @@ int ufs_qcom_phy_enable_ref_clk(struct phy *generic_phy)
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);
@ -464,6 +509,13 @@ void ufs_qcom_phy_disable_ref_clk(struct phy *generic_phy)
struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_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);
clk_disable_unprepare(phy->ref_clk);
/*
* "ref_clk_parent" is optional clock hence make sure that clk
@ -477,56 +529,6 @@ void ufs_qcom_phy_disable_ref_clk(struct phy *generic_phy)
}
EXPORT_SYMBOL_GPL(ufs_qcom_phy_disable_ref_clk);
#define UFS_REF_CLK_EN (1 << 5)
static void ufs_qcom_phy_dev_ref_clk_ctrl(struct phy *generic_phy, bool enable)
{
struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy);
if (phy->dev_ref_clk_ctrl_mmio &&
(enable ^ phy->is_dev_ref_clk_enabled)) {
u32 temp = readl_relaxed(phy->dev_ref_clk_ctrl_mmio);
if (enable)
temp |= UFS_REF_CLK_EN;
else
temp &= ~UFS_REF_CLK_EN;
/*
* If we are here to disable this clock immediately after
* entering into hibern8, we need to make sure that device
* ref_clk is active atleast 1us after the hibern8 enter.
*/
if (!enable)
udelay(1);
writel_relaxed(temp, phy->dev_ref_clk_ctrl_mmio);
/* ensure that ref_clk is enabled/disabled before we return */
wmb();
/*
* If we call hibern8 exit after this, we need to make sure that
* device ref_clk is stable for atleast 1us before the hibern8
* exit command.
*/
if (enable)
udelay(1);
phy->is_dev_ref_clk_enabled = enable;
}
}
void ufs_qcom_phy_enable_dev_ref_clk(struct phy *generic_phy)
{
ufs_qcom_phy_dev_ref_clk_ctrl(generic_phy, true);
}
EXPORT_SYMBOL_GPL(ufs_qcom_phy_enable_dev_ref_clk);
void ufs_qcom_phy_disable_dev_ref_clk(struct phy *generic_phy)
{
ufs_qcom_phy_dev_ref_clk_ctrl(generic_phy, false);
}
EXPORT_SYMBOL_GPL(ufs_qcom_phy_disable_dev_ref_clk);
/* Turn ON M-PHY RMMI interface clocks */
int ufs_qcom_phy_enable_iface_clk(struct phy *generic_phy)
{
@ -536,6 +538,9 @@ int ufs_qcom_phy_enable_iface_clk(struct phy *generic_phy)
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",
@ -561,6 +566,9 @@ void ufs_qcom_phy_disable_iface_clk(struct phy *generic_phy)
{
struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_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);
@ -591,19 +599,26 @@ int ufs_qcom_phy_set_tx_lane_enable(struct phy *generic_phy, u32 tx_lanes)
struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
int ret = 0;
if (!ufs_qcom_phy->phy_spec_ops->set_tx_lane_enable) {
dev_err(ufs_qcom_phy->dev, "%s: set_tx_lane_enable() callback is not supported\n",
__func__);
ret = -ENOTSUPP;
} else {
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);
}
return ret;
}
EXPORT_SYMBOL_GPL(ufs_qcom_phy_set_tx_lane_enable);
int 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);
int ret = 0;
if (ufs_qcom_phy->phy_spec_ops->ctrl_rx_linecfg)
ufs_qcom_phy->phy_spec_ops->ctrl_rx_linecfg(ufs_qcom_phy, ctrl);
return ret;
}
EXPORT_SYMBOL_GPL(ufs_qcom_phy_ctrl_rx_linecfg);
void ufs_qcom_phy_save_controller_version(struct phy *generic_phy,
u8 major, u16 minor, u16 step)
{
@ -636,6 +651,14 @@ int ufs_qcom_phy_calibrate_phy(struct phy *generic_phy, bool is_rate_B)
}
EXPORT_SYMBOL_GPL(ufs_qcom_phy_calibrate_phy);
const char *ufs_qcom_phy_name(struct phy *phy)
{
struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(phy);
return ufs_qcom_phy->name;
}
EXPORT_SYMBOL(ufs_qcom_phy_name);
int ufs_qcom_phy_remove(struct phy *generic_phy,
struct ufs_qcom_phy *ufs_qcom_phy)
{
@ -747,3 +770,21 @@ int ufs_qcom_phy_power_off(struct phy *generic_phy)
return 0;
}
EXPORT_SYMBOL_GPL(ufs_qcom_phy_power_off);
int ufs_qcom_phy_configure_lpm(struct phy *generic_phy, bool enable)
{
struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
int ret = 0;
if (ufs_qcom_phy->phy_spec_ops->configure_lpm) {
ret = ufs_qcom_phy->phy_spec_ops->
configure_lpm(ufs_qcom_phy, enable);
if (ret)
dev_err(ufs_qcom_phy->dev,
"%s: configure_lpm(%s) failed %d\n",
__func__, enable ? "enable" : "disable", ret);
}
return ret;
}
EXPORT_SYMBOL(ufs_qcom_phy_configure_lpm);

View file

@ -37,7 +37,6 @@ config SCSI_UFSHCD
depends on SCSI && SCSI_DMA
select PM_DEVFREQ
select DEVFREQ_GOV_SIMPLE_ONDEMAND
select NLS
---help---
This selects the support for UFS devices in Linux, say Y and make
sure that you know the name of your UFS host adapter (the card
@ -61,14 +60,6 @@ config SCSI_UFSHCD_PCI
If unsure, say N.
config SCSI_UFS_DWC_TC_PCI
tristate "DesignWare pci support using a G210 Test Chip"
depends on SCSI_UFSHCD && PCI
---help---
Synopsys Test Chip is a PHY for prototyping purposes.
If unsure, say N.
config SCSI_UFSHCD_PLATFORM
tristate "Platform bus based UFS Controller support"
depends on SCSI_UFSHCD
@ -80,14 +71,6 @@ config SCSI_UFSHCD_PLATFORM
If unsure, say N.
config SCSI_UFS_DWC_TC_PLATFORM
tristate "DesignWare platform support using a G210 Test Chip"
depends on SCSI_UFSHCD_PLATFORM
---help---
Synopsys Test Chip is a PHY for prototyping purposes.
If unsure, say N.
config SCSI_UFS_QCOM
tristate "QCOM specific hooks to UFS controller platform driver"
depends on SCSI_UFSHCD_PLATFORM && ARCH_QCOM
@ -100,3 +83,26 @@ config SCSI_UFS_QCOM
Select this if you have UFS controller on QCOM chipset.
If unsure, say N.
config SCSI_UFS_QCOM_ICE
bool "QCOM specific hooks to Inline Crypto Engine for UFS driver"
depends on SCSI_UFS_QCOM && CRYPTO_DEV_QCOM_ICE
help
This selects the QCOM specific additions to support Inline Crypto
Engine (ICE).
ICE accelerates the crypto operations and maintains the high UFS
performance.
Select this if you have ICE supported for UFS on QCOM chipset.
If unsure, say N.
config SCSI_UFS_TEST
tristate "Universal Flash Storage host controller driver unit-tests"
depends on SCSI_UFSHCD && IOSCHED_TEST
default m
---help---
This adds UFS Host controller unit-test framework.
The UFS unit-tests register as a block device test utility to
the test-iosched and will be initiated when the test-iosched will
be chosen to be the active I/O scheduler.

View file

@ -1,7 +1,8 @@
# UFSHCD makefile
obj-$(CONFIG_SCSI_UFS_DWC_TC_PCI) += tc-dwc-g210-pci.o ufshcd-dwc.o tc-dwc-g210.o
obj-$(CONFIG_SCSI_UFS_DWC_TC_PLATFORM) += tc-dwc-g210-pltfrm.o ufshcd-dwc.o tc-dwc-g210.o
obj-$(CONFIG_SCSI_UFS_QCOM) += ufs-qcom.o
obj-$(CONFIG_SCSI_UFSHCD) += ufshcd.o
obj-$(CONFIG_SCSI_UFS_QCOM_ICE) += ufs-qcom-ice.o
obj-$(CONFIG_SCSI_UFSHCD) += ufshcd.o ufs_quirks.o
obj-$(CONFIG_SCSI_UFSHCD_PCI) += ufshcd-pci.o
obj-$(CONFIG_SCSI_UFSHCD_PLATFORM) += ufshcd-pltfrm.o
obj-$(CONFIG_SCSI_UFS_TEST) += ufs_test.o
obj-$(CONFIG_DEBUG_FS) += ufs-debugfs.o ufs-qcom-debugfs.o

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,69 @@
/* Copyright (c) 2013-2016, 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.
*
* UFS debugfs - add debugfs interface to the ufshcd.
* This is currently used for statistics collection and exporting from the
* UFS driver.
* This infrastructure can be used for debugging or direct tweaking
* of the driver from userspace.
*
*/
#ifndef _UFS_DEBUGFS_H
#define _UFS_DEBUGFS_H
#include <linux/debugfs.h>
#include "ufshcd.h"
enum ufsdbg_err_inject_scenario {
ERR_INJECT_INTR,
ERR_INJECT_PWR_CHANGE,
ERR_INJECT_UIC,
ERR_INJECT_DME_ATTR,
ERR_INJECT_QUERY,
ERR_INJECT_MAX_ERR_SCENARIOS,
};
#ifdef CONFIG_DEBUG_FS
void ufsdbg_add_debugfs(struct ufs_hba *hba);
void ufsdbg_remove_debugfs(struct ufs_hba *hba);
void ufsdbg_pr_buf_to_std(struct ufs_hba *hba, int offset, int num_regs,
char *str, void *priv);
void ufsdbg_set_err_state(struct ufs_hba *hba);
#else
static inline void ufsdbg_add_debugfs(struct ufs_hba *hba)
{
}
static inline void ufsdbg_remove_debugfs(struct ufs_hba *hba)
{
}
static inline void ufsdbg_pr_buf_to_std(struct ufs_hba *hba, int offset,
int num_regs, char *str, void *priv)
{
}
void ufsdbg_set_err_state(struct ufs_hba *hba)
{
}
#endif
#ifdef CONFIG_UFS_FAULT_INJECTION
void ufsdbg_error_inject_dispatcher(struct ufs_hba *hba,
enum ufsdbg_err_inject_scenario err_scenario,
int success_value, int *ret_value);
#else
static inline void ufsdbg_error_inject_dispatcher(struct ufs_hba *hba,
enum ufsdbg_err_inject_scenario err_scenario,
int success_value, int *ret_value)
{
}
#endif
#endif /* End of Header */

View file

@ -0,0 +1,366 @@
/*
* Copyright (c) 2015, Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/debugfs.h>
#include "ufs-qcom.h"
#include "ufs-qcom-debugfs.h"
#include "ufs-debugfs.h"
#define TESTBUS_CFG_BUFF_LINE_SIZE sizeof("0xXY, 0xXY")
static void ufs_qcom_dbg_remove_debugfs(struct ufs_qcom_host *host);
static int ufs_qcom_dbg_print_en_read(void *data, u64 *attr_val)
{
struct ufs_qcom_host *host = data;
if (!host)
return -EINVAL;
*attr_val = (u64)host->dbg_print_en;
return 0;
}
static int ufs_qcom_dbg_print_en_set(void *data, u64 attr_id)
{
struct ufs_qcom_host *host = data;
if (!host)
return -EINVAL;
if (attr_id & ~UFS_QCOM_DBG_PRINT_ALL)
return -EINVAL;
host->dbg_print_en = (u32)attr_id;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(ufs_qcom_dbg_print_en_ops,
ufs_qcom_dbg_print_en_read,
ufs_qcom_dbg_print_en_set,
"%llu\n");
static int ufs_qcom_dbg_testbus_en_read(void *data, u64 *attr_val)
{
struct ufs_qcom_host *host = data;
bool enabled;
if (!host)
return -EINVAL;
enabled = !!(host->dbg_print_en & UFS_QCOM_DBG_PRINT_TEST_BUS_EN);
*attr_val = (u64)enabled;
return 0;
}
static int ufs_qcom_dbg_testbus_en_set(void *data, u64 attr_id)
{
struct ufs_qcom_host *host = data;
if (!host)
return -EINVAL;
if (!!attr_id)
host->dbg_print_en |= UFS_QCOM_DBG_PRINT_TEST_BUS_EN;
else
host->dbg_print_en &= ~UFS_QCOM_DBG_PRINT_TEST_BUS_EN;
return ufs_qcom_testbus_config(host);
}
DEFINE_SIMPLE_ATTRIBUTE(ufs_qcom_dbg_testbus_en_ops,
ufs_qcom_dbg_testbus_en_read,
ufs_qcom_dbg_testbus_en_set,
"%llu\n");
static int ufs_qcom_dbg_testbus_cfg_show(struct seq_file *file, void *data)
{
struct ufs_qcom_host *host = (struct ufs_qcom_host *)file->private;
seq_printf(file , "Current configuration: major=%d, minor=%d\n\n",
host->testbus.select_major, host->testbus.select_minor);
/* Print usage */
seq_puts(file,
"To change the test-bus configuration, write 'MAJ,MIN' where:\n"
"MAJ - major select\n"
"MIN - minor select\n\n");
return 0;
}
static ssize_t ufs_qcom_dbg_testbus_cfg_write(struct file *file,
const char __user *ubuf, size_t cnt,
loff_t *ppos)
{
struct ufs_qcom_host *host = file->f_mapping->host->i_private;
char configuration[TESTBUS_CFG_BUFF_LINE_SIZE] = {0};
loff_t buff_pos = 0;
char *comma;
int ret = 0;
int major;
int minor;
ret = simple_write_to_buffer(configuration, TESTBUS_CFG_BUFF_LINE_SIZE,
&buff_pos, ubuf, cnt);
if (ret < 0) {
dev_err(host->hba->dev, "%s: failed to read user data\n",
__func__);
goto out;
}
comma = strnchr(configuration, TESTBUS_CFG_BUFF_LINE_SIZE, ',');
if (!comma || comma == configuration) {
dev_err(host->hba->dev,
"%s: error in configuration of testbus\n", __func__);
ret = -EINVAL;
goto out;
}
if (sscanf(configuration, "%i,%i", &major, &minor) != 2) {
dev_err(host->hba->dev,
"%s: couldn't parse input to 2 numeric values\n",
__func__);
ret = -EINVAL;
goto out;
}
host->testbus.select_major = (u8)major;
host->testbus.select_minor = (u8)minor;
/*
* Sanity check of the {major, minor} tuple is done in the
* config function
*/
ret = ufs_qcom_testbus_config(host);
if (!ret)
dev_dbg(host->hba->dev,
"%s: New configuration: major=%d, minor=%d\n",
__func__, host->testbus.select_major,
host->testbus.select_minor);
out:
return ret ? ret : cnt;
}
static int ufs_qcom_dbg_testbus_cfg_open(struct inode *inode, struct file *file)
{
return single_open(file, ufs_qcom_dbg_testbus_cfg_show,
inode->i_private);
}
static const struct file_operations ufs_qcom_dbg_testbus_cfg_desc = {
.open = ufs_qcom_dbg_testbus_cfg_open,
.read = seq_read,
.write = ufs_qcom_dbg_testbus_cfg_write,
};
static int ufs_qcom_dbg_testbus_bus_read(void *data, u64 *attr_val)
{
struct ufs_qcom_host *host = data;
if (!host)
return -EINVAL;
pm_runtime_get_sync(host->hba->dev);
ufshcd_hold(host->hba, false);
*attr_val = (u64)ufshcd_readl(host->hba, UFS_TEST_BUS);
ufshcd_release(host->hba, false);
pm_runtime_put_sync(host->hba->dev);
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(ufs_qcom_dbg_testbus_bus_ops,
ufs_qcom_dbg_testbus_bus_read,
NULL,
"%llu\n");
static int ufs_qcom_dbg_dbg_regs_show(struct seq_file *file, void *data)
{
struct ufs_qcom_host *host = (struct ufs_qcom_host *)file->private;
bool dbg_print_reg = !!(host->dbg_print_en &
UFS_QCOM_DBG_PRINT_REGS_EN);
pm_runtime_get_sync(host->hba->dev);
ufshcd_hold(host->hba, false);
/* Temporarily override the debug print enable */
host->dbg_print_en |= UFS_QCOM_DBG_PRINT_REGS_EN;
ufs_qcom_print_hw_debug_reg_all(host->hba, file, ufsdbg_pr_buf_to_std);
/* Restore previous debug print enable value */
if (!dbg_print_reg)
host->dbg_print_en &= ~UFS_QCOM_DBG_PRINT_REGS_EN;
ufshcd_release(host->hba, false);
pm_runtime_put_sync(host->hba->dev);
return 0;
}
static int ufs_qcom_dbg_dbg_regs_open(struct inode *inode,
struct file *file)
{
return single_open(file, ufs_qcom_dbg_dbg_regs_show,
inode->i_private);
}
static const struct file_operations ufs_qcom_dbg_dbg_regs_desc = {
.open = ufs_qcom_dbg_dbg_regs_open,
.read = seq_read,
};
static int ufs_qcom_dbg_pm_qos_show(struct seq_file *file, void *data)
{
struct ufs_qcom_host *host = (struct ufs_qcom_host *)file->private;
unsigned long flags;
int i;
spin_lock_irqsave(host->hba->host->host_lock, flags);
seq_printf(file, "enabled: %d\n", host->pm_qos.is_enabled);
for (i = 0; i < host->pm_qos.num_groups && host->pm_qos.groups; i++)
seq_printf(file,
"CPU Group #%d(mask=0x%lx): active_reqs=%d, state=%d, latency=%d\n",
i, host->pm_qos.groups[i].mask.bits[0],
host->pm_qos.groups[i].active_reqs,
host->pm_qos.groups[i].state,
host->pm_qos.groups[i].latency_us);
spin_unlock_irqrestore(host->hba->host->host_lock, flags);
return 0;
}
static int ufs_qcom_dbg_pm_qos_open(struct inode *inode,
struct file *file)
{
return single_open(file, ufs_qcom_dbg_pm_qos_show, inode->i_private);
}
static const struct file_operations ufs_qcom_dbg_pm_qos_desc = {
.open = ufs_qcom_dbg_pm_qos_open,
.read = seq_read,
};
void ufs_qcom_dbg_add_debugfs(struct ufs_hba *hba, struct dentry *root)
{
struct ufs_qcom_host *host;
if (!hba || !hba->priv) {
pr_err("%s: NULL host, exiting\n", __func__);
return;
}
host = hba->priv;
host->debugfs_files.debugfs_root = debugfs_create_dir("qcom", root);
if (IS_ERR(host->debugfs_files.debugfs_root))
/* Don't complain -- debugfs just isn't enabled */
goto err_no_root;
if (!host->debugfs_files.debugfs_root) {
/*
* Complain -- debugfs is enabled, but it failed to
* create the directory
*/
dev_err(host->hba->dev,
"%s: NULL debugfs root directory, exiting", __func__);
goto err_no_root;
}
host->debugfs_files.dbg_print_en =
debugfs_create_file("dbg_print_en", S_IRUSR | S_IWUSR,
host->debugfs_files.debugfs_root, host,
&ufs_qcom_dbg_print_en_ops);
if (!host->debugfs_files.dbg_print_en) {
dev_err(host->hba->dev,
"%s: failed to create dbg_print_en debugfs entry\n",
__func__);
goto err;
}
host->debugfs_files.testbus = debugfs_create_dir("testbus",
host->debugfs_files.debugfs_root);
if (!host->debugfs_files.testbus) {
dev_err(host->hba->dev,
"%s: failed create testbus directory\n",
__func__);
goto err;
}
host->debugfs_files.testbus_en =
debugfs_create_file("enable", S_IRUSR | S_IWUSR,
host->debugfs_files.testbus, host,
&ufs_qcom_dbg_testbus_en_ops);
if (!host->debugfs_files.testbus_en) {
dev_err(host->hba->dev,
"%s: failed create testbus_en debugfs entry\n",
__func__);
goto err;
}
host->debugfs_files.testbus_cfg =
debugfs_create_file("configuration", S_IRUSR | S_IWUSR,
host->debugfs_files.testbus, host,
&ufs_qcom_dbg_testbus_cfg_desc);
if (!host->debugfs_files.testbus_cfg) {
dev_err(host->hba->dev,
"%s: failed create testbus_cfg debugfs entry\n",
__func__);
goto err;
}
host->debugfs_files.testbus_bus =
debugfs_create_file("TEST_BUS", S_IRUSR,
host->debugfs_files.testbus, host,
&ufs_qcom_dbg_testbus_bus_ops);
if (!host->debugfs_files.testbus_bus) {
dev_err(host->hba->dev,
"%s: failed create testbus_bus debugfs entry\n",
__func__);
goto err;
}
host->debugfs_files.dbg_regs =
debugfs_create_file("debug-regs", S_IRUSR,
host->debugfs_files.debugfs_root, host,
&ufs_qcom_dbg_dbg_regs_desc);
if (!host->debugfs_files.dbg_regs) {
dev_err(host->hba->dev,
"%s: failed create dbg_regs debugfs entry\n",
__func__);
goto err;
}
host->debugfs_files.pm_qos =
debugfs_create_file("pm_qos", S_IRUSR,
host->debugfs_files.debugfs_root, host,
&ufs_qcom_dbg_pm_qos_desc);
if (!host->debugfs_files.dbg_regs) {
dev_err(host->hba->dev,
"%s: failed create dbg_regs debugfs entry\n",
__func__);
goto err;
}
return;
err:
ufs_qcom_dbg_remove_debugfs(host);
err_no_root:
dev_err(host->hba->dev, "%s: failed to initialize debugfs\n", __func__);
}
static void ufs_qcom_dbg_remove_debugfs(struct ufs_qcom_host *host)
{
debugfs_remove_recursive(host->debugfs_files.debugfs_root);
host->debugfs_files.debugfs_root = NULL;
}

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2015, Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#ifndef QCOM_DEBUGFS_H_
#define QCOM_DEBUGFS_H_
#include "ufshcd.h"
#ifdef CONFIG_DEBUG_FS
void ufs_qcom_dbg_add_debugfs(struct ufs_hba *hba, struct dentry *root);
#endif
#endif /* End of Header */

View file

@ -0,0 +1,655 @@
/*
* Copyright (c) 2014-2016, 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 <linux/io.h>
#include <linux/of.h>
#include <linux/blkdev.h>
#include <crypto/ice.h>
#include "ufs-qcom-ice.h"
#include "ufs-qcom-debugfs.h"
#include "ufshcd.h"
#define UFS_QCOM_CRYPTO_LABEL "ufs-qcom-crypto"
/* Timeout waiting for ICE initialization, that requires TZ access */
#define UFS_QCOM_ICE_COMPLETION_TIMEOUT_MS 500
#define UFS_QCOM_ICE_DEFAULT_DBG_PRINT_EN 0
static void ufs_qcom_ice_dump_regs(struct ufs_qcom_host *qcom_host, int offset,
int len, char *prefix)
{
print_hex_dump(KERN_ERR, prefix,
len > 4 ? DUMP_PREFIX_OFFSET : DUMP_PREFIX_NONE,
16, 4, qcom_host->hba->mmio_base + offset, len * 4,
false);
}
void ufs_qcom_ice_print_regs(struct ufs_qcom_host *qcom_host)
{
int i;
if (!(qcom_host->dbg_print_en & UFS_QCOM_DBG_PRINT_ICE_REGS_EN))
return;
ufs_qcom_ice_dump_regs(qcom_host, REG_UFS_QCOM_ICE_CFG, 1,
"REG_UFS_QCOM_ICE_CFG ");
for (i = 0; i < NUM_QCOM_ICE_CTRL_INFO_n_REGS; i++) {
pr_err("REG_UFS_QCOM_ICE_CTRL_INFO_1_%d = 0x%08X\n", i,
ufshcd_readl(qcom_host->hba,
(REG_UFS_QCOM_ICE_CTRL_INFO_1_n + 8 * i)));
pr_err("REG_UFS_QCOM_ICE_CTRL_INFO_2_%d = 0x%08X\n", i,
ufshcd_readl(qcom_host->hba,
(REG_UFS_QCOM_ICE_CTRL_INFO_2_n + 8 * i)));
}
if (qcom_host->ice.pdev && qcom_host->ice.vops &&
qcom_host->ice.vops->debug)
qcom_host->ice.vops->debug(qcom_host->ice.pdev);
}
static void ufs_qcom_ice_error_cb(void *host_ctrl, u32 error)
{
struct ufs_qcom_host *qcom_host = (struct ufs_qcom_host *)host_ctrl;
dev_err(qcom_host->hba->dev, "%s: Error in ice operation 0x%x",
__func__, error);
if (qcom_host->ice.state == UFS_QCOM_ICE_STATE_ACTIVE)
qcom_host->ice.state = UFS_QCOM_ICE_STATE_DISABLED;
}
static struct platform_device *ufs_qcom_ice_get_pdevice(struct device *ufs_dev)
{
struct device_node *node;
struct platform_device *ice_pdev = NULL;
node = of_parse_phandle(ufs_dev->of_node, UFS_QCOM_CRYPTO_LABEL, 0);
if (!node) {
dev_err(ufs_dev, "%s: ufs-qcom-crypto property not specified\n",
__func__);
goto out;
}
ice_pdev = qcom_ice_get_pdevice(node);
out:
return ice_pdev;
}
static
struct qcom_ice_variant_ops *ufs_qcom_ice_get_vops(struct device *ufs_dev)
{
struct qcom_ice_variant_ops *ice_vops = NULL;
struct device_node *node;
node = of_parse_phandle(ufs_dev->of_node, UFS_QCOM_CRYPTO_LABEL, 0);
if (!node) {
dev_err(ufs_dev, "%s: ufs-qcom-crypto property not specified\n",
__func__);
goto out;
}
ice_vops = qcom_ice_get_variant_ops(node);
if (!ice_vops)
dev_err(ufs_dev, "%s: invalid ice_vops\n", __func__);
of_node_put(node);
out:
return ice_vops;
}
/**
* ufs_qcom_ice_get_dev() - sets pointers to ICE data structs in UFS QCom host
* @qcom_host: Pointer to a UFS QCom internal host structure.
*
* Sets ICE platform device pointer and ICE vops structure
* corresponding to the current UFS device.
*
* Return: -EINVAL in-case of invalid input parameters:
* qcom_host, qcom_host->hba or qcom_host->hba->dev
* -ENODEV in-case ICE device is not required
* -EPROBE_DEFER in-case ICE is required and hasn't been probed yet
* 0 otherwise
*/
int ufs_qcom_ice_get_dev(struct ufs_qcom_host *qcom_host)
{
struct device *ufs_dev;
int err = 0;
if (!qcom_host || !qcom_host->hba || !qcom_host->hba->dev) {
pr_err("%s: invalid qcom_host %p or qcom_host->hba or qcom_host->hba->dev\n",
__func__, qcom_host);
err = -EINVAL;
goto out;
}
ufs_dev = qcom_host->hba->dev;
qcom_host->ice.vops = ufs_qcom_ice_get_vops(ufs_dev);
qcom_host->ice.pdev = ufs_qcom_ice_get_pdevice(ufs_dev);
if (qcom_host->ice.pdev == ERR_PTR(-EPROBE_DEFER)) {
dev_err(ufs_dev, "%s: ICE device not probed yet\n",
__func__);
qcom_host->ice.pdev = NULL;
qcom_host->ice.vops = NULL;
err = -EPROBE_DEFER;
goto out;
}
if (!qcom_host->ice.pdev || !qcom_host->ice.vops) {
dev_err(ufs_dev, "%s: invalid platform device %p or vops %p\n",
__func__, qcom_host->ice.pdev, qcom_host->ice.vops);
qcom_host->ice.pdev = NULL;
qcom_host->ice.vops = NULL;
err = -ENODEV;
goto out;
}
qcom_host->ice.state = UFS_QCOM_ICE_STATE_DISABLED;
out:
return err;
}
static void ufs_qcom_ice_cfg_work(struct work_struct *work)
{
struct ice_data_setting ice_set;
struct ufs_qcom_host *qcom_host =
container_of(work, struct ufs_qcom_host, ice_cfg_work);
if (!qcom_host->ice.vops->config_start || !qcom_host->req_pending)
return;
/*
* config_start is called again as previous attempt returned -EAGAIN,
* this call shall now take care of the necessary key setup.
* 'ice_set' will not actually be used, instead the next call to
* config_start() for this request, in the normal call flow, will
* succeed as the key has now been setup.
*/
qcom_host->ice.vops->config_start(qcom_host->ice.pdev,
qcom_host->req_pending, &ice_set, false);
/*
* 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);
}
/**
* ufs_qcom_ice_init() - initializes the ICE-UFS interface and ICE device
* @qcom_host: Pointer to a UFS QCom internal host structure.
* qcom_host, qcom_host->hba and qcom_host->hba->dev should all
* be valid pointers.
*
* Return: -EINVAL in-case of an error
* 0 otherwise
*/
int ufs_qcom_ice_init(struct ufs_qcom_host *qcom_host)
{
struct device *ufs_dev = qcom_host->hba->dev;
int err;
err = qcom_host->ice.vops->init(qcom_host->ice.pdev,
qcom_host,
ufs_qcom_ice_error_cb);
if (err) {
dev_err(ufs_dev, "%s: ice init failed. err = %d\n",
__func__, err);
goto out;
} else {
qcom_host->ice.state = UFS_QCOM_ICE_STATE_ACTIVE;
}
qcom_host->dbg_print_en |= UFS_QCOM_ICE_DEFAULT_DBG_PRINT_EN;
INIT_WORK(&qcom_host->ice_cfg_work, ufs_qcom_ice_cfg_work);
out:
return err;
}
static inline bool ufs_qcom_is_data_cmd(char cmd_op, bool is_write)
{
if (is_write) {
if (cmd_op == WRITE_6 || cmd_op == WRITE_10 ||
cmd_op == WRITE_16)
return true;
} else {
if (cmd_op == READ_6 || cmd_op == READ_10 ||
cmd_op == READ_16)
return true;
}
return false;
}
int ufs_qcom_ice_req_setup(struct ufs_qcom_host *qcom_host,
struct scsi_cmnd *cmd, u8 *cc_index, bool *enable)
{
struct ice_data_setting ice_set;
char cmd_op = cmd->cmnd[0];
int err;
if (!qcom_host->ice.pdev || !qcom_host->ice.vops) {
dev_dbg(qcom_host->hba->dev, "%s: ice device is not enabled\n",
__func__);
return 0;
}
if (qcom_host->ice.vops->config_start) {
memset(&ice_set, 0, sizeof(ice_set));
err = qcom_host->ice.vops->config_start(qcom_host->ice.pdev,
cmd->request, &ice_set, true);
if (err) {
/*
* config_start() returns -EAGAIN when a key slot is
* available but still not configured. As configuration
* requires a non-atomic context, this means we should
* call the function again from the worker thread to do
* the configuration. For this request the error will
* propagate so it will be re-queued and until the
* configuration is is completed we block further
* request processing.
*/
if (err == -EAGAIN) {
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))
ufshcd_scsi_block_requests(
qcom_host->hba);
} else {
dev_err(qcom_host->hba->dev,
"%s: error in ice_vops->config %d\n",
__func__, err);
}
return err;
}
if (ufs_qcom_is_data_cmd(cmd_op, true))
*enable = !ice_set.encr_bypass;
else if (ufs_qcom_is_data_cmd(cmd_op, false))
*enable = !ice_set.decr_bypass;
if (ice_set.crypto_data.key_index >= 0)
*cc_index = (u8)ice_set.crypto_data.key_index;
}
return 0;
}
/**
* ufs_qcom_ice_cfg_start() - starts configuring UFS's ICE registers
* for an ICE transaction
* @qcom_host: Pointer to a UFS QCom internal host structure.
* qcom_host, qcom_host->hba and qcom_host->hba->dev should all
* be valid pointers.
* @cmd: Pointer to a valid scsi command. cmd->request should also be
* a valid pointer.
*
* Return: -EINVAL in-case of an error
* 0 otherwise
*/
int ufs_qcom_ice_cfg_start(struct ufs_qcom_host *qcom_host,
struct scsi_cmnd *cmd)
{
struct device *dev = qcom_host->hba->dev;
int err = 0;
struct ice_data_setting ice_set;
unsigned int slot = 0;
sector_t lba = 0;
unsigned int ctrl_info_val = 0;
unsigned int bypass = 0;
struct request *req;
char cmd_op;
if (!qcom_host->ice.pdev || !qcom_host->ice.vops) {
dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
goto out;
}
if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE) {
dev_err(dev, "%s: ice state (%d) is not active\n",
__func__, qcom_host->ice.state);
return -EINVAL;
}
if (qcom_host->hw_ver.major == 0x3) {
/* nothing to do here for version 0x3, exit silently */
return 0;
}
req = cmd->request;
if (req->bio)
lba = req->bio->bi_iter.bi_sector;
slot = req->tag;
if (slot < 0 || slot > qcom_host->hba->nutrs) {
dev_err(dev, "%s: slot (%d) is out of boundaries (0...%d)\n",
__func__, slot, qcom_host->hba->nutrs);
return -EINVAL;
}
memset(&ice_set, 0, sizeof(ice_set));
if (qcom_host->ice.vops->config_start) {
err = qcom_host->ice.vops->config_start(qcom_host->ice.pdev,
req, &ice_set, true);
if (err) {
/*
* config_start() returns -EAGAIN when a key slot is
* available but still not configured. As configuration
* requires a non-atomic context, this means we should
* call the function again from the worker thread to do
* the configuration. For this request the error will
* propagate so it will be re-queued and until the
* configuration is is completed we block further
* request processing.
*/
if (err == -EAGAIN) {
qcom_host->req_pending = req;
if (schedule_work(&qcom_host->ice_cfg_work))
ufshcd_scsi_block_requests(
qcom_host->hba);
}
goto out;
}
}
cmd_op = cmd->cmnd[0];
#define UFS_QCOM_DIR_WRITE true
#define UFS_QCOM_DIR_READ false
/* if non data command, bypass shall be enabled */
if (!ufs_qcom_is_data_cmd(cmd_op, UFS_QCOM_DIR_WRITE) &&
!ufs_qcom_is_data_cmd(cmd_op, UFS_QCOM_DIR_READ))
bypass = UFS_QCOM_ICE_ENABLE_BYPASS;
/* if writing data command */
else if (ufs_qcom_is_data_cmd(cmd_op, UFS_QCOM_DIR_WRITE))
bypass = ice_set.encr_bypass ? UFS_QCOM_ICE_ENABLE_BYPASS :
UFS_QCOM_ICE_DISABLE_BYPASS;
/* if reading data command */
else if (ufs_qcom_is_data_cmd(cmd_op, UFS_QCOM_DIR_READ))
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 &
MASK_UFS_QCOM_ICE_CTRL_INFO_KEY_INDEX)
<< OFFSET_UFS_QCOM_ICE_CTRL_INFO_KEY_INDEX;
/* 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)
<< OFFSET_UFS_QCOM_ICE_CTRL_INFO_CDU;
/* Configure ICE bypass mode */
ctrl_info_val |=
(bypass & MASK_UFS_QCOM_ICE_CTRL_INFO_BYPASS)
<< OFFSET_UFS_QCOM_ICE_CTRL_INFO_BYPASS;
if (qcom_host->hw_ver.major == 0x1) {
ufshcd_writel(qcom_host->hba, lba,
(REG_UFS_QCOM_ICE_CTRL_INFO_1_n + 8 * slot));
ufshcd_writel(qcom_host->hba, ctrl_info_val,
(REG_UFS_QCOM_ICE_CTRL_INFO_2_n + 8 * slot));
}
if (qcom_host->hw_ver.major == 0x2) {
ufshcd_writel(qcom_host->hba, (lba & 0xFFFFFFFF),
(REG_UFS_QCOM_ICE_CTRL_INFO_1_n + 16 * slot));
ufshcd_writel(qcom_host->hba, ((lba >> 32) & 0xFFFFFFFF),
(REG_UFS_QCOM_ICE_CTRL_INFO_2_n + 16 * slot));
ufshcd_writel(qcom_host->hba, ctrl_info_val,
(REG_UFS_QCOM_ICE_CTRL_INFO_3_n + 16 * slot));
}
/*
* Ensure UFS-ICE registers are being configured
* before next operation, otherwise UFS Host Controller might
* set get errors
*/
mb();
out:
return err;
}
/**
* ufs_qcom_ice_cfg_end() - finishes configuring UFS's ICE registers
* for an ICE transaction
* @qcom_host: Pointer to a UFS QCom internal host structure.
* qcom_host, qcom_host->hba and
* qcom_host->hba->dev should all
* be valid pointers.
* @cmd: Pointer to a valid scsi command. cmd->request should also be
* a valid pointer.
*
* Return: -EINVAL in-case of an error
* 0 otherwise
*/
int ufs_qcom_ice_cfg_end(struct ufs_qcom_host *qcom_host, struct request *req)
{
int err = 0;
struct device *dev = qcom_host->hba->dev;
if (qcom_host->ice.vops->config_end) {
err = qcom_host->ice.vops->config_end(req);
if (err) {
dev_err(dev, "%s: error in ice_vops->config_end %d\n",
__func__, err);
return err;
}
}
return 0;
}
/**
* ufs_qcom_ice_reset() - resets UFS-ICE interface and ICE device
* @qcom_host: Pointer to a UFS QCom internal host structure.
* qcom_host, qcom_host->hba and qcom_host->hba->dev should all
* be valid pointers.
*
* Return: -EINVAL in-case of an error
* 0 otherwise
*/
int ufs_qcom_ice_reset(struct ufs_qcom_host *qcom_host)
{
struct device *dev = qcom_host->hba->dev;
int err = 0;
if (!qcom_host->ice.pdev) {
dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
goto out;
}
if (!qcom_host->ice.vops) {
dev_err(dev, "%s: invalid ice_vops\n", __func__);
return -EINVAL;
}
if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE)
goto out;
if (qcom_host->ice.vops->reset) {
err = qcom_host->ice.vops->reset(qcom_host->ice.pdev);
if (err) {
dev_err(dev, "%s: ice_vops->reset failed. err %d\n",
__func__, err);
goto out;
}
}
if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE) {
dev_err(qcom_host->hba->dev,
"%s: error. ice.state (%d) is not in active state\n",
__func__, qcom_host->ice.state);
err = -EINVAL;
}
out:
return err;
}
/**
* ufs_qcom_ice_resume() - resumes UFS-ICE interface and ICE device from power
* collapse
* @qcom_host: Pointer to a UFS QCom internal host structure.
* qcom_host, qcom_host->hba and qcom_host->hba->dev should all
* be valid pointers.
*
* Return: -EINVAL in-case of an error
* 0 otherwise
*/
int ufs_qcom_ice_resume(struct ufs_qcom_host *qcom_host)
{
struct device *dev = qcom_host->hba->dev;
int err = 0;
if (!qcom_host->ice.pdev) {
dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
goto out;
}
if (qcom_host->ice.state !=
UFS_QCOM_ICE_STATE_SUSPENDED) {
goto out;
}
if (!qcom_host->ice.vops) {
dev_err(dev, "%s: invalid ice_vops\n", __func__);
return -EINVAL;
}
if (qcom_host->ice.vops->resume) {
err = qcom_host->ice.vops->resume(qcom_host->ice.pdev);
if (err) {
dev_err(dev, "%s: ice_vops->resume failed. err %d\n",
__func__, err);
return err;
}
}
qcom_host->ice.state = UFS_QCOM_ICE_STATE_ACTIVE;
out:
return err;
}
/**
* ufs_qcom_ice_suspend() - suspends UFS-ICE interface and ICE device
* @qcom_host: Pointer to a UFS QCom internal host structure.
* qcom_host, qcom_host->hba and qcom_host->hba->dev should all
* be valid pointers.
*
* Return: -EINVAL in-case of an error
* 0 otherwise
*/
int ufs_qcom_ice_suspend(struct ufs_qcom_host *qcom_host)
{
struct device *dev = qcom_host->hba->dev;
int err = 0;
if (!qcom_host->ice.pdev) {
dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
goto out;
}
if (qcom_host->ice.vops->suspend) {
err = qcom_host->ice.vops->suspend(qcom_host->ice.pdev);
if (err) {
dev_err(qcom_host->hba->dev,
"%s: ice_vops->suspend failed. err %d\n",
__func__, err);
return -EINVAL;
}
}
if (qcom_host->ice.state == UFS_QCOM_ICE_STATE_ACTIVE) {
qcom_host->ice.state = UFS_QCOM_ICE_STATE_SUSPENDED;
} else if (qcom_host->ice.state == UFS_QCOM_ICE_STATE_DISABLED) {
dev_err(qcom_host->hba->dev,
"%s: ice state is invalid: disabled\n",
__func__);
err = -EINVAL;
}
out:
return err;
}
/**
* ufs_qcom_ice_get_status() - returns the status of an ICE transaction
* @qcom_host: Pointer to a UFS QCom internal host structure.
* qcom_host, qcom_host->hba and qcom_host->hba->dev should all
* be valid pointers.
* @ice_status: Pointer to a valid output parameter.
* < 0 in case of ICE transaction failure.
* 0 otherwise.
*
* Return: -EINVAL in-case of an error
* 0 otherwise
*/
int ufs_qcom_ice_get_status(struct ufs_qcom_host *qcom_host, int *ice_status)
{
struct device *dev = NULL;
int err = 0;
int stat = -EINVAL;
*ice_status = 0;
dev = qcom_host->hba->dev;
if (!dev) {
err = -EINVAL;
goto out;
}
if (!qcom_host->ice.pdev) {
dev_dbg(dev, "%s: ice device is not enabled\n", __func__);
goto out;
}
if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE) {
err = -EINVAL;
goto out;
}
if (!qcom_host->ice.vops) {
dev_err(dev, "%s: invalid ice_vops\n", __func__);
return -EINVAL;
}
if (qcom_host->ice.vops->status) {
stat = qcom_host->ice.vops->status(qcom_host->ice.pdev);
if (stat < 0) {
dev_err(dev, "%s: ice_vops->status failed. stat %d\n",
__func__, stat);
err = -EINVAL;
goto out;
}
*ice_status = stat;
}
out:
return err;
}

Some files were not shown because too many files have changed in this diff Show more