summaryrefslogtreecommitdiff
path: root/drivers/scsi
diff options
context:
space:
mode:
authorGilad Broner <gbroner@codeaurora.org>2015-07-14 14:07:05 +0300
committerDavid Keitel <dkeitel@codeaurora.org>2016-03-22 11:00:13 -0700
commit8f1b4b5eb025e773158e5764b7f538ebda1f7bc8 (patch)
treeeb0471ba2cfddcb216c9143f3f7081c1252c176f /drivers/scsi
parent38da06a7709696c2c1179ae0f7ffc8d5b782ae92 (diff)
scsi: ufs-qcom: add sys-fs entries for PM QoS control
Add sys-fs entries to allow user space control PM QoS latency parameters and enable/disable PM QoS voting. The entries are added under: /sys/bus/platform/devices/624000.ufshc/624000.ufshc:ufs_variant/ pm_qos_enable: write 0 to disable PM QoS, 1 to enable. Example: "echo 1 > pm_qos_enable" pm_qos_latency_us: write the desired value for each cpu group, separated by a comma. Example: "echo 10,20 > pm_qos_latency_us" Change-Id: I9797a1e62c4867ab831b4f18cbb1e0ca9834247b Signed-off-by: Gilad Broner <gbroner@codeaurora.org> Signed-off-by: Krishna Konda <kkonda@codeaurora.org> [venkatg@codeaurora.org: resolved trivial merge conflicts] Signed-off-by: Venkat Gopalakrishnan <venkatg@codeaurora.org>
Diffstat (limited to 'drivers/scsi')
-rw-r--r--drivers/scsi/ufs/ufs-qcom.c135
-rw-r--r--drivers/scsi/ufs/ufs-qcom.h4
-rw-r--r--drivers/scsi/ufs/ufshcd.h1
3 files changed, 138 insertions, 2 deletions
diff --git a/drivers/scsi/ufs/ufs-qcom.c b/drivers/scsi/ufs/ufs-qcom.c
index 8825df4d4189..5b1e860fd9b8 100644
--- a/drivers/scsi/ufs/ufs-qcom.c
+++ b/drivers/scsi/ufs/ufs-qcom.c
@@ -58,8 +58,6 @@ static int ufs_qcom_update_sec_cfg(struct ufs_hba *hba, bool restore_sec_cfg);
static void ufs_qcom_get_default_testbus_cfg(struct ufs_qcom_host *host);
static int ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(struct ufs_hba *hba,
u32 clk_cycles);
-static int ufs_qcom_pm_qos_init(struct ufs_qcom_host *host);
-static void ufs_qcom_pm_qos_remove(struct ufs_qcom_host *host);
static void ufs_qcom_pm_qos_suspend(struct ufs_qcom_host *host);
static void ufs_qcom_dump_regs(struct ufs_hba *hba, int offset, int len,
@@ -1410,9 +1408,122 @@ static void ufs_qcom_pm_qos_unvote_work(struct work_struct *work)
group->latency_us, UFS_QCOM_PM_QOS_UNVOTE_TIMEOUT_US);
}
+static ssize_t ufs_qcom_pm_qos_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev->parent);
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", host->pm_qos.is_enabled);
+}
+
+static ssize_t ufs_qcom_pm_qos_enable_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev->parent);
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ unsigned long value;
+ unsigned long flags;
+ bool enable;
+ int i;
+
+ if (kstrtoul(buf, 0, &value))
+ return -EINVAL;
+
+ enable = !!value;
+
+ /*
+ * Must take the spinlock and save irqs before changing the enabled
+ * flag in order to keep correctness of PM QoS release.
+ */
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ if (enable == host->pm_qos.is_enabled) {
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ return count;
+ }
+ host->pm_qos.is_enabled = enable;
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+
+ if (!enable)
+ for (i = 0; i < host->pm_qos.num_groups; i++) {
+ cancel_work_sync(&host->pm_qos.groups[i].vote_work);
+ cancel_work_sync(&host->pm_qos.groups[i].unvote_work);
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ host->pm_qos.groups[i].state = PM_QOS_UNVOTED;
+ host->pm_qos.groups[i].active_reqs = 0;
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ pm_qos_update_request(&host->pm_qos.groups[i].req,
+ PM_QOS_DEFAULT_VALUE);
+ }
+
+ return count;
+}
+
+static ssize_t ufs_qcom_pm_qos_latency_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev->parent);
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ int ret;
+ int i;
+ int offset = 0;
+
+ for (i = 0; i < host->pm_qos.num_groups; i++) {
+ ret = snprintf(&buf[offset], PAGE_SIZE,
+ "cpu group #%d(mask=0x%lx): %d\n", i,
+ host->pm_qos.groups[i].mask.bits[0],
+ host->pm_qos.groups[i].latency_us);
+ if (ret > 0)
+ offset += ret;
+ else
+ break;
+ }
+
+ return offset;
+}
+
+static ssize_t ufs_qcom_pm_qos_latency_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct ufs_hba *hba = dev_get_drvdata(dev->parent);
+ struct ufs_qcom_host *host = ufshcd_get_variant(hba);
+ unsigned long value;
+ unsigned long flags;
+ char *strbuf;
+ char *strbuf_copy;
+ char *token;
+ int i;
+ int ret;
+
+ /* reserve one byte for null termination */
+ strbuf = kmalloc(count + 1, GFP_KERNEL);
+ if (!strbuf)
+ return -ENOMEM;
+ strbuf_copy = strbuf;
+ strlcpy(strbuf, buf, count + 1);
+
+ for (i = 0; i < host->pm_qos.num_groups; i++) {
+ token = strsep(&strbuf, ",");
+ if (!token)
+ break;
+
+ ret = kstrtoul(token, 0, &value);
+ if (ret)
+ break;
+
+ spin_lock_irqsave(hba->host->host_lock, flags);
+ host->pm_qos.groups[i].latency_us = value;
+ spin_unlock_irqrestore(hba->host->host_lock, flags);
+ }
+
+ kfree(strbuf_copy);
+ return count;
+}
+
static int ufs_qcom_pm_qos_init(struct ufs_qcom_host *host)
{
struct device_node *node = host->hba->dev->of_node;
+ struct device_attribute *attr;
int ret = 0;
int num_groups;
int num_values;
@@ -1510,6 +1621,26 @@ static int ufs_qcom_pm_qos_init(struct ufs_qcom_host *host)
pm_qos_add_request(&host->pm_qos.groups[i].req,
PM_QOS_CPU_DMA_LATENCY, PM_QOS_DEFAULT_VALUE);
+ /* PM QoS latency sys-fs attribute */
+ attr = &host->pm_qos.latency_attr;
+ attr->show = ufs_qcom_pm_qos_latency_show;
+ attr->store = ufs_qcom_pm_qos_latency_store;
+ sysfs_attr_init(&attr->attr);
+ attr->attr.name = "pm_qos_latency_us";
+ attr->attr.mode = S_IRUGO | S_IWUSR;
+ if (device_create_file(host->hba->var->dev, attr))
+ dev_dbg(host->hba->dev, "Failed to create sysfs for pm_qos_latency_us\n");
+
+ /* PM QoS enable sys-fs attribute */
+ attr = &host->pm_qos.enable_attr;
+ attr->show = ufs_qcom_pm_qos_enable_show;
+ attr->store = ufs_qcom_pm_qos_enable_store;
+ sysfs_attr_init(&attr->attr);
+ attr->attr.name = "pm_qos_enable";
+ attr->attr.mode = S_IRUGO | S_IWUSR;
+ if (device_create_file(host->hba->var->dev, attr))
+ dev_dbg(host->hba->dev, "Failed to create sysfs for pm_qos enable\n");
+
host->pm_qos.is_enabled = true;
return 0;
diff --git a/drivers/scsi/ufs/ufs-qcom.h b/drivers/scsi/ufs/ufs-qcom.h
index a8cc22ebb0ee..55edd8e777ef 100644
--- a/drivers/scsi/ufs/ufs-qcom.h
+++ b/drivers/scsi/ufs/ufs-qcom.h
@@ -283,6 +283,8 @@ struct ufs_qcom_pm_qos_cpu_group {
/**
* struct ufs_qcom_pm_qos - data related to PM QoS voting logic
* @groups: PM QoS cpu group state array
+ * @enable_attr: sysfs attribute to enable/disable PM QoS voting logic
+ * @latency_attr: sysfs attribute to set latency value
* @workq: single threaded workqueue to run PM QoS voting/unvoting
* @num_clusters: number of clusters defined
* @default_cpu: cpu to use for voting for request not specifying a cpu
@@ -290,6 +292,8 @@ struct ufs_qcom_pm_qos_cpu_group {
*/
struct ufs_qcom_pm_qos {
struct ufs_qcom_pm_qos_cpu_group *groups;
+ struct device_attribute enable_attr;
+ struct device_attribute latency_attr;
struct workqueue_struct *workq;
int num_groups;
int default_cpu;
diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h
index fadf99a11537..c3cab72f3cb0 100644
--- a/drivers/scsi/ufs/ufshcd.h
+++ b/drivers/scsi/ufs/ufshcd.h
@@ -369,6 +369,7 @@ struct ufs_hba_pm_qos_variant_ops {
* @name: variant name
*/
struct ufs_hba_variant {
+ struct device *dev;
const char *name;
struct ufs_hba_variant_ops *vops;
struct ufs_hba_crypto_variant_ops *crypto_vops;