diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 3d0bb5e2e09b..84b7ad22e272 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 @@ -99,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 @@ -151,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 = { @@ -180,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 = { @@ -208,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, }; @@ -229,6 +238,70 @@ 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; +}; + +/* + * 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_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 */ @@ -255,8 +328,19 @@ 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; + 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); + +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); @@ -571,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); @@ -621,29 +706,49 @@ 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; - 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); + /* + * 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 &= ~(0xFF << 10); - config |= mclk_freq << 10; + } - writel_relaxed(config, host->ioaddr + + 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); } @@ -661,7 +766,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; @@ -669,6 +773,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; @@ -835,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; @@ -991,8 +1126,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; @@ -1003,14 +1138,14 @@ 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; 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 + @@ -1062,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; @@ -1125,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; @@ -1278,6 +1413,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); @@ -1377,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); @@ -1564,9 +1771,155 @@ 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); } +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, @@ -1685,6 +2038,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, @@ -1754,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)) @@ -1786,6 +2449,12 @@ 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); + + 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)) { @@ -1846,6 +2515,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 +2531,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; } } @@ -1909,6 +2587,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 @@ -1928,7 +2612,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 +2625,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 +2650,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 +2684,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; } @@ -2005,9 +2698,10 @@ 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); return 0; } @@ -2022,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. @@ -2029,6 +2725,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; }