diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-06-21 10:29:45 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-06-21 10:29:45 -0700 |
| commit | 8cd8cf7a07e5d141b0c75ce6cf470630e11aa11a (patch) | |
| tree | 47e9e5f4950dde81990d1c17920e427e29ecb9e3 /drivers/ufs | |
| parent | 1bc18858358d7a4e263a63b59a12cbfd25c16670 (diff) | |
| parent | 4f87e9068bf3aaf45f226261d5efd50bec42c12c (diff) | |
| download | linux-next-8cd8cf7a07e5d141b0c75ce6cf470630e11aa11a.tar.gz linux-next-8cd8cf7a07e5d141b0c75ce6cf470630e11aa11a.zip | |
Merge tag 'scsi-misc' of git://git.kernel.org/pub/scm/linux/kernel/git/jejb/scsi
Pull SCSI updates from James Bottomley:
"Only ufs driver updates this time, apart from which this is just an
assortment of bug fixes and AI assisted changes.
The biggest other change is the reversion of the sas_user_scan patch
which supported a mpi3mr NVME behaviour but caused major issues for
other sas controllers. The next biggest is the removal of target reset
in tcm_loop.c"
* tag 'scsi-misc' of git://git.kernel.org/pub/scm/linux/kernel/git/jejb/scsi: (56 commits)
scsi: target: Remove tcm_loop target reset handling
scsi: lpfc: Fix spelling mistakes in comments
scsi: ufs: ufs-pci: Add AMD device ID support
scsi: ufs: core: Handle PM commands timeout before SCSI EH
scsi: devinfo: Broaden Promise VTrak E310/E610 identification
scsi: target: Use constant-time crypto_memneq() for CHAP digests
scsi: target: Fix hexadecimal CHAP_I handling
scsi: scsi_debug: Fix one-partition tape setup bounds
scsi: ufs: qcom: dt-bindings: Document the Hawi UFS controller
scsi: mailmap: Update Avri Altman's email address
scsi: ufs: Remove redundant vops NULL check and trivial wrapper
scsi: ufs: Remove unnecessary return in void vops wrappers
scsi: ufs: Fix wrong value printed in unexpected UPIU response case
scsi: ufs: core: Fix NULL pointer dereference in scsi_cmd_priv() calls
scsi: megaraid_mbox: Avoid double kfree()
scsi: pm8001: Fix error code in non_fatal_log_show()
scsi: lpfc: Turn lpfc_queue q_pgs into a flexible array
scsi: ufs: core: Skip link param validation when lanes_per_direction is unset
scsi: sas: Skip opt_sectors when DMA reports no real optimization hint
scsi: Revert "scsi: Fix sas_user_scan() to handle wildcard and multi-channel scans"
...
Diffstat (limited to 'drivers/ufs')
| -rw-r--r-- | drivers/ufs/core/ufs-mcq.c | 7 | ||||
| -rw-r--r-- | drivers/ufs/core/ufs-sysfs.c | 30 | ||||
| -rw-r--r-- | drivers/ufs/core/ufs-txeq.c | 295 | ||||
| -rw-r--r-- | drivers/ufs/core/ufshcd-priv.h | 11 | ||||
| -rw-r--r-- | drivers/ufs/core/ufshcd.c | 296 | ||||
| -rw-r--r-- | drivers/ufs/host/Kconfig | 2 | ||||
| -rw-r--r-- | drivers/ufs/host/tc-dwc-g210-pci.c | 4 | ||||
| -rw-r--r-- | drivers/ufs/host/ufs-exynos.c | 110 | ||||
| -rw-r--r-- | drivers/ufs/host/ufs-qcom.c | 13 | ||||
| -rw-r--r-- | drivers/ufs/host/ufs-qcom.h | 11 | ||||
| -rw-r--r-- | drivers/ufs/host/ufshcd-pci.c | 30 |
11 files changed, 659 insertions, 150 deletions
diff --git a/drivers/ufs/core/ufs-mcq.c b/drivers/ufs/core/ufs-mcq.c index c1b1d67a1ddc..13b60a2d06db 100644 --- a/drivers/ufs/core/ufs-mcq.c +++ b/drivers/ufs/core/ufs-mcq.c @@ -637,7 +637,7 @@ static bool ufshcd_mcq_sqe_search(struct ufs_hba *hba, struct ufs_hw_queue *hwq, int task_tag) { struct scsi_cmnd *cmd = ufshcd_tag_to_cmd(hba, task_tag); - struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd); + struct ufshcd_lrb *lrbp; struct utp_transfer_req_desc *utrd; __le64 cmd_desc_base_addr; bool ret = false; @@ -647,6 +647,11 @@ static bool ufshcd_mcq_sqe_search(struct ufs_hba *hba, if (hba->quirks & UFSHCD_QUIRK_MCQ_BROKEN_RTC) return true; + if (!cmd) + return false; + + lrbp = scsi_cmd_priv(cmd); + mutex_lock(&hwq->sq_mutex); ufshcd_mcq_sq_stop(hba, hwq); diff --git a/drivers/ufs/core/ufs-sysfs.c b/drivers/ufs/core/ufs-sysfs.c index 99af3c73f1af..d9dc4cc3452e 100644 --- a/drivers/ufs/core/ufs-sysfs.c +++ b/drivers/ufs/core/ufs-sysfs.c @@ -594,8 +594,13 @@ static ssize_t device_lvl_exception_id_show(struct device *dev, u64 exception_id; int err; + if (hba->dev_info.wspecversion < 0x410) + return -EOPNOTSUPP; + ufshcd_rpm_get_sync(hba); - err = ufshcd_read_device_lvl_exception_id(hba, &exception_id); + err = ufshcd_query_attr_qword(hba, UPIU_QUERY_OPCODE_READ_ATTR, + QUERY_ATTR_IDN_DEV_LVL_EXCEPTION_ID, + 0, 0, &exception_id); ufshcd_rpm_put_sync(hba); if (err) @@ -1670,6 +1675,12 @@ static inline bool ufshcd_is_wb_attrs(enum attr_idn idn) idn <= QUERY_ATTR_IDN_CURR_WB_BUFF_SIZE; } +static inline bool ufshcd_is_qword_attr(enum attr_idn idn) +{ + return idn == QUERY_ATTR_IDN_TIMESTAMP || + idn == QUERY_ATTR_IDN_DEV_LVL_EXCEPTION_ID; +} + static int wb_read_resize_attrs(struct ufs_hba *hba, enum attr_idn idn, u32 *attr_val) { @@ -1736,6 +1747,7 @@ static ssize_t _name##_show(struct device *dev, \ struct device_attribute *attr, char *buf) \ { \ struct ufs_hba *hba = dev_get_drvdata(dev); \ + u64 qword_value; \ u32 value; \ int ret; \ u8 index = 0; \ @@ -1748,14 +1760,24 @@ static ssize_t _name##_show(struct device *dev, \ if (ufshcd_is_wb_attrs(QUERY_ATTR_IDN##_uname)) \ index = ufshcd_wb_get_query_index(hba); \ ufshcd_rpm_get_sync(hba); \ - ret = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR, \ - QUERY_ATTR_IDN##_uname, index, 0, &value); \ + if (ufshcd_is_qword_attr(QUERY_ATTR_IDN##_uname)) \ + ret = ufshcd_query_attr_qword(hba, \ + UPIU_QUERY_OPCODE_READ_ATTR, \ + QUERY_ATTR_IDN##_uname, \ + index, 0, &qword_value); \ + else \ + ret = ufshcd_query_attr(hba, \ + UPIU_QUERY_OPCODE_READ_ATTR, \ + QUERY_ATTR_IDN##_uname, index, 0, &value); \ ufshcd_rpm_put_sync(hba); \ if (ret) { \ ret = -EINVAL; \ goto out; \ } \ - ret = sysfs_emit(buf, "0x%08X\n", value); \ + if (ufshcd_is_qword_attr(QUERY_ATTR_IDN##_uname)) \ + ret = sysfs_emit(buf, "0x%016llX\n", qword_value); \ + else \ + ret = sysfs_emit(buf, "0x%08X\n", value); \ out: \ up(&hba->host_sem); \ return ret; \ diff --git a/drivers/ufs/core/ufs-txeq.c b/drivers/ufs/core/ufs-txeq.c index b2dc89124353..aa64f2bf4f1e 100644 --- a/drivers/ufs/core/ufs-txeq.c +++ b/drivers/ufs/core/ufs-txeq.c @@ -14,6 +14,9 @@ #include <ufs/unipro.h> #include "ufshcd-priv.h" +#define TX_EQ_SETTING_MASK 0x7 +#define TX_EQ_SETTINGS_VALID_BIT BIT(15) + static bool use_adaptive_txeq; module_param(use_adaptive_txeq, bool, 0644); MODULE_PARM_DESC(use_adaptive_txeq, "Find and apply optimal TX Equalization settings before changing Power Mode (default: false)"); @@ -40,6 +43,28 @@ static bool txeq_presets_selected[UFS_TX_EQ_PRESET_MAX] = {[0 ... (UFS_TX_EQ_PRE module_param_array(txeq_presets_selected, bool, NULL, 0644); MODULE_PARM_DESC(txeq_presets_selected, "Use only the selected Presets out of the 8 TX Equalization Presets for TX EQTR"); +static int txeq_setting_sel_set(const char *val, const struct kernel_param *kp) +{ + return param_set_uint_minmax(val, kp, 0, 1); +} + +static const struct kernel_param_ops txeq_setting_sel_ops = { + .set = txeq_setting_sel_set, + .get = param_get_uint, +}; + +static unsigned int txeq_setting_sel; +module_param_cb(txeq_setting_sel, &txeq_setting_sel_ops, &txeq_setting_sel, 0644); +MODULE_PARM_DESC(txeq_setting_sel, "The qTxEQGnSettings and wTxEQGnSettingsExt Attributes selector used to retrieve and store TX Equalization settings"); + +static bool retrieve_txeq_setting = true; +module_param(retrieve_txeq_setting, bool, 0644); +MODULE_PARM_DESC(retrieve_txeq_setting, "Retrieve TX Equalization settings from qTxEQGnSettings and wTxEQGnSettingsExt Attributes (default: true)"); + +static bool store_txeq_setting = true; +module_param(store_txeq_setting, bool, 0644); +MODULE_PARM_DESC(store_txeq_setting, "Store the optimal TX Equalization settings to qTxEQGnSettings and wTxEQGnSettingsExt Attributes (default: true)"); + /* * ufs_tx_eq_preset - Table of minimum required list of presets. * @@ -117,6 +142,126 @@ static const u32 pa_tx_eq_setting[UFS_HS_GEAR_MAX] = { PA_TXEQG6SETTING }; +/* + * Decode Device TX Equalization PreShoot value based on qTxEQGnSettings bit assignment: + * bit[3:0]: Device TX Logical LANE 0 PreShoot + * bit[7:4]: Device TX Logical LANE 1 PreShoot + */ +static inline u8 tx_eq_device_preshoot_decode(u64 eq, u8 lane) +{ + return (u8)((eq >> (lane * TX_HS_PRESHOOT_SHIFT)) & TX_EQ_SETTING_MASK); +} + +/* + * Decode Device TX Equalization DeEmphasis value based on qTxEQGnSettings bit assignment: + * bit[19:16]: Device TX Logical LANE 0 DeEmphasis + * bit[23:20]: Device TX Logical LANE 1 DeEmphasis + */ +static inline u8 tx_eq_device_deemphasis_decode(u64 eq, u8 lane) +{ + return (u8)((eq >> (lane * TX_HS_DEEMPHASIS_SHIFT + 16)) & TX_EQ_SETTING_MASK); +} + +/* + * Decode Host TX Equalization PreShoot value based on qTxEQGnSettings bit assignment: + * bit[35:32]: Host TX Logical LANE 0 PreShoot + * bit[39:36]: Host TX Logical LANE 1 PreShoot + */ +static inline u8 tx_eq_host_preshoot_decode(u64 eq, u8 lane) +{ + return (u8)((eq >> (lane * TX_HS_PRESHOOT_SHIFT + 32)) & TX_EQ_SETTING_MASK); +} + +/* + * Decode Host TX Equalization DeEmphasis value based on qTxEQGnSettings bit assignment: + * bit[51:48]: Host TX Logical LANE 0 DeEmphasis + * bit[55:52]: Host TX Logical LANE 1 DeEmphasis + */ +static inline u8 tx_eq_host_deemphasis_decode(u64 eq, u8 lane) +{ + return (u8)((eq >> (lane * TX_HS_DEEMPHASIS_SHIFT + 48)) & TX_EQ_SETTING_MASK); +} + +/* + * Decode Device TX precode_en indication based on wTxEQGnSettingsExt bit assignment: + * bit[0]: PreCodeEn for Device TX Logical LANE 0 + * bit[1]: PreCodeEn for Device TX Logical LANE 1 + */ +static inline bool tx_eq_device_precode_en_decode(u16 eq_ext, u8 lane) +{ + return eq_ext & BIT(lane); +} + +/* + * Decode Host TX precode_en indication based on wTxEQGnSettingsExt bit assignment: + * bit[4]: PreCodeEn for Device RX Logical LANE 0 + * bit[5]: PreCodeEn for Device RX Logical LANE 1 + */ +static inline bool tx_eq_host_precode_en_decode(u16 eq_ext, u8 lane) +{ + return eq_ext & BIT(lane + 4); +} + +/* + * Encode Device TX Equalization PreShoot value based on qTxEQGnSettings bit assignment: + * bit[3:0]: Device TX Logical LANE 0 PreShoot + * bit[7:4]: Device TX Logical LANE 1 PreShoot + */ +static inline u64 tx_eq_device_preshoot_encode(u64 val, u8 lane) +{ + return (val & TX_EQ_SETTING_MASK) << (lane * TX_HS_PRESHOOT_SHIFT); +} + +/* + * Encode Device TX Equalization DeEmphasis value based on qTxEQGnSettings bit assignment: + * bit[19:16]: Device TX Logical LANE 0 DeEmphasis + * bit[23:20]: Device TX Logical LANE 1 DeEmphasis + */ +static inline u64 tx_eq_device_deemphasis_encode(u64 val, u8 lane) +{ + return (val & TX_EQ_SETTING_MASK) << (lane * TX_HS_DEEMPHASIS_SHIFT + 16); +} + +/* + * Encode Host TX Equalization PreShoot value based on qTxEQGnSettings bit assignment: + * bit[35:32]: Host TX Logical LANE 0 PreShoot + * bit[39:36]: Host TX Logical LANE 1 PreShoot + */ +static inline u64 tx_eq_host_preshoot_encode(u64 val, u8 lane) +{ + return (val & TX_EQ_SETTING_MASK) << (lane * TX_HS_PRESHOOT_SHIFT + 32); +} + +/* + * Encode Host TX Equalization DeEmphasis value based on qTxEQGnSettings bit assignment: + * bit[51:48]: Host TX Logical LANE 0 DeEmphasis + * bit[55:52]: Host TX Logical LANE 1 DeEmphasis + */ +static inline u64 tx_eq_host_deemphasis_encode(u64 val, u8 lane) +{ + return (val & TX_EQ_SETTING_MASK) << (lane * TX_HS_DEEMPHASIS_SHIFT + 48); +} + +/* + * Encode Device precode_en based on wTxEQGnSettingsExt bit assignment: + * bit[0]: PreCodeEn for Device TX Logical LANE 0 + * bit[1]: PreCodeEn for Device TX Logical LANE 1 + */ +static inline u16 tx_eq_device_precode_en_encode(bool en, u8 lane) +{ + return (u16)en << lane; +} + +/* + * Encode Host precode_en based on wTxEQGnSettingsExt bit assignment: + * bit[4]: PreCodeEn for Device RX Logical LANE 0 + * bit[5]: PreCodeEn for Device RX Logical LANE 1 + */ +static inline u16 tx_eq_host_precode_en_encode(bool en, u8 lane) +{ + return (u16)en << (lane + 4); +} + /** * ufshcd_configure_precoding - Configure Pre-Coding for all active lanes * @hba: per adapter instance @@ -740,7 +885,9 @@ static int ufshcd_setup_tx_eqtr_adapt_length(struct ufs_hba *hba, if (adapt_l0l1l2l3_cap_local > ADAPT_L0L1L2L3_LENGTH_MAX) { dev_err(hba->dev, "local RX_HS_G%u_ADAPT_INITIAL_L0L1L2L3_CAP (0x%x) exceeds MAX\n", gear, adapt_l0l1l2l3_cap_local); - return -EINVAL; + + if (!(hba->quirks & UFSHCD_QUIRK_EXTENDED_TX_EQTR_ADAPT_LENGTH_L0L1L2L3)) + return -EINVAL; } ret = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PEERRXHSG6ADAPTINITIALL0L1L2L3), @@ -751,7 +898,9 @@ static int ufshcd_setup_tx_eqtr_adapt_length(struct ufs_hba *hba, if (adapt_l0l1l2l3_cap_peer > ADAPT_L0L1L2L3_LENGTH_MAX) { dev_err(hba->dev, "peer RX_HS_G%u_ADAPT_INITIAL_L0L1L2L3_CAP (0x%x) exceeds MAX\n", gear, adapt_l0l1l2l3_cap_peer); - return -EINVAL; + + if (!(hba->quirks & UFSHCD_QUIRK_EXTENDED_TX_EQTR_ADAPT_LENGTH_L0L1L2L3)) + return -EINVAL; } t_adapt_l0l1l2l3_local = adapt_cap_to_t_adapt_l0l1l2l3(adapt_l0l1l2l3_cap_local); @@ -1164,6 +1313,7 @@ int ufshcd_config_tx_eq_settings(struct ufs_hba *hba, /* Mark TX Equalization settings as valid */ params->is_valid = true; + params->is_trained = true; params->is_applied = false; } @@ -1291,3 +1441,144 @@ out: return ret; } + +/** + * ufshcd_extract_tx_eq_settings_attrs - Extract TX Equalization settings from UFS attributes + * @hba: per adapter instance + * @gear: target gear + * + * This function extracts previously stored TX Equalization settings from UFS + * attributes qTxEQGnSettings and wTxEQGnSettingsExt. These attributes contain + * the optimal TX Equalization parameters (PreShoot, DeEmphasis, and PreCoding + * enable) that were determined during a previous EQTR procedure. + * + * The function reads: + * 1. qTxEQGnSettings (64-bit): Main attribute containing PreShoot and + * DeEmphasis values for both host and device TX lanes + * 2. wTxEQGnSettingsExt (16-bit): Extended attribute containing PreCoding + * enable flags and validity indicator + */ +static void ufshcd_extract_tx_eq_settings_attrs(struct ufs_hba *hba, u8 gear) +{ + struct ufshcd_tx_eq_params *params; + u32 lane, eq_ext; + int ret; + u64 eq; + + ret = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR, + QUERY_ATTR_IDN_TX_EQ_GN_SETTINGS_EXT, gear - 1, + (u8)txeq_setting_sel, &eq_ext); + if (ret) + return; + + dev_dbg(hba->dev, "%s: HS-G%u wTxEQGnSettingsExt (Selector %u) = 0x%08x\n", + __func__, gear, txeq_setting_sel, eq_ext); + + if (!(eq_ext & TX_EQ_SETTINGS_VALID_BIT)) + return; + + ret = ufshcd_query_attr_qword(hba, UPIU_QUERY_OPCODE_READ_ATTR, + QUERY_ATTR_IDN_TX_EQ_GN_SETTINGS, + gear - 1, (u8)txeq_setting_sel, &eq); + if (ret) + return; + + dev_dbg(hba->dev, "%s: HS-G%u qTxEQGnSettings (Selector %u) = 0x%016llx\n", + __func__, gear, txeq_setting_sel, eq); + + params = &hba->tx_eq_params[gear - 1]; + + for (lane = 0; lane < UFS_MAX_LANES; lane++) { + params->host[lane].preshoot = tx_eq_host_preshoot_decode(eq, lane); + params->host[lane].deemphasis = tx_eq_host_deemphasis_decode(eq, lane); + params->host[lane].precode_en = tx_eq_host_precode_en_decode(eq_ext, lane); + + params->device[lane].preshoot = tx_eq_device_preshoot_decode(eq, lane); + params->device[lane].deemphasis = tx_eq_device_deemphasis_decode(eq, lane); + params->device[lane].precode_en = tx_eq_device_precode_en_decode(eq_ext, lane); + } + + params->is_valid = true; +} + +void ufshcd_retrieve_tx_eq_settings(struct ufs_hba *hba) +{ + u8 gear = (u8)adaptive_txeq_gear; + + if (!hba->max_pwr_info.is_valid || !ufshcd_is_tx_eq_supported(hba) || + !use_adaptive_txeq || !retrieve_txeq_setting) + return; + + for (; gear <= UFS_HS_GEAR_MAX; gear++) + ufshcd_extract_tx_eq_settings_attrs(hba, gear); +} + +/** + * ufshcd_update_tx_eq_settings_attrs - Update TX EQ settings in UFS attributes + * @hba: per adapter instance + * @gear: target gear + * + * This function stores the optimal TX Equalization settings obtained from + * TX EQTR procedure into UFS device attributes for future fast-path retrieval. + * The settings are stored in two complementary attributes: + * + * 1. qTxEQGnSettings (64-bit): Main attribute containing PreShoot and + * DeEmphasis values for both host and device TX lanes + * 2. wTxEQGnSettingsExt (16-bit): Extended attribute containing PreCoding + * enable flags and validity indicator + */ +static void ufshcd_update_tx_eq_settings_attrs(struct ufs_hba *hba, u8 gear) +{ + struct ufshcd_tx_eq_params *params; + u32 lane, eq_ext = 0; + u64 eq = 0; + int ret; + + params = &hba->tx_eq_params[gear - 1]; + if (!params->is_valid || !params->is_trained) + return; + + for (lane = 0; lane < UFS_MAX_LANES; lane++) { + eq |= tx_eq_host_preshoot_encode((u64)params->host[lane].preshoot, lane); + eq |= tx_eq_host_deemphasis_encode((u64)params->host[lane].deemphasis, lane); + eq_ext |= tx_eq_host_precode_en_encode(params->host[lane].precode_en, lane); + + eq |= tx_eq_device_preshoot_encode((u64)params->device[lane].preshoot, lane); + eq |= tx_eq_device_deemphasis_encode((u64)params->device[lane].deemphasis, lane); + eq_ext |= tx_eq_device_precode_en_encode(params->device[lane].precode_en, lane); + } + + /* Set validity flag to indicate valid settings are stored */ + eq_ext |= TX_EQ_SETTINGS_VALID_BIT; + + /* Write qTxEQGnSettings */ + ret = ufshcd_query_attr_qword(hba, UPIU_QUERY_OPCODE_WRITE_ATTR, + QUERY_ATTR_IDN_TX_EQ_GN_SETTINGS, + gear - 1, (u8)txeq_setting_sel, &eq); + if (ret) + return; + + /* Write wTxEQGnSettingsExt */ + ret = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_WRITE_ATTR, + QUERY_ATTR_IDN_TX_EQ_GN_SETTINGS_EXT, gear - 1, + (u8)txeq_setting_sel, &eq_ext); + if (ret) + return; + + dev_dbg(hba->dev, "%s: Saved HS-G%u qTxEQGnSettings (Selector %u) = 0x%016llx\n", + __func__, gear, txeq_setting_sel, eq); + dev_dbg(hba->dev, "%s: Saved HS-G%u wTxEQGnSettingsExt (Selector %u) = 0x%08x\n", + __func__, gear, txeq_setting_sel, eq_ext); +} + +void ufshcd_store_tx_eq_settings(struct ufs_hba *hba) +{ + u8 gear = (u8)adaptive_txeq_gear; + + if (!hba->max_pwr_info.is_valid || !ufshcd_is_tx_eq_supported(hba) || + !use_adaptive_txeq || !store_txeq_setting) + return; + + for (; gear <= UFS_HS_GEAR_MAX; gear++) + ufshcd_update_tx_eq_settings_attrs(hba, gear); +} diff --git a/drivers/ufs/core/ufshcd-priv.h b/drivers/ufs/core/ufshcd-priv.h index 0a72148cb053..e55c2a02c1f5 100644 --- a/drivers/ufs/core/ufshcd-priv.h +++ b/drivers/ufs/core/ufshcd-priv.h @@ -60,6 +60,8 @@ int ufshcd_query_attr_retry(struct ufs_hba *hba, enum query_opcode opcode, u32 *attr_val); int ufshcd_query_attr(struct ufs_hba *hba, enum query_opcode opcode, enum attr_idn idn, u8 index, u8 selector, u32 *attr_val); +int ufshcd_query_attr_qword(struct ufs_hba *hba, enum query_opcode opcode, + enum attr_idn idn, u8 index, u8 sel, u64 *attr_val); int ufshcd_query_flag(struct ufs_hba *hba, enum query_opcode opcode, enum flag_idn idn, u8 index, bool *flag_res); void ufshcd_auto_hibern8_update(struct ufs_hba *hba, u32 ahit); @@ -106,7 +108,6 @@ int ufshcd_exec_raw_upiu_cmd(struct ufs_hba *hba, enum query_opcode desc_op); int ufshcd_wb_toggle(struct ufs_hba *hba, bool enable); -int ufshcd_read_device_lvl_exception_id(struct ufs_hba *hba, u64 *exception_id); int ufshcd_uic_tx_eqtr(struct ufs_hba *hba, int gear); void ufshcd_apply_valid_tx_eq_settings(struct ufs_hba *hba); @@ -117,6 +118,8 @@ void ufshcd_print_tx_eq_params(struct ufs_hba *hba); bool ufshcd_is_txeq_presets_used(struct ufs_hba *hba); bool ufshcd_is_txeq_preset_selected(u8 preshoot, u8 deemphasis); int ufshcd_retrain_tx_eq(struct ufs_hba *hba, u32 gear); +void ufshcd_retrieve_tx_eq_settings(struct ufs_hba *hba); +void ufshcd_store_tx_eq_settings(struct ufs_hba *hba); /* Wrapper functions for safely calling variant operations */ static inline const char *ufshcd_get_var_name(struct ufs_hba *hba) @@ -129,7 +132,7 @@ static inline const char *ufshcd_get_var_name(struct ufs_hba *hba) static inline void ufshcd_vops_exit(struct ufs_hba *hba) { if (hba->vops && hba->vops->exit) - return hba->vops->exit(hba); + hba->vops->exit(hba); } static inline u32 ufshcd_vops_get_ufs_hci_version(struct ufs_hba *hba) @@ -208,7 +211,7 @@ static inline void ufshcd_vops_setup_task_mgmt(struct ufs_hba *hba, int tag, u8 tm_function) { if (hba->vops && hba->vops->setup_task_mgmt) - return hba->vops->setup_task_mgmt(hba, tag, tm_function); + hba->vops->setup_task_mgmt(hba, tag, tm_function); } static inline void ufshcd_vops_hibern8_notify(struct ufs_hba *hba, @@ -216,7 +219,7 @@ static inline void ufshcd_vops_hibern8_notify(struct ufs_hba *hba, enum ufs_notify_change_status status) { if (hba->vops && hba->vops->hibern8_notify) - return hba->vops->hibern8_notify(hba, cmd, status); + hba->vops->hibern8_notify(hba, cmd, status); } static inline int ufshcd_vops_apply_dev_quirks(struct ufs_hba *hba) diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index c3f08957d179..d3044a3089b5 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -460,20 +460,24 @@ static void ufshcd_add_uic_command_trace(struct ufs_hba *hba, const struct uic_command *ucmd, enum ufs_trace_str_t str_t) { - u32 cmd; + u32 cmd, arg1, arg2, arg3; if (!trace_ufshcd_uic_command_enabled()) return; - if (str_t == UFS_CMD_SEND) + if (str_t == UFS_CMD_SEND) { cmd = ucmd->command; - else + arg1 = ucmd->argument1; + arg2 = ucmd->argument2; + arg3 = ucmd->argument3; + } else { cmd = ufshcd_readl(hba, REG_UIC_COMMAND); + arg1 = ufshcd_readl(hba, REG_UIC_COMMAND_ARG_1); + arg2 = ufshcd_readl(hba, REG_UIC_COMMAND_ARG_2); + arg3 = ufshcd_readl(hba, REG_UIC_COMMAND_ARG_3); + } - trace_ufshcd_uic_command(hba, str_t, cmd, - ufshcd_readl(hba, REG_UIC_COMMAND_ARG_1), - ufshcd_readl(hba, REG_UIC_COMMAND_ARG_2), - ufshcd_readl(hba, REG_UIC_COMMAND_ARG_3)); + trace_ufshcd_uic_command(hba, str_t, cmd, arg1, arg2, arg3); } static void ufshcd_add_command_trace(struct ufs_hba *hba, struct scsi_cmnd *cmd, @@ -913,33 +917,6 @@ static inline int ufshcd_get_lists_status(u32 reg) } /** - * ufshcd_get_uic_cmd_result - Get the UIC command result - * @hba: Pointer to adapter instance - * - * This function gets the result of UIC command completion - * - * Return: 0 on success; non-zero value on error. - */ -static inline int ufshcd_get_uic_cmd_result(struct ufs_hba *hba) -{ - return ufshcd_readl(hba, REG_UIC_COMMAND_ARG_2) & - MASK_UIC_COMMAND_RESULT; -} - -/** - * ufshcd_get_dme_attr_val - Get the value of attribute returned by UIC command - * @hba: Pointer to adapter instance - * - * This function gets UIC command argument3 - * - * Return: 0 on success; non-zero value on error. - */ -static inline u32 ufshcd_get_dme_attr_val(struct ufs_hba *hba) -{ - return ufshcd_readl(hba, REG_UIC_COMMAND_ARG_3); -} - -/** * ufshcd_get_req_rsp - returns the TR response transaction type * @ucd_rsp_ptr: pointer to response UPIU * @@ -2598,6 +2575,7 @@ ufshcd_dispatch_uic_cmd(struct ufs_hba *hba, struct uic_command *uic_cmd) lockdep_assert_held(&hba->uic_cmd_mutex); WARN_ON(hba->active_uic_cmd); + WARN_ON_ONCE(uic_cmd->argument2 & MASK_UIC_COMMAND_RESULT); hba->active_uic_cmd = uic_cmd; @@ -3611,6 +3589,67 @@ int ufshcd_query_attr_retry(struct ufs_hba *hba, return ret; } +/** + * ufshcd_query_attr_qword - Function of sending query requests for quad-word attributes + * @hba: per-adapter instance + * @opcode: attribute opcode + * @idn: attribute idn to access + * @index: index field + * @sel: selector field + * @attr_val: the attribute value after the query request completes + * + * Return: 0 for success, non-zero in case of failure. + */ +int ufshcd_query_attr_qword(struct ufs_hba *hba, enum query_opcode opcode, + enum attr_idn idn, u8 index, u8 sel, u64 *attr_val) +{ + struct utp_upiu_query_v4_0 *upiu_req; + struct utp_upiu_query_v4_0 *upiu_resp; + struct ufs_query_req *request = NULL; + struct ufs_query_res *response = NULL; + int err; + + if (!attr_val) { + dev_err(hba->dev, "%s: attribute value required for opcode 0x%x\n", + __func__, opcode); + return -EINVAL; + } + + ufshcd_dev_man_lock(hba); + + ufshcd_init_query(hba, &request, &response, opcode, idn, index, sel); + + switch (opcode) { + case UPIU_QUERY_OPCODE_WRITE_ATTR: + request->query_func = UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST; + upiu_req = (struct utp_upiu_query_v4_0 *)&request->upiu_req; + put_unaligned_be64(*attr_val, &upiu_req->osf3); + break; + case UPIU_QUERY_OPCODE_READ_ATTR: + request->query_func = UPIU_QUERY_FUNC_STANDARD_READ_REQUEST; + break; + default: + dev_err(hba->dev, "%s: Expected query attr opcode but got = 0x%.2x\n", + __func__, opcode); + err = -EINVAL; + goto out_unlock; + } + + err = ufshcd_exec_dev_cmd(hba, DEV_CMD_TYPE_QUERY, dev_cmd_timeout); + if (err) { + dev_err(hba->dev, "%s: opcode 0x%.2x for idn %d failed, index %d, selector %d, err = %d\n", + __func__, opcode, idn, index, sel, err); + goto out_unlock; + } + + upiu_resp = (struct utp_upiu_query_v4_0 *)response; + *attr_val = get_unaligned_be64(&upiu_resp->osf3); + +out_unlock: + ufshcd_dev_man_unlock(hba); + return err; +} + /* * Return: 0 upon success; > 0 in case the UFS device reported an OCS error; * < 0 if another error occurred. @@ -5157,6 +5196,45 @@ void ufshcd_update_evt_hist(struct ufs_hba *hba, u32 id, u32 val) } EXPORT_SYMBOL_GPL(ufshcd_update_evt_hist); +static int ufshcd_validate_link_params(struct ufs_hba *hba) +{ + int ret, val; + + /* + * lanes_per_direction is only populated by the platform glue (it + * defaults to 2 or is read from the "lanes-per-direction" devicetree + * property). Controllers probed via ufshcd-pci leave it unset (0), in + * which case there is no expected lane count to validate the connected + * lanes against. Skip the check instead of failing link startup. + */ + if (!hba->lanes_per_direction) + return 0; + + ret = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_CONNECTEDTXDATALANES), + &val); + if (ret) + return ret; + + if (val != hba->lanes_per_direction) { + dev_err(hba->dev, "Tx lane mismatch [config,reported] [%d,%d]\n", + hba->lanes_per_direction, val); + return -ENOLINK; + } + + ret = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_CONNECTEDRXDATALANES), + &val); + if (ret) + return ret; + + if (val != hba->lanes_per_direction) { + dev_err(hba->dev, "Rx lane mismatch [config,reported] [%d,%d]\n", + hba->lanes_per_direction, val); + return -ENOLINK; + } + + return 0; +} + /** * ufshcd_link_startup - Initialize unipro link startup * @hba: per adapter instance @@ -5230,6 +5308,10 @@ link_startup: goto out; } + ret = ufshcd_validate_link_params(hba); + if (ret) + goto out; + /* Include any host controller configuration via UIC commands */ ret = ufshcd_vops_link_startup_notify(hba, POST_CHANGE); if (ret) @@ -5634,7 +5716,7 @@ static inline int ufshcd_transfer_rsp_status(struct ufs_hba *hba, default: dev_err(hba->dev, "Unexpected request response code = %x\n", - result); + ufshcd_get_req_rsp(lrbp->ucd_rsp_ptr)); result = DID_ERROR << 16; break; } @@ -5716,8 +5798,14 @@ static irqreturn_t ufshcd_uic_cmd_compl(struct ufs_hba *hba, u32 intr_status) hba->errors |= (UFSHCD_UIC_HIBERN8_MASK & intr_status); if (intr_status & UIC_COMMAND_COMPL) { - cmd->argument2 |= ufshcd_get_uic_cmd_result(hba); - cmd->argument3 = ufshcd_get_dme_attr_val(hba); + /* + * Store the UIC command result in the lowest byte of + * cmd->argument2. + */ + cmd->argument2 |= ufshcd_readl(hba, REG_UIC_COMMAND_ARG_2) & + MASK_UIC_COMMAND_RESULT; + /* Store the DME attribute value in cmd->argument3. */ + cmd->argument3 = ufshcd_readl(hba, REG_UIC_COMMAND_ARG_3); if (!hba->uic_async_done) cmd->cmd_active = false; complete(&cmd->done); @@ -6224,46 +6312,6 @@ out: __func__, err); } -/* - * Return: 0 upon success; > 0 in case the UFS device reported an OCS error; - * < 0 if another error occurred. - */ -int ufshcd_read_device_lvl_exception_id(struct ufs_hba *hba, u64 *exception_id) -{ - struct utp_upiu_query_v4_0 *upiu_resp; - struct ufs_query_req *request = NULL; - struct ufs_query_res *response = NULL; - int err; - - if (hba->dev_info.wspecversion < 0x410) - return -EOPNOTSUPP; - - ufshcd_hold(hba); - mutex_lock(&hba->dev_cmd.lock); - - ufshcd_init_query(hba, &request, &response, - UPIU_QUERY_OPCODE_READ_ATTR, - QUERY_ATTR_IDN_DEV_LVL_EXCEPTION_ID, 0, 0); - - request->query_func = UPIU_QUERY_FUNC_STANDARD_READ_REQUEST; - - err = ufshcd_exec_dev_cmd(hba, DEV_CMD_TYPE_QUERY, dev_cmd_timeout); - - if (err) { - dev_err(hba->dev, "%s: failed to read device level exception %d\n", - __func__, err); - goto out; - } - - upiu_resp = (struct utp_upiu_query_v4_0 *)response; - *exception_id = get_unaligned_be64(&upiu_resp->osf3); -out: - mutex_unlock(&hba->dev_cmd.lock); - ufshcd_release(hba); - - return err; -} - static int __ufshcd_wb_toggle(struct ufs_hba *hba, bool set, enum flag_idn idn) { u8 index; @@ -7855,8 +7903,12 @@ static void ufshcd_set_req_abort_skip(struct ufs_hba *hba, unsigned long bitmap) for_each_set_bit(tag, &bitmap, hba->nutrs) { struct scsi_cmnd *cmd = ufshcd_tag_to_cmd(hba, tag); - struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd); + struct ufshcd_lrb *lrbp; + if (!cmd) + continue; + + lrbp = scsi_cmd_priv(cmd); lrbp->req_abort_skip = true; } } @@ -7877,11 +7929,16 @@ static void ufshcd_set_req_abort_skip(struct ufs_hba *hba, unsigned long bitmap) int ufshcd_try_to_abort_task(struct ufs_hba *hba, int tag) { struct scsi_cmnd *cmd = ufshcd_tag_to_cmd(hba, tag); - struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd); + struct ufshcd_lrb *lrbp; int err; int poll_cnt; u8 resp = 0xF; + if (!cmd) + return -EINVAL; + + lrbp = scsi_cmd_priv(cmd); + for (poll_cnt = 100; poll_cnt; poll_cnt--) { err = ufshcd_issue_tm_cmd(hba, lrbp->lun, tag, UFS_QUERY_TASK, &resp); @@ -9107,41 +9164,28 @@ static int ufshcd_device_params_init(struct ufs_hba *hba) dev_err(hba->dev, "%s: Failed getting max supported power mode\n", __func__); + + ufshcd_retrieve_tx_eq_settings(hba); out: return ret; } static void ufshcd_set_timestamp_attr(struct ufs_hba *hba) { - int err; - struct ufs_query_req *request = NULL; - struct ufs_query_res *response = NULL; struct ufs_dev_info *dev_info = &hba->dev_info; - struct utp_upiu_query_v4_0 *upiu_data; + u64 ts_ns; + int err; if (dev_info->wspecversion < 0x400 || hba->dev_quirks & UFS_DEVICE_QUIRK_NO_TIMESTAMP_SUPPORT) return; - ufshcd_dev_man_lock(hba); - - ufshcd_init_query(hba, &request, &response, - UPIU_QUERY_OPCODE_WRITE_ATTR, - QUERY_ATTR_IDN_TIMESTAMP, 0, 0); - - request->query_func = UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST; - - upiu_data = (struct utp_upiu_query_v4_0 *)&request->upiu_req; - - put_unaligned_be64(ktime_get_real_ns(), &upiu_data->osf3); - - err = ufshcd_exec_dev_cmd(hba, DEV_CMD_TYPE_QUERY, dev_cmd_timeout); - + ts_ns = ktime_get_real_ns(); + err = ufshcd_query_attr_qword(hba, UPIU_QUERY_OPCODE_WRITE_ATTR, + QUERY_ATTR_IDN_TIMESTAMP, 0, 0, &ts_ns); if (err) dev_err(hba->dev, "%s: failed to set timestamp %d\n", __func__, err); - - ufshcd_dev_man_unlock(hba); } /** @@ -9466,22 +9510,44 @@ static enum scsi_timeout_action ufshcd_eh_timed_out(struct scsi_cmnd *scmd) { struct ufs_hba *hba = shost_priv(scmd->device->host); - if (!hba->system_suspending) { + if (!hba->pm_op_in_progress) { /* Activate the error handler in the SCSI core. */ return SCSI_EH_NOT_HANDLED; } /* - * If we get here we know that no TMFs are outstanding and also that - * the only pending command is a START STOP UNIT command. Handle the - * timeout of that command directly to prevent a deadlock between + * Handle the timeout directly to prevent a deadlock between * ufshcd_set_dev_pwr_mode() and ufshcd_err_handler(). */ ufshcd_link_recovery(hba); dev_info(hba->dev, "%s() finished; outstanding_tasks = %#lx.\n", __func__, hba->outstanding_tasks); - return scsi_host_busy(hba->host) ? SCSI_EH_RESET_TIMER : SCSI_EH_DONE; + /* + * ufshcd_link_recovery() may already have completed @scmd, e.g. via + * the existing MCQ force-completion path. + */ + if (!test_bit(SCMD_STATE_COMPLETE, &scmd->state)) { + if (!hba->mcq_enabled) { + unsigned long flags; + struct request *rq = scsi_cmd_to_rq(scmd); + + spin_lock_irqsave(&hba->outstanding_lock, flags); + __clear_bit(rq->tag, &hba->outstanding_reqs); + spin_unlock_irqrestore(&hba->outstanding_lock, flags); + } + + if (ufshcd_is_scsi_cmd(scmd)) { + set_host_byte(scmd, DID_REQUEUE); + ufshcd_release_scsi_cmd(hba, scmd); + } else { + set_host_byte(scmd, DID_TIME_OUT); + } + + scsi_done(scmd); + } + + return SCSI_EH_DONE; } static const struct attribute_group *ufshcd_driver_groups[] = { @@ -9836,28 +9902,15 @@ out: static int ufshcd_variant_hba_init(struct ufs_hba *hba) { - int err = 0; + int err = ufshcd_vops_init(hba); - if (!hba->vops) - goto out; - - err = ufshcd_vops_init(hba); if (err) dev_err_probe(hba->dev, err, "%s: variant %s init failed with err %d\n", __func__, ufshcd_get_var_name(hba), err); -out: return err; } -static void ufshcd_variant_hba_exit(struct ufs_hba *hba) -{ - if (!hba->vops) - return; - - ufshcd_vops_exit(hba); -} - static int ufshcd_hba_init(struct ufs_hba *hba) { int err; @@ -9925,7 +9978,7 @@ static void ufshcd_hba_exit(struct ufs_hba *hba) if (hba->eh_wq) destroy_workqueue(hba->eh_wq); ufs_debugfs_hba_exit(hba); - ufshcd_variant_hba_exit(hba); + ufshcd_vops_exit(hba); ufshcd_setup_vreg(hba, false); ufshcd_setup_clocks(hba, false); ufshcd_setup_hba_vreg(hba, false); @@ -10518,7 +10571,6 @@ static int ufshcd_wl_suspend(struct device *dev) hba = shost_priv(sdev->host); down(&hba->host_sem); - hba->system_suspending = true; if (pm_runtime_suspended(dev)) goto out; @@ -10560,7 +10612,6 @@ out: hba->curr_dev_pwr_mode, hba->uic_link_state); if (!ret) hba->is_sys_suspended = false; - hba->system_suspending = false; up(&hba->host_sem); return ret; } @@ -10768,6 +10819,9 @@ static void ufshcd_wl_shutdown(struct scsi_device *sdev) /* Turn on everything while shutting down */ ufshcd_rpm_get_sync(hba); + + ufshcd_store_tx_eq_settings(hba); + scsi_device_quiesce(sdev); shost_for_each_device(sdev, hba->host) { if (sdev == hba->ufs_device_wlun) diff --git a/drivers/ufs/host/Kconfig b/drivers/ufs/host/Kconfig index 964ae70e7390..ff170c0b6da0 100644 --- a/drivers/ufs/host/Kconfig +++ b/drivers/ufs/host/Kconfig @@ -55,7 +55,7 @@ config SCSI_UFS_DWC_TC_PLATFORM If unsure, say N. config SCSI_UFS_QCOM - tristate "QCOM specific hooks to UFS controller platform driver" + tristate "Qualcomm specific hooks to UFS controller platform driver" depends on SCSI_UFSHCD_PLATFORM && ARCH_QCOM depends on GENERIC_MSI_IRQ depends on RESET_CONTROLLER diff --git a/drivers/ufs/host/tc-dwc-g210-pci.c b/drivers/ufs/host/tc-dwc-g210-pci.c index 0167d8bef71a..c6d89f9c44ae 100644 --- a/drivers/ufs/host/tc-dwc-g210-pci.c +++ b/drivers/ufs/host/tc-dwc-g210-pci.c @@ -114,8 +114,8 @@ static const struct dev_pm_ops tc_dwc_g210_pci_pm_ops = { }; static const struct pci_device_id tc_dwc_g210_pci_tbl[] = { - { PCI_VENDOR_ID_SYNOPSYS, 0xB101, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, - { PCI_VENDOR_ID_SYNOPSYS, 0xB102, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VDEVICE(SYNOPSYS, 0xB101) }, + { PCI_VDEVICE(SYNOPSYS, 0xB102) }, { } /* terminate list */ }; diff --git a/drivers/ufs/host/ufs-exynos.c b/drivers/ufs/host/ufs-exynos.c index 77a6c8e44485..b2f65c465525 100644 --- a/drivers/ufs/host/ufs-exynos.c +++ b/drivers/ufs/host/ufs-exynos.c @@ -97,6 +97,10 @@ #define UFS_EXYNOSAUTO_RD_SHARABLE BIT(1) #define UFS_EXYNOSAUTO_SHARABLE (UFS_EXYNOSAUTO_WR_SHARABLE | \ UFS_EXYNOSAUTO_RD_SHARABLE) +#define UFS_EXYNOSAUTOV920_WR_SHARABLE BIT(3) +#define UFS_EXYNOSAUTOV920_RD_SHARABLE BIT(2) +#define UFS_EXYNOSAUTOV920_SHARABLE (UFS_EXYNOSAUTOV920_WR_SHARABLE |\ + UFS_EXYNOSAUTOV920_RD_SHARABLE) #define UFS_GS101_WR_SHARABLE BIT(1) #define UFS_GS101_RD_SHARABLE BIT(0) #define UFS_GS101_SHARABLE (UFS_GS101_WR_SHARABLE | \ @@ -417,6 +421,95 @@ static int exynos7_ufs_post_pwr_change(struct exynos_ufs *ufs, return 0; } +static int exynosautov920_ufs_pre_link(struct exynos_ufs *ufs) +{ + struct ufs_hba *hba = ufs->hba; + int i; + u32 tx_line_reset_period, rx_line_reset_period; + + rx_line_reset_period = (RX_LINE_RESET_TIME * ufs->mclk_rate) + / NSEC_PER_MSEC; + tx_line_reset_period = (TX_LINE_RESET_TIME * ufs->mclk_rate) + / NSEC_PER_MSEC; + + unipro_writel(ufs, 0x5f, 0x44); + + ufshcd_dme_set(hba, UIC_ARG_MIB(0x200), 0x40); + ufshcd_dme_set(hba, UIC_ARG_MIB(0x202), 0x02); + + for_each_ufs_rx_lane(ufs, i) { + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_CLK_PRD, i), + DIV_ROUND_UP(NSEC_PER_SEC, ufs->mclk_rate)); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_CLK_PRD_EN, i), 0x0); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_LINERESET_VALUE2, i), + (rx_line_reset_period >> 16) & 0xFF); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_LINERESET_VALUE1, i), + (rx_line_reset_period >> 8) & 0xFF); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_RX_LINERESET_VALUE0, i), + (rx_line_reset_period) & 0xFF); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x2f, i), 0x69); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x84, i), 0x1); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x25, i), 0xf6); + } + + for_each_ufs_tx_lane(ufs, i) { + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_CLK_PRD, i), + DIV_ROUND_UP(NSEC_PER_SEC, ufs->mclk_rate)); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_CLK_PRD_EN, i), + 0x02); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_LINERESET_PVALUE2, i), + (tx_line_reset_period >> 16) & 0xFF); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_LINERESET_PVALUE1, i), + (tx_line_reset_period >> 8) & 0xFF); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(VND_TX_LINERESET_PVALUE0, i), + (tx_line_reset_period) & 0xFF); + + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x04, i), 0x1); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(0x7f, i), 0x0); + } + + ufshcd_dme_set(hba, UIC_ARG_MIB(0x200), 0x0); + ufshcd_dme_set(hba, UIC_ARG_MIB(PA_LOCAL_TX_LCC_ENABLE), 0x0); + ufshcd_dme_set(hba, UIC_ARG_MIB(0xa011), 0x8000); + + return 0; +} + +static int exynosautov920_ufs_post_link(struct exynos_ufs *ufs) +{ + struct ufs_hba *hba = ufs->hba; + + ufshcd_dme_set(hba, UIC_ARG_MIB(0x9529), 0x1); + ufshcd_dme_set(hba, UIC_ARG_MIB(0x15a4), 0x3e8); + ufshcd_dme_set(hba, UIC_ARG_MIB(0x9529), 0x0); + + return 0; +} + +static int exynosautov920_ufs_pre_pwr_change(struct exynos_ufs *ufs, + struct ufs_pa_layer_attr *pwr) +{ + struct ufs_hba *hba = ufs->hba; + + ufshcd_dme_set(hba, UIC_ARG_MIB(0x15d4), 0x1); + + ufshcd_dme_set(hba, UIC_ARG_MIB(DL_FC0PROTTIMEOUTVAL), 8064); + ufshcd_dme_set(hba, UIC_ARG_MIB(DL_TC0REPLAYTIMEOUTVAL), 28224); + ufshcd_dme_set(hba, UIC_ARG_MIB(DL_AFC0REQTIMEOUTVAL), 20160); + ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA0), 12000); + ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA1), 32000); + ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODEUSERDATA2), 16000); + + unipro_writel(ufs, 8064, UNIPRO_DME_POWERMODE_REQ_LOCALL2TIMER0); + unipro_writel(ufs, 28224, UNIPRO_DME_POWERMODE_REQ_LOCALL2TIMER1); + unipro_writel(ufs, 20160, UNIPRO_DME_POWERMODE_REQ_LOCALL2TIMER2); + unipro_writel(ufs, 12000, UNIPRO_DME_POWERMODE_REQ_REMOTEL2TIMER0); + unipro_writel(ufs, 32000, UNIPRO_DME_POWERMODE_REQ_REMOTEL2TIMER1); + unipro_writel(ufs, 16000, UNIPRO_DME_POWERMODE_REQ_REMOTEL2TIMER2); + + return 0; +} + /* * exynos_ufs_auto_ctrl_hcc - HCI core clock control by h/w * Control should be disabled in the below cases @@ -2201,6 +2294,21 @@ static const struct exynos_ufs_drv_data gs101_ufs_drvs = { .suspend = gs101_ufs_suspend, }; +static const struct exynos_ufs_drv_data exynosautov920_ufs_drvs = { + .uic_attr = &exynos7_uic_attr, + .quirks = UFSHCD_QUIRK_SKIP_DEF_UNIPRO_TIMEOUT_SETTING, + .opts = EXYNOS_UFS_OPT_BROKEN_AUTO_CLK_CTRL | + EXYNOS_UFS_OPT_SKIP_CONFIG_PHY_ATTR | + EXYNOS_UFS_OPT_BROKEN_RX_SEL_IDX | + EXYNOS_UFS_OPT_TIMER_TICK_SELECT, + .iocc_mask = UFS_EXYNOSAUTOV920_SHARABLE, + .drv_init = exynosauto_ufs_drv_init, + .post_hce_enable = exynosauto_ufs_post_hce_enable, + .pre_link = exynosautov920_ufs_pre_link, + .post_link = exynosautov920_ufs_post_link, + .pre_pwr_change = exynosautov920_ufs_pre_pwr_change, +}; + static const struct of_device_id exynos_ufs_of_match[] = { { .compatible = "google,gs101-ufs", .data = &gs101_ufs_drvs }, @@ -2210,6 +2318,8 @@ static const struct of_device_id exynos_ufs_of_match[] = { .data = &exynosauto_ufs_drvs }, { .compatible = "samsung,exynosautov9-ufs-vh", .data = &exynosauto_ufs_vh_drvs }, + { .compatible = "samsung,exynosautov920-ufs", + .data = &exynosautov920_ufs_drvs }, { .compatible = "tesla,fsd-ufs", .data = &fsd_ufs_drvs }, {}, diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c index 9c0973a7ffc3..291c43448764 100644 --- a/drivers/ufs/host/ufs-qcom.c +++ b/drivers/ufs/host/ufs-qcom.c @@ -705,6 +705,13 @@ cfg_timers: return 0; } +static void ufs_qcom_link_startup_post_change(struct ufs_hba *hba) +{ + if (ufshcd_is_auto_hibern8_supported(hba)) + ufshcd_rmwl(hba, UFS_HW_CLK_CTRL_EN, UFS_HW_CLK_CTRL_EN, + UFS_AH8_CFG); +} + static int ufs_qcom_link_startup_notify(struct ufs_hba *hba, enum ufs_notify_change_status status) { @@ -731,6 +738,9 @@ static int ufs_qcom_link_startup_notify(struct ufs_hba *hba, err = ufshcd_disable_host_tx_lcc(hba); break; + case POST_CHANGE: + ufs_qcom_link_startup_post_change(hba); + break; default: break; } @@ -1305,6 +1315,9 @@ static void ufs_qcom_advertise_quirks(struct ufs_hba *hba) if (host->hw_ver.major > 0x3) hba->quirks |= UFSHCD_QUIRK_REINIT_AFTER_MAX_GEAR_SWITCH; + if (host->hw_ver.major == 0x7 && host->hw_ver.minor == 0x1) + hba->quirks |= UFSHCD_QUIRK_EXTENDED_TX_EQTR_ADAPT_LENGTH_L0L1L2L3; + if (drvdata && drvdata->quirks) hba->quirks |= drvdata->quirks; } diff --git a/drivers/ufs/host/ufs-qcom.h b/drivers/ufs/host/ufs-qcom.h index 5d083331a7f4..e20b3ca50577 100644 --- a/drivers/ufs/host/ufs-qcom.h +++ b/drivers/ufs/host/ufs-qcom.h @@ -268,6 +268,17 @@ enum { */ #define NUM_TX_R1W1 13 +/* bit definitions for UFS_AH8_CFG register */ +#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) + static inline void ufs_qcom_get_controller_revision(struct ufs_hba *hba, u8 *major, u16 *minor, u16 *step) diff --git a/drivers/ufs/host/ufshcd-pci.c b/drivers/ufs/host/ufshcd-pci.c index effa3c7a01c5..f2433879b0eb 100644 --- a/drivers/ufs/host/ufshcd-pci.c +++ b/drivers/ufs/host/ufshcd-pci.c @@ -680,21 +680,21 @@ static const struct dev_pm_ops ufshcd_pci_pm_ops = { }; static const struct pci_device_id ufshcd_pci_tbl[] = { - { PCI_VENDOR_ID_REDHAT, 0x0013, PCI_ANY_ID, PCI_ANY_ID, 0, 0, - (kernel_ulong_t)&ufs_qemu_hba_vops }, - { PCI_VENDOR_ID_SAMSUNG, 0xC00C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, - { PCI_VDEVICE(INTEL, 0x9DFA), (kernel_ulong_t)&ufs_intel_cnl_hba_vops }, - { PCI_VDEVICE(INTEL, 0x4B41), (kernel_ulong_t)&ufs_intel_ehl_hba_vops }, - { PCI_VDEVICE(INTEL, 0x4B43), (kernel_ulong_t)&ufs_intel_ehl_hba_vops }, - { PCI_VDEVICE(INTEL, 0x98FA), (kernel_ulong_t)&ufs_intel_lkf_hba_vops }, - { PCI_VDEVICE(INTEL, 0x51FF), (kernel_ulong_t)&ufs_intel_adl_hba_vops }, - { PCI_VDEVICE(INTEL, 0x54FF), (kernel_ulong_t)&ufs_intel_adl_hba_vops }, - { PCI_VDEVICE(INTEL, 0x7E47), (kernel_ulong_t)&ufs_intel_mtl_hba_vops }, - { PCI_VDEVICE(INTEL, 0xA847), (kernel_ulong_t)&ufs_intel_mtl_hba_vops }, - { PCI_VDEVICE(INTEL, 0x7747), (kernel_ulong_t)&ufs_intel_mtl_hba_vops }, - { PCI_VDEVICE(INTEL, 0xE447), (kernel_ulong_t)&ufs_intel_mtl_hba_vops }, - { PCI_VDEVICE(INTEL, 0x4D47), (kernel_ulong_t)&ufs_intel_mtl_hba_vops }, - { PCI_VDEVICE(INTEL, 0xD335), (kernel_ulong_t)&ufs_intel_mtl_hba_vops }, + { PCI_VDEVICE(REDHAT, 0x0013), .driver_data = (kernel_ulong_t)&ufs_qemu_hba_vops }, + { PCI_VDEVICE(SAMSUNG, 0xC00C), .driver_data = 0 }, + { PCI_VDEVICE(INTEL, 0x9DFA), .driver_data = (kernel_ulong_t)&ufs_intel_cnl_hba_vops }, + { PCI_VDEVICE(INTEL, 0x4B41), .driver_data = (kernel_ulong_t)&ufs_intel_ehl_hba_vops }, + { PCI_VDEVICE(INTEL, 0x4B43), .driver_data = (kernel_ulong_t)&ufs_intel_ehl_hba_vops }, + { PCI_VDEVICE(INTEL, 0x98FA), .driver_data = (kernel_ulong_t)&ufs_intel_lkf_hba_vops }, + { PCI_VDEVICE(INTEL, 0x51FF), .driver_data = (kernel_ulong_t)&ufs_intel_adl_hba_vops }, + { PCI_VDEVICE(INTEL, 0x54FF), .driver_data = (kernel_ulong_t)&ufs_intel_adl_hba_vops }, + { PCI_VDEVICE(INTEL, 0x7E47), .driver_data = (kernel_ulong_t)&ufs_intel_mtl_hba_vops }, + { PCI_VDEVICE(INTEL, 0xA847), .driver_data = (kernel_ulong_t)&ufs_intel_mtl_hba_vops }, + { PCI_VDEVICE(INTEL, 0x7747), .driver_data = (kernel_ulong_t)&ufs_intel_mtl_hba_vops }, + { PCI_VDEVICE(INTEL, 0xE447), .driver_data = (kernel_ulong_t)&ufs_intel_mtl_hba_vops }, + { PCI_VDEVICE(INTEL, 0x4D47), .driver_data = (kernel_ulong_t)&ufs_intel_mtl_hba_vops }, + { PCI_VDEVICE(INTEL, 0xD335), .driver_data = (kernel_ulong_t)&ufs_intel_mtl_hba_vops }, + { PCI_VDEVICE(AMD, 0x1B29), .driver_data = 0 }, { } /* terminate list */ }; |
