ufs: Porting UFS driver's changes from kernel msm-4.14 to msm-kona

This is a snapshot of the UFS driver taken as of msm-4.14 commit
<3d758ed5e344> ("scsi: squash of multiple fixes for msm-4.4 kernel"). This
change contains the UFS driver's changes for merging from kernel version
msm-4.14 to msm-kona.

Change-Id: Iece7757565b2fe683956c419cf98a110c4de07f9
Signed-off-by: Can Guo <cang@codeaurora.org>
This commit is contained in:
Can Guo
2018-06-25 01:05:49 -07:00
parent 2c1e3aeea5
commit 2d8e79c2ee
57 changed files with 11914 additions and 2137 deletions

View File

@@ -0,0 +1,33 @@
* MSM Universal Flash Storage (UFS) PHY
UFSPHY nodes are defined to describe on-chip UFS PHY hardware macro.
Each UFS PHY node should have its own node.
To bind UFS PHY with UFS host controller, the controller node should
contain a phandle reference to UFS PHY node.
Required properties:
- compatible : compatible list, contains "qcom,ufsphy"
- reg : <registers mapping>
- vdda-phy-supply : phandle to main PHY supply for analog domain
- vdda-pll-supply : phandle to PHY PLL and Power-Gen block power supply
Optional properties:
- vdda-phy-max-microamp : specifies max. load that can be drawn from phy supply
- vdda-pll-max-microamp : specifies max. load that can be drawn from pll supply
Example:
ufsphy1: ufsphy@0xfc597000 {
compatible = "qcom,ufsphy";
reg = <0xfc597000 0x800>;
vdda-phy-supply = <&pma8084_l4>;
vdda-pll-supply = <&pma8084_l12>;
vdda-phy-max-microamp = <50000>;
vdda-pll-max-microamp = <1000>;
};
ufshc@0xfc598000 {
...
ufs-phy = <&ufsphy1>;
};

View File

@@ -8,8 +8,17 @@ contain a phandle reference to UFS PHY node.
Required properties:
- compatible : compatible list, contains one of the following -
"qcom,ufs-phy-qmp-20nm" for 20nm ufs phy,
"qcom,ufs-phy-qmp-14nm" for legacy 14nm ufs phy,
"qcom,ufs-phy-qmp-v3" for V3 ufs phy present
on msmcobalt platform,
"qcom,ufs-phy-qmp-v4" for V4 ufs phy present
on sm8150 platform,
"qcom,ufs-phy-qrbtc-sdm845" for phy support
for sdm845 emulation,
"qcom,ufs-phy-qmp-v3-falcon" for phy support
for msmfalcon,
"qcom,ufs-phy-qmp-v3-660" for V3 ufs phy present
on SDM660 platform,
"qcom,msm8996-ufs-phy-qmp-14nm" for 14nm ufs phy
present on MSM8996 chipset.
- reg : should contain PHY register address space (mandatory),
@@ -29,14 +38,19 @@ Optional properties:
- vdda-pll-max-microamp : specifies max. load that can be drawn from pll supply
- vddp-ref-clk-supply : phandle to UFS device ref_clk pad power supply
- vddp-ref-clk-max-microamp : specifies max. load that can be drawn from this supply
- vddp-ref-clk-min-uV : specifies min voltage that can be set for reference clock supply
- vddp-ref-clk-max-uV : specifies max voltage that can be set for reference clock supply
- 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:
ufsphy1: ufsphy@fc597000 {
compatible = "qcom,ufs-phy-qmp-20nm";
compatible = "qcom,ufs-phy-qmp-14nm";
reg = <0xfc597000 0x800>;
reg-names = "phy_mem";
#phy-cells = <0>;
lanes-per-direction = <1>;
vdda-phy-supply = <&pma8084_l4>;
vdda-pll-supply = <&pma8084_l12>;
vdda-phy-max-microamp = <50000>;

View File

@@ -11,6 +11,11 @@ Required properties:
"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).
- reset : reset specifier pair consists of phandle for the reset provider
and reset lines used by this controller.
- reset-names : reset signal name strings sorted in the same order as the resets property.
Optional properties:
- phys : phandle to UFS PHY node
@@ -18,6 +23,8 @@ Optional properties:
with "phys" attribute, provides phandle to UFS PHY node
- vdd-hba-supply : phandle to UFS host controller supply regulator node
- vcc-supply : phandle to VCC supply regulator node
- vcc-voltage-level : specifies voltage levels for VCC supply.
Should be specified in pairs (min, max), units uV.
- vccq-supply : phandle to VCCQ supply regulator node
- vccq2-supply : phandle to VCCQ2 supply regulator node
- vcc-supply-1p8 : For embedded UFS devices, valid VCC range is 1.7-1.95V
@@ -38,9 +45,35 @@ 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.
- rpm-level : UFS Runtime power management level. Following PM levels are supported:
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.
- pinctrl-names, pinctrl-0, pinctrl-1,.. pinctrl-n: Refer to "Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt"
for these optional properties
- limit-tx-hs-gear : Specify the max. limit on the TX HS gear.
Valid range: 1-3. 1 => HS-G1, 2 => HS-G2, 3 => HS-G3
- limit-rx-hs-gear : Specify the max. limit on the RX HS gear. Refer "limit-tx-hs-gear" for expected values.
- limit-tx-pwm-gear : Specify the max. limit on the TX PWM gear
Valid range: 1-4. 1 => PWM-G1, 2 => PWM-G2, 3 => PWM-G3, 4 => PWM-G4
- limit-rx-pwm-gear : Specify the max. limit on the RX PWM gear. Refer "limit-tx-pwm-gear" for expected values.
- scsi-cmd-timeout : Specify the command timeout (in seconds) for scsi commands
- dev-ref-clk-freq : Specify the device reference clock frequency, must be one of the following:
0: 19.2 MHz
1: 26 MHz
2: 38.4 MHz
3: 52 MHz
Defaults to 26 MHz if not specified.
- extcon: phandle to external connector (Refer Documentation/devicetree/bindings/extcon/extcon-gpio.txt for more details).
- non-removable : defines if the connected ufs device is not removable
- resets : reset node register
- reset-names : describe reset node register, the "rst" corresponds to reset the whole UFS IP.
@@ -50,9 +83,10 @@ regulators or clocks are always on.
Example:
ufshc@fc598000 {
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>;
@@ -70,4 +104,106 @@ Example:
reset-names = "rst";
phys = <&ufsphy1>;
phy-names = "ufsphy";
rpm-level = <3>;
spm-level = <5>;
dev-ref-clk-freq = <0>; /* reference clock freq: 19.2 MHz */
};
==== 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.
- qcom,vddp-ref-clk-supply : reference clock to ufs device. Controlled by the host driver.
- qcom,vddp-ref-clk-max-microamp : specifies max. load that can be drawn for
ref-clk supply.
Example:
ufshc@0xfc598000 {
...
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";
};

View File

@@ -76,6 +76,7 @@ obj-$(CONFIG_DMA_SHARED_BUFFER) += dma-buf/
obj-$(CONFIG_NUBUS) += nubus/
obj-y += macintosh/
obj-$(CONFIG_IDE) += ide/
obj-$(CONFIG_CRYPTO) += crypto/
obj-y += scsi/
obj-y += nvme/
obj-$(CONFIG_ATA) += ata/
@@ -134,7 +135,6 @@ obj-$(CONFIG_NEW_LEDS) += leds/
obj-$(CONFIG_INFINIBAND) += infiniband/
obj-$(CONFIG_SGI_SN) += sn/
obj-y += firmware/
obj-$(CONFIG_CRYPTO) += crypto/
obj-$(CONFIG_SUPERH) += sh/
ifndef CONFIG_ARCH_USES_GETTIMEOFFSET
obj-y += clocksource/

View File

@@ -28,6 +28,7 @@ static int devfreq_simple_ondemand_func(struct devfreq *df,
unsigned int dfso_downdifferential = DFSO_DOWNDIFFERENCTIAL;
struct devfreq_simple_ondemand_data *data = df->data;
unsigned long max = (df->max_freq) ? df->max_freq : UINT_MAX;
unsigned long min = (df->min_freq) ? df->min_freq : 0;
err = devfreq_update_stats(df);
if (err)
@@ -45,18 +46,30 @@ static int devfreq_simple_ondemand_func(struct devfreq *df,
dfso_upthreshold < dfso_downdifferential)
return -EINVAL;
/* Assume MAX if it is going to be divided by zero */
if (stat->total_time == 0) {
*freq = max;
return 0;
}
/* Prevent overflow */
if (stat->busy_time >= (1 << 24) || stat->total_time >= (1 << 24)) {
stat->busy_time >>= 7;
stat->total_time >>= 7;
}
if (data && data->simple_scaling) {
if (stat->busy_time * 100 >
stat->total_time * dfso_upthreshold)
*freq = max;
else if (stat->busy_time * 100 <
stat->total_time * dfso_downdifferential)
*freq = min;
else
*freq = df->previous_freq;
return 0;
}
/* Assume MAX if it is going to be divided by zero */
if (stat->total_time == 0) {
*freq = max;
return 0;
}
/* Set MAX if it's busy enough */
if (stat->busy_time * 100 >
stat->total_time * dfso_upthreshold) {

View File

@@ -5,7 +5,7 @@ obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o
obj-$(CONFIG_PHY_QCOM_QMP) += phy-qcom-qmp.o
obj-$(CONFIG_PHY_QCOM_QUSB2) += phy-qcom-qusb2.o
obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs.o
obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-14nm.o
obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-20nm.o
obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qrbtc-sdm845.o
obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-v4.o
obj-$(CONFIG_PHY_QCOM_USB_HS) += phy-qcom-usb-hs.o
obj-$(CONFIG_PHY_QCOM_USB_HSIC) += phy-qcom-usb-hsic.o

View File

@@ -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
@@ -77,6 +77,7 @@ struct ufs_qcom_phy_vreg {
int min_uV;
int max_uV;
bool enabled;
bool is_always_on;
};
struct ufs_qcom_phy {
@@ -90,11 +91,16 @@ 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;
struct ufs_qcom_phy_vreg vdda_phy;
struct ufs_qcom_phy_vreg vddp_ref_clk;
/* Number of lanes available (1 or 2) for Rx/Tx */
u32 lanes_per_direction;
unsigned int quirks;
/*
@@ -106,6 +112,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 +139,7 @@ struct ufs_qcom_phy {
bool is_powered_on;
bool is_started;
struct ufs_qcom_phy_specific_ops *phy_spec_ops;
u32 vco_tune1_mode1;
enum phy_mode mode;
};
@@ -128,14 +152,23 @@ 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.
* @dbg_register_dump: pointer to a function that dumps phy registers for debug.
*/
struct ufs_qcom_phy_specific_ops {
int (*calibrate_phy)(struct ufs_qcom_phy *phy, bool is_rate_B);
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);
void (*dbg_register_dump)(struct ufs_qcom_phy *phy);
};
struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy);
@@ -153,4 +186,9 @@ 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);
void ufs_qcom_phy_dump_regs(struct ufs_qcom_phy *phy,
int offset, int len, char *prefix);
#endif

View File

@@ -1,203 +0,0 @@
/*
* Copyright (c) 2013-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 "phy-qcom-ufs-qmp-14nm.h"
#define UFS_PHY_NAME "ufs_phy_qmp_14nm"
#define UFS_PHY_VDDA_PHY_UV (925000)
static
int ufs_qcom_phy_qmp_14nm_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
bool is_rate_B)
{
int tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A);
int tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
int err;
err = ufs_qcom_phy_calibrate(ufs_qcom_phy, phy_cal_table_rate_A,
tbl_size_A, phy_cal_table_rate_B, tbl_size_B, is_rate_B);
if (err)
dev_err(ufs_qcom_phy->dev,
"%s: ufs_qcom_phy_calibrate() failed %d\n",
__func__, err);
return err;
}
static
void ufs_qcom_phy_qmp_14nm_advertise_quirks(struct ufs_qcom_phy *phy_common)
{
phy_common->quirks =
UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
}
static int ufs_qcom_phy_qmp_14nm_init(struct phy *generic_phy)
{
struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
bool is_rate_B = false;
int ret;
if (phy_common->mode == PHY_MODE_UFS_HS_B)
is_rate_B = true;
ret = ufs_qcom_phy_qmp_14nm_phy_calibrate(phy_common, is_rate_B);
if (!ret)
/* phy calibrated, but yet to be started */
phy_common->is_started = false;
return ret;
}
static int ufs_qcom_phy_qmp_14nm_exit(struct phy *generic_phy)
{
return 0;
}
static
int ufs_qcom_phy_qmp_14nm_set_mode(struct phy *generic_phy, enum phy_mode mode)
{
struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
phy_common->mode = PHY_MODE_INVALID;
if (mode > 0)
phy_common->mode = mode;
return 0;
}
static
void ufs_qcom_phy_qmp_14nm_power_control(struct ufs_qcom_phy *phy, bool val)
{
writel_relaxed(val ? 0x1 : 0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
/*
* Before any transactions involving PHY, ensure PHY knows
* that it's analog rail is powered ON (or OFF).
*/
mb();
}
static inline
void ufs_qcom_phy_qmp_14nm_set_tx_lane_enable(struct ufs_qcom_phy *phy, u32 val)
{
/*
* 14nm PHY does not have TX_LANE_ENABLE register.
* Implement this function so as not to propagate error to caller.
*/
}
static inline void ufs_qcom_phy_qmp_14nm_start_serdes(struct ufs_qcom_phy *phy)
{
u32 tmp;
tmp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START);
tmp &= ~MASK_SERDES_START;
tmp |= (1 << OFFSET_SERDES_START);
writel_relaxed(tmp, phy->mmio + UFS_PHY_PHY_START);
/* Ensure register value is committed */
mb();
}
static int ufs_qcom_phy_qmp_14nm_is_pcs_ready(struct ufs_qcom_phy *phy_common)
{
int err = 0;
u32 val;
err = readl_poll_timeout(phy_common->mmio + UFS_PHY_PCS_READY_STATUS,
val, (val & MASK_PCS_READY), 10, 1000000);
if (err)
dev_err(phy_common->dev, "%s: poll for pcs failed err = %d\n",
__func__, err);
return err;
}
static const struct phy_ops ufs_qcom_phy_qmp_14nm_phy_ops = {
.init = ufs_qcom_phy_qmp_14nm_init,
.exit = ufs_qcom_phy_qmp_14nm_exit,
.power_on = ufs_qcom_phy_power_on,
.power_off = ufs_qcom_phy_power_off,
.set_mode = ufs_qcom_phy_qmp_14nm_set_mode,
.owner = THIS_MODULE,
};
static struct ufs_qcom_phy_specific_ops phy_14nm_ops = {
.start_serdes = ufs_qcom_phy_qmp_14nm_start_serdes,
.is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_14nm_is_pcs_ready,
.set_tx_lane_enable = ufs_qcom_phy_qmp_14nm_set_tx_lane_enable,
.power_control = ufs_qcom_phy_qmp_14nm_power_control,
};
static int ufs_qcom_phy_qmp_14nm_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct phy *generic_phy;
struct ufs_qcom_phy_qmp_14nm *phy;
struct ufs_qcom_phy *phy_common;
int err = 0;
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
if (!phy) {
err = -ENOMEM;
goto out;
}
phy_common = &phy->common_cfg;
generic_phy = ufs_qcom_phy_generic_probe(pdev, phy_common,
&ufs_qcom_phy_qmp_14nm_phy_ops, &phy_14nm_ops);
if (!generic_phy) {
err = -EIO;
goto out;
}
err = ufs_qcom_phy_init_clks(phy_common);
if (err)
goto out;
err = ufs_qcom_phy_init_vregulators(phy_common);
if (err)
goto out;
phy_common->vdda_phy.max_uV = UFS_PHY_VDDA_PHY_UV;
phy_common->vdda_phy.min_uV = UFS_PHY_VDDA_PHY_UV;
ufs_qcom_phy_qmp_14nm_advertise_quirks(phy_common);
phy_set_drvdata(generic_phy, phy);
strlcpy(phy_common->name, UFS_PHY_NAME, sizeof(phy_common->name));
out:
return err;
}
static const struct of_device_id ufs_qcom_phy_qmp_14nm_of_match[] = {
{.compatible = "qcom,ufs-phy-qmp-14nm"},
{.compatible = "qcom,msm8996-ufs-phy-qmp-14nm"},
{},
};
MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_14nm_of_match);
static struct platform_driver ufs_qcom_phy_qmp_14nm_driver = {
.probe = ufs_qcom_phy_qmp_14nm_probe,
.driver = {
.of_match_table = ufs_qcom_phy_qmp_14nm_of_match,
.name = "ufs_qcom_phy_qmp_14nm",
},
};
module_platform_driver(ufs_qcom_phy_qmp_14nm_driver);
MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP 14nm");
MODULE_LICENSE("GPL v2");

View File

@@ -1,177 +0,0 @@
/*
* Copyright (c) 2013-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 UFS_QCOM_PHY_QMP_14NM_H_
#define UFS_QCOM_PHY_QMP_14NM_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_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_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_CP_CTRL_MODE0 COM_OFF(0x78)
#define QSERDES_COM_CP_CTRL_MODE1 COM_OFF(0x7C)
#define QSERDES_COM_PLL_RCTRL_MODE0 COM_OFF(0x84)
#define QSERDES_COM_PLL_RCTRL_MODE1 COM_OFF(0x88)
#define QSERDES_COM_PLL_CCTRL_MODE0 COM_OFF(0x90)
#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_LOCK_CMP_EN COM_OFF(0xC8)
#define QSERDES_COM_LOCK_CMP_CFG COM_OFF(0xCC)
#define QSERDES_COM_DEC_START_MODE0 COM_OFF(0xD0)
#define QSERDES_COM_DEC_START_MODE1 COM_OFF(0xD4)
#define QSERDES_COM_DIV_FRAC_START1_MODE0 COM_OFF(0xDC)
#define QSERDES_COM_DIV_FRAC_START2_MODE0 COM_OFF(0xE0)
#define QSERDES_COM_DIV_FRAC_START3_MODE0 COM_OFF(0xE4)
#define QSERDES_COM_DIV_FRAC_START1_MODE1 COM_OFF(0xE8)
#define QSERDES_COM_DIV_FRAC_START2_MODE1 COM_OFF(0xEC)
#define QSERDES_COM_DIV_FRAC_START3_MODE1 COM_OFF(0xF0)
#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0 COM_OFF(0x108)
#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0 COM_OFF(0x10C)
#define QSERDES_COM_INTEGLOOP_GAIN0_MODE1 COM_OFF(0x110)
#define QSERDES_COM_INTEGLOOP_GAIN1_MODE1 COM_OFF(0x114)
#define QSERDES_COM_VCO_TUNE_CTRL COM_OFF(0x124)
#define QSERDES_COM_VCO_TUNE_MAP COM_OFF(0x128)
#define QSERDES_COM_VCO_TUNE1_MODE0 COM_OFF(0x12C)
#define QSERDES_COM_VCO_TUNE2_MODE0 COM_OFF(0x130)
#define QSERDES_COM_VCO_TUNE1_MODE1 COM_OFF(0x134)
#define QSERDES_COM_VCO_TUNE2_MODE1 COM_OFF(0x138)
#define QSERDES_COM_VCO_TUNE_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_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_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_PCS_READY_STATUS PHY_OFF(0x168)
/* UFS PHY TX registers */
#define QSERDES_TX_HIGHZ_TRANSCEIVER_BIAS_DRVR_EN TX_OFF(0, 0x68)
#define QSERDES_TX_LANE_MODE TX_OFF(0, 0x94)
/* UFS PHY RX registers */
#define QSERDES_RX_UCDR_FASTLOCK_FO_GAIN RX_OFF(0, 0x40)
#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)
#define QSERDES_RX_RX_EQ_GAIN2_LSB RX_OFF(0, 0xCC)
#define QSERDES_RX_RX_EQ_GAIN2_MSB RX_OFF(0, 0xD0)
#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2 RX_OFF(0, 0xD8)
#define QSERDES_RX_SIGDET_CNTRL RX_OFF(0, 0x114)
#define QSERDES_RX_SIGDET_LVL RX_OFF(0, 0x118)
#define QSERDES_RX_SIGDET_DEGLITCH_CNTRL RX_OFF(0, 0x11C)
#define QSERDES_RX_RX_INTERFACE_MODE RX_OFF(0, 0x12C)
/*
* This structure represents the 14nm 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_14nm {
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, 0x0e),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL, 0xd7),
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_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, 0x10),
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, 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, 0x02),
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_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),
};
static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = {
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x54),
};
#endif

View File

@@ -1,257 +0,0 @@
/*
* Copyright (c) 2013-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 "phy-qcom-ufs-qmp-20nm.h"
#define UFS_PHY_NAME "ufs_phy_qmp_20nm"
static
int ufs_qcom_phy_qmp_20nm_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
bool is_rate_B)
{
struct ufs_qcom_phy_calibration *tbl_A, *tbl_B;
int tbl_size_A, tbl_size_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;
int err;
if ((major == 0x1) && (minor == 0x002) && (step == 0x0000)) {
tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_2_0);
tbl_A = phy_cal_table_rate_A_1_2_0;
} else if ((major == 0x1) && (minor == 0x003) && (step == 0x0000)) {
tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_3_0);
tbl_A = phy_cal_table_rate_A_1_3_0;
} else {
dev_err(ufs_qcom_phy->dev, "%s: Unknown UFS-PHY version, no calibration values\n",
__func__);
err = -ENODEV;
goto out;
}
tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
tbl_B = phy_cal_table_rate_B;
err = ufs_qcom_phy_calibrate(ufs_qcom_phy, tbl_A, tbl_size_A,
tbl_B, tbl_size_B, is_rate_B);
if (err)
dev_err(ufs_qcom_phy->dev, "%s: ufs_qcom_phy_calibrate() failed %d\n",
__func__, err);
out:
return err;
}
static
void ufs_qcom_phy_qmp_20nm_advertise_quirks(struct ufs_qcom_phy *phy_common)
{
phy_common->quirks =
UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
}
static int ufs_qcom_phy_qmp_20nm_init(struct phy *generic_phy)
{
struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
bool is_rate_B = false;
int ret;
if (phy_common->mode == PHY_MODE_UFS_HS_B)
is_rate_B = true;
ret = ufs_qcom_phy_qmp_20nm_phy_calibrate(phy_common, is_rate_B);
if (!ret)
/* phy calibrated, but yet to be started */
phy_common->is_started = false;
return ret;
}
static int ufs_qcom_phy_qmp_20nm_exit(struct phy *generic_phy)
{
return 0;
}
static
int ufs_qcom_phy_qmp_20nm_set_mode(struct phy *generic_phy, enum phy_mode mode)
{
struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
phy_common->mode = PHY_MODE_INVALID;
if (mode > 0)
phy_common->mode = mode;
return 0;
}
static
void ufs_qcom_phy_qmp_20nm_power_control(struct ufs_qcom_phy *phy, bool val)
{
bool hibern8_exit_after_pwr_collapse = phy->quirks &
UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
if (val) {
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 (hibern8_exit_after_pwr_collapse) {
/*
* Give atleast 1us delay after restoring PHY analog
* power.
*/
usleep_range(1, 2);
writel_relaxed(0x0A, phy->mmio +
QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
writel_relaxed(0x08, phy->mmio +
QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
/*
* Make sure workaround is deactivated before proceeding
* with normal PHY operations.
*/
mb();
}
} else {
if (hibern8_exit_after_pwr_collapse) {
writel_relaxed(0x0A, phy->mmio +
QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
writel_relaxed(0x02, phy->mmio +
QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
/*
* Make sure that above workaround is activated before
* PHY analog power collapse.
*/
mb();
}
writel_relaxed(0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
/*
* ensure that PHY knows its PHY analog rail is going
* to be powered down
*/
mb();
}
}
static
void ufs_qcom_phy_qmp_20nm_set_tx_lane_enable(struct ufs_qcom_phy *phy, u32 val)
{
writel_relaxed(val & UFS_PHY_TX_LANE_ENABLE_MASK,
phy->mmio + UFS_PHY_TX_LANE_ENABLE);
mb();
}
static inline void ufs_qcom_phy_qmp_20nm_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);
mb();
}
static int ufs_qcom_phy_qmp_20nm_is_pcs_ready(struct ufs_qcom_phy *phy_common)
{
int err = 0;
u32 val;
err = readl_poll_timeout(phy_common->mmio + UFS_PHY_PCS_READY_STATUS,
val, (val & MASK_PCS_READY), 10, 1000000);
if (err)
dev_err(phy_common->dev, "%s: poll for pcs failed err = %d\n",
__func__, err);
return err;
}
static const struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = {
.init = ufs_qcom_phy_qmp_20nm_init,
.exit = ufs_qcom_phy_qmp_20nm_exit,
.power_on = ufs_qcom_phy_power_on,
.power_off = ufs_qcom_phy_power_off,
.set_mode = ufs_qcom_phy_qmp_20nm_set_mode,
.owner = THIS_MODULE,
};
static struct ufs_qcom_phy_specific_ops phy_20nm_ops = {
.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,
.power_control = ufs_qcom_phy_qmp_20nm_power_control,
};
static int ufs_qcom_phy_qmp_20nm_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct phy *generic_phy;
struct ufs_qcom_phy_qmp_20nm *phy;
struct ufs_qcom_phy *phy_common;
int err = 0;
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
if (!phy) {
err = -ENOMEM;
goto out;
}
phy_common = &phy->common_cfg;
generic_phy = ufs_qcom_phy_generic_probe(pdev, phy_common,
&ufs_qcom_phy_qmp_20nm_phy_ops, &phy_20nm_ops);
if (!generic_phy) {
err = -EIO;
goto out;
}
err = ufs_qcom_phy_init_clks(phy_common);
if (err)
goto out;
err = ufs_qcom_phy_init_vregulators(phy_common);
if (err)
goto out;
ufs_qcom_phy_qmp_20nm_advertise_quirks(phy_common);
phy_set_drvdata(generic_phy, phy);
strlcpy(phy_common->name, UFS_PHY_NAME, sizeof(phy_common->name));
out:
return err;
}
static const struct of_device_id ufs_qcom_phy_qmp_20nm_of_match[] = {
{.compatible = "qcom,ufs-phy-qmp-20nm"},
{},
};
MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_20nm_of_match);
static struct platform_driver ufs_qcom_phy_qmp_20nm_driver = {
.probe = ufs_qcom_phy_qmp_20nm_probe,
.driver = {
.of_match_table = ufs_qcom_phy_qmp_20nm_of_match,
.name = "ufs_qcom_phy_qmp_20nm",
},
};
module_platform_driver(ufs_qcom_phy_qmp_20nm_driver);
MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP 20nm");
MODULE_LICENSE("GPL v2");

View File

@@ -1,235 +0,0 @@
/*
* Copyright (c) 2013-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 UFS_QCOM_PHY_QMP_20NM_H_
#define UFS_QCOM_PHY_QMP_20NM_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 PLL block registers */
#define QSERDES_COM_SYS_CLK_CTRL COM_OFF(0x0)
#define QSERDES_COM_PLL_VCOTAIL_EN COM_OFF(0x04)
#define QSERDES_COM_PLL_CNTRL COM_OFF(0x14)
#define QSERDES_COM_PLL_IP_SETI COM_OFF(0x24)
#define QSERDES_COM_CORE_CLK_IN_SYNC_SEL COM_OFF(0x28)
#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN COM_OFF(0x30)
#define QSERDES_COM_PLL_CP_SETI COM_OFF(0x34)
#define QSERDES_COM_PLL_IP_SETP COM_OFF(0x38)
#define QSERDES_COM_PLL_CP_SETP COM_OFF(0x3C)
#define QSERDES_COM_SYSCLK_EN_SEL_TXBAND COM_OFF(0x48)
#define QSERDES_COM_RESETSM_CNTRL COM_OFF(0x4C)
#define QSERDES_COM_RESETSM_CNTRL2 COM_OFF(0x50)
#define QSERDES_COM_PLLLOCK_CMP1 COM_OFF(0x90)
#define QSERDES_COM_PLLLOCK_CMP2 COM_OFF(0x94)
#define QSERDES_COM_PLLLOCK_CMP3 COM_OFF(0x98)
#define QSERDES_COM_PLLLOCK_CMP_EN COM_OFF(0x9C)
#define QSERDES_COM_BGTC COM_OFF(0xA0)
#define QSERDES_COM_DEC_START1 COM_OFF(0xAC)
#define QSERDES_COM_PLL_AMP_OS COM_OFF(0xB0)
#define QSERDES_COM_RES_CODE_UP_OFFSET COM_OFF(0xD8)
#define QSERDES_COM_RES_CODE_DN_OFFSET COM_OFF(0xDC)
#define QSERDES_COM_DIV_FRAC_START1 COM_OFF(0x100)
#define QSERDES_COM_DIV_FRAC_START2 COM_OFF(0x104)
#define QSERDES_COM_DIV_FRAC_START3 COM_OFF(0x108)
#define QSERDES_COM_DEC_START2 COM_OFF(0x10C)
#define QSERDES_COM_PLL_RXTXEPCLK_EN COM_OFF(0x110)
#define QSERDES_COM_PLL_CRCTRL COM_OFF(0x114)
#define QSERDES_COM_PLL_CLKEPDIV COM_OFF(0x118)
/* TX LANE n (0, 1) registers */
#define QSERDES_TX_EMP_POST1_LVL(n) TX_OFF(n, 0x08)
#define QSERDES_TX_DRV_LVL(n) TX_OFF(n, 0x0C)
#define QSERDES_TX_LANE_MODE(n) TX_OFF(n, 0x54)
/* RX LANE n (0, 1) registers */
#define QSERDES_RX_CDR_CONTROL1(n) RX_OFF(n, 0x0)
#define QSERDES_RX_CDR_CONTROL_HALF(n) RX_OFF(n, 0x8)
#define QSERDES_RX_RX_EQ_GAIN1_LSB(n) RX_OFF(n, 0xA8)
#define QSERDES_RX_RX_EQ_GAIN1_MSB(n) RX_OFF(n, 0xAC)
#define QSERDES_RX_RX_EQ_GAIN2_LSB(n) RX_OFF(n, 0xB0)
#define QSERDES_RX_RX_EQ_GAIN2_MSB(n) RX_OFF(n, 0xB4)
#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(n) RX_OFF(n, 0xBC)
#define QSERDES_RX_CDR_CONTROL_QUARTER(n) RX_OFF(n, 0xC)
#define QSERDES_RX_SIGDET_CNTRL(n) RX_OFF(n, 0x100)
/* UFS PHY registers */
#define UFS_PHY_PHY_START PHY_OFF(0x00)
#define UFS_PHY_POWER_DOWN_CONTROL PHY_OFF(0x4)
#define UFS_PHY_TX_LANE_ENABLE PHY_OFF(0x44)
#define UFS_PHY_PWM_G1_CLK_DIVIDER PHY_OFF(0x08)
#define UFS_PHY_PWM_G2_CLK_DIVIDER PHY_OFF(0x0C)
#define UFS_PHY_PWM_G3_CLK_DIVIDER PHY_OFF(0x10)
#define UFS_PHY_PWM_G4_CLK_DIVIDER PHY_OFF(0x14)
#define UFS_PHY_CORECLK_PWM_G1_CLK_DIVIDER PHY_OFF(0x34)
#define UFS_PHY_CORECLK_PWM_G2_CLK_DIVIDER PHY_OFF(0x38)
#define UFS_PHY_CORECLK_PWM_G3_CLK_DIVIDER PHY_OFF(0x3C)
#define UFS_PHY_CORECLK_PWM_G4_CLK_DIVIDER PHY_OFF(0x40)
#define UFS_PHY_OMC_STATUS_RDVAL PHY_OFF(0x68)
#define UFS_PHY_LINE_RESET_TIME PHY_OFF(0x28)
#define UFS_PHY_LINE_RESET_GRANULARITY PHY_OFF(0x2C)
#define UFS_PHY_TSYNC_RSYNC_CNTL PHY_OFF(0x48)
#define UFS_PHY_PLL_CNTL PHY_OFF(0x50)
#define UFS_PHY_TX_LARGE_AMP_DRV_LVL PHY_OFF(0x54)
#define UFS_PHY_TX_SMALL_AMP_DRV_LVL PHY_OFF(0x5C)
#define UFS_PHY_TX_LARGE_AMP_POST_EMP_LVL PHY_OFF(0x58)
#define UFS_PHY_TX_SMALL_AMP_POST_EMP_LVL PHY_OFF(0x60)
#define UFS_PHY_CFG_CHANGE_CNT_VAL PHY_OFF(0x64)
#define UFS_PHY_RX_SYNC_WAIT_TIME PHY_OFF(0x6C)
#define UFS_PHY_TX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY PHY_OFF(0xB4)
#define UFS_PHY_RX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY PHY_OFF(0xE0)
#define UFS_PHY_TX_MIN_STALL_NOCONFIG_TIME_CAPABILITY PHY_OFF(0xB8)
#define UFS_PHY_RX_MIN_STALL_NOCONFIG_TIME_CAPABILITY PHY_OFF(0xE4)
#define UFS_PHY_TX_MIN_SAVE_CONFIG_TIME_CAPABILITY PHY_OFF(0xBC)
#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_RX_SIGDET_CTRL3 PHY_OFF(0x14c)
#define UFS_PHY_RMMI_ATTR_CTRL PHY_OFF(0x160)
#define UFS_PHY_RMMI_RX_CFGUPDT_L1 (1 << 7)
#define UFS_PHY_RMMI_TX_CFGUPDT_L1 (1 << 6)
#define UFS_PHY_RMMI_CFGWR_L1 (1 << 5)
#define UFS_PHY_RMMI_CFGRD_L1 (1 << 4)
#define UFS_PHY_RMMI_RX_CFGUPDT_L0 (1 << 3)
#define UFS_PHY_RMMI_TX_CFGUPDT_L0 (1 << 2)
#define UFS_PHY_RMMI_CFGWR_L0 (1 << 1)
#define UFS_PHY_RMMI_CFGRD_L0 (1 << 0)
#define UFS_PHY_RMMI_ATTRID PHY_OFF(0x164)
#define UFS_PHY_RMMI_ATTRWRVAL PHY_OFF(0x168)
#define UFS_PHY_RMMI_ATTRRDVAL_L0_STATUS PHY_OFF(0x16C)
#define UFS_PHY_RMMI_ATTRRDVAL_L1_STATUS PHY_OFF(0x170)
#define UFS_PHY_PCS_READY_STATUS PHY_OFF(0x174)
#define UFS_PHY_TX_LANE_ENABLE_MASK 0x3
/*
* This structure represents the 20nm 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_20nm {
struct ufs_qcom_phy common_cfg;
};
static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_1_2_0[] = {
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL3, 0x0D),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_VCOTAIL_EN, 0xe1),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CRCTRL, 0xcc),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL_TXBAND, 0x08),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CLKEPDIV, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RXTXEPCLK_EN, 0x10),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x82),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x80),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0x80),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x40),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x19),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x90),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL2, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(0), 0xf2),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(0), 0x0c),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(0), 0x12),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(1), 0xf2),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(1), 0x0c),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(1), 0x12),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(0), 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(0), 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(0), 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(0), 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(1), 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(1), 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(1), 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(1), 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x3f),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x1b),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x0f),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x01),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_EMP_POST1_LVL(0), 0x2F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_DRV_LVL(0), 0x20),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_EMP_POST1_LVL(1), 0x2F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_DRV_LVL(1), 0x20),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(0), 0x68),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(1), 0x68),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(1), 0xdc),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(0), 0xdc),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3),
};
static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_1_3_0[] = {
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL3, 0x0D),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_VCOTAIL_EN, 0xe1),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CRCTRL, 0xcc),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL_TXBAND, 0x08),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CLKEPDIV, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RXTXEPCLK_EN, 0x10),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x82),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x80),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0x80),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x40),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x19),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x90),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL2, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(0), 0xf2),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(0), 0x0c),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(0), 0x12),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(1), 0xf2),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(1), 0x0c),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(1), 0x12),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(0), 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(0), 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(0), 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(0), 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(1), 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(1), 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(1), 0xff),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(1), 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x2b),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x38),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x3c),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RES_CODE_UP_OFFSET, 0x02),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RES_CODE_DN_OFFSET, 0x02),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x01),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CNTRL, 0x40),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(0), 0x68),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(1), 0x68),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(1), 0xdc),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(0), 0xdc),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3),
};
static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = {
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x98),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0x65),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x1e),
};
#endif

View File

@@ -0,0 +1,239 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2018, 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-v4.h"
#define UFS_PHY_NAME "ufs_phy_qmp_v4"
static
int ufs_qcom_phy_qmp_v4_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
bool is_rate_B)
{
writel_relaxed(0x01, ufs_qcom_phy->mmio + UFS_PHY_SW_RESET);
/* Ensure PHY is in reset before writing PHY calibration data */
wmb();
/*
* Writing PHY calibration in this order:
* 1. Write Rate-A calibration first (1-lane mode).
* 2. Write 2nd lane configuration if needed.
* 3. Write Rate-B calibration overrides
*/
ufs_qcom_phy_write_tbl(ufs_qcom_phy, phy_cal_table_rate_A,
ARRAY_SIZE(phy_cal_table_rate_A));
if (ufs_qcom_phy->lanes_per_direction == 2)
ufs_qcom_phy_write_tbl(ufs_qcom_phy, phy_cal_table_2nd_lane,
ARRAY_SIZE(phy_cal_table_2nd_lane));
if (is_rate_B)
ufs_qcom_phy_write_tbl(ufs_qcom_phy, phy_cal_table_rate_B,
ARRAY_SIZE(phy_cal_table_rate_B));
writel_relaxed(0x00, ufs_qcom_phy->mmio + UFS_PHY_SW_RESET);
/* flush buffered writes */
wmb();
return 0;
}
static int ufs_qcom_phy_qmp_v4_init(struct phy *generic_phy)
{
struct ufs_qcom_phy_qmp_v4 *phy = phy_get_drvdata(generic_phy);
struct ufs_qcom_phy *phy_common = &phy->common_cfg;
int err;
err = ufs_qcom_phy_init_clks(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(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 int ufs_qcom_phy_qmp_v4_exit(struct phy *generic_phy)
{
return 0;
}
static
void ufs_qcom_phy_qmp_v4_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_v4_set_tx_lane_enable(struct ufs_qcom_phy *phy, u32 val)
{
/*
* v4 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_v4_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_v4_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_v4_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 void ufs_qcom_phy_qmp_v4_dbg_register_dump(struct ufs_qcom_phy *phy)
{
ufs_qcom_phy_dump_regs(phy, COM_BASE, COM_SIZE,
"PHY QSERDES COM Registers ");
ufs_qcom_phy_dump_regs(phy, PHY_BASE, PHY_SIZE,
"PHY Registers ");
ufs_qcom_phy_dump_regs(phy, RX_BASE(0), RX_SIZE,
"PHY RX0 Registers ");
ufs_qcom_phy_dump_regs(phy, TX_BASE(0), TX_SIZE,
"PHY TX0 Registers ");
ufs_qcom_phy_dump_regs(phy, RX_BASE(1), RX_SIZE,
"PHY RX1 Registers ");
ufs_qcom_phy_dump_regs(phy, TX_BASE(1), TX_SIZE,
"PHY TX1 Registers ");
}
struct phy_ops ufs_qcom_phy_qmp_v4_phy_ops = {
.init = ufs_qcom_phy_qmp_v4_init,
.exit = ufs_qcom_phy_qmp_v4_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_v4_ops = {
.calibrate_phy = ufs_qcom_phy_qmp_v4_phy_calibrate,
.start_serdes = ufs_qcom_phy_qmp_v4_start_serdes,
.is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_v4_is_pcs_ready,
.set_tx_lane_enable = ufs_qcom_phy_qmp_v4_set_tx_lane_enable,
.ctrl_rx_linecfg = ufs_qcom_phy_qmp_v4_ctrl_rx_linecfg,
.power_control = ufs_qcom_phy_qmp_v4_power_control,
.dbg_register_dump = ufs_qcom_phy_qmp_v4_dbg_register_dump,
};
static int ufs_qcom_phy_qmp_v4_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct phy *generic_phy;
struct ufs_qcom_phy_qmp_v4 *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_v4_phy_ops, &phy_v4_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 const struct of_device_id ufs_qcom_phy_qmp_v4_of_match[] = {
{.compatible = "qcom,ufs-phy-qmp-v4"},
{},
};
MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_v4_of_match);
static struct platform_driver ufs_qcom_phy_qmp_v4_driver = {
.probe = ufs_qcom_phy_qmp_v4_probe,
.driver = {
.of_match_table = ufs_qcom_phy_qmp_v4_of_match,
.name = "ufs_qcom_phy_qmp_v4",
},
};
module_platform_driver(ufs_qcom_phy_qmp_v4_driver);
MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP v4");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,315 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2018, 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_V4_H_
#define UFS_QCOM_PHY_QMP_V4_H_
#include "phy-qcom-ufs-i.h"
/* QCOM UFS PHY control registers */
#define COM_BASE 0x000
#define COM_SIZE 0x1C0
#define PHY_BASE 0xC00
#define PHY_SIZE 0x200
#define TX_BASE(n) (0x400 + (0x400 * n))
#define TX_SIZE 0x16C
#define RX_BASE(n) (0x600 + (0x400 * n))
#define RX_SIZE 0x200
#define COM_OFF(x) (COM_BASE + x)
#define PHY_OFF(x) (PHY_BASE + x)
#define TX_OFF(n, x) (TX_BASE(n) + x)
#define RX_OFF(n, x) (RX_BASE(n) + x)
/* UFS PHY QSERDES COM registers */
#define QSERDES_COM_SYSCLK_EN_SEL COM_OFF(0x94)
#define QSERDES_COM_HSCLK_SEL COM_OFF(0x158)
#define QSERDES_COM_HSCLK_HS_SWITCH_SEL COM_OFF(0x15C)
#define QSERDES_COM_LOCK_CMP_EN COM_OFF(0xA4)
#define QSERDES_COM_VCO_TUNE_MAP COM_OFF(0x10C)
#define QSERDES_COM_PLL_IVCO COM_OFF(0x58)
#define QSERDES_COM_VCO_TUNE_INITVAL2 COM_OFF(0x124)
#define QSERDES_COM_BIN_VCOCAL_HSCLK_SEL COM_OFF(0x1BC)
#define QSERDES_COM_DEC_START_MODE0 COM_OFF(0xBC)
#define QSERDES_COM_CP_CTRL_MODE0 COM_OFF(0x74)
#define QSERDES_COM_PLL_RCTRL_MODE0 COM_OFF(0x7C)
#define QSERDES_COM_PLL_CCTRL_MODE0 COM_OFF(0x84)
#define QSERDES_COM_LOCK_CMP1_MODE0 COM_OFF(0xAC)
#define QSERDES_COM_LOCK_CMP2_MODE0 COM_OFF(0xB0)
#define QSERDES_COM_BIN_VCOCAL_CMP_CODE1_MODE0 COM_OFF(0x1AC)
#define QSERDES_COM_BIN_VCOCAL_CMP_CODE2_MODE0 COM_OFF(0x1B0)
#define QSERDES_COM_DEC_START_MODE1 COM_OFF(0xC4)
#define QSERDES_COM_CP_CTRL_MODE1 COM_OFF(0x78)
#define QSERDES_COM_PLL_RCTRL_MODE1 COM_OFF(0x80)
#define QSERDES_COM_PLL_CCTRL_MODE1 COM_OFF(0x88)
#define QSERDES_COM_LOCK_CMP1_MODE1 COM_OFF(0xB4)
#define QSERDES_COM_LOCK_CMP2_MODE1 COM_OFF(0xB8)
#define QSERDES_COM_BIN_VCOCAL_CMP_CODE1_MODE1 COM_OFF(0x1B4)
#define QSERDES_COM_BIN_VCOCAL_CMP_CODE2_MODE1 COM_OFF(0x1B8)
#define QSERDES_COM_CMN_IPTRIM COM_OFF(0x60)
/* UFS PHY registers */
#define UFS_PHY_PHY_START PHY_OFF(0x00)
#define UFS_PHY_POWER_DOWN_CONTROL PHY_OFF(0x04)
#define UFS_PHY_SW_RESET PHY_OFF(0x08)
#define UFS_PHY_PCS_READY_STATUS PHY_OFF(0x180)
#define UFS_PHY_LINECFG_DISABLE PHY_OFF(0x148)
#define UFS_PHY_MULTI_LANE_CTRL1 PHY_OFF(0x1E0)
#define UFS_PHY_RX_SIGDET_CTRL2 PHY_OFF(0x158)
#define UFS_PHY_TX_LARGE_AMP_DRV_LVL PHY_OFF(0x30)
#define UFS_PHY_TX_SMALL_AMP_DRV_LVL PHY_OFF(0x38)
#define UFS_PHY_TX_MID_TERM_CTRL1 PHY_OFF(0x1D8)
#define UFS_PHY_DEBUG_BUS_CLKSEL PHY_OFF(0x124)
#define UFS_PHY_PLL_CNTL PHY_OFF(0x2C)
#define UFS_PHY_TIMER_20US_CORECLK_STEPS_MSB PHY_OFF(0x0C)
#define UFS_PHY_TIMER_20US_CORECLK_STEPS_LSB PHY_OFF(0x10)
#define UFS_PHY_TX_PWM_GEAR_BAND PHY_OFF(0x160)
#define UFS_PHY_TX_HS_GEAR_BAND PHY_OFF(0x168)
#define UFS_PHY_TX_HSGEAR_CAPABILITY PHY_OFF(0x74)
#define UFS_PHY_RX_HSGEAR_CAPABILITY PHY_OFF(0xB4)
#define UFS_PHY_RX_MIN_HIBERN8_TIME PHY_OFF(0x150)
/* UFS PHY TX registers */
#define QSERDES_TX0_PWM_GEAR_1_DIVIDER_BAND0_1 TX_OFF(0, 0xD8)
#define QSERDES_TX0_PWM_GEAR_2_DIVIDER_BAND0_1 TX_OFF(0, 0xDC)
#define QSERDES_TX0_PWM_GEAR_3_DIVIDER_BAND0_1 TX_OFF(0, 0xE0)
#define QSERDES_TX0_PWM_GEAR_4_DIVIDER_BAND0_1 TX_OFF(0, 0xE4)
#define QSERDES_TX0_LANE_MODE_1 TX_OFF(0, 0x84)
#define QSERDES_TX0_TRAN_DRVR_EMP_EN TX_OFF(0, 0xB8)
#define QSERDES_TX1_PWM_GEAR_1_DIVIDER_BAND0_1 TX_OFF(1, 0xD8)
#define QSERDES_TX1_PWM_GEAR_2_DIVIDER_BAND0_1 TX_OFF(1, 0xDC)
#define QSERDES_TX1_PWM_GEAR_3_DIVIDER_BAND0_1 TX_OFF(1, 0xE0)
#define QSERDES_TX1_PWM_GEAR_4_DIVIDER_BAND0_1 TX_OFF(1, 0xE4)
#define QSERDES_TX1_LANE_MODE_1 TX_OFF(1, 0x84)
#define QSERDES_TX1_TRAN_DRVR_EMP_EN TX_OFF(1, 0xB8)
/* UFS PHY RX registers */
#define QSERDES_RX0_SIGDET_LVL RX_OFF(0, 0x120)
#define QSERDES_RX0_SIGDET_CNTRL RX_OFF(0, 0x11C)
#define QSERDES_RX0_SIGDET_DEGLITCH_CNTRL RX_OFF(0, 0x124)
#define QSERDES_RX0_RX_BAND RX_OFF(0, 0x128)
#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_PI_CONTROLS RX_OFF(0, 0x44)
#define QSERDES_RX0_UCDR_FASTLOCK_COUNT_LOW RX_OFF(0, 0x3C)
#define QSERDES_RX0_UCDR_PI_CTRL2 RX_OFF(0, 0x48)
#define QSERDES_RX0_RX_TERM_BW RX_OFF(0, 0x80)
#define QSERDES_RX0_RX_EQU_ADAPTOR_CNTRL2 RX_OFF(0, 0xEC)
#define QSERDES_RX0_RX_EQU_ADAPTOR_CNTRL3 RX_OFF(0, 0xF0)
#define QSERDES_RX0_RX_EQU_ADAPTOR_CNTRL4 RX_OFF(0, 0xF4)
#define QSERDES_RX0_RX_OFFSET_ADAPTOR_CNTRL2 RX_OFF(0, 0x114)
#define QSERDES_RX0_RX_IDAC_MEASURE_TIME RX_OFF(0, 0x100)
#define QSERDES_RX0_RX_IDAC_TSETTLE_LOW RX_OFF(0, 0xF8)
#define QSERDES_RX0_RX_IDAC_TSETTLE_HIGH RX_OFF(0, 0xFC)
#define QSERDES_RX0_RX_MODE_00_LOW RX_OFF(0, 0x170)
#define QSERDES_RX0_RX_MODE_00_HIGH RX_OFF(0, 0x174)
#define QSERDES_RX0_RX_MODE_00_HIGH2 RX_OFF(0, 0x178)
#define QSERDES_RX0_RX_MODE_00_HIGH3 RX_OFF(0, 0x17C)
#define QSERDES_RX0_RX_MODE_00_HIGH4 RX_OFF(0, 0x180)
#define QSERDES_RX0_RX_MODE_01_LOW RX_OFF(0, 0x184)
#define QSERDES_RX0_RX_MODE_01_HIGH RX_OFF(0, 0x188)
#define QSERDES_RX0_RX_MODE_01_HIGH2 RX_OFF(0, 0x18C)
#define QSERDES_RX0_RX_MODE_01_HIGH3 RX_OFF(0, 0x190)
#define QSERDES_RX0_RX_MODE_01_HIGH4 RX_OFF(0, 0x194)
#define QSERDES_RX0_RX_MODE_10_LOW RX_OFF(0, 0x198)
#define QSERDES_RX0_RX_MODE_10_HIGH RX_OFF(0, 0x19C)
#define QSERDES_RX0_RX_MODE_10_HIGH2 RX_OFF(0, 0x1A0)
#define QSERDES_RX0_RX_MODE_10_HIGH3 RX_OFF(0, 0x1A4)
#define QSERDES_RX0_RX_MODE_10_HIGH4 RX_OFF(0, 0x1A8)
#define QSERDES_RX0_AC_JTAG_ENABLE RX_OFF(0, 0x68)
#define QSERDES_RX0_UCDR_FO_GAIN RX_OFF(0, 0x08)
#define QSERDES_RX0_UCDR_SO_GAIN RX_OFF(0, 0x14)
#define QSERDES_RX1_SIGDET_LVL RX_OFF(1, 0x120)
#define QSERDES_RX1_SIGDET_CNTRL RX_OFF(1, 0x11C)
#define QSERDES_RX1_SIGDET_DEGLITCH_CNTRL RX_OFF(1, 0x124)
#define QSERDES_RX1_RX_BAND RX_OFF(1, 0x128)
#define QSERDES_RX1_UCDR_FASTLOCK_FO_GAIN RX_OFF(1, 0x30)
#define QSERDES_RX1_UCDR_SO_SATURATION_AND_ENABLE RX_OFF(1, 0x34)
#define QSERDES_RX1_UCDR_PI_CONTROLS RX_OFF(1, 0x44)
#define QSERDES_RX1_UCDR_FASTLOCK_COUNT_LOW RX_OFF(1, 0x3C)
#define QSERDES_RX1_UCDR_PI_CTRL2 RX_OFF(1, 0x48)
#define QSERDES_RX1_RX_TERM_BW RX_OFF(1, 0x80)
#define QSERDES_RX1_RX_EQU_ADAPTOR_CNTRL2 RX_OFF(1, 0xEC)
#define QSERDES_RX1_RX_EQU_ADAPTOR_CNTRL3 RX_OFF(1, 0xF0)
#define QSERDES_RX1_RX_EQU_ADAPTOR_CNTRL4 RX_OFF(1, 0xF4)
#define QSERDES_RX1_RX_OFFSET_ADAPTOR_CNTRL2 RX_OFF(1, 0x114)
#define QSERDES_RX1_RX_IDAC_MEASURE_TIME RX_OFF(1, 0x100)
#define QSERDES_RX1_RX_IDAC_TSETTLE_LOW RX_OFF(1, 0xF8)
#define QSERDES_RX1_RX_IDAC_TSETTLE_HIGH RX_OFF(1, 0xFC)
#define QSERDES_RX1_RX_MODE_00_LOW RX_OFF(1, 0x170)
#define QSERDES_RX1_RX_MODE_00_HIGH RX_OFF(1, 0x174)
#define QSERDES_RX1_RX_MODE_00_HIGH2 RX_OFF(1, 0x178)
#define QSERDES_RX1_RX_MODE_00_HIGH3 RX_OFF(1, 0x17C)
#define QSERDES_RX1_RX_MODE_00_HIGH4 RX_OFF(1, 0x180)
#define QSERDES_RX1_RX_MODE_01_LOW RX_OFF(1, 0x184)
#define QSERDES_RX1_RX_MODE_01_HIGH RX_OFF(1, 0x188)
#define QSERDES_RX1_RX_MODE_01_HIGH2 RX_OFF(1, 0x18C)
#define QSERDES_RX1_RX_MODE_01_HIGH3 RX_OFF(1, 0x190)
#define QSERDES_RX1_RX_MODE_01_HIGH4 RX_OFF(1, 0x194)
#define QSERDES_RX1_RX_MODE_10_LOW RX_OFF(1, 0x198)
#define QSERDES_RX1_RX_MODE_10_HIGH RX_OFF(1, 0x19C)
#define QSERDES_RX1_RX_MODE_10_HIGH2 RX_OFF(1, 0x1A0)
#define QSERDES_RX1_RX_MODE_10_HIGH3 RX_OFF(1, 0x1A4)
#define QSERDES_RX1_RX_MODE_10_HIGH4 RX_OFF(1, 0x1A8)
#define QSERDES_RX1_AC_JTAG_ENABLE RX_OFF(1, 0x68)
#define QSERDES_RX1_UCDR_FO_GAIN RX_OFF(1, 0x08)
#define QSERDES_RX1_UCDR_SO_GAIN RX_OFF(1, 0x14)
#define UFS_PHY_RX_LINECFG_DISABLE_BIT BIT(1)
/*
* This structure represents the v4 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_v4 {
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_SYSCLK_EN_SEL, 0xD9),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_HSCLK_SEL, 0x11),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_HSCLK_HS_SWITCH_SEL, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP_EN, 0x01),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x02),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IVCO, 0x0F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_INITVAL2, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIN_VCOCAL_HSCLK_SEL, 0x11),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START_MODE0, 0x82),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CP_CTRL_MODE0, 0x06),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RCTRL_MODE0, 0x16),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CCTRL_MODE0, 0x36),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE0, 0xFF),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE0, 0x0C),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIN_VCOCAL_CMP_CODE1_MODE0, 0xAC),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIN_VCOCAL_CMP_CODE2_MODE0, 0x1E),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START_MODE1, 0x98),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CP_CTRL_MODE1, 0x06),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RCTRL_MODE1, 0x16),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CCTRL_MODE1, 0x36),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE1, 0x32),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE1, 0x0F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIN_VCOCAL_CMP_CODE1_MODE1, 0xDD),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIN_VCOCAL_CMP_CODE2_MODE1, 0x23),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX0_PWM_GEAR_1_DIVIDER_BAND0_1, 0x06),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX0_PWM_GEAR_2_DIVIDER_BAND0_1, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX0_PWM_GEAR_3_DIVIDER_BAND0_1, 0x01),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX0_PWM_GEAR_4_DIVIDER_BAND0_1, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX0_LANE_MODE_1, 0x05),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX0_TRAN_DRVR_EMP_EN, 0x0C),
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_BAND, 0x18),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_UCDR_FASTLOCK_FO_GAIN, 0x0A),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_UCDR_SO_SATURATION_AND_ENABLE, 0x4B),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_UCDR_PI_CONTROLS, 0xF1),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_UCDR_FASTLOCK_COUNT_LOW, 0x80),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_UCDR_PI_CTRL2, 0x80),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_RX_TERM_BW, 0x1B),
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_RX_OFFSET_ADAPTOR_CNTRL2, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_RX_IDAC_MEASURE_TIME, 0x10),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_RX_IDAC_TSETTLE_LOW, 0xC0),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_RX_IDAC_TSETTLE_HIGH, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_RX_MODE_00_LOW, 0x36),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_RX_MODE_00_HIGH, 0x36),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_RX_MODE_00_HIGH2, 0xF6),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_RX_MODE_00_HIGH3, 0x3B),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_RX_MODE_00_HIGH4, 0x3D),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_RX_MODE_01_LOW, 0xE0),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_RX_MODE_01_HIGH, 0xC8),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_RX_MODE_01_HIGH2, 0xC8),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_RX_MODE_01_HIGH3, 0x3B),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_RX_MODE_01_HIGH4, 0xB1),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_RX_MODE_10_LOW, 0xE0),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_RX_MODE_10_HIGH, 0xC8),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_RX_MODE_10_HIGH2, 0xC8),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_RX_MODE_10_HIGH3, 0x3B),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_RX_MODE_10_HIGH4, 0xB1),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_MIN_HIBERN8_TIME, 0xFF),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL2, 0x6F),
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_TX_MID_TERM_CTRL1, 0x43),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_DEBUG_BUS_CLKSEL, 0x1F),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_PLL_CNTL, 0x03),
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, 0xD8),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_TX_PWM_GEAR_BAND, 0xAA),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_TX_HS_GEAR_BAND, 0x06),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_TX_HSGEAR_CAPABILITY, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_HSGEAR_CAPABILITY, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_AC_JTAG_ENABLE, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_UCDR_FO_GAIN, 0x0C),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX0_UCDR_SO_GAIN, 0x04),
};
static struct ufs_qcom_phy_calibration phy_cal_table_2nd_lane[] = {
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX1_PWM_GEAR_1_DIVIDER_BAND0_1, 0x06),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX1_PWM_GEAR_2_DIVIDER_BAND0_1, 0x03),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX1_PWM_GEAR_3_DIVIDER_BAND0_1, 0x01),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX1_PWM_GEAR_4_DIVIDER_BAND0_1, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX1_LANE_MODE_1, 0x05),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX1_TRAN_DRVR_EMP_EN, 0x0C),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_SIGDET_LVL, 0x24),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_SIGDET_CNTRL, 0x0F),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_SIGDET_DEGLITCH_CNTRL, 0x1E),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_BAND, 0x18),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_UCDR_FASTLOCK_FO_GAIN, 0x0A),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_UCDR_SO_SATURATION_AND_ENABLE, 0x4B),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_UCDR_PI_CONTROLS, 0xF1),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_UCDR_FASTLOCK_COUNT_LOW, 0x80),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_UCDR_PI_CTRL2, 0x80),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_TERM_BW, 0x1B),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_EQU_ADAPTOR_CNTRL2, 0x06),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_EQU_ADAPTOR_CNTRL3, 0x04),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_EQU_ADAPTOR_CNTRL4, 0x1D),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_OFFSET_ADAPTOR_CNTRL2, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_IDAC_MEASURE_TIME, 0x10),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_IDAC_TSETTLE_LOW, 0xC0),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_IDAC_TSETTLE_HIGH, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_MODE_00_LOW, 0x36),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_MODE_00_HIGH, 0x36),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_MODE_00_HIGH2, 0xF6),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_MODE_00_HIGH3, 0x3B),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_MODE_00_HIGH4, 0x3D),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_MODE_01_LOW, 0xE0),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_MODE_01_HIGH, 0xC8),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_MODE_01_HIGH2, 0xC8),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_MODE_01_HIGH3, 0x3B),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_MODE_01_HIGH4, 0xB1),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_MODE_10_LOW, 0xE0),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_MODE_10_HIGH, 0xC8),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_MODE_10_HIGH2, 0xC8),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_MODE_10_HIGH3, 0x3B),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_RX_MODE_10_HIGH4, 0xB1),
UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_MULTI_LANE_CTRL1, 0x02),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_AC_JTAG_ENABLE, 0x00),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_UCDR_FO_GAIN, 0x0C),
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX1_UCDR_SO_GAIN, 0x04),
};
static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = {
UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x06),
};
#endif

View File

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

View File

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

View File

@@ -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
@@ -15,20 +15,33 @@
#include "phy-qcom-ufs-i.h"
#define MAX_PROP_NAME 32
#define VDDA_PHY_MIN_UV 1000000
#define VDDA_PHY_MAX_UV 1000000
#define VDDA_PLL_MIN_UV 1800000
#define VDDA_PHY_MIN_UV 800000
#define VDDA_PHY_MAX_UV 925000
#define VDDA_PLL_MIN_UV 1200000
#define VDDA_PLL_MAX_UV 1800000
#define VDDP_REF_CLK_MIN_UV 1200000
#define VDDP_REF_CLK_MAX_UV 1200000
#define UFS_PHY_DEFAULT_LANES_PER_DIRECTION 1
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) {
@@ -37,9 +50,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
@@ -55,9 +66,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 */
@@ -98,13 +107,6 @@ int ufs_qcom_phy_base_init(struct platform_device *pdev,
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;
}
@@ -139,6 +141,19 @@ struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev,
goto out;
}
if (of_property_read_u32(dev->of_node, "lanes-per-direction",
&common_cfg->lanes_per_direction))
common_cfg->lanes_per_direction =
UFS_PHY_DEFAULT_LANES_PER_DIRECTION;
/*
* UFS PHY power management is managed by its parent (UFS host
* controller) hence set the no the no runtime PM callbacks flag
* on UFS PHY device to avoid any accidental attempt to call the
* PM callbacks for PHY device.
*/
pm_runtime_no_callbacks(&generic_phy->dev);
common_cfg->phy_spec_ops = phy_spec_ops;
common_cfg->dev = dev;
@@ -179,15 +194,19 @@ int ufs_qcom_phy_init_clks(struct ufs_qcom_phy *phy_common)
"qcom,msm8996-ufs-phy-qmp-14nm"))
goto skip_txrx_clk;
err = ufs_qcom_phy_clk_get(phy_common->dev, "tx_iface_clk",
&phy_common->tx_iface_clk);
if (err)
goto out;
/*
* tx_iface_clk does not exist in newer version of ufs-phy HW,
* so don't return error if it is not found
*/
__ufs_qcom_phy_clk_get(phy_common->dev, "tx_iface_clk",
&phy_common->tx_iface_clk, false);
err = ufs_qcom_phy_clk_get(phy_common->dev, "rx_iface_clk",
&phy_common->rx_iface_clk);
if (err)
goto out;
/*
* rx_iface_clk does not exist in newer version of ufs-phy HW,
* so don't return error if it is not found
*/
__ufs_qcom_phy_clk_get(phy_common->dev, "rx_iface_clk",
&phy_common->rx_iface_clk, false);
skip_txrx_clk:
err = ufs_qcom_phy_clk_get(phy_common->dev, "ref_clk_src",
@@ -204,7 +223,15 @@ int ufs_qcom_phy_init_clks(struct ufs_qcom_phy *phy_common)
err = ufs_qcom_phy_clk_get(phy_common->dev, "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(phy_common->dev, "ref_aux_clk",
&phy_common->ref_aux_clk, false);
out:
return err;
}
@@ -218,6 +245,14 @@ static int ufs_qcom_phy_init_vreg(struct device *dev,
char prop_name[MAX_PROP_NAME];
if (dev->of_node) {
snprintf(prop_name, MAX_PROP_NAME, "%s-supply", name);
if (!of_parse_phandle(dev->of_node, prop_name, 0)) {
dev_dbg(dev, "No vreg data found for %s\n", prop_name);
return -ENODATA;
}
}
vreg->name = name;
vreg->reg = devm_regulator_get(dev, name);
if (IS_ERR(vreg->reg)) {
@@ -242,6 +277,9 @@ static int ufs_qcom_phy_init_vreg(struct device *dev,
}
err = 0;
}
snprintf(prop_name, MAX_PROP_NAME, "%s-always-on", name);
vreg->is_always_on = of_property_read_bool(dev->of_node,
prop_name);
}
if (!strcmp(name, "vdda-pll")) {
@@ -270,11 +308,10 @@ int ufs_qcom_phy_init_vregulators(struct ufs_qcom_phy *phy_common)
err = ufs_qcom_phy_init_vreg(phy_common->dev, &phy_common->vdda_phy,
"vdda-phy");
if (err)
goto out;
err = ufs_qcom_phy_init_vreg(phy_common->dev, &phy_common->vddp_ref_clk,
ufs_qcom_phy_init_vreg(phy_common->dev, &phy_common->vddp_ref_clk,
"vddp-ref-clk");
out:
@@ -291,6 +328,8 @@ static int ufs_qcom_phy_cfg_vreg(struct device *dev,
int min_uV;
int uA_load;
WARN_ON(!vreg);
if (regulator_count_voltages(reg) > 0) {
min_uV = on ? vreg->min_uV : 0;
ret = regulator_set_voltage(reg, min_uV, vreg->max_uV);
@@ -382,9 +421,26 @@ static int ufs_qcom_phy_enable_ref_clk(struct ufs_qcom_phy *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);
@@ -399,7 +455,7 @@ static int ufs_qcom_phy_disable_vreg(struct device *dev,
{
int ret = 0;
if (!vreg || !vreg->enabled)
if (!vreg || !vreg->enabled || vreg->is_always_on)
goto out;
ret = regulator_disable(vreg->reg);
@@ -419,6 +475,13 @@ static int ufs_qcom_phy_disable_vreg(struct device *dev,
static void ufs_qcom_phy_disable_ref_clk(struct ufs_qcom_phy *phy)
{
if (phy->is_ref_clk_enabled) {
/*
* "ref_aux_clk" is optional clock and only supported by
* certain phy versions, hence make sure that clk reference
* is available before trying to disable the clock.
*/
if (phy->ref_aux_clk)
clk_disable_unprepare(phy->ref_aux_clk);
clk_disable_unprepare(phy->ref_clk);
/*
* "ref_clk_parent" is optional clock hence make sure that clk
@@ -489,6 +552,9 @@ static int ufs_qcom_phy_enable_iface_clk(struct ufs_qcom_phy *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",
@@ -511,6 +577,9 @@ static int ufs_qcom_phy_enable_iface_clk(struct ufs_qcom_phy *phy)
/* Turn OFF M-PHY RMMI interface clocks */
void ufs_qcom_phy_disable_iface_clk(struct ufs_qcom_phy *phy)
{
if (!phy->tx_iface_clk)
return;
if (phy->is_iface_clk_enabled) {
clk_disable_unprepare(phy->tx_iface_clk);
clk_disable_unprepare(phy->rx_iface_clk);
@@ -518,8 +587,9 @@ void ufs_qcom_phy_disable_iface_clk(struct ufs_qcom_phy *phy)
}
}
static int ufs_qcom_phy_start_serdes(struct ufs_qcom_phy *ufs_qcom_phy)
int ufs_qcom_phy_start_serdes(struct phy *generic_phy)
{
struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
int ret = 0;
if (!ufs_qcom_phy->phy_spec_ops->start_serdes) {
@@ -538,19 +608,24 @@ 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);
if (ufs_qcom_phy->phy_spec_ops->ctrl_rx_linecfg)
ufs_qcom_phy->phy_spec_ops->ctrl_rx_linecfg(ufs_qcom_phy, ctrl);
return 0;
}
void ufs_qcom_phy_save_controller_version(struct phy *generic_phy,
u8 major, u16 minor, u16 step)
{
@@ -562,8 +637,39 @@ void ufs_qcom_phy_save_controller_version(struct phy *generic_phy,
}
EXPORT_SYMBOL_GPL(ufs_qcom_phy_save_controller_version);
static int ufs_qcom_phy_is_pcs_ready(struct ufs_qcom_phy *ufs_qcom_phy)
int ufs_qcom_phy_calibrate_phy(struct phy *generic_phy, bool is_rate_B)
{
struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
int ret = 0;
if (!ufs_qcom_phy->phy_spec_ops->calibrate_phy) {
dev_err(ufs_qcom_phy->dev, "%s: calibrate_phy() callback is not supported\n",
__func__);
ret = -ENOTSUPP;
} else {
ret = ufs_qcom_phy->phy_spec_ops->calibrate_phy(ufs_qcom_phy,
is_rate_B);
if (ret)
dev_err(ufs_qcom_phy->dev, "%s: calibrate_phy() failed %d\n",
__func__, ret);
}
return ret;
}
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_is_pcs_ready(struct phy *generic_phy)
{
struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
if (!ufs_qcom_phy->phy_spec_ops->is_physical_coding_sublayer_ready) {
dev_err(ufs_qcom_phy->dev, "%s: is_physical_coding_sublayer_ready() callback is not supported\n",
__func__);
@@ -583,18 +689,6 @@ int ufs_qcom_phy_power_on(struct phy *generic_phy)
if (phy_common->is_powered_on)
return 0;
if (!phy_common->is_started) {
err = ufs_qcom_phy_start_serdes(phy_common);
if (err)
return err;
err = ufs_qcom_phy_is_pcs_ready(phy_common);
if (err)
return err;
phy_common->is_started = true;
}
err = ufs_qcom_phy_enable_vreg(dev, &phy_common->vdda_phy);
if (err) {
dev_err(dev, "%s enable vdda_phy failed, err=%d\n",
@@ -676,6 +770,42 @@ int ufs_qcom_phy_power_off(struct phy *generic_phy)
}
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);
void ufs_qcom_phy_dump_regs(struct ufs_qcom_phy *phy, int offset,
int len, char *prefix)
{
print_hex_dump(KERN_ERR, prefix,
len > 4 ? DUMP_PREFIX_OFFSET : DUMP_PREFIX_NONE,
16, 4, phy->mmio + offset, len, false);
}
EXPORT_SYMBOL(ufs_qcom_phy_dump_regs);
void ufs_qcom_phy_dbg_register_dump(struct phy *generic_phy)
{
struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy);
if (ufs_qcom_phy->phy_spec_ops->dbg_register_dump)
ufs_qcom_phy->phy_spec_ops->dbg_register_dump(ufs_qcom_phy);
}
EXPORT_SYMBOL(ufs_qcom_phy_dbg_register_dump);
MODULE_AUTHOR("Yaniv Gardi <ygardi@codeaurora.org>");
MODULE_AUTHOR("Vivek Gautam <vivek.gautam@codeaurora.org>");
MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY");

View File

@@ -279,7 +279,11 @@ int __scsi_execute(struct scsi_device *sdev, const unsigned char *cmd,
rq->cmd_len = COMMAND_SIZE(cmd[0]);
memcpy(rq->cmd, cmd, rq->cmd_len);
rq->retries = retries;
if (likely(!sdev->timeout_override))
req->timeout = timeout;
else
req->timeout = sdev->timeout_override;
req->cmd_flags |= flags;
req->rq_flags |= rq_flags | RQF_QUIET;
@@ -2452,6 +2456,33 @@ void scsi_unblock_requests(struct Scsi_Host *shost)
}
EXPORT_SYMBOL(scsi_unblock_requests);
/*
* Function: scsi_set_cmd_timeout_override()
*
* Purpose: Utility function used by low-level drivers to override
timeout for the scsi commands.
*
* Arguments: sdev - scsi device in question
* timeout - timeout in jiffies
*
* Returns: Nothing
*
* Lock status: No locks are assumed held.
*
* Notes: Some platforms might be very slow and command completion may
* take much longer than default scsi command timeouts.
* SCSI Read/Write command timeout can be changed by
* blk_queue_rq_timeout() but there is no option to override
* timeout for rest of the scsi commands. This function would
* would allow this.
*/
void scsi_set_cmd_timeout_override(struct scsi_device *sdev,
unsigned int timeout)
{
sdev->timeout_override = timeout;
}
EXPORT_SYMBOL(scsi_set_cmd_timeout_override);
int __init scsi_init_queue(void)
{
scsi_sdb_cache = kmem_cache_create("scsi_data_buffer",

View File

@@ -16,6 +16,9 @@
#include "scsi_priv.h"
static int do_scsi_runtime_resume(struct device *dev,
const struct dev_pm_ops *pm);
#ifdef CONFIG_PM_SLEEP
static int do_scsi_suspend(struct device *dev, const struct dev_pm_ops *pm)
@@ -77,10 +80,22 @@ static int scsi_dev_type_resume(struct device *dev,
scsi_device_resume(to_scsi_device(dev));
dev_dbg(dev, "scsi resume: %d\n", err);
if (err == 0) {
if (err == 0 && (cb != do_scsi_runtime_resume)) {
pm_runtime_disable(dev);
pm_runtime_set_active(dev);
err = pm_runtime_set_active(dev);
pm_runtime_enable(dev);
if (!err && scsi_is_sdev_device(dev)) {
struct scsi_device *sdev = to_scsi_device(dev);
/*
* If scsi device runtime PM is managed by block layer
* then we should update request queue's runtime status
* as well.
*/
if (sdev->request_queue->dev)
blk_post_runtime_resume(sdev->request_queue, 0);
}
}
return err;
@@ -139,16 +154,6 @@ static int scsi_bus_resume_common(struct device *dev,
else
fn = NULL;
/*
* Forcibly set runtime PM status of request queue to "active" to
* make sure we can again get requests from the queue (see also
* blk_pm_peek_request()).
*
* The resume hook will correct runtime PM status of the disk.
*/
if (scsi_is_sdev_device(dev) && pm_runtime_suspended(dev))
blk_set_runtime_active(to_scsi_device(dev)->request_queue);
if (fn) {
async_schedule_domain(fn, dev, &scsi_sd_pm_domain);
@@ -223,12 +228,32 @@ static int scsi_bus_restore(struct device *dev)
#endif /* CONFIG_PM_SLEEP */
static int do_scsi_runtime_suspend(struct device *dev,
const struct dev_pm_ops *pm)
{
return pm && pm->runtime_suspend ? pm->runtime_suspend(dev) : 0;
}
static int do_scsi_runtime_resume(struct device *dev,
const struct dev_pm_ops *pm)
{
return pm && pm->runtime_resume ? pm->runtime_resume(dev) : 0;
}
static int sdev_runtime_suspend(struct device *dev)
{
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
struct scsi_device *sdev = to_scsi_device(dev);
int err = 0;
if (!sdev->request_queue->dev) {
err = scsi_dev_type_suspend(dev, do_scsi_runtime_suspend);
if (err == -EAGAIN)
pm_schedule_suspend(dev, jiffies_to_msecs(
round_jiffies_up_relative(HZ/10)));
return err;
}
err = blk_pre_runtime_suspend(sdev->request_queue);
if (err)
return err;
@@ -258,6 +283,9 @@ static int sdev_runtime_resume(struct device *dev)
const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
int err = 0;
if (!sdev->request_queue->dev)
return scsi_dev_type_resume(dev, do_scsi_runtime_resume);
blk_pre_runtime_resume(sdev->request_queue);
if (pm && pm->runtime_resume)
err = pm->runtime_resume(dev);

View File

@@ -826,15 +826,10 @@ static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result,
* well-known logical units. Force well-known type
* to enumerate them correctly.
*/
if (scsi_is_wlun(sdev->lun) && sdev->type != TYPE_WLUN) {
sdev_printk(KERN_WARNING, sdev,
"%s: correcting incorrect peripheral device type 0x%x for W-LUN 0x%16xhN\n",
__func__, sdev->type, (unsigned int)sdev->lun);
if (scsi_is_wlun(sdev->lun) && sdev->type != TYPE_WLUN)
sdev->type = TYPE_WLUN;
}
}
if (sdev->type == TYPE_RBC || sdev->type == TYPE_ROM) {
/* RBC and MMC devices can return SCSI-3 compliance and yet
* still not support REPORT LUNS, so make them act as
@@ -969,6 +964,10 @@ static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result,
transport_configure_device(&sdev->sdev_gendev);
/* The LLD can override auto suspend tunables in ->slave_configure() */
sdev->use_rpm_auto = 0;
sdev->autosuspend_delay = SCSI_DEFAULT_AUTOSUSPEND_DELAY;
if (sdev->host->hostt->slave_configure) {
ret = sdev->host->hostt->slave_configure(sdev);
if (ret) {

View File

@@ -1282,6 +1282,7 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev)
device_enable_async_suspend(&sdev->sdev_gendev);
scsi_autopm_get_target(starget);
pm_runtime_set_active(&sdev->sdev_gendev);
if (!sdev->use_rpm_auto)
pm_runtime_forbid(&sdev->sdev_gendev);
pm_runtime_enable(&sdev->sdev_gendev);
scsi_autopm_put_target(starget);

View File

@@ -957,7 +957,10 @@ static int sd_setup_write_same_cmnd(struct scsi_cmnd *cmd)
sector >>= ilog2(sdp->sector_size) - 9;
nr_sectors >>= ilog2(sdp->sector_size) - 9;
if (likely(!sdp->timeout_override))
rq->timeout = SD_WRITE_SAME_TIMEOUT;
else
rq->timeout = sdp->timeout_override;
if (sdkp->ws16 || sector > 0xffffffff || nr_sectors > 0xffff) {
cmd->cmd_len = 16;
@@ -1525,83 +1528,6 @@ static int media_not_present(struct scsi_disk *sdkp,
return 0;
}
/**
* sd_check_events - check media events
* @disk: kernel device descriptor
* @clearing: disk events currently being cleared
*
* Returns mask of DISK_EVENT_*.
*
* Note: this function is invoked from the block subsystem.
**/
static unsigned int sd_check_events(struct gendisk *disk, unsigned int clearing)
{
struct scsi_disk *sdkp = scsi_disk_get(disk);
struct scsi_device *sdp;
int retval;
if (!sdkp)
return 0;
sdp = sdkp->device;
SCSI_LOG_HLQUEUE(3, sd_printk(KERN_INFO, sdkp, "sd_check_events\n"));
/*
* If the device is offline, don't send any commands - just pretend as
* if the command failed. If the device ever comes back online, we
* can deal with it then. It is only because of unrecoverable errors
* that we would ever take a device offline in the first place.
*/
if (!scsi_device_online(sdp)) {
set_media_not_present(sdkp);
goto out;
}
/*
* Using TEST_UNIT_READY enables differentiation between drive with
* no cartridge loaded - NOT READY, drive with changed cartridge -
* UNIT ATTENTION, or with same cartridge - GOOD STATUS.
*
* Drives that auto spin down. eg iomega jaz 1G, will be started
* by sd_spinup_disk() from sd_revalidate_disk(), which happens whenever
* sd_revalidate() is called.
*/
if (scsi_block_when_processing_errors(sdp)) {
struct scsi_sense_hdr sshdr = { 0, };
retval = scsi_test_unit_ready(sdp, SD_TIMEOUT, SD_MAX_RETRIES,
&sshdr);
/* failed to execute TUR, assume media not present */
if (host_byte(retval)) {
set_media_not_present(sdkp);
goto out;
}
if (media_not_present(sdkp, &sshdr))
goto out;
}
/*
* For removable scsi disk we have to recognise the presence
* of a disk in the drive.
*/
if (!sdkp->media_present)
sdp->changed = 1;
sdkp->media_present = 1;
out:
/*
* sdp->changed is set under the following conditions:
*
* Medium present state has changed in either direction.
* Device has indicated UNIT_ATTENTION.
*/
retval = sdp->changed ? DISK_EVENT_MEDIA_CHANGE : 0;
sdp->changed = 0;
scsi_disk_put(sdkp);
return retval;
}
static int sd_sync_cache(struct scsi_disk *sdkp, struct scsi_sense_hdr *sshdr)
{
int retries, res;
@@ -1797,7 +1723,6 @@ static const struct block_device_operations sd_fops = {
#ifdef CONFIG_COMPAT
.compat_ioctl = sd_compat_ioctl,
#endif
.check_events = sd_check_events,
.revalidate_disk = sd_revalidate_disk,
.unlock_native_capacity = sd_unlock_native_capacity,
.pr_ops = &sd_pr_ops,
@@ -2564,11 +2489,6 @@ sd_print_capacity(struct scsi_disk *sdkp,
sizeof(cap_str_10));
if (sdkp->first_scan || old_capacity != sdkp->capacity) {
sd_printk(KERN_NOTICE, sdkp,
"%llu %d-byte logical blocks: (%s/%s)\n",
(unsigned long long)sdkp->capacity,
sector_size, cap_str_10, cap_str_2);
if (sdkp->physical_block_size != sector_size)
sd_printk(KERN_NOTICE, sdkp,
"%u-byte physical blocks\n",
@@ -2658,16 +2578,13 @@ sd_read_cache_type(struct scsi_disk *sdkp, unsigned char *buffer)
{
int len = 0, res;
struct scsi_device *sdp = sdkp->device;
struct Scsi_Host *host = sdp->host;
int dbd;
int modepage;
int first_len;
struct scsi_mode_data data;
struct scsi_sense_hdr sshdr;
int old_wce = sdkp->WCE;
int old_rcd = sdkp->RCD;
int old_dpofua = sdkp->DPOFUA;
if (sdkp->cache_override)
return;
@@ -2689,6 +2606,9 @@ sd_read_cache_type(struct scsi_disk *sdkp, unsigned char *buffer)
dbd = 8;
} else {
modepage = 8;
if (host->set_dbd_for_caching)
dbd = 8;
else
dbd = 0;
}
@@ -2790,15 +2710,6 @@ sd_read_cache_type(struct scsi_disk *sdkp, unsigned char *buffer)
if (sdkp->WCE && sdkp->write_prot)
sdkp->WCE = 0;
if (sdkp->first_scan || old_wce != sdkp->WCE ||
old_rcd != sdkp->RCD || old_dpofua != sdkp->DPOFUA)
sd_printk(KERN_NOTICE, sdkp,
"Write cache: %s, read cache: %s, %s\n",
sdkp->WCE ? "enabled" : "disabled",
sdkp->RCD ? "disabled" : "enabled",
sdkp->DPOFUA ? "supports DPO and FUA"
: "doesn't support DPO or FUA");
return;
}
@@ -3132,10 +3043,10 @@ static int sd_revalidate_disk(struct gendisk *disk)
if (sdkp->opt_xfer_blocks &&
sdkp->opt_xfer_blocks <= dev_max &&
sdkp->opt_xfer_blocks <= SD_DEF_XFER_BLOCKS &&
logical_to_bytes(sdp, sdkp->opt_xfer_blocks) >= PAGE_SIZE) {
q->limits.io_opt = logical_to_bytes(sdp, sdkp->opt_xfer_blocks);
rw_max = logical_to_sectors(sdp, sdkp->opt_xfer_blocks);
} else
sdkp->opt_xfer_blocks * sdp->sector_size >= PAGE_SIZE)
rw_max = q->limits.io_opt =
sdkp->opt_xfer_blocks * sdp->sector_size;
else
rw_max = min_not_zero(logical_to_sectors(sdp, dev_max),
(sector_t)BLK_DEF_MAX_SECTORS);
@@ -3271,6 +3182,9 @@ static void sd_probe_async(void *data, async_cookie_t cookie)
}
blk_pm_runtime_init(sdp->request_queue, dev);
if (sdp->autosuspend_delay >= 0)
pm_runtime_set_autosuspend_delay(dev, sdp->autosuspend_delay);
device_add_disk(dev, gd);
if (sdkp->capacity)
sd_dif_config_host(sdkp);
@@ -3283,8 +3197,6 @@ static void sd_probe_async(void *data, async_cookie_t cookie)
sd_printk(KERN_NOTICE, sdkp, "supports TCG Opal\n");
}
sd_printk(KERN_NOTICE, sdkp, "Attached SCSI %sdisk\n",
sdp->removable ? "removable " : "");
scsi_autopm_put_device(sdp);
put_device(&sdkp->dev);
}
@@ -3529,7 +3441,6 @@ static int sd_suspend_common(struct device *dev, bool ignore_stop_errors)
return 0;
if (sdkp->WCE && sdkp->media_present) {
sd_printk(KERN_NOTICE, sdkp, "Synchronizing SCSI cache\n");
ret = sd_sync_cache(sdkp, &sshdr);
if (ret) {
@@ -3551,7 +3462,7 @@ static int sd_suspend_common(struct device *dev, bool ignore_stop_errors)
}
if (sdkp->device->manage_start_stop) {
sd_printk(KERN_NOTICE, sdkp, "Stopping disk\n");
sd_printk(KERN_DEBUG, sdkp, "Stopping disk\n");
/* an error is not worth aborting a system sleep */
ret = sd_start_stop_device(sdkp, 0);
if (ignore_stop_errors)
@@ -3582,7 +3493,7 @@ static int sd_resume(struct device *dev)
if (!sdkp->device->manage_start_stop)
return 0;
sd_printk(KERN_NOTICE, sdkp, "Starting disk\n");
sd_printk(KERN_DEBUG, sdkp, "Starting disk\n");
ret = sd_start_stop_device(sdkp, 1);
if (!ret)
opal_unlock_from_suspend(sdkp->opal_dev);

View File

@@ -838,7 +838,11 @@ sg_common_write(Sg_fd * sfp, Sg_request * srp,
else
at_head = 1;
if (likely(!sdp->device->timeout_override))
srp->rq->timeout = timeout;
else
srp->rq->timeout = sdp->device->timeout_override;
kref_get(&sfp->f_ref); /* sg_rq_end_io() does kref_put(). */
blk_execute_rq_nowait(sdp->device->request_queue, sdp->disk,
srp->rq, at_head, sg_rq_end_io);
@@ -1555,9 +1559,6 @@ sg_add_device(struct device *cl_dev, struct class_interface *cl_intf)
} else
pr_warn("%s: sg_sys Invalid\n", __func__);
sdev_printk(KERN_NOTICE, scsidp, "Attached scsi generic sg%d "
"type %d\n", sdp->index, scsidp->type);
dev_set_drvdata(cl_dev, sdp);
return 0;

View File

@@ -101,6 +101,40 @@ 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.
config SCSI_UFSHCD_CMD_LOGGING
bool "Universal Flash Storage host controller driver layer command logging support"
depends on SCSI_UFSHCD
help
This selects the UFS host controller driver layer command logging.
UFS host controller driver layer command logging records all the
command information sent from UFS host controller for debugging
purpose.
Select this if you want above mentioned debug information captured.
If unsure, say N.
config SCSI_UFS_HISI
tristate "Hisilicon specific hooks to UFS controller platform driver"
depends on (ARCH_HISI || COMPILE_TEST) && SCSI_UFSHCD_PLATFORM

View File

@@ -3,8 +3,11 @@
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_UFS_QCOM_ICE) += ufs-qcom-ice.o
obj-$(CONFIG_SCSI_UFSHCD) += ufshcd-core.o
ufshcd-core-objs := ufshcd.o ufs-sysfs.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
obj-$(CONFIG_SCSI_UFS_HISI) += ufs-hisi.o

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,76 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2013-2016, 2018, 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_HIBERN8_ENTER,
ERR_INJECT_HIBERN8_EXIT,
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);
void ufsdbg_clr_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)
{
}
void ufsdbg_clr_err_state(struct ufs_hba *hba)
{
}
#endif
#ifdef CONFIG_UFS_FAULT_INJECTION
void ufsdbg_error_inject_dispatcher(struct ufs_hba *hba,
enum ufsdbg_err_inject_scenario err_scenario,
int success_value, int *ret_value);
#else
static inline void ufsdbg_error_inject_dispatcher(struct ufs_hba *hba,
enum ufsdbg_err_inject_scenario err_scenario,
int success_value, int *ret_value)
{
}
#endif
#endif /* End of Header */

View File

@@ -0,0 +1,391 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2015, 2017-2018, 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_DEBUGFS_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;
int ret = 0;
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;
pm_runtime_get_sync(host->hba->dev);
ufshcd_hold(host->hba, false);
ret = ufs_qcom_testbus_config(host);
ufshcd_release(host->hba, false);
pm_runtime_put_sync(host->hba->dev);
return ret;
}
DEFINE_DEBUGFS_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;
unsigned long flags;
struct ufs_hba *hba = host->hba;
ret = simple_write_to_buffer(configuration,
TESTBUS_CFG_BUFF_LINE_SIZE - 1,
&buff_pos, ubuf, cnt);
if (ret < 0) {
dev_err(host->hba->dev, "%s: failed to read user data\n",
__func__);
goto out;
}
configuration[ret] = '\0';
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;
}
if (!ufs_qcom_testbus_cfg_is_ok(host, major, minor)) {
ret = -EPERM;
goto out;
}
spin_lock_irqsave(hba->host->host_lock, flags);
host->testbus.select_major = (u8)major;
host->testbus.select_minor = (u8)minor;
spin_unlock_irqrestore(hba->host->host_lock, flags);
/*
* Sanity check of the {major, minor} tuple is done in the
* config function
*/
pm_runtime_get_sync(host->hba->dev);
ufshcd_hold(host->hba, false);
ret = ufs_qcom_testbus_config(host);
ufshcd_release(host->hba, false);
pm_runtime_put_sync(host->hba->dev);
if (!ret)
dev_dbg(host->hba->dev,
"%s: New configuration: major=%d, minor=%d\n",
__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_DEBUGFS_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\n", __func__);
goto err_no_root;
}
host->debugfs_files.dbg_print_en =
debugfs_create_file("dbg_print_en", 0600,
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", 0600,
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", 0600,
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", 0400,
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", 0400,
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", 0400,
host->debugfs_files.debugfs_root, host,
&ufs_qcom_dbg_pm_qos_desc);
if (!host->debugfs_files.dbg_regs) {
dev_err(host->hba->dev,
"%s: failed create dbg_regs debugfs entry\n",
__func__);
goto err;
}
return;
err:
ufs_qcom_dbg_remove_debugfs(host);
err_no_root:
dev_err(host->hba->dev, "%s: failed to initialize debugfs\n", __func__);
}
static void ufs_qcom_dbg_remove_debugfs(struct ufs_qcom_host *host)
{
debugfs_remove_recursive(host->debugfs_files.debugfs_root);
host->debugfs_files.debugfs_root = NULL;
}

View File

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

View File

@@ -0,0 +1,725 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2014-2018, 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 <linux/spinlock.h>
#include <crypto/ice.h>
#include "ufshcd.h"
#include "ufs-qcom-ice.h"
#include "ufs-qcom-debugfs.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 struct workqueue_struct *ice_workqueue;
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\n",
__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)
{
unsigned long flags;
struct ufs_qcom_host *qcom_host =
container_of(work, struct ufs_qcom_host, ice_cfg_work);
if (!qcom_host->ice.vops->config_start)
return;
spin_lock_irqsave(&qcom_host->ice_work_lock, flags);
if (!qcom_host->req_pending) {
qcom_host->work_pending = false;
spin_unlock_irqrestore(&qcom_host->ice_work_lock, flags);
return;
}
spin_unlock_irqrestore(&qcom_host->ice_work_lock, flags);
/*
* config_start is called again as previous attempt returned -EAGAIN,
* this call shall now take care of the necessary key setup.
*/
qcom_host->ice.vops->config_start(qcom_host->ice.pdev,
qcom_host->req_pending, NULL, false);
spin_lock_irqsave(&qcom_host->ice_work_lock, flags);
qcom_host->req_pending = NULL;
qcom_host->work_pending = false;
spin_unlock_irqrestore(&qcom_host->ice_work_lock, flags);
}
/**
* 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;
ice_workqueue = alloc_workqueue("ice-set-key",
WQ_MEM_RECLAIM | WQ_HIGHPRI, 0);
if (!ice_workqueue) {
dev_err(ufs_dev, "%s: workqueue allocation failed.\n",
__func__);
goto out;
}
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;
unsigned long flags;
if (!qcom_host->ice.pdev || !qcom_host->ice.vops) {
dev_dbg(qcom_host->hba->dev, "%s: ice device is not enabled\n",
__func__);
return 0;
}
if (qcom_host->ice.vops->config_start) {
memset(&ice_set, 0, sizeof(ice_set));
spin_lock_irqsave(
&qcom_host->ice_work_lock, flags);
err = qcom_host->ice.vops->config_start(qcom_host->ice.pdev,
cmd->request, &ice_set, true);
if (err) {
/*
* 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.
*/
if (err == -EAGAIN) {
dev_dbg(qcom_host->hba->dev,
"%s: scheduling task for ice setup\n",
__func__);
if (!qcom_host->work_pending) {
qcom_host->req_pending = cmd->request;
if (!queue_work(ice_workqueue,
&qcom_host->ice_cfg_work)) {
qcom_host->req_pending = NULL;
spin_unlock_irqrestore(
&qcom_host->ice_work_lock,
flags);
return err;
}
qcom_host->work_pending = true;
}
} else {
if (err != -EBUSY)
dev_err(qcom_host->hba->dev,
"%s: error in ice_vops->config %d\n",
__func__, err);
}
spin_unlock_irqrestore(&qcom_host->ice_work_lock,
flags);
return err;
}
spin_unlock_irqrestore(&qcom_host->ice_work_lock, flags);
if (ufs_qcom_is_data_cmd(cmd_op, true))
*enable = !ice_set.encr_bypass;
else if (ufs_qcom_is_data_cmd(cmd_op, false))
*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;
unsigned long flags;
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) >>
UFS_QCOM_ICE_TR_DATA_UNIT_4_KB;
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) {
spin_lock_irqsave(
&qcom_host->ice_work_lock, flags);
err = qcom_host->ice.vops->config_start(qcom_host->ice.pdev,
req, &ice_set, true);
if (err) {
/*
* 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.
*/
if (err == -EAGAIN) {
dev_dbg(qcom_host->hba->dev,
"%s: scheduling task for ice setup\n",
__func__);
if (!qcom_host->work_pending) {
qcom_host->req_pending = cmd->request;
if (!queue_work(ice_workqueue,
&qcom_host->ice_cfg_work)) {
qcom_host->req_pending = NULL;
spin_unlock_irqrestore(
&qcom_host->ice_work_lock,
flags);
return err;
}
qcom_host->work_pending = true;
}
} else {
if (err != -EBUSY)
dev_err(qcom_host->hba->dev,
"%s: error in ice_vops->config %d\n",
__func__, err);
}
spin_unlock_irqrestore(
&qcom_host->ice_work_lock, flags);
return err;
}
spin_unlock_irqrestore(
&qcom_host->ice_work_lock, flags);
}
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
<< 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;
}

View File

@@ -0,0 +1,132 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2014-2018, 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_ICE_H_
#define _UFS_QCOM_ICE_H_
#include <scsi/scsi_cmnd.h>
#include "ufs-qcom.h"
/*
* UFS host controller ICE registers. There are n [0..31]
* of each of these registers
*/
enum {
REG_UFS_QCOM_ICE_CFG = 0x2200,
REG_UFS_QCOM_ICE_CTRL_INFO_1_n = 0x2204,
REG_UFS_QCOM_ICE_CTRL_INFO_2_n = 0x2208,
REG_UFS_QCOM_ICE_CTRL_INFO_3_n = 0x220C,
};
#define NUM_QCOM_ICE_CTRL_INFO_n_REGS 32
/* UFS QCOM ICE CTRL Info register offset */
enum {
OFFSET_UFS_QCOM_ICE_CTRL_INFO_BYPASS = 0,
OFFSET_UFS_QCOM_ICE_CTRL_INFO_KEY_INDEX = 0x1,
OFFSET_UFS_QCOM_ICE_CTRL_INFO_CDU = 0x6,
};
/* UFS QCOM ICE CTRL Info register masks */
enum {
MASK_UFS_QCOM_ICE_CTRL_INFO_BYPASS = 0x1,
MASK_UFS_QCOM_ICE_CTRL_INFO_KEY_INDEX = 0x1F,
MASK_UFS_QCOM_ICE_CTRL_INFO_CDU = 0x8,
};
/* UFS QCOM ICE encryption/decryption bypass state */
enum {
UFS_QCOM_ICE_DISABLE_BYPASS = 0,
UFS_QCOM_ICE_ENABLE_BYPASS = 1,
};
/* UFS QCOM ICE Crypto Data Unit of target DUN of Transfer Request */
enum {
UFS_QCOM_ICE_TR_DATA_UNIT_512_B = 0,
UFS_QCOM_ICE_TR_DATA_UNIT_1_KB = 1,
UFS_QCOM_ICE_TR_DATA_UNIT_2_KB = 2,
UFS_QCOM_ICE_TR_DATA_UNIT_4_KB = 3,
UFS_QCOM_ICE_TR_DATA_UNIT_8_KB = 4,
UFS_QCOM_ICE_TR_DATA_UNIT_16_KB = 5,
UFS_QCOM_ICE_TR_DATA_UNIT_32_KB = 6,
};
/* UFS QCOM ICE internal state */
enum {
UFS_QCOM_ICE_STATE_DISABLED = 0,
UFS_QCOM_ICE_STATE_ACTIVE = 1,
UFS_QCOM_ICE_STATE_SUSPENDED = 2,
};
#ifdef CONFIG_SCSI_UFS_QCOM_ICE
int ufs_qcom_ice_get_dev(struct ufs_qcom_host *qcom_host);
int ufs_qcom_ice_init(struct ufs_qcom_host *qcom_host);
int ufs_qcom_ice_req_setup(struct ufs_qcom_host *qcom_host,
struct scsi_cmnd *cmd, u8 *cc_index, bool *enable);
int ufs_qcom_ice_cfg_start(struct ufs_qcom_host *qcom_host,
struct scsi_cmnd *cmd);
int ufs_qcom_ice_cfg_end(struct ufs_qcom_host *qcom_host,
struct request *req);
int ufs_qcom_ice_reset(struct ufs_qcom_host *qcom_host);
int ufs_qcom_ice_resume(struct ufs_qcom_host *qcom_host);
int ufs_qcom_ice_suspend(struct ufs_qcom_host *qcom_host);
int ufs_qcom_ice_get_status(struct ufs_qcom_host *qcom_host, int *ice_status);
void ufs_qcom_ice_print_regs(struct ufs_qcom_host *qcom_host);
#else
inline int ufs_qcom_ice_get_dev(struct ufs_qcom_host *qcom_host)
{
if (qcom_host) {
qcom_host->ice.pdev = NULL;
qcom_host->ice.vops = NULL;
}
return -ENODEV;
}
inline int ufs_qcom_ice_init(struct ufs_qcom_host *qcom_host)
{
return 0;
}
inline int ufs_qcom_ice_cfg_start(struct ufs_qcom_host *qcom_host,
struct scsi_cmnd *cmd)
{
return 0;
}
inline int ufs_qcom_ice_cfg_end(struct ufs_qcom_host *qcom_host,
struct request *req)
{
return 0;
}
inline int ufs_qcom_ice_reset(struct ufs_qcom_host *qcom_host)
{
return 0;
}
inline int ufs_qcom_ice_resume(struct ufs_qcom_host *qcom_host)
{
return 0;
}
inline int ufs_qcom_ice_suspend(struct ufs_qcom_host *qcom_host)
{
return 0;
}
inline int ufs_qcom_ice_get_status(struct ufs_qcom_host *qcom_host,
int *ice_status)
{
return 0;
}
inline void ufs_qcom_ice_print_regs(struct ufs_qcom_host *qcom_host)
{
}
#endif /* CONFIG_SCSI_UFS_QCOM_ICE */
#endif /* UFS_QCOM_ICE_H_ */

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
/* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2013-2018, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -14,9 +15,14 @@
#ifndef UFS_QCOM_H_
#define UFS_QCOM_H_
#define MAX_UFS_QCOM_HOSTS 1
#include <linux/phy/phy.h>
#include <linux/pm_qos.h>
#include "ufshcd.h"
#define MAX_UFS_QCOM_HOSTS 2
#define MAX_U32 (~(u32)0)
#define MPHY_TX_FSM_STATE 0x41
#define MPHY_RX_FSM_STATE 0xC1
#define TX_FSM_HIBERN8 0x1
#define HBRN8_POLL_TOUT_MS 100
#define DEFAULT_CLK_RATE_HZ 1000000
@@ -95,7 +101,7 @@ enum {
#define QUNIPRO_SEL 0x1
#define UTP_DBG_RAMS_EN 0x20000
#define TEST_BUS_EN BIT(18)
#define TEST_BUS_SEL GENMASK(22, 19)
#define TEST_BUS_SEL 0x780000
#define UFS_REG_TEST_BUS_EN BIT(30)
/* bit definitions for REG_UFS_CFG2 register */
@@ -116,6 +122,17 @@ enum {
DFC_HW_CGC_EN | TRLUT_HW_CGC_EN |\
TMRLUT_HW_CGC_EN | OCSC_HW_CGC_EN)
/* bit definitions for UFS_AH8_CFG register */
#define CC_UFS_HCLK_REQ_EN BIT(1)
#define CC_UFS_SYS_CLK_REQ_EN BIT(2)
#define CC_UFS_ICE_CORE_CLK_REQ_EN BIT(3)
#define CC_UFS_UNIPRO_CORE_CLK_REQ_EN BIT(4)
#define CC_UFS_AUXCLK_REQ_EN BIT(5)
#define UFS_HW_CLK_CTRL_EN (CC_UFS_SYS_CLK_REQ_EN |\
CC_UFS_ICE_CORE_CLK_REQ_EN |\
CC_UFS_UNIPRO_CORE_CLK_REQ_EN |\
CC_UFS_AUXCLK_REQ_EN)
/* bit offset */
enum {
OFFSET_UFS_PHY_SOFT_RESET = 1,
@@ -145,10 +162,24 @@ enum ufs_qcom_phy_init_type {
/* QUniPro Vendor specific attributes */
#define PA_VS_CONFIG_REG1 0x9000
#define SAVECONFIGTIME_MODE_MASK 0x6000
#define PA_VS_CLK_CFG_REG 0x9004
#define PA_VS_CLK_CFG_REG_MASK 0x1FF
#define PA_VS_CORE_CLK_40NS_CYCLES 0x9007
#define PA_VS_CORE_CLK_40NS_CYCLES_MASK 0xF
#define DL_VS_CLK_CFG 0xA00B
#define DL_VS_CLK_CFG_MASK 0x3FF
#define DME_VS_CORE_CLK_CTRL 0xD002
/* bit and mask definitions for DME_VS_CORE_CLK_CTRL attribute */
#define DME_VS_CORE_CLK_CTRL_CORE_CLK_DIV_EN_BIT BIT(8)
#define DME_VS_CORE_CLK_CTRL_MAX_CORE_CLK_1US_CYCLES_MASK_V4 0xFFF
#define DME_VS_CORE_CLK_CTRL_MAX_CORE_CLK_1US_CYCLES_OFFSET_V4 0x10
#define DME_VS_CORE_CLK_CTRL_MAX_CORE_CLK_1US_CYCLES_MASK 0xFF
#define DME_VS_CORE_CLK_CTRL_CORE_CLK_DIV_EN_BIT BIT(8)
#define DME_VS_CORE_CLK_CTRL_DME_HW_CGC_EN BIT(9)
static inline void
ufs_qcom_get_controller_revision(struct ufs_hba *hba,
@@ -207,6 +238,91 @@ struct ufs_qcom_testbus {
u8 select_minor;
};
/**
* struct ufs_qcom_ice_data - ICE related information
* @vops: pointer to variant operations of ICE
* @async_done: completion for supporting ICE's driver asynchronous nature
* @pdev: pointer to the proper ICE platform device
* @state: UFS-ICE interface's internal state (see
* ufs-qcom-ice.h for possible internal states)
* @quirks: UFS-ICE interface related quirks
* @crypto_engine_err: crypto engine errors
*/
struct ufs_qcom_ice_data {
struct qcom_ice_variant_ops *vops;
struct platform_device *pdev;
int state;
u16 quirks;
bool crypto_engine_err;
};
#ifdef CONFIG_DEBUG_FS
struct qcom_debugfs_files {
struct dentry *debugfs_root;
struct dentry *dbg_print_en;
struct dentry *testbus;
struct dentry *testbus_en;
struct dentry *testbus_cfg;
struct dentry *testbus_bus;
struct dentry *dbg_regs;
struct dentry *pm_qos;
};
#endif
/* PM QoS voting state */
enum ufs_qcom_pm_qos_state {
PM_QOS_UNVOTED,
PM_QOS_VOTED,
PM_QOS_REQ_VOTE,
PM_QOS_REQ_UNVOTE,
};
/**
* struct ufs_qcom_pm_qos_cpu_group - data related to cluster PM QoS voting
* logic
* @req: request object for PM QoS
* @vote_work: work object for voting procedure
* @unvote_work: work object for un-voting procedure
* @host: back pointer to the main structure
* @state: voting state machine current state
* @latency_us: requested latency value used for cluster voting, in
* microseconds
* @mask: cpu mask defined for this cluster
* @active_reqs: number of active requests on this cluster
*/
struct ufs_qcom_pm_qos_cpu_group {
struct pm_qos_request req;
struct work_struct vote_work;
struct work_struct unvote_work;
struct ufs_qcom_host *host;
enum ufs_qcom_pm_qos_state state;
s32 latency_us;
cpumask_t mask;
int active_reqs;
};
/**
* struct ufs_qcom_pm_qos - data related to PM QoS voting logic
* @groups: PM QoS cpu group state array
* @enable_attr: sysfs attribute to enable/disable PM QoS voting logic
* @latency_attr: sysfs attribute to set latency value
* @workq: single threaded workqueue to run PM QoS voting/unvoting
* @num_clusters: number of clusters defined
* @default_cpu: cpu to use for voting for request not specifying a cpu
* @is_enabled: flag specifying whether voting logic is enabled
*/
struct ufs_qcom_pm_qos {
struct ufs_qcom_pm_qos_cpu_group *groups;
struct device_attribute enable_attr;
struct device_attribute latency_attr;
struct workqueue_struct *workq;
int num_groups;
int default_cpu;
bool is_enabled;
};
struct ufs_qcom_host {
/*
* Set this capability if host controller supports the QUniPro mode
@@ -221,6 +337,18 @@ struct ufs_qcom_host {
* configuration even after UFS controller core power collapse.
*/
#define UFS_QCOM_CAP_RETAIN_SEC_CFG_AFTER_PWR_COLLAPSE 0x2
/*
* Set this capability if host controller supports Qunipro internal
* clock gating.
*/
#define UFS_QCOM_CAP_QUNIPRO_CLK_GATING 0x4
/*
* Set this capability if host controller supports SVS2 frequencies.
*/
#define UFS_QCOM_CAP_SVS2 0x8
u32 caps;
struct phy *generic_phy;
@@ -231,17 +359,34 @@ struct ufs_qcom_host {
struct clk *tx_l0_sync_clk;
struct clk *rx_l1_sync_clk;
struct clk *tx_l1_sync_clk;
/* PM Quality-of-Service (QoS) data */
struct ufs_qcom_pm_qos pm_qos;
bool disable_lpm;
bool is_lane_clks_enabled;
bool sec_cfg_updated;
struct ufs_qcom_ice_data ice;
void __iomem *dev_ref_clk_ctrl_mmio;
bool is_dev_ref_clk_enabled;
struct ufs_hw_version hw_ver;
#ifdef CONFIG_DEBUG_FS
struct qcom_debugfs_files debugfs_files;
#endif
u32 dev_ref_clk_en_mask;
/* Bitmask for enabling debug prints */
u32 dbg_print_en;
struct ufs_qcom_testbus testbus;
spinlock_t ice_work_lock;
struct work_struct ice_cfg_work;
struct request *req_pending;
struct ufs_vreg *vddp_ref_clk;
bool work_pending;
bool is_phy_pwr_on;
};
static inline u32
@@ -257,7 +402,12 @@ ufs_qcom_get_debug_reg_offset(struct ufs_qcom_host *host, u32 reg)
#define ufs_qcom_is_link_active(hba) ufshcd_is_link_active(hba)
#define ufs_qcom_is_link_hibern8(hba) ufshcd_is_link_hibern8(hba)
bool ufs_qcom_testbus_cfg_is_ok(struct ufs_qcom_host *host, u8 select_major,
u8 select_minor);
int ufs_qcom_testbus_config(struct ufs_qcom_host *host);
void ufs_qcom_print_hw_debug_reg_all(struct ufs_hba *hba, void *priv,
void (*print_fn)(struct ufs_hba *hba, int offset, int num_regs,
char *str, void *priv));
static inline bool ufs_qcom_cap_qunipro(struct ufs_qcom_host *host)
{
@@ -267,4 +417,14 @@ static inline bool ufs_qcom_cap_qunipro(struct ufs_qcom_host *host)
return false;
}
static inline bool ufs_qcom_cap_qunipro_clk_gating(struct ufs_qcom_host *host)
{
return !!(host->caps & UFS_QCOM_CAP_QUNIPRO_CLK_GATING);
}
static inline bool ufs_qcom_cap_svs2(struct ufs_qcom_host *host)
{
return !!(host->caps & UFS_QCOM_CAP_SVS2);
}
#endif /* UFS_QCOM_H_ */

View File

@@ -50,6 +50,7 @@ static inline ssize_t ufs_sysfs_pm_lvl_store(struct device *dev,
hba->rpm_lvl = value;
else
hba->spm_lvl = value;
ufshcd_apply_pm_quirks(hba);
spin_unlock_irqrestore(hba->host->host_lock, flags);
return count;
}

View File

@@ -38,6 +38,7 @@
#include <linux/mutex.h>
#include <linux/types.h>
#include <scsi/ufs/ufs.h>
#define MAX_CDB_SIZE 16
#define GENERAL_UPIU_REQUEST_SIZE 32
@@ -64,6 +65,8 @@
#define UFS_MAX_LUNS (SCSI_W_LUN_BASE + UFS_UPIU_MAX_UNIT_NUM_ID)
#define UFS_UPIU_WLUN_ID (1 << 7)
#define UFS_UPIU_MAX_GENERAL_LUN 8
#define UFS_MAX_WLUS 4
#define UFS_MAX_LUS (UFS_UPIU_MAX_GENERAL_LUN + UFS_MAX_WLUS)
/* Well known logical unit id in LUN field of UPIU */
enum {
@@ -128,63 +131,6 @@ enum {
UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST = 0x81,
};
/* Flag idn for Query Requests*/
enum flag_idn {
QUERY_FLAG_IDN_FDEVICEINIT = 0x01,
QUERY_FLAG_IDN_PERMANENT_WPE = 0x02,
QUERY_FLAG_IDN_PWR_ON_WPE = 0x03,
QUERY_FLAG_IDN_BKOPS_EN = 0x04,
QUERY_FLAG_IDN_LIFE_SPAN_MODE_ENABLE = 0x05,
QUERY_FLAG_IDN_PURGE_ENABLE = 0x06,
QUERY_FLAG_IDN_RESERVED2 = 0x07,
QUERY_FLAG_IDN_FPHYRESOURCEREMOVAL = 0x08,
QUERY_FLAG_IDN_BUSY_RTC = 0x09,
QUERY_FLAG_IDN_RESERVED3 = 0x0A,
QUERY_FLAG_IDN_PERMANENTLY_DISABLE_FW_UPDATE = 0x0B,
};
/* Attribute idn for Query requests */
enum attr_idn {
QUERY_ATTR_IDN_BOOT_LU_EN = 0x00,
QUERY_ATTR_IDN_RESERVED = 0x01,
QUERY_ATTR_IDN_POWER_MODE = 0x02,
QUERY_ATTR_IDN_ACTIVE_ICC_LVL = 0x03,
QUERY_ATTR_IDN_OOO_DATA_EN = 0x04,
QUERY_ATTR_IDN_BKOPS_STATUS = 0x05,
QUERY_ATTR_IDN_PURGE_STATUS = 0x06,
QUERY_ATTR_IDN_MAX_DATA_IN = 0x07,
QUERY_ATTR_IDN_MAX_DATA_OUT = 0x08,
QUERY_ATTR_IDN_DYN_CAP_NEEDED = 0x09,
QUERY_ATTR_IDN_REF_CLK_FREQ = 0x0A,
QUERY_ATTR_IDN_CONF_DESC_LOCK = 0x0B,
QUERY_ATTR_IDN_MAX_NUM_OF_RTT = 0x0C,
QUERY_ATTR_IDN_EE_CONTROL = 0x0D,
QUERY_ATTR_IDN_EE_STATUS = 0x0E,
QUERY_ATTR_IDN_SECONDS_PASSED = 0x0F,
QUERY_ATTR_IDN_CNTX_CONF = 0x10,
QUERY_ATTR_IDN_CORR_PRG_BLK_NUM = 0x11,
QUERY_ATTR_IDN_RESERVED2 = 0x12,
QUERY_ATTR_IDN_RESERVED3 = 0x13,
QUERY_ATTR_IDN_FFU_STATUS = 0x14,
QUERY_ATTR_IDN_PSA_STATE = 0x15,
QUERY_ATTR_IDN_PSA_DATA_SIZE = 0x16,
};
/* Descriptor idn for Query requests */
enum desc_idn {
QUERY_DESC_IDN_DEVICE = 0x0,
QUERY_DESC_IDN_CONFIGURATION = 0x1,
QUERY_DESC_IDN_UNIT = 0x2,
QUERY_DESC_IDN_RFU_0 = 0x3,
QUERY_DESC_IDN_INTERCONNECT = 0x4,
QUERY_DESC_IDN_STRING = 0x5,
QUERY_DESC_IDN_RFU_1 = 0x6,
QUERY_DESC_IDN_GEOMETRY = 0x7,
QUERY_DESC_IDN_POWER = 0x8,
QUERY_DESC_IDN_HEALTH = 0x9,
QUERY_DESC_IDN_MAX,
};
enum desc_header_offset {
QUERY_DESC_LENGTH_OFFSET = 0x00,
QUERY_DESC_DESC_TYPE_OFFSET = 0x01,
@@ -365,17 +311,13 @@ enum bkops_status {
BKOPS_STATUS_MAX = BKOPS_STATUS_CRITICAL,
};
/* UTP QUERY Transaction Specific Fields OpCode */
enum query_opcode {
UPIU_QUERY_OPCODE_NOP = 0x0,
UPIU_QUERY_OPCODE_READ_DESC = 0x1,
UPIU_QUERY_OPCODE_WRITE_DESC = 0x2,
UPIU_QUERY_OPCODE_READ_ATTR = 0x3,
UPIU_QUERY_OPCODE_WRITE_ATTR = 0x4,
UPIU_QUERY_OPCODE_READ_FLAG = 0x5,
UPIU_QUERY_OPCODE_SET_FLAG = 0x6,
UPIU_QUERY_OPCODE_CLEAR_FLAG = 0x7,
UPIU_QUERY_OPCODE_TOGGLE_FLAG = 0x8,
/* bRefClkFreq attribute values */
enum ref_clk_freq {
REF_CLK_FREQ_19_2_MHZ = 0x0,
REF_CLK_FREQ_26_MHZ = 0x1,
REF_CLK_FREQ_38_4_MHZ = 0x2,
REF_CLK_FREQ_52_MHZ = 0x3,
REF_CLK_FREQ_MAX = REF_CLK_FREQ_52_MHZ,
};
/* Query response result code */
@@ -602,10 +544,30 @@ struct ufs_vreg_info {
struct ufs_vreg *vdd_hba;
};
/* Possible values for bDeviceSubClass of device descriptor */
enum {
UFS_DEV_EMBEDDED_BOOTABLE = 0x00,
UFS_DEV_EMBEDDED_NON_BOOTABLE = 0x01,
UFS_DEV_REMOVABLE_BOOTABLE = 0x02,
UFS_DEV_REMOVABLE_NON_BOOTABLE = 0x03,
};
struct ufs_dev_info {
/* device descriptor info */
u8 b_device_sub_class;
u16 w_manufacturer_id;
u8 i_product_name;
/* query flags */
bool f_power_on_wp_en;
/* Keeps information if any of the LU is power on write protected */
bool is_lu_power_on_wp;
/* is Unit Attention Condition cleared on UFS Device LUN? */
unsigned is_ufs_dev_wlun_ua_cleared:1;
/* Device deviations from standard UFS device spec. */
unsigned int quirks;
};
#define MAX_MODEL_LEN 16

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2014-2016, The Linux Foundation. All rights reserved.
* Copyright (c) 2014-2018, 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
@@ -31,16 +31,17 @@
* @quirk: device quirk
*/
struct ufs_dev_fix {
struct ufs_dev_desc card;
u16 w_manufacturer_id;
char *model;
unsigned int quirk;
};
#define END_FIX { { 0 }, 0 }
#define END_FIX { 0 }
/* add specific device quirk */
#define UFS_FIX(_vendor, _model, _quirk) { \
.card.wmanufacturerid = (_vendor),\
.card.model = (_model), \
.w_manufacturer_id = (_vendor),\
.model = (_model), \
.quirk = (_quirk), \
}
@@ -131,4 +132,12 @@ struct ufs_dev_fix {
*/
#define UFS_DEVICE_QUIRK_HOST_PA_SAVECONFIGTIME (1 << 8)
/*
* Some UFS devices may stop responding after switching from HS-G1 to HS-G3.
* Also, it is found that these devices work fine if we do 2 steps switch:
* HS-G1 to HS-G2 followed by HS-G2 to HS-G3. Enabling this quirk for such
* device would apply this 2 steps gear switch workaround.
*/
#define UFS_DEVICE_QUIRK_HS_G1_TO_HS_G3_SWITCH (1 << 8)
#endif /* UFS_QUIRKS_H_ */

1409
drivers/scsi/ufs/ufs_test.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -42,6 +42,22 @@
#define UFSHCD_DEFAULT_LANES_PER_DIRECTION 2
static int ufshcd_parse_reset_info(struct ufs_hba *hba)
{
int ret = 0;
hba->core_reset = devm_reset_control_get(hba->dev,
"core_reset");
if (IS_ERR(hba->core_reset)) {
ret = PTR_ERR(hba->core_reset);
dev_err(hba->dev, "core_reset unavailable,err = %d\n",
ret);
hba->core_reset = NULL;
}
return ret;
}
static int ufshcd_parse_clock_info(struct ufs_hba *hba)
{
int ret = 0;
@@ -128,10 +144,11 @@ static int ufshcd_parse_clock_info(struct ufs_hba *hba)
static int ufshcd_populate_vreg(struct device *dev, const char *name,
struct ufs_vreg **out_vreg)
{
int ret = 0;
int len, ret = 0;
char prop_name[MAX_PROP_SIZE];
struct ufs_vreg *vreg = NULL;
struct device_node *np = dev->of_node;
const __be32 *prop;
if (!np) {
dev_err(dev, "%s: non DT initialization\n", __func__);
@@ -170,8 +187,16 @@ static int ufshcd_populate_vreg(struct device *dev, const char *name,
vreg->min_uV = UFS_VREG_VCC_1P8_MIN_UV;
vreg->max_uV = UFS_VREG_VCC_1P8_MAX_UV;
} else {
prop = of_get_property(np, "vcc-voltage-level", &len);
if (!prop || (len != (2 * sizeof(__be32)))) {
dev_warn(dev, "%s vcc-voltage-level property.\n",
prop ? "invalid format" : "no");
vreg->min_uV = UFS_VREG_VCC_MIN_UV;
vreg->max_uV = UFS_VREG_VCC_MAX_UV;
} else {
vreg->min_uV = be32_to_cpup(&prop[0]);
vreg->max_uV = be32_to_cpup(&prop[1]);
}
}
} else if (!strcmp(name, "vccq")) {
vreg->min_uV = UFS_VREG_VCCQ_MIN_UV;
@@ -221,7 +246,111 @@ static int ufshcd_parse_regulator_info(struct ufs_hba *hba)
return err;
}
#ifdef CONFIG_PM
static void ufshcd_parse_pm_levels(struct ufs_hba *hba)
{
struct device *dev = hba->dev;
struct device_node *np = dev->of_node;
if (np) {
if (of_property_read_u32(np, "rpm-level", &hba->rpm_lvl))
hba->rpm_lvl = -1;
if (of_property_read_u32(np, "spm-level", &hba->spm_lvl))
hba->spm_lvl = -1;
}
}
static int ufshcd_parse_pinctrl_info(struct ufs_hba *hba)
{
int ret = 0;
/* Try to obtain pinctrl handle */
hba->pctrl = devm_pinctrl_get(hba->dev);
if (IS_ERR(hba->pctrl)) {
ret = PTR_ERR(hba->pctrl);
hba->pctrl = NULL;
}
return ret;
}
static int ufshcd_parse_extcon_info(struct ufs_hba *hba)
{
struct extcon_dev *extcon;
extcon = extcon_get_edev_by_phandle(hba->dev, 0);
if (IS_ERR(extcon) && PTR_ERR(extcon) != -ENODEV)
return PTR_ERR(extcon);
if (!IS_ERR(extcon))
hba->extcon = extcon;
return 0;
}
static void ufshcd_parse_gear_limits(struct ufs_hba *hba)
{
struct device *dev = hba->dev;
struct device_node *np = dev->of_node;
int ret;
if (!np)
return;
ret = of_property_read_u32(np, "limit-tx-hs-gear",
&hba->limit_tx_hs_gear);
if (ret)
hba->limit_tx_hs_gear = -1;
ret = of_property_read_u32(np, "limit-rx-hs-gear",
&hba->limit_rx_hs_gear);
if (ret)
hba->limit_rx_hs_gear = -1;
ret = of_property_read_u32(np, "limit-tx-pwm-gear",
&hba->limit_tx_pwm_gear);
if (ret)
hba->limit_tx_pwm_gear = -1;
ret = of_property_read_u32(np, "limit-rx-pwm-gear",
&hba->limit_rx_pwm_gear);
if (ret)
hba->limit_rx_pwm_gear = -1;
}
static void ufshcd_parse_cmd_timeout(struct ufs_hba *hba)
{
struct device *dev = hba->dev;
struct device_node *np = dev->of_node;
int ret;
if (!np)
return;
ret = of_property_read_u32(np, "scsi-cmd-timeout",
&hba->scsi_cmd_timeout);
if (ret)
hba->scsi_cmd_timeout = 0;
}
static void ufshcd_parse_dev_ref_clk_freq(struct ufs_hba *hba)
{
struct device *dev = hba->dev;
struct device_node *np = dev->of_node;
int ret;
if (!np)
return;
ret = of_property_read_u32(np, "dev-ref-clk-freq",
&hba->dev_ref_clk_freq);
if (ret ||
(hba->dev_ref_clk_freq < 0) ||
(hba->dev_ref_clk_freq > REF_CLK_FREQ_52_MHZ))
/* default setting */
hba->dev_ref_clk_freq = REF_CLK_FREQ_26_MHZ;
}
#ifdef CONFIG_SMP
/**
* ufshcd_pltfrm_suspend - suspend power management function
* @dev: pointer to device handle
@@ -292,12 +421,12 @@ static void ufshcd_init_lanes_per_dir(struct ufs_hba *hba)
/**
* ufshcd_pltfrm_init - probe routine of the driver
* @pdev: pointer to Platform device handle
* @vops: pointer to variant ops
* @var: pointer to variant specific data
*
* Returns 0 on success, non-zero value on failure
*/
int ufshcd_pltfrm_init(struct platform_device *pdev,
struct ufs_hba_variant_ops *vops)
struct ufs_hba_variant *var)
{
struct ufs_hba *hba;
void __iomem *mmio_base;
@@ -325,7 +454,7 @@ int ufshcd_pltfrm_init(struct platform_device *pdev,
goto out;
}
hba->vops = vops;
hba->var = var;
err = ufshcd_parse_clock_info(hba);
if (err) {
@@ -340,24 +469,45 @@ int ufshcd_pltfrm_init(struct platform_device *pdev,
goto dealloc_host;
}
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
err = ufshcd_parse_reset_info(hba);
if (err) {
dev_err(&pdev->dev, "%s: reset parse failed %d\n",
__func__, err);
goto dealloc_host;
}
err = ufshcd_parse_pinctrl_info(hba);
if (err) {
dev_dbg(&pdev->dev, "%s: unable to parse pinctrl data %d\n",
__func__, err);
/* let's not fail the probe */
}
ufshcd_parse_dev_ref_clk_freq(hba);
ufshcd_parse_pm_levels(hba);
ufshcd_parse_gear_limits(hba);
ufshcd_parse_cmd_timeout(hba);
err = ufshcd_parse_extcon_info(hba);
if (err)
goto dealloc_host;
if (!dev->dma_mask)
dev->dma_mask = &dev->coherent_dma_mask;
ufshcd_init_lanes_per_dir(hba);
err = ufshcd_init(hba, mmio_base, irq);
if (err) {
dev_err(dev, "Initialization failed\n");
goto out_disable_rpm;
goto dealloc_host;
}
platform_set_drvdata(pdev, hba);
return 0;
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
out_disable_rpm:
pm_runtime_disable(&pdev->dev);
pm_runtime_set_suspended(&pdev->dev);
return 0;
dealloc_host:
ufshcd_dealloc_host(hba);
out:

View File

@@ -1,4 +1,5 @@
/* Copyright (c) 2015, The Linux Foundation. All rights reserved.
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2015-2018, 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
@@ -17,7 +18,7 @@
#include "ufshcd.h"
int ufshcd_pltfrm_init(struct platform_device *pdev,
struct ufs_hba_variant_ops *vops);
struct ufs_hba_variant *var);
void ufshcd_pltfrm_shutdown(struct platform_device *pdev);
#ifdef CONFIG_PM

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -90,10 +90,9 @@ enum {
MASK_64_ADDRESSING_SUPPORT = 0x01000000,
MASK_OUT_OF_ORDER_DATA_DELIVERY_SUPPORT = 0x02000000,
MASK_UIC_DME_TEST_MODE_SUPPORT = 0x04000000,
MASK_CRYPTO_SUPPORT = 0x10000000,
};
#define UFS_MASK(mask, offset) ((mask) << (offset))
/* UFS Version 08h */
#define MINOR_VERSION_NUM_MASK UFS_MASK(0xFFFF, 0)
#define MAJOR_VERSION_NUM_MASK UFS_MASK(0xFFFF, 16)
@@ -104,6 +103,7 @@ enum {
UFSHCI_VERSION_11 = 0x00010100, /* 1.1 */
UFSHCI_VERSION_20 = 0x00000200, /* 2.0 */
UFSHCI_VERSION_21 = 0x00000210, /* 2.1 */
UFSHCI_VERSION_30 = 0x00000300, /* 3.0 */
};
/*
@@ -125,10 +125,16 @@ enum {
#define UFSHCI_AHIBERN8_SCALE_MASK GENMASK(12, 10)
#define UFSHCI_AHIBERN8_SCALE_FACTOR 10
#define UFSHCI_AHIBERN8_MAX (1023 * 100000)
#define AUTO_HIBERN8_IDLE_TIMER_MASK UFS_MASK(0x3FF, 0)
#define AUTO_HIBERN8_TIMER_SCALE_MASK UFS_MASK(0x7, 10)
#define AUTO_HIBERN8_TIMER_SCALE_1_US UFS_MASK(0x0, 10)
#define AUTO_HIBERN8_TIMER_SCALE_10_US UFS_MASK(0x1, 10)
#define AUTO_HIBERN8_TIMER_SCALE_100_US UFS_MASK(0x2, 10)
#define AUTO_HIBERN8_TIMER_SCALE_1_MS UFS_MASK(0x3, 10)
#define AUTO_HIBERN8_TIMER_SCALE_10_MS UFS_MASK(0x4, 10)
#define AUTO_HIBERN8_TIMER_SCALE_100_MS UFS_MASK(0x5, 10)
/*
* IS - Interrupt Status - 20h
*/
/* IS - Interrupt status (20h) / IE - Interrupt enable (24h) */
#define UTP_TRANSFER_REQ_COMPL 0x1
#define UIC_DME_END_PT_RESET 0x2
#define UIC_ERROR 0x4
@@ -143,6 +149,7 @@ enum {
#define DEVICE_FATAL_ERROR 0x800
#define CONTROLLER_FATAL_ERROR 0x10000
#define SYSTEM_BUS_FATAL_ERROR 0x20000
#define CRYPTO_ENGINE_FATAL_ERROR 0x40000
#define UFSHCD_UIC_PWR_MASK (UIC_HIBERNATE_ENTER |\
UIC_HIBERNATE_EXIT |\
@@ -150,14 +157,16 @@ enum {
#define UFSHCD_UIC_MASK (UIC_COMMAND_COMPL | UFSHCD_UIC_PWR_MASK)
#define UFSHCD_ERROR_MASK (UIC_ERROR |\
#define UFSHCD_ERROR_MASK (UIC_ERROR | UIC_LINK_LOST |\
DEVICE_FATAL_ERROR |\
CONTROLLER_FATAL_ERROR |\
SYSTEM_BUS_FATAL_ERROR)
SYSTEM_BUS_FATAL_ERROR |\
CRYPTO_ENGINE_FATAL_ERROR)
#define INT_FATAL_ERRORS (DEVICE_FATAL_ERROR |\
CONTROLLER_FATAL_ERROR |\
SYSTEM_BUS_FATAL_ERROR)
SYSTEM_BUS_FATAL_ERROR |\
CRYPTO_ENGINE_FATAL_ERROR)
/* HCS - Host Controller Status 30h */
#define DEVICE_PRESENT 0x1
@@ -181,6 +190,43 @@ enum {
PWR_FATAL_ERROR = 0x05,
};
/* Host UIC error type */
enum ufshcd_uic_err_type {
UFS_UIC_ERROR_PA,
UFS_UIC_ERROR_DL,
UFS_UIC_ERROR_DME,
};
/* Host UIC error code PHY adapter layer */
enum ufshcd_ec_pa {
UFS_EC_PA_LANE_0,
UFS_EC_PA_LANE_1,
UFS_EC_PA_LANE_2,
UFS_EC_PA_LANE_3,
UFS_EC_PA_LINE_RESET,
UFS_EC_PA_MAX,
};
/* Host UIC error code data link layer */
enum ufshcd_ec_dl {
UFS_EC_DL_NAC_RECEIVED,
UFS_EC_DL_TCx_REPLAY_TIMER_EXPIRED,
UFS_EC_DL_AFCx_REQUEST_TIMER_EXPIRED,
UFS_EC_DL_FCx_PROTECT_TIMER_EXPIRED,
UFS_EC_DL_CRC_ERROR,
UFS_EC_DL_RX_BUFFER_OVERFLOW,
UFS_EC_DL_MAX_FRAME_LENGTH_EXCEEDED,
UFS_EC_DL_WRONG_SEQUENCE_NUMBER,
UFS_EC_DL_AFC_FRAME_SYNTAX_ERROR,
UFS_EC_DL_NAC_FRAME_SYNTAX_ERROR,
UFS_EC_DL_EOF_SYNTAX_ERROR,
UFS_EC_DL_FRAME_SYNTAX_ERROR,
UFS_EC_DL_BAD_CTRL_SYMBOL_TYPE,
UFS_EC_DL_PA_INIT_ERROR,
UFS_EC_DL_PA_ERROR_IND_RECEIVED,
UFS_EC_DL_MAX,
};
/* HCE - Host Controller Enable 34h */
#define CONTROLLER_ENABLE 0x1
#define CONTROLLER_DISABLE 0x0
@@ -188,6 +234,7 @@ enum {
/* UECPA - Host UIC Error Code PHY Adapter Layer 38h */
#define UIC_PHY_ADAPTER_LAYER_ERROR 0x80000000
#define UIC_PHY_ADAPTER_LAYER_GENERIC_ERROR 0x10
#define UIC_PHY_ADAPTER_LAYER_ERROR_CODE_MASK 0x1F
#define UIC_PHY_ADAPTER_LAYER_LANE_ERR_MASK 0xF
@@ -356,6 +403,9 @@ enum {
OCS_PEER_COMM_FAILURE = 0x5,
OCS_ABORTED = 0x6,
OCS_FATAL_ERROR = 0x7,
OCS_DEVICE_FATAL_ERROR = 0x8,
OCS_INVALID_CRYPTO_CONFIG = 0x9,
OCS_GENERAL_CRYPTO_ERROR = 0xA,
OCS_INVALID_COMMAND_STATUS = 0x0F,
MASK_OCS = 0x0F,
};
@@ -391,6 +441,8 @@ struct utp_transfer_cmd_desc {
struct ufshcd_sg_entry prd_table[SG_ALL];
};
#define UTRD_CRYPTO_ENABLE UFS_BIT(23)
/**
* struct request_desc_header - Descriptor Header common to both UTRD and UTMRD
* @dword0: Descriptor Header DW0

View File

@@ -68,6 +68,8 @@
#define CFGRXOVR4 0x00E9
#define RXSQCTRL 0x00B5
#define CFGRXOVR6 0x00BF
#define MPHY_RX_ATTR_ADDR_START 0x81
#define MPHY_RX_ATTR_ADDR_END 0xC1
#define is_mphy_tx_attr(attr) (attr < RX_MODE)
#define RX_MIN_ACTIVATETIME_UNIT_US 100
@@ -165,6 +167,14 @@
/* PHY Adapter Protocol Constants */
#define PA_MAXDATALANES 4
#define DL_FC0ProtectionTimeOutVal_Default 8191
#define DL_TC0ReplayTimeOutVal_Default 65535
#define DL_AFC0ReqTimeOutVal_Default 32767
#define DME_LocalFC0ProtectionTimeOutVal 0xD041
#define DME_LocalTC0ReplayTimeOutVal 0xD042
#define DME_LocalAFC0ReqTimeOutVal 0xD043
/* PA power modes */
enum {
FAST_MODE = 1,

View File

@@ -158,44 +158,45 @@
#define GCC_UFS_CARD_TX_SYMBOL_0_CLK 151
#define GCC_UFS_CARD_UNIPRO_CORE_CLK 152
#define GCC_UFS_CARD_UNIPRO_CORE_CLK_SRC 153
#define GCC_UFS_PHY_AHB_CLK 154
#define GCC_UFS_PHY_AXI_CLK 155
#define GCC_UFS_PHY_AXI_CLK_SRC 156
#define GCC_UFS_PHY_ICE_CORE_CLK 157
#define GCC_UFS_PHY_ICE_CORE_CLK_SRC 158
#define GCC_UFS_PHY_PHY_AUX_CLK 159
#define GCC_UFS_PHY_PHY_AUX_CLK_SRC 160
#define GCC_UFS_PHY_RX_SYMBOL_0_CLK 161
#define GCC_UFS_PHY_RX_SYMBOL_1_CLK 162
#define GCC_UFS_PHY_TX_SYMBOL_0_CLK 163
#define GCC_UFS_PHY_UNIPRO_CORE_CLK 164
#define GCC_UFS_PHY_UNIPRO_CORE_CLK_SRC 165
#define GCC_USB30_PRIM_MASTER_CLK 166
#define GCC_USB30_PRIM_MASTER_CLK_SRC 167
#define GCC_USB30_PRIM_MOCK_UTMI_CLK 168
#define GCC_USB30_PRIM_MOCK_UTMI_CLK_SRC 169
#define GCC_USB30_PRIM_SLEEP_CLK 170
#define GCC_USB30_SEC_MASTER_CLK 171
#define GCC_USB30_SEC_MASTER_CLK_SRC 172
#define GCC_USB30_SEC_MOCK_UTMI_CLK 173
#define GCC_USB30_SEC_MOCK_UTMI_CLK_SRC 174
#define GCC_USB30_SEC_SLEEP_CLK 175
#define GCC_USB3_PRIM_PHY_AUX_CLK 176
#define GCC_USB3_PRIM_PHY_AUX_CLK_SRC 177
#define GCC_USB3_PRIM_PHY_COM_AUX_CLK 178
#define GCC_USB3_PRIM_PHY_PIPE_CLK 179
#define GCC_USB3_SEC_CLKREF_CLK 180
#define GCC_USB3_SEC_PHY_AUX_CLK 181
#define GCC_USB3_SEC_PHY_AUX_CLK_SRC 182
#define GCC_USB3_SEC_PHY_COM_AUX_CLK 183
#define GCC_USB3_SEC_PHY_PIPE_CLK 184
#define GCC_VIDEO_AHB_CLK 185
#define GCC_VIDEO_AXI0_CLK 186
#define GCC_VIDEO_AXI1_CLK 187
#define GCC_VIDEO_XO_CLK 188
#define GPLL0 189
#define GPLL0_OUT_EVEN 190
#define GPLL9 191
#define GCC_UFS_MEM_CLKREF_CLK 154
#define GCC_UFS_PHY_AHB_CLK 155
#define GCC_UFS_PHY_AXI_CLK 156
#define GCC_UFS_PHY_AXI_CLK_SRC 157
#define GCC_UFS_PHY_ICE_CORE_CLK 158
#define GCC_UFS_PHY_ICE_CORE_CLK_SRC 159
#define GCC_UFS_PHY_PHY_AUX_CLK 160
#define GCC_UFS_PHY_PHY_AUX_CLK_SRC 161
#define GCC_UFS_PHY_RX_SYMBOL_0_CLK 162
#define GCC_UFS_PHY_RX_SYMBOL_1_CLK 163
#define GCC_UFS_PHY_TX_SYMBOL_0_CLK 164
#define GCC_UFS_PHY_UNIPRO_CORE_CLK 165
#define GCC_UFS_PHY_UNIPRO_CORE_CLK_SRC 166
#define GCC_USB30_PRIM_MASTER_CLK 167
#define GCC_USB30_PRIM_MASTER_CLK_SRC 168
#define GCC_USB30_PRIM_MOCK_UTMI_CLK 169
#define GCC_USB30_PRIM_MOCK_UTMI_CLK_SRC 170
#define GCC_USB30_PRIM_SLEEP_CLK 171
#define GCC_USB30_SEC_MASTER_CLK 172
#define GCC_USB30_SEC_MASTER_CLK_SRC 173
#define GCC_USB30_SEC_MOCK_UTMI_CLK 174
#define GCC_USB30_SEC_MOCK_UTMI_CLK_SRC 175
#define GCC_USB30_SEC_SLEEP_CLK 176
#define GCC_USB3_PRIM_PHY_AUX_CLK 177
#define GCC_USB3_PRIM_PHY_AUX_CLK_SRC 178
#define GCC_USB3_PRIM_PHY_COM_AUX_CLK 179
#define GCC_USB3_PRIM_PHY_PIPE_CLK 180
#define GCC_USB3_SEC_CLKREF_CLK 181
#define GCC_USB3_SEC_PHY_AUX_CLK 182
#define GCC_USB3_SEC_PHY_AUX_CLK_SRC 183
#define GCC_USB3_SEC_PHY_COM_AUX_CLK 184
#define GCC_USB3_SEC_PHY_PIPE_CLK 185
#define GCC_VIDEO_AHB_CLK 186
#define GCC_VIDEO_AXI0_CLK 187
#define GCC_VIDEO_AXI1_CLK 188
#define GCC_VIDEO_XO_CLK 189
#define GPLL0 190
#define GPLL0_OUT_EVEN 191
#define GPLL9 192
#define PCIE_0_GDSC 0
#define PCIE_1_GDSC 1

View File

@@ -322,6 +322,8 @@ enum req_flag_bits {
__REQ_BACKGROUND, /* background IO */
__REQ_NOWAIT, /* Don't wait if request will block */
__REQ_SORTED = __REQ_RAHEAD, /* elevator knows about this request */
__REQ_URGENT, /* urgent request */
/* command specific flags for REQ_OP_WRITE_ZEROES: */
__REQ_NOUNMAP, /* do not free blocks when zeroing */
@@ -337,6 +339,7 @@ enum req_flag_bits {
#define REQ_SYNC (1ULL << __REQ_SYNC)
#define REQ_META (1ULL << __REQ_META)
#define REQ_PRIO (1ULL << __REQ_PRIO)
#define REQ_URGENT (1ULL << __REQ_URGENT)
#define REQ_NOMERGE (1ULL << __REQ_NOMERGE)
#define REQ_IDLE (1ULL << __REQ_IDLE)
#define REQ_INTEGRITY (1ULL << __REQ_INTEGRITY)

View File

@@ -236,6 +236,9 @@ extern struct devfreq *devfreq_get_devfreq_by_phandle(struct device *dev,
* the governor may consider slowing the frequency down.
* Specify 0 to use the default. Valid value = 0 to 100.
* downdifferential < upthreshold must hold.
* @simple_scaling: Setting this flag will scale the clocks up only if the
* load is above @upthreshold and will scale the clocks
* down only if the load is below @downdifferential.
*
* If the fed devfreq_simple_ondemand_data pointer is NULL to the governor,
* the governor uses the default values.
@@ -243,6 +246,7 @@ extern struct devfreq *devfreq_get_devfreq_by_phandle(struct device *dev,
struct devfreq_simple_ondemand_data {
unsigned int upthreshold;
unsigned int downdifferential;
unsigned int simple_scaling;
};
#endif

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
* Copyright (c) 2013-2016, Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -31,8 +31,15 @@ void ufs_qcom_phy_enable_dev_ref_clk(struct phy *phy);
*/
void ufs_qcom_phy_disable_dev_ref_clk(struct phy *phy);
int ufs_qcom_phy_start_serdes(struct phy *generic_phy);
int ufs_qcom_phy_is_pcs_ready(struct phy *generic_phy);
int ufs_qcom_phy_calibrate_phy(struct phy *generic_phy, bool is_rate_B);
int ufs_qcom_phy_set_tx_lane_enable(struct phy *phy, u32 tx_lanes);
int ufs_qcom_phy_ctrl_rx_linecfg(struct phy *generic_phy, bool ctrl);
void ufs_qcom_phy_save_controller_version(struct phy *phy,
u8 major, u16 minor, u16 step);
const char *ufs_qcom_phy_name(struct phy *phy);
int ufs_qcom_phy_configure_lpm(struct phy *generic_phy, bool enable);
void ufs_qcom_phy_dbg_register_dump(struct phy *generic_phy);
#endif /* PHY_QCOM_UFS_H_ */

View File

@@ -9,6 +9,7 @@
#include <linux/types.h>
#include <linux/scatterlist.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <scsi/scsi_common.h>
#include <scsi/scsi_proto.h>
@@ -253,27 +254,6 @@ static inline int scsi_is_wlun(u64 lun)
#define SCSI_INQ_PQ_NOT_CON 0x01
#define SCSI_INQ_PQ_NOT_CAP 0x03
/*
* Here are some scsi specific ioctl commands which are sometimes useful.
*
* Note that include/linux/cdrom.h also defines IOCTL 0x5300 - 0x5395
*/
/* Used to obtain PUN and LUN info. Conflicts with CDROMAUDIOBUFSIZ */
#define SCSI_IOCTL_GET_IDLUN 0x5382
/* 0x5383 and 0x5384 were used for SCSI_IOCTL_TAGGED_{ENABLE,DISABLE} */
/* Used to obtain the host number of a device. */
#define SCSI_IOCTL_PROBE_HOST 0x5385
/* Used to obtain the bus number for a device */
#define SCSI_IOCTL_GET_BUS_NUMBER 0x5386
/* Used to obtain the PCI location of a device */
#define SCSI_IOCTL_GET_PCI 0x5387
/* Pull a u32 out of a SCSI message (using BE SCSI conventions) */
static inline __u32 scsi_to_u32(__u8 *ptr)
{

View File

@@ -199,6 +199,12 @@ struct scsi_device {
unsigned broken_fua:1; /* Don't set FUA bit */
unsigned lun_in_cdb:1; /* Store LUN bits in CDB[1] */
unsigned unmap_limit_for_ws:1; /* Use the UNMAP limit for WRITE SAME */
unsigned use_rpm_auto:1; /* Enable runtime PM auto suspend */
#define SCSI_DEFAULT_AUTOSUSPEND_DELAY -1
int autosuspend_delay;
/* If non-zero, use timeout (in jiffies) for all commands */
unsigned int timeout_override;
atomic_t disk_events_disable_depth; /* disable depth for disk events */
@@ -455,6 +461,8 @@ extern void sdev_disable_disk_events(struct scsi_device *sdev);
extern void sdev_enable_disk_events(struct scsi_device *sdev);
extern int scsi_vpd_lun_id(struct scsi_device *, char *, size_t);
extern int scsi_vpd_tpg_id(struct scsi_device *, int *);
extern void scsi_set_cmd_timeout_override(struct scsi_device *sdev,
unsigned int timeout);
#ifdef CONFIG_PM
extern int scsi_autopm_get_device(struct scsi_device *);

View File

@@ -654,6 +654,12 @@ struct Scsi_Host {
/* Host responded with short (<36 bytes) INQUIRY result */
unsigned short_inquiry:1;
/*
* Set "DBD" field in mode_sense caching mode page in case it is
* mandatory by LLD standard.
*/
unsigned set_dbd_for_caching:1;
/*
* Optional work queue to be utilized by the transport
*/

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
* Copyright (c) 2013-2018, 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
@@ -54,8 +54,7 @@ UFSCHD_CLK_GATING_STATES;
#define EM(a) { a, #a },
#define EMe(a) { a, #a }
TRACE_EVENT(ufshcd_clk_gating,
DECLARE_EVENT_CLASS(ufshcd_state_change_template,
TP_PROTO(const char *dev_name, int state),
TP_ARGS(dev_name, state),
@@ -70,11 +69,37 @@ TRACE_EVENT(ufshcd_clk_gating,
__entry->state = state;
),
TP_printk("%s: gating state changed to %s",
TP_printk("%s: state changed to %s",
__get_str(dev_name),
__print_symbolic(__entry->state, UFSCHD_CLK_GATING_STATES))
);
DEFINE_EVENT_PRINT(ufshcd_state_change_template, ufshcd_clk_gating,
TP_PROTO(const char *dev_name, int state),
TP_ARGS(dev_name, state),
TP_printk("%s: state changed to %s", __get_str(dev_name),
__print_symbolic(__entry->state,
{ CLKS_OFF, "CLKS_OFF" },
{ CLKS_ON, "CLKS_ON" },
{ REQ_CLKS_OFF, "REQ_CLKS_OFF" },
{ REQ_CLKS_ON, "REQ_CLKS_ON" }))
);
DEFINE_EVENT_PRINT(ufshcd_state_change_template, ufshcd_hibern8_on_idle,
TP_PROTO(const char *dev_name, int state),
TP_ARGS(dev_name, state),
TP_printk("%s: state changed to %s", __get_str(dev_name),
__print_symbolic(__entry->state,
{ HIBERN8_ENTERED, "HIBERN8_ENTER" },
{ HIBERN8_EXITED, "HIBERN8_EXIT" },
{ REQ_HIBERN8_ENTER, "REQ_HIBERN8_ENTER" },
{ REQ_HIBERN8_EXIT, "REQ_HIBERN8_EXIT" }))
);
DEFINE_EVENT(ufshcd_state_change_template, ufshcd_auto_bkops_state,
TP_PROTO(const char *dev_name, int state),
TP_ARGS(dev_name, state));
TRACE_EVENT(ufshcd_clk_scaling,
TP_PROTO(const char *dev_name, const char *state, const char *clk,
@@ -103,26 +128,6 @@ TRACE_EVENT(ufshcd_clk_scaling,
__entry->prev_state, __entry->curr_state)
);
TRACE_EVENT(ufshcd_auto_bkops_state,
TP_PROTO(const char *dev_name, const char *state),
TP_ARGS(dev_name, state),
TP_STRUCT__entry(
__string(dev_name, dev_name)
__string(state, state)
),
TP_fast_assign(
__assign_str(dev_name, dev_name);
__assign_str(state, state);
),
TP_printk("%s: auto bkops - %s",
__get_str(dev_name), __get_str(state))
);
DECLARE_EVENT_CLASS(ufshcd_profiling_template,
TP_PROTO(const char *dev_name, const char *profile_info, s64 time_us,
int err),
@@ -250,10 +255,10 @@ TRACE_EVENT(ufshcd_command,
),
TP_printk(
"%s: %s: tag: %u, DB: 0x%x, size: %d, IS: %u, LBA: %llu, opcode: 0x%x",
__get_str(str), __get_str(dev_name), __entry->tag,
__entry->doorbell, __entry->transfer_len,
__entry->intr, __entry->lba, (u32)__entry->opcode
"%s: %14s: tag: %-2u cmd: 0x%-2x lba: %-9llu size: %-7d DB: 0x%-8x IS: 0x%x",
__get_str(dev_name), __get_str(str), __entry->tag,
(u32)__entry->opcode, __entry->lba, __entry->transfer_len,
__entry->doorbell, __entry->intr
)
);

5
include/uapi/scsi/Kbuild Normal file
View File

@@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
header-y += ufs/
header-y += sg.h
header-y += scsi_ioctl.h

View File

@@ -1,7 +1,9 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#ifndef _SCSI_IOCTL_H
#define _SCSI_IOCTL_H
#include <linux/types.h>
#define SCSI_IOCTL_SEND_COMMAND 1
#define SCSI_IOCTL_TEST_UNIT_READY 2
#define SCSI_IOCTL_BENCHMARK_COMMAND 3
@@ -16,9 +18,25 @@
#define SCSI_REMOVAL_PREVENT 1
#define SCSI_REMOVAL_ALLOW 0
#ifdef __KERNEL__
/*
* Here are some scsi specific ioctl commands which are sometimes useful.
*
* Note that include/linux/cdrom.h also defines IOCTL 0x5300 - 0x5395
*/
struct scsi_device;
/* Used to obtain PUN and LUN info. Conflicts with CDROMAUDIOBUFSIZ */
#define SCSI_IOCTL_GET_IDLUN 0x5382
/* 0x5383 and 0x5384 were used for SCSI_IOCTL_TAGGED_{ENABLE,DISABLE} */
/* Used to obtain the host number of a device. */
#define SCSI_IOCTL_PROBE_HOST 0x5385
/* Used to obtain the bus number for a device */
#define SCSI_IOCTL_GET_BUS_NUMBER 0x5386
/* Used to obtain the PCI location of a device */
#define SCSI_IOCTL_GET_PCI 0x5387
/*
* Structures used for scsi_ioctl et al.
@@ -41,9 +59,12 @@ typedef struct scsi_fctargaddress {
unsigned char host_wwn[8]; // include NULL term.
} Scsi_FCTargAddress;
#ifdef __KERNEL__
struct scsi_device;
int scsi_ioctl_block_when_processing_errors(struct scsi_device *sdev,
int cmd, bool ndelay);
extern int scsi_ioctl(struct scsi_device *, int, void __user *);
#endif /* __KERNEL__ */
#endif /* _SCSI_IOCTL_H */

View File

@@ -1,8 +1,9 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#ifndef _SCSI_GENERIC_H
#define _SCSI_GENERIC_H
#include <linux/compiler.h>
#include <linux/param.h>
/*
* History:
@@ -33,30 +34,27 @@
extern int sg_big_buff; /* for sysctl */
#endif
typedef struct sg_iovec /* same structure as used by readv() Linux system */
{ /* call. It defines one scatter-gather element. */
void __user *iov_base; /* Starting address */
size_t iov_len; /* Length in bytes */
} sg_iovec_t;
typedef struct sg_io_hdr
{
typedef struct sg_io_hdr {
int interface_id; /* [i] 'S' for SCSI generic (required) */
int dxfer_direction; /* [i] data transfer direction */
unsigned char cmd_len; /* [i] SCSI command length */
unsigned char mx_sb_len; /* [i] max length to write to sbp */
unsigned short iovec_count; /* [i] 0 implies no scatter gather */
unsigned int dxfer_len; /* [i] byte count of data transfer */
void __user *dxferp; /* [i], [*io] points to data transfer memory
or scatter gather list */
void __user *dxferp; /* [i], [*io] points to data transfer memory */
/* or scatter gather list */
unsigned char __user *cmdp; /* [i], [*i] points to command to perform */
void __user *sbp; /* [i], [*o] points to sense_buffer memory */
void __user *sbp; /* [i], [*o] points to sense_buffer meimory */
unsigned int timeout; /* [i] MAX_UINT->no timeout (unit: millisec) */
unsigned int flags; /* [i] 0 -> default, see SG_FLAG... */
int pack_id; /* [i->o] unused internally (normally) */
void __user * usr_ptr; /* [i->o] unused internally */
void __user *usr_ptr; /* [i->o] unused internally */
unsigned char status; /* [o] scsi status */
unsigned char masked_status;/* [o] shifted, masked scsi status */
unsigned char msg_status; /* [o] messaging level data (optional) */
@@ -120,8 +118,9 @@ typedef struct sg_req_info { /* used by SG_GET_REQUEST_TABLE ioctl() */
char problem; /* 0 -> no problem detected, 1 -> error to report */
int pack_id; /* pack_id associated with request */
void __user *usr_ptr; /* user provided pointer (in new interface) */
unsigned int duration; /* millisecs elapsed since written (req_state==1)
or request duration (req_state==2) */
unsigned int duration; /* millisecs elapsed since written */
/* (req_state==1) or request duration */
/* (req_state==2) */
int unused;
} sg_req_info_t; /* 20 bytes long on i386 */
@@ -198,6 +197,7 @@ typedef struct sg_req_info { /* used by SG_GET_REQUEST_TABLE ioctl() */
#define SG_DEFAULT_RETRIES 0
/* Defaults, commented if they differ from original sg driver */
#define SG_DEF_FORCE_LOW_DMA 0 /* was 1 -> memory below 16MB on i386 */
#define SG_DEF_FORCE_PACK_ID 0
#define SG_DEF_KEEP_ORPHAN 0
#define SG_DEF_RESERVED_SIZE SG_SCATTER_SZ /* load time option */
@@ -220,8 +220,7 @@ typedef struct sg_req_info Sg_req_info;
#define SG_MAX_SENSE 16 /* this only applies to the sg_header interface */
struct sg_header
{
struct sg_header {
int pack_len; /* [o] reply_len (ie useless), ignored as input */
int reply_len; /* [i] max length of expected reply (inc. sg_header) */
int pack_id; /* [io] id number of packet (use ints >= 0) */
@@ -232,10 +231,9 @@ struct sg_header
unsigned int host_status:8; /* [o] host status (see "DID" codes) */
unsigned int driver_status:8; /* [o] driver status+suggestion */
unsigned int other_flags:10; /* unused */
unsigned char sense_buffer[SG_MAX_SENSE]; /* [o] Output in 3 cases:
when target_status is CHECK_CONDITION or
when target_status is COMMAND_TERMINATED or
when (driver_status & DRIVER_SENSE) is true. */
unsigned char sense_buffer[SG_MAX_SENSE]; /* [o] Output in 3 cases: */
/* when target_status is CHECK_CONDITION or when target_status is */
/* COMMAND_TERMINATED or when (driver_status & DRIVER_SENSE) is true. */
}; /* This structure is 36 bytes long on i386 */

View File

@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
# UAPI Header export list
header-y += ioctl.h
header-y += ufs.h

View File

@@ -0,0 +1,59 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#ifndef UAPI_UFS_IOCTL_H_
#define UAPI_UFS_IOCTL_H_
#include <linux/types.h>
/*
* IOCTL opcode for ufs queries has the following opcode after
* SCSI_IOCTL_GET_PCI
*/
#define UFS_IOCTL_QUERY 0x5388
/**
* struct ufs_ioctl_query_data - used to transfer data to and from user via
* ioctl
* @opcode: type of data to query (descriptor/attribute/flag)
* @idn: id of the data structure
* @buf_size: number of allocated bytes/data size on return
* @buffer: data location
*
* Received: buffer and buf_size (available space for transferred data)
* Submitted: opcode, idn, length, buf_size
*/
struct ufs_ioctl_query_data {
/*
* User should select one of the opcode defined in "enum query_opcode".
* Please check include/uapi/scsi/ufs/ufs.h for the definition of it.
* Note that only UPIU_QUERY_OPCODE_READ_DESC,
* UPIU_QUERY_OPCODE_READ_ATTR & UPIU_QUERY_OPCODE_READ_FLAG are
* supported as of now. All other query_opcode would be considered
* invalid.
* As of now only read query operations are supported.
*/
__u32 opcode;
/*
* User should select one of the idn from "enum flag_idn" or "enum
* attr_idn" or "enum desc_idn" based on whether opcode above is
* attribute, flag or descriptor.
* Please check include/uapi/scsi/ufs/ufs.h for the definition of it.
*/
__u8 idn;
/*
* User should specify the size of the buffer (buffer[0] below) where
* it wants to read the query data (attribute/flag/descriptor).
* As we might end up reading less data then what is specified in
* buf_size. So we are updating buf_size to what exactly we have read.
*/
__u16 buf_size;
/*
* placeholder for the start of the data buffer where kernel will copy
* the query data (attribute/flag/descriptor) read from the UFS device
* Note:
* For Read/Write Attribute you will have to allocate 4 bytes
* For Read/Write Flag you will have to allocate 1 byte
*/
__u8 buffer[0];
};
#endif /* UAPI_UFS_IOCTL_H_ */

View File

@@ -0,0 +1,79 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#ifndef UAPI_UFS_H_
#define UAPI_UFS_H_
#define MAX_QUERY_IDN 0x12
/* Flag idn for Query Requests*/
enum flag_idn {
QUERY_FLAG_IDN_FDEVICEINIT = 0x01,
QUERY_FLAG_IDN_PERMANENT_WPE = 0x02,
QUERY_FLAG_IDN_PWR_ON_WPE = 0x03,
QUERY_FLAG_IDN_BKOPS_EN = 0x04,
QUERY_FLAG_IDN_LIFE_SPAN_MODE_ENABLE = 0x05,
QUERY_FLAG_IDN_PURGE_ENABLE = 0x06,
QUERY_FLAG_IDN_RESERVED2 = 0x07,
QUERY_FLAG_IDN_FPHYRESOURCEREMOVAL = 0x08,
QUERY_FLAG_IDN_BUSY_RTC = 0x09,
QUERY_FLAG_IDN_RESERVED3 = 0x0A,
QUERY_FLAG_IDN_PERMANENTLY_DISABLE_FW_UPDATE = 0x0B,
};
/* Attribute idn for Query requests */
enum attr_idn {
QUERY_ATTR_IDN_BOOT_LU_EN = 0x00,
QUERY_ATTR_IDN_RESERVED = 0x01,
QUERY_ATTR_IDN_POWER_MODE = 0x02,
QUERY_ATTR_IDN_ACTIVE_ICC_LVL = 0x03,
QUERY_ATTR_IDN_OOO_DATA_EN = 0x04,
QUERY_ATTR_IDN_BKOPS_STATUS = 0x05,
QUERY_ATTR_IDN_PURGE_STATUS = 0x06,
QUERY_ATTR_IDN_MAX_DATA_IN = 0x07,
QUERY_ATTR_IDN_MAX_DATA_OUT = 0x08,
QUERY_ATTR_IDN_DYN_CAP_NEEDED = 0x09,
QUERY_ATTR_IDN_REF_CLK_FREQ = 0x0A,
QUERY_ATTR_IDN_CONF_DESC_LOCK = 0x0B,
QUERY_ATTR_IDN_MAX_NUM_OF_RTT = 0x0C,
QUERY_ATTR_IDN_EE_CONTROL = 0x0D,
QUERY_ATTR_IDN_EE_STATUS = 0x0E,
QUERY_ATTR_IDN_SECONDS_PASSED = 0x0F,
QUERY_ATTR_IDN_CNTX_CONF = 0x10,
QUERY_ATTR_IDN_CORR_PRG_BLK_NUM = 0x11,
QUERY_ATTR_IDN_RESERVED2 = 0x12,
QUERY_ATTR_IDN_RESERVED3 = 0x13,
QUERY_ATTR_IDN_FFU_STATUS = 0x14,
QUERY_ATTR_IDN_PSA_STATE = 0x15,
QUERY_ATTR_IDN_PSA_DATA_SIZE = 0x16,
};
#define QUERY_ATTR_IDN_BOOT_LU_EN_MAX 0x02
/* Descriptor idn for Query requests */
enum desc_idn {
QUERY_DESC_IDN_DEVICE = 0x0,
QUERY_DESC_IDN_CONFIGURATION = 0x1,
QUERY_DESC_IDN_UNIT = 0x2,
QUERY_DESC_IDN_RFU_0 = 0x3,
QUERY_DESC_IDN_INTERCONNECT = 0x4,
QUERY_DESC_IDN_STRING = 0x5,
QUERY_DESC_IDN_RFU_1 = 0x6,
QUERY_DESC_IDN_GEOMETRY = 0x7,
QUERY_DESC_IDN_POWER = 0x8,
QUERY_DESC_IDN_HEALTH = 0x9,
QUERY_DESC_IDN_MAX,
};
/* UTP QUERY Transaction Specific Fields OpCode */
enum query_opcode {
UPIU_QUERY_OPCODE_NOP = 0x0,
UPIU_QUERY_OPCODE_READ_DESC = 0x1,
UPIU_QUERY_OPCODE_WRITE_DESC = 0x2,
UPIU_QUERY_OPCODE_READ_ATTR = 0x3,
UPIU_QUERY_OPCODE_WRITE_ATTR = 0x4,
UPIU_QUERY_OPCODE_READ_FLAG = 0x5,
UPIU_QUERY_OPCODE_SET_FLAG = 0x6,
UPIU_QUERY_OPCODE_CLEAR_FLAG = 0x7,
UPIU_QUERY_OPCODE_TOGGLE_FLAG = 0x8,
UPIU_QUERY_OPCODE_MAX,
};
#endif /* UAPI_UFS_H_ */

View File

@@ -1608,6 +1608,20 @@ config FAIL_MMC_REQUEST
and to test how the mmc host driver handles retries from
the block device.
config UFS_FAULT_INJECTION
bool "Fault-injection capability for UFS IO"
select DEBUG_FS
depends on FAULT_INJECTION && SCSI_UFSHCD
help
Provide fault-injection capability for UFS IO.
This will make the UFS host controller driver to randomly
abort ongoing commands in the host controller, update OCS
field according to the injected fatal error and can also
forcefully hang the command indefinitely till upper layer
timeout occurs. This is useful to test error handling in
the UFS controller driver and test how the driver handles
the retries from block/SCSI mid layer.
config FAULT_INJECTION_STACKTRACE_FILTER
bool "stacktrace filter for fault-injection capabilities"
depends on FAULT_INJECTION_DEBUG_FS && STACKTRACE_SUPPORT