iMX7D SDIO tuning debugging

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

iMX7D SDIO tuning debugging

2,430 Views
thomaskofoedhan
Contributor III

We have trouble getting our SDIO interface to an external Wifi Module to function stably. Everything points to a timing issue, as we can influence the error rate by cooling/heating the processor. The interface has a tuning mechanism to add delay to the receiving clock but we are unsure if this works correctly.

There is one register (uSDHCx_CLK_TUNE_CTRL_STATUS) that could help us but that register is a read only which will always just return the power-reset value, which makes it completely useless.

Is there another way to get some information about the tuning results (not only success/fail)?

How much of the tuning process is handled by software and what is handled automatically be hardware?

Labels (1)
Tags (2)
0 Kudos
4 Replies

1,577 Views
igorpadykov
NXP Employee
NXP Employee

Hi Thomas

one can look at patch showing tuning usage with CMD19

u-boot-acadia1.0-beta/0372-ENGR00139221-USDHC-Add-SDXC-UHS-I-support.patch at master · linksprite/u-... 

unfortunately I am not aware of additional tuning descriptions, except sd specification or debugging procedures.

Best regards
igor
-----------------------------------------------------------------------------------------------------------------------
Note: If this post answers your question, please click the Correct Answer button. Thank you!
-----------------------------------------------------------------------------------------------------------------------

0 Kudos

1,577 Views
thomaskofoedhan
Contributor III

The link looks like something for iMX6 processors. The iMX7 has some kind of hardware doing the tuning with only a few setup values available for the user. This is why I need another way to evaluate the result of a tuning.

The implementation of the uSDHCx_CLK_TUNE_CTRL_STATUS read only which will always just return the power-reset value, does not help.

Also there is no mention of a maximum SDIO bus length. Does that mean there is no limit?

0 Kudos

1,577 Views
igorpadykov
NXP Employee
NXP Employee

uSDHC module is the same for i.MX7 and i.MX6, could you clarify what do you

mean by "maximum SDIO bus length"

0 Kudos

1,577 Views
thomaskofoedhan
Contributor III

Can I place my Wifi module 11 or more centimeters from the processor?

There is a difference between iMX6Q and the other iMX6 and iMX7 processors in that the tuning flags in Linux are set to Manual_Tuning for the iMX6Q and Standard_Tuning for the other processors. In the probe function I can see that only if the Manual_Tuning flag is set then the tuning function below is called. Standard tuning uses another function which looks like some of the tuning is handled by hardware, and is apparently not called during probe. 

As the iMX7D processor has the standard tuning flag set, the function does not provide any means to see and evaluate the result of a tuning.

**************************************************************************************************************************************

Linux Flags:

static struct esdhc_soc_data usdhc_imx6q_data = {

                             .flags = ESDHC_FLAG_USDHC | ESDHC_FLAG_MAN_TUNING,

};

 

static struct esdhc_soc_data usdhc_imx6sl_data = {

                             .flags = ESDHC_FLAG_USDHC | ESDHC_FLAG_STD_TUNING

                                                                                       | ESDHC_FLAG_HAVE_CAP1 | ESDHC_FLAG_ERR004536

                                                                                       | ESDHC_FLAG_HS200 | ESDHC_FLAG_BUSFREQ,

};

 

static struct esdhc_soc_data usdhc_imx6sx_data = {

                             .flags = ESDHC_FLAG_USDHC | ESDHC_FLAG_STD_TUNING

                                                                                       | ESDHC_FLAG_HAVE_CAP1 | ESDHC_FLAG_HS200

                                                                                       | ESDHC_FLAG_STATE_LOST_IN_LPMODE

                                                                                       | ESDHC_FLAG_BUSFREQ,

};

 

static struct esdhc_soc_data usdhc_imx6ull_data = {

                             .flags = ESDHC_FLAG_USDHC | ESDHC_FLAG_STD_TUNING

                                                                                       | ESDHC_FLAG_HAVE_CAP1 | ESDHC_FLAG_HS200

                                                                                       | ESDHC_FLAG_STATE_LOST_IN_LPMODE

                                                                                       | ESDHC_FLAG_ERR010450

                                                                                       | ESDHC_FLAG_BUSFREQ,

};

 

static struct esdhc_soc_data usdhc_imx7d_data = {

                             .flags = ESDHC_FLAG_USDHC | ESDHC_FLAG_STD_TUNING

                                                                                       | ESDHC_FLAG_HAVE_CAP1 | ESDHC_FLAG_HS200

                                                                                       | ESDHC_FLAG_HS400 | ESDHC_FLAG_STATE_LOST_IN_LPMODE

                                                                                       | ESDHC_FLAG_BUSFREQ,

};

 

static struct esdhc_soc_data usdhc_imx7ulp_data = {

                             .flags = ESDHC_FLAG_USDHC | ESDHC_FLAG_STD_TUNING

                                                                                       | ESDHC_FLAG_HAVE_CAP1 | ESDHC_FLAG_HS200

                                                                                       | ESDHC_FLAG_STATE_LOST_IN_LPMODE

                                                                                       | ESDHC_FLAG_PMQOS,

};

**************************************************************************************************************************************

'The manual tuning uses a tuning function which is easy to read, understand and debug (see below).

**************************************************************************************************************************************

Manual tuning:

static int esdhc_executing_tuning(struct sdhci_host *host, u32 opcode)

{

                             int min, max, avg, ret;

 

                             /* find the mininum delay first which can pass tuning */

                             min = ESDHC_TUNE_CTRL_MIN;

                             while (min < ESDHC_TUNE_CTRL_MAX) {

                                                          esdhc_prepare_tuning(host, min);

                                                          if (!mmc_send_tuning(host->mmc, opcode, NULL))

                                                                                       break;

                                                          min += ESDHC_TUNE_CTRL_STEP;

                             }

 

                             /* find the maxinum delay which can not pass tuning */

                             max = min + ESDHC_TUNE_CTRL_STEP;

                             while (max < ESDHC_TUNE_CTRL_MAX) {

                                                          esdhc_prepare_tuning(host, max);

                                                          if (mmc_send_tuning(host->mmc, opcode, NULL)) {

                                                                                       max -= ESDHC_TUNE_CTRL_STEP;

                                                                                       break;

                                                          }

                                                          max += ESDHC_TUNE_CTRL_STEP;

                             }

 

                             /* use average delay to get the best timing */

                             avg = (min + max) / 2;

                             esdhc_prepare_tuning(host, avg);

                             ret = mmc_send_tuning(host->mmc, opcode, NULL);

                             esdhc_post_tuning(host);

 

                             dev_dbg(mmc_dev(host->mmc), "tunning %s at 0x%x ret %d\n",

                                                          ret ? "failed" : "passed", avg, ret);

 

                             return ret;

}

**************************************************************************************************************************************

The standard tuning uses the function shown below which does not show any results other than passed/failed.

**************************************************************************************************************************************

Standard tuning:

static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)

{

                             struct sdhci_host *host = mmc_priv(mmc);

                             u16 ctrl;

                             int tuning_loop_counter = MAX_TUNING_LOOP;

                             int err = 0;

                             unsigned long flags;

                             unsigned int tuning_count = 0;

                             bool hs400_tuning;

 

                             spin_lock_irqsave(&host->lock, flags);

 

                             hs400_tuning = host->flags & SDHCI_HS400_TUNING;

                             host->flags &= ~SDHCI_HS400_TUNING;

 

                             if (host->tuning_mode == SDHCI_TUNING_MODE_1)

                                                          tuning_count = host->tuning_count;

 

                             /*

                             * The Host Controller needs tuning in case of SDR104 and DDR50

                             * mode, and for SDR50 mode when Use Tuning for SDR50 is set in

                             * the Capabilities register.

                             * If the Host Controller supports the HS200 mode then the

                             * tuning function has to be executed.

                             */

                             switch (host->timing) {

                             /* HS400 tuning is done in HS200 mode */

                             case MMC_TIMING_MMC_HS400:

                                                          err = -EINVAL;

                                                          goto out_unlock;

 

                             case MMC_TIMING_MMC_HS200:

                                                          /*

                                                          * Periodic re-tuning for HS400 is not expected to be needed, so

                                                          * disable it here.

                                                          */

                                                          if (hs400_tuning)

                                                                                       tuning_count = 0;

                                                          break;

 

                             case MMC_TIMING_UHS_SDR104:

                                                          break;

 

                             case MMC_TIMING_UHS_SDR50:

                                                          if (host->flags & SDHCI_SDR50_NEEDS_TUNING)

                                                                                       break;

                                                          /* FALLTHROUGH */

 

                             case MMC_TIMING_UHS_DDR50:

                                                          if (host->flags & SDHCI_DDR50_NEEDS_TUNING)

                                                                                       break;

                                                          /* FALLTHROUGH */

 

                             default:

                                                          goto out_unlock;

                             }

 

                             if (host->ops->platform_execute_tuning) {

                                                          spin_unlock_irqrestore(&host->lock, flags);

                                                          err = host->ops->platform_execute_tuning(host, opcode);

                                                          return err;

                             }

 

                             ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);

                             ctrl |= SDHCI_CTRL_EXEC_TUNING;

                             if (host->quirks2 & SDHCI_QUIRK2_TUNING_WORK_AROUND)

                                                          ctrl |= SDHCI_CTRL_TUNED_CLK;

                             sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);

 

                             /*

                             * As per the Host Controller spec v3.00, tuning command

                             * generates Buffer Read Ready interrupt, so enable that.

                             *

                             * Note: The spec clearly says that when tuning sequence

                             * is being performed, the controller does not generate

                             * interrupts other than Buffer Read Ready interrupt. But

                             * to make sure we don't hit a controller bug, we _only_

                             * enable Buffer Read Ready interrupt here.

                             */

                             sdhci_writel(host, SDHCI_INT_DATA_AVAIL, SDHCI_INT_ENABLE);

                             sdhci_writel(host, SDHCI_INT_DATA_AVAIL, SDHCI_SIGNAL_ENABLE);

 

                             /*

                             * Issue CMD19 repeatedly till Execute Tuning is set to 0 or the number

                             * of loops reaches 40 times.

                             */

                             do {

                                                          struct mmc_command cmd = {};

                                                          struct mmc_request mrq = {};

 

                                                          cmd.opcode = opcode;

                                                          cmd.arg = 0;

                                                          cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;

                                                          cmd.retries = 0;

                                                          cmd.data = NULL;

                                                          cmd.mrq = &mrq;

                                                          cmd.error = 0;

 

                                                          if (tuning_loop_counter-- == 0)

                                                                                       break;

 

                                                          mrq.cmd = &cmd;

 

                                                          /*

                                                          * In response to CMD19, the card sends 64 bytes of tuning

                                                          * block to the Host Controller. So we set the block size

                                                          * to 64 here.

                                                          */

                                                          if (cmd.opcode == MMC_SEND_TUNING_BLOCK_HS200) {

                                                                                       if (mmc->ios.bus_width == MMC_BUS_WIDTH_8)

                                                                                                                    sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, 128),

                                                                                                                                                      SDHCI_BLOCK_SIZE);

                                                                                       else if (mmc->ios.bus_width == MMC_BUS_WIDTH_4)

                                                                                                                    sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, 64),

                                                                                                                                                      SDHCI_BLOCK_SIZE);

                                                          } else {

                                                                                       sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, 64),

                                                                                                                         SDHCI_BLOCK_SIZE);

                                                          }

 

                                                          /*

                                                          * The tuning block is sent by the card to the host controller.

                                                          * So we set the TRNS_READ bit in the Transfer Mode register.

                                                          * This also takes care of setting DMA Enable and Multi Block

                                                          * Select in the same register to 0.

                                                          */

                                                          sdhci_writew(host, SDHCI_TRNS_READ, SDHCI_TRANSFER_MODE);

 

                                                          sdhci_send_command(host, &cmd);

 

                                                          host->cmd = NULL;

                                                          sdhci_del_timer(host, &mrq);

 

                                                          spin_unlock_irqrestore(&host->lock, flags);

                                                          /* Wait for Buffer Read Ready interrupt */

                                                          wait_event_timeout(host->buf_ready_int,

                                                                                                                                                 (host->tuning_done == 1),

                                                                                                                                                 msecs_to_jiffies(50));

                                                          spin_lock_irqsave(&host->lock, flags);

 

                                                          if (!host->tuning_done) {

                                                                                       pr_info(DRIVER_NAME ": Timeout waiting for Buffer Read Ready interrupt during tuning procedure, falling back to fixed sampling clock\n");

                                                                                       ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);

                                                                                       ctrl &= ~SDHCI_CTRL_TUNED_CLK;

                                                                                       ctrl &= ~SDHCI_CTRL_EXEC_TUNING;

                                                                                       sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);

 

                                                                                       sdhci_do_reset(host, SDHCI_RESET_CMD);

                                                                                       sdhci_do_reset(host, SDHCI_RESET_DATA);

 

                                                                                       err = -EIO;

 

                                                                                       if (cmd.opcode != MMC_SEND_TUNING_BLOCK_HS200)

                                                                                                                    goto out;

 

                                                                                       sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);

                                                                                       sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);

 

                                                                                       spin_unlock_irqrestore(&host->lock, flags);

 

                                                                                       memset(&cmd, 0, sizeof(cmd));

                                                                                       cmd.opcode = MMC_STOP_TRANSMISSION;

                                                                                       cmd.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;

                                                                                       cmd.busy_timeout = 50;

                                                                                       mmc_wait_for_cmd(mmc, &cmd, 0);

 

                                                                                       spin_lock_irqsave(&host->lock, flags);

 

                                                                                       goto out;

                                                          }

 

                                                          host->tuning_done = 0;

 

                                                          ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);

 

                                                          /* Add 1ms delay for SD and eMMC */

                                                          mdelay(1);

                             } while (ctrl & SDHCI_CTRL_EXEC_TUNING);

 

                             /*

                             * The Host Driver has exhausted the maximum number of loops allowed,

                             * so use fixed sampling frequency.

                             */

                             if (tuning_loop_counter < 0) {

                                                          ctrl &= ~SDHCI_CTRL_TUNED_CLK;

                                                          sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);

                             }

                             if (!(ctrl & SDHCI_CTRL_TUNED_CLK)) {

                                                          pr_info(DRIVER_NAME ": Tuning procedure failed, falling back to fixed sampling clock\n");

                                                          err = -EIO;

                             }

 

out:

                             if (tuning_count) {

                                                          /*

                                                          * In case tuning fails, host controllers which support

                                                          * re-tuning can try tuning again at a later time, when the

                                                          * re-tuning timer expires.  So for these controllers, we

                                                          * return 0. Since there might be other controllers who do not

                                                          * have this capability, we return error for them.

                                                          */

                                                          err = 0;

                             }

 

                             host->mmc->retune_period = err ? 0 : tuning_count;

 

                             sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);

                             sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);

out_unlock:

                             spin_unlock_irqrestore(&host->lock, flags);

                             return err;

}