From 67d5bdb5b9014b7bca6d8d35ce6c0380eb0d13db Mon Sep 17 00:00:00 2001 From: Ram Prakash Gupta Date: Fri, 15 May 2020 13:04:16 +0530 Subject: [PATCH 1/7] mmc: sdhci-msm: Add support for pm qos in sdcc Add support of pm qos for sdcc, based on irq affinity and irq notification upon irq migration from one cluster of cpus to any other. Change-Id: I0f6fd6c0cefdfb8876277a785c1bd43714316a15 Signed-off-by: Ram Prakash Gupta --- drivers/mmc/host/sdhci-msm.c | 418 ++++++++++++++++++++++++++++++++++- 1 file changed, 416 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 82a0788795b9..bf795f86c459 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -14,6 +14,11 @@ #include #include #include +#include +#include +#include +#include +#include #include "sdhci-pltfm.h" #include "cqhci.h" @@ -126,6 +131,7 @@ #define CMUX_SHIFT_PHASE_MASK (7 << CMUX_SHIFT_PHASE_SHIFT) #define MSM_MMC_AUTOSUSPEND_DELAY_MS 50 +#define MSM_PMQOS_UNVOTING_DELAY_MS 10 /* msec */ /* Timeout value to avoid infinite waiting for pwr_irq */ #define MSM_PWR_IRQ_TIMEOUT_MS 5000 @@ -359,6 +365,30 @@ struct sdhci_msm_vreg_data { struct sdhci_msm_reg_data *vdd_io_data; }; +/* Per cpu cluster qos group */ +struct qos_cpu_group { + cpumask_t mask; /* CPU mask of cluster */ + unsigned int *votes; /* Different votes for cluster */ + struct dev_pm_qos_request *qos_req; /* Pointer to host qos request*/ + bool voted; + struct sdhci_msm_host *host; + bool initialized; + bool curr_vote; +}; + +/* Per host qos request structure */ +struct sdhci_msm_qos_req { + struct qos_cpu_group *qcg; /* CPU group per host */ + unsigned int num_groups; /* Number of groups */ + unsigned int active_mask; /* Active affine irq mask */ +}; + +enum constraint { + QOS_PERF, + QOS_POWER, + QOS_MAX, +}; + struct sdhci_msm_host { struct platform_device *pdev; void __iomem *core_mem; /* MSM SDCC mapped address */ @@ -389,15 +419,22 @@ struct sdhci_msm_host { bool skip_bus_bw_voting; struct sdhci_msm_bus_vote_data *bus_vote_data; struct delayed_work bus_vote_work; + struct delayed_work pmqos_unvote_work; bool pltfm_init_done; bool core_3_0v_support; bool use_7nm_dll; struct sdhci_msm_dll_hsr *dll_hsr; struct sdhci_msm_regs_restore regs_restore; + struct workqueue_struct *workq; /* QoS work queue */ + struct sdhci_msm_qos_req *sdhci_qos; + struct irq_affinity_notify affinity_notify; }; static struct sdhci_msm_host *sdhci_slot[2]; +static int sdhci_msm_update_qos_constraints(struct qos_cpu_group *qcg, + enum constraint type); + static void sdhci_msm_bus_voting(struct sdhci_host *host, bool enable); static int sdhci_msm_dt_get_array(struct device *dev, const char *prop_name, @@ -3067,6 +3104,343 @@ static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, msm_host->use_7nm_dll = true; } +/* Find cpu group qos from a given cpu */ +static struct qos_cpu_group *cpu_to_group(struct sdhci_msm_qos_req *r, int cpu) +{ + int i; + struct qos_cpu_group *g = r->qcg; + + if (cpu < 0 || cpu > num_possible_cpus()) + return NULL; + + for (i = 0; i < r->num_groups; i++, g++) { + if (cpumask_test_cpu(cpu, &g->mask)) + return &r->qcg[i]; + } + + return NULL; +} + +/* + * Function to put qos vote. This takes qos cpu group of + * host and type of vote as input + */ +static int sdhci_msm_update_qos_constraints(struct qos_cpu_group *qcg, + enum constraint type) +{ + unsigned int vote; + int cpu, err; + struct dev_pm_qos_request *qos_req = qcg->qos_req; + + if (type == QOS_MAX) + vote = S32_MAX; + else + vote = qcg->votes[type]; + + if (qcg->curr_vote == vote) + return 0; + + for_each_cpu(cpu, &qcg->mask) { + err = dev_pm_qos_update_request(qos_req, vote); + if (err < 0) + return err; + ++qos_req; + } + + if (type == QOS_MAX) + qcg->voted = false; + else + qcg->voted = true; + + qcg->curr_vote = vote; + + return 0; +} + +/* Unregister pm qos requests */ +static int remove_group_qos(struct qos_cpu_group *qcg) +{ + int err, cpu; + struct dev_pm_qos_request *qos_req = qcg->qos_req; + + for_each_cpu(cpu, &qcg->mask) { + if (!dev_pm_qos_request_active(qos_req)) { + ++qos_req; + continue; + } + err = dev_pm_qos_remove_request(qos_req); + if (err < 0) + return err; + qos_req++; + } + + return 0; +} + +/* Register pm qos request */ +static int add_group_qos(struct qos_cpu_group *qcg, enum constraint type) +{ + int cpu, err; + struct dev_pm_qos_request *qos_req = qcg->qos_req; + + for_each_cpu(cpu, &qcg->mask) { + memset(qos_req, 0, + sizeof(struct dev_pm_qos_request)); + err = dev_pm_qos_add_request(get_cpu_device(cpu), + qos_req, + DEV_PM_QOS_RESUME_LATENCY, + type); + if (err < 0) + return err; + qos_req++; + } + return 0; +} + +/* Function to remove pm qos vote */ +static void sdhci_msm_unvote_qos_all(struct work_struct *work) +{ + struct sdhci_msm_host *msm_host = container_of(work, + struct sdhci_msm_host, pmqos_unvote_work.work); + struct sdhci_msm_qos_req *qos_req = msm_host->sdhci_qos; + struct qos_cpu_group *qcg; + int i, err; + + if (!qos_req) + return; + qcg = qos_req->qcg; + for (i = 0; ((i < qos_req->num_groups) && qcg->initialized); i++, + qcg++) { + err = sdhci_msm_update_qos_constraints(qcg, QOS_MAX); + if (err) + dev_err(&msm_host->pdev->dev, + "Failed (%d) removing qos vote(%d)\n", err, i); + } +} + +/* Function to vote pmqos from sdcc. */ +static void sdhci_msm_vote_pmqos(struct mmc_host *mmc, int cpu) +{ + struct sdhci_host *host = mmc_priv(mmc); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host); + struct qos_cpu_group *qcg; + + qcg = cpu_to_group(msm_host->sdhci_qos, cpu); + if (!qcg) { + dev_dbg(&msm_host->pdev->dev, "QoS group is undefined\n"); + return; + } + + if (qcg->voted) + return; + + if (sdhci_msm_update_qos_constraints(qcg, QOS_PERF)) + dev_err(&qcg->host->pdev->dev, "%s: update qos - failed\n", + __func__); + dev_dbg(&msm_host->pdev->dev, "Voted pmqos - cpu: %d\n", cpu); +} + +/** + * sdhci_msm_irq_affinity_notify - Callback for affinity changes + * @notify: context as to what irq was changed + * @mask: the new affinity mask + * + * This is a callback function used by the irq_set_affinity_notifier function + * so that we may register to receive changes to the irq affinity masks. + */ +static void +sdhci_msm_irq_affinity_notify(struct irq_affinity_notify *notify, + const cpumask_t *mask) +{ + struct sdhci_msm_host *msm_host = + container_of(notify, struct sdhci_msm_host, affinity_notify); + struct platform_device *pdev = msm_host->pdev; + struct sdhci_msm_qos_req *qos_req = msm_host->sdhci_qos; + struct qos_cpu_group *qcg; + int i, err; + + if (!qos_req) + return; + /* + * If device is in suspend mode, just update the active mask, + * vote would be taken care when device resumes. + */ + msm_host->sdhci_qos->active_mask = cpumask_first(mask); + if (pm_runtime_status_suspended(&pdev->dev)) + return; + + /* Cancel previous scheduled work and unvote votes */ + qcg = qos_req->qcg; + for (i = 0; i < qos_req->num_groups; i++, qcg++) { + err = sdhci_msm_update_qos_constraints(qcg, QOS_MAX); + if (err) + pr_err("%s: Failed (%d) removing qos vote of grp(%d)\n", + mmc_hostname(msm_host->mmc), err, i); + } + + cancel_delayed_work_sync(&msm_host->pmqos_unvote_work); + sdhci_msm_vote_pmqos(msm_host->mmc, + msm_host->sdhci_qos->active_mask); +} + +/** + * sdhci_msm_irq_affinity_release - Callback for affinity notifier release + * @ref: internal core kernel usage + * + * This is a callback function used by the irq_set_affinity_notifier function + * to inform the current notification subscriber that they will no longer + * receive notifications. + */ +static void +inline sdhci_msm_irq_affinity_release(struct kref __always_unused *ref) +{ } + +/* Function for settig up qos based on parsed dt entries */ +static int sdhci_msm_setup_qos(struct sdhci_msm_host *msm_host) +{ + struct platform_device *pdev = msm_host->pdev; + struct sdhci_msm_qos_req *qr = msm_host->sdhci_qos; + struct qos_cpu_group *qcg = qr->qcg; + struct mmc_host *mmc = msm_host->mmc; + struct sdhci_host *host = mmc_priv(mmc); + int i, err; + + if (!msm_host->sdhci_qos) + return 0; + + /* Affine irq to first set of mask */ + WARN_ON(irq_set_affinity_hint(host->irq, &qcg->mask)); + + /* Setup notifier for case of affinity change/migration */ + msm_host->affinity_notify.notify = sdhci_msm_irq_affinity_notify; + msm_host->affinity_notify.release = sdhci_msm_irq_affinity_release; + irq_set_affinity_notifier(host->irq, &msm_host->affinity_notify); + + for (i = 0; i < qr->num_groups; i++, qcg++) { + qcg->qos_req = kcalloc(cpumask_weight(&qcg->mask), + sizeof(struct dev_pm_qos_request), + GFP_KERNEL); + if (!qcg->qos_req) { + dev_err(&pdev->dev, "Memory allocation failed\n"); + if (!i) + return -ENOMEM; + goto free_mem; + } + err = add_group_qos(qcg, S32_MAX); + if (err < 0) { + dev_err(&pdev->dev, "Fail (%d) add qos-req: grp-%d\n", + err, i); + if (!i) { + kfree(qcg->qos_req); + return err; + } + goto free_mem; + } + qcg->initialized = true; + dev_dbg(&pdev->dev, "%s: qcg: 0x%08x | mask: 0x%08x\n", + __func__, qcg, qcg->mask); + } + + INIT_DELAYED_WORK(&msm_host->pmqos_unvote_work, + sdhci_msm_unvote_qos_all); + + /* Vote pmqos during setup for first set of mask*/ + sdhci_msm_update_qos_constraints(qr->qcg, QOS_PERF); + qr->active_mask = cpumask_first(&qr->qcg->mask); + return 0; + +free_mem: + while (i--) { + kfree(qcg->qos_req); + qcg--; + } + + return err; +} + +/* + * QoS init function. It parses dt entries and intializes data + * structures. + */ +static void sdhci_msm_qos_init(struct sdhci_msm_host *msm_host) +{ + struct platform_device *pdev = msm_host->pdev; + struct device_node *np = pdev->dev.of_node; + struct device_node *group_node; + struct sdhci_msm_qos_req *qr; + struct qos_cpu_group *qcg; + int i, err, mask = 0; + + qr = kzalloc(sizeof(*qr), GFP_KERNEL); + if (!qr) + return; + + msm_host->sdhci_qos = qr; + + /* find numbers of qos child node present */ + qr->num_groups = of_get_available_child_count(np); + dev_dbg(&pdev->dev, "num-groups: %d\n", qr->num_groups); + if (!qr->num_groups) { + dev_err(&pdev->dev, "QoS groups undefined\n"); + kfree(qr); + msm_host->sdhci_qos = NULL; + return; + } + qcg = kzalloc(sizeof(*qcg) * qr->num_groups, GFP_KERNEL); + if (!qcg) { + msm_host->sdhci_qos = NULL; + kfree(qr); + return; + } + + /* + * Assign qos cpu group/cluster to host qos request and + * read child entries of qos node + */ + qr->qcg = qcg; + for_each_available_child_of_node(np, group_node) { + err = of_property_read_u32(group_node, "mask", &mask); + if (err) { + dev_dbg(&pdev->dev, "Error reading group mask: %d\n", + err); + continue; + } + qcg->mask.bits[0] = mask; + if (!cpumask_subset(&qcg->mask, cpu_possible_mask)) { + dev_err(&pdev->dev, "Invalid group mask\n"); + goto out_vote_err; + } + + err = of_property_count_u32_elems(group_node, "vote"); + if (err <= 0) { + dev_err(&pdev->dev, "1 vote is needed, bailing out\n"); + goto out_vote_err; + } + qcg->votes = kmalloc(sizeof(*qcg->votes) * err, GFP_KERNEL); + if (!qcg->votes) + goto out_vote_err; + for (i = 0; i < err; i++) { + if (of_property_read_u32_index(group_node, "vote", i, + &qcg->votes[i])) + goto out_vote_err; + } + qcg->host = msm_host; + ++qcg; + } + err = sdhci_msm_setup_qos(msm_host); + if (!err) + return; + dev_err(&pdev->dev, "Failed to setup PM QoS.\n"); + +out_vote_err: + for (i = 0, qcg = qr->qcg; i < qr->num_groups; i++, qcg++) + kfree(qcg->votes); + kfree(qr->qcg); + kfree(qr); + msm_host->sdhci_qos = NULL; +} + static int sdhci_msm_probe(struct platform_device *pdev) { struct sdhci_host *host; @@ -3312,6 +3686,14 @@ static int sdhci_msm_probe(struct platform_device *pdev) pm_runtime_use_autosuspend(&pdev->dev); host->mmc_host_ops.execute_tuning = sdhci_msm_execute_tuning; + + msm_host->workq = create_workqueue("sdhci_msm_generic_swq"); + if (!msm_host->workq) + dev_err(&pdev->dev, "Generic swq creation failed\n"); + + /* Initialize pmqos */ + sdhci_msm_qos_init(msm_host); + if (of_property_read_bool(node, "supports-cqe")) ret = sdhci_msm_cqe_add_host(host, pdev); else @@ -3366,6 +3748,9 @@ static int sdhci_msm_remove(struct platform_device *pdev) struct sdhci_host *host = platform_get_drvdata(pdev); struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host); + struct sdhci_msm_qos_req *r = msm_host->sdhci_qos; + struct qos_cpu_group *qcg; + int i; int dead = (readl_relaxed(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); @@ -3374,6 +3759,19 @@ static int sdhci_msm_remove(struct platform_device *pdev) sdhci_msm_vreg_init(&pdev->dev, msm_host, false); pm_runtime_get_sync(&pdev->dev); + + /* Add delay to complete resume where qos vote is scheduled */ + if (!r) + goto skip_removing_qos; + qcg = r->qcg; + msleep(50); + for (i = 0; i < r->num_groups; i++, qcg++) { + sdhci_msm_update_qos_constraints(qcg, QOS_MAX); + remove_group_qos(qcg); + } + destroy_workqueue(msm_host->workq); + +skip_removing_qos: pm_runtime_disable(&pdev->dev); pm_runtime_put_noidle(&pdev->dev); @@ -3394,7 +3792,15 @@ static __maybe_unused int sdhci_msm_runtime_suspend(struct device *dev) struct sdhci_host *host = dev_get_drvdata(dev); struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host); + struct sdhci_msm_qos_req *qos_req = msm_host->sdhci_qos; + if (!qos_req) + goto skip_qos; + queue_delayed_work(msm_host->workq, + &msm_host->pmqos_unvote_work, + msecs_to_jiffies(MSM_PMQOS_UNVOTING_DELAY_MS)); + +skip_qos: sdhci_msm_registers_save(host); clk_bulk_disable_unprepare(ARRAY_SIZE(msm_host->bulk_clks), msm_host->bulk_clks); @@ -3407,10 +3813,11 @@ static __maybe_unused int sdhci_msm_runtime_resume(struct device *dev) struct sdhci_host *host = dev_get_drvdata(dev); struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host); + struct sdhci_msm_qos_req *qos_req = msm_host->sdhci_qos; int ret; ret = clk_bulk_prepare_enable(ARRAY_SIZE(msm_host->bulk_clks), - msm_host->bulk_clks); + msm_host->bulk_clks); if (ret) return ret; @@ -3420,8 +3827,15 @@ static __maybe_unused int sdhci_msm_runtime_resume(struct device *dev) * restore the SDR DLL settings when the clock is ungated. */ if (msm_host->restore_dll_config && msm_host->clk_rate) - return sdhci_msm_restore_sdr_dll_config(host); + sdhci_msm_restore_sdr_dll_config(host); + if (!qos_req) + goto skip_qos_vote; + cancel_delayed_work_sync(&msm_host->pmqos_unvote_work); + sdhci_msm_vote_pmqos(msm_host->mmc, + msm_host->sdhci_qos->active_mask); + +skip_qos_vote: sdhci_msm_bus_voting(host, true); return 0; } From c2e1c7e55f34e747776f21c8a7e0867d1d25e70b Mon Sep 17 00:00:00 2001 From: Ram Prakash Gupta Date: Wed, 15 Jul 2020 02:34:24 +0530 Subject: [PATCH 2/7] mmc: sdhci-msm: Make clk gating a delayed work With MMC_CAP_SYNC_RUNTIME_PM enabled now host suspend would happen just after host release and it would make clk gating very aggressive, which would have performance impact. Make sdcc clk gating a delayed work to support MMC_CAP_SYNC_RUNTIME_PM. Change-Id: I9cf7c895e3dbcc494564389c916b6dd61f5fc22d Signed-off-by: Ram Prakash Gupta --- drivers/mmc/host/sdhci-msm.c | 69 +++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 21 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index bf795f86c459..43eb72e3cb82 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -132,6 +132,7 @@ #define MSM_MMC_AUTOSUSPEND_DELAY_MS 50 #define MSM_PMQOS_UNVOTING_DELAY_MS 10 /* msec */ +#define MSM_CLK_GATING_DELAY_MS 200 /* msec */ /* Timeout value to avoid infinite waiting for pwr_irq */ #define MSM_PWR_IRQ_TIMEOUT_MS 5000 @@ -420,6 +421,7 @@ struct sdhci_msm_host { struct sdhci_msm_bus_vote_data *bus_vote_data; struct delayed_work bus_vote_work; struct delayed_work pmqos_unvote_work; + struct delayed_work clk_gating_work; bool pltfm_init_done; bool core_3_0v_support; bool use_7nm_dll; @@ -2677,7 +2679,11 @@ static void sdhci_msm_bus_cancel_work_and_set_vote(struct sdhci_host *host, sdhci_msm_bus_set_vote(msm_host, vote); } -#define MSM_MMC_BUS_VOTING_DELAY 200 /* msecs */ +/* Add delay of 100 mSec as MMC_CAP_SYNC_RUNTIME_PM is + * would suspend device immidiatly. + * 200(deferred time) + 100 (suspend time) = 300. + */ +#define MSM_MMC_BUS_VOTING_DELAY 300 /* msecs */ #define VOTE_ZERO 0 /* @@ -3104,6 +3110,17 @@ static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, msm_host->use_7nm_dll = true; } +static void sdhci_msm_clk_gating_delayed_work(struct work_struct *work) +{ + struct sdhci_msm_host *msm_host = container_of(work, + struct sdhci_msm_host, clk_gating_work.work); + struct sdhci_host *host = mmc_priv(msm_host->mmc); + + sdhci_msm_registers_save(host); + clk_bulk_disable_unprepare(ARRAY_SIZE(msm_host->bulk_clks), + msm_host->bulk_clks); +} + /* Find cpu group qos from a given cpu */ static struct qos_cpu_group *cpu_to_group(struct sdhci_msm_qos_req *r, int cpu) { @@ -3566,6 +3583,9 @@ static int sdhci_msm_probe(struct platform_device *pdev) dev_warn(&pdev->dev, "TCXO clk not present (%d)\n", ret); } + INIT_DELAYED_WORK(&msm_host->clk_gating_work, + sdhci_msm_clk_gating_delayed_work); + ret = sdhci_msm_bus_register(msm_host, pdev); if (ret && !msm_host->skip_bus_bw_voting) { dev_err(&pdev->dev, "Bus registration failed (%d)\n", ret); @@ -3672,7 +3692,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) goto vreg_deinit; } - msm_host->mmc->caps |= MMC_CAP_AGGRESSIVE_PM; + msm_host->mmc->caps |= MMC_CAP_AGGRESSIVE_PM | MMC_CAP_SYNC_RUNTIME_PM; msm_host->mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY | MMC_CAP_NEED_RSP_BUSY; #if defined(CONFIG_SDC_QTI) @@ -3681,9 +3701,11 @@ static int sdhci_msm_probe(struct platform_device *pdev) pm_runtime_get_noresume(&pdev->dev); pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); - pm_runtime_set_autosuspend_delay(&pdev->dev, + if (!(msm_host->mmc->caps & MMC_CAP_SYNC_RUNTIME_PM)) { + pm_runtime_set_autosuspend_delay(&pdev->dev, MSM_MMC_AUTOSUSPEND_DELAY_MS); - pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_use_autosuspend(&pdev->dev); + } host->mmc_host_ops.execute_tuning = sdhci_msm_execute_tuning; @@ -3801,9 +3823,10 @@ static __maybe_unused int sdhci_msm_runtime_suspend(struct device *dev) msecs_to_jiffies(MSM_PMQOS_UNVOTING_DELAY_MS)); skip_qos: - sdhci_msm_registers_save(host); - clk_bulk_disable_unprepare(ARRAY_SIZE(msm_host->bulk_clks), - msm_host->bulk_clks); + queue_delayed_work(msm_host->workq, + &msm_host->clk_gating_work, + msecs_to_jiffies(MSM_CLK_GATING_DELAY_MS)); + sdhci_msm_bus_voting(host, false); return 0; } @@ -3816,24 +3839,28 @@ static __maybe_unused int sdhci_msm_runtime_resume(struct device *dev) struct sdhci_msm_qos_req *qos_req = msm_host->sdhci_qos; int ret; - ret = clk_bulk_prepare_enable(ARRAY_SIZE(msm_host->bulk_clks), - msm_host->bulk_clks); - if (ret) - return ret; + ret = cancel_delayed_work_sync(&msm_host->clk_gating_work); + if (!ret) { + ret = clk_bulk_prepare_enable(ARRAY_SIZE(msm_host->bulk_clks), + msm_host->bulk_clks); + if (ret) + return ret; - sdhci_msm_registers_restore(host); - /* - * Whenever core-clock is gated dynamically, it's needed to - * restore the SDR DLL settings when the clock is ungated. - */ - if (msm_host->restore_dll_config && msm_host->clk_rate) - sdhci_msm_restore_sdr_dll_config(host); + sdhci_msm_registers_restore(host); + /* + * Whenever core-clock is gated dynamically, it's needed to + * restore the SDR DLL settings when the clock is ungated. + */ + if (msm_host->restore_dll_config && msm_host->clk_rate) + sdhci_msm_restore_sdr_dll_config(host); + } if (!qos_req) goto skip_qos_vote; - cancel_delayed_work_sync(&msm_host->pmqos_unvote_work); - sdhci_msm_vote_pmqos(msm_host->mmc, - msm_host->sdhci_qos->active_mask); + ret = cancel_delayed_work_sync(&msm_host->pmqos_unvote_work); + if (!ret) + sdhci_msm_vote_pmqos(msm_host->mmc, + msm_host->sdhci_qos->active_mask); skip_qos_vote: sdhci_msm_bus_voting(host, true); From 1c76098345ac09c1351c70d8cfd3abebfabc78f3 Mon Sep 17 00:00:00 2001 From: Ram Prakash Gupta Date: Wed, 22 Jul 2020 08:22:02 +0530 Subject: [PATCH 3/7] mmc: sdhci-msm: Club bus voting with clk gating When sdcc clk is gated, same time driver removes bus vote. So club bus voting with clk gating, this helps reduce one delayed job from sdcc driver. Change-Id: I1129eba9caa5ac08aadc111659d0aa88b02c11db Signed-off-by: Ram Prakash Gupta --- drivers/mmc/host/sdhci-msm.c | 84 ++++++++++-------------------------- 1 file changed, 22 insertions(+), 62 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 43eb72e3cb82..fd5425eb999b 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2267,8 +2267,14 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) msm_set_clock_rate_for_bus_mode(host, clock); out: - if (!msm_host->skip_bus_bw_voting) - sdhci_msm_bus_voting(host, !!clock); + /* Vote on bus only with clock frequency or when changing clock + * frequency. No need to vote when setting clock frequency as 0 + * because after setting clock at 0, we release host, which will + * eventually call host runtime suspend and unvoting would be + * taken care in runtime suspend call. + */ + if (!msm_host->skip_bus_bw_voting && clock) + sdhci_msm_bus_voting(host, true); __sdhci_msm_set_clock(host, clock); } @@ -2637,33 +2643,11 @@ out: return cpu_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; - - msm_host = container_of(work, struct sdhci_msm_host, - bus_vote_work.work); - host = platform_get_drvdata(msm_host->pdev); - - if (!msm_host->bus_vote_data->sdhc_ddr || - !msm_host->bus_vote_data->cpu_sdhc) - return; - /* don't vote for 0 bandwidth if any request is in progress */ - if (!host->mmc->ongoing_mrq) - sdhci_msm_bus_set_vote(msm_host, 0); - else - pr_debug("Transfer in progress. Skipping bus voting to 0\n"); -} - /* * 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, +static void sdhci_msm_bus_get_and_set_vote(struct sdhci_host *host, unsigned int bw) { int vote; @@ -2674,34 +2658,10 @@ static void sdhci_msm_bus_cancel_work_and_set_vote(struct sdhci_host *host, !msm_host->bus_vote_data->sdhc_ddr || !msm_host->bus_vote_data->cpu_sdhc) return; - cancel_delayed_work_sync(&msm_host->bus_vote_work); vote = sdhci_msm_bus_get_vote_for_bw(msm_host, bw); sdhci_msm_bus_set_vote(msm_host, vote); } -/* Add delay of 100 mSec as MMC_CAP_SYNC_RUNTIME_PM is - * would suspend device immidiatly. - * 200(deferred time) + 100 (suspend time) = 300. - */ -#define MSM_MMC_BUS_VOTING_DELAY 300 /* msecs */ -#define VOTE_ZERO 0 - -/* - * This function queues a work which will set the bandwidth - * requirement to 0. - */ -static void sdhci_msm_bus_queue_work(struct sdhci_host *host) -{ - struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); - struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host); - - if (msm_host->bus_vote_data && - msm_host->bus_vote_data->curr_vote != VOTE_ZERO) - queue_delayed_work(system_wq, - &msm_host->bus_vote_work, - msecs_to_jiffies(MSM_MMC_BUS_VOTING_DELAY)); -} - static struct sdhci_msm_bus_vote_data *sdhci_msm_get_bus_vote_data(struct device *dev, struct sdhci_msm_host *host) @@ -2821,8 +2781,6 @@ static int sdhci_msm_bus_register(struct sdhci_msm_host *host, return ret; } - INIT_DELAYED_WORK(&host->bus_vote_work, sdhci_msm_bus_work); - return ret; } @@ -2842,9 +2800,9 @@ static void sdhci_msm_bus_voting(struct sdhci_host *host, bool enable) if (enable) { bw = sdhci_get_bw_required(host, ios); - sdhci_msm_bus_cancel_work_and_set_vote(host, bw); + sdhci_msm_bus_get_and_set_vote(host, bw); } else - sdhci_msm_bus_queue_work(host); + sdhci_msm_bus_get_and_set_vote(host, 0); } /*****************************************************************************\ @@ -3110,7 +3068,7 @@ static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, msm_host->use_7nm_dll = true; } -static void sdhci_msm_clk_gating_delayed_work(struct work_struct *work) +static void sdhci_msm_clkgate_bus_delayed_work(struct work_struct *work) { struct sdhci_msm_host *msm_host = container_of(work, struct sdhci_msm_host, clk_gating_work.work); @@ -3119,6 +3077,7 @@ static void sdhci_msm_clk_gating_delayed_work(struct work_struct *work) sdhci_msm_registers_save(host); clk_bulk_disable_unprepare(ARRAY_SIZE(msm_host->bulk_clks), msm_host->bulk_clks); + sdhci_msm_bus_voting(host, false); } /* Find cpu group qos from a given cpu */ @@ -3584,7 +3543,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) } INIT_DELAYED_WORK(&msm_host->clk_gating_work, - sdhci_msm_clk_gating_delayed_work); + sdhci_msm_clkgate_bus_delayed_work); ret = sdhci_msm_bus_register(msm_host, pdev); if (ret && !msm_host->skip_bus_bw_voting) { @@ -3751,7 +3710,7 @@ vreg_deinit: sdhci_msm_vreg_init(&pdev->dev, msm_host, false); bus_unregister: if (!msm_host->skip_bus_bw_voting) { - sdhci_msm_bus_cancel_work_and_set_vote(host, 0); + sdhci_msm_bus_get_and_set_vote(host, 0); sdhci_msm_bus_unregister(&pdev->dev, msm_host); } clk_disable: @@ -3802,7 +3761,7 @@ skip_removing_qos: if (!IS_ERR(msm_host->bus_clk)) clk_disable_unprepare(msm_host->bus_clk); if (!msm_host->skip_bus_bw_voting) { - sdhci_msm_bus_cancel_work_and_set_vote(host, 0); + sdhci_msm_bus_get_and_set_vote(host, 0); sdhci_msm_bus_unregister(&pdev->dev, msm_host); } sdhci_pltfm_free(pdev); @@ -3827,7 +3786,6 @@ skip_qos: &msm_host->clk_gating_work, msecs_to_jiffies(MSM_CLK_GATING_DELAY_MS)); - sdhci_msm_bus_voting(host, false); return 0; } @@ -3841,10 +3799,14 @@ static __maybe_unused int sdhci_msm_runtime_resume(struct device *dev) ret = cancel_delayed_work_sync(&msm_host->clk_gating_work); if (!ret) { + sdhci_msm_bus_voting(host, true); ret = clk_bulk_prepare_enable(ARRAY_SIZE(msm_host->bulk_clks), msm_host->bulk_clks); - if (ret) + if (ret) { + dev_err(dev, "Failed to enable clocks %d\n", ret); + sdhci_msm_bus_voting(host, false); return ret; + } sdhci_msm_registers_restore(host); /* @@ -3856,14 +3818,12 @@ static __maybe_unused int sdhci_msm_runtime_resume(struct device *dev) } if (!qos_req) - goto skip_qos_vote; + return 0; ret = cancel_delayed_work_sync(&msm_host->pmqos_unvote_work); if (!ret) sdhci_msm_vote_pmqos(msm_host->mmc, msm_host->sdhci_qos->active_mask); -skip_qos_vote: - sdhci_msm_bus_voting(host, true); return 0; } From e288b5efd133d63224bc5843aecbc3fa025330d5 Mon Sep 17 00:00:00 2001 From: Ram Prakash Gupta Date: Mon, 3 Aug 2020 02:10:13 +0530 Subject: [PATCH 4/7] mmc: sdhci-msm: Add sysfs entry for pm qos & clk gating Add sysfs entry for pm qos & clk gating delayed works. Change-Id: I38b73931d8845ab5aa8ec07d1ae40e321c206058 Signed-off-by: Ram Prakash Gupta --- drivers/mmc/host/sdhci-msm.c | 116 ++++++++++++++++++++++++++++++++--- 1 file changed, 106 insertions(+), 10 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index fd5425eb999b..626a56861d08 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -430,6 +430,10 @@ struct sdhci_msm_host { struct workqueue_struct *workq; /* QoS work queue */ struct sdhci_msm_qos_req *sdhci_qos; struct irq_affinity_notify affinity_notify; + struct device_attribute clk_gating; + struct device_attribute pm_qos; + u32 clk_gating_delay; + u32 pm_qos_delay; }; static struct sdhci_msm_host *sdhci_slot[2]; @@ -3417,6 +3421,101 @@ out_vote_err: msm_host->sdhci_qos = NULL; } +static ssize_t show_sdhci_msm_clk_gating(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 = sdhci_pltfm_priv(pltfm_host); + + return scnprintf(buf, PAGE_SIZE, "%u\n", msm_host->clk_gating_delay); +} + +static ssize_t store_sdhci_msm_clk_gating(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 = sdhci_pltfm_priv(pltfm_host); + uint32_t value; + + if (!kstrtou32(buf, 0, &value)) { + msm_host->clk_gating_delay = value; + dev_info(dev, "set clk scaling work delay (%u)\n", value); + } + + return count; +} + +static ssize_t show_sdhci_msm_pm_qos(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 = sdhci_pltfm_priv(pltfm_host); + + return scnprintf(buf, PAGE_SIZE, "%u\n", msm_host->pm_qos_delay); +} + +static ssize_t store_sdhci_msm_pm_qos(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 = sdhci_pltfm_priv(pltfm_host); + uint32_t value; + + if (!kstrtou32(buf, 0, &value)) { + msm_host->pm_qos_delay = value; + dev_info(dev, "set pm qos work delay (%u)\n", value); + } + + return count; +} + +static void sdhci_msm_init_sysfs_gating_qos(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host); + int ret; + + msm_host->clk_gating.show = show_sdhci_msm_clk_gating; + msm_host->clk_gating.store = store_sdhci_msm_clk_gating; + sysfs_attr_init(&msm_host->clk_gating.attr); + msm_host->clk_gating.attr.name = "clk_gating"; + msm_host->clk_gating.attr.mode = 0644; + ret = device_create_file(dev, &msm_host->clk_gating); + if (ret) { + pr_err("%s: %s: failed creating clk gating attr: %d\n", + mmc_hostname(host->mmc), __func__, ret); + } + + msm_host->pm_qos.show = show_sdhci_msm_pm_qos; + msm_host->pm_qos.store = store_sdhci_msm_pm_qos; + sysfs_attr_init(&msm_host->pm_qos.attr); + msm_host->pm_qos.attr.name = "pm_qos"; + msm_host->pm_qos.attr.mode = 0644; + ret = device_create_file(dev, &msm_host->pm_qos); + if (ret) { + pr_err("%s: %s: failed creating pm qos attr: %d\n", + mmc_hostname(host->mmc), __func__, ret); + } +} + +static void sdhci_msm_setup_pm(struct platform_device *pdev, + struct sdhci_msm_host *msm_host) +{ + pm_runtime_get_noresume(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + if (!(msm_host->mmc->caps & MMC_CAP_SYNC_RUNTIME_PM)) { + pm_runtime_set_autosuspend_delay(&pdev->dev, + MSM_MMC_AUTOSUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(&pdev->dev); + } +} + static int sdhci_msm_probe(struct platform_device *pdev) { struct sdhci_host *host; @@ -3657,14 +3756,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) #if defined(CONFIG_SDC_QTI) msm_host->mmc->caps2 |= MMC_CAP2_CLK_SCALE; #endif - pm_runtime_get_noresume(&pdev->dev); - pm_runtime_set_active(&pdev->dev); - pm_runtime_enable(&pdev->dev); - if (!(msm_host->mmc->caps & MMC_CAP_SYNC_RUNTIME_PM)) { - pm_runtime_set_autosuspend_delay(&pdev->dev, - MSM_MMC_AUTOSUSPEND_DELAY_MS); - pm_runtime_use_autosuspend(&pdev->dev); - } + sdhci_msm_setup_pm(pdev, msm_host); host->mmc_host_ops.execute_tuning = sdhci_msm_execute_tuning; @@ -3672,8 +3764,12 @@ static int sdhci_msm_probe(struct platform_device *pdev) if (!msm_host->workq) dev_err(&pdev->dev, "Generic swq creation failed\n"); + msm_host->clk_gating_delay = MSM_CLK_GATING_DELAY_MS; + msm_host->pm_qos_delay = MSM_PMQOS_UNVOTING_DELAY_MS; /* Initialize pmqos */ sdhci_msm_qos_init(msm_host); + /* Initialize sysfs entries */ + sdhci_msm_init_sysfs_gating_qos(dev); if (of_property_read_bool(node, "supports-cqe")) ret = sdhci_msm_cqe_add_host(host, pdev); @@ -3779,12 +3875,12 @@ static __maybe_unused int sdhci_msm_runtime_suspend(struct device *dev) goto skip_qos; queue_delayed_work(msm_host->workq, &msm_host->pmqos_unvote_work, - msecs_to_jiffies(MSM_PMQOS_UNVOTING_DELAY_MS)); + msecs_to_jiffies(msm_host->pm_qos_delay)); skip_qos: queue_delayed_work(msm_host->workq, &msm_host->clk_gating_work, - msecs_to_jiffies(MSM_CLK_GATING_DELAY_MS)); + msecs_to_jiffies(msm_host->clk_gating_delay)); return 0; } From 5827a3e53d9417c5efe1c8ed179da8d3174def44 Mon Sep 17 00:00:00 2001 From: Sarthak Garg Date: Fri, 7 Aug 2020 17:02:52 +0530 Subject: [PATCH 5/7] mmc: sdhci-msm: skip eMMC slot probe if eMMC isn't a bootdevice If eMMC is not a primary bootdevice, there isn't any point of probing eMMC device hence disable the probing in such case. Change-Id: I67a025606b03bb3cc21e818c2b4b519aa2b53888 Signed-off-by: Sarthak Garg --- drivers/mmc/host/sdhci-msm.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 626a56861d08..de180ed8c33c 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -436,6 +436,18 @@ struct sdhci_msm_host { u32 pm_qos_delay; }; +#define ANDROID_BOOT_DEV_MAX 30 +static char android_boot_dev[ANDROID_BOOT_DEV_MAX]; + +#ifndef MODULE +static int __init get_android_boot_dev(char *str) +{ + strlcpy(android_boot_dev, str, ANDROID_BOOT_DEV_MAX); + return 1; +} +__setup("androidboot.bootdevice=", get_android_boot_dev); +#endif + static struct sdhci_msm_host *sdhci_slot[2]; static int sdhci_msm_update_qos_constraints(struct qos_cpu_group *qcg, @@ -3552,6 +3564,13 @@ static int sdhci_msm_probe(struct platform_device *pdev) dev_err(&pdev->dev, "get slot index failed %d\n", ret); else if (ret <= 2) sdhci_slot[ret-1] = msm_host; + + if (of_property_read_bool(dev->of_node, "non-removable") && + strlen(android_boot_dev) && + strcmp(android_boot_dev, dev_name(dev))) { + ret = -ENODEV; + goto pltfm_free; + } } /* From 200e0dd1f79eb6ab773a64c5e901d8c17efc5704 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Fri, 7 Feb 2020 17:34:28 +0530 Subject: [PATCH 6/7] UPSTREAM: mmc: sdhci-msm: Don't enable PWRSAVE_DLL for certain sdhc hosts SDHC core with new 14lpp and later tech DLL should not enable PWRSAVE_DLL since such controller's internal gating cannot meet following MCLK requirement: When MCLK is gated OFF, it is not gated for less than 0.5us and MCLK must be switched on for at-least 1us before DATA starts coming. Adding support for this requirement. Change-Id: I571c8fb40835e094d8feb4df5c2d049c02a0a2f2 Signed-off-by: Ritesh Harjani Signed-off-by: Veerabhadrarao Badiganti Reviewed-by: Can Guo Acked-by: Adrian Hunter Link: https://lore.kernel.org/r/1581077075-26011-1-git-send-email-vbadigan@codeaurora.org Signed-off-by: Ulf Hansson Signed-off-by: Veerabhadrarao Badiganti Signed-off-by: Sarthak Garg --- drivers/mmc/host/sdhci-msm.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index de180ed8c33c..9701cdee9ed8 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -1224,9 +1224,21 @@ static int sdhci_msm_cm_dll_sdc4_calibration(struct sdhci_host *host) goto out; } - config = readl_relaxed(host->ioaddr + msm_offset->core_vendor_spec3); - config |= CORE_PWRSAVE_DLL; - writel_relaxed(config, host->ioaddr + msm_offset->core_vendor_spec3); + /* + * Set CORE_PWRSAVE_DLL bit in CORE_VENDOR_SPEC3. + * When MCLK is gated OFF, it is not gated for less than 0.5us + * and MCLK must be switched on for at-least 1us before DATA + * starts coming. Controllers with 14lpp and later tech DLL cannot + * guarantee above requirement. So PWRSAVE_DLL should not be + * turned on for host controllers using this DLL. + */ + if (!msm_host->use_14lpp_dll_reset) { + config = readl_relaxed(host->ioaddr + + msm_offset->core_vendor_spec3); + config |= CORE_PWRSAVE_DLL; + writel_relaxed(config, host->ioaddr + + msm_offset->core_vendor_spec3); + } /* * Drain writebuffer to ensure above DLL calibration From 20f3b4c8bcfdbf1598c9a66fa6deeab7acf90ea8 Mon Sep 17 00:00:00 2001 From: Veerabhadrarao Badiganti Date: Fri, 10 Jul 2020 20:00:35 +0530 Subject: [PATCH 7/7] mmc: sdhci-msm: Update DDR_CONFIG reg with HSR value if supplied Update DDR_CONFIG register with HSR value if HSR value is supplied in the device tree. Change-Id: Ia811eaf48b4ef6af39dc882ac498bf88436e2f4b Signed-off-by: Veerabhadrarao Badiganti Signed-off-by: Sarthak Garg --- drivers/mmc/host/sdhci-msm.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 9701cdee9ed8..c972e64e90d9 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -1198,7 +1198,13 @@ static int sdhci_msm_cm_dll_sdc4_calibration(struct sdhci_host *host) ddr_cfg_offset = msm_offset->core_ddr_config; else ddr_cfg_offset = msm_offset->core_ddr_config_old; - writel_relaxed(DDR_CONFIG_POR_VAL, host->ioaddr + ddr_cfg_offset); + + if (msm_host->dll_hsr->ddr_config) + config = msm_host->dll_hsr->ddr_config; + else + config = DDR_CONFIG_POR_VAL; + + writel_relaxed(config, host->ioaddr + ddr_cfg_offset); if (mmc->ios.enhanced_strobe) { config = readl_relaxed(host->ioaddr +