diff options
-rw-r--r-- | drivers/mmc/core/core.c | 95 | ||||
-rw-r--r-- | include/linux/mmc/core.h | 7 | ||||
-rw-r--r-- | include/linux/mmc/host.h | 5 |
3 files changed, 101 insertions, 6 deletions
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index d083d2e57abd..f0ed0afe033d 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -120,6 +120,24 @@ static inline void mmc_should_fail_request(struct mmc_host *host, #endif /* CONFIG_FAIL_MMC_REQUEST */ +static inline void mmc_complete_cmd(struct mmc_request *mrq) +{ + if (mrq->cap_cmd_during_tfr && !completion_done(&mrq->cmd_completion)) + complete_all(&mrq->cmd_completion); +} + +void mmc_command_done(struct mmc_host *host, struct mmc_request *mrq) +{ + if (!mrq->cap_cmd_during_tfr) + return; + + mmc_complete_cmd(mrq); + + pr_debug("%s: cmd done, tfr ongoing (CMD%u)\n", + mmc_hostname(host), mrq->cmd->opcode); +} +EXPORT_SYMBOL(mmc_command_done); + /** * mmc_request_done - finish processing an MMC request * @host: MMC host which completed request @@ -146,6 +164,11 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) cmd->retries = 0; } + if (host->ongoing_mrq == mrq) + host->ongoing_mrq = NULL; + + mmc_complete_cmd(mrq); + trace_mmc_request_done(host, mrq); if (err && cmd->retries && !mmc_card_removed(host->card)) { @@ -158,7 +181,8 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) } else { mmc_should_fail_request(host, mrq); - led_trigger_event(host->led, LED_OFF); + if (!host->ongoing_mrq) + led_trigger_event(host->led, LED_OFF); if (mrq->sbc) { pr_debug("%s: req done <CMD%u>: %d: %08x %08x %08x %08x\n", @@ -223,6 +247,15 @@ static void __mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) } } + if (mrq->cap_cmd_during_tfr) { + host->ongoing_mrq = mrq; + /* + * Retry path could come through here without having waiting on + * cmd_completion, so ensure it is reinitialised. + */ + reinit_completion(&mrq->cmd_completion); + } + trace_mmc_request_start(host, mrq); host->ops->request(host, mrq); @@ -389,6 +422,18 @@ static void mmc_wait_done(struct mmc_request *mrq) complete(&mrq->completion); } +static inline void mmc_wait_ongoing_tfr_cmd(struct mmc_host *host) +{ + struct mmc_request *ongoing_mrq = READ_ONCE(host->ongoing_mrq); + + /* + * If there is an ongoing transfer, wait for the command line to become + * available. + */ + if (ongoing_mrq && !completion_done(&ongoing_mrq->cmd_completion)) + wait_for_completion(&ongoing_mrq->cmd_completion); +} + /* *__mmc_start_data_req() - starts data request * @host: MMC host to start the request @@ -396,17 +441,24 @@ static void mmc_wait_done(struct mmc_request *mrq) * * Sets the done callback to be called when request is completed by the card. * Starts data mmc request execution + * If an ongoing transfer is already in progress, wait for the command line + * to become available before sending another command. */ static int __mmc_start_data_req(struct mmc_host *host, struct mmc_request *mrq) { int err; + mmc_wait_ongoing_tfr_cmd(host); + mrq->done = mmc_wait_data_done; mrq->host = host; + init_completion(&mrq->cmd_completion); + err = mmc_start_request(host, mrq); if (err) { mrq->cmd->error = err; + mmc_complete_cmd(mrq); mmc_wait_data_done(mrq); } @@ -417,12 +469,17 @@ static int __mmc_start_req(struct mmc_host *host, struct mmc_request *mrq) { int err; + mmc_wait_ongoing_tfr_cmd(host); + init_completion(&mrq->completion); mrq->done = mmc_wait_done; + init_completion(&mrq->cmd_completion); + err = mmc_start_request(host, mrq); if (err) { mrq->cmd->error = err; + mmc_complete_cmd(mrq); complete(&mrq->completion); } @@ -486,8 +543,7 @@ static int mmc_wait_for_data_req_done(struct mmc_host *host, return err; } -static void mmc_wait_for_req_done(struct mmc_host *host, - struct mmc_request *mrq) +void mmc_wait_for_req_done(struct mmc_host *host, struct mmc_request *mrq) { struct mmc_command *cmd; @@ -528,6 +584,28 @@ static void mmc_wait_for_req_done(struct mmc_host *host, mmc_retune_release(host); } +EXPORT_SYMBOL(mmc_wait_for_req_done); + +/** + * mmc_is_req_done - Determine if a 'cap_cmd_during_tfr' request is done + * @host: MMC host + * @mrq: MMC request + * + * mmc_is_req_done() is used with requests that have + * mrq->cap_cmd_during_tfr = true. mmc_is_req_done() must be called after + * starting a request and before waiting for it to complete. That is, + * either in between calls to mmc_start_req(), or after mmc_wait_for_req() + * and before mmc_wait_for_req_done(). If it is called at other times the + * result is not meaningful. + */ +bool mmc_is_req_done(struct mmc_host *host, struct mmc_request *mrq) +{ + if (host->areq) + return host->context_info.is_done_rcv; + else + return completion_done(&mrq->completion); +} +EXPORT_SYMBOL(mmc_is_req_done); /** * mmc_pre_req - Prepare for a new request @@ -648,13 +726,18 @@ EXPORT_SYMBOL(mmc_start_req); * @mrq: MMC request to start * * Start a new MMC custom command request for a host, and wait - * for the command to complete. Does not attempt to parse the - * response. + * for the command to complete. In the case of 'cap_cmd_during_tfr' + * requests, the transfer is ongoing and the caller can issue further + * commands that do not use the data lines, and then wait by calling + * mmc_wait_for_req_done(). + * Does not attempt to parse the response. */ void mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq) { __mmc_start_req(host, mrq); - mmc_wait_for_req_done(host, mrq); + + if (!mrq->cap_cmd_during_tfr) + mmc_wait_for_req_done(host, mrq); } EXPORT_SYMBOL(mmc_wait_for_req); diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index b01e77de1a74..368bed70aa9d 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -133,8 +133,12 @@ struct mmc_request { struct mmc_command *stop; struct completion completion; + struct completion cmd_completion; void (*done)(struct mmc_request *);/* completion function */ struct mmc_host *host; + + /* Allow other commands during this ongoing data transfer or busy wait */ + bool cap_cmd_during_tfr; }; struct mmc_card; @@ -146,6 +150,9 @@ extern struct mmc_async_req *mmc_start_req(struct mmc_host *, struct mmc_async_req *, int *); extern int mmc_interrupt_hpi(struct mmc_card *); extern void mmc_wait_for_req(struct mmc_host *, struct mmc_request *); +extern void mmc_wait_for_req_done(struct mmc_host *host, + struct mmc_request *mrq); +extern bool mmc_is_req_done(struct mmc_host *host, struct mmc_request *mrq); extern int mmc_wait_for_cmd(struct mmc_host *, struct mmc_command *, int); extern int mmc_app_cmd(struct mmc_host *, struct mmc_card *); extern int mmc_wait_for_app_cmd(struct mmc_host *, struct mmc_card *, diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index aa4bfbf129e4..0b2439441cc8 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -281,6 +281,7 @@ struct mmc_host { #define MMC_CAP_DRIVER_TYPE_A (1 << 23) /* Host supports Driver Type A */ #define MMC_CAP_DRIVER_TYPE_C (1 << 24) /* Host supports Driver Type C */ #define MMC_CAP_DRIVER_TYPE_D (1 << 25) /* Host supports Driver Type D */ +#define MMC_CAP_CMD_DURING_TFR (1 << 29) /* Commands during data transfer */ #define MMC_CAP_CMD23 (1 << 30) /* CMD23 supported. */ #define MMC_CAP_HW_RESET (1 << 31) /* Hardware reset */ @@ -382,6 +383,9 @@ struct mmc_host { struct mmc_async_req *areq; /* active async req */ struct mmc_context_info context_info; /* async synchronization info */ + /* Ongoing data transfer that allows commands during transfer */ + struct mmc_request *ongoing_mrq; + #ifdef CONFIG_FAIL_MMC_REQUEST struct fault_attr fail_mmc_request; #endif @@ -418,6 +422,7 @@ int mmc_power_restore_host(struct mmc_host *host); void mmc_detect_change(struct mmc_host *, unsigned long delay); void mmc_request_done(struct mmc_host *, struct mmc_request *); +void mmc_command_done(struct mmc_host *host, struct mmc_request *mrq); static inline void mmc_signal_sdio_irq(struct mmc_host *host) { |