Au1[12]00 mmc driver. Only tested on the Au1200 at this point though
authorPete Popov <ppopov@embeddedalley.com>
Wed, 21 Sep 2005 06:18:27 +0000 (06:18 +0000)
committerRalf Baechle <ralf@linux-mips.org>
Sat, 29 Oct 2005 18:32:26 +0000 (19:32 +0100)
it should work on the Au1100 as well. Updated defconfig to include driver.

Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
arch/mips/au1000/common/platform.c
drivers/mmc/Kconfig
drivers/mmc/Makefile
drivers/mmc/au1xmmc.c [new file with mode: 0644]
drivers/mmc/au1xmmc.h [new file with mode: 0644]

index 0f29815..efb00fc 100644 (file)
@@ -7,6 +7,7 @@
  * License version 2.  This program is licensed "as is" without any
  * warranty of any kind, whether express or implied.
  */
+#include <linux/config.h>
 #include <linux/device.h>
 #include <linux/kernel.h>
 #include <linux/init.h>
@@ -114,6 +115,24 @@ static struct resource au1xxx_usb_gdt_resources[] = {
        },
 };
 
+static struct resource au1xxx_mmc_resources[] = {
+       [0] = {
+               .start          = SD0_PHYS_ADDR,
+               .end            = SD0_PHYS_ADDR + 0x40,
+               .flags          = IORESOURCE_MEM,
+       },
+       [1] = {
+               .start          = SD1_PHYS_ADDR,
+               .end            = SD1_PHYS_ADDR + 0x40,
+               .flags          = IORESOURCE_MEM,
+       },
+       [2] = {
+               .start          = AU1200_SD_INT,
+               .end            = AU1200_SD_INT,
+               .flags          = IORESOURCE_IRQ,
+       }
+};
+
 static u64 udc_dmamask = ~(u32)0;
 
 static struct platform_device au1xxx_usb_gdt_device = {
@@ -207,6 +226,20 @@ static struct platform_device au1200_ide0_device = {
        .resource       = au1200_ide0_resources,
 };
 
+static u64 au1xxx_mmc_dmamask =  ~(u32)0;
+
+static struct platform_device au1xxx_mmc_device = {
+       .name = "au1xxx-mmc",
+       .id = 0,
+       .dev = {
+               .dma_mask               = &au1xxx_mmc_dmamask,
+               .coherent_dma_mask      = 0xffffffff,
+       },
+       .num_resources  = ARRAY_SIZE(au1xxx_mmc_resources),
+       .resource       = au1xxx_mmc_resources,
+};
+#endif /* #ifdef CONFIG_SOC_AU1200 */
+
 static struct platform_device au1x00_pcmcia_device = {
        .name           = "au1x00-pcmcia",
        .id             = 0,
@@ -226,6 +259,7 @@ static struct platform_device *au1xxx_platform_devices[] __initdata = {
        &au1xxx_usb_otg_device,
        &au1200_lcd_device,
        &au1200_ide0_device,
+       &au1xxx_mmc_device,
 #endif
 };
 
index 4991bbd..c483a86 100644 (file)
@@ -60,4 +60,13 @@ config MMC_WBSD
 
          If unsure, say N.
 
+config MMC_AU1X
+       tristate "Alchemy AU1XX0 MMC Card Interface support"
+       depends on SOC_AU1X00 && MMC
+       help
+         This selects the AMD Alchemy(R) Multimedia card interface.
+         iIf you have a Alchemy platform with a MMC slot, say Y or M here.
+
+         If unsure, say N.
+
 endmenu
index 89510c2..e351e71 100644 (file)
@@ -18,5 +18,6 @@ obj-$(CONFIG_MMC_BLOCK)               += mmc_block.o
 obj-$(CONFIG_MMC_ARMMMCI)      += mmci.o
 obj-$(CONFIG_MMC_PXA)          += pxamci.o
 obj-$(CONFIG_MMC_WBSD)         += wbsd.o
+obj-$(CONFIG_MMC_AU1X)         += au1xmmc.o
 
 mmc_core-y := mmc.o mmc_queue.o mmc_sysfs.o
diff --git a/drivers/mmc/au1xmmc.c b/drivers/mmc/au1xmmc.c
new file mode 100644 (file)
index 0000000..aaf0463
--- /dev/null
@@ -0,0 +1,1026 @@
+/*
+ * linux/drivers/mmc/au1xmmc.c - AU1XX0 MMC driver
+ *
+ *  Copyright (c) 2005, Advanced Micro Devices, Inc.
+ *
+ *  Developed with help from the 2.4.30 MMC AU1XXX controller including
+ *  the following copyright notices:
+ *     Copyright (c) 2003-2004 Embedded Edge, LLC.
+ *     Portions Copyright (C) 2002 Embedix, Inc
+ *     Copyright 2002 Hewlett-Packard Company
+
+ *  2.6 version of this driver inspired by:
+ *     (drivers/mmc/wbsd.c) Copyright (C) 2004-2005 Pierre Ossman,
+ *     All Rights Reserved.
+ *     (drivers/mmc/pxa.c) Copyright (C) 2003 Russell King,
+ *     All Rights Reserved.
+ *
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/* Why is a timer used to detect insert events?
+ *
+ * From the AU1100 MMC application guide:
+ * If the Au1100-based design is intended to support both MultiMediaCards
+ * and 1- or 4-data bit SecureDigital cards, then the solution is to
+ * connect a weak (560KOhm) pull-up resistor to connector pin 1.
+ * In doing so, a MMC card never enters SPI-mode communications,
+ * but now the SecureDigital card-detect feature of CD/DAT3 is ineffective
+ * (the low to high transition will not occur).
+ *
+ * So we use the timer to check the status manually.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+
+#include <linux/mmc/host.h>
+#include <linux/mmc/protocol.h>
+#include <asm/io.h>
+#include <asm/mach-au1x00/au1000.h>
+#include <asm/mach-au1x00/au1xxx_dbdma.h>
+#include <asm/mach-au1x00/au1100_mmc.h>
+#include <asm/scatterlist.h>
+
+#include <au1xxx.h>
+#include "au1xmmc.h"
+
+#define DRIVER_NAME "au1xxx-mmc"
+
+/* Set this to enable special debugging macros */
+/* #define MMC_DEBUG */
+
+#ifdef MMC_DEBUG
+#define DEBUG(fmt, idx, args...) printk("au1xx(%d): DEBUG: " fmt, idx, ##args)
+#else
+#define DEBUG(fmt, idx, args...)
+#endif
+
+const struct {
+       u32 iobase;
+       u32 tx_devid, rx_devid;
+       u16 bcsrpwr;
+       u16 bcsrstatus;
+       u16 wpstatus;
+} au1xmmc_card_table[] = {
+       { SD0_BASE, DSCR_CMD0_SDMS_TX0, DSCR_CMD0_SDMS_RX0,
+         BCSR_BOARD_SD0PWR, BCSR_INT_SD0INSERT, BCSR_STATUS_SD0WP },
+#ifndef CONFIG_MIPS_DB1200
+       { SD1_BASE, DSCR_CMD0_SDMS_TX1, DSCR_CMD0_SDMS_RX1,
+         BCSR_BOARD_DS1PWR, BCSR_INT_SD1INSERT, BCSR_STATUS_SD1WP }
+#endif
+};
+
+#define AU1XMMC_CONTROLLER_COUNT \
+       (sizeof(au1xmmc_card_table) / sizeof(au1xmmc_card_table[0]))
+
+/* This array stores pointers for the hosts (used by the IRQ handler) */
+struct au1xmmc_host *au1xmmc_hosts[AU1XMMC_CONTROLLER_COUNT];
+static int dma = 1;
+
+#ifdef MODULE
+MODULE_PARM(dma, "i");
+MODULE_PARM_DESC(dma, "Use DMA engine for data transfers (0 = disabled)");
+#endif
+
+static inline void IRQ_ON(struct au1xmmc_host *host, u32 mask)
+{
+       u32 val = au_readl(HOST_CONFIG(host));
+       val |= mask;
+       au_writel(val, HOST_CONFIG(host));
+       au_sync();
+}
+
+static inline void FLUSH_FIFO(struct au1xmmc_host *host)
+{
+       u32 val = au_readl(HOST_CONFIG2(host));
+
+       au_writel(val | SD_CONFIG2_FF, HOST_CONFIG2(host));
+       au_sync_delay(1);
+
+       /* SEND_STOP will turn off clock control - this re-enables it */
+       val &= ~SD_CONFIG2_DF;
+
+       au_writel(val, HOST_CONFIG2(host));
+       au_sync();
+}
+
+static inline void IRQ_OFF(struct au1xmmc_host *host, u32 mask)
+{
+       u32 val = au_readl(HOST_CONFIG(host));
+       val &= ~mask;
+       au_writel(val, HOST_CONFIG(host));
+       au_sync();
+}
+
+static inline void SEND_STOP(struct au1xmmc_host *host)
+{
+
+       /* We know the value of CONFIG2, so avoid a read we don't need */
+       u32 mask = SD_CONFIG2_EN;
+
+       WARN_ON(host->status != HOST_S_DATA);
+       host->status = HOST_S_STOP;
+
+       au_writel(mask | SD_CONFIG2_DF, HOST_CONFIG2(host));
+       au_sync();
+
+       /* Send the stop commmand */
+       au_writel(STOP_CMD, HOST_CMD(host));
+}
+
+static void au1xmmc_set_power(struct au1xmmc_host *host, int state)
+{
+
+       u32 val = au1xmmc_card_table[host->id].bcsrpwr;
+
+       bcsr->board &= ~val;
+       if (state) bcsr->board |= val;
+
+       au_sync_delay(1);
+}
+
+static inline int au1xmmc_card_inserted(struct au1xmmc_host *host)
+{
+       return (bcsr->sig_status & au1xmmc_card_table[host->id].bcsrstatus)
+               ? 1 : 0;
+}
+
+static inline int au1xmmc_card_readonly(struct au1xmmc_host *host)
+{
+       return (bcsr->status & au1xmmc_card_table[host->id].wpstatus)
+               ? 1 : 0;
+}
+
+static void au1xmmc_finish_request(struct au1xmmc_host *host)
+{
+
+       struct mmc_request *mrq = host->mrq;
+
+       host->mrq = NULL;
+       host->flags &= HOST_F_ACTIVE;
+
+       host->dma.len = 0;
+       host->dma.dir = 0;
+
+       host->pio.index  = 0;
+       host->pio.offset = 0;
+       host->pio.len = 0;
+
+       host->status = HOST_S_IDLE;
+
+       bcsr->disk_leds |= (1 << 8);
+
+       mmc_request_done(host->mmc, mrq);
+}
+
+static void au1xmmc_tasklet_finish(unsigned long param)
+{
+       struct au1xmmc_host *host = (struct au1xmmc_host *) param;
+       au1xmmc_finish_request(host);
+}
+
+static int au1xmmc_send_command(struct au1xmmc_host *host, int wait,
+                               struct mmc_command *cmd)
+{
+
+       u32 mmccmd = (cmd->opcode << SD_CMD_CI_SHIFT);
+
+       switch(cmd->flags) {
+       case MMC_RSP_R1:
+               mmccmd |= SD_CMD_RT_1;
+               break;
+       case MMC_RSP_R1B:
+               mmccmd |= SD_CMD_RT_1B;
+               break;
+       case MMC_RSP_R2:
+               mmccmd |= SD_CMD_RT_2;
+               break;
+       case MMC_RSP_R3:
+               mmccmd |= SD_CMD_RT_3;
+               break;
+       }
+
+       switch(cmd->opcode) {
+       case MMC_READ_SINGLE_BLOCK:
+       case SD_APP_SEND_SCR:
+               mmccmd |= SD_CMD_CT_2;
+               break;
+       case MMC_READ_MULTIPLE_BLOCK:
+               mmccmd |= SD_CMD_CT_4;
+               break;
+       case MMC_WRITE_BLOCK:
+               mmccmd |= SD_CMD_CT_1;
+               break;
+
+       case MMC_WRITE_MULTIPLE_BLOCK:
+               mmccmd |= SD_CMD_CT_3;
+               break;
+       case MMC_STOP_TRANSMISSION:
+               mmccmd |= SD_CMD_CT_7;
+               break;
+       }
+
+       au_writel(cmd->arg, HOST_CMDARG(host));
+       au_sync();
+
+       if (wait)
+               IRQ_OFF(host, SD_CONFIG_CR);
+
+       au_writel((mmccmd | SD_CMD_GO), HOST_CMD(host));
+       au_sync();
+
+       /* Wait for the command to go on the line */
+
+       while(1) {
+               if (!(au_readl(HOST_CMD(host)) & SD_CMD_GO))
+                       break;
+       }
+
+       /* Wait for the command to come back */
+
+       if (wait) {
+               u32 status = au_readl(HOST_STATUS(host));
+
+               while(!(status & SD_STATUS_CR))
+                       status = au_readl(HOST_STATUS(host));
+
+               /* Clear the CR status */
+               au_writel(SD_STATUS_CR, HOST_STATUS(host));
+
+               IRQ_ON(host, SD_CONFIG_CR);
+       }
+
+       return MMC_ERR_NONE;
+}
+
+static void au1xmmc_data_complete(struct au1xmmc_host *host, u32 status)
+{
+
+       struct mmc_request *mrq = host->mrq;
+       struct mmc_data *data;
+       u32 crc;
+
+       WARN_ON(host->status != HOST_S_DATA && host->status != HOST_S_STOP);
+
+       if (host->mrq == NULL)
+               return;
+
+       data = mrq->cmd->data;
+
+       if (status == 0)
+               status = au_readl(HOST_STATUS(host));
+
+       /* The transaction is really over when the SD_STATUS_DB bit is clear */
+
+       while((host->flags & HOST_F_XMIT) && (status & SD_STATUS_DB))
+               status = au_readl(HOST_STATUS(host));
+
+       data->error = MMC_ERR_NONE;
+       dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, host->dma.dir);
+
+        /* Process any errors */
+
+       crc = (status & (SD_STATUS_WC | SD_STATUS_RC));
+       if (host->flags & HOST_F_XMIT)
+               crc |= ((status & 0x07) == 0x02) ? 0 : 1;
+
+       if (crc)
+               data->error = MMC_ERR_BADCRC;
+
+       /* Clear the CRC bits */
+       au_writel(SD_STATUS_WC | SD_STATUS_RC, HOST_STATUS(host));
+
+       data->bytes_xfered = 0;
+
+       if (data->error == MMC_ERR_NONE) {
+               if (host->flags & HOST_F_DMA) {
+                       u32 chan = DMA_CHANNEL(host);
+
+                       chan_tab_t *c = *((chan_tab_t **) chan);
+                       au1x_dma_chan_t *cp = c->chan_ptr;
+                       data->bytes_xfered = cp->ddma_bytecnt;
+               }
+               else
+                       data->bytes_xfered =
+                               (data->blocks * (1 << data->blksz_bits)) -
+                               host->pio.len;
+       }
+
+       au1xmmc_finish_request(host);
+}
+
+static void au1xmmc_tasklet_data(unsigned long param)
+{
+       struct au1xmmc_host *host = (struct au1xmmc_host *) param;
+
+       u32 status = au_readl(HOST_STATUS(host));
+       au1xmmc_data_complete(host, status);
+}
+
+#define AU1XMMC_MAX_TRANSFER 8
+
+static void au1xmmc_send_pio(struct au1xmmc_host *host)
+{
+
+       struct mmc_data *data = 0;
+       int sg_len, max, count = 0;
+       unsigned char *sg_ptr;
+       u32 status = 0;
+       struct scatterlist *sg;
+
+       data = host->mrq->data;
+
+       if (!(host->flags & HOST_F_XMIT))
+               return;
+
+       /* This is the pointer to the data buffer */
+       sg = &data->sg[host->pio.index];
+       sg_ptr = page_address(sg->page) + sg->offset + host->pio.offset;
+
+       /* This is the space left inside the buffer */
+       sg_len = data->sg[host->pio.index].length - host->pio.offset;
+
+       /* Check to if we need less then the size of the sg_buffer */
+
+       max = (sg_len > host->pio.len) ? host->pio.len : sg_len;
+       if (max > AU1XMMC_MAX_TRANSFER) max = AU1XMMC_MAX_TRANSFER;
+
+       for(count = 0; count < max; count++ ) {
+               unsigned char val;
+
+               status = au_readl(HOST_STATUS(host));
+
+               if (!(status & SD_STATUS_TH))
+                       break;
+
+               val = *sg_ptr++;
+
+               au_writel((unsigned long) val, HOST_TXPORT(host));
+               au_sync();
+       }
+
+       host->pio.len -= count;
+       host->pio.offset += count;
+
+       if (count == sg_len) {
+               host->pio.index++;
+               host->pio.offset = 0;
+       }
+
+       if (host->pio.len == 0) {
+               IRQ_OFF(host, SD_CONFIG_TH);
+
+               if (host->flags & HOST_F_STOP)
+                       SEND_STOP(host);
+
+               tasklet_schedule(&host->data_task);
+       }
+}
+
+static void au1xmmc_receive_pio(struct au1xmmc_host *host)
+{
+
+       struct mmc_data *data = 0;
+       int sg_len = 0, max = 0, count = 0;
+       unsigned char *sg_ptr = 0;
+       u32 status = 0;
+       struct scatterlist *sg;
+
+       data = host->mrq->data;
+
+       if (!(host->flags & HOST_F_RECV))
+               return;
+
+       max = host->pio.len;
+
+       if (host->pio.index < host->dma.len) {
+               sg = &data->sg[host->pio.index];
+               sg_ptr = page_address(sg->page) + sg->offset + host->pio.offset;
+
+               /* This is the space left inside the buffer */
+               sg_len = sg_dma_len(&data->sg[host->pio.index]) - host->pio.offset;
+
+               /* Check to if we need less then the size of the sg_buffer */
+               if (sg_len < max) max = sg_len;
+       }
+
+       if (max > AU1XMMC_MAX_TRANSFER)
+               max = AU1XMMC_MAX_TRANSFER;
+
+       for(count = 0; count < max; count++ ) {
+               u32 val;
+               status = au_readl(HOST_STATUS(host));
+
+               if (!(status & SD_STATUS_NE))
+                       break;
+
+               if (status & SD_STATUS_RC) {
+                       DEBUG("RX CRC Error [%d + %d].\n", host->id,
+                                       host->pio.len, count);
+                       break;
+               }
+
+               if (status & SD_STATUS_RO) {
+                       DEBUG("RX Overrun [%d + %d]\n", host->id,
+                                       host->pio.len, count);
+                       break;
+               }
+               else if (status & SD_STATUS_RU) {
+                       DEBUG("RX Underrun [%d + %d]\n", host->id,
+                                       host->pio.len,  count);
+                       break;
+               }
+
+               val = au_readl(HOST_RXPORT(host));
+
+               if (sg_ptr)
+                       *sg_ptr++ = (unsigned char) (val & 0xFF);
+       }
+
+       host->pio.len -= count;
+       host->pio.offset += count;
+
+       if (sg_len && count == sg_len) {
+               host->pio.index++;
+               host->pio.offset = 0;
+       }
+
+       if (host->pio.len == 0) {
+               //IRQ_OFF(host, SD_CONFIG_RA | SD_CONFIG_RF);
+               IRQ_OFF(host, SD_CONFIG_NE);
+
+               if (host->flags & HOST_F_STOP)
+                       SEND_STOP(host);
+
+               tasklet_schedule(&host->data_task);
+       }
+}
+
+/* static void au1xmmc_cmd_complete
+   This is called when a command has been completed - grab the response
+   and check for errors.  Then start the data transfer if it is indicated.
+*/
+
+static void au1xmmc_cmd_complete(struct au1xmmc_host *host, u32 status)
+{
+
+       struct mmc_request *mrq = host->mrq;
+       struct mmc_command *cmd;
+       int trans;
+
+       if (!host->mrq)
+               return;
+
+       cmd = mrq->cmd;
+       cmd->error = MMC_ERR_NONE;
+
+       if ((cmd->flags & MMC_RSP_MASK) == MMC_RSP_SHORT) {
+
+               /* Techincally, we should be getting all 48 bits of the response
+                * (SD_RESP1 + SD_RESP2), but because our response omits the CRC,
+                * our data ends up being shifted 8 bits to the right.  In this case,
+                * that means that the OSR data starts at bit 31, so we can just
+                * read RESP0 and return that
+                */
+
+               cmd->resp[0] = au_readl(host->iobase + SD_RESP0);
+       }
+       else if ((cmd->flags & MMC_RSP_MASK) == MMC_RSP_LONG) {
+               u32 r[4];
+               int i;
+
+               r[0] = au_readl(host->iobase + SD_RESP3);
+               r[1] = au_readl(host->iobase + SD_RESP2);
+               r[2] = au_readl(host->iobase + SD_RESP1);
+               r[3] = au_readl(host->iobase + SD_RESP0);
+
+               /* The CRC is omitted from the response, so really we only got
+                * 120 bytes, but the engine expects 128 bits, so we have to shift
+                * things up
+                */
+
+               for(i = 0; i < 4; i++) {
+                       cmd->resp[i] = (r[i] & 0x00FFFFFF) << 8;
+                       if (i != 3) cmd->resp[i] |= (r[i + 1] & 0xFF000000) >> 24;
+               }
+       }
+
+        /* Figure out errors */
+
+       if (status & (SD_STATUS_SC | SD_STATUS_WC | SD_STATUS_RC))
+               cmd->error = MMC_ERR_BADCRC;
+
+       trans = host->flags & (HOST_F_XMIT | HOST_F_RECV);
+
+       if (!trans || cmd->error != MMC_ERR_NONE) {
+
+               IRQ_OFF(host, SD_CONFIG_TH | SD_CONFIG_RA|SD_CONFIG_RF);
+               tasklet_schedule(&host->finish_task);
+               return;
+       }
+
+       host->status = HOST_S_DATA;
+
+       if (host->flags & HOST_F_DMA) {
+               u32 channel = DMA_CHANNEL(host);
+
+               /* Start the DMA as soon as the buffer gets something in it */
+
+               if (host->flags & HOST_F_RECV) {
+                       u32 mask = SD_STATUS_DB | SD_STATUS_NE;
+
+                       while((status & mask) != mask)
+                               status = au_readl(HOST_STATUS(host));
+               }
+
+               au1xxx_dbdma_start(channel);
+       }
+}
+
+static void au1xmmc_set_clock(struct au1xmmc_host *host, int rate)
+{
+
+       unsigned int pbus = get_au1x00_speed();
+       unsigned int divisor;
+       u32 config;
+
+       /* From databook:
+          divisor = ((((cpuclock / sbus_divisor) / 2) / mmcclock) / 2) - 1
+       */
+
+       pbus /= ((au_readl(SYS_POWERCTRL) & 0x3) + 2);
+       pbus /= 2;
+
+       divisor = ((pbus / rate) / 2) - 1;
+
+       config = au_readl(HOST_CONFIG(host));
+
+       config &= ~(SD_CONFIG_DIV);
+       config |= (divisor & SD_CONFIG_DIV) | SD_CONFIG_DE;
+
+       au_writel(config, HOST_CONFIG(host));
+       au_sync();
+}
+
+static int
+au1xmmc_prepare_data(struct au1xmmc_host *host, struct mmc_data *data)
+{
+
+       int datalen = data->blocks * (1 << data->blksz_bits);
+
+       if (dma != 0)
+               host->flags |= HOST_F_DMA;
+
+       if (data->flags & MMC_DATA_READ)
+               host->flags |= HOST_F_RECV;
+       else
+               host->flags |= HOST_F_XMIT;
+
+       if (host->mrq->stop)
+               host->flags |= HOST_F_STOP;
+
+       host->dma.dir = DMA_BIDIRECTIONAL;
+
+       host->dma.len = dma_map_sg(mmc_dev(host->mmc), data->sg,
+                                  data->sg_len, host->dma.dir);
+
+       if (host->dma.len == 0)
+               return MMC_ERR_TIMEOUT;
+
+       au_writel((1 << data->blksz_bits) - 1, HOST_BLKSIZE(host));
+
+       if (host->flags & HOST_F_DMA) {
+               int i;
+               u32 channel = DMA_CHANNEL(host);
+
+               au1xxx_dbdma_stop(channel);
+
+               for(i = 0; i < host->dma.len; i++) {
+                       u32 ret = 0, flags = DDMA_FLAGS_NOIE;
+                       struct scatterlist *sg = &data->sg[i];
+                       int sg_len = sg->length;
+
+                       int len = (datalen > sg_len) ? sg_len : datalen;
+
+                       if (i == host->dma.len - 1)
+                               flags = DDMA_FLAGS_IE;
+
+                       if (host->flags & HOST_F_XMIT){
+                               ret = au1xxx_dbdma_put_source_flags(channel,
+                                       (void *) (page_address(sg->page) +
+                                                 sg->offset),
+                                       len, flags);
+                       }
+                       else {
+                               ret = au1xxx_dbdma_put_dest_flags(channel,
+                                       (void *) (page_address(sg->page) +
+                                                 sg->offset),
+                                       len, flags);
+                       }
+
+                       if (!ret)
+                               goto dataerr;
+
+                       datalen -= len;
+               }
+       }
+       else {
+               host->pio.index = 0;
+               host->pio.offset = 0;
+               host->pio.len = datalen;
+
+               if (host->flags & HOST_F_XMIT)
+                       IRQ_ON(host, SD_CONFIG_TH);
+               else
+                       IRQ_ON(host, SD_CONFIG_NE);
+                       //IRQ_ON(host, SD_CONFIG_RA|SD_CONFIG_RF);
+       }
+
+       return MMC_ERR_NONE;
+
+ dataerr:
+       dma_unmap_sg(mmc_dev(host->mmc),data->sg,data->sg_len,host->dma.dir);
+       return MMC_ERR_TIMEOUT;
+}
+
+/* static void au1xmmc_request
+   This actually starts a command or data transaction
+*/
+
+static void au1xmmc_request(struct mmc_host* mmc, struct mmc_request* mrq)
+{
+
+       struct au1xmmc_host *host = mmc_priv(mmc);
+       int ret = MMC_ERR_NONE;
+
+       WARN_ON(irqs_disabled());
+       WARN_ON(host->status != HOST_S_IDLE);
+
+       host->mrq = mrq;
+       host->status = HOST_S_CMD;
+
+       bcsr->disk_leds &= ~(1 << 8);
+
+       if (mrq->data) {
+               FLUSH_FIFO(host);
+               ret = au1xmmc_prepare_data(host, mrq->data);
+       }
+
+       if (ret == MMC_ERR_NONE)
+               ret = au1xmmc_send_command(host, 0, mrq->cmd);
+
+       if (ret != MMC_ERR_NONE) {
+               mrq->cmd->error = ret;
+               au1xmmc_finish_request(host);
+       }
+}
+
+static void au1xmmc_reset_controller(struct au1xmmc_host *host)
+{
+
+       /* Apply the clock */
+       au_writel(SD_ENABLE_CE, HOST_ENABLE(host));
+        au_sync_delay(1);
+
+       au_writel(SD_ENABLE_R | SD_ENABLE_CE, HOST_ENABLE(host));
+       au_sync_delay(5);
+
+       au_writel(~0, HOST_STATUS(host));
+       au_sync();
+
+       au_writel(0, HOST_BLKSIZE(host));
+       au_writel(0x001fffff, HOST_TIMEOUT(host));
+       au_sync();
+
+       au_writel(SD_CONFIG2_EN, HOST_CONFIG2(host));
+        au_sync();
+
+       au_writel(SD_CONFIG2_EN | SD_CONFIG2_FF, HOST_CONFIG2(host));
+       au_sync_delay(1);
+
+       au_writel(SD_CONFIG2_EN, HOST_CONFIG2(host));
+       au_sync();
+
+       /* Configure interrupts */
+       au_writel(AU1XMMC_INTERRUPTS, HOST_CONFIG(host));
+       au_sync();
+}
+
+
+static void au1xmmc_set_ios(struct mmc_host* mmc, struct mmc_ios* ios)
+{
+       struct au1xmmc_host *host = mmc_priv(mmc);
+
+       DEBUG("set_ios (power=%u, clock=%uHz, vdd=%u, mode=%u)\n",
+             host->id, ios->power_mode, ios->clock, ios->vdd,
+             ios->bus_mode);
+
+       if (ios->power_mode == MMC_POWER_OFF)
+               au1xmmc_set_power(host, 0);
+       else if (ios->power_mode == MMC_POWER_ON) {
+               au1xmmc_set_power(host, 1);
+       }
+
+       if (ios->clock && ios->clock != host->clock) {
+               au1xmmc_set_clock(host, ios->clock);
+               host->clock = ios->clock;
+       }
+}
+
+static void au1xmmc_dma_callback(int irq, void *dev_id, struct pt_regs *regs)
+{
+       struct au1xmmc_host *host = (struct au1xmmc_host *) dev_id;
+       u32 status;
+
+       /* Avoid spurious interrupts */
+
+       if (!host->mrq)
+               return;
+
+       if (host->flags & HOST_F_STOP)
+               SEND_STOP(host);
+
+       tasklet_schedule(&host->data_task);
+}
+
+#define STATUS_TIMEOUT (SD_STATUS_RAT | SD_STATUS_DT)
+#define STATUS_DATA_IN  (SD_STATUS_NE)
+#define STATUS_DATA_OUT (SD_STATUS_TH)
+
+static irqreturn_t au1xmmc_irq(int irq, void *dev_id, struct pt_regs *regs)
+{
+
+       u32 status;
+       int i, ret = 0;
+
+       disable_irq(AU1100_SD_IRQ);
+
+       for(i = 0; i < AU1XMMC_CONTROLLER_COUNT; i++) {
+               struct au1xmmc_host * host = au1xmmc_hosts[i];
+               u32 handled = 1;
+
+               status = au_readl(HOST_STATUS(host));
+
+               if (host->mrq && (status & STATUS_TIMEOUT)) {
+                       if (status & SD_STATUS_RAT)
+                               host->mrq->cmd->error = MMC_ERR_TIMEOUT;
+
+                       else if (status & SD_STATUS_DT)
+                               host->mrq->data->error = MMC_ERR_TIMEOUT;
+
+                       /* In PIO mode, interrupts might still be enabled */
+                       IRQ_OFF(host, SD_CONFIG_NE | SD_CONFIG_TH);
+
+                       //IRQ_OFF(host, SD_CONFIG_TH|SD_CONFIG_RA|SD_CONFIG_RF);
+                       tasklet_schedule(&host->finish_task);
+               }
+#if 0
+               else if (status & SD_STATUS_DD) {
+
+                       /* Sometimes we get a DD before a NE in PIO mode */
+
+                       if (!(host->flags & HOST_F_DMA) &&
+                                       (status & SD_STATUS_NE))
+                               au1xmmc_receive_pio(host);
+                       else {
+                               au1xmmc_data_complete(host, status);
+                               //tasklet_schedule(&host->data_task);
+                       }
+               }
+#endif
+               else if (status & (SD_STATUS_CR)) {
+                       if (host->status == HOST_S_CMD)
+                               au1xmmc_cmd_complete(host,status);
+               }
+               else if (!(host->flags & HOST_F_DMA)) {
+                       if ((host->flags & HOST_F_XMIT) &&
+                           (status & STATUS_DATA_OUT))
+                               au1xmmc_send_pio(host);
+                       else if ((host->flags & HOST_F_RECV) &&
+                           (status & STATUS_DATA_IN))
+                               au1xmmc_receive_pio(host);
+               }
+               else if (status & 0x203FBC70) {
+                       DEBUG("Unhandled status %8.8x\n", host->id, status);
+                       handled = 0;
+               }
+
+               au_writel(status, HOST_STATUS(host));
+               au_sync();
+
+               ret |= handled;
+       }
+
+       enable_irq(AU1100_SD_IRQ);
+       return ret;
+}
+
+static void au1xmmc_poll_event(unsigned long arg)
+{
+       struct au1xmmc_host *host = (struct au1xmmc_host *) arg;
+
+       int card = au1xmmc_card_inserted(host);
+        int controller = (host->flags & HOST_F_ACTIVE) ? 1 : 0;
+
+       if (card != controller) {
+               host->flags &= ~HOST_F_ACTIVE;
+               if (card) host->flags |= HOST_F_ACTIVE;
+               mmc_detect_change(host->mmc, 0);
+       }
+
+       if (host->mrq != NULL) {
+               u32 status = au_readl(HOST_STATUS(host));
+               DEBUG("PENDING - %8.8x\n", host->id, status);
+       }
+
+       mod_timer(&host->timer, jiffies + AU1XMMC_DETECT_TIMEOUT);
+}
+
+static dbdev_tab_t au1xmmc_mem_dbdev =
+{
+       DSCR_CMD0_ALWAYS, DEV_FLAGS_ANYUSE, 0, 8, 0x00000000, 0, 0
+};
+
+static void au1xmmc_init_dma(struct au1xmmc_host *host)
+{
+
+       u32 rxchan, txchan;
+
+       int txid = au1xmmc_card_table[host->id].tx_devid;
+       int rxid = au1xmmc_card_table[host->id].rx_devid;
+
+       /* DSCR_CMD0_ALWAYS has a stride of 32 bits, we need a stride
+          of 8 bits.  And since devices are shared, we need to create
+          our own to avoid freaking out other devices
+       */
+
+       int memid = au1xxx_ddma_add_device(&au1xmmc_mem_dbdev);
+
+       txchan = au1xxx_dbdma_chan_alloc(memid, txid,
+                                        au1xmmc_dma_callback, (void *) host);
+
+       rxchan = au1xxx_dbdma_chan_alloc(rxid, memid,
+                                        au1xmmc_dma_callback, (void *) host);
+
+       au1xxx_dbdma_set_devwidth(txchan, 8);
+       au1xxx_dbdma_set_devwidth(rxchan, 8);
+
+       au1xxx_dbdma_ring_alloc(txchan, AU1XMMC_DESCRIPTOR_COUNT);
+       au1xxx_dbdma_ring_alloc(rxchan, AU1XMMC_DESCRIPTOR_COUNT);
+
+       host->tx_chan = txchan;
+       host->rx_chan = rxchan;
+}
+
+struct mmc_host_ops au1xmmc_ops = {
+       .request        = au1xmmc_request,
+       .set_ios        = au1xmmc_set_ios,
+};
+
+static int au1xmmc_probe(struct device *dev)
+{
+
+       int i, ret = 0;
+
+       /* THe interrupt is shared among all controllers */
+       ret = request_irq(AU1100_SD_IRQ, au1xmmc_irq, SA_INTERRUPT, "MMC", 0);
+
+       if (ret) {
+               printk(DRIVER_NAME "ERROR: Couldn't get int %d: %d\n",
+                               AU1100_SD_IRQ, ret);
+               return -ENXIO;
+       }
+
+       disable_irq(AU1100_SD_IRQ);
+
+       for(i = 0; i < AU1XMMC_CONTROLLER_COUNT; i++) {
+               struct mmc_host *mmc = mmc_alloc_host(sizeof(struct au1xmmc_host), dev);
+               struct au1xmmc_host *host = 0;
+
+               if (!mmc) {
+                       printk(DRIVER_NAME "ERROR: no mem for host %d\n", i);
+                       au1xmmc_hosts[i] = 0;
+                       continue;
+               }
+
+               mmc->ops = &au1xmmc_ops;
+
+               mmc->f_min =   450000;
+               mmc->f_max = 24000000;
+
+               mmc->max_seg_size = AU1XMMC_DESCRIPTOR_SIZE;
+               mmc->max_phys_segs = AU1XMMC_DESCRIPTOR_COUNT;
+
+               mmc->ocr_avail = AU1XMMC_OCR;
+
+               host = mmc_priv(mmc);
+               host->mmc = mmc;
+
+               host->id = i;
+               host->iobase = au1xmmc_card_table[host->id].iobase;
+               host->clock = 0;
+               host->power_mode = MMC_POWER_OFF;
+
+               host->flags = au1xmmc_card_inserted(host) ? HOST_F_ACTIVE : 0;
+               host->status = HOST_S_IDLE;
+
+               init_timer(&host->timer);
+
+               host->timer.function = au1xmmc_poll_event;
+               host->timer.data = (unsigned long) host;
+               host->timer.expires = jiffies + AU1XMMC_DETECT_TIMEOUT;
+
+               tasklet_init(&host->data_task, au1xmmc_tasklet_data,
+                               (unsigned long) host);
+
+               tasklet_init(&host->finish_task, au1xmmc_tasklet_finish,
+                               (unsigned long) host);
+
+               spin_lock_init(&host->lock);
+
+               if (dma != 0)
+                       au1xmmc_init_dma(host);
+
+               au1xmmc_reset_controller(host);
+
+               mmc_add_host(mmc);
+               au1xmmc_hosts[i] = host;
+
+               add_timer(&host->timer);
+
+               printk(KERN_INFO DRIVER_NAME ": MMC Controller %d set up at %8.8X (mode=%s)\n",
+                      host->id, host->iobase, dma ? "dma" : "pio");
+       }
+
+       enable_irq(AU1100_SD_IRQ);
+
+       return 0;
+}
+
+static int au1xmmc_remove(struct device *dev)
+{
+
+       int i;
+
+       disable_irq(AU1100_SD_IRQ);
+
+       for(i = 0; i < AU1XMMC_CONTROLLER_COUNT; i++) {
+               struct au1xmmc_host *host = au1xmmc_hosts[i];
+               if (!host) continue;
+
+               tasklet_kill(&host->data_task);
+               tasklet_kill(&host->finish_task);
+
+               del_timer_sync(&host->timer);
+               au1xmmc_set_power(host, 0);
+
+               mmc_remove_host(host->mmc);
+
+               au1xxx_dbdma_chan_free(host->tx_chan);
+               au1xxx_dbdma_chan_free(host->rx_chan);
+
+               au_writel(0x0, HOST_ENABLE(host));
+               au_sync();
+       }
+
+       free_irq(AU1100_SD_IRQ, 0);
+       return 0;
+}
+
+static struct device_driver au1xmmc_driver = {
+       .name          = DRIVER_NAME,
+       .bus           = &platform_bus_type,
+       .probe         = au1xmmc_probe,
+       .remove        = au1xmmc_remove,
+       .suspend       = NULL,
+       .resume        = NULL
+};
+
+static int __init au1xmmc_init(void)
+{
+       return driver_register(&au1xmmc_driver);
+}
+
+static void __exit au1xmmc_exit(void)
+{
+       driver_unregister(&au1xmmc_driver);
+}
+
+module_init(au1xmmc_init);
+module_exit(au1xmmc_exit);
+
+#ifdef MODULE
+MODULE_AUTHOR("Advanced Micro Devices, Inc");
+MODULE_DESCRIPTION("MMC/SD driver for the Alchemy Au1XXX");
+MODULE_LICENSE("GPL");
+#endif
+
diff --git a/drivers/mmc/au1xmmc.h b/drivers/mmc/au1xmmc.h
new file mode 100644 (file)
index 0000000..341cbdf
--- /dev/null
@@ -0,0 +1,96 @@
+#ifndef _AU1XMMC_H_
+#define _AU1XMMC_H_
+
+/* Hardware definitions */
+
+#define AU1XMMC_DESCRIPTOR_COUNT 1
+#define AU1XMMC_DESCRIPTOR_SIZE  2048
+
+#define AU1XMMC_OCR ( MMC_VDD_27_28 | MMC_VDD_28_29 | MMC_VDD_29_30  | \
+                     MMC_VDD_30_31 | MMC_VDD_31_32 | MMC_VDD_32_33  | \
+                     MMC_VDD_33_34 | MMC_VDD_34_35 | MMC_VDD_35_36)
+
+/* Easy access macros */
+
+#define HOST_STATUS(h) ((h)->iobase + SD_STATUS)
+#define HOST_CONFIG(h) ((h)->iobase + SD_CONFIG)
+#define HOST_ENABLE(h) ((h)->iobase + SD_ENABLE)
+#define HOST_TXPORT(h) ((h)->iobase + SD_TXPORT)
+#define HOST_RXPORT(h) ((h)->iobase + SD_RXPORT)
+#define HOST_CMDARG(h) ((h)->iobase + SD_CMDARG)
+#define HOST_BLKSIZE(h)        ((h)->iobase + SD_BLKSIZE)
+#define HOST_CMD(h)    ((h)->iobase + SD_CMD)
+#define HOST_CONFIG2(h)        ((h)->iobase + SD_CONFIG2)
+#define HOST_TIMEOUT(h)        ((h)->iobase + SD_TIMEOUT)
+#define HOST_DEBUG(h)  ((h)->iobase + SD_DEBUG)
+
+#define DMA_CHANNEL(h) \
+       ( ((h)->flags & HOST_F_XMIT) ? (h)->tx_chan : (h)->rx_chan)
+
+/* This gives us a hard value for the stop command that we can write directly
+ * to the command register
+ */
+
+#define STOP_CMD (SD_CMD_RT_1B|SD_CMD_CT_7|(0xC << SD_CMD_CI_SHIFT)|SD_CMD_GO)
+
+/* This is the set of interrupts that we configure by default */
+
+#if 0
+#define AU1XMMC_INTERRUPTS (SD_CONFIG_SC | SD_CONFIG_DT | SD_CONFIG_DD | \
+               SD_CONFIG_RAT | SD_CONFIG_CR | SD_CONFIG_I)
+#endif
+
+#define AU1XMMC_INTERRUPTS (SD_CONFIG_SC | SD_CONFIG_DT | \
+               SD_CONFIG_RAT | SD_CONFIG_CR | SD_CONFIG_I)
+/* The poll event (looking for insert/remove events runs twice a second */
+#define AU1XMMC_DETECT_TIMEOUT (HZ/2)
+
+struct au1xmmc_host {
+  struct mmc_host *mmc;
+  struct mmc_request *mrq;
+
+  u32 id;
+
+  u32 flags;
+  u32 iobase;
+  u32 clock;
+  u32 bus_width;
+  u32 power_mode;
+
+  int status;
+
+   struct {
+          int len;
+          int dir;
+  } dma;
+
+   struct {
+          int index;
+          int offset;
+          int len;
+  } pio;
+
+  u32 tx_chan;
+  u32 rx_chan;
+
+  struct timer_list timer;
+  struct tasklet_struct finish_task;
+  struct tasklet_struct data_task;
+
+  spinlock_t lock;
+};
+
+/* Status flags used by the host structure */
+
+#define HOST_F_XMIT   0x0001
+#define HOST_F_RECV   0x0002
+#define HOST_F_DMA    0x0010
+#define HOST_F_ACTIVE 0x0100
+#define HOST_F_STOP   0x1000
+
+#define HOST_S_IDLE   0x0001
+#define HOST_S_CMD    0x0002
+#define HOST_S_DATA   0x0003
+#define HOST_S_STOP   0x0004
+
+#endif