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

Commit 3f4c1ace authored by Subhash Jadavani's avatar Subhash Jadavani Committed by Sujit Reddy Thumma
Browse files

scsi: ufs: add UFS power management support



This patch adds support for UFS device and UniPro link power management
during runtime/system PM.

Main idea is to define multiple UFS low power levels based on UFS device
and UFS link power states. This would allow any specific platform or pci
driver to choose the best suited low power level during runtime and
system suspend based on their power goals.

Change-Id: Ifb89253fc9ebb3c512cc6d8d70a7055dc1c78fba
Signed-off-by: default avatarSubhash Jadavani <subhashj@codeaurora.org>
parent 0f072903
Loading
Loading
Loading
Loading
+16 −0
Original line number Diff line number Diff line
@@ -62,6 +62,14 @@
#define UFS_MAX_LUNS		(SCSI_W_LUN_BASE + UFS_UPIU_MAX_UNIT_NUM_ID)
#define UFS_UPIU_WLUN_ID	(1 << 7)

/* Well known logical unit id in LUN field of UPIU */
enum {
	UFS_UPIU_REPORT_LUNS_WLUN	= 0x81,
	UFS_UPIU_UFS_DEVICE_WLUN	= 0xD0,
	UFS_UPIU_BOOT_WLUN		= 0xB0,
	UFS_UPIU_RPMB_WLUN		= 0xC4,
};

/*
 * UFS Protocol Information Unit related definitions
 */
@@ -264,6 +272,14 @@ enum {
	UPIU_TASK_MANAGEMENT_FUNC_FAILED	= 0x05,
	UPIU_INCORRECT_LOGICAL_UNIT_NO		= 0x09,
};

/* UFS device power modes */
enum ufs_dev_pwr_mode {
	UFS_ACTIVE_PWR_MODE	= 1,
	UFS_SLEEP_PWR_MODE	= 2,
	UFS_POWERDOWN_PWR_MODE	= 3,
};

/**
 * struct utp_upiu_header - UPIU header structure
 * @dword_0: UPIU header DW-0
+9 −45
Original line number Diff line number Diff line
@@ -215,45 +215,24 @@ out:
 * ufshcd_pltfrm_suspend - suspend power management function
 * @dev: pointer to device handle
 *
 *
 * Returns 0
 * Returns 0 if successful
 * Returns non-zero otherwise
 */
static int ufshcd_pltfrm_suspend(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct ufs_hba *hba =  platform_get_drvdata(pdev);

	/*
	 * TODO:
	 * 1. Call ufshcd_suspend
	 * 2. Do bus specific power management
	 */

	disable_irq(hba->irq);

	return 0;
	return ufshcd_system_suspend(dev_get_drvdata(dev));
}

/**
 * ufshcd_pltfrm_resume - resume power management function
 * @dev: pointer to device handle
 *
 * Returns 0
 * Returns 0 if successful
 * Returns non-zero otherwise
 */
static int ufshcd_pltfrm_resume(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct ufs_hba *hba =  platform_get_drvdata(pdev);

	/*
	 * TODO:
	 * 1. Call ufshcd_resume.
	 * 2. Do bus specific wake up
	 */

	enable_irq(hba->irq);

	return 0;
	return ufshcd_system_resume(dev_get_drvdata(dev));
}
#else
#define ufshcd_pltfrm_suspend	NULL
@@ -263,30 +242,15 @@ static int ufshcd_pltfrm_resume(struct device *dev)
#ifdef CONFIG_PM_RUNTIME
static int ufshcd_pltfrm_runtime_suspend(struct device *dev)
{
	struct ufs_hba *hba =  dev_get_drvdata(dev);

	if (!hba)
		return 0;

	return ufshcd_runtime_suspend(hba);
	return ufshcd_runtime_suspend(dev_get_drvdata(dev));
}
static int ufshcd_pltfrm_runtime_resume(struct device *dev)
{
	struct ufs_hba *hba =  dev_get_drvdata(dev);

	if (!hba)
		return 0;

	return ufshcd_runtime_resume(hba);
	return ufshcd_runtime_resume(dev_get_drvdata(dev));
}
static int ufshcd_pltfrm_runtime_idle(struct device *dev)
{
	struct ufs_hba *hba =  dev_get_drvdata(dev);

	if (!hba)
		return 0;

	return ufshcd_runtime_idle(hba);
	return ufshcd_runtime_idle(dev_get_drvdata(dev));
}
#else /* !CONFIG_PM_RUNTIME */
#define ufshcd_pltfrm_runtime_suspend	NULL
+446 −36
Original line number Diff line number Diff line
@@ -139,6 +139,39 @@ enum {
#define ufshcd_clear_eh_in_progress(h) \
	(h->eh_flags &= ~UFSHCD_EH_IN_PROGRESS)

#define ufshcd_set_ufs_dev_active(h) \
	((h)->curr_dev_pwr_mode = UFS_ACTIVE_PWR_MODE)
#define ufshcd_set_ufs_dev_sleep(h) \
	((h)->curr_dev_pwr_mode = UFS_SLEEP_PWR_MODE)
#define ufshcd_set_ufs_dev_poweroff(h) \
	((h)->curr_dev_pwr_mode = UFS_POWERDOWN_PWR_MODE)
#define ufshcd_is_ufs_dev_active(h) \
	((h)->curr_dev_pwr_mode == UFS_ACTIVE_PWR_MODE)
#define ufshcd_is_ufs_dev_sleep(h) \
	((h)->curr_dev_pwr_mode == UFS_SLEEP_PWR_MODE)
#define ufshcd_is_ufs_dev_poweroff(h) \
	((h)->curr_dev_pwr_mode == UFS_POWERDOWN_PWR_MODE)

static struct ufs_pm_lvl_states ufs_pm_lvl_states[] = {
	{UFS_ACTIVE_PWR_MODE, UIC_LINK_ACTIVE_STATE},
	{UFS_ACTIVE_PWR_MODE, UIC_LINK_HIBERN8_STATE},
	{UFS_SLEEP_PWR_MODE, UIC_LINK_ACTIVE_STATE},
	{UFS_SLEEP_PWR_MODE, UIC_LINK_HIBERN8_STATE},
	{UFS_POWERDOWN_PWR_MODE, UIC_LINK_OFF_STATE},
};

static inline enum ufs_dev_pwr_mode
ufs_get_pm_lvl_to_dev_pwr_mode(enum ufs_pm_level lvl)
{
	return ufs_pm_lvl_states[lvl].dev_state;
}

static inline enum uic_link_state
ufs_get_pm_lvl_to_link_pwr_state(enum ufs_pm_level lvl)
{
	return ufs_pm_lvl_states[lvl].link_state;
}

static void ufshcd_tmc_handler(struct ufs_hba *hba);
static void ufshcd_async_scan(void *data, async_cookie_t cookie);
static int ufshcd_reset_and_restore(struct ufs_hba *hba);
@@ -147,6 +180,22 @@ static int ufshcd_read_sdev_qdepth(struct ufs_hba *hba,
					struct scsi_device *sdev);
static void ufshcd_hba_exit(struct ufs_hba *hba);

static inline void ufshcd_enable_irq(struct ufs_hba *hba)
{
	if (!hba->is_irq_enabled) {
		enable_irq(hba->irq);
		hba->is_irq_enabled = true;
	}
}

static inline void ufshcd_disable_irq(struct ufs_hba *hba)
{
	if (hba->is_irq_enabled) {
		disable_irq(hba->irq);
		hba->is_irq_enabled = false;
	}
}

/*
 * ufshcd_wait_for_register - wait for register value to change
 * @hba - per-adapter interface
@@ -1773,7 +1822,7 @@ static int ufshcd_uic_change_pwr_mode(struct ufs_hba *hba, u8 mode)
	return ufshcd_uic_pwr_ctrl(hba, &uic_cmd);
}

int ufshcd_uic_hibern8_enter(struct ufs_hba *hba)
static int ufshcd_uic_hibern8_enter(struct ufs_hba *hba)
{
	struct uic_command uic_cmd = {0};

@@ -1782,7 +1831,7 @@ int ufshcd_uic_hibern8_enter(struct ufs_hba *hba)
	return ufshcd_uic_pwr_ctrl(hba, &uic_cmd);
}

int ufshcd_uic_hibern8_exit(struct ufs_hba *hba)
static int ufshcd_uic_hibern8_exit(struct ufs_hba *hba)
{
	struct uic_command uic_cmd = {0};

@@ -1996,6 +2045,9 @@ static int ufshcd_hba_enable(struct ufs_hba *hba)
		msleep(5);
	}

	/* UniPro link is disabled at this point */
	ufshcd_set_link_off(hba);

	if (hba->vops && hba->vops->hce_enable_notify)
		hba->vops->hce_enable_notify(hba, PRE_CHANGE);

@@ -2157,6 +2209,19 @@ static int ufshcd_slave_alloc(struct scsi_device *sdev)
			__func__, lun_qdepth);
	scsi_activate_tcq(sdev, lun_qdepth);

	/*
	 * For selecting the UFS device power mode (Active / UFS_Sleep /
	 * UFS_PowerDown), SCSI power management command (START STOP UNIT)
	 * needs to be sent to a "UFS device" Well known Logical Unit (W-LU).
	 * As this command would be sent during the UFS host controller
	 * runtime/system PM callbacks, we need a reference to "scsi_device"
	 * associated to "UFS device" W-LU. This change saves the "scsi_device"
	 * reference for "UFS device" W-LU during slave_configure() callback
	 * from SCSI mid layer.
	 */
	if (ufshcd_scsi_to_upiu_lun(sdev->lun) == UFS_UPIU_UFS_DEVICE_WLUN)
		hba->sdev_ufs_device = sdev;

	return 0;
}

@@ -2219,6 +2284,9 @@ static void ufshcd_slave_destroy(struct scsi_device *sdev)

	hba = shost_priv(sdev->host);
	scsi_deactivate_tcq(sdev, hba->nutrs);
	/* Drop the reference as it won't be needed anymore */
	if (ufshcd_scsi_to_upiu_lun(sdev->lun) == UFS_UPIU_UFS_DEVICE_WLUN)
		hba->sdev_ufs_device = NULL;
}

/**
@@ -3429,6 +3497,8 @@ static void ufshcd_async_scan(void *data, async_cookie_t cookie)
	if (ret)
		goto out;

	/* UniPro link is active now */
	ufshcd_set_link_active(hba);
	ufshcd_config_max_pwr_mode(hba);

	ret = ufshcd_verify_dev_init(hba);
@@ -3439,11 +3509,16 @@ static void ufshcd_async_scan(void *data, async_cookie_t cookie)
	if (ret)
		goto out;

	/* UFS device is also active now */
	ufshcd_set_ufs_dev_active(hba);
	ufshcd_force_reset_auto_bkops(hba);
	hba->ufshcd_state = UFSHCD_STATE_OPERATIONAL;

	/* If we are in error handling context no need to scan the host */
	if (!ufshcd_eh_in_progress(hba)) {
	/*
	 * If we are in error handling context or in power management callbacks
	 * context, no need to scan the host
	 */
	if (!ufshcd_eh_in_progress(hba) && !hba->pm_op_in_progress) {
		ufshcd_init_icc_levels(hba);
		scsi_scan_host(hba->host);
		pm_runtime_put_sync(hba->dev);
@@ -3453,7 +3528,7 @@ out:
	 * If we failed to initialize the device or the device is not
	 * present, turn off the power/clocks etc.
	 */
	if (ret && !ufshcd_eh_in_progress(hba))
	if (ret && !ufshcd_eh_in_progress(hba) && !hba->pm_op_in_progress)
		ufshcd_hba_exit(hba);

	return;
@@ -3799,71 +3874,403 @@ static void ufshcd_hba_exit(struct ufs_hba *hba)
}

/**
 * ufshcd_suspend - suspend power management function
 * ufshcd_set_dev_pwr_mode - sends START STOP UNIT command to set device
 *			     power mode
 * @hba: per adapter instance
 * @state: power state
 * @pwr_mode: device power mode to set
 *
 * Returns -ENOSYS
 * Returns 0 if requested power mode is set successfully
 * Returns non-zero if failed to set the requested power mode
 */
int ufshcd_suspend(struct ufs_hba *hba, pm_message_t state)
static int ufshcd_set_dev_pwr_mode(struct ufs_hba *hba,
				     enum ufs_dev_pwr_mode pwr_mode)
{
	unsigned char cmd[6] = { START_STOP };
	struct scsi_sense_hdr sshdr;
	struct scsi_device *sdp = hba->sdev_ufs_device;
	int ret;

	if (!sdp || !scsi_device_online(sdp))
		return -ENODEV;

	cmd[4] = pwr_mode << 4;

	/*
	 * TODO:
	 * 1. Block SCSI requests from SCSI midlayer
	 * 2. Change the internal driver state to non operational
	 * 3. Set UTRLRSR and UTMRLRSR bits to zero
	 * 4. Wait until outstanding commands are completed
	 * 5. Set HCE to zero to send the UFS host controller to reset state
	 * Current function would be generally called from the power management
	 * callbacks hence set the REQ_PM flag so that it doesn't resume the
	 * already suspended childs.
	 */
	ret = scsi_execute_req_flags(sdp, cmd, DMA_NONE, NULL, 0, &sshdr,
				     START_STOP_TIMEOUT, 0, NULL, REQ_PM);
	if (ret) {
		sdev_printk(KERN_WARNING, sdp,
			  "START_STOP failed for power mode: %d\n", pwr_mode);
		scsi_show_result(ret);
		if (driver_byte(ret) & DRIVER_SENSE) {
			scsi_show_sense_hdr(&sshdr);
			scsi_show_extd_sense(sshdr.asc, sshdr.ascq);
		}
	}

	if (!ret)
		hba->curr_dev_pwr_mode = pwr_mode;

	return -ENOSYS;
	return ret;
}

static int ufshcd_link_state_transition(struct ufs_hba *hba,
					enum uic_link_state req_link_state,
					int check_for_bkops)
{
	int ret = 0;

	if (req_link_state == hba->uic_link_state)
		return 0;

	if (req_link_state == UIC_LINK_HIBERN8_STATE) {
		ret = ufshcd_uic_hibern8_enter(hba);
		if (!ret)
			ufshcd_set_link_hibern8(hba);
		else
			goto out;
	}
	/*
	 * If autobkops is enabled, link can't be turned off because
	 * turning off the link would also turn off the device.
	 */
	else if ((req_link_state == UIC_LINK_OFF_STATE) &&
		   (!check_for_bkops || (check_for_bkops &&
		    !hba->auto_bkops_enabled))) {
		/*
		 * Change controller state to "reset state" which
		 * should also put the link in off/reset state
		 */
		ufshcd_hba_stop(hba);
		/*
		 * TODO: Check if we need any delay to make sure that
		 * controller is reset
		 */
		ufshcd_set_link_off(hba);
	}

out:
	return ret;
}
EXPORT_SYMBOL_GPL(ufshcd_suspend);

/**
 * ufshcd_resume - resume power management function
 * ufshcd_suspend - helper function for suspend operations
 * @hba: per adapter instance
 * @pm_op: runtime PM or system PM
 *
 * This is common function called by both ufshcd_system_suspend() and
 * ufshcd_runtime_suspend().
 *
 * This function will try to put the UFS device and link into low power
 * mode based on the "rpm_lvl" (Runtime PM level) or "spm_lvl"
 * (System PM level).
 *
 * Returns -ENOSYS
 * NOTE: UFS device & link must be active before we enter in this function.
 *
 * Returns 0 for success and non-zero for failure
 */
int ufshcd_resume(struct ufs_hba *hba)
static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
{
	int ret = 0;
	enum ufs_pm_level pm_lvl;
	enum ufs_dev_pwr_mode req_dev_pwr_mode;
	enum uic_link_state req_link_state;

	if (!hba)
		return 0;

	hba->pm_op_in_progress = 1;
	pm_lvl = ufshcd_is_runtime_pm(pm_op) ? hba->rpm_lvl : hba->spm_lvl;
	req_dev_pwr_mode = ufs_get_pm_lvl_to_dev_pwr_mode(pm_lvl);
	req_link_state = ufs_get_pm_lvl_to_link_pwr_state(pm_lvl);

	/*
	 * TODO:
	 * 1. Set HCE to 1, to start the UFS host controller
	 * initialization process
	 * 2. Set UTRLRSR and UTMRLRSR bits to 1
	 * 3. Change the internal driver state to operational
	 * 4. Unblock SCSI requests from SCSI midlayer
	 * If we can't transition into any of the low power modes
	 * just gate the clocks.
	 */
	if (req_dev_pwr_mode == UFS_ACTIVE_PWR_MODE &&
			req_link_state == UIC_LINK_ACTIVE_STATE) {
		goto disable_clks;
	}

	return -ENOSYS;
	if ((req_dev_pwr_mode == hba->curr_dev_pwr_mode) &&
	    (req_link_state == hba->uic_link_state))
		goto out;

	/* UFS device & link must be active before we enter in this function */
	if (!ufshcd_is_ufs_dev_active(hba) || !ufshcd_is_link_active(hba)) {
		ret = -EINVAL;
		goto out;
	}
EXPORT_SYMBOL_GPL(ufshcd_resume);

int ufshcd_runtime_suspend(struct ufs_hba *hba)
	if (ufshcd_is_runtime_pm(pm_op)) {
		/*
		 * The device is idle with no requests in the queue,
		 * allow background operations if needed.
		 */
		ret = ufshcd_bkops_ctrl(hba, BKOPS_STATUS_NON_CRITICAL);
		if (ret)
			goto out;
	}

	if ((req_dev_pwr_mode != hba->curr_dev_pwr_mode) &&
	     ((ufshcd_is_runtime_pm(pm_op) && !hba->auto_bkops_enabled) ||
	       ufshcd_is_system_pm(pm_op))) {
		/* ensure that bkops is disabled */
		ufshcd_disable_auto_bkops(hba);
		ret = ufshcd_set_dev_pwr_mode(hba, req_dev_pwr_mode);
		if (ret)
			goto out;
	}

	ret = ufshcd_link_state_transition(hba, req_link_state, 1);
	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);

disable_clks:
	/*
	 * Call vendor specific suspend callback. As these callbacks may access
	 * vendor specific host controller register space call them before the
	 * host clocks are ON.
	 */
	if (hba->vops && hba->vops->suspend) {
		ret = hba->vops->suspend(hba, pm_op);
		if (ret)
			goto set_link_active;
	}

	/* freeze the hardware by turning off the clocks */
	ufshcd_setup_clocks(hba, false);

	/*
	 * Disable the host irq as host controller as there won't be any
	 * host controller trasanction expected till resume.
	 */
	ufshcd_disable_irq(hba);
	goto out;

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);
	if (ufshcd_is_link_hibern8(hba) && !ufshcd_uic_hibern8_exit(hba))
		ufshcd_set_link_active(hba);
	else if (ufshcd_is_link_off(hba))
		ufshcd_host_reset_and_restore(hba);
set_dev_active:
	if (!ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE))
		ufshcd_disable_auto_bkops(hba);
out:
	hba->pm_op_in_progress = 0;
	return ret;
}

/**
 * ufshcd_resume - helper function for resume operations
 * @hba: per adapter instance
 * @pm_op: runtime PM or system PM
 *
 * This function basically brings the UFS device, UniPro link and controller
 * to active state.
 *
 * Returns 0 for success and non-zero for failure
 */
static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
{
	int ret;
	enum uic_link_state old_link_state;

	if (!hba)
		return 0;

	hba->pm_op_in_progress = 1;
	old_link_state = hba->uic_link_state;
	/* Make sure clocks are enabled before accessing controller */
	ret = ufshcd_setup_clocks(hba, true);
	if (ret)
		goto out;

	/* 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);

	if (ret)
		goto disable_irq_and_clks;

	/*
	 * The device is idle with no requests in the queue,
	 * allow background operations.
	 * Call vendor specific resume callback. As these callbacks may access
	 * vendor specific host controller register space call them when the
	 * host clocks are ON.
	 */
	ret = ufshcd_bkops_ctrl(hba, BKOPS_STATUS_NON_CRITICAL);
	if (hba->vops && hba->vops->resume) {
		ret = hba->vops->resume(hba, pm_op);
		if (ret)
			goto disable_vreg;
	}

	if (ufshcd_is_link_hibern8(hba)) {
		ret = ufshcd_uic_hibern8_exit(hba);
		if (!ret)
			ufshcd_set_link_active(hba);
		else
			goto vendor_suspend;
	} else if (ufshcd_is_link_off(hba)) {
		ret = ufshcd_host_reset_and_restore(hba);
		/*
		 * ufshcd_host_reset_and_restore() should have already
		 * set the link state as active
		 */
		if (ret || !ufshcd_is_link_active(hba))
			goto vendor_suspend;
	}

	if (!ufshcd_is_ufs_dev_active(hba)) {
		ret = ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE);
		if (ret)
			goto set_old_link_state;
	}

	ufshcd_disable_auto_bkops(hba);
	goto out;

set_old_link_state:
	ufshcd_link_state_transition(hba, old_link_state, 0);
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);
disable_irq_and_clks:
	ufshcd_disable_irq(hba);
	ufshcd_setup_clocks(hba, false);
out:
	hba->pm_op_in_progress = 0;
	return ret;
}
EXPORT_SYMBOL(ufshcd_runtime_suspend);

int ufshcd_runtime_resume(struct ufs_hba *hba)
/**
 * ufshcd_system_suspend - system suspend routine
 * @hba: per adapter instance
 * @pm_op: runtime PM or system PM
 *
 * Check the description of ufshcd_suspend() function for more details.
 *
 * Returns 0 for success and non-zero for failure
 */
int ufshcd_system_suspend(struct ufs_hba *hba)
{
	if (!hba)
	int ret = 0;

	if (pm_runtime_suspended(hba->dev)) {
		if (hba->rpm_lvl == hba->spm_lvl)
			/*
			 * There is possibility that device may still be in
			 * active state during the runtime suspend.
			 */
			if ((ufs_get_pm_lvl_to_dev_pwr_mode(hba->spm_lvl) ==
			    hba->curr_dev_pwr_mode) && !hba->auto_bkops_enabled)
				goto out;

		/*
		 * UFS device and/or UFS link low power states during runtime
		 * suspend seems to be different than what is expected during
		 * system suspend. Hence runtime resume the devic & link and
		 * let the system suspend low power states to take effect.
		 * TODO: If resume takes longer time, we might have optimize
		 * it in future by not resuming everything if possible.
		 */
		ret = ufshcd_runtime_resume(hba);
		if (ret)
			goto out;
	}

	ret = ufshcd_suspend(hba, UFS_SYSTEM_PM);
out:
	return ret;
}
EXPORT_SYMBOL(ufshcd_system_suspend);

/**
 * ufshcd_system_resume - system resume routine
 * @hba: per adapter instance
 *
 * Returns 0 for success and non-zero for failure
 */

int ufshcd_system_resume(struct ufs_hba *hba)
{
	if (pm_runtime_suspended(hba->dev))
		/* Let the runtime resume take care of resuming it */
		return 0;
	else
		return ufshcd_resume(hba, UFS_SYSTEM_PM);
}
EXPORT_SYMBOL(ufshcd_system_resume);

/**
 * ufshcd_runtime_suspend - runtime suspend routine
 * @hba: per adapter instance
 *
 * Check the description of ufshcd_suspend() function for more details.
 *
 * Returns 0 for success and non-zero for failure
 */
int ufshcd_runtime_suspend(struct ufs_hba *hba)
{
	return ufshcd_suspend(hba, UFS_RUNTIME_PM);
}
EXPORT_SYMBOL(ufshcd_runtime_suspend);

	return ufshcd_disable_auto_bkops(hba);
/**
 * ufshcd_runtime_resume - runtime resume routine
 * @hba: per adapter instance
 *
 * This function basically brings the UFS device, UniPro link and controller
 * to active state. Following operations are done in this function:
 *
 * 1. Turn on all the controller related clocks
 * 2. Bring the UniPro link out of Hibernate state
 * 3. If UFS device is in sleep state, turn ON VCC rail and bring the UFS device
 *    to active state.
 * 4. If auto-bkops is enabled on the device, disable it.
 *
 * So following would be the possible power state after this function return
 * successfully:
 *	S1: UFS device in Active state with VCC rail ON
 *	    UniPro link in Active state
 *	    All the UFS/UniPro controller clocks are ON
 *
 * Returns 0 for success and non-zero for failure
 */
int ufshcd_runtime_resume(struct ufs_hba *hba)
{
	return ufshcd_resume(hba, UFS_RUNTIME_PM);
}
EXPORT_SYMBOL(ufshcd_runtime_resume);

@@ -4037,6 +4444,8 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq)
	if (err) {
		dev_err(hba->dev, "request irq failed\n");
		goto out_disable;
	} else {
		hba->is_irq_enabled = true;
	}

	/* Enable SCSI tag mapping */
@@ -4071,6 +4480,7 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq)
out_remove_scsi_host:
	scsi_remove_host(hba->host);
out_disable:
	hba->is_irq_enabled = false;
	scsi_host_put(host);
	UFSDBG_REMOVE_DEBUGFS(hba)
	ufshcd_hba_exit(hba);
+66 −3
Original line number Diff line number Diff line
@@ -96,6 +96,51 @@ struct uic_command {
	struct completion done;
};

/* Used to differentiate the power management options */
enum ufs_pm_op {
	UFS_RUNTIME_PM,
	UFS_SYSTEM_PM,
};

#define ufshcd_is_runtime_pm(op) ((op) == UFS_RUNTIME_PM)
#define ufshcd_is_system_pm(op) ((op) == UFS_SYSTEM_PM)

/* Host <-> Device UniPro Link state */
enum uic_link_state {
	UIC_LINK_OFF_STATE	= 0, /* Link powered down or disabled */
	UIC_LINK_ACTIVE_STATE	= 1, /* Link is in Fast/Slow/Sleep state */
	UIC_LINK_HIBERN8_STATE	= 2, /* Link is in Hibernate state */
};

#define ufshcd_is_link_off(hba) ((hba)->uic_link_state & UIC_LINK_OFF_STATE)
#define ufshcd_is_link_active(hba) ((hba)->uic_link_state & \
				    UIC_LINK_ACTIVE_STATE)
#define ufshcd_is_link_hibern8(hba) ((hba)->uic_link_state & \
				    UIC_LINK_HIBERN8_STATE)
#define ufshcd_set_link_off(hba) ((hba)->uic_link_state = UIC_LINK_OFF_STATE)
#define ufshcd_set_link_active(hba) ((hba)->uic_link_state = \
				    UIC_LINK_ACTIVE_STATE)
#define ufshcd_set_link_hibern8(hba) ((hba)->uic_link_state = \
				    UIC_LINK_HIBERN8_STATE)

/*
 * UFS Power management levels.
 * Each level is in increasing order of power savings.
 */
enum ufs_pm_level {
	UFS_PM_LVL_0, /* UFS_ACTIVE_PWR_MODE, UIC_LINK_ACTIVE_STATE */
	UFS_PM_LVL_1, /* UFS_ACTIVE_PWR_MODE, UIC_LINK_HIBERN8_STATE */
	UFS_PM_LVL_2, /* UFS_SLEEP_PWR_MODE, UIC_LINK_ACTIVE_STATE */
	UFS_PM_LVL_3, /* UFS_SLEEP_PWR_MODE, UIC_LINK_HIBERN8_STATE */
	UFS_PM_LVL_4, /* UFS_POWERDOWN_PWR_MODE, UIC_LINK_OFF_STATE */
	UFS_PM_LVL_MAX
};

struct ufs_pm_lvl_states {
	enum ufs_dev_pwr_mode dev_state;
	enum uic_link_state link_state;
};

/**
 * struct ufshcd_lrb - local reference block
 * @utr_descriptor_ptr: UTRD address of the command
@@ -197,6 +242,8 @@ struct ufs_clk_info {
 *                     variant specific Uni-Pro initialization.
 * @link_startup_notify: called before and after Link startup is carried out
 *                       to allow variant specific Uni-Pro initialization.
 * @suspend: called during host controller PM callback
 * @resume: called during host controller PM callback
 */
struct ufs_hba_variant_ops {
	const char *name;
@@ -206,6 +253,8 @@ struct ufs_hba_variant_ops {
	int     (*setup_regulators)(struct ufs_hba *, bool);
	int     (*hce_enable_notify)(struct ufs_hba *, bool);
	int     (*link_startup_notify)(struct ufs_hba *, bool);
	int     (*suspend)(struct ufs_hba *, enum ufs_pm_op);
	int     (*resume)(struct ufs_hba *, enum ufs_pm_op);
};

/**
@@ -270,6 +319,18 @@ struct ufs_hba {

	struct Scsi_Host *host;
	struct device *dev;
	/*
	 * This field is to keep a reference to "scsi_device" corresponding to
	 * "UFS device" W-LU.
	 */
	struct scsi_device *sdev_ufs_device;
	enum ufs_dev_pwr_mode curr_dev_pwr_mode;
	enum uic_link_state uic_link_state;
	/* Desired UFS power management level during runtime PM */
	enum ufs_pm_level rpm_lvl;
	/* Desired UFS power management level during system PM */
	enum ufs_pm_level spm_lvl;
	int pm_op_in_progress;

	struct ufshcd_lrb *lrb;
	unsigned long lrb_in_use;
@@ -284,6 +345,7 @@ struct ufs_hba {
	struct ufs_hba_variant_ops *vops;
	void *priv;
	unsigned int irq;
	bool is_irq_enabled;

	unsigned int quirks;	/* Deviations from standard UFSHCI spec. */

@@ -313,14 +375,13 @@ struct ufs_hba {
	 */
	#define UFSHCD_QUIRK_BROKEN_PWR_MODE_CHANGE      (1 << 5)

	struct uic_command *active_uic_cmd;
	struct mutex uic_cmd_mutex;

	wait_queue_head_t tm_wq;
	wait_queue_head_t tm_tag_wq;
	unsigned long tm_condition;
	unsigned long tm_slots_in_use;

	struct uic_command *active_uic_cmd;
	struct mutex uic_cmd_mutex;
	struct completion *uic_async_done;

	u32 ufshcd_state;
@@ -396,6 +457,8 @@ static inline void check_upiu_size(void)
extern int ufshcd_runtime_suspend(struct ufs_hba *hba);
extern int ufshcd_runtime_resume(struct ufs_hba *hba);
extern int ufshcd_runtime_idle(struct ufs_hba *hba);
extern int ufshcd_system_suspend(struct ufs_hba *hba);
extern int ufshcd_system_resume(struct ufs_hba *hba);
extern int ufshcd_dme_set_attr(struct ufs_hba *hba, u32 attr_sel,
			       u8 attr_set, u32 mib_val, u8 peer);
extern int ufshcd_dme_get_attr(struct ufs_hba *hba, u32 attr_sel,