diff options
author | Gilad Broner <gbroner@codeaurora.org> | 2015-07-14 14:07:05 +0300 |
---|---|---|
committer | David Keitel <dkeitel@codeaurora.org> | 2016-03-22 11:00:13 -0700 |
commit | 8f1b4b5eb025e773158e5764b7f538ebda1f7bc8 (patch) | |
tree | eb0471ba2cfddcb216c9143f3f7081c1252c176f /drivers/scsi | |
parent | 38da06a7709696c2c1179ae0f7ffc8d5b782ae92 (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.c | 135 | ||||
-rw-r--r-- | drivers/scsi/ufs/ufs-qcom.h | 4 | ||||
-rw-r--r-- | drivers/scsi/ufs/ufshcd.h | 1 |
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; |