mmc: sdhci: add SW CMDQ support for SDHCI host
PATCH v2 5/5 mmc: sdhci: add SW CMDQ support for SDHCI host
Add the CMDQ support to SDHCI host interface
--- drivers/mmc/host/sdhci.c | 162 +++++++++++++++++++++++++++++++++++++++++----- drivers/mmc/host/sdhci.h | 2 + 2 files changed, 148 insertions(+), 16 deletions(-)
drivers/mmc/host/sdhci.c +146 -16
@@ -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);
drivers/mmc/host/sdhci.h +2 -0
@@ -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 */
Signature · 1 line
1.7.10.4
Quoted 84 lines · Chuanxiao Dong
Add the CMDQ support to SDHCI host interface
Signed-off-by Chuanxiao Dong <[email protected]>--- drivers/mmc/host/sdhci.c | 162 +++++++++++++++++++++++++++++++++++++++++----- drivers/mmc/host/sdhci.h | 2 + 2 files changed, 148 insertions(+), 16 deletions(-)drivers/mmc/host/sdhci.c +25
-5@@ -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);
drivers/mmc/host/sdhci.c static void sdhci_finish_command(struct sdhci_host *host) · line 1139
Quoted 4 lines
drivers/mmc/host/sdhci.c +1
-0@@ -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.
Quoted 15 lines
drivers/mmc/host/sdhci.c +3
-1+ 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 ?
drivers/mmc/host/sdhci.c static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) · line 1449
Quoted 8 lines
drivers/mmc/host/sdhci.c +2
-3@@ -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.
Quoted 75 lines
drivers/mmc/host/sdhci.c +54
-3+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); + +/*
drivers/mmc/host/sdhci.c static void sdhci_tasklet_finish(unsigned long param) · line 2353
Quoted 10 lines
drivers/mmc/host/sdhci.c +8
-0+* 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?
Quoted 9 lines
drivers/mmc/host/sdhci.c +3
-0+ 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 );
drivers/mmc/host/sdhci.c static void sdhci_tasklet_finish(unsigned long param) · line 2385
Quoted 10 lines
drivers/mmc/host/sdhci.c +3
-1sdhci_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".
Quoted 99 lines
drivers/mmc/host/sdhci.c +47
-3#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);drivers/mmc/host/sdhci.h +2
-1@@ -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 */ --
Quoted 2 lines
1.7.10.4
Finally, shouldn't you enable MMC_CAP2_CAN_DO_CMDQ from somewhere?
Kind regards
Uffe