Commit c2258893 authored by Ben Dooks's avatar Ben Dooks Committed by Linus Torvalds

s3cmci: add SDIO IRQ support

The controller supports SDIO IRQ detection so add support for hardware
assisted SDIO interrupt detection for the SDIO core.  This improves the
response time for SDIO interrupts and thus the transfer rate from devices
such as the Marvel 8686.

As a note, it does seem that the controller will miss an IRQ than is held
asserted, so there are some manual checks to see if the SDIO interrupt is
active after a transfer.

Major testing on the S3C2440.
Signed-off-by: default avatarBen Dooks <ben@simtec.co.uk>
Cc: <linux-mmc@vger.kernel.org>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 9bdd203b
...@@ -190,7 +190,33 @@ static inline u32 disable_imask(struct s3cmci_host *host, u32 imask) ...@@ -190,7 +190,33 @@ static inline u32 disable_imask(struct s3cmci_host *host, u32 imask)
static inline void clear_imask(struct s3cmci_host *host) static inline void clear_imask(struct s3cmci_host *host)
{ {
writel(0, host->base + host->sdiimsk); u32 mask = readl(host->base + host->sdiimsk);
/* preserve the SDIO IRQ mask state */
mask &= S3C2410_SDIIMSK_SDIOIRQ;
writel(mask, host->base + host->sdiimsk);
}
/**
* s3cmci_check_sdio_irq - test whether the SDIO IRQ is being signalled
* @host: The host to check.
*
* Test to see if the SDIO interrupt is being signalled in case the
* controller has failed to re-detect a card interrupt. Read GPE8 and
* see if it is low and if so, signal a SDIO interrupt.
*
* This is currently called if a request is finished (we assume that the
* bus is now idle) and when the SDIO IRQ is enabled in case the IRQ is
* already being indicated.
*/
static void s3cmci_check_sdio_irq(struct s3cmci_host *host)
{
if (host->sdio_irqen) {
if (gpio_get_value(S3C2410_GPE(8)) == 0) {
printk(KERN_DEBUG "%s: signalling irq\n", __func__);
mmc_signal_sdio_irq(host->mmc);
}
}
} }
static inline int get_data_buffer(struct s3cmci_host *host, static inline int get_data_buffer(struct s3cmci_host *host,
...@@ -238,6 +264,64 @@ static inline u32 fifo_free(struct s3cmci_host *host) ...@@ -238,6 +264,64 @@ static inline u32 fifo_free(struct s3cmci_host *host)
return 63 - fifostat; return 63 - fifostat;
} }
/**
* s3cmci_enable_irq - enable IRQ, after having disabled it.
* @host: The device state.
* @more: True if more IRQs are expected from transfer.
*
* Enable the main IRQ if needed after it has been disabled.
*
* The IRQ can be one of the following states:
* - disabled during IDLE
* - disabled whilst processing data
* - enabled during transfer
* - enabled whilst awaiting SDIO interrupt detection
*/
static void s3cmci_enable_irq(struct s3cmci_host *host, bool more)
{
unsigned long flags;
bool enable = false;
local_irq_save(flags);
host->irq_enabled = more;
host->irq_disabled = false;
enable = more | host->sdio_irqen;
if (host->irq_state != enable) {
host->irq_state = enable;
if (enable)
enable_irq(host->irq);
else
disable_irq(host->irq);
}
local_irq_restore(flags);
}
/**
*
*/
static void s3cmci_disable_irq(struct s3cmci_host *host, bool transfer)
{
unsigned long flags;
local_irq_save(flags);
//printk(KERN_DEBUG "%s: transfer %d\n", __func__, transfer);
host->irq_disabled = transfer;
if (transfer && host->irq_state) {
host->irq_state = false;
disable_irq(host->irq);
}
local_irq_restore(flags);
}
static void do_pio_read(struct s3cmci_host *host) static void do_pio_read(struct s3cmci_host *host)
{ {
int res; int res;
...@@ -374,8 +458,7 @@ static void pio_tasklet(unsigned long data) ...@@ -374,8 +458,7 @@ static void pio_tasklet(unsigned long data)
{ {
struct s3cmci_host *host = (struct s3cmci_host *) data; struct s3cmci_host *host = (struct s3cmci_host *) data;
s3cmci_disable_irq(host, true);
disable_irq(host->irq);
if (host->pio_active == XFER_WRITE) if (host->pio_active == XFER_WRITE)
do_pio_write(host); do_pio_write(host);
...@@ -395,9 +478,10 @@ static void pio_tasklet(unsigned long data) ...@@ -395,9 +478,10 @@ static void pio_tasklet(unsigned long data)
host->mrq->data->error = -EINVAL; host->mrq->data->error = -EINVAL;
} }
s3cmci_enable_irq(host, false);
finalize_request(host); finalize_request(host);
} else } else
enable_irq(host->irq); s3cmci_enable_irq(host, true);
} }
/* /*
...@@ -432,17 +516,27 @@ static irqreturn_t s3cmci_irq(int irq, void *dev_id) ...@@ -432,17 +516,27 @@ static irqreturn_t s3cmci_irq(int irq, void *dev_id)
struct s3cmci_host *host = dev_id; struct s3cmci_host *host = dev_id;
struct mmc_command *cmd; struct mmc_command *cmd;
u32 mci_csta, mci_dsta, mci_fsta, mci_dcnt, mci_imsk; u32 mci_csta, mci_dsta, mci_fsta, mci_dcnt, mci_imsk;
u32 mci_cclear, mci_dclear; u32 mci_cclear = 0, mci_dclear;
unsigned long iflags; unsigned long iflags;
mci_dsta = readl(host->base + S3C2410_SDIDSTA);
mci_imsk = readl(host->base + host->sdiimsk);
if (mci_dsta & S3C2410_SDIDSTA_SDIOIRQDETECT) {
if (mci_imsk & S3C2410_SDIIMSK_SDIOIRQ) {
mci_dclear = S3C2410_SDIDSTA_SDIOIRQDETECT;
writel(mci_dclear, host->base + S3C2410_SDIDSTA);
mmc_signal_sdio_irq(host->mmc);
return IRQ_HANDLED;
}
}
spin_lock_irqsave(&host->complete_lock, iflags); spin_lock_irqsave(&host->complete_lock, iflags);
mci_csta = readl(host->base + S3C2410_SDICMDSTAT); mci_csta = readl(host->base + S3C2410_SDICMDSTAT);
mci_dsta = readl(host->base + S3C2410_SDIDSTA);
mci_dcnt = readl(host->base + S3C2410_SDIDCNT); mci_dcnt = readl(host->base + S3C2410_SDIDCNT);
mci_fsta = readl(host->base + S3C2410_SDIFSTA); mci_fsta = readl(host->base + S3C2410_SDIFSTA);
mci_imsk = readl(host->base + host->sdiimsk);
mci_cclear = 0;
mci_dclear = 0; mci_dclear = 0;
if ((host->complete_what == COMPLETION_NONE) || if ((host->complete_what == COMPLETION_NONE) ||
...@@ -776,6 +870,8 @@ static void finalize_request(struct s3cmci_host *host) ...@@ -776,6 +870,8 @@ static void finalize_request(struct s3cmci_host *host)
request_done: request_done:
host->complete_what = COMPLETION_NONE; host->complete_what = COMPLETION_NONE;
host->mrq = NULL; host->mrq = NULL;
s3cmci_check_sdio_irq(host);
mmc_request_done(host->mmc, mrq); mmc_request_done(host->mmc, mrq);
} }
...@@ -1037,7 +1133,7 @@ static void s3cmci_send_request(struct mmc_host *mmc) ...@@ -1037,7 +1133,7 @@ static void s3cmci_send_request(struct mmc_host *mmc)
s3cmci_send_command(host, cmd); s3cmci_send_command(host, cmd);
/* Enable Interrupt */ /* Enable Interrupt */
enable_irq(host->irq); s3cmci_enable_irq(host, true);
} }
static int s3cmci_card_present(struct mmc_host *mmc) static int s3cmci_card_present(struct mmc_host *mmc)
...@@ -1178,11 +1274,52 @@ static int s3cmci_get_ro(struct mmc_host *mmc) ...@@ -1178,11 +1274,52 @@ static int s3cmci_get_ro(struct mmc_host *mmc)
return ret; return ret;
} }
static void s3cmci_enable_sdio_irq(struct mmc_host *mmc, int enable)
{
struct s3cmci_host *host = mmc_priv(mmc);
unsigned long flags;
u32 con;
local_irq_save(flags);
con = readl(host->base + S3C2410_SDICON);
host->sdio_irqen = enable;
if (enable == host->sdio_irqen)
goto same_state;
if (enable) {
con |= S3C2410_SDICON_SDIOIRQ;
enable_imask(host, S3C2410_SDIIMSK_SDIOIRQ);
if (!host->irq_state && !host->irq_disabled) {
host->irq_state = true;
enable_irq(host->irq);
}
} else {
disable_imask(host, S3C2410_SDIIMSK_SDIOIRQ);
con &= ~S3C2410_SDICON_SDIOIRQ;
if (!host->irq_enabled && host->irq_state) {
disable_irq_nosync(host->irq);
host->irq_state = false;
}
}
writel(con, host->base + S3C2410_SDICON);
same_state:
local_irq_restore(flags);
s3cmci_check_sdio_irq(host);
}
static struct mmc_host_ops s3cmci_ops = { static struct mmc_host_ops s3cmci_ops = {
.request = s3cmci_request, .request = s3cmci_request,
.set_ios = s3cmci_set_ios, .set_ios = s3cmci_set_ios,
.get_ro = s3cmci_get_ro, .get_ro = s3cmci_get_ro,
.get_cd = s3cmci_card_present, .get_cd = s3cmci_card_present,
.enable_sdio_irq = s3cmci_enable_sdio_irq,
}; };
static struct s3c24xx_mci_pdata s3cmci_def_pdata = { static struct s3c24xx_mci_pdata s3cmci_def_pdata = {
...@@ -1257,6 +1394,9 @@ static int s3cmci_state_show(struct seq_file *seq, void *v) ...@@ -1257,6 +1394,9 @@ static int s3cmci_state_show(struct seq_file *seq, void *v)
seq_printf(seq, "Prescale = %d\n", host->prescaler); seq_printf(seq, "Prescale = %d\n", host->prescaler);
seq_printf(seq, "is2440 = %d\n", host->is2440); seq_printf(seq, "is2440 = %d\n", host->is2440);
seq_printf(seq, "IRQ = %d\n", host->irq); seq_printf(seq, "IRQ = %d\n", host->irq);
seq_printf(seq, "IRQ enabled = %d\n", host->irq_enabled);
seq_printf(seq, "IRQ disabled = %d\n", host->irq_disabled);
seq_printf(seq, "IRQ state = %d\n", host->irq_state);
seq_printf(seq, "CD IRQ = %d\n", host->irq_cd); seq_printf(seq, "CD IRQ = %d\n", host->irq_cd);
seq_printf(seq, "Do DMA = %d\n", host->dodma); seq_printf(seq, "Do DMA = %d\n", host->dodma);
seq_printf(seq, "SDIIMSK at %d\n", host->sdiimsk); seq_printf(seq, "SDIIMSK at %d\n", host->sdiimsk);
...@@ -1468,6 +1608,7 @@ static int __devinit s3cmci_probe(struct platform_device *pdev) ...@@ -1468,6 +1608,7 @@ static int __devinit s3cmci_probe(struct platform_device *pdev)
* ensure we don't lock the system with un-serviceable requests. */ * ensure we don't lock the system with un-serviceable requests. */
disable_irq(host->irq); disable_irq(host->irq);
host->irq_state = false;
if (host->pdata->gpio_detect) { if (host->pdata->gpio_detect) {
ret = gpio_request(host->pdata->gpio_detect, "s3cmci detect"); ret = gpio_request(host->pdata->gpio_detect, "s3cmci detect");
...@@ -1526,7 +1667,7 @@ static int __devinit s3cmci_probe(struct platform_device *pdev) ...@@ -1526,7 +1667,7 @@ static int __devinit s3cmci_probe(struct platform_device *pdev)
mmc->ops = &s3cmci_ops; mmc->ops = &s3cmci_ops;
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
mmc->caps = MMC_CAP_4_BIT_DATA; mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;
mmc->f_min = host->clk_rate / (host->clk_div * 256); mmc->f_min = host->clk_rate / (host->clk_div * 256);
mmc->f_max = host->clk_rate / host->clk_div; mmc->f_max = host->clk_rate / host->clk_div;
......
...@@ -42,6 +42,11 @@ struct s3cmci_host { ...@@ -42,6 +42,11 @@ struct s3cmci_host {
int dodma; int dodma;
int dmatogo; int dmatogo;
bool irq_disabled;
bool irq_enabled;
bool irq_state;
int sdio_irqen;
struct mmc_request *mrq; struct mmc_request *mrq;
int cmd_is_stop; int cmd_is_stop;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment