Merge "mmc: sdhci-msm: Support Retention of SDCC Regs in CX Collapse"

This commit is contained in:
qctecmdr 2020-03-01 18:13:00 -08:00 • committed by Gerrit - the friendly Code Review server
commit 36bcbe78a2

View file

@ -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 <linux/module.h>
@ -11,6 +11,7 @@
#include <linux/mmc/mmc.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/interconnect.h>
#include <linux/iopoll.h>
#include <linux/regulator/consumer.h>
@ -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;
}