diff --git a/drivers/scsi/ufs/Kconfig b/drivers/scsi/ufs/Kconfig index 1c3484c72b6b..f71efcca00c0 100644 --- a/drivers/scsi/ufs/Kconfig +++ b/drivers/scsi/ufs/Kconfig @@ -249,6 +249,12 @@ config SCSI_SKHPB help Activate or deactive SKHPB driver +config SCSI_SKHID + bool "Activate HPB Host-aware Performance Booster" + depends on (SCSI_UFSHCD || SCSI_UFSHCD_QTI) + help + Activate or deactive SKHPB driver + config MICRON_HPB bool "Activate micron HPB Host-aware Performance Booster" depends on SCSI_UFSHCD && UFSFEATURE && UFSHPB diff --git a/drivers/scsi/ufs/ufs-sysfs.c b/drivers/scsi/ufs/ufs-sysfs.c index 1d10866df1f6..42447e1fac49 100644 --- a/drivers/scsi/ufs/ufs-sysfs.c +++ b/drivers/scsi/ufs/ufs-sysfs.c @@ -7,6 +7,9 @@ #include #include "ufs.h" +#if defined(CONFIG_SCSI_SKHID) +#include "ufshcd.h" +#endif #include "ufs-sysfs.h" static const char *ufschd_uic_link_state_to_string( @@ -182,6 +185,129 @@ static ssize_t auto_hibern8_store(struct device *dev, return count; } +#if defined(CONFIG_SCSI_SKHID) +static ssize_t manual_gc_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ufs_hba *hba = dev_get_drvdata(dev); + u32 status = MANUAL_GC_OFF; + + if (hba->manual_gc.state == MANUAL_GC_DISABLE) + return scnprintf(buf, PAGE_SIZE, "%s", "disabled\n"); + + pm_runtime_get_sync(hba->dev); + + //down_read(&hba->query_lock); + if (hba->manual_gc.hagc_support) { + int err = ufshcd_query_attr_retry(hba, + UPIU_QUERY_OPCODE_READ_ATTR, + QUERY_ATTR_IDN_MANUAL_GC_STATUS, 0, 0, &status); + + hba->manual_gc.hagc_support = err ? false: true; + } + //up_read(&hba->query_lock); + pm_runtime_mark_last_busy(hba->dev); + pm_runtime_put_noidle(hba->dev); + + if (!hba->manual_gc.hagc_support) + return scnprintf(buf, PAGE_SIZE, "%s", "bkops\n"); + return scnprintf(buf, PAGE_SIZE, "%s", + status == MANUAL_GC_OFF ? "off\n" : "on\n"); +} + +static int manual_gc_enable(struct ufs_hba *hba, u32 *value) +{ + return ufshcd_query_attr_retry(hba, + UPIU_QUERY_OPCODE_WRITE_ATTR, + QUERY_ATTR_IDN_MANUAL_GC_CONT, 0, 0, + value); +} + +static ssize_t manual_gc_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ufs_hba *hba = dev_get_drvdata(dev); + u32 value; + int err = 0; + + if (kstrtou32(buf, 0, &value)) + return -EINVAL; + + if (value >= MANUAL_GC_MAX) + return -EINVAL; + + if (value == MANUAL_GC_DISABLE || value == MANUAL_GC_ENABLE) { + hba->manual_gc.state = value; + return count; + } + if (hba->manual_gc.state == MANUAL_GC_DISABLE) + return count; + + pm_runtime_get_sync(hba->dev); + + if (hba->manual_gc.hagc_support) + hba->manual_gc.hagc_support = + manual_gc_enable(hba, &value) ? false : true; + + if (!hba->manual_gc.hagc_support) { + enum query_opcode opcode = (value == MANUAL_GC_ON) ? + UPIU_QUERY_OPCODE_SET_FLAG: + UPIU_QUERY_OPCODE_CLEAR_FLAG; + + err = ufshcd_bkops_ctrl(hba, (value == MANUAL_GC_ON) ? + BKOPS_STATUS_NON_CRITICAL: + BKOPS_STATUS_CRITICAL); + if (!hba->auto_bkops_enabled) + err = -EAGAIN; + + /* flush wb buffer */ + if (hba->dev_info.wspecversion >= 0x0310) { + u8 index = ufshcd_wb_get_query_index(hba); + + ufshcd_query_flag_retry(hba, opcode, + QUERY_FLAG_IDN_WB_BUFF_FLUSH_DURING_HIBERN8, + index, NULL); + ufshcd_query_flag_retry(hba, opcode, + QUERY_FLAG_IDN_WB_BUFF_FLUSH_EN, index, NULL); + } + } + + if (err || hrtimer_active(&hba->manual_gc.hrtimer)) { + pm_runtime_mark_last_busy(hba->dev); + pm_runtime_put_noidle(hba->dev); + return count; + } else { + /* pm_runtime_put_sync in delay_ms */ + hrtimer_start(&hba->manual_gc.hrtimer, + ms_to_ktime(hba->manual_gc.delay_ms), + HRTIMER_MODE_REL); + } + return count; +} + +static ssize_t manual_gc_hold_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ufs_hba *hba = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%lu\n", hba->manual_gc.delay_ms); +} + +static ssize_t manual_gc_hold_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct ufs_hba *hba = dev_get_drvdata(dev); + unsigned long value; + + if (kstrtoul(buf, 0, &value)) + return -EINVAL; + + hba->manual_gc.delay_ms = value; + return count; +} +#endif + static DEVICE_ATTR_RW(rpm_lvl); static DEVICE_ATTR_RO(rpm_target_dev_state); static DEVICE_ATTR_RO(rpm_target_link_state); @@ -189,6 +315,10 @@ static DEVICE_ATTR_RW(spm_lvl); static DEVICE_ATTR_RO(spm_target_dev_state); static DEVICE_ATTR_RO(spm_target_link_state); static DEVICE_ATTR_RW(auto_hibern8); +#if defined(CONFIG_SCSI_SKHID) +static DEVICE_ATTR_RW(manual_gc); +static DEVICE_ATTR_RW(manual_gc_hold); +#endif static struct attribute *ufs_sysfs_ufshcd_attrs[] = { &dev_attr_rpm_lvl.attr, @@ -198,6 +328,10 @@ static struct attribute *ufs_sysfs_ufshcd_attrs[] = { &dev_attr_spm_target_dev_state.attr, &dev_attr_spm_target_link_state.attr, &dev_attr_auto_hibern8.attr, +#if defined(CONFIG_SCSI_SKHID) + &dev_attr_manual_gc.attr, + &dev_attr_manual_gc_hold.attr, +#endif NULL }; diff --git a/drivers/scsi/ufs/ufs.h b/drivers/scsi/ufs/ufs.h index 39a2b164c661..a77312b8a5fb 100644 --- a/drivers/scsi/ufs/ufs.h +++ b/drivers/scsi/ufs/ufs.h @@ -198,6 +198,11 @@ enum attr_idn { QUERY_ATTR_IDN_TW_BUF_LIFETIME_EST = 0x1E, QUERY_ATTR_IDN_TW_CURR_BUF_SIZE = 0x1F, #endif +#if defined(CONFIG_SCSI_SKHID) + /* use one reserved bit */ + QUERY_ATTR_IDN_MANUAL_GC_CONT = 0x12, + QUERY_ATTR_IDN_MANUAL_GC_STATUS = 0x13, +#endif #if defined(CONFIG_UFSFEATURE) QUERY_ATTR_IDN_SUP_VENDOR_OPTIONS = 0xFF, #endif @@ -385,6 +390,16 @@ enum geometry_desc_param { #endif }; +#if defined(CONFIG_SCSI_SKHID) +enum { + MANUAL_GC_OFF = 0, + MANUAL_GC_ON, + MANUAL_GC_DISABLE, + MANUAL_GC_ENABLE, + MANUAL_GC_MAX, +}; +#endif + /* Health descriptor parameters offsets in bytes*/ enum health_desc_param { HEALTH_DESC_PARAM_LEN = 0x0, diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index c946df276b66..baa238cb1f17 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -140,6 +140,11 @@ unsigned int storage_mfrid; /* Polling time to wait for fDeviceInit */ #define FDEVICEINIT_COMPL_TIMEOUT 5000 /* millisecs */ +#if defined(CONFIG_SCSI_SKHID) +/* for manual gc */ +#define UFSHCD_MANUAL_GC_HOLD_HIBERN8 2000 /* 2 seconds */ +#endif + #define ufshcd_toggle_vreg(_dev, _vreg, _on) \ ({ \ int _ret; \ @@ -1698,6 +1703,51 @@ static void ufshcd_clkscaling_init_sysfs(struct ufs_hba *hba) dev_err(hba->dev, "Failed to create sysfs for clkscale_enable\n"); } +#if defined(CONFIG_SCSI_SKHID) +static enum hrtimer_restart ufshcd_mgc_hrtimer_handler(struct hrtimer *timer) +{ + struct ufs_hba *hba = container_of(timer, struct ufs_hba, + manual_gc.hrtimer); + + queue_work(hba->manual_gc.mgc_workq, &hba->manual_gc.hibern8_work); + return HRTIMER_NORESTART; +} + +static void ufshcd_mgc_hibern8_work(struct work_struct *work) +{ + struct ufs_hba *hba = container_of(work, struct ufs_hba, + manual_gc.hibern8_work); + pm_runtime_mark_last_busy(hba->dev); + pm_runtime_put_noidle(hba->dev); + /* bkops will be disabled when power down */ +} + +static void ufshcd_init_manual_gc(struct ufs_hba *hba) +{ + struct ufs_manual_gc *mgc = &hba->manual_gc; + char wq_name[sizeof("ufs_mgc_hibern8_work")]; + + mgc->state = MANUAL_GC_ENABLE; + mgc->hagc_support = true; + mgc->delay_ms = UFSHCD_MANUAL_GC_HOLD_HIBERN8; + + hrtimer_init(&mgc->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + mgc->hrtimer.function = ufshcd_mgc_hrtimer_handler; + + INIT_WORK(&mgc->hibern8_work, ufshcd_mgc_hibern8_work); + snprintf(wq_name, ARRAY_SIZE(wq_name), "ufs_mgc_hibern8_work_%d", + hba->host->host_no); + hba->manual_gc.mgc_workq = create_singlethread_workqueue(wq_name); +} + +static void ufshcd_exit_manual_gc(struct ufs_hba *hba) +{ + hrtimer_cancel(&hba->manual_gc.hrtimer); + cancel_work_sync(&hba->manual_gc.hibern8_work); + destroy_workqueue(hba->manual_gc.mgc_workq); +} +#endif + static void ufshcd_ungate_work(struct work_struct *work) { int ret; @@ -3080,7 +3130,7 @@ static inline void ufshcd_init_query(struct ufs_hba *hba, (*request)->upiu_req.selector = selector; } -#if defined(CONFIG_SCSI_SKHPB) +#if defined(CONFIG_SCSI_SKHPB) || defined(CONFIG_SCSI_SKHID) int ufshcd_query_flag_retry(struct ufs_hba *hba, enum query_opcode opcode, enum flag_idn idn, u8 index, bool *flag_res) #else @@ -3091,6 +3141,11 @@ static int ufshcd_query_flag_retry(struct ufs_hba *hba, int ret; int retries; +#if defined(CONFIG_SCSI_SKHID) + if (ufshcd_eh_in_progress(hba)) + return -EBUSY; +#endif + for (retries = 0; retries < QUERY_REQ_RETRIES; retries++) { ret = ufshcd_query_flag(hba, opcode, idn, index, flag_res); if (ret) @@ -3255,14 +3310,25 @@ EXPORT_SYMBOL_GPL(ufshcd_query_attr); * * Returns 0 for success, non-zero in case of failure */ +#if defined(CONFIG_SCSI_SKHID) +int ufshcd_query_attr_retry(struct ufs_hba *hba, + enum query_opcode opcode, enum attr_idn idn, u8 index, u8 selector, + u32 *attr_val) +#else static int ufshcd_query_attr_retry(struct ufs_hba *hba, enum query_opcode opcode, enum attr_idn idn, u8 index, u8 selector, u32 *attr_val) +#endif { int ret = 0; u32 retries; - for (retries = QUERY_REQ_RETRIES; retries > 0; retries--) { +#if defined(CONFIG_SCSI_SKHID) + if (ufshcd_eh_in_progress(hba)) + return -EBUSY; +#endif + + for (retries = QUERY_REQ_RETRIES; retries > 0; retries--) { ret = ufshcd_query_attr(hba, opcode, idn, index, selector, attr_val); if (ret) @@ -5591,12 +5657,21 @@ static inline int ufshcd_get_bkops_status(struct ufs_hba *hba, u32 *status) * to know whether auto bkops is enabled or disabled after this function * returns control to it. */ +#if defined(CONFIG_SCSI_SKHID) +int ufshcd_bkops_ctrl(struct ufs_hba *hba, enum bkops_status status) +#else static int ufshcd_bkops_ctrl(struct ufs_hba *hba, enum bkops_status status) +#endif { int err; u32 curr_status = 0; +#if defined(CONFIG_SCSI_SKHID) + if (ufshcd_eh_in_progress(hba)) + return -EBUSY; +#endif + err = ufshcd_get_bkops_status(hba, &curr_status); if (err) { dev_err(hba->dev, "%s: failed to get BKOPS status %d\n", @@ -9557,7 +9632,9 @@ void ufshcd_remove(struct ufs_hba *hba) /* disable interrupts */ ufshcd_disable_intr(hba, hba->intr_mask); ufshcd_hba_stop(hba, true); - +#if defined(CONFIG_SCSI_SKHID) + ufshcd_exit_manual_gc(hba); +#endif ufshcd_exit_clk_scaling(hba); ufshcd_exit_clk_gating(hba); if (ufshcd_is_clkscaling_supported(hba)) @@ -9742,7 +9819,9 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) ufshcd_init_clk_gating(hba); ufshcd_init_clk_scaling(hba); - +#if defined(CONFIG_SCSI_SKHID) + ufshcd_init_manual_gc(hba); +#endif /* * In order to avoid any spurious interrupt immediately after * registering UFS controller interrupt handler, clear any pending UFS @@ -9847,6 +9926,9 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) out_remove_scsi_host: scsi_remove_host(hba->host); exit_gating: +#if defined(CONFIG_SCSI_SKHID) + ufshcd_exit_manual_gc(hba); +#endif ufshcd_exit_clk_scaling(hba); ufshcd_exit_clk_gating(hba); destroy_workqueue(hba->eh_wq); diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h index 0273ec1dcc8a..989cc4eadebf 100644 --- a/drivers/scsi/ufs/ufshcd.h +++ b/drivers/scsi/ufs/ufshcd.h @@ -484,6 +484,18 @@ struct ufs_clk_gating { struct workqueue_struct *clk_gating_workq; }; +#if defined(CONFIG_SCSI_SKHID) +/* for manual gc */ +struct ufs_manual_gc { + int state; + bool hagc_support; + struct hrtimer hrtimer; + unsigned long delay_ms; + struct work_struct hibern8_work; + struct workqueue_struct *mgc_workq; +}; +#endif + struct ufs_saved_pwr_info { struct ufs_pa_layer_attr info; bool is_valid; @@ -936,6 +948,9 @@ struct ufs_hba { /* Keeps information of the UFS device connected to this host */ struct ufs_dev_info dev_info; bool auto_bkops_enabled; +#if defined(CONFIG_SCSI_SKHID) + struct ufs_manual_gc manual_gc; +#endif struct ufs_vreg_info vreg_info; struct list_head clk_list_head; @@ -1252,6 +1267,12 @@ extern int ufshcd_dme_get_attr(struct ufs_hba *hba, u32 attr_sel, u32 *mib_val, u8 peer); extern int ufshcd_config_pwr_mode(struct ufs_hba *hba, struct ufs_pa_layer_attr *desired_pwr_mode); +#if defined(CONFIG_SCSI_SKHID) +extern int ufshcd_query_attr_retry(struct ufs_hba *hba, + enum query_opcode opcode, enum attr_idn idn, u8 index, u8 selector, + u32 *attr_val); +extern int ufshcd_bkops_ctrl(struct ufs_hba *hba, enum bkops_status status); +#endif /* UIC command interfaces for DME primitives */ #define DME_LOCAL 0 #define DME_PEER 1 @@ -1541,7 +1562,7 @@ static inline u8 ufshcd_scsi_to_upiu_lun(unsigned int scsi_lun) return scsi_lun & UFS_UPIU_MAX_UNIT_NUM_ID; } -#if defined(CONFIG_SCSI_SKHPB) +#if defined(CONFIG_SCSI_SKHPB) || defined(CONFIG_SCSI_SKHID) int ufshcd_query_flag_retry(struct ufs_hba *hba, enum query_opcode opcode, enum flag_idn idn, u8 index, bool *flag_res); #endif