Re: [V2 PATCH 5/5] mmc: sdhci: add SW CMDQ support for SDHCI host

Ulf Hansson
2015-05-13T13:03:28Z

On 3 April 2015 at 13:26, Chuanxiao Dong <chuanxiao.dong@intel.com> wrote:
> Add the CMDQ support to SDHCI host interface
>
> Signed-off-by: Chuanxiao Dong <chuanxiao.dong@intel.com>
> ---
> drivers/mmc/host/sdhci.c | 162 +++++++++++++++++++++++++++++++++++++++++-----
> drivers/mmc/host/sdhci.h | 2 +
> 2 files changed, 148 insertions(+), 16 deletions(-)
>
> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
> index aad89d2..1dde07b 100644
> --- a/drivers/mmc/host/sdhci.c
> +++ b/drivers/mmc/host/sdhci.c
> @@ -742,14 +742,14 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
> struct mmc_data *data = cmd->data;
> int ret;
>
> - WARN_ON(host->data);
> -
> if (data || (cmd->flags & MMC_RSP_BUSY))
> sdhci_set_timeout(host, cmd);
>
> if (!data)
> return;
>
> + WARN_ON(host->data);
> +
> /* Sanity checks */
> BUG_ON(data->blksz * data->blocks > 524288);
> BUG_ON(data->blksz > host->mmc->max_blk_size);
> @@ -927,7 +927,9 @@ static void sdhci_set_transfer_mode(struct sdhci_host *host,
> if (!(host->quirks2 & SDHCI_QUIRK2_SUPPORT_SINGLE))
> mode = SDHCI_TRNS_BLK_CNT_EN;
>
> - if (mmc_op_multi(cmd->opcode) || data->blocks > 1) {
> + if (mmc_op_cmdq_execute_task(cmd->opcode) && (data->blocks > 1))
> + mode |= SDHCI_TRNS_MULTI;
> + else if (mmc_op_multi(cmd->opcode) || (data->blocks > 1)) {
> mode = SDHCI_TRNS_BLK_CNT_EN | SDHCI_TRNS_MULTI;
> /*
> * If we are sending CMD23, CMD12 never gets sent
> @@ -1004,8 +1006,12 @@ static void sdhci_finish_data(struct sdhci_host *host)
> }
>
> sdhci_send_command(host, data->stop);
> - } else
> - tasklet_schedule(&host->finish_tasklet);
> + } else {
> + if (host->mmc->context_info.is_cmdq_busy)
> + tasklet_schedule(&host->finish_async_data_tasklet);
> + else
> + tasklet_schedule(&host->finish_tasklet);
> + }
> }
>
> void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
> @@ -1048,6 +1054,14 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
> timeout += 10 * HZ;
> mod_timer(&host->timer, timeout);
>
> + /*
> + * For the command which has async data like CMD46/47, the data
> + * is not completed at the same time with command. Thus needs a
> + * separate timeout timer to make sure driver won't wait for ever
> + */
> + if (mmc_op_cmdq_execute_task(cmd->opcode))
> + mod_timer(&host->async_data_timer, timeout);
> +
> host->cmd = cmd;
> host->busy_handle = 0;
>
> @@ -1084,6 +1098,12 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
> cmd->opcode == MMC_SEND_TUNING_BLOCK_HS200)
> flags |= SDHCI_CMD_DATA;
>
> + /* CMD46/47 doesn't wait for data */
> + if (mmc_op_cmdq_execute_task(cmd->opcode)) {
> + cmd->data = NULL;
> + host->mrq->data = NULL;
> + }
> +
> sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND);
> }
> EXPORT_SYMBOL_GPL(sdhci_send_command);
> @@ -1116,6 +1136,9 @@ static void sdhci_finish_command(struct sdhci_host *host)
> if (host->cmd == host->mrq->precmd) {
> host->cmd = NULL;
> sdhci_send_command(host, host->mrq->cmd);
> + } else if ((host->cmd == host->mrq->cmd) && host->mrq->cmd2) {

I notice this cmd2 here again.

Since it seems like host drivers will be using this, I definitely
think some more explanation of it could be useful in previous patches.

> + host->cmd = NULL;
> + sdhci_send_command(host, host->mrq->cmd2);
> } else {
>
> /* Processed actual command. */
> @@ -1400,7 +1423,7 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
> if ((host->flags & SDHCI_NEEDS_RETUNING) &&
> !(present_state & (SDHCI_DOING_WRITE | SDHCI_DOING_READ)) &&
> (present_state & SDHCI_DATA_0_LVL_MASK)) {
> - if (mmc->card) {
> + if (mmc->card && !mmc->context_info.is_cmdq_busy) {
> /* eMMC uses cmd21 but sd and sdio use cmd19 */
> tuning_opcode =
> mmc->card->type == MMC_TYPE_MMC ?
> @@ -1422,9 +1445,15 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
> }
> }
>
> - if (mrq->precmd && !(host->flags & SDHCI_AUTO_CMD23))
> - sdhci_send_command(host, mrq->precmd);
> - else
> + if (mrq->precmd) {
> + if (mrq->precmd->opcode == 23) {

23? Please use the existing define instead.

> + if (!(host->flags & SDHCI_AUTO_CMD23))
> + sdhci_send_command(host, mrq->precmd);
> + else
> + sdhci_send_command(host, mrq->cmd);
> + } else
> + sdhci_send_command(host, mrq->precmd);
> + } else
> sdhci_send_command(host, mrq->cmd);
> }
>
> @@ -2248,7 +2277,7 @@ static const struct mmc_host_ops sdhci_ops = {
> * *
> \*****************************************************************************/
>
> -static void sdhci_tasklet_finish(unsigned long param)
> +static void sdhci_tasklet_finish_async_data(unsigned long param)
> {
> struct sdhci_host *host;
> unsigned long flags;
> @@ -2262,14 +2291,69 @@ static void sdhci_tasklet_finish(unsigned long param)
> * If this tasklet gets rescheduled while running, it will
> * be run again afterwards but without any active request.
> */
> - if (!host->mrq) {
> + if (!host->mmc->areq || !host->mmc->areq->mrq->data) {
> spin_unlock_irqrestore(&host->lock, flags);
> return;
> }
>
> - del_timer(&host->timer);
> + del_timer(&host->async_data_timer);
> +
> + mrq = host->mmc->areq->mrq;
> +
> + /*
> + * The controller needs a reset of internal state machines
> + * upon error conditions.
> + */
> + if (!(host->flags & SDHCI_DEVICE_DEAD) &&
> + ((mrq->data && mrq->data->error) ||
> + (host->quirks & SDHCI_QUIRK_RESET_AFTER_REQUEST))) {
> +
> + /* Some controllers need this kick or reset won't work here */
> + if (host->quirks & SDHCI_QUIRK_CLOCK_BEFORE_RESET)
> + /* This is to force an update */
> + host->ops->set_clock(host, host->clock);
> +
> + sdhci_reset(host, SDHCI_RESET_DATA);
> + }
> +
> + host->data = NULL;
> +
> +#ifndef SDHCI_USE_LEDS_CLASS
> + sdhci_deactivate_led(host);
> +#endif
> + mmiowb();
> + spin_unlock_irqrestore(&host->lock, flags);
> +
> + mmc_request_done(host->mmc, mrq);
> +
> + sdhci_runtime_pm_put(host);
> +}
> +
> +static void sdhci_tasklet_finish(unsigned long param)
> +{
> + struct sdhci_host *host;
> + unsigned long flags;
> + struct mmc_request *mrq;
> + int opcode;
> +
> + host = (struct sdhci_host *)param;
> +
> + spin_lock_irqsave(&host->lock, flags);
> +
> + /*
> + * If this tasklet gets rescheduled while running, it will
> + * be run again afterwards but without any active request.
> + */
> + if (!host->mrq) {
> + spin_unlock_irqrestore(&host->lock, flags);
> + return;
> + }
>
> mrq = host->mrq;
> + BUG_ON(!mrq->cmd);

BUG_ON? No way to bail out and handle errors?

> + opcode = mrq->cmd->opcode;
> +
> + del_timer(&host->timer);
>
> /*
> * The controller needs a reset of internal state machines
> @@ -2291,21 +2375,29 @@ static void sdhci_tasklet_finish(unsigned long param)
> controllers do not like that. */
> sdhci_do_reset(host, SDHCI_RESET_CMD);
> sdhci_do_reset(host, SDHCI_RESET_DATA);
> + host->data = NULL;
> }
>
> host->mrq = NULL;
> host->cmd = NULL;
> - host->data = NULL;
>
> + /* CMD46/47 sill have pending data */
> + if (!mmc_op_cmdq_execute_task(opcode)) {

You might want to move the if statement within the "#ifndef
SDHCI_USE_LEDS_CLASS".

> #ifndef SDHCI_USE_LEDS_CLASS
> - sdhci_deactivate_led(host);
> + sdhci_deactivate_led(host);
> #endif
> + }
>
> mmiowb();
> spin_unlock_irqrestore(&host->lock, flags);
>
> mmc_request_done(host->mmc, mrq);
> - sdhci_runtime_pm_put(host);
> + /*
> + * host will be put in D0i3 when pending data is done
> + * for CMD46/47
> + */
> + if (!mmc_op_cmdq_execute_task(opcode))
> + sdhci_runtime_pm_put(host);
> }
>
> static void sdhci_timeout_timer(unsigned long data)
> @@ -2339,6 +2431,39 @@ static void sdhci_timeout_timer(unsigned long data)
> spin_unlock_irqrestore(&host->lock, flags);
> }
>
> +static void sdhci_async_data_timeout_timer(unsigned long data)
> +{
> + struct sdhci_host *host;
> + unsigned long flags;
> + struct mmc_request *mrq;
> +
> + host = (struct sdhci_host *)data;
> +
> + spin_lock_irqsave(&host->lock, flags);
> +
> + if (host->mmc->areq && host->mmc->areq->mrq) {
> + mrq = host->mmc->areq->mrq;
> + pr_err("%s: SW CMDQ Timeout waiting for hardware interrupt\n",
> + mmc_hostname(host->mmc));
> + pr_err("%s: CMDQ CMD %d, ARG 0x%x\n", mmc_hostname(host->mmc),
> + mrq->postcmd->opcode, mrq->postcmd->arg);
> + sdhci_dumpregs(host);
> +
> + /*
> + * if host has data then means IRQ handler is not
> + * called yet. if host has no data means IRQ handler
> + * is called and maybe the soft tasklet is stuck
> + */
> + if (host->data) {
> + host->data->error = -ETIMEDOUT;
> + sdhci_finish_data(host);
> + }
> + }
> +
> + mmiowb();
> + spin_unlock_irqrestore(&host->lock, flags);
> +}
> +
> static void sdhci_tuning_timer(unsigned long data)
> {
> struct sdhci_host *host;
> @@ -2548,7 +2673,8 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask)
> }
>
> if (intmask & SDHCI_INT_DATA_END) {
> - if (host->cmd) {
> + if (!host->mmc->context_info.is_cmdq_busy &&
> + host->cmd) {
> /*
> * Data managed to finish before the
> * command completed. Make sure we do
> @@ -3405,8 +3531,12 @@ int sdhci_add_host(struct sdhci_host *host)
> */
> tasklet_init(&host->finish_tasklet,
> sdhci_tasklet_finish, (unsigned long)host);
> + tasklet_init(&host->finish_async_data_tasklet,
> + sdhci_tasklet_finish_async_data, (unsigned long)host);
>
> setup_timer(&host->timer, sdhci_timeout_timer, (unsigned long)host);
> + setup_timer(&host->async_data_timer, sdhci_async_data_timeout_timer,
> + (unsigned long)host);
>
> init_waitqueue_head(&host->buf_ready_int);
>
> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
> index e639b7f..2686b97 100644
> --- a/drivers/mmc/host/sdhci.h
> +++ b/drivers/mmc/host/sdhci.h
> @@ -480,8 +480,10 @@ struct sdhci_host {
> unsigned int align_mask; /* ADMA alignment mask */
>
> struct tasklet_struct finish_tasklet; /* Tasklet structures */
> + struct tasklet_struct finish_async_data_tasklet;
>
> struct timer_list timer; /* Timer for timeouts */
> + struct timer_list async_data_timer; /* Timer for SW CMDQ timeouts */
>
> u32 caps; /* Alternative CAPABILITY_0 */
> u32 caps1; /* Alternative CAPABILITY_1 */
> --
> 1.7.10.4
>

Finally, shouldn't you enable MMC_CAP2_CAN_DO_CMDQ from somewhere?

Kind regards
Uffe