diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index d96524761540..ccddf5560795 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -33,6 +33,7 @@ #include #include #include +#include #include "sdhci-pltfm.h" @@ -180,6 +181,12 @@ struct sdhci_msm_pin_data { struct sdhci_msm_pad_data *pad_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; @@ -193,9 +200,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 */ @@ -205,6 +224,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 { @@ -1126,6 +1146,218 @@ out: 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) @@ -1476,6 +1708,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); @@ -1581,6 +1843,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) @@ -1611,6 +1874,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) { @@ -1749,10 +2013,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 */ @@ -1764,12 +2036,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: @@ -1798,12 +2082,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_pins(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; } diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 49107102083a..2cc859cdffcf 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1370,6 +1370,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; } @@ -1380,6 +1383,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; } diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 8d0ae224402d..9f1a25241220 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -562,6 +562,7 @@ struct sdhci_ops { void (*adma_workaround)(struct sdhci_host *host, u32 intmask); void (*platform_init)(struct sdhci_host *host); void (*card_event)(struct sdhci_host *host); + void (*platform_bus_voting)(struct sdhci_host *host, u32 enable); void (*voltage_switch)(struct sdhci_host *host); int (*select_drive_strength)(struct sdhci_host *host, struct mmc_card *card,