| /* |
| * Universal Flash Storage Feature Support |
| * |
| * Copyright (C) 2017-2018 Samsung Electronics Co., Ltd. |
| * |
| * Authors: |
| * Yongmyung Lee <ymhungry.lee@samsung.com> |
| * Jinyoung Choi <j-young.choi@samsung.com> |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| * See the COPYING file in the top-level directory or visit |
| * <http://www.gnu.org/licenses/gpl-2.0.html> |
| * |
| * 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. |
| * |
| * This program is provided "AS IS" and "WITH ALL FAULTS" and |
| * without warranty of any kind. You are solely responsible for |
| * determining the appropriateness of using and distributing |
| * the program and assume all risks associated with your exercise |
| * of rights with respect to the program, including but not limited |
| * to infringement of third party rights, the risks and costs of |
| * program errors, damage to or loss of data, programs or equipment, |
| * and unavailability or interruption of operations. Under no |
| * circumstances will the contributor of this Program be liable for |
| * any damages of any kind arising from your use or distribution of |
| * this program. |
| * |
| * The Linux Foundation chooses to take subject only to the GPLv2 |
| * license terms, and distributes only under these terms. |
| */ |
| |
| |
| #include "ufsfeature.h" |
| #include "ufshcd.h" |
| #include "ufs_quirks.h" |
| |
| #if defined(CONFIG_UFSHPB) |
| #include "ufshpb.h" |
| #endif |
| |
| #define QUERY_REQ_TIMEOUT 1500 /* msec */ |
| |
| struct ufsf_feature_para ufsf_para; |
| |
| static inline void ufsf_init_query(struct ufs_hba *hba, |
| struct ufs_query_req **request, |
| struct ufs_query_res **response, |
| enum query_opcode opcode, u8 idn, |
| u8 index, u8 selector) |
| { |
| *request = &hba->dev_cmd.query.request; |
| *response = &hba->dev_cmd.query.response; |
| memset(*request, 0, sizeof(struct ufs_query_req)); |
| memset(*response, 0, sizeof(struct ufs_query_res)); |
| (*request)->upiu_req.opcode = opcode; |
| (*request)->upiu_req.idn = idn; |
| (*request)->upiu_req.index = index; |
| (*request)->upiu_req.selector = selector; |
| } |
| |
| /* |
| * ufs feature common functions. |
| */ |
| int ufsf_query_flag(struct ufs_hba *hba, enum query_opcode opcode, |
| enum flag_idn idn, u8 index, bool *flag_res) |
| { |
| struct ufs_query_req *request = NULL; |
| struct ufs_query_res *response = NULL; |
| u8 selector; |
| int err; |
| |
| BUG_ON(!hba); |
| |
| ufshcd_hold(hba, false); |
| mutex_lock(&hba->dev_cmd.lock); |
| |
| if (hba->card->wmanufacturerid == UFS_VENDOR_SAMSUNG || |
| hba->card->wmanufacturerid == UFS_VENDOR_MICRON) |
| selector = UFSFEATURE_SELECTOR; |
| else |
| selector = 0; |
| |
| /* |
| * Init the query response and request parameters |
| */ |
| ufsf_init_query(hba, &request, &response, opcode, idn, index, |
| selector); |
| |
| switch (opcode) { |
| case UPIU_QUERY_OPCODE_SET_FLAG: |
| case UPIU_QUERY_OPCODE_CLEAR_FLAG: |
| case UPIU_QUERY_OPCODE_TOGGLE_FLAG: |
| request->query_func = UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST; |
| break; |
| case UPIU_QUERY_OPCODE_READ_FLAG: |
| request->query_func = UPIU_QUERY_FUNC_STANDARD_READ_REQUEST; |
| if (!flag_res) { |
| /* No dummy reads */ |
| dev_err(hba->dev, "%s: Invalid argument for read request\n", |
| __func__); |
| err = -EINVAL; |
| goto out_unlock; |
| } |
| break; |
| default: |
| dev_err(hba->dev, |
| "%s: Expected query flag opcode but got = %d\n", |
| __func__, opcode); |
| err = -EINVAL; |
| goto out_unlock; |
| } |
| |
| /* Send query request */ |
| err = ufshcd_exec_dev_cmd(hba, DEV_CMD_TYPE_QUERY, QUERY_REQ_TIMEOUT); |
| if (err) { |
| dev_err(hba->dev, |
| "%s: Sending flag query for idn %d failed, err = %d\n", |
| __func__, idn, err); |
| goto out_unlock; |
| } |
| |
| if (flag_res) |
| *flag_res = (be32_to_cpu(response->upiu_res.value) & |
| MASK_QUERY_UPIU_FLAG_LOC) & 0x1; |
| |
| out_unlock: |
| mutex_unlock(&hba->dev_cmd.lock); |
| ufshcd_release(hba); |
| return err; |
| } |
| |
| int ufsf_query_flag_retry(struct ufs_hba *hba, enum query_opcode opcode, |
| enum flag_idn idn, u8 idx, bool *flag_res) |
| { |
| int ret; |
| int retries; |
| |
| for (retries = 0; retries < UFSF_QUERY_REQ_RETRIES; retries++) { |
| ret = ufsf_query_flag(hba, opcode, idn, idx, flag_res); |
| if (ret) |
| dev_dbg(hba->dev, |
| "%s: failed with error %d, retries %d\n", |
| __func__, ret, retries); |
| else |
| break; |
| } |
| if (ret) |
| dev_err(hba->dev, |
| "%s: query flag, opcode %d, idn %d, failed with error %d after %d retires\n", |
| __func__, opcode, idn, ret, retries); |
| return ret; |
| } |
| |
| int ufsf_query_attr_retry(struct ufs_hba *hba, enum query_opcode opcode, |
| enum attr_idn idn, u8 idx, u32 *attr_val) |
| { |
| int ret; |
| int retries; |
| u8 selector; |
| |
| if (hba->card->wmanufacturerid == UFS_VENDOR_SAMSUNG || |
| hba->card->wmanufacturerid == UFS_VENDOR_MICRON) |
| selector = UFSFEATURE_SELECTOR; |
| else |
| selector = 0; |
| |
| for (retries = 0; retries < UFSF_QUERY_REQ_RETRIES; retries++) { |
| ret = ufshcd_query_attr(hba, opcode, idn, idx, |
| selector, attr_val); |
| if (ret) |
| dev_dbg(hba->dev, |
| "%s: failed with error %d, retries %d\n", |
| __func__, ret, retries); |
| else |
| break; |
| } |
| if (ret) |
| dev_err(hba->dev, |
| "%s: query attr, opcode %d, idn %d, failed with error %d after %d retires\n", |
| __func__, opcode, idn, ret, retries); |
| return ret; |
| } |
| |
| static int ufsf_read_desc(struct ufs_hba *hba, u8 desc_id, u8 desc_index, |
| u8 selector, u8 *desc_buf, u32 size) |
| { |
| int err = 0; |
| |
| pm_runtime_get_sync(hba->dev); |
| |
| err = ufshcd_query_descriptor_retry(hba, UPIU_QUERY_OPCODE_READ_DESC, |
| desc_id, desc_index, |
| selector, |
| desc_buf, &size); |
| if (err) |
| ERR_MSG("reading Device Desc failed. err = %d", err); |
| |
| pm_runtime_put_sync(hba->dev); |
| |
| return err; |
| } |
| |
| static int ufsf_read_dev_desc(struct ufsf_feature *ufsf, u8 selector) |
| { |
| u8 desc_buf[UFSF_QUERY_DESC_DEVICE_MAX_SIZE] = {0}; |
| int ret; |
| |
| ret = ufsf_read_desc(ufsf->hba, QUERY_DESC_IDN_DEVICE, 0, selector, |
| desc_buf, UFSF_QUERY_DESC_DEVICE_MAX_SIZE); |
| if (ret) |
| return ret; |
| |
| ufsf->num_lu = desc_buf[DEVICE_DESC_PARAM_NUM_LU]; |
| INIT_INFO("device lu count %d", ufsf->num_lu); |
| |
| INIT_INFO("sel=%u length=%u(0x%x) bSupport=0x%.2x, extend=0x%.2x_%.2x", |
| selector, desc_buf[DEVICE_DESC_PARAM_LEN], |
| desc_buf[DEVICE_DESC_PARAM_LEN], |
| desc_buf[DEVICE_DESC_PARAM_FEAT_SUP], |
| desc_buf[DEVICE_DESC_PARAM_EX_FEAT_SUP+2], |
| desc_buf[DEVICE_DESC_PARAM_EX_FEAT_SUP+3]); |
| |
| #if defined(CONFIG_UFSHPB) |
| ufshpb_get_dev_info(&ufsf->hpb_dev_info, desc_buf); |
| #endif |
| |
| #if defined(CONFIG_UFSTW) |
| ufstw_get_dev_info(&ufsf->tw_dev_info, desc_buf); |
| #endif |
| return 0; |
| } |
| |
| static int ufsf_read_geo_desc(struct ufsf_feature *ufsf, u8 selector) |
| { |
| u8 geo_buf[UFSF_QUERY_DESC_GEOMETRY_MAX_SIZE]; |
| int ret; |
| |
| ret = ufsf_read_desc(ufsf->hba, QUERY_DESC_IDN_GEOMETRY, 0, selector, |
| geo_buf, UFSF_QUERY_DESC_GEOMETRY_MAX_SIZE); |
| if (ret) |
| return ret; |
| |
| #if defined(CONFIG_UFSHPB) |
| if (ufsf->hpb_dev_info.hpb_device) |
| ufshpb_get_geo_info(&ufsf->hpb_dev_info, geo_buf); |
| #endif |
| |
| #if defined(CONFIG_UFSTW) |
| if (ufsf->tw_dev_info.tw_device) |
| ufstw_get_geo_info(&ufsf->tw_dev_info, geo_buf); |
| #endif |
| return 0; |
| } |
| |
| static int ufsf_read_unit_desc(struct ufsf_feature *ufsf, |
| unsigned int lun, u8 selector) |
| { |
| u8 unit_buf[UFSF_QUERY_DESC_UNIT_MAX_SIZE]; |
| int lu_enable, ret = 0; |
| |
| ret = ufsf_read_desc(ufsf->hba, QUERY_DESC_IDN_UNIT, lun, selector, |
| unit_buf, UFSF_QUERY_DESC_UNIT_MAX_SIZE); |
| if (ret) { |
| ERR_MSG("read unit desc failed. ret %d", ret); |
| goto out; |
| } |
| |
| lu_enable = unit_buf[UNIT_DESC_PARAM_LU_ENABLE]; |
| if (!lu_enable) |
| return 0; |
| |
| #if defined(CONFIG_UFSHPB) |
| if (ufsf->hpb_dev_info.hpb_device) { |
| ret = ufshpb_get_lu_info(ufsf, lun, unit_buf); |
| if (ret == -ENOMEM) |
| goto out; |
| } |
| #endif |
| |
| #if defined(CONFIG_UFSTW) |
| if (ufsf->tw_dev_info.tw_device) { |
| ret = ufstw_get_lu_info(ufsf, lun, unit_buf); |
| if (ret == -ENOMEM) |
| goto out; |
| } |
| #endif |
| out: |
| return ret; |
| } |
| |
| void ufsf_device_check(struct ufs_hba *hba) |
| { |
| struct ufsf_feature *ufsf = &hba->ufsf; |
| int ret; |
| unsigned int lun; |
| u8 selector = 0; |
| |
| ufsf->slave_conf_cnt = 0; |
| |
| ufsf->hba = hba; |
| |
| if (hba->card->wmanufacturerid == UFS_VENDOR_SAMSUNG || |
| hba->card->wmanufacturerid == UFS_VENDOR_MICRON) |
| selector = UFSFEATURE_SELECTOR; |
| |
| ret = ufsf_read_dev_desc(ufsf, selector); |
| if (ret) |
| return; |
| |
| ret = ufsf_read_geo_desc(ufsf, selector); |
| if (ret) |
| return; |
| |
| seq_scan_lu(lun) { |
| ret = ufsf_read_unit_desc(ufsf, lun, selector); |
| if (ret == -ENOMEM) |
| goto out_free_mem; |
| } |
| /* huangjianan@TECH.Storage.UFS, 2019/12/09, Add for UFS+ RUS */ |
| create_ufsplus_ctrl_proc(ufsf); |
| |
| return; |
| out_free_mem: |
| #if defined(CONFIG_UFSHPB) |
| seq_scan_lu(lun) |
| kfree(ufsf->ufshpb_lup[lun]); |
| |
| /* don't call init handler */ |
| ufsf->ufshpb_state = HPB_NOT_SUPPORTED; |
| #endif |
| #if defined(CONFIG_UFSTW) |
| seq_scan_lu(lun) |
| kfree(ufsf->tw_lup[lun]); |
| |
| ufsf->tw_dev_info.tw_device = false; |
| atomic_set(&ufsf->tw_state, TW_NOT_SUPPORTED); |
| #endif |
| return; |
| } |
| |
| static void ufsf_print_query_buf(unsigned char *field, int size) |
| { |
| unsigned char buf[255]; |
| unsigned int count = 0; |
| int i; |
| |
| count += snprintf(buf, 8, "(0x00):"); |
| |
| for (i = 0; i < size; i++) { |
| count += snprintf(buf + count, 4, " %.2X", field[i]); |
| |
| if ((i + 1) % 16 == 0) { |
| buf[count] = '\n'; |
| buf[count + 1] = '\0'; |
| printk(buf); |
| count = 0; |
| count += snprintf(buf, 8, "(0x%.2X):", i + 1); |
| } else if ((i + 1) % 4 == 0) |
| count += snprintf(buf + count, 3, " :"); |
| } |
| buf[count] = '\n'; |
| buf[count + 1] = '\0'; |
| printk(buf); |
| } |
| |
| inline int ufsf_check_query(__u32 opcode) |
| { |
| return (opcode & 0xffff0000) >> 16 == UFSFEATURE_QUERY_OPCODE; |
| } |
| |
| int ufsf_query_ioctl(struct ufsf_feature *ufsf, unsigned int lun, |
| void __user *buffer, |
| struct ufs_ioctl_query_data_hpb *ioctl_data, u8 selector) |
| { |
| unsigned char *kernel_buf; |
| int opcode; |
| int err = 0; |
| int index = 0; |
| int length = 0; |
| int buf_len = 0; |
| |
| opcode = ioctl_data->opcode & 0xffff; |
| |
| INFO_MSG("op %u idn %u sel %u size %u(0x%X)", opcode, ioctl_data->idn, |
| selector, ioctl_data->buf_size, ioctl_data->buf_size); |
| |
| buf_len = (ioctl_data->idn == QUERY_DESC_IDN_STRING) ? |
| IOCTL_DEV_CTX_MAX_SIZE : QUERY_DESC_MAX_SIZE; |
| if (ioctl_data->buf_size > buf_len) { |
| err = -EINVAL; |
| goto out; |
| } |
| |
| kernel_buf = kzalloc(buf_len, GFP_KERNEL); |
| if (!kernel_buf) { |
| err = -ENOMEM; |
| goto out; |
| } |
| |
| switch (opcode) { |
| case UPIU_QUERY_OPCODE_WRITE_DESC: |
| err = copy_from_user(kernel_buf, buffer + |
| sizeof(struct ufs_ioctl_query_data_hpb), |
| ioctl_data->buf_size); |
| INFO_MSG("buf size %d", ioctl_data->buf_size); |
| ufsf_print_query_buf(kernel_buf, ioctl_data->buf_size); |
| if (err) |
| goto out_release_mem; |
| break; |
| |
| case UPIU_QUERY_OPCODE_READ_DESC: |
| switch (ioctl_data->idn) { |
| case QUERY_DESC_IDN_UNIT: |
| if (!ufs_is_valid_unit_desc_lun(lun)) { |
| ERR_MSG("No unit descriptor for lun 0x%x", lun); |
| err = -EINVAL; |
| goto out_release_mem; |
| } |
| index = lun; |
| INFO_MSG("read lu desc lun: %d", index); |
| break; |
| |
| case QUERY_DESC_IDN_STRING: |
| #if defined(CONFIG_UFSHPB) |
| if (!ufs_is_valid_unit_desc_lun(lun)) { |
| ERR_MSG("No unit descriptor for lun 0x%x", lun); |
| err = -EINVAL; |
| goto out_release_mem; |
| } |
| err = ufshpb_issue_req_dev_ctx(ufsf->ufshpb_lup[lun], |
| kernel_buf, |
| ioctl_data->buf_size); |
| if (err < 0) |
| goto out_release_mem; |
| |
| goto copy_buffer; |
| #endif |
| case QUERY_DESC_IDN_DEVICE: |
| case QUERY_DESC_IDN_GEOMETRY: |
| case QUERY_DESC_IDN_CONFIGURATION: |
| break; |
| |
| default: |
| ERR_MSG("invalid idn %d", ioctl_data->idn); |
| err = -EINVAL; |
| goto out_release_mem; |
| } |
| break; |
| default: |
| ERR_MSG("invalid opcode %d", opcode); |
| err = -EINVAL; |
| goto out_release_mem; |
| } |
| |
| length = ioctl_data->buf_size; |
| |
| err = ufshcd_query_descriptor_retry(ufsf->hba, opcode, ioctl_data->idn, |
| index, selector, kernel_buf, |
| &length); |
| if (err) |
| goto out_release_mem; |
| |
| #if defined(CONFIG_UFSHPB) |
| copy_buffer: |
| #endif |
| if (opcode == UPIU_QUERY_OPCODE_READ_DESC) { |
| err = copy_to_user(buffer, ioctl_data, |
| sizeof(struct ufs_ioctl_query_data_hpb)); |
| if (err) |
| ERR_MSG("Failed copying back to user."); |
| |
| err = copy_to_user(buffer + sizeof(struct ufs_ioctl_query_data_hpb), |
| kernel_buf, ioctl_data->buf_size); |
| if (err) |
| ERR_MSG("Fail: copy rsp_buffer to user space."); |
| } |
| out_release_mem: |
| kfree(kernel_buf); |
| out: |
| return err; |
| } |
| |
| inline bool ufsf_is_valid_lun(int lun) |
| { |
| return lun < UFS_UPIU_MAX_GENERAL_LUN; |
| } |
| |
| inline int ufsf_get_ee_status(struct ufs_hba *hba, u32 *status) |
| { |
| return ufsf_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR, |
| QUERY_ATTR_IDN_EE_STATUS, 0, status); |
| } |
| |
| /* |
| * Wrapper functions for ufshpb. |
| */ |
| #if defined(CONFIG_UFSHPB) |
| inline int ufsf_hpb_prepare_pre_req(struct ufsf_feature *ufsf, |
| struct scsi_cmnd *cmd, int lun) |
| { |
| if (ufsf->ufshpb_state == HPB_PRESENT) |
| return ufshpb_prepare_pre_req(ufsf, cmd, lun); |
| return -ENODEV; |
| } |
| |
| inline int ufsf_hpb_prepare_add_lrbp(struct ufsf_feature *ufsf, int add_tag) |
| { |
| if (ufsf->ufshpb_state == HPB_PRESENT) |
| return ufshpb_prepare_add_lrbp(ufsf, add_tag); |
| return -ENODEV; |
| } |
| |
| inline void ufsf_hpb_end_pre_req(struct ufsf_feature *ufsf, |
| struct request *req) |
| { |
| ufshpb_end_pre_req(ufsf, req); |
| } |
| |
| inline void ufsf_hpb_change_lun(struct ufsf_feature *ufsf, |
| struct ufshcd_lrb *lrbp) |
| { |
| int ctx_lba = LI_EN_32(lrbp->cmd->cmnd + 2); |
| |
| if (ufsf->ufshpb_state == HPB_PRESENT && |
| ufsf->issue_ioctl == true && ctx_lba == READ10_DEBUG_LBA) { |
| lrbp->lun = READ10_DEBUG_LUN; |
| INFO_MSG("lun 0x%X lba 0x%X", lrbp->lun, ctx_lba); |
| } |
| } |
| |
| inline void ufsf_hpb_prep_fn(struct ufsf_feature *ufsf, |
| struct ufshcd_lrb *lrbp) |
| { |
| if (ufsf->ufshpb_state == HPB_PRESENT |
| && ufsf->issue_ioctl == false) |
| ufshpb_prep_fn(ufsf, lrbp); |
| } |
| |
| inline void ufsf_hpb_noti_rb(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp) |
| { |
| if (ufsf->ufshpb_state == HPB_PRESENT) |
| ufshpb_rsp_upiu(ufsf, lrbp); |
| } |
| |
| inline void ufsf_hpb_reset_lu(struct ufsf_feature *ufsf) |
| { |
| ufsf->ufshpb_state = HPB_RESET; |
| schedule_work(&ufsf->ufshpb_reset_work); |
| } |
| |
| inline void ufsf_hpb_reset_host(struct ufsf_feature *ufsf) |
| { |
| if (ufsf->ufshpb_state == HPB_PRESENT) |
| ufsf->ufshpb_state = HPB_RESET; |
| } |
| |
| inline void ufsf_hpb_init(struct ufsf_feature *ufsf) |
| { |
| if (ufsf->hpb_dev_info.hpb_device && |
| ufsf->ufshpb_state == HPB_NEED_INIT) { |
| INIT_WORK(&ufsf->ufshpb_init_work, ufshpb_init_handler); |
| schedule_work(&ufsf->ufshpb_init_work); |
| } |
| } |
| |
| inline void ufsf_hpb_reset(struct ufsf_feature *ufsf) |
| { |
| if (ufsf->hpb_dev_info.hpb_device && |
| ufsf->ufshpb_state == HPB_RESET) |
| schedule_work(&ufsf->ufshpb_reset_work); |
| } |
| |
| inline void ufsf_hpb_suspend(struct ufsf_feature *ufsf) |
| { |
| if (ufsf->ufshpb_state == HPB_PRESENT) |
| ufshpb_suspend(ufsf); |
| } |
| |
| inline void ufsf_hpb_resume(struct ufsf_feature *ufsf) |
| { |
| if (ufsf->ufshpb_state == HPB_PRESENT) |
| ufshpb_resume(ufsf); |
| } |
| |
| inline void ufsf_hpb_release(struct ufsf_feature *ufsf) |
| { |
| ufshpb_release(ufsf, HPB_NEED_INIT); |
| } |
| |
| inline void ufsf_hpb_set_init_state(struct ufsf_feature *ufsf) |
| { |
| ufsf->ufshpb_state = HPB_NEED_INIT; |
| } |
| #else |
| inline int ufsf_hpb_prepare_pre_req(struct ufsf_feature *ufsf, |
| struct scsi_cmnd *cmd, int lun) |
| { |
| return 0; |
| } |
| |
| inline int ufsf_hpb_prepare_add_lrbp(struct ufsf_feature *ufsf, int add_tag) |
| { |
| return 0; |
| } |
| |
| inline void ufsf_hpb_end_pre_req(struct ufsf_feature *ufsf, |
| struct request *req) {} |
| inline void ufsf_hpb_change_lun(struct ufsf_feature *ufsf, |
| struct ufshcd_lrb *lrbp) {} |
| inline void ufsf_hpb_prep_fn(struct ufsf_feature *ufsf, |
| struct ufshcd_lrb *lrbp) {} |
| inline void ufsf_hpb_noti_rb(struct ufsf_feature *ufsf, |
| struct ufshcd_lrb *lrbp) {} |
| inline void ufsf_hpb_reset_lu(struct ufsf_feature *ufsf) {} |
| inline void ufsf_hpb_reset_host(struct ufsf_feature *ufsf) {} |
| inline void ufsf_hpb_init(struct ufsf_feature *ufsf) {} |
| inline void ufsf_hpb_reset(struct ufsf_feature *ufsf) {} |
| inline void ufsf_hpb_suspend(struct ufsf_feature *ufsf) {} |
| inline void ufsf_hpb_resume(struct ufsf_feature *ufsf) {} |
| inline void ufsf_hpb_release(struct ufsf_feature *ufsf) {} |
| inline void ufsf_hpb_set_init_state(struct ufsf_feature *ufsf) {} |
| #endif |
| |
| /* |
| * Wrapper functions for ufstw. |
| */ |
| |
| #if defined(CONFIG_UFSTW) |
| inline void ufsf_tw_prep_fn(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp) |
| { |
| ufstw_prep_fn(ufsf, lrbp); |
| } |
| |
| inline void ufsf_tw_init(struct ufsf_feature *ufsf) |
| { |
| INIT_INFO("init start.. tw_state %d\n", |
| atomic_read(&ufsf->tw_state)); |
| |
| if (ufsf->tw_dev_info.tw_device && |
| atomic_read(&ufsf->tw_state) == TW_NEED_INIT) { |
| INIT_WORK(&ufsf->tw_init_work, ufstw_init_work_fn); |
| schedule_work(&ufsf->tw_init_work); |
| } |
| } |
| |
| inline void ufsf_tw_reset(struct ufsf_feature *ufsf) |
| { |
| INIT_INFO("reset start.. tw_state %d\n", |
| atomic_read(&ufsf->tw_state)); |
| |
| if (ufsf->tw_dev_info.tw_device && |
| atomic_read(&ufsf->tw_state) == TW_RESET) |
| schedule_work(&ufsf->tw_reset_work); |
| } |
| |
| inline void ufsf_tw_suspend(struct ufsf_feature *ufsf) |
| { |
| if (atomic_read(&ufsf->tw_state) == TW_PRESENT) |
| ufstw_suspend(ufsf); |
| } |
| |
| inline void ufsf_tw_resume(struct ufsf_feature *ufsf) |
| { |
| if (atomic_read(&ufsf->tw_state) == TW_PRESENT) |
| ufstw_resume(ufsf); |
| } |
| |
| inline void ufsf_tw_release(struct ufsf_feature *ufsf) |
| { |
| ufstw_release(&ufsf->tw_kref); |
| } |
| |
| inline void ufsf_tw_set_init_state(struct ufsf_feature *ufsf) |
| { |
| atomic_set(&ufsf->tw_state, TW_NEED_INIT); |
| } |
| |
| inline void ufsf_tw_reset_lu(struct ufsf_feature *ufsf) |
| { |
| INFO_MSG("run reset_lu.. tw_state(%d) -> TW_RESET", |
| atomic_read(&ufsf->tw_state)); |
| atomic_set(&ufsf->tw_state, TW_RESET); |
| if (ufsf->tw_dev_info.tw_device) |
| schedule_work(&ufsf->tw_reset_work); |
| } |
| |
| inline void ufsf_tw_reset_host(struct ufsf_feature *ufsf) |
| { |
| INFO_MSG("run reset_host.. tw_state(%d) -> TW_RESET", |
| atomic_read(&ufsf->tw_state)); |
| if (atomic_read(&ufsf->tw_state) == TW_PRESENT) |
| atomic_set(&ufsf->tw_state, TW_RESET); |
| } |
| |
| inline void ufsf_tw_ee_handler(struct ufsf_feature *ufsf) |
| { |
| u32 status = 0; |
| int err; |
| |
| if (ufsf->tw_debug && (atomic_read(&ufsf->tw_state) != TW_PRESENT)) { |
| ERR_MSG("tw_state %d", atomic_read(&ufsf->tw_state)); |
| return; |
| } |
| |
| if ((atomic_read(&ufsf->tw_state) == TW_PRESENT) |
| && (ufsf->tw_ee_mode == TW_EE_MODE_AUTO)) { |
| err = ufsf_get_ee_status(ufsf->hba, &status); |
| if (err) { |
| dev_err(ufsf->hba->dev, |
| "%s: failed to get tw ee status %d\n", |
| __func__, err); |
| return; |
| } |
| if (status & MASK_EE_TW) |
| ufstw_ee_handler(ufsf); |
| } |
| } |
| #else |
| inline void ufsf_tw_prep_fn(struct ufsf_feature *ufsf, |
| struct ufshcd_lrb *lrbp) {} |
| inline void ufsf_tw_init(struct ufsf_feature *ufsf) {} |
| inline void ufsf_tw_reset(struct ufsf_feature *ufsf) {} |
| inline void ufsf_tw_suspend(struct ufsf_feature *ufsf) {} |
| inline void ufsf_tw_resume(struct ufsf_feature *ufsf) {} |
| inline void ufsf_tw_release(struct ufsf_feature *ufsf) {} |
| inline void ufsf_tw_set_init_state(struct ufsf_feature *ufsf) {} |
| inline void ufsf_tw_reset_lu(struct ufsf_feature *ufsf) {} |
| inline void ufsf_tw_reset_host(struct ufsf_feature *ufsf) {} |
| inline void ufsf_tw_ee_handler(struct ufsf_feature *ufsf) {} |
| #endif |
| |
| /* huangjianan@TECH.Storage.UFS, 2019/12/09, Add for UFS+ RUS */ |
| int create_ufsplus_ctrl_proc(struct ufsf_feature *ufsf) |
| { |
| ufsf_para.ctrl_dir = NULL; |
| ufsf_para.ufsf = NULL; |
| |
| ufsf_para.ctrl_dir = proc_mkdir("ufsplus_ctrl", NULL); |
| if (!ufsf_para.ctrl_dir) |
| return -ENOMEM; |
| ufsf_para.ufsf = ufsf; |
| |
| return 0; |
| } |
| |
| void remove_ufsplus_ctrl_proc(void) |
| { |
| if (ufsf_para.ctrl_dir) { |
| remove_proc_entry("ufsplus_ctrl", NULL); |
| ufsf_para.ctrl_dir = NULL; |
| ufsf_para.ufsf = NULL; |
| } |
| |
| return; |
| } |