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:
parent
7e4fbe38aa
commit
cce6fbcbcc
39 changed files with 11530 additions and 1035 deletions
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
265
drivers/phy/phy-qcom-ufs-qmp-v3.c
Normal file
265
drivers/phy/phy-qcom-ufs-qmp-v3.c
Normal 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");
|
||||
275
drivers/phy/phy-qcom-ufs-qmp-v3.h
Normal file
275
drivers/phy/phy-qcom-ufs-qmp-v3.h
Normal 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
|
||||
205
drivers/phy/phy-qcom-ufs-qrbtc-v2.c
Normal file
205
drivers/phy/phy-qcom-ufs-qrbtc-v2.c
Normal 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");
|
||||
116
drivers/phy/phy-qcom-ufs-qrbtc-v2.h
Normal file
116
drivers/phy/phy-qcom-ufs-qrbtc-v2.h
Normal 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
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
1661
drivers/scsi/ufs/ufs-debugfs.c
Normal file
1661
drivers/scsi/ufs/ufs-debugfs.c
Normal file
File diff suppressed because it is too large
Load diff
69
drivers/scsi/ufs/ufs-debugfs.h
Normal file
69
drivers/scsi/ufs/ufs-debugfs.h
Normal 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 */
|
||||
366
drivers/scsi/ufs/ufs-qcom-debugfs.c
Normal file
366
drivers/scsi/ufs/ufs-qcom-debugfs.c
Normal 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;
|
||||
}
|
||||
24
drivers/scsi/ufs/ufs-qcom-debugfs.h
Normal file
24
drivers/scsi/ufs/ufs-qcom-debugfs.h
Normal 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 */
|
||||
655
drivers/scsi/ufs/ufs-qcom-ice.c
Normal file
655
drivers/scsi/ufs/ufs-qcom-ice.c
Normal 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
Loading…
Reference in a new issue