scsi: ufs: provide sysfs attribute to select the PM level
authorsubhashj@codeaurora.org <subhashj@codeaurora.org>
Fri, 23 Dec 2016 02:41:00 +0000 (18:41 -0800)
committerMartin K. Petersen <martin.petersen@oracle.com>
Thu, 5 Jan 2017 23:10:03 +0000 (18:10 -0500)
This patch provides the sysfs attribute to choose the power management
level for UFS runtime and system suspend.

Reviewed-by: Sujit Reddy Thumma <sthumma@codeaurora.org>
Signed-off-by: Subhash Jadavani <subhashj@codeaurora.org>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
drivers/scsi/ufs/ufshcd.c
drivers/scsi/ufs/ufshcd.h

index 4468ba059696490de891ed7d26f00a30dee34745..3036f606545d3f2af3137b36c6e05473a2f1eef5 100644 (file)
@@ -686,6 +686,28 @@ static inline int ufshcd_is_hba_active(struct ufs_hba *hba)
        return (ufshcd_readl(hba, REG_CONTROLLER_ENABLE) & 0x1) ? 0 : 1;
 }
 
+static const char *ufschd_uic_link_state_to_string(
+                       enum uic_link_state state)
+{
+       switch (state) {
+       case UIC_LINK_OFF_STATE:        return "OFF";
+       case UIC_LINK_ACTIVE_STATE:     return "ACTIVE";
+       case UIC_LINK_HIBERN8_STATE:    return "HIBERN8";
+       default:                        return "UNKNOWN";
+       }
+}
+
+static const char *ufschd_ufs_dev_pwr_mode_to_string(
+                       enum ufs_dev_pwr_mode state)
+{
+       switch (state) {
+       case UFS_ACTIVE_PWR_MODE:       return "ACTIVE";
+       case UFS_SLEEP_PWR_MODE:        return "SLEEP";
+       case UFS_POWERDOWN_PWR_MODE:    return "POWERDOWN";
+       default:                        return "UNKNOWN";
+       }
+}
+
 u32 ufshcd_get_local_unipro_ver(struct ufs_hba *hba)
 {
        /* HCI version 1.0 and 1.1 supports UniPro 1.41 */
@@ -6709,6 +6731,127 @@ int ufshcd_runtime_idle(struct ufs_hba *hba)
 }
 EXPORT_SYMBOL(ufshcd_runtime_idle);
 
+static inline ssize_t ufshcd_pm_lvl_store(struct device *dev,
+                                          struct device_attribute *attr,
+                                          const char *buf, size_t count,
+                                          bool rpm)
+{
+       struct ufs_hba *hba = dev_get_drvdata(dev);
+       unsigned long flags, value;
+
+       if (kstrtoul(buf, 0, &value))
+               return -EINVAL;
+
+       if ((value < UFS_PM_LVL_0) || (value >= UFS_PM_LVL_MAX))
+               return -EINVAL;
+
+       spin_lock_irqsave(hba->host->host_lock, flags);
+       if (rpm)
+               hba->rpm_lvl = value;
+       else
+               hba->spm_lvl = value;
+       spin_unlock_irqrestore(hba->host->host_lock, flags);
+       return count;
+}
+
+static ssize_t ufshcd_rpm_lvl_show(struct device *dev,
+               struct device_attribute *attr, char *buf)
+{
+       struct ufs_hba *hba = dev_get_drvdata(dev);
+       int curr_len;
+       u8 lvl;
+
+       curr_len = snprintf(buf, PAGE_SIZE,
+                           "\nCurrent Runtime PM level [%d] => dev_state [%s] link_state [%s]\n",
+                           hba->rpm_lvl,
+                           ufschd_ufs_dev_pwr_mode_to_string(
+                               ufs_pm_lvl_states[hba->rpm_lvl].dev_state),
+                           ufschd_uic_link_state_to_string(
+                               ufs_pm_lvl_states[hba->rpm_lvl].link_state));
+
+       curr_len += snprintf((buf + curr_len), (PAGE_SIZE - curr_len),
+                            "\nAll available Runtime PM levels info:\n");
+       for (lvl = UFS_PM_LVL_0; lvl < UFS_PM_LVL_MAX; lvl++)
+               curr_len += snprintf((buf + curr_len), (PAGE_SIZE - curr_len),
+                                    "\tRuntime PM level [%d] => dev_state [%s] link_state [%s]\n",
+                                   lvl,
+                                   ufschd_ufs_dev_pwr_mode_to_string(
+                                       ufs_pm_lvl_states[lvl].dev_state),
+                                   ufschd_uic_link_state_to_string(
+                                       ufs_pm_lvl_states[lvl].link_state));
+
+       return curr_len;
+}
+
+static ssize_t ufshcd_rpm_lvl_store(struct device *dev,
+               struct device_attribute *attr, const char *buf, size_t count)
+{
+       return ufshcd_pm_lvl_store(dev, attr, buf, count, true);
+}
+
+static void ufshcd_add_rpm_lvl_sysfs_nodes(struct ufs_hba *hba)
+{
+       hba->rpm_lvl_attr.show = ufshcd_rpm_lvl_show;
+       hba->rpm_lvl_attr.store = ufshcd_rpm_lvl_store;
+       sysfs_attr_init(&hba->rpm_lvl_attr.attr);
+       hba->rpm_lvl_attr.attr.name = "rpm_lvl";
+       hba->rpm_lvl_attr.attr.mode = 0644;
+       if (device_create_file(hba->dev, &hba->rpm_lvl_attr))
+               dev_err(hba->dev, "Failed to create sysfs for rpm_lvl\n");
+}
+
+static ssize_t ufshcd_spm_lvl_show(struct device *dev,
+               struct device_attribute *attr, char *buf)
+{
+       struct ufs_hba *hba = dev_get_drvdata(dev);
+       int curr_len;
+       u8 lvl;
+
+       curr_len = snprintf(buf, PAGE_SIZE,
+                           "\nCurrent System PM level [%d] => dev_state [%s] link_state [%s]\n",
+                           hba->spm_lvl,
+                           ufschd_ufs_dev_pwr_mode_to_string(
+                               ufs_pm_lvl_states[hba->spm_lvl].dev_state),
+                           ufschd_uic_link_state_to_string(
+                               ufs_pm_lvl_states[hba->spm_lvl].link_state));
+
+       curr_len += snprintf((buf + curr_len), (PAGE_SIZE - curr_len),
+                            "\nAll available System PM levels info:\n");
+       for (lvl = UFS_PM_LVL_0; lvl < UFS_PM_LVL_MAX; lvl++)
+               curr_len += snprintf((buf + curr_len), (PAGE_SIZE - curr_len),
+                                    "\tSystem PM level [%d] => dev_state [%s] link_state [%s]\n",
+                                   lvl,
+                                   ufschd_ufs_dev_pwr_mode_to_string(
+                                       ufs_pm_lvl_states[lvl].dev_state),
+                                   ufschd_uic_link_state_to_string(
+                                       ufs_pm_lvl_states[lvl].link_state));
+
+       return curr_len;
+}
+
+static ssize_t ufshcd_spm_lvl_store(struct device *dev,
+               struct device_attribute *attr, const char *buf, size_t count)
+{
+       return ufshcd_pm_lvl_store(dev, attr, buf, count, false);
+}
+
+static void ufshcd_add_spm_lvl_sysfs_nodes(struct ufs_hba *hba)
+{
+       hba->spm_lvl_attr.show = ufshcd_spm_lvl_show;
+       hba->spm_lvl_attr.store = ufshcd_spm_lvl_store;
+       sysfs_attr_init(&hba->spm_lvl_attr.attr);
+       hba->spm_lvl_attr.attr.name = "spm_lvl";
+       hba->spm_lvl_attr.attr.mode = 0644;
+       if (device_create_file(hba->dev, &hba->spm_lvl_attr))
+               dev_err(hba->dev, "Failed to create sysfs for spm_lvl\n");
+}
+
+static inline void ufshcd_add_sysfs_nodes(struct ufs_hba *hba)
+{
+       ufshcd_add_rpm_lvl_sysfs_nodes(hba);
+       ufshcd_add_spm_lvl_sysfs_nodes(hba);
+}
+
 /**
  * ufshcd_shutdown - shutdown routine
  * @hba: per adapter instance
@@ -7133,6 +7276,7 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq)
        ufshcd_set_ufs_dev_active(hba);
 
        async_schedule(ufshcd_async_scan, hba);
+       ufshcd_add_sysfs_nodes(hba);
 
        return 0;
 
index bdd2284967f2a5703cf583a68a26a52d7f3f636a..787323be0919fc5948cf9c8ed3f36dd0561bcca4 100644 (file)
@@ -435,6 +435,8 @@ struct ufs_hba {
        enum ufs_pm_level rpm_lvl;
        /* Desired UFS power management level during system PM */
        enum ufs_pm_level spm_lvl;
+       struct device_attribute rpm_lvl_attr;
+       struct device_attribute spm_lvl_attr;
        int pm_op_in_progress;
 
        struct ufshcd_lrb *lrb;