|
@@ -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;
|
|
|
|