Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/drzeus/mmc
authorLinus Torvalds <torvalds@linux-foundation.org>
Wed, 16 Jul 2008 22:11:07 +0000 (15:11 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 16 Jul 2008 22:17:52 +0000 (15:17 -0700)
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/drzeus/mmc: (68 commits)
  sdio_uart: Fix SDIO break control to now return success or an error
  mmc: host driver for Ricoh Bay1Controllers
  sdio: sdio_io.c Fix sparse warnings
  sdio: fix the use of hard coded timeout value.
  mmc: OLPC: update vdd/powerup quirk comment
  mmc: fix spares errors of sdhci.c
  mmc: remove multiwrite capability
  wbsd: fix bad dma_addr_t conversion
  atmel-mci: Driver for Atmel on-chip MMC controllers
  mmc: fix sdio_io sparse errors
  mmc: wbsd.c fix shadowing of 'dma' variable
  MMC: S3C24XX: Refuse incorrectly aligned transfers
  MMC: S3C24XX: Add maintainer entry
  MMC: S3C24XX: Update error debugging.
  MMC: S3C24XX: Add media presence test to request handling.
  MMC: S3C24XX: Fix use of msecs where jiffies are needed
  MMC: S3C24XX: Add MODULE_ALIAS() entries for the platform devices
  MMC: S3C24XX: Fix s3c2410_dma_request() return code check.
  MMC: S3C24XX: Allow card-detect on non-IRQ capable pin
  MMC: S3C24XX: Ensure host->mrq->data is valid
  ...

Manually fixed up bogus executable bits on drivers/mmc/core/sdio_io.c
and include/linux/mmc/sdio_func.h when merging.

45 files changed:
MAINTAINERS
arch/avr32/boards/atngw100/setup.c
arch/avr32/boards/atstk1000/atstk1002.c
arch/avr32/mach-at32ap/at32ap700x.c
drivers/mmc/card/block.c
drivers/mmc/card/mmc_test.c
drivers/mmc/card/sdio_uart.c
drivers/mmc/core/core.c
drivers/mmc/core/mmc.c
drivers/mmc/core/sd.c
drivers/mmc/core/sdio_cis.c
drivers/mmc/core/sdio_io.c
drivers/mmc/host/Kconfig
drivers/mmc/host/Makefile
drivers/mmc/host/at91_mci.c
drivers/mmc/host/atmel-mci-regs.h [new file with mode: 0644]
drivers/mmc/host/atmel-mci.c [new file with mode: 0644]
drivers/mmc/host/au1xmmc.c
drivers/mmc/host/au1xmmc.h [deleted file]
drivers/mmc/host/imxmmc.c
drivers/mmc/host/mmc_spi.c
drivers/mmc/host/mmci.c
drivers/mmc/host/omap.c
drivers/mmc/host/pxamci.c
drivers/mmc/host/s3cmci.c [new file with mode: 0644]
drivers/mmc/host/s3cmci.h [new file with mode: 0644]
drivers/mmc/host/sdhci-pci.c [new file with mode: 0644]
drivers/mmc/host/sdhci.c
drivers/mmc/host/sdhci.h
drivers/mmc/host/sdricoh_cs.c [new file with mode: 0644]
drivers/mmc/host/tifm_sd.c
drivers/mmc/host/wbsd.c
drivers/net/wireless/libertas/if_sdio.c
include/asm-arm/arch-at91/at91_mci.h
include/asm-arm/arch-s3c2410/regs-sdi.h
include/asm-arm/plat-s3c24xx/mci.h [new file with mode: 0644]
include/asm-avr32/arch-at32ap/board.h
include/asm-avr32/atmel-mci.h [new file with mode: 0644]
include/asm-mips/mach-au1x00/au1100_mmc.h
include/linux/mmc/core.h
include/linux/mmc/host.h
include/linux/mmc/mmc.h
include/linux/mmc/sdio_func.h
include/linux/pci_ids.h
include/linux/spi/mmc_spi.h

index 2e535e8de44bb5b80fb148be52158253b4596702..2b9212f90446f288ab1d5f9bf7b1067946790626 100644 (file)
@@ -348,7 +348,9 @@ W:  http://www.linux-usb.org/SpeedTouch/
 S:     Maintained
 
 ALCHEMY AU1XX0 MMC DRIVER
-S:     Orphan
+P:     Manuel Lauss
+M:     manuel.lauss@gmail.com
+S:     Maintained
 
 ALI1563 I2C DRIVER
 P:     Rudolf Marek
@@ -3559,6 +3561,13 @@ L:       linux-s390@vger.kernel.org
 W:     http://www.ibm.com/developerworks/linux/linux390/
 S:     Supported
 
+S3C24XX SD/MMC Driver
+P:     Ben Dooks
+M:     ben-linux@fluff.org
+L:     linux-arm-kernel@lists.arm.linux.org.uk (subscribers-only)
+L:     linux-kernel@vger.kernel.org
+S:     Supported
+
 SAA7146 VIDEO4LINUX-2 DRIVER
 P:     Michael Hunold
 M:     michael@mihu.de
@@ -3631,6 +3640,12 @@ P:       Jim Cromie
 M:     jim.cromie@gmail.com
 S:     Maintained
 
+SDRICOH_CS MMC/SD HOST CONTROLLER INTERFACE DRIVER
+P:     Sascha Sommer
+M:     saschasommer@freenet.de
+L:     sdricohcs-devel@lists.sourceforge.net (subscribers-only)
+S:     Maintained
+
 SECURITY CONTACT
 P:     Security Officers
 M:     security@kernel.org
index a51bb9fb3c89d2047151a5da22d14c7582a43643..c7fe94d03a1eb1358a2d77b2a663bc6a81fba757 100644 (file)
@@ -19,6 +19,7 @@
 #include <linux/leds.h>
 #include <linux/spi/spi.h>
 
+#include <asm/atmel-mci.h>
 #include <asm/io.h>
 #include <asm/setup.h>
 
@@ -51,6 +52,11 @@ static struct spi_board_info spi0_board_info[] __initdata = {
        },
 };
 
+static struct mci_platform_data __initdata mci0_data = {
+       .detect_pin     = GPIO_PIN_PC(25),
+       .wp_pin         = GPIO_PIN_PE(0),
+};
+
 /*
  * The next two functions should go away as the boot loader is
  * supposed to initialize the macb address registers with a valid
@@ -170,6 +176,7 @@ static int __init atngw100_init(void)
        set_hw_addr(at32_add_device_eth(1, &eth_data[1]));
 
        at32_add_device_spi(0, spi0_board_info, ARRAY_SIZE(spi0_board_info));
+       at32_add_device_mci(0, &mci0_data);
        at32_add_device_usba(0, NULL);
 
        for (i = 0; i < ARRAY_SIZE(ngw_leds); i++) {
index 86b363c1c25bea00dfe011425c044d6d91edd0f9..e11659b732fab61d743f5e72b3487b298c5f9646 100644 (file)
@@ -234,6 +234,9 @@ static int __init atstk1002_init(void)
 #ifdef CONFIG_BOARD_ATSTK100X_SPI1
        at32_add_device_spi(1, spi1_board_info, ARRAY_SIZE(spi1_board_info));
 #endif
+#ifndef CONFIG_BOARD_ATSTK1002_SW2_CUSTOM
+       at32_add_device_mci(0, NULL);
+#endif
 #ifdef CONFIG_BOARD_ATSTK1002_SW5_CUSTOM
        set_hw_addr(at32_add_device_eth(1, &eth_data[1]));
 #else
index 07b21b121eef995726a6e8a981f3c416497e997e..021d5121718469387fc1c4d95dd797a12f40bb3b 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/spi/spi.h>
 #include <linux/usb/atmel_usba_udc.h>
 
+#include <asm/atmel-mci.h>
 #include <asm/io.h>
 #include <asm/irq.h>
 
@@ -1278,20 +1279,32 @@ static struct clk atmel_mci0_pclk = {
        .index          = 9,
 };
 
-struct platform_device *__init at32_add_device_mci(unsigned int id)
+struct platform_device *__init
+at32_add_device_mci(unsigned int id, struct mci_platform_data *data)
 {
-       struct platform_device *pdev;
+       struct mci_platform_data        _data;
+       struct platform_device          *pdev;
+       struct dw_dma_slave             *dws;
 
        if (id != 0)
                return NULL;
 
        pdev = platform_device_alloc("atmel_mci", id);
        if (!pdev)
-               return NULL;
+               goto fail;
 
        if (platform_device_add_resources(pdev, atmel_mci0_resource,
                                ARRAY_SIZE(atmel_mci0_resource)))
-               goto err_add_resources;
+               goto fail;
+
+       if (!data) {
+               data = &_data;
+               memset(data, 0, sizeof(struct mci_platform_data));
+       }
+
+       if (platform_device_add_data(pdev, data,
+                               sizeof(struct mci_platform_data)))
+               goto fail;
 
        select_peripheral(PA(10), PERIPH_A, 0); /* CLK   */
        select_peripheral(PA(11), PERIPH_A, 0); /* CMD   */
@@ -1300,12 +1313,19 @@ struct platform_device *__init at32_add_device_mci(unsigned int id)
        select_peripheral(PA(14), PERIPH_A, 0); /* DATA2 */
        select_peripheral(PA(15), PERIPH_A, 0); /* DATA3 */
 
+       if (data) {
+               if (data->detect_pin != GPIO_PIN_NONE)
+                       at32_select_gpio(data->detect_pin, 0);
+               if (data->wp_pin != GPIO_PIN_NONE)
+                       at32_select_gpio(data->wp_pin, 0);
+       }
+
        atmel_mci0_pclk.dev = &pdev->dev;
 
        platform_device_add(pdev);
        return pdev;
 
-err_add_resources:
+fail:
        platform_device_put(pdev);
        return NULL;
 }
index f9ad960d7c1a61305e6f3f1841bc0e8437048a18..66e5a5487c20a228c67793e90d2f95b5dc36e53b 100644 (file)
@@ -2,7 +2,7 @@
  * Block driver for media (i.e., flash cards)
  *
  * Copyright 2002 Hewlett-Packard Company
- * Copyright 2005-2007 Pierre Ossman
+ * Copyright 2005-2008 Pierre Ossman
  *
  * Use consistent with the GNU GPL is permitted,
  * provided that this copyright notice is
@@ -237,17 +237,6 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req)
                if (brq.data.blocks > card->host->max_blk_count)
                        brq.data.blocks = card->host->max_blk_count;
 
-               /*
-                * If the host doesn't support multiple block writes, force
-                * block writes to single block. SD cards are excepted from
-                * this rule as they support querying the number of
-                * successfully written sectors.
-                */
-               if (rq_data_dir(req) != READ &&
-                   !(card->host->caps & MMC_CAP_MULTIWRITE) &&
-                   !mmc_card_sd(card))
-                       brq.data.blocks = 1;
-
                if (brq.data.blocks > 1) {
                        /* SPI multiblock writes terminate using a special
                         * token, not a STOP_TRANSMISSION request.
@@ -296,22 +285,24 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req)
 
                mmc_queue_bounce_post(mq);
 
+               /*
+                * Check for errors here, but don't jump to cmd_err
+                * until later as we need to wait for the card to leave
+                * programming mode even when things go wrong.
+                */
                if (brq.cmd.error) {
                        printk(KERN_ERR "%s: error %d sending read/write command\n",
                               req->rq_disk->disk_name, brq.cmd.error);
-                       goto cmd_err;
                }
 
                if (brq.data.error) {
                        printk(KERN_ERR "%s: error %d transferring data\n",
                               req->rq_disk->disk_name, brq.data.error);
-                       goto cmd_err;
                }
 
                if (brq.stop.error) {
                        printk(KERN_ERR "%s: error %d sending stop command\n",
                               req->rq_disk->disk_name, brq.stop.error);
-                       goto cmd_err;
                }
 
                if (!mmc_host_is_spi(card->host) && rq_data_dir(req) != READ) {
@@ -344,6 +335,9 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req)
 #endif
                }
 
+               if (brq.cmd.error || brq.data.error || brq.stop.error)
+                       goto cmd_err;
+
                /*
                 * A block was successfully transferred.
                 */
@@ -362,30 +356,32 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req)
         * mark the known good sectors as ok.
         *
         * If the card is not SD, we can still ok written sectors
-        * if the controller can do proper error reporting.
+        * as reported by the controller (which might be less than
+        * the real number of written sectors, but never more).
         *
         * For reads we just fail the entire chunk as that should
         * be safe in all cases.
         */
-       if (rq_data_dir(req) != READ && mmc_card_sd(card)) {
-               u32 blocks;
-               unsigned int bytes;
-
-               blocks = mmc_sd_num_wr_blocks(card);
-               if (blocks != (u32)-1) {
-                       if (card->csd.write_partial)
-                               bytes = blocks << md->block_bits;
-                       else
-                               bytes = blocks << 9;
+       if (rq_data_dir(req) != READ) {
+               if (mmc_card_sd(card)) {
+                       u32 blocks;
+                       unsigned int bytes;
+
+                       blocks = mmc_sd_num_wr_blocks(card);
+                       if (blocks != (u32)-1) {
+                               if (card->csd.write_partial)
+                                       bytes = blocks << md->block_bits;
+                               else
+                                       bytes = blocks << 9;
+                               spin_lock_irq(&md->lock);
+                               ret = __blk_end_request(req, 0, bytes);
+                               spin_unlock_irq(&md->lock);
+                       }
+               } else {
                        spin_lock_irq(&md->lock);
-                       ret = __blk_end_request(req, 0, bytes);
+                       ret = __blk_end_request(req, 0, brq.data.bytes_xfered);
                        spin_unlock_irq(&md->lock);
                }
-       } else if (rq_data_dir(req) != READ &&
-                  (card->host->caps & MMC_CAP_MULTIWRITE)) {
-               spin_lock_irq(&md->lock);
-               ret = __blk_end_request(req, 0, brq.data.bytes_xfered);
-               spin_unlock_irq(&md->lock);
        }
 
        mmc_release_host(card->host);
index ffadee549a414cd99a8f3a5c5db71f3554d019c8..d6b9b486417cc702acb18bba2fd413baedf592d9 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  linux/drivers/mmc/card/mmc_test.c
  *
- *  Copyright 2007 Pierre Ossman
+ *  Copyright 2007-2008 Pierre Ossman
  *
  * 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
 struct mmc_test_card {
        struct mmc_card *card;
 
+       u8              scratch[BUFFER_SIZE];
        u8              *buffer;
 };
 
 /*******************************************************************/
-/*  Helper functions                                               */
+/*  General helper functions                                       */
 /*******************************************************************/
 
+/*
+ * Configure correct block size in card
+ */
 static int mmc_test_set_blksize(struct mmc_test_card *test, unsigned size)
 {
        struct mmc_command cmd;
@@ -48,117 +52,61 @@ static int mmc_test_set_blksize(struct mmc_test_card *test, unsigned size)
        return 0;
 }
 
-static int __mmc_test_transfer(struct mmc_test_card *test, int write,
-       unsigned broken_xfer, u8 *buffer, unsigned addr,
-       unsigned blocks, unsigned blksz)
+/*
+ * Fill in the mmc_request structure given a set of transfer parameters.
+ */
+static void mmc_test_prepare_mrq(struct mmc_test_card *test,
+       struct mmc_request *mrq, struct scatterlist *sg, unsigned sg_len,
+       unsigned dev_addr, unsigned blocks, unsigned blksz, int write)
 {
-       int ret, busy;
-
-       struct mmc_request mrq;
-       struct mmc_command cmd;
-       struct mmc_command stop;
-       struct mmc_data data;
-
-       struct scatterlist sg;
-
-       memset(&mrq, 0, sizeof(struct mmc_request));
-
-       mrq.cmd = &cmd;
-       mrq.data = &data;
-
-       memset(&cmd, 0, sizeof(struct mmc_command));
+       BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
 
-       if (broken_xfer) {
-               if (blocks > 1) {
-                       cmd.opcode = write ?
-                               MMC_WRITE_BLOCK : MMC_READ_SINGLE_BLOCK;
-               } else {
-                       cmd.opcode = MMC_SEND_STATUS;
-               }
+       if (blocks > 1) {
+               mrq->cmd->opcode = write ?
+                       MMC_WRITE_MULTIPLE_BLOCK : MMC_READ_MULTIPLE_BLOCK;
        } else {
-               if (blocks > 1) {
-                       cmd.opcode = write ?
-                               MMC_WRITE_MULTIPLE_BLOCK : MMC_READ_MULTIPLE_BLOCK;
-               } else {
-                       cmd.opcode = write ?
-                               MMC_WRITE_BLOCK : MMC_READ_SINGLE_BLOCK;
-               }
+               mrq->cmd->opcode = write ?
+                       MMC_WRITE_BLOCK : MMC_READ_SINGLE_BLOCK;
        }
 
-       if (broken_xfer && blocks == 1)
-               cmd.arg = test->card->rca << 16;
-       else
-               cmd.arg = addr;
-       cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
+       mrq->cmd->arg = dev_addr;
+       mrq->cmd->flags = MMC_RSP_R1 | MMC_CMD_ADTC;
 
-       memset(&stop, 0, sizeof(struct mmc_command));
-
-       if (!broken_xfer && (blocks > 1)) {
-               stop.opcode = MMC_STOP_TRANSMISSION;
-               stop.arg = 0;
-               stop.flags = MMC_RSP_R1B | MMC_CMD_AC;
-
-               mrq.stop = &stop;
+       if (blocks == 1)
+               mrq->stop = NULL;
+       else {
+               mrq->stop->opcode = MMC_STOP_TRANSMISSION;
+               mrq->stop->arg = 0;
+               mrq->stop->flags = MMC_RSP_R1B | MMC_CMD_AC;
        }
 
-       memset(&data, 0, sizeof(struct mmc_data));
-
-       data.blksz = blksz;
-       data.blocks = blocks;
-       data.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ;
-       data.sg = &sg;
-       data.sg_len = 1;
-
-       sg_init_one(&sg, buffer, blocks * blksz);
-
-       mmc_set_data_timeout(&data, test->card);
+       mrq->data->blksz = blksz;
+       mrq->data->blocks = blocks;
+       mrq->data->flags = write ? MMC_DATA_WRITE : MMC_DATA_READ;
+       mrq->data->sg = sg;
+       mrq->data->sg_len = sg_len;
 
-       mmc_wait_for_req(test->card->host, &mrq);
-
-       ret = 0;
-
-       if (broken_xfer) {
-               if (!ret && cmd.error)
-                       ret = cmd.error;
-               if (!ret && data.error == 0)
-                       ret = RESULT_FAIL;
-               if (!ret && data.error != -ETIMEDOUT)
-                       ret = data.error;
-               if (!ret && stop.error)
-                       ret = stop.error;
-               if (blocks > 1) {
-                       if (!ret && data.bytes_xfered > blksz)
-                               ret = RESULT_FAIL;
-               } else {
-                       if (!ret && data.bytes_xfered > 0)
-                               ret = RESULT_FAIL;
-               }
-       } else {
-               if (!ret && cmd.error)
-                       ret = cmd.error;
-               if (!ret && data.error)
-                       ret = data.error;
-               if (!ret && stop.error)
-                       ret = stop.error;
-               if (!ret && data.bytes_xfered != blocks * blksz)
-                       ret = RESULT_FAIL;
-       }
+       mmc_set_data_timeout(mrq->data, test->card);
+}
 
-       if (ret == -EINVAL)
-               ret = RESULT_UNSUP_HOST;
+/*
+ * Wait for the card to finish the busy state
+ */
+static int mmc_test_wait_busy(struct mmc_test_card *test)
+{
+       int ret, busy;
+       struct mmc_command cmd;
 
        busy = 0;
        do {
-               int ret2;
-
                memset(&cmd, 0, sizeof(struct mmc_command));
 
                cmd.opcode = MMC_SEND_STATUS;
                cmd.arg = test->card->rca << 16;
                cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
 
-               ret2 = mmc_wait_for_cmd(test->card->host, &cmd, 0);
-               if (ret2)
+               ret = mmc_wait_for_cmd(test->card->host, &cmd, 0);
+               if (ret)
                        break;
 
                if (!busy && !(cmd.resp[0] & R1_READY_FOR_DATA)) {
@@ -172,14 +120,57 @@ static int __mmc_test_transfer(struct mmc_test_card *test, int write,
        return ret;
 }
 
-static int mmc_test_transfer(struct mmc_test_card *test, int write,
-       u8 *buffer, unsigned addr, unsigned blocks, unsigned blksz)
+/*
+ * Transfer a single sector of kernel addressable data
+ */
+static int mmc_test_buffer_transfer(struct mmc_test_card *test,
+       u8 *buffer, unsigned addr, unsigned blksz, int write)
 {
-       return __mmc_test_transfer(test, write, 0, buffer,
-                       addr, blocks, blksz);
+       int ret;
+
+       struct mmc_request mrq;
+       struct mmc_command cmd;
+       struct mmc_command stop;
+       struct mmc_data data;
+
+       struct scatterlist sg;
+
+       memset(&mrq, 0, sizeof(struct mmc_request));
+       memset(&cmd, 0, sizeof(struct mmc_command));
+       memset(&data, 0, sizeof(struct mmc_data));
+       memset(&stop, 0, sizeof(struct mmc_command));
+
+       mrq.cmd = &cmd;
+       mrq.data = &data;
+       mrq.stop = &stop;
+
+       sg_init_one(&sg, buffer, blksz);
+
+       mmc_test_prepare_mrq(test, &mrq, &sg, 1, addr, 1, blksz, write);
+
+       mmc_wait_for_req(test->card->host, &mrq);
+
+       if (cmd.error)
+               return cmd.error;
+       if (data.error)
+               return data.error;
+
+       ret = mmc_test_wait_busy(test);
+       if (ret)
+               return ret;
+
+       return 0;
 }
 
-static int mmc_test_prepare_verify(struct mmc_test_card *test, int write)
+/*******************************************************************/
+/*  Test preparation and cleanup                                   */
+/*******************************************************************/
+
+/*
+ * Fill the first couple of sectors of the card with known data
+ * so that bad reads/writes can be detected
+ */
+static int __mmc_test_prepare(struct mmc_test_card *test, int write)
 {
        int ret, i;
 
@@ -188,15 +179,14 @@ static int mmc_test_prepare_verify(struct mmc_test_card *test, int write)
                return ret;
 
        if (write)
-               memset(test->buffer, 0xDF, BUFFER_SIZE);
+               memset(test->buffer, 0xDF, 512);
        else {
-               for (i = 0;i < BUFFER_SIZE;i++)
+               for (i = 0;i < 512;i++)
                        test->buffer[i] = i;
        }
 
        for (i = 0;i < BUFFER_SIZE / 512;i++) {
-               ret = mmc_test_transfer(test, 1, test->buffer + i * 512,
-                       i * 512, 1, 512);
+               ret = mmc_test_buffer_transfer(test, test->buffer, i * 512, 512, 1);
                if (ret)
                        return ret;
        }
@@ -204,41 +194,218 @@ static int mmc_test_prepare_verify(struct mmc_test_card *test, int write)
        return 0;
 }
 
-static int mmc_test_prepare_verify_write(struct mmc_test_card *test)
+static int mmc_test_prepare_write(struct mmc_test_card *test)
+{
+       return __mmc_test_prepare(test, 1);
+}
+
+static int mmc_test_prepare_read(struct mmc_test_card *test)
+{
+       return __mmc_test_prepare(test, 0);
+}
+
+static int mmc_test_cleanup(struct mmc_test_card *test)
 {
-       return mmc_test_prepare_verify(test, 1);
+       int ret, i;
+
+       ret = mmc_test_set_blksize(test, 512);
+       if (ret)
+               return ret;
+
+       memset(test->buffer, 0, 512);
+
+       for (i = 0;i < BUFFER_SIZE / 512;i++) {
+               ret = mmc_test_buffer_transfer(test, test->buffer, i * 512, 512, 1);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
 }
 
-static int mmc_test_prepare_verify_read(struct mmc_test_card *test)
+/*******************************************************************/
+/*  Test execution helpers                                         */
+/*******************************************************************/
+
+/*
+ * Modifies the mmc_request to perform the "short transfer" tests
+ */
+static void mmc_test_prepare_broken_mrq(struct mmc_test_card *test,
+       struct mmc_request *mrq, int write)
 {
-       return mmc_test_prepare_verify(test, 0);
+       BUG_ON(!mrq || !mrq->cmd || !mrq->data);
+
+       if (mrq->data->blocks > 1) {
+               mrq->cmd->opcode = write ?
+                       MMC_WRITE_BLOCK : MMC_READ_SINGLE_BLOCK;
+               mrq->stop = NULL;
+       } else {
+               mrq->cmd->opcode = MMC_SEND_STATUS;
+               mrq->cmd->arg = test->card->rca << 16;
+       }
 }
 
-static int mmc_test_verified_transfer(struct mmc_test_card *test, int write,
-       u8 *buffer, unsigned addr, unsigned blocks, unsigned blksz)
+/*
+ * Checks that a normal transfer didn't have any errors
+ */
+static int mmc_test_check_result(struct mmc_test_card *test,
+       struct mmc_request *mrq)
 {
-       int ret, i, sectors;
+       int ret;
 
-       /*
-        * It is assumed that the above preparation has been done.
-        */
+       BUG_ON(!mrq || !mrq->cmd || !mrq->data);
+
+       ret = 0;
 
-       memset(test->buffer, 0, BUFFER_SIZE);
+       if (!ret && mrq->cmd->error)
+               ret = mrq->cmd->error;
+       if (!ret && mrq->data->error)
+               ret = mrq->data->error;
+       if (!ret && mrq->stop && mrq->stop->error)
+               ret = mrq->stop->error;
+       if (!ret && mrq->data->bytes_xfered !=
+               mrq->data->blocks * mrq->data->blksz)
+               ret = RESULT_FAIL;
+
+       if (ret == -EINVAL)
+               ret = RESULT_UNSUP_HOST;
+
+       return ret;
+}
+
+/*
+ * Checks that a "short transfer" behaved as expected
+ */
+static int mmc_test_check_broken_result(struct mmc_test_card *test,
+       struct mmc_request *mrq)
+{
+       int ret;
+
+       BUG_ON(!mrq || !mrq->cmd || !mrq->data);
+
+       ret = 0;
+
+       if (!ret && mrq->cmd->error)
+               ret = mrq->cmd->error;
+       if (!ret && mrq->data->error == 0)
+               ret = RESULT_FAIL;
+       if (!ret && mrq->data->error != -ETIMEDOUT)
+               ret = mrq->data->error;
+       if (!ret && mrq->stop && mrq->stop->error)
+               ret = mrq->stop->error;
+       if (mrq->data->blocks > 1) {
+               if (!ret && mrq->data->bytes_xfered > mrq->data->blksz)
+                       ret = RESULT_FAIL;
+       } else {
+               if (!ret && mrq->data->bytes_xfered > 0)
+                       ret = RESULT_FAIL;
+       }
+
+       if (ret == -EINVAL)
+               ret = RESULT_UNSUP_HOST;
+
+       return ret;
+}
+
+/*
+ * Tests a basic transfer with certain parameters
+ */
+static int mmc_test_simple_transfer(struct mmc_test_card *test,
+       struct scatterlist *sg, unsigned sg_len, unsigned dev_addr,
+       unsigned blocks, unsigned blksz, int write)
+{
+       struct mmc_request mrq;
+       struct mmc_command cmd;
+       struct mmc_command stop;
+       struct mmc_data data;
+
+       memset(&mrq, 0, sizeof(struct mmc_request));
+       memset(&cmd, 0, sizeof(struct mmc_command));
+       memset(&data, 0, sizeof(struct mmc_data));
+       memset(&stop, 0, sizeof(struct mmc_command));
+
+       mrq.cmd = &cmd;
+       mrq.data = &data;
+       mrq.stop = &stop;
+
+       mmc_test_prepare_mrq(test, &mrq, sg, sg_len, dev_addr,
+               blocks, blksz, write);
+
+       mmc_wait_for_req(test->card->host, &mrq);
+
+       mmc_test_wait_busy(test);
+
+       return mmc_test_check_result(test, &mrq);
+}
+
+/*
+ * Tests a transfer where the card will fail completely or partly
+ */
+static int mmc_test_broken_transfer(struct mmc_test_card *test,
+       unsigned blocks, unsigned blksz, int write)
+{
+       struct mmc_request mrq;
+       struct mmc_command cmd;
+       struct mmc_command stop;
+       struct mmc_data data;
+
+       struct scatterlist sg;
+
+       memset(&mrq, 0, sizeof(struct mmc_request));
+       memset(&cmd, 0, sizeof(struct mmc_command));
+       memset(&data, 0, sizeof(struct mmc_data));
+       memset(&stop, 0, sizeof(struct mmc_command));
+
+       mrq.cmd = &cmd;
+       mrq.data = &data;
+       mrq.stop = &stop;
+
+       sg_init_one(&sg, test->buffer, blocks * blksz);
+
+       mmc_test_prepare_mrq(test, &mrq, &sg, 1, 0, blocks, blksz, write);
+       mmc_test_prepare_broken_mrq(test, &mrq, write);
+
+       mmc_wait_for_req(test->card->host, &mrq);
+
+       mmc_test_wait_busy(test);
+
+       return mmc_test_check_broken_result(test, &mrq);
+}
+
+/*
+ * Does a complete transfer test where data is also validated
+ *
+ * Note: mmc_test_prepare() must have been done before this call
+ */
+static int mmc_test_transfer(struct mmc_test_card *test,
+       struct scatterlist *sg, unsigned sg_len, unsigned dev_addr,
+       unsigned blocks, unsigned blksz, int write)
+{
+       int ret, i;
+       unsigned long flags;
 
        if (write) {
                for (i = 0;i < blocks * blksz;i++)
-                       buffer[i] = i;
+                       test->scratch[i] = i;
+       } else {
+               memset(test->scratch, 0, BUFFER_SIZE);
        }
+       local_irq_save(flags);
+       sg_copy_from_buffer(sg, sg_len, test->scratch, BUFFER_SIZE);
+       local_irq_restore(flags);
 
        ret = mmc_test_set_blksize(test, blksz);
        if (ret)
                return ret;
 
-       ret = mmc_test_transfer(test, write, buffer, addr, blocks, blksz);
+       ret = mmc_test_simple_transfer(test, sg, sg_len, dev_addr,
+               blocks, blksz, write);
        if (ret)
                return ret;
 
        if (write) {
+               int sectors;
+
                ret = mmc_test_set_blksize(test, 512);
                if (ret)
                        return ret;
@@ -253,9 +420,9 @@ static int mmc_test_verified_transfer(struct mmc_test_card *test, int write,
                memset(test->buffer, 0, sectors * 512);
 
                for (i = 0;i < sectors;i++) {
-                       ret = mmc_test_transfer(test, 0,
+                       ret = mmc_test_buffer_transfer(test,
                                test->buffer + i * 512,
-                               addr + i * 512, 1, 512);
+                               dev_addr + i * 512, 512, 0);
                        if (ret)
                                return ret;
                }
@@ -270,8 +437,11 @@ static int mmc_test_verified_transfer(struct mmc_test_card *test, int write,
                                return RESULT_FAIL;
                }
        } else {
+               local_irq_save(flags);
+               sg_copy_to_buffer(sg, sg_len, test->scratch, BUFFER_SIZE);
+               local_irq_restore(flags);
                for (i = 0;i < blocks * blksz;i++) {
-                       if (buffer[i] != (u8)i)
+                       if (test->scratch[i] != (u8)i)
                                return RESULT_FAIL;
                }
        }
@@ -279,26 +449,6 @@ static int mmc_test_verified_transfer(struct mmc_test_card *test, int write,
        return 0;
 }
 
-static int mmc_test_cleanup_verify(struct mmc_test_card *test)
-{
-       int ret, i;
-
-       ret = mmc_test_set_blksize(test, 512);
-       if (ret)
-               return ret;
-
-       memset(test->buffer, 0, BUFFER_SIZE);
-
-       for (i = 0;i < BUFFER_SIZE / 512;i++) {
-               ret = mmc_test_transfer(test, 1, test->buffer + i * 512,
-                       i * 512, 1, 512);
-               if (ret)
-                       return ret;
-       }
-
-       return 0;
-}
-
 /*******************************************************************/
 /*  Tests                                                          */
 /*******************************************************************/
@@ -314,12 +464,15 @@ struct mmc_test_case {
 static int mmc_test_basic_write(struct mmc_test_card *test)
 {
        int ret;
+       struct scatterlist sg;
 
        ret = mmc_test_set_blksize(test, 512);
        if (ret)
                return ret;
 
-       ret = mmc_test_transfer(test, 1, test->buffer, 0, 1, 512);
+       sg_init_one(&sg, test->buffer, 512);
+
+       ret = mmc_test_simple_transfer(test, &sg, 1, 0, 1, 512, 1);
        if (ret)
                return ret;
 
@@ -329,12 +482,15 @@ static int mmc_test_basic_write(struct mmc_test_card *test)
 static int mmc_test_basic_read(struct mmc_test_card *test)
 {
        int ret;
+       struct scatterlist sg;
 
        ret = mmc_test_set_blksize(test, 512);
        if (ret)
                return ret;
 
-       ret = mmc_test_transfer(test, 0, test->buffer, 0, 1, 512);
+       sg_init_one(&sg, test->buffer, 512);
+
+       ret = mmc_test_simple_transfer(test, &sg, 1, 0, 1, 512, 1);
        if (ret)
                return ret;
 
@@ -344,8 +500,11 @@ static int mmc_test_basic_read(struct mmc_test_card *test)
 static int mmc_test_verify_write(struct mmc_test_card *test)
 {
        int ret;
+       struct scatterlist sg;
+
+       sg_init_one(&sg, test->buffer, 512);
 
-       ret = mmc_test_verified_transfer(test, 1, test->buffer, 0, 1, 512);
+       ret = mmc_test_transfer(test, &sg, 1, 0, 1, 512, 1);
        if (ret)
                return ret;
 
@@ -355,8 +514,11 @@ static int mmc_test_verify_write(struct mmc_test_card *test)
 static int mmc_test_verify_read(struct mmc_test_card *test)
 {
        int ret;
+       struct scatterlist sg;
+
+       sg_init_one(&sg, test->buffer, 512);
 
-       ret = mmc_test_verified_transfer(test, 0, test->buffer, 0, 1, 512);
+       ret = mmc_test_transfer(test, &sg, 1, 0, 1, 512, 0);
        if (ret)
                return ret;
 
@@ -367,6 +529,7 @@ static int mmc_test_multi_write(struct mmc_test_card *test)
 {
        int ret;
        unsigned int size;
+       struct scatterlist sg;
 
        if (test->card->host->max_blk_count == 1)
                return RESULT_UNSUP_HOST;
@@ -379,8 +542,9 @@ static int mmc_test_multi_write(struct mmc_test_card *test)
        if (size < 1024)
                return RESULT_UNSUP_HOST;
 
-       ret = mmc_test_verified_transfer(test, 1, test->buffer, 0,
-               size / 512, 512);
+       sg_init_one(&sg, test->buffer, size);
+
+       ret = mmc_test_transfer(test, &sg, 1, 0, size/512, 512, 1);
        if (ret)
                return ret;
 
@@ -391,6 +555,7 @@ static int mmc_test_multi_read(struct mmc_test_card *test)
 {
        int ret;
        unsigned int size;
+       struct scatterlist sg;
 
        if (test->card->host->max_blk_count == 1)
                return RESULT_UNSUP_HOST;
@@ -403,8 +568,9 @@ static int mmc_test_multi_read(struct mmc_test_card *test)
        if (size < 1024)
                return RESULT_UNSUP_HOST;
 
-       ret = mmc_test_verified_transfer(test, 0, test->buffer, 0,
-               size / 512, 512);
+       sg_init_one(&sg, test->buffer, size);
+
+       ret = mmc_test_transfer(test, &sg, 1, 0, size/512, 512, 0);
        if (ret)
                return ret;
 
@@ -414,13 +580,14 @@ static int mmc_test_multi_read(struct mmc_test_card *test)
 static int mmc_test_pow2_write(struct mmc_test_card *test)
 {
        int ret, i;
+       struct scatterlist sg;
 
        if (!test->card->csd.write_partial)
                return RESULT_UNSUP_CARD;
 
        for (i = 1; i < 512;i <<= 1) {
-               ret = mmc_test_verified_transfer(test, 1,
-                       test->buffer, 0, 1, i);
+               sg_init_one(&sg, test->buffer, i);
+               ret = mmc_test_transfer(test, &sg, 1, 0, 1, i, 1);
                if (ret)
                        return ret;
        }
@@ -431,13 +598,14 @@ static int mmc_test_pow2_write(struct mmc_test_card *test)
 static int mmc_test_pow2_read(struct mmc_test_card *test)
 {
        int ret, i;
+       struct scatterlist sg;
 
        if (!test->card->csd.read_partial)
                return RESULT_UNSUP_CARD;
 
        for (i = 1; i < 512;i <<= 1) {
-               ret = mmc_test_verified_transfer(test, 0,
-                       test->buffer, 0, 1, i);
+               sg_init_one(&sg, test->buffer, i);
+               ret = mmc_test_transfer(test, &sg, 1, 0, 1, i, 0);
                if (ret)
                        return ret;
        }
@@ -448,13 +616,14 @@ static int mmc_test_pow2_read(struct mmc_test_card *test)
 static int mmc_test_weird_write(struct mmc_test_card *test)
 {
        int ret, i;
+       struct scatterlist sg;
 
        if (!test->card->csd.write_partial)
                return RESULT_UNSUP_CARD;
 
        for (i = 3; i < 512;i += 7) {
-               ret = mmc_test_verified_transfer(test, 1,
-                       test->buffer, 0, 1, i);
+               sg_init_one(&sg, test->buffer, i);
+               ret = mmc_test_transfer(test, &sg, 1, 0, 1, i, 1);
                if (ret)
                        return ret;
        }
@@ -465,13 +634,14 @@ static int mmc_test_weird_write(struct mmc_test_card *test)
 static int mmc_test_weird_read(struct mmc_test_card *test)
 {
        int ret, i;
+       struct scatterlist sg;
 
        if (!test->card->csd.read_partial)
                return RESULT_UNSUP_CARD;
 
        for (i = 3; i < 512;i += 7) {
-               ret = mmc_test_verified_transfer(test, 0,
-                       test->buffer, 0, 1, i);
+               sg_init_one(&sg, test->buffer, i);
+               ret = mmc_test_transfer(test, &sg, 1, 0, 1, i, 0);
                if (ret)
                        return ret;
        }
@@ -482,10 +652,11 @@ static int mmc_test_weird_read(struct mmc_test_card *test)
 static int mmc_test_align_write(struct mmc_test_card *test)
 {
        int ret, i;
+       struct scatterlist sg;
 
        for (i = 1;i < 4;i++) {
-               ret = mmc_test_verified_transfer(test, 1, test->buffer + i,
-                       0, 1, 512);
+               sg_init_one(&sg, test->buffer + i, 512);
+               ret = mmc_test_transfer(test, &sg, 1, 0, 1, 512, 1);
                if (ret)
                        return ret;
        }
@@ -496,10 +667,11 @@ static int mmc_test_align_write(struct mmc_test_card *test)
 static int mmc_test_align_read(struct mmc_test_card *test)
 {
        int ret, i;
+       struct scatterlist sg;
 
        for (i = 1;i < 4;i++) {
-               ret = mmc_test_verified_transfer(test, 0, test->buffer + i,
-                       0, 1, 512);
+               sg_init_one(&sg, test->buffer + i, 512);
+               ret = mmc_test_transfer(test, &sg, 1, 0, 1, 512, 0);
                if (ret)
                        return ret;
        }
@@ -511,6 +683,7 @@ static int mmc_test_align_multi_write(struct mmc_test_card *test)
 {
        int ret, i;
        unsigned int size;
+       struct scatterlist sg;
 
        if (test->card->host->max_blk_count == 1)
                return RESULT_UNSUP_HOST;
@@ -524,8 +697,8 @@ static int mmc_test_align_multi_write(struct mmc_test_card *test)
                return RESULT_UNSUP_HOST;
 
        for (i = 1;i < 4;i++) {
-               ret = mmc_test_verified_transfer(test, 1, test->buffer + i,
-                       0, size / 512, 512);
+               sg_init_one(&sg, test->buffer + i, size);
+               ret = mmc_test_transfer(test, &sg, 1, 0, size/512, 512, 1);
                if (ret)
                        return ret;
        }
@@ -537,6 +710,7 @@ static int mmc_test_align_multi_read(struct mmc_test_card *test)
 {
        int ret, i;
        unsigned int size;
+       struct scatterlist sg;
 
        if (test->card->host->max_blk_count == 1)
                return RESULT_UNSUP_HOST;
@@ -550,8 +724,8 @@ static int mmc_test_align_multi_read(struct mmc_test_card *test)
                return RESULT_UNSUP_HOST;
 
        for (i = 1;i < 4;i++) {
-               ret = mmc_test_verified_transfer(test, 0, test->buffer + i,
-                       0, size / 512, 512);
+               sg_init_one(&sg, test->buffer + i, size);
+               ret = mmc_test_transfer(test, &sg, 1, 0, size/512, 512, 0);
                if (ret)
                        return ret;
        }
@@ -567,7 +741,7 @@ static int mmc_test_xfersize_write(struct mmc_test_card *test)
        if (ret)
                return ret;
 
-       ret = __mmc_test_transfer(test, 1, 1, test->buffer, 0, 1, 512);
+       ret = mmc_test_broken_transfer(test, 1, 512, 1);
        if (ret)
                return ret;
 
@@ -582,7 +756,7 @@ static int mmc_test_xfersize_read(struct mmc_test_card *test)
        if (ret)
                return ret;
 
-       ret = __mmc_test_transfer(test, 0, 1, test->buffer, 0, 1, 512);
+       ret = mmc_test_broken_transfer(test, 1, 512, 0);
        if (ret)
                return ret;
 
@@ -600,7 +774,7 @@ static int mmc_test_multi_xfersize_write(struct mmc_test_card *test)
        if (ret)
                return ret;
 
-       ret = __mmc_test_transfer(test, 1, 1, test->buffer, 0, 2, 512);
+       ret = mmc_test_broken_transfer(test, 2, 512, 1);
        if (ret)
                return ret;
 
@@ -618,7 +792,7 @@ static int mmc_test_multi_xfersize_read(struct mmc_test_card *test)
        if (ret)
                return ret;
 
-       ret = __mmc_test_transfer(test, 0, 1, test->buffer, 0, 2, 512);
+       ret = mmc_test_broken_transfer(test, 2, 512, 0);
        if (ret)
                return ret;
 
@@ -638,86 +812,86 @@ static const struct mmc_test_case mmc_test_cases[] = {
 
        {
                .name = "Basic write (with data verification)",
-               .prepare = mmc_test_prepare_verify_write,
+               .prepare = mmc_test_prepare_write,
                .run = mmc_test_verify_write,
-               .cleanup = mmc_test_cleanup_verify,
+               .cleanup = mmc_test_cleanup,
        },
 
        {
                .name = "Basic read (with data verification)",
-               .prepare = mmc_test_prepare_verify_read,
+               .prepare = mmc_test_prepare_read,
                .run = mmc_test_verify_read,
-               .cleanup = mmc_test_cleanup_verify,
+               .cleanup = mmc_test_cleanup,
        },
 
        {
                .name = "Multi-block write",
-               .prepare = mmc_test_prepare_verify_write,
+               .prepare = mmc_test_prepare_write,
                .run = mmc_test_multi_write,
-               .cleanup = mmc_test_cleanup_verify,
+               .cleanup = mmc_test_cleanup,
        },
 
        {
                .name = "Multi-block read",
-               .prepare = mmc_test_prepare_verify_read,
+               .prepare = mmc_test_prepare_read,
                .run = mmc_test_multi_read,
-               .cleanup = mmc_test_cleanup_verify,
+               .cleanup = mmc_test_cleanup,
        },
 
        {
                .name = "Power of two block writes",
-               .prepare = mmc_test_prepare_verify_write,
+               .prepare = mmc_test_prepare_write,
                .run = mmc_test_pow2_write,
-               .cleanup = mmc_test_cleanup_verify,
+               .cleanup = mmc_test_cleanup,
        },
 
        {
                .name = "Power of two block reads",
-               .prepare = mmc_test_prepare_verify_read,
+               .prepare = mmc_test_prepare_read,
                .run = mmc_test_pow2_read,
-               .cleanup = mmc_test_cleanup_verify,
+               .cleanup = mmc_test_cleanup,
        },
 
        {
                .name = "Weird sized block writes",
-               .prepare = mmc_test_prepare_verify_write,
+               .prepare = mmc_test_prepare_write,
                .run = mmc_test_weird_write,
-               .cleanup = mmc_test_cleanup_verify,
+               .cleanup = mmc_test_cleanup,
        },
 
        {
                .name = "Weird sized block reads",
-               .prepare = mmc_test_prepare_verify_read,
+               .prepare = mmc_test_prepare_read,
                .run = mmc_test_weird_read,
-               .cleanup = mmc_test_cleanup_verify,
+               .cleanup = mmc_test_cleanup,
        },
 
        {
                .name = "Badly aligned write",
-               .prepare = mmc_test_prepare_verify_write,
+               .prepare = mmc_test_prepare_write,
                .run = mmc_test_align_write,
-               .cleanup = mmc_test_cleanup_verify,
+               .cleanup = mmc_test_cleanup,
        },
 
        {
                .name = "Badly aligned read",
-               .prepare = mmc_test_prepare_verify_read,
+               .prepare = mmc_test_prepare_read,
                .run = mmc_test_align_read,
-               .cleanup = mmc_test_cleanup_verify,
+               .cleanup = mmc_test_cleanup,
        },
 
        {
                .name = "Badly aligned multi-block write",
-               .prepare = mmc_test_prepare_verify_write,
+               .prepare = mmc_test_prepare_write,
                .run = mmc_test_align_multi_write,
-               .cleanup = mmc_test_cleanup_verify,
+               .cleanup = mmc_test_cleanup,
        },
 
        {
                .name = "Badly aligned multi-block read",
-               .prepare = mmc_test_prepare_verify_read,
+               .prepare = mmc_test_prepare_read,
                .run = mmc_test_align_multi_read,
-               .cleanup = mmc_test_cleanup_verify,
+               .cleanup = mmc_test_cleanup,
        },
 
        {
@@ -743,7 +917,7 @@ static const struct mmc_test_case mmc_test_cases[] = {
 
 static struct mutex mmc_test_lock;
 
-static void mmc_test_run(struct mmc_test_card *test)
+static void mmc_test_run(struct mmc_test_card *test, int testcase)
 {
        int i, ret;
 
@@ -753,6 +927,9 @@ static void mmc_test_run(struct mmc_test_card *test)
        mmc_claim_host(test->card->host);
 
        for (i = 0;i < ARRAY_SIZE(mmc_test_cases);i++) {
+               if (testcase && ((i + 1) != testcase))
+                       continue;
+
                printk(KERN_INFO "%s: Test case %d. %s...\n",
                        mmc_hostname(test->card->host), i + 1,
                        mmc_test_cases[i].name);
@@ -824,9 +1001,12 @@ static ssize_t mmc_test_store(struct device *dev,
 {
        struct mmc_card *card;
        struct mmc_test_card *test;
+       int testcase;
 
        card = container_of(dev, struct mmc_card, dev);
 
+       testcase = simple_strtol(buf, NULL, 10);
+
        test = kzalloc(sizeof(struct mmc_test_card), GFP_KERNEL);
        if (!test)
                return -ENOMEM;
@@ -836,7 +1016,7 @@ static ssize_t mmc_test_store(struct device *dev,
        test->buffer = kzalloc(BUFFER_SIZE, GFP_KERNEL);
        if (test->buffer) {
                mutex_lock(&mmc_test_lock);
-               mmc_test_run(test);
+               mmc_test_run(test, testcase);
                mutex_unlock(&mmc_test_lock);
        }
 
@@ -852,6 +1032,9 @@ static int mmc_test_probe(struct mmc_card *card)
 {
        int ret;
 
+       if ((card->type != MMC_TYPE_MMC) && (card->type != MMC_TYPE_SD))
+               return -ENODEV;
+
        mutex_init(&mmc_test_lock);
 
        ret = device_create_file(&card->dev, &dev_attr_test);
index eeea84c309e691c8a7c838b527b95c7ce50c296c..78ad48718ab028e61b1d77d83e29de8efbe9930c 100644 (file)
@@ -885,12 +885,14 @@ static void sdio_uart_set_termios(struct tty_struct *tty, struct ktermios *old_t
        sdio_uart_release_func(port);
 }
 
-static void sdio_uart_break_ctl(struct tty_struct *tty, int break_state)
+static int sdio_uart_break_ctl(struct tty_struct *tty, int break_state)
 {
        struct sdio_uart_port *port = tty->driver_data;
+       int result;
 
-       if (sdio_uart_claim_func(port) != 0)
-               return;
+       result = sdio_uart_claim_func(port);
+       if (result != 0)
+               return result;
 
        if (break_state == -1)
                port->lcr |= UART_LCR_SBC;
@@ -899,6 +901,7 @@ static void sdio_uart_break_ctl(struct tty_struct *tty, int break_state)
        sdio_out(port, UART_LCR, port->lcr);
 
        sdio_uart_release_func(port);
+       return 0;
 }
 
 static int sdio_uart_tiocmget(struct tty_struct *tty, struct file *file)
index 01ced4c5a61d45016234654c9e0cf3313ad0ff3d..3ee5b8c3b5ce42fa205d3bbc2ee2d5e4f6420794 100644 (file)
@@ -3,7 +3,7 @@
  *
  *  Copyright (C) 2003-2004 Russell King, All Rights Reserved.
  *  SD support Copyright (C) 2004 Ian Molton, All Rights Reserved.
- *  Copyright (C) 2005-2007 Pierre Ossman, All Rights Reserved.
+ *  Copyright (C) 2005-2008 Pierre Ossman, All Rights Reserved.
  *  MMCv4 support Copyright (C) 2006 Philip Langdale, All Rights Reserved.
  *
  * This program is free software; you can redistribute it and/or modify
@@ -294,6 +294,33 @@ void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card)
 }
 EXPORT_SYMBOL(mmc_set_data_timeout);
 
+/**
+ *     mmc_align_data_size - pads a transfer size to a more optimal value
+ *     @card: the MMC card associated with the data transfer
+ *     @sz: original transfer size
+ *
+ *     Pads the original data size with a number of extra bytes in
+ *     order to avoid controller bugs and/or performance hits
+ *     (e.g. some controllers revert to PIO for certain sizes).
+ *
+ *     Returns the improved size, which might be unmodified.
+ *
+ *     Note that this function is only relevant when issuing a
+ *     single scatter gather entry.
+ */
+unsigned int mmc_align_data_size(struct mmc_card *card, unsigned int sz)
+{
+       /*
+        * FIXME: We don't have a system for the controller to tell
+        * the core about its problems yet, so for now we just 32-bit
+        * align the size.
+        */
+       sz = ((sz + 3) / 4) * 4;
+
+       return sz;
+}
+EXPORT_SYMBOL(mmc_align_data_size);
+
 /**
  *     __mmc_claim_host - exclusively claim a host
  *     @host: mmc host to claim
@@ -638,6 +665,9 @@ void mmc_rescan(struct work_struct *work)
                 */
                mmc_bus_put(host);
 
+               if (host->ops->get_cd && host->ops->get_cd(host) == 0)
+                       goto out;
+
                mmc_claim_host(host);
 
                mmc_power_up(host);
@@ -652,7 +682,7 @@ void mmc_rescan(struct work_struct *work)
                if (!err) {
                        if (mmc_attach_sdio(host, ocr))
                                mmc_power_off(host);
-                       return;
+                       goto out;
                }
 
                /*
@@ -662,7 +692,7 @@ void mmc_rescan(struct work_struct *work)
                if (!err) {
                        if (mmc_attach_sd(host, ocr))
                                mmc_power_off(host);
-                       return;
+                       goto out;
                }
 
                /*
@@ -672,7 +702,7 @@ void mmc_rescan(struct work_struct *work)
                if (!err) {
                        if (mmc_attach_mmc(host, ocr))
                                mmc_power_off(host);
-                       return;
+                       goto out;
                }
 
                mmc_release_host(host);
@@ -683,6 +713,9 @@ void mmc_rescan(struct work_struct *work)
 
                mmc_bus_put(host);
        }
+out:
+       if (host->caps & MMC_CAP_NEEDS_POLL)
+               mmc_schedule_delayed_work(&host->detect, HZ);
 }
 
 void mmc_start_host(struct mmc_host *host)
index 3da29eef8f7dbc10640ec57237dc32d3c951183f..fdd7c760be8cff590d00310d620ab4b4fc7e02b3 100644 (file)
@@ -288,7 +288,7 @@ static struct device_type mmc_type = {
 /*
  * Handle the detection and initialisation of a card.
  *
- * In the case of a resume, "curcard" will contain the card
+ * In the case of a resume, "oldcard" will contain the card
  * we're trying to reinitialise.
  */
 static int mmc_init_card(struct mmc_host *host, u32 ocr,
index 7ef3b15c5e3df9dbabb5bfa70923585bf1a6328f..26fc098d77cd5d9261852f34aa902b11e88eaa79 100644 (file)
@@ -326,7 +326,7 @@ static struct device_type sd_type = {
 /*
  * Handle the detection and initialisation of a card.
  *
- * In the case of a resume, "curcard" will contain the card
+ * In the case of a resume, "oldcard" will contain the card
  * we're trying to reinitialise.
  */
 static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
@@ -494,13 +494,13 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
         * Check if read-only switch is active.
         */
        if (!oldcard) {
-               if (!host->ops->get_ro) {
+               if (!host->ops->get_ro || host->ops->get_ro(host) < 0) {
                        printk(KERN_WARNING "%s: host does not "
                                "support reading read-only "
                                "switch. assuming write-enable.\n",
                                mmc_hostname(host));
                } else {
-                       if (host->ops->get_ro(host))
+                       if (host->ops->get_ro(host) > 0)
                                mmc_card_set_readonly(card);
                }
        }
index d5e51b1c7b3fb715ebc81dcad5468c572748d42e..956bd7677502c3c1ebe69d3c03f65641abce060e 100644 (file)
@@ -129,6 +129,12 @@ static int cistpl_funce_func(struct sdio_func *func,
        /* TPLFE_MAX_BLK_SIZE */
        func->max_blksize = buf[12] | (buf[13] << 8);
 
+       /* TPLFE_ENABLE_TIMEOUT_VAL, present in ver 1.1 and above */
+       if (vsn > SDIO_SDIO_REV_1_00)
+               func->enable_timeout = (buf[28] | (buf[29] << 8)) * 10;
+       else
+               func->enable_timeout = jiffies_to_msecs(HZ);
+
        return 0;
 }
 
index 625b92ce9cef2abedbaf853f2e0d2ba6dc7a7314..f61fc2d4cd0a649a02e8e2acc10da53a7ffe7c2a 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  linux/drivers/mmc/core/sdio_io.c
  *
- *  Copyright 2007 Pierre Ossman
+ *  Copyright 2007-2008 Pierre Ossman
  *
  * 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
@@ -76,11 +76,7 @@ int sdio_enable_func(struct sdio_func *func)
        if (ret)
                goto err;
 
-       /*
-        * FIXME: This should timeout based on information in the CIS,
-        * but we don't have card to parse that yet.
-        */
-       timeout = jiffies + HZ;
+       timeout = jiffies + msecs_to_jiffies(func->enable_timeout);
 
        while (1) {
                ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IORx, 0, &reg);
@@ -167,10 +163,8 @@ int sdio_set_block_size(struct sdio_func *func, unsigned blksz)
                return -EINVAL;
 
        if (blksz == 0) {
-               blksz = min(min(
-                       func->max_blksize,
-                       func->card->host->max_blk_size),
-                       512u);
+               blksz = min(func->max_blksize, func->card->host->max_blk_size);
+               blksz = min(blksz, 512u);
        }
 
        ret = mmc_io_rw_direct(func->card, 1, 0,
@@ -186,9 +180,116 @@ int sdio_set_block_size(struct sdio_func *func, unsigned blksz)
        func->cur_blksize = blksz;
        return 0;
 }
-
 EXPORT_SYMBOL_GPL(sdio_set_block_size);
 
+/*
+ * Calculate the maximum byte mode transfer size
+ */
+static inline unsigned int sdio_max_byte_size(struct sdio_func *func)
+{
+       unsigned mval = min(func->card->host->max_seg_size,
+                           func->card->host->max_blk_size);
+       mval = min(mval, func->max_blksize);
+       return min(mval, 512u); /* maximum size for byte mode */
+}
+
+/**
+ *     sdio_align_size - pads a transfer size to a more optimal value
+ *     @func: SDIO function
+ *     @sz: original transfer size
+ *
+ *     Pads the original data size with a number of extra bytes in
+ *     order to avoid controller bugs and/or performance hits
+ *     (e.g. some controllers revert to PIO for certain sizes).
+ *
+ *     If possible, it will also adjust the size so that it can be
+ *     handled in just a single request.
+ *
+ *     Returns the improved size, which might be unmodified.
+ */
+unsigned int sdio_align_size(struct sdio_func *func, unsigned int sz)
+{
+       unsigned int orig_sz;
+       unsigned int blk_sz, byte_sz;
+       unsigned chunk_sz;
+
+       orig_sz = sz;
+
+       /*
+        * Do a first check with the controller, in case it
+        * wants to increase the size up to a point where it
+        * might need more than one block.
+        */
+       sz = mmc_align_data_size(func->card, sz);
+
+       /*
+        * If we can still do this with just a byte transfer, then
+        * we're done.
+        */
+       if (sz <= sdio_max_byte_size(func))
+               return sz;
+
+       if (func->card->cccr.multi_block) {
+               /*
+                * Check if the transfer is already block aligned
+                */
+               if ((sz % func->cur_blksize) == 0)
+                       return sz;
+
+               /*
+                * Realign it so that it can be done with one request,
+                * and recheck if the controller still likes it.
+                */
+               blk_sz = ((sz + func->cur_blksize - 1) /
+                       func->cur_blksize) * func->cur_blksize;
+               blk_sz = mmc_align_data_size(func->card, blk_sz);
+
+               /*
+                * This value is only good if it is still just
+                * one request.
+                */
+               if ((blk_sz % func->cur_blksize) == 0)
+                       return blk_sz;
+
+               /*
+                * We failed to do one request, but at least try to
+                * pad the remainder properly.
+                */
+               byte_sz = mmc_align_data_size(func->card,
+                               sz % func->cur_blksize);
+               if (byte_sz <= sdio_max_byte_size(func)) {
+                       blk_sz = sz / func->cur_blksize;
+                       return blk_sz * func->cur_blksize + byte_sz;
+               }
+       } else {
+               /*
+                * We need multiple requests, so first check that the
+                * controller can handle the chunk size;
+                */
+               chunk_sz = mmc_align_data_size(func->card,
+                               sdio_max_byte_size(func));
+               if (chunk_sz == sdio_max_byte_size(func)) {
+                       /*
+                        * Fix up the size of the remainder (if any)
+                        */
+                       byte_sz = orig_sz % chunk_sz;
+                       if (byte_sz) {
+                               byte_sz = mmc_align_data_size(func->card,
+                                               byte_sz);
+                       }
+
+                       return (orig_sz / chunk_sz) * chunk_sz + byte_sz;
+               }
+       }
+
+       /*
+        * The controller is simply incapable of transferring the size
+        * we want in decent manner, so just return the original size.
+        */
+       return orig_sz;
+}
+EXPORT_SYMBOL_GPL(sdio_align_size);
+
 /* Split an arbitrarily sized data transfer into several
  * IO_RW_EXTENDED commands. */
 static int sdio_io_rw_ext_helper(struct sdio_func *func, int write,
@@ -199,14 +300,13 @@ static int sdio_io_rw_ext_helper(struct sdio_func *func, int write,
        int ret;
 
        /* Do the bulk of the transfer using block mode (if supported). */
-       if (func->card->cccr.multi_block) {
+       if (func->card->cccr.multi_block && (size > sdio_max_byte_size(func))) {
                /* Blocks per command is limited by host count, host transfer
                 * size (we only use a single sg entry) and the maximum for
                 * IO_RW_EXTENDED of 511 blocks. */
-               max_blocks = min(min(
-                       func->card->host->max_blk_count,
-                       func->card->host->max_seg_size / func->cur_blksize),
-                       511u);
+               max_blocks = min(func->card->host->max_blk_count,
+                       func->card->host->max_seg_size / func->cur_blksize);
+               max_blocks = min(max_blocks, 511u);
 
                while (remainder > func->cur_blksize) {
                        unsigned blocks;
@@ -231,11 +331,7 @@ static int sdio_io_rw_ext_helper(struct sdio_func *func, int write,
 
        /* Write the remainder using byte mode. */
        while (remainder > 0) {
-               size = remainder;
-               if (size > func->cur_blksize)
-                       size = func->cur_blksize;
-               if (size > 512)
-                       size = 512; /* maximum size for byte mode */
+               size = min(remainder, sdio_max_byte_size(func));
 
                ret = mmc_io_rw_extended(func->card, write, func->num, addr,
                         incr_addr, buf, 1, size);
@@ -260,11 +356,10 @@ static int sdio_io_rw_ext_helper(struct sdio_func *func, int write,
  *     function. If there is a problem reading the address, 0xff
  *     is returned and @err_ret will contain the error code.
  */
-unsigned char sdio_readb(struct sdio_func *func, unsigned int addr,
-       int *err_ret)
+u8 sdio_readb(struct sdio_func *func, unsigned int addr, int *err_ret)
 {
        int ret;
-       unsigned char val;
+       u8 val;
 
        BUG_ON(!func);
 
@@ -293,8 +388,7 @@ EXPORT_SYMBOL_GPL(sdio_readb);
  *     function. @err_ret will contain the status of the actual
  *     transfer.
  */
-void sdio_writeb(struct sdio_func *func, unsigned char b, unsigned int addr,
-       int *err_ret)
+void sdio_writeb(struct sdio_func *func, u8 b, unsigned int addr, int *err_ret)
 {
        int ret;
 
@@ -355,7 +449,6 @@ int sdio_readsb(struct sdio_func *func, void *dst, unsigned int addr,
 {
        return sdio_io_rw_ext_helper(func, 0, addr, 0, dst, count);
 }
-
 EXPORT_SYMBOL_GPL(sdio_readsb);
 
 /**
@@ -385,8 +478,7 @@ EXPORT_SYMBOL_GPL(sdio_writesb);
  *     function. If there is a problem reading the address, 0xffff
  *     is returned and @err_ret will contain the error code.
  */
-unsigned short sdio_readw(struct sdio_func *func, unsigned int addr,
-       int *err_ret)
+u16 sdio_readw(struct sdio_func *func, unsigned int addr, int *err_ret)
 {
        int ret;
 
@@ -400,7 +492,7 @@ unsigned short sdio_readw(struct sdio_func *func, unsigned int addr,
                return 0xFFFF;
        }
 
-       return le16_to_cpu(*(u16*)func->tmpbuf);
+       return le16_to_cpup((__le16 *)func->tmpbuf);
 }
 EXPORT_SYMBOL_GPL(sdio_readw);
 
@@ -415,12 +507,11 @@ EXPORT_SYMBOL_GPL(sdio_readw);
  *     function. @err_ret will contain the status of the actual
  *     transfer.
  */
-void sdio_writew(struct sdio_func *func, unsigned short b, unsigned int addr,
-       int *err_ret)
+void sdio_writew(struct sdio_func *func, u16 b, unsigned int addr, int *err_ret)
 {
        int ret;
 
-       *(u16*)func->tmpbuf = cpu_to_le16(b);
+       *(__le16 *)func->tmpbuf = cpu_to_le16(b);
 
        ret = sdio_memcpy_toio(func, addr, func->tmpbuf, 2);
        if (err_ret)
@@ -439,8 +530,7 @@ EXPORT_SYMBOL_GPL(sdio_writew);
  *     0xffffffff is returned and @err_ret will contain the error
  *     code.
  */
-unsigned long sdio_readl(struct sdio_func *func, unsigned int addr,
-       int *err_ret)
+u32 sdio_readl(struct sdio_func *func, unsigned int addr, int *err_ret)
 {
        int ret;
 
@@ -454,7 +544,7 @@ unsigned long sdio_readl(struct sdio_func *func, unsigned int addr,
                return 0xFFFFFFFF;
        }
 
-       return le32_to_cpu(*(u32*)func->tmpbuf);
+       return le32_to_cpup((__le32 *)func->tmpbuf);
 }
 EXPORT_SYMBOL_GPL(sdio_readl);
 
@@ -469,12 +559,11 @@ EXPORT_SYMBOL_GPL(sdio_readl);
  *     function. @err_ret will contain the status of the actual
  *     transfer.
  */
-void sdio_writel(struct sdio_func *func, unsigned long b, unsigned int addr,
-       int *err_ret)
+void sdio_writel(struct sdio_func *func, u32 b, unsigned int addr, int *err_ret)
 {
        int ret;
 
-       *(u32*)func->tmpbuf = cpu_to_le32(b);
+       *(__le32 *)func->tmpbuf = cpu_to_le32(b);
 
        ret = sdio_memcpy_toio(func, addr, func->tmpbuf, 4);
        if (err_ret)
index dead61754ad76ef23ed532fbc503fe1d334491a9..dc6f2579f85cd81f31e4b1316d2b7d7ba69a127a 100644 (file)
@@ -26,18 +26,31 @@ config MMC_PXA
 
 config MMC_SDHCI
        tristate "Secure Digital Host Controller Interface support"
-       depends on PCI
+       depends on HAS_DMA
        help
-         This select the generic Secure Digital Host Controller Interface.
+         This selects the generic Secure Digital Host Controller Interface.
          It is used by manufacturers such as Texas Instruments(R), Ricoh(R)
          and Toshiba(R). Most controllers found in laptops are of this type.
+
+         If you have a controller with this interface, say Y or M here. You
+         also need to enable an appropriate bus interface.
+
+         If unsure, say N.
+
+config MMC_SDHCI_PCI
+       tristate "SDHCI support on PCI bus"
+       depends on MMC_SDHCI && PCI
+       help
+         This selects the PCI Secure Digital Host Controller Interface.
+         Most controllers found today are PCI devices.
+
          If you have a controller with this interface, say Y or M here.
 
          If unsure, say N.
 
 config MMC_RICOH_MMC
        tristate "Ricoh MMC Controller Disabler  (EXPERIMENTAL)"
-       depends on PCI && EXPERIMENTAL && MMC_SDHCI
+       depends on MMC_SDHCI_PCI
        help
          This selects the disabler for the Ricoh MMC Controller. This
          proprietary controller is unnecessary because the SDHCI driver
@@ -91,6 +104,16 @@ config MMC_AT91
 
          If unsure, say N.
 
+config MMC_ATMELMCI
+       tristate "Atmel Multimedia Card Interface support"
+       depends on AVR32
+       help
+         This selects the Atmel Multimedia Card Interface driver. If
+         you have an AT32 (AVR32) platform with a Multimedia Card
+         slot, say Y or M here.
+
+         If unsure, say N.
+
 config MMC_IMX
        tristate "Motorola i.MX Multimedia Card Interface support"
        depends on ARCH_IMX
@@ -130,3 +153,24 @@ config MMC_SPI
 
          If unsure, or if your system has no SPI master driver, say N.
 
+config MMC_S3C
+       tristate "Samsung S3C SD/MMC Card Interface support"
+       depends on ARCH_S3C2410 && MMC
+       help
+         This selects a driver for the MCI interface found in
+          Samsung's S3C2410, S3C2412, S3C2440, S3C2442 CPUs.
+         If you have a board based on one of those and a MMC/SD
+         slot, say Y or M here.
+
+         If unsure, say N.
+
+config MMC_SDRICOH_CS
+       tristate "MMC/SD driver for Ricoh Bay1Controllers (EXPERIMENTAL)"
+       depends on EXPERIMENTAL && MMC && PCI && PCMCIA
+       help
+         Say Y here if your Notebook reports a Ricoh Bay1Controller PCMCIA
+         card whenever you insert a MMC or SD card into the card slot.
+
+         To compile this driver as a module, choose M here: the
+         module will be called sdricoh_cs.
+
index 3877c87e6da26e699a730959104dc61ce5992290..db52eebfb50ee77e04705e9bb1aca3ab0c29b7ea 100644 (file)
@@ -10,11 +10,15 @@ obj-$(CONFIG_MMC_ARMMMCI)   += mmci.o
 obj-$(CONFIG_MMC_PXA)          += pxamci.o
 obj-$(CONFIG_MMC_IMX)          += imxmmc.o
 obj-$(CONFIG_MMC_SDHCI)                += sdhci.o
+obj-$(CONFIG_MMC_SDHCI_PCI)    += sdhci-pci.o
 obj-$(CONFIG_MMC_RICOH_MMC)    += ricoh_mmc.o
 obj-$(CONFIG_MMC_WBSD)         += wbsd.o
 obj-$(CONFIG_MMC_AU1X)         += au1xmmc.o
 obj-$(CONFIG_MMC_OMAP)         += omap.o
 obj-$(CONFIG_MMC_AT91)         += at91_mci.o
+obj-$(CONFIG_MMC_ATMELMCI)     += atmel-mci.o
 obj-$(CONFIG_MMC_TIFM_SD)      += tifm_sd.o
 obj-$(CONFIG_MMC_SPI)          += mmc_spi.o
+obj-$(CONFIG_MMC_S3C)          += s3cmci.o
+obj-$(CONFIG_MMC_SDRICOH_CS)   += sdricoh_cs.o
 
index 8979ad330a4d5e6e154b4e8be4794a0b28765eba..f15e2064305cd227ab93f7fdaaa8006ceb684e36 100644 (file)
@@ -125,8 +125,71 @@ struct at91mci_host
 
        /* Latest in the scatterlist that has been enabled for transfer */
        int transfer_index;
+
+       /* Timer for timeouts */
+       struct timer_list timer;
 };
 
+/*
+ * Reset the controller and restore most of the state
+ */
+static void at91_reset_host(struct at91mci_host *host)
+{
+       unsigned long flags;
+       u32 mr;
+       u32 sdcr;
+       u32 dtor;
+       u32 imr;
+
+       local_irq_save(flags);
+       imr = at91_mci_read(host, AT91_MCI_IMR);
+
+       at91_mci_write(host, AT91_MCI_IDR, 0xffffffff);
+
+       /* save current state */
+       mr = at91_mci_read(host, AT91_MCI_MR) & 0x7fff;
+       sdcr = at91_mci_read(host, AT91_MCI_SDCR);
+       dtor = at91_mci_read(host, AT91_MCI_DTOR);
+
+       /* reset the controller */
+       at91_mci_write(host, AT91_MCI_CR, AT91_MCI_MCIDIS | AT91_MCI_SWRST);
+
+       /* restore state */
+       at91_mci_write(host, AT91_MCI_CR, AT91_MCI_MCIEN);
+       at91_mci_write(host, AT91_MCI_MR, mr);
+       at91_mci_write(host, AT91_MCI_SDCR, sdcr);
+       at91_mci_write(host, AT91_MCI_DTOR, dtor);
+       at91_mci_write(host, AT91_MCI_IER, imr);
+
+       /* make sure sdio interrupts will fire */
+       at91_mci_read(host, AT91_MCI_SR);
+
+       local_irq_restore(flags);
+}
+
+static void at91_timeout_timer(unsigned long data)
+{
+       struct at91mci_host *host;
+
+       host = (struct at91mci_host *)data;
+
+       if (host->request) {
+               dev_err(host->mmc->parent, "Timeout waiting end of packet\n");
+
+               if (host->cmd && host->cmd->data) {
+                       host->cmd->data->error = -ETIMEDOUT;
+               } else {
+                       if (host->cmd)
+                               host->cmd->error = -ETIMEDOUT;
+                       else
+                               host->request->cmd->error = -ETIMEDOUT;
+               }
+
+               at91_reset_host(host);
+               mmc_request_done(host->mmc, host->request);
+       }
+}
+
 /*
  * Copy from sg to a dma block - used for transfers
  */
@@ -135,9 +198,14 @@ static inline void at91_mci_sg_to_dma(struct at91mci_host *host, struct mmc_data
        unsigned int len, i, size;
        unsigned *dmabuf = host->buffer;
 
-       size = host->total_length;
+       size = data->blksz * data->blocks;
        len = data->sg_len;
 
+       /* AT91SAM926[0/3] Data Write Operation and number of bytes erratum */
+       if (cpu_is_at91sam9260() || cpu_is_at91sam9263())
+               if (host->total_length == 12)
+                       memset(dmabuf, 0, 12);
+
        /*
         * Just loop through all entries. Size might not
         * be the entire list though so make sure that
@@ -159,9 +227,10 @@ static inline void at91_mci_sg_to_dma(struct at91mci_host *host, struct mmc_data
 
                        for (index = 0; index < (amount / 4); index++)
                                *dmabuf++ = swab32(sgbuffer[index]);
-               }
-               else
+               } else {
                        memcpy(dmabuf, sgbuffer, amount);
+                       dmabuf += amount;
+               }
 
                kunmap_atomic(sgbuffer, KM_BIO_SRC_IRQ);
 
@@ -233,11 +302,11 @@ static void at91_mci_pre_dma_read(struct at91mci_host *host)
 
                if (i == 0) {
                        at91_mci_write(host, ATMEL_PDC_RPR, sg->dma_address);
-                       at91_mci_write(host, ATMEL_PDC_RCR, sg->length / 4);
+                       at91_mci_write(host, ATMEL_PDC_RCR, (data->blksz & 0x3) ? sg->length : sg->length / 4);
                }
                else {
                        at91_mci_write(host, ATMEL_PDC_RNPR, sg->dma_address);
-                       at91_mci_write(host, ATMEL_PDC_RNCR, sg->length / 4);
+                       at91_mci_write(host, ATMEL_PDC_RNCR, (data->blksz & 0x3) ? sg->length : sg->length / 4);
                }
        }
 
@@ -277,8 +346,6 @@ static void at91_mci_post_dma_read(struct at91mci_host *host)
 
                dma_unmap_page(NULL, sg->dma_address, sg->length, DMA_FROM_DEVICE);
 
-               data->bytes_xfered += sg->length;
-
                if (cpu_is_at91rm9200()) {      /* AT91RM9200 errata */
                        unsigned int *buffer;
                        int index;
@@ -294,6 +361,8 @@ static void at91_mci_post_dma_read(struct at91mci_host *host)
                }
 
                flush_dcache_page(sg_page(sg));
+
+               data->bytes_xfered += sg->length;
        }
 
        /* Is there another transfer to trigger? */
@@ -334,10 +403,32 @@ static void at91_mci_handle_transmitted(struct at91mci_host *host)
                at91_mci_write(host, AT91_MCI_IER, AT91_MCI_BLKE);
        } else
                at91_mci_write(host, AT91_MCI_IER, AT91_MCI_NOTBUSY);
+}
+
+/*
+ * Update bytes tranfered count during a write operation
+ */
+static void at91_mci_update_bytes_xfered(struct at91mci_host *host)
+{
+       struct mmc_data *data;
+
+       /* always deal with the effective request (and not the current cmd) */
+
+       if (host->request->cmd && host->request->cmd->error != 0)
+               return;
 
-       data->bytes_xfered = host->total_length;
+       if (host->request->data) {
+               data = host->request->data;
+               if (data->flags & MMC_DATA_WRITE) {
+                       /* card is in IDLE mode now */
+                       pr_debug("-> bytes_xfered %d, total_length = %d\n",
+                               data->bytes_xfered, host->total_length);
+                       data->bytes_xfered = data->blksz * data->blocks;
+               }
+       }
 }
 
+
 /*Handle after command sent ready*/
 static int at91_mci_handle_cmdrdy(struct at91mci_host *host)
 {
@@ -350,8 +441,7 @@ static int at91_mci_handle_cmdrdy(struct at91mci_host *host)
                } else return 1;
        } else if (host->cmd->data->flags & MMC_DATA_WRITE) {
                /*After sendding multi-block-write command, start DMA transfer*/
-               at91_mci_write(host, AT91_MCI_IER, AT91_MCI_TXBUFE);
-               at91_mci_write(host, AT91_MCI_IER, AT91_MCI_BLKE);
+               at91_mci_write(host, AT91_MCI_IER, AT91_MCI_TXBUFE | AT91_MCI_BLKE);
                at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_TXTEN);
        }
 
@@ -430,11 +520,19 @@ static void at91_mci_send_command(struct at91mci_host *host, struct mmc_command
 
        if (data) {
 
-               if ( data->blksz & 0x3 ) {
-                       pr_debug("Unsupported block size\n");
-                       cmd->error = -EINVAL;
-                       mmc_request_done(host->mmc, host->request);
-                       return;
+               if (cpu_is_at91rm9200() || cpu_is_at91sam9261()) {
+                       if (data->blksz & 0x3) {
+                               pr_debug("Unsupported block size\n");
+                               cmd->error = -EINVAL;
+                               mmc_request_done(host->mmc, host->request);
+                               return;
+                       }
+                       if (data->flags & MMC_DATA_STREAM) {
+                               pr_debug("Stream commands not supported\n");
+                               cmd->error = -EINVAL;
+                               mmc_request_done(host->mmc, host->request);
+                               return;
+                       }
                }
 
                block_length = data->blksz;
@@ -481,8 +579,16 @@ static void at91_mci_send_command(struct at91mci_host *host, struct mmc_command
                ier = AT91_MCI_CMDRDY;
        } else {
                /* zero block length and PDC mode */
-               mr = at91_mci_read(host, AT91_MCI_MR) & 0x7fff;
-               at91_mci_write(host, AT91_MCI_MR, mr | (block_length << 16) | AT91_MCI_PDCMODE);
+               mr = at91_mci_read(host, AT91_MCI_MR) & 0x5fff;
+               mr |= (data->blksz & 0x3) ? AT91_MCI_PDCFBYTE : 0;
+               mr |= (block_length << 16);
+               mr |= AT91_MCI_PDCMODE;
+               at91_mci_write(host, AT91_MCI_MR, mr);
+
+               if (!(cpu_is_at91rm9200() || cpu_is_at91sam9261()))
+                       at91_mci_write(host, AT91_MCI_BLKR,
+                               AT91_MCI_BLKR_BCNT(blocks) |
+                               AT91_MCI_BLKR_BLKLEN(block_length));
 
                /*
                 * Disable the PDC controller
@@ -508,6 +614,13 @@ static void at91_mci_send_command(struct at91mci_host *host, struct mmc_command
                                 * Handle a write
                                 */
                                host->total_length = block_length * blocks;
+                               /*
+                                * AT91SAM926[0/3] Data Write Operation and
+                                * number of bytes erratum
+                                */
+                               if (cpu_is_at91sam9260 () || cpu_is_at91sam9263())
+                                       if (host->total_length < 12)
+                                               host->total_length = 12;
                                host->buffer = dma_alloc_coherent(NULL,
                                                host->total_length,
                                                &host->physical_address, GFP_KERNEL);
@@ -517,7 +630,9 @@ static void at91_mci_send_command(struct at91mci_host *host, struct mmc_command
                                pr_debug("Transmitting %d bytes\n", host->total_length);
 
                                at91_mci_write(host, ATMEL_PDC_TPR, host->physical_address);
-                               at91_mci_write(host, ATMEL_PDC_TCR, host->total_length / 4);
+                               at91_mci_write(host, ATMEL_PDC_TCR, (data->blksz & 0x3) ?
+                                               host->total_length : host->total_length / 4);
+
                                ier = AT91_MCI_CMDRDY;
                        }
                }
@@ -552,20 +667,26 @@ static void at91_mci_process_next(struct at91mci_host *host)
        else if ((!(host->flags & FL_SENT_STOP)) && host->request->stop) {
                host->flags |= FL_SENT_STOP;
                at91_mci_send_command(host, host->request->stop);
-       }
-       else
+       } else {
+               del_timer(&host->timer);
+               /* the at91rm9200 mci controller hangs after some transfers,
+                * and the workaround is to reset it after each transfer.
+                */
+               if (cpu_is_at91rm9200())
+                       at91_reset_host(host);
                mmc_request_done(host->mmc, host->request);
+       }
 }
 
 /*
  * Handle a command that has been completed
  */
-static void at91_mci_completed_command(struct at91mci_host *host)
+static void at91_mci_completed_command(struct at91mci_host *host, unsigned int status)
 {
        struct mmc_command *cmd = host->cmd;
-       unsigned int status;
+       struct mmc_data *data = cmd->data;
 
-       at91_mci_write(host, AT91_MCI_IDR, 0xffffffff);
+       at91_mci_write(host, AT91_MCI_IDR, 0xffffffff & ~(AT91_MCI_SDIOIRQA | AT91_MCI_SDIOIRQB));
 
        cmd->resp[0] = at91_mci_read(host, AT91_MCI_RSPR(0));
        cmd->resp[1] = at91_mci_read(host, AT91_MCI_RSPR(1));
@@ -577,25 +698,34 @@ static void at91_mci_completed_command(struct at91mci_host *host)
                host->buffer = NULL;
        }
 
-       status = at91_mci_read(host, AT91_MCI_SR);
-
-       pr_debug("Status = %08X [%08X %08X %08X %08X]\n",
-                status, cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3]);
+       pr_debug("Status = %08X/%08x [%08X %08X %08X %08X]\n",
+                status, at91_mci_read(host, AT91_MCI_SR),
+                cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3]);
 
        if (status & AT91_MCI_ERRORS) {
                if ((status & AT91_MCI_RCRCE) && !(mmc_resp_type(cmd) & MMC_RSP_CRC)) {
                        cmd->error = 0;
                }
                else {
-                       if (status & (AT91_MCI_RTOE | AT91_MCI_DTOE))
-                               cmd->error = -ETIMEDOUT;
-                       else if (status & (AT91_MCI_RCRCE | AT91_MCI_DCRCE))
-                               cmd->error = -EILSEQ;
-                       else
-                               cmd->error = -EIO;
+                       if (status & (AT91_MCI_DTOE | AT91_MCI_DCRCE)) {
+                               if (data) {
+                                       if (status & AT91_MCI_DTOE)
+                                               data->error = -ETIMEDOUT;
+                                       else if (status & AT91_MCI_DCRCE)
+                                               data->error = -EILSEQ;
+                               }
+                       } else {
+                               if (status & AT91_MCI_RTOE)
+                                       cmd->error = -ETIMEDOUT;
+                               else if (status & AT91_MCI_RCRCE)
+                                       cmd->error = -EILSEQ;
+                               else
+                                       cmd->error = -EIO;
+                       }
 
-                       pr_debug("Error detected and set to %d (cmd = %d, retries = %d)\n",
-                                cmd->error, cmd->opcode, cmd->retries);
+                       pr_debug("Error detected and set to %d/%d (cmd = %d, retries = %d)\n",
+                               cmd->error, data ? data->error : 0,
+                                cmd->opcode, cmd->retries);
                }
        }
        else
@@ -613,6 +743,8 @@ static void at91_mci_request(struct mmc_host *mmc, struct mmc_request *mrq)
        host->request = mrq;
        host->flags = 0;
 
+       mod_timer(&host->timer, jiffies +  HZ);
+
        at91_mci_process_next(host);
 }
 
@@ -736,6 +868,7 @@ static irqreturn_t at91_mci_irq(int irq, void *devid)
 
                if (int_status & AT91_MCI_NOTBUSY) {
                        pr_debug("Card is ready\n");
+                       at91_mci_update_bytes_xfered(host);
                        completed = 1;
                }
 
@@ -744,9 +877,21 @@ static irqreturn_t at91_mci_irq(int irq, void *devid)
 
                if (int_status & AT91_MCI_BLKE) {
                        pr_debug("Block transfer has ended\n");
-                       completed = 1;
+                       if (host->request->data && host->request->data->blocks > 1) {
+                               /* multi block write : complete multi write
+                                * command and send stop */
+                               completed = 1;
+                       } else {
+                               at91_mci_write(host, AT91_MCI_IER, AT91_MCI_NOTBUSY);
+                       }
                }
 
+               if (int_status & AT91_MCI_SDIOIRQA)
+                       mmc_signal_sdio_irq(host->mmc);
+
+               if (int_status & AT91_MCI_SDIOIRQB)
+                       mmc_signal_sdio_irq(host->mmc);
+
                if (int_status & AT91_MCI_TXRDY)
                        pr_debug("Ready to transmit\n");
 
@@ -761,10 +906,10 @@ static irqreturn_t at91_mci_irq(int irq, void *devid)
 
        if (completed) {
                pr_debug("Completed command\n");
-               at91_mci_write(host, AT91_MCI_IDR, 0xffffffff);
-               at91_mci_completed_command(host);
+               at91_mci_write(host, AT91_MCI_IDR, 0xffffffff & ~(AT91_MCI_SDIOIRQA | AT91_MCI_SDIOIRQB));
+               at91_mci_completed_command(host, int_status);
        } else
-               at91_mci_write(host, AT91_MCI_IDR, int_status);
+               at91_mci_write(host, AT91_MCI_IDR, int_status & ~(AT91_MCI_SDIOIRQA | AT91_MCI_SDIOIRQB));
 
        return IRQ_HANDLED;
 }
@@ -793,25 +938,33 @@ static irqreturn_t at91_mmc_det_irq(int irq, void *_host)
 
 static int at91_mci_get_ro(struct mmc_host *mmc)
 {
-       int read_only = 0;
        struct at91mci_host *host = mmc_priv(mmc);
 
-       if (host->board->wp_pin) {
-               read_only = gpio_get_value(host->board->wp_pin);
-               printk(KERN_WARNING "%s: card is %s\n", mmc_hostname(mmc),
-                               (read_only ? "read-only" : "read-write") );
-       }
-       else {
-               printk(KERN_WARNING "%s: host does not support reading read-only "
-                               "switch.  Assuming write-enable.\n", mmc_hostname(mmc));
-       }
-       return read_only;
+       if (host->board->wp_pin)
+               return !!gpio_get_value(host->board->wp_pin);
+       /*
+        * Board doesn't support read only detection; let the mmc core
+        * decide what to do.
+        */
+       return -ENOSYS;
+}
+
+static void at91_mci_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+       struct at91mci_host *host = mmc_priv(mmc);
+
+       pr_debug("%s: sdio_irq %c : %s\n", mmc_hostname(host->mmc),
+               host->board->slot_b ? 'B':'A', enable ? "enable" : "disable");
+       at91_mci_write(host, enable ? AT91_MCI_IER : AT91_MCI_IDR,
+               host->board->slot_b ? AT91_MCI_SDIOIRQB : AT91_MCI_SDIOIRQA);
+
 }
 
 static const struct mmc_host_ops at91_mci_ops = {
        .request        = at91_mci_request,
        .set_ios        = at91_mci_set_ios,
        .get_ro         = at91_mci_get_ro,
+       .enable_sdio_irq = at91_mci_enable_sdio_irq,
 };
 
 /*
@@ -842,6 +995,7 @@ static int __init at91_mci_probe(struct platform_device *pdev)
        mmc->f_min = 375000;
        mmc->f_max = 25000000;
        mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+       mmc->caps = MMC_CAP_SDIO_IRQ;
 
        mmc->max_blk_size = 4095;
        mmc->max_blk_count = mmc->max_req_size;
@@ -935,6 +1089,8 @@ static int __init at91_mci_probe(struct platform_device *pdev)
 
        mmc_add_host(mmc);
 
+       setup_timer(&host->timer, at91_timeout_timer, (unsigned long)host);
+
        /*
         * monitor card insertion/removal if we can
         */
@@ -995,6 +1151,7 @@ static int __exit at91_mci_remove(struct platform_device *pdev)
        }
 
        at91_mci_disable(host);
+       del_timer_sync(&host->timer);
        mmc_remove_host(mmc);
        free_irq(host->irq, host);
 
diff --git a/drivers/mmc/host/atmel-mci-regs.h b/drivers/mmc/host/atmel-mci-regs.h
new file mode 100644 (file)
index 0000000..a9a5657
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Atmel MultiMedia Card Interface driver
+ *
+ * Copyright (C) 2004-2006 Atmel Corporation
+ *
+ * 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.
+ */
+#ifndef __DRIVERS_MMC_ATMEL_MCI_H__
+#define __DRIVERS_MMC_ATMEL_MCI_H__
+
+/* MCI Register Definitions */
+#define MCI_CR                 0x0000  /* Control */
+# define MCI_CR_MCIEN          (  1 <<  0)     /* MCI Enable */
+# define MCI_CR_MCIDIS         (  1 <<  1)     /* MCI Disable */
+# define MCI_CR_SWRST          (  1 <<  7)     /* Software Reset */
+#define MCI_MR                 0x0004  /* Mode */
+# define MCI_MR_CLKDIV(x)      ((x) <<  0)     /* Clock Divider */
+# define MCI_MR_RDPROOF                (  1 << 11)     /* Read Proof */
+# define MCI_MR_WRPROOF                (  1 << 12)     /* Write Proof */
+#define MCI_DTOR               0x0008  /* Data Timeout */
+# define MCI_DTOCYC(x)         ((x) <<  0)     /* Data Timeout Cycles */
+# define MCI_DTOMUL(x)         ((x) <<  4)     /* Data Timeout Multiplier */
+#define MCI_SDCR               0x000c  /* SD Card / SDIO */
+# define MCI_SDCSEL_SLOT_A     (  0 <<  0)     /* Select SD slot A */
+# define MCI_SDCSEL_SLOT_B     (  1 <<  0)     /* Select SD slot A */
+# define MCI_SDCBUS_1BIT       (  0 <<  7)     /* 1-bit data bus */
+# define MCI_SDCBUS_4BIT       (  1 <<  7)     /* 4-bit data bus */
+#define MCI_ARGR               0x0010  /* Command Argument */
+#define MCI_CMDR               0x0014  /* Command */
+# define MCI_CMDR_CMDNB(x)     ((x) <<  0)     /* Command Opcode */
+# define MCI_CMDR_RSPTYP_NONE  (  0 <<  6)     /* No response */
+# define MCI_CMDR_RSPTYP_48BIT (  1 <<  6)     /* 48-bit response */
+# define MCI_CMDR_RSPTYP_136BIT        (  2 <<  6)     /* 136-bit response */
+# define MCI_CMDR_SPCMD_INIT   (  1 <<  8)     /* Initialization command */
+# define MCI_CMDR_SPCMD_SYNC   (  2 <<  8)     /* Synchronized command */
+# define MCI_CMDR_SPCMD_INT    (  4 <<  8)     /* Interrupt command */
+# define MCI_CMDR_SPCMD_INTRESP        (  5 <<  8)     /* Interrupt response */
+# define MCI_CMDR_OPDCMD       (  1 << 11)     /* Open Drain */
+# define MCI_CMDR_MAXLAT_5CYC  (  0 << 12)     /* Max latency 5 cycles */
+# define MCI_CMDR_MAXLAT_64CYC (  1 << 12)     /* Max latency 64 cycles */
+# define MCI_CMDR_START_XFER   (  1 << 16)     /* Start data transfer */
+# define MCI_CMDR_STOP_XFER    (  2 << 16)     /* Stop data transfer */
+# define MCI_CMDR_TRDIR_WRITE  (  0 << 18)     /* Write data */
+# define MCI_CMDR_TRDIR_READ   (  1 << 18)     /* Read data */
+# define MCI_CMDR_BLOCK                (  0 << 19)     /* Single-block transfer */
+# define MCI_CMDR_MULTI_BLOCK  (  1 << 19)     /* Multi-block transfer */
+# define MCI_CMDR_STREAM       (  2 << 19)     /* MMC Stream transfer */
+# define MCI_CMDR_SDIO_BYTE    (  4 << 19)     /* SDIO Byte transfer */
+# define MCI_CMDR_SDIO_BLOCK   (  5 << 19)     /* SDIO Block transfer */
+# define MCI_CMDR_SDIO_SUSPEND (  1 << 24)     /* SDIO Suspend Command */
+# define MCI_CMDR_SDIO_RESUME  (  2 << 24)     /* SDIO Resume Command */
+#define MCI_BLKR               0x0018  /* Block */
+# define MCI_BCNT(x)           ((x) <<  0)     /* Data Block Count */
+# define MCI_BLKLEN(x)         ((x) << 16)     /* Data Block Length */
+#define MCI_RSPR               0x0020  /* Response 0 */
+#define MCI_RSPR1              0x0024  /* Response 1 */
+#define MCI_RSPR2              0x0028  /* Response 2 */
+#define MCI_RSPR3              0x002c  /* Response 3 */
+#define MCI_RDR                        0x0030  /* Receive Data */
+#define MCI_TDR                        0x0034  /* Transmit Data */
+#define MCI_SR                 0x0040  /* Status */
+#define MCI_IER                        0x0044  /* Interrupt Enable */
+#define MCI_IDR                        0x0048  /* Interrupt Disable */
+#define MCI_IMR                        0x004c  /* Interrupt Mask */
+# define MCI_CMDRDY            (  1 <<   0)    /* Command Ready */
+# define MCI_RXRDY             (  1 <<   1)    /* Receiver Ready */
+# define MCI_TXRDY             (  1 <<   2)    /* Transmitter Ready */
+# define MCI_BLKE              (  1 <<   3)    /* Data Block Ended */
+# define MCI_DTIP              (  1 <<   4)    /* Data Transfer In Progress */
+# define MCI_NOTBUSY           (  1 <<   5)    /* Data Not Busy */
+# define MCI_SDIOIRQA          (  1 <<   8)    /* SDIO IRQ in slot A */
+# define MCI_SDIOIRQB          (  1 <<   9)    /* SDIO IRQ in slot B */
+# define MCI_RINDE             (  1 <<  16)    /* Response Index Error */
+# define MCI_RDIRE             (  1 <<  17)    /* Response Direction Error */
+# define MCI_RCRCE             (  1 <<  18)    /* Response CRC Error */
+# define MCI_RENDE             (  1 <<  19)    /* Response End Bit Error */
+# define MCI_RTOE              (  1 <<  20)    /* Response Time-Out Error */
+# define MCI_DCRCE             (  1 <<  21)    /* Data CRC Error */
+# define MCI_DTOE              (  1 <<  22)    /* Data Time-Out Error */
+# define MCI_OVRE              (  1 <<  30)    /* RX Overrun Error */
+# define MCI_UNRE              (  1 <<  31)    /* TX Underrun Error */
+
+/* Register access macros */
+#define mci_readl(port,reg)                            \
+       __raw_readl((port)->regs + MCI_##reg)
+#define mci_writel(port,reg,value)                     \
+       __raw_writel((value), (port)->regs + MCI_##reg)
+
+#endif /* __DRIVERS_MMC_ATMEL_MCI_H__ */
diff --git a/drivers/mmc/host/atmel-mci.c b/drivers/mmc/host/atmel-mci.c
new file mode 100644 (file)
index 0000000..cce873c
--- /dev/null
@@ -0,0 +1,981 @@
+/*
+ * Atmel MultiMedia Card Interface driver
+ *
+ * Copyright (C) 2004-2008 Atmel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/blkdev.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/scatterlist.h>
+
+#include <linux/mmc/host.h>
+
+#include <asm/atmel-mci.h>
+#include <asm/io.h>
+#include <asm/unaligned.h>
+
+#include <asm/arch/board.h>
+#include <asm/arch/gpio.h>
+
+#include "atmel-mci-regs.h"
+
+#define ATMCI_DATA_ERROR_FLAGS (MCI_DCRCE | MCI_DTOE | MCI_OVRE | MCI_UNRE)
+
+enum {
+       EVENT_CMD_COMPLETE = 0,
+       EVENT_DATA_ERROR,
+       EVENT_DATA_COMPLETE,
+       EVENT_STOP_SENT,
+       EVENT_STOP_COMPLETE,
+       EVENT_XFER_COMPLETE,
+};
+
+struct atmel_mci {
+       struct mmc_host         *mmc;
+       void __iomem            *regs;
+
+       struct scatterlist      *sg;
+       unsigned int            pio_offset;
+
+       struct mmc_request      *mrq;
+       struct mmc_command      *cmd;
+       struct mmc_data         *data;
+
+       u32                     cmd_status;
+       u32                     data_status;
+       u32                     stop_status;
+       u32                     stop_cmdr;
+
+       u32                     mode_reg;
+       u32                     sdc_reg;
+
+       struct tasklet_struct   tasklet;
+       unsigned long           pending_events;
+       unsigned long           completed_events;
+
+       int                     present;
+       int                     detect_pin;
+       int                     wp_pin;
+
+       /* For detect pin debouncing */
+       struct timer_list       detect_timer;
+
+       unsigned long           bus_hz;
+       unsigned long           mapbase;
+       struct clk              *mck;
+       struct platform_device  *pdev;
+};
+
+#define atmci_is_completed(host, event)                                \
+       test_bit(event, &host->completed_events)
+#define atmci_test_and_clear_pending(host, event)              \
+       test_and_clear_bit(event, &host->pending_events)
+#define atmci_test_and_set_completed(host, event)              \
+       test_and_set_bit(event, &host->completed_events)
+#define atmci_set_completed(host, event)                       \
+       set_bit(event, &host->completed_events)
+#define atmci_set_pending(host, event)                         \
+       set_bit(event, &host->pending_events)
+#define atmci_clear_pending(host, event)                       \
+       clear_bit(event, &host->pending_events)
+
+
+static void atmci_enable(struct atmel_mci *host)
+{
+       clk_enable(host->mck);
+       mci_writel(host, CR, MCI_CR_MCIEN);
+       mci_writel(host, MR, host->mode_reg);
+       mci_writel(host, SDCR, host->sdc_reg);
+}
+
+static void atmci_disable(struct atmel_mci *host)
+{
+       mci_writel(host, CR, MCI_CR_SWRST);
+
+       /* Stall until write is complete, then disable the bus clock */
+       mci_readl(host, SR);
+       clk_disable(host->mck);
+}
+
+static inline unsigned int ns_to_clocks(struct atmel_mci *host,
+                                       unsigned int ns)
+{
+       return (ns * (host->bus_hz / 1000000) + 999) / 1000;
+}
+
+static void atmci_set_timeout(struct atmel_mci *host,
+                             struct mmc_data *data)
+{
+       static unsigned dtomul_to_shift[] = {
+               0, 4, 7, 8, 10, 12, 16, 20
+       };
+       unsigned        timeout;
+       unsigned        dtocyc;
+       unsigned        dtomul;
+
+       timeout = ns_to_clocks(host, data->timeout_ns) + data->timeout_clks;
+
+       for (dtomul = 0; dtomul < 8; dtomul++) {
+               unsigned shift = dtomul_to_shift[dtomul];
+               dtocyc = (timeout + (1 << shift) - 1) >> shift;
+               if (dtocyc < 15)
+                       break;
+       }
+
+       if (dtomul >= 8) {
+               dtomul = 7;
+               dtocyc = 15;
+       }
+
+       dev_vdbg(&host->mmc->class_dev, "setting timeout to %u cycles\n",
+                       dtocyc << dtomul_to_shift[dtomul]);
+       mci_writel(host, DTOR, (MCI_DTOMUL(dtomul) | MCI_DTOCYC(dtocyc)));
+}
+
+/*
+ * Return mask with command flags to be enabled for this command.
+ */
+static u32 atmci_prepare_command(struct mmc_host *mmc,
+                                struct mmc_command *cmd)
+{
+       struct mmc_data *data;
+       u32             cmdr;
+
+       cmd->error = -EINPROGRESS;
+
+       cmdr = MCI_CMDR_CMDNB(cmd->opcode);
+
+       if (cmd->flags & MMC_RSP_PRESENT) {
+               if (cmd->flags & MMC_RSP_136)
+                       cmdr |= MCI_CMDR_RSPTYP_136BIT;
+               else
+                       cmdr |= MCI_CMDR_RSPTYP_48BIT;
+       }
+
+       /*
+        * This should really be MAXLAT_5 for CMD2 and ACMD41, but
+        * it's too difficult to determine whether this is an ACMD or
+        * not. Better make it 64.
+        */
+       cmdr |= MCI_CMDR_MAXLAT_64CYC;
+
+       if (mmc->ios.bus_mode == MMC_BUSMODE_OPENDRAIN)
+               cmdr |= MCI_CMDR_OPDCMD;
+
+       data = cmd->data;
+       if (data) {
+               cmdr |= MCI_CMDR_START_XFER;
+               if (data->flags & MMC_DATA_STREAM)
+                       cmdr |= MCI_CMDR_STREAM;
+               else if (data->blocks > 1)
+                       cmdr |= MCI_CMDR_MULTI_BLOCK;
+               else
+                       cmdr |= MCI_CMDR_BLOCK;
+
+               if (data->flags & MMC_DATA_READ)
+                       cmdr |= MCI_CMDR_TRDIR_READ;
+       }
+
+       return cmdr;
+}
+
+static void atmci_start_command(struct atmel_mci *host,
+                               struct mmc_command *cmd,
+                               u32 cmd_flags)
+{
+       /* Must read host->cmd after testing event flags */
+       smp_rmb();
+       WARN_ON(host->cmd);
+       host->cmd = cmd;
+
+       dev_vdbg(&host->mmc->class_dev,
+                       "start command: ARGR=0x%08x CMDR=0x%08x\n",
+                       cmd->arg, cmd_flags);
+
+       mci_writel(host, ARGR, cmd->arg);
+       mci_writel(host, CMDR, cmd_flags);
+}
+
+static void send_stop_cmd(struct mmc_host *mmc, struct mmc_data *data)
+{
+       struct atmel_mci *host = mmc_priv(mmc);
+
+       atmci_start_command(host, data->stop, host->stop_cmdr);
+       mci_writel(host, IER, MCI_CMDRDY);
+}
+
+static void atmci_request_end(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+       struct atmel_mci *host = mmc_priv(mmc);
+
+       WARN_ON(host->cmd || host->data);
+       host->mrq = NULL;
+
+       atmci_disable(host);
+
+       mmc_request_done(mmc, mrq);
+}
+
+/*
+ * Returns a mask of interrupt flags to be enabled after the whole
+ * request has been prepared.
+ */
+static u32 atmci_submit_data(struct mmc_host *mmc, struct mmc_data *data)
+{
+       struct atmel_mci        *host = mmc_priv(mmc);
+       u32                     iflags;
+
+       data->error = -EINPROGRESS;
+
+       WARN_ON(host->data);
+       host->sg = NULL;
+       host->data = data;
+
+       mci_writel(host, BLKR, MCI_BCNT(data->blocks)
+                       | MCI_BLKLEN(data->blksz));
+       dev_vdbg(&mmc->class_dev, "BLKR=0x%08x\n",
+                       MCI_BCNT(data->blocks) | MCI_BLKLEN(data->blksz));
+
+       iflags = ATMCI_DATA_ERROR_FLAGS;
+       host->sg = data->sg;
+       host->pio_offset = 0;
+       if (data->flags & MMC_DATA_READ)
+               iflags |= MCI_RXRDY;
+       else
+               iflags |= MCI_TXRDY;
+
+       return iflags;
+}
+
+static void atmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+       struct atmel_mci        *host = mmc_priv(mmc);
+       struct mmc_data         *data;
+       struct mmc_command      *cmd;
+       u32                     iflags;
+       u32                     cmdflags = 0;
+
+       iflags = mci_readl(host, IMR);
+       if (iflags)
+               dev_warn(&mmc->class_dev, "WARNING: IMR=0x%08x\n",
+                               mci_readl(host, IMR));
+
+       WARN_ON(host->mrq != NULL);
+
+       /*
+        * We may "know" the card is gone even though there's still an
+        * electrical connection. If so, we really need to communicate
+        * this to the MMC core since there won't be any more
+        * interrupts as the card is completely removed. Otherwise,
+        * the MMC core might believe the card is still there even
+        * though the card was just removed very slowly.
+        */
+       if (!host->present) {
+               mrq->cmd->error = -ENOMEDIUM;
+               mmc_request_done(mmc, mrq);
+               return;
+       }
+
+       host->mrq = mrq;
+       host->pending_events = 0;
+       host->completed_events = 0;
+
+       atmci_enable(host);
+
+       /* We don't support multiple blocks of weird lengths. */
+       data = mrq->data;
+       if (data) {
+               if (data->blocks > 1 && data->blksz & 3)
+                       goto fail;
+               atmci_set_timeout(host, data);
+       }
+
+       iflags = MCI_CMDRDY;
+       cmd = mrq->cmd;
+       cmdflags = atmci_prepare_command(mmc, cmd);
+       atmci_start_command(host, cmd, cmdflags);
+
+       if (data)
+               iflags |= atmci_submit_data(mmc, data);
+
+       if (mrq->stop) {
+               host->stop_cmdr = atmci_prepare_command(mmc, mrq->stop);
+               host->stop_cmdr |= MCI_CMDR_STOP_XFER;
+               if (!(data->flags & MMC_DATA_WRITE))
+                       host->stop_cmdr |= MCI_CMDR_TRDIR_READ;
+               if (data->flags & MMC_DATA_STREAM)
+                       host->stop_cmdr |= MCI_CMDR_STREAM;
+               else
+                       host->stop_cmdr |= MCI_CMDR_MULTI_BLOCK;
+       }
+
+       /*
+        * We could have enabled interrupts earlier, but I suspect
+        * that would open up a nice can of interesting race
+        * conditions (e.g. command and data complete, but stop not
+        * prepared yet.)
+        */
+       mci_writel(host, IER, iflags);
+
+       return;
+
+fail:
+       atmci_disable(host);
+       host->mrq = NULL;
+       mrq->cmd->error = -EINVAL;
+       mmc_request_done(mmc, mrq);
+}
+
+static void atmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+       struct atmel_mci        *host = mmc_priv(mmc);
+
+       if (ios->clock) {
+               u32 clkdiv;
+
+               /* Set clock rate */
+               clkdiv = DIV_ROUND_UP(host->bus_hz, 2 * ios->clock) - 1;
+               if (clkdiv > 255) {
+                       dev_warn(&mmc->class_dev,
+                               "clock %u too slow; using %lu\n",
+                               ios->clock, host->bus_hz / (2 * 256));
+                       clkdiv = 255;
+               }
+
+               host->mode_reg = MCI_MR_CLKDIV(clkdiv) | MCI_MR_WRPROOF
+                                       | MCI_MR_RDPROOF;
+       }
+
+       switch (ios->bus_width) {
+       case MMC_BUS_WIDTH_1:
+               host->sdc_reg = 0;
+               break;
+       case MMC_BUS_WIDTH_4:
+               host->sdc_reg = MCI_SDCBUS_4BIT;
+               break;
+       }
+
+       switch (ios->power_mode) {
+       case MMC_POWER_ON:
+               /* Send init sequence (74 clock cycles) */
+               atmci_enable(host);
+               mci_writel(host, CMDR, MCI_CMDR_SPCMD_INIT);
+               while (!(mci_readl(host, SR) & MCI_CMDRDY))
+                       cpu_relax();
+               atmci_disable(host);
+               break;
+       default:
+               /*
+                * TODO: None of the currently available AVR32-based
+                * boards allow MMC power to be turned off. Implement
+                * power control when this can be tested properly.
+                */
+               break;
+       }
+}
+
+static int atmci_get_ro(struct mmc_host *mmc)
+{
+       int                     read_only = 0;
+       struct atmel_mci        *host = mmc_priv(mmc);
+
+       if (host->wp_pin >= 0) {
+               read_only = gpio_get_value(host->wp_pin);
+               dev_dbg(&mmc->class_dev, "card is %s\n",
+                               read_only ? "read-only" : "read-write");
+       } else {
+               dev_dbg(&mmc->class_dev,
+                       "no pin for checking read-only switch."
+                       " Assuming write-enable.\n");
+       }
+
+       return read_only;
+}
+
+static struct mmc_host_ops atmci_ops = {
+       .request        = atmci_request,
+       .set_ios        = atmci_set_ios,
+       .get_ro         = atmci_get_ro,
+};
+
+static void atmci_command_complete(struct atmel_mci *host,
+                       struct mmc_command *cmd, u32 status)
+{
+       /* Read the response from the card (up to 16 bytes) */
+       cmd->resp[0] = mci_readl(host, RSPR);
+       cmd->resp[1] = mci_readl(host, RSPR);
+       cmd->resp[2] = mci_readl(host, RSPR);
+       cmd->resp[3] = mci_readl(host, RSPR);
+
+       if (status & MCI_RTOE)
+               cmd->error = -ETIMEDOUT;
+       else if ((cmd->flags & MMC_RSP_CRC) && (status & MCI_RCRCE))
+               cmd->error = -EILSEQ;
+       else if (status & (MCI_RINDE | MCI_RDIRE | MCI_RENDE))
+               cmd->error = -EIO;
+       else
+               cmd->error = 0;
+
+       if (cmd->error) {
+               dev_dbg(&host->mmc->class_dev,
+                       "command error: status=0x%08x\n", status);
+
+               if (cmd->data) {
+                       host->data = NULL;
+                       mci_writel(host, IDR, MCI_NOTBUSY
+                                       | MCI_TXRDY | MCI_RXRDY
+                                       | ATMCI_DATA_ERROR_FLAGS);
+               }
+       }
+}
+
+static void atmci_detect_change(unsigned long data)
+{
+       struct atmel_mci *host = (struct atmel_mci *)data;
+       struct mmc_request *mrq = host->mrq;
+       int present;
+
+       /*
+        * atmci_remove() sets detect_pin to -1 before freeing the
+        * interrupt. We must not re-enable the interrupt if it has
+        * been freed.
+        */
+       smp_rmb();
+       if (host->detect_pin < 0)
+               return;
+
+       enable_irq(gpio_to_irq(host->detect_pin));
+       present = !gpio_get_value(host->detect_pin);
+
+       dev_vdbg(&host->pdev->dev, "detect change: %d (was %d)\n",
+                       present, host->present);
+
+       if (present != host->present) {
+               dev_dbg(&host->mmc->class_dev, "card %s\n",
+                       present ? "inserted" : "removed");
+               host->present = present;
+
+               /* Reset controller if card is gone */
+               if (!present) {
+                       mci_writel(host, CR, MCI_CR_SWRST);
+                       mci_writel(host, IDR, ~0UL);
+                       mci_writel(host, CR, MCI_CR_MCIEN);
+               }
+
+               /* Clean up queue if present */
+               if (mrq) {
+                       /*
+                        * Reset controller to terminate any ongoing
+                        * commands or data transfers.
+                        */
+                       mci_writel(host, CR, MCI_CR_SWRST);
+
+                       if (!atmci_is_completed(host, EVENT_CMD_COMPLETE))
+                               mrq->cmd->error = -ENOMEDIUM;
+
+                       if (mrq->data && !atmci_is_completed(host,
+                                               EVENT_DATA_COMPLETE)) {
+                               host->data = NULL;
+                               mrq->data->error = -ENOMEDIUM;
+                       }
+                       if (mrq->stop && !atmci_is_completed(host,
+                                               EVENT_STOP_COMPLETE))
+                               mrq->stop->error = -ENOMEDIUM;
+
+                       host->cmd = NULL;
+                       atmci_request_end(host->mmc, mrq);
+               }
+
+               mmc_detect_change(host->mmc, 0);
+       }
+}
+
+static void atmci_tasklet_func(unsigned long priv)
+{
+       struct mmc_host         *mmc = (struct mmc_host *)priv;
+       struct atmel_mci        *host = mmc_priv(mmc);
+       struct mmc_request      *mrq = host->mrq;
+       struct mmc_data         *data = host->data;
+
+       dev_vdbg(&mmc->class_dev,
+               "tasklet: pending/completed/mask %lx/%lx/%x\n",
+               host->pending_events, host->completed_events,
+               mci_readl(host, IMR));
+
+       if (atmci_test_and_clear_pending(host, EVENT_CMD_COMPLETE)) {
+               /*
+                * host->cmd must be set to NULL before the interrupt
+                * handler sees EVENT_CMD_COMPLETE
+                */
+               host->cmd = NULL;
+               smp_wmb();
+               atmci_set_completed(host, EVENT_CMD_COMPLETE);
+               atmci_command_complete(host, mrq->cmd, host->cmd_status);
+
+               if (!mrq->cmd->error && mrq->stop
+                               && atmci_is_completed(host, EVENT_XFER_COMPLETE)
+                               && !atmci_test_and_set_completed(host,
+                                       EVENT_STOP_SENT))
+                       send_stop_cmd(host->mmc, mrq->data);
+       }
+       if (atmci_test_and_clear_pending(host, EVENT_STOP_COMPLETE)) {
+               /*
+                * host->cmd must be set to NULL before the interrupt
+                * handler sees EVENT_STOP_COMPLETE
+                */
+               host->cmd = NULL;
+               smp_wmb();
+               atmci_set_completed(host, EVENT_STOP_COMPLETE);
+               atmci_command_complete(host, mrq->stop, host->stop_status);
+       }
+       if (atmci_test_and_clear_pending(host, EVENT_DATA_ERROR)) {
+               u32 status = host->data_status;
+
+               dev_vdbg(&mmc->class_dev, "data error: status=%08x\n", status);
+
+               atmci_set_completed(host, EVENT_DATA_ERROR);
+               atmci_set_completed(host, EVENT_DATA_COMPLETE);
+
+               if (status & MCI_DTOE) {
+                       dev_dbg(&mmc->class_dev,
+                                       "data timeout error\n");
+                       data->error = -ETIMEDOUT;
+               } else if (status & MCI_DCRCE) {
+                       dev_dbg(&mmc->class_dev, "data CRC error\n");
+                       data->error = -EILSEQ;
+               } else {
+                       dev_dbg(&mmc->class_dev,
+                                       "data FIFO error (status=%08x)\n",
+                                       status);
+                       data->error = -EIO;
+               }
+
+               if (host->present && data->stop
+                               && atmci_is_completed(host, EVENT_CMD_COMPLETE)
+                               && !atmci_test_and_set_completed(
+                                       host, EVENT_STOP_SENT))
+                       send_stop_cmd(host->mmc, data);
+
+               host->data = NULL;
+       }
+       if (atmci_test_and_clear_pending(host, EVENT_DATA_COMPLETE)) {
+               atmci_set_completed(host, EVENT_DATA_COMPLETE);
+
+               if (!atmci_is_completed(host, EVENT_DATA_ERROR)) {
+                       data->bytes_xfered = data->blocks * data->blksz;
+                       data->error = 0;
+               }
+
+               host->data = NULL;
+       }
+
+       if (host->mrq && !host->cmd && !host->data)
+               atmci_request_end(mmc, host->mrq);
+}
+
+static void atmci_read_data_pio(struct atmel_mci *host)
+{
+       struct scatterlist      *sg = host->sg;
+       void                    *buf = sg_virt(sg);
+       unsigned int            offset = host->pio_offset;
+       struct mmc_data         *data = host->data;
+       u32                     value;
+       u32                     status;
+       unsigned int            nbytes = 0;
+
+       do {
+               value = mci_readl(host, RDR);
+               if (likely(offset + 4 <= sg->length)) {
+                       put_unaligned(value, (u32 *)(buf + offset));
+
+                       offset += 4;
+                       nbytes += 4;
+
+                       if (offset == sg->length) {
+                               host->sg = sg = sg_next(sg);
+                               if (!sg)
+                                       goto done;
+
+                               offset = 0;
+                               buf = sg_virt(sg);
+                       }
+               } else {
+                       unsigned int remaining = sg->length - offset;
+                       memcpy(buf + offset, &value, remaining);
+                       nbytes += remaining;
+
+                       flush_dcache_page(sg_page(sg));
+                       host->sg = sg = sg_next(sg);
+                       if (!sg)
+                               goto done;
+
+                       offset = 4 - remaining;
+                       buf = sg_virt(sg);
+                       memcpy(buf, (u8 *)&value + remaining, offset);
+                       nbytes += offset;
+               }
+
+               status = mci_readl(host, SR);
+               if (status & ATMCI_DATA_ERROR_FLAGS) {
+                       mci_writel(host, IDR, (MCI_NOTBUSY | MCI_RXRDY
+                                               | ATMCI_DATA_ERROR_FLAGS));
+                       host->data_status = status;
+                       atmci_set_pending(host, EVENT_DATA_ERROR);
+                       tasklet_schedule(&host->tasklet);
+                       break;
+               }
+       } while (status & MCI_RXRDY);
+
+       host->pio_offset = offset;
+       data->bytes_xfered += nbytes;
+
+       return;
+
+done:
+       mci_writel(host, IDR, MCI_RXRDY);
+       mci_writel(host, IER, MCI_NOTBUSY);
+       data->bytes_xfered += nbytes;
+       atmci_set_completed(host, EVENT_XFER_COMPLETE);
+       if (data->stop && atmci_is_completed(host, EVENT_CMD_COMPLETE)
+                       && !atmci_test_and_set_completed(host, EVENT_STOP_SENT))
+               send_stop_cmd(host->mmc, data);
+}
+
+static void atmci_write_data_pio(struct atmel_mci *host)
+{
+       struct scatterlist      *sg = host->sg;
+       void                    *buf = sg_virt(sg);
+       unsigned int            offset = host->pio_offset;
+       struct mmc_data         *data = host->data;
+       u32                     value;
+       u32                     status;
+       unsigned int            nbytes = 0;
+
+       do {
+               if (likely(offset + 4 <= sg->length)) {
+                       value = get_unaligned((u32 *)(buf + offset));
+                       mci_writel(host, TDR, value);
+
+                       offset += 4;
+                       nbytes += 4;
+                       if (offset == sg->length) {
+                               host->sg = sg = sg_next(sg);
+                               if (!sg)
+                                       goto done;
+
+                               offset = 0;
+                               buf = sg_virt(sg);
+                       }
+               } else {
+                       unsigned int remaining = sg->length - offset;
+
+                       value = 0;
+                       memcpy(&value, buf + offset, remaining);
+                       nbytes += remaining;
+
+                       host->sg = sg = sg_next(sg);
+                       if (!sg) {
+                               mci_writel(host, TDR, value);
+                               goto done;
+                       }
+
+                       offset = 4 - remaining;
+                       buf = sg_virt(sg);
+                       memcpy((u8 *)&value + remaining, buf, offset);
+                       mci_writel(host, TDR, value);
+                       nbytes += offset;
+               }
+
+               status = mci_readl(host, SR);
+               if (status & ATMCI_DATA_ERROR_FLAGS) {
+                       mci_writel(host, IDR, (MCI_NOTBUSY | MCI_TXRDY
+                                               | ATMCI_DATA_ERROR_FLAGS));
+                       host->data_status = status;
+                       atmci_set_pending(host, EVENT_DATA_ERROR);
+                       tasklet_schedule(&host->tasklet);
+                       break;
+               }
+       } while (status & MCI_TXRDY);
+
+       host->pio_offset = offset;
+       data->bytes_xfered += nbytes;
+
+       return;
+
+done:
+       mci_writel(host, IDR, MCI_TXRDY);
+       mci_writel(host, IER, MCI_NOTBUSY);
+       data->bytes_xfered += nbytes;
+       atmci_set_completed(host, EVENT_XFER_COMPLETE);
+       if (data->stop && atmci_is_completed(host, EVENT_CMD_COMPLETE)
+                       && !atmci_test_and_set_completed(host, EVENT_STOP_SENT))
+               send_stop_cmd(host->mmc, data);
+}
+
+static void atmci_cmd_interrupt(struct mmc_host *mmc, u32 status)
+{
+       struct atmel_mci        *host = mmc_priv(mmc);
+
+       mci_writel(host, IDR, MCI_CMDRDY);
+
+       if (atmci_is_completed(host, EVENT_STOP_SENT)) {
+               host->stop_status = status;
+               atmci_set_pending(host, EVENT_STOP_COMPLETE);
+       } else {
+               host->cmd_status = status;
+               atmci_set_pending(host, EVENT_CMD_COMPLETE);
+       }
+
+       tasklet_schedule(&host->tasklet);
+}
+
+static irqreturn_t atmci_interrupt(int irq, void *dev_id)
+{
+       struct mmc_host         *mmc = dev_id;
+       struct atmel_mci        *host = mmc_priv(mmc);
+       u32                     status, mask, pending;
+       unsigned int            pass_count = 0;
+
+       spin_lock(&mmc->lock);
+
+       do {
+               status = mci_readl(host, SR);
+               mask = mci_readl(host, IMR);
+               pending = status & mask;
+               if (!pending)
+                       break;
+
+               if (pending & ATMCI_DATA_ERROR_FLAGS) {
+                       mci_writel(host, IDR, ATMCI_DATA_ERROR_FLAGS
+                                       | MCI_RXRDY | MCI_TXRDY);
+                       pending &= mci_readl(host, IMR);
+                       host->data_status = status;
+                       atmci_set_pending(host, EVENT_DATA_ERROR);
+                       tasklet_schedule(&host->tasklet);
+               }
+               if (pending & MCI_NOTBUSY) {
+                       mci_writel(host, IDR, (MCI_NOTBUSY
+                                              | ATMCI_DATA_ERROR_FLAGS));
+                       atmci_set_pending(host, EVENT_DATA_COMPLETE);
+                       tasklet_schedule(&host->tasklet);
+               }
+               if (pending & MCI_RXRDY)
+                       atmci_read_data_pio(host);
+               if (pending & MCI_TXRDY)
+                       atmci_write_data_pio(host);
+
+               if (pending & MCI_CMDRDY)
+                       atmci_cmd_interrupt(mmc, status);
+       } while (pass_count++ < 5);
+
+       spin_unlock(&mmc->lock);
+
+       return pass_count ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static irqreturn_t atmci_detect_interrupt(int irq, void *dev_id)
+{
+       struct mmc_host         *mmc = dev_id;
+       struct atmel_mci        *host = mmc_priv(mmc);
+
+       /*
+        * Disable interrupts until the pin has stabilized and check
+        * the state then. Use mod_timer() since we may be in the
+        * middle of the timer routine when this interrupt triggers.
+        */
+       disable_irq_nosync(irq);
+       mod_timer(&host->detect_timer, jiffies + msecs_to_jiffies(20));
+
+       return IRQ_HANDLED;
+}
+
+static int __init atmci_probe(struct platform_device *pdev)
+{
+       struct mci_platform_data        *pdata;
+       struct atmel_mci *host;
+       struct mmc_host *mmc;
+       struct resource *regs;
+       int irq;
+       int ret;
+
+       regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!regs)
+               return -ENXIO;
+       pdata = pdev->dev.platform_data;
+       if (!pdata)
+               return -ENXIO;
+       irq = platform_get_irq(pdev, 0);
+       if (irq < 0)
+               return irq;
+
+       mmc = mmc_alloc_host(sizeof(struct atmel_mci), &pdev->dev);
+       if (!mmc)
+               return -ENOMEM;
+
+       host = mmc_priv(mmc);
+       host->pdev = pdev;
+       host->mmc = mmc;
+       host->detect_pin = pdata->detect_pin;
+       host->wp_pin = pdata->wp_pin;
+
+       host->mck = clk_get(&pdev->dev, "mci_clk");
+       if (IS_ERR(host->mck)) {
+               ret = PTR_ERR(host->mck);
+               goto err_clk_get;
+       }
+
+       ret = -ENOMEM;
+       host->regs = ioremap(regs->start, regs->end - regs->start + 1);
+       if (!host->regs)
+               goto err_ioremap;
+
+       clk_enable(host->mck);
+       mci_writel(host, CR, MCI_CR_SWRST);
+       host->bus_hz = clk_get_rate(host->mck);
+       clk_disable(host->mck);
+
+       host->mapbase = regs->start;
+
+       mmc->ops = &atmci_ops;
+       mmc->f_min = (host->bus_hz + 511) / 512;
+       mmc->f_max = host->bus_hz / 2;
+       mmc->ocr_avail  = MMC_VDD_32_33 | MMC_VDD_33_34;
+       mmc->caps |= MMC_CAP_4_BIT_DATA;
+
+       mmc->max_hw_segs = 64;
+       mmc->max_phys_segs = 64;
+       mmc->max_req_size = 32768 * 512;
+       mmc->max_blk_size = 32768;
+       mmc->max_blk_count = 512;
+
+       tasklet_init(&host->tasklet, atmci_tasklet_func, (unsigned long)mmc);
+
+       ret = request_irq(irq, atmci_interrupt, 0, pdev->dev.bus_id, mmc);
+       if (ret)
+               goto err_request_irq;
+
+       /* Assume card is present if we don't have a detect pin */
+       host->present = 1;
+       if (host->detect_pin >= 0) {
+               if (gpio_request(host->detect_pin, "mmc_detect")) {
+                       dev_dbg(&mmc->class_dev, "no detect pin available\n");
+                       host->detect_pin = -1;
+               } else {
+                       host->present = !gpio_get_value(host->detect_pin);
+               }
+       }
+       if (host->wp_pin >= 0) {
+               if (gpio_request(host->wp_pin, "mmc_wp")) {
+                       dev_dbg(&mmc->class_dev, "no WP pin available\n");
+                       host->wp_pin = -1;
+               }
+       }
+
+       platform_set_drvdata(pdev, host);
+
+       mmc_add_host(mmc);
+
+       if (host->detect_pin >= 0) {
+               setup_timer(&host->detect_timer, atmci_detect_change,
+                               (unsigned long)host);
+
+               ret = request_irq(gpio_to_irq(host->detect_pin),
+                               atmci_detect_interrupt,
+                               IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+                               "mmc-detect", mmc);
+               if (ret) {
+                       dev_dbg(&mmc->class_dev,
+                               "could not request IRQ %d for detect pin\n",
+                               gpio_to_irq(host->detect_pin));
+                       gpio_free(host->detect_pin);
+                       host->detect_pin = -1;
+               }
+       }
+
+       dev_info(&mmc->class_dev,
+                       "Atmel MCI controller at 0x%08lx irq %d\n",
+                       host->mapbase, irq);
+
+       return 0;
+
+err_request_irq:
+       iounmap(host->regs);
+err_ioremap:
+       clk_put(host->mck);
+err_clk_get:
+       mmc_free_host(mmc);
+       return ret;
+}
+
+static int __exit atmci_remove(struct platform_device *pdev)
+{
+       struct atmel_mci *host = platform_get_drvdata(pdev);
+
+       platform_set_drvdata(pdev, NULL);
+
+       if (host) {
+               if (host->detect_pin >= 0) {
+                       int pin = host->detect_pin;
+
+                       /* Make sure the timer doesn't enable the interrupt */
+                       host->detect_pin = -1;
+                       smp_wmb();
+
+                       free_irq(gpio_to_irq(pin), host->mmc);
+                       del_timer_sync(&host->detect_timer);
+                       gpio_free(pin);
+               }
+
+               mmc_remove_host(host->mmc);
+
+               clk_enable(host->mck);
+               mci_writel(host, IDR, ~0UL);
+               mci_writel(host, CR, MCI_CR_MCIDIS);
+               mci_readl(host, SR);
+               clk_disable(host->mck);
+
+               if (host->wp_pin >= 0)
+                       gpio_free(host->wp_pin);
+
+               free_irq(platform_get_irq(pdev, 0), host->mmc);
+               iounmap(host->regs);
+
+               clk_put(host->mck);
+
+               mmc_free_host(host->mmc);
+       }
+       return 0;
+}
+
+static struct platform_driver atmci_driver = {
+       .remove         = __exit_p(atmci_remove),
+       .driver         = {
+               .name           = "atmel_mci",
+       },
+};
+
+static int __init atmci_init(void)
+{
+       return platform_driver_probe(&atmci_driver, atmci_probe);
+}
+
+static void __exit atmci_exit(void)
+{
+       platform_driver_unregister(&atmci_driver);
+}
+
+module_init(atmci_init);
+module_exit(atmci_exit);
+
+MODULE_DESCRIPTION("Atmel Multimedia Card Interface driver");
+MODULE_AUTHOR("Haavard Skinnemoen <haavard.skinnemoen@atmel.com>");
+MODULE_LICENSE("GPL v2");
index cc5f7bc546afc95d5379faca5634a1f439363e50..3f15eb204895f25cfaa75af21e8ee59dca16b8f2 100644 (file)
@@ -21,7 +21,7 @@
  * published by the Free Software Foundation.
  */
 
-/* Why is a timer used to detect insert events?
+/* Why don't we use the SD controllers' carddetect feature?
  *
  * From the AU1100 MMC application guide:
  * If the Au1100-based design is intended to support both MultiMediaCards
@@ -30,8 +30,6 @@
  * 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/module.h>
 #include <linux/interrupt.h>
 #include <linux/dma-mapping.h>
 #include <linux/scatterlist.h>
-
+#include <linux/leds.h>
 #include <linux/mmc/host.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 <au1xxx.h>
-#include "au1xmmc.h"
-
 #define DRIVER_NAME "au1xxx-mmc"
 
 /* Set this to enable special debugging macros */
+/* #define DEBUG */
 
 #ifdef DEBUG
-#define DBG(fmt, idx, args...) printk("au1xx(%d): DEBUG: " fmt, idx, ##args)
+#define DBG(fmt, idx, args...) \
+       printk(KERN_DEBUG "au1xmmc(%d): DEBUG: " fmt, idx, ##args)
 #else
-#define DBG(fmt, idx, args...)
+#define DBG(fmt, idx, args...) do {} while (0)
 #endif
 
-const struct {
+/* 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)
+
+/* 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. */
+#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 flags;
        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
-};
+       u32 clock;
+       u32 bus_width;
+       u32 power_mode;
 
-#define AU1XMMC_CONTROLLER_COUNT (ARRAY_SIZE(au1xmmc_card_table))
+       int status;
 
-/* This array stores pointers for the hosts (used by the IRQ handler) */
-struct au1xmmc_host *au1xmmc_hosts[AU1XMMC_CONTROLLER_COUNT];
-static int dma = 1;
+       struct {
+               int len;
+               int dir;
+       } dma;
 
-#ifdef MODULE
-module_param(dma, bool, 0);
-MODULE_PARM_DESC(dma, "Use DMA engine for data transfers (0 = disabled)");
-#endif
+       struct {
+               int index;
+               int offset;
+               int len;
+       } pio;
+
+       u32 tx_chan;
+       u32 rx_chan;
+
+       int irq;
+
+       struct tasklet_struct finish_task;
+       struct tasklet_struct data_task;
+       struct au1xmmc_platform_data *platdata;
+       struct platform_device *pdev;
+       struct resource *ioarea;
+};
+
+/* 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
+
+/* 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)
 
 static inline void IRQ_ON(struct au1xmmc_host *host, u32 mask)
 {
@@ -119,14 +176,13 @@ static inline void IRQ_OFF(struct au1xmmc_host *host, u32 mask)
 
 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;
+       u32 config2;
 
        WARN_ON(host->status != HOST_S_DATA);
        host->status = HOST_S_STOP;
 
-       au_writel(mask | SD_CONFIG2_DF, HOST_CONFIG2(host));
+       config2 = au_readl(HOST_CONFIG2(host));
+       au_writel(config2 | SD_CONFIG2_DF, HOST_CONFIG2(host));
        au_sync();
 
        /* Send the stop commmand */
@@ -135,35 +191,36 @@ static inline void SEND_STOP(struct au1xmmc_host *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);
+       if (host->platdata && host->platdata->set_power)
+               host->platdata->set_power(host->mmc, state);
 }
 
-static inline int au1xmmc_card_inserted(struct au1xmmc_host *host)
+static int au1xmmc_card_inserted(struct mmc_host *mmc)
 {
-       return (bcsr->sig_status & au1xmmc_card_table[host->id].bcsrstatus)
-               ? 1 : 0;
+       struct au1xmmc_host *host = mmc_priv(mmc);
+
+       if (host->platdata && host->platdata->card_inserted)
+               return !!host->platdata->card_inserted(host->mmc);
+
+       return -ENOSYS;
 }
 
 static int au1xmmc_card_readonly(struct mmc_host *mmc)
 {
        struct au1xmmc_host *host = mmc_priv(mmc);
-       return (bcsr->status & au1xmmc_card_table[host->id].wpstatus)
-               ? 1 : 0;
+
+       if (host->platdata && host->platdata->card_readonly)
+               return !!host->platdata->card_readonly(mmc);
+
+       return -ENOSYS;
 }
 
 static void au1xmmc_finish_request(struct au1xmmc_host *host)
 {
-
        struct mmc_request *mrq = host->mrq;
 
        host->mrq = NULL;
-       host->flags &= HOST_F_ACTIVE;
+       host->flags &= HOST_F_ACTIVE | HOST_F_DMA;
 
        host->dma.len = 0;
        host->dma.dir = 0;
@@ -174,8 +231,6 @@ static void au1xmmc_finish_request(struct au1xmmc_host *host)
 
        host->status = HOST_S_IDLE;
 
-       bcsr->disk_leds |= (1 << 8);
-
        mmc_request_done(host->mmc, mrq);
 }
 
@@ -235,18 +290,14 @@ static int au1xmmc_send_command(struct au1xmmc_host *host, int wait,
        au_sync();
 
        /* Wait for the command to go on the line */
-
-       while(1) {
-               if (!(au_readl(HOST_CMD(host)) & SD_CMD_GO))
-                       break;
-       }
+       while (au_readl(HOST_CMD(host)) & SD_CMD_GO)
+               /* nop */;
 
        /* Wait for the command to come back */
-
        if (wait) {
                u32 status = au_readl(HOST_STATUS(host));
 
-               while(!(status & SD_STATUS_CR))
+               while (!(status & SD_STATUS_CR))
                        status = au_readl(HOST_STATUS(host));
 
                /* Clear the CR status */
@@ -260,12 +311,11 @@ static int au1xmmc_send_command(struct au1xmmc_host *host, int wait,
 
 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);
+       WARN_ON((host->status != HOST_S_DATA) && (host->status != HOST_S_STOP));
 
        if (host->mrq == NULL)
                return;
@@ -276,15 +326,13 @@ static void au1xmmc_data_complete(struct au1xmmc_host *host, u32 status)
                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))
+       while ((host->flags & HOST_F_XMIT) && (status & SD_STATUS_DB))
                status = au_readl(HOST_STATUS(host));
 
        data->error = 0;
        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;
@@ -299,16 +347,16 @@ static void au1xmmc_data_complete(struct au1xmmc_host *host, u32 status)
 
        if (!data->error) {
                if (host->flags & HOST_F_DMA) {
+#ifdef CONFIG_SOC_AU1200       /* DBDMA */
                        u32 chan = DMA_CHANNEL(host);
 
-                       chan_tab_t *c = *((chan_tab_t **) chan);
+                       chan_tab_t *c = *((chan_tab_t **)chan);
                        au1x_dma_chan_t *cp = c->chan_ptr;
                        data->bytes_xfered = cp->ddma_bytecnt;
-               }
-               else
+#endif
+               else
                        data->bytes_xfered =
-                               (data->blocks * data->blksz) -
-                               host->pio.len;
+                               (data->blocks * data->blksz) - host->pio.len;
        }
 
        au1xmmc_finish_request(host);
@@ -316,7 +364,7 @@ static void au1xmmc_data_complete(struct au1xmmc_host *host, u32 status)
 
 static void au1xmmc_tasklet_data(unsigned long param)
 {
-       struct au1xmmc_host *host = (struct au1xmmc_host *) param;
+       struct au1xmmc_host *host = (struct au1xmmc_host *)param;
 
        u32 status = au_readl(HOST_STATUS(host));
        au1xmmc_data_complete(host, status);
@@ -326,11 +374,10 @@ static void au1xmmc_tasklet_data(unsigned long param)
 
 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 mmc_data *data;
+       int sg_len, max, count;
+       unsigned char *sg_ptr, val;
+       u32 status;
        struct scatterlist *sg;
 
        data = host->mrq->data;
@@ -345,14 +392,12 @@ static void au1xmmc_send_pio(struct au1xmmc_host *host)
        /* 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 */
-
+       /* Check if we need less than 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;
+       if (max > AU1XMMC_MAX_TRANSFER)
+               max = AU1XMMC_MAX_TRANSFER;
 
+       for (count = 0; count < max; count++) {
                status = au_readl(HOST_STATUS(host));
 
                if (!(status & SD_STATUS_TH))
@@ -360,7 +405,7 @@ static void au1xmmc_send_pio(struct au1xmmc_host *host)
 
                val = *sg_ptr++;
 
-               au_writel((unsigned long) val, HOST_TXPORT(host));
+               au_writel((unsigned long)val, HOST_TXPORT(host));
                au_sync();
        }
 
@@ -384,11 +429,10 @@ static void au1xmmc_send_pio(struct au1xmmc_host *host)
 
 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 mmc_data *data;
+       int max, count, sg_len = 0;
+       unsigned char *sg_ptr = NULL;
+       u32 status, val;
        struct scatterlist *sg;
 
        data = host->mrq->data;
@@ -405,33 +449,33 @@ static void au1xmmc_receive_pio(struct au1xmmc_host *host)
                /* 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;
+               /* Check if we need less than 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;
+       for (count = 0; count < max; count++) {
                status = au_readl(HOST_STATUS(host));
 
                if (!(status & SD_STATUS_NE))
                        break;
 
                if (status & SD_STATUS_RC) {
-                       DBG("RX CRC Error [%d + %d].\n", host->id,
+                       DBG("RX CRC Error [%d + %d].\n", host->pdev->id,
                                        host->pio.len, count);
                        break;
                }
 
                if (status & SD_STATUS_RO) {
-                       DBG("RX Overrun [%d + %d]\n", host->id,
+                       DBG("RX Overrun [%d + %d]\n", host->pdev->id,
                                        host->pio.len, count);
                        break;
                }
                else if (status & SD_STATUS_RU) {
-                       DBG("RX Underrun [%d + %d]\n", host->id,
+                       DBG("RX Underrun [%d + %d]\n", host->pdev->id,
                                        host->pio.len,  count);
                        break;
                }
@@ -439,7 +483,7 @@ static void au1xmmc_receive_pio(struct au1xmmc_host *host)
                val = au_readl(HOST_RXPORT(host));
 
                if (sg_ptr)
-                       *sg_ptr++ = (unsigned char) (val & 0xFF);
+                       *sg_ptr++ = (unsigned char)(val & 0xFF);
        }
 
        host->pio.len -= count;
@@ -451,7 +495,7 @@ static void au1xmmc_receive_pio(struct au1xmmc_host *host)
        }
 
        if (host->pio.len == 0) {
-               //IRQ_OFF(host, SD_CONFIG_RA | SD_CONFIG_RF);
+               /* IRQ_OFF(host, SD_CONFIG_RA | SD_CONFIG_RF); */
                IRQ_OFF(host, SD_CONFIG_NE);
 
                if (host->flags & HOST_F_STOP)
@@ -461,17 +505,15 @@ static void au1xmmc_receive_pio(struct au1xmmc_host *host)
        }
 }
 
-/* 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.
-*/
-
+/* 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;
+       u32 r[4];
+       int i, trans;
 
        if (!host->mrq)
                return;
@@ -481,9 +523,6 @@ static void au1xmmc_cmd_complete(struct au1xmmc_host *host, u32 status)
 
        if (cmd->flags & MMC_RSP_PRESENT) {
                if (cmd->flags & MMC_RSP_136) {
-                       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);
@@ -491,10 +530,9 @@ static void au1xmmc_cmd_complete(struct au1xmmc_host *host, u32 status)
 
                        /* 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
+                        * 128 bits, so we have to shift things up.
                         */
-
-                       for(i = 0; i < 4; i++) {
+                       for (i = 0; i < 4; i++) {
                                cmd->resp[i] = (r[i] & 0x00FFFFFF) << 8;
                                if (i != 3)
                                        cmd->resp[i] |= (r[i + 1] & 0xFF000000) >> 24;
@@ -505,22 +543,20 @@ static void au1xmmc_cmd_complete(struct au1xmmc_host *host, u32 status)
                         * 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
+                        * so we can just read RESP0 and return that.
                         */
                        cmd->resp[0] = au_readl(host->iobase + SD_RESP0);
                }
        }
 
         /* Figure out errors */
-
        if (status & (SD_STATUS_SC | SD_STATUS_WC | SD_STATUS_RC))
                cmd->error = -EILSEQ;
 
        trans = host->flags & (HOST_F_XMIT | HOST_F_RECV);
 
        if (!trans || cmd->error) {
-
-               IRQ_OFF(host, SD_CONFIG_TH | SD_CONFIG_RA|SD_CONFIG_RF);
+               IRQ_OFF(host, SD_CONFIG_TH | SD_CONFIG_RA | SD_CONFIG_RF);
                tasklet_schedule(&host->finish_task);
                return;
        }
@@ -528,6 +564,7 @@ static void au1xmmc_cmd_complete(struct au1xmmc_host *host, u32 status)
        host->status = HOST_S_DATA;
 
        if (host->flags & HOST_F_DMA) {
+#ifdef CONFIG_SOC_AU1200       /* DBDMA */
                u32 channel = DMA_CHANNEL(host);
 
                /* Start the DMA as soon as the buffer gets something in it */
@@ -540,23 +577,21 @@ static void au1xmmc_cmd_complete(struct au1xmmc_host *host, u32 status)
                }
 
                au1xxx_dbdma_start(channel);
+#endif
        }
 }
 
 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
-       */
-
+        * 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));
@@ -568,15 +603,11 @@ static void au1xmmc_set_clock(struct au1xmmc_host *host, int rate)
        au_sync();
 }
 
-static int
-au1xmmc_prepare_data(struct au1xmmc_host *host, struct mmc_data *data)
+static int au1xmmc_prepare_data(struct au1xmmc_host *host,
+                               struct mmc_data *data)
 {
-
        int datalen = data->blocks * data->blksz;
 
-       if (dma != 0)
-               host->flags |= HOST_F_DMA;
-
        if (data->flags & MMC_DATA_READ)
                host->flags |= HOST_F_RECV;
        else
@@ -596,12 +627,13 @@ au1xmmc_prepare_data(struct au1xmmc_host *host, struct mmc_data *data)
        au_writel(data->blksz - 1, HOST_BLKSIZE(host));
 
        if (host->flags & HOST_F_DMA) {
+#ifdef CONFIG_SOC_AU1200       /* DBDMA */
                int i;
                u32 channel = DMA_CHANNEL(host);
 
                au1xxx_dbdma_stop(channel);
 
-               for(i = 0; i < host->dma.len; i++) {
+               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;
@@ -611,23 +643,21 @@ au1xmmc_prepare_data(struct au1xmmc_host *host, struct mmc_data *data)
                        if (i == host->dma.len - 1)
                                flags = DDMA_FLAGS_IE;
 
-                       if (host->flags & HOST_F_XMIT){
-                               ret = au1xxx_dbdma_put_source_flags(channel,
-                                       (void *) sg_virt(sg), len, flags);
-                       }
-                       else {
-                               ret = au1xxx_dbdma_put_dest_flags(channel,
-                                       (void *) sg_virt(sg),
-                                       len, flags);
+                       if (host->flags & HOST_F_XMIT) {
+                               ret = au1xxx_dbdma_put_source_flags(channel,
+                                       (void *)sg_virt(sg), len, flags);
+                       } else {
+                               ret = au1xxx_dbdma_put_dest_flags(channel,
+                                       (void *)sg_virt(sg), len, flags);
                        }
 
-                       if (!ret)
+                       if (!ret)
                                goto dataerr;
 
                        datalen -= len;
                }
-       }
-       else {
+#endif
+       else {
                host->pio.index = 0;
                host->pio.offset = 0;
                host->pio.len = datalen;
@@ -636,25 +666,21 @@ au1xmmc_prepare_data(struct au1xmmc_host *host, struct mmc_data *data)
                        IRQ_ON(host, SD_CONFIG_TH);
                else
                        IRQ_ON(host, SD_CONFIG_NE);
-                       //IRQ_ON(host, SD_CONFIG_RA|SD_CONFIG_RF);
+                       /* IRQ_ON(host, SD_CONFIG_RA | SD_CONFIG_RF); */
        }
 
        return 0;
 
- dataerr:
-       dma_unmap_sg(mmc_dev(host->mmc),data->sg,data->sg_len,host->dma.dir);
+dataerr:
+       dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+                       host->dma.dir);
        return -ETIMEDOUT;
 }
 
-/* static void au1xmmc_request
-   This actually starts a command or data transaction
-*/
-
+/* 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);
-       unsigned int flags = 0;
        int ret = 0;
 
        WARN_ON(irqs_disabled());
@@ -663,11 +689,15 @@ static void au1xmmc_request(struct mmc_host* mmc, struct mmc_request* mrq)
        host->mrq = mrq;
        host->status = HOST_S_CMD;
 
-       bcsr->disk_leds &= ~(1 << 8);
+       /* fail request immediately if no card is present */
+       if (0 == au1xmmc_card_inserted(mmc)) {
+               mrq->cmd->error = -ENOMEDIUM;
+               au1xmmc_finish_request(host);
+               return;
+       }
 
        if (mrq->data) {
                FLUSH_FIFO(host);
-               flags = mrq->data->flags;
                ret = au1xmmc_prepare_data(host, mrq->data);
        }
 
@@ -682,7 +712,6 @@ static void au1xmmc_request(struct mmc_host* mmc, struct mmc_request* mrq)
 
 static void au1xmmc_reset_controller(struct au1xmmc_host *host)
 {
-
        /* Apply the clock */
        au_writel(SD_ENABLE_CE, HOST_ENABLE(host));
         au_sync_delay(1);
@@ -712,9 +741,10 @@ static void au1xmmc_reset_controller(struct au1xmmc_host *host)
 }
 
 
-static void au1xmmc_set_ios(struct mmc_host* mmc, struct mmc_ios* ios)
+static void au1xmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
 {
        struct au1xmmc_host *host = mmc_priv(mmc);
+       u32 config2;
 
        if (ios->power_mode == MMC_POWER_OFF)
                au1xmmc_set_power(host, 0);
@@ -726,21 +756,18 @@ static void au1xmmc_set_ios(struct mmc_host* mmc, struct mmc_ios* ios)
                au1xmmc_set_clock(host, ios->clock);
                host->clock = ios->clock;
        }
-}
-
-static void au1xmmc_dma_callback(int irq, void *dev_id)
-{
-       struct au1xmmc_host *host = (struct au1xmmc_host *) dev_id;
-
-       /* Avoid spurious interrupts */
 
-       if (!host->mrq)
-               return;
-
-       if (host->flags & HOST_F_STOP)
-               SEND_STOP(host);
-
-       tasklet_schedule(&host->data_task);
+       config2 = au_readl(HOST_CONFIG2(host));
+       switch (ios->bus_width) {
+       case MMC_BUS_WIDTH_4:
+               config2 |= SD_CONFIG2_WB;
+               break;
+       case MMC_BUS_WIDTH_1:
+               config2 &= ~SD_CONFIG2_WB;
+               break;
+       }
+       au_writel(config2, HOST_CONFIG2(host));
+       au_sync();
 }
 
 #define STATUS_TIMEOUT (SD_STATUS_RAT | SD_STATUS_DT)
@@ -749,245 +776,354 @@ static void au1xmmc_dma_callback(int irq, void *dev_id)
 
 static irqreturn_t au1xmmc_irq(int irq, void *dev_id)
 {
-
+       struct au1xmmc_host *host = dev_id;
        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));
 
-               status = au_readl(HOST_STATUS(host));
+       if (!(status & SD_STATUS_I))
+               return IRQ_NONE;        /* not ours */
 
-               if (host->mrq && (status & STATUS_TIMEOUT)) {
-                       if (status & SD_STATUS_RAT)
-                               host->mrq->cmd->error = -ETIMEDOUT;
+       if (status & SD_STATUS_SI)      /* SDIO */
+               mmc_signal_sdio_irq(host->mmc);
 
-                       else if (status & SD_STATUS_DT)
-                               host->mrq->data->error = -ETIMEDOUT;
+       if (host->mrq && (status & STATUS_TIMEOUT)) {
+               if (status & SD_STATUS_RAT)
+                       host->mrq->cmd->error = -ETIMEDOUT;
+               else if (status & SD_STATUS_DT)
+                       host->mrq->data->error = -ETIMEDOUT;
 
-                       /* In PIO mode, interrupts might still be enabled */
-                       IRQ_OFF(host, SD_CONFIG_NE | SD_CONFIG_TH);
+               /* 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);
-               }
+               /* 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);
-                       }
+       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) {
-                       DBG("Unhandled status %8.8x\n", host->id, status);
-                       handled = 0;
-               }
-
-               au_writel(status, HOST_STATUS(host));
-               au_sync();
-
-               ret |= handled;
+       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 & 0x203F3C70) {
+                       DBG("Unhandled status %8.8x\n", host->pdev->id,
+                               status);
        }
 
-       enable_irq(AU1100_SD_IRQ);
-       return ret;
+       au_writel(status, HOST_STATUS(host));
+       au_sync();
+
+       return IRQ_HANDLED;
 }
 
-static void au1xmmc_poll_event(unsigned long arg)
-{
-       struct au1xmmc_host *host = (struct au1xmmc_host *) arg;
+#ifdef CONFIG_SOC_AU1200
+/* 8bit memory DMA device */
+static dbdev_tab_t au1xmmc_mem_dbdev = {
+       .dev_id         = DSCR_CMD0_ALWAYS,
+       .dev_flags      = DEV_FLAGS_ANYUSE,
+       .dev_tsize      = 0,
+       .dev_devwidth   = 8,
+       .dev_physaddr   = 0x00000000,
+       .dev_intlevel   = 0,
+       .dev_intpolarity = 0,
+};
+static int memid;
 
-       int card = au1xmmc_card_inserted(host);
-        int controller = (host->flags & HOST_F_ACTIVE) ? 1 : 0;
+static void au1xmmc_dbdma_callback(int irq, void *dev_id)
+{
+       struct au1xmmc_host *host = (struct au1xmmc_host *)dev_id;
 
-       if (card != controller) {
-               host->flags &= ~HOST_F_ACTIVE;
-               if (card) host->flags |= HOST_F_ACTIVE;
-               mmc_detect_change(host->mmc, 0);
-       }
+       /* Avoid spurious interrupts */
+       if (!host->mrq)
+               return;
 
-       if (host->mrq != NULL) {
-               u32 status = au_readl(HOST_STATUS(host));
-               DBG("PENDING - %8.8x\n", host->id, status);
-       }
+       if (host->flags & HOST_F_STOP)
+               SEND_STOP(host);
 
-       mod_timer(&host->timer, jiffies + AU1XMMC_DETECT_TIMEOUT);
+       tasklet_schedule(&host->data_task);
 }
 
-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)
+static int au1xmmc_dbdma_init(struct au1xmmc_host *host)
 {
+       struct resource *res;
+       int txid, rxid;
+
+       res = platform_get_resource(host->pdev, IORESOURCE_DMA, 0);
+       if (!res)
+               return -ENODEV;
+       txid = res->start;
+
+       res = platform_get_resource(host->pdev, IORESOURCE_DMA, 1);
+       if (!res)
+               return -ENODEV;
+       rxid = res->start;
+
+       if (!memid)
+               return -ENODEV;
+
+       host->tx_chan = au1xxx_dbdma_chan_alloc(memid, txid,
+                               au1xmmc_dbdma_callback, (void *)host);
+       if (!host->tx_chan) {
+               dev_err(&host->pdev->dev, "cannot allocate TX DMA\n");
+               return -ENODEV;
+       }
 
-       u32 rxchan, txchan;
-
-       int txid = au1xmmc_card_table[host->id].tx_devid;
-       int rxid = au1xmmc_card_table[host->id].rx_devid;
+       host->rx_chan = au1xxx_dbdma_chan_alloc(rxid, memid,
+                               au1xmmc_dbdma_callback, (void *)host);
+       if (!host->rx_chan) {
+               dev_err(&host->pdev->dev, "cannot allocate RX DMA\n");
+               au1xxx_dbdma_chan_free(host->tx_chan);
+               return -ENODEV;
+       }
 
-       /* 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
-       */
+       au1xxx_dbdma_set_devwidth(host->tx_chan, 8);
+       au1xxx_dbdma_set_devwidth(host->rx_chan, 8);
 
-       int memid = au1xxx_ddma_add_device(&au1xmmc_mem_dbdev);
+       au1xxx_dbdma_ring_alloc(host->tx_chan, AU1XMMC_DESCRIPTOR_COUNT);
+       au1xxx_dbdma_ring_alloc(host->rx_chan, AU1XMMC_DESCRIPTOR_COUNT);
 
-       txchan = au1xxx_dbdma_chan_alloc(memid, txid,
-                                        au1xmmc_dma_callback, (void *) host);
+       /* DBDMA is good to go */
+       host->flags |= HOST_F_DMA;
 
-       rxchan = au1xxx_dbdma_chan_alloc(rxid, memid,
-                                        au1xmmc_dma_callback, (void *) host);
+       return 0;
+}
 
-       au1xxx_dbdma_set_devwidth(txchan, 8);
-       au1xxx_dbdma_set_devwidth(rxchan, 8);
+static void au1xmmc_dbdma_shutdown(struct au1xmmc_host *host)
+{
+       if (host->flags & HOST_F_DMA) {
+               host->flags &= ~HOST_F_DMA;
+               au1xxx_dbdma_chan_free(host->tx_chan);
+               au1xxx_dbdma_chan_free(host->rx_chan);
+       }
+}
+#endif
 
-       au1xxx_dbdma_ring_alloc(txchan, AU1XMMC_DESCRIPTOR_COUNT);
-       au1xxx_dbdma_ring_alloc(rxchan, AU1XMMC_DESCRIPTOR_COUNT);
+static void au1xmmc_enable_sdio_irq(struct mmc_host *mmc, int en)
+{
+       struct au1xmmc_host *host = mmc_priv(mmc);
 
-       host->tx_chan = txchan;
-       host->rx_chan = rxchan;
+       if (en)
+               IRQ_ON(host, SD_CONFIG_SI);
+       else
+               IRQ_OFF(host, SD_CONFIG_SI);
 }
 
 static const struct mmc_host_ops au1xmmc_ops = {
        .request        = au1xmmc_request,
        .set_ios        = au1xmmc_set_ios,
        .get_ro         = au1xmmc_card_readonly,
+       .get_cd         = au1xmmc_card_inserted,
+       .enable_sdio_irq = au1xmmc_enable_sdio_irq,
 };
 
 static int __devinit au1xmmc_probe(struct platform_device *pdev)
 {
+       struct mmc_host *mmc;
+       struct au1xmmc_host *host;
+       struct resource *r;
+       int ret;
+
+       mmc = mmc_alloc_host(sizeof(struct au1xmmc_host), &pdev->dev);
+       if (!mmc) {
+               dev_err(&pdev->dev, "no memory for mmc_host\n");
+               ret = -ENOMEM;
+               goto out0;
+       }
 
-       int i, ret = 0;
-
-       /* THe interrupt is shared among all controllers */
-       ret = request_irq(AU1100_SD_IRQ, au1xmmc_irq, IRQF_DISABLED, "MMC", 0);
+       host = mmc_priv(mmc);
+       host->mmc = mmc;
+       host->platdata = pdev->dev.platform_data;
+       host->pdev = pdev;
 
-       if (ret) {
-               printk(DRIVER_NAME "ERROR: Couldn't get int %d: %d\n",
-                               AU1100_SD_IRQ, ret);
-               return -ENXIO;
+       ret = -ENODEV;
+       r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!r) {
+               dev_err(&pdev->dev, "no mmio defined\n");
+               goto out1;
        }
 
-       disable_irq(AU1100_SD_IRQ);
+       host->ioarea = request_mem_region(r->start, r->end - r->start + 1,
+                                          pdev->name);
+       if (!host->ioarea) {
+               dev_err(&pdev->dev, "mmio already in use\n");
+               goto out1;
+       }
 
-       for(i = 0; i < AU1XMMC_CONTROLLER_COUNT; i++) {
-               struct mmc_host *mmc = mmc_alloc_host(sizeof(struct au1xmmc_host), &pdev->dev);
-               struct au1xmmc_host *host = 0;
+       host->iobase = (unsigned long)ioremap(r->start, 0x3c);
+       if (!host->iobase) {
+               dev_err(&pdev->dev, "cannot remap mmio\n");
+               goto out2;
+       }
 
-               if (!mmc) {
-                       printk(DRIVER_NAME "ERROR: no mem for host %d\n", i);
-                       au1xmmc_hosts[i] = 0;
-                       continue;
-               }
+       r = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+       if (!r) {
+               dev_err(&pdev->dev, "no IRQ defined\n");
+               goto out3;
+       }
 
-               mmc->ops = &au1xmmc_ops;
+       host->irq = r->start;
+       /* IRQ is shared among both SD controllers */
+       ret = request_irq(host->irq, au1xmmc_irq, IRQF_SHARED,
+                         DRIVER_NAME, host);
+       if (ret) {
+               dev_err(&pdev->dev, "cannot grab IRQ\n");
+               goto out3;
+       }
 
-               mmc->f_min =   450000;
-               mmc->f_max = 24000000;
+       mmc->ops = &au1xmmc_ops;
 
-               mmc->max_seg_size = AU1XMMC_DESCRIPTOR_SIZE;
-               mmc->max_phys_segs = AU1XMMC_DESCRIPTOR_COUNT;
+       mmc->f_min =   450000;
+       mmc->f_max = 24000000;
 
-               mmc->max_blk_size = 2048;
-               mmc->max_blk_count = 512;
+       mmc->max_seg_size = AU1XMMC_DESCRIPTOR_SIZE;
+       mmc->max_phys_segs = AU1XMMC_DESCRIPTOR_COUNT;
 
-               mmc->ocr_avail = AU1XMMC_OCR;
+       mmc->max_blk_size = 2048;
+       mmc->max_blk_count = 512;
 
-               host = mmc_priv(mmc);
-               host->mmc = mmc;
+       mmc->ocr_avail = AU1XMMC_OCR;
+       mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;
 
-               host->id = i;
-               host->iobase = au1xmmc_card_table[host->id].iobase;
-               host->clock = 0;
-               host->power_mode = MMC_POWER_OFF;
+       host->status = HOST_S_IDLE;
 
-               host->flags = au1xmmc_card_inserted(host) ? HOST_F_ACTIVE : 0;
-               host->status = HOST_S_IDLE;
+       /* board-specific carddetect setup, if any */
+       if (host->platdata && host->platdata->cd_setup) {
+               ret = host->platdata->cd_setup(mmc, 1);
+               if (ret) {
+                       dev_warn(&pdev->dev, "board CD setup failed\n");
+                       mmc->caps |= MMC_CAP_NEEDS_POLL;
+               }
+       } else
+               mmc->caps |= MMC_CAP_NEEDS_POLL;
 
-               init_timer(&host->timer);
+       tasklet_init(&host->data_task, au1xmmc_tasklet_data,
+                       (unsigned long)host);
 
-               host->timer.function = au1xmmc_poll_event;
-               host->timer.data = (unsigned long) host;
-               host->timer.expires = jiffies + AU1XMMC_DETECT_TIMEOUT;
+       tasklet_init(&host->finish_task, au1xmmc_tasklet_finish,
+                       (unsigned long)host);
 
-               tasklet_init(&host->data_task, au1xmmc_tasklet_data,
-                               (unsigned long) host);
+#ifdef CONFIG_SOC_AU1200
+       ret = au1xmmc_dbdma_init(host);
+       if (ret)
+               printk(KERN_INFO DRIVER_NAME ": DBDMA init failed; using PIO\n");
+#endif
 
-               tasklet_init(&host->finish_task, au1xmmc_tasklet_finish,
-                               (unsigned long) host);
+#ifdef CONFIG_LEDS_CLASS
+       if (host->platdata && host->platdata->led) {
+               struct led_classdev *led = host->platdata->led;
+               led->name = mmc_hostname(mmc);
+               led->brightness = LED_OFF;
+               led->default_trigger = mmc_hostname(mmc);
+               ret = led_classdev_register(mmc_dev(mmc), led);
+               if (ret)
+                       goto out5;
+       }
+#endif
 
-               spin_lock_init(&host->lock);
+       au1xmmc_reset_controller(host);
 
-               if (dma != 0)
-                       au1xmmc_init_dma(host);
+       ret = mmc_add_host(mmc);
+       if (ret) {
+               dev_err(&pdev->dev, "cannot add mmc host\n");
+               goto out6;
+       }
 
-               au1xmmc_reset_controller(host);
+       platform_set_drvdata(pdev, mmc);
 
-               mmc_add_host(mmc);
-               au1xmmc_hosts[i] = host;
+       printk(KERN_INFO DRIVER_NAME ": MMC Controller %d set up at %8.8X"
+               " (mode=%s)\n", pdev->id, host->iobase,
+               host->flags & HOST_F_DMA ? "dma" : "pio");
 
-               add_timer(&host->timer);
+       return 0;       /* all ok */
 
-               printk(KERN_INFO DRIVER_NAME ": MMC Controller %d set up at %8.8X (mode=%s)\n",
-                      host->id, host->iobase, dma ? "dma" : "pio");
-       }
+out6:
+#ifdef CONFIG_LEDS_CLASS
+       if (host->platdata && host->platdata->led)
+               led_classdev_unregister(host->platdata->led);
+out5:
+#endif
+       au_writel(0, HOST_ENABLE(host));
+       au_writel(0, HOST_CONFIG(host));
+       au_writel(0, HOST_CONFIG2(host));
+       au_sync();
 
-       enable_irq(AU1100_SD_IRQ);
+#ifdef CONFIG_SOC_AU1200
+       au1xmmc_dbdma_shutdown(host);
+#endif
 
-       return 0;
+       tasklet_kill(&host->data_task);
+       tasklet_kill(&host->finish_task);
+
+       if (host->platdata && host->platdata->cd_setup &&
+           !(mmc->caps & MMC_CAP_NEEDS_POLL))
+               host->platdata->cd_setup(mmc, 0);
+
+       free_irq(host->irq, host);
+out3:
+       iounmap((void *)host->iobase);
+out2:
+       release_resource(host->ioarea);
+       kfree(host->ioarea);
+out1:
+       mmc_free_host(mmc);
+out0:
+       return ret;
 }
 
 static int __devexit au1xmmc_remove(struct platform_device *pdev)
 {
+       struct mmc_host *mmc = platform_get_drvdata(pdev);
+       struct au1xmmc_host *host;
+
+       if (mmc) {
+               host  = mmc_priv(mmc);
 
-       int i;
+               mmc_remove_host(mmc);
 
-       disable_irq(AU1100_SD_IRQ);
+#ifdef CONFIG_LEDS_CLASS
+               if (host->platdata && host->platdata->led)
+                       led_classdev_unregister(host->platdata->led);
+#endif
 
-       for(i = 0; i < AU1XMMC_CONTROLLER_COUNT; i++) {
-               struct au1xmmc_host *host = au1xmmc_hosts[i];
-               if (!host) continue;
+               if (host->platdata && host->platdata->cd_setup &&
+                   !(mmc->caps & MMC_CAP_NEEDS_POLL))
+                       host->platdata->cd_setup(mmc, 0);
+
+               au_writel(0, HOST_ENABLE(host));
+               au_writel(0, HOST_CONFIG(host));
+               au_writel(0, HOST_CONFIG2(host));
+               au_sync();
 
                tasklet_kill(&host->data_task);
                tasklet_kill(&host->finish_task);
 
-               del_timer_sync(&host->timer);
+#ifdef CONFIG_SOC_AU1200
+               au1xmmc_dbdma_shutdown(host);
+#endif
                au1xmmc_set_power(host, 0);
 
-               mmc_remove_host(host->mmc);
-
-               au1xxx_dbdma_chan_free(host->tx_chan);
-               au1xxx_dbdma_chan_free(host->rx_chan);
+               free_irq(host->irq, host);
+               iounmap((void *)host->iobase);
+               release_resource(host->ioarea);
+               kfree(host->ioarea);
 
-               au_writel(0x0, HOST_ENABLE(host));
-               au_sync();
+               mmc_free_host(mmc);
        }
-
-       free_irq(AU1100_SD_IRQ, 0);
        return 0;
 }
 
@@ -1004,21 +1140,31 @@ static struct platform_driver au1xmmc_driver = {
 
 static int __init au1xmmc_init(void)
 {
+#ifdef CONFIG_SOC_AU1200
+       /* 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.
+        */
+       memid = au1xxx_ddma_add_device(&au1xmmc_mem_dbdev);
+       if (!memid)
+               printk(KERN_ERR "au1xmmc: cannot add memory dbdma dev\n");
+#endif
        return platform_driver_register(&au1xmmc_driver);
 }
 
 static void __exit au1xmmc_exit(void)
 {
+#ifdef CONFIG_SOC_AU1200
+       if (memid)
+               au1xxx_ddma_del_device(memid);
+#endif
        platform_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");
 MODULE_ALIAS("platform:au1xxx-mmc");
-#endif
-
diff --git a/drivers/mmc/host/au1xmmc.h b/drivers/mmc/host/au1xmmc.h
deleted file mode 100644 (file)
index 341cbdf..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-#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
index eed211b2ac707dc6bf02af26cdf8c60d443d63d7..5e880c0f13495105105efee78d1eadcebdfa4a29 100644 (file)
@@ -892,9 +892,12 @@ static int imxmci_get_ro(struct mmc_host *mmc)
        struct imxmci_host *host = mmc_priv(mmc);
 
        if (host->pdata && host->pdata->get_ro)
-               return host->pdata->get_ro(mmc_dev(mmc));
-       /* Host doesn't support read only detection so assume writeable */
-       return 0;
+               return !!host->pdata->get_ro(mmc_dev(mmc));
+       /*
+        * Board doesn't support read only detection; let the mmc core
+        * decide what to do.
+        */
+       return -ENOSYS;
 }
 
 
index 35508584ac2ae55ffae78479877fe67b32a3a7d2..41cc63360e43d357db2d5d42a06cf55b0abd47b3 100644 (file)
@@ -1126,16 +1126,28 @@ static int mmc_spi_get_ro(struct mmc_host *mmc)
        struct mmc_spi_host *host = mmc_priv(mmc);
 
        if (host->pdata && host->pdata->get_ro)
-               return host->pdata->get_ro(mmc->parent);
-       /* board doesn't support read only detection; assume writeable */
-       return 0;
+               return !!host->pdata->get_ro(mmc->parent);
+       /*
+        * Board doesn't support read only detection; let the mmc core
+        * decide what to do.
+        */
+       return -ENOSYS;
 }
 
+static int mmc_spi_get_cd(struct mmc_host *mmc)
+{
+       struct mmc_spi_host *host = mmc_priv(mmc);
+
+       if (host->pdata && host->pdata->get_cd)
+               return !!host->pdata->get_cd(mmc->parent);
+       return -ENOSYS;
+}
 
 static const struct mmc_host_ops mmc_spi_ops = {
        .request        = mmc_spi_request,
        .set_ios        = mmc_spi_set_ios,
        .get_ro         = mmc_spi_get_ro,
+       .get_cd         = mmc_spi_get_cd,
 };
 
 
@@ -1240,10 +1252,7 @@ static int mmc_spi_probe(struct spi_device *spi)
        mmc->ops = &mmc_spi_ops;
        mmc->max_blk_size = MMC_SPI_BLOCKSIZE;
 
-       /* As long as we keep track of the number of successfully
-        * transmitted blocks, we're good for multiwrite.
-        */
-       mmc->caps = MMC_CAP_SPI | MMC_CAP_MULTIWRITE;
+       mmc->caps = MMC_CAP_SPI;
 
        /* SPI doesn't need the lowspeed device identification thing for
         * MMC or SD cards, since it never comes up in open drain mode.
@@ -1319,17 +1328,23 @@ static int mmc_spi_probe(struct spi_device *spi)
                        goto fail_glue_init;
        }
 
+       /* pass platform capabilities, if any */
+       if (host->pdata)
+               mmc->caps |= host->pdata->caps;
+
        status = mmc_add_host(mmc);
        if (status != 0)
                goto fail_add_host;
 
-       dev_info(&spi->dev, "SD/MMC host %s%s%s%s\n",
+       dev_info(&spi->dev, "SD/MMC host %s%s%s%s%s\n",
                        mmc->class_dev.bus_id,
                        host->dma_dev ? "" : ", no DMA",
                        (host->pdata && host->pdata->get_ro)
                                ? "" : ", no WP",
                        (host->pdata && host->pdata->setpower)
-                               ? "" : ", no poweroff");
+                               ? "" : ", no poweroff",
+                       (mmc->caps & MMC_CAP_NEEDS_POLL)
+                               ? ", cd polling" : "");
        return 0;
 
 fail_add_host:
index da5fecad74d9a79152076730981a36d35cfe3bed..696cf3647ceb3e61c6a9f41bf5ffae889c11ffef 100644 (file)
@@ -535,7 +535,6 @@ static int mmci_probe(struct amba_device *dev, void *id)
        mmc->f_min = (host->mclk + 511) / 512;
        mmc->f_max = min(host->mclk, fmax);
        mmc->ocr_avail = plat->ocr_mask;
-       mmc->caps = MMC_CAP_MULTIWRITE;
 
        /*
         * We can do SGIO
index 549517c35675971b3448a988f83cb2b2369b305d..dbc26eb6a89e06c280f263a88ed02a20736220e7 100644 (file)
@@ -1317,7 +1317,7 @@ static int __init mmc_omap_new_slot(struct mmc_omap_host *host, int id)
 
        host->slots[id] = slot;
 
-       mmc->caps = MMC_CAP_MULTIWRITE;
+       mmc->caps = 0;
        if (host->pdata->conf.wire4)
                mmc->caps |= MMC_CAP_4_BIT_DATA;
 
index d89475d36988228c09e60b54034c288bd81368e6..d39f59738866c02e8712b9d3eb6cc686de808382 100644 (file)
@@ -374,9 +374,12 @@ static int pxamci_get_ro(struct mmc_host *mmc)
        struct pxamci_host *host = mmc_priv(mmc);
 
        if (host->pdata && host->pdata->get_ro)
-               return host->pdata->get_ro(mmc_dev(mmc));
-       /* Host doesn't support read only detection so assume writeable */
-       return 0;
+               return !!host->pdata->get_ro(mmc_dev(mmc));
+       /*
+        * Board doesn't support read only detection; let the mmc core
+        * decide what to do.
+        */
+       return -ENOSYS;
 }
 
 static void pxamci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
diff --git a/drivers/mmc/host/s3cmci.c b/drivers/mmc/host/s3cmci.c
new file mode 100644 (file)
index 0000000..6a1e499
--- /dev/null
@@ -0,0 +1,1446 @@
+/*
+ *  linux/drivers/mmc/s3cmci.h - Samsung S3C MCI driver
+ *
+ *  Copyright (C) 2004-2006 maintech GmbH, Thomas Kleffel <tk@maintech.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/clk.h>
+#include <linux/mmc/host.h>
+#include <linux/platform_device.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+
+#include <asm/dma.h>
+
+#include <asm/arch/regs-sdi.h>
+#include <asm/arch/regs-gpio.h>
+
+#include <asm/plat-s3c24xx/mci.h>
+
+#include "s3cmci.h"
+
+#define DRIVER_NAME "s3c-mci"
+
+enum dbg_channels {
+       dbg_err   = (1 << 0),
+       dbg_debug = (1 << 1),
+       dbg_info  = (1 << 2),
+       dbg_irq   = (1 << 3),
+       dbg_sg    = (1 << 4),
+       dbg_dma   = (1 << 5),
+       dbg_pio   = (1 << 6),
+       dbg_fail  = (1 << 7),
+       dbg_conf  = (1 << 8),
+};
+
+static const int dbgmap_err   = dbg_err | dbg_fail;
+static const int dbgmap_info  = dbg_info | dbg_conf;
+static const int dbgmap_debug = dbg_debug;
+
+#define dbg(host, channels, args...)             \
+       do {                                      \
+       if (dbgmap_err & channels)                \
+               dev_err(&host->pdev->dev, args);  \
+       else if (dbgmap_info & channels)          \
+               dev_info(&host->pdev->dev, args); \
+       else if (dbgmap_debug & channels)         \
+               dev_dbg(&host->pdev->dev, args);  \
+       } while (0)
+
+#define RESSIZE(ressource) (((ressource)->end - (ressource)->start)+1)
+
+static struct s3c2410_dma_client s3cmci_dma_client = {
+       .name           = "s3c-mci",
+};
+
+static void finalize_request(struct s3cmci_host *host);
+static void s3cmci_send_request(struct mmc_host *mmc);
+static void s3cmci_reset(struct s3cmci_host *host);
+
+#ifdef CONFIG_MMC_DEBUG
+
+static void dbg_dumpregs(struct s3cmci_host *host, char *prefix)
+{
+       u32 con, pre, cmdarg, cmdcon, cmdsta, r0, r1, r2, r3, timer, bsize;
+       u32 datcon, datcnt, datsta, fsta, imask;
+
+       con     = readl(host->base + S3C2410_SDICON);
+       pre     = readl(host->base + S3C2410_SDIPRE);
+       cmdarg  = readl(host->base + S3C2410_SDICMDARG);
+       cmdcon  = readl(host->base + S3C2410_SDICMDCON);
+       cmdsta  = readl(host->base + S3C2410_SDICMDSTAT);
+       r0      = readl(host->base + S3C2410_SDIRSP0);
+       r1      = readl(host->base + S3C2410_SDIRSP1);
+       r2      = readl(host->base + S3C2410_SDIRSP2);
+       r3      = readl(host->base + S3C2410_SDIRSP3);
+       timer   = readl(host->base + S3C2410_SDITIMER);
+       bsize   = readl(host->base + S3C2410_SDIBSIZE);
+       datcon  = readl(host->base + S3C2410_SDIDCON);
+       datcnt  = readl(host->base + S3C2410_SDIDCNT);
+       datsta  = readl(host->base + S3C2410_SDIDSTA);
+       fsta    = readl(host->base + S3C2410_SDIFSTA);
+       imask   = readl(host->base + host->sdiimsk);
+
+       dbg(host, dbg_debug, "%s  CON:[%08x]  PRE:[%08x]  TMR:[%08x]\n",
+                               prefix, con, pre, timer);
+
+       dbg(host, dbg_debug, "%s CCON:[%08x] CARG:[%08x] CSTA:[%08x]\n",
+                               prefix, cmdcon, cmdarg, cmdsta);
+
+       dbg(host, dbg_debug, "%s DCON:[%08x] FSTA:[%08x]"
+                              " DSTA:[%08x] DCNT:[%08x]\n",
+                               prefix, datcon, fsta, datsta, datcnt);
+
+       dbg(host, dbg_debug, "%s   R0:[%08x]   R1:[%08x]"
+                              "   R2:[%08x]   R3:[%08x]\n",
+                               prefix, r0, r1, r2, r3);
+}
+
+static void prepare_dbgmsg(struct s3cmci_host *host, struct mmc_command *cmd,
+                          int stop)
+{
+       snprintf(host->dbgmsg_cmd, 300,
+                "#%u%s op:%i arg:0x%08x flags:0x08%x retries:%u",
+                host->ccnt, (stop ? " (STOP)" : ""),
+                cmd->opcode, cmd->arg, cmd->flags, cmd->retries);
+
+       if (cmd->data) {
+               snprintf(host->dbgmsg_dat, 300,
+                        "#%u bsize:%u blocks:%u bytes:%u",
+                        host->dcnt, cmd->data->blksz,
+                        cmd->data->blocks,
+                        cmd->data->blocks * cmd->data->blksz);
+       } else {
+               host->dbgmsg_dat[0] = '\0';
+       }
+}
+
+static void dbg_dumpcmd(struct s3cmci_host *host, struct mmc_command *cmd,
+                       int fail)
+{
+       unsigned int dbglvl = fail ? dbg_fail : dbg_debug;
+
+       if (!cmd)
+               return;
+
+       if (cmd->error == 0) {
+               dbg(host, dbglvl, "CMD[OK] %s R0:0x%08x\n",
+                       host->dbgmsg_cmd, cmd->resp[0]);
+       } else {
+               dbg(host, dbglvl, "CMD[ERR %i] %s Status:%s\n",
+                       cmd->error, host->dbgmsg_cmd, host->status);
+       }
+
+       if (!cmd->data)
+               return;
+
+       if (cmd->data->error == 0) {
+               dbg(host, dbglvl, "DAT[OK] %s\n", host->dbgmsg_dat);
+       } else {
+               dbg(host, dbglvl, "DAT[ERR %i] %s DCNT:0x%08x\n",
+                       cmd->data->error, host->dbgmsg_dat,
+                       readl(host->base + S3C2410_SDIDCNT));
+       }
+}
+#else
+static void dbg_dumpcmd(struct s3cmci_host *host,
+                       struct mmc_command *cmd, int fail) { }
+
+static void prepare_dbgmsg(struct s3cmci_host *host, struct mmc_command *cmd,
+                          int stop) { }
+
+static void dbg_dumpregs(struct s3cmci_host *host, char *prefix) { }
+
+#endif /* CONFIG_MMC_DEBUG */
+
+static inline u32 enable_imask(struct s3cmci_host *host, u32 imask)
+{
+       u32 newmask;
+
+       newmask = readl(host->base + host->sdiimsk);
+       newmask |= imask;
+
+       writel(newmask, host->base + host->sdiimsk);
+
+       return newmask;
+}
+
+static inline u32 disable_imask(struct s3cmci_host *host, u32 imask)
+{
+       u32 newmask;
+
+       newmask = readl(host->base + host->sdiimsk);
+       newmask &= ~imask;
+
+       writel(newmask, host->base + host->sdiimsk);
+
+       return newmask;
+}
+
+static inline void clear_imask(struct s3cmci_host *host)
+{
+       writel(0, host->base + host->sdiimsk);
+}
+
+static inline int get_data_buffer(struct s3cmci_host *host,
+                                 u32 *words, u32 **pointer)
+{
+       struct scatterlist *sg;
+
+       if (host->pio_active == XFER_NONE)
+               return -EINVAL;
+
+       if ((!host->mrq) || (!host->mrq->data))
+               return -EINVAL;
+
+       if (host->pio_sgptr >= host->mrq->data->sg_len) {
+               dbg(host, dbg_debug, "no more buffers (%i/%i)\n",
+                     host->pio_sgptr, host->mrq->data->sg_len);
+               return -EBUSY;
+       }
+       sg = &host->mrq->data->sg[host->pio_sgptr];
+
+       *words = sg->length >> 2;
+       *pointer = sg_virt(sg);
+
+       host->pio_sgptr++;
+
+       dbg(host, dbg_sg, "new buffer (%i/%i)\n",
+           host->pio_sgptr, host->mrq->data->sg_len);
+
+       return 0;
+}
+
+static inline u32 fifo_count(struct s3cmci_host *host)
+{
+       u32 fifostat = readl(host->base + S3C2410_SDIFSTA);
+
+       fifostat &= S3C2410_SDIFSTA_COUNTMASK;
+       return fifostat >> 2;
+}
+
+static inline u32 fifo_free(struct s3cmci_host *host)
+{
+       u32 fifostat = readl(host->base + S3C2410_SDIFSTA);
+
+       fifostat &= S3C2410_SDIFSTA_COUNTMASK;
+       return (63 - fifostat) >> 2;
+}
+
+static void do_pio_read(struct s3cmci_host *host)
+{
+       int res;
+       u32 fifo;
+       void __iomem *from_ptr;
+
+       /* write real prescaler to host, it might be set slow to fix */
+       writel(host->prescaler, host->base + S3C2410_SDIPRE);
+
+       from_ptr = host->base + host->sdidata;
+
+       while ((fifo = fifo_count(host))) {
+               if (!host->pio_words) {
+                       res = get_data_buffer(host, &host->pio_words,
+                                             &host->pio_ptr);
+                       if (res) {
+                               host->pio_active = XFER_NONE;
+                               host->complete_what = COMPLETION_FINALIZE;
+
+                               dbg(host, dbg_pio, "pio_read(): "
+                                   "complete (no more data).\n");
+                               return;
+                       }
+
+                       dbg(host, dbg_pio,
+                           "pio_read(): new target: [%i]@[%p]\n",
+                           host->pio_words, host->pio_ptr);
+               }
+
+               dbg(host, dbg_pio,
+                   "pio_read(): fifo:[%02i] buffer:[%03i] dcnt:[%08X]\n",
+                   fifo, host->pio_words,
+                   readl(host->base + S3C2410_SDIDCNT));
+
+               if (fifo > host->pio_words)
+                       fifo = host->pio_words;
+
+               host->pio_words -= fifo;
+               host->pio_count += fifo;
+
+               while (fifo--)
+                       *(host->pio_ptr++) = readl(from_ptr);
+       }
+
+       if (!host->pio_words) {
+               res = get_data_buffer(host, &host->pio_words, &host->pio_ptr);
+               if (res) {
+                       dbg(host, dbg_pio,
+                           "pio_read(): complete (no more buffers).\n");
+                       host->pio_active = XFER_NONE;
+                       host->complete_what = COMPLETION_FINALIZE;
+
+                       return;
+               }
+       }
+
+       enable_imask(host,
+                    S3C2410_SDIIMSK_RXFIFOHALF | S3C2410_SDIIMSK_RXFIFOLAST);
+}
+
+static void do_pio_write(struct s3cmci_host *host)
+{
+       void __iomem *to_ptr;
+       int res;
+       u32 fifo;
+
+       to_ptr = host->base + host->sdidata;
+
+       while ((fifo = fifo_free(host))) {
+               if (!host->pio_words) {
+                       res = get_data_buffer(host, &host->pio_words,
+                                                       &host->pio_ptr);
+                       if (res) {
+                               dbg(host, dbg_pio,
+                                   "pio_write(): complete (no more data).\n");
+                               host->pio_active = XFER_NONE;
+
+                               return;
+                       }
+
+                       dbg(host, dbg_pio,
+                           "pio_write(): new source: [%i]@[%p]\n",
+                           host->pio_words, host->pio_ptr);
+
+               }
+
+               if (fifo > host->pio_words)
+                       fifo = host->pio_words;
+
+               host->pio_words -= fifo;
+               host->pio_count += fifo;
+
+               while (fifo--)
+                       writel(*(host->pio_ptr++), to_ptr);
+       }
+
+       enable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF);
+}
+
+static void pio_tasklet(unsigned long data)
+{
+       struct s3cmci_host *host = (struct s3cmci_host *) data;
+
+
+       disable_irq(host->irq);
+
+       if (host->pio_active == XFER_WRITE)
+               do_pio_write(host);
+
+       if (host->pio_active == XFER_READ)
+               do_pio_read(host);
+
+       if (host->complete_what == COMPLETION_FINALIZE) {
+               clear_imask(host);
+               if (host->pio_active != XFER_NONE) {
+                       dbg(host, dbg_err, "unfinished %s "
+                           "- pio_count:[%u] pio_words:[%u]\n",
+                           (host->pio_active == XFER_READ) ? "read" : "write",
+                           host->pio_count, host->pio_words);
+
+                       if (host->mrq->data)
+                               host->mrq->data->error = -EINVAL;
+               }
+
+               finalize_request(host);
+       } else
+               enable_irq(host->irq);
+}
+
+/*
+ * ISR for SDI Interface IRQ
+ * Communication between driver and ISR works as follows:
+ *   host->mrq                         points to current request
+ *   host->complete_what       Indicates when the request is considered done
+ *     COMPLETION_CMDSENT        when the command was sent
+ *     COMPLETION_RSPFIN          when a response was received
+ *     COMPLETION_XFERFINISH     when the data transfer is finished
+ *     COMPLETION_XFERFINISH_RSPFIN both of the above.
+ *   host->complete_request    is the completion-object the driver waits for
+ *
+ * 1) Driver sets up host->mrq and host->complete_what
+ * 2) Driver prepares the transfer
+ * 3) Driver enables interrupts
+ * 4) Driver starts transfer
+ * 5) Driver waits for host->complete_rquest
+ * 6) ISR checks for request status (errors and success)
+ * 6) ISR sets host->mrq->cmd->error and host->mrq->data->error
+ * 7) ISR completes host->complete_request
+ * 8) ISR disables interrupts
+ * 9) Driver wakes up and takes care of the request
+ *
+ * Note: "->error"-fields are expected to be set to 0 before the request
+ *       was issued by mmc.c - therefore they are only set, when an error
+ *       contition comes up
+ */
+
+static irqreturn_t s3cmci_irq(int irq, void *dev_id)
+{
+       struct s3cmci_host *host = dev_id;
+       struct mmc_command *cmd;
+       u32 mci_csta, mci_dsta, mci_fsta, mci_dcnt, mci_imsk;
+       u32 mci_cclear, mci_dclear;
+       unsigned long iflags;
+
+       spin_lock_irqsave(&host->complete_lock, iflags);
+
+       mci_csta = readl(host->base + S3C2410_SDICMDSTAT);
+       mci_dsta = readl(host->base + S3C2410_SDIDSTA);
+       mci_dcnt = readl(host->base + S3C2410_SDIDCNT);
+       mci_fsta = readl(host->base + S3C2410_SDIFSTA);
+       mci_imsk = readl(host->base + host->sdiimsk);
+       mci_cclear = 0;
+       mci_dclear = 0;
+
+       if ((host->complete_what == COMPLETION_NONE) ||
+           (host->complete_what == COMPLETION_FINALIZE)) {
+               host->status = "nothing to complete";
+               clear_imask(host);
+               goto irq_out;
+       }
+
+       if (!host->mrq) {
+               host->status = "no active mrq";
+               clear_imask(host);
+               goto irq_out;
+       }
+
+       cmd = host->cmd_is_stop ? host->mrq->stop : host->mrq->cmd;
+
+       if (!cmd) {
+               host->status = "no active cmd";
+               clear_imask(host);
+               goto irq_out;
+       }
+
+       if (!host->dodma) {
+               if ((host->pio_active == XFER_WRITE) &&
+                   (mci_fsta & S3C2410_SDIFSTA_TFDET)) {
+
+                       disable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF);
+                       tasklet_schedule(&host->pio_tasklet);
+                       host->status = "pio tx";
+               }
+
+               if ((host->pio_active == XFER_READ) &&
+                   (mci_fsta & S3C2410_SDIFSTA_RFDET)) {
+
+                       disable_imask(host,
+                                     S3C2410_SDIIMSK_RXFIFOHALF |
+                                     S3C2410_SDIIMSK_RXFIFOLAST);
+
+                       tasklet_schedule(&host->pio_tasklet);
+                       host->status = "pio rx";
+               }
+       }
+
+       if (mci_csta & S3C2410_SDICMDSTAT_CMDTIMEOUT) {
+               dbg(host, dbg_err, "CMDSTAT: error CMDTIMEOUT\n");
+               cmd->error = -ETIMEDOUT;
+               host->status = "error: command timeout";
+               goto fail_transfer;
+       }
+
+       if (mci_csta & S3C2410_SDICMDSTAT_CMDSENT) {
+               if (host->complete_what == COMPLETION_CMDSENT) {
+                       host->status = "ok: command sent";
+                       goto close_transfer;
+               }
+
+               mci_cclear |= S3C2410_SDICMDSTAT_CMDSENT;
+       }
+
+       if (mci_csta & S3C2410_SDICMDSTAT_CRCFAIL) {
+               if (cmd->flags & MMC_RSP_CRC) {
+                       if (host->mrq->cmd->flags & MMC_RSP_136) {
+                               dbg(host, dbg_irq,
+                                   "fixup: ignore CRC fail with long rsp\n");
+                       } else {
+                               /* note, we used to fail the transfer
+                                * here, but it seems that this is just
+                                * the hardware getting it wrong.
+                                *
+                                * cmd->error = -EILSEQ;
+                                * host->status = "error: bad command crc";
+                                * goto fail_transfer;
+                               */
+                       }
+               }
+
+               mci_cclear |= S3C2410_SDICMDSTAT_CRCFAIL;
+       }
+
+       if (mci_csta & S3C2410_SDICMDSTAT_RSPFIN) {
+               if (host->complete_what == COMPLETION_RSPFIN) {
+                       host->status = "ok: command response received";
+                       goto close_transfer;
+               }
+
+               if (host->complete_what == COMPLETION_XFERFINISH_RSPFIN)
+                       host->complete_what = COMPLETION_XFERFINISH;
+
+               mci_cclear |= S3C2410_SDICMDSTAT_RSPFIN;
+       }
+
+       /* errors handled after this point are only relevant
+          when a data transfer is in progress */
+
+       if (!cmd->data)
+               goto clear_status_bits;
+
+       /* Check for FIFO failure */
+       if (host->is2440) {
+               if (mci_fsta & S3C2440_SDIFSTA_FIFOFAIL) {
+                       dbg(host, dbg_err, "FIFO failure\n");
+                       host->mrq->data->error = -EILSEQ;
+                       host->status = "error: 2440 fifo failure";
+                       goto fail_transfer;
+               }
+       } else {
+               if (mci_dsta & S3C2410_SDIDSTA_FIFOFAIL) {
+                       dbg(host, dbg_err, "FIFO failure\n");
+                       cmd->data->error = -EILSEQ;
+                       host->status = "error:  fifo failure";
+                       goto fail_transfer;
+               }
+       }
+
+       if (mci_dsta & S3C2410_SDIDSTA_RXCRCFAIL) {
+               dbg(host, dbg_err, "bad data crc (outgoing)\n");
+               cmd->data->error = -EILSEQ;
+               host->status = "error: bad data crc (outgoing)";
+               goto fail_transfer;
+       }
+
+       if (mci_dsta & S3C2410_SDIDSTA_CRCFAIL) {
+               dbg(host, dbg_err, "bad data crc (incoming)\n");
+               cmd->data->error = -EILSEQ;
+               host->status = "error: bad data crc (incoming)";
+               goto fail_transfer;
+       }
+
+       if (mci_dsta & S3C2410_SDIDSTA_DATATIMEOUT) {
+               dbg(host, dbg_err, "data timeout\n");
+               cmd->data->error = -ETIMEDOUT;
+               host->status = "error: data timeout";
+               goto fail_transfer;
+       }
+
+       if (mci_dsta & S3C2410_SDIDSTA_XFERFINISH) {
+               if (host->complete_what == COMPLETION_XFERFINISH) {
+                       host->status = "ok: data transfer completed";
+                       goto close_transfer;
+               }
+
+               if (host->complete_what == COMPLETION_XFERFINISH_RSPFIN)
+                       host->complete_what = COMPLETION_RSPFIN;
+
+               mci_dclear |= S3C2410_SDIDSTA_XFERFINISH;
+       }
+
+clear_status_bits:
+       writel(mci_cclear, host->base + S3C2410_SDICMDSTAT);
+       writel(mci_dclear, host->base + S3C2410_SDIDSTA);
+
+       goto irq_out;
+
+fail_transfer:
+       host->pio_active = XFER_NONE;
+
+close_transfer:
+       host->complete_what = COMPLETION_FINALIZE;
+
+       clear_imask(host);
+       tasklet_schedule(&host->pio_tasklet);
+
+       goto irq_out;
+
+irq_out:
+       dbg(host, dbg_irq,
+           "csta:0x%08x dsta:0x%08x fsta:0x%08x dcnt:0x%08x status:%s.\n",
+           mci_csta, mci_dsta, mci_fsta, mci_dcnt, host->status);
+
+       spin_unlock_irqrestore(&host->complete_lock, iflags);
+       return IRQ_HANDLED;
+
+}
+
+/*
+ * ISR for the CardDetect Pin
+*/
+
+static irqreturn_t s3cmci_irq_cd(int irq, void *dev_id)
+{
+       struct s3cmci_host *host = (struct s3cmci_host *)dev_id;
+
+       dbg(host, dbg_irq, "card detect\n");
+
+       mmc_detect_change(host->mmc, msecs_to_jiffies(500));
+
+       return IRQ_HANDLED;
+}
+
+void s3cmci_dma_done_callback(struct s3c2410_dma_chan *dma_ch, void *buf_id,
+                             int size, enum s3c2410_dma_buffresult result)
+{
+       struct s3cmci_host *host = buf_id;
+       unsigned long iflags;
+       u32 mci_csta, mci_dsta, mci_fsta, mci_dcnt;
+
+       mci_csta = readl(host->base + S3C2410_SDICMDSTAT);
+       mci_dsta = readl(host->base + S3C2410_SDIDSTA);
+       mci_fsta = readl(host->base + S3C2410_SDIFSTA);
+       mci_dcnt = readl(host->base + S3C2410_SDIDCNT);
+
+       BUG_ON(!host->mrq);
+       BUG_ON(!host->mrq->data);
+       BUG_ON(!host->dmatogo);
+
+       spin_lock_irqsave(&host->complete_lock, iflags);
+
+       if (result != S3C2410_RES_OK) {
+               dbg(host, dbg_fail, "DMA FAILED: csta=0x%08x dsta=0x%08x "
+                       "fsta=0x%08x dcnt:0x%08x result:0x%08x toGo:%u\n",
+                       mci_csta, mci_dsta, mci_fsta,
+                       mci_dcnt, result, host->dmatogo);
+
+               goto fail_request;
+       }
+
+       host->dmatogo--;
+       if (host->dmatogo) {
+               dbg(host, dbg_dma, "DMA DONE  Size:%i DSTA:[%08x] "
+                       "DCNT:[%08x] toGo:%u\n",
+                       size, mci_dsta, mci_dcnt, host->dmatogo);
+
+               goto out;
+       }
+
+       dbg(host, dbg_dma, "DMA FINISHED Size:%i DSTA:%08x DCNT:%08x\n",
+               size, mci_dsta, mci_dcnt);
+
+       host->complete_what = COMPLETION_FINALIZE;
+
+out:
+       tasklet_schedule(&host->pio_tasklet);
+       spin_unlock_irqrestore(&host->complete_lock, iflags);
+       return;
+
+fail_request:
+       host->mrq->data->error = -EINVAL;
+       host->complete_what = COMPLETION_FINALIZE;
+       writel(0, host->base + host->sdiimsk);
+       goto out;
+
+}
+
+static void finalize_request(struct s3cmci_host *host)
+{
+       struct mmc_request *mrq = host->mrq;
+       struct mmc_command *cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd;
+       int debug_as_failure = 0;
+
+       if (host->complete_what != COMPLETION_FINALIZE)
+               return;
+
+       if (!mrq)
+               return;
+
+       if (cmd->data && (cmd->error == 0) &&
+           (cmd->data->error == 0)) {
+               if (host->dodma && (!host->dma_complete)) {
+                       dbg(host, dbg_dma, "DMA Missing!\n");
+                       return;
+               }
+       }
+
+       /* Read response from controller. */
+       cmd->resp[0] = readl(host->base + S3C2410_SDIRSP0);
+       cmd->resp[1] = readl(host->base + S3C2410_SDIRSP1);
+       cmd->resp[2] = readl(host->base + S3C2410_SDIRSP2);
+       cmd->resp[3] = readl(host->base + S3C2410_SDIRSP3);
+
+       writel(host->prescaler, host->base + S3C2410_SDIPRE);
+
+       if (cmd->error)
+               debug_as_failure = 1;
+
+       if (cmd->data && cmd->data->error)
+               debug_as_failure = 1;
+
+       dbg_dumpcmd(host, cmd, debug_as_failure);
+
+       /* Cleanup controller */
+       writel(0, host->base + S3C2410_SDICMDARG);
+       writel(S3C2410_SDIDCON_STOP, host->base + S3C2410_SDIDCON);
+       writel(0, host->base + S3C2410_SDICMDCON);
+       writel(0, host->base + host->sdiimsk);
+
+       if (cmd->data && cmd->error)
+               cmd->data->error = cmd->error;
+
+       if (cmd->data && cmd->data->stop && (!host->cmd_is_stop)) {
+               host->cmd_is_stop = 1;
+               s3cmci_send_request(host->mmc);
+               return;
+       }
+
+       /* If we have no data transfer we are finished here */
+       if (!mrq->data)
+               goto request_done;
+
+       /* Calulate the amout of bytes transfer if there was no error */
+       if (mrq->data->error == 0) {
+               mrq->data->bytes_xfered =
+                       (mrq->data->blocks * mrq->data->blksz);
+       } else {
+               mrq->data->bytes_xfered = 0;
+       }
+
+       /* If we had an error while transfering data we flush the
+        * DMA channel and the fifo to clear out any garbage. */
+       if (mrq->data->error != 0) {
+               if (host->dodma)
+                       s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH);
+
+               if (host->is2440) {
+                       /* Clear failure register and reset fifo. */
+                       writel(S3C2440_SDIFSTA_FIFORESET |
+                              S3C2440_SDIFSTA_FIFOFAIL,
+                              host->base + S3C2410_SDIFSTA);
+               } else {
+                       u32 mci_con;
+
+                       /* reset fifo */
+                       mci_con = readl(host->base + S3C2410_SDICON);
+                       mci_con |= S3C2410_SDICON_FIFORESET;
+
+                       writel(mci_con, host->base + S3C2410_SDICON);
+               }
+       }
+
+request_done:
+       host->complete_what = COMPLETION_NONE;
+       host->mrq = NULL;
+       mmc_request_done(host->mmc, mrq);
+}
+
+
+void s3cmci_dma_setup(struct s3cmci_host *host, enum s3c2410_dmasrc source)
+{
+       static enum s3c2410_dmasrc last_source = -1;
+       static int setup_ok;
+
+       if (last_source == source)
+               return;
+
+       last_source = source;
+
+       s3c2410_dma_devconfig(host->dma, source, 3,
+                             host->mem->start + host->sdidata);
+
+       if (!setup_ok) {
+               s3c2410_dma_config(host->dma, 4,
+                       (S3C2410_DCON_HWTRIG | S3C2410_DCON_CH0_SDI));
+               s3c2410_dma_set_buffdone_fn(host->dma,
+                                           s3cmci_dma_done_callback);
+               s3c2410_dma_setflags(host->dma, S3C2410_DMAF_AUTOSTART);
+               setup_ok = 1;
+       }
+}
+
+static void s3cmci_send_command(struct s3cmci_host *host,
+                                       struct mmc_command *cmd)
+{
+       u32 ccon, imsk;
+
+       imsk  = S3C2410_SDIIMSK_CRCSTATUS | S3C2410_SDIIMSK_CMDTIMEOUT |
+               S3C2410_SDIIMSK_RESPONSEND | S3C2410_SDIIMSK_CMDSENT |
+               S3C2410_SDIIMSK_RESPONSECRC;
+
+       enable_imask(host, imsk);
+
+       if (cmd->data)
+               host->complete_what = COMPLETION_XFERFINISH_RSPFIN;
+       else if (cmd->flags & MMC_RSP_PRESENT)
+               host->complete_what = COMPLETION_RSPFIN;
+       else
+               host->complete_what = COMPLETION_CMDSENT;
+
+       writel(cmd->arg, host->base + S3C2410_SDICMDARG);
+
+       ccon  = cmd->opcode & S3C2410_SDICMDCON_INDEX;
+       ccon |= S3C2410_SDICMDCON_SENDERHOST | S3C2410_SDICMDCON_CMDSTART;
+
+       if (cmd->flags & MMC_RSP_PRESENT)
+               ccon |= S3C2410_SDICMDCON_WAITRSP;
+
+       if (cmd->flags & MMC_RSP_136)
+               ccon |= S3C2410_SDICMDCON_LONGRSP;
+
+       writel(ccon, host->base + S3C2410_SDICMDCON);
+}
+
+static int s3cmci_setup_data(struct s3cmci_host *host, struct mmc_data *data)
+{
+       u32 dcon, imsk, stoptries = 3;
+
+       /* write DCON register */
+
+       if (!data) {
+               writel(0, host->base + S3C2410_SDIDCON);
+               return 0;
+       }
+
+       if ((data->blksz & 3) != 0) {
+               /* We cannot deal with unaligned blocks with more than
+                * one block being transfered. */
+
+               if (data->blocks > 1)
+                       return -EINVAL;
+
+               /* No support yet for non-word block transfers. */
+               return -EINVAL;
+       }
+
+       while (readl(host->base + S3C2410_SDIDSTA) &
+              (S3C2410_SDIDSTA_TXDATAON | S3C2410_SDIDSTA_RXDATAON)) {
+
+               dbg(host, dbg_err,
+                   "mci_setup_data() transfer stillin progress.\n");
+
+               writel(S3C2410_SDIDCON_STOP, host->base + S3C2410_SDIDCON);
+               s3cmci_reset(host);
+
+               if ((stoptries--) == 0) {
+                       dbg_dumpregs(host, "DRF");
+                       return -EINVAL;
+               }
+       }
+
+       dcon  = data->blocks & S3C2410_SDIDCON_BLKNUM_MASK;
+
+       if (host->dodma)
+               dcon |= S3C2410_SDIDCON_DMAEN;
+
+       if (host->bus_width == MMC_BUS_WIDTH_4)
+               dcon |= S3C2410_SDIDCON_WIDEBUS;
+
+       if (!(data->flags & MMC_DATA_STREAM))
+               dcon |= S3C2410_SDIDCON_BLOCKMODE;
+
+       if (data->flags & MMC_DATA_WRITE) {
+               dcon |= S3C2410_SDIDCON_TXAFTERRESP;
+               dcon |= S3C2410_SDIDCON_XFER_TXSTART;
+       }
+
+       if (data->flags & MMC_DATA_READ) {
+               dcon |= S3C2410_SDIDCON_RXAFTERCMD;
+               dcon |= S3C2410_SDIDCON_XFER_RXSTART;
+       }
+
+       if (host->is2440) {
+               dcon |= S3C2440_SDIDCON_DS_WORD;
+               dcon |= S3C2440_SDIDCON_DATSTART;
+       }
+
+       writel(dcon, host->base + S3C2410_SDIDCON);
+
+       /* write BSIZE register */
+
+       writel(data->blksz, host->base + S3C2410_SDIBSIZE);
+
+       /* add to IMASK register */
+       imsk = S3C2410_SDIIMSK_FIFOFAIL | S3C2410_SDIIMSK_DATACRC |
+              S3C2410_SDIIMSK_DATATIMEOUT | S3C2410_SDIIMSK_DATAFINISH;
+
+       enable_imask(host, imsk);
+
+       /* write TIMER register */
+
+       if (host->is2440) {
+               writel(0x007FFFFF, host->base + S3C2410_SDITIMER);
+       } else {
+               writel(0x0000FFFF, host->base + S3C2410_SDITIMER);
+
+               /* FIX: set slow clock to prevent timeouts on read */
+               if (data->flags & MMC_DATA_READ)
+                       writel(0xFF, host->base + S3C2410_SDIPRE);
+       }
+
+       return 0;
+}
+
+#define BOTH_DIR (MMC_DATA_WRITE | MMC_DATA_READ)
+
+static int s3cmci_prepare_pio(struct s3cmci_host *host, struct mmc_data *data)
+{
+       int rw = (data->flags & MMC_DATA_WRITE) ? 1 : 0;
+
+       BUG_ON((data->flags & BOTH_DIR) == BOTH_DIR);
+
+       host->pio_sgptr = 0;
+       host->pio_words = 0;
+       host->pio_count = 0;
+       host->pio_active = rw ? XFER_WRITE : XFER_READ;
+
+       if (rw) {
+               do_pio_write(host);
+               enable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF);
+       } else {
+               enable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF
+                            | S3C2410_SDIIMSK_RXFIFOLAST);
+       }
+
+       return 0;
+}
+
+static int s3cmci_prepare_dma(struct s3cmci_host *host, struct mmc_data *data)
+{
+       int dma_len, i;
+       int rw = (data->flags & MMC_DATA_WRITE) ? 1 : 0;
+
+       BUG_ON((data->flags & BOTH_DIR) == BOTH_DIR);
+
+       s3cmci_dma_setup(host, rw ? S3C2410_DMASRC_MEM : S3C2410_DMASRC_HW);
+       s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH);
+
+       dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+                            (rw) ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
+
+       if (dma_len == 0)
+               return -ENOMEM;
+
+       host->dma_complete = 0;
+       host->dmatogo = dma_len;
+
+       for (i = 0; i < dma_len; i++) {
+               int res;
+
+               dbg(host, dbg_dma, "enqueue %i:%u@%u\n", i,
+                       sg_dma_address(&data->sg[i]),
+                       sg_dma_len(&data->sg[i]));
+
+               res = s3c2410_dma_enqueue(host->dma, (void *) host,
+                                         sg_dma_address(&data->sg[i]),
+                                         sg_dma_len(&data->sg[i]));
+
+               if (res) {
+                       s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH);
+                       return -EBUSY;
+               }
+       }
+
+       s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_START);
+
+       return 0;
+}
+
+static void s3cmci_send_request(struct mmc_host *mmc)
+{
+       struct s3cmci_host *host = mmc_priv(mmc);
+       struct mmc_request *mrq = host->mrq;
+       struct mmc_command *cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd;
+
+       host->ccnt++;
+       prepare_dbgmsg(host, cmd, host->cmd_is_stop);
+
+       /* Clear command, data and fifo status registers
+          Fifo clear only necessary on 2440, but doesn't hurt on 2410
+       */
+       writel(0xFFFFFFFF, host->base + S3C2410_SDICMDSTAT);
+       writel(0xFFFFFFFF, host->base + S3C2410_SDIDSTA);
+       writel(0xFFFFFFFF, host->base + S3C2410_SDIFSTA);
+
+       if (cmd->data) {
+               int res = s3cmci_setup_data(host, cmd->data);
+
+               host->dcnt++;
+
+               if (res) {
+                       dbg(host, dbg_err, "setup data error %d\n", res);
+                       cmd->error = res;
+                       cmd->data->error = res;
+
+                       mmc_request_done(mmc, mrq);
+                       return;
+               }
+
+               if (host->dodma)
+                       res = s3cmci_prepare_dma(host, cmd->data);
+               else
+                       res = s3cmci_prepare_pio(host, cmd->data);
+
+               if (res) {
+                       dbg(host, dbg_err, "data prepare error %d\n", res);
+                       cmd->error = res;
+                       cmd->data->error = res;
+
+                       mmc_request_done(mmc, mrq);
+                       return;
+               }
+       }
+
+       /* Send command */
+       s3cmci_send_command(host, cmd);
+
+       /* Enable Interrupt */
+       enable_irq(host->irq);
+}
+
+static int s3cmci_card_present(struct s3cmci_host *host)
+{
+       struct s3c24xx_mci_pdata *pdata = host->pdata;
+       int ret;
+
+       if (pdata->gpio_detect == 0)
+               return -ENOSYS;
+
+       ret = s3c2410_gpio_getpin(pdata->gpio_detect) ? 0 : 1;
+       return ret ^ pdata->detect_invert;
+}
+
+static void s3cmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+       struct s3cmci_host *host = mmc_priv(mmc);
+
+       host->status = "mmc request";
+       host->cmd_is_stop = 0;
+       host->mrq = mrq;
+
+       if (s3cmci_card_present(host) == 0) {
+               dbg(host, dbg_err, "%s: no medium present\n", __func__);
+               host->mrq->cmd->error = -ENOMEDIUM;
+               mmc_request_done(mmc, mrq);
+       } else
+               s3cmci_send_request(mmc);
+}
+
+static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+       struct s3cmci_host *host = mmc_priv(mmc);
+       u32 mci_psc, mci_con;
+
+       /* Set the power state */
+
+       mci_con = readl(host->base + S3C2410_SDICON);
+
+       switch (ios->power_mode) {
+       case MMC_POWER_ON:
+       case MMC_POWER_UP:
+               s3c2410_gpio_cfgpin(S3C2410_GPE5, S3C2410_GPE5_SDCLK);
+               s3c2410_gpio_cfgpin(S3C2410_GPE6, S3C2410_GPE6_SDCMD);
+               s3c2410_gpio_cfgpin(S3C2410_GPE7, S3C2410_GPE7_SDDAT0);
+               s3c2410_gpio_cfgpin(S3C2410_GPE8, S3C2410_GPE8_SDDAT1);
+               s3c2410_gpio_cfgpin(S3C2410_GPE9, S3C2410_GPE9_SDDAT2);
+               s3c2410_gpio_cfgpin(S3C2410_GPE10, S3C2410_GPE10_SDDAT3);
+
+               if (host->pdata->set_power)
+                       host->pdata->set_power(ios->power_mode, ios->vdd);
+
+               if (!host->is2440)
+                       mci_con |= S3C2410_SDICON_FIFORESET;
+
+               break;
+
+       case MMC_POWER_OFF:
+       default:
+               s3c2410_gpio_setpin(S3C2410_GPE5, 0);
+               s3c2410_gpio_cfgpin(S3C2410_GPE5, S3C2410_GPE5_OUTP);
+
+               if (host->is2440)
+                       mci_con |= S3C2440_SDICON_SDRESET;
+
+               if (host->pdata->set_power)
+                       host->pdata->set_power(ios->power_mode, ios->vdd);
+
+               break;
+       }
+
+       /* Set clock */
+       for (mci_psc = 0; mci_psc < 255; mci_psc++) {
+               host->real_rate = host->clk_rate / (host->clk_div*(mci_psc+1));
+
+               if (host->real_rate <= ios->clock)
+                       break;
+       }
+
+       if (mci_psc > 255)
+               mci_psc = 255;
+
+       host->prescaler = mci_psc;
+       writel(host->prescaler, host->base + S3C2410_SDIPRE);
+
+       /* If requested clock is 0, real_rate will be 0, too */
+       if (ios->clock == 0)
+               host->real_rate = 0;
+
+       /* Set CLOCK_ENABLE */
+       if (ios->clock)
+               mci_con |= S3C2410_SDICON_CLOCKTYPE;
+       else
+               mci_con &= ~S3C2410_SDICON_CLOCKTYPE;
+
+       writel(mci_con, host->base + S3C2410_SDICON);
+
+       if ((ios->power_mode == MMC_POWER_ON) ||
+           (ios->power_mode == MMC_POWER_UP)) {
+               dbg(host, dbg_conf, "running at %lukHz (requested: %ukHz).\n",
+                       host->real_rate/1000, ios->clock/1000);
+       } else {
+               dbg(host, dbg_conf, "powered down.\n");
+       }
+
+       host->bus_width = ios->bus_width;
+}
+
+static void s3cmci_reset(struct s3cmci_host *host)
+{
+       u32 con = readl(host->base + S3C2410_SDICON);
+
+       con |= S3C2440_SDICON_SDRESET;
+       writel(con, host->base + S3C2410_SDICON);
+}
+
+static int s3cmci_get_ro(struct mmc_host *mmc)
+{
+       struct s3cmci_host *host = mmc_priv(mmc);
+       struct s3c24xx_mci_pdata *pdata = host->pdata;
+       int ret;
+
+       if (pdata->gpio_wprotect == 0)
+               return 0;
+
+       ret = s3c2410_gpio_getpin(pdata->gpio_wprotect);
+
+       if (pdata->wprotect_invert)
+               ret = !ret;
+
+       return ret;
+}
+
+static struct mmc_host_ops s3cmci_ops = {
+       .request        = s3cmci_request,
+       .set_ios        = s3cmci_set_ios,
+       .get_ro         = s3cmci_get_ro,
+};
+
+static struct s3c24xx_mci_pdata s3cmci_def_pdata = {
+       /* This is currently here to avoid a number of if (host->pdata)
+        * checks. Any zero fields to ensure reaonable defaults are picked. */
+};
+
+static int __devinit s3cmci_probe(struct platform_device *pdev, int is2440)
+{
+       struct s3cmci_host *host;
+       struct mmc_host *mmc;
+       int ret;
+
+       mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev);
+       if (!mmc) {
+               ret = -ENOMEM;
+               goto probe_out;
+       }
+
+       host = mmc_priv(mmc);
+       host->mmc       = mmc;
+       host->pdev      = pdev;
+       host->is2440    = is2440;
+
+       host->pdata = pdev->dev.platform_data;
+       if (!host->pdata) {
+               pdev->dev.platform_data = &s3cmci_def_pdata;
+               host->pdata = &s3cmci_def_pdata;
+       }
+
+       spin_lock_init(&host->complete_lock);
+       tasklet_init(&host->pio_tasklet, pio_tasklet, (unsigned long) host);
+
+       if (is2440) {
+               host->sdiimsk   = S3C2440_SDIIMSK;
+               host->sdidata   = S3C2440_SDIDATA;
+               host->clk_div   = 1;
+       } else {
+               host->sdiimsk   = S3C2410_SDIIMSK;
+               host->sdidata   = S3C2410_SDIDATA;
+               host->clk_div   = 2;
+       }
+
+       host->dodma             = 0;
+       host->complete_what     = COMPLETION_NONE;
+       host->pio_active        = XFER_NONE;
+
+       host->dma               = S3CMCI_DMA;
+
+       host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!host->mem) {
+               dev_err(&pdev->dev,
+                       "failed to get io memory region resouce.\n");
+
+               ret = -ENOENT;
+               goto probe_free_host;
+       }
+
+       host->mem = request_mem_region(host->mem->start,
+                                      RESSIZE(host->mem), pdev->name);
+
+       if (!host->mem) {
+               dev_err(&pdev->dev, "failed to request io memory region.\n");
+               ret = -ENOENT;
+               goto probe_free_host;
+       }
+
+       host->base = ioremap(host->mem->start, RESSIZE(host->mem));
+       if (host->base == 0) {
+               dev_err(&pdev->dev, "failed to ioremap() io memory region.\n");
+               ret = -EINVAL;
+               goto probe_free_mem_region;
+       }
+
+       host->irq = platform_get_irq(pdev, 0);
+       if (host->irq == 0) {
+               dev_err(&pdev->dev, "failed to get interrupt resouce.\n");
+               ret = -EINVAL;
+               goto probe_iounmap;
+       }
+
+       if (request_irq(host->irq, s3cmci_irq, 0, DRIVER_NAME, host)) {
+               dev_err(&pdev->dev, "failed to request mci interrupt.\n");
+               ret = -ENOENT;
+               goto probe_iounmap;
+       }
+
+       /* We get spurious interrupts even when we have set the IMSK
+        * register to ignore everything, so use disable_irq() to make
+        * ensure we don't lock the system with un-serviceable requests. */
+
+       disable_irq(host->irq);
+
+       host->irq_cd = s3c2410_gpio_getirq(host->pdata->gpio_detect);
+
+       if (host->irq_cd >= 0) {
+               if (request_irq(host->irq_cd, s3cmci_irq_cd,
+                               IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+                               DRIVER_NAME, host)) {
+                       dev_err(&pdev->dev, "can't get card detect irq.\n");
+                       ret = -ENOENT;
+                       goto probe_free_irq;
+               }
+       } else {
+               dev_warn(&pdev->dev, "host detect has no irq available\n");
+               s3c2410_gpio_cfgpin(host->pdata->gpio_detect,
+                                   S3C2410_GPIO_INPUT);
+       }
+
+       if (host->pdata->gpio_wprotect)
+               s3c2410_gpio_cfgpin(host->pdata->gpio_wprotect,
+                                   S3C2410_GPIO_INPUT);
+
+       if (s3c2410_dma_request(S3CMCI_DMA, &s3cmci_dma_client, NULL) < 0) {
+               dev_err(&pdev->dev, "unable to get DMA channel.\n");
+               ret = -EBUSY;
+               goto probe_free_irq_cd;
+       }
+
+       host->clk = clk_get(&pdev->dev, "sdi");
+       if (IS_ERR(host->clk)) {
+               dev_err(&pdev->dev, "failed to find clock source.\n");
+               ret = PTR_ERR(host->clk);
+               host->clk = NULL;
+               goto probe_free_host;
+       }
+
+       ret = clk_enable(host->clk);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to enable clock source.\n");
+               goto clk_free;
+       }
+
+       host->clk_rate = clk_get_rate(host->clk);
+
+       mmc->ops        = &s3cmci_ops;
+       mmc->ocr_avail  = MMC_VDD_32_33 | MMC_VDD_33_34;
+       mmc->caps       = MMC_CAP_4_BIT_DATA;
+       mmc->f_min      = host->clk_rate / (host->clk_div * 256);
+       mmc->f_max      = host->clk_rate / host->clk_div;
+
+       if (host->pdata->ocr_avail)
+               mmc->ocr_avail = host->pdata->ocr_avail;
+
+       mmc->max_blk_count      = 4095;
+       mmc->max_blk_size       = 4095;
+       mmc->max_req_size       = 4095 * 512;
+       mmc->max_seg_size       = mmc->max_req_size;
+
+       mmc->max_phys_segs      = 128;
+       mmc->max_hw_segs        = 128;
+
+       dbg(host, dbg_debug,
+           "probe: mode:%s mapped mci_base:%p irq:%u irq_cd:%u dma:%u.\n",
+           (host->is2440?"2440":""),
+           host->base, host->irq, host->irq_cd, host->dma);
+
+       ret = mmc_add_host(mmc);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to add mmc host.\n");
+               goto free_dmabuf;
+       }
+
+       platform_set_drvdata(pdev, mmc);
+       dev_info(&pdev->dev, "initialisation done.\n");
+
+       return 0;
+
+ free_dmabuf:
+       clk_disable(host->clk);
+
+ clk_free:
+       clk_put(host->clk);
+
+ probe_free_irq_cd:
+       if (host->irq_cd >= 0)
+               free_irq(host->irq_cd, host);
+
+ probe_free_irq:
+       free_irq(host->irq, host);
+
+ probe_iounmap:
+       iounmap(host->base);
+
+ probe_free_mem_region:
+       release_mem_region(host->mem->start, RESSIZE(host->mem));
+
+ probe_free_host:
+       mmc_free_host(mmc);
+ probe_out:
+       return ret;
+}
+
+static int __devexit s3cmci_remove(struct platform_device *pdev)
+{
+       struct mmc_host         *mmc  = platform_get_drvdata(pdev);
+       struct s3cmci_host      *host = mmc_priv(mmc);
+
+       mmc_remove_host(mmc);
+
+       clk_disable(host->clk);
+       clk_put(host->clk);
+
+       tasklet_disable(&host->pio_tasklet);
+       s3c2410_dma_free(S3CMCI_DMA, &s3cmci_dma_client);
+
+       if (host->irq_cd >= 0)
+               free_irq(host->irq_cd, host);
+       free_irq(host->irq, host);
+
+       iounmap(host->base);
+       release_mem_region(host->mem->start, RESSIZE(host->mem));
+
+       mmc_free_host(mmc);
+       return 0;
+}
+
+static int __devinit s3cmci_probe_2410(struct platform_device *dev)
+{
+       return s3cmci_probe(dev, 0);
+}
+
+static int __devinit s3cmci_probe_2412(struct platform_device *dev)
+{
+       return s3cmci_probe(dev, 1);
+}
+
+static int __devinit s3cmci_probe_2440(struct platform_device *dev)
+{
+       return s3cmci_probe(dev, 1);
+}
+
+#ifdef CONFIG_PM
+
+static int s3cmci_suspend(struct platform_device *dev, pm_message_t state)
+{
+       struct mmc_host *mmc = platform_get_drvdata(dev);
+
+       return  mmc_suspend_host(mmc, state);
+}
+
+static int s3cmci_resume(struct platform_device *dev)
+{
+       struct mmc_host *mmc = platform_get_drvdata(dev);
+
+       return mmc_resume_host(mmc);
+}
+
+#else /* CONFIG_PM */
+#define s3cmci_suspend NULL
+#define s3cmci_resume NULL
+#endif /* CONFIG_PM */
+
+
+static struct platform_driver s3cmci_driver_2410 = {
+       .driver.name    = "s3c2410-sdi",
+       .driver.owner   = THIS_MODULE,
+       .probe          = s3cmci_probe_2410,
+       .remove         = __devexit_p(s3cmci_remove),
+       .suspend        = s3cmci_suspend,
+       .resume         = s3cmci_resume,
+};
+
+static struct platform_driver s3cmci_driver_2412 = {
+       .driver.name    = "s3c2412-sdi",
+       .driver.owner   = THIS_MODULE,
+       .probe          = s3cmci_probe_2412,
+       .remove         = __devexit_p(s3cmci_remove),
+       .suspend        = s3cmci_suspend,
+       .resume         = s3cmci_resume,
+};
+
+static struct platform_driver s3cmci_driver_2440 = {
+       .driver.name    = "s3c2440-sdi",
+       .driver.owner   = THIS_MODULE,
+       .probe          = s3cmci_probe_2440,
+       .remove         = __devexit_p(s3cmci_remove),
+       .suspend        = s3cmci_suspend,
+       .resume         = s3cmci_resume,
+};
+
+
+static int __init s3cmci_init(void)
+{
+       platform_driver_register(&s3cmci_driver_2410);
+       platform_driver_register(&s3cmci_driver_2412);
+       platform_driver_register(&s3cmci_driver_2440);
+       return 0;
+}
+
+static void __exit s3cmci_exit(void)
+{
+       platform_driver_unregister(&s3cmci_driver_2410);
+       platform_driver_unregister(&s3cmci_driver_2412);
+       platform_driver_unregister(&s3cmci_driver_2440);
+}
+
+module_init(s3cmci_init);
+module_exit(s3cmci_exit);
+
+MODULE_DESCRIPTION("Samsung S3C MMC/SD Card Interface driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Thomas Kleffel <tk@maintech.de>");
+MODULE_ALIAS("platform:s3c2410-sdi");
+MODULE_ALIAS("platform:s3c2412-sdi");
+MODULE_ALIAS("platform:s3c2440-sdi");
diff --git a/drivers/mmc/host/s3cmci.h b/drivers/mmc/host/s3cmci.h
new file mode 100644 (file)
index 0000000..37d9c60
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ *  linux/drivers/mmc/s3cmci.h - Samsung S3C MCI driver
+ *
+ *  Copyright (C) 2004-2006 Thomas Kleffel, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/* FIXME: DMA Resource management ?! */
+#define S3CMCI_DMA 0
+
+enum s3cmci_waitfor {
+       COMPLETION_NONE,
+       COMPLETION_FINALIZE,
+       COMPLETION_CMDSENT,
+       COMPLETION_RSPFIN,
+       COMPLETION_XFERFINISH,
+       COMPLETION_XFERFINISH_RSPFIN,
+};
+
+struct s3cmci_host {
+       struct platform_device  *pdev;
+       struct s3c24xx_mci_pdata *pdata;
+       struct mmc_host         *mmc;
+       struct resource         *mem;
+       struct clk              *clk;
+       void __iomem            *base;
+       int                     irq;
+       int                     irq_cd;
+       int                     dma;
+
+       unsigned long           clk_rate;
+       unsigned long           clk_div;
+       unsigned long           real_rate;
+       u8                      prescaler;
+
+       int                     is2440;
+       unsigned                sdiimsk;
+       unsigned                sdidata;
+       int                     dodma;
+       int                     dmatogo;
+
+       struct mmc_request      *mrq;
+       int                     cmd_is_stop;
+
+       spinlock_t              complete_lock;
+       enum s3cmci_waitfor     complete_what;
+
+       int                     dma_complete;
+
+       u32                     pio_sgptr;
+       u32                     pio_words;
+       u32                     pio_count;
+       u32                     *pio_ptr;
+#define XFER_NONE 0
+#define XFER_READ 1
+#define XFER_WRITE 2
+       u32                     pio_active;
+
+       int                     bus_width;
+
+       char                    dbgmsg_cmd[301];
+       char                    dbgmsg_dat[301];
+       char                    *status;
+
+       unsigned int            ccnt, dcnt;
+       struct tasklet_struct   pio_tasklet;
+};
diff --git a/drivers/mmc/host/sdhci-pci.c b/drivers/mmc/host/sdhci-pci.c
new file mode 100644 (file)
index 0000000..deb607c
--- /dev/null
@@ -0,0 +1,732 @@
+/*  linux/drivers/mmc/host/sdhci-pci.c - SDHCI on PCI bus interface
+ *
+ *  Copyright (C) 2005-2008 Pierre Ossman, 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.
+ *
+ * Thanks to the following companies for their support:
+ *
+ *     - JMicron (hardware and technical support)
+ */
+
+#include <linux/delay.h>
+#include <linux/highmem.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+
+#include <linux/mmc/host.h>
+
+#include <asm/scatterlist.h>
+#include <asm/io.h>
+
+#include "sdhci.h"
+
+/*
+ * PCI registers
+ */
+
+#define PCI_SDHCI_IFPIO                        0x00
+#define PCI_SDHCI_IFDMA                        0x01
+#define PCI_SDHCI_IFVENDOR             0x02
+
+#define PCI_SLOT_INFO                  0x40    /* 8 bits */
+#define  PCI_SLOT_INFO_SLOTS(x)                ((x >> 4) & 7)
+#define  PCI_SLOT_INFO_FIRST_BAR_MASK  0x07
+
+#define MAX_SLOTS                      8
+
+struct sdhci_pci_chip;
+struct sdhci_pci_slot;
+
+struct sdhci_pci_fixes {
+       unsigned int            quirks;
+
+       int                     (*probe)(struct sdhci_pci_chip*);
+
+       int                     (*probe_slot)(struct sdhci_pci_slot*);
+       void                    (*remove_slot)(struct sdhci_pci_slot*, int);
+
+       int                     (*suspend)(struct sdhci_pci_chip*,
+                                       pm_message_t);
+       int                     (*resume)(struct sdhci_pci_chip*);
+};
+
+struct sdhci_pci_slot {
+       struct sdhci_pci_chip   *chip;
+       struct sdhci_host       *host;
+
+       int                     pci_bar;
+};
+
+struct sdhci_pci_chip {
+       struct pci_dev          *pdev;
+
+       unsigned int            quirks;
+       const struct sdhci_pci_fixes *fixes;
+
+       int                     num_slots;      /* Slots on controller */
+       struct sdhci_pci_slot   *slots[MAX_SLOTS]; /* Pointers to host slots */
+};
+
+
+/*****************************************************************************\
+ *                                                                           *
+ * Hardware specific quirk handling                                          *
+ *                                                                           *
+\*****************************************************************************/
+
+static int ricoh_probe(struct sdhci_pci_chip *chip)
+{
+       if (chip->pdev->subsystem_vendor == PCI_VENDOR_ID_IBM)
+               chip->quirks |= SDHCI_QUIRK_CLOCK_BEFORE_RESET;
+
+       if (chip->pdev->subsystem_vendor == PCI_VENDOR_ID_SAMSUNG)
+               chip->quirks |= SDHCI_QUIRK_NO_CARD_NO_RESET;
+
+       return 0;
+}
+
+static const struct sdhci_pci_fixes sdhci_ricoh = {
+       .probe          = ricoh_probe,
+       .quirks         = SDHCI_QUIRK_32BIT_DMA_ADDR,
+};
+
+static const struct sdhci_pci_fixes sdhci_ene_712 = {
+       .quirks         = SDHCI_QUIRK_SINGLE_POWER_WRITE |
+                         SDHCI_QUIRK_BROKEN_DMA,
+};
+
+static const struct sdhci_pci_fixes sdhci_ene_714 = {
+       .quirks         = SDHCI_QUIRK_SINGLE_POWER_WRITE |
+                         SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS |
+                         SDHCI_QUIRK_BROKEN_DMA,
+};
+
+static const struct sdhci_pci_fixes sdhci_cafe = {
+       .quirks         = SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER |
+                         SDHCI_QUIRK_BROKEN_TIMEOUT_VAL,
+};
+
+static int jmicron_pmos(struct sdhci_pci_chip *chip, int on)
+{
+       u8 scratch;
+       int ret;
+
+       ret = pci_read_config_byte(chip->pdev, 0xAE, &scratch);
+       if (ret)
+               return ret;
+
+       /*
+        * Turn PMOS on [bit 0], set over current detection to 2.4 V
+        * [bit 1:2] and enable over current debouncing [bit 6].
+        */
+       if (on)
+               scratch |= 0x47;
+       else
+               scratch &= ~0x47;
+
+       ret = pci_write_config_byte(chip->pdev, 0xAE, scratch);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+static int jmicron_probe(struct sdhci_pci_chip *chip)
+{
+       int ret;
+
+       if (chip->pdev->revision == 0) {
+               chip->quirks |= SDHCI_QUIRK_32BIT_DMA_ADDR |
+                         SDHCI_QUIRK_32BIT_DMA_SIZE |
+                         SDHCI_QUIRK_32BIT_ADMA_SIZE |
+                         SDHCI_QUIRK_RESET_AFTER_REQUEST;
+       }
+
+       /*
+        * JMicron chips can have two interfaces to the same hardware
+        * in order to work around limitations in Microsoft's driver.
+        * We need to make sure we only bind to one of them.
+        *
+        * This code assumes two things:
+        *
+        * 1. The PCI code adds subfunctions in order.
+        *
+        * 2. The MMC interface has a lower subfunction number
+        *    than the SD interface.
+        */
+       if (chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_SD) {
+               struct pci_dev *sd_dev;
+
+               sd_dev = NULL;
+               while ((sd_dev = pci_get_device(PCI_VENDOR_ID_JMICRON,
+                       PCI_DEVICE_ID_JMICRON_JMB38X_MMC, sd_dev)) != NULL) {
+                       if ((PCI_SLOT(chip->pdev->devfn) ==
+                               PCI_SLOT(sd_dev->devfn)) &&
+                               (chip->pdev->bus == sd_dev->bus))
+                               break;
+               }
+
+               if (sd_dev) {
+                       pci_dev_put(sd_dev);
+                       dev_info(&chip->pdev->dev, "Refusing to bind to "
+                               "secondary interface.\n");
+                       return -ENODEV;
+               }
+       }
+
+       /*
+        * JMicron chips need a bit of a nudge to enable the power
+        * output pins.
+        */
+       ret = jmicron_pmos(chip, 1);
+       if (ret) {
+               dev_err(&chip->pdev->dev, "Failure enabling card power\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+static void jmicron_enable_mmc(struct sdhci_host *host, int on)
+{
+       u8 scratch;
+
+       scratch = readb(host->ioaddr + 0xC0);
+
+       if (on)
+               scratch |= 0x01;
+       else
+               scratch &= ~0x01;
+
+       writeb(scratch, host->ioaddr + 0xC0);
+}
+
+static int jmicron_probe_slot(struct sdhci_pci_slot *slot)
+{
+       if (slot->chip->pdev->revision == 0) {
+               u16 version;
+
+               version = readl(slot->host->ioaddr + SDHCI_HOST_VERSION);
+               version = (version & SDHCI_VENDOR_VER_MASK) >>
+                       SDHCI_VENDOR_VER_SHIFT;
+
+               /*
+                * Older versions of the chip have lots of nasty glitches
+                * in the ADMA engine. It's best just to avoid it
+                * completely.
+                */
+               if (version < 0xAC)
+                       slot->host->quirks |= SDHCI_QUIRK_BROKEN_ADMA;
+       }
+
+       /*
+        * The secondary interface requires a bit set to get the
+        * interrupts.
+        */
+       if (slot->chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC)
+               jmicron_enable_mmc(slot->host, 1);
+
+       return 0;
+}
+
+static void jmicron_remove_slot(struct sdhci_pci_slot *slot, int dead)
+{
+       if (dead)
+               return;
+
+       if (slot->chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC)
+               jmicron_enable_mmc(slot->host, 0);
+}
+
+static int jmicron_suspend(struct sdhci_pci_chip *chip, pm_message_t state)
+{
+       int i;
+
+       if (chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC) {
+               for (i = 0;i < chip->num_slots;i++)
+                       jmicron_enable_mmc(chip->slots[i]->host, 0);
+       }
+
+       return 0;
+}
+
+static int jmicron_resume(struct sdhci_pci_chip *chip)
+{
+       int ret, i;
+
+       if (chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC) {
+               for (i = 0;i < chip->num_slots;i++)
+                       jmicron_enable_mmc(chip->slots[i]->host, 1);
+       }
+
+       ret = jmicron_pmos(chip, 1);
+       if (ret) {
+               dev_err(&chip->pdev->dev, "Failure enabling card power\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+static const struct sdhci_pci_fixes sdhci_jmicron = {
+       .probe          = jmicron_probe,
+
+       .probe_slot     = jmicron_probe_slot,
+       .remove_slot    = jmicron_remove_slot,
+
+       .suspend        = jmicron_suspend,
+       .resume         = jmicron_resume,
+};
+
+static const struct pci_device_id pci_ids[] __devinitdata = {
+       {
+               .vendor         = PCI_VENDOR_ID_RICOH,
+               .device         = PCI_DEVICE_ID_RICOH_R5C822,
+               .subvendor      = PCI_ANY_ID,
+               .subdevice      = PCI_ANY_ID,
+               .driver_data    = (kernel_ulong_t)&sdhci_ricoh,
+       },
+
+       {
+               .vendor         = PCI_VENDOR_ID_ENE,
+               .device         = PCI_DEVICE_ID_ENE_CB712_SD,
+               .subvendor      = PCI_ANY_ID,
+               .subdevice      = PCI_ANY_ID,
+               .driver_data    = (kernel_ulong_t)&sdhci_ene_712,
+       },
+
+       {
+               .vendor         = PCI_VENDOR_ID_ENE,
+               .device         = PCI_DEVICE_ID_ENE_CB712_SD_2,
+               .subvendor      = PCI_ANY_ID,
+               .subdevice      = PCI_ANY_ID,
+               .driver_data    = (kernel_ulong_t)&sdhci_ene_712,
+       },
+
+       {
+               .vendor         = PCI_VENDOR_ID_ENE,
+               .device         = PCI_DEVICE_ID_ENE_CB714_SD,
+               .subvendor      = PCI_ANY_ID,
+               .subdevice      = PCI_ANY_ID,
+               .driver_data    = (kernel_ulong_t)&sdhci_ene_714,
+       },
+
+       {
+               .vendor         = PCI_VENDOR_ID_ENE,
+               .device         = PCI_DEVICE_ID_ENE_CB714_SD_2,
+               .subvendor      = PCI_ANY_ID,
+               .subdevice      = PCI_ANY_ID,
+               .driver_data    = (kernel_ulong_t)&sdhci_ene_714,
+       },
+
+       {
+               .vendor         = PCI_VENDOR_ID_MARVELL,
+               .device         = PCI_DEVICE_ID_MARVELL_CAFE_SD,
+               .subvendor      = PCI_ANY_ID,
+               .subdevice      = PCI_ANY_ID,
+               .driver_data    = (kernel_ulong_t)&sdhci_cafe,
+       },
+
+       {
+               .vendor         = PCI_VENDOR_ID_JMICRON,
+               .device         = PCI_DEVICE_ID_JMICRON_JMB38X_SD,
+               .subvendor      = PCI_ANY_ID,
+               .subdevice      = PCI_ANY_ID,
+               .driver_data    = (kernel_ulong_t)&sdhci_jmicron,
+       },
+
+       {
+               .vendor         = PCI_VENDOR_ID_JMICRON,
+               .device         = PCI_DEVICE_ID_JMICRON_JMB38X_MMC,
+               .subvendor      = PCI_ANY_ID,
+               .subdevice      = PCI_ANY_ID,
+               .driver_data    = (kernel_ulong_t)&sdhci_jmicron,
+       },
+
+       {       /* Generic SD host controller */
+               PCI_DEVICE_CLASS((PCI_CLASS_SYSTEM_SDHCI << 8), 0xFFFF00)
+       },
+
+       { /* end: all zeroes */ },
+};
+
+MODULE_DEVICE_TABLE(pci, pci_ids);
+
+/*****************************************************************************\
+ *                                                                           *
+ * SDHCI core callbacks                                                      *
+ *                                                                           *
+\*****************************************************************************/
+
+static int sdhci_pci_enable_dma(struct sdhci_host *host)
+{
+       struct sdhci_pci_slot *slot;
+       struct pci_dev *pdev;
+       int ret;
+
+       slot = sdhci_priv(host);
+       pdev = slot->chip->pdev;
+
+       if (((pdev->class & 0xFFFF00) == (PCI_CLASS_SYSTEM_SDHCI << 8)) &&
+               ((pdev->class & 0x0000FF) != PCI_SDHCI_IFDMA) &&
+               (host->flags & SDHCI_USE_DMA)) {
+               dev_warn(&pdev->dev, "Will use DMA mode even though HW "
+                       "doesn't fully claim to support it.\n");
+       }
+
+       ret = pci_set_dma_mask(pdev, DMA_32BIT_MASK);
+       if (ret)
+               return ret;
+
+       pci_set_master(pdev);
+
+       return 0;
+}
+
+static struct sdhci_ops sdhci_pci_ops = {
+       .enable_dma     = sdhci_pci_enable_dma,
+};
+
+/*****************************************************************************\
+ *                                                                           *
+ * Suspend/resume                                                            *
+ *                                                                           *
+\*****************************************************************************/
+
+#ifdef CONFIG_PM
+
+static int sdhci_pci_suspend (struct pci_dev *pdev, pm_message_t state)
+{
+       struct sdhci_pci_chip *chip;
+       struct sdhci_pci_slot *slot;
+       int i, ret;
+
+       chip = pci_get_drvdata(pdev);
+       if (!chip)
+               return 0;
+
+       for (i = 0;i < chip->num_slots;i++) {
+               slot = chip->slots[i];
+               if (!slot)
+                       continue;
+
+               ret = sdhci_suspend_host(slot->host, state);
+
+               if (ret) {
+                       for (i--;i >= 0;i--)
+                               sdhci_resume_host(chip->slots[i]->host);
+                       return ret;
+               }
+       }
+
+       if (chip->fixes && chip->fixes->suspend) {
+               ret = chip->fixes->suspend(chip, state);
+               if (ret) {
+                       for (i = chip->num_slots - 1;i >= 0;i--)
+                               sdhci_resume_host(chip->slots[i]->host);
+                       return ret;
+               }
+       }
+
+       pci_save_state(pdev);
+       pci_enable_wake(pdev, pci_choose_state(pdev, state), 0);
+       pci_disable_device(pdev);
+       pci_set_power_state(pdev, pci_choose_state(pdev, state));
+
+       return 0;
+}
+
+static int sdhci_pci_resume (struct pci_dev *pdev)
+{
+       struct sdhci_pci_chip *chip;
+       struct sdhci_pci_slot *slot;
+       int i, ret;
+
+       chip = pci_get_drvdata(pdev);
+       if (!chip)
+               return 0;
+
+       pci_set_power_state(pdev, PCI_D0);
+       pci_restore_state(pdev);
+       ret = pci_enable_device(pdev);
+       if (ret)
+               return ret;
+
+       if (chip->fixes && chip->fixes->resume) {
+               ret = chip->fixes->resume(chip);
+               if (ret)
+                       return ret;
+       }
+
+       for (i = 0;i < chip->num_slots;i++) {
+               slot = chip->slots[i];
+               if (!slot)
+                       continue;
+
+               ret = sdhci_resume_host(slot->host);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+#else /* CONFIG_PM */
+
+#define sdhci_pci_suspend NULL
+#define sdhci_pci_resume NULL
+
+#endif /* CONFIG_PM */
+
+/*****************************************************************************\
+ *                                                                           *
+ * Device probing/removal                                                    *
+ *                                                                           *
+\*****************************************************************************/
+
+static struct sdhci_pci_slot * __devinit sdhci_pci_probe_slot(
+       struct pci_dev *pdev, struct sdhci_pci_chip *chip, int bar)
+{
+       struct sdhci_pci_slot *slot;
+       struct sdhci_host *host;
+
+       resource_size_t addr;
+
+       int ret;
+
+       if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM)) {
+               dev_err(&pdev->dev, "BAR %d is not iomem. Aborting.\n", bar);
+               return ERR_PTR(-ENODEV);
+       }
+
+       if (pci_resource_len(pdev, bar) != 0x100) {
+               dev_err(&pdev->dev, "Invalid iomem size. You may "
+                       "experience problems.\n");
+       }
+
+       if ((pdev->class & 0x0000FF) == PCI_SDHCI_IFVENDOR) {
+               dev_err(&pdev->dev, "Vendor specific interface. Aborting.\n");
+               return ERR_PTR(-ENODEV);
+       }
+
+       if ((pdev->class & 0x0000FF) > PCI_SDHCI_IFVENDOR) {
+               dev_err(&pdev->dev, "Unknown interface. Aborting.\n");
+               return ERR_PTR(-ENODEV);
+       }
+
+       host = sdhci_alloc_host(&pdev->dev, sizeof(struct sdhci_pci_slot));
+       if (IS_ERR(host)) {
+               ret = PTR_ERR(host);
+               goto unmap;
+       }
+
+       slot = sdhci_priv(host);
+
+       slot->chip = chip;
+       slot->host = host;
+       slot->pci_bar = bar;
+
+       host->hw_name = "PCI";
+       host->ops = &sdhci_pci_ops;
+       host->quirks = chip->quirks;
+
+       host->irq = pdev->irq;
+
+       ret = pci_request_region(pdev, bar, mmc_hostname(host->mmc));
+       if (ret) {
+               dev_err(&pdev->dev, "cannot request region\n");
+               return ERR_PTR(ret);
+       }
+
+       addr = pci_resource_start(pdev, bar);
+       host->ioaddr = ioremap_nocache(addr, pci_resource_len(pdev, bar));
+       if (!host->ioaddr) {
+               dev_err(&pdev->dev, "failed to remap registers\n");
+               goto release;
+       }
+
+       if (chip->fixes && chip->fixes->probe_slot) {
+               ret = chip->fixes->probe_slot(slot);
+               if (ret)
+                       goto unmap;
+       }
+
+       ret = sdhci_add_host(host);
+       if (ret)
+               goto remove;
+
+       return slot;
+
+remove:
+       if (chip->fixes && chip->fixes->remove_slot)
+               chip->fixes->remove_slot(slot, 0);
+
+unmap:
+       iounmap(host->ioaddr);
+
+release:
+       pci_release_region(pdev, bar);
+       sdhci_free_host(host);
+
+       return ERR_PTR(ret);
+}
+
+static void sdhci_pci_remove_slot(struct sdhci_pci_slot *slot)
+{
+       int dead;
+       u32 scratch;
+
+       dead = 0;
+       scratch = readl(slot->host->ioaddr + SDHCI_INT_STATUS);
+       if (scratch == (u32)-1)
+               dead = 1;
+
+       sdhci_remove_host(slot->host, dead);
+
+       if (slot->chip->fixes && slot->chip->fixes->remove_slot)
+               slot->chip->fixes->remove_slot(slot, dead);
+
+       pci_release_region(slot->chip->pdev, slot->pci_bar);
+
+       sdhci_free_host(slot->host);
+}
+
+static int __devinit sdhci_pci_probe(struct pci_dev *pdev,
+                                    const struct pci_device_id *ent)
+{
+       struct sdhci_pci_chip *chip;
+       struct sdhci_pci_slot *slot;
+
+       u8 slots, rev, first_bar;
+       int ret, i;
+
+       BUG_ON(pdev == NULL);
+       BUG_ON(ent == NULL);
+
+       pci_read_config_byte(pdev, PCI_CLASS_REVISION, &rev);
+
+       dev_info(&pdev->dev, "SDHCI controller found [%04x:%04x] (rev %x)\n",
+                (int)pdev->vendor, (int)pdev->device, (int)rev);
+
+       ret = pci_read_config_byte(pdev, PCI_SLOT_INFO, &slots);
+       if (ret)
+               return ret;
+
+       slots = PCI_SLOT_INFO_SLOTS(slots) + 1;
+       dev_dbg(&pdev->dev, "found %d slot(s)\n", slots);
+       if (slots == 0)
+               return -ENODEV;
+
+       BUG_ON(slots > MAX_SLOTS);
+
+       ret = pci_read_config_byte(pdev, PCI_SLOT_INFO, &first_bar);
+       if (ret)
+               return ret;
+
+       first_bar &= PCI_SLOT_INFO_FIRST_BAR_MASK;
+
+       if (first_bar > 5) {
+               dev_err(&pdev->dev, "Invalid first BAR. Aborting.\n");
+               return -ENODEV;
+       }
+
+       ret = pci_enable_device(pdev);
+       if (ret)
+               return ret;
+
+       chip = kzalloc(sizeof(struct sdhci_pci_chip), GFP_KERNEL);
+       if (!chip) {
+               ret = -ENOMEM;
+               goto err;
+       }
+
+       chip->pdev = pdev;
+       chip->fixes = (const struct sdhci_pci_fixes*)ent->driver_data;
+       if (chip->fixes)
+               chip->quirks = chip->fixes->quirks;
+       chip->num_slots = slots;
+
+       pci_set_drvdata(pdev, chip);
+
+       if (chip->fixes && chip->fixes->probe) {
+               ret = chip->fixes->probe(chip);
+               if (ret)
+                       goto free;
+       }
+
+       for (i = 0;i < slots;i++) {
+               slot = sdhci_pci_probe_slot(pdev, chip, first_bar + i);
+               if (IS_ERR(slot)) {
+                       for (i--;i >= 0;i--)
+                               sdhci_pci_remove_slot(chip->slots[i]);
+                       ret = PTR_ERR(slot);
+                       goto free;
+               }
+
+               chip->slots[i] = slot;
+       }
+
+       return 0;
+
+free:
+       pci_set_drvdata(pdev, NULL);
+       kfree(chip);
+
+err:
+       pci_disable_device(pdev);
+       return ret;
+}
+
+static void __devexit sdhci_pci_remove(struct pci_dev *pdev)
+{
+       int i;
+       struct sdhci_pci_chip *chip;
+
+       chip = pci_get_drvdata(pdev);
+
+       if (chip) {
+               for (i = 0;i < chip->num_slots; i++)
+                       sdhci_pci_remove_slot(chip->slots[i]);
+
+               pci_set_drvdata(pdev, NULL);
+               kfree(chip);
+       }
+
+       pci_disable_device(pdev);
+}
+
+static struct pci_driver sdhci_driver = {
+       .name =         "sdhci-pci",
+       .id_table =     pci_ids,
+       .probe =        sdhci_pci_probe,
+       .remove =       __devexit_p(sdhci_pci_remove),
+       .suspend =      sdhci_pci_suspend,
+       .resume =       sdhci_pci_resume,
+};
+
+/*****************************************************************************\
+ *                                                                           *
+ * Driver init/exit                                                          *
+ *                                                                           *
+\*****************************************************************************/
+
+static int __init sdhci_drv_init(void)
+{
+       return pci_register_driver(&sdhci_driver);
+}
+
+static void __exit sdhci_drv_exit(void)
+{
+       pci_unregister_driver(&sdhci_driver);
+}
+
+module_init(sdhci_drv_init);
+module_exit(sdhci_drv_exit);
+
+MODULE_AUTHOR("Pierre Ossman <drzeus@drzeus.cx>");
+MODULE_DESCRIPTION("Secure Digital Host Controller Interface PCI driver");
+MODULE_LICENSE("GPL");
index b413aa6c246b938649f47e63a2af5482f76fdc11..17701c3da73326b9fa796086a617b253c506b1e5 100644 (file)
@@ -15,7 +15,7 @@
 
 #include <linux/delay.h>
 #include <linux/highmem.h>
-#include <linux/pci.h>
+#include <linux/io.h>
 #include <linux/dma-mapping.h>
 #include <linux/scatterlist.h>
 
 
 static unsigned int debug_quirks = 0;
 
-/*
- * Different quirks to handle when the hardware deviates from a strict
- * interpretation of the SDHCI specification.
- */
-
-/* Controller doesn't honor resets unless we touch the clock register */
-#define SDHCI_QUIRK_CLOCK_BEFORE_RESET                 (1<<0)
-/* Controller has bad caps bits, but really supports DMA */
-#define SDHCI_QUIRK_FORCE_DMA                          (1<<1)
-/* Controller doesn't like to be reset when there is no card inserted. */
-#define SDHCI_QUIRK_NO_CARD_NO_RESET                   (1<<2)
-/* Controller doesn't like clearing the power reg before a change */
-#define SDHCI_QUIRK_SINGLE_POWER_WRITE                 (1<<3)
-/* Controller has flaky internal state so reset it on each ios change */
-#define SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS              (1<<4)
-/* Controller has an unusable DMA engine */
-#define SDHCI_QUIRK_BROKEN_DMA                         (1<<5)
-/* Controller can only DMA from 32-bit aligned addresses */
-#define SDHCI_QUIRK_32BIT_DMA_ADDR                     (1<<6)
-/* Controller can only DMA chunk sizes that are a multiple of 32 bits */
-#define SDHCI_QUIRK_32BIT_DMA_SIZE                     (1<<7)
-/* Controller needs to be reset after each request to stay stable */
-#define SDHCI_QUIRK_RESET_AFTER_REQUEST                        (1<<8)
-/* Controller needs voltage and power writes to happen separately */
-#define SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER            (1<<9)
-/* Controller has an off-by-one issue with timeout value */
-#define SDHCI_QUIRK_INCR_TIMEOUT_CONTROL               (1<<10)
-
-static const struct pci_device_id pci_ids[] __devinitdata = {
-       {
-               .vendor         = PCI_VENDOR_ID_RICOH,
-               .device         = PCI_DEVICE_ID_RICOH_R5C822,
-               .subvendor      = PCI_VENDOR_ID_IBM,
-               .subdevice      = PCI_ANY_ID,
-               .driver_data    = SDHCI_QUIRK_CLOCK_BEFORE_RESET |
-                                 SDHCI_QUIRK_FORCE_DMA,
-       },
-
-       {
-               .vendor         = PCI_VENDOR_ID_RICOH,
-               .device         = PCI_DEVICE_ID_RICOH_R5C822,
-               .subvendor      = PCI_VENDOR_ID_SAMSUNG,
-               .subdevice      = PCI_ANY_ID,
-               .driver_data    = SDHCI_QUIRK_FORCE_DMA |
-                                 SDHCI_QUIRK_NO_CARD_NO_RESET,
-       },
-
-       {
-               .vendor         = PCI_VENDOR_ID_RICOH,
-               .device         = PCI_DEVICE_ID_RICOH_R5C822,
-               .subvendor      = PCI_ANY_ID,
-               .subdevice      = PCI_ANY_ID,
-               .driver_data    = SDHCI_QUIRK_FORCE_DMA,
-       },
-
-       {
-               .vendor         = PCI_VENDOR_ID_TI,
-               .device         = PCI_DEVICE_ID_TI_XX21_XX11_SD,
-               .subvendor      = PCI_ANY_ID,
-               .subdevice      = PCI_ANY_ID,
-               .driver_data    = SDHCI_QUIRK_FORCE_DMA,
-       },
-
-       {
-               .vendor         = PCI_VENDOR_ID_ENE,
-               .device         = PCI_DEVICE_ID_ENE_CB712_SD,
-               .subvendor      = PCI_ANY_ID,
-               .subdevice      = PCI_ANY_ID,
-               .driver_data    = SDHCI_QUIRK_SINGLE_POWER_WRITE |
-                                 SDHCI_QUIRK_BROKEN_DMA,
-       },
-
-       {
-               .vendor         = PCI_VENDOR_ID_ENE,
-               .device         = PCI_DEVICE_ID_ENE_CB712_SD_2,
-               .subvendor      = PCI_ANY_ID,
-               .subdevice      = PCI_ANY_ID,
-               .driver_data    = SDHCI_QUIRK_SINGLE_POWER_WRITE |
-                                 SDHCI_QUIRK_BROKEN_DMA,
-       },
-
-       {
-               .vendor         = PCI_VENDOR_ID_ENE,
-               .device         = PCI_DEVICE_ID_ENE_CB714_SD,
-               .subvendor      = PCI_ANY_ID,
-               .subdevice      = PCI_ANY_ID,
-               .driver_data    = SDHCI_QUIRK_SINGLE_POWER_WRITE |
-                                 SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS |
-                                 SDHCI_QUIRK_BROKEN_DMA,
-       },
-
-       {
-               .vendor         = PCI_VENDOR_ID_ENE,
-               .device         = PCI_DEVICE_ID_ENE_CB714_SD_2,
-               .subvendor      = PCI_ANY_ID,
-               .subdevice      = PCI_ANY_ID,
-               .driver_data    = SDHCI_QUIRK_SINGLE_POWER_WRITE |
-                                 SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS |
-                                 SDHCI_QUIRK_BROKEN_DMA,
-       },
-
-       {
-               .vendor         = PCI_VENDOR_ID_MARVELL,
-               .device         = PCI_DEVICE_ID_MARVELL_CAFE_SD,
-               .subvendor      = PCI_ANY_ID,
-               .subdevice      = PCI_ANY_ID,
-               .driver_data    = SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER |
-                                 SDHCI_QUIRK_INCR_TIMEOUT_CONTROL,
-       },
-
-       {
-               .vendor         = PCI_VENDOR_ID_JMICRON,
-               .device         = PCI_DEVICE_ID_JMICRON_JMB38X_SD,
-               .subvendor      = PCI_ANY_ID,
-               .subdevice      = PCI_ANY_ID,
-               .driver_data    = SDHCI_QUIRK_32BIT_DMA_ADDR |
-                                 SDHCI_QUIRK_32BIT_DMA_SIZE |
-                                 SDHCI_QUIRK_RESET_AFTER_REQUEST,
-       },
-
-       {       /* Generic SD host controller */
-               PCI_DEVICE_CLASS((PCI_CLASS_SYSTEM_SDHCI << 8), 0xFFFF00)
-       },
-
-       { /* end: all zeroes */ },
-};
-
-MODULE_DEVICE_TABLE(pci, pci_ids);
-
 static void sdhci_prepare_data(struct sdhci_host *, struct mmc_data *);
 static void sdhci_finish_data(struct sdhci_host *);
 
@@ -215,7 +86,7 @@ static void sdhci_reset(struct sdhci_host *host, u8 mask)
 {
        unsigned long timeout;
 
-       if (host->chip->quirks & SDHCI_QUIRK_NO_CARD_NO_RESET) {
+       if (host->quirks & SDHCI_QUIRK_NO_CARD_NO_RESET) {
                if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) &
                        SDHCI_CARD_PRESENT))
                        return;
@@ -253,7 +124,8 @@ static void sdhci_init(struct sdhci_host *host)
                SDHCI_INT_END_BIT | SDHCI_INT_CRC | SDHCI_INT_TIMEOUT |
                SDHCI_INT_CARD_REMOVE | SDHCI_INT_CARD_INSERT |
                SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL |
-               SDHCI_INT_DMA_END | SDHCI_INT_DATA_END | SDHCI_INT_RESPONSE;
+               SDHCI_INT_DMA_END | SDHCI_INT_DATA_END | SDHCI_INT_RESPONSE |
+               SDHCI_INT_ADMA_ERROR;
 
        writel(intmask, host->ioaddr + SDHCI_INT_ENABLE);
        writel(intmask, host->ioaddr + SDHCI_SIGNAL_ENABLE);
@@ -443,23 +315,226 @@ static void sdhci_transfer_pio(struct sdhci_host *host)
        DBG("PIO transfer complete.\n");
 }
 
-static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_data *data)
+static char *sdhci_kmap_atomic(struct scatterlist *sg, unsigned long *flags)
 {
-       u8 count;
-       unsigned target_timeout, current_timeout;
+       local_irq_save(*flags);
+       return kmap_atomic(sg_page(sg), KM_BIO_SRC_IRQ) + sg->offset;
+}
 
-       WARN_ON(host->data);
+static void sdhci_kunmap_atomic(void *buffer, unsigned long *flags)
+{
+       kunmap_atomic(buffer, KM_BIO_SRC_IRQ);
+       local_irq_restore(*flags);
+}
 
-       if (data == NULL)
-               return;
+static int sdhci_adma_table_pre(struct sdhci_host *host,
+       struct mmc_data *data)
+{
+       int direction;
 
-       /* Sanity checks */
-       BUG_ON(data->blksz * data->blocks > 524288);
-       BUG_ON(data->blksz > host->mmc->max_blk_size);
-       BUG_ON(data->blocks > 65535);
+       u8 *desc;
+       u8 *align;
+       dma_addr_t addr;
+       dma_addr_t align_addr;
+       int len, offset;
 
-       host->data = data;
-       host->data_early = 0;
+       struct scatterlist *sg;
+       int i;
+       char *buffer;
+       unsigned long flags;
+
+       /*
+        * The spec does not specify endianness of descriptor table.
+        * We currently guess that it is LE.
+        */
+
+       if (data->flags & MMC_DATA_READ)
+               direction = DMA_FROM_DEVICE;
+       else
+               direction = DMA_TO_DEVICE;
+
+       /*
+        * The ADMA descriptor table is mapped further down as we
+        * need to fill it with data first.
+        */
+
+       host->align_addr = dma_map_single(mmc_dev(host->mmc),
+               host->align_buffer, 128 * 4, direction);
+       if (dma_mapping_error(host->align_addr))
+               goto fail;
+       BUG_ON(host->align_addr & 0x3);
+
+       host->sg_count = dma_map_sg(mmc_dev(host->mmc),
+               data->sg, data->sg_len, direction);
+       if (host->sg_count == 0)
+               goto unmap_align;
+
+       desc = host->adma_desc;
+       align = host->align_buffer;
+
+       align_addr = host->align_addr;
+
+       for_each_sg(data->sg, sg, host->sg_count, i) {
+               addr = sg_dma_address(sg);
+               len = sg_dma_len(sg);
+
+               /*
+                * The SDHCI specification states that ADMA
+                * addresses must be 32-bit aligned. If they
+                * aren't, then we use a bounce buffer for
+                * the (up to three) bytes that screw up the
+                * alignment.
+                */
+               offset = (4 - (addr & 0x3)) & 0x3;
+               if (offset) {
+                       if (data->flags & MMC_DATA_WRITE) {
+                               buffer = sdhci_kmap_atomic(sg, &flags);
+                               memcpy(align, buffer, offset);
+                               sdhci_kunmap_atomic(buffer, &flags);
+                       }
+
+                       desc[7] = (align_addr >> 24) & 0xff;
+                       desc[6] = (align_addr >> 16) & 0xff;
+                       desc[5] = (align_addr >> 8) & 0xff;
+                       desc[4] = (align_addr >> 0) & 0xff;
+
+                       BUG_ON(offset > 65536);
+
+                       desc[3] = (offset >> 8) & 0xff;
+                       desc[2] = (offset >> 0) & 0xff;
+
+                       desc[1] = 0x00;
+                       desc[0] = 0x21; /* tran, valid */
+
+                       align += 4;
+                       align_addr += 4;
+
+                       desc += 8;
+
+                       addr += offset;
+                       len -= offset;
+               }
+
+               desc[7] = (addr >> 24) & 0xff;
+               desc[6] = (addr >> 16) & 0xff;
+               desc[5] = (addr >> 8) & 0xff;
+               desc[4] = (addr >> 0) & 0xff;
+
+               BUG_ON(len > 65536);
+
+               desc[3] = (len >> 8) & 0xff;
+               desc[2] = (len >> 0) & 0xff;
+
+               desc[1] = 0x00;
+               desc[0] = 0x21; /* tran, valid */
+
+               desc += 8;
+
+               /*
+                * If this triggers then we have a calculation bug
+                * somewhere. :/
+                */
+               WARN_ON((desc - host->adma_desc) > (128 * 2 + 1) * 4);
+       }
+
+       /*
+        * Add a terminating entry.
+        */
+       desc[7] = 0;
+       desc[6] = 0;
+       desc[5] = 0;
+       desc[4] = 0;
+
+       desc[3] = 0;
+       desc[2] = 0;
+
+       desc[1] = 0x00;
+       desc[0] = 0x03; /* nop, end, valid */
+
+       /*
+        * Resync align buffer as we might have changed it.
+        */
+       if (data->flags & MMC_DATA_WRITE) {
+               dma_sync_single_for_device(mmc_dev(host->mmc),
+                       host->align_addr, 128 * 4, direction);
+       }
+
+       host->adma_addr = dma_map_single(mmc_dev(host->mmc),
+               host->adma_desc, (128 * 2 + 1) * 4, DMA_TO_DEVICE);
+       if (dma_mapping_error(host->align_addr))
+               goto unmap_entries;
+       BUG_ON(host->adma_addr & 0x3);
+
+       return 0;
+
+unmap_entries:
+       dma_unmap_sg(mmc_dev(host->mmc), data->sg,
+               data->sg_len, direction);
+unmap_align:
+       dma_unmap_single(mmc_dev(host->mmc), host->align_addr,
+               128 * 4, direction);
+fail:
+       return -EINVAL;
+}
+
+static void sdhci_adma_table_post(struct sdhci_host *host,
+       struct mmc_data *data)
+{
+       int direction;
+
+       struct scatterlist *sg;
+       int i, size;
+       u8 *align;
+       char *buffer;
+       unsigned long flags;
+
+       if (data->flags & MMC_DATA_READ)
+               direction = DMA_FROM_DEVICE;
+       else
+               direction = DMA_TO_DEVICE;
+
+       dma_unmap_single(mmc_dev(host->mmc), host->adma_addr,
+               (128 * 2 + 1) * 4, DMA_TO_DEVICE);
+
+       dma_unmap_single(mmc_dev(host->mmc), host->align_addr,
+               128 * 4, direction);
+
+       if (data->flags & MMC_DATA_READ) {
+               dma_sync_sg_for_cpu(mmc_dev(host->mmc), data->sg,
+                       data->sg_len, direction);
+
+               align = host->align_buffer;
+
+               for_each_sg(data->sg, sg, host->sg_count, i) {
+                       if (sg_dma_address(sg) & 0x3) {
+                               size = 4 - (sg_dma_address(sg) & 0x3);
+
+                               buffer = sdhci_kmap_atomic(sg, &flags);
+                               memcpy(buffer, align, size);
+                               sdhci_kunmap_atomic(buffer, &flags);
+
+                               align += 4;
+                       }
+               }
+       }
+
+       dma_unmap_sg(mmc_dev(host->mmc), data->sg,
+               data->sg_len, direction);
+}
+
+static u8 sdhci_calc_timeout(struct sdhci_host *host, struct mmc_data *data)
+{
+       u8 count;
+       unsigned target_timeout, current_timeout;
+
+       /*
+        * If the host controller provides us with an incorrect timeout
+        * value, just skip the check and use 0xE.  The hardware may take
+        * longer to time out, but that's much better than having a too-short
+        * timeout value.
+        */
+       if ((host->quirks & SDHCI_QUIRK_BROKEN_TIMEOUT_VAL))
+               return 0xE;
 
        /* timeout in us */
        target_timeout = data->timeout_ns / 1000 +
@@ -484,52 +559,158 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_data *data)
                        break;
        }
 
-       /*
-        * Compensate for an off-by-one error in the CaFe hardware; otherwise,
-        * a too-small count gives us interrupt timeouts.
-        */
-       if ((host->chip->quirks & SDHCI_QUIRK_INCR_TIMEOUT_CONTROL))
-               count++;
-
        if (count >= 0xF) {
                printk(KERN_WARNING "%s: Too large timeout requested!\n",
                        mmc_hostname(host->mmc));
                count = 0xE;
        }
 
+       return count;
+}
+
+static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_data *data)
+{
+       u8 count;
+       u8 ctrl;
+       int ret;
+
+       WARN_ON(host->data);
+
+       if (data == NULL)
+               return;
+
+       /* Sanity checks */
+       BUG_ON(data->blksz * data->blocks > 524288);
+       BUG_ON(data->blksz > host->mmc->max_blk_size);
+       BUG_ON(data->blocks > 65535);
+
+       host->data = data;
+       host->data_early = 0;
+
+       count = sdhci_calc_timeout(host, data);
        writeb(count, host->ioaddr + SDHCI_TIMEOUT_CONTROL);
 
        if (host->flags & SDHCI_USE_DMA)
                host->flags |= SDHCI_REQ_USE_DMA;
 
-       if (unlikely((host->flags & SDHCI_REQ_USE_DMA) &&
-               (host->chip->quirks & SDHCI_QUIRK_32BIT_DMA_SIZE) &&
-               ((data->blksz * data->blocks) & 0x3))) {
-               DBG("Reverting to PIO because of transfer size (%d)\n",
-                       data->blksz * data->blocks);
-               host->flags &= ~SDHCI_REQ_USE_DMA;
+       /*
+        * FIXME: This doesn't account for merging when mapping the
+        * scatterlist.
+        */
+       if (host->flags & SDHCI_REQ_USE_DMA) {
+               int broken, i;
+               struct scatterlist *sg;
+
+               broken = 0;
+               if (host->flags & SDHCI_USE_ADMA) {
+                       if (host->quirks & SDHCI_QUIRK_32BIT_ADMA_SIZE)
+                               broken = 1;
+               } else {
+                       if (host->quirks & SDHCI_QUIRK_32BIT_DMA_SIZE)
+                               broken = 1;
+               }
+
+               if (unlikely(broken)) {
+                       for_each_sg(data->sg, sg, data->sg_len, i) {
+                               if (sg->length & 0x3) {
+                                       DBG("Reverting to PIO because of "
+                                               "transfer size (%d)\n",
+                                               sg->length);
+                                       host->flags &= ~SDHCI_REQ_USE_DMA;
+                                       break;
+                               }
+                       }
+               }
        }
 
        /*
         * The assumption here being that alignment is the same after
         * translation to device address space.
         */
-       if (unlikely((host->flags & SDHCI_REQ_USE_DMA) &&
-               (host->chip->quirks & SDHCI_QUIRK_32BIT_DMA_ADDR) &&
-               (data->sg->offset & 0x3))) {
-               DBG("Reverting to PIO because of bad alignment\n");
-               host->flags &= ~SDHCI_REQ_USE_DMA;
+       if (host->flags & SDHCI_REQ_USE_DMA) {
+               int broken, i;
+               struct scatterlist *sg;
+
+               broken = 0;
+               if (host->flags & SDHCI_USE_ADMA) {
+                       /*
+                        * As we use 3 byte chunks to work around
+                        * alignment problems, we need to check this
+                        * quirk.
+                        */
+                       if (host->quirks & SDHCI_QUIRK_32BIT_ADMA_SIZE)
+                               broken = 1;
+               } else {
+                       if (host->quirks & SDHCI_QUIRK_32BIT_DMA_ADDR)
+             &n