Skip to content

Commit

Permalink
mmc: sdhci-sprd: Add SD HS mode online tuning
Browse files Browse the repository at this point in the history
First of all, Unisoc's IC provides cmd delay and read delay to ensure
that the host can get the correct data. However, according to SD Spec,
there is no need to do tuning in high speed mode, but with the
development of chip processes, it is more and more difficult to find
a suitable delay to cover all the chips. Therefore, we need SD high
speed mode online tuning.

In addition, we added mmc_sd_switch() and mmc_send_status() to the
header file to allow it to be usable by the driver.

Signed-off-by: Wenchao Chen <wenchao.chen@unisoc.com>
Link: https://lore.kernel.org/r/20230825091743.15613-3-wenchao.chen@unisoc.com
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
  • Loading branch information
Wenchao Chen authored and Ulf Hansson committed Aug 25, 2023
1 parent bac8068 commit d83d251
Showing 1 changed file with 149 additions and 0 deletions.
149 changes: 149 additions & 0 deletions drivers/mmc/host/sdhci-sprd.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#include <linux/dma-mapping.h>
#include <linux/highmem.h>
#include <linux/iopoll.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
Expand Down Expand Up @@ -72,6 +74,11 @@
#define SDHCI_SPRD_CLK_DEF_RATE 26000000
#define SDHCI_SPRD_PHY_DLL_CLK 52000000

#define SDHCI_SPRD_MAX_RANGE 0xff
#define SDHCI_SPRD_CMD_DLY_MASK GENMASK(15, 8)
#define SDHCI_SPRD_POSRD_DLY_MASK GENMASK(23, 16)
#define SDHCI_SPRD_CPST_EN GENMASK(27, 24)

struct sdhci_sprd_host {
u32 version;
struct clk *clk_sdio;
Expand All @@ -85,6 +92,11 @@ struct sdhci_sprd_host {
u32 phy_delay[MMC_TIMING_MMC_HS400 + 2];
};

enum sdhci_sprd_tuning_type {
SDHCI_SPRD_TUNING_SD_HS_CMD,
SDHCI_SPRD_TUNING_SD_HS_DATA,
};

struct sdhci_sprd_phy_cfg {
const char *property;
u8 timing;
Expand Down Expand Up @@ -532,6 +544,138 @@ static void sdhci_sprd_hs400_enhanced_strobe(struct mmc_host *mmc,
SDHCI_SPRD_REG_32_DLL_DLY);
}

static int mmc_send_tuning_cmd(struct mmc_card *card)
{
return mmc_send_status(card, NULL);
}

static int mmc_send_tuning_data(struct mmc_card *card)
{
u8 *status;
int ret;

status = kmalloc(64, GFP_KERNEL);
if (!status)
return -ENOMEM;

ret = mmc_sd_switch(card, 0, 0, 0, status);

kfree(status);

return ret;
}

static int sdhci_sprd_get_best_clk_sample(struct mmc_host *mmc, u8 *value)
{
int range_end = SDHCI_SPRD_MAX_RANGE;
int range_length = 0;
int middle_range = 0;
int count = 0;
int i;

for (i = 0; i <= SDHCI_SPRD_MAX_RANGE; i++) {
if (value[i]) {
pr_debug("%s: tuning ok: %d\n", mmc_hostname(mmc), i);
count++;
} else {
pr_debug("%s: tuning fail: %d\n", mmc_hostname(mmc), i);
if (range_length < count) {
range_length = count;
range_end = i - 1;
count = 0;
}
}
}

if (!count)
return -EIO;

if (count > range_length) {
range_length = count;
range_end = i - 1;
}

middle_range = range_end - (range_length - 1) / 2;

return middle_range;
}

static int sdhci_sprd_tuning(struct mmc_host *mmc, struct mmc_card *card,
enum sdhci_sprd_tuning_type type)
{
struct sdhci_host *host = mmc_priv(mmc);
struct sdhci_sprd_host *sprd_host = TO_SPRD_HOST(host);
u32 *p = sprd_host->phy_delay;
u32 dll_cfg, dll_dly;
int best_clk_sample;
int err = 0;
u8 *value;
int i;

value = kmalloc(SDHCI_SPRD_MAX_RANGE + 1, GFP_KERNEL);
if (!value)
return -ENOMEM;

sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA);

dll_cfg = sdhci_readl(host, SDHCI_SPRD_REG_32_DLL_CFG);
dll_cfg &= ~SDHCI_SPRD_CPST_EN;
sdhci_writel(host, dll_cfg, SDHCI_SPRD_REG_32_DLL_CFG);

dll_dly = p[mmc->ios.timing];

for (i = 0; i <= SDHCI_SPRD_MAX_RANGE; i++) {
if (type == SDHCI_SPRD_TUNING_SD_HS_CMD) {
dll_dly &= ~SDHCI_SPRD_CMD_DLY_MASK;
dll_dly |= ((i << 8) & SDHCI_SPRD_CMD_DLY_MASK);
} else {
dll_dly &= ~SDHCI_SPRD_POSRD_DLY_MASK;
dll_dly |= ((i << 16) & SDHCI_SPRD_POSRD_DLY_MASK);
}

sdhci_writel(host, dll_dly, SDHCI_SPRD_REG_32_DLL_DLY);

if (type == SDHCI_SPRD_TUNING_SD_HS_CMD)
value[i] = !mmc_send_tuning_cmd(card);
else
value[i] = !mmc_send_tuning_data(card);
}

best_clk_sample = sdhci_sprd_get_best_clk_sample(mmc, value);
if (best_clk_sample < 0) {
dev_err(mmc_dev(host->mmc), "all tuning phase fail!\n");
goto out;
}

if (type == SDHCI_SPRD_TUNING_SD_HS_CMD) {
p[mmc->ios.timing] &= ~SDHCI_SPRD_CMD_DLY_MASK;
p[mmc->ios.timing] |= ((best_clk_sample << 8) & SDHCI_SPRD_CMD_DLY_MASK);
} else {
p[mmc->ios.timing] &= ~(SDHCI_SPRD_POSRD_DLY_MASK);
p[mmc->ios.timing] |= ((best_clk_sample << 16) & SDHCI_SPRD_POSRD_DLY_MASK);
}

pr_debug("%s: the best clk sample %d, delay value 0x%08x\n",
mmc_hostname(host->mmc), best_clk_sample, p[mmc->ios.timing]);

out:
sdhci_writel(host, p[mmc->ios.timing], SDHCI_SPRD_REG_32_DLL_DLY);

kfree(value);

return err;
}

static int sdhci_sprd_prepare_sd_hs_cmd_tuning(struct mmc_host *mmc, struct mmc_card *card)
{
return sdhci_sprd_tuning(mmc, card, SDHCI_SPRD_TUNING_SD_HS_CMD);
}

static int sdhci_sprd_execute_sd_hs_data_tuning(struct mmc_host *mmc, struct mmc_card *card)
{
return sdhci_sprd_tuning(mmc, card, SDHCI_SPRD_TUNING_SD_HS_DATA);
}

static void sdhci_sprd_phy_param_parse(struct sdhci_sprd_host *sprd_host,
struct device_node *np)
{
Expand Down Expand Up @@ -576,6 +720,11 @@ static int sdhci_sprd_probe(struct platform_device *pdev)
host->mmc_host_ops.request = sdhci_sprd_request;
host->mmc_host_ops.hs400_enhanced_strobe =
sdhci_sprd_hs400_enhanced_strobe;
host->mmc_host_ops.prepare_sd_hs_tuning =
sdhci_sprd_prepare_sd_hs_cmd_tuning;
host->mmc_host_ops.execute_sd_hs_tuning =
sdhci_sprd_execute_sd_hs_data_tuning;

/*
* We can not use the standard ops to change and detect the voltage
* signal for Spreadtrum SD host controller, since our voltage regulator
Expand Down

0 comments on commit d83d251

Please sign in to comment.