ufs: qcom: Create sysfs node "ufs_pm_mode"

In some HQX targets (where Linux is running as guest OS),
when S2R is triggered from host OS, the GVM is sent to
Deep Sleep state.

Since Deep Sleep is getting triggered from Guest point of view,
ufs spm level is set to 5, Power OFF SSU command is sent to the
ufs device and all regulators are turned off from ufs. But since
it is actually an S2R event from Host PoV, the  regulators are
not turned off by the PMIC. This is causing power leakage.

To fix this issue, create a sysfs node "ufs_pm_mode". The Host OS
will write to this node which the event (S2R or Deep Sleep) is
getting triggered at the Host. UFS will set the correct spm level
accordingly. For S2R case, the default spm level is retained while
for Deep Sleep it is set to 5.

The default value for this node is 0( which is set during ufs init).
This means it will not set anything unless "S2R" or "DEEPSLEEP" is
explicitly written to the node.

ufs_pm_mode can have three values: "NONE","S2R","DEEPSLEEP":

1. When "NONE" is written to sysfs node: the spm level
   is set to 5 for deep sleep and existing spm value
   left unchanged for s2idle case.

2. When "S2R" is written to the node: the default spm level
   value is retained.

3. When "DEEPSLEEP" is written to the node: the spm level
   is set to 5.

Change-Id: I232449cf93a3b37652897e74a621bf219302b76b
Signed-off-by: Ram Kumar Dwivedi <quic_rdwivedi@quicinc.com>
This commit is contained in:
Ram Kumar Dwivedi 2024-07-17 19:31:40 +05:30
parent 1f9023b537
commit 4ab95acc04
2 changed files with 78 additions and 3 deletions

View file

@ -87,6 +87,12 @@ enum {
UFS_QCOM_CMD_COMPL,
};
enum {
UFS_QCOM_SYSFS_NONE,
UFS_QCOM_SYSFS_S2R,
UFS_QCOM_SYSFS_DEEPSLEEP,
};
static char android_boot_dev[ANDROID_BOOT_DEV_MAX];
static DEFINE_PER_CPU(struct freq_qos_request, qos_min_req);
@ -5571,6 +5577,59 @@ static ssize_t irq_affinity_support_show(struct device *dev,
static DEVICE_ATTR_RW(irq_affinity_support);
static ssize_t ufs_pm_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int ret = -1;
struct ufs_hba *hba = dev_get_drvdata(dev);
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
switch (host->ufs_pm_mode) {
case 0:
ret = scnprintf(buf, 6, "NONE\n");
break;
case 1:
ret = scnprintf(buf, 5, "S2R\n");
break;
case 2:
ret = scnprintf(buf, 12, "DEEPSLEEP\n");
break;
default:
break;
}
return ret;
}
static ssize_t ufs_pm_mode_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
char kbuff[12] = {0};
struct ufs_hba *hba = dev_get_drvdata(dev);
struct ufs_qcom_host *host = ufshcd_get_variant(hba);
if (!buf)
return -EINVAL;
strscpy(kbuff, buf, 11);
if (!strncasecmp(kbuff, "NONE", 4))
host->ufs_pm_mode = 0;
else if (!strncasecmp(kbuff, "S2R", 3))
host->ufs_pm_mode = 1;
else if (!strncasecmp(kbuff, "DEEPSLEEP", 9))
host->ufs_pm_mode = 2;
else
dev_err(hba->dev, "Invalid entry for ufs_pm_mode\n");
return count;
}
static DEVICE_ATTR_RW(ufs_pm_mode);
static struct attribute *ufs_qcom_sysfs_attrs[] = {
&dev_attr_err_state.attr,
&dev_attr_power_mode.attr,
@ -5582,6 +5641,7 @@ static struct attribute *ufs_qcom_sysfs_attrs[] = {
&dev_attr_hibern8_count.attr,
&dev_attr_ber_th_exceeded.attr,
&dev_attr_irq_affinity_support.attr,
&dev_attr_ufs_pm_mode.attr,
NULL
};
@ -6052,10 +6112,24 @@ static int ufs_qcom_suspend_prepare(struct device *dev)
* regulators is turned off in DS. For other senerios
* like s2idle, retain the default spm level.
*/
if (pm_suspend_target_state == PM_SUSPEND_MEM)
hba->spm_lvl = UFS_PM_LVL_5;
else
switch (host->ufs_pm_mode) {
case UFS_QCOM_SYSFS_NONE:
if (pm_suspend_target_state == PM_SUSPEND_MEM)
hba->spm_lvl = UFS_PM_LVL_5;
else
hba->spm_lvl = host->spm_lvl_default;
break;
case UFS_QCOM_SYSFS_S2R:
hba->spm_lvl = host->spm_lvl_default;
break;
case UFS_QCOM_SYSFS_DEEPSLEEP:
hba->spm_lvl = UFS_PM_LVL_5;
break;
default:
break;
}
dev_info(dev, "spm level is set to %d\n", hba->spm_lvl);
return ufshcd_suspend_prepare(dev);
}

View file

@ -592,6 +592,7 @@ struct ufs_qcom_host {
bool bypass_g4_cfgready;
bool is_dt_pm_level_read;
u32 spm_lvl_default;
u32 ufs_pm_mode;
bool is_phy_pwr_on;
/* Protect the usage of is_phy_pwr_on against racing */
struct mutex phy_mutex;