157 lines
4.2 KiB
C
157 lines
4.2 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
// Copyright (C) 2018 Western Digital Corporation
|
|
|
|
#include <linux/err.h>
|
|
#include <linux/string.h>
|
|
|
|
#include "ufs-sysfs.h"
|
|
|
|
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";
|
|
}
|
|
}
|
|
|
|
static inline ssize_t ufs_sysfs_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_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 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 rpm_lvl_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
return ufs_sysfs_pm_lvl_store(dev, attr, buf, count, true);
|
|
}
|
|
|
|
static ssize_t 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 spm_lvl_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
return ufs_sysfs_pm_lvl_store(dev, attr, buf, count, false);
|
|
}
|
|
|
|
static DEVICE_ATTR_RW(rpm_lvl);
|
|
static DEVICE_ATTR_RW(spm_lvl);
|
|
|
|
static struct attribute *ufs_sysfs_ufshcd_attrs[] = {
|
|
&dev_attr_rpm_lvl.attr,
|
|
&dev_attr_spm_lvl.attr,
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group ufs_sysfs_default_group = {
|
|
.attrs = ufs_sysfs_ufshcd_attrs,
|
|
};
|
|
|
|
static const struct attribute_group *ufs_sysfs_groups[] = {
|
|
&ufs_sysfs_default_group,
|
|
NULL,
|
|
};
|
|
|
|
void ufs_sysfs_add_nodes(struct device *dev)
|
|
{
|
|
int ret;
|
|
|
|
ret = sysfs_create_groups(&dev->kobj, ufs_sysfs_groups);
|
|
if (ret)
|
|
dev_err(dev,
|
|
"%s: sysfs groups creation failed (err = %d)\n",
|
|
__func__, ret);
|
|
}
|
|
|
|
void ufs_sysfs_remove_nodes(struct device *dev)
|
|
{
|
|
sysfs_remove_groups(&dev->kobj, ufs_sysfs_groups);
|
|
}
|