Message ID | 20150403112638.GF29880@intel.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
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 -- To unsubscribe from this list: send the line "unsubscribe linux-mmc" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
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) { + 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) { + 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); + 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)) { #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 */
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(-)