summaryrefslogtreecommitdiff
path: root/drivers/mmc/host/s3cmci.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mmc/host/s3cmci.c')
-rw-r--r--drivers/mmc/host/s3cmci.c677
1 files changed, 448 insertions, 229 deletions
diff --git a/drivers/mmc/host/s3cmci.c b/drivers/mmc/host/s3cmci.c
index 3b2085b57769..5287be0d590a 100644
--- a/drivers/mmc/host/s3cmci.c
+++ b/drivers/mmc/host/s3cmci.c
@@ -3,9 +3,6 @@
*
* Copyright (C) 2004-2006 maintech GmbH, Thomas Kleffel <tk@maintech.de>
*
- * Current driver maintained by Ben Dooks and Simtec Electronics
- * Copyright (C) 2008 Simtec Electronics <ben-linux@fluff.org>
- *
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
@@ -16,14 +13,17 @@
#include <linux/clk.h>
#include <linux/mmc/host.h>
#include <linux/platform_device.h>
-#include <linux/cpufreq.h>
#include <linux/irq.h>
#include <linux/io.h>
-
+#include <linux/mmc/mmc.h>
#include <asm/dma.h>
+/* Use the old headers system */
#include <mach/regs-sdi.h>
#include <mach/regs-gpio.h>
+#include <mach/gpio.h>
+#include <asm/delay.h>
+#include <linux/delay.h>
#include <asm/plat-s3c24xx/mci.h>
@@ -31,6 +31,22 @@
#define DRIVER_NAME "s3c-mci"
+#define printk_err(fmt, args...) printk(KERN_ERR "[ ERROR ] s3c2443-sdi: " fmt, ## args)
+#define printk_info(fmt, args...) printk(KERN_INFO "s3c2443-sdi: " fmt, ## args)
+#define printk_dbg(fmt, args...) printk(KERN_DEBUG "s3c2443-sdi: " fmt, ## args)
+
+/* Enable/disable the debug messages */
+#if 0
+#define S3C2443_SDI_DEBUG
+#endif
+
+#ifdef S3C2443_SDI_DEBUG
+# define printk_debug(fmt, args...) printk(KERN_DEBUG "s3c2443-sdi: %s() " fmt, __FUNCTION__ , ## args)
+#else
+# define printk_debug(fmt, args...)
+#endif
+
+
enum dbg_channels {
dbg_err = (1 << 0),
dbg_debug = (1 << 1),
@@ -43,9 +59,9 @@ enum dbg_channels {
dbg_conf = (1 << 8),
};
-static const int dbgmap_err = dbg_fail;
+static const int dbgmap_err = dbg_err | dbg_fail;
static const int dbgmap_info = dbg_info | dbg_conf;
-static const int dbgmap_debug = dbg_err | dbg_debug;
+static const int dbgmap_debug = dbg_debug;
#define dbg(host, channels, args...) \
do { \
@@ -189,11 +205,17 @@ static inline u32 disable_imask(struct s3cmci_host *host, u32 imask)
static inline void clear_imask(struct s3cmci_host *host)
{
- writel(0, host->base + host->sdiimsk);
+ ulong imsk, to_write = 0;
+
+ imsk = readl(host->base + host->sdiimsk);
+ if ((imsk & S3C2410_SDIIMSK_SDIOIRQ) && host->sdio_irq)
+ to_write = S3C2410_SDIIMSK_SDIOIRQ;
+
+ writel(to_write, host->base + host->sdiimsk);
}
static inline int get_data_buffer(struct s3cmci_host *host,
- u32 *bytes, u32 **pointer)
+ u32 *words, u32 **pointer)
{
struct scatterlist *sg;
@@ -210,7 +232,7 @@ static inline int get_data_buffer(struct s3cmci_host *host,
}
sg = &host->mrq->data->sg[host->pio_sgptr];
- *bytes = sg->length;
+ *words = sg->length >> 2;
*pointer = sg_virt(sg);
host->pio_sgptr++;
@@ -226,7 +248,7 @@ static inline u32 fifo_count(struct s3cmci_host *host)
u32 fifostat = readl(host->base + S3C2410_SDIFSTA);
fifostat &= S3C2410_SDIFSTA_COUNTMASK;
- return fifostat;
+ return fifostat >> 2;
}
static inline u32 fifo_free(struct s3cmci_host *host)
@@ -234,15 +256,13 @@ static inline u32 fifo_free(struct s3cmci_host *host)
u32 fifostat = readl(host->base + S3C2410_SDIFSTA);
fifostat &= S3C2410_SDIFSTA_COUNTMASK;
- return 63 - fifostat;
+ return (63 - fifostat) >> 2;
}
static void do_pio_read(struct s3cmci_host *host)
{
int res;
u32 fifo;
- u32 *ptr;
- u32 fifo_words;
void __iomem *from_ptr;
/* write real prescaler to host, it might be set slow to fix */
@@ -273,35 +293,14 @@ static void do_pio_read(struct s3cmci_host *host)
fifo, host->pio_bytes,
readl(host->base + S3C2410_SDIDCNT));
- /* If we have reached the end of the block, we can
- * read a word and get 1 to 3 bytes. If we in the
- * middle of the block, we have to read full words,
- * otherwise we will write garbage, so round down to
- * an even multiple of 4. */
- if (fifo >= host->pio_bytes)
+ if (fifo > host->pio_bytes)
fifo = host->pio_bytes;
- else
- fifo -= fifo & 3;
host->pio_bytes -= fifo;
host->pio_count += fifo;
- fifo_words = fifo >> 2;
- ptr = host->pio_ptr;
- while (fifo_words--)
- *ptr++ = readl(from_ptr);
- host->pio_ptr = ptr;
-
- if (fifo & 3) {
- u32 n = fifo & 3;
- u32 data = readl(from_ptr);
- u8 *p = (u8 *)host->pio_ptr;
-
- while (n--) {
- *p++ = data;
- data >>= 8;
- }
- }
+ while (fifo--)
+ *(host->pio_ptr++) = readl(from_ptr);
}
if (!host->pio_bytes) {
@@ -325,7 +324,6 @@ static void do_pio_write(struct s3cmci_host *host)
void __iomem *to_ptr;
int res;
u32 fifo;
- u32 *ptr;
to_ptr = host->base + host->sdidata;
@@ -347,23 +345,14 @@ static void do_pio_write(struct s3cmci_host *host)
}
- /* If we have reached the end of the block, we have to
- * write exactly the remaining number of bytes. If we
- * in the middle of the block, we have to write full
- * words, so round down to an even multiple of 4. */
- if (fifo >= host->pio_bytes)
+ if (fifo > host->pio_bytes)
fifo = host->pio_bytes;
- else
- fifo -= fifo & 3;
host->pio_bytes -= fifo;
host->pio_count += fifo;
- fifo = (fifo + 3) >> 2;
- ptr = host->pio_ptr;
while (fifo--)
- writel(*ptr++, to_ptr);
- host->pio_ptr = ptr;
+ writel(*(host->pio_ptr++), to_ptr);
}
enable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF);
@@ -399,6 +388,17 @@ static void pio_tasklet(unsigned long data)
enable_irq(host->irq);
}
+/* The DMA-callback will call this function by errors or transfer completes */
+static void dma_tasklet(unsigned long data)
+{
+ struct s3cmci_host *host = (struct s3cmci_host *) data;
+
+ if (host->complete_what == COMPLETION_FINALIZE) {
+ clear_imask(host);
+ finalize_request(host);
+ }
+}
+
/*
* ISR for SDI Interface IRQ
* Communication between driver and ISR works as follows:
@@ -444,6 +444,15 @@ static irqreturn_t s3cmci_irq(int irq, void *dev_id)
mci_cclear = 0;
mci_dclear = 0;
+ printk_debug("IRQ: cmd 0x%08x | dsta 0x%08x | imsk 0x%08x\n",
+ mci_csta, mci_dsta, mci_imsk);
+
+ if (mci_dsta & S3C2410_SDIDSTA_SDIOIRQDETECT) {
+ printk_debug("SDIO IRQ detected\n");
+ mmc_signal_sdio_irq(host->mmc);
+ writel(S3C2410_SDIDSTA_SDIOIRQDETECT, host->base + S3C2410_SDIDSTA);
+ }
+
if ((host->complete_what == COMPLETION_NONE) ||
(host->complete_what == COMPLETION_FINALIZE)) {
host->status = "nothing to complete";
@@ -487,19 +496,22 @@ static irqreturn_t s3cmci_irq(int irq, void *dev_id)
}
if (mci_csta & S3C2410_SDICMDSTAT_CMDTIMEOUT) {
- dbg(host, dbg_err, "CMDSTAT: error CMDTIMEOUT\n");
+ dbg(host, dbg_debug, "CMDSTAT: error CMDTIMEOUT\n");
cmd->error = -ETIMEDOUT;
host->status = "error: command timeout";
goto fail_transfer;
}
if (mci_csta & S3C2410_SDICMDSTAT_CMDSENT) {
+
+ /* @FIXME: Move the write-command to a correct place! (Luis G.) */
+ mci_cclear |= S3C2410_SDICMDSTAT_CMDSENT;
+ writel(mci_cclear, host->base + S3C2410_SDICMDSTAT);
+
if (host->complete_what == COMPLETION_CMDSENT) {
host->status = "ok: command sent";
goto close_transfer;
}
-
- mci_cclear |= S3C2410_SDICMDSTAT_CMDSENT;
}
if (mci_csta & S3C2410_SDICMDSTAT_CRCFAIL) {
@@ -603,8 +615,22 @@ close_transfer:
host->complete_what = COMPLETION_FINALIZE;
clear_imask(host);
- tasklet_schedule(&host->pio_tasklet);
+ /*
+ * If we have received an interrupt although we are waiting for the
+ * DMA-callback (cmd->data), then something went wrong with the last
+ * transfer. This happens when the card is removed before
+ * the card initialization was completed.
+ * (Luis Galdos)
+ */
+ if (cmd->data && host->dodma && !cmd->data->error && !cmd->error) {
+ host->dma_complete = 1;
+ cmd->error = cmd->data->error = -EILSEQ;
+ printk_debug("[SD] Waiting DMA (CMD %08x | DAT %08x)\n",
+ mci_csta, mci_dsta);
+ }
+
+ tasklet_schedule(&host->pio_tasklet);
goto irq_out;
irq_out:
@@ -621,13 +647,26 @@ irq_out:
* ISR for the CardDetect Pin
*/
+static int s3cmci_card_present(struct mmc_host *mmc);
+
static irqreturn_t s3cmci_irq_cd(int irq, void *dev_id)
{
struct s3cmci_host *host = (struct s3cmci_host *)dev_id;
-
- dbg(host, dbg_irq, "card detect\n");
-
- mmc_detect_change(host->mmc, msecs_to_jiffies(500));
+ int val;
+ static int card_present;
+
+ /*
+ * Get the current status of the GPIO for checking if the card state has
+ * really changed since the last interrupt. Otherwise it will generates
+ * more than one interrupt for the same state
+ * Luis Galdos
+ */
+ val = s3cmci_card_present(host->mmc);
+ if (val != card_present) {
+ dbg(host, dbg_debug, "Card detect IRQ: %s\n", val ? "insert" : "remove");
+ card_present = val;
+ mmc_detect_change(host->mmc, msecs_to_jiffies(500));
+ }
return IRQ_HANDLED;
}
@@ -652,29 +691,49 @@ static void s3cmci_dma_done_callback(struct s3c2410_dma_chan *dma_ch,
spin_lock_irqsave(&host->complete_lock, iflags);
if (result != S3C2410_RES_OK) {
- dbg(host, dbg_fail, "DMA FAILED: csta=0x%08x dsta=0x%08x "
- "fsta=0x%08x dcnt:0x%08x result:0x%08x toGo:%u\n",
- mci_csta, mci_dsta, mci_fsta,
- mci_dcnt, result, host->dmatogo);
+ struct mmc_request *mrq = host->mrq;
+
+ if (mrq) {
+ struct mmc_command *cmd;
+
+ cmd = mrq->cmd;
+ dbg(host, dbg_fail, "DMA FAILED: CMD%i csta=0x%08x dsta=0x%08x "
+ "fsta=0x%08x dcnt:0x%08x result:%i toGo:%u\n",
+ cmd->opcode, mci_csta, mci_dsta, mci_fsta,
+ mci_dcnt, result, host->dmatogo);
+ } else
+ dbg(host, dbg_fail, "DMA FAILED: csta=0x%08x dsta=0x%08x "
+ "fsta=0x%08x dcnt:0x%08x result:0x%08x toGo:%u\n",
+ mci_csta, mci_dsta, mci_fsta,
+ mci_dcnt, result, host->dmatogo);
goto fail_request;
}
host->dmatogo--;
if (host->dmatogo) {
- dbg(host, dbg_dma, "DMA DONE Size:%i DSTA:[%08x] "
- "DCNT:[%08x] toGo:%u\n",
- size, mci_dsta, mci_dcnt, host->dmatogo);
+ printk_debug("DMA DONE Size:%i DSTA:[%08x] "
+ "DCNT:[%08x] toGo:%u\n",
+ size, mci_dsta, mci_dcnt, host->dmatogo);
- goto out;
+ if (mci_dsta & S3C2410_SDIDSTA_XFERFINISH) {
+ printk_err("SDI has no more data, but DMA waits for more?\n");
+ goto fail_request;
+ }
+
+ spin_unlock_irqrestore(&host->complete_lock, iflags);
+ return;
}
- dbg(host, dbg_dma, "DMA FINISHED Size:%i DSTA:%08x DCNT:%08x\n",
- size, mci_dsta, mci_dcnt);
+ printk_debug("DMA ENDE Size:%i DSTA:[%08x] DCNT:[%08x]\n",
+ size, mci_dsta, mci_dcnt);
+ host->dma_complete = 1;
host->complete_what = COMPLETION_FINALIZE;
out:
+ /* @FIXME: Check the performance modification through this delay */
+ udelay(10);
tasklet_schedule(&host->pio_tasklet);
spin_unlock_irqrestore(&host->complete_lock, iflags);
return;
@@ -693,17 +752,22 @@ static void finalize_request(struct s3cmci_host *host)
struct mmc_command *cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd;
int debug_as_failure = 0;
- if (host->complete_what != COMPLETION_FINALIZE)
- return;
+ spin_lock(&host->complete_lock);
+
+ if (host->complete_what != COMPLETION_FINALIZE) {
+ printk_debug("Nothing to complete!\n");
+ goto exit_unlock;
+ }
- if (!mrq)
- return;
+ if (!mrq) {
+ printk_err("Empty MMC request found!\n");
+ goto exit_unlock;
+ }
- if (cmd->data && (cmd->error == 0) &&
- (cmd->data->error == 0)) {
+ if (cmd->data && (cmd->error == 0) && (cmd->data->error == 0)) {
if (host->dodma && (!host->dma_complete)) {
- dbg(host, dbg_dma, "DMA Missing!\n");
- return;
+ printk_err("DMA complete missing (%i)\n", host->dma_complete);
+ goto exit_unlock;
}
}
@@ -725,17 +789,19 @@ static void finalize_request(struct s3cmci_host *host)
/* Cleanup controller */
writel(0, host->base + S3C2410_SDICMDARG);
- writel(S3C2410_SDIDCON_STOP, host->base + S3C2410_SDIDCON);
writel(0, host->base + S3C2410_SDICMDCON);
- writel(0, host->base + host->sdiimsk);
-
+ writel(S3C2410_SDIDCON_STOP, host->base + S3C2410_SDIDCON);
+ if (!host->sdio_irq) {
+ writel(0, host->base + host->sdiimsk);
+ }
+
if (cmd->data && cmd->error)
cmd->data->error = cmd->error;
if (cmd->data && cmd->data->stop && (!host->cmd_is_stop)) {
host->cmd_is_stop = 1;
s3cmci_send_request(host->mmc);
- return;
+ goto exit_unlock;
}
/* If we have no data transfer we are finished here */
@@ -776,28 +842,38 @@ request_done:
host->complete_what = COMPLETION_NONE;
host->mrq = NULL;
mmc_request_done(host->mmc, mrq);
+
+ exit_unlock:
+ spin_unlock(&host->complete_lock);
+
}
static void s3cmci_dma_setup(struct s3cmci_host *host,
enum s3c2410_dmasrc source)
{
static enum s3c2410_dmasrc last_source = -1;
- static int setup_ok;
+ static int setup_ok = 0;
if (last_source == source)
return;
last_source = source;
- s3c2410_dma_devconfig(host->dma, source, 3,
+ s3c2410_dma_devconfig(host->dma, source,
+ S3C2410_DISRCC_INC | S3C2410_DISRCC_APB,
host->mem->start + host->sdidata);
if (!setup_ok) {
- s3c2410_dma_config(host->dma, 4,
- (S3C2410_DCON_HWTRIG | S3C2410_DCON_CH0_SDI));
+ printk_debug("Setting up the DMA channel!\n");
+ s3c2410_dma_config(host->dma,
+ 4,
+ S3C2410_DCON_HANDSHAKE |
+ S3C2410_DCON_SYNC_PCLK |
+ S3C2410_DCON_HWTRIG);
+
s3c2410_dma_set_buffdone_fn(host->dma,
s3cmci_dma_done_callback);
- s3c2410_dma_setflags(host->dma, S3C2410_DMAF_AUTOSTART);
+ s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH);
setup_ok = 1;
}
}
@@ -849,10 +925,11 @@ static int s3cmci_setup_data(struct s3cmci_host *host, struct mmc_data *data)
/* We cannot deal with unaligned blocks with more than
* one block being transfered. */
- if (data->blocks > 1) {
- pr_warning("%s: can't do non-word sized block transfers (blksz %d)\n", __func__, data->blksz);
+ if (data->blocks > 1)
return -EINVAL;
- }
+
+ /* No support yet for non-word block transfers. */
+ return -EINVAL;
}
while (readl(host->base + S3C2410_SDIDSTA) &
@@ -899,12 +976,15 @@ static int s3cmci_setup_data(struct s3cmci_host *host, struct mmc_data *data)
writel(dcon, host->base + S3C2410_SDIDCON);
/* write BSIZE register */
-
writel(data->blksz, host->base + S3C2410_SDIBSIZE);
/* add to IMASK register */
imsk = S3C2410_SDIIMSK_FIFOFAIL | S3C2410_SDIIMSK_DATACRC |
- S3C2410_SDIIMSK_DATATIMEOUT | S3C2410_SDIIMSK_DATAFINISH;
+ S3C2410_SDIIMSK_DATATIMEOUT;
+
+ /* By DMA-support we will use the DMA-callback for the transfer-complete */
+ if (!host->dodma)
+ imsk |= S3C2410_SDIIMSK_DATAFINISH;
enable_imask(host, imsk);
@@ -951,25 +1031,31 @@ static int s3cmci_prepare_dma(struct s3cmci_host *host, struct mmc_data *data)
{
int dma_len, i;
int rw = (data->flags & MMC_DATA_WRITE) ? 1 : 0;
-
+ int retval;
+
BUG_ON((data->flags & BOTH_DIR) == BOTH_DIR);
- s3cmci_dma_setup(host, rw ? S3C2410_DMASRC_MEM : S3C2410_DMASRC_HW);
+ printk_debug("New DMA transfer | %i Blks | %i Blksz\n",
+ data->blocks, data->blksz);
+
s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH);
+ s3cmci_dma_setup(host, rw ? S3C2410_DMASRC_MEM : S3C2410_DMASRC_HW);
dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
(rw) ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
- if (dma_len == 0)
- return -ENOMEM;
+ if (dma_len == 0) {
+ printk_err("dma_map_sg failed, no memory available?\n");
+ retval = -ENOMEM;
+ goto exit_all;
+ }
- host->dma_complete = 0;
host->dmatogo = dma_len;
for (i = 0; i < dma_len; i++) {
int res;
- dbg(host, dbg_dma, "enqueue %i:%u@%u\n", i,
+ printk_debug("Enqueue %i @ 0x%08x | %u bytes\n", i,
sg_dma_address(&data->sg[i]),
sg_dma_len(&data->sg[i]));
@@ -979,13 +1065,31 @@ static int s3cmci_prepare_dma(struct s3cmci_host *host, struct mmc_data *data)
if (res) {
s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH);
- return -EBUSY;
+ printk_err("Couldn't enqueue a DMA-buffer\n");
+ retval = -EBUSY;
+ goto exit_all;
}
}
+ /*
+ * Disable the data counter interrupt, then the DMA-callback will be
+ * responsible for finalizing the request
+ */
+ disable_imask(host, S3C2410_SDIIMSK_DATAFINISH |
+ S3C2410_SDIIMSK_RXFIFOHALF |
+ S3C2410_SDIIMSK_RXFIFOFULL |
+ S3C2410_SDIIMSK_RXFIFOLAST |
+ S3C2410_SDIIMSK_TXFIFOEMPTY |
+ S3C2410_SDIIMSK_TXFIFOHALF);
+
+ host->dma_complete = 0;
s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_START);
- return 0;
+ retval = 0;
+
+ exit_all:
+
+ return retval;
}
static void s3cmci_send_request(struct mmc_host *mmc)
@@ -997,6 +1101,10 @@ static void s3cmci_send_request(struct mmc_host *mmc)
host->ccnt++;
prepare_dbgmsg(host, cmd, host->cmd_is_stop);
+ /* @XXX: Sending the switch command with data leads to a DMA-failure, why? */
+ if (cmd->opcode == MMC_SWITCH && cmd->data)
+ udelay(200);
+
/* Clear command, data and fifo status registers
Fifo clear only necessary on 2440, but doesn't hurt on 2410
*/
@@ -1007,6 +1115,11 @@ static void s3cmci_send_request(struct mmc_host *mmc)
if (cmd->data) {
int res = s3cmci_setup_data(host, cmd->data);
+ printk_debug("CMD%u: %s transfer | %i blks | %u blksz\n",
+ cmd->opcode,
+ (cmd->data->flags & MMC_DATA_READ) ? "RX" : "TX",
+ cmd->data->blocks, cmd->data->blksz);
+
host->dcnt++;
if (res) {
@@ -1033,11 +1146,28 @@ static void s3cmci_send_request(struct mmc_host *mmc)
}
}
+ /* Enable Interrupt */
+ if (!host->dodma)
+ enable_irq(host->irq);
+
/* Send command */
s3cmci_send_command(host, cmd);
- /* Enable Interrupt */
- enable_irq(host->irq);
+ /*
+ * If the host supports DMA, then disable the data finish interrupts and
+ * the FIFO-interrupts, then the DMA-controller will handle the data
+ * transfer and will finish the transfer if no errors ocurrs
+ * @XXX: This is probably not the correct place for this operation
+ * (Luis Galdos)
+ */
+ if (cmd->data && host->dodma)
+ disable_imask(host, S3C2410_SDIIMSK_DATAFINISH |
+ S3C2410_SDIIMSK_RXFIFOHALF |
+ S3C2410_SDIIMSK_RXFIFOFULL |
+ S3C2410_SDIIMSK_RXFIFOLAST |
+ S3C2410_SDIIMSK_TXFIFOEMPTY |
+ S3C2410_SDIIMSK_TXFIFOHALF);
+
}
static int s3cmci_card_present(struct mmc_host *mmc)
@@ -1062,40 +1192,18 @@ static void s3cmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
host->mrq = mrq;
if (s3cmci_card_present(mmc) == 0) {
- dbg(host, dbg_err, "%s: no medium present\n", __func__);
+ dbg(host, dbg_debug, "%s: no medium present\n", __func__);
host->mrq->cmd->error = -ENOMEDIUM;
mmc_request_done(mmc, mrq);
} else
s3cmci_send_request(mmc);
}
-static void s3cmci_set_clk(struct s3cmci_host *host, struct mmc_ios *ios)
-{
- u32 mci_psc;
-
- /* Set clock */
- for (mci_psc = 0; mci_psc < 255; mci_psc++) {
- host->real_rate = host->clk_rate / (host->clk_div*(mci_psc+1));
-
- if (host->real_rate <= ios->clock)
- break;
- }
-
- if (mci_psc > 255)
- mci_psc = 255;
-
- host->prescaler = mci_psc;
- writel(host->prescaler, host->base + S3C2410_SDIPRE);
-
- /* If requested clock is 0, real_rate will be 0, too */
- if (ios->clock == 0)
- host->real_rate = 0;
-}
-
static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
struct s3cmci_host *host = mmc_priv(mmc);
- u32 mci_con;
+ u32 mci_psc, mci_con;
+ int waitclk;
/* Set the power state */
@@ -1104,13 +1212,6 @@ static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
switch (ios->power_mode) {
case MMC_POWER_ON:
case MMC_POWER_UP:
- s3c2410_gpio_cfgpin(S3C2410_GPE5, S3C2410_GPE5_SDCLK);
- s3c2410_gpio_cfgpin(S3C2410_GPE6, S3C2410_GPE6_SDCMD);
- s3c2410_gpio_cfgpin(S3C2410_GPE7, S3C2410_GPE7_SDDAT0);
- s3c2410_gpio_cfgpin(S3C2410_GPE8, S3C2410_GPE8_SDDAT1);
- s3c2410_gpio_cfgpin(S3C2410_GPE9, S3C2410_GPE9_SDDAT2);
- s3c2410_gpio_cfgpin(S3C2410_GPE10, S3C2410_GPE10_SDDAT3);
-
if (host->pdata->set_power)
host->pdata->set_power(ios->power_mode, ios->vdd);
@@ -1121,9 +1222,6 @@ static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
case MMC_POWER_OFF:
default:
- s3c2410_gpio_setpin(S3C2410_GPE5, 0);
- s3c2410_gpio_cfgpin(S3C2410_GPE5, S3C2410_GPE5_OUTP);
-
if (host->is2440)
mci_con |= S3C2440_SDICON_SDRESET;
@@ -1133,8 +1231,6 @@ static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
break;
}
- s3cmci_set_clk(host, ios);
-
/* Set CLOCK_ENABLE */
if (ios->clock)
mci_con |= S3C2410_SDICON_CLOCKTYPE;
@@ -1143,12 +1239,47 @@ static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
writel(mci_con, host->base + S3C2410_SDICON);
+
+ /* Set clock */
+ for (mci_psc = 0; mci_psc < 255; mci_psc++) {
+ host->real_rate = host->clk_rate / (host->clk_div*(mci_psc+1));
+
+ if (host->real_rate <= ios->clock)
+ break;
+ }
+
+ if (mci_psc > 255)
+ mci_psc = 255;
+
+
+ /* If requested clock is 0, real_rate will be 0, too */
+ if (ios->clock == 0)
+ host->real_rate = 0;
+
+ host->prescaler = mci_psc;
+ writel(host->prescaler, host->base + S3C2410_SDIPRE);
+
+ /*
+ * According to the data sheet first configure the register SDICON and
+ * wait at least 74 SDCLK before initializing the SD card.
+ * (Luis Galdos)
+ */
+ waitclk = 1;
+ if (host->real_rate)
+ waitclk = 100 * (1000000 / host->real_rate);
+
+ if (waitclk > 1000)
+ mdelay(waitclk / 1000);
+ else
+ udelay(waitclk);
+
+
if ((ios->power_mode == MMC_POWER_ON) ||
(ios->power_mode == MMC_POWER_UP)) {
- dbg(host, dbg_conf, "running at %lukHz (requested: %ukHz).\n",
+ dbg(host, dbg_debug, "running at %lukHz (requested: %ukHz).\n",
host->real_rate/1000, ios->clock/1000);
} else {
- dbg(host, dbg_conf, "powered down.\n");
+ dbg(host, dbg_debug, "powered down.\n");
}
host->bus_width = ios->bus_width;
@@ -1179,11 +1310,42 @@ static int s3cmci_get_ro(struct mmc_host *mmc)
return ret;
}
+/* Here only mask or unmask the SDIO interrupt */
+static void s3cmci_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+ struct s3cmci_host *host;
+ ulong con, imsk;
+
+ if (!(host = mmc_priv(mmc))) {
+ printk_err("NULL host pointer found!\n");
+ return;
+ }
+
+ printk_debug("%s SDIO IRQ\n", enable ? "Enabling" : "Disabling");
+
+ con = readl(host->base + S3C2410_SDICON);
+ imsk = readl(host->base + host->sdiimsk);
+
+ if (enable) {
+ host->sdio_irq = 1;
+ imsk |= S3C2410_SDIIMSK_SDIOIRQ;
+ con |= S3C2410_SDICON_SDIOIRQ;
+ } else {
+ host->sdio_irq = 0;
+
+ imsk |= S3C2410_SDIIMSK_SDIOIRQ;
+ con &= ~S3C2410_SDICON_SDIOIRQ;
+ }
+
+ writel(con, host->base + S3C2410_SDICON);
+ writel(imsk, host->base + host->sdiimsk);
+}
+
static struct mmc_host_ops s3cmci_ops = {
- .request = s3cmci_request,
- .set_ios = s3cmci_set_ios,
- .get_ro = s3cmci_get_ro,
- .get_cd = s3cmci_card_present,
+ .request = s3cmci_request,
+ .set_ios = s3cmci_set_ios,
+ .get_ro = s3cmci_get_ro,
+ .enable_sdio_irq = s3cmci_enable_sdio_irq,
};
static struct s3c24xx_mci_pdata s3cmci_def_pdata = {
@@ -1191,61 +1353,6 @@ static struct s3c24xx_mci_pdata s3cmci_def_pdata = {
* checks. Any zero fields to ensure reaonable defaults are picked. */
};
-#ifdef CONFIG_CPU_FREQ
-
-static int s3cmci_cpufreq_transition(struct notifier_block *nb,
- unsigned long val, void *data)
-{
- struct s3cmci_host *host;
- struct mmc_host *mmc;
- unsigned long newclk;
- unsigned long flags;
-
- host = container_of(nb, struct s3cmci_host, freq_transition);
- newclk = clk_get_rate(host->clk);
- mmc = host->mmc;
-
- if ((val == CPUFREQ_PRECHANGE && newclk > host->clk_rate) ||
- (val == CPUFREQ_POSTCHANGE && newclk < host->clk_rate)) {
- spin_lock_irqsave(&mmc->lock, flags);
-
- host->clk_rate = newclk;
-
- if (mmc->ios.power_mode != MMC_POWER_OFF &&
- mmc->ios.clock != 0)
- s3cmci_set_clk(host, &mmc->ios);
-
- spin_unlock_irqrestore(&mmc->lock, flags);
- }
-
- return 0;
-}
-
-static inline int s3cmci_cpufreq_register(struct s3cmci_host *host)
-{
- host->freq_transition.notifier_call = s3cmci_cpufreq_transition;
-
- return cpufreq_register_notifier(&host->freq_transition,
- CPUFREQ_TRANSITION_NOTIFIER);
-}
-
-static inline void s3cmci_cpufreq_deregister(struct s3cmci_host *host)
-{
- cpufreq_unregister_notifier(&host->freq_transition,
- CPUFREQ_TRANSITION_NOTIFIER);
-}
-
-#else
-static inline int s3cmci_cpufreq_register(struct s3cmci_host *host)
-{
- return 0;
-}
-
-static inline void s3cmci_cpufreq_deregister(struct s3cmci_host *host)
-{
-}
-#endif
-
static int __devinit s3cmci_probe(struct platform_device *pdev, int is2440)
{
struct s3cmci_host *host;
@@ -1269,9 +1376,6 @@ static int __devinit s3cmci_probe(struct platform_device *pdev, int is2440)
host->pdata = &s3cmci_def_pdata;
}
- spin_lock_init(&host->complete_lock);
- tasklet_init(&host->pio_tasklet, pio_tasklet, (unsigned long) host);
-
if (is2440) {
host->sdiimsk = S3C2440_SDIIMSK;
host->sdidata = S3C2440_SDIDATA;
@@ -1282,12 +1386,22 @@ static int __devinit s3cmci_probe(struct platform_device *pdev, int is2440)
host->clk_div = 2;
}
- host->dodma = 0;
+ /* Configure the DMA-support depending on the passed platform configuration */
+ host->dodma = (host->pdata->dma_enable) ? 1 : 0;
host->complete_what = COMPLETION_NONE;
host->pio_active = XFER_NONE;
- host->dma = S3CMCI_DMA;
+ /* DMA-channel to use (include/asm-arm/arch-s3c2410/dma.h) */
+ host->dma = DMACH_SDI;
+
+ spin_lock_init(&host->complete_lock);
+ /* Depending on the DMA-support use different tasklet-functions */
+ if (!host->dodma)
+ tasklet_init(&host->pio_tasklet, pio_tasklet, (unsigned long) host);
+ else
+ tasklet_init(&host->pio_tasklet, dma_tasklet, (unsigned long) host);
+
host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!host->mem) {
dev_err(&pdev->dev,
@@ -1352,10 +1466,12 @@ static int __devinit s3cmci_probe(struct platform_device *pdev, int is2440)
s3c2410_gpio_cfgpin(host->pdata->gpio_wprotect,
S3C2410_GPIO_INPUT);
- if (s3c2410_dma_request(S3CMCI_DMA, &s3cmci_dma_client, NULL) < 0) {
- dev_err(&pdev->dev, "unable to get DMA channel.\n");
- ret = -EBUSY;
- goto probe_free_irq_cd;
+ if (host->dodma) {
+ if (s3c2410_dma_request(host->dma, &s3cmci_dma_client, NULL) < 0) {
+ dev_err(&pdev->dev, "unable to get DMA channel.\n");
+ ret = -EBUSY;
+ goto probe_free_irq_cd;
+ }
}
host->clk = clk_get(&pdev->dev, "sdi");
@@ -1363,7 +1479,7 @@ static int __devinit s3cmci_probe(struct platform_device *pdev, int is2440)
dev_err(&pdev->dev, "failed to find clock source.\n");
ret = PTR_ERR(host->clk);
host->clk = NULL;
- goto probe_free_host;
+ goto probe_free_dmach;
}
ret = clk_enable(host->clk);
@@ -1376,7 +1492,7 @@ static int __devinit s3cmci_probe(struct platform_device *pdev, int is2440)
mmc->ops = &s3cmci_ops;
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_max = host->clk_rate / host->clk_div;
@@ -1396,25 +1512,30 @@ static int __devinit s3cmci_probe(struct platform_device *pdev, int is2440)
(host->is2440?"2440":""),
host->base, host->irq, host->irq_cd, host->dma);
- ret = s3cmci_cpufreq_register(host);
- if (ret) {
- dev_err(&pdev->dev, "failed to register cpufreq\n");
- goto free_dmabuf;
- }
-
ret = mmc_add_host(mmc);
if (ret) {
dev_err(&pdev->dev, "failed to add mmc host.\n");
- goto free_cpufreq;
+ goto free_dmabuf;
}
+ /* @FIXME: Get the GPIO-list from the platform-data */
+ s3c2410_gpio_cfgpin(S3C2410_GPE5, S3C2410_GPE5_SDCLK);
+ s3c2410_gpio_cfgpin(S3C2410_GPE6, S3C2410_GPE6_SDCMD);
+ s3c2410_gpio_cfgpin(S3C2410_GPE7, S3C2410_GPE7_SDDAT0);
+ s3c2410_gpio_cfgpin(S3C2410_GPE8, S3C2410_GPE8_SDDAT1);
+ s3c2410_gpio_cfgpin(S3C2410_GPE9, S3C2410_GPE9_SDDAT2);
+ s3c2410_gpio_cfgpin(S3C2410_GPE10, S3C2410_GPE10_SDDAT3);
+
platform_set_drvdata(pdev, mmc);
dev_info(&pdev->dev, "initialisation done.\n");
- return 0;
+ /* Enable the wakeup for this device (Luis Galdos) */
+ device_init_wakeup(&pdev->dev, 1);
+ device_set_wakeup_enable(&pdev->dev, 0);
+
+ enable_irq(host->irq);
- free_cpufreq:
- s3cmci_cpufreq_deregister(host);
+ return 0;
free_dmabuf:
clk_disable(host->clk);
@@ -1422,6 +1543,10 @@ static int __devinit s3cmci_probe(struct platform_device *pdev, int is2440)
clk_free:
clk_put(host->clk);
+ probe_free_dmach:
+ if (host->dodma)
+ s3c2410_dma_free(host->dma, &s3cmci_dma_client);
+
probe_free_irq_cd:
if (host->irq_cd >= 0)
free_irq(host->irq_cd, host);
@@ -1449,11 +1574,26 @@ static void s3cmci_shutdown(struct platform_device *pdev)
if (host->irq_cd >= 0)
free_irq(host->irq_cd, host);
- s3cmci_cpufreq_deregister(host);
mmc_remove_host(mmc);
clk_disable(host->clk);
}
+/*
+ * Don't remove the MMC-host when going to shutdown the system. This can lead to some
+ * failures when the host has mounted a card with our root file system.
+ * (Luis Galdos)
+ */
+static void s3cmci_2440_shutdown(struct platform_device *pdev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(pdev);
+ struct s3cmci_host *host = mmc_priv(mmc);
+
+ if (host->irq_cd >= 0)
+ free_irq(host->irq_cd, host);
+
+ clk_disable(host->clk);
+}
+
static int __devexit s3cmci_remove(struct platform_device *pdev)
{
struct mmc_host *mmc = platform_get_drvdata(pdev);
@@ -1464,7 +1604,10 @@ static int __devexit s3cmci_remove(struct platform_device *pdev)
clk_put(host->clk);
tasklet_disable(&host->pio_tasklet);
- s3c2410_dma_free(S3CMCI_DMA, &s3cmci_dma_client);
+
+ /* Free the DMA-channel */
+ if (host->dodma)
+ s3c2410_dma_free(host->dma, &s3cmci_dma_client);
free_irq(host->irq, host);
@@ -1492,18 +1635,94 @@ static int __devinit s3cmci_2440_probe(struct platform_device *dev)
#ifdef CONFIG_PM
-static int s3cmci_suspend(struct platform_device *dev, pm_message_t state)
+static int s3cmci_suspend(struct platform_device *pdev, pm_message_t state)
{
- struct mmc_host *mmc = platform_get_drvdata(dev);
+ int retval;
+ struct mmc_host *mmc;
+ struct s3cmci_host *host;
+
+ mmc = platform_get_drvdata(pdev);
+ host = mmc_priv(mmc);
+
+ retval = mmc_suspend_host(mmc, state);
+ if (retval)
+ goto exit_suspend;
+
+ /* Check if the wakeup is enabled. IN that case configure it as ext wakeup */
+ if (device_may_wakeup(&pdev->dev)) {
+ struct s3c24xx_mci_pdata *pdata;
+ unsigned long detect;
+
+ pdata = host->pdata;
+
+ retval = enable_irq_wake(host->irq_cd);
+ if (retval) {
+ dev_err(&pdev->dev, "Couldn't enable wake IRQ %i\n",
+ host->irq_cd);
+ goto exit_suspend;
+ }
+
+ /*
+ * Reconfigure the card for generating the wakeup when a new
+ * card is plugged into the slot
+ */
+ detect = (pdata->detect_invert) ? (IRQF_TRIGGER_RISING) :
+ (IRQF_TRIGGER_FALLING);
+ set_irq_type(host->irq_cd, detect);
+ }
- return mmc_suspend_host(mmc, state);
+ writel(S3C2440_SDICON_SDRESET | S3C2410_SDIDCON_STOP,
+ host->base + S3C2410_SDICON);
+
+ clk_disable(host->clk);
+exit_suspend:
+ return retval;
}
-static int s3cmci_resume(struct platform_device *dev)
+static int s3cmci_resume(struct platform_device *pdev)
{
- struct mmc_host *mmc = platform_get_drvdata(dev);
+ struct s3cmci_host *host;
+ struct mmc_host *mmc;
+ int retval;
- return mmc_resume_host(mmc);
+ mmc = platform_get_drvdata(pdev);
+ host = mmc_priv(mmc);
+
+ if (device_may_wakeup(&pdev->dev)) {
+ retval = disable_irq_wake(host->irq_cd);
+ if (retval) {
+ dev_err(&pdev->dev, "Couldn't disable wake IRQ %i\n",
+ host->irq_cd);
+ goto exit_resume;
+ }
+
+ set_irq_type(host->irq_cd, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING);
+ }
+
+ clk_enable(host->clk);
+
+ /* @FIXME: Why do we need to free que DMA-channel? */
+ s3c2410_dma_free(host->dma, &s3cmci_dma_client);
+ s3c2410_dma_request(host->dma, &s3cmci_dma_client, NULL);
+
+ /*
+ * By unsafe resumes we MUST check the card state at this point, then the
+ * higher MMC-layer is probably transferring some kind of data to the
+ * block device that doesn't exist any more.
+ */
+#if defined(CONFIG_MMC_UNSAFE_RESUME)
+ if (s3cmci_card_present(mmc))
+ retval = mmc_resume_host(mmc);
+ else {
+ retval = 0;
+ mmc_detect_change(mmc, msecs_to_jiffies(500));
+ }
+#else
+ retval = mmc_resume_host(mmc);
+#endif /* CONFIG_MMC_UNSAFE_RESUME */
+
+exit_resume:
+ return retval;
}
#else /* CONFIG_PM */
@@ -1537,7 +1756,7 @@ static struct platform_driver s3cmci_2440_driver = {
.driver.owner = THIS_MODULE,
.probe = s3cmci_2440_probe,
.remove = __devexit_p(s3cmci_remove),
- .shutdown = s3cmci_shutdown,
+ .shutdown = s3cmci_2440_shutdown,
.suspend = s3cmci_suspend,
.resume = s3cmci_resume,
};
@@ -1563,7 +1782,7 @@ module_exit(s3cmci_exit);
MODULE_DESCRIPTION("Samsung S3C MMC/SD Card Interface driver");
MODULE_LICENSE("GPL v2");
-MODULE_AUTHOR("Thomas Kleffel <tk@maintech.de>, Ben Dooks <ben-linux@fluff.org>");
+MODULE_AUTHOR("Thomas Kleffel <tk@maintech.de>");
MODULE_ALIAS("platform:s3c2410-sdi");
MODULE_ALIAS("platform:s3c2412-sdi");
MODULE_ALIAS("platform:s3c2440-sdi");