diff options
Diffstat (limited to 'drivers/mmc/host/sdhci.c')
-rw-r--r-- | drivers/mmc/host/sdhci.c | 207 |
1 files changed, 116 insertions, 91 deletions
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index a8141ff9be03..59acf8e3331e 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1,13 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * linux/drivers/mmc/host/sdhci.c - Secure Digital Host Controller Interface driver * * Copyright (C) 2005-2008 Pierre Ossman, All Rights Reserved. * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * * Thanks to the following companies for their support: * * - JMicron (hardware and technical support) @@ -446,6 +442,28 @@ static inline void sdhci_led_deactivate(struct sdhci_host *host) #endif +static void sdhci_mod_timer(struct sdhci_host *host, struct mmc_request *mrq, + unsigned long timeout) +{ + if (sdhci_data_line_cmd(mrq->cmd)) + mod_timer(&host->data_timer, timeout); + else + mod_timer(&host->timer, timeout); +} + +static void sdhci_del_timer(struct sdhci_host *host, struct mmc_request *mrq) +{ + if (sdhci_data_line_cmd(mrq->cmd)) + del_timer(&host->data_timer); + else + del_timer(&host->timer); +} + +static inline bool sdhci_has_requests(struct sdhci_host *host) +{ + return host->cmd || host->data_cmd; +} + /*****************************************************************************\ * * * Core functions * @@ -1221,6 +1239,18 @@ static void __sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq) { int i; + if (host->cmd && host->cmd->mrq == mrq) + host->cmd = NULL; + + if (host->data_cmd && host->data_cmd->mrq == mrq) + host->data_cmd = NULL; + + if (host->data && host->data->mrq == mrq) + host->data = NULL; + + if (sdhci_needs_reset(host, mrq)) + host->pending_reset = true; + for (i = 0; i < SDHCI_MAX_MRQS; i++) { if (host->mrqs_done[i] == mrq) { WARN_ON(1); @@ -1237,24 +1267,17 @@ static void __sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq) WARN_ON(i >= SDHCI_MAX_MRQS); - tasklet_schedule(&host->finish_tasklet); + sdhci_del_timer(host, mrq); + + if (!sdhci_has_requests(host)) + sdhci_led_deactivate(host); } static void sdhci_finish_mrq(struct sdhci_host *host, struct mmc_request *mrq) { - if (host->cmd && host->cmd->mrq == mrq) - host->cmd = NULL; - - if (host->data_cmd && host->data_cmd->mrq == mrq) - host->data_cmd = NULL; - - if (host->data && host->data->mrq == mrq) - host->data = NULL; - - if (sdhci_needs_reset(host, mrq)) - host->pending_reset = true; - __sdhci_finish_mrq(host, mrq); + + queue_work(host->complete_wq, &host->complete_work); } static void sdhci_finish_data(struct sdhci_host *host) @@ -1305,34 +1328,17 @@ static void sdhci_finish_data(struct sdhci_host *host) * responsibility to send the stop command if required. */ if (data->mrq->cap_cmd_during_tfr) { - sdhci_finish_mrq(host, data->mrq); + __sdhci_finish_mrq(host, data->mrq); } else { /* Avoid triggering warning in sdhci_send_command() */ host->cmd = NULL; sdhci_send_command(host, data->stop); } } else { - sdhci_finish_mrq(host, data->mrq); + __sdhci_finish_mrq(host, data->mrq); } } -static void sdhci_mod_timer(struct sdhci_host *host, struct mmc_request *mrq, - unsigned long timeout) -{ - if (sdhci_data_line_cmd(mrq->cmd)) - mod_timer(&host->data_timer, timeout); - else - mod_timer(&host->timer, timeout); -} - -static void sdhci_del_timer(struct sdhci_host *host, struct mmc_request *mrq) -{ - if (sdhci_data_line_cmd(mrq->cmd)) - del_timer(&host->data_timer); - else - del_timer(&host->timer); -} - void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) { int flags; @@ -1492,7 +1498,7 @@ static void sdhci_finish_command(struct sdhci_host *host) sdhci_finish_data(host); if (!cmd->data) - sdhci_finish_mrq(host, cmd->mrq); + __sdhci_finish_mrq(host, cmd->mrq); } } @@ -1807,7 +1813,6 @@ void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) sdhci_send_command(host, mrq->cmd); } - mmiowb(); spin_unlock_irqrestore(&host->lock, flags); } EXPORT_SYMBOL_GPL(sdhci_request); @@ -2010,8 +2015,6 @@ void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) */ if (host->quirks & SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS) sdhci_do_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); - - mmiowb(); } EXPORT_SYMBOL_GPL(sdhci_set_ios); @@ -2105,7 +2108,6 @@ static void sdhci_enable_sdio_irq_nolock(struct sdhci_host *host, int enable) sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); - mmiowb(); } } @@ -2131,6 +2133,17 @@ void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable) } EXPORT_SYMBOL_GPL(sdhci_enable_sdio_irq); +static void sdhci_ack_sdio_irq(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + if (host->flags & SDHCI_SDIO_IRQ_ENABLED) + sdhci_enable_sdio_irq_nolock(host, true); + spin_unlock_irqrestore(&host->lock, flags); +} + int sdhci_start_signal_voltage_switch(struct mmc_host *mmc, struct mmc_ios *ios) { @@ -2353,7 +2366,6 @@ void sdhci_send_tuning(struct sdhci_host *host, u32 opcode) host->tuning_done = 0; - mmiowb(); spin_unlock_irqrestore(&host->lock, flags); /* Wait for Buffer Read Ready interrupt */ @@ -2369,9 +2381,9 @@ static int __sdhci_execute_tuning(struct sdhci_host *host, u32 opcode) /* * Issue opcode repeatedly till Execute Tuning is set to 0 or the number - * of loops reaches 40 times. + * of loops reaches tuning loop count. */ - for (i = 0; i < MAX_TUNING_LOOP; i++) { + for (i = 0; i < host->tuning_loop_count; i++) { u16 ctrl; sdhci_send_tuning(host, opcode); @@ -2528,11 +2540,6 @@ static void sdhci_pre_req(struct mmc_host *mmc, struct mmc_request *mrq) sdhci_pre_dma_transfer(host, mrq->data, COOKIE_PRE_MAPPED); } -static inline bool sdhci_has_requests(struct sdhci_host *host) -{ - return host->cmd || host->data_cmd; -} - static void sdhci_error_out_mrqs(struct sdhci_host *host, int err) { if (host->data_cmd) { @@ -2585,6 +2592,7 @@ static const struct mmc_host_ops sdhci_ops = { .get_ro = sdhci_get_ro, .hw_reset = sdhci_hw_reset, .enable_sdio_irq = sdhci_enable_sdio_irq, + .ack_sdio_irq = sdhci_ack_sdio_irq, .start_signal_voltage_switch = sdhci_start_signal_voltage_switch, .prepare_hs400_tuning = sdhci_prepare_hs400_tuning, .execute_tuning = sdhci_execute_tuning, @@ -2594,7 +2602,7 @@ static const struct mmc_host_ops sdhci_ops = { /*****************************************************************************\ * * - * Tasklets * + * Request done * * * \*****************************************************************************/ @@ -2617,8 +2625,6 @@ static bool sdhci_request_done(struct sdhci_host *host) return true; } - sdhci_del_timer(host, mrq); - /* * Always unmap the data buffers if they were mapped by * sdhci_prepare_data() whenever we finish with a request. @@ -2700,12 +2706,8 @@ static bool sdhci_request_done(struct sdhci_host *host) host->pending_reset = false; } - if (!sdhci_has_requests(host)) - sdhci_led_deactivate(host); - host->mrqs_done[i] = NULL; - mmiowb(); spin_unlock_irqrestore(&host->lock, flags); mmc_request_done(host->mmc, mrq); @@ -2713,9 +2715,10 @@ static bool sdhci_request_done(struct sdhci_host *host) return false; } -static void sdhci_tasklet_finish(unsigned long param) +static void sdhci_complete_work(struct work_struct *work) { - struct sdhci_host *host = (struct sdhci_host *)param; + struct sdhci_host *host = container_of(work, struct sdhci_host, + complete_work); while (!sdhci_request_done(host)) ; @@ -2739,7 +2742,6 @@ static void sdhci_timeout_timer(struct timer_list *t) sdhci_finish_mrq(host, host->cmd->mrq); } - mmiowb(); spin_unlock_irqrestore(&host->lock, flags); } @@ -2761,6 +2763,7 @@ static void sdhci_timeout_data_timer(struct timer_list *t) if (host->data) { host->data->error = -ETIMEDOUT; sdhci_finish_data(host); + queue_work(host->complete_wq, &host->complete_work); } else if (host->data_cmd) { host->data_cmd->error = -ETIMEDOUT; sdhci_finish_mrq(host, host->data_cmd->mrq); @@ -2770,7 +2773,6 @@ static void sdhci_timeout_data_timer(struct timer_list *t) } } - mmiowb(); spin_unlock_irqrestore(&host->lock, flags); } @@ -2827,7 +2829,7 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask, u32 *intmask_p) return; } - sdhci_finish_mrq(host, host->cmd->mrq); + __sdhci_finish_mrq(host, host->cmd->mrq); return; } @@ -2841,7 +2843,7 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask, u32 *intmask_p) if (mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) { mrq->sbc->error = err; - sdhci_finish_mrq(host, mrq); + __sdhci_finish_mrq(host, mrq); return; } } @@ -2905,7 +2907,7 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) if (intmask & SDHCI_INT_DATA_TIMEOUT) { host->data_cmd = NULL; data_cmd->error = -ETIMEDOUT; - sdhci_finish_mrq(host, data_cmd->mrq); + __sdhci_finish_mrq(host, data_cmd->mrq); return; } if (intmask & SDHCI_INT_DATA_END) { @@ -2918,7 +2920,7 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) if (host->cmd == data_cmd) return; - sdhci_finish_mrq(host, data_cmd->mrq); + __sdhci_finish_mrq(host, data_cmd->mrq); return; } } @@ -3001,12 +3003,24 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) } } +static inline bool sdhci_defer_done(struct sdhci_host *host, + struct mmc_request *mrq) +{ + struct mmc_data *data = mrq->data; + + return host->pending_reset || + ((host->flags & SDHCI_REQ_USE_DMA) && data && + data->host_cookie == COOKIE_MAPPED); +} + static irqreturn_t sdhci_irq(int irq, void *dev_id) { + struct mmc_request *mrqs_done[SDHCI_MAX_MRQS] = {0}; irqreturn_t result = IRQ_NONE; struct sdhci_host *host = dev_id; u32 intmask, mask, unexpected = 0; int max_loops = 16; + int i; spin_lock(&host->lock); @@ -3081,8 +3095,7 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) if ((intmask & SDHCI_INT_CARD_INT) && (host->ier & SDHCI_INT_CARD_INT)) { sdhci_enable_sdio_irq_nolock(host, false); - host->thread_isr |= SDHCI_INT_CARD_INT; - result = IRQ_WAKE_THREAD; + sdio_signal_irq(host->mmc); } intmask &= ~(SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE | @@ -3100,9 +3113,30 @@ cont: intmask = sdhci_readl(host, SDHCI_INT_STATUS); } while (intmask && --max_loops); + + /* Determine if mrqs can be completed immediately */ + for (i = 0; i < SDHCI_MAX_MRQS; i++) { + struct mmc_request *mrq = host->mrqs_done[i]; + + if (!mrq) + continue; + + if (sdhci_defer_done(host, mrq)) { + result = IRQ_WAKE_THREAD; + } else { + mrqs_done[i] = mrq; + host->mrqs_done[i] = NULL; + } + } out: spin_unlock(&host->lock); + /* Process mrqs ready for immediate completion */ + for (i = 0; i < SDHCI_MAX_MRQS; i++) { + if (mrqs_done[i]) + mmc_request_done(host->mmc, mrqs_done[i]); + } + if (unexpected) { pr_err("%s: Unexpected interrupt 0x%08x.\n", mmc_hostname(host->mmc), unexpected); @@ -3118,6 +3152,9 @@ static irqreturn_t sdhci_thread_irq(int irq, void *dev_id) unsigned long flags; u32 isr; + while (!sdhci_request_done(host)) + ; + spin_lock_irqsave(&host->lock, flags); isr = host->thread_isr; host->thread_isr = 0; @@ -3130,16 +3167,7 @@ static irqreturn_t sdhci_thread_irq(int irq, void *dev_id) mmc_detect_change(mmc, msecs_to_jiffies(200)); } - if (isr & SDHCI_INT_CARD_INT) { - sdio_run_irqs(host->mmc); - - spin_lock_irqsave(&host->lock, flags); - if (host->flags & SDHCI_SDIO_IRQ_ENABLED) - sdhci_enable_sdio_irq_nolock(host, true); - spin_unlock_irqrestore(&host->lock, flags); - } - - return isr ? IRQ_HANDLED : IRQ_NONE; + return IRQ_HANDLED; } /*****************************************************************************\ @@ -3251,7 +3279,6 @@ int sdhci_resume_host(struct sdhci_host *host) mmc->ops->set_ios(mmc, &mmc->ios); } else { sdhci_init(host, (host->mmc->pm_flags & MMC_PM_KEEP_POWER)); - mmiowb(); } if (host->irq_wake_enabled) { @@ -3391,7 +3418,6 @@ void sdhci_cqe_enable(struct mmc_host *mmc) mmc_hostname(mmc), host->ier, sdhci_readl(host, SDHCI_INT_STATUS)); - mmiowb(); spin_unlock_irqrestore(&host->lock, flags); } EXPORT_SYMBOL_GPL(sdhci_cqe_enable); @@ -3416,7 +3442,6 @@ void sdhci_cqe_disable(struct mmc_host *mmc, bool recovery) mmc_hostname(mmc), host->ier, sdhci_readl(host, SDHCI_INT_STATUS)); - mmiowb(); spin_unlock_irqrestore(&host->lock, flags); } EXPORT_SYMBOL_GPL(sdhci_cqe_disable); @@ -3494,6 +3519,7 @@ struct sdhci_host *sdhci_alloc_host(struct device *dev, host->cqe_err_ier = SDHCI_CQE_INT_ERR_MASK; host->tuning_delay = -1; + host->tuning_loop_count = MAX_TUNING_LOOP; host->sdma_boundary = SDHCI_DEFAULT_BOUNDARY_ARG; @@ -4224,14 +4250,15 @@ EXPORT_SYMBOL_GPL(sdhci_cleanup_host); int __sdhci_add_host(struct sdhci_host *host) { + unsigned int flags = WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_HIGHPRI; struct mmc_host *mmc = host->mmc; int ret; - /* - * Init tasklets. - */ - tasklet_init(&host->finish_tasklet, - sdhci_tasklet_finish, (unsigned long)host); + host->complete_wq = alloc_workqueue("sdhci", flags, 0); + if (!host->complete_wq) + return -ENOMEM; + + INIT_WORK(&host->complete_work, sdhci_complete_work); timer_setup(&host->timer, sdhci_timeout_timer, 0); timer_setup(&host->data_timer, sdhci_timeout_data_timer, 0); @@ -4245,7 +4272,7 @@ int __sdhci_add_host(struct sdhci_host *host) if (ret) { pr_err("%s: Failed to request IRQ %d: %d\n", mmc_hostname(mmc), host->irq, ret); - goto untasklet; + goto unwq; } ret = sdhci_led_register(host); @@ -4255,8 +4282,6 @@ int __sdhci_add_host(struct sdhci_host *host) goto unirq; } - mmiowb(); - ret = mmc_add_host(mmc); if (ret) goto unled; @@ -4278,8 +4303,8 @@ unirq: sdhci_writel(host, 0, SDHCI_INT_ENABLE); sdhci_writel(host, 0, SDHCI_SIGNAL_ENABLE); free_irq(host->irq, host); -untasklet: - tasklet_kill(&host->finish_tasklet); +unwq: + destroy_workqueue(host->complete_wq); return ret; } @@ -4341,7 +4366,7 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) del_timer_sync(&host->timer); del_timer_sync(&host->data_timer); - tasklet_kill(&host->finish_tasklet); + destroy_workqueue(host->complete_wq); if (!IS_ERR(mmc->supply.vqmmc)) regulator_disable(mmc->supply.vqmmc); |