#include "ufs-exynos.h"
+#ifdef CONFIG_SCSI_UFS_VENDOR_WB
+#include "ufs_ctmwb.h"
+#endif
/*
* Exynos's Vendor specific registers for UFSHCI
*/
@@ -989,6 +992,9 @@ static int exynos_ufs_init(struct ufs_hba *hba)
goto phy_off;
ufs->hba = hba;
+#ifdef CONFIG_SCSI_UFS_VENDOR_WB
+ ufs->hba->wb_ops = ufshcd_ctmwb_init(); #endif
ufs->opts = ufs->drv_data->opts;
ufs->rx_sel_idx = PA_MAXDATALANES;
if (ufs->opts & EXYNOS_UFS_OPT_BROKEN_RX_SEL_IDX) diff --git
a/drivers/scsi/ufs/ufs_ctmwb.c b/drivers/scsi/ufs/ufs_ctmwb.c new file
mode 100644 index 000000000000..ab39f40721ae
--- /dev/null
+++ b/drivers/scsi/ufs/ufs_ctmwb.c
@@ -0,0 +1,279 @@
+#include "ufshcd.h"
+#include "ufshci.h"
+#include "ufs_ctmwb.h"
+
+static struct ufshba_ctmwb hba_ctmwb;
+
+/* Query request retries */
+#define QUERY_REQ_RETRIES 3
+
+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)
+{
+ int ret = 0;
+ u32 retries;
+
+ for (retries = QUERY_REQ_RETRIES; retries > 0; retries--) {
+ ret = ufshcd_query_attr(hba, opcode, idn, index,
+ 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 attribute, idn %d, failed with error %d
after %d retires\n",
+ __func__, idn, ret, QUERY_REQ_RETRIES);
+ return ret;
+}
+
+static int ufshcd_query_flag_retry(struct ufs_hba *hba,
+ enum query_opcode opcode, enum flag_idn idn, bool *flag_res) {
+ int ret;
+ int retries;
+
+ for (retries = 0; retries < QUERY_REQ_RETRIES; retries++) {
+ ret = ufshcd_query_flag(hba, opcode, idn, 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 attribute, opcode %d, idn %d, failed with
error %d after %d retries\n",
+ __func__, (int)opcode, (int)idn, ret, retries);
+ return ret;
+}
+
+static int ufshcd_reset_ctmwb(struct ufs_hba *hba, bool force) {
+ int err = 0;
+
+ if (!hba_ctmwb.support_ctmwb)
+ return 0;
+
+ if (ufshcd_is_ctmwb_off(hba_ctmwb)) {
+ dev_info(hba->dev, "%s: turbo write already disabled.
ctmwb_state = %d\n",
+ __func__, hba_ctmwb.ufs_ctmwb_state);
+ return 0;
+ }
+
+ if (ufshcd_is_ctmwb_err(hba_ctmwb))
+ dev_err(hba->dev, "%s: previous turbo write control was
failed.\n",
+ __func__);
+
+ if (force)
+ err = ufshcd_query_flag_retry(hba,
UPIU_QUERY_OPCODE_CLEAR_FLAG,
+ QUERY_FLAG_IDN_WB_EN, NULL);
+
+ if (err) {
+ ufshcd_set_ctmwb_err(hba_ctmwb);
+ dev_err(hba->dev, "%s: disable turbo write failed. err
= %d\n",
+ __func__, err);
+ } else {
+ ufshcd_set_ctmwb_off(hba_ctmwb);
+ dev_info(hba->dev, "%s: ufs turbo write disabled \n",
__func__);
+ }
+
+ return 0;
+}
+
+static int ufshcd_get_ctmwb_buf_status(struct ufs_hba *hba, u32
+*status) {
+ return ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR,
+ QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE, 0, 0, status); }
+
+static int ufshcd_ctmwb_manual_flush_ctrl(struct ufs_hba *hba, int
+en) {
+ int err = 0;
+
+ dev_info(hba->dev, "%s: %sable turbo write manual flush\n",
+ __func__, en ? "en" : "dis");
+ if (en) {
+ err = ufshcd_query_flag_retry(hba,
UPIU_QUERY_OPCODE_SET_FLAG,
+ QUERY_FLAG_IDN_WB_BUFF_FLUSH_EN, NULL);
+ if (err)
+ dev_err(hba->dev, "%s: enable turbo write failed. err
= %d\n",
+ __func__, err);
+ } else {
+ err = ufshcd_query_flag_retry(hba,
UPIU_QUERY_OPCODE_CLEAR_FLAG,
+ QUERY_FLAG_IDN_WB_BUFF_FLUSH_EN, NULL);
+ if (err)
+ dev_err(hba->dev, "%s: disable turbo write failed. err
= %d\n",
+ __func__, err);
+ }
+
+ return err;
+}
+
+static int ufshcd_ctmwb_flush_ctrl(struct ufs_hba *hba) {
+ int err = 0;
+ u32 curr_status = 0;
+
+ err = ufshcd_get_ctmwb_buf_status(hba, &curr_status);
+
+ if (!err && (curr_status <= UFS_WB_MANUAL_FLUSH_THRESHOLD)) {
+ dev_info(hba->dev, "%s: enable ctmwb manual flush, buf
status : %d\n",
+ __func__, curr_status);
+ scsi_block_requests(hba->host);
+ err = ufshcd_ctmwb_manual_flush_ctrl(hba, 1);
+ if (!err) {
+ mdelay(100);
+ err = ufshcd_ctmwb_manual_flush_ctrl(hba, 0);
+ if (err)
+ dev_err(hba->dev, "%s: disable ctmwb manual
flush failed. err = %d\n",
+ __func__, err);
+ } else
+ dev_err(hba->dev, "%s: enable ctmwb manual flush
failed. err = %d\n",
+ __func__, err);
+ scsi_unblock_requests(hba->host);
+ }
+ return err;
+}
+
+static int ufshcd_ctmwb_ctrl(struct ufs_hba *hba, bool enable) {
+ int err;
+#if 0
Did you miss removing these #if 0?
+ if (!hba->support_ctmwb)
+ return;
+
+ if (hba->pm_op_in_progress) {
+ dev_err(hba->dev, "%s: ctmwb ctrl during pm operation is not
allowed.\n",
+ __func__);
+ return;
+ }
+
+ if (hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL) {
+ dev_err(hba->dev, "%s: ufs host is not available.\n",
+ __func__);
+ return;
+ }
+ if (ufshcd_is_ctmwb_err(hba_ctmwb))
+ dev_err(hba->dev, "%s: previous turbo write control was
failed.\n",
+ __func__);
+#endif
+ if (enable) {
+ if (ufshcd_is_ctmwb_on(hba_ctmwb)) {
+ dev_err(hba->dev, "%s: turbo write already enabled.
ctmwb_state = %d\n",
+ __func__, hba_ctmwb.ufs_ctmwb_state);
+ return 0;
+ }
+ pm_runtime_get_sync(hba->dev);
+ err = ufshcd_query_flag_retry(hba,
UPIU_QUERY_OPCODE_SET_FLAG,
+ QUERY_FLAG_IDN_WB_EN, NULL);
+ if (err) {
+ ufshcd_set_ctmwb_err(hba_ctmwb);
+ dev_err(hba->dev, "%s: enable turbo write failed. err
= %d\n",
+ __func__, err);
+ } else {
+ ufshcd_set_ctmwb_on(hba_ctmwb);
+ dev_info(hba->dev, "%s: ufs turbo write enabled \n",
__func__);
+ }
+ } else {
+ if (ufshcd_is_ctmwb_off(hba_ctmwb)) {
+ dev_err(hba->dev, "%s: turbo write already disabled.
ctmwb_state = %d\n",
+ __func__, hba_ctmwb.ufs_ctmwb_state);
+ return 0;
+ }
+ pm_runtime_get_sync(hba->dev);
+ err = ufshcd_query_flag_retry(hba,
UPIU_QUERY_OPCODE_CLEAR_FLAG,
+ QUERY_FLAG_IDN_WB_EN, NULL);
+ if (err) {
+ ufshcd_set_ctmwb_err(hba_ctmwb);
+ dev_err(hba->dev, "%s: disable turbo write failed. err
= %d\n",
+ __func__, err);
+ } else {
+ ufshcd_set_ctmwb_off(hba_ctmwb);
+ dev_info(hba->dev, "%s: ufs turbo write disabled \n",
__func__);
What is 'turbo write'?
+ }
+ }
+
+ pm_runtime_put_sync(hba->dev);
+
+ return 0;
+}
+
+/**
+ * ufshcd_get_ctmwbbuf_unit - get ctmwb buffer alloc units
+ * @sdev: pointer to SCSI device
+ *
+ * Read dLUNumTurboWriteBufferAllocUnits in UNIT Descriptor
+ * to check if LU supports turbo write feature */ static int
+ufshcd_get_ctmwbbuf_unit(struct ufs_hba *hba) {
+ struct scsi_device *sdev = hba->sdev_ufs_device;
+ struct ufshba_ctmwb *hba_ctmwb = (struct ufshba_ctmwb *)hba->wb_ops;
+ int ret = 0;
+
+ u32 dLUNumTurboWriteBufferAllocUnits = 0;
+ u8 desc_buf[4];
+
+ if (!hba_ctmwb->support_ctmwb)
+ return 0;
+
+ ret = ufshcd_read_unit_desc_param(hba,
+ ufshcd_scsi_to_upiu_lun(sdev->lun),
+ UNIT_DESC_PARAM_WB_BUF_ALLOC_UNITS,
+ desc_buf,
+ sizeof(dLUNumTurboWriteBufferAllocUnits));
+
+ /* Some WLUN doesn't support unit descriptor */
+ if ((ret == -EOPNOTSUPP) || scsi_is_wlun(sdev->lun)){
+ hba_ctmwb->support_ctmwb_lu = false;
+ dev_info(hba->dev,"%s: do not support WB\n", __func__);
+ return 0;
+ }
+
+ dLUNumTurboWriteBufferAllocUnits = ((desc_buf[0] << 24)|
+ (desc_buf[1] << 16) |
+ (desc_buf[2] << 8) |
+ desc_buf[3]);
+
+ if (dLUNumTurboWriteBufferAllocUnits) {
+ hba_ctmwb->support_ctmwb_lu = true;
+ dev_info(hba->dev, "%s: LU %d supports ctmwb, ctmwbbuf unit :
0x%x\n",
+ __func__, (int)sdev->lun,
dLUNumTurboWriteBufferAllocUnits);
+ } else
+ hba_ctmwb->support_ctmwb_lu = false;
+
+ return 0;
+}
+
+static inline int ufshcd_ctmwb_toggle_flush(struct ufs_hba *hba, enum
+ufs_pm_op pm_op) {
+ ufshcd_ctmwb_flush_ctrl(hba);
+
+ if (ufshcd_is_system_pm(pm_op))
+ ufshcd_reset_ctmwb(hba, true);
+
+ return 0;
+}
+
+static struct ufs_wb_ops exynos_ctmwb_ops = {
+ .wb_toggle_flush_vendor = ufshcd_ctmwb_toggle_flush,
+ .wb_alloc_units_vendor = ufshcd_get_ctmwbbuf_unit,
+ .wb_ctrl_vendor = ufshcd_ctmwb_ctrl,
+ .wb_reset_vendor = ufshcd_reset_ctmwb, };
+
+struct ufs_wb_ops *ufshcd_ctmwb_init(void) {
+ hba_ctmwb.support_ctmwb = 1;
+
+ return &exynos_ctmwb_ops;
+}
+EXPORT_SYMBOL_GPL(ufshcd_ctmwb_init);
+
diff --git a/drivers/scsi/ufs/ufs_ctmwb.h
b/drivers/scsi/ufs/ufs_ctmwb.h new file mode 100644 index
000000000000..073e21a4900b
--- /dev/null
+++ b/drivers/scsi/ufs/ufs_ctmwb.h
@@ -0,0 +1,27 @@
+#ifndef _UFS_CTMWB_H_
+#define _UFS_CTMWB_H_
+
+enum ufs_ctmwb_state {
+ UFS_WB_OFF_STATE = 0, /* turbo write disabled state */
+ UFS_WB_ON_STATE = 1, /* turbo write enabled state */
+ UFS_WB_ERR_STATE = 2, /* turbo write error state */
+};
+
+#define ufshcd_is_ctmwb_off(hba) ((hba).ufs_ctmwb_state ==
+UFS_WB_OFF_STATE) #define ufshcd_is_ctmwb_on(hba)
+((hba).ufs_ctmwb_state == UFS_WB_ON_STATE) #define
+ufshcd_is_ctmwb_err(hba) ((hba).ufs_ctmwb_state == UFS_WB_ERR_STATE)
+#define ufshcd_set_ctmwb_off(hba) ((hba).ufs_ctmwb_state =
+UFS_WB_OFF_STATE) #define ufshcd_set_ctmwb_on(hba)
+((hba).ufs_ctmwb_state = UFS_WB_ON_STATE) #define
+ufshcd_set_ctmwb_err(hba) ((hba).ufs_ctmwb_state = UFS_WB_ERR_STATE)
+
+#define UFS_WB_MANUAL_FLUSH_THRESHOLD 5
+
+struct ufshba_ctmwb {
+ enum ufs_ctmwb_state ufs_ctmwb_state;
+ bool support_ctmwb;
+
+ bool support_ctmwb_lu;
+};
+
+struct ufs_wb_ops *ufshcd_ctmwb_init(void); #endif
-Asutosh
--
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
Linux Foundation Collaborative Project