diff options
Diffstat (limited to 'target/linux/s3c24xx/patches/0019-s3c_mci.patch.patch')
-rwxr-xr-x | target/linux/s3c24xx/patches/0019-s3c_mci.patch.patch | 1704 |
1 files changed, 1704 insertions, 0 deletions
diff --git a/target/linux/s3c24xx/patches/0019-s3c_mci.patch.patch b/target/linux/s3c24xx/patches/0019-s3c_mci.patch.patch new file mode 100755 index 0000000000..8f033d5661 --- /dev/null +++ b/target/linux/s3c24xx/patches/0019-s3c_mci.patch.patch @@ -0,0 +1,1704 @@ +From ed9a479d4e02e6415e4242211266845013ecad77 Mon Sep 17 00:00:00 2001 +From: mokopatches <mokopatches@openmoko.org> +Date: Wed, 16 Jul 2008 14:44:50 +0100 +Subject: [PATCH] s3c_mci.patch + This is a MMC/SD driver for the Samsung S3C24xx SD/MMC controller, originally + developed years ago by Thomas Kleffel <tk@maintech.de>. + +Due to time restraints, he had no time to further maintain the driver and +follow the mainline Linux changes in the SD/MMC stack. + +With his authorization, I have taken over the task of making it compliant to +the current mainline SD/MMC API and take care of the mainline kernel merge. + +After a potential kernel inclusion, we would co-maintain the driver. + +Acked-by: Thomas Kleffel <tk@maintech.de> +Signed-off-by: Harald Welte <laforge@gnumonks.org> +--- + arch/arm/mach-s3c2412/s3c2412.c | 3 + + arch/arm/mach-s3c2440/s3c2440.c | 3 + + arch/arm/mach-s3c2442/s3c2442.c | 4 + + drivers/mmc/host/Kconfig | 11 + + drivers/mmc/host/Makefile | 2 +- + drivers/mmc/host/s3cmci.c | 1419 +++++++++++++++++++++++++++++++ + drivers/mmc/host/s3cmci.h | 69 ++ + include/asm-arm/arch-s3c2410/mci.h | 13 + + include/asm-arm/arch-s3c2410/regs-sdi.h | 22 +- + 9 files changed, 1542 insertions(+), 4 deletions(-) + create mode 100644 drivers/mmc/host/s3cmci.c + create mode 100644 drivers/mmc/host/s3cmci.h + create mode 100644 include/asm-arm/arch-s3c2410/mci.h + +diff --git a/arch/arm/mach-s3c2412/s3c2412.c b/arch/arm/mach-s3c2412/s3c2412.c +index 98a0de9..ee4436d 100644 +--- a/arch/arm/mach-s3c2412/s3c2412.c ++++ b/arch/arm/mach-s3c2412/s3c2412.c +@@ -216,5 +216,8 @@ int __init s3c2412_init(void) + { + printk("S3C2412: Initialising architecture\n"); + ++ /* make sure SD/MMC driver can distinguish 2412 from 2410 */ ++ s3c_device_sdi.name = "s3c2412-sdi"; ++ + return sysdev_register(&s3c2412_sysdev); + } +diff --git a/arch/arm/mach-s3c2440/s3c2440.c b/arch/arm/mach-s3c2440/s3c2440.c +index 90e1da6..6c18cbd 100644 +--- a/arch/arm/mach-s3c2440/s3c2440.c ++++ b/arch/arm/mach-s3c2440/s3c2440.c +@@ -46,6 +46,9 @@ int __init s3c2440_init(void) + s3c_device_wdt.resource[1].start = IRQ_S3C2440_WDT; + s3c_device_wdt.resource[1].end = IRQ_S3C2440_WDT; + ++ /* make sure SD/MMC driver can distinguish 2440 from 2410 */ ++ s3c_device_sdi.name = "s3c2440-sdi"; ++ + /* register our system device for everything else */ + + return sysdev_register(&s3c2440_sysdev); +diff --git a/arch/arm/mach-s3c2442/s3c2442.c b/arch/arm/mach-s3c2442/s3c2442.c +index fbf8264..fbfb330 100644 +--- a/arch/arm/mach-s3c2442/s3c2442.c ++++ b/arch/arm/mach-s3c2442/s3c2442.c +@@ -21,6 +21,7 @@ + + #include <asm/plat-s3c24xx/s3c2442.h> + #include <asm/plat-s3c24xx/cpu.h> ++#include <asm/plat-s3c24xx/devs.h> + + static struct sys_device s3c2442_sysdev = { + .cls = &s3c2442_sysclass, +@@ -30,5 +31,8 @@ int __init s3c2442_init(void) + { + printk("S3C2442: Initialising architecture\n"); + ++ /* make sure SD/MMC driver can distinguish 2440 from 2410 */ ++ s3c_device_sdi.name = "s3c2440-sdi"; ++ + return sysdev_register(&s3c2442_sysdev); + } +diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig +index dead617..fd59856 100644 +--- a/drivers/mmc/host/Kconfig ++++ b/drivers/mmc/host/Kconfig +@@ -130,3 +130,14 @@ config MMC_SPI + + If unsure, or if your system has no SPI master driver, say N. + ++config MMC_S3C ++ tristate "Samsung S3C24xx SD/MMC Card Interface support" ++ depends on ARCH_S3C2410 && MMC ++ help ++ This selects a driver for the MCI interface found in ++ Samsung's S3C2410, S3C2412, S3C2440, S3C2442 CPUs. ++ If you have a board based on one of those and a MMC/SD ++ slot, say Y or M here. ++ ++ If unsure, say N. ++ +diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile +index 3877c87..071d256 100644 +--- a/drivers/mmc/host/Makefile ++++ b/drivers/mmc/host/Makefile +@@ -17,4 +17,4 @@ obj-$(CONFIG_MMC_OMAP) += omap.o + obj-$(CONFIG_MMC_AT91) += at91_mci.o + obj-$(CONFIG_MMC_TIFM_SD) += tifm_sd.o + obj-$(CONFIG_MMC_SPI) += mmc_spi.o +- ++obj-$(CONFIG_MMC_S3C) += s3cmci.o +diff --git a/drivers/mmc/host/s3cmci.c b/drivers/mmc/host/s3cmci.c +new file mode 100644 +index 0000000..edba055 +--- /dev/null ++++ b/drivers/mmc/host/s3cmci.c +@@ -0,0 +1,1419 @@ ++/* ++ * linux/drivers/mmc/s3cmci.h - Samsung S3C MCI driver ++ * ++ * Copyright (C) 2004-2006 maintech GmbH, Thomas Kleffel <tk@maintech.de> ++ * Copyright (C) 2007 Harald Welte <laforge@gnumonks.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. ++ */ ++ ++#include <linux/module.h> ++#include <linux/dma-mapping.h> ++#include <linux/clk.h> ++#include <linux/mmc/host.h> ++#include <linux/platform_device.h> ++#include <linux/irq.h> ++ ++#include <asm/dma.h> ++#include <asm/dma-mapping.h> ++ ++#include <asm/io.h> ++#include <asm/arch/regs-sdi.h> ++#include <asm/arch/regs-gpio.h> ++#include <asm/arch/mci.h> ++#include <asm/arch/dma.h> ++ ++#include "s3cmci.h" ++ ++#define DRIVER_NAME "s3c-mci" ++ ++enum dbg_channels { ++ dbg_err = (1 << 0), ++ dbg_debug = (1 << 1), ++ dbg_info = (1 << 2), ++ dbg_irq = (1 << 3), ++ dbg_sg = (1 << 4), ++ dbg_dma = (1 << 5), ++ dbg_pio = (1 << 6), ++ dbg_fail = (1 << 7), ++ dbg_conf = (1 << 8), ++}; ++ ++static const int dbgmap_err = dbg_err | dbg_fail; ++static const int dbgmap_info = dbg_info | dbg_conf; ++static const int dbgmap_debug = dbg_debug; ++ ++#define dbg(host, channels, args...) \ ++ do { \ ++ if (dbgmap_err & channels) \ ++ dev_err(&host->pdev->dev, args); \ ++ else if (dbgmap_info & channels) \ ++ dev_info(&host->pdev->dev, args);\ ++ else if (dbgmap_debug & channels) \ ++ dev_dbg(&host->pdev->dev, args); \ ++ } while (0) ++ ++#define RESSIZE(ressource) (((ressource)->end - (ressource)->start)+1) ++ ++static struct s3c2410_dma_client s3cmci_dma_client = { ++ .name = "s3c-mci", ++}; ++ ++static void finalize_request(struct s3cmci_host *host); ++static void s3cmci_send_request(struct mmc_host *mmc); ++static void s3cmci_reset(struct s3cmci_host *host); ++ ++#ifdef CONFIG_MMC_DEBUG ++static inline void dbg_dumpregs(struct s3cmci_host *host, char *prefix) ++{ ++ u32 con, pre, cmdarg, cmdcon, cmdsta, r0, r1, r2, r3, timer, bsize; ++ u32 datcon, datcnt, datsta, fsta, imask; ++ ++ con = readl(host->base + S3C2410_SDICON); ++ pre = readl(host->base + S3C2410_SDIPRE); ++ cmdarg = readl(host->base + S3C2410_SDICMDARG); ++ cmdcon = readl(host->base + S3C2410_SDICMDCON); ++ cmdsta = readl(host->base + S3C2410_SDICMDSTAT); ++ r0 = readl(host->base + S3C2410_SDIRSP0); ++ r1 = readl(host->base + S3C2410_SDIRSP1); ++ r2 = readl(host->base + S3C2410_SDIRSP2); ++ r3 = readl(host->base + S3C2410_SDIRSP3); ++ timer = readl(host->base + S3C2410_SDITIMER); ++ bsize = readl(host->base + S3C2410_SDIBSIZE); ++ datcon = readl(host->base + S3C2410_SDIDCON); ++ datcnt = readl(host->base + S3C2410_SDIDCNT); ++ datsta = readl(host->base + S3C2410_SDIDSTA); ++ fsta = readl(host->base + S3C2410_SDIFSTA); ++ imask = readl(host->base + host->sdiimsk); ++ ++ dbg(host, dbg_debug, "%s CON:[%08x] PRE:[%08x] TMR:[%08x]\n", ++ prefix, con, pre, timer); ++ ++ dbg(host, dbg_debug, "%s CCON:[%08x] CARG:[%08x] CSTA:[%08x]\n", ++ prefix, cmdcon, cmdarg, cmdsta); ++ ++ dbg(host, dbg_debug, "%s DCON:[%08x] FSTA:[%08x]" ++ " DSTA:[%08x] DCNT:[%08x]\n", ++ prefix, datcon, fsta, datsta, datcnt); ++ ++ dbg(host, dbg_debug, "%s R0:[%08x] R1:[%08x]" ++ " R2:[%08x] R3:[%08x]\n", ++ prefix, r0, r1, r2, r3); ++} ++ ++static void prepare_dbgmsg(struct s3cmci_host *host, struct mmc_command *cmd, ++ int stop) ++{ ++ snprintf(host->dbgmsg_cmd, 300, ++ "#%u%s op:CMD%d arg:0x%08x flags:0x08%x retries:%u", ++ host->ccnt, (stop?" (STOP)":""), cmd->opcode, ++ cmd->arg, cmd->flags, cmd->retries); ++ ++ if (cmd->data) { ++ snprintf(host->dbgmsg_dat, 300, ++ "#%u bsize:%u blocks:%u bytes:%u", ++ host->dcnt, cmd->data->blksz, ++ cmd->data->blocks, ++ cmd->data->blocks * cmd->data->blksz); ++ } else { ++ host->dbgmsg_dat[0] = '\0'; ++ } ++} ++ ++static void dbg_dumpcmd(struct s3cmci_host *host, struct mmc_command *cmd, ++ int fail) ++{ ++ unsigned int dbglvl = fail?dbg_fail:dbg_debug; ++ ++ if (!cmd) ++ return; ++ ++ if (cmd->error == 0) ++ dbg(host, dbglvl, "CMD[OK] %s R0:0x%08x\n", ++ host->dbgmsg_cmd, cmd->resp[0]); ++ else ++ dbg(host, dbglvl, "CMD[FAIL(%d)] %s Status:%s\n", ++ cmd->error, host->dbgmsg_cmd, host->status); ++ ++ if (!cmd->data) ++ return; ++ ++ if (cmd->data->error == 0) ++ dbg(host, dbglvl, "DAT[OK] %s\n", host->dbgmsg_dat); ++ else ++ dbg(host, dbglvl, "DAT[FAIL(%d)] %s DCNT:0x%08x\n", ++ cmd->data->error, host->dbgmsg_dat, ++ readl(host->base + S3C2410_SDIDCNT)); ++} ++#endif /* CONFIG_MMC_DEBUG */ ++ ++static inline u32 enable_imask(struct s3cmci_host *host, u32 imask) ++{ ++ u32 newmask; ++ ++ newmask = readl(host->base + host->sdiimsk); ++ newmask |= imask; ++ ++ writel(newmask, host->base + host->sdiimsk); ++ ++ return newmask; ++} ++ ++static inline u32 disable_imask(struct s3cmci_host *host, u32 imask) ++{ ++ u32 newmask; ++ ++ newmask = readl(host->base + host->sdiimsk); ++ newmask &= ~imask; ++ ++ writel(newmask, host->base + host->sdiimsk); ++ ++ return newmask; ++} ++ ++static inline void clear_imask(struct s3cmci_host *host) ++{ ++ writel(0, host->base + host->sdiimsk); ++} ++ ++static inline int get_data_buffer(struct s3cmci_host *host, ++ u32 *bytes, u8 **pointer) ++{ ++ struct scatterlist *sg; ++ ++ if (host->pio_active == XFER_NONE) ++ return -EINVAL; ++ ++ if ((!host->mrq) || (!host->mrq->data)) ++ return -EINVAL; ++ ++ if (host->pio_sgptr >= host->mrq->data->sg_len) { ++ dbg(host, dbg_debug, "no more buffers (%i/%i)\n", ++ host->pio_sgptr, host->mrq->data->sg_len); ++ return -EBUSY; ++ } ++ sg = &host->mrq->data->sg[host->pio_sgptr]; ++ ++ *bytes = sg->length; ++ *pointer = page_address(sg_page(sg)) + sg->offset; ++ ++ host->pio_sgptr++; ++ ++ dbg(host, dbg_sg, "new buffer (%i/%i)\n", ++ host->pio_sgptr, host->mrq->data->sg_len); ++ ++ return 0; ++} ++ ++#define FIFO_FILL(host) (readl(host->base + S3C2410_SDIFSTA) & \ ++ S3C2410_SDIFSTA_COUNTMASK) ++#define FIFO_FREE(host) (63 - (readl(host->base + S3C2410_SDIFSTA) \ ++ & S3C2410_SDIFSTA_COUNTMASK)) ++ ++static inline void do_pio_read(struct s3cmci_host *host) ++{ ++ int res; ++ int fifo; ++ void __iomem *from_ptr; ++ ++ /* write real prescaler to host, it might be set slow to fix */ ++ writel(host->prescaler, host->base + S3C2410_SDIPRE); ++ ++ from_ptr = host->base + host->sdidata; ++ ++ while ((fifo = FIFO_FILL(host))) { ++ if (!host->pio_bytes) { ++ res = get_data_buffer(host, &host->pio_bytes, ++ &host->pio_ptr); ++ if (res) { ++ host->pio_active = XFER_NONE; ++ host->complete_what = COMPLETION_FINALIZE; ++ ++ dbg(host, dbg_pio, "pio_read(): " ++ "complete (no more data).\n"); ++ return; ++ } ++ ++ dbg(host, dbg_pio, "pio_read(): new target: " ++ "[%i]@[%p]\n", host->pio_bytes, host->pio_ptr); ++ } ++ ++ dbg(host, dbg_pio, "pio_read(): fifo:[%02i] " ++ "buffer:[%03i] dcnt:[%08X]\n", fifo, host->pio_bytes, ++ readl(host->base + S3C2410_SDIDCNT)); ++ ++ if (fifo > host->pio_bytes) ++ fifo = host->pio_bytes; ++ ++ host->pio_bytes -= fifo; ++ host->pio_count += fifo; ++ ++ /* we might have an unaligned start of data */ ++ while (((unsigned long)host->pio_ptr & 0x03) && fifo) { ++ *(host->pio_ptr++) = readb(host->base + host->sdidata_b); ++ fifo--; ++ } ++ ++ /* and a major chunk of data in the middle */ ++ for (; fifo >= 4; fifo -=4) { ++ *(u32 *) host->pio_ptr = readl(from_ptr); ++ host->pio_ptr+= 4; ++ } ++ ++ /* as well as some non-modulo-four trailer */ ++ while (fifo) { ++ *(host->pio_ptr++) = readb(host->base + host->sdidata_b); ++ fifo--; ++ } ++ } ++ ++ if (!host->pio_bytes) { ++ res = get_data_buffer(host, &host->pio_bytes, &host->pio_ptr); ++ if (res) { ++ dbg(host, dbg_pio, "pio_read(): " ++ "complete (no more buffers).\n"); ++ host->pio_active = XFER_NONE; ++ host->complete_what = COMPLETION_FINALIZE; ++ ++ return; ++ } ++ } ++ ++ enable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF ++ | S3C2410_SDIIMSK_RXFIFOLAST); ++} ++ ++static inline void do_pio_write(struct s3cmci_host *host) ++{ ++ int res; ++ int fifo; ++ ++ void __iomem *to_ptr; ++ ++ to_ptr = host->base + host->sdidata; ++ ++ while ((fifo = FIFO_FREE(host))) { ++ if (!host->pio_bytes) { ++ res = get_data_buffer(host, &host->pio_bytes, ++ &host->pio_ptr); ++ if (res) { ++ dbg(host, dbg_pio, "pio_write(): " ++ "complete (no more data).\n"); ++ host->pio_active = XFER_NONE; ++ ++ return; ++ } ++ ++ dbg(host, dbg_pio, "pio_write(): " ++ "new source: [%i]@[%p]\n", ++ host->pio_bytes, host->pio_ptr); ++ ++ } ++ ++ if (fifo > host->pio_bytes) ++ fifo = host->pio_bytes; ++ ++ host->pio_bytes -= fifo; ++ host->pio_count += fifo; ++ ++ /* we might have an unaligned start of data */ ++ while (((unsigned long)host->pio_ptr & 0x03) && fifo) { ++ writeb(*(host->pio_ptr++), host->base + host->sdidata_b); ++ fifo--; ++ } ++ ++ /* and a major chunk of data in the middle */ ++ for (; fifo >= 4; fifo -=4) { ++ writel(*(u32 *) host->pio_ptr, to_ptr); ++ host->pio_ptr += 4; ++ } ++ ++ /* as well as some non-modulo-four trailer */ ++ while (fifo) { ++ writeb(*(host->pio_ptr++), host->base + host->sdidata_b); ++ fifo--; ++ } ++ } ++ ++ enable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF); ++} ++ ++static void pio_tasklet(unsigned long data) ++{ ++ struct s3cmci_host *host = (struct s3cmci_host *) data; ++ ++ disable_irq(host->irq); ++ ++ if (host->pio_active == XFER_WRITE) ++ do_pio_write(host); ++ ++ if (host->pio_active == XFER_READ) ++ do_pio_read(host); ++ ++ if (host->complete_what == COMPLETION_FINALIZE) { ++ clear_imask(host); ++ if (host->pio_active != XFER_NONE) { ++ dbg(host, dbg_err, "unfinished %s " ++ "- pio_count:[%u] pio_bytes:[%u]\n", ++ (host->pio_active == XFER_READ)?"read":"write", ++ host->pio_count, host->pio_bytes); ++ ++ host->mrq->data->error = -EIO; ++ } ++ ++ finalize_request(host); ++ } else ++ enable_irq(host->irq); ++} ++ ++/* ++ * ISR for SDI Interface IRQ ++ * Communication between driver and ISR works as follows: ++ * host->mrq points to current request ++ * host->complete_what tells ISR when the request is considered done ++ * COMPLETION_CMDSENT when the command was sent ++ * COMPLETION_RSPFIN when a response was received ++ * COMPLETION_XFERFINISH when the data transfer is finished ++ * COMPLETION_XFERFINISH_RSPFIN both of the above. ++ * host->complete_request is the completion-object the driver waits for ++ * ++ * 1) Driver sets up host->mrq and host->complete_what ++ * 2) Driver prepares the transfer ++ * 3) Driver enables interrupts ++ * 4) Driver starts transfer ++ * 5) Driver waits for host->complete_rquest ++ * 6) ISR checks for request status (errors and success) ++ * 6) ISR sets host->mrq->cmd->error and host->mrq->data->error ++ * 7) ISR completes host->complete_request ++ * 8) ISR disables interrupts ++ * 9) Driver wakes up and takes care of the request ++ * ++ * Note: "->error"-fields are expected to be set to 0 before the request ++ * was issued by mmc.c - therefore they are only set, when an error ++ * contition comes up ++ */ ++ ++static irqreturn_t s3cmci_irq(int irq, void *dev_id) ++{ ++ struct s3cmci_host *host; ++ struct mmc_command *cmd; ++ u32 mci_csta, mci_dsta, mci_fsta, mci_dcnt, mci_imsk; ++ u32 mci_cclear, mci_dclear; ++ unsigned long iflags; ++ int cardint = 0; ++ ++ host = (struct s3cmci_host *)dev_id; ++ ++ spin_lock_irqsave(&host->complete_lock, iflags); ++ ++ mci_csta = readl(host->base + S3C2410_SDICMDSTAT); ++ mci_dsta = readl(host->base + S3C2410_SDIDSTA); ++ mci_dcnt = readl(host->base + S3C2410_SDIDCNT); ++ mci_fsta = readl(host->base + S3C2410_SDIFSTA); ++ mci_imsk = readl(host->base + host->sdiimsk); ++ mci_cclear = 0; ++ mci_dclear = 0; ++ ++ if ((host->complete_what == COMPLETION_NONE) || ++ (host->complete_what == COMPLETION_FINALIZE)) { ++ host->status = "nothing to complete"; ++ clear_imask(host); ++ goto irq_out; ++ } ++ ++ if (!host->mrq) { ++ host->status = "no active mrq"; ++ clear_imask(host); ++ goto irq_out; ++ } ++ ++ cmd = host->cmd_is_stop?host->mrq->stop:host->mrq->cmd; ++ ++ if (!cmd) { ++ host->status = "no active cmd"; ++ clear_imask(host); ++ goto irq_out; ++ } ++ ++ if (!host->dodma) { ++ if ((host->pio_active == XFER_WRITE) && ++ (mci_fsta & S3C2410_SDIFSTA_TFDET)) { ++ ++ disable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF); ++ tasklet_schedule(&host->pio_tasklet); ++ host->status = "pio tx"; ++ } ++ ++ if ((host->pio_active == XFER_READ) && ++ (mci_fsta & S3C2410_SDIFSTA_RFDET)) { ++ ++ disable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF | ++ S3C2410_SDIIMSK_RXFIFOLAST); ++ ++ tasklet_schedule(&host->pio_tasklet); ++ host->status = "pio rx"; ++ } ++ } ++ ++ if (mci_csta & S3C2410_SDICMDSTAT_CMDTIMEOUT) { ++ cmd->error = -ETIMEDOUT; ++ host->status = "error: command timeout"; ++ goto fail_transfer; ++ } ++ ++ if (mci_csta & S3C2410_SDICMDSTAT_CMDSENT) { ++ 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) { ++ if (cmd->flags & MMC_RSP_CRC) { ++ if (host->mrq->cmd->flags & MMC_RSP_136) { ++ dbg(host, dbg_irq, "fixup for chip bug: " ++ "ignore CRC fail with long rsp\n"); ++ } else { ++ cmd->error = -EILSEQ; ++ host->status = "error: bad command crc"; ++ goto fail_transfer; ++ } ++ } ++ ++ mci_cclear |= S3C2410_SDICMDSTAT_CRCFAIL; ++ } ++ ++ if (mci_csta & S3C2410_SDICMDSTAT_RSPFIN) { ++ if (host->complete_what == COMPLETION_RSPFIN) { ++ host->status = "ok: command response received"; ++ goto close_transfer; ++ } ++ ++ if (host->complete_what == COMPLETION_XFERFINISH_RSPFIN) ++ host->complete_what = COMPLETION_XFERFINISH; ++ ++ mci_cclear |= S3C2410_SDICMDSTAT_RSPFIN; ++ } ++ ++ /* errors handled after this point are only relevant ++ when a data transfer is in progress */ ++ ++ if (!cmd->data) ++ goto clear_status_bits; ++ ++ /* Check for FIFO failure */ ++ if (host->is2440) { ++ if (mci_fsta & S3C2440_SDIFSTA_FIFOFAIL) { ++ host->mrq->data->error = -EIO; ++ host->status = "error: 2440 fifo failure"; ++ goto fail_transfer; ++ } ++ } else { ++ if (mci_dsta & S3C2410_SDIDSTA_FIFOFAIL) { ++ cmd->data->error = -EIO; ++ host->status = "error: fifo failure"; ++ goto fail_transfer; ++ } ++ } ++ ++ if (mci_dsta & S3C2410_SDIDSTA_RXCRCFAIL) { ++ cmd->data->error = -EILSEQ; ++ host->status = "error: bad data crc (outgoing)"; ++ goto fail_transfer; ++ } ++ ++ if (mci_dsta & S3C2410_SDIDSTA_CRCFAIL) { ++ cmd->data->error = -EIO; ++ host->status = "error: bad data crc (incoming)"; ++ goto fail_transfer; ++ } ++ ++ if (mci_dsta & S3C2410_SDIDSTA_DATATIMEOUT) { ++ cmd->data->error = -ETIMEDOUT; ++ host->status = "error: data timeout"; ++ goto fail_transfer; ++ } ++ ++ if (mci_dsta & S3C2410_SDIDSTA_XFERFINISH) { ++ if (host->complete_what == COMPLETION_XFERFINISH) { ++ host->status = "ok: data transfer completed"; ++ goto close_transfer; ++ } ++ ++ if (host->complete_what == COMPLETION_XFERFINISH_RSPFIN) ++ host->complete_what = COMPLETION_RSPFIN; ++ ++ mci_dclear |= S3C2410_SDIDSTA_XFERFINISH; ++ } ++ ++ if (mci_dsta & S3C2410_SDIDSTA_SDIOIRQDETECT) { ++ cardint = 1; ++ mci_dclear |= S3C2410_SDIDSTA_SDIOIRQDETECT; ++ } ++ ++clear_status_bits: ++ writel(mci_cclear, host->base + S3C2410_SDICMDSTAT); ++ writel(mci_dclear, host->base + S3C2410_SDIDSTA); ++ ++ goto irq_out; ++ ++fail_transfer: ++ host->pio_active = XFER_NONE; ++ ++close_transfer: ++ host->complete_what = COMPLETION_FINALIZE; ++ ++ clear_imask(host); ++ tasklet_schedule(&host->pio_tasklet); ++ ++ goto irq_out; ++ ++irq_out: ++ dbg(host, dbg_irq, "csta:0x%08x dsta:0x%08x " ++ "fsta:0x%08x dcnt:0x%08x status:%s.\n", ++ mci_csta, mci_dsta, mci_fsta, ++ mci_dcnt, host->status); ++ ++ spin_unlock_irqrestore(&host->complete_lock, iflags); ++ ++ /* We have to delay this as it calls back into the driver */ ++ if (cardint) ++ mmc_signal_sdio_irq(host->mmc); ++ ++ return IRQ_HANDLED; ++ ++} ++ ++/* ++ * ISR for the CardDetect Pin ++*/ ++ ++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, 500); ++ ++ return IRQ_HANDLED; ++} ++ ++void s3cmci_dma_done_callback(struct s3c2410_dma_chan *dma_ch, void *buf_id, ++ int size, enum s3c2410_dma_buffresult result) ++{ ++ unsigned long iflags; ++ u32 mci_csta, mci_dsta, mci_fsta, mci_dcnt; ++ struct s3cmci_host *host = (struct s3cmci_host *)buf_id; ++ ++ mci_csta = readl(host->base + S3C2410_SDICMDSTAT); ++ mci_dsta = readl(host->base + S3C2410_SDIDSTA); ++ mci_fsta = readl(host->base + S3C2410_SDIFSTA); ++ mci_dcnt = readl(host->base + S3C2410_SDIDCNT); ++ ++ if ((!host->mrq) || (!host->mrq) || (!host->mrq->data)) ++ return; ++ ++ if (!host->dmatogo) ++ return; ++ ++ 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); ++ ++ 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); ++ ++ goto out; ++ } ++ ++ dbg(host, dbg_dma, "DMA FINISHED Size:%i DSTA:%08x DCNT:%08x\n", ++ size, mci_dsta, mci_dcnt); ++ ++ host->complete_what = COMPLETION_FINALIZE; ++ ++out: ++ tasklet_schedule(&host->pio_tasklet); ++ spin_unlock_irqrestore(&host->complete_lock, iflags); ++ return; ++ ++ ++fail_request: ++ host->mrq->data->error = -EIO; ++ host->complete_what = COMPLETION_FINALIZE; ++ writel(0, host->base + host->sdiimsk); ++ goto out; ++ ++} ++ ++static void finalize_request(struct s3cmci_host *host) ++{ ++ struct mmc_request *mrq = host->mrq; ++ struct mmc_command *cmd = host->cmd_is_stop?mrq->stop:mrq->cmd; ++ int debug_as_failure = 0; ++ ++ if (host->complete_what != COMPLETION_FINALIZE) ++ return; ++ ++ if (!mrq) ++ return; ++ ++ if (cmd->data && (cmd->error == 0) && ++ (cmd->data->error == 0)) { ++ ++ if (host->dodma && (!host->dma_complete)) { ++ dbg(host, dbg_dma, "DMA Missing!\n"); ++ return; ++ } ++ } ++ ++ /* Read response */ ++ cmd->resp[0] = readl(host->base + S3C2410_SDIRSP0); ++ cmd->resp[1] = readl(host->base + S3C2410_SDIRSP1); ++ cmd->resp[2] = readl(host->base + S3C2410_SDIRSP2); ++ cmd->resp[3] = readl(host->base + S3C2410_SDIRSP3); ++ ++ /* reset clock speed, as it could still be set low for */ ++ writel(host->prescaler, host->base + S3C2410_SDIPRE); ++ ++ if (cmd->error) ++ debug_as_failure = 1; ++ ++ if (cmd->data && cmd->data->error) ++ debug_as_failure = 1; ++ ++#ifdef CONFIG_MMC_DEBUG ++ dbg_dumpcmd(host, cmd, debug_as_failure); ++#endif ++ /* 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); ++ ++ 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; ++ } ++ ++ /* If we have no data transfer we are finished here */ ++ if (!mrq->data) ++ goto request_done; ++ ++ /* Calulate the amout of bytes transfer, but only if there was ++ * no error */ ++ if (mrq->data->error == 0) ++ mrq->data->bytes_xfered = ++ (mrq->data->blocks * mrq->data->blksz); ++ else ++ mrq->data->bytes_xfered = 0; ++ ++ /* If we had an error while transfering data we flush the ++ * DMA channel and the fifo to clear out any garbage */ ++ if (mrq->data->error) { ++ if (host->dodma) ++ s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH); ++ ++ if (host->is2440) { ++ /* Clear failure register and reset fifo */ ++ writel(S3C2440_SDIFSTA_FIFORESET | ++ S3C2440_SDIFSTA_FIFOFAIL, ++ host->base + S3C2410_SDIFSTA); ++ } else { ++ u32 mci_con; ++ ++ /* reset fifo */ ++ mci_con = readl(host->base + S3C2410_SDICON); ++ mci_con |= S3C2410_SDICON_FIFORESET; ++ ++ writel(mci_con, host->base + S3C2410_SDICON); ++ } ++ } ++ ++request_done: ++ host->complete_what = COMPLETION_NONE; ++ host->mrq = NULL; ++ mmc_request_done(host->mmc, mrq); ++} ++ ++ ++void s3cmci_dma_setup(struct s3cmci_host *host, enum s3c2410_dmasrc source) ++{ ++ static int setup_ok; ++ static enum s3c2410_dmasrc last_source = -1; ++ ++ if (last_source == source) ++ return; ++ ++ last_source = source; ++ ++ s3c2410_dma_devconfig(host->dma, source, 3, ++ host->mem->start + host->sdidata); ++ ++ if (!setup_ok) { ++ s3c2410_dma_config(host->dma, 4, ++ (S3C2410_DCON_HWTRIG | S3C2410_DCON_CH0_SDI)); ++ s3c2410_dma_set_buffdone_fn(host->dma, ++ s3cmci_dma_done_callback); ++ s3c2410_dma_setflags(host->dma, S3C2410_DMAF_AUTOSTART); ++ setup_ok = 1; ++ } ++} ++ ++static void s3cmci_send_command(struct s3cmci_host *host, ++ struct mmc_command *cmd) ++{ ++ u32 ccon, imsk; ++ ++ imsk = S3C2410_SDIIMSK_CRCSTATUS | S3C2410_SDIIMSK_CMDTIMEOUT | ++ S3C2410_SDIIMSK_RESPONSEND | S3C2410_SDIIMSK_CMDSENT | ++ S3C2410_SDIIMSK_RESPONSECRC; ++ ++ enable_imask(host, imsk); ++ ++ if (cmd->data) ++ host->complete_what = COMPLETION_XFERFINISH_RSPFIN; ++ else if (cmd->flags & MMC_RSP_PRESENT) ++ host->complete_what = COMPLETION_RSPFIN; ++ else ++ host->complete_what = COMPLETION_CMDSENT; ++ ++ writel(cmd->arg, host->base + S3C2410_SDICMDARG); ++ ++ ccon = cmd->opcode & S3C2410_SDICMDCON_INDEX; ++ ccon |= S3C2410_SDICMDCON_SENDERHOST | S3C2410_SDICMDCON_CMDSTART; ++ ++ if (cmd->flags & MMC_RSP_PRESENT) ++ ccon |= S3C2410_SDICMDCON_WAITRSP; ++ ++ if (cmd->flags & MMC_RSP_136) ++ ccon |= S3C2410_SDICMDCON_LONGRSP; ++ ++ writel(ccon, host->base + S3C2410_SDICMDCON); ++} ++ ++static int s3cmci_setup_data(struct s3cmci_host *host, struct mmc_data *data) ++{ ++ u32 dcon, imsk, stoptries = 3; ++ ++ /* write DCON register */ ++ ++ if (!data) { ++ writel(0, host->base + S3C2410_SDIDCON); ++ return 0; ++ } ++ ++ while (readl(host->base + S3C2410_SDIDSTA) & ++ (S3C2410_SDIDSTA_TXDATAON | S3C2410_SDIDSTA_RXDATAON)) { ++ ++ dbg(host, dbg_err, ++ "mci_setup_data() transfer stillin progress.\n"); ++ ++ writel(S3C2410_SDIDCON_STOP, host->base + S3C2410_SDIDCON); ++ s3cmci_reset(host); ++ ++ if (0 == (stoptries--)) { ++#ifdef CONFIG_MMC_DEBUG ++ dbg_dumpregs(host, "DRF"); ++#endif ++ ++ return -EINVAL; ++ } ++ } ++ ++ dcon = data->blocks & S3C2410_SDIDCON_BLKNUM_MASK; ++ ++ if (host->dodma) ++ dcon |= S3C2410_SDIDCON_DMAEN; ++ ++ if (host->bus_width == MMC_BUS_WIDTH_4) ++ dcon |= S3C2410_SDIDCON_WIDEBUS; ++ ++ if (!(data->flags & MMC_DATA_STREAM)) ++ dcon |= S3C2410_SDIDCON_BLOCKMODE; ++ ++ if (data->flags & MMC_DATA_WRITE) { ++ dcon |= S3C2410_SDIDCON_TXAFTERRESP; ++ dcon |= S3C2410_SDIDCON_XFER_TXSTART; ++ } ++ ++ if (data->flags & MMC_DATA_READ) { ++ dcon |= S3C2410_SDIDCON_RXAFTERCMD; ++ dcon |= S3C2410_SDIDCON_XFER_RXSTART; ++ } ++ ++ if (host->is2440) { ++ dcon |= S3C2440_SDIDCON_DS_WORD; ++ dcon |= S3C2440_SDIDCON_DATSTART; ++ } ++ ++ 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; ++ ++ enable_imask(host, imsk); ++ ++ /* write TIMER register */ ++ ++ if (host->is2440) { ++ writel(0x007FFFFF, host->base + S3C2410_SDITIMER); ++ } else { ++ writel(0x0000FFFF, host->base + S3C2410_SDITIMER); ++ ++ /* FIX: set slow clock to prevent timeouts on read */ ++ if (data->flags & MMC_DATA_READ) ++ writel(0xFF, host->base + S3C2410_SDIPRE); ++ } ++ ++ return 0; ++} ++ ++static int s3cmci_prepare_pio(struct s3cmci_host *host, struct mmc_data *data) ++{ ++ int rw = (data->flags & MMC_DATA_WRITE)?1:0; ++ ++ if (rw != ((data->flags & MMC_DATA_READ)?0:1)) ++ return -EINVAL; ++ ++ host->pio_sgptr = 0; ++ host->pio_bytes = 0; ++ host->pio_count = 0; ++ host->pio_active = rw?XFER_WRITE:XFER_READ; ++ ++ if (rw) { ++ do_pio_write(host); ++ enable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF); ++ } else { ++ enable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF ++ | S3C2410_SDIIMSK_RXFIFOLAST); ++ } ++ ++ return 0; ++} ++ ++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; ++ ++ if (rw != ((data->flags & MMC_DATA_READ)?0:1)) ++ return -EINVAL; ++ ++ s3cmci_dma_setup(host, rw?S3C2410_DMASRC_MEM:S3C2410_DMASRC_HW); ++ s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH); ++ ++ 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; ++ ++ 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, ++ sg_dma_address(&data->sg[i]), ++ sg_dma_len(&data->sg[i])); ++ ++ res = s3c2410_dma_enqueue(host->dma, (void *) host, ++ sg_dma_address(&data->sg[i]), ++ sg_dma_len(&data->sg[i])); ++ ++ if (res) { ++ s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH); ++ return -EBUSY; ++ } ++ } ++ ++ s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_START); ++ ++ return 0; ++} ++ ++static void s3cmci_send_request(struct mmc_host *mmc) ++{ ++ struct s3cmci_host *host = mmc_priv(mmc); ++ struct mmc_request *mrq = host->mrq; ++ struct mmc_command *cmd = host->cmd_is_stop?mrq->stop:mrq->cmd; ++ ++ host->ccnt++; ++#ifdef CONFIG_MMC_DEBUG ++ prepare_dbgmsg(host, cmd, host->cmd_is_stop); ++#endif ++ /* clear command, data and fifo status registers; ++ * Fifo clear only necessary on 2440, but doesn't hurt on 2410 */ ++ writel(0xFFFFFFFF, host->base + S3C2410_SDICMDSTAT); ++ writel(0xFFFFFFFF, host->base + S3C2410_SDIDSTA); ++ writel(0xFFFFFFFF, host->base + S3C2410_SDIFSTA); ++ ++ if (cmd->data) { ++ int res; ++ res = s3cmci_setup_data(host, cmd->data); ++ ++ host->dcnt++; ++ ++ if (res) { ++ cmd->error = -EIO; ++ cmd->data->error = -EIO; ++ ++ mmc_request_done(mmc, mrq); ++ return; ++ } ++ ++ ++ if (host->dodma) ++ res = s3cmci_prepare_dma(host, cmd->data); ++ else ++ res = s3cmci_prepare_pio(host, cmd->data); ++ ++ if (res) { ++ cmd->error = -EIO; ++ cmd->data->error = -EIO; ++ ++ mmc_request_done(mmc, mrq); ++ return; ++ } ++ ++ } ++ ++ s3cmci_send_command(host, cmd); ++ enable_irq(host->irq); ++} ++ ++static void s3cmci_request(struct mmc_host *mmc, struct mmc_request *mrq) ++{ ++ struct s3cmci_host *host = mmc_priv(mmc); ++ ++ host->cmd_is_stop = 0; ++ host->mrq = mrq; ++ ++ s3cmci_send_request(mmc); ++} ++ ++static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) ++{ ++ struct s3cmci_host *host = mmc_priv(mmc); ++ u32 mci_psc, mci_con; ++ ++ /* Set power */ ++ mci_con = readl(host->base + S3C2410_SDICON); ++ 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); ++ ++ if (!host->is2440) ++ mci_con |= S3C2410_SDICON_FIFORESET; ++ ++ break; ++ ++ case MMC_POWER_OFF: ++ default: ++ s3c2410_gpio_setpin(S3C2410_GPE5, 0); ++ s3c2410_gpio_cfgpin(S3C2410_GPE5, S3C2410_GPE5_OUTP); ++ ++ if (host->pdata->set_power) ++ host->pdata->set_power(ios->power_mode, ios->vdd); ++ ++ if (host->is2440) ++ mci_con |= S3C2440_SDICON_SDRESET; ++ ++ break; ++ } ++ ++ /* 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; ++ ++ /* Set CLOCK_ENABLE */ ++ if (ios->clock) ++ mci_con |= S3C2410_SDICON_CLOCKTYPE; ++ else ++ mci_con &= ~S3C2410_SDICON_CLOCKTYPE; ++ ++ writel(mci_con, host->base + S3C2410_SDICON); ++ ++ if ((ios->power_mode == MMC_POWER_ON) ++ || (ios->power_mode == MMC_POWER_UP)) { ++ ++ dbg(host, dbg_conf, "running at %lukHz (requested: %ukHz).\n", ++ host->real_rate/1000, ios->clock/1000); ++ } else { ++ dbg(host, dbg_conf, "powered down.\n"); ++ } ++ ++ host->bus_width = ios->bus_width; ++ ++} ++ ++static void s3cmci_reset(struct s3cmci_host *host) ++{ ++ u32 con = readl(host->base + S3C2410_SDICON); ++ ++ con |= S3C2440_SDICON_SDRESET; ++ ++ writel(con, host->base + S3C2410_SDICON); ++} ++ ++static int s3cmci_get_ro(struct mmc_host *mmc) ++{ ++ struct s3cmci_host *host = mmc_priv(mmc); ++ ++ if (host->pdata->gpio_wprotect == 0) ++ return 0; ++ ++ return s3c2410_gpio_getpin(host->pdata->gpio_wprotect); ++} ++ ++static void s3cmci_enable_sdio_irq(struct mmc_host *mmc, int enable) ++{ ++ struct s3cmci_host *host = mmc_priv(mmc); ++ ++ if (enable) ++ enable_imask(host, S3C2410_SDIIMSK_SDIOIRQ); ++ else ++ disable_imask(host, S3C2410_SDIIMSK_SDIOIRQ); ++} ++ ++static struct mmc_host_ops s3cmci_ops = { ++ .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 = { ++ .do_dma = 0, ++ .gpio_detect = 0, ++ .set_power = NULL, ++ .ocr_avail = MMC_VDD_32_33, ++}; ++ ++static int s3cmci_probe(struct platform_device *pdev, int is2440) ++{ ++ struct mmc_host *mmc; ++ struct s3cmci_host *host; ++ ++ int ret; ++ ++ mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev); ++ if (!mmc) { ++ ret = -ENOMEM; ++ goto probe_out; ++ } ++ ++ host = mmc_priv(mmc); ++ host->mmc = mmc; ++ host->pdev = pdev; ++ ++ host->pdata = pdev->dev.platform_data; ++ if (!host->pdata) { ++ pdev->dev.platform_data = &s3cmci_def_pdata; ++ host->pdata = &s3cmci_def_pdata; ++ } ++ ++ spin_lock_init(&host->complete_lock); ++ tasklet_init(&host->pio_tasklet, pio_tasklet, (unsigned long) host); ++ if (is2440) { ++ host->is2440 = 1; ++ host->sdiimsk = S3C2440_SDIIMSK; ++ host->sdidata = S3C2440_SDIDATA; ++ host->sdidata_b = S3C2440_SDIDATA_BYTE; ++ host->clk_div = 1; ++ } else { ++ host->is2440 = 0; ++ host->sdiimsk = S3C2410_SDIIMSK; ++ host->sdidata = S3C2410_SDIDATA; ++ host->sdidata_b = S3C2410_SDIDATA_BYTE; ++ host->clk_div = 2; ++ } ++ host->dodma = host->pdata->do_dma; ++ host->complete_what = COMPLETION_NONE; ++ host->pio_active = XFER_NONE; ++ ++ host->dma = DMACH_SDI; ++ host->irq_cd = s3c2410_gpio_getirq(host->pdata->gpio_detect); ++ s3c2410_gpio_cfgpin(host->pdata->gpio_detect, S3C2410_GPIO_IRQ); ++ ++ host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!host->mem) { ++ dev_err(&pdev->dev, ++ "failed to get io memory region resouce.\n"); ++ ++ ret = -ENOENT; ++ goto probe_free_host; ++ } ++ ++ host->mem = request_mem_region(host->mem->start, ++ RESSIZE(host->mem), pdev->name); ++ ++ if (!host->mem) { ++ dev_err(&pdev->dev, "failed to request io memory region.\n"); ++ ret = -ENOENT; ++ goto probe_free_host; ++ } ++ ++ host->base = ioremap(host->mem->start, RESSIZE(host->mem)); ++ if (host->base == 0) { ++ dev_err(&pdev->dev, "failed to ioremap() io memory region.\n"); ++ ret = -EINVAL; ++ goto probe_free_mem_region; ++ } ++ ++ host->irq = platform_get_irq(pdev, 0); ++ if (host->irq == 0) { ++ dev_err(&pdev->dev, "failed to get interrupt resouce.\n"); ++ ret = -EINVAL; ++ goto probe_iounmap; ++ } ++ ++ if (request_irq(host->irq, s3cmci_irq, 0, DRIVER_NAME, host)) { ++ dev_err(&pdev->dev, "failed to request mci interrupt.\n"); ++ ret = -ENOENT; ++ goto probe_iounmap; ++ } ++ ++ disable_irq(host->irq); ++ ++ s3c2410_gpio_cfgpin(host->pdata->gpio_detect, S3C2410_GPIO_IRQ); ++ set_irq_type(host->irq_cd, IRQT_BOTHEDGE); ++ ++ if (request_irq(host->irq_cd, s3cmci_irq_cd, 0, DRIVER_NAME, host)) { ++ dev_err(&pdev->dev, ++ "failed to request card detect interrupt.\n"); ++ ++ ret = -ENOENT; ++ goto probe_free_irq; ++ } ++ ++ if (host->pdata->gpio_wprotect) ++ s3c2410_gpio_cfgpin(host->pdata->gpio_wprotect, ++ S3C2410_GPIO_INPUT); ++ ++ if (s3c2410_dma_request(host->dma, &s3cmci_dma_client, NULL)) { ++ dev_err(&pdev->dev, "unable to get DMA channel.\n"); ++ ret = -EBUSY; ++ goto probe_free_irq_cd; ++ } ++ ++ host->clk = clk_get(&pdev->dev, "sdi"); ++ if (IS_ERR(host->clk)) { ++ dev_err(&pdev->dev, "failed to find clock source.\n"); ++ ret = PTR_ERR(host->clk); ++ host->clk = NULL; ++ goto probe_free_host; ++ } ++ ++ ret = clk_enable(host->clk); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to enable clock source.\n"); ++ goto clk_free; ++ } ++ ++ host->clk_rate = clk_get_rate(host->clk); ++ ++ mmc->ops = &s3cmci_ops; ++ mmc->ocr_avail = host->pdata->ocr_avail; ++ 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; ++ ++ mmc->max_blk_count = 4095; ++ mmc->max_blk_size = 4095; ++ mmc->max_req_size = 4095 * 512; ++ mmc->max_seg_size = mmc->max_req_size; ++ ++ mmc->max_phys_segs = 128; ++ mmc->max_hw_segs = 128; ++ ++ dbg(host, dbg_debug, "probe: mode:%s mapped mci_base:%p irq:%u " ++ "irq_cd:%u dma:%u.\n", (host->is2440?"2440":""), ++ host->base, host->irq, host->irq_cd, host->dma); ++ ++ ret = mmc_add_host(mmc); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to add mmc host.\n"); ++ goto free_dmabuf; ++ } ++ ++ platform_set_drvdata(pdev, mmc); ++ ++ dev_info(&pdev->dev, "initialisation done.\n"); ++ return 0; ++ ++ free_dmabuf: ++ clk_disable(host->clk); ++ ++ clk_free: ++ clk_put(host->clk); ++ ++ probe_free_irq_cd: ++ free_irq(host->irq_cd, host); ++ ++ probe_free_irq: ++ free_irq(host->irq, host); ++ ++ probe_iounmap: ++ iounmap(host->base); ++ ++ probe_free_mem_region: ++ release_mem_region(host->mem->start, RESSIZE(host->mem)); ++ ++ probe_free_host: ++ mmc_free_host(mmc); ++ probe_out: ++ return ret; ++} ++ ++static int s3cmci_remove(struct platform_device *pdev) ++{ ++ struct mmc_host *mmc = platform_get_drvdata(pdev); ++ struct s3cmci_host *host = mmc_priv(mmc); ++ ++ mmc_remove_host(mmc); ++ clk_disable(host->clk); ++ clk_put(host->clk); ++ s3c2410_dma_free(host->dma, &s3cmci_dma_client); ++ free_irq(host->irq_cd, host); ++ free_irq(host->irq, host); ++ iounmap(host->base); ++ release_mem_region(host->mem->start, RESSIZE(host->mem)); ++ mmc_free_host(mmc); ++ ++ return 0; ++} ++ ++static int s3cmci_probe_2410(struct platform_device *dev) ++{ ++ return s3cmci_probe(dev, 0); ++} ++ ++static int s3cmci_probe_2412(struct platform_device *dev) ++{ ++ return s3cmci_probe(dev, 1); ++} ++ ++static int s3cmci_probe_2440(struct platform_device *dev) ++{ ++ return s3cmci_probe(dev, 1); ++} ++ ++#ifdef CONFIG_PM ++ ++static int s3cmci_suspend(struct platform_device *dev, pm_message_t state) ++{ ++ struct mmc_host *mmc = platform_get_drvdata(dev); ++ ++ return mmc_suspend_host(mmc, state); ++} ++ ++static int s3cmci_resume(struct platform_device *dev) ++{ ++ struct mmc_host *mmc = platform_get_drvdata(dev); ++ ++ return mmc_resume_host(mmc); ++} ++ ++#else /* CONFIG_PM */ ++#define s3cmci_suspend NULL ++#define s3cmci_resume NULL ++#endif /* CONFIG_PM */ ++ ++ ++static struct platform_driver s3cmci_driver_2410 = { ++ .driver.name = "s3c2410-sdi", ++ .probe = s3cmci_probe_2410, ++ .remove = s3cmci_remove, ++ .suspend = s3cmci_suspend, ++ .resume = s3cmci_resume, ++}; ++ ++static struct platform_driver s3cmci_driver_2412 = { ++ .driver.name = "s3c2412-sdi", ++ .probe = s3cmci_probe_2412, ++ .remove = s3cmci_remove, ++ .suspend = s3cmci_suspend, ++ .resume = s3cmci_resume, ++}; ++ ++static struct platform_driver s3cmci_driver_2440 = { ++ .driver.name = "s3c2440-sdi", ++ .probe = s3cmci_probe_2440, ++ .remove = s3cmci_remove, ++ .suspend = s3cmci_suspend, ++ .resume = s3cmci_resume, ++}; ++ ++ ++static int __init s3cmci_init(void) ++{ ++ platform_driver_register(&s3cmci_driver_2410); ++ platform_driver_register(&s3cmci_driver_2412); ++ platform_driver_register(&s3cmci_driver_2440); ++ return 0; ++} ++ ++static void __exit s3cmci_exit(void) ++{ ++ platform_driver_unregister(&s3cmci_driver_2410); ++ platform_driver_unregister(&s3cmci_driver_2412); ++ platform_driver_unregister(&s3cmci_driver_2440); ++} ++ ++module_init(s3cmci_init); ++module_exit(s3cmci_exit); ++ ++MODULE_DESCRIPTION("Samsung S3C MMC/SD Card Interface driver"); ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Thomas Kleffel <tk@maintech.de>"); +diff --git a/drivers/mmc/host/s3cmci.h b/drivers/mmc/host/s3cmci.h +new file mode 100644 +index 0000000..9644b45 +--- /dev/null ++++ b/drivers/mmc/host/s3cmci.h +@@ -0,0 +1,69 @@ ++/* ++ * linux/drivers/mmc/s3cmci.h - Samsung S3C MCI driver ++ * ++ * Copyright (C) 2004-2006 Thomas Kleffel, 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 version 2 as ++ * published by the Free Software Foundation. ++ */ ++ ++enum s3cmci_waitfor { ++ COMPLETION_NONE, ++ COMPLETION_FINALIZE, ++ COMPLETION_CMDSENT, ++ COMPLETION_RSPFIN, ++ COMPLETION_XFERFINISH, ++ COMPLETION_XFERFINISH_RSPFIN, ++}; ++ ++struct s3cmci_host { ++ struct platform_device *pdev; ++ struct s3c24xx_mci_pdata *pdata; ++ struct mmc_host *mmc; ++ struct resource *mem; ++ struct clk *clk; ++ void __iomem *base; ++ int irq; ++ int irq_cd; ++ int dma; ++ ++ unsigned long clk_rate; ++ unsigned long clk_div; ++ unsigned long real_rate; ++ u8 prescaler; ++ ++ int is2440; ++ unsigned sdiimsk; ++ unsigned sdidata; ++ unsigned sdidata_b; ++ int dodma; ++ ++ int dmatogo; ++ ++ struct mmc_request *mrq; ++ int cmd_is_stop; ++ ++ spinlock_t complete_lock; ++ enum s3cmci_waitfor complete_what; ++ ++ int dma_complete; ++ ++ u32 pio_sgptr; ++ u32 pio_bytes; ++ u32 pio_count; ++ u8 *pio_ptr; ++#define XFER_NONE 0 ++#define XFER_READ 1 ++#define XFER_WRITE 2 ++ u32 pio_active; ++ ++ int bus_width; ++ ++ char dbgmsg_cmd[301]; ++ char dbgmsg_dat[301]; ++ char *status; ++ ++ unsigned int ccnt, dcnt; ++ struct tasklet_struct pio_tasklet; ++}; +diff --git a/include/asm-arm/arch-s3c2410/mci.h b/include/asm-arm/arch-s3c2410/mci.h +new file mode 100644 +index 0000000..24e6cd1 +--- /dev/null ++++ b/include/asm-arm/arch-s3c2410/mci.h +@@ -0,0 +1,13 @@ ++#ifndef _ARCH_MCI_H ++#define _ARCH_MCI_H ++ ++struct s3c24xx_mci_pdata { ++ unsigned int gpio_detect; ++ unsigned int gpio_wprotect; ++ unsigned long ocr_avail; ++ unsigned int do_dma; ++ void (*set_power)(unsigned char power_mode, ++ unsigned short vdd); ++}; ++ ++#endif /* _ARCH_NCI_H */ +diff --git a/include/asm-arm/arch-s3c2410/regs-sdi.h b/include/asm-arm/arch-s3c2410/regs-sdi.h +index bb9d30b..4bb1ec5 100644 +--- a/include/asm-arm/arch-s3c2410/regs-sdi.h ++++ b/include/asm-arm/arch-s3c2410/regs-sdi.h +@@ -28,9 +28,17 @@ + #define S3C2410_SDIDCNT (0x30) + #define S3C2410_SDIDSTA (0x34) + #define S3C2410_SDIFSTA (0x38) ++ + #define S3C2410_SDIDATA (0x3C) ++#define S3C2410_SDIDATA_BYTE (0x3C) + #define S3C2410_SDIIMSK (0x40) + ++#define S3C2440_SDIDATA (0x40) ++#define S3C2440_SDIDATA_BYTE (0x48) ++#define S3C2440_SDIIMSK (0x3C) ++ ++#define S3C2440_SDICON_SDRESET (1<<8) ++#define S3C2440_SDICON_MMCCLOCK (1<<5) + #define S3C2410_SDICON_BYTEORDER (1<<4) + #define S3C2410_SDICON_SDIOIRQ (1<<3) + #define S3C2410_SDICON_RWAITEN (1<<2) +@@ -42,7 +50,8 @@ + #define S3C2410_SDICMDCON_LONGRSP (1<<10) + #define S3C2410_SDICMDCON_WAITRSP (1<<9) + #define S3C2410_SDICMDCON_CMDSTART (1<<8) +-#define S3C2410_SDICMDCON_INDEX (0xff) ++#define S3C2410_SDICMDCON_SENDERHOST (1<<6) ++#define S3C2410_SDICMDCON_INDEX (0x3f) + + #define S3C2410_SDICMDSTAT_CRCFAIL (1<<12) + #define S3C2410_SDICMDSTAT_CMDSENT (1<<11) +@@ -51,6 +60,9 @@ + #define S3C2410_SDICMDSTAT_XFERING (1<<8) + #define S3C2410_SDICMDSTAT_INDEX (0xff) + ++#define S3C2440_SDIDCON_DS_BYTE (0<<22) ++#define S3C2440_SDIDCON_DS_HALFWORD (1<<22) ++#define S3C2440_SDIDCON_DS_WORD (2<<22) + #define S3C2410_SDIDCON_IRQPERIOD (1<<21) + #define S3C2410_SDIDCON_TXAFTERRESP (1<<20) + #define S3C2410_SDIDCON_RXAFTERCMD (1<<19) +@@ -59,6 +71,7 @@ + #define S3C2410_SDIDCON_WIDEBUS (1<<16) + #define S3C2410_SDIDCON_DMAEN (1<<15) + #define S3C2410_SDIDCON_STOP (1<<14) ++#define S3C2440_SDIDCON_DATSTART (1<<14) + #define S3C2410_SDIDCON_DATMODE (3<<12) + #define S3C2410_SDIDCON_BLKNUM (0x7ff) + +@@ -68,6 +81,7 @@ + #define S3C2410_SDIDCON_XFER_RXSTART (2<<12) + #define S3C2410_SDIDCON_XFER_TXSTART (3<<12) + ++#define S3C2410_SDIDCON_BLKNUM_MASK (0xFFF) + #define S3C2410_SDIDCNT_BLKNUM_SHIFT (12) + + #define S3C2410_SDIDSTA_RDYWAITREQ (1<<10) +@@ -82,10 +96,12 @@ + #define S3C2410_SDIDSTA_TXDATAON (1<<1) + #define S3C2410_SDIDSTA_RXDATAON (1<<0) + ++#define S3C2440_SDIFSTA_FIFORESET (1<<16) ++#define S3C2440_SDIFSTA_FIFOFAIL (3<<14) /* 3 is correct (2 bits) */ + #define S3C2410_SDIFSTA_TFDET (1<<13) + #define S3C2410_SDIFSTA_RFDET (1<<12) +-#define S3C2410_SDIFSTA_TXHALF (1<<11) +-#define S3C2410_SDIFSTA_TXEMPTY (1<<10) ++#define S3C2410_SDIFSTA_TFHALF (1<<11) ++#define S3C2410_SDIFSTA_TFEMPTY (1<<10) + #define S3C2410_SDIFSTA_RFLAST (1<<9) + #define S3C2410_SDIFSTA_RFFULL (1<<8) + #define S3C2410_SDIFSTA_RFHALF (1<<7) +-- +1.5.6.3 + |