mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-08 12:58:12 -04:00
Merge "mmc: sdhci-msm: Support Retention of SDCC Regs in CX Collapse"
This commit is contained in:
commit
36bcbe78a2
1 changed files with 727 additions and 30 deletions
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue