mmc: msm_sdcc: vote for msm bus bandwidth
If there is no voting for the system fabric clock, it may run at the
minimum clock speed. If system fabric is running at lower speed (than what
is needed by SDCC workload), SDCC read & write throughput numbers may be
degraded.
This patch adds the msm bus voting for bandwidth required by SDCC driver
based on card clock speed and bus width.
CRs-Fixed: 355327
Change-Id: I2542a3b9d2b9909d48304d52b3256ec607e433e2
Signed-off-by: Subhash Jadavani <subhashj@codeaurora.org>
diff --git a/drivers/mmc/host/msm_sdcc.c b/drivers/mmc/host/msm_sdcc.c
index 0d11dca..25195b7 100644
--- a/drivers/mmc/host/msm_sdcc.c
+++ b/drivers/mmc/host/msm_sdcc.c
@@ -55,6 +55,7 @@
#include <mach/dma.h>
#include <mach/sdio_al.h>
#include <mach/mpm.h>
+#include <mach/msm_bus.h>
#include "msm_sdcc.h"
#include "msm_sdcc_dml.h"
@@ -72,6 +73,8 @@
/* Use SPS only if transfer size is more than this macro */
#define SPS_MIN_XFER_SIZE MCI_FIFOSIZE
+#define MSM_MMC_BUS_VOTING_DELAY 200 /* msecs */
+
#if defined(CONFIG_DEBUG_FS)
static void msmsdcc_dbg_createhost(struct msmsdcc_host *);
static struct dentry *debugfs_dir;
@@ -2614,6 +2617,179 @@
}
}
+/* Returns required bandwidth in Bytes per Sec */
+static unsigned int msmsdcc_get_bw_required(struct msmsdcc_host *host,
+ struct mmc_ios *ios)
+{
+ unsigned int bw;
+
+ bw = host->clk_rate;
+ /*
+ * For DDR mode, SDCC controller clock will be at
+ * the double rate than the actual clock that goes to card.
+ */
+ if (ios->bus_width == MMC_BUS_WIDTH_4)
+ bw /= 2;
+ else if (ios->bus_width == MMC_BUS_WIDTH_1)
+ bw /= 8;
+
+ return bw;
+}
+
+static int msmsdcc_msm_bus_get_vote_for_bw(struct msmsdcc_host *host,
+ unsigned int bw)
+{
+ unsigned int *table = host->plat->msm_bus_voting_data->bw_vecs;
+ unsigned int size = host->plat->msm_bus_voting_data->bw_vecs_size;
+ int i;
+
+ if (host->msm_bus_vote.is_max_bw_needed && bw)
+ return host->msm_bus_vote.max_bw_vote;
+
+ for (i = 0; i < size; i++) {
+ if (bw <= table[i])
+ break;
+ }
+
+ if (i && (i == size))
+ i--;
+
+ return i;
+}
+
+static int msmsdcc_msm_bus_register(struct msmsdcc_host *host)
+{
+ int rc = 0;
+ struct msm_bus_scale_pdata *use_cases;
+
+ if (host->plat->msm_bus_voting_data &&
+ host->plat->msm_bus_voting_data->use_cases &&
+ host->plat->msm_bus_voting_data->bw_vecs &&
+ host->plat->msm_bus_voting_data->bw_vecs_size) {
+ use_cases = host->plat->msm_bus_voting_data->use_cases;
+ host->msm_bus_vote.client_handle =
+ msm_bus_scale_register_client(use_cases);
+ } else {
+ return 0;
+ }
+
+ if (!host->msm_bus_vote.client_handle) {
+ pr_err("%s: msm_bus_scale_register_client() failed\n",
+ mmc_hostname(host->mmc));
+ rc = -EFAULT;
+ } else {
+ /* cache the vote index for minimum and maximum bandwidth */
+ host->msm_bus_vote.min_bw_vote =
+ msmsdcc_msm_bus_get_vote_for_bw(host, 0);
+ host->msm_bus_vote.max_bw_vote =
+ msmsdcc_msm_bus_get_vote_for_bw(host, UINT_MAX);
+ }
+
+ return rc;
+}
+
+static void msmsdcc_msm_bus_unregister(struct msmsdcc_host *host)
+{
+ if (host->msm_bus_vote.client_handle)
+ msm_bus_scale_unregister_client(
+ host->msm_bus_vote.client_handle);
+}
+
+/*
+ * This function must be called with host lock acquired.
+ * Caller of this function should also ensure that msm bus client
+ * handle is not null.
+ */
+static inline int msmsdcc_msm_bus_set_vote(struct msmsdcc_host *host,
+ int vote,
+ unsigned long flags)
+{
+ int rc = 0;
+
+ if (vote != host->msm_bus_vote.curr_vote) {
+ spin_unlock_irqrestore(&host->lock, flags);
+ rc = msm_bus_scale_client_update_request(
+ host->msm_bus_vote.client_handle, vote);
+ if (rc)
+ pr_err("%s: msm_bus_scale_client_update_request() failed."
+ " bus_client_handle=0x%x, vote=%d, err=%d\n",
+ mmc_hostname(host->mmc),
+ host->msm_bus_vote.client_handle, vote, rc);
+ spin_lock_irqsave(&host->lock, flags);
+ if (!rc)
+ host->msm_bus_vote.curr_vote = vote;
+ }
+
+ return rc;
+}
+
+/*
+ * Internal work. Work to set 0 bandwidth for msm bus.
+ */
+static void msmsdcc_msm_bus_work(struct work_struct *work)
+{
+ struct msmsdcc_host *host = container_of(work,
+ struct msmsdcc_host,
+ msm_bus_vote.vote_work.work);
+ unsigned long flags;
+
+ if (!host->msm_bus_vote.client_handle)
+ return;
+
+ spin_lock_irqsave(&host->lock, flags);
+ /* don't vote for 0 bandwidth if any request is in progress */
+ if (!host->curr.mrq)
+ msmsdcc_msm_bus_set_vote(host,
+ host->msm_bus_vote.min_bw_vote, flags);
+ else
+ pr_warning("%s: %s: SDCC transfer in progress. skipping"
+ " bus voting to 0 bandwidth\n",
+ mmc_hostname(host->mmc), __func__);
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+/*
+ * This function cancels any scheduled delayed work
+ * and sets the bus vote based on ios argument.
+ * If "ios" argument is NULL, bandwidth required is 0 else
+ * calculate the bandwidth based on ios parameters.
+ */
+static void msmsdcc_msm_bus_cancel_work_and_set_vote(
+ struct msmsdcc_host *host,
+ struct mmc_ios *ios)
+{
+ unsigned long flags;
+ unsigned int bw;
+ int vote;
+
+ if (!host->msm_bus_vote.client_handle)
+ return;
+
+ bw = ios ? msmsdcc_get_bw_required(host, ios) : 0;
+
+ cancel_delayed_work_sync(&host->msm_bus_vote.vote_work);
+ spin_lock_irqsave(&host->lock, flags);
+ vote = msmsdcc_msm_bus_get_vote_for_bw(host, bw);
+ msmsdcc_msm_bus_set_vote(host, vote, flags);
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+/* This function queues a work which will set the bandwidth requiement to 0 */
+static void msmsdcc_msm_bus_queue_work(struct msmsdcc_host *host)
+{
+ unsigned long flags;
+
+ if (!host->msm_bus_vote.client_handle)
+ return;
+
+ spin_lock_irqsave(&host->lock, flags);
+ if (host->msm_bus_vote.min_bw_vote != host->msm_bus_vote.curr_vote)
+ queue_delayed_work(system_nrt_wq,
+ &host->msm_bus_vote.vote_work,
+ msecs_to_jiffies(MSM_MMC_BUS_VOTING_DELAY));
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
static void
msmsdcc_cfg_sdio_wakeup(struct msmsdcc_host *host, bool enable_wakeup_irq)
{
@@ -2969,14 +3145,14 @@
msmsdcc_pm_qos_update_latency(host, 1);
if (mmc->card && mmc_card_sdio(mmc->card))
- return 0;
+ goto out;
if (host->sdcc_suspended && host->pending_resume &&
!pm_runtime_suspended(dev)) {
host->pending_resume = false;
pm_runtime_get_noresume(dev);
rc = msmsdcc_runtime_resume(dev);
- goto out;
+ goto skip_get_sync;
}
if (dev->power.runtime_status == RPM_SUSPENDING) {
@@ -2988,14 +3164,15 @@
rc = pm_runtime_get_sync(dev);
-out:
+skip_get_sync:
if (rc < 0) {
pr_info("%s: %s: failed with error %d", mmc_hostname(mmc),
__func__, rc);
msmsdcc_print_rpm_info(host);
return rc;
}
-
+out:
+ msmsdcc_msm_bus_cancel_work_and_set_vote(host, &mmc->ios);
return 0;
}
@@ -3006,8 +3183,10 @@
msmsdcc_pm_qos_update_latency(host, 0);
- if (mmc->card && mmc_card_sdio(mmc->card))
- return 0;
+ if (mmc->card && mmc_card_sdio(mmc->card)) {
+ rc = 0;
+ goto out;
+ }
if (host->plat->disable_runtime_pm)
return -ENOTSUPP;
@@ -3027,7 +3206,9 @@
return rc;
}
- return 0;
+out:
+ msmsdcc_msm_bus_queue_work(host);
+ return rc;
}
#else
static void msmsdcc_print_rpm_info(struct msmsdcc_host *host) {}
@@ -3041,8 +3222,10 @@
msmsdcc_pm_qos_update_latency(host, 1);
- if (mmc->card && mmc_card_sdio(mmc->card))
- return 0;
+ if (mmc->card && mmc_card_sdio(mmc->card)) {
+ rc = 0;
+ goto out;
+ }
if (host->sdcc_suspended && host->pending_resume) {
host->pending_resume = false;
@@ -3067,7 +3250,7 @@
__func__, rc);
return rc;
}
-
+ msmsdcc_msm_bus_cancel_work_and_set_vote(host, &mmc->ios);
return 0;
}
@@ -3079,7 +3262,7 @@
msmsdcc_pm_qos_update_latency(host, 0);
if (mmc->card && mmc_card_sdio(mmc->card))
- return 0;
+ goto out;
mutex_lock(&host->clk_mutex);
spin_lock_irqsave(&host->lock, flags);
@@ -3092,6 +3275,8 @@
spin_unlock_irqrestore(&host->lock, flags);
mutex_unlock(&host->clk_mutex);
+out:
+ msmsdcc_msm_bus_queue_work(host);
return 0;
}
#endif
@@ -4211,10 +4396,46 @@
static DEVICE_ATTR(polling, S_IRUGO | S_IWUSR,
show_polling, set_polling);
+
+static ssize_t
+show_sdcc_to_mem_max_bus_bw(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct mmc_host *mmc = dev_get_drvdata(dev);
+ struct msmsdcc_host *host = mmc_priv(mmc);
+
+ return snprintf(buf, PAGE_SIZE, "%u\n",
+ host->msm_bus_vote.is_max_bw_needed);
+}
+
+static ssize_t
+set_sdcc_to_mem_max_bus_bw(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct mmc_host *mmc = dev_get_drvdata(dev);
+ struct msmsdcc_host *host = mmc_priv(mmc);
+ uint32_t value;
+ unsigned long flags;
+
+ if (!kstrtou32(buf, 0, &value)) {
+ spin_lock_irqsave(&host->lock, flags);
+ host->msm_bus_vote.is_max_bw_needed = !!value;
+ spin_unlock_irqrestore(&host->lock, flags);
+ }
+
+ return count;
+}
+
+static DEVICE_ATTR(max_bus_bw, S_IRUGO | S_IWUSR,
+ show_sdcc_to_mem_max_bus_bw, set_sdcc_to_mem_max_bus_bw);
+
static struct attribute *dev_attrs[] = {
- &dev_attr_polling.attr,
+ &dev_attr_max_bus_bw.attr,
+ /* if polling is enabled, this will be filled with dev_attr_polling */
+ NULL,
NULL,
};
+
static struct attribute_group dev_attr_grp = {
.attrs = dev_attrs,
};
@@ -4716,6 +4937,14 @@
pm_qos_add_request(&host->pm_qos_req_dma,
PM_QOS_CPU_DMA_LATENCY, PM_QOS_DEFAULT_VALUE);
+ ret = msmsdcc_msm_bus_register(host);
+ if (ret)
+ goto pm_qos_remove;
+
+ if (host->msm_bus_vote.client_handle)
+ INIT_DELAYED_WORK(&host->msm_bus_vote.vote_work,
+ msmsdcc_msm_bus_work);
+
ret = msmsdcc_vreg_init(host, true);
if (ret) {
pr_err("%s: msmsdcc_vreg_init() failed (%d)\n", __func__, ret);
@@ -4965,11 +5194,12 @@
#if defined(CONFIG_DEBUG_FS)
msmsdcc_dbg_createhost(host);
#endif
- if (!plat->status_irq) {
- ret = sysfs_create_group(&pdev->dev.kobj, &dev_attr_grp);
- if (ret)
- goto platform_irq_free;
- }
+ if (!plat->status_irq)
+ dev_attrs[1] = &dev_attr_polling.attr;
+
+ ret = sysfs_create_group(&pdev->dev.kobj, &dev_attr_grp);
+ if (ret)
+ goto platform_irq_free;
return 0;
platform_irq_free:
@@ -4999,6 +5229,8 @@
msmsdcc_vreg_init(host, false);
clk_disable:
clk_disable(host->clk);
+ msmsdcc_msm_bus_unregister(host);
+ pm_qos_remove:
if (host->cpu_dma_latency)
pm_qos_remove_request(&host->pm_qos_req_dma);
clk_put:
@@ -5076,6 +5308,11 @@
if (host->cpu_dma_latency)
pm_qos_remove_request(&host->pm_qos_req_dma);
+ if (host->msm_bus_vote.client_handle) {
+ msmsdcc_msm_bus_cancel_work_and_set_vote(host, NULL);
+ msmsdcc_msm_bus_unregister(host);
+ }
+
msmsdcc_vreg_init(host, false);
if (host->is_dma_mode) {
@@ -5190,9 +5427,11 @@
int rc = 0;
unsigned long flags;
+ if (host->plat->is_sdio_al_client) {
+ rc = 0;
+ goto out;
+ }
- if (host->plat->is_sdio_al_client)
- return 0;
pr_debug("%s: %s: start\n", mmc_hostname(mmc), __func__);
if (mmc) {
host->sdcc_suspending = 1;
@@ -5247,6 +5486,9 @@
wake_unlock(&host->sdio_suspend_wlock);
}
pr_debug("%s: %s: ends with err=%d\n", mmc_hostname(mmc), __func__, rc);
+out:
+ /* set bus bandwidth to 0 immediately */
+ msmsdcc_msm_bus_cancel_work_and_set_vote(host, NULL);
return rc;
}
diff --git a/drivers/mmc/host/msm_sdcc.h b/drivers/mmc/host/msm_sdcc.h
index e6bd16c..e29ed91 100644
--- a/drivers/mmc/host/msm_sdcc.h
+++ b/drivers/mmc/host/msm_sdcc.h
@@ -319,6 +319,15 @@
struct tasklet_struct tlet;
};
+struct msmsdcc_msm_bus_vote {
+ uint32_t client_handle;
+ uint32_t curr_vote;
+ int min_bw_vote;
+ int max_bw_vote;
+ bool is_max_bw_needed;
+ struct delayed_work vote_work;
+};
+
struct msmsdcc_host {
struct resource *core_irqres;
struct resource *bam_irqres;
@@ -398,6 +407,7 @@
bool sdio_wakeupirq_disabled;
struct mutex clk_mutex;
bool pending_resume;
+ struct msmsdcc_msm_bus_vote msm_bus_vote;
};
int msmsdcc_set_pwrsave(struct mmc_host *mmc, int pwrsave);