1638 lines
49 KiB
Diff
Executable File
1638 lines
49 KiB
Diff
Executable File
From 3a9e6d01e200d3caab806474dcf870b7fc8c113d Mon Sep 17 00:00:00 2001
|
|
From: mokopatches <mokopatches@openmoko.org>
|
|
Date: Fri, 25 Jul 2008 22:21:24 +0100
|
|
Subject: [PATCH] atheros_2_0_hcd.patch
|
|
|
|
---
|
|
drivers/sdio/hcd/Kconfig | 14 +
|
|
drivers/sdio/hcd/Makefile | 1 +
|
|
drivers/sdio/hcd/s3c24xx/Makefile | 2 +
|
|
drivers/sdio/hcd/s3c24xx/s3c24xx_hcd.c | 1502 ++++++++++++++++++++++++++++++++
|
|
drivers/sdio/hcd/s3c24xx/s3c24xx_hcd.h | 67 ++
|
|
5 files changed, 1586 insertions(+), 0 deletions(-)
|
|
create mode 100644 drivers/sdio/hcd/Kconfig
|
|
create mode 100644 drivers/sdio/hcd/Makefile
|
|
create mode 100644 drivers/sdio/hcd/s3c24xx/Makefile
|
|
create mode 100644 drivers/sdio/hcd/s3c24xx/s3c24xx_hcd.c
|
|
create mode 100644 drivers/sdio/hcd/s3c24xx/s3c24xx_hcd.h
|
|
|
|
diff --git a/drivers/sdio/hcd/Kconfig b/drivers/sdio/hcd/Kconfig
|
|
new file mode 100644
|
|
index 0000000..e4d8397
|
|
--- /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 0000000..e2401e2
|
|
--- /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 0000000..d2d099c
|
|
--- /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 0000000..3c4758b
|
|
--- /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 0000000..eb262fc
|
|
--- /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
|
|
--
|
|
1.5.6.3
|
|
|