Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2608b872 authored by Asutosh Das's avatar Asutosh Das
Browse files

scsi: ufs: add write booster feature support



Write Booster enable: SW will enable the feature
when clocks are scaled up.
Once clocks and gear are scaled up,
SW will set “fWriteBoosterEn” flag
before exiting from ufshcd_devfreq_scale() function.

Write Booster disable: SW will disable the feature,
when clocks are scaled down. Once clocks and gear
are scaled down, SW will clear “fWriteBoosterEn” flag
before exiting from ufshcd_devfreq_scale() function.

Write Booster Buffer flush will be enabled in following cases:
 1. During runtime suspend, driver would query
    bAvailableWriteBoosterBufferSize attribute and enable the
    Write Booster Buffer Flush if less than 30% Write Booster
    Buffer is available.

Write Booster Buffer flush will be disabled in following case:
During runtime suspend, if available buffer size is >30%,
then Write Booster Flush will be stopped by
clearing fWriteBoosterBufferFlushEn flag.

In non-reduction case, during runtime-suspend,
dCurrentWriteBoosterBufferSize attribute would determine
if flush is possible.
If this value is > 0 and availBuf is less
than 30% then flush is needed and vcc needs to be ON.

Change-Id: I89c488a7641697173a296c1ebadc1f4720090cfd
Signed-off-by: default avatarAsutosh Das <asutoshd@codeaurora.org>
Signed-off-by: default avatarSubhash Jadavani <subhashj@codeaurora.org>
parent 01dafb76
Loading
Loading
Loading
Loading
+42 −1
Original line number Diff line number Diff line
@@ -137,7 +137,7 @@ enum desc_header_offset {
};

enum ufs_desc_def_size {
	QUERY_DESC_DEVICE_DEF_SIZE		= 0x40,
	QUERY_DESC_DEVICE_DEF_SIZE		= 0x59,
	QUERY_DESC_CONFIGURATION_DEF_SIZE	= 0x90,
	QUERY_DESC_UNIT_DEF_SIZE		= 0x23,
	QUERY_DESC_INTERCONNECT_DEF_SIZE	= 0x06,
@@ -204,6 +204,7 @@ enum device_desc_param {
	DEVICE_DESC_PARAM_PSA_MAX_DATA		= 0x25,
	DEVICE_DESC_PARAM_PSA_TMT		= 0x29,
	DEVICE_DESC_PARAM_PRDCT_REV		= 0x2A,
	DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP	= 0x4F,
};

/* Interconnect descriptor parameters offsets in bytes*/
@@ -374,6 +375,38 @@ enum ufs_dev_pwr_mode {
	UFS_POWERDOWN_PWR_MODE	= 3,
};

enum ufs_dev_wb_buf_avail_size {
	UFS_WB_0_PERCENT_BUF_REMAIN = 0x0,
	UFS_WB_10_PERCENT_BUF_REMAIN = 0x1,
	UFS_WB_20_PERCENT_BUF_REMAIN = 0x2,
	UFS_WB_30_PERCENT_BUF_REMAIN = 0x3,
	UFS_WB_40_PERCENT_BUF_REMAIN = 0x4,
	UFS_WB_50_PERCENT_BUF_REMAIN = 0x5,
	UFS_WB_60_PERCENT_BUF_REMAIN = 0x6,
	UFS_WB_70_PERCENT_BUF_REMAIN = 0x7,
	UFS_WB_80_PERCENT_BUF_REMAIN = 0x8,
	UFS_WB_90_PERCENT_BUF_REMAIN = 0x9,
	UFS_WB_100_PERCENT_BUF_REMAIN = 0xA,
};

enum ufs_dev_wb_buf_life_time_est {
	UFS_WB_0_10_PERCENT_USED = 0x1,
	UFS_WB_10_20_PERCENT_USED = 0x2,
	UFS_WB_20_30_PERCENT_USED = 0x3,
	UFS_WB_30_40_PERCENT_USED = 0x4,
	UFS_WB_40_50_PERCENT_USED = 0x5,
	UFS_WB_50_60_PERCENT_USED = 0x6,
	UFS_WB_60_70_PERCENT_USED = 0x7,
	UFS_WB_70_80_PERCENT_USED = 0x8,
	UFS_WB_80_90_PERCENT_USED = 0x9,
	UFS_WB_90_100_PERCENT_USED = 0xA,
	UFS_WB_MAX_USED = 0xB,
};

enum ufs_dev_wb_buf_user_cap_config {
	UFS_WB_BUFF_PRESERVE_USER_SPACE = 1,
	UFS_WB_BUFF_USER_SPACE_RED_EN = 2,
};
/**
 * struct utp_upiu_header - UPIU header structure
 * @dword_0: UPIU header DW-0
@@ -555,12 +588,18 @@ enum {
	UFS_DEV_REMOVABLE_NON_BOOTABLE	= 0x03,
};

/* Possible values for dExtendedUFSFeaturesSupport */
enum {
	UFS_DEV_WRITE_BOOSTER_SUP	= BIT(8),
};

struct ufs_dev_info {
	/* device descriptor info */
	u8	b_device_sub_class;
	u16	w_manufacturer_id;
	u8	i_product_name;
	u16	w_spec_version;
	u32	d_ext_ufs_feature_sup;

	/* query flags */
	bool f_power_on_wp_en;
@@ -572,6 +611,8 @@ struct ufs_dev_info {

	/* Device deviations from standard UFS device spec. */
	unsigned int quirks;

	bool keep_vcc_on;
};

#define MAX_MODEL_LEN 16
+231 −3
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@
#include <linux/nls.h>
#include <linux/of.h>
#include <linux/bitfield.h>
#include <linux/blkdev.h>
#include <linux/suspend.h>
#include "ufshcd.h"
#include "ufs_quirks.h"
@@ -50,6 +51,13 @@
#include "ufs-debugfs.h"
#include "ufs-qcom.h"

static bool ufshcd_wb_sup(struct ufs_hba *hba);
static int ufshcd_wb_ctrl(struct ufs_hba *hba, bool enable);
static int ufshcd_wb_buf_flush_enable(struct ufs_hba *hba);
static int ufshcd_wb_buf_flush_disable(struct ufs_hba *hba);
static bool ufshcd_wb_is_buf_flush_needed(struct ufs_hba *hba);
static int ufshcd_wb_toggle_flush_during_h8(struct ufs_hba *hba, bool set);

#ifdef CONFIG_DEBUG_FS

static int ufshcd_tag_req_type(struct request *rq)
@@ -376,6 +384,38 @@ static inline bool ufshcd_is_card_offline(struct ufs_hba *hba)
	return (atomic_read(&hba->card_state) == UFS_CARD_STATE_OFFLINE);
}

static inline void ufshcd_wb_toggle_flush(struct ufs_hba *hba)
{
	/*
	 * Query dAvailableWriteBoosterBufferSize attribute and enable
	 * the Write BoosterBuffer Flush if only 30% Write Booster
	 * Buffer is available.
	 * In reduction case, flush only if 10% is available
	 */
	if (ufshcd_wb_is_buf_flush_needed(hba))
		ufshcd_wb_buf_flush_enable(hba);
	else
		ufshcd_wb_buf_flush_disable(hba);
}

static inline void ufshcd_wb_config(struct ufs_hba *hba)
{
	int ret;

	if (!ufshcd_wb_sup(hba))
		return;

	ret = ufshcd_wb_ctrl(hba, true);
	if (ret)
		dev_err(hba->dev, "%s: Enable WB failed: %d\n", __func__, ret);
	else
		dev_info(hba->dev, "%s: Write Booster Configured\n", __func__);
	ret = ufshcd_wb_toggle_flush_during_h8(hba, true);
	if (ret)
		dev_err(hba->dev, "%s: En WB flush during H8: failed: %d\n",
			__func__, ret);
}

static inline bool ufshcd_is_device_offline(struct ufs_hba *hba)
{
	if (hba->extcon && ufshcd_is_card_offline(hba))
@@ -1833,6 +1873,10 @@ static int ufshcd_devfreq_scale(struct ufs_hba *hba, bool scale_up)
				hba->clk_gating.delay_ms_pwr_save;
	}

	/* Enable Write Booster if we have scaled up else disable it */
	up_write(&hba->lock);
	ufshcd_wb_ctrl(hba, scale_up);
	down_write(&hba->lock);
	goto clk_scaling_unprepare;

scale_up_gear:
@@ -6880,6 +6924,163 @@ static void ufshcd_bkops_exception_event_handler(struct ufs_hba *hba)
				__func__, err);
}

static bool ufshcd_wb_sup(struct ufs_hba *hba)
{
	return !!(hba->dev_info.d_ext_ufs_feature_sup &
		  UFS_DEV_WRITE_BOOSTER_SUP);
}

static int ufshcd_wb_ctrl(struct ufs_hba *hba, bool enable)
{
	int ret;
	enum query_opcode opcode;

	if (!ufshcd_wb_sup(hba))
		return 0;

	if (enable)
		opcode = UPIU_QUERY_OPCODE_SET_FLAG;
	else
		opcode = UPIU_QUERY_OPCODE_CLEAR_FLAG;

	ret = ufshcd_query_flag_retry(hba, opcode,
				      QUERY_FLAG_IDN_WB_EN, NULL);
	if (ret) {
		dev_err(hba->dev, "%s write booster %s failed %d\n",
			__func__, enable ? "enable" : "disable", ret);
		return ret;
	}

	hba->wb_enabled = enable;
	dev_dbg(hba->dev, "%s write booster %s %d\n",
			__func__, enable ? "enable" : "disable", ret);

	return ret;
}

static int ufshcd_wb_toggle_flush_during_h8(struct ufs_hba *hba, bool set)
{
	int val;

	if (set)
		val =  UPIU_QUERY_OPCODE_SET_FLAG;
	else
		val = UPIU_QUERY_OPCODE_CLEAR_FLAG;

	return ufshcd_query_flag_retry(hba, val,
			       QUERY_FLAG_IDN_WB_BUFF_FLUSH_DURING_HIBERN8,
				       NULL);
}

static int ufshcd_wb_buf_flush_enable(struct ufs_hba *hba)
{
	int ret;

	if (!ufshcd_wb_sup(hba) || hba->wb_buf_flush_enabled)
		return 0;

	ret = ufshcd_query_flag_retry(hba, UPIU_QUERY_OPCODE_SET_FLAG,
				      QUERY_FLAG_IDN_WB_BUFF_FLUSH_EN, NULL);
	if (ret)
		dev_err(hba->dev, "%s WB - buf flush enable failed %d\n",
			__func__, ret);
	else
		hba->wb_buf_flush_enabled = true;

	dev_dbg(hba->dev, "WB - Flush enabled: %d\n", ret);
	return ret;
}

static int ufshcd_wb_buf_flush_disable(struct ufs_hba *hba)
{
	int ret;

	if (!ufshcd_wb_sup(hba) || !hba->wb_buf_flush_enabled)
		return 0;

	ret = ufshcd_query_flag_retry(hba, UPIU_QUERY_OPCODE_CLEAR_FLAG,
				      QUERY_FLAG_IDN_WB_BUFF_FLUSH_EN, NULL);
	if (ret) {
		dev_warn(hba->dev, "%s: WB - buf flush disable failed %d\n",
			__func__, ret);
	} else {
		hba->wb_buf_flush_enabled = false;
		dev_dbg(hba->dev, "WB - Flush disabled: %d\n", ret);
	}

	return ret;
}

static bool ufshcd_wb_is_buf_flush_needed(struct ufs_hba *hba)
{
	int ret;
	u32 cur_buf, status, avail_buf;

	if (!ufshcd_wb_sup(hba))
		return false;

	ret = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR,
				      QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE,
				      0, 0, &avail_buf);
	if (ret) {
		dev_warn(hba->dev, "%s dAvailableWriteBoosterBufferSize read failed %d\n",
			 __func__, ret);
		return false;
	}

	ret = ufshcd_vops_get_user_cap_mode(hba);
	if (ret <= 0) {
		dev_dbg(hba->dev, "Get user-cap reduction mode: failed: %d\n",
			ret);
		/* Most commonly used */
		ret = UFS_WB_BUFF_PRESERVE_USER_SPACE;
	}

	hba->dev_info.keep_vcc_on = false;
	if (ret == UFS_WB_BUFF_USER_SPACE_RED_EN) {
		if (avail_buf <= UFS_WB_10_PERCENT_BUF_REMAIN) {
			hba->dev_info.keep_vcc_on = true;
			return true;
		}
		return false;
	} else if (ret == UFS_WB_BUFF_PRESERVE_USER_SPACE) {
		ret = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_READ_ATTR,
					      QUERY_ATTR_IDN_CURR_WB_BUFF_SIZE,
					      0, 0, &cur_buf);
		if (ret) {
			dev_err(hba->dev, "%s dCurWriteBoosterBufferSize read failed %d\n",
				 __func__, ret);
			return false;
		}

		if (!cur_buf) {
			dev_info(hba->dev, "dCurWBBuf: %d WB disabled until free-space is available\n",
				 cur_buf);
			return false;
		}

		ret = ufshcd_get_ee_status(hba, &status);
		if (ret) {
			dev_err(hba->dev, "%s: failed to get exception status %d\n",
				__func__, ret);
			if (avail_buf < UFS_WB_40_PERCENT_BUF_REMAIN) {
				hba->dev_info.keep_vcc_on = true;
				return true;
			}
			return false;
		}

		status &= hba->ee_ctrl_mask;

		if ((status & MASK_EE_URGENT_BKOPS) ||
		    (avail_buf < UFS_WB_40_PERCENT_BUF_REMAIN)) {
			hba->dev_info.keep_vcc_on = true;
			return true;
		}
	}
	return false;
}

/**
 * ufshcd_exception_event_handler - handle exceptions raised by device
 * @work: pointer to work data
@@ -8353,6 +8554,21 @@ static int ufs_get_device_desc(struct ufs_hba *hba,

	model_index = desc_buf[DEVICE_DESC_PARAM_PRDCT_NAME];


	/* Enable WB only for UFS-3.1 OR if desc len >= 0x59 */
	if ((dev_desc->wspecversion >= 0x310) ||
	    (dev_desc->wmanufacturerid == UFS_VENDOR_TOSHIBA &&
	     dev_desc->wspecversion >= 0x300 &&
	     hba->desc_size.dev_desc >= 0x59))
		hba->dev_info.d_ext_ufs_feature_sup =
			desc_buf[DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP]
								<< 24 |
			desc_buf[DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP + 1]
								<< 16 |
			desc_buf[DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP + 2]
								<< 8 |
			desc_buf[DEVICE_DESC_PARAM_EXT_UFS_FEATURE_SUP + 3];

	/* Zero-pad entire buffer for string termination. */
	memset(desc_buf, 0, buff_len);

@@ -8922,6 +9138,8 @@ static int ufshcd_probe_hba(struct ufs_hba *hba)
	/* set the state as operational after switching to desired gear */
	hba->ufshcd_state = UFSHCD_STATE_OPERATIONAL;

	ufshcd_wb_config(hba);

	/*
	 * If we are in error handling context or in power management callbacks
	 * context, no need to scan the host
@@ -10223,6 +10441,9 @@ static void ufshcd_vreg_set_lpm(struct ufs_hba *hba)
	 *
	 * Ignore the error returned by ufshcd_toggle_vreg() as device is anyway
	 * in low power state which would save some power.
	 *
	 * If Write Booster is enabled and the device needs to flush the WB
	 * buffer OR if bkops status is urgent for WB, keep Vcc on.
	 */
	if (ufshcd_is_ufs_dev_poweroff(hba) && ufshcd_is_link_off(hba) &&
	    !hba->dev_info.is_lu_power_on_wp) {
@@ -10241,6 +10462,7 @@ static void ufshcd_vreg_set_lpm(struct ufs_hba *hba)
		else
			ufshcd_config_vreg_lpm(hba, hba->vreg_info.vccq2);
	} else if (!ufshcd_is_ufs_dev_active(hba)) {
		if (!hba->dev_info.keep_vcc_on)
			ufshcd_toggle_vreg(hba->dev, hba->vreg_info.vcc, false);
		if (!ufshcd_is_link_active(hba)) {
			ufshcd_config_vreg_lpm(hba, hba->vreg_info.vccq);
@@ -10436,10 +10658,15 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
			/* make sure that auto bkops is disabled */
			ufshcd_disable_auto_bkops(hba);
		}
		ufshcd_wb_toggle_flush(hba);
	} else if (!ufshcd_is_runtime_pm(pm_op)) {
		ufshcd_wb_buf_flush_disable(hba);
		hba->dev_info.keep_vcc_on = false;
	}

	if ((req_dev_pwr_mode != hba->curr_dev_pwr_mode) &&
	     ((ufshcd_is_runtime_pm(pm_op) && !hba->auto_bkops_enabled) ||
	    ((ufshcd_is_runtime_pm(pm_op) && (!hba->auto_bkops_enabled)
	      && !hba->wb_buf_flush_enabled) ||
	     !ufshcd_is_runtime_pm(pm_op))) {
		/* ensure that bkops is disabled */
		ufshcd_disable_auto_bkops(hba);
@@ -10593,6 +10820,7 @@ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
			hba->hibern8_on_idle.state = HIBERN8_EXITED;
	}

	ufshcd_wb_buf_flush_disable(hba);
	if (!ufshcd_is_ufs_dev_active(hba)) {
		ret = ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE);
		if (ret)
+9 −0
Original line number Diff line number Diff line
@@ -363,6 +363,7 @@ struct ufs_hba_variant_ops {
	u32	(*get_scale_down_gear)(struct ufs_hba *hba);
	int	(*set_bus_vote)(struct ufs_hba *hba, bool on);
	int	(*phy_initialization)(struct ufs_hba *);
	u32	(*get_user_cap_mode)(struct ufs_hba *hba);
#ifdef CONFIG_DEBUG_FS
	void	(*add_debugfs)(struct ufs_hba *hba, struct dentry *root);
	void	(*remove_debugfs)(struct ufs_hba *hba);
@@ -979,6 +980,7 @@ struct ufs_hba {
	/* Keeps information of the UFS device connected to this host */
	struct ufs_dev_info dev_info;
	bool auto_bkops_enabled;
	bool wb_buf_flush_enabled;

#ifdef CONFIG_DEBUG_FS
	struct debugfs_files debugfs_files;
@@ -1082,6 +1084,7 @@ struct ufs_hba {

	bool phy_init_g4;
	bool force_g4;
	bool wb_enabled;
};

static inline void ufshcd_set_card_removal_ongoing(struct ufs_hba *hba)
@@ -1644,4 +1647,10 @@ static inline u8 ufshcd_scsi_to_upiu_lun(unsigned int scsi_lun)
int ufshcd_dump_regs(struct ufs_hba *hba, size_t offset, size_t len,
		     const char *prefix);

static inline unsigned int ufshcd_vops_get_user_cap_mode(struct ufs_hba *hba)
{
	if (hba->var && hba->var->vops->get_user_cap_mode)
		return hba->var->vops->get_user_cap_mode(hba);
	return 0;
}
#endif /* End of Header */
+7 −0
Original line number Diff line number Diff line
@@ -17,6 +17,9 @@ enum flag_idn {
	QUERY_FLAG_IDN_BUSY_RTC                         = 0x09,
	QUERY_FLAG_IDN_RESERVED3                        = 0x0A,
	QUERY_FLAG_IDN_PERMANENTLY_DISABLE_FW_UPDATE    = 0x0B,
	QUERY_FLAG_IDN_WB_EN                            = 0x0E,
	QUERY_FLAG_IDN_WB_BUFF_FLUSH_EN                 = 0x0F,
	QUERY_FLAG_IDN_WB_BUFF_FLUSH_DURING_HIBERN8     = 0x10,
};

/* Attribute idn for Query requests */
@@ -45,6 +48,10 @@ enum attr_idn {
	QUERY_ATTR_IDN_PSA_STATE                = 0x15,
	QUERY_ATTR_IDN_PSA_DATA_SIZE            = 0x16,
	QUERY_ATTR_IDN_REF_CLK_GATING_WAIT_TIME	= 0x17,
	QUERY_ATTR_IDN_WB_FLUSH_STATUS	        = 0x1C,
	QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE       = 0x1D,
	QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST    = 0x1E,
	QUERY_ATTR_IDN_CURR_WB_BUFF_SIZE        = 0x1F,
};

#define QUERY_ATTR_IDN_BOOT_LU_EN_MAX	0x02