Re: [PATCH] mmc: core: Add host specific tuning support for SD HS mode

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Wed, 9 Aug 2023 at 07:30, Wenchao Chen <wenchao.chen@xxxxxxxxxx> wrote:
>
> Added .prepare_hs_tuning and .execute_hs_tuning host
> callbacks to support host-specific tuning for SD high
> speed mode.

This certainly needs to be clarified more. Especially why it's needed
for the sdhci-sprd variant.

>
> Signed-off-by: Wenchao Chen <wenchao.chen@xxxxxxxxxx>
> ---
>  drivers/mmc/core/sd.c         |  12 ++++
>  drivers/mmc/host/sdhci-sprd.c | 124 ++++++++++++++++++++++++++++++++++
>  include/linux/mmc/host.h      |   6 ++
>  3 files changed, 142 insertions(+)
>
> diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c
> index 246ce027ae0a..ac2da8f2fbce 100644
> --- a/drivers/mmc/core/sd.c
> +++ b/drivers/mmc/core/sd.c
> @@ -1518,6 +1518,12 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
>                  */
>                 mmc_set_clock(host, mmc_sd_get_max_clock(card));
>
> +               if (host->ops->prepare_hs_tuning) {
> +                       err = host->ops->prepare_hs_tuning(host, card);
> +                       if (err)
> +                               goto free_card;
> +               }

Adding a new callback for this is a bit questionable, I think.

In the ->set_ios() callback, we could instead check MMC_TIMING_SD_HS
and when the clock is updated, then also run a tuning sequence, right?

> +
>                 /*
>                  * Switch to wider bus (if supported).
>                  */
> @@ -1529,6 +1535,12 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
>
>                         mmc_set_bus_width(host, MMC_BUS_WIDTH_4);
>                 }
> +
> +               if (host->ops->execute_hs_tuning) {
> +                       err = host->ops->execute_hs_tuning(host, card);
> +                       if (err)
> +                               goto free_card;
> +               }

Similar to the above comment, in the ->set_ios() callback we could
instead check MMC_TIMING_SD_HS when moving to MMC_BUS_WIDTH_4, then
run a tuning sequence, right?

>         }
>  cont:
>         if (!oldcard) {
> diff --git a/drivers/mmc/host/sdhci-sprd.c b/drivers/mmc/host/sdhci-sprd.c
> index 7f4ee2e12735..5f365dcbb9c7 100644
> --- a/drivers/mmc/host/sdhci-sprd.c
> +++ b/drivers/mmc/host/sdhci-sprd.c
> @@ -9,6 +9,7 @@
>  #include <linux/dma-mapping.h>
>  #include <linux/highmem.h>
>  #include <linux/iopoll.h>
> +#include <linux/mmc/mmc.h>
>  #include <linux/module.h>
>  #include <linux/of.h>
>  #include <linux/of_device.h>
> @@ -22,6 +23,9 @@
>  #include "sdhci-pltfm.h"
>  #include "mmc_hsq.h"
>
> +#include "../core/mmc_ops.h"
> +#include "../core/sd_ops.h"

No, this isn't how we include header files. Instead move the functions
that you may need to include/linux/mmc/host.h.

Also, please split up core changes from host driver changes.

> +
>  /* SDHCI_ARGUMENT2 register high 16bit */
>  #define SDHCI_SPRD_ARG2_STUFF          GENMASK(31, 16)
>
> @@ -73,6 +77,11 @@
>  #define SDHCI_SPRD_CLK_DEF_RATE                26000000
>  #define SDHCI_SPRD_PHY_DLL_CLK         52000000
>
> +#define SDHCI_SPRD_MAX_PHASE           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;
> @@ -86,6 +95,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;
> @@ -533,6 +547,111 @@ 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[64];

We use kmalloc-ed data for data transfers.

> +
> +       return mmc_sd_switch(card, 0, 0, 0, status);
> +}
> +
> +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;
> +       int err = 0;
> +       int i;
> +       bool value;
> +       int final_phase;
> +       u32 dll_cfg, dll_dly;
> +       int range_end = SDHCI_SPRD_MAX_PHASE;
> +       int len = 0;
> +       int count = 0;
> +
> +       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_PHASE; 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 = !mmc_send_tuning_cmd(card);
> +               else
> +                       value = !mmc_send_tuning_data(card);
> +
> +               if (value) {
> +                       dev_dbg(mmc_dev(host->mmc), "tuning ok: %d\n", i);
> +                       count++;
> +               } else {
> +                       dev_dbg(mmc_dev(host->mmc), "tuning fail: %d\n", i);
> +                       if (len < count) {
> +                               len = count;
> +                               range_end = i - 1;
> +                               count = 0;
> +                       }
> +               }
> +       }
> +
> +       if (!count) {
> +               final_phase = 0;
> +               dev_err(mmc_dev(host->mmc), "all tuning phase fail!\n");
> +               err = -EIO;
> +               goto out;
> +       }
> +
> +       if (count > len) {
> +               len = count;
> +               range_end = i - 1;
> +       }
> +
> +       final_phase = range_end - (len - 1) / 2;

The whole len, count, range_end, final_phase things look rather messy.
Please have a look and try to clean up that part a bit, I am sure it
can be done, somehow.

> +
> +       if (type == SDHCI_SPRD_TUNING_SD_HS_CMD) {
> +               p[mmc->ios.timing] &= ~SDHCI_SPRD_CMD_DLY_MASK;
> +               p[mmc->ios.timing] |= ((final_phase << 8) & SDHCI_SPRD_CMD_DLY_MASK);
> +       } else {
> +               p[mmc->ios.timing] &= ~(SDHCI_SPRD_POSRD_DLY_MASK);
> +               p[mmc->ios.timing] |= ((final_phase << 16) & SDHCI_SPRD_POSRD_DLY_MASK);
> +       }
> +
> +       dev_info(mmc_dev(host->mmc), "the best step %d, phase 0x%02x, delay value 0x%08x\n",
> +                       final_phase, final_phase, p[mmc->ios.timing]);

Does this really deserve to be a dev_info? Looks like a dev_dbg to me, no?

> +
> +out:
> +       sdhci_writel(host, p[mmc->ios.timing], SDHCI_SPRD_REG_32_DLL_DLY);
> +
> +       return err;
> +}
> +
> +static int sdhci_sprd_prepare_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_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)
>  {
> @@ -577,6 +696,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_hs_tuning =
> +               sdhci_sprd_prepare_hs_cmd_tuning;
> +       host->mmc_host_ops.execute_hs_tuning =
> +               sdhci_sprd_execute_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
> diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
> index 461d1543893b..13cf894b9e3c 100644
> --- a/include/linux/mmc/host.h
> +++ b/include/linux/mmc/host.h
> @@ -184,6 +184,12 @@ struct mmc_host_ops {
>         /* Execute HS400 tuning depending host driver */
>         int     (*execute_hs400_tuning)(struct mmc_host *host, struct mmc_card *card);
>
> +       /* Prepare HS tuning depending host driver */
> +       int     (*prepare_hs_tuning)(struct mmc_host *host, struct mmc_card *card);
> +
> +       /* Execute HS tuning depending host driver */
> +       int     (*execute_hs_tuning)(struct mmc_host *host, struct mmc_card *card);
> +
>         /* Prepare switch to DDR during the HS400 init sequence */
>         int     (*hs400_prepare_ddr)(struct mmc_host *host);
>

Kind regards
Uffe



[Index of Archives]     [Linux Memonry Technology]     [Linux USB Devel]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux