Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8a3e818f authored by Sahitya Tummala's avatar Sahitya Tummala Committed by Xiaonian Wang
Browse files

mmc: sdhci: Vote for the required MSM bus bandwidth



Vote for the MSM bus bandwidth required by SDHC driver
based on the speed and bus width of the card. Otherwise,
the system clocks may run at minimum clock speed and
thus affecting the performance.

Change-Id: Icf0c8710adbe2770f4eae283a50f4a13671f703f
Signed-off-by: default avatarSahitya Tummala <stummala@codeaurora.org>
Signed-off-by: default avatarSubhash Jadavani <subhashj@codeaurora.org>
[xiaonian@codeaurora.org: fix trivial merge conflict]
Signed-off-by: default avatarXiaonian Wang <xiaonian@codeaurora.org>
parent c6f48d42
Loading
Loading
Loading
Loading
+329 −1
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/mmc/mmc.h>
#include <linux/msm-bus.h>

#include "sdhci-pltfm.h"

@@ -150,6 +151,12 @@ struct sdhci_msm_pin_data {
	struct sdhci_msm_gpio_data *gpio_data;
};

struct sdhci_msm_bus_voting_data {
	struct msm_bus_scale_pdata *bus_pdata;
	unsigned int *bw_vecs;
	unsigned int bw_vecs_size;
};

struct sdhci_msm_pltfm_data {
	/* Supported UHS-I Modes */
	u32 caps;
@@ -163,9 +170,21 @@ struct sdhci_msm_pltfm_data {
	bool nonremovable;
	struct sdhci_msm_pin_data *pin_data;
	u32 cpu_dma_latency_us;
	struct sdhci_msm_bus_voting_data *voting_data;
};

struct sdhci_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 device_attribute max_bus_bw;
};

struct sdhci_msm_host {
	struct platform_device	*pdev;
	void __iomem *core_mem;    /* MSM SDCC mapped address */
	struct clk	 *clk;     /* main SD/MMC bus clock */
	struct clk	 *pclk;    /* SDHC peripheral bus clock */
@@ -175,6 +194,7 @@ struct sdhci_msm_host {
	struct mmc_host  *mmc;
	struct sdhci_pltfm_data sdhci_msm_pdata;
	wait_queue_head_t pwr_irq_wait;
	struct sdhci_msm_bus_vote msm_bus_vote;
};

enum vdd_io_level {
@@ -638,6 +658,44 @@ static int sdhci_msm_setup_pins(struct sdhci_msm_pltfm_data *pdata, bool enable)
	return ret;
}

static int sdhci_msm_dt_get_array(struct device *dev, const char *prop_name,
				 u32 **out, int *len, u32 size)
{
	int ret = 0;
	struct device_node *np = dev->of_node;
	size_t sz;
	u32 *arr = NULL;

	if (!of_get_property(np, prop_name, len)) {
		ret = -EINVAL;
		goto out;
	}
	sz = *len = *len / sizeof(*arr);
	if (sz <= 0 || (size > 0 && (sz != size))) {
		dev_err(dev, "%s invalid size\n", prop_name);
		ret = -EINVAL;
		goto out;
	}

	arr = devm_kzalloc(dev, sz * sizeof(*arr), GFP_KERNEL);
	if (!arr) {
		dev_err(dev, "%s failed allocating memory\n", prop_name);
		ret = -ENOMEM;
		goto out;
	}

	ret = of_property_read_u32_array(np, prop_name, arr, sz);
	if (ret < 0) {
		dev_err(dev, "%s failed reading array %d\n", prop_name, ret);
		goto out;
	}
	*out = arr;
out:
	if (ret)
		*len = 0;
	return ret;
}

#define MAX_PROP_SIZE 32
static int sdhci_msm_dt_parse_vreg_info(struct device *dev,
		struct sdhci_msm_reg_data **vreg_data, const char *vreg_name)
@@ -848,6 +906,218 @@ static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev)
	return NULL;
}

/* Returns required bandwidth in Bytes per Sec */
static unsigned int sdhci_get_bw_required(struct sdhci_host *host,
					struct mmc_ios *ios)
{
	unsigned int bw;

	bw = host->clock;
	/*
	 * 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 sdhci_msm_bus_get_vote_for_bw(struct sdhci_msm_host *host,
					   unsigned int bw)
{
	unsigned int *table = host->pdata->voting_data->bw_vecs;
	unsigned int size = host->pdata->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;
}

/*
 * 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 sdhci_msm_bus_set_vote(struct sdhci_msm_host *msm_host,
					     int vote,
					     unsigned long flags)
{
	struct sdhci_host *host =  platform_get_drvdata(msm_host->pdev);
	int rc = 0;

	if (vote != msm_host->msm_bus_vote.curr_vote) {
		spin_unlock_irqrestore(&host->lock, flags);
		rc = msm_bus_scale_client_update_request(
				msm_host->msm_bus_vote.client_handle, vote);
		spin_lock_irqsave(&host->lock, flags);
		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),
				msm_host->msm_bus_vote.client_handle, vote, rc);
			goto out;
		}
		msm_host->msm_bus_vote.curr_vote = vote;
	}
out:
	return rc;
}

/*
 * Internal work. Work to set 0 bandwidth for msm bus.
 */
static void sdhci_msm_bus_work(struct work_struct *work)
{
	struct sdhci_msm_host *msm_host;
	struct sdhci_host *host;
	unsigned long flags;

	msm_host = container_of(work, struct sdhci_msm_host,
				msm_bus_vote.vote_work.work);
	host =  platform_get_drvdata(msm_host->pdev);

	if (!msm_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->mrq) {
		sdhci_msm_bus_set_vote(msm_host,
			msm_host->msm_bus_vote.min_bw_vote, flags);
	} else
		pr_warning("%s: %s: 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 bw (bandwidth) argument.
 */
static void sdhci_msm_bus_cancel_work_and_set_vote(struct sdhci_host *host,
						unsigned int bw)
{
	int vote;
	unsigned long flags;
	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
	struct sdhci_msm_host *msm_host = pltfm_host->priv;

	cancel_delayed_work_sync(&msm_host->msm_bus_vote.vote_work);
	spin_lock_irqsave(&host->lock, flags);
	vote = sdhci_msm_bus_get_vote_for_bw(msm_host, bw);
	sdhci_msm_bus_set_vote(msm_host, vote, flags);
	spin_unlock_irqrestore(&host->lock, flags);
}

#define MSM_MMC_BUS_VOTING_DELAY	200 /* msecs */

/* This function queues a work which will set the bandwidth requiement to 0 */
static void sdhci_msm_bus_queue_work(struct sdhci_host *host)
{
	unsigned long flags;
	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
	struct sdhci_msm_host *msm_host = pltfm_host->priv;

	spin_lock_irqsave(&host->lock, flags);
	if (msm_host->msm_bus_vote.min_bw_vote !=
		msm_host->msm_bus_vote.curr_vote)
		queue_delayed_work(system_wq,
				   &msm_host->msm_bus_vote.vote_work,
				   msecs_to_jiffies(MSM_MMC_BUS_VOTING_DELAY));
	spin_unlock_irqrestore(&host->lock, flags);
}

static int sdhci_msm_bus_register(struct sdhci_msm_host *host,
				struct platform_device *pdev)
{
	int rc = 0;
	struct msm_bus_scale_pdata *bus_pdata;

	struct sdhci_msm_bus_voting_data *data;
	struct device *dev = &pdev->dev;

	data = devm_kzalloc(dev,
		sizeof(struct sdhci_msm_bus_voting_data), GFP_KERNEL);
	if (!data) {
		dev_err(&pdev->dev,
			"%s: failed to allocate memory\n", __func__);
		rc = -ENOMEM;
		goto out;
	}
	data->bus_pdata = msm_bus_cl_get_pdata(pdev);
	if (data->bus_pdata) {
		rc = sdhci_msm_dt_get_array(dev, "qcom,bus-bw-vectors-bps",
				&data->bw_vecs, &data->bw_vecs_size, 0);
		if (rc) {
			dev_err(&pdev->dev,
				"%s: Failed to get bus-bw-vectors-bps\n",
				__func__);
			goto out;
		}
		host->pdata->voting_data = data;
	}
	if (host->pdata->voting_data &&
		host->pdata->voting_data->bus_pdata &&
		host->pdata->voting_data->bw_vecs &&
		host->pdata->voting_data->bw_vecs_size) {

		bus_pdata = host->pdata->voting_data->bus_pdata;
		host->msm_bus_vote.client_handle =
				msm_bus_scale_register_client(bus_pdata);
		if (!host->msm_bus_vote.client_handle) {
			dev_err(&pdev->dev, "msm_bus_scale_register_client()\n");
			rc = -EFAULT;
			goto out;
		}
		/* cache the vote index for minimum and maximum bandwidth */
		host->msm_bus_vote.min_bw_vote =
				sdhci_msm_bus_get_vote_for_bw(host, 0);
		host->msm_bus_vote.max_bw_vote =
				sdhci_msm_bus_get_vote_for_bw(host, UINT_MAX);
	} else {
		devm_kfree(dev, data);
	}

out:
	return rc;
}

static void sdhci_msm_bus_unregister(struct sdhci_msm_host *host)
{
	if (host->msm_bus_vote.client_handle)
		msm_bus_scale_unregister_client(
			host->msm_bus_vote.client_handle);
}

static void sdhci_msm_bus_voting(struct sdhci_host *host, u32 enable)
{
	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
	struct sdhci_msm_host *msm_host = pltfm_host->priv;
	struct mmc_ios *ios = &host->mmc->ios;
	unsigned int bw;

	if (!msm_host->msm_bus_vote.client_handle)
		return;

	bw = sdhci_get_bw_required(host, ios);
	if (enable)
		sdhci_msm_bus_cancel_work_and_set_vote(host, bw);
	else
		sdhci_msm_bus_queue_work(host);
}

/* Regulator utility functions */
static int sdhci_msm_vreg_init_reg(struct device *dev,
					struct sdhci_msm_reg_data *vreg)
@@ -1200,6 +1470,36 @@ static irqreturn_t sdhci_msm_pwr_irq(int irq, void *data)
	return IRQ_HANDLED;
}

static ssize_t
show_sdhci_max_bus_bw(struct device *dev, struct device_attribute *attr,
			char *buf)
{
	struct sdhci_host *host = dev_get_drvdata(dev);
	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
	struct sdhci_msm_host *msm_host = pltfm_host->priv;

	return snprintf(buf, PAGE_SIZE, "%u\n",
			msm_host->msm_bus_vote.is_max_bw_needed);
}

static ssize_t
store_sdhci_max_bus_bw(struct device *dev, struct device_attribute *attr,
		const char *buf, size_t count)
{
	struct sdhci_host *host = dev_get_drvdata(dev);
	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
	struct sdhci_msm_host *msm_host = pltfm_host->priv;
	uint32_t value;
	unsigned long flags;

	if (!kstrtou32(buf, 0, &value)) {
		spin_lock_irqsave(&host->lock, flags);
		msm_host->msm_bus_vote.is_max_bw_needed = !!value;
		spin_unlock_irqrestore(&host->lock, flags);
	}
	return count;
}

static void sdhci_msm_check_power_status(struct sdhci_host *host)
{
	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
@@ -1305,6 +1605,7 @@ static struct sdhci_ops sdhci_msm_ops = {
	.toggle_cdr = sdhci_msm_toggle_cdr,
	.get_max_segments = sdhci_msm_max_segs,
	.set_clock = sdhci_msm_set_clock,
	.platform_bus_voting = sdhci_msm_bus_voting,
};

static int sdhci_msm_probe(struct platform_device *pdev)
@@ -1335,6 +1636,7 @@ static int sdhci_msm_probe(struct platform_device *pdev)
	pltfm_host = sdhci_priv(host);
	pltfm_host->priv = msm_host;
	msm_host->mmc = host->mmc;
	msm_host->pdev = pdev;

	/* Extract platform data */
	if (pdev->dev.of_node) {
@@ -1469,10 +1771,18 @@ static int sdhci_msm_probe(struct platform_device *pdev)

	host->cpu_dma_latency_us = msm_host->pdata->cpu_dma_latency_us;

	ret = sdhci_msm_bus_register(msm_host, pdev);
	if (ret)
		goto vreg_deinit;

	if (msm_host->msm_bus_vote.client_handle)
		INIT_DELAYED_WORK(&msm_host->msm_bus_vote.vote_work,
				  sdhci_msm_bus_work);

	ret = sdhci_add_host(host);
	if (ret) {
		dev_err(&pdev->dev, "Add host failed (%d)\n", ret);
		goto vreg_deinit;
		goto bus_unregister;
	}

	 /* Set core clk rate, optionally override from dts */
@@ -1484,12 +1794,24 @@ static int sdhci_msm_probe(struct platform_device *pdev)
		goto remove_host;
	}

	msm_host->msm_bus_vote.max_bus_bw.show = show_sdhci_max_bus_bw;
	msm_host->msm_bus_vote.max_bus_bw.store = store_sdhci_max_bus_bw;
	sysfs_attr_init(&msm_host->msm_bus_vote.max_bus_bw.attr);
	msm_host->msm_bus_vote.max_bus_bw.attr.name = "max_bus_bw";
	msm_host->msm_bus_vote.max_bus_bw.attr.mode = S_IRUGO | S_IWUSR;
	ret = device_create_file(&pdev->dev,
			&msm_host->msm_bus_vote.max_bus_bw);
	if (ret)
		goto remove_host;

	/* Successful initialization */
	goto out;

remove_host:
	dead = (readl_relaxed(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff);
	sdhci_remove_host(host, dead);
bus_unregister:
	sdhci_msm_bus_unregister(msm_host);
vreg_deinit:
	sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, false);
clk_disable:
@@ -1518,12 +1840,18 @@ static int sdhci_msm_remove(struct platform_device *pdev)
			0xffffffff);

	pr_debug("%s: %s\n", dev_name(&pdev->dev), __func__);
	device_remove_file(&pdev->dev, &msm_host->msm_bus_vote.max_bus_bw);
	sdhci_remove_host(host, dead);
	sdhci_pltfm_free(pdev);
	sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, false);

	if (pdata->pin_data)
		sdhci_msm_setup_gpio(pdata, false);

	if (msm_host->msm_bus_vote.client_handle) {
		sdhci_msm_bus_cancel_work_and_set_vote(host, 0);
		sdhci_msm_bus_unregister(msm_host);
	}
	return 0;
}

+6 −0
Original line number Diff line number Diff line
@@ -1497,6 +1497,9 @@ static int sdhci_enable(struct mmc_host *mmc)
	if (host->cpu_dma_latency_us)
		pm_qos_update_request(&host->pm_qos_req_dma,
					host->cpu_dma_latency_us);
	if (host->ops->platform_bus_voting)
		host->ops->platform_bus_voting(host, 1);

	return 0;
}

@@ -1507,6 +1510,9 @@ static int sdhci_disable(struct mmc_host *mmc)
	if (host->cpu_dma_latency_us)
		pm_qos_update_request(&host->pm_qos_req_dma,
					PM_QOS_DEFAULT_VALUE);
	if (host->ops->platform_bus_voting)
		host->ops->platform_bus_voting(host, 0);

	return 0;
}

+2 −0
Original line number Diff line number Diff line
@@ -486,6 +486,7 @@ struct sdhci_host {
	bool pending_reset;	/* Cmd/data reset is pending */

	struct mmc_request *mrqs_done[SDHCI_MAX_MRQS];	/* Requests done */
	struct mmc_request *mrq;	/* Current request */
	struct mmc_command *cmd;	/* Current command */
	struct mmc_command *data_cmd;	/* Current data command */
	struct mmc_data *data;	/* Current data request */
@@ -575,6 +576,7 @@ struct sdhci_ops {
	void    (*adma_workaround)(struct sdhci_host *host, u32 intmask);
	unsigned int	(*get_max_segments)(void);
	void    (*card_event)(struct sdhci_host *host);
	void	(*platform_bus_voting)(struct sdhci_host *host, u32 enable);
	void	(*toggle_cdr)(struct sdhci_host *host, bool enable);
	void	(*check_power_status)(struct sdhci_host *host);
	void	(*voltage_switch)(struct sdhci_host *host);