From 3149e59afced68289e046fb1436489d75426e9c2 Mon Sep 17 00:00:00 2001 From: zhucc2 Date: Tue, 6 Jul 2021 10:19:54 +0800 Subject: [PATCH] scsi/ufs: port SamSung UFS HID feature. this patch is supply by SS, and based on sm4350, we also add macros to compatible with other vendors. Change-Id: Iea082bfbc8463f7cb23bad5afe2bb0b6118dab92 Reviewed-on: https://gerrit.mot.com/2002941 SME-Granted: SME Approvals Granted SLTApproved: Slta Waiver Tested-by: Jira Key Reviewed-by: Huosheng Liao Submit-Approved: Jira Key --- drivers/scsi/ufs/Kconfig | 18 +- drivers/scsi/ufs/Makefile | 1 + drivers/scsi/ufs/ufs-qcom.c | 2 +- drivers/scsi/ufs/ufs.h | 7 + drivers/scsi/ufs/ufsfeature.c | 53 +- drivers/scsi/ufs/ufsfeature.h | 8 +- drivers/scsi/ufs/ufshcd.c | 32 +- drivers/scsi/ufs/ufshcd.h | 3 + drivers/scsi/ufs/ufshid.c | 949 ++++++++++++++++++++++++++++++++++ drivers/scsi/ufs/ufshid.h | 144 ++++++ 10 files changed, 1204 insertions(+), 13 deletions(-) create mode 100755 drivers/scsi/ufs/ufshid.c create mode 100755 drivers/scsi/ufs/ufshid.h diff --git a/drivers/scsi/ufs/Kconfig b/drivers/scsi/ufs/Kconfig index f71efcca00c0..fbb422dfb846 100644 --- a/drivers/scsi/ufs/Kconfig +++ b/drivers/scsi/ufs/Kconfig @@ -243,6 +243,22 @@ config UFSTW_BOOT_ENABLED fTurboWriteEn and fTurboWriteBufferFlushDuringHibnerEnter flags are enabled at boot time. +config UFSHID + bool "UFS Host Initiated Defrag support" + depends on SCSI_UFSHCD && UFSFEATURE + ---help--- + UFS HID Feature Enable + +config UFSHID_POC + bool "UFSHID_POC test" + depends on SCSI_UFSHCD && UFSFEATURE && UFSHID + ---help--- + UFS HID POC test + 1. block to enter suspend state + 2. auto_hibern8 enable/disable (HCI) + 3. set flag bDefragOperation + 4. debug msg enable (default : disable) + config SCSI_SKHPB bool "Activate HPB Host-aware Performance Booster" depends on (SCSI_UFSHCD || SCSI_UFSHCD_QTI) @@ -250,7 +266,7 @@ config SCSI_SKHPB Activate or deactive SKHPB driver config SCSI_SKHID - bool "Activate HPB Host-aware Performance Booster" + bool "UFS Host Initiated Defrag supportr" depends on (SCSI_UFSHCD || SCSI_UFSHCD_QTI) help Activate or deactive SKHPB driver diff --git a/drivers/scsi/ufs/Makefile b/drivers/scsi/ufs/Makefile index f0d24fac1ee8..f1e3eaa10a05 100644 --- a/drivers/scsi/ufs/Makefile +++ b/drivers/scsi/ufs/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_SCSI_UFSHCD) += ufshcd-core.o obj-$(CONFIG_UFSFEATURE) += ufsfeature.o obj-$(CONFIG_UFSHPB) += ufshpb.o obj-$(CONFIG_UFSTW) += ufstw.o +obj-$(CONFIG_UFSHID) += ufshid.o ufshcd-core-y += ufshcd.o ufs-sysfs.o ufshcd-core-$(CONFIG_SCSI_UFS_BSG) += ufs_bsg.o obj-$(CONFIG_SCSI_SKHPB) += ufshpb_skh.o diff --git a/drivers/scsi/ufs/ufs-qcom.c b/drivers/scsi/ufs/ufs-qcom.c index 8d976bc3e3e0..5ade5e502898 100644 --- a/drivers/scsi/ufs/ufs-qcom.c +++ b/drivers/scsi/ufs/ufs-qcom.c @@ -2259,7 +2259,7 @@ ufs_qcom_query_ioctl(struct ufs_hba *hba, u8 lun, void __user *buffer) #if defined(CONFIG_UFSFEATURE) if (ufsf_check_query(ioctl_data->opcode)) { - err = ufsf_query_ioctl(hba, lun, buffer, ioctl_data, + err = ufsf_query_ioctl(&hba->ufsf, lun, buffer, ioctl_data, UFSFEATURE_SELECTOR); goto out_release_mem; } diff --git a/drivers/scsi/ufs/ufs.h b/drivers/scsi/ufs/ufs.h index a77312b8a5fb..dfe046fd8f3d 100644 --- a/drivers/scsi/ufs/ufs.h +++ b/drivers/scsi/ufs/ufs.h @@ -198,6 +198,10 @@ enum attr_idn { QUERY_ATTR_IDN_TW_BUF_LIFETIME_EST = 0x1E, QUERY_ATTR_IDN_TW_CURR_BUF_SIZE = 0x1F, #endif +#if defined(CONFIG_UFSHID) + QUERY_ATTR_IDN_HID_OPERATION = 0x20, + QUERY_ATTR_IDN_HID_FRAG_LEVEL = 0x21, +#endif #if defined(CONFIG_SCSI_SKHID) /* use one reserved bit */ QUERY_ATTR_IDN_MANUAL_GC_CONT = 0x12, @@ -325,6 +329,9 @@ enum device_desc_param { DEVICE_DESC_PARAM_TW_BUF_TYPE = 0x54, DEVICE_DESC_PARAM_TW_SHARED_BUF_ALLOC_UNITS = 0x55, #endif +#if defined(CONFIG_UFSHID) + DEVICE_DESC_PARAM_HID_VER = 0x59, +#endif }; /* Interconnect descriptor parameters offsets in bytes*/ diff --git a/drivers/scsi/ufs/ufsfeature.c b/drivers/scsi/ufs/ufsfeature.c index 3cb57d2d412a..71122ab54e65 100755 --- a/drivers/scsi/ufs/ufsfeature.c +++ b/drivers/scsi/ufs/ufsfeature.c @@ -218,6 +218,10 @@ static int ufsf_read_dev_desc(struct ufsf_feature *ufsf, u8 selector) #if defined(CONFIG_UFSTW) ufstw_get_dev_info(ufsf, desc_buf); #endif + +#if defined(CONFIG_UFSHID) + ufshid_get_dev_info(ufsf, desc_buf); +#endif return 0; } @@ -393,7 +397,7 @@ static inline void ufsf_set_read10_debug_cmd(unsigned char *cdb, int lba, cdb[8] = GET_BYTE_0(len); } -int ufsf_query_ioctl(struct ufs_hba *hba, int lun, void __user *buffer, +int ufsf_query_ioctl(struct ufsf_feature *ufsf, int lun, void __user *buffer, struct ufs_ioctl_query_data *ioctl_data, u8 selector) { unsigned char *kernel_buf; @@ -439,7 +443,7 @@ int ufsf_query_ioctl(struct ufs_hba *hba, int lun, void __user *buffer, index = lun; INFO_MSG("read lu desc lun: %d", index); break; -#if 0 + case QUERY_DESC_IDN_STRING: if (!ufs_is_valid_unit_desc_lun(lun)) { ERR_MSG("No unit descriptor for lun 0x%x", lun); @@ -452,7 +456,6 @@ int ufsf_query_ioctl(struct ufs_hba *hba, int lun, void __user *buffer, goto out_release_mem; goto copy_buffer; -#endif case QUERY_DESC_IDN_DEVICE: case QUERY_DESC_IDN_GEOMETRY: case QUERY_DESC_IDN_CONFIGURATION: @@ -472,15 +475,13 @@ int ufsf_query_ioctl(struct ufs_hba *hba, int lun, void __user *buffer, length = ioctl_data->buf_size; - err = ufshcd_query_descriptor_retry(hba, opcode, ioctl_data->idn, + err = ufshcd_query_descriptor_retry(ufsf->hba, opcode, ioctl_data->idn, index, selector, kernel_buf, &length); if (err) goto out_release_mem; -#if 0 -copy_buffer: -#endif +copy_buffer: if (opcode == UPIU_QUERY_OPCODE_READ_DESC) { err = copy_to_user(buffer, ioctl_data, sizeof(struct ufs_ioctl_query_data)); @@ -584,6 +585,12 @@ inline void ufsf_reset_host(struct ufsf_feature *ufsf) if (ufstw_get_state(ufsf) == TW_PRESENT) ufstw_reset_host(ufsf); #endif +#if defined(CONFIG_UFSHID) + INFO_MSG("run reset_host.. hid_state(%d) -> HID_RESET", + ufshid_get_state(ufsf)); + if (ufshid_get_state(ufsf) == HID_PRESENT) + ufshid_reset_host(ufsf); +#endif } inline void ufsf_init(struct ufsf_feature *ufsf) @@ -599,6 +606,10 @@ inline void ufsf_init(struct ufsf_feature *ufsf) if (ufstw_get_state(ufsf) == TW_NEED_INIT) ufstw_init(ufsf); #endif +#if defined(CONFIG_UFSHID) + if (ufshid_get_state(ufsf) == HID_NEED_INIT) + ufshid_init(ufsf); +#endif } inline void ufsf_reset(struct ufsf_feature *ufsf) @@ -618,6 +629,10 @@ inline void ufsf_reset(struct ufsf_feature *ufsf) ufstw_reset(ufsf, false); } #endif +#if defined(CONFIG_UFSHID) + if (ufshid_get_state(ufsf) == HID_RESET) + ufshid_reset(ufsf); +#endif } inline void ufsf_remove(struct ufsf_feature *ufsf) @@ -631,6 +646,10 @@ inline void ufsf_remove(struct ufsf_feature *ufsf) if (ufstw_get_state(ufsf) == TW_PRESENT) ufstw_remove(ufsf); #endif +#if defined(CONFIG_UFSHID) + if (ufshid_get_state(ufsf) == HID_PRESENT) + ufshid_remove(ufsf); +#endif } inline void ufsf_set_init_state(struct ufsf_feature *ufsf) @@ -649,6 +668,9 @@ inline void ufsf_set_init_state(struct ufsf_feature *ufsf) #if defined(CONFIG_UFSTW) ufstw_set_state(ufsf, TW_NEED_INIT); #endif +#if defined(CONFIG_UFSHID) + ufshid_set_state(ufsf, HID_NEED_INIT); +#endif } inline void ufsf_suspend(struct ufsf_feature *ufsf) @@ -662,6 +684,10 @@ inline void ufsf_suspend(struct ufsf_feature *ufsf) if ((ufshpb_get_state(ufsf) == HPB_PRESENT) && IS_RAM_SIZE_GREATER_THAN_4G(ram_size)) ufshpb_suspend(ufsf); #endif +#if defined(CONFIG_UFSHID) + if (ufshid_get_state(ufsf) == HID_PRESENT) + ufshid_suspend(ufsf); +#endif } inline void ufsf_resume(struct ufsf_feature *ufsf) @@ -680,8 +706,21 @@ inline void ufsf_resume(struct ufsf_feature *ufsf) if (ufstw_get_state(ufsf) == TW_RESET) ufstw_reset(ufsf, true); #endif + +#if defined(CONFIG_UFSHID) + if (ufshid_get_state(ufsf) == HID_SUSPEND) + ufshid_resume(ufsf); +#endif } +inline void ufsf_on_idle(struct ufsf_feature *ufsf, bool scsi_req) +{ +#if defined(CONFIG_UFSHID) + if (ufshid_get_state(ufsf) == HID_PRESENT && + !ufsf->hba->outstanding_reqs && scsi_req) + ufshid_on_idle(ufsf); +#endif +} inline void ufsf_change_lun(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp) diff --git a/drivers/scsi/ufs/ufsfeature.h b/drivers/scsi/ufs/ufsfeature.h index 4b5b7b56adb2..ca0477745a8f 100755 --- a/drivers/scsi/ufs/ufsfeature.h +++ b/drivers/scsi/ufs/ufsfeature.h @@ -44,6 +44,7 @@ #include "ufshpb.h" #include "ufstw.h" +#include "ufshid.h" /* Version info */ #define UFSFEATURE_DD_VER 0x010100 @@ -157,6 +158,10 @@ struct ufsf_feature { struct ufstw_lu *tw_lup[UFS_UPIU_MAX_GENERAL_LUN]; atomic_t tw_state; #endif +#if defined(CONFIG_UFSHID) + atomic_t hid_state; + struct ufshid_dev *hid_dev; +#endif }; struct ufs_hba; @@ -165,7 +170,7 @@ struct ufs_ioctl_query_data; void ufsf_device_check(struct ufs_hba *hba); int ufsf_check_query(__u32 opcode); -int ufsf_query_ioctl(struct ufs_hba *hba, int lun, void __user *buffer, +int ufsf_query_ioctl(struct ufsf_feature *ufsf, int lun, void __user *buffer, struct ufs_ioctl_query_data *ioctl_data, u8 selector); int ufsf_query_flag_retry(struct ufs_hba *hba, enum query_opcode opcode, @@ -186,6 +191,7 @@ void ufsf_remove(struct ufsf_feature *ufsf); void ufsf_set_init_state(struct ufsf_feature *ufsf); void ufsf_suspend(struct ufsf_feature *ufsf); void ufsf_resume(struct ufsf_feature *ufsf); +void ufsf_on_idle(struct ufsf_feature *ufsf, bool scsi_req); /* for hpb */ void ufsf_hpb_noti_rb(struct ufsf_feature *ufsf, struct ufshcd_lrb *lrbp); diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index 87d266162cd1..0ca91b71002d 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -423,13 +423,21 @@ static inline void ufshcd_wb_config(struct ufs_hba *hba) ufshcd_wb_toggle_flush(hba, true); } +#if defined(CONFIG_UFSFEATURE) +void ufshcd_scsi_unblock_requests(struct ufs_hba *hba) +#else static void ufshcd_scsi_unblock_requests(struct ufs_hba *hba) +#endif { if (atomic_dec_and_test(&hba->scsi_block_reqs_cnt)) scsi_unblock_requests(hba->host); } +#if defined(CONFIG_UFSFEATURE) +void ufshcd_scsi_block_requests(struct ufs_hba *hba) +#else static void ufshcd_scsi_block_requests(struct ufs_hba *hba) +#endif { if (atomic_inc_return(&hba->scsi_block_reqs_cnt) == 1) scsi_block_requests(hba->host); @@ -1208,8 +1216,12 @@ static bool ufshcd_is_devfreq_scaling_required(struct ufs_hba *hba, return false; } +#if defined(CONFIG_UFSFEATURE) +int ufshcd_wait_for_doorbell_clr(struct ufs_hba *hba, u64 wait_timeout_us) +#else static int ufshcd_wait_for_doorbell_clr(struct ufs_hba *hba, u64 wait_timeout_us) +#endif { unsigned long flags; int ret = 0; @@ -5380,6 +5392,9 @@ static void __ufshcd_transfer_req_compl(struct ufs_hba *hba, struct scsi_cmnd *cmd; int result; int index; +#if defined(CONFIG_UFSFEATURE) + bool scsi_req = false; +#endif for_each_set_bit(index, &completed_reqs, hba->nutrs) { lrbp = &hba->lrb[index]; @@ -5405,6 +5420,10 @@ static void __ufshcd_transfer_req_compl(struct ufs_hba *hba, /* Do not touch lrbp after scsi done */ cmd->scsi_done(cmd); __ufshcd_release(hba); +#if defined(CONFIG_UFSFEATURE) + if (IS_SAMSUNG_DEVICE(storage_mfrid)) + scsi_req = true; +#endif } else if (lrbp->command_type == UTP_CMD_TYPE_DEV_MANAGE || lrbp->command_type == UTP_CMD_TYPE_UFS_STORAGE) { lrbp->compl_time_stamp = ktime_get(); @@ -5425,6 +5444,10 @@ static void __ufshcd_transfer_req_compl(struct ufs_hba *hba, /* we might have free'd some tags above */ wake_up(&hba->dev_cmd.tag_wq); +#if defined(CONFIG_UFSFEATURE) + if (IS_SAMSUNG_DEVICE(storage_mfrid)) + ufsf_on_idle(&hba->ufsf, scsi_req); +#endif } /** @@ -9618,7 +9641,8 @@ void ufshcd_remove(struct ufs_hba *hba) ufshcd_disable_intr(hba, hba->intr_mask); ufshcd_hba_stop(hba, true); #if defined(CONFIG_SCSI_SKHID) - ufshcd_exit_manual_gc(hba); + if (IS_SKHYNIX_DEVICE(storage_mfrid)) + ufshcd_exit_manual_gc(hba); #endif ufshcd_exit_clk_scaling(hba); ufshcd_exit_clk_gating(hba); @@ -9805,7 +9829,8 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) ufshcd_init_clk_scaling(hba); #if defined(CONFIG_SCSI_SKHID) - ufshcd_init_manual_gc(hba); + if (IS_SKHYNIX_DEVICE(storage_mfrid)) + ufshcd_init_manual_gc(hba); #endif /* * In order to avoid any spurious interrupt immediately after @@ -9912,7 +9937,8 @@ out_remove_scsi_host: scsi_remove_host(hba->host); exit_gating: #if defined(CONFIG_SCSI_SKHID) - ufshcd_exit_manual_gc(hba); + if (IS_SKHYNIX_DEVICE(storage_mfrid)) + ufshcd_exit_manual_gc(hba); #endif ufshcd_exit_clk_scaling(hba); ufshcd_exit_clk_gating(hba); diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h index 989cc4eadebf..aeba90206c3e 100644 --- a/drivers/scsi/ufs/ufshcd.h +++ b/drivers/scsi/ufs/ufshcd.h @@ -1356,6 +1356,9 @@ void ufshcd_fixup_dev_quirks(struct ufs_hba *hba, struct ufs_dev_fix *fixups); #define SD_RAW false #if defined(CONFIG_UFSFEATURE) +void ufshcd_scsi_unblock_requests(struct ufs_hba *hba); +void ufshcd_scsi_block_requests(struct ufs_hba *hba); +int ufshcd_wait_for_doorbell_clr(struct ufs_hba *hba, u64 wait_timeout_us); int ufshcd_exec_dev_cmd(struct ufs_hba *hba, enum dev_cmd_type cmd_type, int timeout); #endif diff --git a/drivers/scsi/ufs/ufshid.c b/drivers/scsi/ufs/ufshid.c new file mode 100755 index 000000000000..1f10b9f53a99 --- /dev/null +++ b/drivers/scsi/ufs/ufshid.c @@ -0,0 +1,949 @@ +/* + * Universal Flash Storage Host Initiated Defrag (UFS HID) + * + * Copyright (C) 2019-2019 Samsung Electronics Co., Ltd. + * + * Authors: + * Yongmyung Lee + * Jieon Seol + * + * 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 + * + * + * 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 "ufshcd.h" +#include "ufsfeature.h" +#include "ufshid.h" + +static int ufshid_create_sysfs(struct ufshid_dev *hid); + +inline int ufshid_get_state(struct ufsf_feature *ufsf) +{ + return atomic_read(&ufsf->hid_state); +} + +inline void ufshid_set_state(struct ufsf_feature *ufsf, int state) +{ + atomic_set(&ufsf->hid_state, state); +} + +static inline int ufshid_is_not_present(struct ufshid_dev *hid) +{ + enum UFSHID_STATE cur_state = ufshid_get_state(hid->ufsf); + + if (cur_state != HID_PRESENT) { + INFO_MSG("hid_state != HID_PRESENT (%d)", cur_state); + return -ENODEV; + } + return 0; +} + +static int ufshid_read_attr(struct ufshid_dev *hid, u8 idn, u32 *attr_val) +{ + struct ufs_hba *hba = hid->ufsf->hba; + int ret = 0; + + pm_runtime_get_sync(hba->dev); + + ret = ufsf_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR, idn, 0, + attr_val); + if (ret) { + ERR_MSG("read attr [0x%.2X] fail. (%d)", idn, ret); + goto err_out; + } + + HID_DEBUG(hid, "hid_attr read [0x%.2X] %u (0x%X)", idn, *attr_val, + *attr_val); + TMSG(hid->ufsf, 0, "[ufshid] read_attr IDN %s (%d)", + idn == QUERY_ATTR_IDN_HID_OPERATION ? "HID_OP" : + idn == QUERY_ATTR_IDN_HID_FRAG_LEVEL ? "HID_LEV" : "UNKNOWN", idn); +err_out: + pm_runtime_mark_last_busy(hba->dev); + pm_runtime_put_noidle(hba->dev); + + return ret; +} + +static int ufshid_write_attr(struct ufshid_dev *hid, u8 idn, u32 val) +{ + struct ufs_hba *hba = hid->ufsf->hba; + int ret = 0; + + pm_runtime_get_sync(hba->dev); + + ret = ufsf_query_attr_retry(hba, UPIU_QUERY_OPCODE_WRITE_ATTR, idn, 0, + &val); + if (ret) { + ERR_MSG("write attr [0x%.2X] fail. (%d)", idn, ret); + goto err_out; + } + + HID_DEBUG(hid, "hid_attr write [0x%.2X] %u (0x%X)", idn, val, val); + TMSG(hid->ufsf, 0, "[ufshid] write_attr IDN %s (%d)", + idn == QUERY_ATTR_IDN_HID_OPERATION ? "HID_OP" : + idn == QUERY_ATTR_IDN_HID_FRAG_LEVEL ? "HID_LEV" : "UNKNOWN", idn); +err_out: + pm_runtime_mark_last_busy(hba->dev); + pm_runtime_put_noidle(hba->dev); + + return ret; +} + +static inline int ufshid_version_check(int spec_version) +{ + INFO_MSG("Support HID Spec : Driver = (%.4x), Device = (%.4x)", + UFSHID_VER, spec_version); + INFO_MSG("HID Driver version (%.6X%s)", + UFSHID_DD_VER, UFSHID_DD_VER_POST); + + if (spec_version != UFSHID_VER) { + ERR_MSG("UFS HID version mismatched"); + return -ENODEV; + } + return 0; +} + +void ufshid_get_dev_info(struct ufsf_feature *ufsf, u8 *desc_buf) +{ + int ret = 0, spec_version; + + ufsf->hid_dev = NULL; + + if (!(LI_EN_32(&desc_buf[DEVICE_DESC_PARAM_EX_FEAT_SUP]) & + UFS_FEATURE_SUPPORT_HID_BIT)) { + INFO_MSG("bUFSExFeaturesSupport: HID not support"); + goto err_out; + } + + INFO_MSG("bUFSExFeaturesSupport: HID support"); + spec_version = + LI_EN_16(&desc_buf[DEVICE_DESC_PARAM_HID_VER]); + ret = ufshid_version_check(spec_version); + if (ret) + goto err_out; + + ufsf->hid_dev = kzalloc(sizeof(struct ufshid_dev), GFP_KERNEL); + if (!ufsf->hid_dev) { + ERR_MSG("hid_dev memalloc fail"); + goto err_out; + } + + ufsf->hid_dev->ufsf = ufsf; + return; +err_out: + ufshid_set_state(ufsf, HID_FAILED); +} + +static int ufshid_get_analyze_and_issue_execute(struct ufshid_dev *hid) +{ + u32 attr_val; + int frag_level; + + if (ufshid_write_attr(hid, QUERY_ATTR_IDN_HID_OPERATION, + HID_OP_EXECUTE)) + return -EINVAL; + if (ufshid_read_attr(hid, QUERY_ATTR_IDN_HID_FRAG_LEVEL, &attr_val)) + return -EINVAL; + + frag_level = attr_val & HID_FRAG_LEVEL_MASK; + HID_DEBUG(hid, "Frag_lv %d Freg_stat %d HID_need_exec %d", + frag_level, HID_FRAG_UPDATE_STAT(attr_val), + HID_EXECUTE_REQ_STAT(attr_val)); + + if (frag_level == HID_LEV_GRAY) + return -EAGAIN; + + return (HID_EXECUTE_REQ_STAT(attr_val)) ? + HID_REQUIRED : HID_NOT_REQUIRED; +} + +static int ufshid_issue_disable(struct ufshid_dev *hid) +{ + u32 attr_val; + + if (ufshid_write_attr(hid, QUERY_ATTR_IDN_HID_OPERATION, + HID_OP_DISABLE)) + return -EINVAL; + if (ufshid_read_attr(hid, QUERY_ATTR_IDN_HID_FRAG_LEVEL, &attr_val)) + return -EINVAL; + + HID_DEBUG(hid, "Frag_lv %d Freg_stat %d HID_need_exec %d", + attr_val & HID_FRAG_LEVEL_MASK, + HID_FRAG_UPDATE_STAT(attr_val), + HID_EXECUTE_REQ_STAT(attr_val)); + + return 0; +} + +/* + * Lock status: hid_sysfs lock was held when called. + */ +static void ufshid_auto_hibern8_enable(struct ufshid_dev *hid, + unsigned int val) +{ + struct ufs_hba *hba = hid->ufsf->hba; + unsigned long flags; + u32 reg; + + val = !!val; + + /* Update auto hibern8 timer value if supported */ + if (!ufshcd_is_auto_hibern8_supported(hba)) + return; + + pm_runtime_get_sync(hba->dev); + ufshcd_hold(hba, false); + down_write(&hba->clk_scaling_lock); + ufshcd_scsi_block_requests(hba); + /* wait for all the outstanding requests to finish */ + ufshcd_wait_for_doorbell_clr(hba, U64_MAX); + spin_lock_irqsave(hba->host->host_lock, flags); + + reg = ufshcd_readl(hba, REG_AUTO_HIBERNATE_IDLE_TIMER); + INFO_MSG("ahit-reg 0x%X", reg); + + if (val ^ (reg == hba->ahit)) { + ufshcd_writel(hba, val ? hba->ahit : HID_AUTO_HIBERN8_DISABLE, + REG_AUTO_HIBERNATE_IDLE_TIMER); + /* Make sure the timer gets applied before further operations */ + mb(); + + INFO_MSG("[Before] is_auto_enabled %d", hid->is_auto_enabled); + hid->is_auto_enabled = val; + + reg = ufshcd_readl(hba, REG_AUTO_HIBERNATE_IDLE_TIMER); + INFO_MSG("[After] is_auto_enabled %d ahit-reg 0x%X", + hid->is_auto_enabled, reg); + } else { + INFO_MSG("is_auto_enabled %d. so it does not changed", + hid->is_auto_enabled); + } + + spin_unlock_irqrestore(hba->host->host_lock, flags); + ufshcd_scsi_unblock_requests(hba); + up_write(&hba->clk_scaling_lock); + ufshcd_release(hba); + pm_runtime_mark_last_busy(hba->dev); + pm_runtime_put_noidle(hba->dev); +} + +static void ufshid_block_enter_suspend(struct ufshid_dev *hid) +{ + struct ufs_hba *hba = hid->ufsf->hba; + unsigned long flags; + +#if defined(CONFIG_UFSHID_POC) + if (unlikely(hid->block_suspend)) + return; + + hid->block_suspend = true; +#endif + pm_runtime_get_sync(hba->dev); + ufshcd_hold(hba, false); + + spin_lock_irqsave(hba->host->host_lock, flags); + HID_DEBUG(hid, + "dev->power.usage_count %d hba->clk_gating.active_reqs %d", + atomic_read(&hba->dev->power.usage_count), + hba->clk_gating.active_reqs); + spin_unlock_irqrestore(hba->host->host_lock, flags); +} + +/* + * If the return value is not err, pm_runtime_put_noidle() must be called once. + * IMPORTANT : ufshid_hold_runtime_pm() & ufshid_release_runtime_pm() pair. + */ +static int ufshid_hold_runtime_pm(struct ufshid_dev *hid) +{ + struct ufs_hba *hba = hid->ufsf->hba; + + if (ufshid_get_state(hid->ufsf) == HID_SUSPEND) { + /* Check that device was suspended by System PM */ + pm_runtime_get_sync(hba->dev); + + /* If it success, device was suspended by Runtime PM */ + if (ufshid_get_state(hid->ufsf) == HID_PRESENT && + hba->curr_dev_pwr_mode == UFS_ACTIVE_PWR_MODE && + hba->uic_link_state == UIC_LINK_ACTIVE_STATE) + goto resume_success; + + INFO_MSG("RPM resume failed. Maybe it was SPM suspend"); + INFO_MSG("UFS state (POWER = %d LINK = %d)", + hba->curr_dev_pwr_mode, hba->uic_link_state); + + pm_runtime_mark_last_busy(hba->dev); + pm_runtime_put_noidle(hba->dev); + return -ENODEV; + } + + if (ufshid_is_not_present(hid)) + return -ENODEV; + + pm_runtime_get_sync(hba->dev); +resume_success: + return 0; +} + +static inline void ufshid_release_runtime_pm(struct ufshid_dev *hid) +{ + struct ufs_hba *hba = hid->ufsf->hba; + + pm_runtime_mark_last_busy(hba->dev); + pm_runtime_put_noidle(hba->dev); +} + +/* + * Lock status: hid_sysfs lock was held when called. + */ +static int ufshid_trigger_on(struct ufshid_dev *hid) + __must_hold(&hid->sysfs_lock) +{ + int ret; + + if (hid->hid_trigger) + return 0; + + ret = ufshid_hold_runtime_pm(hid); + if (ret) + return ret; + + hid->hid_trigger = true; + HID_DEBUG(hid, "trigger 0 -> 1"); + + ufshid_block_enter_suspend(hid); + + ufshid_auto_hibern8_enable(hid, 0); + + schedule_delayed_work(&hid->hid_trigger_work, 0); + + ufshid_release_runtime_pm(hid); + + return 0; +} + +static void ufshid_allow_enter_suspend(struct ufshid_dev *hid) +{ + struct ufs_hba *hba = hid->ufsf->hba; + unsigned long flags; + +#if defined(CONFIG_UFSHID_POC) + if (unlikely(!hid->block_suspend)) + return; + + hid->block_suspend = false; +#endif + ufshcd_release(hba); + pm_runtime_mark_last_busy(hba->dev); + pm_runtime_put_noidle(hba->dev); + + spin_lock_irqsave(hba->host->host_lock, flags); + HID_DEBUG(hid, + "dev->power.usage_count %d hba->clk_gating.active_reqs %d", + atomic_read(&hba->dev->power.usage_count), + hba->clk_gating.active_reqs); + spin_unlock_irqrestore(hba->host->host_lock, flags); +} + +/* + * Lock status: hid_sysfs lock was held when called. + */ +static int ufshid_trigger_off(struct ufshid_dev *hid) + __must_hold(&hid->sysfs_lock) +{ + int ret; + + if (!hid->hid_trigger) + return 0; + + ret = ufshid_hold_runtime_pm(hid); + if (ret) + return ret; + + hid->hid_trigger = false; + HID_DEBUG(hid, "hid_trigger 1 -> 0"); + + ufshid_issue_disable(hid); + + ufshid_auto_hibern8_enable(hid, 1); + + ufshid_allow_enter_suspend(hid); + + ufshid_release_runtime_pm(hid); + + return 0; +} + +static void ufshid_trigger_work_fn(struct work_struct *dwork) +{ + struct ufshid_dev *hid; + int ret; + + hid = container_of(dwork, struct ufshid_dev, hid_trigger_work.work); + + if (ufshid_is_not_present(hid)) + return; + + HID_DEBUG(hid, "start hid_trigger_work_fn"); + + ret = ufshid_get_analyze_and_issue_execute(hid); + + mutex_lock(&hid->sysfs_lock); + if (!hid->hid_trigger) { + HID_DEBUG(hid, "hid_trigger == false, return"); + goto finish_work; + } + + if (ret == HID_NOT_REQUIRED) { + ret = ufshid_trigger_off(hid); + if (likely(!ret)) + goto finish_work; + + WARN_MSG("trigger off fail.. must check it"); + + } else if (ret == HID_REQUIRED) { + HID_DEBUG(hid, "HID_REQUIRED, so sched (%d ms)", + hid->hid_trigger_delay); + + } else { + HID_DEBUG(hid, "issue_HID ERR(%X), so resched for retry", ret); + } + mutex_unlock(&hid->sysfs_lock); + + schedule_delayed_work(&hid->hid_trigger_work, + msecs_to_jiffies(hid->hid_trigger_delay)); + + HID_DEBUG(hid, "end hid_trigger_work_fn"); + return; +finish_work: + mutex_unlock(&hid->sysfs_lock); +} + +void ufshid_init(struct ufsf_feature *ufsf) +{ + struct ufshid_dev *hid; + int ret; + + INFO_MSG("HID_INIT_START"); + + hid = ufsf->hid_dev; + BUG_ON(!hid); + + hid->hid_trigger = false; + hid->hid_trigger_delay = HID_TRIGGER_WORKER_DELAY_MS_DEFAULT; + INIT_DELAYED_WORK(&hid->hid_trigger_work, ufshid_trigger_work_fn); + + hid->hid_debug = false; +#if defined(CONFIG_UFSHID_POC) + hid->hid_debug = true; + hid->block_suspend = false; +#endif + + /* If HCI supports auto hibern8, UFS Driver use it default */ + if (ufshcd_is_auto_hibern8_supported(ufsf->hba)) + hid->is_auto_enabled = true; + else + hid->is_auto_enabled = false; + + ret = ufshid_create_sysfs(hid); + if (ret) { + ERR_MSG("sysfs init fail. so hid driver disabled"); + kfree(hid); + ufshid_set_state(ufsf, HID_FAILED); + return; + } + + INFO_MSG("UFS HID create sysfs finished"); + + ufshid_set_state(ufsf, HID_PRESENT); +} + +void ufshid_reset_host(struct ufsf_feature *ufsf) +{ + struct ufshid_dev *hid = ufsf->hid_dev; + + if (!hid) + return; + + ufshid_set_state(ufsf, HID_RESET); + cancel_delayed_work_sync(&hid->hid_trigger_work); +} + +void ufshid_reset(struct ufsf_feature *ufsf) +{ + struct ufshid_dev *hid = ufsf->hid_dev; + + if (!hid) + return; + + ufshid_set_state(ufsf, HID_PRESENT); + + /* + * hid_trigger will be checked under sysfs_lock in worker. + */ + if (hid->hid_trigger) + schedule_delayed_work(&hid->hid_trigger_work, 0); + + INFO_MSG("reset completed."); +} + +static inline void ufshid_remove_sysfs(struct ufshid_dev *hid) +{ + int ret; + + ret = kobject_uevent(&hid->kobj, KOBJ_REMOVE); + INFO_MSG("kobject removed (%d)", ret); + kobject_del(&hid->kobj); +} + +void ufshid_remove(struct ufsf_feature *ufsf) +{ + struct ufshid_dev *hid = ufsf->hid_dev; + int ret; + + if (!hid) + return; + + INFO_MSG("start HID release"); + + mutex_lock(&hid->sysfs_lock); + + ret = ufshid_trigger_off(hid); + if (unlikely(ret)) + ERR_MSG("trigger off fail ret (%d)", ret); + + ufshid_remove_sysfs(hid); + + ufshid_set_state(ufsf, HID_FAILED); + + mutex_unlock(&hid->sysfs_lock); + + cancel_delayed_work_sync(&hid->hid_trigger_work); + + kfree(hid); + + INFO_MSG("end HID release"); +} + +void ufshid_suspend(struct ufsf_feature *ufsf) +{ + struct ufshid_dev *hid = ufsf->hid_dev; + + if (!hid) + return; + + if (unlikely(hid->hid_trigger)) + ERR_MSG("hid_trigger was set to block the suspend. so weird"); + ufshid_set_state(ufsf, HID_SUSPEND); + + cancel_delayed_work_sync(&hid->hid_trigger_work); +} + +void ufshid_resume(struct ufsf_feature *ufsf) +{ + struct ufshid_dev *hid = ufsf->hid_dev; + + if (!hid) + return; + + if (unlikely(hid->hid_trigger)) + ERR_MSG("hid_trigger need to off"); + ufshid_set_state(ufsf, HID_PRESENT); +} + +/* + * this function is called in irq context. + * so cancel_delayed_work_sync() do not use due to waiting. + */ +void ufshid_on_idle(struct ufsf_feature *ufsf) +{ + struct ufshid_dev *hid = ufsf->hid_dev; + + if (!hid) + return; + /* + * When hid_trigger_work will be scheduled, + * check hid_trigger under sysfs_lock. + */ + if (!hid->hid_trigger) + return; + + if (delayed_work_pending(&hid->hid_trigger_work)) + cancel_delayed_work(&hid->hid_trigger_work); + + schedule_delayed_work(&hid->hid_trigger_work, 0); +} + +/* sysfs function */ +static ssize_t ufshid_sysfs_show_version(struct ufshid_dev *hid, char *buf) +{ + INFO_MSG("HID version (%.4X) D/D version (%.6X%s)", + UFSHID_VER, UFSHID_DD_VER, UFSHID_DD_VER_POST); + + return snprintf(buf, PAGE_SIZE, + "HID version (%.4X) D/D version (%.6X%s)\n", + UFSHID_VER, UFSHID_DD_VER, UFSHID_DD_VER_POST); +} + +static ssize_t ufshid_sysfs_show_trigger(struct ufshid_dev *hid, char *buf) +{ + INFO_MSG("hid_trigger %d", hid->hid_trigger); + + return snprintf(buf, PAGE_SIZE, "%d\n", hid->hid_trigger); +} + +static ssize_t ufshid_sysfs_store_trigger(struct ufshid_dev *hid, + const char *buf, size_t count) +{ + unsigned long val; + ssize_t ret; + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + if (val != 0 && val != 1) + return -EINVAL; + + INFO_MSG("HID_trigger %lu", val); + + if (val == hid->hid_trigger) + return count; + + if (val) + ret = ufshid_trigger_on(hid); + else + ret = ufshid_trigger_off(hid); + + if (ret) { + INFO_MSG("Changing trigger val %lu is fail (%ld)", val, ret); + return ret; + } + + return count; +} + +static ssize_t ufshid_sysfs_show_trigger_interval(struct ufshid_dev *hid, + char *buf) +{ + INFO_MSG("hid_trigger_interval %d", hid->hid_trigger_delay); + + return snprintf(buf, PAGE_SIZE, "%d\n", hid->hid_trigger_delay); +} + +static ssize_t ufshid_sysfs_store_trigger_interval(struct ufshid_dev *hid, + const char *buf, + size_t count) +{ + unsigned long val; + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + if (val < HID_TRIGGER_WORKER_DELAY_MS_MIN || + val > HID_TRIGGER_WORKER_DELAY_MS_MAX) { + INFO_MSG("hid_trigger_interval (min) %4dms ~ (max) %4dms", + HID_TRIGGER_WORKER_DELAY_MS_MIN, + HID_TRIGGER_WORKER_DELAY_MS_MAX); + return -EINVAL; + } + + hid->hid_trigger_delay = (unsigned int)val; + INFO_MSG("hid_trigger_interval %d", hid->hid_trigger_delay); + + return count; +} + +static ssize_t ufshid_sysfs_show_debug(struct ufshid_dev *hid, char *buf) +{ + INFO_MSG("debug %d", hid->hid_debug); + + return snprintf(buf, PAGE_SIZE, "%d\n", hid->hid_debug); +} + +static ssize_t ufshid_sysfs_store_debug(struct ufshid_dev *hid, const char *buf, + size_t count) +{ + unsigned long val; + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + if (val != 0 && val != 1) + return -EINVAL; + + hid->hid_debug = val ? true : false; + + INFO_MSG("debug %d", hid->hid_debug); + + return count; +} + +static ssize_t ufshid_sysfs_show_color(struct ufshid_dev *hid, char *buf) +{ + u32 attr_val; + int frag_level; + + if (ufshid_write_attr(hid, QUERY_ATTR_IDN_HID_OPERATION, + HID_OP_ANALYZE)) { + ERR_MSG("query HID_OPERATION fail"); + return -EINVAL; + } + + if (ufshid_read_attr(hid, QUERY_ATTR_IDN_HID_FRAG_LEVEL, &attr_val)) { + ERR_MSG("query HID_FRAG_LEVEL fail"); + return -EINVAL; + } + + frag_level = attr_val & HID_FRAG_LEVEL_MASK; + INFO_MSG("Frag_lv %d Freg_stat %d HID_need_exec %d", frag_level, + HID_FRAG_UPDATE_STAT(attr_val), + HID_EXECUTE_REQ_STAT(attr_val)); + + return snprintf(buf, PAGE_SIZE, "%s\n", + frag_level == HID_LEV_RED ? "RED" : + frag_level == HID_LEV_YELLOW ? "YELLOW" : + frag_level == HID_LEV_GREEN ? "GREEN" : + frag_level == HID_LEV_GRAY ? "GRAY" : "UNKNOWN"); +} + +#if defined(CONFIG_UFSHID_POC) +static ssize_t ufshid_sysfs_show_debug_op(struct ufshid_dev *hid, char *buf) +{ + u32 attr_val; + + if (ufshid_read_attr(hid, QUERY_ATTR_IDN_HID_OPERATION, &attr_val)) + return -EINVAL; + + INFO_MSG("hid_op %d", attr_val); + + return snprintf(buf, PAGE_SIZE, "%d\n", attr_val); +} + +static ssize_t ufshid_sysfs_store_debug_op(struct ufshid_dev *hid, + const char *buf, size_t count) +{ + unsigned long val; + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + if (val >= HID_OP_MAX) + return -EINVAL; + + if (hid->hid_trigger) { + ERR_MSG("debug_op cannot change, current hid_trigger is ON"); + return -EINVAL; + } + + if (ufshid_write_attr(hid, QUERY_ATTR_IDN_HID_OPERATION, val)) + return -EINVAL; + + INFO_MSG("hid_op %ld is set!", val); + return count; +} + +static ssize_t ufshid_sysfs_show_block_suspend(struct ufshid_dev *hid, + char *buf) +{ + INFO_MSG("block suspend %d", hid->block_suspend); + + return snprintf(buf, PAGE_SIZE, "%d\n", hid->block_suspend); +} + +static ssize_t ufshid_sysfs_store_block_suspend(struct ufshid_dev *hid, + const char *buf, size_t count) +{ + unsigned long val; + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + if (val != 0 && val != 1) + return -EINVAL; + + INFO_MSG("HID_block_suspend %lu", val); + + if (val == hid->block_suspend) + return count; + + if (val) + ufshid_block_enter_suspend(hid); + else + ufshid_allow_enter_suspend(hid); + + hid->block_suspend = val ? true : false; + + return count; +} + +static ssize_t ufshid_sysfs_show_auto_hibern8_enable(struct ufshid_dev *hid, + char *buf) +{ + INFO_MSG("HCI auto hibern8 %d", hid->is_auto_enabled); + + return snprintf(buf, PAGE_SIZE, "%d\n", hid->is_auto_enabled); +} + +static ssize_t ufshid_sysfs_store_auto_hibern8_enable(struct ufshid_dev *hid, + const char *buf, + size_t count) +{ + unsigned long val; + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + if (val != 0 && val != 1) + return -EINVAL; + + ufshid_auto_hibern8_enable(hid, val); + + return count; +} +#endif + +/* SYSFS DEFINE */ +#define define_sysfs_ro(_name) __ATTR(_name, 0444, \ + ufshid_sysfs_show_##_name, NULL) +#define define_sysfs_rw(_name) __ATTR(_name, 0644, \ + ufshid_sysfs_show_##_name, \ + ufshid_sysfs_store_##_name) + +static struct ufshid_sysfs_entry ufshid_sysfs_entries[] = { + define_sysfs_ro(version), + define_sysfs_ro(color), + + define_sysfs_rw(trigger), + define_sysfs_rw(trigger_interval), + + /* debug */ + define_sysfs_rw(debug), +#if defined(CONFIG_UFSHID_POC) + /* Attribute (RAW) */ + define_sysfs_rw(debug_op), + define_sysfs_rw(block_suspend), + define_sysfs_rw(auto_hibern8_enable), +#endif + __ATTR_NULL +}; + +static ssize_t ufshid_attr_show(struct kobject *kobj, struct attribute *attr, + char *page) +{ + struct ufshid_sysfs_entry *entry; + struct ufshid_dev *hid; + ssize_t error; + + entry = container_of(attr, struct ufshid_sysfs_entry, attr); + if (!entry->show) + return -EIO; + + hid = container_of(kobj, struct ufshid_dev, kobj); + error = ufshid_hold_runtime_pm(hid); + if (error) + return error; + + mutex_lock(&hid->sysfs_lock); + error = entry->show(hid, page); + mutex_unlock(&hid->sysfs_lock); + + ufshid_release_runtime_pm(hid); + return error; +} + +static ssize_t ufshid_attr_store(struct kobject *kobj, struct attribute *attr, + const char *page, size_t length) +{ + struct ufshid_sysfs_entry *entry; + struct ufshid_dev *hid; + ssize_t error; + + entry = container_of(attr, struct ufshid_sysfs_entry, attr); + if (!entry->store) + return -EIO; + + hid = container_of(kobj, struct ufshid_dev, kobj); + error = ufshid_hold_runtime_pm(hid); + if (error) + return error; + + mutex_lock(&hid->sysfs_lock); + error = entry->store(hid, page, length); + mutex_unlock(&hid->sysfs_lock); + + ufshid_release_runtime_pm(hid); + return error; +} + +static const struct sysfs_ops ufshid_sysfs_ops = { + .show = ufshid_attr_show, + .store = ufshid_attr_store, +}; + +static struct kobj_type ufshid_ktype = { + .sysfs_ops = &ufshid_sysfs_ops, + .release = NULL, +}; + +static int ufshid_create_sysfs(struct ufshid_dev *hid) +{ + struct device *dev = hid->ufsf->hba->dev; + struct ufshid_sysfs_entry *entry; + int err; + + hid->sysfs_entries = ufshid_sysfs_entries; + + kobject_init(&hid->kobj, &ufshid_ktype); + mutex_init(&hid->sysfs_lock); + + INFO_MSG("ufshid creates sysfs ufshid %p dev->kobj %p", + &hid->kobj, &dev->kobj); + + err = kobject_add(&hid->kobj, kobject_get(&dev->kobj), "ufshid"); + if (!err) { + for (entry = hid->sysfs_entries; entry->attr.name != NULL; + entry++) { + INFO_MSG("ufshid sysfs attr creates: %s", + entry->attr.name); + err = sysfs_create_file(&hid->kobj, &entry->attr); + if (err) { + ERR_MSG("create entry(%s) failed", + entry->attr.name); + goto kobj_del; + } + } + kobject_uevent(&hid->kobj, KOBJ_ADD); + } else { + ERR_MSG("kobject_add failed"); + } + + return err; +kobj_del: + err = kobject_uevent(&hid->kobj, KOBJ_REMOVE); + INFO_MSG("kobject removed (%d)", err); + kobject_del(&hid->kobj); + return -EINVAL; +} diff --git a/drivers/scsi/ufs/ufshid.h b/drivers/scsi/ufs/ufshid.h new file mode 100755 index 000000000000..849822ed5121 --- /dev/null +++ b/drivers/scsi/ufs/ufshid.h @@ -0,0 +1,144 @@ +/* + * Universal Flash Storage Host Initiated Defrag (UFS HID) + * + * Copyright (C) 2019-2019 Samsung Electronics Co., Ltd. + * + * Authors: + * Yongmyung Lee + * Jieon Seol + * + * 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 + * + * + * 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. + */ + +#ifndef _UFSHID_H_ +#define _UFSHID_H_ + +#include +#include +#include +#include +#include +#include + +#include "../../../block/blk.h" + +#define UFSHID_VER 0x0101 +#define UFSHID_DD_VER 0x010202 +#define UFSHID_DD_VER_POST "" + +#define UFS_FEATURE_SUPPORT_HID_BIT 0x400 + +#define HID_TRIGGER_WORKER_DELAY_MS_DEFAULT 2000 +#define HID_TRIGGER_WORKER_DELAY_MS_MIN 100 +#define HID_TRIGGER_WORKER_DELAY_MS_MAX 10000 + +#define HID_FRAG_LEVEL_MASK 0xF +#define HID_FRAG_UPDATE_STAT_SHIFT 30 +#define HID_EXECUTE_REQ_STAT_SHIFT 31 +#define HID_FRAG_UPDATE_STAT(val) ((val >> HID_FRAG_UPDATE_STAT_SHIFT) & 0x1) +#define HID_EXECUTE_REQ_STAT(val) ((val >> HID_EXECUTE_REQ_STAT_SHIFT) & 0x1) + +#define HID_AUTO_HIBERN8_DISABLE (FIELD_PREP(UFSHCI_AHIBERN8_TIMER_MASK, 0) | \ + FIELD_PREP(UFSHCI_AHIBERN8_SCALE_MASK, 3)) + + +#define HID_DEBUG(hid, msg, args...) \ + do { if (hid->hid_debug) \ + pr_err("%40s:%3d [%01d%02d%02d] " msg "\n", \ + __func__, __LINE__, \ + hid->hid_trigger, \ + atomic_read(&hid->ufsf->hba->dev->power.usage_count),\ + hid->ufsf->hba->clk_gating.active_reqs, ##args); \ + } while (0) + +enum UFSHID_STATE { + HID_NEED_INIT = 0, + HID_PRESENT = 1, + HID_SUSPEND = 2, + HID_FAILED = -2, + HID_RESET = -3, +}; + +enum { + HID_OP_DISABLE = 0, + HID_OP_ANALYZE = 1, + HID_OP_EXECUTE = 2, + HID_OP_MAX +}; + +enum { + HID_NOT_REQUIRED = 0, + HID_REQUIRED = 1 +}; + +enum { + HID_LEV_GRAY = 0, + HID_LEV_GREEN = 1, + HID_LEV_YELLOW = 2, + HID_LEV_RED = 3, +}; + +struct ufshid_dev { + struct ufsf_feature *ufsf; + + unsigned int hid_trigger; /* default value is false */ + struct delayed_work hid_trigger_work; + unsigned int hid_trigger_delay; + + bool is_auto_enabled; + + /* for sysfs */ + struct kobject kobj; + struct mutex sysfs_lock; + struct ufshid_sysfs_entry *sysfs_entries; + + /* for debug */ + bool hid_debug; +#if defined(CONFIG_UFSHID_POC) + bool block_suspend; +#endif +}; + +struct ufshid_sysfs_entry { + struct attribute attr; + ssize_t (*show)(struct ufshid_dev *hid, char *buf); + ssize_t (*store)(struct ufshid_dev *hid, const char *buf, size_t count); +}; + +int ufshid_get_state(struct ufsf_feature *ufsf); +void ufshid_set_state(struct ufsf_feature *ufsf, int state); +void ufshid_get_dev_info(struct ufsf_feature *ufsf, u8 *desc_buf); +void ufshid_set_init_state(struct ufsf_feature *ufsf); +void ufshid_init(struct ufsf_feature *ufsf); +void ufshid_reset(struct ufsf_feature *ufsf); +void ufshid_reset_host(struct ufsf_feature *ufsf); +void ufshid_remove(struct ufsf_feature *ufsf); +void ufshid_suspend(struct ufsf_feature *ufsf); +void ufshid_resume(struct ufsf_feature *ufsf); +void ufshid_on_idle(struct ufsf_feature *ufsf); +#endif /* End of Header */