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

Commit 49dd4d4f 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: add UFS power management support"

parents 3e2b941d 3f4c1ace
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,