diff options
author | mokopatches <mokopatches@openmoko.org> | 2008-11-19 17:03:21 +0000 |
---|---|---|
committer | warmcat <andy@warmcat.com> | 2008-11-19 17:03:21 +0000 |
commit | 0a5a3e0ff3050d7baeeb40f50cf3ea7c174ef21e (patch) | |
tree | 5a884cbb5b51fb1d437b6ec0e07587a09c3421f5 | |
parent | ddb66e6fd40f9896da1b76c7234e4a1607ae8965 (diff) |
atheros_2_0_hcd.patch
-rw-r--r-- | drivers/sdio/hcd/Kconfig | 14 | ||||
-rw-r--r-- | drivers/sdio/hcd/Makefile | 1 | ||||
-rw-r--r-- | drivers/sdio/hcd/s3c24xx/Makefile | 2 | ||||
-rw-r--r-- | drivers/sdio/hcd/s3c24xx/s3c24xx_hcd.c | 1502 | ||||
-rw-r--r-- | drivers/sdio/hcd/s3c24xx/s3c24xx_hcd.h | 67 |
5 files changed, 1586 insertions, 0 deletions
diff --git a/drivers/sdio/hcd/Kconfig b/drivers/sdio/hcd/Kconfig new file mode 100644 index 00000000000..e4d8397414d --- /dev/null +++ b/drivers/sdio/hcd/Kconfig @@ -0,0 +1,14 @@ +config SDIO_S3C24XX + tristate "Samsung s3c24xx host controller" + depends on PLAT_S3C24XX && SDIO + default m + help + good luck. + +config SDIO_S3C24XX_DMA + bool "Samsung s3c24xx host controller DMA I/O" + depends on SDIO_S3C24XX + default n + help + good luck. + diff --git a/drivers/sdio/hcd/Makefile b/drivers/sdio/hcd/Makefile new file mode 100644 index 00000000000..e2401e2bd5c --- /dev/null +++ b/drivers/sdio/hcd/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_PLAT_S3C24XX) += s3c24xx/ diff --git a/drivers/sdio/hcd/s3c24xx/Makefile b/drivers/sdio/hcd/s3c24xx/Makefile new file mode 100644 index 00000000000..d2d099c910a --- /dev/null +++ b/drivers/sdio/hcd/s3c24xx/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_PLAT_S3C24XX) += sdio_s3c24xx_hcd.o +sdio_s3c24xx_hcd-objs := s3c24xx_hcd.o diff --git a/drivers/sdio/hcd/s3c24xx/s3c24xx_hcd.c b/drivers/sdio/hcd/s3c24xx/s3c24xx_hcd.c new file mode 100644 index 00000000000..3c4758b419e --- /dev/null +++ b/drivers/sdio/hcd/s3c24xx/s3c24xx_hcd.c @@ -0,0 +1,1502 @@ +/* + * s3c24xx_hcd.c - Samsung S3C MCI driver, Atheros SDIO API compatible. + * + * Copyright (C) 2007 by OpenMoko, Inc. + * Written by Samuel Ortiz <sameo@openedhand.com> + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/dma-mapping.h> +#include <linux/errno.h> +#include <linux/platform_device.h> +#include <linux/device.h> +#include <linux/clk.h> +#include <linux/fs.h> +#include <linux/ioport.h> +#include <linux/workqueue.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/seq_file.h> +#include <linux/debugfs.h> + +#include <linux/sdio/ctsystem.h> +#include <linux/sdio/sdio_busdriver.h> +#include <linux/sdio/sdio_lib.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/uaccess.h> +#include <asm/dma.h> +#include <asm/dma-mapping.h> + +#include <asm/arch/regs-sdi.h> +#include <asm/arch/regs-gpio.h> +#include <asm/arch/mci.h> +#include <asm/arch/gta02.h> + +#include "s3c24xx_hcd.h" + +#define DESCRIPTION "S3c24xx SDIO host controller" +#define AUTHOR "Samuel Ortiz <sameo@openedhand.com>" + +#define RESSIZE(ressource) (((ressource)->end - (ressource)->start)+1) + +static struct s3c2410_dma_client s3c24xx_hcd_dma_client = { + .name = "s3c24xx_hcd", +}; + +extern struct platform_device s3c_device_sdi; + +static void dump_request(struct s3c24xx_hcd_context * context) +{ + if (context->hcd.pCurrentRequest != NULL) { + DBG_PRINT(SDDBG_ERROR, ("Current Request Command:%d, ARG:0x%8.8X flags: 0x%04x\n", + context->hcd.pCurrentRequest->Command, context->hcd.pCurrentRequest->Argument, + context->hcd.pCurrentRequest->Flags)); + if (IS_SDREQ_DATA_TRANS(context->hcd.pCurrentRequest->Flags)) { + DBG_PRINT(SDDBG_ERROR, ("Data %s, Blocks: %d, BlockLen:%d Remaining: %d \n", + IS_SDREQ_WRITE_DATA(context->hcd.pCurrentRequest->Flags) ? "WRITE":"READ", + context->hcd.pCurrentRequest->BlockCount, + context->hcd.pCurrentRequest->BlockLen, + context->hcd.pCurrentRequest->DataRemaining)); + } + } +} + +static void s3c24xx_dump_regs(struct s3c24xx_hcd_context * context) +{ + u32 con, pre, cmdarg, cmdcon, cmdsta, r0, r1, r2, r3, timer, bsize; + u32 datcon, datcnt, datsta, fsta, imask; + + con = readl(context->base + S3C2410_SDICON); + pre = readl(context->base + S3C2410_SDIPRE); + cmdarg = readl(context->base + S3C2410_SDICMDARG); + cmdcon = readl(context->base + S3C2410_SDICMDCON); + cmdsta = readl(context->base + S3C2410_SDICMDSTAT); + r0 = readl(context->base + S3C2410_SDIRSP0); + r1 = readl(context->base + S3C2410_SDIRSP1); + r2 = readl(context->base + S3C2410_SDIRSP2); + r3 = readl(context->base + S3C2410_SDIRSP3); + timer = readl(context->base + S3C2410_SDITIMER); + bsize = readl(context->base + S3C2410_SDIBSIZE); + datcon = readl(context->base + S3C2410_SDIDCON); + datcnt = readl(context->base + S3C2410_SDIDCNT); + datsta = readl(context->base + S3C2410_SDIDSTA); + fsta = readl(context->base + S3C2410_SDIFSTA); + imask = readl(context->base + S3C2440_SDIIMSK); + + printk("SDICON: 0x%08x\n", con); + printk("SDIPRE: 0x%08x\n", pre); + printk("SDICmdArg: 0x%08x\n", cmdarg); + printk("SDICmdCon: 0x%08x\n", cmdcon); + printk("SDICmdSta: 0x%08x\n", cmdsta); + printk("SDIRSP0: 0x%08x\n", r0); + printk("SDIRSP1: 0x%08x\n", r1); + printk("SDIRSP2: 0x%08x\n", r2); + printk("SDIRSP3: 0x%08x\n", r3); + printk("SDIDTimer: 0x%08x\n", timer); + printk("SDIBSize: 0x%08x\n", bsize); + printk("SDIDatCon: 0x%08x\n", datcon); + printk("SDIDatCnt: 0x%08x\n", datcnt); + printk("SDIDatSta: 0x%08x\n", datsta); + printk("SDIFSta: 0x%08x\n", fsta); + printk("SDIIntMsk: 0x%08x\n", imask); +} + +static inline void s3c24xx_hcd_clear_imask(struct s3c24xx_hcd_context * context) +{ + if (context->int_sdio) { + writel(S3C2410_SDIIMSK_SDIOIRQ | S3C2410_SDIIMSK_READWAIT, + context->base + S3C2440_SDIIMSK); + } else { + writel(0, context->base + S3C2440_SDIIMSK); + } +} + +static inline void s3c24xx_hcd_set_imask(struct s3c24xx_hcd_context * context) +{ + writel(context->int_mask, context->base + S3C2440_SDIIMSK); +} + + +static inline void s3c24xx_hcd_clear_dsta(struct s3c24xx_hcd_context * context) +{ + u32 dsta; + + dsta = readl(context->base + S3C2410_SDIDSTA); + writel(dsta, context->base + S3C2410_SDIDSTA); +} + +static inline void s3c24xx_hcd_clear_csta(struct s3c24xx_hcd_context * context) +{ + u32 csta, csta_clear = 0; + + csta = readl(context->base + S3C2410_SDICMDSTAT); + + if (csta & S3C2410_SDICMDSTAT_CRCFAIL) + csta_clear |= S3C2410_SDICMDSTAT_CRCFAIL; + if (csta & S3C2410_SDICMDSTAT_CMDSENT) + csta_clear |= S3C2410_SDICMDSTAT_CMDSENT; + if (csta & S3C2410_SDICMDSTAT_CMDTIMEOUT) + csta_clear |= S3C2410_SDICMDSTAT_CMDTIMEOUT; + if (csta & S3C2410_SDICMDSTAT_RSPFIN) + csta_clear |= S3C2410_SDICMDSTAT_RSPFIN; + + writel(csta_clear, context->base + S3C2410_SDICMDSTAT); +} + +static inline void s3c24xx_hcd_clear_sta(struct s3c24xx_hcd_context * context) +{ + u32 csta, dsta, csta_clear = 0, dsta_clear = 0; + + csta = readl(context->base + S3C2410_SDICMDSTAT); + dsta = readl(context->base + S3C2410_SDIDSTA); + + if (csta & S3C2410_SDICMDSTAT_CRCFAIL) + csta_clear |= S3C2410_SDICMDSTAT_CRCFAIL; + if (csta & S3C2410_SDICMDSTAT_CMDSENT) + csta_clear |= S3C2410_SDICMDSTAT_CMDSENT; + if (csta & S3C2410_SDICMDSTAT_CMDTIMEOUT) + csta_clear |= S3C2410_SDICMDSTAT_CMDTIMEOUT; + if (csta & S3C2410_SDICMDSTAT_RSPFIN) + csta_clear |= S3C2410_SDICMDSTAT_RSPFIN; + + + if (dsta & S3C2410_SDIDSTA_RDYWAITREQ) + dsta_clear |= S3C2410_SDIDSTA_RDYWAITREQ; + if (dsta & S3C2410_SDIDSTA_SDIOIRQDETECT) + dsta_clear |= S3C2410_SDIDSTA_SDIOIRQDETECT; + if (dsta & S3C2410_SDIDSTA_FIFOFAIL) + dsta_clear |= S3C2410_SDIDSTA_FIFOFAIL; + if (dsta & S3C2410_SDIDSTA_CRCFAIL) + dsta_clear |= S3C2410_SDIDSTA_CRCFAIL; + if (dsta & S3C2410_SDIDSTA_RXCRCFAIL) + dsta_clear |= S3C2410_SDIDSTA_RXCRCFAIL; + if (dsta & S3C2410_SDIDSTA_DATATIMEOUT) + dsta_clear |= S3C2410_SDIDSTA_DATATIMEOUT; + if (dsta & S3C2410_SDIDSTA_XFERFINISH) + dsta_clear |= S3C2410_SDIDSTA_XFERFINISH; + if (dsta & S3C2410_SDIDSTA_BUSYFINISH) + dsta_clear |= S3C2410_SDIDSTA_BUSYFINISH; + if (dsta & S3C2410_SDIDSTA_SBITERR) + dsta_clear |= S3C2410_SDIDSTA_SBITERR; + + writel(csta_clear, context->base + S3C2410_SDICMDSTAT); + writel(dsta_clear, context->base + S3C2410_SDIDSTA); +} + +static inline void s3c24xx_hcd_fifo_reset(struct s3c24xx_hcd_context * context) +{ + u32 fsta; + + fsta = readl(context->base + S3C2410_SDIFSTA); + fsta |= S3C2440_SDIFSTA_FIFORESET; + writel(fsta, context->base + S3C2410_SDIFSTA); +} + +#if 0 +static void s3c24xx_hcd_reset(struct s3c24xx_hcd_context * context) +{ + u32 con, counter; + unsigned long flags; + + spin_lock_irqsave(&context->lock, flags); + + con = readl(context->base + S3C2410_SDICON); + + con |= S3C2440_SDICON_SDRESET; + + writel(con, context->base + S3C2410_SDICON); + + counter = 1000; + while(counter) { + con = readl(context->base + S3C2410_SDICON); + if (!(con & S3C2440_SDICON_SDRESET)) + break; + counter--; + mdelay(1); + } + + spin_unlock_irqrestore(&context->lock, flags); +} +#endif + +static SDIO_STATUS s3c24xx_hcd_clock_enable(struct s3c24xx_hcd_context * context, + unsigned int clock_rate, + unsigned char enable) +{ + SDIO_STATUS status = SDIO_STATUS_SUCCESS; + unsigned long flags; + u32 con; + + spin_lock_irqsave(&context->lock, flags); + + con = readl(context->base + S3C2410_SDICON); + + if (enable && clock_rate) { + con |= S3C2410_SDICON_CLOCKTYPE; + } else { + con &= ~S3C2410_SDICON_CLOCKTYPE; + } + + if (clock_rate) { + int prescaler; + + for (prescaler = 0; prescaler < 0xff; prescaler++) { + context->device.actual_clock_rate = + context->device.max_clock_rate / (prescaler + 1); + + if (context->device.actual_clock_rate <= clock_rate && + context->device.actual_clock_rate <= context->hcd.MaxClockRate) + break; + } + + if (prescaler == 0xff) + DBG_PRINT(SDDBG_ERROR , ("Using lowest clock rate\n")); + + writel(prescaler, context->base + S3C2410_SDIPRE); + } + + writel(con, context->base + S3C2410_SDICON); + + spin_unlock_irqrestore(&context->lock, flags); + + return SDIOErrorToOSError(status); +} + +static void s3c24xx_hcd_set_bus_mode(struct s3c24xx_hcd_context *context, + PSDCONFIG_BUS_MODE_DATA pMode) +{ + u32 datacon; + unsigned long flags; + + DBG_PRINT(SDDBG_TRACE , ("SetBusMode\n")); + + spin_lock_irqsave(&context->lock, flags); + datacon = readl(context->base + S3C2410_SDIDCON); + + switch (SDCONFIG_GET_BUSWIDTH(pMode->BusModeFlags)) { + case SDCONFIG_BUS_WIDTH_1_BIT: + context->bus_width = 1; + datacon &= S3C2410_SDIDCON_WIDEBUS; + break; + case SDCONFIG_BUS_WIDTH_4_BIT: + context->bus_width = 4; + datacon |= S3C2410_SDIDCON_WIDEBUS; + break; + default: + DBG_PRINT(SDDBG_TRACE , ("Unknown bus width: %d\n", SDCONFIG_GET_BUSWIDTH(pMode->BusModeFlags))); + break; + } + + writel(datacon, context->base + S3C2410_SDIDCON); + spin_unlock_irqrestore(&context->lock, flags); + + /* Set clock rate and enable clock */ + s3c24xx_hcd_clock_enable(context, pMode->ClockRate, 1); + pMode->ActualClockRate = context->device.actual_clock_rate; + + DBG_PRINT(SDDBG_TRACE , ("BUS mode: %d bits wide, actual clock rate: %d kHz (requested %d kHz)\n", + context->bus_width, pMode->ActualClockRate / 1000, pMode->ClockRate / 1000)); +} + + +static void s3c24xx_hcd_dma_complete(struct s3c24xx_hcd_context * context) +{ + u32 dsta, counter, i; + PSDREQUEST req; + SDIO_STATUS status = SDIO_STATUS_ERROR; + + req = GET_CURRENT_REQUEST(&context->hcd); + if (req == NULL) { + DBG_PRINT(SDDBG_ERROR, ("%s(): No current request\n", __FUNCTION__)); + return; + } + + if (context->complete == S3C24XX_HCD_DATA_READ) { + /* DMA READ completion */ + if (context->latest_xfer_size != req->DataRemaining) { + DBG_PRINT(SDDBG_ERROR, ("Unexpected read xfer size: %d <-> %d\n", + context->latest_xfer_size, req->DataRemaining)); + status = SDIO_STATUS_BUS_WRITE_ERROR; + } + + counter = 0; + dsta = readl(context->base + S3C2410_SDIDSTA); + while (!(dsta & S3C2410_SDIDSTA_XFERFINISH)) { + if (counter > 500) { + printk("read xfer timed out\n"); + s3c24xx_dump_regs(context); + memcpy(req->pDataBuffer, context->io_buffer, + req->BlockCount * req->BlockLen); + printk("Transfer: %dx%d\n", req->BlockCount, req->BlockLen); + for (i = 0; i < req->DataRemaining; i++) + printk("0x%x ", *(((char *)context->io_buffer) + i)); + printk("\n"); + status = SDIO_STATUS_BUS_READ_TIMEOUT; + goto out; + } + dsta = readl(context->base + S3C2410_SDIDSTA); + counter++; + mdelay(1); + }; + + dma_sync_single(NULL, context->io_buffer_dma, + req->BlockCount * req->BlockLen, DMA_BIDIRECTIONAL); + + writel(S3C2410_SDIDSTA_XFERFINISH, context->base + S3C2410_SDIDSTA); + + memcpy(req->pDataBuffer, context->io_buffer, + req->BlockCount * req->BlockLen); + + req->DataRemaining = 0; + status = SDIO_STATUS_SUCCESS; + + } else if (context->complete == S3C24XX_HCD_DATA_WRITE) { + /* DMA WRITE completion */ + if (context->latest_xfer_size != req->DataRemaining) { + DBG_PRINT(SDDBG_ERROR, ("Unexpected write xfer size: %d <-> %d\n", + context->latest_xfer_size, req->DataRemaining)); + status = SDIO_STATUS_BUS_WRITE_ERROR; + } + + dsta = readl(context->base + S3C2410_SDIDSTA); + counter = 0; + while (!(dsta & S3C2410_SDIDSTA_XFERFINISH)) { + if (counter > 500) { + printk("write xfer timed out\n"); + status = SDIO_STATUS_BUS_WRITE_ERROR; + goto out; + } + dsta = readl(context->base + S3C2410_SDIDSTA); + counter++; + mdelay(1); + }; + + writel(S3C2410_SDIDSTA_XFERFINISH, context->base + S3C2410_SDIDSTA); + req->DataRemaining = 0; + status = SDIO_STATUS_SUCCESS; + } + + out: + req->Status = status; +} + +static void s3c24xx_hcd_pio_complete(struct s3c24xx_hcd_context * context) +{ + u32 fsta, counter; + u8 *ptr; + int fifo_count; + PSDREQUEST req; + SDIO_STATUS status = SDIO_STATUS_ERROR; + + req = GET_CURRENT_REQUEST(&context->hcd); + if (req == NULL) { + DBG_PRINT(SDDBG_ERROR, ("%s(): No current request\n", __FUNCTION__)); + return; + } + + ptr = req->pDataBuffer; + + if (context->complete == S3C24XX_HCD_DATA_READ) { + counter = 0; + DBG_PRINT(SDDBG_TRACE, ("Data read...")); + do { + counter++; + fsta = readl(context->base + S3C2410_SDIFSTA); + mdelay(1); + if (counter > 1000) { + DBG_PRINT(SDDBG_ERROR, ("DATA read timeout\n")); + status = SDIO_STATUS_BUS_READ_TIMEOUT; + s3c24xx_dump_regs(context); + goto out; + } + } while(!(fsta & S3C2410_SDIFSTA_RFDET)); + DBG_PRINT(SDDBG_TRACE, ("RX detected\n")); + + while (1) { + counter = 0; + fifo_count = (readl(context->base + S3C2410_SDIFSTA) & S3C2410_SDIFSTA_COUNTMASK); + while (!fifo_count) { + counter++; + mdelay(1); + if (counter > 500) { + s3c24xx_dump_regs(context); + DBG_PRINT(SDDBG_ERROR, ("No more bytes in FIFO\n")); + goto out; + } + fifo_count = (readl(context->base + S3C2410_SDIFSTA) & S3C2410_SDIFSTA_COUNTMASK); + } + + if (fifo_count > req->DataRemaining) { + DBG_PRINT(SDDBG_ERROR, ("DATA read, fifo_count %d > expected %d\n", fifo_count, req->DataRemaining)); + fifo_count = req->DataRemaining; + } + + req->DataRemaining -= fifo_count; + while (fifo_count > 0) { + if (context->data_size == 4) + *(ptr) = readl(context->base + S3C2440_SDIDATA); + else if (context->data_size == 2) + *(ptr) = readw(context->base + S3C2440_SDIDATA); + else + *(ptr) = readb(context->base + S3C2440_SDIDATA); + + ptr += context->data_size; + fifo_count -= context->data_size; + + } + + if (!req->DataRemaining) { + /* We poll for xfer finish */ + counter = 0; + while (!(readl(context->base + S3C2410_SDIDSTA) + & S3C2410_SDIDSTA_XFERFINISH)) { + counter++; + mdelay(1); + if (counter > 500) { + DBG_PRINT(SDDBG_ERROR, ("RX XFERFINISH missing\n")); + s3c24xx_dump_regs(context); + break; + } + } + + status = SDIO_STATUS_SUCCESS; + goto out; + } + } + + } else if (context->complete == S3C24XX_HCD_DATA_WRITE) { + counter = 0; + DBG_PRINT(SDDBG_TRACE, ("Data write...")); + do { + counter++; + fsta = readl(context->base + S3C2410_SDIFSTA); + mdelay(1); + if (counter > 1000) { + DBG_PRINT(SDDBG_ERROR, ("DATA write timeout\n")); + status = SDIO_STATUS_BUS_WRITE_ERROR; + goto out; + break; + } + + } while(!(fsta & S3C2410_SDIFSTA_TFDET)); + DBG_PRINT(SDDBG_TRACE, ("TX detected\n")); + + while (1) { + counter = 0; + fifo_count = 63 - (readl(context->base + S3C2410_SDIFSTA) & S3C2410_SDIFSTA_COUNTMASK); + while (!fifo_count) { + counter++; + mdelay(1); + if (counter > 500) { + s3c24xx_dump_regs(context); + DBG_PRINT(SDDBG_ERROR, ("No more space in FIFO\n")); + goto out; + } + fifo_count = 63 - (readl(context->base + S3C2410_SDIFSTA) & S3C2410_SDIFSTA_COUNTMASK); + } + + if (fifo_count > req->DataRemaining) + fifo_count = req->DataRemaining; + + req->DataRemaining -= fifo_count; + + while (fifo_count > 0) { + if (context->data_size == 4) + writel(*(ptr), context->base + S3C2440_SDIDATA); + else if (context->data_size == 2) + writew(*(ptr), context->base + S3C2440_SDIDATA); + else + writeb(*(ptr), context->base + S3C2440_SDIDATA); + + ptr += context->data_size; + fifo_count -= context->data_size; + } + + if (!req->DataRemaining) { + /* We poll for xfer finish */ + counter = 0; + while (!(readl(context->base + S3C2410_SDIDSTA) + & S3C2410_SDIDSTA_XFERFINISH)) { + counter++; + mdelay(1); + if (counter > 500) { + DBG_PRINT(SDDBG_ERROR, ("RX XFERFINISH missing\n")); + s3c24xx_dump_regs(context); + break; + } + } + + status = SDIO_STATUS_SUCCESS; + goto out; + } + } + + } else { + DBG_PRINT(SDDBG_ERROR, ("Wrong context: %d\n", context->complete)); + } + + out: + req->Status = status; +} + +static void s3c24xx_hcd_io_work(struct work_struct *work) +{ + PSDREQUEST req; + SDIO_STATUS status = SDIO_STATUS_SUCCESS; + struct s3c24xx_hcd_context * context = + container_of(work, struct s3c24xx_hcd_context, io_work); + + req = GET_CURRENT_REQUEST(&context->hcd); + if (req == NULL) { + DBG_PRINT(SDDBG_ERROR, ("%s(): No current request\n", __FUNCTION__)); + return; + } + + if (req->Status == SDIO_STATUS_BUS_RESP_TIMEOUT) { + DBG_PRINT(SDDBG_ERROR, ("### TIMEOUT ###\n")); + s3c24xx_dump_regs(context); + goto out; + } + + if (context->complete == S3C24XX_HCD_NO_RESPONSE && + req->Status == SDIO_STATUS_SUCCESS) { + DBG_PRINT(SDDBG_TRACE, ("CMD done, Status: %d\n", req->Status)); + printk("CMD done, Status: %d\n", req->Status); + goto out; + } + + if ((context->complete == S3C24XX_HCD_RESPONSE_SHORT || + context->complete == S3C24XX_HCD_RESPONSE_LONG || + context->complete == S3C24XX_HCD_DATA_READ || + context->complete == S3C24XX_HCD_DATA_WRITE) && + req->Status == SDIO_STATUS_SUCCESS) { + u32 resp[4]; + + /* We need to copy the response data and send it over */ + resp[0] = readl(context->base + S3C2410_SDIRSP0); + resp[1] = readl(context->base + S3C2410_SDIRSP1); + resp[2] = readl(context->base + S3C2410_SDIRSP2); + resp[3] = readl(context->base + S3C2410_SDIRSP3); + + if (GET_SDREQ_RESP_TYPE(req->Flags) != SDREQ_FLAGS_RESP_R2) { + DBG_PRINT(SDDBG_TRACE, ("SHORT response: 0x%08x\n", resp[0])); + memcpy(&req->Response[1], (u8*)resp, 4); + req->Response[5] = (readl(context->base + S3C2410_SDICMDSTAT) & 0xff); + } else { + printk("LONG response: 0x%08x\n", resp[0]); + DBG_PRINT(SDDBG_TRACE, ("LONG response: 0x%08x\n", resp[0])); + memcpy(&req->Response[1], (u8*)resp, 16); + //req->Response[17] = (readl(context->base + S3C2410_SDICMDSTAT) & 0xff); + } + + /* There is a data stage */ + if (context->complete == S3C24XX_HCD_DATA_READ || + context->complete == S3C24XX_HCD_DATA_WRITE) { + status = SDIO_CheckResponse(&context->hcd, req, + SDHCD_CHECK_DATA_TRANS_OK); + + if (!SDIO_SUCCESS(status)) { + DBG_PRINT(SDDBG_ERROR, + ("Target not ready for data xfer\n")); + return; + } + + if (context->dma_en) { + dma_sync_single(NULL, context->io_buffer_dma, + req->BlockCount * req->BlockLen, DMA_BIDIRECTIONAL); + + s3c2410_dma_ctrl(context->dma_channel, S3C2410_DMAOP_START); + + wait_for_completion(&context->dma_complete); + + s3c24xx_hcd_dma_complete(context); + } else { + s3c24xx_hcd_pio_complete(context); + } + } + } + + out: + s3c24xx_hcd_clear_sta(context); + s3c24xx_hcd_clear_imask(context); + + writel(0, context->base + S3C2410_SDICMDARG); + writel(0, context->base + S3C2410_SDICMDCON); + + SDIO_HandleHcdEvent(&context->hcd, EVENT_HCD_TRANSFER_DONE); +} + +static void s3c24xx_hcd_irq_work(struct work_struct *work) +{ + struct s3c24xx_hcd_context * context = + container_of(work, struct s3c24xx_hcd_context, irq_work); + + disable_irq(context->io_irq); + + writel(S3C2410_SDIDSTA_SDIOIRQDETECT, context->base + S3C2410_SDIDSTA); + + SDIO_HandleHcdEvent(&context->hcd, EVENT_HCD_SDIO_IRQ_PENDING); + + enable_irq(context->io_irq); +} + +void s3c24xx_hcd_dma_done(struct s3c2410_dma_chan *dma_ch, void *buf_id, + int size, enum s3c2410_dma_buffresult result) +{ + struct s3c24xx_hcd_context * context = + (struct s3c24xx_hcd_context *) buf_id; + + if (result != S3C2410_RES_OK) { + DBG_PRINT(SDDBG_ERROR, ("%s(): DMA xfer failed: %d\n", __FUNCTION__, result)); + s3c24xx_dump_regs(context); + } + + context->latest_xfer_size = size; + complete(&context->dma_complete); +} + +static int s3c24xx_hcd_prepare_dma(struct s3c24xx_hcd_context * context) +{ + PSDREQUEST req; + SDIO_STATUS status = SDIO_STATUS_SUCCESS; + int read = 0, hwcfg = S3C2410_DISRCC_INC | S3C2410_DISRCC_APB; + enum s3c2410_dmasrc source = S3C2410_DMASRC_MEM; + + req = GET_CURRENT_REQUEST(&context->hcd); + if (req == NULL) { + DBG_PRINT(SDDBG_ERROR, ("%s(): No current request\n", __FUNCTION__)); + status = SDIO_STATUS_ERROR; + } + + if (!context->dma_en) { + DBG_PRINT(SDDBG_ERROR, ("%s(): DMA is disabled\n", __FUNCTION__)); + status = SDIO_STATUS_ERROR; + } + + if (!IS_SDREQ_DATA_TRANS(req->Flags)) { + DBG_PRINT(SDDBG_ERROR, ("%s(): No data to transfer\n", __FUNCTION__)); + status = SDIO_STATUS_ERROR; + } + + if(!IS_SDREQ_WRITE_DATA(req->Flags)) { + read = 1; + source = S3C2410_DMASRC_HW; + hwcfg = S3C2410_DISRCC_APB | 1; + } else { + memcpy(context->io_buffer, req->pDataBuffer, req->DataRemaining); + dma_sync_single(NULL, context->io_buffer_dma, + req->BlockCount * req->BlockLen, DMA_BIDIRECTIONAL); + + } + + s3c2410_dma_devconfig(context->dma_channel, source, hwcfg, + (unsigned long)context->mem->start + S3C2440_SDIDATA); + + s3c2410_dma_config(context->dma_channel, context->data_size, + S3C2410_DCON_CH0_SDI); + //(S3C2410_DCON_HWTRIG | S3C2410_DCON_CH0_SDI)); + + s3c2410_dma_set_buffdone_fn(context->dma_channel, s3c24xx_hcd_dma_done); + +// s3c2410_dma_setflags(context->dma_channel, S3C2410_DMAF_AUTOSTART); + + s3c2410_dma_ctrl(context->dma_channel, S3C2410_DMAOP_FLUSH); + + s3c2410_dma_enqueue(context->dma_channel, context, + context->io_buffer_dma, + req->DataRemaining); + + return 0; +} + + +static irqreturn_t s3c24xx_hcd_irq(int irq, void *dev_id) +{ + u32 cmdsta, dsta, fsta; + unsigned long flags, trace = 0; + PSDREQUEST req; + struct s3c24xx_hcd_context * context = + (struct s3c24xx_hcd_context *)dev_id; + + spin_lock_irqsave(&context->lock, flags); + + s3c24xx_hcd_clear_imask(context); + + cmdsta = readl(context->base + S3C2410_SDICMDSTAT); + dsta = readl(context->base + S3C2410_SDIDSTA); + fsta = readl(context->base + S3C2410_SDIFSTA); + + context->cmdsta = cmdsta; + context->dsta = dsta; + context->fsta = fsta; + + s3c24xx_hcd_clear_csta(context); + + if (dsta & S3C2410_SDIDSTA_SDIOIRQDETECT) { + writel(S3C2410_SDIDSTA_SDIOIRQDETECT, context->base + S3C2410_SDIDSTA); + + if (context->int_sdio) { + u32 imask; + + context->int_sdio = 0; + + imask = readl(context->base + S3C2440_SDIIMSK); + imask &= ~S3C2410_SDIIMSK_SDIOIRQ; + writel(imask, context->base + S3C2440_SDIIMSK); + schedule_work(&context->irq_work); + } + } + + req = GET_CURRENT_REQUEST(&context->hcd); + if (req == NULL) { + DBG_PRINT(SDDBG_TRACE, ("%s(): No current request\n", __FUNCTION__)); + goto out; + } + + if (cmdsta & S3C2410_SDICMDSTAT_CMDTIMEOUT) { + DBG_PRINT(SDDBG_ERROR, ("TIMEOUT\n")); + printk("TIMEOUT\n"); + req->Status = SDIO_STATUS_BUS_RESP_TIMEOUT; + writel(S3C2410_SDICMDSTAT_CMDTIMEOUT, context->base + S3C2410_SDICMDSTAT); + schedule_work(&context->io_work); + } + + if (cmdsta & S3C2410_SDICMDSTAT_CRCFAIL) { + DBG_PRINT(SDDBG_ERROR, ("CRCFAIL 0x%x\n", cmdsta)); + printk("CRCFAIL 0x%x\n", cmdsta); + req->Status = SDIO_STATUS_BUS_RESP_CRC_ERR; + dump_request(context); + writel(S3C2410_SDICMDSTAT_CRCFAIL, context->base + S3C2410_SDICMDSTAT); + schedule_work(&context->io_work); + } + + + if (cmdsta & S3C2410_SDICMDSTAT_CMDSENT) { + writel(S3C2410_SDICMDSTAT_CMDSENT, context->base + S3C2410_SDICMDSTAT); + + if (context->complete == S3C24XX_HCD_NO_RESPONSE) { + req->Status = SDIO_STATUS_SUCCESS; + trace = 1; + schedule_work(&context->io_work); + } + } + + if (cmdsta & S3C2410_SDICMDSTAT_RSPFIN || + (IS_SDREQ_WRITE_DATA(req->Flags) && (fsta & S3C2410_SDIFSTA_TFDET)) || + (!IS_SDREQ_WRITE_DATA(req->Flags) && (fsta & S3C2410_SDIFSTA_RFDET))) { + + writel(S3C2410_SDICMDSTAT_RSPFIN, context->base + S3C2410_SDICMDSTAT); + + if (context->complete == S3C24XX_HCD_RESPONSE_SHORT || + context->complete == S3C24XX_HCD_RESPONSE_LONG || + context->complete == S3C24XX_HCD_DATA_READ || + context->complete == S3C24XX_HCD_DATA_WRITE) { + req->Status = SDIO_STATUS_SUCCESS; + if (trace) + printk("IO work already scheduled, cmdsta: 0x%x\n", cmdsta); + schedule_work(&context->io_work); + } + } + + out: + if (dsta & S3C2410_SDIDSTA_RDYWAITREQ) { + printk("S3C2410_SDIDSTA_RDYWAITREQ\n"); + //writel(S3C2410_SDIDSTA_RDYWAITREQ, context->base + S3C2410_SDIDSTA); + } + + if (dsta & S3C2410_SDIDSTA_FIFOFAIL) { + printk("S3C2410_SDIDSTA_FIFOFAIL\n"); + writel(S3C2410_SDIDSTA_FIFOFAIL, context->base + S3C2410_SDIDSTA); + } + + if (dsta & S3C2410_SDIDSTA_CRCFAIL) { + printk("S3C2410_SDIDSTA_CRCFAIL\n"); + writel(S3C2410_SDIDSTA_CRCFAIL, context->base + S3C2410_SDIDSTA); + } + + if (dsta & S3C2410_SDIDSTA_RXCRCFAIL) { + printk("S3C2410_SDIDSTA_RXCRCFAIL\n"); + writel(S3C2410_SDIDSTA_RXCRCFAIL, context->base + S3C2410_SDIDSTA); + } + + if (dsta & S3C2410_SDIDSTA_DATATIMEOUT) { + printk("S3C2410_SDIDSTA_DATATIMEOUT\n"); + writel(S3C2410_SDIDSTA_DATATIMEOUT, context->base + S3C2410_SDIDSTA); + } + + if (dsta & S3C2410_SDIDSTA_BUSYFINISH) { + printk("S3C2410_SDIDSTA_BUSYFINISH\n"); + writel(S3C2410_SDIDSTA_BUSYFINISH, context->base + S3C2410_SDIDSTA); + } + + if (dsta & S3C2410_SDIDSTA_SBITERR) { + printk("S3C2410_SDIDSTA_SBIERR\n"); + writel(S3C2410_SDIDSTA_SBITERR, context->base + S3C2410_SDIDSTA); + } + + spin_unlock_irqrestore(&context->lock, flags); + return IRQ_HANDLED; +} + + +SDIO_STATUS s3c24xx_hcd_config(PSDHCD hcd, PSDCONFIG config) +{ + u32 con, imsk; + SDIO_STATUS status = SDIO_STATUS_SUCCESS; + PSDCONFIG_SDIO_INT_CTRL_DATA int_data; + struct s3c24xx_hcd_context * context = (struct s3c24xx_hcd_context *)hcd->pContext; + + switch (GET_SDCONFIG_CMD(config)){ + case SDCONFIG_GET_WP: + DBG_PRINT(SDDBG_TRACE, ("config GET_WP\n")); + *((SDCONFIG_WP_VALUE *)config->pData) = 0; + status = SDIO_STATUS_SUCCESS; + break; + case SDCONFIG_SEND_INIT_CLOCKS: + DBG_PRINT(SDDBG_TRACE, ("config SEND_INIT_CLOCKS\n")); + + /* We stop/start the clock */ + con = readl(context->base + S3C2410_SDICON); + + con &= ~S3C2410_SDICON_CLOCKTYPE; + writel(con, context->base + S3C2410_SDICON); + + mdelay(100); + + con |= S3C2410_SDICON_CLOCKTYPE; + writel(con, context->base + S3C2410_SDICON); + + mdelay(100); + + status = SDIO_STATUS_SUCCESS; + break; + case SDCONFIG_SDIO_INT_CTRL: + DBG_PRINT(SDDBG_TRACE, ("config SDIO_INT_CTRL\n")); + int_data = GET_SDCONFIG_CMD_DATA(PSDCONFIG_SDIO_INT_CTRL_DATA, config); + + if (int_data->SlotIRQEnable & + (IRQ_DETECT_1_BIT | IRQ_DETECT_4_BIT | IRQ_DETECT_MULTI_BLK) ) { + imsk = readl(context->base + S3C2440_SDIIMSK); + + if (int_data->SlotIRQEnable) { + printk("SDIO_INT_CTRL enable IRQ\n"); + DBG_PRINT(SDDBG_TRACE, ("SDIO_INT_CTRL enable IRQ\n")); + context->int_sdio = 1; + imsk |= S3C2410_SDIIMSK_SDIOIRQ; + writel(imsk, context->base + S3C2440_SDIIMSK); + } else { + printk("SDIO_INT_CTRL disable IRQ\n"); + DBG_PRINT(SDDBG_TRACE, ("SDIO_INT_CTRL disable IRQ\n")); + context->int_sdio = 0; + imsk &= ~S3C2410_SDIIMSK_SDIOIRQ; + writel(imsk, context->base + S3C2440_SDIIMSK); + } + } + status = SDIO_STATUS_SUCCESS; + break; + case SDCONFIG_SDIO_REARM_INT: + DBG_PRINT(SDDBG_TRACE, ("config SDIO_REARM_INT\n")); + + context->int_sdio = 1; + imsk = readl(context->base + S3C2440_SDIIMSK); + imsk |= S3C2410_SDIIMSK_SDIOIRQ; + writel(imsk, context->base + S3C2440_SDIIMSK); + + status = SDIO_STATUS_SUCCESS; + break; + case SDCONFIG_FUNC_CHANGE_BUS_MODE: + case SDCONFIG_BUS_MODE_CTRL: + s3c24xx_hcd_set_bus_mode(context, (PSDCONFIG_BUS_MODE_DATA)(config->pData)); + DBG_PRINT(SDDBG_TRACE, ("config BUS_MODE_CTRL\n")); + status = SDIO_STATUS_SUCCESS; + break; + case SDCONFIG_POWER_CTRL: + DBG_PRINT(SDDBG_TRACE, ("config POWER_CTRL\n")); + status = SDIO_STATUS_SUCCESS; + break; + case SDCONFIG_GET_HCD_DEBUG: + DBG_PRINT(SDDBG_TRACE, ("config GET_HCD_DEBUG\n")); + status = SDIO_STATUS_SUCCESS; + break; + case SDCONFIG_SET_HCD_DEBUG: + DBG_PRINT(SDDBG_TRACE, ("config SET_HCD_DEBUG\n")); + status = SDIO_STATUS_SUCCESS; + break; + default: + /* invalid request */ + DBG_PRINT(SDDBG_ERROR, ("%s() - unsupported command: 0x%X\n", + __FUNCTION__, GET_SDCONFIG_CMD(config))); + status = SDIO_STATUS_INVALID_PARAMETER; + } + + return SDIOErrorToOSError(status); +} + + +SDIO_STATUS s3c24xx_hcd_request(PSDHCD hcd) +{ + SDIO_STATUS status = SDIO_STATUS_PENDING; + PSDREQUEST req; + u32 cmdcon, imask; + unsigned long flags; + struct s3c24xx_hcd_context * context = + (struct s3c24xx_hcd_context *)hcd->pContext; + + req = GET_CURRENT_REQUEST(hcd); + DBG_ASSERT(req != NULL); + + if (req->Flags & SDREQ_FLAGS_DATA_SHORT_TRANSFER) + printk("### SHORT TRANSFER ###\n"); + + spin_lock_irqsave(&context->lock, flags); + + /* Clear command, data and fifo status registers */ + writel(0xFFFFFFFF, context->base + S3C2410_SDICMDSTAT); + writel(0xFFFFFFFF, context->base + S3C2410_SDIDSTA); + writel(0xFFFFFFFF, context->base + S3C2410_SDIFSTA); + + /* Enabling irqs */ + imask = S3C2410_SDIIMSK_READWAIT; + + cmdcon = readl(context->base + S3C2410_SDICMDCON); + + switch (GET_SDREQ_RESP_TYPE(req->Flags)) { + case SDREQ_FLAGS_NO_RESP: + cmdcon &= ~S3C2410_SDICMDCON_WAITRSP; + context->complete = S3C24XX_HCD_NO_RESPONSE; + imask |= S3C2410_SDIIMSK_CMDSENT; + break; + case SDREQ_FLAGS_RESP_R1: + case SDREQ_FLAGS_RESP_R1B: + case SDREQ_FLAGS_RESP_R3: + case SDREQ_FLAGS_RESP_SDIO_R4: + case SDREQ_FLAGS_RESP_SDIO_R5: + case SDREQ_FLAGS_RESP_R6: + cmdcon &= ~S3C2410_SDICMDCON_LONGRSP; + cmdcon |= S3C2410_SDICMDCON_WAITRSP; + context->complete = S3C24XX_HCD_RESPONSE_SHORT; + imask |= S3C2410_SDIIMSK_CRCSTATUS | S3C2410_SDIIMSK_RESPONSEND + | S3C2410_SDIIMSK_CMDTIMEOUT | S3C2410_SDIIMSK_RESPONSECRC; + break; + case SDREQ_FLAGS_RESP_R2: + cmdcon |= S3C2410_SDICMDCON_LONGRSP; + cmdcon |= S3C2410_SDICMDCON_WAITRSP; + context->complete = S3C24XX_HCD_RESPONSE_LONG; + imask |= S3C2410_SDIIMSK_CRCSTATUS | S3C2410_SDIIMSK_RESPONSEND + | S3C2410_SDIIMSK_CMDTIMEOUT | S3C2410_SDIIMSK_RESPONSECRC; + break; + + } + + /* There is a data part */ + if (IS_SDREQ_DATA_TRANS(req->Flags)) { + u32 dcon = 0; + + if (readl(context->base + S3C2410_SDIDSTA) & + (S3C2410_SDIDSTA_TXDATAON | S3C2410_SDIDSTA_RXDATAON)) { + printk("##### DATA ON: 0x%x ######\n", readl(context->base + S3C2410_SDIDSTA)); + } + + /* Setting timer */ + writel(0x7fffff, context->base + S3C2410_SDITIMER); + + /* Block size */ + writel(req->BlockLen, context->base + S3C2410_SDIBSIZE); + /* Number of blocks */ + dcon |= (0xfff & req->BlockCount); + + if (context->bus_width == 4) + dcon |= S3C2410_SDIDCON_WIDEBUS; + + req->DataRemaining = req->BlockCount * req->BlockLen; + + /* Set data size, and start the transfer */ + dcon |= S3C2410_SDIDCON_IRQPERIOD; + if (!(req->DataRemaining % 4)) { + context->data_size = 4; + dcon |= S3C2440_SDIDCON_DS_WORD; + } else if (!(req->DataRemaining % 2)) { + context->data_size = 2; + dcon |= S3C2440_SDIDCON_DS_HALFWORD; + } else { + context->data_size = 1; + dcon |= S3C2440_SDIDCON_DS_BYTE; + } + +#ifdef CONFIG_SDIO_S3C24XX_DMA + if (req->DataRemaining > 16) { + context->dma_en = 1; + } else +#endif + { + context->dma_en = 0; + context->data_size = 1; + dcon |= S3C2440_SDIDCON_DS_BYTE; + } + + if (context->dma_en) { + dcon |= S3C2410_SDIDCON_DMAEN; + s3c24xx_hcd_prepare_dma(context); + } + + if (IS_SDREQ_WRITE_DATA(req->Flags)) { + /* Data write */ + DBG_PRINT(SDDBG_TRACE, ("Start data write, block count=%d, block size=%d\n", + req->BlockCount, req->BlockLen)); + + /* Data configuration: transmit after resp, block mode*/ + dcon |= S3C2410_SDIDCON_TXAFTERRESP | S3C2410_SDIDCON_BLOCKMODE; + + /* This is a write */ + dcon |= S3C2410_SDIDCON_XFER_TXSTART; + + imask |= S3C2410_SDIIMSK_TXFIFOHALF | S3C2410_SDIIMSK_TXFIFOEMPTY | + S3C2410_SDIIMSK_FIFOFAIL | S3C2410_SDIIMSK_DATACRC | + S3C2410_SDIIMSK_DATATIMEOUT | S3C2410_SDIIMSK_DATAFINISH; + + context->complete = S3C24XX_HCD_DATA_WRITE; + } else { + /* Data read */ + DBG_PRINT(SDDBG_TRACE, ("Start data read, block count=%d, block size=%d\n", + req->BlockCount, req->BlockLen)); + + /* Data configuration: receive after cmd, block mode*/ + dcon |= S3C2410_SDIDCON_RXAFTERCMD | S3C2410_SDIDCON_BLOCKMODE; + + /* This is a read */ + dcon |= S3C2410_SDIDCON_XFER_RXSTART; + + imask |= S3C2410_SDIIMSK_RXFIFOHALF | S3C2410_SDIIMSK_RXFIFOLAST | + S3C2410_SDIIMSK_FIFOFAIL | S3C2410_SDIIMSK_DATACRC | + S3C2410_SDIIMSK_DATATIMEOUT | S3C2410_SDIIMSK_DATAFINISH; + + context->complete = S3C24XX_HCD_DATA_READ; + } + + dcon |= S3C2440_SDIDCON_DATSTART; + + writel(dcon, context->base + S3C2410_SDIDCON); + + cmdcon |= S3C2410_SDICMDCON_WITHDATA; + + } else { + cmdcon &= ~S3C2410_SDICMDCON_WITHDATA; + } + + cmdcon |= req->Command & S3C2410_SDICMDCON_INDEX; + cmdcon |= S3C2410_SDICMDCON_SENDERHOST | S3C2410_SDICMDCON_CMDSTART; + + req->Status = SDIO_STATUS_PENDING; + + if (context->int_sdio) + imask |= S3C2410_SDIIMSK_SDIOIRQ; + context->int_mask = imask; + writel(imask, context->base + S3C2440_SDIIMSK); + writel(req->Argument, context->base + S3C2410_SDICMDARG); + writel(cmdcon, context->base + S3C2410_SDICMDCON); + + spin_unlock_irqrestore(&context->lock, flags); + + return status; +} + +static int s3c24xx_hcd_hw_init(struct s3c24xx_hcd_context * context) +{ + SDIO_STATUS status = SDIO_STATUS_SUCCESS; + u32 con, datacon; + + /* Clock */ + context->device.clock = clk_get(NULL, "sdi"); + if (IS_ERR(context->device.clock)) { + DBG_PRINT(SDDBG_ERROR, ("Couldn't get clock\n")); + status = PTR_ERR(context->device.clock); + context->device.clock = NULL; + return status; + } + + status = clk_enable(context->device.clock); + if (SDIO_IS_ERROR(status)) { + DBG_PRINT(SDDBG_ERROR, ("Couldn't get clock\n")); + return SDIOErrorToOSError(status); + } + + context->device.max_clock_rate = clk_get_rate(context->device.clock); + context->device.actual_clock_rate = context->device.max_clock_rate; + + /* I/O */ + context->mem = request_mem_region(context->mem->start, + RESSIZE(context->mem), context->description); + + if (!context->mem) { + DBG_PRINT(SDDBG_ERROR, ("Failed to request io memory region\n")); + status = -ENOENT; + goto out_disable_clock; + } + + context->base = ioremap(context->mem->start, RESSIZE(context->mem)); + if (context->base == 0) { + DBG_PRINT(SDDBG_ERROR, ("failed to ioremap() io memory region.\n")); + status = -EINVAL; + goto out_free_mem_region; + } + + /* IRQ */ +#if 0 + context->cd_irq = s3c2410_gpio_getirq(GTA02v1_GPIO_nSD_DETECT); + s3c2410_gpio_cfgpin(GTA02v1_GPIO_nSD_DETECT, S3C2410_GPIO_IRQ); + + if (request_irq(context->cd_irq, s3c24xx_hcd_cd_irq, 0, context->description, context)) { + DBG_PRINT(SDDBG_ERROR, ("failed to request card detect interrupt.\n")); + status = -ENOENT; + goto out_unmap_mem_region; + } +#endif + + if (request_irq(context->io_irq, s3c24xx_hcd_irq, 0, context->description, context)) { + DBG_PRINT(SDDBG_ERROR, ("failed to request mci interrupt.\n")); + status = -ENOENT; + goto out_unmap_mem_region; + } + + + /* DMA */ + context->io_buffer_size = 4 * 4096; + context->io_buffer = dma_alloc_writecombine(&context->pdev->dev, + context->io_buffer_size, + &context->io_buffer_dma, + GFP_KERNEL | GFP_DMA); + + if (context->io_buffer == NULL) { + DBG_PRINT(SDDBG_ERROR, ("failed to allocate DMA buffer\n")); + status = -ENOMEM; + goto out_free_irq; + + } + + if (s3c2410_dma_request(context->dma_channel, &s3c24xx_hcd_dma_client, NULL)) { + DBG_PRINT(SDDBG_ERROR, ("unable to get DMA channel.\n")); + status = -ENOENT; + goto out_free_dma; + } + + + /* Set multiplexing */ + 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); + + con = readl(context->base + S3C2410_SDICON); + con |= S3C2410_SDICON_SDIOIRQ; + writel(con, context->base + S3C2410_SDICON); + + datacon = readl(context->base + S3C2410_SDIDCON); + datacon |= S3C2410_SDIDCON_WIDEBUS; + writel(datacon, context->base + S3C2410_SDIDCON); + + printk("S3c24xx SDIO: IRQ:%d Detect IRQ:%d DMA channel:%d base@0x%p PCLK@%ld kHz\n", + context->io_irq, context->cd_irq, context->dma_channel, context->base, + context->device.max_clock_rate/1000); + + return SDIOErrorToOSError(status); + + out_free_dma: + dma_free_writecombine(&context->pdev->dev,context->io_buffer_size, + context->io_buffer, context->io_buffer_dma); + + out_free_irq: + free_irq(context->io_irq, context); + + out_unmap_mem_region: + iounmap(context->base); + + out_free_mem_region: + release_mem_region(context->mem->start, RESSIZE(context->mem)); + + out_disable_clock: + clk_disable(context->device.clock); + + return SDIOErrorToOSError(status); +} + +static void s3c24xx_hcd_hw_cleanup(struct s3c24xx_hcd_context * context) +{ + clk_disable(context->device.clock); + free_irq(context->io_irq, context); + iounmap(context->base); + release_mem_region(context->mem->start, RESSIZE(context->mem)); + dma_free_writecombine(&context->pdev->dev,context->io_buffer_size, + context->io_buffer, context->io_buffer_dma); +} + +static int s3c24xx_hcd_pnp_probe(struct pnp_dev *pBusDevice, const struct pnp_device_id *pId) +{ + SDIO_STATUS status = SDIO_STATUS_SUCCESS; + + status = s3c24xx_hcd_hw_init(&hcd_context); + if (SDIO_IS_ERROR(status)) { + DBG_PRINT(SDDBG_ERROR, ("HW Init failed\n")); + return SDIOErrorToOSError(status); + } + + status = SDIO_RegisterHostController(&hcd_context.hcd); + if (SDIO_IS_ERROR(status)) { + DBG_PRINT(SDDBG_ERROR, ("Host registration failed\n")); + s3c24xx_hcd_hw_cleanup(&hcd_context); + return SDIOErrorToOSError(status); + } + + /* Our card is built-in, we force the attachement event */ + SDIO_HandleHcdEvent(&hcd_context.hcd, EVENT_HCD_ATTACH); + + return 0; +} + +static void s3c24xx_hcd_pnp_remove(struct pnp_dev *pBusDevice) +{ +} + +/* the driver context data */ +struct s3c24xx_hcd_context hcd_context = { + .description = DESCRIPTION, + .hcd.pName = "sdio_s3c24xx", + .hcd.Version = CT_SDIO_STACK_VERSION_CODE, + .hcd.pModule = THIS_MODULE, + /* builtin card, 4 bits bus */ + .hcd.Attributes = SDHCD_ATTRIB_BUS_4BIT | SDHCD_ATTRIB_BUS_1BIT | SDHCD_ATTRIB_MULTI_BLK_IRQ, + .hcd.SlotNumber = 0, + .hcd.MaxSlotCurrent = 500, /* 1/2 amp */ + .hcd.SlotVoltageCaps = SLOT_POWER_3_3V, /* 3.3V */ + .hcd.SlotVoltagePreferred = SLOT_POWER_3_3V, /* 3.3V */ + .hcd.MaxClockRate = 25000000, + .hcd.MaxBytesPerBlock = 0xfff, /* 0 - 4095 */ + .hcd.MaxBlocksPerTrans = 0xfff, /* 0 - 4095 */ + .hcd.pContext = &hcd_context, + .hcd.pRequest = s3c24xx_hcd_request, + .hcd.pConfigure = s3c24xx_hcd_config, + .device.pnp_device.name = "sdio_s3c24xx_hcd", + .device.pnp_driver.name = "sdio_s3c24xx_hcd", + .device.pnp_driver.probe = s3c24xx_hcd_pnp_probe, + .device.pnp_driver.remove = s3c24xx_hcd_pnp_remove, +}; + +static int s3c24xx_hcd_probe(struct platform_device * pdev) +{ + SDIO_STATUS status = SDIO_STATUS_SUCCESS; + struct resource *r = NULL; + + printk("S3c2440 SDIO Host controller\n"); + + hcd_context.pdev = pdev; + + hcd_context.mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (hcd_context.mem == NULL) { + DBG_PRINT(SDDBG_ERROR, ("No memory region\n")); + status = SDIO_STATUS_NO_RESOURCES; + goto out; + } + + hcd_context.io_irq = platform_get_irq(pdev, 0); + if (hcd_context.io_irq == 0) { + DBG_PRINT(SDDBG_ERROR, ("No IRQ\n")); + status = SDIO_STATUS_NO_RESOURCES; + goto out; + } + + r = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (r == NULL) { + DBG_PRINT(SDDBG_ERROR, ("No DMA channel\n")); + status = SDIO_STATUS_NO_RESOURCES; + goto out; + } + hcd_context.dma_channel = r->start; + hcd_context.dma_en = 0; + + hcd_context.int_sdio = 0; + + spin_lock_init(&hcd_context.lock); + + init_completion(&hcd_context.dma_complete); + init_completion(&hcd_context.xfer_complete); + + INIT_WORK(&hcd_context.io_work, s3c24xx_hcd_io_work); + INIT_WORK(&hcd_context.irq_work, s3c24xx_hcd_irq_work); + + mdelay(100); + + status = SDIO_BusAddOSDevice(&hcd_context.device.dma, + &hcd_context.device.pnp_driver, + &hcd_context.device.pnp_device); + + out: + + return SDIOErrorToOSError(status); +} + +/* + * module cleanup + */ +static int s3c24xx_hcd_remove(struct platform_device * pdev) { + printk("S3C2440 SDIO host controller unloaded\n"); + SDIO_BusRemoveOSDevice(&hcd_context.device.pnp_driver, &hcd_context.device.pnp_device); + + return 0; +} + +static struct platform_driver s3c24xx_hcd_sdio = +{ + .driver.name = "s3c24xx-sdio", + .probe = s3c24xx_hcd_probe, + .remove = s3c24xx_hcd_remove, +}; + +#ifdef CONFIG_DEBUG_FS +static struct dentry *debugfs_dir; + +static int s3c24xx_hcd_debugfs_show(struct seq_file *s, void *data) +{ + PSDREQUEST req; + u32 con, pre, cmdarg, cmdcon, cmdsta, r0, r1, r2, r3, timer, bsize; + u32 datcon, datcnt, datsta, fsta, imask; + struct s3c24xx_hcd_context * context = &hcd_context; + + + con = readl(context->base + S3C2410_SDICON); + pre = readl(context->base + S3C2410_SDIPRE); + cmdarg = readl(context->base + S3C2410_SDICMDARG); + cmdcon = readl(context->base + S3C2410_SDICMDCON); + cmdsta = readl(context->base + S3C2410_SDICMDSTAT); + r0 = readl(context->base + S3C2410_SDIRSP0); + r1 = readl(context->base + S3C2410_SDIRSP1); + r2 = readl(context->base + S3C2410_SDIRSP2); + r3 = readl(context->base + S3C2410_SDIRSP3); + timer = readl(context->base + S3C2410_SDITIMER); + bsize = readl(context->base + S3C2410_SDIBSIZE); + datcon = readl(context->base + S3C2410_SDIDCON); + datcnt = readl(context->base + S3C2410_SDIDCNT); + datsta = readl(context->base + S3C2410_SDIDSTA); + fsta = readl(context->base + S3C2410_SDIFSTA); + imask = readl(context->base + S3C2440_SDIIMSK); + + seq_printf(s, "SDICON: 0x%08x\n", con); + seq_printf(s, "SDIPRE: 0x%08x\n", pre); + seq_printf(s, "SDICmdArg: 0x%08x\n", cmdarg); + seq_printf(s, "SDICmdCon: 0x%08x\n", cmdcon); + seq_printf(s, "SDICmdSta: 0x%08x\n", cmdsta); + seq_printf(s, "SDIRSP0: 0x%08x\n", r0); + seq_printf(s, "SDIRSP1: 0x%08x\n", r1); + seq_printf(s, "SDIRSP2: 0x%08x\n", r2); + seq_printf(s, "SDIRSP3: 0x%08x\n", r3); + seq_printf(s, "SDIDTimer: 0x%08x\n", timer); + seq_printf(s, "SDIBSize: 0x%08x\n", bsize); + seq_printf(s, "SDIDatCon: 0x%08x\n", datcon); + seq_printf(s, "SDIDatCnt: 0x%08x\n", datcnt); + seq_printf(s, "SDIDatSta: 0x%08x\n", datsta); + seq_printf(s, "SDIFSta: 0x%08x\n", fsta); + seq_printf(s, "SDIIntMsk: 0x%08x\n", imask); + seq_printf(s, "\n"); + + seq_printf(s, "Current REQ: \n"); + req = GET_CURRENT_REQUEST(&context->hcd); + if (req == NULL) { + seq_printf(s, " No current request\n"); + } else { + seq_printf(s, " Command: %d\n", req->Command); + seq_printf(s, " Args: 0x%x\n", req->Argument); + seq_printf(s, " Flags: 0x%x\n", req->Flags); + seq_printf(s, " %d blocks x %d bytes\n", req->BlockCount, req->BlockLen); + seq_printf(s, " %d bytes remaining\n", req->DataRemaining); + } + + seq_printf(s, "Context: \n"); + seq_printf(s, " INT mask: 0x%x\n", context->int_mask); + seq_printf(s, " sdio INT: %d\n", context->int_sdio); + seq_printf(s, " cmdsta: 0x%x\n", context->cmdsta); + seq_printf(s, " dsta: 0x%x\n", context->dsta); + seq_printf(s, " fsta: 0x%x\n", context->fsta); + + return 0; +} + +static int s3c24xx_hcd_debugfs_open(struct inode *inode, + struct file *file) +{ + return single_open(file, s3c24xx_hcd_debugfs_show, NULL); +} + +static const struct file_operations s3c24xx_hcd_debugfs_fops = { + .open = s3c24xx_hcd_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + + +static int s3c24xx_debugfs_init(struct s3c24xx_hcd_context * context) +{ + debugfs_dir = debugfs_create_dir("s3c24xx_sdio", NULL); + + debugfs_create_file("registers", 0444, debugfs_dir, + (void *)context, + &s3c24xx_hcd_debugfs_fops); + + return 0; +} + +#else + +static int s3c24xx_debugfs_init(struct s3c24xx_hcd_context * context) +{ + return 0; +} + +#endif + +static int __init s3c24xx_hcd_init(void) +{ + int ret; + + ret = s3c24xx_debugfs_init(&hcd_context); + if (ret) { + printk("%s(): debugfs init failed\n", __FUNCTION__); + } + + platform_driver_register(&s3c24xx_hcd_sdio); + + return 0; +} + +static void __exit s3c24xx_hcd_exit(void) +{ + platform_driver_unregister(&s3c24xx_hcd_sdio); +} + + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION(DESCRIPTION); +MODULE_AUTHOR(AUTHOR); + +module_init(s3c24xx_hcd_init); +module_exit(s3c24xx_hcd_exit); diff --git a/drivers/sdio/hcd/s3c24xx/s3c24xx_hcd.h b/drivers/sdio/hcd/s3c24xx/s3c24xx_hcd.h new file mode 100644 index 00000000000..eb262fc4b25 --- /dev/null +++ b/drivers/sdio/hcd/s3c24xx/s3c24xx_hcd.h @@ -0,0 +1,67 @@ +#ifndef __SDIO_S3C24XX_HCD_H___ +#define __SDIO_S3C24XX_HCD_H___ + +#define S3C24XX_HCD_NO_RESPONSE 1 +#define S3C24XX_HCD_RESPONSE_SHORT 2 +#define S3C24XX_HCD_RESPONSE_LONG 3 +#define S3C24XX_HCD_DATA_READ 4 +#define S3C24XX_HCD_DATA_WRITE 5 + +struct s3c24xx_hcd_device { + OS_PNPDEVICE pnp_device; /* the OS device for this HCD */ + OS_PNPDRIVER pnp_driver; /* the OS driver for this HCD */ + SDDMA_DESCRIPTION dma; + struct clk * clock; + unsigned long max_clock_rate; + unsigned long actual_clock_rate; +}; + + +/* driver wide data, this driver only supports one device, + * so we include the per device data here also */ +struct s3c24xx_hcd_context { + PTEXT description; /* human readable device decsription */ + SDHCD hcd; /* HCD description for bus driver */ + struct s3c24xx_hcd_device device; /* the single device's info */ + struct platform_device *pdev; + struct resource *mem; + void __iomem *base; + UINT32 io_irq; + UINT32 cd_irq; + BOOL card_inserted; /* card inserted flag */ + BOOL cmd_processed; /* command phase was processed */ + UINT32 fifo_depth; /* FIFO depth for the bus mode */ + BOOL irq_masked; + UINT32 bus_width; + UINT32 data_size; /* Word, half word, or byte */ + UINT32 latest_xfer_size; + + void *io_buffer; /* Kernel address */ + dma_addr_t io_buffer_dma; /* Bus address */ + UINT32 io_buffer_size; + UINT32 dma_channel; + UINT32 dma_en; + struct completion dma_complete; + struct completion xfer_complete; + + UINT32 int_mask; + UINT32 int_sdio; /* Do we have SDIO interrupt on ? */ + + UINT32 complete; + + UINT32 cmdsta; + UINT32 dsta; + UINT32 fsta; + + spinlock_t lock; + + struct work_struct io_work; + struct work_struct irq_work; +}; + +SDIO_STATUS s3c24xx_hcd_config(PSDHCD hcd, PSDCONFIG config); +SDIO_STATUS s3c24xx_hcd_request(PSDHCD hcd); + +struct s3c24xx_hcd_context hcd_context; + +#endif |