Skip to content

Commit

Permalink
ufs: add UFS power management support
Browse files Browse the repository at this point in the history
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.

bkops handlig:
To put the UFS device in sleep state when bkops is disabled, first query
the bkops status from the device and enable bkops on device only if
device needs time to perform the bkops.

START_STOP handling:
Before sending START_STOP_UNIT to the device well-known logical unit
(w-lun) to make sure that the device w-lun unit attention condition is
cleared.

Write protection:
UFS device specification allows LUs to be write protected, either
permanently or power on write protected. If any LU is power on write
protected and if the card is power cycled (by powering off VCCQ and/or
VCC rails), LU's write protect status would be lost. So this means those
LUs can be written now. To ensures that UFS device is power cycled only
if the power on protect is not set for any of the LUs, check if power on
write protect is set and if device is in sleep/power-off state & link in
inactive state (Hibern8 or OFF state).
If none of the Logical Units on UFS device is power on write protected
then all UFS device power rails (VCC, VCCQ & VCCQ2) can be turned off if
UFS device is in power-off state and UFS link is in OFF state. But current
implementation would disable all device power rails even if UFS link is
not in OFF state.

Low power mode:
If UFS link is in OFF state then UFS host controller can be power collapsed
to avoid leakage current from it. Note that if UFS host controller is power
collapsed, full UFS reinitialization will be required on resume to
re-establish the link between host and device.

Signed-off-by: Subhash Jadavani <subhashj@codeaurora.org>
Signed-off-by: Dolev Raviv <draviv@codeaurora.org>
Signed-off-by: Sujit Reddy Thumma <sthumma@codeaurora.org>
Signed-off-by: Christoph Hellwig <hch@lst.de>
  • Loading branch information
Subhash Jadavani authored and Christoph Hellwig committed Oct 1, 2014
1 parent 0ce147d commit 57d104c
Show file tree
Hide file tree
Showing 6 changed files with 989 additions and 182 deletions.
36 changes: 35 additions & 1 deletion drivers/scsi/ufs/ufs.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ enum {
/* Flag idn for Query Requests*/
enum flag_idn {
QUERY_FLAG_IDN_FDEVICEINIT = 0x01,
QUERY_FLAG_IDN_PWR_ON_WPE = 0x03,
QUERY_FLAG_IDN_BKOPS_EN = 0x04,
};

Expand Down Expand Up @@ -194,6 +195,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,
Expand Down Expand Up @@ -226,11 +239,12 @@ enum {
};

/* Background operation status */
enum {
enum bkops_status {
BKOPS_STATUS_NO_OP = 0x0,
BKOPS_STATUS_NON_CRITICAL = 0x1,
BKOPS_STATUS_PERF_IMPACT = 0x2,
BKOPS_STATUS_CRITICAL = 0x3,
BKOPS_STATUS_MAX = BKOPS_STATUS_CRITICAL,
};

/* UTP QUERY Transaction Specific Fields OpCode */
Expand Down Expand Up @@ -291,6 +305,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
Expand Down Expand Up @@ -437,6 +459,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;
Expand All @@ -454,4 +482,10 @@ struct ufs_vreg_info {
struct ufs_vreg *vdd_hba;
};

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 */
45 changes: 10 additions & 35 deletions drivers/scsi/ufs/ufshcd-pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,34 +43,24 @@
* @pdev: pointer to PCI device handle
* @state: power state
*
* Returns -ENOSYS
* Returns 0 if successful
* Returns non-zero otherwise
*/
static int ufshcd_pci_suspend(struct device *dev)
{
/*
* TODO:
* 1. Call ufshcd_suspend
* 2. Do bus specific power management
*/

return -ENOSYS;
return ufshcd_system_suspend(dev_get_drvdata(dev));
}

/**
* ufshcd_pci_resume - resume power management function
* @pdev: pointer to PCI device handle
*
* Returns -ENOSYS
* Returns 0 if successful
* Returns non-zero otherwise
*/
static int ufshcd_pci_resume(struct device *dev)
{
/*
* TODO:
* 1. Call ufshcd_resume.
* 2. Do bus specific wake up
*/

return -ENOSYS;
return ufshcd_system_resume(dev_get_drvdata(dev));
}
#else
#define ufshcd_pci_suspend NULL
Expand All @@ -80,30 +70,15 @@ static int ufshcd_pci_resume(struct device *dev)
#ifdef CONFIG_PM_RUNTIME
static int ufshcd_pci_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_pci_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_pci_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_pci_runtime_suspend NULL
Expand All @@ -117,7 +92,7 @@ static int ufshcd_pci_runtime_idle(struct device *dev)
*/
static void ufshcd_pci_shutdown(struct pci_dev *pdev)
{
ufshcd_hba_stop((struct ufs_hba *)pci_get_drvdata(pdev));
ufshcd_shutdown((struct ufs_hba *)pci_get_drvdata(pdev));
}

/**
Expand Down
60 changes: 15 additions & 45 deletions drivers/scsi/ufs/ufshcd-pltfrm.c
Original file line number Diff line number Diff line change
Expand Up @@ -225,45 +225,24 @@ static int ufshcd_parse_regulator_info(struct ufs_hba *hba)
* 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
Expand All @@ -273,37 +252,27 @@ 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
#define ufshcd_pltfrm_runtime_resume NULL
#define ufshcd_pltfrm_runtime_idle NULL
#endif /* CONFIG_PM_RUNTIME */

static void ufshcd_pltfrm_shutdown(struct platform_device *pdev)
{
ufshcd_shutdown((struct ufs_hba *)platform_get_drvdata(pdev));
}

/**
* ufshcd_pltfrm_probe - probe routine of the driver
* @pdev: pointer to Platform device handle
Expand Down Expand Up @@ -404,6 +373,7 @@ static const struct dev_pm_ops ufshcd_dev_pm_ops = {
static struct platform_driver ufshcd_pltfrm_driver = {
.probe = ufshcd_pltfrm_probe,
.remove = ufshcd_pltfrm_remove,
.shutdown = ufshcd_pltfrm_shutdown,
.driver = {
.name = "ufshcd",
.owner = THIS_MODULE,
Expand Down
Loading

0 comments on commit 57d104c

Please sign in to comment.