From 8fdb4867f354a5e6c52729a1813b2e0f193dfe80 Mon Sep 17 00:00:00 2001 From: Pradeep P V K Date: Fri, 8 Nov 2019 19:08:39 +0530 Subject: [PATCH 1/3] mmc: sdhci-msm: Add support for bus bandwidth voting Vote for the MSM bus bandwidth required by SDHC driver based on the clock frequency and bus width of the card. Otherwise,the system clocks may run at the minimum clock speed and thus affecting the performance. This change is based on Georgi Djakov [RFC] (https://lkml.org/lkml/2018/10/11/499). Change-Id: I5bae7af1ff8536cc596ca056438105991ca91839 Signed-off-by: Sahitya Tummala Signed-off-by: Subhash Jadavani Signed-off-by: Veerabhadrarao Badiganti Signed-off-by: Pradeep P V K Signed-off-by: Asutosh Das Signed-off-by: Bao D. Nguyen --- drivers/mmc/host/sdhci-msm.c | 370 ++++++++++++++++++++++++++++++++++- 1 file changed, 365 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 3d0bb5e2e09b..641a330628d5 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2,7 +2,7 @@ /* * drivers/mmc/host/sdhci-msm.c - Qualcomm SDHCI Platform driver * - * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. + * Copyright (c) 2013-2014,2020. The Linux Foundation. All rights reserved. */ #include @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -229,6 +230,30 @@ struct sdhci_msm_variant_info { const struct sdhci_msm_offset *offset; }; +struct msm_bus_vectors { + u64 ab; + u64 ib; +}; + +struct msm_bus_path { + unsigned int num_paths; + struct msm_bus_vectors *vec; +}; + +struct sdhci_msm_bus_vote_data { + const char *name; + unsigned int num_usecase; + struct msm_bus_path *usecase; + + unsigned int *bw_vecs; + unsigned int bw_vecs_size; + + struct icc_path *sdhc_ddr; + struct icc_path *cpu_sdhc; + + u32 curr_vote; +}; + struct sdhci_msm_host { struct platform_device *pdev; void __iomem *core_mem; /* MSM SDCC mapped address */ @@ -255,8 +280,13 @@ struct sdhci_msm_host { bool use_cdr; u32 transfer_mode; bool updated_ddr_cfg; + bool skip_bus_bw_voting; + struct sdhci_msm_bus_vote_data *bus_vote_data; + struct delayed_work bus_vote_work; }; +static void sdhci_msm_bus_voting(struct sdhci_host *host, bool enable); + static const struct sdhci_msm_offset *sdhci_priv_msm_offset(struct sdhci_host *host) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); @@ -1564,6 +1594,8 @@ 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); __sdhci_msm_set_clock(host, clock); } @@ -1685,6 +1717,315 @@ static void sdhci_msm_set_regulator_caps(struct sdhci_msm_host *msm_host) pr_debug("%s: supported caps: 0x%08x\n", mmc_hostname(mmc), caps); } +static int sdhci_msm_dt_get_array(struct device *dev, const char *prop_name, + u32 **bw_vecs, 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) { + 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; + } + *bw_vecs = arr; +out: + if (ret) + *len = 0; + return ret; +} + +/* Returns required bandwidth in Bytes per Sec */ +static unsigned long sdhci_get_bw_required(struct sdhci_host *host, + struct mmc_ios *ios) +{ + unsigned long bw; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host); + + bw = msm_host->clk_rate; + + 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) +{ + struct sdhci_msm_bus_vote_data *bvd = host->bus_vote_data; + + const unsigned int *table = bvd->bw_vecs; + unsigned int size = bvd->bw_vecs_size; + int i; + + for (i = 0; i < size; i++) { + if (bw <= table[i]) + return i; + } + + return i - 1; +} + +/* + * Caller of this function should 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) +{ + struct sdhci_host *host = platform_get_drvdata(msm_host->pdev); + struct sdhci_msm_bus_vote_data *bvd = msm_host->bus_vote_data; + struct msm_bus_path *usecase = bvd->usecase; + struct msm_bus_vectors *vec = usecase[vote].vec; + int ddr_rc, cpu_rc; + + if (vote == bvd->curr_vote) + return 0; + + pr_debug("%s: vote:%d sdhc_ddr ab:%llu ib:%llu cpu_sdhc ab:%llu ib:%llu\n", + mmc_hostname(host->mmc), vote, vec[0].ab, + vec[0].ib, vec[1].ab, vec[1].ib); + ddr_rc = icc_set_bw(bvd->sdhc_ddr, vec[0].ab, vec[0].ib); + cpu_rc = icc_set_bw(bvd->cpu_sdhc, vec[1].ab, vec[1].ib); + if (ddr_rc || cpu_rc) { + pr_err("%s: icc_set() failed\n", + mmc_hostname(host->mmc)); + goto out; + } + bvd->curr_vote = vote; +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, + unsigned int bw) +{ + int vote; + 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->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); +} + +#define MSM_MMC_BUS_VOTING_DELAY 200 /* 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) + +{ + struct platform_device *pdev = to_platform_device(dev); + struct device_node *of_node = dev->of_node; + struct sdhci_msm_bus_vote_data *bvd = NULL; + struct msm_bus_path *usecase = NULL; + int ret = 0, i = 0, j, num_paths, len; + const u32 *vec_arr = NULL; + + if (!pdev) { + dev_err(dev, "Null platform device!\n"); + return NULL; + } + + bvd = devm_kzalloc(dev, sizeof(*bvd), GFP_KERNEL); + if (!bvd) + return bvd; + + ret = sdhci_msm_dt_get_array(dev, "qcom,bus-bw-vectors-bps", + &bvd->bw_vecs, &bvd->bw_vecs_size, 0); + if (ret) { + if (ret == -EINVAL) { + dev_dbg(dev, "No dt property of bus bw. voting defined!\n"); + dev_dbg(dev, "Skipping Bus BW voting now!!\n"); + host->skip_bus_bw_voting = true; + } + goto out; + } + + ret = of_property_read_string(of_node, "qcom,msm-bus,name", + &bvd->name); + if (ret) { + dev_err(dev, "Bus name missing err:(%d)\n", ret); + goto out; + } + + ret = of_property_read_u32(of_node, "qcom,msm-bus,num-cases", + &bvd->num_usecase); + if (ret) { + dev_err(dev, "num-usecases not found err:(%d)\n", ret); + goto out; + } + + usecase = devm_kzalloc(dev, (sizeof(struct msm_bus_path) * + bvd->num_usecase), GFP_KERNEL); + if (!usecase) + goto out; + + ret = of_property_read_u32(of_node, "qcom,msm-bus,num-paths", + &num_paths); + if (ret) { + dev_err(dev, "num_paths not found err:(%d)\n", ret); + goto out; + } + + vec_arr = of_get_property(of_node, "qcom,msm-bus,vectors-KBps", &len); + if (!vec_arr) { + dev_err(dev, "Vector array not found\n"); + goto out; + } + + for (i = 0; i < bvd->num_usecase; i++) { + usecase[i].num_paths = num_paths; + usecase[i].vec = devm_kcalloc(dev, num_paths, + sizeof(struct msm_bus_vectors), + GFP_KERNEL); + if (!usecase[i].vec) + goto out; + for (j = 0; j < num_paths; j++) { + int idx = ((i * num_paths) + j) * 2; + + usecase[i].vec[j].ab = (u64) + be32_to_cpu(vec_arr[idx]); + usecase[i].vec[j].ib = (u64) + be32_to_cpu(vec_arr[idx + 1]); + } + } + + bvd->usecase = usecase; + return bvd; +out: + bvd = NULL; + return bvd; +} + +static int sdhci_msm_bus_register(struct sdhci_msm_host *host, + struct platform_device *pdev) +{ + struct sdhci_msm_bus_vote_data *bsd; + struct device *dev = &pdev->dev; + int ret = 0; + + bsd = sdhci_msm_get_bus_vote_data(dev, host); + if (!bsd) { + dev_err(&pdev->dev, "Failed to get bus_scale data\n"); + return -EINVAL; + } + host->bus_vote_data = bsd; + + bsd->sdhc_ddr = of_icc_get(&pdev->dev, "sdhc-ddr"); + if (IS_ERR_OR_NULL(bsd->sdhc_ddr)) { + dev_err(&pdev->dev, "(%ld): failed getting %s path\n", + PTR_ERR(bsd->sdhc_ddr), "sdhc-ddr"); + ret = PTR_ERR(bsd->sdhc_ddr); + bsd->sdhc_ddr = NULL; + return ret; + } + + bsd->cpu_sdhc = of_icc_get(&pdev->dev, "cpu-sdhc"); + if (IS_ERR_OR_NULL(bsd->cpu_sdhc)) { + dev_err(&pdev->dev, "(%ld): failed getting %s path\n", + PTR_ERR(bsd->cpu_sdhc), "cpu-sdhc"); + ret = PTR_ERR(bsd->cpu_sdhc); + bsd->cpu_sdhc = NULL; + return ret; + } + + INIT_DELAYED_WORK(&host->bus_vote_work, sdhci_msm_bus_work); + + return ret; +} + +static void sdhci_msm_bus_unregister(struct device *dev, + struct sdhci_msm_host *host) +{ + struct sdhci_msm_bus_vote_data *bsd = host->bus_vote_data; + + icc_put(bsd->sdhc_ddr); + icc_put(bsd->cpu_sdhc); +} + +static void sdhci_msm_bus_voting(struct sdhci_host *host, bool enable) +{ + struct mmc_ios *ios = &host->mmc->ios; + unsigned int bw; + + if (enable) { + bw = sdhci_get_bw_required(host, ios); + sdhci_msm_bus_cancel_work_and_set_vote(host, bw); + } else + sdhci_msm_bus_queue_work(host); +} + static const struct sdhci_msm_variant_ops mci_var_ops = { .msm_readl_relaxed = sdhci_msm_mci_variant_readl_relaxed, .msm_writel_relaxed = sdhci_msm_mci_variant_writel_relaxed, @@ -1846,6 +2187,15 @@ static int sdhci_msm_probe(struct platform_device *pdev) dev_warn(&pdev->dev, "TCXO clk not present (%d)\n", ret); } + 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); + goto clk_disable; + } + + if (!msm_host->skip_bus_bw_voting) + sdhci_msm_bus_voting(host, true); + if (!msm_host->mci_removed) { core_memres = platform_get_resource(pdev, IORESOURCE_MEM, 1); msm_host->core_mem = devm_ioremap_resource(&pdev->dev, @@ -1853,7 +2203,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) if (IS_ERR(msm_host->core_mem)) { ret = PTR_ERR(msm_host->core_mem); - goto clk_disable; + goto bus_unregister; } } @@ -1928,7 +2278,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->pwr_irq = platform_get_irq_byname(pdev, "pwr_irq"); if (msm_host->pwr_irq < 0) { ret = msm_host->pwr_irq; - goto clk_disable; + goto bus_unregister; } sdhci_msm_init_pwr_irq_wait(msm_host); @@ -1941,7 +2291,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) dev_name(&pdev->dev), host); if (ret) { dev_err(&pdev->dev, "Request IRQ failed (%d)\n", ret); - goto clk_disable; + goto bus_unregister; } pm_runtime_get_noresume(&pdev->dev); @@ -1966,6 +2316,11 @@ pm_runtime_disable: pm_runtime_disable(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); pm_runtime_put_noidle(&pdev->dev); +bus_unregister: + if (!msm_host->skip_bus_bw_voting) { + sdhci_msm_bus_cancel_work_and_set_vote(host, 0); + sdhci_msm_bus_unregister(&pdev->dev, msm_host); + } clk_disable: clk_bulk_disable_unprepare(ARRAY_SIZE(msm_host->bulk_clks), msm_host->bulk_clks); @@ -1995,6 +2350,10 @@ static int sdhci_msm_remove(struct platform_device *pdev) msm_host->bulk_clks); 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_unregister(&pdev->dev, msm_host); + } sdhci_pltfm_free(pdev); return 0; } @@ -2007,7 +2366,7 @@ static __maybe_unused int sdhci_msm_runtime_suspend(struct device *dev) clk_bulk_disable_unprepare(ARRAY_SIZE(msm_host->bulk_clks), msm_host->bulk_clks); - + sdhci_msm_bus_voting(host, false); return 0; } @@ -2029,6 +2388,7 @@ static __maybe_unused int sdhci_msm_runtime_resume(struct device *dev) if (msm_host->restore_dll_config && msm_host->clk_rate) return sdhci_msm_restore_sdr_dll_config(host); + sdhci_msm_bus_voting(host, true); return 0; } From f49b39526f797f679d729f8dfdd70474d89a2848 Mon Sep 17 00:00:00 2001 From: "Bao D. Nguyen" Date: Thu, 30 Jan 2020 20:50:28 -0800 Subject: [PATCH 2/3] mmc: sdhci-msm: Add SD card DLL setting for SRD104 Mode Port the SDCC's DLL setting logic from Qualcomm's Kona target to Lahaina using the DLL setting recommended by the Hardware Settings Reference guide which is defined in the dt file. This is a snapshot of the sdhci-msm driver changes taken as of msm-4.19 commit <74f401c241ba> ("mmc: sdhci-msm: Port base platform driver from 4.14 to 4.19"). Change-Id: Iaf85a498f9c9d051300ad84654497cf4f0762ba7 Signed-off-by: Veerabhadrarao Badiganti Signed-off-by: Bao D. Nguyen --- drivers/mmc/host/sdhci-msm.c | 122 +++++++++++++++++++++++++++++++---- 1 file changed, 111 insertions(+), 11 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 641a330628d5..510ae3ae4afb 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -100,7 +100,12 @@ #define CORE_PWRSAVE_DLL BIT(3) -#define DDR_CONFIG_POR_VAL 0x80040873 +#define DDR_CONFIG_POR_VAL 0x80040873 +#define DLL_USR_CTL_POR_VAL 0x10800 +#define ENABLE_DLL_LOCK_STATUS BIT(26) +#define FINE_TUNE_MODE_EN BIT(27) +#define BIAS_OK_SIGNAL BIT(29) +#define DLL_CONFIG_3_POR_VAL 0x10 #define INVALID_TUNING_PHASE -1 @@ -152,6 +157,7 @@ struct sdhci_msm_offset { u32 core_dll_config_3; u32 core_ddr_config_old; /* Applicable to sdcc minor ver < 0x49 */ u32 core_ddr_config; + u32 core_dll_usr_ctl; /* Present on SDCC5.1 onwards */ }; static const struct sdhci_msm_offset sdhci_msm_v5_offset = { @@ -181,6 +187,7 @@ static const struct sdhci_msm_offset sdhci_msm_v5_offset = { .core_dll_config_2 = 0x254, .core_dll_config_3 = 0x258, .core_ddr_config = 0x25c, + .core_dll_usr_ctl = 0x388, }; static const struct sdhci_msm_offset sdhci_msm_mci_offset = { @@ -209,6 +216,7 @@ static const struct sdhci_msm_offset sdhci_msm_mci_offset = { .core_ddr_200_cfg = 0x184, .core_vendor_spec3 = 0x1b0, .core_dll_config_2 = 0x1b4, + .core_dll_config_3 = 0x1b8, .core_ddr_config_old = 0x1b8, .core_ddr_config = 0x1bc, }; @@ -254,6 +262,19 @@ struct sdhci_msm_bus_vote_data { u32 curr_vote; }; +/* + * DLL registers which needs be programmed with HSR settings. + * Add any new register only at the end and don't change the + * sequence. + */ +struct sdhci_msm_dll_hsr { + u32 dll_config; + u32 dll_config_2; + u32 dll_config_3; + u32 dll_usr_ctl; + u32 ddr_config; +}; + struct sdhci_msm_host { struct platform_device *pdev; void __iomem *core_mem; /* MSM SDCC mapped address */ @@ -283,10 +304,15 @@ struct sdhci_msm_host { bool skip_bus_bw_voting; struct sdhci_msm_bus_vote_data *bus_vote_data; struct delayed_work bus_vote_work; + bool use_7nm_dll; + struct sdhci_msm_dll_hsr *dll_hsr; }; 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, + u32 **bw_vecs, int *len, u32 size); + static const struct sdhci_msm_offset *sdhci_priv_msm_offset(struct sdhci_host *host) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); @@ -651,21 +677,31 @@ static int msm_init_cm_dll(struct sdhci_host *host) config |= CORE_DLL_PDN; writel_relaxed(config, host->ioaddr + msm_offset->core_dll_config); - msm_cm_dll_set_freq(host); if (msm_host->use_14lpp_dll_reset && !IS_ERR_OR_NULL(msm_host->xo_clk)) { u32 mclk_freq = 0; + switch (host->clock) { + case 208000000: + case 202000000: + case 201500000: + case 200000000: + mclk_freq = 42; + break; + case 192000000: + mclk_freq = 40; + break; + default: + pr_err("%s: %s: Error. Unsupported clk freq\n", + mmc_hostname(mmc), __func__); + } + config = readl_relaxed(host->ioaddr + msm_offset->core_dll_config_2); config &= CORE_FLL_CYCLE_CNT; if (config) - mclk_freq = DIV_ROUND_CLOSEST_ULL((host->clock * 8), - xo_clk); - else - mclk_freq = DIV_ROUND_CLOSEST_ULL((host->clock * 4), - xo_clk); + mclk_freq *= 2; config = readl_relaxed(host->ioaddr + msm_offset->core_dll_config_2); @@ -691,7 +727,6 @@ static int msm_init_cm_dll(struct sdhci_host *host) msm_offset->core_dll_config); if (msm_host->use_14lpp_dll_reset) { - msm_cm_dll_set_freq(host); config = readl_relaxed(host->ioaddr + msm_offset->core_dll_config_2); config &= ~CORE_DLL_CLOCK_DISABLE; @@ -699,6 +734,37 @@ static int msm_init_cm_dll(struct sdhci_host *host) msm_offset->core_dll_config_2); } + /* Configure Tassadar DLL (Only applicable for 7FF projects) */ + if (msm_host->use_7nm_dll) { + if (msm_host->dll_hsr) { + writel_relaxed(msm_host->dll_hsr->dll_usr_ctl, + host->ioaddr + + msm_offset->core_dll_usr_ctl); + writel_relaxed(msm_host->dll_hsr->dll_config_3, + host->ioaddr + + msm_offset->core_dll_config_3); + } else { + writel_relaxed(DLL_USR_CTL_POR_VAL | FINE_TUNE_MODE_EN | + ENABLE_DLL_LOCK_STATUS | BIAS_OK_SIGNAL, + host->ioaddr + + msm_offset->core_dll_usr_ctl); + + writel_relaxed(DLL_CONFIG_3_POR_VAL, host->ioaddr + + msm_offset->core_dll_config_3); + } + } + + /* + * Update the lower two bytes of DLL_CONFIG only with HSR values. + * Since these are the static settings. + */ + if (msm_host->dll_hsr) { + writel_relaxed((readl_relaxed(host->ioaddr + + msm_offset->core_dll_config) | + (msm_host->dll_hsr->dll_config & 0xffff)), + host->ioaddr + msm_offset->core_dll_config); + } + config = readl_relaxed(host->ioaddr + msm_offset->core_dll_config); config |= CORE_DLL_EN; @@ -1021,8 +1087,8 @@ static int sdhci_msm_hs400_dll_calibration(struct sdhci_host *host) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host); - struct mmc_host *mmc = host->mmc; int ret; + struct mmc_host *mmc = host->mmc; u32 config; const struct sdhci_msm_offset *msm_offset = msm_host->offset; @@ -1038,9 +1104,9 @@ static int sdhci_msm_hs400_dll_calibration(struct sdhci_host *host) goto out; if (!mmc->ios.enhanced_strobe) { - /* Set the selected phase in delay line hw block */ + /* set the selected phase in delay line hw block */ ret = msm_config_cm_dll_phase(host, - msm_host->saved_tuning_phase); + msm_host->saved_tuning_phase); if (ret) goto out; config = readl_relaxed(host->ioaddr + @@ -1308,6 +1374,32 @@ static void sdhci_msm_set_uhs_signaling(struct sdhci_host *host, sdhci_msm_hs400(host, &mmc->ios); } +static int sdhci_msm_dt_parse_hsr_info(struct device *dev, + struct sdhci_msm_host *msm_host) + +{ + u32 *dll_hsr_table = NULL; + int dll_hsr_table_len, dll_hsr_reg_count; + int ret = 0; + + if (sdhci_msm_dt_get_array(dev, "qcom,dll-hsr-list", + &dll_hsr_table, &dll_hsr_table_len, 0)) + goto skip_hsr; + + dll_hsr_reg_count = sizeof(struct sdhci_msm_dll_hsr) / sizeof(u32); + if (dll_hsr_table_len != dll_hsr_reg_count) { + dev_err(dev, "Number of HSR entries are not matching\n"); + ret = -EINVAL; + } else { + msm_host->dll_hsr = (struct sdhci_msm_dll_hsr *)dll_hsr_table; + } + +skip_hsr: + if (!msm_host->dll_hsr) + dev_info(dev, "Failed to get dll hsr settings from dt\n"); + return ret; +} + static inline void sdhci_msm_init_pwr_irq_wait(struct sdhci_msm_host *msm_host) { init_waitqueue_head(&msm_host->pwr_irq_wait); @@ -2127,6 +2219,8 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->saved_tuning_phase = INVALID_TUNING_PHASE; + sdhci_msm_dt_parse_hsr_info(&pdev->dev, msm_host); + /* Setup SDCC bus voter clock. */ msm_host->bus_clk = devm_clk_get(&pdev->dev, "bus"); if (!IS_ERR(msm_host->bus_clk)) { @@ -2259,6 +2353,12 @@ static int sdhci_msm_probe(struct platform_device *pdev) if (core_major == 1 && core_minor >= 0x49) msm_host->updated_ddr_cfg = true; + /* 7FF projects with 7nm DLL */ + if ((core_major == 1) && ((core_minor == 0x6e) || + (core_minor == 0x71) || + (core_minor == 0x72))) + msm_host->use_7nm_dll = true; + /* * Power on reset state may trigger power irq if previous status of * PWRCTL was either BUS_ON or IO_HIGH_V. So before enabling pwr irq From cdf5fc99f4aea2486d0f3eaefc2f19d15159130c Mon Sep 17 00:00:00 2001 From: "Bao D. Nguyen" Date: Wed, 5 Feb 2020 13:49:05 -0800 Subject: [PATCH 3/3] mmc: sdhci-msm: Support Retention of SDCC Regs in CX Collapse During the CX Power Collapse event, the SDCC registers contents are not retained. Add the SW support to save and restore the SDCC's registers content during CX collapse event so that the SD card can resume to normal operation after CX collapse resumes. Change-Id: I8b452eaea2591c455147c475eb2811e5ebd12ddc Signed-off-by: Bao D. Nguyen --- drivers/mmc/host/sdhci-msm.c | 301 +++++++++++++++++++++++++++++++---- 1 file changed, 269 insertions(+), 32 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 510ae3ae4afb..84b7ad22e272 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -275,6 +275,33 @@ struct sdhci_msm_dll_hsr { u32 ddr_config; }; +struct sdhci_msm_regs_restore { + bool is_supported; + bool is_valid; + u32 vendor_pwrctl_mask; + u32 vendor_pwrctl_ctl; + u32 vendor_caps_0; + u32 vendor_func; + u32 vendor_func2; + u32 vendor_func3; + u32 hc_2c_2e; + u32 hc_28_2a; + u32 hc_34_36; + u32 hc_38_3a; + u32 hc_3c_3e; + u32 hc_caps_1; + u32 testbus_config; + u32 dll_config; + u32 dll_config2; + u32 dll_config3; + u32 dll_usr_ctl; +}; + +enum dll_init_context { + DLL_INIT_NORMAL = 0, + DLL_INIT_FROM_CX_COLLAPSE_EXIT, +}; + struct sdhci_msm_host { struct platform_device *pdev; void __iomem *core_mem; /* MSM SDCC mapped address */ @@ -306,6 +333,7 @@ struct sdhci_msm_host { struct delayed_work bus_vote_work; bool use_7nm_dll; struct sdhci_msm_dll_hsr *dll_hsr; + struct sdhci_msm_regs_restore regs_restore; }; static void sdhci_msm_bus_voting(struct sdhci_host *host, bool enable); @@ -627,7 +655,8 @@ static inline void msm_cm_dll_set_freq(struct sdhci_host *host) } /* Initialize the DLL (Programmable Delay Line) */ -static int msm_init_cm_dll(struct sdhci_host *host) +static int msm_init_cm_dll(struct sdhci_host *host, + enum dll_init_context init_context) { struct mmc_host *mmc = host->mmc; struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); @@ -682,34 +711,44 @@ static int msm_init_cm_dll(struct sdhci_host *host) !IS_ERR_OR_NULL(msm_host->xo_clk)) { u32 mclk_freq = 0; - switch (host->clock) { - case 208000000: - case 202000000: - case 201500000: - case 200000000: - mclk_freq = 42; - break; - case 192000000: - mclk_freq = 40; - break; - default: - pr_err("%s: %s: Error. Unsupported clk freq\n", - mmc_hostname(mmc), __func__); + /* + * Only configure the mclk_freq in normal DLL init + * context. If the DLL init is coming from + * CX Collapse Exit context, the host->clock may be zero. + * The DLL_CONFIG_2 register has already been restored to + * proper value prior to getting here. + */ + if (init_context == DLL_INIT_NORMAL) { + switch (host->clock) { + case 208000000: + case 202000000: + case 201500000: + case 200000000: + mclk_freq = 42; + break; + case 192000000: + mclk_freq = 40; + break; + default: + pr_err("%s: %s: Error. Unsupported clk freq\n", + mmc_hostname(mmc), __func__); + + } + + config = readl_relaxed(host->ioaddr + + msm_offset->core_dll_config_2); + config &= CORE_FLL_CYCLE_CNT; + if (config) + mclk_freq *= 2; + + config = readl_relaxed(host->ioaddr + + msm_offset->core_dll_config_2); + config &= ~(0xFF << 10); + config |= mclk_freq << 10; + + writel_relaxed(config, host->ioaddr + + msm_offset->core_dll_config_2); } - - config = readl_relaxed(host->ioaddr + - msm_offset->core_dll_config_2); - config &= CORE_FLL_CYCLE_CNT; - if (config) - mclk_freq *= 2; - - config = readl_relaxed(host->ioaddr + - msm_offset->core_dll_config_2); - config &= ~(0xFF << 10); - config |= mclk_freq << 10; - - writel_relaxed(config, host->ioaddr + - msm_offset->core_dll_config_2); /* wait for 5us before enabling DLL clock */ udelay(5); } @@ -931,7 +970,7 @@ static int sdhci_msm_cdclp533_calibration(struct sdhci_host *host) * Retuning in HS400 (DDR mode) will fail, just reset the * tuning block and restore the saved tuning phase. */ - ret = msm_init_cm_dll(host); + ret = msm_init_cm_dll(host, DLL_INIT_NORMAL); if (ret) goto out; @@ -1099,7 +1138,7 @@ static int sdhci_msm_hs400_dll_calibration(struct sdhci_host *host) * Retuning in HS400 (DDR mode) will fail, just reset the * tuning block and restore the saved tuning phase. */ - ret = msm_init_cm_dll(host); + ret = msm_init_cm_dll(host, DLL_INIT_NORMAL); if (ret) goto out; @@ -1158,7 +1197,7 @@ static int sdhci_msm_restore_sdr_dll_config(struct sdhci_host *host) return 0; /* Reset the tuning block */ - ret = msm_init_cm_dll(host); + ret = msm_init_cm_dll(host, DLL_INIT_NORMAL); if (ret) return ret; @@ -1221,7 +1260,7 @@ static int sdhci_msm_execute_tuning(struct mmc_host *mmc, u32 opcode) retry: /* First of all reset the tuning block */ - rc = msm_init_cm_dll(host); + rc = msm_init_cm_dll(host, DLL_INIT_NORMAL); if (rc) return rc; @@ -1499,6 +1538,52 @@ static void sdhci_msm_dump_pwr_ctrl_regs(struct sdhci_host *host) msm_host_readl(msm_host, host, msm_offset->core_pwrctl_ctl)); } +static int sdhci_msm_clear_pwrctl_status(struct sdhci_host *host, u32 value) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host); + const struct sdhci_msm_offset *msm_offset = msm_host->offset; + int ret = 0, retry = 10; + + /* + * There is a rare HW scenario where the first clear pulse could be + * lost when actual reset and clear/read of status register is + * happening at a time. Hence, retry for at least 10 times to make + * sure status register is cleared. Otherwise, this will result in + * a spurious power IRQ resulting in system instability. + */ + do { + if (retry == 0) { + pr_err("%s: Timedout clearing (0x%x) pwrctl status register\n", + mmc_hostname(host->mmc), value); + sdhci_msm_dump_pwr_ctrl_regs(host); + WARN_ON(1); + ret = -EBUSY; + break; + } + + /* + * Clear the PWRCTL_STATUS interrupt bits by writing to the + * corresponding bits in the PWRCTL_CLEAR register. + */ + msm_host_writel(msm_host, value, host, + msm_offset->core_pwrctl_clear); + /* + * SDHC has core_mem and hc_mem device memory and these memory + * addresses do not fall within 1KB region. Hence, any update to + * core_mem address space would require an mb() to ensure this + * gets completed before its next update to registers within + * hc_mem. + */ + mb(); + retry--; + udelay(10); + } while (value & msm_host_readl(msm_host, host, + msm_offset->core_pwrctl_status)); + + return ret; +} + static void sdhci_msm_handle_pwr_irq(struct sdhci_host *host, int irq) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); @@ -1691,6 +1776,150 @@ out: __sdhci_msm_set_clock(host, clock); } +static void sdhci_msm_registers_save(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host); + const struct sdhci_msm_offset *msm_offset = msm_host->offset; + + if (!msm_host->regs_restore.is_supported) + return; + + msm_host->regs_restore.vendor_func = readl_relaxed(host->ioaddr + + msm_offset->core_vendor_spec); + msm_host->regs_restore.vendor_pwrctl_mask = + readl_relaxed(host->ioaddr + + msm_offset->core_pwrctl_mask); + msm_host->regs_restore.vendor_func2 = + readl_relaxed(host->ioaddr + + msm_offset->core_vendor_spec_func2); + msm_host->regs_restore.vendor_func3 = + readl_relaxed(host->ioaddr + + msm_offset->core_vendor_spec3); + msm_host->regs_restore.hc_2c_2e = + sdhci_readl(host, SDHCI_CLOCK_CONTROL); + msm_host->regs_restore.hc_3c_3e = + sdhci_readl(host, SDHCI_AUTO_CMD_STATUS); + msm_host->regs_restore.vendor_pwrctl_ctl = + readl_relaxed(host->ioaddr + + msm_offset->core_pwrctl_ctl); + msm_host->regs_restore.hc_38_3a = + sdhci_readl(host, SDHCI_SIGNAL_ENABLE); + msm_host->regs_restore.hc_34_36 = + sdhci_readl(host, SDHCI_INT_ENABLE); + msm_host->regs_restore.hc_28_2a = + sdhci_readl(host, SDHCI_HOST_CONTROL); + msm_host->regs_restore.vendor_caps_0 = + readl_relaxed(host->ioaddr + + msm_offset->core_vendor_spec_capabilities0); + msm_host->regs_restore.hc_caps_1 = + sdhci_readl(host, SDHCI_CAPABILITIES_1); + msm_host->regs_restore.testbus_config = readl_relaxed(host->ioaddr + + msm_offset->core_testbus_config); + msm_host->regs_restore.dll_config = readl_relaxed(host->ioaddr + + msm_offset->core_dll_config); + msm_host->regs_restore.dll_config2 = readl_relaxed(host->ioaddr + + msm_offset->core_dll_config_2); + msm_host->regs_restore.dll_config = readl_relaxed(host->ioaddr + + msm_offset->core_dll_config); + msm_host->regs_restore.dll_config2 = readl_relaxed(host->ioaddr + + msm_offset->core_dll_config_2); + msm_host->regs_restore.dll_config3 = readl_relaxed(host->ioaddr + + msm_offset->core_dll_config_3); + msm_host->regs_restore.dll_usr_ctl = readl_relaxed(host->ioaddr + + msm_offset->core_dll_usr_ctl); + + msm_host->regs_restore.is_valid = true; + + pr_debug("%s: %s: registers saved. PWRCTL_MASK = 0x%x\n", + mmc_hostname(host->mmc), __func__, + readl_relaxed(host->ioaddr + + msm_offset->core_pwrctl_mask)); +} + +static void sdhci_msm_registers_restore(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host); + const struct sdhci_msm_offset *msm_offset = msm_host->offset; + u32 irq_status; + struct mmc_ios ios = host->mmc->ios; + + if (!msm_host->regs_restore.is_supported || + !msm_host->regs_restore.is_valid) + return; + + writel_relaxed(0, host->ioaddr + msm_offset->core_pwrctl_mask); + writel_relaxed(msm_host->regs_restore.vendor_func, host->ioaddr + + msm_offset->core_vendor_spec); + writel_relaxed(msm_host->regs_restore.vendor_func2, + host->ioaddr + + msm_offset->core_vendor_spec_func2); + writel_relaxed(msm_host->regs_restore.vendor_func3, + host->ioaddr + + msm_offset->core_vendor_spec3); + sdhci_writel(host, msm_host->regs_restore.hc_2c_2e, + SDHCI_CLOCK_CONTROL); + sdhci_writel(host, msm_host->regs_restore.hc_3c_3e, + SDHCI_AUTO_CMD_STATUS); + sdhci_writel(host, msm_host->regs_restore.hc_38_3a, + SDHCI_SIGNAL_ENABLE); + sdhci_writel(host, msm_host->regs_restore.hc_34_36, + SDHCI_INT_ENABLE); + sdhci_writel(host, msm_host->regs_restore.hc_28_2a, + SDHCI_HOST_CONTROL); + writel_relaxed(msm_host->regs_restore.vendor_caps_0, + host->ioaddr + + msm_offset->core_vendor_spec_capabilities0); + sdhci_writel(host, msm_host->regs_restore.hc_caps_1, + SDHCI_CAPABILITIES_1); + writel_relaxed(msm_host->regs_restore.testbus_config, host->ioaddr + + msm_offset->core_testbus_config); + msm_host->regs_restore.is_valid = false; + + /* + * Clear the PWRCTL_STATUS register. + * There is a rare HW scenario where the first clear pulse could be + * lost when actual reset and clear/read of status register is + * happening at a time. Hence, retry for at least 10 times to make + * sure status register is cleared. Otherwise, this will result in + * a spurious power IRQ resulting in system instability. + */ + irq_status = msm_host_readl(msm_host, host, + msm_offset->core_pwrctl_status); + + irq_status &= INT_MASK; + sdhci_msm_clear_pwrctl_status(host, irq_status); + + writel_relaxed(msm_host->regs_restore.vendor_pwrctl_ctl, + host->ioaddr + msm_offset->core_pwrctl_ctl); + writel_relaxed(msm_host->regs_restore.vendor_pwrctl_mask, + host->ioaddr + msm_offset->core_pwrctl_mask); + + if (((ios.timing == MMC_TIMING_MMC_HS400) || + (ios.timing == MMC_TIMING_MMC_HS200) || + (ios.timing == MMC_TIMING_UHS_SDR104)) + && (ios.clock > CORE_FREQ_100MHZ)) { + writel_relaxed(msm_host->regs_restore.dll_config2, + host->ioaddr + msm_offset->core_dll_config_2); + writel_relaxed(msm_host->regs_restore.dll_config3, + host->ioaddr + msm_offset->core_dll_config_3); + writel_relaxed(msm_host->regs_restore.dll_usr_ctl, + host->ioaddr + msm_offset->core_dll_usr_ctl); + writel_relaxed(msm_host->regs_restore.dll_config & + ~(CORE_DLL_RST | CORE_DLL_PDN), + host->ioaddr + msm_offset->core_dll_config); + + msm_init_cm_dll(host, DLL_INIT_FROM_CX_COLLAPSE_EXIT); + msm_config_cm_dll_phase(host, msm_host->saved_tuning_phase); + } + + pr_debug("%s: %s: registers restored. PWRCTL_MASK = 0x%x\n", + mmc_hostname(host->mmc), __func__, + readl_relaxed(host->ioaddr + + msm_offset->core_pwrctl_mask)); +} + /* * Platform specific register write functions. This is so that, if any * register write needs to be followed up by platform specific actions, @@ -2187,6 +2416,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) u8 core_major; const struct sdhci_msm_offset *msm_offset; const struct sdhci_msm_variant_info *var_info; + struct device *dev = &pdev->dev; host = sdhci_pltfm_init(pdev, &sdhci_msm_pdata, sizeof(*msm_host)); if (IS_ERR(host)) @@ -2221,6 +2451,10 @@ static int sdhci_msm_probe(struct platform_device *pdev) sdhci_msm_dt_parse_hsr_info(&pdev->dev, msm_host); + msm_host->regs_restore.is_supported = + of_property_read_bool(dev->of_node, + "qcom,restore-after-cx-collapse"); + /* Setup SDCC bus voter clock. */ msm_host->bus_clk = devm_clk_get(&pdev->dev, "bus"); if (!IS_ERR(msm_host->bus_clk)) { @@ -2464,6 +2698,7 @@ static __maybe_unused int sdhci_msm_runtime_suspend(struct device *dev) struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host); + 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); @@ -2481,6 +2716,8 @@ static __maybe_unused int sdhci_msm_runtime_resume(struct device *dev) 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.