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

Commit c8618627 authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "scsi: ufs: don't power cycle device if power on write protected"

parents f43c2947 4fb76bba
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
@@ -177,6 +177,18 @@ enum unit_desc_param {
	UNIT_DESC_PARAM_LARGE_UNIT_SIZE_M1	= 0x22,
};

/*
 * Logical Unit Write Protect
 * 00h: LU not write protected
 * 01h: LU write protected when fPowerOnWPEn =1
 * 02h: LU permanently write protected when fPermanentWPEn =1
 */
enum ufs_lu_wp_type {
	UFS_LU_NO_WP		= 0x00,
	UFS_LU_POWER_ON_WP	= 0x01,
	UFS_LU_PERM_WP		= 0x02,
};

/* bActiveICCLevel parameter current units */
enum {
	UFSHCD_NANO_AMP		= 0,
@@ -415,6 +427,12 @@ struct ufs_query_res {
#define UFS_VREG_VCCQ2_MIN_UV	   1650000 /* uV */
#define UFS_VREG_VCCQ2_MAX_UV	   1950000 /* uV */

/*
 * VCCQ & VCCQ2 current requirement when UFS device is in sleep state
 * and link is in Hibern8 state.
 */
#define UFS_VREG_LPM_LOAD_UA	1000 /* uA */

struct ufs_vreg {
	struct regulator *reg;
	const char *name;
@@ -431,4 +449,10 @@ struct ufs_vreg_info {
	struct ufs_vreg *vccq2;
};

struct ufs_dev_info {
	bool f_power_on_wp_en;
	/* Keeps information if any of the LU is power on write protected */
	bool is_lu_power_on_wp;
};

#endif /* End of Header */
+166 −37
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@
 *
 * This code is based on drivers/scsi/ufs/ufshcd.c
 * Copyright (C) 2011-2013 Samsung India Software Operations
 * Copyright (c) 2013, The Linux Foundation. All rights reserved.
 *
 * Authors:
 *	Santosh Yaraganavi <santosh.sy@samsung.com>
@@ -31,6 +32,9 @@
 * 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 <linux/async.h>
@@ -2564,6 +2568,62 @@ static void ufshcd_set_queue_depth(struct scsi_device *sdev)
	scsi_activate_tcq(sdev, lun_qdepth);
}

/*
 * ufshcd_get_lu_wp - returns the "b_lu_write_protect" from UNIT DESCRIPTOR
 * @hba: per-adapter instance
 * @lun: UFS device lun id
 * @b_lu_write_protect: pointer to buffer to hold the LU's write protect info
 *
 * Returns 0 in case of success and b_lu_write_protect status would be returned
 * @b_lu_write_protect parameter.
 * Returns -ENOTSUPP if reading b_lu_write_protect is not supported.
 * Returns -EINVAL in case of invalid parameters passed to this function.
 */
static int ufshcd_get_lu_wp(struct ufs_hba *hba,
			    u8 lun,
			    u8 *b_lu_write_protect)
{
	int ret;

	if (!b_lu_write_protect)
		ret = -EINVAL;
	/*
	 * According to UFS device spec, RPMB LU can't be write
	 * protected so skip reading bLUWriteProtect parameter for
	 * it. For other W-LUs, UNIT DESCRIPTOR is not available.
	 */
	else if (lun >= UFS_UPIU_MAX_GENERAL_LUN)
		ret = -ENOTSUPP;
	else
		ret = ufshcd_read_unit_desc_param(hba,
					  lun,
					  UNIT_DESC_PARAM_LU_WR_PROTECT,
					  b_lu_write_protect,
					  sizeof(*b_lu_write_protect));
	return ret;
}

/**
 * ufshcd_get_lu_power_on_wp_status - get LU's power on write protect
 * status
 * @hba: per-adapter instance
 * @sdev: pointer to SCSI device
 *
 */
static inline void ufshcd_get_lu_power_on_wp_status(struct ufs_hba *hba,
						    struct scsi_device *sdev)
{
	if (hba->dev_info.f_power_on_wp_en &&
	    !hba->dev_info.is_lu_power_on_wp) {
		u8 b_lu_write_protect;

		if (!ufshcd_get_lu_wp(hba, ufshcd_scsi_to_upiu_lun(sdev->lun),
				      &b_lu_write_protect) &&
		    (b_lu_write_protect == UFS_LU_POWER_ON_WP))
			hba->dev_info.is_lu_power_on_wp = true;
	}
}

/**
 * ufshcd_slave_alloc - handle initial SCSI device configurations
 * @sdev: pointer to SCSI device
@@ -2595,6 +2655,8 @@ static int ufshcd_slave_alloc(struct scsi_device *sdev)

	ufshcd_set_queue_depth(sdev);

	ufshcd_get_lu_power_on_wp_status(hba, sdev);

	/*
	 * For selecting the UFS device power mode (Active / UFS_Sleep /
	 * UFS_PowerDown), SCSI power management command (START STOP UNIT)
@@ -3950,6 +4012,14 @@ static int ufshcd_probe_hba(struct ufs_hba *hba)
	 * context, no need to scan the host
	 */
	if (!ufshcd_eh_in_progress(hba) && !hba->pm_op_in_progress) {
		bool flag;

		/* clear any previous UFS device information */
		memset(&hba->dev_info, 0, sizeof(hba->dev_info));
		if (!ufshcd_query_flag(hba, UPIU_QUERY_OPCODE_READ_FLAG,
				       QUERY_FLAG_IDN_PWR_ON_WPE, &flag))
			hba->dev_info.f_power_on_wp_en = flag;

		ufshcd_init_icc_levels(hba);
		scsi_scan_host(hba->host);
		pm_runtime_put_sync(hba->dev);
@@ -4215,6 +4285,42 @@ static struct scsi_host_template ufshcd_driver_template = {
	.can_queue		= UFSHCD_CAN_QUEUE,
};

static int ufshcd_config_vreg_load(struct device *dev, struct ufs_vreg *vreg,
				   int ua)
{
	int ret = 0;
	struct regulator *reg = vreg->reg;
	const char *name = vreg->name;

	BUG_ON(!vreg);

	ret = regulator_set_optimum_mode(reg, ua);
	if (ret >= 0) {
		/*
		 * regulator_set_optimum_mode() returns new regulator
		 * mode upon success.
		 */
		ret = 0;
	} else {
		dev_err(dev, "%s: %s set optimum mode(ua=%d) failed, err=%d\n",
				__func__, name, ua, ret);
	}

	return ret;
}

static inline int ufshcd_config_vreg_lpm(struct ufs_hba *hba,
					 struct ufs_vreg *vreg)
{
	return ufshcd_config_vreg_load(hba->dev, vreg, UFS_VREG_LPM_LOAD_UA);
}

static inline int ufshcd_config_vreg_hpm(struct ufs_hba *hba,
					 struct ufs_vreg *vreg)
{
	return ufshcd_config_vreg_load(hba->dev, vreg, vreg->max_uA);
}

static int ufshcd_config_vreg(struct device *dev,
		struct ufs_vreg *vreg, bool on)
{
@@ -4235,19 +4341,10 @@ static int ufshcd_config_vreg(struct device *dev,
		}

		uA_load = on ? vreg->max_uA : 0;
		ret = regulator_set_optimum_mode(reg, uA_load);
		if (ret >= 0) {
			/*
			 * regulator_set_optimum_mode() returns new regulator
			 * mode upon success.
			 */
			ret = 0;
		} else {
			dev_err(dev, "%s: %s set optimum mode(uA_load=%d) failed, err=%d\n",
					__func__, name, uA_load, ret);
		ret = ufshcd_config_vreg_load(dev, vreg, uA_load);
		if (ret)
			goto out;
	}
	}
out:
	return ret;
}
@@ -4672,6 +4769,60 @@ out:
	return ret;
}

static void ufshcd_vreg_set_lpm(struct ufs_hba *hba)
{
	/*
	 * If UFS device is either in UFS_Sleep turn off VCC rail to save some
	 * power.
	 *
	 * If UFS device and link is in OFF state, all power supplies (VCC,
	 * VCCQ, VCCQ2) can be turned off if power on write protect is not
	 * required. If UFS link is inactive (Hibern8 or OFF state) and device
	 * is in sleep state, put VCCQ & VCCQ2 rails in LPM mode.
	 *
	 * Ignore the error returned by ufshcd_toggle_vreg() as device is anyway
	 * in low power state which would save some power.
	 */
	if (ufshcd_is_ufs_dev_poweroff(hba) &&
	    !hba->dev_info.is_lu_power_on_wp) {
		ufshcd_setup_vreg(hba, false);
	} else if (!ufshcd_is_ufs_dev_active(hba)) {
		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);
			ufshcd_config_vreg_lpm(hba, hba->vreg_info.vccq2);
		}
	}
}

static int ufshcd_vreg_set_hpm(struct ufs_hba *hba)
{
	int ret = 0;

	if (ufshcd_is_ufs_dev_poweroff(hba) &&
	    !hba->dev_info.is_lu_power_on_wp) {
		ret = ufshcd_setup_vreg(hba, true);
	} else if (!ufshcd_is_ufs_dev_active(hba)) {
		ret = ufshcd_toggle_vreg(hba->dev, hba->vreg_info.vcc, true);
		if (!ret && !ufshcd_is_link_active(hba)) {
			ret = ufshcd_config_vreg_hpm(hba, hba->vreg_info.vccq);
			if (ret)
				goto vcc_disable;
			ret = ufshcd_config_vreg_hpm(hba, hba->vreg_info.vccq2);
			if (ret)
				goto vccq_lpm;
		}
	}
	goto out;

vccq_lpm:
	ufshcd_config_vreg_lpm(hba, hba->vreg_info.vccq);
vcc_disable:
	ufshcd_toggle_vreg(hba->dev, hba->vreg_info.vcc, false);
out:
	return ret;
}

/**
 * ufshcd_suspend - helper function for suspend operations
 * @hba: per adapter instance
@@ -4743,18 +4894,7 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
	if (ret)
		goto set_dev_active;

	/*
	 * If UFS device is either in UFS_Sleep turn off VCC rail to
	 * save some power.
	 * If UFS device is in UFS_Poweroff state, all power supplies
	 * (VCC, VCCQ, VCCQ2) can be turned off.
	 * Ignore the error returned by ufshcd_toggle_vreg() as device
	 * is anyway in low power state which would save some power.
	 */
	if (ufshcd_is_ufs_dev_poweroff(hba))
		ufshcd_setup_vreg(hba, false);
	else if (ufshcd_is_ufs_dev_sleep(hba))
		ufshcd_toggle_vreg(hba->dev, hba->vreg_info.vcc, false);
	ufshcd_vreg_set_lpm(hba);

disable_clks:
	/*
@@ -4791,10 +4931,7 @@ vops_resume:
	if (hba->vops && hba->vops->resume)
		hba->vops->resume(hba, pm_op);
set_link_active:
	if (ufshcd_is_ufs_dev_poweroff(hba))
		ufshcd_setup_vreg(hba, true);
	else if (ufshcd_is_ufs_dev_sleep(hba))
		ufshcd_toggle_vreg(hba->dev, hba->vreg_info.vcc, true);
	ufshcd_vreg_set_hpm(hba);
	if (ufshcd_is_link_hibern8(hba) && !ufshcd_uic_hibern8_exit(hba))
		ufshcd_set_link_active(hba);
	else if (ufshcd_is_link_off(hba))
@@ -4838,12 +4975,7 @@ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
	/* enable the host irq as host controller would be active soon */
	ufshcd_enable_irq(hba);

	/* Bring regulators back online if its turned off during suspend. */
	if (ufshcd_is_ufs_dev_poweroff(hba))
		ret = ufshcd_setup_vreg(hba, true);
	else if (ufshcd_is_ufs_dev_sleep(hba))
		ret = ufshcd_toggle_vreg(hba->dev, hba->vreg_info.vcc, true);

	ret = ufshcd_vreg_set_hpm(hba);
	if (ret)
		goto disable_irq_and_vops_clks;

@@ -4889,10 +5021,7 @@ vendor_suspend:
	if (hba->vops && hba->vops->suspend)
		hba->vops->suspend(hba, pm_op);
disable_vreg:
	if (ufshcd_is_ufs_dev_poweroff(hba))
		ufshcd_setup_vreg(hba, false);
	else if (ufshcd_is_ufs_dev_sleep(hba))
		ufshcd_toggle_vreg(hba->dev, hba->vreg_info.vcc, false);
	ufshcd_vreg_set_lpm(hba);
disable_irq_and_vops_clks:
	ufshcd_disable_irq(hba);
	if (hba->vops && hba->vops->setup_clocks)
+2 −0
Original line number Diff line number Diff line
@@ -436,6 +436,8 @@ struct ufs_hba {
	/* Device management request data */
	struct ufs_dev_cmd dev_cmd;

	/* Keeps information of the UFS device connected to this host */
	struct ufs_dev_info dev_info;
	bool auto_bkops_enabled;
	struct ufs_vreg_info vreg_info;
	struct list_head clk_list_head;