mmc: sdio: Add API to manage SDIO IRQs from a workqueue
authorUlf Hansson <ulf.hansson@linaro.org>
Thu, 13 Apr 2017 14:48:11 +0000 (16:48 +0200)
committerUlf Hansson <ulf.hansson@linaro.org>
Tue, 20 Jun 2017 08:30:11 +0000 (10:30 +0200)
For hosts not supporting MMC_CAP2_SDIO_IRQ_NOTHREAD but MMC_CAP_SDIO_IRQ,
the SDIO IRQs are processed from a dedicated kernel thread. For these
cases, the host calls mmc_signal_sdio_irq() from its ISR to signal a new
SDIO IRQ.

Signaling an SDIO IRQ makes the host's ->enable_sdio_irq() callback to be
invoked to temporary disable the IRQs, before the kernel thread is woken up
to process it. When processing of the IRQs are completed, they are
re-enabled by the kernel thread, again via invoking the host's
->enable_sdio_irq().

The observation from this, is that the execution path is being unnecessary
complex, as the host driver already knows that it needs to temporary
disable the IRQs before signaling a new one. Moreover, replacing the kernel
thread with a work/workqueue would not only greatly simplify the code, but
also make it more robust.

To address the above problems, let's continue to build upon the support for
MMC_CAP2_SDIO_IRQ_NOTHREAD, as it already implements SDIO IRQs to be
processed without using the clumsy kernel thread and without the ping-pong
calls of the host's ->enable_sdio_irq() callback for each processed IRQ.

Therefore, let's add new API sdio_signal_irq(), which enables hosts to
signal/process SDIO IRQs by using a work/workqueue, rather than using the
kernel thread.

Add also a new host callback ->ack_sdio_irq(), which the work invokes when
the SDIO IRQs have been processed. This informs the host about when it
shall re-enable the SDIO IRQs. Potentially, we could re-use the existing
->enable_sdio_irq() callback instead of adding a new one, however it has
turned out that it's more convenient for hosts to get this information via
a separate callback.

Hosts that wants to use this new method to signal/process SDIO IRQs, must
enable MMC_CAP2_SDIO_IRQ_NOTHREAD and implement the ->ack_sdio_irq()
callback.

Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
Tested-by: Douglas Anderson <dianders@chromium.org>
Reviewed-by: Douglas Anderson <dianders@chromium.org>
drivers/mmc/core/host.c
drivers/mmc/core/sdio_irq.c
drivers/mmc/core/sdio_ops.h
include/linux/mmc/host.h

index 3f8c85d..8823c97 100644 (file)
@@ -30,6 +30,7 @@
 #include "host.h"
 #include "slot-gpio.h"
 #include "pwrseq.h"
+#include "sdio_ops.h"
 
 #define cls_dev_to_mmc_host(d) container_of(d, struct mmc_host, class_dev)
 
@@ -379,6 +380,7 @@ struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
        spin_lock_init(&host->lock);
        init_waitqueue_head(&host->wq);
        INIT_DELAYED_WORK(&host->detect, mmc_rescan);
+       INIT_DELAYED_WORK(&host->sdio_irq_work, sdio_irq_work);
        setup_timer(&host->retune_timer, mmc_retune_timer, (unsigned long)host);
 
        /*
index 44d9c86..c771843 100644 (file)
@@ -98,11 +98,27 @@ void sdio_run_irqs(struct mmc_host *host)
        if (host->sdio_irqs) {
                host->sdio_irq_pending = true;
                process_sdio_pending_irqs(host);
+               if (host->ops->ack_sdio_irq)
+                       host->ops->ack_sdio_irq(host);
        }
        mmc_release_host(host);
 }
 EXPORT_SYMBOL_GPL(sdio_run_irqs);
 
+void sdio_irq_work(struct work_struct *work)
+{
+       struct mmc_host *host =
+               container_of(work, struct mmc_host, sdio_irq_work.work);
+
+       sdio_run_irqs(host);
+}
+
+void sdio_signal_irq(struct mmc_host *host)
+{
+       queue_delayed_work(system_wq, &host->sdio_irq_work, 0);
+}
+EXPORT_SYMBOL_GPL(sdio_signal_irq);
+
 static int sdio_irq_thread(void *_host)
 {
        struct mmc_host *host = _host;
index ee35cb4..96945ca 100644 (file)
@@ -17,6 +17,7 @@
 
 struct mmc_host;
 struct mmc_card;
+struct work_struct;
 
 int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr);
 int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn,
@@ -25,6 +26,7 @@ int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn,
        unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz);
 int sdio_reset(struct mmc_host *host);
 unsigned int mmc_align_data_size(struct mmc_card *card, unsigned int sz);
+void sdio_irq_work(struct work_struct *work);
 
 static inline bool sdio_is_io_busy(u32 opcode, u32 arg)
 {
index 21385ac..f186b26 100644 (file)
@@ -130,6 +130,7 @@ struct mmc_host_ops {
        int     (*get_cd)(struct mmc_host *host);
 
        void    (*enable_sdio_irq)(struct mmc_host *host, int enable);
+       void    (*ack_sdio_irq)(struct mmc_host *host);
 
        /* optional callback for HC quirks */
        void    (*init_card)(struct mmc_host *host, struct mmc_card *card);
@@ -358,6 +359,7 @@ struct mmc_host {
 
        unsigned int            sdio_irqs;
        struct task_struct      *sdio_irq_thread;
+       struct delayed_work     sdio_irq_work;
        bool                    sdio_irq_pending;
        atomic_t                sdio_irq_thread_abort;
 
@@ -428,6 +430,7 @@ static inline void mmc_signal_sdio_irq(struct mmc_host *host)
 }
 
 void sdio_run_irqs(struct mmc_host *host);
+void sdio_signal_irq(struct mmc_host *host);
 
 #ifdef CONFIG_REGULATOR
 int mmc_regulator_get_ocrmask(struct regulator *supply);