[RFC PATCH] mmc: dw_mmc: rework the accurate timeout calculation

Jaehoon Chung jh80.chung at samsung.com
Sun Apr 29 23:41:09 PDT 2018


Dear Ulf,

On 04/20/2018 04:14 PM, Ulf Hansson wrote:
> On 29 March 2018 at 10:18, Shawn Lin <shawn.lin at rock-chips.com> wrote:
>> The intention of this patch is to rework the timeout calculation
>> for dw_mmc, which isn't ideal enough. It's harmless to add longest
>> timeout as normally the transfer is fine and the occasional failure
>> could break out by no matter the Hardware interrupt or software timer.
>>
>> However, it doesn't make sense in the tuning process, as it could
>> trigger more timeout than usual, which is very painful for booting
>> time. Currently we set TMOUT to 0xffffffff anyway. Assume the max
>> clock is 200MHz when doing tuning for HS200, the we have:
>>
>> MSEC_PER_SEC * 0xffffff / 200MHz = 83ms for hardware timemout interrupt
>> and 93ms for software timer.
>>
>> However, we should note that the cards should guarantee to complete a
>> sequence of 40 times CMD21 execulations within 150ms per the spec. But
>> we now have 83ms timeout for each CMD21, which only contains 128Bytes
>> data playload.
>>
>> Maybe the simplest way is to reduce the HW/SW timeout value for tuning
>> process but it's still worth trying to rework a more accurate timeout.
>>
>> Signed-off-by: Shawn Lin <shawn.lin at rock-chips.com>
> 
> Jaehoon, any comments on this one?

Sorry for late.

Actually, drto/crto timeout value are something wrong when clock is 400KHz.
It should be set to huge value. And it will be also overflowed.

I will test this patch on my target. Will share the result until tomorrow.

Best Regards,
Jaehoon Chung


> 
> Kind regards
> Uffe
> 
>>
>> ---
>>
>>  drivers/mmc/host/dw_mmc.c | 107 +++++++++++++++++++++++++++++++++-------------
>>  drivers/mmc/host/dw_mmc.h |   4 ++
>>  2 files changed, 82 insertions(+), 29 deletions(-)
>>
>> diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c
>> index 623f4d2..c85d703 100644
>> --- a/drivers/mmc/host/dw_mmc.c
>> +++ b/drivers/mmc/host/dw_mmc.c
>> @@ -378,23 +378,84 @@ static u32 dw_mci_prep_stop_abort(struct dw_mci *host, struct mmc_command *cmd)
>>         return cmdr;
>>  }
>>
>> -static inline void dw_mci_set_cto(struct dw_mci *host)
>> +static void dw_mci_calc_timeout(struct dw_mci *host)
>>  {
>> -       unsigned int cto_clks;
>> -       unsigned int cto_div;
>> -       unsigned int cto_ms;
>> -       unsigned long irqflags;
>> +       struct mmc_data *data = host->data;
>> +       struct mmc_host *mmc = host->slot->mmc;
>> +       u64 trsf_time_per_blksz;
>> +       u64 idle_time_per_trsf;
>> +       u64 val;
>> +       struct mmc_ios *ios = &mmc->ios;
>> +       unsigned char bus_width = ios->bus_width;
>> +       unsigned int freq, clk_div;
>> +       unsigned int cto_clks, drto_clks;
>> +
>> +       clk_div = (mci_readl(host, CLKDIV) & 0xff) * 2;
>> +       if (clk_div == 0)
>> +               clk_div = 1;
>> +
>> +       freq = host->bus_hz / clk_div;
>> +
>> +       /*
>> +        * The largest 136 bit response in 100KHz is nearly 1ms,
>> +        * and add other transfer parameter per spec, we should
>> +        * allow 2ms maybe. And should also add some spare time
>> +        * to tolerate anything unknown leading to unexpect long
>> +        * latency.
>> +        */
>> +       host->cmd_timeout_ns = 10 * NSEC_PER_MSEC;
>> +       host->data_timeout_ns = 10 * NSEC_PER_MSEC;
>> +
>> +       if (data) {
>> +               trsf_time_per_blksz = DIV_ROUND_UP_ULL((u64)data->blksz *
>> +                                       NSEC_PER_SEC * (8 / bus_width), freq);
>> +
>> +               /*
>> +                * Especially multiply by 2 to account for write for anything
>> +                * unknown in the cards, for each block.
>> +                */
>> +               if (data->flags & MMC_DATA_WRITE)
>> +                       trsf_time_per_blksz *= 2;
>> +
>> +               /* Calculate idle time from core basd on spec */
>> +               idle_time_per_trsf = DIV_ROUND_UP_ULL(data->timeout_ns,
>> +                                                     NSEC_PER_USEC);
>> +               if (data->timeout_clks) {
>> +                       /* Align up from cycle of MHz to Hz */
>> +                       val = 1000000ULL * data->timeout_clks;
>> +                       if (DIV_ROUND_UP_ULL(val, freq))
>> +                               idle_time_per_trsf++;
>> +                       idle_time_per_trsf += val;
>> +               }
>> +
>> +               /* Calculate timeout for the entire data */
>> +               host->data_timeout_ns +=
>> +                       data->blocks * (DIV_ROUND_UP_ULL(idle_time_per_trsf,
>> +                                       NSEC_PER_USEC) + trsf_time_per_blksz);
>>
>> -       cto_clks = mci_readl(host, TMOUT) & 0xff;
>> -       cto_div = (mci_readl(host, CLKDIV) & 0xff) * 2;
>> -       if (cto_div == 0)
>> -               cto_div = 1;
>> +               drto_clks = DIV_ROUND_UP_ULL(host->data_timeout_ns * freq,
>> +                                            NSEC_PER_SEC);
>> +               if (drto_clks > 0xffffff)
>> +                       drto_clks = 0xffffff;
>>
>> -       cto_ms = DIV_ROUND_UP_ULL((u64)MSEC_PER_SEC * cto_clks * cto_div,
>> -                                 host->bus_hz);
>> +               /* Set accurate hw data timeout */
>> +               mci_writel(host, TMOUT, drto_clks << 8);
>> +       }
>>
>> -       /* add a bit spare time */
>> -       cto_ms += 10;
>> +       host->cmd_timeout_ns += host->cmd->busy_timeout * NSEC_PER_MSEC;
>> +       cto_clks = DIV_ROUND_UP_ULL(host->cmd_timeout_ns * freq,
>> +                                   NSEC_PER_SEC);
>> +       if (cto_clks > 0xff)
>> +               cto_clks = 0xff;
>> +
>> +       /* Set accurate cmd timeout */
>> +       mci_writel(host, TMOUT, cto_clks |
>> +                               (mci_readl(host, TMOUT) & 0xffffff00));
>> +}
>> +
>> +static inline void dw_mci_set_cto(struct dw_mci *host)
>> +{
>> +       unsigned long irqflags;
>>
>>         /*
>>          * The durations we're working with are fairly short so we have to be
>> @@ -412,7 +473,7 @@ static inline void dw_mci_set_cto(struct dw_mci *host)
>>         spin_lock_irqsave(&host->irq_lock, irqflags);
>>         if (!test_bit(EVENT_CMD_COMPLETE, &host->pending_events))
>>                 mod_timer(&host->cto_timer,
>> -                       jiffies + msecs_to_jiffies(cto_ms) + 1);
>> +                       jiffies + nsecs_to_jiffies(host->cmd_timeout_ns) + 1);
>>         spin_unlock_irqrestore(&host->irq_lock, irqflags);
>>  }
>>
>> @@ -424,6 +485,8 @@ static void dw_mci_start_command(struct dw_mci *host,
>>                  "start command: ARGR=0x%08x CMDR=0x%08x\n",
>>                  cmd->arg, cmd_flags);
>>
>> +       dw_mci_calc_timeout(host);
>> +
>>         mci_writel(host, CMDARG, cmd->arg);
>>         wmb(); /* drain writebuffer */
>>         dw_mci_wait_while_busy(host, cmd_flags);
>> @@ -1923,26 +1986,12 @@ static int dw_mci_data_complete(struct dw_mci *host, struct mmc_data *data)
>>
>>  static void dw_mci_set_drto(struct dw_mci *host)
>>  {
>> -       unsigned int drto_clks;
>> -       unsigned int drto_div;
>> -       unsigned int drto_ms;
>>         unsigned long irqflags;
>>
>> -       drto_clks = mci_readl(host, TMOUT) >> 8;
>> -       drto_div = (mci_readl(host, CLKDIV) & 0xff) * 2;
>> -       if (drto_div == 0)
>> -               drto_div = 1;
>> -
>> -       drto_ms = DIV_ROUND_UP_ULL((u64)MSEC_PER_SEC * drto_clks * drto_div,
>> -                                  host->bus_hz);
>> -
>> -       /* add a bit spare time */
>> -       drto_ms += 10;
>> -
>>         spin_lock_irqsave(&host->irq_lock, irqflags);
>>         if (!test_bit(EVENT_DATA_COMPLETE, &host->pending_events))
>>                 mod_timer(&host->dto_timer,
>> -                         jiffies + msecs_to_jiffies(drto_ms));
>> +                         jiffies + nsecs_to_jiffies(host->data_timeout_ns));
>>         spin_unlock_irqrestore(&host->irq_lock, irqflags);
>>  }
>>
>> diff --git a/drivers/mmc/host/dw_mmc.h b/drivers/mmc/host/dw_mmc.h
>> index 46e9f8e..4330128 100644
>> --- a/drivers/mmc/host/dw_mmc.h
>> +++ b/drivers/mmc/host/dw_mmc.h
>> @@ -126,7 +126,9 @@ struct dw_mci_dma_slave {
>>   * @sdio_id0: Number of slot0 in the SDIO interrupt registers.
>>   * @cmd11_timer: Timer for SD3.0 voltage switch over scheme.
>>   * @cto_timer: Timer for broken command transfer over scheme.
>> + * @cmd_timeout_ns: Timeout value accociated with cto_timer
>>   * @dto_timer: Timer for broken data transfer over scheme.
>> + * @data_timeout_ns: Timeout value accociated with dto_timer
>>   *
>>   * Locking
>>   * =======
>> @@ -233,7 +235,9 @@ struct dw_mci {
>>
>>         struct timer_list       cmd11_timer;
>>         struct timer_list       cto_timer;
>> +       u64                     cmd_timeout_ns;
>>         struct timer_list       dto_timer;
>> +       u64                     data_timeout_ns;
>>  };
>>
>>  /* DMA ops for Internal/External DMAC interface */
>> --
>> 1.9.1
>>
>>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
> the body of a message to majordomo at vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 
> 
> 




More information about the Linux-rockchip mailing list