/* * s3c24xx_hcd.c - Samsung S3C MCI driver, Atheros SDIO API compatible. * * Copyright (C) 2007 by Openmoko, Inc. * Written by Samuel Ortiz * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "s3c24xx_hcd.h" #define DESCRIPTION "S3c24xx SDIO host controller" #define AUTHOR "Samuel Ortiz " #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);