aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/mmc
diff options
context:
space:
mode:
authorroot <root@artemis.panaceas.org>2015-12-25 04:40:36 +0000
committerroot <root@artemis.panaceas.org>2015-12-25 04:40:36 +0000
commit849369d6c66d3054688672f97d31fceb8e8230fb (patch)
tree6135abc790ca67dedbe07c39806591e70eda81ce /drivers/mmc
downloadlinux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.tar.gz
linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.tar.bz2
linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.zip
initial_commit
Diffstat (limited to 'drivers/mmc')
-rw-r--r--drivers/mmc/Kconfig30
-rw-r--r--drivers/mmc/Makefile10
-rw-r--r--drivers/mmc/card/Kconfig86
-rw-r--r--drivers/mmc/card/Makefile11
-rw-r--r--drivers/mmc/card/block.c1662
-rw-r--r--drivers/mmc/card/mmc_test.c3019
-rw-r--r--drivers/mmc/card/queue.c433
-rw-r--r--drivers/mmc/card/queue.h49
-rw-r--r--drivers/mmc/card/sdio_uart.c1223
-rwxr-xr-xdrivers/mmc/card/sdio_wifi_pwr.c21
-rw-r--r--drivers/mmc/core/Kconfig46
-rw-r--r--drivers/mmc/core/Makefile12
-rw-r--r--drivers/mmc/core/bus.c340
-rw-r--r--drivers/mmc/core/bus.h31
-rw-r--r--drivers/mmc/core/core.c2214
-rw-r--r--drivers/mmc/core/core.h79
-rw-r--r--drivers/mmc/core/debugfs.c330
-rw-r--r--drivers/mmc/core/host.c393
-rw-r--r--drivers/mmc/core/host.h41
-rw-r--r--drivers/mmc/core/mmc.c1461
-rw-r--r--drivers/mmc/core/mmc_ops.c550
-rw-r--r--drivers/mmc/core/mmc_ops.h31
-rw-r--r--drivers/mmc/core/quirks.c79
-rw-r--r--drivers/mmc/core/sd.c1269
-rw-r--r--drivers/mmc/core/sd.h17
-rw-r--r--drivers/mmc/core/sd_ops.c391
-rw-r--r--drivers/mmc/core/sd_ops.h25
-rw-r--r--drivers/mmc/core/sdio.c1000
-rw-r--r--drivers/mmc/core/sdio_bus.c334
-rw-r--r--drivers/mmc/core/sdio_bus.h22
-rw-r--r--drivers/mmc/core/sdio_cis.c412
-rw-r--r--drivers/mmc/core/sdio_cis.h23
-rwxr-xr-xdrivers/mmc/core/sdio_io.c721
-rw-r--r--drivers/mmc/core/sdio_irq.c307
-rw-r--r--drivers/mmc/core/sdio_ops.c200
-rw-r--r--drivers/mmc/core/sdio_ops.h23
-rw-r--r--drivers/mmc/host/Kconfig587
-rw-r--r--drivers/mmc/host/Makefile61
-rw-r--r--drivers/mmc/host/at91_mci.c1215
-rw-r--r--drivers/mmc/host/atmel-mci-regs.h128
-rw-r--r--drivers/mmc/host/atmel-mci.c1910
-rw-r--r--drivers/mmc/host/au1xmmc.c1211
-rw-r--r--drivers/mmc/host/bfin_sdh.c644
-rw-r--r--drivers/mmc/host/cb710-mmc.c799
-rw-r--r--drivers/mmc/host/cb710-mmc.h104
-rw-r--r--drivers/mmc/host/davinci_mmc.c1479
-rw-r--r--drivers/mmc/host/dw_mmc.c1859
-rw-r--r--drivers/mmc/host/dw_mmc.h168
-rw-r--r--drivers/mmc/host/imxmmc.c1169
-rw-r--r--drivers/mmc/host/imxmmc.h64
-rw-r--r--drivers/mmc/host/jz4740_mmc.c1029
-rw-r--r--drivers/mmc/host/mmc_spi.c1554
-rw-r--r--drivers/mmc/host/mmci.c1504
-rw-r--r--drivers/mmc/host/mmci.h220
-rw-r--r--drivers/mmc/host/msm_sdcc.c1467
-rw-r--r--drivers/mmc/host/msm_sdcc.h252
-rw-r--r--drivers/mmc/host/mvsdio.c918
-rw-r--r--drivers/mmc/host/mvsdio.h190
-rw-r--r--drivers/mmc/host/mxcmmc.c1065
-rw-r--r--drivers/mmc/host/mxs-mmc.c872
-rw-r--r--drivers/mmc/host/of_mmc_spi.c181
-rw-r--r--drivers/mmc/host/omap.c1637
-rw-r--r--drivers/mmc/host/omap_hsmmc.c2551
-rw-r--r--drivers/mmc/host/pxamci.c890
-rw-r--r--drivers/mmc/host/pxamci.h90
-rw-r--r--drivers/mmc/host/s3cmci.c1932
-rw-r--r--drivers/mmc/host/s3cmci.h82
-rw-r--r--drivers/mmc/host/sdhci-cns3xxx.c97
-rw-r--r--drivers/mmc/host/sdhci-dove.c70
-rw-r--r--drivers/mmc/host/sdhci-esdhc-imx.c988
-rw-r--r--drivers/mmc/host/sdhci-esdhc.h107
-rw-r--r--drivers/mmc/host/sdhci-of-core.c253
-rw-r--r--drivers/mmc/host/sdhci-of-esdhc.c91
-rw-r--r--drivers/mmc/host/sdhci-of-hlwd.c65
-rw-r--r--drivers/mmc/host/sdhci-of.h42
-rw-r--r--drivers/mmc/host/sdhci-pci.c1151
-rw-r--r--drivers/mmc/host/sdhci-pltfm.c241
-rw-r--r--drivers/mmc/host/sdhci-pltfm.h28
-rw-r--r--drivers/mmc/host/sdhci-pxa.c303
-rw-r--r--drivers/mmc/host/sdhci-s3c.c661
-rw-r--r--drivers/mmc/host/sdhci-spear.c298
-rw-r--r--drivers/mmc/host/sdhci-tegra.c263
-rwxr-xr-xdrivers/mmc/host/sdhci.c3047
-rw-r--r--drivers/mmc/host/sdhci.h386
-rw-r--r--drivers/mmc/host/sdricoh_cs.c572
-rw-r--r--drivers/mmc/host/sh_mmcif.c1204
-rw-r--r--drivers/mmc/host/sh_mobile_sdhi.c216
-rw-r--r--drivers/mmc/host/tifm_sd.c1094
-rw-r--r--drivers/mmc/host/tmio_mmc.c160
-rw-r--r--drivers/mmc/host/tmio_mmc.h137
-rw-r--r--drivers/mmc/host/tmio_mmc_dma.c321
-rw-r--r--drivers/mmc/host/tmio_mmc_pio.c1037
-rw-r--r--drivers/mmc/host/ushc.c579
-rw-r--r--drivers/mmc/host/via-sdmmc.c1357
-rw-r--r--drivers/mmc/host/vub300.c2503
-rw-r--r--drivers/mmc/host/wbsd.c2047
-rw-r--r--drivers/mmc/host/wbsd.h185
97 files changed, 64230 insertions, 0 deletions
diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig
new file mode 100644
index 00000000..f2eeb38e
--- /dev/null
+++ b/drivers/mmc/Kconfig
@@ -0,0 +1,30 @@
+#
+# MMC subsystem configuration
+#
+
+menuconfig MMC
+ tristate "MMC/SD/SDIO card support"
+ depends on HAS_IOMEM
+ help
+ This selects MultiMediaCard, Secure Digital and Secure
+ Digital I/O support.
+
+ If you want MMC/SD/SDIO support, you should say Y here and
+ also to your specific host controller driver.
+
+config MMC_DEBUG
+ bool "MMC debugging"
+ depends on MMC != n
+ help
+ This is an option for use by developers; most people should
+ say N here. This enables MMC core and driver debugging.
+
+if MMC
+
+source "drivers/mmc/core/Kconfig"
+
+source "drivers/mmc/card/Kconfig"
+
+source "drivers/mmc/host/Kconfig"
+
+endif # MMC
diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile
new file mode 100644
index 00000000..12eef393
--- /dev/null
+++ b/drivers/mmc/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for the kernel mmc device drivers.
+#
+
+subdir-ccflags-$(CONFIG_MMC_DEBUG) := -DDEBUG
+
+obj-$(CONFIG_MMC) += core/
+obj-$(CONFIG_MMC) += card/
+obj-$(CONFIG_MMC) += host/
+
diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig
new file mode 100644
index 00000000..c3be4964
--- /dev/null
+++ b/drivers/mmc/card/Kconfig
@@ -0,0 +1,86 @@
+#
+# MMC/SD card drivers
+#
+
+comment "MMC/SD/SDIO Card Drivers"
+
+config MMC_BLOCK
+ tristate "MMC block device driver"
+ depends on BLOCK
+ default y
+ help
+ Say Y here to enable the MMC block device driver support.
+ This provides a block device driver, which you can use to
+ mount the filesystem. Almost everyone wishing MMC support
+ should say Y or M here.
+
+config MMC_BLOCK_MINORS
+ int "Number of minors per block device"
+ depends on MMC_BLOCK
+ range 4 256
+ default 8
+ help
+ Number of minors per block device. One is needed for every
+ partition on the disk (plus one for the whole disk).
+
+ Number of total MMC minors available is 256, so your number
+ of supported block devices will be limited to 256 divided
+ by this number.
+
+ Default is 8 to be backwards compatible with previous
+ hardwired device numbering.
+
+ If unsure, say 8 here.
+
+config MMC_BLOCK_BOUNCE
+ bool "Use bounce buffer for simple hosts"
+ depends on MMC_BLOCK
+ default y
+ help
+ SD/MMC is a high latency protocol where it is crucial to
+ send large requests in order to get high performance. Many
+ controllers, however, are restricted to continuous memory
+ (i.e. they can't do scatter-gather), something the kernel
+ rarely can provide.
+
+ Say Y here to help these restricted hosts by bouncing
+ requests back and forth from a large buffer. You will get
+ a big performance gain at the cost of up to 64 KiB of
+ physical memory.
+
+ If unsure, say Y here.
+
+config MMC_BLOCK_DEFERRED_RESUME
+ bool "Deferr MMC layer resume until I/O is requested"
+ depends on MMC_BLOCK
+ default n
+ help
+ Say Y here to enable deferred MMC resume until I/O
+ is requested. This will reduce overall resume latency and
+ save power when theres an SD card inserted but not being used.
+
+config SDIO_UART
+ tristate "SDIO UART/GPS class support"
+ help
+ SDIO function driver for SDIO cards that implements the UART
+ class, as well as the GPS class which appears like a UART.
+
+config MMC_TEST
+ tristate "MMC host test driver"
+ help
+ Development driver that performs a series of reads and writes
+ to a memory card in order to expose certain well known bugs
+ in host controllers. The tests are executed by writing to the
+ "test" file in debugfs under each card. Note that whatever is
+ on your card will be overwritten by these tests.
+
+ This driver is only of interest to those developing or
+ testing a host driver. Most people should say N here.
+config SDIO_WIFI_PWR
+ tristate "Netronix wifi power control"
+ default m
+ help
+ This provides an interface to control sdio wifi power,
+ say M here.
+
+ If unsure, say N.
diff --git a/drivers/mmc/card/Makefile b/drivers/mmc/card/Makefile
new file mode 100644
index 00000000..d1b9cb18
--- /dev/null
+++ b/drivers/mmc/card/Makefile
@@ -0,0 +1,11 @@
+#
+# Makefile for MMC/SD card drivers
+#
+
+obj-$(CONFIG_MMC_BLOCK) += mmc_block.o
+mmc_block-objs := block.o queue.o
+obj-$(CONFIG_MMC_TEST) += mmc_test.o
+
+obj-$(CONFIG_SDIO_UART) += sdio_uart.o
+
+obj-$(CONFIG_SDIO_WIFI_PWR) += sdio_wifi_pwr.o
diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c
new file mode 100644
index 00000000..cf0e48c0
--- /dev/null
+++ b/drivers/mmc/card/block.c
@@ -0,0 +1,1662 @@
+/*
+ * Block driver for media (i.e., flash cards)
+ *
+ * Copyright 2002 Hewlett-Packard Company
+ * Copyright 2005-2008 Pierre Ossman
+ *
+ * Use consistent with the GNU GPL is permitted,
+ * provided that this copyright notice is
+ * preserved in its entirety in all copies and derived works.
+ *
+ * HEWLETT-PACKARD COMPANY MAKES NO WARRANTIES, EXPRESSED OR IMPLIED,
+ * AS TO THE USEFULNESS OR CORRECTNESS OF THIS CODE OR ITS
+ * FITNESS FOR ANY PARTICULAR PURPOSE.
+ *
+ * Many thanks to Alessandro Rubini and Jonathan Corbet!
+ *
+ * Author: Andrew Christian
+ * 28 May 2002
+ */
+#include <linux/moduleparam.h>
+#include <linux/module.h>
+#include <linux/init.h>
+
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/hdreg.h>
+#include <linux/kdev_t.h>
+#include <linux/blkdev.h>
+#include <linux/mutex.h>
+#include <linux/scatterlist.h>
+#include <linux/string_helpers.h>
+#include <linux/delay.h>
+#include <linux/capability.h>
+#include <linux/compat.h>
+
+#include <linux/mmc/ioctl.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/sd.h>
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+
+#include "queue.h"
+
+MODULE_ALIAS("mmc:block");
+#ifdef MODULE_PARAM_PREFIX
+#undef MODULE_PARAM_PREFIX
+#endif
+#define MODULE_PARAM_PREFIX "mmcblk."
+
+#define INAND_CMD38_ARG_EXT_CSD 113
+#define INAND_CMD38_ARG_ERASE 0x00
+#define INAND_CMD38_ARG_TRIM 0x01
+#define INAND_CMD38_ARG_SECERASE 0x80
+#define INAND_CMD38_ARG_SECTRIM1 0x81
+#define INAND_CMD38_ARG_SECTRIM2 0x88
+
+static DEFINE_MUTEX(block_mutex);
+
+/*
+ * The defaults come from config options but can be overriden by module
+ * or bootarg options.
+ */
+static int perdev_minors = CONFIG_MMC_BLOCK_MINORS;
+
+/*
+ * We've only got one major, so number of mmcblk devices is
+ * limited to 256 / number of minors per device.
+ */
+static int max_devices;
+
+/* 256 minors, so at most 256 separate devices */
+static DECLARE_BITMAP(dev_use, 256);
+static DECLARE_BITMAP(name_use, 256);
+
+/*
+ * There is one mmc_blk_data per slot.
+ */
+struct mmc_blk_data {
+ spinlock_t lock;
+ struct gendisk *disk;
+ struct mmc_queue queue;
+ struct list_head part;
+
+ unsigned int flags;
+#define MMC_BLK_CMD23 (1 << 0) /* Can do SET_BLOCK_COUNT for multiblock */
+#define MMC_BLK_REL_WR (1 << 1) /* MMC Reliable write support */
+
+ unsigned int usage;
+ unsigned int read_only;
+ unsigned int part_type;
+ unsigned int name_idx;
+
+ /*
+ * Only set in main mmc_blk_data associated
+ * with mmc_card with mmc_set_drvdata, and keeps
+ * track of the current selected device partition.
+ */
+ unsigned int part_curr;
+ struct device_attribute force_ro;
+};
+
+static DEFINE_MUTEX(open_lock);
+
+enum mmc_blk_status {
+ MMC_BLK_SUCCESS = 0,
+ MMC_BLK_PARTIAL,
+ MMC_BLK_RETRY,
+ MMC_BLK_RETRY_SINGLE,
+ MMC_BLK_DATA_ERR,
+ MMC_BLK_CMD_ERR,
+ MMC_BLK_ABORT,
+};
+
+module_param(perdev_minors, int, 0444);
+MODULE_PARM_DESC(perdev_minors, "Minors numbers to allocate per device");
+
+static struct mmc_blk_data *mmc_blk_get(struct gendisk *disk)
+{
+ struct mmc_blk_data *md;
+
+ mutex_lock(&open_lock);
+ md = disk->private_data;
+ if (md && md->usage == 0)
+ md = NULL;
+ if (md)
+ md->usage++;
+ mutex_unlock(&open_lock);
+
+ return md;
+}
+
+static inline int mmc_get_devidx(struct gendisk *disk)
+{
+ int devidx = disk->first_minor / perdev_minors;
+ return devidx;
+}
+
+static void mmc_blk_put(struct mmc_blk_data *md)
+{
+ mutex_lock(&open_lock);
+ md->usage--;
+ if (md->usage == 0) {
+ int devidx = mmc_get_devidx(md->disk);
+ blk_cleanup_queue(md->queue.queue);
+
+ __clear_bit(devidx, dev_use);
+
+ put_disk(md->disk);
+ kfree(md);
+ }
+ mutex_unlock(&open_lock);
+}
+
+static ssize_t force_ro_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev));
+
+ ret = snprintf(buf, PAGE_SIZE, "%d",
+ get_disk_ro(dev_to_disk(dev)) ^
+ md->read_only);
+ mmc_blk_put(md);
+ return ret;
+}
+
+static ssize_t force_ro_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ char *end;
+ struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev));
+ unsigned long set = simple_strtoul(buf, &end, 0);
+ if (end == buf) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ set_disk_ro(dev_to_disk(dev), set || md->read_only);
+ ret = count;
+out:
+ mmc_blk_put(md);
+ return ret;
+}
+
+static int mmc_blk_open(struct block_device *bdev, fmode_t mode)
+{
+ struct mmc_blk_data *md = mmc_blk_get(bdev->bd_disk);
+ int ret = -ENXIO;
+
+ mutex_lock(&block_mutex);
+ if (md) {
+ if (md->usage == 2)
+ check_disk_change(bdev);
+ ret = 0;
+
+ if ((mode & FMODE_WRITE) && md->read_only) {
+ mmc_blk_put(md);
+ ret = -EROFS;
+ }
+ }
+ mutex_unlock(&block_mutex);
+
+ return ret;
+}
+
+static int mmc_blk_release(struct gendisk *disk, fmode_t mode)
+{
+ struct mmc_blk_data *md = disk->private_data;
+
+ mutex_lock(&block_mutex);
+ mmc_blk_put(md);
+ mutex_unlock(&block_mutex);
+ return 0;
+}
+
+static int
+mmc_blk_getgeo(struct block_device *bdev, struct hd_geometry *geo)
+{
+ geo->cylinders = get_capacity(bdev->bd_disk) / (4 * 16);
+ geo->heads = 4;
+ geo->sectors = 16;
+ return 0;
+}
+
+struct mmc_blk_ioc_data {
+ struct mmc_ioc_cmd ic;
+ unsigned char *buf;
+ u64 buf_bytes;
+};
+
+static struct mmc_blk_ioc_data *mmc_blk_ioctl_copy_from_user(
+ struct mmc_ioc_cmd __user *user)
+{
+ struct mmc_blk_ioc_data *idata;
+ int err;
+
+ idata = kzalloc(sizeof(*idata), GFP_KERNEL);
+ if (!idata) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ if (copy_from_user(&idata->ic, user, sizeof(idata->ic))) {
+ err = -EFAULT;
+ goto idata_err;
+ }
+
+ idata->buf_bytes = (u64) idata->ic.blksz * idata->ic.blocks;
+ if (idata->buf_bytes > MMC_IOC_MAX_BYTES) {
+ err = -EOVERFLOW;
+ goto idata_err;
+ }
+
+ if (!idata->buf_bytes)
+ return idata;
+
+ idata->buf = kzalloc(idata->buf_bytes, GFP_KERNEL);
+ if (!idata->buf) {
+ err = -ENOMEM;
+ goto idata_err;
+ }
+
+ if (copy_from_user(idata->buf, (void __user *)(unsigned long)
+ idata->ic.data_ptr, idata->buf_bytes)) {
+ err = -EFAULT;
+ goto copy_err;
+ }
+
+ return idata;
+
+copy_err:
+ kfree(idata->buf);
+idata_err:
+ kfree(idata);
+out:
+ return ERR_PTR(err);
+}
+
+static int mmc_blk_ioctl_cmd(struct block_device *bdev,
+ struct mmc_ioc_cmd __user *ic_ptr)
+{
+ struct mmc_blk_ioc_data *idata;
+ struct mmc_blk_data *md;
+ struct mmc_card *card;
+ struct mmc_command cmd = {0};
+ struct mmc_data data = {0};
+ struct mmc_request mrq = {0};
+ struct scatterlist sg;
+ int err;
+
+ /*
+ * The caller must have CAP_SYS_RAWIO, and must be calling this on the
+ * whole block device, not on a partition. This prevents overspray
+ * between sibling partitions.
+ */
+ if ((!capable(CAP_SYS_RAWIO)) || (bdev != bdev->bd_contains))
+ return -EPERM;
+
+ idata = mmc_blk_ioctl_copy_from_user(ic_ptr);
+ if (IS_ERR(idata))
+ return PTR_ERR(idata);
+
+ md = mmc_blk_get(bdev->bd_disk);
+ if (!md) {
+ err = -EINVAL;
+ goto cmd_done;
+ }
+
+ card = md->queue.card;
+ if (IS_ERR(card)) {
+ err = PTR_ERR(card);
+ goto cmd_done;
+ }
+
+ cmd.opcode = idata->ic.opcode;
+ cmd.arg = idata->ic.arg;
+ cmd.flags = idata->ic.flags;
+
+ if (idata->buf_bytes) {
+ data.sg = &sg;
+ data.sg_len = 1;
+ data.blksz = idata->ic.blksz;
+ data.blocks = idata->ic.blocks;
+
+ sg_init_one(data.sg, idata->buf, idata->buf_bytes);
+
+ if (idata->ic.write_flag)
+ data.flags = MMC_DATA_WRITE;
+ else
+ data.flags = MMC_DATA_READ;
+
+ /* data.flags must already be set before doing this. */
+ mmc_set_data_timeout(&data, card);
+
+ /* Allow overriding the timeout_ns for empirical tuning. */
+ if (idata->ic.data_timeout_ns)
+ data.timeout_ns = idata->ic.data_timeout_ns;
+
+ if ((cmd.flags & MMC_RSP_R1B) == MMC_RSP_R1B) {
+ /*
+ * Pretend this is a data transfer and rely on the
+ * host driver to compute timeout. When all host
+ * drivers support cmd.cmd_timeout for R1B, this
+ * can be changed to:
+ *
+ * mrq.data = NULL;
+ * cmd.cmd_timeout = idata->ic.cmd_timeout_ms;
+ */
+ data.timeout_ns = idata->ic.cmd_timeout_ms * 1000000;
+ }
+
+ mrq.data = &data;
+ }
+
+ mrq.cmd = &cmd;
+
+ mmc_claim_host(card->host);
+
+ if (idata->ic.is_acmd) {
+ err = mmc_app_cmd(card->host, card);
+ if (err)
+ goto cmd_rel_host;
+ }
+
+ mmc_wait_for_req(card->host, &mrq);
+
+ if (cmd.error) {
+ dev_err(mmc_dev(card->host), "%s: cmd error %d\n",
+ __func__, cmd.error);
+ err = cmd.error;
+ goto cmd_rel_host;
+ }
+ if (data.error) {
+ dev_err(mmc_dev(card->host), "%s: data error %d\n",
+ __func__, data.error);
+ err = data.error;
+ goto cmd_rel_host;
+ }
+
+ /*
+ * According to the SD specs, some commands require a delay after
+ * issuing the command.
+ */
+ if (idata->ic.postsleep_min_us)
+ usleep_range(idata->ic.postsleep_min_us, idata->ic.postsleep_max_us);
+
+ if (copy_to_user(&(ic_ptr->response), cmd.resp, sizeof(cmd.resp))) {
+ err = -EFAULT;
+ goto cmd_rel_host;
+ }
+
+ if (!idata->ic.write_flag) {
+ if (copy_to_user((void __user *)(unsigned long) idata->ic.data_ptr,
+ idata->buf, idata->buf_bytes)) {
+ err = -EFAULT;
+ goto cmd_rel_host;
+ }
+ }
+
+cmd_rel_host:
+ mmc_release_host(card->host);
+
+cmd_done:
+ mmc_blk_put(md);
+ kfree(idata->buf);
+ kfree(idata);
+ return err;
+}
+
+static int mmc_blk_ioctl(struct block_device *bdev, fmode_t mode,
+ unsigned int cmd, unsigned long arg)
+{
+ int ret = -EINVAL;
+ if (cmd == MMC_IOC_CMD)
+ ret = mmc_blk_ioctl_cmd(bdev, (struct mmc_ioc_cmd __user *)arg);
+ return ret;
+}
+
+#ifdef CONFIG_COMPAT
+static int mmc_blk_compat_ioctl(struct block_device *bdev, fmode_t mode,
+ unsigned int cmd, unsigned long arg)
+{
+ return mmc_blk_ioctl(bdev, mode, cmd, (unsigned long) compat_ptr(arg));
+}
+#endif
+
+static const struct block_device_operations mmc_bdops = {
+ .open = mmc_blk_open,
+ .release = mmc_blk_release,
+ .getgeo = mmc_blk_getgeo,
+ .owner = THIS_MODULE,
+ .ioctl = mmc_blk_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = mmc_blk_compat_ioctl,
+#endif
+};
+
+static inline int mmc_blk_part_switch(struct mmc_card *card,
+ struct mmc_blk_data *md)
+{
+ int ret;
+ struct mmc_blk_data *main_md = mmc_get_drvdata(card);
+ if (main_md->part_curr == md->part_type)
+ return 0;
+
+ if (mmc_card_mmc(card)) {
+ card->ext_csd.part_config &= ~EXT_CSD_PART_CONFIG_ACC_MASK;
+ card->ext_csd.part_config |= md->part_type;
+
+ ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_PART_CONFIG, card->ext_csd.part_config,
+ card->ext_csd.part_time);
+ if (ret)
+ return ret;
+}
+
+ main_md->part_curr = md->part_type;
+ return 0;
+}
+
+static u32 mmc_sd_num_wr_blocks(struct mmc_card *card)
+{
+ int err;
+ u32 result;
+ __be32 *blocks;
+
+ struct mmc_request mrq = {0};
+ struct mmc_command cmd = {0};
+ struct mmc_data data = {0};
+ unsigned int timeout_us;
+
+ struct scatterlist sg;
+
+ cmd.opcode = MMC_APP_CMD;
+ cmd.arg = card->rca << 16;
+ cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC;
+
+ err = mmc_wait_for_cmd(card->host, &cmd, 0);
+ if (err)
+ return (u32)-1;
+ if (!mmc_host_is_spi(card->host) && !(cmd.resp[0] & R1_APP_CMD))
+ return (u32)-1;
+
+ memset(&cmd, 0, sizeof(struct mmc_command));
+
+ cmd.opcode = SD_APP_SEND_NUM_WR_BLKS;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
+
+ data.timeout_ns = card->csd.tacc_ns * 100;
+ data.timeout_clks = card->csd.tacc_clks * 100;
+
+ timeout_us = data.timeout_ns / 1000;
+ timeout_us += data.timeout_clks * 1000 /
+ (card->host->ios.clock / 1000);
+
+ if (timeout_us > 100000) {
+ data.timeout_ns = 100000000;
+ data.timeout_clks = 0;
+ }
+
+ data.blksz = 4;
+ data.blocks = 1;
+ data.flags = MMC_DATA_READ;
+ data.sg = &sg;
+ data.sg_len = 1;
+
+ mrq.cmd = &cmd;
+ mrq.data = &data;
+
+ blocks = kmalloc(4, GFP_KERNEL);
+ if (!blocks)
+ return (u32)-1;
+
+ sg_init_one(&sg, blocks, 4);
+
+ mmc_wait_for_req(card->host, &mrq);
+
+ result = ntohl(*blocks);
+ kfree(blocks);
+
+ if (cmd.error || data.error)
+ result = (u32)-1;
+
+ return result;
+}
+
+static int send_stop(struct mmc_card *card, u32 *status)
+{
+ struct mmc_command cmd = {0};
+ int err;
+
+ cmd.opcode = MMC_STOP_TRANSMISSION;
+ cmd.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
+ err = mmc_wait_for_cmd(card->host, &cmd, 5);
+ if (err == 0)
+ *status = cmd.resp[0];
+ return err;
+}
+
+static int get_card_status(struct mmc_card *card, u32 *status, int retries)
+{
+ struct mmc_command cmd = {0};
+ int err;
+
+ cmd.opcode = MMC_SEND_STATUS;
+ if (!mmc_host_is_spi(card->host))
+ cmd.arg = card->rca << 16;
+ cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;
+ err = mmc_wait_for_cmd(card->host, &cmd, retries);
+ if (err == 0)
+ *status = cmd.resp[0];
+ return err;
+}
+
+#define ERR_RETRY 2
+#define ERR_ABORT 1
+#define ERR_CONTINUE 0
+
+static int mmc_blk_cmd_error(struct request *req, const char *name, int error,
+ bool status_valid, u32 status)
+{
+ switch (error) {
+ case -EILSEQ:
+ /* response crc error, retry the r/w cmd */
+ pr_err("%s: %s sending %s command, card status %#x\n",
+ req->rq_disk->disk_name, "response CRC error",
+ name, status);
+ return ERR_RETRY;
+
+ case -ETIMEDOUT:
+ pr_err("%s: %s sending %s command, card status %#x\n",
+ req->rq_disk->disk_name, "timed out", name, status);
+
+ /* If the status cmd initially failed, retry the r/w cmd */
+ if (!status_valid)
+ return ERR_RETRY;
+
+ /*
+ * If it was a r/w cmd crc error, or illegal command
+ * (eg, issued in wrong state) then retry - we should
+ * have corrected the state problem above.
+ */
+ if (status & (R1_COM_CRC_ERROR | R1_ILLEGAL_COMMAND))
+ return ERR_RETRY;
+
+ /* Otherwise abort the command */
+ return ERR_ABORT;
+
+ default:
+ /* We don't understand the error code the driver gave us */
+ pr_err("%s: unknown error %d sending read/write command, card status %#x\n",
+ req->rq_disk->disk_name, error, status);
+ return ERR_ABORT;
+ }
+}
+
+/*
+ * Initial r/w and stop cmd error recovery.
+ * We don't know whether the card received the r/w cmd or not, so try to
+ * restore things back to a sane state. Essentially, we do this as follows:
+ * - Obtain card status. If the first attempt to obtain card status fails,
+ * the status word will reflect the failed status cmd, not the failed
+ * r/w cmd. If we fail to obtain card status, it suggests we can no
+ * longer communicate with the card.
+ * - Check the card state. If the card received the cmd but there was a
+ * transient problem with the response, it might still be in a data transfer
+ * mode. Try to send it a stop command. If this fails, we can't recover.
+ * - If the r/w cmd failed due to a response CRC error, it was probably
+ * transient, so retry the cmd.
+ * - If the r/w cmd timed out, but we didn't get the r/w cmd status, retry.
+ * - If the r/w cmd timed out, and the r/w cmd failed due to CRC error or
+ * illegal cmd, retry.
+ * Otherwise we don't understand what happened, so abort.
+ */
+static int mmc_blk_cmd_recovery(struct mmc_card *card, struct request *req,
+ struct mmc_blk_request *brq)
+{
+ bool prev_cmd_status_valid = true;
+ u32 status, stop_status = 0;
+ int err, retry;
+
+ /*
+ * Try to get card status which indicates both the card state
+ * and why there was no response. If the first attempt fails,
+ * we can't be sure the returned status is for the r/w command.
+ */
+ for (retry = 2; retry >= 0; retry--) {
+ err = get_card_status(card, &status, 0);
+ if (!err)
+ break;
+
+ prev_cmd_status_valid = false;
+ pr_err("%s: error %d sending status command, %sing\n",
+ req->rq_disk->disk_name, err, retry ? "retry" : "abort");
+ }
+
+ /* We couldn't get a response from the card. Give up. */
+ if (err)
+ return ERR_ABORT;
+
+ /*
+ * Check the current card state. If it is in some data transfer
+ * mode, tell it to stop (and hopefully transition back to TRAN.)
+ */
+ if (R1_CURRENT_STATE(status) == R1_STATE_DATA ||
+ R1_CURRENT_STATE(status) == R1_STATE_RCV) {
+ err = send_stop(card, &stop_status);
+ if (err)
+ pr_err("%s: error %d sending stop command\n",
+ req->rq_disk->disk_name, err);
+
+ /*
+ * If the stop cmd also timed out, the card is probably
+ * not present, so abort. Other errors are bad news too.
+ */
+ if (err)
+ return ERR_ABORT;
+ }
+
+ /* Check for set block count errors */
+ if (brq->sbc.error)
+ return mmc_blk_cmd_error(req, "SET_BLOCK_COUNT", brq->sbc.error,
+ prev_cmd_status_valid, status);
+
+ /* Check for r/w command errors */
+ if (brq->cmd.error)
+ return mmc_blk_cmd_error(req, "r/w cmd", brq->cmd.error,
+ prev_cmd_status_valid, status);
+
+ /* Now for stop errors. These aren't fatal to the transfer. */
+ pr_err("%s: error %d sending stop command, original cmd response %#x, card status %#x\n",
+ req->rq_disk->disk_name, brq->stop.error,
+ brq->cmd.resp[0], status);
+
+ /*
+ * Subsitute in our own stop status as this will give the error
+ * state which happened during the execution of the r/w command.
+ */
+ if (stop_status) {
+ brq->stop.resp[0] = stop_status;
+ brq->stop.error = 0;
+ }
+ return ERR_CONTINUE;
+}
+
+static int mmc_blk_issue_discard_rq(struct mmc_queue *mq, struct request *req)
+{
+ struct mmc_blk_data *md = mq->data;
+ struct mmc_card *card = md->queue.card;
+ unsigned int from, nr, arg;
+ int err = 0;
+
+ if (!mmc_can_erase(card)) {
+ err = -EOPNOTSUPP;
+ goto out;
+ }
+
+ from = blk_rq_pos(req);
+ nr = blk_rq_sectors(req);
+
+ if (mmc_can_trim(card))
+ arg = MMC_TRIM_ARG;
+ else
+ arg = MMC_ERASE_ARG;
+
+ if (card->quirks & MMC_QUIRK_INAND_CMD38) {
+ err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+ INAND_CMD38_ARG_EXT_CSD,
+ arg == MMC_TRIM_ARG ?
+ INAND_CMD38_ARG_TRIM :
+ INAND_CMD38_ARG_ERASE,
+ 0);
+ if (err)
+ goto out;
+ }
+ err = mmc_erase(card, from, nr, arg);
+out:
+ spin_lock_irq(&md->lock);
+ __blk_end_request(req, err, blk_rq_bytes(req));
+ spin_unlock_irq(&md->lock);
+
+ return err ? 0 : 1;
+}
+
+static int mmc_blk_issue_secdiscard_rq(struct mmc_queue *mq,
+ struct request *req)
+{
+ struct mmc_blk_data *md = mq->data;
+ struct mmc_card *card = md->queue.card;
+ unsigned int from, nr, arg;
+ int err = 0;
+
+ if (!mmc_can_secure_erase_trim(card)) {
+ err = -EOPNOTSUPP;
+ goto out;
+ }
+
+ from = blk_rq_pos(req);
+ nr = blk_rq_sectors(req);
+
+ if (mmc_can_trim(card) && !mmc_erase_group_aligned(card, from, nr))
+ arg = MMC_SECURE_TRIM1_ARG;
+ else
+ arg = MMC_SECURE_ERASE_ARG;
+
+ if (card->quirks & MMC_QUIRK_INAND_CMD38) {
+ err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+ INAND_CMD38_ARG_EXT_CSD,
+ arg == MMC_SECURE_TRIM1_ARG ?
+ INAND_CMD38_ARG_SECTRIM1 :
+ INAND_CMD38_ARG_SECERASE,
+ 0);
+ if (err)
+ goto out;
+ }
+ err = mmc_erase(card, from, nr, arg);
+ if (!err && arg == MMC_SECURE_TRIM1_ARG) {
+ if (card->quirks & MMC_QUIRK_INAND_CMD38) {
+ err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+ INAND_CMD38_ARG_EXT_CSD,
+ INAND_CMD38_ARG_SECTRIM2,
+ 0);
+ if (err)
+ goto out;
+ }
+ err = mmc_erase(card, from, nr, MMC_SECURE_TRIM2_ARG);
+ }
+out:
+ spin_lock_irq(&md->lock);
+ __blk_end_request(req, err, blk_rq_bytes(req));
+ spin_unlock_irq(&md->lock);
+
+ return err ? 0 : 1;
+}
+
+static int mmc_blk_issue_flush(struct mmc_queue *mq, struct request *req)
+{
+ struct mmc_blk_data *md = mq->data;
+
+ /*
+ * No-op, only service this because we need REQ_FUA for reliable
+ * writes.
+ */
+ spin_lock_irq(&md->lock);
+ __blk_end_request_all(req, 0);
+ spin_unlock_irq(&md->lock);
+
+ return 1;
+}
+
+/*
+ * Reformat current write as a reliable write, supporting
+ * both legacy and the enhanced reliable write MMC cards.
+ * In each transfer we'll handle only as much as a single
+ * reliable write can handle, thus finish the request in
+ * partial completions.
+ */
+static inline void mmc_apply_rel_rw(struct mmc_blk_request *brq,
+ struct mmc_card *card,
+ struct request *req)
+{
+ if (!(card->ext_csd.rel_param & EXT_CSD_WR_REL_PARAM_EN)) {
+ /* Legacy mode imposes restrictions on transfers. */
+ if (!IS_ALIGNED(brq->cmd.arg, card->ext_csd.rel_sectors))
+ brq->data.blocks = 1;
+
+ if (brq->data.blocks > card->ext_csd.rel_sectors)
+ brq->data.blocks = card->ext_csd.rel_sectors;
+ else if (brq->data.blocks < card->ext_csd.rel_sectors)
+ brq->data.blocks = 1;
+ }
+}
+
+#define CMD_ERRORS \
+ (R1_OUT_OF_RANGE | /* Command argument out of range */ \
+ R1_ADDRESS_ERROR | /* Misaligned address */ \
+ R1_BLOCK_LEN_ERROR | /* Transferred block length incorrect */\
+ R1_WP_VIOLATION | /* Tried to write to protected block */ \
+ R1_CC_ERROR | /* Card controller error */ \
+ R1_ERROR) /* General/unknown error */
+
+static int mmc_blk_err_check(struct mmc_card *card,
+ struct mmc_async_req *areq)
+{
+ enum mmc_blk_status ret = MMC_BLK_SUCCESS;
+ struct mmc_queue_req *mq_mrq = container_of(areq, struct mmc_queue_req,
+ mmc_active);
+ struct mmc_blk_request *brq = &mq_mrq->brq;
+ struct request *req = mq_mrq->req;
+
+ /*
+ * sbc.error indicates a problem with the set block count
+ * command. No data will have been transferred.
+ *
+ * cmd.error indicates a problem with the r/w command. No
+ * data will have been transferred.
+ *
+ * stop.error indicates a problem with the stop command. Data
+ * may have been transferred, or may still be transferring.
+ */
+ if (brq->sbc.error || brq->cmd.error || brq->stop.error) {
+ switch (mmc_blk_cmd_recovery(card, req, brq)) {
+ case ERR_RETRY:
+ return MMC_BLK_RETRY;
+ case ERR_ABORT:
+ return MMC_BLK_ABORT;
+ case ERR_CONTINUE:
+ break;
+ }
+ }
+
+ /*
+ * Check for errors relating to the execution of the
+ * initial command - such as address errors. No data
+ * has been transferred.
+ */
+ if (brq->cmd.resp[0] & CMD_ERRORS) {
+ pr_err("%s: r/w command failed, status = %#x\n",
+ req->rq_disk->disk_name, brq->cmd.resp[0]);
+ return MMC_BLK_ABORT;
+ }
+
+ /*
+ * Everything else is either success, or a data error of some
+ * kind. If it was a write, we may have transitioned to
+ * program mode, which we have to wait for it to complete.
+ */
+ if (!mmc_host_is_spi(card->host) && rq_data_dir(req) != READ) {
+ u32 status;
+ do {
+ int err = get_card_status(card, &status, 5);
+ if (err) {
+ printk(KERN_ERR "%s: error %d requesting status\n",
+ req->rq_disk->disk_name, err);
+ return MMC_BLK_CMD_ERR;
+ }
+ /*
+ * Some cards mishandle the status bits,
+ * so make sure to check both the busy
+ * indication and the card state.
+ */
+ } while (!(status & R1_READY_FOR_DATA) ||
+ (R1_CURRENT_STATE(status) == R1_STATE_PRG));
+ }
+
+ if (brq->data.error) {
+ pr_err("%s: error %d transferring data, sector %u, nr %u, cmd response %#x, card status %#x\n",
+ req->rq_disk->disk_name, brq->data.error,
+ (unsigned)blk_rq_pos(req),
+ (unsigned)blk_rq_sectors(req),
+ brq->cmd.resp[0], brq->stop.resp[0]);
+
+ if (rq_data_dir(req) == READ) {
+ if (brq->data.blocks > 1) {
+ /* Redo read one sector at a time */
+ pr_warning("%s: retrying using single block read\n",
+ req->rq_disk->disk_name);
+ return MMC_BLK_RETRY_SINGLE;
+ }
+ return MMC_BLK_DATA_ERR;
+ } else {
+ return MMC_BLK_CMD_ERR;
+ }
+ }
+
+ if (ret == MMC_BLK_SUCCESS &&
+ blk_rq_bytes(req) != brq->data.bytes_xfered)
+ ret = MMC_BLK_PARTIAL;
+
+ return ret;
+}
+
+static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq,
+ struct mmc_card *card,
+ int disable_multi,
+ struct mmc_queue *mq)
+{
+ u32 readcmd, writecmd;
+ struct mmc_blk_request *brq = &mqrq->brq;
+ struct request *req = mqrq->req;
+ struct mmc_blk_data *md = mq->data;
+
+ /*
+ * Reliable writes are used to implement Forced Unit Access and
+ * REQ_META accesses, and are supported only on MMCs.
+ */
+ bool do_rel_wr = ((req->cmd_flags & REQ_FUA) ||
+ (req->cmd_flags & REQ_META)) &&
+ (rq_data_dir(req) == WRITE) &&
+ (md->flags & MMC_BLK_REL_WR);
+
+ memset(brq, 0, sizeof(struct mmc_blk_request));
+ brq->mrq.cmd = &brq->cmd;
+ brq->mrq.data = &brq->data;
+
+ brq->cmd.arg = blk_rq_pos(req);
+ if (!mmc_card_blockaddr(card))
+ brq->cmd.arg <<= 9;
+ brq->cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
+ brq->data.blksz = 512;
+ brq->stop.opcode = MMC_STOP_TRANSMISSION;
+ brq->stop.arg = 0;
+ brq->stop.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
+ brq->data.blocks = blk_rq_sectors(req);
+
+ /*
+ * The block layer doesn't support all sector count
+ * restrictions, so we need to be prepared for too big
+ * requests.
+ */
+ if (brq->data.blocks > card->host->max_blk_count)
+ brq->data.blocks = card->host->max_blk_count;
+
+ /*
+ * After a read error, we redo the request one sector at a time
+ * in order to accurately determine which sectors can be read
+ * successfully.
+ */
+ if (disable_multi && brq->data.blocks > 1)
+ brq->data.blocks = 1;
+
+ if (brq->data.blocks > 1 || do_rel_wr) {
+ /* SPI multiblock writes terminate using a special
+ * token, not a STOP_TRANSMISSION request.
+ */
+ if (!mmc_host_is_spi(card->host) ||
+ rq_data_dir(req) == READ)
+ brq->mrq.stop = &brq->stop;
+ readcmd = MMC_READ_MULTIPLE_BLOCK;
+ writecmd = MMC_WRITE_MULTIPLE_BLOCK;
+ } else {
+ brq->mrq.stop = NULL;
+ readcmd = MMC_READ_SINGLE_BLOCK;
+ writecmd = MMC_WRITE_BLOCK;
+ }
+ if (rq_data_dir(req) == READ) {
+ brq->cmd.opcode = readcmd;
+ brq->data.flags |= MMC_DATA_READ;
+ } else {
+ brq->cmd.opcode = writecmd;
+ brq->data.flags |= MMC_DATA_WRITE;
+ }
+
+ if (do_rel_wr)
+ mmc_apply_rel_rw(brq, card, req);
+
+ /*
+ * Pre-defined multi-block transfers are preferable to
+ * open ended-ones (and necessary for reliable writes).
+ * However, it is not sufficient to just send CMD23,
+ * and avoid the final CMD12, as on an error condition
+ * CMD12 (stop) needs to be sent anyway. This, coupled
+ * with Auto-CMD23 enhancements provided by some
+ * hosts, means that the complexity of dealing
+ * with this is best left to the host. If CMD23 is
+ * supported by card and host, we'll fill sbc in and let
+ * the host deal with handling it correctly. This means
+ * that for hosts that don't expose MMC_CAP_CMD23, no
+ * change of behavior will be observed.
+ *
+ * N.B: Some MMC cards experience perf degradation.
+ * We'll avoid using CMD23-bounded multiblock writes for
+ * these, while retaining features like reliable writes.
+ */
+
+ if ((md->flags & MMC_BLK_CMD23) &&
+ mmc_op_multi(brq->cmd.opcode) &&
+ (do_rel_wr || !(card->quirks & MMC_QUIRK_BLK_NO_CMD23))) {
+ brq->sbc.opcode = MMC_SET_BLOCK_COUNT;
+ brq->sbc.arg = brq->data.blocks |
+ (do_rel_wr ? (1 << 31) : 0);
+ brq->sbc.flags = MMC_RSP_R1 | MMC_CMD_AC;
+ brq->mrq.sbc = &brq->sbc;
+ }
+
+ mmc_set_data_timeout(&brq->data, card);
+
+ brq->data.sg = mqrq->sg;
+ brq->data.sg_len = mmc_queue_map_sg(mq, mqrq);
+
+ /*
+ * Adjust the sg list so it is the same size as the
+ * request.
+ */
+ if (brq->data.blocks != blk_rq_sectors(req)) {
+ int i, data_size = brq->data.blocks << 9;
+ struct scatterlist *sg;
+
+ for_each_sg(brq->data.sg, sg, brq->data.sg_len, i) {
+ data_size -= sg->length;
+ if (data_size <= 0) {
+ sg->length += data_size;
+ i++;
+ break;
+ }
+ }
+ brq->data.sg_len = i;
+ }
+
+ mqrq->mmc_active.mrq = &brq->mrq;
+ mqrq->mmc_active.err_check = mmc_blk_err_check;
+
+ mmc_queue_bounce_pre(mqrq);
+}
+
+static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc)
+{
+ struct mmc_blk_data *md = mq->data;
+ struct mmc_card *card = md->queue.card;
+ struct mmc_blk_request *brq = &mq->mqrq_cur->brq;
+ int ret = 1, disable_multi = 0, retry = 0;
+ enum mmc_blk_status status;
+ struct mmc_queue_req *mq_rq;
+ struct request *req;
+ struct mmc_async_req *areq;
+
+ if (!rqc && !mq->mqrq_prev->req)
+ return 0;
+
+ do {
+ if (rqc) {
+ mmc_blk_rw_rq_prep(mq->mqrq_cur, card, 0, mq);
+ areq = &mq->mqrq_cur->mmc_active;
+ } else
+ areq = NULL;
+ areq = mmc_start_req(card->host, areq, (int *) &status);
+ if (!areq)
+ return 0;
+
+ mq_rq = container_of(areq, struct mmc_queue_req, mmc_active);
+ brq = &mq_rq->brq;
+ req = mq_rq->req;
+ mmc_queue_bounce_post(mq_rq);
+
+ switch (status) {
+ case MMC_BLK_SUCCESS:
+ case MMC_BLK_PARTIAL:
+ /*
+ * A block was successfully transferred.
+ */
+ spin_lock_irq(&md->lock);
+ ret = __blk_end_request(req, 0,
+ brq->data.bytes_xfered);
+ spin_unlock_irq(&md->lock);
+ if (status == MMC_BLK_SUCCESS && ret) {
+ /*
+ * The blk_end_request has returned non zero
+ * even though all data is transfered and no
+ * erros returned by host.
+ * If this happen it's a bug.
+ */
+ printk(KERN_ERR "%s BUG rq_tot %d d_xfer %d\n",
+ __func__, blk_rq_bytes(req),
+ brq->data.bytes_xfered);
+ rqc = NULL;
+ goto cmd_abort;
+ }
+ break;
+ case MMC_BLK_CMD_ERR:
+ goto cmd_err;
+ case MMC_BLK_RETRY_SINGLE:
+ disable_multi = 1;
+ break;
+ case MMC_BLK_RETRY:
+ if (retry++ < 5)
+ break;
+ case MMC_BLK_ABORT:
+ goto cmd_abort;
+ case MMC_BLK_DATA_ERR:
+ /*
+ * After an error, we redo I/O one sector at a
+ * time, so we only reach here after trying to
+ * read a single sector.
+ */
+ spin_lock_irq(&md->lock);
+ ret = __blk_end_request(req, -EIO,
+ brq->data.blksz);
+ spin_unlock_irq(&md->lock);
+ if (!ret)
+ goto start_new_req;
+ break;
+ }
+
+ if (ret) {
+ /*
+ * In case of a none complete request
+ * prepare it again and resend.
+ */
+ mmc_blk_rw_rq_prep(mq_rq, card, disable_multi, mq);
+ mmc_start_req(card->host, &mq_rq->mmc_active, NULL);
+ }
+ } while (ret);
+
+ return 1;
+
+ cmd_err:
+ /*
+ * If this is an SD card and we're writing, we can first
+ * mark the known good sectors as ok.
+ *
+ * If the card is not SD, we can still ok written sectors
+ * as reported by the controller (which might be less than
+ * the real number of written sectors, but never more).
+ */
+ if (mmc_card_sd(card)) {
+ u32 blocks;
+
+ blocks = mmc_sd_num_wr_blocks(card);
+ if (blocks != (u32)-1) {
+ spin_lock_irq(&md->lock);
+ ret = __blk_end_request(req, 0, blocks << 9);
+ spin_unlock_irq(&md->lock);
+ }
+ } else {
+ spin_lock_irq(&md->lock);
+ ret = __blk_end_request(req, 0, brq->data.bytes_xfered);
+ spin_unlock_irq(&md->lock);
+ }
+
+ cmd_abort:
+ spin_lock_irq(&md->lock);
+ while (ret)
+ ret = __blk_end_request(req, -EIO, blk_rq_cur_bytes(req));
+ spin_unlock_irq(&md->lock);
+
+ start_new_req:
+ if (rqc) {
+ mmc_blk_rw_rq_prep(mq->mqrq_cur, card, 0, mq);
+ mmc_start_req(card->host, &mq->mqrq_cur->mmc_active, NULL);
+ }
+
+ return 0;
+}
+
+static int
+mmc_blk_set_blksize(struct mmc_blk_data *md, struct mmc_card *card);
+
+static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req)
+{
+ int ret;
+ struct mmc_blk_data *md = mq->data;
+ struct mmc_card *card = md->queue.card;
+
+#ifdef CONFIG_MMC_BLOCK_DEFERRED_RESUME
+ if (mmc_bus_needs_resume(card->host)) {
+ mmc_resume_bus(card->host);
+ mmc_blk_set_blksize(md, card);
+ }
+#endif
+
+ if (req && !mq->mqrq_prev->req)
+ /* claim host only for the first request */
+ mmc_claim_host(card->host);
+
+ ret = mmc_blk_part_switch(card, md);
+ if (ret) {
+ ret = 0;
+ goto out;
+ }
+
+ if (req && req->cmd_flags & REQ_DISCARD) {
+ /* complete ongoing async transfer before issuing discard */
+ if (card->host->areq)
+ mmc_blk_issue_rw_rq(mq, NULL);
+ if (req->cmd_flags & REQ_SECURE)
+ ret = mmc_blk_issue_secdiscard_rq(mq, req);
+ else
+ ret = mmc_blk_issue_discard_rq(mq, req);
+ } else if (req && req->cmd_flags & REQ_FLUSH) {
+ /* complete ongoing async transfer before issuing flush */
+ if (card->host->areq)
+ mmc_blk_issue_rw_rq(mq, NULL);
+ ret = mmc_blk_issue_flush(mq, req);
+ } else {
+ ret = mmc_blk_issue_rw_rq(mq, req);
+ }
+
+out:
+ if (!req)
+ /* release host only when there are no more requests */
+ mmc_release_host(card->host);
+ return ret;
+}
+
+static inline int mmc_blk_readonly(struct mmc_card *card)
+{
+ return mmc_card_readonly(card) ||
+ !(card->csd.cmdclass & CCC_BLOCK_WRITE);
+}
+
+static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card,
+ struct device *parent,
+ sector_t size,
+ bool default_ro,
+ const char *subname)
+{
+ struct mmc_blk_data *md;
+ int devidx, ret;
+
+ devidx = find_first_zero_bit(dev_use, max_devices);
+ if (devidx >= max_devices)
+ return ERR_PTR(-ENOSPC);
+ __set_bit(devidx, dev_use);
+
+ md = kzalloc(sizeof(struct mmc_blk_data), GFP_KERNEL);
+ if (!md) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ /*
+ * !subname implies we are creating main mmc_blk_data that will be
+ * associated with mmc_card with mmc_set_drvdata. Due to device
+ * partitions, devidx will not coincide with a per-physical card
+ * index anymore so we keep track of a name index.
+ */
+ if (!subname) {
+ md->name_idx = find_first_zero_bit(name_use, max_devices);
+ __set_bit(md->name_idx, name_use);
+ }
+ else
+ md->name_idx = ((struct mmc_blk_data *)
+ dev_to_disk(parent)->private_data)->name_idx;
+
+ /*
+ * Set the read-only status based on the supported commands
+ * and the write protect switch.
+ */
+ md->read_only = mmc_blk_readonly(card);
+
+ md->disk = alloc_disk(perdev_minors);
+ if (md->disk == NULL) {
+ ret = -ENOMEM;
+ goto err_kfree;
+ }
+
+ spin_lock_init(&md->lock);
+ INIT_LIST_HEAD(&md->part);
+ md->usage = 1;
+
+ ret = mmc_init_queue(&md->queue, card, &md->lock, subname);
+ if (ret)
+ goto err_putdisk;
+
+ md->queue.issue_fn = mmc_blk_issue_rq;
+ md->queue.data = md;
+
+ md->disk->major = MMC_BLOCK_MAJOR;
+ md->disk->first_minor = devidx * perdev_minors;
+ md->disk->fops = &mmc_bdops;
+ md->disk->private_data = md;
+ md->disk->queue = md->queue.queue;
+ md->disk->driverfs_dev = parent;
+ set_disk_ro(md->disk, md->read_only || default_ro);
+ md->disk->flags = GENHD_FL_EXT_DEVT;
+
+ /*
+ * As discussed on lkml, GENHD_FL_REMOVABLE should:
+ *
+ * - be set for removable media with permanent block devices
+ * - be unset for removable block devices with permanent media
+ *
+ * Since MMC block devices clearly fall under the second
+ * case, we do not set GENHD_FL_REMOVABLE. Userspace
+ * should use the block device creation/destruction hotplug
+ * messages to tell when the card is present.
+ */
+
+ snprintf(md->disk->disk_name, sizeof(md->disk->disk_name),
+ "mmcblk%d%s", md->name_idx, subname ? subname : "");
+
+ blk_queue_logical_block_size(md->queue.queue, 512);
+ set_capacity(md->disk, size);
+
+ if (mmc_host_cmd23(card->host)) {
+ if (mmc_card_mmc(card) ||
+ (mmc_card_sd(card) &&
+ card->scr.cmds & SD_SCR_CMD23_SUPPORT))
+ md->flags |= MMC_BLK_CMD23;
+ }
+
+ if (mmc_card_mmc(card) &&
+ md->flags & MMC_BLK_CMD23 &&
+ ((card->ext_csd.rel_param & EXT_CSD_WR_REL_PARAM_EN) ||
+ card->ext_csd.rel_sectors)) {
+ md->flags |= MMC_BLK_REL_WR;
+ blk_queue_flush(md->queue.queue, REQ_FLUSH | REQ_FUA);
+ }
+
+ return md;
+
+ err_putdisk:
+ put_disk(md->disk);
+ err_kfree:
+ kfree(md);
+ out:
+ return ERR_PTR(ret);
+}
+
+static struct mmc_blk_data *mmc_blk_alloc(struct mmc_card *card)
+{
+ sector_t size;
+ struct mmc_blk_data *md;
+
+ if (!mmc_card_sd(card) && mmc_card_blockaddr(card)) {
+ /*
+ * The EXT_CSD sector count is in number or 512 byte
+ * sectors.
+ */
+ size = card->ext_csd.sectors;
+ } else {
+ /*
+ * The CSD capacity field is in units of read_blkbits.
+ * set_capacity takes units of 512 bytes.
+ */
+ size = card->csd.capacity << (card->csd.read_blkbits - 9);
+ }
+
+ md = mmc_blk_alloc_req(card, &card->dev, size, false, NULL);
+ return md;
+}
+
+static int mmc_blk_alloc_part(struct mmc_card *card,
+ struct mmc_blk_data *md,
+ unsigned int part_type,
+ sector_t size,
+ bool default_ro,
+ const char *subname)
+{
+ char cap_str[10];
+ struct mmc_blk_data *part_md;
+
+ part_md = mmc_blk_alloc_req(card, disk_to_dev(md->disk), size, default_ro,
+ subname);
+ if (IS_ERR(part_md))
+ return PTR_ERR(part_md);
+ part_md->part_type = part_type;
+ list_add(&part_md->part, &md->part);
+
+ string_get_size((u64)get_capacity(part_md->disk) << 9, STRING_UNITS_2,
+ cap_str, sizeof(cap_str));
+ printk(KERN_INFO "%s: %s %s partition %u %s\n",
+ part_md->disk->disk_name, mmc_card_id(card),
+ mmc_card_name(card), part_md->part_type, cap_str);
+ return 0;
+}
+
+static int mmc_blk_alloc_parts(struct mmc_card *card, struct mmc_blk_data *md)
+{
+ int ret = 0;
+
+ if (!mmc_card_mmc(card))
+ return 0;
+
+ if (card->ext_csd.boot_size) {
+ ret = mmc_blk_alloc_part(card, md, EXT_CSD_PART_CONFIG_ACC_BOOT0,
+ card->ext_csd.boot_size >> 9,
+ true,
+ "boot0");
+ if (ret)
+ return ret;
+ ret = mmc_blk_alloc_part(card, md, EXT_CSD_PART_CONFIG_ACC_BOOT1,
+ card->ext_csd.boot_size >> 9,
+ true,
+ "boot1");
+ if (ret)
+ return ret;
+ }
+
+ return ret;
+}
+
+static int
+mmc_blk_set_blksize(struct mmc_blk_data *md, struct mmc_card *card)
+{
+ int err;
+
+ mmc_claim_host(card->host);
+ err = mmc_set_blocklen(card, 512);
+ mmc_release_host(card->host);
+
+ if (err) {
+ printk(KERN_ERR "%s: unable to set block size to 512: %d\n",
+ md->disk->disk_name, err);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void mmc_blk_remove_req(struct mmc_blk_data *md)
+{
+ if (md) {
+ if (md->disk->flags & GENHD_FL_UP) {
+ device_remove_file(disk_to_dev(md->disk), &md->force_ro);
+
+ /* Stop new requests from getting into the queue */
+ del_gendisk(md->disk);
+ }
+
+ /* Then flush out any already in there */
+ mmc_cleanup_queue(&md->queue);
+ mmc_blk_put(md);
+ }
+}
+
+static void mmc_blk_remove_parts(struct mmc_card *card,
+ struct mmc_blk_data *md)
+{
+ struct list_head *pos, *q;
+ struct mmc_blk_data *part_md;
+
+ __clear_bit(md->name_idx, name_use);
+ list_for_each_safe(pos, q, &md->part) {
+ part_md = list_entry(pos, struct mmc_blk_data, part);
+ list_del(pos);
+ mmc_blk_remove_req(part_md);
+ }
+}
+
+static int mmc_add_disk(struct mmc_blk_data *md)
+{
+ int ret;
+
+ add_disk(md->disk);
+ md->force_ro.show = force_ro_show;
+ md->force_ro.store = force_ro_store;
+ sysfs_attr_init(&md->force_ro.attr);
+ md->force_ro.attr.name = "force_ro";
+ md->force_ro.attr.mode = S_IRUGO | S_IWUSR;
+ ret = device_create_file(disk_to_dev(md->disk), &md->force_ro);
+ if (ret)
+ del_gendisk(md->disk);
+
+ return ret;
+}
+
+static const struct mmc_fixup blk_fixups[] =
+{
+ MMC_FIXUP("SEM02G", 0x2, 0x100, add_quirk, MMC_QUIRK_INAND_CMD38),
+ MMC_FIXUP("SEM04G", 0x2, 0x100, add_quirk, MMC_QUIRK_INAND_CMD38),
+ MMC_FIXUP("SEM08G", 0x2, 0x100, add_quirk, MMC_QUIRK_INAND_CMD38),
+ MMC_FIXUP("SEM16G", 0x2, 0x100, add_quirk, MMC_QUIRK_INAND_CMD38),
+ MMC_FIXUP("SEM32G", 0x2, 0x100, add_quirk, MMC_QUIRK_INAND_CMD38),
+
+ /*
+ * Some MMC cards experience performance degradation with CMD23
+ * instead of CMD12-bounded multiblock transfers. For now we'll
+ * black list what's bad...
+ * - Certain Toshiba cards.
+ *
+ * N.B. This doesn't affect SD cards.
+ */
+ MMC_FIXUP("MMC08G", 0x11, CID_OEMID_ANY, add_quirk_mmc,
+ MMC_QUIRK_BLK_NO_CMD23),
+ MMC_FIXUP("MMC16G", 0x11, CID_OEMID_ANY, add_quirk_mmc,
+ MMC_QUIRK_BLK_NO_CMD23),
+ MMC_FIXUP("MMC32G", 0x11, CID_OEMID_ANY, add_quirk_mmc,
+ MMC_QUIRK_BLK_NO_CMD23),
+ END_FIXUP
+};
+
+static int mmc_blk_probe(struct mmc_card *card)
+{
+ struct mmc_blk_data *md, *part_md;
+ int err;
+ char cap_str[10];
+
+ /*
+ * Check that the card supports the command class(es) we need.
+ */
+ if (!(card->csd.cmdclass & CCC_BLOCK_READ))
+ return -ENODEV;
+
+ md = mmc_blk_alloc(card);
+ if (IS_ERR(md))
+ return PTR_ERR(md);
+
+ err = mmc_blk_set_blksize(md, card);
+ if (err)
+ goto out;
+
+ string_get_size((u64)get_capacity(md->disk) << 9, STRING_UNITS_2,
+ cap_str, sizeof(cap_str));
+ printk(KERN_INFO "%s: %s %s %s %s\n",
+ md->disk->disk_name, mmc_card_id(card), mmc_card_name(card),
+ cap_str, md->read_only ? "(ro)" : "");
+
+ /*if (mmc_blk_alloc_parts(card, md))
+ goto out;
+ */ //@Sam
+ mmc_set_drvdata(card, md);
+ mmc_fixup_device(card, blk_fixups);
+
+#ifdef CONFIG_MMC_BLOCK_DEFERRED_RESUME
+ mmc_set_bus_resume_policy(card->host, 1);
+#endif
+ if (mmc_add_disk(md))
+ goto out;
+
+ list_for_each_entry(part_md, &md->part, part) {
+ if (mmc_add_disk(part_md))
+ goto out;
+ }
+ return 0;
+
+ out:
+ mmc_blk_remove_parts(card, md);
+ mmc_blk_remove_req(md);
+ return err;
+}
+
+static void mmc_blk_remove(struct mmc_card *card)
+{
+ struct mmc_blk_data *md = mmc_get_drvdata(card);
+
+ mmc_blk_remove_parts(card, md);
+ mmc_claim_host(card->host);
+ mmc_blk_part_switch(card, md);
+ mmc_release_host(card->host);
+ mmc_blk_remove_req(md);
+ mmc_set_drvdata(card, NULL);
+#ifdef CONFIG_MMC_BLOCK_DEFERRED_RESUME
+ mmc_set_bus_resume_policy(card->host, 0);
+#endif
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int mmc_blk_suspend(struct mmc_card *card)
+{
+ struct mmc_blk_data *part_md;
+ struct mmc_blk_data *md = mmc_get_drvdata(card);
+
+ if (md) {
+ mmc_queue_suspend(&md->queue);
+ list_for_each_entry(part_md, &md->part, part) {
+ mmc_queue_suspend(&part_md->queue);
+ }
+ }
+ return 0;
+}
+
+static int mmc_blk_resume(struct mmc_card *card)
+{
+ struct mmc_blk_data *part_md;
+ struct mmc_blk_data *md = mmc_get_drvdata(card);
+
+ if (md) {
+#ifndef CONFIG_MMC_BLOCK_DEFERRED_RESUME
+ mmc_blk_set_blksize(md, card);
+#endif
+
+ /*
+ * Resume involves the card going into idle state,
+ * so current partition is always the main one.
+ */
+ md->part_curr = md->part_type;
+ mmc_queue_resume(&md->queue);
+ list_for_each_entry(part_md, &md->part, part) {
+ mmc_queue_resume(&part_md->queue);
+ }
+ }
+ return 0;
+}
+#else
+#define mmc_blk_suspend NULL
+#define mmc_blk_resume NULL
+#endif
+
+static struct mmc_driver mmc_driver = {
+ .drv = {
+ .name = "mmcblk",
+ },
+ .probe = mmc_blk_probe,
+ .remove = mmc_blk_remove,
+ .suspend = mmc_blk_suspend,
+ .resume = mmc_blk_resume,
+};
+
+static int __init mmc_blk_init(void)
+{
+ int res;
+
+ if (perdev_minors != CONFIG_MMC_BLOCK_MINORS)
+ pr_info("mmcblk: using %d minors per device\n", perdev_minors);
+
+ max_devices = 256 / perdev_minors;
+
+ res = register_blkdev(MMC_BLOCK_MAJOR, "mmc");
+ if (res)
+ goto out;
+
+ res = mmc_register_driver(&mmc_driver);
+ if (res)
+ goto out2;
+
+ return 0;
+ out2:
+ unregister_blkdev(MMC_BLOCK_MAJOR, "mmc");
+ out:
+ return res;
+}
+
+static void __exit mmc_blk_exit(void)
+{
+ mmc_unregister_driver(&mmc_driver);
+ unregister_blkdev(MMC_BLOCK_MAJOR, "mmc");
+}
+
+module_init(mmc_blk_init);
+module_exit(mmc_blk_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Multimedia Card (MMC) block device driver");
+
diff --git a/drivers/mmc/card/mmc_test.c b/drivers/mmc/card/mmc_test.c
new file mode 100644
index 00000000..006a5e9f
--- /dev/null
+++ b/drivers/mmc/card/mmc_test.c
@@ -0,0 +1,3019 @@
+/*
+ * linux/drivers/mmc/card/mmc_test.c
+ *
+ * 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
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ */
+
+#include <linux/mmc/core.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/slab.h>
+
+#include <linux/scatterlist.h>
+#include <linux/swap.h> /* For nr_free_buffer_pages() */
+#include <linux/list.h>
+
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+#include <linux/seq_file.h>
+
+#define RESULT_OK 0
+#define RESULT_FAIL 1
+#define RESULT_UNSUP_HOST 2
+#define RESULT_UNSUP_CARD 3
+
+#define BUFFER_ORDER 2
+#define BUFFER_SIZE (PAGE_SIZE << BUFFER_ORDER)
+
+/*
+ * Limit the test area size to the maximum MMC HC erase group size. Note that
+ * the maximum SD allocation unit size is just 4MiB.
+ */
+#define TEST_AREA_MAX_SIZE (128 * 1024 * 1024)
+
+/**
+ * struct mmc_test_pages - pages allocated by 'alloc_pages()'.
+ * @page: first page in the allocation
+ * @order: order of the number of pages allocated
+ */
+struct mmc_test_pages {
+ struct page *page;
+ unsigned int order;
+};
+
+/**
+ * struct mmc_test_mem - allocated memory.
+ * @arr: array of allocations
+ * @cnt: number of allocations
+ */
+struct mmc_test_mem {
+ struct mmc_test_pages *arr;
+ unsigned int cnt;
+};
+
+/**
+ * struct mmc_test_area - information for performance tests.
+ * @max_sz: test area size (in bytes)
+ * @dev_addr: address on card at which to do performance tests
+ * @max_tfr: maximum transfer size allowed by driver (in bytes)
+ * @max_segs: maximum segments allowed by driver in scatterlist @sg
+ * @max_seg_sz: maximum segment size allowed by driver
+ * @blocks: number of (512 byte) blocks currently mapped by @sg
+ * @sg_len: length of currently mapped scatterlist @sg
+ * @mem: allocated memory
+ * @sg: scatterlist
+ */
+struct mmc_test_area {
+ unsigned long max_sz;
+ unsigned int dev_addr;
+ unsigned int max_tfr;
+ unsigned int max_segs;
+ unsigned int max_seg_sz;
+ unsigned int blocks;
+ unsigned int sg_len;
+ struct mmc_test_mem *mem;
+ struct scatterlist *sg;
+};
+
+/**
+ * struct mmc_test_transfer_result - transfer results for performance tests.
+ * @link: double-linked list
+ * @count: amount of group of sectors to check
+ * @sectors: amount of sectors to check in one group
+ * @ts: time values of transfer
+ * @rate: calculated transfer rate
+ * @iops: I/O operations per second (times 100)
+ */
+struct mmc_test_transfer_result {
+ struct list_head link;
+ unsigned int count;
+ unsigned int sectors;
+ struct timespec ts;
+ unsigned int rate;
+ unsigned int iops;
+};
+
+/**
+ * struct mmc_test_general_result - results for tests.
+ * @link: double-linked list
+ * @card: card under test
+ * @testcase: number of test case
+ * @result: result of test run
+ * @tr_lst: transfer measurements if any as mmc_test_transfer_result
+ */
+struct mmc_test_general_result {
+ struct list_head link;
+ struct mmc_card *card;
+ int testcase;
+ int result;
+ struct list_head tr_lst;
+};
+
+/**
+ * struct mmc_test_dbgfs_file - debugfs related file.
+ * @link: double-linked list
+ * @card: card under test
+ * @file: file created under debugfs
+ */
+struct mmc_test_dbgfs_file {
+ struct list_head link;
+ struct mmc_card *card;
+ struct dentry *file;
+};
+
+/**
+ * struct mmc_test_card - test information.
+ * @card: card under test
+ * @scratch: transfer buffer
+ * @buffer: transfer buffer
+ * @highmem: buffer for highmem tests
+ * @area: information for performance tests
+ * @gr: pointer to results of current testcase
+ */
+struct mmc_test_card {
+ struct mmc_card *card;
+
+ u8 scratch[BUFFER_SIZE];
+ u8 *buffer;
+#ifdef CONFIG_HIGHMEM
+ struct page *highmem;
+#endif
+ struct mmc_test_area area;
+ struct mmc_test_general_result *gr;
+};
+
+enum mmc_test_prep_media {
+ MMC_TEST_PREP_NONE = 0,
+ MMC_TEST_PREP_WRITE_FULL = 1 << 0,
+ MMC_TEST_PREP_ERASE = 1 << 1,
+};
+
+struct mmc_test_multiple_rw {
+ unsigned int *sg_len;
+ unsigned int *bs;
+ unsigned int len;
+ unsigned int size;
+ bool do_write;
+ bool do_nonblock_req;
+ enum mmc_test_prep_media prepare;
+};
+
+struct mmc_test_async_req {
+ struct mmc_async_req areq;
+ struct mmc_test_card *test;
+};
+
+/*******************************************************************/
+/* General helper functions */
+/*******************************************************************/
+
+/*
+ * Configure correct block size in card
+ */
+static int mmc_test_set_blksize(struct mmc_test_card *test, unsigned size)
+{
+ return mmc_set_blocklen(test->card, size);
+}
+
+/*
+ * 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)
+{
+ BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
+
+ if (blocks > 1) {
+ mrq->cmd->opcode = write ?
+ MMC_WRITE_MULTIPLE_BLOCK : MMC_READ_MULTIPLE_BLOCK;
+ } else {
+ mrq->cmd->opcode = write ?
+ MMC_WRITE_BLOCK : MMC_READ_SINGLE_BLOCK;
+ }
+
+ mrq->cmd->arg = dev_addr;
+ if (!mmc_card_blockaddr(test->card))
+ mrq->cmd->arg <<= 9;
+
+ mrq->cmd->flags = MMC_RSP_R1 | MMC_CMD_ADTC;
+
+ 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;
+ }
+
+ 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_set_data_timeout(mrq->data, test->card);
+}
+
+static int mmc_test_busy(struct mmc_command *cmd)
+{
+ return !(cmd->resp[0] & R1_READY_FOR_DATA) ||
+ (R1_CURRENT_STATE(cmd->resp[0]) == 7);
+}
+
+/*
+ * 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 = {0};
+
+ busy = 0;
+ do {
+ 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;
+
+ ret = mmc_wait_for_cmd(test->card->host, &cmd, 0);
+ if (ret)
+ break;
+
+ if (!busy && mmc_test_busy(&cmd)) {
+ busy = 1;
+ if (test->card->host->caps & MMC_CAP_WAIT_WHILE_BUSY)
+ printk(KERN_INFO "%s: Warning: Host did not "
+ "wait for busy state to end.\n",
+ mmc_hostname(test->card->host));
+ }
+ } while (mmc_test_busy(&cmd));
+
+ return ret;
+}
+
+/*
+ * 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)
+{
+ int ret;
+
+ struct mmc_request mrq = {0};
+ struct mmc_command cmd = {0};
+ struct mmc_command stop = {0};
+ struct mmc_data data = {0};
+
+ struct scatterlist sg;
+
+ 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 void mmc_test_free_mem(struct mmc_test_mem *mem)
+{
+ if (!mem)
+ return;
+ while (mem->cnt--)
+ __free_pages(mem->arr[mem->cnt].page,
+ mem->arr[mem->cnt].order);
+ kfree(mem->arr);
+ kfree(mem);
+}
+
+/*
+ * Allocate a lot of memory, preferably max_sz but at least min_sz. In case
+ * there isn't much memory do not exceed 1/16th total lowmem pages. Also do
+ * not exceed a maximum number of segments and try not to make segments much
+ * bigger than maximum segment size.
+ */
+static struct mmc_test_mem *mmc_test_alloc_mem(unsigned long min_sz,
+ unsigned long max_sz,
+ unsigned int max_segs,
+ unsigned int max_seg_sz)
+{
+ unsigned long max_page_cnt = DIV_ROUND_UP(max_sz, PAGE_SIZE);
+ unsigned long min_page_cnt = DIV_ROUND_UP(min_sz, PAGE_SIZE);
+ unsigned long max_seg_page_cnt = DIV_ROUND_UP(max_seg_sz, PAGE_SIZE);
+ unsigned long page_cnt = 0;
+ unsigned long limit = nr_free_buffer_pages() >> 4;
+ struct mmc_test_mem *mem;
+
+ if (max_page_cnt > limit)
+ max_page_cnt = limit;
+ if (min_page_cnt > max_page_cnt)
+ min_page_cnt = max_page_cnt;
+
+ if (max_seg_page_cnt > max_page_cnt)
+ max_seg_page_cnt = max_page_cnt;
+
+ if (max_segs > max_page_cnt)
+ max_segs = max_page_cnt;
+
+ mem = kzalloc(sizeof(struct mmc_test_mem), GFP_KERNEL);
+ if (!mem)
+ return NULL;
+
+ mem->arr = kzalloc(sizeof(struct mmc_test_pages) * max_segs,
+ GFP_KERNEL);
+ if (!mem->arr)
+ goto out_free;
+
+ while (max_page_cnt) {
+ struct page *page;
+ unsigned int order;
+ gfp_t flags = GFP_KERNEL | GFP_DMA | __GFP_NOWARN |
+ __GFP_NORETRY;
+
+ order = get_order(max_seg_page_cnt << PAGE_SHIFT);
+ while (1) {
+ page = alloc_pages(flags, order);
+ if (page || !order)
+ break;
+ order -= 1;
+ }
+ if (!page) {
+ if (page_cnt < min_page_cnt)
+ goto out_free;
+ break;
+ }
+ mem->arr[mem->cnt].page = page;
+ mem->arr[mem->cnt].order = order;
+ mem->cnt += 1;
+ if (max_page_cnt <= (1UL << order))
+ break;
+ max_page_cnt -= 1UL << order;
+ page_cnt += 1UL << order;
+ if (mem->cnt >= max_segs) {
+ if (page_cnt < min_page_cnt)
+ goto out_free;
+ break;
+ }
+ }
+
+ return mem;
+
+out_free:
+ mmc_test_free_mem(mem);
+ return NULL;
+}
+
+/*
+ * Map memory into a scatterlist. Optionally allow the same memory to be
+ * mapped more than once.
+ */
+static int mmc_test_map_sg(struct mmc_test_mem *mem, unsigned long size,
+ struct scatterlist *sglist, int repeat,
+ unsigned int max_segs, unsigned int max_seg_sz,
+ unsigned int *sg_len, int min_sg_len)
+{
+ struct scatterlist *sg = NULL;
+ unsigned int i;
+ unsigned long sz = size;
+
+ sg_init_table(sglist, max_segs);
+ if (min_sg_len > max_segs)
+ min_sg_len = max_segs;
+
+ *sg_len = 0;
+ do {
+ for (i = 0; i < mem->cnt; i++) {
+ unsigned long len = PAGE_SIZE << mem->arr[i].order;
+
+ if (min_sg_len && (size / min_sg_len < len))
+ len = ALIGN(size / min_sg_len, 512);
+ if (len > sz)
+ len = sz;
+ if (len > max_seg_sz)
+ len = max_seg_sz;
+ if (sg)
+ sg = sg_next(sg);
+ else
+ sg = sglist;
+ if (!sg)
+ return -EINVAL;
+ sg_set_page(sg, mem->arr[i].page, len, 0);
+ sz -= len;
+ *sg_len += 1;
+ if (!sz)
+ break;
+ }
+ } while (sz && repeat);
+
+ if (sz)
+ return -EINVAL;
+
+ if (sg)
+ sg_mark_end(sg);
+
+ return 0;
+}
+
+/*
+ * Map memory into a scatterlist so that no pages are contiguous. Allow the
+ * same memory to be mapped more than once.
+ */
+static int mmc_test_map_sg_max_scatter(struct mmc_test_mem *mem,
+ unsigned long sz,
+ struct scatterlist *sglist,
+ unsigned int max_segs,
+ unsigned int max_seg_sz,
+ unsigned int *sg_len)
+{
+ struct scatterlist *sg = NULL;
+ unsigned int i = mem->cnt, cnt;
+ unsigned long len;
+ void *base, *addr, *last_addr = NULL;
+
+ sg_init_table(sglist, max_segs);
+
+ *sg_len = 0;
+ while (sz) {
+ base = page_address(mem->arr[--i].page);
+ cnt = 1 << mem->arr[i].order;
+ while (sz && cnt) {
+ addr = base + PAGE_SIZE * --cnt;
+ if (last_addr && last_addr + PAGE_SIZE == addr)
+ continue;
+ last_addr = addr;
+ len = PAGE_SIZE;
+ if (len > max_seg_sz)
+ len = max_seg_sz;
+ if (len > sz)
+ len = sz;
+ if (sg)
+ sg = sg_next(sg);
+ else
+ sg = sglist;
+ if (!sg)
+ return -EINVAL;
+ sg_set_page(sg, virt_to_page(addr), len, 0);
+ sz -= len;
+ *sg_len += 1;
+ }
+ if (i == 0)
+ i = mem->cnt;
+ }
+
+ if (sg)
+ sg_mark_end(sg);
+
+ return 0;
+}
+
+/*
+ * Calculate transfer rate in bytes per second.
+ */
+static unsigned int mmc_test_rate(uint64_t bytes, struct timespec *ts)
+{
+ uint64_t ns;
+
+ ns = ts->tv_sec;
+ ns *= 1000000000;
+ ns += ts->tv_nsec;
+
+ bytes *= 1000000000;
+
+ while (ns > UINT_MAX) {
+ bytes >>= 1;
+ ns >>= 1;
+ }
+
+ if (!ns)
+ return 0;
+
+ do_div(bytes, (uint32_t)ns);
+
+ return bytes;
+}
+
+/*
+ * Save transfer results for future usage
+ */
+static void mmc_test_save_transfer_result(struct mmc_test_card *test,
+ unsigned int count, unsigned int sectors, struct timespec ts,
+ unsigned int rate, unsigned int iops)
+{
+ struct mmc_test_transfer_result *tr;
+
+ if (!test->gr)
+ return;
+
+ tr = kmalloc(sizeof(struct mmc_test_transfer_result), GFP_KERNEL);
+ if (!tr)
+ return;
+
+ tr->count = count;
+ tr->sectors = sectors;
+ tr->ts = ts;
+ tr->rate = rate;
+ tr->iops = iops;
+
+ list_add_tail(&tr->link, &test->gr->tr_lst);
+}
+
+/*
+ * Print the transfer rate.
+ */
+static void mmc_test_print_rate(struct mmc_test_card *test, uint64_t bytes,
+ struct timespec *ts1, struct timespec *ts2)
+{
+ unsigned int rate, iops, sectors = bytes >> 9;
+ struct timespec ts;
+
+ ts = timespec_sub(*ts2, *ts1);
+
+ rate = mmc_test_rate(bytes, &ts);
+ iops = mmc_test_rate(100, &ts); /* I/O ops per sec x 100 */
+
+ printk(KERN_INFO "%s: Transfer of %u sectors (%u%s KiB) took %lu.%09lu "
+ "seconds (%u kB/s, %u KiB/s, %u.%02u IOPS)\n",
+ mmc_hostname(test->card->host), sectors, sectors >> 1,
+ (sectors & 1 ? ".5" : ""), (unsigned long)ts.tv_sec,
+ (unsigned long)ts.tv_nsec, rate / 1000, rate / 1024,
+ iops / 100, iops % 100);
+
+ mmc_test_save_transfer_result(test, 1, sectors, ts, rate, iops);
+}
+
+/*
+ * Print the average transfer rate.
+ */
+static void mmc_test_print_avg_rate(struct mmc_test_card *test, uint64_t bytes,
+ unsigned int count, struct timespec *ts1,
+ struct timespec *ts2)
+{
+ unsigned int rate, iops, sectors = bytes >> 9;
+ uint64_t tot = bytes * count;
+ struct timespec ts;
+
+ ts = timespec_sub(*ts2, *ts1);
+
+ rate = mmc_test_rate(tot, &ts);
+ iops = mmc_test_rate(count * 100, &ts); /* I/O ops per sec x 100 */
+
+ printk(KERN_INFO "%s: Transfer of %u x %u sectors (%u x %u%s KiB) took "
+ "%lu.%09lu seconds (%u kB/s, %u KiB/s, "
+ "%u.%02u IOPS, sg_len %d)\n",
+ mmc_hostname(test->card->host), count, sectors, count,
+ sectors >> 1, (sectors & 1 ? ".5" : ""),
+ (unsigned long)ts.tv_sec, (unsigned long)ts.tv_nsec,
+ rate / 1000, rate / 1024, iops / 100, iops % 100,
+ test->area.sg_len);
+
+ mmc_test_save_transfer_result(test, count, sectors, ts, rate, iops);
+}
+
+/*
+ * Return the card size in sectors.
+ */
+static unsigned int mmc_test_capacity(struct mmc_card *card)
+{
+ if (!mmc_card_sd(card) && mmc_card_blockaddr(card))
+ return card->ext_csd.sectors;
+ else
+ return card->csd.capacity << (card->csd.read_blkbits - 9);
+}
+
+/*******************************************************************/
+/* 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;
+
+ ret = mmc_test_set_blksize(test, 512);
+ if (ret)
+ return ret;
+
+ if (write)
+ memset(test->buffer, 0xDF, 512);
+ else {
+ for (i = 0;i < 512;i++)
+ test->buffer[i] = i;
+ }
+
+ for (i = 0;i < BUFFER_SIZE / 512;i++) {
+ ret = mmc_test_buffer_transfer(test, test->buffer, i, 512, 1);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+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)
+{
+ 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, 1);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+/*******************************************************************/
+/* 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)
+{
+ 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;
+ }
+}
+
+/*
+ * 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;
+
+ BUG_ON(!mrq || !mrq->cmd || !mrq->data);
+
+ ret = 0;
+
+ 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;
+}
+
+static int mmc_test_check_result_async(struct mmc_card *card,
+ struct mmc_async_req *areq)
+{
+ struct mmc_test_async_req *test_async =
+ container_of(areq, struct mmc_test_async_req, areq);
+
+ mmc_test_wait_busy(test_async->test);
+
+ return mmc_test_check_result(test_async->test, areq->mrq);
+}
+
+/*
+ * 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 nonblock transfer with certain parameters
+ */
+static void mmc_test_nonblock_reset(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;
+}
+static int mmc_test_nonblock_transfer(struct mmc_test_card *test,
+ struct scatterlist *sg, unsigned sg_len,
+ unsigned dev_addr, unsigned blocks,
+ unsigned blksz, int write, int count)
+{
+ struct mmc_request mrq1;
+ struct mmc_command cmd1;
+ struct mmc_command stop1;
+ struct mmc_data data1;
+
+ struct mmc_request mrq2;
+ struct mmc_command cmd2;
+ struct mmc_command stop2;
+ struct mmc_data data2;
+
+ struct mmc_test_async_req test_areq[2];
+ struct mmc_async_req *done_areq;
+ struct mmc_async_req *cur_areq = &test_areq[0].areq;
+ struct mmc_async_req *other_areq = &test_areq[1].areq;
+ int i;
+ int ret;
+
+ test_areq[0].test = test;
+ test_areq[1].test = test;
+
+ mmc_test_nonblock_reset(&mrq1, &cmd1, &stop1, &data1);
+ mmc_test_nonblock_reset(&mrq2, &cmd2, &stop2, &data2);
+
+ cur_areq->mrq = &mrq1;
+ cur_areq->err_check = mmc_test_check_result_async;
+ other_areq->mrq = &mrq2;
+ other_areq->err_check = mmc_test_check_result_async;
+
+ for (i = 0; i < count; i++) {
+ mmc_test_prepare_mrq(test, cur_areq->mrq, sg, sg_len, dev_addr,
+ blocks, blksz, write);
+ done_areq = mmc_start_req(test->card->host, cur_areq, &ret);
+
+ if (ret || (!done_areq && i > 0))
+ goto err;
+
+ if (done_areq) {
+ if (done_areq->mrq == &mrq2)
+ mmc_test_nonblock_reset(&mrq2, &cmd2,
+ &stop2, &data2);
+ else
+ mmc_test_nonblock_reset(&mrq1, &cmd1,
+ &stop1, &data1);
+ }
+ done_areq = cur_areq;
+ cur_areq = other_areq;
+ other_areq = done_areq;
+ dev_addr += blocks;
+ }
+
+ done_areq = mmc_start_req(test->card->host, NULL, &ret);
+
+ return ret;
+err:
+ 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 = {0};
+ struct mmc_command cmd = {0};
+ struct mmc_command stop = {0};
+ struct mmc_data data = {0};
+
+ 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 = {0};
+ struct mmc_command cmd = {0};
+ struct mmc_command stop = {0};
+ struct mmc_data data = {0};
+
+ struct scatterlist sg;
+
+ 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++)
+ 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_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;
+
+ sectors = (blocks * blksz + 511) / 512;
+ if ((sectors * 512) == (blocks * blksz))
+ sectors++;
+
+ if ((sectors * 512) > BUFFER_SIZE)
+ return -EINVAL;
+
+ memset(test->buffer, 0, sectors * 512);
+
+ for (i = 0;i < sectors;i++) {
+ ret = mmc_test_buffer_transfer(test,
+ test->buffer + i * 512,
+ dev_addr + i, 512, 0);
+ if (ret)
+ return ret;
+ }
+
+ for (i = 0;i < blocks * blksz;i++) {
+ if (test->buffer[i] != (u8)i)
+ return RESULT_FAIL;
+ }
+
+ for (;i < sectors * 512;i++) {
+ if (test->buffer[i] != 0xDF)
+ 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 (test->scratch[i] != (u8)i)
+ return RESULT_FAIL;
+ }
+ }
+
+ return 0;
+}
+
+/*******************************************************************/
+/* Tests */
+/*******************************************************************/
+
+struct mmc_test_case {
+ const char *name;
+
+ int (*prepare)(struct mmc_test_card *);
+ int (*run)(struct mmc_test_card *);
+ int (*cleanup)(struct mmc_test_card *);
+};
+
+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;
+
+ sg_init_one(&sg, test->buffer, 512);
+
+ ret = mmc_test_simple_transfer(test, &sg, 1, 0, 1, 512, 1);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+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;
+
+ sg_init_one(&sg, test->buffer, 512);
+
+ ret = mmc_test_simple_transfer(test, &sg, 1, 0, 1, 512, 0);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+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_transfer(test, &sg, 1, 0, 1, 512, 1);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+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_transfer(test, &sg, 1, 0, 1, 512, 0);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+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;
+
+ size = PAGE_SIZE * 2;
+ size = min(size, test->card->host->max_req_size);
+ size = min(size, test->card->host->max_seg_size);
+ size = min(size, test->card->host->max_blk_count * 512);
+
+ if (size < 1024)
+ return RESULT_UNSUP_HOST;
+
+ sg_init_one(&sg, test->buffer, size);
+
+ ret = mmc_test_transfer(test, &sg, 1, 0, size/512, 512, 1);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+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;
+
+ size = PAGE_SIZE * 2;
+ size = min(size, test->card->host->max_req_size);
+ size = min(size, test->card->host->max_seg_size);
+ size = min(size, test->card->host->max_blk_count * 512);
+
+ if (size < 1024)
+ return RESULT_UNSUP_HOST;
+
+ sg_init_one(&sg, test->buffer, size);
+
+ ret = mmc_test_transfer(test, &sg, 1, 0, size/512, 512, 0);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+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) {
+ sg_init_one(&sg, test->buffer, i);
+ ret = mmc_test_transfer(test, &sg, 1, 0, 1, i, 1);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+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) {
+ sg_init_one(&sg, test->buffer, i);
+ ret = mmc_test_transfer(test, &sg, 1, 0, 1, i, 0);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+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) {
+ sg_init_one(&sg, test->buffer, i);
+ ret = mmc_test_transfer(test, &sg, 1, 0, 1, i, 1);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+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) {
+ sg_init_one(&sg, test->buffer, i);
+ ret = mmc_test_transfer(test, &sg, 1, 0, 1, i, 0);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mmc_test_align_write(struct mmc_test_card *test)
+{
+ int ret, i;
+ struct scatterlist sg;
+
+ for (i = 1;i < 4;i++) {
+ sg_init_one(&sg, test->buffer + i, 512);
+ ret = mmc_test_transfer(test, &sg, 1, 0, 1, 512, 1);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mmc_test_align_read(struct mmc_test_card *test)
+{
+ int ret, i;
+ struct scatterlist sg;
+
+ for (i = 1;i < 4;i++) {
+ sg_init_one(&sg, test->buffer + i, 512);
+ ret = mmc_test_transfer(test, &sg, 1, 0, 1, 512, 0);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+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;
+
+ size = PAGE_SIZE * 2;
+ size = min(size, test->card->host->max_req_size);
+ size = min(size, test->card->host->max_seg_size);
+ size = min(size, test->card->host->max_blk_count * 512);
+
+ if (size < 1024)
+ return RESULT_UNSUP_HOST;
+
+ for (i = 1;i < 4;i++) {
+ sg_init_one(&sg, test->buffer + i, size);
+ ret = mmc_test_transfer(test, &sg, 1, 0, size/512, 512, 1);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+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;
+
+ size = PAGE_SIZE * 2;
+ size = min(size, test->card->host->max_req_size);
+ size = min(size, test->card->host->max_seg_size);
+ size = min(size, test->card->host->max_blk_count * 512);
+
+ if (size < 1024)
+ return RESULT_UNSUP_HOST;
+
+ for (i = 1;i < 4;i++) {
+ sg_init_one(&sg, test->buffer + i, size);
+ ret = mmc_test_transfer(test, &sg, 1, 0, size/512, 512, 0);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mmc_test_xfersize_write(struct mmc_test_card *test)
+{
+ int ret;
+
+ ret = mmc_test_set_blksize(test, 512);
+ if (ret)
+ return ret;
+
+ ret = mmc_test_broken_transfer(test, 1, 512, 1);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int mmc_test_xfersize_read(struct mmc_test_card *test)
+{
+ int ret;
+
+ ret = mmc_test_set_blksize(test, 512);
+ if (ret)
+ return ret;
+
+ ret = mmc_test_broken_transfer(test, 1, 512, 0);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int mmc_test_multi_xfersize_write(struct mmc_test_card *test)
+{
+ int ret;
+
+ if (test->card->host->max_blk_count == 1)
+ return RESULT_UNSUP_HOST;
+
+ ret = mmc_test_set_blksize(test, 512);
+ if (ret)
+ return ret;
+
+ ret = mmc_test_broken_transfer(test, 2, 512, 1);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int mmc_test_multi_xfersize_read(struct mmc_test_card *test)
+{
+ int ret;
+
+ if (test->card->host->max_blk_count == 1)
+ return RESULT_UNSUP_HOST;
+
+ ret = mmc_test_set_blksize(test, 512);
+ if (ret)
+ return ret;
+
+ ret = mmc_test_broken_transfer(test, 2, 512, 0);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+#ifdef CONFIG_HIGHMEM
+
+static int mmc_test_write_high(struct mmc_test_card *test)
+{
+ int ret;
+ struct scatterlist sg;
+
+ sg_init_table(&sg, 1);
+ sg_set_page(&sg, test->highmem, 512, 0);
+
+ ret = mmc_test_transfer(test, &sg, 1, 0, 1, 512, 1);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int mmc_test_read_high(struct mmc_test_card *test)
+{
+ int ret;
+ struct scatterlist sg;
+
+ sg_init_table(&sg, 1);
+ sg_set_page(&sg, test->highmem, 512, 0);
+
+ ret = mmc_test_transfer(test, &sg, 1, 0, 1, 512, 0);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int mmc_test_multi_write_high(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;
+
+ size = PAGE_SIZE * 2;
+ size = min(size, test->card->host->max_req_size);
+ size = min(size, test->card->host->max_seg_size);
+ size = min(size, test->card->host->max_blk_count * 512);
+
+ if (size < 1024)
+ return RESULT_UNSUP_HOST;
+
+ sg_init_table(&sg, 1);
+ sg_set_page(&sg, test->highmem, size, 0);
+
+ ret = mmc_test_transfer(test, &sg, 1, 0, size/512, 512, 1);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int mmc_test_multi_read_high(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;
+
+ size = PAGE_SIZE * 2;
+ size = min(size, test->card->host->max_req_size);
+ size = min(size, test->card->host->max_seg_size);
+ size = min(size, test->card->host->max_blk_count * 512);
+
+ if (size < 1024)
+ return RESULT_UNSUP_HOST;
+
+ sg_init_table(&sg, 1);
+ sg_set_page(&sg, test->highmem, size, 0);
+
+ ret = mmc_test_transfer(test, &sg, 1, 0, size/512, 512, 0);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+#else
+
+static int mmc_test_no_highmem(struct mmc_test_card *test)
+{
+ printk(KERN_INFO "%s: Highmem not configured - test skipped\n",
+ mmc_hostname(test->card->host));
+ return 0;
+}
+
+#endif /* CONFIG_HIGHMEM */
+
+/*
+ * Map sz bytes so that it can be transferred.
+ */
+static int mmc_test_area_map(struct mmc_test_card *test, unsigned long sz,
+ int max_scatter, int min_sg_len)
+{
+ struct mmc_test_area *t = &test->area;
+ int err;
+
+ t->blocks = sz >> 9;
+
+ if (max_scatter) {
+ err = mmc_test_map_sg_max_scatter(t->mem, sz, t->sg,
+ t->max_segs, t->max_seg_sz,
+ &t->sg_len);
+ } else {
+ err = mmc_test_map_sg(t->mem, sz, t->sg, 1, t->max_segs,
+ t->max_seg_sz, &t->sg_len, min_sg_len);
+ }
+ if (err)
+ printk(KERN_INFO "%s: Failed to map sg list\n",
+ mmc_hostname(test->card->host));
+ return err;
+}
+
+/*
+ * Transfer bytes mapped by mmc_test_area_map().
+ */
+static int mmc_test_area_transfer(struct mmc_test_card *test,
+ unsigned int dev_addr, int write)
+{
+ struct mmc_test_area *t = &test->area;
+
+ return mmc_test_simple_transfer(test, t->sg, t->sg_len, dev_addr,
+ t->blocks, 512, write);
+}
+
+/*
+ * Map and transfer bytes for multiple transfers.
+ */
+static int mmc_test_area_io_seq(struct mmc_test_card *test, unsigned long sz,
+ unsigned int dev_addr, int write,
+ int max_scatter, int timed, int count,
+ bool nonblock, int min_sg_len)
+{
+ struct timespec ts1, ts2;
+ int ret = 0;
+ int i;
+ struct mmc_test_area *t = &test->area;
+
+ /*
+ * In the case of a maximally scattered transfer, the maximum transfer
+ * size is further limited by using PAGE_SIZE segments.
+ */
+ if (max_scatter) {
+ struct mmc_test_area *t = &test->area;
+ unsigned long max_tfr;
+
+ if (t->max_seg_sz >= PAGE_SIZE)
+ max_tfr = t->max_segs * PAGE_SIZE;
+ else
+ max_tfr = t->max_segs * t->max_seg_sz;
+ if (sz > max_tfr)
+ sz = max_tfr;
+ }
+
+ ret = mmc_test_area_map(test, sz, max_scatter, min_sg_len);
+ if (ret)
+ return ret;
+
+ if (timed)
+ getnstimeofday(&ts1);
+ if (nonblock)
+ ret = mmc_test_nonblock_transfer(test, t->sg, t->sg_len,
+ dev_addr, t->blocks, 512, write, count);
+ else
+ for (i = 0; i < count && ret == 0; i++) {
+ ret = mmc_test_area_transfer(test, dev_addr, write);
+ dev_addr += sz >> 9;
+ }
+
+ if (ret)
+ return ret;
+
+ if (timed)
+ getnstimeofday(&ts2);
+
+ if (timed)
+ mmc_test_print_avg_rate(test, sz, count, &ts1, &ts2);
+
+ return 0;
+}
+
+static int mmc_test_area_io(struct mmc_test_card *test, unsigned long sz,
+ unsigned int dev_addr, int write, int max_scatter,
+ int timed)
+{
+ return mmc_test_area_io_seq(test, sz, dev_addr, write, max_scatter,
+ timed, 1, false, 0);
+}
+
+/*
+ * Write the test area entirely.
+ */
+static int mmc_test_area_fill(struct mmc_test_card *test)
+{
+ struct mmc_test_area *t = &test->area;
+
+ return mmc_test_area_io(test, t->max_tfr, t->dev_addr, 1, 0, 0);
+}
+
+/*
+ * Erase the test area entirely.
+ */
+static int mmc_test_area_erase(struct mmc_test_card *test)
+{
+ struct mmc_test_area *t = &test->area;
+
+ if (!mmc_can_erase(test->card))
+ return 0;
+
+ return mmc_erase(test->card, t->dev_addr, t->max_sz >> 9,
+ MMC_ERASE_ARG);
+}
+
+/*
+ * Cleanup struct mmc_test_area.
+ */
+static int mmc_test_area_cleanup(struct mmc_test_card *test)
+{
+ struct mmc_test_area *t = &test->area;
+
+ kfree(t->sg);
+ mmc_test_free_mem(t->mem);
+
+ return 0;
+}
+
+/*
+ * Initialize an area for testing large transfers. The test area is set to the
+ * middle of the card because cards may have different charateristics at the
+ * front (for FAT file system optimization). Optionally, the area is erased
+ * (if the card supports it) which may improve write performance. Optionally,
+ * the area is filled with data for subsequent read tests.
+ */
+static int mmc_test_area_init(struct mmc_test_card *test, int erase, int fill)
+{
+ struct mmc_test_area *t = &test->area;
+ unsigned long min_sz = 64 * 1024, sz;
+ int ret;
+
+ ret = mmc_test_set_blksize(test, 512);
+ if (ret)
+ return ret;
+
+ /* Make the test area size about 4MiB */
+ sz = (unsigned long)test->card->pref_erase << 9;
+ t->max_sz = sz;
+ while (t->max_sz < 4 * 1024 * 1024)
+ t->max_sz += sz;
+ while (t->max_sz > TEST_AREA_MAX_SIZE && t->max_sz > sz)
+ t->max_sz -= sz;
+
+ t->max_segs = test->card->host->max_segs;
+ t->max_seg_sz = test->card->host->max_seg_size;
+
+ t->max_tfr = t->max_sz;
+ if (t->max_tfr >> 9 > test->card->host->max_blk_count)
+ t->max_tfr = test->card->host->max_blk_count << 9;
+ if (t->max_tfr > test->card->host->max_req_size)
+ t->max_tfr = test->card->host->max_req_size;
+ if (t->max_tfr / t->max_seg_sz > t->max_segs)
+ t->max_tfr = t->max_segs * t->max_seg_sz;
+
+ /*
+ * Try to allocate enough memory for a max. sized transfer. Less is OK
+ * because the same memory can be mapped into the scatterlist more than
+ * once. Also, take into account the limits imposed on scatterlist
+ * segments by the host driver.
+ */
+ t->mem = mmc_test_alloc_mem(min_sz, t->max_tfr, t->max_segs,
+ t->max_seg_sz);
+ if (!t->mem)
+ return -ENOMEM;
+
+ t->sg = kmalloc(sizeof(struct scatterlist) * t->max_segs, GFP_KERNEL);
+ if (!t->sg) {
+ ret = -ENOMEM;
+ goto out_free;
+ }
+
+ t->dev_addr = mmc_test_capacity(test->card) / 2;
+ t->dev_addr -= t->dev_addr % (t->max_sz >> 9);
+
+ if (erase) {
+ ret = mmc_test_area_erase(test);
+ if (ret)
+ goto out_free;
+ }
+
+ if (fill) {
+ ret = mmc_test_area_fill(test);
+ if (ret)
+ goto out_free;
+ }
+
+ return 0;
+
+out_free:
+ mmc_test_area_cleanup(test);
+ return ret;
+}
+
+/*
+ * Prepare for large transfers. Do not erase the test area.
+ */
+static int mmc_test_area_prepare(struct mmc_test_card *test)
+{
+ return mmc_test_area_init(test, 0, 0);
+}
+
+/*
+ * Prepare for large transfers. Do erase the test area.
+ */
+static int mmc_test_area_prepare_erase(struct mmc_test_card *test)
+{
+ return mmc_test_area_init(test, 1, 0);
+}
+
+/*
+ * Prepare for large transfers. Erase and fill the test area.
+ */
+static int mmc_test_area_prepare_fill(struct mmc_test_card *test)
+{
+ return mmc_test_area_init(test, 1, 1);
+}
+
+/*
+ * Test best-case performance. Best-case performance is expected from
+ * a single large transfer.
+ *
+ * An additional option (max_scatter) allows the measurement of the same
+ * transfer but with no contiguous pages in the scatter list. This tests
+ * the efficiency of DMA to handle scattered pages.
+ */
+static int mmc_test_best_performance(struct mmc_test_card *test, int write,
+ int max_scatter)
+{
+ struct mmc_test_area *t = &test->area;
+
+ return mmc_test_area_io(test, t->max_tfr, t->dev_addr, write,
+ max_scatter, 1);
+}
+
+/*
+ * Best-case read performance.
+ */
+static int mmc_test_best_read_performance(struct mmc_test_card *test)
+{
+ return mmc_test_best_performance(test, 0, 0);
+}
+
+/*
+ * Best-case write performance.
+ */
+static int mmc_test_best_write_performance(struct mmc_test_card *test)
+{
+ return mmc_test_best_performance(test, 1, 0);
+}
+
+/*
+ * Best-case read performance into scattered pages.
+ */
+static int mmc_test_best_read_perf_max_scatter(struct mmc_test_card *test)
+{
+ return mmc_test_best_performance(test, 0, 1);
+}
+
+/*
+ * Best-case write performance from scattered pages.
+ */
+static int mmc_test_best_write_perf_max_scatter(struct mmc_test_card *test)
+{
+ return mmc_test_best_performance(test, 1, 1);
+}
+
+/*
+ * Single read performance by transfer size.
+ */
+static int mmc_test_profile_read_perf(struct mmc_test_card *test)
+{
+ struct mmc_test_area *t = &test->area;
+ unsigned long sz;
+ unsigned int dev_addr;
+ int ret;
+
+ for (sz = 512; sz < t->max_tfr; sz <<= 1) {
+ dev_addr = t->dev_addr + (sz >> 9);
+ ret = mmc_test_area_io(test, sz, dev_addr, 0, 0, 1);
+ if (ret)
+ return ret;
+ }
+ sz = t->max_tfr;
+ dev_addr = t->dev_addr;
+ return mmc_test_area_io(test, sz, dev_addr, 0, 0, 1);
+}
+
+/*
+ * Single write performance by transfer size.
+ */
+static int mmc_test_profile_write_perf(struct mmc_test_card *test)
+{
+ struct mmc_test_area *t = &test->area;
+ unsigned long sz;
+ unsigned int dev_addr;
+ int ret;
+
+ ret = mmc_test_area_erase(test);
+ if (ret)
+ return ret;
+ for (sz = 512; sz < t->max_tfr; sz <<= 1) {
+ dev_addr = t->dev_addr + (sz >> 9);
+ ret = mmc_test_area_io(test, sz, dev_addr, 1, 0, 1);
+ if (ret)
+ return ret;
+ }
+ ret = mmc_test_area_erase(test);
+ if (ret)
+ return ret;
+ sz = t->max_tfr;
+ dev_addr = t->dev_addr;
+ return mmc_test_area_io(test, sz, dev_addr, 1, 0, 1);
+}
+
+/*
+ * Single trim performance by transfer size.
+ */
+static int mmc_test_profile_trim_perf(struct mmc_test_card *test)
+{
+ struct mmc_test_area *t = &test->area;
+ unsigned long sz;
+ unsigned int dev_addr;
+ struct timespec ts1, ts2;
+ int ret;
+
+ if (!mmc_can_trim(test->card))
+ return RESULT_UNSUP_CARD;
+
+ if (!mmc_can_erase(test->card))
+ return RESULT_UNSUP_HOST;
+
+ for (sz = 512; sz < t->max_sz; sz <<= 1) {
+ dev_addr = t->dev_addr + (sz >> 9);
+ getnstimeofday(&ts1);
+ ret = mmc_erase(test->card, dev_addr, sz >> 9, MMC_TRIM_ARG);
+ if (ret)
+ return ret;
+ getnstimeofday(&ts2);
+ mmc_test_print_rate(test, sz, &ts1, &ts2);
+ }
+ dev_addr = t->dev_addr;
+ getnstimeofday(&ts1);
+ ret = mmc_erase(test->card, dev_addr, sz >> 9, MMC_TRIM_ARG);
+ if (ret)
+ return ret;
+ getnstimeofday(&ts2);
+ mmc_test_print_rate(test, sz, &ts1, &ts2);
+ return 0;
+}
+
+static int mmc_test_seq_read_perf(struct mmc_test_card *test, unsigned long sz)
+{
+ struct mmc_test_area *t = &test->area;
+ unsigned int dev_addr, i, cnt;
+ struct timespec ts1, ts2;
+ int ret;
+
+ cnt = t->max_sz / sz;
+ dev_addr = t->dev_addr;
+ getnstimeofday(&ts1);
+ for (i = 0; i < cnt; i++) {
+ ret = mmc_test_area_io(test, sz, dev_addr, 0, 0, 0);
+ if (ret)
+ return ret;
+ dev_addr += (sz >> 9);
+ }
+ getnstimeofday(&ts2);
+ mmc_test_print_avg_rate(test, sz, cnt, &ts1, &ts2);
+ return 0;
+}
+
+/*
+ * Consecutive read performance by transfer size.
+ */
+static int mmc_test_profile_seq_read_perf(struct mmc_test_card *test)
+{
+ struct mmc_test_area *t = &test->area;
+ unsigned long sz;
+ int ret;
+
+ for (sz = 512; sz < t->max_tfr; sz <<= 1) {
+ ret = mmc_test_seq_read_perf(test, sz);
+ if (ret)
+ return ret;
+ }
+ sz = t->max_tfr;
+ return mmc_test_seq_read_perf(test, sz);
+}
+
+static int mmc_test_seq_write_perf(struct mmc_test_card *test, unsigned long sz)
+{
+ struct mmc_test_area *t = &test->area;
+ unsigned int dev_addr, i, cnt;
+ struct timespec ts1, ts2;
+ int ret;
+
+ ret = mmc_test_area_erase(test);
+ if (ret)
+ return ret;
+ cnt = t->max_sz / sz;
+ dev_addr = t->dev_addr;
+ getnstimeofday(&ts1);
+ for (i = 0; i < cnt; i++) {
+ ret = mmc_test_area_io(test, sz, dev_addr, 1, 0, 0);
+ if (ret)
+ return ret;
+ dev_addr += (sz >> 9);
+ }
+ getnstimeofday(&ts2);
+ mmc_test_print_avg_rate(test, sz, cnt, &ts1, &ts2);
+ return 0;
+}
+
+/*
+ * Consecutive write performance by transfer size.
+ */
+static int mmc_test_profile_seq_write_perf(struct mmc_test_card *test)
+{
+ struct mmc_test_area *t = &test->area;
+ unsigned long sz;
+ int ret;
+
+ for (sz = 512; sz < t->max_tfr; sz <<= 1) {
+ ret = mmc_test_seq_write_perf(test, sz);
+ if (ret)
+ return ret;
+ }
+ sz = t->max_tfr;
+ return mmc_test_seq_write_perf(test, sz);
+}
+
+/*
+ * Consecutive trim performance by transfer size.
+ */
+static int mmc_test_profile_seq_trim_perf(struct mmc_test_card *test)
+{
+ struct mmc_test_area *t = &test->area;
+ unsigned long sz;
+ unsigned int dev_addr, i, cnt;
+ struct timespec ts1, ts2;
+ int ret;
+
+ if (!mmc_can_trim(test->card))
+ return RESULT_UNSUP_CARD;
+
+ if (!mmc_can_erase(test->card))
+ return RESULT_UNSUP_HOST;
+
+ for (sz = 512; sz <= t->max_sz; sz <<= 1) {
+ ret = mmc_test_area_erase(test);
+ if (ret)
+ return ret;
+ ret = mmc_test_area_fill(test);
+ if (ret)
+ return ret;
+ cnt = t->max_sz / sz;
+ dev_addr = t->dev_addr;
+ getnstimeofday(&ts1);
+ for (i = 0; i < cnt; i++) {
+ ret = mmc_erase(test->card, dev_addr, sz >> 9,
+ MMC_TRIM_ARG);
+ if (ret)
+ return ret;
+ dev_addr += (sz >> 9);
+ }
+ getnstimeofday(&ts2);
+ mmc_test_print_avg_rate(test, sz, cnt, &ts1, &ts2);
+ }
+ return 0;
+}
+
+static unsigned int rnd_next = 1;
+
+static unsigned int mmc_test_rnd_num(unsigned int rnd_cnt)
+{
+ uint64_t r;
+
+ rnd_next = rnd_next * 1103515245 + 12345;
+ r = (rnd_next >> 16) & 0x7fff;
+ return (r * rnd_cnt) >> 15;
+}
+
+static int mmc_test_rnd_perf(struct mmc_test_card *test, int write, int print,
+ unsigned long sz)
+{
+ unsigned int dev_addr, cnt, rnd_addr, range1, range2, last_ea = 0, ea;
+ unsigned int ssz;
+ struct timespec ts1, ts2, ts;
+ int ret;
+
+ ssz = sz >> 9;
+
+ rnd_addr = mmc_test_capacity(test->card) / 4;
+ range1 = rnd_addr / test->card->pref_erase;
+ range2 = range1 / ssz;
+
+ getnstimeofday(&ts1);
+ for (cnt = 0; cnt < UINT_MAX; cnt++) {
+ getnstimeofday(&ts2);
+ ts = timespec_sub(ts2, ts1);
+ if (ts.tv_sec >= 10)
+ break;
+ ea = mmc_test_rnd_num(range1);
+ if (ea == last_ea)
+ ea -= 1;
+ last_ea = ea;
+ dev_addr = rnd_addr + test->card->pref_erase * ea +
+ ssz * mmc_test_rnd_num(range2);
+ ret = mmc_test_area_io(test, sz, dev_addr, write, 0, 0);
+ if (ret)
+ return ret;
+ }
+ if (print)
+ mmc_test_print_avg_rate(test, sz, cnt, &ts1, &ts2);
+ return 0;
+}
+
+static int mmc_test_random_perf(struct mmc_test_card *test, int write)
+{
+ struct mmc_test_area *t = &test->area;
+ unsigned int next;
+ unsigned long sz;
+ int ret;
+
+ for (sz = 512; sz < t->max_tfr; sz <<= 1) {
+ /*
+ * When writing, try to get more consistent results by running
+ * the test twice with exactly the same I/O but outputting the
+ * results only for the 2nd run.
+ */
+ if (write) {
+ next = rnd_next;
+ ret = mmc_test_rnd_perf(test, write, 0, sz);
+ if (ret)
+ return ret;
+ rnd_next = next;
+ }
+ ret = mmc_test_rnd_perf(test, write, 1, sz);
+ if (ret)
+ return ret;
+ }
+ sz = t->max_tfr;
+ if (write) {
+ next = rnd_next;
+ ret = mmc_test_rnd_perf(test, write, 0, sz);
+ if (ret)
+ return ret;
+ rnd_next = next;
+ }
+ return mmc_test_rnd_perf(test, write, 1, sz);
+}
+
+/*
+ * Random read performance by transfer size.
+ */
+static int mmc_test_random_read_perf(struct mmc_test_card *test)
+{
+ return mmc_test_random_perf(test, 0);
+}
+
+/*
+ * Random write performance by transfer size.
+ */
+static int mmc_test_random_write_perf(struct mmc_test_card *test)
+{
+ return mmc_test_random_perf(test, 1);
+}
+
+static int mmc_test_seq_perf(struct mmc_test_card *test, int write,
+ unsigned int tot_sz, int max_scatter)
+{
+ struct mmc_test_area *t = &test->area;
+ unsigned int dev_addr, i, cnt, sz, ssz;
+ struct timespec ts1, ts2;
+ int ret;
+
+ sz = t->max_tfr;
+
+ /*
+ * In the case of a maximally scattered transfer, the maximum transfer
+ * size is further limited by using PAGE_SIZE segments.
+ */
+ if (max_scatter) {
+ unsigned long max_tfr;
+
+ if (t->max_seg_sz >= PAGE_SIZE)
+ max_tfr = t->max_segs * PAGE_SIZE;
+ else
+ max_tfr = t->max_segs * t->max_seg_sz;
+ if (sz > max_tfr)
+ sz = max_tfr;
+ }
+
+ ssz = sz >> 9;
+ dev_addr = mmc_test_capacity(test->card) / 4;
+ if (tot_sz > dev_addr << 9)
+ tot_sz = dev_addr << 9;
+ cnt = tot_sz / sz;
+ dev_addr &= 0xffff0000; /* Round to 64MiB boundary */
+
+ getnstimeofday(&ts1);
+ for (i = 0; i < cnt; i++) {
+ ret = mmc_test_area_io(test, sz, dev_addr, write,
+ max_scatter, 0);
+ if (ret)
+ return ret;
+ dev_addr += ssz;
+ }
+ getnstimeofday(&ts2);
+
+ mmc_test_print_avg_rate(test, sz, cnt, &ts1, &ts2);
+
+ return 0;
+}
+
+static int mmc_test_large_seq_perf(struct mmc_test_card *test, int write)
+{
+ int ret, i;
+
+ for (i = 0; i < 10; i++) {
+ ret = mmc_test_seq_perf(test, write, 10 * 1024 * 1024, 1);
+ if (ret)
+ return ret;
+ }
+ for (i = 0; i < 5; i++) {
+ ret = mmc_test_seq_perf(test, write, 100 * 1024 * 1024, 1);
+ if (ret)
+ return ret;
+ }
+ for (i = 0; i < 3; i++) {
+ ret = mmc_test_seq_perf(test, write, 1000 * 1024 * 1024, 1);
+ if (ret)
+ return ret;
+ }
+
+ return ret;
+}
+
+/*
+ * Large sequential read performance.
+ */
+static int mmc_test_large_seq_read_perf(struct mmc_test_card *test)
+{
+ return mmc_test_large_seq_perf(test, 0);
+}
+
+/*
+ * Large sequential write performance.
+ */
+static int mmc_test_large_seq_write_perf(struct mmc_test_card *test)
+{
+ return mmc_test_large_seq_perf(test, 1);
+}
+
+static int mmc_test_rw_multiple(struct mmc_test_card *test,
+ struct mmc_test_multiple_rw *tdata,
+ unsigned int reqsize, unsigned int size,
+ int min_sg_len)
+{
+ unsigned int dev_addr;
+ struct mmc_test_area *t = &test->area;
+ int ret = 0;
+
+ /* Set up test area */
+ if (size > mmc_test_capacity(test->card) / 2 * 512)
+ size = mmc_test_capacity(test->card) / 2 * 512;
+ if (reqsize > t->max_tfr)
+ reqsize = t->max_tfr;
+ dev_addr = mmc_test_capacity(test->card) / 4;
+ if ((dev_addr & 0xffff0000))
+ dev_addr &= 0xffff0000; /* Round to 64MiB boundary */
+ else
+ dev_addr &= 0xfffff800; /* Round to 1MiB boundary */
+ if (!dev_addr)
+ goto err;
+
+ if (reqsize > size)
+ return 0;
+
+ /* prepare test area */
+ if (mmc_can_erase(test->card) &&
+ tdata->prepare & MMC_TEST_PREP_ERASE) {
+ ret = mmc_erase(test->card, dev_addr,
+ size / 512, MMC_SECURE_ERASE_ARG);
+ if (ret)
+ ret = mmc_erase(test->card, dev_addr,
+ size / 512, MMC_ERASE_ARG);
+ if (ret)
+ goto err;
+ }
+
+ /* Run test */
+ ret = mmc_test_area_io_seq(test, reqsize, dev_addr,
+ tdata->do_write, 0, 1, size / reqsize,
+ tdata->do_nonblock_req, min_sg_len);
+ if (ret)
+ goto err;
+
+ return ret;
+ err:
+ printk(KERN_INFO "[%s] error\n", __func__);
+ return ret;
+}
+
+static int mmc_test_rw_multiple_size(struct mmc_test_card *test,
+ struct mmc_test_multiple_rw *rw)
+{
+ int ret = 0;
+ int i;
+ void *pre_req = test->card->host->ops->pre_req;
+ void *post_req = test->card->host->ops->post_req;
+
+ if (rw->do_nonblock_req &&
+ ((!pre_req && post_req) || (pre_req && !post_req))) {
+ printk(KERN_INFO "error: only one of pre/post is defined\n");
+ return -EINVAL;
+ }
+
+ for (i = 0 ; i < rw->len && ret == 0; i++) {
+ ret = mmc_test_rw_multiple(test, rw, rw->bs[i], rw->size, 0);
+ if (ret)
+ break;
+ }
+ return ret;
+}
+
+static int mmc_test_rw_multiple_sg_len(struct mmc_test_card *test,
+ struct mmc_test_multiple_rw *rw)
+{
+ int ret = 0;
+ int i;
+
+ for (i = 0 ; i < rw->len && ret == 0; i++) {
+ ret = mmc_test_rw_multiple(test, rw, 512*1024, rw->size,
+ rw->sg_len[i]);
+ if (ret)
+ break;
+ }
+ return ret;
+}
+
+/*
+ * Multiple blocking write 4k to 4 MB chunks
+ */
+static int mmc_test_profile_mult_write_blocking_perf(struct mmc_test_card *test)
+{
+ unsigned int bs[] = {1 << 12, 1 << 13, 1 << 14, 1 << 15, 1 << 16,
+ 1 << 17, 1 << 18, 1 << 19, 1 << 20, 1 << 22};
+ struct mmc_test_multiple_rw test_data = {
+ .bs = bs,
+ .size = TEST_AREA_MAX_SIZE,
+ .len = ARRAY_SIZE(bs),
+ .do_write = true,
+ .do_nonblock_req = false,
+ .prepare = MMC_TEST_PREP_ERASE,
+ };
+
+ return mmc_test_rw_multiple_size(test, &test_data);
+};
+
+/*
+ * Multiple non-blocking write 4k to 4 MB chunks
+ */
+static int mmc_test_profile_mult_write_nonblock_perf(struct mmc_test_card *test)
+{
+ unsigned int bs[] = {1 << 12, 1 << 13, 1 << 14, 1 << 15, 1 << 16,
+ 1 << 17, 1 << 18, 1 << 19, 1 << 20, 1 << 22};
+ struct mmc_test_multiple_rw test_data = {
+ .bs = bs,
+ .size = TEST_AREA_MAX_SIZE,
+ .len = ARRAY_SIZE(bs),
+ .do_write = true,
+ .do_nonblock_req = true,
+ .prepare = MMC_TEST_PREP_ERASE,
+ };
+
+ return mmc_test_rw_multiple_size(test, &test_data);
+}
+
+/*
+ * Multiple blocking read 4k to 4 MB chunks
+ */
+static int mmc_test_profile_mult_read_blocking_perf(struct mmc_test_card *test)
+{
+ unsigned int bs[] = {1 << 12, 1 << 13, 1 << 14, 1 << 15, 1 << 16,
+ 1 << 17, 1 << 18, 1 << 19, 1 << 20, 1 << 22};
+ struct mmc_test_multiple_rw test_data = {
+ .bs = bs,
+ .size = TEST_AREA_MAX_SIZE,
+ .len = ARRAY_SIZE(bs),
+ .do_write = false,
+ .do_nonblock_req = false,
+ .prepare = MMC_TEST_PREP_NONE,
+ };
+
+ return mmc_test_rw_multiple_size(test, &test_data);
+}
+
+/*
+ * Multiple non-blocking read 4k to 4 MB chunks
+ */
+static int mmc_test_profile_mult_read_nonblock_perf(struct mmc_test_card *test)
+{
+ unsigned int bs[] = {1 << 12, 1 << 13, 1 << 14, 1 << 15, 1 << 16,
+ 1 << 17, 1 << 18, 1 << 19, 1 << 20, 1 << 22};
+ struct mmc_test_multiple_rw test_data = {
+ .bs = bs,
+ .size = TEST_AREA_MAX_SIZE,
+ .len = ARRAY_SIZE(bs),
+ .do_write = false,
+ .do_nonblock_req = true,
+ .prepare = MMC_TEST_PREP_NONE,
+ };
+
+ return mmc_test_rw_multiple_size(test, &test_data);
+}
+
+/*
+ * Multiple blocking write 1 to 512 sg elements
+ */
+static int mmc_test_profile_sglen_wr_blocking_perf(struct mmc_test_card *test)
+{
+ unsigned int sg_len[] = {1, 1 << 3, 1 << 4, 1 << 5, 1 << 6,
+ 1 << 7, 1 << 8, 1 << 9};
+ struct mmc_test_multiple_rw test_data = {
+ .sg_len = sg_len,
+ .size = TEST_AREA_MAX_SIZE,
+ .len = ARRAY_SIZE(sg_len),
+ .do_write = true,
+ .do_nonblock_req = false,
+ .prepare = MMC_TEST_PREP_ERASE,
+ };
+
+ return mmc_test_rw_multiple_sg_len(test, &test_data);
+};
+
+/*
+ * Multiple non-blocking write 1 to 512 sg elements
+ */
+static int mmc_test_profile_sglen_wr_nonblock_perf(struct mmc_test_card *test)
+{
+ unsigned int sg_len[] = {1, 1 << 3, 1 << 4, 1 << 5, 1 << 6,
+ 1 << 7, 1 << 8, 1 << 9};
+ struct mmc_test_multiple_rw test_data = {
+ .sg_len = sg_len,
+ .size = TEST_AREA_MAX_SIZE,
+ .len = ARRAY_SIZE(sg_len),
+ .do_write = true,
+ .do_nonblock_req = true,
+ .prepare = MMC_TEST_PREP_ERASE,
+ };
+
+ return mmc_test_rw_multiple_sg_len(test, &test_data);
+}
+
+/*
+ * Multiple blocking read 1 to 512 sg elements
+ */
+static int mmc_test_profile_sglen_r_blocking_perf(struct mmc_test_card *test)
+{
+ unsigned int sg_len[] = {1, 1 << 3, 1 << 4, 1 << 5, 1 << 6,
+ 1 << 7, 1 << 8, 1 << 9};
+ struct mmc_test_multiple_rw test_data = {
+ .sg_len = sg_len,
+ .size = TEST_AREA_MAX_SIZE,
+ .len = ARRAY_SIZE(sg_len),
+ .do_write = false,
+ .do_nonblock_req = false,
+ .prepare = MMC_TEST_PREP_NONE,
+ };
+
+ return mmc_test_rw_multiple_sg_len(test, &test_data);
+}
+
+/*
+ * Multiple non-blocking read 1 to 512 sg elements
+ */
+static int mmc_test_profile_sglen_r_nonblock_perf(struct mmc_test_card *test)
+{
+ unsigned int sg_len[] = {1, 1 << 3, 1 << 4, 1 << 5, 1 << 6,
+ 1 << 7, 1 << 8, 1 << 9};
+ struct mmc_test_multiple_rw test_data = {
+ .sg_len = sg_len,
+ .size = TEST_AREA_MAX_SIZE,
+ .len = ARRAY_SIZE(sg_len),
+ .do_write = false,
+ .do_nonblock_req = true,
+ .prepare = MMC_TEST_PREP_NONE,
+ };
+
+ return mmc_test_rw_multiple_sg_len(test, &test_data);
+}
+
+static const struct mmc_test_case mmc_test_cases[] = {
+ {
+ .name = "Basic write (no data verification)",
+ .run = mmc_test_basic_write,
+ },
+
+ {
+ .name = "Basic read (no data verification)",
+ .run = mmc_test_basic_read,
+ },
+
+ {
+ .name = "Basic write (with data verification)",
+ .prepare = mmc_test_prepare_write,
+ .run = mmc_test_verify_write,
+ .cleanup = mmc_test_cleanup,
+ },
+
+ {
+ .name = "Basic read (with data verification)",
+ .prepare = mmc_test_prepare_read,
+ .run = mmc_test_verify_read,
+ .cleanup = mmc_test_cleanup,
+ },
+
+ {
+ .name = "Multi-block write",
+ .prepare = mmc_test_prepare_write,
+ .run = mmc_test_multi_write,
+ .cleanup = mmc_test_cleanup,
+ },
+
+ {
+ .name = "Multi-block read",
+ .prepare = mmc_test_prepare_read,
+ .run = mmc_test_multi_read,
+ .cleanup = mmc_test_cleanup,
+ },
+
+ {
+ .name = "Power of two block writes",
+ .prepare = mmc_test_prepare_write,
+ .run = mmc_test_pow2_write,
+ .cleanup = mmc_test_cleanup,
+ },
+
+ {
+ .name = "Power of two block reads",
+ .prepare = mmc_test_prepare_read,
+ .run = mmc_test_pow2_read,
+ .cleanup = mmc_test_cleanup,
+ },
+
+ {
+ .name = "Weird sized block writes",
+ .prepare = mmc_test_prepare_write,
+ .run = mmc_test_weird_write,
+ .cleanup = mmc_test_cleanup,
+ },
+
+ {
+ .name = "Weird sized block reads",
+ .prepare = mmc_test_prepare_read,
+ .run = mmc_test_weird_read,
+ .cleanup = mmc_test_cleanup,
+ },
+
+ {
+ .name = "Badly aligned write",
+ .prepare = mmc_test_prepare_write,
+ .run = mmc_test_align_write,
+ .cleanup = mmc_test_cleanup,
+ },
+
+ {
+ .name = "Badly aligned read",
+ .prepare = mmc_test_prepare_read,
+ .run = mmc_test_align_read,
+ .cleanup = mmc_test_cleanup,
+ },
+
+ {
+ .name = "Badly aligned multi-block write",
+ .prepare = mmc_test_prepare_write,
+ .run = mmc_test_align_multi_write,
+ .cleanup = mmc_test_cleanup,
+ },
+
+ {
+ .name = "Badly aligned multi-block read",
+ .prepare = mmc_test_prepare_read,
+ .run = mmc_test_align_multi_read,
+ .cleanup = mmc_test_cleanup,
+ },
+
+ {
+ .name = "Correct xfer_size at write (start failure)",
+ .run = mmc_test_xfersize_write,
+ },
+
+ {
+ .name = "Correct xfer_size at read (start failure)",
+ .run = mmc_test_xfersize_read,
+ },
+
+ {
+ .name = "Correct xfer_size at write (midway failure)",
+ .run = mmc_test_multi_xfersize_write,
+ },
+
+ {
+ .name = "Correct xfer_size at read (midway failure)",
+ .run = mmc_test_multi_xfersize_read,
+ },
+
+#ifdef CONFIG_HIGHMEM
+
+ {
+ .name = "Highmem write",
+ .prepare = mmc_test_prepare_write,
+ .run = mmc_test_write_high,
+ .cleanup = mmc_test_cleanup,
+ },
+
+ {
+ .name = "Highmem read",
+ .prepare = mmc_test_prepare_read,
+ .run = mmc_test_read_high,
+ .cleanup = mmc_test_cleanup,
+ },
+
+ {
+ .name = "Multi-block highmem write",
+ .prepare = mmc_test_prepare_write,
+ .run = mmc_test_multi_write_high,
+ .cleanup = mmc_test_cleanup,
+ },
+
+ {
+ .name = "Multi-block highmem read",
+ .prepare = mmc_test_prepare_read,
+ .run = mmc_test_multi_read_high,
+ .cleanup = mmc_test_cleanup,
+ },
+
+#else
+
+ {
+ .name = "Highmem write",
+ .run = mmc_test_no_highmem,
+ },
+
+ {
+ .name = "Highmem read",
+ .run = mmc_test_no_highmem,
+ },
+
+ {
+ .name = "Multi-block highmem write",
+ .run = mmc_test_no_highmem,
+ },
+
+ {
+ .name = "Multi-block highmem read",
+ .run = mmc_test_no_highmem,
+ },
+
+#endif /* CONFIG_HIGHMEM */
+
+ {
+ .name = "Best-case read performance",
+ .prepare = mmc_test_area_prepare_fill,
+ .run = mmc_test_best_read_performance,
+ .cleanup = mmc_test_area_cleanup,
+ },
+
+ {
+ .name = "Best-case write performance",
+ .prepare = mmc_test_area_prepare_erase,
+ .run = mmc_test_best_write_performance,
+ .cleanup = mmc_test_area_cleanup,
+ },
+
+ {
+ .name = "Best-case read performance into scattered pages",
+ .prepare = mmc_test_area_prepare_fill,
+ .run = mmc_test_best_read_perf_max_scatter,
+ .cleanup = mmc_test_area_cleanup,
+ },
+
+ {
+ .name = "Best-case write performance from scattered pages",
+ .prepare = mmc_test_area_prepare_erase,
+ .run = mmc_test_best_write_perf_max_scatter,
+ .cleanup = mmc_test_area_cleanup,
+ },
+
+ {
+ .name = "Single read performance by transfer size",
+ .prepare = mmc_test_area_prepare_fill,
+ .run = mmc_test_profile_read_perf,
+ .cleanup = mmc_test_area_cleanup,
+ },
+
+ {
+ .name = "Single write performance by transfer size",
+ .prepare = mmc_test_area_prepare,
+ .run = mmc_test_profile_write_perf,
+ .cleanup = mmc_test_area_cleanup,
+ },
+
+ {
+ .name = "Single trim performance by transfer size",
+ .prepare = mmc_test_area_prepare_fill,
+ .run = mmc_test_profile_trim_perf,
+ .cleanup = mmc_test_area_cleanup,
+ },
+
+ {
+ .name = "Consecutive read performance by transfer size",
+ .prepare = mmc_test_area_prepare_fill,
+ .run = mmc_test_profile_seq_read_perf,
+ .cleanup = mmc_test_area_cleanup,
+ },
+
+ {
+ .name = "Consecutive write performance by transfer size",
+ .prepare = mmc_test_area_prepare,
+ .run = mmc_test_profile_seq_write_perf,
+ .cleanup = mmc_test_area_cleanup,
+ },
+
+ {
+ .name = "Consecutive trim performance by transfer size",
+ .prepare = mmc_test_area_prepare,
+ .run = mmc_test_profile_seq_trim_perf,
+ .cleanup = mmc_test_area_cleanup,
+ },
+
+ {
+ .name = "Random read performance by transfer size",
+ .prepare = mmc_test_area_prepare,
+ .run = mmc_test_random_read_perf,
+ .cleanup = mmc_test_area_cleanup,
+ },
+
+ {
+ .name = "Random write performance by transfer size",
+ .prepare = mmc_test_area_prepare,
+ .run = mmc_test_random_write_perf,
+ .cleanup = mmc_test_area_cleanup,
+ },
+
+ {
+ .name = "Large sequential read into scattered pages",
+ .prepare = mmc_test_area_prepare,
+ .run = mmc_test_large_seq_read_perf,
+ .cleanup = mmc_test_area_cleanup,
+ },
+
+ {
+ .name = "Large sequential write from scattered pages",
+ .prepare = mmc_test_area_prepare,
+ .run = mmc_test_large_seq_write_perf,
+ .cleanup = mmc_test_area_cleanup,
+ },
+
+ {
+ .name = "Write performance with blocking req 4k to 4MB",
+ .prepare = mmc_test_area_prepare,
+ .run = mmc_test_profile_mult_write_blocking_perf,
+ .cleanup = mmc_test_area_cleanup,
+ },
+
+ {
+ .name = "Write performance with non-blocking req 4k to 4MB",
+ .prepare = mmc_test_area_prepare,
+ .run = mmc_test_profile_mult_write_nonblock_perf,
+ .cleanup = mmc_test_area_cleanup,
+ },
+
+ {
+ .name = "Read performance with blocking req 4k to 4MB",
+ .prepare = mmc_test_area_prepare,
+ .run = mmc_test_profile_mult_read_blocking_perf,
+ .cleanup = mmc_test_area_cleanup,
+ },
+
+ {
+ .name = "Read performance with non-blocking req 4k to 4MB",
+ .prepare = mmc_test_area_prepare,
+ .run = mmc_test_profile_mult_read_nonblock_perf,
+ .cleanup = mmc_test_area_cleanup,
+ },
+
+ {
+ .name = "Write performance blocking req 1 to 512 sg elems",
+ .prepare = mmc_test_area_prepare,
+ .run = mmc_test_profile_sglen_wr_blocking_perf,
+ .cleanup = mmc_test_area_cleanup,
+ },
+
+ {
+ .name = "Write performance non-blocking req 1 to 512 sg elems",
+ .prepare = mmc_test_area_prepare,
+ .run = mmc_test_profile_sglen_wr_nonblock_perf,
+ .cleanup = mmc_test_area_cleanup,
+ },
+
+ {
+ .name = "Read performance blocking req 1 to 512 sg elems",
+ .prepare = mmc_test_area_prepare,
+ .run = mmc_test_profile_sglen_r_blocking_perf,
+ .cleanup = mmc_test_area_cleanup,
+ },
+
+ {
+ .name = "Read performance non-blocking req 1 to 512 sg elems",
+ .prepare = mmc_test_area_prepare,
+ .run = mmc_test_profile_sglen_r_nonblock_perf,
+ .cleanup = mmc_test_area_cleanup,
+ },
+};
+
+static DEFINE_MUTEX(mmc_test_lock);
+
+static LIST_HEAD(mmc_test_result);
+
+static void mmc_test_run(struct mmc_test_card *test, int testcase)
+{
+ int i, ret;
+
+ printk(KERN_INFO "%s: Starting tests of card %s...\n",
+ mmc_hostname(test->card->host), mmc_card_id(test->card));
+
+ mmc_claim_host(test->card->host);
+
+ for (i = 0;i < ARRAY_SIZE(mmc_test_cases);i++) {
+ struct mmc_test_general_result *gr;
+
+ 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);
+
+ if (mmc_test_cases[i].prepare) {
+ ret = mmc_test_cases[i].prepare(test);
+ if (ret) {
+ printk(KERN_INFO "%s: Result: Prepare "
+ "stage failed! (%d)\n",
+ mmc_hostname(test->card->host),
+ ret);
+ continue;
+ }
+ }
+
+ gr = kzalloc(sizeof(struct mmc_test_general_result),
+ GFP_KERNEL);
+ if (gr) {
+ INIT_LIST_HEAD(&gr->tr_lst);
+
+ /* Assign data what we know already */
+ gr->card = test->card;
+ gr->testcase = i;
+
+ /* Append container to global one */
+ list_add_tail(&gr->link, &mmc_test_result);
+
+ /*
+ * Save the pointer to created container in our private
+ * structure.
+ */
+ test->gr = gr;
+ }
+
+ ret = mmc_test_cases[i].run(test);
+ switch (ret) {
+ case RESULT_OK:
+ printk(KERN_INFO "%s: Result: OK\n",
+ mmc_hostname(test->card->host));
+ break;
+ case RESULT_FAIL:
+ printk(KERN_INFO "%s: Result: FAILED\n",
+ mmc_hostname(test->card->host));
+ break;
+ case RESULT_UNSUP_HOST:
+ printk(KERN_INFO "%s: Result: UNSUPPORTED "
+ "(by host)\n",
+ mmc_hostname(test->card->host));
+ break;
+ case RESULT_UNSUP_CARD:
+ printk(KERN_INFO "%s: Result: UNSUPPORTED "
+ "(by card)\n",
+ mmc_hostname(test->card->host));
+ break;
+ default:
+ printk(KERN_INFO "%s: Result: ERROR (%d)\n",
+ mmc_hostname(test->card->host), ret);
+ }
+
+ /* Save the result */
+ if (gr)
+ gr->result = ret;
+
+ if (mmc_test_cases[i].cleanup) {
+ ret = mmc_test_cases[i].cleanup(test);
+ if (ret) {
+ printk(KERN_INFO "%s: Warning: Cleanup "
+ "stage failed! (%d)\n",
+ mmc_hostname(test->card->host),
+ ret);
+ }
+ }
+ }
+
+ mmc_release_host(test->card->host);
+
+ printk(KERN_INFO "%s: Tests completed.\n",
+ mmc_hostname(test->card->host));
+}
+
+static void mmc_test_free_result(struct mmc_card *card)
+{
+ struct mmc_test_general_result *gr, *grs;
+
+ mutex_lock(&mmc_test_lock);
+
+ list_for_each_entry_safe(gr, grs, &mmc_test_result, link) {
+ struct mmc_test_transfer_result *tr, *trs;
+
+ if (card && gr->card != card)
+ continue;
+
+ list_for_each_entry_safe(tr, trs, &gr->tr_lst, link) {
+ list_del(&tr->link);
+ kfree(tr);
+ }
+
+ list_del(&gr->link);
+ kfree(gr);
+ }
+
+ mutex_unlock(&mmc_test_lock);
+}
+
+static LIST_HEAD(mmc_test_file_test);
+
+static int mtf_test_show(struct seq_file *sf, void *data)
+{
+ struct mmc_card *card = (struct mmc_card *)sf->private;
+ struct mmc_test_general_result *gr;
+
+ mutex_lock(&mmc_test_lock);
+
+ list_for_each_entry(gr, &mmc_test_result, link) {
+ struct mmc_test_transfer_result *tr;
+
+ if (gr->card != card)
+ continue;
+
+ seq_printf(sf, "Test %d: %d\n", gr->testcase + 1, gr->result);
+
+ list_for_each_entry(tr, &gr->tr_lst, link) {
+ seq_printf(sf, "%u %d %lu.%09lu %u %u.%02u\n",
+ tr->count, tr->sectors,
+ (unsigned long)tr->ts.tv_sec,
+ (unsigned long)tr->ts.tv_nsec,
+ tr->rate, tr->iops / 100, tr->iops % 100);
+ }
+ }
+
+ mutex_unlock(&mmc_test_lock);
+
+ return 0;
+}
+
+static int mtf_test_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, mtf_test_show, inode->i_private);
+}
+
+static ssize_t mtf_test_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct seq_file *sf = (struct seq_file *)file->private_data;
+ struct mmc_card *card = (struct mmc_card *)sf->private;
+ struct mmc_test_card *test;
+ char lbuf[12];
+ long testcase;
+
+ if (count >= sizeof(lbuf))
+ return -EINVAL;
+
+ if (copy_from_user(lbuf, buf, count))
+ return -EFAULT;
+ lbuf[count] = '\0';
+
+ if (strict_strtol(lbuf, 10, &testcase))
+ return -EINVAL;
+
+ test = kzalloc(sizeof(struct mmc_test_card), GFP_KERNEL);
+ if (!test)
+ return -ENOMEM;
+
+ /*
+ * Remove all test cases associated with given card. Thus we have only
+ * actual data of the last run.
+ */
+ mmc_test_free_result(card);
+
+ test->card = card;
+
+ test->buffer = kzalloc(BUFFER_SIZE, GFP_KERNEL);
+#ifdef CONFIG_HIGHMEM
+ test->highmem = alloc_pages(GFP_KERNEL | __GFP_HIGHMEM, BUFFER_ORDER);
+#endif
+
+#ifdef CONFIG_HIGHMEM
+ if (test->buffer && test->highmem) {
+#else
+ if (test->buffer) {
+#endif
+ mutex_lock(&mmc_test_lock);
+ mmc_test_run(test, testcase);
+ mutex_unlock(&mmc_test_lock);
+ }
+
+#ifdef CONFIG_HIGHMEM
+ __free_pages(test->highmem, BUFFER_ORDER);
+#endif
+ kfree(test->buffer);
+ kfree(test);
+
+ return count;
+}
+
+static const struct file_operations mmc_test_fops_test = {
+ .open = mtf_test_open,
+ .read = seq_read,
+ .write = mtf_test_write,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int mtf_testlist_show(struct seq_file *sf, void *data)
+{
+ int i;
+
+ mutex_lock(&mmc_test_lock);
+
+ for (i = 0; i < ARRAY_SIZE(mmc_test_cases); i++)
+ seq_printf(sf, "%d:\t%s\n", i+1, mmc_test_cases[i].name);
+
+ mutex_unlock(&mmc_test_lock);
+
+ return 0;
+}
+
+static int mtf_testlist_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, mtf_testlist_show, inode->i_private);
+}
+
+static const struct file_operations mmc_test_fops_testlist = {
+ .open = mtf_testlist_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void mmc_test_free_file_test(struct mmc_card *card)
+{
+ struct mmc_test_dbgfs_file *df, *dfs;
+
+ mutex_lock(&mmc_test_lock);
+
+ list_for_each_entry_safe(df, dfs, &mmc_test_file_test, link) {
+ if (card && df->card != card)
+ continue;
+ debugfs_remove(df->file);
+ list_del(&df->link);
+ kfree(df);
+ }
+
+ mutex_unlock(&mmc_test_lock);
+}
+
+static int mmc_test_register_file_test(struct mmc_card *card)
+{
+ struct dentry *file = NULL;
+ struct mmc_test_dbgfs_file *df;
+ int ret = 0;
+
+ mutex_lock(&mmc_test_lock);
+
+ if (card->debugfs_root)
+ file = debugfs_create_file("test", S_IWUSR | S_IRUGO,
+ card->debugfs_root, card, &mmc_test_fops_test);
+
+ if (IS_ERR_OR_NULL(file)) {
+ dev_err(&card->dev,
+ "Can't create test. Perhaps debugfs is disabled.\n");
+ ret = -ENODEV;
+ goto err;
+ }
+
+ if (card->debugfs_root)
+ file = debugfs_create_file("testlist", S_IRUGO,
+ card->debugfs_root, card, &mmc_test_fops_testlist);
+
+ if (IS_ERR_OR_NULL(file)) {
+ dev_err(&card->dev,
+ "Can't create testlist. Perhaps debugfs is disabled.\n");
+ ret = -ENODEV;
+ goto err;
+ }
+
+ df = kmalloc(sizeof(struct mmc_test_dbgfs_file), GFP_KERNEL);
+ if (!df) {
+ debugfs_remove(file);
+ dev_err(&card->dev,
+ "Can't allocate memory for internal usage.\n");
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ df->card = card;
+ df->file = file;
+
+ list_add(&df->link, &mmc_test_file_test);
+
+err:
+ mutex_unlock(&mmc_test_lock);
+
+ return ret;
+}
+
+static int mmc_test_probe(struct mmc_card *card)
+{
+ int ret;
+
+ if (!mmc_card_mmc(card) && !mmc_card_sd(card))
+ return -ENODEV;
+
+ ret = mmc_test_register_file_test(card);
+ if (ret)
+ return ret;
+
+ dev_info(&card->dev, "Card claimed for testing.\n");
+
+ return 0;
+}
+
+static void mmc_test_remove(struct mmc_card *card)
+{
+ mmc_test_free_result(card);
+ mmc_test_free_file_test(card);
+}
+
+static struct mmc_driver mmc_driver = {
+ .drv = {
+ .name = "mmc_test",
+ },
+ .probe = mmc_test_probe,
+ .remove = mmc_test_remove,
+};
+
+static int __init mmc_test_init(void)
+{
+ return mmc_register_driver(&mmc_driver);
+}
+
+static void __exit mmc_test_exit(void)
+{
+ /* Clear stalled data if card is still plugged */
+ mmc_test_free_result(NULL);
+ mmc_test_free_file_test(NULL);
+
+ mmc_unregister_driver(&mmc_driver);
+}
+
+module_init(mmc_test_init);
+module_exit(mmc_test_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Multimedia Card (MMC) host test driver");
+MODULE_AUTHOR("Pierre Ossman");
diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c
new file mode 100644
index 00000000..de1aaf98
--- /dev/null
+++ b/drivers/mmc/card/queue.c
@@ -0,0 +1,433 @@
+/*
+ * linux/drivers/mmc/card/queue.c
+ *
+ * Copyright (C) 2003 Russell King, All Rights Reserved.
+ * Copyright 2006-2007 Pierre Ossman
+ *
+ * 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/slab.h>
+#include <linux/module.h>
+#include <linux/blkdev.h>
+#include <linux/freezer.h>
+#include <linux/kthread.h>
+#include <linux/scatterlist.h>
+
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+#include "queue.h"
+
+#define MMC_QUEUE_BOUNCESZ (512 * 1024)
+
+#define MMC_QUEUE_SUSPENDED (1 << 0)
+
+/*
+ * Prepare a MMC request. This just filters out odd stuff.
+ */
+static int mmc_prep_request(struct request_queue *q, struct request *req)
+{
+ /*
+ * We only like normal block requests and discards.
+ */
+ if (req->cmd_type != REQ_TYPE_FS && !(req->cmd_flags & REQ_DISCARD)) {
+ blk_dump_rq_flags(req, "MMC bad request");
+ return BLKPREP_KILL;
+ }
+
+ req->cmd_flags |= REQ_DONTPREP;
+
+ return BLKPREP_OK;
+}
+
+static int mmc_queue_thread(void *d)
+{
+ struct mmc_queue *mq = d;
+ struct request_queue *q = mq->queue;
+
+ current->flags |= PF_MEMALLOC;
+
+ down(&mq->thread_sem);
+ do {
+ struct request *req = NULL;
+ struct mmc_queue_req *tmp;
+
+ spin_lock_irq(q->queue_lock);
+ set_current_state(TASK_INTERRUPTIBLE);
+ req = blk_fetch_request(q);
+ mq->mqrq_cur->req = req;
+ spin_unlock_irq(q->queue_lock);
+
+ if (req || mq->mqrq_prev->req) {
+ set_current_state(TASK_RUNNING);
+ mq->issue_fn(mq, req);
+ } else {
+ if (kthread_should_stop()) {
+ set_current_state(TASK_RUNNING);
+ break;
+ }
+ up(&mq->thread_sem);
+ schedule();
+ down(&mq->thread_sem);
+ }
+
+ /* Current request becomes previous request and vice versa. */
+ mq->mqrq_prev->brq.mrq.data = NULL;
+ mq->mqrq_prev->req = NULL;
+ tmp = mq->mqrq_prev;
+ mq->mqrq_prev = mq->mqrq_cur;
+ mq->mqrq_cur = tmp;
+ } while (1);
+ up(&mq->thread_sem);
+
+ return 0;
+}
+
+/*
+ * Generic MMC request handler. This is called for any queue on a
+ * particular host. When the host is not busy, we look for a request
+ * on any queue on this host, and attempt to issue it. This may
+ * not be the queue we were asked to process.
+ */
+static void mmc_request(struct request_queue *q)
+{
+ struct mmc_queue *mq = q->queuedata;
+ struct request *req;
+
+ if (!mq) {
+ while ((req = blk_fetch_request(q)) != NULL) {
+ req->cmd_flags |= REQ_QUIET;
+ __blk_end_request_all(req, -EIO);
+ }
+ return;
+ }
+
+ if (!mq->mqrq_cur->req && !mq->mqrq_prev->req)
+ wake_up_process(mq->thread);
+}
+
+struct scatterlist *mmc_alloc_sg(int sg_len, int *err)
+{
+ struct scatterlist *sg;
+
+ sg = kmalloc(sizeof(struct scatterlist)*sg_len, GFP_KERNEL);
+ if (!sg)
+ *err = -ENOMEM;
+ else {
+ *err = 0;
+ sg_init_table(sg, sg_len);
+ }
+
+ return sg;
+}
+
+static void mmc_queue_setup_discard(struct request_queue *q,
+ struct mmc_card *card)
+{
+ unsigned max_discard;
+
+ max_discard = mmc_calc_max_discard(card);
+ if (!max_discard)
+ return;
+
+ queue_flag_set_unlocked(QUEUE_FLAG_DISCARD, q);
+ q->limits.max_discard_sectors = max_discard;
+ if (card->erased_byte == 0)
+ q->limits.discard_zeroes_data = 1;
+ q->limits.discard_granularity = card->pref_erase << 9;
+ /* granularity must not be greater than max. discard */
+ if (card->pref_erase > max_discard)
+ q->limits.discard_granularity = 0;
+ if (mmc_can_secure_erase_trim(card))
+ queue_flag_set_unlocked(QUEUE_FLAG_SECDISCARD, q);
+}
+
+/**
+ * mmc_init_queue - initialise a queue structure.
+ * @mq: mmc queue
+ * @card: mmc card to attach this queue
+ * @lock: queue lock
+ * @subname: partition subname
+ *
+ * Initialise a MMC card request queue.
+ */
+int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card,
+ spinlock_t *lock, const char *subname)
+{
+ struct mmc_host *host = card->host;
+ u64 limit = BLK_BOUNCE_HIGH;
+ int ret;
+ struct mmc_queue_req *mqrq_cur = &mq->mqrq[0];
+ struct mmc_queue_req *mqrq_prev = &mq->mqrq[1];
+
+ if (mmc_dev(host)->dma_mask && *mmc_dev(host)->dma_mask)
+ limit = *mmc_dev(host)->dma_mask;
+
+ mq->card = card;
+ mq->queue = blk_init_queue(mmc_request, lock);
+ if (!mq->queue)
+ return -ENOMEM;
+
+ memset(&mq->mqrq_cur, 0, sizeof(mq->mqrq_cur));
+ memset(&mq->mqrq_prev, 0, sizeof(mq->mqrq_prev));
+ mq->mqrq_cur = mqrq_cur;
+ mq->mqrq_prev = mqrq_prev;
+ mq->queue->queuedata = mq;
+
+ blk_queue_prep_rq(mq->queue, mmc_prep_request);
+ queue_flag_set_unlocked(QUEUE_FLAG_NONROT, mq->queue);
+ if (mmc_can_erase(card))
+ mmc_queue_setup_discard(mq->queue, card);
+
+#ifdef CONFIG_MMC_BLOCK_BOUNCE
+ if (host->max_segs == 1) {
+ unsigned int bouncesz;
+
+ bouncesz = MMC_QUEUE_BOUNCESZ;
+
+ if (bouncesz > host->max_req_size)
+ bouncesz = host->max_req_size;
+ if (bouncesz > host->max_seg_size)
+ bouncesz = host->max_seg_size;
+ if (bouncesz > (host->max_blk_count * 512))
+ bouncesz = host->max_blk_count * 512;
+
+ if (bouncesz > 512) {
+ mqrq_cur->bounce_buf = kmalloc(bouncesz, GFP_KERNEL);
+ if (!mqrq_cur->bounce_buf) {
+ printk(KERN_WARNING "%s: unable to "
+ "allocate bounce cur buffer\n",
+ mmc_card_name(card));
+ }
+ mqrq_prev->bounce_buf = kmalloc(bouncesz, GFP_KERNEL);
+ if (!mqrq_prev->bounce_buf) {
+ printk(KERN_WARNING "%s: unable to "
+ "allocate bounce prev buffer\n",
+ mmc_card_name(card));
+ kfree(mqrq_cur->bounce_buf);
+ mqrq_cur->bounce_buf = NULL;
+ }
+ }
+
+ if (mqrq_cur->bounce_buf && mqrq_prev->bounce_buf) {
+ blk_queue_bounce_limit(mq->queue, BLK_BOUNCE_ANY);
+ blk_queue_max_hw_sectors(mq->queue, bouncesz / 512);
+ blk_queue_max_segments(mq->queue, bouncesz / 512);
+ blk_queue_max_segment_size(mq->queue, bouncesz);
+
+ mqrq_cur->sg = mmc_alloc_sg(1, &ret);
+ if (ret)
+ goto cleanup_queue;
+
+ mqrq_cur->bounce_sg =
+ mmc_alloc_sg(bouncesz / 512, &ret);
+ if (ret)
+ goto cleanup_queue;
+
+ mqrq_prev->sg = mmc_alloc_sg(1, &ret);
+ if (ret)
+ goto cleanup_queue;
+
+ mqrq_prev->bounce_sg =
+ mmc_alloc_sg(bouncesz / 512, &ret);
+ if (ret)
+ goto cleanup_queue;
+ }
+ }
+#endif
+
+ if (!mqrq_cur->bounce_buf && !mqrq_prev->bounce_buf) {
+ blk_queue_bounce_limit(mq->queue, limit);
+ blk_queue_max_hw_sectors(mq->queue,
+ min(host->max_blk_count, host->max_req_size / 512));
+ blk_queue_max_segments(mq->queue, host->max_segs);
+ blk_queue_max_segment_size(mq->queue, host->max_seg_size);
+
+ mqrq_cur->sg = mmc_alloc_sg(host->max_segs, &ret);
+ if (ret)
+ goto cleanup_queue;
+
+
+ mqrq_prev->sg = mmc_alloc_sg(host->max_segs, &ret);
+ if (ret)
+ goto cleanup_queue;
+ }
+
+ sema_init(&mq->thread_sem, 1);
+
+ mq->thread = kthread_run(mmc_queue_thread, mq, "mmcqd/%d%s",
+ host->index, subname ? subname : "");
+
+ if (IS_ERR(mq->thread)) {
+ ret = PTR_ERR(mq->thread);
+ goto free_bounce_sg;
+ }
+
+ return 0;
+ free_bounce_sg:
+ kfree(mqrq_cur->bounce_sg);
+ mqrq_cur->bounce_sg = NULL;
+ kfree(mqrq_prev->bounce_sg);
+ mqrq_prev->bounce_sg = NULL;
+
+ cleanup_queue:
+ kfree(mqrq_cur->sg);
+ mqrq_cur->sg = NULL;
+ kfree(mqrq_cur->bounce_buf);
+ mqrq_cur->bounce_buf = NULL;
+
+ kfree(mqrq_prev->sg);
+ mqrq_prev->sg = NULL;
+ kfree(mqrq_prev->bounce_buf);
+ mqrq_prev->bounce_buf = NULL;
+
+ blk_cleanup_queue(mq->queue);
+ return ret;
+}
+
+void mmc_cleanup_queue(struct mmc_queue *mq)
+{
+ struct request_queue *q = mq->queue;
+ unsigned long flags;
+ struct mmc_queue_req *mqrq_cur = mq->mqrq_cur;
+ struct mmc_queue_req *mqrq_prev = mq->mqrq_prev;
+
+ /* Make sure the queue isn't suspended, as that will deadlock */
+ mmc_queue_resume(mq);
+
+ /* Then terminate our worker thread */
+ kthread_stop(mq->thread);
+
+ /* Empty the queue */
+ spin_lock_irqsave(q->queue_lock, flags);
+ q->queuedata = NULL;
+ blk_start_queue(q);
+ spin_unlock_irqrestore(q->queue_lock, flags);
+
+ kfree(mqrq_cur->bounce_sg);
+ mqrq_cur->bounce_sg = NULL;
+
+ kfree(mqrq_cur->sg);
+ mqrq_cur->sg = NULL;
+
+ kfree(mqrq_cur->bounce_buf);
+ mqrq_cur->bounce_buf = NULL;
+
+ kfree(mqrq_prev->bounce_sg);
+ mqrq_prev->bounce_sg = NULL;
+
+ kfree(mqrq_prev->sg);
+ mqrq_prev->sg = NULL;
+
+ kfree(mqrq_prev->bounce_buf);
+ mqrq_prev->bounce_buf = NULL;
+
+ mq->card = NULL;
+}
+EXPORT_SYMBOL(mmc_cleanup_queue);
+
+/**
+ * mmc_queue_suspend - suspend a MMC request queue
+ * @mq: MMC queue to suspend
+ *
+ * Stop the block request queue, and wait for our thread to
+ * complete any outstanding requests. This ensures that we
+ * won't suspend while a request is being processed.
+ */
+void mmc_queue_suspend(struct mmc_queue *mq)
+{
+ struct request_queue *q = mq->queue;
+ unsigned long flags;
+
+ if (!(mq->flags & MMC_QUEUE_SUSPENDED)) {
+ mq->flags |= MMC_QUEUE_SUSPENDED;
+
+ spin_lock_irqsave(q->queue_lock, flags);
+ blk_stop_queue(q);
+ spin_unlock_irqrestore(q->queue_lock, flags);
+
+ down(&mq->thread_sem);
+ }
+}
+
+/**
+ * mmc_queue_resume - resume a previously suspended MMC request queue
+ * @mq: MMC queue to resume
+ */
+void mmc_queue_resume(struct mmc_queue *mq)
+{
+ struct request_queue *q = mq->queue;
+ unsigned long flags;
+
+ if (mq->flags & MMC_QUEUE_SUSPENDED) {
+ mq->flags &= ~MMC_QUEUE_SUSPENDED;
+
+ up(&mq->thread_sem);
+
+ spin_lock_irqsave(q->queue_lock, flags);
+ blk_start_queue(q);
+ spin_unlock_irqrestore(q->queue_lock, flags);
+ }
+}
+
+/*
+ * Prepare the sg list(s) to be handed of to the host driver
+ */
+unsigned int mmc_queue_map_sg(struct mmc_queue *mq, struct mmc_queue_req *mqrq)
+{
+ unsigned int sg_len;
+ size_t buflen;
+ struct scatterlist *sg;
+ int i;
+
+ if (!mqrq->bounce_buf)
+ return blk_rq_map_sg(mq->queue, mqrq->req, mqrq->sg);
+
+ BUG_ON(!mqrq->bounce_sg);
+
+ sg_len = blk_rq_map_sg(mq->queue, mqrq->req, mqrq->bounce_sg);
+
+ mqrq->bounce_sg_len = sg_len;
+
+ buflen = 0;
+ for_each_sg(mqrq->bounce_sg, sg, sg_len, i)
+ buflen += sg->length;
+
+ sg_init_one(mqrq->sg, mqrq->bounce_buf, buflen);
+
+ return 1;
+}
+
+/*
+ * If writing, bounce the data to the buffer before the request
+ * is sent to the host driver
+ */
+void mmc_queue_bounce_pre(struct mmc_queue_req *mqrq)
+{
+ if (!mqrq->bounce_buf)
+ return;
+
+ if (rq_data_dir(mqrq->req) != WRITE)
+ return;
+
+ sg_copy_to_buffer(mqrq->bounce_sg, mqrq->bounce_sg_len,
+ mqrq->bounce_buf, mqrq->sg[0].length);
+}
+
+/*
+ * If reading, bounce the data from the buffer after the request
+ * has been handled by the host driver
+ */
+void mmc_queue_bounce_post(struct mmc_queue_req *mqrq)
+{
+ if (!mqrq->bounce_buf)
+ return;
+
+ if (rq_data_dir(mqrq->req) != READ)
+ return;
+
+ sg_copy_from_buffer(mqrq->bounce_sg, mqrq->bounce_sg_len,
+ mqrq->bounce_buf, mqrq->sg[0].length);
+}
diff --git a/drivers/mmc/card/queue.h b/drivers/mmc/card/queue.h
new file mode 100644
index 00000000..d2a1eb4b
--- /dev/null
+++ b/drivers/mmc/card/queue.h
@@ -0,0 +1,49 @@
+#ifndef MMC_QUEUE_H
+#define MMC_QUEUE_H
+
+struct request;
+struct task_struct;
+
+struct mmc_blk_request {
+ struct mmc_request mrq;
+ struct mmc_command sbc;
+ struct mmc_command cmd;
+ struct mmc_command stop;
+ struct mmc_data data;
+};
+
+struct mmc_queue_req {
+ struct request *req;
+ struct mmc_blk_request brq;
+ struct scatterlist *sg;
+ char *bounce_buf;
+ struct scatterlist *bounce_sg;
+ unsigned int bounce_sg_len;
+ struct mmc_async_req mmc_active;
+};
+
+struct mmc_queue {
+ struct mmc_card *card;
+ struct task_struct *thread;
+ struct semaphore thread_sem;
+ unsigned int flags;
+ int (*issue_fn)(struct mmc_queue *, struct request *);
+ void *data;
+ struct request_queue *queue;
+ struct mmc_queue_req mqrq[2];
+ struct mmc_queue_req *mqrq_cur;
+ struct mmc_queue_req *mqrq_prev;
+};
+
+extern int mmc_init_queue(struct mmc_queue *, struct mmc_card *, spinlock_t *,
+ const char *);
+extern void mmc_cleanup_queue(struct mmc_queue *);
+extern void mmc_queue_suspend(struct mmc_queue *);
+extern void mmc_queue_resume(struct mmc_queue *);
+
+extern unsigned int mmc_queue_map_sg(struct mmc_queue *,
+ struct mmc_queue_req *);
+extern void mmc_queue_bounce_pre(struct mmc_queue_req *);
+extern void mmc_queue_bounce_post(struct mmc_queue_req *);
+
+#endif
diff --git a/drivers/mmc/card/sdio_uart.c b/drivers/mmc/card/sdio_uart.c
new file mode 100644
index 00000000..c8c9edb3
--- /dev/null
+++ b/drivers/mmc/card/sdio_uart.c
@@ -0,0 +1,1223 @@
+/*
+ * linux/drivers/mmc/card/sdio_uart.c - SDIO UART/GPS driver
+ *
+ * Based on drivers/serial/8250.c and drivers/serial/serial_core.c
+ * by Russell King.
+ *
+ * Author: Nicolas Pitre
+ * Created: June 15, 2007
+ * Copyright: MontaVista Software, Inc.
+ *
+ * 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.
+ */
+
+/*
+ * Note: Although this driver assumes a 16550A-like UART implementation,
+ * it is not possible to leverage the common 8250/16550 driver, nor the
+ * core UART infrastructure, as they assumes direct access to the hardware
+ * registers, often under a spinlock. This is not possible in the SDIO
+ * context as SDIO access functions must be able to sleep.
+ *
+ * Because we need to lock the SDIO host to ensure an exclusive access to
+ * the card, we simply rely on that lock to also prevent and serialize
+ * concurrent access to the same port.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/mutex.h>
+#include <linux/seq_file.h>
+#include <linux/serial_reg.h>
+#include <linux/circ_buf.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/kfifo.h>
+#include <linux/slab.h>
+
+#include <linux/mmc/core.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/sdio_ids.h>
+
+
+#define UART_NR 8 /* Number of UARTs this driver can handle */
+
+
+#define FIFO_SIZE PAGE_SIZE
+#define WAKEUP_CHARS 256
+
+struct uart_icount {
+ __u32 cts;
+ __u32 dsr;
+ __u32 rng;
+ __u32 dcd;
+ __u32 rx;
+ __u32 tx;
+ __u32 frame;
+ __u32 overrun;
+ __u32 parity;
+ __u32 brk;
+};
+
+struct sdio_uart_port {
+ struct tty_port port;
+ struct kref kref;
+ struct tty_struct *tty;
+ unsigned int index;
+ struct sdio_func *func;
+ struct mutex func_lock;
+ struct task_struct *in_sdio_uart_irq;
+ unsigned int regs_offset;
+ struct kfifo xmit_fifo;
+ spinlock_t write_lock;
+ struct uart_icount icount;
+ unsigned int uartclk;
+ unsigned int mctrl;
+ unsigned int rx_mctrl;
+ unsigned int read_status_mask;
+ unsigned int ignore_status_mask;
+ unsigned char x_char;
+ unsigned char ier;
+ unsigned char lcr;
+};
+
+static struct sdio_uart_port *sdio_uart_table[UART_NR];
+static DEFINE_SPINLOCK(sdio_uart_table_lock);
+
+static int sdio_uart_add_port(struct sdio_uart_port *port)
+{
+ int index, ret = -EBUSY;
+
+ kref_init(&port->kref);
+ mutex_init(&port->func_lock);
+ spin_lock_init(&port->write_lock);
+ if (kfifo_alloc(&port->xmit_fifo, FIFO_SIZE, GFP_KERNEL))
+ return -ENOMEM;
+
+ spin_lock(&sdio_uart_table_lock);
+ for (index = 0; index < UART_NR; index++) {
+ if (!sdio_uart_table[index]) {
+ port->index = index;
+ sdio_uart_table[index] = port;
+ ret = 0;
+ break;
+ }
+ }
+ spin_unlock(&sdio_uart_table_lock);
+
+ return ret;
+}
+
+static struct sdio_uart_port *sdio_uart_port_get(unsigned index)
+{
+ struct sdio_uart_port *port;
+
+ if (index >= UART_NR)
+ return NULL;
+
+ spin_lock(&sdio_uart_table_lock);
+ port = sdio_uart_table[index];
+ if (port)
+ kref_get(&port->kref);
+ spin_unlock(&sdio_uart_table_lock);
+
+ return port;
+}
+
+static void sdio_uart_port_destroy(struct kref *kref)
+{
+ struct sdio_uart_port *port =
+ container_of(kref, struct sdio_uart_port, kref);
+ kfifo_free(&port->xmit_fifo);
+ kfree(port);
+}
+
+static void sdio_uart_port_put(struct sdio_uart_port *port)
+{
+ kref_put(&port->kref, sdio_uart_port_destroy);
+}
+
+static void sdio_uart_port_remove(struct sdio_uart_port *port)
+{
+ struct sdio_func *func;
+ struct tty_struct *tty;
+
+ BUG_ON(sdio_uart_table[port->index] != port);
+
+ spin_lock(&sdio_uart_table_lock);
+ sdio_uart_table[port->index] = NULL;
+ spin_unlock(&sdio_uart_table_lock);
+
+ /*
+ * We're killing a port that potentially still is in use by
+ * the tty layer. Be careful to prevent any further access
+ * to the SDIO function and arrange for the tty layer to
+ * give up on that port ASAP.
+ * Beware: the lock ordering is critical.
+ */
+ mutex_lock(&port->port.mutex);
+ mutex_lock(&port->func_lock);
+ func = port->func;
+ sdio_claim_host(func);
+ port->func = NULL;
+ mutex_unlock(&port->func_lock);
+ tty = tty_port_tty_get(&port->port);
+ /* tty_hangup is async so is this safe as is ?? */
+ if (tty) {
+ tty_hangup(tty);
+ tty_kref_put(tty);
+ }
+ mutex_unlock(&port->port.mutex);
+ sdio_release_irq(func);
+ sdio_disable_func(func);
+ sdio_release_host(func);
+
+ sdio_uart_port_put(port);
+}
+
+static int sdio_uart_claim_func(struct sdio_uart_port *port)
+{
+ mutex_lock(&port->func_lock);
+ if (unlikely(!port->func)) {
+ mutex_unlock(&port->func_lock);
+ return -ENODEV;
+ }
+ if (likely(port->in_sdio_uart_irq != current))
+ sdio_claim_host(port->func);
+ mutex_unlock(&port->func_lock);
+ return 0;
+}
+
+static inline void sdio_uart_release_func(struct sdio_uart_port *port)
+{
+ if (likely(port->in_sdio_uart_irq != current))
+ sdio_release_host(port->func);
+}
+
+static inline unsigned int sdio_in(struct sdio_uart_port *port, int offset)
+{
+ unsigned char c;
+ c = sdio_readb(port->func, port->regs_offset + offset, NULL);
+ return c;
+}
+
+static inline void sdio_out(struct sdio_uart_port *port, int offset, int value)
+{
+ sdio_writeb(port->func, value, port->regs_offset + offset, NULL);
+}
+
+static unsigned int sdio_uart_get_mctrl(struct sdio_uart_port *port)
+{
+ unsigned char status;
+ unsigned int ret;
+
+ /* FIXME: What stops this losing the delta bits and breaking
+ sdio_uart_check_modem_status ? */
+ status = sdio_in(port, UART_MSR);
+
+ ret = 0;
+ if (status & UART_MSR_DCD)
+ ret |= TIOCM_CAR;
+ if (status & UART_MSR_RI)
+ ret |= TIOCM_RNG;
+ if (status & UART_MSR_DSR)
+ ret |= TIOCM_DSR;
+ if (status & UART_MSR_CTS)
+ ret |= TIOCM_CTS;
+ return ret;
+}
+
+static void sdio_uart_write_mctrl(struct sdio_uart_port *port,
+ unsigned int mctrl)
+{
+ unsigned char mcr = 0;
+
+ if (mctrl & TIOCM_RTS)
+ mcr |= UART_MCR_RTS;
+ if (mctrl & TIOCM_DTR)
+ mcr |= UART_MCR_DTR;
+ if (mctrl & TIOCM_OUT1)
+ mcr |= UART_MCR_OUT1;
+ if (mctrl & TIOCM_OUT2)
+ mcr |= UART_MCR_OUT2;
+ if (mctrl & TIOCM_LOOP)
+ mcr |= UART_MCR_LOOP;
+
+ sdio_out(port, UART_MCR, mcr);
+}
+
+static inline void sdio_uart_update_mctrl(struct sdio_uart_port *port,
+ unsigned int set, unsigned int clear)
+{
+ unsigned int old;
+
+ old = port->mctrl;
+ port->mctrl = (old & ~clear) | set;
+ if (old != port->mctrl)
+ sdio_uart_write_mctrl(port, port->mctrl);
+}
+
+#define sdio_uart_set_mctrl(port, x) sdio_uart_update_mctrl(port, x, 0)
+#define sdio_uart_clear_mctrl(port, x) sdio_uart_update_mctrl(port, 0, x)
+
+static void sdio_uart_change_speed(struct sdio_uart_port *port,
+ struct ktermios *termios,
+ struct ktermios *old)
+{
+ unsigned char cval, fcr = 0;
+ unsigned int baud, quot;
+
+ switch (termios->c_cflag & CSIZE) {
+ case CS5:
+ cval = UART_LCR_WLEN5;
+ break;
+ case CS6:
+ cval = UART_LCR_WLEN6;
+ break;
+ case CS7:
+ cval = UART_LCR_WLEN7;
+ break;
+ default:
+ case CS8:
+ cval = UART_LCR_WLEN8;
+ break;
+ }
+
+ if (termios->c_cflag & CSTOPB)
+ cval |= UART_LCR_STOP;
+ if (termios->c_cflag & PARENB)
+ cval |= UART_LCR_PARITY;
+ if (!(termios->c_cflag & PARODD))
+ cval |= UART_LCR_EPAR;
+
+ for (;;) {
+ baud = tty_termios_baud_rate(termios);
+ if (baud == 0)
+ baud = 9600; /* Special case: B0 rate. */
+ if (baud <= port->uartclk)
+ break;
+ /*
+ * Oops, the quotient was zero. Try again with the old
+ * baud rate if possible, otherwise default to 9600.
+ */
+ termios->c_cflag &= ~CBAUD;
+ if (old) {
+ termios->c_cflag |= old->c_cflag & CBAUD;
+ old = NULL;
+ } else
+ termios->c_cflag |= B9600;
+ }
+ quot = (2 * port->uartclk + baud) / (2 * baud);
+
+ if (baud < 2400)
+ fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_1;
+ else
+ fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_10;
+
+ port->read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR;
+ if (termios->c_iflag & INPCK)
+ port->read_status_mask |= UART_LSR_FE | UART_LSR_PE;
+ if (termios->c_iflag & (BRKINT | PARMRK))
+ port->read_status_mask |= UART_LSR_BI;
+
+ /*
+ * Characters to ignore
+ */
+ port->ignore_status_mask = 0;
+ if (termios->c_iflag & IGNPAR)
+ port->ignore_status_mask |= UART_LSR_PE | UART_LSR_FE;
+ if (termios->c_iflag & IGNBRK) {
+ port->ignore_status_mask |= UART_LSR_BI;
+ /*
+ * If we're ignoring parity and break indicators,
+ * ignore overruns too (for real raw support).
+ */
+ if (termios->c_iflag & IGNPAR)
+ port->ignore_status_mask |= UART_LSR_OE;
+ }
+
+ /*
+ * ignore all characters if CREAD is not set
+ */
+ if ((termios->c_cflag & CREAD) == 0)
+ port->ignore_status_mask |= UART_LSR_DR;
+
+ /*
+ * CTS flow control flag and modem status interrupts
+ */
+ port->ier &= ~UART_IER_MSI;
+ if ((termios->c_cflag & CRTSCTS) || !(termios->c_cflag & CLOCAL))
+ port->ier |= UART_IER_MSI;
+
+ port->lcr = cval;
+
+ sdio_out(port, UART_IER, port->ier);
+ sdio_out(port, UART_LCR, cval | UART_LCR_DLAB);
+ sdio_out(port, UART_DLL, quot & 0xff);
+ sdio_out(port, UART_DLM, quot >> 8);
+ sdio_out(port, UART_LCR, cval);
+ sdio_out(port, UART_FCR, fcr);
+
+ sdio_uart_write_mctrl(port, port->mctrl);
+}
+
+static void sdio_uart_start_tx(struct sdio_uart_port *port)
+{
+ if (!(port->ier & UART_IER_THRI)) {
+ port->ier |= UART_IER_THRI;
+ sdio_out(port, UART_IER, port->ier);
+ }
+}
+
+static void sdio_uart_stop_tx(struct sdio_uart_port *port)
+{
+ if (port->ier & UART_IER_THRI) {
+ port->ier &= ~UART_IER_THRI;
+ sdio_out(port, UART_IER, port->ier);
+ }
+}
+
+static void sdio_uart_stop_rx(struct sdio_uart_port *port)
+{
+ port->ier &= ~UART_IER_RLSI;
+ port->read_status_mask &= ~UART_LSR_DR;
+ sdio_out(port, UART_IER, port->ier);
+}
+
+static void sdio_uart_receive_chars(struct sdio_uart_port *port,
+ unsigned int *status)
+{
+ struct tty_struct *tty = tty_port_tty_get(&port->port);
+ unsigned int ch, flag;
+ int max_count = 256;
+
+ do {
+ ch = sdio_in(port, UART_RX);
+ flag = TTY_NORMAL;
+ port->icount.rx++;
+
+ if (unlikely(*status & (UART_LSR_BI | UART_LSR_PE |
+ UART_LSR_FE | UART_LSR_OE))) {
+ /*
+ * For statistics only
+ */
+ if (*status & UART_LSR_BI) {
+ *status &= ~(UART_LSR_FE | UART_LSR_PE);
+ port->icount.brk++;
+ } else if (*status & UART_LSR_PE)
+ port->icount.parity++;
+ else if (*status & UART_LSR_FE)
+ port->icount.frame++;
+ if (*status & UART_LSR_OE)
+ port->icount.overrun++;
+
+ /*
+ * Mask off conditions which should be ignored.
+ */
+ *status &= port->read_status_mask;
+ if (*status & UART_LSR_BI)
+ flag = TTY_BREAK;
+ else if (*status & UART_LSR_PE)
+ flag = TTY_PARITY;
+ else if (*status & UART_LSR_FE)
+ flag = TTY_FRAME;
+ }
+
+ if ((*status & port->ignore_status_mask & ~UART_LSR_OE) == 0)
+ if (tty)
+ tty_insert_flip_char(tty, ch, flag);
+
+ /*
+ * Overrun is special. Since it's reported immediately,
+ * it doesn't affect the current character.
+ */
+ if (*status & ~port->ignore_status_mask & UART_LSR_OE)
+ if (tty)
+ tty_insert_flip_char(tty, 0, TTY_OVERRUN);
+
+ *status = sdio_in(port, UART_LSR);
+ } while ((*status & UART_LSR_DR) && (max_count-- > 0));
+ if (tty) {
+ tty_flip_buffer_push(tty);
+ tty_kref_put(tty);
+ }
+}
+
+static void sdio_uart_transmit_chars(struct sdio_uart_port *port)
+{
+ struct kfifo *xmit = &port->xmit_fifo;
+ int count;
+ struct tty_struct *tty;
+ u8 iobuf[16];
+ int len;
+
+ if (port->x_char) {
+ sdio_out(port, UART_TX, port->x_char);
+ port->icount.tx++;
+ port->x_char = 0;
+ return;
+ }
+
+ tty = tty_port_tty_get(&port->port);
+
+ if (tty == NULL || !kfifo_len(xmit) ||
+ tty->stopped || tty->hw_stopped) {
+ sdio_uart_stop_tx(port);
+ tty_kref_put(tty);
+ return;
+ }
+
+ len = kfifo_out_locked(xmit, iobuf, 16, &port->write_lock);
+ for (count = 0; count < len; count++) {
+ sdio_out(port, UART_TX, iobuf[count]);
+ port->icount.tx++;
+ }
+
+ len = kfifo_len(xmit);
+ if (len < WAKEUP_CHARS) {
+ tty_wakeup(tty);
+ if (len == 0)
+ sdio_uart_stop_tx(port);
+ }
+ tty_kref_put(tty);
+}
+
+static void sdio_uart_check_modem_status(struct sdio_uart_port *port)
+{
+ int status;
+ struct tty_struct *tty;
+
+ status = sdio_in(port, UART_MSR);
+
+ if ((status & UART_MSR_ANY_DELTA) == 0)
+ return;
+
+ if (status & UART_MSR_TERI)
+ port->icount.rng++;
+ if (status & UART_MSR_DDSR)
+ port->icount.dsr++;
+ if (status & UART_MSR_DDCD) {
+ port->icount.dcd++;
+ /* DCD raise - wake for open */
+ if (status & UART_MSR_DCD)
+ wake_up_interruptible(&port->port.open_wait);
+ else {
+ /* DCD drop - hang up if tty attached */
+ tty = tty_port_tty_get(&port->port);
+ if (tty) {
+ tty_hangup(tty);
+ tty_kref_put(tty);
+ }
+ }
+ }
+ if (status & UART_MSR_DCTS) {
+ port->icount.cts++;
+ tty = tty_port_tty_get(&port->port);
+ if (tty && (tty->termios->c_cflag & CRTSCTS)) {
+ int cts = (status & UART_MSR_CTS);
+ if (tty->hw_stopped) {
+ if (cts) {
+ tty->hw_stopped = 0;
+ sdio_uart_start_tx(port);
+ tty_wakeup(tty);
+ }
+ } else {
+ if (!cts) {
+ tty->hw_stopped = 1;
+ sdio_uart_stop_tx(port);
+ }
+ }
+ }
+ tty_kref_put(tty);
+ }
+}
+
+/*
+ * This handles the interrupt from one port.
+ */
+static void sdio_uart_irq(struct sdio_func *func)
+{
+ struct sdio_uart_port *port = sdio_get_drvdata(func);
+ unsigned int iir, lsr;
+
+ /*
+ * In a few places sdio_uart_irq() is called directly instead of
+ * waiting for the actual interrupt to be raised and the SDIO IRQ
+ * thread scheduled in order to reduce latency. However, some
+ * interaction with the tty core may end up calling us back
+ * (serial echo, flow control, etc.) through those same places
+ * causing undesirable effects. Let's stop the recursion here.
+ */
+ if (unlikely(port->in_sdio_uart_irq == current))
+ return;
+
+ iir = sdio_in(port, UART_IIR);
+ if (iir & UART_IIR_NO_INT)
+ return;
+
+ port->in_sdio_uart_irq = current;
+ lsr = sdio_in(port, UART_LSR);
+ if (lsr & UART_LSR_DR)
+ sdio_uart_receive_chars(port, &lsr);
+ sdio_uart_check_modem_status(port);
+ if (lsr & UART_LSR_THRE)
+ sdio_uart_transmit_chars(port);
+ port->in_sdio_uart_irq = NULL;
+}
+
+static int uart_carrier_raised(struct tty_port *tport)
+{
+ struct sdio_uart_port *port =
+ container_of(tport, struct sdio_uart_port, port);
+ unsigned int ret = sdio_uart_claim_func(port);
+ if (ret) /* Missing hardware shouldn't block for carrier */
+ return 1;
+ ret = sdio_uart_get_mctrl(port);
+ sdio_uart_release_func(port);
+ if (ret & TIOCM_CAR)
+ return 1;
+ return 0;
+}
+
+/**
+ * uart_dtr_rts - port helper to set uart signals
+ * @tport: tty port to be updated
+ * @onoff: set to turn on DTR/RTS
+ *
+ * Called by the tty port helpers when the modem signals need to be
+ * adjusted during an open, close and hangup.
+ */
+
+static void uart_dtr_rts(struct tty_port *tport, int onoff)
+{
+ struct sdio_uart_port *port =
+ container_of(tport, struct sdio_uart_port, port);
+ int ret = sdio_uart_claim_func(port);
+ if (ret)
+ return;
+ if (onoff == 0)
+ sdio_uart_clear_mctrl(port, TIOCM_DTR | TIOCM_RTS);
+ else
+ sdio_uart_set_mctrl(port, TIOCM_DTR | TIOCM_RTS);
+ sdio_uart_release_func(port);
+}
+
+/**
+ * sdio_uart_activate - start up hardware
+ * @tport: tty port to activate
+ * @tty: tty bound to this port
+ *
+ * Activate a tty port. The port locking guarantees us this will be
+ * run exactly once per set of opens, and if successful will see the
+ * shutdown method run exactly once to match. Start up and shutdown are
+ * protected from each other by the internal locking and will not run
+ * at the same time even during a hangup event.
+ *
+ * If we successfully start up the port we take an extra kref as we
+ * will keep it around until shutdown when the kref is dropped.
+ */
+
+static int sdio_uart_activate(struct tty_port *tport, struct tty_struct *tty)
+{
+ struct sdio_uart_port *port =
+ container_of(tport, struct sdio_uart_port, port);
+ int ret;
+
+ /*
+ * Set the TTY IO error marker - we will only clear this
+ * once we have successfully opened the port.
+ */
+ set_bit(TTY_IO_ERROR, &tty->flags);
+
+ kfifo_reset(&port->xmit_fifo);
+
+ ret = sdio_uart_claim_func(port);
+ if (ret)
+ return ret;
+ ret = sdio_enable_func(port->func);
+ if (ret)
+ goto err1;
+ ret = sdio_claim_irq(port->func, sdio_uart_irq);
+ if (ret)
+ goto err2;
+
+ /*
+ * Clear the FIFO buffers and disable them.
+ * (they will be reenabled in sdio_change_speed())
+ */
+ sdio_out(port, UART_FCR, UART_FCR_ENABLE_FIFO);
+ sdio_out(port, UART_FCR, UART_FCR_ENABLE_FIFO |
+ UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
+ sdio_out(port, UART_FCR, 0);
+
+ /*
+ * Clear the interrupt registers.
+ */
+ (void) sdio_in(port, UART_LSR);
+ (void) sdio_in(port, UART_RX);
+ (void) sdio_in(port, UART_IIR);
+ (void) sdio_in(port, UART_MSR);
+
+ /*
+ * Now, initialize the UART
+ */
+ sdio_out(port, UART_LCR, UART_LCR_WLEN8);
+
+ port->ier = UART_IER_RLSI|UART_IER_RDI|UART_IER_RTOIE|UART_IER_UUE;
+ port->mctrl = TIOCM_OUT2;
+
+ sdio_uart_change_speed(port, tty->termios, NULL);
+
+ if (tty->termios->c_cflag & CBAUD)
+ sdio_uart_set_mctrl(port, TIOCM_RTS | TIOCM_DTR);
+
+ if (tty->termios->c_cflag & CRTSCTS)
+ if (!(sdio_uart_get_mctrl(port) & TIOCM_CTS))
+ tty->hw_stopped = 1;
+
+ clear_bit(TTY_IO_ERROR, &tty->flags);
+
+ /* Kick the IRQ handler once while we're still holding the host lock */
+ sdio_uart_irq(port->func);
+
+ sdio_uart_release_func(port);
+ return 0;
+
+err2:
+ sdio_disable_func(port->func);
+err1:
+ sdio_uart_release_func(port);
+ return ret;
+}
+
+/**
+ * sdio_uart_shutdown - stop hardware
+ * @tport: tty port to shut down
+ *
+ * Deactivate a tty port. The port locking guarantees us this will be
+ * run only if a successful matching activate already ran. The two are
+ * protected from each other by the internal locking and will not run
+ * at the same time even during a hangup event.
+ */
+
+static void sdio_uart_shutdown(struct tty_port *tport)
+{
+ struct sdio_uart_port *port =
+ container_of(tport, struct sdio_uart_port, port);
+ int ret;
+
+ ret = sdio_uart_claim_func(port);
+ if (ret)
+ return;
+
+ sdio_uart_stop_rx(port);
+
+ /* Disable interrupts from this port */
+ sdio_release_irq(port->func);
+ port->ier = 0;
+ sdio_out(port, UART_IER, 0);
+
+ sdio_uart_clear_mctrl(port, TIOCM_OUT2);
+
+ /* Disable break condition and FIFOs. */
+ port->lcr &= ~UART_LCR_SBC;
+ sdio_out(port, UART_LCR, port->lcr);
+ sdio_out(port, UART_FCR, UART_FCR_ENABLE_FIFO |
+ UART_FCR_CLEAR_RCVR |
+ UART_FCR_CLEAR_XMIT);
+ sdio_out(port, UART_FCR, 0);
+
+ sdio_disable_func(port->func);
+
+ sdio_uart_release_func(port);
+}
+
+/**
+ * sdio_uart_install - install method
+ * @driver: the driver in use (sdio_uart in our case)
+ * @tty: the tty being bound
+ *
+ * Look up and bind the tty and the driver together. Initialize
+ * any needed private data (in our case the termios)
+ */
+
+static int sdio_uart_install(struct tty_driver *driver, struct tty_struct *tty)
+{
+ int idx = tty->index;
+ struct sdio_uart_port *port = sdio_uart_port_get(idx);
+ int ret = tty_init_termios(tty);
+
+ if (ret == 0) {
+ tty_driver_kref_get(driver);
+ tty->count++;
+ /* This is the ref sdio_uart_port get provided */
+ tty->driver_data = port;
+ driver->ttys[idx] = tty;
+ } else
+ sdio_uart_port_put(port);
+ return ret;
+}
+
+/**
+ * sdio_uart_cleanup - called on the last tty kref drop
+ * @tty: the tty being destroyed
+ *
+ * Called asynchronously when the last reference to the tty is dropped.
+ * We cannot destroy the tty->driver_data port kref until this point
+ */
+
+static void sdio_uart_cleanup(struct tty_struct *tty)
+{
+ struct sdio_uart_port *port = tty->driver_data;
+ tty->driver_data = NULL; /* Bug trap */
+ sdio_uart_port_put(port);
+}
+
+/*
+ * Open/close/hangup is now entirely boilerplate
+ */
+
+static int sdio_uart_open(struct tty_struct *tty, struct file *filp)
+{
+ struct sdio_uart_port *port = tty->driver_data;
+ return tty_port_open(&port->port, tty, filp);
+}
+
+static void sdio_uart_close(struct tty_struct *tty, struct file * filp)
+{
+ struct sdio_uart_port *port = tty->driver_data;
+ tty_port_close(&port->port, tty, filp);
+}
+
+static void sdio_uart_hangup(struct tty_struct *tty)
+{
+ struct sdio_uart_port *port = tty->driver_data;
+ tty_port_hangup(&port->port);
+}
+
+static int sdio_uart_write(struct tty_struct *tty, const unsigned char *buf,
+ int count)
+{
+ struct sdio_uart_port *port = tty->driver_data;
+ int ret;
+
+ if (!port->func)
+ return -ENODEV;
+
+ ret = kfifo_in_locked(&port->xmit_fifo, buf, count, &port->write_lock);
+ if (!(port->ier & UART_IER_THRI)) {
+ int err = sdio_uart_claim_func(port);
+ if (!err) {
+ sdio_uart_start_tx(port);
+ sdio_uart_irq(port->func);
+ sdio_uart_release_func(port);
+ } else
+ ret = err;
+ }
+
+ return ret;
+}
+
+static int sdio_uart_write_room(struct tty_struct *tty)
+{
+ struct sdio_uart_port *port = tty->driver_data;
+ return FIFO_SIZE - kfifo_len(&port->xmit_fifo);
+}
+
+static int sdio_uart_chars_in_buffer(struct tty_struct *tty)
+{
+ struct sdio_uart_port *port = tty->driver_data;
+ return kfifo_len(&port->xmit_fifo);
+}
+
+static void sdio_uart_send_xchar(struct tty_struct *tty, char ch)
+{
+ struct sdio_uart_port *port = tty->driver_data;
+
+ port->x_char = ch;
+ if (ch && !(port->ier & UART_IER_THRI)) {
+ if (sdio_uart_claim_func(port) != 0)
+ return;
+ sdio_uart_start_tx(port);
+ sdio_uart_irq(port->func);
+ sdio_uart_release_func(port);
+ }
+}
+
+static void sdio_uart_throttle(struct tty_struct *tty)
+{
+ struct sdio_uart_port *port = tty->driver_data;
+
+ if (!I_IXOFF(tty) && !(tty->termios->c_cflag & CRTSCTS))
+ return;
+
+ if (sdio_uart_claim_func(port) != 0)
+ return;
+
+ if (I_IXOFF(tty)) {
+ port->x_char = STOP_CHAR(tty);
+ sdio_uart_start_tx(port);
+ }
+
+ if (tty->termios->c_cflag & CRTSCTS)
+ sdio_uart_clear_mctrl(port, TIOCM_RTS);
+
+ sdio_uart_irq(port->func);
+ sdio_uart_release_func(port);
+}
+
+static void sdio_uart_unthrottle(struct tty_struct *tty)
+{
+ struct sdio_uart_port *port = tty->driver_data;
+
+ if (!I_IXOFF(tty) && !(tty->termios->c_cflag & CRTSCTS))
+ return;
+
+ if (sdio_uart_claim_func(port) != 0)
+ return;
+
+ if (I_IXOFF(tty)) {
+ if (port->x_char) {
+ port->x_char = 0;
+ } else {
+ port->x_char = START_CHAR(tty);
+ sdio_uart_start_tx(port);
+ }
+ }
+
+ if (tty->termios->c_cflag & CRTSCTS)
+ sdio_uart_set_mctrl(port, TIOCM_RTS);
+
+ sdio_uart_irq(port->func);
+ sdio_uart_release_func(port);
+}
+
+static void sdio_uart_set_termios(struct tty_struct *tty,
+ struct ktermios *old_termios)
+{
+ struct sdio_uart_port *port = tty->driver_data;
+ unsigned int cflag = tty->termios->c_cflag;
+
+ if (sdio_uart_claim_func(port) != 0)
+ return;
+
+ sdio_uart_change_speed(port, tty->termios, old_termios);
+
+ /* Handle transition to B0 status */
+ if ((old_termios->c_cflag & CBAUD) && !(cflag & CBAUD))
+ sdio_uart_clear_mctrl(port, TIOCM_RTS | TIOCM_DTR);
+
+ /* Handle transition away from B0 status */
+ if (!(old_termios->c_cflag & CBAUD) && (cflag & CBAUD)) {
+ unsigned int mask = TIOCM_DTR;
+ if (!(cflag & CRTSCTS) || !test_bit(TTY_THROTTLED, &tty->flags))
+ mask |= TIOCM_RTS;
+ sdio_uart_set_mctrl(port, mask);
+ }
+
+ /* Handle turning off CRTSCTS */
+ if ((old_termios->c_cflag & CRTSCTS) && !(cflag & CRTSCTS)) {
+ tty->hw_stopped = 0;
+ sdio_uart_start_tx(port);
+ }
+
+ /* Handle turning on CRTSCTS */
+ if (!(old_termios->c_cflag & CRTSCTS) && (cflag & CRTSCTS)) {
+ if (!(sdio_uart_get_mctrl(port) & TIOCM_CTS)) {
+ tty->hw_stopped = 1;
+ sdio_uart_stop_tx(port);
+ }
+ }
+
+ sdio_uart_release_func(port);
+}
+
+static int sdio_uart_break_ctl(struct tty_struct *tty, int break_state)
+{
+ struct sdio_uart_port *port = tty->driver_data;
+ int result;
+
+ result = sdio_uart_claim_func(port);
+ if (result != 0)
+ return result;
+
+ if (break_state == -1)
+ port->lcr |= UART_LCR_SBC;
+ else
+ port->lcr &= ~UART_LCR_SBC;
+ sdio_out(port, UART_LCR, port->lcr);
+
+ sdio_uart_release_func(port);
+ return 0;
+}
+
+static int sdio_uart_tiocmget(struct tty_struct *tty)
+{
+ struct sdio_uart_port *port = tty->driver_data;
+ int result;
+
+ result = sdio_uart_claim_func(port);
+ if (!result) {
+ result = port->mctrl | sdio_uart_get_mctrl(port);
+ sdio_uart_release_func(port);
+ }
+
+ return result;
+}
+
+static int sdio_uart_tiocmset(struct tty_struct *tty,
+ unsigned int set, unsigned int clear)
+{
+ struct sdio_uart_port *port = tty->driver_data;
+ int result;
+
+ result = sdio_uart_claim_func(port);
+ if (!result) {
+ sdio_uart_update_mctrl(port, set, clear);
+ sdio_uart_release_func(port);
+ }
+
+ return result;
+}
+
+static int sdio_uart_proc_show(struct seq_file *m, void *v)
+{
+ int i;
+
+ seq_printf(m, "serinfo:1.0 driver%s%s revision:%s\n",
+ "", "", "");
+ for (i = 0; i < UART_NR; i++) {
+ struct sdio_uart_port *port = sdio_uart_port_get(i);
+ if (port) {
+ seq_printf(m, "%d: uart:SDIO", i);
+ if (capable(CAP_SYS_ADMIN)) {
+ seq_printf(m, " tx:%d rx:%d",
+ port->icount.tx, port->icount.rx);
+ if (port->icount.frame)
+ seq_printf(m, " fe:%d",
+ port->icount.frame);
+ if (port->icount.parity)
+ seq_printf(m, " pe:%d",
+ port->icount.parity);
+ if (port->icount.brk)
+ seq_printf(m, " brk:%d",
+ port->icount.brk);
+ if (port->icount.overrun)
+ seq_printf(m, " oe:%d",
+ port->icount.overrun);
+ if (port->icount.cts)
+ seq_printf(m, " cts:%d",
+ port->icount.cts);
+ if (port->icount.dsr)
+ seq_printf(m, " dsr:%d",
+ port->icount.dsr);
+ if (port->icount.rng)
+ seq_printf(m, " rng:%d",
+ port->icount.rng);
+ if (port->icount.dcd)
+ seq_printf(m, " dcd:%d",
+ port->icount.dcd);
+ }
+ sdio_uart_port_put(port);
+ seq_putc(m, '\n');
+ }
+ }
+ return 0;
+}
+
+static int sdio_uart_proc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, sdio_uart_proc_show, NULL);
+}
+
+static const struct file_operations sdio_uart_proc_fops = {
+ .owner = THIS_MODULE,
+ .open = sdio_uart_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static const struct tty_port_operations sdio_uart_port_ops = {
+ .dtr_rts = uart_dtr_rts,
+ .carrier_raised = uart_carrier_raised,
+ .shutdown = sdio_uart_shutdown,
+ .activate = sdio_uart_activate,
+};
+
+static const struct tty_operations sdio_uart_ops = {
+ .open = sdio_uart_open,
+ .close = sdio_uart_close,
+ .write = sdio_uart_write,
+ .write_room = sdio_uart_write_room,
+ .chars_in_buffer = sdio_uart_chars_in_buffer,
+ .send_xchar = sdio_uart_send_xchar,
+ .throttle = sdio_uart_throttle,
+ .unthrottle = sdio_uart_unthrottle,
+ .set_termios = sdio_uart_set_termios,
+ .hangup = sdio_uart_hangup,
+ .break_ctl = sdio_uart_break_ctl,
+ .tiocmget = sdio_uart_tiocmget,
+ .tiocmset = sdio_uart_tiocmset,
+ .install = sdio_uart_install,
+ .cleanup = sdio_uart_cleanup,
+ .proc_fops = &sdio_uart_proc_fops,
+};
+
+static struct tty_driver *sdio_uart_tty_driver;
+
+static int sdio_uart_probe(struct sdio_func *func,
+ const struct sdio_device_id *id)
+{
+ struct sdio_uart_port *port;
+ int ret;
+
+ port = kzalloc(sizeof(struct sdio_uart_port), GFP_KERNEL);
+ if (!port)
+ return -ENOMEM;
+
+ if (func->class == SDIO_CLASS_UART) {
+ printk(KERN_WARNING "%s: need info on UART class basic setup\n",
+ sdio_func_id(func));
+ kfree(port);
+ return -ENOSYS;
+ } else if (func->class == SDIO_CLASS_GPS) {
+ /*
+ * We need tuple 0x91. It contains SUBTPL_SIOREG
+ * and SUBTPL_RCVCAPS.
+ */
+ struct sdio_func_tuple *tpl;
+ for (tpl = func->tuples; tpl; tpl = tpl->next) {
+ if (tpl->code != 0x91)
+ continue;
+ if (tpl->size < 10)
+ continue;
+ if (tpl->data[1] == 0) /* SUBTPL_SIOREG */
+ break;
+ }
+ if (!tpl) {
+ printk(KERN_WARNING
+ "%s: can't find tuple 0x91 subtuple 0 (SUBTPL_SIOREG) for GPS class\n",
+ sdio_func_id(func));
+ kfree(port);
+ return -EINVAL;
+ }
+ printk(KERN_DEBUG "%s: Register ID = 0x%02x, Exp ID = 0x%02x\n",
+ sdio_func_id(func), tpl->data[2], tpl->data[3]);
+ port->regs_offset = (tpl->data[4] << 0) |
+ (tpl->data[5] << 8) |
+ (tpl->data[6] << 16);
+ printk(KERN_DEBUG "%s: regs offset = 0x%x\n",
+ sdio_func_id(func), port->regs_offset);
+ port->uartclk = tpl->data[7] * 115200;
+ if (port->uartclk == 0)
+ port->uartclk = 115200;
+ printk(KERN_DEBUG "%s: clk %d baudcode %u 4800-div %u\n",
+ sdio_func_id(func), port->uartclk,
+ tpl->data[7], tpl->data[8] | (tpl->data[9] << 8));
+ } else {
+ kfree(port);
+ return -EINVAL;
+ }
+
+ port->func = func;
+ sdio_set_drvdata(func, port);
+ tty_port_init(&port->port);
+ port->port.ops = &sdio_uart_port_ops;
+
+ ret = sdio_uart_add_port(port);
+ if (ret) {
+ kfree(port);
+ } else {
+ struct device *dev;
+ dev = tty_register_device(sdio_uart_tty_driver,
+ port->index, &func->dev);
+ if (IS_ERR(dev)) {
+ sdio_uart_port_remove(port);
+ ret = PTR_ERR(dev);
+ }
+ }
+
+ return ret;
+}
+
+static void sdio_uart_remove(struct sdio_func *func)
+{
+ struct sdio_uart_port *port = sdio_get_drvdata(func);
+
+ tty_unregister_device(sdio_uart_tty_driver, port->index);
+ sdio_uart_port_remove(port);
+}
+
+static const struct sdio_device_id sdio_uart_ids[] = {
+ { SDIO_DEVICE_CLASS(SDIO_CLASS_UART) },
+ { SDIO_DEVICE_CLASS(SDIO_CLASS_GPS) },
+ { /* end: all zeroes */ },
+};
+
+MODULE_DEVICE_TABLE(sdio, sdio_uart_ids);
+
+static struct sdio_driver sdio_uart_driver = {
+ .probe = sdio_uart_probe,
+ .remove = sdio_uart_remove,
+ .name = "sdio_uart",
+ .id_table = sdio_uart_ids,
+};
+
+static int __init sdio_uart_init(void)
+{
+ int ret;
+ struct tty_driver *tty_drv;
+
+ sdio_uart_tty_driver = tty_drv = alloc_tty_driver(UART_NR);
+ if (!tty_drv)
+ return -ENOMEM;
+
+ tty_drv->owner = THIS_MODULE;
+ tty_drv->driver_name = "sdio_uart";
+ tty_drv->name = "ttySDIO";
+ tty_drv->major = 0; /* dynamically allocated */
+ tty_drv->minor_start = 0;
+ tty_drv->type = TTY_DRIVER_TYPE_SERIAL;
+ tty_drv->subtype = SERIAL_TYPE_NORMAL;
+ tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
+ tty_drv->init_termios = tty_std_termios;
+ tty_drv->init_termios.c_cflag = B4800 | CS8 | CREAD | HUPCL | CLOCAL;
+ tty_drv->init_termios.c_ispeed = 4800;
+ tty_drv->init_termios.c_ospeed = 4800;
+ tty_set_operations(tty_drv, &sdio_uart_ops);
+
+ ret = tty_register_driver(tty_drv);
+ if (ret)
+ goto err1;
+
+ ret = sdio_register_driver(&sdio_uart_driver);
+ if (ret)
+ goto err2;
+
+ return 0;
+
+err2:
+ tty_unregister_driver(tty_drv);
+err1:
+ put_tty_driver(tty_drv);
+ return ret;
+}
+
+static void __exit sdio_uart_exit(void)
+{
+ sdio_unregister_driver(&sdio_uart_driver);
+ tty_unregister_driver(sdio_uart_tty_driver);
+ put_tty_driver(sdio_uart_tty_driver);
+}
+
+module_init(sdio_uart_init);
+module_exit(sdio_uart_exit);
+
+MODULE_AUTHOR("Nicolas Pitre");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mmc/card/sdio_wifi_pwr.c b/drivers/mmc/card/sdio_wifi_pwr.c
new file mode 100755
index 00000000..10c65e7d
--- /dev/null
+++ b/drivers/mmc/card/sdio_wifi_pwr.c
@@ -0,0 +1,21 @@
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+
+extern void ntx_wifi_power_ctrl (int isWifiEnable);
+static int __init sdio_wifi_pwr_init(void)
+{
+ ntx_wifi_power_ctrl(1);
+ return 0;
+}
+module_init(sdio_wifi_pwr_init);
+
+static void __exit sdio_wifi_pwr_exit(void)
+{
+ ntx_wifi_power_ctrl(0);
+}
+module_exit(sdio_wifi_pwr_exit);
+
+MODULE_DESCRIPTION("sdio wifi power control driver");
+MODULE_AUTHOR("Netronix");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mmc/core/Kconfig b/drivers/mmc/core/Kconfig
new file mode 100644
index 00000000..85c2e1ac
--- /dev/null
+++ b/drivers/mmc/core/Kconfig
@@ -0,0 +1,46 @@
+#
+# MMC core configuration
+#
+
+config MMC_UNSAFE_RESUME
+ bool "Assume MMC/SD cards are non-removable (DANGEROUS)"
+ help
+ If you say Y here, the MMC layer will assume that all cards
+ stayed in their respective slots during the suspend. The
+ normal behaviour is to remove them at suspend and
+ redetecting them at resume. Breaking this assumption will
+ in most cases result in data corruption.
+
+ This option is usually just for embedded systems which use
+ a MMC/SD card for rootfs. Most people should say N here.
+
+ This option sets a default which can be overridden by the
+ module parameter "removable=0" or "removable=1".
+
+config MMC_CLKGATE
+ bool "MMC host clock gating (EXPERIMENTAL)"
+ depends on EXPERIMENTAL
+ help
+ This will attempt to aggressively gate the clock to the MMC card.
+ This is done to save power due to gating off the logic and bus
+ noise when the MMC card is not in use. Your host driver has to
+ support handling this in order for it to be of any use.
+
+ If unsure, say N.
+
+config MMC_EMBEDDED_SDIO
+ boolean "MMC embedded SDIO device support (EXPERIMENTAL)"
+ depends on EXPERIMENTAL
+ help
+ If you say Y here, support will be added for embedded SDIO
+ devices which do not contain the necessary enumeration
+ support in hardware to be properly detected.
+
+config MMC_PARANOID_SD_INIT
+ bool "Enable paranoid SD card initialization (EXPERIMENTAL)"
+ depends on EXPERIMENTAL
+ help
+ If you say Y here, the MMC layer will be extra paranoid
+ about re-trying SD init requests. This can be a useful
+ work-around for buggy controllers and hardware. Enable
+ if you are experiencing issues with SD detection.
diff --git a/drivers/mmc/core/Makefile b/drivers/mmc/core/Makefile
new file mode 100644
index 00000000..63950197
--- /dev/null
+++ b/drivers/mmc/core/Makefile
@@ -0,0 +1,12 @@
+#
+# Makefile for the kernel mmc core.
+#
+
+obj-$(CONFIG_MMC) += mmc_core.o
+mmc_core-y := core.o bus.o host.o \
+ mmc.o mmc_ops.o sd.o sd_ops.o \
+ sdio.o sdio_ops.o sdio_bus.o \
+ sdio_cis.o sdio_io.o sdio_irq.o \
+ quirks.o
+
+mmc_core-$(CONFIG_DEBUG_FS) += debugfs.o
diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c
new file mode 100644
index 00000000..e0fa84b0
--- /dev/null
+++ b/drivers/mmc/core/bus.c
@@ -0,0 +1,340 @@
+/*
+ * linux/drivers/mmc/core/bus.c
+ *
+ * Copyright (C) 2003 Russell King, All Rights Reserved.
+ * Copyright (C) 2007 Pierre Ossman
+ *
+ * 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.
+ *
+ * MMC card bus driver model
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/pm_runtime.h>
+
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+
+#include "core.h"
+#include "sdio_cis.h"
+#include "bus.h"
+
+#define to_mmc_driver(d) container_of(d, struct mmc_driver, drv)
+
+static ssize_t mmc_type_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mmc_card *card = mmc_dev_to_card(dev);
+
+ switch (card->type) {
+ case MMC_TYPE_MMC:
+ return sprintf(buf, "MMC\n");
+ case MMC_TYPE_SD:
+ return sprintf(buf, "SD\n");
+ case MMC_TYPE_SDIO:
+ return sprintf(buf, "SDIO\n");
+ case MMC_TYPE_SD_COMBO:
+ return sprintf(buf, "SDcombo\n");
+ default:
+ return -EFAULT;
+ }
+}
+
+static struct device_attribute mmc_dev_attrs[] = {
+ __ATTR(type, S_IRUGO, mmc_type_show, NULL),
+ __ATTR_NULL,
+};
+
+/*
+ * This currently matches any MMC driver to any MMC card - drivers
+ * themselves make the decision whether to drive this card in their
+ * probe method.
+ */
+static int mmc_bus_match(struct device *dev, struct device_driver *drv)
+{
+ return 1;
+}
+
+static int
+mmc_bus_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ struct mmc_card *card = mmc_dev_to_card(dev);
+ const char *type;
+ int retval = 0;
+
+ switch (card->type) {
+ case MMC_TYPE_MMC:
+ type = "MMC";
+ break;
+ case MMC_TYPE_SD:
+ type = "SD";
+ break;
+ case MMC_TYPE_SDIO:
+ type = "SDIO";
+ break;
+ case MMC_TYPE_SD_COMBO:
+ type = "SDcombo";
+ break;
+ default:
+ type = NULL;
+ }
+
+ if (type) {
+ retval = add_uevent_var(env, "MMC_TYPE=%s", type);
+ if (retval)
+ return retval;
+ }
+
+ retval = add_uevent_var(env, "MMC_NAME=%s", mmc_card_name(card));
+ if (retval)
+ return retval;
+
+ /*
+ * Request the mmc_block device. Note: that this is a direct request
+ * for the module it carries no information as to what is inserted.
+ */
+ retval = add_uevent_var(env, "MODALIAS=mmc:block");
+
+ return retval;
+}
+
+static int mmc_bus_probe(struct device *dev)
+{
+ struct mmc_driver *drv = to_mmc_driver(dev->driver);
+ struct mmc_card *card = mmc_dev_to_card(dev);
+
+ return drv->probe(card);
+}
+
+static int mmc_bus_remove(struct device *dev)
+{
+ struct mmc_driver *drv = to_mmc_driver(dev->driver);
+ struct mmc_card *card = mmc_dev_to_card(dev);
+
+ drv->remove(card);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int mmc_bus_suspend(struct device *dev)
+{
+ struct mmc_driver *drv = to_mmc_driver(dev->driver);
+ struct mmc_card *card = mmc_dev_to_card(dev);
+ int ret = 0;
+
+ if (dev->driver && drv->suspend)
+ ret = drv->suspend(card);
+ return ret;
+}
+
+static int mmc_bus_resume(struct device *dev)
+{
+ struct mmc_driver *drv = to_mmc_driver(dev->driver);
+ struct mmc_card *card = mmc_dev_to_card(dev);
+ int ret = 0;
+
+ if (dev->driver && drv->resume)
+ ret = drv->resume(card);
+ return ret;
+}
+#endif
+
+#ifdef CONFIG_PM_RUNTIME
+static int mmc_runtime_suspend(struct device *dev)
+{
+ struct mmc_card *card = mmc_dev_to_card(dev);
+
+ return mmc_power_save_host(card->host);
+}
+
+static int mmc_runtime_resume(struct device *dev)
+{
+ struct mmc_card *card = mmc_dev_to_card(dev);
+
+ return mmc_power_restore_host(card->host);
+}
+
+static int mmc_runtime_idle(struct device *dev)
+{
+ return pm_runtime_suspend(dev);
+}
+#endif /* CONFIG_PM_RUNTIME */
+
+static const struct dev_pm_ops mmc_bus_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(mmc_bus_suspend, mmc_bus_resume)
+ SET_RUNTIME_PM_OPS(mmc_runtime_suspend, mmc_runtime_resume,
+mmc_runtime_idle)
+};
+
+static struct bus_type mmc_bus_type = {
+ .name = "mmc",
+ .dev_attrs = mmc_dev_attrs,
+ .match = mmc_bus_match,
+ .uevent = mmc_bus_uevent,
+ .probe = mmc_bus_probe,
+ .remove = mmc_bus_remove,
+ .pm = &mmc_bus_pm_ops,
+};
+
+int mmc_register_bus(void)
+{
+ return bus_register(&mmc_bus_type);
+}
+
+void mmc_unregister_bus(void)
+{
+ bus_unregister(&mmc_bus_type);
+}
+
+/**
+ * mmc_register_driver - register a media driver
+ * @drv: MMC media driver
+ */
+int mmc_register_driver(struct mmc_driver *drv)
+{
+ drv->drv.bus = &mmc_bus_type;
+ return driver_register(&drv->drv);
+}
+
+EXPORT_SYMBOL(mmc_register_driver);
+
+/**
+ * mmc_unregister_driver - unregister a media driver
+ * @drv: MMC media driver
+ */
+void mmc_unregister_driver(struct mmc_driver *drv)
+{
+ drv->drv.bus = &mmc_bus_type;
+ driver_unregister(&drv->drv);
+}
+
+EXPORT_SYMBOL(mmc_unregister_driver);
+
+static void mmc_release_card(struct device *dev)
+{
+ struct mmc_card *card = mmc_dev_to_card(dev);
+
+ sdio_free_common_cis(card);
+
+ if (card->info)
+ kfree(card->info);
+
+ kfree(card);
+}
+
+/*
+ * Allocate and initialise a new MMC card structure.
+ */
+struct mmc_card *mmc_alloc_card(struct mmc_host *host, struct device_type *type)
+{
+ struct mmc_card *card;
+
+ card = kzalloc(sizeof(struct mmc_card), GFP_KERNEL);
+ if (!card)
+ return ERR_PTR(-ENOMEM);
+
+ card->host = host;
+
+ device_initialize(&card->dev);
+
+ card->dev.parent = mmc_classdev(host);
+ card->dev.bus = &mmc_bus_type;
+ card->dev.release = mmc_release_card;
+ card->dev.type = type;
+
+ return card;
+}
+
+/*
+ * Register a new MMC card with the driver model.
+ */
+int mmc_add_card(struct mmc_card *card)
+{
+ int ret;
+ const char *type;
+
+ dev_set_name(&card->dev, "%s:%04x", mmc_hostname(card->host), card->rca);
+
+ switch (card->type) {
+ case MMC_TYPE_MMC:
+ type = "MMC";
+ break;
+ case MMC_TYPE_SD:
+ type = "SD";
+ if (mmc_card_blockaddr(card)) {
+ if (mmc_card_ext_capacity(card))
+ type = "SDXC";
+ else
+ type = "SDHC";
+ }
+ break;
+ case MMC_TYPE_SDIO:
+ type = "SDIO";
+ break;
+ case MMC_TYPE_SD_COMBO:
+ type = "SD-combo";
+ if (mmc_card_blockaddr(card))
+ type = "SDHC-combo";
+ break;
+ default:
+ type = "?";
+ break;
+ }
+
+ if (mmc_host_is_spi(card->host)) {
+ printk(KERN_INFO "%s: new %s%s%s card on SPI\n",
+ mmc_hostname(card->host),
+ mmc_card_highspeed(card) ? "high speed " : "",
+ mmc_card_ddr_mode(card) ? "DDR " : "",
+ type);
+ } else {
+ printk(KERN_INFO "%s: new %s%s%s card at address %04x\n",
+ mmc_hostname(card->host),
+ mmc_sd_card_uhs(card) ? "ultra high speed " :
+ (mmc_card_highspeed(card) ? "high speed " : ""),
+ mmc_card_ddr_mode(card) ? "DDR " : "",
+ type, card->rca);
+ }
+
+#ifdef CONFIG_DEBUG_FS
+ mmc_add_card_debugfs(card);
+#endif
+
+ ret = device_add(&card->dev);
+ if (ret)
+ return ret;
+
+ mmc_card_set_present(card);
+
+ return 0;
+}
+
+/*
+ * Unregister a new MMC card with the driver model, and
+ * (eventually) free it.
+ */
+void mmc_remove_card(struct mmc_card *card)
+{
+#ifdef CONFIG_DEBUG_FS
+ mmc_remove_card_debugfs(card);
+#endif
+
+ if (mmc_card_present(card)) {
+ if (mmc_host_is_spi(card->host)) {
+ printk(KERN_INFO "%s: SPI card removed\n",
+ mmc_hostname(card->host));
+ } else {
+ printk(KERN_INFO "%s: card %04x removed\n",
+ mmc_hostname(card->host), card->rca);
+ }
+ device_del(&card->dev);
+ }
+
+ put_device(&card->dev);
+}
+
diff --git a/drivers/mmc/core/bus.h b/drivers/mmc/core/bus.h
new file mode 100644
index 00000000..00a19710
--- /dev/null
+++ b/drivers/mmc/core/bus.h
@@ -0,0 +1,31 @@
+/*
+ * linux/drivers/mmc/core/bus.h
+ *
+ * Copyright (C) 2003 Russell King, All Rights Reserved.
+ * Copyright 2007 Pierre Ossman
+ *
+ * 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 _MMC_CORE_BUS_H
+#define _MMC_CORE_BUS_H
+
+#define MMC_DEV_ATTR(name, fmt, args...) \
+static ssize_t mmc_##name##_show (struct device *dev, struct device_attribute *attr, char *buf) \
+{ \
+ struct mmc_card *card = mmc_dev_to_card(dev); \
+ return sprintf(buf, fmt, args); \
+} \
+static DEVICE_ATTR(name, S_IRUGO, mmc_##name##_show, NULL)
+
+struct mmc_card *mmc_alloc_card(struct mmc_host *host,
+ struct device_type *type);
+int mmc_add_card(struct mmc_card *card);
+void mmc_remove_card(struct mmc_card *card);
+
+int mmc_register_bus(void);
+void mmc_unregister_bus(void);
+
+#endif
+
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
new file mode 100644
index 00000000..e637fbd1
--- /dev/null
+++ b/drivers/mmc/core/core.c
@@ -0,0 +1,2214 @@
+/*
+ * linux/drivers/mmc/core/core.c
+ *
+ * Copyright (C) 2003-2004 Russell King, All Rights Reserved.
+ * SD support Copyright (C) 2004 Ian Molton, 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
+ * 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/init.h>
+#include <linux/interrupt.h>
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/pagemap.h>
+#include <linux/err.h>
+#include <linux/leds.h>
+#include <linux/scatterlist.h>
+#include <linux/log2.h>
+#include <linux/regulator/consumer.h>
+#include <linux/pm_runtime.h>
+#include <linux/wakelock.h>
+
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/sd.h>
+
+#include "core.h"
+#include "bus.h"
+#include "host.h"
+#include "sdio_bus.h"
+
+#include "mmc_ops.h"
+#include "sd_ops.h"
+#include "sdio_ops.h"
+
+static struct workqueue_struct *workqueue;
+
+/*
+ * Enabling software CRCs on the data blocks can be a significant (30%)
+ * performance cost, and for other reasons may not always be desired.
+ * So we allow it it to be disabled.
+ */
+int use_spi_crc = 1;
+module_param(use_spi_crc, bool, 0);
+
+/*
+ * We normally treat cards as removed during suspend if they are not
+ * known to be on a non-removable bus, to avoid the risk of writing
+ * back data to a different card after resume. Allow this to be
+ * overridden if necessary.
+ */
+#ifdef CONFIG_MMC_UNSAFE_RESUME
+int mmc_assume_removable;
+#else
+int mmc_assume_removable = 1;
+#endif
+EXPORT_SYMBOL(mmc_assume_removable);
+module_param_named(removable, mmc_assume_removable, bool, 0644);
+MODULE_PARM_DESC(
+ removable,
+ "MMC/SD cards are removable and may be removed during suspend");
+
+/*
+ * Internal function. Schedule delayed work in the MMC work queue.
+ */
+static int mmc_schedule_delayed_work(struct delayed_work *work,
+ unsigned long delay)
+{
+ return queue_delayed_work(workqueue, work, delay);
+}
+
+/*
+ * Internal function. Flush all scheduled work from the MMC work queue.
+ */
+static void mmc_flush_scheduled_work(void)
+{
+ flush_workqueue(workqueue);
+}
+
+/**
+ * mmc_request_done - finish processing an MMC request
+ * @host: MMC host which completed request
+ * @mrq: MMC request which request
+ *
+ * MMC drivers should call this function when they have completed
+ * their processing of a request.
+ */
+void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq)
+{
+ struct mmc_command *cmd = mrq->cmd;
+ int err = cmd->error;
+
+ if (err && cmd->retries && mmc_host_is_spi(host)) {
+ if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND)
+ cmd->retries = 0;
+ }
+
+ if (err && cmd->retries) {
+ /*
+ * Request starter must handle retries - see
+ * mmc_wait_for_req_done().
+ */
+ if (mrq->done)
+ mrq->done(mrq);
+ } else {
+ led_trigger_event(host->led, LED_OFF);
+
+ pr_debug("%s: req done (CMD%u): %d: %08x %08x %08x %08x\n",
+ mmc_hostname(host), cmd->opcode, err,
+ cmd->resp[0], cmd->resp[1],
+ cmd->resp[2], cmd->resp[3]);
+
+ if (mrq->data) {
+ pr_debug("%s: %d bytes transferred: %d\n",
+ mmc_hostname(host),
+ mrq->data->bytes_xfered, mrq->data->error);
+ }
+
+ if (mrq->stop) {
+ pr_debug("%s: (CMD%u): %d: %08x %08x %08x %08x\n",
+ mmc_hostname(host), mrq->stop->opcode,
+ mrq->stop->error,
+ mrq->stop->resp[0], mrq->stop->resp[1],
+ mrq->stop->resp[2], mrq->stop->resp[3]);
+ }
+
+ if (mrq->done)
+ mrq->done(mrq);
+
+ mmc_host_clk_release(host);
+ }
+}
+
+EXPORT_SYMBOL(mmc_request_done);
+
+static void
+mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
+{
+#ifdef CONFIG_MMC_DEBUG
+ unsigned int i, sz;
+ struct scatterlist *sg;
+#endif
+
+ pr_debug("%s: starting CMD%u arg %08x flags %08x\n",
+ mmc_hostname(host), mrq->cmd->opcode,
+ mrq->cmd->arg, mrq->cmd->flags);
+
+ if (mrq->data) {
+ pr_debug("%s: blksz %d blocks %d flags %08x "
+ "tsac %d ms nsac %d\n",
+ mmc_hostname(host), mrq->data->blksz,
+ mrq->data->blocks, mrq->data->flags,
+ mrq->data->timeout_ns / 1000000,
+ mrq->data->timeout_clks);
+ }
+
+ if (mrq->stop) {
+ pr_debug("%s: CMD%u arg %08x flags %08x\n",
+ mmc_hostname(host), mrq->stop->opcode,
+ mrq->stop->arg, mrq->stop->flags);
+ }
+
+ WARN_ON(!host->claimed);
+
+ mrq->cmd->error = 0;
+ mrq->cmd->mrq = mrq;
+ if (mrq->data) {
+ BUG_ON(mrq->data->blksz > host->max_blk_size);
+ BUG_ON(mrq->data->blocks > host->max_blk_count);
+ BUG_ON(mrq->data->blocks * mrq->data->blksz >
+ host->max_req_size);
+
+#ifdef CONFIG_MMC_DEBUG
+ sz = 0;
+ for_each_sg(mrq->data->sg, sg, mrq->data->sg_len, i)
+ sz += sg->length;
+ BUG_ON(sz != mrq->data->blocks * mrq->data->blksz);
+#endif
+
+ mrq->cmd->data = mrq->data;
+ mrq->data->error = 0;
+ mrq->data->mrq = mrq;
+ if (mrq->stop) {
+ mrq->data->stop = mrq->stop;
+ mrq->stop->error = 0;
+ mrq->stop->mrq = mrq;
+ }
+ }
+ mmc_host_clk_hold(host);
+ led_trigger_event(host->led, LED_FULL);
+ host->ops->request(host, mrq);
+}
+
+static void mmc_wait_done(struct mmc_request *mrq)
+{
+ complete(&mrq->completion);
+}
+
+static void __mmc_start_req(struct mmc_host *host, struct mmc_request *mrq)
+{
+ init_completion(&mrq->completion);
+ mrq->done = mmc_wait_done;
+ mmc_start_request(host, mrq);
+}
+
+static void mmc_wait_for_req_done(struct mmc_host *host,
+ struct mmc_request *mrq)
+{
+ struct mmc_command *cmd;
+
+ while (1) {
+ wait_for_completion(&mrq->completion);
+
+ cmd = mrq->cmd;
+ if (!cmd->error || !cmd->retries)
+ break;
+
+ pr_debug("%s: req failed (CMD%u): %d, retrying...\n",
+ mmc_hostname(host), cmd->opcode, cmd->error);
+ cmd->retries--;
+ cmd->error = 0;
+ host->ops->request(host, mrq);
+ }
+}
+
+/**
+ * mmc_pre_req - Prepare for a new request
+ * @host: MMC host to prepare command
+ * @mrq: MMC request to prepare for
+ * @is_first_req: true if there is no previous started request
+ * that may run in parellel to this call, otherwise false
+ *
+ * mmc_pre_req() is called in prior to mmc_start_req() to let
+ * host prepare for the new request. Preparation of a request may be
+ * performed while another request is running on the host.
+ */
+static void mmc_pre_req(struct mmc_host *host, struct mmc_request *mrq,
+ bool is_first_req)
+{
+ if (host->ops->pre_req)
+ host->ops->pre_req(host, mrq, is_first_req);
+}
+
+/**
+ * mmc_post_req - Post process a completed request
+ * @host: MMC host to post process command
+ * @mrq: MMC request to post process for
+ * @err: Error, if non zero, clean up any resources made in pre_req
+ *
+ * Let the host post process a completed request. Post processing of
+ * a request may be performed while another reuqest is running.
+ */
+static void mmc_post_req(struct mmc_host *host, struct mmc_request *mrq,
+ int err)
+{
+ if (host->ops->post_req)
+ host->ops->post_req(host, mrq, err);
+}
+
+/**
+ * mmc_start_req - start a non-blocking request
+ * @host: MMC host to start command
+ * @areq: async request to start
+ * @error: out parameter returns 0 for success, otherwise non zero
+ *
+ * Start a new MMC custom command request for a host.
+ * If there is on ongoing async request wait for completion
+ * of that request and start the new one and return.
+ * Does not wait for the new request to complete.
+ *
+ * Returns the completed request, NULL in case of none completed.
+ * Wait for the an ongoing request (previoulsy started) to complete and
+ * return the completed request. If there is no ongoing request, NULL
+ * is returned without waiting. NULL is not an error condition.
+ */
+struct mmc_async_req *mmc_start_req(struct mmc_host *host,
+ struct mmc_async_req *areq, int *error)
+{
+ int err = 0;
+ struct mmc_async_req *data = host->areq;
+
+ /* Prepare a new request */
+ if (areq)
+ mmc_pre_req(host, areq->mrq, !host->areq);
+
+ if (host->areq) {
+ mmc_wait_for_req_done(host, host->areq->mrq);
+ err = host->areq->err_check(host->card, host->areq);
+ if (err) {
+ mmc_post_req(host, host->areq->mrq, 0);
+ if (areq)
+ mmc_post_req(host, areq->mrq, -EINVAL);
+
+ host->areq = NULL;
+ goto out;
+ }
+ }
+
+ if (areq)
+ __mmc_start_req(host, areq->mrq);
+
+ if (host->areq)
+ mmc_post_req(host, host->areq->mrq, 0);
+
+ host->areq = areq;
+ out:
+ if (error)
+ *error = err;
+ return data;
+}
+EXPORT_SYMBOL(mmc_start_req);
+
+/**
+ * mmc_wait_for_req - start a request and wait for completion
+ * @host: MMC host to start command
+ * @mrq: MMC request to start
+ *
+ * Start a new MMC custom command request for a host, and wait
+ * for the command to complete. Does not attempt to parse the
+ * response.
+ */
+void mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq)
+{
+ __mmc_start_req(host, mrq);
+ mmc_wait_for_req_done(host, mrq);
+}
+EXPORT_SYMBOL(mmc_wait_for_req);
+
+/**
+ * mmc_wait_for_cmd - start a command and wait for completion
+ * @host: MMC host to start command
+ * @cmd: MMC command to start
+ * @retries: maximum number of retries
+ *
+ * Start a new MMC command for a host, and wait for the command
+ * to complete. Return any error that occurred while the command
+ * was executing. Do not attempt to parse the response.
+ */
+int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries)
+{
+ struct mmc_request mrq = {0};
+
+ WARN_ON(!host->claimed);
+
+ memset(cmd->resp, 0, sizeof(cmd->resp));
+ cmd->retries = retries;
+
+ mrq.cmd = cmd;
+ cmd->data = NULL;
+
+ mmc_wait_for_req(host, &mrq);
+
+ return cmd->error;
+}
+
+EXPORT_SYMBOL(mmc_wait_for_cmd);
+
+/**
+ * mmc_set_data_timeout - set the timeout for a data command
+ * @data: data phase for command
+ * @card: the MMC card associated with the data transfer
+ *
+ * Computes the data timeout parameters according to the
+ * correct algorithm given the card type.
+ */
+void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card)
+{
+ unsigned int mult;
+
+ /*
+ * SDIO cards only define an upper 1 s limit on access.
+ */
+ if (mmc_card_sdio(card)) {
+ data->timeout_ns = 1000000000;
+ data->timeout_clks = 0;
+ return;
+ }
+
+ /*
+ * SD cards use a 100 multiplier rather than 10
+ */
+ mult = mmc_card_sd(card) ? 100 : 10;
+
+ /*
+ * Scale up the multiplier (and therefore the timeout) by
+ * the r2w factor for writes.
+ */
+ if (data->flags & MMC_DATA_WRITE)
+ mult <<= card->csd.r2w_factor;
+
+ data->timeout_ns = card->csd.tacc_ns * mult;
+ data->timeout_clks = card->csd.tacc_clks * mult;
+
+ /*
+ * SD cards also have an upper limit on the timeout.
+ */
+ if (mmc_card_sd(card)) {
+ unsigned int timeout_us, limit_us;
+
+ timeout_us = data->timeout_ns / 1000;
+ if (mmc_host_clk_rate(card->host))
+ timeout_us += data->timeout_clks * 1000 /
+ (mmc_host_clk_rate(card->host) / 1000);
+
+ if (data->flags & MMC_DATA_WRITE)
+ /*
+ * The limit is really 250 ms, but that is
+ * insufficient for some crappy cards.
+ */
+ limit_us = 300000;
+ else
+ limit_us = 100000;
+
+ /*
+ * SDHC cards always use these fixed values.
+ */
+ if (timeout_us > limit_us || mmc_card_blockaddr(card)) {
+ data->timeout_ns = limit_us * 1000;
+ data->timeout_clks = 0;
+ }
+ }
+ /*
+ * Some cards need very high timeouts if driven in SPI mode.
+ * The worst observed timeout was 900ms after writing a
+ * continuous stream of data until the internal logic
+ * overflowed.
+ */
+ if (mmc_host_is_spi(card->host)) {
+ if (data->flags & MMC_DATA_WRITE) {
+ if (data->timeout_ns < 1000000000)
+ data->timeout_ns = 1000000000; /* 1s */
+ } else {
+ if (data->timeout_ns < 100000000)
+ data->timeout_ns = 100000000; /* 100ms */
+ }
+ }
+}
+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_host_enable - enable a host.
+ * @host: mmc host to enable
+ *
+ * Hosts that support power saving can use the 'enable' and 'disable'
+ * methods to exit and enter power saving states. For more information
+ * see comments for struct mmc_host_ops.
+ */
+int mmc_host_enable(struct mmc_host *host)
+{
+ if (!(host->caps & MMC_CAP_DISABLE))
+ return 0;
+
+ if (host->en_dis_recurs)
+ return 0;
+
+ if (host->nesting_cnt++)
+ return 0;
+
+ cancel_delayed_work_sync(&host->disable);
+
+ if (host->enabled)
+ return 0;
+
+ if (host->ops->enable) {
+ int err;
+
+ host->en_dis_recurs = 1;
+ err = host->ops->enable(host);
+ host->en_dis_recurs = 0;
+
+ if (err) {
+ pr_debug("%s: enable error %d\n",
+ mmc_hostname(host), err);
+ return err;
+ }
+ }
+ host->enabled = 1;
+ return 0;
+}
+EXPORT_SYMBOL(mmc_host_enable);
+
+static int mmc_host_do_disable(struct mmc_host *host, int lazy)
+{
+ if (host->ops->disable) {
+ int err;
+
+ host->en_dis_recurs = 1;
+ err = host->ops->disable(host, lazy);
+ host->en_dis_recurs = 0;
+
+ if (err < 0) {
+ pr_debug("%s: disable error %d\n",
+ mmc_hostname(host), err);
+ return err;
+ }
+ if (err > 0) {
+ unsigned long delay = msecs_to_jiffies(err);
+
+ mmc_schedule_delayed_work(&host->disable, delay);
+ }
+ }
+ host->enabled = 0;
+ return 0;
+}
+
+/**
+ * mmc_host_disable - disable a host.
+ * @host: mmc host to disable
+ *
+ * Hosts that support power saving can use the 'enable' and 'disable'
+ * methods to exit and enter power saving states. For more information
+ * see comments for struct mmc_host_ops.
+ */
+int mmc_host_disable(struct mmc_host *host)
+{
+ int err;
+
+ if (!(host->caps & MMC_CAP_DISABLE))
+ return 0;
+
+ if (host->en_dis_recurs)
+ return 0;
+
+ if (--host->nesting_cnt)
+ return 0;
+
+ if (!host->enabled)
+ return 0;
+
+ err = mmc_host_do_disable(host, 0);
+ return err;
+}
+EXPORT_SYMBOL(mmc_host_disable);
+
+/**
+ * __mmc_claim_host - exclusively claim a host
+ * @host: mmc host to claim
+ * @abort: whether or not the operation should be aborted
+ *
+ * Claim a host for a set of operations. If @abort is non null and
+ * dereference a non-zero value then this will return prematurely with
+ * that non-zero value without acquiring the lock. Returns zero
+ * with the lock held otherwise.
+ */
+int __mmc_claim_host(struct mmc_host *host, atomic_t *abort)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ unsigned long flags;
+ int stop;
+
+ might_sleep();
+
+ add_wait_queue(&host->wq, &wait);
+ spin_lock_irqsave(&host->lock, flags);
+ while (1) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ stop = abort ? atomic_read(abort) : 0;
+ if (stop || !host->claimed || host->claimer == current)
+ break;
+ spin_unlock_irqrestore(&host->lock, flags);
+ schedule();
+ spin_lock_irqsave(&host->lock, flags);
+ }
+ set_current_state(TASK_RUNNING);
+ if (!stop) {
+ host->claimed = 1;
+ host->claimer = current;
+ host->claim_cnt += 1;
+ } else
+ wake_up(&host->wq);
+ spin_unlock_irqrestore(&host->lock, flags);
+ remove_wait_queue(&host->wq, &wait);
+ if (!stop)
+ mmc_host_enable(host);
+ return stop;
+}
+
+EXPORT_SYMBOL(__mmc_claim_host);
+
+/**
+ * mmc_try_claim_host - try exclusively to claim a host
+ * @host: mmc host to claim
+ *
+ * Returns %1 if the host is claimed, %0 otherwise.
+ */
+int mmc_try_claim_host(struct mmc_host *host)
+{
+ int claimed_host = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+ if (!host->claimed || host->claimer == current) {
+ host->claimed = 1;
+ host->claimer = current;
+ host->claim_cnt += 1;
+ claimed_host = 1;
+ }
+ spin_unlock_irqrestore(&host->lock, flags);
+ return claimed_host;
+}
+EXPORT_SYMBOL(mmc_try_claim_host);
+
+/**
+ * mmc_do_release_host - release a claimed host
+ * @host: mmc host to release
+ *
+ * If you successfully claimed a host, this function will
+ * release it again.
+ */
+void mmc_do_release_host(struct mmc_host *host)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+ if (--host->claim_cnt) {
+ /* Release for nested claim */
+ spin_unlock_irqrestore(&host->lock, flags);
+ } else {
+ host->claimed = 0;
+ host->claimer = NULL;
+ spin_unlock_irqrestore(&host->lock, flags);
+ wake_up(&host->wq);
+ }
+}
+EXPORT_SYMBOL(mmc_do_release_host);
+
+void mmc_host_deeper_disable(struct work_struct *work)
+{
+ struct mmc_host *host =
+ container_of(work, struct mmc_host, disable.work);
+
+ /* If the host is claimed then we do not want to disable it anymore */
+ if (!mmc_try_claim_host(host))
+ return;
+ mmc_host_do_disable(host, 1);
+ mmc_do_release_host(host);
+}
+
+/**
+ * mmc_host_lazy_disable - lazily disable a host.
+ * @host: mmc host to disable
+ *
+ * Hosts that support power saving can use the 'enable' and 'disable'
+ * methods to exit and enter power saving states. For more information
+ * see comments for struct mmc_host_ops.
+ */
+int mmc_host_lazy_disable(struct mmc_host *host)
+{
+ if (!(host->caps & MMC_CAP_DISABLE))
+ return 0;
+
+ if (host->en_dis_recurs)
+ return 0;
+
+ if (--host->nesting_cnt)
+ return 0;
+
+ if (!host->enabled)
+ return 0;
+
+ if (host->disable_delay) {
+ mmc_schedule_delayed_work(&host->disable,
+ msecs_to_jiffies(host->disable_delay));
+ return 0;
+ } else
+ return mmc_host_do_disable(host, 1);
+}
+EXPORT_SYMBOL(mmc_host_lazy_disable);
+
+/**
+ * mmc_release_host - release a host
+ * @host: mmc host to release
+ *
+ * Release a MMC host, allowing others to claim the host
+ * for their operations.
+ */
+void mmc_release_host(struct mmc_host *host)
+{
+ WARN_ON(!host->claimed);
+
+ mmc_host_lazy_disable(host);
+
+ mmc_do_release_host(host);
+}
+
+EXPORT_SYMBOL(mmc_release_host);
+
+/*
+ * Internal function that does the actual ios call to the host driver,
+ * optionally printing some debug output.
+ */
+static inline void mmc_set_ios(struct mmc_host *host)
+{
+ struct mmc_ios *ios = &host->ios;
+
+ pr_debug("%s: clock %uHz busmode %u powermode %u cs %u Vdd %u "
+ "width %u timing %u\n",
+ mmc_hostname(host), ios->clock, ios->bus_mode,
+ ios->power_mode, ios->chip_select, ios->vdd,
+ ios->bus_width, ios->timing);
+
+ if (ios->clock > 0)
+ mmc_set_ungated(host);
+ host->ops->set_ios(host, ios);
+}
+
+/*
+ * Control chip select pin on a host.
+ */
+void mmc_set_chip_select(struct mmc_host *host, int mode)
+{
+ mmc_host_clk_hold(host);
+ host->ios.chip_select = mode;
+ mmc_set_ios(host);
+ mmc_host_clk_release(host);
+}
+
+/*
+ * Sets the host clock to the highest possible frequency that
+ * is below "hz".
+ */
+static void __mmc_set_clock(struct mmc_host *host, unsigned int hz)
+{
+ WARN_ON(hz < host->f_min);
+
+ if (hz > host->f_max)
+ hz = host->f_max;
+
+ host->ios.clock = hz;
+ mmc_set_ios(host);
+}
+
+void mmc_set_clock(struct mmc_host *host, unsigned int hz)
+{
+ mmc_host_clk_hold(host);
+ __mmc_set_clock(host, hz);
+ mmc_host_clk_release(host);
+}
+
+#ifdef CONFIG_MMC_CLKGATE
+/*
+ * This gates the clock by setting it to 0 Hz.
+ */
+void mmc_gate_clock(struct mmc_host *host)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->clk_lock, flags);
+ host->clk_old = host->ios.clock;
+ host->ios.clock = 0;
+ host->clk_gated = true;
+ spin_unlock_irqrestore(&host->clk_lock, flags);
+ mmc_set_ios(host);
+}
+
+/*
+ * This restores the clock from gating by using the cached
+ * clock value.
+ */
+void mmc_ungate_clock(struct mmc_host *host)
+{
+ /*
+ * We should previously have gated the clock, so the clock shall
+ * be 0 here! The clock may however be 0 during initialization,
+ * when some request operations are performed before setting
+ * the frequency. When ungate is requested in that situation
+ * we just ignore the call.
+ */
+ if (host->clk_old) {
+ BUG_ON(host->ios.clock);
+ /* This call will also set host->clk_gated to false */
+ __mmc_set_clock(host, host->clk_old);
+ }
+}
+
+void mmc_set_ungated(struct mmc_host *host)
+{
+ unsigned long flags;
+
+ /*
+ * We've been given a new frequency while the clock is gated,
+ * so make sure we regard this as ungating it.
+ */
+ spin_lock_irqsave(&host->clk_lock, flags);
+ host->clk_gated = false;
+ spin_unlock_irqrestore(&host->clk_lock, flags);
+}
+
+#else
+void mmc_set_ungated(struct mmc_host *host)
+{
+}
+#endif
+
+/*
+ * Change the bus mode (open drain/push-pull) of a host.
+ */
+void mmc_set_bus_mode(struct mmc_host *host, unsigned int mode)
+{
+ mmc_host_clk_hold(host);
+ host->ios.bus_mode = mode;
+ mmc_set_ios(host);
+ mmc_host_clk_release(host);
+}
+
+/*
+ * Change data bus width of a host.
+ */
+void mmc_set_bus_width(struct mmc_host *host, unsigned int width)
+{
+ mmc_host_clk_hold(host);
+ host->ios.bus_width = width;
+ mmc_set_ios(host);
+ mmc_host_clk_release(host);
+}
+
+/**
+ * mmc_vdd_to_ocrbitnum - Convert a voltage to the OCR bit number
+ * @vdd: voltage (mV)
+ * @low_bits: prefer low bits in boundary cases
+ *
+ * This function returns the OCR bit number according to the provided @vdd
+ * value. If conversion is not possible a negative errno value returned.
+ *
+ * Depending on the @low_bits flag the function prefers low or high OCR bits
+ * on boundary voltages. For example,
+ * with @low_bits = true, 3300 mV translates to ilog2(MMC_VDD_32_33);
+ * with @low_bits = false, 3300 mV translates to ilog2(MMC_VDD_33_34);
+ *
+ * Any value in the [1951:1999] range translates to the ilog2(MMC_VDD_20_21).
+ */
+static int mmc_vdd_to_ocrbitnum(int vdd, bool low_bits)
+{
+ const int max_bit = ilog2(MMC_VDD_35_36);
+ int bit;
+
+ if (vdd < 1650 || vdd > 3600)
+ return -EINVAL;
+
+ if (vdd >= 1650 && vdd <= 1950)
+ return ilog2(MMC_VDD_165_195);
+
+ if (low_bits)
+ vdd -= 1;
+
+ /* Base 2000 mV, step 100 mV, bit's base 8. */
+ bit = (vdd - 2000) / 100 + 8;
+ if (bit > max_bit)
+ return max_bit;
+ return bit;
+}
+
+/**
+ * mmc_vddrange_to_ocrmask - Convert a voltage range to the OCR mask
+ * @vdd_min: minimum voltage value (mV)
+ * @vdd_max: maximum voltage value (mV)
+ *
+ * This function returns the OCR mask bits according to the provided @vdd_min
+ * and @vdd_max values. If conversion is not possible the function returns 0.
+ *
+ * Notes wrt boundary cases:
+ * This function sets the OCR bits for all boundary voltages, for example
+ * [3300:3400] range is translated to MMC_VDD_32_33 | MMC_VDD_33_34 |
+ * MMC_VDD_34_35 mask.
+ */
+u32 mmc_vddrange_to_ocrmask(int vdd_min, int vdd_max)
+{
+ u32 mask = 0;
+
+ if (vdd_max < vdd_min)
+ return 0;
+
+ /* Prefer high bits for the boundary vdd_max values. */
+ vdd_max = mmc_vdd_to_ocrbitnum(vdd_max, false);
+ if (vdd_max < 0)
+ return 0;
+
+ /* Prefer low bits for the boundary vdd_min values. */
+ vdd_min = mmc_vdd_to_ocrbitnum(vdd_min, true);
+ if (vdd_min < 0)
+ return 0;
+
+ /* Fill the mask, from max bit to min bit. */
+ while (vdd_max >= vdd_min)
+ mask |= 1 << vdd_max--;
+
+ return mask;
+}
+EXPORT_SYMBOL(mmc_vddrange_to_ocrmask);
+
+#ifdef CONFIG_REGULATOR
+
+/**
+ * mmc_regulator_get_ocrmask - return mask of supported voltages
+ * @supply: regulator to use
+ *
+ * This returns either a negative errno, or a mask of voltages that
+ * can be provided to MMC/SD/SDIO devices using the specified voltage
+ * regulator. This would normally be called before registering the
+ * MMC host adapter.
+ */
+int mmc_regulator_get_ocrmask(struct regulator *supply)
+{
+ int result = 0;
+ int count;
+ int i;
+
+ count = regulator_count_voltages(supply);
+ if (count < 0)
+ return count;
+
+ for (i = 0; i < count; i++) {
+ int vdd_uV;
+ int vdd_mV;
+
+ vdd_uV = regulator_list_voltage(supply, i);
+ if (vdd_uV <= 0)
+ continue;
+
+ vdd_mV = vdd_uV / 1000;
+ result |= mmc_vddrange_to_ocrmask(vdd_mV, vdd_mV);
+ }
+
+ return result;
+}
+EXPORT_SYMBOL(mmc_regulator_get_ocrmask);
+
+/**
+ * mmc_regulator_set_ocr - set regulator to match host->ios voltage
+ * @mmc: the host to regulate
+ * @supply: regulator to use
+ * @vdd_bit: zero for power off, else a bit number (host->ios.vdd)
+ *
+ * Returns zero on success, else negative errno.
+ *
+ * MMC host drivers may use this to enable or disable a regulator using
+ * a particular supply voltage. This would normally be called from the
+ * set_ios() method.
+ */
+int mmc_regulator_set_ocr(struct mmc_host *mmc,
+ struct regulator *supply,
+ unsigned short vdd_bit)
+{
+ int result = 0;
+ int min_uV, max_uV;
+
+ if (vdd_bit) {
+ int tmp;
+ int voltage;
+
+ /* REVISIT mmc_vddrange_to_ocrmask() may have set some
+ * bits this regulator doesn't quite support ... don't
+ * be too picky, most cards and regulators are OK with
+ * a 0.1V range goof (it's a small error percentage).
+ */
+ tmp = vdd_bit - ilog2(MMC_VDD_165_195);
+ if (tmp == 0) {
+ min_uV = 1650 * 1000;
+ max_uV = 1950 * 1000;
+ } else {
+ min_uV = 1900 * 1000 + tmp * 100 * 1000;
+ max_uV = min_uV + 100 * 1000;
+ }
+
+ /* avoid needless changes to this voltage; the regulator
+ * might not allow this operation
+ */
+ voltage = regulator_get_voltage(supply);
+ if (voltage < 0)
+ result = voltage;
+ else if (voltage < min_uV || voltage > max_uV)
+ result = regulator_set_voltage(supply, min_uV, max_uV);
+ else
+ result = 0;
+
+ if (result == 0 && !mmc->regulator_enabled) {
+ result = regulator_enable(supply);
+ if (!result)
+ mmc->regulator_enabled = true;
+ }
+ } else if (mmc->regulator_enabled) {
+ result = regulator_disable(supply);
+ if (result == 0)
+ mmc->regulator_enabled = false;
+ }
+
+ if (result)
+ dev_err(mmc_dev(mmc),
+ "could not set regulator OCR (%d)\n", result);
+ return result;
+}
+EXPORT_SYMBOL(mmc_regulator_set_ocr);
+
+#endif /* CONFIG_REGULATOR */
+
+/*
+ * Mask off any voltages we don't support and select
+ * the lowest voltage
+ */
+u32 mmc_select_voltage(struct mmc_host *host, u32 ocr)
+{
+ int bit;
+
+ ocr &= host->ocr_avail;
+
+ bit = ffs(ocr);
+ if (bit) {
+ bit -= 1;
+
+ ocr &= 3 << bit;
+
+ mmc_host_clk_hold(host);
+ host->ios.vdd = bit;
+ mmc_set_ios(host);
+ mmc_host_clk_release(host);
+ } else {
+ pr_warning("%s: host doesn't support card's voltages\n",
+ mmc_hostname(host));
+ ocr = 0;
+ }
+
+ return ocr;
+}
+
+int mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage, bool cmd11)
+{
+ struct mmc_command cmd = {0};
+ int err = 0;
+
+ BUG_ON(!host);
+
+ /*
+ * Send CMD11 only if the request is to switch the card to
+ * 1.8V signalling.
+ */
+ if ((signal_voltage != MMC_SIGNAL_VOLTAGE_330) && cmd11) {
+ cmd.opcode = SD_SWITCH_VOLTAGE;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
+
+ err = mmc_wait_for_cmd(host, &cmd, 0);
+ if (err)
+ return err;
+
+ if (!mmc_host_is_spi(host) && (cmd.resp[0] & R1_ERROR))
+ return -EIO;
+ }
+
+ host->ios.signal_voltage = signal_voltage;
+
+ if (host->ops->start_signal_voltage_switch)
+ err = host->ops->start_signal_voltage_switch(host, &host->ios);
+
+ return err;
+}
+
+/*
+ * Select timing parameters for host.
+ */
+void mmc_set_timing(struct mmc_host *host, unsigned int timing)
+{
+ mmc_host_clk_hold(host);
+ host->ios.timing = timing;
+ mmc_set_ios(host);
+ mmc_host_clk_release(host);
+}
+
+/*
+ * Select appropriate driver type for host.
+ */
+void mmc_set_driver_type(struct mmc_host *host, unsigned int drv_type)
+{
+ mmc_host_clk_hold(host);
+ host->ios.drv_type = drv_type;
+ mmc_set_ios(host);
+ mmc_host_clk_release(host);
+}
+
+/*
+ * Apply power to the MMC stack. This is a two-stage process.
+ * First, we enable power to the card without the clock running.
+ * We then wait a bit for the power to stabilise. Finally,
+ * enable the bus drivers and clock to the card.
+ *
+ * We must _NOT_ enable the clock prior to power stablising.
+ *
+ * If a host does all the power sequencing itself, ignore the
+ * initial MMC_POWER_UP stage.
+ */
+static void mmc_power_up(struct mmc_host *host)
+{
+ int bit;
+
+ mmc_host_clk_hold(host);
+
+ /* If ocr is set, we use it */
+ if (host->ocr)
+ bit = ffs(host->ocr) - 1;
+ else
+ bit = fls(host->ocr_avail) - 1;
+
+ host->ios.vdd = bit;
+ if (mmc_host_is_spi(host)) {
+ host->ios.chip_select = MMC_CS_HIGH;
+ host->ios.bus_mode = MMC_BUSMODE_PUSHPULL;
+ } else {
+ host->ios.chip_select = MMC_CS_DONTCARE;
+ host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN;
+ }
+ host->ios.power_mode = MMC_POWER_UP;
+ host->ios.bus_width = MMC_BUS_WIDTH_1;
+ host->ios.timing = MMC_TIMING_LEGACY;
+ mmc_set_ios(host);
+
+ /*
+ * This delay should be sufficient to allow the power supply
+ * to reach the minimum voltage.
+ */
+ mmc_delay(10);
+
+ host->ios.clock = host->f_init;
+
+ host->ios.power_mode = MMC_POWER_ON;
+ mmc_set_ios(host);
+
+ /*
+ * This delay must be at least 74 clock sizes, or 1 ms, or the
+ * time required to reach a stable voltage.
+ */
+ mmc_delay(10);
+
+ mmc_host_clk_release(host);
+}
+
+void mmc_power_off(struct mmc_host *host)
+{
+ mmc_host_clk_hold(host);
+
+ host->ios.clock = 0;
+ host->ios.vdd = 0;
+
+ /*
+ * Reset ocr mask to be the highest possible voltage supported for
+ * this mmc host. This value will be used at next power up.
+ */
+ host->ocr = 1 << (fls(host->ocr_avail) - 1);
+
+ if (!mmc_host_is_spi(host)) {
+ host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN;
+ host->ios.chip_select = MMC_CS_DONTCARE;
+ }
+ host->ios.power_mode = MMC_POWER_OFF;
+ host->ios.bus_width = MMC_BUS_WIDTH_1;
+ host->ios.timing = MMC_TIMING_LEGACY;
+ mmc_set_ios(host);
+
+ mmc_host_clk_release(host);
+}
+
+/*
+ * Cleanup when the last reference to the bus operator is dropped.
+ */
+static void __mmc_release_bus(struct mmc_host *host)
+{
+ BUG_ON(!host);
+ BUG_ON(host->bus_refs);
+ BUG_ON(!host->bus_dead);
+
+ host->bus_ops = NULL;
+}
+
+/*
+ * Increase reference count of bus operator
+ */
+static inline void mmc_bus_get(struct mmc_host *host)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+ host->bus_refs++;
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+/*
+ * Decrease reference count of bus operator and free it if
+ * it is the last reference.
+ */
+static inline void mmc_bus_put(struct mmc_host *host)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+ host->bus_refs--;
+ if ((host->bus_refs == 0) && host->bus_ops)
+ __mmc_release_bus(host);
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+int mmc_resume_bus(struct mmc_host *host)
+{
+ unsigned long flags;
+
+ if (!mmc_bus_needs_resume(host))
+ return -EINVAL;
+
+ printk("%s: Starting deferred resume\n", mmc_hostname(host));
+ spin_lock_irqsave(&host->lock, flags);
+ host->bus_resume_flags &= ~MMC_BUSRESUME_NEEDS_RESUME;
+ host->rescan_disable = 0;
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ mmc_bus_get(host);
+ if (host->bus_ops && !host->bus_dead) {
+ mmc_power_up(host);
+ BUG_ON(!host->bus_ops->resume);
+ host->bus_ops->resume(host);
+ }
+
+ if (host->bus_ops->detect && !host->bus_dead)
+ host->bus_ops->detect(host);
+
+ mmc_bus_put(host);
+ printk("%s: Deferred resume completed\n", mmc_hostname(host));
+ return 0;
+}
+
+EXPORT_SYMBOL(mmc_resume_bus);
+
+/*
+ * Assign a mmc bus handler to a host. Only one bus handler may control a
+ * host at any given time.
+ */
+void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops)
+{
+ unsigned long flags;
+
+ BUG_ON(!host);
+ BUG_ON(!ops);
+
+ WARN_ON(!host->claimed);
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ BUG_ON(host->bus_ops);
+ BUG_ON(host->bus_refs);
+
+ host->bus_ops = ops;
+ host->bus_refs = 1;
+ host->bus_dead = 0;
+
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+/*
+ * Remove the current bus handler from a host.
+ */
+void mmc_detach_bus(struct mmc_host *host)
+{
+ unsigned long flags;
+
+ BUG_ON(!host);
+
+ WARN_ON(!host->claimed);
+ WARN_ON(!host->bus_ops);
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ host->bus_dead = 1;
+
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ mmc_bus_put(host);
+}
+
+/**
+ * mmc_detect_change - process change of state on a MMC socket
+ * @host: host which changed state.
+ * @delay: optional delay to wait before detection (jiffies)
+ *
+ * MMC drivers should call this when they detect a card has been
+ * inserted or removed. The MMC layer will confirm that any
+ * present card is still functional, and initialize any newly
+ * inserted.
+ */
+void mmc_detect_change(struct mmc_host *host, unsigned long delay)
+{
+#ifdef CONFIG_MMC_DEBUG
+ unsigned long flags;
+ spin_lock_irqsave(&host->lock, flags);
+ WARN_ON(host->removed);
+ spin_unlock_irqrestore(&host->lock, flags);
+#endif
+
+ wake_lock(&host->detect_wake_lock);
+ mmc_schedule_delayed_work(&host->detect, delay);
+}
+
+EXPORT_SYMBOL(mmc_detect_change);
+
+void mmc_init_erase(struct mmc_card *card)
+{
+ unsigned int sz;
+
+ if (is_power_of_2(card->erase_size))
+ card->erase_shift = ffs(card->erase_size) - 1;
+ else
+ card->erase_shift = 0;
+
+ /*
+ * It is possible to erase an arbitrarily large area of an SD or MMC
+ * card. That is not desirable because it can take a long time
+ * (minutes) potentially delaying more important I/O, and also the
+ * timeout calculations become increasingly hugely over-estimated.
+ * Consequently, 'pref_erase' is defined as a guide to limit erases
+ * to that size and alignment.
+ *
+ * For SD cards that define Allocation Unit size, limit erases to one
+ * Allocation Unit at a time. For MMC cards that define High Capacity
+ * Erase Size, whether it is switched on or not, limit to that size.
+ * Otherwise just have a stab at a good value. For modern cards it
+ * will end up being 4MiB. Note that if the value is too small, it
+ * can end up taking longer to erase.
+ */
+ if (mmc_card_sd(card) && card->ssr.au) {
+ card->pref_erase = card->ssr.au;
+ card->erase_shift = ffs(card->ssr.au) - 1;
+ } else if (card->ext_csd.hc_erase_size) {
+ card->pref_erase = card->ext_csd.hc_erase_size;
+ } else {
+ sz = (card->csd.capacity << (card->csd.read_blkbits - 9)) >> 11;
+ if (sz < 128)
+ card->pref_erase = 512 * 1024 / 512;
+ else if (sz < 512)
+ card->pref_erase = 1024 * 1024 / 512;
+ else if (sz < 1024)
+ card->pref_erase = 2 * 1024 * 1024 / 512;
+ else
+ card->pref_erase = 4 * 1024 * 1024 / 512;
+ if (card->pref_erase < card->erase_size)
+ card->pref_erase = card->erase_size;
+ else {
+ sz = card->pref_erase % card->erase_size;
+ if (sz)
+ card->pref_erase += card->erase_size - sz;
+ }
+ }
+}
+
+static unsigned int mmc_mmc_erase_timeout(struct mmc_card *card,
+ unsigned int arg, unsigned int qty)
+{
+ unsigned int erase_timeout;
+
+ if (card->ext_csd.erase_group_def & 1) {
+ /* High Capacity Erase Group Size uses HC timeouts */
+ if (arg == MMC_TRIM_ARG)
+ erase_timeout = card->ext_csd.trim_timeout;
+ else
+ erase_timeout = card->ext_csd.hc_erase_timeout;
+ } else {
+ /* CSD Erase Group Size uses write timeout */
+ unsigned int mult = (10 << card->csd.r2w_factor);
+ unsigned int timeout_clks = card->csd.tacc_clks * mult;
+ unsigned int timeout_us;
+
+ /* Avoid overflow: e.g. tacc_ns=80000000 mult=1280 */
+ if (card->csd.tacc_ns < 1000000)
+ timeout_us = (card->csd.tacc_ns * mult) / 1000;
+ else
+ timeout_us = (card->csd.tacc_ns / 1000) * mult;
+
+ /*
+ * ios.clock is only a target. The real clock rate might be
+ * less but not that much less, so fudge it by multiplying by 2.
+ */
+ timeout_clks <<= 1;
+ timeout_us += (timeout_clks * 1000) /
+ (mmc_host_clk_rate(card->host) / 1000);
+
+ erase_timeout = timeout_us / 1000;
+
+ /*
+ * Theoretically, the calculation could underflow so round up
+ * to 1ms in that case.
+ */
+ if (!erase_timeout)
+ erase_timeout = 1;
+ }
+
+ /* Multiplier for secure operations */
+ if (arg & MMC_SECURE_ARGS) {
+ if (arg == MMC_SECURE_ERASE_ARG)
+ erase_timeout *= card->ext_csd.sec_erase_mult;
+ else
+ erase_timeout *= card->ext_csd.sec_trim_mult;
+ }
+
+ erase_timeout *= qty;
+
+ /*
+ * Ensure at least a 1 second timeout for SPI as per
+ * 'mmc_set_data_timeout()'
+ */
+ if (mmc_host_is_spi(card->host) && erase_timeout < 1000)
+ erase_timeout = 1000;
+
+ return erase_timeout;
+}
+
+static unsigned int mmc_sd_erase_timeout(struct mmc_card *card,
+ unsigned int arg,
+ unsigned int qty)
+{
+ unsigned int erase_timeout;
+
+ if (card->ssr.erase_timeout) {
+ /* Erase timeout specified in SD Status Register (SSR) */
+ erase_timeout = card->ssr.erase_timeout * qty +
+ card->ssr.erase_offset;
+ } else {
+ /*
+ * Erase timeout not specified in SD Status Register (SSR) so
+ * use 250ms per write block.
+ */
+ erase_timeout = 250 * qty;
+ }
+
+ /* Must not be less than 1 second */
+ if (erase_timeout < 1000)
+ erase_timeout = 1000;
+
+ return erase_timeout;
+}
+
+static unsigned int mmc_erase_timeout(struct mmc_card *card,
+ unsigned int arg,
+ unsigned int qty)
+{
+ if (mmc_card_sd(card))
+ return mmc_sd_erase_timeout(card, arg, qty);
+ else
+ return mmc_mmc_erase_timeout(card, arg, qty);
+}
+
+static int mmc_do_erase(struct mmc_card *card, unsigned int from,
+ unsigned int to, unsigned int arg)
+{
+ struct mmc_command cmd = {0};
+ unsigned int qty = 0;
+ int err;
+
+ /*
+ * qty is used to calculate the erase timeout which depends on how many
+ * erase groups (or allocation units in SD terminology) are affected.
+ * We count erasing part of an erase group as one erase group.
+ * For SD, the allocation units are always a power of 2. For MMC, the
+ * erase group size is almost certainly also power of 2, but it does not
+ * seem to insist on that in the JEDEC standard, so we fall back to
+ * division in that case. SD may not specify an allocation unit size,
+ * in which case the timeout is based on the number of write blocks.
+ *
+ * Note that the timeout for secure trim 2 will only be correct if the
+ * number of erase groups specified is the same as the total of all
+ * preceding secure trim 1 commands. Since the power may have been
+ * lost since the secure trim 1 commands occurred, it is generally
+ * impossible to calculate the secure trim 2 timeout correctly.
+ */
+ if (card->erase_shift)
+ qty += ((to >> card->erase_shift) -
+ (from >> card->erase_shift)) + 1;
+ else if (mmc_card_sd(card))
+ qty += to - from + 1;
+ else
+ qty += ((to / card->erase_size) -
+ (from / card->erase_size)) + 1;
+
+ if (!mmc_card_blockaddr(card)) {
+ from <<= 9;
+ to <<= 9;
+ }
+
+ if (mmc_card_sd(card))
+ cmd.opcode = SD_ERASE_WR_BLK_START;
+ else
+ cmd.opcode = MMC_ERASE_GROUP_START;
+ cmd.arg = from;
+ cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC;
+ err = mmc_wait_for_cmd(card->host, &cmd, 0);
+ if (err) {
+ printk(KERN_ERR "mmc_erase: group start error %d, "
+ "status %#x\n", err, cmd.resp[0]);
+ err = -EINVAL;
+ goto out;
+ }
+
+ memset(&cmd, 0, sizeof(struct mmc_command));
+ if (mmc_card_sd(card))
+ cmd.opcode = SD_ERASE_WR_BLK_END;
+ else
+ cmd.opcode = MMC_ERASE_GROUP_END;
+ cmd.arg = to;
+ cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC;
+ err = mmc_wait_for_cmd(card->host, &cmd, 0);
+ if (err) {
+ printk(KERN_ERR "mmc_erase: group end error %d, status %#x\n",
+ err, cmd.resp[0]);
+ err = -EINVAL;
+ goto out;
+ }
+
+ memset(&cmd, 0, sizeof(struct mmc_command));
+ cmd.opcode = MMC_ERASE;
+ cmd.arg = arg;
+ cmd.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
+ cmd.cmd_timeout_ms = mmc_erase_timeout(card, arg, qty);
+ err = mmc_wait_for_cmd(card->host, &cmd, 0);
+ if (err) {
+ printk(KERN_ERR "mmc_erase: erase error %d, status %#x\n",
+ err, cmd.resp[0]);
+ err = -EIO;
+ goto out;
+ }
+
+ if (mmc_host_is_spi(card->host))
+ goto out;
+
+ do {
+ memset(&cmd, 0, sizeof(struct mmc_command));
+ cmd.opcode = MMC_SEND_STATUS;
+ cmd.arg = card->rca << 16;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
+ /* Do not retry else we can't see errors */
+ err = mmc_wait_for_cmd(card->host, &cmd, 0);
+ if (err || (cmd.resp[0] & 0xFDF92000)) {
+ printk(KERN_ERR "error %d requesting status %#x\n",
+ err, cmd.resp[0]);
+ err = -EIO;
+ goto out;
+ }
+ } while (!(cmd.resp[0] & R1_READY_FOR_DATA) ||
+ R1_CURRENT_STATE(cmd.resp[0]) == 7);
+out:
+ return err;
+}
+
+/**
+ * mmc_erase - erase sectors.
+ * @card: card to erase
+ * @from: first sector to erase
+ * @nr: number of sectors to erase
+ * @arg: erase command argument (SD supports only %MMC_ERASE_ARG)
+ *
+ * Caller must claim host before calling this function.
+ */
+int mmc_erase(struct mmc_card *card, unsigned int from, unsigned int nr,
+ unsigned int arg)
+{
+ unsigned int rem, to = from + nr;
+
+ if (!(card->host->caps & MMC_CAP_ERASE) ||
+ !(card->csd.cmdclass & CCC_ERASE))
+ return -EOPNOTSUPP;
+
+ if (!card->erase_size)
+ return -EOPNOTSUPP;
+
+ if (mmc_card_sd(card) && arg != MMC_ERASE_ARG)
+ return -EOPNOTSUPP;
+
+ if ((arg & MMC_SECURE_ARGS) &&
+ !(card->ext_csd.sec_feature_support & EXT_CSD_SEC_ER_EN))
+ return -EOPNOTSUPP;
+
+ if ((arg & MMC_TRIM_ARGS) &&
+ !(card->ext_csd.sec_feature_support & EXT_CSD_SEC_GB_CL_EN))
+ return -EOPNOTSUPP;
+
+ if (arg == MMC_SECURE_ERASE_ARG) {
+ if (from % card->erase_size || nr % card->erase_size)
+ return -EINVAL;
+ }
+
+ if (arg == MMC_ERASE_ARG) {
+ rem = from % card->erase_size;
+ if (rem) {
+ rem = card->erase_size - rem;
+ from += rem;
+ if (nr > rem)
+ nr -= rem;
+ else
+ return 0;
+ }
+ rem = nr % card->erase_size;
+ if (rem)
+ nr -= rem;
+ }
+
+ if (nr == 0)
+ return 0;
+
+ to = from + nr;
+
+ if (to <= from)
+ return -EINVAL;
+
+ /* 'from' and 'to' are inclusive */
+ to -= 1;
+
+ return mmc_do_erase(card, from, to, arg);
+}
+EXPORT_SYMBOL(mmc_erase);
+
+int mmc_can_erase(struct mmc_card *card)
+{
+ if ((card->host->caps & MMC_CAP_ERASE) &&
+ (card->csd.cmdclass & CCC_ERASE) && card->erase_size)
+ return 1;
+ return 0;
+}
+EXPORT_SYMBOL(mmc_can_erase);
+
+int mmc_can_trim(struct mmc_card *card)
+{
+ if (card->ext_csd.sec_feature_support & EXT_CSD_SEC_GB_CL_EN)
+ return 1;
+ return 0;
+}
+EXPORT_SYMBOL(mmc_can_trim);
+
+int mmc_can_secure_erase_trim(struct mmc_card *card)
+{
+ if (card->ext_csd.sec_feature_support & EXT_CSD_SEC_ER_EN)
+ return 1;
+ return 0;
+}
+EXPORT_SYMBOL(mmc_can_secure_erase_trim);
+
+int mmc_erase_group_aligned(struct mmc_card *card, unsigned int from,
+ unsigned int nr)
+{
+ if (!card->erase_size)
+ return 0;
+ if (from % card->erase_size || nr % card->erase_size)
+ return 0;
+ return 1;
+}
+EXPORT_SYMBOL(mmc_erase_group_aligned);
+
+static unsigned int mmc_do_calc_max_discard(struct mmc_card *card,
+ unsigned int arg)
+{
+ struct mmc_host *host = card->host;
+ unsigned int max_discard, x, y, qty = 0, max_qty, timeout;
+ unsigned int last_timeout = 0;
+
+ if (card->erase_shift)
+ max_qty = UINT_MAX >> card->erase_shift;
+ else if (mmc_card_sd(card))
+ max_qty = UINT_MAX;
+ else
+ max_qty = UINT_MAX / card->erase_size;
+
+ /* Find the largest qty with an OK timeout */
+ do {
+ y = 0;
+ for (x = 1; x && x <= max_qty && max_qty - x >= qty; x <<= 1) {
+ timeout = mmc_erase_timeout(card, arg, qty + x);
+ if (timeout > host->max_discard_to)
+ break;
+ if (timeout < last_timeout)
+ break;
+ last_timeout = timeout;
+ y = x;
+ }
+ qty += y;
+ } while (y);
+
+ if (!qty)
+ return 0;
+
+ if (qty == 1)
+ return 1;
+
+ /* Convert qty to sectors */
+ if (card->erase_shift)
+ max_discard = --qty << card->erase_shift;
+ else if (mmc_card_sd(card))
+ max_discard = qty;
+ else
+ max_discard = --qty * card->erase_size;
+
+ return max_discard;
+}
+
+unsigned int mmc_calc_max_discard(struct mmc_card *card)
+{
+ struct mmc_host *host = card->host;
+ unsigned int max_discard, max_trim;
+
+ if (!host->max_discard_to)
+ return UINT_MAX;
+
+ /*
+ * Without erase_group_def set, MMC erase timeout depends on clock
+ * frequence which can change. In that case, the best choice is
+ * just the preferred erase size.
+ */
+ if (mmc_card_mmc(card) && !(card->ext_csd.erase_group_def & 1))
+ return card->pref_erase;
+
+ max_discard = mmc_do_calc_max_discard(card, MMC_ERASE_ARG);
+ if (mmc_can_trim(card)) {
+ max_trim = mmc_do_calc_max_discard(card, MMC_TRIM_ARG);
+ if (max_trim < max_discard)
+ max_discard = max_trim;
+ } else if (max_discard < card->erase_size) {
+ max_discard = 0;
+ }
+ pr_debug("%s: calculated max. discard sectors %u for timeout %u ms\n",
+ mmc_hostname(host), max_discard, host->max_discard_to);
+ return max_discard;
+}
+EXPORT_SYMBOL(mmc_calc_max_discard);
+
+int mmc_set_blocklen(struct mmc_card *card, unsigned int blocklen)
+{
+ struct mmc_command cmd = {0};
+
+ if (mmc_card_blockaddr(card) || mmc_card_ddr_mode(card))
+ return 0;
+
+ cmd.opcode = MMC_SET_BLOCKLEN;
+ cmd.arg = blocklen;
+ cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC;
+ return mmc_wait_for_cmd(card->host, &cmd, 5);
+}
+EXPORT_SYMBOL(mmc_set_blocklen);
+
+static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
+{
+ host->f_init = freq;
+
+#ifdef CONFIG_MMC_DEBUG
+ pr_info("%s: %s: trying to init card at %u Hz\n",
+ mmc_hostname(host), __func__, host->f_init);
+#endif
+ mmc_power_up(host);
+
+ /*
+ * sdio_reset sends CMD52 to reset card. Since we do not know
+ * if the card is being re-initialized, just send it. CMD52
+ * should be ignored by SD/eMMC cards.
+ */
+ sdio_reset(host);
+ mmc_go_idle(host);
+
+ mmc_send_if_cond(host, host->ocr_avail);
+
+ /* Order's important: probe SDIO, then SD, then MMC */
+ if (!mmc_attach_sdio(host))
+ return 0;
+ if (!mmc_attach_sd(host))
+ return 0;
+ if (!mmc_attach_mmc(host))
+ return 0;
+
+ mmc_power_off(host);
+ return -EIO;
+}
+
+void mmc_rescan(struct work_struct *work)
+{
+ static const unsigned freqs[] = { 400000, 300000, 200000, 100000 };
+ struct mmc_host *host =
+ container_of(work, struct mmc_host, detect.work);
+ int i;
+ bool extend_wakelock = false;
+
+ if (host->rescan_disable)
+ return;
+
+ mmc_bus_get(host);
+
+ /*
+ * if there is a _removable_ card registered, check whether it is
+ * still present
+ */
+ if (host->bus_ops && host->bus_ops->detect && !host->bus_dead
+ && !(host->caps & MMC_CAP_NONREMOVABLE))
+ host->bus_ops->detect(host);
+
+ /* If the card was removed the bus will be marked
+ * as dead - extend the wakelock so userspace
+ * can respond */
+ if (host->bus_dead)
+ extend_wakelock = 1;
+
+ /*
+ * Let mmc_bus_put() free the bus/bus_ops if we've found that
+ * the card is no longer present.
+ */
+ mmc_bus_put(host);
+ mmc_bus_get(host);
+
+ /* if there still is a card present, stop here */
+ if (host->bus_ops != NULL) {
+ mmc_bus_put(host);
+ goto out;
+ }
+
+ /*
+ * Only we can add a new handler, so it's safe to
+ * release the lock here.
+ */
+ mmc_bus_put(host);
+
+ if (host->ops->get_cd && host->ops->get_cd(host) == 0)
+ goto out;
+
+ mmc_claim_host(host);
+ for (i = 0; i < ARRAY_SIZE(freqs); i++) {
+ if (!mmc_rescan_try_freq(host, max(freqs[i], host->f_min))) {
+ extend_wakelock = true;
+ break;
+ }
+ if (freqs[i] <= host->f_min)
+ break;
+ }
+ mmc_release_host(host);
+
+ out:
+ if (extend_wakelock)
+ wake_lock_timeout(&host->detect_wake_lock, HZ / 2);
+ else
+ wake_unlock(&host->detect_wake_lock);
+ if (host->caps & MMC_CAP_NEEDS_POLL) {
+ wake_lock(&host->detect_wake_lock);
+ mmc_schedule_delayed_work(&host->detect, HZ);
+ }
+}
+
+void mmc_start_host(struct mmc_host *host)
+{
+ mmc_power_off(host);
+ mmc_detect_change(host, 0);
+}
+
+void mmc_stop_host(struct mmc_host *host)
+{
+#ifdef CONFIG_MMC_DEBUG
+ unsigned long flags;
+ spin_lock_irqsave(&host->lock, flags);
+ host->removed = 1;
+ spin_unlock_irqrestore(&host->lock, flags);
+#endif
+
+ if (host->caps & MMC_CAP_DISABLE)
+ cancel_delayed_work(&host->disable);
+ if (cancel_delayed_work_sync(&host->detect))
+ wake_unlock(&host->detect_wake_lock);
+ mmc_flush_scheduled_work();
+
+ /* clear pm flags now and let card drivers set them as needed */
+ host->pm_flags = 0;
+
+ mmc_bus_get(host);
+ if (host->bus_ops && !host->bus_dead) {
+ if (host->bus_ops->remove)
+ host->bus_ops->remove(host);
+
+ mmc_claim_host(host);
+ mmc_detach_bus(host);
+ mmc_power_off(host);
+ mmc_release_host(host);
+ mmc_bus_put(host);
+ return;
+ }
+ mmc_bus_put(host);
+
+ BUG_ON(host->card);
+
+ mmc_power_off(host);
+}
+
+int mmc_power_save_host(struct mmc_host *host)
+{
+ int ret = 0;
+
+ mmc_bus_get(host);
+
+ if (!host->bus_ops || host->bus_dead || !host->bus_ops->power_restore) {
+ mmc_bus_put(host);
+ return -EINVAL;
+ }
+
+ if (host->bus_ops->power_save)
+ ret = host->bus_ops->power_save(host);
+
+ mmc_bus_put(host);
+
+ mmc_power_off(host);
+
+ return ret;
+}
+EXPORT_SYMBOL(mmc_power_save_host);
+
+int mmc_power_restore_host(struct mmc_host *host)
+{
+ int ret;
+
+ mmc_bus_get(host);
+
+ if (!host->bus_ops || host->bus_dead || !host->bus_ops->power_restore) {
+ mmc_bus_put(host);
+ return -EINVAL;
+ }
+
+ mmc_power_up(host);
+ ret = host->bus_ops->power_restore(host);
+
+ mmc_bus_put(host);
+
+ return ret;
+}
+EXPORT_SYMBOL(mmc_power_restore_host);
+
+int mmc_card_awake(struct mmc_host *host)
+{
+ int err = -ENOSYS;
+
+ mmc_bus_get(host);
+
+ if (host->bus_ops && !host->bus_dead && host->bus_ops->awake)
+ err = host->bus_ops->awake(host);
+
+ mmc_bus_put(host);
+
+ return err;
+}
+EXPORT_SYMBOL(mmc_card_awake);
+
+int mmc_card_sleep(struct mmc_host *host)
+{
+ int err = -ENOSYS;
+
+ mmc_bus_get(host);
+
+ if (host->bus_ops && !host->bus_dead && host->bus_ops->awake)
+ err = host->bus_ops->sleep(host);
+
+ mmc_bus_put(host);
+
+ return err;
+}
+EXPORT_SYMBOL(mmc_card_sleep);
+
+int mmc_card_can_sleep(struct mmc_host *host)
+{
+ struct mmc_card *card = host->card;
+
+ if (card && mmc_card_mmc(card) && card->ext_csd.rev >= 3)
+ return 1;
+ return 0;
+}
+EXPORT_SYMBOL(mmc_card_can_sleep);
+
+#ifdef CONFIG_PM
+
+/**
+ * mmc_suspend_host - suspend a host
+ * @host: mmc host
+ */
+int mmc_suspend_host(struct mmc_host *host)
+{
+ int err = 0;
+
+ if (mmc_bus_needs_resume(host))
+ return 0;
+
+ if (host->caps & MMC_CAP_DISABLE)
+ cancel_delayed_work(&host->disable);
+ if (cancel_delayed_work(&host->detect))
+ wake_unlock(&host->detect_wake_lock);
+ mmc_flush_scheduled_work();
+
+ mmc_bus_get(host);
+ if (host->bus_ops && !host->bus_dead) {
+ if (host->bus_ops->suspend)
+ err = host->bus_ops->suspend(host);
+ if (err == -ENOSYS || !host->bus_ops->resume) {
+ /*
+ * We simply "remove" the card in this case.
+ * It will be redetected on resume.
+ */
+ if (host->bus_ops->remove)
+ host->bus_ops->remove(host);
+ mmc_claim_host(host);
+ mmc_detach_bus(host);
+ mmc_power_off(host);
+ mmc_release_host(host);
+ host->pm_flags = 0;
+ err = 0;
+ }
+ }
+ mmc_bus_put(host);
+
+ if (!err && !mmc_card_keep_power(host))
+ mmc_power_off(host);
+
+ return err;
+}
+
+EXPORT_SYMBOL(mmc_suspend_host);
+
+/**
+ * mmc_resume_host - resume a previously suspended host
+ * @host: mmc host
+ */
+int mmc_resume_host(struct mmc_host *host)
+{
+ int err = 0;
+
+ mmc_bus_get(host);
+ if (mmc_bus_manual_resume(host)) {
+ host->bus_resume_flags |= MMC_BUSRESUME_NEEDS_RESUME;
+ mmc_bus_put(host);
+ return 0;
+ }
+
+ if (host->bus_ops && !host->bus_dead) {
+ if (!mmc_card_keep_power(host)) {
+ mmc_power_up(host);
+ mmc_select_voltage(host, host->ocr);
+ /*
+ * Tell runtime PM core we just powered up the card,
+ * since it still believes the card is powered off.
+ * Note that currently runtime PM is only enabled
+ * for SDIO cards that are MMC_CAP_POWER_OFF_CARD
+ */
+ if (mmc_card_sdio(host->card) &&
+ (host->caps & MMC_CAP_POWER_OFF_CARD)) {
+ pm_runtime_disable(&host->card->dev);
+ pm_runtime_set_active(&host->card->dev);
+ pm_runtime_enable(&host->card->dev);
+ }
+ }
+ BUG_ON(!host->bus_ops->resume);
+ err = host->bus_ops->resume(host);
+ if (err) {
+ printk(KERN_WARNING "%s: error %d during resume "
+ "(card was removed?)\n",
+ mmc_hostname(host), err);
+ err = 0;
+ }
+ }
+ host->pm_flags &= ~MMC_PM_KEEP_POWER;
+ mmc_bus_put(host);
+
+ return err;
+}
+EXPORT_SYMBOL(mmc_resume_host);
+
+/* Do the card removal on suspend if card is assumed removeable
+ * Do that in pm notifier while userspace isn't yet frozen, so we will be able
+ to sync the card.
+*/
+int mmc_pm_notify(struct notifier_block *notify_block,
+ unsigned long mode, void *unused)
+{
+ struct mmc_host *host = container_of(
+ notify_block, struct mmc_host, pm_notify);
+ unsigned long flags;
+
+
+ switch (mode) {
+ case PM_HIBERNATION_PREPARE:
+ case PM_SUSPEND_PREPARE:
+
+ spin_lock_irqsave(&host->lock, flags);
+ if (mmc_bus_needs_resume(host)) {
+ spin_unlock_irqrestore(&host->lock, flags);
+ break;
+ }
+ host->rescan_disable = 1;
+ spin_unlock_irqrestore(&host->lock, flags);
+ if (cancel_delayed_work_sync(&host->detect))
+ wake_unlock(&host->detect_wake_lock);
+
+ if (!host->bus_ops || host->bus_ops->suspend)
+ break;
+
+ mmc_claim_host(host);
+
+ if (host->bus_ops->remove)
+ host->bus_ops->remove(host);
+
+ mmc_detach_bus(host);
+ mmc_power_off(host);
+ mmc_release_host(host);
+ host->pm_flags = 0;
+ break;
+
+ case PM_POST_SUSPEND:
+ case PM_POST_HIBERNATION:
+ case PM_POST_RESTORE:
+
+ spin_lock_irqsave(&host->lock, flags);
+ if (mmc_bus_manual_resume(host)) {
+ spin_unlock_irqrestore(&host->lock, flags);
+ break;
+ }
+ host->rescan_disable = 0;
+ spin_unlock_irqrestore(&host->lock, flags);
+ mmc_detect_change(host, 0);
+
+ }
+
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_MMC_EMBEDDED_SDIO
+void mmc_set_embedded_sdio_data(struct mmc_host *host,
+ struct sdio_cis *cis,
+ struct sdio_cccr *cccr,
+ struct sdio_embedded_func *funcs,
+ int num_funcs)
+{
+ host->embedded_sdio_data.cis = cis;
+ host->embedded_sdio_data.cccr = cccr;
+ host->embedded_sdio_data.funcs = funcs;
+ host->embedded_sdio_data.num_funcs = num_funcs;
+}
+
+EXPORT_SYMBOL(mmc_set_embedded_sdio_data);
+#endif
+
+static int __init mmc_init(void)
+{
+ int ret;
+
+ workqueue = alloc_ordered_workqueue("kmmcd", 0);
+ if (!workqueue)
+ return -ENOMEM;
+
+ ret = mmc_register_bus();
+ if (ret)
+ goto destroy_workqueue;
+
+ ret = mmc_register_host_class();
+ if (ret)
+ goto unregister_bus;
+
+ ret = sdio_register_bus();
+ if (ret)
+ goto unregister_host_class;
+
+ return 0;
+
+unregister_host_class:
+ mmc_unregister_host_class();
+unregister_bus:
+ mmc_unregister_bus();
+destroy_workqueue:
+ destroy_workqueue(workqueue);
+
+ return ret;
+}
+
+static void __exit mmc_exit(void)
+{
+ sdio_unregister_bus();
+ mmc_unregister_host_class();
+ mmc_unregister_bus();
+ destroy_workqueue(workqueue);
+}
+
+subsys_initcall(mmc_init);
+module_exit(mmc_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h
new file mode 100644
index 00000000..afed70f7
--- /dev/null
+++ b/drivers/mmc/core/core.h
@@ -0,0 +1,79 @@
+/*
+ * linux/drivers/mmc/core/core.h
+ *
+ * Copyright (C) 2003 Russell King, All Rights Reserved.
+ * Copyright 2007 Pierre Ossman
+ *
+ * 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 _MMC_CORE_CORE_H
+#define _MMC_CORE_CORE_H
+
+#include <linux/delay.h>
+
+#define MMC_CMD_RETRIES 3
+
+struct mmc_bus_ops {
+ int (*awake)(struct mmc_host *);
+ int (*sleep)(struct mmc_host *);
+ void (*remove)(struct mmc_host *);
+ void (*detect)(struct mmc_host *);
+ int (*suspend)(struct mmc_host *);
+ int (*resume)(struct mmc_host *);
+ int (*power_save)(struct mmc_host *);
+ int (*power_restore)(struct mmc_host *);
+};
+
+void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops);
+void mmc_detach_bus(struct mmc_host *host);
+
+void mmc_init_erase(struct mmc_card *card);
+
+void mmc_set_chip_select(struct mmc_host *host, int mode);
+void mmc_set_clock(struct mmc_host *host, unsigned int hz);
+void mmc_set_tuning(struct mmc_host *host, unsigned int tuning);
+void mmc_finish_tuning(struct mmc_host *host);
+void mmc_gate_clock(struct mmc_host *host);
+void mmc_ungate_clock(struct mmc_host *host);
+void mmc_set_ungated(struct mmc_host *host);
+void mmc_set_bus_mode(struct mmc_host *host, unsigned int mode);
+void mmc_set_bus_width(struct mmc_host *host, unsigned int width);
+u32 mmc_select_voltage(struct mmc_host *host, u32 ocr);
+int mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage,
+ bool cmd11);
+void mmc_set_timing(struct mmc_host *host, unsigned int timing);
+void mmc_set_driver_type(struct mmc_host *host, unsigned int drv_type);
+void mmc_power_off(struct mmc_host *host);
+
+static inline void mmc_delay(unsigned int ms)
+{
+ if (ms < 1000 / HZ) {
+ cond_resched();
+ mdelay(ms);
+ } else {
+ msleep(ms);
+ }
+}
+
+void mmc_rescan(struct work_struct *work);
+void mmc_start_host(struct mmc_host *host);
+void mmc_stop_host(struct mmc_host *host);
+
+int mmc_attach_mmc(struct mmc_host *host);
+int mmc_attach_sd(struct mmc_host *host);
+int mmc_attach_sdio(struct mmc_host *host);
+
+/* Module parameters */
+extern int use_spi_crc;
+
+/* Debugfs information for hosts and cards */
+void mmc_add_host_debugfs(struct mmc_host *host);
+void mmc_remove_host_debugfs(struct mmc_host *host);
+
+void mmc_add_card_debugfs(struct mmc_card *card);
+void mmc_remove_card_debugfs(struct mmc_card *card);
+
+#endif
+
diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c
new file mode 100644
index 00000000..998797ed
--- /dev/null
+++ b/drivers/mmc/core/debugfs.c
@@ -0,0 +1,330 @@
+/*
+ * Debugfs support for hosts and cards
+ *
+ * Copyright (C) 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/debugfs.h>
+#include <linux/fs.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/stat.h>
+
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+
+#include "core.h"
+#include "mmc_ops.h"
+
+/* The debugfs functions are optimized away when CONFIG_DEBUG_FS isn't set. */
+static int mmc_ios_show(struct seq_file *s, void *data)
+{
+ static const char *vdd_str[] = {
+ [8] = "2.0",
+ [9] = "2.1",
+ [10] = "2.2",
+ [11] = "2.3",
+ [12] = "2.4",
+ [13] = "2.5",
+ [14] = "2.6",
+ [15] = "2.7",
+ [16] = "2.8",
+ [17] = "2.9",
+ [18] = "3.0",
+ [19] = "3.1",
+ [20] = "3.2",
+ [21] = "3.3",
+ [22] = "3.4",
+ [23] = "3.5",
+ [24] = "3.6",
+ };
+ struct mmc_host *host = s->private;
+ struct mmc_ios *ios = &host->ios;
+ const char *str;
+
+ seq_printf(s, "clock:\t\t%u Hz\n", ios->clock);
+ seq_printf(s, "vdd:\t\t%u ", ios->vdd);
+ if ((1 << ios->vdd) & MMC_VDD_165_195)
+ seq_printf(s, "(1.65 - 1.95 V)\n");
+ else if (ios->vdd < (ARRAY_SIZE(vdd_str) - 1)
+ && vdd_str[ios->vdd] && vdd_str[ios->vdd + 1])
+ seq_printf(s, "(%s ~ %s V)\n", vdd_str[ios->vdd],
+ vdd_str[ios->vdd + 1]);
+ else
+ seq_printf(s, "(invalid)\n");
+
+ switch (ios->bus_mode) {
+ case MMC_BUSMODE_OPENDRAIN:
+ str = "open drain";
+ break;
+ case MMC_BUSMODE_PUSHPULL:
+ str = "push-pull";
+ break;
+ default:
+ str = "invalid";
+ break;
+ }
+ seq_printf(s, "bus mode:\t%u (%s)\n", ios->bus_mode, str);
+
+ switch (ios->chip_select) {
+ case MMC_CS_DONTCARE:
+ str = "don't care";
+ break;
+ case MMC_CS_HIGH:
+ str = "active high";
+ break;
+ case MMC_CS_LOW:
+ str = "active low";
+ break;
+ default:
+ str = "invalid";
+ break;
+ }
+ seq_printf(s, "chip select:\t%u (%s)\n", ios->chip_select, str);
+
+ switch (ios->power_mode) {
+ case MMC_POWER_OFF:
+ str = "off";
+ break;
+ case MMC_POWER_UP:
+ str = "up";
+ break;
+ case MMC_POWER_ON:
+ str = "on";
+ break;
+ default:
+ str = "invalid";
+ break;
+ }
+ seq_printf(s, "power mode:\t%u (%s)\n", ios->power_mode, str);
+ seq_printf(s, "bus width:\t%u (%u bits)\n",
+ ios->bus_width, 1 << ios->bus_width);
+
+ switch (ios->timing) {
+ case MMC_TIMING_LEGACY:
+ str = "legacy";
+ break;
+ case MMC_TIMING_MMC_HS:
+ str = "mmc high-speed";
+ break;
+ case MMC_TIMING_SD_HS:
+ str = "sd high-speed";
+ break;
+ default:
+ str = "invalid";
+ break;
+ }
+ seq_printf(s, "timing spec:\t%u (%s)\n", ios->timing, str);
+
+ return 0;
+}
+
+static int mmc_ios_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, mmc_ios_show, inode->i_private);
+}
+
+static const struct file_operations mmc_ios_fops = {
+ .open = mmc_ios_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int mmc_clock_opt_get(void *data, u64 *val)
+{
+ struct mmc_host *host = data;
+
+ *val = host->ios.clock;
+
+ return 0;
+}
+
+static int mmc_clock_opt_set(void *data, u64 val)
+{
+ struct mmc_host *host = data;
+
+ /* We need this check due to input value is u64 */
+ if (val > host->f_max)
+ return -EINVAL;
+
+ mmc_claim_host(host);
+ mmc_set_clock(host, (unsigned int) val);
+ mmc_release_host(host);
+
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(mmc_clock_fops, mmc_clock_opt_get, mmc_clock_opt_set,
+ "%llu\n");
+
+void mmc_add_host_debugfs(struct mmc_host *host)
+{
+ struct dentry *root;
+
+ root = debugfs_create_dir(mmc_hostname(host), NULL);
+ if (IS_ERR(root))
+ /* Don't complain -- debugfs just isn't enabled */
+ return;
+ if (!root)
+ /* Complain -- debugfs is enabled, but it failed to
+ * create the directory. */
+ goto err_root;
+
+ host->debugfs_root = root;
+
+ if (!debugfs_create_file("ios", S_IRUSR, root, host, &mmc_ios_fops))
+ goto err_node;
+
+ if (!debugfs_create_file("clock", S_IRUSR | S_IWUSR, root, host,
+ &mmc_clock_fops))
+ goto err_node;
+
+#ifdef CONFIG_MMC_CLKGATE
+ if (!debugfs_create_u32("clk_delay", (S_IRUSR | S_IWUSR),
+ root, &host->clk_delay))
+ goto err_node;
+#endif
+ return;
+
+err_node:
+ debugfs_remove_recursive(root);
+ host->debugfs_root = NULL;
+err_root:
+ dev_err(&host->class_dev, "failed to initialize debugfs\n");
+}
+
+void mmc_remove_host_debugfs(struct mmc_host *host)
+{
+ debugfs_remove_recursive(host->debugfs_root);
+}
+
+static int mmc_dbg_card_status_get(void *data, u64 *val)
+{
+ struct mmc_card *card = data;
+ u32 status;
+ int ret;
+
+ mmc_claim_host(card->host);
+
+ ret = mmc_send_status(data, &status);
+ if (!ret)
+ *val = status;
+
+ mmc_release_host(card->host);
+
+ return ret;
+}
+DEFINE_SIMPLE_ATTRIBUTE(mmc_dbg_card_status_fops, mmc_dbg_card_status_get,
+ NULL, "%08llx\n");
+
+#define EXT_CSD_STR_LEN 1025
+
+static int mmc_ext_csd_open(struct inode *inode, struct file *filp)
+{
+ struct mmc_card *card = inode->i_private;
+ char *buf;
+ ssize_t n = 0;
+ u8 *ext_csd;
+ int err, i;
+
+ buf = kmalloc(EXT_CSD_STR_LEN + 1, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ext_csd = kmalloc(512, GFP_KERNEL);
+ if (!ext_csd) {
+ err = -ENOMEM;
+ goto out_free;
+ }
+
+ mmc_claim_host(card->host);
+ err = mmc_send_ext_csd(card, ext_csd);
+ mmc_release_host(card->host);
+ if (err)
+ goto out_free;
+
+ for (i = 511; i >= 0; i--)
+ n += sprintf(buf + n, "%02x", ext_csd[i]);
+ n += sprintf(buf + n, "\n");
+ BUG_ON(n != EXT_CSD_STR_LEN);
+
+ filp->private_data = buf;
+ kfree(ext_csd);
+ return 0;
+
+out_free:
+ kfree(buf);
+ kfree(ext_csd);
+ return err;
+}
+
+static ssize_t mmc_ext_csd_read(struct file *filp, char __user *ubuf,
+ size_t cnt, loff_t *ppos)
+{
+ char *buf = filp->private_data;
+
+ return simple_read_from_buffer(ubuf, cnt, ppos,
+ buf, EXT_CSD_STR_LEN);
+}
+
+static int mmc_ext_csd_release(struct inode *inode, struct file *file)
+{
+ kfree(file->private_data);
+ return 0;
+}
+
+static const struct file_operations mmc_dbg_ext_csd_fops = {
+ .open = mmc_ext_csd_open,
+ .read = mmc_ext_csd_read,
+ .release = mmc_ext_csd_release,
+ .llseek = default_llseek,
+};
+
+void mmc_add_card_debugfs(struct mmc_card *card)
+{
+ struct mmc_host *host = card->host;
+ struct dentry *root;
+
+ if (!host->debugfs_root)
+ return;
+
+ root = debugfs_create_dir(mmc_card_id(card), host->debugfs_root);
+ if (IS_ERR(root))
+ /* Don't complain -- debugfs just isn't enabled */
+ return;
+ if (!root)
+ /* Complain -- debugfs is enabled, but it failed to
+ * create the directory. */
+ goto err;
+
+ card->debugfs_root = root;
+
+ if (!debugfs_create_x32("state", S_IRUSR, root, &card->state))
+ goto err;
+
+ if (mmc_card_mmc(card) || mmc_card_sd(card))
+ if (!debugfs_create_file("status", S_IRUSR, root, card,
+ &mmc_dbg_card_status_fops))
+ goto err;
+
+ if (mmc_card_mmc(card))
+ if (!debugfs_create_file("ext_csd", S_IRUSR, root, card,
+ &mmc_dbg_ext_csd_fops))
+ goto err;
+
+ return;
+
+err:
+ debugfs_remove_recursive(root);
+ card->debugfs_root = NULL;
+ dev_err(&card->dev, "failed to initialize debugfs\n");
+}
+
+void mmc_remove_card_debugfs(struct mmc_card *card)
+{
+ debugfs_remove_recursive(card->debugfs_root);
+}
diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c
new file mode 100644
index 00000000..e09f0a7e
--- /dev/null
+++ b/drivers/mmc/core/host.c
@@ -0,0 +1,393 @@
+/*
+ * linux/drivers/mmc/core/host.c
+ *
+ * Copyright (C) 2003 Russell King, All Rights Reserved.
+ * Copyright (C) 2007-2008 Pierre Ossman
+ * Copyright (C) 2010 Linus Walleij
+ *
+ * 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.
+ *
+ * MMC host class device management
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/idr.h>
+#include <linux/pagemap.h>
+#include <linux/leds.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+
+#include "core.h"
+#include "host.h"
+
+#define cls_dev_to_mmc_host(d) container_of(d, struct mmc_host, class_dev)
+
+static void mmc_host_classdev_release(struct device *dev)
+{
+ struct mmc_host *host = cls_dev_to_mmc_host(dev);
+ kfree(host);
+}
+
+static struct class mmc_host_class = {
+ .name = "mmc_host",
+ .dev_release = mmc_host_classdev_release,
+};
+
+int mmc_register_host_class(void)
+{
+ return class_register(&mmc_host_class);
+}
+
+void mmc_unregister_host_class(void)
+{
+ class_unregister(&mmc_host_class);
+}
+
+static DEFINE_IDR(mmc_host_idr);
+static DEFINE_SPINLOCK(mmc_host_lock);
+
+#ifdef CONFIG_MMC_CLKGATE
+
+/*
+ * Enabling clock gating will make the core call out to the host
+ * once up and once down when it performs a request or card operation
+ * intermingled in any fashion. The driver will see this through
+ * set_ios() operations with ios.clock field set to 0 to gate (disable)
+ * the block clock, and to the old frequency to enable it again.
+ */
+static void mmc_host_clk_gate_delayed(struct mmc_host *host)
+{
+ unsigned long tick_ns;
+ unsigned long freq = host->ios.clock;
+ unsigned long flags;
+
+ if (!freq) {
+ pr_debug("%s: frequency set to 0 in disable function, "
+ "this means the clock is already disabled.\n",
+ mmc_hostname(host));
+ return;
+ }
+ /*
+ * New requests may have appeared while we were scheduling,
+ * then there is no reason to delay the check before
+ * clk_disable().
+ */
+ spin_lock_irqsave(&host->clk_lock, flags);
+
+ /*
+ * Delay n bus cycles (at least 8 from MMC spec) before attempting
+ * to disable the MCI block clock. The reference count may have
+ * gone up again after this delay due to rescheduling!
+ */
+ if (!host->clk_requests) {
+ spin_unlock_irqrestore(&host->clk_lock, flags);
+ tick_ns = DIV_ROUND_UP(1000000000, freq);
+ ndelay(host->clk_delay * tick_ns);
+ } else {
+ /* New users appeared while waiting for this work */
+ spin_unlock_irqrestore(&host->clk_lock, flags);
+ return;
+ }
+ mutex_lock(&host->clk_gate_mutex);
+ spin_lock_irqsave(&host->clk_lock, flags);
+ if (!host->clk_requests) {
+ spin_unlock_irqrestore(&host->clk_lock, flags);
+ /* This will set host->ios.clock to 0 */
+ mmc_gate_clock(host);
+ spin_lock_irqsave(&host->clk_lock, flags);
+ pr_debug("%s: gated MCI clock\n", mmc_hostname(host));
+ }
+ spin_unlock_irqrestore(&host->clk_lock, flags);
+ mutex_unlock(&host->clk_gate_mutex);
+}
+
+/*
+ * Internal work. Work to disable the clock at some later point.
+ */
+static void mmc_host_clk_gate_work(struct work_struct *work)
+{
+ struct mmc_host *host = container_of(work, struct mmc_host,
+ clk_gate_work);
+
+ mmc_host_clk_gate_delayed(host);
+}
+
+/**
+ * mmc_host_clk_hold - ungate hardware MCI clocks
+ * @host: host to ungate.
+ *
+ * Makes sure the host ios.clock is restored to a non-zero value
+ * past this call. Increase clock reference count and ungate clock
+ * if we're the first user.
+ */
+void mmc_host_clk_hold(struct mmc_host *host)
+{
+ unsigned long flags;
+
+ mutex_lock(&host->clk_gate_mutex);
+ spin_lock_irqsave(&host->clk_lock, flags);
+ if (host->clk_gated) {
+ spin_unlock_irqrestore(&host->clk_lock, flags);
+ mmc_ungate_clock(host);
+ spin_lock_irqsave(&host->clk_lock, flags);
+ pr_debug("%s: ungated MCI clock\n", mmc_hostname(host));
+ }
+ host->clk_requests++;
+ spin_unlock_irqrestore(&host->clk_lock, flags);
+ mutex_unlock(&host->clk_gate_mutex);
+}
+
+/**
+ * mmc_host_may_gate_card - check if this card may be gated
+ * @card: card to check.
+ */
+static bool mmc_host_may_gate_card(struct mmc_card *card)
+{
+ /* If there is no card we may gate it */
+ if (!card)
+ return true;
+ /*
+ * Don't gate SDIO cards! These need to be clocked at all times
+ * since they may be independent systems generating interrupts
+ * and other events. The clock requests counter from the core will
+ * go down to zero since the core does not need it, but we will not
+ * gate the clock, because there is somebody out there that may still
+ * be using it.
+ */
+ return !(card->quirks & MMC_QUIRK_BROKEN_CLK_GATING);
+}
+
+/**
+ * mmc_host_clk_release - gate off hardware MCI clocks
+ * @host: host to gate.
+ *
+ * Calls the host driver with ios.clock set to zero as often as possible
+ * in order to gate off hardware MCI clocks. Decrease clock reference
+ * count and schedule disabling of clock.
+ */
+void mmc_host_clk_release(struct mmc_host *host)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->clk_lock, flags);
+ host->clk_requests--;
+ if (mmc_host_may_gate_card(host->card) &&
+ !host->clk_requests)
+ queue_work(system_nrt_wq, &host->clk_gate_work);
+ spin_unlock_irqrestore(&host->clk_lock, flags);
+}
+
+/**
+ * mmc_host_clk_rate - get current clock frequency setting
+ * @host: host to get the clock frequency for.
+ *
+ * Returns current clock frequency regardless of gating.
+ */
+unsigned int mmc_host_clk_rate(struct mmc_host *host)
+{
+ unsigned long freq;
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->clk_lock, flags);
+ if (host->clk_gated)
+ freq = host->clk_old;
+ else
+ freq = host->ios.clock;
+ spin_unlock_irqrestore(&host->clk_lock, flags);
+ return freq;
+}
+
+/**
+ * mmc_host_clk_init - set up clock gating code
+ * @host: host with potential clock to control
+ */
+static inline void mmc_host_clk_init(struct mmc_host *host)
+{
+ host->clk_requests = 0;
+ /* Hold MCI clock for 8 cycles by default */
+ host->clk_delay = 8;
+ host->clk_gated = false;
+ INIT_WORK(&host->clk_gate_work, mmc_host_clk_gate_work);
+ spin_lock_init(&host->clk_lock);
+ mutex_init(&host->clk_gate_mutex);
+}
+
+/**
+ * mmc_host_clk_exit - shut down clock gating code
+ * @host: host with potential clock to control
+ */
+static inline void mmc_host_clk_exit(struct mmc_host *host)
+{
+ /*
+ * Wait for any outstanding gate and then make sure we're
+ * ungated before exiting.
+ */
+ if (cancel_work_sync(&host->clk_gate_work))
+ mmc_host_clk_gate_delayed(host);
+ if (host->clk_gated)
+ mmc_host_clk_hold(host);
+ /* There should be only one user now */
+ WARN_ON(host->clk_requests > 1);
+}
+
+#else
+
+static inline void mmc_host_clk_init(struct mmc_host *host)
+{
+}
+
+static inline void mmc_host_clk_exit(struct mmc_host *host)
+{
+}
+
+#endif
+
+/**
+ * mmc_alloc_host - initialise the per-host structure.
+ * @extra: sizeof private data structure
+ * @dev: pointer to host device model structure
+ *
+ * Initialise the per-host structure.
+ */
+struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
+{
+ int err;
+ struct mmc_host *host;
+
+ if (!idr_pre_get(&mmc_host_idr, GFP_KERNEL))
+ return NULL;
+
+ host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);
+ if (!host)
+ return NULL;
+
+ spin_lock(&mmc_host_lock);
+ err = idr_get_new(&mmc_host_idr, host, &host->index);
+ spin_unlock(&mmc_host_lock);
+ if (err)
+ goto free;
+
+ dev_set_name(&host->class_dev, "mmc%d", host->index);
+
+ host->parent = dev;
+ host->class_dev.parent = dev;
+ host->class_dev.class = &mmc_host_class;
+ device_initialize(&host->class_dev);
+
+ mmc_host_clk_init(host);
+
+ spin_lock_init(&host->lock);
+ init_waitqueue_head(&host->wq);
+ wake_lock_init(&host->detect_wake_lock, WAKE_LOCK_SUSPEND,
+ kasprintf(GFP_KERNEL, "%s_detect", mmc_hostname(host)));
+ INIT_DELAYED_WORK(&host->detect, mmc_rescan);
+ INIT_DELAYED_WORK_DEFERRABLE(&host->disable, mmc_host_deeper_disable);
+#ifdef CONFIG_PM
+ host->pm_notify.notifier_call = mmc_pm_notify;
+#endif
+
+ /*
+ * By default, hosts do not support SGIO or large requests.
+ * They have to set these according to their abilities.
+ */
+ host->max_segs = 1;
+ host->max_seg_size = PAGE_CACHE_SIZE;
+
+ host->max_req_size = PAGE_CACHE_SIZE;
+ host->max_blk_size = 512;
+ host->max_blk_count = PAGE_CACHE_SIZE / 512;
+
+ return host;
+
+free:
+ kfree(host);
+ return NULL;
+}
+
+EXPORT_SYMBOL(mmc_alloc_host);
+
+/**
+ * mmc_add_host - initialise host hardware
+ * @host: mmc host
+ *
+ * Register the host with the driver model. The host must be
+ * prepared to start servicing requests before this function
+ * completes.
+ */
+int mmc_add_host(struct mmc_host *host)
+{
+ int err;
+
+ WARN_ON((host->caps & MMC_CAP_SDIO_IRQ) &&
+ !host->ops->enable_sdio_irq);
+
+ err = device_add(&host->class_dev);
+ if (err)
+ return err;
+
+ led_trigger_register_simple(dev_name(&host->class_dev), &host->led);
+
+#ifdef CONFIG_DEBUG_FS
+ mmc_add_host_debugfs(host);
+#endif
+
+ mmc_start_host(host);
+ if (!(host->pm_flags & MMC_PM_IGNORE_PM_NOTIFY))
+ register_pm_notifier(&host->pm_notify);
+
+ return 0;
+}
+
+EXPORT_SYMBOL(mmc_add_host);
+
+/**
+ * mmc_remove_host - remove host hardware
+ * @host: mmc host
+ *
+ * Unregister and remove all cards associated with this host,
+ * and power down the MMC bus. No new requests will be issued
+ * after this function has returned.
+ */
+void mmc_remove_host(struct mmc_host *host)
+{
+ if (!(host->pm_flags & MMC_PM_IGNORE_PM_NOTIFY))
+ unregister_pm_notifier(&host->pm_notify);
+
+ mmc_stop_host(host);
+
+#ifdef CONFIG_DEBUG_FS
+ mmc_remove_host_debugfs(host);
+#endif
+
+ device_del(&host->class_dev);
+
+ led_trigger_unregister_simple(host->led);
+
+ mmc_host_clk_exit(host);
+}
+
+EXPORT_SYMBOL(mmc_remove_host);
+
+/**
+ * mmc_free_host - free the host structure
+ * @host: mmc host
+ *
+ * Free the host once all references to it have been dropped.
+ */
+void mmc_free_host(struct mmc_host *host)
+{
+ spin_lock(&mmc_host_lock);
+ idr_remove(&mmc_host_idr, host->index);
+ spin_unlock(&mmc_host_lock);
+ wake_lock_destroy(&host->detect_wake_lock);
+
+ put_device(&host->class_dev);
+}
+
+EXPORT_SYMBOL(mmc_free_host);
diff --git a/drivers/mmc/core/host.h b/drivers/mmc/core/host.h
new file mode 100644
index 00000000..fb8a5cd2
--- /dev/null
+++ b/drivers/mmc/core/host.h
@@ -0,0 +1,41 @@
+/*
+ * linux/drivers/mmc/core/host.h
+ *
+ * Copyright (C) 2003 Russell King, All Rights Reserved.
+ * Copyright 2007 Pierre Ossman
+ *
+ * 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 _MMC_CORE_HOST_H
+#define _MMC_CORE_HOST_H
+#include <linux/mmc/host.h>
+
+int mmc_register_host_class(void);
+void mmc_unregister_host_class(void);
+
+#ifdef CONFIG_MMC_CLKGATE
+void mmc_host_clk_hold(struct mmc_host *host);
+void mmc_host_clk_release(struct mmc_host *host);
+unsigned int mmc_host_clk_rate(struct mmc_host *host);
+
+#else
+static inline void mmc_host_clk_hold(struct mmc_host *host)
+{
+}
+
+static inline void mmc_host_clk_release(struct mmc_host *host)
+{
+}
+
+static inline unsigned int mmc_host_clk_rate(struct mmc_host *host)
+{
+ return host->ios.clock;
+}
+#endif
+
+void mmc_host_deeper_disable(struct work_struct *work);
+
+#endif
+
diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
new file mode 100644
index 00000000..c377cf6a
--- /dev/null
+++ b/drivers/mmc/core/mmc.c
@@ -0,0 +1,1461 @@
+/*
+ * linux/drivers/mmc/core/mmc.c
+ *
+ * Copyright (C) 2003-2004 Russell King, All Rights Reserved.
+ * Copyright (C) 2005-2007 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
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/err.h>
+#include <linux/slab.h>
+
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/mmc.h>
+
+#include "core.h"
+#include "bus.h"
+#include "mmc_ops.h"
+#include "sd_ops.h"
+
+static const unsigned int tran_exp[] = {
+ 10000, 100000, 1000000, 10000000,
+ 0, 0, 0, 0
+};
+
+static const unsigned char tran_mant[] = {
+ 0, 10, 12, 13, 15, 20, 25, 30,
+ 35, 40, 45, 50, 55, 60, 70, 80,
+};
+
+static const unsigned int tacc_exp[] = {
+ 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000,
+};
+
+static const unsigned int tacc_mant[] = {
+ 0, 10, 12, 13, 15, 20, 25, 30,
+ 35, 40, 45, 50, 55, 60, 70, 80,
+};
+
+#define UNSTUFF_BITS(resp,start,size) \
+ ({ \
+ const int __size = size; \
+ const u32 __mask = (__size < 32 ? 1 << __size : 0) - 1; \
+ const int __off = 3 - ((start) / 32); \
+ const int __shft = (start) & 31; \
+ u32 __res; \
+ \
+ __res = resp[__off] >> __shft; \
+ if (__size + __shft > 32) \
+ __res |= resp[__off-1] << ((32 - __shft) % 32); \
+ __res & __mask; \
+ })
+
+/*
+ * Given the decoded CSD structure, decode the raw CID to our CID structure.
+ */
+static int mmc_decode_cid(struct mmc_card *card)
+{
+ u32 *resp = card->raw_cid;
+
+ /*
+ * The selection of the format here is based upon published
+ * specs from sandisk and from what people have reported.
+ */
+ switch (card->csd.mmca_vsn) {
+ case 0: /* MMC v1.0 - v1.2 */
+ case 1: /* MMC v1.4 */
+ card->cid.manfid = UNSTUFF_BITS(resp, 104, 24);
+ card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8);
+ card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8);
+ card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8);
+ card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8);
+ card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8);
+ card->cid.prod_name[5] = UNSTUFF_BITS(resp, 56, 8);
+ card->cid.prod_name[6] = UNSTUFF_BITS(resp, 48, 8);
+ card->cid.hwrev = UNSTUFF_BITS(resp, 44, 4);
+ card->cid.fwrev = UNSTUFF_BITS(resp, 40, 4);
+ card->cid.serial = UNSTUFF_BITS(resp, 16, 24);
+ card->cid.month = UNSTUFF_BITS(resp, 12, 4);
+ card->cid.year = UNSTUFF_BITS(resp, 8, 4) + 1997;
+ break;
+
+ case 2: /* MMC v2.0 - v2.2 */
+ case 3: /* MMC v3.1 - v3.3 */
+ case 4: /* MMC v4 */
+ card->cid.manfid = UNSTUFF_BITS(resp, 120, 8);
+ card->cid.oemid = UNSTUFF_BITS(resp, 104, 16);
+ card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8);
+ card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8);
+ card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8);
+ card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8);
+ card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8);
+ card->cid.prod_name[5] = UNSTUFF_BITS(resp, 56, 8);
+ card->cid.serial = UNSTUFF_BITS(resp, 16, 32);
+ card->cid.month = UNSTUFF_BITS(resp, 12, 4);
+ card->cid.year = UNSTUFF_BITS(resp, 8, 4) + 1997;
+ break;
+
+ default:
+ printk(KERN_ERR "%s: card has unknown MMCA version %d\n",
+ mmc_hostname(card->host), card->csd.mmca_vsn);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void mmc_set_erase_size(struct mmc_card *card)
+{
+ if (card->ext_csd.erase_group_def & 1)
+ card->erase_size = card->ext_csd.hc_erase_size;
+ else
+ card->erase_size = card->csd.erase_size;
+
+ mmc_init_erase(card);
+}
+
+/*
+ * Given a 128-bit response, decode to our card CSD structure.
+ */
+static int mmc_decode_csd(struct mmc_card *card)
+{
+ struct mmc_csd *csd = &card->csd;
+ unsigned int e, m, a, b;
+ u32 *resp = card->raw_csd;
+
+ /*
+ * We only understand CSD structure v1.1 and v1.2.
+ * v1.2 has extra information in bits 15, 11 and 10.
+ * We also support eMMC v4.4 & v4.41.
+ */
+ csd->structure = UNSTUFF_BITS(resp, 126, 2);
+ if (csd->structure == 0) {
+ printk(KERN_ERR "%s: unrecognised CSD structure version %d\n",
+ mmc_hostname(card->host), csd->structure);
+ return -EINVAL;
+ }
+
+ csd->mmca_vsn = UNSTUFF_BITS(resp, 122, 4);
+ m = UNSTUFF_BITS(resp, 115, 4);
+ e = UNSTUFF_BITS(resp, 112, 3);
+ csd->tacc_ns = (tacc_exp[e] * tacc_mant[m] + 9) / 10;
+ csd->tacc_clks = UNSTUFF_BITS(resp, 104, 8) * 100;
+
+ m = UNSTUFF_BITS(resp, 99, 4);
+ e = UNSTUFF_BITS(resp, 96, 3);
+ csd->max_dtr = tran_exp[e] * tran_mant[m];
+ csd->cmdclass = UNSTUFF_BITS(resp, 84, 12);
+
+ e = UNSTUFF_BITS(resp, 47, 3);
+ m = UNSTUFF_BITS(resp, 62, 12);
+ csd->capacity = (1 + m) << (e + 2);
+
+ csd->read_blkbits = UNSTUFF_BITS(resp, 80, 4);
+ csd->read_partial = UNSTUFF_BITS(resp, 79, 1);
+ csd->write_misalign = UNSTUFF_BITS(resp, 78, 1);
+ csd->read_misalign = UNSTUFF_BITS(resp, 77, 1);
+ csd->r2w_factor = UNSTUFF_BITS(resp, 26, 3);
+ csd->write_blkbits = UNSTUFF_BITS(resp, 22, 4);
+ csd->write_partial = UNSTUFF_BITS(resp, 21, 1);
+
+ if (csd->write_blkbits >= 9) {
+ a = UNSTUFF_BITS(resp, 42, 5);
+ b = UNSTUFF_BITS(resp, 37, 5);
+ csd->erase_size = (a + 1) * (b + 1);
+ csd->erase_size <<= csd->write_blkbits - 9;
+ }
+
+ return 0;
+}
+
+/*
+ * Read extended CSD.
+ */
+static int mmc_get_ext_csd(struct mmc_card *card, u8 **new_ext_csd)
+{
+ int err;
+ u8 *ext_csd;
+
+ BUG_ON(!card);
+ BUG_ON(!new_ext_csd);
+
+ *new_ext_csd = NULL;
+
+ if (card->csd.mmca_vsn < CSD_SPEC_VER_4)
+ return 0;
+
+ /*
+ * As the ext_csd is so large and mostly unused, we don't store the
+ * raw block in mmc_card.
+ */
+ ext_csd = kmalloc(512, GFP_KERNEL);
+ if (!ext_csd) {
+ printk(KERN_ERR "%s: could not allocate a buffer to "
+ "receive the ext_csd.\n", mmc_hostname(card->host));
+ return -ENOMEM;
+ }
+
+ err = mmc_send_ext_csd(card, ext_csd);
+ if (err) {
+ kfree(ext_csd);
+ *new_ext_csd = NULL;
+
+ /* If the host or the card can't do the switch,
+ * fail more gracefully. */
+ if ((err != -EINVAL)
+ && (err != -ENOSYS)
+ && (err != -EFAULT))
+ return err;
+
+ /*
+ * High capacity cards should have this "magic" size
+ * stored in their CSD.
+ */
+ if (card->csd.capacity == (4096 * 512)) {
+ printk(KERN_ERR "%s: unable to read EXT_CSD "
+ "on a possible high capacity card. "
+ "Card will be ignored.\n",
+ mmc_hostname(card->host));
+ } else {
+ printk(KERN_WARNING "%s: unable to read "
+ "EXT_CSD, performance might "
+ "suffer.\n",
+ mmc_hostname(card->host));
+ err = 0;
+ }
+ } else
+ *new_ext_csd = ext_csd;
+
+ return err;
+}
+
+/*
+ * Decode extended CSD.
+ */
+static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd)
+{
+ int err = 0;
+
+ BUG_ON(!card);
+
+ if (!ext_csd)
+ return 0;
+
+ /* Version is coded in the CSD_STRUCTURE byte in the EXT_CSD register */
+ card->ext_csd.raw_ext_csd_structure = ext_csd[EXT_CSD_STRUCTURE];
+ if (card->csd.structure == 3) {
+ if (card->ext_csd.raw_ext_csd_structure > 2) {
+ printk(KERN_ERR "%s: unrecognised EXT_CSD structure "
+ "version %d\n", mmc_hostname(card->host),
+ card->ext_csd.raw_ext_csd_structure);
+ err = -EINVAL;
+ goto out;
+ }
+ }
+
+ card->ext_csd.rev = ext_csd[EXT_CSD_REV];
+#if 0
+ /* workaround: support emmc 5.1 cards to work at emmc 4.4 mode */
+ if (card->ext_csd.rev > 7) {
+ printk(KERN_ERR "%s: unrecognised EXT_CSD revision %d\n",
+ mmc_hostname(card->host), card->ext_csd.rev);
+ err = -EINVAL;
+ goto out;
+ }
+#endif
+
+ card->ext_csd.raw_sectors[0] = ext_csd[EXT_CSD_SEC_CNT + 0];
+ card->ext_csd.raw_sectors[1] = ext_csd[EXT_CSD_SEC_CNT + 1];
+ card->ext_csd.raw_sectors[2] = ext_csd[EXT_CSD_SEC_CNT + 2];
+ card->ext_csd.raw_sectors[3] = ext_csd[EXT_CSD_SEC_CNT + 3];
+ if (card->ext_csd.rev >= 2) {
+ card->ext_csd.sectors =
+ ext_csd[EXT_CSD_SEC_CNT + 0] << 0 |
+ ext_csd[EXT_CSD_SEC_CNT + 1] << 8 |
+ ext_csd[EXT_CSD_SEC_CNT + 2] << 16 |
+ ext_csd[EXT_CSD_SEC_CNT + 3] << 24;
+
+ /* Cards with density > 2GiB are sector addressed */
+ if (card->ext_csd.sectors > (2u * 1024 * 1024 * 1024) / 512)
+ mmc_card_set_blockaddr(card);
+ }
+
+ card->ext_csd.boot_info = ext_csd[EXT_CSD_BOOT_INFO];
+ card->ext_csd.boot_config = ext_csd[EXT_CSD_PART_CONFIG];
+ card->ext_csd.boot_bus_width = ext_csd[EXT_CSD_BOOT_BUS_WIDTH];
+
+ card->ext_csd.raw_card_type = ext_csd[EXT_CSD_CARD_TYPE];
+ switch (ext_csd[EXT_CSD_CARD_TYPE] & EXT_CSD_CARD_TYPE_MASK) {
+ case EXT_CSD_CARD_TYPE_DDR_52 | EXT_CSD_CARD_TYPE_52 |
+ EXT_CSD_CARD_TYPE_26:
+ card->ext_csd.hs_max_dtr = 52000000;
+ card->ext_csd.card_type = EXT_CSD_CARD_TYPE_DDR_52;
+ break;
+ case EXT_CSD_CARD_TYPE_DDR_1_2V | EXT_CSD_CARD_TYPE_52 |
+ EXT_CSD_CARD_TYPE_26:
+ card->ext_csd.hs_max_dtr = 52000000;
+ card->ext_csd.card_type = EXT_CSD_CARD_TYPE_DDR_1_2V;
+ break;
+ case EXT_CSD_CARD_TYPE_DDR_1_8V | EXT_CSD_CARD_TYPE_52 |
+ EXT_CSD_CARD_TYPE_26:
+ card->ext_csd.hs_max_dtr = 52000000;
+ card->ext_csd.card_type = EXT_CSD_CARD_TYPE_DDR_1_8V;
+ break;
+ case EXT_CSD_CARD_TYPE_52 | EXT_CSD_CARD_TYPE_26:
+ card->ext_csd.hs_max_dtr = 52000000;
+ break;
+ case EXT_CSD_CARD_TYPE_26:
+ card->ext_csd.hs_max_dtr = 26000000;
+ break;
+ default:
+ /* MMC v4 spec says this cannot happen */
+ printk(KERN_WARNING "%s: card is mmc v4 but doesn't "
+ "support any high-speed modes.\n",
+ mmc_hostname(card->host));
+ }
+
+ card->ext_csd.raw_s_a_timeout = ext_csd[EXT_CSD_S_A_TIMEOUT];
+ card->ext_csd.raw_erase_timeout_mult =
+ ext_csd[EXT_CSD_ERASE_TIMEOUT_MULT];
+ card->ext_csd.raw_hc_erase_grp_size =
+ ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE];
+ if (card->ext_csd.rev >= 3) {
+ u8 sa_shift = ext_csd[EXT_CSD_S_A_TIMEOUT];
+ card->ext_csd.part_config = ext_csd[EXT_CSD_PART_CONFIG];
+
+ /* EXT_CSD value is in units of 10ms, but we store in ms */
+ card->ext_csd.part_time = 10 * ext_csd[EXT_CSD_PART_SWITCH_TIME];
+
+ /* Sleep / awake timeout in 100ns units */
+ if (sa_shift > 0 && sa_shift <= 0x17)
+ card->ext_csd.sa_timeout =
+ 1 << ext_csd[EXT_CSD_S_A_TIMEOUT];
+ card->ext_csd.erase_group_def =
+ ext_csd[EXT_CSD_ERASE_GROUP_DEF];
+ card->ext_csd.hc_erase_timeout = 300 *
+ ext_csd[EXT_CSD_ERASE_TIMEOUT_MULT];
+ card->ext_csd.hc_erase_size =
+ ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] << 10;
+
+ card->ext_csd.rel_sectors = ext_csd[EXT_CSD_REL_WR_SEC_C];
+
+ /*
+ * There are two boot regions of equal size, defined in
+ * multiples of 128K.
+ */
+ card->ext_csd.boot_size = ext_csd[EXT_CSD_BOOT_MULT] << 17;
+ }
+
+ card->ext_csd.raw_hc_erase_gap_size =
+ ext_csd[EXT_CSD_PARTITION_ATTRIBUTE];
+ card->ext_csd.raw_sec_trim_mult =
+ ext_csd[EXT_CSD_SEC_TRIM_MULT];
+ card->ext_csd.raw_sec_erase_mult =
+ ext_csd[EXT_CSD_SEC_ERASE_MULT];
+ card->ext_csd.raw_sec_feature_support =
+ ext_csd[EXT_CSD_SEC_FEATURE_SUPPORT];
+ card->ext_csd.raw_trim_mult =
+ ext_csd[EXT_CSD_TRIM_MULT];
+ if (card->ext_csd.rev >= 4) {
+ /*
+ * Enhanced area feature support -- check whether the eMMC
+ * card has the Enhanced area enabled. If so, export enhanced
+ * area offset and size to user by adding sysfs interface.
+ */
+ card->ext_csd.raw_partition_support = ext_csd[EXT_CSD_PARTITION_SUPPORT];
+ if ((ext_csd[EXT_CSD_PARTITION_SUPPORT] & 0x2) &&
+ (ext_csd[EXT_CSD_PARTITION_ATTRIBUTE] & 0x1)) {
+ u8 hc_erase_grp_sz =
+ ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE];
+ u8 hc_wp_grp_sz =
+ ext_csd[EXT_CSD_HC_WP_GRP_SIZE];
+
+ card->ext_csd.enhanced_area_en = 1;
+ /*
+ * calculate the enhanced data area offset, in bytes
+ */
+ card->ext_csd.enhanced_area_offset =
+ (ext_csd[139] << 24) + (ext_csd[138] << 16) +
+ (ext_csd[137] << 8) + ext_csd[136];
+ if (mmc_card_blockaddr(card))
+ card->ext_csd.enhanced_area_offset <<= 9;
+ /*
+ * calculate the enhanced data area size, in kilobytes
+ */
+ card->ext_csd.enhanced_area_size =
+ (ext_csd[142] << 16) + (ext_csd[141] << 8) +
+ ext_csd[140];
+ card->ext_csd.enhanced_area_size *=
+ (size_t)(hc_erase_grp_sz * hc_wp_grp_sz);
+ card->ext_csd.enhanced_area_size <<= 9;
+ } else {
+ /*
+ * If the enhanced area is not enabled, disable these
+ * device attributes.
+ */
+ card->ext_csd.enhanced_area_offset = -EINVAL;
+ card->ext_csd.enhanced_area_size = -EINVAL;
+ }
+ card->ext_csd.sec_trim_mult =
+ ext_csd[EXT_CSD_SEC_TRIM_MULT];
+ card->ext_csd.sec_erase_mult =
+ ext_csd[EXT_CSD_SEC_ERASE_MULT];
+ card->ext_csd.sec_feature_support =
+ ext_csd[EXT_CSD_SEC_FEATURE_SUPPORT];
+ card->ext_csd.trim_timeout = 300 *
+ ext_csd[EXT_CSD_TRIM_MULT];
+ }
+
+ if (card->ext_csd.rev >= 5)
+ card->ext_csd.rel_param = ext_csd[EXT_CSD_WR_REL_PARAM];
+
+ card->ext_csd.raw_erased_mem_count = ext_csd[EXT_CSD_ERASED_MEM_CONT];
+ if (ext_csd[EXT_CSD_ERASED_MEM_CONT])
+ card->erased_byte = 0xFF;
+ else
+ card->erased_byte = 0x0;
+
+out:
+ return err;
+}
+
+static inline void mmc_free_ext_csd(u8 *ext_csd)
+{
+ kfree(ext_csd);
+}
+
+
+static int mmc_compare_ext_csds(struct mmc_card *card, unsigned bus_width)
+{
+ u8 *bw_ext_csd;
+ int err;
+
+ if (bus_width == MMC_BUS_WIDTH_1)
+ return 0;
+
+ err = mmc_get_ext_csd(card, &bw_ext_csd);
+
+ if (err || bw_ext_csd == NULL) {
+ if (bus_width != MMC_BUS_WIDTH_1)
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (bus_width == MMC_BUS_WIDTH_1)
+ goto out;
+
+ /* only compare read only fields */
+ err = (!(card->ext_csd.raw_partition_support ==
+ bw_ext_csd[EXT_CSD_PARTITION_SUPPORT]) &&
+ (card->ext_csd.raw_erased_mem_count ==
+ bw_ext_csd[EXT_CSD_ERASED_MEM_CONT]) &&
+ (card->ext_csd.rev ==
+ bw_ext_csd[EXT_CSD_REV]) &&
+ (card->ext_csd.raw_ext_csd_structure ==
+ bw_ext_csd[EXT_CSD_STRUCTURE]) &&
+ (card->ext_csd.raw_card_type ==
+ bw_ext_csd[EXT_CSD_CARD_TYPE]) &&
+ (card->ext_csd.raw_s_a_timeout ==
+ bw_ext_csd[EXT_CSD_S_A_TIMEOUT]) &&
+ (card->ext_csd.raw_hc_erase_gap_size ==
+ bw_ext_csd[EXT_CSD_HC_WP_GRP_SIZE]) &&
+ (card->ext_csd.raw_erase_timeout_mult ==
+ bw_ext_csd[EXT_CSD_ERASE_TIMEOUT_MULT]) &&
+ (card->ext_csd.raw_hc_erase_grp_size ==
+ bw_ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE]) &&
+ (card->ext_csd.raw_sec_trim_mult ==
+ bw_ext_csd[EXT_CSD_SEC_TRIM_MULT]) &&
+ (card->ext_csd.raw_sec_erase_mult ==
+ bw_ext_csd[EXT_CSD_SEC_ERASE_MULT]) &&
+ (card->ext_csd.raw_sec_feature_support ==
+ bw_ext_csd[EXT_CSD_SEC_FEATURE_SUPPORT]) &&
+ (card->ext_csd.raw_trim_mult ==
+ bw_ext_csd[EXT_CSD_TRIM_MULT]) &&
+ (card->ext_csd.raw_sectors[0] ==
+ bw_ext_csd[EXT_CSD_SEC_CNT + 0]) &&
+ (card->ext_csd.raw_sectors[1] ==
+ bw_ext_csd[EXT_CSD_SEC_CNT + 1]) &&
+ (card->ext_csd.raw_sectors[2] ==
+ bw_ext_csd[EXT_CSD_SEC_CNT + 2]) &&
+ (card->ext_csd.raw_sectors[3] ==
+ bw_ext_csd[EXT_CSD_SEC_CNT + 3]));
+ if (err)
+ err = -EINVAL;
+
+out:
+ mmc_free_ext_csd(bw_ext_csd);
+ return err;
+}
+
+/* switch the partitions */
+static ssize_t
+setup_boot_partitions(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int err, busy = 0;
+ u32 part;
+ u8 *ext_csd, boot_config;
+ struct mmc_command cmd;
+ struct mmc_card *card = container_of(dev, struct mmc_card, dev);
+
+ BUG_ON(!card);
+
+ sscanf(buf, "%d\n", &part);
+
+ if (card->csd.mmca_vsn < CSD_SPEC_VER_4) {
+ printk(KERN_ERR "%s: invalid mmc version"
+ " mmc version is below version 4!)\n",
+ mmc_hostname(card->host));
+ return -EINVAL;
+ }
+
+ /* it's a normal SD/MMC but user request to configure boot partition */
+ if (card->ext_csd.boot_size <= 0) {
+ pr_err("%s: fail to send SWITCH command to card " \
+ "to update boot_config of the EXT_CSD!\n",
+ mmc_hostname(card->host));
+ return -EINVAL;
+ }
+
+ /*
+ * partition must be -
+ * 0 - user area
+ * 1 - boot partition 1
+ * 2 - boot partition 2
+ * DO NOT switch the partitions that used to be accessed
+ * in OS layer HERE
+ */
+ if (part & EXT_CSD_BOOT_PARTITION_ACCESS_MASK) {
+ pr_err("%s: DO NOT switch the partitions that used to be\n" \
+ " accessed in OS layer HERE. please following the\n" \
+ " guidance of Documentation/mmc/mmc-dev-parts.txt.\n",
+ mmc_hostname(card->host));
+ return -EINVAL;
+ }
+
+ ext_csd = kmalloc(512, GFP_KERNEL);
+ if (!ext_csd) {
+ printk(KERN_ERR "%s: could not allocate a buffer to "
+ "receive the ext_csd.\n", mmc_hostname(card->host));
+ return -ENOMEM;
+ }
+
+ mmc_claim_host(card->host);
+ err = mmc_send_ext_csd(card, ext_csd);
+ if (err) {
+ printk(KERN_ERR "%s: unable to read EXT_CSD.\n",
+ mmc_hostname(card->host));
+ goto err_rtn;
+ }
+
+ /* enable the boot partition in boot mode */
+ /* boot enable be -
+ * 0x00 - disable boot enable.
+ * 0x08 - boot partition 1 is enabled for boot.
+ * 0x10 - boot partition 2 is enabled for boot.
+ * 0x38 - User area is enabled for boot.
+ */
+ switch (part & EXT_CSD_BOOT_PARTITION_ENABLE_MASK) {
+ case 0:
+ boot_config = (ext_csd[EXT_CSD_PART_CONFIG]
+ & ~EXT_CSD_BOOT_PARTITION_ENABLE_MASK
+ & ~EXT_CSD_BOOT_ACK_ENABLE);
+ break;
+ case EXT_CSD_BOOT_PARTITION_PART1:
+ boot_config = ((ext_csd[EXT_CSD_PART_CONFIG]
+ & ~EXT_CSD_BOOT_PARTITION_ENABLE_MASK)
+ | EXT_CSD_BOOT_PARTITION_PART1
+ | EXT_CSD_BOOT_ACK_ENABLE);
+ break;
+ case EXT_CSD_BOOT_PARTITION_PART2:
+ boot_config = ((ext_csd[EXT_CSD_PART_CONFIG]
+ & ~EXT_CSD_BOOT_PARTITION_ENABLE_MASK)
+ | EXT_CSD_BOOT_PARTITION_PART2
+ | EXT_CSD_BOOT_ACK_ENABLE);
+ break;
+ case EXT_CSD_BOOT_PARTITION_ENABLE_MASK:
+ boot_config = ((ext_csd[EXT_CSD_PART_CONFIG]
+ | EXT_CSD_BOOT_PARTITION_ENABLE_MASK)
+ & ~EXT_CSD_BOOT_ACK_ENABLE);
+ break;
+ default:
+ printk(KERN_ERR "%s: wrong boot config parameter"
+ " 00 (disable boot), 08 (enable boot1),"
+ "16 (enable boot2), 56 (User area)\n",
+ mmc_hostname(card->host));
+ err = -EINVAL;
+ goto err_rtn;
+ }
+
+ err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_PART_CONFIG, boot_config, card->ext_csd.part_time);
+ if (err) {
+ pr_err("%s: fail to send SWITCH command to card " \
+ "to update boot_config of the EXT_CSD!\n",
+ mmc_hostname(card->host));
+ goto err_rtn;
+ }
+
+ /* waiting for the card to finish the busy state */
+ do {
+ memset(&cmd, 0, sizeof(struct mmc_command));
+
+ cmd.opcode = MMC_SEND_STATUS;
+ cmd.arg = card->rca << 16;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
+
+ err = mmc_wait_for_cmd(card->host, &cmd, 0);
+ if (err || busy > 100) {
+ printk(KERN_ERR "%s: failed to wait for"
+ "the busy state to end.\n",
+ mmc_hostname(card->host));
+ break;
+ }
+
+ if (!busy && !(cmd.resp[0] & R1_READY_FOR_DATA)) {
+ printk(KERN_INFO "%s: card is in busy state"
+ "pls wait for busy state to end.\n",
+ mmc_hostname(card->host));
+ }
+ busy++;
+ } while (!(cmd.resp[0] & R1_READY_FOR_DATA));
+
+
+ /* Now check whether it works */
+ err = mmc_send_ext_csd(card, ext_csd);
+ if (err) {
+ printk(KERN_ERR "%s: %d unable to re-read EXT_CSD.\n",
+ mmc_hostname(card->host), err);
+ goto err_rtn;
+ }
+
+ card->ext_csd.boot_config = ext_csd[EXT_CSD_PART_CONFIG];
+
+err_rtn:
+ mmc_release_host(card->host);
+ kfree(ext_csd);
+ if (err)
+ return err;
+ else
+ return count;
+}
+
+/* configure the boot bus */
+static ssize_t
+setup_boot_bus(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int err, busy = 0;
+ u32 boot_bus, new_bus;
+ u8 *ext_csd;
+ struct mmc_command cmd;
+ struct mmc_card *card = container_of(dev, struct mmc_card, dev);
+
+ BUG_ON(!card);
+
+ sscanf(buf, "%d\n", &boot_bus);
+
+ if (card->csd.mmca_vsn < CSD_SPEC_VER_4) {
+ pr_err("%s: fail to send SWITCH command to card " \
+ "to update boot_config of the EXT_CSD!\n",
+ mmc_hostname(card->host));
+ return -EINVAL;
+ }
+
+ /* it's a normal SD/MMC but user request to configure boot bus */
+ if (card->ext_csd.boot_size <= 0) {
+ printk(KERN_ERR "%s: this is a normal SD/MMC card"
+ " but you request to configure boot bus !\n",
+ mmc_hostname(card->host));
+ return -EINVAL;
+ }
+
+ ext_csd = kmalloc(512, GFP_KERNEL);
+ if (!ext_csd) {
+ printk(KERN_ERR "%s: could not allocate a buffer to "
+ "receive the ext_csd.\n", mmc_hostname(card->host));
+ return -ENOMEM;
+ }
+
+ mmc_claim_host(card->host);
+ err = mmc_send_ext_csd(card, ext_csd);
+ if (err) {
+ printk(KERN_ERR "%s: unable to read EXT_CSD.\n",
+ mmc_hostname(card->host));
+ goto err_rtn;
+ }
+
+ /* Configure the boot bus width when boot partition is enabled */
+ if (((boot_bus & EXT_CSD_BOOT_BUS_WIDTH_MODE_MASK) >> 3) > 2
+ || (boot_bus & EXT_CSD_BOOT_BUS_WIDTH_WIDTH_MASK) > 2
+ || (boot_bus & ~EXT_CSD_BOOT_BUS_WIDTH_MASK) > 0) {
+ printk(KERN_ERR "%s: Invalid inputs!\n",
+ mmc_hostname(card->host));
+ err = -EINVAL;
+ goto err_rtn;
+ }
+
+ err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_BOOT_BUS_WIDTH, boot_bus, card->ext_csd.part_time);
+ if (err) {
+ printk(KERN_ERR "%s: fail to send SWITCH command to "
+ "card to swich partition for access!\n",
+ mmc_hostname(card->host));
+ goto err_rtn;
+ }
+
+ /* waiting for the card to finish the busy state */
+ do {
+ memset(&cmd, 0, sizeof(struct mmc_command));
+
+ cmd.opcode = MMC_SEND_STATUS;
+ cmd.arg = card->rca << 16;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
+
+ err = mmc_wait_for_cmd(card->host, &cmd, 0);
+ if (err || busy > 100) {
+ printk(KERN_ERR "%s: failed to wait for"
+ "the busy state to end.\n",
+ mmc_hostname(card->host));
+ break;
+ }
+
+ if (!busy && !(cmd.resp[0] & R1_READY_FOR_DATA)) {
+ printk(KERN_INFO "%s: card is in busy state"
+ "pls wait for busy state to end.\n",
+ mmc_hostname(card->host));
+ }
+ busy++;
+ } while (!(cmd.resp[0] & R1_READY_FOR_DATA));
+
+ /* Now check whether it works */
+ err = mmc_send_ext_csd(card, ext_csd);
+ if (err) {
+ printk(KERN_ERR "%s: %d unable to re-read EXT_CSD.\n",
+ mmc_hostname(card->host), err);
+ goto err_rtn;
+ }
+
+ new_bus = ext_csd[EXT_CSD_BOOT_BUS_WIDTH];
+ if (boot_bus != new_bus) {
+ printk(KERN_ERR "%s: after SWITCH, current boot bus mode %d"
+ " is not same as requested bus mode %d!\n",
+ mmc_hostname(card->host), new_bus, boot_bus);
+ goto err_rtn;
+ }
+ card->ext_csd.boot_bus_width = ext_csd[EXT_CSD_BOOT_BUS_WIDTH];
+
+err_rtn:
+ mmc_release_host(card->host);
+ kfree(ext_csd);
+ if (err)
+ return err;
+ else
+ return count;
+}
+
+MMC_DEV_ATTR(cid, "%08x%08x%08x%08x\n", card->raw_cid[0], card->raw_cid[1],
+ card->raw_cid[2], card->raw_cid[3]);
+MMC_DEV_ATTR(csd, "%08x%08x%08x%08x\n", card->raw_csd[0], card->raw_csd[1],
+ card->raw_csd[2], card->raw_csd[3]);
+MMC_DEV_ATTR(date, "%02d/%04d\n", card->cid.month, card->cid.year);
+MMC_DEV_ATTR(erase_size, "%u\n", card->erase_size << 9);
+MMC_DEV_ATTR(preferred_erase_size, "%u\n", card->pref_erase << 9);
+MMC_DEV_ATTR(fwrev, "0x%x\n", card->cid.fwrev);
+MMC_DEV_ATTR(hwrev, "0x%x\n", card->cid.hwrev);
+MMC_DEV_ATTR(manfid, "0x%06x\n", card->cid.manfid);
+MMC_DEV_ATTR(name, "%s\n", card->cid.prod_name);
+MMC_DEV_ATTR(oemid, "0x%04x\n", card->cid.oemid);
+MMC_DEV_ATTR(serial, "0x%08x\n", card->cid.serial);
+
+static ssize_t mmc_boot_info_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ char *boot_partition[8] = {
+ "Device not boot enabled",
+ "Boot partition 1 enabled",
+ "Boot partition 2 enabled",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "User area enabled for boot"};
+
+ char *bus_width[4] = {
+ "x1 (sdr) or x4 (ddr) bus width in boot operation mode",
+ "x4 (sdr/ddr) bus width in boot operation mode",
+ "x8 (sdr/ddr) bus width in boot operation mode",
+ "Reserved"};
+
+ char *boot_mode[4] = {
+ "Use single data rate + backward compatible timings in boot operation",
+ "Use single data rate + high speed timings in boot operation mode",
+ "Use dual data rate in boot operation",
+ "Reserved"};
+
+ int partition;
+ int width;
+ int mode;
+ u8 *ext_csd = NULL;
+ struct mmc_card *card = container_of(dev, struct mmc_card, dev);
+
+ /* read it again because user may change it */
+ mmc_claim_host(card->host);
+ mmc_get_ext_csd(card, &ext_csd);
+ mmc_release_host(card->host);
+ mmc_read_ext_csd(card, ext_csd);
+
+ partition = (card->ext_csd.boot_config >> 3) & 0x7;
+ width = card->ext_csd.boot_bus_width & 0x3;
+ mode = (card->ext_csd.boot_bus_width >> 3) & 0x3;
+
+ return sprintf(buf,
+ "boot_info:0x%02x;\n"
+ " ALT_BOOT_MODE:%x - %s\n"
+ " DDR_BOOT_MODE:%x - %s\n"
+ " HS_BOOTMODE:%x - %s\n"
+ "boot_size:%04dKB\n"
+ "boot_partition:0x%02x;\n"
+ " BOOT_ACK:%x - %s\n"
+ " BOOT_PARTITION-ENABLE: %x - %s\n"
+ "boot_bus:0x%02x\n"
+ " BOOT_MODE:%x - %s\n"
+ " RESET_BOOT_BUS_WIDTH:%x - %s\n"
+ " BOOT_BUS_WIDTH:%x - %s\n",
+
+ card->ext_csd.boot_info,
+ !!(card->ext_csd.boot_info & 0x1),
+ (card->ext_csd.boot_info & 0x1) ?
+ "Supports alternate boot method" :
+ "Does not support alternate boot method",
+ !!(card->ext_csd.boot_info & 0x2),
+ (card->ext_csd.boot_info & 0x2) ?
+ "Supports alternate dual data rate during boot" :
+ "Does not support dual data rate during boot",
+ !!(card->ext_csd.boot_info & 0x4),
+ (card->ext_csd.boot_info & 0x4) ?
+ "Supports high speed timing during boot" :
+ "Does not support high speed timing during boot",
+
+ card->ext_csd.boot_size >> 10,
+
+ card->ext_csd.boot_config,
+ !!(card->ext_csd.boot_config & 0x40),
+ (card->ext_csd.boot_config & 0x40) ?
+ "Boot acknowledge sent during boot operation" :
+ "No boot acknowledge sent",
+ partition,
+ boot_partition[partition],
+
+ card->ext_csd.boot_bus_width,
+ mode,
+ boot_mode[mode],
+ !!(card->ext_csd.boot_bus_width & 0x4),
+ (card->ext_csd.boot_bus_width & 0x4) ?
+ "Retain boot bus width and boot mode after boot operation" :
+ "Reset bus width to x1, single data rate and backward"
+ "compatible timings after boot operation",
+ width,
+ bus_width[width]);
+}
+
+DEVICE_ATTR(boot_config, S_IWUSR | S_IWGRP, NULL, setup_boot_partitions);
+DEVICE_ATTR(boot_bus_config, S_IWUSR | S_IWGRP, NULL, setup_boot_bus);
+DEVICE_ATTR(boot_info, S_IRUGO, mmc_boot_info_show, NULL);
+MMC_DEV_ATTR(enhanced_area_offset, "%llu\n",
+ card->ext_csd.enhanced_area_offset);
+MMC_DEV_ATTR(enhanced_area_size, "%u\n", card->ext_csd.enhanced_area_size);
+
+static struct attribute *mmc_std_attrs[] = {
+ &dev_attr_cid.attr,
+ &dev_attr_csd.attr,
+ &dev_attr_date.attr,
+ &dev_attr_erase_size.attr,
+ &dev_attr_preferred_erase_size.attr,
+ &dev_attr_fwrev.attr,
+ &dev_attr_hwrev.attr,
+ &dev_attr_manfid.attr,
+ &dev_attr_name.attr,
+ &dev_attr_oemid.attr,
+ &dev_attr_serial.attr,
+ &dev_attr_boot_info.attr,
+ &dev_attr_boot_config.attr,
+ &dev_attr_boot_bus_config.attr,
+ &dev_attr_enhanced_area_offset.attr,
+ &dev_attr_enhanced_area_size.attr,
+ NULL,
+};
+
+static struct attribute_group mmc_std_attr_group = {
+ .attrs = mmc_std_attrs,
+};
+
+static const struct attribute_group *mmc_attr_groups[] = {
+ &mmc_std_attr_group,
+ NULL,
+};
+
+static struct device_type mmc_type = {
+ .groups = mmc_attr_groups,
+};
+
+/*
+ * Handle the detection and initialisation of a 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,
+ struct mmc_card *oldcard)
+{
+ struct mmc_card *card;
+ int err, ddr = 0;
+ u32 cid[4];
+ unsigned int max_dtr;
+ u32 rocr;
+ u8 *ext_csd = NULL;
+
+ BUG_ON(!host);
+ WARN_ON(!host->claimed);
+
+ /*
+ * Since we're changing the OCR value, we seem to
+ * need to tell some cards to go back to the idle
+ * state. We wait 1ms to give cards time to
+ * respond.
+ */
+ mmc_go_idle(host);
+
+ /* The extra bit indicates that we support high capacity */
+ err = mmc_send_op_cond(host, ocr | (1 << 30), &rocr);
+ if (err)
+ goto err;
+
+ /*
+ * For SPI, enable CRC as appropriate.
+ */
+ if (mmc_host_is_spi(host)) {
+ err = mmc_spi_set_crc(host, use_spi_crc);
+ if (err)
+ goto err;
+ }
+
+ /*
+ * Fetch CID from card.
+ */
+ if (mmc_host_is_spi(host))
+ err = mmc_send_cid(host, cid);
+ else
+ err = mmc_all_send_cid(host, cid);
+ if (err)
+ goto err;
+
+ if (oldcard) {
+ if (memcmp(cid, oldcard->raw_cid, sizeof(cid)) != 0) {
+ err = -ENOENT;
+ goto err;
+ }
+
+ card = oldcard;
+ } else {
+ /*
+ * Allocate card structure.
+ */
+ card = mmc_alloc_card(host, &mmc_type);
+ if (IS_ERR(card)) {
+ err = PTR_ERR(card);
+ goto err;
+ }
+
+ card->type = MMC_TYPE_MMC;
+ card->rca = 1;
+ memcpy(card->raw_cid, cid, sizeof(card->raw_cid));
+ }
+
+ /*
+ * For native busses: set card RCA and quit open drain mode.
+ */
+ if (!mmc_host_is_spi(host)) {
+ err = mmc_set_relative_addr(card);
+ if (err)
+ goto free_card;
+
+ mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
+ }
+
+ if (!oldcard) {
+ /*
+ * Fetch CSD from card.
+ */
+ err = mmc_send_csd(card, card->raw_csd);
+ if (err)
+ goto free_card;
+
+ err = mmc_decode_csd(card);
+ if (err)
+ goto free_card;
+ err = mmc_decode_cid(card);
+ if (err)
+ goto free_card;
+ }
+
+ /*
+ * Select card, as all following commands rely on that.
+ */
+ if (!mmc_host_is_spi(host)) {
+ err = mmc_select_card(card);
+ if (err)
+ goto free_card;
+ }
+
+ if (!oldcard) {
+ /*
+ * Fetch and process extended CSD.
+ */
+
+ err = mmc_get_ext_csd(card, &ext_csd);
+ if (err)
+ goto free_card;
+ err = mmc_read_ext_csd(card, ext_csd);
+ if (err)
+ goto free_card;
+
+ /* If doing byte addressing, check if required to do sector
+ * addressing. Handle the case of <2GB cards needing sector
+ * addressing. See section 8.1 JEDEC Standard JED84-A441;
+ * ocr register has bit 30 set for sector addressing.
+ */
+ if (!(mmc_card_blockaddr(card)) && (rocr & (1<<30)))
+ mmc_card_set_blockaddr(card);
+
+ /* Erase size depends on CSD and Extended CSD */
+ mmc_set_erase_size(card);
+ }
+
+ /*
+ * If enhanced_area_en is TRUE, host needs to enable ERASE_GRP_DEF
+ * bit. This bit will be lost every time after a reset or power off.
+ */
+ if (card->ext_csd.enhanced_area_en) {
+ err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_ERASE_GROUP_DEF, 1, 0);
+
+ if (err && err != -EBADMSG)
+ goto free_card;
+
+ if (err) {
+ err = 0;
+ /*
+ * Just disable enhanced area off & sz
+ * will try to enable ERASE_GROUP_DEF
+ * during next time reinit
+ */
+ card->ext_csd.enhanced_area_offset = -EINVAL;
+ card->ext_csd.enhanced_area_size = -EINVAL;
+ } else {
+ card->ext_csd.erase_group_def = 1;
+ /*
+ * enable ERASE_GRP_DEF successfully.
+ * This will affect the erase size, so
+ * here need to reset erase size
+ */
+ mmc_set_erase_size(card);
+ }
+ }
+
+ /*
+ * Ensure eMMC user default partition is enabled
+ */
+ if (card->ext_csd.part_config & EXT_CSD_PART_CONFIG_ACC_MASK) {
+ card->ext_csd.part_config &= ~EXT_CSD_PART_CONFIG_ACC_MASK;
+ err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_PART_CONFIG,
+ card->ext_csd.part_config,
+ card->ext_csd.part_time);
+ if (err && err != -EBADMSG)
+ goto free_card;
+ }
+
+ /*
+ * Activate high speed (if supported)
+ */
+ if ((card->ext_csd.hs_max_dtr != 0) &&
+ (host->caps & MMC_CAP_MMC_HIGHSPEED)) {
+ err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_HS_TIMING, 1, 0);
+ if (err && err != -EBADMSG)
+ goto free_card;
+
+ if (err) {
+ printk(KERN_WARNING "%s: switch to highspeed failed\n",
+ mmc_hostname(card->host));
+ err = 0;
+ } else {
+ mmc_card_set_highspeed(card);
+ mmc_set_timing(card->host, MMC_TIMING_MMC_HS);
+ }
+ }
+
+ /*
+ * Compute bus speed.
+ */
+ max_dtr = (unsigned int)-1;
+
+ if (mmc_card_highspeed(card)) {
+ if (max_dtr > card->ext_csd.hs_max_dtr)
+ max_dtr = card->ext_csd.hs_max_dtr;
+ } else if (max_dtr > card->csd.max_dtr) {
+ max_dtr = card->csd.max_dtr;
+ }
+
+ mmc_set_clock(host, max_dtr);
+
+ /*
+ * Indicate DDR mode (if supported).
+ */
+ if (mmc_card_highspeed(card)) {
+ if ((card->ext_csd.card_type & EXT_CSD_CARD_TYPE_DDR_1_8V)
+ && ((host->caps & (MMC_CAP_1_8V_DDR |
+ MMC_CAP_UHS_DDR50))
+ == (MMC_CAP_1_8V_DDR | MMC_CAP_UHS_DDR50)))
+ ddr = MMC_1_8V_DDR_MODE;
+ else if ((card->ext_csd.card_type & EXT_CSD_CARD_TYPE_DDR_1_2V)
+ && ((host->caps & (MMC_CAP_1_2V_DDR |
+ MMC_CAP_UHS_DDR50))
+ == (MMC_CAP_1_2V_DDR | MMC_CAP_UHS_DDR50)))
+ ddr = MMC_1_2V_DDR_MODE;
+ }
+
+ /*
+ * Activate wide bus and DDR (if supported).
+ */
+ if ((card->csd.mmca_vsn >= CSD_SPEC_VER_4) &&
+ (host->caps & (MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA))) {
+ static unsigned ext_csd_bits[][2] = {
+ { EXT_CSD_BUS_WIDTH_8, EXT_CSD_DDR_BUS_WIDTH_8 },
+ { EXT_CSD_BUS_WIDTH_4, EXT_CSD_DDR_BUS_WIDTH_4 },
+ { EXT_CSD_BUS_WIDTH_1, EXT_CSD_BUS_WIDTH_1 },
+ };
+ static unsigned bus_widths[] = {
+ MMC_BUS_WIDTH_8,
+ MMC_BUS_WIDTH_4,
+ MMC_BUS_WIDTH_1
+ };
+ unsigned idx, bus_width = 0;
+
+ if (host->caps & MMC_CAP_8_BIT_DATA)
+ idx = 0;
+ else
+ idx = 1;
+ for (; idx < ARRAY_SIZE(bus_widths); idx++) {
+ bus_width = bus_widths[idx];
+ if (bus_width == MMC_BUS_WIDTH_1)
+ ddr = 0; /* no DDR for 1-bit width */
+ err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_BUS_WIDTH,
+ ext_csd_bits[idx][0],
+ 0);
+ if (!err) {
+ mmc_set_bus_width(card->host, bus_width);
+
+ /*
+ * If controller can't handle bus width test,
+ * compare ext_csd previously read in 1 bit mode
+ * against ext_csd at new bus width
+ */
+ if (!(host->caps & MMC_CAP_BUS_WIDTH_TEST))
+ err = mmc_compare_ext_csds(card,
+ bus_width);
+ else
+ err = mmc_bus_test(card, bus_width);
+ if (!err)
+ break;
+ }
+ }
+
+ if (!err && ddr) {
+ err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_BUS_WIDTH,
+ ext_csd_bits[idx][1],
+ 0);
+ }
+ if (err) {
+ printk(KERN_WARNING "%s: switch to bus width %d ddr %d "
+ "failed\n", mmc_hostname(card->host),
+ 1 << bus_width, ddr);
+ goto free_card;
+ } else if (ddr) {
+ /*
+ * eMMC cards can support 3.3V to 1.2V i/o (vccq)
+ * signaling.
+ *
+ * EXT_CSD_CARD_TYPE_DDR_1_8V means 3.3V or 1.8V vccq.
+ *
+ * 1.8V vccq at 3.3V core voltage (vcc) is not required
+ * in the JEDEC spec for DDR.
+ *
+ * Do not force change in vccq since we are obviously
+ * working and no change to vccq is needed.
+ *
+ * WARNING: eMMC rules are NOT the same as SD DDR
+ */
+ if (ddr == MMC_1_2V_DDR_MODE) {
+ err = mmc_set_signal_voltage(host,
+ MMC_SIGNAL_VOLTAGE_120, 0);
+ if (err)
+ goto err;
+ }
+ mmc_card_set_ddr_mode(card);
+ mmc_set_timing(card->host, MMC_TIMING_UHS_DDR50);
+ mmc_set_bus_width(card->host, bus_width);
+ }
+ }
+
+ if (!oldcard)
+ host->card = card;
+
+ mmc_free_ext_csd(ext_csd);
+ return 0;
+
+free_card:
+ if (!oldcard)
+ mmc_remove_card(card);
+err:
+ mmc_free_ext_csd(ext_csd);
+
+ return err;
+}
+
+/*
+ * Host is being removed. Free up the current card.
+ */
+static void mmc_remove(struct mmc_host *host)
+{
+ BUG_ON(!host);
+ BUG_ON(!host->card);
+
+ mmc_remove_card(host->card);
+ host->card = NULL;
+}
+
+/*
+ * Card detection callback from host.
+ */
+static void mmc_detect(struct mmc_host *host)
+{
+ int err;
+
+ BUG_ON(!host);
+ BUG_ON(!host->card);
+
+ mmc_claim_host(host);
+
+ /*
+ * Just check if our card has been removed.
+ */
+ err = mmc_send_status(host->card, NULL);
+
+ mmc_release_host(host);
+
+ if (err) {
+ mmc_remove(host);
+
+ mmc_claim_host(host);
+ mmc_detach_bus(host);
+ mmc_power_off(host);
+ mmc_release_host(host);
+ }
+}
+
+/*
+ * Suspend callback from host.
+ */
+static int mmc_suspend(struct mmc_host *host)
+{
+ BUG_ON(!host);
+ BUG_ON(!host->card);
+
+ mmc_claim_host(host);
+ if (!mmc_host_is_spi(host))
+ mmc_deselect_cards(host);
+ host->card->state &= ~MMC_STATE_HIGHSPEED;
+ mmc_release_host(host);
+
+ return 0;
+}
+
+/*
+ * Resume callback from host.
+ *
+ * This function tries to determine if the same card is still present
+ * and, if so, restore all state to it.
+ */
+static int mmc_resume(struct mmc_host *host)
+{
+ int err;
+
+ BUG_ON(!host);
+ BUG_ON(!host->card);
+
+ mmc_claim_host(host);
+ err = mmc_send_op_cond(host, 0, 0);
+ err = mmc_init_card(host, host->ocr, host->card);
+ mmc_release_host(host);
+
+ return err;
+}
+
+static int mmc_power_restore(struct mmc_host *host)
+{
+ int ret;
+
+ host->card->state &= ~MMC_STATE_HIGHSPEED;
+ mmc_claim_host(host);
+ ret = mmc_init_card(host, host->ocr, host->card);
+ mmc_release_host(host);
+
+ return ret;
+}
+
+static int mmc_sleep(struct mmc_host *host)
+{
+ struct mmc_card *card = host->card;
+ int err = -ENOSYS;
+
+ if (card && card->ext_csd.rev >= 3) {
+ err = mmc_card_sleepawake(host, 1);
+ if (err < 0)
+ pr_debug("%s: Error %d while putting card into sleep",
+ mmc_hostname(host), err);
+ }
+
+ return err;
+}
+
+static int mmc_awake(struct mmc_host *host)
+{
+ struct mmc_card *card = host->card;
+ int err = -ENOSYS;
+
+ if (card && card->ext_csd.rev >= 3) {
+ err = mmc_card_sleepawake(host, 0);
+ if (err < 0)
+ pr_debug("%s: Error %d while awaking sleeping card",
+ mmc_hostname(host), err);
+ }
+
+ return err;
+}
+
+static const struct mmc_bus_ops mmc_ops = {
+ .awake = mmc_awake,
+ .sleep = mmc_sleep,
+ .remove = mmc_remove,
+ .detect = mmc_detect,
+ .suspend = NULL,
+ .resume = NULL,
+ .power_restore = mmc_power_restore,
+};
+
+static const struct mmc_bus_ops mmc_ops_unsafe = {
+ .awake = mmc_awake,
+ .sleep = mmc_sleep,
+ .remove = mmc_remove,
+ .detect = mmc_detect,
+ .suspend = mmc_suspend,
+ .resume = mmc_resume,
+ .power_restore = mmc_power_restore,
+};
+
+static void mmc_attach_bus_ops(struct mmc_host *host)
+{
+ const struct mmc_bus_ops *bus_ops;
+
+ if (!mmc_card_is_removable(host))
+ bus_ops = &mmc_ops_unsafe;
+ else
+ bus_ops = &mmc_ops;
+ mmc_attach_bus(host, bus_ops);
+}
+
+/*
+ * Starting point for MMC card init.
+ */
+int mmc_attach_mmc(struct mmc_host *host)
+{
+ int err;
+ u32 ocr;
+
+ BUG_ON(!host);
+ WARN_ON(!host->claimed);
+
+ err = mmc_send_op_cond(host, 0, &ocr);
+ if (err)
+ return err;
+
+ mmc_attach_bus_ops(host);
+ if (host->ocr_avail_mmc)
+ host->ocr_avail = host->ocr_avail_mmc;
+
+ /*
+ * We need to get OCR a different way for SPI.
+ */
+ if (mmc_host_is_spi(host)) {
+ err = mmc_spi_read_ocr(host, 1, &ocr);
+ if (err)
+ goto err;
+ }
+
+ /*
+ * Sanity check the voltages that the card claims to
+ * support.
+ */
+ if (ocr & 0x7F) {
+ printk(KERN_WARNING "%s: card claims to support voltages "
+ "below the defined range. These will be ignored.\n",
+ mmc_hostname(host));
+ ocr &= ~0x7F;
+ }
+
+ host->ocr = mmc_select_voltage(host, ocr);
+
+ /*
+ * Can we support the voltage of the card?
+ */
+ if (!host->ocr) {
+ err = -EINVAL;
+ goto err;
+ }
+
+ /*
+ * Detect and init the card.
+ */
+ err = mmc_init_card(host, host->ocr, NULL);
+ if (err)
+ goto err;
+
+ mmc_release_host(host);
+ err = mmc_add_card(host->card);
+ mmc_claim_host(host);
+ if (err)
+ goto remove_card;
+
+ return 0;
+
+remove_card:
+ mmc_release_host(host);
+ mmc_remove_card(host->card);
+ mmc_claim_host(host);
+ host->card = NULL;
+err:
+ mmc_detach_bus(host);
+
+ printk(KERN_ERR "%s: error %d whilst initialising MMC card\n",
+ mmc_hostname(host), err);
+
+ return err;
+}
diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c
new file mode 100644
index 00000000..b11876c2
--- /dev/null
+++ b/drivers/mmc/core/mmc_ops.c
@@ -0,0 +1,550 @@
+/*
+ * linux/drivers/mmc/core/mmc_ops.h
+ *
+ * Copyright 2006-2007 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
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ */
+
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/scatterlist.h>
+
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/mmc.h>
+
+#include "core.h"
+#include "mmc_ops.h"
+
+static int _mmc_select_card(struct mmc_host *host, struct mmc_card *card)
+{
+ int err;
+ struct mmc_command cmd = {0};
+
+ BUG_ON(!host);
+
+ cmd.opcode = MMC_SELECT_CARD;
+
+ if (card) {
+ cmd.arg = card->rca << 16;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
+ } else {
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_NONE | MMC_CMD_AC;
+ }
+
+ err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+int mmc_select_card(struct mmc_card *card)
+{
+ BUG_ON(!card);
+
+ return _mmc_select_card(card->host, card);
+}
+
+int mmc_deselect_cards(struct mmc_host *host)
+{
+ return _mmc_select_card(host, NULL);
+}
+
+int mmc_card_sleepawake(struct mmc_host *host, int sleep)
+{
+ struct mmc_command cmd = {0};
+ struct mmc_card *card = host->card;
+ int err;
+
+ if (sleep)
+ mmc_deselect_cards(host);
+
+ cmd.opcode = MMC_SLEEP_AWAKE;
+ cmd.arg = card->rca << 16;
+ if (sleep)
+ cmd.arg |= 1 << 15;
+
+ cmd.flags = MMC_RSP_R1B | MMC_CMD_AC;
+ err = mmc_wait_for_cmd(host, &cmd, 0);
+ if (err)
+ return err;
+
+ /*
+ * If the host does not wait while the card signals busy, then we will
+ * will have to wait the sleep/awake timeout. Note, we cannot use the
+ * SEND_STATUS command to poll the status because that command (and most
+ * others) is invalid while the card sleeps.
+ */
+ if (!(host->caps & MMC_CAP_WAIT_WHILE_BUSY))
+ mmc_delay(DIV_ROUND_UP(card->ext_csd.sa_timeout, 10000));
+
+ if (!sleep)
+ err = mmc_select_card(card);
+
+ return err;
+}
+
+int mmc_go_idle(struct mmc_host *host)
+{
+ int err;
+ struct mmc_command cmd = {0};
+
+ /*
+ * Non-SPI hosts need to prevent chipselect going active during
+ * GO_IDLE; that would put chips into SPI mode. Remind them of
+ * that in case of hardware that won't pull up DAT3/nCS otherwise.
+ *
+ * SPI hosts ignore ios.chip_select; it's managed according to
+ * rules that must accommodate non-MMC slaves which this layer
+ * won't even know about.
+ */
+ if (!mmc_host_is_spi(host)) {
+ mmc_set_chip_select(host, MMC_CS_HIGH);
+ mmc_delay(1);
+ }
+
+ cmd.opcode = MMC_GO_IDLE_STATE;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_NONE | MMC_CMD_BC;
+
+ err = mmc_wait_for_cmd(host, &cmd, 0);
+
+ mmc_delay(1);
+
+ if (!mmc_host_is_spi(host)) {
+ mmc_set_chip_select(host, MMC_CS_DONTCARE);
+ mmc_delay(1);
+ }
+
+ host->use_spi_crc = 0;
+
+ return err;
+}
+
+int mmc_send_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)
+{
+ struct mmc_command cmd = {0};
+ int i, err = 0;
+
+ BUG_ON(!host);
+
+ cmd.opcode = MMC_SEND_OP_COND;
+ cmd.arg = mmc_host_is_spi(host) ? 0 : ocr;
+ cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R3 | MMC_CMD_BCR;
+
+ for (i = 100; i; i--) {
+ err = mmc_wait_for_cmd(host, &cmd, 0);
+ if (err)
+ break;
+
+ /* if we're just probing, do a single pass */
+ if (ocr == 0)
+ break;
+
+ /* otherwise wait until reset completes */
+ if (mmc_host_is_spi(host)) {
+ if (!(cmd.resp[0] & R1_SPI_IDLE))
+ break;
+ } else {
+ if (cmd.resp[0] & MMC_CARD_BUSY)
+ break;
+ }
+
+ err = -ETIMEDOUT;
+
+ mmc_delay(10);
+ }
+
+ if (rocr && !mmc_host_is_spi(host))
+ *rocr = cmd.resp[0];
+
+ return err;
+}
+
+int mmc_all_send_cid(struct mmc_host *host, u32 *cid)
+{
+ int err;
+ struct mmc_command cmd = {0};
+
+ BUG_ON(!host);
+ BUG_ON(!cid);
+
+ cmd.opcode = MMC_ALL_SEND_CID;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_R2 | MMC_CMD_BCR;
+
+ err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
+ if (err)
+ return err;
+
+ memcpy(cid, cmd.resp, sizeof(u32) * 4);
+
+ return 0;
+}
+
+int mmc_set_relative_addr(struct mmc_card *card)
+{
+ int err;
+ struct mmc_command cmd = {0};
+
+ BUG_ON(!card);
+ BUG_ON(!card->host);
+
+ cmd.opcode = MMC_SET_RELATIVE_ADDR;
+ cmd.arg = card->rca << 16;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
+
+ err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static int
+mmc_send_cxd_native(struct mmc_host *host, u32 arg, u32 *cxd, int opcode)
+{
+ int err;
+ struct mmc_command cmd = {0};
+
+ BUG_ON(!host);
+ BUG_ON(!cxd);
+
+ cmd.opcode = opcode;
+ cmd.arg = arg;
+ cmd.flags = MMC_RSP_R2 | MMC_CMD_AC;
+
+ err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
+ if (err)
+ return err;
+
+ memcpy(cxd, cmd.resp, sizeof(u32) * 4);
+
+ return 0;
+}
+
+static int
+mmc_send_cxd_data(struct mmc_card *card, struct mmc_host *host,
+ u32 opcode, void *buf, unsigned len)
+{
+ struct mmc_request mrq = {0};
+ struct mmc_command cmd = {0};
+ struct mmc_data data = {0};
+ struct scatterlist sg;
+ void *data_buf;
+
+ /* dma onto stack is unsafe/nonportable, but callers to this
+ * routine normally provide temporary on-stack buffers ...
+ */
+ data_buf = kmalloc(len, GFP_KERNEL);
+ if (data_buf == NULL)
+ return -ENOMEM;
+
+ mrq.cmd = &cmd;
+ mrq.data = &data;
+
+ cmd.opcode = opcode;
+ cmd.arg = 0;
+
+ /* NOTE HACK: the MMC_RSP_SPI_R1 is always correct here, but we
+ * rely on callers to never use this with "native" calls for reading
+ * CSD or CID. Native versions of those commands use the R2 type,
+ * not R1 plus a data block.
+ */
+ cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
+
+ data.blksz = len;
+ data.blocks = 1;
+ data.flags = MMC_DATA_READ;
+ data.sg = &sg;
+ data.sg_len = 1;
+
+ sg_init_one(&sg, data_buf, len);
+
+ if (opcode == MMC_SEND_CSD || opcode == MMC_SEND_CID) {
+ /*
+ * The spec states that CSR and CID accesses have a timeout
+ * of 64 clock cycles.
+ */
+ data.timeout_ns = 0;
+ data.timeout_clks = 64;
+ } else
+ mmc_set_data_timeout(&data, card);
+
+ mmc_wait_for_req(host, &mrq);
+
+ memcpy(buf, data_buf, len);
+ kfree(data_buf);
+
+ if (cmd.error)
+ return cmd.error;
+ if (data.error)
+ return data.error;
+
+ return 0;
+}
+
+int mmc_send_csd(struct mmc_card *card, u32 *csd)
+{
+ int ret, i;
+
+ if (!mmc_host_is_spi(card->host))
+ return mmc_send_cxd_native(card->host, card->rca << 16,
+ csd, MMC_SEND_CSD);
+
+ ret = mmc_send_cxd_data(card, card->host, MMC_SEND_CSD, csd, 16);
+ if (ret)
+ return ret;
+
+ for (i = 0;i < 4;i++)
+ csd[i] = be32_to_cpu(csd[i]);
+
+ return 0;
+}
+
+int mmc_send_cid(struct mmc_host *host, u32 *cid)
+{
+ int ret, i;
+
+ if (!mmc_host_is_spi(host)) {
+ if (!host->card)
+ return -EINVAL;
+ return mmc_send_cxd_native(host, host->card->rca << 16,
+ cid, MMC_SEND_CID);
+ }
+
+ ret = mmc_send_cxd_data(NULL, host, MMC_SEND_CID, cid, 16);
+ if (ret)
+ return ret;
+
+ for (i = 0;i < 4;i++)
+ cid[i] = be32_to_cpu(cid[i]);
+
+ return 0;
+}
+
+int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd)
+{
+ return mmc_send_cxd_data(card, card->host, MMC_SEND_EXT_CSD,
+ ext_csd, 512);
+}
+
+int mmc_spi_read_ocr(struct mmc_host *host, int highcap, u32 *ocrp)
+{
+ struct mmc_command cmd = {0};
+ int err;
+
+ cmd.opcode = MMC_SPI_READ_OCR;
+ cmd.arg = highcap ? (1 << 30) : 0;
+ cmd.flags = MMC_RSP_SPI_R3;
+
+ err = mmc_wait_for_cmd(host, &cmd, 0);
+
+ *ocrp = cmd.resp[1];
+ return err;
+}
+
+int mmc_spi_set_crc(struct mmc_host *host, int use_crc)
+{
+ struct mmc_command cmd = {0};
+ int err;
+
+ cmd.opcode = MMC_SPI_CRC_ON_OFF;
+ cmd.flags = MMC_RSP_SPI_R1;
+ cmd.arg = use_crc;
+
+ err = mmc_wait_for_cmd(host, &cmd, 0);
+ if (!err)
+ host->use_spi_crc = use_crc;
+ return err;
+}
+
+/**
+ * mmc_switch - modify EXT_CSD register
+ * @card: the MMC card associated with the data transfer
+ * @set: cmd set values
+ * @index: EXT_CSD register index
+ * @value: value to program into EXT_CSD register
+ * @timeout_ms: timeout (ms) for operation performed by register write,
+ * timeout of zero implies maximum possible timeout
+ *
+ * Modifies the EXT_CSD register for selected card.
+ */
+int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value,
+ unsigned int timeout_ms)
+{
+ int err;
+ struct mmc_command cmd = {0};
+ u32 status;
+
+ BUG_ON(!card);
+ BUG_ON(!card->host);
+
+ cmd.opcode = MMC_SWITCH;
+ cmd.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
+ (index << 16) |
+ (value << 8) |
+ set;
+ cmd.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
+ cmd.cmd_timeout_ms = timeout_ms;
+
+ err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES);
+ if (err)
+ return err;
+ mdelay(1);
+
+ /* Must check status to be sure of no errors */
+ do {
+ err = mmc_send_status(card, &status);
+ if (err)
+ return err;
+ if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY)
+ break;
+ if (mmc_host_is_spi(card->host))
+ break;
+ } while (R1_CURRENT_STATE(status) == 7);
+
+ if (mmc_host_is_spi(card->host)) {
+ if (status & R1_SPI_ILLEGAL_COMMAND)
+ return -EBADMSG;
+ } else {
+ if (status & 0xFDFFA000)
+ printk(KERN_WARNING "%s: unexpected status %#x after "
+ "switch", mmc_hostname(card->host), status);
+ if (status & R1_SWITCH_ERROR)
+ return -EBADMSG;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mmc_switch);
+
+int mmc_send_status(struct mmc_card *card, u32 *status)
+{
+ int err;
+ struct mmc_command cmd = {0};
+
+ BUG_ON(!card);
+ BUG_ON(!card->host);
+
+ cmd.opcode = MMC_SEND_STATUS;
+ if (!mmc_host_is_spi(card->host))
+ cmd.arg = card->rca << 16;
+ cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;
+
+ err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES);
+ if (err)
+ return err;
+
+ /* NOTE: callers are required to understand the difference
+ * between "native" and SPI format status words!
+ */
+ if (status)
+ *status = cmd.resp[0];
+
+ return 0;
+}
+
+static int
+mmc_send_bus_test(struct mmc_card *card, struct mmc_host *host, u8 opcode,
+ u8 len)
+{
+ struct mmc_request mrq = {0};
+ struct mmc_command cmd = {0};
+ struct mmc_data data = {0};
+ struct scatterlist sg;
+ u8 *data_buf;
+ u8 *test_buf;
+ int i, err;
+ static u8 testdata_8bit[8] = { 0x55, 0xaa, 0, 0, 0, 0, 0, 0 };
+ static u8 testdata_4bit[4] = { 0x5a, 0, 0, 0 };
+
+ /* dma onto stack is unsafe/nonportable, but callers to this
+ * routine normally provide temporary on-stack buffers ...
+ */
+ data_buf = kmalloc(len, GFP_KERNEL);
+ if (!data_buf)
+ return -ENOMEM;
+
+ if (len == 8)
+ test_buf = testdata_8bit;
+ else if (len == 4)
+ test_buf = testdata_4bit;
+ else {
+ printk(KERN_ERR "%s: Invalid bus_width %d\n",
+ mmc_hostname(host), len);
+ kfree(data_buf);
+ return -EINVAL;
+ }
+
+ if (opcode == MMC_BUS_TEST_W)
+ memcpy(data_buf, test_buf, len);
+
+ mrq.cmd = &cmd;
+ mrq.data = &data;
+ cmd.opcode = opcode;
+ cmd.arg = 0;
+
+ /* NOTE HACK: the MMC_RSP_SPI_R1 is always correct here, but we
+ * rely on callers to never use this with "native" calls for reading
+ * CSD or CID. Native versions of those commands use the R2 type,
+ * not R1 plus a data block.
+ */
+ cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
+
+ data.blksz = len;
+ data.blocks = 1;
+ if (opcode == MMC_BUS_TEST_R)
+ data.flags = MMC_DATA_READ;
+ else
+ data.flags = MMC_DATA_WRITE;
+
+ data.sg = &sg;
+ data.sg_len = 1;
+ sg_init_one(&sg, data_buf, len);
+ mmc_wait_for_req(host, &mrq);
+ err = 0;
+ if (opcode == MMC_BUS_TEST_R) {
+ for (i = 0; i < len / 4; i++)
+ if ((test_buf[i] ^ data_buf[i]) != 0xff) {
+ err = -EIO;
+ break;
+ }
+ }
+ kfree(data_buf);
+
+ if (cmd.error)
+ return cmd.error;
+ if (data.error)
+ return data.error;
+
+ return err;
+}
+
+int mmc_bus_test(struct mmc_card *card, u8 bus_width)
+{
+ int err, width;
+
+ if (bus_width == MMC_BUS_WIDTH_8)
+ width = 8;
+ else if (bus_width == MMC_BUS_WIDTH_4)
+ width = 4;
+ else if (bus_width == MMC_BUS_WIDTH_1)
+ return 0; /* no need for test */
+ else
+ return -EINVAL;
+
+ /*
+ * Ignore errors from BUS_TEST_W. BUS_TEST_R will fail if there
+ * is a problem. This improves chances that the test will work.
+ */
+ mmc_send_bus_test(card, card->host, MMC_BUS_TEST_W, width);
+ err = mmc_send_bus_test(card, card->host, MMC_BUS_TEST_R, width);
+ return err;
+}
diff --git a/drivers/mmc/core/mmc_ops.h b/drivers/mmc/core/mmc_ops.h
new file mode 100644
index 00000000..9276946f
--- /dev/null
+++ b/drivers/mmc/core/mmc_ops.h
@@ -0,0 +1,31 @@
+/*
+ * linux/drivers/mmc/core/mmc_ops.h
+ *
+ * Copyright 2006-2007 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
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ */
+
+#ifndef _MMC_MMC_OPS_H
+#define _MMC_MMC_OPS_H
+
+int mmc_select_card(struct mmc_card *card);
+int mmc_deselect_cards(struct mmc_host *host);
+int mmc_go_idle(struct mmc_host *host);
+int mmc_send_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr);
+int mmc_all_send_cid(struct mmc_host *host, u32 *cid);
+int mmc_set_relative_addr(struct mmc_card *card);
+int mmc_send_csd(struct mmc_card *card, u32 *csd);
+int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd);
+int mmc_send_status(struct mmc_card *card, u32 *status);
+int mmc_send_cid(struct mmc_host *host, u32 *cid);
+int mmc_spi_read_ocr(struct mmc_host *host, int highcap, u32 *ocrp);
+int mmc_spi_set_crc(struct mmc_host *host, int use_crc);
+int mmc_card_sleepawake(struct mmc_host *host, int sleep);
+int mmc_bus_test(struct mmc_card *card, u8 bus_width);
+
+#endif
+
diff --git a/drivers/mmc/core/quirks.c b/drivers/mmc/core/quirks.c
new file mode 100644
index 00000000..3a596217
--- /dev/null
+++ b/drivers/mmc/core/quirks.c
@@ -0,0 +1,79 @@
+/*
+ * This file contains work-arounds for many known SD/MMC
+ * and SDIO hardware bugs.
+ *
+ * Copyright (c) 2011 Andrei Warkentin <andreiw@motorola.com>
+ * Copyright (c) 2011 Pierre Tardy <tardyp@gmail.com>
+ * Inspired from pci fixup code:
+ * Copyright (c) 1999 Martin Mares <mj@ucw.cz>
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/mmc/card.h>
+
+#ifndef SDIO_VENDOR_ID_TI
+#define SDIO_VENDOR_ID_TI 0x0097
+#endif
+
+#ifndef SDIO_DEVICE_ID_TI_WL1271
+#define SDIO_DEVICE_ID_TI_WL1271 0x4076
+#endif
+
+/*
+ * This hook just adds a quirk for all sdio devices
+ */
+static void add_quirk_for_sdio_devices(struct mmc_card *card, int data)
+{
+ if (mmc_card_sdio(card))
+ card->quirks |= data;
+}
+
+static const struct mmc_fixup mmc_fixup_methods[] = {
+ /* by default sdio devices are considered CLK_GATING broken */
+ /* good cards will be whitelisted as they are tested */
+ SDIO_FIXUP(SDIO_ANY_ID, SDIO_ANY_ID,
+ add_quirk_for_sdio_devices,
+ MMC_QUIRK_BROKEN_CLK_GATING),
+
+ SDIO_FIXUP(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271,
+ remove_quirk, MMC_QUIRK_BROKEN_CLK_GATING),
+
+ SDIO_FIXUP(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271,
+ add_quirk, MMC_QUIRK_NONSTD_FUNC_IF),
+
+ SDIO_FIXUP(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271,
+ add_quirk, MMC_QUIRK_DISABLE_CD),
+
+ END_FIXUP
+};
+
+void mmc_fixup_device(struct mmc_card *card, const struct mmc_fixup *table)
+{
+ const struct mmc_fixup *f;
+ u64 rev = cid_rev_card(card);
+
+ /* Non-core specific workarounds. */
+ if (!table)
+ table = mmc_fixup_methods;
+
+ for (f = table; f->vendor_fixup; f++) {
+ if ((f->manfid == CID_MANFID_ANY ||
+ f->manfid == card->cid.manfid) &&
+ (f->oemid == CID_OEMID_ANY ||
+ f->oemid == card->cid.oemid) &&
+ (f->name == CID_NAME_ANY ||
+ !strncmp(f->name, card->cid.prod_name,
+ sizeof(card->cid.prod_name))) &&
+ (f->cis_vendor == card->cis.vendor ||
+ f->cis_vendor == (u16) SDIO_ANY_ID) &&
+ (f->cis_device == card->cis.device ||
+ f->cis_device == (u16) SDIO_ANY_ID) &&
+ rev >= f->rev_start && rev <= f->rev_end) {
+ dev_dbg(&card->dev, "calling %pF\n", f->vendor_fixup);
+ f->vendor_fixup(card, f->data);
+ }
+ }
+}
+EXPORT_SYMBOL(mmc_fixup_device);
diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c
new file mode 100644
index 00000000..074ded0f
--- /dev/null
+++ b/drivers/mmc/core/sd.c
@@ -0,0 +1,1269 @@
+/*
+ * linux/drivers/mmc/core/sd.c
+ *
+ * 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.
+ *
+ * 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/err.h>
+#include <linux/slab.h>
+
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/sd.h>
+
+#include "core.h"
+#include "bus.h"
+#include "mmc_ops.h"
+#include "sd.h"
+#include "sd_ops.h"
+
+static const unsigned int tran_exp[] = {
+ 10000, 100000, 1000000, 10000000,
+ 0, 0, 0, 0
+};
+
+static const unsigned char tran_mant[] = {
+ 0, 10, 12, 13, 15, 20, 25, 30,
+ 35, 40, 45, 50, 55, 60, 70, 80,
+};
+
+static const unsigned int tacc_exp[] = {
+ 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000,
+};
+
+static const unsigned int tacc_mant[] = {
+ 0, 10, 12, 13, 15, 20, 25, 30,
+ 35, 40, 45, 50, 55, 60, 70, 80,
+};
+
+#define UNSTUFF_BITS(resp,start,size) \
+ ({ \
+ const int __size = size; \
+ const u32 __mask = (__size < 32 ? 1 << __size : 0) - 1; \
+ const int __off = 3 - ((start) / 32); \
+ const int __shft = (start) & 31; \
+ u32 __res; \
+ \
+ __res = resp[__off] >> __shft; \
+ if (__size + __shft > 32) \
+ __res |= resp[__off-1] << ((32 - __shft) % 32); \
+ __res & __mask; \
+ })
+
+/*
+ * Given the decoded CSD structure, decode the raw CID to our CID structure.
+ */
+void mmc_decode_cid(struct mmc_card *card)
+{
+ u32 *resp = card->raw_cid;
+
+ memset(&card->cid, 0, sizeof(struct mmc_cid));
+
+ /*
+ * SD doesn't currently have a version field so we will
+ * have to assume we can parse this.
+ */
+ card->cid.manfid = UNSTUFF_BITS(resp, 120, 8);
+ card->cid.oemid = UNSTUFF_BITS(resp, 104, 16);
+ card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8);
+ card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8);
+ card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8);
+ card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8);
+ card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8);
+ card->cid.hwrev = UNSTUFF_BITS(resp, 60, 4);
+ card->cid.fwrev = UNSTUFF_BITS(resp, 56, 4);
+ card->cid.serial = UNSTUFF_BITS(resp, 24, 32);
+ card->cid.year = UNSTUFF_BITS(resp, 12, 8);
+ card->cid.month = UNSTUFF_BITS(resp, 8, 4);
+
+ card->cid.year += 2000; /* SD cards year offset */
+}
+
+/*
+ * Given a 128-bit response, decode to our card CSD structure.
+ */
+static int mmc_decode_csd(struct mmc_card *card)
+{
+ struct mmc_csd *csd = &card->csd;
+ unsigned int e, m, csd_struct;
+ u32 *resp = card->raw_csd;
+
+ csd_struct = UNSTUFF_BITS(resp, 126, 2);
+
+ switch (csd_struct) {
+ case 0:
+ m = UNSTUFF_BITS(resp, 115, 4);
+ e = UNSTUFF_BITS(resp, 112, 3);
+ csd->tacc_ns = (tacc_exp[e] * tacc_mant[m] + 9) / 10;
+ csd->tacc_clks = UNSTUFF_BITS(resp, 104, 8) * 100;
+
+ m = UNSTUFF_BITS(resp, 99, 4);
+ e = UNSTUFF_BITS(resp, 96, 3);
+ csd->max_dtr = tran_exp[e] * tran_mant[m];
+ csd->cmdclass = UNSTUFF_BITS(resp, 84, 12);
+
+ e = UNSTUFF_BITS(resp, 47, 3);
+ m = UNSTUFF_BITS(resp, 62, 12);
+ csd->capacity = (1 + m) << (e + 2);
+
+ csd->read_blkbits = UNSTUFF_BITS(resp, 80, 4);
+ csd->read_partial = UNSTUFF_BITS(resp, 79, 1);
+ csd->write_misalign = UNSTUFF_BITS(resp, 78, 1);
+ csd->read_misalign = UNSTUFF_BITS(resp, 77, 1);
+ csd->r2w_factor = UNSTUFF_BITS(resp, 26, 3);
+ csd->write_blkbits = UNSTUFF_BITS(resp, 22, 4);
+ csd->write_partial = UNSTUFF_BITS(resp, 21, 1);
+
+ if (UNSTUFF_BITS(resp, 46, 1)) {
+ csd->erase_size = 1;
+ } else if (csd->write_blkbits >= 9) {
+ csd->erase_size = UNSTUFF_BITS(resp, 39, 7) + 1;
+ csd->erase_size <<= csd->write_blkbits - 9;
+ }
+ break;
+ case 1:
+ /*
+ * This is a block-addressed SDHC or SDXC card. Most
+ * interesting fields are unused and have fixed
+ * values. To avoid getting tripped by buggy cards,
+ * we assume those fixed values ourselves.
+ */
+ mmc_card_set_blockaddr(card);
+
+ csd->tacc_ns = 0; /* Unused */
+ csd->tacc_clks = 0; /* Unused */
+
+ m = UNSTUFF_BITS(resp, 99, 4);
+ e = UNSTUFF_BITS(resp, 96, 3);
+ csd->max_dtr = tran_exp[e] * tran_mant[m];
+ csd->cmdclass = UNSTUFF_BITS(resp, 84, 12);
+ csd->c_size = UNSTUFF_BITS(resp, 48, 22);
+
+ /* SDXC cards have a minimum C_SIZE of 0x00FFFF */
+ if (csd->c_size >= 0xFFFF)
+ mmc_card_set_ext_capacity(card);
+
+ m = UNSTUFF_BITS(resp, 48, 22);
+ csd->capacity = (1 + m) << 10;
+
+ csd->read_blkbits = 9;
+ csd->read_partial = 0;
+ csd->write_misalign = 0;
+ csd->read_misalign = 0;
+ csd->r2w_factor = 4; /* Unused */
+ csd->write_blkbits = 9;
+ csd->write_partial = 0;
+ csd->erase_size = 1;
+ break;
+ default:
+ printk(KERN_ERR "%s: unrecognised CSD structure version %d\n",
+ mmc_hostname(card->host), csd_struct);
+ return -EINVAL;
+ }
+
+ card->erase_size = csd->erase_size;
+
+ return 0;
+}
+
+/*
+ * Given a 64-bit response, decode to our card SCR structure.
+ */
+static int mmc_decode_scr(struct mmc_card *card)
+{
+ struct sd_scr *scr = &card->scr;
+ unsigned int scr_struct;
+ u32 resp[4];
+
+ resp[3] = card->raw_scr[1];
+ resp[2] = card->raw_scr[0];
+
+ scr_struct = UNSTUFF_BITS(resp, 60, 4);
+ if (scr_struct != 0) {
+ printk(KERN_ERR "%s: unrecognised SCR structure version %d\n",
+ mmc_hostname(card->host), scr_struct);
+ return -EINVAL;
+ }
+
+ scr->sda_vsn = UNSTUFF_BITS(resp, 56, 4);
+ scr->bus_widths = UNSTUFF_BITS(resp, 48, 4);
+ if (scr->sda_vsn == SCR_SPEC_VER_2)
+ /* Check if Physical Layer Spec v3.0 is supported */
+ scr->sda_spec3 = UNSTUFF_BITS(resp, 47, 1);
+
+ if (UNSTUFF_BITS(resp, 55, 1))
+ card->erased_byte = 0xFF;
+ else
+ card->erased_byte = 0x0;
+
+ if (scr->sda_spec3)
+ scr->cmds = UNSTUFF_BITS(resp, 32, 2);
+ return 0;
+}
+
+/*
+ * Fetch and process SD Status register.
+ */
+static int mmc_read_ssr(struct mmc_card *card)
+{
+ unsigned int au, es, et, eo;
+ int err, i;
+ u32 *ssr;
+
+ if (!(card->csd.cmdclass & CCC_APP_SPEC)) {
+ printk(KERN_WARNING "%s: card lacks mandatory SD Status "
+ "function.\n", mmc_hostname(card->host));
+ return 0;
+ }
+
+ ssr = kmalloc(64, GFP_KERNEL);
+ if (!ssr)
+ return -ENOMEM;
+
+ err = mmc_app_sd_status(card, ssr);
+ if (err) {
+ printk(KERN_WARNING "%s: problem reading SD Status "
+ "register.\n", mmc_hostname(card->host));
+ err = 0;
+ goto out;
+ }
+
+ for (i = 0; i < 16; i++)
+ ssr[i] = be32_to_cpu(ssr[i]);
+
+ /*
+ * UNSTUFF_BITS only works with four u32s so we have to offset the
+ * bitfield positions accordingly.
+ */
+ au = UNSTUFF_BITS(ssr, 428 - 384, 4);
+ if (au > 0 || au <= 9) {
+ card->ssr.au = 1 << (au + 4);
+ es = UNSTUFF_BITS(ssr, 408 - 384, 16);
+ et = UNSTUFF_BITS(ssr, 402 - 384, 6);
+ eo = UNSTUFF_BITS(ssr, 400 - 384, 2);
+ if (es && et) {
+ card->ssr.erase_timeout = (et * 1000) / es;
+ card->ssr.erase_offset = eo * 1000;
+ }
+ } else {
+ printk(KERN_WARNING "%s: SD Status: Invalid Allocation Unit "
+ "size.\n", mmc_hostname(card->host));
+ }
+out:
+ kfree(ssr);
+ return err;
+}
+
+/*
+ * Fetches and decodes switch information
+ */
+static int mmc_read_switch(struct mmc_card *card)
+{
+ int err;
+ u8 *status;
+
+ if (card->scr.sda_vsn < SCR_SPEC_VER_1)
+ return 0;
+
+ if (!(card->csd.cmdclass & CCC_SWITCH)) {
+ printk(KERN_WARNING "%s: card lacks mandatory switch "
+ "function, performance might suffer.\n",
+ mmc_hostname(card->host));
+ return 0;
+ }
+
+ err = -EIO;
+
+ status = kmalloc(64, GFP_KERNEL);
+ if (!status) {
+ printk(KERN_ERR "%s: could not allocate a buffer for "
+ "switch capabilities.\n",
+ mmc_hostname(card->host));
+ return -ENOMEM;
+ }
+
+ /* Find out the supported Bus Speed Modes. */
+ err = mmc_sd_switch(card, 0, 0, 1, status);
+ if (err) {
+ /*
+ * If the host or the card can't do the switch,
+ * fail more gracefully.
+ */
+ if (err != -EINVAL && err != -ENOSYS && err != -EFAULT)
+ goto out;
+
+ printk(KERN_WARNING "%s: problem reading Bus Speed modes.\n",
+ mmc_hostname(card->host));
+ err = 0;
+
+ goto out;
+ }
+
+ if (status[13] & 0x02)
+ card->sw_caps.hs_max_dtr = 50000000;
+
+ if (card->scr.sda_spec3) {
+ card->sw_caps.sd3_bus_mode = status[13];
+
+ /* Find out Driver Strengths supported by the card */
+ err = mmc_sd_switch(card, 0, 2, 1, status);
+ if (err) {
+ /*
+ * If the host or the card can't do the switch,
+ * fail more gracefully.
+ */
+ if (err != -EINVAL && err != -ENOSYS && err != -EFAULT)
+ goto out;
+
+ printk(KERN_WARNING "%s: problem reading "
+ "Driver Strength.\n",
+ mmc_hostname(card->host));
+ err = 0;
+
+ goto out;
+ }
+
+ card->sw_caps.sd3_drv_type = status[9];
+
+ /* Find out Current Limits supported by the card */
+ err = mmc_sd_switch(card, 0, 3, 1, status);
+ if (err) {
+ /*
+ * If the host or the card can't do the switch,
+ * fail more gracefully.
+ */
+ if (err != -EINVAL && err != -ENOSYS && err != -EFAULT)
+ goto out;
+
+ printk(KERN_WARNING "%s: problem reading "
+ "Current Limit.\n",
+ mmc_hostname(card->host));
+ err = 0;
+
+ goto out;
+ }
+
+ card->sw_caps.sd3_curr_limit = status[7];
+ }
+
+out:
+ kfree(status);
+
+ return err;
+}
+
+/*
+ * Test if the card supports high-speed mode and, if so, switch to it.
+ */
+int mmc_sd_switch_hs(struct mmc_card *card)
+{
+ int err;
+ u8 *status;
+
+ if (card->scr.sda_vsn < SCR_SPEC_VER_1)
+ return 0;
+
+ if (!(card->csd.cmdclass & CCC_SWITCH))
+ return 0;
+
+ if (!(card->host->caps & MMC_CAP_SD_HIGHSPEED))
+ return 0;
+
+ if (card->sw_caps.hs_max_dtr == 0)
+ return 0;
+
+ err = -EIO;
+
+ status = kmalloc(64, GFP_KERNEL);
+ if (!status) {
+ printk(KERN_ERR "%s: could not allocate a buffer for "
+ "switch capabilities.\n", mmc_hostname(card->host));
+ return -ENOMEM;
+ }
+
+ err = mmc_sd_switch(card, 1, 0, 1, status);
+ if (err)
+ goto out;
+
+ if ((status[16] & 0xF) != 1) {
+ printk(KERN_WARNING "%s: Problem switching card "
+ "into high-speed mode!\n",
+ mmc_hostname(card->host));
+ err = 0;
+ } else {
+ err = 1;
+ }
+
+out:
+ kfree(status);
+
+ return err;
+}
+
+static int sd_select_driver_type(struct mmc_card *card, u8 *status)
+{
+ int host_drv_type = 0, card_drv_type = 0;
+ int err;
+
+ /*
+ * If the host doesn't support any of the Driver Types A,C or D,
+ * default Driver Type B is used.
+ */
+ if (!(card->host->caps & (MMC_CAP_DRIVER_TYPE_A | MMC_CAP_DRIVER_TYPE_C
+ | MMC_CAP_DRIVER_TYPE_D)))
+ return 0;
+
+ if (card->host->caps & MMC_CAP_DRIVER_TYPE_A) {
+ host_drv_type = MMC_SET_DRIVER_TYPE_A;
+ if (card->sw_caps.sd3_drv_type & SD_DRIVER_TYPE_A)
+ card_drv_type = MMC_SET_DRIVER_TYPE_A;
+ else if (card->sw_caps.sd3_drv_type & SD_DRIVER_TYPE_B)
+ card_drv_type = MMC_SET_DRIVER_TYPE_B;
+ else if (card->sw_caps.sd3_drv_type & SD_DRIVER_TYPE_C)
+ card_drv_type = MMC_SET_DRIVER_TYPE_C;
+ } else if (card->host->caps & MMC_CAP_DRIVER_TYPE_C) {
+ host_drv_type = MMC_SET_DRIVER_TYPE_C;
+ if (card->sw_caps.sd3_drv_type & SD_DRIVER_TYPE_C)
+ card_drv_type = MMC_SET_DRIVER_TYPE_C;
+ } else if (!(card->host->caps & MMC_CAP_DRIVER_TYPE_D)) {
+ /*
+ * If we are here, that means only the default driver type
+ * B is supported by the host.
+ */
+ host_drv_type = MMC_SET_DRIVER_TYPE_B;
+ if (card->sw_caps.sd3_drv_type & SD_DRIVER_TYPE_B)
+ card_drv_type = MMC_SET_DRIVER_TYPE_B;
+ else if (card->sw_caps.sd3_drv_type & SD_DRIVER_TYPE_C)
+ card_drv_type = MMC_SET_DRIVER_TYPE_C;
+ }
+
+ err = mmc_sd_switch(card, 1, 2, card_drv_type, status);
+ if (err)
+ return err;
+
+ if ((status[15] & 0xF) != card_drv_type) {
+ printk(KERN_WARNING "%s: Problem setting driver strength!\n",
+ mmc_hostname(card->host));
+ return 0;
+ }
+
+ mmc_set_driver_type(card->host, host_drv_type);
+
+ return 0;
+}
+
+static int sd_set_bus_speed_mode(struct mmc_card *card, u8 *status)
+{
+ unsigned int bus_speed = 0, timing = 0;
+ int err;
+
+ /*
+ * If the host doesn't support any of the UHS-I modes, fallback on
+ * default speed.
+ */
+ if (!(card->host->caps & (MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 |
+ MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104 | MMC_CAP_UHS_DDR50)))
+ return 0;
+
+ if ((card->host->caps & MMC_CAP_UHS_SDR104) &&
+ (card->sw_caps.sd3_bus_mode & SD_MODE_UHS_SDR104)) {
+ bus_speed = UHS_SDR104_BUS_SPEED;
+ timing = MMC_TIMING_UHS_SDR104;
+ card->sw_caps.uhs_max_dtr = UHS_SDR104_MAX_DTR;
+ } else if ((card->host->caps & MMC_CAP_UHS_DDR50) &&
+ (card->sw_caps.sd3_bus_mode & SD_MODE_UHS_DDR50)) {
+ bus_speed = UHS_DDR50_BUS_SPEED;
+ timing = MMC_TIMING_UHS_DDR50;
+ card->sw_caps.uhs_max_dtr = UHS_DDR50_MAX_DTR;
+ } else if ((card->host->caps & (MMC_CAP_UHS_SDR104 |
+ MMC_CAP_UHS_SDR50)) && (card->sw_caps.sd3_bus_mode &
+ SD_MODE_UHS_SDR50)) {
+ bus_speed = UHS_SDR50_BUS_SPEED;
+ timing = MMC_TIMING_UHS_SDR50;
+ card->sw_caps.uhs_max_dtr = UHS_SDR50_MAX_DTR;
+ } else if ((card->host->caps & (MMC_CAP_UHS_SDR104 |
+ MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR25)) &&
+ (card->sw_caps.sd3_bus_mode & SD_MODE_UHS_SDR25)) {
+ bus_speed = UHS_SDR25_BUS_SPEED;
+ timing = MMC_TIMING_UHS_SDR25;
+ card->sw_caps.uhs_max_dtr = UHS_SDR25_MAX_DTR;
+ } else if ((card->host->caps & (MMC_CAP_UHS_SDR104 |
+ MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR25 |
+ MMC_CAP_UHS_SDR12)) && (card->sw_caps.sd3_bus_mode &
+ SD_MODE_UHS_SDR12)) {
+ bus_speed = UHS_SDR12_BUS_SPEED;
+ timing = MMC_TIMING_UHS_SDR12;
+ card->sw_caps.uhs_max_dtr = UHS_SDR12_MAX_DTR;
+ }
+
+ card->sd_bus_speed = bus_speed;
+ err = mmc_sd_switch(card, 1, 0, bus_speed, status);
+ if (err)
+ return err;
+
+ if ((status[16] & 0xF) != bus_speed)
+ printk(KERN_WARNING "%s: Problem setting bus speed mode!\n",
+ mmc_hostname(card->host));
+ else {
+ mmc_set_timing(card->host, timing);
+ mmc_set_clock(card->host, card->sw_caps.uhs_max_dtr);
+ }
+
+ return 0;
+}
+
+static int sd_set_current_limit(struct mmc_card *card, u8 *status)
+{
+ int current_limit = 0;
+ int err;
+
+ /*
+ * Current limit switch is only defined for SDR50, SDR104, and DDR50
+ * bus speed modes. For other bus speed modes, we set the default
+ * current limit of 200mA.
+ */
+ if ((card->sd_bus_speed == UHS_SDR50_BUS_SPEED) ||
+ (card->sd_bus_speed == UHS_SDR104_BUS_SPEED) ||
+ (card->sd_bus_speed == UHS_DDR50_BUS_SPEED)) {
+ if (card->host->caps & MMC_CAP_MAX_CURRENT_800) {
+ if (card->sw_caps.sd3_curr_limit & SD_MAX_CURRENT_800)
+ current_limit = SD_SET_CURRENT_LIMIT_800;
+ else if (card->sw_caps.sd3_curr_limit &
+ SD_MAX_CURRENT_600)
+ current_limit = SD_SET_CURRENT_LIMIT_600;
+ else if (card->sw_caps.sd3_curr_limit &
+ SD_MAX_CURRENT_400)
+ current_limit = SD_SET_CURRENT_LIMIT_400;
+ else if (card->sw_caps.sd3_curr_limit &
+ SD_MAX_CURRENT_200)
+ current_limit = SD_SET_CURRENT_LIMIT_200;
+ } else if (card->host->caps & MMC_CAP_MAX_CURRENT_600) {
+ if (card->sw_caps.sd3_curr_limit & SD_MAX_CURRENT_600)
+ current_limit = SD_SET_CURRENT_LIMIT_600;
+ else if (card->sw_caps.sd3_curr_limit &
+ SD_MAX_CURRENT_400)
+ current_limit = SD_SET_CURRENT_LIMIT_400;
+ else if (card->sw_caps.sd3_curr_limit &
+ SD_MAX_CURRENT_200)
+ current_limit = SD_SET_CURRENT_LIMIT_200;
+ } else if (card->host->caps & MMC_CAP_MAX_CURRENT_400) {
+ if (card->sw_caps.sd3_curr_limit & SD_MAX_CURRENT_400)
+ current_limit = SD_SET_CURRENT_LIMIT_400;
+ else if (card->sw_caps.sd3_curr_limit &
+ SD_MAX_CURRENT_200)
+ current_limit = SD_SET_CURRENT_LIMIT_200;
+ } else if (card->host->caps & MMC_CAP_MAX_CURRENT_200) {
+ if (card->sw_caps.sd3_curr_limit & SD_MAX_CURRENT_200)
+ current_limit = SD_SET_CURRENT_LIMIT_200;
+ }
+ } else
+ current_limit = SD_SET_CURRENT_LIMIT_200;
+
+ err = mmc_sd_switch(card, 1, 3, current_limit, status);
+ if (err)
+ return err;
+
+ if (((status[15] >> 4) & 0x0F) != current_limit)
+ printk(KERN_WARNING "%s: Problem setting current limit!\n",
+ mmc_hostname(card->host));
+
+ return 0;
+}
+
+/*
+ * UHS-I specific initialization procedure
+ */
+static int mmc_sd_init_uhs_card(struct mmc_card *card)
+{
+ int err;
+ u8 *status;
+
+ if (!card->scr.sda_spec3)
+ return 0;
+
+ if (!(card->csd.cmdclass & CCC_SWITCH))
+ return 0;
+
+ status = kmalloc(64, GFP_KERNEL);
+ if (!status) {
+ printk(KERN_ERR "%s: could not allocate a buffer for "
+ "switch capabilities.\n", mmc_hostname(card->host));
+ return -ENOMEM;
+ }
+
+ /* Set 4-bit bus width */
+ if ((card->host->caps & MMC_CAP_4_BIT_DATA) &&
+ (card->scr.bus_widths & SD_SCR_BUS_WIDTH_4)) {
+ err = mmc_app_set_bus_width(card, MMC_BUS_WIDTH_4);
+ if (err)
+ goto out;
+
+ mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4);
+ }
+
+ /* Set the driver strength for the card */
+ err = sd_select_driver_type(card, status);
+ if (err)
+ goto out;
+
+ /* Set current limit for the card */
+ err = sd_set_current_limit(card, status);
+ if (err)
+ goto out;
+
+ /* Set bus speed mode of the card */
+ err = sd_set_bus_speed_mode(card, status);
+ if (err)
+ goto out;
+
+ /* SPI mode doesn't define CMD19 */
+ if (!mmc_host_is_spi(card->host) && card->host->ops->execute_tuning)
+ err = card->host->ops->execute_tuning(card->host);
+
+out:
+ kfree(status);
+
+ return err;
+}
+
+MMC_DEV_ATTR(cid, "%08x%08x%08x%08x\n", card->raw_cid[0], card->raw_cid[1],
+ card->raw_cid[2], card->raw_cid[3]);
+MMC_DEV_ATTR(csd, "%08x%08x%08x%08x\n", card->raw_csd[0], card->raw_csd[1],
+ card->raw_csd[2], card->raw_csd[3]);
+MMC_DEV_ATTR(scr, "%08x%08x\n", card->raw_scr[0], card->raw_scr[1]);
+MMC_DEV_ATTR(date, "%02d/%04d\n", card->cid.month, card->cid.year);
+MMC_DEV_ATTR(erase_size, "%u\n", card->erase_size << 9);
+MMC_DEV_ATTR(preferred_erase_size, "%u\n", card->pref_erase << 9);
+MMC_DEV_ATTR(fwrev, "0x%x\n", card->cid.fwrev);
+MMC_DEV_ATTR(hwrev, "0x%x\n", card->cid.hwrev);
+MMC_DEV_ATTR(manfid, "0x%06x\n", card->cid.manfid);
+MMC_DEV_ATTR(name, "%s\n", card->cid.prod_name);
+MMC_DEV_ATTR(oemid, "0x%04x\n", card->cid.oemid);
+MMC_DEV_ATTR(serial, "0x%08x\n", card->cid.serial);
+
+
+static struct attribute *sd_std_attrs[] = {
+ &dev_attr_cid.attr,
+ &dev_attr_csd.attr,
+ &dev_attr_scr.attr,
+ &dev_attr_date.attr,
+ &dev_attr_erase_size.attr,
+ &dev_attr_preferred_erase_size.attr,
+ &dev_attr_fwrev.attr,
+ &dev_attr_hwrev.attr,
+ &dev_attr_manfid.attr,
+ &dev_attr_name.attr,
+ &dev_attr_oemid.attr,
+ &dev_attr_serial.attr,
+ NULL,
+};
+
+static struct attribute_group sd_std_attr_group = {
+ .attrs = sd_std_attrs,
+};
+
+static const struct attribute_group *sd_attr_groups[] = {
+ &sd_std_attr_group,
+ NULL,
+};
+
+struct device_type sd_type = {
+ .groups = sd_attr_groups,
+};
+
+/*
+ * Fetch CID from card.
+ */
+int mmc_sd_get_cid(struct mmc_host *host, u32 ocr, u32 *cid, u32 *rocr)
+{
+ int err;
+
+ /*
+ * Since we're changing the OCR value, we seem to
+ * need to tell some cards to go back to the idle
+ * state. We wait 1ms to give cards time to
+ * respond.
+ */
+ mmc_go_idle(host);
+
+ /*
+ * If SD_SEND_IF_COND indicates an SD 2.0
+ * compliant card and we should set bit 30
+ * of the ocr to indicate that we can handle
+ * block-addressed SDHC cards.
+ */
+ err = mmc_send_if_cond(host, ocr);
+ if (!err)
+ ocr |= SD_OCR_CCS;
+
+ /*
+ * If the host supports one of UHS-I modes, request the card
+ * to switch to 1.8V signaling level.
+ */
+ if ((host->caps & (MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 |
+ MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104 | MMC_CAP_UHS_DDR50))
+ && (host->ocr_avail_sd & MMC_VDD_165_195))
+ ocr |= SD_OCR_S18R;
+
+ /* If the host can supply more than 150mA, XPC should be set to 1. */
+ if (host->caps & (MMC_CAP_SET_XPC_330 | MMC_CAP_SET_XPC_300 |
+ MMC_CAP_SET_XPC_180))
+ ocr |= SD_OCR_XPC;
+
+try_again:
+ err = mmc_send_app_op_cond(host, ocr, rocr);
+ if (err)
+ return err;
+
+ /*
+ * In case CCS and S18A in the response is set, start Signal Voltage
+ * Switch procedure. SPI mode doesn't support CMD11.
+ */
+ if (!mmc_host_is_spi(host) && rocr &&
+ ((*rocr & 0x41000000) == 0x41000000)) {
+ err = mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_180, true);
+ if (err) {
+ ocr &= ~SD_OCR_S18R;
+ goto try_again;
+ }
+ }
+
+ if (mmc_host_is_spi(host))
+ err = mmc_send_cid(host, cid);
+ else
+ err = mmc_all_send_cid(host, cid);
+
+ return err;
+}
+
+int mmc_sd_get_csd(struct mmc_host *host, struct mmc_card *card)
+{
+ int err;
+
+ /*
+ * Fetch CSD from card.
+ */
+ err = mmc_send_csd(card, card->raw_csd);
+ if (err)
+ return err;
+
+ err = mmc_decode_csd(card);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+int mmc_sd_setup_card(struct mmc_host *host, struct mmc_card *card,
+ bool reinit)
+{
+ int err;
+#ifdef CONFIG_MMC_PARANOID_SD_INIT
+ int retries;
+#endif
+
+ if (!reinit) {
+ /*
+ * Fetch SCR from card.
+ */
+ err = mmc_app_send_scr(card, card->raw_scr);
+ if (err)
+ return err;
+
+ err = mmc_decode_scr(card);
+ if (err)
+ return err;
+
+ /*
+ * Fetch and process SD Status register.
+ */
+ err = mmc_read_ssr(card);
+ if (err)
+ return err;
+
+ /* Erase init depends on CSD and SSR */
+ mmc_init_erase(card);
+
+ /*
+ * Fetch switch information from card.
+ */
+#ifdef CONFIG_MMC_PARANOID_SD_INIT
+ for (retries = 1; retries <= 3; retries++) {
+ err = mmc_read_switch(card);
+ if (!err) {
+ if (retries > 1) {
+ printk(KERN_WARNING
+ "%s: recovered\n",
+ mmc_hostname(host));
+ }
+ break;
+ } else {
+ printk(KERN_WARNING
+ "%s: read switch failed (attempt %d)\n",
+ mmc_hostname(host), retries);
+ }
+ }
+#else
+ err = mmc_read_switch(card);
+#endif
+
+ if (err)
+ return err;
+ }
+
+ /*
+ * For SPI, enable CRC as appropriate.
+ * This CRC enable is located AFTER the reading of the
+ * card registers because some SDHC cards are not able
+ * to provide valid CRCs for non-512-byte blocks.
+ */
+ if (mmc_host_is_spi(host)) {
+ err = mmc_spi_set_crc(host, use_spi_crc);
+ if (err)
+ return err;
+ }
+
+ /*
+ * Check if read-only switch is active.
+ */
+ if (!reinit) {
+ int ro = -1;
+
+ if (host->ops->get_ro)
+ ro = host->ops->get_ro(host);
+
+ if (ro < 0) {
+ printk(KERN_WARNING "%s: host does not "
+ "support reading read-only "
+ "switch. assuming write-enable.\n",
+ mmc_hostname(host));
+ } else if (ro > 0) {
+ mmc_card_set_readonly(card);
+ }
+ }
+
+ return 0;
+}
+
+unsigned mmc_sd_get_max_clock(struct mmc_card *card)
+{
+ unsigned max_dtr = (unsigned int)-1;
+
+ if (mmc_card_highspeed(card)) {
+ if (max_dtr > card->sw_caps.hs_max_dtr)
+ max_dtr = card->sw_caps.hs_max_dtr;
+ } else if (max_dtr > card->csd.max_dtr) {
+ max_dtr = card->csd.max_dtr;
+ }
+
+ return max_dtr;
+}
+
+void mmc_sd_go_highspeed(struct mmc_card *card)
+{
+ mmc_card_set_highspeed(card);
+ mmc_set_timing(card->host, MMC_TIMING_SD_HS);
+}
+
+/*
+ * Handle the detection and initialisation of a 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,
+ struct mmc_card *oldcard)
+{
+ struct mmc_card *card;
+ int err;
+ u32 cid[4];
+ u32 rocr = 0;
+
+ BUG_ON(!host);
+ WARN_ON(!host->claimed);
+
+ err = mmc_sd_get_cid(host, ocr, cid, &rocr);
+ if (err)
+ return err;
+
+ if (oldcard) {
+ if (memcmp(cid, oldcard->raw_cid, sizeof(cid)) != 0)
+ return -ENOENT;
+
+ card = oldcard;
+ } else {
+ /*
+ * Allocate card structure.
+ */
+ card = mmc_alloc_card(host, &sd_type);
+ if (IS_ERR(card))
+ return PTR_ERR(card);
+
+ card->type = MMC_TYPE_SD;
+ memcpy(card->raw_cid, cid, sizeof(card->raw_cid));
+ }
+
+ /*
+ * For native busses: get card RCA and quit open drain mode.
+ */
+ if (!mmc_host_is_spi(host)) {
+ err = mmc_send_relative_addr(host, &card->rca);
+ if (err)
+ return err;
+
+ mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
+ }
+
+ if (!oldcard) {
+ err = mmc_sd_get_csd(host, card);
+ if (err)
+ return err;
+
+ mmc_decode_cid(card);
+ }
+
+ /*
+ * Select card, as all following commands rely on that.
+ */
+ if (!mmc_host_is_spi(host)) {
+ err = mmc_select_card(card);
+ if (err)
+ return err;
+ }
+
+ err = mmc_sd_setup_card(host, card, oldcard != NULL);
+ if (err)
+ goto free_card;
+
+ /* Initialization sequence for UHS-I cards */
+ if (rocr & SD_ROCR_S18A) {
+ err = mmc_sd_init_uhs_card(card);
+ if (err)
+ goto free_card;
+
+ /* Card is an ultra-high-speed card */
+ mmc_sd_card_set_uhs(card);
+
+ /*
+ * Since initialization is now complete, enable preset
+ * value registers for UHS-I cards.
+ */
+ if (host->ops->enable_preset_value)
+ host->ops->enable_preset_value(host, true);
+ } else {
+ /*
+ * Attempt to change to high-speed (if supported)
+ */
+ err = mmc_sd_switch_hs(card);
+ if (err > 0)
+ mmc_sd_go_highspeed(card);
+ else if (err)
+ goto free_card;
+
+ /*
+ * Set bus speed.
+ */
+ mmc_set_clock(host, mmc_sd_get_max_clock(card));
+
+ /*
+ * Switch to wider bus (if supported).
+ */
+ if ((host->caps & MMC_CAP_4_BIT_DATA) &&
+ (card->scr.bus_widths & SD_SCR_BUS_WIDTH_4)) {
+ err = mmc_app_set_bus_width(card, MMC_BUS_WIDTH_4);
+ if (err)
+ goto free_card;
+
+ mmc_set_bus_width(host, MMC_BUS_WIDTH_4);
+ }
+ }
+
+ host->card = card;
+ return 0;
+
+free_card:
+ if (!oldcard)
+ mmc_remove_card(card);
+
+ return err;
+}
+
+/*
+ * Host is being removed. Free up the current card.
+ */
+static void mmc_sd_remove(struct mmc_host *host)
+{
+ BUG_ON(!host);
+ BUG_ON(!host->card);
+
+ mmc_remove_card(host->card);
+ host->card = NULL;
+}
+
+/*
+ * Card detection callback from host.
+ */
+static void mmc_sd_detect(struct mmc_host *host)
+{
+ int err = 0;
+#ifdef CONFIG_MMC_PARANOID_SD_INIT
+ int retries = 5;
+#endif
+
+ BUG_ON(!host);
+ BUG_ON(!host->card);
+
+ mmc_claim_host(host);
+
+ /*
+ * Just check if our card has been removed.
+ */
+#ifdef CONFIG_MMC_PARANOID_SD_INIT
+ while(retries) {
+ err = mmc_send_status(host->card, NULL);
+ if (err) {
+ retries--;
+ udelay(5);
+ continue;
+ }
+ break;
+ }
+ if (!retries) {
+ printk(KERN_ERR "%s(%s): Unable to re-detect card (%d)\n",
+ __func__, mmc_hostname(host), err);
+ }
+#else
+ err = mmc_send_status(host->card, NULL);
+#endif
+ mmc_release_host(host);
+
+ if (err) {
+ mmc_sd_remove(host);
+
+ mmc_claim_host(host);
+ mmc_detach_bus(host);
+ mmc_power_off(host);
+ mmc_release_host(host);
+ }
+}
+
+/*
+ * Suspend callback from host.
+ */
+static int mmc_sd_suspend(struct mmc_host *host)
+{
+ BUG_ON(!host);
+ BUG_ON(!host->card);
+
+ mmc_claim_host(host);
+ if (!mmc_host_is_spi(host))
+ mmc_deselect_cards(host);
+ host->card->state &= ~MMC_STATE_HIGHSPEED;
+ mmc_release_host(host);
+
+ return 0;
+}
+
+/*
+ * Resume callback from host.
+ *
+ * This function tries to determine if the same card is still present
+ * and, if so, restore all state to it.
+ */
+static int mmc_sd_resume(struct mmc_host *host)
+{
+ int err;
+#ifdef CONFIG_MMC_PARANOID_SD_INIT
+ int retries;
+#endif
+
+ BUG_ON(!host);
+ BUG_ON(!host->card);
+
+ mmc_claim_host(host);
+#ifdef CONFIG_MMC_PARANOID_SD_INIT
+ retries = 5;
+ while (retries) {
+ err = mmc_sd_init_card(host, host->ocr, host->card);
+
+ if (err) {
+ printk(KERN_ERR "%s: Re-init card rc = %d (retries = %d)\n",
+ mmc_hostname(host), err, retries);
+ mdelay(5);
+ retries--;
+ continue;
+ }
+ break;
+ }
+#else
+ err = mmc_sd_init_card(host, host->ocr, host->card);
+#endif
+ mmc_release_host(host);
+
+ return err;
+}
+
+static int mmc_sd_power_restore(struct mmc_host *host)
+{
+ int ret;
+
+ host->card->state &= ~MMC_STATE_HIGHSPEED;
+ mmc_claim_host(host);
+ ret = mmc_sd_init_card(host, host->ocr, host->card);
+ mmc_release_host(host);
+
+ return ret;
+}
+
+static const struct mmc_bus_ops mmc_sd_ops = {
+ .remove = mmc_sd_remove,
+ .detect = mmc_sd_detect,
+ .suspend = NULL,
+ .resume = NULL,
+ .power_restore = mmc_sd_power_restore,
+};
+
+static const struct mmc_bus_ops mmc_sd_ops_unsafe = {
+ .remove = mmc_sd_remove,
+ .detect = mmc_sd_detect,
+ .suspend = mmc_sd_suspend,
+ .resume = mmc_sd_resume,
+ .power_restore = mmc_sd_power_restore,
+};
+
+static void mmc_sd_attach_bus_ops(struct mmc_host *host)
+{
+ const struct mmc_bus_ops *bus_ops;
+
+ if (!mmc_card_is_removable(host))
+ bus_ops = &mmc_sd_ops_unsafe;
+ else
+ bus_ops = &mmc_sd_ops;
+ mmc_attach_bus(host, bus_ops);
+}
+
+/*
+ * Starting point for SD card init.
+ */
+int mmc_attach_sd(struct mmc_host *host)
+{
+ int err;
+ u32 ocr;
+#ifdef CONFIG_MMC_PARANOID_SD_INIT
+ int retries;
+#endif
+
+ BUG_ON(!host);
+ WARN_ON(!host->claimed);
+
+ /* Make sure we are at 3.3V signalling voltage */
+ err = mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_330, false);
+ if (err)
+ return err;
+
+ /* Disable preset value enable if already set since last time */
+ if (host->ops->enable_preset_value)
+ host->ops->enable_preset_value(host, false);
+
+ err = mmc_send_app_op_cond(host, 0, &ocr);
+ if (err)
+ return err;
+
+ mmc_sd_attach_bus_ops(host);
+ if (host->ocr_avail_sd)
+ host->ocr_avail = host->ocr_avail_sd;
+
+ /*
+ * We need to get OCR a different way for SPI.
+ */
+ if (mmc_host_is_spi(host)) {
+ mmc_go_idle(host);
+
+ err = mmc_spi_read_ocr(host, 0, &ocr);
+ if (err)
+ goto err;
+ }
+
+ /*
+ * Sanity check the voltages that the card claims to
+ * support.
+ */
+ if (ocr & 0x7F) {
+ printk(KERN_WARNING "%s: card claims to support voltages "
+ "below the defined range. These will be ignored.\n",
+ mmc_hostname(host));
+ ocr &= ~0x7F;
+ }
+
+ if ((ocr & MMC_VDD_165_195) &&
+ !(host->ocr_avail_sd & MMC_VDD_165_195)) {
+ printk(KERN_WARNING "%s: SD card claims to support the "
+ "incompletely defined 'low voltage range'. This "
+ "will be ignored.\n", mmc_hostname(host));
+ ocr &= ~MMC_VDD_165_195;
+ }
+
+ host->ocr = mmc_select_voltage(host, ocr);
+
+ /*
+ * Can we support the voltage(s) of the card(s)?
+ */
+ if (!host->ocr) {
+ err = -EINVAL;
+ goto err;
+ }
+
+ /*
+ * Detect and init the card.
+ */
+#ifdef CONFIG_MMC_PARANOID_SD_INIT
+ retries = 5;
+ while (retries) {
+ err = mmc_sd_init_card(host, host->ocr, NULL);
+ if (err) {
+ retries--;
+ continue;
+ }
+ break;
+ }
+
+ if (!retries) {
+ printk(KERN_ERR "%s: mmc_sd_init_card() failure (err = %d)\n",
+ mmc_hostname(host), err);
+ goto err;
+ }
+#else
+ err = mmc_sd_init_card(host, host->ocr, NULL);
+ if (err)
+ goto err;
+#endif
+
+ mmc_release_host(host);
+ err = mmc_add_card(host->card);
+ mmc_claim_host(host);
+ if (err)
+ goto remove_card;
+
+ return 0;
+
+remove_card:
+ mmc_release_host(host);
+ mmc_remove_card(host->card);
+ host->card = NULL;
+ mmc_claim_host(host);
+err:
+ mmc_detach_bus(host);
+
+ printk(KERN_ERR "%s: error %d whilst initialising SD card\n",
+ mmc_hostname(host), err);
+
+ return err;
+}
+
diff --git a/drivers/mmc/core/sd.h b/drivers/mmc/core/sd.h
new file mode 100644
index 00000000..4b34b24f
--- /dev/null
+++ b/drivers/mmc/core/sd.h
@@ -0,0 +1,17 @@
+#ifndef _MMC_CORE_SD_H
+#define _MMC_CORE_SD_H
+
+#include <linux/mmc/card.h>
+
+extern struct device_type sd_type;
+
+int mmc_sd_get_cid(struct mmc_host *host, u32 ocr, u32 *cid, u32 *rocr);
+int mmc_sd_get_csd(struct mmc_host *host, struct mmc_card *card);
+void mmc_decode_cid(struct mmc_card *card);
+int mmc_sd_setup_card(struct mmc_host *host, struct mmc_card *card,
+ bool reinit);
+unsigned mmc_sd_get_max_clock(struct mmc_card *card);
+int mmc_sd_switch_hs(struct mmc_card *card);
+void mmc_sd_go_highspeed(struct mmc_card *card);
+
+#endif
diff --git a/drivers/mmc/core/sd_ops.c b/drivers/mmc/core/sd_ops.c
new file mode 100644
index 00000000..021fed15
--- /dev/null
+++ b/drivers/mmc/core/sd_ops.c
@@ -0,0 +1,391 @@
+/*
+ * linux/drivers/mmc/core/sd_ops.h
+ *
+ * Copyright 2006-2007 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
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ */
+
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/scatterlist.h>
+
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/sd.h>
+
+#include "core.h"
+#include "sd_ops.h"
+
+int mmc_app_cmd(struct mmc_host *host, struct mmc_card *card)
+{
+ int err;
+ struct mmc_command cmd = {0};
+
+ BUG_ON(!host);
+ BUG_ON(card && (card->host != host));
+
+ cmd.opcode = MMC_APP_CMD;
+
+ if (card) {
+ cmd.arg = card->rca << 16;
+ cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC;
+ } else {
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_BCR;
+ }
+
+ err = mmc_wait_for_cmd(host, &cmd, 0);
+ if (err)
+ return err;
+
+ /* Check that card supported application commands */
+ if (!mmc_host_is_spi(host) && !(cmd.resp[0] & R1_APP_CMD))
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mmc_app_cmd);
+
+/**
+ * mmc_wait_for_app_cmd - start an application command and wait for
+ completion
+ * @host: MMC host to start command
+ * @card: Card to send MMC_APP_CMD to
+ * @cmd: MMC command to start
+ * @retries: maximum number of retries
+ *
+ * Sends a MMC_APP_CMD, checks the card response, sends the command
+ * in the parameter and waits for it to complete. Return any error
+ * that occurred while the command was executing. Do not attempt to
+ * parse the response.
+ */
+int mmc_wait_for_app_cmd(struct mmc_host *host, struct mmc_card *card,
+ struct mmc_command *cmd, int retries)
+{
+ struct mmc_request mrq = {0};
+
+ int i, err;
+
+ BUG_ON(!cmd);
+ BUG_ON(retries < 0);
+
+ err = -EIO;
+
+ /*
+ * We have to resend MMC_APP_CMD for each attempt so
+ * we cannot use the retries field in mmc_command.
+ */
+ for (i = 0;i <= retries;i++) {
+ err = mmc_app_cmd(host, card);
+ if (err) {
+ /* no point in retrying; no APP commands allowed */
+ if (mmc_host_is_spi(host)) {
+ if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND)
+ break;
+ }
+ continue;
+ }
+
+ memset(&mrq, 0, sizeof(struct mmc_request));
+
+ memset(cmd->resp, 0, sizeof(cmd->resp));
+ cmd->retries = 0;
+
+ mrq.cmd = cmd;
+ cmd->data = NULL;
+
+ mmc_wait_for_req(host, &mrq);
+
+ err = cmd->error;
+ if (!cmd->error)
+ break;
+
+ /* no point in retrying illegal APP commands */
+ if (mmc_host_is_spi(host)) {
+ if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND)
+ break;
+ }
+ }
+
+ return err;
+}
+
+EXPORT_SYMBOL(mmc_wait_for_app_cmd);
+
+int mmc_app_set_bus_width(struct mmc_card *card, int width)
+{
+ int err;
+ struct mmc_command cmd = {0};
+
+ BUG_ON(!card);
+ BUG_ON(!card->host);
+
+ cmd.opcode = SD_APP_SET_BUS_WIDTH;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
+
+ switch (width) {
+ case MMC_BUS_WIDTH_1:
+ cmd.arg = SD_BUS_WIDTH_1;
+ break;
+ case MMC_BUS_WIDTH_4:
+ cmd.arg = SD_BUS_WIDTH_4;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ err = mmc_wait_for_app_cmd(card->host, card, &cmd, MMC_CMD_RETRIES);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+int mmc_send_app_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)
+{
+ struct mmc_command cmd = {0};
+ int i, err = 0;
+
+ BUG_ON(!host);
+
+ cmd.opcode = SD_APP_OP_COND;
+ if (mmc_host_is_spi(host))
+ cmd.arg = ocr & (1 << 30); /* SPI only defines one bit */
+ else
+ cmd.arg = ocr;
+ cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R3 | MMC_CMD_BCR;
+
+ for (i = 100; i; i--) {
+ err = mmc_wait_for_app_cmd(host, NULL, &cmd, MMC_CMD_RETRIES);
+ if (err)
+ break;
+
+ /* if we're just probing, do a single pass */
+ if (ocr == 0)
+ break;
+
+ /* otherwise wait until reset completes */
+ if (mmc_host_is_spi(host)) {
+ if (!(cmd.resp[0] & R1_SPI_IDLE))
+ break;
+ } else {
+ if (cmd.resp[0] & MMC_CARD_BUSY)
+ break;
+ }
+
+ err = -ETIMEDOUT;
+
+ mmc_delay(10);
+ }
+
+ if (rocr && !mmc_host_is_spi(host))
+ *rocr = cmd.resp[0];
+
+ return err;
+}
+
+int mmc_send_if_cond(struct mmc_host *host, u32 ocr)
+{
+ struct mmc_command cmd = {0};
+ int err;
+ static const u8 test_pattern = 0xAA;
+ u8 result_pattern;
+
+ /*
+ * To support SD 2.0 cards, we must always invoke SD_SEND_IF_COND
+ * before SD_APP_OP_COND. This command will harmlessly fail for
+ * SD 1.0 cards.
+ */
+ cmd.opcode = SD_SEND_IF_COND;
+ cmd.arg = ((ocr & 0xFF8000) != 0) << 8 | test_pattern;
+ cmd.flags = MMC_RSP_SPI_R7 | MMC_RSP_R7 | MMC_CMD_BCR;
+
+ err = mmc_wait_for_cmd(host, &cmd, 0);
+ if (err)
+ return err;
+
+ if (mmc_host_is_spi(host))
+ result_pattern = cmd.resp[1] & 0xFF;
+ else
+ result_pattern = cmd.resp[0] & 0xFF;
+
+ if (result_pattern != test_pattern)
+ return -EIO;
+
+ return 0;
+}
+
+int mmc_send_relative_addr(struct mmc_host *host, unsigned int *rca)
+{
+ int err;
+ struct mmc_command cmd = {0};
+
+ BUG_ON(!host);
+ BUG_ON(!rca);
+
+ cmd.opcode = SD_SEND_RELATIVE_ADDR;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_R6 | MMC_CMD_BCR;
+
+ err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
+ if (err)
+ return err;
+
+ *rca = cmd.resp[0] >> 16;
+
+ return 0;
+}
+
+int mmc_app_send_scr(struct mmc_card *card, u32 *scr)
+{
+ int err;
+ struct mmc_request mrq = {0};
+ struct mmc_command cmd = {0};
+ struct mmc_data data = {0};
+ struct scatterlist sg;
+ void *data_buf;
+
+ BUG_ON(!card);
+ BUG_ON(!card->host);
+ BUG_ON(!scr);
+
+ /* NOTE: caller guarantees scr is heap-allocated */
+
+ err = mmc_app_cmd(card->host, card);
+ if (err)
+ return err;
+
+ /* dma onto stack is unsafe/nonportable, but callers to this
+ * routine normally provide temporary on-stack buffers ...
+ */
+ data_buf = kmalloc(sizeof(card->raw_scr), GFP_KERNEL);
+ if (data_buf == NULL)
+ return -ENOMEM;
+
+ mrq.cmd = &cmd;
+ mrq.data = &data;
+
+ cmd.opcode = SD_APP_SEND_SCR;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
+
+ data.blksz = 8;
+ data.blocks = 1;
+ data.flags = MMC_DATA_READ;
+ data.sg = &sg;
+ data.sg_len = 1;
+
+ sg_init_one(&sg, data_buf, 8);
+
+ mmc_set_data_timeout(&data, card);
+
+ mmc_wait_for_req(card->host, &mrq);
+
+ memcpy(scr, data_buf, sizeof(card->raw_scr));
+ kfree(data_buf);
+
+ if (cmd.error)
+ return cmd.error;
+ if (data.error)
+ return data.error;
+
+ scr[0] = be32_to_cpu(scr[0]);
+ scr[1] = be32_to_cpu(scr[1]);
+
+ return 0;
+}
+
+int mmc_sd_switch(struct mmc_card *card, int mode, int group,
+ u8 value, u8 *resp)
+{
+ struct mmc_request mrq = {0};
+ struct mmc_command cmd = {0};
+ struct mmc_data data = {0};
+ struct scatterlist sg;
+
+ BUG_ON(!card);
+ BUG_ON(!card->host);
+
+ /* NOTE: caller guarantees resp is heap-allocated */
+
+ mode = !!mode;
+ value &= 0xF;
+
+ mrq.cmd = &cmd;
+ mrq.data = &data;
+
+ cmd.opcode = SD_SWITCH;
+ cmd.arg = mode << 31 | 0x00FFFFFF;
+ cmd.arg &= ~(0xF << (group * 4));
+ cmd.arg |= value << (group * 4);
+ cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
+
+ data.blksz = 64;
+ data.blocks = 1;
+ data.flags = MMC_DATA_READ;
+ data.sg = &sg;
+ data.sg_len = 1;
+
+ sg_init_one(&sg, resp, 64);
+
+ mmc_set_data_timeout(&data, card);
+
+ mmc_wait_for_req(card->host, &mrq);
+
+ if (cmd.error)
+ return cmd.error;
+ if (data.error)
+ return data.error;
+
+ return 0;
+}
+
+int mmc_app_sd_status(struct mmc_card *card, void *ssr)
+{
+ int err;
+ struct mmc_request mrq = {0};
+ struct mmc_command cmd = {0};
+ struct mmc_data data = {0};
+ struct scatterlist sg;
+
+ BUG_ON(!card);
+ BUG_ON(!card->host);
+ BUG_ON(!ssr);
+
+ /* NOTE: caller guarantees ssr is heap-allocated */
+
+ err = mmc_app_cmd(card->host, card);
+ if (err)
+ return err;
+
+ mrq.cmd = &cmd;
+ mrq.data = &data;
+
+ cmd.opcode = SD_APP_SD_STATUS;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_ADTC;
+
+ data.blksz = 64;
+ data.blocks = 1;
+ data.flags = MMC_DATA_READ;
+ data.sg = &sg;
+ data.sg_len = 1;
+
+ sg_init_one(&sg, ssr, 64);
+
+ mmc_set_data_timeout(&data, card);
+
+ mmc_wait_for_req(card->host, &mrq);
+
+ if (cmd.error)
+ return cmd.error;
+ if (data.error)
+ return data.error;
+
+ return 0;
+}
diff --git a/drivers/mmc/core/sd_ops.h b/drivers/mmc/core/sd_ops.h
new file mode 100644
index 00000000..ffc2305d
--- /dev/null
+++ b/drivers/mmc/core/sd_ops.h
@@ -0,0 +1,25 @@
+/*
+ * linux/drivers/mmc/core/sd_ops.h
+ *
+ * Copyright 2006-2007 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
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ */
+
+#ifndef _MMC_SD_OPS_H
+#define _MMC_SD_OPS_H
+
+int mmc_app_set_bus_width(struct mmc_card *card, int width);
+int mmc_send_app_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr);
+int mmc_send_if_cond(struct mmc_host *host, u32 ocr);
+int mmc_send_relative_addr(struct mmc_host *host, unsigned int *rca);
+int mmc_app_send_scr(struct mmc_card *card, u32 *scr);
+int mmc_sd_switch(struct mmc_card *card, int mode, int group,
+ u8 value, u8 *resp);
+int mmc_app_sd_status(struct mmc_card *card, void *ssr);
+
+#endif
+
diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c
new file mode 100644
index 00000000..3a490f7b
--- /dev/null
+++ b/drivers/mmc/core/sdio.c
@@ -0,0 +1,1000 @@
+/*
+ * linux/drivers/mmc/sdio.c
+ *
+ * Copyright 2006-2007 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
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ */
+
+#include <linux/err.h>
+#include <linux/pm_runtime.h>
+
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/sdio_ids.h>
+
+#include "core.h"
+#include "bus.h"
+#include "sd.h"
+#include "sdio_bus.h"
+#include "mmc_ops.h"
+#include "sd_ops.h"
+#include "sdio_ops.h"
+#include "sdio_cis.h"
+
+#ifdef CONFIG_MMC_EMBEDDED_SDIO
+#include <linux/mmc/sdio_ids.h>
+#endif
+
+static int sdio_read_fbr(struct sdio_func *func)
+{
+ int ret;
+ unsigned char data;
+
+ if (mmc_card_nonstd_func_interface(func->card)) {
+ func->class = SDIO_CLASS_NONE;
+ return 0;
+ }
+
+ ret = mmc_io_rw_direct(func->card, 0, 0,
+ SDIO_FBR_BASE(func->num) + SDIO_FBR_STD_IF, 0, &data);
+ if (ret)
+ goto out;
+
+ data &= 0x0f;
+
+ if (data == 0x0f) {
+ ret = mmc_io_rw_direct(func->card, 0, 0,
+ SDIO_FBR_BASE(func->num) + SDIO_FBR_STD_IF_EXT, 0, &data);
+ if (ret)
+ goto out;
+ }
+
+ func->class = data;
+
+out:
+ return ret;
+}
+
+static int sdio_init_func(struct mmc_card *card, unsigned int fn)
+{
+ int ret;
+ struct sdio_func *func;
+
+ BUG_ON(fn > SDIO_MAX_FUNCS);
+
+ func = sdio_alloc_func(card);
+ if (IS_ERR(func))
+ return PTR_ERR(func);
+
+ func->num = fn;
+
+ if (!(card->quirks & MMC_QUIRK_NONSTD_SDIO)) {
+ ret = sdio_read_fbr(func);
+ if (ret)
+ goto fail;
+
+ ret = sdio_read_func_cis(func);
+ if (ret)
+ goto fail;
+ } else {
+ func->vendor = func->card->cis.vendor;
+ func->device = func->card->cis.device;
+ func->max_blksize = func->card->cis.blksize;
+ }
+
+ card->sdio_func[fn - 1] = func;
+
+ return 0;
+
+fail:
+ /*
+ * It is okay to remove the function here even though we hold
+ * the host lock as we haven't registered the device yet.
+ */
+ sdio_remove_func(func);
+ return ret;
+}
+
+static int sdio_read_cccr(struct mmc_card *card)
+{
+ int ret;
+ int cccr_vsn;
+ unsigned char data;
+
+ memset(&card->cccr, 0, sizeof(struct sdio_cccr));
+
+ ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_CCCR, 0, &data);
+ if (ret)
+ goto out;
+
+ cccr_vsn = data & 0x0f;
+
+ if (cccr_vsn > SDIO_CCCR_REV_1_20) {
+ printk(KERN_ERR "%s: unrecognised CCCR structure version %d\n",
+ mmc_hostname(card->host), cccr_vsn);
+ return -EINVAL;
+ }
+
+ card->cccr.sdio_vsn = (data & 0xf0) >> 4;
+
+ ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_CAPS, 0, &data);
+ if (ret)
+ goto out;
+
+ if (data & SDIO_CCCR_CAP_SMB)
+ card->cccr.multi_block = 1;
+ if (data & SDIO_CCCR_CAP_LSC)
+ card->cccr.low_speed = 1;
+ if (data & SDIO_CCCR_CAP_4BLS)
+ card->cccr.wide_bus = 1;
+
+ if (cccr_vsn >= SDIO_CCCR_REV_1_10) {
+ ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_POWER, 0, &data);
+ if (ret)
+ goto out;
+
+ if (data & SDIO_POWER_SMPC)
+ card->cccr.high_power = 1;
+ }
+
+ if (cccr_vsn >= SDIO_CCCR_REV_1_20) {
+ ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_SPEED, 0, &data);
+ if (ret)
+ goto out;
+
+ if (data & SDIO_SPEED_SHS)
+ card->cccr.high_speed = 1;
+ }
+
+out:
+ return ret;
+}
+
+static int sdio_enable_wide(struct mmc_card *card)
+{
+ int ret;
+ u8 ctrl;
+
+ if (!(card->host->caps & MMC_CAP_4_BIT_DATA))
+ return 0;
+
+ if (card->cccr.low_speed && !card->cccr.wide_bus)
+ return 0;
+
+ ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_IF, 0, &ctrl);
+ if (ret)
+ return ret;
+
+ ctrl |= SDIO_BUS_WIDTH_4BIT;
+
+ ret = mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_IF, ctrl, NULL);
+ if (ret)
+ return ret;
+
+ return 1;
+}
+
+/*
+ * If desired, disconnect the pull-up resistor on CD/DAT[3] (pin 1)
+ * of the card. This may be required on certain setups of boards,
+ * controllers and embedded sdio device which do not need the card's
+ * pull-up. As a result, card detection is disabled and power is saved.
+ */
+static int sdio_disable_cd(struct mmc_card *card)
+{
+ int ret;
+ u8 ctrl;
+
+ if (!mmc_card_disable_cd(card))
+ return 0;
+
+ ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_IF, 0, &ctrl);
+ if (ret)
+ return ret;
+
+ ctrl |= SDIO_BUS_CD_DISABLE;
+
+ return mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_IF, ctrl, NULL);
+}
+
+/*
+ * Devices that remain active during a system suspend are
+ * put back into 1-bit mode.
+ */
+static int sdio_disable_wide(struct mmc_card *card)
+{
+ int ret;
+ u8 ctrl;
+
+ if (!(card->host->caps & MMC_CAP_4_BIT_DATA))
+ return 0;
+
+ if (card->cccr.low_speed && !card->cccr.wide_bus)
+ return 0;
+
+ ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_IF, 0, &ctrl);
+ if (ret)
+ return ret;
+
+ if (!(ctrl & SDIO_BUS_WIDTH_4BIT))
+ return 0;
+
+ ctrl &= ~SDIO_BUS_WIDTH_4BIT;
+ ctrl |= SDIO_BUS_ASYNC_INT;
+
+ ret = mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_IF, ctrl, NULL);
+ if (ret)
+ return ret;
+
+ mmc_set_bus_width(card->host, MMC_BUS_WIDTH_1);
+
+ return 0;
+}
+
+
+static int sdio_enable_4bit_bus(struct mmc_card *card)
+{
+ int err;
+
+ if (card->type == MMC_TYPE_SDIO)
+ return sdio_enable_wide(card);
+
+ if ((card->host->caps & MMC_CAP_4_BIT_DATA) &&
+ (card->scr.bus_widths & SD_SCR_BUS_WIDTH_4)) {
+ err = mmc_app_set_bus_width(card, MMC_BUS_WIDTH_4);
+ if (err)
+ return err;
+ } else
+ return 0;
+
+ err = sdio_enable_wide(card);
+ if (err <= 0)
+ mmc_app_set_bus_width(card, MMC_BUS_WIDTH_1);
+
+ return err;
+}
+
+
+/*
+ * Test if the card supports high-speed mode and, if so, switch to it.
+ */
+static int mmc_sdio_switch_hs(struct mmc_card *card, int enable)
+{
+ int ret;
+ u8 speed;
+
+ if (!(card->host->caps & MMC_CAP_SD_HIGHSPEED))
+ return 0;
+
+ if (!card->cccr.high_speed)
+ return 0;
+
+ ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_SPEED, 0, &speed);
+ if (ret)
+ return ret;
+
+ if (enable)
+ speed |= SDIO_SPEED_EHS;
+ else
+ speed &= ~SDIO_SPEED_EHS;
+
+ ret = mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_SPEED, speed, NULL);
+ if (ret)
+ return ret;
+
+ return 1;
+}
+
+/*
+ * Enable SDIO/combo card's high-speed mode. Return 0/1 if [not]supported.
+ */
+static int sdio_enable_hs(struct mmc_card *card)
+{
+ int ret;
+
+ ret = mmc_sdio_switch_hs(card, true);
+ if (ret <= 0 || card->type == MMC_TYPE_SDIO)
+ return ret;
+
+ ret = mmc_sd_switch_hs(card);
+ if (ret <= 0)
+ mmc_sdio_switch_hs(card, false);
+
+ return ret;
+}
+
+static unsigned mmc_sdio_get_max_clock(struct mmc_card *card)
+{
+ unsigned max_dtr;
+
+ if (mmc_card_highspeed(card)) {
+ /*
+ * The SDIO specification doesn't mention how
+ * the CIS transfer speed register relates to
+ * high-speed, but it seems that 25 MHz is
+ * mandatory.
+ */
+ max_dtr = 25000000;
+ } else {
+ max_dtr = card->cis.max_dtr;
+ }
+
+ if (card->type == MMC_TYPE_SD_COMBO)
+ max_dtr = min(max_dtr, mmc_sd_get_max_clock(card));
+
+ return max_dtr;
+}
+
+/*
+ * Handle the detection and initialisation of a card.
+ *
+ * In the case of a resume, "oldcard" will contain the card
+ * we're trying to reinitialise.
+ */
+static int mmc_sdio_init_card(struct mmc_host *host, u32 ocr,
+ struct mmc_card *oldcard, int powered_resume)
+{
+ struct mmc_card *card;
+ int err;
+
+ BUG_ON(!host);
+ WARN_ON(!host->claimed);
+
+ /*
+ * Inform the card of the voltage
+ */
+ if (!powered_resume) {
+ err = mmc_send_io_op_cond(host, host->ocr, &ocr);
+ if (err)
+ goto err;
+ }
+
+ /*
+ * For SPI, enable CRC as appropriate.
+ */
+ if (mmc_host_is_spi(host)) {
+ err = mmc_spi_set_crc(host, use_spi_crc);
+ if (err)
+ goto err;
+ }
+
+ /*
+ * Allocate card structure.
+ */
+ card = mmc_alloc_card(host, NULL);
+ if (IS_ERR(card)) {
+ err = PTR_ERR(card);
+ goto err;
+ }
+
+ if ((ocr & R4_MEMORY_PRESENT) &&
+ mmc_sd_get_cid(host, host->ocr & ocr, card->raw_cid, NULL) == 0) {
+ card->type = MMC_TYPE_SD_COMBO;
+
+ if (oldcard && (oldcard->type != MMC_TYPE_SD_COMBO ||
+ memcmp(card->raw_cid, oldcard->raw_cid, sizeof(card->raw_cid)) != 0)) {
+ mmc_remove_card(card);
+ return -ENOENT;
+ }
+ } else {
+ card->type = MMC_TYPE_SDIO;
+
+ if (oldcard && oldcard->type != MMC_TYPE_SDIO) {
+ mmc_remove_card(card);
+ return -ENOENT;
+ }
+ }
+
+ /*
+ * Call the optional HC's init_card function to handle quirks.
+ */
+ if (host->ops->init_card)
+ host->ops->init_card(host, card);
+
+ /*
+ * For native busses: set card RCA and quit open drain mode.
+ */
+ if (!powered_resume && !mmc_host_is_spi(host)) {
+ err = mmc_send_relative_addr(host, &card->rca);
+ if (err)
+ goto remove;
+
+ /*
+ * Update oldcard with the new RCA received from the SDIO
+ * device -- we're doing this so that it's updated in the
+ * "card" struct when oldcard overwrites that later.
+ */
+ if (oldcard)
+ oldcard->rca = card->rca;
+
+ mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
+ }
+
+ /*
+ * Read CSD, before selecting the card
+ */
+ if (!oldcard && card->type == MMC_TYPE_SD_COMBO) {
+ err = mmc_sd_get_csd(host, card);
+ if (err)
+ return err;
+
+ mmc_decode_cid(card);
+ }
+
+ /*
+ * Select card, as all following commands rely on that.
+ */
+ if (!powered_resume && !mmc_host_is_spi(host)) {
+ err = mmc_select_card(card);
+ if (err)
+ goto remove;
+ }
+
+ if (card->quirks & MMC_QUIRK_NONSTD_SDIO) {
+ /*
+ * This is non-standard SDIO device, meaning it doesn't
+ * have any CIA (Common I/O area) registers present.
+ * It's host's responsibility to fill cccr and cis
+ * structures in init_card().
+ */
+ mmc_set_clock(host, card->cis.max_dtr);
+
+ if (card->cccr.high_speed) {
+ mmc_card_set_highspeed(card);
+ mmc_set_timing(card->host, MMC_TIMING_SD_HS);
+ }
+
+ goto finish;
+ }
+
+#ifdef CONFIG_MMC_EMBEDDED_SDIO
+ if (host->embedded_sdio_data.cccr)
+ memcpy(&card->cccr, host->embedded_sdio_data.cccr, sizeof(struct sdio_cccr));
+ else {
+#endif
+ /*
+ * Read the common registers.
+ */
+ err = sdio_read_cccr(card);
+ if (err)
+ goto remove;
+#ifdef CONFIG_MMC_EMBEDDED_SDIO
+ }
+#endif
+
+#ifdef CONFIG_MMC_EMBEDDED_SDIO
+ if (host->embedded_sdio_data.cis)
+ memcpy(&card->cis, host->embedded_sdio_data.cis, sizeof(struct sdio_cis));
+ else {
+#endif
+ /*
+ * Read the common CIS tuples.
+ */
+ err = sdio_read_common_cis(card);
+ if (err)
+ goto remove;
+#ifdef CONFIG_MMC_EMBEDDED_SDIO
+ }
+#endif
+
+ if (oldcard) {
+ int same = (card->cis.vendor == oldcard->cis.vendor &&
+ card->cis.device == oldcard->cis.device);
+ mmc_remove_card(card);
+ if (!same)
+ return -ENOENT;
+
+ card = oldcard;
+ }
+ mmc_fixup_device(card, NULL);
+
+ if (card->type == MMC_TYPE_SD_COMBO) {
+ err = mmc_sd_setup_card(host, card, oldcard != NULL);
+ /* handle as SDIO-only card if memory init failed */
+ if (err) {
+ mmc_go_idle(host);
+ if (mmc_host_is_spi(host))
+ /* should not fail, as it worked previously */
+ mmc_spi_set_crc(host, use_spi_crc);
+ card->type = MMC_TYPE_SDIO;
+ } else
+ card->dev.type = &sd_type;
+ }
+
+ /*
+ * If needed, disconnect card detection pull-up resistor.
+ */
+ err = sdio_disable_cd(card);
+ if (err)
+ goto remove;
+
+ /*
+ * Switch to high-speed (if supported).
+ */
+ err = sdio_enable_hs(card);
+ if (err > 0)
+ mmc_sd_go_highspeed(card);
+ else if (err)
+ goto remove;
+
+ /*
+ * Change to the card's maximum speed.
+ */
+ mmc_set_clock(host, mmc_sdio_get_max_clock(card));
+
+ /*
+ * Switch to wider bus (if supported).
+ */
+ err = sdio_enable_4bit_bus(card);
+ if (err > 0)
+ mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4);
+ else if (err)
+ goto remove;
+
+finish:
+ if (!oldcard)
+ host->card = card;
+ return 0;
+
+remove:
+ if (!oldcard)
+ mmc_remove_card(card);
+
+err:
+ return err;
+}
+
+/*
+ * Host is being removed. Free up the current card.
+ */
+static void mmc_sdio_remove(struct mmc_host *host)
+{
+ int i;
+
+ BUG_ON(!host);
+ BUG_ON(!host->card);
+
+ for (i = 0;i < host->card->sdio_funcs;i++) {
+ if (host->card->sdio_func[i]) {
+ sdio_remove_func(host->card->sdio_func[i]);
+ host->card->sdio_func[i] = NULL;
+ }
+ }
+
+ mmc_remove_card(host->card);
+ host->card = NULL;
+}
+
+/*
+ * Card detection callback from host.
+ */
+static void mmc_sdio_detect(struct mmc_host *host)
+{
+ int err;
+
+ BUG_ON(!host);
+ BUG_ON(!host->card);
+
+ /* Make sure card is powered before detecting it */
+ if (host->caps & MMC_CAP_POWER_OFF_CARD) {
+ err = pm_runtime_get_sync(&host->card->dev);
+ if (err < 0)
+ goto out;
+ }
+
+ mmc_claim_host(host);
+
+ /*
+ * Just check if our card has been removed.
+ */
+ err = mmc_select_card(host->card);
+
+ mmc_release_host(host);
+
+ /*
+ * Tell PM core it's OK to power off the card now.
+ *
+ * The _sync variant is used in order to ensure that the card
+ * is left powered off in case an error occurred, and the card
+ * is going to be removed.
+ *
+ * Since there is no specific reason to believe a new user
+ * is about to show up at this point, the _sync variant is
+ * desirable anyway.
+ */
+ if (host->caps & MMC_CAP_POWER_OFF_CARD)
+ pm_runtime_put_sync(&host->card->dev);
+
+out:
+ if (err) {
+ mmc_sdio_remove(host);
+
+ mmc_claim_host(host);
+ mmc_detach_bus(host);
+ mmc_power_off(host);
+ mmc_release_host(host);
+ }
+}
+
+/*
+ * SDIO suspend. We need to suspend all functions separately.
+ * Therefore all registered functions must have drivers with suspend
+ * and resume methods. Failing that we simply remove the whole card.
+ */
+static int mmc_sdio_suspend(struct mmc_host *host)
+{
+ int i, err = 0;
+
+ for (i = 0; i < host->card->sdio_funcs; i++) {
+ struct sdio_func *func = host->card->sdio_func[i];
+ if (func && sdio_func_present(func) && func->dev.driver) {
+ const struct dev_pm_ops *pmops = func->dev.driver->pm;
+ if (!pmops || !pmops->suspend || !pmops->resume) {
+ /* force removal of entire card in that case */
+ err = -ENOSYS;
+ } else
+ err = pmops->suspend(&func->dev);
+ if (err)
+ break;
+ }
+ }
+ while (err && --i >= 0) {
+ struct sdio_func *func = host->card->sdio_func[i];
+ if (func && sdio_func_present(func) && func->dev.driver) {
+ const struct dev_pm_ops *pmops = func->dev.driver->pm;
+ pmops->resume(&func->dev);
+ }
+ }
+
+ if (!err && mmc_card_keep_power(host) && mmc_card_wake_sdio_irq(host)) {
+ mmc_claim_host(host);
+ sdio_disable_wide(host->card);
+ mmc_release_host(host);
+ }
+
+ if (!err)
+ mmc_claim_host(host);
+
+ return err;
+}
+
+static int mmc_sdio_resume(struct mmc_host *host)
+{
+ int i, err = 0;
+
+ BUG_ON(!host);
+ BUG_ON(!host->card);
+
+ /* No need to reinitialize powered-resumed nonremovable cards */
+ if (mmc_card_is_removable(host) && !mmc_card_keep_power(host))
+ err = mmc_sdio_init_card(host, host->ocr, host->card,
+ mmc_card_keep_power(host));
+ else if (mmc_card_keep_power(host) && mmc_card_wake_sdio_irq(host)) {
+ /* We may have switched to 1-bit mode during suspend */
+ err = sdio_enable_4bit_bus(host->card);
+ if (err > 0) {
+ mmc_set_bus_width(host, MMC_BUS_WIDTH_4);
+ err = 0;
+ }
+ }
+
+ if (!err && host->sdio_irqs)
+ wake_up_process(host->sdio_irq_thread);
+ mmc_release_host(host);
+
+ /*
+ * If the card looked to be the same as before suspending, then
+ * we proceed to resume all card functions. If one of them returns
+ * an error then we simply return that error to the core and the
+ * card will be redetected as new. It is the responsibility of
+ * the function driver to perform further tests with the extra
+ * knowledge it has of the card to confirm the card is indeed the
+ * same as before suspending (same MAC address for network cards,
+ * etc.) and return an error otherwise.
+ */
+ for (i = 0; !err && i < host->card->sdio_funcs; i++) {
+ struct sdio_func *func = host->card->sdio_func[i];
+ if (func && sdio_func_present(func) && func->dev.driver) {
+ const struct dev_pm_ops *pmops = func->dev.driver->pm;
+ err = pmops->resume(&func->dev);
+ }
+ }
+
+ return err;
+}
+
+static int mmc_sdio_power_restore(struct mmc_host *host)
+{
+ int ret;
+ u32 ocr;
+
+ BUG_ON(!host);
+ BUG_ON(!host->card);
+
+ mmc_claim_host(host);
+
+ /*
+ * Reset the card by performing the same steps that are taken by
+ * mmc_rescan_try_freq() and mmc_attach_sdio() during a "normal" probe.
+ *
+ * sdio_reset() is technically not needed. Having just powered up the
+ * hardware, it should already be in reset state. However, some
+ * platforms (such as SD8686 on OLPC) do not instantly cut power,
+ * meaning that a reset is required when restoring power soon after
+ * powering off. It is harmless in other cases.
+ *
+ * The CMD5 reset (mmc_send_io_op_cond()), according to the SDIO spec,
+ * is not necessary for non-removable cards. However, it is required
+ * for OLPC SD8686 (which expects a [CMD5,5,3,7] init sequence), and
+ * harmless in other situations.
+ *
+ * With these steps taken, mmc_select_voltage() is also required to
+ * restore the correct voltage setting of the card.
+ */
+ sdio_reset(host);
+ mmc_go_idle(host);
+ mmc_send_if_cond(host, host->ocr_avail);
+
+ ret = mmc_send_io_op_cond(host, 0, &ocr);
+ if (ret)
+ goto out;
+
+ if (host->ocr_avail_sdio)
+ host->ocr_avail = host->ocr_avail_sdio;
+
+ host->ocr = mmc_select_voltage(host, ocr & ~0x7F);
+ if (!host->ocr) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = mmc_sdio_init_card(host, host->ocr, host->card,
+ mmc_card_keep_power(host));
+ if (!ret && host->sdio_irqs)
+ mmc_signal_sdio_irq(host);
+
+out:
+ mmc_release_host(host);
+
+ return ret;
+}
+
+static const struct mmc_bus_ops mmc_sdio_ops = {
+ .remove = mmc_sdio_remove,
+ .detect = mmc_sdio_detect,
+ .suspend = mmc_sdio_suspend,
+ .resume = mmc_sdio_resume,
+ .power_restore = mmc_sdio_power_restore,
+};
+
+
+/*
+ * Starting point for SDIO card init.
+ */
+int mmc_attach_sdio(struct mmc_host *host)
+{
+ int err, i, funcs;
+ u32 ocr;
+ struct mmc_card *card;
+
+ BUG_ON(!host);
+ WARN_ON(!host->claimed);
+
+ err = mmc_send_io_op_cond(host, 0, &ocr);
+ if (err)
+ return err;
+
+ mmc_attach_bus(host, &mmc_sdio_ops);
+ if (host->ocr_avail_sdio)
+ host->ocr_avail = host->ocr_avail_sdio;
+
+ /*
+ * Sanity check the voltages that the card claims to
+ * support.
+ */
+ if (ocr & 0x7F) {
+ printk(KERN_WARNING "%s: card claims to support voltages "
+ "below the defined range. These will be ignored.\n",
+ mmc_hostname(host));
+ ocr &= ~0x7F;
+ }
+
+ host->ocr = mmc_select_voltage(host, ocr);
+
+ /*
+ * Can we support the voltage(s) of the card(s)?
+ */
+ if (!host->ocr) {
+ err = -EINVAL;
+ goto err;
+ }
+
+ /*
+ * Detect and init the card.
+ */
+ err = mmc_sdio_init_card(host, host->ocr, NULL, 0);
+ if (err)
+ goto err;
+ card = host->card;
+
+ /*
+ * Enable runtime PM only if supported by host+card+board
+ */
+ if (host->caps & MMC_CAP_POWER_OFF_CARD) {
+ /*
+ * Let runtime PM core know our card is active
+ */
+ err = pm_runtime_set_active(&card->dev);
+ if (err)
+ goto remove;
+
+ /*
+ * Enable runtime PM for this card
+ */
+ pm_runtime_enable(&card->dev);
+ }
+
+ /*
+ * The number of functions on the card is encoded inside
+ * the ocr.
+ */
+ funcs = (ocr & 0x70000000) >> 28;
+ card->sdio_funcs = 0;
+
+#ifdef CONFIG_MMC_EMBEDDED_SDIO
+ if (host->embedded_sdio_data.funcs)
+ card->sdio_funcs = funcs = host->embedded_sdio_data.num_funcs;
+#endif
+
+ /*
+ * Initialize (but don't add) all present functions.
+ */
+ for (i = 0; i < funcs; i++, card->sdio_funcs++) {
+#ifdef CONFIG_MMC_EMBEDDED_SDIO
+ if (host->embedded_sdio_data.funcs) {
+ struct sdio_func *tmp;
+
+ tmp = sdio_alloc_func(host->card);
+ if (IS_ERR(tmp))
+ goto remove;
+ tmp->num = (i + 1);
+ card->sdio_func[i] = tmp;
+ tmp->class = host->embedded_sdio_data.funcs[i].f_class;
+ tmp->max_blksize = host->embedded_sdio_data.funcs[i].f_maxblksize;
+ tmp->vendor = card->cis.vendor;
+ tmp->device = card->cis.device;
+ } else {
+#endif
+ err = sdio_init_func(host->card, i + 1);
+ if (err)
+ goto remove;
+#ifdef CONFIG_MMC_EMBEDDED_SDIO
+ }
+#endif
+ /*
+ * Enable Runtime PM for this func (if supported)
+ */
+ if (host->caps & MMC_CAP_POWER_OFF_CARD)
+ pm_runtime_enable(&card->sdio_func[i]->dev);
+ }
+
+ /*
+ * First add the card to the driver model...
+ */
+ mmc_release_host(host);
+ err = mmc_add_card(host->card);
+ if (err)
+ goto remove_added;
+
+ /*
+ * ...then the SDIO functions.
+ */
+ for (i = 0;i < funcs;i++) {
+ err = sdio_add_func(host->card->sdio_func[i]);
+ if (err)
+ goto remove_added;
+ }
+
+ mmc_claim_host(host);
+ return 0;
+
+
+remove_added:
+ /* Remove without lock if the device has been added. */
+ mmc_sdio_remove(host);
+ mmc_claim_host(host);
+remove:
+ /* And with lock if it hasn't been added. */
+ mmc_release_host(host);
+ if (host->card)
+ mmc_sdio_remove(host);
+ mmc_claim_host(host);
+err:
+ mmc_detach_bus(host);
+
+ printk(KERN_ERR "%s: error %d whilst initialising SDIO card\n",
+ mmc_hostname(host), err);
+
+ return err;
+}
+
+int sdio_reset_comm(struct mmc_card *card)
+{
+ struct mmc_host *host = card->host;
+ u32 ocr;
+ int err;
+
+ printk("%s():\n", __func__);
+ mmc_claim_host(host);
+
+ mmc_go_idle(host);
+
+ mmc_set_clock(host, host->f_min);
+
+ err = mmc_send_io_op_cond(host, 0, &ocr);
+ if (err)
+ goto err;
+
+ host->ocr = mmc_select_voltage(host, ocr);
+ if (!host->ocr) {
+ err = -EINVAL;
+ goto err;
+ }
+
+ err = mmc_send_io_op_cond(host, host->ocr, &ocr);
+ if (err)
+ goto err;
+
+ if (mmc_host_is_spi(host)) {
+ err = mmc_spi_set_crc(host, use_spi_crc);
+ if (err)
+ goto err;
+ }
+
+ if (!mmc_host_is_spi(host)) {
+ err = mmc_send_relative_addr(host, &card->rca);
+ if (err)
+ goto err;
+ mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
+ }
+ if (!mmc_host_is_spi(host)) {
+ err = mmc_select_card(card);
+ if (err)
+ goto err;
+ }
+
+ /*
+ * Switch to high-speed (if supported).
+ */
+ err = sdio_enable_hs(card);
+ if (err > 0)
+ mmc_sd_go_highspeed(card);
+ else if (err)
+ goto err;
+
+ /*
+ * Change to the card's maximum speed.
+ */
+ mmc_set_clock(host, mmc_sdio_get_max_clock(card));
+
+ err = sdio_enable_4bit_bus(card);
+ if (err > 0)
+ mmc_set_bus_width(host, MMC_BUS_WIDTH_4);
+ else if (err)
+ goto err;
+
+ mmc_release_host(host);
+ return 0;
+err:
+ printk("%s: Error resetting SDIO communications (%d)\n",
+ mmc_hostname(host), err);
+ mmc_release_host(host);
+ return err;
+}
+EXPORT_SYMBOL(sdio_reset_comm);
diff --git a/drivers/mmc/core/sdio_bus.c b/drivers/mmc/core/sdio_bus.c
new file mode 100644
index 00000000..52429a98
--- /dev/null
+++ b/drivers/mmc/core/sdio_bus.c
@@ -0,0 +1,334 @@
+/*
+ * linux/drivers/mmc/core/sdio_bus.c
+ *
+ * Copyright 2007 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
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * SDIO function driver model
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/pm_runtime.h>
+
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sdio_func.h>
+
+#include "sdio_cis.h"
+#include "sdio_bus.h"
+
+#ifdef CONFIG_MMC_EMBEDDED_SDIO
+#include <linux/mmc/host.h>
+#endif
+
+/* show configuration fields */
+#define sdio_config_attr(field, format_string) \
+static ssize_t \
+field##_show(struct device *dev, struct device_attribute *attr, char *buf) \
+{ \
+ struct sdio_func *func; \
+ \
+ func = dev_to_sdio_func (dev); \
+ return sprintf (buf, format_string, func->field); \
+}
+
+sdio_config_attr(class, "0x%02x\n");
+sdio_config_attr(vendor, "0x%04x\n");
+sdio_config_attr(device, "0x%04x\n");
+
+static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct sdio_func *func = dev_to_sdio_func (dev);
+
+ return sprintf(buf, "sdio:c%02Xv%04Xd%04X\n",
+ func->class, func->vendor, func->device);
+}
+
+static struct device_attribute sdio_dev_attrs[] = {
+ __ATTR_RO(class),
+ __ATTR_RO(vendor),
+ __ATTR_RO(device),
+ __ATTR_RO(modalias),
+ __ATTR_NULL,
+};
+
+static const struct sdio_device_id *sdio_match_one(struct sdio_func *func,
+ const struct sdio_device_id *id)
+{
+ if (id->class != (__u8)SDIO_ANY_ID && id->class != func->class)
+ return NULL;
+ if (id->vendor != (__u16)SDIO_ANY_ID && id->vendor != func->vendor)
+ return NULL;
+ if (id->device != (__u16)SDIO_ANY_ID && id->device != func->device)
+ return NULL;
+ return id;
+}
+
+static const struct sdio_device_id *sdio_match_device(struct sdio_func *func,
+ struct sdio_driver *sdrv)
+{
+ const struct sdio_device_id *ids;
+
+ ids = sdrv->id_table;
+
+ if (ids) {
+ while (ids->class || ids->vendor || ids->device) {
+ if (sdio_match_one(func, ids))
+ return ids;
+ ids++;
+ }
+ }
+
+ return NULL;
+}
+
+static int sdio_bus_match(struct device *dev, struct device_driver *drv)
+{
+ struct sdio_func *func = dev_to_sdio_func(dev);
+ struct sdio_driver *sdrv = to_sdio_driver(drv);
+
+ if (sdio_match_device(func, sdrv))
+ return 1;
+
+ return 0;
+}
+
+static int
+sdio_bus_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ struct sdio_func *func = dev_to_sdio_func(dev);
+
+ if (add_uevent_var(env,
+ "SDIO_CLASS=%02X", func->class))
+ return -ENOMEM;
+
+ if (add_uevent_var(env,
+ "SDIO_ID=%04X:%04X", func->vendor, func->device))
+ return -ENOMEM;
+
+ if (add_uevent_var(env,
+ "MODALIAS=sdio:c%02Xv%04Xd%04X",
+ func->class, func->vendor, func->device))
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int sdio_bus_probe(struct device *dev)
+{
+ struct sdio_driver *drv = to_sdio_driver(dev->driver);
+ struct sdio_func *func = dev_to_sdio_func(dev);
+ const struct sdio_device_id *id;
+ int ret;
+
+ id = sdio_match_device(func, drv);
+ if (!id)
+ return -ENODEV;
+
+ /* Unbound SDIO functions are always suspended.
+ * During probe, the function is set active and the usage count
+ * is incremented. If the driver supports runtime PM,
+ * it should call pm_runtime_put_noidle() in its probe routine and
+ * pm_runtime_get_noresume() in its remove routine.
+ */
+ if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD) {
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0)
+ goto out;
+ }
+
+ /* Set the default block size so the driver is sure it's something
+ * sensible. */
+ sdio_claim_host(func);
+ ret = sdio_set_block_size(func, 0);
+ sdio_release_host(func);
+ if (ret)
+ goto disable_runtimepm;
+
+ ret = drv->probe(func, id);
+ if (ret)
+ goto disable_runtimepm;
+
+ return 0;
+
+disable_runtimepm:
+ if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD)
+ pm_runtime_put_noidle(dev);
+out:
+ return ret;
+}
+
+static int sdio_bus_remove(struct device *dev)
+{
+ struct sdio_driver *drv = to_sdio_driver(dev->driver);
+ struct sdio_func *func = dev_to_sdio_func(dev);
+ int ret = 0;
+
+ /* Make sure card is powered before invoking ->remove() */
+ if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD) {
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0)
+ goto out;
+ }
+
+ drv->remove(func);
+
+ if (func->irq_handler) {
+ printk(KERN_WARNING "WARNING: driver %s did not remove "
+ "its interrupt handler!\n", drv->name);
+ sdio_claim_host(func);
+ sdio_release_irq(func);
+ sdio_release_host(func);
+ }
+
+ /* First, undo the increment made directly above */
+ if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD)
+ pm_runtime_put_noidle(dev);
+
+ /* Then undo the runtime PM settings in sdio_bus_probe() */
+ if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD)
+ pm_runtime_put_sync(dev);
+
+out:
+ return ret;
+}
+
+#ifdef CONFIG_PM_RUNTIME
+
+static const struct dev_pm_ops sdio_bus_pm_ops = {
+ SET_RUNTIME_PM_OPS(
+ pm_generic_runtime_suspend,
+ pm_generic_runtime_resume,
+ pm_generic_runtime_idle
+ )
+};
+
+#define SDIO_PM_OPS_PTR (&sdio_bus_pm_ops)
+
+#else /* !CONFIG_PM_RUNTIME */
+
+#define SDIO_PM_OPS_PTR NULL
+
+#endif /* !CONFIG_PM_RUNTIME */
+
+static struct bus_type sdio_bus_type = {
+ .name = "sdio",
+ .dev_attrs = sdio_dev_attrs,
+ .match = sdio_bus_match,
+ .uevent = sdio_bus_uevent,
+ .probe = sdio_bus_probe,
+ .remove = sdio_bus_remove,
+ .pm = SDIO_PM_OPS_PTR,
+};
+
+int sdio_register_bus(void)
+{
+ return bus_register(&sdio_bus_type);
+}
+
+void sdio_unregister_bus(void)
+{
+ bus_unregister(&sdio_bus_type);
+}
+
+/**
+ * sdio_register_driver - register a function driver
+ * @drv: SDIO function driver
+ */
+int sdio_register_driver(struct sdio_driver *drv)
+{
+ drv->drv.name = drv->name;
+ drv->drv.bus = &sdio_bus_type;
+ return driver_register(&drv->drv);
+}
+EXPORT_SYMBOL_GPL(sdio_register_driver);
+
+/**
+ * sdio_unregister_driver - unregister a function driver
+ * @drv: SDIO function driver
+ */
+void sdio_unregister_driver(struct sdio_driver *drv)
+{
+ drv->drv.bus = &sdio_bus_type;
+ driver_unregister(&drv->drv);
+}
+EXPORT_SYMBOL_GPL(sdio_unregister_driver);
+
+static void sdio_release_func(struct device *dev)
+{
+ struct sdio_func *func = dev_to_sdio_func(dev);
+
+#ifdef CONFIG_MMC_EMBEDDED_SDIO
+ /*
+ * If this device is embedded then we never allocated
+ * cis tables for this func
+ */
+ if (!func->card->host->embedded_sdio_data.funcs)
+#endif
+ sdio_free_func_cis(func);
+
+ if (func->info)
+ kfree(func->info);
+
+ kfree(func);
+}
+
+/*
+ * Allocate and initialise a new SDIO function structure.
+ */
+struct sdio_func *sdio_alloc_func(struct mmc_card *card)
+{
+ struct sdio_func *func;
+
+ func = kzalloc(sizeof(struct sdio_func), GFP_KERNEL);
+ if (!func)
+ return ERR_PTR(-ENOMEM);
+
+ func->card = card;
+
+ device_initialize(&func->dev);
+
+ func->dev.parent = &card->dev;
+ func->dev.bus = &sdio_bus_type;
+ func->dev.release = sdio_release_func;
+
+ return func;
+}
+
+/*
+ * Register a new SDIO function with the driver model.
+ */
+int sdio_add_func(struct sdio_func *func)
+{
+ int ret;
+
+ dev_set_name(&func->dev, "%s:%d", mmc_card_id(func->card), func->num);
+
+ ret = device_add(&func->dev);
+ if (ret == 0)
+ sdio_func_set_present(func);
+
+ return ret;
+}
+
+/*
+ * Unregister a SDIO function with the driver model, and
+ * (eventually) free it.
+ * This function can be called through error paths where sdio_add_func() was
+ * never executed (because a failure occurred at an earlier point).
+ */
+void sdio_remove_func(struct sdio_func *func)
+{
+ if (!sdio_func_present(func))
+ return;
+
+ device_del(&func->dev);
+ put_device(&func->dev);
+}
+
diff --git a/drivers/mmc/core/sdio_bus.h b/drivers/mmc/core/sdio_bus.h
new file mode 100644
index 00000000..567a7682
--- /dev/null
+++ b/drivers/mmc/core/sdio_bus.h
@@ -0,0 +1,22 @@
+/*
+ * linux/drivers/mmc/core/sdio_bus.h
+ *
+ * Copyright 2007 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
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ */
+#ifndef _MMC_CORE_SDIO_BUS_H
+#define _MMC_CORE_SDIO_BUS_H
+
+struct sdio_func *sdio_alloc_func(struct mmc_card *card);
+int sdio_add_func(struct sdio_func *func);
+void sdio_remove_func(struct sdio_func *func);
+
+int sdio_register_bus(void);
+void sdio_unregister_bus(void);
+
+#endif
+
diff --git a/drivers/mmc/core/sdio_cis.c b/drivers/mmc/core/sdio_cis.c
new file mode 100644
index 00000000..541bdb89
--- /dev/null
+++ b/drivers/mmc/core/sdio_cis.c
@@ -0,0 +1,412 @@
+/*
+ * linux/drivers/mmc/core/sdio_cis.c
+ *
+ * Author: Nicolas Pitre
+ * Created: June 11, 2007
+ * Copyright: MontaVista Software Inc.
+ *
+ * Copyright 2007 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
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/sdio_func.h>
+
+#include "sdio_cis.h"
+#include "sdio_ops.h"
+
+static int cistpl_vers_1(struct mmc_card *card, struct sdio_func *func,
+ const unsigned char *buf, unsigned size)
+{
+ unsigned i, nr_strings;
+ char **buffer, *string;
+
+ /* Find all null-terminated (including zero length) strings in
+ the TPLLV1_INFO field. Trailing garbage is ignored. */
+ buf += 2;
+ size -= 2;
+
+ nr_strings = 0;
+ for (i = 0; i < size; i++) {
+ if (buf[i] == 0xff)
+ break;
+ if (buf[i] == 0)
+ nr_strings++;
+ }
+ if (nr_strings == 0)
+ return 0;
+
+ size = i;
+
+ buffer = kzalloc(sizeof(char*) * nr_strings + size, GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
+
+ string = (char*)(buffer + nr_strings);
+
+ for (i = 0; i < nr_strings; i++) {
+ buffer[i] = string;
+ strcpy(string, buf);
+ string += strlen(string) + 1;
+ buf += strlen(buf) + 1;
+ }
+
+ if (func) {
+ func->num_info = nr_strings;
+ func->info = (const char**)buffer;
+ } else {
+ card->num_info = nr_strings;
+ card->info = (const char**)buffer;
+ }
+
+ return 0;
+}
+
+static int cistpl_manfid(struct mmc_card *card, struct sdio_func *func,
+ const unsigned char *buf, unsigned size)
+{
+ unsigned int vendor, device;
+
+ /* TPLMID_MANF */
+ vendor = buf[0] | (buf[1] << 8);
+
+ /* TPLMID_CARD */
+ device = buf[2] | (buf[3] << 8);
+
+ if (func) {
+ func->vendor = vendor;
+ func->device = device;
+ } else {
+ card->cis.vendor = vendor;
+ card->cis.device = device;
+ }
+
+ return 0;
+}
+
+static const unsigned char speed_val[16] =
+ { 0, 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80 };
+static const unsigned int speed_unit[8] =
+ { 10000, 100000, 1000000, 10000000, 0, 0, 0, 0 };
+
+
+typedef int (tpl_parse_t)(struct mmc_card *, struct sdio_func *,
+ const unsigned char *, unsigned);
+
+struct cis_tpl {
+ unsigned char code;
+ unsigned char min_size;
+ tpl_parse_t *parse;
+};
+
+static int cis_tpl_parse(struct mmc_card *card, struct sdio_func *func,
+ const char *tpl_descr,
+ const struct cis_tpl *tpl, int tpl_count,
+ unsigned char code,
+ const unsigned char *buf, unsigned size)
+{
+ int i, ret;
+
+ /* look for a matching code in the table */
+ for (i = 0; i < tpl_count; i++, tpl++) {
+ if (tpl->code == code)
+ break;
+ }
+ if (i < tpl_count) {
+ if (size >= tpl->min_size) {
+ if (tpl->parse)
+ ret = tpl->parse(card, func, buf, size);
+ else
+ ret = -EILSEQ; /* known tuple, not parsed */
+ } else {
+ /* invalid tuple */
+ ret = -EINVAL;
+ }
+ if (ret && ret != -EILSEQ && ret != -ENOENT) {
+ printk(KERN_ERR "%s: bad %s tuple 0x%02x (%u bytes)\n",
+ mmc_hostname(card->host), tpl_descr, code, size);
+ }
+ } else {
+ /* unknown tuple */
+ ret = -ENOENT;
+ }
+
+ return ret;
+}
+
+static int cistpl_funce_common(struct mmc_card *card, struct sdio_func *func,
+ const unsigned char *buf, unsigned size)
+{
+ /* Only valid for the common CIS (function 0) */
+ if (func)
+ return -EINVAL;
+
+ /* TPLFE_FN0_BLK_SIZE */
+ card->cis.blksize = buf[1] | (buf[2] << 8);
+
+ /* TPLFE_MAX_TRAN_SPEED */
+ card->cis.max_dtr = speed_val[(buf[3] >> 3) & 15] *
+ speed_unit[buf[3] & 7];
+
+ return 0;
+}
+
+static int cistpl_funce_func(struct mmc_card *card, struct sdio_func *func,
+ const unsigned char *buf, unsigned size)
+{
+ unsigned vsn;
+ unsigned min_size;
+
+ /* Only valid for the individual function's CIS (1-7) */
+ if (!func)
+ return -EINVAL;
+
+ /*
+ * This tuple has a different length depending on the SDIO spec
+ * version.
+ */
+ vsn = func->card->cccr.sdio_vsn;
+ min_size = (vsn == SDIO_SDIO_REV_1_00) ? 28 : 42;
+
+ if (size < min_size)
+ return -EINVAL;
+
+ /* 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;
+}
+
+/*
+ * Known TPLFE_TYPEs table for CISTPL_FUNCE tuples.
+ *
+ * Note that, unlike PCMCIA, CISTPL_FUNCE tuples are not parsed depending
+ * on the TPLFID_FUNCTION value of the previous CISTPL_FUNCID as on SDIO
+ * TPLFID_FUNCTION is always hardcoded to 0x0C.
+ */
+static const struct cis_tpl cis_tpl_funce_list[] = {
+ { 0x00, 4, cistpl_funce_common },
+ { 0x01, 0, cistpl_funce_func },
+ { 0x04, 1+1+6, /* CISTPL_FUNCE_LAN_NODE_ID */ },
+};
+
+static int cistpl_funce(struct mmc_card *card, struct sdio_func *func,
+ const unsigned char *buf, unsigned size)
+{
+ if (size < 1)
+ return -EINVAL;
+
+ return cis_tpl_parse(card, func, "CISTPL_FUNCE",
+ cis_tpl_funce_list,
+ ARRAY_SIZE(cis_tpl_funce_list),
+ buf[0], buf, size);
+}
+
+/* Known TPL_CODEs table for CIS tuples */
+static const struct cis_tpl cis_tpl_list[] = {
+ { 0x15, 3, cistpl_vers_1 },
+ { 0x20, 4, cistpl_manfid },
+ { 0x21, 2, /* cistpl_funcid */ },
+ { 0x22, 0, cistpl_funce },
+};
+
+static int sdio_read_cis(struct mmc_card *card, struct sdio_func *func)
+{
+ int ret;
+ struct sdio_func_tuple *this, **prev;
+ unsigned i, ptr = 0;
+
+ /*
+ * Note that this works for the common CIS (function number 0) as
+ * well as a function's CIS * since SDIO_CCCR_CIS and SDIO_FBR_CIS
+ * have the same offset.
+ */
+ for (i = 0; i < 3; i++) {
+ unsigned char x, fn;
+
+ if (func)
+ fn = func->num;
+ else
+ fn = 0;
+
+ ret = mmc_io_rw_direct(card, 0, 0,
+ SDIO_FBR_BASE(fn) + SDIO_FBR_CIS + i, 0, &x);
+ if (ret)
+ return ret;
+ ptr |= x << (i * 8);
+ }
+
+ if (func)
+ prev = &func->tuples;
+ else
+ prev = &card->tuples;
+
+ BUG_ON(*prev);
+
+ do {
+ unsigned char tpl_code, tpl_link;
+
+ ret = mmc_io_rw_direct(card, 0, 0, ptr++, 0, &tpl_code);
+ if (ret)
+ break;
+
+ /* 0xff means we're done */
+ if (tpl_code == 0xff)
+ break;
+
+ /* null entries have no link field or data */
+ if (tpl_code == 0x00)
+ continue;
+
+ ret = mmc_io_rw_direct(card, 0, 0, ptr++, 0, &tpl_link);
+ if (ret)
+ break;
+
+ /* a size of 0xff also means we're done */
+ if (tpl_link == 0xff)
+ break;
+
+ this = kmalloc(sizeof(*this) + tpl_link, GFP_KERNEL);
+ if (!this)
+ return -ENOMEM;
+
+ for (i = 0; i < tpl_link; i++) {
+ ret = mmc_io_rw_direct(card, 0, 0,
+ ptr + i, 0, &this->data[i]);
+ if (ret)
+ break;
+ }
+ if (ret) {
+ kfree(this);
+ break;
+ }
+
+ /* Try to parse the CIS tuple */
+ ret = cis_tpl_parse(card, func, "CIS",
+ cis_tpl_list, ARRAY_SIZE(cis_tpl_list),
+ tpl_code, this->data, tpl_link);
+ if (ret == -EILSEQ || ret == -ENOENT) {
+ /*
+ * The tuple is unknown or known but not parsed.
+ * Queue the tuple for the function driver.
+ */
+ this->next = NULL;
+ this->code = tpl_code;
+ this->size = tpl_link;
+ *prev = this;
+ prev = &this->next;
+
+ if (ret == -ENOENT) {
+ /* warn about unknown tuples */
+ printk(KERN_WARNING "%s: queuing unknown"
+ " CIS tuple 0x%02x (%u bytes)\n",
+ mmc_hostname(card->host),
+ tpl_code, tpl_link);
+ }
+
+ /* keep on analyzing tuples */
+ ret = 0;
+ } else {
+ /*
+ * We don't need the tuple anymore if it was
+ * successfully parsed by the SDIO core or if it is
+ * not going to be queued for a driver.
+ */
+ kfree(this);
+ }
+
+ ptr += tpl_link;
+ } while (!ret);
+
+ /*
+ * Link in all unknown tuples found in the common CIS so that
+ * drivers don't have to go digging in two places.
+ */
+ if (func)
+ *prev = card->tuples;
+
+ return ret;
+}
+
+int sdio_read_common_cis(struct mmc_card *card)
+{
+ return sdio_read_cis(card, NULL);
+}
+
+void sdio_free_common_cis(struct mmc_card *card)
+{
+ struct sdio_func_tuple *tuple, *victim;
+
+ tuple = card->tuples;
+
+ while (tuple) {
+ victim = tuple;
+ tuple = tuple->next;
+ kfree(victim);
+ }
+
+ card->tuples = NULL;
+}
+
+int sdio_read_func_cis(struct sdio_func *func)
+{
+ int ret;
+
+ ret = sdio_read_cis(func->card, func);
+ if (ret)
+ return ret;
+
+ /*
+ * Since we've linked to tuples in the card structure,
+ * we must make sure we have a reference to it.
+ */
+ get_device(&func->card->dev);
+
+ /*
+ * Vendor/device id is optional for function CIS, so
+ * copy it from the card structure as needed.
+ */
+ if (func->vendor == 0) {
+ func->vendor = func->card->cis.vendor;
+ func->device = func->card->cis.device;
+ }
+
+ return 0;
+}
+
+void sdio_free_func_cis(struct sdio_func *func)
+{
+ struct sdio_func_tuple *tuple, *victim;
+
+ tuple = func->tuples;
+
+ while (tuple && tuple != func->card->tuples) {
+ victim = tuple;
+ tuple = tuple->next;
+ kfree(victim);
+ }
+
+ func->tuples = NULL;
+
+ /*
+ * We have now removed the link to the tuples in the
+ * card structure, so remove the reference.
+ */
+ put_device(&func->card->dev);
+}
+
diff --git a/drivers/mmc/core/sdio_cis.h b/drivers/mmc/core/sdio_cis.h
new file mode 100644
index 00000000..4d903c2e
--- /dev/null
+++ b/drivers/mmc/core/sdio_cis.h
@@ -0,0 +1,23 @@
+/*
+ * linux/drivers/mmc/core/sdio_cis.h
+ *
+ * Author: Nicolas Pitre
+ * Created: June 11, 2007
+ * Copyright: MontaVista Software Inc.
+ *
+ * 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.
+ */
+
+#ifndef _MMC_SDIO_CIS_H
+#define _MMC_SDIO_CIS_H
+
+int sdio_read_common_cis(struct mmc_card *card);
+void sdio_free_common_cis(struct mmc_card *card);
+
+int sdio_read_func_cis(struct sdio_func *func);
+void sdio_free_func_cis(struct sdio_func *func);
+
+#endif
diff --git a/drivers/mmc/core/sdio_io.c b/drivers/mmc/core/sdio_io.c
new file mode 100755
index 00000000..0f687cde
--- /dev/null
+++ b/drivers/mmc/core/sdio_io.c
@@ -0,0 +1,721 @@
+/*
+ * linux/drivers/mmc/core/sdio_io.c
+ *
+ * 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
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ */
+
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/sdio_func.h>
+
+#include "sdio_ops.h"
+
+/**
+ * sdio_claim_host - exclusively claim a bus for a certain SDIO function
+ * @func: SDIO function that will be accessed
+ *
+ * Claim a bus for a set of operations. The SDIO function given
+ * is used to figure out which bus is relevant.
+ */
+void sdio_claim_host(struct sdio_func *func)
+{
+ BUG_ON(!func);
+ BUG_ON(!func->card);
+
+ mmc_claim_host(func->card->host);
+}
+EXPORT_SYMBOL_GPL(sdio_claim_host);
+
+/**
+ * sdio_release_host - release a bus for a certain SDIO function
+ * @func: SDIO function that was accessed
+ *
+ * Release a bus, allowing others to claim the bus for their
+ * operations.
+ */
+void sdio_release_host(struct sdio_func *func)
+{
+ BUG_ON(!func);
+ BUG_ON(!func->card);
+
+ mmc_release_host(func->card->host);
+}
+EXPORT_SYMBOL_GPL(sdio_release_host);
+
+/**
+ * sdio_enable_func - enables a SDIO function for usage
+ * @func: SDIO function to enable
+ *
+ * Powers up and activates a SDIO function so that register
+ * access is possible.
+ */
+int sdio_enable_func(struct sdio_func *func)
+{
+ int ret;
+ unsigned char reg;
+ unsigned long timeout;
+
+ BUG_ON(!func);
+ BUG_ON(!func->card);
+
+ pr_debug("SDIO: Enabling device %s...\n", sdio_func_id(func));
+
+ ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IOEx, 0, &reg);
+ if (ret)
+ goto err;
+
+ reg |= 1 << func->num;
+
+ ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IOEx, reg, NULL);
+ if (ret)
+ goto err;
+
+ timeout = jiffies + msecs_to_jiffies(func->enable_timeout);
+
+ while (1) {
+ ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IORx, 0, &reg);
+ if (ret)
+ goto err;
+ if (reg & (1 << func->num))
+ break;
+ ret = -ETIME;
+ if (time_after(jiffies, timeout))
+ goto err;
+ }
+
+ pr_debug("SDIO: Enabled device %s\n", sdio_func_id(func));
+
+ return 0;
+
+err:
+ pr_debug("SDIO: Failed to enable device %s\n", sdio_func_id(func));
+ return ret;
+}
+EXPORT_SYMBOL_GPL(sdio_enable_func);
+
+/**
+ * sdio_disable_func - disable a SDIO function
+ * @func: SDIO function to disable
+ *
+ * Powers down and deactivates a SDIO function. Register access
+ * to this function will fail until the function is reenabled.
+ */
+int sdio_disable_func(struct sdio_func *func)
+{
+ int ret;
+ unsigned char reg;
+
+ BUG_ON(!func);
+ BUG_ON(!func->card);
+
+ pr_debug("SDIO: Disabling device %s...\n", sdio_func_id(func));
+
+ ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IOEx, 0, &reg);
+ if (ret)
+ goto err;
+
+ reg &= ~(1 << func->num);
+
+ ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IOEx, reg, NULL);
+ if (ret)
+ goto err;
+
+ pr_debug("SDIO: Disabled device %s\n", sdio_func_id(func));
+
+ return 0;
+
+err:
+ pr_debug("SDIO: Failed to disable device %s\n", sdio_func_id(func));
+ return -EIO;
+}
+EXPORT_SYMBOL_GPL(sdio_disable_func);
+
+/**
+ * sdio_set_block_size - set the block size of an SDIO function
+ * @func: SDIO function to change
+ * @blksz: new block size or 0 to use the default.
+ *
+ * The default block size is the largest supported by both the function
+ * and the host, with a maximum of 512 to ensure that arbitrarily sized
+ * data transfer use the optimal (least) number of commands.
+ *
+ * A driver may call this to override the default block size set by the
+ * core. This can be used to set a block size greater than the maximum
+ * that reported by the card; it is the driver's responsibility to ensure
+ * it uses a value that the card supports.
+ *
+ * Returns 0 on success, -EINVAL if the host does not support the
+ * requested block size, or -EIO (etc.) if one of the resultant FBR block
+ * size register writes failed.
+ *
+ */
+int sdio_set_block_size(struct sdio_func *func, unsigned blksz)
+{
+ int ret;
+
+ if (blksz > func->card->host->max_blk_size)
+ return -EINVAL;
+
+ if (blksz == 0) {
+ blksz = min(func->max_blksize, func->card->host->max_blk_size);
+ blksz = min(blksz, 512u);
+ }
+
+ ret = mmc_io_rw_direct(func->card, 1, 0,
+ SDIO_FBR_BASE(func->num) + SDIO_FBR_BLKSIZE,
+ blksz & 0xff, NULL);
+ if (ret)
+ return ret;
+ ret = mmc_io_rw_direct(func->card, 1, 0,
+ SDIO_FBR_BASE(func->num) + SDIO_FBR_BLKSIZE + 1,
+ (blksz >> 8) & 0xff, NULL);
+ if (ret)
+ return ret;
+ 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);
+
+ if (mmc_blksz_for_byte_mode(func->card))
+ mval = min(mval, func->cur_blksize);
+ else
+ 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,
+ unsigned addr, int incr_addr, u8 *buf, unsigned size)
+{
+ unsigned remainder = size;
+ unsigned max_blocks;
+ int ret;
+
+ /* Do the bulk of the transfer using block mode (if supported). */
+ 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(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;
+
+ blocks = remainder / func->cur_blksize;
+ if (blocks > max_blocks)
+ blocks = max_blocks;
+ size = blocks * func->cur_blksize;
+
+ ret = mmc_io_rw_extended(func->card, write,
+ func->num, addr, incr_addr, buf,
+ blocks, func->cur_blksize);
+ if (ret)
+ return ret;
+
+ remainder -= size;
+ buf += size;
+ if (incr_addr)
+ addr += size;
+ }
+ }
+
+ /* Write the remainder using byte mode. */
+ while (remainder > 0) {
+ size = min(remainder, sdio_max_byte_size(func));
+
+ ret = mmc_io_rw_extended(func->card, write, func->num, addr,
+ incr_addr, buf, 1, size);
+ if (ret)
+ return ret;
+
+ remainder -= size;
+ buf += size;
+ if (incr_addr)
+ addr += size;
+ }
+ return 0;
+}
+
+/**
+ * sdio_readb - read a single byte from a SDIO function
+ * @func: SDIO function to access
+ * @addr: address to read
+ * @err_ret: optional status value from transfer
+ *
+ * Reads a single byte from the address space of a given SDIO
+ * function. If there is a problem reading the address, 0xff
+ * is returned and @err_ret will contain the error code.
+ */
+u8 sdio_readb(struct sdio_func *func, unsigned int addr, int *err_ret)
+{
+ int ret;
+ u8 val;
+
+ BUG_ON(!func);
+
+ if (err_ret)
+ *err_ret = 0;
+
+ ret = mmc_io_rw_direct(func->card, 0, func->num, addr, 0, &val);
+ if (ret) {
+ if (err_ret)
+ *err_ret = ret;
+ return 0xFF;
+ }
+
+ return val;
+}
+EXPORT_SYMBOL_GPL(sdio_readb);
+
+/**
+ * sdio_writeb - write a single byte to a SDIO function
+ * @func: SDIO function to access
+ * @b: byte to write
+ * @addr: address to write to
+ * @err_ret: optional status value from transfer
+ *
+ * Writes a single byte to the address space of a given SDIO
+ * function. @err_ret will contain the status of the actual
+ * transfer.
+ */
+void sdio_writeb(struct sdio_func *func, u8 b, unsigned int addr, int *err_ret)
+{
+ int ret;
+
+ BUG_ON(!func);
+
+ ret = mmc_io_rw_direct(func->card, 1, func->num, addr, b, NULL);
+ if (err_ret)
+ *err_ret = ret;
+}
+EXPORT_SYMBOL_GPL(sdio_writeb);
+
+/**
+ * sdio_writeb_readb - write and read a byte from SDIO function
+ * @func: SDIO function to access
+ * @write_byte: byte to write
+ * @addr: address to write to
+ * @err_ret: optional status value from transfer
+ *
+ * Performs a RAW (Read after Write) operation as defined by SDIO spec -
+ * single byte is written to address space of a given SDIO function and
+ * response is read back from the same address, both using single request.
+ * If there is a problem with the operation, 0xff is returned and
+ * @err_ret will contain the error code.
+ */
+u8 sdio_writeb_readb(struct sdio_func *func, u8 write_byte,
+ unsigned int addr, int *err_ret)
+{
+ int ret;
+ u8 val;
+
+ ret = mmc_io_rw_direct(func->card, 1, func->num, addr,
+ write_byte, &val);
+ if (err_ret)
+ *err_ret = ret;
+ if (ret)
+ val = 0xff;
+
+ return val;
+}
+EXPORT_SYMBOL_GPL(sdio_writeb_readb);
+
+/**
+ * sdio_memcpy_fromio - read a chunk of memory from a SDIO function
+ * @func: SDIO function to access
+ * @dst: buffer to store the data
+ * @addr: address to begin reading from
+ * @count: number of bytes to read
+ *
+ * Reads from the address space of a given SDIO function. Return
+ * value indicates if the transfer succeeded or not.
+ */
+int sdio_memcpy_fromio(struct sdio_func *func, void *dst,
+ unsigned int addr, int count)
+{
+ return sdio_io_rw_ext_helper(func, 0, addr, 1, dst, count);
+}
+EXPORT_SYMBOL_GPL(sdio_memcpy_fromio);
+
+/**
+ * sdio_memcpy_toio - write a chunk of memory to a SDIO function
+ * @func: SDIO function to access
+ * @addr: address to start writing to
+ * @src: buffer that contains the data to write
+ * @count: number of bytes to write
+ *
+ * Writes to the address space of a given SDIO function. Return
+ * value indicates if the transfer succeeded or not.
+ */
+int sdio_memcpy_toio(struct sdio_func *func, unsigned int addr,
+ void *src, int count)
+{
+ return sdio_io_rw_ext_helper(func, 1, addr, 1, src, count);
+}
+EXPORT_SYMBOL_GPL(sdio_memcpy_toio);
+
+/**
+ * sdio_readsb - read from a FIFO on a SDIO function
+ * @func: SDIO function to access
+ * @dst: buffer to store the data
+ * @addr: address of (single byte) FIFO
+ * @count: number of bytes to read
+ *
+ * Reads from the specified FIFO of a given SDIO function. Return
+ * value indicates if the transfer succeeded or not.
+ */
+int sdio_readsb(struct sdio_func *func, void *dst, unsigned int addr,
+ int count)
+{
+ return sdio_io_rw_ext_helper(func, 0, addr, 0, dst, count);
+}
+EXPORT_SYMBOL_GPL(sdio_readsb);
+
+/**
+ * sdio_writesb - write to a FIFO of a SDIO function
+ * @func: SDIO function to access
+ * @addr: address of (single byte) FIFO
+ * @src: buffer that contains the data to write
+ * @count: number of bytes to write
+ *
+ * Writes to the specified FIFO of a given SDIO function. Return
+ * value indicates if the transfer succeeded or not.
+ */
+int sdio_writesb(struct sdio_func *func, unsigned int addr, void *src,
+ int count)
+{
+ return sdio_io_rw_ext_helper(func, 1, addr, 0, src, count);
+}
+EXPORT_SYMBOL_GPL(sdio_writesb);
+
+/**
+ * sdio_readw - read a 16 bit integer from a SDIO function
+ * @func: SDIO function to access
+ * @addr: address to read
+ * @err_ret: optional status value from transfer
+ *
+ * Reads a 16 bit integer from the address space of a given SDIO
+ * function. If there is a problem reading the address, 0xffff
+ * is returned and @err_ret will contain the error code.
+ */
+u16 sdio_readw(struct sdio_func *func, unsigned int addr, int *err_ret)
+{
+ int ret;
+
+ if (err_ret)
+ *err_ret = 0;
+
+ ret = sdio_memcpy_fromio(func, func->tmpbuf, addr, 2);
+ if (ret) {
+ if (err_ret)
+ *err_ret = ret;
+ return 0xFFFF;
+ }
+
+ return le16_to_cpup((__le16 *)func->tmpbuf);
+}
+EXPORT_SYMBOL_GPL(sdio_readw);
+
+/**
+ * sdio_writew - write a 16 bit integer to a SDIO function
+ * @func: SDIO function to access
+ * @b: integer to write
+ * @addr: address to write to
+ * @err_ret: optional status value from transfer
+ *
+ * Writes a 16 bit integer to the address space of a given SDIO
+ * function. @err_ret will contain the status of the actual
+ * transfer.
+ */
+void sdio_writew(struct sdio_func *func, u16 b, unsigned int addr, int *err_ret)
+{
+ int ret;
+
+ *(__le16 *)func->tmpbuf = cpu_to_le16(b);
+
+ ret = sdio_memcpy_toio(func, addr, func->tmpbuf, 2);
+ if (err_ret)
+ *err_ret = ret;
+}
+EXPORT_SYMBOL_GPL(sdio_writew);
+
+/**
+ * sdio_readl - read a 32 bit integer from a SDIO function
+ * @func: SDIO function to access
+ * @addr: address to read
+ * @err_ret: optional status value from transfer
+ *
+ * Reads a 32 bit integer from the address space of a given SDIO
+ * function. If there is a problem reading the address,
+ * 0xffffffff is returned and @err_ret will contain the error
+ * code.
+ */
+u32 sdio_readl(struct sdio_func *func, unsigned int addr, int *err_ret)
+{
+ int ret;
+
+ if (err_ret)
+ *err_ret = 0;
+
+ ret = sdio_memcpy_fromio(func, func->tmpbuf, addr, 4);
+ if (ret) {
+ if (err_ret)
+ *err_ret = ret;
+ return 0xFFFFFFFF;
+ }
+
+ return le32_to_cpup((__le32 *)func->tmpbuf);
+}
+EXPORT_SYMBOL_GPL(sdio_readl);
+
+/**
+ * sdio_writel - write a 32 bit integer to a SDIO function
+ * @func: SDIO function to access
+ * @b: integer to write
+ * @addr: address to write to
+ * @err_ret: optional status value from transfer
+ *
+ * Writes a 32 bit integer to the address space of a given SDIO
+ * function. @err_ret will contain the status of the actual
+ * transfer.
+ */
+void sdio_writel(struct sdio_func *func, u32 b, unsigned int addr, int *err_ret)
+{
+ int ret;
+
+ *(__le32 *)func->tmpbuf = cpu_to_le32(b);
+
+ ret = sdio_memcpy_toio(func, addr, func->tmpbuf, 4);
+ if (err_ret)
+ *err_ret = ret;
+}
+EXPORT_SYMBOL_GPL(sdio_writel);
+
+/**
+ * sdio_f0_readb - read a single byte from SDIO function 0
+ * @func: an SDIO function of the card
+ * @addr: address to read
+ * @err_ret: optional status value from transfer
+ *
+ * Reads a single byte from the address space of SDIO function 0.
+ * If there is a problem reading the address, 0xff is returned
+ * and @err_ret will contain the error code.
+ */
+unsigned char sdio_f0_readb(struct sdio_func *func, unsigned int addr,
+ int *err_ret)
+{
+ int ret;
+ unsigned char val;
+
+ BUG_ON(!func);
+
+ if (err_ret)
+ *err_ret = 0;
+
+ ret = mmc_io_rw_direct(func->card, 0, 0, addr, 0, &val);
+ if (ret) {
+ if (err_ret)
+ *err_ret = ret;
+ return 0xFF;
+ }
+
+ return val;
+}
+EXPORT_SYMBOL_GPL(sdio_f0_readb);
+
+/**
+ * sdio_f0_writeb - write a single byte to SDIO function 0
+ * @func: an SDIO function of the card
+ * @b: byte to write
+ * @addr: address to write to
+ * @err_ret: optional status value from transfer
+ *
+ * Writes a single byte to the address space of SDIO function 0.
+ * @err_ret will contain the status of the actual transfer.
+ *
+ * Only writes to the vendor specific CCCR registers (0xF0 -
+ * 0xFF) are permiited; @err_ret will be set to -EINVAL for *
+ * writes outside this range.
+ */
+void sdio_f0_writeb(struct sdio_func *func, unsigned char b, unsigned int addr,
+ int *err_ret)
+{
+ int ret;
+
+ BUG_ON(!func);
+
+ if ((addr < 0xF0 || addr > 0xFF) && (!mmc_card_lenient_fn0(func->card))) {
+ if (err_ret)
+ *err_ret = -EINVAL;
+ return;
+ }
+
+ ret = mmc_io_rw_direct(func->card, 1, 0, addr, b, NULL);
+ if (err_ret)
+ *err_ret = ret;
+}
+EXPORT_SYMBOL_GPL(sdio_f0_writeb);
+
+/**
+ * sdio_get_host_pm_caps - get host power management capabilities
+ * @func: SDIO function attached to host
+ *
+ * Returns a capability bitmask corresponding to power management
+ * features supported by the host controller that the card function
+ * might rely upon during a system suspend. The host doesn't need
+ * to be claimed, nor the function active, for this information to be
+ * obtained.
+ */
+mmc_pm_flag_t sdio_get_host_pm_caps(struct sdio_func *func)
+{
+ BUG_ON(!func);
+ BUG_ON(!func->card);
+
+ return func->card->host->pm_caps;
+}
+EXPORT_SYMBOL_GPL(sdio_get_host_pm_caps);
+
+/**
+ * sdio_set_host_pm_flags - set wanted host power management capabilities
+ * @func: SDIO function attached to host
+ *
+ * Set a capability bitmask corresponding to wanted host controller
+ * power management features for the upcoming suspend state.
+ * This must be called, if needed, each time the suspend method of
+ * the function driver is called, and must contain only bits that
+ * were returned by sdio_get_host_pm_caps().
+ * The host doesn't need to be claimed, nor the function active,
+ * for this information to be set.
+ */
+int sdio_set_host_pm_flags(struct sdio_func *func, mmc_pm_flag_t flags)
+{
+ struct mmc_host *host;
+
+ BUG_ON(!func);
+ BUG_ON(!func->card);
+
+ host = func->card->host;
+
+ if (flags & ~host->pm_caps)
+ return -EINVAL;
+
+ /* function suspend methods are serialized, hence no lock needed */
+ host->pm_flags |= flags;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(sdio_set_host_pm_flags);
diff --git a/drivers/mmc/core/sdio_irq.c b/drivers/mmc/core/sdio_irq.c
new file mode 100644
index 00000000..d58ae915
--- /dev/null
+++ b/drivers/mmc/core/sdio_irq.c
@@ -0,0 +1,307 @@
+/*
+ * linux/drivers/mmc/core/sdio_irq.c
+ *
+ * Author: Nicolas Pitre
+ * Created: June 18, 2007
+ * Copyright: MontaVista Software Inc.
+ *
+ * Copyright 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
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/kthread.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+
+#include <linux/mmc/core.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/sdio_func.h>
+
+#include "sdio_ops.h"
+
+static int process_sdio_pending_irqs(struct mmc_host *host)
+{
+ struct mmc_card *card = host->card;
+ int i, ret, count;
+ unsigned char pending;
+ struct sdio_func *func;
+
+ /*
+ * Optimization, if there is only 1 function interrupt registered
+ * and we know an IRQ was signaled then call irq handler directly.
+ * Otherwise do the full probe.
+ */
+ func = card->sdio_single_irq;
+ if (func && host->sdio_irq_pending) {
+ func->irq_handler(func);
+ return 1;
+ }
+
+ ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_INTx, 0, &pending);
+ if (ret) {
+ printk(KERN_DEBUG "%s: error %d reading SDIO_CCCR_INTx\n",
+ mmc_card_id(card), ret);
+ return ret;
+ }
+
+ count = 0;
+ for (i = 1; i <= 7; i++) {
+ if (pending & (1 << i)) {
+ func = card->sdio_func[i - 1];
+ if (!func) {
+ printk(KERN_WARNING "%s: pending IRQ for "
+ "non-existent function\n",
+ mmc_card_id(card));
+ ret = -EINVAL;
+ } else if (func->irq_handler) {
+ func->irq_handler(func);
+ count++;
+ } else {
+ printk(KERN_WARNING "%s: pending IRQ with no handler\n",
+ sdio_func_id(func));
+ ret = -EINVAL;
+ }
+ }
+ }
+
+ if (count)
+ return count;
+
+ return ret;
+}
+
+static int sdio_irq_thread(void *_host)
+{
+ struct mmc_host *host = _host;
+ struct sched_param param = { .sched_priority = 1 };
+ unsigned long period, idle_period;
+ int ret;
+
+ sched_setscheduler(current, SCHED_FIFO, &param);
+
+ /*
+ * We want to allow for SDIO cards to work even on non SDIO
+ * aware hosts. One thing that non SDIO host cannot do is
+ * asynchronous notification of pending SDIO card interrupts
+ * hence we poll for them in that case.
+ */
+ idle_period = msecs_to_jiffies(10);
+ period = (host->caps & MMC_CAP_SDIO_IRQ) ?
+ MAX_SCHEDULE_TIMEOUT : idle_period;
+
+ pr_debug("%s: IRQ thread started (poll period = %lu jiffies)\n",
+ mmc_hostname(host), period);
+
+ do {
+ /*
+ * We claim the host here on drivers behalf for a couple
+ * reasons:
+ *
+ * 1) it is already needed to retrieve the CCCR_INTx;
+ * 2) we want the driver(s) to clear the IRQ condition ASAP;
+ * 3) we need to control the abort condition locally.
+ *
+ * Just like traditional hard IRQ handlers, we expect SDIO
+ * IRQ handlers to be quick and to the point, so that the
+ * holding of the host lock does not cover too much work
+ * that doesn't require that lock to be held.
+ */
+ ret = __mmc_claim_host(host, &host->sdio_irq_thread_abort);
+ if (ret)
+ break;
+ ret = process_sdio_pending_irqs(host);
+ host->sdio_irq_pending = false;
+ mmc_release_host(host);
+
+ /*
+ * Give other threads a chance to run in the presence of
+ * errors.
+ */
+ if (ret < 0) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (!kthread_should_stop())
+ schedule_timeout(HZ);
+ set_current_state(TASK_RUNNING);
+ }
+
+ /*
+ * Adaptive polling frequency based on the assumption
+ * that an interrupt will be closely followed by more.
+ * This has a substantial benefit for network devices.
+ */
+ if (!(host->caps & MMC_CAP_SDIO_IRQ)) {
+ if (ret > 0)
+ period /= 2;
+ else {
+ period++;
+ if (period > idle_period)
+ period = idle_period;
+ }
+ }
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (host->caps & MMC_CAP_SDIO_IRQ)
+ host->ops->enable_sdio_irq(host, 1);
+ if (!kthread_should_stop())
+ schedule_timeout(period);
+ set_current_state(TASK_RUNNING);
+ } while (!kthread_should_stop());
+
+ if (host->caps & MMC_CAP_SDIO_IRQ)
+ host->ops->enable_sdio_irq(host, 0);
+
+ pr_debug("%s: IRQ thread exiting with code %d\n",
+ mmc_hostname(host), ret);
+
+ return ret;
+}
+
+static int sdio_card_irq_get(struct mmc_card *card)
+{
+ struct mmc_host *host = card->host;
+
+ WARN_ON(!host->claimed);
+
+ if (!host->sdio_irqs++) {
+ atomic_set(&host->sdio_irq_thread_abort, 0);
+ host->sdio_irq_thread =
+ kthread_run(sdio_irq_thread, host, "ksdioirqd/%s",
+ mmc_hostname(host));
+ if (IS_ERR(host->sdio_irq_thread)) {
+ int err = PTR_ERR(host->sdio_irq_thread);
+ host->sdio_irqs--;
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static int sdio_card_irq_put(struct mmc_card *card)
+{
+ struct mmc_host *host = card->host;
+
+ WARN_ON(!host->claimed);
+ BUG_ON(host->sdio_irqs < 1);
+
+ if (!--host->sdio_irqs) {
+ atomic_set(&host->sdio_irq_thread_abort, 1);
+ kthread_stop(host->sdio_irq_thread);
+ }
+
+ return 0;
+}
+
+/* If there is only 1 function registered set sdio_single_irq */
+static void sdio_single_irq_set(struct mmc_card *card)
+{
+ struct sdio_func *func;
+ int i;
+
+ card->sdio_single_irq = NULL;
+ if ((card->host->caps & MMC_CAP_SDIO_IRQ) &&
+ card->host->sdio_irqs == 1)
+ for (i = 0; i < card->sdio_funcs; i++) {
+ func = card->sdio_func[i];
+ if (func && func->irq_handler) {
+ card->sdio_single_irq = func;
+ break;
+ }
+ }
+}
+
+/**
+ * sdio_claim_irq - claim the IRQ for a SDIO function
+ * @func: SDIO function
+ * @handler: IRQ handler callback
+ *
+ * Claim and activate the IRQ for the given SDIO function. The provided
+ * handler will be called when that IRQ is asserted. The host is always
+ * claimed already when the handler is called so the handler must not
+ * call sdio_claim_host() nor sdio_release_host().
+ */
+int sdio_claim_irq(struct sdio_func *func, sdio_irq_handler_t *handler)
+{
+ int ret;
+ unsigned char reg;
+
+ BUG_ON(!func);
+ BUG_ON(!func->card);
+
+ pr_debug("SDIO: Enabling IRQ for %s...\n", sdio_func_id(func));
+
+ if (func->irq_handler) {
+ pr_debug("SDIO: IRQ for %s already in use.\n", sdio_func_id(func));
+ return -EBUSY;
+ }
+
+ ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IENx, 0, &reg);
+ if (ret)
+ return ret;
+
+ reg |= 1 << func->num;
+
+ reg |= 1; /* Master interrupt enable */
+
+ ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, reg, NULL);
+ if (ret)
+ return ret;
+
+ func->irq_handler = handler;
+ ret = sdio_card_irq_get(func->card);
+ if (ret)
+ func->irq_handler = NULL;
+ sdio_single_irq_set(func->card);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(sdio_claim_irq);
+
+/**
+ * sdio_release_irq - release the IRQ for a SDIO function
+ * @func: SDIO function
+ *
+ * Disable and release the IRQ for the given SDIO function.
+ */
+int sdio_release_irq(struct sdio_func *func)
+{
+ int ret;
+ unsigned char reg;
+
+ BUG_ON(!func);
+ BUG_ON(!func->card);
+
+ pr_debug("SDIO: Disabling IRQ for %s...\n", sdio_func_id(func));
+
+ if (func->irq_handler) {
+ func->irq_handler = NULL;
+ sdio_card_irq_put(func->card);
+ sdio_single_irq_set(func->card);
+ }
+
+ ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IENx, 0, &reg);
+ if (ret)
+ return ret;
+
+ reg &= ~(1 << func->num);
+
+ /* Disable master interrupt with the last function interrupt */
+ if (!(reg & 0xFE))
+ reg = 0;
+
+ ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, reg, NULL);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(sdio_release_irq);
+
diff --git a/drivers/mmc/core/sdio_ops.c b/drivers/mmc/core/sdio_ops.c
new file mode 100644
index 00000000..f087d876
--- /dev/null
+++ b/drivers/mmc/core/sdio_ops.c
@@ -0,0 +1,200 @@
+/*
+ * linux/drivers/mmc/sdio_ops.c
+ *
+ * Copyright 2006-2007 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
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ */
+
+#include <linux/scatterlist.h>
+
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/sdio.h>
+
+#include "core.h"
+#include "sdio_ops.h"
+
+int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)
+{
+ struct mmc_command cmd = {0};
+ int i, err = 0;
+
+ BUG_ON(!host);
+
+ cmd.opcode = SD_IO_SEND_OP_COND;
+ cmd.arg = ocr;
+ cmd.flags = MMC_RSP_SPI_R4 | MMC_RSP_R4 | MMC_CMD_BCR;
+
+ for (i = 100; i; i--) {
+ err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
+ if (err)
+ break;
+
+ /* if we're just probing, do a single pass */
+ if (ocr == 0)
+ break;
+
+ /* otherwise wait until reset completes */
+ if (mmc_host_is_spi(host)) {
+ /*
+ * Both R1_SPI_IDLE and MMC_CARD_BUSY indicate
+ * an initialized card under SPI, but some cards
+ * (Marvell's) only behave when looking at this
+ * one.
+ */
+ if (cmd.resp[1] & MMC_CARD_BUSY)
+ break;
+ } else {
+ if (cmd.resp[0] & MMC_CARD_BUSY)
+ break;
+ }
+
+ err = -ETIMEDOUT;
+
+ mmc_delay(10);
+ }
+
+ if (rocr)
+ *rocr = cmd.resp[mmc_host_is_spi(host) ? 1 : 0];
+
+ return err;
+}
+
+static int mmc_io_rw_direct_host(struct mmc_host *host, int write, unsigned fn,
+ unsigned addr, u8 in, u8 *out)
+{
+ struct mmc_command cmd = {0};
+ int err;
+
+ BUG_ON(!host);
+ BUG_ON(fn > 7);
+
+ /* sanity check */
+ if (addr & ~0x1FFFF)
+ return -EINVAL;
+
+ cmd.opcode = SD_IO_RW_DIRECT;
+ cmd.arg = write ? 0x80000000 : 0x00000000;
+ cmd.arg |= fn << 28;
+ cmd.arg |= (write && out) ? 0x08000000 : 0x00000000;
+ cmd.arg |= addr << 9;
+ cmd.arg |= in;
+ cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_AC;
+
+ err = mmc_wait_for_cmd(host, &cmd, 0);
+ if (err)
+ return err;
+
+ if (mmc_host_is_spi(host)) {
+ /* host driver already reported errors */
+ } else {
+ if (cmd.resp[0] & R5_ERROR)
+ return -EIO;
+ if (cmd.resp[0] & R5_FUNCTION_NUMBER)
+ return -EINVAL;
+ if (cmd.resp[0] & R5_OUT_OF_RANGE)
+ return -ERANGE;
+ }
+
+ if (out) {
+ if (mmc_host_is_spi(host))
+ *out = (cmd.resp[0] >> 8) & 0xFF;
+ else
+ *out = cmd.resp[0] & 0xFF;
+ }
+
+ return 0;
+}
+
+int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn,
+ unsigned addr, u8 in, u8 *out)
+{
+ BUG_ON(!card);
+ return mmc_io_rw_direct_host(card->host, write, fn, addr, in, out);
+}
+
+int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn,
+ unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz)
+{
+ struct mmc_request mrq = {0};
+ struct mmc_command cmd = {0};
+ struct mmc_data data = {0};
+ struct scatterlist sg;
+
+ BUG_ON(!card);
+ BUG_ON(fn > 7);
+ BUG_ON(blocks == 1 && blksz > 512);
+ WARN_ON(blocks == 0);
+ WARN_ON(blksz == 0);
+
+ /* sanity check */
+ if (addr & ~0x1FFFF)
+ return -EINVAL;
+
+ mrq.cmd = &cmd;
+ mrq.data = &data;
+
+ cmd.opcode = SD_IO_RW_EXTENDED;
+ cmd.arg = write ? 0x80000000 : 0x00000000;
+ cmd.arg |= fn << 28;
+ cmd.arg |= incr_addr ? 0x04000000 : 0x00000000;
+ cmd.arg |= addr << 9;
+ if (blocks == 1 && blksz <= 512)
+ cmd.arg |= (blksz == 512) ? 0 : blksz; /* byte mode */
+ else
+ cmd.arg |= 0x08000000 | blocks; /* block mode */
+ cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC;
+
+ 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, buf, blksz * blocks);
+
+ mmc_set_data_timeout(&data, card);
+
+ mmc_wait_for_req(card->host, &mrq);
+
+ if (cmd.error)
+ return cmd.error;
+ if (data.error)
+ return data.error;
+
+ if (mmc_host_is_spi(card->host)) {
+ /* host driver already reported errors */
+ } else {
+ if (cmd.resp[0] & R5_ERROR)
+ return -EIO;
+ if (cmd.resp[0] & R5_FUNCTION_NUMBER)
+ return -EINVAL;
+ if (cmd.resp[0] & R5_OUT_OF_RANGE)
+ return -ERANGE;
+ }
+
+ return 0;
+}
+
+int sdio_reset(struct mmc_host *host)
+{
+ int ret;
+ u8 abort;
+
+ /* SDIO Simplified Specification V2.0, 4.4 Reset for SDIO */
+
+ ret = mmc_io_rw_direct_host(host, 0, 0, SDIO_CCCR_ABORT, 0, &abort);
+ if (ret)
+ abort = 0x08;
+ else
+ abort |= 0x08;
+
+ ret = mmc_io_rw_direct_host(host, 1, 0, SDIO_CCCR_ABORT, abort, NULL);
+ return ret;
+}
+
diff --git a/drivers/mmc/core/sdio_ops.h b/drivers/mmc/core/sdio_ops.h
new file mode 100644
index 00000000..12a4d3ab
--- /dev/null
+++ b/drivers/mmc/core/sdio_ops.h
@@ -0,0 +1,23 @@
+/*
+ * linux/drivers/mmc/sdio_ops.c
+ *
+ * Copyright 2006-2007 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
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ */
+
+#ifndef _MMC_SDIO_OPS_H
+#define _MMC_SDIO_OPS_H
+
+int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr);
+int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn,
+ unsigned addr, u8 in, u8* out);
+int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn,
+ unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz);
+int sdio_reset(struct mmc_host *host);
+
+#endif
+
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
new file mode 100644
index 00000000..d86cb895
--- /dev/null
+++ b/drivers/mmc/host/Kconfig
@@ -0,0 +1,587 @@
+#
+# MMC/SD host controller drivers
+#
+
+comment "MMC/SD/SDIO Host Controller Drivers"
+
+config MMC_ARMMMCI
+ tristate "ARM AMBA Multimedia Card Interface support"
+ depends on ARM_AMBA
+ help
+ This selects the ARM(R) AMBA(R) PrimeCell Multimedia Card
+ Interface (PL180 and PL181) support. If you have an ARM(R)
+ platform with a Multimedia Card slot, say Y or M here.
+
+ If unsure, say N.
+
+config MMC_PXA
+ tristate "Intel PXA25x/26x/27x Multimedia Card Interface support"
+ depends on ARCH_PXA
+ help
+ This selects the Intel(R) PXA(R) Multimedia card Interface.
+ If you have a PXA(R) platform with a Multimedia Card slot,
+ say Y or M here.
+
+ If unsure, say N.
+
+config MMC_SDHCI
+ tristate "Secure Digital Host Controller Interface support"
+ depends on HAS_DMA
+ help
+ 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_IO_ACCESSORS
+ bool
+ depends on MMC_SDHCI
+ help
+ This is silent Kconfig symbol that is selected by the drivers that
+ need to overwrite SDHCI IO memory accessors.
+
+config MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER
+ bool
+ select MMC_SDHCI_IO_ACCESSORS
+ help
+ This option is selected by drivers running on big endian hosts
+ and performing I/O to a SDHCI controller through a bus that
+ implements a hardware byte swapper using a 32-bit datum.
+ This endian mapping mode is called "data invariance" and
+ has the effect of scrambling the addresses and formats of data
+ accessed in sizes other than the datum size.
+
+ This is the case for the Freescale eSDHC and Nintendo Wii SDHCI.
+
+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
+ bool "Ricoh MMC Controller Disabler (EXPERIMENTAL)"
+ depends on MMC_SDHCI_PCI
+ help
+ This adds a pci quirk to disable Ricoh MMC Controller. This
+ proprietary controller is unnecessary because the SDHCI driver
+ supports MMC cards on the SD controller, but if it is not
+ disabled, it will steal the MMC cards away - rendering them
+ useless. It is safe to select this even if you don't
+ have a Ricoh based card reader.
+
+ If unsure, say Y.
+
+config MMC_SDHCI_OF
+ tristate "SDHCI support on OpenFirmware platforms"
+ depends on MMC_SDHCI && OF
+ help
+ This selects the OF support for Secure Digital Host Controller
+ Interfaces.
+
+ If unsure, say N.
+
+config MMC_SDHCI_OF_ESDHC
+ bool "SDHCI OF support for the Freescale eSDHC controller"
+ depends on MMC_SDHCI_OF
+ depends on PPC_OF
+ select MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER
+ help
+ This selects the Freescale eSDHC controller support.
+
+ If unsure, say N.
+
+config MMC_SDHCI_OF_HLWD
+ bool "SDHCI OF support for the Nintendo Wii SDHCI controllers"
+ depends on MMC_SDHCI_OF
+ depends on PPC_OF
+ select MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER
+ help
+ This selects the Secure Digital Host Controller Interface (SDHCI)
+ found in the "Hollywood" chipset of the Nintendo Wii video game
+ console.
+
+ If unsure, say N.
+
+config MMC_SDHCI_PLTFM
+ tristate "SDHCI support on the platform specific bus"
+ depends on MMC_SDHCI
+ help
+ This selects the platform specific bus support for Secure Digital Host
+ Controller Interface.
+
+ If you have a controller with this interface, say Y or M here.
+
+ If unsure, say N.
+
+config MMC_SDHCI_CNS3XXX
+ bool "SDHCI support on the Cavium Networks CNS3xxx SoC"
+ depends on ARCH_CNS3XXX
+ depends on MMC_SDHCI_PLTFM
+ help
+ This selects the SDHCI support for CNS3xxx System-on-Chip devices.
+
+ If unsure, say N.
+
+config MMC_SDHCI_ESDHC_IMX
+ bool "SDHCI platform support for the Freescale eSDHC i.MX controller"
+ depends on MMC_SDHCI_PLTFM && (ARCH_MX25 || ARCH_MX35 || ARCH_MX5 || ARCH_MX6)
+ select MMC_SDHCI_IO_ACCESSORS
+ help
+ This selects the Freescale eSDHC controller support on the platform
+ bus, found on platforms like mx35/51.
+
+ If unsure, say N.
+
+config MMC_SDHCI_DOVE
+ bool "SDHCI support on Marvell's Dove SoC"
+ depends on ARCH_DOVE
+ depends on MMC_SDHCI_PLTFM
+ select MMC_SDHCI_IO_ACCESSORS
+ help
+ This selects the Secure Digital Host Controller Interface in
+ Marvell's Dove SoC.
+
+ If unsure, say N.
+
+config MMC_SDHCI_TEGRA
+ bool "SDHCI platform support for the Tegra SD/MMC Controller"
+ depends on MMC_SDHCI_PLTFM && ARCH_TEGRA
+ select MMC_SDHCI_IO_ACCESSORS
+ help
+ This selects the Tegra SD/MMC controller. If you have a Tegra
+ platform with SD or MMC devices, say Y or M here.
+
+ If unsure, say N.
+
+config MMC_SDHCI_S3C
+ tristate "SDHCI support on Samsung S3C SoC"
+ depends on MMC_SDHCI && PLAT_SAMSUNG
+ help
+ This selects the Secure Digital Host Controller Interface (SDHCI)
+ often referrered to as the HSMMC block in some of the Samsung S3C
+ range of SoC.
+
+ Note, due to the problems with DMA, the DMA support is only
+ available with CONFIG_EXPERIMENTAL is selected.
+
+ If you have a controller with this interface, say Y or M here.
+
+ If unsure, say N.
+
+config MMC_SDHCI_PXA
+ tristate "Marvell PXA168/PXA910/MMP2 SD Host Controller support"
+ depends on ARCH_PXA || ARCH_MMP
+ select MMC_SDHCI
+ select MMC_SDHCI_IO_ACCESSORS
+ help
+ This selects the Marvell(R) PXA168/PXA910/MMP2 SD Host Controller.
+ If you have a PXA168/PXA910/MMP2 platform with SD Host Controller
+ and a card slot, say Y or M here.
+
+ If unsure, say N.
+
+config MMC_SDHCI_SPEAR
+ tristate "SDHCI support on ST SPEAr platform"
+ depends on MMC_SDHCI && PLAT_SPEAR
+ help
+ This selects the Secure Digital Host Controller Interface (SDHCI)
+ often referrered to as the HSMMC block in some of the ST SPEAR range
+ of SoC
+
+ If you have a controller with this interface, say Y or M here.
+
+ If unsure, say N.
+
+config MMC_SDHCI_S3C_DMA
+ bool "DMA support on S3C SDHCI"
+ depends on MMC_SDHCI_S3C && EXPERIMENTAL
+ help
+ Enable DMA support on the Samsung S3C SDHCI glue. The DMA
+ has proved to be problematic if the controller encounters
+ certain errors, and thus should be treated with care.
+
+ YMMV.
+
+config MMC_OMAP
+ tristate "TI OMAP Multimedia Card Interface support"
+ depends on ARCH_OMAP
+ select TPS65010 if MACH_OMAP_H2
+ help
+ This selects the TI OMAP Multimedia card Interface.
+ If you have an OMAP board with a Multimedia Card slot,
+ say Y or M here.
+
+ If unsure, say N.
+
+config MMC_OMAP_HS
+ tristate "TI OMAP High Speed Multimedia Card Interface support"
+ depends on SOC_OMAP2430 || ARCH_OMAP3 || ARCH_OMAP4
+ help
+ This selects the TI OMAP High Speed Multimedia card Interface.
+ If you have an OMAP2430 or OMAP3 board or OMAP4 board with a
+ Multimedia Card slot, say Y or M here.
+
+ If unsure, say N.
+
+config MMC_WBSD
+ tristate "Winbond W83L51xD SD/MMC Card Interface support"
+ depends on ISA_DMA_API
+ help
+ This selects the Winbond(R) W83L51xD Secure digital and
+ Multimedia card Interface.
+ If you have a machine with a integrated W83L518D or W83L519D
+ SD/MMC card reader, say Y or M here.
+
+ If unsure, say N.
+
+config MMC_AU1X
+ tristate "Alchemy AU1XX0 MMC Card Interface support"
+ depends on SOC_AU1200
+ help
+ This selects the AMD Alchemy(R) Multimedia card interface.
+ If you have a Alchemy platform with a MMC slot, say Y or M here.
+
+ If unsure, say N.
+
+choice
+ prompt "Atmel SD/MMC Driver"
+ depends on AVR32 || ARCH_AT91
+ default MMC_ATMELMCI if AVR32
+ help
+ Choose which driver to use for the Atmel MCI Silicon
+
+config MMC_AT91
+ tristate "AT91 SD/MMC Card Interface support"
+ depends on ARCH_AT91
+ help
+ This selects the AT91 MCI controller.
+
+ If unsure, say N.
+
+config MMC_ATMELMCI
+ tristate "Atmel Multimedia Card Interface support"
+ depends on AVR32 || ARCH_AT91
+ help
+ This selects the Atmel Multimedia Card Interface driver. If
+ you have an AT32 (AVR32) or AT91 platform with a Multimedia
+ Card slot, say Y or M here.
+
+ If unsure, say N.
+
+endchoice
+
+config MMC_ATMELMCI_DMA
+ bool "Atmel MCI DMA support (EXPERIMENTAL)"
+ depends on MMC_ATMELMCI && (AVR32 || ARCH_AT91SAM9G45) && DMA_ENGINE && EXPERIMENTAL
+ help
+ Say Y here to have the Atmel MCI driver use a DMA engine to
+ do data transfers and thus increase the throughput and
+ reduce the CPU utilization. Note that this is highly
+ experimental and may cause the driver to lock up.
+
+ If unsure, say N.
+
+config MMC_IMX
+ tristate "Motorola i.MX Multimedia Card Interface support"
+ depends on ARCH_MX1
+ help
+ This selects the Motorola i.MX Multimedia card Interface.
+ If you have a i.MX platform with a Multimedia Card slot,
+ say Y or M here.
+
+ If unsure, say N.
+
+config MMC_MSM
+ tristate "Qualcomm SDCC Controller Support"
+ depends on MMC && ARCH_MSM
+ help
+ This provides support for the SD/MMC cell found in the
+ MSM and QSD SOCs from Qualcomm. The controller also has
+ support for SDIO devices.
+
+config MMC_MXC
+ tristate "Freescale i.MX2/3 Multimedia Card Interface support"
+ depends on MACH_MX21 || MACH_MX27 || ARCH_MX31
+ help
+ This selects the Freescale i.MX2/3 Multimedia card Interface.
+ If you have a i.MX platform with a Multimedia Card slot,
+ say Y or M here.
+
+ If unsure, say N.
+
+config MMC_MXS
+ tristate "Freescale MXS Multimedia Card Interface support"
+ depends on ARCH_MXS && MXS_DMA
+ help
+ This selects the Freescale SSP MMC controller found on MXS based
+ platforms like mx23/28.
+
+ If unsure, say N.
+
+config MMC_TIFM_SD
+ tristate "TI Flash Media MMC/SD Interface support (EXPERIMENTAL)"
+ depends on EXPERIMENTAL && PCI
+ select TIFM_CORE
+ help
+ Say Y here if you want to be able to access MMC/SD cards with
+ the Texas Instruments(R) Flash Media card reader, found in many
+ laptops.
+ This option 'selects' (turns on, enables) 'TIFM_CORE', but you
+ probably also need appropriate card reader host adapter, such as
+ 'Misc devices: TI Flash Media PCI74xx/PCI76xx host adapter support
+ (TIFM_7XX1)'.
+
+ To compile this driver as a module, choose M here: the
+ module will be called tifm_sd.
+
+config MMC_MVSDIO
+ tristate "Marvell MMC/SD/SDIO host driver"
+ depends on PLAT_ORION
+ ---help---
+ This selects the Marvell SDIO host driver.
+ SDIO may currently be found on the Kirkwood 88F6281 and 88F6192
+ SoC controllers.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mvsdio.
+
+config MMC_DAVINCI
+ tristate "TI DAVINCI Multimedia Card Interface support"
+ depends on ARCH_DAVINCI
+ help
+ This selects the TI DAVINCI Multimedia card Interface.
+ If you have an DAVINCI board with a Multimedia Card slot,
+ say Y or M here. If unsure, say N.
+
+config MMC_SPI
+ tristate "MMC/SD/SDIO over SPI"
+ depends on SPI_MASTER && !HIGHMEM && HAS_DMA
+ select CRC7
+ select CRC_ITU_T
+ help
+ Some systems access MMC/SD/SDIO cards using a SPI controller
+ instead of using a "native" MMC/SD/SDIO controller. This has a
+ disadvantage of being relatively high overhead, but a compensating
+ advantage of working on many systems without dedicated MMC/SD/SDIO
+ controllers.
+
+ 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
+ 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_S3C_HW_SDIO_IRQ
+ bool "Hardware support for SDIO IRQ"
+ depends on MMC_S3C
+ help
+ Enable the hardware support for SDIO interrupts instead of using
+ the generic polling code.
+
+choice
+ prompt "Samsung S3C SD/MMC transfer code"
+ depends on MMC_S3C
+
+config MMC_S3C_PIO
+ bool "Use PIO transfers only"
+ help
+ Use PIO to transfer data between memory and the hardware.
+
+ PIO is slower than DMA as it requires CPU instructions to
+ move the data. This has been the traditional default for
+ the S3C MCI driver.
+
+config MMC_S3C_DMA
+ bool "Use DMA transfers only (EXPERIMENTAL)"
+ depends on EXPERIMENTAL
+ help
+ Use DMA to transfer data between memory and the hardare.
+
+ Currently, the DMA support in this driver seems to not be
+ working properly and needs to be debugged before this
+ option is useful.
+
+config MMC_S3C_PIODMA
+ bool "Support for both PIO and DMA (EXPERIMENTAL)"
+ help
+ Compile both the PIO and DMA transfer routines into the
+ driver and let the platform select at run-time which one
+ is best.
+
+ See notes for the DMA option.
+
+endchoice
+
+config MMC_SDRICOH_CS
+ tristate "MMC/SD driver for Ricoh Bay1Controllers (EXPERIMENTAL)"
+ depends on EXPERIMENTAL && 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.
+
+config MMC_TMIO_CORE
+ tristate
+
+config MMC_TMIO
+ tristate "Toshiba Mobile IO Controller (TMIO) MMC/SD function support"
+ depends on MFD_TMIO || MFD_ASIC3
+ select MMC_TMIO_CORE
+ help
+ This provides support for the SD/MMC cell found in TC6393XB,
+ T7L66XB and also HTC ASIC3
+
+config MMC_SDHI
+ tristate "SH-Mobile SDHI SD/SDIO controller support"
+ depends on SUPERH || ARCH_SHMOBILE
+ select MMC_TMIO_CORE
+ help
+ This provides support for the SDHI SD/SDIO controller found in
+ SuperH and ARM SH-Mobile SoCs
+
+config MMC_CB710
+ tristate "ENE CB710 MMC/SD Interface support"
+ depends on PCI
+ select MISC_DEVICES
+ select CB710_CORE
+ help
+ This option enables support for MMC/SD part of ENE CB710/720 Flash
+ memory card reader found in some laptops (ie. some versions of
+ HP Compaq nx9500).
+
+ This driver can also be built as a module. If so, the module
+ will be called cb710-mmc.
+
+config MMC_VIA_SDMMC
+ tristate "VIA SD/MMC Card Reader Driver"
+ depends on PCI
+ help
+ This selects the VIA SD/MMC Card Reader driver, say Y or M here.
+ VIA provides one multi-functional card reader which integrated into
+ some motherboards manufactured by VIA. This card reader supports
+ SD/MMC/SDHC.
+ If you have a controller with this interface, say Y or M here.
+
+ If unsure, say N.
+
+config SDH_BFIN
+ tristate "Blackfin Secure Digital Host support"
+ depends on (BF54x && !BF544) || (BF51x && !BF512)
+ help
+ If you say yes here you will get support for the Blackfin on-chip
+ Secure Digital Host interface. This includes support for MMC and
+ SD cards.
+
+ To compile this driver as a module, choose M here: the
+ module will be called bfin_sdh.
+
+ If unsure, say N.
+
+config SDH_BFIN_MISSING_CMD_PULLUP_WORKAROUND
+ bool "Blackfin EZkit Missing SDH_CMD Pull Up Resistor Workaround"
+ depends on SDH_BFIN
+ help
+ If you say yes here SD-Cards may work on the EZkit.
+
+config MMC_DW
+ tristate "Synopsys DesignWare Memory Card Interface"
+ depends on ARM
+ help
+ This selects support for the Synopsys DesignWare Mobile Storage IP
+ block, this provides host support for SD and MMC interfaces, in both
+ PIO and external DMA modes.
+
+config MMC_DW_IDMAC
+ bool "Internal DMAC interface"
+ depends on MMC_DW
+ help
+ This selects support for the internal DMAC block within the Synopsys
+ Designware Mobile Storage IP block. This disables the external DMA
+ interface.
+
+config MMC_SH_MMCIF
+ tristate "SuperH Internal MMCIF support"
+ depends on MMC_BLOCK && (SUPERH || ARCH_SHMOBILE)
+ help
+ This selects the MMC Host Interface controller (MMCIF).
+
+ This driver supports MMCIF in sh7724/sh7757/sh7372.
+
+config MMC_JZ4740
+ tristate "JZ4740 SD/Multimedia Card Interface support"
+ depends on MACH_JZ4740
+ help
+ This selects support for the SD/MMC controller on Ingenic JZ4740
+ SoCs.
+ If you have a board based on such a SoC and with a SD/MMC slot,
+ say Y or M here.
+
+config MMC_VUB300
+ tristate "VUB300 USB to SDIO/SD/MMC Host Controller support"
+ depends on USB
+ help
+ This selects support for Elan Digital Systems' VUB300 chip.
+
+ The VUB300 is a USB-SDIO Host Controller Interface chip
+ that enables the host computer to use SDIO/SD/MMC cards
+ via a USB 2.0 or USB 1.1 host.
+
+ The VUB300 chip will be found in both physically separate
+ USB to SDIO/SD/MMC adapters and embedded on some motherboards.
+
+ The VUB300 chip supports SD and MMC memory cards in addition
+ to single and multifunction SDIO cards.
+
+ Some SDIO cards will need a firmware file to be loaded and
+ sent to VUB300 chip in order to achieve better data throughput.
+ Download these "Offload Pseudocode" from Elan Digital Systems'
+ web-site http://www.elandigitalsystems.com/support/downloads.php
+ and put them in /lib/firmware. Note that without these additional
+ firmware files the VUB300 chip will still function, but not at
+ the best obtainable data rate.
+
+ To compile this mmc host controller driver as a module,
+ choose M here: the module will be called vub300.
+
+ If you have a computer with an embedded VUB300 chip
+ or if you intend connecting a USB adapter based on a
+ VUB300 chip say Y or M here.
+
+config MMC_USHC
+ tristate "USB SD Host Controller (USHC) support"
+ depends on USB
+ help
+ This selects support for USB SD Host Controllers based on
+ the Cypress Astoria chip with firmware compliant with CSR's
+ USB SD Host Controller specification (CS-118793-SP).
+
+ CSR boards with this device include: USB<>SDIO (M1985v2),
+ and Ultrasira.
+
+ Note: These controllers only support SDIO cards and do not
+ support MMC or SD memory cards.
+config SDHCI_USE_LEDS_CLASS
+ tristate "Support LEDs indicators for SDHC HOST"
+ depends on MMC_SDHCI && LEDS_CLASS
+ help
+ This selects support for SDHCI HOST Controllers use LED Class
+ to control the status of LEDs
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
new file mode 100644
index 00000000..58a5cf73
--- /dev/null
+++ b/drivers/mmc/host/Makefile
@@ -0,0 +1,61 @@
+#
+# Makefile for MMC/SD host controller drivers
+#
+
+obj-$(CONFIG_MMC_ARMMMCI) += mmci.o
+obj-$(CONFIG_MMC_PXA) += pxamci.o
+obj-$(CONFIG_MMC_IMX) += imxmmc.o
+obj-$(CONFIG_MMC_MXC) += mxcmmc.o
+obj-$(CONFIG_MMC_MXS) += mxs-mmc.o
+obj-$(CONFIG_MMC_SDHCI) += sdhci.o
+obj-$(CONFIG_MMC_SDHCI_PCI) += sdhci-pci.o
+obj-$(CONFIG_MMC_SDHCI_PXA) += sdhci-pxa.o
+obj-$(CONFIG_MMC_SDHCI_S3C) += sdhci-s3c.o
+obj-$(CONFIG_MMC_SDHCI_SPEAR) += sdhci-spear.o
+obj-$(CONFIG_MMC_WBSD) += wbsd.o
+obj-$(CONFIG_MMC_AU1X) += au1xmmc.o
+obj-$(CONFIG_MMC_OMAP) += omap.o
+obj-$(CONFIG_MMC_OMAP_HS) += omap_hsmmc.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_MSM) += msm_sdcc.o
+obj-$(CONFIG_MMC_MVSDIO) += mvsdio.o
+obj-$(CONFIG_MMC_DAVINCI) += davinci_mmc.o
+obj-$(CONFIG_MMC_SPI) += mmc_spi.o
+ifeq ($(CONFIG_OF),y)
+obj-$(CONFIG_MMC_SPI) += of_mmc_spi.o
+endif
+obj-$(CONFIG_MMC_S3C) += s3cmci.o
+obj-$(CONFIG_MMC_SDRICOH_CS) += sdricoh_cs.o
+obj-$(CONFIG_MMC_TMIO) += tmio_mmc.o
+obj-$(CONFIG_MMC_TMIO_CORE) += tmio_mmc_core.o
+tmio_mmc_core-y := tmio_mmc_pio.o
+ifneq ($(CONFIG_MMC_SDHI),n)
+tmio_mmc_core-y += tmio_mmc_dma.o
+endif
+obj-$(CONFIG_MMC_SDHI) += sh_mobile_sdhi.o
+obj-$(CONFIG_MMC_CB710) += cb710-mmc.o
+obj-$(CONFIG_MMC_VIA_SDMMC) += via-sdmmc.o
+obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o
+obj-$(CONFIG_MMC_DW) += dw_mmc.o
+obj-$(CONFIG_MMC_SH_MMCIF) += sh_mmcif.o
+obj-$(CONFIG_MMC_JZ4740) += jz4740_mmc.o
+obj-$(CONFIG_MMC_VUB300) += vub300.o
+obj-$(CONFIG_MMC_USHC) += ushc.o
+
+obj-$(CONFIG_MMC_SDHCI_PLTFM) += sdhci-platform.o
+sdhci-platform-y := sdhci-pltfm.o
+sdhci-platform-$(CONFIG_MMC_SDHCI_CNS3XXX) += sdhci-cns3xxx.o
+sdhci-platform-$(CONFIG_MMC_SDHCI_ESDHC_IMX) += sdhci-esdhc-imx.o
+sdhci-platform-$(CONFIG_MMC_SDHCI_DOVE) += sdhci-dove.o
+sdhci-platform-$(CONFIG_MMC_SDHCI_TEGRA) += sdhci-tegra.o
+
+obj-$(CONFIG_MMC_SDHCI_OF) += sdhci-of.o
+sdhci-of-y := sdhci-of-core.o
+sdhci-of-$(CONFIG_MMC_SDHCI_OF_ESDHC) += sdhci-of-esdhc.o
+sdhci-of-$(CONFIG_MMC_SDHCI_OF_HLWD) += sdhci-of-hlwd.o
+
+ifeq ($(CONFIG_CB710_DEBUG),y)
+ CFLAGS-cb710-mmc += -DDEBUG
+endif
diff --git a/drivers/mmc/host/at91_mci.c b/drivers/mmc/host/at91_mci.c
new file mode 100644
index 00000000..d3e6a962
--- /dev/null
+++ b/drivers/mmc/host/at91_mci.c
@@ -0,0 +1,1215 @@
+/*
+ * linux/drivers/mmc/host/at91_mci.c - ATMEL AT91 MCI Driver
+ *
+ * Copyright (C) 2005 Cougar Creek Computing Devices Ltd, All Rights Reserved
+ *
+ * Copyright (C) 2006 Malcolm Noyes
+ *
+ * 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.
+ */
+
+/*
+ This is the AT91 MCI driver that has been tested with both MMC cards
+ and SD-cards. Boards that support write protect are now supported.
+ The CCAT91SBC001 board does not support SD cards.
+
+ The three entry points are at91_mci_request, at91_mci_set_ios
+ and at91_mci_get_ro.
+
+ SET IOS
+ This configures the device to put it into the correct mode and clock speed
+ required.
+
+ MCI REQUEST
+ MCI request processes the commands sent in the mmc_request structure. This
+ can consist of a processing command and a stop command in the case of
+ multiple block transfers.
+
+ There are three main types of request, commands, reads and writes.
+
+ Commands are straight forward. The command is submitted to the controller and
+ the request function returns. When the controller generates an interrupt to indicate
+ the command is finished, the response to the command are read and the mmc_request_done
+ function called to end the request.
+
+ Reads and writes work in a similar manner to normal commands but involve the PDC (DMA)
+ controller to manage the transfers.
+
+ A read is done from the controller directly to the scatterlist passed in from the request.
+ Due to a bug in the AT91RM9200 controller, when a read is completed, all the words are byte
+ swapped in the scatterlist buffers. AT91SAM926x are not affected by this bug.
+
+ The sequence of read interrupts is: ENDRX, RXBUFF, CMDRDY
+
+ A write is slightly different in that the bytes to write are read from the scatterlist
+ into a dma memory buffer (this is in case the source buffer should be read only). The
+ entire write buffer is then done from this single dma memory buffer.
+
+ The sequence of write interrupts is: ENDTX, TXBUFE, NOTBUSY, CMDRDY
+
+ GET RO
+ Gets the status of the write protect pin, if available.
+*/
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/blkdev.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/dma-mapping.h>
+#include <linux/clk.h>
+#include <linux/atmel_pdc.h>
+#include <linux/gfp.h>
+#include <linux/highmem.h>
+
+#include <linux/mmc/host.h>
+#include <linux/mmc/sdio.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/gpio.h>
+
+#include <mach/board.h>
+#include <mach/cpu.h>
+#include <mach/at91_mci.h>
+
+#define DRIVER_NAME "at91_mci"
+
+static inline int at91mci_is_mci1rev2xx(void)
+{
+ return ( cpu_is_at91sam9260()
+ || cpu_is_at91sam9263()
+ || cpu_is_at91cap9()
+ || cpu_is_at91sam9rl()
+ || cpu_is_at91sam9g10()
+ || cpu_is_at91sam9g20()
+ );
+}
+
+#define FL_SENT_COMMAND (1 << 0)
+#define FL_SENT_STOP (1 << 1)
+
+#define AT91_MCI_ERRORS (AT91_MCI_RINDE | AT91_MCI_RDIRE | AT91_MCI_RCRCE \
+ | AT91_MCI_RENDE | AT91_MCI_RTOE | AT91_MCI_DCRCE \
+ | AT91_MCI_DTOE | AT91_MCI_OVRE | AT91_MCI_UNRE)
+
+#define at91_mci_read(host, reg) __raw_readl((host)->baseaddr + (reg))
+#define at91_mci_write(host, reg, val) __raw_writel((val), (host)->baseaddr + (reg))
+
+#define MCI_BLKSIZE 512
+#define MCI_MAXBLKSIZE 4095
+#define MCI_BLKATONCE 256
+#define MCI_BUFSIZE (MCI_BLKSIZE * MCI_BLKATONCE)
+
+/*
+ * Low level type for this driver
+ */
+struct at91mci_host
+{
+ struct mmc_host *mmc;
+ struct mmc_command *cmd;
+ struct mmc_request *request;
+
+ void __iomem *baseaddr;
+ int irq;
+
+ struct at91_mmc_data *board;
+ int present;
+
+ struct clk *mci_clk;
+
+ /*
+ * Flag indicating when the command has been sent. This is used to
+ * work out whether or not to send the stop
+ */
+ unsigned int flags;
+ /* flag for current bus settings */
+ u32 bus_mode;
+
+ /* DMA buffer used for transmitting */
+ unsigned int* buffer;
+ dma_addr_t physical_address;
+ unsigned int total_length;
+
+ /* Latest in the scatterlist that has been enabled for transfer, but not freed */
+ int in_use_index;
+
+ /* 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
+ */
+static inline void at91_mci_sg_to_dma(struct at91mci_host *host, struct mmc_data *data)
+{
+ unsigned int len, i, size;
+ unsigned *dmabuf = host->buffer;
+
+ size = data->blksz * data->blocks;
+ len = data->sg_len;
+
+ /* MCI1 rev2xx Data Write Operation and number of bytes erratum */
+ if (at91mci_is_mci1rev2xx())
+ 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
+ * we do not transfer too much.
+ */
+ for (i = 0; i < len; i++) {
+ struct scatterlist *sg;
+ int amount;
+ unsigned int *sgbuffer;
+
+ sg = &data->sg[i];
+
+ sgbuffer = kmap_atomic(sg_page(sg), KM_BIO_SRC_IRQ) + sg->offset;
+ amount = min(size, sg->length);
+ size -= amount;
+
+ if (cpu_is_at91rm9200()) { /* AT91RM9200 errata */
+ int index;
+
+ for (index = 0; index < (amount / 4); index++)
+ *dmabuf++ = swab32(sgbuffer[index]);
+ } else {
+ char *tmpv = (char *)dmabuf;
+ memcpy(tmpv, sgbuffer, amount);
+ tmpv += amount;
+ dmabuf = (unsigned *)tmpv;
+ }
+
+ kunmap_atomic(sgbuffer, KM_BIO_SRC_IRQ);
+
+ if (size == 0)
+ break;
+ }
+
+ /*
+ * Check that we didn't get a request to transfer
+ * more data than can fit into the SG list.
+ */
+ BUG_ON(size != 0);
+}
+
+/*
+ * Handle after a dma read
+ */
+static void at91_mci_post_dma_read(struct at91mci_host *host)
+{
+ struct mmc_command *cmd;
+ struct mmc_data *data;
+ unsigned int len, i, size;
+ unsigned *dmabuf = host->buffer;
+
+ pr_debug("post dma read\n");
+
+ cmd = host->cmd;
+ if (!cmd) {
+ pr_debug("no command\n");
+ return;
+ }
+
+ data = cmd->data;
+ if (!data) {
+ pr_debug("no data\n");
+ return;
+ }
+
+ size = data->blksz * data->blocks;
+ len = data->sg_len;
+
+ at91_mci_write(host, AT91_MCI_IDR, AT91_MCI_ENDRX);
+ at91_mci_write(host, AT91_MCI_IER, AT91_MCI_RXBUFF);
+
+ for (i = 0; i < len; i++) {
+ struct scatterlist *sg;
+ int amount;
+ unsigned int *sgbuffer;
+
+ sg = &data->sg[i];
+
+ sgbuffer = kmap_atomic(sg_page(sg), KM_BIO_SRC_IRQ) + sg->offset;
+ amount = min(size, sg->length);
+ size -= amount;
+
+ if (cpu_is_at91rm9200()) { /* AT91RM9200 errata */
+ int index;
+ for (index = 0; index < (amount / 4); index++)
+ sgbuffer[index] = swab32(*dmabuf++);
+ } else {
+ char *tmpv = (char *)dmabuf;
+ memcpy(sgbuffer, tmpv, amount);
+ tmpv += amount;
+ dmabuf = (unsigned *)tmpv;
+ }
+
+ flush_kernel_dcache_page(sg_page(sg));
+ kunmap_atomic(sgbuffer, KM_BIO_SRC_IRQ);
+ data->bytes_xfered += amount;
+ if (size == 0)
+ break;
+ }
+
+ pr_debug("post dma read done\n");
+}
+
+/*
+ * Handle transmitted data
+ */
+static void at91_mci_handle_transmitted(struct at91mci_host *host)
+{
+ struct mmc_command *cmd;
+ struct mmc_data *data;
+
+ pr_debug("Handling the transmit\n");
+
+ /* Disable the transfer */
+ at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS);
+
+ /* Now wait for cmd ready */
+ at91_mci_write(host, AT91_MCI_IDR, AT91_MCI_TXBUFE);
+
+ cmd = host->cmd;
+ if (!cmd) return;
+
+ data = cmd->data;
+ if (!data) return;
+
+ if (cmd->data->blocks > 1) {
+ pr_debug("multiple write : wait for BLKE...\n");
+ 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;
+
+ 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)
+{
+ if (!host->cmd)
+ return 1;
+ else if (!host->cmd->data) {
+ if (host->flags & FL_SENT_STOP) {
+ /*After multi block write, we must wait for NOTBUSY*/
+ at91_mci_write(host, AT91_MCI_IER, AT91_MCI_NOTBUSY);
+ } 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_BLKE);
+ at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_TXTEN);
+ }
+
+ /* command not completed, have to wait */
+ return 0;
+}
+
+
+/*
+ * Enable the controller
+ */
+static void at91_mci_enable(struct at91mci_host *host)
+{
+ unsigned int mr;
+
+ at91_mci_write(host, AT91_MCI_CR, AT91_MCI_MCIEN);
+ at91_mci_write(host, AT91_MCI_IDR, 0xffffffff);
+ at91_mci_write(host, AT91_MCI_DTOR, AT91_MCI_DTOMUL_1M | AT91_MCI_DTOCYC);
+ mr = AT91_MCI_PDCMODE | 0x34a;
+
+ if (at91mci_is_mci1rev2xx())
+ mr |= AT91_MCI_RDPROOF | AT91_MCI_WRPROOF;
+
+ at91_mci_write(host, AT91_MCI_MR, mr);
+
+ /* use Slot A or B (only one at same time) */
+ at91_mci_write(host, AT91_MCI_SDCR, host->board->slot_b);
+}
+
+/*
+ * Disable the controller
+ */
+static void at91_mci_disable(struct at91mci_host *host)
+{
+ at91_mci_write(host, AT91_MCI_CR, AT91_MCI_MCIDIS | AT91_MCI_SWRST);
+}
+
+/*
+ * Send a command
+ */
+static void at91_mci_send_command(struct at91mci_host *host, struct mmc_command *cmd)
+{
+ unsigned int cmdr, mr;
+ unsigned int block_length;
+ struct mmc_data *data = cmd->data;
+
+ unsigned int blocks;
+ unsigned int ier = 0;
+
+ host->cmd = cmd;
+
+ /* Needed for leaving busy state before CMD1 */
+ if ((at91_mci_read(host, AT91_MCI_SR) & AT91_MCI_RTOE) && (cmd->opcode == 1)) {
+ pr_debug("Clearing timeout\n");
+ at91_mci_write(host, AT91_MCI_ARGR, 0);
+ at91_mci_write(host, AT91_MCI_CMDR, AT91_MCI_OPDCMD);
+ while (!(at91_mci_read(host, AT91_MCI_SR) & AT91_MCI_CMDRDY)) {
+ /* spin */
+ pr_debug("Clearing: SR = %08X\n", at91_mci_read(host, AT91_MCI_SR));
+ }
+ }
+
+ cmdr = cmd->opcode;
+
+ if (mmc_resp_type(cmd) == MMC_RSP_NONE)
+ cmdr |= AT91_MCI_RSPTYP_NONE;
+ else {
+ /* if a response is expected then allow maximum response latancy */
+ cmdr |= AT91_MCI_MAXLAT;
+ /* set 136 bit response for R2, 48 bit response otherwise */
+ if (mmc_resp_type(cmd) == MMC_RSP_R2)
+ cmdr |= AT91_MCI_RSPTYP_136;
+ else
+ cmdr |= AT91_MCI_RSPTYP_48;
+ }
+
+ if (data) {
+
+ 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;
+ blocks = data->blocks;
+
+ /* always set data start - also set direction flag for read */
+ if (data->flags & MMC_DATA_READ)
+ cmdr |= (AT91_MCI_TRDIR | AT91_MCI_TRCMD_START);
+ else if (data->flags & MMC_DATA_WRITE)
+ cmdr |= AT91_MCI_TRCMD_START;
+
+ if (cmd->opcode == SD_IO_RW_EXTENDED) {
+ cmdr |= AT91_MCI_TRTYP_SDIO_BLOCK;
+ } else {
+ if (data->flags & MMC_DATA_STREAM)
+ cmdr |= AT91_MCI_TRTYP_STREAM;
+ if (data->blocks > 1)
+ cmdr |= AT91_MCI_TRTYP_MULTIPLE;
+ }
+ }
+ else {
+ block_length = 0;
+ blocks = 0;
+ }
+
+ if (host->flags & FL_SENT_STOP)
+ cmdr |= AT91_MCI_TRCMD_STOP;
+
+ if (host->bus_mode == MMC_BUSMODE_OPENDRAIN)
+ cmdr |= AT91_MCI_OPDCMD;
+
+ /*
+ * Set the arguments and send the command
+ */
+ pr_debug("Sending command %d as %08X, arg = %08X, blocks = %d, length = %d (MR = %08X)\n",
+ cmd->opcode, cmdr, cmd->arg, blocks, block_length, at91_mci_read(host, AT91_MCI_MR));
+
+ if (!data) {
+ at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_TXTDIS | ATMEL_PDC_RXTDIS);
+ at91_mci_write(host, ATMEL_PDC_RPR, 0);
+ at91_mci_write(host, ATMEL_PDC_RCR, 0);
+ at91_mci_write(host, ATMEL_PDC_RNPR, 0);
+ at91_mci_write(host, ATMEL_PDC_RNCR, 0);
+ at91_mci_write(host, ATMEL_PDC_TPR, 0);
+ at91_mci_write(host, ATMEL_PDC_TCR, 0);
+ at91_mci_write(host, ATMEL_PDC_TNPR, 0);
+ at91_mci_write(host, ATMEL_PDC_TNCR, 0);
+ ier = AT91_MCI_CMDRDY;
+ } else {
+ /* zero block length and PDC mode */
+ 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
+ */
+ at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS);
+
+ if (cmdr & AT91_MCI_TRCMD_START) {
+ data->bytes_xfered = 0;
+ host->transfer_index = 0;
+ host->in_use_index = 0;
+ if (cmdr & AT91_MCI_TRDIR) {
+ /*
+ * Handle a read
+ */
+ host->total_length = 0;
+
+ at91_mci_write(host, ATMEL_PDC_RPR, host->physical_address);
+ at91_mci_write(host, ATMEL_PDC_RCR, (data->blksz & 0x3) ?
+ (blocks * block_length) : (blocks * block_length) / 4);
+ at91_mci_write(host, ATMEL_PDC_RNPR, 0);
+ at91_mci_write(host, ATMEL_PDC_RNCR, 0);
+
+ ier = AT91_MCI_ENDRX /* | AT91_MCI_RXBUFF */;
+ }
+ else {
+ /*
+ * Handle a write
+ */
+ host->total_length = block_length * blocks;
+ /*
+ * MCI1 rev2xx Data Write Operation and
+ * number of bytes erratum
+ */
+ if (at91mci_is_mci1rev2xx())
+ if (host->total_length < 12)
+ host->total_length = 12;
+
+ at91_mci_sg_to_dma(host, data);
+
+ 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, (data->blksz & 0x3) ?
+ host->total_length : host->total_length / 4);
+
+ ier = AT91_MCI_CMDRDY;
+ }
+ }
+ }
+
+ /*
+ * Send the command and then enable the PDC - not the other way round as
+ * the data sheet says
+ */
+
+ at91_mci_write(host, AT91_MCI_ARGR, cmd->arg);
+ at91_mci_write(host, AT91_MCI_CMDR, cmdr);
+
+ if (cmdr & AT91_MCI_TRCMD_START) {
+ if (cmdr & AT91_MCI_TRDIR)
+ at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTEN);
+ }
+
+ /* Enable selected interrupts */
+ at91_mci_write(host, AT91_MCI_IER, AT91_MCI_ERRORS | ier);
+}
+
+/*
+ * Process the next step in the request
+ */
+static void at91_mci_process_next(struct at91mci_host *host)
+{
+ if (!(host->flags & FL_SENT_COMMAND)) {
+ host->flags |= FL_SENT_COMMAND;
+ at91_mci_send_command(host, host->request->cmd);
+ }
+ else if ((!(host->flags & FL_SENT_STOP)) && host->request->stop) {
+ host->flags |= FL_SENT_STOP;
+ at91_mci_send_command(host, host->request->stop);
+ } 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, unsigned int status)
+{
+ struct mmc_command *cmd = host->cmd;
+ struct mmc_data *data = cmd->data;
+
+ 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));
+ cmd->resp[2] = at91_mci_read(host, AT91_MCI_RSPR(2));
+ cmd->resp[3] = at91_mci_read(host, AT91_MCI_RSPR(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_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/%d (cmd = %d, retries = %d)\n",
+ cmd->error, data ? data->error : 0,
+ cmd->opcode, cmd->retries);
+ }
+ }
+ else
+ cmd->error = 0;
+
+ at91_mci_process_next(host);
+}
+
+/*
+ * Handle an MMC request
+ */
+static void at91_mci_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct at91mci_host *host = mmc_priv(mmc);
+ host->request = mrq;
+ host->flags = 0;
+
+ /* more than 1s timeout needed with slow SD cards */
+ mod_timer(&host->timer, jiffies + msecs_to_jiffies(2000));
+
+ at91_mci_process_next(host);
+}
+
+/*
+ * Set the IOS
+ */
+static void at91_mci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ int clkdiv;
+ struct at91mci_host *host = mmc_priv(mmc);
+ unsigned long at91_master_clock = clk_get_rate(host->mci_clk);
+
+ host->bus_mode = ios->bus_mode;
+
+ if (ios->clock == 0) {
+ /* Disable the MCI controller */
+ at91_mci_write(host, AT91_MCI_CR, AT91_MCI_MCIDIS);
+ clkdiv = 0;
+ }
+ else {
+ /* Enable the MCI controller */
+ at91_mci_write(host, AT91_MCI_CR, AT91_MCI_MCIEN);
+
+ if ((at91_master_clock % (ios->clock * 2)) == 0)
+ clkdiv = ((at91_master_clock / ios->clock) / 2) - 1;
+ else
+ clkdiv = (at91_master_clock / ios->clock) / 2;
+
+ pr_debug("clkdiv = %d. mcck = %ld\n", clkdiv,
+ at91_master_clock / (2 * (clkdiv + 1)));
+ }
+ if (ios->bus_width == MMC_BUS_WIDTH_4 && host->board->wire4) {
+ pr_debug("MMC: Setting controller bus width to 4\n");
+ at91_mci_write(host, AT91_MCI_SDCR, at91_mci_read(host, AT91_MCI_SDCR) | AT91_MCI_SDCBUS);
+ }
+ else {
+ pr_debug("MMC: Setting controller bus width to 1\n");
+ at91_mci_write(host, AT91_MCI_SDCR, at91_mci_read(host, AT91_MCI_SDCR) & ~AT91_MCI_SDCBUS);
+ }
+
+ /* Set the clock divider */
+ at91_mci_write(host, AT91_MCI_MR, (at91_mci_read(host, AT91_MCI_MR) & ~AT91_MCI_CLKDIV) | clkdiv);
+
+ /* maybe switch power to the card */
+ if (host->board->vcc_pin) {
+ switch (ios->power_mode) {
+ case MMC_POWER_OFF:
+ gpio_set_value(host->board->vcc_pin, 0);
+ break;
+ case MMC_POWER_UP:
+ gpio_set_value(host->board->vcc_pin, 1);
+ break;
+ case MMC_POWER_ON:
+ break;
+ default:
+ WARN_ON(1);
+ }
+ }
+}
+
+/*
+ * Handle an interrupt
+ */
+static irqreturn_t at91_mci_irq(int irq, void *devid)
+{
+ struct at91mci_host *host = devid;
+ int completed = 0;
+ unsigned int int_status, int_mask;
+
+ int_status = at91_mci_read(host, AT91_MCI_SR);
+ int_mask = at91_mci_read(host, AT91_MCI_IMR);
+
+ pr_debug("MCI irq: status = %08X, %08X, %08X\n", int_status, int_mask,
+ int_status & int_mask);
+
+ int_status = int_status & int_mask;
+
+ if (int_status & AT91_MCI_ERRORS) {
+ completed = 1;
+
+ if (int_status & AT91_MCI_UNRE)
+ pr_debug("MMC: Underrun error\n");
+ if (int_status & AT91_MCI_OVRE)
+ pr_debug("MMC: Overrun error\n");
+ if (int_status & AT91_MCI_DTOE)
+ pr_debug("MMC: Data timeout\n");
+ if (int_status & AT91_MCI_DCRCE)
+ pr_debug("MMC: CRC error in data\n");
+ if (int_status & AT91_MCI_RTOE)
+ pr_debug("MMC: Response timeout\n");
+ if (int_status & AT91_MCI_RENDE)
+ pr_debug("MMC: Response end bit error\n");
+ if (int_status & AT91_MCI_RCRCE)
+ pr_debug("MMC: Response CRC error\n");
+ if (int_status & AT91_MCI_RDIRE)
+ pr_debug("MMC: Response direction error\n");
+ if (int_status & AT91_MCI_RINDE)
+ pr_debug("MMC: Response index error\n");
+ } else {
+ /* Only continue processing if no errors */
+
+ if (int_status & AT91_MCI_TXBUFE) {
+ pr_debug("TX buffer empty\n");
+ at91_mci_handle_transmitted(host);
+ }
+
+ if (int_status & AT91_MCI_ENDRX) {
+ pr_debug("ENDRX\n");
+ at91_mci_post_dma_read(host);
+ }
+
+ if (int_status & AT91_MCI_RXBUFF) {
+ pr_debug("RX buffer full\n");
+ at91_mci_write(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS);
+ at91_mci_write(host, AT91_MCI_IDR, AT91_MCI_RXBUFF | AT91_MCI_ENDRX);
+ completed = 1;
+ }
+
+ if (int_status & AT91_MCI_ENDTX)
+ pr_debug("Transmit has ended\n");
+
+ if (int_status & AT91_MCI_NOTBUSY) {
+ pr_debug("Card is ready\n");
+ at91_mci_update_bytes_xfered(host);
+ completed = 1;
+ }
+
+ if (int_status & AT91_MCI_DTIP)
+ pr_debug("Data transfer in progress\n");
+
+ if (int_status & AT91_MCI_BLKE) {
+ pr_debug("Block transfer has ended\n");
+ 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");
+
+ if (int_status & AT91_MCI_RXRDY)
+ pr_debug("Ready to receive\n");
+
+ if (int_status & AT91_MCI_CMDRDY) {
+ pr_debug("Command ready\n");
+ completed = at91_mci_handle_cmdrdy(host);
+ }
+ }
+
+ if (completed) {
+ pr_debug("Completed command\n");
+ 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_SDIOIRQA | AT91_MCI_SDIOIRQB));
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t at91_mmc_det_irq(int irq, void *_host)
+{
+ struct at91mci_host *host = _host;
+ int present = !gpio_get_value(irq_to_gpio(irq));
+
+ /*
+ * we expect this irq on both insert and remove,
+ * and use a short delay to debounce.
+ */
+ if (present != host->present) {
+ host->present = present;
+ pr_debug("%s: card %s\n", mmc_hostname(host->mmc),
+ present ? "insert" : "remove");
+ if (!present) {
+ pr_debug("****** Resetting SD-card bus width ******\n");
+ at91_mci_write(host, AT91_MCI_SDCR, at91_mci_read(host, AT91_MCI_SDCR) & ~AT91_MCI_SDCBUS);
+ }
+ /* 0.5s needed because of early card detect switch firing */
+ mmc_detect_change(host->mmc, msecs_to_jiffies(500));
+ }
+ return IRQ_HANDLED;
+}
+
+static int at91_mci_get_ro(struct mmc_host *mmc)
+{
+ struct at91mci_host *host = mmc_priv(mmc);
+
+ 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,
+};
+
+/*
+ * Probe for the device
+ */
+static int __init at91_mci_probe(struct platform_device *pdev)
+{
+ struct mmc_host *mmc;
+ struct at91mci_host *host;
+ struct resource *res;
+ int ret;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENXIO;
+
+ if (!request_mem_region(res->start, resource_size(res), DRIVER_NAME))
+ return -EBUSY;
+
+ mmc = mmc_alloc_host(sizeof(struct at91mci_host), &pdev->dev);
+ if (!mmc) {
+ ret = -ENOMEM;
+ dev_dbg(&pdev->dev, "couldn't allocate mmc host\n");
+ goto fail6;
+ }
+
+ mmc->ops = &at91_mci_ops;
+ mmc->f_min = 375000;
+ mmc->f_max = 25000000;
+ mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+ mmc->caps = 0;
+
+ mmc->max_blk_size = MCI_MAXBLKSIZE;
+ mmc->max_blk_count = MCI_BLKATONCE;
+ mmc->max_req_size = MCI_BUFSIZE;
+ mmc->max_segs = MCI_BLKATONCE;
+ mmc->max_seg_size = MCI_BUFSIZE;
+
+ host = mmc_priv(mmc);
+ host->mmc = mmc;
+ host->bus_mode = 0;
+ host->board = pdev->dev.platform_data;
+ if (host->board->wire4) {
+ if (at91mci_is_mci1rev2xx())
+ mmc->caps |= MMC_CAP_4_BIT_DATA;
+ else
+ dev_warn(&pdev->dev, "4 wire bus mode not supported"
+ " - using 1 wire\n");
+ }
+
+ host->buffer = dma_alloc_coherent(&pdev->dev, MCI_BUFSIZE,
+ &host->physical_address, GFP_KERNEL);
+ if (!host->buffer) {
+ ret = -ENOMEM;
+ dev_err(&pdev->dev, "Can't allocate transmit buffer\n");
+ goto fail5;
+ }
+
+ /* Add SDIO capability when available */
+ if (at91mci_is_mci1rev2xx()) {
+ /* at91mci MCI1 rev2xx sdio interrupt erratum */
+ if (host->board->wire4 || !host->board->slot_b)
+ mmc->caps |= MMC_CAP_SDIO_IRQ;
+ }
+
+ /*
+ * Reserve GPIOs ... board init code makes sure these pins are set
+ * up as GPIOs with the right direction (input, except for vcc)
+ */
+ if (host->board->det_pin) {
+ ret = gpio_request(host->board->det_pin, "mmc_detect");
+ if (ret < 0) {
+ dev_dbg(&pdev->dev, "couldn't claim card detect pin\n");
+ goto fail4b;
+ }
+ }
+ if (host->board->wp_pin) {
+ ret = gpio_request(host->board->wp_pin, "mmc_wp");
+ if (ret < 0) {
+ dev_dbg(&pdev->dev, "couldn't claim wp sense pin\n");
+ goto fail4;
+ }
+ }
+ if (host->board->vcc_pin) {
+ ret = gpio_request(host->board->vcc_pin, "mmc_vcc");
+ if (ret < 0) {
+ dev_dbg(&pdev->dev, "couldn't claim vcc switch pin\n");
+ goto fail3;
+ }
+ }
+
+ /*
+ * Get Clock
+ */
+ host->mci_clk = clk_get(&pdev->dev, "mci_clk");
+ if (IS_ERR(host->mci_clk)) {
+ ret = -ENODEV;
+ dev_dbg(&pdev->dev, "no mci_clk?\n");
+ goto fail2;
+ }
+
+ /*
+ * Map I/O region
+ */
+ host->baseaddr = ioremap(res->start, resource_size(res));
+ if (!host->baseaddr) {
+ ret = -ENOMEM;
+ goto fail1;
+ }
+
+ /*
+ * Reset hardware
+ */
+ clk_enable(host->mci_clk); /* Enable the peripheral clock */
+ at91_mci_disable(host);
+ at91_mci_enable(host);
+
+ /*
+ * Allocate the MCI interrupt
+ */
+ host->irq = platform_get_irq(pdev, 0);
+ ret = request_irq(host->irq, at91_mci_irq, IRQF_SHARED,
+ mmc_hostname(mmc), host);
+ if (ret) {
+ dev_dbg(&pdev->dev, "request MCI interrupt failed\n");
+ goto fail0;
+ }
+
+ setup_timer(&host->timer, at91_timeout_timer, (unsigned long)host);
+
+ platform_set_drvdata(pdev, mmc);
+
+ /*
+ * Add host to MMC layer
+ */
+ if (host->board->det_pin) {
+ host->present = !gpio_get_value(host->board->det_pin);
+ }
+ else
+ host->present = -1;
+
+ mmc_add_host(mmc);
+
+ /*
+ * monitor card insertion/removal if we can
+ */
+ if (host->board->det_pin) {
+ ret = request_irq(gpio_to_irq(host->board->det_pin),
+ at91_mmc_det_irq, 0, mmc_hostname(mmc), host);
+ if (ret)
+ dev_warn(&pdev->dev, "request MMC detect irq failed\n");
+ else
+ device_init_wakeup(&pdev->dev, 1);
+ }
+
+ pr_debug("Added MCI driver\n");
+
+ return 0;
+
+fail0:
+ clk_disable(host->mci_clk);
+ iounmap(host->baseaddr);
+fail1:
+ clk_put(host->mci_clk);
+fail2:
+ if (host->board->vcc_pin)
+ gpio_free(host->board->vcc_pin);
+fail3:
+ if (host->board->wp_pin)
+ gpio_free(host->board->wp_pin);
+fail4:
+ if (host->board->det_pin)
+ gpio_free(host->board->det_pin);
+fail4b:
+ if (host->buffer)
+ dma_free_coherent(&pdev->dev, MCI_BUFSIZE,
+ host->buffer, host->physical_address);
+fail5:
+ mmc_free_host(mmc);
+fail6:
+ release_mem_region(res->start, resource_size(res));
+ dev_err(&pdev->dev, "probe failed, err %d\n", ret);
+ return ret;
+}
+
+/*
+ * Remove a device
+ */
+static int __exit at91_mci_remove(struct platform_device *pdev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(pdev);
+ struct at91mci_host *host;
+ struct resource *res;
+
+ if (!mmc)
+ return -1;
+
+ host = mmc_priv(mmc);
+
+ if (host->buffer)
+ dma_free_coherent(&pdev->dev, MCI_BUFSIZE,
+ host->buffer, host->physical_address);
+
+ if (host->board->det_pin) {
+ if (device_can_wakeup(&pdev->dev))
+ free_irq(gpio_to_irq(host->board->det_pin), host);
+ device_init_wakeup(&pdev->dev, 0);
+ gpio_free(host->board->det_pin);
+ }
+
+ at91_mci_disable(host);
+ del_timer_sync(&host->timer);
+ mmc_remove_host(mmc);
+ free_irq(host->irq, host);
+
+ clk_disable(host->mci_clk); /* Disable the peripheral clock */
+ clk_put(host->mci_clk);
+
+ if (host->board->vcc_pin)
+ gpio_free(host->board->vcc_pin);
+ if (host->board->wp_pin)
+ gpio_free(host->board->wp_pin);
+
+ iounmap(host->baseaddr);
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ release_mem_region(res->start, resource_size(res));
+
+ mmc_free_host(mmc);
+ platform_set_drvdata(pdev, NULL);
+ pr_debug("MCI Removed\n");
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int at91_mci_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct mmc_host *mmc = platform_get_drvdata(pdev);
+ struct at91mci_host *host = mmc_priv(mmc);
+ int ret = 0;
+
+ if (host->board->det_pin && device_may_wakeup(&pdev->dev))
+ enable_irq_wake(host->board->det_pin);
+
+ if (mmc)
+ ret = mmc_suspend_host(mmc);
+
+ return ret;
+}
+
+static int at91_mci_resume(struct platform_device *pdev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(pdev);
+ struct at91mci_host *host = mmc_priv(mmc);
+ int ret = 0;
+
+ if (host->board->det_pin && device_may_wakeup(&pdev->dev))
+ disable_irq_wake(host->board->det_pin);
+
+ if (mmc)
+ ret = mmc_resume_host(mmc);
+
+ return ret;
+}
+#else
+#define at91_mci_suspend NULL
+#define at91_mci_resume NULL
+#endif
+
+static struct platform_driver at91_mci_driver = {
+ .remove = __exit_p(at91_mci_remove),
+ .suspend = at91_mci_suspend,
+ .resume = at91_mci_resume,
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init at91_mci_init(void)
+{
+ return platform_driver_probe(&at91_mci_driver, at91_mci_probe);
+}
+
+static void __exit at91_mci_exit(void)
+{
+ platform_driver_unregister(&at91_mci_driver);
+}
+
+module_init(at91_mci_init);
+module_exit(at91_mci_exit);
+
+MODULE_DESCRIPTION("AT91 Multimedia Card Interface driver");
+MODULE_AUTHOR("Nick Randell");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:at91_mci");
diff --git a/drivers/mmc/host/atmel-mci-regs.h b/drivers/mmc/host/atmel-mci-regs.h
new file mode 100644
index 00000000..fc8a0fe7
--- /dev/null
+++ b/drivers/mmc/host/atmel-mci-regs.h
@@ -0,0 +1,128 @@
+/*
+ * 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.
+ */
+
+/*
+ * Superset of MCI IP registers integrated in Atmel AVR32 and AT91 Processors
+ * Registers and bitfields marked with [2] are only available in MCI2
+ */
+
+#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_PWSEN ( 1 << 2) /* Power Save Enable */
+# define MCI_CR_PWSDIS ( 1 << 3) /* Power Save 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_PWSDIV(x) ((x) << 8) /* Power Saving Divider */
+# define MCI_MR_RDPROOF ( 1 << 11) /* Read Proof */
+# define MCI_MR_WRPROOF ( 1 << 12) /* Write Proof */
+# define MCI_MR_PDCFBYTE ( 1 << 13) /* Force Byte Transfer */
+# define MCI_MR_PDCPADV ( 1 << 14) /* Padding Value */
+# define MCI_MR_PDCMODE ( 1 << 15) /* PDC-oriented Mode */
+#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_SDCSEL_MASK ( 3 << 0)
+# define MCI_SDCBUS_1BIT ( 0 << 6) /* 1-bit data bus */
+# define MCI_SDCBUS_4BIT ( 2 << 6) /* 4-bit data bus */
+# define MCI_SDCBUS_8BIT ( 3 << 6) /* 8-bit data bus[2] */
+# define MCI_SDCBUS_MASK ( 3 << 6)
+#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_CSTOR 0x001c /* Completion Signal Timeout[2] */
+# define MCI_CSTOCYC(x) ((x) << 0) /* CST cycles */
+# define MCI_CSTOMUL(x) ((x) << 4) /* CST multiplier */
+#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 */
+#define MCI_DMA 0x0050 /* DMA Configuration[2] */
+# define MCI_DMA_OFFSET(x) ((x) << 0) /* DMA Write Buffer Offset */
+# define MCI_DMA_CHKSIZE(x) ((x) << 4) /* DMA Channel Read and Write Chunk Size */
+# define MCI_DMAEN ( 1 << 8) /* DMA Hardware Handshaking Enable */
+#define MCI_CFG 0x0054 /* Configuration[2] */
+# define MCI_CFG_FIFOMODE_1DATA ( 1 << 0) /* MCI Internal FIFO control mode */
+# define MCI_CFG_FERRCTRL_COR ( 1 << 4) /* Flow Error flag reset control mode */
+# define MCI_CFG_HSMODE ( 1 << 8) /* High Speed Mode */
+# define MCI_CFG_LSYNC ( 1 << 12) /* Synchronize on the last block */
+#define MCI_WPMR 0x00e4 /* Write Protection Mode[2] */
+# define MCI_WP_EN ( 1 << 0) /* WP Enable */
+# define MCI_WP_KEY (0x4d4349 << 8) /* WP Key */
+#define MCI_WPSR 0x00e8 /* Write Protection Status[2] */
+# define MCI_GET_WP_VS(x) ((x) & 0x0f)
+# define MCI_GET_WP_VSRC(x) (((x) >> 8) & 0xffff)
+#define MCI_FIFO_APERTURE 0x0200 /* FIFO Aperture[2] */
+
+/* This is not including the FIFO Aperture on MCI2 */
+#define MCI_REGS_SIZE 0x100
+
+/* 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
index 00000000..b6cd3867
--- /dev/null
+++ b/drivers/mmc/host/atmel-mci.c
@@ -0,0 +1,1910 @@
+/*
+ * 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/debugfs.h>
+#include <linux/device.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/gpio.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/seq_file.h>
+#include <linux/slab.h>
+#include <linux/stat.h>
+
+#include <linux/mmc/host.h>
+#include <linux/mmc/sdio.h>
+
+#include <mach/atmel-mci.h>
+#include <linux/atmel-mci.h>
+
+#include <asm/io.h>
+#include <asm/unaligned.h>
+
+#include <mach/cpu.h>
+#include <mach/board.h>
+
+#include "atmel-mci-regs.h"
+
+#define ATMCI_DATA_ERROR_FLAGS (MCI_DCRCE | MCI_DTOE | MCI_OVRE | MCI_UNRE)
+#define ATMCI_DMA_THRESHOLD 16
+
+enum {
+ EVENT_CMD_COMPLETE = 0,
+ EVENT_XFER_COMPLETE,
+ EVENT_DATA_COMPLETE,
+ EVENT_DATA_ERROR,
+};
+
+enum atmel_mci_state {
+ STATE_IDLE = 0,
+ STATE_SENDING_CMD,
+ STATE_SENDING_DATA,
+ STATE_DATA_BUSY,
+ STATE_SENDING_STOP,
+ STATE_DATA_ERROR,
+};
+
+struct atmel_mci_dma {
+#ifdef CONFIG_MMC_ATMELMCI_DMA
+ struct dma_chan *chan;
+ struct dma_async_tx_descriptor *data_desc;
+#endif
+};
+
+/**
+ * struct atmel_mci - MMC controller state shared between all slots
+ * @lock: Spinlock protecting the queue and associated data.
+ * @regs: Pointer to MMIO registers.
+ * @sg: Scatterlist entry currently being processed by PIO code, if any.
+ * @pio_offset: Offset into the current scatterlist entry.
+ * @cur_slot: The slot which is currently using the controller.
+ * @mrq: The request currently being processed on @cur_slot,
+ * or NULL if the controller is idle.
+ * @cmd: The command currently being sent to the card, or NULL.
+ * @data: The data currently being transferred, or NULL if no data
+ * transfer is in progress.
+ * @dma: DMA client state.
+ * @data_chan: DMA channel being used for the current data transfer.
+ * @cmd_status: Snapshot of SR taken upon completion of the current
+ * command. Only valid when EVENT_CMD_COMPLETE is pending.
+ * @data_status: Snapshot of SR taken upon completion of the current
+ * data transfer. Only valid when EVENT_DATA_COMPLETE or
+ * EVENT_DATA_ERROR is pending.
+ * @stop_cmdr: Value to be loaded into CMDR when the stop command is
+ * to be sent.
+ * @tasklet: Tasklet running the request state machine.
+ * @pending_events: Bitmask of events flagged by the interrupt handler
+ * to be processed by the tasklet.
+ * @completed_events: Bitmask of events which the state machine has
+ * processed.
+ * @state: Tasklet state.
+ * @queue: List of slots waiting for access to the controller.
+ * @need_clock_update: Update the clock rate before the next request.
+ * @need_reset: Reset controller before next request.
+ * @mode_reg: Value of the MR register.
+ * @cfg_reg: Value of the CFG register.
+ * @bus_hz: The rate of @mck in Hz. This forms the basis for MMC bus
+ * rate and timeout calculations.
+ * @mapbase: Physical address of the MMIO registers.
+ * @mck: The peripheral bus clock hooked up to the MMC controller.
+ * @pdev: Platform device associated with the MMC controller.
+ * @slot: Slots sharing this MMC controller.
+ *
+ * Locking
+ * =======
+ *
+ * @lock is a softirq-safe spinlock protecting @queue as well as
+ * @cur_slot, @mrq and @state. These must always be updated
+ * at the same time while holding @lock.
+ *
+ * @lock also protects mode_reg and need_clock_update since these are
+ * used to synchronize mode register updates with the queue
+ * processing.
+ *
+ * The @mrq field of struct atmel_mci_slot is also protected by @lock,
+ * and must always be written at the same time as the slot is added to
+ * @queue.
+ *
+ * @pending_events and @completed_events are accessed using atomic bit
+ * operations, so they don't need any locking.
+ *
+ * None of the fields touched by the interrupt handler need any
+ * locking. However, ordering is important: Before EVENT_DATA_ERROR or
+ * EVENT_DATA_COMPLETE is set in @pending_events, all data-related
+ * interrupts must be disabled and @data_status updated with a
+ * snapshot of SR. Similarly, before EVENT_CMD_COMPLETE is set, the
+ * CMDRDY interrupt must be disabled and @cmd_status updated with a
+ * snapshot of SR, and before EVENT_XFER_COMPLETE can be set, the
+ * bytes_xfered field of @data must be written. This is ensured by
+ * using barriers.
+ */
+struct atmel_mci {
+ spinlock_t lock;
+ void __iomem *regs;
+
+ struct scatterlist *sg;
+ unsigned int pio_offset;
+
+ struct atmel_mci_slot *cur_slot;
+ struct mmc_request *mrq;
+ struct mmc_command *cmd;
+ struct mmc_data *data;
+
+ struct atmel_mci_dma dma;
+ struct dma_chan *data_chan;
+
+ u32 cmd_status;
+ u32 data_status;
+ u32 stop_cmdr;
+
+ struct tasklet_struct tasklet;
+ unsigned long pending_events;
+ unsigned long completed_events;
+ enum atmel_mci_state state;
+ struct list_head queue;
+
+ bool need_clock_update;
+ bool need_reset;
+ u32 mode_reg;
+ u32 cfg_reg;
+ unsigned long bus_hz;
+ unsigned long mapbase;
+ struct clk *mck;
+ struct platform_device *pdev;
+
+ struct atmel_mci_slot *slot[ATMEL_MCI_MAX_NR_SLOTS];
+};
+
+/**
+ * struct atmel_mci_slot - MMC slot state
+ * @mmc: The mmc_host representing this slot.
+ * @host: The MMC controller this slot is using.
+ * @sdc_reg: Value of SDCR to be written before using this slot.
+ * @sdio_irq: SDIO irq mask for this slot.
+ * @mrq: mmc_request currently being processed or waiting to be
+ * processed, or NULL when the slot is idle.
+ * @queue_node: List node for placing this node in the @queue list of
+ * &struct atmel_mci.
+ * @clock: Clock rate configured by set_ios(). Protected by host->lock.
+ * @flags: Random state bits associated with the slot.
+ * @detect_pin: GPIO pin used for card detection, or negative if not
+ * available.
+ * @wp_pin: GPIO pin used for card write protect sending, or negative
+ * if not available.
+ * @detect_is_active_high: The state of the detect pin when it is active.
+ * @detect_timer: Timer used for debouncing @detect_pin interrupts.
+ */
+struct atmel_mci_slot {
+ struct mmc_host *mmc;
+ struct atmel_mci *host;
+
+ u32 sdc_reg;
+ u32 sdio_irq;
+
+ struct mmc_request *mrq;
+ struct list_head queue_node;
+
+ unsigned int clock;
+ unsigned long flags;
+#define ATMCI_CARD_PRESENT 0
+#define ATMCI_CARD_NEED_INIT 1
+#define ATMCI_SHUTDOWN 2
+
+ int detect_pin;
+ int wp_pin;
+ bool detect_is_active_high;
+
+ struct timer_list detect_timer;
+};
+
+#define atmci_test_and_clear_pending(host, event) \
+ test_and_clear_bit(event, &host->pending_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)
+
+/*
+ * Enable or disable features/registers based on
+ * whether the processor supports them
+ */
+static bool mci_has_rwproof(void)
+{
+ if (cpu_is_at91sam9261() || cpu_is_at91rm9200())
+ return false;
+ else
+ return true;
+}
+
+/*
+ * The new MCI2 module isn't 100% compatible with the old MCI module,
+ * and it has a few nice features which we want to use...
+ */
+static inline bool atmci_is_mci2(void)
+{
+ if (cpu_is_at91sam9g45())
+ return true;
+
+ return false;
+}
+
+
+/*
+ * The debugfs stuff below is mostly optimized away when
+ * CONFIG_DEBUG_FS is not set.
+ */
+static int atmci_req_show(struct seq_file *s, void *v)
+{
+ struct atmel_mci_slot *slot = s->private;
+ struct mmc_request *mrq;
+ struct mmc_command *cmd;
+ struct mmc_command *stop;
+ struct mmc_data *data;
+
+ /* Make sure we get a consistent snapshot */
+ spin_lock_bh(&slot->host->lock);
+ mrq = slot->mrq;
+
+ if (mrq) {
+ cmd = mrq->cmd;
+ data = mrq->data;
+ stop = mrq->stop;
+
+ if (cmd)
+ seq_printf(s,
+ "CMD%u(0x%x) flg %x rsp %x %x %x %x err %d\n",
+ cmd->opcode, cmd->arg, cmd->flags,
+ cmd->resp[0], cmd->resp[1], cmd->resp[2],
+ cmd->resp[3], cmd->error);
+ if (data)
+ seq_printf(s, "DATA %u / %u * %u flg %x err %d\n",
+ data->bytes_xfered, data->blocks,
+ data->blksz, data->flags, data->error);
+ if (stop)
+ seq_printf(s,
+ "CMD%u(0x%x) flg %x rsp %x %x %x %x err %d\n",
+ stop->opcode, stop->arg, stop->flags,
+ stop->resp[0], stop->resp[1], stop->resp[2],
+ stop->resp[3], stop->error);
+ }
+
+ spin_unlock_bh(&slot->host->lock);
+
+ return 0;
+}
+
+static int atmci_req_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, atmci_req_show, inode->i_private);
+}
+
+static const struct file_operations atmci_req_fops = {
+ .owner = THIS_MODULE,
+ .open = atmci_req_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void atmci_show_status_reg(struct seq_file *s,
+ const char *regname, u32 value)
+{
+ static const char *sr_bit[] = {
+ [0] = "CMDRDY",
+ [1] = "RXRDY",
+ [2] = "TXRDY",
+ [3] = "BLKE",
+ [4] = "DTIP",
+ [5] = "NOTBUSY",
+ [6] = "ENDRX",
+ [7] = "ENDTX",
+ [8] = "SDIOIRQA",
+ [9] = "SDIOIRQB",
+ [12] = "SDIOWAIT",
+ [14] = "RXBUFF",
+ [15] = "TXBUFE",
+ [16] = "RINDE",
+ [17] = "RDIRE",
+ [18] = "RCRCE",
+ [19] = "RENDE",
+ [20] = "RTOE",
+ [21] = "DCRCE",
+ [22] = "DTOE",
+ [23] = "CSTOE",
+ [24] = "BLKOVRE",
+ [25] = "DMADONE",
+ [26] = "FIFOEMPTY",
+ [27] = "XFRDONE",
+ [30] = "OVRE",
+ [31] = "UNRE",
+ };
+ unsigned int i;
+
+ seq_printf(s, "%s:\t0x%08x", regname, value);
+ for (i = 0; i < ARRAY_SIZE(sr_bit); i++) {
+ if (value & (1 << i)) {
+ if (sr_bit[i])
+ seq_printf(s, " %s", sr_bit[i]);
+ else
+ seq_puts(s, " UNKNOWN");
+ }
+ }
+ seq_putc(s, '\n');
+}
+
+static int atmci_regs_show(struct seq_file *s, void *v)
+{
+ struct atmel_mci *host = s->private;
+ u32 *buf;
+
+ buf = kmalloc(MCI_REGS_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ /*
+ * Grab a more or less consistent snapshot. Note that we're
+ * not disabling interrupts, so IMR and SR may not be
+ * consistent.
+ */
+ spin_lock_bh(&host->lock);
+ clk_enable(host->mck);
+ memcpy_fromio(buf, host->regs, MCI_REGS_SIZE);
+ clk_disable(host->mck);
+ spin_unlock_bh(&host->lock);
+
+ seq_printf(s, "MR:\t0x%08x%s%s CLKDIV=%u\n",
+ buf[MCI_MR / 4],
+ buf[MCI_MR / 4] & MCI_MR_RDPROOF ? " RDPROOF" : "",
+ buf[MCI_MR / 4] & MCI_MR_WRPROOF ? " WRPROOF" : "",
+ buf[MCI_MR / 4] & 0xff);
+ seq_printf(s, "DTOR:\t0x%08x\n", buf[MCI_DTOR / 4]);
+ seq_printf(s, "SDCR:\t0x%08x\n", buf[MCI_SDCR / 4]);
+ seq_printf(s, "ARGR:\t0x%08x\n", buf[MCI_ARGR / 4]);
+ seq_printf(s, "BLKR:\t0x%08x BCNT=%u BLKLEN=%u\n",
+ buf[MCI_BLKR / 4],
+ buf[MCI_BLKR / 4] & 0xffff,
+ (buf[MCI_BLKR / 4] >> 16) & 0xffff);
+ if (atmci_is_mci2())
+ seq_printf(s, "CSTOR:\t0x%08x\n", buf[MCI_CSTOR / 4]);
+
+ /* Don't read RSPR and RDR; it will consume the data there */
+
+ atmci_show_status_reg(s, "SR", buf[MCI_SR / 4]);
+ atmci_show_status_reg(s, "IMR", buf[MCI_IMR / 4]);
+
+ if (atmci_is_mci2()) {
+ u32 val;
+
+ val = buf[MCI_DMA / 4];
+ seq_printf(s, "DMA:\t0x%08x OFFSET=%u CHKSIZE=%u%s\n",
+ val, val & 3,
+ ((val >> 4) & 3) ?
+ 1 << (((val >> 4) & 3) + 1) : 1,
+ val & MCI_DMAEN ? " DMAEN" : "");
+
+ val = buf[MCI_CFG / 4];
+ seq_printf(s, "CFG:\t0x%08x%s%s%s%s\n",
+ val,
+ val & MCI_CFG_FIFOMODE_1DATA ? " FIFOMODE_ONE_DATA" : "",
+ val & MCI_CFG_FERRCTRL_COR ? " FERRCTRL_CLEAR_ON_READ" : "",
+ val & MCI_CFG_HSMODE ? " HSMODE" : "",
+ val & MCI_CFG_LSYNC ? " LSYNC" : "");
+ }
+
+ kfree(buf);
+
+ return 0;
+}
+
+static int atmci_regs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, atmci_regs_show, inode->i_private);
+}
+
+static const struct file_operations atmci_regs_fops = {
+ .owner = THIS_MODULE,
+ .open = atmci_regs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void atmci_init_debugfs(struct atmel_mci_slot *slot)
+{
+ struct mmc_host *mmc = slot->mmc;
+ struct atmel_mci *host = slot->host;
+ struct dentry *root;
+ struct dentry *node;
+
+ root = mmc->debugfs_root;
+ if (!root)
+ return;
+
+ node = debugfs_create_file("regs", S_IRUSR, root, host,
+ &atmci_regs_fops);
+ if (IS_ERR(node))
+ return;
+ if (!node)
+ goto err;
+
+ node = debugfs_create_file("req", S_IRUSR, root, slot, &atmci_req_fops);
+ if (!node)
+ goto err;
+
+ node = debugfs_create_u32("state", S_IRUSR, root, (u32 *)&host->state);
+ if (!node)
+ goto err;
+
+ node = debugfs_create_x32("pending_events", S_IRUSR, root,
+ (u32 *)&host->pending_events);
+ if (!node)
+ goto err;
+
+ node = debugfs_create_x32("completed_events", S_IRUSR, root,
+ (u32 *)&host->completed_events);
+ if (!node)
+ goto err;
+
+ return;
+
+err:
+ dev_err(&mmc->class_dev, "failed to initialize debugfs for slot\n");
+}
+
+static inline unsigned int ns_to_clocks(struct atmel_mci *host,
+ unsigned int ns)
+{
+ /*
+ * It is easier here to use us instead of ns for the timeout,
+ * it prevents from overflows during calculation.
+ */
+ unsigned int us = DIV_ROUND_UP(ns, 1000);
+
+ /* Maximum clock frequency is host->bus_hz/2 */
+ return us * (DIV_ROUND_UP(host->bus_hz, 2000000));
+}
+
+static void atmci_set_timeout(struct atmel_mci *host,
+ struct atmel_mci_slot *slot, 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(&slot->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 (cmd->opcode == SD_IO_RW_EXTENDED) {
+ cmdr |= MCI_CMDR_SDIO_BLOCK;
+ } else {
+ 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)
+{
+ WARN_ON(host->cmd);
+ host->cmd = cmd;
+
+ dev_vdbg(&host->pdev->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 atmel_mci *host, struct mmc_data *data)
+{
+ atmci_start_command(host, data->stop, host->stop_cmdr);
+ mci_writel(host, IER, MCI_CMDRDY);
+}
+
+#ifdef CONFIG_MMC_ATMELMCI_DMA
+static void atmci_dma_cleanup(struct atmel_mci *host)
+{
+ struct mmc_data *data = host->data;
+
+ if (data)
+ dma_unmap_sg(host->dma.chan->device->dev,
+ data->sg, data->sg_len,
+ ((data->flags & MMC_DATA_WRITE)
+ ? DMA_TO_DEVICE : DMA_FROM_DEVICE));
+}
+
+static void atmci_stop_dma(struct atmel_mci *host)
+{
+ struct dma_chan *chan = host->data_chan;
+
+ if (chan) {
+ dmaengine_terminate_all(chan);
+ atmci_dma_cleanup(host);
+ } else {
+ /* Data transfer was stopped by the interrupt handler */
+ atmci_set_pending(host, EVENT_XFER_COMPLETE);
+ mci_writel(host, IER, MCI_NOTBUSY);
+ }
+}
+
+/* This function is called by the DMA driver from tasklet context. */
+static void atmci_dma_complete(void *arg)
+{
+ struct atmel_mci *host = arg;
+ struct mmc_data *data = host->data;
+
+ dev_vdbg(&host->pdev->dev, "DMA complete\n");
+
+ if (atmci_is_mci2())
+ /* Disable DMA hardware handshaking on MCI */
+ mci_writel(host, DMA, mci_readl(host, DMA) & ~MCI_DMAEN);
+
+ atmci_dma_cleanup(host);
+
+ /*
+ * If the card was removed, data will be NULL. No point trying
+ * to send the stop command or waiting for NBUSY in this case.
+ */
+ if (data) {
+ atmci_set_pending(host, EVENT_XFER_COMPLETE);
+ tasklet_schedule(&host->tasklet);
+
+ /*
+ * Regardless of what the documentation says, we have
+ * to wait for NOTBUSY even after block read
+ * operations.
+ *
+ * When the DMA transfer is complete, the controller
+ * may still be reading the CRC from the card, i.e.
+ * the data transfer is still in progress and we
+ * haven't seen all the potential error bits yet.
+ *
+ * The interrupt handler will schedule a different
+ * tasklet to finish things up when the data transfer
+ * is completely done.
+ *
+ * We may not complete the mmc request here anyway
+ * because the mmc layer may call back and cause us to
+ * violate the "don't submit new operations from the
+ * completion callback" rule of the dma engine
+ * framework.
+ */
+ mci_writel(host, IER, MCI_NOTBUSY);
+ }
+}
+
+static int
+atmci_prepare_data_dma(struct atmel_mci *host, struct mmc_data *data)
+{
+ struct dma_chan *chan;
+ struct dma_async_tx_descriptor *desc;
+ struct scatterlist *sg;
+ unsigned int i;
+ enum dma_data_direction direction;
+ unsigned int sglen;
+
+ /*
+ * We don't do DMA on "complex" transfers, i.e. with
+ * non-word-aligned buffers or lengths. Also, we don't bother
+ * with all the DMA setup overhead for short transfers.
+ */
+ if (data->blocks * data->blksz < ATMCI_DMA_THRESHOLD)
+ return -EINVAL;
+ if (data->blksz & 3)
+ return -EINVAL;
+
+ for_each_sg(data->sg, sg, data->sg_len, i) {
+ if (sg->offset & 3 || sg->length & 3)
+ return -EINVAL;
+ }
+
+ /* If we don't have a channel, we can't do DMA */
+ chan = host->dma.chan;
+ if (chan)
+ host->data_chan = chan;
+
+ if (!chan)
+ return -ENODEV;
+
+ if (atmci_is_mci2())
+ mci_writel(host, DMA, MCI_DMA_CHKSIZE(3) | MCI_DMAEN);
+
+ if (data->flags & MMC_DATA_READ)
+ direction = DMA_FROM_DEVICE;
+ else
+ direction = DMA_TO_DEVICE;
+
+ sglen = dma_map_sg(chan->device->dev, data->sg,
+ data->sg_len, direction);
+
+ desc = chan->device->device_prep_slave_sg(chan,
+ data->sg, sglen, direction,
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ if (!desc)
+ goto unmap_exit;
+
+ host->dma.data_desc = desc;
+ desc->callback = atmci_dma_complete;
+ desc->callback_param = host;
+
+ return 0;
+unmap_exit:
+ dma_unmap_sg(chan->device->dev, data->sg, data->sg_len, direction);
+ return -ENOMEM;
+}
+
+static void atmci_submit_data(struct atmel_mci *host)
+{
+ struct dma_chan *chan = host->data_chan;
+ struct dma_async_tx_descriptor *desc = host->dma.data_desc;
+
+ if (chan) {
+ dmaengine_submit(desc);
+ dma_async_issue_pending(chan);
+ }
+}
+
+#else /* CONFIG_MMC_ATMELMCI_DMA */
+
+static int atmci_prepare_data_dma(struct atmel_mci *host, struct mmc_data *data)
+{
+ return -ENOSYS;
+}
+
+static void atmci_submit_data(struct atmel_mci *host) {}
+
+static void atmci_stop_dma(struct atmel_mci *host)
+{
+ /* Data transfer was stopped by the interrupt handler */
+ atmci_set_pending(host, EVENT_XFER_COMPLETE);
+ mci_writel(host, IER, MCI_NOTBUSY);
+}
+
+#endif /* CONFIG_MMC_ATMELMCI_DMA */
+
+/*
+ * Returns a mask of interrupt flags to be enabled after the whole
+ * request has been prepared.
+ */
+static u32 atmci_prepare_data(struct atmel_mci *host, struct mmc_data *data)
+{
+ u32 iflags;
+
+ data->error = -EINPROGRESS;
+
+ WARN_ON(host->data);
+ host->sg = NULL;
+ host->data = data;
+
+ iflags = ATMCI_DATA_ERROR_FLAGS;
+ if (atmci_prepare_data_dma(host, data)) {
+ host->data_chan = NULL;
+
+ /*
+ * Errata: MMC data write operation with less than 12
+ * bytes is impossible.
+ *
+ * Errata: MCI Transmit Data Register (TDR) FIFO
+ * corruption when length is not multiple of 4.
+ */
+ if (data->blocks * data->blksz < 12
+ || (data->blocks * data->blksz) & 3)
+ host->need_reset = true;
+
+ 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_start_request(struct atmel_mci *host,
+ struct atmel_mci_slot *slot)
+{
+ struct mmc_request *mrq;
+ struct mmc_command *cmd;
+ struct mmc_data *data;
+ u32 iflags;
+ u32 cmdflags;
+
+ mrq = slot->mrq;
+ host->cur_slot = slot;
+ host->mrq = mrq;
+
+ host->pending_events = 0;
+ host->completed_events = 0;
+ host->data_status = 0;
+
+ if (host->need_reset) {
+ mci_writel(host, CR, MCI_CR_SWRST);
+ mci_writel(host, CR, MCI_CR_MCIEN);
+ mci_writel(host, MR, host->mode_reg);
+ if (atmci_is_mci2())
+ mci_writel(host, CFG, host->cfg_reg);
+ host->need_reset = false;
+ }
+ mci_writel(host, SDCR, slot->sdc_reg);
+
+ iflags = mci_readl(host, IMR);
+ if (iflags & ~(MCI_SDIOIRQA | MCI_SDIOIRQB))
+ dev_warn(&slot->mmc->class_dev, "WARNING: IMR=0x%08x\n",
+ iflags);
+
+ if (unlikely(test_and_clear_bit(ATMCI_CARD_NEED_INIT, &slot->flags))) {
+ /* Send init sequence (74 clock cycles) */
+ mci_writel(host, CMDR, MCI_CMDR_SPCMD_INIT);
+ while (!(mci_readl(host, SR) & MCI_CMDRDY))
+ cpu_relax();
+ }
+ iflags = 0;
+ data = mrq->data;
+ if (data) {
+ atmci_set_timeout(host, slot, data);
+
+ /* Must set block count/size before sending command */
+ mci_writel(host, BLKR, MCI_BCNT(data->blocks)
+ | MCI_BLKLEN(data->blksz));
+ dev_vdbg(&slot->mmc->class_dev, "BLKR=0x%08x\n",
+ MCI_BCNT(data->blocks) | MCI_BLKLEN(data->blksz));
+
+ iflags |= atmci_prepare_data(host, data);
+ }
+
+ iflags |= MCI_CMDRDY;
+ cmd = mrq->cmd;
+ cmdflags = atmci_prepare_command(slot->mmc, cmd);
+ atmci_start_command(host, cmd, cmdflags);
+
+ if (data)
+ atmci_submit_data(host);
+
+ if (mrq->stop) {
+ host->stop_cmdr = atmci_prepare_command(slot->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);
+}
+
+static void atmci_queue_request(struct atmel_mci *host,
+ struct atmel_mci_slot *slot, struct mmc_request *mrq)
+{
+ dev_vdbg(&slot->mmc->class_dev, "queue request: state=%d\n",
+ host->state);
+
+ spin_lock_bh(&host->lock);
+ slot->mrq = mrq;
+ if (host->state == STATE_IDLE) {
+ host->state = STATE_SENDING_CMD;
+ atmci_start_request(host, slot);
+ } else {
+ list_add_tail(&slot->queue_node, &host->queue);
+ }
+ spin_unlock_bh(&host->lock);
+}
+
+static void atmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct atmel_mci_slot *slot = mmc_priv(mmc);
+ struct atmel_mci *host = slot->host;
+ struct mmc_data *data;
+
+ WARN_ON(slot->mrq);
+
+ /*
+ * 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 (!test_bit(ATMCI_CARD_PRESENT, &slot->flags)) {
+ mrq->cmd->error = -ENOMEDIUM;
+ mmc_request_done(mmc, mrq);
+ return;
+ }
+
+ /* We don't support multiple blocks of weird lengths. */
+ data = mrq->data;
+ if (data && data->blocks > 1 && data->blksz & 3) {
+ mrq->cmd->error = -EINVAL;
+ mmc_request_done(mmc, mrq);
+ }
+
+ atmci_queue_request(host, slot, mrq);
+}
+
+static void atmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct atmel_mci_slot *slot = mmc_priv(mmc);
+ struct atmel_mci *host = slot->host;
+ unsigned int i;
+
+ slot->sdc_reg &= ~MCI_SDCBUS_MASK;
+ switch (ios->bus_width) {
+ case MMC_BUS_WIDTH_1:
+ slot->sdc_reg |= MCI_SDCBUS_1BIT;
+ break;
+ case MMC_BUS_WIDTH_4:
+ slot->sdc_reg |= MCI_SDCBUS_4BIT;
+ break;
+ }
+
+ if (ios->clock) {
+ unsigned int clock_min = ~0U;
+ u32 clkdiv;
+
+ spin_lock_bh(&host->lock);
+ if (!host->mode_reg) {
+ clk_enable(host->mck);
+ mci_writel(host, CR, MCI_CR_SWRST);
+ mci_writel(host, CR, MCI_CR_MCIEN);
+ if (atmci_is_mci2())
+ mci_writel(host, CFG, host->cfg_reg);
+ }
+
+ /*
+ * Use mirror of ios->clock to prevent race with mmc
+ * core ios update when finding the minimum.
+ */
+ slot->clock = ios->clock;
+ for (i = 0; i < ATMEL_MCI_MAX_NR_SLOTS; i++) {
+ if (host->slot[i] && host->slot[i]->clock
+ && host->slot[i]->clock < clock_min)
+ clock_min = host->slot[i]->clock;
+ }
+
+ /* Calculate clock divider */
+ clkdiv = DIV_ROUND_UP(host->bus_hz, 2 * clock_min) - 1;
+ if (clkdiv > 255) {
+ dev_warn(&mmc->class_dev,
+ "clock %u too slow; using %lu\n",
+ clock_min, host->bus_hz / (2 * 256));
+ clkdiv = 255;
+ }
+
+ host->mode_reg = MCI_MR_CLKDIV(clkdiv);
+
+ /*
+ * WRPROOF and RDPROOF prevent overruns/underruns by
+ * stopping the clock when the FIFO is full/empty.
+ * This state is not expected to last for long.
+ */
+ if (mci_has_rwproof())
+ host->mode_reg |= (MCI_MR_WRPROOF | MCI_MR_RDPROOF);
+
+ if (atmci_is_mci2()) {
+ /* setup High Speed mode in relation with card capacity */
+ if (ios->timing == MMC_TIMING_SD_HS)
+ host->cfg_reg |= MCI_CFG_HSMODE;
+ else
+ host->cfg_reg &= ~MCI_CFG_HSMODE;
+ }
+
+ if (list_empty(&host->queue)) {
+ mci_writel(host, MR, host->mode_reg);
+ if (atmci_is_mci2())
+ mci_writel(host, CFG, host->cfg_reg);
+ } else {
+ host->need_clock_update = true;
+ }
+
+ spin_unlock_bh(&host->lock);
+ } else {
+ bool any_slot_active = false;
+
+ spin_lock_bh(&host->lock);
+ slot->clock = 0;
+ for (i = 0; i < ATMEL_MCI_MAX_NR_SLOTS; i++) {
+ if (host->slot[i] && host->slot[i]->clock) {
+ any_slot_active = true;
+ break;
+ }
+ }
+ if (!any_slot_active) {
+ mci_writel(host, CR, MCI_CR_MCIDIS);
+ if (host->mode_reg) {
+ mci_readl(host, MR);
+ clk_disable(host->mck);
+ }
+ host->mode_reg = 0;
+ }
+ spin_unlock_bh(&host->lock);
+ }
+
+ switch (ios->power_mode) {
+ case MMC_POWER_UP:
+ set_bit(ATMCI_CARD_NEED_INIT, &slot->flags);
+ 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.
+ *
+ * We also need to hook this into the clock management
+ * somehow so that newly inserted cards aren't
+ * subjected to a fast clock before we have a chance
+ * to figure out what the maximum rate is. Currently,
+ * there's no way to avoid this, and there never will
+ * be for boards that don't support power control.
+ */
+ break;
+ }
+}
+
+static int atmci_get_ro(struct mmc_host *mmc)
+{
+ int read_only = -ENOSYS;
+ struct atmel_mci_slot *slot = mmc_priv(mmc);
+
+ if (gpio_is_valid(slot->wp_pin)) {
+ read_only = gpio_get_value(slot->wp_pin);
+ dev_dbg(&mmc->class_dev, "card is %s\n",
+ read_only ? "read-only" : "read-write");
+ }
+
+ return read_only;
+}
+
+static int atmci_get_cd(struct mmc_host *mmc)
+{
+ int present = -ENOSYS;
+ struct atmel_mci_slot *slot = mmc_priv(mmc);
+
+ if (gpio_is_valid(slot->detect_pin)) {
+ present = !(gpio_get_value(slot->detect_pin) ^
+ slot->detect_is_active_high);
+ dev_dbg(&mmc->class_dev, "card is %spresent\n",
+ present ? "" : "not ");
+ }
+
+ return present;
+}
+
+static void atmci_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+ struct atmel_mci_slot *slot = mmc_priv(mmc);
+ struct atmel_mci *host = slot->host;
+
+ if (enable)
+ mci_writel(host, IER, slot->sdio_irq);
+ else
+ mci_writel(host, IDR, slot->sdio_irq);
+}
+
+static const struct mmc_host_ops atmci_ops = {
+ .request = atmci_request,
+ .set_ios = atmci_set_ios,
+ .get_ro = atmci_get_ro,
+ .get_cd = atmci_get_cd,
+ .enable_sdio_irq = atmci_enable_sdio_irq,
+};
+
+/* Called with host->lock held */
+static void atmci_request_end(struct atmel_mci *host, struct mmc_request *mrq)
+ __releases(&host->lock)
+ __acquires(&host->lock)
+{
+ struct atmel_mci_slot *slot = NULL;
+ struct mmc_host *prev_mmc = host->cur_slot->mmc;
+
+ WARN_ON(host->cmd || host->data);
+
+ /*
+ * Update the MMC clock rate if necessary. This may be
+ * necessary if set_ios() is called when a different slot is
+ * busy transferring data.
+ */
+ if (host->need_clock_update) {
+ mci_writel(host, MR, host->mode_reg);
+ if (atmci_is_mci2())
+ mci_writel(host, CFG, host->cfg_reg);
+ }
+
+ host->cur_slot->mrq = NULL;
+ host->mrq = NULL;
+ if (!list_empty(&host->queue)) {
+ slot = list_entry(host->queue.next,
+ struct atmel_mci_slot, queue_node);
+ list_del(&slot->queue_node);
+ dev_vdbg(&host->pdev->dev, "list not empty: %s is next\n",
+ mmc_hostname(slot->mmc));
+ host->state = STATE_SENDING_CMD;
+ atmci_start_request(host, slot);
+ } else {
+ dev_vdbg(&host->pdev->dev, "list empty\n");
+ host->state = STATE_IDLE;
+ }
+
+ spin_unlock(&host->lock);
+ mmc_request_done(prev_mmc, mrq);
+ spin_lock(&host->lock);
+}
+
+static void atmci_command_complete(struct atmel_mci *host,
+ struct mmc_command *cmd)
+{
+ u32 status = host->cmd_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->pdev->dev,
+ "command error: status=0x%08x\n", status);
+
+ if (cmd->data) {
+ atmci_stop_dma(host);
+ 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_slot *slot = (struct atmel_mci_slot *)data;
+ bool present;
+ bool present_old;
+
+ /*
+ * atmci_cleanup_slot() sets the ATMCI_SHUTDOWN flag before
+ * freeing the interrupt. We must not re-enable the interrupt
+ * if it has been freed, and if we're shutting down, it
+ * doesn't really matter whether the card is present or not.
+ */
+ smp_rmb();
+ if (test_bit(ATMCI_SHUTDOWN, &slot->flags))
+ return;
+
+ enable_irq(gpio_to_irq(slot->detect_pin));
+ present = !(gpio_get_value(slot->detect_pin) ^
+ slot->detect_is_active_high);
+ present_old = test_bit(ATMCI_CARD_PRESENT, &slot->flags);
+
+ dev_vdbg(&slot->mmc->class_dev, "detect change: %d (was %d)\n",
+ present, present_old);
+
+ if (present != present_old) {
+ struct atmel_mci *host = slot->host;
+ struct mmc_request *mrq;
+
+ dev_dbg(&slot->mmc->class_dev, "card %s\n",
+ present ? "inserted" : "removed");
+
+ spin_lock(&host->lock);
+
+ if (!present)
+ clear_bit(ATMCI_CARD_PRESENT, &slot->flags);
+ else
+ set_bit(ATMCI_CARD_PRESENT, &slot->flags);
+
+ /* Clean up queue if present */
+ mrq = slot->mrq;
+ if (mrq) {
+ if (mrq == host->mrq) {
+ /*
+ * Reset controller to terminate any ongoing
+ * commands or data transfers.
+ */
+ mci_writel(host, CR, MCI_CR_SWRST);
+ mci_writel(host, CR, MCI_CR_MCIEN);
+ mci_writel(host, MR, host->mode_reg);
+ if (atmci_is_mci2())
+ mci_writel(host, CFG, host->cfg_reg);
+
+ host->data = NULL;
+ host->cmd = NULL;
+
+ switch (host->state) {
+ case STATE_IDLE:
+ break;
+ case STATE_SENDING_CMD:
+ mrq->cmd->error = -ENOMEDIUM;
+ if (!mrq->data)
+ break;
+ /* fall through */
+ case STATE_SENDING_DATA:
+ mrq->data->error = -ENOMEDIUM;
+ atmci_stop_dma(host);
+ break;
+ case STATE_DATA_BUSY:
+ case STATE_DATA_ERROR:
+ if (mrq->data->error == -EINPROGRESS)
+ mrq->data->error = -ENOMEDIUM;
+ if (!mrq->stop)
+ break;
+ /* fall through */
+ case STATE_SENDING_STOP:
+ mrq->stop->error = -ENOMEDIUM;
+ break;
+ }
+
+ atmci_request_end(host, mrq);
+ } else {
+ list_del(&slot->queue_node);
+ mrq->cmd->error = -ENOMEDIUM;
+ if (mrq->data)
+ mrq->data->error = -ENOMEDIUM;
+ if (mrq->stop)
+ mrq->stop->error = -ENOMEDIUM;
+
+ spin_unlock(&host->lock);
+ mmc_request_done(slot->mmc, mrq);
+ spin_lock(&host->lock);
+ }
+ }
+ spin_unlock(&host->lock);
+
+ mmc_detect_change(slot->mmc, 0);
+ }
+}
+
+static void atmci_tasklet_func(unsigned long priv)
+{
+ struct atmel_mci *host = (struct atmel_mci *)priv;
+ struct mmc_request *mrq = host->mrq;
+ struct mmc_data *data = host->data;
+ struct mmc_command *cmd = host->cmd;
+ enum atmel_mci_state state = host->state;
+ enum atmel_mci_state prev_state;
+ u32 status;
+
+ spin_lock(&host->lock);
+
+ state = host->state;
+
+ dev_vdbg(&host->pdev->dev,
+ "tasklet: state %u pending/completed/mask %lx/%lx/%x\n",
+ state, host->pending_events, host->completed_events,
+ mci_readl(host, IMR));
+
+ do {
+ prev_state = state;
+
+ switch (state) {
+ case STATE_IDLE:
+ break;
+
+ case STATE_SENDING_CMD:
+ if (!atmci_test_and_clear_pending(host,
+ EVENT_CMD_COMPLETE))
+ break;
+
+ host->cmd = NULL;
+ atmci_set_completed(host, EVENT_CMD_COMPLETE);
+ atmci_command_complete(host, mrq->cmd);
+ if (!mrq->data || cmd->error) {
+ atmci_request_end(host, host->mrq);
+ goto unlock;
+ }
+
+ prev_state = state = STATE_SENDING_DATA;
+ /* fall through */
+
+ case STATE_SENDING_DATA:
+ if (atmci_test_and_clear_pending(host,
+ EVENT_DATA_ERROR)) {
+ atmci_stop_dma(host);
+ if (data->stop)
+ send_stop_cmd(host, data);
+ state = STATE_DATA_ERROR;
+ break;
+ }
+
+ if (!atmci_test_and_clear_pending(host,
+ EVENT_XFER_COMPLETE))
+ break;
+
+ atmci_set_completed(host, EVENT_XFER_COMPLETE);
+ prev_state = state = STATE_DATA_BUSY;
+ /* fall through */
+
+ case STATE_DATA_BUSY:
+ if (!atmci_test_and_clear_pending(host,
+ EVENT_DATA_COMPLETE))
+ break;
+
+ host->data = NULL;
+ atmci_set_completed(host, EVENT_DATA_COMPLETE);
+ status = host->data_status;
+ if (unlikely(status & ATMCI_DATA_ERROR_FLAGS)) {
+ if (status & MCI_DTOE) {
+ dev_dbg(&host->pdev->dev,
+ "data timeout error\n");
+ data->error = -ETIMEDOUT;
+ } else if (status & MCI_DCRCE) {
+ dev_dbg(&host->pdev->dev,
+ "data CRC error\n");
+ data->error = -EILSEQ;
+ } else {
+ dev_dbg(&host->pdev->dev,
+ "data FIFO error (status=%08x)\n",
+ status);
+ data->error = -EIO;
+ }
+ } else {
+ data->bytes_xfered = data->blocks * data->blksz;
+ data->error = 0;
+ mci_writel(host, IDR, ATMCI_DATA_ERROR_FLAGS);
+ }
+
+ if (!data->stop) {
+ atmci_request_end(host, host->mrq);
+ goto unlock;
+ }
+
+ prev_state = state = STATE_SENDING_STOP;
+ if (!data->error)
+ send_stop_cmd(host, data);
+ /* fall through */
+
+ case STATE_SENDING_STOP:
+ if (!atmci_test_and_clear_pending(host,
+ EVENT_CMD_COMPLETE))
+ break;
+
+ host->cmd = NULL;
+ atmci_command_complete(host, mrq->stop);
+ atmci_request_end(host, host->mrq);
+ goto unlock;
+
+ case STATE_DATA_ERROR:
+ if (!atmci_test_and_clear_pending(host,
+ EVENT_XFER_COMPLETE))
+ break;
+
+ state = STATE_DATA_BUSY;
+ break;
+ }
+ } while (state != prev_state);
+
+ host->state = state;
+
+unlock:
+ spin_unlock(&host->lock);
+}
+
+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) {
+ flush_dcache_page(sg_page(sg));
+ 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;
+ data->bytes_xfered += nbytes;
+ smp_wmb();
+ atmci_set_pending(host, EVENT_DATA_ERROR);
+ tasklet_schedule(&host->tasklet);
+ return;
+ }
+ } 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;
+ smp_wmb();
+ atmci_set_pending(host, EVENT_XFER_COMPLETE);
+}
+
+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;
+ data->bytes_xfered += nbytes;
+ smp_wmb();
+ atmci_set_pending(host, EVENT_DATA_ERROR);
+ tasklet_schedule(&host->tasklet);
+ return;
+ }
+ } 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;
+ smp_wmb();
+ atmci_set_pending(host, EVENT_XFER_COMPLETE);
+}
+
+static void atmci_cmd_interrupt(struct atmel_mci *host, u32 status)
+{
+ mci_writel(host, IDR, MCI_CMDRDY);
+
+ host->cmd_status = status;
+ smp_wmb();
+ atmci_set_pending(host, EVENT_CMD_COMPLETE);
+ tasklet_schedule(&host->tasklet);
+}
+
+static void atmci_sdio_interrupt(struct atmel_mci *host, u32 status)
+{
+ int i;
+
+ for (i = 0; i < ATMEL_MCI_MAX_NR_SLOTS; i++) {
+ struct atmel_mci_slot *slot = host->slot[i];
+ if (slot && (status & slot->sdio_irq)) {
+ mmc_signal_sdio_irq(slot->mmc);
+ }
+ }
+}
+
+
+static irqreturn_t atmci_interrupt(int irq, void *dev_id)
+{
+ struct atmel_mci *host = dev_id;
+ u32 status, mask, pending;
+ unsigned int pass_count = 0;
+
+ 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;
+ smp_wmb();
+ atmci_set_pending(host, EVENT_DATA_ERROR);
+ tasklet_schedule(&host->tasklet);
+ }
+ if (pending & MCI_NOTBUSY) {
+ mci_writel(host, IDR,
+ ATMCI_DATA_ERROR_FLAGS | MCI_NOTBUSY);
+ if (!host->data_status)
+ host->data_status = status;
+ smp_wmb();
+ 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(host, status);
+
+ if (pending & (MCI_SDIOIRQA | MCI_SDIOIRQB))
+ atmci_sdio_interrupt(host, status);
+
+ } while (pass_count++ < 5);
+
+ return pass_count ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static irqreturn_t atmci_detect_interrupt(int irq, void *dev_id)
+{
+ struct atmel_mci_slot *slot = dev_id;
+
+ /*
+ * 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(&slot->detect_timer, jiffies + msecs_to_jiffies(20));
+
+ return IRQ_HANDLED;
+}
+
+static int __init atmci_init_slot(struct atmel_mci *host,
+ struct mci_slot_pdata *slot_data, unsigned int id,
+ u32 sdc_reg, u32 sdio_irq)
+{
+ struct mmc_host *mmc;
+ struct atmel_mci_slot *slot;
+
+ mmc = mmc_alloc_host(sizeof(struct atmel_mci_slot), &host->pdev->dev);
+ if (!mmc)
+ return -ENOMEM;
+
+ slot = mmc_priv(mmc);
+ slot->mmc = mmc;
+ slot->host = host;
+ slot->detect_pin = slot_data->detect_pin;
+ slot->wp_pin = slot_data->wp_pin;
+ slot->detect_is_active_high = slot_data->detect_is_active_high;
+ slot->sdc_reg = sdc_reg;
+ slot->sdio_irq = sdio_irq;
+
+ mmc->ops = &atmci_ops;
+ mmc->f_min = DIV_ROUND_UP(host->bus_hz, 512);
+ mmc->f_max = host->bus_hz / 2;
+ mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+ if (sdio_irq)
+ mmc->caps |= MMC_CAP_SDIO_IRQ;
+ if (atmci_is_mci2())
+ mmc->caps |= MMC_CAP_SD_HIGHSPEED;
+ if (slot_data->bus_width >= 4)
+ mmc->caps |= MMC_CAP_4_BIT_DATA;
+
+ mmc->max_segs = 64;
+ mmc->max_req_size = 32768 * 512;
+ mmc->max_blk_size = 32768;
+ mmc->max_blk_count = 512;
+
+ /* Assume card is present initially */
+ set_bit(ATMCI_CARD_PRESENT, &slot->flags);
+ if (gpio_is_valid(slot->detect_pin)) {
+ if (gpio_request(slot->detect_pin, "mmc_detect")) {
+ dev_dbg(&mmc->class_dev, "no detect pin available\n");
+ slot->detect_pin = -EBUSY;
+ } else if (gpio_get_value(slot->detect_pin) ^
+ slot->detect_is_active_high) {
+ clear_bit(ATMCI_CARD_PRESENT, &slot->flags);
+ }
+ }
+
+ if (!gpio_is_valid(slot->detect_pin))
+ mmc->caps |= MMC_CAP_NEEDS_POLL;
+
+ if (gpio_is_valid(slot->wp_pin)) {
+ if (gpio_request(slot->wp_pin, "mmc_wp")) {
+ dev_dbg(&mmc->class_dev, "no WP pin available\n");
+ slot->wp_pin = -EBUSY;
+ }
+ }
+
+ host->slot[id] = slot;
+ mmc_add_host(mmc);
+
+ if (gpio_is_valid(slot->detect_pin)) {
+ int ret;
+
+ setup_timer(&slot->detect_timer, atmci_detect_change,
+ (unsigned long)slot);
+
+ ret = request_irq(gpio_to_irq(slot->detect_pin),
+ atmci_detect_interrupt,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+ "mmc-detect", slot);
+ if (ret) {
+ dev_dbg(&mmc->class_dev,
+ "could not request IRQ %d for detect pin\n",
+ gpio_to_irq(slot->detect_pin));
+ gpio_free(slot->detect_pin);
+ slot->detect_pin = -EBUSY;
+ }
+ }
+
+ atmci_init_debugfs(slot);
+
+ return 0;
+}
+
+static void __exit atmci_cleanup_slot(struct atmel_mci_slot *slot,
+ unsigned int id)
+{
+ /* Debugfs stuff is cleaned up by mmc core */
+
+ set_bit(ATMCI_SHUTDOWN, &slot->flags);
+ smp_wmb();
+
+ mmc_remove_host(slot->mmc);
+
+ if (gpio_is_valid(slot->detect_pin)) {
+ int pin = slot->detect_pin;
+
+ free_irq(gpio_to_irq(pin), slot);
+ del_timer_sync(&slot->detect_timer);
+ gpio_free(pin);
+ }
+ if (gpio_is_valid(slot->wp_pin))
+ gpio_free(slot->wp_pin);
+
+ slot->host->slot[id] = NULL;
+ mmc_free_host(slot->mmc);
+}
+
+#ifdef CONFIG_MMC_ATMELMCI_DMA
+static bool filter(struct dma_chan *chan, void *slave)
+{
+ struct mci_dma_data *sl = slave;
+
+ if (sl && find_slave_dev(sl) == chan->device->dev) {
+ chan->private = slave_data_ptr(sl);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static void atmci_configure_dma(struct atmel_mci *host)
+{
+ struct mci_platform_data *pdata;
+
+ if (host == NULL)
+ return;
+
+ pdata = host->pdev->dev.platform_data;
+
+ if (pdata && find_slave_dev(pdata->dma_slave)) {
+ dma_cap_mask_t mask;
+
+ setup_dma_addr(pdata->dma_slave,
+ host->mapbase + MCI_TDR,
+ host->mapbase + MCI_RDR);
+
+ /* Try to grab a DMA channel */
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+ host->dma.chan =
+ dma_request_channel(mask, filter, pdata->dma_slave);
+ }
+ if (!host->dma.chan)
+ dev_notice(&host->pdev->dev, "DMA not available, using PIO\n");
+ else
+ dev_info(&host->pdev->dev,
+ "Using %s for DMA transfers\n",
+ dma_chan_name(host->dma.chan));
+}
+#else
+static void atmci_configure_dma(struct atmel_mci *host) {}
+#endif
+
+static int __init atmci_probe(struct platform_device *pdev)
+{
+ struct mci_platform_data *pdata;
+ struct atmel_mci *host;
+ struct resource *regs;
+ unsigned int nr_slots;
+ 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;
+
+ host = kzalloc(sizeof(struct atmel_mci), GFP_KERNEL);
+ if (!host)
+ return -ENOMEM;
+
+ host->pdev = pdev;
+ spin_lock_init(&host->lock);
+ INIT_LIST_HEAD(&host->queue);
+
+ 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, resource_size(regs));
+ 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;
+
+ tasklet_init(&host->tasklet, atmci_tasklet_func, (unsigned long)host);
+
+ ret = request_irq(irq, atmci_interrupt, 0, dev_name(&pdev->dev), host);
+ if (ret)
+ goto err_request_irq;
+
+ atmci_configure_dma(host);
+
+ platform_set_drvdata(pdev, host);
+
+ /* We need at least one slot to succeed */
+ nr_slots = 0;
+ ret = -ENODEV;
+ if (pdata->slot[0].bus_width) {
+ ret = atmci_init_slot(host, &pdata->slot[0],
+ 0, MCI_SDCSEL_SLOT_A, MCI_SDIOIRQA);
+ if (!ret)
+ nr_slots++;
+ }
+ if (pdata->slot[1].bus_width) {
+ ret = atmci_init_slot(host, &pdata->slot[1],
+ 1, MCI_SDCSEL_SLOT_B, MCI_SDIOIRQB);
+ if (!ret)
+ nr_slots++;
+ }
+
+ if (!nr_slots) {
+ dev_err(&pdev->dev, "init failed: no slot defined\n");
+ goto err_init_slot;
+ }
+
+ dev_info(&pdev->dev,
+ "Atmel MCI controller at 0x%08lx irq %d, %u slots\n",
+ host->mapbase, irq, nr_slots);
+
+ return 0;
+
+err_init_slot:
+#ifdef CONFIG_MMC_ATMELMCI_DMA
+ if (host->dma.chan)
+ dma_release_channel(host->dma.chan);
+#endif
+ free_irq(irq, host);
+err_request_irq:
+ iounmap(host->regs);
+err_ioremap:
+ clk_put(host->mck);
+err_clk_get:
+ kfree(host);
+ return ret;
+}
+
+static int __exit atmci_remove(struct platform_device *pdev)
+{
+ struct atmel_mci *host = platform_get_drvdata(pdev);
+ unsigned int i;
+
+ platform_set_drvdata(pdev, NULL);
+
+ for (i = 0; i < ATMEL_MCI_MAX_NR_SLOTS; i++) {
+ if (host->slot[i])
+ atmci_cleanup_slot(host->slot[i], i);
+ }
+
+ clk_enable(host->mck);
+ mci_writel(host, IDR, ~0UL);
+ mci_writel(host, CR, MCI_CR_MCIDIS);
+ mci_readl(host, SR);
+ clk_disable(host->mck);
+
+#ifdef CONFIG_MMC_ATMELMCI_DMA
+ if (host->dma.chan)
+ dma_release_channel(host->dma.chan);
+#endif
+
+ free_irq(platform_get_irq(pdev, 0), host);
+ iounmap(host->regs);
+
+ clk_put(host->mck);
+ kfree(host);
+
+ 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);
+}
+
+late_initcall(atmci_init); /* try to load after dma driver when built-in */
+module_exit(atmci_exit);
+
+MODULE_DESCRIPTION("Atmel Multimedia Card Interface driver");
+MODULE_AUTHOR("Haavard Skinnemoen (Atmel)");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mmc/host/au1xmmc.c b/drivers/mmc/host/au1xmmc.c
new file mode 100644
index 00000000..ef72e874
--- /dev/null
+++ b/drivers/mmc/host/au1xmmc.c
@@ -0,0 +1,1211 @@
+/*
+ * linux/drivers/mmc/host/au1xmmc.c - AU1XX0 MMC driver
+ *
+ * Copyright (c) 2005, Advanced Micro Devices, Inc.
+ *
+ * Developed with help from the 2.4.30 MMC AU1XXX controller including
+ * the following copyright notices:
+ * Copyright (c) 2003-2004 Embedded Edge, LLC.
+ * Portions Copyright (C) 2002 Embedix, Inc
+ * Copyright 2002 Hewlett-Packard Company
+
+ * 2.6 version of this driver inspired by:
+ * (drivers/mmc/wbsd.c) Copyright (C) 2004-2005 Pierre Ossman,
+ * All Rights Reserved.
+ * (drivers/mmc/pxa.c) Copyright (C) 2003 Russell King,
+ * All Rights Reserved.
+ *
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/* Why 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
+ * and 1- or 4-data bit SecureDigital cards, then the solution is to
+ * connect a weak (560KOhm) pull-up resistor to connector pin 1.
+ * In doing so, a MMC card never enters SPI-mode communications,
+ * but now the SecureDigital card-detect feature of CD/DAT3 is ineffective
+ * (the low to high transition will not occur).
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/scatterlist.h>
+#include <linux/leds.h>
+#include <linux/mmc/host.h>
+#include <linux/slab.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>
+
+#define DRIVER_NAME "au1xxx-mmc"
+
+/* Set this to enable special debugging macros */
+/* #define DEBUG */
+
+#ifdef DEBUG
+#define DBG(fmt, idx, args...) \
+ printk(KERN_DEBUG "au1xmmc(%d): DEBUG: " fmt, idx, ##args)
+#else
+#define DBG(fmt, idx, args...) do {} while (0)
+#endif
+
+/* Hardware definitions */
+#define AU1XMMC_DESCRIPTOR_COUNT 1
+
+/* max DMA seg size: 64KB on Au1100, 4MB on Au1200 */
+#ifdef CONFIG_SOC_AU1100
+#define AU1XMMC_DESCRIPTOR_SIZE 0x0000ffff
+#else /* Au1200 */
+#define AU1XMMC_DESCRIPTOR_SIZE 0x003fffff
+#endif
+
+#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 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;
+
+ 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)
+{
+ u32 val = au_readl(HOST_CONFIG(host));
+ val |= mask;
+ au_writel(val, HOST_CONFIG(host));
+ au_sync();
+}
+
+static inline void FLUSH_FIFO(struct au1xmmc_host *host)
+{
+ u32 val = au_readl(HOST_CONFIG2(host));
+
+ au_writel(val | SD_CONFIG2_FF, HOST_CONFIG2(host));
+ au_sync_delay(1);
+
+ /* SEND_STOP will turn off clock control - this re-enables it */
+ val &= ~SD_CONFIG2_DF;
+
+ au_writel(val, HOST_CONFIG2(host));
+ au_sync();
+}
+
+static inline void IRQ_OFF(struct au1xmmc_host *host, u32 mask)
+{
+ u32 val = au_readl(HOST_CONFIG(host));
+ val &= ~mask;
+ au_writel(val, HOST_CONFIG(host));
+ au_sync();
+}
+
+static inline void SEND_STOP(struct au1xmmc_host *host)
+{
+ u32 config2;
+
+ WARN_ON(host->status != HOST_S_DATA);
+ host->status = HOST_S_STOP;
+
+ config2 = au_readl(HOST_CONFIG2(host));
+ au_writel(config2 | SD_CONFIG2_DF, HOST_CONFIG2(host));
+ au_sync();
+
+ /* Send the stop command */
+ au_writel(STOP_CMD, HOST_CMD(host));
+}
+
+static void au1xmmc_set_power(struct au1xmmc_host *host, int state)
+{
+ if (host->platdata && host->platdata->set_power)
+ host->platdata->set_power(host->mmc, state);
+}
+
+static int au1xmmc_card_inserted(struct mmc_host *mmc)
+{
+ 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);
+
+ 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_F_DMA;
+
+ host->dma.len = 0;
+ host->dma.dir = 0;
+
+ host->pio.index = 0;
+ host->pio.offset = 0;
+ host->pio.len = 0;
+
+ host->status = HOST_S_IDLE;
+
+ mmc_request_done(host->mmc, mrq);
+}
+
+static void au1xmmc_tasklet_finish(unsigned long param)
+{
+ struct au1xmmc_host *host = (struct au1xmmc_host *) param;
+ au1xmmc_finish_request(host);
+}
+
+static int au1xmmc_send_command(struct au1xmmc_host *host, int wait,
+ struct mmc_command *cmd, struct mmc_data *data)
+{
+ u32 mmccmd = (cmd->opcode << SD_CMD_CI_SHIFT);
+
+ switch (mmc_resp_type(cmd)) {
+ case MMC_RSP_NONE:
+ break;
+ case MMC_RSP_R1:
+ mmccmd |= SD_CMD_RT_1;
+ break;
+ case MMC_RSP_R1B:
+ mmccmd |= SD_CMD_RT_1B;
+ break;
+ case MMC_RSP_R2:
+ mmccmd |= SD_CMD_RT_2;
+ break;
+ case MMC_RSP_R3:
+ mmccmd |= SD_CMD_RT_3;
+ break;
+ default:
+ printk(KERN_INFO "au1xmmc: unhandled response type %02x\n",
+ mmc_resp_type(cmd));
+ return -EINVAL;
+ }
+
+ if (data) {
+ if (data->flags & MMC_DATA_READ) {
+ if (data->blocks > 1)
+ mmccmd |= SD_CMD_CT_4;
+ else
+ mmccmd |= SD_CMD_CT_2;
+ } else if (data->flags & MMC_DATA_WRITE) {
+ if (data->blocks > 1)
+ mmccmd |= SD_CMD_CT_3;
+ else
+ mmccmd |= SD_CMD_CT_1;
+ }
+ }
+
+ au_writel(cmd->arg, HOST_CMDARG(host));
+ au_sync();
+
+ if (wait)
+ IRQ_OFF(host, SD_CONFIG_CR);
+
+ au_writel((mmccmd | SD_CMD_GO), HOST_CMD(host));
+ au_sync();
+
+ /* Wait for the command to go on the line */
+ while (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))
+ status = au_readl(HOST_STATUS(host));
+
+ /* Clear the CR status */
+ au_writel(SD_STATUS_CR, HOST_STATUS(host));
+
+ IRQ_ON(host, SD_CONFIG_CR);
+ }
+
+ return 0;
+}
+
+static void au1xmmc_data_complete(struct au1xmmc_host *host, u32 status)
+{
+ struct mmc_request *mrq = host->mrq;
+ struct mmc_data *data;
+ u32 crc;
+
+ WARN_ON((host->status != HOST_S_DATA) && (host->status != HOST_S_STOP));
+
+ if (host->mrq == NULL)
+ return;
+
+ data = mrq->cmd->data;
+
+ if (status == 0)
+ status = au_readl(HOST_STATUS(host));
+
+ /* The transaction is really over when the SD_STATUS_DB bit is clear */
+ while ((host->flags & HOST_F_XMIT) && (status & SD_STATUS_DB))
+ status = au_readl(HOST_STATUS(host));
+
+ data->error = 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;
+
+ if (crc)
+ data->error = -EILSEQ;
+
+ /* Clear the CRC bits */
+ au_writel(SD_STATUS_WC | SD_STATUS_RC, HOST_STATUS(host));
+
+ data->bytes_xfered = 0;
+
+ 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);
+ au1x_dma_chan_t *cp = c->chan_ptr;
+ data->bytes_xfered = cp->ddma_bytecnt;
+#endif
+ } else
+ data->bytes_xfered =
+ (data->blocks * data->blksz) - host->pio.len;
+ }
+
+ au1xmmc_finish_request(host);
+}
+
+static void au1xmmc_tasklet_data(unsigned long param)
+{
+ struct au1xmmc_host *host = (struct au1xmmc_host *)param;
+
+ u32 status = au_readl(HOST_STATUS(host));
+ au1xmmc_data_complete(host, status);
+}
+
+#define AU1XMMC_MAX_TRANSFER 8
+
+static void au1xmmc_send_pio(struct au1xmmc_host *host)
+{
+ struct mmc_data *data;
+ int sg_len, max, count;
+ unsigned char *sg_ptr, val;
+ u32 status;
+ struct scatterlist *sg;
+
+ data = host->mrq->data;
+
+ if (!(host->flags & HOST_F_XMIT))
+ return;
+
+ /* This is the pointer to the data buffer */
+ sg = &data->sg[host->pio.index];
+ sg_ptr = sg_virt(sg) + host->pio.offset;
+
+ /* This is the space left inside the buffer */
+ sg_len = data->sg[host->pio.index].length - host->pio.offset;
+
+ /* 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++) {
+ status = au_readl(HOST_STATUS(host));
+
+ if (!(status & SD_STATUS_TH))
+ break;
+
+ val = *sg_ptr++;
+
+ au_writel((unsigned long)val, HOST_TXPORT(host));
+ au_sync();
+ }
+
+ host->pio.len -= count;
+ host->pio.offset += count;
+
+ if (count == sg_len) {
+ host->pio.index++;
+ host->pio.offset = 0;
+ }
+
+ if (host->pio.len == 0) {
+ IRQ_OFF(host, SD_CONFIG_TH);
+
+ if (host->flags & HOST_F_STOP)
+ SEND_STOP(host);
+
+ tasklet_schedule(&host->data_task);
+ }
+}
+
+static void au1xmmc_receive_pio(struct au1xmmc_host *host)
+{
+ struct mmc_data *data;
+ int max, count, sg_len = 0;
+ unsigned char *sg_ptr = NULL;
+ u32 status, val;
+ struct scatterlist *sg;
+
+ data = host->mrq->data;
+
+ if (!(host->flags & HOST_F_RECV))
+ return;
+
+ max = host->pio.len;
+
+ if (host->pio.index < host->dma.len) {
+ sg = &data->sg[host->pio.index];
+ sg_ptr = sg_virt(sg) + host->pio.offset;
+
+ /* This is the space left inside the buffer */
+ sg_len = sg_dma_len(&data->sg[host->pio.index]) - host->pio.offset;
+
+ /* Check 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++) {
+ 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->pdev->id,
+ host->pio.len, count);
+ break;
+ }
+
+ if (status & SD_STATUS_RO) {
+ 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->pdev->id,
+ host->pio.len, count);
+ break;
+ }
+
+ val = au_readl(HOST_RXPORT(host));
+
+ if (sg_ptr)
+ *sg_ptr++ = (unsigned char)(val & 0xFF);
+ }
+
+ host->pio.len -= count;
+ host->pio.offset += count;
+
+ if (sg_len && count == sg_len) {
+ host->pio.index++;
+ host->pio.offset = 0;
+ }
+
+ if (host->pio.len == 0) {
+ /* IRQ_OFF(host, SD_CONFIG_RA | SD_CONFIG_RF); */
+ IRQ_OFF(host, SD_CONFIG_NE);
+
+ if (host->flags & HOST_F_STOP)
+ SEND_STOP(host);
+
+ tasklet_schedule(&host->data_task);
+ }
+}
+
+/* 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;
+ u32 r[4];
+ int i, trans;
+
+ if (!host->mrq)
+ return;
+
+ cmd = mrq->cmd;
+ cmd->error = 0;
+
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ if (cmd->flags & MMC_RSP_136) {
+ r[0] = au_readl(host->iobase + SD_RESP3);
+ r[1] = au_readl(host->iobase + SD_RESP2);
+ r[2] = au_readl(host->iobase + SD_RESP1);
+ r[3] = au_readl(host->iobase + SD_RESP0);
+
+ /* The CRC is omitted from the response, so really
+ * we only got 120 bytes, but the engine expects
+ * 128 bits, so we have to shift things up.
+ */
+ for (i = 0; i < 4; i++) {
+ cmd->resp[i] = (r[i] & 0x00FFFFFF) << 8;
+ if (i != 3)
+ cmd->resp[i] |= (r[i + 1] & 0xFF000000) >> 24;
+ }
+ } else {
+ /* Techincally, we should be getting all 48 bits of
+ * the response (SD_RESP1 + SD_RESP2), but because
+ * our response omits the CRC, our data ends up
+ * being shifted 8 bits to the right. In this case,
+ * that means that the OSR data starts at bit 31,
+ * so we can just read RESP0 and return that.
+ */
+ cmd->resp[0] = au_readl(host->iobase + SD_RESP0);
+ }
+ }
+
+ /* 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);
+ tasklet_schedule(&host->finish_task);
+ return;
+ }
+
+ 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 */
+
+ if (host->flags & HOST_F_RECV) {
+ u32 mask = SD_STATUS_DB | SD_STATUS_NE;
+
+ while((status & mask) != mask)
+ status = au_readl(HOST_STATUS(host));
+ }
+
+ au1xxx_dbdma_start(channel);
+#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
+ */
+ pbus /= ((au_readl(SYS_POWERCTRL) & 0x3) + 2);
+ pbus /= 2;
+ divisor = ((pbus / rate) / 2) - 1;
+
+ config = au_readl(HOST_CONFIG(host));
+
+ config &= ~(SD_CONFIG_DIV);
+ config |= (divisor & SD_CONFIG_DIV) | SD_CONFIG_DE;
+
+ au_writel(config, HOST_CONFIG(host));
+ au_sync();
+}
+
+static int au1xmmc_prepare_data(struct au1xmmc_host *host,
+ struct mmc_data *data)
+{
+ int datalen = data->blocks * data->blksz;
+
+ if (data->flags & MMC_DATA_READ)
+ host->flags |= HOST_F_RECV;
+ else
+ host->flags |= HOST_F_XMIT;
+
+ if (host->mrq->stop)
+ host->flags |= HOST_F_STOP;
+
+ host->dma.dir = DMA_BIDIRECTIONAL;
+
+ host->dma.len = dma_map_sg(mmc_dev(host->mmc), data->sg,
+ data->sg_len, host->dma.dir);
+
+ if (host->dma.len == 0)
+ return -ETIMEDOUT;
+
+ 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++) {
+ u32 ret = 0, flags = DDMA_FLAGS_NOIE;
+ struct scatterlist *sg = &data->sg[i];
+ int sg_len = sg->length;
+
+ int len = (datalen > sg_len) ? sg_len : datalen;
+
+ if (i == host->dma.len - 1)
+ flags = DDMA_FLAGS_IE;
+
+ if (host->flags & HOST_F_XMIT) {
+ ret = au1xxx_dbdma_put_source(channel,
+ sg_phys(sg), len, flags);
+ } else {
+ ret = au1xxx_dbdma_put_dest(channel,
+ sg_phys(sg), len, flags);
+ }
+
+ if (!ret)
+ goto dataerr;
+
+ datalen -= len;
+ }
+#endif
+ } else {
+ host->pio.index = 0;
+ host->pio.offset = 0;
+ host->pio.len = datalen;
+
+ if (host->flags & HOST_F_XMIT)
+ IRQ_ON(host, SD_CONFIG_TH);
+ else
+ IRQ_ON(host, SD_CONFIG_NE);
+ /* IRQ_ON(host, SD_CONFIG_RA | SD_CONFIG_RF); */
+ }
+
+ return 0;
+
+dataerr:
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+ host->dma.dir);
+ return -ETIMEDOUT;
+}
+
+/* This actually starts a command or data transaction */
+static void au1xmmc_request(struct mmc_host* mmc, struct mmc_request* mrq)
+{
+ struct au1xmmc_host *host = mmc_priv(mmc);
+ int ret = 0;
+
+ WARN_ON(irqs_disabled());
+ WARN_ON(host->status != HOST_S_IDLE);
+
+ host->mrq = mrq;
+ host->status = HOST_S_CMD;
+
+ /* 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);
+ ret = au1xmmc_prepare_data(host, mrq->data);
+ }
+
+ if (!ret)
+ ret = au1xmmc_send_command(host, 0, mrq->cmd, mrq->data);
+
+ if (ret) {
+ mrq->cmd->error = ret;
+ au1xmmc_finish_request(host);
+ }
+}
+
+static void au1xmmc_reset_controller(struct au1xmmc_host *host)
+{
+ /* Apply the clock */
+ au_writel(SD_ENABLE_CE, HOST_ENABLE(host));
+ au_sync_delay(1);
+
+ au_writel(SD_ENABLE_R | SD_ENABLE_CE, HOST_ENABLE(host));
+ au_sync_delay(5);
+
+ au_writel(~0, HOST_STATUS(host));
+ au_sync();
+
+ au_writel(0, HOST_BLKSIZE(host));
+ au_writel(0x001fffff, HOST_TIMEOUT(host));
+ au_sync();
+
+ au_writel(SD_CONFIG2_EN, HOST_CONFIG2(host));
+ au_sync();
+
+ au_writel(SD_CONFIG2_EN | SD_CONFIG2_FF, HOST_CONFIG2(host));
+ au_sync_delay(1);
+
+ au_writel(SD_CONFIG2_EN, HOST_CONFIG2(host));
+ au_sync();
+
+ /* Configure interrupts */
+ au_writel(AU1XMMC_INTERRUPTS, HOST_CONFIG(host));
+ au_sync();
+}
+
+
+static void au1xmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct au1xmmc_host *host = mmc_priv(mmc);
+ u32 config2;
+
+ if (ios->power_mode == MMC_POWER_OFF)
+ au1xmmc_set_power(host, 0);
+ else if (ios->power_mode == MMC_POWER_ON) {
+ au1xmmc_set_power(host, 1);
+ }
+
+ if (ios->clock && ios->clock != host->clock) {
+ au1xmmc_set_clock(host, ios->clock);
+ host->clock = ios->clock;
+ }
+
+ 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)
+#define STATUS_DATA_IN (SD_STATUS_NE)
+#define STATUS_DATA_OUT (SD_STATUS_TH)
+
+static irqreturn_t au1xmmc_irq(int irq, void *dev_id)
+{
+ struct au1xmmc_host *host = dev_id;
+ u32 status;
+
+ status = au_readl(HOST_STATUS(host));
+
+ if (!(status & SD_STATUS_I))
+ return IRQ_NONE; /* not ours */
+
+ if (status & SD_STATUS_SI) /* SDIO */
+ mmc_signal_sdio_irq(host->mmc);
+
+ 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);
+
+ /* IRQ_OFF(host, SD_CONFIG_TH | SD_CONFIG_RA | SD_CONFIG_RF); */
+ tasklet_schedule(&host->finish_task);
+ }
+#if 0
+ else if (status & SD_STATUS_DD) {
+ /* Sometimes we get a DD before a NE in PIO mode */
+ if (!(host->flags & HOST_F_DMA) && (status & SD_STATUS_NE))
+ au1xmmc_receive_pio(host);
+ else {
+ au1xmmc_data_complete(host, status);
+ /* tasklet_schedule(&host->data_task); */
+ }
+ }
+#endif
+ else if (status & SD_STATUS_CR) {
+ if (host->status == HOST_S_CMD)
+ au1xmmc_cmd_complete(host, status);
+
+ } else if (!(host->flags & HOST_F_DMA)) {
+ if ((host->flags & HOST_F_XMIT) && (status & STATUS_DATA_OUT))
+ au1xmmc_send_pio(host);
+ else if ((host->flags & HOST_F_RECV) && (status & STATUS_DATA_IN))
+ au1xmmc_receive_pio(host);
+
+ } else if (status & 0x203F3C70) {
+ DBG("Unhandled status %8.8x\n", host->pdev->id,
+ status);
+ }
+
+ au_writel(status, HOST_STATUS(host));
+ au_sync();
+
+ return IRQ_HANDLED;
+}
+
+#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;
+
+static void au1xmmc_dbdma_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);
+}
+
+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;
+ }
+
+ 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;
+ }
+
+ au1xxx_dbdma_set_devwidth(host->tx_chan, 8);
+ au1xxx_dbdma_set_devwidth(host->rx_chan, 8);
+
+ au1xxx_dbdma_ring_alloc(host->tx_chan, AU1XMMC_DESCRIPTOR_COUNT);
+ au1xxx_dbdma_ring_alloc(host->rx_chan, AU1XMMC_DESCRIPTOR_COUNT);
+
+ /* DBDMA is good to go */
+ host->flags |= HOST_F_DMA;
+
+ return 0;
+}
+
+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
+
+static void au1xmmc_enable_sdio_irq(struct mmc_host *mmc, int en)
+{
+ struct au1xmmc_host *host = mmc_priv(mmc);
+
+ 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;
+ }
+
+ host = mmc_priv(mmc);
+ host->mmc = mmc;
+ host->platdata = pdev->dev.platform_data;
+ host->pdev = pdev;
+
+ ret = -ENODEV;
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!r) {
+ dev_err(&pdev->dev, "no mmio defined\n");
+ goto out1;
+ }
+
+ host->ioarea = request_mem_region(r->start, resource_size(r),
+ pdev->name);
+ if (!host->ioarea) {
+ dev_err(&pdev->dev, "mmio already in use\n");
+ goto out1;
+ }
+
+ host->iobase = (unsigned long)ioremap(r->start, 0x3c);
+ if (!host->iobase) {
+ dev_err(&pdev->dev, "cannot remap mmio\n");
+ goto out2;
+ }
+
+ r = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!r) {
+ dev_err(&pdev->dev, "no IRQ defined\n");
+ goto out3;
+ }
+
+ 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->ops = &au1xmmc_ops;
+
+ mmc->f_min = 450000;
+ mmc->f_max = 24000000;
+
+ mmc->max_seg_size = AU1XMMC_DESCRIPTOR_SIZE;
+ mmc->max_segs = AU1XMMC_DESCRIPTOR_COUNT;
+
+ mmc->max_blk_size = 2048;
+ mmc->max_blk_count = 512;
+
+ mmc->ocr_avail = AU1XMMC_OCR;
+ mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;
+
+ 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;
+
+ /* platform may not be able to use all advertised caps */
+ if (host->platdata)
+ mmc->caps &= ~(host->platdata->mask_host_caps);
+
+ tasklet_init(&host->data_task, au1xmmc_tasklet_data,
+ (unsigned long)host);
+
+ tasklet_init(&host->finish_task, au1xmmc_tasklet_finish,
+ (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
+
+#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
+
+ au1xmmc_reset_controller(host);
+
+ ret = mmc_add_host(mmc);
+ if (ret) {
+ dev_err(&pdev->dev, "cannot add mmc host\n");
+ goto out6;
+ }
+
+ platform_set_drvdata(pdev, 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");
+
+ return 0; /* all ok */
+
+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();
+
+#ifdef CONFIG_SOC_AU1200
+ au1xmmc_dbdma_shutdown(host);
+#endif
+
+ 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 au1xmmc_host *host = platform_get_drvdata(pdev);
+
+ if (host) {
+ mmc_remove_host(host->mmc);
+
+#ifdef CONFIG_LEDS_CLASS
+ if (host->platdata && host->platdata->led)
+ led_classdev_unregister(host->platdata->led);
+#endif
+
+ if (host->platdata && host->platdata->cd_setup &&
+ !(host->mmc->caps & MMC_CAP_NEEDS_POLL))
+ host->platdata->cd_setup(host->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);
+
+#ifdef CONFIG_SOC_AU1200
+ au1xmmc_dbdma_shutdown(host);
+#endif
+ au1xmmc_set_power(host, 0);
+
+ free_irq(host->irq, host);
+ iounmap((void *)host->iobase);
+ release_resource(host->ioarea);
+ kfree(host->ioarea);
+
+ mmc_free_host(host->mmc);
+ platform_set_drvdata(pdev, NULL);
+ }
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int au1xmmc_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct au1xmmc_host *host = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = mmc_suspend_host(host->mmc);
+ if (ret)
+ return ret;
+
+ au_writel(0, HOST_CONFIG2(host));
+ au_writel(0, HOST_CONFIG(host));
+ au_writel(0xffffffff, HOST_STATUS(host));
+ au_writel(0, HOST_ENABLE(host));
+ au_sync();
+
+ return 0;
+}
+
+static int au1xmmc_resume(struct platform_device *pdev)
+{
+ struct au1xmmc_host *host = platform_get_drvdata(pdev);
+
+ au1xmmc_reset_controller(host);
+
+ return mmc_resume_host(host->mmc);
+}
+#else
+#define au1xmmc_suspend NULL
+#define au1xmmc_resume NULL
+#endif
+
+static struct platform_driver au1xmmc_driver = {
+ .probe = au1xmmc_probe,
+ .remove = au1xmmc_remove,
+ .suspend = au1xmmc_suspend,
+ .resume = au1xmmc_resume,
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+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);
+
+MODULE_AUTHOR("Advanced Micro Devices, Inc");
+MODULE_DESCRIPTION("MMC/SD driver for the Alchemy Au1XXX");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:au1xxx-mmc");
diff --git a/drivers/mmc/host/bfin_sdh.c b/drivers/mmc/host/bfin_sdh.c
new file mode 100644
index 00000000..0371bf50
--- /dev/null
+++ b/drivers/mmc/host/bfin_sdh.c
@@ -0,0 +1,644 @@
+/*
+ * bfin_sdh.c - Analog Devices Blackfin SDH Controller
+ *
+ * Copyright (C) 2007-2009 Analog Device Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#define DRIVER_NAME "bfin-sdh"
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/mmc/host.h>
+#include <linux/proc_fs.h>
+#include <linux/gfp.h>
+
+#include <asm/cacheflush.h>
+#include <asm/dma.h>
+#include <asm/portmux.h>
+#include <asm/bfin_sdh.h>
+
+#if defined(CONFIG_BF51x)
+#define bfin_read_SDH_PWR_CTL bfin_read_RSI_PWR_CTL
+#define bfin_write_SDH_PWR_CTL bfin_write_RSI_PWR_CTL
+#define bfin_read_SDH_CLK_CTL bfin_read_RSI_CLK_CTL
+#define bfin_write_SDH_CLK_CTL bfin_write_RSI_CLK_CTL
+#define bfin_write_SDH_ARGUMENT bfin_write_RSI_ARGUMENT
+#define bfin_write_SDH_COMMAND bfin_write_RSI_COMMAND
+#define bfin_write_SDH_DATA_TIMER bfin_write_RSI_DATA_TIMER
+#define bfin_read_SDH_RESPONSE0 bfin_read_RSI_RESPONSE0
+#define bfin_read_SDH_RESPONSE1 bfin_read_RSI_RESPONSE1
+#define bfin_read_SDH_RESPONSE2 bfin_read_RSI_RESPONSE2
+#define bfin_read_SDH_RESPONSE3 bfin_read_RSI_RESPONSE3
+#define bfin_write_SDH_DATA_LGTH bfin_write_RSI_DATA_LGTH
+#define bfin_read_SDH_DATA_CTL bfin_read_RSI_DATA_CTL
+#define bfin_write_SDH_DATA_CTL bfin_write_RSI_DATA_CTL
+#define bfin_read_SDH_DATA_CNT bfin_read_RSI_DATA_CNT
+#define bfin_write_SDH_STATUS_CLR bfin_write_RSI_STATUS_CLR
+#define bfin_read_SDH_E_STATUS bfin_read_RSI_E_STATUS
+#define bfin_write_SDH_E_STATUS bfin_write_RSI_E_STATUS
+#define bfin_read_SDH_STATUS bfin_read_RSI_STATUS
+#define bfin_write_SDH_MASK0 bfin_write_RSI_MASK0
+#define bfin_read_SDH_CFG bfin_read_RSI_CFG
+#define bfin_write_SDH_CFG bfin_write_RSI_CFG
+#endif
+
+struct dma_desc_array {
+ unsigned long start_addr;
+ unsigned short cfg;
+ unsigned short x_count;
+ short x_modify;
+} __packed;
+
+struct sdh_host {
+ struct mmc_host *mmc;
+ spinlock_t lock;
+ struct resource *res;
+ void __iomem *base;
+ int irq;
+ int stat_irq;
+ int dma_ch;
+ int dma_dir;
+ struct dma_desc_array *sg_cpu;
+ dma_addr_t sg_dma;
+ int dma_len;
+
+ unsigned int imask;
+ unsigned int power_mode;
+ unsigned int clk_div;
+
+ struct mmc_request *mrq;
+ struct mmc_command *cmd;
+ struct mmc_data *data;
+};
+
+static struct bfin_sd_host *get_sdh_data(struct platform_device *pdev)
+{
+ return pdev->dev.platform_data;
+}
+
+static void sdh_stop_clock(struct sdh_host *host)
+{
+ bfin_write_SDH_CLK_CTL(bfin_read_SDH_CLK_CTL() & ~CLK_E);
+ SSYNC();
+}
+
+static void sdh_enable_stat_irq(struct sdh_host *host, unsigned int mask)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+ host->imask |= mask;
+ bfin_write_SDH_MASK0(mask);
+ SSYNC();
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void sdh_disable_stat_irq(struct sdh_host *host, unsigned int mask)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+ host->imask &= ~mask;
+ bfin_write_SDH_MASK0(host->imask);
+ SSYNC();
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static int sdh_setup_data(struct sdh_host *host, struct mmc_data *data)
+{
+ unsigned int length;
+ unsigned int data_ctl;
+ unsigned int dma_cfg;
+ unsigned int cycle_ns, timeout;
+
+ dev_dbg(mmc_dev(host->mmc), "%s enter flags: 0x%x\n", __func__, data->flags);
+ host->data = data;
+ data_ctl = 0;
+ dma_cfg = 0;
+
+ length = data->blksz * data->blocks;
+ bfin_write_SDH_DATA_LGTH(length);
+
+ if (data->flags & MMC_DATA_STREAM)
+ data_ctl |= DTX_MODE;
+
+ if (data->flags & MMC_DATA_READ)
+ data_ctl |= DTX_DIR;
+ /* Only supports power-of-2 block size */
+ if (data->blksz & (data->blksz - 1))
+ return -EINVAL;
+ data_ctl |= ((ffs(data->blksz) - 1) << 4);
+
+ bfin_write_SDH_DATA_CTL(data_ctl);
+ /* the time of a host clock period in ns */
+ cycle_ns = 1000000000 / (get_sclk() / (2 * (host->clk_div + 1)));
+ timeout = data->timeout_ns / cycle_ns;
+ timeout += data->timeout_clks;
+ bfin_write_SDH_DATA_TIMER(timeout);
+ SSYNC();
+
+ if (data->flags & MMC_DATA_READ) {
+ host->dma_dir = DMA_FROM_DEVICE;
+ dma_cfg |= WNR;
+ } else
+ host->dma_dir = DMA_TO_DEVICE;
+
+ sdh_enable_stat_irq(host, (DAT_CRC_FAIL | DAT_TIME_OUT | DAT_END));
+ host->dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, host->dma_dir);
+#if defined(CONFIG_BF54x)
+ dma_cfg |= DMAFLOW_ARRAY | NDSIZE_5 | RESTART | WDSIZE_32 | DMAEN;
+ {
+ struct scatterlist *sg;
+ int i;
+ for_each_sg(data->sg, sg, host->dma_len, i) {
+ host->sg_cpu[i].start_addr = sg_dma_address(sg);
+ host->sg_cpu[i].cfg = dma_cfg;
+ host->sg_cpu[i].x_count = sg_dma_len(sg) / 4;
+ host->sg_cpu[i].x_modify = 4;
+ dev_dbg(mmc_dev(host->mmc), "%d: start_addr:0x%lx, "
+ "cfg:0x%x, x_count:0x%x, x_modify:0x%x\n",
+ i, host->sg_cpu[i].start_addr,
+ host->sg_cpu[i].cfg, host->sg_cpu[i].x_count,
+ host->sg_cpu[i].x_modify);
+ }
+ }
+ flush_dcache_range((unsigned int)host->sg_cpu,
+ (unsigned int)host->sg_cpu +
+ host->dma_len * sizeof(struct dma_desc_array));
+ /* Set the last descriptor to stop mode */
+ host->sg_cpu[host->dma_len - 1].cfg &= ~(DMAFLOW | NDSIZE);
+ host->sg_cpu[host->dma_len - 1].cfg |= DI_EN;
+
+ set_dma_curr_desc_addr(host->dma_ch, (unsigned long *)host->sg_dma);
+ set_dma_x_count(host->dma_ch, 0);
+ set_dma_x_modify(host->dma_ch, 0);
+ set_dma_config(host->dma_ch, dma_cfg);
+#elif defined(CONFIG_BF51x)
+ /* RSI DMA doesn't work in array mode */
+ dma_cfg |= WDSIZE_32 | DMAEN;
+ set_dma_start_addr(host->dma_ch, sg_dma_address(&data->sg[0]));
+ set_dma_x_count(host->dma_ch, length / 4);
+ set_dma_x_modify(host->dma_ch, 4);
+ set_dma_config(host->dma_ch, dma_cfg);
+#endif
+ bfin_write_SDH_DATA_CTL(bfin_read_SDH_DATA_CTL() | DTX_DMA_E | DTX_E);
+
+ SSYNC();
+
+ dev_dbg(mmc_dev(host->mmc), "%s exit\n", __func__);
+ return 0;
+}
+
+static void sdh_start_cmd(struct sdh_host *host, struct mmc_command *cmd)
+{
+ unsigned int sdh_cmd;
+ unsigned int stat_mask;
+
+ dev_dbg(mmc_dev(host->mmc), "%s enter cmd: 0x%p\n", __func__, cmd);
+ WARN_ON(host->cmd != NULL);
+ host->cmd = cmd;
+
+ sdh_cmd = 0;
+ stat_mask = 0;
+
+ sdh_cmd |= cmd->opcode;
+
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ sdh_cmd |= CMD_RSP;
+ stat_mask |= CMD_RESP_END;
+ } else {
+ stat_mask |= CMD_SENT;
+ }
+
+ if (cmd->flags & MMC_RSP_136)
+ sdh_cmd |= CMD_L_RSP;
+
+ stat_mask |= CMD_CRC_FAIL | CMD_TIME_OUT;
+
+ sdh_enable_stat_irq(host, stat_mask);
+
+ bfin_write_SDH_ARGUMENT(cmd->arg);
+ bfin_write_SDH_COMMAND(sdh_cmd | CMD_E);
+ bfin_write_SDH_CLK_CTL(bfin_read_SDH_CLK_CTL() | CLK_E);
+ SSYNC();
+}
+
+static void sdh_finish_request(struct sdh_host *host, struct mmc_request *mrq)
+{
+ dev_dbg(mmc_dev(host->mmc), "%s enter\n", __func__);
+ host->mrq = NULL;
+ host->cmd = NULL;
+ host->data = NULL;
+ mmc_request_done(host->mmc, mrq);
+}
+
+static int sdh_cmd_done(struct sdh_host *host, unsigned int stat)
+{
+ struct mmc_command *cmd = host->cmd;
+ int ret = 0;
+
+ dev_dbg(mmc_dev(host->mmc), "%s enter cmd: %p\n", __func__, cmd);
+ if (!cmd)
+ return 0;
+
+ host->cmd = NULL;
+
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ cmd->resp[0] = bfin_read_SDH_RESPONSE0();
+ if (cmd->flags & MMC_RSP_136) {
+ cmd->resp[1] = bfin_read_SDH_RESPONSE1();
+ cmd->resp[2] = bfin_read_SDH_RESPONSE2();
+ cmd->resp[3] = bfin_read_SDH_RESPONSE3();
+ }
+ }
+ if (stat & CMD_TIME_OUT)
+ cmd->error = -ETIMEDOUT;
+ else if (stat & CMD_CRC_FAIL && cmd->flags & MMC_RSP_CRC)
+ cmd->error = -EILSEQ;
+
+ sdh_disable_stat_irq(host, (CMD_SENT | CMD_RESP_END | CMD_TIME_OUT | CMD_CRC_FAIL));
+
+ if (host->data && !cmd->error) {
+ if (host->data->flags & MMC_DATA_WRITE) {
+ ret = sdh_setup_data(host, host->data);
+ if (ret)
+ return 0;
+ }
+
+ sdh_enable_stat_irq(host, DAT_END | RX_OVERRUN | TX_UNDERRUN | DAT_TIME_OUT);
+ } else
+ sdh_finish_request(host, host->mrq);
+
+ return 1;
+}
+
+static int sdh_data_done(struct sdh_host *host, unsigned int stat)
+{
+ struct mmc_data *data = host->data;
+
+ dev_dbg(mmc_dev(host->mmc), "%s enter stat: 0x%x\n", __func__, stat);
+ if (!data)
+ return 0;
+
+ disable_dma(host->dma_ch);
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+ host->dma_dir);
+
+ if (stat & DAT_TIME_OUT)
+ data->error = -ETIMEDOUT;
+ else if (stat & DAT_CRC_FAIL)
+ data->error = -EILSEQ;
+ else if (stat & (RX_OVERRUN | TX_UNDERRUN))
+ data->error = -EIO;
+
+ if (!data->error)
+ data->bytes_xfered = data->blocks * data->blksz;
+ else
+ data->bytes_xfered = 0;
+
+ sdh_disable_stat_irq(host, DAT_END | DAT_TIME_OUT | DAT_CRC_FAIL | RX_OVERRUN | TX_UNDERRUN);
+ bfin_write_SDH_STATUS_CLR(DAT_END_STAT | DAT_TIMEOUT_STAT | \
+ DAT_CRC_FAIL_STAT | DAT_BLK_END_STAT | RX_OVERRUN | TX_UNDERRUN);
+ bfin_write_SDH_DATA_CTL(0);
+ SSYNC();
+
+ host->data = NULL;
+ if (host->mrq->stop) {
+ sdh_stop_clock(host);
+ sdh_start_cmd(host, host->mrq->stop);
+ } else {
+ sdh_finish_request(host, host->mrq);
+ }
+
+ return 1;
+}
+
+static void sdh_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct sdh_host *host = mmc_priv(mmc);
+ int ret = 0;
+
+ dev_dbg(mmc_dev(host->mmc), "%s enter, mrp:%p, cmd:%p\n", __func__, mrq, mrq->cmd);
+ WARN_ON(host->mrq != NULL);
+
+ host->mrq = mrq;
+ host->data = mrq->data;
+
+ if (mrq->data && mrq->data->flags & MMC_DATA_READ) {
+ ret = sdh_setup_data(host, mrq->data);
+ if (ret)
+ return;
+ }
+
+ sdh_start_cmd(host, mrq->cmd);
+}
+
+static void sdh_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct sdh_host *host;
+ unsigned long flags;
+ u16 clk_ctl = 0;
+ u16 pwr_ctl = 0;
+ u16 cfg;
+ host = mmc_priv(mmc);
+
+ spin_lock_irqsave(&host->lock, flags);
+ if (ios->clock) {
+ unsigned long sys_clk, ios_clk;
+ unsigned char clk_div;
+ ios_clk = 2 * ios->clock;
+ sys_clk = get_sclk();
+ clk_div = sys_clk / ios_clk;
+ if (sys_clk % ios_clk == 0)
+ clk_div -= 1;
+ clk_div = min_t(unsigned char, clk_div, 0xFF);
+ clk_ctl |= clk_div;
+ clk_ctl |= CLK_E;
+ host->clk_div = clk_div;
+ } else
+ sdh_stop_clock(host);
+
+ if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN)
+#ifdef CONFIG_SDH_BFIN_MISSING_CMD_PULLUP_WORKAROUND
+ pwr_ctl |= ROD_CTL;
+#else
+ pwr_ctl |= SD_CMD_OD | ROD_CTL;
+#endif
+
+ if (ios->bus_width == MMC_BUS_WIDTH_4) {
+ cfg = bfin_read_SDH_CFG();
+ cfg &= ~PD_SDDAT3;
+ cfg |= PUP_SDDAT3;
+ /* Enable 4 bit SDIO */
+ cfg |= (SD4E | MWE);
+ bfin_write_SDH_CFG(cfg);
+ clk_ctl |= WIDE_BUS;
+ } else {
+ cfg = bfin_read_SDH_CFG();
+ cfg |= MWE;
+ bfin_write_SDH_CFG(cfg);
+ }
+
+ bfin_write_SDH_CLK_CTL(clk_ctl);
+
+ host->power_mode = ios->power_mode;
+ if (ios->power_mode == MMC_POWER_ON)
+ pwr_ctl |= PWR_ON;
+
+ bfin_write_SDH_PWR_CTL(pwr_ctl);
+ SSYNC();
+
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ dev_dbg(mmc_dev(host->mmc), "SDH: clk_div = 0x%x actual clock:%ld expected clock:%d\n",
+ host->clk_div,
+ host->clk_div ? get_sclk() / (2 * (host->clk_div + 1)) : 0,
+ ios->clock);
+}
+
+static const struct mmc_host_ops sdh_ops = {
+ .request = sdh_request,
+ .set_ios = sdh_set_ios,
+};
+
+static irqreturn_t sdh_dma_irq(int irq, void *devid)
+{
+ struct sdh_host *host = devid;
+
+ dev_dbg(mmc_dev(host->mmc), "%s enter, irq_stat: 0x%04x\n", __func__,
+ get_dma_curr_irqstat(host->dma_ch));
+ clear_dma_irqstat(host->dma_ch);
+ SSYNC();
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t sdh_stat_irq(int irq, void *devid)
+{
+ struct sdh_host *host = devid;
+ unsigned int status;
+ int handled = 0;
+
+ dev_dbg(mmc_dev(host->mmc), "%s enter\n", __func__);
+ status = bfin_read_SDH_E_STATUS();
+ if (status & SD_CARD_DET) {
+ mmc_detect_change(host->mmc, 0);
+ bfin_write_SDH_E_STATUS(SD_CARD_DET);
+ }
+ status = bfin_read_SDH_STATUS();
+ if (status & (CMD_SENT | CMD_RESP_END | CMD_TIME_OUT | CMD_CRC_FAIL)) {
+ handled |= sdh_cmd_done(host, status);
+ bfin_write_SDH_STATUS_CLR(CMD_SENT_STAT | CMD_RESP_END_STAT | \
+ CMD_TIMEOUT_STAT | CMD_CRC_FAIL_STAT);
+ SSYNC();
+ }
+
+ status = bfin_read_SDH_STATUS();
+ if (status & (DAT_END | DAT_TIME_OUT | DAT_CRC_FAIL | RX_OVERRUN | TX_UNDERRUN))
+ handled |= sdh_data_done(host, status);
+
+ dev_dbg(mmc_dev(host->mmc), "%s exit\n\n", __func__);
+
+ return IRQ_RETVAL(handled);
+}
+
+static int __devinit sdh_probe(struct platform_device *pdev)
+{
+ struct mmc_host *mmc;
+ struct sdh_host *host;
+ struct bfin_sd_host *drv_data = get_sdh_data(pdev);
+ int ret;
+
+ if (!drv_data) {
+ dev_err(&pdev->dev, "missing platform driver data\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ mmc = mmc_alloc_host(sizeof(struct sdh_host), &pdev->dev);
+ if (!mmc) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ mmc->ops = &sdh_ops;
+ mmc->max_segs = 32;
+ mmc->max_seg_size = 1 << 16;
+ mmc->max_blk_size = 1 << 11;
+ mmc->max_blk_count = 1 << 11;
+ mmc->max_req_size = PAGE_SIZE;
+ mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+ mmc->f_max = get_sclk();
+ mmc->f_min = mmc->f_max >> 9;
+ mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_NEEDS_POLL;
+ host = mmc_priv(mmc);
+ host->mmc = mmc;
+
+ spin_lock_init(&host->lock);
+ host->irq = drv_data->irq_int0;
+ host->dma_ch = drv_data->dma_chan;
+
+ ret = request_dma(host->dma_ch, DRIVER_NAME "DMA");
+ if (ret) {
+ dev_err(&pdev->dev, "unable to request DMA channel\n");
+ goto out1;
+ }
+
+ ret = set_dma_callback(host->dma_ch, sdh_dma_irq, host);
+ if (ret) {
+ dev_err(&pdev->dev, "unable to request DMA irq\n");
+ goto out2;
+ }
+
+ host->sg_cpu = dma_alloc_coherent(&pdev->dev, PAGE_SIZE, &host->sg_dma, GFP_KERNEL);
+ if (host->sg_cpu == NULL) {
+ ret = -ENOMEM;
+ goto out2;
+ }
+
+ platform_set_drvdata(pdev, mmc);
+ mmc_add_host(mmc);
+
+ ret = request_irq(host->irq, sdh_stat_irq, 0, "SDH Status IRQ", host);
+ if (ret) {
+ dev_err(&pdev->dev, "unable to request status irq\n");
+ goto out3;
+ }
+
+ ret = peripheral_request_list(drv_data->pin_req, DRIVER_NAME);
+ if (ret) {
+ dev_err(&pdev->dev, "unable to request peripheral pins\n");
+ goto out4;
+ }
+#if defined(CONFIG_BF54x)
+ /* Secure Digital Host shares DMA with Nand controller */
+ bfin_write_DMAC1_PERIMUX(bfin_read_DMAC1_PERIMUX() | 0x1);
+#endif
+
+ bfin_write_SDH_CFG(bfin_read_SDH_CFG() | CLKS_EN);
+ SSYNC();
+
+ /* Disable card inserting detection pin. set MMC_CAP_NEES_POLL, and
+ * mmc stack will do the detection.
+ */
+ bfin_write_SDH_CFG((bfin_read_SDH_CFG() & 0x1F) | (PUP_SDDAT | PUP_SDDAT3));
+ SSYNC();
+
+ return 0;
+
+out4:
+ free_irq(host->irq, host);
+out3:
+ mmc_remove_host(mmc);
+ dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
+out2:
+ free_dma(host->dma_ch);
+out1:
+ mmc_free_host(mmc);
+ out:
+ return ret;
+}
+
+static int __devexit sdh_remove(struct platform_device *pdev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(pdev);
+
+ platform_set_drvdata(pdev, NULL);
+
+ if (mmc) {
+ struct sdh_host *host = mmc_priv(mmc);
+
+ mmc_remove_host(mmc);
+
+ sdh_stop_clock(host);
+ free_irq(host->irq, host);
+ free_dma(host->dma_ch);
+ dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
+
+ mmc_free_host(mmc);
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int sdh_suspend(struct platform_device *dev, pm_message_t state)
+{
+ struct mmc_host *mmc = platform_get_drvdata(dev);
+ struct bfin_sd_host *drv_data = get_sdh_data(dev);
+ int ret = 0;
+
+ if (mmc)
+ ret = mmc_suspend_host(mmc);
+
+ bfin_write_SDH_PWR_CTL(bfin_read_SDH_PWR_CTL() & ~PWR_ON);
+ peripheral_free_list(drv_data->pin_req);
+
+ return ret;
+}
+
+static int sdh_resume(struct platform_device *dev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(dev);
+ struct bfin_sd_host *drv_data = get_sdh_data(dev);
+ int ret = 0;
+
+ ret = peripheral_request_list(drv_data->pin_req, DRIVER_NAME);
+ if (ret) {
+ dev_err(&dev->dev, "unable to request peripheral pins\n");
+ return ret;
+ }
+
+ bfin_write_SDH_PWR_CTL(bfin_read_SDH_PWR_CTL() | PWR_ON);
+#if defined(CONFIG_BF54x)
+ /* Secure Digital Host shares DMA with Nand controller */
+ bfin_write_DMAC1_PERIMUX(bfin_read_DMAC1_PERIMUX() | 0x1);
+#endif
+ bfin_write_SDH_CFG(bfin_read_SDH_CFG() | CLKS_EN);
+ SSYNC();
+
+ bfin_write_SDH_CFG((bfin_read_SDH_CFG() & 0x1F) | (PUP_SDDAT | PUP_SDDAT3));
+ SSYNC();
+
+ if (mmc)
+ ret = mmc_resume_host(mmc);
+
+ return ret;
+}
+#else
+# define sdh_suspend NULL
+# define sdh_resume NULL
+#endif
+
+static struct platform_driver sdh_driver = {
+ .probe = sdh_probe,
+ .remove = __devexit_p(sdh_remove),
+ .suspend = sdh_suspend,
+ .resume = sdh_resume,
+ .driver = {
+ .name = DRIVER_NAME,
+ },
+};
+
+static int __init sdh_init(void)
+{
+ return platform_driver_register(&sdh_driver);
+}
+module_init(sdh_init);
+
+static void __exit sdh_exit(void)
+{
+ platform_driver_unregister(&sdh_driver);
+}
+module_exit(sdh_exit);
+
+MODULE_DESCRIPTION("Blackfin Secure Digital Host Driver");
+MODULE_AUTHOR("Cliff Cai, Roy Huang");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mmc/host/cb710-mmc.c b/drivers/mmc/host/cb710-mmc.c
new file mode 100644
index 00000000..ce2a47b7
--- /dev/null
+++ b/drivers/mmc/host/cb710-mmc.c
@@ -0,0 +1,799 @@
+/*
+ * cb710/mmc.c
+ *
+ * Copyright by Michał Mirosław, 2008-2009
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include "cb710-mmc.h"
+
+static const u8 cb710_clock_divider_log2[8] = {
+/* 1, 2, 4, 8, 16, 32, 128, 512 */
+ 0, 1, 2, 3, 4, 5, 7, 9
+};
+#define CB710_MAX_DIVIDER_IDX \
+ (ARRAY_SIZE(cb710_clock_divider_log2) - 1)
+
+static const u8 cb710_src_freq_mhz[16] = {
+ 33, 10, 20, 25, 30, 35, 40, 45,
+ 50, 55, 60, 65, 70, 75, 80, 85
+};
+
+static void cb710_mmc_select_clock_divider(struct mmc_host *mmc, int hz)
+{
+ struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+ struct pci_dev *pdev = cb710_slot_to_chip(slot)->pdev;
+ u32 src_freq_idx;
+ u32 divider_idx;
+ int src_hz;
+
+ /* on CB710 in HP nx9500:
+ * src_freq_idx == 0
+ * indexes 1-7 work as written in the table
+ * indexes 0,8-15 give no clock output
+ */
+ pci_read_config_dword(pdev, 0x48, &src_freq_idx);
+ src_freq_idx = (src_freq_idx >> 16) & 0xF;
+ src_hz = cb710_src_freq_mhz[src_freq_idx] * 1000000;
+
+ for (divider_idx = 0; divider_idx < CB710_MAX_DIVIDER_IDX; ++divider_idx) {
+ if (hz >= src_hz >> cb710_clock_divider_log2[divider_idx])
+ break;
+ }
+
+ if (src_freq_idx)
+ divider_idx |= 0x8;
+ else if (divider_idx == 0)
+ divider_idx = 1;
+
+ cb710_pci_update_config_reg(pdev, 0x40, ~0xF0000000, divider_idx << 28);
+
+ dev_dbg(cb710_slot_dev(slot),
+ "clock set to %d Hz, wanted %d Hz; src_freq_idx = %d, divider_idx = %d|%d\n",
+ src_hz >> cb710_clock_divider_log2[divider_idx & 7],
+ hz, src_freq_idx, divider_idx & 7, divider_idx & 8);
+}
+
+static void __cb710_mmc_enable_irq(struct cb710_slot *slot,
+ unsigned short enable, unsigned short mask)
+{
+ /* clear global IE
+ * - it gets set later if any interrupt sources are enabled */
+ mask |= CB710_MMC_IE_IRQ_ENABLE;
+
+ /* look like interrupt is fired whenever
+ * WORD[0x0C] & WORD[0x10] != 0;
+ * -> bit 15 port 0x0C seems to be global interrupt enable
+ */
+
+ enable = (cb710_read_port_16(slot, CB710_MMC_IRQ_ENABLE_PORT)
+ & ~mask) | enable;
+
+ if (enable)
+ enable |= CB710_MMC_IE_IRQ_ENABLE;
+
+ cb710_write_port_16(slot, CB710_MMC_IRQ_ENABLE_PORT, enable);
+}
+
+static void cb710_mmc_enable_irq(struct cb710_slot *slot,
+ unsigned short enable, unsigned short mask)
+{
+ struct cb710_mmc_reader *reader = mmc_priv(cb710_slot_to_mmc(slot));
+ unsigned long flags;
+
+ spin_lock_irqsave(&reader->irq_lock, flags);
+ /* this is the only thing irq_lock protects */
+ __cb710_mmc_enable_irq(slot, enable, mask);
+ spin_unlock_irqrestore(&reader->irq_lock, flags);
+}
+
+static void cb710_mmc_reset_events(struct cb710_slot *slot)
+{
+ cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT, 0xFF);
+ cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT, 0xFF);
+ cb710_write_port_8(slot, CB710_MMC_STATUS2_PORT, 0xFF);
+}
+
+static void cb710_mmc_enable_4bit_data(struct cb710_slot *slot, int enable)
+{
+ if (enable)
+ cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT,
+ CB710_MMC_C1_4BIT_DATA_BUS, 0);
+ else
+ cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT,
+ 0, CB710_MMC_C1_4BIT_DATA_BUS);
+}
+
+static int cb710_check_event(struct cb710_slot *slot, u8 what)
+{
+ u16 status;
+
+ status = cb710_read_port_16(slot, CB710_MMC_STATUS_PORT);
+
+ if (status & CB710_MMC_S0_FIFO_UNDERFLOW) {
+ /* it is just a guess, so log it */
+ dev_dbg(cb710_slot_dev(slot),
+ "CHECK : ignoring bit 6 in status %04X\n", status);
+ cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT,
+ CB710_MMC_S0_FIFO_UNDERFLOW);
+ status &= ~CB710_MMC_S0_FIFO_UNDERFLOW;
+ }
+
+ if (status & CB710_MMC_STATUS_ERROR_EVENTS) {
+ dev_dbg(cb710_slot_dev(slot),
+ "CHECK : returning EIO on status %04X\n", status);
+ cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT, status & 0xFF);
+ cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT,
+ CB710_MMC_S1_RESET);
+ return -EIO;
+ }
+
+ /* 'what' is a bit in MMC_STATUS1 */
+ if ((status >> 8) & what) {
+ cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT, what);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int cb710_wait_for_event(struct cb710_slot *slot, u8 what)
+{
+ int err = 0;
+ unsigned limit = 2000000; /* FIXME: real timeout */
+
+#ifdef CONFIG_CB710_DEBUG
+ u32 e, x;
+ e = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+#endif
+
+ while (!(err = cb710_check_event(slot, what))) {
+ if (!--limit) {
+ cb710_dump_regs(cb710_slot_to_chip(slot),
+ CB710_DUMP_REGS_MMC);
+ err = -ETIMEDOUT;
+ break;
+ }
+ udelay(1);
+ }
+
+#ifdef CONFIG_CB710_DEBUG
+ x = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+
+ limit = 2000000 - limit;
+ if (limit > 100)
+ dev_dbg(cb710_slot_dev(slot),
+ "WAIT10: waited %d loops, what %d, entry val %08X, exit val %08X\n",
+ limit, what, e, x);
+#endif
+ return err < 0 ? err : 0;
+}
+
+
+static int cb710_wait_while_busy(struct cb710_slot *slot, uint8_t mask)
+{
+ unsigned limit = 500000; /* FIXME: real timeout */
+ int err = 0;
+
+#ifdef CONFIG_CB710_DEBUG
+ u32 e, x;
+ e = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+#endif
+
+ while (cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT) & mask) {
+ if (!--limit) {
+ cb710_dump_regs(cb710_slot_to_chip(slot),
+ CB710_DUMP_REGS_MMC);
+ err = -ETIMEDOUT;
+ break;
+ }
+ udelay(1);
+ }
+
+#ifdef CONFIG_CB710_DEBUG
+ x = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+
+ limit = 500000 - limit;
+ if (limit > 100)
+ dev_dbg(cb710_slot_dev(slot),
+ "WAIT12: waited %d loops, mask %02X, entry val %08X, exit val %08X\n",
+ limit, mask, e, x);
+#endif
+ return err;
+}
+
+static void cb710_mmc_set_transfer_size(struct cb710_slot *slot,
+ size_t count, size_t blocksize)
+{
+ cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+ cb710_write_port_32(slot, CB710_MMC_TRANSFER_SIZE_PORT,
+ ((count - 1) << 16)|(blocksize - 1));
+
+ dev_vdbg(cb710_slot_dev(slot), "set up for %zu block%s of %zu bytes\n",
+ count, count == 1 ? "" : "s", blocksize);
+}
+
+static void cb710_mmc_fifo_hack(struct cb710_slot *slot)
+{
+ /* without this, received data is prepended with 8-bytes of zeroes */
+ u32 r1, r2;
+ int ok = 0;
+
+ r1 = cb710_read_port_32(slot, CB710_MMC_DATA_PORT);
+ r2 = cb710_read_port_32(slot, CB710_MMC_DATA_PORT);
+ if (cb710_read_port_8(slot, CB710_MMC_STATUS0_PORT)
+ & CB710_MMC_S0_FIFO_UNDERFLOW) {
+ cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT,
+ CB710_MMC_S0_FIFO_UNDERFLOW);
+ ok = 1;
+ }
+
+ dev_dbg(cb710_slot_dev(slot),
+ "FIFO-read-hack: expected STATUS0 bit was %s\n",
+ ok ? "set." : "NOT SET!");
+ dev_dbg(cb710_slot_dev(slot),
+ "FIFO-read-hack: dwords ignored: %08X %08X - %s\n",
+ r1, r2, (r1|r2) ? "BAD (NOT ZERO)!" : "ok");
+}
+
+static int cb710_mmc_receive_pio(struct cb710_slot *slot,
+ struct sg_mapping_iter *miter, size_t dw_count)
+{
+ if (!(cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT) & CB710_MMC_S2_FIFO_READY)) {
+ int err = cb710_wait_for_event(slot,
+ CB710_MMC_S1_PIO_TRANSFER_DONE);
+ if (err)
+ return err;
+ }
+
+ cb710_sg_dwiter_write_from_io(miter,
+ slot->iobase + CB710_MMC_DATA_PORT, dw_count);
+
+ return 0;
+}
+
+static bool cb710_is_transfer_size_supported(struct mmc_data *data)
+{
+ return !(data->blksz & 15 && (data->blocks != 1 || data->blksz != 8));
+}
+
+static int cb710_mmc_receive(struct cb710_slot *slot, struct mmc_data *data)
+{
+ struct sg_mapping_iter miter;
+ size_t len, blocks = data->blocks;
+ int err = 0;
+
+ /* TODO: I don't know how/if the hardware handles non-16B-boundary blocks
+ * except single 8B block */
+ if (unlikely(data->blksz & 15 && (data->blocks != 1 || data->blksz != 8)))
+ return -EINVAL;
+
+ sg_miter_start(&miter, data->sg, data->sg_len, SG_MITER_TO_SG);
+
+ cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
+ 15, CB710_MMC_C2_READ_PIO_SIZE_MASK);
+
+ cb710_mmc_fifo_hack(slot);
+
+ while (blocks-- > 0) {
+ len = data->blksz;
+
+ while (len >= 16) {
+ err = cb710_mmc_receive_pio(slot, &miter, 4);
+ if (err)
+ goto out;
+ len -= 16;
+ }
+
+ if (!len)
+ continue;
+
+ cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
+ len - 1, CB710_MMC_C2_READ_PIO_SIZE_MASK);
+
+ len = (len >= 8) ? 4 : 2;
+ err = cb710_mmc_receive_pio(slot, &miter, len);
+ if (err)
+ goto out;
+ }
+out:
+ sg_miter_stop(&miter);
+ return err;
+}
+
+static int cb710_mmc_send(struct cb710_slot *slot, struct mmc_data *data)
+{
+ struct sg_mapping_iter miter;
+ size_t len, blocks = data->blocks;
+ int err = 0;
+
+ /* TODO: I don't know how/if the hardware handles multiple
+ * non-16B-boundary blocks */
+ if (unlikely(data->blocks > 1 && data->blksz & 15))
+ return -EINVAL;
+
+ sg_miter_start(&miter, data->sg, data->sg_len, SG_MITER_FROM_SG);
+
+ cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT,
+ 0, CB710_MMC_C2_READ_PIO_SIZE_MASK);
+
+ while (blocks-- > 0) {
+ len = (data->blksz + 15) >> 4;
+ do {
+ if (!(cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT)
+ & CB710_MMC_S2_FIFO_EMPTY)) {
+ err = cb710_wait_for_event(slot,
+ CB710_MMC_S1_PIO_TRANSFER_DONE);
+ if (err)
+ goto out;
+ }
+ cb710_sg_dwiter_read_to_io(&miter,
+ slot->iobase + CB710_MMC_DATA_PORT, 4);
+ } while (--len);
+ }
+out:
+ sg_miter_stop(&miter);
+ return err;
+}
+
+static u16 cb710_encode_cmd_flags(struct cb710_mmc_reader *reader,
+ struct mmc_command *cmd)
+{
+ unsigned int flags = cmd->flags;
+ u16 cb_flags = 0;
+
+ /* Windows driver returned 0 for commands for which no response
+ * is expected. It happened that there were only two such commands
+ * used: MMC_GO_IDLE_STATE and MMC_GO_INACTIVE_STATE so it might
+ * as well be a bug in that driver.
+ *
+ * Original driver set bit 14 for MMC/SD application
+ * commands. There's no difference 'on the wire' and
+ * it apparently works without it anyway.
+ */
+
+ switch (flags & MMC_CMD_MASK) {
+ case MMC_CMD_AC: cb_flags = CB710_MMC_CMD_AC; break;
+ case MMC_CMD_ADTC: cb_flags = CB710_MMC_CMD_ADTC; break;
+ case MMC_CMD_BC: cb_flags = CB710_MMC_CMD_BC; break;
+ case MMC_CMD_BCR: cb_flags = CB710_MMC_CMD_BCR; break;
+ }
+
+ if (flags & MMC_RSP_BUSY)
+ cb_flags |= CB710_MMC_RSP_BUSY;
+
+ cb_flags |= cmd->opcode << CB710_MMC_CMD_CODE_SHIFT;
+
+ if (cmd->data && (cmd->data->flags & MMC_DATA_READ))
+ cb_flags |= CB710_MMC_DATA_READ;
+
+ if (flags & MMC_RSP_PRESENT) {
+ /* Windows driver set 01 at bits 4,3 except for
+ * MMC_SET_BLOCKLEN where it set 10. Maybe the
+ * hardware can do something special about this
+ * command? The original driver looks buggy/incomplete
+ * anyway so we ignore this for now.
+ *
+ * I assume that 00 here means no response is expected.
+ */
+ cb_flags |= CB710_MMC_RSP_PRESENT;
+
+ if (flags & MMC_RSP_136)
+ cb_flags |= CB710_MMC_RSP_136;
+ if (!(flags & MMC_RSP_CRC))
+ cb_flags |= CB710_MMC_RSP_NO_CRC;
+ }
+
+ return cb_flags;
+}
+
+static void cb710_receive_response(struct cb710_slot *slot,
+ struct mmc_command *cmd)
+{
+ unsigned rsp_opcode, wanted_opcode;
+
+ /* Looks like final byte with CRC is always stripped (same as SDHCI) */
+ if (cmd->flags & MMC_RSP_136) {
+ u32 resp[4];
+
+ resp[0] = cb710_read_port_32(slot, CB710_MMC_RESPONSE3_PORT);
+ resp[1] = cb710_read_port_32(slot, CB710_MMC_RESPONSE2_PORT);
+ resp[2] = cb710_read_port_32(slot, CB710_MMC_RESPONSE1_PORT);
+ resp[3] = cb710_read_port_32(slot, CB710_MMC_RESPONSE0_PORT);
+ rsp_opcode = resp[0] >> 24;
+
+ cmd->resp[0] = (resp[0] << 8)|(resp[1] >> 24);
+ cmd->resp[1] = (resp[1] << 8)|(resp[2] >> 24);
+ cmd->resp[2] = (resp[2] << 8)|(resp[3] >> 24);
+ cmd->resp[3] = (resp[3] << 8);
+ } else {
+ rsp_opcode = cb710_read_port_32(slot, CB710_MMC_RESPONSE1_PORT) & 0x3F;
+ cmd->resp[0] = cb710_read_port_32(slot, CB710_MMC_RESPONSE0_PORT);
+ }
+
+ wanted_opcode = (cmd->flags & MMC_RSP_OPCODE) ? cmd->opcode : 0x3F;
+ if (rsp_opcode != wanted_opcode)
+ cmd->error = -EILSEQ;
+}
+
+static int cb710_mmc_transfer_data(struct cb710_slot *slot,
+ struct mmc_data *data)
+{
+ int error, to;
+
+ if (data->flags & MMC_DATA_READ)
+ error = cb710_mmc_receive(slot, data);
+ else
+ error = cb710_mmc_send(slot, data);
+
+ to = cb710_wait_for_event(slot, CB710_MMC_S1_DATA_TRANSFER_DONE);
+ if (!error)
+ error = to;
+
+ if (!error)
+ data->bytes_xfered = data->blksz * data->blocks;
+ return error;
+}
+
+static int cb710_mmc_command(struct mmc_host *mmc, struct mmc_command *cmd)
+{
+ struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+ struct cb710_mmc_reader *reader = mmc_priv(mmc);
+ struct mmc_data *data = cmd->data;
+
+ u16 cb_cmd = cb710_encode_cmd_flags(reader, cmd);
+ dev_dbg(cb710_slot_dev(slot), "cmd request: 0x%04X\n", cb_cmd);
+
+ if (data) {
+ if (!cb710_is_transfer_size_supported(data)) {
+ data->error = -EINVAL;
+ return -1;
+ }
+ cb710_mmc_set_transfer_size(slot, data->blocks, data->blksz);
+ }
+
+ cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20|CB710_MMC_S2_BUSY_10);
+ cb710_write_port_16(slot, CB710_MMC_CMD_TYPE_PORT, cb_cmd);
+ cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+ cb710_write_port_32(slot, CB710_MMC_CMD_PARAM_PORT, cmd->arg);
+ cb710_mmc_reset_events(slot);
+ cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+ cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x01, 0);
+
+ cmd->error = cb710_wait_for_event(slot, CB710_MMC_S1_COMMAND_SENT);
+ if (cmd->error)
+ return -1;
+
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ cb710_receive_response(slot, cmd);
+ if (cmd->error)
+ return -1;
+ }
+
+ if (data)
+ data->error = cb710_mmc_transfer_data(slot, data);
+ return 0;
+}
+
+static void cb710_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+ struct cb710_mmc_reader *reader = mmc_priv(mmc);
+
+ WARN_ON(reader->mrq != NULL);
+
+ reader->mrq = mrq;
+ cb710_mmc_enable_irq(slot, CB710_MMC_IE_TEST_MASK, 0);
+
+ if (!cb710_mmc_command(mmc, mrq->cmd) && mrq->stop)
+ cb710_mmc_command(mmc, mrq->stop);
+
+ tasklet_schedule(&reader->finish_req_tasklet);
+}
+
+static int cb710_mmc_powerup(struct cb710_slot *slot)
+{
+#ifdef CONFIG_CB710_DEBUG
+ struct cb710_chip *chip = cb710_slot_to_chip(slot);
+#endif
+ int err;
+
+ /* a lot of magic for now */
+ dev_dbg(cb710_slot_dev(slot), "bus powerup\n");
+ cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+ err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+ if (unlikely(err))
+ return err;
+ cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x80, 0);
+ cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0x80, 0);
+ cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+ mdelay(1);
+ dev_dbg(cb710_slot_dev(slot), "after delay 1\n");
+ cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+ err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+ if (unlikely(err))
+ return err;
+ cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x09, 0);
+ cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+ mdelay(1);
+ dev_dbg(cb710_slot_dev(slot), "after delay 2\n");
+ cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+ err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+ if (unlikely(err))
+ return err;
+ cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0, 0x08);
+ cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+ mdelay(2);
+ dev_dbg(cb710_slot_dev(slot), "after delay 3\n");
+ cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+ cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x06, 0);
+ cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x70, 0);
+ cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT, 0x80, 0);
+ cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0x03, 0);
+ cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+ err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20);
+ if (unlikely(err))
+ return err;
+ /* This port behaves weird: quick byte reads of 0x08,0x09 return
+ * 0xFF,0x00 after writing 0xFFFF to 0x08; it works correctly when
+ * read/written from userspace... What am I missing here?
+ * (it doesn't depend on write-to-read delay) */
+ cb710_write_port_16(slot, CB710_MMC_CONFIGB_PORT, 0xFFFF);
+ cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x06, 0);
+ cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+ dev_dbg(cb710_slot_dev(slot), "bus powerup finished\n");
+
+ return cb710_check_event(slot, 0);
+}
+
+static void cb710_mmc_powerdown(struct cb710_slot *slot)
+{
+ cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0, 0x81);
+ cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0, 0x80);
+}
+
+static void cb710_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+ struct cb710_mmc_reader *reader = mmc_priv(mmc);
+ int err;
+
+ cb710_mmc_select_clock_divider(mmc, ios->clock);
+
+ if (ios->power_mode != reader->last_power_mode)
+ switch (ios->power_mode) {
+ case MMC_POWER_ON:
+ err = cb710_mmc_powerup(slot);
+ if (err) {
+ dev_warn(cb710_slot_dev(slot),
+ "powerup failed (%d)- retrying\n", err);
+ cb710_mmc_powerdown(slot);
+ udelay(1);
+ err = cb710_mmc_powerup(slot);
+ if (err)
+ dev_warn(cb710_slot_dev(slot),
+ "powerup retry failed (%d) - expect errors\n",
+ err);
+ }
+ reader->last_power_mode = MMC_POWER_ON;
+ break;
+ case MMC_POWER_OFF:
+ cb710_mmc_powerdown(slot);
+ reader->last_power_mode = MMC_POWER_OFF;
+ break;
+ case MMC_POWER_UP:
+ default:
+ /* ignore */;
+ }
+
+ cb710_mmc_enable_4bit_data(slot, ios->bus_width != MMC_BUS_WIDTH_1);
+
+ cb710_mmc_enable_irq(slot, CB710_MMC_IE_TEST_MASK, 0);
+}
+
+static int cb710_mmc_get_ro(struct mmc_host *mmc)
+{
+ struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+
+ return cb710_read_port_8(slot, CB710_MMC_STATUS3_PORT)
+ & CB710_MMC_S3_WRITE_PROTECTED;
+}
+
+static int cb710_mmc_get_cd(struct mmc_host *mmc)
+{
+ struct cb710_slot *slot = cb710_mmc_to_slot(mmc);
+
+ return cb710_read_port_8(slot, CB710_MMC_STATUS3_PORT)
+ & CB710_MMC_S3_CARD_DETECTED;
+}
+
+static int cb710_mmc_irq_handler(struct cb710_slot *slot)
+{
+ struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+ struct cb710_mmc_reader *reader = mmc_priv(mmc);
+ u32 status, config1, config2, irqen;
+
+ status = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT);
+ irqen = cb710_read_port_32(slot, CB710_MMC_IRQ_ENABLE_PORT);
+ config2 = cb710_read_port_32(slot, CB710_MMC_CONFIGB_PORT);
+ config1 = cb710_read_port_32(slot, CB710_MMC_CONFIG_PORT);
+
+ dev_dbg(cb710_slot_dev(slot), "interrupt; status: %08X, "
+ "ie: %08X, c2: %08X, c1: %08X\n",
+ status, irqen, config2, config1);
+
+ if (status & (CB710_MMC_S1_CARD_CHANGED << 8)) {
+ /* ack the event */
+ cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT,
+ CB710_MMC_S1_CARD_CHANGED);
+ if ((irqen & CB710_MMC_IE_CISTATUS_MASK)
+ == CB710_MMC_IE_CISTATUS_MASK)
+ mmc_detect_change(mmc, HZ/5);
+ } else {
+ dev_dbg(cb710_slot_dev(slot), "unknown interrupt (test)\n");
+ spin_lock(&reader->irq_lock);
+ __cb710_mmc_enable_irq(slot, 0, CB710_MMC_IE_TEST_MASK);
+ spin_unlock(&reader->irq_lock);
+ }
+
+ return 1;
+}
+
+static void cb710_mmc_finish_request_tasklet(unsigned long data)
+{
+ struct mmc_host *mmc = (void *)data;
+ struct cb710_mmc_reader *reader = mmc_priv(mmc);
+ struct mmc_request *mrq = reader->mrq;
+
+ reader->mrq = NULL;
+ mmc_request_done(mmc, mrq);
+}
+
+static const struct mmc_host_ops cb710_mmc_host = {
+ .request = cb710_mmc_request,
+ .set_ios = cb710_mmc_set_ios,
+ .get_ro = cb710_mmc_get_ro,
+ .get_cd = cb710_mmc_get_cd,
+};
+
+#ifdef CONFIG_PM
+
+static int cb710_mmc_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+ struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+ int err;
+
+ err = mmc_suspend_host(mmc);
+ if (err)
+ return err;
+
+ cb710_mmc_enable_irq(slot, 0, ~0);
+ return 0;
+}
+
+static int cb710_mmc_resume(struct platform_device *pdev)
+{
+ struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+ struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+
+ cb710_mmc_enable_irq(slot, 0, ~0);
+
+ return mmc_resume_host(mmc);
+}
+
+#endif /* CONFIG_PM */
+
+static int __devinit cb710_mmc_init(struct platform_device *pdev)
+{
+ struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+ struct cb710_chip *chip = cb710_slot_to_chip(slot);
+ struct mmc_host *mmc;
+ struct cb710_mmc_reader *reader;
+ int err;
+ u32 val;
+
+ mmc = mmc_alloc_host(sizeof(*reader), cb710_slot_dev(slot));
+ if (!mmc)
+ return -ENOMEM;
+
+ dev_set_drvdata(&pdev->dev, mmc);
+
+ /* harmless (maybe) magic */
+ pci_read_config_dword(chip->pdev, 0x48, &val);
+ val = cb710_src_freq_mhz[(val >> 16) & 0xF];
+ dev_dbg(cb710_slot_dev(slot), "source frequency: %dMHz\n", val);
+ val *= 1000000;
+
+ mmc->ops = &cb710_mmc_host;
+ mmc->f_max = val;
+ mmc->f_min = val >> cb710_clock_divider_log2[CB710_MAX_DIVIDER_IDX];
+ mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34;
+ mmc->caps = MMC_CAP_4_BIT_DATA;
+
+ reader = mmc_priv(mmc);
+
+ tasklet_init(&reader->finish_req_tasklet,
+ cb710_mmc_finish_request_tasklet, (unsigned long)mmc);
+ spin_lock_init(&reader->irq_lock);
+ cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);
+
+ cb710_mmc_enable_irq(slot, 0, ~0);
+ cb710_set_irq_handler(slot, cb710_mmc_irq_handler);
+
+ err = mmc_add_host(mmc);
+ if (unlikely(err))
+ goto err_free_mmc;
+
+ dev_dbg(cb710_slot_dev(slot), "mmc_hostname is %s\n",
+ mmc_hostname(mmc));
+
+ cb710_mmc_enable_irq(slot, CB710_MMC_IE_CARD_INSERTION_STATUS, 0);
+
+ return 0;
+
+err_free_mmc:
+ dev_dbg(cb710_slot_dev(slot), "mmc_add_host() failed: %d\n", err);
+
+ cb710_set_irq_handler(slot, NULL);
+ mmc_free_host(mmc);
+ return err;
+}
+
+static int __devexit cb710_mmc_exit(struct platform_device *pdev)
+{
+ struct cb710_slot *slot = cb710_pdev_to_slot(pdev);
+ struct mmc_host *mmc = cb710_slot_to_mmc(slot);
+ struct cb710_mmc_reader *reader = mmc_priv(mmc);
+
+ cb710_mmc_enable_irq(slot, 0, CB710_MMC_IE_CARD_INSERTION_STATUS);
+
+ mmc_remove_host(mmc);
+
+ /* IRQs should be disabled now, but let's stay on the safe side */
+ cb710_mmc_enable_irq(slot, 0, ~0);
+ cb710_set_irq_handler(slot, NULL);
+
+ /* clear config ports - just in case */
+ cb710_write_port_32(slot, CB710_MMC_CONFIG_PORT, 0);
+ cb710_write_port_16(slot, CB710_MMC_CONFIGB_PORT, 0);
+
+ tasklet_kill(&reader->finish_req_tasklet);
+
+ mmc_free_host(mmc);
+ return 0;
+}
+
+static struct platform_driver cb710_mmc_driver = {
+ .driver.name = "cb710-mmc",
+ .probe = cb710_mmc_init,
+ .remove = __devexit_p(cb710_mmc_exit),
+#ifdef CONFIG_PM
+ .suspend = cb710_mmc_suspend,
+ .resume = cb710_mmc_resume,
+#endif
+};
+
+static int __init cb710_mmc_init_module(void)
+{
+ return platform_driver_register(&cb710_mmc_driver);
+}
+
+static void __exit cb710_mmc_cleanup_module(void)
+{
+ platform_driver_unregister(&cb710_mmc_driver);
+}
+
+module_init(cb710_mmc_init_module);
+module_exit(cb710_mmc_cleanup_module);
+
+MODULE_AUTHOR("Michał Mirosław <mirq-linux@rere.qmqm.pl>");
+MODULE_DESCRIPTION("ENE CB710 memory card reader driver - MMC/SD part");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:cb710-mmc");
diff --git a/drivers/mmc/host/cb710-mmc.h b/drivers/mmc/host/cb710-mmc.h
new file mode 100644
index 00000000..e845c776
--- /dev/null
+++ b/drivers/mmc/host/cb710-mmc.h
@@ -0,0 +1,104 @@
+/*
+ * cb710/cb710-mmc.h
+ *
+ * Copyright by Michał Mirosław, 2008-2009
+ *
+ * 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 LINUX_CB710_MMC_H
+#define LINUX_CB710_MMC_H
+
+#include <linux/cb710.h>
+
+/* per-MMC-reader structure */
+struct cb710_mmc_reader {
+ struct tasklet_struct finish_req_tasklet;
+ struct mmc_request *mrq;
+ spinlock_t irq_lock;
+ unsigned char last_power_mode;
+};
+
+/* some device struct walking */
+
+static inline struct mmc_host *cb710_slot_to_mmc(struct cb710_slot *slot)
+{
+ return dev_get_drvdata(&slot->pdev.dev);
+}
+
+static inline struct cb710_slot *cb710_mmc_to_slot(struct mmc_host *mmc)
+{
+ struct platform_device *pdev = container_of(mmc_dev(mmc),
+ struct platform_device, dev);
+ return cb710_pdev_to_slot(pdev);
+}
+
+/* registers (this might be all wrong ;) */
+
+#define CB710_MMC_DATA_PORT 0x00
+
+#define CB710_MMC_CONFIG_PORT 0x04
+#define CB710_MMC_CONFIG0_PORT 0x04
+#define CB710_MMC_CONFIG1_PORT 0x05
+#define CB710_MMC_C1_4BIT_DATA_BUS 0x40
+#define CB710_MMC_CONFIG2_PORT 0x06
+#define CB710_MMC_C2_READ_PIO_SIZE_MASK 0x0F /* N-1 */
+#define CB710_MMC_CONFIG3_PORT 0x07
+
+#define CB710_MMC_CONFIGB_PORT 0x08
+
+#define CB710_MMC_IRQ_ENABLE_PORT 0x0C
+#define CB710_MMC_IE_TEST_MASK 0x00BF
+#define CB710_MMC_IE_CARD_INSERTION_STATUS 0x1000
+#define CB710_MMC_IE_IRQ_ENABLE 0x8000
+#define CB710_MMC_IE_CISTATUS_MASK \
+ (CB710_MMC_IE_CARD_INSERTION_STATUS|CB710_MMC_IE_IRQ_ENABLE)
+
+#define CB710_MMC_STATUS_PORT 0x10
+#define CB710_MMC_STATUS_ERROR_EVENTS 0x60FF
+#define CB710_MMC_STATUS0_PORT 0x10
+#define CB710_MMC_S0_FIFO_UNDERFLOW 0x40
+#define CB710_MMC_STATUS1_PORT 0x11
+#define CB710_MMC_S1_COMMAND_SENT 0x01
+#define CB710_MMC_S1_DATA_TRANSFER_DONE 0x02
+#define CB710_MMC_S1_PIO_TRANSFER_DONE 0x04
+#define CB710_MMC_S1_CARD_CHANGED 0x10
+#define CB710_MMC_S1_RESET 0x20
+#define CB710_MMC_STATUS2_PORT 0x12
+#define CB710_MMC_S2_FIFO_READY 0x01
+#define CB710_MMC_S2_FIFO_EMPTY 0x02
+#define CB710_MMC_S2_BUSY_10 0x10
+#define CB710_MMC_S2_BUSY_20 0x20
+#define CB710_MMC_STATUS3_PORT 0x13
+#define CB710_MMC_S3_CARD_DETECTED 0x02
+#define CB710_MMC_S3_WRITE_PROTECTED 0x04
+
+#define CB710_MMC_CMD_TYPE_PORT 0x14
+#define CB710_MMC_RSP_TYPE_MASK 0x0007
+#define CB710_MMC_RSP_R1 (0)
+#define CB710_MMC_RSP_136 (5)
+#define CB710_MMC_RSP_NO_CRC (2)
+#define CB710_MMC_RSP_PRESENT_MASK 0x0018
+#define CB710_MMC_RSP_NONE (0 << 3)
+#define CB710_MMC_RSP_PRESENT (1 << 3)
+#define CB710_MMC_RSP_PRESENT_X (2 << 3)
+#define CB710_MMC_CMD_TYPE_MASK 0x0060
+#define CB710_MMC_CMD_BC (0 << 5)
+#define CB710_MMC_CMD_BCR (1 << 5)
+#define CB710_MMC_CMD_AC (2 << 5)
+#define CB710_MMC_CMD_ADTC (3 << 5)
+#define CB710_MMC_DATA_READ 0x0080
+#define CB710_MMC_CMD_CODE_MASK 0x3F00
+#define CB710_MMC_CMD_CODE_SHIFT 8
+#define CB710_MMC_IS_APP_CMD 0x4000
+#define CB710_MMC_RSP_BUSY 0x8000
+
+#define CB710_MMC_CMD_PARAM_PORT 0x18
+#define CB710_MMC_TRANSFER_SIZE_PORT 0x1C
+#define CB710_MMC_RESPONSE0_PORT 0x20
+#define CB710_MMC_RESPONSE1_PORT 0x24
+#define CB710_MMC_RESPONSE2_PORT 0x28
+#define CB710_MMC_RESPONSE3_PORT 0x2C
+
+#endif /* LINUX_CB710_MMC_H */
diff --git a/drivers/mmc/host/davinci_mmc.c b/drivers/mmc/host/davinci_mmc.c
new file mode 100644
index 00000000..0076c744
--- /dev/null
+++ b/drivers/mmc/host/davinci_mmc.c
@@ -0,0 +1,1479 @@
+/*
+ * davinci_mmc.c - TI DaVinci MMC/SD/SDIO driver
+ *
+ * Copyright (C) 2006 Texas Instruments.
+ * Original author: Purushotam Kumar
+ * Copyright (C) 2009 David Brownell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/cpufreq.h>
+#include <linux/mmc/host.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/mmc/mmc.h>
+
+#include <mach/mmc.h>
+#include <mach/edma.h>
+
+/*
+ * Register Definitions
+ */
+#define DAVINCI_MMCCTL 0x00 /* Control Register */
+#define DAVINCI_MMCCLK 0x04 /* Memory Clock Control Register */
+#define DAVINCI_MMCST0 0x08 /* Status Register 0 */
+#define DAVINCI_MMCST1 0x0C /* Status Register 1 */
+#define DAVINCI_MMCIM 0x10 /* Interrupt Mask Register */
+#define DAVINCI_MMCTOR 0x14 /* Response Time-Out Register */
+#define DAVINCI_MMCTOD 0x18 /* Data Read Time-Out Register */
+#define DAVINCI_MMCBLEN 0x1C /* Block Length Register */
+#define DAVINCI_MMCNBLK 0x20 /* Number of Blocks Register */
+#define DAVINCI_MMCNBLC 0x24 /* Number of Blocks Counter Register */
+#define DAVINCI_MMCDRR 0x28 /* Data Receive Register */
+#define DAVINCI_MMCDXR 0x2C /* Data Transmit Register */
+#define DAVINCI_MMCCMD 0x30 /* Command Register */
+#define DAVINCI_MMCARGHL 0x34 /* Argument Register */
+#define DAVINCI_MMCRSP01 0x38 /* Response Register 0 and 1 */
+#define DAVINCI_MMCRSP23 0x3C /* Response Register 0 and 1 */
+#define DAVINCI_MMCRSP45 0x40 /* Response Register 0 and 1 */
+#define DAVINCI_MMCRSP67 0x44 /* Response Register 0 and 1 */
+#define DAVINCI_MMCDRSP 0x48 /* Data Response Register */
+#define DAVINCI_MMCETOK 0x4C
+#define DAVINCI_MMCCIDX 0x50 /* Command Index Register */
+#define DAVINCI_MMCCKC 0x54
+#define DAVINCI_MMCTORC 0x58
+#define DAVINCI_MMCTODC 0x5C
+#define DAVINCI_MMCBLNC 0x60
+#define DAVINCI_SDIOCTL 0x64
+#define DAVINCI_SDIOST0 0x68
+#define DAVINCI_SDIOIEN 0x6C
+#define DAVINCI_SDIOIST 0x70
+#define DAVINCI_MMCFIFOCTL 0x74 /* FIFO Control Register */
+
+/* DAVINCI_MMCCTL definitions */
+#define MMCCTL_DATRST (1 << 0)
+#define MMCCTL_CMDRST (1 << 1)
+#define MMCCTL_WIDTH_8_BIT (1 << 8)
+#define MMCCTL_WIDTH_4_BIT (1 << 2)
+#define MMCCTL_DATEG_DISABLED (0 << 6)
+#define MMCCTL_DATEG_RISING (1 << 6)
+#define MMCCTL_DATEG_FALLING (2 << 6)
+#define MMCCTL_DATEG_BOTH (3 << 6)
+#define MMCCTL_PERMDR_LE (0 << 9)
+#define MMCCTL_PERMDR_BE (1 << 9)
+#define MMCCTL_PERMDX_LE (0 << 10)
+#define MMCCTL_PERMDX_BE (1 << 10)
+
+/* DAVINCI_MMCCLK definitions */
+#define MMCCLK_CLKEN (1 << 8)
+#define MMCCLK_CLKRT_MASK (0xFF << 0)
+
+/* IRQ bit definitions, for DAVINCI_MMCST0 and DAVINCI_MMCIM */
+#define MMCST0_DATDNE BIT(0) /* data done */
+#define MMCST0_BSYDNE BIT(1) /* busy done */
+#define MMCST0_RSPDNE BIT(2) /* command done */
+#define MMCST0_TOUTRD BIT(3) /* data read timeout */
+#define MMCST0_TOUTRS BIT(4) /* command response timeout */
+#define MMCST0_CRCWR BIT(5) /* data write CRC error */
+#define MMCST0_CRCRD BIT(6) /* data read CRC error */
+#define MMCST0_CRCRS BIT(7) /* command response CRC error */
+#define MMCST0_DXRDY BIT(9) /* data transmit ready (fifo empty) */
+#define MMCST0_DRRDY BIT(10) /* data receive ready (data in fifo)*/
+#define MMCST0_DATED BIT(11) /* DAT3 edge detect */
+#define MMCST0_TRNDNE BIT(12) /* transfer done */
+
+/* DAVINCI_MMCST1 definitions */
+#define MMCST1_BUSY (1 << 0)
+
+/* DAVINCI_MMCCMD definitions */
+#define MMCCMD_CMD_MASK (0x3F << 0)
+#define MMCCMD_PPLEN (1 << 7)
+#define MMCCMD_BSYEXP (1 << 8)
+#define MMCCMD_RSPFMT_MASK (3 << 9)
+#define MMCCMD_RSPFMT_NONE (0 << 9)
+#define MMCCMD_RSPFMT_R1456 (1 << 9)
+#define MMCCMD_RSPFMT_R2 (2 << 9)
+#define MMCCMD_RSPFMT_R3 (3 << 9)
+#define MMCCMD_DTRW (1 << 11)
+#define MMCCMD_STRMTP (1 << 12)
+#define MMCCMD_WDATX (1 << 13)
+#define MMCCMD_INITCK (1 << 14)
+#define MMCCMD_DCLR (1 << 15)
+#define MMCCMD_DMATRIG (1 << 16)
+
+/* DAVINCI_MMCFIFOCTL definitions */
+#define MMCFIFOCTL_FIFORST (1 << 0)
+#define MMCFIFOCTL_FIFODIR_WR (1 << 1)
+#define MMCFIFOCTL_FIFODIR_RD (0 << 1)
+#define MMCFIFOCTL_FIFOLEV (1 << 2) /* 0 = 128 bits, 1 = 256 bits */
+#define MMCFIFOCTL_ACCWD_4 (0 << 3) /* access width of 4 bytes */
+#define MMCFIFOCTL_ACCWD_3 (1 << 3) /* access width of 3 bytes */
+#define MMCFIFOCTL_ACCWD_2 (2 << 3) /* access width of 2 bytes */
+#define MMCFIFOCTL_ACCWD_1 (3 << 3) /* access width of 1 byte */
+
+/* DAVINCI_SDIOST0 definitions */
+#define SDIOST0_DAT1_HI BIT(0)
+
+/* DAVINCI_SDIOIEN definitions */
+#define SDIOIEN_IOINTEN BIT(0)
+
+/* DAVINCI_SDIOIST definitions */
+#define SDIOIST_IOINT BIT(0)
+
+/* MMCSD Init clock in Hz in opendrain mode */
+#define MMCSD_INIT_CLOCK 200000
+
+/*
+ * One scatterlist dma "segment" is at most MAX_CCNT rw_threshold units,
+ * and we handle up to MAX_NR_SG segments. MMC_BLOCK_BOUNCE kicks in only
+ * for drivers with max_segs == 1, making the segments bigger (64KB)
+ * than the page or two that's otherwise typical. nr_sg (passed from
+ * platform data) == 16 gives at least the same throughput boost, using
+ * EDMA transfer linkage instead of spending CPU time copying pages.
+ */
+#define MAX_CCNT ((1 << 16) - 1)
+
+#define MAX_NR_SG 16
+
+static unsigned rw_threshold = 32;
+module_param(rw_threshold, uint, S_IRUGO);
+MODULE_PARM_DESC(rw_threshold,
+ "Read/Write threshold. Default = 32");
+
+static unsigned __initdata use_dma = 1;
+module_param(use_dma, uint, 0);
+MODULE_PARM_DESC(use_dma, "Whether to use DMA or not. Default = 1");
+
+struct mmc_davinci_host {
+ struct mmc_command *cmd;
+ struct mmc_data *data;
+ struct mmc_host *mmc;
+ struct clk *clk;
+ unsigned int mmc_input_clk;
+ void __iomem *base;
+ struct resource *mem_res;
+ int mmc_irq, sdio_irq;
+ unsigned char bus_mode;
+
+#define DAVINCI_MMC_DATADIR_NONE 0
+#define DAVINCI_MMC_DATADIR_READ 1
+#define DAVINCI_MMC_DATADIR_WRITE 2
+ unsigned char data_dir;
+ unsigned char suspended;
+
+ /* buffer is used during PIO of one scatterlist segment, and
+ * is updated along with buffer_bytes_left. bytes_left applies
+ * to all N blocks of the PIO transfer.
+ */
+ u8 *buffer;
+ u32 buffer_bytes_left;
+ u32 bytes_left;
+
+ u32 rxdma, txdma;
+ bool use_dma;
+ bool do_dma;
+ bool sdio_int;
+
+ /* Scatterlist DMA uses one or more parameter RAM entries:
+ * the main one (associated with rxdma or txdma) plus zero or
+ * more links. The entries for a given transfer differ only
+ * by memory buffer (address, length) and link field.
+ */
+ struct edmacc_param tx_template;
+ struct edmacc_param rx_template;
+ unsigned n_link;
+ u32 links[MAX_NR_SG - 1];
+
+ /* For PIO we walk scatterlists one segment at a time. */
+ unsigned int sg_len;
+ struct scatterlist *sg;
+
+ /* Version of the MMC/SD controller */
+ u8 version;
+ /* for ns in one cycle calculation */
+ unsigned ns_in_one_cycle;
+ /* Number of sg segments */
+ u8 nr_sg;
+#ifdef CONFIG_CPU_FREQ
+ struct notifier_block freq_transition;
+#endif
+};
+
+
+/* PIO only */
+static void mmc_davinci_sg_to_buf(struct mmc_davinci_host *host)
+{
+ host->buffer_bytes_left = sg_dma_len(host->sg);
+ host->buffer = sg_virt(host->sg);
+ if (host->buffer_bytes_left > host->bytes_left)
+ host->buffer_bytes_left = host->bytes_left;
+}
+
+static void davinci_fifo_data_trans(struct mmc_davinci_host *host,
+ unsigned int n)
+{
+ u8 *p;
+ unsigned int i;
+
+ if (host->buffer_bytes_left == 0) {
+ host->sg = sg_next(host->data->sg);
+ mmc_davinci_sg_to_buf(host);
+ }
+
+ p = host->buffer;
+ if (n > host->buffer_bytes_left)
+ n = host->buffer_bytes_left;
+ host->buffer_bytes_left -= n;
+ host->bytes_left -= n;
+
+ /* NOTE: we never transfer more than rw_threshold bytes
+ * to/from the fifo here; there's no I/O overlap.
+ * This also assumes that access width( i.e. ACCWD) is 4 bytes
+ */
+ if (host->data_dir == DAVINCI_MMC_DATADIR_WRITE) {
+ for (i = 0; i < (n >> 2); i++) {
+ writel(*((u32 *)p), host->base + DAVINCI_MMCDXR);
+ p = p + 4;
+ }
+ if (n & 3) {
+ iowrite8_rep(host->base + DAVINCI_MMCDXR, p, (n & 3));
+ p = p + (n & 3);
+ }
+ } else {
+ for (i = 0; i < (n >> 2); i++) {
+ *((u32 *)p) = readl(host->base + DAVINCI_MMCDRR);
+ p = p + 4;
+ }
+ if (n & 3) {
+ ioread8_rep(host->base + DAVINCI_MMCDRR, p, (n & 3));
+ p = p + (n & 3);
+ }
+ }
+ host->buffer = p;
+}
+
+static void mmc_davinci_start_command(struct mmc_davinci_host *host,
+ struct mmc_command *cmd)
+{
+ u32 cmd_reg = 0;
+ u32 im_val;
+
+ dev_dbg(mmc_dev(host->mmc), "CMD%d, arg 0x%08x%s\n",
+ cmd->opcode, cmd->arg,
+ ({ char *s;
+ switch (mmc_resp_type(cmd)) {
+ case MMC_RSP_R1:
+ s = ", R1/R5/R6/R7 response";
+ break;
+ case MMC_RSP_R1B:
+ s = ", R1b response";
+ break;
+ case MMC_RSP_R2:
+ s = ", R2 response";
+ break;
+ case MMC_RSP_R3:
+ s = ", R3/R4 response";
+ break;
+ default:
+ s = ", (R? response)";
+ break;
+ }; s; }));
+ host->cmd = cmd;
+
+ switch (mmc_resp_type(cmd)) {
+ case MMC_RSP_R1B:
+ /* There's some spec confusion about when R1B is
+ * allowed, but if the card doesn't issue a BUSY
+ * then it's harmless for us to allow it.
+ */
+ cmd_reg |= MMCCMD_BSYEXP;
+ /* FALLTHROUGH */
+ case MMC_RSP_R1: /* 48 bits, CRC */
+ cmd_reg |= MMCCMD_RSPFMT_R1456;
+ break;
+ case MMC_RSP_R2: /* 136 bits, CRC */
+ cmd_reg |= MMCCMD_RSPFMT_R2;
+ break;
+ case MMC_RSP_R3: /* 48 bits, no CRC */
+ cmd_reg |= MMCCMD_RSPFMT_R3;
+ break;
+ default:
+ cmd_reg |= MMCCMD_RSPFMT_NONE;
+ dev_dbg(mmc_dev(host->mmc), "unknown resp_type %04x\n",
+ mmc_resp_type(cmd));
+ break;
+ }
+
+ /* Set command index */
+ cmd_reg |= cmd->opcode;
+
+ /* Enable EDMA transfer triggers */
+ if (host->do_dma)
+ cmd_reg |= MMCCMD_DMATRIG;
+
+ if (host->version == MMC_CTLR_VERSION_2 && host->data != NULL &&
+ host->data_dir == DAVINCI_MMC_DATADIR_READ)
+ cmd_reg |= MMCCMD_DMATRIG;
+
+ /* Setting whether command involves data transfer or not */
+ if (cmd->data)
+ cmd_reg |= MMCCMD_WDATX;
+
+ /* Setting whether stream or block transfer */
+ if (cmd->flags & MMC_DATA_STREAM)
+ cmd_reg |= MMCCMD_STRMTP;
+
+ /* Setting whether data read or write */
+ if (host->data_dir == DAVINCI_MMC_DATADIR_WRITE)
+ cmd_reg |= MMCCMD_DTRW;
+
+ if (host->bus_mode == MMC_BUSMODE_PUSHPULL)
+ cmd_reg |= MMCCMD_PPLEN;
+
+ /* set Command timeout */
+ writel(0x1FFF, host->base + DAVINCI_MMCTOR);
+
+ /* Enable interrupt (calculate here, defer until FIFO is stuffed). */
+ im_val = MMCST0_RSPDNE | MMCST0_CRCRS | MMCST0_TOUTRS;
+ if (host->data_dir == DAVINCI_MMC_DATADIR_WRITE) {
+ im_val |= MMCST0_DATDNE | MMCST0_CRCWR;
+
+ if (!host->do_dma)
+ im_val |= MMCST0_DXRDY;
+ } else if (host->data_dir == DAVINCI_MMC_DATADIR_READ) {
+ im_val |= MMCST0_DATDNE | MMCST0_CRCRD | MMCST0_TOUTRD;
+
+ if (!host->do_dma)
+ im_val |= MMCST0_DRRDY;
+ }
+
+ /*
+ * Before non-DMA WRITE commands the controller needs priming:
+ * FIFO should be populated with 32 bytes i.e. whatever is the FIFO size
+ */
+ if (!host->do_dma && (host->data_dir == DAVINCI_MMC_DATADIR_WRITE))
+ davinci_fifo_data_trans(host, rw_threshold);
+
+ writel(cmd->arg, host->base + DAVINCI_MMCARGHL);
+ writel(cmd_reg, host->base + DAVINCI_MMCCMD);
+ writel(im_val, host->base + DAVINCI_MMCIM);
+}
+
+/*----------------------------------------------------------------------*/
+
+/* DMA infrastructure */
+
+static void davinci_abort_dma(struct mmc_davinci_host *host)
+{
+ int sync_dev;
+
+ if (host->data_dir == DAVINCI_MMC_DATADIR_READ)
+ sync_dev = host->rxdma;
+ else
+ sync_dev = host->txdma;
+
+ edma_stop(sync_dev);
+ edma_clean_channel(sync_dev);
+}
+
+static void
+mmc_davinci_xfer_done(struct mmc_davinci_host *host, struct mmc_data *data);
+
+static void mmc_davinci_dma_cb(unsigned channel, u16 ch_status, void *data)
+{
+ if (DMA_COMPLETE != ch_status) {
+ struct mmc_davinci_host *host = data;
+
+ /* Currently means: DMA Event Missed, or "null" transfer
+ * request was seen. In the future, TC errors (like bad
+ * addresses) might be presented too.
+ */
+ dev_warn(mmc_dev(host->mmc), "DMA %s error\n",
+ (host->data->flags & MMC_DATA_WRITE)
+ ? "write" : "read");
+ host->data->error = -EIO;
+ mmc_davinci_xfer_done(host, host->data);
+ }
+}
+
+/* Set up tx or rx template, to be modified and updated later */
+static void __init mmc_davinci_dma_setup(struct mmc_davinci_host *host,
+ bool tx, struct edmacc_param *template)
+{
+ unsigned sync_dev;
+ const u16 acnt = 4;
+ const u16 bcnt = rw_threshold >> 2;
+ const u16 ccnt = 0;
+ u32 src_port = 0;
+ u32 dst_port = 0;
+ s16 src_bidx, dst_bidx;
+ s16 src_cidx, dst_cidx;
+
+ /*
+ * A-B Sync transfer: each DMA request is for one "frame" of
+ * rw_threshold bytes, broken into "acnt"-size chunks repeated
+ * "bcnt" times. Each segment needs "ccnt" such frames; since
+ * we tell the block layer our mmc->max_seg_size limit, we can
+ * trust (later) that it's within bounds.
+ *
+ * The FIFOs are read/written in 4-byte chunks (acnt == 4) and
+ * EDMA will optimize memory operations to use larger bursts.
+ */
+ if (tx) {
+ sync_dev = host->txdma;
+
+ /* src_prt, ccnt, and link to be set up later */
+ src_bidx = acnt;
+ src_cidx = acnt * bcnt;
+
+ dst_port = host->mem_res->start + DAVINCI_MMCDXR;
+ dst_bidx = 0;
+ dst_cidx = 0;
+ } else {
+ sync_dev = host->rxdma;
+
+ src_port = host->mem_res->start + DAVINCI_MMCDRR;
+ src_bidx = 0;
+ src_cidx = 0;
+
+ /* dst_prt, ccnt, and link to be set up later */
+ dst_bidx = acnt;
+ dst_cidx = acnt * bcnt;
+ }
+
+ /*
+ * We can't use FIFO mode for the FIFOs because MMC FIFO addresses
+ * are not 256-bit (32-byte) aligned. So we use INCR, and the W8BIT
+ * parameter is ignored.
+ */
+ edma_set_src(sync_dev, src_port, INCR, W8BIT);
+ edma_set_dest(sync_dev, dst_port, INCR, W8BIT);
+
+ edma_set_src_index(sync_dev, src_bidx, src_cidx);
+ edma_set_dest_index(sync_dev, dst_bidx, dst_cidx);
+
+ edma_set_transfer_params(sync_dev, acnt, bcnt, ccnt, 8, ABSYNC);
+
+ edma_read_slot(sync_dev, template);
+
+ /* don't bother with irqs or chaining */
+ template->opt |= EDMA_CHAN_SLOT(sync_dev) << 12;
+}
+
+static void mmc_davinci_send_dma_request(struct mmc_davinci_host *host,
+ struct mmc_data *data)
+{
+ struct edmacc_param *template;
+ int channel, slot;
+ unsigned link;
+ struct scatterlist *sg;
+ unsigned sg_len;
+ unsigned bytes_left = host->bytes_left;
+ const unsigned shift = ffs(rw_threshold) - 1;
+
+ if (host->data_dir == DAVINCI_MMC_DATADIR_WRITE) {
+ template = &host->tx_template;
+ channel = host->txdma;
+ } else {
+ template = &host->rx_template;
+ channel = host->rxdma;
+ }
+
+ /* We know sg_len and ccnt will never be out of range because
+ * we told the mmc layer which in turn tells the block layer
+ * to ensure that it only hands us one scatterlist segment
+ * per EDMA PARAM entry. Update the PARAM
+ * entries needed for each segment of this scatterlist.
+ */
+ for (slot = channel, link = 0, sg = data->sg, sg_len = host->sg_len;
+ sg_len-- != 0 && bytes_left;
+ sg = sg_next(sg), slot = host->links[link++]) {
+ u32 buf = sg_dma_address(sg);
+ unsigned count = sg_dma_len(sg);
+
+ template->link_bcntrld = sg_len
+ ? (EDMA_CHAN_SLOT(host->links[link]) << 5)
+ : 0xffff;
+
+ if (count > bytes_left)
+ count = bytes_left;
+ bytes_left -= count;
+
+ if (host->data_dir == DAVINCI_MMC_DATADIR_WRITE)
+ template->src = buf;
+ else
+ template->dst = buf;
+ template->ccnt = count >> shift;
+
+ edma_write_slot(slot, template);
+ }
+
+ if (host->version == MMC_CTLR_VERSION_2)
+ edma_clear_event(channel);
+
+ edma_start(channel);
+}
+
+static int mmc_davinci_start_dma_transfer(struct mmc_davinci_host *host,
+ struct mmc_data *data)
+{
+ int i;
+ int mask = rw_threshold - 1;
+
+ host->sg_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+ ((data->flags & MMC_DATA_WRITE)
+ ? DMA_TO_DEVICE
+ : DMA_FROM_DEVICE));
+
+ /* no individual DMA segment should need a partial FIFO */
+ for (i = 0; i < host->sg_len; i++) {
+ if (sg_dma_len(data->sg + i) & mask) {
+ dma_unmap_sg(mmc_dev(host->mmc),
+ data->sg, data->sg_len,
+ (data->flags & MMC_DATA_WRITE)
+ ? DMA_TO_DEVICE
+ : DMA_FROM_DEVICE);
+ return -1;
+ }
+ }
+
+ host->do_dma = 1;
+ mmc_davinci_send_dma_request(host, data);
+
+ return 0;
+}
+
+static void __init_or_module
+davinci_release_dma_channels(struct mmc_davinci_host *host)
+{
+ unsigned i;
+
+ if (!host->use_dma)
+ return;
+
+ for (i = 0; i < host->n_link; i++)
+ edma_free_slot(host->links[i]);
+
+ edma_free_channel(host->txdma);
+ edma_free_channel(host->rxdma);
+}
+
+static int __init davinci_acquire_dma_channels(struct mmc_davinci_host *host)
+{
+ u32 link_size;
+ int r, i;
+
+ /* Acquire master DMA write channel */
+ r = edma_alloc_channel(host->txdma, mmc_davinci_dma_cb, host,
+ EVENTQ_DEFAULT);
+ if (r < 0) {
+ dev_warn(mmc_dev(host->mmc), "alloc %s channel err %d\n",
+ "tx", r);
+ return r;
+ }
+ mmc_davinci_dma_setup(host, true, &host->tx_template);
+
+ /* Acquire master DMA read channel */
+ r = edma_alloc_channel(host->rxdma, mmc_davinci_dma_cb, host,
+ EVENTQ_DEFAULT);
+ if (r < 0) {
+ dev_warn(mmc_dev(host->mmc), "alloc %s channel err %d\n",
+ "rx", r);
+ goto free_master_write;
+ }
+ mmc_davinci_dma_setup(host, false, &host->rx_template);
+
+ /* Allocate parameter RAM slots, which will later be bound to a
+ * channel as needed to handle a scatterlist.
+ */
+ link_size = min_t(unsigned, host->nr_sg, ARRAY_SIZE(host->links));
+ for (i = 0; i < link_size; i++) {
+ r = edma_alloc_slot(EDMA_CTLR(host->txdma), EDMA_SLOT_ANY);
+ if (r < 0) {
+ dev_dbg(mmc_dev(host->mmc), "dma PaRAM alloc --> %d\n",
+ r);
+ break;
+ }
+ host->links[i] = r;
+ }
+ host->n_link = i;
+
+ return 0;
+
+free_master_write:
+ edma_free_channel(host->txdma);
+
+ return r;
+}
+
+/*----------------------------------------------------------------------*/
+
+static void
+mmc_davinci_prepare_data(struct mmc_davinci_host *host, struct mmc_request *req)
+{
+ int fifo_lev = (rw_threshold == 32) ? MMCFIFOCTL_FIFOLEV : 0;
+ int timeout;
+ struct mmc_data *data = req->data;
+
+ if (host->version == MMC_CTLR_VERSION_2)
+ fifo_lev = (rw_threshold == 64) ? MMCFIFOCTL_FIFOLEV : 0;
+
+ host->data = data;
+ if (data == NULL) {
+ host->data_dir = DAVINCI_MMC_DATADIR_NONE;
+ writel(0, host->base + DAVINCI_MMCBLEN);
+ writel(0, host->base + DAVINCI_MMCNBLK);
+ return;
+ }
+
+ dev_dbg(mmc_dev(host->mmc), "%s %s, %d blocks of %d bytes\n",
+ (data->flags & MMC_DATA_STREAM) ? "stream" : "block",
+ (data->flags & MMC_DATA_WRITE) ? "write" : "read",
+ data->blocks, data->blksz);
+ dev_dbg(mmc_dev(host->mmc), " DTO %d cycles + %d ns\n",
+ data->timeout_clks, data->timeout_ns);
+ timeout = data->timeout_clks +
+ (data->timeout_ns / host->ns_in_one_cycle);
+ if (timeout > 0xffff)
+ timeout = 0xffff;
+
+ writel(timeout, host->base + DAVINCI_MMCTOD);
+ writel(data->blocks, host->base + DAVINCI_MMCNBLK);
+ writel(data->blksz, host->base + DAVINCI_MMCBLEN);
+
+ /* Configure the FIFO */
+ switch (data->flags & MMC_DATA_WRITE) {
+ case MMC_DATA_WRITE:
+ host->data_dir = DAVINCI_MMC_DATADIR_WRITE;
+ writel(fifo_lev | MMCFIFOCTL_FIFODIR_WR | MMCFIFOCTL_FIFORST,
+ host->base + DAVINCI_MMCFIFOCTL);
+ writel(fifo_lev | MMCFIFOCTL_FIFODIR_WR,
+ host->base + DAVINCI_MMCFIFOCTL);
+ break;
+
+ default:
+ host->data_dir = DAVINCI_MMC_DATADIR_READ;
+ writel(fifo_lev | MMCFIFOCTL_FIFODIR_RD | MMCFIFOCTL_FIFORST,
+ host->base + DAVINCI_MMCFIFOCTL);
+ writel(fifo_lev | MMCFIFOCTL_FIFODIR_RD,
+ host->base + DAVINCI_MMCFIFOCTL);
+ break;
+ }
+
+ host->buffer = NULL;
+ host->bytes_left = data->blocks * data->blksz;
+
+ /* For now we try to use DMA whenever we won't need partial FIFO
+ * reads or writes, either for the whole transfer (as tested here)
+ * or for any individual scatterlist segment (tested when we call
+ * start_dma_transfer).
+ *
+ * While we *could* change that, unusual block sizes are rarely
+ * used. The occasional fallback to PIO should't hurt.
+ */
+ if (host->use_dma && (host->bytes_left & (rw_threshold - 1)) == 0
+ && mmc_davinci_start_dma_transfer(host, data) == 0) {
+ /* zero this to ensure we take no PIO paths */
+ host->bytes_left = 0;
+ } else {
+ /* Revert to CPU Copy */
+ host->sg_len = data->sg_len;
+ host->sg = host->data->sg;
+ mmc_davinci_sg_to_buf(host);
+ }
+}
+
+static void mmc_davinci_request(struct mmc_host *mmc, struct mmc_request *req)
+{
+ struct mmc_davinci_host *host = mmc_priv(mmc);
+ unsigned long timeout = jiffies + msecs_to_jiffies(900);
+ u32 mmcst1 = 0;
+
+ /* Card may still be sending BUSY after a previous operation,
+ * typically some kind of write. If so, we can't proceed yet.
+ */
+ while (time_before(jiffies, timeout)) {
+ mmcst1 = readl(host->base + DAVINCI_MMCST1);
+ if (!(mmcst1 & MMCST1_BUSY))
+ break;
+ cpu_relax();
+ }
+ if (mmcst1 & MMCST1_BUSY) {
+ dev_err(mmc_dev(host->mmc), "still BUSY? bad ... \n");
+ req->cmd->error = -ETIMEDOUT;
+ mmc_request_done(mmc, req);
+ return;
+ }
+
+ host->do_dma = 0;
+ mmc_davinci_prepare_data(host, req);
+ mmc_davinci_start_command(host, req->cmd);
+}
+
+static unsigned int calculate_freq_for_card(struct mmc_davinci_host *host,
+ unsigned int mmc_req_freq)
+{
+ unsigned int mmc_freq = 0, mmc_pclk = 0, mmc_push_pull_divisor = 0;
+
+ mmc_pclk = host->mmc_input_clk;
+ if (mmc_req_freq && mmc_pclk > (2 * mmc_req_freq))
+ mmc_push_pull_divisor = ((unsigned int)mmc_pclk
+ / (2 * mmc_req_freq)) - 1;
+ else
+ mmc_push_pull_divisor = 0;
+
+ mmc_freq = (unsigned int)mmc_pclk
+ / (2 * (mmc_push_pull_divisor + 1));
+
+ if (mmc_freq > mmc_req_freq)
+ mmc_push_pull_divisor = mmc_push_pull_divisor + 1;
+ /* Convert ns to clock cycles */
+ if (mmc_req_freq <= 400000)
+ host->ns_in_one_cycle = (1000000) / (((mmc_pclk
+ / (2 * (mmc_push_pull_divisor + 1)))/1000));
+ else
+ host->ns_in_one_cycle = (1000000) / (((mmc_pclk
+ / (2 * (mmc_push_pull_divisor + 1)))/1000000));
+
+ return mmc_push_pull_divisor;
+}
+
+static void calculate_clk_divider(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ unsigned int open_drain_freq = 0, mmc_pclk = 0;
+ unsigned int mmc_push_pull_freq = 0;
+ struct mmc_davinci_host *host = mmc_priv(mmc);
+
+ if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN) {
+ u32 temp;
+
+ /* Ignoring the init clock value passed for fixing the inter
+ * operability with different cards.
+ */
+ open_drain_freq = ((unsigned int)mmc_pclk
+ / (2 * MMCSD_INIT_CLOCK)) - 1;
+
+ if (open_drain_freq > 0xFF)
+ open_drain_freq = 0xFF;
+
+ temp = readl(host->base + DAVINCI_MMCCLK) & ~MMCCLK_CLKRT_MASK;
+ temp |= open_drain_freq;
+ writel(temp, host->base + DAVINCI_MMCCLK);
+
+ /* Convert ns to clock cycles */
+ host->ns_in_one_cycle = (1000000) / (MMCSD_INIT_CLOCK/1000);
+ } else {
+ u32 temp;
+ mmc_push_pull_freq = calculate_freq_for_card(host, ios->clock);
+
+ if (mmc_push_pull_freq > 0xFF)
+ mmc_push_pull_freq = 0xFF;
+
+ temp = readl(host->base + DAVINCI_MMCCLK) & ~MMCCLK_CLKEN;
+ writel(temp, host->base + DAVINCI_MMCCLK);
+
+ udelay(10);
+
+ temp = readl(host->base + DAVINCI_MMCCLK) & ~MMCCLK_CLKRT_MASK;
+ temp |= mmc_push_pull_freq;
+ writel(temp, host->base + DAVINCI_MMCCLK);
+
+ writel(temp | MMCCLK_CLKEN, host->base + DAVINCI_MMCCLK);
+
+ udelay(10);
+ }
+}
+
+static void mmc_davinci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct mmc_davinci_host *host = mmc_priv(mmc);
+
+ dev_dbg(mmc_dev(host->mmc),
+ "clock %dHz busmode %d powermode %d Vdd %04x\n",
+ ios->clock, ios->bus_mode, ios->power_mode,
+ ios->vdd);
+
+ switch (ios->bus_width) {
+ case MMC_BUS_WIDTH_8:
+ dev_dbg(mmc_dev(host->mmc), "Enabling 8 bit mode\n");
+ writel((readl(host->base + DAVINCI_MMCCTL) &
+ ~MMCCTL_WIDTH_4_BIT) | MMCCTL_WIDTH_8_BIT,
+ host->base + DAVINCI_MMCCTL);
+ break;
+ case MMC_BUS_WIDTH_4:
+ dev_dbg(mmc_dev(host->mmc), "Enabling 4 bit mode\n");
+ if (host->version == MMC_CTLR_VERSION_2)
+ writel((readl(host->base + DAVINCI_MMCCTL) &
+ ~MMCCTL_WIDTH_8_BIT) | MMCCTL_WIDTH_4_BIT,
+ host->base + DAVINCI_MMCCTL);
+ else
+ writel(readl(host->base + DAVINCI_MMCCTL) |
+ MMCCTL_WIDTH_4_BIT,
+ host->base + DAVINCI_MMCCTL);
+ break;
+ case MMC_BUS_WIDTH_1:
+ dev_dbg(mmc_dev(host->mmc), "Enabling 1 bit mode\n");
+ if (host->version == MMC_CTLR_VERSION_2)
+ writel(readl(host->base + DAVINCI_MMCCTL) &
+ ~(MMCCTL_WIDTH_8_BIT | MMCCTL_WIDTH_4_BIT),
+ host->base + DAVINCI_MMCCTL);
+ else
+ writel(readl(host->base + DAVINCI_MMCCTL) &
+ ~MMCCTL_WIDTH_4_BIT,
+ host->base + DAVINCI_MMCCTL);
+ break;
+ }
+
+ calculate_clk_divider(mmc, ios);
+
+ host->bus_mode = ios->bus_mode;
+ if (ios->power_mode == MMC_POWER_UP) {
+ unsigned long timeout = jiffies + msecs_to_jiffies(50);
+ bool lose = true;
+
+ /* Send clock cycles, poll completion */
+ writel(0, host->base + DAVINCI_MMCARGHL);
+ writel(MMCCMD_INITCK, host->base + DAVINCI_MMCCMD);
+ while (time_before(jiffies, timeout)) {
+ u32 tmp = readl(host->base + DAVINCI_MMCST0);
+
+ if (tmp & MMCST0_RSPDNE) {
+ lose = false;
+ break;
+ }
+ cpu_relax();
+ }
+ if (lose)
+ dev_warn(mmc_dev(host->mmc), "powerup timeout\n");
+ }
+
+ /* FIXME on power OFF, reset things ... */
+}
+
+static void
+mmc_davinci_xfer_done(struct mmc_davinci_host *host, struct mmc_data *data)
+{
+ host->data = NULL;
+
+ if (host->mmc->caps & MMC_CAP_SDIO_IRQ) {
+ /*
+ * SDIO Interrupt Detection work-around as suggested by
+ * Davinci Errata (TMS320DM355 Silicon Revision 1.1 Errata
+ * 2.1.6): Signal SDIO interrupt only if it is enabled by core
+ */
+ if (host->sdio_int && !(readl(host->base + DAVINCI_SDIOST0) &
+ SDIOST0_DAT1_HI)) {
+ writel(SDIOIST_IOINT, host->base + DAVINCI_SDIOIST);
+ mmc_signal_sdio_irq(host->mmc);
+ }
+ }
+
+ if (host->do_dma) {
+ davinci_abort_dma(host);
+
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+ (data->flags & MMC_DATA_WRITE)
+ ? DMA_TO_DEVICE
+ : DMA_FROM_DEVICE);
+ host->do_dma = false;
+ }
+ host->data_dir = DAVINCI_MMC_DATADIR_NONE;
+
+ if (!data->stop || (host->cmd && host->cmd->error)) {
+ mmc_request_done(host->mmc, data->mrq);
+ writel(0, host->base + DAVINCI_MMCIM);
+ } else
+ mmc_davinci_start_command(host, data->stop);
+}
+
+static void mmc_davinci_cmd_done(struct mmc_davinci_host *host,
+ struct mmc_command *cmd)
+{
+ host->cmd = NULL;
+
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ if (cmd->flags & MMC_RSP_136) {
+ /* response type 2 */
+ cmd->resp[3] = readl(host->base + DAVINCI_MMCRSP01);
+ cmd->resp[2] = readl(host->base + DAVINCI_MMCRSP23);
+ cmd->resp[1] = readl(host->base + DAVINCI_MMCRSP45);
+ cmd->resp[0] = readl(host->base + DAVINCI_MMCRSP67);
+ } else {
+ /* response types 1, 1b, 3, 4, 5, 6 */
+ cmd->resp[0] = readl(host->base + DAVINCI_MMCRSP67);
+ }
+ }
+
+ if (host->data == NULL || cmd->error) {
+ if (cmd->error == -ETIMEDOUT)
+ cmd->mrq->cmd->retries = 0;
+ mmc_request_done(host->mmc, cmd->mrq);
+ writel(0, host->base + DAVINCI_MMCIM);
+ }
+}
+
+static inline void mmc_davinci_reset_ctrl(struct mmc_davinci_host *host,
+ int val)
+{
+ u32 temp;
+
+ temp = readl(host->base + DAVINCI_MMCCTL);
+ if (val) /* reset */
+ temp |= MMCCTL_CMDRST | MMCCTL_DATRST;
+ else /* enable */
+ temp &= ~(MMCCTL_CMDRST | MMCCTL_DATRST);
+
+ writel(temp, host->base + DAVINCI_MMCCTL);
+ udelay(10);
+}
+
+static void
+davinci_abort_data(struct mmc_davinci_host *host, struct mmc_data *data)
+{
+ mmc_davinci_reset_ctrl(host, 1);
+ mmc_davinci_reset_ctrl(host, 0);
+}
+
+static irqreturn_t mmc_davinci_sdio_irq(int irq, void *dev_id)
+{
+ struct mmc_davinci_host *host = dev_id;
+ unsigned int status;
+
+ status = readl(host->base + DAVINCI_SDIOIST);
+ if (status & SDIOIST_IOINT) {
+ dev_dbg(mmc_dev(host->mmc),
+ "SDIO interrupt status %x\n", status);
+ writel(status | SDIOIST_IOINT, host->base + DAVINCI_SDIOIST);
+ mmc_signal_sdio_irq(host->mmc);
+ }
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t mmc_davinci_irq(int irq, void *dev_id)
+{
+ struct mmc_davinci_host *host = (struct mmc_davinci_host *)dev_id;
+ unsigned int status, qstatus;
+ int end_command = 0;
+ int end_transfer = 0;
+ struct mmc_data *data = host->data;
+
+ if (host->cmd == NULL && host->data == NULL) {
+ status = readl(host->base + DAVINCI_MMCST0);
+ dev_dbg(mmc_dev(host->mmc),
+ "Spurious interrupt 0x%04x\n", status);
+ /* Disable the interrupt from mmcsd */
+ writel(0, host->base + DAVINCI_MMCIM);
+ return IRQ_NONE;
+ }
+
+ status = readl(host->base + DAVINCI_MMCST0);
+ qstatus = status;
+
+ /* handle FIFO first when using PIO for data.
+ * bytes_left will decrease to zero as I/O progress and status will
+ * read zero over iteration because this controller status
+ * register(MMCST0) reports any status only once and it is cleared
+ * by read. So, it is not unbouned loop even in the case of
+ * non-dma.
+ */
+ while (host->bytes_left && (status & (MMCST0_DXRDY | MMCST0_DRRDY))) {
+ davinci_fifo_data_trans(host, rw_threshold);
+ status = readl(host->base + DAVINCI_MMCST0);
+ if (!status)
+ break;
+ qstatus |= status;
+ }
+
+ if (qstatus & MMCST0_DATDNE) {
+ /* All blocks sent/received, and CRC checks passed */
+ if (data != NULL) {
+ if ((host->do_dma == 0) && (host->bytes_left > 0)) {
+ /* if datasize < rw_threshold
+ * no RX ints are generated
+ */
+ davinci_fifo_data_trans(host, host->bytes_left);
+ }
+ end_transfer = 1;
+ data->bytes_xfered = data->blocks * data->blksz;
+ } else {
+ dev_err(mmc_dev(host->mmc),
+ "DATDNE with no host->data\n");
+ }
+ }
+
+ if (qstatus & MMCST0_TOUTRD) {
+ /* Read data timeout */
+ data->error = -ETIMEDOUT;
+ end_transfer = 1;
+
+ dev_dbg(mmc_dev(host->mmc),
+ "read data timeout, status %x\n",
+ qstatus);
+
+ davinci_abort_data(host, data);
+ }
+
+ if (qstatus & (MMCST0_CRCWR | MMCST0_CRCRD)) {
+ /* Data CRC error */
+ data->error = -EILSEQ;
+ end_transfer = 1;
+
+ /* NOTE: this controller uses CRCWR to report both CRC
+ * errors and timeouts (on writes). MMCDRSP values are
+ * only weakly documented, but 0x9f was clearly a timeout
+ * case and the two three-bit patterns in various SD specs
+ * (101, 010) aren't part of it ...
+ */
+ if (qstatus & MMCST0_CRCWR) {
+ u32 temp = readb(host->base + DAVINCI_MMCDRSP);
+
+ if (temp == 0x9f)
+ data->error = -ETIMEDOUT;
+ }
+ dev_dbg(mmc_dev(host->mmc), "data %s %s error\n",
+ (qstatus & MMCST0_CRCWR) ? "write" : "read",
+ (data->error == -ETIMEDOUT) ? "timeout" : "CRC");
+
+ davinci_abort_data(host, data);
+ }
+
+ if (qstatus & MMCST0_TOUTRS) {
+ /* Command timeout */
+ if (host->cmd) {
+ dev_dbg(mmc_dev(host->mmc),
+ "CMD%d timeout, status %x\n",
+ host->cmd->opcode, qstatus);
+ host->cmd->error = -ETIMEDOUT;
+ if (data) {
+ end_transfer = 1;
+ davinci_abort_data(host, data);
+ } else
+ end_command = 1;
+ }
+ }
+
+ if (qstatus & MMCST0_CRCRS) {
+ /* Command CRC error */
+ dev_dbg(mmc_dev(host->mmc), "Command CRC error\n");
+ if (host->cmd) {
+ host->cmd->error = -EILSEQ;
+ end_command = 1;
+ }
+ }
+
+ if (qstatus & MMCST0_RSPDNE) {
+ /* End of command phase */
+ end_command = (int) host->cmd;
+ }
+
+ if (end_command)
+ mmc_davinci_cmd_done(host, host->cmd);
+ if (end_transfer)
+ mmc_davinci_xfer_done(host, data);
+ return IRQ_HANDLED;
+}
+
+static int mmc_davinci_get_cd(struct mmc_host *mmc)
+{
+ struct platform_device *pdev = to_platform_device(mmc->parent);
+ struct davinci_mmc_config *config = pdev->dev.platform_data;
+
+ if (!config || !config->get_cd)
+ return -ENOSYS;
+ return config->get_cd(pdev->id);
+}
+
+static int mmc_davinci_get_ro(struct mmc_host *mmc)
+{
+ struct platform_device *pdev = to_platform_device(mmc->parent);
+ struct davinci_mmc_config *config = pdev->dev.platform_data;
+
+ if (!config || !config->get_ro)
+ return -ENOSYS;
+ return config->get_ro(pdev->id);
+}
+
+static void mmc_davinci_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+ struct mmc_davinci_host *host = mmc_priv(mmc);
+
+ if (enable) {
+ if (!(readl(host->base + DAVINCI_SDIOST0) & SDIOST0_DAT1_HI)) {
+ writel(SDIOIST_IOINT, host->base + DAVINCI_SDIOIST);
+ mmc_signal_sdio_irq(host->mmc);
+ } else {
+ host->sdio_int = true;
+ writel(readl(host->base + DAVINCI_SDIOIEN) |
+ SDIOIEN_IOINTEN, host->base + DAVINCI_SDIOIEN);
+ }
+ } else {
+ host->sdio_int = false;
+ writel(readl(host->base + DAVINCI_SDIOIEN) & ~SDIOIEN_IOINTEN,
+ host->base + DAVINCI_SDIOIEN);
+ }
+}
+
+static struct mmc_host_ops mmc_davinci_ops = {
+ .request = mmc_davinci_request,
+ .set_ios = mmc_davinci_set_ios,
+ .get_cd = mmc_davinci_get_cd,
+ .get_ro = mmc_davinci_get_ro,
+ .enable_sdio_irq = mmc_davinci_enable_sdio_irq,
+};
+
+/*----------------------------------------------------------------------*/
+
+#ifdef CONFIG_CPU_FREQ
+static int mmc_davinci_cpufreq_transition(struct notifier_block *nb,
+ unsigned long val, void *data)
+{
+ struct mmc_davinci_host *host;
+ unsigned int mmc_pclk;
+ struct mmc_host *mmc;
+ unsigned long flags;
+
+ host = container_of(nb, struct mmc_davinci_host, freq_transition);
+ mmc = host->mmc;
+ mmc_pclk = clk_get_rate(host->clk);
+
+ if (val == CPUFREQ_POSTCHANGE) {
+ spin_lock_irqsave(&mmc->lock, flags);
+ host->mmc_input_clk = mmc_pclk;
+ calculate_clk_divider(mmc, &mmc->ios);
+ spin_unlock_irqrestore(&mmc->lock, flags);
+ }
+
+ return 0;
+}
+
+static inline int mmc_davinci_cpufreq_register(struct mmc_davinci_host *host)
+{
+ host->freq_transition.notifier_call = mmc_davinci_cpufreq_transition;
+
+ return cpufreq_register_notifier(&host->freq_transition,
+ CPUFREQ_TRANSITION_NOTIFIER);
+}
+
+static inline void mmc_davinci_cpufreq_deregister(struct mmc_davinci_host *host)
+{
+ cpufreq_unregister_notifier(&host->freq_transition,
+ CPUFREQ_TRANSITION_NOTIFIER);
+}
+#else
+static inline int mmc_davinci_cpufreq_register(struct mmc_davinci_host *host)
+{
+ return 0;
+}
+
+static inline void mmc_davinci_cpufreq_deregister(struct mmc_davinci_host *host)
+{
+}
+#endif
+static void __init init_mmcsd_host(struct mmc_davinci_host *host)
+{
+
+ mmc_davinci_reset_ctrl(host, 1);
+
+ writel(0, host->base + DAVINCI_MMCCLK);
+ writel(MMCCLK_CLKEN, host->base + DAVINCI_MMCCLK);
+
+ writel(0x1FFF, host->base + DAVINCI_MMCTOR);
+ writel(0xFFFF, host->base + DAVINCI_MMCTOD);
+
+ mmc_davinci_reset_ctrl(host, 0);
+}
+
+static int __init davinci_mmcsd_probe(struct platform_device *pdev)
+{
+ struct davinci_mmc_config *pdata = pdev->dev.platform_data;
+ struct mmc_davinci_host *host = NULL;
+ struct mmc_host *mmc = NULL;
+ struct resource *r, *mem = NULL;
+ int ret = 0, irq = 0;
+ size_t mem_size;
+
+ /* REVISIT: when we're fully converted, fail if pdata is NULL */
+
+ ret = -ENODEV;
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ irq = platform_get_irq(pdev, 0);
+ if (!r || irq == NO_IRQ)
+ goto out;
+
+ ret = -EBUSY;
+ mem_size = resource_size(r);
+ mem = request_mem_region(r->start, mem_size, pdev->name);
+ if (!mem)
+ goto out;
+
+ ret = -ENOMEM;
+ mmc = mmc_alloc_host(sizeof(struct mmc_davinci_host), &pdev->dev);
+ if (!mmc)
+ goto out;
+
+ host = mmc_priv(mmc);
+ host->mmc = mmc; /* Important */
+
+ r = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ if (!r)
+ goto out;
+ host->rxdma = r->start;
+
+ r = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+ if (!r)
+ goto out;
+ host->txdma = r->start;
+
+ host->mem_res = mem;
+ host->base = ioremap(mem->start, mem_size);
+ if (!host->base)
+ goto out;
+
+ ret = -ENXIO;
+ host->clk = clk_get(&pdev->dev, "MMCSDCLK");
+ if (IS_ERR(host->clk)) {
+ ret = PTR_ERR(host->clk);
+ goto out;
+ }
+ clk_enable(host->clk);
+ host->mmc_input_clk = clk_get_rate(host->clk);
+
+ init_mmcsd_host(host);
+
+ if (pdata->nr_sg)
+ host->nr_sg = pdata->nr_sg - 1;
+
+ if (host->nr_sg > MAX_NR_SG || !host->nr_sg)
+ host->nr_sg = MAX_NR_SG;
+
+ host->use_dma = use_dma;
+ host->mmc_irq = irq;
+ host->sdio_irq = platform_get_irq(pdev, 1);
+
+ if (host->use_dma && davinci_acquire_dma_channels(host) != 0)
+ host->use_dma = 0;
+
+ /* REVISIT: someday, support IRQ-driven card detection. */
+ mmc->caps |= MMC_CAP_NEEDS_POLL;
+ mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY;
+
+ if (pdata && (pdata->wires == 4 || pdata->wires == 0))
+ mmc->caps |= MMC_CAP_4_BIT_DATA;
+
+ if (pdata && (pdata->wires == 8))
+ mmc->caps |= (MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA);
+
+ host->version = pdata->version;
+
+ mmc->ops = &mmc_davinci_ops;
+ mmc->f_min = 312500;
+ mmc->f_max = 25000000;
+ if (pdata && pdata->max_freq)
+ mmc->f_max = pdata->max_freq;
+ if (pdata && pdata->caps)
+ mmc->caps |= pdata->caps;
+ mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+
+ /* With no iommu coalescing pages, each phys_seg is a hw_seg.
+ * Each hw_seg uses one EDMA parameter RAM slot, always one
+ * channel and then usually some linked slots.
+ */
+ mmc->max_segs = 1 + host->n_link;
+
+ /* EDMA limit per hw segment (one or two MBytes) */
+ mmc->max_seg_size = MAX_CCNT * rw_threshold;
+
+ /* MMC/SD controller limits for multiblock requests */
+ mmc->max_blk_size = 4095; /* BLEN is 12 bits */
+ mmc->max_blk_count = 65535; /* NBLK is 16 bits */
+ mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
+
+ dev_dbg(mmc_dev(host->mmc), "max_segs=%d\n", mmc->max_segs);
+ dev_dbg(mmc_dev(host->mmc), "max_blk_size=%d\n", mmc->max_blk_size);
+ dev_dbg(mmc_dev(host->mmc), "max_req_size=%d\n", mmc->max_req_size);
+ dev_dbg(mmc_dev(host->mmc), "max_seg_size=%d\n", mmc->max_seg_size);
+
+ platform_set_drvdata(pdev, host);
+
+ ret = mmc_davinci_cpufreq_register(host);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register cpufreq\n");
+ goto cpu_freq_fail;
+ }
+
+ ret = mmc_add_host(mmc);
+ if (ret < 0)
+ goto out;
+
+ ret = request_irq(irq, mmc_davinci_irq, 0, mmc_hostname(mmc), host);
+ if (ret)
+ goto out;
+
+ if (host->sdio_irq >= 0) {
+ ret = request_irq(host->sdio_irq, mmc_davinci_sdio_irq, 0,
+ mmc_hostname(mmc), host);
+ if (!ret)
+ mmc->caps |= MMC_CAP_SDIO_IRQ;
+ }
+
+ rename_region(mem, mmc_hostname(mmc));
+
+ dev_info(mmc_dev(host->mmc), "Using %s, %d-bit mode\n",
+ host->use_dma ? "DMA" : "PIO",
+ (mmc->caps & MMC_CAP_4_BIT_DATA) ? 4 : 1);
+
+ return 0;
+
+out:
+ mmc_davinci_cpufreq_deregister(host);
+cpu_freq_fail:
+ if (host) {
+ davinci_release_dma_channels(host);
+
+ if (host->clk) {
+ clk_disable(host->clk);
+ clk_put(host->clk);
+ }
+
+ if (host->base)
+ iounmap(host->base);
+ }
+
+ if (mmc)
+ mmc_free_host(mmc);
+
+ if (mem)
+ release_resource(mem);
+
+ dev_dbg(&pdev->dev, "probe err %d\n", ret);
+
+ return ret;
+}
+
+static int __exit davinci_mmcsd_remove(struct platform_device *pdev)
+{
+ struct mmc_davinci_host *host = platform_get_drvdata(pdev);
+
+ platform_set_drvdata(pdev, NULL);
+ if (host) {
+ mmc_davinci_cpufreq_deregister(host);
+
+ mmc_remove_host(host->mmc);
+ free_irq(host->mmc_irq, host);
+ if (host->mmc->caps & MMC_CAP_SDIO_IRQ)
+ free_irq(host->sdio_irq, host);
+
+ davinci_release_dma_channels(host);
+
+ clk_disable(host->clk);
+ clk_put(host->clk);
+
+ iounmap(host->base);
+
+ release_resource(host->mem_res);
+
+ mmc_free_host(host->mmc);
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int davinci_mmcsd_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mmc_davinci_host *host = platform_get_drvdata(pdev);
+ int ret;
+
+ mmc_host_enable(host->mmc);
+ ret = mmc_suspend_host(host->mmc);
+ if (!ret) {
+ writel(0, host->base + DAVINCI_MMCIM);
+ mmc_davinci_reset_ctrl(host, 1);
+ mmc_host_disable(host->mmc);
+ clk_disable(host->clk);
+ host->suspended = 1;
+ } else {
+ host->suspended = 0;
+ mmc_host_disable(host->mmc);
+ }
+
+ return ret;
+}
+
+static int davinci_mmcsd_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct mmc_davinci_host *host = platform_get_drvdata(pdev);
+ int ret;
+
+ if (!host->suspended)
+ return 0;
+
+ clk_enable(host->clk);
+ mmc_host_enable(host->mmc);
+
+ mmc_davinci_reset_ctrl(host, 0);
+ ret = mmc_resume_host(host->mmc);
+ if (!ret)
+ host->suspended = 0;
+
+ return ret;
+}
+
+static const struct dev_pm_ops davinci_mmcsd_pm = {
+ .suspend = davinci_mmcsd_suspend,
+ .resume = davinci_mmcsd_resume,
+};
+
+#define davinci_mmcsd_pm_ops (&davinci_mmcsd_pm)
+#else
+#define davinci_mmcsd_pm_ops NULL
+#endif
+
+static struct platform_driver davinci_mmcsd_driver = {
+ .driver = {
+ .name = "davinci_mmc",
+ .owner = THIS_MODULE,
+ .pm = davinci_mmcsd_pm_ops,
+ },
+ .remove = __exit_p(davinci_mmcsd_remove),
+};
+
+static int __init davinci_mmcsd_init(void)
+{
+ return platform_driver_probe(&davinci_mmcsd_driver,
+ davinci_mmcsd_probe);
+}
+module_init(davinci_mmcsd_init);
+
+static void __exit davinci_mmcsd_exit(void)
+{
+ platform_driver_unregister(&davinci_mmcsd_driver);
+}
+module_exit(davinci_mmcsd_exit);
+
+MODULE_AUTHOR("Texas Instruments India");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("MMC/SD driver for Davinci MMC controller");
+
diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c
new file mode 100644
index 00000000..66dcddb9
--- /dev/null
+++ b/drivers/mmc/host/dw_mmc.c
@@ -0,0 +1,1859 @@
+/*
+ * Synopsys DesignWare Multimedia Card Interface driver
+ * (Based on NXP driver for lpc 31xx)
+ *
+ * Copyright (C) 2009 NXP Semiconductors
+ * Copyright (C) 2009, 2010 Imagination Technologies Ltd.
+ *
+ * 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.
+ */
+
+#include <linux/blkdev.h>
+#include <linux/clk.h>
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.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/seq_file.h>
+#include <linux/slab.h>
+#include <linux/stat.h>
+#include <linux/delay.h>
+#include <linux/irq.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/dw_mmc.h>
+#include <linux/bitops.h>
+#include <linux/regulator/consumer.h>
+
+#include "dw_mmc.h"
+
+/* Common flag combinations */
+#define DW_MCI_DATA_ERROR_FLAGS (SDMMC_INT_DTO | SDMMC_INT_DCRC | \
+ SDMMC_INT_HTO | SDMMC_INT_SBE | \
+ SDMMC_INT_EBE)
+#define DW_MCI_CMD_ERROR_FLAGS (SDMMC_INT_RTO | SDMMC_INT_RCRC | \
+ SDMMC_INT_RESP_ERR)
+#define DW_MCI_ERROR_FLAGS (DW_MCI_DATA_ERROR_FLAGS | \
+ DW_MCI_CMD_ERROR_FLAGS | SDMMC_INT_HLE)
+#define DW_MCI_SEND_STATUS 1
+#define DW_MCI_RECV_STATUS 2
+#define DW_MCI_DMA_THRESHOLD 16
+
+#ifdef CONFIG_MMC_DW_IDMAC
+struct idmac_desc {
+ u32 des0; /* Control Descriptor */
+#define IDMAC_DES0_DIC BIT(1)
+#define IDMAC_DES0_LD BIT(2)
+#define IDMAC_DES0_FD BIT(3)
+#define IDMAC_DES0_CH BIT(4)
+#define IDMAC_DES0_ER BIT(5)
+#define IDMAC_DES0_CES BIT(30)
+#define IDMAC_DES0_OWN BIT(31)
+
+ u32 des1; /* Buffer sizes */
+#define IDMAC_SET_BUFFER1_SIZE(d, s) \
+ ((d)->des1 = ((d)->des1 & 0x03ffc000) | ((s) & 0x3fff))
+
+ u32 des2; /* buffer 1 physical address */
+
+ u32 des3; /* buffer 2 physical address */
+};
+#endif /* CONFIG_MMC_DW_IDMAC */
+
+/**
+ * struct dw_mci_slot - MMC slot state
+ * @mmc: The mmc_host representing this slot.
+ * @host: The MMC controller this slot is using.
+ * @ctype: Card type for this slot.
+ * @mrq: mmc_request currently being processed or waiting to be
+ * processed, or NULL when the slot is idle.
+ * @queue_node: List node for placing this node in the @queue list of
+ * &struct dw_mci.
+ * @clock: Clock rate configured by set_ios(). Protected by host->lock.
+ * @flags: Random state bits associated with the slot.
+ * @id: Number of this slot.
+ * @last_detect_state: Most recently observed card detect state.
+ */
+struct dw_mci_slot {
+ struct mmc_host *mmc;
+ struct dw_mci *host;
+
+ u32 ctype;
+
+ struct mmc_request *mrq;
+ struct list_head queue_node;
+
+ unsigned int clock;
+ unsigned long flags;
+#define DW_MMC_CARD_PRESENT 0
+#define DW_MMC_CARD_NEED_INIT 1
+ int id;
+ int last_detect_state;
+};
+
+#if defined(CONFIG_DEBUG_FS)
+static int dw_mci_req_show(struct seq_file *s, void *v)
+{
+ struct dw_mci_slot *slot = s->private;
+ struct mmc_request *mrq;
+ struct mmc_command *cmd;
+ struct mmc_command *stop;
+ struct mmc_data *data;
+
+ /* Make sure we get a consistent snapshot */
+ spin_lock_bh(&slot->host->lock);
+ mrq = slot->mrq;
+
+ if (mrq) {
+ cmd = mrq->cmd;
+ data = mrq->data;
+ stop = mrq->stop;
+
+ if (cmd)
+ seq_printf(s,
+ "CMD%u(0x%x) flg %x rsp %x %x %x %x err %d\n",
+ cmd->opcode, cmd->arg, cmd->flags,
+ cmd->resp[0], cmd->resp[1], cmd->resp[2],
+ cmd->resp[2], cmd->error);
+ if (data)
+ seq_printf(s, "DATA %u / %u * %u flg %x err %d\n",
+ data->bytes_xfered, data->blocks,
+ data->blksz, data->flags, data->error);
+ if (stop)
+ seq_printf(s,
+ "CMD%u(0x%x) flg %x rsp %x %x %x %x err %d\n",
+ stop->opcode, stop->arg, stop->flags,
+ stop->resp[0], stop->resp[1], stop->resp[2],
+ stop->resp[2], stop->error);
+ }
+
+ spin_unlock_bh(&slot->host->lock);
+
+ return 0;
+}
+
+static int dw_mci_req_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, dw_mci_req_show, inode->i_private);
+}
+
+static const struct file_operations dw_mci_req_fops = {
+ .owner = THIS_MODULE,
+ .open = dw_mci_req_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int dw_mci_regs_show(struct seq_file *s, void *v)
+{
+ seq_printf(s, "STATUS:\t0x%08x\n", SDMMC_STATUS);
+ seq_printf(s, "RINTSTS:\t0x%08x\n", SDMMC_RINTSTS);
+ seq_printf(s, "CMD:\t0x%08x\n", SDMMC_CMD);
+ seq_printf(s, "CTRL:\t0x%08x\n", SDMMC_CTRL);
+ seq_printf(s, "INTMASK:\t0x%08x\n", SDMMC_INTMASK);
+ seq_printf(s, "CLKENA:\t0x%08x\n", SDMMC_CLKENA);
+
+ return 0;
+}
+
+static int dw_mci_regs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, dw_mci_regs_show, inode->i_private);
+}
+
+static const struct file_operations dw_mci_regs_fops = {
+ .owner = THIS_MODULE,
+ .open = dw_mci_regs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void dw_mci_init_debugfs(struct dw_mci_slot *slot)
+{
+ struct mmc_host *mmc = slot->mmc;
+ struct dw_mci *host = slot->host;
+ struct dentry *root;
+ struct dentry *node;
+
+ root = mmc->debugfs_root;
+ if (!root)
+ return;
+
+ node = debugfs_create_file("regs", S_IRUSR, root, host,
+ &dw_mci_regs_fops);
+ if (!node)
+ goto err;
+
+ node = debugfs_create_file("req", S_IRUSR, root, slot,
+ &dw_mci_req_fops);
+ if (!node)
+ goto err;
+
+ node = debugfs_create_u32("state", S_IRUSR, root, (u32 *)&host->state);
+ if (!node)
+ goto err;
+
+ node = debugfs_create_x32("pending_events", S_IRUSR, root,
+ (u32 *)&host->pending_events);
+ if (!node)
+ goto err;
+
+ node = debugfs_create_x32("completed_events", S_IRUSR, root,
+ (u32 *)&host->completed_events);
+ if (!node)
+ goto err;
+
+ return;
+
+err:
+ dev_err(&mmc->class_dev, "failed to initialize debugfs for slot\n");
+}
+#endif /* defined(CONFIG_DEBUG_FS) */
+
+static void dw_mci_set_timeout(struct dw_mci *host)
+{
+ /* timeout (maximum) */
+ mci_writel(host, TMOUT, 0xffffffff);
+}
+
+static u32 dw_mci_prepare_command(struct mmc_host *mmc, struct mmc_command *cmd)
+{
+ struct mmc_data *data;
+ u32 cmdr;
+ cmd->error = -EINPROGRESS;
+
+ cmdr = cmd->opcode;
+
+ if (cmdr == MMC_STOP_TRANSMISSION)
+ cmdr |= SDMMC_CMD_STOP;
+ else
+ cmdr |= SDMMC_CMD_PRV_DAT_WAIT;
+
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ /* We expect a response, so set this bit */
+ cmdr |= SDMMC_CMD_RESP_EXP;
+ if (cmd->flags & MMC_RSP_136)
+ cmdr |= SDMMC_CMD_RESP_LONG;
+ }
+
+ if (cmd->flags & MMC_RSP_CRC)
+ cmdr |= SDMMC_CMD_RESP_CRC;
+
+ data = cmd->data;
+ if (data) {
+ cmdr |= SDMMC_CMD_DAT_EXP;
+ if (data->flags & MMC_DATA_STREAM)
+ cmdr |= SDMMC_CMD_STRM_MODE;
+ if (data->flags & MMC_DATA_WRITE)
+ cmdr |= SDMMC_CMD_DAT_WR;
+ }
+
+ return cmdr;
+}
+
+static void dw_mci_start_command(struct dw_mci *host,
+ struct mmc_command *cmd, u32 cmd_flags)
+{
+ host->cmd = cmd;
+ dev_vdbg(&host->pdev->dev,
+ "start command: ARGR=0x%08x CMDR=0x%08x\n",
+ cmd->arg, cmd_flags);
+
+ mci_writel(host, CMDARG, cmd->arg);
+ wmb();
+
+ mci_writel(host, CMD, cmd_flags | SDMMC_CMD_START);
+}
+
+static void send_stop_cmd(struct dw_mci *host, struct mmc_data *data)
+{
+ dw_mci_start_command(host, data->stop, host->stop_cmdr);
+}
+
+/* DMA interface functions */
+static void dw_mci_stop_dma(struct dw_mci *host)
+{
+ if (host->use_dma) {
+ host->dma_ops->stop(host);
+ host->dma_ops->cleanup(host);
+ } else {
+ /* Data transfer was stopped by the interrupt handler */
+ set_bit(EVENT_XFER_COMPLETE, &host->pending_events);
+ }
+}
+
+#ifdef CONFIG_MMC_DW_IDMAC
+static void dw_mci_dma_cleanup(struct dw_mci *host)
+{
+ struct mmc_data *data = host->data;
+
+ if (data)
+ dma_unmap_sg(&host->pdev->dev, data->sg, data->sg_len,
+ ((data->flags & MMC_DATA_WRITE)
+ ? DMA_TO_DEVICE : DMA_FROM_DEVICE));
+}
+
+static void dw_mci_idmac_stop_dma(struct dw_mci *host)
+{
+ u32 temp;
+
+ /* Disable and reset the IDMAC interface */
+ temp = mci_readl(host, CTRL);
+ temp &= ~SDMMC_CTRL_USE_IDMAC;
+ temp |= SDMMC_CTRL_DMA_RESET;
+ mci_writel(host, CTRL, temp);
+
+ /* Stop the IDMAC running */
+ temp = mci_readl(host, BMOD);
+ temp &= ~(SDMMC_IDMAC_ENABLE | SDMMC_IDMAC_FB);
+ mci_writel(host, BMOD, temp);
+}
+
+static void dw_mci_idmac_complete_dma(struct dw_mci *host)
+{
+ struct mmc_data *data = host->data;
+
+ dev_vdbg(&host->pdev->dev, "DMA complete\n");
+
+ host->dma_ops->cleanup(host);
+
+ /*
+ * If the card was removed, data will be NULL. No point in trying to
+ * send the stop command or waiting for NBUSY in this case.
+ */
+ if (data) {
+ set_bit(EVENT_XFER_COMPLETE, &host->pending_events);
+ tasklet_schedule(&host->tasklet);
+ }
+}
+
+static void dw_mci_translate_sglist(struct dw_mci *host, struct mmc_data *data,
+ unsigned int sg_len)
+{
+ int i;
+ struct idmac_desc *desc = host->sg_cpu;
+
+ for (i = 0; i < sg_len; i++, desc++) {
+ unsigned int length = sg_dma_len(&data->sg[i]);
+ u32 mem_addr = sg_dma_address(&data->sg[i]);
+
+ /* Set the OWN bit and disable interrupts for this descriptor */
+ desc->des0 = IDMAC_DES0_OWN | IDMAC_DES0_DIC | IDMAC_DES0_CH;
+
+ /* Buffer length */
+ IDMAC_SET_BUFFER1_SIZE(desc, length);
+
+ /* Physical address to DMA to/from */
+ desc->des2 = mem_addr;
+ }
+
+ /* Set first descriptor */
+ desc = host->sg_cpu;
+ desc->des0 |= IDMAC_DES0_FD;
+
+ /* Set last descriptor */
+ desc = host->sg_cpu + (i - 1) * sizeof(struct idmac_desc);
+ desc->des0 &= ~(IDMAC_DES0_CH | IDMAC_DES0_DIC);
+ desc->des0 |= IDMAC_DES0_LD;
+
+ wmb();
+}
+
+static void dw_mci_idmac_start_dma(struct dw_mci *host, unsigned int sg_len)
+{
+ u32 temp;
+
+ dw_mci_translate_sglist(host, host->data, sg_len);
+
+ /* Select IDMAC interface */
+ temp = mci_readl(host, CTRL);
+ temp |= SDMMC_CTRL_USE_IDMAC;
+ mci_writel(host, CTRL, temp);
+
+ wmb();
+
+ /* Enable the IDMAC */
+ temp = mci_readl(host, BMOD);
+ temp |= SDMMC_IDMAC_ENABLE | SDMMC_IDMAC_FB;
+ mci_writel(host, BMOD, temp);
+
+ /* Start it running */
+ mci_writel(host, PLDMND, 1);
+}
+
+static int dw_mci_idmac_init(struct dw_mci *host)
+{
+ struct idmac_desc *p;
+ int i;
+
+ /* Number of descriptors in the ring buffer */
+ host->ring_size = PAGE_SIZE / sizeof(struct idmac_desc);
+
+ /* Forward link the descriptor list */
+ for (i = 0, p = host->sg_cpu; i < host->ring_size - 1; i++, p++)
+ p->des3 = host->sg_dma + (sizeof(struct idmac_desc) * (i + 1));
+
+ /* Set the last descriptor as the end-of-ring descriptor */
+ p->des3 = host->sg_dma;
+ p->des0 = IDMAC_DES0_ER;
+
+ /* Mask out interrupts - get Tx & Rx complete only */
+ mci_writel(host, IDINTEN, SDMMC_IDMAC_INT_NI | SDMMC_IDMAC_INT_RI |
+ SDMMC_IDMAC_INT_TI);
+
+ /* Set the descriptor base address */
+ mci_writel(host, DBADDR, host->sg_dma);
+ return 0;
+}
+
+static struct dw_mci_dma_ops dw_mci_idmac_ops = {
+ .init = dw_mci_idmac_init,
+ .start = dw_mci_idmac_start_dma,
+ .stop = dw_mci_idmac_stop_dma,
+ .complete = dw_mci_idmac_complete_dma,
+ .cleanup = dw_mci_dma_cleanup,
+};
+#endif /* CONFIG_MMC_DW_IDMAC */
+
+static int dw_mci_submit_data_dma(struct dw_mci *host, struct mmc_data *data)
+{
+ struct scatterlist *sg;
+ unsigned int i, direction, sg_len;
+ u32 temp;
+
+ /* If we don't have a channel, we can't do DMA */
+ if (!host->use_dma)
+ return -ENODEV;
+
+ /*
+ * We don't do DMA on "complex" transfers, i.e. with
+ * non-word-aligned buffers or lengths. Also, we don't bother
+ * with all the DMA setup overhead for short transfers.
+ */
+ if (data->blocks * data->blksz < DW_MCI_DMA_THRESHOLD)
+ return -EINVAL;
+ if (data->blksz & 3)
+ return -EINVAL;
+
+ for_each_sg(data->sg, sg, data->sg_len, i) {
+ if (sg->offset & 3 || sg->length & 3)
+ return -EINVAL;
+ }
+
+ if (data->flags & MMC_DATA_READ)
+ direction = DMA_FROM_DEVICE;
+ else
+ direction = DMA_TO_DEVICE;
+
+ sg_len = dma_map_sg(&host->pdev->dev, data->sg, data->sg_len,
+ direction);
+
+ dev_vdbg(&host->pdev->dev,
+ "sd sg_cpu: %#lx sg_dma: %#lx sg_len: %d\n",
+ (unsigned long)host->sg_cpu, (unsigned long)host->sg_dma,
+ sg_len);
+
+ /* Enable the DMA interface */
+ temp = mci_readl(host, CTRL);
+ temp |= SDMMC_CTRL_DMA_ENABLE;
+ mci_writel(host, CTRL, temp);
+
+ /* Disable RX/TX IRQs, let DMA handle it */
+ temp = mci_readl(host, INTMASK);
+ temp &= ~(SDMMC_INT_RXDR | SDMMC_INT_TXDR);
+ mci_writel(host, INTMASK, temp);
+
+ host->dma_ops->start(host, sg_len);
+
+ return 0;
+}
+
+static void dw_mci_submit_data(struct dw_mci *host, struct mmc_data *data)
+{
+ u32 temp;
+
+ data->error = -EINPROGRESS;
+
+ WARN_ON(host->data);
+ host->sg = NULL;
+ host->data = data;
+
+ if (dw_mci_submit_data_dma(host, data)) {
+ host->sg = data->sg;
+ host->pio_offset = 0;
+ if (data->flags & MMC_DATA_READ)
+ host->dir_status = DW_MCI_RECV_STATUS;
+ else
+ host->dir_status = DW_MCI_SEND_STATUS;
+
+ temp = mci_readl(host, INTMASK);
+ temp |= SDMMC_INT_TXDR | SDMMC_INT_RXDR;
+ mci_writel(host, INTMASK, temp);
+
+ temp = mci_readl(host, CTRL);
+ temp &= ~SDMMC_CTRL_DMA_ENABLE;
+ mci_writel(host, CTRL, temp);
+ }
+}
+
+static void mci_send_cmd(struct dw_mci_slot *slot, u32 cmd, u32 arg)
+{
+ struct dw_mci *host = slot->host;
+ unsigned long timeout = jiffies + msecs_to_jiffies(500);
+ unsigned int cmd_status = 0;
+
+ mci_writel(host, CMDARG, arg);
+ wmb();
+ mci_writel(host, CMD, SDMMC_CMD_START | cmd);
+
+ while (time_before(jiffies, timeout)) {
+ cmd_status = mci_readl(host, CMD);
+ if (!(cmd_status & SDMMC_CMD_START))
+ return;
+ }
+ dev_err(&slot->mmc->class_dev,
+ "Timeout sending command (cmd %#x arg %#x status %#x)\n",
+ cmd, arg, cmd_status);
+}
+
+static void dw_mci_setup_bus(struct dw_mci_slot *slot)
+{
+ struct dw_mci *host = slot->host;
+ u32 div;
+
+ if (slot->clock != host->current_speed) {
+ if (host->bus_hz % slot->clock)
+ /*
+ * move the + 1 after the divide to prevent
+ * over-clocking the card.
+ */
+ div = ((host->bus_hz / slot->clock) >> 1) + 1;
+ else
+ div = (host->bus_hz / slot->clock) >> 1;
+
+ dev_info(&slot->mmc->class_dev,
+ "Bus speed (slot %d) = %dHz (slot req %dHz, actual %dHZ"
+ " div = %d)\n", slot->id, host->bus_hz, slot->clock,
+ div ? ((host->bus_hz / div) >> 1) : host->bus_hz, div);
+
+ /* disable clock */
+ mci_writel(host, CLKENA, 0);
+ mci_writel(host, CLKSRC, 0);
+
+ /* inform CIU */
+ mci_send_cmd(slot,
+ SDMMC_CMD_UPD_CLK | SDMMC_CMD_PRV_DAT_WAIT, 0);
+
+ /* set clock to desired speed */
+ mci_writel(host, CLKDIV, div);
+
+ /* inform CIU */
+ mci_send_cmd(slot,
+ SDMMC_CMD_UPD_CLK | SDMMC_CMD_PRV_DAT_WAIT, 0);
+
+ /* enable clock */
+ mci_writel(host, CLKENA, SDMMC_CLKEN_ENABLE |
+ SDMMC_CLKEN_LOW_PWR);
+
+ /* inform CIU */
+ mci_send_cmd(slot,
+ SDMMC_CMD_UPD_CLK | SDMMC_CMD_PRV_DAT_WAIT, 0);
+
+ host->current_speed = slot->clock;
+ }
+
+ /* Set the current slot bus width */
+ mci_writel(host, CTYPE, slot->ctype);
+}
+
+static void dw_mci_start_request(struct dw_mci *host,
+ struct dw_mci_slot *slot)
+{
+ struct mmc_request *mrq;
+ struct mmc_command *cmd;
+ struct mmc_data *data;
+ u32 cmdflags;
+
+ mrq = slot->mrq;
+ if (host->pdata->select_slot)
+ host->pdata->select_slot(slot->id);
+
+ /* Slot specific timing and width adjustment */
+ dw_mci_setup_bus(slot);
+
+ host->cur_slot = slot;
+ host->mrq = mrq;
+
+ host->pending_events = 0;
+ host->completed_events = 0;
+ host->data_status = 0;
+
+ data = mrq->data;
+ if (data) {
+ dw_mci_set_timeout(host);
+ mci_writel(host, BYTCNT, data->blksz*data->blocks);
+ mci_writel(host, BLKSIZ, data->blksz);
+ }
+
+ cmd = mrq->cmd;
+ cmdflags = dw_mci_prepare_command(slot->mmc, cmd);
+
+ /* this is the first command, send the initialization clock */
+ if (test_and_clear_bit(DW_MMC_CARD_NEED_INIT, &slot->flags))
+ cmdflags |= SDMMC_CMD_INIT;
+
+ if (data) {
+ dw_mci_submit_data(host, data);
+ wmb();
+ }
+
+ dw_mci_start_command(host, cmd, cmdflags);
+
+ if (mrq->stop)
+ host->stop_cmdr = dw_mci_prepare_command(slot->mmc, mrq->stop);
+}
+
+static void dw_mci_queue_request(struct dw_mci *host, struct dw_mci_slot *slot,
+ struct mmc_request *mrq)
+{
+ dev_vdbg(&slot->mmc->class_dev, "queue request: state=%d\n",
+ host->state);
+
+ spin_lock_bh(&host->lock);
+ slot->mrq = mrq;
+
+ if (host->state == STATE_IDLE) {
+ host->state = STATE_SENDING_CMD;
+ dw_mci_start_request(host, slot);
+ } else {
+ list_add_tail(&slot->queue_node, &host->queue);
+ }
+
+ spin_unlock_bh(&host->lock);
+}
+
+static void dw_mci_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct dw_mci_slot *slot = mmc_priv(mmc);
+ struct dw_mci *host = slot->host;
+
+ WARN_ON(slot->mrq);
+
+ if (!test_bit(DW_MMC_CARD_PRESENT, &slot->flags)) {
+ mrq->cmd->error = -ENOMEDIUM;
+ mmc_request_done(mmc, mrq);
+ return;
+ }
+
+ /* We don't support multiple blocks of weird lengths. */
+ dw_mci_queue_request(host, slot, mrq);
+}
+
+static void dw_mci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct dw_mci_slot *slot = mmc_priv(mmc);
+ u32 regs;
+
+ /* set default 1 bit mode */
+ slot->ctype = SDMMC_CTYPE_1BIT;
+
+ switch (ios->bus_width) {
+ case MMC_BUS_WIDTH_1:
+ slot->ctype = SDMMC_CTYPE_1BIT;
+ break;
+ case MMC_BUS_WIDTH_4:
+ slot->ctype = SDMMC_CTYPE_4BIT;
+ break;
+ case MMC_BUS_WIDTH_8:
+ slot->ctype = SDMMC_CTYPE_8BIT;
+ break;
+ }
+
+ /* DDR mode set */
+ if (ios->ddr) {
+ regs = mci_readl(slot->host, UHS_REG);
+ regs |= (0x1 << slot->id) << 16;
+ mci_writel(slot->host, UHS_REG, regs);
+ }
+
+ if (ios->clock) {
+ /*
+ * Use mirror of ios->clock to prevent race with mmc
+ * core ios update when finding the minimum.
+ */
+ slot->clock = ios->clock;
+ }
+
+ switch (ios->power_mode) {
+ case MMC_POWER_UP:
+ set_bit(DW_MMC_CARD_NEED_INIT, &slot->flags);
+ break;
+ default:
+ break;
+ }
+}
+
+static int dw_mci_get_ro(struct mmc_host *mmc)
+{
+ int read_only;
+ struct dw_mci_slot *slot = mmc_priv(mmc);
+ struct dw_mci_board *brd = slot->host->pdata;
+
+ /* Use platform get_ro function, else try on board write protect */
+ if (brd->get_ro)
+ read_only = brd->get_ro(slot->id);
+ else
+ read_only =
+ mci_readl(slot->host, WRTPRT) & (1 << slot->id) ? 1 : 0;
+
+ dev_dbg(&mmc->class_dev, "card is %s\n",
+ read_only ? "read-only" : "read-write");
+
+ return read_only;
+}
+
+static int dw_mci_get_cd(struct mmc_host *mmc)
+{
+ int present;
+ struct dw_mci_slot *slot = mmc_priv(mmc);
+ struct dw_mci_board *brd = slot->host->pdata;
+
+ /* Use platform get_cd function, else try onboard card detect */
+ if (brd->quirks & DW_MCI_QUIRK_BROKEN_CARD_DETECTION)
+ present = 1;
+ else if (brd->get_cd)
+ present = !brd->get_cd(slot->id);
+ else
+ present = (mci_readl(slot->host, CDETECT) & (1 << slot->id))
+ == 0 ? 1 : 0;
+
+ if (present)
+ dev_dbg(&mmc->class_dev, "card is present\n");
+ else
+ dev_dbg(&mmc->class_dev, "card is not present\n");
+
+ return present;
+}
+
+static const struct mmc_host_ops dw_mci_ops = {
+ .request = dw_mci_request,
+ .set_ios = dw_mci_set_ios,
+ .get_ro = dw_mci_get_ro,
+ .get_cd = dw_mci_get_cd,
+};
+
+static void dw_mci_request_end(struct dw_mci *host, struct mmc_request *mrq)
+ __releases(&host->lock)
+ __acquires(&host->lock)
+{
+ struct dw_mci_slot *slot;
+ struct mmc_host *prev_mmc = host->cur_slot->mmc;
+
+ WARN_ON(host->cmd || host->data);
+
+ host->cur_slot->mrq = NULL;
+ host->mrq = NULL;
+ if (!list_empty(&host->queue)) {
+ slot = list_entry(host->queue.next,
+ struct dw_mci_slot, queue_node);
+ list_del(&slot->queue_node);
+ dev_vdbg(&host->pdev->dev, "list not empty: %s is next\n",
+ mmc_hostname(slot->mmc));
+ host->state = STATE_SENDING_CMD;
+ dw_mci_start_request(host, slot);
+ } else {
+ dev_vdbg(&host->pdev->dev, "list empty\n");
+ host->state = STATE_IDLE;
+ }
+
+ spin_unlock(&host->lock);
+ mmc_request_done(prev_mmc, mrq);
+ spin_lock(&host->lock);
+}
+
+static void dw_mci_command_complete(struct dw_mci *host, struct mmc_command *cmd)
+{
+ u32 status = host->cmd_status;
+
+ host->cmd_status = 0;
+
+ /* Read the response from the card (up to 16 bytes) */
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ if (cmd->flags & MMC_RSP_136) {
+ cmd->resp[3] = mci_readl(host, RESP0);
+ cmd->resp[2] = mci_readl(host, RESP1);
+ cmd->resp[1] = mci_readl(host, RESP2);
+ cmd->resp[0] = mci_readl(host, RESP3);
+ } else {
+ cmd->resp[0] = mci_readl(host, RESP0);
+ cmd->resp[1] = 0;
+ cmd->resp[2] = 0;
+ cmd->resp[3] = 0;
+ }
+ }
+
+ if (status & SDMMC_INT_RTO)
+ cmd->error = -ETIMEDOUT;
+ else if ((cmd->flags & MMC_RSP_CRC) && (status & SDMMC_INT_RCRC))
+ cmd->error = -EILSEQ;
+ else if (status & SDMMC_INT_RESP_ERR)
+ cmd->error = -EIO;
+ else
+ cmd->error = 0;
+
+ if (cmd->error) {
+ /* newer ip versions need a delay between retries */
+ if (host->quirks & DW_MCI_QUIRK_RETRY_DELAY)
+ mdelay(20);
+
+ if (cmd->data) {
+ host->data = NULL;
+ dw_mci_stop_dma(host);
+ }
+ }
+}
+
+static void dw_mci_tasklet_func(unsigned long priv)
+{
+ struct dw_mci *host = (struct dw_mci *)priv;
+ struct mmc_data *data;
+ struct mmc_command *cmd;
+ enum dw_mci_state state;
+ enum dw_mci_state prev_state;
+ u32 status;
+
+ spin_lock(&host->lock);
+
+ state = host->state;
+ data = host->data;
+
+ do {
+ prev_state = state;
+
+ switch (state) {
+ case STATE_IDLE:
+ break;
+
+ case STATE_SENDING_CMD:
+ if (!test_and_clear_bit(EVENT_CMD_COMPLETE,
+ &host->pending_events))
+ break;
+
+ cmd = host->cmd;
+ host->cmd = NULL;
+ set_bit(EVENT_CMD_COMPLETE, &host->completed_events);
+ dw_mci_command_complete(host, host->mrq->cmd);
+ if (!host->mrq->data || cmd->error) {
+ dw_mci_request_end(host, host->mrq);
+ goto unlock;
+ }
+
+ prev_state = state = STATE_SENDING_DATA;
+ /* fall through */
+
+ case STATE_SENDING_DATA:
+ if (test_and_clear_bit(EVENT_DATA_ERROR,
+ &host->pending_events)) {
+ dw_mci_stop_dma(host);
+ if (data->stop)
+ send_stop_cmd(host, data);
+ state = STATE_DATA_ERROR;
+ break;
+ }
+
+ if (!test_and_clear_bit(EVENT_XFER_COMPLETE,
+ &host->pending_events))
+ break;
+
+ set_bit(EVENT_XFER_COMPLETE, &host->completed_events);
+ prev_state = state = STATE_DATA_BUSY;
+ /* fall through */
+
+ case STATE_DATA_BUSY:
+ if (!test_and_clear_bit(EVENT_DATA_COMPLETE,
+ &host->pending_events))
+ break;
+
+ host->data = NULL;
+ set_bit(EVENT_DATA_COMPLETE, &host->completed_events);
+ status = host->data_status;
+
+ if (status & DW_MCI_DATA_ERROR_FLAGS) {
+ if (status & SDMMC_INT_DTO) {
+ dev_err(&host->pdev->dev,
+ "data timeout error\n");
+ data->error = -ETIMEDOUT;
+ } else if (status & SDMMC_INT_DCRC) {
+ dev_err(&host->pdev->dev,
+ "data CRC error\n");
+ data->error = -EILSEQ;
+ } else {
+ dev_err(&host->pdev->dev,
+ "data FIFO error "
+ "(status=%08x)\n",
+ status);
+ data->error = -EIO;
+ }
+ } else {
+ data->bytes_xfered = data->blocks * data->blksz;
+ data->error = 0;
+ }
+
+ if (!data->stop) {
+ dw_mci_request_end(host, host->mrq);
+ goto unlock;
+ }
+
+ prev_state = state = STATE_SENDING_STOP;
+ if (!data->error)
+ send_stop_cmd(host, data);
+ /* fall through */
+
+ case STATE_SENDING_STOP:
+ if (!test_and_clear_bit(EVENT_CMD_COMPLETE,
+ &host->pending_events))
+ break;
+
+ host->cmd = NULL;
+ dw_mci_command_complete(host, host->mrq->stop);
+ dw_mci_request_end(host, host->mrq);
+ goto unlock;
+
+ case STATE_DATA_ERROR:
+ if (!test_and_clear_bit(EVENT_XFER_COMPLETE,
+ &host->pending_events))
+ break;
+
+ state = STATE_DATA_BUSY;
+ break;
+ }
+ } while (state != prev_state);
+
+ host->state = state;
+unlock:
+ spin_unlock(&host->lock);
+
+}
+
+static void dw_mci_push_data16(struct dw_mci *host, void *buf, int cnt)
+{
+ u16 *pdata = (u16 *)buf;
+
+ WARN_ON(cnt % 2 != 0);
+
+ cnt = cnt >> 1;
+ while (cnt > 0) {
+ mci_writew(host, DATA, *pdata++);
+ cnt--;
+ }
+}
+
+static void dw_mci_pull_data16(struct dw_mci *host, void *buf, int cnt)
+{
+ u16 *pdata = (u16 *)buf;
+
+ WARN_ON(cnt % 2 != 0);
+
+ cnt = cnt >> 1;
+ while (cnt > 0) {
+ *pdata++ = mci_readw(host, DATA);
+ cnt--;
+ }
+}
+
+static void dw_mci_push_data32(struct dw_mci *host, void *buf, int cnt)
+{
+ u32 *pdata = (u32 *)buf;
+
+ WARN_ON(cnt % 4 != 0);
+ WARN_ON((unsigned long)pdata & 0x3);
+
+ cnt = cnt >> 2;
+ while (cnt > 0) {
+ mci_writel(host, DATA, *pdata++);
+ cnt--;
+ }
+}
+
+static void dw_mci_pull_data32(struct dw_mci *host, void *buf, int cnt)
+{
+ u32 *pdata = (u32 *)buf;
+
+ WARN_ON(cnt % 4 != 0);
+ WARN_ON((unsigned long)pdata & 0x3);
+
+ cnt = cnt >> 2;
+ while (cnt > 0) {
+ *pdata++ = mci_readl(host, DATA);
+ cnt--;
+ }
+}
+
+static void dw_mci_push_data64(struct dw_mci *host, void *buf, int cnt)
+{
+ u64 *pdata = (u64 *)buf;
+
+ WARN_ON(cnt % 8 != 0);
+
+ cnt = cnt >> 3;
+ while (cnt > 0) {
+ mci_writeq(host, DATA, *pdata++);
+ cnt--;
+ }
+}
+
+static void dw_mci_pull_data64(struct dw_mci *host, void *buf, int cnt)
+{
+ u64 *pdata = (u64 *)buf;
+
+ WARN_ON(cnt % 8 != 0);
+
+ cnt = cnt >> 3;
+ while (cnt > 0) {
+ *pdata++ = mci_readq(host, DATA);
+ cnt--;
+ }
+}
+
+static void dw_mci_read_data_pio(struct dw_mci *host)
+{
+ struct scatterlist *sg = host->sg;
+ void *buf = sg_virt(sg);
+ unsigned int offset = host->pio_offset;
+ struct mmc_data *data = host->data;
+ int shift = host->data_shift;
+ u32 status;
+ unsigned int nbytes = 0, len;
+
+ do {
+ len = SDMMC_GET_FCNT(mci_readl(host, STATUS)) << shift;
+ if (offset + len <= sg->length) {
+ host->pull_data(host, (void *)(buf + offset), len);
+
+ offset += len;
+ nbytes += len;
+
+ if (offset == sg->length) {
+ flush_dcache_page(sg_page(sg));
+ host->sg = sg = sg_next(sg);
+ if (!sg)
+ goto done;
+
+ offset = 0;
+ buf = sg_virt(sg);
+ }
+ } else {
+ unsigned int remaining = sg->length - offset;
+ host->pull_data(host, (void *)(buf + offset),
+ remaining);
+ nbytes += remaining;
+
+ flush_dcache_page(sg_page(sg));
+ host->sg = sg = sg_next(sg);
+ if (!sg)
+ goto done;
+
+ offset = len - remaining;
+ buf = sg_virt(sg);
+ host->pull_data(host, buf, offset);
+ nbytes += offset;
+ }
+
+ status = mci_readl(host, MINTSTS);
+ mci_writel(host, RINTSTS, SDMMC_INT_RXDR);
+ if (status & DW_MCI_DATA_ERROR_FLAGS) {
+ host->data_status = status;
+ data->bytes_xfered += nbytes;
+ smp_wmb();
+
+ set_bit(EVENT_DATA_ERROR, &host->pending_events);
+
+ tasklet_schedule(&host->tasklet);
+ return;
+ }
+ } while (status & SDMMC_INT_RXDR); /*if the RXDR is ready read again*/
+ len = SDMMC_GET_FCNT(mci_readl(host, STATUS));
+ host->pio_offset = offset;
+ data->bytes_xfered += nbytes;
+ return;
+
+done:
+ data->bytes_xfered += nbytes;
+ smp_wmb();
+ set_bit(EVENT_XFER_COMPLETE, &host->pending_events);
+}
+
+static void dw_mci_write_data_pio(struct dw_mci *host)
+{
+ struct scatterlist *sg = host->sg;
+ void *buf = sg_virt(sg);
+ unsigned int offset = host->pio_offset;
+ struct mmc_data *data = host->data;
+ int shift = host->data_shift;
+ u32 status;
+ unsigned int nbytes = 0, len;
+
+ do {
+ len = SDMMC_FIFO_SZ -
+ (SDMMC_GET_FCNT(mci_readl(host, STATUS)) << shift);
+ if (offset + len <= sg->length) {
+ host->push_data(host, (void *)(buf + offset), len);
+
+ offset += len;
+ nbytes += len;
+ 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;
+
+ host->push_data(host, (void *)(buf + offset),
+ remaining);
+ nbytes += remaining;
+
+ host->sg = sg = sg_next(sg);
+ if (!sg)
+ goto done;
+
+ offset = len - remaining;
+ buf = sg_virt(sg);
+ host->push_data(host, (void *)buf, offset);
+ nbytes += offset;
+ }
+
+ status = mci_readl(host, MINTSTS);
+ mci_writel(host, RINTSTS, SDMMC_INT_TXDR);
+ if (status & DW_MCI_DATA_ERROR_FLAGS) {
+ host->data_status = status;
+ data->bytes_xfered += nbytes;
+
+ smp_wmb();
+
+ set_bit(EVENT_DATA_ERROR, &host->pending_events);
+
+ tasklet_schedule(&host->tasklet);
+ return;
+ }
+ } while (status & SDMMC_INT_TXDR); /* if TXDR write again */
+
+ host->pio_offset = offset;
+ data->bytes_xfered += nbytes;
+
+ return;
+
+done:
+ data->bytes_xfered += nbytes;
+ smp_wmb();
+ set_bit(EVENT_XFER_COMPLETE, &host->pending_events);
+}
+
+static void dw_mci_cmd_interrupt(struct dw_mci *host, u32 status)
+{
+ if (!host->cmd_status)
+ host->cmd_status = status;
+
+ smp_wmb();
+
+ set_bit(EVENT_CMD_COMPLETE, &host->pending_events);
+ tasklet_schedule(&host->tasklet);
+}
+
+static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
+{
+ struct dw_mci *host = dev_id;
+ u32 status, pending;
+ unsigned int pass_count = 0;
+
+ do {
+ status = mci_readl(host, RINTSTS);
+ pending = mci_readl(host, MINTSTS); /* read-only mask reg */
+
+ /*
+ * DTO fix - version 2.10a and below, and only if internal DMA
+ * is configured.
+ */
+ if (host->quirks & DW_MCI_QUIRK_IDMAC_DTO) {
+ if (!pending &&
+ ((mci_readl(host, STATUS) >> 17) & 0x1fff))
+ pending |= SDMMC_INT_DATA_OVER;
+ }
+
+ if (!pending)
+ break;
+
+ if (pending & DW_MCI_CMD_ERROR_FLAGS) {
+ mci_writel(host, RINTSTS, DW_MCI_CMD_ERROR_FLAGS);
+ host->cmd_status = status;
+ smp_wmb();
+ set_bit(EVENT_CMD_COMPLETE, &host->pending_events);
+ tasklet_schedule(&host->tasklet);
+ }
+
+ if (pending & DW_MCI_DATA_ERROR_FLAGS) {
+ /* if there is an error report DATA_ERROR */
+ mci_writel(host, RINTSTS, DW_MCI_DATA_ERROR_FLAGS);
+ host->data_status = status;
+ smp_wmb();
+ set_bit(EVENT_DATA_ERROR, &host->pending_events);
+ tasklet_schedule(&host->tasklet);
+ }
+
+ if (pending & SDMMC_INT_DATA_OVER) {
+ mci_writel(host, RINTSTS, SDMMC_INT_DATA_OVER);
+ if (!host->data_status)
+ host->data_status = status;
+ smp_wmb();
+ if (host->dir_status == DW_MCI_RECV_STATUS) {
+ if (host->sg != NULL)
+ dw_mci_read_data_pio(host);
+ }
+ set_bit(EVENT_DATA_COMPLETE, &host->pending_events);
+ tasklet_schedule(&host->tasklet);
+ }
+
+ if (pending & SDMMC_INT_RXDR) {
+ mci_writel(host, RINTSTS, SDMMC_INT_RXDR);
+ if (host->sg)
+ dw_mci_read_data_pio(host);
+ }
+
+ if (pending & SDMMC_INT_TXDR) {
+ mci_writel(host, RINTSTS, SDMMC_INT_TXDR);
+ if (host->sg)
+ dw_mci_write_data_pio(host);
+ }
+
+ if (pending & SDMMC_INT_CMD_DONE) {
+ mci_writel(host, RINTSTS, SDMMC_INT_CMD_DONE);
+ dw_mci_cmd_interrupt(host, status);
+ }
+
+ if (pending & SDMMC_INT_CD) {
+ mci_writel(host, RINTSTS, SDMMC_INT_CD);
+ tasklet_schedule(&host->card_tasklet);
+ }
+
+ } while (pass_count++ < 5);
+
+#ifdef CONFIG_MMC_DW_IDMAC
+ /* Handle DMA interrupts */
+ pending = mci_readl(host, IDSTS);
+ if (pending & (SDMMC_IDMAC_INT_TI | SDMMC_IDMAC_INT_RI)) {
+ mci_writel(host, IDSTS, SDMMC_IDMAC_INT_TI | SDMMC_IDMAC_INT_RI);
+ mci_writel(host, IDSTS, SDMMC_IDMAC_INT_NI);
+ set_bit(EVENT_DATA_COMPLETE, &host->pending_events);
+ host->dma_ops->complete(host);
+ }
+#endif
+
+ return IRQ_HANDLED;
+}
+
+static void dw_mci_tasklet_card(unsigned long data)
+{
+ struct dw_mci *host = (struct dw_mci *)data;
+ int i;
+
+ for (i = 0; i < host->num_slots; i++) {
+ struct dw_mci_slot *slot = host->slot[i];
+ struct mmc_host *mmc = slot->mmc;
+ struct mmc_request *mrq;
+ int present;
+ u32 ctrl;
+
+ present = dw_mci_get_cd(mmc);
+ while (present != slot->last_detect_state) {
+ spin_lock(&host->lock);
+
+ dev_dbg(&slot->mmc->class_dev, "card %s\n",
+ present ? "inserted" : "removed");
+
+ /* Card change detected */
+ slot->last_detect_state = present;
+
+ /* Power up slot */
+ if (present != 0) {
+ if (host->pdata->setpower)
+ host->pdata->setpower(slot->id,
+ mmc->ocr_avail);
+
+ set_bit(DW_MMC_CARD_PRESENT, &slot->flags);
+ }
+
+ /* Clean up queue if present */
+ mrq = slot->mrq;
+ if (mrq) {
+ if (mrq == host->mrq) {
+ host->data = NULL;
+ host->cmd = NULL;
+
+ switch (host->state) {
+ case STATE_IDLE:
+ break;
+ case STATE_SENDING_CMD:
+ mrq->cmd->error = -ENOMEDIUM;
+ if (!mrq->data)
+ break;
+ /* fall through */
+ case STATE_SENDING_DATA:
+ mrq->data->error = -ENOMEDIUM;
+ dw_mci_stop_dma(host);
+ break;
+ case STATE_DATA_BUSY:
+ case STATE_DATA_ERROR:
+ if (mrq->data->error == -EINPROGRESS)
+ mrq->data->error = -ENOMEDIUM;
+ if (!mrq->stop)
+ break;
+ /* fall through */
+ case STATE_SENDING_STOP:
+ mrq->stop->error = -ENOMEDIUM;
+ break;
+ }
+
+ dw_mci_request_end(host, mrq);
+ } else {
+ list_del(&slot->queue_node);
+ mrq->cmd->error = -ENOMEDIUM;
+ if (mrq->data)
+ mrq->data->error = -ENOMEDIUM;
+ if (mrq->stop)
+ mrq->stop->error = -ENOMEDIUM;
+
+ spin_unlock(&host->lock);
+ mmc_request_done(slot->mmc, mrq);
+ spin_lock(&host->lock);
+ }
+ }
+
+ /* Power down slot */
+ if (present == 0) {
+ if (host->pdata->setpower)
+ host->pdata->setpower(slot->id, 0);
+ clear_bit(DW_MMC_CARD_PRESENT, &slot->flags);
+
+ /*
+ * Clear down the FIFO - doing so generates a
+ * block interrupt, hence setting the
+ * scatter-gather pointer to NULL.
+ */
+ host->sg = NULL;
+
+ ctrl = mci_readl(host, CTRL);
+ ctrl |= SDMMC_CTRL_FIFO_RESET;
+ mci_writel(host, CTRL, ctrl);
+
+#ifdef CONFIG_MMC_DW_IDMAC
+ ctrl = mci_readl(host, BMOD);
+ ctrl |= 0x01; /* Software reset of DMA */
+ mci_writel(host, BMOD, ctrl);
+#endif
+
+ }
+
+ spin_unlock(&host->lock);
+ present = dw_mci_get_cd(mmc);
+ }
+
+ mmc_detect_change(slot->mmc,
+ msecs_to_jiffies(host->pdata->detect_delay_ms));
+ }
+}
+
+static int __init dw_mci_init_slot(struct dw_mci *host, unsigned int id)
+{
+ struct mmc_host *mmc;
+ struct dw_mci_slot *slot;
+
+ mmc = mmc_alloc_host(sizeof(struct dw_mci_slot), &host->pdev->dev);
+ if (!mmc)
+ return -ENOMEM;
+
+ slot = mmc_priv(mmc);
+ slot->id = id;
+ slot->mmc = mmc;
+ slot->host = host;
+
+ mmc->ops = &dw_mci_ops;
+ mmc->f_min = DIV_ROUND_UP(host->bus_hz, 510);
+ mmc->f_max = host->bus_hz;
+
+ if (host->pdata->get_ocr)
+ mmc->ocr_avail = host->pdata->get_ocr(id);
+ else
+ mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+
+ /*
+ * Start with slot power disabled, it will be enabled when a card
+ * is detected.
+ */
+ if (host->pdata->setpower)
+ host->pdata->setpower(id, 0);
+
+ if (host->pdata->caps)
+ mmc->caps = host->pdata->caps;
+ else
+ mmc->caps = 0;
+
+ if (host->pdata->get_bus_wd)
+ if (host->pdata->get_bus_wd(slot->id) >= 4)
+ mmc->caps |= MMC_CAP_4_BIT_DATA;
+
+ if (host->pdata->quirks & DW_MCI_QUIRK_HIGHSPEED)
+ mmc->caps |= MMC_CAP_SD_HIGHSPEED;
+
+#ifdef CONFIG_MMC_DW_IDMAC
+ mmc->max_segs = host->ring_size;
+ mmc->max_blk_size = 65536;
+ mmc->max_blk_count = host->ring_size;
+ mmc->max_seg_size = 0x1000;
+ mmc->max_req_size = mmc->max_seg_size * mmc->max_blk_count;
+#else
+ if (host->pdata->blk_settings) {
+ mmc->max_segs = host->pdata->blk_settings->max_segs;
+ mmc->max_blk_size = host->pdata->blk_settings->max_blk_size;
+ mmc->max_blk_count = host->pdata->blk_settings->max_blk_count;
+ mmc->max_req_size = host->pdata->blk_settings->max_req_size;
+ mmc->max_seg_size = host->pdata->blk_settings->max_seg_size;
+ } else {
+ /* Useful defaults if platform data is unset. */
+ mmc->max_segs = 64;
+ mmc->max_blk_size = 65536; /* BLKSIZ is 16 bits */
+ mmc->max_blk_count = 512;
+ mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
+ mmc->max_seg_size = mmc->max_req_size;
+ }
+#endif /* CONFIG_MMC_DW_IDMAC */
+
+ host->vmmc = regulator_get(mmc_dev(mmc), "vmmc");
+ if (IS_ERR(host->vmmc)) {
+ printk(KERN_INFO "%s: no vmmc regulator found\n", mmc_hostname(mmc));
+ host->vmmc = NULL;
+ } else
+ regulator_enable(host->vmmc);
+
+ if (dw_mci_get_cd(mmc))
+ set_bit(DW_MMC_CARD_PRESENT, &slot->flags);
+ else
+ clear_bit(DW_MMC_CARD_PRESENT, &slot->flags);
+
+ host->slot[id] = slot;
+ mmc_add_host(mmc);
+
+#if defined(CONFIG_DEBUG_FS)
+ dw_mci_init_debugfs(slot);
+#endif
+
+ /* Card initially undetected */
+ slot->last_detect_state = 0;
+
+ /*
+ * Card may have been plugged in prior to boot so we
+ * need to run the detect tasklet
+ */
+ tasklet_schedule(&host->card_tasklet);
+
+ return 0;
+}
+
+static void dw_mci_cleanup_slot(struct dw_mci_slot *slot, unsigned int id)
+{
+ /* Shutdown detect IRQ */
+ if (slot->host->pdata->exit)
+ slot->host->pdata->exit(id);
+
+ /* Debugfs stuff is cleaned up by mmc core */
+ mmc_remove_host(slot->mmc);
+ slot->host->slot[id] = NULL;
+ mmc_free_host(slot->mmc);
+}
+
+static void dw_mci_init_dma(struct dw_mci *host)
+{
+ /* Alloc memory for sg translation */
+ host->sg_cpu = dma_alloc_coherent(&host->pdev->dev, PAGE_SIZE,
+ &host->sg_dma, GFP_KERNEL);
+ if (!host->sg_cpu) {
+ dev_err(&host->pdev->dev, "%s: could not alloc DMA memory\n",
+ __func__);
+ goto no_dma;
+ }
+
+ /* Determine which DMA interface to use */
+#ifdef CONFIG_MMC_DW_IDMAC
+ host->dma_ops = &dw_mci_idmac_ops;
+ dev_info(&host->pdev->dev, "Using internal DMA controller.\n");
+#endif
+
+ if (!host->dma_ops)
+ goto no_dma;
+
+ if (host->dma_ops->init) {
+ if (host->dma_ops->init(host)) {
+ dev_err(&host->pdev->dev, "%s: Unable to initialize "
+ "DMA Controller.\n", __func__);
+ goto no_dma;
+ }
+ } else {
+ dev_err(&host->pdev->dev, "DMA initialization not found.\n");
+ goto no_dma;
+ }
+
+ host->use_dma = 1;
+ return;
+
+no_dma:
+ dev_info(&host->pdev->dev, "Using PIO mode.\n");
+ host->use_dma = 0;
+ return;
+}
+
+static bool mci_wait_reset(struct device *dev, struct dw_mci *host)
+{
+ unsigned long timeout = jiffies + msecs_to_jiffies(500);
+ unsigned int ctrl;
+
+ mci_writel(host, CTRL, (SDMMC_CTRL_RESET | SDMMC_CTRL_FIFO_RESET |
+ SDMMC_CTRL_DMA_RESET));
+
+ /* wait till resets clear */
+ do {
+ ctrl = mci_readl(host, CTRL);
+ if (!(ctrl & (SDMMC_CTRL_RESET | SDMMC_CTRL_FIFO_RESET |
+ SDMMC_CTRL_DMA_RESET)))
+ return true;
+ } while (time_before(jiffies, timeout));
+
+ dev_err(dev, "Timeout resetting block (ctrl %#x)\n", ctrl);
+
+ return false;
+}
+
+static int dw_mci_probe(struct platform_device *pdev)
+{
+ struct dw_mci *host;
+ struct resource *regs;
+ struct dw_mci_board *pdata;
+ int irq, ret, i, width;
+ u32 fifo_size;
+
+ regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!regs)
+ return -ENXIO;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ host = kzalloc(sizeof(struct dw_mci), GFP_KERNEL);
+ if (!host)
+ return -ENOMEM;
+
+ host->pdev = pdev;
+ host->pdata = pdata = pdev->dev.platform_data;
+ if (!pdata || !pdata->init) {
+ dev_err(&pdev->dev,
+ "Platform data must supply init function\n");
+ ret = -ENODEV;
+ goto err_freehost;
+ }
+
+ if (!pdata->select_slot && pdata->num_slots > 1) {
+ dev_err(&pdev->dev,
+ "Platform data must supply select_slot function\n");
+ ret = -ENODEV;
+ goto err_freehost;
+ }
+
+ if (!pdata->bus_hz) {
+ dev_err(&pdev->dev,
+ "Platform data must supply bus speed\n");
+ ret = -ENODEV;
+ goto err_freehost;
+ }
+
+ host->bus_hz = pdata->bus_hz;
+ host->quirks = pdata->quirks;
+
+ spin_lock_init(&host->lock);
+ INIT_LIST_HEAD(&host->queue);
+
+ ret = -ENOMEM;
+ host->regs = ioremap(regs->start, regs->end - regs->start + 1);
+ if (!host->regs)
+ goto err_freehost;
+
+ host->dma_ops = pdata->dma_ops;
+ dw_mci_init_dma(host);
+
+ /*
+ * Get the host data width - this assumes that HCON has been set with
+ * the correct values.
+ */
+ i = (mci_readl(host, HCON) >> 7) & 0x7;
+ if (!i) {
+ host->push_data = dw_mci_push_data16;
+ host->pull_data = dw_mci_pull_data16;
+ width = 16;
+ host->data_shift = 1;
+ } else if (i == 2) {
+ host->push_data = dw_mci_push_data64;
+ host->pull_data = dw_mci_pull_data64;
+ width = 64;
+ host->data_shift = 3;
+ } else {
+ /* Check for a reserved value, and warn if it is */
+ WARN((i != 1),
+ "HCON reports a reserved host data width!\n"
+ "Defaulting to 32-bit access.\n");
+ host->push_data = dw_mci_push_data32;
+ host->pull_data = dw_mci_pull_data32;
+ width = 32;
+ host->data_shift = 2;
+ }
+
+ /* Reset all blocks */
+ if (!mci_wait_reset(&pdev->dev, host)) {
+ ret = -ENODEV;
+ goto err_dmaunmap;
+ }
+
+ /* Clear the interrupts for the host controller */
+ mci_writel(host, RINTSTS, 0xFFFFFFFF);
+ mci_writel(host, INTMASK, 0); /* disable all mmc interrupt first */
+
+ /* Put in max timeout */
+ mci_writel(host, TMOUT, 0xFFFFFFFF);
+
+ /*
+ * FIFO threshold settings RxMark = fifo_size / 2 - 1,
+ * Tx Mark = fifo_size / 2 DMA Size = 8
+ */
+ fifo_size = mci_readl(host, FIFOTH);
+ fifo_size = (fifo_size >> 16) & 0x7ff;
+ host->fifoth_val = ((0x2 << 28) | ((fifo_size/2 - 1) << 16) |
+ ((fifo_size/2) << 0));
+ mci_writel(host, FIFOTH, host->fifoth_val);
+
+ /* disable clock to CIU */
+ mci_writel(host, CLKENA, 0);
+ mci_writel(host, CLKSRC, 0);
+
+ tasklet_init(&host->tasklet, dw_mci_tasklet_func, (unsigned long)host);
+ tasklet_init(&host->card_tasklet,
+ dw_mci_tasklet_card, (unsigned long)host);
+
+ ret = request_irq(irq, dw_mci_interrupt, 0, "dw-mci", host);
+ if (ret)
+ goto err_dmaunmap;
+
+ platform_set_drvdata(pdev, host);
+
+ if (host->pdata->num_slots)
+ host->num_slots = host->pdata->num_slots;
+ else
+ host->num_slots = ((mci_readl(host, HCON) >> 1) & 0x1F) + 1;
+
+ /* We need at least one slot to succeed */
+ for (i = 0; i < host->num_slots; i++) {
+ ret = dw_mci_init_slot(host, i);
+ if (ret) {
+ ret = -ENODEV;
+ goto err_init_slot;
+ }
+ }
+
+ /*
+ * Enable interrupts for command done, data over, data empty, card det,
+ * receive ready and error such as transmit, receive timeout, crc error
+ */
+ mci_writel(host, RINTSTS, 0xFFFFFFFF);
+ mci_writel(host, INTMASK, SDMMC_INT_CMD_DONE | SDMMC_INT_DATA_OVER |
+ SDMMC_INT_TXDR | SDMMC_INT_RXDR |
+ DW_MCI_ERROR_FLAGS | SDMMC_INT_CD);
+ mci_writel(host, CTRL, SDMMC_CTRL_INT_ENABLE); /* Enable mci interrupt */
+
+ dev_info(&pdev->dev, "DW MMC controller at irq %d, "
+ "%d bit host data width\n", irq, width);
+ if (host->quirks & DW_MCI_QUIRK_IDMAC_DTO)
+ dev_info(&pdev->dev, "Internal DMAC interrupt fix enabled.\n");
+
+ return 0;
+
+err_init_slot:
+ /* De-init any initialized slots */
+ while (i > 0) {
+ if (host->slot[i])
+ dw_mci_cleanup_slot(host->slot[i], i);
+ i--;
+ }
+ free_irq(irq, host);
+
+err_dmaunmap:
+ if (host->use_dma && host->dma_ops->exit)
+ host->dma_ops->exit(host);
+ dma_free_coherent(&host->pdev->dev, PAGE_SIZE,
+ host->sg_cpu, host->sg_dma);
+ iounmap(host->regs);
+
+ if (host->vmmc) {
+ regulator_disable(host->vmmc);
+ regulator_put(host->vmmc);
+ }
+
+
+err_freehost:
+ kfree(host);
+ return ret;
+}
+
+static int __exit dw_mci_remove(struct platform_device *pdev)
+{
+ struct dw_mci *host = platform_get_drvdata(pdev);
+ int i;
+
+ mci_writel(host, RINTSTS, 0xFFFFFFFF);
+ mci_writel(host, INTMASK, 0); /* disable all mmc interrupt first */
+
+ platform_set_drvdata(pdev, NULL);
+
+ for (i = 0; i < host->num_slots; i++) {
+ dev_dbg(&pdev->dev, "remove slot %d\n", i);
+ if (host->slot[i])
+ dw_mci_cleanup_slot(host->slot[i], i);
+ }
+
+ /* disable clock to CIU */
+ mci_writel(host, CLKENA, 0);
+ mci_writel(host, CLKSRC, 0);
+
+ free_irq(platform_get_irq(pdev, 0), host);
+ dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
+
+ if (host->use_dma && host->dma_ops->exit)
+ host->dma_ops->exit(host);
+
+ if (host->vmmc) {
+ regulator_disable(host->vmmc);
+ regulator_put(host->vmmc);
+ }
+
+ iounmap(host->regs);
+
+ kfree(host);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+/*
+ * TODO: we should probably disable the clock to the card in the suspend path.
+ */
+static int dw_mci_suspend(struct platform_device *pdev, pm_message_t mesg)
+{
+ int i, ret;
+ struct dw_mci *host = platform_get_drvdata(pdev);
+
+ for (i = 0; i < host->num_slots; i++) {
+ struct dw_mci_slot *slot = host->slot[i];
+ if (!slot)
+ continue;
+ ret = mmc_suspend_host(slot->mmc);
+ if (ret < 0) {
+ while (--i >= 0) {
+ slot = host->slot[i];
+ if (slot)
+ mmc_resume_host(host->slot[i]->mmc);
+ }
+ return ret;
+ }
+ }
+
+ if (host->vmmc)
+ regulator_disable(host->vmmc);
+
+ return 0;
+}
+
+static int dw_mci_resume(struct platform_device *pdev)
+{
+ int i, ret;
+ struct dw_mci *host = platform_get_drvdata(pdev);
+
+ if (host->vmmc)
+ regulator_enable(host->vmmc);
+
+ if (host->dma_ops->init)
+ host->dma_ops->init(host);
+
+ if (!mci_wait_reset(&pdev->dev, host)) {
+ ret = -ENODEV;
+ return ret;
+ }
+
+ /* Restore the old value at FIFOTH register */
+ mci_writel(host, FIFOTH, host->fifoth_val);
+
+ mci_writel(host, RINTSTS, 0xFFFFFFFF);
+ mci_writel(host, INTMASK, SDMMC_INT_CMD_DONE | SDMMC_INT_DATA_OVER |
+ SDMMC_INT_TXDR | SDMMC_INT_RXDR |
+ DW_MCI_ERROR_FLAGS | SDMMC_INT_CD);
+ mci_writel(host, CTRL, SDMMC_CTRL_INT_ENABLE);
+
+ for (i = 0; i < host->num_slots; i++) {
+ struct dw_mci_slot *slot = host->slot[i];
+ if (!slot)
+ continue;
+ ret = mmc_resume_host(host->slot[i]->mmc);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+#else
+#define dw_mci_suspend NULL
+#define dw_mci_resume NULL
+#endif /* CONFIG_PM */
+
+static struct platform_driver dw_mci_driver = {
+ .remove = __exit_p(dw_mci_remove),
+ .suspend = dw_mci_suspend,
+ .resume = dw_mci_resume,
+ .driver = {
+ .name = "dw_mmc",
+ },
+};
+
+static int __init dw_mci_init(void)
+{
+ return platform_driver_probe(&dw_mci_driver, dw_mci_probe);
+}
+
+static void __exit dw_mci_exit(void)
+{
+ platform_driver_unregister(&dw_mci_driver);
+}
+
+module_init(dw_mci_init);
+module_exit(dw_mci_exit);
+
+MODULE_DESCRIPTION("DW Multimedia Card Interface driver");
+MODULE_AUTHOR("NXP Semiconductor VietNam");
+MODULE_AUTHOR("Imagination Technologies Ltd");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mmc/host/dw_mmc.h b/drivers/mmc/host/dw_mmc.h
new file mode 100644
index 00000000..23c662af
--- /dev/null
+++ b/drivers/mmc/host/dw_mmc.h
@@ -0,0 +1,168 @@
+/*
+ * Synopsys DesignWare Multimedia Card Interface driver
+ * (Based on NXP driver for lpc 31xx)
+ *
+ * Copyright (C) 2009 NXP Semiconductors
+ * Copyright (C) 2009, 2010 Imagination Technologies Ltd.
+ *
+ * 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.
+ */
+
+#ifndef _DW_MMC_H_
+#define _DW_MMC_H_
+
+#define SDMMC_CTRL 0x000
+#define SDMMC_PWREN 0x004
+#define SDMMC_CLKDIV 0x008
+#define SDMMC_CLKSRC 0x00c
+#define SDMMC_CLKENA 0x010
+#define SDMMC_TMOUT 0x014
+#define SDMMC_CTYPE 0x018
+#define SDMMC_BLKSIZ 0x01c
+#define SDMMC_BYTCNT 0x020
+#define SDMMC_INTMASK 0x024
+#define SDMMC_CMDARG 0x028
+#define SDMMC_CMD 0x02c
+#define SDMMC_RESP0 0x030
+#define SDMMC_RESP1 0x034
+#define SDMMC_RESP2 0x038
+#define SDMMC_RESP3 0x03c
+#define SDMMC_MINTSTS 0x040
+#define SDMMC_RINTSTS 0x044
+#define SDMMC_STATUS 0x048
+#define SDMMC_FIFOTH 0x04c
+#define SDMMC_CDETECT 0x050
+#define SDMMC_WRTPRT 0x054
+#define SDMMC_GPIO 0x058
+#define SDMMC_TCBCNT 0x05c
+#define SDMMC_TBBCNT 0x060
+#define SDMMC_DEBNCE 0x064
+#define SDMMC_USRID 0x068
+#define SDMMC_VERID 0x06c
+#define SDMMC_HCON 0x070
+#define SDMMC_UHS_REG 0x074
+#define SDMMC_BMOD 0x080
+#define SDMMC_PLDMND 0x084
+#define SDMMC_DBADDR 0x088
+#define SDMMC_IDSTS 0x08c
+#define SDMMC_IDINTEN 0x090
+#define SDMMC_DSCADDR 0x094
+#define SDMMC_BUFADDR 0x098
+#define SDMMC_DATA 0x100
+
+/* shift bit field */
+#define _SBF(f, v) ((v) << (f))
+
+/* Control register defines */
+#define SDMMC_CTRL_USE_IDMAC BIT(25)
+#define SDMMC_CTRL_CEATA_INT_EN BIT(11)
+#define SDMMC_CTRL_SEND_AS_CCSD BIT(10)
+#define SDMMC_CTRL_SEND_CCSD BIT(9)
+#define SDMMC_CTRL_ABRT_READ_DATA BIT(8)
+#define SDMMC_CTRL_SEND_IRQ_RESP BIT(7)
+#define SDMMC_CTRL_READ_WAIT BIT(6)
+#define SDMMC_CTRL_DMA_ENABLE BIT(5)
+#define SDMMC_CTRL_INT_ENABLE BIT(4)
+#define SDMMC_CTRL_DMA_RESET BIT(2)
+#define SDMMC_CTRL_FIFO_RESET BIT(1)
+#define SDMMC_CTRL_RESET BIT(0)
+/* Clock Enable register defines */
+#define SDMMC_CLKEN_LOW_PWR BIT(16)
+#define SDMMC_CLKEN_ENABLE BIT(0)
+/* time-out register defines */
+#define SDMMC_TMOUT_DATA(n) _SBF(8, (n))
+#define SDMMC_TMOUT_DATA_MSK 0xFFFFFF00
+#define SDMMC_TMOUT_RESP(n) ((n) & 0xFF)
+#define SDMMC_TMOUT_RESP_MSK 0xFF
+/* card-type register defines */
+#define SDMMC_CTYPE_8BIT BIT(16)
+#define SDMMC_CTYPE_4BIT BIT(0)
+#define SDMMC_CTYPE_1BIT 0
+/* Interrupt status & mask register defines */
+#define SDMMC_INT_SDIO BIT(16)
+#define SDMMC_INT_EBE BIT(15)
+#define SDMMC_INT_ACD BIT(14)
+#define SDMMC_INT_SBE BIT(13)
+#define SDMMC_INT_HLE BIT(12)
+#define SDMMC_INT_FRUN BIT(11)
+#define SDMMC_INT_HTO BIT(10)
+#define SDMMC_INT_DTO BIT(9)
+#define SDMMC_INT_RTO BIT(8)
+#define SDMMC_INT_DCRC BIT(7)
+#define SDMMC_INT_RCRC BIT(6)
+#define SDMMC_INT_RXDR BIT(5)
+#define SDMMC_INT_TXDR BIT(4)
+#define SDMMC_INT_DATA_OVER BIT(3)
+#define SDMMC_INT_CMD_DONE BIT(2)
+#define SDMMC_INT_RESP_ERR BIT(1)
+#define SDMMC_INT_CD BIT(0)
+#define SDMMC_INT_ERROR 0xbfc2
+/* Command register defines */
+#define SDMMC_CMD_START BIT(31)
+#define SDMMC_CMD_CCS_EXP BIT(23)
+#define SDMMC_CMD_CEATA_RD BIT(22)
+#define SDMMC_CMD_UPD_CLK BIT(21)
+#define SDMMC_CMD_INIT BIT(15)
+#define SDMMC_CMD_STOP BIT(14)
+#define SDMMC_CMD_PRV_DAT_WAIT BIT(13)
+#define SDMMC_CMD_SEND_STOP BIT(12)
+#define SDMMC_CMD_STRM_MODE BIT(11)
+#define SDMMC_CMD_DAT_WR BIT(10)
+#define SDMMC_CMD_DAT_EXP BIT(9)
+#define SDMMC_CMD_RESP_CRC BIT(8)
+#define SDMMC_CMD_RESP_LONG BIT(7)
+#define SDMMC_CMD_RESP_EXP BIT(6)
+#define SDMMC_CMD_INDX(n) ((n) & 0x1F)
+/* Status register defines */
+#define SDMMC_GET_FCNT(x) (((x)>>17) & 0x1FF)
+#define SDMMC_FIFO_SZ 32
+/* Internal DMAC interrupt defines */
+#define SDMMC_IDMAC_INT_AI BIT(9)
+#define SDMMC_IDMAC_INT_NI BIT(8)
+#define SDMMC_IDMAC_INT_CES BIT(5)
+#define SDMMC_IDMAC_INT_DU BIT(4)
+#define SDMMC_IDMAC_INT_FBE BIT(2)
+#define SDMMC_IDMAC_INT_RI BIT(1)
+#define SDMMC_IDMAC_INT_TI BIT(0)
+/* Internal DMAC bus mode bits */
+#define SDMMC_IDMAC_ENABLE BIT(7)
+#define SDMMC_IDMAC_FB BIT(1)
+#define SDMMC_IDMAC_SWRESET BIT(0)
+
+/* Register access macros */
+#define mci_readl(dev, reg) \
+ __raw_readl(dev->regs + SDMMC_##reg)
+#define mci_writel(dev, reg, value) \
+ __raw_writel((value), dev->regs + SDMMC_##reg)
+
+/* 16-bit FIFO access macros */
+#define mci_readw(dev, reg) \
+ __raw_readw(dev->regs + SDMMC_##reg)
+#define mci_writew(dev, reg, value) \
+ __raw_writew((value), dev->regs + SDMMC_##reg)
+
+/* 64-bit FIFO access macros */
+#ifdef readq
+#define mci_readq(dev, reg) \
+ __raw_readq(dev->regs + SDMMC_##reg)
+#define mci_writeq(dev, reg, value) \
+ __raw_writeq((value), dev->regs + SDMMC_##reg)
+#else
+/*
+ * Dummy readq implementation for architectures that don't define it.
+ *
+ * We would assume that none of these architectures would configure
+ * the IP block with a 64bit FIFO width, so this code will never be
+ * executed on those machines. Defining these macros here keeps the
+ * rest of the code free from ifdefs.
+ */
+#define mci_readq(dev, reg) \
+ (*(volatile u64 __force *)(dev->regs + SDMMC_##reg))
+#define mci_writeq(dev, reg, value) \
+ (*(volatile u64 __force *)(dev->regs + SDMMC_##reg) = value)
+#endif
+
+#endif /* _DW_MMC_H_ */
diff --git a/drivers/mmc/host/imxmmc.c b/drivers/mmc/host/imxmmc.c
new file mode 100644
index 00000000..881f7ba5
--- /dev/null
+++ b/drivers/mmc/host/imxmmc.c
@@ -0,0 +1,1169 @@
+/*
+ * linux/drivers/mmc/host/imxmmc.c - Motorola i.MX MMCI driver
+ *
+ * Copyright (C) 2004 Sascha Hauer, Pengutronix <sascha@saschahauer.de>
+ * Copyright (C) 2006 Pavel Pisa, PiKRON <ppisa@pikron.com>
+ *
+ * derived from pxamci.c by Russell King
+ *
+ * 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/init.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/blkdev.h>
+#include <linux/dma-mapping.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+
+#include <asm/dma.h>
+#include <asm/irq.h>
+#include <asm/sizes.h>
+#include <mach/mmc.h>
+#include <mach/imx-dma.h>
+
+#include "imxmmc.h"
+
+#define DRIVER_NAME "imx-mmc"
+
+#define IMXMCI_INT_MASK_DEFAULT (INT_MASK_BUF_READY | INT_MASK_DATA_TRAN | \
+ INT_MASK_WRITE_OP_DONE | INT_MASK_END_CMD_RES | \
+ INT_MASK_AUTO_CARD_DETECT | INT_MASK_DAT0_EN | INT_MASK_SDIO)
+
+struct imxmci_host {
+ struct mmc_host *mmc;
+ spinlock_t lock;
+ struct resource *res;
+ void __iomem *base;
+ int irq;
+ imx_dmach_t dma;
+ volatile unsigned int imask;
+ unsigned int power_mode;
+ unsigned int present;
+ struct imxmmc_platform_data *pdata;
+
+ struct mmc_request *req;
+ struct mmc_command *cmd;
+ struct mmc_data *data;
+
+ struct timer_list timer;
+ struct tasklet_struct tasklet;
+ unsigned int status_reg;
+ unsigned long pending_events;
+ /* Next two fields are there for CPU driven transfers to overcome SDHC deficiencies */
+ u16 *data_ptr;
+ unsigned int data_cnt;
+ atomic_t stuck_timeout;
+
+ unsigned int dma_nents;
+ unsigned int dma_size;
+ unsigned int dma_dir;
+ int dma_allocated;
+
+ unsigned char actual_bus_width;
+
+ int prev_cmd_code;
+
+ struct clk *clk;
+};
+
+#define IMXMCI_PEND_IRQ_b 0
+#define IMXMCI_PEND_DMA_END_b 1
+#define IMXMCI_PEND_DMA_ERR_b 2
+#define IMXMCI_PEND_WAIT_RESP_b 3
+#define IMXMCI_PEND_DMA_DATA_b 4
+#define IMXMCI_PEND_CPU_DATA_b 5
+#define IMXMCI_PEND_CARD_XCHG_b 6
+#define IMXMCI_PEND_SET_INIT_b 7
+#define IMXMCI_PEND_STARTED_b 8
+
+#define IMXMCI_PEND_IRQ_m (1 << IMXMCI_PEND_IRQ_b)
+#define IMXMCI_PEND_DMA_END_m (1 << IMXMCI_PEND_DMA_END_b)
+#define IMXMCI_PEND_DMA_ERR_m (1 << IMXMCI_PEND_DMA_ERR_b)
+#define IMXMCI_PEND_WAIT_RESP_m (1 << IMXMCI_PEND_WAIT_RESP_b)
+#define IMXMCI_PEND_DMA_DATA_m (1 << IMXMCI_PEND_DMA_DATA_b)
+#define IMXMCI_PEND_CPU_DATA_m (1 << IMXMCI_PEND_CPU_DATA_b)
+#define IMXMCI_PEND_CARD_XCHG_m (1 << IMXMCI_PEND_CARD_XCHG_b)
+#define IMXMCI_PEND_SET_INIT_m (1 << IMXMCI_PEND_SET_INIT_b)
+#define IMXMCI_PEND_STARTED_m (1 << IMXMCI_PEND_STARTED_b)
+
+static void imxmci_stop_clock(struct imxmci_host *host)
+{
+ int i = 0;
+ u16 reg;
+
+ reg = readw(host->base + MMC_REG_STR_STP_CLK);
+ writew(reg & ~STR_STP_CLK_START_CLK, host->base + MMC_REG_STR_STP_CLK);
+ while (i < 0x1000) {
+ if (!(i & 0x7f)) {
+ reg = readw(host->base + MMC_REG_STR_STP_CLK);
+ writew(reg | STR_STP_CLK_STOP_CLK,
+ host->base + MMC_REG_STR_STP_CLK);
+ }
+
+ reg = readw(host->base + MMC_REG_STATUS);
+ if (!(reg & STATUS_CARD_BUS_CLK_RUN)) {
+ /* Check twice before cut */
+ reg = readw(host->base + MMC_REG_STATUS);
+ if (!(reg & STATUS_CARD_BUS_CLK_RUN))
+ return;
+ }
+
+ i++;
+ }
+ dev_dbg(mmc_dev(host->mmc), "imxmci_stop_clock blocked, no luck\n");
+}
+
+static int imxmci_start_clock(struct imxmci_host *host)
+{
+ unsigned int trials = 0;
+ unsigned int delay_limit = 128;
+ unsigned long flags;
+ u16 reg;
+
+ reg = readw(host->base + MMC_REG_STR_STP_CLK);
+ writew(reg & ~STR_STP_CLK_STOP_CLK, host->base + MMC_REG_STR_STP_CLK);
+
+ clear_bit(IMXMCI_PEND_STARTED_b, &host->pending_events);
+
+ /*
+ * Command start of the clock, this usually succeeds in less
+ * then 6 delay loops, but during card detection (low clockrate)
+ * it takes up to 5000 delay loops and sometimes fails for the first time
+ */
+ reg = readw(host->base + MMC_REG_STR_STP_CLK);
+ writew(reg | STR_STP_CLK_START_CLK, host->base + MMC_REG_STR_STP_CLK);
+
+ do {
+ unsigned int delay = delay_limit;
+
+ while (delay--) {
+ reg = readw(host->base + MMC_REG_STATUS);
+ if (reg & STATUS_CARD_BUS_CLK_RUN) {
+ /* Check twice before cut */
+ reg = readw(host->base + MMC_REG_STATUS);
+ if (reg & STATUS_CARD_BUS_CLK_RUN)
+ return 0;
+ }
+
+ if (test_bit(IMXMCI_PEND_STARTED_b, &host->pending_events))
+ return 0;
+ }
+
+ local_irq_save(flags);
+ /*
+ * Ensure, that request is not doubled under all possible circumstances.
+ * It is possible, that cock running state is missed, because some other
+ * IRQ or schedule delays this function execution and the clocks has
+ * been already stopped by other means (response processing, SDHC HW)
+ */
+ if (!test_bit(IMXMCI_PEND_STARTED_b, &host->pending_events)) {
+ reg = readw(host->base + MMC_REG_STR_STP_CLK);
+ writew(reg | STR_STP_CLK_START_CLK,
+ host->base + MMC_REG_STR_STP_CLK);
+ }
+ local_irq_restore(flags);
+
+ } while (++trials < 256);
+
+ dev_err(mmc_dev(host->mmc), "imxmci_start_clock blocked, no luck\n");
+
+ return -1;
+}
+
+static void imxmci_softreset(struct imxmci_host *host)
+{
+ int i;
+
+ /* reset sequence */
+ writew(0x08, host->base + MMC_REG_STR_STP_CLK);
+ writew(0x0D, host->base + MMC_REG_STR_STP_CLK);
+
+ for (i = 0; i < 8; i++)
+ writew(0x05, host->base + MMC_REG_STR_STP_CLK);
+
+ writew(0xff, host->base + MMC_REG_RES_TO);
+ writew(512, host->base + MMC_REG_BLK_LEN);
+ writew(1, host->base + MMC_REG_NOB);
+}
+
+static int imxmci_busy_wait_for_status(struct imxmci_host *host,
+ unsigned int *pstat, unsigned int stat_mask,
+ int timeout, const char *where)
+{
+ int loops = 0;
+
+ while (!(*pstat & stat_mask)) {
+ loops += 2;
+ if (loops >= timeout) {
+ dev_dbg(mmc_dev(host->mmc), "busy wait timeout in %s, STATUS = 0x%x (0x%x)\n",
+ where, *pstat, stat_mask);
+ return -1;
+ }
+ udelay(2);
+ *pstat |= readw(host->base + MMC_REG_STATUS);
+ }
+ if (!loops)
+ return 0;
+
+ /* The busy-wait is expected there for clock <8MHz due to SDHC hardware flaws */
+ if (!(stat_mask & STATUS_END_CMD_RESP) || (host->mmc->ios.clock >= 8000000))
+ dev_info(mmc_dev(host->mmc), "busy wait for %d usec in %s, STATUS = 0x%x (0x%x)\n",
+ loops, where, *pstat, stat_mask);
+ return loops;
+}
+
+static void imxmci_setup_data(struct imxmci_host *host, struct mmc_data *data)
+{
+ unsigned int nob = data->blocks;
+ unsigned int blksz = data->blksz;
+ unsigned int datasz = nob * blksz;
+ int i;
+
+ if (data->flags & MMC_DATA_STREAM)
+ nob = 0xffff;
+
+ host->data = data;
+ data->bytes_xfered = 0;
+
+ writew(nob, host->base + MMC_REG_NOB);
+ writew(blksz, host->base + MMC_REG_BLK_LEN);
+
+ /*
+ * DMA cannot be used for small block sizes, we have to use CPU driven transfers otherwise.
+ * We are in big troubles for non-512 byte transfers according to note in the paragraph
+ * 20.6.7 of User Manual anyway, but we need to be able to transfer SCR at least.
+ * The situation is even more complex in reality. The SDHC in not able to handle wll
+ * partial FIFO fills and reads. The length has to be rounded up to burst size multiple.
+ * This is required for SCR read at least.
+ */
+ if (datasz < 512) {
+ host->dma_size = datasz;
+ if (data->flags & MMC_DATA_READ) {
+ host->dma_dir = DMA_FROM_DEVICE;
+
+ /* Hack to enable read SCR */
+ writew(1, host->base + MMC_REG_NOB);
+ writew(512, host->base + MMC_REG_BLK_LEN);
+ } else {
+ host->dma_dir = DMA_TO_DEVICE;
+ }
+
+ /* Convert back to virtual address */
+ host->data_ptr = (u16 *)sg_virt(data->sg);
+ host->data_cnt = 0;
+
+ clear_bit(IMXMCI_PEND_DMA_DATA_b, &host->pending_events);
+ set_bit(IMXMCI_PEND_CPU_DATA_b, &host->pending_events);
+
+ return;
+ }
+
+ if (data->flags & MMC_DATA_READ) {
+ host->dma_dir = DMA_FROM_DEVICE;
+ host->dma_nents = dma_map_sg(mmc_dev(host->mmc), data->sg,
+ data->sg_len, host->dma_dir);
+
+ imx_dma_setup_sg(host->dma, data->sg, data->sg_len, datasz,
+ host->res->start + MMC_REG_BUFFER_ACCESS,
+ DMA_MODE_READ);
+
+ /*imx_dma_setup_mem2dev_ccr(host->dma, DMA_MODE_READ, IMX_DMA_WIDTH_16, CCR_REN);*/
+ CCR(host->dma) = CCR_DMOD_LINEAR | CCR_DSIZ_32 | CCR_SMOD_FIFO | CCR_SSIZ_16 | CCR_REN;
+ } else {
+ host->dma_dir = DMA_TO_DEVICE;
+
+ host->dma_nents = dma_map_sg(mmc_dev(host->mmc), data->sg,
+ data->sg_len, host->dma_dir);
+
+ imx_dma_setup_sg(host->dma, data->sg, data->sg_len, datasz,
+ host->res->start + MMC_REG_BUFFER_ACCESS,
+ DMA_MODE_WRITE);
+
+ /*imx_dma_setup_mem2dev_ccr(host->dma, DMA_MODE_WRITE, IMX_DMA_WIDTH_16, CCR_REN);*/
+ CCR(host->dma) = CCR_SMOD_LINEAR | CCR_SSIZ_32 | CCR_DMOD_FIFO | CCR_DSIZ_16 | CCR_REN;
+ }
+
+#if 1 /* This code is there only for consistency checking and can be disabled in future */
+ host->dma_size = 0;
+ for (i = 0; i < host->dma_nents; i++)
+ host->dma_size += data->sg[i].length;
+
+ if (datasz > host->dma_size) {
+ dev_err(mmc_dev(host->mmc), "imxmci_setup_data datasz 0x%x > 0x%x dm_size\n",
+ datasz, host->dma_size);
+ }
+#endif
+
+ host->dma_size = datasz;
+
+ wmb();
+
+ set_bit(IMXMCI_PEND_DMA_DATA_b, &host->pending_events);
+ clear_bit(IMXMCI_PEND_CPU_DATA_b, &host->pending_events);
+
+ /* start DMA engine for read, write is delayed after initial response */
+ if (host->dma_dir == DMA_FROM_DEVICE)
+ imx_dma_enable(host->dma);
+}
+
+static void imxmci_start_cmd(struct imxmci_host *host, struct mmc_command *cmd, unsigned int cmdat)
+{
+ unsigned long flags;
+ u32 imask;
+
+ WARN_ON(host->cmd != NULL);
+ host->cmd = cmd;
+
+ /* Ensure, that clock are stopped else command programming and start fails */
+ imxmci_stop_clock(host);
+
+ if (cmd->flags & MMC_RSP_BUSY)
+ cmdat |= CMD_DAT_CONT_BUSY;
+
+ switch (mmc_resp_type(cmd)) {
+ case MMC_RSP_R1: /* short CRC, OPCODE */
+ case MMC_RSP_R1B:/* short CRC, OPCODE, BUSY */
+ cmdat |= CMD_DAT_CONT_RESPONSE_FORMAT_R1;
+ break;
+ case MMC_RSP_R2: /* long 136 bit + CRC */
+ cmdat |= CMD_DAT_CONT_RESPONSE_FORMAT_R2;
+ break;
+ case MMC_RSP_R3: /* short */
+ cmdat |= CMD_DAT_CONT_RESPONSE_FORMAT_R3;
+ break;
+ default:
+ break;
+ }
+
+ if (test_and_clear_bit(IMXMCI_PEND_SET_INIT_b, &host->pending_events))
+ cmdat |= CMD_DAT_CONT_INIT; /* This command needs init */
+
+ if (host->actual_bus_width == MMC_BUS_WIDTH_4)
+ cmdat |= CMD_DAT_CONT_BUS_WIDTH_4;
+
+ writew(cmd->opcode, host->base + MMC_REG_CMD);
+ writew(cmd->arg >> 16, host->base + MMC_REG_ARGH);
+ writew(cmd->arg & 0xffff, host->base + MMC_REG_ARGL);
+ writew(cmdat, host->base + MMC_REG_CMD_DAT_CONT);
+
+ atomic_set(&host->stuck_timeout, 0);
+ set_bit(IMXMCI_PEND_WAIT_RESP_b, &host->pending_events);
+
+
+ imask = IMXMCI_INT_MASK_DEFAULT;
+ imask &= ~INT_MASK_END_CMD_RES;
+ if (cmdat & CMD_DAT_CONT_DATA_ENABLE) {
+ /* imask &= ~INT_MASK_BUF_READY; */
+ imask &= ~INT_MASK_DATA_TRAN;
+ if (cmdat & CMD_DAT_CONT_WRITE)
+ imask &= ~INT_MASK_WRITE_OP_DONE;
+ if (test_bit(IMXMCI_PEND_CPU_DATA_b, &host->pending_events))
+ imask &= ~INT_MASK_BUF_READY;
+ }
+
+ spin_lock_irqsave(&host->lock, flags);
+ host->imask = imask;
+ writew(host->imask, host->base + MMC_REG_INT_MASK);
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ dev_dbg(mmc_dev(host->mmc), "CMD%02d (0x%02x) mask set to 0x%04x\n",
+ cmd->opcode, cmd->opcode, imask);
+
+ imxmci_start_clock(host);
+}
+
+static void imxmci_finish_request(struct imxmci_host *host, struct mmc_request *req)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ host->pending_events &= ~(IMXMCI_PEND_WAIT_RESP_m | IMXMCI_PEND_DMA_END_m |
+ IMXMCI_PEND_DMA_DATA_m | IMXMCI_PEND_CPU_DATA_m);
+
+ host->imask = IMXMCI_INT_MASK_DEFAULT;
+ writew(host->imask, host->base + MMC_REG_INT_MASK);
+
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ if (req && req->cmd)
+ host->prev_cmd_code = req->cmd->opcode;
+
+ host->req = NULL;
+ host->cmd = NULL;
+ host->data = NULL;
+ mmc_request_done(host->mmc, req);
+}
+
+static int imxmci_finish_data(struct imxmci_host *host, unsigned int stat)
+{
+ struct mmc_data *data = host->data;
+ int data_error;
+
+ if (test_and_clear_bit(IMXMCI_PEND_DMA_DATA_b, &host->pending_events)) {
+ imx_dma_disable(host->dma);
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->dma_nents,
+ host->dma_dir);
+ }
+
+ if (stat & STATUS_ERR_MASK) {
+ dev_dbg(mmc_dev(host->mmc), "request failed. status: 0x%08x\n", stat);
+ if (stat & (STATUS_CRC_READ_ERR | STATUS_CRC_WRITE_ERR))
+ data->error = -EILSEQ;
+ else if (stat & STATUS_TIME_OUT_READ)
+ data->error = -ETIMEDOUT;
+ else
+ data->error = -EIO;
+ } else {
+ data->bytes_xfered = host->dma_size;
+ }
+
+ data_error = data->error;
+
+ host->data = NULL;
+
+ return data_error;
+}
+
+static int imxmci_cmd_done(struct imxmci_host *host, unsigned int stat)
+{
+ struct mmc_command *cmd = host->cmd;
+ int i;
+ u32 a, b, c;
+ struct mmc_data *data = host->data;
+
+ if (!cmd)
+ return 0;
+
+ host->cmd = NULL;
+
+ if (stat & STATUS_TIME_OUT_RESP) {
+ dev_dbg(mmc_dev(host->mmc), "CMD TIMEOUT\n");
+ cmd->error = -ETIMEDOUT;
+ } else if (stat & STATUS_RESP_CRC_ERR && cmd->flags & MMC_RSP_CRC) {
+ dev_dbg(mmc_dev(host->mmc), "cmd crc error\n");
+ cmd->error = -EILSEQ;
+ }
+
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ if (cmd->flags & MMC_RSP_136) {
+ for (i = 0; i < 4; i++) {
+ a = readw(host->base + MMC_REG_RES_FIFO);
+ b = readw(host->base + MMC_REG_RES_FIFO);
+ cmd->resp[i] = a << 16 | b;
+ }
+ } else {
+ a = readw(host->base + MMC_REG_RES_FIFO);
+ b = readw(host->base + MMC_REG_RES_FIFO);
+ c = readw(host->base + MMC_REG_RES_FIFO);
+ cmd->resp[0] = a << 24 | b << 8 | c >> 8;
+ }
+ }
+
+ dev_dbg(mmc_dev(host->mmc), "RESP 0x%08x, 0x%08x, 0x%08x, 0x%08x, error %d\n",
+ cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3], cmd->error);
+
+ if (data && !cmd->error && !(stat & STATUS_ERR_MASK)) {
+ if (host->req->data->flags & MMC_DATA_WRITE) {
+
+ /* Wait for FIFO to be empty before starting DMA write */
+
+ stat = readw(host->base + MMC_REG_STATUS);
+ if (imxmci_busy_wait_for_status(host, &stat,
+ STATUS_APPL_BUFF_FE,
+ 40, "imxmci_cmd_done DMA WR") < 0) {
+ cmd->error = -EIO;
+ imxmci_finish_data(host, stat);
+ if (host->req)
+ imxmci_finish_request(host, host->req);
+ dev_warn(mmc_dev(host->mmc), "STATUS = 0x%04x\n",
+ stat);
+ return 0;
+ }
+
+ if (test_bit(IMXMCI_PEND_DMA_DATA_b, &host->pending_events))
+ imx_dma_enable(host->dma);
+ }
+ } else {
+ struct mmc_request *req;
+ imxmci_stop_clock(host);
+ req = host->req;
+
+ if (data)
+ imxmci_finish_data(host, stat);
+
+ if (req)
+ imxmci_finish_request(host, req);
+ else
+ dev_warn(mmc_dev(host->mmc), "imxmci_cmd_done: no request to finish\n");
+ }
+
+ return 1;
+}
+
+static int imxmci_data_done(struct imxmci_host *host, unsigned int stat)
+{
+ struct mmc_data *data = host->data;
+ int data_error;
+
+ if (!data)
+ return 0;
+
+ data_error = imxmci_finish_data(host, stat);
+
+ if (host->req->stop) {
+ imxmci_stop_clock(host);
+ imxmci_start_cmd(host, host->req->stop, 0);
+ } else {
+ struct mmc_request *req;
+ req = host->req;
+ if (req)
+ imxmci_finish_request(host, req);
+ else
+ dev_warn(mmc_dev(host->mmc), "imxmci_data_done: no request to finish\n");
+ }
+
+ return 1;
+}
+
+static int imxmci_cpu_driven_data(struct imxmci_host *host, unsigned int *pstat)
+{
+ int i;
+ int burst_len;
+ int trans_done = 0;
+ unsigned int stat = *pstat;
+
+ if (host->actual_bus_width != MMC_BUS_WIDTH_4)
+ burst_len = 16;
+ else
+ burst_len = 64;
+
+ /* This is unfortunately required */
+ dev_dbg(mmc_dev(host->mmc), "imxmci_cpu_driven_data running STATUS = 0x%x\n",
+ stat);
+
+ udelay(20); /* required for clocks < 8MHz*/
+
+ if (host->dma_dir == DMA_FROM_DEVICE) {
+ imxmci_busy_wait_for_status(host, &stat,
+ STATUS_APPL_BUFF_FF | STATUS_DATA_TRANS_DONE |
+ STATUS_TIME_OUT_READ,
+ 50, "imxmci_cpu_driven_data read");
+
+ while ((stat & (STATUS_APPL_BUFF_FF | STATUS_DATA_TRANS_DONE)) &&
+ !(stat & STATUS_TIME_OUT_READ) &&
+ (host->data_cnt < 512)) {
+
+ udelay(20); /* required for clocks < 8MHz*/
+
+ for (i = burst_len; i >= 2 ; i -= 2) {
+ u16 data;
+ data = readw(host->base + MMC_REG_BUFFER_ACCESS);
+ udelay(10); /* required for clocks < 8MHz*/
+ if (host->data_cnt+2 <= host->dma_size) {
+ *(host->data_ptr++) = data;
+ } else {
+ if (host->data_cnt < host->dma_size)
+ *(u8 *)(host->data_ptr) = data;
+ }
+ host->data_cnt += 2;
+ }
+
+ stat = readw(host->base + MMC_REG_STATUS);
+
+ dev_dbg(mmc_dev(host->mmc), "imxmci_cpu_driven_data read %d burst %d STATUS = 0x%x\n",
+ host->data_cnt, burst_len, stat);
+ }
+
+ if ((stat & STATUS_DATA_TRANS_DONE) && (host->data_cnt >= 512))
+ trans_done = 1;
+
+ if (host->dma_size & 0x1ff)
+ stat &= ~STATUS_CRC_READ_ERR;
+
+ if (stat & STATUS_TIME_OUT_READ) {
+ dev_dbg(mmc_dev(host->mmc), "imxmci_cpu_driven_data read timeout STATUS = 0x%x\n",
+ stat);
+ trans_done = -1;
+ }
+
+ } else {
+ imxmci_busy_wait_for_status(host, &stat,
+ STATUS_APPL_BUFF_FE,
+ 20, "imxmci_cpu_driven_data write");
+
+ while ((stat & STATUS_APPL_BUFF_FE) &&
+ (host->data_cnt < host->dma_size)) {
+ if (burst_len >= host->dma_size - host->data_cnt) {
+ burst_len = host->dma_size - host->data_cnt;
+ host->data_cnt = host->dma_size;
+ trans_done = 1;
+ } else {
+ host->data_cnt += burst_len;
+ }
+
+ for (i = burst_len; i > 0 ; i -= 2)
+ writew(*(host->data_ptr++), host->base + MMC_REG_BUFFER_ACCESS);
+
+ stat = readw(host->base + MMC_REG_STATUS);
+
+ dev_dbg(mmc_dev(host->mmc), "imxmci_cpu_driven_data write burst %d STATUS = 0x%x\n",
+ burst_len, stat);
+ }
+ }
+
+ *pstat = stat;
+
+ return trans_done;
+}
+
+static void imxmci_dma_irq(int dma, void *devid)
+{
+ struct imxmci_host *host = devid;
+ u32 stat = readw(host->base + MMC_REG_STATUS);
+
+ atomic_set(&host->stuck_timeout, 0);
+ host->status_reg = stat;
+ set_bit(IMXMCI_PEND_DMA_END_b, &host->pending_events);
+ tasklet_schedule(&host->tasklet);
+}
+
+static irqreturn_t imxmci_irq(int irq, void *devid)
+{
+ struct imxmci_host *host = devid;
+ u32 stat = readw(host->base + MMC_REG_STATUS);
+ int handled = 1;
+
+ writew(host->imask | INT_MASK_SDIO | INT_MASK_AUTO_CARD_DETECT,
+ host->base + MMC_REG_INT_MASK);
+
+ atomic_set(&host->stuck_timeout, 0);
+ host->status_reg = stat;
+ set_bit(IMXMCI_PEND_IRQ_b, &host->pending_events);
+ set_bit(IMXMCI_PEND_STARTED_b, &host->pending_events);
+ tasklet_schedule(&host->tasklet);
+
+ return IRQ_RETVAL(handled);
+}
+
+static void imxmci_tasklet_fnc(unsigned long data)
+{
+ struct imxmci_host *host = (struct imxmci_host *)data;
+ u32 stat;
+ unsigned int data_dir_mask = 0; /* STATUS_WR_CRC_ERROR_CODE_MASK */
+ int timeout = 0;
+
+ if (atomic_read(&host->stuck_timeout) > 4) {
+ char *what;
+ timeout = 1;
+ stat = readw(host->base + MMC_REG_STATUS);
+ host->status_reg = stat;
+ if (test_bit(IMXMCI_PEND_WAIT_RESP_b, &host->pending_events))
+ if (test_bit(IMXMCI_PEND_DMA_DATA_b, &host->pending_events))
+ what = "RESP+DMA";
+ else
+ what = "RESP";
+ else
+ if (test_bit(IMXMCI_PEND_DMA_DATA_b, &host->pending_events))
+ if (test_bit(IMXMCI_PEND_DMA_END_b, &host->pending_events))
+ what = "DATA";
+ else
+ what = "DMA";
+ else
+ what = "???";
+
+ dev_err(mmc_dev(host->mmc),
+ "%s TIMEOUT, hardware stucked STATUS = 0x%04x IMASK = 0x%04x\n",
+ what, stat,
+ readw(host->base + MMC_REG_INT_MASK));
+ dev_err(mmc_dev(host->mmc),
+ "CMD_DAT_CONT = 0x%04x, MMC_BLK_LEN = 0x%04x, MMC_NOB = 0x%04x, DMA_CCR = 0x%08x\n",
+ readw(host->base + MMC_REG_CMD_DAT_CONT),
+ readw(host->base + MMC_REG_BLK_LEN),
+ readw(host->base + MMC_REG_NOB),
+ CCR(host->dma));
+ dev_err(mmc_dev(host->mmc), "CMD%d, prevCMD%d, bus %d-bit, dma_size = 0x%x\n",
+ host->cmd ? host->cmd->opcode : 0,
+ host->prev_cmd_code,
+ 1 << host->actual_bus_width, host->dma_size);
+ }
+
+ if (!host->present || timeout)
+ host->status_reg = STATUS_TIME_OUT_RESP | STATUS_TIME_OUT_READ |
+ STATUS_CRC_READ_ERR | STATUS_CRC_WRITE_ERR;
+
+ if (test_bit(IMXMCI_PEND_IRQ_b, &host->pending_events) || timeout) {
+ clear_bit(IMXMCI_PEND_IRQ_b, &host->pending_events);
+
+ stat = readw(host->base + MMC_REG_STATUS);
+ /*
+ * This is not required in theory, but there is chance to miss some flag
+ * which clears automatically by mask write, FreeScale original code keeps
+ * stat from IRQ time so do I
+ */
+ stat |= host->status_reg;
+
+ if (test_bit(IMXMCI_PEND_CPU_DATA_b, &host->pending_events))
+ stat &= ~STATUS_CRC_READ_ERR;
+
+ if (test_bit(IMXMCI_PEND_WAIT_RESP_b, &host->pending_events)) {
+ imxmci_busy_wait_for_status(host, &stat,
+ STATUS_END_CMD_RESP | STATUS_ERR_MASK,
+ 20, "imxmci_tasklet_fnc resp (ERRATUM #4)");
+ }
+
+ if (stat & (STATUS_END_CMD_RESP | STATUS_ERR_MASK)) {
+ if (test_and_clear_bit(IMXMCI_PEND_WAIT_RESP_b, &host->pending_events))
+ imxmci_cmd_done(host, stat);
+ if (host->data && (stat & STATUS_ERR_MASK))
+ imxmci_data_done(host, stat);
+ }
+
+ if (test_bit(IMXMCI_PEND_CPU_DATA_b, &host->pending_events)) {
+ stat |= readw(host->base + MMC_REG_STATUS);
+ if (imxmci_cpu_driven_data(host, &stat)) {
+ if (test_and_clear_bit(IMXMCI_PEND_WAIT_RESP_b, &host->pending_events))
+ imxmci_cmd_done(host, stat);
+ atomic_clear_mask(IMXMCI_PEND_IRQ_m|IMXMCI_PEND_CPU_DATA_m,
+ &host->pending_events);
+ imxmci_data_done(host, stat);
+ }
+ }
+ }
+
+ if (test_bit(IMXMCI_PEND_DMA_END_b, &host->pending_events) &&
+ !test_bit(IMXMCI_PEND_WAIT_RESP_b, &host->pending_events)) {
+
+ stat = readw(host->base + MMC_REG_STATUS);
+ /* Same as above */
+ stat |= host->status_reg;
+
+ if (host->dma_dir == DMA_TO_DEVICE)
+ data_dir_mask = STATUS_WRITE_OP_DONE;
+ else
+ data_dir_mask = STATUS_DATA_TRANS_DONE;
+
+ if (stat & data_dir_mask) {
+ clear_bit(IMXMCI_PEND_DMA_END_b, &host->pending_events);
+ imxmci_data_done(host, stat);
+ }
+ }
+
+ if (test_and_clear_bit(IMXMCI_PEND_CARD_XCHG_b, &host->pending_events)) {
+
+ if (host->cmd)
+ imxmci_cmd_done(host, STATUS_TIME_OUT_RESP);
+
+ if (host->data)
+ imxmci_data_done(host, STATUS_TIME_OUT_READ |
+ STATUS_CRC_READ_ERR | STATUS_CRC_WRITE_ERR);
+
+ if (host->req)
+ imxmci_finish_request(host, host->req);
+
+ mmc_detect_change(host->mmc, msecs_to_jiffies(100));
+
+ }
+}
+
+static void imxmci_request(struct mmc_host *mmc, struct mmc_request *req)
+{
+ struct imxmci_host *host = mmc_priv(mmc);
+ unsigned int cmdat;
+
+ WARN_ON(host->req != NULL);
+
+ host->req = req;
+
+ cmdat = 0;
+
+ if (req->data) {
+ imxmci_setup_data(host, req->data);
+
+ cmdat |= CMD_DAT_CONT_DATA_ENABLE;
+
+ if (req->data->flags & MMC_DATA_WRITE)
+ cmdat |= CMD_DAT_CONT_WRITE;
+
+ if (req->data->flags & MMC_DATA_STREAM)
+ cmdat |= CMD_DAT_CONT_STREAM_BLOCK;
+ }
+
+ imxmci_start_cmd(host, req->cmd, cmdat);
+}
+
+#define CLK_RATE 19200000
+
+static void imxmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct imxmci_host *host = mmc_priv(mmc);
+ int prescaler;
+
+ if (ios->bus_width == MMC_BUS_WIDTH_4) {
+ host->actual_bus_width = MMC_BUS_WIDTH_4;
+ imx_gpio_mode(PB11_PF_SD_DAT3);
+ BLR(host->dma) = 0; /* burst 64 byte read/write */
+ } else {
+ host->actual_bus_width = MMC_BUS_WIDTH_1;
+ imx_gpio_mode(GPIO_PORTB | GPIO_IN | GPIO_PUEN | 11);
+ BLR(host->dma) = 16; /* burst 16 byte read/write */
+ }
+
+ if (host->power_mode != ios->power_mode) {
+ switch (ios->power_mode) {
+ case MMC_POWER_OFF:
+ break;
+ case MMC_POWER_UP:
+ set_bit(IMXMCI_PEND_SET_INIT_b, &host->pending_events);
+ break;
+ case MMC_POWER_ON:
+ break;
+ }
+ host->power_mode = ios->power_mode;
+ }
+
+ if (ios->clock) {
+ unsigned int clk;
+ u16 reg;
+
+ /* The prescaler is 5 for PERCLK2 equal to 96MHz
+ * then 96MHz / 5 = 19.2 MHz
+ */
+ clk = clk_get_rate(host->clk);
+ prescaler = (clk + (CLK_RATE * 7) / 8) / CLK_RATE;
+ switch (prescaler) {
+ case 0:
+ case 1: prescaler = 0;
+ break;
+ case 2: prescaler = 1;
+ break;
+ case 3: prescaler = 2;
+ break;
+ case 4: prescaler = 4;
+ break;
+ default:
+ case 5: prescaler = 5;
+ break;
+ }
+
+ dev_dbg(mmc_dev(host->mmc), "PERCLK2 %d MHz -> prescaler %d\n",
+ clk, prescaler);
+
+ for (clk = 0; clk < 8; clk++) {
+ int x;
+ x = CLK_RATE / (1 << clk);
+ if (x <= ios->clock)
+ break;
+ }
+
+ /* enable controller */
+ reg = readw(host->base + MMC_REG_STR_STP_CLK);
+ writew(reg | STR_STP_CLK_ENABLE,
+ host->base + MMC_REG_STR_STP_CLK);
+
+ imxmci_stop_clock(host);
+ writew((prescaler << 3) | clk, host->base + MMC_REG_CLK_RATE);
+ /*
+ * Under my understanding, clock should not be started there, because it would
+ * initiate SDHC sequencer and send last or random command into card
+ */
+ /* imxmci_start_clock(host); */
+
+ dev_dbg(mmc_dev(host->mmc),
+ "MMC_CLK_RATE: 0x%08x\n",
+ readw(host->base + MMC_REG_CLK_RATE));
+ } else {
+ imxmci_stop_clock(host);
+ }
+}
+
+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));
+ /*
+ * Board doesn't support read only detection; let the mmc core
+ * decide what to do.
+ */
+ return -ENOSYS;
+}
+
+
+static const struct mmc_host_ops imxmci_ops = {
+ .request = imxmci_request,
+ .set_ios = imxmci_set_ios,
+ .get_ro = imxmci_get_ro,
+};
+
+static void imxmci_check_status(unsigned long data)
+{
+ struct imxmci_host *host = (struct imxmci_host *)data;
+
+ if (host->pdata && host->pdata->card_present &&
+ host->pdata->card_present(mmc_dev(host->mmc)) != host->present) {
+ host->present ^= 1;
+ dev_info(mmc_dev(host->mmc), "card %s\n",
+ host->present ? "inserted" : "removed");
+
+ set_bit(IMXMCI_PEND_CARD_XCHG_b, &host->pending_events);
+ tasklet_schedule(&host->tasklet);
+ }
+
+ if (test_bit(IMXMCI_PEND_WAIT_RESP_b, &host->pending_events) ||
+ test_bit(IMXMCI_PEND_DMA_DATA_b, &host->pending_events)) {
+ atomic_inc(&host->stuck_timeout);
+ if (atomic_read(&host->stuck_timeout) > 4)
+ tasklet_schedule(&host->tasklet);
+ } else {
+ atomic_set(&host->stuck_timeout, 0);
+
+ }
+
+ mod_timer(&host->timer, jiffies + (HZ>>1));
+}
+
+static int __init imxmci_probe(struct platform_device *pdev)
+{
+ struct mmc_host *mmc;
+ struct imxmci_host *host = NULL;
+ struct resource *r;
+ int ret = 0, irq;
+ u16 rev_no;
+
+ printk(KERN_INFO "i.MX mmc driver\n");
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ irq = platform_get_irq(pdev, 0);
+ if (!r || irq < 0)
+ return -ENXIO;
+
+ r = request_mem_region(r->start, resource_size(r), pdev->name);
+ if (!r)
+ return -EBUSY;
+
+ mmc = mmc_alloc_host(sizeof(struct imxmci_host), &pdev->dev);
+ if (!mmc) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ mmc->ops = &imxmci_ops;
+ mmc->f_min = 150000;
+ mmc->f_max = CLK_RATE/2;
+ mmc->ocr_avail = MMC_VDD_32_33;
+ mmc->caps = MMC_CAP_4_BIT_DATA;
+
+ /* MMC core transfer sizes tunable parameters */
+ mmc->max_segs = 64;
+ mmc->max_seg_size = 64*512; /* default PAGE_CACHE_SIZE */
+ mmc->max_req_size = 64*512; /* default PAGE_CACHE_SIZE */
+ mmc->max_blk_size = 2048;
+ mmc->max_blk_count = 65535;
+
+ host = mmc_priv(mmc);
+ host->base = ioremap(r->start, resource_size(r));
+ if (!host->base) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ host->mmc = mmc;
+ host->dma_allocated = 0;
+ host->pdata = pdev->dev.platform_data;
+ if (!host->pdata)
+ dev_warn(&pdev->dev, "No platform data provided!\n");
+
+ spin_lock_init(&host->lock);
+ host->res = r;
+ host->irq = irq;
+
+ host->clk = clk_get(&pdev->dev, "perclk2");
+ if (IS_ERR(host->clk)) {
+ ret = PTR_ERR(host->clk);
+ goto out;
+ }
+ clk_enable(host->clk);
+
+ imx_gpio_mode(PB8_PF_SD_DAT0);
+ imx_gpio_mode(PB9_PF_SD_DAT1);
+ imx_gpio_mode(PB10_PF_SD_DAT2);
+ /* Configured as GPIO with pull-up to ensure right MCC card mode */
+ /* Switched to PB11_PF_SD_DAT3 if 4 bit bus is configured */
+ imx_gpio_mode(GPIO_PORTB | GPIO_IN | GPIO_PUEN | 11);
+ /* imx_gpio_mode(PB11_PF_SD_DAT3); */
+ imx_gpio_mode(PB12_PF_SD_CLK);
+ imx_gpio_mode(PB13_PF_SD_CMD);
+
+ imxmci_softreset(host);
+
+ rev_no = readw(host->base + MMC_REG_REV_NO);
+ if (rev_no != 0x390) {
+ dev_err(mmc_dev(host->mmc), "wrong rev.no. 0x%08x. aborting.\n",
+ readw(host->base + MMC_REG_REV_NO));
+ goto out;
+ }
+
+ /* recommended in data sheet */
+ writew(0x2db4, host->base + MMC_REG_READ_TO);
+
+ host->imask = IMXMCI_INT_MASK_DEFAULT;
+ writew(host->imask, host->base + MMC_REG_INT_MASK);
+
+ host->dma = imx_dma_request_by_prio(DRIVER_NAME, DMA_PRIO_LOW);
+ if(host->dma < 0) {
+ dev_err(mmc_dev(host->mmc), "imx_dma_request_by_prio failed\n");
+ ret = -EBUSY;
+ goto out;
+ }
+ host->dma_allocated = 1;
+ imx_dma_setup_handlers(host->dma, imxmci_dma_irq, NULL, host);
+ RSSR(host->dma) = DMA_REQ_SDHC;
+
+ tasklet_init(&host->tasklet, imxmci_tasklet_fnc, (unsigned long)host);
+ host->status_reg=0;
+ host->pending_events=0;
+
+ ret = request_irq(host->irq, imxmci_irq, 0, DRIVER_NAME, host);
+ if (ret)
+ goto out;
+
+ if (host->pdata && host->pdata->card_present)
+ host->present = host->pdata->card_present(mmc_dev(mmc));
+ else /* if there is no way to detect assume that card is present */
+ host->present = 1;
+
+ init_timer(&host->timer);
+ host->timer.data = (unsigned long)host;
+ host->timer.function = imxmci_check_status;
+ add_timer(&host->timer);
+ mod_timer(&host->timer, jiffies + (HZ >> 1));
+
+ platform_set_drvdata(pdev, mmc);
+
+ mmc_add_host(mmc);
+
+ return 0;
+
+out:
+ if (host) {
+ if (host->dma_allocated) {
+ imx_dma_free(host->dma);
+ host->dma_allocated = 0;
+ }
+ if (host->clk) {
+ clk_disable(host->clk);
+ clk_put(host->clk);
+ }
+ if (host->base)
+ iounmap(host->base);
+ }
+ if (mmc)
+ mmc_free_host(mmc);
+ release_mem_region(r->start, resource_size(r));
+ return ret;
+}
+
+static int __exit imxmci_remove(struct platform_device *pdev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(pdev);
+
+ platform_set_drvdata(pdev, NULL);
+
+ if (mmc) {
+ struct imxmci_host *host = mmc_priv(mmc);
+
+ tasklet_disable(&host->tasklet);
+
+ del_timer_sync(&host->timer);
+ mmc_remove_host(mmc);
+
+ free_irq(host->irq, host);
+ iounmap(host->base);
+ if (host->dma_allocated) {
+ imx_dma_free(host->dma);
+ host->dma_allocated = 0;
+ }
+
+ tasklet_kill(&host->tasklet);
+
+ clk_disable(host->clk);
+ clk_put(host->clk);
+
+ release_mem_region(host->res->start, resource_size(host->res));
+
+ mmc_free_host(mmc);
+ }
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int imxmci_suspend(struct platform_device *dev, pm_message_t state)
+{
+ struct mmc_host *mmc = platform_get_drvdata(dev);
+ int ret = 0;
+
+ if (mmc)
+ ret = mmc_suspend_host(mmc);
+
+ return ret;
+}
+
+static int imxmci_resume(struct platform_device *dev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(dev);
+ struct imxmci_host *host;
+ int ret = 0;
+
+ if (mmc) {
+ host = mmc_priv(mmc);
+ if (host)
+ set_bit(IMXMCI_PEND_SET_INIT_b, &host->pending_events);
+ ret = mmc_resume_host(mmc);
+ }
+
+ return ret;
+}
+#else
+#define imxmci_suspend NULL
+#define imxmci_resume NULL
+#endif /* CONFIG_PM */
+
+static struct platform_driver imxmci_driver = {
+ .remove = __exit_p(imxmci_remove),
+ .suspend = imxmci_suspend,
+ .resume = imxmci_resume,
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ }
+};
+
+static int __init imxmci_init(void)
+{
+ return platform_driver_probe(&imxmci_driver, imxmci_probe);
+}
+
+static void __exit imxmci_exit(void)
+{
+ platform_driver_unregister(&imxmci_driver);
+}
+
+module_init(imxmci_init);
+module_exit(imxmci_exit);
+
+MODULE_DESCRIPTION("i.MX Multimedia Card Interface Driver");
+MODULE_AUTHOR("Sascha Hauer, Pengutronix");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-mmc");
diff --git a/drivers/mmc/host/imxmmc.h b/drivers/mmc/host/imxmmc.h
new file mode 100644
index 00000000..09d5d4ee
--- /dev/null
+++ b/drivers/mmc/host/imxmmc.h
@@ -0,0 +1,64 @@
+#define MMC_REG_STR_STP_CLK 0x00
+#define MMC_REG_STATUS 0x04
+#define MMC_REG_CLK_RATE 0x08
+#define MMC_REG_CMD_DAT_CONT 0x0C
+#define MMC_REG_RES_TO 0x10
+#define MMC_REG_READ_TO 0x14
+#define MMC_REG_BLK_LEN 0x18
+#define MMC_REG_NOB 0x1C
+#define MMC_REG_REV_NO 0x20
+#define MMC_REG_INT_MASK 0x24
+#define MMC_REG_CMD 0x28
+#define MMC_REG_ARGH 0x2C
+#define MMC_REG_ARGL 0x30
+#define MMC_REG_RES_FIFO 0x34
+#define MMC_REG_BUFFER_ACCESS 0x38
+
+#define STR_STP_CLK_IPG_CLK_GATE_DIS (1<<15)
+#define STR_STP_CLK_IPG_PERCLK_GATE_DIS (1<<14)
+#define STR_STP_CLK_ENDIAN (1<<5)
+#define STR_STP_CLK_RESET (1<<3)
+#define STR_STP_CLK_ENABLE (1<<2)
+#define STR_STP_CLK_START_CLK (1<<1)
+#define STR_STP_CLK_STOP_CLK (1<<0)
+#define STATUS_CARD_PRESENCE (1<<15)
+#define STATUS_SDIO_INT_ACTIVE (1<<14)
+#define STATUS_END_CMD_RESP (1<<13)
+#define STATUS_WRITE_OP_DONE (1<<12)
+#define STATUS_DATA_TRANS_DONE (1<<11)
+#define STATUS_WR_CRC_ERROR_CODE_MASK (3<<10)
+#define STATUS_CARD_BUS_CLK_RUN (1<<8)
+#define STATUS_APPL_BUFF_FF (1<<7)
+#define STATUS_APPL_BUFF_FE (1<<6)
+#define STATUS_RESP_CRC_ERR (1<<5)
+#define STATUS_CRC_READ_ERR (1<<3)
+#define STATUS_CRC_WRITE_ERR (1<<2)
+#define STATUS_TIME_OUT_RESP (1<<1)
+#define STATUS_TIME_OUT_READ (1<<0)
+#define STATUS_ERR_MASK 0x2f
+#define CLK_RATE_PRESCALER(x) ((x) & 0x7)
+#define CLK_RATE_CLK_RATE(x) (((x) & 0x7) << 3)
+#define CMD_DAT_CONT_CMD_RESP_LONG_OFF (1<<12)
+#define CMD_DAT_CONT_STOP_READWAIT (1<<11)
+#define CMD_DAT_CONT_START_READWAIT (1<<10)
+#define CMD_DAT_CONT_BUS_WIDTH_1 (0<<8)
+#define CMD_DAT_CONT_BUS_WIDTH_4 (2<<8)
+#define CMD_DAT_CONT_INIT (1<<7)
+#define CMD_DAT_CONT_BUSY (1<<6)
+#define CMD_DAT_CONT_STREAM_BLOCK (1<<5)
+#define CMD_DAT_CONT_WRITE (1<<4)
+#define CMD_DAT_CONT_DATA_ENABLE (1<<3)
+#define CMD_DAT_CONT_RESPONSE_FORMAT_R1 (1)
+#define CMD_DAT_CONT_RESPONSE_FORMAT_R2 (2)
+#define CMD_DAT_CONT_RESPONSE_FORMAT_R3 (3)
+#define CMD_DAT_CONT_RESPONSE_FORMAT_R4 (4)
+#define CMD_DAT_CONT_RESPONSE_FORMAT_R5 (5)
+#define CMD_DAT_CONT_RESPONSE_FORMAT_R6 (6)
+#define INT_MASK_AUTO_CARD_DETECT (1<<6)
+#define INT_MASK_DAT0_EN (1<<5)
+#define INT_MASK_SDIO (1<<4)
+#define INT_MASK_BUF_READY (1<<3)
+#define INT_MASK_END_CMD_RES (1<<2)
+#define INT_MASK_WRITE_OP_DONE (1<<1)
+#define INT_MASK_DATA_TRAN (1<<0)
+#define INT_ALL (0x7f)
diff --git a/drivers/mmc/host/jz4740_mmc.c b/drivers/mmc/host/jz4740_mmc.c
new file mode 100644
index 00000000..74218ad6
--- /dev/null
+++ b/drivers/mmc/host/jz4740_mmc.c
@@ -0,0 +1,1029 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de>
+ * JZ4740 SD/MMC controller driver
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/mmc/host.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/scatterlist.h>
+#include <linux/clk.h>
+
+#include <linux/bitops.h>
+#include <linux/gpio.h>
+#include <asm/mach-jz4740/gpio.h>
+#include <asm/cacheflush.h>
+#include <linux/dma-mapping.h>
+
+#include <asm/mach-jz4740/jz4740_mmc.h>
+
+#define JZ_REG_MMC_STRPCL 0x00
+#define JZ_REG_MMC_STATUS 0x04
+#define JZ_REG_MMC_CLKRT 0x08
+#define JZ_REG_MMC_CMDAT 0x0C
+#define JZ_REG_MMC_RESTO 0x10
+#define JZ_REG_MMC_RDTO 0x14
+#define JZ_REG_MMC_BLKLEN 0x18
+#define JZ_REG_MMC_NOB 0x1C
+#define JZ_REG_MMC_SNOB 0x20
+#define JZ_REG_MMC_IMASK 0x24
+#define JZ_REG_MMC_IREG 0x28
+#define JZ_REG_MMC_CMD 0x2C
+#define JZ_REG_MMC_ARG 0x30
+#define JZ_REG_MMC_RESP_FIFO 0x34
+#define JZ_REG_MMC_RXFIFO 0x38
+#define JZ_REG_MMC_TXFIFO 0x3C
+
+#define JZ_MMC_STRPCL_EXIT_MULTIPLE BIT(7)
+#define JZ_MMC_STRPCL_EXIT_TRANSFER BIT(6)
+#define JZ_MMC_STRPCL_START_READWAIT BIT(5)
+#define JZ_MMC_STRPCL_STOP_READWAIT BIT(4)
+#define JZ_MMC_STRPCL_RESET BIT(3)
+#define JZ_MMC_STRPCL_START_OP BIT(2)
+#define JZ_MMC_STRPCL_CLOCK_CONTROL (BIT(1) | BIT(0))
+#define JZ_MMC_STRPCL_CLOCK_STOP BIT(0)
+#define JZ_MMC_STRPCL_CLOCK_START BIT(1)
+
+
+#define JZ_MMC_STATUS_IS_RESETTING BIT(15)
+#define JZ_MMC_STATUS_SDIO_INT_ACTIVE BIT(14)
+#define JZ_MMC_STATUS_PRG_DONE BIT(13)
+#define JZ_MMC_STATUS_DATA_TRAN_DONE BIT(12)
+#define JZ_MMC_STATUS_END_CMD_RES BIT(11)
+#define JZ_MMC_STATUS_DATA_FIFO_AFULL BIT(10)
+#define JZ_MMC_STATUS_IS_READWAIT BIT(9)
+#define JZ_MMC_STATUS_CLK_EN BIT(8)
+#define JZ_MMC_STATUS_DATA_FIFO_FULL BIT(7)
+#define JZ_MMC_STATUS_DATA_FIFO_EMPTY BIT(6)
+#define JZ_MMC_STATUS_CRC_RES_ERR BIT(5)
+#define JZ_MMC_STATUS_CRC_READ_ERROR BIT(4)
+#define JZ_MMC_STATUS_TIMEOUT_WRITE BIT(3)
+#define JZ_MMC_STATUS_CRC_WRITE_ERROR BIT(2)
+#define JZ_MMC_STATUS_TIMEOUT_RES BIT(1)
+#define JZ_MMC_STATUS_TIMEOUT_READ BIT(0)
+
+#define JZ_MMC_STATUS_READ_ERROR_MASK (BIT(4) | BIT(0))
+#define JZ_MMC_STATUS_WRITE_ERROR_MASK (BIT(3) | BIT(2))
+
+
+#define JZ_MMC_CMDAT_IO_ABORT BIT(11)
+#define JZ_MMC_CMDAT_BUS_WIDTH_4BIT BIT(10)
+#define JZ_MMC_CMDAT_DMA_EN BIT(8)
+#define JZ_MMC_CMDAT_INIT BIT(7)
+#define JZ_MMC_CMDAT_BUSY BIT(6)
+#define JZ_MMC_CMDAT_STREAM BIT(5)
+#define JZ_MMC_CMDAT_WRITE BIT(4)
+#define JZ_MMC_CMDAT_DATA_EN BIT(3)
+#define JZ_MMC_CMDAT_RESPONSE_FORMAT (BIT(2) | BIT(1) | BIT(0))
+#define JZ_MMC_CMDAT_RSP_R1 1
+#define JZ_MMC_CMDAT_RSP_R2 2
+#define JZ_MMC_CMDAT_RSP_R3 3
+
+#define JZ_MMC_IRQ_SDIO BIT(7)
+#define JZ_MMC_IRQ_TXFIFO_WR_REQ BIT(6)
+#define JZ_MMC_IRQ_RXFIFO_RD_REQ BIT(5)
+#define JZ_MMC_IRQ_END_CMD_RES BIT(2)
+#define JZ_MMC_IRQ_PRG_DONE BIT(1)
+#define JZ_MMC_IRQ_DATA_TRAN_DONE BIT(0)
+
+
+#define JZ_MMC_CLK_RATE 24000000
+
+enum jz4740_mmc_state {
+ JZ4740_MMC_STATE_READ_RESPONSE,
+ JZ4740_MMC_STATE_TRANSFER_DATA,
+ JZ4740_MMC_STATE_SEND_STOP,
+ JZ4740_MMC_STATE_DONE,
+};
+
+struct jz4740_mmc_host {
+ struct mmc_host *mmc;
+ struct platform_device *pdev;
+ struct jz4740_mmc_platform_data *pdata;
+ struct clk *clk;
+
+ int irq;
+ int card_detect_irq;
+
+ struct resource *mem;
+ void __iomem *base;
+ struct mmc_request *req;
+ struct mmc_command *cmd;
+
+ unsigned long waiting;
+
+ uint32_t cmdat;
+
+ uint16_t irq_mask;
+
+ spinlock_t lock;
+
+ struct timer_list timeout_timer;
+ struct sg_mapping_iter miter;
+ enum jz4740_mmc_state state;
+};
+
+static void jz4740_mmc_set_irq_enabled(struct jz4740_mmc_host *host,
+ unsigned int irq, bool enabled)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+ if (enabled)
+ host->irq_mask &= ~irq;
+ else
+ host->irq_mask |= irq;
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ writew(host->irq_mask, host->base + JZ_REG_MMC_IMASK);
+}
+
+static void jz4740_mmc_clock_enable(struct jz4740_mmc_host *host,
+ bool start_transfer)
+{
+ uint16_t val = JZ_MMC_STRPCL_CLOCK_START;
+
+ if (start_transfer)
+ val |= JZ_MMC_STRPCL_START_OP;
+
+ writew(val, host->base + JZ_REG_MMC_STRPCL);
+}
+
+static void jz4740_mmc_clock_disable(struct jz4740_mmc_host *host)
+{
+ uint32_t status;
+ unsigned int timeout = 1000;
+
+ writew(JZ_MMC_STRPCL_CLOCK_STOP, host->base + JZ_REG_MMC_STRPCL);
+ do {
+ status = readl(host->base + JZ_REG_MMC_STATUS);
+ } while (status & JZ_MMC_STATUS_CLK_EN && --timeout);
+}
+
+static void jz4740_mmc_reset(struct jz4740_mmc_host *host)
+{
+ uint32_t status;
+ unsigned int timeout = 1000;
+
+ writew(JZ_MMC_STRPCL_RESET, host->base + JZ_REG_MMC_STRPCL);
+ udelay(10);
+ do {
+ status = readl(host->base + JZ_REG_MMC_STATUS);
+ } while (status & JZ_MMC_STATUS_IS_RESETTING && --timeout);
+}
+
+static void jz4740_mmc_request_done(struct jz4740_mmc_host *host)
+{
+ struct mmc_request *req;
+
+ req = host->req;
+ host->req = NULL;
+
+ mmc_request_done(host->mmc, req);
+}
+
+static unsigned int jz4740_mmc_poll_irq(struct jz4740_mmc_host *host,
+ unsigned int irq)
+{
+ unsigned int timeout = 0x800;
+ uint16_t status;
+
+ do {
+ status = readw(host->base + JZ_REG_MMC_IREG);
+ } while (!(status & irq) && --timeout);
+
+ if (timeout == 0) {
+ set_bit(0, &host->waiting);
+ mod_timer(&host->timeout_timer, jiffies + 5*HZ);
+ jz4740_mmc_set_irq_enabled(host, irq, true);
+ return true;
+ }
+
+ return false;
+}
+
+static void jz4740_mmc_transfer_check_state(struct jz4740_mmc_host *host,
+ struct mmc_data *data)
+{
+ int status;
+
+ status = readl(host->base + JZ_REG_MMC_STATUS);
+ if (status & JZ_MMC_STATUS_WRITE_ERROR_MASK) {
+ if (status & (JZ_MMC_STATUS_TIMEOUT_WRITE)) {
+ host->req->cmd->error = -ETIMEDOUT;
+ data->error = -ETIMEDOUT;
+ } else {
+ host->req->cmd->error = -EIO;
+ data->error = -EIO;
+ }
+ }
+}
+
+static bool jz4740_mmc_write_data(struct jz4740_mmc_host *host,
+ struct mmc_data *data)
+{
+ struct sg_mapping_iter *miter = &host->miter;
+ void __iomem *fifo_addr = host->base + JZ_REG_MMC_TXFIFO;
+ uint32_t *buf;
+ bool timeout;
+ size_t i, j;
+
+ while (sg_miter_next(miter)) {
+ buf = miter->addr;
+ i = miter->length / 4;
+ j = i / 8;
+ i = i & 0x7;
+ while (j) {
+ timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ);
+ if (unlikely(timeout))
+ goto poll_timeout;
+
+ writel(buf[0], fifo_addr);
+ writel(buf[1], fifo_addr);
+ writel(buf[2], fifo_addr);
+ writel(buf[3], fifo_addr);
+ writel(buf[4], fifo_addr);
+ writel(buf[5], fifo_addr);
+ writel(buf[6], fifo_addr);
+ writel(buf[7], fifo_addr);
+ buf += 8;
+ --j;
+ }
+ if (unlikely(i)) {
+ timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ);
+ if (unlikely(timeout))
+ goto poll_timeout;
+
+ while (i) {
+ writel(*buf, fifo_addr);
+ ++buf;
+ --i;
+ }
+ }
+ data->bytes_xfered += miter->length;
+ }
+ sg_miter_stop(miter);
+
+ return false;
+
+poll_timeout:
+ miter->consumed = (void *)buf - miter->addr;
+ data->bytes_xfered += miter->consumed;
+ sg_miter_stop(miter);
+
+ return true;
+}
+
+static bool jz4740_mmc_read_data(struct jz4740_mmc_host *host,
+ struct mmc_data *data)
+{
+ struct sg_mapping_iter *miter = &host->miter;
+ void __iomem *fifo_addr = host->base + JZ_REG_MMC_RXFIFO;
+ uint32_t *buf;
+ uint32_t d;
+ uint16_t status;
+ size_t i, j;
+ unsigned int timeout;
+
+ while (sg_miter_next(miter)) {
+ buf = miter->addr;
+ i = miter->length;
+ j = i / 32;
+ i = i & 0x1f;
+ while (j) {
+ timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_RXFIFO_RD_REQ);
+ if (unlikely(timeout))
+ goto poll_timeout;
+
+ buf[0] = readl(fifo_addr);
+ buf[1] = readl(fifo_addr);
+ buf[2] = readl(fifo_addr);
+ buf[3] = readl(fifo_addr);
+ buf[4] = readl(fifo_addr);
+ buf[5] = readl(fifo_addr);
+ buf[6] = readl(fifo_addr);
+ buf[7] = readl(fifo_addr);
+
+ buf += 8;
+ --j;
+ }
+
+ if (unlikely(i)) {
+ timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_RXFIFO_RD_REQ);
+ if (unlikely(timeout))
+ goto poll_timeout;
+
+ while (i >= 4) {
+ *buf++ = readl(fifo_addr);
+ i -= 4;
+ }
+ if (unlikely(i > 0)) {
+ d = readl(fifo_addr);
+ memcpy(buf, &d, i);
+ }
+ }
+ data->bytes_xfered += miter->length;
+
+ /* This can go away once MIPS implements
+ * flush_kernel_dcache_page */
+ flush_dcache_page(miter->page);
+ }
+ sg_miter_stop(miter);
+
+ /* For whatever reason there is sometime one word more in the fifo then
+ * requested */
+ timeout = 1000;
+ status = readl(host->base + JZ_REG_MMC_STATUS);
+ while (!(status & JZ_MMC_STATUS_DATA_FIFO_EMPTY) && --timeout) {
+ d = readl(fifo_addr);
+ status = readl(host->base + JZ_REG_MMC_STATUS);
+ }
+
+ return false;
+
+poll_timeout:
+ miter->consumed = (void *)buf - miter->addr;
+ data->bytes_xfered += miter->consumed;
+ sg_miter_stop(miter);
+
+ return true;
+}
+
+static void jz4740_mmc_timeout(unsigned long data)
+{
+ struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)data;
+
+ if (!test_and_clear_bit(0, &host->waiting))
+ return;
+
+ jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_END_CMD_RES, false);
+
+ host->req->cmd->error = -ETIMEDOUT;
+ jz4740_mmc_request_done(host);
+}
+
+static void jz4740_mmc_read_response(struct jz4740_mmc_host *host,
+ struct mmc_command *cmd)
+{
+ int i;
+ uint16_t tmp;
+ void __iomem *fifo_addr = host->base + JZ_REG_MMC_RESP_FIFO;
+
+ if (cmd->flags & MMC_RSP_136) {
+ tmp = readw(fifo_addr);
+ for (i = 0; i < 4; ++i) {
+ cmd->resp[i] = tmp << 24;
+ tmp = readw(fifo_addr);
+ cmd->resp[i] |= tmp << 8;
+ tmp = readw(fifo_addr);
+ cmd->resp[i] |= tmp >> 8;
+ }
+ } else {
+ cmd->resp[0] = readw(fifo_addr) << 24;
+ cmd->resp[0] |= readw(fifo_addr) << 8;
+ cmd->resp[0] |= readw(fifo_addr) & 0xff;
+ }
+}
+
+static void jz4740_mmc_send_command(struct jz4740_mmc_host *host,
+ struct mmc_command *cmd)
+{
+ uint32_t cmdat = host->cmdat;
+
+ host->cmdat &= ~JZ_MMC_CMDAT_INIT;
+ jz4740_mmc_clock_disable(host);
+
+ host->cmd = cmd;
+
+ if (cmd->flags & MMC_RSP_BUSY)
+ cmdat |= JZ_MMC_CMDAT_BUSY;
+
+ switch (mmc_resp_type(cmd)) {
+ case MMC_RSP_R1B:
+ case MMC_RSP_R1:
+ cmdat |= JZ_MMC_CMDAT_RSP_R1;
+ break;
+ case MMC_RSP_R2:
+ cmdat |= JZ_MMC_CMDAT_RSP_R2;
+ break;
+ case MMC_RSP_R3:
+ cmdat |= JZ_MMC_CMDAT_RSP_R3;
+ break;
+ default:
+ break;
+ }
+
+ if (cmd->data) {
+ cmdat |= JZ_MMC_CMDAT_DATA_EN;
+ if (cmd->data->flags & MMC_DATA_WRITE)
+ cmdat |= JZ_MMC_CMDAT_WRITE;
+ if (cmd->data->flags & MMC_DATA_STREAM)
+ cmdat |= JZ_MMC_CMDAT_STREAM;
+
+ writew(cmd->data->blksz, host->base + JZ_REG_MMC_BLKLEN);
+ writew(cmd->data->blocks, host->base + JZ_REG_MMC_NOB);
+ }
+
+ writeb(cmd->opcode, host->base + JZ_REG_MMC_CMD);
+ writel(cmd->arg, host->base + JZ_REG_MMC_ARG);
+ writel(cmdat, host->base + JZ_REG_MMC_CMDAT);
+
+ jz4740_mmc_clock_enable(host, 1);
+}
+
+static void jz_mmc_prepare_data_transfer(struct jz4740_mmc_host *host)
+{
+ struct mmc_command *cmd = host->req->cmd;
+ struct mmc_data *data = cmd->data;
+ int direction;
+
+ if (data->flags & MMC_DATA_READ)
+ direction = SG_MITER_TO_SG;
+ else
+ direction = SG_MITER_FROM_SG;
+
+ sg_miter_start(&host->miter, data->sg, data->sg_len, direction);
+}
+
+
+static irqreturn_t jz_mmc_irq_worker(int irq, void *devid)
+{
+ struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)devid;
+ struct mmc_command *cmd = host->req->cmd;
+ struct mmc_request *req = host->req;
+ bool timeout = false;
+
+ if (cmd->error)
+ host->state = JZ4740_MMC_STATE_DONE;
+
+ switch (host->state) {
+ case JZ4740_MMC_STATE_READ_RESPONSE:
+ if (cmd->flags & MMC_RSP_PRESENT)
+ jz4740_mmc_read_response(host, cmd);
+
+ if (!cmd->data)
+ break;
+
+ jz_mmc_prepare_data_transfer(host);
+
+ case JZ4740_MMC_STATE_TRANSFER_DATA:
+ if (cmd->data->flags & MMC_DATA_READ)
+ timeout = jz4740_mmc_read_data(host, cmd->data);
+ else
+ timeout = jz4740_mmc_write_data(host, cmd->data);
+
+ if (unlikely(timeout)) {
+ host->state = JZ4740_MMC_STATE_TRANSFER_DATA;
+ break;
+ }
+
+ jz4740_mmc_transfer_check_state(host, cmd->data);
+
+ timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_DATA_TRAN_DONE);
+ if (unlikely(timeout)) {
+ host->state = JZ4740_MMC_STATE_SEND_STOP;
+ break;
+ }
+ writew(JZ_MMC_IRQ_DATA_TRAN_DONE, host->base + JZ_REG_MMC_IREG);
+
+ case JZ4740_MMC_STATE_SEND_STOP:
+ if (!req->stop)
+ break;
+
+ jz4740_mmc_send_command(host, req->stop);
+
+ timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_PRG_DONE);
+ if (timeout) {
+ host->state = JZ4740_MMC_STATE_DONE;
+ break;
+ }
+ case JZ4740_MMC_STATE_DONE:
+ break;
+ }
+
+ if (!timeout)
+ jz4740_mmc_request_done(host);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t jz_mmc_irq(int irq, void *devid)
+{
+ struct jz4740_mmc_host *host = devid;
+ struct mmc_command *cmd = host->cmd;
+ uint16_t irq_reg, status, tmp;
+
+ irq_reg = readw(host->base + JZ_REG_MMC_IREG);
+
+ tmp = irq_reg;
+ irq_reg &= ~host->irq_mask;
+
+ tmp &= ~(JZ_MMC_IRQ_TXFIFO_WR_REQ | JZ_MMC_IRQ_RXFIFO_RD_REQ |
+ JZ_MMC_IRQ_PRG_DONE | JZ_MMC_IRQ_DATA_TRAN_DONE);
+
+ if (tmp != irq_reg)
+ writew(tmp & ~irq_reg, host->base + JZ_REG_MMC_IREG);
+
+ if (irq_reg & JZ_MMC_IRQ_SDIO) {
+ writew(JZ_MMC_IRQ_SDIO, host->base + JZ_REG_MMC_IREG);
+ mmc_signal_sdio_irq(host->mmc);
+ irq_reg &= ~JZ_MMC_IRQ_SDIO;
+ }
+
+ if (host->req && cmd && irq_reg) {
+ if (test_and_clear_bit(0, &host->waiting)) {
+ del_timer(&host->timeout_timer);
+
+ status = readl(host->base + JZ_REG_MMC_STATUS);
+
+ if (status & JZ_MMC_STATUS_TIMEOUT_RES) {
+ cmd->error = -ETIMEDOUT;
+ } else if (status & JZ_MMC_STATUS_CRC_RES_ERR) {
+ cmd->error = -EIO;
+ } else if (status & (JZ_MMC_STATUS_CRC_READ_ERROR |
+ JZ_MMC_STATUS_CRC_WRITE_ERROR)) {
+ if (cmd->data)
+ cmd->data->error = -EIO;
+ cmd->error = -EIO;
+ } else if (status & (JZ_MMC_STATUS_CRC_READ_ERROR |
+ JZ_MMC_STATUS_CRC_WRITE_ERROR)) {
+ if (cmd->data)
+ cmd->data->error = -EIO;
+ cmd->error = -EIO;
+ }
+
+ jz4740_mmc_set_irq_enabled(host, irq_reg, false);
+ writew(irq_reg, host->base + JZ_REG_MMC_IREG);
+
+ return IRQ_WAKE_THREAD;
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int jz4740_mmc_set_clock_rate(struct jz4740_mmc_host *host, int rate)
+{
+ int div = 0;
+ int real_rate;
+
+ jz4740_mmc_clock_disable(host);
+ clk_set_rate(host->clk, JZ_MMC_CLK_RATE);
+
+ real_rate = clk_get_rate(host->clk);
+
+ while (real_rate > rate && div < 7) {
+ ++div;
+ real_rate >>= 1;
+ }
+
+ writew(div, host->base + JZ_REG_MMC_CLKRT);
+ return real_rate;
+}
+
+static void jz4740_mmc_request(struct mmc_host *mmc, struct mmc_request *req)
+{
+ struct jz4740_mmc_host *host = mmc_priv(mmc);
+
+ host->req = req;
+
+ writew(0xffff, host->base + JZ_REG_MMC_IREG);
+
+ writew(JZ_MMC_IRQ_END_CMD_RES, host->base + JZ_REG_MMC_IREG);
+ jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_END_CMD_RES, true);
+
+ host->state = JZ4740_MMC_STATE_READ_RESPONSE;
+ set_bit(0, &host->waiting);
+ mod_timer(&host->timeout_timer, jiffies + 5*HZ);
+ jz4740_mmc_send_command(host, req->cmd);
+}
+
+static void jz4740_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct jz4740_mmc_host *host = mmc_priv(mmc);
+ if (ios->clock)
+ jz4740_mmc_set_clock_rate(host, ios->clock);
+
+ switch (ios->power_mode) {
+ case MMC_POWER_UP:
+ jz4740_mmc_reset(host);
+ if (gpio_is_valid(host->pdata->gpio_power))
+ gpio_set_value(host->pdata->gpio_power,
+ !host->pdata->power_active_low);
+ host->cmdat |= JZ_MMC_CMDAT_INIT;
+ clk_enable(host->clk);
+ break;
+ case MMC_POWER_ON:
+ break;
+ default:
+ if (gpio_is_valid(host->pdata->gpio_power))
+ gpio_set_value(host->pdata->gpio_power,
+ host->pdata->power_active_low);
+ clk_disable(host->clk);
+ break;
+ }
+
+ switch (ios->bus_width) {
+ case MMC_BUS_WIDTH_1:
+ host->cmdat &= ~JZ_MMC_CMDAT_BUS_WIDTH_4BIT;
+ break;
+ case MMC_BUS_WIDTH_4:
+ host->cmdat |= JZ_MMC_CMDAT_BUS_WIDTH_4BIT;
+ break;
+ default:
+ break;
+ }
+}
+
+static int jz4740_mmc_get_ro(struct mmc_host *mmc)
+{
+ struct jz4740_mmc_host *host = mmc_priv(mmc);
+ if (!gpio_is_valid(host->pdata->gpio_read_only))
+ return -ENOSYS;
+
+ return gpio_get_value(host->pdata->gpio_read_only) ^
+ host->pdata->read_only_active_low;
+}
+
+static int jz4740_mmc_get_cd(struct mmc_host *mmc)
+{
+ struct jz4740_mmc_host *host = mmc_priv(mmc);
+ if (!gpio_is_valid(host->pdata->gpio_card_detect))
+ return -ENOSYS;
+
+ return gpio_get_value(host->pdata->gpio_card_detect) ^
+ host->pdata->card_detect_active_low;
+}
+
+static irqreturn_t jz4740_mmc_card_detect_irq(int irq, void *devid)
+{
+ struct jz4740_mmc_host *host = devid;
+
+ mmc_detect_change(host->mmc, HZ / 2);
+
+ return IRQ_HANDLED;
+}
+
+static void jz4740_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+ struct jz4740_mmc_host *host = mmc_priv(mmc);
+ jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_SDIO, enable);
+}
+
+static const struct mmc_host_ops jz4740_mmc_ops = {
+ .request = jz4740_mmc_request,
+ .set_ios = jz4740_mmc_set_ios,
+ .get_ro = jz4740_mmc_get_ro,
+ .get_cd = jz4740_mmc_get_cd,
+ .enable_sdio_irq = jz4740_mmc_enable_sdio_irq,
+};
+
+static const struct jz_gpio_bulk_request jz4740_mmc_pins[] = {
+ JZ_GPIO_BULK_PIN(MSC_CMD),
+ JZ_GPIO_BULK_PIN(MSC_CLK),
+ JZ_GPIO_BULK_PIN(MSC_DATA0),
+ JZ_GPIO_BULK_PIN(MSC_DATA1),
+ JZ_GPIO_BULK_PIN(MSC_DATA2),
+ JZ_GPIO_BULK_PIN(MSC_DATA3),
+};
+
+static int __devinit jz4740_mmc_request_gpio(struct device *dev, int gpio,
+ const char *name, bool output, int value)
+{
+ int ret;
+
+ if (!gpio_is_valid(gpio))
+ return 0;
+
+ ret = gpio_request(gpio, name);
+ if (ret) {
+ dev_err(dev, "Failed to request %s gpio: %d\n", name, ret);
+ return ret;
+ }
+
+ if (output)
+ gpio_direction_output(gpio, value);
+ else
+ gpio_direction_input(gpio);
+
+ return 0;
+}
+
+static int __devinit jz4740_mmc_request_gpios(struct platform_device *pdev)
+{
+ int ret;
+ struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data;
+
+ if (!pdata)
+ return 0;
+
+ ret = jz4740_mmc_request_gpio(&pdev->dev, pdata->gpio_card_detect,
+ "MMC detect change", false, 0);
+ if (ret)
+ goto err;
+
+ ret = jz4740_mmc_request_gpio(&pdev->dev, pdata->gpio_read_only,
+ "MMC read only", false, 0);
+ if (ret)
+ goto err_free_gpio_card_detect;
+
+ ret = jz4740_mmc_request_gpio(&pdev->dev, pdata->gpio_power,
+ "MMC read only", true, pdata->power_active_low);
+ if (ret)
+ goto err_free_gpio_read_only;
+
+ return 0;
+
+err_free_gpio_read_only:
+ if (gpio_is_valid(pdata->gpio_read_only))
+ gpio_free(pdata->gpio_read_only);
+err_free_gpio_card_detect:
+ if (gpio_is_valid(pdata->gpio_card_detect))
+ gpio_free(pdata->gpio_card_detect);
+err:
+ return ret;
+}
+
+static int __devinit jz4740_mmc_request_cd_irq(struct platform_device *pdev,
+ struct jz4740_mmc_host *host)
+{
+ struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data;
+
+ if (!gpio_is_valid(pdata->gpio_card_detect))
+ return 0;
+
+ host->card_detect_irq = gpio_to_irq(pdata->gpio_card_detect);
+ if (host->card_detect_irq < 0) {
+ dev_warn(&pdev->dev, "Failed to get card detect irq\n");
+ return 0;
+ }
+
+ return request_irq(host->card_detect_irq, jz4740_mmc_card_detect_irq,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "MMC card detect", host);
+}
+
+static void jz4740_mmc_free_gpios(struct platform_device *pdev)
+{
+ struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data;
+
+ if (!pdata)
+ return;
+
+ if (gpio_is_valid(pdata->gpio_power))
+ gpio_free(pdata->gpio_power);
+ if (gpio_is_valid(pdata->gpio_read_only))
+ gpio_free(pdata->gpio_read_only);
+ if (gpio_is_valid(pdata->gpio_card_detect))
+ gpio_free(pdata->gpio_card_detect);
+}
+
+static inline size_t jz4740_mmc_num_pins(struct jz4740_mmc_host *host)
+{
+ size_t num_pins = ARRAY_SIZE(jz4740_mmc_pins);
+ if (host->pdata && host->pdata->data_1bit)
+ num_pins -= 3;
+
+ return num_pins;
+}
+
+static int __devinit jz4740_mmc_probe(struct platform_device* pdev)
+{
+ int ret;
+ struct mmc_host *mmc;
+ struct jz4740_mmc_host *host;
+ struct jz4740_mmc_platform_data *pdata;
+
+ pdata = pdev->dev.platform_data;
+
+ mmc = mmc_alloc_host(sizeof(struct jz4740_mmc_host), &pdev->dev);
+ if (!mmc) {
+ dev_err(&pdev->dev, "Failed to alloc mmc host structure\n");
+ return -ENOMEM;
+ }
+
+ host = mmc_priv(mmc);
+ host->pdata = pdata;
+
+ host->irq = platform_get_irq(pdev, 0);
+ if (host->irq < 0) {
+ ret = host->irq;
+ dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
+ goto err_free_host;
+ }
+
+ host->clk = clk_get(&pdev->dev, "mmc");
+ if (IS_ERR(host->clk)) {
+ ret = PTR_ERR(host->clk);
+ dev_err(&pdev->dev, "Failed to get mmc clock\n");
+ goto err_free_host;
+ }
+
+ host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!host->mem) {
+ ret = -ENOENT;
+ dev_err(&pdev->dev, "Failed to get base platform memory\n");
+ goto err_clk_put;
+ }
+
+ host->mem = request_mem_region(host->mem->start,
+ resource_size(host->mem), pdev->name);
+ if (!host->mem) {
+ ret = -EBUSY;
+ dev_err(&pdev->dev, "Failed to request base memory region\n");
+ goto err_clk_put;
+ }
+
+ host->base = ioremap_nocache(host->mem->start, resource_size(host->mem));
+ if (!host->base) {
+ ret = -EBUSY;
+ dev_err(&pdev->dev, "Failed to ioremap base memory\n");
+ goto err_release_mem_region;
+ }
+
+ ret = jz_gpio_bulk_request(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request mmc pins: %d\n", ret);
+ goto err_iounmap;
+ }
+
+ ret = jz4740_mmc_request_gpios(pdev);
+ if (ret)
+ goto err_gpio_bulk_free;
+
+ mmc->ops = &jz4740_mmc_ops;
+ mmc->f_min = JZ_MMC_CLK_RATE / 128;
+ mmc->f_max = JZ_MMC_CLK_RATE;
+ mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+ mmc->caps = (pdata && pdata->data_1bit) ? 0 : MMC_CAP_4_BIT_DATA;
+ mmc->caps |= MMC_CAP_SDIO_IRQ;
+
+ mmc->max_blk_size = (1 << 10) - 1;
+ mmc->max_blk_count = (1 << 15) - 1;
+ mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
+
+ mmc->max_segs = 128;
+ mmc->max_seg_size = mmc->max_req_size;
+
+ host->mmc = mmc;
+ host->pdev = pdev;
+ spin_lock_init(&host->lock);
+ host->irq_mask = 0xffff;
+
+ ret = jz4740_mmc_request_cd_irq(pdev, host);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request card detect irq\n");
+ goto err_free_gpios;
+ }
+
+ ret = request_threaded_irq(host->irq, jz_mmc_irq, jz_mmc_irq_worker, 0,
+ dev_name(&pdev->dev), host);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request irq: %d\n", ret);
+ goto err_free_card_detect_irq;
+ }
+
+ jz4740_mmc_reset(host);
+ jz4740_mmc_clock_disable(host);
+ setup_timer(&host->timeout_timer, jz4740_mmc_timeout,
+ (unsigned long)host);
+ /* It is not important when it times out, it just needs to timeout. */
+ set_timer_slack(&host->timeout_timer, HZ);
+
+ platform_set_drvdata(pdev, host);
+ ret = mmc_add_host(mmc);
+
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to add mmc host: %d\n", ret);
+ goto err_free_irq;
+ }
+ dev_info(&pdev->dev, "JZ SD/MMC card driver registered\n");
+
+ return 0;
+
+err_free_irq:
+ free_irq(host->irq, host);
+err_free_card_detect_irq:
+ if (host->card_detect_irq >= 0)
+ free_irq(host->card_detect_irq, host);
+err_free_gpios:
+ jz4740_mmc_free_gpios(pdev);
+err_gpio_bulk_free:
+ jz_gpio_bulk_free(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+err_iounmap:
+ iounmap(host->base);
+err_release_mem_region:
+ release_mem_region(host->mem->start, resource_size(host->mem));
+err_clk_put:
+ clk_put(host->clk);
+err_free_host:
+ platform_set_drvdata(pdev, NULL);
+ mmc_free_host(mmc);
+
+ return ret;
+}
+
+static int __devexit jz4740_mmc_remove(struct platform_device *pdev)
+{
+ struct jz4740_mmc_host *host = platform_get_drvdata(pdev);
+
+ del_timer_sync(&host->timeout_timer);
+ jz4740_mmc_set_irq_enabled(host, 0xff, false);
+ jz4740_mmc_reset(host);
+
+ mmc_remove_host(host->mmc);
+
+ free_irq(host->irq, host);
+ if (host->card_detect_irq >= 0)
+ free_irq(host->card_detect_irq, host);
+
+ jz4740_mmc_free_gpios(pdev);
+ jz_gpio_bulk_free(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+
+ iounmap(host->base);
+ release_mem_region(host->mem->start, resource_size(host->mem));
+
+ clk_put(host->clk);
+
+ platform_set_drvdata(pdev, NULL);
+ mmc_free_host(host->mmc);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+
+static int jz4740_mmc_suspend(struct device *dev)
+{
+ struct jz4740_mmc_host *host = dev_get_drvdata(dev);
+
+ mmc_suspend_host(host->mmc);
+
+ jz_gpio_bulk_suspend(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+
+ return 0;
+}
+
+static int jz4740_mmc_resume(struct device *dev)
+{
+ struct jz4740_mmc_host *host = dev_get_drvdata(dev);
+
+ jz_gpio_bulk_resume(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+
+ mmc_resume_host(host->mmc);
+
+ return 0;
+}
+
+const struct dev_pm_ops jz4740_mmc_pm_ops = {
+ .suspend = jz4740_mmc_suspend,
+ .resume = jz4740_mmc_resume,
+ .poweroff = jz4740_mmc_suspend,
+ .restore = jz4740_mmc_resume,
+};
+
+#define JZ4740_MMC_PM_OPS (&jz4740_mmc_pm_ops)
+#else
+#define JZ4740_MMC_PM_OPS NULL
+#endif
+
+static struct platform_driver jz4740_mmc_driver = {
+ .probe = jz4740_mmc_probe,
+ .remove = __devexit_p(jz4740_mmc_remove),
+ .driver = {
+ .name = "jz4740-mmc",
+ .owner = THIS_MODULE,
+ .pm = JZ4740_MMC_PM_OPS,
+ },
+};
+
+static int __init jz4740_mmc_init(void)
+{
+ return platform_driver_register(&jz4740_mmc_driver);
+}
+module_init(jz4740_mmc_init);
+
+static void __exit jz4740_mmc_exit(void)
+{
+ platform_driver_unregister(&jz4740_mmc_driver);
+}
+module_exit(jz4740_mmc_exit);
+
+MODULE_DESCRIPTION("JZ4740 SD/MMC controller driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
diff --git a/drivers/mmc/host/mmc_spi.c b/drivers/mmc/host/mmc_spi.c
new file mode 100644
index 00000000..7c1e16aa
--- /dev/null
+++ b/drivers/mmc/host/mmc_spi.c
@@ -0,0 +1,1554 @@
+/*
+ * mmc_spi.c - Access SD/MMC cards through SPI master controllers
+ *
+ * (C) Copyright 2005, Intec Automation,
+ * Mike Lavender (mike@steroidmicros)
+ * (C) Copyright 2006-2007, David Brownell
+ * (C) Copyright 2007, Axis Communications,
+ * Hans-Peter Nilsson (hp@axis.com)
+ * (C) Copyright 2007, ATRON electronic GmbH,
+ * Jan Nikitenko <jan.nikitenko@gmail.com>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/bio.h>
+#include <linux/dma-mapping.h>
+#include <linux/crc7.h>
+#include <linux/crc-itu-t.h>
+#include <linux/scatterlist.h>
+
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h> /* for R1_SPI_* bit values */
+
+#include <linux/spi/spi.h>
+#include <linux/spi/mmc_spi.h>
+
+#include <asm/unaligned.h>
+
+
+/* NOTES:
+ *
+ * - For now, we won't try to interoperate with a real mmc/sd/sdio
+ * controller, although some of them do have hardware support for
+ * SPI protocol. The main reason for such configs would be mmc-ish
+ * cards like DataFlash, which don't support that "native" protocol.
+ *
+ * We don't have a "DataFlash/MMC/SD/SDIO card slot" abstraction to
+ * switch between driver stacks, and in any case if "native" mode
+ * is available, it will be faster and hence preferable.
+ *
+ * - MMC depends on a different chipselect management policy than the
+ * SPI interface currently supports for shared bus segments: it needs
+ * to issue multiple spi_message requests with the chipselect active,
+ * using the results of one message to decide the next one to issue.
+ *
+ * Pending updates to the programming interface, this driver expects
+ * that it not share the bus with other drivers (precluding conflicts).
+ *
+ * - We tell the controller to keep the chipselect active from the
+ * beginning of an mmc_host_ops.request until the end. So beware
+ * of SPI controller drivers that mis-handle the cs_change flag!
+ *
+ * However, many cards seem OK with chipselect flapping up/down
+ * during that time ... at least on unshared bus segments.
+ */
+
+
+/*
+ * Local protocol constants, internal to data block protocols.
+ */
+
+/* Response tokens used to ack each block written: */
+#define SPI_MMC_RESPONSE_CODE(x) ((x) & 0x1f)
+#define SPI_RESPONSE_ACCEPTED ((2 << 1)|1)
+#define SPI_RESPONSE_CRC_ERR ((5 << 1)|1)
+#define SPI_RESPONSE_WRITE_ERR ((6 << 1)|1)
+
+/* Read and write blocks start with these tokens and end with crc;
+ * on error, read tokens act like a subset of R2_SPI_* values.
+ */
+#define SPI_TOKEN_SINGLE 0xfe /* single block r/w, multiblock read */
+#define SPI_TOKEN_MULTI_WRITE 0xfc /* multiblock write */
+#define SPI_TOKEN_STOP_TRAN 0xfd /* terminate multiblock write */
+
+#define MMC_SPI_BLOCKSIZE 512
+
+
+/* These fixed timeouts come from the latest SD specs, which say to ignore
+ * the CSD values. The R1B value is for card erase (e.g. the "I forgot the
+ * card's password" scenario); it's mostly applied to STOP_TRANSMISSION after
+ * reads which takes nowhere near that long. Older cards may be able to use
+ * shorter timeouts ... but why bother?
+ */
+#define r1b_timeout (HZ * 3)
+
+/* One of the critical speed parameters is the amount of data which may
+ * be transferred in one command. If this value is too low, the SD card
+ * controller has to do multiple partial block writes (argggh!). With
+ * today (2008) SD cards there is little speed gain if we transfer more
+ * than 64 KBytes at a time. So use this value until there is any indication
+ * that we should do more here.
+ */
+#define MMC_SPI_BLOCKSATONCE 128
+
+/****************************************************************************/
+
+/*
+ * Local Data Structures
+ */
+
+/* "scratch" is per-{command,block} data exchanged with the card */
+struct scratch {
+ u8 status[29];
+ u8 data_token;
+ __be16 crc_val;
+};
+
+struct mmc_spi_host {
+ struct mmc_host *mmc;
+ struct spi_device *spi;
+
+ unsigned char power_mode;
+ u16 powerup_msecs;
+
+ struct mmc_spi_platform_data *pdata;
+
+ /* for bulk data transfers */
+ struct spi_transfer token, t, crc, early_status;
+ struct spi_message m;
+
+ /* for status readback */
+ struct spi_transfer status;
+ struct spi_message readback;
+
+ /* underlying DMA-aware controller, or null */
+ struct device *dma_dev;
+
+ /* buffer used for commands and for message "overhead" */
+ struct scratch *data;
+ dma_addr_t data_dma;
+
+ /* Specs say to write ones most of the time, even when the card
+ * has no need to read its input data; and many cards won't care.
+ * This is our source of those ones.
+ */
+ void *ones;
+ dma_addr_t ones_dma;
+};
+
+
+/****************************************************************************/
+
+/*
+ * MMC-over-SPI protocol glue, used by the MMC stack interface
+ */
+
+static inline int mmc_cs_off(struct mmc_spi_host *host)
+{
+ /* chipselect will always be inactive after setup() */
+ return spi_setup(host->spi);
+}
+
+static int
+mmc_spi_readbytes(struct mmc_spi_host *host, unsigned len)
+{
+ int status;
+
+ if (len > sizeof(*host->data)) {
+ WARN_ON(1);
+ return -EIO;
+ }
+
+ host->status.len = len;
+
+ if (host->dma_dev)
+ dma_sync_single_for_device(host->dma_dev,
+ host->data_dma, sizeof(*host->data),
+ DMA_FROM_DEVICE);
+
+ status = spi_sync_locked(host->spi, &host->readback);
+
+ if (host->dma_dev)
+ dma_sync_single_for_cpu(host->dma_dev,
+ host->data_dma, sizeof(*host->data),
+ DMA_FROM_DEVICE);
+
+ return status;
+}
+
+static int mmc_spi_skip(struct mmc_spi_host *host, unsigned long timeout,
+ unsigned n, u8 byte)
+{
+ u8 *cp = host->data->status;
+ unsigned long start = jiffies;
+
+ while (1) {
+ int status;
+ unsigned i;
+
+ status = mmc_spi_readbytes(host, n);
+ if (status < 0)
+ return status;
+
+ for (i = 0; i < n; i++) {
+ if (cp[i] != byte)
+ return cp[i];
+ }
+
+ if (time_is_before_jiffies(start + timeout))
+ break;
+
+ /* If we need long timeouts, we may release the CPU.
+ * We use jiffies here because we want to have a relation
+ * between elapsed time and the blocking of the scheduler.
+ */
+ if (time_is_before_jiffies(start+1))
+ schedule();
+ }
+ return -ETIMEDOUT;
+}
+
+static inline int
+mmc_spi_wait_unbusy(struct mmc_spi_host *host, unsigned long timeout)
+{
+ return mmc_spi_skip(host, timeout, sizeof(host->data->status), 0);
+}
+
+static int mmc_spi_readtoken(struct mmc_spi_host *host, unsigned long timeout)
+{
+ return mmc_spi_skip(host, timeout, 1, 0xff);
+}
+
+
+/*
+ * Note that for SPI, cmd->resp[0] is not the same data as "native" protocol
+ * hosts return! The low byte holds R1_SPI bits. The next byte may hold
+ * R2_SPI bits ... for SEND_STATUS, or after data read errors.
+ *
+ * cmd->resp[1] holds any four-byte response, for R3 (READ_OCR) and on
+ * newer cards R7 (IF_COND).
+ */
+
+static char *maptype(struct mmc_command *cmd)
+{
+ switch (mmc_spi_resp_type(cmd)) {
+ case MMC_RSP_SPI_R1: return "R1";
+ case MMC_RSP_SPI_R1B: return "R1B";
+ case MMC_RSP_SPI_R2: return "R2/R5";
+ case MMC_RSP_SPI_R3: return "R3/R4/R7";
+ default: return "?";
+ }
+}
+
+/* return zero, else negative errno after setting cmd->error */
+static int mmc_spi_response_get(struct mmc_spi_host *host,
+ struct mmc_command *cmd, int cs_on)
+{
+ u8 *cp = host->data->status;
+ u8 *end = cp + host->t.len;
+ int value = 0;
+ int bitshift;
+ u8 leftover = 0;
+ unsigned short rotator;
+ int i;
+ char tag[32];
+
+ snprintf(tag, sizeof(tag), " ... CMD%d response SPI_%s",
+ cmd->opcode, maptype(cmd));
+
+ /* Except for data block reads, the whole response will already
+ * be stored in the scratch buffer. It's somewhere after the
+ * command and the first byte we read after it. We ignore that
+ * first byte. After STOP_TRANSMISSION command it may include
+ * two data bits, but otherwise it's all ones.
+ */
+ cp += 8;
+ while (cp < end && *cp == 0xff)
+ cp++;
+
+ /* Data block reads (R1 response types) may need more data... */
+ if (cp == end) {
+ cp = host->data->status;
+ end = cp+1;
+
+ /* Card sends N(CR) (== 1..8) bytes of all-ones then one
+ * status byte ... and we already scanned 2 bytes.
+ *
+ * REVISIT block read paths use nasty byte-at-a-time I/O
+ * so it can always DMA directly into the target buffer.
+ * It'd probably be better to memcpy() the first chunk and
+ * avoid extra i/o calls...
+ *
+ * Note we check for more than 8 bytes, because in practice,
+ * some SD cards are slow...
+ */
+ for (i = 2; i < 16; i++) {
+ value = mmc_spi_readbytes(host, 1);
+ if (value < 0)
+ goto done;
+ if (*cp != 0xff)
+ goto checkstatus;
+ }
+ value = -ETIMEDOUT;
+ goto done;
+ }
+
+checkstatus:
+ bitshift = 0;
+ if (*cp & 0x80) {
+ /* Houston, we have an ugly card with a bit-shifted response */
+ rotator = *cp++ << 8;
+ /* read the next byte */
+ if (cp == end) {
+ value = mmc_spi_readbytes(host, 1);
+ if (value < 0)
+ goto done;
+ cp = host->data->status;
+ end = cp+1;
+ }
+ rotator |= *cp++;
+ while (rotator & 0x8000) {
+ bitshift++;
+ rotator <<= 1;
+ }
+ cmd->resp[0] = rotator >> 8;
+ leftover = rotator;
+ } else {
+ cmd->resp[0] = *cp++;
+ }
+ cmd->error = 0;
+
+ /* Status byte: the entire seven-bit R1 response. */
+ if (cmd->resp[0] != 0) {
+ if ((R1_SPI_PARAMETER | R1_SPI_ADDRESS)
+ & cmd->resp[0])
+ value = -EFAULT; /* Bad address */
+ else if (R1_SPI_ILLEGAL_COMMAND & cmd->resp[0])
+ value = -ENOSYS; /* Function not implemented */
+ else if (R1_SPI_COM_CRC & cmd->resp[0])
+ value = -EILSEQ; /* Illegal byte sequence */
+ else if ((R1_SPI_ERASE_SEQ | R1_SPI_ERASE_RESET)
+ & cmd->resp[0])
+ value = -EIO; /* I/O error */
+ /* else R1_SPI_IDLE, "it's resetting" */
+ }
+
+ switch (mmc_spi_resp_type(cmd)) {
+
+ /* SPI R1B == R1 + busy; STOP_TRANSMISSION (for multiblock reads)
+ * and less-common stuff like various erase operations.
+ */
+ case MMC_RSP_SPI_R1B:
+ /* maybe we read all the busy tokens already */
+ while (cp < end && *cp == 0)
+ cp++;
+ if (cp == end)
+ mmc_spi_wait_unbusy(host, r1b_timeout);
+ break;
+
+ /* SPI R2 == R1 + second status byte; SEND_STATUS
+ * SPI R5 == R1 + data byte; IO_RW_DIRECT
+ */
+ case MMC_RSP_SPI_R2:
+ /* read the next byte */
+ if (cp == end) {
+ value = mmc_spi_readbytes(host, 1);
+ if (value < 0)
+ goto done;
+ cp = host->data->status;
+ end = cp+1;
+ }
+ if (bitshift) {
+ rotator = leftover << 8;
+ rotator |= *cp << bitshift;
+ cmd->resp[0] |= (rotator & 0xFF00);
+ } else {
+ cmd->resp[0] |= *cp << 8;
+ }
+ break;
+
+ /* SPI R3, R4, or R7 == R1 + 4 bytes */
+ case MMC_RSP_SPI_R3:
+ rotator = leftover << 8;
+ cmd->resp[1] = 0;
+ for (i = 0; i < 4; i++) {
+ cmd->resp[1] <<= 8;
+ /* read the next byte */
+ if (cp == end) {
+ value = mmc_spi_readbytes(host, 1);
+ if (value < 0)
+ goto done;
+ cp = host->data->status;
+ end = cp+1;
+ }
+ if (bitshift) {
+ rotator |= *cp++ << bitshift;
+ cmd->resp[1] |= (rotator >> 8);
+ rotator <<= 8;
+ } else {
+ cmd->resp[1] |= *cp++;
+ }
+ }
+ break;
+
+ /* SPI R1 == just one status byte */
+ case MMC_RSP_SPI_R1:
+ break;
+
+ default:
+ dev_dbg(&host->spi->dev, "bad response type %04x\n",
+ mmc_spi_resp_type(cmd));
+ if (value >= 0)
+ value = -EINVAL;
+ goto done;
+ }
+
+ if (value < 0)
+ dev_dbg(&host->spi->dev, "%s: resp %04x %08x\n",
+ tag, cmd->resp[0], cmd->resp[1]);
+
+ /* disable chipselect on errors and some success cases */
+ if (value >= 0 && cs_on)
+ return value;
+done:
+ if (value < 0)
+ cmd->error = value;
+ mmc_cs_off(host);
+ return value;
+}
+
+/* Issue command and read its response.
+ * Returns zero on success, negative for error.
+ *
+ * On error, caller must cope with mmc core retry mechanism. That
+ * means immediate low-level resubmit, which affects the bus lock...
+ */
+static int
+mmc_spi_command_send(struct mmc_spi_host *host,
+ struct mmc_request *mrq,
+ struct mmc_command *cmd, int cs_on)
+{
+ struct scratch *data = host->data;
+ u8 *cp = data->status;
+ u32 arg = cmd->arg;
+ int status;
+ struct spi_transfer *t;
+
+ /* We can handle most commands (except block reads) in one full
+ * duplex I/O operation before either starting the next transfer
+ * (data block or command) or else deselecting the card.
+ *
+ * First, write 7 bytes:
+ * - an all-ones byte to ensure the card is ready
+ * - opcode byte (plus start and transmission bits)
+ * - four bytes of big-endian argument
+ * - crc7 (plus end bit) ... always computed, it's cheap
+ *
+ * We init the whole buffer to all-ones, which is what we need
+ * to write while we're reading (later) response data.
+ */
+ memset(cp++, 0xff, sizeof(data->status));
+
+ *cp++ = 0x40 | cmd->opcode;
+ *cp++ = (u8)(arg >> 24);
+ *cp++ = (u8)(arg >> 16);
+ *cp++ = (u8)(arg >> 8);
+ *cp++ = (u8)arg;
+ *cp++ = (crc7(0, &data->status[1], 5) << 1) | 0x01;
+
+ /* Then, read up to 13 bytes (while writing all-ones):
+ * - N(CR) (== 1..8) bytes of all-ones
+ * - status byte (for all response types)
+ * - the rest of the response, either:
+ * + nothing, for R1 or R1B responses
+ * + second status byte, for R2 responses
+ * + four data bytes, for R3 and R7 responses
+ *
+ * Finally, read some more bytes ... in the nice cases we know in
+ * advance how many, and reading 1 more is always OK:
+ * - N(EC) (== 0..N) bytes of all-ones, before deselect/finish
+ * - N(RC) (== 1..N) bytes of all-ones, before next command
+ * - N(WR) (== 1..N) bytes of all-ones, before data write
+ *
+ * So in those cases one full duplex I/O of at most 21 bytes will
+ * handle the whole command, leaving the card ready to receive a
+ * data block or new command. We do that whenever we can, shaving
+ * CPU and IRQ costs (especially when using DMA or FIFOs).
+ *
+ * There are two other cases, where it's not generally practical
+ * to rely on a single I/O:
+ *
+ * - R1B responses need at least N(EC) bytes of all-zeroes.
+ *
+ * In this case we can *try* to fit it into one I/O, then
+ * maybe read more data later.
+ *
+ * - Data block reads are more troublesome, since a variable
+ * number of padding bytes precede the token and data.
+ * + N(CX) (== 0..8) bytes of all-ones, before CSD or CID
+ * + N(AC) (== 1..many) bytes of all-ones
+ *
+ * In this case we currently only have minimal speedups here:
+ * when N(CR) == 1 we can avoid I/O in response_get().
+ */
+ if (cs_on && (mrq->data->flags & MMC_DATA_READ)) {
+ cp += 2; /* min(N(CR)) + status */
+ /* R1 */
+ } else {
+ cp += 10; /* max(N(CR)) + status + min(N(RC),N(WR)) */
+ if (cmd->flags & MMC_RSP_SPI_S2) /* R2/R5 */
+ cp++;
+ else if (cmd->flags & MMC_RSP_SPI_B4) /* R3/R4/R7 */
+ cp += 4;
+ else if (cmd->flags & MMC_RSP_BUSY) /* R1B */
+ cp = data->status + sizeof(data->status);
+ /* else: R1 (most commands) */
+ }
+
+ dev_dbg(&host->spi->dev, " mmc_spi: CMD%d, resp %s\n",
+ cmd->opcode, maptype(cmd));
+
+ /* send command, leaving chipselect active */
+ spi_message_init(&host->m);
+
+ t = &host->t;
+ memset(t, 0, sizeof(*t));
+ t->tx_buf = t->rx_buf = data->status;
+ t->tx_dma = t->rx_dma = host->data_dma;
+ t->len = cp - data->status;
+ t->cs_change = 1;
+ spi_message_add_tail(t, &host->m);
+
+ if (host->dma_dev) {
+ host->m.is_dma_mapped = 1;
+ dma_sync_single_for_device(host->dma_dev,
+ host->data_dma, sizeof(*host->data),
+ DMA_BIDIRECTIONAL);
+ }
+ status = spi_sync_locked(host->spi, &host->m);
+
+ if (host->dma_dev)
+ dma_sync_single_for_cpu(host->dma_dev,
+ host->data_dma, sizeof(*host->data),
+ DMA_BIDIRECTIONAL);
+ if (status < 0) {
+ dev_dbg(&host->spi->dev, " ... write returned %d\n", status);
+ cmd->error = status;
+ return status;
+ }
+
+ /* after no-data commands and STOP_TRANSMISSION, chipselect off */
+ return mmc_spi_response_get(host, cmd, cs_on);
+}
+
+/* Build data message with up to four separate transfers. For TX, we
+ * start by writing the data token. And in most cases, we finish with
+ * a status transfer.
+ *
+ * We always provide TX data for data and CRC. The MMC/SD protocol
+ * requires us to write ones; but Linux defaults to writing zeroes;
+ * so we explicitly initialize it to all ones on RX paths.
+ *
+ * We also handle DMA mapping, so the underlying SPI controller does
+ * not need to (re)do it for each message.
+ */
+static void
+mmc_spi_setup_data_message(
+ struct mmc_spi_host *host,
+ int multiple,
+ enum dma_data_direction direction)
+{
+ struct spi_transfer *t;
+ struct scratch *scratch = host->data;
+ dma_addr_t dma = host->data_dma;
+
+ spi_message_init(&host->m);
+ if (dma)
+ host->m.is_dma_mapped = 1;
+
+ /* for reads, readblock() skips 0xff bytes before finding
+ * the token; for writes, this transfer issues that token.
+ */
+ if (direction == DMA_TO_DEVICE) {
+ t = &host->token;
+ memset(t, 0, sizeof(*t));
+ t->len = 1;
+ if (multiple)
+ scratch->data_token = SPI_TOKEN_MULTI_WRITE;
+ else
+ scratch->data_token = SPI_TOKEN_SINGLE;
+ t->tx_buf = &scratch->data_token;
+ if (dma)
+ t->tx_dma = dma + offsetof(struct scratch, data_token);
+ spi_message_add_tail(t, &host->m);
+ }
+
+ /* Body of transfer is buffer, then CRC ...
+ * either TX-only, or RX with TX-ones.
+ */
+ t = &host->t;
+ memset(t, 0, sizeof(*t));
+ t->tx_buf = host->ones;
+ t->tx_dma = host->ones_dma;
+ /* length and actual buffer info are written later */
+ spi_message_add_tail(t, &host->m);
+
+ t = &host->crc;
+ memset(t, 0, sizeof(*t));
+ t->len = 2;
+ if (direction == DMA_TO_DEVICE) {
+ /* the actual CRC may get written later */
+ t->tx_buf = &scratch->crc_val;
+ if (dma)
+ t->tx_dma = dma + offsetof(struct scratch, crc_val);
+ } else {
+ t->tx_buf = host->ones;
+ t->tx_dma = host->ones_dma;
+ t->rx_buf = &scratch->crc_val;
+ if (dma)
+ t->rx_dma = dma + offsetof(struct scratch, crc_val);
+ }
+ spi_message_add_tail(t, &host->m);
+
+ /*
+ * A single block read is followed by N(EC) [0+] all-ones bytes
+ * before deselect ... don't bother.
+ *
+ * Multiblock reads are followed by N(AC) [1+] all-ones bytes before
+ * the next block is read, or a STOP_TRANSMISSION is issued. We'll
+ * collect that single byte, so readblock() doesn't need to.
+ *
+ * For a write, the one-byte data response follows immediately, then
+ * come zero or more busy bytes, then N(WR) [1+] all-ones bytes.
+ * Then single block reads may deselect, and multiblock ones issue
+ * the next token (next data block, or STOP_TRAN). We can try to
+ * minimize I/O ops by using a single read to collect end-of-busy.
+ */
+ if (multiple || direction == DMA_TO_DEVICE) {
+ t = &host->early_status;
+ memset(t, 0, sizeof(*t));
+ t->len = (direction == DMA_TO_DEVICE)
+ ? sizeof(scratch->status)
+ : 1;
+ t->tx_buf = host->ones;
+ t->tx_dma = host->ones_dma;
+ t->rx_buf = scratch->status;
+ if (dma)
+ t->rx_dma = dma + offsetof(struct scratch, status);
+ t->cs_change = 1;
+ spi_message_add_tail(t, &host->m);
+ }
+}
+
+/*
+ * Write one block:
+ * - caller handled preceding N(WR) [1+] all-ones bytes
+ * - data block
+ * + token
+ * + data bytes
+ * + crc16
+ * - an all-ones byte ... card writes a data-response byte
+ * - followed by N(EC) [0+] all-ones bytes, card writes zero/'busy'
+ *
+ * Return negative errno, else success.
+ */
+static int
+mmc_spi_writeblock(struct mmc_spi_host *host, struct spi_transfer *t,
+ unsigned long timeout)
+{
+ struct spi_device *spi = host->spi;
+ int status, i;
+ struct scratch *scratch = host->data;
+ u32 pattern;
+
+ if (host->mmc->use_spi_crc)
+ scratch->crc_val = cpu_to_be16(
+ crc_itu_t(0, t->tx_buf, t->len));
+ if (host->dma_dev)
+ dma_sync_single_for_device(host->dma_dev,
+ host->data_dma, sizeof(*scratch),
+ DMA_BIDIRECTIONAL);
+
+ status = spi_sync_locked(spi, &host->m);
+
+ if (status != 0) {
+ dev_dbg(&spi->dev, "write error (%d)\n", status);
+ return status;
+ }
+
+ if (host->dma_dev)
+ dma_sync_single_for_cpu(host->dma_dev,
+ host->data_dma, sizeof(*scratch),
+ DMA_BIDIRECTIONAL);
+
+ /*
+ * Get the transmission data-response reply. It must follow
+ * immediately after the data block we transferred. This reply
+ * doesn't necessarily tell whether the write operation succeeded;
+ * it just says if the transmission was ok and whether *earlier*
+ * writes succeeded; see the standard.
+ *
+ * In practice, there are (even modern SDHC-)cards which are late
+ * in sending the response, and miss the time frame by a few bits,
+ * so we have to cope with this situation and check the response
+ * bit-by-bit. Arggh!!!
+ */
+ pattern = scratch->status[0] << 24;
+ pattern |= scratch->status[1] << 16;
+ pattern |= scratch->status[2] << 8;
+ pattern |= scratch->status[3];
+
+ /* First 3 bit of pattern are undefined */
+ pattern |= 0xE0000000;
+
+ /* left-adjust to leading 0 bit */
+ while (pattern & 0x80000000)
+ pattern <<= 1;
+ /* right-adjust for pattern matching. Code is in bit 4..0 now. */
+ pattern >>= 27;
+
+ switch (pattern) {
+ case SPI_RESPONSE_ACCEPTED:
+ status = 0;
+ break;
+ case SPI_RESPONSE_CRC_ERR:
+ /* host shall then issue MMC_STOP_TRANSMISSION */
+ status = -EILSEQ;
+ break;
+ case SPI_RESPONSE_WRITE_ERR:
+ /* host shall then issue MMC_STOP_TRANSMISSION,
+ * and should MMC_SEND_STATUS to sort it out
+ */
+ status = -EIO;
+ break;
+ default:
+ status = -EPROTO;
+ break;
+ }
+ if (status != 0) {
+ dev_dbg(&spi->dev, "write error %02x (%d)\n",
+ scratch->status[0], status);
+ return status;
+ }
+
+ t->tx_buf += t->len;
+ if (host->dma_dev)
+ t->tx_dma += t->len;
+
+ /* Return when not busy. If we didn't collect that status yet,
+ * we'll need some more I/O.
+ */
+ for (i = 4; i < sizeof(scratch->status); i++) {
+ /* card is non-busy if the most recent bit is 1 */
+ if (scratch->status[i] & 0x01)
+ return 0;
+ }
+ return mmc_spi_wait_unbusy(host, timeout);
+}
+
+/*
+ * Read one block:
+ * - skip leading all-ones bytes ... either
+ * + N(AC) [1..f(clock,CSD)] usually, else
+ * + N(CX) [0..8] when reading CSD or CID
+ * - data block
+ * + token ... if error token, no data or crc
+ * + data bytes
+ * + crc16
+ *
+ * After single block reads, we're done; N(EC) [0+] all-ones bytes follow
+ * before dropping chipselect.
+ *
+ * For multiblock reads, caller either reads the next block or issues a
+ * STOP_TRANSMISSION command.
+ */
+static int
+mmc_spi_readblock(struct mmc_spi_host *host, struct spi_transfer *t,
+ unsigned long timeout)
+{
+ struct spi_device *spi = host->spi;
+ int status;
+ struct scratch *scratch = host->data;
+ unsigned int bitshift;
+ u8 leftover;
+
+ /* At least one SD card sends an all-zeroes byte when N(CX)
+ * applies, before the all-ones bytes ... just cope with that.
+ */
+ status = mmc_spi_readbytes(host, 1);
+ if (status < 0)
+ return status;
+ status = scratch->status[0];
+ if (status == 0xff || status == 0)
+ status = mmc_spi_readtoken(host, timeout);
+
+ if (status < 0) {
+ dev_dbg(&spi->dev, "read error %02x (%d)\n", status, status);
+ return status;
+ }
+
+ /* The token may be bit-shifted...
+ * the first 0-bit precedes the data stream.
+ */
+ bitshift = 7;
+ while (status & 0x80) {
+ status <<= 1;
+ bitshift--;
+ }
+ leftover = status << 1;
+
+ if (host->dma_dev) {
+ dma_sync_single_for_device(host->dma_dev,
+ host->data_dma, sizeof(*scratch),
+ DMA_BIDIRECTIONAL);
+ dma_sync_single_for_device(host->dma_dev,
+ t->rx_dma, t->len,
+ DMA_FROM_DEVICE);
+ }
+
+ status = spi_sync_locked(spi, &host->m);
+
+ if (host->dma_dev) {
+ dma_sync_single_for_cpu(host->dma_dev,
+ host->data_dma, sizeof(*scratch),
+ DMA_BIDIRECTIONAL);
+ dma_sync_single_for_cpu(host->dma_dev,
+ t->rx_dma, t->len,
+ DMA_FROM_DEVICE);
+ }
+
+ if (bitshift) {
+ /* Walk through the data and the crc and do
+ * all the magic to get byte-aligned data.
+ */
+ u8 *cp = t->rx_buf;
+ unsigned int len;
+ unsigned int bitright = 8 - bitshift;
+ u8 temp;
+ for (len = t->len; len; len--) {
+ temp = *cp;
+ *cp++ = leftover | (temp >> bitshift);
+ leftover = temp << bitright;
+ }
+ cp = (u8 *) &scratch->crc_val;
+ temp = *cp;
+ *cp++ = leftover | (temp >> bitshift);
+ leftover = temp << bitright;
+ temp = *cp;
+ *cp = leftover | (temp >> bitshift);
+ }
+
+ if (host->mmc->use_spi_crc) {
+ u16 crc = crc_itu_t(0, t->rx_buf, t->len);
+
+ be16_to_cpus(&scratch->crc_val);
+ if (scratch->crc_val != crc) {
+ dev_dbg(&spi->dev, "read - crc error: crc_val=0x%04x, "
+ "computed=0x%04x len=%d\n",
+ scratch->crc_val, crc, t->len);
+ return -EILSEQ;
+ }
+ }
+
+ t->rx_buf += t->len;
+ if (host->dma_dev)
+ t->rx_dma += t->len;
+
+ return 0;
+}
+
+/*
+ * An MMC/SD data stage includes one or more blocks, optional CRCs,
+ * and inline handshaking. That handhaking makes it unlike most
+ * other SPI protocol stacks.
+ */
+static void
+mmc_spi_data_do(struct mmc_spi_host *host, struct mmc_command *cmd,
+ struct mmc_data *data, u32 blk_size)
+{
+ struct spi_device *spi = host->spi;
+ struct device *dma_dev = host->dma_dev;
+ struct spi_transfer *t;
+ enum dma_data_direction direction;
+ struct scatterlist *sg;
+ unsigned n_sg;
+ int multiple = (data->blocks > 1);
+ u32 clock_rate;
+ unsigned long timeout;
+
+ if (data->flags & MMC_DATA_READ)
+ direction = DMA_FROM_DEVICE;
+ else
+ direction = DMA_TO_DEVICE;
+ mmc_spi_setup_data_message(host, multiple, direction);
+ t = &host->t;
+
+ if (t->speed_hz)
+ clock_rate = t->speed_hz;
+ else
+ clock_rate = spi->max_speed_hz;
+
+ timeout = data->timeout_ns +
+ data->timeout_clks * 1000000 / clock_rate;
+ timeout = usecs_to_jiffies((unsigned int)(timeout / 1000)) + 1;
+
+ /* Handle scatterlist segments one at a time, with synch for
+ * each 512-byte block
+ */
+ for (sg = data->sg, n_sg = data->sg_len; n_sg; n_sg--, sg++) {
+ int status = 0;
+ dma_addr_t dma_addr = 0;
+ void *kmap_addr;
+ unsigned length = sg->length;
+ enum dma_data_direction dir = direction;
+
+ /* set up dma mapping for controller drivers that might
+ * use DMA ... though they may fall back to PIO
+ */
+ if (dma_dev) {
+ /* never invalidate whole *shared* pages ... */
+ if ((sg->offset != 0 || length != PAGE_SIZE)
+ && dir == DMA_FROM_DEVICE)
+ dir = DMA_BIDIRECTIONAL;
+
+ dma_addr = dma_map_page(dma_dev, sg_page(sg), 0,
+ PAGE_SIZE, dir);
+ if (direction == DMA_TO_DEVICE)
+ t->tx_dma = dma_addr + sg->offset;
+ else
+ t->rx_dma = dma_addr + sg->offset;
+ }
+
+ /* allow pio too; we don't allow highmem */
+ kmap_addr = kmap(sg_page(sg));
+ if (direction == DMA_TO_DEVICE)
+ t->tx_buf = kmap_addr + sg->offset;
+ else
+ t->rx_buf = kmap_addr + sg->offset;
+
+ /* transfer each block, and update request status */
+ while (length) {
+ t->len = min(length, blk_size);
+
+ dev_dbg(&host->spi->dev,
+ " mmc_spi: %s block, %d bytes\n",
+ (direction == DMA_TO_DEVICE)
+ ? "write"
+ : "read",
+ t->len);
+
+ if (direction == DMA_TO_DEVICE)
+ status = mmc_spi_writeblock(host, t, timeout);
+ else
+ status = mmc_spi_readblock(host, t, timeout);
+ if (status < 0)
+ break;
+
+ data->bytes_xfered += t->len;
+ length -= t->len;
+
+ if (!multiple)
+ break;
+ }
+
+ /* discard mappings */
+ if (direction == DMA_FROM_DEVICE)
+ flush_kernel_dcache_page(sg_page(sg));
+ kunmap(sg_page(sg));
+ if (dma_dev)
+ dma_unmap_page(dma_dev, dma_addr, PAGE_SIZE, dir);
+
+ if (status < 0) {
+ data->error = status;
+ dev_dbg(&spi->dev, "%s status %d\n",
+ (direction == DMA_TO_DEVICE)
+ ? "write" : "read",
+ status);
+ break;
+ }
+ }
+
+ /* NOTE some docs describe an MMC-only SET_BLOCK_COUNT (CMD23) that
+ * can be issued before multiblock writes. Unlike its more widely
+ * documented analogue for SD cards (SET_WR_BLK_ERASE_COUNT, ACMD23),
+ * that can affect the STOP_TRAN logic. Complete (and current)
+ * MMC specs should sort that out before Linux starts using CMD23.
+ */
+ if (direction == DMA_TO_DEVICE && multiple) {
+ struct scratch *scratch = host->data;
+ int tmp;
+ const unsigned statlen = sizeof(scratch->status);
+
+ dev_dbg(&spi->dev, " mmc_spi: STOP_TRAN\n");
+
+ /* Tweak the per-block message we set up earlier by morphing
+ * it to hold single buffer with the token followed by some
+ * all-ones bytes ... skip N(BR) (0..1), scan the rest for
+ * "not busy any longer" status, and leave chip selected.
+ */
+ INIT_LIST_HEAD(&host->m.transfers);
+ list_add(&host->early_status.transfer_list,
+ &host->m.transfers);
+
+ memset(scratch->status, 0xff, statlen);
+ scratch->status[0] = SPI_TOKEN_STOP_TRAN;
+
+ host->early_status.tx_buf = host->early_status.rx_buf;
+ host->early_status.tx_dma = host->early_status.rx_dma;
+ host->early_status.len = statlen;
+
+ if (host->dma_dev)
+ dma_sync_single_for_device(host->dma_dev,
+ host->data_dma, sizeof(*scratch),
+ DMA_BIDIRECTIONAL);
+
+ tmp = spi_sync_locked(spi, &host->m);
+
+ if (host->dma_dev)
+ dma_sync_single_for_cpu(host->dma_dev,
+ host->data_dma, sizeof(*scratch),
+ DMA_BIDIRECTIONAL);
+
+ if (tmp < 0) {
+ if (!data->error)
+ data->error = tmp;
+ return;
+ }
+
+ /* Ideally we collected "not busy" status with one I/O,
+ * avoiding wasteful byte-at-a-time scanning... but more
+ * I/O is often needed.
+ */
+ for (tmp = 2; tmp < statlen; tmp++) {
+ if (scratch->status[tmp] != 0)
+ return;
+ }
+ tmp = mmc_spi_wait_unbusy(host, timeout);
+ if (tmp < 0 && !data->error)
+ data->error = tmp;
+ }
+}
+
+/****************************************************************************/
+
+/*
+ * MMC driver implementation -- the interface to the MMC stack
+ */
+
+static void mmc_spi_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct mmc_spi_host *host = mmc_priv(mmc);
+ int status = -EINVAL;
+ int crc_retry = 5;
+ struct mmc_command stop;
+
+#ifdef DEBUG
+ /* MMC core and layered drivers *MUST* issue SPI-aware commands */
+ {
+ struct mmc_command *cmd;
+ int invalid = 0;
+
+ cmd = mrq->cmd;
+ if (!mmc_spi_resp_type(cmd)) {
+ dev_dbg(&host->spi->dev, "bogus command\n");
+ cmd->error = -EINVAL;
+ invalid = 1;
+ }
+
+ cmd = mrq->stop;
+ if (cmd && !mmc_spi_resp_type(cmd)) {
+ dev_dbg(&host->spi->dev, "bogus STOP command\n");
+ cmd->error = -EINVAL;
+ invalid = 1;
+ }
+
+ if (invalid) {
+ dump_stack();
+ mmc_request_done(host->mmc, mrq);
+ return;
+ }
+ }
+#endif
+
+ /* request exclusive bus access */
+ spi_bus_lock(host->spi->master);
+
+crc_recover:
+ /* issue command; then optionally data and stop */
+ status = mmc_spi_command_send(host, mrq, mrq->cmd, mrq->data != NULL);
+ if (status == 0 && mrq->data) {
+ mmc_spi_data_do(host, mrq->cmd, mrq->data, mrq->data->blksz);
+
+ /*
+ * The SPI bus is not always reliable for large data transfers.
+ * If an occasional crc error is reported by the SD device with
+ * data read/write over SPI, it may be recovered by repeating
+ * the last SD command again. The retry count is set to 5 to
+ * ensure the driver passes stress tests.
+ */
+ if (mrq->data->error == -EILSEQ && crc_retry) {
+ stop.opcode = MMC_STOP_TRANSMISSION;
+ stop.arg = 0;
+ stop.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
+ status = mmc_spi_command_send(host, mrq, &stop, 0);
+ crc_retry--;
+ mrq->data->error = 0;
+ goto crc_recover;
+ }
+
+ if (mrq->stop)
+ status = mmc_spi_command_send(host, mrq, mrq->stop, 0);
+ else
+ mmc_cs_off(host);
+ }
+
+ /* release the bus */
+ spi_bus_unlock(host->spi->master);
+
+ mmc_request_done(host->mmc, mrq);
+}
+
+/* See Section 6.4.1, in SD "Simplified Physical Layer Specification 2.0"
+ *
+ * NOTE that here we can't know that the card has just been powered up;
+ * not all MMC/SD sockets support power switching.
+ *
+ * FIXME when the card is still in SPI mode, e.g. from a previous kernel,
+ * this doesn't seem to do the right thing at all...
+ */
+static void mmc_spi_initsequence(struct mmc_spi_host *host)
+{
+ /* Try to be very sure any previous command has completed;
+ * wait till not-busy, skip debris from any old commands.
+ */
+ mmc_spi_wait_unbusy(host, r1b_timeout);
+ mmc_spi_readbytes(host, 10);
+
+ /*
+ * Do a burst with chipselect active-high. We need to do this to
+ * meet the requirement of 74 clock cycles with both chipselect
+ * and CMD (MOSI) high before CMD0 ... after the card has been
+ * powered up to Vdd(min), and so is ready to take commands.
+ *
+ * Some cards are particularly needy of this (e.g. Viking "SD256")
+ * while most others don't seem to care.
+ *
+ * Note that this is one of the places MMC/SD plays games with the
+ * SPI protocol. Another is that when chipselect is released while
+ * the card returns BUSY status, the clock must issue several cycles
+ * with chipselect high before the card will stop driving its output.
+ */
+ host->spi->mode |= SPI_CS_HIGH;
+ if (spi_setup(host->spi) != 0) {
+ /* Just warn; most cards work without it. */
+ dev_warn(&host->spi->dev,
+ "can't change chip-select polarity\n");
+ host->spi->mode &= ~SPI_CS_HIGH;
+ } else {
+ mmc_spi_readbytes(host, 18);
+
+ host->spi->mode &= ~SPI_CS_HIGH;
+ if (spi_setup(host->spi) != 0) {
+ /* Wot, we can't get the same setup we had before? */
+ dev_err(&host->spi->dev,
+ "can't restore chip-select polarity\n");
+ }
+ }
+}
+
+static char *mmc_powerstring(u8 power_mode)
+{
+ switch (power_mode) {
+ case MMC_POWER_OFF: return "off";
+ case MMC_POWER_UP: return "up";
+ case MMC_POWER_ON: return "on";
+ }
+ return "?";
+}
+
+static void mmc_spi_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct mmc_spi_host *host = mmc_priv(mmc);
+
+ if (host->power_mode != ios->power_mode) {
+ int canpower;
+
+ canpower = host->pdata && host->pdata->setpower;
+
+ dev_dbg(&host->spi->dev, "mmc_spi: power %s (%d)%s\n",
+ mmc_powerstring(ios->power_mode),
+ ios->vdd,
+ canpower ? ", can switch" : "");
+
+ /* switch power on/off if possible, accounting for
+ * max 250msec powerup time if needed.
+ */
+ if (canpower) {
+ switch (ios->power_mode) {
+ case MMC_POWER_OFF:
+ case MMC_POWER_UP:
+ host->pdata->setpower(&host->spi->dev,
+ ios->vdd);
+ if (ios->power_mode == MMC_POWER_UP)
+ msleep(host->powerup_msecs);
+ }
+ }
+
+ /* See 6.4.1 in the simplified SD card physical spec 2.0 */
+ if (ios->power_mode == MMC_POWER_ON)
+ mmc_spi_initsequence(host);
+
+ /* If powering down, ground all card inputs to avoid power
+ * delivery from data lines! On a shared SPI bus, this
+ * will probably be temporary; 6.4.2 of the simplified SD
+ * spec says this must last at least 1msec.
+ *
+ * - Clock low means CPOL 0, e.g. mode 0
+ * - MOSI low comes from writing zero
+ * - Chipselect is usually active low...
+ */
+ if (canpower && ios->power_mode == MMC_POWER_OFF) {
+ int mres;
+ u8 nullbyte = 0;
+
+ host->spi->mode &= ~(SPI_CPOL|SPI_CPHA);
+ mres = spi_setup(host->spi);
+ if (mres < 0)
+ dev_dbg(&host->spi->dev,
+ "switch to SPI mode 0 failed\n");
+
+ if (spi_write(host->spi, &nullbyte, 1) < 0)
+ dev_dbg(&host->spi->dev,
+ "put spi signals to low failed\n");
+
+ /*
+ * Now clock should be low due to spi mode 0;
+ * MOSI should be low because of written 0x00;
+ * chipselect should be low (it is active low)
+ * power supply is off, so now MMC is off too!
+ *
+ * FIXME no, chipselect can be high since the
+ * device is inactive and SPI_CS_HIGH is clear...
+ */
+ msleep(10);
+ if (mres == 0) {
+ host->spi->mode |= (SPI_CPOL|SPI_CPHA);
+ mres = spi_setup(host->spi);
+ if (mres < 0)
+ dev_dbg(&host->spi->dev,
+ "switch back to SPI mode 3"
+ " failed\n");
+ }
+ }
+
+ host->power_mode = ios->power_mode;
+ }
+
+ if (host->spi->max_speed_hz != ios->clock && ios->clock != 0) {
+ int status;
+
+ host->spi->max_speed_hz = ios->clock;
+ status = spi_setup(host->spi);
+ dev_dbg(&host->spi->dev,
+ "mmc_spi: clock to %d Hz, %d\n",
+ host->spi->max_speed_hz, status);
+ }
+}
+
+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; 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,
+};
+
+
+/****************************************************************************/
+
+/*
+ * SPI driver implementation
+ */
+
+static irqreturn_t
+mmc_spi_detect_irq(int irq, void *mmc)
+{
+ struct mmc_spi_host *host = mmc_priv(mmc);
+ u16 delay_msec = max(host->pdata->detect_delay, (u16)100);
+
+ mmc_detect_change(mmc, msecs_to_jiffies(delay_msec));
+ return IRQ_HANDLED;
+}
+
+static int mmc_spi_probe(struct spi_device *spi)
+{
+ void *ones;
+ struct mmc_host *mmc;
+ struct mmc_spi_host *host;
+ int status;
+
+ /* We rely on full duplex transfers, mostly to reduce
+ * per-transfer overheads (by making fewer transfers).
+ */
+ if (spi->master->flags & SPI_MASTER_HALF_DUPLEX)
+ return -EINVAL;
+
+ /* MMC and SD specs only seem to care that sampling is on the
+ * rising edge ... meaning SPI modes 0 or 3. So either SPI mode
+ * should be legit. We'll use mode 0 since the steady state is 0,
+ * which is appropriate for hotplugging, unless the platform data
+ * specify mode 3 (if hardware is not compatible to mode 0).
+ */
+ if (spi->mode != SPI_MODE_3)
+ spi->mode = SPI_MODE_0;
+ spi->bits_per_word = 8;
+
+ status = spi_setup(spi);
+ if (status < 0) {
+ dev_dbg(&spi->dev, "needs SPI mode %02x, %d KHz; %d\n",
+ spi->mode, spi->max_speed_hz / 1000,
+ status);
+ return status;
+ }
+
+ /* We need a supply of ones to transmit. This is the only time
+ * the CPU touches these, so cache coherency isn't a concern.
+ *
+ * NOTE if many systems use more than one MMC-over-SPI connector
+ * it'd save some memory to share this. That's evidently rare.
+ */
+ status = -ENOMEM;
+ ones = kmalloc(MMC_SPI_BLOCKSIZE, GFP_KERNEL);
+ if (!ones)
+ goto nomem;
+ memset(ones, 0xff, MMC_SPI_BLOCKSIZE);
+
+ mmc = mmc_alloc_host(sizeof(*host), &spi->dev);
+ if (!mmc)
+ goto nomem;
+
+ mmc->ops = &mmc_spi_ops;
+ mmc->max_blk_size = MMC_SPI_BLOCKSIZE;
+ mmc->max_segs = MMC_SPI_BLOCKSATONCE;
+ mmc->max_req_size = MMC_SPI_BLOCKSATONCE * MMC_SPI_BLOCKSIZE;
+ mmc->max_blk_count = MMC_SPI_BLOCKSATONCE;
+
+ 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.
+ * That's good; some SPI masters can't handle very low speeds!
+ *
+ * However, low speed SDIO cards need not handle over 400 KHz;
+ * that's the only reason not to use a few MHz for f_min (until
+ * the upper layer reads the target frequency from the CSD).
+ */
+ mmc->f_min = 400000;
+ mmc->f_max = spi->max_speed_hz;
+
+ host = mmc_priv(mmc);
+ host->mmc = mmc;
+ host->spi = spi;
+
+ host->ones = ones;
+
+ /* Platform data is used to hook up things like card sensing
+ * and power switching gpios.
+ */
+ host->pdata = mmc_spi_get_pdata(spi);
+ if (host->pdata)
+ mmc->ocr_avail = host->pdata->ocr_mask;
+ if (!mmc->ocr_avail) {
+ dev_warn(&spi->dev, "ASSUMING 3.2-3.4 V slot power\n");
+ mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34;
+ }
+ if (host->pdata && host->pdata->setpower) {
+ host->powerup_msecs = host->pdata->powerup_msecs;
+ if (!host->powerup_msecs || host->powerup_msecs > 250)
+ host->powerup_msecs = 250;
+ }
+
+ dev_set_drvdata(&spi->dev, mmc);
+
+ /* preallocate dma buffers */
+ host->data = kmalloc(sizeof(*host->data), GFP_KERNEL);
+ if (!host->data)
+ goto fail_nobuf1;
+
+ if (spi->master->dev.parent->dma_mask) {
+ struct device *dev = spi->master->dev.parent;
+
+ host->dma_dev = dev;
+ host->ones_dma = dma_map_single(dev, ones,
+ MMC_SPI_BLOCKSIZE, DMA_TO_DEVICE);
+ host->data_dma = dma_map_single(dev, host->data,
+ sizeof(*host->data), DMA_BIDIRECTIONAL);
+
+ /* REVISIT in theory those map operations can fail... */
+
+ dma_sync_single_for_cpu(host->dma_dev,
+ host->data_dma, sizeof(*host->data),
+ DMA_BIDIRECTIONAL);
+ }
+
+ /* setup message for status/busy readback */
+ spi_message_init(&host->readback);
+ host->readback.is_dma_mapped = (host->dma_dev != NULL);
+
+ spi_message_add_tail(&host->status, &host->readback);
+ host->status.tx_buf = host->ones;
+ host->status.tx_dma = host->ones_dma;
+ host->status.rx_buf = &host->data->status;
+ host->status.rx_dma = host->data_dma + offsetof(struct scratch, status);
+ host->status.cs_change = 1;
+
+ /* register card detect irq */
+ if (host->pdata && host->pdata->init) {
+ status = host->pdata->init(&spi->dev, mmc_spi_detect_irq, mmc);
+ if (status != 0)
+ 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%s\n",
+ dev_name(&mmc->class_dev),
+ host->dma_dev ? "" : ", no DMA",
+ (host->pdata && host->pdata->get_ro)
+ ? "" : ", no WP",
+ (host->pdata && host->pdata->setpower)
+ ? "" : ", no poweroff",
+ (mmc->caps & MMC_CAP_NEEDS_POLL)
+ ? ", cd polling" : "");
+ return 0;
+
+fail_add_host:
+ mmc_remove_host (mmc);
+fail_glue_init:
+ if (host->dma_dev)
+ dma_unmap_single(host->dma_dev, host->data_dma,
+ sizeof(*host->data), DMA_BIDIRECTIONAL);
+ kfree(host->data);
+
+fail_nobuf1:
+ mmc_free_host(mmc);
+ mmc_spi_put_pdata(spi);
+ dev_set_drvdata(&spi->dev, NULL);
+
+nomem:
+ kfree(ones);
+ return status;
+}
+
+
+static int __devexit mmc_spi_remove(struct spi_device *spi)
+{
+ struct mmc_host *mmc = dev_get_drvdata(&spi->dev);
+ struct mmc_spi_host *host;
+
+ if (mmc) {
+ host = mmc_priv(mmc);
+
+ /* prevent new mmc_detect_change() calls */
+ if (host->pdata && host->pdata->exit)
+ host->pdata->exit(&spi->dev, mmc);
+
+ mmc_remove_host(mmc);
+
+ if (host->dma_dev) {
+ dma_unmap_single(host->dma_dev, host->ones_dma,
+ MMC_SPI_BLOCKSIZE, DMA_TO_DEVICE);
+ dma_unmap_single(host->dma_dev, host->data_dma,
+ sizeof(*host->data), DMA_BIDIRECTIONAL);
+ }
+
+ kfree(host->data);
+ kfree(host->ones);
+
+ spi->max_speed_hz = mmc->f_max;
+ mmc_free_host(mmc);
+ mmc_spi_put_pdata(spi);
+ dev_set_drvdata(&spi->dev, NULL);
+ }
+ return 0;
+}
+
+static struct of_device_id mmc_spi_of_match_table[] __devinitdata = {
+ { .compatible = "mmc-spi-slot", },
+ {},
+};
+
+static struct spi_driver mmc_spi_driver = {
+ .driver = {
+ .name = "mmc_spi",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ .of_match_table = mmc_spi_of_match_table,
+ },
+ .probe = mmc_spi_probe,
+ .remove = __devexit_p(mmc_spi_remove),
+};
+
+
+static int __init mmc_spi_init(void)
+{
+ return spi_register_driver(&mmc_spi_driver);
+}
+module_init(mmc_spi_init);
+
+
+static void __exit mmc_spi_exit(void)
+{
+ spi_unregister_driver(&mmc_spi_driver);
+}
+module_exit(mmc_spi_exit);
+
+
+MODULE_AUTHOR("Mike Lavender, David Brownell, "
+ "Hans-Peter Nilsson, Jan Nikitenko");
+MODULE_DESCRIPTION("SPI SD/MMC host driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("spi:mmc_spi");
diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c
new file mode 100644
index 00000000..e9dd9d22
--- /dev/null
+++ b/drivers/mmc/host/mmci.c
@@ -0,0 +1,1504 @@
+/*
+ * linux/drivers/mmc/host/mmci.c - ARM PrimeCell MMCI PL180/1 driver
+ *
+ * Copyright (C) 2003 Deep Blue Solutions, Ltd, All Rights Reserved.
+ * Copyright (C) 2010 ST-Ericsson SA
+ *
+ * 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/moduleparam.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/highmem.h>
+#include <linux/log2.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/amba/bus.h>
+#include <linux/clk.h>
+#include <linux/scatterlist.h>
+#include <linux/gpio.h>
+#include <linux/regulator/consumer.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/amba/mmci.h>
+
+#include <asm/div64.h>
+#include <asm/io.h>
+#include <asm/sizes.h>
+
+#include "mmci.h"
+
+#define DRIVER_NAME "mmci-pl18x"
+
+static unsigned int fmax = 515633;
+
+/**
+ * struct variant_data - MMCI variant-specific quirks
+ * @clkreg: default value for MCICLOCK register
+ * @clkreg_enable: enable value for MMCICLOCK register
+ * @datalength_bits: number of bits in the MMCIDATALENGTH register
+ * @fifosize: number of bytes that can be written when MMCI_TXFIFOEMPTY
+ * is asserted (likewise for RX)
+ * @fifohalfsize: number of bytes that can be written when MCI_TXFIFOHALFEMPTY
+ * is asserted (likewise for RX)
+ * @sdio: variant supports SDIO
+ * @st_clkdiv: true if using a ST-specific clock divider algorithm
+ * @blksz_datactrl16: true if Block size is at b16..b30 position in datactrl register
+ */
+struct variant_data {
+ unsigned int clkreg;
+ unsigned int clkreg_enable;
+ unsigned int datalength_bits;
+ unsigned int fifosize;
+ unsigned int fifohalfsize;
+ bool sdio;
+ bool st_clkdiv;
+ bool blksz_datactrl16;
+};
+
+static struct variant_data variant_arm = {
+ .fifosize = 16 * 4,
+ .fifohalfsize = 8 * 4,
+ .datalength_bits = 16,
+};
+
+static struct variant_data variant_arm_extended_fifo = {
+ .fifosize = 128 * 4,
+ .fifohalfsize = 64 * 4,
+ .datalength_bits = 16,
+};
+
+static struct variant_data variant_u300 = {
+ .fifosize = 16 * 4,
+ .fifohalfsize = 8 * 4,
+ .clkreg_enable = MCI_ST_U300_HWFCEN,
+ .datalength_bits = 16,
+ .sdio = true,
+};
+
+static struct variant_data variant_ux500 = {
+ .fifosize = 30 * 4,
+ .fifohalfsize = 8 * 4,
+ .clkreg = MCI_CLK_ENABLE,
+ .clkreg_enable = MCI_ST_UX500_HWFCEN,
+ .datalength_bits = 24,
+ .sdio = true,
+ .st_clkdiv = true,
+};
+
+static struct variant_data variant_ux500v2 = {
+ .fifosize = 30 * 4,
+ .fifohalfsize = 8 * 4,
+ .clkreg = MCI_CLK_ENABLE,
+ .clkreg_enable = MCI_ST_UX500_HWFCEN,
+ .datalength_bits = 24,
+ .sdio = true,
+ .st_clkdiv = true,
+ .blksz_datactrl16 = true,
+};
+
+/*
+ * This must be called with host->lock held
+ */
+static void mmci_set_clkreg(struct mmci_host *host, unsigned int desired)
+{
+ struct variant_data *variant = host->variant;
+ u32 clk = variant->clkreg;
+
+ if (desired) {
+ if (desired >= host->mclk) {
+ clk = MCI_CLK_BYPASS;
+ if (variant->st_clkdiv)
+ clk |= MCI_ST_UX500_NEG_EDGE;
+ host->cclk = host->mclk;
+ } else if (variant->st_clkdiv) {
+ /*
+ * DB8500 TRM says f = mclk / (clkdiv + 2)
+ * => clkdiv = (mclk / f) - 2
+ * Round the divider up so we don't exceed the max
+ * frequency
+ */
+ clk = DIV_ROUND_UP(host->mclk, desired) - 2;
+ if (clk >= 256)
+ clk = 255;
+ host->cclk = host->mclk / (clk + 2);
+ } else {
+ /*
+ * PL180 TRM says f = mclk / (2 * (clkdiv + 1))
+ * => clkdiv = mclk / (2 * f) - 1
+ */
+ clk = host->mclk / (2 * desired) - 1;
+ if (clk >= 256)
+ clk = 255;
+ host->cclk = host->mclk / (2 * (clk + 1));
+ }
+
+ clk |= variant->clkreg_enable;
+ clk |= MCI_CLK_ENABLE;
+ /* This hasn't proven to be worthwhile */
+ /* clk |= MCI_CLK_PWRSAVE; */
+ }
+
+ if (host->mmc->ios.bus_width == MMC_BUS_WIDTH_4)
+ clk |= MCI_4BIT_BUS;
+ if (host->mmc->ios.bus_width == MMC_BUS_WIDTH_8)
+ clk |= MCI_ST_8BIT_BUS;
+
+ writel(clk, host->base + MMCICLOCK);
+}
+
+static void
+mmci_request_end(struct mmci_host *host, struct mmc_request *mrq)
+{
+ writel(0, host->base + MMCICOMMAND);
+
+ BUG_ON(host->data);
+
+ host->mrq = NULL;
+ host->cmd = NULL;
+
+ /*
+ * Need to drop the host lock here; mmc_request_done may call
+ * back into the driver...
+ */
+ spin_unlock(&host->lock);
+ mmc_request_done(host->mmc, mrq);
+ spin_lock(&host->lock);
+}
+
+static void mmci_set_mask1(struct mmci_host *host, unsigned int mask)
+{
+ void __iomem *base = host->base;
+
+ if (host->singleirq) {
+ unsigned int mask0 = readl(base + MMCIMASK0);
+
+ mask0 &= ~MCI_IRQ1MASK;
+ mask0 |= mask;
+
+ writel(mask0, base + MMCIMASK0);
+ }
+
+ writel(mask, base + MMCIMASK1);
+}
+
+static void mmci_stop_data(struct mmci_host *host)
+{
+ writel(0, host->base + MMCIDATACTRL);
+ mmci_set_mask1(host, 0);
+ host->data = NULL;
+}
+
+static void mmci_init_sg(struct mmci_host *host, struct mmc_data *data)
+{
+ unsigned int flags = SG_MITER_ATOMIC;
+
+ if (data->flags & MMC_DATA_READ)
+ flags |= SG_MITER_TO_SG;
+ else
+ flags |= SG_MITER_FROM_SG;
+
+ sg_miter_start(&host->sg_miter, data->sg, data->sg_len, flags);
+}
+
+/*
+ * All the DMA operation mode stuff goes inside this ifdef.
+ * This assumes that you have a generic DMA device interface,
+ * no custom DMA interfaces are supported.
+ */
+#ifdef CONFIG_DMA_ENGINE
+static void __devinit mmci_dma_setup(struct mmci_host *host)
+{
+ struct mmci_platform_data *plat = host->plat;
+ const char *rxname, *txname;
+ dma_cap_mask_t mask;
+
+ if (!plat || !plat->dma_filter) {
+ dev_info(mmc_dev(host->mmc), "no DMA platform data\n");
+ return;
+ }
+
+ /* initialize pre request cookie */
+ host->next_data.cookie = 1;
+
+ /* Try to acquire a generic DMA engine slave channel */
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+
+ /*
+ * If only an RX channel is specified, the driver will
+ * attempt to use it bidirectionally, however if it is
+ * is specified but cannot be located, DMA will be disabled.
+ */
+ if (plat->dma_rx_param) {
+ host->dma_rx_channel = dma_request_channel(mask,
+ plat->dma_filter,
+ plat->dma_rx_param);
+ /* E.g if no DMA hardware is present */
+ if (!host->dma_rx_channel)
+ dev_err(mmc_dev(host->mmc), "no RX DMA channel\n");
+ }
+
+ if (plat->dma_tx_param) {
+ host->dma_tx_channel = dma_request_channel(mask,
+ plat->dma_filter,
+ plat->dma_tx_param);
+ if (!host->dma_tx_channel)
+ dev_warn(mmc_dev(host->mmc), "no TX DMA channel\n");
+ } else {
+ host->dma_tx_channel = host->dma_rx_channel;
+ }
+
+ if (host->dma_rx_channel)
+ rxname = dma_chan_name(host->dma_rx_channel);
+ else
+ rxname = "none";
+
+ if (host->dma_tx_channel)
+ txname = dma_chan_name(host->dma_tx_channel);
+ else
+ txname = "none";
+
+ dev_info(mmc_dev(host->mmc), "DMA channels RX %s, TX %s\n",
+ rxname, txname);
+
+ /*
+ * Limit the maximum segment size in any SG entry according to
+ * the parameters of the DMA engine device.
+ */
+ if (host->dma_tx_channel) {
+ struct device *dev = host->dma_tx_channel->device->dev;
+ unsigned int max_seg_size = dma_get_max_seg_size(dev);
+
+ if (max_seg_size < host->mmc->max_seg_size)
+ host->mmc->max_seg_size = max_seg_size;
+ }
+ if (host->dma_rx_channel) {
+ struct device *dev = host->dma_rx_channel->device->dev;
+ unsigned int max_seg_size = dma_get_max_seg_size(dev);
+
+ if (max_seg_size < host->mmc->max_seg_size)
+ host->mmc->max_seg_size = max_seg_size;
+ }
+}
+
+/*
+ * This is used in __devinit or __devexit so inline it
+ * so it can be discarded.
+ */
+static inline void mmci_dma_release(struct mmci_host *host)
+{
+ struct mmci_platform_data *plat = host->plat;
+
+ if (host->dma_rx_channel)
+ dma_release_channel(host->dma_rx_channel);
+ if (host->dma_tx_channel && plat->dma_tx_param)
+ dma_release_channel(host->dma_tx_channel);
+ host->dma_rx_channel = host->dma_tx_channel = NULL;
+}
+
+static void mmci_dma_unmap(struct mmci_host *host, struct mmc_data *data)
+{
+ struct dma_chan *chan = host->dma_current;
+ enum dma_data_direction dir;
+ u32 status;
+ int i;
+
+ /* Wait up to 1ms for the DMA to complete */
+ for (i = 0; ; i++) {
+ status = readl(host->base + MMCISTATUS);
+ if (!(status & MCI_RXDATAAVLBLMASK) || i >= 100)
+ break;
+ udelay(10);
+ }
+
+ /*
+ * Check to see whether we still have some data left in the FIFO -
+ * this catches DMA controllers which are unable to monitor the
+ * DMALBREQ and DMALSREQ signals while allowing us to DMA to non-
+ * contiguous buffers. On TX, we'll get a FIFO underrun error.
+ */
+ if (status & MCI_RXDATAAVLBLMASK) {
+ dmaengine_terminate_all(chan);
+ if (!data->error)
+ data->error = -EIO;
+ }
+
+ if (data->flags & MMC_DATA_WRITE) {
+ dir = DMA_TO_DEVICE;
+ } else {
+ dir = DMA_FROM_DEVICE;
+ }
+
+ if (!data->host_cookie)
+ dma_unmap_sg(chan->device->dev, data->sg, data->sg_len, dir);
+
+ /*
+ * Use of DMA with scatter-gather is impossible.
+ * Give up with DMA and switch back to PIO mode.
+ */
+ if (status & MCI_RXDATAAVLBLMASK) {
+ dev_err(mmc_dev(host->mmc), "buggy DMA detected. Taking evasive action.\n");
+ mmci_dma_release(host);
+ }
+}
+
+static void mmci_dma_data_error(struct mmci_host *host)
+{
+ dev_err(mmc_dev(host->mmc), "error during DMA transfer!\n");
+ dmaengine_terminate_all(host->dma_current);
+}
+
+static int mmci_dma_prep_data(struct mmci_host *host, struct mmc_data *data,
+ struct mmci_host_next *next)
+{
+ struct variant_data *variant = host->variant;
+ struct dma_slave_config conf = {
+ .src_addr = host->phybase + MMCIFIFO,
+ .dst_addr = host->phybase + MMCIFIFO,
+ .src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
+ .dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
+ .src_maxburst = variant->fifohalfsize >> 2, /* # of words */
+ .dst_maxburst = variant->fifohalfsize >> 2, /* # of words */
+ };
+ struct dma_chan *chan;
+ struct dma_device *device;
+ struct dma_async_tx_descriptor *desc;
+ int nr_sg;
+
+ /* Check if next job is already prepared */
+ if (data->host_cookie && !next &&
+ host->dma_current && host->dma_desc_current)
+ return 0;
+
+ if (!next) {
+ host->dma_current = NULL;
+ host->dma_desc_current = NULL;
+ }
+
+ if (data->flags & MMC_DATA_READ) {
+ conf.direction = DMA_FROM_DEVICE;
+ chan = host->dma_rx_channel;
+ } else {
+ conf.direction = DMA_TO_DEVICE;
+ chan = host->dma_tx_channel;
+ }
+
+ /* If there's no DMA channel, fall back to PIO */
+ if (!chan)
+ return -EINVAL;
+
+ /* If less than or equal to the fifo size, don't bother with DMA */
+ if (data->blksz * data->blocks <= variant->fifosize)
+ return -EINVAL;
+
+ device = chan->device;
+ nr_sg = dma_map_sg(device->dev, data->sg, data->sg_len, conf.direction);
+ if (nr_sg == 0)
+ return -EINVAL;
+
+ dmaengine_slave_config(chan, &conf);
+ desc = device->device_prep_slave_sg(chan, data->sg, nr_sg,
+ conf.direction, DMA_CTRL_ACK);
+ if (!desc)
+ goto unmap_exit;
+
+ if (next) {
+ next->dma_chan = chan;
+ next->dma_desc = desc;
+ } else {
+ host->dma_current = chan;
+ host->dma_desc_current = desc;
+ }
+
+ return 0;
+
+ unmap_exit:
+ if (!next)
+ dmaengine_terminate_all(chan);
+ dma_unmap_sg(device->dev, data->sg, data->sg_len, conf.direction);
+ return -ENOMEM;
+}
+
+static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl)
+{
+ int ret;
+ struct mmc_data *data = host->data;
+
+ ret = mmci_dma_prep_data(host, host->data, NULL);
+ if (ret)
+ return ret;
+
+ /* Okay, go for it. */
+ dev_vdbg(mmc_dev(host->mmc),
+ "Submit MMCI DMA job, sglen %d blksz %04x blks %04x flags %08x\n",
+ data->sg_len, data->blksz, data->blocks, data->flags);
+ dmaengine_submit(host->dma_desc_current);
+ dma_async_issue_pending(host->dma_current);
+
+ datactrl |= MCI_DPSM_DMAENABLE;
+
+ /* Trigger the DMA transfer */
+ writel(datactrl, host->base + MMCIDATACTRL);
+
+ /*
+ * Let the MMCI say when the data is ended and it's time
+ * to fire next DMA request. When that happens, MMCI will
+ * call mmci_data_end()
+ */
+ writel(readl(host->base + MMCIMASK0) | MCI_DATAENDMASK,
+ host->base + MMCIMASK0);
+ return 0;
+}
+
+static void mmci_get_next_data(struct mmci_host *host, struct mmc_data *data)
+{
+ struct mmci_host_next *next = &host->next_data;
+
+ if (data->host_cookie && data->host_cookie != next->cookie) {
+ printk(KERN_WARNING "[%s] invalid cookie: data->host_cookie %d"
+ " host->next_data.cookie %d\n",
+ __func__, data->host_cookie, host->next_data.cookie);
+ data->host_cookie = 0;
+ }
+
+ if (!data->host_cookie)
+ return;
+
+ host->dma_desc_current = next->dma_desc;
+ host->dma_current = next->dma_chan;
+
+ next->dma_desc = NULL;
+ next->dma_chan = NULL;
+}
+
+static void mmci_pre_request(struct mmc_host *mmc, struct mmc_request *mrq,
+ bool is_first_req)
+{
+ struct mmci_host *host = mmc_priv(mmc);
+ struct mmc_data *data = mrq->data;
+ struct mmci_host_next *nd = &host->next_data;
+
+ if (!data)
+ return;
+
+ if (data->host_cookie) {
+ data->host_cookie = 0;
+ return;
+ }
+
+ /* if config for dma */
+ if (((data->flags & MMC_DATA_WRITE) && host->dma_tx_channel) ||
+ ((data->flags & MMC_DATA_READ) && host->dma_rx_channel)) {
+ if (mmci_dma_prep_data(host, data, nd))
+ data->host_cookie = 0;
+ else
+ data->host_cookie = ++nd->cookie < 0 ? 1 : nd->cookie;
+ }
+}
+
+static void mmci_post_request(struct mmc_host *mmc, struct mmc_request *mrq,
+ int err)
+{
+ struct mmci_host *host = mmc_priv(mmc);
+ struct mmc_data *data = mrq->data;
+ struct dma_chan *chan;
+ enum dma_data_direction dir;
+
+ if (!data)
+ return;
+
+ if (data->flags & MMC_DATA_READ) {
+ dir = DMA_FROM_DEVICE;
+ chan = host->dma_rx_channel;
+ } else {
+ dir = DMA_TO_DEVICE;
+ chan = host->dma_tx_channel;
+ }
+
+
+ /* if config for dma */
+ if (chan) {
+ if (err)
+ dmaengine_terminate_all(chan);
+ if (err || data->host_cookie)
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg,
+ data->sg_len, dir);
+ mrq->data->host_cookie = 0;
+ }
+}
+
+#else
+/* Blank functions if the DMA engine is not available */
+static void mmci_get_next_data(struct mmci_host *host, struct mmc_data *data)
+{
+}
+static inline void mmci_dma_setup(struct mmci_host *host)
+{
+}
+
+static inline void mmci_dma_release(struct mmci_host *host)
+{
+}
+
+static inline void mmci_dma_unmap(struct mmci_host *host, struct mmc_data *data)
+{
+}
+
+static inline void mmci_dma_data_error(struct mmci_host *host)
+{
+}
+
+static inline int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl)
+{
+ return -ENOSYS;
+}
+
+#define mmci_pre_request NULL
+#define mmci_post_request NULL
+
+#endif
+
+static void mmci_start_data(struct mmci_host *host, struct mmc_data *data)
+{
+ struct variant_data *variant = host->variant;
+ unsigned int datactrl, timeout, irqmask;
+ unsigned long long clks;
+ void __iomem *base;
+ int blksz_bits;
+
+ dev_dbg(mmc_dev(host->mmc), "blksz %04x blks %04x flags %08x\n",
+ data->blksz, data->blocks, data->flags);
+
+ host->data = data;
+ host->size = data->blksz * data->blocks;
+ data->bytes_xfered = 0;
+
+ clks = (unsigned long long)data->timeout_ns * host->cclk;
+ do_div(clks, 1000000000UL);
+
+ timeout = data->timeout_clks + (unsigned int)clks;
+
+ base = host->base;
+ writel(timeout, base + MMCIDATATIMER);
+ writel(host->size, base + MMCIDATALENGTH);
+
+ blksz_bits = ffs(data->blksz) - 1;
+ BUG_ON(1 << blksz_bits != data->blksz);
+
+ if (variant->blksz_datactrl16)
+ datactrl = MCI_DPSM_ENABLE | (data->blksz << 16);
+ else
+ datactrl = MCI_DPSM_ENABLE | blksz_bits << 4;
+
+ if (data->flags & MMC_DATA_READ)
+ datactrl |= MCI_DPSM_DIRECTION;
+
+ /*
+ * Attempt to use DMA operation mode, if this
+ * should fail, fall back to PIO mode
+ */
+ if (!mmci_dma_start_data(host, datactrl))
+ return;
+
+ /* IRQ mode, map the SG list for CPU reading/writing */
+ mmci_init_sg(host, data);
+
+ if (data->flags & MMC_DATA_READ) {
+ irqmask = MCI_RXFIFOHALFFULLMASK;
+
+ /*
+ * If we have less than the fifo 'half-full' threshold to
+ * transfer, trigger a PIO interrupt as soon as any data
+ * is available.
+ */
+ if (host->size < variant->fifohalfsize)
+ irqmask |= MCI_RXDATAAVLBLMASK;
+ } else {
+ /*
+ * We don't actually need to include "FIFO empty" here
+ * since its implicit in "FIFO half empty".
+ */
+ irqmask = MCI_TXFIFOHALFEMPTYMASK;
+ }
+
+ /* The ST Micro variants has a special bit to enable SDIO */
+ if (variant->sdio && host->mmc->card)
+ if (mmc_card_sdio(host->mmc->card))
+ datactrl |= MCI_ST_DPSM_SDIOEN;
+
+ writel(datactrl, base + MMCIDATACTRL);
+ writel(readl(base + MMCIMASK0) & ~MCI_DATAENDMASK, base + MMCIMASK0);
+ mmci_set_mask1(host, irqmask);
+}
+
+static void
+mmci_start_command(struct mmci_host *host, struct mmc_command *cmd, u32 c)
+{
+ void __iomem *base = host->base;
+
+ dev_dbg(mmc_dev(host->mmc), "op %02x arg %08x flags %08x\n",
+ cmd->opcode, cmd->arg, cmd->flags);
+
+ if (readl(base + MMCICOMMAND) & MCI_CPSM_ENABLE) {
+ writel(0, base + MMCICOMMAND);
+ udelay(1);
+ }
+
+ c |= cmd->opcode | MCI_CPSM_ENABLE;
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ if (cmd->flags & MMC_RSP_136)
+ c |= MCI_CPSM_LONGRSP;
+ c |= MCI_CPSM_RESPONSE;
+ }
+ if (/*interrupt*/0)
+ c |= MCI_CPSM_INTERRUPT;
+
+ host->cmd = cmd;
+
+ writel(cmd->arg, base + MMCIARGUMENT);
+ writel(c, base + MMCICOMMAND);
+}
+
+static void
+mmci_data_irq(struct mmci_host *host, struct mmc_data *data,
+ unsigned int status)
+{
+ /* First check for errors */
+ if (status & (MCI_DATACRCFAIL|MCI_DATATIMEOUT|MCI_STARTBITERR|
+ MCI_TXUNDERRUN|MCI_RXOVERRUN)) {
+ u32 remain, success;
+
+ /* Terminate the DMA transfer */
+ if (dma_inprogress(host))
+ mmci_dma_data_error(host);
+
+ /*
+ * Calculate how far we are into the transfer. Note that
+ * the data counter gives the number of bytes transferred
+ * on the MMC bus, not on the host side. On reads, this
+ * can be as much as a FIFO-worth of data ahead. This
+ * matters for FIFO overruns only.
+ */
+ remain = readl(host->base + MMCIDATACNT);
+ success = data->blksz * data->blocks - remain;
+
+ dev_dbg(mmc_dev(host->mmc), "MCI ERROR IRQ, status 0x%08x at 0x%08x\n",
+ status, success);
+ if (status & MCI_DATACRCFAIL) {
+ /* Last block was not successful */
+ success -= 1;
+ data->error = -EILSEQ;
+ } else if (status & MCI_DATATIMEOUT) {
+ data->error = -ETIMEDOUT;
+ } else if (status & MCI_STARTBITERR) {
+ data->error = -ECOMM;
+ } else if (status & MCI_TXUNDERRUN) {
+ data->error = -EIO;
+ } else if (status & MCI_RXOVERRUN) {
+ if (success > host->variant->fifosize)
+ success -= host->variant->fifosize;
+ else
+ success = 0;
+ data->error = -EIO;
+ }
+ data->bytes_xfered = round_down(success, data->blksz);
+ }
+
+ if (status & MCI_DATABLOCKEND)
+ dev_err(mmc_dev(host->mmc), "stray MCI_DATABLOCKEND interrupt\n");
+
+ if (status & MCI_DATAEND || data->error) {
+ if (dma_inprogress(host))
+ mmci_dma_unmap(host, data);
+ mmci_stop_data(host);
+
+ if (!data->error)
+ /* The error clause is handled above, success! */
+ data->bytes_xfered = data->blksz * data->blocks;
+
+ if (!data->stop) {
+ mmci_request_end(host, data->mrq);
+ } else {
+ mmci_start_command(host, data->stop, 0);
+ }
+ }
+}
+
+static void
+mmci_cmd_irq(struct mmci_host *host, struct mmc_command *cmd,
+ unsigned int status)
+{
+ void __iomem *base = host->base;
+
+ host->cmd = NULL;
+
+ if (status & MCI_CMDTIMEOUT) {
+ cmd->error = -ETIMEDOUT;
+ } else if (status & MCI_CMDCRCFAIL && cmd->flags & MMC_RSP_CRC) {
+ cmd->error = -EILSEQ;
+ } else {
+ cmd->resp[0] = readl(base + MMCIRESPONSE0);
+ cmd->resp[1] = readl(base + MMCIRESPONSE1);
+ cmd->resp[2] = readl(base + MMCIRESPONSE2);
+ cmd->resp[3] = readl(base + MMCIRESPONSE3);
+ }
+
+ if (!cmd->data || cmd->error) {
+ if (host->data) {
+ /* Terminate the DMA transfer */
+ if (dma_inprogress(host))
+ mmci_dma_data_error(host);
+ mmci_stop_data(host);
+ }
+ mmci_request_end(host, cmd->mrq);
+ } else if (!(cmd->data->flags & MMC_DATA_READ)) {
+ mmci_start_data(host, cmd->data);
+ }
+}
+
+static int mmci_pio_read(struct mmci_host *host, char *buffer, unsigned int remain)
+{
+ void __iomem *base = host->base;
+ char *ptr = buffer;
+ u32 status;
+ int host_remain = host->size;
+
+ do {
+ int count = host_remain - (readl(base + MMCIFIFOCNT) << 2);
+
+ if (count > remain)
+ count = remain;
+
+ if (count <= 0)
+ break;
+
+ readsl(base + MMCIFIFO, ptr, count >> 2);
+
+ ptr += count;
+ remain -= count;
+ host_remain -= count;
+
+ if (remain == 0)
+ break;
+
+ status = readl(base + MMCISTATUS);
+ } while (status & MCI_RXDATAAVLBL);
+
+ return ptr - buffer;
+}
+
+static int mmci_pio_write(struct mmci_host *host, char *buffer, unsigned int remain, u32 status)
+{
+ struct variant_data *variant = host->variant;
+ void __iomem *base = host->base;
+ char *ptr = buffer;
+
+ do {
+ unsigned int count, maxcnt;
+
+ maxcnt = status & MCI_TXFIFOEMPTY ?
+ variant->fifosize : variant->fifohalfsize;
+ count = min(remain, maxcnt);
+
+ /*
+ * The ST Micro variant for SDIO transfer sizes
+ * less then 8 bytes should have clock H/W flow
+ * control disabled.
+ */
+ if (variant->sdio &&
+ mmc_card_sdio(host->mmc->card)) {
+ if (count < 8)
+ writel(readl(host->base + MMCICLOCK) &
+ ~variant->clkreg_enable,
+ host->base + MMCICLOCK);
+ else
+ writel(readl(host->base + MMCICLOCK) |
+ variant->clkreg_enable,
+ host->base + MMCICLOCK);
+ }
+
+ /*
+ * SDIO especially may want to send something that is
+ * not divisible by 4 (as opposed to card sectors
+ * etc), and the FIFO only accept full 32-bit writes.
+ * So compensate by adding +3 on the count, a single
+ * byte become a 32bit write, 7 bytes will be two
+ * 32bit writes etc.
+ */
+ writesl(base + MMCIFIFO, ptr, (count + 3) >> 2);
+
+ ptr += count;
+ remain -= count;
+
+ if (remain == 0)
+ break;
+
+ status = readl(base + MMCISTATUS);
+ } while (status & MCI_TXFIFOHALFEMPTY);
+
+ return ptr - buffer;
+}
+
+/*
+ * PIO data transfer IRQ handler.
+ */
+static irqreturn_t mmci_pio_irq(int irq, void *dev_id)
+{
+ struct mmci_host *host = dev_id;
+ struct sg_mapping_iter *sg_miter = &host->sg_miter;
+ struct variant_data *variant = host->variant;
+ void __iomem *base = host->base;
+ unsigned long flags;
+ u32 status;
+
+ status = readl(base + MMCISTATUS);
+
+ dev_dbg(mmc_dev(host->mmc), "irq1 (pio) %08x\n", status);
+
+ local_irq_save(flags);
+
+ do {
+ unsigned int remain, len;
+ char *buffer;
+
+ /*
+ * For write, we only need to test the half-empty flag
+ * here - if the FIFO is completely empty, then by
+ * definition it is more than half empty.
+ *
+ * For read, check for data available.
+ */
+ if (!(status & (MCI_TXFIFOHALFEMPTY|MCI_RXDATAAVLBL)))
+ break;
+
+ if (!sg_miter_next(sg_miter))
+ break;
+
+ buffer = sg_miter->addr;
+ remain = sg_miter->length;
+
+ len = 0;
+ if (status & MCI_RXACTIVE)
+ len = mmci_pio_read(host, buffer, remain);
+ if (status & MCI_TXACTIVE)
+ len = mmci_pio_write(host, buffer, remain, status);
+
+ sg_miter->consumed = len;
+
+ host->size -= len;
+ remain -= len;
+
+ if (remain)
+ break;
+
+ status = readl(base + MMCISTATUS);
+ } while (1);
+
+ sg_miter_stop(sg_miter);
+
+ local_irq_restore(flags);
+
+ /*
+ * If we have less than the fifo 'half-full' threshold to transfer,
+ * trigger a PIO interrupt as soon as any data is available.
+ */
+ if (status & MCI_RXACTIVE && host->size < variant->fifohalfsize)
+ mmci_set_mask1(host, MCI_RXDATAAVLBLMASK);
+
+ /*
+ * If we run out of data, disable the data IRQs; this
+ * prevents a race where the FIFO becomes empty before
+ * the chip itself has disabled the data path, and
+ * stops us racing with our data end IRQ.
+ */
+ if (host->size == 0) {
+ mmci_set_mask1(host, 0);
+ writel(readl(base + MMCIMASK0) | MCI_DATAENDMASK, base + MMCIMASK0);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Handle completion of command and data transfers.
+ */
+static irqreturn_t mmci_irq(int irq, void *dev_id)
+{
+ struct mmci_host *host = dev_id;
+ u32 status;
+ int ret = 0;
+
+ spin_lock(&host->lock);
+
+ do {
+ struct mmc_command *cmd;
+ struct mmc_data *data;
+
+ status = readl(host->base + MMCISTATUS);
+
+ if (host->singleirq) {
+ if (status & readl(host->base + MMCIMASK1))
+ mmci_pio_irq(irq, dev_id);
+
+ status &= ~MCI_IRQ1MASK;
+ }
+
+ status &= readl(host->base + MMCIMASK0);
+ writel(status, host->base + MMCICLEAR);
+
+ dev_dbg(mmc_dev(host->mmc), "irq0 (data+cmd) %08x\n", status);
+
+ data = host->data;
+ if (status & (MCI_DATACRCFAIL|MCI_DATATIMEOUT|MCI_STARTBITERR|
+ MCI_TXUNDERRUN|MCI_RXOVERRUN|MCI_DATAEND|
+ MCI_DATABLOCKEND) && data)
+ mmci_data_irq(host, data, status);
+
+ cmd = host->cmd;
+ if (status & (MCI_CMDCRCFAIL|MCI_CMDTIMEOUT|MCI_CMDSENT|MCI_CMDRESPEND) && cmd)
+ mmci_cmd_irq(host, cmd, status);
+
+ ret = 1;
+ } while (status);
+
+ spin_unlock(&host->lock);
+
+ return IRQ_RETVAL(ret);
+}
+
+static void mmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct mmci_host *host = mmc_priv(mmc);
+ unsigned long flags;
+
+ WARN_ON(host->mrq != NULL);
+
+ if (mrq->data && !is_power_of_2(mrq->data->blksz)) {
+ dev_err(mmc_dev(mmc), "unsupported block size (%d bytes)\n",
+ mrq->data->blksz);
+ mrq->cmd->error = -EINVAL;
+ mmc_request_done(mmc, mrq);
+ return;
+ }
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ host->mrq = mrq;
+
+ if (mrq->data)
+ mmci_get_next_data(host, mrq->data);
+
+ if (mrq->data && mrq->data->flags & MMC_DATA_READ)
+ mmci_start_data(host, mrq->data);
+
+ mmci_start_command(host, mrq->cmd, 0);
+
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void mmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct mmci_host *host = mmc_priv(mmc);
+ u32 pwr = 0;
+ unsigned long flags;
+ int ret;
+
+ switch (ios->power_mode) {
+ case MMC_POWER_OFF:
+ if (host->vcc)
+ ret = mmc_regulator_set_ocr(mmc, host->vcc, 0);
+ break;
+ case MMC_POWER_UP:
+ if (host->vcc) {
+ ret = mmc_regulator_set_ocr(mmc, host->vcc, ios->vdd);
+ if (ret) {
+ dev_err(mmc_dev(mmc), "unable to set OCR\n");
+ /*
+ * The .set_ios() function in the mmc_host_ops
+ * struct return void, and failing to set the
+ * power should be rare so we print an error
+ * and return here.
+ */
+ return;
+ }
+ }
+ if (host->plat->vdd_handler)
+ pwr |= host->plat->vdd_handler(mmc_dev(mmc), ios->vdd,
+ ios->power_mode);
+ /* The ST version does not have this, fall through to POWER_ON */
+ if (host->hw_designer != AMBA_VENDOR_ST) {
+ pwr |= MCI_PWR_UP;
+ break;
+ }
+ case MMC_POWER_ON:
+ pwr |= MCI_PWR_ON;
+ break;
+ }
+
+ if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN) {
+ if (host->hw_designer != AMBA_VENDOR_ST)
+ pwr |= MCI_ROD;
+ else {
+ /*
+ * The ST Micro variant use the ROD bit for something
+ * else and only has OD (Open Drain).
+ */
+ pwr |= MCI_OD;
+ }
+ }
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ mmci_set_clkreg(host, ios->clock);
+
+ if (host->pwr != pwr) {
+ host->pwr = pwr;
+ writel(pwr, host->base + MMCIPOWER);
+ }
+
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static int mmci_get_ro(struct mmc_host *mmc)
+{
+ struct mmci_host *host = mmc_priv(mmc);
+
+ if (host->gpio_wp == -ENOSYS)
+ return -ENOSYS;
+
+ return gpio_get_value_cansleep(host->gpio_wp);
+}
+
+static int mmci_get_cd(struct mmc_host *mmc)
+{
+ struct mmci_host *host = mmc_priv(mmc);
+ struct mmci_platform_data *plat = host->plat;
+ unsigned int status;
+
+ if (host->gpio_cd == -ENOSYS) {
+ if (!plat->status)
+ return 1; /* Assume always present */
+
+ status = plat->status(mmc_dev(host->mmc));
+ } else
+ status = !!gpio_get_value_cansleep(host->gpio_cd)
+ ^ plat->cd_invert;
+
+ /*
+ * Use positive logic throughout - status is zero for no card,
+ * non-zero for card inserted.
+ */
+ return status;
+}
+
+static irqreturn_t mmci_cd_irq(int irq, void *dev_id)
+{
+ struct mmci_host *host = dev_id;
+
+ mmc_detect_change(host->mmc, msecs_to_jiffies(500));
+
+ return IRQ_HANDLED;
+}
+
+static const struct mmc_host_ops mmci_ops = {
+ .request = mmci_request,
+ .pre_req = mmci_pre_request,
+ .post_req = mmci_post_request,
+ .set_ios = mmci_set_ios,
+ .get_ro = mmci_get_ro,
+ .get_cd = mmci_get_cd,
+};
+
+static int __devinit mmci_probe(struct amba_device *dev,
+ const struct amba_id *id)
+{
+ struct mmci_platform_data *plat = dev->dev.platform_data;
+ struct variant_data *variant = id->data;
+ struct mmci_host *host;
+ struct mmc_host *mmc;
+ int ret;
+
+ /* must have platform data */
+ if (!plat) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = amba_request_regions(dev, DRIVER_NAME);
+ if (ret)
+ goto out;
+
+ mmc = mmc_alloc_host(sizeof(struct mmci_host), &dev->dev);
+ if (!mmc) {
+ ret = -ENOMEM;
+ goto rel_regions;
+ }
+
+ host = mmc_priv(mmc);
+ host->mmc = mmc;
+
+ host->gpio_wp = -ENOSYS;
+ host->gpio_cd = -ENOSYS;
+ host->gpio_cd_irq = -1;
+
+ host->hw_designer = amba_manf(dev);
+ host->hw_revision = amba_rev(dev);
+ dev_dbg(mmc_dev(mmc), "designer ID = 0x%02x\n", host->hw_designer);
+ dev_dbg(mmc_dev(mmc), "revision = 0x%01x\n", host->hw_revision);
+
+ host->clk = clk_get(&dev->dev, NULL);
+ if (IS_ERR(host->clk)) {
+ ret = PTR_ERR(host->clk);
+ host->clk = NULL;
+ goto host_free;
+ }
+
+ ret = clk_enable(host->clk);
+ if (ret)
+ goto clk_free;
+
+ host->plat = plat;
+ host->variant = variant;
+ host->mclk = clk_get_rate(host->clk);
+ /*
+ * According to the spec, mclk is max 100 MHz,
+ * so we try to adjust the clock down to this,
+ * (if possible).
+ */
+ if (host->mclk > 100000000) {
+ ret = clk_set_rate(host->clk, 100000000);
+ if (ret < 0)
+ goto clk_disable;
+ host->mclk = clk_get_rate(host->clk);
+ dev_dbg(mmc_dev(mmc), "eventual mclk rate: %u Hz\n",
+ host->mclk);
+ }
+ host->phybase = dev->res.start;
+ host->base = ioremap(dev->res.start, resource_size(&dev->res));
+ if (!host->base) {
+ ret = -ENOMEM;
+ goto clk_disable;
+ }
+
+ mmc->ops = &mmci_ops;
+ mmc->f_min = (host->mclk + 511) / 512;
+ /*
+ * If the platform data supplies a maximum operating
+ * frequency, this takes precedence. Else, we fall back
+ * to using the module parameter, which has a (low)
+ * default value in case it is not specified. Either
+ * value must not exceed the clock rate into the block,
+ * of course.
+ */
+ if (plat->f_max)
+ mmc->f_max = min(host->mclk, plat->f_max);
+ else
+ mmc->f_max = min(host->mclk, fmax);
+ dev_dbg(mmc_dev(mmc), "clocking block at %u Hz\n", mmc->f_max);
+
+#ifdef CONFIG_REGULATOR
+ /* If we're using the regulator framework, try to fetch a regulator */
+ host->vcc = regulator_get(&dev->dev, "vmmc");
+ if (IS_ERR(host->vcc))
+ host->vcc = NULL;
+ else {
+ int mask = mmc_regulator_get_ocrmask(host->vcc);
+
+ if (mask < 0)
+ dev_err(&dev->dev, "error getting OCR mask (%d)\n",
+ mask);
+ else {
+ host->mmc->ocr_avail = (u32) mask;
+ if (plat->ocr_mask)
+ dev_warn(&dev->dev,
+ "Provided ocr_mask/setpower will not be used "
+ "(using regulator instead)\n");
+ }
+ }
+#endif
+ /* Fall back to platform data if no regulator is found */
+ if (host->vcc == NULL)
+ mmc->ocr_avail = plat->ocr_mask;
+ mmc->caps = plat->capabilities;
+
+ /*
+ * We can do SGIO
+ */
+ mmc->max_segs = NR_SG;
+
+ /*
+ * Since only a certain number of bits are valid in the data length
+ * register, we must ensure that we don't exceed 2^num-1 bytes in a
+ * single request.
+ */
+ mmc->max_req_size = (1 << variant->datalength_bits) - 1;
+
+ /*
+ * Set the maximum segment size. Since we aren't doing DMA
+ * (yet) we are only limited by the data length register.
+ */
+ mmc->max_seg_size = mmc->max_req_size;
+
+ /*
+ * Block size can be up to 2048 bytes, but must be a power of two.
+ */
+ mmc->max_blk_size = 2048;
+
+ /*
+ * No limit on the number of blocks transferred.
+ */
+ mmc->max_blk_count = mmc->max_req_size;
+
+ spin_lock_init(&host->lock);
+
+ writel(0, host->base + MMCIMASK0);
+ writel(0, host->base + MMCIMASK1);
+ writel(0xfff, host->base + MMCICLEAR);
+
+ if (gpio_is_valid(plat->gpio_cd)) {
+ ret = gpio_request(plat->gpio_cd, DRIVER_NAME " (cd)");
+ if (ret == 0)
+ ret = gpio_direction_input(plat->gpio_cd);
+ if (ret == 0)
+ host->gpio_cd = plat->gpio_cd;
+ else if (ret != -ENOSYS)
+ goto err_gpio_cd;
+
+ /*
+ * A gpio pin that will detect cards when inserted and removed
+ * will most likely want to trigger on the edges if it is
+ * 0 when ejected and 1 when inserted (or mutatis mutandis
+ * for the inverted case) so we request triggers on both
+ * edges.
+ */
+ ret = request_any_context_irq(gpio_to_irq(plat->gpio_cd),
+ mmci_cd_irq,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ DRIVER_NAME " (cd)", host);
+ if (ret >= 0)
+ host->gpio_cd_irq = gpio_to_irq(plat->gpio_cd);
+ }
+ if (gpio_is_valid(plat->gpio_wp)) {
+ ret = gpio_request(plat->gpio_wp, DRIVER_NAME " (wp)");
+ if (ret == 0)
+ ret = gpio_direction_input(plat->gpio_wp);
+ if (ret == 0)
+ host->gpio_wp = plat->gpio_wp;
+ else if (ret != -ENOSYS)
+ goto err_gpio_wp;
+ }
+
+ if ((host->plat->status || host->gpio_cd != -ENOSYS)
+ && host->gpio_cd_irq < 0)
+ mmc->caps |= MMC_CAP_NEEDS_POLL;
+
+ ret = request_irq(dev->irq[0], mmci_irq, IRQF_SHARED, DRIVER_NAME " (cmd)", host);
+ if (ret)
+ goto unmap;
+
+ if (dev->irq[1] == NO_IRQ)
+ host->singleirq = true;
+ else {
+ ret = request_irq(dev->irq[1], mmci_pio_irq, IRQF_SHARED,
+ DRIVER_NAME " (pio)", host);
+ if (ret)
+ goto irq0_free;
+ }
+
+ writel(MCI_IRQENABLE, host->base + MMCIMASK0);
+
+ amba_set_drvdata(dev, mmc);
+
+ dev_info(&dev->dev, "%s: PL%03x manf %x rev%u at 0x%08llx irq %d,%d (pio)\n",
+ mmc_hostname(mmc), amba_part(dev), amba_manf(dev),
+ amba_rev(dev), (unsigned long long)dev->res.start,
+ dev->irq[0], dev->irq[1]);
+
+ mmci_dma_setup(host);
+
+ mmc_add_host(mmc);
+
+ return 0;
+
+ irq0_free:
+ free_irq(dev->irq[0], host);
+ unmap:
+ if (host->gpio_wp != -ENOSYS)
+ gpio_free(host->gpio_wp);
+ err_gpio_wp:
+ if (host->gpio_cd_irq >= 0)
+ free_irq(host->gpio_cd_irq, host);
+ if (host->gpio_cd != -ENOSYS)
+ gpio_free(host->gpio_cd);
+ err_gpio_cd:
+ iounmap(host->base);
+ clk_disable:
+ clk_disable(host->clk);
+ clk_free:
+ clk_put(host->clk);
+ host_free:
+ mmc_free_host(mmc);
+ rel_regions:
+ amba_release_regions(dev);
+ out:
+ return ret;
+}
+
+static int __devexit mmci_remove(struct amba_device *dev)
+{
+ struct mmc_host *mmc = amba_get_drvdata(dev);
+
+ amba_set_drvdata(dev, NULL);
+
+ if (mmc) {
+ struct mmci_host *host = mmc_priv(mmc);
+
+ mmc_remove_host(mmc);
+
+ writel(0, host->base + MMCIMASK0);
+ writel(0, host->base + MMCIMASK1);
+
+ writel(0, host->base + MMCICOMMAND);
+ writel(0, host->base + MMCIDATACTRL);
+
+ mmci_dma_release(host);
+ free_irq(dev->irq[0], host);
+ if (!host->singleirq)
+ free_irq(dev->irq[1], host);
+
+ if (host->gpio_wp != -ENOSYS)
+ gpio_free(host->gpio_wp);
+ if (host->gpio_cd_irq >= 0)
+ free_irq(host->gpio_cd_irq, host);
+ if (host->gpio_cd != -ENOSYS)
+ gpio_free(host->gpio_cd);
+
+ iounmap(host->base);
+ clk_disable(host->clk);
+ clk_put(host->clk);
+
+ if (host->vcc)
+ mmc_regulator_set_ocr(mmc, host->vcc, 0);
+ regulator_put(host->vcc);
+
+ mmc_free_host(mmc);
+
+ amba_release_regions(dev);
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int mmci_suspend(struct amba_device *dev, pm_message_t state)
+{
+ struct mmc_host *mmc = amba_get_drvdata(dev);
+ int ret = 0;
+
+ if (mmc) {
+ struct mmci_host *host = mmc_priv(mmc);
+
+ ret = mmc_suspend_host(mmc);
+ if (ret == 0)
+ writel(0, host->base + MMCIMASK0);
+ }
+
+ return ret;
+}
+
+static int mmci_resume(struct amba_device *dev)
+{
+ struct mmc_host *mmc = amba_get_drvdata(dev);
+ int ret = 0;
+
+ if (mmc) {
+ struct mmci_host *host = mmc_priv(mmc);
+
+ writel(MCI_IRQENABLE, host->base + MMCIMASK0);
+
+ ret = mmc_resume_host(mmc);
+ }
+
+ return ret;
+}
+#else
+#define mmci_suspend NULL
+#define mmci_resume NULL
+#endif
+
+static struct amba_id mmci_ids[] = {
+ {
+ .id = 0x00041180,
+ .mask = 0xff0fffff,
+ .data = &variant_arm,
+ },
+ {
+ .id = 0x01041180,
+ .mask = 0xff0fffff,
+ .data = &variant_arm_extended_fifo,
+ },
+ {
+ .id = 0x00041181,
+ .mask = 0x000fffff,
+ .data = &variant_arm,
+ },
+ /* ST Micro variants */
+ {
+ .id = 0x00180180,
+ .mask = 0x00ffffff,
+ .data = &variant_u300,
+ },
+ {
+ .id = 0x00280180,
+ .mask = 0x00ffffff,
+ .data = &variant_u300,
+ },
+ {
+ .id = 0x00480180,
+ .mask = 0xf0ffffff,
+ .data = &variant_ux500,
+ },
+ {
+ .id = 0x10480180,
+ .mask = 0xf0ffffff,
+ .data = &variant_ux500v2,
+ },
+ { 0, 0 },
+};
+
+static struct amba_driver mmci_driver = {
+ .drv = {
+ .name = DRIVER_NAME,
+ },
+ .probe = mmci_probe,
+ .remove = __devexit_p(mmci_remove),
+ .suspend = mmci_suspend,
+ .resume = mmci_resume,
+ .id_table = mmci_ids,
+};
+
+static int __init mmci_init(void)
+{
+ return amba_driver_register(&mmci_driver);
+}
+
+static void __exit mmci_exit(void)
+{
+ amba_driver_unregister(&mmci_driver);
+}
+
+module_init(mmci_init);
+module_exit(mmci_exit);
+module_param(fmax, uint, 0444);
+
+MODULE_DESCRIPTION("ARM PrimeCell PL180/181 Multimedia Card Interface driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mmc/host/mmci.h b/drivers/mmc/host/mmci.h
new file mode 100644
index 00000000..79e4143a
--- /dev/null
+++ b/drivers/mmc/host/mmci.h
@@ -0,0 +1,220 @@
+/*
+ * linux/drivers/mmc/host/mmci.h - ARM PrimeCell MMCI PL180/1 driver
+ *
+ * Copyright (C) 2003 Deep Blue Solutions, Ltd, 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.
+ */
+#define MMCIPOWER 0x000
+#define MCI_PWR_OFF 0x00
+#define MCI_PWR_UP 0x02
+#define MCI_PWR_ON 0x03
+#define MCI_OD (1 << 6)
+#define MCI_ROD (1 << 7)
+/*
+ * The ST Micro version does not have ROD and reuse the voltage registers
+ * for direction settings
+ */
+#define MCI_ST_DATA2DIREN (1 << 2)
+#define MCI_ST_CMDDIREN (1 << 3)
+#define MCI_ST_DATA0DIREN (1 << 4)
+#define MCI_ST_DATA31DIREN (1 << 5)
+#define MCI_ST_FBCLKEN (1 << 7)
+#define MCI_ST_DATA74DIREN (1 << 8)
+
+#define MMCICLOCK 0x004
+#define MCI_CLK_ENABLE (1 << 8)
+#define MCI_CLK_PWRSAVE (1 << 9)
+#define MCI_CLK_BYPASS (1 << 10)
+#define MCI_4BIT_BUS (1 << 11)
+/*
+ * 8bit wide buses, hardware flow contronl, negative edges and clock inversion
+ * supported in ST Micro U300 and Ux500 versions
+ */
+#define MCI_ST_8BIT_BUS (1 << 12)
+#define MCI_ST_U300_HWFCEN (1 << 13)
+#define MCI_ST_UX500_NEG_EDGE (1 << 13)
+#define MCI_ST_UX500_HWFCEN (1 << 14)
+#define MCI_ST_UX500_CLK_INV (1 << 15)
+
+#define MMCIARGUMENT 0x008
+#define MMCICOMMAND 0x00c
+#define MCI_CPSM_RESPONSE (1 << 6)
+#define MCI_CPSM_LONGRSP (1 << 7)
+#define MCI_CPSM_INTERRUPT (1 << 8)
+#define MCI_CPSM_PENDING (1 << 9)
+#define MCI_CPSM_ENABLE (1 << 10)
+#define MCI_SDIO_SUSP (1 << 11)
+#define MCI_ENCMD_COMPL (1 << 12)
+#define MCI_NIEN (1 << 13)
+#define MCI_CE_ATACMD (1 << 14)
+
+#define MMCIRESPCMD 0x010
+#define MMCIRESPONSE0 0x014
+#define MMCIRESPONSE1 0x018
+#define MMCIRESPONSE2 0x01c
+#define MMCIRESPONSE3 0x020
+#define MMCIDATATIMER 0x024
+#define MMCIDATALENGTH 0x028
+#define MMCIDATACTRL 0x02c
+#define MCI_DPSM_ENABLE (1 << 0)
+#define MCI_DPSM_DIRECTION (1 << 1)
+#define MCI_DPSM_MODE (1 << 2)
+#define MCI_DPSM_DMAENABLE (1 << 3)
+#define MCI_DPSM_BLOCKSIZE (1 << 4)
+/* Control register extensions in the ST Micro U300 and Ux500 versions */
+#define MCI_ST_DPSM_RWSTART (1 << 8)
+#define MCI_ST_DPSM_RWSTOP (1 << 9)
+#define MCI_ST_DPSM_RWMOD (1 << 10)
+#define MCI_ST_DPSM_SDIOEN (1 << 11)
+/* Control register extensions in the ST Micro Ux500 versions */
+#define MCI_ST_DPSM_DMAREQCTL (1 << 12)
+#define MCI_ST_DPSM_DBOOTMODEEN (1 << 13)
+#define MCI_ST_DPSM_BUSYMODE (1 << 14)
+#define MCI_ST_DPSM_DDRMODE (1 << 15)
+
+#define MMCIDATACNT 0x030
+#define MMCISTATUS 0x034
+#define MCI_CMDCRCFAIL (1 << 0)
+#define MCI_DATACRCFAIL (1 << 1)
+#define MCI_CMDTIMEOUT (1 << 2)
+#define MCI_DATATIMEOUT (1 << 3)
+#define MCI_TXUNDERRUN (1 << 4)
+#define MCI_RXOVERRUN (1 << 5)
+#define MCI_CMDRESPEND (1 << 6)
+#define MCI_CMDSENT (1 << 7)
+#define MCI_DATAEND (1 << 8)
+#define MCI_STARTBITERR (1 << 9)
+#define MCI_DATABLOCKEND (1 << 10)
+#define MCI_CMDACTIVE (1 << 11)
+#define MCI_TXACTIVE (1 << 12)
+#define MCI_RXACTIVE (1 << 13)
+#define MCI_TXFIFOHALFEMPTY (1 << 14)
+#define MCI_RXFIFOHALFFULL (1 << 15)
+#define MCI_TXFIFOFULL (1 << 16)
+#define MCI_RXFIFOFULL (1 << 17)
+#define MCI_TXFIFOEMPTY (1 << 18)
+#define MCI_RXFIFOEMPTY (1 << 19)
+#define MCI_TXDATAAVLBL (1 << 20)
+#define MCI_RXDATAAVLBL (1 << 21)
+/* Extended status bits for the ST Micro variants */
+#define MCI_ST_SDIOIT (1 << 22)
+#define MCI_ST_CEATAEND (1 << 23)
+
+#define MMCICLEAR 0x038
+#define MCI_CMDCRCFAILCLR (1 << 0)
+#define MCI_DATACRCFAILCLR (1 << 1)
+#define MCI_CMDTIMEOUTCLR (1 << 2)
+#define MCI_DATATIMEOUTCLR (1 << 3)
+#define MCI_TXUNDERRUNCLR (1 << 4)
+#define MCI_RXOVERRUNCLR (1 << 5)
+#define MCI_CMDRESPENDCLR (1 << 6)
+#define MCI_CMDSENTCLR (1 << 7)
+#define MCI_DATAENDCLR (1 << 8)
+#define MCI_STARTBITERRCLR (1 << 9)
+#define MCI_DATABLOCKENDCLR (1 << 10)
+/* Extended status bits for the ST Micro variants */
+#define MCI_ST_SDIOITC (1 << 22)
+#define MCI_ST_CEATAENDC (1 << 23)
+
+#define MMCIMASK0 0x03c
+#define MCI_CMDCRCFAILMASK (1 << 0)
+#define MCI_DATACRCFAILMASK (1 << 1)
+#define MCI_CMDTIMEOUTMASK (1 << 2)
+#define MCI_DATATIMEOUTMASK (1 << 3)
+#define MCI_TXUNDERRUNMASK (1 << 4)
+#define MCI_RXOVERRUNMASK (1 << 5)
+#define MCI_CMDRESPENDMASK (1 << 6)
+#define MCI_CMDSENTMASK (1 << 7)
+#define MCI_DATAENDMASK (1 << 8)
+#define MCI_STARTBITERRMASK (1 << 9)
+#define MCI_DATABLOCKENDMASK (1 << 10)
+#define MCI_CMDACTIVEMASK (1 << 11)
+#define MCI_TXACTIVEMASK (1 << 12)
+#define MCI_RXACTIVEMASK (1 << 13)
+#define MCI_TXFIFOHALFEMPTYMASK (1 << 14)
+#define MCI_RXFIFOHALFFULLMASK (1 << 15)
+#define MCI_TXFIFOFULLMASK (1 << 16)
+#define MCI_RXFIFOFULLMASK (1 << 17)
+#define MCI_TXFIFOEMPTYMASK (1 << 18)
+#define MCI_RXFIFOEMPTYMASK (1 << 19)
+#define MCI_TXDATAAVLBLMASK (1 << 20)
+#define MCI_RXDATAAVLBLMASK (1 << 21)
+/* Extended status bits for the ST Micro variants */
+#define MCI_ST_SDIOITMASK (1 << 22)
+#define MCI_ST_CEATAENDMASK (1 << 23)
+
+#define MMCIMASK1 0x040
+#define MMCIFIFOCNT 0x048
+#define MMCIFIFO 0x080 /* to 0x0bc */
+
+#define MCI_IRQENABLE \
+ (MCI_CMDCRCFAILMASK|MCI_DATACRCFAILMASK|MCI_CMDTIMEOUTMASK| \
+ MCI_DATATIMEOUTMASK|MCI_TXUNDERRUNMASK|MCI_RXOVERRUNMASK| \
+ MCI_CMDRESPENDMASK|MCI_CMDSENTMASK|MCI_STARTBITERRMASK)
+
+/* These interrupts are directed to IRQ1 when two IRQ lines are available */
+#define MCI_IRQ1MASK \
+ (MCI_RXFIFOHALFFULLMASK | MCI_RXDATAAVLBLMASK | \
+ MCI_TXFIFOHALFEMPTYMASK)
+
+#define NR_SG 16
+
+struct clk;
+struct variant_data;
+struct dma_chan;
+
+struct mmci_host_next {
+ struct dma_async_tx_descriptor *dma_desc;
+ struct dma_chan *dma_chan;
+ s32 cookie;
+};
+
+struct mmci_host {
+ phys_addr_t phybase;
+ void __iomem *base;
+ struct mmc_request *mrq;
+ struct mmc_command *cmd;
+ struct mmc_data *data;
+ struct mmc_host *mmc;
+ struct clk *clk;
+ int gpio_cd;
+ int gpio_wp;
+ int gpio_cd_irq;
+ bool singleirq;
+
+ spinlock_t lock;
+
+ unsigned int mclk;
+ unsigned int cclk;
+ u32 pwr;
+ struct mmci_platform_data *plat;
+ struct variant_data *variant;
+
+ u8 hw_designer;
+ u8 hw_revision:4;
+
+ struct timer_list timer;
+ unsigned int oldstat;
+
+ /* pio stuff */
+ struct sg_mapping_iter sg_miter;
+ unsigned int size;
+ struct regulator *vcc;
+
+#ifdef CONFIG_DMA_ENGINE
+ /* DMA stuff */
+ struct dma_chan *dma_current;
+ struct dma_chan *dma_rx_channel;
+ struct dma_chan *dma_tx_channel;
+ struct dma_async_tx_descriptor *dma_desc_current;
+ struct mmci_host_next next_data;
+
+#define dma_inprogress(host) ((host)->dma_current)
+#else
+#define dma_inprogress(host) (0)
+#endif
+};
+
diff --git a/drivers/mmc/host/msm_sdcc.c b/drivers/mmc/host/msm_sdcc.c
new file mode 100644
index 00000000..a4c865a5
--- /dev/null
+++ b/drivers/mmc/host/msm_sdcc.c
@@ -0,0 +1,1467 @@
+/*
+ * linux/drivers/mmc/host/msm_sdcc.c - Qualcomm MSM 7X00A SDCC Driver
+ *
+ * Copyright (C) 2007 Google Inc,
+ * Copyright (C) 2003 Deep Blue Solutions, Ltd, All Rights Reserved.
+ * Copyright (C) 2009, Code Aurora Forum. 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.
+ *
+ * Based on mmci.c
+ *
+ * Author: San Mehat (san@android.com)
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/highmem.h>
+#include <linux/log2.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/sdio.h>
+#include <linux/clk.h>
+#include <linux/scatterlist.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/debugfs.h>
+#include <linux/io.h>
+#include <linux/memory.h>
+#include <linux/gfp.h>
+#include <linux/gpio.h>
+
+#include <asm/cacheflush.h>
+#include <asm/div64.h>
+#include <asm/sizes.h>
+
+#include <mach/mmc.h>
+#include <mach/msm_iomap.h>
+#include <mach/dma.h>
+#include <mach/clk.h>
+
+#include "msm_sdcc.h"
+
+#define DRIVER_NAME "msm-sdcc"
+
+#define BUSCLK_PWRSAVE 1
+#define BUSCLK_TIMEOUT (HZ)
+static unsigned int msmsdcc_fmin = 144000;
+static unsigned int msmsdcc_fmax = 50000000;
+static unsigned int msmsdcc_4bit = 1;
+static unsigned int msmsdcc_pwrsave = 1;
+static unsigned int msmsdcc_piopoll = 1;
+static unsigned int msmsdcc_sdioirq;
+
+#define PIO_SPINMAX 30
+#define CMD_SPINMAX 20
+
+
+static inline void
+msmsdcc_disable_clocks(struct msmsdcc_host *host, int deferr)
+{
+ WARN_ON(!host->clks_on);
+
+ BUG_ON(host->curr.mrq);
+
+ if (deferr) {
+ mod_timer(&host->busclk_timer, jiffies + BUSCLK_TIMEOUT);
+ } else {
+ del_timer_sync(&host->busclk_timer);
+ /* Need to check clks_on again in case the busclk
+ * timer fired
+ */
+ if (host->clks_on) {
+ clk_disable(host->clk);
+ clk_disable(host->pclk);
+ host->clks_on = 0;
+ }
+ }
+}
+
+static inline int
+msmsdcc_enable_clocks(struct msmsdcc_host *host)
+{
+ int rc;
+
+ del_timer_sync(&host->busclk_timer);
+
+ if (!host->clks_on) {
+ rc = clk_enable(host->pclk);
+ if (rc)
+ return rc;
+ rc = clk_enable(host->clk);
+ if (rc) {
+ clk_disable(host->pclk);
+ return rc;
+ }
+ udelay(1 + ((3 * USEC_PER_SEC) /
+ (host->clk_rate ? host->clk_rate : msmsdcc_fmin)));
+ host->clks_on = 1;
+ }
+ return 0;
+}
+
+static inline unsigned int
+msmsdcc_readl(struct msmsdcc_host *host, unsigned int reg)
+{
+ return readl(host->base + reg);
+}
+
+static inline void
+msmsdcc_writel(struct msmsdcc_host *host, u32 data, unsigned int reg)
+{
+ writel(data, host->base + reg);
+ /* 3 clk delay required! */
+ udelay(1 + ((3 * USEC_PER_SEC) /
+ (host->clk_rate ? host->clk_rate : msmsdcc_fmin)));
+}
+
+static void
+msmsdcc_start_command(struct msmsdcc_host *host, struct mmc_command *cmd,
+ u32 c);
+
+static void msmsdcc_reset_and_restore(struct msmsdcc_host *host)
+{
+ u32 mci_clk = 0;
+ u32 mci_mask0 = 0;
+ int ret = 0;
+
+ /* Save the controller state */
+ mci_clk = readl(host->base + MMCICLOCK);
+ mci_mask0 = readl(host->base + MMCIMASK0);
+
+ /* Reset the controller */
+ ret = clk_reset(host->clk, CLK_RESET_ASSERT);
+ if (ret)
+ pr_err("%s: Clock assert failed at %u Hz with err %d\n",
+ mmc_hostname(host->mmc), host->clk_rate, ret);
+
+ ret = clk_reset(host->clk, CLK_RESET_DEASSERT);
+ if (ret)
+ pr_err("%s: Clock deassert failed at %u Hz with err %d\n",
+ mmc_hostname(host->mmc), host->clk_rate, ret);
+
+ pr_info("%s: Controller has been re-initialiazed\n",
+ mmc_hostname(host->mmc));
+
+ /* Restore the contoller state */
+ writel(host->pwr, host->base + MMCIPOWER);
+ writel(mci_clk, host->base + MMCICLOCK);
+ writel(mci_mask0, host->base + MMCIMASK0);
+ ret = clk_set_rate(host->clk, host->clk_rate);
+ if (ret)
+ pr_err("%s: Failed to set clk rate %u Hz (%d)\n",
+ mmc_hostname(host->mmc), host->clk_rate, ret);
+}
+
+static void
+msmsdcc_request_end(struct msmsdcc_host *host, struct mmc_request *mrq)
+{
+ BUG_ON(host->curr.data);
+
+ host->curr.mrq = NULL;
+ host->curr.cmd = NULL;
+
+ if (mrq->data)
+ mrq->data->bytes_xfered = host->curr.data_xfered;
+ if (mrq->cmd->error == -ETIMEDOUT)
+ mdelay(5);
+
+#if BUSCLK_PWRSAVE
+ msmsdcc_disable_clocks(host, 1);
+#endif
+ /*
+ * Need to drop the host lock here; mmc_request_done may call
+ * back into the driver...
+ */
+ spin_unlock(&host->lock);
+ mmc_request_done(host->mmc, mrq);
+ spin_lock(&host->lock);
+}
+
+static void
+msmsdcc_stop_data(struct msmsdcc_host *host)
+{
+ host->curr.data = NULL;
+ host->curr.got_dataend = 0;
+}
+
+uint32_t msmsdcc_fifo_addr(struct msmsdcc_host *host)
+{
+ return host->memres->start + MMCIFIFO;
+}
+
+static inline void
+msmsdcc_start_command_exec(struct msmsdcc_host *host, u32 arg, u32 c) {
+ msmsdcc_writel(host, arg, MMCIARGUMENT);
+ msmsdcc_writel(host, c, MMCICOMMAND);
+}
+
+static void
+msmsdcc_dma_exec_func(struct msm_dmov_cmd *cmd)
+{
+ struct msmsdcc_host *host = (struct msmsdcc_host *)cmd->data;
+
+ msmsdcc_writel(host, host->cmd_timeout, MMCIDATATIMER);
+ msmsdcc_writel(host, (unsigned int)host->curr.xfer_size,
+ MMCIDATALENGTH);
+ msmsdcc_writel(host, host->cmd_pio_irqmask, MMCIMASK1);
+ msmsdcc_writel(host, host->cmd_datactrl, MMCIDATACTRL);
+
+ if (host->cmd_cmd) {
+ msmsdcc_start_command_exec(host,
+ (u32) host->cmd_cmd->arg,
+ (u32) host->cmd_c);
+ }
+ host->dma.active = 1;
+}
+
+static void
+msmsdcc_dma_complete_tlet(unsigned long data)
+{
+ struct msmsdcc_host *host = (struct msmsdcc_host *)data;
+ unsigned long flags;
+ struct mmc_request *mrq;
+ struct msm_dmov_errdata err;
+
+ spin_lock_irqsave(&host->lock, flags);
+ host->dma.active = 0;
+
+ err = host->dma.err;
+ mrq = host->curr.mrq;
+ BUG_ON(!mrq);
+ WARN_ON(!mrq->data);
+
+ if (!(host->dma.result & DMOV_RSLT_VALID)) {
+ pr_err("msmsdcc: Invalid DataMover result\n");
+ goto out;
+ }
+
+ if (host->dma.result & DMOV_RSLT_DONE) {
+ host->curr.data_xfered = host->curr.xfer_size;
+ } else {
+ /* Error or flush */
+ if (host->dma.result & DMOV_RSLT_ERROR)
+ pr_err("%s: DMA error (0x%.8x)\n",
+ mmc_hostname(host->mmc), host->dma.result);
+ if (host->dma.result & DMOV_RSLT_FLUSH)
+ pr_err("%s: DMA channel flushed (0x%.8x)\n",
+ mmc_hostname(host->mmc), host->dma.result);
+
+ pr_err("Flush data: %.8x %.8x %.8x %.8x %.8x %.8x\n",
+ err.flush[0], err.flush[1], err.flush[2],
+ err.flush[3], err.flush[4], err.flush[5]);
+
+ msmsdcc_reset_and_restore(host);
+ if (!mrq->data->error)
+ mrq->data->error = -EIO;
+ }
+ dma_unmap_sg(mmc_dev(host->mmc), host->dma.sg, host->dma.num_ents,
+ host->dma.dir);
+
+ host->dma.sg = NULL;
+ host->dma.busy = 0;
+
+ if (host->curr.got_dataend || mrq->data->error) {
+
+ /*
+ * If we've already gotten our DATAEND / DATABLKEND
+ * for this request, then complete it through here.
+ */
+ msmsdcc_stop_data(host);
+
+ if (!mrq->data->error)
+ host->curr.data_xfered = host->curr.xfer_size;
+ if (!mrq->data->stop || mrq->cmd->error) {
+ host->curr.mrq = NULL;
+ host->curr.cmd = NULL;
+ mrq->data->bytes_xfered = host->curr.data_xfered;
+
+ spin_unlock_irqrestore(&host->lock, flags);
+#if BUSCLK_PWRSAVE
+ msmsdcc_disable_clocks(host, 1);
+#endif
+ mmc_request_done(host->mmc, mrq);
+ return;
+ } else
+ msmsdcc_start_command(host, mrq->data->stop, 0);
+ }
+
+out:
+ spin_unlock_irqrestore(&host->lock, flags);
+ return;
+}
+
+static void
+msmsdcc_dma_complete_func(struct msm_dmov_cmd *cmd,
+ unsigned int result,
+ struct msm_dmov_errdata *err)
+{
+ struct msmsdcc_dma_data *dma_data =
+ container_of(cmd, struct msmsdcc_dma_data, hdr);
+ struct msmsdcc_host *host = dma_data->host;
+
+ dma_data->result = result;
+ if (err)
+ memcpy(&dma_data->err, err, sizeof(struct msm_dmov_errdata));
+
+ tasklet_schedule(&host->dma_tlet);
+}
+
+static int validate_dma(struct msmsdcc_host *host, struct mmc_data *data)
+{
+ if (host->dma.channel == -1)
+ return -ENOENT;
+
+ if ((data->blksz * data->blocks) < MCI_FIFOSIZE)
+ return -EINVAL;
+ if ((data->blksz * data->blocks) % MCI_FIFOSIZE)
+ return -EINVAL;
+ return 0;
+}
+
+static int msmsdcc_config_dma(struct msmsdcc_host *host, struct mmc_data *data)
+{
+ struct msmsdcc_nc_dmadata *nc;
+ dmov_box *box;
+ uint32_t rows;
+ uint32_t crci;
+ unsigned int n;
+ int i, rc;
+ struct scatterlist *sg = data->sg;
+
+ rc = validate_dma(host, data);
+ if (rc)
+ return rc;
+
+ host->dma.sg = data->sg;
+ host->dma.num_ents = data->sg_len;
+
+ BUG_ON(host->dma.num_ents > NR_SG); /* Prevent memory corruption */
+
+ nc = host->dma.nc;
+
+ switch (host->pdev_id) {
+ case 1:
+ crci = MSMSDCC_CRCI_SDC1;
+ break;
+ case 2:
+ crci = MSMSDCC_CRCI_SDC2;
+ break;
+ case 3:
+ crci = MSMSDCC_CRCI_SDC3;
+ break;
+ case 4:
+ crci = MSMSDCC_CRCI_SDC4;
+ break;
+ default:
+ host->dma.sg = NULL;
+ host->dma.num_ents = 0;
+ return -ENOENT;
+ }
+
+ if (data->flags & MMC_DATA_READ)
+ host->dma.dir = DMA_FROM_DEVICE;
+ else
+ host->dma.dir = DMA_TO_DEVICE;
+
+ host->curr.user_pages = 0;
+
+ box = &nc->cmd[0];
+
+ /* location of command block must be 64 bit aligned */
+ BUG_ON(host->dma.cmd_busaddr & 0x07);
+
+ nc->cmdptr = (host->dma.cmd_busaddr >> 3) | CMD_PTR_LP;
+ host->dma.hdr.cmdptr = DMOV_CMD_PTR_LIST |
+ DMOV_CMD_ADDR(host->dma.cmdptr_busaddr);
+ host->dma.hdr.complete_func = msmsdcc_dma_complete_func;
+
+ n = dma_map_sg(mmc_dev(host->mmc), host->dma.sg,
+ host->dma.num_ents, host->dma.dir);
+ if (n == 0) {
+ printk(KERN_ERR "%s: Unable to map in all sg elements\n",
+ mmc_hostname(host->mmc));
+ host->dma.sg = NULL;
+ host->dma.num_ents = 0;
+ return -ENOMEM;
+ }
+
+ for_each_sg(host->dma.sg, sg, n, i) {
+
+ box->cmd = CMD_MODE_BOX;
+
+ if (i == n - 1)
+ box->cmd |= CMD_LC;
+ rows = (sg_dma_len(sg) % MCI_FIFOSIZE) ?
+ (sg_dma_len(sg) / MCI_FIFOSIZE) + 1 :
+ (sg_dma_len(sg) / MCI_FIFOSIZE) ;
+
+ if (data->flags & MMC_DATA_READ) {
+ box->src_row_addr = msmsdcc_fifo_addr(host);
+ box->dst_row_addr = sg_dma_address(sg);
+
+ box->src_dst_len = (MCI_FIFOSIZE << 16) |
+ (MCI_FIFOSIZE);
+ box->row_offset = MCI_FIFOSIZE;
+
+ box->num_rows = rows * ((1 << 16) + 1);
+ box->cmd |= CMD_SRC_CRCI(crci);
+ } else {
+ box->src_row_addr = sg_dma_address(sg);
+ box->dst_row_addr = msmsdcc_fifo_addr(host);
+
+ box->src_dst_len = (MCI_FIFOSIZE << 16) |
+ (MCI_FIFOSIZE);
+ box->row_offset = (MCI_FIFOSIZE << 16);
+
+ box->num_rows = rows * ((1 << 16) + 1);
+ box->cmd |= CMD_DST_CRCI(crci);
+ }
+ box++;
+ }
+
+ return 0;
+}
+
+static int
+snoop_cccr_abort(struct mmc_command *cmd)
+{
+ if ((cmd->opcode == 52) &&
+ (cmd->arg & 0x80000000) &&
+ (((cmd->arg >> 9) & 0x1ffff) == SDIO_CCCR_ABORT))
+ return 1;
+ return 0;
+}
+
+static void
+msmsdcc_start_command_deferred(struct msmsdcc_host *host,
+ struct mmc_command *cmd, u32 *c)
+{
+ *c |= (cmd->opcode | MCI_CPSM_ENABLE);
+
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ if (cmd->flags & MMC_RSP_136)
+ *c |= MCI_CPSM_LONGRSP;
+ *c |= MCI_CPSM_RESPONSE;
+ }
+
+ if (/*interrupt*/0)
+ *c |= MCI_CPSM_INTERRUPT;
+
+ if ((((cmd->opcode == 17) || (cmd->opcode == 18)) ||
+ ((cmd->opcode == 24) || (cmd->opcode == 25))) ||
+ (cmd->opcode == 53))
+ *c |= MCI_CSPM_DATCMD;
+
+ if (host->prog_scan && (cmd->opcode == 12)) {
+ *c |= MCI_CPSM_PROGENA;
+ host->prog_enable = true;
+ }
+
+ if (cmd == cmd->mrq->stop)
+ *c |= MCI_CSPM_MCIABORT;
+
+ if (snoop_cccr_abort(cmd))
+ *c |= MCI_CSPM_MCIABORT;
+
+ if (host->curr.cmd != NULL) {
+ printk(KERN_ERR "%s: Overlapping command requests\n",
+ mmc_hostname(host->mmc));
+ }
+ host->curr.cmd = cmd;
+}
+
+static void
+msmsdcc_start_data(struct msmsdcc_host *host, struct mmc_data *data,
+ struct mmc_command *cmd, u32 c)
+{
+ unsigned int datactrl, timeout;
+ unsigned long long clks;
+ unsigned int pio_irqmask = 0;
+
+ host->curr.data = data;
+ host->curr.xfer_size = data->blksz * data->blocks;
+ host->curr.xfer_remain = host->curr.xfer_size;
+ host->curr.data_xfered = 0;
+ host->curr.got_dataend = 0;
+
+ memset(&host->pio, 0, sizeof(host->pio));
+
+ datactrl = MCI_DPSM_ENABLE | (data->blksz << 4);
+
+ if (!msmsdcc_config_dma(host, data))
+ datactrl |= MCI_DPSM_DMAENABLE;
+ else {
+ host->pio.sg = data->sg;
+ host->pio.sg_len = data->sg_len;
+ host->pio.sg_off = 0;
+
+ if (data->flags & MMC_DATA_READ) {
+ pio_irqmask = MCI_RXFIFOHALFFULLMASK;
+ if (host->curr.xfer_remain < MCI_FIFOSIZE)
+ pio_irqmask |= MCI_RXDATAAVLBLMASK;
+ } else
+ pio_irqmask = MCI_TXFIFOHALFEMPTYMASK;
+ }
+
+ if (data->flags & MMC_DATA_READ)
+ datactrl |= MCI_DPSM_DIRECTION;
+
+ clks = (unsigned long long)data->timeout_ns * host->clk_rate;
+ do_div(clks, NSEC_PER_SEC);
+ timeout = data->timeout_clks + (unsigned int)clks*2 ;
+
+ if (datactrl & MCI_DPSM_DMAENABLE) {
+ /* Save parameters for the exec function */
+ host->cmd_timeout = timeout;
+ host->cmd_pio_irqmask = pio_irqmask;
+ host->cmd_datactrl = datactrl;
+ host->cmd_cmd = cmd;
+
+ host->dma.hdr.execute_func = msmsdcc_dma_exec_func;
+ host->dma.hdr.data = (void *)host;
+ host->dma.busy = 1;
+
+ if (cmd) {
+ msmsdcc_start_command_deferred(host, cmd, &c);
+ host->cmd_c = c;
+ }
+ msm_dmov_enqueue_cmd(host->dma.channel, &host->dma.hdr);
+ if (data->flags & MMC_DATA_WRITE)
+ host->prog_scan = true;
+ } else {
+ msmsdcc_writel(host, timeout, MMCIDATATIMER);
+
+ msmsdcc_writel(host, host->curr.xfer_size, MMCIDATALENGTH);
+
+ msmsdcc_writel(host, pio_irqmask, MMCIMASK1);
+ msmsdcc_writel(host, datactrl, MMCIDATACTRL);
+
+ if (cmd) {
+ /* Daisy-chain the command if requested */
+ msmsdcc_start_command(host, cmd, c);
+ }
+ }
+}
+
+static void
+msmsdcc_start_command(struct msmsdcc_host *host, struct mmc_command *cmd, u32 c)
+{
+ if (cmd == cmd->mrq->stop)
+ c |= MCI_CSPM_MCIABORT;
+
+ host->stats.cmds++;
+
+ msmsdcc_start_command_deferred(host, cmd, &c);
+ msmsdcc_start_command_exec(host, cmd->arg, c);
+}
+
+static void
+msmsdcc_data_err(struct msmsdcc_host *host, struct mmc_data *data,
+ unsigned int status)
+{
+ if (status & MCI_DATACRCFAIL) {
+ pr_err("%s: Data CRC error\n", mmc_hostname(host->mmc));
+ pr_err("%s: opcode 0x%.8x\n", __func__,
+ data->mrq->cmd->opcode);
+ pr_err("%s: blksz %d, blocks %d\n", __func__,
+ data->blksz, data->blocks);
+ data->error = -EILSEQ;
+ } else if (status & MCI_DATATIMEOUT) {
+ pr_err("%s: Data timeout\n", mmc_hostname(host->mmc));
+ data->error = -ETIMEDOUT;
+ } else if (status & MCI_RXOVERRUN) {
+ pr_err("%s: RX overrun\n", mmc_hostname(host->mmc));
+ data->error = -EIO;
+ } else if (status & MCI_TXUNDERRUN) {
+ pr_err("%s: TX underrun\n", mmc_hostname(host->mmc));
+ data->error = -EIO;
+ } else {
+ pr_err("%s: Unknown error (0x%.8x)\n",
+ mmc_hostname(host->mmc), status);
+ data->error = -EIO;
+ }
+}
+
+
+static int
+msmsdcc_pio_read(struct msmsdcc_host *host, char *buffer, unsigned int remain)
+{
+ uint32_t *ptr = (uint32_t *) buffer;
+ int count = 0;
+
+ if (remain % 4)
+ remain = ((remain >> 2) + 1) << 2;
+
+ while (msmsdcc_readl(host, MMCISTATUS) & MCI_RXDATAAVLBL) {
+ *ptr = msmsdcc_readl(host, MMCIFIFO + (count % MCI_FIFOSIZE));
+ ptr++;
+ count += sizeof(uint32_t);
+
+ remain -= sizeof(uint32_t);
+ if (remain == 0)
+ break;
+ }
+ return count;
+}
+
+static int
+msmsdcc_pio_write(struct msmsdcc_host *host, char *buffer,
+ unsigned int remain, u32 status)
+{
+ void __iomem *base = host->base;
+ char *ptr = buffer;
+
+ do {
+ unsigned int count, maxcnt, sz;
+
+ maxcnt = status & MCI_TXFIFOEMPTY ? MCI_FIFOSIZE :
+ MCI_FIFOHALFSIZE;
+ count = min(remain, maxcnt);
+
+ sz = count % 4 ? (count >> 2) + 1 : (count >> 2);
+ writesl(base + MMCIFIFO, ptr, sz);
+ ptr += count;
+ remain -= count;
+
+ if (remain == 0)
+ break;
+
+ status = msmsdcc_readl(host, MMCISTATUS);
+ } while (status & MCI_TXFIFOHALFEMPTY);
+
+ return ptr - buffer;
+}
+
+static int
+msmsdcc_spin_on_status(struct msmsdcc_host *host, uint32_t mask, int maxspin)
+{
+ while (maxspin) {
+ if ((msmsdcc_readl(host, MMCISTATUS) & mask))
+ return 0;
+ udelay(1);
+ --maxspin;
+ }
+ return -ETIMEDOUT;
+}
+
+static irqreturn_t
+msmsdcc_pio_irq(int irq, void *dev_id)
+{
+ struct msmsdcc_host *host = dev_id;
+ uint32_t status;
+
+ status = msmsdcc_readl(host, MMCISTATUS);
+
+ do {
+ unsigned long flags;
+ unsigned int remain, len;
+ char *buffer;
+
+ if (!(status & (MCI_TXFIFOHALFEMPTY | MCI_RXDATAAVLBL))) {
+ if (host->curr.xfer_remain == 0 || !msmsdcc_piopoll)
+ break;
+
+ if (msmsdcc_spin_on_status(host,
+ (MCI_TXFIFOHALFEMPTY |
+ MCI_RXDATAAVLBL),
+ PIO_SPINMAX)) {
+ break;
+ }
+ }
+
+ /* Map the current scatter buffer */
+ local_irq_save(flags);
+ buffer = kmap_atomic(sg_page(host->pio.sg),
+ KM_BIO_SRC_IRQ) + host->pio.sg->offset;
+ buffer += host->pio.sg_off;
+ remain = host->pio.sg->length - host->pio.sg_off;
+ len = 0;
+ if (status & MCI_RXACTIVE)
+ len = msmsdcc_pio_read(host, buffer, remain);
+ if (status & MCI_TXACTIVE)
+ len = msmsdcc_pio_write(host, buffer, remain, status);
+
+ /* Unmap the buffer */
+ kunmap_atomic(buffer, KM_BIO_SRC_IRQ);
+ local_irq_restore(flags);
+
+ host->pio.sg_off += len;
+ host->curr.xfer_remain -= len;
+ host->curr.data_xfered += len;
+ remain -= len;
+
+ if (remain == 0) {
+ /* This sg page is full - do some housekeeping */
+ if (status & MCI_RXACTIVE && host->curr.user_pages)
+ flush_dcache_page(sg_page(host->pio.sg));
+
+ if (!--host->pio.sg_len) {
+ memset(&host->pio, 0, sizeof(host->pio));
+ break;
+ }
+
+ /* Advance to next sg */
+ host->pio.sg++;
+ host->pio.sg_off = 0;
+ }
+
+ status = msmsdcc_readl(host, MMCISTATUS);
+ } while (1);
+
+ if (status & MCI_RXACTIVE && host->curr.xfer_remain < MCI_FIFOSIZE)
+ msmsdcc_writel(host, MCI_RXDATAAVLBLMASK, MMCIMASK1);
+
+ if (!host->curr.xfer_remain)
+ msmsdcc_writel(host, 0, MMCIMASK1);
+
+ return IRQ_HANDLED;
+}
+
+static void msmsdcc_do_cmdirq(struct msmsdcc_host *host, uint32_t status)
+{
+ struct mmc_command *cmd = host->curr.cmd;
+
+ host->curr.cmd = NULL;
+ cmd->resp[0] = msmsdcc_readl(host, MMCIRESPONSE0);
+ cmd->resp[1] = msmsdcc_readl(host, MMCIRESPONSE1);
+ cmd->resp[2] = msmsdcc_readl(host, MMCIRESPONSE2);
+ cmd->resp[3] = msmsdcc_readl(host, MMCIRESPONSE3);
+
+ if (status & MCI_CMDTIMEOUT) {
+ cmd->error = -ETIMEDOUT;
+ } else if (status & MCI_CMDCRCFAIL &&
+ cmd->flags & MMC_RSP_CRC) {
+ pr_err("%s: Command CRC error\n", mmc_hostname(host->mmc));
+ cmd->error = -EILSEQ;
+ }
+
+ if (!cmd->data || cmd->error) {
+ if (host->curr.data && host->dma.sg)
+ msm_dmov_stop_cmd(host->dma.channel,
+ &host->dma.hdr, 0);
+ else if (host->curr.data) { /* Non DMA */
+ msmsdcc_reset_and_restore(host);
+ msmsdcc_stop_data(host);
+ msmsdcc_request_end(host, cmd->mrq);
+ } else { /* host->data == NULL */
+ if (!cmd->error && host->prog_enable) {
+ if (status & MCI_PROGDONE) {
+ host->prog_scan = false;
+ host->prog_enable = false;
+ msmsdcc_request_end(host, cmd->mrq);
+ } else {
+ host->curr.cmd = cmd;
+ }
+ } else {
+ if (host->prog_enable) {
+ host->prog_scan = false;
+ host->prog_enable = false;
+ }
+ msmsdcc_request_end(host, cmd->mrq);
+ }
+ }
+ } else if (cmd->data)
+ if (!(cmd->data->flags & MMC_DATA_READ))
+ msmsdcc_start_data(host, cmd->data,
+ NULL, 0);
+}
+
+static void
+msmsdcc_handle_irq_data(struct msmsdcc_host *host, u32 status,
+ void __iomem *base)
+{
+ struct mmc_data *data = host->curr.data;
+
+ if (status & (MCI_CMDSENT | MCI_CMDRESPEND | MCI_CMDCRCFAIL |
+ MCI_CMDTIMEOUT | MCI_PROGDONE) && host->curr.cmd) {
+ msmsdcc_do_cmdirq(host, status);
+ }
+
+ if (!data)
+ return;
+
+ /* Check for data errors */
+ if (status & (MCI_DATACRCFAIL | MCI_DATATIMEOUT |
+ MCI_TXUNDERRUN | MCI_RXOVERRUN)) {
+ msmsdcc_data_err(host, data, status);
+ host->curr.data_xfered = 0;
+ if (host->dma.sg)
+ msm_dmov_stop_cmd(host->dma.channel,
+ &host->dma.hdr, 0);
+ else {
+ msmsdcc_reset_and_restore(host);
+ if (host->curr.data)
+ msmsdcc_stop_data(host);
+ if (!data->stop)
+ msmsdcc_request_end(host, data->mrq);
+ else
+ msmsdcc_start_command(host, data->stop, 0);
+ }
+ }
+
+ /* Check for data done */
+ if (!host->curr.got_dataend && (status & MCI_DATAEND))
+ host->curr.got_dataend = 1;
+
+ /*
+ * If DMA is still in progress, we complete via the completion handler
+ */
+ if (host->curr.got_dataend && !host->dma.busy) {
+ /*
+ * There appears to be an issue in the controller where
+ * if you request a small block transfer (< fifo size),
+ * you may get your DATAEND/DATABLKEND irq without the
+ * PIO data irq.
+ *
+ * Check to see if there is still data to be read,
+ * and simulate a PIO irq.
+ */
+ if (readl(base + MMCISTATUS) & MCI_RXDATAAVLBL)
+ msmsdcc_pio_irq(1, host);
+
+ msmsdcc_stop_data(host);
+ if (!data->error)
+ host->curr.data_xfered = host->curr.xfer_size;
+
+ if (!data->stop)
+ msmsdcc_request_end(host, data->mrq);
+ else
+ msmsdcc_start_command(host, data->stop, 0);
+ }
+}
+
+static irqreturn_t
+msmsdcc_irq(int irq, void *dev_id)
+{
+ struct msmsdcc_host *host = dev_id;
+ void __iomem *base = host->base;
+ u32 status;
+ int ret = 0;
+ int cardint = 0;
+
+ spin_lock(&host->lock);
+
+ do {
+ status = msmsdcc_readl(host, MMCISTATUS);
+ status &= msmsdcc_readl(host, MMCIMASK0);
+ msmsdcc_writel(host, status, MMCICLEAR);
+
+ if (status & MCI_SDIOINTR)
+ status &= ~MCI_SDIOINTR;
+
+ if (!status)
+ break;
+
+ msmsdcc_handle_irq_data(host, status, base);
+
+ if (status & MCI_SDIOINTOPER) {
+ cardint = 1;
+ status &= ~MCI_SDIOINTOPER;
+ }
+ ret = 1;
+ } while (status);
+
+ spin_unlock(&host->lock);
+
+ /*
+ * We have to delay handling the card interrupt as it calls
+ * back into the driver.
+ */
+ if (cardint)
+ mmc_signal_sdio_irq(host->mmc);
+
+ return IRQ_RETVAL(ret);
+}
+
+static void
+msmsdcc_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct msmsdcc_host *host = mmc_priv(mmc);
+ unsigned long flags;
+
+ WARN_ON(host->curr.mrq != NULL);
+ WARN_ON(host->pwr == 0);
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ host->stats.reqs++;
+
+ if (host->eject) {
+ if (mrq->data && !(mrq->data->flags & MMC_DATA_READ)) {
+ mrq->cmd->error = 0;
+ mrq->data->bytes_xfered = mrq->data->blksz *
+ mrq->data->blocks;
+ } else
+ mrq->cmd->error = -ENOMEDIUM;
+
+ spin_unlock_irqrestore(&host->lock, flags);
+ mmc_request_done(mmc, mrq);
+ return;
+ }
+
+ msmsdcc_enable_clocks(host);
+
+ host->curr.mrq = mrq;
+
+ if (mrq->data && mrq->data->flags & MMC_DATA_READ)
+ /* Queue/read data, daisy-chain command when data starts */
+ msmsdcc_start_data(host, mrq->data, mrq->cmd, 0);
+ else
+ msmsdcc_start_command(host, mrq->cmd, 0);
+
+ if (host->cmdpoll && !msmsdcc_spin_on_status(host,
+ MCI_CMDRESPEND|MCI_CMDCRCFAIL|MCI_CMDTIMEOUT,
+ CMD_SPINMAX)) {
+ uint32_t status = msmsdcc_readl(host, MMCISTATUS);
+ msmsdcc_do_cmdirq(host, status);
+ msmsdcc_writel(host,
+ MCI_CMDRESPEND | MCI_CMDCRCFAIL | MCI_CMDTIMEOUT,
+ MMCICLEAR);
+ host->stats.cmdpoll_hits++;
+ } else {
+ host->stats.cmdpoll_misses++;
+ }
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void msmsdcc_setup_gpio(struct msmsdcc_host *host, bool enable)
+{
+ struct msm_mmc_gpio_data *curr;
+ int i, rc = 0;
+
+ if (!host->plat->gpio_data && host->gpio_config_status == enable)
+ return;
+
+ curr = host->plat->gpio_data;
+ for (i = 0; i < curr->size; i++) {
+ if (enable) {
+ rc = gpio_request(curr->gpio[i].no,
+ curr->gpio[i].name);
+ if (rc) {
+ pr_err("%s: gpio_request(%d, %s) failed %d\n",
+ mmc_hostname(host->mmc),
+ curr->gpio[i].no,
+ curr->gpio[i].name, rc);
+ goto free_gpios;
+ }
+ } else {
+ gpio_free(curr->gpio[i].no);
+ }
+ }
+ host->gpio_config_status = enable;
+ return;
+
+free_gpios:
+ for (; i >= 0; i--)
+ gpio_free(curr->gpio[i].no);
+}
+
+static void
+msmsdcc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct msmsdcc_host *host = mmc_priv(mmc);
+ u32 clk = 0, pwr = 0;
+ int rc;
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ msmsdcc_enable_clocks(host);
+
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ if (ios->clock) {
+ if (ios->clock != host->clk_rate) {
+ rc = clk_set_rate(host->clk, ios->clock);
+ if (rc < 0)
+ pr_err("%s: Error setting clock rate (%d)\n",
+ mmc_hostname(host->mmc), rc);
+ else
+ host->clk_rate = ios->clock;
+ }
+ clk |= MCI_CLK_ENABLE;
+ }
+
+ if (ios->bus_width == MMC_BUS_WIDTH_4)
+ clk |= (2 << 10); /* Set WIDEBUS */
+
+ if (ios->clock > 400000 && msmsdcc_pwrsave)
+ clk |= (1 << 9); /* PWRSAVE */
+
+ clk |= (1 << 12); /* FLOW_ENA */
+ clk |= (1 << 15); /* feedback clock */
+
+ if (host->plat->translate_vdd)
+ pwr |= host->plat->translate_vdd(mmc_dev(mmc), ios->vdd);
+
+ switch (ios->power_mode) {
+ case MMC_POWER_OFF:
+ msmsdcc_setup_gpio(host, false);
+ break;
+ case MMC_POWER_UP:
+ pwr |= MCI_PWR_UP;
+ msmsdcc_setup_gpio(host, true);
+ break;
+ case MMC_POWER_ON:
+ pwr |= MCI_PWR_ON;
+ break;
+ }
+
+ if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN)
+ pwr |= MCI_OD;
+
+ msmsdcc_writel(host, clk, MMCICLOCK);
+
+ if (host->pwr != pwr) {
+ host->pwr = pwr;
+ msmsdcc_writel(host, pwr, MMCIPOWER);
+ }
+#if BUSCLK_PWRSAVE
+ spin_lock_irqsave(&host->lock, flags);
+ msmsdcc_disable_clocks(host, 1);
+ spin_unlock_irqrestore(&host->lock, flags);
+#endif
+}
+
+static void msmsdcc_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+ struct msmsdcc_host *host = mmc_priv(mmc);
+ unsigned long flags;
+ u32 status;
+
+ spin_lock_irqsave(&host->lock, flags);
+ if (msmsdcc_sdioirq == 1) {
+ status = msmsdcc_readl(host, MMCIMASK0);
+ if (enable)
+ status |= MCI_SDIOINTOPERMASK;
+ else
+ status &= ~MCI_SDIOINTOPERMASK;
+ host->saved_irq0mask = status;
+ msmsdcc_writel(host, status, MMCIMASK0);
+ }
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static const struct mmc_host_ops msmsdcc_ops = {
+ .request = msmsdcc_request,
+ .set_ios = msmsdcc_set_ios,
+ .enable_sdio_irq = msmsdcc_enable_sdio_irq,
+};
+
+static void
+msmsdcc_check_status(unsigned long data)
+{
+ struct msmsdcc_host *host = (struct msmsdcc_host *)data;
+ unsigned int status;
+
+ if (!host->plat->status) {
+ mmc_detect_change(host->mmc, 0);
+ goto out;
+ }
+
+ status = host->plat->status(mmc_dev(host->mmc));
+ host->eject = !status;
+ if (status ^ host->oldstat) {
+ pr_info("%s: Slot status change detected (%d -> %d)\n",
+ mmc_hostname(host->mmc), host->oldstat, status);
+ if (status)
+ mmc_detect_change(host->mmc, (5 * HZ) / 2);
+ else
+ mmc_detect_change(host->mmc, 0);
+ }
+
+ host->oldstat = status;
+
+out:
+ if (host->timer.function)
+ mod_timer(&host->timer, jiffies + HZ);
+}
+
+static irqreturn_t
+msmsdcc_platform_status_irq(int irq, void *dev_id)
+{
+ struct msmsdcc_host *host = dev_id;
+
+ printk(KERN_DEBUG "%s: %d\n", __func__, irq);
+ msmsdcc_check_status((unsigned long) host);
+ return IRQ_HANDLED;
+}
+
+static void
+msmsdcc_status_notify_cb(int card_present, void *dev_id)
+{
+ struct msmsdcc_host *host = dev_id;
+
+ printk(KERN_DEBUG "%s: card_present %d\n", mmc_hostname(host->mmc),
+ card_present);
+ msmsdcc_check_status((unsigned long) host);
+}
+
+static void
+msmsdcc_busclk_expired(unsigned long _data)
+{
+ struct msmsdcc_host *host = (struct msmsdcc_host *) _data;
+
+ if (host->clks_on)
+ msmsdcc_disable_clocks(host, 0);
+}
+
+static int
+msmsdcc_init_dma(struct msmsdcc_host *host)
+{
+ memset(&host->dma, 0, sizeof(struct msmsdcc_dma_data));
+ host->dma.host = host;
+ host->dma.channel = -1;
+
+ if (!host->dmares)
+ return -ENODEV;
+
+ host->dma.nc = dma_alloc_coherent(NULL,
+ sizeof(struct msmsdcc_nc_dmadata),
+ &host->dma.nc_busaddr,
+ GFP_KERNEL);
+ if (host->dma.nc == NULL) {
+ pr_err("Unable to allocate DMA buffer\n");
+ return -ENOMEM;
+ }
+ memset(host->dma.nc, 0x00, sizeof(struct msmsdcc_nc_dmadata));
+ host->dma.cmd_busaddr = host->dma.nc_busaddr;
+ host->dma.cmdptr_busaddr = host->dma.nc_busaddr +
+ offsetof(struct msmsdcc_nc_dmadata, cmdptr);
+ host->dma.channel = host->dmares->start;
+
+ return 0;
+}
+
+static int
+msmsdcc_probe(struct platform_device *pdev)
+{
+ struct msm_mmc_platform_data *plat = pdev->dev.platform_data;
+ struct msmsdcc_host *host;
+ struct mmc_host *mmc;
+ struct resource *cmd_irqres = NULL;
+ struct resource *pio_irqres = NULL;
+ struct resource *stat_irqres = NULL;
+ struct resource *memres = NULL;
+ struct resource *dmares = NULL;
+ int ret;
+
+ /* must have platform data */
+ if (!plat) {
+ pr_err("%s: Platform data not available\n", __func__);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (pdev->id < 1 || pdev->id > 4)
+ return -EINVAL;
+
+ if (pdev->resource == NULL || pdev->num_resources < 2) {
+ pr_err("%s: Invalid resource\n", __func__);
+ return -ENXIO;
+ }
+
+ memres = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ cmd_irqres = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+ "cmd_irq");
+ pio_irqres = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+ "pio_irq");
+ stat_irqres = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+ "status_irq");
+
+ if (!cmd_irqres || !pio_irqres || !memres) {
+ pr_err("%s: Invalid resource\n", __func__);
+ return -ENXIO;
+ }
+
+ /*
+ * Setup our host structure
+ */
+
+ mmc = mmc_alloc_host(sizeof(struct msmsdcc_host), &pdev->dev);
+ if (!mmc) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ host = mmc_priv(mmc);
+ host->pdev_id = pdev->id;
+ host->plat = plat;
+ host->mmc = mmc;
+ host->curr.cmd = NULL;
+
+ host->cmdpoll = 1;
+
+ host->base = ioremap(memres->start, PAGE_SIZE);
+ if (!host->base) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ host->cmd_irqres = cmd_irqres;
+ host->pio_irqres = pio_irqres;
+ host->memres = memres;
+ host->dmares = dmares;
+ spin_lock_init(&host->lock);
+
+ tasklet_init(&host->dma_tlet, msmsdcc_dma_complete_tlet,
+ (unsigned long)host);
+
+ /*
+ * Setup DMA
+ */
+ msmsdcc_init_dma(host);
+
+ /* Get our clocks */
+ host->pclk = clk_get(&pdev->dev, "sdc_pclk");
+ if (IS_ERR(host->pclk)) {
+ ret = PTR_ERR(host->pclk);
+ goto host_free;
+ }
+
+ host->clk = clk_get(&pdev->dev, "sdc_clk");
+ if (IS_ERR(host->clk)) {
+ ret = PTR_ERR(host->clk);
+ goto pclk_put;
+ }
+
+ /* Enable clocks */
+ ret = msmsdcc_enable_clocks(host);
+ if (ret)
+ goto clk_put;
+
+ ret = clk_set_rate(host->clk, msmsdcc_fmin);
+ if (ret) {
+ pr_err("%s: Clock rate set failed (%d)\n", __func__, ret);
+ goto clk_disable;
+ }
+
+ host->pclk_rate = clk_get_rate(host->pclk);
+ host->clk_rate = clk_get_rate(host->clk);
+
+ /*
+ * Setup MMC host structure
+ */
+ mmc->ops = &msmsdcc_ops;
+ mmc->f_min = msmsdcc_fmin;
+ mmc->f_max = msmsdcc_fmax;
+ mmc->ocr_avail = plat->ocr_mask;
+
+ if (msmsdcc_4bit)
+ mmc->caps |= MMC_CAP_4_BIT_DATA;
+ if (msmsdcc_sdioirq)
+ mmc->caps |= MMC_CAP_SDIO_IRQ;
+ mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED;
+
+ mmc->max_segs = NR_SG;
+ mmc->max_blk_size = 4096; /* MCI_DATA_CTL BLOCKSIZE up to 4096 */
+ mmc->max_blk_count = 65536;
+
+ mmc->max_req_size = 33554432; /* MCI_DATA_LENGTH is 25 bits */
+ mmc->max_seg_size = mmc->max_req_size;
+
+ msmsdcc_writel(host, 0, MMCIMASK0);
+ msmsdcc_writel(host, 0x5e007ff, MMCICLEAR);
+
+ msmsdcc_writel(host, MCI_IRQENABLE, MMCIMASK0);
+ host->saved_irq0mask = MCI_IRQENABLE;
+
+ /*
+ * Setup card detect change
+ */
+
+ memset(&host->timer, 0, sizeof(host->timer));
+
+ if (stat_irqres && !(stat_irqres->flags & IORESOURCE_DISABLED)) {
+ unsigned long irqflags = IRQF_SHARED |
+ (stat_irqres->flags & IRQF_TRIGGER_MASK);
+
+ host->stat_irq = stat_irqres->start;
+ ret = request_irq(host->stat_irq,
+ msmsdcc_platform_status_irq,
+ irqflags,
+ DRIVER_NAME " (slot)",
+ host);
+ if (ret) {
+ pr_err("%s: Unable to get slot IRQ %d (%d)\n",
+ mmc_hostname(mmc), host->stat_irq, ret);
+ goto clk_disable;
+ }
+ } else if (plat->register_status_notify) {
+ plat->register_status_notify(msmsdcc_status_notify_cb, host);
+ } else if (!plat->status)
+ pr_err("%s: No card detect facilities available\n",
+ mmc_hostname(mmc));
+ else {
+ init_timer(&host->timer);
+ host->timer.data = (unsigned long)host;
+ host->timer.function = msmsdcc_check_status;
+ host->timer.expires = jiffies + HZ;
+ add_timer(&host->timer);
+ }
+
+ if (plat->status) {
+ host->oldstat = host->plat->status(mmc_dev(host->mmc));
+ host->eject = !host->oldstat;
+ }
+
+ init_timer(&host->busclk_timer);
+ host->busclk_timer.data = (unsigned long) host;
+ host->busclk_timer.function = msmsdcc_busclk_expired;
+
+ ret = request_irq(cmd_irqres->start, msmsdcc_irq, IRQF_SHARED,
+ DRIVER_NAME " (cmd)", host);
+ if (ret)
+ goto stat_irq_free;
+
+ ret = request_irq(pio_irqres->start, msmsdcc_pio_irq, IRQF_SHARED,
+ DRIVER_NAME " (pio)", host);
+ if (ret)
+ goto cmd_irq_free;
+
+ mmc_set_drvdata(pdev, mmc);
+ mmc_add_host(mmc);
+
+ pr_info("%s: Qualcomm MSM SDCC at 0x%016llx irq %d,%d dma %d\n",
+ mmc_hostname(mmc), (unsigned long long)memres->start,
+ (unsigned int) cmd_irqres->start,
+ (unsigned int) host->stat_irq, host->dma.channel);
+ pr_info("%s: 4 bit data mode %s\n", mmc_hostname(mmc),
+ (mmc->caps & MMC_CAP_4_BIT_DATA ? "enabled" : "disabled"));
+ pr_info("%s: MMC clock %u -> %u Hz, PCLK %u Hz\n",
+ mmc_hostname(mmc), msmsdcc_fmin, msmsdcc_fmax, host->pclk_rate);
+ pr_info("%s: Slot eject status = %d\n", mmc_hostname(mmc), host->eject);
+ pr_info("%s: Power save feature enable = %d\n",
+ mmc_hostname(mmc), msmsdcc_pwrsave);
+
+ if (host->dma.channel != -1) {
+ pr_info("%s: DM non-cached buffer at %p, dma_addr 0x%.8x\n",
+ mmc_hostname(mmc), host->dma.nc, host->dma.nc_busaddr);
+ pr_info("%s: DM cmd busaddr 0x%.8x, cmdptr busaddr 0x%.8x\n",
+ mmc_hostname(mmc), host->dma.cmd_busaddr,
+ host->dma.cmdptr_busaddr);
+ } else
+ pr_info("%s: PIO transfer enabled\n", mmc_hostname(mmc));
+ if (host->timer.function)
+ pr_info("%s: Polling status mode enabled\n", mmc_hostname(mmc));
+
+ return 0;
+ cmd_irq_free:
+ free_irq(cmd_irqres->start, host);
+ stat_irq_free:
+ if (host->stat_irq)
+ free_irq(host->stat_irq, host);
+ clk_disable:
+ msmsdcc_disable_clocks(host, 0);
+ clk_put:
+ clk_put(host->clk);
+ pclk_put:
+ clk_put(host->pclk);
+ host_free:
+ mmc_free_host(mmc);
+ out:
+ return ret;
+}
+
+#ifdef CONFIG_PM
+#ifdef CONFIG_MMC_MSM7X00A_RESUME_IN_WQ
+static void
+do_resume_work(struct work_struct *work)
+{
+ struct msmsdcc_host *host =
+ container_of(work, struct msmsdcc_host, resume_task);
+ struct mmc_host *mmc = host->mmc;
+
+ if (mmc) {
+ mmc_resume_host(mmc);
+ if (host->stat_irq)
+ enable_irq(host->stat_irq);
+ }
+}
+#endif
+
+
+static int
+msmsdcc_suspend(struct platform_device *dev, pm_message_t state)
+{
+ struct mmc_host *mmc = mmc_get_drvdata(dev);
+ int rc = 0;
+
+ if (mmc) {
+ struct msmsdcc_host *host = mmc_priv(mmc);
+
+ if (host->stat_irq)
+ disable_irq(host->stat_irq);
+
+ if (mmc->card && mmc->card->type != MMC_TYPE_SDIO)
+ rc = mmc_suspend_host(mmc);
+ if (!rc)
+ msmsdcc_writel(host, 0, MMCIMASK0);
+ if (host->clks_on)
+ msmsdcc_disable_clocks(host, 0);
+ }
+ return rc;
+}
+
+static int
+msmsdcc_resume(struct platform_device *dev)
+{
+ struct mmc_host *mmc = mmc_get_drvdata(dev);
+
+ if (mmc) {
+ struct msmsdcc_host *host = mmc_priv(mmc);
+
+ msmsdcc_enable_clocks(host);
+
+ msmsdcc_writel(host, host->saved_irq0mask, MMCIMASK0);
+
+ if (mmc->card && mmc->card->type != MMC_TYPE_SDIO)
+ mmc_resume_host(mmc);
+ if (host->stat_irq)
+ enable_irq(host->stat_irq);
+#if BUSCLK_PWRSAVE
+ msmsdcc_disable_clocks(host, 1);
+#endif
+ }
+ return 0;
+}
+#else
+#define msmsdcc_suspend 0
+#define msmsdcc_resume 0
+#endif
+
+static struct platform_driver msmsdcc_driver = {
+ .probe = msmsdcc_probe,
+ .suspend = msmsdcc_suspend,
+ .resume = msmsdcc_resume,
+ .driver = {
+ .name = "msm_sdcc",
+ },
+};
+
+static int __init msmsdcc_init(void)
+{
+ return platform_driver_register(&msmsdcc_driver);
+}
+
+static void __exit msmsdcc_exit(void)
+{
+ platform_driver_unregister(&msmsdcc_driver);
+}
+
+module_init(msmsdcc_init);
+module_exit(msmsdcc_exit);
+
+MODULE_DESCRIPTION("Qualcomm MSM 7X00A Multimedia Card Interface driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mmc/host/msm_sdcc.h b/drivers/mmc/host/msm_sdcc.h
new file mode 100644
index 00000000..42d7bbc9
--- /dev/null
+++ b/drivers/mmc/host/msm_sdcc.h
@@ -0,0 +1,252 @@
+/*
+ * linux/drivers/mmc/host/msmsdcc.h - QCT MSM7K SDC Controller
+ *
+ * Copyright (C) 2008 Google, 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.
+ *
+ * - Based on mmci.h
+ */
+
+#ifndef _MSM_SDCC_H
+#define _MSM_SDCC_H
+
+#define MSMSDCC_CRCI_SDC1 6
+#define MSMSDCC_CRCI_SDC2 7
+#define MSMSDCC_CRCI_SDC3 12
+#define MSMSDCC_CRCI_SDC4 13
+
+#define MMCIPOWER 0x000
+#define MCI_PWR_OFF 0x00
+#define MCI_PWR_UP 0x02
+#define MCI_PWR_ON 0x03
+#define MCI_OD (1 << 6)
+
+#define MMCICLOCK 0x004
+#define MCI_CLK_ENABLE (1 << 8)
+#define MCI_CLK_PWRSAVE (1 << 9)
+#define MCI_CLK_WIDEBUS (1 << 10)
+#define MCI_CLK_FLOWENA (1 << 12)
+#define MCI_CLK_INVERTOUT (1 << 13)
+#define MCI_CLK_SELECTIN (1 << 14)
+
+#define MMCIARGUMENT 0x008
+#define MMCICOMMAND 0x00c
+#define MCI_CPSM_RESPONSE (1 << 6)
+#define MCI_CPSM_LONGRSP (1 << 7)
+#define MCI_CPSM_INTERRUPT (1 << 8)
+#define MCI_CPSM_PENDING (1 << 9)
+#define MCI_CPSM_ENABLE (1 << 10)
+#define MCI_CPSM_PROGENA (1 << 11)
+#define MCI_CSPM_DATCMD (1 << 12)
+#define MCI_CSPM_MCIABORT (1 << 13)
+#define MCI_CSPM_CCSENABLE (1 << 14)
+#define MCI_CSPM_CCSDISABLE (1 << 15)
+
+
+#define MMCIRESPCMD 0x010
+#define MMCIRESPONSE0 0x014
+#define MMCIRESPONSE1 0x018
+#define MMCIRESPONSE2 0x01c
+#define MMCIRESPONSE3 0x020
+#define MMCIDATATIMER 0x024
+#define MMCIDATALENGTH 0x028
+
+#define MMCIDATACTRL 0x02c
+#define MCI_DPSM_ENABLE (1 << 0)
+#define MCI_DPSM_DIRECTION (1 << 1)
+#define MCI_DPSM_MODE (1 << 2)
+#define MCI_DPSM_DMAENABLE (1 << 3)
+
+#define MMCIDATACNT 0x030
+#define MMCISTATUS 0x034
+#define MCI_CMDCRCFAIL (1 << 0)
+#define MCI_DATACRCFAIL (1 << 1)
+#define MCI_CMDTIMEOUT (1 << 2)
+#define MCI_DATATIMEOUT (1 << 3)
+#define MCI_TXUNDERRUN (1 << 4)
+#define MCI_RXOVERRUN (1 << 5)
+#define MCI_CMDRESPEND (1 << 6)
+#define MCI_CMDSENT (1 << 7)
+#define MCI_DATAEND (1 << 8)
+#define MCI_DATABLOCKEND (1 << 10)
+#define MCI_CMDACTIVE (1 << 11)
+#define MCI_TXACTIVE (1 << 12)
+#define MCI_RXACTIVE (1 << 13)
+#define MCI_TXFIFOHALFEMPTY (1 << 14)
+#define MCI_RXFIFOHALFFULL (1 << 15)
+#define MCI_TXFIFOFULL (1 << 16)
+#define MCI_RXFIFOFULL (1 << 17)
+#define MCI_TXFIFOEMPTY (1 << 18)
+#define MCI_RXFIFOEMPTY (1 << 19)
+#define MCI_TXDATAAVLBL (1 << 20)
+#define MCI_RXDATAAVLBL (1 << 21)
+#define MCI_SDIOINTR (1 << 22)
+#define MCI_PROGDONE (1 << 23)
+#define MCI_ATACMDCOMPL (1 << 24)
+#define MCI_SDIOINTOPER (1 << 25)
+#define MCI_CCSTIMEOUT (1 << 26)
+
+#define MMCICLEAR 0x038
+#define MCI_CMDCRCFAILCLR (1 << 0)
+#define MCI_DATACRCFAILCLR (1 << 1)
+#define MCI_CMDTIMEOUTCLR (1 << 2)
+#define MCI_DATATIMEOUTCLR (1 << 3)
+#define MCI_TXUNDERRUNCLR (1 << 4)
+#define MCI_RXOVERRUNCLR (1 << 5)
+#define MCI_CMDRESPENDCLR (1 << 6)
+#define MCI_CMDSENTCLR (1 << 7)
+#define MCI_DATAENDCLR (1 << 8)
+#define MCI_DATABLOCKENDCLR (1 << 10)
+
+#define MMCIMASK0 0x03c
+#define MCI_CMDCRCFAILMASK (1 << 0)
+#define MCI_DATACRCFAILMASK (1 << 1)
+#define MCI_CMDTIMEOUTMASK (1 << 2)
+#define MCI_DATATIMEOUTMASK (1 << 3)
+#define MCI_TXUNDERRUNMASK (1 << 4)
+#define MCI_RXOVERRUNMASK (1 << 5)
+#define MCI_CMDRESPENDMASK (1 << 6)
+#define MCI_CMDSENTMASK (1 << 7)
+#define MCI_DATAENDMASK (1 << 8)
+#define MCI_DATABLOCKENDMASK (1 << 10)
+#define MCI_CMDACTIVEMASK (1 << 11)
+#define MCI_TXACTIVEMASK (1 << 12)
+#define MCI_RXACTIVEMASK (1 << 13)
+#define MCI_TXFIFOHALFEMPTYMASK (1 << 14)
+#define MCI_RXFIFOHALFFULLMASK (1 << 15)
+#define MCI_TXFIFOFULLMASK (1 << 16)
+#define MCI_RXFIFOFULLMASK (1 << 17)
+#define MCI_TXFIFOEMPTYMASK (1 << 18)
+#define MCI_RXFIFOEMPTYMASK (1 << 19)
+#define MCI_TXDATAAVLBLMASK (1 << 20)
+#define MCI_RXDATAAVLBLMASK (1 << 21)
+#define MCI_SDIOINTMASK (1 << 22)
+#define MCI_PROGDONEMASK (1 << 23)
+#define MCI_ATACMDCOMPLMASK (1 << 24)
+#define MCI_SDIOINTOPERMASK (1 << 25)
+#define MCI_CCSTIMEOUTMASK (1 << 26)
+
+#define MMCIMASK1 0x040
+#define MMCIFIFOCNT 0x044
+#define MCICCSTIMER 0x058
+
+#define MMCIFIFO 0x080 /* to 0x0bc */
+
+#define MCI_IRQENABLE \
+ (MCI_CMDCRCFAILMASK|MCI_DATACRCFAILMASK|MCI_CMDTIMEOUTMASK| \
+ MCI_DATATIMEOUTMASK|MCI_TXUNDERRUNMASK|MCI_RXOVERRUNMASK| \
+ MCI_CMDRESPENDMASK|MCI_CMDSENTMASK|MCI_DATAENDMASK|MCI_PROGDONEMASK)
+
+/*
+ * The size of the FIFO in bytes.
+ */
+#define MCI_FIFOSIZE (16*4)
+
+#define MCI_FIFOHALFSIZE (MCI_FIFOSIZE / 2)
+
+#define NR_SG 32
+
+struct clk;
+
+struct msmsdcc_nc_dmadata {
+ dmov_box cmd[NR_SG];
+ uint32_t cmdptr;
+};
+
+struct msmsdcc_dma_data {
+ struct msmsdcc_nc_dmadata *nc;
+ dma_addr_t nc_busaddr;
+ dma_addr_t cmd_busaddr;
+ dma_addr_t cmdptr_busaddr;
+
+ struct msm_dmov_cmd hdr;
+ enum dma_data_direction dir;
+
+ struct scatterlist *sg;
+ int num_ents;
+
+ int channel;
+ struct msmsdcc_host *host;
+ int busy; /* Set if DM is busy */
+ int active;
+ unsigned int result;
+ struct msm_dmov_errdata err;
+};
+
+struct msmsdcc_pio_data {
+ struct scatterlist *sg;
+ unsigned int sg_len;
+ unsigned int sg_off;
+};
+
+struct msmsdcc_curr_req {
+ struct mmc_request *mrq;
+ struct mmc_command *cmd;
+ struct mmc_data *data;
+ unsigned int xfer_size; /* Total data size */
+ unsigned int xfer_remain; /* Bytes remaining to send */
+ unsigned int data_xfered; /* Bytes acked by BLKEND irq */
+ int got_dataend;
+ int user_pages;
+};
+
+struct msmsdcc_stats {
+ unsigned int reqs;
+ unsigned int cmds;
+ unsigned int cmdpoll_hits;
+ unsigned int cmdpoll_misses;
+};
+
+struct msmsdcc_host {
+ struct resource *cmd_irqres;
+ struct resource *pio_irqres;
+ struct resource *memres;
+ struct resource *dmares;
+ void __iomem *base;
+ int pdev_id;
+ unsigned int stat_irq;
+
+ struct msmsdcc_curr_req curr;
+
+ struct mmc_host *mmc;
+ struct clk *clk; /* main MMC bus clock */
+ struct clk *pclk; /* SDCC peripheral bus clock */
+ unsigned int clks_on; /* set if clocks are enabled */
+ struct timer_list busclk_timer;
+
+ unsigned int eject; /* eject state */
+
+ spinlock_t lock;
+
+ unsigned int clk_rate; /* Current clock rate */
+ unsigned int pclk_rate;
+
+ u32 pwr;
+ u32 saved_irq0mask; /* MMCIMASK0 reg value */
+ struct msm_mmc_platform_data *plat;
+
+ struct timer_list timer;
+ unsigned int oldstat;
+
+ struct msmsdcc_dma_data dma;
+ struct msmsdcc_pio_data pio;
+ int cmdpoll;
+ struct msmsdcc_stats stats;
+
+ struct tasklet_struct dma_tlet;
+ /* Command parameters */
+ unsigned int cmd_timeout;
+ unsigned int cmd_pio_irqmask;
+ unsigned int cmd_datactrl;
+ struct mmc_command *cmd_cmd;
+ u32 cmd_c;
+ bool gpio_config_status;
+
+ bool prog_scan;
+ bool prog_enable;
+};
+
+#endif
diff --git a/drivers/mmc/host/mvsdio.c b/drivers/mmc/host/mvsdio.c
new file mode 100644
index 00000000..a5bf60e0
--- /dev/null
+++ b/drivers/mmc/host/mvsdio.c
@@ -0,0 +1,918 @@
+/*
+ * Marvell MMC/SD/SDIO driver
+ *
+ * Authors: Maen Suleiman, Nicolas Pitre
+ * Copyright (C) 2008-2009 Marvell Ltd.
+ *
+ * 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/init.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/mbus.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/scatterlist.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/mmc/host.h>
+
+#include <asm/sizes.h>
+#include <asm/unaligned.h>
+#include <plat/mvsdio.h>
+
+#include "mvsdio.h"
+
+#define DRIVER_NAME "mvsdio"
+
+static int maxfreq = MVSD_CLOCKRATE_MAX;
+static int nodma;
+
+struct mvsd_host {
+ void __iomem *base;
+ struct mmc_request *mrq;
+ spinlock_t lock;
+ unsigned int xfer_mode;
+ unsigned int intr_en;
+ unsigned int ctrl;
+ unsigned int pio_size;
+ void *pio_ptr;
+ unsigned int sg_frags;
+ unsigned int ns_per_clk;
+ unsigned int clock;
+ unsigned int base_clock;
+ struct timer_list timer;
+ struct mmc_host *mmc;
+ struct device *dev;
+ struct resource *res;
+ int irq;
+ int gpio_card_detect;
+ int gpio_write_protect;
+};
+
+#define mvsd_write(offs, val) writel(val, iobase + (offs))
+#define mvsd_read(offs) readl(iobase + (offs))
+
+static int mvsd_setup_data(struct mvsd_host *host, struct mmc_data *data)
+{
+ void __iomem *iobase = host->base;
+ unsigned int tmout;
+ int tmout_index;
+
+ /*
+ * Hardware weirdness. The FIFO_EMPTY bit of the HW_STATE
+ * register is sometimes not set before a while when some
+ * "unusual" data block sizes are used (such as with the SWITCH
+ * command), even despite the fact that the XFER_DONE interrupt
+ * was raised. And if another data transfer starts before
+ * this bit comes to good sense (which eventually happens by
+ * itself) then the new transfer simply fails with a timeout.
+ */
+ if (!(mvsd_read(MVSD_HW_STATE) & (1 << 13))) {
+ unsigned long t = jiffies + HZ;
+ unsigned int hw_state, count = 0;
+ do {
+ if (time_after(jiffies, t)) {
+ dev_warn(host->dev, "FIFO_EMPTY bit missing\n");
+ break;
+ }
+ hw_state = mvsd_read(MVSD_HW_STATE);
+ count++;
+ } while (!(hw_state & (1 << 13)));
+ dev_dbg(host->dev, "*** wait for FIFO_EMPTY bit "
+ "(hw=0x%04x, count=%d, jiffies=%ld)\n",
+ hw_state, count, jiffies - (t - HZ));
+ }
+
+ /* If timeout=0 then maximum timeout index is used. */
+ tmout = DIV_ROUND_UP(data->timeout_ns, host->ns_per_clk);
+ tmout += data->timeout_clks;
+ tmout_index = fls(tmout - 1) - 12;
+ if (tmout_index < 0)
+ tmout_index = 0;
+ if (tmout_index > MVSD_HOST_CTRL_TMOUT_MAX)
+ tmout_index = MVSD_HOST_CTRL_TMOUT_MAX;
+
+ dev_dbg(host->dev, "data %s at 0x%08x: blocks=%d blksz=%d tmout=%u (%d)\n",
+ (data->flags & MMC_DATA_READ) ? "read" : "write",
+ (u32)sg_virt(data->sg), data->blocks, data->blksz,
+ tmout, tmout_index);
+
+ host->ctrl &= ~MVSD_HOST_CTRL_TMOUT_MASK;
+ host->ctrl |= MVSD_HOST_CTRL_TMOUT(tmout_index);
+ mvsd_write(MVSD_HOST_CTRL, host->ctrl);
+ mvsd_write(MVSD_BLK_COUNT, data->blocks);
+ mvsd_write(MVSD_BLK_SIZE, data->blksz);
+
+ if (nodma || (data->blksz | data->sg->offset) & 3) {
+ /*
+ * We cannot do DMA on a buffer which offset or size
+ * is not aligned on a 4-byte boundary.
+ */
+ host->pio_size = data->blocks * data->blksz;
+ host->pio_ptr = sg_virt(data->sg);
+ if (!nodma)
+ printk(KERN_DEBUG "%s: fallback to PIO for data "
+ "at 0x%p size %d\n",
+ mmc_hostname(host->mmc),
+ host->pio_ptr, host->pio_size);
+ return 1;
+ } else {
+ dma_addr_t phys_addr;
+ int dma_dir = (data->flags & MMC_DATA_READ) ?
+ DMA_FROM_DEVICE : DMA_TO_DEVICE;
+ host->sg_frags = dma_map_sg(mmc_dev(host->mmc), data->sg,
+ data->sg_len, dma_dir);
+ phys_addr = sg_dma_address(data->sg);
+ mvsd_write(MVSD_SYS_ADDR_LOW, (u32)phys_addr & 0xffff);
+ mvsd_write(MVSD_SYS_ADDR_HI, (u32)phys_addr >> 16);
+ return 0;
+ }
+}
+
+static void mvsd_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct mvsd_host *host = mmc_priv(mmc);
+ void __iomem *iobase = host->base;
+ struct mmc_command *cmd = mrq->cmd;
+ u32 cmdreg = 0, xfer = 0, intr = 0;
+ unsigned long flags;
+
+ BUG_ON(host->mrq != NULL);
+ host->mrq = mrq;
+
+ dev_dbg(host->dev, "cmd %d (hw state 0x%04x)\n",
+ cmd->opcode, mvsd_read(MVSD_HW_STATE));
+
+ cmdreg = MVSD_CMD_INDEX(cmd->opcode);
+
+ if (cmd->flags & MMC_RSP_BUSY)
+ cmdreg |= MVSD_CMD_RSP_48BUSY;
+ else if (cmd->flags & MMC_RSP_136)
+ cmdreg |= MVSD_CMD_RSP_136;
+ else if (cmd->flags & MMC_RSP_PRESENT)
+ cmdreg |= MVSD_CMD_RSP_48;
+ else
+ cmdreg |= MVSD_CMD_RSP_NONE;
+
+ if (cmd->flags & MMC_RSP_CRC)
+ cmdreg |= MVSD_CMD_CHECK_CMDCRC;
+
+ if (cmd->flags & MMC_RSP_OPCODE)
+ cmdreg |= MVSD_CMD_INDX_CHECK;
+
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ cmdreg |= MVSD_UNEXPECTED_RESP;
+ intr |= MVSD_NOR_UNEXP_RSP;
+ }
+
+ if (mrq->data) {
+ struct mmc_data *data = mrq->data;
+ int pio;
+
+ cmdreg |= MVSD_CMD_DATA_PRESENT | MVSD_CMD_CHECK_DATACRC16;
+ xfer |= MVSD_XFER_MODE_HW_WR_DATA_EN;
+ if (data->flags & MMC_DATA_READ)
+ xfer |= MVSD_XFER_MODE_TO_HOST;
+
+ pio = mvsd_setup_data(host, data);
+ if (pio) {
+ xfer |= MVSD_XFER_MODE_PIO;
+ /* PIO section of mvsd_irq has comments on those bits */
+ if (data->flags & MMC_DATA_WRITE)
+ intr |= MVSD_NOR_TX_AVAIL;
+ else if (host->pio_size > 32)
+ intr |= MVSD_NOR_RX_FIFO_8W;
+ else
+ intr |= MVSD_NOR_RX_READY;
+ }
+
+ if (data->stop) {
+ struct mmc_command *stop = data->stop;
+ u32 cmd12reg = 0;
+
+ mvsd_write(MVSD_AUTOCMD12_ARG_LOW, stop->arg & 0xffff);
+ mvsd_write(MVSD_AUTOCMD12_ARG_HI, stop->arg >> 16);
+
+ if (stop->flags & MMC_RSP_BUSY)
+ cmd12reg |= MVSD_AUTOCMD12_BUSY;
+ if (stop->flags & MMC_RSP_OPCODE)
+ cmd12reg |= MVSD_AUTOCMD12_INDX_CHECK;
+ cmd12reg |= MVSD_AUTOCMD12_INDEX(stop->opcode);
+ mvsd_write(MVSD_AUTOCMD12_CMD, cmd12reg);
+
+ xfer |= MVSD_XFER_MODE_AUTO_CMD12;
+ intr |= MVSD_NOR_AUTOCMD12_DONE;
+ } else {
+ intr |= MVSD_NOR_XFER_DONE;
+ }
+ } else {
+ intr |= MVSD_NOR_CMD_DONE;
+ }
+
+ mvsd_write(MVSD_ARG_LOW, cmd->arg & 0xffff);
+ mvsd_write(MVSD_ARG_HI, cmd->arg >> 16);
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ host->xfer_mode &= MVSD_XFER_MODE_INT_CHK_EN;
+ host->xfer_mode |= xfer;
+ mvsd_write(MVSD_XFER_MODE, host->xfer_mode);
+
+ mvsd_write(MVSD_NOR_INTR_STATUS, ~MVSD_NOR_CARD_INT);
+ mvsd_write(MVSD_ERR_INTR_STATUS, 0xffff);
+ mvsd_write(MVSD_CMD, cmdreg);
+
+ host->intr_en &= MVSD_NOR_CARD_INT;
+ host->intr_en |= intr | MVSD_NOR_ERROR;
+ mvsd_write(MVSD_NOR_INTR_EN, host->intr_en);
+ mvsd_write(MVSD_ERR_INTR_EN, 0xffff);
+
+ mod_timer(&host->timer, jiffies + 5 * HZ);
+
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static u32 mvsd_finish_cmd(struct mvsd_host *host, struct mmc_command *cmd,
+ u32 err_status)
+{
+ void __iomem *iobase = host->base;
+
+ if (cmd->flags & MMC_RSP_136) {
+ unsigned int response[8], i;
+ for (i = 0; i < 8; i++)
+ response[i] = mvsd_read(MVSD_RSP(i));
+ cmd->resp[0] = ((response[0] & 0x03ff) << 22) |
+ ((response[1] & 0xffff) << 6) |
+ ((response[2] & 0xfc00) >> 10);
+ cmd->resp[1] = ((response[2] & 0x03ff) << 22) |
+ ((response[3] & 0xffff) << 6) |
+ ((response[4] & 0xfc00) >> 10);
+ cmd->resp[2] = ((response[4] & 0x03ff) << 22) |
+ ((response[5] & 0xffff) << 6) |
+ ((response[6] & 0xfc00) >> 10);
+ cmd->resp[3] = ((response[6] & 0x03ff) << 22) |
+ ((response[7] & 0x3fff) << 8);
+ } else if (cmd->flags & MMC_RSP_PRESENT) {
+ unsigned int response[3], i;
+ for (i = 0; i < 3; i++)
+ response[i] = mvsd_read(MVSD_RSP(i));
+ cmd->resp[0] = ((response[2] & 0x003f) << (8 - 8)) |
+ ((response[1] & 0xffff) << (14 - 8)) |
+ ((response[0] & 0x03ff) << (30 - 8));
+ cmd->resp[1] = ((response[0] & 0xfc00) >> 10);
+ cmd->resp[2] = 0;
+ cmd->resp[3] = 0;
+ }
+
+ if (err_status & MVSD_ERR_CMD_TIMEOUT) {
+ cmd->error = -ETIMEDOUT;
+ } else if (err_status & (MVSD_ERR_CMD_CRC | MVSD_ERR_CMD_ENDBIT |
+ MVSD_ERR_CMD_INDEX | MVSD_ERR_CMD_STARTBIT)) {
+ cmd->error = -EILSEQ;
+ }
+ err_status &= ~(MVSD_ERR_CMD_TIMEOUT | MVSD_ERR_CMD_CRC |
+ MVSD_ERR_CMD_ENDBIT | MVSD_ERR_CMD_INDEX |
+ MVSD_ERR_CMD_STARTBIT);
+
+ return err_status;
+}
+
+static u32 mvsd_finish_data(struct mvsd_host *host, struct mmc_data *data,
+ u32 err_status)
+{
+ void __iomem *iobase = host->base;
+
+ if (host->pio_ptr) {
+ host->pio_ptr = NULL;
+ host->pio_size = 0;
+ } else {
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->sg_frags,
+ (data->flags & MMC_DATA_READ) ?
+ DMA_FROM_DEVICE : DMA_TO_DEVICE);
+ }
+
+ if (err_status & MVSD_ERR_DATA_TIMEOUT)
+ data->error = -ETIMEDOUT;
+ else if (err_status & (MVSD_ERR_DATA_CRC | MVSD_ERR_DATA_ENDBIT))
+ data->error = -EILSEQ;
+ else if (err_status & MVSD_ERR_XFER_SIZE)
+ data->error = -EBADE;
+ err_status &= ~(MVSD_ERR_DATA_TIMEOUT | MVSD_ERR_DATA_CRC |
+ MVSD_ERR_DATA_ENDBIT | MVSD_ERR_XFER_SIZE);
+
+ dev_dbg(host->dev, "data done: blocks_left=%d, bytes_left=%d\n",
+ mvsd_read(MVSD_CURR_BLK_LEFT), mvsd_read(MVSD_CURR_BYTE_LEFT));
+ data->bytes_xfered =
+ (data->blocks - mvsd_read(MVSD_CURR_BLK_LEFT)) * data->blksz;
+ /* We can't be sure about the last block when errors are detected */
+ if (data->bytes_xfered && data->error)
+ data->bytes_xfered -= data->blksz;
+
+ /* Handle Auto cmd 12 response */
+ if (data->stop) {
+ unsigned int response[3], i;
+ for (i = 0; i < 3; i++)
+ response[i] = mvsd_read(MVSD_AUTO_RSP(i));
+ data->stop->resp[0] = ((response[2] & 0x003f) << (8 - 8)) |
+ ((response[1] & 0xffff) << (14 - 8)) |
+ ((response[0] & 0x03ff) << (30 - 8));
+ data->stop->resp[1] = ((response[0] & 0xfc00) >> 10);
+ data->stop->resp[2] = 0;
+ data->stop->resp[3] = 0;
+
+ if (err_status & MVSD_ERR_AUTOCMD12) {
+ u32 err_cmd12 = mvsd_read(MVSD_AUTOCMD12_ERR_STATUS);
+ dev_dbg(host->dev, "c12err 0x%04x\n", err_cmd12);
+ if (err_cmd12 & MVSD_AUTOCMD12_ERR_NOTEXE)
+ data->stop->error = -ENOEXEC;
+ else if (err_cmd12 & MVSD_AUTOCMD12_ERR_TIMEOUT)
+ data->stop->error = -ETIMEDOUT;
+ else if (err_cmd12)
+ data->stop->error = -EILSEQ;
+ err_status &= ~MVSD_ERR_AUTOCMD12;
+ }
+ }
+
+ return err_status;
+}
+
+static irqreturn_t mvsd_irq(int irq, void *dev)
+{
+ struct mvsd_host *host = dev;
+ void __iomem *iobase = host->base;
+ u32 intr_status, intr_done_mask;
+ int irq_handled = 0;
+
+ intr_status = mvsd_read(MVSD_NOR_INTR_STATUS);
+ dev_dbg(host->dev, "intr 0x%04x intr_en 0x%04x hw_state 0x%04x\n",
+ intr_status, mvsd_read(MVSD_NOR_INTR_EN),
+ mvsd_read(MVSD_HW_STATE));
+
+ spin_lock(&host->lock);
+
+ /* PIO handling, if needed. Messy business... */
+ if (host->pio_size &&
+ (intr_status & host->intr_en &
+ (MVSD_NOR_RX_READY | MVSD_NOR_RX_FIFO_8W))) {
+ u16 *p = host->pio_ptr;
+ int s = host->pio_size;
+ while (s >= 32 && (intr_status & MVSD_NOR_RX_FIFO_8W)) {
+ readsw(iobase + MVSD_FIFO, p, 16);
+ p += 16;
+ s -= 32;
+ intr_status = mvsd_read(MVSD_NOR_INTR_STATUS);
+ }
+ /*
+ * Normally we'd use < 32 here, but the RX_FIFO_8W bit
+ * doesn't appear to assert when there is exactly 32 bytes
+ * (8 words) left to fetch in a transfer.
+ */
+ if (s <= 32) {
+ while (s >= 4 && (intr_status & MVSD_NOR_RX_READY)) {
+ put_unaligned(mvsd_read(MVSD_FIFO), p++);
+ put_unaligned(mvsd_read(MVSD_FIFO), p++);
+ s -= 4;
+ intr_status = mvsd_read(MVSD_NOR_INTR_STATUS);
+ }
+ if (s && s < 4 && (intr_status & MVSD_NOR_RX_READY)) {
+ u16 val[2] = {0, 0};
+ val[0] = mvsd_read(MVSD_FIFO);
+ val[1] = mvsd_read(MVSD_FIFO);
+ memcpy(p, ((void *)&val) + 4 - s, s);
+ s = 0;
+ intr_status = mvsd_read(MVSD_NOR_INTR_STATUS);
+ }
+ if (s == 0) {
+ host->intr_en &=
+ ~(MVSD_NOR_RX_READY | MVSD_NOR_RX_FIFO_8W);
+ mvsd_write(MVSD_NOR_INTR_EN, host->intr_en);
+ } else if (host->intr_en & MVSD_NOR_RX_FIFO_8W) {
+ host->intr_en &= ~MVSD_NOR_RX_FIFO_8W;
+ host->intr_en |= MVSD_NOR_RX_READY;
+ mvsd_write(MVSD_NOR_INTR_EN, host->intr_en);
+ }
+ }
+ dev_dbg(host->dev, "pio %d intr 0x%04x hw_state 0x%04x\n",
+ s, intr_status, mvsd_read(MVSD_HW_STATE));
+ host->pio_ptr = p;
+ host->pio_size = s;
+ irq_handled = 1;
+ } else if (host->pio_size &&
+ (intr_status & host->intr_en &
+ (MVSD_NOR_TX_AVAIL | MVSD_NOR_TX_FIFO_8W))) {
+ u16 *p = host->pio_ptr;
+ int s = host->pio_size;
+ /*
+ * The TX_FIFO_8W bit is unreliable. When set, bursting
+ * 16 halfwords all at once in the FIFO drops data. Actually
+ * TX_AVAIL does go off after only one word is pushed even if
+ * TX_FIFO_8W remains set.
+ */
+ while (s >= 4 && (intr_status & MVSD_NOR_TX_AVAIL)) {
+ mvsd_write(MVSD_FIFO, get_unaligned(p++));
+ mvsd_write(MVSD_FIFO, get_unaligned(p++));
+ s -= 4;
+ intr_status = mvsd_read(MVSD_NOR_INTR_STATUS);
+ }
+ if (s < 4) {
+ if (s && (intr_status & MVSD_NOR_TX_AVAIL)) {
+ u16 val[2] = {0, 0};
+ memcpy(((void *)&val) + 4 - s, p, s);
+ mvsd_write(MVSD_FIFO, val[0]);
+ mvsd_write(MVSD_FIFO, val[1]);
+ s = 0;
+ intr_status = mvsd_read(MVSD_NOR_INTR_STATUS);
+ }
+ if (s == 0) {
+ host->intr_en &=
+ ~(MVSD_NOR_TX_AVAIL | MVSD_NOR_TX_FIFO_8W);
+ mvsd_write(MVSD_NOR_INTR_EN, host->intr_en);
+ }
+ }
+ dev_dbg(host->dev, "pio %d intr 0x%04x hw_state 0x%04x\n",
+ s, intr_status, mvsd_read(MVSD_HW_STATE));
+ host->pio_ptr = p;
+ host->pio_size = s;
+ irq_handled = 1;
+ }
+
+ mvsd_write(MVSD_NOR_INTR_STATUS, intr_status);
+
+ intr_done_mask = MVSD_NOR_CARD_INT | MVSD_NOR_RX_READY |
+ MVSD_NOR_RX_FIFO_8W | MVSD_NOR_TX_FIFO_8W;
+ if (intr_status & host->intr_en & ~intr_done_mask) {
+ struct mmc_request *mrq = host->mrq;
+ struct mmc_command *cmd = mrq->cmd;
+ u32 err_status = 0;
+
+ del_timer(&host->timer);
+ host->mrq = NULL;
+
+ host->intr_en &= MVSD_NOR_CARD_INT;
+ mvsd_write(MVSD_NOR_INTR_EN, host->intr_en);
+ mvsd_write(MVSD_ERR_INTR_EN, 0);
+
+ spin_unlock(&host->lock);
+
+ if (intr_status & MVSD_NOR_UNEXP_RSP) {
+ cmd->error = -EPROTO;
+ } else if (intr_status & MVSD_NOR_ERROR) {
+ err_status = mvsd_read(MVSD_ERR_INTR_STATUS);
+ dev_dbg(host->dev, "err 0x%04x\n", err_status);
+ }
+
+ err_status = mvsd_finish_cmd(host, cmd, err_status);
+ if (mrq->data)
+ err_status = mvsd_finish_data(host, mrq->data, err_status);
+ if (err_status) {
+ printk(KERN_ERR "%s: unhandled error status %#04x\n",
+ mmc_hostname(host->mmc), err_status);
+ cmd->error = -ENOMSG;
+ }
+
+ mmc_request_done(host->mmc, mrq);
+ irq_handled = 1;
+ } else
+ spin_unlock(&host->lock);
+
+ if (intr_status & MVSD_NOR_CARD_INT) {
+ mmc_signal_sdio_irq(host->mmc);
+ irq_handled = 1;
+ }
+
+ if (irq_handled)
+ return IRQ_HANDLED;
+
+ printk(KERN_ERR "%s: unhandled interrupt status=0x%04x en=0x%04x "
+ "pio=%d\n", mmc_hostname(host->mmc), intr_status,
+ host->intr_en, host->pio_size);
+ return IRQ_NONE;
+}
+
+static void mvsd_timeout_timer(unsigned long data)
+{
+ struct mvsd_host *host = (struct mvsd_host *)data;
+ void __iomem *iobase = host->base;
+ struct mmc_request *mrq;
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+ mrq = host->mrq;
+ if (mrq) {
+ printk(KERN_ERR "%s: Timeout waiting for hardware interrupt.\n",
+ mmc_hostname(host->mmc));
+ printk(KERN_ERR "%s: hw_state=0x%04x, intr_status=0x%04x "
+ "intr_en=0x%04x\n", mmc_hostname(host->mmc),
+ mvsd_read(MVSD_HW_STATE),
+ mvsd_read(MVSD_NOR_INTR_STATUS),
+ mvsd_read(MVSD_NOR_INTR_EN));
+
+ host->mrq = NULL;
+
+ mvsd_write(MVSD_SW_RESET, MVSD_SW_RESET_NOW);
+
+ host->xfer_mode &= MVSD_XFER_MODE_INT_CHK_EN;
+ mvsd_write(MVSD_XFER_MODE, host->xfer_mode);
+
+ host->intr_en &= MVSD_NOR_CARD_INT;
+ mvsd_write(MVSD_NOR_INTR_EN, host->intr_en);
+ mvsd_write(MVSD_ERR_INTR_EN, 0);
+ mvsd_write(MVSD_ERR_INTR_STATUS, 0xffff);
+
+ mrq->cmd->error = -ETIMEDOUT;
+ mvsd_finish_cmd(host, mrq->cmd, 0);
+ if (mrq->data) {
+ mrq->data->error = -ETIMEDOUT;
+ mvsd_finish_data(host, mrq->data, 0);
+ }
+ }
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ if (mrq)
+ mmc_request_done(host->mmc, mrq);
+}
+
+static irqreturn_t mvsd_card_detect_irq(int irq, void *dev)
+{
+ struct mvsd_host *host = dev;
+ mmc_detect_change(host->mmc, msecs_to_jiffies(100));
+ return IRQ_HANDLED;
+}
+
+static void mvsd_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+ struct mvsd_host *host = mmc_priv(mmc);
+ void __iomem *iobase = host->base;
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+ if (enable) {
+ host->xfer_mode |= MVSD_XFER_MODE_INT_CHK_EN;
+ host->intr_en |= MVSD_NOR_CARD_INT;
+ } else {
+ host->xfer_mode &= ~MVSD_XFER_MODE_INT_CHK_EN;
+ host->intr_en &= ~MVSD_NOR_CARD_INT;
+ }
+ mvsd_write(MVSD_XFER_MODE, host->xfer_mode);
+ mvsd_write(MVSD_NOR_INTR_EN, host->intr_en);
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static int mvsd_get_ro(struct mmc_host *mmc)
+{
+ struct mvsd_host *host = mmc_priv(mmc);
+
+ if (host->gpio_write_protect)
+ return gpio_get_value(host->gpio_write_protect);
+
+ /*
+ * Board doesn't support read only detection; let the mmc core
+ * decide what to do.
+ */
+ return -ENOSYS;
+}
+
+static void mvsd_power_up(struct mvsd_host *host)
+{
+ void __iomem *iobase = host->base;
+ dev_dbg(host->dev, "power up\n");
+ mvsd_write(MVSD_NOR_INTR_EN, 0);
+ mvsd_write(MVSD_ERR_INTR_EN, 0);
+ mvsd_write(MVSD_SW_RESET, MVSD_SW_RESET_NOW);
+ mvsd_write(MVSD_XFER_MODE, 0);
+ mvsd_write(MVSD_NOR_STATUS_EN, 0xffff);
+ mvsd_write(MVSD_ERR_STATUS_EN, 0xffff);
+ mvsd_write(MVSD_NOR_INTR_STATUS, 0xffff);
+ mvsd_write(MVSD_ERR_INTR_STATUS, 0xffff);
+}
+
+static void mvsd_power_down(struct mvsd_host *host)
+{
+ void __iomem *iobase = host->base;
+ dev_dbg(host->dev, "power down\n");
+ mvsd_write(MVSD_NOR_INTR_EN, 0);
+ mvsd_write(MVSD_ERR_INTR_EN, 0);
+ mvsd_write(MVSD_SW_RESET, MVSD_SW_RESET_NOW);
+ mvsd_write(MVSD_XFER_MODE, MVSD_XFER_MODE_STOP_CLK);
+ mvsd_write(MVSD_NOR_STATUS_EN, 0);
+ mvsd_write(MVSD_ERR_STATUS_EN, 0);
+ mvsd_write(MVSD_NOR_INTR_STATUS, 0xffff);
+ mvsd_write(MVSD_ERR_INTR_STATUS, 0xffff);
+}
+
+static void mvsd_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct mvsd_host *host = mmc_priv(mmc);
+ void __iomem *iobase = host->base;
+ u32 ctrl_reg = 0;
+
+ if (ios->power_mode == MMC_POWER_UP)
+ mvsd_power_up(host);
+
+ if (ios->clock == 0) {
+ mvsd_write(MVSD_XFER_MODE, MVSD_XFER_MODE_STOP_CLK);
+ mvsd_write(MVSD_CLK_DIV, MVSD_BASE_DIV_MAX);
+ host->clock = 0;
+ dev_dbg(host->dev, "clock off\n");
+ } else if (ios->clock != host->clock) {
+ u32 m = DIV_ROUND_UP(host->base_clock, ios->clock) - 1;
+ if (m > MVSD_BASE_DIV_MAX)
+ m = MVSD_BASE_DIV_MAX;
+ mvsd_write(MVSD_CLK_DIV, m);
+ host->clock = ios->clock;
+ host->ns_per_clk = 1000000000 / (host->base_clock / (m+1));
+ dev_dbg(host->dev, "clock=%d (%d), div=0x%04x\n",
+ ios->clock, host->base_clock / (m+1), m);
+ }
+
+ /* default transfer mode */
+ ctrl_reg |= MVSD_HOST_CTRL_BIG_ENDIAN;
+ ctrl_reg &= ~MVSD_HOST_CTRL_LSB_FIRST;
+
+ /* default to maximum timeout */
+ ctrl_reg |= MVSD_HOST_CTRL_TMOUT_MASK;
+ ctrl_reg |= MVSD_HOST_CTRL_TMOUT_EN;
+
+ if (ios->bus_mode == MMC_BUSMODE_PUSHPULL)
+ ctrl_reg |= MVSD_HOST_CTRL_PUSH_PULL_EN;
+
+ if (ios->bus_width == MMC_BUS_WIDTH_4)
+ ctrl_reg |= MVSD_HOST_CTRL_DATA_WIDTH_4_BITS;
+
+ /*
+ * The HI_SPEED_EN bit is causing trouble with many (but not all)
+ * high speed SD, SDHC and SDIO cards. Not enabling that bit
+ * makes all cards work. So let's just ignore that bit for now
+ * and revisit this issue if problems for not enabling this bit
+ * are ever reported.
+ */
+#if 0
+ if (ios->timing == MMC_TIMING_MMC_HS ||
+ ios->timing == MMC_TIMING_SD_HS)
+ ctrl_reg |= MVSD_HOST_CTRL_HI_SPEED_EN;
+#endif
+
+ host->ctrl = ctrl_reg;
+ mvsd_write(MVSD_HOST_CTRL, ctrl_reg);
+ dev_dbg(host->dev, "ctrl 0x%04x: %s %s %s\n", ctrl_reg,
+ (ctrl_reg & MVSD_HOST_CTRL_PUSH_PULL_EN) ?
+ "push-pull" : "open-drain",
+ (ctrl_reg & MVSD_HOST_CTRL_DATA_WIDTH_4_BITS) ?
+ "4bit-width" : "1bit-width",
+ (ctrl_reg & MVSD_HOST_CTRL_HI_SPEED_EN) ?
+ "high-speed" : "");
+
+ if (ios->power_mode == MMC_POWER_OFF)
+ mvsd_power_down(host);
+}
+
+static const struct mmc_host_ops mvsd_ops = {
+ .request = mvsd_request,
+ .get_ro = mvsd_get_ro,
+ .set_ios = mvsd_set_ios,
+ .enable_sdio_irq = mvsd_enable_sdio_irq,
+};
+
+static void __init mv_conf_mbus_windows(struct mvsd_host *host,
+ struct mbus_dram_target_info *dram)
+{
+ void __iomem *iobase = host->base;
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ writel(0, iobase + MVSD_WINDOW_CTRL(i));
+ writel(0, iobase + MVSD_WINDOW_BASE(i));
+ }
+
+ for (i = 0; i < dram->num_cs; i++) {
+ struct mbus_dram_window *cs = dram->cs + i;
+ writel(((cs->size - 1) & 0xffff0000) |
+ (cs->mbus_attr << 8) |
+ (dram->mbus_dram_target_id << 4) | 1,
+ iobase + MVSD_WINDOW_CTRL(i));
+ writel(cs->base, iobase + MVSD_WINDOW_BASE(i));
+ }
+}
+
+static int __init mvsd_probe(struct platform_device *pdev)
+{
+ struct mmc_host *mmc = NULL;
+ struct mvsd_host *host = NULL;
+ const struct mvsdio_platform_data *mvsd_data;
+ struct resource *r;
+ int ret, irq;
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ irq = platform_get_irq(pdev, 0);
+ mvsd_data = pdev->dev.platform_data;
+ if (!r || irq < 0 || !mvsd_data)
+ return -ENXIO;
+
+ r = request_mem_region(r->start, SZ_1K, DRIVER_NAME);
+ if (!r)
+ return -EBUSY;
+
+ mmc = mmc_alloc_host(sizeof(struct mvsd_host), &pdev->dev);
+ if (!mmc) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ host = mmc_priv(mmc);
+ host->mmc = mmc;
+ host->dev = &pdev->dev;
+ host->res = r;
+ host->base_clock = mvsd_data->clock / 2;
+
+ mmc->ops = &mvsd_ops;
+
+ mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+ mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ |
+ MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED;
+
+ mmc->f_min = DIV_ROUND_UP(host->base_clock, MVSD_BASE_DIV_MAX);
+ mmc->f_max = maxfreq;
+
+ mmc->max_blk_size = 2048;
+ mmc->max_blk_count = 65535;
+
+ mmc->max_segs = 1;
+ mmc->max_seg_size = mmc->max_blk_size * mmc->max_blk_count;
+ mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
+
+ spin_lock_init(&host->lock);
+
+ host->base = ioremap(r->start, SZ_4K);
+ if (!host->base) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ /* (Re-)program MBUS remapping windows if we are asked to. */
+ if (mvsd_data->dram != NULL)
+ mv_conf_mbus_windows(host, mvsd_data->dram);
+
+ mvsd_power_down(host);
+
+ ret = request_irq(irq, mvsd_irq, 0, DRIVER_NAME, host);
+ if (ret) {
+ printk(KERN_ERR "%s: cannot assign irq %d\n", DRIVER_NAME, irq);
+ goto out;
+ } else
+ host->irq = irq;
+
+ if (mvsd_data->gpio_card_detect) {
+ ret = gpio_request(mvsd_data->gpio_card_detect,
+ DRIVER_NAME " cd");
+ if (ret == 0) {
+ gpio_direction_input(mvsd_data->gpio_card_detect);
+ irq = gpio_to_irq(mvsd_data->gpio_card_detect);
+ ret = request_irq(irq, mvsd_card_detect_irq,
+ IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING,
+ DRIVER_NAME " cd", host);
+ if (ret == 0)
+ host->gpio_card_detect =
+ mvsd_data->gpio_card_detect;
+ else
+ gpio_free(mvsd_data->gpio_card_detect);
+ }
+ }
+ if (!host->gpio_card_detect)
+ mmc->caps |= MMC_CAP_NEEDS_POLL;
+
+ if (mvsd_data->gpio_write_protect) {
+ ret = gpio_request(mvsd_data->gpio_write_protect,
+ DRIVER_NAME " wp");
+ if (ret == 0) {
+ gpio_direction_input(mvsd_data->gpio_write_protect);
+ host->gpio_write_protect =
+ mvsd_data->gpio_write_protect;
+ }
+ }
+
+ setup_timer(&host->timer, mvsd_timeout_timer, (unsigned long)host);
+ platform_set_drvdata(pdev, mmc);
+ ret = mmc_add_host(mmc);
+ if (ret)
+ goto out;
+
+ printk(KERN_NOTICE "%s: %s driver initialized, ",
+ mmc_hostname(mmc), DRIVER_NAME);
+ if (host->gpio_card_detect)
+ printk("using GPIO %d for card detection\n",
+ host->gpio_card_detect);
+ else
+ printk("lacking card detect (fall back to polling)\n");
+ return 0;
+
+out:
+ if (host) {
+ if (host->irq)
+ free_irq(host->irq, host);
+ if (host->gpio_card_detect) {
+ free_irq(gpio_to_irq(host->gpio_card_detect), host);
+ gpio_free(host->gpio_card_detect);
+ }
+ if (host->gpio_write_protect)
+ gpio_free(host->gpio_write_protect);
+ if (host->base)
+ iounmap(host->base);
+ }
+ if (r)
+ release_resource(r);
+ if (mmc)
+ mmc_free_host(mmc);
+
+ return ret;
+}
+
+static int __exit mvsd_remove(struct platform_device *pdev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(pdev);
+
+ if (mmc) {
+ struct mvsd_host *host = mmc_priv(mmc);
+
+ if (host->gpio_card_detect) {
+ free_irq(gpio_to_irq(host->gpio_card_detect), host);
+ gpio_free(host->gpio_card_detect);
+ }
+ mmc_remove_host(mmc);
+ free_irq(host->irq, host);
+ if (host->gpio_write_protect)
+ gpio_free(host->gpio_write_protect);
+ del_timer_sync(&host->timer);
+ mvsd_power_down(host);
+ iounmap(host->base);
+ release_resource(host->res);
+ mmc_free_host(mmc);
+ }
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int mvsd_suspend(struct platform_device *dev, pm_message_t state)
+{
+ struct mmc_host *mmc = platform_get_drvdata(dev);
+ int ret = 0;
+
+ if (mmc)
+ ret = mmc_suspend_host(mmc);
+
+ return ret;
+}
+
+static int mvsd_resume(struct platform_device *dev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(dev);
+ int ret = 0;
+
+ if (mmc)
+ ret = mmc_resume_host(mmc);
+
+ return ret;
+}
+#else
+#define mvsd_suspend NULL
+#define mvsd_resume NULL
+#endif
+
+static struct platform_driver mvsd_driver = {
+ .remove = __exit_p(mvsd_remove),
+ .suspend = mvsd_suspend,
+ .resume = mvsd_resume,
+ .driver = {
+ .name = DRIVER_NAME,
+ },
+};
+
+static int __init mvsd_init(void)
+{
+ return platform_driver_probe(&mvsd_driver, mvsd_probe);
+}
+
+static void __exit mvsd_exit(void)
+{
+ platform_driver_unregister(&mvsd_driver);
+}
+
+module_init(mvsd_init);
+module_exit(mvsd_exit);
+
+/* maximum card clock frequency (default 50MHz) */
+module_param(maxfreq, int, 0);
+
+/* force PIO transfers all the time */
+module_param(nodma, int, 0);
+
+MODULE_AUTHOR("Maen Suleiman, Nicolas Pitre");
+MODULE_DESCRIPTION("Marvell MMC,SD,SDIO Host Controller driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:mvsdio");
diff --git a/drivers/mmc/host/mvsdio.h b/drivers/mmc/host/mvsdio.h
new file mode 100644
index 00000000..7d9727b9
--- /dev/null
+++ b/drivers/mmc/host/mvsdio.h
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2008 Marvell Semiconductors, 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.
+ */
+
+#ifndef __MVSDIO_H
+#define __MVSDIO_H
+
+/*
+ * Clock rates
+ */
+
+#define MVSD_CLOCKRATE_MAX 50000000
+#define MVSD_BASE_DIV_MAX 0x7ff
+
+
+/*
+ * Register offsets
+ */
+
+#define MVSD_SYS_ADDR_LOW 0x000
+#define MVSD_SYS_ADDR_HI 0x004
+#define MVSD_BLK_SIZE 0x008
+#define MVSD_BLK_COUNT 0x00c
+#define MVSD_ARG_LOW 0x010
+#define MVSD_ARG_HI 0x014
+#define MVSD_XFER_MODE 0x018
+#define MVSD_CMD 0x01c
+#define MVSD_RSP(i) (0x020 + ((i)<<2))
+#define MVSD_RSP0 0x020
+#define MVSD_RSP1 0x024
+#define MVSD_RSP2 0x028
+#define MVSD_RSP3 0x02c
+#define MVSD_RSP4 0x030
+#define MVSD_RSP5 0x034
+#define MVSD_RSP6 0x038
+#define MVSD_RSP7 0x03c
+#define MVSD_FIFO 0x040
+#define MVSD_RSP_CRC7 0x044
+#define MVSD_HW_STATE 0x048
+#define MVSD_HOST_CTRL 0x050
+#define MVSD_BLK_GAP_CTRL 0x054
+#define MVSD_CLK_CTRL 0x058
+#define MVSD_SW_RESET 0x05c
+#define MVSD_NOR_INTR_STATUS 0x060
+#define MVSD_ERR_INTR_STATUS 0x064
+#define MVSD_NOR_STATUS_EN 0x068
+#define MVSD_ERR_STATUS_EN 0x06c
+#define MVSD_NOR_INTR_EN 0x070
+#define MVSD_ERR_INTR_EN 0x074
+#define MVSD_AUTOCMD12_ERR_STATUS 0x078
+#define MVSD_CURR_BYTE_LEFT 0x07c
+#define MVSD_CURR_BLK_LEFT 0x080
+#define MVSD_AUTOCMD12_ARG_LOW 0x084
+#define MVSD_AUTOCMD12_ARG_HI 0x088
+#define MVSD_AUTOCMD12_CMD 0x08c
+#define MVSD_AUTO_RSP(i) (0x090 + ((i)<<2))
+#define MVSD_AUTO_RSP0 0x090
+#define MVSD_AUTO_RSP1 0x094
+#define MVSD_AUTO_RSP2 0x098
+#define MVSD_CLK_DIV 0x128
+
+#define MVSD_WINDOW_CTRL(i) (0x108 + ((i) << 3))
+#define MVSD_WINDOW_BASE(i) (0x10c + ((i) << 3))
+
+
+/*
+ * MVSD_CMD
+ */
+
+#define MVSD_CMD_RSP_NONE (0 << 0)
+#define MVSD_CMD_RSP_136 (1 << 0)
+#define MVSD_CMD_RSP_48 (2 << 0)
+#define MVSD_CMD_RSP_48BUSY (3 << 0)
+
+#define MVSD_CMD_CHECK_DATACRC16 (1 << 2)
+#define MVSD_CMD_CHECK_CMDCRC (1 << 3)
+#define MVSD_CMD_INDX_CHECK (1 << 4)
+#define MVSD_CMD_DATA_PRESENT (1 << 5)
+#define MVSD_UNEXPECTED_RESP (1 << 7)
+#define MVSD_CMD_INDEX(x) ((x) << 8)
+
+
+/*
+ * MVSD_AUTOCMD12_CMD
+ */
+
+#define MVSD_AUTOCMD12_BUSY (1 << 0)
+#define MVSD_AUTOCMD12_INDX_CHECK (1 << 1)
+#define MVSD_AUTOCMD12_INDEX(x) ((x) << 8)
+
+/*
+ * MVSD_XFER_MODE
+ */
+
+#define MVSD_XFER_MODE_WR_DATA_START (1 << 0)
+#define MVSD_XFER_MODE_HW_WR_DATA_EN (1 << 1)
+#define MVSD_XFER_MODE_AUTO_CMD12 (1 << 2)
+#define MVSD_XFER_MODE_INT_CHK_EN (1 << 3)
+#define MVSD_XFER_MODE_TO_HOST (1 << 4)
+#define MVSD_XFER_MODE_STOP_CLK (1 << 5)
+#define MVSD_XFER_MODE_PIO (1 << 6)
+
+
+/*
+ * MVSD_HOST_CTRL
+ */
+
+#define MVSD_HOST_CTRL_PUSH_PULL_EN (1 << 0)
+
+#define MVSD_HOST_CTRL_CARD_TYPE_MEM_ONLY (0 << 1)
+#define MVSD_HOST_CTRL_CARD_TYPE_IO_ONLY (1 << 1)
+#define MVSD_HOST_CTRL_CARD_TYPE_IO_MEM_COMBO (2 << 1)
+#define MVSD_HOST_CTRL_CARD_TYPE_IO_MMC (3 << 1)
+#define MVSD_HOST_CTRL_CARD_TYPE_MASK (3 << 1)
+
+#define MVSD_HOST_CTRL_BIG_ENDIAN (1 << 3)
+#define MVSD_HOST_CTRL_LSB_FIRST (1 << 4)
+#define MVSD_HOST_CTRL_DATA_WIDTH_4_BITS (1 << 9)
+#define MVSD_HOST_CTRL_HI_SPEED_EN (1 << 10)
+
+#define MVSD_HOST_CTRL_TMOUT_MAX 0xf
+#define MVSD_HOST_CTRL_TMOUT_MASK (0xf << 11)
+#define MVSD_HOST_CTRL_TMOUT(x) ((x) << 11)
+#define MVSD_HOST_CTRL_TMOUT_EN (1 << 15)
+
+
+/*
+ * MVSD_SW_RESET
+ */
+
+#define MVSD_SW_RESET_NOW (1 << 8)
+
+
+/*
+ * Normal interrupt status bits
+ */
+
+#define MVSD_NOR_CMD_DONE (1 << 0)
+#define MVSD_NOR_XFER_DONE (1 << 1)
+#define MVSD_NOR_BLK_GAP_EVT (1 << 2)
+#define MVSD_NOR_DMA_DONE (1 << 3)
+#define MVSD_NOR_TX_AVAIL (1 << 4)
+#define MVSD_NOR_RX_READY (1 << 5)
+#define MVSD_NOR_CARD_INT (1 << 8)
+#define MVSD_NOR_READ_WAIT_ON (1 << 9)
+#define MVSD_NOR_RX_FIFO_8W (1 << 10)
+#define MVSD_NOR_TX_FIFO_8W (1 << 11)
+#define MVSD_NOR_SUSPEND_ON (1 << 12)
+#define MVSD_NOR_AUTOCMD12_DONE (1 << 13)
+#define MVSD_NOR_UNEXP_RSP (1 << 14)
+#define MVSD_NOR_ERROR (1 << 15)
+
+
+/*
+ * Error status bits
+ */
+
+#define MVSD_ERR_CMD_TIMEOUT (1 << 0)
+#define MVSD_ERR_CMD_CRC (1 << 1)
+#define MVSD_ERR_CMD_ENDBIT (1 << 2)
+#define MVSD_ERR_CMD_INDEX (1 << 3)
+#define MVSD_ERR_DATA_TIMEOUT (1 << 4)
+#define MVSD_ERR_DATA_CRC (1 << 5)
+#define MVSD_ERR_DATA_ENDBIT (1 << 6)
+#define MVSD_ERR_AUTOCMD12 (1 << 8)
+#define MVSD_ERR_CMD_STARTBIT (1 << 9)
+#define MVSD_ERR_XFER_SIZE (1 << 10)
+#define MVSD_ERR_RESP_T_BIT (1 << 11)
+#define MVSD_ERR_CRC_ENDBIT (1 << 12)
+#define MVSD_ERR_CRC_STARTBIT (1 << 13)
+#define MVSD_ERR_CRC_STATUS (1 << 14)
+
+
+/*
+ * CMD12 error status bits
+ */
+
+#define MVSD_AUTOCMD12_ERR_NOTEXE (1 << 0)
+#define MVSD_AUTOCMD12_ERR_TIMEOUT (1 << 1)
+#define MVSD_AUTOCMD12_ERR_CRC (1 << 2)
+#define MVSD_AUTOCMD12_ERR_ENDBIT (1 << 3)
+#define MVSD_AUTOCMD12_ERR_INDEX (1 << 4)
+#define MVSD_AUTOCMD12_ERR_RESP_T_BIT (1 << 5)
+#define MVSD_AUTOCMD12_ERR_RESP_STARTBIT (1 << 6)
+
+#endif
diff --git a/drivers/mmc/host/mxcmmc.c b/drivers/mmc/host/mxcmmc.c
new file mode 100644
index 00000000..e12fbc51
--- /dev/null
+++ b/drivers/mmc/host/mxcmmc.c
@@ -0,0 +1,1065 @@
+/*
+ * linux/drivers/mmc/host/mxcmmc.c - Freescale i.MX MMCI driver
+ *
+ * This is a driver for the SDHC controller found in Freescale MX2/MX3
+ * SoCs. It is basically the same hardware as found on MX1 (imxmmc.c).
+ * Unlike the hardware found on MX1, this hardware just works and does
+ * not need all the quirks found in imxmmc.c, hence the separate driver.
+ *
+ * Copyright (C) 2008 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>
+ * Copyright (C) 2006 Pavel Pisa, PiKRON <ppisa@pikron.com>
+ *
+ * derived from pxamci.c by Russell King
+ *
+ * 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/init.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/blkdev.h>
+#include <linux/dma-mapping.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/regulator/consumer.h>
+#include <linux/dmaengine.h>
+
+#include <asm/dma.h>
+#include <asm/irq.h>
+#include <asm/sizes.h>
+#include <mach/mmc.h>
+
+#include <mach/dma.h>
+
+#define DRIVER_NAME "mxc-mmc"
+
+#define MMC_REG_STR_STP_CLK 0x00
+#define MMC_REG_STATUS 0x04
+#define MMC_REG_CLK_RATE 0x08
+#define MMC_REG_CMD_DAT_CONT 0x0C
+#define MMC_REG_RES_TO 0x10
+#define MMC_REG_READ_TO 0x14
+#define MMC_REG_BLK_LEN 0x18
+#define MMC_REG_NOB 0x1C
+#define MMC_REG_REV_NO 0x20
+#define MMC_REG_INT_CNTR 0x24
+#define MMC_REG_CMD 0x28
+#define MMC_REG_ARG 0x2C
+#define MMC_REG_RES_FIFO 0x34
+#define MMC_REG_BUFFER_ACCESS 0x38
+
+#define STR_STP_CLK_RESET (1 << 3)
+#define STR_STP_CLK_START_CLK (1 << 1)
+#define STR_STP_CLK_STOP_CLK (1 << 0)
+
+#define STATUS_CARD_INSERTION (1 << 31)
+#define STATUS_CARD_REMOVAL (1 << 30)
+#define STATUS_YBUF_EMPTY (1 << 29)
+#define STATUS_XBUF_EMPTY (1 << 28)
+#define STATUS_YBUF_FULL (1 << 27)
+#define STATUS_XBUF_FULL (1 << 26)
+#define STATUS_BUF_UND_RUN (1 << 25)
+#define STATUS_BUF_OVFL (1 << 24)
+#define STATUS_SDIO_INT_ACTIVE (1 << 14)
+#define STATUS_END_CMD_RESP (1 << 13)
+#define STATUS_WRITE_OP_DONE (1 << 12)
+#define STATUS_DATA_TRANS_DONE (1 << 11)
+#define STATUS_READ_OP_DONE (1 << 11)
+#define STATUS_WR_CRC_ERROR_CODE_MASK (3 << 10)
+#define STATUS_CARD_BUS_CLK_RUN (1 << 8)
+#define STATUS_BUF_READ_RDY (1 << 7)
+#define STATUS_BUF_WRITE_RDY (1 << 6)
+#define STATUS_RESP_CRC_ERR (1 << 5)
+#define STATUS_CRC_READ_ERR (1 << 3)
+#define STATUS_CRC_WRITE_ERR (1 << 2)
+#define STATUS_TIME_OUT_RESP (1 << 1)
+#define STATUS_TIME_OUT_READ (1 << 0)
+#define STATUS_ERR_MASK 0x2f
+
+#define CMD_DAT_CONT_CMD_RESP_LONG_OFF (1 << 12)
+#define CMD_DAT_CONT_STOP_READWAIT (1 << 11)
+#define CMD_DAT_CONT_START_READWAIT (1 << 10)
+#define CMD_DAT_CONT_BUS_WIDTH_4 (2 << 8)
+#define CMD_DAT_CONT_INIT (1 << 7)
+#define CMD_DAT_CONT_WRITE (1 << 4)
+#define CMD_DAT_CONT_DATA_ENABLE (1 << 3)
+#define CMD_DAT_CONT_RESPONSE_48BIT_CRC (1 << 0)
+#define CMD_DAT_CONT_RESPONSE_136BIT (2 << 0)
+#define CMD_DAT_CONT_RESPONSE_48BIT (3 << 0)
+
+#define INT_SDIO_INT_WKP_EN (1 << 18)
+#define INT_CARD_INSERTION_WKP_EN (1 << 17)
+#define INT_CARD_REMOVAL_WKP_EN (1 << 16)
+#define INT_CARD_INSERTION_EN (1 << 15)
+#define INT_CARD_REMOVAL_EN (1 << 14)
+#define INT_SDIO_IRQ_EN (1 << 13)
+#define INT_DAT0_EN (1 << 12)
+#define INT_BUF_READ_EN (1 << 4)
+#define INT_BUF_WRITE_EN (1 << 3)
+#define INT_END_CMD_RES_EN (1 << 2)
+#define INT_WRITE_OP_DONE_EN (1 << 1)
+#define INT_READ_OP_EN (1 << 0)
+
+struct mxcmci_host {
+ struct mmc_host *mmc;
+ struct resource *res;
+ void __iomem *base;
+ int irq;
+ int detect_irq;
+ struct dma_chan *dma;
+ struct dma_async_tx_descriptor *desc;
+ int do_dma;
+ int default_irq_mask;
+ int use_sdio;
+ unsigned int power_mode;
+ struct imxmmc_platform_data *pdata;
+
+ struct mmc_request *req;
+ struct mmc_command *cmd;
+ struct mmc_data *data;
+
+ unsigned int datasize;
+ unsigned int dma_dir;
+
+ u16 rev_no;
+ unsigned int cmdat;
+
+ struct clk *clk;
+
+ int clock;
+
+ struct work_struct datawork;
+ spinlock_t lock;
+
+ struct regulator *vcc;
+
+ int burstlen;
+ int dmareq;
+ struct dma_slave_config dma_slave_config;
+ struct imx_dma_data dma_data;
+};
+
+static void mxcmci_set_clk_rate(struct mxcmci_host *host, unsigned int clk_ios);
+
+static inline void mxcmci_init_ocr(struct mxcmci_host *host)
+{
+ host->vcc = regulator_get(mmc_dev(host->mmc), "vmmc");
+
+ if (IS_ERR(host->vcc)) {
+ host->vcc = NULL;
+ } else {
+ host->mmc->ocr_avail = mmc_regulator_get_ocrmask(host->vcc);
+ if (host->pdata && host->pdata->ocr_avail)
+ dev_warn(mmc_dev(host->mmc),
+ "pdata->ocr_avail will not be used\n");
+ }
+
+ if (host->vcc == NULL) {
+ /* fall-back to platform data */
+ if (host->pdata && host->pdata->ocr_avail)
+ host->mmc->ocr_avail = host->pdata->ocr_avail;
+ else
+ host->mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+ }
+}
+
+static inline void mxcmci_set_power(struct mxcmci_host *host,
+ unsigned char power_mode,
+ unsigned int vdd)
+{
+ if (host->vcc) {
+ if (power_mode == MMC_POWER_UP)
+ mmc_regulator_set_ocr(host->mmc, host->vcc, vdd);
+ else if (power_mode == MMC_POWER_OFF)
+ mmc_regulator_set_ocr(host->mmc, host->vcc, 0);
+ }
+
+ if (host->pdata && host->pdata->setpower)
+ host->pdata->setpower(mmc_dev(host->mmc), vdd);
+}
+
+static inline int mxcmci_use_dma(struct mxcmci_host *host)
+{
+ return host->do_dma;
+}
+
+static void mxcmci_softreset(struct mxcmci_host *host)
+{
+ int i;
+
+ dev_dbg(mmc_dev(host->mmc), "mxcmci_softreset\n");
+
+ /* reset sequence */
+ writew(STR_STP_CLK_RESET, host->base + MMC_REG_STR_STP_CLK);
+ writew(STR_STP_CLK_RESET | STR_STP_CLK_START_CLK,
+ host->base + MMC_REG_STR_STP_CLK);
+
+ for (i = 0; i < 8; i++)
+ writew(STR_STP_CLK_START_CLK, host->base + MMC_REG_STR_STP_CLK);
+
+ writew(0xff, host->base + MMC_REG_RES_TO);
+}
+static int mxcmci_setup_dma(struct mmc_host *mmc);
+
+static int mxcmci_setup_data(struct mxcmci_host *host, struct mmc_data *data)
+{
+ unsigned int nob = data->blocks;
+ unsigned int blksz = data->blksz;
+ unsigned int datasize = nob * blksz;
+ struct scatterlist *sg;
+ int i, nents;
+
+ if (data->flags & MMC_DATA_STREAM)
+ nob = 0xffff;
+
+ host->data = data;
+ data->bytes_xfered = 0;
+
+ writew(nob, host->base + MMC_REG_NOB);
+ writew(blksz, host->base + MMC_REG_BLK_LEN);
+ host->datasize = datasize;
+
+ if (!mxcmci_use_dma(host))
+ return 0;
+
+ for_each_sg(data->sg, sg, data->sg_len, i) {
+ if (sg->offset & 3 || sg->length & 3) {
+ host->do_dma = 0;
+ return 0;
+ }
+ }
+
+ if (data->flags & MMC_DATA_READ)
+ host->dma_dir = DMA_FROM_DEVICE;
+ else
+ host->dma_dir = DMA_TO_DEVICE;
+
+ nents = dma_map_sg(host->dma->device->dev, data->sg,
+ data->sg_len, host->dma_dir);
+ if (nents != data->sg_len)
+ return -EINVAL;
+
+ host->desc = host->dma->device->device_prep_slave_sg(host->dma,
+ data->sg, data->sg_len, host->dma_dir,
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+
+ if (!host->desc) {
+ dma_unmap_sg(host->dma->device->dev, data->sg, data->sg_len,
+ host->dma_dir);
+ host->do_dma = 0;
+ return 0; /* Fall back to PIO */
+ }
+ wmb();
+
+ dmaengine_submit(host->desc);
+
+ return 0;
+}
+
+static int mxcmci_start_cmd(struct mxcmci_host *host, struct mmc_command *cmd,
+ unsigned int cmdat)
+{
+ u32 int_cntr = host->default_irq_mask;
+ unsigned long flags;
+
+ WARN_ON(host->cmd != NULL);
+ host->cmd = cmd;
+
+ switch (mmc_resp_type(cmd)) {
+ case MMC_RSP_R1: /* short CRC, OPCODE */
+ case MMC_RSP_R1B:/* short CRC, OPCODE, BUSY */
+ cmdat |= CMD_DAT_CONT_RESPONSE_48BIT_CRC;
+ break;
+ case MMC_RSP_R2: /* long 136 bit + CRC */
+ cmdat |= CMD_DAT_CONT_RESPONSE_136BIT;
+ break;
+ case MMC_RSP_R3: /* short */
+ cmdat |= CMD_DAT_CONT_RESPONSE_48BIT;
+ break;
+ case MMC_RSP_NONE:
+ break;
+ default:
+ dev_err(mmc_dev(host->mmc), "unhandled response type 0x%x\n",
+ mmc_resp_type(cmd));
+ cmd->error = -EINVAL;
+ return -EINVAL;
+ }
+
+ int_cntr = INT_END_CMD_RES_EN;
+
+ if (mxcmci_use_dma(host))
+ int_cntr |= INT_READ_OP_EN | INT_WRITE_OP_DONE_EN;
+
+ spin_lock_irqsave(&host->lock, flags);
+ if (host->use_sdio)
+ int_cntr |= INT_SDIO_IRQ_EN;
+ writel(int_cntr, host->base + MMC_REG_INT_CNTR);
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ writew(cmd->opcode, host->base + MMC_REG_CMD);
+ writel(cmd->arg, host->base + MMC_REG_ARG);
+ writew(cmdat, host->base + MMC_REG_CMD_DAT_CONT);
+
+ return 0;
+}
+
+static void mxcmci_finish_request(struct mxcmci_host *host,
+ struct mmc_request *req)
+{
+ u32 int_cntr = host->default_irq_mask;
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+ if (host->use_sdio)
+ int_cntr |= INT_SDIO_IRQ_EN;
+ writel(int_cntr, host->base + MMC_REG_INT_CNTR);
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ host->req = NULL;
+ host->cmd = NULL;
+ host->data = NULL;
+
+ mmc_request_done(host->mmc, req);
+}
+
+static int mxcmci_finish_data(struct mxcmci_host *host, unsigned int stat)
+{
+ struct mmc_data *data = host->data;
+ int data_error;
+
+ if (mxcmci_use_dma(host)) {
+ dmaengine_terminate_all(host->dma);
+ dma_unmap_sg(host->dma->device->dev, data->sg, data->sg_len,
+ host->dma_dir);
+ }
+
+ if (stat & STATUS_ERR_MASK) {
+ dev_dbg(mmc_dev(host->mmc), "request failed. status: 0x%08x\n",
+ stat);
+ if (stat & STATUS_CRC_READ_ERR) {
+ dev_err(mmc_dev(host->mmc), "%s: -EILSEQ\n", __func__);
+ data->error = -EILSEQ;
+ } else if (stat & STATUS_CRC_WRITE_ERR) {
+ u32 err_code = (stat >> 9) & 0x3;
+ if (err_code == 2) { /* No CRC response */
+ dev_err(mmc_dev(host->mmc),
+ "%s: No CRC -ETIMEDOUT\n", __func__);
+ data->error = -ETIMEDOUT;
+ } else {
+ dev_err(mmc_dev(host->mmc),
+ "%s: -EILSEQ\n", __func__);
+ data->error = -EILSEQ;
+ }
+ } else if (stat & STATUS_TIME_OUT_READ) {
+ dev_err(mmc_dev(host->mmc),
+ "%s: read -ETIMEDOUT\n", __func__);
+ data->error = -ETIMEDOUT;
+ } else {
+ dev_err(mmc_dev(host->mmc), "%s: -EIO\n", __func__);
+ data->error = -EIO;
+ }
+ } else {
+ data->bytes_xfered = host->datasize;
+ }
+
+ data_error = data->error;
+
+ host->data = NULL;
+
+ return data_error;
+}
+
+static void mxcmci_read_response(struct mxcmci_host *host, unsigned int stat)
+{
+ struct mmc_command *cmd = host->cmd;
+ int i;
+ u32 a, b, c;
+
+ if (!cmd)
+ return;
+
+ if (stat & STATUS_TIME_OUT_RESP) {
+ dev_dbg(mmc_dev(host->mmc), "CMD TIMEOUT\n");
+ cmd->error = -ETIMEDOUT;
+ } else if (stat & STATUS_RESP_CRC_ERR && cmd->flags & MMC_RSP_CRC) {
+ dev_dbg(mmc_dev(host->mmc), "cmd crc error\n");
+ cmd->error = -EILSEQ;
+ }
+
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ if (cmd->flags & MMC_RSP_136) {
+ for (i = 0; i < 4; i++) {
+ a = readw(host->base + MMC_REG_RES_FIFO);
+ b = readw(host->base + MMC_REG_RES_FIFO);
+ cmd->resp[i] = a << 16 | b;
+ }
+ } else {
+ a = readw(host->base + MMC_REG_RES_FIFO);
+ b = readw(host->base + MMC_REG_RES_FIFO);
+ c = readw(host->base + MMC_REG_RES_FIFO);
+ cmd->resp[0] = a << 24 | b << 8 | c >> 8;
+ }
+ }
+}
+
+static int mxcmci_poll_status(struct mxcmci_host *host, u32 mask)
+{
+ u32 stat;
+ unsigned long timeout = jiffies + HZ;
+
+ do {
+ stat = readl(host->base + MMC_REG_STATUS);
+ if (stat & STATUS_ERR_MASK)
+ return stat;
+ if (time_after(jiffies, timeout)) {
+ mxcmci_softreset(host);
+ mxcmci_set_clk_rate(host, host->clock);
+ return STATUS_TIME_OUT_READ;
+ }
+ if (stat & mask)
+ return 0;
+ cpu_relax();
+ } while (1);
+}
+
+static int mxcmci_pull(struct mxcmci_host *host, void *_buf, int bytes)
+{
+ unsigned int stat;
+ u32 *buf = _buf;
+
+ while (bytes > 3) {
+ stat = mxcmci_poll_status(host,
+ STATUS_BUF_READ_RDY | STATUS_READ_OP_DONE);
+ if (stat)
+ return stat;
+ *buf++ = readl(host->base + MMC_REG_BUFFER_ACCESS);
+ bytes -= 4;
+ }
+
+ if (bytes) {
+ u8 *b = (u8 *)buf;
+ u32 tmp;
+
+ stat = mxcmci_poll_status(host,
+ STATUS_BUF_READ_RDY | STATUS_READ_OP_DONE);
+ if (stat)
+ return stat;
+ tmp = readl(host->base + MMC_REG_BUFFER_ACCESS);
+ memcpy(b, &tmp, bytes);
+ }
+
+ return 0;
+}
+
+static int mxcmci_push(struct mxcmci_host *host, void *_buf, int bytes)
+{
+ unsigned int stat;
+ u32 *buf = _buf;
+
+ while (bytes > 3) {
+ stat = mxcmci_poll_status(host, STATUS_BUF_WRITE_RDY);
+ if (stat)
+ return stat;
+ writel(*buf++, host->base + MMC_REG_BUFFER_ACCESS);
+ bytes -= 4;
+ }
+
+ if (bytes) {
+ u8 *b = (u8 *)buf;
+ u32 tmp;
+
+ stat = mxcmci_poll_status(host, STATUS_BUF_WRITE_RDY);
+ if (stat)
+ return stat;
+
+ memcpy(&tmp, b, bytes);
+ writel(tmp, host->base + MMC_REG_BUFFER_ACCESS);
+ }
+
+ stat = mxcmci_poll_status(host, STATUS_BUF_WRITE_RDY);
+ if (stat)
+ return stat;
+
+ return 0;
+}
+
+static int mxcmci_transfer_data(struct mxcmci_host *host)
+{
+ struct mmc_data *data = host->req->data;
+ struct scatterlist *sg;
+ int stat, i;
+
+ host->data = data;
+ host->datasize = 0;
+
+ if (data->flags & MMC_DATA_READ) {
+ for_each_sg(data->sg, sg, data->sg_len, i) {
+ stat = mxcmci_pull(host, sg_virt(sg), sg->length);
+ if (stat)
+ return stat;
+ host->datasize += sg->length;
+ }
+ } else {
+ for_each_sg(data->sg, sg, data->sg_len, i) {
+ stat = mxcmci_push(host, sg_virt(sg), sg->length);
+ if (stat)
+ return stat;
+ host->datasize += sg->length;
+ }
+ stat = mxcmci_poll_status(host, STATUS_WRITE_OP_DONE);
+ if (stat)
+ return stat;
+ }
+ return 0;
+}
+
+static void mxcmci_datawork(struct work_struct *work)
+{
+ struct mxcmci_host *host = container_of(work, struct mxcmci_host,
+ datawork);
+ int datastat = mxcmci_transfer_data(host);
+
+ writel(STATUS_READ_OP_DONE | STATUS_WRITE_OP_DONE,
+ host->base + MMC_REG_STATUS);
+ mxcmci_finish_data(host, datastat);
+
+ if (host->req->stop) {
+ if (mxcmci_start_cmd(host, host->req->stop, 0)) {
+ mxcmci_finish_request(host, host->req);
+ return;
+ }
+ } else {
+ mxcmci_finish_request(host, host->req);
+ }
+}
+
+static void mxcmci_data_done(struct mxcmci_host *host, unsigned int stat)
+{
+ struct mmc_data *data = host->data;
+ int data_error;
+
+ if (!data)
+ return;
+
+ data_error = mxcmci_finish_data(host, stat);
+
+ mxcmci_read_response(host, stat);
+ host->cmd = NULL;
+
+ if (host->req->stop) {
+ if (mxcmci_start_cmd(host, host->req->stop, 0)) {
+ mxcmci_finish_request(host, host->req);
+ return;
+ }
+ } else {
+ mxcmci_finish_request(host, host->req);
+ }
+}
+
+static void mxcmci_cmd_done(struct mxcmci_host *host, unsigned int stat)
+{
+ mxcmci_read_response(host, stat);
+ host->cmd = NULL;
+
+ if (!host->data && host->req) {
+ mxcmci_finish_request(host, host->req);
+ return;
+ }
+
+ /* For the DMA case the DMA engine handles the data transfer
+ * automatically. For non DMA we have to do it ourselves.
+ * Don't do it in interrupt context though.
+ */
+ if (!mxcmci_use_dma(host) && host->data)
+ schedule_work(&host->datawork);
+
+}
+
+static irqreturn_t mxcmci_irq(int irq, void *devid)
+{
+ struct mxcmci_host *host = devid;
+ unsigned long flags;
+ bool sdio_irq;
+ u32 stat;
+
+ stat = readl(host->base + MMC_REG_STATUS);
+ writel(stat & ~(STATUS_SDIO_INT_ACTIVE | STATUS_DATA_TRANS_DONE |
+ STATUS_WRITE_OP_DONE), host->base + MMC_REG_STATUS);
+
+ dev_dbg(mmc_dev(host->mmc), "%s: 0x%08x\n", __func__, stat);
+
+ spin_lock_irqsave(&host->lock, flags);
+ sdio_irq = (stat & STATUS_SDIO_INT_ACTIVE) && host->use_sdio;
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ if (mxcmci_use_dma(host) &&
+ (stat & (STATUS_READ_OP_DONE | STATUS_WRITE_OP_DONE)))
+ writel(STATUS_READ_OP_DONE | STATUS_WRITE_OP_DONE,
+ host->base + MMC_REG_STATUS);
+
+ if (sdio_irq) {
+ writel(STATUS_SDIO_INT_ACTIVE, host->base + MMC_REG_STATUS);
+ mmc_signal_sdio_irq(host->mmc);
+ }
+
+ if (stat & STATUS_END_CMD_RESP)
+ mxcmci_cmd_done(host, stat);
+
+ if (mxcmci_use_dma(host) &&
+ (stat & (STATUS_DATA_TRANS_DONE | STATUS_WRITE_OP_DONE)))
+ mxcmci_data_done(host, stat);
+
+ if (host->default_irq_mask &&
+ (stat & (STATUS_CARD_INSERTION | STATUS_CARD_REMOVAL)))
+ mmc_detect_change(host->mmc, msecs_to_jiffies(200));
+
+ return IRQ_HANDLED;
+}
+
+static void mxcmci_request(struct mmc_host *mmc, struct mmc_request *req)
+{
+ struct mxcmci_host *host = mmc_priv(mmc);
+ unsigned int cmdat = host->cmdat;
+ int error;
+
+ WARN_ON(host->req != NULL);
+
+ host->req = req;
+ host->cmdat &= ~CMD_DAT_CONT_INIT;
+
+ if (host->dma)
+ host->do_dma = 1;
+
+ if (req->data) {
+ error = mxcmci_setup_data(host, req->data);
+ if (error) {
+ req->cmd->error = error;
+ goto out;
+ }
+
+
+ cmdat |= CMD_DAT_CONT_DATA_ENABLE;
+
+ if (req->data->flags & MMC_DATA_WRITE)
+ cmdat |= CMD_DAT_CONT_WRITE;
+ }
+
+ error = mxcmci_start_cmd(host, req->cmd, cmdat);
+
+out:
+ if (error)
+ mxcmci_finish_request(host, req);
+}
+
+static void mxcmci_set_clk_rate(struct mxcmci_host *host, unsigned int clk_ios)
+{
+ unsigned int divider;
+ int prescaler = 0;
+ unsigned int clk_in = clk_get_rate(host->clk);
+
+ while (prescaler <= 0x800) {
+ for (divider = 1; divider <= 0xF; divider++) {
+ int x;
+
+ x = (clk_in / (divider + 1));
+
+ if (prescaler)
+ x /= (prescaler * 2);
+
+ if (x <= clk_ios)
+ break;
+ }
+ if (divider < 0x10)
+ break;
+
+ if (prescaler == 0)
+ prescaler = 1;
+ else
+ prescaler <<= 1;
+ }
+
+ writew((prescaler << 4) | divider, host->base + MMC_REG_CLK_RATE);
+
+ dev_dbg(mmc_dev(host->mmc), "scaler: %d divider: %d in: %d out: %d\n",
+ prescaler, divider, clk_in, clk_ios);
+}
+
+static int mxcmci_setup_dma(struct mmc_host *mmc)
+{
+ struct mxcmci_host *host = mmc_priv(mmc);
+ struct dma_slave_config *config = &host->dma_slave_config;
+
+ config->dst_addr = host->res->start + MMC_REG_BUFFER_ACCESS;
+ config->src_addr = host->res->start + MMC_REG_BUFFER_ACCESS;
+ config->dst_addr_width = 4;
+ config->src_addr_width = 4;
+ config->dst_maxburst = host->burstlen;
+ config->src_maxburst = host->burstlen;
+
+ return dmaengine_slave_config(host->dma, config);
+}
+
+static void mxcmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct mxcmci_host *host = mmc_priv(mmc);
+ int burstlen, ret;
+
+ /*
+ * use burstlen of 64 in 4 bit mode (--> reg value 0)
+ * use burstlen of 16 in 1 bit mode (--> reg value 16)
+ */
+ if (ios->bus_width == MMC_BUS_WIDTH_4)
+ burstlen = 64;
+ else
+ burstlen = 16;
+
+ if (mxcmci_use_dma(host) && burstlen != host->burstlen) {
+ host->burstlen = burstlen;
+ ret = mxcmci_setup_dma(mmc);
+ if (ret) {
+ dev_err(mmc_dev(host->mmc),
+ "failed to config DMA channel. Falling back to PIO\n");
+ dma_release_channel(host->dma);
+ host->do_dma = 0;
+ host->dma = NULL;
+ }
+ }
+
+ if (ios->bus_width == MMC_BUS_WIDTH_4)
+ host->cmdat |= CMD_DAT_CONT_BUS_WIDTH_4;
+ else
+ host->cmdat &= ~CMD_DAT_CONT_BUS_WIDTH_4;
+
+ if (host->power_mode != ios->power_mode) {
+ mxcmci_set_power(host, ios->power_mode, ios->vdd);
+ host->power_mode = ios->power_mode;
+
+ if (ios->power_mode == MMC_POWER_ON)
+ host->cmdat |= CMD_DAT_CONT_INIT;
+ }
+
+ if (ios->clock) {
+ mxcmci_set_clk_rate(host, ios->clock);
+ writew(STR_STP_CLK_START_CLK, host->base + MMC_REG_STR_STP_CLK);
+ } else {
+ writew(STR_STP_CLK_STOP_CLK, host->base + MMC_REG_STR_STP_CLK);
+ }
+
+ host->clock = ios->clock;
+}
+
+static irqreturn_t mxcmci_detect_irq(int irq, void *data)
+{
+ struct mmc_host *mmc = data;
+
+ dev_dbg(mmc_dev(mmc), "%s\n", __func__);
+
+ mmc_detect_change(mmc, msecs_to_jiffies(250));
+ return IRQ_HANDLED;
+}
+
+static int mxcmci_get_ro(struct mmc_host *mmc)
+{
+ struct mxcmci_host *host = mmc_priv(mmc);
+
+ if (host->pdata && host->pdata->get_ro)
+ 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 mxcmci_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+ struct mxcmci_host *host = mmc_priv(mmc);
+ unsigned long flags;
+ u32 int_cntr;
+
+ spin_lock_irqsave(&host->lock, flags);
+ host->use_sdio = enable;
+ int_cntr = readl(host->base + MMC_REG_INT_CNTR);
+
+ if (enable)
+ int_cntr |= INT_SDIO_IRQ_EN;
+ else
+ int_cntr &= ~INT_SDIO_IRQ_EN;
+
+ writel(int_cntr, host->base + MMC_REG_INT_CNTR);
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void mxcmci_init_card(struct mmc_host *host, struct mmc_card *card)
+{
+ /*
+ * MX3 SoCs have a silicon bug which corrupts CRC calculation of
+ * multi-block transfers when connected SDIO peripheral doesn't
+ * drive the BUSY line as required by the specs.
+ * One way to prevent this is to only allow 1-bit transfers.
+ */
+
+ if (cpu_is_mx3() && card->type == MMC_TYPE_SDIO)
+ host->caps &= ~MMC_CAP_4_BIT_DATA;
+ else
+ host->caps |= MMC_CAP_4_BIT_DATA;
+}
+
+static bool filter(struct dma_chan *chan, void *param)
+{
+ struct mxcmci_host *host = param;
+
+ if (!imx_dma_is_general_purpose(chan))
+ return false;
+
+ chan->private = &host->dma_data;
+
+ return true;
+}
+
+static const struct mmc_host_ops mxcmci_ops = {
+ .request = mxcmci_request,
+ .set_ios = mxcmci_set_ios,
+ .get_ro = mxcmci_get_ro,
+ .enable_sdio_irq = mxcmci_enable_sdio_irq,
+ .init_card = mxcmci_init_card,
+};
+
+static int mxcmci_probe(struct platform_device *pdev)
+{
+ struct mmc_host *mmc;
+ struct mxcmci_host *host = NULL;
+ struct resource *iores, *r;
+ int ret = 0, irq;
+ dma_cap_mask_t mask;
+
+ printk(KERN_INFO "i.MX SDHC driver\n");
+
+ iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ irq = platform_get_irq(pdev, 0);
+ if (!iores || irq < 0)
+ return -EINVAL;
+
+ r = request_mem_region(iores->start, resource_size(iores), pdev->name);
+ if (!r)
+ return -EBUSY;
+
+ mmc = mmc_alloc_host(sizeof(struct mxcmci_host), &pdev->dev);
+ if (!mmc) {
+ ret = -ENOMEM;
+ goto out_release_mem;
+ }
+
+ mmc->ops = &mxcmci_ops;
+ mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;
+
+ /* MMC core transfer sizes tunable parameters */
+ mmc->max_segs = 64;
+ mmc->max_blk_size = 2048;
+ mmc->max_blk_count = 65535;
+ mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
+ mmc->max_seg_size = mmc->max_req_size;
+
+ host = mmc_priv(mmc);
+ host->base = ioremap(r->start, resource_size(r));
+ if (!host->base) {
+ ret = -ENOMEM;
+ goto out_free;
+ }
+
+ host->mmc = mmc;
+ host->pdata = pdev->dev.platform_data;
+ spin_lock_init(&host->lock);
+
+ mxcmci_init_ocr(host);
+
+ if (host->pdata && host->pdata->dat3_card_detect)
+ host->default_irq_mask =
+ INT_CARD_INSERTION_EN | INT_CARD_REMOVAL_EN;
+ else
+ host->default_irq_mask = 0;
+
+ host->res = r;
+ host->irq = irq;
+
+ host->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(host->clk)) {
+ ret = PTR_ERR(host->clk);
+ goto out_iounmap;
+ }
+ clk_enable(host->clk);
+
+ mxcmci_softreset(host);
+
+ host->rev_no = readw(host->base + MMC_REG_REV_NO);
+ if (host->rev_no != 0x400) {
+ ret = -ENODEV;
+ dev_err(mmc_dev(host->mmc), "wrong rev.no. 0x%08x. aborting.\n",
+ host->rev_no);
+ goto out_clk_put;
+ }
+
+ mmc->f_min = clk_get_rate(host->clk) >> 16;
+ mmc->f_max = clk_get_rate(host->clk) >> 1;
+
+ /* recommended in data sheet */
+ writew(0x2db4, host->base + MMC_REG_READ_TO);
+
+ writel(host->default_irq_mask, host->base + MMC_REG_INT_CNTR);
+
+ r = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ if (r) {
+ host->dmareq = r->start;
+ host->dma_data.peripheral_type = IMX_DMATYPE_SDHC;
+ host->dma_data.priority = DMA_PRIO_LOW;
+ host->dma_data.dma_request = host->dmareq;
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+ host->dma = dma_request_channel(mask, filter, host);
+ if (host->dma)
+ mmc->max_seg_size = dma_get_max_seg_size(
+ host->dma->device->dev);
+ }
+
+ if (!host->dma)
+ dev_info(mmc_dev(host->mmc), "dma not available. Using PIO\n");
+
+ INIT_WORK(&host->datawork, mxcmci_datawork);
+
+ ret = request_irq(host->irq, mxcmci_irq, 0, DRIVER_NAME, host);
+ if (ret)
+ goto out_free_dma;
+
+ platform_set_drvdata(pdev, mmc);
+
+ if (host->pdata && host->pdata->init) {
+ ret = host->pdata->init(&pdev->dev, mxcmci_detect_irq,
+ host->mmc);
+ if (ret)
+ goto out_free_irq;
+ }
+
+ mmc_add_host(mmc);
+
+ return 0;
+
+out_free_irq:
+ free_irq(host->irq, host);
+out_free_dma:
+ if (host->dma)
+ dma_release_channel(host->dma);
+out_clk_put:
+ clk_disable(host->clk);
+ clk_put(host->clk);
+out_iounmap:
+ iounmap(host->base);
+out_free:
+ mmc_free_host(mmc);
+out_release_mem:
+ release_mem_region(iores->start, resource_size(iores));
+ return ret;
+}
+
+static int mxcmci_remove(struct platform_device *pdev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(pdev);
+ struct mxcmci_host *host = mmc_priv(mmc);
+
+ platform_set_drvdata(pdev, NULL);
+
+ mmc_remove_host(mmc);
+
+ if (host->vcc)
+ regulator_put(host->vcc);
+
+ if (host->pdata && host->pdata->exit)
+ host->pdata->exit(&pdev->dev, mmc);
+
+ free_irq(host->irq, host);
+ iounmap(host->base);
+
+ if (host->dma)
+ dma_release_channel(host->dma);
+
+ clk_disable(host->clk);
+ clk_put(host->clk);
+
+ release_mem_region(host->res->start, resource_size(host->res));
+
+ mmc_free_host(mmc);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int mxcmci_suspend(struct device *dev)
+{
+ struct mmc_host *mmc = dev_get_drvdata(dev);
+ struct mxcmci_host *host = mmc_priv(mmc);
+ int ret = 0;
+
+ if (mmc)
+ ret = mmc_suspend_host(mmc);
+ clk_disable(host->clk);
+
+ return ret;
+}
+
+static int mxcmci_resume(struct device *dev)
+{
+ struct mmc_host *mmc = dev_get_drvdata(dev);
+ struct mxcmci_host *host = mmc_priv(mmc);
+ int ret = 0;
+
+ clk_enable(host->clk);
+ if (mmc)
+ ret = mmc_resume_host(mmc);
+
+ return ret;
+}
+
+static const struct dev_pm_ops mxcmci_pm_ops = {
+ .suspend = mxcmci_suspend,
+ .resume = mxcmci_resume,
+};
+#endif
+
+static struct platform_driver mxcmci_driver = {
+ .probe = mxcmci_probe,
+ .remove = mxcmci_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &mxcmci_pm_ops,
+#endif
+ }
+};
+
+static int __init mxcmci_init(void)
+{
+ return platform_driver_register(&mxcmci_driver);
+}
+
+static void __exit mxcmci_exit(void)
+{
+ platform_driver_unregister(&mxcmci_driver);
+}
+
+module_init(mxcmci_init);
+module_exit(mxcmci_exit);
+
+MODULE_DESCRIPTION("i.MX Multimedia Card Interface Driver");
+MODULE_AUTHOR("Sascha Hauer, Pengutronix");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-mmc");
diff --git a/drivers/mmc/host/mxs-mmc.c b/drivers/mmc/host/mxs-mmc.c
new file mode 100644
index 00000000..d513d473
--- /dev/null
+++ b/drivers/mmc/host/mxs-mmc.c
@@ -0,0 +1,872 @@
+/*
+ * Portions copyright (C) 2003 Russell King, PXA MMCI Driver
+ * Portions copyright (C) 2004-2005 Pierre Ossman, W83L51xD SD/MMC driver
+ *
+ * Copyright 2008 Embedded Alley Solutions, Inc.
+ * Copyright 2009-2011 Freescale Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/highmem.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/completion.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/sdio.h>
+#include <linux/gpio.h>
+#include <linux/regulator/consumer.h>
+
+#include <mach/mxs.h>
+#include <mach/common.h>
+#include <mach/dma.h>
+#include <mach/mmc.h>
+
+#define DRIVER_NAME "mxs-mmc"
+
+/* card detect polling timeout */
+#define MXS_MMC_DETECT_TIMEOUT (HZ/2)
+
+#define SSP_VERSION_LATEST 4
+#define ssp_is_old() (host->version < SSP_VERSION_LATEST)
+
+/* SSP registers */
+#define HW_SSP_CTRL0 0x000
+#define BM_SSP_CTRL0_RUN (1 << 29)
+#define BM_SSP_CTRL0_SDIO_IRQ_CHECK (1 << 28)
+#define BM_SSP_CTRL0_IGNORE_CRC (1 << 26)
+#define BM_SSP_CTRL0_READ (1 << 25)
+#define BM_SSP_CTRL0_DATA_XFER (1 << 24)
+#define BP_SSP_CTRL0_BUS_WIDTH (22)
+#define BM_SSP_CTRL0_BUS_WIDTH (0x3 << 22)
+#define BM_SSP_CTRL0_WAIT_FOR_IRQ (1 << 21)
+#define BM_SSP_CTRL0_LONG_RESP (1 << 19)
+#define BM_SSP_CTRL0_GET_RESP (1 << 17)
+#define BM_SSP_CTRL0_ENABLE (1 << 16)
+#define BP_SSP_CTRL0_XFER_COUNT (0)
+#define BM_SSP_CTRL0_XFER_COUNT (0xffff)
+#define HW_SSP_CMD0 0x010
+#define BM_SSP_CMD0_DBL_DATA_RATE_EN (1 << 25)
+#define BM_SSP_CMD0_SLOW_CLKING_EN (1 << 22)
+#define BM_SSP_CMD0_CONT_CLKING_EN (1 << 21)
+#define BM_SSP_CMD0_APPEND_8CYC (1 << 20)
+#define BP_SSP_CMD0_BLOCK_SIZE (16)
+#define BM_SSP_CMD0_BLOCK_SIZE (0xf << 16)
+#define BP_SSP_CMD0_BLOCK_COUNT (8)
+#define BM_SSP_CMD0_BLOCK_COUNT (0xff << 8)
+#define BP_SSP_CMD0_CMD (0)
+#define BM_SSP_CMD0_CMD (0xff)
+#define HW_SSP_CMD1 0x020
+#define HW_SSP_XFER_SIZE 0x030
+#define HW_SSP_BLOCK_SIZE 0x040
+#define BP_SSP_BLOCK_SIZE_BLOCK_COUNT (4)
+#define BM_SSP_BLOCK_SIZE_BLOCK_COUNT (0xffffff << 4)
+#define BP_SSP_BLOCK_SIZE_BLOCK_SIZE (0)
+#define BM_SSP_BLOCK_SIZE_BLOCK_SIZE (0xf)
+#define HW_SSP_TIMING (ssp_is_old() ? 0x050 : 0x070)
+#define BP_SSP_TIMING_TIMEOUT (16)
+#define BM_SSP_TIMING_TIMEOUT (0xffff << 16)
+#define BP_SSP_TIMING_CLOCK_DIVIDE (8)
+#define BM_SSP_TIMING_CLOCK_DIVIDE (0xff << 8)
+#define BP_SSP_TIMING_CLOCK_RATE (0)
+#define BM_SSP_TIMING_CLOCK_RATE (0xff)
+#define HW_SSP_CTRL1 (ssp_is_old() ? 0x060 : 0x080)
+#define BM_SSP_CTRL1_SDIO_IRQ (1 << 31)
+#define BM_SSP_CTRL1_SDIO_IRQ_EN (1 << 30)
+#define BM_SSP_CTRL1_RESP_ERR_IRQ (1 << 29)
+#define BM_SSP_CTRL1_RESP_ERR_IRQ_EN (1 << 28)
+#define BM_SSP_CTRL1_RESP_TIMEOUT_IRQ (1 << 27)
+#define BM_SSP_CTRL1_RESP_TIMEOUT_IRQ_EN (1 << 26)
+#define BM_SSP_CTRL1_DATA_TIMEOUT_IRQ (1 << 25)
+#define BM_SSP_CTRL1_DATA_TIMEOUT_IRQ_EN (1 << 24)
+#define BM_SSP_CTRL1_DATA_CRC_IRQ (1 << 23)
+#define BM_SSP_CTRL1_DATA_CRC_IRQ_EN (1 << 22)
+#define BM_SSP_CTRL1_FIFO_UNDERRUN_IRQ (1 << 21)
+#define BM_SSP_CTRL1_FIFO_UNDERRUN_IRQ_EN (1 << 20)
+#define BM_SSP_CTRL1_RECV_TIMEOUT_IRQ (1 << 17)
+#define BM_SSP_CTRL1_RECV_TIMEOUT_IRQ_EN (1 << 16)
+#define BM_SSP_CTRL1_FIFO_OVERRUN_IRQ (1 << 15)
+#define BM_SSP_CTRL1_FIFO_OVERRUN_IRQ_EN (1 << 14)
+#define BM_SSP_CTRL1_DMA_ENABLE (1 << 13)
+#define BM_SSP_CTRL1_POLARITY (1 << 9)
+#define BP_SSP_CTRL1_WORD_LENGTH (4)
+#define BM_SSP_CTRL1_WORD_LENGTH (0xf << 4)
+#define BP_SSP_CTRL1_SSP_MODE (0)
+#define BM_SSP_CTRL1_SSP_MODE (0xf)
+#define HW_SSP_SDRESP0 (ssp_is_old() ? 0x080 : 0x0a0)
+#define HW_SSP_SDRESP1 (ssp_is_old() ? 0x090 : 0x0b0)
+#define HW_SSP_SDRESP2 (ssp_is_old() ? 0x0a0 : 0x0c0)
+#define HW_SSP_SDRESP3 (ssp_is_old() ? 0x0b0 : 0x0d0)
+#define HW_SSP_STATUS (ssp_is_old() ? 0x0c0 : 0x100)
+#define BM_SSP_STATUS_CARD_DETECT (1 << 28)
+#define BM_SSP_STATUS_SDIO_IRQ (1 << 17)
+#define HW_SSP_VERSION (cpu_is_mx23() ? 0x110 : 0x130)
+#define BP_SSP_VERSION_MAJOR (24)
+
+#define BF_SSP(value, field) (((value) << BP_SSP_##field) & BM_SSP_##field)
+
+#define MXS_MMC_IRQ_BITS (BM_SSP_CTRL1_SDIO_IRQ | \
+ BM_SSP_CTRL1_RESP_ERR_IRQ | \
+ BM_SSP_CTRL1_RESP_TIMEOUT_IRQ | \
+ BM_SSP_CTRL1_DATA_TIMEOUT_IRQ | \
+ BM_SSP_CTRL1_DATA_CRC_IRQ | \
+ BM_SSP_CTRL1_FIFO_UNDERRUN_IRQ | \
+ BM_SSP_CTRL1_RECV_TIMEOUT_IRQ | \
+ BM_SSP_CTRL1_FIFO_OVERRUN_IRQ)
+
+#define SSP_PIO_NUM 3
+
+struct mxs_mmc_host {
+ struct mmc_host *mmc;
+ struct mmc_request *mrq;
+ struct mmc_command *cmd;
+ struct mmc_data *data;
+
+ void __iomem *base;
+ int irq;
+ struct resource *res;
+ struct resource *dma_res;
+ struct clk *clk;
+ unsigned int clk_rate;
+
+ struct dma_chan *dmach;
+ struct mxs_dma_data dma_data;
+ unsigned int dma_dir;
+ u32 ssp_pio_words[SSP_PIO_NUM];
+
+ unsigned int version;
+ unsigned char bus_width;
+ spinlock_t lock;
+ int sdio_irq_en;
+};
+
+static int mxs_mmc_get_ro(struct mmc_host *mmc)
+{
+ struct mxs_mmc_host *host = mmc_priv(mmc);
+ struct mxs_mmc_platform_data *pdata =
+ mmc_dev(host->mmc)->platform_data;
+
+ if (!pdata)
+ return -EFAULT;
+
+ if (!gpio_is_valid(pdata->wp_gpio))
+ return -EINVAL;
+
+ return gpio_get_value(pdata->wp_gpio);
+}
+
+static int mxs_mmc_get_cd(struct mmc_host *mmc)
+{
+ struct mxs_mmc_host *host = mmc_priv(mmc);
+
+ return !(readl(host->base + HW_SSP_STATUS) &
+ BM_SSP_STATUS_CARD_DETECT);
+}
+
+static void mxs_mmc_reset(struct mxs_mmc_host *host)
+{
+ u32 ctrl0, ctrl1;
+
+ mxs_reset_block(host->base);
+
+ ctrl0 = BM_SSP_CTRL0_IGNORE_CRC;
+ ctrl1 = BF_SSP(0x3, CTRL1_SSP_MODE) |
+ BF_SSP(0x7, CTRL1_WORD_LENGTH) |
+ BM_SSP_CTRL1_DMA_ENABLE |
+ BM_SSP_CTRL1_POLARITY |
+ BM_SSP_CTRL1_RECV_TIMEOUT_IRQ_EN |
+ BM_SSP_CTRL1_DATA_CRC_IRQ_EN |
+ BM_SSP_CTRL1_DATA_TIMEOUT_IRQ_EN |
+ BM_SSP_CTRL1_RESP_TIMEOUT_IRQ_EN |
+ BM_SSP_CTRL1_RESP_ERR_IRQ_EN;
+
+ writel(BF_SSP(0xffff, TIMING_TIMEOUT) |
+ BF_SSP(2, TIMING_CLOCK_DIVIDE) |
+ BF_SSP(0, TIMING_CLOCK_RATE),
+ host->base + HW_SSP_TIMING);
+
+ if (host->sdio_irq_en) {
+ ctrl0 |= BM_SSP_CTRL0_SDIO_IRQ_CHECK;
+ ctrl1 |= BM_SSP_CTRL1_SDIO_IRQ_EN;
+ }
+
+ writel(ctrl0, host->base + HW_SSP_CTRL0);
+ writel(ctrl1, host->base + HW_SSP_CTRL1);
+}
+
+static void mxs_mmc_start_cmd(struct mxs_mmc_host *host,
+ struct mmc_command *cmd);
+
+static void mxs_mmc_request_done(struct mxs_mmc_host *host)
+{
+ struct mmc_command *cmd = host->cmd;
+ struct mmc_data *data = host->data;
+ struct mmc_request *mrq = host->mrq;
+
+ if (mmc_resp_type(cmd) & MMC_RSP_PRESENT) {
+ if (mmc_resp_type(cmd) & MMC_RSP_136) {
+ cmd->resp[3] = readl(host->base + HW_SSP_SDRESP0);
+ cmd->resp[2] = readl(host->base + HW_SSP_SDRESP1);
+ cmd->resp[1] = readl(host->base + HW_SSP_SDRESP2);
+ cmd->resp[0] = readl(host->base + HW_SSP_SDRESP3);
+ } else {
+ cmd->resp[0] = readl(host->base + HW_SSP_SDRESP0);
+ }
+ }
+
+ if (data) {
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg,
+ data->sg_len, host->dma_dir);
+ /*
+ * If there was an error on any block, we mark all
+ * data blocks as being in error.
+ */
+ if (!data->error)
+ data->bytes_xfered = data->blocks * data->blksz;
+ else
+ data->bytes_xfered = 0;
+
+ host->data = NULL;
+ if (mrq->stop) {
+ mxs_mmc_start_cmd(host, mrq->stop);
+ return;
+ }
+ }
+
+ host->mrq = NULL;
+ mmc_request_done(host->mmc, mrq);
+}
+
+static void mxs_mmc_dma_irq_callback(void *param)
+{
+ struct mxs_mmc_host *host = param;
+
+ mxs_mmc_request_done(host);
+}
+
+static irqreturn_t mxs_mmc_irq_handler(int irq, void *dev_id)
+{
+ struct mxs_mmc_host *host = dev_id;
+ struct mmc_command *cmd = host->cmd;
+ struct mmc_data *data = host->data;
+ u32 stat;
+
+ spin_lock(&host->lock);
+
+ stat = readl(host->base + HW_SSP_CTRL1);
+ writel(stat & MXS_MMC_IRQ_BITS,
+ host->base + HW_SSP_CTRL1 + MXS_CLR_ADDR);
+
+ if ((stat & BM_SSP_CTRL1_SDIO_IRQ) && (stat & BM_SSP_CTRL1_SDIO_IRQ_EN))
+ mmc_signal_sdio_irq(host->mmc);
+
+ spin_unlock(&host->lock);
+
+ if (stat & BM_SSP_CTRL1_RESP_TIMEOUT_IRQ)
+ cmd->error = -ETIMEDOUT;
+ else if (stat & BM_SSP_CTRL1_RESP_ERR_IRQ)
+ cmd->error = -EIO;
+
+ if (data) {
+ if (stat & (BM_SSP_CTRL1_DATA_TIMEOUT_IRQ |
+ BM_SSP_CTRL1_RECV_TIMEOUT_IRQ))
+ data->error = -ETIMEDOUT;
+ else if (stat & BM_SSP_CTRL1_DATA_CRC_IRQ)
+ data->error = -EILSEQ;
+ else if (stat & (BM_SSP_CTRL1_FIFO_UNDERRUN_IRQ |
+ BM_SSP_CTRL1_FIFO_OVERRUN_IRQ))
+ data->error = -EIO;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static struct dma_async_tx_descriptor *mxs_mmc_prep_dma(
+ struct mxs_mmc_host *host, unsigned int append)
+{
+ struct dma_async_tx_descriptor *desc;
+ struct mmc_data *data = host->data;
+ struct scatterlist * sgl;
+ unsigned int sg_len;
+
+ if (data) {
+ /* data */
+ dma_map_sg(mmc_dev(host->mmc), data->sg,
+ data->sg_len, host->dma_dir);
+ sgl = data->sg;
+ sg_len = data->sg_len;
+ } else {
+ /* pio */
+ sgl = (struct scatterlist *) host->ssp_pio_words;
+ sg_len = SSP_PIO_NUM;
+ }
+
+ desc = host->dmach->device->device_prep_slave_sg(host->dmach,
+ sgl, sg_len, host->dma_dir, append);
+ if (desc) {
+ desc->callback = mxs_mmc_dma_irq_callback;
+ desc->callback_param = host;
+ } else {
+ if (data)
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg,
+ data->sg_len, host->dma_dir);
+ }
+
+ return desc;
+}
+
+static void mxs_mmc_bc(struct mxs_mmc_host *host)
+{
+ struct mmc_command *cmd = host->cmd;
+ struct dma_async_tx_descriptor *desc;
+ u32 ctrl0, cmd0, cmd1;
+
+ ctrl0 = BM_SSP_CTRL0_ENABLE | BM_SSP_CTRL0_IGNORE_CRC;
+ cmd0 = BF_SSP(cmd->opcode, CMD0_CMD) | BM_SSP_CMD0_APPEND_8CYC;
+ cmd1 = cmd->arg;
+
+ if (host->sdio_irq_en) {
+ ctrl0 |= BM_SSP_CTRL0_SDIO_IRQ_CHECK;
+ cmd0 |= BM_SSP_CMD0_CONT_CLKING_EN | BM_SSP_CMD0_SLOW_CLKING_EN;
+ }
+
+ host->ssp_pio_words[0] = ctrl0;
+ host->ssp_pio_words[1] = cmd0;
+ host->ssp_pio_words[2] = cmd1;
+ host->dma_dir = DMA_NONE;
+ desc = mxs_mmc_prep_dma(host, 0);
+ if (!desc)
+ goto out;
+
+ dmaengine_submit(desc);
+ return;
+
+out:
+ dev_warn(mmc_dev(host->mmc),
+ "%s: failed to prep dma\n", __func__);
+}
+
+static void mxs_mmc_ac(struct mxs_mmc_host *host)
+{
+ struct mmc_command *cmd = host->cmd;
+ struct dma_async_tx_descriptor *desc;
+ u32 ignore_crc, get_resp, long_resp;
+ u32 ctrl0, cmd0, cmd1;
+
+ ignore_crc = (mmc_resp_type(cmd) & MMC_RSP_CRC) ?
+ 0 : BM_SSP_CTRL0_IGNORE_CRC;
+ get_resp = (mmc_resp_type(cmd) & MMC_RSP_PRESENT) ?
+ BM_SSP_CTRL0_GET_RESP : 0;
+ long_resp = (mmc_resp_type(cmd) & MMC_RSP_136) ?
+ BM_SSP_CTRL0_LONG_RESP : 0;
+
+ ctrl0 = BM_SSP_CTRL0_ENABLE | ignore_crc | get_resp | long_resp;
+ cmd0 = BF_SSP(cmd->opcode, CMD0_CMD);
+ cmd1 = cmd->arg;
+
+ if (host->sdio_irq_en) {
+ ctrl0 |= BM_SSP_CTRL0_SDIO_IRQ_CHECK;
+ cmd0 |= BM_SSP_CMD0_CONT_CLKING_EN | BM_SSP_CMD0_SLOW_CLKING_EN;
+ }
+
+ host->ssp_pio_words[0] = ctrl0;
+ host->ssp_pio_words[1] = cmd0;
+ host->ssp_pio_words[2] = cmd1;
+ host->dma_dir = DMA_NONE;
+ desc = mxs_mmc_prep_dma(host, 0);
+ if (!desc)
+ goto out;
+
+ dmaengine_submit(desc);
+ return;
+
+out:
+ dev_warn(mmc_dev(host->mmc),
+ "%s: failed to prep dma\n", __func__);
+}
+
+static unsigned short mxs_ns_to_ssp_ticks(unsigned clock_rate, unsigned ns)
+{
+ const unsigned int ssp_timeout_mul = 4096;
+ /*
+ * Calculate ticks in ms since ns are large numbers
+ * and might overflow
+ */
+ const unsigned int clock_per_ms = clock_rate / 1000;
+ const unsigned int ms = ns / 1000;
+ const unsigned int ticks = ms * clock_per_ms;
+ const unsigned int ssp_ticks = ticks / ssp_timeout_mul;
+
+ WARN_ON(ssp_ticks == 0);
+ return ssp_ticks;
+}
+
+static void mxs_mmc_adtc(struct mxs_mmc_host *host)
+{
+ struct mmc_command *cmd = host->cmd;
+ struct mmc_data *data = cmd->data;
+ struct dma_async_tx_descriptor *desc;
+ struct scatterlist *sgl = data->sg, *sg;
+ unsigned int sg_len = data->sg_len;
+ int i;
+
+ unsigned short dma_data_dir, timeout;
+ unsigned int data_size = 0, log2_blksz;
+ unsigned int blocks = data->blocks;
+
+ u32 ignore_crc, get_resp, long_resp, read;
+ u32 ctrl0, cmd0, cmd1, val;
+
+ ignore_crc = (mmc_resp_type(cmd) & MMC_RSP_CRC) ?
+ 0 : BM_SSP_CTRL0_IGNORE_CRC;
+ get_resp = (mmc_resp_type(cmd) & MMC_RSP_PRESENT) ?
+ BM_SSP_CTRL0_GET_RESP : 0;
+ long_resp = (mmc_resp_type(cmd) & MMC_RSP_136) ?
+ BM_SSP_CTRL0_LONG_RESP : 0;
+
+ if (data->flags & MMC_DATA_WRITE) {
+ dma_data_dir = DMA_TO_DEVICE;
+ read = 0;
+ } else {
+ dma_data_dir = DMA_FROM_DEVICE;
+ read = BM_SSP_CTRL0_READ;
+ }
+
+ ctrl0 = BF_SSP(host->bus_width, CTRL0_BUS_WIDTH) |
+ ignore_crc | get_resp | long_resp |
+ BM_SSP_CTRL0_DATA_XFER | read |
+ BM_SSP_CTRL0_WAIT_FOR_IRQ |
+ BM_SSP_CTRL0_ENABLE;
+
+ cmd0 = BF_SSP(cmd->opcode, CMD0_CMD);
+
+ /* get logarithm to base 2 of block size for setting register */
+ log2_blksz = ilog2(data->blksz);
+
+ /*
+ * take special care of the case that data size from data->sg
+ * is not equal to blocks x blksz
+ */
+ for_each_sg(sgl, sg, sg_len, i)
+ data_size += sg->length;
+
+ if (data_size != data->blocks * data->blksz)
+ blocks = 1;
+
+ /* xfer count, block size and count need to be set differently */
+ if (ssp_is_old()) {
+ ctrl0 |= BF_SSP(data_size, CTRL0_XFER_COUNT);
+ cmd0 |= BF_SSP(log2_blksz, CMD0_BLOCK_SIZE) |
+ BF_SSP(blocks - 1, CMD0_BLOCK_COUNT);
+ } else {
+ writel(data_size, host->base + HW_SSP_XFER_SIZE);
+ writel(BF_SSP(log2_blksz, BLOCK_SIZE_BLOCK_SIZE) |
+ BF_SSP(blocks - 1, BLOCK_SIZE_BLOCK_COUNT),
+ host->base + HW_SSP_BLOCK_SIZE);
+ }
+
+ if ((cmd->opcode == MMC_STOP_TRANSMISSION) ||
+ (cmd->opcode == SD_IO_RW_EXTENDED))
+ cmd0 |= BM_SSP_CMD0_APPEND_8CYC;
+
+ cmd1 = cmd->arg;
+
+ if (host->sdio_irq_en) {
+ ctrl0 |= BM_SSP_CTRL0_SDIO_IRQ_CHECK;
+ cmd0 |= BM_SSP_CMD0_CONT_CLKING_EN | BM_SSP_CMD0_SLOW_CLKING_EN;
+ }
+
+ /* set the timeout count */
+ timeout = mxs_ns_to_ssp_ticks(host->clk_rate, data->timeout_ns);
+ val = readl(host->base + HW_SSP_TIMING);
+ val &= ~(BM_SSP_TIMING_TIMEOUT);
+ val |= BF_SSP(timeout, TIMING_TIMEOUT);
+ writel(val, host->base + HW_SSP_TIMING);
+
+ /* pio */
+ host->ssp_pio_words[0] = ctrl0;
+ host->ssp_pio_words[1] = cmd0;
+ host->ssp_pio_words[2] = cmd1;
+ host->dma_dir = DMA_NONE;
+ desc = mxs_mmc_prep_dma(host, 0);
+ if (!desc)
+ goto out;
+
+ /* append data sg */
+ WARN_ON(host->data != NULL);
+ host->data = data;
+ host->dma_dir = dma_data_dir;
+ desc = mxs_mmc_prep_dma(host, 1);
+ if (!desc)
+ goto out;
+
+ dmaengine_submit(desc);
+ return;
+out:
+ dev_warn(mmc_dev(host->mmc),
+ "%s: failed to prep dma\n", __func__);
+}
+
+static void mxs_mmc_start_cmd(struct mxs_mmc_host *host,
+ struct mmc_command *cmd)
+{
+ host->cmd = cmd;
+
+ switch (mmc_cmd_type(cmd)) {
+ case MMC_CMD_BC:
+ mxs_mmc_bc(host);
+ break;
+ case MMC_CMD_BCR:
+ mxs_mmc_ac(host);
+ break;
+ case MMC_CMD_AC:
+ mxs_mmc_ac(host);
+ break;
+ case MMC_CMD_ADTC:
+ mxs_mmc_adtc(host);
+ break;
+ default:
+ dev_warn(mmc_dev(host->mmc),
+ "%s: unknown MMC command\n", __func__);
+ break;
+ }
+}
+
+static void mxs_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct mxs_mmc_host *host = mmc_priv(mmc);
+
+ WARN_ON(host->mrq != NULL);
+ host->mrq = mrq;
+ mxs_mmc_start_cmd(host, mrq->cmd);
+}
+
+static void mxs_mmc_set_clk_rate(struct mxs_mmc_host *host, unsigned int rate)
+{
+ unsigned int ssp_clk, ssp_sck;
+ u32 clock_divide, clock_rate;
+ u32 val;
+
+ ssp_clk = clk_get_rate(host->clk);
+
+ for (clock_divide = 2; clock_divide <= 254; clock_divide += 2) {
+ clock_rate = DIV_ROUND_UP(ssp_clk, rate * clock_divide);
+ clock_rate = (clock_rate > 0) ? clock_rate - 1 : 0;
+ if (clock_rate <= 255)
+ break;
+ }
+
+ if (clock_divide > 254) {
+ dev_err(mmc_dev(host->mmc),
+ "%s: cannot set clock to %d\n", __func__, rate);
+ return;
+ }
+
+ ssp_sck = ssp_clk / clock_divide / (1 + clock_rate);
+
+ val = readl(host->base + HW_SSP_TIMING);
+ val &= ~(BM_SSP_TIMING_CLOCK_DIVIDE | BM_SSP_TIMING_CLOCK_RATE);
+ val |= BF_SSP(clock_divide, TIMING_CLOCK_DIVIDE);
+ val |= BF_SSP(clock_rate, TIMING_CLOCK_RATE);
+ writel(val, host->base + HW_SSP_TIMING);
+
+ host->clk_rate = ssp_sck;
+
+ dev_dbg(mmc_dev(host->mmc),
+ "%s: clock_divide %d, clock_rate %d, ssp_clk %d, rate_actual %d, rate_requested %d\n",
+ __func__, clock_divide, clock_rate, ssp_clk, ssp_sck, rate);
+}
+
+static void mxs_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct mxs_mmc_host *host = mmc_priv(mmc);
+
+ if (ios->bus_width == MMC_BUS_WIDTH_8)
+ host->bus_width = 2;
+ else if (ios->bus_width == MMC_BUS_WIDTH_4)
+ host->bus_width = 1;
+ else
+ host->bus_width = 0;
+
+ if (ios->clock)
+ mxs_mmc_set_clk_rate(host, ios->clock);
+}
+
+static void mxs_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+ struct mxs_mmc_host *host = mmc_priv(mmc);
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ host->sdio_irq_en = enable;
+
+ if (enable) {
+ writel(BM_SSP_CTRL0_SDIO_IRQ_CHECK,
+ host->base + HW_SSP_CTRL0 + MXS_SET_ADDR);
+ writel(BM_SSP_CTRL1_SDIO_IRQ_EN,
+ host->base + HW_SSP_CTRL1 + MXS_SET_ADDR);
+
+ if (readl(host->base + HW_SSP_STATUS) & BM_SSP_STATUS_SDIO_IRQ)
+ mmc_signal_sdio_irq(host->mmc);
+
+ } else {
+ writel(BM_SSP_CTRL0_SDIO_IRQ_CHECK,
+ host->base + HW_SSP_CTRL0 + MXS_CLR_ADDR);
+ writel(BM_SSP_CTRL1_SDIO_IRQ_EN,
+ host->base + HW_SSP_CTRL1 + MXS_CLR_ADDR);
+ }
+
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static const struct mmc_host_ops mxs_mmc_ops = {
+ .request = mxs_mmc_request,
+ .get_ro = mxs_mmc_get_ro,
+ .get_cd = mxs_mmc_get_cd,
+ .set_ios = mxs_mmc_set_ios,
+ .enable_sdio_irq = mxs_mmc_enable_sdio_irq,
+};
+
+static bool mxs_mmc_dma_filter(struct dma_chan *chan, void *param)
+{
+ struct mxs_mmc_host *host = param;
+
+ if (!mxs_dma_is_apbh(chan))
+ return false;
+
+ if (chan->chan_id != host->dma_res->start)
+ return false;
+
+ chan->private = &host->dma_data;
+
+ return true;
+}
+
+static int mxs_mmc_probe(struct platform_device *pdev)
+{
+ struct mxs_mmc_host *host;
+ struct mmc_host *mmc;
+ struct resource *iores, *dmares, *r;
+ struct mxs_mmc_platform_data *pdata;
+ int ret = 0, irq_err, irq_dma;
+ dma_cap_mask_t mask;
+
+ iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ irq_err = platform_get_irq(pdev, 0);
+ irq_dma = platform_get_irq(pdev, 1);
+ if (!iores || !dmares || irq_err < 0 || irq_dma < 0)
+ return -EINVAL;
+
+ r = request_mem_region(iores->start, resource_size(iores), pdev->name);
+ if (!r)
+ return -EBUSY;
+
+ mmc = mmc_alloc_host(sizeof(struct mxs_mmc_host), &pdev->dev);
+ if (!mmc) {
+ ret = -ENOMEM;
+ goto out_release_mem;
+ }
+
+ host = mmc_priv(mmc);
+ host->base = ioremap(r->start, resource_size(r));
+ if (!host->base) {
+ ret = -ENOMEM;
+ goto out_mmc_free;
+ }
+
+ /* only major verion does matter */
+ host->version = readl(host->base + HW_SSP_VERSION) >>
+ BP_SSP_VERSION_MAJOR;
+
+ host->mmc = mmc;
+ host->res = r;
+ host->dma_res = dmares;
+ host->irq = irq_err;
+ host->sdio_irq_en = 0;
+
+ host->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(host->clk)) {
+ ret = PTR_ERR(host->clk);
+ goto out_iounmap;
+ }
+ clk_enable(host->clk);
+
+ mxs_mmc_reset(host);
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+ host->dma_data.chan_irq = irq_dma;
+ host->dmach = dma_request_channel(mask, mxs_mmc_dma_filter, host);
+ if (!host->dmach) {
+ dev_err(mmc_dev(host->mmc),
+ "%s: failed to request dma\n", __func__);
+ goto out_clk_put;
+ }
+
+ /* set mmc core parameters */
+ mmc->ops = &mxs_mmc_ops;
+ mmc->caps = MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED |
+ MMC_CAP_SDIO_IRQ | MMC_CAP_NEEDS_POLL;
+
+ pdata = mmc_dev(host->mmc)->platform_data;
+ if (pdata) {
+ if (pdata->flags & SLOTF_8_BIT_CAPABLE)
+ mmc->caps |= MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA;
+ if (pdata->flags & SLOTF_4_BIT_CAPABLE)
+ mmc->caps |= MMC_CAP_4_BIT_DATA;
+ }
+
+ mmc->f_min = 400000;
+ mmc->f_max = 288000000;
+ mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+
+ mmc->max_segs = 52;
+ mmc->max_blk_size = 1 << 0xf;
+ mmc->max_blk_count = (ssp_is_old()) ? 0xff : 0xffffff;
+ mmc->max_req_size = (ssp_is_old()) ? 0xffff : 0xffffffff;
+ mmc->max_seg_size = dma_get_max_seg_size(host->dmach->device->dev);
+
+ platform_set_drvdata(pdev, mmc);
+
+ ret = request_irq(host->irq, mxs_mmc_irq_handler, 0, DRIVER_NAME, host);
+ if (ret)
+ goto out_free_dma;
+
+ spin_lock_init(&host->lock);
+
+ ret = mmc_add_host(mmc);
+ if (ret)
+ goto out_free_irq;
+
+ dev_info(mmc_dev(host->mmc), "initialized\n");
+
+ return 0;
+
+out_free_irq:
+ free_irq(host->irq, host);
+out_free_dma:
+ if (host->dmach)
+ dma_release_channel(host->dmach);
+out_clk_put:
+ clk_disable(host->clk);
+ clk_put(host->clk);
+out_iounmap:
+ iounmap(host->base);
+out_mmc_free:
+ mmc_free_host(mmc);
+out_release_mem:
+ release_mem_region(iores->start, resource_size(iores));
+ return ret;
+}
+
+static int mxs_mmc_remove(struct platform_device *pdev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(pdev);
+ struct mxs_mmc_host *host = mmc_priv(mmc);
+ struct resource *res = host->res;
+
+ mmc_remove_host(mmc);
+
+ free_irq(host->irq, host);
+
+ platform_set_drvdata(pdev, NULL);
+
+ if (host->dmach)
+ dma_release_channel(host->dmach);
+
+ clk_disable(host->clk);
+ clk_put(host->clk);
+
+ iounmap(host->base);
+
+ mmc_free_host(mmc);
+
+ release_mem_region(res->start, resource_size(res));
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int mxs_mmc_suspend(struct device *dev)
+{
+ struct mmc_host *mmc = dev_get_drvdata(dev);
+ struct mxs_mmc_host *host = mmc_priv(mmc);
+ int ret = 0;
+
+ ret = mmc_suspend_host(mmc);
+
+ clk_disable(host->clk);
+
+ return ret;
+}
+
+static int mxs_mmc_resume(struct device *dev)
+{
+ struct mmc_host *mmc = dev_get_drvdata(dev);
+ struct mxs_mmc_host *host = mmc_priv(mmc);
+ int ret = 0;
+
+ clk_enable(host->clk);
+
+ ret = mmc_resume_host(mmc);
+
+ return ret;
+}
+
+static const struct dev_pm_ops mxs_mmc_pm_ops = {
+ .suspend = mxs_mmc_suspend,
+ .resume = mxs_mmc_resume,
+};
+#endif
+
+static struct platform_driver mxs_mmc_driver = {
+ .probe = mxs_mmc_probe,
+ .remove = mxs_mmc_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &mxs_mmc_pm_ops,
+#endif
+ },
+};
+
+static int __init mxs_mmc_init(void)
+{
+ return platform_driver_register(&mxs_mmc_driver);
+}
+
+static void __exit mxs_mmc_exit(void)
+{
+ platform_driver_unregister(&mxs_mmc_driver);
+}
+
+module_init(mxs_mmc_init);
+module_exit(mxs_mmc_exit);
+
+MODULE_DESCRIPTION("FREESCALE MXS MMC peripheral");
+MODULE_AUTHOR("Freescale Semiconductor");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mmc/host/of_mmc_spi.c b/drivers/mmc/host/of_mmc_spi.c
new file mode 100644
index 00000000..ab66f245
--- /dev/null
+++ b/drivers/mmc/host/of_mmc_spi.c
@@ -0,0 +1,181 @@
+/*
+ * OpenFirmware bindings for the MMC-over-SPI driver
+ *
+ * Copyright (c) MontaVista Software, Inc. 2008.
+ *
+ * Author: Anton Vorontsov <avorontsov@ru.mvista.com>
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/mmc_spi.h>
+#include <linux/mmc/core.h>
+#include <linux/mmc/host.h>
+
+/* For archs that don't support NO_IRQ (such as mips), provide a dummy value */
+#ifndef NO_IRQ
+#define NO_IRQ 0
+#endif
+
+MODULE_LICENSE("GPL");
+
+enum {
+ CD_GPIO = 0,
+ WP_GPIO,
+ NUM_GPIOS,
+};
+
+struct of_mmc_spi {
+ int gpios[NUM_GPIOS];
+ bool alow_gpios[NUM_GPIOS];
+ int detect_irq;
+ struct mmc_spi_platform_data pdata;
+};
+
+static struct of_mmc_spi *to_of_mmc_spi(struct device *dev)
+{
+ return container_of(dev->platform_data, struct of_mmc_spi, pdata);
+}
+
+static int of_mmc_spi_read_gpio(struct device *dev, int gpio_num)
+{
+ struct of_mmc_spi *oms = to_of_mmc_spi(dev);
+ bool active_low = oms->alow_gpios[gpio_num];
+ bool value = gpio_get_value(oms->gpios[gpio_num]);
+
+ return active_low ^ value;
+}
+
+static int of_mmc_spi_get_cd(struct device *dev)
+{
+ return of_mmc_spi_read_gpio(dev, CD_GPIO);
+}
+
+static int of_mmc_spi_get_ro(struct device *dev)
+{
+ return of_mmc_spi_read_gpio(dev, WP_GPIO);
+}
+
+static int of_mmc_spi_init(struct device *dev,
+ irqreturn_t (*irqhandler)(int, void *), void *mmc)
+{
+ struct of_mmc_spi *oms = to_of_mmc_spi(dev);
+
+ return request_threaded_irq(oms->detect_irq, NULL, irqhandler, 0,
+ dev_name(dev), mmc);
+}
+
+static void of_mmc_spi_exit(struct device *dev, void *mmc)
+{
+ struct of_mmc_spi *oms = to_of_mmc_spi(dev);
+
+ free_irq(oms->detect_irq, mmc);
+}
+
+struct mmc_spi_platform_data *mmc_spi_get_pdata(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct device_node *np = dev->of_node;
+ struct of_mmc_spi *oms;
+ const u32 *voltage_ranges;
+ int num_ranges;
+ int i;
+ int ret = -EINVAL;
+
+ if (dev->platform_data || !np)
+ return dev->platform_data;
+
+ oms = kzalloc(sizeof(*oms), GFP_KERNEL);
+ if (!oms)
+ return NULL;
+
+ voltage_ranges = of_get_property(np, "voltage-ranges", &num_ranges);
+ num_ranges = num_ranges / sizeof(*voltage_ranges) / 2;
+ if (!voltage_ranges || !num_ranges) {
+ dev_err(dev, "OF: voltage-ranges unspecified\n");
+ goto err_ocr;
+ }
+
+ for (i = 0; i < num_ranges; i++) {
+ const int j = i * 2;
+ u32 mask;
+
+ mask = mmc_vddrange_to_ocrmask(voltage_ranges[j],
+ voltage_ranges[j + 1]);
+ if (!mask) {
+ ret = -EINVAL;
+ dev_err(dev, "OF: voltage-range #%d is invalid\n", i);
+ goto err_ocr;
+ }
+ oms->pdata.ocr_mask |= mask;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(oms->gpios); i++) {
+ enum of_gpio_flags gpio_flags;
+
+ oms->gpios[i] = of_get_gpio_flags(np, i, &gpio_flags);
+ if (!gpio_is_valid(oms->gpios[i]))
+ continue;
+
+ ret = gpio_request(oms->gpios[i], dev_name(dev));
+ if (ret < 0) {
+ oms->gpios[i] = -EINVAL;
+ continue;
+ }
+
+ if (gpio_flags & OF_GPIO_ACTIVE_LOW)
+ oms->alow_gpios[i] = true;
+ }
+
+ if (gpio_is_valid(oms->gpios[CD_GPIO]))
+ oms->pdata.get_cd = of_mmc_spi_get_cd;
+ if (gpio_is_valid(oms->gpios[WP_GPIO]))
+ oms->pdata.get_ro = of_mmc_spi_get_ro;
+
+ oms->detect_irq = irq_of_parse_and_map(np, 0);
+ if (oms->detect_irq != NO_IRQ) {
+ oms->pdata.init = of_mmc_spi_init;
+ oms->pdata.exit = of_mmc_spi_exit;
+ } else {
+ oms->pdata.caps |= MMC_CAP_NEEDS_POLL;
+ }
+
+ dev->platform_data = &oms->pdata;
+ return dev->platform_data;
+err_ocr:
+ kfree(oms);
+ return NULL;
+}
+EXPORT_SYMBOL(mmc_spi_get_pdata);
+
+void mmc_spi_put_pdata(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct device_node *np = dev->of_node;
+ struct of_mmc_spi *oms = to_of_mmc_spi(dev);
+ int i;
+
+ if (!dev->platform_data || !np)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(oms->gpios); i++) {
+ if (gpio_is_valid(oms->gpios[i]))
+ gpio_free(oms->gpios[i]);
+ }
+ kfree(oms);
+ dev->platform_data = NULL;
+}
+EXPORT_SYMBOL(mmc_spi_put_pdata);
diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c
new file mode 100644
index 00000000..a6c32904
--- /dev/null
+++ b/drivers/mmc/host/omap.c
@@ -0,0 +1,1637 @@
+/*
+ * linux/drivers/mmc/host/omap.c
+ *
+ * Copyright (C) 2004 Nokia Corporation
+ * Written by Tuukka Tikkanen and Juha Yrjölä<juha.yrjola@nokia.com>
+ * Misc hacks here and there by Tony Lindgren <tony@atomide.com>
+ * Other hacks (DMA, SD, etc) by David Brownell
+ *
+ * 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/moduleparam.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/clk.h>
+#include <linux/scatterlist.h>
+#include <linux/i2c/tps65010.h>
+#include <linux/slab.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+
+#include <plat/board.h>
+#include <plat/mmc.h>
+#include <mach/gpio.h>
+#include <plat/dma.h>
+#include <plat/mux.h>
+#include <plat/fpga.h>
+
+#define OMAP_MMC_REG_CMD 0x00
+#define OMAP_MMC_REG_ARGL 0x01
+#define OMAP_MMC_REG_ARGH 0x02
+#define OMAP_MMC_REG_CON 0x03
+#define OMAP_MMC_REG_STAT 0x04
+#define OMAP_MMC_REG_IE 0x05
+#define OMAP_MMC_REG_CTO 0x06
+#define OMAP_MMC_REG_DTO 0x07
+#define OMAP_MMC_REG_DATA 0x08
+#define OMAP_MMC_REG_BLEN 0x09
+#define OMAP_MMC_REG_NBLK 0x0a
+#define OMAP_MMC_REG_BUF 0x0b
+#define OMAP_MMC_REG_SDIO 0x0d
+#define OMAP_MMC_REG_REV 0x0f
+#define OMAP_MMC_REG_RSP0 0x10
+#define OMAP_MMC_REG_RSP1 0x11
+#define OMAP_MMC_REG_RSP2 0x12
+#define OMAP_MMC_REG_RSP3 0x13
+#define OMAP_MMC_REG_RSP4 0x14
+#define OMAP_MMC_REG_RSP5 0x15
+#define OMAP_MMC_REG_RSP6 0x16
+#define OMAP_MMC_REG_RSP7 0x17
+#define OMAP_MMC_REG_IOSR 0x18
+#define OMAP_MMC_REG_SYSC 0x19
+#define OMAP_MMC_REG_SYSS 0x1a
+
+#define OMAP_MMC_STAT_CARD_ERR (1 << 14)
+#define OMAP_MMC_STAT_CARD_IRQ (1 << 13)
+#define OMAP_MMC_STAT_OCR_BUSY (1 << 12)
+#define OMAP_MMC_STAT_A_EMPTY (1 << 11)
+#define OMAP_MMC_STAT_A_FULL (1 << 10)
+#define OMAP_MMC_STAT_CMD_CRC (1 << 8)
+#define OMAP_MMC_STAT_CMD_TOUT (1 << 7)
+#define OMAP_MMC_STAT_DATA_CRC (1 << 6)
+#define OMAP_MMC_STAT_DATA_TOUT (1 << 5)
+#define OMAP_MMC_STAT_END_BUSY (1 << 4)
+#define OMAP_MMC_STAT_END_OF_DATA (1 << 3)
+#define OMAP_MMC_STAT_CARD_BUSY (1 << 2)
+#define OMAP_MMC_STAT_END_OF_CMD (1 << 0)
+
+#define OMAP_MMC_REG(host, reg) (OMAP_MMC_REG_##reg << (host)->reg_shift)
+#define OMAP_MMC_READ(host, reg) __raw_readw((host)->virt_base + OMAP_MMC_REG(host, reg))
+#define OMAP_MMC_WRITE(host, reg, val) __raw_writew((val), (host)->virt_base + OMAP_MMC_REG(host, reg))
+
+/*
+ * Command types
+ */
+#define OMAP_MMC_CMDTYPE_BC 0
+#define OMAP_MMC_CMDTYPE_BCR 1
+#define OMAP_MMC_CMDTYPE_AC 2
+#define OMAP_MMC_CMDTYPE_ADTC 3
+
+
+#define DRIVER_NAME "mmci-omap"
+
+/* Specifies how often in millisecs to poll for card status changes
+ * when the cover switch is open */
+#define OMAP_MMC_COVER_POLL_DELAY 500
+
+struct mmc_omap_host;
+
+struct mmc_omap_slot {
+ int id;
+ unsigned int vdd;
+ u16 saved_con;
+ u16 bus_mode;
+ unsigned int fclk_freq;
+ unsigned powered:1;
+
+ struct tasklet_struct cover_tasklet;
+ struct timer_list cover_timer;
+ unsigned cover_open;
+
+ struct mmc_request *mrq;
+ struct mmc_omap_host *host;
+ struct mmc_host *mmc;
+ struct omap_mmc_slot_data *pdata;
+};
+
+struct mmc_omap_host {
+ int initialized;
+ int suspended;
+ struct mmc_request * mrq;
+ struct mmc_command * cmd;
+ struct mmc_data * data;
+ struct mmc_host * mmc;
+ struct device * dev;
+ unsigned char id; /* 16xx chips have 2 MMC blocks */
+ struct clk * iclk;
+ struct clk * fclk;
+ struct resource *mem_res;
+ void __iomem *virt_base;
+ unsigned int phys_base;
+ int irq;
+ unsigned char bus_mode;
+ unsigned char hw_bus_mode;
+ unsigned int reg_shift;
+
+ struct work_struct cmd_abort_work;
+ unsigned abort:1;
+ struct timer_list cmd_abort_timer;
+
+ struct work_struct slot_release_work;
+ struct mmc_omap_slot *next_slot;
+ struct work_struct send_stop_work;
+ struct mmc_data *stop_data;
+
+ unsigned int sg_len;
+ int sg_idx;
+ u16 * buffer;
+ u32 buffer_bytes_left;
+ u32 total_bytes_left;
+
+ unsigned use_dma:1;
+ unsigned brs_received:1, dma_done:1;
+ unsigned dma_is_read:1;
+ unsigned dma_in_use:1;
+ int dma_ch;
+ spinlock_t dma_lock;
+ struct timer_list dma_timer;
+ unsigned dma_len;
+
+ struct mmc_omap_slot *slots[OMAP_MMC_MAX_SLOTS];
+ struct mmc_omap_slot *current_slot;
+ spinlock_t slot_lock;
+ wait_queue_head_t slot_wq;
+ int nr_slots;
+
+ struct timer_list clk_timer;
+ spinlock_t clk_lock; /* for changing enabled state */
+ unsigned int fclk_enabled:1;
+
+ struct omap_mmc_platform_data *pdata;
+};
+
+static struct workqueue_struct *mmc_omap_wq;
+
+static void mmc_omap_fclk_offdelay(struct mmc_omap_slot *slot)
+{
+ unsigned long tick_ns;
+
+ if (slot != NULL && slot->host->fclk_enabled && slot->fclk_freq > 0) {
+ tick_ns = (1000000000 + slot->fclk_freq - 1) / slot->fclk_freq;
+ ndelay(8 * tick_ns);
+ }
+}
+
+static void mmc_omap_fclk_enable(struct mmc_omap_host *host, unsigned int enable)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->clk_lock, flags);
+ if (host->fclk_enabled != enable) {
+ host->fclk_enabled = enable;
+ if (enable)
+ clk_enable(host->fclk);
+ else
+ clk_disable(host->fclk);
+ }
+ spin_unlock_irqrestore(&host->clk_lock, flags);
+}
+
+static void mmc_omap_select_slot(struct mmc_omap_slot *slot, int claimed)
+{
+ struct mmc_omap_host *host = slot->host;
+ unsigned long flags;
+
+ if (claimed)
+ goto no_claim;
+ spin_lock_irqsave(&host->slot_lock, flags);
+ while (host->mmc != NULL) {
+ spin_unlock_irqrestore(&host->slot_lock, flags);
+ wait_event(host->slot_wq, host->mmc == NULL);
+ spin_lock_irqsave(&host->slot_lock, flags);
+ }
+ host->mmc = slot->mmc;
+ spin_unlock_irqrestore(&host->slot_lock, flags);
+no_claim:
+ del_timer(&host->clk_timer);
+ if (host->current_slot != slot || !claimed)
+ mmc_omap_fclk_offdelay(host->current_slot);
+
+ if (host->current_slot != slot) {
+ OMAP_MMC_WRITE(host, CON, slot->saved_con & 0xFC00);
+ if (host->pdata->switch_slot != NULL)
+ host->pdata->switch_slot(mmc_dev(slot->mmc), slot->id);
+ host->current_slot = slot;
+ }
+
+ if (claimed) {
+ mmc_omap_fclk_enable(host, 1);
+
+ /* Doing the dummy read here seems to work around some bug
+ * at least in OMAP24xx silicon where the command would not
+ * start after writing the CMD register. Sigh. */
+ OMAP_MMC_READ(host, CON);
+
+ OMAP_MMC_WRITE(host, CON, slot->saved_con);
+ } else
+ mmc_omap_fclk_enable(host, 0);
+}
+
+static void mmc_omap_start_request(struct mmc_omap_host *host,
+ struct mmc_request *req);
+
+static void mmc_omap_slot_release_work(struct work_struct *work)
+{
+ struct mmc_omap_host *host = container_of(work, struct mmc_omap_host,
+ slot_release_work);
+ struct mmc_omap_slot *next_slot = host->next_slot;
+ struct mmc_request *rq;
+
+ host->next_slot = NULL;
+ mmc_omap_select_slot(next_slot, 1);
+
+ rq = next_slot->mrq;
+ next_slot->mrq = NULL;
+ mmc_omap_start_request(host, rq);
+}
+
+static void mmc_omap_release_slot(struct mmc_omap_slot *slot, int clk_enabled)
+{
+ struct mmc_omap_host *host = slot->host;
+ unsigned long flags;
+ int i;
+
+ BUG_ON(slot == NULL || host->mmc == NULL);
+
+ if (clk_enabled)
+ /* Keeps clock running for at least 8 cycles on valid freq */
+ mod_timer(&host->clk_timer, jiffies + HZ/10);
+ else {
+ del_timer(&host->clk_timer);
+ mmc_omap_fclk_offdelay(slot);
+ mmc_omap_fclk_enable(host, 0);
+ }
+
+ spin_lock_irqsave(&host->slot_lock, flags);
+ /* Check for any pending requests */
+ for (i = 0; i < host->nr_slots; i++) {
+ struct mmc_omap_slot *new_slot;
+
+ if (host->slots[i] == NULL || host->slots[i]->mrq == NULL)
+ continue;
+
+ BUG_ON(host->next_slot != NULL);
+ new_slot = host->slots[i];
+ /* The current slot should not have a request in queue */
+ BUG_ON(new_slot == host->current_slot);
+
+ host->next_slot = new_slot;
+ host->mmc = new_slot->mmc;
+ spin_unlock_irqrestore(&host->slot_lock, flags);
+ queue_work(mmc_omap_wq, &host->slot_release_work);
+ return;
+ }
+
+ host->mmc = NULL;
+ wake_up(&host->slot_wq);
+ spin_unlock_irqrestore(&host->slot_lock, flags);
+}
+
+static inline
+int mmc_omap_cover_is_open(struct mmc_omap_slot *slot)
+{
+ if (slot->pdata->get_cover_state)
+ return slot->pdata->get_cover_state(mmc_dev(slot->mmc),
+ slot->id);
+ return 0;
+}
+
+static ssize_t
+mmc_omap_show_cover_switch(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct mmc_host *mmc = container_of(dev, struct mmc_host, class_dev);
+ struct mmc_omap_slot *slot = mmc_priv(mmc);
+
+ return sprintf(buf, "%s\n", mmc_omap_cover_is_open(slot) ? "open" :
+ "closed");
+}
+
+static DEVICE_ATTR(cover_switch, S_IRUGO, mmc_omap_show_cover_switch, NULL);
+
+static ssize_t
+mmc_omap_show_slot_name(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct mmc_host *mmc = container_of(dev, struct mmc_host, class_dev);
+ struct mmc_omap_slot *slot = mmc_priv(mmc);
+
+ return sprintf(buf, "%s\n", slot->pdata->name);
+}
+
+static DEVICE_ATTR(slot_name, S_IRUGO, mmc_omap_show_slot_name, NULL);
+
+static void
+mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd)
+{
+ u32 cmdreg;
+ u32 resptype;
+ u32 cmdtype;
+
+ host->cmd = cmd;
+
+ resptype = 0;
+ cmdtype = 0;
+
+ /* Our hardware needs to know exact type */
+ switch (mmc_resp_type(cmd)) {
+ case MMC_RSP_NONE:
+ break;
+ case MMC_RSP_R1:
+ case MMC_RSP_R1B:
+ /* resp 1, 1b, 6, 7 */
+ resptype = 1;
+ break;
+ case MMC_RSP_R2:
+ resptype = 2;
+ break;
+ case MMC_RSP_R3:
+ resptype = 3;
+ break;
+ default:
+ dev_err(mmc_dev(host->mmc), "Invalid response type: %04x\n", mmc_resp_type(cmd));
+ break;
+ }
+
+ if (mmc_cmd_type(cmd) == MMC_CMD_ADTC) {
+ cmdtype = OMAP_MMC_CMDTYPE_ADTC;
+ } else if (mmc_cmd_type(cmd) == MMC_CMD_BC) {
+ cmdtype = OMAP_MMC_CMDTYPE_BC;
+ } else if (mmc_cmd_type(cmd) == MMC_CMD_BCR) {
+ cmdtype = OMAP_MMC_CMDTYPE_BCR;
+ } else {
+ cmdtype = OMAP_MMC_CMDTYPE_AC;
+ }
+
+ cmdreg = cmd->opcode | (resptype << 8) | (cmdtype << 12);
+
+ if (host->current_slot->bus_mode == MMC_BUSMODE_OPENDRAIN)
+ cmdreg |= 1 << 6;
+
+ if (cmd->flags & MMC_RSP_BUSY)
+ cmdreg |= 1 << 11;
+
+ if (host->data && !(host->data->flags & MMC_DATA_WRITE))
+ cmdreg |= 1 << 15;
+
+ mod_timer(&host->cmd_abort_timer, jiffies + HZ/2);
+
+ OMAP_MMC_WRITE(host, CTO, 200);
+ OMAP_MMC_WRITE(host, ARGL, cmd->arg & 0xffff);
+ OMAP_MMC_WRITE(host, ARGH, cmd->arg >> 16);
+ OMAP_MMC_WRITE(host, IE,
+ OMAP_MMC_STAT_A_EMPTY | OMAP_MMC_STAT_A_FULL |
+ OMAP_MMC_STAT_CMD_CRC | OMAP_MMC_STAT_CMD_TOUT |
+ OMAP_MMC_STAT_DATA_CRC | OMAP_MMC_STAT_DATA_TOUT |
+ OMAP_MMC_STAT_END_OF_CMD | OMAP_MMC_STAT_CARD_ERR |
+ OMAP_MMC_STAT_END_OF_DATA);
+ OMAP_MMC_WRITE(host, CMD, cmdreg);
+}
+
+static void
+mmc_omap_release_dma(struct mmc_omap_host *host, struct mmc_data *data,
+ int abort)
+{
+ enum dma_data_direction dma_data_dir;
+
+ BUG_ON(host->dma_ch < 0);
+ if (data->error)
+ omap_stop_dma(host->dma_ch);
+ /* Release DMA channel lazily */
+ mod_timer(&host->dma_timer, jiffies + HZ);
+ if (data->flags & MMC_DATA_WRITE)
+ dma_data_dir = DMA_TO_DEVICE;
+ else
+ dma_data_dir = DMA_FROM_DEVICE;
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->sg_len,
+ dma_data_dir);
+}
+
+static void mmc_omap_send_stop_work(struct work_struct *work)
+{
+ struct mmc_omap_host *host = container_of(work, struct mmc_omap_host,
+ send_stop_work);
+ struct mmc_omap_slot *slot = host->current_slot;
+ struct mmc_data *data = host->stop_data;
+ unsigned long tick_ns;
+
+ tick_ns = (1000000000 + slot->fclk_freq - 1)/slot->fclk_freq;
+ ndelay(8*tick_ns);
+
+ mmc_omap_start_command(host, data->stop);
+}
+
+static void
+mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data)
+{
+ if (host->dma_in_use)
+ mmc_omap_release_dma(host, data, data->error);
+
+ host->data = NULL;
+ host->sg_len = 0;
+
+ /* NOTE: MMC layer will sometimes poll-wait CMD13 next, issuing
+ * dozens of requests until the card finishes writing data.
+ * It'd be cheaper to just wait till an EOFB interrupt arrives...
+ */
+
+ if (!data->stop) {
+ struct mmc_host *mmc;
+
+ host->mrq = NULL;
+ mmc = host->mmc;
+ mmc_omap_release_slot(host->current_slot, 1);
+ mmc_request_done(mmc, data->mrq);
+ return;
+ }
+
+ host->stop_data = data;
+ queue_work(mmc_omap_wq, &host->send_stop_work);
+}
+
+static void
+mmc_omap_send_abort(struct mmc_omap_host *host, int maxloops)
+{
+ struct mmc_omap_slot *slot = host->current_slot;
+ unsigned int restarts, passes, timeout;
+ u16 stat = 0;
+
+ /* Sending abort takes 80 clocks. Have some extra and round up */
+ timeout = (120*1000000 + slot->fclk_freq - 1)/slot->fclk_freq;
+ restarts = 0;
+ while (restarts < maxloops) {
+ OMAP_MMC_WRITE(host, STAT, 0xFFFF);
+ OMAP_MMC_WRITE(host, CMD, (3 << 12) | (1 << 7));
+
+ passes = 0;
+ while (passes < timeout) {
+ stat = OMAP_MMC_READ(host, STAT);
+ if (stat & OMAP_MMC_STAT_END_OF_CMD)
+ goto out;
+ udelay(1);
+ passes++;
+ }
+
+ restarts++;
+ }
+out:
+ OMAP_MMC_WRITE(host, STAT, stat);
+}
+
+static void
+mmc_omap_abort_xfer(struct mmc_omap_host *host, struct mmc_data *data)
+{
+ if (host->dma_in_use)
+ mmc_omap_release_dma(host, data, 1);
+
+ host->data = NULL;
+ host->sg_len = 0;
+
+ mmc_omap_send_abort(host, 10000);
+}
+
+static void
+mmc_omap_end_of_data(struct mmc_omap_host *host, struct mmc_data *data)
+{
+ unsigned long flags;
+ int done;
+
+ if (!host->dma_in_use) {
+ mmc_omap_xfer_done(host, data);
+ return;
+ }
+ done = 0;
+ spin_lock_irqsave(&host->dma_lock, flags);
+ if (host->dma_done)
+ done = 1;
+ else
+ host->brs_received = 1;
+ spin_unlock_irqrestore(&host->dma_lock, flags);
+ if (done)
+ mmc_omap_xfer_done(host, data);
+}
+
+static void
+mmc_omap_dma_timer(unsigned long data)
+{
+ struct mmc_omap_host *host = (struct mmc_omap_host *) data;
+
+ BUG_ON(host->dma_ch < 0);
+ omap_free_dma(host->dma_ch);
+ host->dma_ch = -1;
+}
+
+static void
+mmc_omap_dma_done(struct mmc_omap_host *host, struct mmc_data *data)
+{
+ unsigned long flags;
+ int done;
+
+ done = 0;
+ spin_lock_irqsave(&host->dma_lock, flags);
+ if (host->brs_received)
+ done = 1;
+ else
+ host->dma_done = 1;
+ spin_unlock_irqrestore(&host->dma_lock, flags);
+ if (done)
+ mmc_omap_xfer_done(host, data);
+}
+
+static void
+mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd)
+{
+ host->cmd = NULL;
+
+ del_timer(&host->cmd_abort_timer);
+
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ if (cmd->flags & MMC_RSP_136) {
+ /* response type 2 */
+ cmd->resp[3] =
+ OMAP_MMC_READ(host, RSP0) |
+ (OMAP_MMC_READ(host, RSP1) << 16);
+ cmd->resp[2] =
+ OMAP_MMC_READ(host, RSP2) |
+ (OMAP_MMC_READ(host, RSP3) << 16);
+ cmd->resp[1] =
+ OMAP_MMC_READ(host, RSP4) |
+ (OMAP_MMC_READ(host, RSP5) << 16);
+ cmd->resp[0] =
+ OMAP_MMC_READ(host, RSP6) |
+ (OMAP_MMC_READ(host, RSP7) << 16);
+ } else {
+ /* response types 1, 1b, 3, 4, 5, 6 */
+ cmd->resp[0] =
+ OMAP_MMC_READ(host, RSP6) |
+ (OMAP_MMC_READ(host, RSP7) << 16);
+ }
+ }
+
+ if (host->data == NULL || cmd->error) {
+ struct mmc_host *mmc;
+
+ if (host->data != NULL)
+ mmc_omap_abort_xfer(host, host->data);
+ host->mrq = NULL;
+ mmc = host->mmc;
+ mmc_omap_release_slot(host->current_slot, 1);
+ mmc_request_done(mmc, cmd->mrq);
+ }
+}
+
+/*
+ * Abort stuck command. Can occur when card is removed while it is being
+ * read.
+ */
+static void mmc_omap_abort_command(struct work_struct *work)
+{
+ struct mmc_omap_host *host = container_of(work, struct mmc_omap_host,
+ cmd_abort_work);
+ BUG_ON(!host->cmd);
+
+ dev_dbg(mmc_dev(host->mmc), "Aborting stuck command CMD%d\n",
+ host->cmd->opcode);
+
+ if (host->cmd->error == 0)
+ host->cmd->error = -ETIMEDOUT;
+
+ if (host->data == NULL) {
+ struct mmc_command *cmd;
+ struct mmc_host *mmc;
+
+ cmd = host->cmd;
+ host->cmd = NULL;
+ mmc_omap_send_abort(host, 10000);
+
+ host->mrq = NULL;
+ mmc = host->mmc;
+ mmc_omap_release_slot(host->current_slot, 1);
+ mmc_request_done(mmc, cmd->mrq);
+ } else
+ mmc_omap_cmd_done(host, host->cmd);
+
+ host->abort = 0;
+ enable_irq(host->irq);
+}
+
+static void
+mmc_omap_cmd_timer(unsigned long data)
+{
+ struct mmc_omap_host *host = (struct mmc_omap_host *) data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->slot_lock, flags);
+ if (host->cmd != NULL && !host->abort) {
+ OMAP_MMC_WRITE(host, IE, 0);
+ disable_irq(host->irq);
+ host->abort = 1;
+ queue_work(mmc_omap_wq, &host->cmd_abort_work);
+ }
+ spin_unlock_irqrestore(&host->slot_lock, flags);
+}
+
+/* PIO only */
+static void
+mmc_omap_sg_to_buf(struct mmc_omap_host *host)
+{
+ struct scatterlist *sg;
+
+ sg = host->data->sg + host->sg_idx;
+ host->buffer_bytes_left = sg->length;
+ host->buffer = sg_virt(sg);
+ if (host->buffer_bytes_left > host->total_bytes_left)
+ host->buffer_bytes_left = host->total_bytes_left;
+}
+
+static void
+mmc_omap_clk_timer(unsigned long data)
+{
+ struct mmc_omap_host *host = (struct mmc_omap_host *) data;
+
+ mmc_omap_fclk_enable(host, 0);
+}
+
+/* PIO only */
+static void
+mmc_omap_xfer_data(struct mmc_omap_host *host, int write)
+{
+ int n;
+
+ if (host->buffer_bytes_left == 0) {
+ host->sg_idx++;
+ BUG_ON(host->sg_idx == host->sg_len);
+ mmc_omap_sg_to_buf(host);
+ }
+ n = 64;
+ if (n > host->buffer_bytes_left)
+ n = host->buffer_bytes_left;
+ host->buffer_bytes_left -= n;
+ host->total_bytes_left -= n;
+ host->data->bytes_xfered += n;
+
+ if (write) {
+ __raw_writesw(host->virt_base + OMAP_MMC_REG(host, DATA), host->buffer, n);
+ } else {
+ __raw_readsw(host->virt_base + OMAP_MMC_REG(host, DATA), host->buffer, n);
+ }
+}
+
+static inline void mmc_omap_report_irq(u16 status)
+{
+ static const char *mmc_omap_status_bits[] = {
+ "EOC", "CD", "CB", "BRS", "EOFB", "DTO", "DCRC", "CTO",
+ "CCRC", "CRW", "AF", "AE", "OCRB", "CIRQ", "CERR"
+ };
+ int i, c = 0;
+
+ for (i = 0; i < ARRAY_SIZE(mmc_omap_status_bits); i++)
+ if (status & (1 << i)) {
+ if (c)
+ printk(" ");
+ printk("%s", mmc_omap_status_bits[i]);
+ c++;
+ }
+}
+
+static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
+{
+ struct mmc_omap_host * host = (struct mmc_omap_host *)dev_id;
+ u16 status;
+ int end_command;
+ int end_transfer;
+ int transfer_error, cmd_error;
+
+ if (host->cmd == NULL && host->data == NULL) {
+ status = OMAP_MMC_READ(host, STAT);
+ dev_info(mmc_dev(host->slots[0]->mmc),
+ "Spurious IRQ 0x%04x\n", status);
+ if (status != 0) {
+ OMAP_MMC_WRITE(host, STAT, status);
+ OMAP_MMC_WRITE(host, IE, 0);
+ }
+ return IRQ_HANDLED;
+ }
+
+ end_command = 0;
+ end_transfer = 0;
+ transfer_error = 0;
+ cmd_error = 0;
+
+ while ((status = OMAP_MMC_READ(host, STAT)) != 0) {
+ int cmd;
+
+ OMAP_MMC_WRITE(host, STAT, status);
+ if (host->cmd != NULL)
+ cmd = host->cmd->opcode;
+ else
+ cmd = -1;
+#ifdef CONFIG_MMC_DEBUG
+ dev_dbg(mmc_dev(host->mmc), "MMC IRQ %04x (CMD %d): ",
+ status, cmd);
+ mmc_omap_report_irq(status);
+ printk("\n");
+#endif
+ if (host->total_bytes_left) {
+ if ((status & OMAP_MMC_STAT_A_FULL) ||
+ (status & OMAP_MMC_STAT_END_OF_DATA))
+ mmc_omap_xfer_data(host, 0);
+ if (status & OMAP_MMC_STAT_A_EMPTY)
+ mmc_omap_xfer_data(host, 1);
+ }
+
+ if (status & OMAP_MMC_STAT_END_OF_DATA)
+ end_transfer = 1;
+
+ if (status & OMAP_MMC_STAT_DATA_TOUT) {
+ dev_dbg(mmc_dev(host->mmc), "data timeout (CMD%d)\n",
+ cmd);
+ if (host->data) {
+ host->data->error = -ETIMEDOUT;
+ transfer_error = 1;
+ }
+ }
+
+ if (status & OMAP_MMC_STAT_DATA_CRC) {
+ if (host->data) {
+ host->data->error = -EILSEQ;
+ dev_dbg(mmc_dev(host->mmc),
+ "data CRC error, bytes left %d\n",
+ host->total_bytes_left);
+ transfer_error = 1;
+ } else {
+ dev_dbg(mmc_dev(host->mmc), "data CRC error\n");
+ }
+ }
+
+ if (status & OMAP_MMC_STAT_CMD_TOUT) {
+ /* Timeouts are routine with some commands */
+ if (host->cmd) {
+ struct mmc_omap_slot *slot =
+ host->current_slot;
+ if (slot == NULL ||
+ !mmc_omap_cover_is_open(slot))
+ dev_err(mmc_dev(host->mmc),
+ "command timeout (CMD%d)\n",
+ cmd);
+ host->cmd->error = -ETIMEDOUT;
+ end_command = 1;
+ cmd_error = 1;
+ }
+ }
+
+ if (status & OMAP_MMC_STAT_CMD_CRC) {
+ if (host->cmd) {
+ dev_err(mmc_dev(host->mmc),
+ "command CRC error (CMD%d, arg 0x%08x)\n",
+ cmd, host->cmd->arg);
+ host->cmd->error = -EILSEQ;
+ end_command = 1;
+ cmd_error = 1;
+ } else
+ dev_err(mmc_dev(host->mmc),
+ "command CRC error without cmd?\n");
+ }
+
+ if (status & OMAP_MMC_STAT_CARD_ERR) {
+ dev_dbg(mmc_dev(host->mmc),
+ "ignoring card status error (CMD%d)\n",
+ cmd);
+ end_command = 1;
+ }
+
+ /*
+ * NOTE: On 1610 the END_OF_CMD may come too early when
+ * starting a write
+ */
+ if ((status & OMAP_MMC_STAT_END_OF_CMD) &&
+ (!(status & OMAP_MMC_STAT_A_EMPTY))) {
+ end_command = 1;
+ }
+ }
+
+ if (cmd_error && host->data) {
+ del_timer(&host->cmd_abort_timer);
+ host->abort = 1;
+ OMAP_MMC_WRITE(host, IE, 0);
+ disable_irq_nosync(host->irq);
+ queue_work(mmc_omap_wq, &host->cmd_abort_work);
+ return IRQ_HANDLED;
+ }
+
+ if (end_command && host->cmd)
+ mmc_omap_cmd_done(host, host->cmd);
+ if (host->data != NULL) {
+ if (transfer_error)
+ mmc_omap_xfer_done(host, host->data);
+ else if (end_transfer)
+ mmc_omap_end_of_data(host, host->data);
+ }
+
+ return IRQ_HANDLED;
+}
+
+void omap_mmc_notify_cover_event(struct device *dev, int num, int is_closed)
+{
+ int cover_open;
+ struct mmc_omap_host *host = dev_get_drvdata(dev);
+ struct mmc_omap_slot *slot = host->slots[num];
+
+ BUG_ON(num >= host->nr_slots);
+
+ /* Other subsystems can call in here before we're initialised. */
+ if (host->nr_slots == 0 || !host->slots[num])
+ return;
+
+ cover_open = mmc_omap_cover_is_open(slot);
+ if (cover_open != slot->cover_open) {
+ slot->cover_open = cover_open;
+ sysfs_notify(&slot->mmc->class_dev.kobj, NULL, "cover_switch");
+ }
+
+ tasklet_hi_schedule(&slot->cover_tasklet);
+}
+
+static void mmc_omap_cover_timer(unsigned long arg)
+{
+ struct mmc_omap_slot *slot = (struct mmc_omap_slot *) arg;
+ tasklet_schedule(&slot->cover_tasklet);
+}
+
+static void mmc_omap_cover_handler(unsigned long param)
+{
+ struct mmc_omap_slot *slot = (struct mmc_omap_slot *)param;
+ int cover_open = mmc_omap_cover_is_open(slot);
+
+ mmc_detect_change(slot->mmc, 0);
+ if (!cover_open)
+ return;
+
+ /*
+ * If no card is inserted, we postpone polling until
+ * the cover has been closed.
+ */
+ if (slot->mmc->card == NULL || !mmc_card_present(slot->mmc->card))
+ return;
+
+ mod_timer(&slot->cover_timer,
+ jiffies + msecs_to_jiffies(OMAP_MMC_COVER_POLL_DELAY));
+}
+
+/* Prepare to transfer the next segment of a scatterlist */
+static void
+mmc_omap_prepare_dma(struct mmc_omap_host *host, struct mmc_data *data)
+{
+ int dma_ch = host->dma_ch;
+ unsigned long data_addr;
+ u16 buf, frame;
+ u32 count;
+ struct scatterlist *sg = &data->sg[host->sg_idx];
+ int src_port = 0;
+ int dst_port = 0;
+ int sync_dev = 0;
+
+ data_addr = host->phys_base + OMAP_MMC_REG(host, DATA);
+ frame = data->blksz;
+ count = sg_dma_len(sg);
+
+ if ((data->blocks == 1) && (count > data->blksz))
+ count = frame;
+
+ host->dma_len = count;
+
+ /* FIFO is 16x2 bytes on 15xx, and 32x2 bytes on 16xx and 24xx.
+ * Use 16 or 32 word frames when the blocksize is at least that large.
+ * Blocksize is usually 512 bytes; but not for some SD reads.
+ */
+ if (cpu_is_omap15xx() && frame > 32)
+ frame = 32;
+ else if (frame > 64)
+ frame = 64;
+ count /= frame;
+ frame >>= 1;
+
+ if (!(data->flags & MMC_DATA_WRITE)) {
+ buf = 0x800f | ((frame - 1) << 8);
+
+ if (cpu_class_is_omap1()) {
+ src_port = OMAP_DMA_PORT_TIPB;
+ dst_port = OMAP_DMA_PORT_EMIFF;
+ }
+ if (cpu_is_omap24xx())
+ sync_dev = OMAP24XX_DMA_MMC1_RX;
+
+ omap_set_dma_src_params(dma_ch, src_port,
+ OMAP_DMA_AMODE_CONSTANT,
+ data_addr, 0, 0);
+ omap_set_dma_dest_params(dma_ch, dst_port,
+ OMAP_DMA_AMODE_POST_INC,
+ sg_dma_address(sg), 0, 0);
+ omap_set_dma_dest_data_pack(dma_ch, 1);
+ omap_set_dma_dest_burst_mode(dma_ch, OMAP_DMA_DATA_BURST_4);
+ } else {
+ buf = 0x0f80 | ((frame - 1) << 0);
+
+ if (cpu_class_is_omap1()) {
+ src_port = OMAP_DMA_PORT_EMIFF;
+ dst_port = OMAP_DMA_PORT_TIPB;
+ }
+ if (cpu_is_omap24xx())
+ sync_dev = OMAP24XX_DMA_MMC1_TX;
+
+ omap_set_dma_dest_params(dma_ch, dst_port,
+ OMAP_DMA_AMODE_CONSTANT,
+ data_addr, 0, 0);
+ omap_set_dma_src_params(dma_ch, src_port,
+ OMAP_DMA_AMODE_POST_INC,
+ sg_dma_address(sg), 0, 0);
+ omap_set_dma_src_data_pack(dma_ch, 1);
+ omap_set_dma_src_burst_mode(dma_ch, OMAP_DMA_DATA_BURST_4);
+ }
+
+ /* Max limit for DMA frame count is 0xffff */
+ BUG_ON(count > 0xffff);
+
+ OMAP_MMC_WRITE(host, BUF, buf);
+ omap_set_dma_transfer_params(dma_ch, OMAP_DMA_DATA_TYPE_S16,
+ frame, count, OMAP_DMA_SYNC_FRAME,
+ sync_dev, 0);
+}
+
+/* A scatterlist segment completed */
+static void mmc_omap_dma_cb(int lch, u16 ch_status, void *data)
+{
+ struct mmc_omap_host *host = (struct mmc_omap_host *) data;
+ struct mmc_data *mmcdat = host->data;
+
+ if (unlikely(host->dma_ch < 0)) {
+ dev_err(mmc_dev(host->mmc),
+ "DMA callback while DMA not enabled\n");
+ return;
+ }
+ /* FIXME: We really should do something to _handle_ the errors */
+ if (ch_status & OMAP1_DMA_TOUT_IRQ) {
+ dev_err(mmc_dev(host->mmc),"DMA timeout\n");
+ return;
+ }
+ if (ch_status & OMAP_DMA_DROP_IRQ) {
+ dev_err(mmc_dev(host->mmc), "DMA sync error\n");
+ return;
+ }
+ if (!(ch_status & OMAP_DMA_BLOCK_IRQ)) {
+ return;
+ }
+ mmcdat->bytes_xfered += host->dma_len;
+ host->sg_idx++;
+ if (host->sg_idx < host->sg_len) {
+ mmc_omap_prepare_dma(host, host->data);
+ omap_start_dma(host->dma_ch);
+ } else
+ mmc_omap_dma_done(host, host->data);
+}
+
+static int mmc_omap_get_dma_channel(struct mmc_omap_host *host, struct mmc_data *data)
+{
+ const char *dma_dev_name;
+ int sync_dev, dma_ch, is_read, r;
+
+ is_read = !(data->flags & MMC_DATA_WRITE);
+ del_timer_sync(&host->dma_timer);
+ if (host->dma_ch >= 0) {
+ if (is_read == host->dma_is_read)
+ return 0;
+ omap_free_dma(host->dma_ch);
+ host->dma_ch = -1;
+ }
+
+ if (is_read) {
+ if (host->id == 0) {
+ sync_dev = OMAP_DMA_MMC_RX;
+ dma_dev_name = "MMC1 read";
+ } else {
+ sync_dev = OMAP_DMA_MMC2_RX;
+ dma_dev_name = "MMC2 read";
+ }
+ } else {
+ if (host->id == 0) {
+ sync_dev = OMAP_DMA_MMC_TX;
+ dma_dev_name = "MMC1 write";
+ } else {
+ sync_dev = OMAP_DMA_MMC2_TX;
+ dma_dev_name = "MMC2 write";
+ }
+ }
+ r = omap_request_dma(sync_dev, dma_dev_name, mmc_omap_dma_cb,
+ host, &dma_ch);
+ if (r != 0) {
+ dev_dbg(mmc_dev(host->mmc), "omap_request_dma() failed with %d\n", r);
+ return r;
+ }
+ host->dma_ch = dma_ch;
+ host->dma_is_read = is_read;
+
+ return 0;
+}
+
+static inline void set_cmd_timeout(struct mmc_omap_host *host, struct mmc_request *req)
+{
+ u16 reg;
+
+ reg = OMAP_MMC_READ(host, SDIO);
+ reg &= ~(1 << 5);
+ OMAP_MMC_WRITE(host, SDIO, reg);
+ /* Set maximum timeout */
+ OMAP_MMC_WRITE(host, CTO, 0xff);
+}
+
+static inline void set_data_timeout(struct mmc_omap_host *host, struct mmc_request *req)
+{
+ unsigned int timeout, cycle_ns;
+ u16 reg;
+
+ cycle_ns = 1000000000 / host->current_slot->fclk_freq;
+ timeout = req->data->timeout_ns / cycle_ns;
+ timeout += req->data->timeout_clks;
+
+ /* Check if we need to use timeout multiplier register */
+ reg = OMAP_MMC_READ(host, SDIO);
+ if (timeout > 0xffff) {
+ reg |= (1 << 5);
+ timeout /= 1024;
+ } else
+ reg &= ~(1 << 5);
+ OMAP_MMC_WRITE(host, SDIO, reg);
+ OMAP_MMC_WRITE(host, DTO, timeout);
+}
+
+static void
+mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req)
+{
+ struct mmc_data *data = req->data;
+ int i, use_dma, block_size;
+ unsigned sg_len;
+
+ host->data = data;
+ if (data == NULL) {
+ OMAP_MMC_WRITE(host, BLEN, 0);
+ OMAP_MMC_WRITE(host, NBLK, 0);
+ OMAP_MMC_WRITE(host, BUF, 0);
+ host->dma_in_use = 0;
+ set_cmd_timeout(host, req);
+ return;
+ }
+
+ block_size = data->blksz;
+
+ OMAP_MMC_WRITE(host, NBLK, data->blocks - 1);
+ OMAP_MMC_WRITE(host, BLEN, block_size - 1);
+ set_data_timeout(host, req);
+
+ /* cope with calling layer confusion; it issues "single
+ * block" writes using multi-block scatterlists.
+ */
+ sg_len = (data->blocks == 1) ? 1 : data->sg_len;
+
+ /* Only do DMA for entire blocks */
+ use_dma = host->use_dma;
+ if (use_dma) {
+ for (i = 0; i < sg_len; i++) {
+ if ((data->sg[i].length % block_size) != 0) {
+ use_dma = 0;
+ break;
+ }
+ }
+ }
+
+ host->sg_idx = 0;
+ if (use_dma) {
+ if (mmc_omap_get_dma_channel(host, data) == 0) {
+ enum dma_data_direction dma_data_dir;
+
+ if (data->flags & MMC_DATA_WRITE)
+ dma_data_dir = DMA_TO_DEVICE;
+ else
+ dma_data_dir = DMA_FROM_DEVICE;
+
+ host->sg_len = dma_map_sg(mmc_dev(host->mmc), data->sg,
+ sg_len, dma_data_dir);
+ host->total_bytes_left = 0;
+ mmc_omap_prepare_dma(host, req->data);
+ host->brs_received = 0;
+ host->dma_done = 0;
+ host->dma_in_use = 1;
+ } else
+ use_dma = 0;
+ }
+
+ /* Revert to PIO? */
+ if (!use_dma) {
+ OMAP_MMC_WRITE(host, BUF, 0x1f1f);
+ host->total_bytes_left = data->blocks * block_size;
+ host->sg_len = sg_len;
+ mmc_omap_sg_to_buf(host);
+ host->dma_in_use = 0;
+ }
+}
+
+static void mmc_omap_start_request(struct mmc_omap_host *host,
+ struct mmc_request *req)
+{
+ BUG_ON(host->mrq != NULL);
+
+ host->mrq = req;
+
+ /* only touch fifo AFTER the controller readies it */
+ mmc_omap_prepare_data(host, req);
+ mmc_omap_start_command(host, req->cmd);
+ if (host->dma_in_use)
+ omap_start_dma(host->dma_ch);
+}
+
+static void mmc_omap_request(struct mmc_host *mmc, struct mmc_request *req)
+{
+ struct mmc_omap_slot *slot = mmc_priv(mmc);
+ struct mmc_omap_host *host = slot->host;
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->slot_lock, flags);
+ if (host->mmc != NULL) {
+ BUG_ON(slot->mrq != NULL);
+ slot->mrq = req;
+ spin_unlock_irqrestore(&host->slot_lock, flags);
+ return;
+ } else
+ host->mmc = mmc;
+ spin_unlock_irqrestore(&host->slot_lock, flags);
+ mmc_omap_select_slot(slot, 1);
+ mmc_omap_start_request(host, req);
+}
+
+static void mmc_omap_set_power(struct mmc_omap_slot *slot, int power_on,
+ int vdd)
+{
+ struct mmc_omap_host *host;
+
+ host = slot->host;
+
+ if (slot->pdata->set_power != NULL)
+ slot->pdata->set_power(mmc_dev(slot->mmc), slot->id, power_on,
+ vdd);
+
+ if (cpu_is_omap24xx()) {
+ u16 w;
+
+ if (power_on) {
+ w = OMAP_MMC_READ(host, CON);
+ OMAP_MMC_WRITE(host, CON, w | (1 << 11));
+ } else {
+ w = OMAP_MMC_READ(host, CON);
+ OMAP_MMC_WRITE(host, CON, w & ~(1 << 11));
+ }
+ }
+}
+
+static int mmc_omap_calc_divisor(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct mmc_omap_slot *slot = mmc_priv(mmc);
+ struct mmc_omap_host *host = slot->host;
+ int func_clk_rate = clk_get_rate(host->fclk);
+ int dsor;
+
+ if (ios->clock == 0)
+ return 0;
+
+ dsor = func_clk_rate / ios->clock;
+ if (dsor < 1)
+ dsor = 1;
+
+ if (func_clk_rate / dsor > ios->clock)
+ dsor++;
+
+ if (dsor > 250)
+ dsor = 250;
+
+ slot->fclk_freq = func_clk_rate / dsor;
+
+ if (ios->bus_width == MMC_BUS_WIDTH_4)
+ dsor |= 1 << 15;
+
+ return dsor;
+}
+
+static void mmc_omap_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct mmc_omap_slot *slot = mmc_priv(mmc);
+ struct mmc_omap_host *host = slot->host;
+ int i, dsor;
+ int clk_enabled;
+
+ mmc_omap_select_slot(slot, 0);
+
+ dsor = mmc_omap_calc_divisor(mmc, ios);
+
+ if (ios->vdd != slot->vdd)
+ slot->vdd = ios->vdd;
+
+ clk_enabled = 0;
+ switch (ios->power_mode) {
+ case MMC_POWER_OFF:
+ mmc_omap_set_power(slot, 0, ios->vdd);
+ break;
+ case MMC_POWER_UP:
+ /* Cannot touch dsor yet, just power up MMC */
+ mmc_omap_set_power(slot, 1, ios->vdd);
+ goto exit;
+ case MMC_POWER_ON:
+ mmc_omap_fclk_enable(host, 1);
+ clk_enabled = 1;
+ dsor |= 1 << 11;
+ break;
+ }
+
+ if (slot->bus_mode != ios->bus_mode) {
+ if (slot->pdata->set_bus_mode != NULL)
+ slot->pdata->set_bus_mode(mmc_dev(mmc), slot->id,
+ ios->bus_mode);
+ slot->bus_mode = ios->bus_mode;
+ }
+
+ /* On insanely high arm_per frequencies something sometimes
+ * goes somehow out of sync, and the POW bit is not being set,
+ * which results in the while loop below getting stuck.
+ * Writing to the CON register twice seems to do the trick. */
+ for (i = 0; i < 2; i++)
+ OMAP_MMC_WRITE(host, CON, dsor);
+ slot->saved_con = dsor;
+ if (ios->power_mode == MMC_POWER_ON) {
+ /* worst case at 400kHz, 80 cycles makes 200 microsecs */
+ int usecs = 250;
+
+ /* Send clock cycles, poll completion */
+ OMAP_MMC_WRITE(host, IE, 0);
+ OMAP_MMC_WRITE(host, STAT, 0xffff);
+ OMAP_MMC_WRITE(host, CMD, 1 << 7);
+ while (usecs > 0 && (OMAP_MMC_READ(host, STAT) & 1) == 0) {
+ udelay(1);
+ usecs--;
+ }
+ OMAP_MMC_WRITE(host, STAT, 1);
+ }
+
+exit:
+ mmc_omap_release_slot(slot, clk_enabled);
+}
+
+static const struct mmc_host_ops mmc_omap_ops = {
+ .request = mmc_omap_request,
+ .set_ios = mmc_omap_set_ios,
+};
+
+static int __init mmc_omap_new_slot(struct mmc_omap_host *host, int id)
+{
+ struct mmc_omap_slot *slot = NULL;
+ struct mmc_host *mmc;
+ int r;
+
+ mmc = mmc_alloc_host(sizeof(struct mmc_omap_slot), host->dev);
+ if (mmc == NULL)
+ return -ENOMEM;
+
+ slot = mmc_priv(mmc);
+ slot->host = host;
+ slot->mmc = mmc;
+ slot->id = id;
+ slot->pdata = &host->pdata->slots[id];
+
+ host->slots[id] = slot;
+
+ mmc->caps = 0;
+ if (host->pdata->slots[id].wires >= 4)
+ mmc->caps |= MMC_CAP_4_BIT_DATA;
+
+ mmc->ops = &mmc_omap_ops;
+ mmc->f_min = 400000;
+
+ if (cpu_class_is_omap2())
+ mmc->f_max = 48000000;
+ else
+ mmc->f_max = 24000000;
+ if (host->pdata->max_freq)
+ mmc->f_max = min(host->pdata->max_freq, mmc->f_max);
+ mmc->ocr_avail = slot->pdata->ocr_mask;
+
+ /* Use scatterlist DMA to reduce per-transfer costs.
+ * NOTE max_seg_size assumption that small blocks aren't
+ * normally used (except e.g. for reading SD registers).
+ */
+ mmc->max_segs = 32;
+ mmc->max_blk_size = 2048; /* BLEN is 11 bits (+1) */
+ mmc->max_blk_count = 2048; /* NBLK is 11 bits (+1) */
+ mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
+ mmc->max_seg_size = mmc->max_req_size;
+
+ r = mmc_add_host(mmc);
+ if (r < 0)
+ goto err_remove_host;
+
+ if (slot->pdata->name != NULL) {
+ r = device_create_file(&mmc->class_dev,
+ &dev_attr_slot_name);
+ if (r < 0)
+ goto err_remove_host;
+ }
+
+ if (slot->pdata->get_cover_state != NULL) {
+ r = device_create_file(&mmc->class_dev,
+ &dev_attr_cover_switch);
+ if (r < 0)
+ goto err_remove_slot_name;
+
+ setup_timer(&slot->cover_timer, mmc_omap_cover_timer,
+ (unsigned long)slot);
+ tasklet_init(&slot->cover_tasklet, mmc_omap_cover_handler,
+ (unsigned long)slot);
+ tasklet_schedule(&slot->cover_tasklet);
+ }
+
+ return 0;
+
+err_remove_slot_name:
+ if (slot->pdata->name != NULL)
+ device_remove_file(&mmc->class_dev, &dev_attr_slot_name);
+err_remove_host:
+ mmc_remove_host(mmc);
+ mmc_free_host(mmc);
+ return r;
+}
+
+static void mmc_omap_remove_slot(struct mmc_omap_slot *slot)
+{
+ struct mmc_host *mmc = slot->mmc;
+
+ if (slot->pdata->name != NULL)
+ device_remove_file(&mmc->class_dev, &dev_attr_slot_name);
+ if (slot->pdata->get_cover_state != NULL)
+ device_remove_file(&mmc->class_dev, &dev_attr_cover_switch);
+
+ tasklet_kill(&slot->cover_tasklet);
+ del_timer_sync(&slot->cover_timer);
+ flush_workqueue(mmc_omap_wq);
+
+ mmc_remove_host(mmc);
+ mmc_free_host(mmc);
+}
+
+static int __init mmc_omap_probe(struct platform_device *pdev)
+{
+ struct omap_mmc_platform_data *pdata = pdev->dev.platform_data;
+ struct mmc_omap_host *host = NULL;
+ struct resource *res;
+ int i, ret = 0;
+ int irq;
+
+ if (pdata == NULL) {
+ dev_err(&pdev->dev, "platform data missing\n");
+ return -ENXIO;
+ }
+ if (pdata->nr_slots == 0) {
+ dev_err(&pdev->dev, "no slots\n");
+ return -ENXIO;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ irq = platform_get_irq(pdev, 0);
+ if (res == NULL || irq < 0)
+ return -ENXIO;
+
+ res = request_mem_region(res->start, resource_size(res),
+ pdev->name);
+ if (res == NULL)
+ return -EBUSY;
+
+ host = kzalloc(sizeof(struct mmc_omap_host), GFP_KERNEL);
+ if (host == NULL) {
+ ret = -ENOMEM;
+ goto err_free_mem_region;
+ }
+
+ INIT_WORK(&host->slot_release_work, mmc_omap_slot_release_work);
+ INIT_WORK(&host->send_stop_work, mmc_omap_send_stop_work);
+
+ INIT_WORK(&host->cmd_abort_work, mmc_omap_abort_command);
+ setup_timer(&host->cmd_abort_timer, mmc_omap_cmd_timer,
+ (unsigned long) host);
+
+ spin_lock_init(&host->clk_lock);
+ setup_timer(&host->clk_timer, mmc_omap_clk_timer, (unsigned long) host);
+
+ spin_lock_init(&host->dma_lock);
+ setup_timer(&host->dma_timer, mmc_omap_dma_timer, (unsigned long) host);
+ spin_lock_init(&host->slot_lock);
+ init_waitqueue_head(&host->slot_wq);
+
+ host->pdata = pdata;
+ host->dev = &pdev->dev;
+ platform_set_drvdata(pdev, host);
+
+ host->id = pdev->id;
+ host->mem_res = res;
+ host->irq = irq;
+
+ host->use_dma = 1;
+ host->dev->dma_mask = &pdata->dma_mask;
+ host->dma_ch = -1;
+
+ host->irq = irq;
+ host->phys_base = host->mem_res->start;
+ host->virt_base = ioremap(res->start, resource_size(res));
+ if (!host->virt_base)
+ goto err_ioremap;
+
+ host->iclk = clk_get(&pdev->dev, "ick");
+ if (IS_ERR(host->iclk)) {
+ ret = PTR_ERR(host->iclk);
+ goto err_free_mmc_host;
+ }
+ clk_enable(host->iclk);
+
+ host->fclk = clk_get(&pdev->dev, "fck");
+ if (IS_ERR(host->fclk)) {
+ ret = PTR_ERR(host->fclk);
+ goto err_free_iclk;
+ }
+
+ ret = request_irq(host->irq, mmc_omap_irq, 0, DRIVER_NAME, host);
+ if (ret)
+ goto err_free_fclk;
+
+ if (pdata->init != NULL) {
+ ret = pdata->init(&pdev->dev);
+ if (ret < 0)
+ goto err_free_irq;
+ }
+
+ host->nr_slots = pdata->nr_slots;
+ for (i = 0; i < pdata->nr_slots; i++) {
+ ret = mmc_omap_new_slot(host, i);
+ if (ret < 0) {
+ while (--i >= 0)
+ mmc_omap_remove_slot(host->slots[i]);
+
+ goto err_plat_cleanup;
+ }
+ }
+
+ host->reg_shift = (cpu_is_omap7xx() ? 1 : 2);
+
+ return 0;
+
+err_plat_cleanup:
+ if (pdata->cleanup)
+ pdata->cleanup(&pdev->dev);
+err_free_irq:
+ free_irq(host->irq, host);
+err_free_fclk:
+ clk_put(host->fclk);
+err_free_iclk:
+ clk_disable(host->iclk);
+ clk_put(host->iclk);
+err_free_mmc_host:
+ iounmap(host->virt_base);
+err_ioremap:
+ kfree(host);
+err_free_mem_region:
+ release_mem_region(res->start, resource_size(res));
+ return ret;
+}
+
+static int mmc_omap_remove(struct platform_device *pdev)
+{
+ struct mmc_omap_host *host = platform_get_drvdata(pdev);
+ int i;
+
+ platform_set_drvdata(pdev, NULL);
+
+ BUG_ON(host == NULL);
+
+ for (i = 0; i < host->nr_slots; i++)
+ mmc_omap_remove_slot(host->slots[i]);
+
+ if (host->pdata->cleanup)
+ host->pdata->cleanup(&pdev->dev);
+
+ mmc_omap_fclk_enable(host, 0);
+ free_irq(host->irq, host);
+ clk_put(host->fclk);
+ clk_disable(host->iclk);
+ clk_put(host->iclk);
+
+ iounmap(host->virt_base);
+ release_mem_region(pdev->resource[0].start,
+ pdev->resource[0].end - pdev->resource[0].start + 1);
+
+ kfree(host);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int mmc_omap_suspend(struct platform_device *pdev, pm_message_t mesg)
+{
+ int i, ret = 0;
+ struct mmc_omap_host *host = platform_get_drvdata(pdev);
+
+ if (host == NULL || host->suspended)
+ return 0;
+
+ for (i = 0; i < host->nr_slots; i++) {
+ struct mmc_omap_slot *slot;
+
+ slot = host->slots[i];
+ ret = mmc_suspend_host(slot->mmc);
+ if (ret < 0) {
+ while (--i >= 0) {
+ slot = host->slots[i];
+ mmc_resume_host(slot->mmc);
+ }
+ return ret;
+ }
+ }
+ host->suspended = 1;
+ return 0;
+}
+
+static int mmc_omap_resume(struct platform_device *pdev)
+{
+ int i, ret = 0;
+ struct mmc_omap_host *host = platform_get_drvdata(pdev);
+
+ if (host == NULL || !host->suspended)
+ return 0;
+
+ for (i = 0; i < host->nr_slots; i++) {
+ struct mmc_omap_slot *slot;
+ slot = host->slots[i];
+ ret = mmc_resume_host(slot->mmc);
+ if (ret < 0)
+ return ret;
+
+ host->suspended = 0;
+ }
+ return 0;
+}
+#else
+#define mmc_omap_suspend NULL
+#define mmc_omap_resume NULL
+#endif
+
+static struct platform_driver mmc_omap_driver = {
+ .remove = mmc_omap_remove,
+ .suspend = mmc_omap_suspend,
+ .resume = mmc_omap_resume,
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init mmc_omap_init(void)
+{
+ int ret;
+
+ mmc_omap_wq = alloc_workqueue("mmc_omap", 0, 0);
+ if (!mmc_omap_wq)
+ return -ENOMEM;
+
+ ret = platform_driver_probe(&mmc_omap_driver, mmc_omap_probe);
+ if (ret)
+ destroy_workqueue(mmc_omap_wq);
+ return ret;
+}
+
+static void __exit mmc_omap_exit(void)
+{
+ platform_driver_unregister(&mmc_omap_driver);
+ destroy_workqueue(mmc_omap_wq);
+}
+
+module_init(mmc_omap_init);
+module_exit(mmc_omap_exit);
+
+MODULE_DESCRIPTION("OMAP Multimedia Card driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRIVER_NAME);
+MODULE_AUTHOR("Juha Yrjölä");
diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c
new file mode 100644
index 00000000..b0c69104
--- /dev/null
+++ b/drivers/mmc/host/omap_hsmmc.c
@@ -0,0 +1,2551 @@
+/*
+ * drivers/mmc/host/omap_hsmmc.c
+ *
+ * Driver for OMAP2430/3430 MMC controller.
+ *
+ * Copyright (C) 2007 Texas Instruments.
+ *
+ * Authors:
+ * Syed Mohammed Khasim <x0khasim@ti.com>
+ * Madhusudhan <madhu.cr@ti.com>
+ * Mohit Jalori <mjalori@ti.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+#include <linux/timer.h>
+#include <linux/clk.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/core.h>
+#include <linux/mmc/mmc.h>
+#include <linux/io.h>
+#include <linux/semaphore.h>
+#include <linux/gpio.h>
+#include <linux/regulator/consumer.h>
+#include <plat/dma.h>
+#include <mach/hardware.h>
+#include <plat/board.h>
+#include <plat/mmc.h>
+#include <plat/cpu.h>
+
+/* OMAP HSMMC Host Controller Registers */
+#define OMAP_HSMMC_SYSCONFIG 0x0010
+#define OMAP_HSMMC_SYSSTATUS 0x0014
+#define OMAP_HSMMC_CON 0x002C
+#define OMAP_HSMMC_BLK 0x0104
+#define OMAP_HSMMC_ARG 0x0108
+#define OMAP_HSMMC_CMD 0x010C
+#define OMAP_HSMMC_RSP10 0x0110
+#define OMAP_HSMMC_RSP32 0x0114
+#define OMAP_HSMMC_RSP54 0x0118
+#define OMAP_HSMMC_RSP76 0x011C
+#define OMAP_HSMMC_DATA 0x0120
+#define OMAP_HSMMC_HCTL 0x0128
+#define OMAP_HSMMC_SYSCTL 0x012C
+#define OMAP_HSMMC_STAT 0x0130
+#define OMAP_HSMMC_IE 0x0134
+#define OMAP_HSMMC_ISE 0x0138
+#define OMAP_HSMMC_CAPA 0x0140
+
+#define VS18 (1 << 26)
+#define VS30 (1 << 25)
+#define SDVS18 (0x5 << 9)
+#define SDVS30 (0x6 << 9)
+#define SDVS33 (0x7 << 9)
+#define SDVS_MASK 0x00000E00
+#define SDVSCLR 0xFFFFF1FF
+#define SDVSDET 0x00000400
+#define AUTOIDLE 0x1
+#define SDBP (1 << 8)
+#define DTO 0xe
+#define ICE 0x1
+#define ICS 0x2
+#define CEN (1 << 2)
+#define CLKD_MASK 0x0000FFC0
+#define CLKD_SHIFT 6
+#define DTO_MASK 0x000F0000
+#define DTO_SHIFT 16
+#define INT_EN_MASK 0x307F0033
+#define BWR_ENABLE (1 << 4)
+#define BRR_ENABLE (1 << 5)
+#define DTO_ENABLE (1 << 20)
+#define INIT_STREAM (1 << 1)
+#define DP_SELECT (1 << 21)
+#define DDIR (1 << 4)
+#define DMA_EN 0x1
+#define MSBS (1 << 5)
+#define BCE (1 << 1)
+#define FOUR_BIT (1 << 1)
+#define DW8 (1 << 5)
+#define CC 0x1
+#define TC 0x02
+#define OD 0x1
+#define ERR (1 << 15)
+#define CMD_TIMEOUT (1 << 16)
+#define DATA_TIMEOUT (1 << 20)
+#define CMD_CRC (1 << 17)
+#define DATA_CRC (1 << 21)
+#define CARD_ERR (1 << 28)
+#define STAT_CLEAR 0xFFFFFFFF
+#define INIT_STREAM_CMD 0x00000000
+#define DUAL_VOLT_OCR_BIT 7
+#define SRC (1 << 25)
+#define SRD (1 << 26)
+#define SOFTRESET (1 << 1)
+#define RESETDONE (1 << 0)
+
+/*
+ * FIXME: Most likely all the data using these _DEVID defines should come
+ * from the platform_data, or implemented in controller and slot specific
+ * functions.
+ */
+#define OMAP_MMC1_DEVID 0
+#define OMAP_MMC2_DEVID 1
+#define OMAP_MMC3_DEVID 2
+#define OMAP_MMC4_DEVID 3
+#define OMAP_MMC5_DEVID 4
+
+#define MMC_TIMEOUT_MS 20
+#define OMAP_MMC_MASTER_CLOCK 96000000
+#define DRIVER_NAME "omap_hsmmc"
+
+/* Timeouts for entering power saving states on inactivity, msec */
+#define OMAP_MMC_DISABLED_TIMEOUT 100
+#define OMAP_MMC_SLEEP_TIMEOUT 1000
+#define OMAP_MMC_OFF_TIMEOUT 8000
+
+/*
+ * One controller can have multiple slots, like on some omap boards using
+ * omap.c controller driver. Luckily this is not currently done on any known
+ * omap_hsmmc.c device.
+ */
+#define mmc_slot(host) (host->pdata->slots[host->slot_id])
+
+/*
+ * MMC Host controller read/write API's
+ */
+#define OMAP_HSMMC_READ(base, reg) \
+ __raw_readl((base) + OMAP_HSMMC_##reg)
+
+#define OMAP_HSMMC_WRITE(base, reg, val) \
+ __raw_writel((val), (base) + OMAP_HSMMC_##reg)
+
+struct omap_hsmmc_next {
+ unsigned int dma_len;
+ s32 cookie;
+};
+
+struct omap_hsmmc_host {
+ struct device *dev;
+ struct mmc_host *mmc;
+ struct mmc_request *mrq;
+ struct mmc_command *cmd;
+ struct mmc_data *data;
+ struct clk *fclk;
+ struct clk *iclk;
+ struct clk *dbclk;
+ /*
+ * vcc == configured supply
+ * vcc_aux == optional
+ * - MMC1, supply for DAT4..DAT7
+ * - MMC2/MMC2, external level shifter voltage supply, for
+ * chip (SDIO, eMMC, etc) or transceiver (MMC2 only)
+ */
+ struct regulator *vcc;
+ struct regulator *vcc_aux;
+ struct work_struct mmc_carddetect_work;
+ void __iomem *base;
+ resource_size_t mapbase;
+ spinlock_t irq_lock; /* Prevent races with irq handler */
+ unsigned int id;
+ unsigned int dma_len;
+ unsigned int dma_sg_idx;
+ unsigned char bus_mode;
+ unsigned char power_mode;
+ u32 *buffer;
+ u32 bytesleft;
+ int suspended;
+ int irq;
+ int use_dma, dma_ch;
+ int dma_line_tx, dma_line_rx;
+ int slot_id;
+ int got_dbclk;
+ int response_busy;
+ int context_loss;
+ int dpm_state;
+ int vdd;
+ int protect_card;
+ int reqs_blocked;
+ int use_reg;
+ int req_in_progress;
+ struct omap_hsmmc_next next_data;
+
+ struct omap_mmc_platform_data *pdata;
+};
+
+static int omap_hsmmc_card_detect(struct device *dev, int slot)
+{
+ struct omap_mmc_platform_data *mmc = dev->platform_data;
+
+ /* NOTE: assumes card detect signal is active-low */
+ return !gpio_get_value_cansleep(mmc->slots[0].switch_pin);
+}
+
+static int omap_hsmmc_get_wp(struct device *dev, int slot)
+{
+ struct omap_mmc_platform_data *mmc = dev->platform_data;
+
+ /* NOTE: assumes write protect signal is active-high */
+ return gpio_get_value_cansleep(mmc->slots[0].gpio_wp);
+}
+
+static int omap_hsmmc_get_cover_state(struct device *dev, int slot)
+{
+ struct omap_mmc_platform_data *mmc = dev->platform_data;
+
+ /* NOTE: assumes card detect signal is active-low */
+ return !gpio_get_value_cansleep(mmc->slots[0].switch_pin);
+}
+
+#ifdef CONFIG_PM
+
+static int omap_hsmmc_suspend_cdirq(struct device *dev, int slot)
+{
+ struct omap_mmc_platform_data *mmc = dev->platform_data;
+
+ disable_irq(mmc->slots[0].card_detect_irq);
+ return 0;
+}
+
+static int omap_hsmmc_resume_cdirq(struct device *dev, int slot)
+{
+ struct omap_mmc_platform_data *mmc = dev->platform_data;
+
+ enable_irq(mmc->slots[0].card_detect_irq);
+ return 0;
+}
+
+#else
+
+#define omap_hsmmc_suspend_cdirq NULL
+#define omap_hsmmc_resume_cdirq NULL
+
+#endif
+
+#ifdef CONFIG_REGULATOR
+
+static int omap_hsmmc_1_set_power(struct device *dev, int slot, int power_on,
+ int vdd)
+{
+ struct omap_hsmmc_host *host =
+ platform_get_drvdata(to_platform_device(dev));
+ int ret;
+
+ if (mmc_slot(host).before_set_reg)
+ mmc_slot(host).before_set_reg(dev, slot, power_on, vdd);
+
+ if (power_on)
+ ret = mmc_regulator_set_ocr(host->mmc, host->vcc, vdd);
+ else
+ ret = mmc_regulator_set_ocr(host->mmc, host->vcc, 0);
+
+ if (mmc_slot(host).after_set_reg)
+ mmc_slot(host).after_set_reg(dev, slot, power_on, vdd);
+
+ return ret;
+}
+
+static int omap_hsmmc_235_set_power(struct device *dev, int slot, int power_on,
+ int vdd)
+{
+ struct omap_hsmmc_host *host =
+ platform_get_drvdata(to_platform_device(dev));
+ int ret = 0;
+
+ /*
+ * If we don't see a Vcc regulator, assume it's a fixed
+ * voltage always-on regulator.
+ */
+ if (!host->vcc)
+ return 0;
+
+ if (mmc_slot(host).before_set_reg)
+ mmc_slot(host).before_set_reg(dev, slot, power_on, vdd);
+
+ /*
+ * Assume Vcc regulator is used only to power the card ... OMAP
+ * VDDS is used to power the pins, optionally with a transceiver to
+ * support cards using voltages other than VDDS (1.8V nominal). When a
+ * transceiver is used, DAT3..7 are muxed as transceiver control pins.
+ *
+ * In some cases this regulator won't support enable/disable;
+ * e.g. it's a fixed rail for a WLAN chip.
+ *
+ * In other cases vcc_aux switches interface power. Example, for
+ * eMMC cards it represents VccQ. Sometimes transceivers or SDIO
+ * chips/cards need an interface voltage rail too.
+ */
+ if (power_on) {
+ ret = mmc_regulator_set_ocr(host->mmc, host->vcc, vdd);
+ /* Enable interface voltage rail, if needed */
+ if (ret == 0 && host->vcc_aux) {
+ ret = regulator_enable(host->vcc_aux);
+ if (ret < 0)
+ ret = mmc_regulator_set_ocr(host->mmc,
+ host->vcc, 0);
+ }
+ } else {
+ /* Shut down the rail */
+ if (host->vcc_aux)
+ ret = regulator_disable(host->vcc_aux);
+ if (!ret) {
+ /* Then proceed to shut down the local regulator */
+ ret = mmc_regulator_set_ocr(host->mmc,
+ host->vcc, 0);
+ }
+ }
+
+ if (mmc_slot(host).after_set_reg)
+ mmc_slot(host).after_set_reg(dev, slot, power_on, vdd);
+
+ return ret;
+}
+
+static int omap_hsmmc_4_set_power(struct device *dev, int slot, int power_on,
+ int vdd)
+{
+ return 0;
+}
+
+static int omap_hsmmc_1_set_sleep(struct device *dev, int slot, int sleep,
+ int vdd, int cardsleep)
+{
+ struct omap_hsmmc_host *host =
+ platform_get_drvdata(to_platform_device(dev));
+ int mode = sleep ? REGULATOR_MODE_STANDBY : REGULATOR_MODE_NORMAL;
+
+ return regulator_set_mode(host->vcc, mode);
+}
+
+static int omap_hsmmc_235_set_sleep(struct device *dev, int slot, int sleep,
+ int vdd, int cardsleep)
+{
+ struct omap_hsmmc_host *host =
+ platform_get_drvdata(to_platform_device(dev));
+ int err, mode;
+
+ /*
+ * If we don't see a Vcc regulator, assume it's a fixed
+ * voltage always-on regulator.
+ */
+ if (!host->vcc)
+ return 0;
+
+ mode = sleep ? REGULATOR_MODE_STANDBY : REGULATOR_MODE_NORMAL;
+
+ if (!host->vcc_aux)
+ return regulator_set_mode(host->vcc, mode);
+
+ if (cardsleep) {
+ /* VCC can be turned off if card is asleep */
+ if (sleep)
+ err = mmc_regulator_set_ocr(host->mmc, host->vcc, 0);
+ else
+ err = mmc_regulator_set_ocr(host->mmc, host->vcc, vdd);
+ } else
+ err = regulator_set_mode(host->vcc, mode);
+ if (err)
+ return err;
+
+ if (!mmc_slot(host).vcc_aux_disable_is_sleep)
+ return regulator_set_mode(host->vcc_aux, mode);
+
+ if (sleep)
+ return regulator_disable(host->vcc_aux);
+ else
+ return regulator_enable(host->vcc_aux);
+}
+
+static int omap_hsmmc_4_set_sleep(struct device *dev, int slot, int sleep,
+ int vdd, int cardsleep)
+{
+ return 0;
+}
+
+static int omap_hsmmc_reg_get(struct omap_hsmmc_host *host)
+{
+ struct regulator *reg;
+ int ret = 0;
+ int ocr_value = 0;
+
+ switch (host->id) {
+ case OMAP_MMC1_DEVID:
+ /* On-chip level shifting via PBIAS0/PBIAS1 */
+ mmc_slot(host).set_power = omap_hsmmc_1_set_power;
+ mmc_slot(host).set_sleep = omap_hsmmc_1_set_sleep;
+ break;
+ case OMAP_MMC2_DEVID:
+ case OMAP_MMC3_DEVID:
+ case OMAP_MMC5_DEVID:
+ /* Off-chip level shifting, or none */
+ mmc_slot(host).set_power = omap_hsmmc_235_set_power;
+ mmc_slot(host).set_sleep = omap_hsmmc_235_set_sleep;
+ break;
+ case OMAP_MMC4_DEVID:
+ mmc_slot(host).set_power = omap_hsmmc_4_set_power;
+ mmc_slot(host).set_sleep = omap_hsmmc_4_set_sleep;
+ default:
+ pr_err("MMC%d configuration not supported!\n", host->id);
+ return -EINVAL;
+ }
+
+ reg = regulator_get(host->dev, "vmmc");
+ if (IS_ERR(reg)) {
+ dev_dbg(host->dev, "vmmc regulator missing\n");
+ /*
+ * HACK: until fixed.c regulator is usable,
+ * we don't require a main regulator
+ * for MMC2 or MMC3
+ */
+ if (host->id == OMAP_MMC1_DEVID) {
+ ret = PTR_ERR(reg);
+ goto err;
+ }
+ } else {
+ host->vcc = reg;
+ ocr_value = mmc_regulator_get_ocrmask(reg);
+ if (!mmc_slot(host).ocr_mask) {
+ mmc_slot(host).ocr_mask = ocr_value;
+ } else {
+ if (!(mmc_slot(host).ocr_mask & ocr_value)) {
+ pr_err("MMC%d ocrmask %x is not supported\n",
+ host->id, mmc_slot(host).ocr_mask);
+ mmc_slot(host).ocr_mask = 0;
+ return -EINVAL;
+ }
+ }
+
+ /* Allow an aux regulator */
+ reg = regulator_get(host->dev, "vmmc_aux");
+ host->vcc_aux = IS_ERR(reg) ? NULL : reg;
+
+ /* For eMMC do not power off when not in sleep state */
+ if (mmc_slot(host).no_regulator_off_init)
+ return 0;
+ /*
+ * UGLY HACK: workaround regulator framework bugs.
+ * When the bootloader leaves a supply active, it's
+ * initialized with zero usecount ... and we can't
+ * disable it without first enabling it. Until the
+ * framework is fixed, we need a workaround like this
+ * (which is safe for MMC, but not in general).
+ */
+ if (regulator_is_enabled(host->vcc) > 0) {
+ regulator_enable(host->vcc);
+ regulator_disable(host->vcc);
+ }
+ if (host->vcc_aux) {
+ if (regulator_is_enabled(reg) > 0) {
+ regulator_enable(reg);
+ regulator_disable(reg);
+ }
+ }
+ }
+
+ return 0;
+
+err:
+ mmc_slot(host).set_power = NULL;
+ mmc_slot(host).set_sleep = NULL;
+ return ret;
+}
+
+static void omap_hsmmc_reg_put(struct omap_hsmmc_host *host)
+{
+ regulator_put(host->vcc);
+ regulator_put(host->vcc_aux);
+ mmc_slot(host).set_power = NULL;
+ mmc_slot(host).set_sleep = NULL;
+}
+
+static inline int omap_hsmmc_have_reg(void)
+{
+ return 1;
+}
+
+#else
+
+static inline int omap_hsmmc_reg_get(struct omap_hsmmc_host *host)
+{
+ return -EINVAL;
+}
+
+static inline void omap_hsmmc_reg_put(struct omap_hsmmc_host *host)
+{
+}
+
+static inline int omap_hsmmc_have_reg(void)
+{
+ return 0;
+}
+
+#endif
+
+static int omap_hsmmc_gpio_init(struct omap_mmc_platform_data *pdata)
+{
+ int ret;
+
+ if (gpio_is_valid(pdata->slots[0].switch_pin)) {
+ if (pdata->slots[0].cover)
+ pdata->slots[0].get_cover_state =
+ omap_hsmmc_get_cover_state;
+ else
+ pdata->slots[0].card_detect = omap_hsmmc_card_detect;
+ pdata->slots[0].card_detect_irq =
+ gpio_to_irq(pdata->slots[0].switch_pin);
+ ret = gpio_request(pdata->slots[0].switch_pin, "mmc_cd");
+ if (ret)
+ return ret;
+ ret = gpio_direction_input(pdata->slots[0].switch_pin);
+ if (ret)
+ goto err_free_sp;
+ } else
+ pdata->slots[0].switch_pin = -EINVAL;
+
+ if (gpio_is_valid(pdata->slots[0].gpio_wp)) {
+ pdata->slots[0].get_ro = omap_hsmmc_get_wp;
+ ret = gpio_request(pdata->slots[0].gpio_wp, "mmc_wp");
+ if (ret)
+ goto err_free_cd;
+ ret = gpio_direction_input(pdata->slots[0].gpio_wp);
+ if (ret)
+ goto err_free_wp;
+ } else
+ pdata->slots[0].gpio_wp = -EINVAL;
+
+ return 0;
+
+err_free_wp:
+ gpio_free(pdata->slots[0].gpio_wp);
+err_free_cd:
+ if (gpio_is_valid(pdata->slots[0].switch_pin))
+err_free_sp:
+ gpio_free(pdata->slots[0].switch_pin);
+ return ret;
+}
+
+static void omap_hsmmc_gpio_free(struct omap_mmc_platform_data *pdata)
+{
+ if (gpio_is_valid(pdata->slots[0].gpio_wp))
+ gpio_free(pdata->slots[0].gpio_wp);
+ if (gpio_is_valid(pdata->slots[0].switch_pin))
+ gpio_free(pdata->slots[0].switch_pin);
+}
+
+/*
+ * Stop clock to the card
+ */
+static void omap_hsmmc_stop_clock(struct omap_hsmmc_host *host)
+{
+ OMAP_HSMMC_WRITE(host->base, SYSCTL,
+ OMAP_HSMMC_READ(host->base, SYSCTL) & ~CEN);
+ if ((OMAP_HSMMC_READ(host->base, SYSCTL) & CEN) != 0x0)
+ dev_dbg(mmc_dev(host->mmc), "MMC Clock is not stoped\n");
+}
+
+static void omap_hsmmc_enable_irq(struct omap_hsmmc_host *host,
+ struct mmc_command *cmd)
+{
+ unsigned int irq_mask;
+
+ if (host->use_dma)
+ irq_mask = INT_EN_MASK & ~(BRR_ENABLE | BWR_ENABLE);
+ else
+ irq_mask = INT_EN_MASK;
+
+ /* Disable timeout for erases */
+ if (cmd->opcode == MMC_ERASE)
+ irq_mask &= ~DTO_ENABLE;
+
+ OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
+ OMAP_HSMMC_WRITE(host->base, ISE, irq_mask);
+ OMAP_HSMMC_WRITE(host->base, IE, irq_mask);
+}
+
+static void omap_hsmmc_disable_irq(struct omap_hsmmc_host *host)
+{
+ OMAP_HSMMC_WRITE(host->base, ISE, 0);
+ OMAP_HSMMC_WRITE(host->base, IE, 0);
+ OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
+}
+
+#ifdef CONFIG_PM
+
+/*
+ * Restore the MMC host context, if it was lost as result of a
+ * power state change.
+ */
+static int omap_hsmmc_context_restore(struct omap_hsmmc_host *host)
+{
+ struct mmc_ios *ios = &host->mmc->ios;
+ struct omap_mmc_platform_data *pdata = host->pdata;
+ int context_loss = 0;
+ u32 hctl, capa, con;
+ u16 dsor = 0;
+ unsigned long timeout;
+
+ if (pdata->get_context_loss_count) {
+ context_loss = pdata->get_context_loss_count(host->dev);
+ if (context_loss < 0)
+ return 1;
+ }
+
+ dev_dbg(mmc_dev(host->mmc), "context was %slost\n",
+ context_loss == host->context_loss ? "not " : "");
+ if (host->context_loss == context_loss)
+ return 1;
+
+ /* Wait for hardware reset */
+ timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
+ while ((OMAP_HSMMC_READ(host->base, SYSSTATUS) & RESETDONE) != RESETDONE
+ && time_before(jiffies, timeout))
+ ;
+
+ /* Do software reset */
+ OMAP_HSMMC_WRITE(host->base, SYSCONFIG, SOFTRESET);
+ timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
+ while ((OMAP_HSMMC_READ(host->base, SYSSTATUS) & RESETDONE) != RESETDONE
+ && time_before(jiffies, timeout))
+ ;
+
+ OMAP_HSMMC_WRITE(host->base, SYSCONFIG,
+ OMAP_HSMMC_READ(host->base, SYSCONFIG) | AUTOIDLE);
+
+ if (host->id == OMAP_MMC1_DEVID) {
+ if (host->power_mode != MMC_POWER_OFF &&
+ (1 << ios->vdd) <= MMC_VDD_23_24)
+ hctl = SDVS18;
+ else
+ hctl = SDVS30;
+ capa = VS30 | VS18;
+ } else {
+ hctl = SDVS18;
+ capa = VS18;
+ }
+
+ OMAP_HSMMC_WRITE(host->base, HCTL,
+ OMAP_HSMMC_READ(host->base, HCTL) | hctl);
+
+ OMAP_HSMMC_WRITE(host->base, CAPA,
+ OMAP_HSMMC_READ(host->base, CAPA) | capa);
+
+ OMAP_HSMMC_WRITE(host->base, HCTL,
+ OMAP_HSMMC_READ(host->base, HCTL) | SDBP);
+
+ timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
+ while ((OMAP_HSMMC_READ(host->base, HCTL) & SDBP) != SDBP
+ && time_before(jiffies, timeout))
+ ;
+
+ omap_hsmmc_disable_irq(host);
+
+ /* Do not initialize card-specific things if the power is off */
+ if (host->power_mode == MMC_POWER_OFF)
+ goto out;
+
+ con = OMAP_HSMMC_READ(host->base, CON);
+ switch (ios->bus_width) {
+ case MMC_BUS_WIDTH_8:
+ OMAP_HSMMC_WRITE(host->base, CON, con | DW8);
+ break;
+ case MMC_BUS_WIDTH_4:
+ OMAP_HSMMC_WRITE(host->base, CON, con & ~DW8);
+ OMAP_HSMMC_WRITE(host->base, HCTL,
+ OMAP_HSMMC_READ(host->base, HCTL) | FOUR_BIT);
+ break;
+ case MMC_BUS_WIDTH_1:
+ OMAP_HSMMC_WRITE(host->base, CON, con & ~DW8);
+ OMAP_HSMMC_WRITE(host->base, HCTL,
+ OMAP_HSMMC_READ(host->base, HCTL) & ~FOUR_BIT);
+ break;
+ }
+
+ if (ios->clock) {
+ dsor = OMAP_MMC_MASTER_CLOCK / ios->clock;
+ if (dsor < 1)
+ dsor = 1;
+
+ if (OMAP_MMC_MASTER_CLOCK / dsor > ios->clock)
+ dsor++;
+
+ if (dsor > 250)
+ dsor = 250;
+ }
+
+ OMAP_HSMMC_WRITE(host->base, SYSCTL,
+ OMAP_HSMMC_READ(host->base, SYSCTL) & ~CEN);
+ OMAP_HSMMC_WRITE(host->base, SYSCTL, (dsor << 6) | (DTO << 16));
+ OMAP_HSMMC_WRITE(host->base, SYSCTL,
+ OMAP_HSMMC_READ(host->base, SYSCTL) | ICE);
+
+ timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
+ while ((OMAP_HSMMC_READ(host->base, SYSCTL) & ICS) != ICS
+ && time_before(jiffies, timeout))
+ ;
+
+ OMAP_HSMMC_WRITE(host->base, SYSCTL,
+ OMAP_HSMMC_READ(host->base, SYSCTL) | CEN);
+
+ con = OMAP_HSMMC_READ(host->base, CON);
+ if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN)
+ OMAP_HSMMC_WRITE(host->base, CON, con | OD);
+ else
+ OMAP_HSMMC_WRITE(host->base, CON, con & ~OD);
+out:
+ host->context_loss = context_loss;
+
+ dev_dbg(mmc_dev(host->mmc), "context is restored\n");
+ return 0;
+}
+
+/*
+ * Save the MMC host context (store the number of power state changes so far).
+ */
+static void omap_hsmmc_context_save(struct omap_hsmmc_host *host)
+{
+ struct omap_mmc_platform_data *pdata = host->pdata;
+ int context_loss;
+
+ if (pdata->get_context_loss_count) {
+ context_loss = pdata->get_context_loss_count(host->dev);
+ if (context_loss < 0)
+ return;
+ host->context_loss = context_loss;
+ }
+}
+
+#else
+
+static int omap_hsmmc_context_restore(struct omap_hsmmc_host *host)
+{
+ return 0;
+}
+
+static void omap_hsmmc_context_save(struct omap_hsmmc_host *host)
+{
+}
+
+#endif
+
+/*
+ * Send init stream sequence to card
+ * before sending IDLE command
+ */
+static void send_init_stream(struct omap_hsmmc_host *host)
+{
+ int reg = 0;
+ unsigned long timeout;
+
+ if (host->protect_card)
+ return;
+
+ disable_irq(host->irq);
+
+ OMAP_HSMMC_WRITE(host->base, IE, INT_EN_MASK);
+ OMAP_HSMMC_WRITE(host->base, CON,
+ OMAP_HSMMC_READ(host->base, CON) | INIT_STREAM);
+ OMAP_HSMMC_WRITE(host->base, CMD, INIT_STREAM_CMD);
+
+ timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
+ while ((reg != CC) && time_before(jiffies, timeout))
+ reg = OMAP_HSMMC_READ(host->base, STAT) & CC;
+
+ OMAP_HSMMC_WRITE(host->base, CON,
+ OMAP_HSMMC_READ(host->base, CON) & ~INIT_STREAM);
+
+ OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
+ OMAP_HSMMC_READ(host->base, STAT);
+
+ enable_irq(host->irq);
+}
+
+static inline
+int omap_hsmmc_cover_is_closed(struct omap_hsmmc_host *host)
+{
+ int r = 1;
+
+ if (mmc_slot(host).get_cover_state)
+ r = mmc_slot(host).get_cover_state(host->dev, host->slot_id);
+ return r;
+}
+
+static ssize_t
+omap_hsmmc_show_cover_switch(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct mmc_host *mmc = container_of(dev, struct mmc_host, class_dev);
+ struct omap_hsmmc_host *host = mmc_priv(mmc);
+
+ return sprintf(buf, "%s\n",
+ omap_hsmmc_cover_is_closed(host) ? "closed" : "open");
+}
+
+static DEVICE_ATTR(cover_switch, S_IRUGO, omap_hsmmc_show_cover_switch, NULL);
+
+static ssize_t
+omap_hsmmc_show_slot_name(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct mmc_host *mmc = container_of(dev, struct mmc_host, class_dev);
+ struct omap_hsmmc_host *host = mmc_priv(mmc);
+
+ return sprintf(buf, "%s\n", mmc_slot(host).name);
+}
+
+static DEVICE_ATTR(slot_name, S_IRUGO, omap_hsmmc_show_slot_name, NULL);
+
+/*
+ * Configure the response type and send the cmd.
+ */
+static void
+omap_hsmmc_start_command(struct omap_hsmmc_host *host, struct mmc_command *cmd,
+ struct mmc_data *data)
+{
+ int cmdreg = 0, resptype = 0, cmdtype = 0;
+
+ dev_dbg(mmc_dev(host->mmc), "%s: CMD%d, argument 0x%08x\n",
+ mmc_hostname(host->mmc), cmd->opcode, cmd->arg);
+ host->cmd = cmd;
+
+ omap_hsmmc_enable_irq(host, cmd);
+
+ host->response_busy = 0;
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ if (cmd->flags & MMC_RSP_136)
+ resptype = 1;
+ else if (cmd->flags & MMC_RSP_BUSY) {
+ resptype = 3;
+ host->response_busy = 1;
+ } else
+ resptype = 2;
+ }
+
+ /*
+ * Unlike OMAP1 controller, the cmdtype does not seem to be based on
+ * ac, bc, adtc, bcr. Only commands ending an open ended transfer need
+ * a val of 0x3, rest 0x0.
+ */
+ if (cmd == host->mrq->stop)
+ cmdtype = 0x3;
+
+ cmdreg = (cmd->opcode << 24) | (resptype << 16) | (cmdtype << 22);
+
+ if (data) {
+ cmdreg |= DP_SELECT | MSBS | BCE;
+ if (data->flags & MMC_DATA_READ)
+ cmdreg |= DDIR;
+ else
+ cmdreg &= ~(DDIR);
+ }
+
+ if (host->use_dma)
+ cmdreg |= DMA_EN;
+
+ host->req_in_progress = 1;
+
+ OMAP_HSMMC_WRITE(host->base, ARG, cmd->arg);
+ OMAP_HSMMC_WRITE(host->base, CMD, cmdreg);
+}
+
+static int
+omap_hsmmc_get_dma_dir(struct omap_hsmmc_host *host, struct mmc_data *data)
+{
+ if (data->flags & MMC_DATA_WRITE)
+ return DMA_TO_DEVICE;
+ else
+ return DMA_FROM_DEVICE;
+}
+
+static void omap_hsmmc_request_done(struct omap_hsmmc_host *host, struct mmc_request *mrq)
+{
+ int dma_ch;
+
+ spin_lock(&host->irq_lock);
+ host->req_in_progress = 0;
+ dma_ch = host->dma_ch;
+ spin_unlock(&host->irq_lock);
+
+ omap_hsmmc_disable_irq(host);
+ /* Do not complete the request if DMA is still in progress */
+ if (mrq->data && host->use_dma && dma_ch != -1)
+ return;
+ host->mrq = NULL;
+ mmc_request_done(host->mmc, mrq);
+}
+
+/*
+ * Notify the transfer complete to MMC core
+ */
+static void
+omap_hsmmc_xfer_done(struct omap_hsmmc_host *host, struct mmc_data *data)
+{
+ if (!data) {
+ struct mmc_request *mrq = host->mrq;
+
+ /* TC before CC from CMD6 - don't know why, but it happens */
+ if (host->cmd && host->cmd->opcode == 6 &&
+ host->response_busy) {
+ host->response_busy = 0;
+ return;
+ }
+
+ omap_hsmmc_request_done(host, mrq);
+ return;
+ }
+
+ host->data = NULL;
+
+ if (!data->error)
+ data->bytes_xfered += data->blocks * (data->blksz);
+ else
+ data->bytes_xfered = 0;
+
+ if (!data->stop) {
+ omap_hsmmc_request_done(host, data->mrq);
+ return;
+ }
+ omap_hsmmc_start_command(host, data->stop, NULL);
+}
+
+/*
+ * Notify the core about command completion
+ */
+static void
+omap_hsmmc_cmd_done(struct omap_hsmmc_host *host, struct mmc_command *cmd)
+{
+ host->cmd = NULL;
+
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ if (cmd->flags & MMC_RSP_136) {
+ /* response type 2 */
+ cmd->resp[3] = OMAP_HSMMC_READ(host->base, RSP10);
+ cmd->resp[2] = OMAP_HSMMC_READ(host->base, RSP32);
+ cmd->resp[1] = OMAP_HSMMC_READ(host->base, RSP54);
+ cmd->resp[0] = OMAP_HSMMC_READ(host->base, RSP76);
+ } else {
+ /* response types 1, 1b, 3, 4, 5, 6 */
+ cmd->resp[0] = OMAP_HSMMC_READ(host->base, RSP10);
+ }
+ }
+ if ((host->data == NULL && !host->response_busy) || cmd->error)
+ omap_hsmmc_request_done(host, cmd->mrq);
+}
+
+/*
+ * DMA clean up for command errors
+ */
+static void omap_hsmmc_dma_cleanup(struct omap_hsmmc_host *host, int errno)
+{
+ int dma_ch;
+
+ host->data->error = errno;
+
+ spin_lock(&host->irq_lock);
+ dma_ch = host->dma_ch;
+ host->dma_ch = -1;
+ spin_unlock(&host->irq_lock);
+
+ if (host->use_dma && dma_ch != -1) {
+ dma_unmap_sg(mmc_dev(host->mmc), host->data->sg,
+ host->data->sg_len,
+ omap_hsmmc_get_dma_dir(host, host->data));
+ omap_free_dma(dma_ch);
+ }
+ host->data = NULL;
+}
+
+/*
+ * Readable error output
+ */
+#ifdef CONFIG_MMC_DEBUG
+static void omap_hsmmc_report_irq(struct omap_hsmmc_host *host, u32 status)
+{
+ /* --- means reserved bit without definition at documentation */
+ static const char *omap_hsmmc_status_bits[] = {
+ "CC", "TC", "BGE", "---", "BWR", "BRR", "---", "---", "CIRQ",
+ "OBI", "---", "---", "---", "---", "---", "ERRI", "CTO", "CCRC",
+ "CEB", "CIE", "DTO", "DCRC", "DEB", "---", "ACE", "---",
+ "---", "---", "---", "CERR", "CERR", "BADA", "---", "---", "---"
+ };
+ char res[256];
+ char *buf = res;
+ int len, i;
+
+ len = sprintf(buf, "MMC IRQ 0x%x :", status);
+ buf += len;
+
+ for (i = 0; i < ARRAY_SIZE(omap_hsmmc_status_bits); i++)
+ if (status & (1 << i)) {
+ len = sprintf(buf, " %s", omap_hsmmc_status_bits[i]);
+ buf += len;
+ }
+
+ dev_dbg(mmc_dev(host->mmc), "%s\n", res);
+}
+#endif /* CONFIG_MMC_DEBUG */
+
+/*
+ * MMC controller internal state machines reset
+ *
+ * Used to reset command or data internal state machines, using respectively
+ * SRC or SRD bit of SYSCTL register
+ * Can be called from interrupt context
+ */
+static inline void omap_hsmmc_reset_controller_fsm(struct omap_hsmmc_host *host,
+ unsigned long bit)
+{
+ unsigned long i = 0;
+ unsigned long limit = (loops_per_jiffy *
+ msecs_to_jiffies(MMC_TIMEOUT_MS));
+
+ OMAP_HSMMC_WRITE(host->base, SYSCTL,
+ OMAP_HSMMC_READ(host->base, SYSCTL) | bit);
+
+ /*
+ * OMAP4 ES2 and greater has an updated reset logic.
+ * Monitor a 0->1 transition first
+ */
+ if (mmc_slot(host).features & HSMMC_HAS_UPDATED_RESET) {
+ while ((!(OMAP_HSMMC_READ(host->base, SYSCTL) & bit))
+ && (i++ < limit))
+ cpu_relax();
+ }
+ i = 0;
+
+ while ((OMAP_HSMMC_READ(host->base, SYSCTL) & bit) &&
+ (i++ < limit))
+ cpu_relax();
+
+ if (OMAP_HSMMC_READ(host->base, SYSCTL) & bit)
+ dev_err(mmc_dev(host->mmc),
+ "Timeout waiting on controller reset in %s\n",
+ __func__);
+}
+
+static void omap_hsmmc_do_irq(struct omap_hsmmc_host *host, int status)
+{
+ struct mmc_data *data;
+ int end_cmd = 0, end_trans = 0;
+
+ if (!host->req_in_progress) {
+ do {
+ OMAP_HSMMC_WRITE(host->base, STAT, status);
+ /* Flush posted write */
+ status = OMAP_HSMMC_READ(host->base, STAT);
+ } while (status & INT_EN_MASK);
+ return;
+ }
+
+ data = host->data;
+ dev_dbg(mmc_dev(host->mmc), "IRQ Status is %x\n", status);
+
+ if (status & ERR) {
+#ifdef CONFIG_MMC_DEBUG
+ omap_hsmmc_report_irq(host, status);
+#endif
+ if ((status & CMD_TIMEOUT) ||
+ (status & CMD_CRC)) {
+ if (host->cmd) {
+ if (status & CMD_TIMEOUT) {
+ omap_hsmmc_reset_controller_fsm(host,
+ SRC);
+ host->cmd->error = -ETIMEDOUT;
+ } else {
+ host->cmd->error = -EILSEQ;
+ }
+ end_cmd = 1;
+ }
+ if (host->data || host->response_busy) {
+ if (host->data)
+ omap_hsmmc_dma_cleanup(host,
+ -ETIMEDOUT);
+ host->response_busy = 0;
+ omap_hsmmc_reset_controller_fsm(host, SRD);
+ }
+ }
+ if ((status & DATA_TIMEOUT) ||
+ (status & DATA_CRC)) {
+ if (host->data || host->response_busy) {
+ int err = (status & DATA_TIMEOUT) ?
+ -ETIMEDOUT : -EILSEQ;
+
+ if (host->data)
+ omap_hsmmc_dma_cleanup(host, err);
+ else
+ host->mrq->cmd->error = err;
+ host->response_busy = 0;
+ omap_hsmmc_reset_controller_fsm(host, SRD);
+ end_trans = 1;
+ }
+ }
+ if (status & CARD_ERR) {
+ dev_dbg(mmc_dev(host->mmc),
+ "Ignoring card err CMD%d\n", host->cmd->opcode);
+ if (host->cmd)
+ end_cmd = 1;
+ if (host->data)
+ end_trans = 1;
+ }
+ }
+
+ OMAP_HSMMC_WRITE(host->base, STAT, status);
+
+ if (end_cmd || ((status & CC) && host->cmd))
+ omap_hsmmc_cmd_done(host, host->cmd);
+ if ((end_trans || (status & TC)) && host->mrq)
+ omap_hsmmc_xfer_done(host, data);
+}
+
+/*
+ * MMC controller IRQ handler
+ */
+static irqreturn_t omap_hsmmc_irq(int irq, void *dev_id)
+{
+ struct omap_hsmmc_host *host = dev_id;
+ int status;
+
+ status = OMAP_HSMMC_READ(host->base, STAT);
+ do {
+ omap_hsmmc_do_irq(host, status);
+ /* Flush posted write */
+ status = OMAP_HSMMC_READ(host->base, STAT);
+ } while (status & INT_EN_MASK);
+
+ return IRQ_HANDLED;
+}
+
+static void set_sd_bus_power(struct omap_hsmmc_host *host)
+{
+ unsigned long i;
+
+ OMAP_HSMMC_WRITE(host->base, HCTL,
+ OMAP_HSMMC_READ(host->base, HCTL) | SDBP);
+ for (i = 0; i < loops_per_jiffy; i++) {
+ if (OMAP_HSMMC_READ(host->base, HCTL) & SDBP)
+ break;
+ cpu_relax();
+ }
+}
+
+/*
+ * Switch MMC interface voltage ... only relevant for MMC1.
+ *
+ * MMC2 and MMC3 use fixed 1.8V levels, and maybe a transceiver.
+ * The MMC2 transceiver controls are used instead of DAT4..DAT7.
+ * Some chips, like eMMC ones, use internal transceivers.
+ */
+static int omap_hsmmc_switch_opcond(struct omap_hsmmc_host *host, int vdd)
+{
+ u32 reg_val = 0;
+ int ret;
+
+ /* Disable the clocks */
+ clk_disable(host->fclk);
+ clk_disable(host->iclk);
+ if (host->got_dbclk)
+ clk_disable(host->dbclk);
+
+ /* Turn the power off */
+ ret = mmc_slot(host).set_power(host->dev, host->slot_id, 0, 0);
+
+ /* Turn the power ON with given VDD 1.8 or 3.0v */
+ if (!ret)
+ ret = mmc_slot(host).set_power(host->dev, host->slot_id, 1,
+ vdd);
+ clk_enable(host->iclk);
+ clk_enable(host->fclk);
+ if (host->got_dbclk)
+ clk_enable(host->dbclk);
+
+ if (ret != 0)
+ goto err;
+
+ OMAP_HSMMC_WRITE(host->base, HCTL,
+ OMAP_HSMMC_READ(host->base, HCTL) & SDVSCLR);
+ reg_val = OMAP_HSMMC_READ(host->base, HCTL);
+
+ /*
+ * If a MMC dual voltage card is detected, the set_ios fn calls
+ * this fn with VDD bit set for 1.8V. Upon card removal from the
+ * slot, omap_hsmmc_set_ios sets the VDD back to 3V on MMC_POWER_OFF.
+ *
+ * Cope with a bit of slop in the range ... per data sheets:
+ * - "1.8V" for vdds_mmc1/vdds_mmc1a can be up to 2.45V max,
+ * but recommended values are 1.71V to 1.89V
+ * - "3.0V" for vdds_mmc1/vdds_mmc1a can be up to 3.5V max,
+ * but recommended values are 2.7V to 3.3V
+ *
+ * Board setup code shouldn't permit anything very out-of-range.
+ * TWL4030-family VMMC1 and VSIM regulators are fine (avoiding the
+ * middle range) but VSIM can't power DAT4..DAT7 at more than 3V.
+ */
+ if ((1 << vdd) <= MMC_VDD_23_24)
+ reg_val |= SDVS18;
+ else
+ reg_val |= SDVS30;
+
+ OMAP_HSMMC_WRITE(host->base, HCTL, reg_val);
+ set_sd_bus_power(host);
+
+ return 0;
+err:
+ dev_dbg(mmc_dev(host->mmc), "Unable to switch operating voltage\n");
+ return ret;
+}
+
+/* Protect the card while the cover is open */
+static void omap_hsmmc_protect_card(struct omap_hsmmc_host *host)
+{
+ if (!mmc_slot(host).get_cover_state)
+ return;
+
+ host->reqs_blocked = 0;
+ if (mmc_slot(host).get_cover_state(host->dev, host->slot_id)) {
+ if (host->protect_card) {
+ printk(KERN_INFO "%s: cover is closed, "
+ "card is now accessible\n",
+ mmc_hostname(host->mmc));
+ host->protect_card = 0;
+ }
+ } else {
+ if (!host->protect_card) {
+ printk(KERN_INFO "%s: cover is open, "
+ "card is now inaccessible\n",
+ mmc_hostname(host->mmc));
+ host->protect_card = 1;
+ }
+ }
+}
+
+/*
+ * Work Item to notify the core about card insertion/removal
+ */
+static void omap_hsmmc_detect(struct work_struct *work)
+{
+ struct omap_hsmmc_host *host =
+ container_of(work, struct omap_hsmmc_host, mmc_carddetect_work);
+ struct omap_mmc_slot_data *slot = &mmc_slot(host);
+ int carddetect;
+
+ if (host->suspended)
+ return;
+
+ sysfs_notify(&host->mmc->class_dev.kobj, NULL, "cover_switch");
+
+ if (slot->card_detect)
+ carddetect = slot->card_detect(host->dev, host->slot_id);
+ else {
+ omap_hsmmc_protect_card(host);
+ carddetect = -ENOSYS;
+ }
+
+ if (carddetect)
+ mmc_detect_change(host->mmc, (HZ * 200) / 1000);
+ else
+ mmc_detect_change(host->mmc, (HZ * 50) / 1000);
+}
+
+/*
+ * ISR for handling card insertion and removal
+ */
+static irqreturn_t omap_hsmmc_cd_handler(int irq, void *dev_id)
+{
+ struct omap_hsmmc_host *host = (struct omap_hsmmc_host *)dev_id;
+
+ if (host->suspended)
+ return IRQ_HANDLED;
+ schedule_work(&host->mmc_carddetect_work);
+
+ return IRQ_HANDLED;
+}
+
+static int omap_hsmmc_get_dma_sync_dev(struct omap_hsmmc_host *host,
+ struct mmc_data *data)
+{
+ int sync_dev;
+
+ if (data->flags & MMC_DATA_WRITE)
+ sync_dev = host->dma_line_tx;
+ else
+ sync_dev = host->dma_line_rx;
+ return sync_dev;
+}
+
+static void omap_hsmmc_config_dma_params(struct omap_hsmmc_host *host,
+ struct mmc_data *data,
+ struct scatterlist *sgl)
+{
+ int blksz, nblk, dma_ch;
+
+ dma_ch = host->dma_ch;
+ if (data->flags & MMC_DATA_WRITE) {
+ omap_set_dma_dest_params(dma_ch, 0, OMAP_DMA_AMODE_CONSTANT,
+ (host->mapbase + OMAP_HSMMC_DATA), 0, 0);
+ omap_set_dma_src_params(dma_ch, 0, OMAP_DMA_AMODE_POST_INC,
+ sg_dma_address(sgl), 0, 0);
+ } else {
+ omap_set_dma_src_params(dma_ch, 0, OMAP_DMA_AMODE_CONSTANT,
+ (host->mapbase + OMAP_HSMMC_DATA), 0, 0);
+ omap_set_dma_dest_params(dma_ch, 0, OMAP_DMA_AMODE_POST_INC,
+ sg_dma_address(sgl), 0, 0);
+ }
+
+ blksz = host->data->blksz;
+ nblk = sg_dma_len(sgl) / blksz;
+
+ omap_set_dma_transfer_params(dma_ch, OMAP_DMA_DATA_TYPE_S32,
+ blksz / 4, nblk, OMAP_DMA_SYNC_FRAME,
+ omap_hsmmc_get_dma_sync_dev(host, data),
+ !(data->flags & MMC_DATA_WRITE));
+
+ omap_start_dma(dma_ch);
+}
+
+/*
+ * DMA call back function
+ */
+static void omap_hsmmc_dma_cb(int lch, u16 ch_status, void *cb_data)
+{
+ struct omap_hsmmc_host *host = cb_data;
+ struct mmc_data *data = host->mrq->data;
+ int dma_ch, req_in_progress;
+
+ if (!(ch_status & OMAP_DMA_BLOCK_IRQ)) {
+ dev_warn(mmc_dev(host->mmc), "unexpected dma status %x\n",
+ ch_status);
+ return;
+ }
+
+ spin_lock(&host->irq_lock);
+ if (host->dma_ch < 0) {
+ spin_unlock(&host->irq_lock);
+ return;
+ }
+
+ host->dma_sg_idx++;
+ if (host->dma_sg_idx < host->dma_len) {
+ /* Fire up the next transfer. */
+ omap_hsmmc_config_dma_params(host, data,
+ data->sg + host->dma_sg_idx);
+ spin_unlock(&host->irq_lock);
+ return;
+ }
+
+ if (!data->host_cookie)
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+ omap_hsmmc_get_dma_dir(host, data));
+
+ req_in_progress = host->req_in_progress;
+ dma_ch = host->dma_ch;
+ host->dma_ch = -1;
+ spin_unlock(&host->irq_lock);
+
+ omap_free_dma(dma_ch);
+
+ /* If DMA has finished after TC, complete the request */
+ if (!req_in_progress) {
+ struct mmc_request *mrq = host->mrq;
+
+ host->mrq = NULL;
+ mmc_request_done(host->mmc, mrq);
+ }
+}
+
+static int omap_hsmmc_pre_dma_transfer(struct omap_hsmmc_host *host,
+ struct mmc_data *data,
+ struct omap_hsmmc_next *next)
+{
+ int dma_len;
+
+ if (!next && data->host_cookie &&
+ data->host_cookie != host->next_data.cookie) {
+ printk(KERN_WARNING "[%s] invalid cookie: data->host_cookie %d"
+ " host->next_data.cookie %d\n",
+ __func__, data->host_cookie, host->next_data.cookie);
+ data->host_cookie = 0;
+ }
+
+ /* Check if next job is already prepared */
+ if (next ||
+ (!next && data->host_cookie != host->next_data.cookie)) {
+ dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg,
+ data->sg_len,
+ omap_hsmmc_get_dma_dir(host, data));
+
+ } else {
+ dma_len = host->next_data.dma_len;
+ host->next_data.dma_len = 0;
+ }
+
+
+ if (dma_len == 0)
+ return -EINVAL;
+
+ if (next) {
+ next->dma_len = dma_len;
+ data->host_cookie = ++next->cookie < 0 ? 1 : next->cookie;
+ } else
+ host->dma_len = dma_len;
+
+ return 0;
+}
+
+/*
+ * Routine to configure and start DMA for the MMC card
+ */
+static int omap_hsmmc_start_dma_transfer(struct omap_hsmmc_host *host,
+ struct mmc_request *req)
+{
+ int dma_ch = 0, ret = 0, i;
+ struct mmc_data *data = req->data;
+
+ /* Sanity check: all the SG entries must be aligned by block size. */
+ for (i = 0; i < data->sg_len; i++) {
+ struct scatterlist *sgl;
+
+ sgl = data->sg + i;
+ if (sgl->length % data->blksz)
+ return -EINVAL;
+ }
+ if ((data->blksz % 4) != 0)
+ /* REVISIT: The MMC buffer increments only when MSB is written.
+ * Return error for blksz which is non multiple of four.
+ */
+ return -EINVAL;
+
+ BUG_ON(host->dma_ch != -1);
+
+ ret = omap_request_dma(omap_hsmmc_get_dma_sync_dev(host, data),
+ "MMC/SD", omap_hsmmc_dma_cb, host, &dma_ch);
+ if (ret != 0) {
+ dev_err(mmc_dev(host->mmc),
+ "%s: omap_request_dma() failed with %d\n",
+ mmc_hostname(host->mmc), ret);
+ return ret;
+ }
+ ret = omap_hsmmc_pre_dma_transfer(host, data, NULL);
+ if (ret)
+ return ret;
+
+ host->dma_ch = dma_ch;
+ host->dma_sg_idx = 0;
+
+ omap_hsmmc_config_dma_params(host, data, data->sg);
+
+ return 0;
+}
+
+static void set_data_timeout(struct omap_hsmmc_host *host,
+ unsigned int timeout_ns,
+ unsigned int timeout_clks)
+{
+ unsigned int timeout, cycle_ns;
+ uint32_t reg, clkd, dto = 0;
+
+ reg = OMAP_HSMMC_READ(host->base, SYSCTL);
+ clkd = (reg & CLKD_MASK) >> CLKD_SHIFT;
+ if (clkd == 0)
+ clkd = 1;
+
+ cycle_ns = 1000000000 / (clk_get_rate(host->fclk) / clkd);
+ timeout = timeout_ns / cycle_ns;
+ timeout += timeout_clks;
+ if (timeout) {
+ while ((timeout & 0x80000000) == 0) {
+ dto += 1;
+ timeout <<= 1;
+ }
+ dto = 31 - dto;
+ timeout <<= 1;
+ if (timeout && dto)
+ dto += 1;
+ if (dto >= 13)
+ dto -= 13;
+ else
+ dto = 0;
+ if (dto > 14)
+ dto = 14;
+ }
+
+ reg &= ~DTO_MASK;
+ reg |= dto << DTO_SHIFT;
+ OMAP_HSMMC_WRITE(host->base, SYSCTL, reg);
+}
+
+/*
+ * Configure block length for MMC/SD cards and initiate the transfer.
+ */
+static int
+omap_hsmmc_prepare_data(struct omap_hsmmc_host *host, struct mmc_request *req)
+{
+ int ret;
+ host->data = req->data;
+
+ if (req->data == NULL) {
+ OMAP_HSMMC_WRITE(host->base, BLK, 0);
+ /*
+ * Set an arbitrary 100ms data timeout for commands with
+ * busy signal.
+ */
+ if (req->cmd->flags & MMC_RSP_BUSY)
+ set_data_timeout(host, 100000000U, 0);
+ return 0;
+ }
+
+ OMAP_HSMMC_WRITE(host->base, BLK, (req->data->blksz)
+ | (req->data->blocks << 16));
+ set_data_timeout(host, req->data->timeout_ns, req->data->timeout_clks);
+
+ if (host->use_dma) {
+ ret = omap_hsmmc_start_dma_transfer(host, req);
+ if (ret != 0) {
+ dev_dbg(mmc_dev(host->mmc), "MMC start dma failure\n");
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static void omap_hsmmc_post_req(struct mmc_host *mmc, struct mmc_request *mrq,
+ int err)
+{
+ struct omap_hsmmc_host *host = mmc_priv(mmc);
+ struct mmc_data *data = mrq->data;
+
+ if (host->use_dma) {
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+ omap_hsmmc_get_dma_dir(host, data));
+ data->host_cookie = 0;
+ }
+}
+
+static void omap_hsmmc_pre_req(struct mmc_host *mmc, struct mmc_request *mrq,
+ bool is_first_req)
+{
+ struct omap_hsmmc_host *host = mmc_priv(mmc);
+
+ if (mrq->data->host_cookie) {
+ mrq->data->host_cookie = 0;
+ return ;
+ }
+
+ if (host->use_dma)
+ if (omap_hsmmc_pre_dma_transfer(host, mrq->data,
+ &host->next_data))
+ mrq->data->host_cookie = 0;
+}
+
+/*
+ * Request function. for read/write operation
+ */
+static void omap_hsmmc_request(struct mmc_host *mmc, struct mmc_request *req)
+{
+ struct omap_hsmmc_host *host = mmc_priv(mmc);
+ int err;
+
+ BUG_ON(host->req_in_progress);
+ BUG_ON(host->dma_ch != -1);
+ if (host->protect_card) {
+ if (host->reqs_blocked < 3) {
+ /*
+ * Ensure the controller is left in a consistent
+ * state by resetting the command and data state
+ * machines.
+ */
+ omap_hsmmc_reset_controller_fsm(host, SRD);
+ omap_hsmmc_reset_controller_fsm(host, SRC);
+ host->reqs_blocked += 1;
+ }
+ req->cmd->error = -EBADF;
+ if (req->data)
+ req->data->error = -EBADF;
+ req->cmd->retries = 0;
+ mmc_request_done(mmc, req);
+ return;
+ } else if (host->reqs_blocked)
+ host->reqs_blocked = 0;
+ WARN_ON(host->mrq != NULL);
+ host->mrq = req;
+ err = omap_hsmmc_prepare_data(host, req);
+ if (err) {
+ req->cmd->error = err;
+ if (req->data)
+ req->data->error = err;
+ host->mrq = NULL;
+ mmc_request_done(mmc, req);
+ return;
+ }
+
+ omap_hsmmc_start_command(host, req->cmd, req->data);
+}
+
+/* Routine to configure clock values. Exposed API to core */
+static void omap_hsmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct omap_hsmmc_host *host = mmc_priv(mmc);
+ u16 dsor = 0;
+ unsigned long regval;
+ unsigned long timeout;
+ u32 con;
+ int do_send_init_stream = 0;
+
+ mmc_host_enable(host->mmc);
+
+ if (ios->power_mode != host->power_mode) {
+ switch (ios->power_mode) {
+ case MMC_POWER_OFF:
+ mmc_slot(host).set_power(host->dev, host->slot_id,
+ 0, 0);
+ host->vdd = 0;
+ break;
+ case MMC_POWER_UP:
+ mmc_slot(host).set_power(host->dev, host->slot_id,
+ 1, ios->vdd);
+ host->vdd = ios->vdd;
+ break;
+ case MMC_POWER_ON:
+ do_send_init_stream = 1;
+ break;
+ }
+ host->power_mode = ios->power_mode;
+ }
+
+ /* FIXME: set registers based only on changes to ios */
+
+ con = OMAP_HSMMC_READ(host->base, CON);
+ switch (mmc->ios.bus_width) {
+ case MMC_BUS_WIDTH_8:
+ OMAP_HSMMC_WRITE(host->base, CON, con | DW8);
+ break;
+ case MMC_BUS_WIDTH_4:
+ OMAP_HSMMC_WRITE(host->base, CON, con & ~DW8);
+ OMAP_HSMMC_WRITE(host->base, HCTL,
+ OMAP_HSMMC_READ(host->base, HCTL) | FOUR_BIT);
+ break;
+ case MMC_BUS_WIDTH_1:
+ OMAP_HSMMC_WRITE(host->base, CON, con & ~DW8);
+ OMAP_HSMMC_WRITE(host->base, HCTL,
+ OMAP_HSMMC_READ(host->base, HCTL) & ~FOUR_BIT);
+ break;
+ }
+
+ if (host->pdata->controller_flags & OMAP_HSMMC_SUPPORTS_DUAL_VOLT) {
+ /* Only MMC1 can interface at 3V without some flavor
+ * of external transceiver; but they all handle 1.8V.
+ */
+ if ((OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET) &&
+ (ios->vdd == DUAL_VOLT_OCR_BIT)) {
+ /*
+ * The mmc_select_voltage fn of the core does
+ * not seem to set the power_mode to
+ * MMC_POWER_UP upon recalculating the voltage.
+ * vdd 1.8v.
+ */
+ if (omap_hsmmc_switch_opcond(host, ios->vdd) != 0)
+ dev_dbg(mmc_dev(host->mmc),
+ "Switch operation failed\n");
+ }
+ }
+
+ if (ios->clock) {
+ dsor = OMAP_MMC_MASTER_CLOCK / ios->clock;
+ if (dsor < 1)
+ dsor = 1;
+
+ if (OMAP_MMC_MASTER_CLOCK / dsor > ios->clock)
+ dsor++;
+
+ if (dsor > 250)
+ dsor = 250;
+ }
+ omap_hsmmc_stop_clock(host);
+ regval = OMAP_HSMMC_READ(host->base, SYSCTL);
+ regval = regval & ~(CLKD_MASK);
+ regval = regval | (dsor << 6) | (DTO << 16);
+ OMAP_HSMMC_WRITE(host->base, SYSCTL, regval);
+ OMAP_HSMMC_WRITE(host->base, SYSCTL,
+ OMAP_HSMMC_READ(host->base, SYSCTL) | ICE);
+
+ /* Wait till the ICS bit is set */
+ timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
+ while ((OMAP_HSMMC_READ(host->base, SYSCTL) & ICS) != ICS
+ && time_before(jiffies, timeout))
+ msleep(1);
+
+ OMAP_HSMMC_WRITE(host->base, SYSCTL,
+ OMAP_HSMMC_READ(host->base, SYSCTL) | CEN);
+
+ if (do_send_init_stream)
+ send_init_stream(host);
+
+ con = OMAP_HSMMC_READ(host->base, CON);
+ if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN)
+ OMAP_HSMMC_WRITE(host->base, CON, con | OD);
+ else
+ OMAP_HSMMC_WRITE(host->base, CON, con & ~OD);
+
+ if (host->power_mode == MMC_POWER_OFF)
+ mmc_host_disable(host->mmc);
+ else
+ mmc_host_lazy_disable(host->mmc);
+}
+
+static int omap_hsmmc_get_cd(struct mmc_host *mmc)
+{
+ struct omap_hsmmc_host *host = mmc_priv(mmc);
+
+ if (!mmc_slot(host).card_detect)
+ return -ENOSYS;
+ return mmc_slot(host).card_detect(host->dev, host->slot_id);
+}
+
+static int omap_hsmmc_get_ro(struct mmc_host *mmc)
+{
+ struct omap_hsmmc_host *host = mmc_priv(mmc);
+
+ if (!mmc_slot(host).get_ro)
+ return -ENOSYS;
+ return mmc_slot(host).get_ro(host->dev, 0);
+}
+
+static void omap_hsmmc_init_card(struct mmc_host *mmc, struct mmc_card *card)
+{
+ struct omap_hsmmc_host *host = mmc_priv(mmc);
+
+ if (mmc_slot(host).init_card)
+ mmc_slot(host).init_card(card);
+}
+
+static void omap_hsmmc_conf_bus_power(struct omap_hsmmc_host *host)
+{
+ u32 hctl, capa, value;
+
+ /* Only MMC1 supports 3.0V */
+ if (host->pdata->controller_flags & OMAP_HSMMC_SUPPORTS_DUAL_VOLT) {
+ hctl = SDVS30;
+ capa = VS30 | VS18;
+ } else {
+ hctl = SDVS18;
+ capa = VS18;
+ }
+
+ value = OMAP_HSMMC_READ(host->base, HCTL) & ~SDVS_MASK;
+ OMAP_HSMMC_WRITE(host->base, HCTL, value | hctl);
+
+ value = OMAP_HSMMC_READ(host->base, CAPA);
+ OMAP_HSMMC_WRITE(host->base, CAPA, value | capa);
+
+ /* Set the controller to AUTO IDLE mode */
+ value = OMAP_HSMMC_READ(host->base, SYSCONFIG);
+ OMAP_HSMMC_WRITE(host->base, SYSCONFIG, value | AUTOIDLE);
+
+ /* Set SD bus power bit */
+ set_sd_bus_power(host);
+}
+
+/*
+ * Dynamic power saving handling, FSM:
+ * ENABLED -> DISABLED -> CARDSLEEP / REGSLEEP -> OFF
+ * ^___________| | |
+ * |______________________|______________________|
+ *
+ * ENABLED: mmc host is fully functional
+ * DISABLED: fclk is off
+ * CARDSLEEP: fclk is off, card is asleep, voltage regulator is asleep
+ * REGSLEEP: fclk is off, voltage regulator is asleep
+ * OFF: fclk is off, voltage regulator is off
+ *
+ * Transition handlers return the timeout for the next state transition
+ * or negative error.
+ */
+
+enum {ENABLED = 0, DISABLED, CARDSLEEP, REGSLEEP, OFF};
+
+/* Handler for [ENABLED -> DISABLED] transition */
+static int omap_hsmmc_enabled_to_disabled(struct omap_hsmmc_host *host)
+{
+ omap_hsmmc_context_save(host);
+ clk_disable(host->fclk);
+ host->dpm_state = DISABLED;
+
+ dev_dbg(mmc_dev(host->mmc), "ENABLED -> DISABLED\n");
+
+ if (host->power_mode == MMC_POWER_OFF)
+ return 0;
+
+ return OMAP_MMC_SLEEP_TIMEOUT;
+}
+
+/* Handler for [DISABLED -> REGSLEEP / CARDSLEEP] transition */
+static int omap_hsmmc_disabled_to_sleep(struct omap_hsmmc_host *host)
+{
+ int err, new_state;
+
+ if (!mmc_try_claim_host(host->mmc))
+ return 0;
+
+ clk_enable(host->fclk);
+ omap_hsmmc_context_restore(host);
+ if (mmc_card_can_sleep(host->mmc)) {
+ err = mmc_card_sleep(host->mmc);
+ if (err < 0) {
+ clk_disable(host->fclk);
+ mmc_release_host(host->mmc);
+ return err;
+ }
+ new_state = CARDSLEEP;
+ } else {
+ new_state = REGSLEEP;
+ }
+ if (mmc_slot(host).set_sleep)
+ mmc_slot(host).set_sleep(host->dev, host->slot_id, 1, 0,
+ new_state == CARDSLEEP);
+ /* FIXME: turn off bus power and perhaps interrupts too */
+ clk_disable(host->fclk);
+ host->dpm_state = new_state;
+
+ mmc_release_host(host->mmc);
+
+ dev_dbg(mmc_dev(host->mmc), "DISABLED -> %s\n",
+ host->dpm_state == CARDSLEEP ? "CARDSLEEP" : "REGSLEEP");
+
+ if (mmc_slot(host).no_off)
+ return 0;
+
+ if ((host->mmc->caps & MMC_CAP_NONREMOVABLE) ||
+ mmc_slot(host).card_detect ||
+ (mmc_slot(host).get_cover_state &&
+ mmc_slot(host).get_cover_state(host->dev, host->slot_id)))
+ return OMAP_MMC_OFF_TIMEOUT;
+
+ return 0;
+}
+
+/* Handler for [REGSLEEP / CARDSLEEP -> OFF] transition */
+static int omap_hsmmc_sleep_to_off(struct omap_hsmmc_host *host)
+{
+ if (!mmc_try_claim_host(host->mmc))
+ return 0;
+
+ if (mmc_slot(host).no_off)
+ return 0;
+
+ if (!((host->mmc->caps & MMC_CAP_NONREMOVABLE) ||
+ mmc_slot(host).card_detect ||
+ (mmc_slot(host).get_cover_state &&
+ mmc_slot(host).get_cover_state(host->dev, host->slot_id)))) {
+ mmc_release_host(host->mmc);
+ return 0;
+ }
+
+ mmc_slot(host).set_power(host->dev, host->slot_id, 0, 0);
+ host->vdd = 0;
+ host->power_mode = MMC_POWER_OFF;
+
+ dev_dbg(mmc_dev(host->mmc), "%s -> OFF\n",
+ host->dpm_state == CARDSLEEP ? "CARDSLEEP" : "REGSLEEP");
+
+ host->dpm_state = OFF;
+
+ mmc_release_host(host->mmc);
+
+ return 0;
+}
+
+/* Handler for [DISABLED -> ENABLED] transition */
+static int omap_hsmmc_disabled_to_enabled(struct omap_hsmmc_host *host)
+{
+ int err;
+
+ err = clk_enable(host->fclk);
+ if (err < 0)
+ return err;
+
+ omap_hsmmc_context_restore(host);
+ host->dpm_state = ENABLED;
+
+ dev_dbg(mmc_dev(host->mmc), "DISABLED -> ENABLED\n");
+
+ return 0;
+}
+
+/* Handler for [SLEEP -> ENABLED] transition */
+static int omap_hsmmc_sleep_to_enabled(struct omap_hsmmc_host *host)
+{
+ if (!mmc_try_claim_host(host->mmc))
+ return 0;
+
+ clk_enable(host->fclk);
+ omap_hsmmc_context_restore(host);
+ if (mmc_slot(host).set_sleep)
+ mmc_slot(host).set_sleep(host->dev, host->slot_id, 0,
+ host->vdd, host->dpm_state == CARDSLEEP);
+ if (mmc_card_can_sleep(host->mmc))
+ mmc_card_awake(host->mmc);
+
+ dev_dbg(mmc_dev(host->mmc), "%s -> ENABLED\n",
+ host->dpm_state == CARDSLEEP ? "CARDSLEEP" : "REGSLEEP");
+
+ host->dpm_state = ENABLED;
+
+ mmc_release_host(host->mmc);
+
+ return 0;
+}
+
+/* Handler for [OFF -> ENABLED] transition */
+static int omap_hsmmc_off_to_enabled(struct omap_hsmmc_host *host)
+{
+ clk_enable(host->fclk);
+
+ omap_hsmmc_context_restore(host);
+ omap_hsmmc_conf_bus_power(host);
+ mmc_power_restore_host(host->mmc);
+
+ host->dpm_state = ENABLED;
+
+ dev_dbg(mmc_dev(host->mmc), "OFF -> ENABLED\n");
+
+ return 0;
+}
+
+/*
+ * Bring MMC host to ENABLED from any other PM state.
+ */
+static int omap_hsmmc_enable(struct mmc_host *mmc)
+{
+ struct omap_hsmmc_host *host = mmc_priv(mmc);
+
+ switch (host->dpm_state) {
+ case DISABLED:
+ return omap_hsmmc_disabled_to_enabled(host);
+ case CARDSLEEP:
+ case REGSLEEP:
+ return omap_hsmmc_sleep_to_enabled(host);
+ case OFF:
+ return omap_hsmmc_off_to_enabled(host);
+ default:
+ dev_dbg(mmc_dev(host->mmc), "UNKNOWN state\n");
+ return -EINVAL;
+ }
+}
+
+/*
+ * Bring MMC host in PM state (one level deeper).
+ */
+static int omap_hsmmc_disable(struct mmc_host *mmc, int lazy)
+{
+ struct omap_hsmmc_host *host = mmc_priv(mmc);
+
+ switch (host->dpm_state) {
+ case ENABLED: {
+ int delay;
+
+ delay = omap_hsmmc_enabled_to_disabled(host);
+ if (lazy || delay < 0)
+ return delay;
+ return 0;
+ }
+ case DISABLED:
+ return omap_hsmmc_disabled_to_sleep(host);
+ case CARDSLEEP:
+ case REGSLEEP:
+ return omap_hsmmc_sleep_to_off(host);
+ default:
+ dev_dbg(mmc_dev(host->mmc), "UNKNOWN state\n");
+ return -EINVAL;
+ }
+}
+
+static int omap_hsmmc_enable_fclk(struct mmc_host *mmc)
+{
+ struct omap_hsmmc_host *host = mmc_priv(mmc);
+ int err;
+
+ err = clk_enable(host->fclk);
+ if (err)
+ return err;
+ dev_dbg(mmc_dev(host->mmc), "mmc_fclk: enabled\n");
+ omap_hsmmc_context_restore(host);
+ return 0;
+}
+
+static int omap_hsmmc_disable_fclk(struct mmc_host *mmc, int lazy)
+{
+ struct omap_hsmmc_host *host = mmc_priv(mmc);
+
+ omap_hsmmc_context_save(host);
+ clk_disable(host->fclk);
+ dev_dbg(mmc_dev(host->mmc), "mmc_fclk: disabled\n");
+ return 0;
+}
+
+static const struct mmc_host_ops omap_hsmmc_ops = {
+ .enable = omap_hsmmc_enable_fclk,
+ .disable = omap_hsmmc_disable_fclk,
+ .post_req = omap_hsmmc_post_req,
+ .pre_req = omap_hsmmc_pre_req,
+ .request = omap_hsmmc_request,
+ .set_ios = omap_hsmmc_set_ios,
+ .get_cd = omap_hsmmc_get_cd,
+ .get_ro = omap_hsmmc_get_ro,
+ .init_card = omap_hsmmc_init_card,
+ /* NYET -- enable_sdio_irq */
+};
+
+static const struct mmc_host_ops omap_hsmmc_ps_ops = {
+ .enable = omap_hsmmc_enable,
+ .disable = omap_hsmmc_disable,
+ .request = omap_hsmmc_request,
+ .set_ios = omap_hsmmc_set_ios,
+ .get_cd = omap_hsmmc_get_cd,
+ .get_ro = omap_hsmmc_get_ro,
+ .init_card = omap_hsmmc_init_card,
+ /* NYET -- enable_sdio_irq */
+};
+
+#ifdef CONFIG_DEBUG_FS
+
+static int omap_hsmmc_regs_show(struct seq_file *s, void *data)
+{
+ struct mmc_host *mmc = s->private;
+ struct omap_hsmmc_host *host = mmc_priv(mmc);
+ int context_loss = 0;
+
+ if (host->pdata->get_context_loss_count)
+ context_loss = host->pdata->get_context_loss_count(host->dev);
+
+ seq_printf(s, "mmc%d:\n"
+ " enabled:\t%d\n"
+ " dpm_state:\t%d\n"
+ " nesting_cnt:\t%d\n"
+ " ctx_loss:\t%d:%d\n"
+ "\nregs:\n",
+ mmc->index, mmc->enabled ? 1 : 0,
+ host->dpm_state, mmc->nesting_cnt,
+ host->context_loss, context_loss);
+
+ if (host->suspended || host->dpm_state == OFF) {
+ seq_printf(s, "host suspended, can't read registers\n");
+ return 0;
+ }
+
+ if (clk_enable(host->fclk) != 0) {
+ seq_printf(s, "can't read the regs\n");
+ return 0;
+ }
+
+ seq_printf(s, "SYSCONFIG:\t0x%08x\n",
+ OMAP_HSMMC_READ(host->base, SYSCONFIG));
+ seq_printf(s, "CON:\t\t0x%08x\n",
+ OMAP_HSMMC_READ(host->base, CON));
+ seq_printf(s, "HCTL:\t\t0x%08x\n",
+ OMAP_HSMMC_READ(host->base, HCTL));
+ seq_printf(s, "SYSCTL:\t\t0x%08x\n",
+ OMAP_HSMMC_READ(host->base, SYSCTL));
+ seq_printf(s, "IE:\t\t0x%08x\n",
+ OMAP_HSMMC_READ(host->base, IE));
+ seq_printf(s, "ISE:\t\t0x%08x\n",
+ OMAP_HSMMC_READ(host->base, ISE));
+ seq_printf(s, "CAPA:\t\t0x%08x\n",
+ OMAP_HSMMC_READ(host->base, CAPA));
+
+ clk_disable(host->fclk);
+
+ return 0;
+}
+
+static int omap_hsmmc_regs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, omap_hsmmc_regs_show, inode->i_private);
+}
+
+static const struct file_operations mmc_regs_fops = {
+ .open = omap_hsmmc_regs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void omap_hsmmc_debugfs(struct mmc_host *mmc)
+{
+ if (mmc->debugfs_root)
+ debugfs_create_file("regs", S_IRUSR, mmc->debugfs_root,
+ mmc, &mmc_regs_fops);
+}
+
+#else
+
+static void omap_hsmmc_debugfs(struct mmc_host *mmc)
+{
+}
+
+#endif
+
+static int __init omap_hsmmc_probe(struct platform_device *pdev)
+{
+ struct omap_mmc_platform_data *pdata = pdev->dev.platform_data;
+ struct mmc_host *mmc;
+ struct omap_hsmmc_host *host = NULL;
+ struct resource *res;
+ int ret, irq;
+
+ if (pdata == NULL) {
+ dev_err(&pdev->dev, "Platform Data is missing\n");
+ return -ENXIO;
+ }
+
+ if (pdata->nr_slots == 0) {
+ dev_err(&pdev->dev, "No Slots\n");
+ return -ENXIO;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ irq = platform_get_irq(pdev, 0);
+ if (res == NULL || irq < 0)
+ return -ENXIO;
+
+ res->start += pdata->reg_offset;
+ res->end += pdata->reg_offset;
+ res = request_mem_region(res->start, resource_size(res), pdev->name);
+ if (res == NULL)
+ return -EBUSY;
+
+ ret = omap_hsmmc_gpio_init(pdata);
+ if (ret)
+ goto err;
+
+ mmc = mmc_alloc_host(sizeof(struct omap_hsmmc_host), &pdev->dev);
+ if (!mmc) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ host = mmc_priv(mmc);
+ host->mmc = mmc;
+ host->pdata = pdata;
+ host->dev = &pdev->dev;
+ host->use_dma = 1;
+ host->dev->dma_mask = &pdata->dma_mask;
+ host->dma_ch = -1;
+ host->irq = irq;
+ host->id = pdev->id;
+ host->slot_id = 0;
+ host->mapbase = res->start;
+ host->base = ioremap(host->mapbase, SZ_4K);
+ host->power_mode = MMC_POWER_OFF;
+ host->next_data.cookie = 1;
+
+ platform_set_drvdata(pdev, host);
+ INIT_WORK(&host->mmc_carddetect_work, omap_hsmmc_detect);
+
+ if (mmc_slot(host).power_saving)
+ mmc->ops = &omap_hsmmc_ps_ops;
+ else
+ mmc->ops = &omap_hsmmc_ops;
+
+ /*
+ * If regulator_disable can only put vcc_aux to sleep then there is
+ * no off state.
+ */
+ if (mmc_slot(host).vcc_aux_disable_is_sleep)
+ mmc_slot(host).no_off = 1;
+
+ mmc->f_min = 400000;
+ mmc->f_max = 52000000;
+
+ spin_lock_init(&host->irq_lock);
+
+ host->iclk = clk_get(&pdev->dev, "ick");
+ if (IS_ERR(host->iclk)) {
+ ret = PTR_ERR(host->iclk);
+ host->iclk = NULL;
+ goto err1;
+ }
+ host->fclk = clk_get(&pdev->dev, "fck");
+ if (IS_ERR(host->fclk)) {
+ ret = PTR_ERR(host->fclk);
+ host->fclk = NULL;
+ clk_put(host->iclk);
+ goto err1;
+ }
+
+ omap_hsmmc_context_save(host);
+
+ mmc->caps |= MMC_CAP_DISABLE;
+ mmc_set_disable_delay(mmc, OMAP_MMC_DISABLED_TIMEOUT);
+ /* we start off in DISABLED state */
+ host->dpm_state = DISABLED;
+
+ if (clk_enable(host->iclk) != 0) {
+ clk_put(host->iclk);
+ clk_put(host->fclk);
+ goto err1;
+ }
+
+ if (mmc_host_enable(host->mmc) != 0) {
+ clk_disable(host->iclk);
+ clk_put(host->iclk);
+ clk_put(host->fclk);
+ goto err1;
+ }
+
+ if (cpu_is_omap2430()) {
+ host->dbclk = clk_get(&pdev->dev, "mmchsdb_fck");
+ /*
+ * MMC can still work without debounce clock.
+ */
+ if (IS_ERR(host->dbclk))
+ dev_warn(mmc_dev(host->mmc),
+ "Failed to get debounce clock\n");
+ else
+ host->got_dbclk = 1;
+
+ if (host->got_dbclk)
+ if (clk_enable(host->dbclk) != 0)
+ dev_dbg(mmc_dev(host->mmc), "Enabling debounce"
+ " clk failed\n");
+ }
+
+ /* Since we do only SG emulation, we can have as many segs
+ * as we want. */
+ mmc->max_segs = 1024;
+
+ mmc->max_blk_size = 512; /* Block Length at max can be 1024 */
+ mmc->max_blk_count = 0xFFFF; /* No. of Blocks is 16 bits */
+ mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
+ mmc->max_seg_size = mmc->max_req_size;
+
+ mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED |
+ MMC_CAP_WAIT_WHILE_BUSY | MMC_CAP_ERASE;
+
+ mmc->caps |= mmc_slot(host).caps;
+ if (mmc->caps & MMC_CAP_8_BIT_DATA)
+ mmc->caps |= MMC_CAP_4_BIT_DATA;
+
+ if (mmc_slot(host).nonremovable)
+ mmc->caps |= MMC_CAP_NONREMOVABLE;
+
+ omap_hsmmc_conf_bus_power(host);
+
+ /* Select DMA lines */
+ switch (host->id) {
+ case OMAP_MMC1_DEVID:
+ host->dma_line_tx = OMAP24XX_DMA_MMC1_TX;
+ host->dma_line_rx = OMAP24XX_DMA_MMC1_RX;
+ break;
+ case OMAP_MMC2_DEVID:
+ host->dma_line_tx = OMAP24XX_DMA_MMC2_TX;
+ host->dma_line_rx = OMAP24XX_DMA_MMC2_RX;
+ break;
+ case OMAP_MMC3_DEVID:
+ host->dma_line_tx = OMAP34XX_DMA_MMC3_TX;
+ host->dma_line_rx = OMAP34XX_DMA_MMC3_RX;
+ break;
+ case OMAP_MMC4_DEVID:
+ host->dma_line_tx = OMAP44XX_DMA_MMC4_TX;
+ host->dma_line_rx = OMAP44XX_DMA_MMC4_RX;
+ break;
+ case OMAP_MMC5_DEVID:
+ host->dma_line_tx = OMAP44XX_DMA_MMC5_TX;
+ host->dma_line_rx = OMAP44XX_DMA_MMC5_RX;
+ break;
+ default:
+ dev_err(mmc_dev(host->mmc), "Invalid MMC id\n");
+ goto err_irq;
+ }
+
+ /* Request IRQ for MMC operations */
+ ret = request_irq(host->irq, omap_hsmmc_irq, IRQF_DISABLED,
+ mmc_hostname(mmc), host);
+ if (ret) {
+ dev_dbg(mmc_dev(host->mmc), "Unable to grab HSMMC IRQ\n");
+ goto err_irq;
+ }
+
+ if (pdata->init != NULL) {
+ if (pdata->init(&pdev->dev) != 0) {
+ dev_dbg(mmc_dev(host->mmc),
+ "Unable to configure MMC IRQs\n");
+ goto err_irq_cd_init;
+ }
+ }
+
+ if (omap_hsmmc_have_reg() && !mmc_slot(host).set_power) {
+ ret = omap_hsmmc_reg_get(host);
+ if (ret)
+ goto err_reg;
+ host->use_reg = 1;
+ }
+
+ mmc->ocr_avail = mmc_slot(host).ocr_mask;
+
+ /* Request IRQ for card detect */
+ if ((mmc_slot(host).card_detect_irq)) {
+ ret = request_irq(mmc_slot(host).card_detect_irq,
+ omap_hsmmc_cd_handler,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING
+ | IRQF_DISABLED,
+ mmc_hostname(mmc), host);
+ if (ret) {
+ dev_dbg(mmc_dev(host->mmc),
+ "Unable to grab MMC CD IRQ\n");
+ goto err_irq_cd;
+ }
+ pdata->suspend = omap_hsmmc_suspend_cdirq;
+ pdata->resume = omap_hsmmc_resume_cdirq;
+ }
+
+ omap_hsmmc_disable_irq(host);
+
+ mmc_host_lazy_disable(host->mmc);
+
+ omap_hsmmc_protect_card(host);
+
+ mmc_add_host(mmc);
+
+ if (mmc_slot(host).name != NULL) {
+ ret = device_create_file(&mmc->class_dev, &dev_attr_slot_name);
+ if (ret < 0)
+ goto err_slot_name;
+ }
+ if (mmc_slot(host).card_detect_irq && mmc_slot(host).get_cover_state) {
+ ret = device_create_file(&mmc->class_dev,
+ &dev_attr_cover_switch);
+ if (ret < 0)
+ goto err_slot_name;
+ }
+
+ omap_hsmmc_debugfs(mmc);
+
+ return 0;
+
+err_slot_name:
+ mmc_remove_host(mmc);
+ free_irq(mmc_slot(host).card_detect_irq, host);
+err_irq_cd:
+ if (host->use_reg)
+ omap_hsmmc_reg_put(host);
+err_reg:
+ if (host->pdata->cleanup)
+ host->pdata->cleanup(&pdev->dev);
+err_irq_cd_init:
+ free_irq(host->irq, host);
+err_irq:
+ mmc_host_disable(host->mmc);
+ clk_disable(host->iclk);
+ clk_put(host->fclk);
+ clk_put(host->iclk);
+ if (host->got_dbclk) {
+ clk_disable(host->dbclk);
+ clk_put(host->dbclk);
+ }
+err1:
+ iounmap(host->base);
+ platform_set_drvdata(pdev, NULL);
+ mmc_free_host(mmc);
+err_alloc:
+ omap_hsmmc_gpio_free(pdata);
+err:
+ release_mem_region(res->start, resource_size(res));
+ return ret;
+}
+
+static int omap_hsmmc_remove(struct platform_device *pdev)
+{
+ struct omap_hsmmc_host *host = platform_get_drvdata(pdev);
+ struct resource *res;
+
+ if (host) {
+ mmc_host_enable(host->mmc);
+ mmc_remove_host(host->mmc);
+ if (host->use_reg)
+ omap_hsmmc_reg_put(host);
+ if (host->pdata->cleanup)
+ host->pdata->cleanup(&pdev->dev);
+ free_irq(host->irq, host);
+ if (mmc_slot(host).card_detect_irq)
+ free_irq(mmc_slot(host).card_detect_irq, host);
+ flush_work_sync(&host->mmc_carddetect_work);
+
+ mmc_host_disable(host->mmc);
+ clk_disable(host->iclk);
+ clk_put(host->fclk);
+ clk_put(host->iclk);
+ if (host->got_dbclk) {
+ clk_disable(host->dbclk);
+ clk_put(host->dbclk);
+ }
+
+ mmc_free_host(host->mmc);
+ iounmap(host->base);
+ omap_hsmmc_gpio_free(pdev->dev.platform_data);
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res)
+ release_mem_region(res->start, resource_size(res));
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int omap_hsmmc_suspend(struct device *dev)
+{
+ int ret = 0;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct omap_hsmmc_host *host = platform_get_drvdata(pdev);
+
+ if (host && host->suspended)
+ return 0;
+
+ if (host) {
+ host->suspended = 1;
+ if (host->pdata->suspend) {
+ ret = host->pdata->suspend(&pdev->dev,
+ host->slot_id);
+ if (ret) {
+ dev_dbg(mmc_dev(host->mmc),
+ "Unable to handle MMC board"
+ " level suspend\n");
+ host->suspended = 0;
+ return ret;
+ }
+ }
+ cancel_work_sync(&host->mmc_carddetect_work);
+ ret = mmc_suspend_host(host->mmc);
+ mmc_host_enable(host->mmc);
+ if (ret == 0) {
+ omap_hsmmc_disable_irq(host);
+ OMAP_HSMMC_WRITE(host->base, HCTL,
+ OMAP_HSMMC_READ(host->base, HCTL) & ~SDBP);
+ mmc_host_disable(host->mmc);
+ clk_disable(host->iclk);
+ if (host->got_dbclk)
+ clk_disable(host->dbclk);
+ } else {
+ host->suspended = 0;
+ if (host->pdata->resume) {
+ ret = host->pdata->resume(&pdev->dev,
+ host->slot_id);
+ if (ret)
+ dev_dbg(mmc_dev(host->mmc),
+ "Unmask interrupt failed\n");
+ }
+ mmc_host_disable(host->mmc);
+ }
+
+ }
+ return ret;
+}
+
+/* Routine to resume the MMC device */
+static int omap_hsmmc_resume(struct device *dev)
+{
+ int ret = 0;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct omap_hsmmc_host *host = platform_get_drvdata(pdev);
+
+ if (host && !host->suspended)
+ return 0;
+
+ if (host) {
+ ret = clk_enable(host->iclk);
+ if (ret)
+ goto clk_en_err;
+
+ if (mmc_host_enable(host->mmc) != 0) {
+ clk_disable(host->iclk);
+ goto clk_en_err;
+ }
+
+ if (host->got_dbclk)
+ clk_enable(host->dbclk);
+
+ omap_hsmmc_conf_bus_power(host);
+
+ if (host->pdata->resume) {
+ ret = host->pdata->resume(&pdev->dev, host->slot_id);
+ if (ret)
+ dev_dbg(mmc_dev(host->mmc),
+ "Unmask interrupt failed\n");
+ }
+
+ omap_hsmmc_protect_card(host);
+
+ /* Notify the core to resume the host */
+ ret = mmc_resume_host(host->mmc);
+ if (ret == 0)
+ host->suspended = 0;
+
+ mmc_host_lazy_disable(host->mmc);
+ }
+
+ return ret;
+
+clk_en_err:
+ dev_dbg(mmc_dev(host->mmc),
+ "Failed to enable MMC clocks during resume\n");
+ return ret;
+}
+
+#else
+#define omap_hsmmc_suspend NULL
+#define omap_hsmmc_resume NULL
+#endif
+
+static struct dev_pm_ops omap_hsmmc_dev_pm_ops = {
+ .suspend = omap_hsmmc_suspend,
+ .resume = omap_hsmmc_resume,
+};
+
+static struct platform_driver omap_hsmmc_driver = {
+ .remove = omap_hsmmc_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .pm = &omap_hsmmc_dev_pm_ops,
+ },
+};
+
+static int __init omap_hsmmc_init(void)
+{
+ /* Register the MMC driver */
+ return platform_driver_probe(&omap_hsmmc_driver, omap_hsmmc_probe);
+}
+
+static void __exit omap_hsmmc_cleanup(void)
+{
+ /* Unregister MMC driver */
+ platform_driver_unregister(&omap_hsmmc_driver);
+}
+
+module_init(omap_hsmmc_init);
+module_exit(omap_hsmmc_cleanup);
+
+MODULE_DESCRIPTION("OMAP High Speed Multimedia Card driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRIVER_NAME);
+MODULE_AUTHOR("Texas Instruments Inc");
diff --git a/drivers/mmc/host/pxamci.c b/drivers/mmc/host/pxamci.c
new file mode 100644
index 00000000..7257738f
--- /dev/null
+++ b/drivers/mmc/host/pxamci.c
@@ -0,0 +1,890 @@
+/*
+ * linux/drivers/mmc/host/pxa.c - PXA MMCI driver
+ *
+ * Copyright (C) 2003 Russell King, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This hardware is really sick:
+ * - No way to clear interrupts.
+ * - Have to turn off the clock whenever we touch the device.
+ * - Doesn't tell you how many data blocks were transferred.
+ * Yuck!
+ *
+ * 1 and 3 byte data transfers not supported
+ * max block length up to 1023
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/mmc/host.h>
+#include <linux/io.h>
+#include <linux/regulator/consumer.h>
+#include <linux/gpio.h>
+#include <linux/gfp.h>
+
+#include <asm/sizes.h>
+
+#include <mach/hardware.h>
+#include <mach/dma.h>
+#include <mach/mmc.h>
+
+#include "pxamci.h"
+
+#define DRIVER_NAME "pxa2xx-mci"
+
+#define NR_SG 1
+#define CLKRT_OFF (~0)
+
+#define mmc_has_26MHz() (cpu_is_pxa300() || cpu_is_pxa310() \
+ || cpu_is_pxa935())
+
+struct pxamci_host {
+ struct mmc_host *mmc;
+ spinlock_t lock;
+ struct resource *res;
+ void __iomem *base;
+ struct clk *clk;
+ unsigned long clkrate;
+ int irq;
+ int dma;
+ unsigned int clkrt;
+ unsigned int cmdat;
+ unsigned int imask;
+ unsigned int power_mode;
+ struct pxamci_platform_data *pdata;
+
+ struct mmc_request *mrq;
+ struct mmc_command *cmd;
+ struct mmc_data *data;
+
+ dma_addr_t sg_dma;
+ struct pxa_dma_desc *sg_cpu;
+ unsigned int dma_len;
+
+ unsigned int dma_dir;
+ unsigned int dma_drcmrrx;
+ unsigned int dma_drcmrtx;
+
+ struct regulator *vcc;
+};
+
+static inline void pxamci_init_ocr(struct pxamci_host *host)
+{
+#ifdef CONFIG_REGULATOR
+ host->vcc = regulator_get(mmc_dev(host->mmc), "vmmc");
+
+ if (IS_ERR(host->vcc))
+ host->vcc = NULL;
+ else {
+ host->mmc->ocr_avail = mmc_regulator_get_ocrmask(host->vcc);
+ if (host->pdata && host->pdata->ocr_mask)
+ dev_warn(mmc_dev(host->mmc),
+ "ocr_mask/setpower will not be used\n");
+ }
+#endif
+ if (host->vcc == NULL) {
+ /* fall-back to platform data */
+ host->mmc->ocr_avail = host->pdata ?
+ host->pdata->ocr_mask :
+ MMC_VDD_32_33 | MMC_VDD_33_34;
+ }
+}
+
+static inline int pxamci_set_power(struct pxamci_host *host,
+ unsigned char power_mode,
+ unsigned int vdd)
+{
+ int on;
+
+ if (host->vcc) {
+ int ret;
+
+ if (power_mode == MMC_POWER_UP) {
+ ret = mmc_regulator_set_ocr(host->mmc, host->vcc, vdd);
+ if (ret)
+ return ret;
+ } else if (power_mode == MMC_POWER_OFF) {
+ ret = mmc_regulator_set_ocr(host->mmc, host->vcc, 0);
+ if (ret)
+ return ret;
+ }
+ }
+ if (!host->vcc && host->pdata &&
+ gpio_is_valid(host->pdata->gpio_power)) {
+ on = ((1 << vdd) & host->pdata->ocr_mask);
+ gpio_set_value(host->pdata->gpio_power,
+ !!on ^ host->pdata->gpio_power_invert);
+ }
+ if (!host->vcc && host->pdata && host->pdata->setpower)
+ host->pdata->setpower(mmc_dev(host->mmc), vdd);
+
+ return 0;
+}
+
+static void pxamci_stop_clock(struct pxamci_host *host)
+{
+ if (readl(host->base + MMC_STAT) & STAT_CLK_EN) {
+ unsigned long timeout = 10000;
+ unsigned int v;
+
+ writel(STOP_CLOCK, host->base + MMC_STRPCL);
+
+ do {
+ v = readl(host->base + MMC_STAT);
+ if (!(v & STAT_CLK_EN))
+ break;
+ udelay(1);
+ } while (timeout--);
+
+ if (v & STAT_CLK_EN)
+ dev_err(mmc_dev(host->mmc), "unable to stop clock\n");
+ }
+}
+
+static void pxamci_enable_irq(struct pxamci_host *host, unsigned int mask)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+ host->imask &= ~mask;
+ writel(host->imask, host->base + MMC_I_MASK);
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void pxamci_disable_irq(struct pxamci_host *host, unsigned int mask)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+ host->imask |= mask;
+ writel(host->imask, host->base + MMC_I_MASK);
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void pxamci_setup_data(struct pxamci_host *host, struct mmc_data *data)
+{
+ unsigned int nob = data->blocks;
+ unsigned long long clks;
+ unsigned int timeout;
+ bool dalgn = 0;
+ u32 dcmd;
+ int i;
+
+ host->data = data;
+
+ if (data->flags & MMC_DATA_STREAM)
+ nob = 0xffff;
+
+ writel(nob, host->base + MMC_NOB);
+ writel(data->blksz, host->base + MMC_BLKLEN);
+
+ clks = (unsigned long long)data->timeout_ns * host->clkrate;
+ do_div(clks, 1000000000UL);
+ timeout = (unsigned int)clks + (data->timeout_clks << host->clkrt);
+ writel((timeout + 255) / 256, host->base + MMC_RDTO);
+
+ if (data->flags & MMC_DATA_READ) {
+ host->dma_dir = DMA_FROM_DEVICE;
+ dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC;
+ DRCMR(host->dma_drcmrtx) = 0;
+ DRCMR(host->dma_drcmrrx) = host->dma | DRCMR_MAPVLD;
+ } else {
+ host->dma_dir = DMA_TO_DEVICE;
+ dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG;
+ DRCMR(host->dma_drcmrrx) = 0;
+ DRCMR(host->dma_drcmrtx) = host->dma | DRCMR_MAPVLD;
+ }
+
+ dcmd |= DCMD_BURST32 | DCMD_WIDTH1;
+
+ host->dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+ host->dma_dir);
+
+ for (i = 0; i < host->dma_len; i++) {
+ unsigned int length = sg_dma_len(&data->sg[i]);
+ host->sg_cpu[i].dcmd = dcmd | length;
+ if (length & 31 && !(data->flags & MMC_DATA_READ))
+ host->sg_cpu[i].dcmd |= DCMD_ENDIRQEN;
+ /* Not aligned to 8-byte boundary? */
+ if (sg_dma_address(&data->sg[i]) & 0x7)
+ dalgn = 1;
+ if (data->flags & MMC_DATA_READ) {
+ host->sg_cpu[i].dsadr = host->res->start + MMC_RXFIFO;
+ host->sg_cpu[i].dtadr = sg_dma_address(&data->sg[i]);
+ } else {
+ host->sg_cpu[i].dsadr = sg_dma_address(&data->sg[i]);
+ host->sg_cpu[i].dtadr = host->res->start + MMC_TXFIFO;
+ }
+ host->sg_cpu[i].ddadr = host->sg_dma + (i + 1) *
+ sizeof(struct pxa_dma_desc);
+ }
+ host->sg_cpu[host->dma_len - 1].ddadr = DDADR_STOP;
+ wmb();
+
+ /*
+ * The PXA27x DMA controller encounters overhead when working with
+ * unaligned (to 8-byte boundaries) data, so switch on byte alignment
+ * mode only if we have unaligned data.
+ */
+ if (dalgn)
+ DALGN |= (1 << host->dma);
+ else
+ DALGN &= ~(1 << host->dma);
+ DDADR(host->dma) = host->sg_dma;
+
+ /*
+ * workaround for erratum #91:
+ * only start DMA now if we are doing a read,
+ * otherwise we wait until CMD/RESP has finished
+ * before starting DMA.
+ */
+ if (!cpu_is_pxa27x() || data->flags & MMC_DATA_READ)
+ DCSR(host->dma) = DCSR_RUN;
+}
+
+static void pxamci_start_cmd(struct pxamci_host *host, struct mmc_command *cmd, unsigned int cmdat)
+{
+ WARN_ON(host->cmd != NULL);
+ host->cmd = cmd;
+
+ if (cmd->flags & MMC_RSP_BUSY)
+ cmdat |= CMDAT_BUSY;
+
+#define RSP_TYPE(x) ((x) & ~(MMC_RSP_BUSY|MMC_RSP_OPCODE))
+ switch (RSP_TYPE(mmc_resp_type(cmd))) {
+ case RSP_TYPE(MMC_RSP_R1): /* r1, r1b, r6, r7 */
+ cmdat |= CMDAT_RESP_SHORT;
+ break;
+ case RSP_TYPE(MMC_RSP_R3):
+ cmdat |= CMDAT_RESP_R3;
+ break;
+ case RSP_TYPE(MMC_RSP_R2):
+ cmdat |= CMDAT_RESP_R2;
+ break;
+ default:
+ break;
+ }
+
+ writel(cmd->opcode, host->base + MMC_CMD);
+ writel(cmd->arg >> 16, host->base + MMC_ARGH);
+ writel(cmd->arg & 0xffff, host->base + MMC_ARGL);
+ writel(cmdat, host->base + MMC_CMDAT);
+ writel(host->clkrt, host->base + MMC_CLKRT);
+
+ writel(START_CLOCK, host->base + MMC_STRPCL);
+
+ pxamci_enable_irq(host, END_CMD_RES);
+}
+
+static void pxamci_finish_request(struct pxamci_host *host, struct mmc_request *mrq)
+{
+ host->mrq = NULL;
+ host->cmd = NULL;
+ host->data = NULL;
+ mmc_request_done(host->mmc, mrq);
+}
+
+static int pxamci_cmd_done(struct pxamci_host *host, unsigned int stat)
+{
+ struct mmc_command *cmd = host->cmd;
+ int i;
+ u32 v;
+
+ if (!cmd)
+ return 0;
+
+ host->cmd = NULL;
+
+ /*
+ * Did I mention this is Sick. We always need to
+ * discard the upper 8 bits of the first 16-bit word.
+ */
+ v = readl(host->base + MMC_RES) & 0xffff;
+ for (i = 0; i < 4; i++) {
+ u32 w1 = readl(host->base + MMC_RES) & 0xffff;
+ u32 w2 = readl(host->base + MMC_RES) & 0xffff;
+ cmd->resp[i] = v << 24 | w1 << 8 | w2 >> 8;
+ v = w2;
+ }
+
+ if (stat & STAT_TIME_OUT_RESPONSE) {
+ cmd->error = -ETIMEDOUT;
+ } else if (stat & STAT_RES_CRC_ERR && cmd->flags & MMC_RSP_CRC) {
+ /*
+ * workaround for erratum #42:
+ * Intel PXA27x Family Processor Specification Update Rev 001
+ * A bogus CRC error can appear if the msb of a 136 bit
+ * response is a one.
+ */
+ if (cpu_is_pxa27x() &&
+ (cmd->flags & MMC_RSP_136 && cmd->resp[0] & 0x80000000))
+ pr_debug("ignoring CRC from command %d - *risky*\n", cmd->opcode);
+ else
+ cmd->error = -EILSEQ;
+ }
+
+ pxamci_disable_irq(host, END_CMD_RES);
+ if (host->data && !cmd->error) {
+ pxamci_enable_irq(host, DATA_TRAN_DONE);
+ /*
+ * workaround for erratum #91, if doing write
+ * enable DMA late
+ */
+ if (cpu_is_pxa27x() && host->data->flags & MMC_DATA_WRITE)
+ DCSR(host->dma) = DCSR_RUN;
+ } else {
+ pxamci_finish_request(host, host->mrq);
+ }
+
+ return 1;
+}
+
+static int pxamci_data_done(struct pxamci_host *host, unsigned int stat)
+{
+ struct mmc_data *data = host->data;
+
+ if (!data)
+ return 0;
+
+ DCSR(host->dma) = 0;
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+ host->dma_dir);
+
+ if (stat & STAT_READ_TIME_OUT)
+ data->error = -ETIMEDOUT;
+ else if (stat & (STAT_CRC_READ_ERROR|STAT_CRC_WRITE_ERROR))
+ data->error = -EILSEQ;
+
+ /*
+ * There appears to be a hardware design bug here. There seems to
+ * be no way to find out how much data was transferred to the card.
+ * This means that if there was an error on any block, we mark all
+ * data blocks as being in error.
+ */
+ if (!data->error)
+ data->bytes_xfered = data->blocks * data->blksz;
+ else
+ data->bytes_xfered = 0;
+
+ pxamci_disable_irq(host, DATA_TRAN_DONE);
+
+ host->data = NULL;
+ if (host->mrq->stop) {
+ pxamci_stop_clock(host);
+ pxamci_start_cmd(host, host->mrq->stop, host->cmdat);
+ } else {
+ pxamci_finish_request(host, host->mrq);
+ }
+
+ return 1;
+}
+
+static irqreturn_t pxamci_irq(int irq, void *devid)
+{
+ struct pxamci_host *host = devid;
+ unsigned int ireg;
+ int handled = 0;
+
+ ireg = readl(host->base + MMC_I_REG) & ~readl(host->base + MMC_I_MASK);
+
+ if (ireg) {
+ unsigned stat = readl(host->base + MMC_STAT);
+
+ pr_debug("PXAMCI: irq %08x stat %08x\n", ireg, stat);
+
+ if (ireg & END_CMD_RES)
+ handled |= pxamci_cmd_done(host, stat);
+ if (ireg & DATA_TRAN_DONE)
+ handled |= pxamci_data_done(host, stat);
+ if (ireg & SDIO_INT) {
+ mmc_signal_sdio_irq(host->mmc);
+ handled = 1;
+ }
+ }
+
+ return IRQ_RETVAL(handled);
+}
+
+static void pxamci_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct pxamci_host *host = mmc_priv(mmc);
+ unsigned int cmdat;
+
+ WARN_ON(host->mrq != NULL);
+
+ host->mrq = mrq;
+
+ pxamci_stop_clock(host);
+
+ cmdat = host->cmdat;
+ host->cmdat &= ~CMDAT_INIT;
+
+ if (mrq->data) {
+ pxamci_setup_data(host, mrq->data);
+
+ cmdat &= ~CMDAT_BUSY;
+ cmdat |= CMDAT_DATAEN | CMDAT_DMAEN;
+ if (mrq->data->flags & MMC_DATA_WRITE)
+ cmdat |= CMDAT_WRITE;
+
+ if (mrq->data->flags & MMC_DATA_STREAM)
+ cmdat |= CMDAT_STREAM;
+ }
+
+ pxamci_start_cmd(host, mrq->cmd, cmdat);
+}
+
+static int pxamci_get_ro(struct mmc_host *mmc)
+{
+ struct pxamci_host *host = mmc_priv(mmc);
+
+ if (host->pdata && gpio_is_valid(host->pdata->gpio_card_ro)) {
+ if (host->pdata->gpio_card_ro_invert)
+ return !gpio_get_value(host->pdata->gpio_card_ro);
+ else
+ return gpio_get_value(host->pdata->gpio_card_ro);
+ }
+ if (host->pdata && host->pdata->get_ro)
+ 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)
+{
+ struct pxamci_host *host = mmc_priv(mmc);
+
+ if (ios->clock) {
+ unsigned long rate = host->clkrate;
+ unsigned int clk = rate / ios->clock;
+
+ if (host->clkrt == CLKRT_OFF)
+ clk_enable(host->clk);
+
+ if (ios->clock == 26000000) {
+ /* to support 26MHz */
+ host->clkrt = 7;
+ } else {
+ /* to handle (19.5MHz, 26MHz) */
+ if (!clk)
+ clk = 1;
+
+ /*
+ * clk might result in a lower divisor than we
+ * desire. check for that condition and adjust
+ * as appropriate.
+ */
+ if (rate / clk > ios->clock)
+ clk <<= 1;
+ host->clkrt = fls(clk) - 1;
+ }
+
+ /*
+ * we write clkrt on the next command
+ */
+ } else {
+ pxamci_stop_clock(host);
+ if (host->clkrt != CLKRT_OFF) {
+ host->clkrt = CLKRT_OFF;
+ clk_disable(host->clk);
+ }
+ }
+
+ if (host->power_mode != ios->power_mode) {
+ int ret;
+
+ host->power_mode = ios->power_mode;
+
+ ret = pxamci_set_power(host, ios->power_mode, ios->vdd);
+ if (ret) {
+ dev_err(mmc_dev(mmc), "unable to set power\n");
+ /*
+ * The .set_ios() function in the mmc_host_ops
+ * struct return void, and failing to set the
+ * power should be rare so we print an error and
+ * return here.
+ */
+ return;
+ }
+
+ if (ios->power_mode == MMC_POWER_ON)
+ host->cmdat |= CMDAT_INIT;
+ }
+
+ if (ios->bus_width == MMC_BUS_WIDTH_4)
+ host->cmdat |= CMDAT_SD_4DAT;
+ else
+ host->cmdat &= ~CMDAT_SD_4DAT;
+
+ dev_dbg(mmc_dev(mmc), "PXAMCI: clkrt = %x cmdat = %x\n",
+ host->clkrt, host->cmdat);
+}
+
+static void pxamci_enable_sdio_irq(struct mmc_host *host, int enable)
+{
+ struct pxamci_host *pxa_host = mmc_priv(host);
+
+ if (enable)
+ pxamci_enable_irq(pxa_host, SDIO_INT);
+ else
+ pxamci_disable_irq(pxa_host, SDIO_INT);
+}
+
+static const struct mmc_host_ops pxamci_ops = {
+ .request = pxamci_request,
+ .get_ro = pxamci_get_ro,
+ .set_ios = pxamci_set_ios,
+ .enable_sdio_irq = pxamci_enable_sdio_irq,
+};
+
+static void pxamci_dma_irq(int dma, void *devid)
+{
+ struct pxamci_host *host = devid;
+ int dcsr = DCSR(dma);
+ DCSR(dma) = dcsr & ~DCSR_STOPIRQEN;
+
+ if (dcsr & DCSR_ENDINTR) {
+ writel(BUF_PART_FULL, host->base + MMC_PRTBUF);
+ } else {
+ printk(KERN_ERR "%s: DMA error on channel %d (DCSR=%#x)\n",
+ mmc_hostname(host->mmc), dma, dcsr);
+ host->data->error = -EIO;
+ pxamci_data_done(host, 0);
+ }
+}
+
+static irqreturn_t pxamci_detect_irq(int irq, void *devid)
+{
+ struct pxamci_host *host = mmc_priv(devid);
+
+ mmc_detect_change(devid, msecs_to_jiffies(host->pdata->detect_delay_ms));
+ return IRQ_HANDLED;
+}
+
+static int pxamci_probe(struct platform_device *pdev)
+{
+ struct mmc_host *mmc;
+ struct pxamci_host *host = NULL;
+ struct resource *r, *dmarx, *dmatx;
+ int ret, irq, gpio_cd = -1, gpio_ro = -1, gpio_power = -1;
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ irq = platform_get_irq(pdev, 0);
+ if (!r || irq < 0)
+ return -ENXIO;
+
+ r = request_mem_region(r->start, SZ_4K, DRIVER_NAME);
+ if (!r)
+ return -EBUSY;
+
+ mmc = mmc_alloc_host(sizeof(struct pxamci_host), &pdev->dev);
+ if (!mmc) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ mmc->ops = &pxamci_ops;
+
+ /*
+ * We can do SG-DMA, but we don't because we never know how much
+ * data we successfully wrote to the card.
+ */
+ mmc->max_segs = NR_SG;
+
+ /*
+ * Our hardware DMA can handle a maximum of one page per SG entry.
+ */
+ mmc->max_seg_size = PAGE_SIZE;
+
+ /*
+ * Block length register is only 10 bits before PXA27x.
+ */
+ mmc->max_blk_size = cpu_is_pxa25x() ? 1023 : 2048;
+
+ /*
+ * Block count register is 16 bits.
+ */
+ mmc->max_blk_count = 65535;
+
+ host = mmc_priv(mmc);
+ host->mmc = mmc;
+ host->dma = -1;
+ host->pdata = pdev->dev.platform_data;
+ host->clkrt = CLKRT_OFF;
+
+ host->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(host->clk)) {
+ ret = PTR_ERR(host->clk);
+ host->clk = NULL;
+ goto out;
+ }
+
+ host->clkrate = clk_get_rate(host->clk);
+
+ /*
+ * Calculate minimum clock rate, rounding up.
+ */
+ mmc->f_min = (host->clkrate + 63) / 64;
+ mmc->f_max = (mmc_has_26MHz()) ? 26000000 : host->clkrate;
+
+ pxamci_init_ocr(host);
+
+ mmc->caps = 0;
+ host->cmdat = 0;
+ if (!cpu_is_pxa25x()) {
+ mmc->caps |= MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;
+ host->cmdat |= CMDAT_SDIO_INT_EN;
+ if (mmc_has_26MHz())
+ mmc->caps |= MMC_CAP_MMC_HIGHSPEED |
+ MMC_CAP_SD_HIGHSPEED;
+ }
+
+ host->sg_cpu = dma_alloc_coherent(&pdev->dev, PAGE_SIZE, &host->sg_dma, GFP_KERNEL);
+ if (!host->sg_cpu) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ spin_lock_init(&host->lock);
+ host->res = r;
+ host->irq = irq;
+ host->imask = MMC_I_MASK_ALL;
+
+ host->base = ioremap(r->start, SZ_4K);
+ if (!host->base) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ /*
+ * Ensure that the host controller is shut down, and setup
+ * with our defaults.
+ */
+ pxamci_stop_clock(host);
+ writel(0, host->base + MMC_SPI);
+ writel(64, host->base + MMC_RESTO);
+ writel(host->imask, host->base + MMC_I_MASK);
+
+ host->dma = pxa_request_dma(DRIVER_NAME, DMA_PRIO_LOW,
+ pxamci_dma_irq, host);
+ if (host->dma < 0) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ ret = request_irq(host->irq, pxamci_irq, 0, DRIVER_NAME, host);
+ if (ret)
+ goto out;
+
+ platform_set_drvdata(pdev, mmc);
+
+ dmarx = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ if (!dmarx) {
+ ret = -ENXIO;
+ goto out;
+ }
+ host->dma_drcmrrx = dmarx->start;
+
+ dmatx = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+ if (!dmatx) {
+ ret = -ENXIO;
+ goto out;
+ }
+ host->dma_drcmrtx = dmatx->start;
+
+ if (host->pdata) {
+ gpio_cd = host->pdata->gpio_card_detect;
+ gpio_ro = host->pdata->gpio_card_ro;
+ gpio_power = host->pdata->gpio_power;
+ }
+ if (gpio_is_valid(gpio_power)) {
+ ret = gpio_request(gpio_power, "mmc card power");
+ if (ret) {
+ dev_err(&pdev->dev, "Failed requesting gpio_power %d\n", gpio_power);
+ goto out;
+ }
+ gpio_direction_output(gpio_power,
+ host->pdata->gpio_power_invert);
+ }
+ if (gpio_is_valid(gpio_ro)) {
+ ret = gpio_request(gpio_ro, "mmc card read only");
+ if (ret) {
+ dev_err(&pdev->dev, "Failed requesting gpio_ro %d\n", gpio_ro);
+ goto err_gpio_ro;
+ }
+ gpio_direction_input(gpio_ro);
+ }
+ if (gpio_is_valid(gpio_cd)) {
+ ret = gpio_request(gpio_cd, "mmc card detect");
+ if (ret) {
+ dev_err(&pdev->dev, "Failed requesting gpio_cd %d\n", gpio_cd);
+ goto err_gpio_cd;
+ }
+ gpio_direction_input(gpio_cd);
+
+ ret = request_irq(gpio_to_irq(gpio_cd), pxamci_detect_irq,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "mmc card detect", mmc);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request card detect IRQ\n");
+ goto err_request_irq;
+ }
+ }
+
+ if (host->pdata && host->pdata->init)
+ host->pdata->init(&pdev->dev, pxamci_detect_irq, mmc);
+
+ if (gpio_is_valid(gpio_power) && host->pdata->setpower)
+ dev_warn(&pdev->dev, "gpio_power and setpower() both defined\n");
+ if (gpio_is_valid(gpio_ro) && host->pdata->get_ro)
+ dev_warn(&pdev->dev, "gpio_ro and get_ro() both defined\n");
+
+ mmc_add_host(mmc);
+
+ return 0;
+
+err_request_irq:
+ gpio_free(gpio_cd);
+err_gpio_cd:
+ gpio_free(gpio_ro);
+err_gpio_ro:
+ gpio_free(gpio_power);
+ out:
+ if (host) {
+ if (host->dma >= 0)
+ pxa_free_dma(host->dma);
+ if (host->base)
+ iounmap(host->base);
+ if (host->sg_cpu)
+ dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
+ if (host->clk)
+ clk_put(host->clk);
+ }
+ if (mmc)
+ mmc_free_host(mmc);
+ release_resource(r);
+ return ret;
+}
+
+static int pxamci_remove(struct platform_device *pdev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(pdev);
+ int gpio_cd = -1, gpio_ro = -1, gpio_power = -1;
+
+ platform_set_drvdata(pdev, NULL);
+
+ if (mmc) {
+ struct pxamci_host *host = mmc_priv(mmc);
+
+ mmc_remove_host(mmc);
+
+ if (host->pdata) {
+ gpio_cd = host->pdata->gpio_card_detect;
+ gpio_ro = host->pdata->gpio_card_ro;
+ gpio_power = host->pdata->gpio_power;
+ }
+ if (gpio_is_valid(gpio_cd)) {
+ free_irq(gpio_to_irq(gpio_cd), mmc);
+ gpio_free(gpio_cd);
+ }
+ if (gpio_is_valid(gpio_ro))
+ gpio_free(gpio_ro);
+ if (gpio_is_valid(gpio_power))
+ gpio_free(gpio_power);
+ if (host->vcc)
+ regulator_put(host->vcc);
+
+ if (host->pdata && host->pdata->exit)
+ host->pdata->exit(&pdev->dev, mmc);
+
+ pxamci_stop_clock(host);
+ writel(TXFIFO_WR_REQ|RXFIFO_RD_REQ|CLK_IS_OFF|STOP_CMD|
+ END_CMD_RES|PRG_DONE|DATA_TRAN_DONE,
+ host->base + MMC_I_MASK);
+
+ DRCMR(host->dma_drcmrrx) = 0;
+ DRCMR(host->dma_drcmrtx) = 0;
+
+ free_irq(host->irq, host);
+ pxa_free_dma(host->dma);
+ iounmap(host->base);
+ dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
+
+ clk_put(host->clk);
+
+ release_resource(host->res);
+
+ mmc_free_host(mmc);
+ }
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int pxamci_suspend(struct device *dev)
+{
+ struct mmc_host *mmc = dev_get_drvdata(dev);
+ int ret = 0;
+
+ if (mmc)
+ ret = mmc_suspend_host(mmc);
+
+ return ret;
+}
+
+static int pxamci_resume(struct device *dev)
+{
+ struct mmc_host *mmc = dev_get_drvdata(dev);
+ int ret = 0;
+
+ if (mmc)
+ ret = mmc_resume_host(mmc);
+
+ return ret;
+}
+
+static const struct dev_pm_ops pxamci_pm_ops = {
+ .suspend = pxamci_suspend,
+ .resume = pxamci_resume,
+};
+#endif
+
+static struct platform_driver pxamci_driver = {
+ .probe = pxamci_probe,
+ .remove = pxamci_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &pxamci_pm_ops,
+#endif
+ },
+};
+
+static int __init pxamci_init(void)
+{
+ return platform_driver_register(&pxamci_driver);
+}
+
+static void __exit pxamci_exit(void)
+{
+ platform_driver_unregister(&pxamci_driver);
+}
+
+module_init(pxamci_init);
+module_exit(pxamci_exit);
+
+MODULE_DESCRIPTION("PXA Multimedia Card Interface Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pxa2xx-mci");
diff --git a/drivers/mmc/host/pxamci.h b/drivers/mmc/host/pxamci.h
new file mode 100644
index 00000000..f6c2e2fc
--- /dev/null
+++ b/drivers/mmc/host/pxamci.h
@@ -0,0 +1,90 @@
+#define MMC_STRPCL 0x0000
+#define STOP_CLOCK (1 << 0)
+#define START_CLOCK (2 << 0)
+
+#define MMC_STAT 0x0004
+#define STAT_END_CMD_RES (1 << 13)
+#define STAT_PRG_DONE (1 << 12)
+#define STAT_DATA_TRAN_DONE (1 << 11)
+#define STAT_CLK_EN (1 << 8)
+#define STAT_RECV_FIFO_FULL (1 << 7)
+#define STAT_XMIT_FIFO_EMPTY (1 << 6)
+#define STAT_RES_CRC_ERR (1 << 5)
+#define STAT_SPI_READ_ERROR_TOKEN (1 << 4)
+#define STAT_CRC_READ_ERROR (1 << 3)
+#define STAT_CRC_WRITE_ERROR (1 << 2)
+#define STAT_TIME_OUT_RESPONSE (1 << 1)
+#define STAT_READ_TIME_OUT (1 << 0)
+
+#define MMC_CLKRT 0x0008 /* 3 bit */
+
+#define MMC_SPI 0x000c
+#define SPI_CS_ADDRESS (1 << 3)
+#define SPI_CS_EN (1 << 2)
+#define CRC_ON (1 << 1)
+#define SPI_EN (1 << 0)
+
+#define MMC_CMDAT 0x0010
+#define CMDAT_SDIO_INT_EN (1 << 11)
+#define CMDAT_SD_4DAT (1 << 8)
+#define CMDAT_DMAEN (1 << 7)
+#define CMDAT_INIT (1 << 6)
+#define CMDAT_BUSY (1 << 5)
+#define CMDAT_STREAM (1 << 4) /* 1 = stream */
+#define CMDAT_WRITE (1 << 3) /* 1 = write */
+#define CMDAT_DATAEN (1 << 2)
+#define CMDAT_RESP_NONE (0 << 0)
+#define CMDAT_RESP_SHORT (1 << 0)
+#define CMDAT_RESP_R2 (2 << 0)
+#define CMDAT_RESP_R3 (3 << 0)
+
+#define MMC_RESTO 0x0014 /* 7 bit */
+
+#define MMC_RDTO 0x0018 /* 16 bit */
+
+#define MMC_BLKLEN 0x001c /* 10 bit */
+
+#define MMC_NOB 0x0020 /* 16 bit */
+
+#define MMC_PRTBUF 0x0024
+#define BUF_PART_FULL (1 << 0)
+
+#define MMC_I_MASK 0x0028
+
+/*PXA27x MMC interrupts*/
+#define SDIO_SUSPEND_ACK (1 << 12)
+#define SDIO_INT (1 << 11)
+#define RD_STALLED (1 << 10)
+#define RES_ERR (1 << 9)
+#define DAT_ERR (1 << 8)
+#define TINT (1 << 7)
+
+/*PXA2xx MMC interrupts*/
+#define TXFIFO_WR_REQ (1 << 6)
+#define RXFIFO_RD_REQ (1 << 5)
+#define CLK_IS_OFF (1 << 4)
+#define STOP_CMD (1 << 3)
+#define END_CMD_RES (1 << 2)
+#define PRG_DONE (1 << 1)
+#define DATA_TRAN_DONE (1 << 0)
+
+#if defined(CONFIG_PXA27x) || defined(CONFIG_PXA3xx)
+#define MMC_I_MASK_ALL 0x00001fff
+#else
+#define MMC_I_MASK_ALL 0x0000007f
+#endif
+
+#define MMC_I_REG 0x002c
+/* same as MMC_I_MASK */
+
+#define MMC_CMD 0x0030
+
+#define MMC_ARGH 0x0034 /* 16 bit */
+
+#define MMC_ARGL 0x0038 /* 16 bit */
+
+#define MMC_RES 0x003c /* 16 bit */
+
+#define MMC_RXFIFO 0x0040 /* 8 bit */
+
+#define MMC_TXFIFO 0x0044 /* 8 bit */
diff --git a/drivers/mmc/host/s3cmci.c b/drivers/mmc/host/s3cmci.c
new file mode 100644
index 00000000..a04f87d7
--- /dev/null
+++ b/drivers/mmc/host/s3cmci.c
@@ -0,0 +1,1932 @@
+/*
+ * linux/drivers/mmc/s3cmci.h - Samsung S3C MCI driver
+ *
+ * Copyright (C) 2004-2006 maintech GmbH, Thomas Kleffel <tk@maintech.de>
+ *
+ * Current driver maintained by Ben Dooks and Simtec Electronics
+ * Copyright (C) 2008 Simtec Electronics <ben-linux@fluff.org>
+ *
+ * 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/cpufreq.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+
+#include <mach/dma.h>
+
+#include <mach/regs-sdi.h>
+#include <mach/regs-gpio.h>
+
+#include <plat/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_fail;
+static const int dbgmap_info = dbg_info | dbg_conf;
+static const int dbgmap_debug = dbg_err | 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)
+
+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 */
+
+/**
+ * s3cmci_host_usedma - return whether the host is using dma or pio
+ * @host: The host state
+ *
+ * Return true if the host is using DMA to transfer data, else false
+ * to use PIO mode. Will return static data depending on the driver
+ * configuration.
+ */
+static inline bool s3cmci_host_usedma(struct s3cmci_host *host)
+{
+#ifdef CONFIG_MMC_S3C_PIO
+ return false;
+#elif defined(CONFIG_MMC_S3C_DMA)
+ return true;
+#else
+ return host->dodma;
+#endif
+}
+
+/**
+ * s3cmci_host_canpio - return true if host has pio code available
+ *
+ * Return true if the driver has been compiled with the PIO support code
+ * available.
+ */
+static inline bool s3cmci_host_canpio(void)
+{
+#ifdef CONFIG_MMC_S3C_PIO
+ return true;
+#else
+ return false;
+#endif
+}
+
+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)
+{
+ u32 mask = readl(host->base + host->sdiimsk);
+
+ /* preserve the SDIO IRQ mask state */
+ mask &= S3C2410_SDIIMSK_SDIOIRQ;
+ writel(mask, host->base + host->sdiimsk);
+}
+
+/**
+ * s3cmci_check_sdio_irq - test whether the SDIO IRQ is being signalled
+ * @host: The host to check.
+ *
+ * Test to see if the SDIO interrupt is being signalled in case the
+ * controller has failed to re-detect a card interrupt. Read GPE8 and
+ * see if it is low and if so, signal a SDIO interrupt.
+ *
+ * This is currently called if a request is finished (we assume that the
+ * bus is now idle) and when the SDIO IRQ is enabled in case the IRQ is
+ * already being indicated.
+*/
+static void s3cmci_check_sdio_irq(struct s3cmci_host *host)
+{
+ if (host->sdio_irqen) {
+ if (gpio_get_value(S3C2410_GPE(8)) == 0) {
+ printk(KERN_DEBUG "%s: signalling irq\n", __func__);
+ mmc_signal_sdio_irq(host->mmc);
+ }
+ }
+}
+
+static inline int get_data_buffer(struct s3cmci_host *host,
+ u32 *bytes, 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];
+
+ *bytes = sg->length;
+ *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;
+}
+
+static inline u32 fifo_free(struct s3cmci_host *host)
+{
+ u32 fifostat = readl(host->base + S3C2410_SDIFSTA);
+
+ fifostat &= S3C2410_SDIFSTA_COUNTMASK;
+ return 63 - fifostat;
+}
+
+/**
+ * s3cmci_enable_irq - enable IRQ, after having disabled it.
+ * @host: The device state.
+ * @more: True if more IRQs are expected from transfer.
+ *
+ * Enable the main IRQ if needed after it has been disabled.
+ *
+ * The IRQ can be one of the following states:
+ * - disabled during IDLE
+ * - disabled whilst processing data
+ * - enabled during transfer
+ * - enabled whilst awaiting SDIO interrupt detection
+ */
+static void s3cmci_enable_irq(struct s3cmci_host *host, bool more)
+{
+ unsigned long flags;
+ bool enable = false;
+
+ local_irq_save(flags);
+
+ host->irq_enabled = more;
+ host->irq_disabled = false;
+
+ enable = more | host->sdio_irqen;
+
+ if (host->irq_state != enable) {
+ host->irq_state = enable;
+
+ if (enable)
+ enable_irq(host->irq);
+ else
+ disable_irq(host->irq);
+ }
+
+ local_irq_restore(flags);
+}
+
+/**
+ *
+ */
+static void s3cmci_disable_irq(struct s3cmci_host *host, bool transfer)
+{
+ unsigned long flags;
+
+ local_irq_save(flags);
+
+ //printk(KERN_DEBUG "%s: transfer %d\n", __func__, transfer);
+
+ host->irq_disabled = transfer;
+
+ if (transfer && host->irq_state) {
+ host->irq_state = false;
+ disable_irq(host->irq);
+ }
+
+ local_irq_restore(flags);
+}
+
+static void do_pio_read(struct s3cmci_host *host)
+{
+ int res;
+ u32 fifo;
+ u32 *ptr;
+ u32 fifo_words;
+ 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_bytes) {
+ res = get_data_buffer(host, &host->pio_bytes,
+ &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_bytes, host->pio_ptr);
+ }
+
+ dbg(host, dbg_pio,
+ "pio_read(): fifo:[%02i] buffer:[%03i] dcnt:[%08X]\n",
+ fifo, host->pio_bytes,
+ readl(host->base + S3C2410_SDIDCNT));
+
+ /* If we have reached the end of the block, we can
+ * read a word and get 1 to 3 bytes. If we in the
+ * middle of the block, we have to read full words,
+ * otherwise we will write garbage, so round down to
+ * an even multiple of 4. */
+ if (fifo >= host->pio_bytes)
+ fifo = host->pio_bytes;
+ else
+ fifo -= fifo & 3;
+
+ host->pio_bytes -= fifo;
+ host->pio_count += fifo;
+
+ fifo_words = fifo >> 2;
+ ptr = host->pio_ptr;
+ while (fifo_words--)
+ *ptr++ = readl(from_ptr);
+ host->pio_ptr = ptr;
+
+ if (fifo & 3) {
+ u32 n = fifo & 3;
+ u32 data = readl(from_ptr);
+ u8 *p = (u8 *)host->pio_ptr;
+
+ while (n--) {
+ *p++ = data;
+ data >>= 8;
+ }
+ }
+ }
+
+ if (!host->pio_bytes) {
+ res = get_data_buffer(host, &host->pio_bytes, &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;
+ u32 *ptr;
+
+ to_ptr = host->base + host->sdidata;
+
+ while ((fifo = fifo_free(host)) > 3) {
+ if (!host->pio_bytes) {
+ res = get_data_buffer(host, &host->pio_bytes,
+ &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_bytes, host->pio_ptr);
+
+ }
+
+ /* If we have reached the end of the block, we have to
+ * write exactly the remaining number of bytes. If we
+ * in the middle of the block, we have to write full
+ * words, so round down to an even multiple of 4. */
+ if (fifo >= host->pio_bytes)
+ fifo = host->pio_bytes;
+ else
+ fifo -= fifo & 3;
+
+ host->pio_bytes -= fifo;
+ host->pio_count += fifo;
+
+ fifo = (fifo + 3) >> 2;
+ ptr = host->pio_ptr;
+ while (fifo--)
+ writel(*ptr++, to_ptr);
+ host->pio_ptr = ptr;
+ }
+
+ enable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF);
+}
+
+static void pio_tasklet(unsigned long data)
+{
+ struct s3cmci_host *host = (struct s3cmci_host *) data;
+
+ s3cmci_disable_irq(host, true);
+
+ 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_bytes:[%u]\n",
+ (host->pio_active == XFER_READ) ? "read" : "write",
+ host->pio_count, host->pio_bytes);
+
+ if (host->mrq->data)
+ host->mrq->data->error = -EINVAL;
+ }
+
+ s3cmci_enable_irq(host, false);
+ finalize_request(host);
+ } else
+ s3cmci_enable_irq(host, true);
+}
+
+/*
+ * 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 = 0, mci_dclear;
+ unsigned long iflags;
+
+ mci_dsta = readl(host->base + S3C2410_SDIDSTA);
+ mci_imsk = readl(host->base + host->sdiimsk);
+
+ if (mci_dsta & S3C2410_SDIDSTA_SDIOIRQDETECT) {
+ if (mci_imsk & S3C2410_SDIIMSK_SDIOIRQ) {
+ mci_dclear = S3C2410_SDIDSTA_SDIOIRQDETECT;
+ writel(mci_dclear, host->base + S3C2410_SDIDSTA);
+
+ mmc_signal_sdio_irq(host->mmc);
+ return IRQ_HANDLED;
+ }
+ }
+
+ spin_lock_irqsave(&host->complete_lock, iflags);
+
+ mci_csta = readl(host->base + S3C2410_SDICMDSTAT);
+ mci_dcnt = readl(host->base + S3C2410_SDIDCNT);
+ mci_fsta = readl(host->base + S3C2410_SDIFSTA);
+ 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 (!s3cmci_host_usedma(host)) {
+ 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;
+}
+
+static 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->dma_complete = 1;
+ 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;
+ clear_imask(host);
+
+ goto out;
+}
+
+static void finalize_request(struct s3cmci_host *host)
+{
+ struct mmc_request *mrq = host->mrq;
+ struct mmc_command *cmd;
+ int debug_as_failure = 0;
+
+ if (host->complete_what != COMPLETION_FINALIZE)
+ return;
+
+ if (!mrq)
+ return;
+ cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd;
+
+ if (cmd->data && (cmd->error == 0) &&
+ (cmd->data->error == 0)) {
+ if (s3cmci_host_usedma(host) && (!host->dma_complete)) {
+ dbg(host, dbg_dma, "DMA Missing (%d)!\n",
+ host->dma_complete);
+ 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);
+ clear_imask(host);
+
+ 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;
+
+ /* Calculate 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 transferring data we flush the
+ * DMA channel and the fifo to clear out any garbage. */
+ if (mrq->data->error != 0) {
+ if (s3cmci_host_usedma(host))
+ 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;
+
+ s3cmci_check_sdio_irq(host);
+ mmc_request_done(host->mmc, mrq);
+}
+
+static 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,
+ host->mem->start + host->sdidata);
+
+ if (!setup_ok) {
+ s3c2410_dma_config(host->dma, 4);
+ 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 transferred. */
+
+ if (data->blocks > 1) {
+ pr_warning("%s: can't do non-word sized block transfers (blksz %d)\n", __func__, data->blksz);
+ 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 (s3cmci_host_usedma(host))
+ 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_bytes = 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;
+
+ 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: %08x@%u\n", i,
+ sg_dma_address(&data->sg[i]),
+ sg_dma_len(&data->sg[i]));
+
+ res = s3c2410_dma_enqueue(host->dma, 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 (s3cmci_host_usedma(host))
+ 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 */
+ s3cmci_enable_irq(host, true);
+}
+
+static int s3cmci_card_present(struct mmc_host *mmc)
+{
+ struct s3cmci_host *host = mmc_priv(mmc);
+ struct s3c24xx_mci_pdata *pdata = host->pdata;
+ int ret;
+
+ if (pdata->no_detect)
+ return -ENOSYS;
+
+ ret = gpio_get_value(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(mmc) == 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_clk(struct s3cmci_host *host, struct mmc_ios *ios)
+{
+ u32 mci_psc;
+
+ /* 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;
+}
+
+static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct s3cmci_host *host = mmc_priv(mmc);
+ u32 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_GPE(5), S3C2410_GPE5_SDCLK);
+ s3c2410_gpio_cfgpin(S3C2410_GPE(6), S3C2410_GPE6_SDCMD);
+ s3c2410_gpio_cfgpin(S3C2410_GPE(7), S3C2410_GPE7_SDDAT0);
+ s3c2410_gpio_cfgpin(S3C2410_GPE(8), S3C2410_GPE8_SDDAT1);
+ s3c2410_gpio_cfgpin(S3C2410_GPE(9), S3C2410_GPE9_SDDAT2);
+ s3c2410_gpio_cfgpin(S3C2410_GPE(10), 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:
+ gpio_direction_output(S3C2410_GPE(5), 0);
+
+ if (host->is2440)
+ mci_con |= S3C2440_SDICON_SDRESET;
+
+ if (host->pdata->set_power)
+ host->pdata->set_power(ios->power_mode, ios->vdd);
+
+ break;
+ }
+
+ s3cmci_set_clk(host, ios);
+
+ /* 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->no_wprotect)
+ return 0;
+
+ ret = gpio_get_value(pdata->gpio_wprotect) ? 1 : 0;
+ ret ^= pdata->wprotect_invert;
+
+ return ret;
+}
+
+static void s3cmci_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+ struct s3cmci_host *host = mmc_priv(mmc);
+ unsigned long flags;
+ u32 con;
+
+ local_irq_save(flags);
+
+ con = readl(host->base + S3C2410_SDICON);
+ host->sdio_irqen = enable;
+
+ if (enable == host->sdio_irqen)
+ goto same_state;
+
+ if (enable) {
+ con |= S3C2410_SDICON_SDIOIRQ;
+ enable_imask(host, S3C2410_SDIIMSK_SDIOIRQ);
+
+ if (!host->irq_state && !host->irq_disabled) {
+ host->irq_state = true;
+ enable_irq(host->irq);
+ }
+ } else {
+ disable_imask(host, S3C2410_SDIIMSK_SDIOIRQ);
+ con &= ~S3C2410_SDICON_SDIOIRQ;
+
+ if (!host->irq_enabled && host->irq_state) {
+ disable_irq_nosync(host->irq);
+ host->irq_state = false;
+ }
+ }
+
+ writel(con, host->base + S3C2410_SDICON);
+
+ same_state:
+ local_irq_restore(flags);
+
+ s3cmci_check_sdio_irq(host);
+}
+
+static struct mmc_host_ops s3cmci_ops = {
+ .request = s3cmci_request,
+ .set_ios = s3cmci_set_ios,
+ .get_ro = s3cmci_get_ro,
+ .get_cd = s3cmci_card_present,
+ .enable_sdio_irq = s3cmci_enable_sdio_irq,
+};
+
+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 reasonable defaults are picked. */
+ .no_wprotect = 1,
+ .no_detect = 1,
+};
+
+#ifdef CONFIG_CPU_FREQ
+
+static int s3cmci_cpufreq_transition(struct notifier_block *nb,
+ unsigned long val, void *data)
+{
+ struct s3cmci_host *host;
+ struct mmc_host *mmc;
+ unsigned long newclk;
+ unsigned long flags;
+
+ host = container_of(nb, struct s3cmci_host, freq_transition);
+ newclk = clk_get_rate(host->clk);
+ mmc = host->mmc;
+
+ if ((val == CPUFREQ_PRECHANGE && newclk > host->clk_rate) ||
+ (val == CPUFREQ_POSTCHANGE && newclk < host->clk_rate)) {
+ spin_lock_irqsave(&mmc->lock, flags);
+
+ host->clk_rate = newclk;
+
+ if (mmc->ios.power_mode != MMC_POWER_OFF &&
+ mmc->ios.clock != 0)
+ s3cmci_set_clk(host, &mmc->ios);
+
+ spin_unlock_irqrestore(&mmc->lock, flags);
+ }
+
+ return 0;
+}
+
+static inline int s3cmci_cpufreq_register(struct s3cmci_host *host)
+{
+ host->freq_transition.notifier_call = s3cmci_cpufreq_transition;
+
+ return cpufreq_register_notifier(&host->freq_transition,
+ CPUFREQ_TRANSITION_NOTIFIER);
+}
+
+static inline void s3cmci_cpufreq_deregister(struct s3cmci_host *host)
+{
+ cpufreq_unregister_notifier(&host->freq_transition,
+ CPUFREQ_TRANSITION_NOTIFIER);
+}
+
+#else
+static inline int s3cmci_cpufreq_register(struct s3cmci_host *host)
+{
+ return 0;
+}
+
+static inline void s3cmci_cpufreq_deregister(struct s3cmci_host *host)
+{
+}
+#endif
+
+
+#ifdef CONFIG_DEBUG_FS
+
+static int s3cmci_state_show(struct seq_file *seq, void *v)
+{
+ struct s3cmci_host *host = seq->private;
+
+ seq_printf(seq, "Register base = 0x%08x\n", (u32)host->base);
+ seq_printf(seq, "Clock rate = %ld\n", host->clk_rate);
+ seq_printf(seq, "Prescale = %d\n", host->prescaler);
+ seq_printf(seq, "is2440 = %d\n", host->is2440);
+ seq_printf(seq, "IRQ = %d\n", host->irq);
+ seq_printf(seq, "IRQ enabled = %d\n", host->irq_enabled);
+ seq_printf(seq, "IRQ disabled = %d\n", host->irq_disabled);
+ seq_printf(seq, "IRQ state = %d\n", host->irq_state);
+ seq_printf(seq, "CD IRQ = %d\n", host->irq_cd);
+ seq_printf(seq, "Do DMA = %d\n", s3cmci_host_usedma(host));
+ seq_printf(seq, "SDIIMSK at %d\n", host->sdiimsk);
+ seq_printf(seq, "SDIDATA at %d\n", host->sdidata);
+
+ return 0;
+}
+
+static int s3cmci_state_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, s3cmci_state_show, inode->i_private);
+}
+
+static const struct file_operations s3cmci_fops_state = {
+ .owner = THIS_MODULE,
+ .open = s3cmci_state_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+#define DBG_REG(_r) { .addr = S3C2410_SDI##_r, .name = #_r }
+
+struct s3cmci_reg {
+ unsigned short addr;
+ unsigned char *name;
+} debug_regs[] = {
+ DBG_REG(CON),
+ DBG_REG(PRE),
+ DBG_REG(CMDARG),
+ DBG_REG(CMDCON),
+ DBG_REG(CMDSTAT),
+ DBG_REG(RSP0),
+ DBG_REG(RSP1),
+ DBG_REG(RSP2),
+ DBG_REG(RSP3),
+ DBG_REG(TIMER),
+ DBG_REG(BSIZE),
+ DBG_REG(DCON),
+ DBG_REG(DCNT),
+ DBG_REG(DSTA),
+ DBG_REG(FSTA),
+ {}
+};
+
+static int s3cmci_regs_show(struct seq_file *seq, void *v)
+{
+ struct s3cmci_host *host = seq->private;
+ struct s3cmci_reg *rptr = debug_regs;
+
+ for (; rptr->name; rptr++)
+ seq_printf(seq, "SDI%s\t=0x%08x\n", rptr->name,
+ readl(host->base + rptr->addr));
+
+ seq_printf(seq, "SDIIMSK\t=0x%08x\n", readl(host->base + host->sdiimsk));
+
+ return 0;
+}
+
+static int s3cmci_regs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, s3cmci_regs_show, inode->i_private);
+}
+
+static const struct file_operations s3cmci_fops_regs = {
+ .owner = THIS_MODULE,
+ .open = s3cmci_regs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void s3cmci_debugfs_attach(struct s3cmci_host *host)
+{
+ struct device *dev = &host->pdev->dev;
+
+ host->debug_root = debugfs_create_dir(dev_name(dev), NULL);
+ if (IS_ERR(host->debug_root)) {
+ dev_err(dev, "failed to create debugfs root\n");
+ return;
+ }
+
+ host->debug_state = debugfs_create_file("state", 0444,
+ host->debug_root, host,
+ &s3cmci_fops_state);
+
+ if (IS_ERR(host->debug_state))
+ dev_err(dev, "failed to create debug state file\n");
+
+ host->debug_regs = debugfs_create_file("regs", 0444,
+ host->debug_root, host,
+ &s3cmci_fops_regs);
+
+ if (IS_ERR(host->debug_regs))
+ dev_err(dev, "failed to create debug regs file\n");
+}
+
+static void s3cmci_debugfs_remove(struct s3cmci_host *host)
+{
+ debugfs_remove(host->debug_regs);
+ debugfs_remove(host->debug_state);
+ debugfs_remove(host->debug_root);
+}
+
+#else
+static inline void s3cmci_debugfs_attach(struct s3cmci_host *host) { }
+static inline void s3cmci_debugfs_remove(struct s3cmci_host *host) { }
+
+#endif /* CONFIG_DEBUG_FS */
+
+static int __devinit s3cmci_probe(struct platform_device *pdev)
+{
+ struct s3cmci_host *host;
+ struct mmc_host *mmc;
+ int ret;
+ int is2440;
+ int i;
+
+ is2440 = platform_get_device_id(pdev)->driver_data;
+
+ mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev);
+ if (!mmc) {
+ ret = -ENOMEM;
+ goto probe_out;
+ }
+
+ for (i = S3C2410_GPE(5); i <= S3C2410_GPE(10); i++) {
+ ret = gpio_request(i, dev_name(&pdev->dev));
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get gpio %d\n", i);
+
+ for (i--; i >= S3C2410_GPE(5); i--)
+ gpio_free(i);
+
+ goto probe_free_host;
+ }
+ }
+
+ 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->complete_what = COMPLETION_NONE;
+ host->pio_active = XFER_NONE;
+
+#ifdef CONFIG_MMC_S3C_PIODMA
+ host->dodma = host->pdata->use_dma;
+#endif
+
+ 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_gpio;
+ }
+
+ host->mem = request_mem_region(host->mem->start,
+ resource_size(host->mem), pdev->name);
+
+ if (!host->mem) {
+ dev_err(&pdev->dev, "failed to request io memory region.\n");
+ ret = -ENOENT;
+ goto probe_free_gpio;
+ }
+
+ host->base = ioremap(host->mem->start, resource_size(host->mem));
+ if (!host->base) {
+ 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_state = false;
+
+ if (!host->pdata->no_detect) {
+ ret = gpio_request(host->pdata->gpio_detect, "s3cmci detect");
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get detect gpio\n");
+ goto probe_free_irq;
+ }
+
+ host->irq_cd = gpio_to_irq(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_gpio_cd;
+ }
+ } else {
+ dev_warn(&pdev->dev,
+ "host detect has no irq available\n");
+ gpio_direction_input(host->pdata->gpio_detect);
+ }
+ } else
+ host->irq_cd = -1;
+
+ if (!host->pdata->no_wprotect) {
+ ret = gpio_request(host->pdata->gpio_wprotect, "s3cmci wp");
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get writeprotect\n");
+ goto probe_free_irq_cd;
+ }
+
+ gpio_direction_input(host->pdata->gpio_wprotect);
+ }
+
+ /* depending on the dma state, get a dma channel to use. */
+
+ if (s3cmci_host_usedma(host)) {
+ host->dma = s3c2410_dma_request(DMACH_SDI, &s3cmci_dma_client,
+ host);
+ if (host->dma < 0) {
+ dev_err(&pdev->dev, "cannot get DMA channel.\n");
+ if (!s3cmci_host_canpio()) {
+ ret = -EBUSY;
+ goto probe_free_gpio_wp;
+ } else {
+ dev_warn(&pdev->dev, "falling back to PIO.\n");
+ host->dodma = 0;
+ }
+ }
+ }
+
+ 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_dma;
+ }
+
+ 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;
+#ifdef CONFIG_MMC_S3C_HW_SDIO_IRQ
+ mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;
+#else
+ mmc->caps = MMC_CAP_4_BIT_DATA;
+#endif
+ 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_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 = s3cmci_cpufreq_register(host);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register cpufreq\n");
+ goto free_dmabuf;
+ }
+
+ ret = mmc_add_host(mmc);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add mmc host.\n");
+ goto free_cpufreq;
+ }
+
+ s3cmci_debugfs_attach(host);
+
+ platform_set_drvdata(pdev, mmc);
+ dev_info(&pdev->dev, "%s - using %s, %s SDIO IRQ\n", mmc_hostname(mmc),
+ s3cmci_host_usedma(host) ? "dma" : "pio",
+ mmc->caps & MMC_CAP_SDIO_IRQ ? "hw" : "sw");
+
+ return 0;
+
+ free_cpufreq:
+ s3cmci_cpufreq_deregister(host);
+
+ free_dmabuf:
+ clk_disable(host->clk);
+
+ clk_free:
+ clk_put(host->clk);
+
+ probe_free_dma:
+ if (s3cmci_host_usedma(host))
+ s3c2410_dma_free(host->dma, &s3cmci_dma_client);
+
+ probe_free_gpio_wp:
+ if (!host->pdata->no_wprotect)
+ gpio_free(host->pdata->gpio_wprotect);
+
+ probe_free_gpio_cd:
+ if (!host->pdata->no_detect)
+ gpio_free(host->pdata->gpio_detect);
+
+ 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, resource_size(host->mem));
+
+ probe_free_gpio:
+ for (i = S3C2410_GPE(5); i <= S3C2410_GPE(10); i++)
+ gpio_free(i);
+
+ probe_free_host:
+ mmc_free_host(mmc);
+
+ probe_out:
+ return ret;
+}
+
+static void s3cmci_shutdown(struct platform_device *pdev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(pdev);
+ struct s3cmci_host *host = mmc_priv(mmc);
+
+ if (host->irq_cd >= 0)
+ free_irq(host->irq_cd, host);
+
+ s3cmci_debugfs_remove(host);
+ s3cmci_cpufreq_deregister(host);
+ mmc_remove_host(mmc);
+ clk_disable(host->clk);
+}
+
+static int __devexit s3cmci_remove(struct platform_device *pdev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(pdev);
+ struct s3cmci_host *host = mmc_priv(mmc);
+ struct s3c24xx_mci_pdata *pd = host->pdata;
+ int i;
+
+ s3cmci_shutdown(pdev);
+
+ clk_put(host->clk);
+
+ tasklet_disable(&host->pio_tasklet);
+
+ if (s3cmci_host_usedma(host))
+ s3c2410_dma_free(host->dma, &s3cmci_dma_client);
+
+ free_irq(host->irq, host);
+
+ if (!pd->no_wprotect)
+ gpio_free(pd->gpio_wprotect);
+
+ if (!pd->no_detect)
+ gpio_free(pd->gpio_detect);
+
+ for (i = S3C2410_GPE(5); i <= S3C2410_GPE(10); i++)
+ gpio_free(i);
+
+
+ iounmap(host->base);
+ release_mem_region(host->mem->start, resource_size(host->mem));
+
+ mmc_free_host(mmc);
+ return 0;
+}
+
+static struct platform_device_id s3cmci_driver_ids[] = {
+ {
+ .name = "s3c2410-sdi",
+ .driver_data = 0,
+ }, {
+ .name = "s3c2412-sdi",
+ .driver_data = 1,
+ }, {
+ .name = "s3c2440-sdi",
+ .driver_data = 1,
+ },
+ { }
+};
+
+MODULE_DEVICE_TABLE(platform, s3cmci_driver_ids);
+
+
+#ifdef CONFIG_PM
+
+static int s3cmci_suspend(struct device *dev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(to_platform_device(dev));
+
+ return mmc_suspend_host(mmc);
+}
+
+static int s3cmci_resume(struct device *dev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(to_platform_device(dev));
+
+ return mmc_resume_host(mmc);
+}
+
+static const struct dev_pm_ops s3cmci_pm = {
+ .suspend = s3cmci_suspend,
+ .resume = s3cmci_resume,
+};
+
+#define s3cmci_pm_ops &s3cmci_pm
+#else /* CONFIG_PM */
+#define s3cmci_pm_ops NULL
+#endif /* CONFIG_PM */
+
+
+static struct platform_driver s3cmci_driver = {
+ .driver = {
+ .name = "s3c-sdi",
+ .owner = THIS_MODULE,
+ .pm = s3cmci_pm_ops,
+ },
+ .id_table = s3cmci_driver_ids,
+ .probe = s3cmci_probe,
+ .remove = __devexit_p(s3cmci_remove),
+ .shutdown = s3cmci_shutdown,
+};
+
+static int __init s3cmci_init(void)
+{
+ return platform_driver_register(&s3cmci_driver);
+}
+
+static void __exit s3cmci_exit(void)
+{
+ platform_driver_unregister(&s3cmci_driver);
+}
+
+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>, Ben Dooks <ben-linux@fluff.org>");
diff --git a/drivers/mmc/host/s3cmci.h b/drivers/mmc/host/s3cmci.h
new file mode 100644
index 00000000..c76b53db
--- /dev/null
+++ b/drivers/mmc/host/s3cmci.h
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+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;
+
+ bool irq_disabled;
+ bool irq_enabled;
+ bool irq_state;
+ int sdio_irqen;
+
+ 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_bytes;
+ 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;
+
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *debug_root;
+ struct dentry *debug_state;
+ struct dentry *debug_regs;
+#endif
+
+#ifdef CONFIG_CPU_FREQ
+ struct notifier_block freq_transition;
+#endif
+};
diff --git a/drivers/mmc/host/sdhci-cns3xxx.c b/drivers/mmc/host/sdhci-cns3xxx.c
new file mode 100644
index 00000000..9ebd1d77
--- /dev/null
+++ b/drivers/mmc/host/sdhci-cns3xxx.c
@@ -0,0 +1,97 @@
+/*
+ * SDHCI support for CNS3xxx SoC
+ *
+ * Copyright 2008 Cavium Networks
+ * Copyright 2010 MontaVista Software, LLC.
+ *
+ * Authors: Scott Shu
+ * Anton Vorontsov <avorontsov@mvista.com>
+ *
+ * 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/delay.h>
+#include <linux/device.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sdhci-pltfm.h>
+#include <mach/cns3xxx.h>
+#include "sdhci.h"
+#include "sdhci-pltfm.h"
+
+static unsigned int sdhci_cns3xxx_get_max_clk(struct sdhci_host *host)
+{
+ return 150000000;
+}
+
+static void sdhci_cns3xxx_set_clock(struct sdhci_host *host, unsigned int clock)
+{
+ struct device *dev = mmc_dev(host->mmc);
+ int div = 1;
+ u16 clk;
+ unsigned long timeout;
+
+ if (clock == host->clock)
+ return;
+
+ sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL);
+
+ if (clock == 0)
+ goto out;
+
+ while (host->max_clk / div > clock) {
+ /*
+ * On CNS3xxx divider grows linearly up to 4, and then
+ * exponentially up to 256.
+ */
+ if (div < 4)
+ div += 1;
+ else if (div < 256)
+ div *= 2;
+ else
+ break;
+ }
+
+ dev_dbg(dev, "desired SD clock: %d, actual: %d\n",
+ clock, host->max_clk / div);
+
+ /* Divide by 3 is special. */
+ if (div != 3)
+ div >>= 1;
+
+ clk = div << SDHCI_DIVIDER_SHIFT;
+ clk |= SDHCI_CLOCK_INT_EN;
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
+
+ timeout = 20;
+ while (!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL))
+ & SDHCI_CLOCK_INT_STABLE)) {
+ if (timeout == 0) {
+ dev_warn(dev, "clock is unstable");
+ break;
+ }
+ timeout--;
+ mdelay(1);
+ }
+
+ clk |= SDHCI_CLOCK_CARD_EN;
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
+out:
+ host->clock = clock;
+}
+
+static struct sdhci_ops sdhci_cns3xxx_ops = {
+ .get_max_clock = sdhci_cns3xxx_get_max_clk,
+ .set_clock = sdhci_cns3xxx_set_clock,
+};
+
+struct sdhci_pltfm_data sdhci_cns3xxx_pdata = {
+ .ops = &sdhci_cns3xxx_ops,
+ .quirks = SDHCI_QUIRK_BROKEN_DMA |
+ SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK |
+ SDHCI_QUIRK_INVERTED_WRITE_PROTECT |
+ SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
+ SDHCI_QUIRK_BROKEN_TIMEOUT_VAL |
+ SDHCI_QUIRK_NONSTANDARD_CLOCK,
+};
diff --git a/drivers/mmc/host/sdhci-dove.c b/drivers/mmc/host/sdhci-dove.c
new file mode 100644
index 00000000..2aeef4ff
--- /dev/null
+++ b/drivers/mmc/host/sdhci-dove.c
@@ -0,0 +1,70 @@
+/*
+ * sdhci-dove.c Support for SDHCI on Marvell's Dove SoC
+ *
+ * Author: Saeed Bishara <saeed@marvell.com>
+ * Mike Rapoport <mike@compulab.co.il>
+ * Based on sdhci-cns3xxx.c
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/io.h>
+#include <linux/mmc/host.h>
+
+#include "sdhci.h"
+#include "sdhci-pltfm.h"
+
+static u16 sdhci_dove_readw(struct sdhci_host *host, int reg)
+{
+ u16 ret;
+
+ switch (reg) {
+ case SDHCI_HOST_VERSION:
+ case SDHCI_SLOT_INT_STATUS:
+ /* those registers don't exist */
+ return 0;
+ default:
+ ret = readw(host->ioaddr + reg);
+ }
+ return ret;
+}
+
+static u32 sdhci_dove_readl(struct sdhci_host *host, int reg)
+{
+ u32 ret;
+
+ switch (reg) {
+ case SDHCI_CAPABILITIES:
+ ret = readl(host->ioaddr + reg);
+ /* Mask the support for 3.0V */
+ ret &= ~SDHCI_CAN_VDD_300;
+ break;
+ default:
+ ret = readl(host->ioaddr + reg);
+ }
+ return ret;
+}
+
+static struct sdhci_ops sdhci_dove_ops = {
+ .read_w = sdhci_dove_readw,
+ .read_l = sdhci_dove_readl,
+};
+
+struct sdhci_pltfm_data sdhci_dove_pdata = {
+ .ops = &sdhci_dove_ops,
+ .quirks = SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER |
+ SDHCI_QUIRK_NO_BUSY_IRQ |
+ SDHCI_QUIRK_BROKEN_TIMEOUT_VAL |
+ SDHCI_QUIRK_FORCE_DMA,
+};
diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c
new file mode 100644
index 00000000..5c1882b8
--- /dev/null
+++ b/drivers/mmc/host/sdhci-esdhc-imx.c
@@ -0,0 +1,988 @@
+/*
+ * Freescale eSDHC i.MX controller driver for the platform bus.
+ *
+ * derived from the OF-version.
+ *
+ * Copyright (c) 2010 Pengutronix e.K.
+ * Author: Wolfram Sang <w.sang@pengutronix.de>
+ *
+ * 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.
+ */
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sdhci-pltfm.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/sd.h>
+#include <linux/scatterlist.h>
+#include <mach/hardware.h>
+#include <mach/esdhc.h>
+#include "sdhci.h"
+#include "sdhci-pltfm.h"
+#include "sdhci-esdhc.h"
+
+/* VENDOR SPEC register */
+#define SDHCI_VENDOR_SPEC 0xC0
+#define SDHCI_VENDOR_SPEC_SDIO_QUIRK 0x00000002
+
+#define SDHCI_MIX_CTRL_AC23EN (1 << 7)
+#define SDHCI_MIX_CTRL_EXE_TUNE (1 << 22)
+#define SDHCI_MIX_CTRL_SMPCLK_SEL (1 << 23)
+#define SDHCI_MIX_CTRL_AUTO_TUNE (1 << 24)
+#define SDHCI_MIX_CTRL_FBCLK_SEL (1 << 25)
+
+#define SDHCI_SYS_CTRL 0x2C
+#define SDHCI_SYS_CTRL_RSTA_LSH 24
+
+#define SDHCI_DLL_CTRL 0x60
+#define SDHCI_DLL_OVERRIDE_OFFSET 0x9
+#define SDHCI_DLL_OVERRIDE_EN_OFFSET 0x8
+
+#define SDHCI_TUNE_CTRL_STATUS 0x68
+#define SDHCI_TUNE_CTRL_STEP 0x1
+#define SDHCI_TUNE_CTRL_MIN 0x0
+#define SDHCI_TUNE_CTRL_MAX ((1 << 7) - 1)
+
+#define SDHCI_VENDOR_SPEC_VSELECT (1 << 1)
+#define SDHCI_VENDOR_SPEC_FRC_SDCLK_ON (1 << 8)
+
+#define SDHCI_PRESENT_STATE_CLSL (1 << 23)
+#define SDHCI_PRESENT_STATE_DLSL_L4 (0xF << 24)
+#define SDHCI_PRESENT_STATE_DLSL_H4 (0xF << 28)
+
+#define ESDHC_FLAG_GPIO_FOR_CD_WP (1 << 0)
+
+#define SDHCI_CTRL_D3CD 0x08
+
+#define SDHCI_PROT_CTRL_DMASEL_MASK (3 << 8)
+#define SDHCI_PROT_CTRL_DTW (3 << 1)
+#define SDHCI_PROT_CTRL_8BIT (2 << 1)
+#define SDHCI_PROT_CTRL_4BIT (1 << 1)
+#define SDHCI_PROT_CTRL_1BIT (0 << 1)
+#define SDHCI_PROT_CTRL_LCTL (1 << 0)
+
+/* SDHCI_FSL_SVN_300 only for mx6q and mx6dl */
+#define SDHCI_FSL_SVN_300 0x3
+
+#define SDHCI_TUNE_CTRL 0xCC
+#define SDHCI_TUNE_CTRL_STD_TUNING_EN (1 << 24)
+
+#define SDHCI_HOST_CAP_UHS_MODE_MASK 0x7
+
+#define SDHCI_TUNING_BLOCK_PATTERN_LEN 64
+/*
+ * There is an INT DMA ERR mis-match between eSDHC and STD SDHC SPEC:
+ * Bit25 is used in STD SPEC, and is reserved in fsl eSDHC design,
+ * but bit28 is used as the INT DMA ERR in fsl eSDHC design.
+ * Define this macro DMA error INT for fsl eSDHC
+ */
+#define SDHCI_INT_VENDOR_SPEC_DMA_ERR 0x10000000
+
+/*
+ * The CMDTYPE of the CMD register (offset 0xE) should be set to
+ * "11" when the STOP CMD12 is issued on imx53 to abort one
+ * open ended multi-blk IO. Otherwise the TC INT wouldn't
+ * be generated.
+ * In exact block transfer, the controller doesn't complete the
+ * operations automatically as required at the end of the
+ * transfer and remains on hold if the abort command is not sent.
+ * As a result, the TC flag is not asserted and SW received timeout
+ * exeception. Bit1 of Vendor Spec registor is used to fix it.
+ */
+#define ESDHC_FLAG_MULTIBLK_NO_INT (1 << 1)
+
+static void esdhc_prepare_tuning(struct sdhci_host *host, u32 val);
+static void esdhc_post_tuning(struct sdhci_host *host);
+
+struct pltfm_imx_data {
+ int flags;
+ u32 scratchpad;
+ /* uhs mode for sdhc host control2 */
+ unsigned char uhs_mode;
+};
+
+static void request_done(struct mmc_request *mrq)
+{
+ complete(&mrq->completion);
+}
+
+static int esdhc_send_tuning_cmd(struct sdhci_host *host)
+{
+ struct mmc_command cmd = {0};
+ struct mmc_request mrq = {0};
+ struct mmc_data data = {0};
+ struct scatterlist sg;
+ char tuning_pattern[SDHCI_TUNING_BLOCK_PATTERN_LEN];
+
+ cmd.opcode = MMC_SEND_TUNING_BLOCK;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
+
+ data.blksz = SDHCI_TUNING_BLOCK_PATTERN_LEN;
+ data.blocks = 1;
+ data.flags = MMC_DATA_READ;
+ data.sg = &sg;
+ data.sg_len = 1;
+
+ sg_init_one(&sg, tuning_pattern, sizeof(tuning_pattern));
+
+ mrq.cmd = &cmd;
+ mrq.cmd->mrq = &mrq;
+ mrq.data = &data;
+ mrq.data->mrq = &mrq;
+ mrq.cmd->data = mrq.data;
+
+ mrq.done = request_done;
+
+ init_completion(&(mrq.completion));
+ sdhci_request(host->mmc, &mrq);
+ wait_for_completion(&(mrq.completion));
+
+ if (cmd.error)
+ return cmd.error;
+ if (data.error)
+ return data.error;
+
+ return 0;
+}
+
+static int esdhc_execute_tuning(struct sdhci_host *host)
+{
+ int min, max, avg;
+
+ min = host->tuning_min;
+ while (min < host->tuning_max) {
+ esdhc_prepare_tuning(host, min);
+ if (!esdhc_send_tuning_cmd(host))
+ break;
+ min += host->tuning_step;
+ }
+
+ max = min + host->tuning_step;
+ while (max < host->tuning_max) {
+ esdhc_prepare_tuning(host, max);
+ if (esdhc_send_tuning_cmd(host)) {
+ max -= host->tuning_step;
+ break;
+ }
+ max += host->tuning_step;
+ }
+
+ avg = (min + max) / 2;
+ esdhc_prepare_tuning(host, avg);
+ esdhc_send_tuning_cmd(host);
+ esdhc_post_tuning(host);
+ return 0;
+}
+
+static inline void esdhc_clrset_le(struct sdhci_host *host, u32 mask, u32 val, int reg)
+{
+ void __iomem *base = host->ioaddr + (reg & ~0x3);
+ u32 shift = (reg & 0x3) * 8;
+
+ writel(((readl(base) & ~(mask << shift)) | (val << shift)), base);
+}
+
+static u32 esdhc_readl_le(struct sdhci_host *host, int reg)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct pltfm_imx_data *imx_data = pltfm_host->priv;
+ struct esdhc_platform_data *boarddata
+ = host->mmc->parent->platform_data;
+
+ /* fake CARD_PRESENT flag on mx25/35 */
+ u32 val = readl(host->ioaddr + reg);
+
+ /*
+ * mx6q: SDHCI_PRESENT_STATE bit 16, CINST is not functional on SD3.
+ * So move the section up, and check GPIO for card presence again in
+ * the following block.
+ */
+ if (reg == SDHCI_PRESENT_STATE && cpu_is_mx6()) {
+ u32 fsl_prss = readl(host->ioaddr + SDHCI_PRESENT_STATE);
+ /* save the least 20 bits */
+ val |= fsl_prss & 0x000FFFFF;
+ /* move dat[0-3] line signal bits */
+ val |= (fsl_prss & 0x0F000000) >> 4;
+ /* move cmd line signal bits */
+ val |= (fsl_prss & 0x00800000) << 1;
+ }
+
+ if (unlikely(reg == SDHCI_PRESENT_STATE)) {
+ if (boarddata && boarddata->always_present)
+ val |= SDHCI_CARD_PRESENT;
+ else if (imx_data->flags & ESDHC_FLAG_GPIO_FOR_CD_WP) {
+ if (boarddata && gpio_is_valid(boarddata->cd_gpio)
+ && gpio_get_value(boarddata->cd_gpio))
+ /* no card, if a valid gpio says so */
+ val &= ~SDHCI_CARD_PRESENT;
+ else
+ /* in all other cases assume card is present */
+ val |= SDHCI_CARD_PRESENT;
+ }
+ }
+
+ if (reg == SDHCI_INT_STATUS && cpu_is_mx6()
+ && mx6q_revision() == IMX_CHIP_REVISION_1_0) {
+ /*
+ * on mx6q TO1.0, there is low possibility that
+ * DATA END interrupt comes ealier than DMA
+ * END interrupt which is conflict with standard
+ * host controller spec. In this case, read the
+ * status register again will workaround this issue.
+ */
+ if ((val & SDHCI_INT_DATA_END) && \
+ !(val & SDHCI_INT_DMA_END))
+ val = readl(host->ioaddr + reg);
+ } else if (reg == SDHCI_CAPABILITIES_1
+ && (cpu_is_mx6q() || cpu_is_mx6dl())) {
+ /*
+ * on mx6q, no cap_1 available, fake one.
+ */
+ val = SDHCI_SUPPORT_DDR50 | SDHCI_SUPPORT_SDR104 | \
+ SDHCI_SUPPORT_SDR50;
+ } else if (reg == SDHCI_CAPABILITIES_1 && cpu_is_mx6sl()) {
+ val = readl(host->ioaddr + SDHCI_CAPABILITIES) & 0xFFFF;
+ } else if (reg == SDHCI_MAX_CURRENT && cpu_is_mx6()) {
+ /*
+ * on mx6q, no max current available, fake one.
+ */
+ val = 0;
+ val |= 0xFF << SDHCI_MAX_CURRENT_330_SHIFT;
+ val |= 0xFF << SDHCI_MAX_CURRENT_300_SHIFT;
+ val |= 0xFF << SDHCI_MAX_CURRENT_180_SHIFT;
+ }
+
+ if (unlikely(reg == SDHCI_CAPABILITIES)) {
+ /* In FSL esdhc IC module, only bit20 is used to indicate the
+ * ADMA2 capability of esdhc, but this bit is messed up on
+ * some SOCs (e.g. on MX25, MX35 this bit is set, but they
+ * don't actually support ADMA2). So set the BROKEN_ADMA
+ * uirk on MX25/35 platforms.
+ */
+
+ if (val & SDHCI_CAN_DO_ADMA1) {
+ val &= ~SDHCI_CAN_DO_ADMA1;
+ val |= SDHCI_CAN_DO_ADMA2;
+ }
+ }
+
+ if (unlikely(reg == SDHCI_INT_STATUS)) {
+ if (val & SDHCI_INT_VENDOR_SPEC_DMA_ERR) {
+ val &= ~SDHCI_INT_VENDOR_SPEC_DMA_ERR;
+ val |= SDHCI_INT_ADMA_ERROR;
+ }
+ }
+
+ return val;
+}
+
+static void esdhc_writel_le(struct sdhci_host *host, u32 val, int reg)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct pltfm_imx_data *imx_data = pltfm_host->priv;
+ u32 data;
+
+ if (unlikely((reg == SDHCI_INT_ENABLE || reg == SDHCI_SIGNAL_ENABLE))) {
+ if (imx_data->flags & ESDHC_FLAG_GPIO_FOR_CD_WP)
+ /*
+ * these interrupts won't work with a custom
+ * card_detect gpio (only applied to mx25/35)
+ */
+ val &= ~(SDHCI_INT_CARD_REMOVE | \
+ SDHCI_INT_CARD_INSERT);
+
+ if (!(val & SDHCI_INT_CARD_INT) && cpu_is_mx6()
+ && mx6q_revision() == IMX_CHIP_REVISION_1_0)
+ /*
+ * write 1 to clear card interrupt status bit
+ * (only applied to mx6q TO1.0)
+ * uSDHC used for mx6q has such problem which is
+ * not consistant with standard host controller
+ * definition.
+ * eSDHC used for mx25/35/50/51/53 does not have
+ * such problem.
+ */
+ writel(SDHCI_INT_CARD_INT, \
+ host->ioaddr + SDHCI_INT_STATUS);
+
+ if (val & SDHCI_INT_CARD_INT && !cpu_is_mx6()) {
+ /*
+ * clear D3CD bit and set D3CD bit to avoid
+ * losing card interrupt
+ * (applied to all processors except mx6q)
+ * eSDHC controller used for mx25/35/50/51/53
+ * has such issue, so that we need to do following
+ * operation to avoid losing card interrupt.
+ * uSDCH controller used for mx6q and after won't
+ * have such problem.
+ */
+ data = readl(host->ioaddr + SDHCI_HOST_CONTROL);
+ data &= ~SDHCI_CTRL_D3CD;
+ writel(data, host->ioaddr + SDHCI_HOST_CONTROL);
+ data |= SDHCI_CTRL_D3CD;
+ writel(data, host->ioaddr + SDHCI_HOST_CONTROL);
+ }
+ }
+
+ if (unlikely((imx_data->flags & ESDHC_FLAG_MULTIBLK_NO_INT)
+ && (reg == SDHCI_INT_STATUS)
+ && (val & SDHCI_INT_DATA_END))) {
+ u32 v;
+ v = readl(host->ioaddr + SDHCI_VENDOR_SPEC);
+ v &= ~SDHCI_VENDOR_SPEC_SDIO_QUIRK;
+ writel(v, host->ioaddr + SDHCI_VENDOR_SPEC);
+ }
+
+ if (unlikely(reg == SDHCI_INT_ENABLE || reg == SDHCI_SIGNAL_ENABLE)) {
+ if (val & SDHCI_INT_ADMA_ERROR) {
+ val &= ~SDHCI_INT_ADMA_ERROR;
+ val |= SDHCI_INT_VENDOR_SPEC_DMA_ERR;
+ }
+ }
+
+ writel(val, host->ioaddr + reg);
+}
+
+static u16 esdhc_readw_le(struct sdhci_host *host, int reg)
+{
+ u16 ret;
+ u32 val;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct pltfm_imx_data *imx_data = pltfm_host->priv;
+
+ switch (reg) {
+ case SDHCI_HOST_VERSION:
+ reg ^= 2;
+ val = readl(host->ioaddr + reg);
+ if ((cpu_is_mx6q() || cpu_is_mx6dl()) &&
+ ((val & SDHCI_SPEC_VER_MASK) >> SDHCI_SPEC_VER_SHIFT)
+ == SDHCI_FSL_SVN_300) {
+ val &= ~SDHCI_SPEC_VER_MASK;
+ val |= SDHCI_SPEC_300;
+ }
+ ret = 0xFFFF & val;
+ return ret;
+ case SDHCI_HOST_CONTROL2:
+ ret = 0;
+ /* collect bit info from several regs */
+ val = readl(host->ioaddr + SDHCI_VENDOR_SPEC);
+ ret |= (val & SDHCI_VENDOR_SPEC_VSELECT)
+ ? SDHCI_CTRL_VDD_180 : 0;
+
+ if (cpu_is_mx6sl()) {
+ val = readl(host->ioaddr + SDHCI_ACMD12_ERR);
+ ret |= (val & SDHCI_MIX_CTRL_EXE_TUNE)
+ ? SDHCI_CTRL_EXEC_TUNING : 0;
+ ret |= (val & SDHCI_MIX_CTRL_SMPCLK_SEL)
+ ? SDHCI_CTRL_TUNED_CLK : 0;
+ } else {
+ val = readl(host->ioaddr + SDHCI_MIX_CTRL);
+ ret |= (val & SDHCI_MIX_CTRL_EXE_TUNE)
+ ? SDHCI_CTRL_EXEC_TUNING : 0;
+ ret |= (val & SDHCI_MIX_CTRL_SMPCLK_SEL)
+ ? SDHCI_CTRL_TUNED_CLK : 0;
+ }
+ ret |= SDHCI_CTRL_UHS_MASK & imx_data->uhs_mode;
+ /* no preset enable available */
+ ret &= ~SDHCI_CTRL_PRESET_VAL_ENABLE;
+
+ return ret;
+ }
+
+ return readw(host->ioaddr + reg);
+}
+
+static void esdhc_post_tuning(struct sdhci_host *host)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct pltfm_imx_data *imx_data = pltfm_host->priv;
+ u32 reg;
+
+ imx_data->scratchpad &= ~SDHCI_MIX_CTRL_EXE_TUNE;
+ reg = readl(host->ioaddr + SDHCI_MIX_CTRL);
+ reg &= ~SDHCI_MIX_CTRL_EXE_TUNE;
+ writel(reg, host->ioaddr + SDHCI_MIX_CTRL);
+}
+
+static void esdhc_reset(struct sdhci_host *host)
+{
+ unsigned long timeout;
+ u32 reg;
+
+ reg = readl(host->ioaddr + SDHCI_SYS_CTRL);
+ reg |= 1 << SDHCI_SYS_CTRL_RSTA_LSH;
+ writel(reg, host->ioaddr + SDHCI_SYS_CTRL);
+
+ /* Wait max 100ms */
+ timeout = 100;
+
+ /* hw clears the bit when it's done */
+ while (readl(host->ioaddr + SDHCI_SYS_CTRL)
+ & (1 << SDHCI_SYS_CTRL_RSTA_LSH)) {
+ if (timeout == 0) {
+ printk(KERN_ERR "%s: Reset never completed.\n",
+ mmc_hostname(host->mmc));
+ return;
+ }
+ timeout--;
+ mdelay(1);
+ }
+}
+
+static void esdhc_prepare_tuning(struct sdhci_host *host, u32 val)
+{
+ u32 reg;
+
+ esdhc_reset(host);
+ mdelay(1);
+
+ reg = readl(host->ioaddr + SDHCI_MIX_CTRL);
+ reg |= SDHCI_MIX_CTRL_EXE_TUNE | \
+ SDHCI_MIX_CTRL_SMPCLK_SEL | \
+ SDHCI_MIX_CTRL_FBCLK_SEL;
+ writel(reg, host->ioaddr + SDHCI_MIX_CTRL);
+ writel((val << 8), host->ioaddr + SDHCI_TUNE_CTRL_STATUS);
+}
+
+
+static void esdhc_writew_le(struct sdhci_host *host, u16 val, int reg)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct esdhc_platform_data *boarddata =
+ host->mmc->parent->platform_data;
+ struct pltfm_imx_data *imx_data = pltfm_host->priv;
+ u32 orig_reg;
+
+ switch (reg) {
+ case SDHCI_CLOCK_CONTROL:
+ orig_reg = readl(host->ioaddr + SDHCI_VENDOR_SPEC);
+ if (val & SDHCI_CLOCK_CARD_EN) {
+ writel(orig_reg | SDHCI_VENDOR_SPEC_FRC_SDCLK_ON, \
+ host->ioaddr + SDHCI_VENDOR_SPEC);
+ } else {
+ writel(orig_reg & ~SDHCI_VENDOR_SPEC_FRC_SDCLK_ON, \
+ host->ioaddr + SDHCI_VENDOR_SPEC);
+ }
+
+ return;
+ case SDHCI_HOST_CONTROL2:
+ orig_reg = readl(host->ioaddr + SDHCI_VENDOR_SPEC);
+ if (val & SDHCI_CTRL_VDD_180) {
+ orig_reg |= SDHCI_VENDOR_SPEC_VSELECT;
+ writel(orig_reg, host->ioaddr + SDHCI_VENDOR_SPEC);
+ } else {
+ orig_reg &= ~SDHCI_VENDOR_SPEC_VSELECT;
+ writel(orig_reg, host->ioaddr + SDHCI_VENDOR_SPEC);
+ }
+
+ /*
+ * FSL sdhc controls bus and signal voltage via one bit
+ * VSELECT in VENDOR_SPEC, which has been set in
+ * SDHCI_POWER_CONTROL. So we skip the SDHCI_CTRL_VDD_180
+ * here.
+ *
+ * ignore exec_tuning flag written to SDHCI_HOST_CONTROL2,
+ * tuning will be handled differently for FSL SDHC ip.
+ */
+ if (cpu_is_mx6sl()) {
+ orig_reg = readl(host->ioaddr + SDHCI_TUNE_CTRL);
+ if (val & SDHCI_CTRL_EXEC_TUNING) {
+ orig_reg |= SDHCI_TUNE_CTRL_STD_TUNING_EN;
+ writel(orig_reg,
+ host->ioaddr + SDHCI_TUNE_CTRL);
+
+ orig_reg =
+ readl(host->ioaddr + SDHCI_ACMD12_ERR);
+ orig_reg |= SDHCI_MIX_CTRL_EXE_TUNE;
+ writel(orig_reg,
+ host->ioaddr + SDHCI_ACMD12_ERR);
+ }
+ } else {
+ orig_reg = readl(host->ioaddr + SDHCI_MIX_CTRL);
+ orig_reg &= ~SDHCI_MIX_CTRL_SMPCLK_SEL;
+ orig_reg |= (val & SDHCI_CTRL_TUNED_CLK)
+ ? SDHCI_MIX_CTRL_SMPCLK_SEL : 0;
+ }
+
+ if (val & SDHCI_CTRL_UHS_DDR50) {
+ orig_reg |= SDHCI_MIX_CTRL_DDREN;
+ imx_data->scratchpad |= SDHCI_MIX_CTRL_DDREN;
+ } else {
+ orig_reg &= ~SDHCI_MIX_CTRL_DDREN;
+ imx_data->scratchpad &= ~SDHCI_MIX_CTRL_DDREN;
+ }
+ writel(orig_reg, host->ioaddr + SDHCI_MIX_CTRL);
+
+ /* set clock frequency again */
+ esdhc_set_clock(host, host->clock);
+ imx_data->uhs_mode = val & SDHCI_CTRL_UHS_MASK;
+
+ /* delay line setting */
+ if (!boarddata->delay_line)
+ return;
+
+ if (val & SDHCI_CTRL_UHS_DDR50)
+ writel((boarddata->delay_line \
+ << SDHCI_DLL_OVERRIDE_OFFSET) \
+ | (1 << SDHCI_DLL_OVERRIDE_EN_OFFSET),
+ host->ioaddr + SDHCI_DLL_CTRL);
+ else
+ writel(0, host->ioaddr + SDHCI_DLL_CTRL);
+
+ return;
+ case SDHCI_TRANSFER_MODE:
+ /*
+ * Postpone this write, we must do it together with a
+ * command write that is down below.
+ */
+ if ((imx_data->flags & ESDHC_FLAG_MULTIBLK_NO_INT)
+ && (host->cmd->opcode == SD_IO_RW_EXTENDED)
+ && (host->cmd->data->blocks > 1)
+ && (host->cmd->data->flags & MMC_DATA_READ)) {
+ u32 v;
+ v = readl(host->ioaddr + SDHCI_VENDOR_SPEC);
+ v |= SDHCI_VENDOR_SPEC_SDIO_QUIRK;
+ writel(v, host->ioaddr + SDHCI_VENDOR_SPEC);
+ }
+ imx_data->scratchpad = val;
+
+ if (val & SDHCI_TRNS_AUTO_CMD23)
+ imx_data->scratchpad |= SDHCI_MIX_CTRL_AC23EN;
+
+ return;
+ case SDHCI_COMMAND:
+ if ((host->cmd->opcode == MMC_STOP_TRANSMISSION ||
+ host->cmd->opcode == MMC_SET_BLOCK_COUNT) &&
+ (imx_data->flags & ESDHC_FLAG_MULTIBLK_NO_INT))
+ val |= SDHCI_CMD_ABORTCMD;
+
+ writel(0x08800880, host->ioaddr + SDHCI_CAPABILITIES_1);
+ if (cpu_is_mx6()) {
+ imx_data->scratchpad |= \
+ (readl(host->ioaddr + SDHCI_MIX_CTRL) & \
+ (SDHCI_MIX_CTRL_EXE_TUNE | \
+ SDHCI_MIX_CTRL_SMPCLK_SEL | \
+ SDHCI_MIX_CTRL_AUTO_TUNE | \
+ SDHCI_MIX_CTRL_FBCLK_SEL | \
+ SDHCI_MIX_CTRL_DDREN));
+
+ writel(imx_data->scratchpad,
+ host->ioaddr + SDHCI_MIX_CTRL);
+
+ writel(val << 16,
+ host->ioaddr + SDHCI_TRANSFER_MODE);
+ } else {
+ writel(val << 16 | imx_data->scratchpad,
+ host->ioaddr + SDHCI_TRANSFER_MODE);
+ }
+ return;
+ case SDHCI_BLOCK_SIZE:
+ val &= ~SDHCI_MAKE_BLKSZ(0x7, 0);
+ break;
+ }
+ esdhc_clrset_le(host, 0xffff, val, reg);
+}
+
+static u8 esdhc_readb_le(struct sdhci_host *host, int reg)
+{
+ u8 ret;
+ u32 reg_val;
+
+ ret = 0;
+ switch (reg) {
+ case SDHCI_POWER_CONTROL:
+ reg_val = readl(host->ioaddr + SDHCI_VENDOR_SPEC);
+ ret |= reg_val & SDHCI_VENDOR_SPEC_VSELECT
+ ? SDHCI_POWER_180 : SDHCI_POWER_300;
+ /* could not power off */
+ ret |= SDHCI_POWER_ON;
+ return ret;
+ case SDHCI_HOST_CONTROL:
+ reg_val = readl(host->ioaddr + SDHCI_HOST_CONTROL);
+ if (reg_val & SDHCI_PROT_CTRL_LCTL)
+ ret |= SDHCI_CTRL_LED;
+ else
+ ret &= ~SDHCI_CTRL_LED;
+
+ ret |= (reg_val & SDHCI_PROT_CTRL_DMASEL_MASK) >> 5;
+ if (SDHCI_PROT_CTRL_8BIT == (reg_val & SDHCI_PROT_CTRL_DTW)) {
+ ret &= ~SDHCI_CTRL_4BITBUS;
+ ret |= SDHCI_CTRL_8BITBUS;
+ } else if (SDHCI_PROT_CTRL_4BIT
+ == (reg_val & SDHCI_PROT_CTRL_DTW)) {
+ ret &= ~SDHCI_CTRL_8BITBUS;
+ ret |= SDHCI_CTRL_4BITBUS;
+ } else if (SDHCI_PROT_CTRL_1BIT
+ == (reg_val & SDHCI_PROT_CTRL_DTW))
+ ret &= ~(SDHCI_CTRL_8BITBUS | SDHCI_CTRL_4BITBUS);
+ return ret;
+ case SDHCI_SOFTWARE_RESET:
+ reg_val = readl(host->ioaddr + SDHCI_CLOCK_CONTROL);
+ ret = reg_val >> 24;
+ return ret;
+ case SDHCI_RESPONSE + 3:
+ reg_val = readl(host->ioaddr + SDHCI_RESPONSE);
+ ret = reg_val >> 24;
+ return ret;
+ case SDHCI_RESPONSE + 7:
+ reg_val = readl(host->ioaddr + SDHCI_RESPONSE + 4);
+ ret = reg_val >> 24;
+ return ret;
+ case SDHCI_RESPONSE + 11:
+ reg_val = readl(host->ioaddr + SDHCI_RESPONSE + 8);
+ ret = reg_val >> 24;
+ return ret;
+ default:
+ ret = 0;
+ break;
+ }
+ return ret;
+}
+
+static void esdhc_writeb_le(struct sdhci_host *host, u8 val, int reg)
+{
+ u32 new_val;
+
+ switch (reg) {
+ case SDHCI_POWER_CONTROL:
+ /*
+ * FSL put some DMA bits here
+ * If your board has a regulator, code should be here
+ */
+ if (val == (SDHCI_POWER_ON | SDHCI_POWER_180)) {
+ u32 reg;
+
+ /* stop sd clock */
+ reg = readl(host->ioaddr + SDHCI_VENDOR_SPEC);
+ writel(reg & ~SDHCI_VENDOR_SPEC_FRC_SDCLK_ON, \
+ host->ioaddr + SDHCI_VENDOR_SPEC);
+
+ /* switch to 1.8V */
+ reg = readl(host->ioaddr + SDHCI_VENDOR_SPEC);
+ reg |= SDHCI_VENDOR_SPEC_VSELECT;
+ writel(reg, host->ioaddr + SDHCI_VENDOR_SPEC);
+
+ } else {
+ u32 reg;
+
+ reg = readl(host->ioaddr + SDHCI_VENDOR_SPEC);
+ reg &= ~SDHCI_VENDOR_SPEC_VSELECT;
+ writel(reg, host->ioaddr + SDHCI_VENDOR_SPEC);
+ }
+ return;
+ case SDHCI_HOST_CONTROL:
+ /* FSL messed up here, so we can just keep those three */
+ new_val = val & (SDHCI_CTRL_LED);
+ if (val & SDHCI_CTRL_8BITBUS) {
+ new_val |= SDHCI_PROT_CTRL_8BIT;
+ new_val &= ~SDHCI_PROT_CTRL_4BIT;
+ } else if (val & SDHCI_CTRL_4BITBUS) {
+ new_val &= ~SDHCI_PROT_CTRL_8BIT;
+ new_val |= SDHCI_PROT_CTRL_4BIT;
+ }
+ /* ensure the endianess */
+ new_val |= ESDHC_HOST_CONTROL_LE;
+ /* DMA mode bits are shifted */
+ new_val |= (val & SDHCI_CTRL_DMA_MASK) << 5;
+
+ esdhc_clrset_le(host, 0xffff, new_val, reg);
+ return;
+ }
+ esdhc_clrset_le(host, 0xff, val, reg);
+}
+
+static unsigned int esdhc_pltfm_get_max_clock(struct sdhci_host *host)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+
+ return clk_get_rate(pltfm_host->clk);
+}
+
+static unsigned int esdhc_pltfm_get_min_clock(struct sdhci_host *host)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+
+ return clk_get_rate(pltfm_host->clk) / 256 / 16;
+}
+
+static unsigned int esdhc_pltfm_get_ro(struct sdhci_host *host)
+{
+ struct esdhc_platform_data *boarddata = host->mmc->parent->platform_data;
+
+ if (boarddata && gpio_is_valid(boarddata->wp_gpio))
+ return gpio_get_value(boarddata->wp_gpio);
+ else
+ return -ENOSYS;
+}
+
+static int plt_8bit_width(struct sdhci_host *host, int width)
+{
+ u32 reg = readl(host->ioaddr + SDHCI_HOST_CONTROL);
+
+ reg &= ~SDHCI_PROT_CTRL_DTW;
+
+ if (width == MMC_BUS_WIDTH_8)
+ reg |= SDHCI_PROT_CTRL_8BIT;
+ else if (width == MMC_BUS_WIDTH_4)
+ reg |= SDHCI_PROT_CTRL_4BIT;
+
+ writel(reg, host->ioaddr + SDHCI_HOST_CONTROL);
+ return 0;
+}
+
+static void plt_clk_ctrl(struct sdhci_host *host, bool enable)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+
+ if (enable) {
+ clk_enable(pltfm_host->clk);
+ host->clk_status = true;
+ } else {
+ clk_disable(pltfm_host->clk);
+ host->clk_status = false;
+ }
+}
+
+static struct sdhci_ops sdhci_esdhc_ops = {
+ .read_l = esdhc_readl_le,
+ .read_w = esdhc_readw_le,
+ .read_b = esdhc_readb_le,
+ .write_l = esdhc_writel_le,
+ .write_w = esdhc_writew_le,
+ .write_b = esdhc_writeb_le,
+ .set_clock = esdhc_set_clock,
+ .get_max_clock = esdhc_pltfm_get_max_clock,
+ .get_min_clock = esdhc_pltfm_get_min_clock,
+ .platform_8bit_width = plt_8bit_width,
+ .platform_clk_ctrl = plt_clk_ctrl,
+};
+
+static irqreturn_t cd_irq(int irq, void *data)
+{
+ struct sdhci_host *sdhost = (struct sdhci_host *)data;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhost);
+ struct pltfm_imx_data *imx_data = pltfm_host->priv;
+
+ writel(0, sdhost->ioaddr + SDHCI_MIX_CTRL);
+ writel(0, sdhost->ioaddr + SDHCI_TUNE_CTRL_STATUS);
+
+ if (cpu_is_mx6()) {
+ imx_data->scratchpad &= ~SDHCI_MIX_CTRL_DDREN;
+ imx_data->scratchpad &= ~SDHCI_MIX_CTRL_FBCLK_SEL;
+ imx_data->scratchpad &= ~SDHCI_MIX_CTRL_SMPCLK_SEL;
+ }
+
+ esdhc_reset(sdhost);
+ mdelay(1);
+
+ tasklet_schedule(&sdhost->card_tasklet);
+ return IRQ_HANDLED;
+};
+
+extern int gSleep_Mode_Suspend;
+void eschc_cd_enable (struct sdhci_host *host, bool enable)
+{
+ struct esdhc_platform_data *boarddata = host->mmc->parent->platform_data;
+ if (gSleep_Mode_Suspend && boarddata->cd_gpio && (ESDHC_CD_GPIO == boarddata->cd_type)) {
+ if (enable) {
+ request_irq(gpio_to_irq(boarddata->cd_gpio), cd_irq,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+ mmc_hostname(host->mmc), host);
+ }
+ else {
+ free_irq(gpio_to_irq(boarddata->cd_gpio), host);
+ }
+ }
+}
+
+static int esdhc_pltfm_init(struct sdhci_host *host, struct sdhci_pltfm_data *pdata)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct esdhc_platform_data *boarddata = host->mmc->parent->platform_data;
+ struct clk *clk;
+ int err;
+ struct pltfm_imx_data *imx_data;
+ u32 reg;
+
+ clk = clk_get(mmc_dev(host->mmc), NULL);
+ if (IS_ERR(clk)) {
+ dev_err(mmc_dev(host->mmc), "clk err\n");
+ return PTR_ERR(clk);
+ }
+ clk_enable(clk);
+ pltfm_host->clk = clk;
+
+ imx_data = kzalloc(sizeof(struct pltfm_imx_data), GFP_KERNEL);
+ if (!imx_data) {
+ clk_disable(pltfm_host->clk);
+ clk_put(pltfm_host->clk);
+ return -ENOMEM;
+ }
+ pltfm_host->priv = imx_data;
+
+ host->quirks |= SDHCI_QUIRK_BROKEN_TIMEOUT_VAL;
+
+ if (cpu_is_mx25() || cpu_is_mx35())
+ /* Fix errata ENGcm07207 present on i.MX25 and i.MX35 */
+ host->quirks |= SDHCI_QUIRK_NO_MULTIBLOCK
+ | SDHCI_QUIRK_BROKEN_ADMA;
+
+ /*
+ * on mx6dl TO 1.1, ADMA can work when ahb bus frequency is low,
+ * like 24Mhz.
+ */
+ if (mx6dl_revision() >= IMX_CHIP_REVISION_1_1)
+ host->quirks &= ~SDHCI_QUIRK_BROKEN_ADMA;
+
+ if (cpu_is_mx6())
+ host->quirks2 |= SDHCI_QUIRK_BROKEN_AUTO_CMD23,
+
+ /* write_protect can't be routed to controller, use gpio */
+ sdhci_esdhc_ops.get_ro = esdhc_pltfm_get_ro;
+
+ if (!(cpu_is_mx25() || cpu_is_mx35() || cpu_is_mx51() || cpu_is_mx6()))
+ imx_data->flags |= ESDHC_FLAG_MULTIBLK_NO_INT;
+
+ host->ocr_avail_sd = MMC_VDD_29_30 | MMC_VDD_30_31 | \
+ MMC_VDD_32_33 | MMC_VDD_33_34;
+ host->ocr_avail_mmc = MMC_VDD_29_30 | MMC_VDD_30_31 | \
+ MMC_VDD_32_33 | MMC_VDD_33_34;
+ host->ocr_avail_sdio = MMC_VDD_29_30 | MMC_VDD_30_31 | \
+ MMC_VDD_32_33 | MMC_VDD_33_34;
+
+ if (cpu_is_mx6q() || cpu_is_mx6dl())
+ sdhci_esdhc_ops.platform_execute_tuning = esdhc_execute_tuning;
+
+ if (boarddata->support_18v) {
+ host->ocr_avail_sd |= MMC_VDD_165_195;
+ host->ocr_avail_mmc |= MMC_VDD_165_195;
+ host->ocr_avail_sdio |= MMC_VDD_165_195;
+ }
+
+ if (boarddata->support_8bit)
+ host->mmc->caps |= MMC_CAP_8_BIT_DATA;
+ if (boarddata->keep_power_at_suspend)
+ host->mmc->pm_caps |= (MMC_PM_KEEP_POWER | \
+ MMC_PM_WAKE_SDIO_IRQ);
+ if (boarddata->cd_type == ESDHC_CD_PERMANENT)
+ host->mmc->caps |= MMC_CAP_NONREMOVABLE;
+ else if (boarddata->cd_type == ESDHC_CD_CONTROLLER
+ || boarddata->cd_type == ESDHC_CD_NONE
+ || boarddata->cd_type == ESDHC_CD_GPIO)
+ host->mmc->caps &= ~MMC_CAP_NONREMOVABLE;
+ if (boarddata->runtime_pm)
+ host->mmc->caps |= MMC_CAP_POWER_OFF_CARD;
+ if (cpu_is_mx6()) {
+ host->mmc->caps |= MMC_CAP_1_8V_DDR;
+ host->tuning_min = SDHCI_TUNE_CTRL_MIN;
+ host->tuning_max = SDHCI_TUNE_CTRL_MAX;
+ host->tuning_step = SDHCI_TUNE_CTRL_STEP;
+ host->clk_mgr_en = true;
+ }
+
+ reg = readl(host->ioaddr + SDHCI_MIX_CTRL);
+ reg &= ~SDHCI_MIX_CTRL_DDREN;
+ writel(reg, host->ioaddr + SDHCI_MIX_CTRL);
+
+ if (boarddata) {
+ /* Device is always present, e.x, populated emmc device */
+ if (boarddata->always_present) {
+ /* remove BROKEN_CD to disable card polling */
+ host->quirks &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION;
+ /* if it is always present, invalid cd_gpio */
+ boarddata->cd_gpio = ARCH_NR_GPIOS + 1;
+ if (host->clk_mgr_en)
+ clk_disable(pltfm_host->clk);
+ return 0;
+ }
+
+ if (ESDHC_CD_WIFI_PWR == boarddata->cd_type) {
+ extern void ntx_register_wifi_cd (irq_handler_t handler, void *data);
+ host->mmc->caps &= ~MMC_CAP_NONREMOVABLE;
+ ntx_register_wifi_cd (cd_irq, host);
+ }
+ else {
+ err = gpio_request_one(boarddata->wp_gpio, GPIOF_IN, "ESDHC_WP");
+ if (err) {
+ dev_warn(mmc_dev(host->mmc),
+ "no write-protect pin available!\n");
+ boarddata->wp_gpio = err;
+ }
+
+ err = gpio_request_one(boarddata->cd_gpio, GPIOF_IN, "ESDHC_CD");
+ if (err) {
+ dev_warn(mmc_dev(host->mmc),
+ "no card-detect pin available!\n");
+ goto no_card_detect_pin;
+ }
+
+ err = request_irq(gpio_to_irq(boarddata->cd_gpio), cd_irq,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+ mmc_hostname(host->mmc), host);
+ if (err) {
+ dev_warn(mmc_dev(host->mmc), "request irq error\n");
+ goto no_card_detect_irq;
+ }
+ }
+ imx_data->flags |= ESDHC_FLAG_GPIO_FOR_CD_WP;
+ /* Now we have a working card_detect again */
+ host->quirks &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION;
+ }
+
+ if (host->clk_mgr_en)
+ clk_disable(pltfm_host->clk);
+ return 0;
+
+ no_card_detect_irq:
+ gpio_free(boarddata->cd_gpio);
+ no_card_detect_pin:
+ boarddata->cd_gpio = err;
+ kfree(imx_data);
+ if (host->clk_mgr_en)
+ clk_disable(pltfm_host->clk);
+ return 0;
+}
+
+static void esdhc_pltfm_exit(struct sdhci_host *host)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct esdhc_platform_data *boarddata = host->mmc->parent->platform_data;
+ struct pltfm_imx_data *imx_data = pltfm_host->priv;
+
+ if (boarddata && gpio_is_valid(boarddata->wp_gpio))
+ gpio_free(boarddata->wp_gpio);
+
+ if (boarddata && gpio_is_valid(boarddata->cd_gpio)) {
+ gpio_free(boarddata->cd_gpio);
+
+ if (!(host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION))
+ free_irq(gpio_to_irq(boarddata->cd_gpio), host);
+ }
+
+ if (!host->clk_mgr_en)
+ clk_disable(pltfm_host->clk);
+ clk_put(pltfm_host->clk);
+ kfree(imx_data);
+}
+
+struct sdhci_pltfm_data sdhci_esdhc_imx_pdata = {
+ .quirks = ESDHC_DEFAULT_QUIRKS | SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC
+ | SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC
+ | SDHCI_QUIRK_BROKEN_CARD_DETECTION
+ | SDHCI_QUIRK_BROKEN_ADMA
+ | SDHCI_QUIRK_NO_HISPD_BIT,
+ /* ADMA has issues. Might be fixable */
+ .ops = &sdhci_esdhc_ops,
+ .init = esdhc_pltfm_init,
+ .exit = esdhc_pltfm_exit,
+};
diff --git a/drivers/mmc/host/sdhci-esdhc.h b/drivers/mmc/host/sdhci-esdhc.h
new file mode 100644
index 00000000..58d4fc6d
--- /dev/null
+++ b/drivers/mmc/host/sdhci-esdhc.h
@@ -0,0 +1,107 @@
+/*
+ * Freescale eSDHC controller driver generics for OF and pltfm.
+ *
+ * Copyright (C) 2007, 2011, 2012 Freescale Semiconductor, Inc.
+ * Copyright (c) 2009 MontaVista Software, Inc.
+ * Copyright (c) 2010 Pengutronix e.K.
+ * Author: Wolfram Sang <w.sang@pengutronix.de>
+ *
+ * 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.
+ */
+
+#ifndef _DRIVERS_MMC_SDHCI_ESDHC_H
+#define _DRIVERS_MMC_SDHCI_ESDHC_H
+#include <linux/platform_device.h>
+
+/*
+ * Ops and quirks for the Freescale eSDHC controller.
+ */
+
+#define ESDHC_DEFAULT_QUIRKS (SDHCI_QUIRK_FORCE_BLK_SZ_2048 | \
+ SDHCI_QUIRK_NO_BUSY_IRQ | \
+ SDHCI_QUIRK_NONSTANDARD_CLOCK | \
+ SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | \
+ SDHCI_QUIRK_PIO_NEEDS_DELAY | \
+ SDHCI_QUIRK_RESTORE_IRQS_AFTER_RESET)
+
+#define ESDHC_SYSTEM_CONTROL 0x2c
+#define ESDHC_CLOCK_MASK 0x0000fff0
+#define ESDHC_PREDIV_SHIFT 8
+#define ESDHC_DIVIDER_SHIFT 4
+#define ESDHC_CLOCK_PEREN 0x00000004
+#define ESDHC_CLOCK_HCKEN 0x00000002
+#define ESDHC_CLOCK_IPGEN 0x00000001
+
+/* pltfm-specific */
+#define ESDHC_HOST_CONTROL_LE 0x20
+
+/* OF-specific */
+#define ESDHC_DMA_SYSCTL 0x40c
+#define ESDHC_DMA_SNOOP 0x00000040
+
+#define ESDHC_HOST_CONTROL_RES 0x05
+
+#define SDHCI_MIX_CTRL 0x48
+#define SDHCI_MIX_CTRL_DDREN (1 << 3)
+
+static inline void esdhc_set_clock(struct sdhci_host *host, unsigned int clock)
+{
+ int pre_div = 2;
+ int div = 1;
+ u32 temp;
+ struct esdhc_platform_data *boarddata;
+ int ddr_mode = 0;
+ struct platform_device *pdev = to_platform_device(host->mmc->parent);
+
+ boarddata = host->mmc->parent->platform_data;
+ if (cpu_is_mx6q() || cpu_is_mx6dl() || cpu_is_mx6sl()) {
+ pre_div = 1;
+ if (readl(host->ioaddr + SDHCI_MIX_CTRL) &
+ SDHCI_MIX_CTRL_DDREN) {
+ ddr_mode = 1;
+ pre_div = 2;
+ }
+ }
+ temp = sdhci_readl(host, ESDHC_SYSTEM_CONTROL);
+ temp &= ~(ESDHC_CLOCK_IPGEN | ESDHC_CLOCK_HCKEN | ESDHC_CLOCK_PEREN
+ | ESDHC_CLOCK_MASK);
+ sdhci_writel(host, temp, ESDHC_SYSTEM_CONTROL);
+
+ if (clock == 0)
+ goto out;
+
+ while (host->max_clk / pre_div / 16 > clock && pre_div < 256)
+ pre_div *= 2;
+
+ while (host->max_clk / pre_div / div > clock && div < 16)
+ div++;
+
+ dev_dbg(mmc_dev(host->mmc), "desired SD clock: %d, actual: %d\n",
+ clock, host->max_clk / pre_div / div);
+
+ pre_div >>= (1 + ddr_mode);
+ div--;
+
+ temp = sdhci_readl(host, ESDHC_SYSTEM_CONTROL);
+ temp |= (ESDHC_CLOCK_IPGEN | ESDHC_CLOCK_HCKEN | ESDHC_CLOCK_PEREN
+ | (div << ESDHC_DIVIDER_SHIFT)
+ | (pre_div << ESDHC_PREDIV_SHIFT));
+ sdhci_writel(host, temp, ESDHC_SYSTEM_CONTROL);
+ mdelay(1);
+
+ /* if there's board callback function
+ * for pad setting change, that means
+ * board needs to reconfig its pad for
+ * corresponding sd bus frequency
+ */
+ if (boarddata->platform_pad_change) {
+ BUG_ON(!pdev);
+ boarddata->platform_pad_change(pdev->id, clock);
+ }
+out:
+ host->clock = clock;
+}
+
+#endif /* _DRIVERS_MMC_SDHCI_ESDHC_H */
diff --git a/drivers/mmc/host/sdhci-of-core.c b/drivers/mmc/host/sdhci-of-core.c
new file mode 100644
index 00000000..60e4186a
--- /dev/null
+++ b/drivers/mmc/host/sdhci-of-core.c
@@ -0,0 +1,253 @@
+/*
+ * OpenFirmware bindings for Secure Digital Host Controller Interface.
+ *
+ * Copyright (c) 2007 Freescale Semiconductor, Inc.
+ * Copyright (c) 2009 MontaVista Software, Inc.
+ *
+ * Authors: Xiaobo Xie <X.Xie@freescale.com>
+ * Anton Vorontsov <avorontsov@ru.mvista.com>
+ *
+ * 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.
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/mmc/host.h>
+#ifdef CONFIG_PPC
+#include <asm/machdep.h>
+#endif
+#include "sdhci-of.h"
+#include "sdhci.h"
+
+#ifdef CONFIG_MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER
+
+/*
+ * These accessors are designed for big endian hosts doing I/O to
+ * little endian controllers incorporating a 32-bit hardware byte swapper.
+ */
+
+u32 sdhci_be32bs_readl(struct sdhci_host *host, int reg)
+{
+ return in_be32(host->ioaddr + reg);
+}
+
+u16 sdhci_be32bs_readw(struct sdhci_host *host, int reg)
+{
+ return in_be16(host->ioaddr + (reg ^ 0x2));
+}
+
+u8 sdhci_be32bs_readb(struct sdhci_host *host, int reg)
+{
+ return in_8(host->ioaddr + (reg ^ 0x3));
+}
+
+void sdhci_be32bs_writel(struct sdhci_host *host, u32 val, int reg)
+{
+ out_be32(host->ioaddr + reg, val);
+}
+
+void sdhci_be32bs_writew(struct sdhci_host *host, u16 val, int reg)
+{
+ struct sdhci_of_host *of_host = sdhci_priv(host);
+ int base = reg & ~0x3;
+ int shift = (reg & 0x2) * 8;
+
+ switch (reg) {
+ case SDHCI_TRANSFER_MODE:
+ /*
+ * Postpone this write, we must do it together with a
+ * command write that is down below.
+ */
+ of_host->xfer_mode_shadow = val;
+ return;
+ case SDHCI_COMMAND:
+ sdhci_be32bs_writel(host, val << 16 | of_host->xfer_mode_shadow,
+ SDHCI_TRANSFER_MODE);
+ return;
+ }
+ clrsetbits_be32(host->ioaddr + base, 0xffff << shift, val << shift);
+}
+
+void sdhci_be32bs_writeb(struct sdhci_host *host, u8 val, int reg)
+{
+ int base = reg & ~0x3;
+ int shift = (reg & 0x3) * 8;
+
+ clrsetbits_be32(host->ioaddr + base , 0xff << shift, val << shift);
+}
+#endif /* CONFIG_MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER */
+
+#ifdef CONFIG_PM
+
+static int sdhci_of_suspend(struct platform_device *ofdev, pm_message_t state)
+{
+ struct sdhci_host *host = dev_get_drvdata(&ofdev->dev);
+
+ return mmc_suspend_host(host->mmc);
+}
+
+static int sdhci_of_resume(struct platform_device *ofdev)
+{
+ struct sdhci_host *host = dev_get_drvdata(&ofdev->dev);
+
+ return mmc_resume_host(host->mmc);
+}
+
+#else
+
+#define sdhci_of_suspend NULL
+#define sdhci_of_resume NULL
+
+#endif
+
+static bool __devinit sdhci_of_wp_inverted(struct device_node *np)
+{
+ if (of_get_property(np, "sdhci,wp-inverted", NULL))
+ return true;
+
+ /* Old device trees don't have the wp-inverted property. */
+#ifdef CONFIG_PPC
+ return machine_is(mpc837x_rdb) || machine_is(mpc837x_mds);
+#else
+ return false;
+#endif
+}
+
+static const struct of_device_id sdhci_of_match[];
+static int __devinit sdhci_of_probe(struct platform_device *ofdev)
+{
+ const struct of_device_id *match;
+ struct device_node *np = ofdev->dev.of_node;
+ struct sdhci_of_data *sdhci_of_data;
+ struct sdhci_host *host;
+ struct sdhci_of_host *of_host;
+ const __be32 *clk;
+ int size;
+ int ret;
+
+ match = of_match_device(sdhci_of_match, &ofdev->dev);
+ if (!match)
+ return -EINVAL;
+ sdhci_of_data = match->data;
+
+ if (!of_device_is_available(np))
+ return -ENODEV;
+
+ host = sdhci_alloc_host(&ofdev->dev, sizeof(*of_host));
+ if (IS_ERR(host))
+ return -ENOMEM;
+
+ of_host = sdhci_priv(host);
+ dev_set_drvdata(&ofdev->dev, host);
+
+ host->ioaddr = of_iomap(np, 0);
+ if (!host->ioaddr) {
+ ret = -ENOMEM;
+ goto err_addr_map;
+ }
+
+ host->irq = irq_of_parse_and_map(np, 0);
+ if (!host->irq) {
+ ret = -EINVAL;
+ goto err_no_irq;
+ }
+
+ host->hw_name = dev_name(&ofdev->dev);
+ if (sdhci_of_data) {
+ host->quirks = sdhci_of_data->quirks;
+ host->ops = &sdhci_of_data->ops;
+ }
+
+ if (of_get_property(np, "sdhci,auto-cmd12", NULL))
+ host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
+
+
+ if (of_get_property(np, "sdhci,1-bit-only", NULL))
+ host->quirks |= SDHCI_QUIRK_FORCE_1_BIT_DATA;
+
+ if (sdhci_of_wp_inverted(np))
+ host->quirks |= SDHCI_QUIRK_INVERTED_WRITE_PROTECT;
+
+ clk = of_get_property(np, "clock-frequency", &size);
+ if (clk && size == sizeof(*clk) && *clk)
+ of_host->clock = be32_to_cpup(clk);
+
+ ret = sdhci_add_host(host);
+ if (ret)
+ goto err_add_host;
+
+ return 0;
+
+err_add_host:
+ irq_dispose_mapping(host->irq);
+err_no_irq:
+ iounmap(host->ioaddr);
+err_addr_map:
+ sdhci_free_host(host);
+ return ret;
+}
+
+static int __devexit sdhci_of_remove(struct platform_device *ofdev)
+{
+ struct sdhci_host *host = dev_get_drvdata(&ofdev->dev);
+
+ sdhci_remove_host(host, 0);
+ sdhci_free_host(host);
+ irq_dispose_mapping(host->irq);
+ iounmap(host->ioaddr);
+ return 0;
+}
+
+static const struct of_device_id sdhci_of_match[] = {
+#ifdef CONFIG_MMC_SDHCI_OF_ESDHC
+ { .compatible = "fsl,mpc8379-esdhc", .data = &sdhci_esdhc, },
+ { .compatible = "fsl,mpc8536-esdhc", .data = &sdhci_esdhc, },
+ { .compatible = "fsl,esdhc", .data = &sdhci_esdhc, },
+#endif
+#ifdef CONFIG_MMC_SDHCI_OF_HLWD
+ { .compatible = "nintendo,hollywood-sdhci", .data = &sdhci_hlwd, },
+#endif
+ { .compatible = "generic-sdhci", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, sdhci_of_match);
+
+static struct platform_driver sdhci_of_driver = {
+ .driver = {
+ .name = "sdhci-of",
+ .owner = THIS_MODULE,
+ .of_match_table = sdhci_of_match,
+ },
+ .probe = sdhci_of_probe,
+ .remove = __devexit_p(sdhci_of_remove),
+ .suspend = sdhci_of_suspend,
+ .resume = sdhci_of_resume,
+};
+
+static int __init sdhci_of_init(void)
+{
+ return platform_driver_register(&sdhci_of_driver);
+}
+module_init(sdhci_of_init);
+
+static void __exit sdhci_of_exit(void)
+{
+ platform_driver_unregister(&sdhci_of_driver);
+}
+module_exit(sdhci_of_exit);
+
+MODULE_DESCRIPTION("Secure Digital Host Controller Interface OF driver");
+MODULE_AUTHOR("Xiaobo Xie <X.Xie@freescale.com>, "
+ "Anton Vorontsov <avorontsov@ru.mvista.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mmc/host/sdhci-of-esdhc.c b/drivers/mmc/host/sdhci-of-esdhc.c
new file mode 100644
index 00000000..ba40d6d0
--- /dev/null
+++ b/drivers/mmc/host/sdhci-of-esdhc.c
@@ -0,0 +1,91 @@
+/*
+ * Freescale eSDHC controller driver.
+ *
+ * Copyright (c) 2007 Freescale Semiconductor, Inc.
+ * Copyright (c) 2009 MontaVista Software, Inc.
+ *
+ * Authors: Xiaobo Xie <X.Xie@freescale.com>
+ * Anton Vorontsov <avorontsov@ru.mvista.com>
+ *
+ * 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.
+ */
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/mmc/host.h>
+#include "sdhci-of.h"
+#include "sdhci.h"
+#include "sdhci-esdhc.h"
+
+static u16 esdhc_readw(struct sdhci_host *host, int reg)
+{
+ u16 ret;
+
+ if (unlikely(reg == SDHCI_HOST_VERSION))
+ ret = in_be16(host->ioaddr + reg);
+ else
+ ret = sdhci_be32bs_readw(host, reg);
+ return ret;
+}
+
+static void esdhc_writew(struct sdhci_host *host, u16 val, int reg)
+{
+ if (reg == SDHCI_BLOCK_SIZE) {
+ /*
+ * Two last DMA bits are reserved, and first one is used for
+ * non-standard blksz of 4096 bytes that we don't support
+ * yet. So clear the DMA boundary bits.
+ */
+ val &= ~SDHCI_MAKE_BLKSZ(0x7, 0);
+ }
+ sdhci_be32bs_writew(host, val, reg);
+}
+
+static void esdhc_writeb(struct sdhci_host *host, u8 val, int reg)
+{
+ /* Prevent SDHCI core from writing reserved bits (e.g. HISPD). */
+ if (reg == SDHCI_HOST_CONTROL)
+ val &= ~ESDHC_HOST_CONTROL_RES;
+ sdhci_be32bs_writeb(host, val, reg);
+}
+
+static int esdhc_of_enable_dma(struct sdhci_host *host)
+{
+ setbits32(host->ioaddr + ESDHC_DMA_SYSCTL, ESDHC_DMA_SNOOP);
+ return 0;
+}
+
+static unsigned int esdhc_of_get_max_clock(struct sdhci_host *host)
+{
+ struct sdhci_of_host *of_host = sdhci_priv(host);
+
+ return of_host->clock;
+}
+
+static unsigned int esdhc_of_get_min_clock(struct sdhci_host *host)
+{
+ struct sdhci_of_host *of_host = sdhci_priv(host);
+
+ return of_host->clock / 256 / 16;
+}
+
+struct sdhci_of_data sdhci_esdhc = {
+ /* card detection could be handled via GPIO */
+ .quirks = ESDHC_DEFAULT_QUIRKS | SDHCI_QUIRK_BROKEN_CARD_DETECTION
+ | SDHCI_QUIRK_NO_CARD_NO_RESET,
+ .ops = {
+ .read_l = sdhci_be32bs_readl,
+ .read_w = esdhc_readw,
+ .read_b = sdhci_be32bs_readb,
+ .write_l = sdhci_be32bs_writel,
+ .write_w = esdhc_writew,
+ .write_b = esdhc_writeb,
+ .set_clock = esdhc_set_clock,
+ .enable_dma = esdhc_of_enable_dma,
+ .get_max_clock = esdhc_of_get_max_clock,
+ .get_min_clock = esdhc_of_get_min_clock,
+ },
+};
diff --git a/drivers/mmc/host/sdhci-of-hlwd.c b/drivers/mmc/host/sdhci-of-hlwd.c
new file mode 100644
index 00000000..68ddb754
--- /dev/null
+++ b/drivers/mmc/host/sdhci-of-hlwd.c
@@ -0,0 +1,65 @@
+/*
+ * drivers/mmc/host/sdhci-of-hlwd.c
+ *
+ * Nintendo Wii Secure Digital Host Controller Interface.
+ * Copyright (C) 2009 The GameCube Linux Team
+ * Copyright (C) 2009 Albert Herranz
+ *
+ * Based on sdhci-of-esdhc.c
+ *
+ * Copyright (c) 2007 Freescale Semiconductor, Inc.
+ * Copyright (c) 2009 MontaVista Software, Inc.
+ *
+ * Authors: Xiaobo Xie <X.Xie@freescale.com>
+ * Anton Vorontsov <avorontsov@ru.mvista.com>
+ *
+ * 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.
+ */
+
+#include <linux/delay.h>
+#include <linux/mmc/host.h>
+#include "sdhci-of.h"
+#include "sdhci.h"
+
+/*
+ * Ops and quirks for the Nintendo Wii SDHCI controllers.
+ */
+
+/*
+ * We need a small delay after each write, or things go horribly wrong.
+ */
+#define SDHCI_HLWD_WRITE_DELAY 5 /* usecs */
+
+static void sdhci_hlwd_writel(struct sdhci_host *host, u32 val, int reg)
+{
+ sdhci_be32bs_writel(host, val, reg);
+ udelay(SDHCI_HLWD_WRITE_DELAY);
+}
+
+static void sdhci_hlwd_writew(struct sdhci_host *host, u16 val, int reg)
+{
+ sdhci_be32bs_writew(host, val, reg);
+ udelay(SDHCI_HLWD_WRITE_DELAY);
+}
+
+static void sdhci_hlwd_writeb(struct sdhci_host *host, u8 val, int reg)
+{
+ sdhci_be32bs_writeb(host, val, reg);
+ udelay(SDHCI_HLWD_WRITE_DELAY);
+}
+
+struct sdhci_of_data sdhci_hlwd = {
+ .quirks = SDHCI_QUIRK_32BIT_DMA_ADDR |
+ SDHCI_QUIRK_32BIT_DMA_SIZE,
+ .ops = {
+ .read_l = sdhci_be32bs_readl,
+ .read_w = sdhci_be32bs_readw,
+ .read_b = sdhci_be32bs_readb,
+ .write_l = sdhci_hlwd_writel,
+ .write_w = sdhci_hlwd_writew,
+ .write_b = sdhci_hlwd_writeb,
+ },
+};
diff --git a/drivers/mmc/host/sdhci-of.h b/drivers/mmc/host/sdhci-of.h
new file mode 100644
index 00000000..ad09ad99
--- /dev/null
+++ b/drivers/mmc/host/sdhci-of.h
@@ -0,0 +1,42 @@
+/*
+ * OpenFirmware bindings for Secure Digital Host Controller Interface.
+ *
+ * Copyright (c) 2007 Freescale Semiconductor, Inc.
+ * Copyright (c) 2009 MontaVista Software, Inc.
+ *
+ * Authors: Xiaobo Xie <X.Xie@freescale.com>
+ * Anton Vorontsov <avorontsov@ru.mvista.com>
+ *
+ * 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.
+ */
+
+#ifndef __SDHCI_OF_H
+#define __SDHCI_OF_H
+
+#include <linux/types.h>
+#include "sdhci.h"
+
+struct sdhci_of_data {
+ unsigned int quirks;
+ struct sdhci_ops ops;
+};
+
+struct sdhci_of_host {
+ unsigned int clock;
+ u16 xfer_mode_shadow;
+};
+
+extern u32 sdhci_be32bs_readl(struct sdhci_host *host, int reg);
+extern u16 sdhci_be32bs_readw(struct sdhci_host *host, int reg);
+extern u8 sdhci_be32bs_readb(struct sdhci_host *host, int reg);
+extern void sdhci_be32bs_writel(struct sdhci_host *host, u32 val, int reg);
+extern void sdhci_be32bs_writew(struct sdhci_host *host, u16 val, int reg);
+extern void sdhci_be32bs_writeb(struct sdhci_host *host, u8 val, int reg);
+
+extern struct sdhci_of_data sdhci_esdhc;
+extern struct sdhci_of_data sdhci_hlwd;
+
+#endif /* __SDHCI_OF_H */
diff --git a/drivers/mmc/host/sdhci-pci.c b/drivers/mmc/host/sdhci-pci.c
new file mode 100644
index 00000000..936bbca1
--- /dev/null
+++ b/drivers/mmc/host/sdhci-pci.c
@@ -0,0 +1,1151 @@
+/* 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/slab.h>
+#include <linux/device.h>
+#include <linux/mmc/host.h>
+#include <linux/scatterlist.h>
+#include <linux/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_SAMSUNG ||
+ chip->pdev->subsystem_vendor == PCI_VENDOR_ID_SONY)
+ chip->quirks |= SDHCI_QUIRK_NO_CARD_NO_RESET;
+ return 0;
+}
+
+static int ricoh_mmc_probe_slot(struct sdhci_pci_slot *slot)
+{
+ slot->host->caps =
+ ((0x21 << SDHCI_TIMEOUT_CLK_SHIFT)
+ & SDHCI_TIMEOUT_CLK_MASK) |
+
+ ((0x21 << SDHCI_CLOCK_BASE_SHIFT)
+ & SDHCI_CLOCK_BASE_MASK) |
+
+ SDHCI_TIMEOUT_CLK_UNIT |
+ SDHCI_CAN_VDD_330 |
+ SDHCI_CAN_DO_SDMA;
+ return 0;
+}
+
+static int ricoh_mmc_resume(struct sdhci_pci_chip *chip)
+{
+ /* Apply a delay to allow controller to settle */
+ /* Otherwise it becomes confused if card state changed
+ during suspend */
+ msleep(500);
+ return 0;
+}
+
+static const struct sdhci_pci_fixes sdhci_ricoh = {
+ .probe = ricoh_probe,
+ .quirks = SDHCI_QUIRK_32BIT_DMA_ADDR |
+ SDHCI_QUIRK_FORCE_DMA |
+ SDHCI_QUIRK_CLOCK_BEFORE_RESET,
+};
+
+static const struct sdhci_pci_fixes sdhci_ricoh_mmc = {
+ .probe_slot = ricoh_mmc_probe_slot,
+ .resume = ricoh_mmc_resume,
+ .quirks = SDHCI_QUIRK_32BIT_DMA_ADDR |
+ SDHCI_QUIRK_CLOCK_BEFORE_RESET |
+ SDHCI_QUIRK_NO_CARD_NO_RESET |
+ SDHCI_QUIRK_MISSING_CAPS
+};
+
+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_NO_BUSY_IRQ |
+ SDHCI_QUIRK_BROKEN_TIMEOUT_VAL,
+};
+
+/*
+ * ADMA operation is disabled for Moorestown platform due to
+ * hardware bugs.
+ */
+static int mrst_hc_probe(struct sdhci_pci_chip *chip)
+{
+ /*
+ * slots number is fixed here for MRST as SDIO3/5 are never used and
+ * have hardware bugs.
+ */
+ chip->num_slots = 1;
+ return 0;
+}
+
+static const struct sdhci_pci_fixes sdhci_intel_mrst_hc0 = {
+ .quirks = SDHCI_QUIRK_BROKEN_ADMA | SDHCI_QUIRK_NO_HISPD_BIT,
+};
+
+static const struct sdhci_pci_fixes sdhci_intel_mrst_hc1_hc2 = {
+ .quirks = SDHCI_QUIRK_BROKEN_ADMA | SDHCI_QUIRK_NO_HISPD_BIT,
+ .probe = mrst_hc_probe,
+};
+
+static const struct sdhci_pci_fixes sdhci_intel_mfd_sd = {
+ .quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC,
+};
+
+static const struct sdhci_pci_fixes sdhci_intel_mfd_emmc_sdio = {
+ .quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC,
+};
+
+/* O2Micro extra registers */
+#define O2_SD_LOCK_WP 0xD3
+#define O2_SD_MULTI_VCC3V 0xEE
+#define O2_SD_CLKREQ 0xEC
+#define O2_SD_CAPS 0xE0
+#define O2_SD_ADMA1 0xE2
+#define O2_SD_ADMA2 0xE7
+#define O2_SD_INF_MOD 0xF1
+
+static int o2_probe(struct sdhci_pci_chip *chip)
+{
+ int ret;
+ u8 scratch;
+
+ switch (chip->pdev->device) {
+ case PCI_DEVICE_ID_O2_8220:
+ case PCI_DEVICE_ID_O2_8221:
+ case PCI_DEVICE_ID_O2_8320:
+ case PCI_DEVICE_ID_O2_8321:
+ /* This extra setup is required due to broken ADMA. */
+ ret = pci_read_config_byte(chip->pdev, O2_SD_LOCK_WP, &scratch);
+ if (ret)
+ return ret;
+ scratch &= 0x7f;
+ pci_write_config_byte(chip->pdev, O2_SD_LOCK_WP, scratch);
+
+ /* Set Multi 3 to VCC3V# */
+ pci_write_config_byte(chip->pdev, O2_SD_MULTI_VCC3V, 0x08);
+
+ /* Disable CLK_REQ# support after media DET */
+ ret = pci_read_config_byte(chip->pdev, O2_SD_CLKREQ, &scratch);
+ if (ret)
+ return ret;
+ scratch |= 0x20;
+ pci_write_config_byte(chip->pdev, O2_SD_CLKREQ, scratch);
+
+ /* Choose capabilities, enable SDMA. We have to write 0x01
+ * to the capabilities register first to unlock it.
+ */
+ ret = pci_read_config_byte(chip->pdev, O2_SD_CAPS, &scratch);
+ if (ret)
+ return ret;
+ scratch |= 0x01;
+ pci_write_config_byte(chip->pdev, O2_SD_CAPS, scratch);
+ pci_write_config_byte(chip->pdev, O2_SD_CAPS, 0x73);
+
+ /* Disable ADMA1/2 */
+ pci_write_config_byte(chip->pdev, O2_SD_ADMA1, 0x39);
+ pci_write_config_byte(chip->pdev, O2_SD_ADMA2, 0x08);
+
+ /* Disable the infinite transfer mode */
+ ret = pci_read_config_byte(chip->pdev, O2_SD_INF_MOD, &scratch);
+ if (ret)
+ return ret;
+ scratch |= 0x08;
+ pci_write_config_byte(chip->pdev, O2_SD_INF_MOD, scratch);
+
+ /* Lock WP */
+ ret = pci_read_config_byte(chip->pdev, O2_SD_LOCK_WP, &scratch);
+ if (ret)
+ return ret;
+ scratch |= 0x80;
+ pci_write_config_byte(chip->pdev, O2_SD_LOCK_WP, scratch);
+ }
+
+ return 0;
+}
+
+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;
+ u16 mmcdev = 0;
+
+ 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 |
+ SDHCI_QUIRK_BROKEN_SMALL_PIO;
+ }
+
+ /*
+ * 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)
+ mmcdev = PCI_DEVICE_ID_JMICRON_JMB38X_MMC;
+ else if (chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB388_SD)
+ mmcdev = PCI_DEVICE_ID_JMICRON_JMB388_ESD;
+
+ if (mmcdev) {
+ struct pci_dev *sd_dev;
+
+ sd_dev = NULL;
+ while ((sd_dev = pci_get_device(PCI_VENDOR_ID_JMICRON,
+ mmcdev, 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;
+ }
+
+ /* quirk for unsable RO-detection on JM388 chips */
+ if (chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB388_SD ||
+ chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB388_ESD)
+ chip->quirks |= SDHCI_QUIRK_UNSTABLE_RO_DETECT;
+
+ 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;
+ }
+
+ /* JM388 MMC doesn't support 1.8V while SD supports it */
+ if (slot->chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB388_ESD) {
+ slot->host->ocr_avail_sd = MMC_VDD_32_33 | MMC_VDD_33_34 |
+ MMC_VDD_29_30 | MMC_VDD_30_31 |
+ MMC_VDD_165_195; /* allow 1.8V */
+ slot->host->ocr_avail_mmc = MMC_VDD_32_33 | MMC_VDD_33_34 |
+ MMC_VDD_29_30 | MMC_VDD_30_31; /* no 1.8V for MMC */
+ }
+
+ /*
+ * The secondary interface requires a bit set to get the
+ * interrupts.
+ */
+ if (slot->chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC ||
+ slot->chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB388_ESD)
+ jmicron_enable_mmc(slot->host, 1);
+
+ slot->host->mmc->caps |= MMC_CAP_BUS_WIDTH_TEST;
+
+ 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 ||
+ slot->chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB388_ESD)
+ 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 ||
+ chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB388_ESD) {
+ 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 ||
+ chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB388_ESD) {
+ 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_o2 = {
+ .probe = o2_probe,
+};
+
+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,
+};
+
+/* SysKonnect CardBus2SDIO extra registers */
+#define SYSKT_CTRL 0x200
+#define SYSKT_RDFIFO_STAT 0x204
+#define SYSKT_WRFIFO_STAT 0x208
+#define SYSKT_POWER_DATA 0x20c
+#define SYSKT_POWER_330 0xef
+#define SYSKT_POWER_300 0xf8
+#define SYSKT_POWER_184 0xcc
+#define SYSKT_POWER_CMD 0x20d
+#define SYSKT_POWER_START (1 << 7)
+#define SYSKT_POWER_STATUS 0x20e
+#define SYSKT_POWER_STATUS_OK (1 << 0)
+#define SYSKT_BOARD_REV 0x210
+#define SYSKT_CHIP_REV 0x211
+#define SYSKT_CONF_DATA 0x212
+#define SYSKT_CONF_DATA_1V8 (1 << 2)
+#define SYSKT_CONF_DATA_2V5 (1 << 1)
+#define SYSKT_CONF_DATA_3V3 (1 << 0)
+
+static int syskt_probe(struct sdhci_pci_chip *chip)
+{
+ if ((chip->pdev->class & 0x0000FF) == PCI_SDHCI_IFVENDOR) {
+ chip->pdev->class &= ~0x0000FF;
+ chip->pdev->class |= PCI_SDHCI_IFDMA;
+ }
+ return 0;
+}
+
+static int syskt_probe_slot(struct sdhci_pci_slot *slot)
+{
+ int tm, ps;
+
+ u8 board_rev = readb(slot->host->ioaddr + SYSKT_BOARD_REV);
+ u8 chip_rev = readb(slot->host->ioaddr + SYSKT_CHIP_REV);
+ dev_info(&slot->chip->pdev->dev, "SysKonnect CardBus2SDIO, "
+ "board rev %d.%d, chip rev %d.%d\n",
+ board_rev >> 4, board_rev & 0xf,
+ chip_rev >> 4, chip_rev & 0xf);
+ if (chip_rev >= 0x20)
+ slot->host->quirks |= SDHCI_QUIRK_FORCE_DMA;
+
+ writeb(SYSKT_POWER_330, slot->host->ioaddr + SYSKT_POWER_DATA);
+ writeb(SYSKT_POWER_START, slot->host->ioaddr + SYSKT_POWER_CMD);
+ udelay(50);
+ tm = 10; /* Wait max 1 ms */
+ do {
+ ps = readw(slot->host->ioaddr + SYSKT_POWER_STATUS);
+ if (ps & SYSKT_POWER_STATUS_OK)
+ break;
+ udelay(100);
+ } while (--tm);
+ if (!tm) {
+ dev_err(&slot->chip->pdev->dev,
+ "power regulator never stabilized");
+ writeb(0, slot->host->ioaddr + SYSKT_POWER_CMD);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static const struct sdhci_pci_fixes sdhci_syskt = {
+ .quirks = SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER,
+ .probe = syskt_probe,
+ .probe_slot = syskt_probe_slot,
+};
+
+static int via_probe(struct sdhci_pci_chip *chip)
+{
+ if (chip->pdev->revision == 0x10)
+ chip->quirks |= SDHCI_QUIRK_DELAY_AFTER_POWER;
+
+ return 0;
+}
+
+static const struct sdhci_pci_fixes sdhci_via = {
+ .probe = via_probe,
+};
+
+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_RICOH,
+ .device = 0x843,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_ricoh_mmc,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_RICOH,
+ .device = 0xe822,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_ricoh_mmc,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_RICOH,
+ .device = 0xe823,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_ricoh_mmc,
+ },
+
+ {
+ .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_88ALP01_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,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_JMICRON,
+ .device = PCI_DEVICE_ID_JMICRON_JMB388_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_JMB388_ESD,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_jmicron,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_SYSKONNECT,
+ .device = 0x8000,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_syskt,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_VIA,
+ .device = 0x95d0,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_via,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_INTEL,
+ .device = PCI_DEVICE_ID_INTEL_MRST_SD0,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_intel_mrst_hc0,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_INTEL,
+ .device = PCI_DEVICE_ID_INTEL_MRST_SD1,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_intel_mrst_hc1_hc2,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_INTEL,
+ .device = PCI_DEVICE_ID_INTEL_MRST_SD2,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_intel_mrst_hc1_hc2,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_INTEL,
+ .device = PCI_DEVICE_ID_INTEL_MFD_SD,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_intel_mfd_sd,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_INTEL,
+ .device = PCI_DEVICE_ID_INTEL_MFD_SDIO1,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_intel_mfd_emmc_sdio,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_INTEL,
+ .device = PCI_DEVICE_ID_INTEL_MFD_SDIO2,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_intel_mfd_emmc_sdio,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_INTEL,
+ .device = PCI_DEVICE_ID_INTEL_MFD_EMMC0,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_intel_mfd_emmc_sdio,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_INTEL,
+ .device = PCI_DEVICE_ID_INTEL_MFD_EMMC1,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_intel_mfd_emmc_sdio,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_O2,
+ .device = PCI_DEVICE_ID_O2_8120,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_o2,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_O2,
+ .device = PCI_DEVICE_ID_O2_8220,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_o2,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_O2,
+ .device = PCI_DEVICE_ID_O2_8221,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_o2,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_O2,
+ .device = PCI_DEVICE_ID_O2_8320,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_o2,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_O2,
+ .device = PCI_DEVICE_ID_O2_8321,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_o2,
+ },
+
+ { /* 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_SDMA)) {
+ 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_BIT_MASK(32));
+ 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;
+ mmc_pm_flag_t slot_pm_flags;
+ mmc_pm_flag_t pm_flags = 0;
+ 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;
+ }
+
+ slot_pm_flags = slot->host->mmc->pm_flags;
+ if (slot_pm_flags & MMC_PM_WAKE_SDIO_IRQ)
+ sdhci_enable_irq_wakeups(slot->host);
+
+ pm_flags |= slot_pm_flags;
+ }
+
+ 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);
+ if (pm_flags & MMC_PM_KEEP_POWER) {
+ if (pm_flags & MMC_PM_WAKE_SDIO_IRQ) {
+ pci_pme_active(pdev, true);
+ pci_enable_wake(pdev, PCI_D3hot, 1);
+ }
+ pci_set_power_state(pdev, PCI_D3hot);
+ } else {
+ 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;
+ 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)) {
+ dev_err(&pdev->dev, "cannot allocate host\n");
+ return ERR_CAST(host);
+ }
+
+ 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");
+ goto free;
+ }
+
+ host->ioaddr = pci_ioremap_bar(pdev, bar);
+ if (!host->ioaddr) {
+ dev_err(&pdev->dev, "failed to remap registers\n");
+ ret = -ENOMEM;
+ goto release;
+ }
+
+ if (chip->fixes && chip->fixes->probe_slot) {
+ ret = chip->fixes->probe_slot(slot);
+ if (ret)
+ goto unmap;
+ }
+
+ host->mmc->pm_caps = MMC_PM_KEEP_POWER | MMC_PM_WAKE_SDIO_IRQ;
+
+ 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);
+
+free:
+ 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, first_bar;
+ int ret, i;
+
+ BUG_ON(pdev == NULL);
+ BUG_ON(ent == NULL);
+
+ dev_info(&pdev->dev, "SDHCI controller found [%04x:%04x] (rev %x)\n",
+ (int)pdev->vendor, (int)pdev->device, (int)pdev->revision);
+
+ 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;
+ }
+
+ slots = chip->num_slots; /* Quirk may have changed this */
+
+ 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 <pierre@ossman.eu>");
+MODULE_DESCRIPTION("Secure Digital Host Controller Interface PCI driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mmc/host/sdhci-pltfm.c b/drivers/mmc/host/sdhci-pltfm.c
new file mode 100644
index 00000000..61f3aafc
--- /dev/null
+++ b/drivers/mmc/host/sdhci-pltfm.c
@@ -0,0 +1,241 @@
+/*
+ * sdhci-pltfm.c Support for SDHCI platform devices
+ * Copyright (c) 2009 Intel 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* Supports:
+ * SDHCI platform devices
+ *
+ * Inspired by sdhci-pci.c, by Pierre Ossman
+ */
+
+#include <linux/delay.h>
+#include <linux/highmem.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+
+#include <linux/mmc/host.h>
+
+#include <linux/io.h>
+#include <linux/mmc/sdhci-pltfm.h>
+
+#include "sdhci.h"
+#include "sdhci-pltfm.h"
+
+/*****************************************************************************\
+ * *
+ * SDHCI core callbacks *
+ * *
+\*****************************************************************************/
+
+static struct sdhci_ops sdhci_pltfm_ops = {
+};
+
+/*****************************************************************************\
+ * *
+ * Device probing/removal *
+ * *
+\*****************************************************************************/
+
+static int __devinit sdhci_pltfm_probe(struct platform_device *pdev)
+{
+ const struct platform_device_id *platid = platform_get_device_id(pdev);
+ struct sdhci_pltfm_data *pdata;
+ struct sdhci_host *host;
+ struct sdhci_pltfm_host *pltfm_host;
+ struct resource *iomem;
+ int ret;
+
+ if (platid && platid->driver_data)
+ pdata = (void *)platid->driver_data;
+ else
+ pdata = pdev->dev.platform_data;
+
+ iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!iomem) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ if (resource_size(iomem) < 0x100)
+ dev_err(&pdev->dev, "Invalid iomem size. You may "
+ "experience problems.\n");
+
+ /* Some PCI-based MFD need the parent here */
+ if (pdev->dev.parent != &platform_bus)
+ host = sdhci_alloc_host(pdev->dev.parent, sizeof(*pltfm_host));
+ else
+ host = sdhci_alloc_host(&pdev->dev, sizeof(*pltfm_host));
+
+ if (IS_ERR(host)) {
+ ret = PTR_ERR(host);
+ goto err;
+ }
+
+ pltfm_host = sdhci_priv(host);
+
+ host->hw_name = "platform";
+ if (pdata && pdata->ops)
+ host->ops = pdata->ops;
+ else
+ host->ops = &sdhci_pltfm_ops;
+ if (pdata)
+ host->quirks = pdata->quirks;
+ host->irq = platform_get_irq(pdev, 0);
+
+ if (!request_mem_region(iomem->start, resource_size(iomem),
+ mmc_hostname(host->mmc))) {
+ dev_err(&pdev->dev, "cannot request region\n");
+ ret = -EBUSY;
+ goto err_request;
+ }
+
+ host->ioaddr = ioremap(iomem->start, resource_size(iomem));
+ if (!host->ioaddr) {
+ dev_err(&pdev->dev, "failed to remap registers\n");
+ ret = -ENOMEM;
+ goto err_remap;
+ }
+
+ if (pdata && pdata->init) {
+ ret = pdata->init(host, pdata);
+ if (ret)
+ goto err_plat_init;
+ }
+
+ ret = sdhci_add_host(host);
+ if (ret)
+ goto err_add_host;
+
+ platform_set_drvdata(pdev, host);
+
+ return 0;
+
+err_add_host:
+ if (pdata && pdata->exit)
+ pdata->exit(host);
+err_plat_init:
+ iounmap(host->ioaddr);
+err_remap:
+ release_mem_region(iomem->start, resource_size(iomem));
+err_request:
+ sdhci_free_host(host);
+err:
+ printk(KERN_ERR"Probing of sdhci-pltfm failed: %d\n", ret);
+ return ret;
+}
+
+static int __devexit sdhci_pltfm_remove(struct platform_device *pdev)
+{
+ const struct platform_device_id *platid = platform_get_device_id(pdev);
+ struct sdhci_pltfm_data *pdata;
+ struct sdhci_host *host = platform_get_drvdata(pdev);
+ struct resource *iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ int dead;
+ u32 scratch;
+
+ if (platid && platid->driver_data)
+ pdata = (void *)platid->driver_data;
+ else
+ pdata = pdev->dev.platform_data;
+
+ dead = 0;
+ scratch = readl(host->ioaddr + SDHCI_INT_STATUS);
+ if (scratch == (u32)-1)
+ dead = 1;
+
+ sdhci_remove_host(host, dead);
+ if (pdata && pdata->exit)
+ pdata->exit(host);
+ iounmap(host->ioaddr);
+ release_mem_region(iomem->start, resource_size(iomem));
+ sdhci_free_host(host);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+static const struct platform_device_id sdhci_pltfm_ids[] = {
+ { "sdhci", },
+#ifdef CONFIG_MMC_SDHCI_CNS3XXX
+ { "sdhci-cns3xxx", (kernel_ulong_t)&sdhci_cns3xxx_pdata },
+#endif
+#ifdef CONFIG_MMC_SDHCI_ESDHC_IMX
+ { "sdhci-esdhc-imx", (kernel_ulong_t)&sdhci_esdhc_imx_pdata },
+#endif
+#ifdef CONFIG_MMC_SDHCI_DOVE
+ { "sdhci-dove", (kernel_ulong_t)&sdhci_dove_pdata },
+#endif
+#ifdef CONFIG_MMC_SDHCI_TEGRA
+ { "sdhci-tegra", (kernel_ulong_t)&sdhci_tegra_pdata },
+#endif
+ { },
+};
+MODULE_DEVICE_TABLE(platform, sdhci_pltfm_ids);
+
+#ifdef CONFIG_PM
+static int sdhci_pltfm_suspend(struct platform_device *dev, pm_message_t state)
+{
+ struct sdhci_host *host = platform_get_drvdata(dev);
+
+ return sdhci_suspend_host(host, state);
+}
+
+static int sdhci_pltfm_resume(struct platform_device *dev)
+{
+ struct sdhci_host *host = platform_get_drvdata(dev);
+
+ return sdhci_resume_host(host);
+}
+#else
+#define sdhci_pltfm_suspend NULL
+#define sdhci_pltfm_resume NULL
+#endif /* CONFIG_PM */
+
+static struct platform_driver sdhci_pltfm_driver = {
+ .driver = {
+ .name = "sdhci",
+ .owner = THIS_MODULE,
+ },
+ .probe = sdhci_pltfm_probe,
+ .remove = __devexit_p(sdhci_pltfm_remove),
+ .id_table = sdhci_pltfm_ids,
+ .suspend = sdhci_pltfm_suspend,
+ .resume = sdhci_pltfm_resume,
+};
+
+/*****************************************************************************\
+ * *
+ * Driver init/exit *
+ * *
+\*****************************************************************************/
+
+static int __init sdhci_drv_init(void)
+{
+ return platform_driver_register(&sdhci_pltfm_driver);
+}
+
+static void __exit sdhci_drv_exit(void)
+{
+ platform_driver_unregister(&sdhci_pltfm_driver);
+}
+
+module_init(sdhci_drv_init);
+module_exit(sdhci_drv_exit);
+
+MODULE_DESCRIPTION("Secure Digital Host Controller Interface platform driver");
+MODULE_AUTHOR("Mocean Laboratories <info@mocean-labs.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mmc/host/sdhci-pltfm.h b/drivers/mmc/host/sdhci-pltfm.h
new file mode 100644
index 00000000..2b37016a
--- /dev/null
+++ b/drivers/mmc/host/sdhci-pltfm.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2010 MontaVista Software, LLC.
+ *
+ * Author: Anton Vorontsov <avorontsov@ru.mvista.com>
+ *
+ * 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_SDHCI_PLTFM_H
+#define _DRIVERS_MMC_SDHCI_PLTFM_H
+
+#include <linux/clk.h>
+#include <linux/types.h>
+#include <linux/mmc/sdhci-pltfm.h>
+
+struct sdhci_pltfm_host {
+ struct clk *clk;
+ void *priv; /* to handle quirks across io-accessor calls */
+};
+
+extern struct sdhci_pltfm_data sdhci_cns3xxx_pdata;
+extern struct sdhci_pltfm_data sdhci_esdhc_imx_pdata;
+extern struct sdhci_pltfm_data sdhci_dove_pdata;
+extern struct sdhci_pltfm_data sdhci_tegra_pdata;
+
+#endif /* _DRIVERS_MMC_SDHCI_PLTFM_H */
diff --git a/drivers/mmc/host/sdhci-pxa.c b/drivers/mmc/host/sdhci-pxa.c
new file mode 100644
index 00000000..089c9a68
--- /dev/null
+++ b/drivers/mmc/host/sdhci-pxa.c
@@ -0,0 +1,303 @@
+/* linux/drivers/mmc/host/sdhci-pxa.c
+ *
+ * Copyright (C) 2010 Marvell International Ltd.
+ * Zhangfei Gao <zhangfei.gao@marvell.com>
+ * Kevin Wang <dwang4@marvell.com>
+ * Mingwei Wang <mwwang@marvell.com>
+ * Philip Rakity <prakity@marvell.com>
+ * Mark Brown <markb@marvell.com>
+ *
+ * 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.
+ */
+
+/* Supports:
+ * SDHCI support for MMP2/PXA910/PXA168
+ *
+ * Refer to sdhci-s3c.c.
+ */
+
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/mmc/host.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <plat/sdhci.h>
+#include "sdhci.h"
+
+#define DRIVER_NAME "sdhci-pxa"
+
+#define SD_FIFO_PARAM 0x104
+#define DIS_PAD_SD_CLK_GATE 0x400
+
+struct sdhci_pxa {
+ struct sdhci_host *host;
+ struct sdhci_pxa_platdata *pdata;
+ struct clk *clk;
+ struct resource *res;
+
+ u8 clk_enable;
+};
+
+/*****************************************************************************\
+ * *
+ * SDHCI core callbacks *
+ * *
+\*****************************************************************************/
+static void set_clock(struct sdhci_host *host, unsigned int clock)
+{
+ struct sdhci_pxa *pxa = sdhci_priv(host);
+ u32 tmp = 0;
+
+ if (clock == 0) {
+ if (pxa->clk_enable) {
+ clk_disable(pxa->clk);
+ pxa->clk_enable = 0;
+ }
+ } else {
+ if (0 == pxa->clk_enable) {
+ if (pxa->pdata->flags & PXA_FLAG_DISABLE_CLOCK_GATING) {
+ tmp = readl(host->ioaddr + SD_FIFO_PARAM);
+ tmp |= DIS_PAD_SD_CLK_GATE;
+ writel(tmp, host->ioaddr + SD_FIFO_PARAM);
+ }
+ clk_enable(pxa->clk);
+ pxa->clk_enable = 1;
+ }
+ }
+}
+
+static int set_uhs_signaling(struct sdhci_host *host, unsigned int uhs)
+{
+ u16 ctrl_2;
+
+ /*
+ * Set V18_EN -- UHS modes do not work without this.
+ * does not change signaling voltage
+ */
+ ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+
+ /* Select Bus Speed Mode for host */
+ ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
+ switch (uhs) {
+ case MMC_TIMING_UHS_SDR12:
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
+ break;
+ case MMC_TIMING_UHS_SDR25:
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
+ break;
+ case MMC_TIMING_UHS_SDR50:
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR50 | SDHCI_CTRL_VDD_180;
+ break;
+ case MMC_TIMING_UHS_SDR104:
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR104 | SDHCI_CTRL_VDD_180;
+ break;
+ case MMC_TIMING_UHS_DDR50:
+ ctrl_2 |= SDHCI_CTRL_UHS_DDR50 | SDHCI_CTRL_VDD_180;
+ break;
+ }
+
+ sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
+ pr_debug("%s:%s uhs = %d, ctrl_2 = %04X\n",
+ __func__, mmc_hostname(host->mmc), uhs, ctrl_2);
+
+ return 0;
+}
+
+static struct sdhci_ops sdhci_pxa_ops = {
+ .set_uhs_signaling = set_uhs_signaling,
+ .set_clock = set_clock,
+};
+
+/*****************************************************************************\
+ * *
+ * Device probing/removal *
+ * *
+\*****************************************************************************/
+
+static int __devinit sdhci_pxa_probe(struct platform_device *pdev)
+{
+ struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data;
+ struct device *dev = &pdev->dev;
+ struct sdhci_host *host = NULL;
+ struct resource *iomem = NULL;
+ struct sdhci_pxa *pxa = NULL;
+ int ret, irq;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(dev, "no irq specified\n");
+ return irq;
+ }
+
+ iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!iomem) {
+ dev_err(dev, "no memory specified\n");
+ return -ENOENT;
+ }
+
+ host = sdhci_alloc_host(&pdev->dev, sizeof(struct sdhci_pxa));
+ if (IS_ERR(host)) {
+ dev_err(dev, "failed to alloc host\n");
+ return PTR_ERR(host);
+ }
+
+ pxa = sdhci_priv(host);
+ pxa->host = host;
+ pxa->pdata = pdata;
+ pxa->clk_enable = 0;
+
+ pxa->clk = clk_get(dev, "PXA-SDHCLK");
+ if (IS_ERR(pxa->clk)) {
+ dev_err(dev, "failed to get io clock\n");
+ ret = PTR_ERR(pxa->clk);
+ goto out;
+ }
+
+ pxa->res = request_mem_region(iomem->start, resource_size(iomem),
+ mmc_hostname(host->mmc));
+ if (!pxa->res) {
+ dev_err(&pdev->dev, "cannot request region\n");
+ ret = -EBUSY;
+ goto out;
+ }
+
+ host->ioaddr = ioremap(iomem->start, resource_size(iomem));
+ if (!host->ioaddr) {
+ dev_err(&pdev->dev, "failed to remap registers\n");
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ host->hw_name = "MMC";
+ host->ops = &sdhci_pxa_ops;
+ host->irq = irq;
+ host->quirks = SDHCI_QUIRK_BROKEN_ADMA
+ | SDHCI_QUIRK_BROKEN_TIMEOUT_VAL
+ | SDHCI_QUIRK_32BIT_DMA_ADDR
+ | SDHCI_QUIRK_32BIT_DMA_SIZE
+ | SDHCI_QUIRK_32BIT_ADMA_SIZE
+ | SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC;
+
+ if (pdata->quirks)
+ host->quirks |= pdata->quirks;
+
+ /* enable 1/8V DDR capable */
+ host->mmc->caps |= MMC_CAP_1_8V_DDR;
+
+ /* If slot design supports 8 bit data, indicate this to MMC. */
+ if (pdata->flags & PXA_FLAG_SD_8_BIT_CAPABLE_SLOT)
+ host->mmc->caps |= MMC_CAP_8_BIT_DATA;
+
+ ret = sdhci_add_host(host);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add host\n");
+ goto out;
+ }
+
+ if (pxa->pdata->max_speed)
+ host->mmc->f_max = pxa->pdata->max_speed;
+
+ platform_set_drvdata(pdev, host);
+
+ return 0;
+out:
+ if (host) {
+ clk_put(pxa->clk);
+ if (host->ioaddr)
+ iounmap(host->ioaddr);
+ if (pxa->res)
+ release_mem_region(pxa->res->start,
+ resource_size(pxa->res));
+ sdhci_free_host(host);
+ }
+
+ return ret;
+}
+
+static int __devexit sdhci_pxa_remove(struct platform_device *pdev)
+{
+ struct sdhci_host *host = platform_get_drvdata(pdev);
+ struct sdhci_pxa *pxa = sdhci_priv(host);
+ int dead = 0;
+ u32 scratch;
+
+ if (host) {
+ scratch = readl(host->ioaddr + SDHCI_INT_STATUS);
+ if (scratch == (u32)-1)
+ dead = 1;
+
+ sdhci_remove_host(host, dead);
+
+ if (host->ioaddr)
+ iounmap(host->ioaddr);
+ if (pxa->res)
+ release_mem_region(pxa->res->start,
+ resource_size(pxa->res));
+ if (pxa->clk_enable) {
+ clk_disable(pxa->clk);
+ pxa->clk_enable = 0;
+ }
+ clk_put(pxa->clk);
+
+ sdhci_free_host(host);
+ platform_set_drvdata(pdev, NULL);
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int sdhci_pxa_suspend(struct platform_device *dev, pm_message_t state)
+{
+ struct sdhci_host *host = platform_get_drvdata(dev);
+
+ return sdhci_suspend_host(host, state);
+}
+
+static int sdhci_pxa_resume(struct platform_device *dev)
+{
+ struct sdhci_host *host = platform_get_drvdata(dev);
+
+ return sdhci_resume_host(host);
+}
+#else
+#define sdhci_pxa_suspend NULL
+#define sdhci_pxa_resume NULL
+#endif
+
+static struct platform_driver sdhci_pxa_driver = {
+ .probe = sdhci_pxa_probe,
+ .remove = __devexit_p(sdhci_pxa_remove),
+ .suspend = sdhci_pxa_suspend,
+ .resume = sdhci_pxa_resume,
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+/*****************************************************************************\
+ * *
+ * Driver init/exit *
+ * *
+\*****************************************************************************/
+
+static int __init sdhci_pxa_init(void)
+{
+ return platform_driver_register(&sdhci_pxa_driver);
+}
+
+static void __exit sdhci_pxa_exit(void)
+{
+ platform_driver_unregister(&sdhci_pxa_driver);
+}
+
+module_init(sdhci_pxa_init);
+module_exit(sdhci_pxa_exit);
+
+MODULE_DESCRIPTION("SDH controller driver for PXA168/PXA910/MMP2");
+MODULE_AUTHOR("Zhangfei Gao <zhangfei.gao@marvell.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c
new file mode 100644
index 00000000..8cd999f4
--- /dev/null
+++ b/drivers/mmc/host/sdhci-s3c.c
@@ -0,0 +1,661 @@
+/* linux/drivers/mmc/host/sdhci-s3c.c
+ *
+ * Copyright 2008 Openmoko Inc.
+ * Copyright 2008 Simtec Electronics
+ * Ben Dooks <ben@simtec.co.uk>
+ * http://armlinux.simtec.co.uk/
+ *
+ * SDHCI (HSMMC) support for Samsung SoC
+ *
+ * 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/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+
+#include <linux/mmc/host.h>
+
+#include <plat/sdhci.h>
+#include <plat/regs-sdhci.h>
+
+#include "sdhci.h"
+
+#define MAX_BUS_CLK (4)
+
+/**
+ * struct sdhci_s3c - S3C SDHCI instance
+ * @host: The SDHCI host created
+ * @pdev: The platform device we where created from.
+ * @ioarea: The resource created when we claimed the IO area.
+ * @pdata: The platform data for this controller.
+ * @cur_clk: The index of the current bus clock.
+ * @clk_io: The clock for the internal bus interface.
+ * @clk_bus: The clocks that are available for the SD/MMC bus clock.
+ */
+struct sdhci_s3c {
+ struct sdhci_host *host;
+ struct platform_device *pdev;
+ struct resource *ioarea;
+ struct s3c_sdhci_platdata *pdata;
+ unsigned int cur_clk;
+ int ext_cd_irq;
+ int ext_cd_gpio;
+
+ struct clk *clk_io;
+ struct clk *clk_bus[MAX_BUS_CLK];
+};
+
+static inline struct sdhci_s3c *to_s3c(struct sdhci_host *host)
+{
+ return sdhci_priv(host);
+}
+
+/**
+ * get_curclk - convert ctrl2 register to clock source number
+ * @ctrl2: Control2 register value.
+ */
+static u32 get_curclk(u32 ctrl2)
+{
+ ctrl2 &= S3C_SDHCI_CTRL2_SELBASECLK_MASK;
+ ctrl2 >>= S3C_SDHCI_CTRL2_SELBASECLK_SHIFT;
+
+ return ctrl2;
+}
+
+static void sdhci_s3c_check_sclk(struct sdhci_host *host)
+{
+ struct sdhci_s3c *ourhost = to_s3c(host);
+ u32 tmp = readl(host->ioaddr + S3C_SDHCI_CONTROL2);
+
+ if (get_curclk(tmp) != ourhost->cur_clk) {
+ dev_dbg(&ourhost->pdev->dev, "restored ctrl2 clock setting\n");
+
+ tmp &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK;
+ tmp |= ourhost->cur_clk << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT;
+ writel(tmp, host->ioaddr + 0x80);
+ }
+}
+
+/**
+ * sdhci_s3c_get_max_clk - callback to get maximum clock frequency.
+ * @host: The SDHCI host instance.
+ *
+ * Callback to return the maximum clock rate acheivable by the controller.
+*/
+static unsigned int sdhci_s3c_get_max_clk(struct sdhci_host *host)
+{
+ struct sdhci_s3c *ourhost = to_s3c(host);
+ struct clk *busclk;
+ unsigned int rate, max;
+ int clk;
+
+ /* note, a reset will reset the clock source */
+
+ sdhci_s3c_check_sclk(host);
+
+ for (max = 0, clk = 0; clk < MAX_BUS_CLK; clk++) {
+ busclk = ourhost->clk_bus[clk];
+ if (!busclk)
+ continue;
+
+ rate = clk_get_rate(busclk);
+ if (rate > max)
+ max = rate;
+ }
+
+ return max;
+}
+
+/**
+ * sdhci_s3c_consider_clock - consider one the bus clocks for current setting
+ * @ourhost: Our SDHCI instance.
+ * @src: The source clock index.
+ * @wanted: The clock frequency wanted.
+ */
+static unsigned int sdhci_s3c_consider_clock(struct sdhci_s3c *ourhost,
+ unsigned int src,
+ unsigned int wanted)
+{
+ unsigned long rate;
+ struct clk *clksrc = ourhost->clk_bus[src];
+ int div;
+
+ if (!clksrc)
+ return UINT_MAX;
+
+ /*
+ * Clock divider's step is different as 1 from that of host controller
+ * when 'clk_type' is S3C_SDHCI_CLK_DIV_EXTERNAL.
+ */
+ if (ourhost->pdata->clk_type) {
+ rate = clk_round_rate(clksrc, wanted);
+ return wanted - rate;
+ }
+
+ rate = clk_get_rate(clksrc);
+
+ for (div = 1; div < 256; div *= 2) {
+ if ((rate / div) <= wanted)
+ break;
+ }
+
+ dev_dbg(&ourhost->pdev->dev, "clk %d: rate %ld, want %d, got %ld\n",
+ src, rate, wanted, rate / div);
+
+ return (wanted - (rate / div));
+}
+
+/**
+ * sdhci_s3c_set_clock - callback on clock change
+ * @host: The SDHCI host being changed
+ * @clock: The clock rate being requested.
+ *
+ * When the card's clock is going to be changed, look at the new frequency
+ * and find the best clock source to go with it.
+*/
+static void sdhci_s3c_set_clock(struct sdhci_host *host, unsigned int clock)
+{
+ struct sdhci_s3c *ourhost = to_s3c(host);
+ unsigned int best = UINT_MAX;
+ unsigned int delta;
+ int best_src = 0;
+ int src;
+ u32 ctrl;
+
+ /* don't bother if the clock is going off. */
+ if (clock == 0)
+ return;
+
+ for (src = 0; src < MAX_BUS_CLK; src++) {
+ delta = sdhci_s3c_consider_clock(ourhost, src, clock);
+ if (delta < best) {
+ best = delta;
+ best_src = src;
+ }
+ }
+
+ dev_dbg(&ourhost->pdev->dev,
+ "selected source %d, clock %d, delta %d\n",
+ best_src, clock, best);
+
+ /* select the new clock source */
+
+ if (ourhost->cur_clk != best_src) {
+ struct clk *clk = ourhost->clk_bus[best_src];
+
+ /* turn clock off to card before changing clock source */
+ writew(0, host->ioaddr + SDHCI_CLOCK_CONTROL);
+
+ ourhost->cur_clk = best_src;
+ host->max_clk = clk_get_rate(clk);
+
+ ctrl = readl(host->ioaddr + S3C_SDHCI_CONTROL2);
+ ctrl &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK;
+ ctrl |= best_src << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT;
+ writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL2);
+ }
+
+ /* reconfigure the hardware for new clock rate */
+
+ {
+ struct mmc_ios ios;
+
+ ios.clock = clock;
+
+ if (ourhost->pdata->cfg_card)
+ (ourhost->pdata->cfg_card)(ourhost->pdev, host->ioaddr,
+ &ios, NULL);
+ }
+}
+
+/**
+ * sdhci_s3c_get_min_clock - callback to get minimal supported clock value
+ * @host: The SDHCI host being queried
+ *
+ * To init mmc host properly a minimal clock value is needed. For high system
+ * bus clock's values the standard formula gives values out of allowed range.
+ * The clock still can be set to lower values, if clock source other then
+ * system bus is selected.
+*/
+static unsigned int sdhci_s3c_get_min_clock(struct sdhci_host *host)
+{
+ struct sdhci_s3c *ourhost = to_s3c(host);
+ unsigned int delta, min = UINT_MAX;
+ int src;
+
+ for (src = 0; src < MAX_BUS_CLK; src++) {
+ delta = sdhci_s3c_consider_clock(ourhost, src, 0);
+ if (delta == UINT_MAX)
+ continue;
+ /* delta is a negative value in this case */
+ if (-delta < min)
+ min = -delta;
+ }
+ return min;
+}
+
+/* sdhci_cmu_get_max_clk - callback to get maximum clock frequency.*/
+static unsigned int sdhci_cmu_get_max_clock(struct sdhci_host *host)
+{
+ struct sdhci_s3c *ourhost = to_s3c(host);
+
+ return clk_round_rate(ourhost->clk_bus[ourhost->cur_clk], UINT_MAX);
+}
+
+/* sdhci_cmu_get_min_clock - callback to get minimal supported clock value. */
+static unsigned int sdhci_cmu_get_min_clock(struct sdhci_host *host)
+{
+ struct sdhci_s3c *ourhost = to_s3c(host);
+
+ /*
+ * initial clock can be in the frequency range of
+ * 100KHz-400KHz, so we set it as max value.
+ */
+ return clk_round_rate(ourhost->clk_bus[ourhost->cur_clk], 400000);
+}
+
+/* sdhci_cmu_set_clock - callback on clock change.*/
+static void sdhci_cmu_set_clock(struct sdhci_host *host, unsigned int clock)
+{
+ struct sdhci_s3c *ourhost = to_s3c(host);
+
+ /* don't bother if the clock is going off */
+ if (clock == 0)
+ return;
+
+ sdhci_s3c_set_clock(host, clock);
+
+ clk_set_rate(ourhost->clk_bus[ourhost->cur_clk], clock);
+
+ host->clock = clock;
+}
+
+/**
+ * sdhci_s3c_platform_8bit_width - support 8bit buswidth
+ * @host: The SDHCI host being queried
+ * @width: MMC_BUS_WIDTH_ macro for the bus width being requested
+ *
+ * We have 8-bit width support but is not a v3 controller.
+ * So we add platform_8bit_width() and support 8bit width.
+ */
+static int sdhci_s3c_platform_8bit_width(struct sdhci_host *host, int width)
+{
+ u8 ctrl;
+
+ ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);
+
+ switch (width) {
+ case MMC_BUS_WIDTH_8:
+ ctrl |= SDHCI_CTRL_8BITBUS;
+ ctrl &= ~SDHCI_CTRL_4BITBUS;
+ break;
+ case MMC_BUS_WIDTH_4:
+ ctrl |= SDHCI_CTRL_4BITBUS;
+ ctrl &= ~SDHCI_CTRL_8BITBUS;
+ break;
+ default:
+ ctrl &= ~SDHCI_CTRL_4BITBUS;
+ ctrl &= ~SDHCI_CTRL_8BITBUS;
+ break;
+ }
+
+ sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
+
+ return 0;
+}
+
+static struct sdhci_ops sdhci_s3c_ops = {
+ .get_max_clock = sdhci_s3c_get_max_clk,
+ .set_clock = sdhci_s3c_set_clock,
+ .get_min_clock = sdhci_s3c_get_min_clock,
+ .platform_8bit_width = sdhci_s3c_platform_8bit_width,
+};
+
+static void sdhci_s3c_notify_change(struct platform_device *dev, int state)
+{
+ struct sdhci_host *host = platform_get_drvdata(dev);
+ unsigned long flags;
+
+ if (host) {
+ spin_lock_irqsave(&host->lock, flags);
+ if (state) {
+ dev_dbg(&dev->dev, "card inserted.\n");
+ host->flags &= ~SDHCI_DEVICE_DEAD;
+ host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
+ } else {
+ dev_dbg(&dev->dev, "card removed.\n");
+ host->flags |= SDHCI_DEVICE_DEAD;
+ host->quirks &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION;
+ }
+ tasklet_schedule(&host->card_tasklet);
+ spin_unlock_irqrestore(&host->lock, flags);
+ }
+}
+
+static irqreturn_t sdhci_s3c_gpio_card_detect_thread(int irq, void *dev_id)
+{
+ struct sdhci_s3c *sc = dev_id;
+ int status = gpio_get_value(sc->ext_cd_gpio);
+ if (sc->pdata->ext_cd_gpio_invert)
+ status = !status;
+ sdhci_s3c_notify_change(sc->pdev, status);
+ return IRQ_HANDLED;
+}
+
+static void sdhci_s3c_setup_card_detect_gpio(struct sdhci_s3c *sc)
+{
+ struct s3c_sdhci_platdata *pdata = sc->pdata;
+ struct device *dev = &sc->pdev->dev;
+
+ if (gpio_request(pdata->ext_cd_gpio, "SDHCI EXT CD") == 0) {
+ sc->ext_cd_gpio = pdata->ext_cd_gpio;
+ sc->ext_cd_irq = gpio_to_irq(pdata->ext_cd_gpio);
+ if (sc->ext_cd_irq &&
+ request_threaded_irq(sc->ext_cd_irq, NULL,
+ sdhci_s3c_gpio_card_detect_thread,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ dev_name(dev), sc) == 0) {
+ int status = gpio_get_value(sc->ext_cd_gpio);
+ if (pdata->ext_cd_gpio_invert)
+ status = !status;
+ sdhci_s3c_notify_change(sc->pdev, status);
+ } else {
+ dev_warn(dev, "cannot request irq for card detect\n");
+ sc->ext_cd_irq = 0;
+ }
+ } else {
+ dev_err(dev, "cannot request gpio for card detect\n");
+ }
+}
+
+static int __devinit sdhci_s3c_probe(struct platform_device *pdev)
+{
+ struct s3c_sdhci_platdata *pdata = pdev->dev.platform_data;
+ struct device *dev = &pdev->dev;
+ struct sdhci_host *host;
+ struct sdhci_s3c *sc;
+ struct resource *res;
+ int ret, irq, ptr, clks;
+
+ if (!pdata) {
+ dev_err(dev, "no device data specified\n");
+ return -ENOENT;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(dev, "no irq specified\n");
+ return irq;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "no memory specified\n");
+ return -ENOENT;
+ }
+
+ host = sdhci_alloc_host(dev, sizeof(struct sdhci_s3c));
+ if (IS_ERR(host)) {
+ dev_err(dev, "sdhci_alloc_host() failed\n");
+ return PTR_ERR(host);
+ }
+
+ sc = sdhci_priv(host);
+
+ sc->host = host;
+ sc->pdev = pdev;
+ sc->pdata = pdata;
+ sc->ext_cd_gpio = -1; /* invalid gpio number */
+
+ platform_set_drvdata(pdev, host);
+
+ sc->clk_io = clk_get(dev, "hsmmc");
+ if (IS_ERR(sc->clk_io)) {
+ dev_err(dev, "failed to get io clock\n");
+ ret = PTR_ERR(sc->clk_io);
+ goto err_io_clk;
+ }
+
+ /* enable the local io clock and keep it running for the moment. */
+ clk_enable(sc->clk_io);
+
+ for (clks = 0, ptr = 0; ptr < MAX_BUS_CLK; ptr++) {
+ struct clk *clk;
+ char *name = pdata->clocks[ptr];
+
+ if (name == NULL)
+ continue;
+
+ clk = clk_get(dev, name);
+ if (IS_ERR(clk)) {
+ dev_err(dev, "failed to get clock %s\n", name);
+ continue;
+ }
+
+ clks++;
+ sc->clk_bus[ptr] = clk;
+
+ /*
+ * save current clock index to know which clock bus
+ * is used later in overriding functions.
+ */
+ sc->cur_clk = ptr;
+
+ clk_enable(clk);
+
+ dev_info(dev, "clock source %d: %s (%ld Hz)\n",
+ ptr, name, clk_get_rate(clk));
+ }
+
+ if (clks == 0) {
+ dev_err(dev, "failed to find any bus clocks\n");
+ ret = -ENOENT;
+ goto err_no_busclks;
+ }
+
+ sc->ioarea = request_mem_region(res->start, resource_size(res),
+ mmc_hostname(host->mmc));
+ if (!sc->ioarea) {
+ dev_err(dev, "failed to reserve register area\n");
+ ret = -ENXIO;
+ goto err_req_regs;
+ }
+
+ host->ioaddr = ioremap_nocache(res->start, resource_size(res));
+ if (!host->ioaddr) {
+ dev_err(dev, "failed to map registers\n");
+ ret = -ENXIO;
+ goto err_req_regs;
+ }
+
+ /* Ensure we have minimal gpio selected CMD/CLK/Detect */
+ if (pdata->cfg_gpio)
+ pdata->cfg_gpio(pdev, pdata->max_width);
+
+ host->hw_name = "samsung-hsmmc";
+ host->ops = &sdhci_s3c_ops;
+ host->quirks = 0;
+ host->irq = irq;
+
+ /* Setup quirks for the controller */
+ host->quirks |= SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC;
+ host->quirks |= SDHCI_QUIRK_NO_HISPD_BIT;
+
+#ifndef CONFIG_MMC_SDHCI_S3C_DMA
+
+ /* we currently see overruns on errors, so disable the SDMA
+ * support as well. */
+ host->quirks |= SDHCI_QUIRK_BROKEN_DMA;
+
+#endif /* CONFIG_MMC_SDHCI_S3C_DMA */
+
+ /* It seems we do not get an DATA transfer complete on non-busy
+ * transfers, not sure if this is a problem with this specific
+ * SDHCI block, or a missing configuration that needs to be set. */
+ host->quirks |= SDHCI_QUIRK_NO_BUSY_IRQ;
+
+ /* This host supports the Auto CMD12 */
+ host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
+
+ if (pdata->cd_type == S3C_SDHCI_CD_NONE ||
+ pdata->cd_type == S3C_SDHCI_CD_PERMANENT)
+ host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
+
+ if (pdata->cd_type == S3C_SDHCI_CD_PERMANENT)
+ host->mmc->caps = MMC_CAP_NONREMOVABLE;
+
+ if (pdata->host_caps)
+ host->mmc->caps |= pdata->host_caps;
+
+ host->quirks |= (SDHCI_QUIRK_32BIT_DMA_ADDR |
+ SDHCI_QUIRK_32BIT_DMA_SIZE);
+
+ /* HSMMC on Samsung SoCs uses SDCLK as timeout clock */
+ host->quirks |= SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK;
+
+ /*
+ * If controller does not have internal clock divider,
+ * we can use overriding functions instead of default.
+ */
+ if (pdata->clk_type) {
+ sdhci_s3c_ops.set_clock = sdhci_cmu_set_clock;
+ sdhci_s3c_ops.get_min_clock = sdhci_cmu_get_min_clock;
+ sdhci_s3c_ops.get_max_clock = sdhci_cmu_get_max_clock;
+ }
+
+ /* It supports additional host capabilities if needed */
+ if (pdata->host_caps)
+ host->mmc->caps |= pdata->host_caps;
+
+ ret = sdhci_add_host(host);
+ if (ret) {
+ dev_err(dev, "sdhci_add_host() failed\n");
+ goto err_add_host;
+ }
+
+ /* The following two methods of card detection might call
+ sdhci_s3c_notify_change() immediately, so they can be called
+ only after sdhci_add_host(). Setup errors are ignored. */
+ if (pdata->cd_type == S3C_SDHCI_CD_EXTERNAL && pdata->ext_cd_init)
+ pdata->ext_cd_init(&sdhci_s3c_notify_change);
+ if (pdata->cd_type == S3C_SDHCI_CD_GPIO &&
+ gpio_is_valid(pdata->ext_cd_gpio))
+ sdhci_s3c_setup_card_detect_gpio(sc);
+
+ return 0;
+
+ err_add_host:
+ release_resource(sc->ioarea);
+ kfree(sc->ioarea);
+
+ err_req_regs:
+ for (ptr = 0; ptr < MAX_BUS_CLK; ptr++) {
+ clk_disable(sc->clk_bus[ptr]);
+ clk_put(sc->clk_bus[ptr]);
+ }
+
+ err_no_busclks:
+ clk_disable(sc->clk_io);
+ clk_put(sc->clk_io);
+
+ err_io_clk:
+ sdhci_free_host(host);
+
+ return ret;
+}
+
+static int __devexit sdhci_s3c_remove(struct platform_device *pdev)
+{
+ struct s3c_sdhci_platdata *pdata = pdev->dev.platform_data;
+ struct sdhci_host *host = platform_get_drvdata(pdev);
+ struct sdhci_s3c *sc = sdhci_priv(host);
+ int ptr;
+
+ if (pdata->cd_type == S3C_SDHCI_CD_EXTERNAL && pdata->ext_cd_cleanup)
+ pdata->ext_cd_cleanup(&sdhci_s3c_notify_change);
+
+ if (sc->ext_cd_irq)
+ free_irq(sc->ext_cd_irq, sc);
+
+ if (gpio_is_valid(sc->ext_cd_gpio))
+ gpio_free(sc->ext_cd_gpio);
+
+ sdhci_remove_host(host, 1);
+
+ for (ptr = 0; ptr < 3; ptr++) {
+ if (sc->clk_bus[ptr]) {
+ clk_disable(sc->clk_bus[ptr]);
+ clk_put(sc->clk_bus[ptr]);
+ }
+ }
+ clk_disable(sc->clk_io);
+ clk_put(sc->clk_io);
+
+ iounmap(host->ioaddr);
+ release_resource(sc->ioarea);
+ kfree(sc->ioarea);
+
+ sdhci_free_host(host);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+
+static int sdhci_s3c_suspend(struct platform_device *dev, pm_message_t pm)
+{
+ struct sdhci_host *host = platform_get_drvdata(dev);
+
+ sdhci_suspend_host(host, pm);
+ return 0;
+}
+
+static int sdhci_s3c_resume(struct platform_device *dev)
+{
+ struct sdhci_host *host = platform_get_drvdata(dev);
+
+ sdhci_resume_host(host);
+ return 0;
+}
+
+#else
+#define sdhci_s3c_suspend NULL
+#define sdhci_s3c_resume NULL
+#endif
+
+static struct platform_driver sdhci_s3c_driver = {
+ .probe = sdhci_s3c_probe,
+ .remove = __devexit_p(sdhci_s3c_remove),
+ .suspend = sdhci_s3c_suspend,
+ .resume = sdhci_s3c_resume,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "s3c-sdhci",
+ },
+};
+
+static int __init sdhci_s3c_init(void)
+{
+ return platform_driver_register(&sdhci_s3c_driver);
+}
+
+static void __exit sdhci_s3c_exit(void)
+{
+ platform_driver_unregister(&sdhci_s3c_driver);
+}
+
+module_init(sdhci_s3c_init);
+module_exit(sdhci_s3c_exit);
+
+MODULE_DESCRIPTION("Samsung SDHCI (HSMMC) glue");
+MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:s3c-sdhci");
diff --git a/drivers/mmc/host/sdhci-spear.c b/drivers/mmc/host/sdhci-spear.c
new file mode 100644
index 00000000..60a4c97d
--- /dev/null
+++ b/drivers/mmc/host/sdhci-spear.c
@@ -0,0 +1,298 @@
+/*
+ * drivers/mmc/host/sdhci-spear.c
+ *
+ * Support of SDHCI platform devices for spear soc family
+ *
+ * Copyright (C) 2010 ST Microelectronics
+ * Viresh Kumar<viresh.kumar@st.com>
+ *
+ * Inspired by sdhci-pltfm.c
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/highmem.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sdhci-spear.h>
+#include <linux/io.h>
+#include "sdhci.h"
+
+struct spear_sdhci {
+ struct clk *clk;
+ struct sdhci_plat_data *data;
+};
+
+/* sdhci ops */
+static struct sdhci_ops sdhci_pltfm_ops = {
+ /* Nothing to do for now. */
+};
+
+/* gpio card detection interrupt handler */
+static irqreturn_t sdhci_gpio_irq(int irq, void *dev_id)
+{
+ struct platform_device *pdev = dev_id;
+ struct sdhci_host *host = platform_get_drvdata(pdev);
+ struct spear_sdhci *sdhci = dev_get_platdata(&pdev->dev);
+ unsigned long gpio_irq_type;
+ int val;
+
+ val = gpio_get_value(sdhci->data->card_int_gpio);
+
+ /* val == 1 -> card removed, val == 0 -> card inserted */
+ /* if card removed - set irq for low level, else vice versa */
+ gpio_irq_type = val ? IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH;
+ irq_set_irq_type(irq, gpio_irq_type);
+
+ if (sdhci->data->card_power_gpio >= 0) {
+ if (!sdhci->data->power_always_enb) {
+ /* if card inserted, give power, otherwise remove it */
+ val = sdhci->data->power_active_high ? !val : val ;
+ gpio_set_value(sdhci->data->card_power_gpio, val);
+ }
+ }
+
+ /* inform sdhci driver about card insertion/removal */
+ tasklet_schedule(&host->card_tasklet);
+
+ return IRQ_HANDLED;
+}
+
+static int __devinit sdhci_probe(struct platform_device *pdev)
+{
+ struct sdhci_host *host;
+ struct resource *iomem;
+ struct spear_sdhci *sdhci;
+ int ret;
+
+ BUG_ON(pdev == NULL);
+
+ iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!iomem) {
+ ret = -ENOMEM;
+ dev_dbg(&pdev->dev, "memory resource not defined\n");
+ goto err;
+ }
+
+ if (!request_mem_region(iomem->start, resource_size(iomem),
+ "spear-sdhci")) {
+ ret = -EBUSY;
+ dev_dbg(&pdev->dev, "cannot request region\n");
+ goto err;
+ }
+
+ sdhci = kzalloc(sizeof(*sdhci), GFP_KERNEL);
+ if (!sdhci) {
+ ret = -ENOMEM;
+ dev_dbg(&pdev->dev, "cannot allocate memory for sdhci\n");
+ goto err_kzalloc;
+ }
+
+ /* clk enable */
+ sdhci->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(sdhci->clk)) {
+ ret = PTR_ERR(sdhci->clk);
+ dev_dbg(&pdev->dev, "Error getting clock\n");
+ goto err_clk_get;
+ }
+
+ ret = clk_enable(sdhci->clk);
+ if (ret) {
+ dev_dbg(&pdev->dev, "Error enabling clock\n");
+ goto err_clk_enb;
+ }
+
+ /* overwrite platform_data */
+ sdhci->data = dev_get_platdata(&pdev->dev);
+ pdev->dev.platform_data = sdhci;
+
+ if (pdev->dev.parent)
+ host = sdhci_alloc_host(pdev->dev.parent, 0);
+ else
+ host = sdhci_alloc_host(&pdev->dev, 0);
+
+ if (IS_ERR(host)) {
+ ret = PTR_ERR(host);
+ dev_dbg(&pdev->dev, "error allocating host\n");
+ goto err_alloc_host;
+ }
+
+ host->hw_name = "sdhci";
+ host->ops = &sdhci_pltfm_ops;
+ host->irq = platform_get_irq(pdev, 0);
+ host->quirks = SDHCI_QUIRK_BROKEN_ADMA;
+
+ host->ioaddr = ioremap(iomem->start, resource_size(iomem));
+ if (!host->ioaddr) {
+ ret = -ENOMEM;
+ dev_dbg(&pdev->dev, "failed to remap registers\n");
+ goto err_ioremap;
+ }
+
+ ret = sdhci_add_host(host);
+ if (ret) {
+ dev_dbg(&pdev->dev, "error adding host\n");
+ goto err_add_host;
+ }
+
+ platform_set_drvdata(pdev, host);
+
+ /*
+ * It is optional to use GPIOs for sdhci Power control & sdhci card
+ * interrupt detection. If sdhci->data is NULL, then use original sdhci
+ * lines otherwise GPIO lines.
+ * If GPIO is selected for power control, then power should be disabled
+ * after card removal and should be enabled when card insertion
+ * interrupt occurs
+ */
+ if (!sdhci->data)
+ return 0;
+
+ if (sdhci->data->card_power_gpio >= 0) {
+ int val = 0;
+
+ ret = gpio_request(sdhci->data->card_power_gpio, "sdhci");
+ if (ret < 0) {
+ dev_dbg(&pdev->dev, "gpio request fail: %d\n",
+ sdhci->data->card_power_gpio);
+ goto err_pgpio_request;
+ }
+
+ if (sdhci->data->power_always_enb)
+ val = sdhci->data->power_active_high;
+ else
+ val = !sdhci->data->power_active_high;
+
+ ret = gpio_direction_output(sdhci->data->card_power_gpio, val);
+ if (ret) {
+ dev_dbg(&pdev->dev, "gpio set direction fail: %d\n",
+ sdhci->data->card_power_gpio);
+ goto err_pgpio_direction;
+ }
+
+ gpio_set_value(sdhci->data->card_power_gpio, 1);
+ }
+
+ if (sdhci->data->card_int_gpio >= 0) {
+ ret = gpio_request(sdhci->data->card_int_gpio, "sdhci");
+ if (ret < 0) {
+ dev_dbg(&pdev->dev, "gpio request fail: %d\n",
+ sdhci->data->card_int_gpio);
+ goto err_igpio_request;
+ }
+
+ ret = gpio_direction_input(sdhci->data->card_int_gpio);
+ if (ret) {
+ dev_dbg(&pdev->dev, "gpio set direction fail: %d\n",
+ sdhci->data->card_int_gpio);
+ goto err_igpio_direction;
+ }
+ ret = request_irq(gpio_to_irq(sdhci->data->card_int_gpio),
+ sdhci_gpio_irq, IRQF_TRIGGER_LOW,
+ mmc_hostname(host->mmc), pdev);
+ if (ret) {
+ dev_dbg(&pdev->dev, "gpio request irq fail: %d\n",
+ sdhci->data->card_int_gpio);
+ goto err_igpio_request_irq;
+ }
+
+ }
+
+ return 0;
+
+err_igpio_request_irq:
+err_igpio_direction:
+ if (sdhci->data->card_int_gpio >= 0)
+ gpio_free(sdhci->data->card_int_gpio);
+err_igpio_request:
+err_pgpio_direction:
+ if (sdhci->data->card_power_gpio >= 0)
+ gpio_free(sdhci->data->card_power_gpio);
+err_pgpio_request:
+ platform_set_drvdata(pdev, NULL);
+ sdhci_remove_host(host, 1);
+err_add_host:
+ iounmap(host->ioaddr);
+err_ioremap:
+ sdhci_free_host(host);
+err_alloc_host:
+ clk_disable(sdhci->clk);
+err_clk_enb:
+ clk_put(sdhci->clk);
+err_clk_get:
+ kfree(sdhci);
+err_kzalloc:
+ release_mem_region(iomem->start, resource_size(iomem));
+err:
+ dev_err(&pdev->dev, "spear-sdhci probe failed: %d\n", ret);
+ return ret;
+}
+
+static int __devexit sdhci_remove(struct platform_device *pdev)
+{
+ struct sdhci_host *host = platform_get_drvdata(pdev);
+ struct resource *iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ struct spear_sdhci *sdhci = dev_get_platdata(&pdev->dev);
+ int dead;
+ u32 scratch;
+
+ if (sdhci->data) {
+ if (sdhci->data->card_int_gpio >= 0) {
+ free_irq(gpio_to_irq(sdhci->data->card_int_gpio), pdev);
+ gpio_free(sdhci->data->card_int_gpio);
+ }
+
+ if (sdhci->data->card_power_gpio >= 0)
+ gpio_free(sdhci->data->card_power_gpio);
+ }
+
+ platform_set_drvdata(pdev, NULL);
+ dead = 0;
+ scratch = readl(host->ioaddr + SDHCI_INT_STATUS);
+ if (scratch == (u32)-1)
+ dead = 1;
+
+ sdhci_remove_host(host, dead);
+ iounmap(host->ioaddr);
+ sdhci_free_host(host);
+ clk_disable(sdhci->clk);
+ clk_put(sdhci->clk);
+ kfree(sdhci);
+ if (iomem)
+ release_mem_region(iomem->start, resource_size(iomem));
+
+ return 0;
+}
+
+static struct platform_driver sdhci_driver = {
+ .driver = {
+ .name = "sdhci",
+ .owner = THIS_MODULE,
+ },
+ .probe = sdhci_probe,
+ .remove = __devexit_p(sdhci_remove),
+};
+
+static int __init sdhci_init(void)
+{
+ return platform_driver_register(&sdhci_driver);
+}
+module_init(sdhci_init);
+
+static void __exit sdhci_exit(void)
+{
+ platform_driver_unregister(&sdhci_driver);
+}
+module_exit(sdhci_exit);
+
+MODULE_DESCRIPTION("SPEAr Secure Digital Host Controller Interface driver");
+MODULE_AUTHOR("Viresh Kumar <viresh.kumar@st.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mmc/host/sdhci-tegra.c b/drivers/mmc/host/sdhci-tegra.c
new file mode 100644
index 00000000..343c97ed
--- /dev/null
+++ b/drivers/mmc/host/sdhci-tegra.c
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2010 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+
+#include <mach/gpio.h>
+#include <mach/sdhci.h>
+
+#include "sdhci.h"
+#include "sdhci-pltfm.h"
+
+static u32 tegra_sdhci_readl(struct sdhci_host *host, int reg)
+{
+ u32 val;
+
+ if (unlikely(reg == SDHCI_PRESENT_STATE)) {
+ /* Use wp_gpio here instead? */
+ val = readl(host->ioaddr + reg);
+ return val | SDHCI_WRITE_PROTECT;
+ }
+
+ return readl(host->ioaddr + reg);
+}
+
+static u16 tegra_sdhci_readw(struct sdhci_host *host, int reg)
+{
+ if (unlikely(reg == SDHCI_HOST_VERSION)) {
+ /* Erratum: Version register is invalid in HW. */
+ return SDHCI_SPEC_200;
+ }
+
+ return readw(host->ioaddr + reg);
+}
+
+static void tegra_sdhci_writel(struct sdhci_host *host, u32 val, int reg)
+{
+ /* Seems like we're getting spurious timeout and crc errors, so
+ * disable signalling of them. In case of real errors software
+ * timers should take care of eventually detecting them.
+ */
+ if (unlikely(reg == SDHCI_SIGNAL_ENABLE))
+ val &= ~(SDHCI_INT_TIMEOUT|SDHCI_INT_CRC);
+
+ writel(val, host->ioaddr + reg);
+
+ if (unlikely(reg == SDHCI_INT_ENABLE)) {
+ /* Erratum: Must enable block gap interrupt detection */
+ u8 gap_ctrl = readb(host->ioaddr + SDHCI_BLOCK_GAP_CONTROL);
+ if (val & SDHCI_INT_CARD_INT)
+ gap_ctrl |= 0x8;
+ else
+ gap_ctrl &= ~0x8;
+ writeb(gap_ctrl, host->ioaddr + SDHCI_BLOCK_GAP_CONTROL);
+ }
+}
+
+static unsigned int tegra_sdhci_get_ro(struct sdhci_host *sdhci)
+{
+ struct platform_device *pdev = to_platform_device(mmc_dev(sdhci->mmc));
+ struct tegra_sdhci_platform_data *plat;
+
+ plat = pdev->dev.platform_data;
+
+ if (!gpio_is_valid(plat->wp_gpio))
+ return -1;
+
+ return gpio_get_value(plat->wp_gpio);
+}
+
+static irqreturn_t carddetect_irq(int irq, void *data)
+{
+ struct sdhci_host *sdhost = (struct sdhci_host *)data;
+
+ tasklet_schedule(&sdhost->card_tasklet);
+ return IRQ_HANDLED;
+};
+
+static int tegra_sdhci_8bit(struct sdhci_host *host, int bus_width)
+{
+ struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
+ struct tegra_sdhci_platform_data *plat;
+ u32 ctrl;
+
+ plat = pdev->dev.platform_data;
+
+ ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);
+ if (plat->is_8bit && bus_width == MMC_BUS_WIDTH_8) {
+ ctrl &= ~SDHCI_CTRL_4BITBUS;
+ ctrl |= SDHCI_CTRL_8BITBUS;
+ } else {
+ ctrl &= ~SDHCI_CTRL_8BITBUS;
+ if (bus_width == MMC_BUS_WIDTH_4)
+ ctrl |= SDHCI_CTRL_4BITBUS;
+ else
+ ctrl &= ~SDHCI_CTRL_4BITBUS;
+ }
+ sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
+ return 0;
+}
+
+
+static int tegra_sdhci_pltfm_init(struct sdhci_host *host,
+ struct sdhci_pltfm_data *pdata)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
+ struct tegra_sdhci_platform_data *plat;
+ struct clk *clk;
+ int rc;
+
+ plat = pdev->dev.platform_data;
+ if (plat == NULL) {
+ dev_err(mmc_dev(host->mmc), "missing platform data\n");
+ return -ENXIO;
+ }
+
+ if (gpio_is_valid(plat->power_gpio)) {
+ rc = gpio_request(plat->power_gpio, "sdhci_power");
+ if (rc) {
+ dev_err(mmc_dev(host->mmc),
+ "failed to allocate power gpio\n");
+ goto out;
+ }
+ tegra_gpio_enable(plat->power_gpio);
+ gpio_direction_output(plat->power_gpio, 1);
+ }
+
+ if (gpio_is_valid(plat->cd_gpio)) {
+ rc = gpio_request(plat->cd_gpio, "sdhci_cd");
+ if (rc) {
+ dev_err(mmc_dev(host->mmc),
+ "failed to allocate cd gpio\n");
+ goto out_power;
+ }
+ tegra_gpio_enable(plat->cd_gpio);
+ gpio_direction_input(plat->cd_gpio);
+
+ rc = request_irq(gpio_to_irq(plat->cd_gpio), carddetect_irq,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+ mmc_hostname(host->mmc), host);
+
+ if (rc) {
+ dev_err(mmc_dev(host->mmc), "request irq error\n");
+ goto out_cd;
+ }
+
+ }
+
+ if (gpio_is_valid(plat->wp_gpio)) {
+ rc = gpio_request(plat->wp_gpio, "sdhci_wp");
+ if (rc) {
+ dev_err(mmc_dev(host->mmc),
+ "failed to allocate wp gpio\n");
+ goto out_irq;
+ }
+ tegra_gpio_enable(plat->wp_gpio);
+ gpio_direction_input(plat->wp_gpio);
+ }
+
+ clk = clk_get(mmc_dev(host->mmc), NULL);
+ if (IS_ERR(clk)) {
+ dev_err(mmc_dev(host->mmc), "clk err\n");
+ rc = PTR_ERR(clk);
+ goto out_wp;
+ }
+ clk_enable(clk);
+ pltfm_host->clk = clk;
+
+ host->mmc->pm_caps = plat->pm_flags;
+
+ if (plat->is_8bit)
+ host->mmc->caps |= MMC_CAP_8_BIT_DATA;
+
+ return 0;
+
+out_wp:
+ if (gpio_is_valid(plat->wp_gpio)) {
+ tegra_gpio_disable(plat->wp_gpio);
+ gpio_free(plat->wp_gpio);
+ }
+
+out_irq:
+ if (gpio_is_valid(plat->cd_gpio))
+ free_irq(gpio_to_irq(plat->cd_gpio), host);
+out_cd:
+ if (gpio_is_valid(plat->cd_gpio)) {
+ tegra_gpio_disable(plat->cd_gpio);
+ gpio_free(plat->cd_gpio);
+ }
+
+out_power:
+ if (gpio_is_valid(plat->power_gpio)) {
+ tegra_gpio_disable(plat->power_gpio);
+ gpio_free(plat->power_gpio);
+ }
+
+out:
+ return rc;
+}
+
+static void tegra_sdhci_pltfm_exit(struct sdhci_host *host)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
+ struct tegra_sdhci_platform_data *plat;
+
+ plat = pdev->dev.platform_data;
+
+ if (gpio_is_valid(plat->wp_gpio)) {
+ tegra_gpio_disable(plat->wp_gpio);
+ gpio_free(plat->wp_gpio);
+ }
+
+ if (gpio_is_valid(plat->cd_gpio)) {
+ free_irq(gpio_to_irq(plat->cd_gpio), host);
+ tegra_gpio_disable(plat->cd_gpio);
+ gpio_free(plat->cd_gpio);
+ }
+
+ if (gpio_is_valid(plat->power_gpio)) {
+ tegra_gpio_disable(plat->power_gpio);
+ gpio_free(plat->power_gpio);
+ }
+
+ clk_disable(pltfm_host->clk);
+ clk_put(pltfm_host->clk);
+}
+
+static struct sdhci_ops tegra_sdhci_ops = {
+ .get_ro = tegra_sdhci_get_ro,
+ .read_l = tegra_sdhci_readl,
+ .read_w = tegra_sdhci_readw,
+ .write_l = tegra_sdhci_writel,
+ .platform_8bit_width = tegra_sdhci_8bit,
+};
+
+struct sdhci_pltfm_data sdhci_tegra_pdata = {
+ .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL |
+ SDHCI_QUIRK_SINGLE_POWER_WRITE |
+ SDHCI_QUIRK_NO_HISPD_BIT |
+ SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC,
+ .ops = &tegra_sdhci_ops,
+ .init = tegra_sdhci_pltfm_init,
+ .exit = tegra_sdhci_pltfm_exit,
+};
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
new file mode 100755
index 00000000..b041b9f1
--- /dev/null
+++ b/drivers/mmc/host/sdhci.c
@@ -0,0 +1,3047 @@
+/*
+ * linux/drivers/mmc/host/sdhci.c - Secure Digital Host Controller Interface driver
+ *
+ * Copyright (C) 2005-2011 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/io.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/scatterlist.h>
+#include <linux/regulator/consumer.h>
+
+#include <linux/leds.h>
+
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+
+#include "sdhci.h"
+
+#define DRIVER_NAME "sdhci"
+#define CLK_TIMEOUT (1 * HZ)
+
+#define DBG(f, x...) \
+ pr_debug(DRIVER_NAME " [%s()]: " f, __func__,## x)
+
+#if defined(CONFIG_LEDS_CLASS) || (defined(CONFIG_LEDS_CLASS_MODULE) && \
+ defined(CONFIG_MMC_SDHCI_MODULE))
+/* FIXME: temp workaround for calling clk_enable in an interrupt context */
+#define SDHCI_USE_LEDS_CLASS_BROKEN
+#endif
+
+#define MAX_TUNING_LOOP 40
+
+static unsigned int debug_quirks = 0;
+
+static void sdhci_finish_data(struct sdhci_host *);
+
+static void sdhci_send_command(struct sdhci_host *, struct mmc_command *);
+static void sdhci_finish_command(struct sdhci_host *);
+static int sdhci_execute_tuning(struct mmc_host *mmc);
+static void sdhci_tuning_timer(unsigned long data);
+static int sdhci_pre_dma_transfer(struct sdhci_host *host,
+ struct mmc_data *data,
+ struct sdhci_host_next *next);
+
+static void sdhci_clk_worker(struct work_struct *work)
+{
+ struct sdhci_host *host =
+ container_of(work, struct sdhci_host, clk_worker.work);
+
+ if (host->ops->platform_clk_ctrl && host->clk_status)
+ host->ops->platform_clk_ctrl(host, false);
+}
+
+static inline bool sdhci_is_sdio_attached(struct sdhci_host *host)
+{
+ struct mmc_card *card = host->mmc->card;
+
+ if (card && card->sdio_func[0])
+ return true;
+ return false;
+}
+
+static bool sdhci_can_gate_clk(struct sdhci_host *host)
+{
+ if (!sdhci_is_sdio_attached(host))
+ return true;
+ if (host->power_mode == MMC_POWER_OFF)
+ return true;
+ return false;
+}
+
+static void sdhci_enable_clk(struct sdhci_host *host)
+{
+ if (host->clk_mgr_en) {
+ cancel_delayed_work_sync(&host->clk_worker);
+ if (!host->clk_status && host->ops->platform_clk_ctrl)
+ host->ops->platform_clk_ctrl(host, true);
+ }
+}
+
+static void sdhci_disable_clk(struct sdhci_host *host, int delay)
+{
+ if (host->clk_mgr_en && sdhci_can_gate_clk(host)) {
+ if (delay == 0 && !in_interrupt()) {
+ if (host->ops->platform_clk_ctrl && host->clk_status)
+ host->ops->platform_clk_ctrl(host, false);
+ } else
+ schedule_delayed_work(&host->clk_worker, delay);
+ }
+}
+
+static void sdhci_dumpregs(struct sdhci_host *host)
+{
+ printk(KERN_DEBUG DRIVER_NAME ": =========== REGISTER DUMP (%s)===========\n",
+ mmc_hostname(host->mmc));
+
+ printk(KERN_DEBUG DRIVER_NAME ": Sys addr: 0x%08x | Version: 0x%08x\n",
+ sdhci_readl(host, SDHCI_DMA_ADDRESS),
+ sdhci_readw(host, SDHCI_HOST_VERSION));
+ printk(KERN_DEBUG DRIVER_NAME ": Blk size: 0x%08x | Blk cnt: 0x%08x\n",
+ sdhci_readw(host, SDHCI_BLOCK_SIZE),
+ sdhci_readw(host, SDHCI_BLOCK_COUNT));
+ printk(KERN_DEBUG DRIVER_NAME ": Argument: 0x%08x | Trn mode: 0x%08x\n",
+ sdhci_readl(host, SDHCI_ARGUMENT),
+ sdhci_readw(host, SDHCI_TRANSFER_MODE));
+ printk(KERN_DEBUG DRIVER_NAME ": Present: 0x%08x | Host ctl: 0x%08x\n",
+ sdhci_readl(host, SDHCI_PRESENT_STATE),
+ sdhci_readb(host, SDHCI_HOST_CONTROL));
+ printk(KERN_DEBUG DRIVER_NAME ": Power: 0x%08x | Blk gap: 0x%08x\n",
+ sdhci_readb(host, SDHCI_POWER_CONTROL),
+ sdhci_readb(host, SDHCI_BLOCK_GAP_CONTROL));
+ printk(KERN_DEBUG DRIVER_NAME ": Wake-up: 0x%08x | Clock: 0x%08x\n",
+ sdhci_readb(host, SDHCI_WAKE_UP_CONTROL),
+ sdhci_readw(host, SDHCI_CLOCK_CONTROL));
+ printk(KERN_DEBUG DRIVER_NAME ": Timeout: 0x%08x | Int stat: 0x%08x\n",
+ sdhci_readb(host, SDHCI_TIMEOUT_CONTROL),
+ sdhci_readl(host, SDHCI_INT_STATUS));
+ printk(KERN_DEBUG DRIVER_NAME ": Int enab: 0x%08x | Sig enab: 0x%08x\n",
+ sdhci_readl(host, SDHCI_INT_ENABLE),
+ sdhci_readl(host, SDHCI_SIGNAL_ENABLE));
+ printk(KERN_DEBUG DRIVER_NAME ": AC12 err: 0x%08x | Slot int: 0x%08x\n",
+ sdhci_readw(host, SDHCI_ACMD12_ERR),
+ sdhci_readw(host, SDHCI_SLOT_INT_STATUS));
+ printk(KERN_DEBUG DRIVER_NAME ": Caps: 0x%08x | Caps_1: 0x%08x\n",
+ sdhci_readl(host, SDHCI_CAPABILITIES),
+ sdhci_readl(host, SDHCI_CAPABILITIES_1));
+ printk(KERN_DEBUG DRIVER_NAME ": Cmd: 0x%08x | Max curr: 0x%08x\n",
+ sdhci_readw(host, SDHCI_COMMAND),
+ sdhci_readl(host, SDHCI_MAX_CURRENT));
+ printk(KERN_DEBUG DRIVER_NAME ": Host ctl2: 0x%08x\n",
+ sdhci_readw(host, SDHCI_HOST_CONTROL2));
+
+ if (host->flags & SDHCI_USE_ADMA)
+ printk(KERN_DEBUG DRIVER_NAME ": ADMA Err: 0x%08x | ADMA Ptr: 0x%08x\n",
+ readl(host->ioaddr + SDHCI_ADMA_ERROR),
+ readl(host->ioaddr + SDHCI_ADMA_ADDRESS));
+
+ printk(KERN_DEBUG DRIVER_NAME ": ===========================================\n");
+}
+
+/*****************************************************************************\
+ * *
+ * Low level functions *
+ * *
+\*****************************************************************************/
+
+static void sdhci_clear_set_irqs(struct sdhci_host *host, u32 clear, u32 set)
+{
+ u32 ier;
+
+ ier = sdhci_readl(host, SDHCI_INT_ENABLE);
+ ier &= ~clear;
+ ier |= set;
+ sdhci_writel(host, ier, SDHCI_INT_ENABLE);
+ sdhci_writel(host, ier, SDHCI_SIGNAL_ENABLE);
+}
+
+static void sdhci_unmask_irqs(struct sdhci_host *host, u32 irqs)
+{
+ sdhci_clear_set_irqs(host, 0, irqs);
+}
+
+static void sdhci_mask_irqs(struct sdhci_host *host, u32 irqs)
+{
+ sdhci_clear_set_irqs(host, irqs, 0);
+}
+
+static void sdhci_set_card_detection(struct sdhci_host *host, bool enable)
+{
+ u32 irqs = SDHCI_INT_CARD_REMOVE | SDHCI_INT_CARD_INSERT;
+
+ if (host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION)
+ return;
+
+ if (enable)
+ sdhci_unmask_irqs(host, irqs);
+ else
+ sdhci_mask_irqs(host, irqs);
+}
+
+static void sdhci_enable_card_detection(struct sdhci_host *host)
+{
+ sdhci_set_card_detection(host, true);
+}
+
+static void sdhci_disable_card_detection(struct sdhci_host *host)
+{
+ sdhci_set_card_detection(host, false);
+}
+
+static void sdhci_reset(struct sdhci_host *host, u8 mask)
+{
+ unsigned long timeout;
+ u32 uninitialized_var(ier);
+
+ if (host->quirks & SDHCI_QUIRK_NO_CARD_NO_RESET) {
+ if (!(sdhci_readl(host, SDHCI_PRESENT_STATE) &
+ SDHCI_CARD_PRESENT))
+ return;
+ }
+
+ if (host->quirks & SDHCI_QUIRK_RESTORE_IRQS_AFTER_RESET)
+ ier = sdhci_readl(host, SDHCI_INT_ENABLE);
+
+ if (host->ops->platform_reset_enter)
+ host->ops->platform_reset_enter(host, mask);
+
+ sdhci_writeb(host, mask, SDHCI_SOFTWARE_RESET);
+
+ if (mask & SDHCI_RESET_ALL)
+ host->clock = 0;
+
+ /* Wait max 100 ms */
+ timeout = 100;
+
+ /* hw clears the bit when it's done */
+ while (sdhci_readb(host, SDHCI_SOFTWARE_RESET) & mask) {
+ if (timeout == 0) {
+ printk(KERN_ERR "%s: Reset 0x%x never completed.\n",
+ mmc_hostname(host->mmc), (int)mask);
+ sdhci_dumpregs(host);
+ return;
+ }
+ timeout--;
+ mdelay(1);
+ }
+
+ if (host->ops->platform_reset_exit)
+ host->ops->platform_reset_exit(host, mask);
+
+ if (host->quirks & SDHCI_QUIRK_RESTORE_IRQS_AFTER_RESET)
+ sdhci_clear_set_irqs(host, SDHCI_INT_ALL_MASK, ier);
+}
+
+static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
+
+static void sdhci_init(struct sdhci_host *host, int soft)
+{
+ if (soft)
+ sdhci_reset(host, SDHCI_RESET_CMD|SDHCI_RESET_DATA);
+ else
+ sdhci_reset(host, SDHCI_RESET_ALL);
+
+ sdhci_clear_set_irqs(host, SDHCI_INT_ALL_MASK,
+ SDHCI_INT_BUS_POWER | SDHCI_INT_DATA_END_BIT |
+ SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_INDEX |
+ SDHCI_INT_END_BIT | SDHCI_INT_CRC | SDHCI_INT_TIMEOUT |
+ SDHCI_INT_DATA_END | SDHCI_INT_RESPONSE);
+
+ if (soft) {
+ /* force clock reconfiguration */
+ host->clock = 0;
+ sdhci_set_ios(host->mmc, &host->mmc->ios);
+ }
+}
+
+static void sdhci_reinit(struct sdhci_host *host)
+{
+ sdhci_init(host, 0);
+ sdhci_enable_card_detection(host);
+}
+
+static void sdhci_activate_led(struct sdhci_host *host)
+{
+ u8 ctrl;
+
+ ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);
+ ctrl |= SDHCI_CTRL_LED;
+ sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
+}
+
+static void sdhci_deactivate_led(struct sdhci_host *host)
+{
+ u8 ctrl;
+
+ ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);
+ ctrl &= ~SDHCI_CTRL_LED;
+ sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
+}
+
+#ifdef CONFIG_SDHCI_USE_LEDS_CLASS
+static void sdhci_led_control(struct led_classdev *led,
+ enum led_brightness brightness)
+{
+ struct sdhci_host *host = container_of(led, struct sdhci_host, led);
+ unsigned long flags;
+
+ sdhci_enable_clk(host);
+ spin_lock_irqsave(&host->lock, flags);
+
+ if (brightness == LED_OFF)
+ sdhci_deactivate_led(host);
+ else
+ sdhci_activate_led(host);
+
+ spin_unlock_irqrestore(&host->lock, flags);
+ sdhci_disable_clk(host, CLK_TIMEOUT);
+}
+#endif
+
+/*****************************************************************************\
+ * *
+ * Core functions *
+ * *
+\*****************************************************************************/
+
+static void sdhci_read_block_pio(struct sdhci_host *host)
+{
+ unsigned long flags;
+ size_t blksize, len, chunk;
+ u32 uninitialized_var(scratch);
+ u8 *buf;
+
+ DBG("PIO reading\n");
+
+ blksize = host->data->blksz;
+ chunk = 0;
+
+ local_irq_save(flags);
+
+ while (blksize) {
+ if (!sg_miter_next(&host->sg_miter))
+ BUG();
+
+ len = min(host->sg_miter.length, blksize);
+
+ blksize -= len;
+ host->sg_miter.consumed = len;
+
+ buf = host->sg_miter.addr;
+
+ while (len) {
+ if (chunk == 0) {
+ scratch = sdhci_readl(host, SDHCI_BUFFER);
+ chunk = 4;
+ }
+
+ *buf = scratch & 0xFF;
+
+ buf++;
+ scratch >>= 8;
+ chunk--;
+ len--;
+ }
+ }
+
+ sg_miter_stop(&host->sg_miter);
+
+ local_irq_restore(flags);
+}
+
+static void sdhci_write_block_pio(struct sdhci_host *host)
+{
+ unsigned long flags;
+ size_t blksize, len, chunk;
+ u32 scratch;
+ u8 *buf;
+
+ DBG("PIO writing\n");
+
+ blksize = host->data->blksz;
+ chunk = 0;
+ scratch = 0;
+
+ local_irq_save(flags);
+
+ while (blksize) {
+ if (!sg_miter_next(&host->sg_miter))
+ BUG();
+
+ len = min(host->sg_miter.length, blksize);
+
+ blksize -= len;
+ host->sg_miter.consumed = len;
+
+ buf = host->sg_miter.addr;
+
+ while (len) {
+ scratch |= (u32)*buf << (chunk * 8);
+
+ buf++;
+ chunk++;
+ len--;
+
+ if ((chunk == 4) || ((len == 0) && (blksize == 0))) {
+ sdhci_writel(host, scratch, SDHCI_BUFFER);
+ chunk = 0;
+ scratch = 0;
+ }
+ }
+ }
+
+ sg_miter_stop(&host->sg_miter);
+
+ local_irq_restore(flags);
+}
+
+static void sdhci_transfer_pio(struct sdhci_host *host)
+{
+ u32 mask;
+
+ BUG_ON(!host->data);
+
+ if (host->blocks == 0)
+ return;
+
+ if (host->data->flags & MMC_DATA_READ)
+ mask = SDHCI_DATA_AVAILABLE;
+ else
+ mask = SDHCI_SPACE_AVAILABLE;
+
+ /*
+ * Some controllers (JMicron JMB38x) mess up the buffer bits
+ * for transfers < 4 bytes. As long as it is just one block,
+ * we can ignore the bits.
+ */
+ if ((host->quirks & SDHCI_QUIRK_BROKEN_SMALL_PIO) &&
+ (host->data->blocks == 1))
+ mask = ~0;
+
+ while (sdhci_readl(host, SDHCI_PRESENT_STATE) & mask) {
+ if (host->quirks & SDHCI_QUIRK_PIO_NEEDS_DELAY)
+ udelay(100);
+
+ if (host->data->flags & MMC_DATA_READ)
+ sdhci_read_block_pio(host);
+ else
+ sdhci_write_block_pio(host);
+
+ host->blocks--;
+ if (host->blocks == 0)
+ break;
+ }
+
+ DBG("PIO transfer complete.\n");
+}
+
+static char *sdhci_kmap_atomic(struct scatterlist *sg, unsigned long *flags)
+{
+ local_irq_save(*flags);
+ return kmap_atomic(sg_page(sg), KM_BIO_SRC_IRQ) + sg->offset;
+}
+
+static void sdhci_kunmap_atomic(void *buffer, unsigned long *flags)
+{
+ kunmap_atomic(buffer, KM_BIO_SRC_IRQ);
+ local_irq_restore(*flags);
+}
+
+static void sdhci_set_adma_desc(u8 *desc, u32 addr, int len, unsigned cmd)
+{
+ __le32 *dataddr = (__le32 __force *)(desc + 4);
+ __le16 *cmdlen = (__le16 __force *)desc;
+
+ /* SDHCI specification says ADMA descriptors should be 4 byte
+ * aligned, so using 16 or 32bit operations should be safe. */
+
+ cmdlen[0] = cpu_to_le16(cmd);
+ cmdlen[1] = cpu_to_le16(len);
+
+ dataddr[0] = cpu_to_le32(addr);
+}
+
+static int sdhci_adma_table_pre(struct sdhci_host *host,
+ struct mmc_data *data)
+{
+ int direction;
+
+ u8 *desc;
+ u8 *align;
+ dma_addr_t addr;
+ dma_addr_t align_addr;
+ int len, offset;
+
+ 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(mmc_dev(host->mmc), host->align_addr))
+ goto fail;
+ BUG_ON(host->align_addr & 0x3);
+
+ host->sg_count = sdhci_pre_dma_transfer(host, data, NULL);
+ 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);
+ WARN_ON(((long)buffer & PAGE_MASK) > (PAGE_SIZE - 3));
+ memcpy(align, buffer, offset);
+ sdhci_kunmap_atomic(buffer, &flags);
+ }
+
+ /* tran, valid */
+ sdhci_set_adma_desc(desc, align_addr, offset, 0x21);
+
+ BUG_ON(offset > 65536);
+
+ align += 4;
+ align_addr += 4;
+
+ desc += 8;
+
+ addr += offset;
+ len -= offset;
+ }
+
+ BUG_ON(len > 65536);
+
+ /* tran, valid */
+ sdhci_set_adma_desc(desc, addr, len, 0x21);
+ desc += 8;
+
+ /*
+ * If this triggers then we have a calculation bug
+ * somewhere. :/
+ */
+ WARN_ON((desc - host->adma_desc) > (128 * 2 + 1) * 4);
+ }
+
+ if (host->quirks & SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC) {
+ /*
+ * Mark the last descriptor as the terminating descriptor
+ */
+ if (desc != host->adma_desc) {
+ desc -= 8;
+ desc[0] |= 0x2; /* end */
+ }
+ } else {
+ /*
+ * Add a terminating entry.
+ */
+
+ /* nop, end, valid */
+ sdhci_set_adma_desc(desc, 0, 0, 0x3);
+ }
+
+ /*
+ * 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(mmc_dev(host->mmc), host->adma_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);
+ WARN_ON(((long)buffer & PAGE_MASK) > (PAGE_SIZE - 3));
+ memcpy(buffer, align, size);
+ sdhci_kunmap_atomic(buffer, &flags);
+
+ align += 4;
+ }
+ }
+ }
+
+ if (!data->host_cookie)
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg,
+ data->sg_len, direction);
+}
+
+static u8 sdhci_calc_timeout(struct sdhci_host *host, struct mmc_command *cmd)
+{
+ u8 count;
+ struct mmc_data *data = cmd->data;
+ 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;
+
+ /* Unspecified timeout, assume max */
+ if (!data && !cmd->cmd_timeout_ms)
+ return 0xE;
+
+ /* timeout in us */
+ if (!data)
+ target_timeout = cmd->cmd_timeout_ms * 1000;
+ else
+ target_timeout = data->timeout_ns / 1000 +
+ data->timeout_clks / host->clock;
+
+ if (host->quirks & SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK)
+ host->timeout_clk = host->clock / 1000;
+
+ /*
+ * Figure out needed cycles.
+ * We do this in steps in order to fit inside a 32 bit int.
+ * The first step is the minimum timeout, which will have a
+ * minimum resolution of 6 bits:
+ * (1) 2^13*1000 > 2^22,
+ * (2) host->timeout_clk < 2^16
+ * =>
+ * (1) / (2) > 2^6
+ */
+ BUG_ON(!host->timeout_clk);
+ count = 0;
+ current_timeout = (1 << 13) * 1000 / host->timeout_clk;
+ while (current_timeout < target_timeout) {
+ count++;
+ current_timeout <<= 1;
+ if (count >= 0xF)
+ break;
+ }
+
+ if (count >= 0xF) {
+ printk(KERN_WARNING "%s: Too large timeout requested for CMD%d!\n",
+ mmc_hostname(host->mmc), cmd->opcode);
+ count = 0xE;
+ }
+
+ return count;
+}
+
+static void sdhci_set_transfer_irqs(struct sdhci_host *host)
+{
+ u32 pio_irqs = SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL;
+ u32 dma_irqs = SDHCI_INT_DMA_END | SDHCI_INT_ADMA_ERROR;
+
+ if (host->flags & SDHCI_REQ_USE_DMA)
+ sdhci_clear_set_irqs(host, pio_irqs, dma_irqs);
+ else
+ sdhci_clear_set_irqs(host, dma_irqs, pio_irqs);
+}
+
+static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
+{
+ u8 count;
+ u8 ctrl;
+ struct mmc_data *data = cmd->data;
+ int ret;
+
+ WARN_ON(host->data);
+
+ if (data || (cmd->flags & MMC_RSP_BUSY)) {
+ count = sdhci_calc_timeout(host, cmd);
+ sdhci_writeb(host, count, SDHCI_TIMEOUT_CONTROL);
+ }
+
+ if (!data)
+ 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;
+ host->data->bytes_xfered = 0;
+
+ if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA))
+ 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 (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)
+ broken = 1;
+ }
+
+ if (unlikely(broken)) {
+ for_each_sg(data->sg, sg, data->sg_len, i) {
+ if (sg->offset & 0x3) {
+ DBG("Reverting to PIO because of "
+ "bad alignment\n");
+ host->flags &= ~SDHCI_REQ_USE_DMA;
+ break;
+ }
+ }
+ }
+ }
+
+ if (host->flags & SDHCI_REQ_USE_DMA) {
+ if (host->flags & SDHCI_USE_ADMA) {
+ ret = sdhci_adma_table_pre(host, data);
+ if (ret) {
+ /*
+ * This only happens when someone fed
+ * us an invalid request.
+ */
+ WARN_ON(1);
+ host->flags &= ~SDHCI_REQ_USE_DMA;
+ } else {
+ sdhci_writel(host, host->adma_addr,
+ SDHCI_ADMA_ADDRESS);
+ }
+ } else {
+ int sg_cnt;
+
+ sg_cnt = sdhci_pre_dma_transfer(host, data, NULL);
+ if (sg_cnt == 0) {
+ /*
+ * This only happens when someone fed
+ * us an invalid request.
+ */
+ WARN_ON(1);
+ host->flags &= ~SDHCI_REQ_USE_DMA;
+ } else {
+ WARN_ON(sg_cnt != 1);
+ sdhci_writel(host, sg_dma_address(data->sg),
+ SDHCI_DMA_ADDRESS);
+ }
+ }
+ }
+
+ /*
+ * Always adjust the DMA selection as some controllers
+ * (e.g. JMicron) can't do PIO properly when the selection
+ * is ADMA.
+ */
+ if (host->version >= SDHCI_SPEC_200) {
+ ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);
+ ctrl &= ~SDHCI_CTRL_DMA_MASK;
+ if ((host->flags & SDHCI_REQ_USE_DMA) &&
+ (host->flags & SDHCI_USE_ADMA))
+ ctrl |= SDHCI_CTRL_ADMA32;
+ else
+ ctrl |= SDHCI_CTRL_SDMA;
+ sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
+ }
+
+ if (!(host->flags & SDHCI_REQ_USE_DMA)) {
+ int flags;
+
+ flags = SG_MITER_ATOMIC;
+ if (host->data->flags & MMC_DATA_READ)
+ flags |= SG_MITER_TO_SG;
+ else
+ flags |= SG_MITER_FROM_SG;
+ sg_miter_start(&host->sg_miter, data->sg, data->sg_len, flags);
+ host->blocks = data->blocks;
+ }
+
+ sdhci_set_transfer_irqs(host);
+
+ /* Set the DMA boundary value and block size */
+ sdhci_writew(host, SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG,
+ data->blksz), SDHCI_BLOCK_SIZE);
+ sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT);
+}
+
+static void sdhci_set_transfer_mode(struct sdhci_host *host,
+ struct mmc_command *cmd)
+{
+ u16 mode;
+ struct mmc_data *data = cmd->data;
+
+ if (data == NULL)
+ return;
+
+ WARN_ON(!host->data);
+
+ mode = SDHCI_TRNS_BLK_CNT_EN;
+ if (mmc_op_multi(cmd->opcode) || data->blocks > 1) {
+ mode |= SDHCI_TRNS_MULTI;
+ /*
+ * If we are sending CMD23, CMD12 never gets sent
+ * on successful completion (so no Auto-CMD12).
+ */
+ if (!host->mrq->sbc && (host->flags & SDHCI_AUTO_CMD12))
+ mode |= SDHCI_TRNS_AUTO_CMD12;
+ else if (host->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
+ mode |= SDHCI_TRNS_AUTO_CMD23;
+ sdhci_writel(host, host->mrq->sbc->arg, SDHCI_ARGUMENT2);
+ }
+ }
+
+ if (data->flags & MMC_DATA_READ)
+ mode |= SDHCI_TRNS_READ;
+ if (host->flags & SDHCI_REQ_USE_DMA)
+ mode |= SDHCI_TRNS_DMA;
+
+ sdhci_writew(host, mode, SDHCI_TRANSFER_MODE);
+}
+
+static void sdhci_finish_data(struct sdhci_host *host)
+{
+ struct mmc_data *data;
+
+ BUG_ON(!host->data);
+
+ data = host->data;
+ host->data = NULL;
+
+ if (host->flags & SDHCI_REQ_USE_DMA) {
+ if (host->flags & SDHCI_USE_ADMA)
+ sdhci_adma_table_post(host, data);
+ else {
+ if (!data->host_cookie)
+ dma_unmap_sg(mmc_dev(host->mmc),
+ data->sg, data->sg_len,
+ (data->flags & MMC_DATA_READ) ?
+ DMA_FROM_DEVICE : DMA_TO_DEVICE);
+ }
+ }
+
+ /*
+ * The specification states that the block count register must
+ * be updated, but it does not specify at what point in the
+ * data flow. That makes the register entirely useless to read
+ * back so we have to assume that nothing made it to the card
+ * in the event of an error.
+ */
+ if (data->error)
+ data->bytes_xfered = 0;
+ else
+ data->bytes_xfered = data->blksz * data->blocks;
+
+ /*
+ * Need to send CMD12 if -
+ * a) open-ended multiblock transfer (no CMD23)
+ * b) error in multiblock transfer
+ */
+ if (data->stop &&
+ (data->error ||
+ !host->mrq->sbc)) {
+
+ /*
+ * The controller needs a reset of internal state machines
+ * upon error conditions.
+ */
+ if (data->error) {
+ sdhci_reset(host, SDHCI_RESET_CMD);
+ sdhci_reset(host, SDHCI_RESET_DATA);
+ }
+
+ sdhci_send_command(host, data->stop);
+ } else
+ tasklet_schedule(&host->finish_tasklet);
+}
+
+static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
+{
+ int flags;
+ u32 mask;
+ unsigned long timeout;
+
+ WARN_ON(host->cmd);
+
+ /* Wait max 10 ms */
+ timeout = 10;
+
+ mask = SDHCI_CMD_INHIBIT;
+ if ((cmd->data != NULL) || (cmd->flags & MMC_RSP_BUSY))
+ mask |= SDHCI_DATA_INHIBIT;
+
+ /* We shouldn't wait for data inihibit for stop commands, even
+ though they might use busy signaling */
+ if (host->mrq->data && (cmd == host->mrq->data->stop))
+ mask &= ~SDHCI_DATA_INHIBIT;
+
+ while (sdhci_readl(host, SDHCI_PRESENT_STATE) & mask) {
+ if (timeout == 0) {
+ printk(KERN_ERR "%s: Controller never released "
+ "inhibit bit(s).\n", mmc_hostname(host->mmc));
+ sdhci_dumpregs(host);
+ cmd->error = -EIO;
+ tasklet_schedule(&host->finish_tasklet);
+ return;
+ }
+ timeout--;
+ mdelay(1);
+ }
+
+ mod_timer(&host->timer, jiffies + 10 * HZ);
+
+ host->cmd = cmd;
+
+ sdhci_prepare_data(host, cmd);
+
+ sdhci_writel(host, cmd->arg, SDHCI_ARGUMENT);
+
+ sdhci_set_transfer_mode(host, cmd);
+
+ if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) {
+ printk(KERN_ERR "%s: Unsupported response type!\n",
+ mmc_hostname(host->mmc));
+ cmd->error = -EINVAL;
+ tasklet_schedule(&host->finish_tasklet);
+ return;
+ }
+
+ if (!(cmd->flags & MMC_RSP_PRESENT))
+ flags = SDHCI_CMD_RESP_NONE;
+ else if (cmd->flags & MMC_RSP_136)
+ flags = SDHCI_CMD_RESP_LONG;
+ else if (cmd->flags & MMC_RSP_BUSY)
+ flags = SDHCI_CMD_RESP_SHORT_BUSY;
+ else
+ flags = SDHCI_CMD_RESP_SHORT;
+
+ if (cmd->flags & MMC_RSP_CRC)
+ flags |= SDHCI_CMD_CRC;
+ if (cmd->flags & MMC_RSP_OPCODE)
+ flags |= SDHCI_CMD_INDEX;
+
+ /* CMD19 is special in that the Data Present Select should be set */
+ if (cmd->data || (cmd->opcode == MMC_SEND_TUNING_BLOCK))
+ flags |= SDHCI_CMD_DATA;
+
+ sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND);
+}
+
+static void sdhci_finish_command(struct sdhci_host *host)
+{
+ int i;
+
+ BUG_ON(host->cmd == NULL);
+
+ if (host->cmd->flags & MMC_RSP_PRESENT) {
+ if (host->cmd->flags & MMC_RSP_136) {
+ /* CRC is stripped so we need to do some shifting. */
+ for (i = 0;i < 4;i++) {
+ host->cmd->resp[i] = sdhci_readl(host,
+ SDHCI_RESPONSE + (3-i)*4) << 8;
+ if (i != 3)
+ host->cmd->resp[i] |=
+ sdhci_readb(host,
+ SDHCI_RESPONSE + (3-i)*4-1);
+ }
+ } else {
+ host->cmd->resp[0] = sdhci_readl(host, SDHCI_RESPONSE);
+ }
+ }
+
+ host->cmd->error = 0;
+
+ /* Finished CMD23, now send actual command. */
+ if (host->cmd == host->mrq->sbc) {
+ host->cmd = NULL;
+ sdhci_send_command(host, host->mrq->cmd);
+ } else {
+
+ /* Processed actual command. */
+ if (host->data && host->data_early)
+ sdhci_finish_data(host);
+
+ if (!host->cmd->data)
+ tasklet_schedule(&host->finish_tasklet);
+
+ host->cmd = NULL;
+ }
+}
+
+static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
+{
+ int div = 0; /* Initialized for compiler warning */
+ u16 clk = 0;
+ unsigned long timeout;
+
+ if (clock && clock == host->clock)
+ return;
+
+ if (host->ops->set_clock) {
+ host->ops->set_clock(host, clock);
+ if (host->quirks & SDHCI_QUIRK_NONSTANDARD_CLOCK)
+ return;
+ }
+
+ sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL);
+
+ if (clock == 0)
+ goto out;
+
+ if (host->version >= SDHCI_SPEC_300) {
+ /*
+ * Check if the Host Controller supports Programmable Clock
+ * Mode.
+ */
+ if (host->clk_mul) {
+ u16 ctrl;
+
+ /*
+ * We need to figure out whether the Host Driver needs
+ * to select Programmable Clock Mode, or the value can
+ * be set automatically by the Host Controller based on
+ * the Preset Value registers.
+ */
+ ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ if (!(ctrl & SDHCI_CTRL_PRESET_VAL_ENABLE)) {
+ for (div = 1; div <= 1024; div++) {
+ if (((host->max_clk * host->clk_mul) /
+ div) <= clock)
+ break;
+ }
+ /*
+ * Set Programmable Clock Mode in the Clock
+ * Control register.
+ */
+ clk = SDHCI_PROG_CLOCK_MODE;
+ div--;
+ }
+ } else {
+ /* Version 3.00 divisors must be a multiple of 2. */
+ if (host->max_clk <= clock)
+ div = 1;
+ else {
+ for (div = 2; div < SDHCI_MAX_DIV_SPEC_300;
+ div += 2) {
+ if ((host->max_clk / div) <= clock)
+ break;
+ }
+ }
+ div >>= 1;
+ }
+ } else {
+ /* Version 2.00 divisors must be a power of 2. */
+ for (div = 1; div < SDHCI_MAX_DIV_SPEC_200; div *= 2) {
+ if ((host->max_clk / div) <= clock)
+ break;
+ }
+ div >>= 1;
+ }
+
+ clk |= (div & SDHCI_DIV_MASK) << SDHCI_DIVIDER_SHIFT;
+ clk |= ((div & SDHCI_DIV_HI_MASK) >> SDHCI_DIV_MASK_LEN)
+ << SDHCI_DIVIDER_HI_SHIFT;
+ clk |= SDHCI_CLOCK_INT_EN;
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
+
+ /* Wait max 20 ms */
+ timeout = 20;
+ while (!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL))
+ & SDHCI_CLOCK_INT_STABLE)) {
+ if (timeout == 0) {
+ printk(KERN_ERR "%s: Internal clock never "
+ "stabilised.\n", mmc_hostname(host->mmc));
+ sdhci_dumpregs(host);
+ return;
+ }
+ timeout--;
+ mdelay(1);
+ }
+
+ clk |= SDHCI_CLOCK_CARD_EN;
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
+
+out:
+ host->clock = clock;
+}
+
+static void sdhci_set_power(struct sdhci_host *host, unsigned short power)
+{
+ u8 pwr = 0;
+
+ if (power != (unsigned short)-1) {
+ switch (1 << power) {
+ case MMC_VDD_165_195:
+ pwr = SDHCI_POWER_180;
+ break;
+ case MMC_VDD_29_30:
+ case MMC_VDD_30_31:
+ pwr = SDHCI_POWER_300;
+ break;
+ case MMC_VDD_32_33:
+ case MMC_VDD_33_34:
+ pwr = SDHCI_POWER_330;
+ break;
+ default:
+ BUG();
+ }
+ }
+
+ if (host->pwr == pwr)
+ return;
+
+ host->pwr = pwr;
+
+ if (pwr == 0) {
+ sdhci_writeb(host, 0, SDHCI_POWER_CONTROL);
+ return;
+ }
+
+ /*
+ * Spec says that we should clear the power reg before setting
+ * a new value. Some controllers don't seem to like this though.
+ */
+ if (!(host->quirks & SDHCI_QUIRK_SINGLE_POWER_WRITE))
+ sdhci_writeb(host, 0, SDHCI_POWER_CONTROL);
+
+ /*
+ * At least the Marvell CaFe chip gets confused if we set the voltage
+ * and set turn on power at the same time, so set the voltage first.
+ */
+ if (host->quirks & SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER)
+ sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL);
+
+ pwr |= SDHCI_POWER_ON;
+
+ sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL);
+
+ /*
+ * Some controllers need an extra 10ms delay of 10ms before they
+ * can apply clock after applying power
+ */
+ if (host->quirks & SDHCI_QUIRK_DELAY_AFTER_POWER)
+ mdelay(10);
+}
+
+/*****************************************************************************\
+ * *
+ * MMC callbacks *
+ * *
+\*****************************************************************************/
+
+void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct sdhci_host *host;
+ bool present;
+ unsigned long flags;
+
+ host = mmc_priv(mmc);
+
+ sdhci_enable_clk(host);
+ spin_lock_irqsave(&host->lock, flags);
+
+ WARN_ON(host->mrq != NULL);
+
+#ifndef CONFIG_SDHCI_USE_LEDS_CLASS
+ sdhci_activate_led(host);
+#endif
+
+ /*
+ * Ensure we don't send the STOP for non-SET_BLOCK_COUNTED
+ * requests if Auto-CMD12 is enabled.
+ */
+ if (!mrq->sbc && (host->flags & SDHCI_AUTO_CMD12)) {
+ if (mrq->stop) {
+ mrq->data->stop = NULL;
+ mrq->stop = NULL;
+ }
+ }
+
+ host->mrq = mrq;
+
+ /* If polling, assume that the card is always present. */
+ if (host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION)
+ present = true;
+ else
+ present = sdhci_readl(host, SDHCI_PRESENT_STATE) &
+ SDHCI_CARD_PRESENT;
+
+ if (!present || host->flags & SDHCI_DEVICE_DEAD) {
+ host->mrq->cmd->error = -ENOMEDIUM;
+ tasklet_schedule(&host->finish_tasklet);
+ } else {
+ u32 present_state;
+
+ present_state = sdhci_readl(host, SDHCI_PRESENT_STATE);
+ /*
+ * Check if the re-tuning timer has already expired and there
+ * is no on-going data transfer. If so, we need to execute
+ * tuning procedure before sending command.
+ */
+ if ((host->flags & SDHCI_NEEDS_RETUNING) &&
+ !(present_state & (SDHCI_DOING_WRITE | SDHCI_DOING_READ))) {
+ spin_unlock_irqrestore(&host->lock, flags);
+ sdhci_execute_tuning(mmc);
+ spin_lock_irqsave(&host->lock, flags);
+
+ /* Restore original mmc_request structure */
+ host->mrq = mrq;
+ }
+
+ if (mrq->sbc && !(host->flags & SDHCI_AUTO_CMD23))
+ sdhci_send_command(host, mrq->sbc);
+ else
+ sdhci_send_command(host, mrq->cmd);
+ }
+
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+EXPORT_SYMBOL_GPL(sdhci_request);
+
+static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct sdhci_host *host;
+ unsigned long flags;
+ u8 ctrl;
+
+ host = mmc_priv(mmc);
+
+ host->power_mode = ios->power_mode;
+
+ sdhci_enable_clk(host);
+ spin_lock_irqsave(&host->lock, flags);
+
+ if (host->flags & SDHCI_DEVICE_DEAD)
+ goto out;
+
+ /*
+ * Reset the chip on each power off.
+ * Should clear out any weird states.
+ */
+ if (ios->power_mode == MMC_POWER_OFF) {
+ sdhci_writel(host, 0, SDHCI_SIGNAL_ENABLE);
+ sdhci_reinit(host);
+ }
+
+ sdhci_set_clock(host, ios->clock);
+
+ if (ios->power_mode == MMC_POWER_OFF)
+ sdhci_set_power(host, -1);
+ else
+ sdhci_set_power(host, ios->vdd);
+
+ if (host->ops->platform_send_init_74_clocks)
+ host->ops->platform_send_init_74_clocks(host, ios->power_mode);
+
+ /*
+ * If your platform has 8-bit width support but is not a v3 controller,
+ * or if it requires special setup code, you should implement that in
+ * platform_8bit_width().
+ */
+ if (host->ops->platform_8bit_width)
+ host->ops->platform_8bit_width(host, ios->bus_width);
+ else {
+ ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);
+ if (ios->bus_width == MMC_BUS_WIDTH_8) {
+ ctrl &= ~SDHCI_CTRL_4BITBUS;
+ if (host->version >= SDHCI_SPEC_300)
+ ctrl |= SDHCI_CTRL_8BITBUS;
+ } else {
+ if (host->version >= SDHCI_SPEC_300)
+ ctrl &= ~SDHCI_CTRL_8BITBUS;
+ if (ios->bus_width == MMC_BUS_WIDTH_4)
+ ctrl |= SDHCI_CTRL_4BITBUS;
+ else
+ ctrl &= ~SDHCI_CTRL_4BITBUS;
+ }
+ sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
+ }
+
+ ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);
+
+ if ((ios->timing == MMC_TIMING_SD_HS ||
+ ios->timing == MMC_TIMING_MMC_HS)
+ && !(host->quirks & SDHCI_QUIRK_NO_HISPD_BIT))
+ ctrl |= SDHCI_CTRL_HISPD;
+ else
+ ctrl &= ~SDHCI_CTRL_HISPD;
+
+ if (host->version >= SDHCI_SPEC_300) {
+ u16 clk, ctrl_2;
+ unsigned int clock;
+
+ /* In case of UHS-I modes, set High Speed Enable */
+ if ((ios->timing == MMC_TIMING_UHS_SDR50) ||
+ (ios->timing == MMC_TIMING_UHS_SDR104) ||
+ (ios->timing == MMC_TIMING_UHS_DDR50) ||
+ (ios->timing == MMC_TIMING_UHS_SDR25))
+ ctrl |= SDHCI_CTRL_HISPD;
+
+ ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ if (!(ctrl_2 & SDHCI_CTRL_PRESET_VAL_ENABLE)) {
+ sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
+ /*
+ * We only need to set Driver Strength if the
+ * preset value enable is not set.
+ */
+ ctrl_2 &= ~SDHCI_CTRL_DRV_TYPE_MASK;
+ if (ios->drv_type == MMC_SET_DRIVER_TYPE_A)
+ ctrl_2 |= SDHCI_CTRL_DRV_TYPE_A;
+ else if (ios->drv_type == MMC_SET_DRIVER_TYPE_C)
+ ctrl_2 |= SDHCI_CTRL_DRV_TYPE_C;
+
+ sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
+ } else {
+ /*
+ * According to SDHC Spec v3.00, if the Preset Value
+ * Enable in the Host Control 2 register is set, we
+ * need to reset SD Clock Enable before changing High
+ * Speed Enable to avoid generating clock gliches.
+ */
+
+ /* Reset SD Clock Enable */
+ clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
+ clk &= ~SDHCI_CLOCK_CARD_EN;
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
+
+ sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
+
+ /* Re-enable SD Clock */
+ clock = host->clock;
+ host->clock = 0;
+ sdhci_set_clock(host, clock);
+ }
+
+
+ /* Reset SD Clock Enable */
+ clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
+ clk &= ~SDHCI_CLOCK_CARD_EN;
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
+
+ if (host->ops->set_uhs_signaling)
+ host->ops->set_uhs_signaling(host, ios->timing);
+ else {
+ ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ /* Select Bus Speed Mode for host */
+ ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
+ if (ios->timing == MMC_TIMING_UHS_SDR12)
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
+ else if (ios->timing == MMC_TIMING_UHS_SDR25)
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
+ else if (ios->timing == MMC_TIMING_UHS_SDR50)
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR50;
+ else if (ios->timing == MMC_TIMING_UHS_SDR104)
+ ctrl_2 |= SDHCI_CTRL_UHS_SDR104;
+ else if (ios->timing == MMC_TIMING_UHS_DDR50)
+ ctrl_2 |= SDHCI_CTRL_UHS_DDR50;
+ sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
+ }
+
+ /* Re-enable SD Clock */
+ clock = host->clock;
+ host->clock = 0;
+ sdhci_set_clock(host, clock);
+ } else
+ sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
+
+ /*
+ * Some (ENE) controllers go apeshit on some ios operation,
+ * signalling timeout and CRC errors even on CMD0. Resetting
+ * it on each ios seems to solve the problem.
+ */
+ if (host->quirks & SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS)
+ sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA);
+
+out:
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ if (ios->power_mode == MMC_POWER_OFF)
+ sdhci_disable_clk(host, 0);
+}
+
+static int check_ro(struct sdhci_host *host)
+{
+ unsigned long flags;
+ int is_readonly;
+
+ sdhci_enable_clk(host);
+ spin_lock_irqsave(&host->lock, flags);
+
+ if (host->flags & SDHCI_DEVICE_DEAD)
+ is_readonly = 0;
+ else if (host->ops->get_ro)
+ is_readonly = host->ops->get_ro(host);
+ else
+ is_readonly = !(sdhci_readl(host, SDHCI_PRESENT_STATE)
+ & SDHCI_WRITE_PROTECT);
+
+ spin_unlock_irqrestore(&host->lock, flags);
+ sdhci_disable_clk(host, CLK_TIMEOUT);
+
+ /* This quirk needs to be replaced by a callback-function later */
+ return host->quirks & SDHCI_QUIRK_INVERTED_WRITE_PROTECT ?
+ !is_readonly : is_readonly;
+}
+
+#define SAMPLE_COUNT 5
+
+static int sdhci_get_ro(struct mmc_host *mmc)
+{
+ struct sdhci_host *host;
+ int i, ro_count;
+
+ host = mmc_priv(mmc);
+
+ if (!(host->quirks & SDHCI_QUIRK_UNSTABLE_RO_DETECT))
+ return check_ro(host);
+
+ ro_count = 0;
+ for (i = 0; i < SAMPLE_COUNT; i++) {
+ if (check_ro(host)) {
+ if (++ro_count > SAMPLE_COUNT / 2)
+ return 1;
+ }
+ msleep(30);
+ }
+ return 0;
+}
+
+static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+ struct sdhci_host *host;
+ unsigned long flags;
+
+ host = mmc_priv(mmc);
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ if (host->flags & SDHCI_DEVICE_DEAD)
+ goto out;
+
+ if (enable)
+ sdhci_unmask_irqs(host, SDHCI_INT_CARD_INT);
+ else
+ sdhci_mask_irqs(host, SDHCI_INT_CARD_INT);
+out:
+ mmiowb();
+
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
+ struct mmc_ios *ios)
+{
+ struct sdhci_host *host;
+ u8 pwr;
+ u16 clk, ctrl;
+ u32 present_state;
+
+ host = mmc_priv(mmc);
+
+ /*
+ * Signal Voltage Switching is only applicable for Host Controllers
+ * v3.00 and above.
+ */
+ if (host->version < SDHCI_SPEC_300)
+ return 0;
+
+ /*
+ * We first check whether the request is to set signalling voltage
+ * to 3.3V. If so, we change the voltage to 3.3V and return quickly.
+ */
+ ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ if (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_330) {
+ /* Set 1.8V Signal Enable in the Host Control2 register to 0 */
+ ctrl &= ~SDHCI_CTRL_VDD_180;
+ sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
+
+ /* Wait for 5ms */
+ usleep_range(5000, 5500);
+
+ /* 3.3V regulator output should be stable within 5 ms */
+ ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ if (!(ctrl & SDHCI_CTRL_VDD_180))
+ return 0;
+ else {
+ printk(KERN_INFO DRIVER_NAME ": Switching to 3.3V "
+ "signalling voltage failed\n");
+ return -EIO;
+ }
+ } else if (!(ctrl & SDHCI_CTRL_VDD_180) &&
+ (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_180)) {
+ /* Stop SDCLK */
+ clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
+ clk &= ~SDHCI_CLOCK_CARD_EN;
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
+
+ /* Check whether DAT[3:0] is 0000 */
+ present_state = sdhci_readl(host, SDHCI_PRESENT_STATE);
+ if (!((present_state & SDHCI_DATA_LVL_MASK) >>
+ SDHCI_DATA_LVL_SHIFT)) {
+ /*
+ * Enable 1.8V Signal Enable in the Host Control2
+ * register
+ */
+ ctrl |= SDHCI_CTRL_VDD_180;
+ sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
+
+ /* Wait for 5ms */
+ usleep_range(5000, 5500);
+
+ ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ if (ctrl & SDHCI_CTRL_VDD_180) {
+ /* Provide SDCLK again and wait for 1ms*/
+ clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
+ clk |= SDHCI_CLOCK_CARD_EN;
+ sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
+ usleep_range(1000, 1500);
+
+ /*
+ * If DAT[3:0] level is 1111b, then the card
+ * was successfully switched to 1.8V signaling.
+ */
+ present_state = sdhci_readl(host,
+ SDHCI_PRESENT_STATE);
+ if ((present_state & SDHCI_DATA_LVL_MASK) ==
+ SDHCI_DATA_LVL_MASK)
+ return 0;
+ }
+ }
+
+ /*
+ * If we are here, that means the switch to 1.8V signaling
+ * failed. We power cycle the card, and retry initialization
+ * sequence by setting S18R to 0.
+ */
+ pwr = sdhci_readb(host, SDHCI_POWER_CONTROL);
+ pwr &= ~SDHCI_POWER_ON;
+ sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL);
+
+ /* Wait for 1ms as per the spec */
+ usleep_range(1000, 1500);
+ pwr |= SDHCI_POWER_ON;
+ sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL);
+
+ printk(KERN_INFO DRIVER_NAME ": Switching to 1.8V signalling "
+ "voltage failed, retrying with S18R set to 0\n");
+ return -EAGAIN;
+ } else
+ /* No signal voltage switch required */
+ return 0;
+}
+
+static int sdhci_execute_tuning(struct mmc_host *mmc)
+{
+ struct sdhci_host *host;
+ u16 ctrl;
+ u32 ier;
+ int tuning_loop_counter = MAX_TUNING_LOOP;
+ unsigned long timeout;
+ int err = 0;
+
+ host = mmc_priv(mmc);
+
+ disable_irq(host->irq);
+ spin_lock(&host->lock);
+
+ ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+
+ /*
+ * Host Controller needs tuning only in case of SDR104 mode
+ * and for SDR50 mode when Use Tuning for SDR50 is set in
+ * Capabilities register.
+ */
+ if (((ctrl & SDHCI_CTRL_UHS_MASK) == SDHCI_CTRL_UHS_SDR104) ||
+ (((ctrl & SDHCI_CTRL_UHS_MASK) == SDHCI_CTRL_UHS_SDR50) &&
+ (host->flags & SDHCI_SDR50_NEEDS_TUNING)))
+ ctrl |= SDHCI_CTRL_EXEC_TUNING;
+ else {
+ spin_unlock(&host->lock);
+ enable_irq(host->irq);
+ return 0;
+ }
+
+ if (ctrl & SDHCI_CTRL_EXEC_TUNING) {
+ if (host->ops->platform_execute_tuning) {
+ spin_unlock(&host->lock);
+ enable_irq(host->irq);
+ return host->ops->platform_execute_tuning(host);
+ }
+ }
+
+ sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
+
+ /*
+ * As per the Host Controller spec v3.00, tuning command
+ * generates Buffer Read Ready interrupt, so enable that.
+ *
+ * Note: The spec clearly says that when tuning sequence
+ * is being performed, the controller does not generate
+ * interrupts other than Buffer Read Ready interrupt. But
+ * to make sure we don't hit a controller bug, we _only_
+ * enable Buffer Read Ready interrupt here.
+ */
+ ier = sdhci_readl(host, SDHCI_INT_ENABLE);
+ sdhci_clear_set_irqs(host, ier, SDHCI_INT_DATA_AVAIL);
+
+ /*
+ * Issue CMD19 repeatedly till Execute Tuning is set to 0 or the number
+ * of loops reaches 40 times or a timeout of 150ms occurs.
+ */
+ timeout = 150;
+ do {
+ struct mmc_command cmd = {0};
+ struct mmc_request mrq = {0};
+
+ if (!tuning_loop_counter && !timeout)
+ break;
+
+ cmd.opcode = MMC_SEND_TUNING_BLOCK;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
+ cmd.retries = 0;
+ cmd.data = NULL;
+ cmd.error = 0;
+
+ mrq.cmd = &cmd;
+ host->mrq = &mrq;
+
+ /*
+ * In response to CMD19, the card sends 64 bytes of tuning
+ * block to the Host Controller. So we set the block size
+ * to 64 here.
+ */
+ sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, 64), SDHCI_BLOCK_SIZE);
+
+ /*
+ * The tuning block is sent by the card to the host controller.
+ * So we set the TRNS_READ bit in the Transfer Mode register.
+ * This also takes care of setting DMA Enable and Multi Block
+ * Select in the same register to 0.
+ */
+ sdhci_writew(host, SDHCI_TRNS_READ, SDHCI_TRANSFER_MODE);
+
+ sdhci_send_command(host, &cmd);
+
+ host->cmd = NULL;
+ host->mrq = NULL;
+
+ spin_unlock(&host->lock);
+ enable_irq(host->irq);
+
+ /* Wait for Buffer Read Ready interrupt */
+ wait_event_interruptible_timeout(host->buf_ready_int,
+ (host->tuning_done == 1),
+ msecs_to_jiffies(50));
+ disable_irq(host->irq);
+ spin_lock(&host->lock);
+
+ if (!host->tuning_done) {
+ printk(KERN_INFO DRIVER_NAME ": Timeout waiting for "
+ "Buffer Read Ready interrupt during tuning "
+ "procedure, falling back to fixed sampling "
+ "clock\n");
+ ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ ctrl &= ~SDHCI_CTRL_TUNED_CLK;
+ ctrl &= ~SDHCI_CTRL_EXEC_TUNING;
+ sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
+
+ err = -EIO;
+ goto out;
+ }
+
+ host->tuning_done = 0;
+
+ ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ tuning_loop_counter--;
+ timeout--;
+ mdelay(1);
+ } while (ctrl & SDHCI_CTRL_EXEC_TUNING);
+
+ /*
+ * The Host Driver has exhausted the maximum number of loops allowed,
+ * so use fixed sampling frequency.
+ */
+ if (!tuning_loop_counter || !timeout) {
+ ctrl &= ~SDHCI_CTRL_TUNED_CLK;
+ sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
+ } else {
+ if (!(ctrl & SDHCI_CTRL_TUNED_CLK)) {
+ printk(KERN_INFO DRIVER_NAME ": Tuning procedure"
+ " failed, falling back to fixed sampling"
+ " clock\n");
+ err = -EIO;
+ }
+ }
+
+out:
+ /*
+ * If this is the very first time we are here, we start the retuning
+ * timer. Since only during the first time, SDHCI_NEEDS_RETUNING
+ * flag won't be set, we check this condition before actually starting
+ * the timer.
+ */
+ if (!(host->flags & SDHCI_NEEDS_RETUNING) && host->tuning_count &&
+ (host->tuning_mode == SDHCI_TUNING_MODE_1)) {
+ mod_timer(&host->tuning_timer, jiffies +
+ host->tuning_count * HZ);
+ /* Tuning mode 1 limits the maximum data length to 4MB */
+ mmc->max_blk_count = (4 * 1024 * 1024) / mmc->max_blk_size;
+ } else {
+ host->flags &= ~SDHCI_NEEDS_RETUNING;
+ /* Reload the new initial value for timer */
+ if (host->tuning_mode == SDHCI_TUNING_MODE_1)
+ mod_timer(&host->tuning_timer, jiffies +
+ host->tuning_count * HZ);
+ }
+
+ /*
+ * In case tuning fails, host controllers which support re-tuning can
+ * try tuning again at a later time, when the re-tuning timer expires.
+ * So for these controllers, we return 0. Since there might be other
+ * controllers who do not have this capability, we return error for
+ * them.
+ */
+ if (err && host->tuning_count &&
+ host->tuning_mode == SDHCI_TUNING_MODE_1)
+ err = 0;
+
+ sdhci_clear_set_irqs(host, SDHCI_INT_DATA_AVAIL, ier);
+ spin_unlock(&host->lock);
+ enable_irq(host->irq);
+
+ return err;
+}
+
+static void sdhci_enable_preset_value(struct mmc_host *mmc, bool enable)
+{
+ struct sdhci_host *host;
+ u16 ctrl;
+ unsigned long flags;
+
+ host = mmc_priv(mmc);
+
+ /* Host Controller v3.00 defines preset value registers */
+ if (host->version < SDHCI_SPEC_300)
+ return;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+
+ /*
+ * We only enable or disable Preset Value if they are not already
+ * enabled or disabled respectively. Otherwise, we bail out.
+ */
+ if (enable && !(ctrl & SDHCI_CTRL_PRESET_VAL_ENABLE)) {
+ ctrl |= SDHCI_CTRL_PRESET_VAL_ENABLE;
+ sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
+ } else if (!enable && (ctrl & SDHCI_CTRL_PRESET_VAL_ENABLE)) {
+ ctrl &= ~SDHCI_CTRL_PRESET_VAL_ENABLE;
+ sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
+ }
+
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void sdhci_post_req(struct mmc_host *mmc, struct mmc_request *mrq,
+ int err)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+ struct mmc_data *data = mrq->data;
+
+ if (host->flags & SDHCI_REQ_USE_DMA) {
+ if (data->host_cookie)
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+ data->flags & MMC_DATA_WRITE ? \
+ DMA_TO_DEVICE : DMA_FROM_DEVICE);
+ mrq->data->host_cookie = 0;
+ }
+}
+
+static int sdhci_pre_dma_transfer(struct sdhci_host *host,
+ struct mmc_data *data,
+ struct sdhci_host_next *next)
+{
+ int sg_count;
+
+ if (!next && data->host_cookie &&
+ data->host_cookie != host->next_data.cookie) {
+ printk(KERN_DEBUG "[%s] invalid cookie: data->host_cookie %d"
+ " host->next_data.cookie %d\n",
+ __func__, data->host_cookie, host->next_data.cookie);
+ data->host_cookie = 0;
+ }
+
+ /* Check if next job is already prepared */
+ if (next ||
+ (!next && data->host_cookie != host->next_data.cookie)) {
+ sg_count = dma_map_sg(mmc_dev(host->mmc), data->sg,
+ data->sg_len,
+ data->flags & MMC_DATA_WRITE ? \
+ DMA_TO_DEVICE : DMA_FROM_DEVICE);
+
+ } else {
+ sg_count = host->next_data.sg_count;
+ host->next_data.sg_count = 0;
+ }
+
+
+ if (sg_count == 0)
+ return -EINVAL;
+
+ if (next) {
+ next->sg_count = sg_count;
+ data->host_cookie = ++next->cookie < 0 ? 1 : next->cookie;
+ } else
+ host->sg_count = sg_count;
+
+ return sg_count;
+}
+
+static void sdhci_pre_req(struct mmc_host *mmc, struct mmc_request *mrq,
+ bool is_first_req)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+
+ if (mrq->data->host_cookie) {
+ mrq->data->host_cookie = 0;
+ return ;
+ }
+
+ if (host->flags & SDHCI_REQ_USE_DMA)
+ if (sdhci_pre_dma_transfer(host,
+ mrq->data,
+ &host->next_data) < 0)
+ mrq->data->host_cookie = 0;
+}
+
+static const struct mmc_host_ops sdhci_ops = {
+ .request = sdhci_request,
+ .post_req = sdhci_post_req,
+ .pre_req = sdhci_pre_req,
+ .set_ios = sdhci_set_ios,
+ .get_ro = sdhci_get_ro,
+ .enable_sdio_irq = sdhci_enable_sdio_irq,
+ .start_signal_voltage_switch = sdhci_start_signal_voltage_switch,
+ .execute_tuning = sdhci_execute_tuning,
+ .enable_preset_value = sdhci_enable_preset_value,
+};
+
+/*****************************************************************************\
+ * *
+ * Tasklets *
+ * *
+\*****************************************************************************/
+
+static void sdhci_tasklet_card(unsigned long param)
+{
+ struct sdhci_host *host;
+ unsigned long flags;
+
+ host = (struct sdhci_host*)param;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ if (host->clk_mgr_en)
+ goto out;
+
+ if (!(sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) {
+ if (host->mrq) {
+ printk(KERN_ERR "%s: Card removed during transfer!\n",
+ mmc_hostname(host->mmc));
+ printk(KERN_ERR "%s: Resetting controller.\n",
+ mmc_hostname(host->mmc));
+
+ sdhci_reset(host, SDHCI_RESET_CMD);
+ sdhci_reset(host, SDHCI_RESET_DATA);
+
+ host->mrq->cmd->error = -ENOMEDIUM;
+ tasklet_schedule(&host->finish_tasklet);
+ }
+ }
+out:
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ mmc_detect_change(host->mmc, msecs_to_jiffies(500));
+}
+
+static void sdhci_tasklet_finish(unsigned long param)
+{
+ struct sdhci_host *host;
+ unsigned long flags;
+ struct mmc_request *mrq;
+
+ host = (struct sdhci_host*)param;
+
+ /*
+ * If this tasklet gets rescheduled while running, it will
+ * be run again afterwards but without any active request.
+ */
+ if (!host->mrq)
+ return;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ del_timer(&host->timer);
+
+ mrq = host->mrq;
+
+ /*
+ * The controller needs a reset of internal state machines
+ * upon error conditions.
+ */
+ if (!(host->flags & SDHCI_DEVICE_DEAD) &&
+ ((mrq->cmd && mrq->cmd->error) ||
+ (mrq->data && (mrq->data->error ||
+ (mrq->data->stop && mrq->data->stop->error))) ||
+ (host->quirks & SDHCI_QUIRK_RESET_AFTER_REQUEST))) {
+
+ /* Some controllers need this kick or reset won't work here */
+ if (host->quirks & SDHCI_QUIRK_CLOCK_BEFORE_RESET) {
+ unsigned int clock;
+
+ /* This is to force an update */
+ clock = host->clock;
+ host->clock = 0;
+ sdhci_set_clock(host, clock);
+ }
+
+ /* Spec says we should do both at the same time, but Ricoh
+ controllers do not like that. */
+ sdhci_reset(host, SDHCI_RESET_CMD);
+ sdhci_reset(host, SDHCI_RESET_DATA);
+ }
+
+ host->mrq = NULL;
+ host->cmd = NULL;
+ host->data = NULL;
+
+#ifndef CONFIG_SDHCI_USE_LEDS_CLASS
+ sdhci_deactivate_led(host);
+#endif
+
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+ sdhci_disable_clk(host, CLK_TIMEOUT);
+
+ mmc_request_done(host->mmc, mrq);
+}
+
+static void sdhci_timeout_timer(unsigned long data)
+{
+ struct sdhci_host *host;
+ unsigned long flags;
+
+ host = (struct sdhci_host*)data;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ if (host->mrq) {
+ printk(KERN_ERR "%s: Timeout waiting for hardware "
+ "interrupt.\n", mmc_hostname(host->mmc));
+ sdhci_dumpregs(host);
+
+ if (host->data) {
+ host->data->error = -ETIMEDOUT;
+ sdhci_finish_data(host);
+ } else {
+ if (host->cmd)
+ host->cmd->error = -ETIMEDOUT;
+ else
+ host->mrq->cmd->error = -ETIMEDOUT;
+
+ tasklet_schedule(&host->finish_tasklet);
+ }
+ }
+
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void sdhci_tuning_timer(unsigned long data)
+{
+ struct sdhci_host *host;
+ unsigned long flags;
+
+ host = (struct sdhci_host *)data;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ host->flags |= SDHCI_NEEDS_RETUNING;
+
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+/*****************************************************************************\
+ * *
+ * Interrupt handling *
+ * *
+\*****************************************************************************/
+
+static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask)
+{
+ BUG_ON(intmask == 0);
+
+ if (!host->cmd) {
+ printk(KERN_ERR "%s: Got command interrupt 0x%08x even "
+ "though no command operation was in progress.\n",
+ mmc_hostname(host->mmc), (unsigned)intmask);
+ sdhci_dumpregs(host);
+ return;
+ }
+
+ if (intmask & SDHCI_INT_TIMEOUT)
+ host->cmd->error = -ETIMEDOUT;
+ else if (intmask & (SDHCI_INT_CRC | SDHCI_INT_END_BIT |
+ SDHCI_INT_INDEX))
+ host->cmd->error = -EILSEQ;
+
+ if (host->cmd->error) {
+ tasklet_schedule(&host->finish_tasklet);
+ return;
+ }
+
+ /*
+ * The host can send and interrupt when the busy state has
+ * ended, allowing us to wait without wasting CPU cycles.
+ * Unfortunately this is overloaded on the "data complete"
+ * interrupt, so we need to take some care when handling
+ * it.
+ *
+ * Note: The 1.0 specification is a bit ambiguous about this
+ * feature so there might be some problems with older
+ * controllers.
+ */
+ if (host->cmd->flags & MMC_RSP_BUSY) {
+ if (host->cmd->data)
+ DBG("Cannot wait for busy signal when also "
+ "doing a data transfer");
+ else if (!(host->quirks & SDHCI_QUIRK_NO_BUSY_IRQ))
+ return;
+
+ /* The controller does not support the end-of-busy IRQ,
+ * fall through and take the SDHCI_INT_RESPONSE */
+ }
+
+ if (intmask & SDHCI_INT_RESPONSE)
+ sdhci_finish_command(host);
+}
+
+#ifdef CONFIG_MMC_DEBUG
+static void sdhci_show_adma_error(struct sdhci_host *host)
+{
+ const char *name = mmc_hostname(host->mmc);
+ u8 *desc = host->adma_desc;
+ __le32 *dma;
+ __le16 *len;
+ u8 attr;
+
+ sdhci_dumpregs(host);
+
+ while (true) {
+ dma = (__le32 *)(desc + 4);
+ len = (__le16 *)(desc + 2);
+ attr = *desc;
+
+ DBG("%s: %p: DMA 0x%08x, LEN 0x%04x, Attr=0x%02x\n",
+ name, desc, le32_to_cpu(*dma), le16_to_cpu(*len), attr);
+
+ desc += 8;
+
+ if (attr & 2)
+ break;
+ }
+}
+#else
+static void sdhci_show_adma_error(struct sdhci_host *host) { }
+#endif
+
+static void sdhci_data_irq(struct sdhci_host *host, u32 intmask)
+{
+ BUG_ON(intmask == 0);
+
+ /* CMD19 generates _only_ Buffer Read Ready interrupt */
+ if (intmask & SDHCI_INT_DATA_AVAIL) {
+ if (SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND)) ==
+ MMC_SEND_TUNING_BLOCK) {
+ host->tuning_done = 1;
+ wake_up(&host->buf_ready_int);
+ return;
+ }
+ }
+
+ if (!host->data) {
+ /*
+ * The "data complete" interrupt is also used to
+ * indicate that a busy state has ended. See comment
+ * above in sdhci_cmd_irq().
+ */
+ if (host->cmd && (host->cmd->flags & MMC_RSP_BUSY)) {
+ if (intmask & SDHCI_INT_DATA_END) {
+ sdhci_finish_command(host);
+ return;
+ }
+ }
+
+ printk(KERN_ERR "%s: Got data interrupt 0x%08x even "
+ "though no data operation was in progress.\n",
+ mmc_hostname(host->mmc), (unsigned)intmask);
+ sdhci_dumpregs(host);
+
+ return;
+ }
+
+ if (intmask & SDHCI_INT_DATA_TIMEOUT)
+ host->data->error = -ETIMEDOUT;
+ else if (intmask & SDHCI_INT_DATA_END_BIT)
+ host->data->error = -EILSEQ;
+ else if ((intmask & SDHCI_INT_DATA_CRC) &&
+ SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND))
+ != MMC_BUS_TEST_R)
+ host->data->error = -EILSEQ;
+ else if (intmask & SDHCI_INT_ADMA_ERROR) {
+ printk(KERN_ERR "%s: ADMA error\n", mmc_hostname(host->mmc));
+ sdhci_show_adma_error(host);
+ host->data->error = -EIO;
+ }
+
+ if (host->data->error)
+ sdhci_finish_data(host);
+ else {
+ if (intmask & (SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL))
+ sdhci_transfer_pio(host);
+
+ /*
+ * We currently don't do anything fancy with DMA
+ * boundaries, but as we can't disable the feature
+ * we need to at least restart the transfer.
+ *
+ * According to the spec sdhci_readl(host, SDHCI_DMA_ADDRESS)
+ * should return a valid address to continue from, but as
+ * some controllers are faulty, don't trust them.
+ */
+ if (intmask & SDHCI_INT_DMA_END) {
+ u32 dmastart, dmanow;
+ dmastart = sg_dma_address(host->data->sg);
+ dmanow = dmastart + host->data->bytes_xfered;
+ /*
+ * Force update to the next DMA block boundary.
+ */
+ dmanow = (dmanow &
+ ~(SDHCI_DEFAULT_BOUNDARY_SIZE - 1)) +
+ SDHCI_DEFAULT_BOUNDARY_SIZE;
+ host->data->bytes_xfered = dmanow - dmastart;
+ DBG("%s: DMA base 0x%08x, transferred 0x%06x bytes,"
+ " next 0x%08x\n",
+ mmc_hostname(host->mmc), dmastart,
+ host->data->bytes_xfered, dmanow);
+ sdhci_writel(host, dmanow, SDHCI_DMA_ADDRESS);
+ }
+
+ if (intmask & SDHCI_INT_DATA_END) {
+ if (host->cmd) {
+ /*
+ * Data managed to finish before the
+ * command completed. Make sure we do
+ * things in the proper order.
+ */
+ host->data_early = 1;
+ } else {
+ sdhci_finish_data(host);
+ }
+ }
+ }
+}
+
+static irqreturn_t sdhci_irq(int irq, void *dev_id)
+{
+ irqreturn_t result;
+ struct sdhci_host* host = dev_id;
+ u32 intmask;
+ int cardint = 0;
+
+ spin_lock(&host->lock);
+
+ intmask = sdhci_readl(host, SDHCI_INT_STATUS);
+
+ if (!intmask || intmask == 0xffffffff) {
+ result = IRQ_NONE;
+ goto out;
+ }
+
+ DBG("*** %s got interrupt: 0x%08x\n",
+ mmc_hostname(host->mmc), intmask);
+
+ if (intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) {
+ sdhci_writel(host, intmask & (SDHCI_INT_CARD_INSERT |
+ SDHCI_INT_CARD_REMOVE), SDHCI_INT_STATUS);
+ tasklet_schedule(&host->card_tasklet);
+ }
+
+ intmask &= ~(SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE);
+
+ if (intmask & SDHCI_INT_CMD_MASK) {
+ sdhci_writel(host, intmask & SDHCI_INT_CMD_MASK,
+ SDHCI_INT_STATUS);
+ sdhci_cmd_irq(host, intmask & SDHCI_INT_CMD_MASK);
+ }
+
+ if (intmask & SDHCI_INT_DATA_MASK) {
+ sdhci_writel(host, intmask & SDHCI_INT_DATA_MASK,
+ SDHCI_INT_STATUS);
+ sdhci_data_irq(host, intmask & SDHCI_INT_DATA_MASK);
+ }
+
+ intmask &= ~(SDHCI_INT_CMD_MASK | SDHCI_INT_DATA_MASK);
+
+ intmask &= ~SDHCI_INT_ERROR;
+
+ if (intmask & SDHCI_INT_BUS_POWER) {
+ printk(KERN_ERR "%s: Card is consuming too much power!\n",
+ mmc_hostname(host->mmc));
+ sdhci_writel(host, SDHCI_INT_BUS_POWER, SDHCI_INT_STATUS);
+ }
+
+ intmask &= ~SDHCI_INT_BUS_POWER;
+
+ if (intmask & SDHCI_INT_CARD_INT)
+ cardint = 1;
+
+ intmask &= ~SDHCI_INT_CARD_INT;
+
+ if (intmask) {
+ printk(KERN_ERR "%s: Unexpected interrupt 0x%08x.\n",
+ mmc_hostname(host->mmc), intmask);
+ sdhci_dumpregs(host);
+
+ sdhci_writel(host, intmask, SDHCI_INT_STATUS);
+ }
+
+ result = IRQ_HANDLED;
+
+ mmiowb();
+out:
+ spin_unlock(&host->lock);
+
+ /*
+ * We have to delay this as it calls back into the driver.
+ */
+ if (cardint)
+ mmc_signal_sdio_irq(host->mmc);
+
+ return result;
+}
+
+/*****************************************************************************\
+ * *
+ * Suspend/resume *
+ * *
+\*****************************************************************************/
+
+#ifdef CONFIG_PM
+
+extern void eschc_cd_enable (struct sdhci_host *host, bool enable);
+int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state)
+{
+ int ret;
+
+ sdhci_enable_clk(host);
+ sdhci_disable_card_detection(host);
+
+
+ /* Disable tuning since we are suspending */
+ if (host->version >= SDHCI_SPEC_300 && host->tuning_count &&
+ host->tuning_mode == SDHCI_TUNING_MODE_1) {
+ del_timer_sync(&host->tuning_timer);
+ host->flags &= ~SDHCI_NEEDS_RETUNING;
+ }
+
+ ret = mmc_suspend_host(host->mmc);
+ if (ret)
+ goto out;
+
+ free_irq(host->irq, host);
+
+ if (host->vmmc)
+ ret = regulator_disable(host->vmmc);
+
+out:
+ /* sync worker
+ * mmc_suspend_host may disable the clk
+ */
+ sdhci_enable_clk(host);
+ sdhci_disable_clk(host, 0);
+ eschc_cd_enable (host, 0);
+ return ret;
+}
+
+EXPORT_SYMBOL_GPL(sdhci_suspend_host);
+
+int sdhci_resume_host(struct sdhci_host *host)
+{
+ int ret;
+
+ if (host->vmmc) {
+ ret = regulator_enable(host->vmmc);
+ if (ret)
+ return ret;
+ }
+
+ eschc_cd_enable (host, 1);
+ sdhci_enable_clk(host);
+ if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) {
+ if (host->ops->enable_dma)
+ host->ops->enable_dma(host);
+ }
+
+ ret = request_irq(host->irq, sdhci_irq, IRQF_SHARED,
+ mmc_hostname(host->mmc), host);
+ if (ret)
+ goto out;
+
+ sdhci_init(host, (host->mmc->pm_flags & MMC_PM_KEEP_POWER));
+ mmiowb();
+
+ ret = mmc_resume_host(host->mmc);
+
+ /* mmc_resume_host may disable the clk */
+ sdhci_enable_clk(host);
+ sdhci_enable_card_detection(host);
+
+out:
+ /* sync worker */
+ sdhci_disable_clk(host, 0);
+
+ /* Set the re-tuning expiration flag */
+ if ((host->version >= SDHCI_SPEC_300) && host->tuning_count &&
+ (host->tuning_mode == SDHCI_TUNING_MODE_1))
+ host->flags |= SDHCI_NEEDS_RETUNING;
+
+ return ret;
+}
+
+EXPORT_SYMBOL_GPL(sdhci_resume_host);
+
+void sdhci_enable_irq_wakeups(struct sdhci_host *host)
+{
+ u8 val;
+
+ sdhci_enable_clk(host);
+ val = sdhci_readb(host, SDHCI_WAKE_UP_CONTROL);
+ val |= SDHCI_WAKE_ON_INT;
+ sdhci_writeb(host, val, SDHCI_WAKE_UP_CONTROL);
+ sdhci_disable_clk(host, CLK_TIMEOUT);
+}
+
+EXPORT_SYMBOL_GPL(sdhci_enable_irq_wakeups);
+
+#endif /* CONFIG_PM */
+
+/*****************************************************************************\
+ * *
+ * Device allocation/registration *
+ * *
+\*****************************************************************************/
+
+struct sdhci_host *sdhci_alloc_host(struct device *dev,
+ size_t priv_size)
+{
+ struct mmc_host *mmc;
+ struct sdhci_host *host;
+
+ WARN_ON(dev == NULL);
+
+ mmc = mmc_alloc_host(sizeof(struct sdhci_host) + priv_size, dev);
+ if (!mmc)
+ return ERR_PTR(-ENOMEM);
+
+ host = mmc_priv(mmc);
+ host->mmc = mmc;
+
+ return host;
+}
+
+EXPORT_SYMBOL_GPL(sdhci_alloc_host);
+
+int sdhci_add_host(struct sdhci_host *host)
+{
+ struct mmc_host *mmc;
+ u32 caps[2];
+ u32 max_current_caps;
+ unsigned int ocr_avail;
+ int ret;
+
+ WARN_ON(host == NULL);
+ if (host == NULL)
+ return -EINVAL;
+
+ mmc = host->mmc;
+
+ if (debug_quirks)
+ host->quirks = debug_quirks;
+
+ if (host->clk_mgr_en)
+ INIT_DELAYED_WORK(&host->clk_worker, sdhci_clk_worker);
+
+ sdhci_enable_clk(host);
+ sdhci_reset(host, SDHCI_RESET_ALL);
+
+ host->version = sdhci_readw(host, SDHCI_HOST_VERSION);
+ host->version = (host->version & SDHCI_SPEC_VER_MASK)
+ >> SDHCI_SPEC_VER_SHIFT;
+ if (host->version > SDHCI_SPEC_300) {
+ printk(KERN_ERR "%s: Unknown controller version (%d). "
+ "You may experience problems.\n", mmc_hostname(mmc),
+ host->version);
+ }
+
+ caps[0] = (host->quirks & SDHCI_QUIRK_MISSING_CAPS) ? host->caps :
+ sdhci_readl(host, SDHCI_CAPABILITIES);
+
+ caps[1] = (host->version >= SDHCI_SPEC_300) ?
+ sdhci_readl(host, SDHCI_CAPABILITIES_1) : 0;
+
+ if (host->quirks & SDHCI_QUIRK_FORCE_DMA)
+ host->flags |= SDHCI_USE_SDMA;
+ else if (!(caps[0] & SDHCI_CAN_DO_SDMA))
+ DBG("Controller doesn't have SDMA capability\n");
+ else
+ host->flags |= SDHCI_USE_SDMA;
+
+ if ((host->quirks & SDHCI_QUIRK_BROKEN_DMA) &&
+ (host->flags & SDHCI_USE_SDMA)) {
+ DBG("Disabling DMA as it is marked broken\n");
+ host->flags &= ~SDHCI_USE_SDMA;
+ }
+
+ if ((host->version >= SDHCI_SPEC_200) &&
+ (caps[0] & SDHCI_CAN_DO_ADMA2))
+ host->flags |= SDHCI_USE_ADMA;
+
+ if ((host->quirks & SDHCI_QUIRK_BROKEN_ADMA) &&
+ (host->flags & SDHCI_USE_ADMA)) {
+ DBG("Disabling ADMA as it is marked broken\n");
+ host->flags &= ~SDHCI_USE_ADMA;
+ }
+
+ if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) {
+ if (host->ops->enable_dma) {
+ if (host->ops->enable_dma(host)) {
+ printk(KERN_WARNING "%s: No suitable DMA "
+ "available. Falling back to PIO.\n",
+ mmc_hostname(mmc));
+ host->flags &=
+ ~(SDHCI_USE_SDMA | SDHCI_USE_ADMA);
+ }
+ }
+ }
+
+ if (host->flags & SDHCI_USE_ADMA) {
+ /*
+ * We need to allocate descriptors for all sg entries
+ * (128) and potentially one alignment transfer for
+ * each of those entries.
+ */
+ host->adma_desc = kmalloc((128 * 2 + 1) * 4, GFP_KERNEL);
+ host->align_buffer = kmalloc(128 * 4, GFP_KERNEL);
+ if (!host->adma_desc || !host->align_buffer) {
+ kfree(host->adma_desc);
+ kfree(host->align_buffer);
+ printk(KERN_WARNING "%s: Unable to allocate ADMA "
+ "buffers. Falling back to standard DMA.\n",
+ mmc_hostname(mmc));
+ host->flags &= ~SDHCI_USE_ADMA;
+ }
+ }
+
+ /*
+ * If we use DMA, then it's up to the caller to set the DMA
+ * mask, but PIO does not need the hw shim so we set a new
+ * mask here in that case.
+ */
+ if (!(host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA))) {
+ host->dma_mask = DMA_BIT_MASK(64);
+ mmc_dev(host->mmc)->dma_mask = &host->dma_mask;
+ }
+
+ if (host->version >= SDHCI_SPEC_300)
+ host->max_clk = (caps[0] & SDHCI_CLOCK_V3_BASE_MASK)
+ >> SDHCI_CLOCK_BASE_SHIFT;
+ else
+ host->max_clk = (caps[0] & SDHCI_CLOCK_BASE_MASK)
+ >> SDHCI_CLOCK_BASE_SHIFT;
+
+ host->max_clk *= 1000000;
+ if (host->max_clk == 0 || host->quirks &
+ SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN) {
+ if (!host->ops->get_max_clock) {
+ printk(KERN_ERR
+ "%s: Hardware doesn't specify base clock "
+ "frequency.\n", mmc_hostname(mmc));
+ sdhci_disable_clk(host, 0);
+ return -ENODEV;
+ }
+ host->max_clk = host->ops->get_max_clock(host);
+ }
+
+ host->timeout_clk =
+ (caps[0] & SDHCI_TIMEOUT_CLK_MASK) >> SDHCI_TIMEOUT_CLK_SHIFT;
+ if (host->timeout_clk == 0) {
+ if (host->ops->get_timeout_clock) {
+ host->timeout_clk = host->ops->get_timeout_clock(host);
+ } else if (!(host->quirks &
+ SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK)) {
+ printk(KERN_ERR
+ "%s: Hardware doesn't specify timeout clock "
+ "frequency.\n", mmc_hostname(mmc));
+ sdhci_disable_clk(host, 0);
+ return -ENODEV;
+ }
+ }
+ if (caps[0] & SDHCI_TIMEOUT_CLK_UNIT)
+ host->timeout_clk *= 1000;
+
+ host->next_data.cookie = 1;
+ /*
+ * In case of Host Controller v3.00, find out whether clock
+ * multiplier is supported.
+ */
+ host->clk_mul = (caps[1] & SDHCI_CLOCK_MUL_MASK) >>
+ SDHCI_CLOCK_MUL_SHIFT;
+
+ /*
+ * In case the value in Clock Multiplier is 0, then programmable
+ * clock mode is not supported, otherwise the actual clock
+ * multiplier is one more than the value of Clock Multiplier
+ * in the Capabilities Register.
+ */
+ if (host->clk_mul)
+ host->clk_mul += 1;
+
+ /*
+ * Set host parameters.
+ */
+ mmc->ops = &sdhci_ops;
+ mmc->f_max = host->max_clk;
+ if (host->ops->get_min_clock)
+ mmc->f_min = host->ops->get_min_clock(host);
+ else if (host->version >= SDHCI_SPEC_300) {
+ if (host->clk_mul) {
+ mmc->f_min = (host->max_clk * host->clk_mul) / 1024;
+ mmc->f_max = host->max_clk * host->clk_mul;
+ } else
+ mmc->f_min = host->max_clk / SDHCI_MAX_DIV_SPEC_300;
+ } else
+ mmc->f_min = host->max_clk / SDHCI_MAX_DIV_SPEC_200;
+
+ mmc->caps |= MMC_CAP_SDIO_IRQ | MMC_CAP_ERASE | MMC_CAP_CMD23;
+
+ if (host->quirks & SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12)
+ host->flags |= SDHCI_AUTO_CMD12;
+
+ /* Auto-CMD23 stuff only works in ADMA or PIO. */
+ if ((host->version >= SDHCI_SPEC_300) &&
+ !(host->quirks2 & SDHCI_QUIRK_BROKEN_AUTO_CMD23) &&
+ ((host->flags & SDHCI_USE_ADMA) ||
+ !(host->flags & SDHCI_USE_SDMA))) {
+ host->flags |= SDHCI_AUTO_CMD23;
+ DBG("%s: Auto-CMD23 available\n", mmc_hostname(mmc));
+ } else {
+ DBG("%s: Auto-CMD23 unavailable\n", mmc_hostname(mmc));
+ }
+
+ /*
+ * A controller may support 8-bit width, but the board itself
+ * might not have the pins brought out. Boards that support
+ * 8-bit width must set "mmc->caps |= MMC_CAP_8_BIT_DATA;" in
+ * their platform code before calling sdhci_add_host(), and we
+ * won't assume 8-bit width for hosts without that CAP.
+ */
+ if (!(host->quirks & SDHCI_QUIRK_FORCE_1_BIT_DATA))
+ mmc->caps |= MMC_CAP_4_BIT_DATA;
+
+ if (caps[0] & SDHCI_CAN_DO_HISPD)
+ mmc->caps |= MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED;
+
+ if ((host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION) &&
+ mmc_card_is_removable(mmc))
+ mmc->caps |= MMC_CAP_NEEDS_POLL;
+
+ /* UHS-I mode(s) supported by the host controller. */
+ if (host->version >= SDHCI_SPEC_300)
+ mmc->caps |= MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25;
+
+ /* SDR104 supports also implies SDR50 support */
+ if (caps[1] & SDHCI_SUPPORT_SDR104)
+ mmc->caps |= MMC_CAP_UHS_SDR104 | MMC_CAP_UHS_SDR50;
+ else if (caps[1] & SDHCI_SUPPORT_SDR50)
+ mmc->caps |= MMC_CAP_UHS_SDR50;
+
+ if (caps[1] & SDHCI_SUPPORT_DDR50)
+ mmc->caps |= MMC_CAP_UHS_DDR50;
+
+ /* Does the host needs tuning for SDR50? */
+ if (caps[1] & SDHCI_USE_SDR50_TUNING)
+ host->flags |= SDHCI_SDR50_NEEDS_TUNING;
+
+ /* Driver Type(s) (A, C, D) supported by the host */
+ if (caps[1] & SDHCI_DRIVER_TYPE_A)
+ mmc->caps |= MMC_CAP_DRIVER_TYPE_A;
+ if (caps[1] & SDHCI_DRIVER_TYPE_C)
+ mmc->caps |= MMC_CAP_DRIVER_TYPE_C;
+ if (caps[1] & SDHCI_DRIVER_TYPE_D)
+ mmc->caps |= MMC_CAP_DRIVER_TYPE_D;
+
+ /* Initial value for re-tuning timer count */
+ host->tuning_count = (caps[1] & SDHCI_RETUNING_TIMER_COUNT_MASK) >>
+ SDHCI_RETUNING_TIMER_COUNT_SHIFT;
+
+ /*
+ * In case Re-tuning Timer is not disabled, the actual value of
+ * re-tuning timer will be 2 ^ (n - 1).
+ */
+ if (host->tuning_count)
+ host->tuning_count = 1 << (host->tuning_count - 1);
+
+ /* Re-tuning mode supported by the Host Controller */
+ host->tuning_mode = (caps[1] & SDHCI_RETUNING_MODE_MASK) >>
+ SDHCI_RETUNING_MODE_SHIFT;
+
+ ocr_avail = 0;
+ /*
+ * According to SD Host Controller spec v3.00, if the Host System
+ * can afford more than 150mA, Host Driver should set XPC to 1. Also
+ * the value is meaningful only if Voltage Support in the Capabilities
+ * register is set. The actual current value is 4 times the register
+ * value.
+ */
+ max_current_caps = sdhci_readl(host, SDHCI_MAX_CURRENT);
+
+ if (caps[0] & SDHCI_CAN_VDD_330) {
+ int max_current_330;
+
+ ocr_avail |= MMC_VDD_32_33 | MMC_VDD_33_34;
+
+ max_current_330 = ((max_current_caps &
+ SDHCI_MAX_CURRENT_330_MASK) >>
+ SDHCI_MAX_CURRENT_330_SHIFT) *
+ SDHCI_MAX_CURRENT_MULTIPLIER;
+
+ if (max_current_330 > 150)
+ mmc->caps |= MMC_CAP_SET_XPC_330;
+ }
+ if (caps[0] & SDHCI_CAN_VDD_300) {
+ int max_current_300;
+
+ ocr_avail |= MMC_VDD_29_30 | MMC_VDD_30_31;
+
+ max_current_300 = ((max_current_caps &
+ SDHCI_MAX_CURRENT_300_MASK) >>
+ SDHCI_MAX_CURRENT_300_SHIFT) *
+ SDHCI_MAX_CURRENT_MULTIPLIER;
+
+ if (max_current_300 > 150)
+ mmc->caps |= MMC_CAP_SET_XPC_300;
+ }
+ if (caps[0] & SDHCI_CAN_VDD_180) {
+ int max_current_180;
+
+ ocr_avail |= MMC_VDD_165_195;
+
+ max_current_180 = ((max_current_caps &
+ SDHCI_MAX_CURRENT_180_MASK) >>
+ SDHCI_MAX_CURRENT_180_SHIFT) *
+ SDHCI_MAX_CURRENT_MULTIPLIER;
+
+ if (max_current_180 > 150)
+ mmc->caps |= MMC_CAP_SET_XPC_180;
+
+ /* Maximum current capabilities of the host at 1.8V */
+ if (max_current_180 >= 800)
+ mmc->caps |= MMC_CAP_MAX_CURRENT_800;
+ else if (max_current_180 >= 600)
+ mmc->caps |= MMC_CAP_MAX_CURRENT_600;
+ else if (max_current_180 >= 400)
+ mmc->caps |= MMC_CAP_MAX_CURRENT_400;
+ else
+ mmc->caps |= MMC_CAP_MAX_CURRENT_200;
+ }
+
+ mmc->ocr_avail = ocr_avail;
+ mmc->ocr_avail_sdio = ocr_avail;
+ if (host->ocr_avail_sdio)
+ mmc->ocr_avail_sdio &= host->ocr_avail_sdio;
+ mmc->ocr_avail_sd = ocr_avail;
+ if (host->ocr_avail_sd)
+ mmc->ocr_avail_sd &= host->ocr_avail_sd;
+ else /* normal SD controllers don't support 1.8V */
+ mmc->ocr_avail_sd &= ~MMC_VDD_165_195;
+ mmc->ocr_avail_mmc = ocr_avail;
+ if (host->ocr_avail_mmc)
+ mmc->ocr_avail_mmc &= host->ocr_avail_mmc;
+
+ if (mmc->ocr_avail == 0) {
+ printk(KERN_ERR "%s: Hardware doesn't report any "
+ "support voltages.\n", mmc_hostname(mmc));
+ sdhci_disable_clk(host, 0);
+ return -ENODEV;
+ }
+
+ spin_lock_init(&host->lock);
+
+ /*
+ * Maximum number of segments. Depends on if the hardware
+ * can do scatter/gather or not.
+ */
+ if (host->flags & SDHCI_USE_ADMA)
+ mmc->max_segs = 128;
+ else if (host->flags & SDHCI_USE_SDMA)
+ mmc->max_segs = 1;
+ else /* PIO */
+ mmc->max_segs = 128;
+
+ /*
+ * Maximum number of sectors in one transfer. Limited by DMA boundary
+ * size (512KiB).
+ */
+ mmc->max_req_size = 524288;
+
+ /*
+ * Maximum segment size. Could be one segment with the maximum number
+ * of bytes. When doing hardware scatter/gather, each entry cannot
+ * be larger than 64 KiB though.
+ */
+ if (host->flags & SDHCI_USE_ADMA) {
+ if (host->quirks & SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC)
+ mmc->max_seg_size = 65535;
+ else
+ mmc->max_seg_size = 65536;
+ } else {
+ mmc->max_seg_size = mmc->max_req_size;
+ }
+
+ /*
+ * Maximum block size. This varies from controller to controller and
+ * is specified in the capabilities register.
+ */
+ if (host->quirks & SDHCI_QUIRK_FORCE_BLK_SZ_2048) {
+ mmc->max_blk_size = 2;
+ } else {
+ mmc->max_blk_size = (caps[0] & SDHCI_MAX_BLOCK_MASK) >>
+ SDHCI_MAX_BLOCK_SHIFT;
+ if (mmc->max_blk_size >= 3) {
+ printk(KERN_WARNING "%s: Invalid maximum block size, "
+ "assuming 512 bytes\n", mmc_hostname(mmc));
+ mmc->max_blk_size = 0;
+ }
+ }
+
+ mmc->max_blk_size = 512 << mmc->max_blk_size;
+
+ /*
+ * Maximum block count.
+ */
+ mmc->max_blk_count = (host->quirks & SDHCI_QUIRK_NO_MULTIBLOCK) ? 1 : 65535;
+
+ mmc->tuning_min = host->tuning_min;
+ mmc->tuning_max = host->tuning_max;
+ mmc->tuning_step = host->tuning_step;
+ /*
+ * Init tasklets.
+ */
+ tasklet_init(&host->card_tasklet,
+ sdhci_tasklet_card, (unsigned long)host);
+ tasklet_init(&host->finish_tasklet,
+ sdhci_tasklet_finish, (unsigned long)host);
+
+ setup_timer(&host->timer, sdhci_timeout_timer, (unsigned long)host);
+
+ if (host->version >= SDHCI_SPEC_300) {
+ init_waitqueue_head(&host->buf_ready_int);
+
+ /* Initialize re-tuning timer */
+ init_timer(&host->tuning_timer);
+ host->tuning_timer.data = (unsigned long)host;
+ host->tuning_timer.function = sdhci_tuning_timer;
+ }
+
+ host->vmmc = regulator_get(mmc_dev(mmc), "vmmc");
+ if (IS_ERR(host->vmmc)) {
+ printk(KERN_INFO "%s: no vmmc regulator found\n", mmc_hostname(mmc));
+ host->vmmc = NULL;
+ } else {
+ regulator_enable(host->vmmc);
+ }
+
+ sdhci_init(host, 0);
+
+ ret = request_irq(host->irq, sdhci_irq, IRQF_SHARED,
+ mmc_hostname(mmc), host);
+ if (ret)
+ goto untasklet;
+
+#ifdef CONFIG_MMC_DEBUG
+ sdhci_dumpregs(host);
+#endif
+
+#ifdef CONFIG_SDHCI_USE_LEDS_CLASS
+ snprintf(host->led_name, sizeof(host->led_name),
+ "%s::", mmc_hostname(mmc));
+ host->led.name = host->led_name;
+ host->led.brightness = LED_OFF;
+ host->led.default_trigger = mmc_hostname(mmc);
+ host->led.brightness_set = sdhci_led_control;
+
+ ret = led_classdev_register(mmc_dev(mmc), &host->led);
+ if (ret)
+ goto reset;
+#endif
+
+ mmiowb();
+
+ mmc_add_host(mmc);
+
+ printk(KERN_INFO "%s: SDHCI controller on %s [%s] using %s\n",
+ mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)),
+ (host->flags & SDHCI_USE_ADMA) ? "ADMA" :
+ (host->flags & SDHCI_USE_SDMA) ? "DMA" : "PIO");
+
+ sdhci_enable_card_detection(host);
+ sdhci_disable_clk(host, CLK_TIMEOUT);
+ return 0;
+
+#ifdef CONFIG_SDHCI_USE_LEDS_CLASS
+reset:
+ sdhci_reset(host, SDHCI_RESET_ALL);
+ free_irq(host->irq, host);
+#endif
+untasklet:
+ tasklet_kill(&host->card_tasklet);
+ tasklet_kill(&host->finish_tasklet);
+ sdhci_disable_clk(host, 0);
+ return ret;
+}
+
+EXPORT_SYMBOL_GPL(sdhci_add_host);
+
+void sdhci_remove_host(struct sdhci_host *host, int dead)
+{
+ unsigned long flags;
+
+ sdhci_enable_clk(host);
+
+ if (dead) {
+ spin_lock_irqsave(&host->lock, flags);
+
+ host->flags |= SDHCI_DEVICE_DEAD;
+
+ if (host->mrq) {
+ printk(KERN_ERR "%s: Controller removed during "
+ " transfer!\n", mmc_hostname(host->mmc));
+
+ host->mrq->cmd->error = -ENOMEDIUM;
+ tasklet_schedule(&host->finish_tasklet);
+ }
+
+ spin_unlock_irqrestore(&host->lock, flags);
+ }
+
+ sdhci_enable_clk(host);
+ sdhci_disable_card_detection(host);
+
+ mmc_remove_host(host->mmc);
+
+#ifdef CONFIG_SDHCI_USE_LEDS_CLASS
+ led_classdev_unregister(&host->led);
+#endif
+
+ sdhci_enable_clk(host);
+ if (!dead)
+ sdhci_reset(host, SDHCI_RESET_ALL);
+ sdhci_disable_clk(host, 0);
+
+ free_irq(host->irq, host);
+
+ del_timer_sync(&host->timer);
+ if (host->version >= SDHCI_SPEC_300)
+ del_timer_sync(&host->tuning_timer);
+
+ tasklet_kill(&host->card_tasklet);
+ tasklet_kill(&host->finish_tasklet);
+
+ if (host->vmmc) {
+ regulator_disable(host->vmmc);
+ regulator_put(host->vmmc);
+ }
+
+ kfree(host->adma_desc);
+ kfree(host->align_buffer);
+
+ host->adma_desc = NULL;
+ host->align_buffer = NULL;
+}
+
+EXPORT_SYMBOL_GPL(sdhci_remove_host);
+
+void sdhci_free_host(struct sdhci_host *host)
+{
+ mmc_free_host(host->mmc);
+}
+
+EXPORT_SYMBOL_GPL(sdhci_free_host);
+
+/*****************************************************************************\
+ * *
+ * Driver init/exit *
+ * *
+\*****************************************************************************/
+
+static int __init sdhci_drv_init(void)
+{
+ printk(KERN_INFO DRIVER_NAME
+ ": Secure Digital Host Controller Interface driver\n");
+ printk(KERN_INFO DRIVER_NAME ": Copyright(c) Pierre Ossman\n");
+
+ return 0;
+}
+
+static void __exit sdhci_drv_exit(void)
+{
+}
+
+module_init(sdhci_drv_init);
+module_exit(sdhci_drv_exit);
+
+module_param(debug_quirks, uint, 0444);
+
+MODULE_AUTHOR("Pierre Ossman <pierre@ossman.eu>");
+MODULE_DESCRIPTION("Secure Digital Host Controller Interface core driver");
+MODULE_LICENSE("GPL");
+
+MODULE_PARM_DESC(debug_quirks, "Force certain quirks.");
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
new file mode 100644
index 00000000..ef45fa98
--- /dev/null
+++ b/drivers/mmc/host/sdhci.h
@@ -0,0 +1,386 @@
+/*
+ * linux/drivers/mmc/host/sdhci.h - Secure Digital Host Controller Interface driver
+ *
+ * Header file for Host Controller registers and I/O accessors.
+ *
+ * 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.
+ */
+#ifndef __SDHCI_HW_H
+#define __SDHCI_HW_H
+
+#include <linux/scatterlist.h>
+#include <linux/compiler.h>
+#include <linux/types.h>
+#include <linux/io.h>
+
+#include <linux/mmc/sdhci.h>
+
+/*
+ * Controller registers
+ */
+
+#define SDHCI_DMA_ADDRESS 0x00
+#define SDHCI_ARGUMENT2 SDHCI_DMA_ADDRESS
+
+#define SDHCI_BLOCK_SIZE 0x04
+#define SDHCI_MAKE_BLKSZ(dma, blksz) (((dma & 0x7) << 12) | (blksz & 0xFFF))
+
+#define SDHCI_BLOCK_COUNT 0x06
+
+#define SDHCI_ARGUMENT 0x08
+
+#define SDHCI_TRANSFER_MODE 0x0C
+#define SDHCI_TRNS_DMA 0x01
+#define SDHCI_TRNS_BLK_CNT_EN 0x02
+#define SDHCI_TRNS_AUTO_CMD12 0x04
+#define SDHCI_TRNS_AUTO_CMD23 0x08
+#define SDHCI_TRNS_READ 0x10
+#define SDHCI_TRNS_MULTI 0x20
+
+#define SDHCI_COMMAND 0x0E
+#define SDHCI_CMD_RESP_MASK 0x03
+#define SDHCI_CMD_CRC 0x08
+#define SDHCI_CMD_INDEX 0x10
+#define SDHCI_CMD_DATA 0x20
+#define SDHCI_CMD_ABORTCMD 0xC0
+
+#define SDHCI_CMD_RESP_NONE 0x00
+#define SDHCI_CMD_RESP_LONG 0x01
+#define SDHCI_CMD_RESP_SHORT 0x02
+#define SDHCI_CMD_RESP_SHORT_BUSY 0x03
+
+#define SDHCI_MAKE_CMD(c, f) (((c & 0xff) << 8) | (f & 0xff))
+#define SDHCI_GET_CMD(c) ((c>>8) & 0x3f)
+
+#define SDHCI_RESPONSE 0x10
+
+#define SDHCI_BUFFER 0x20
+
+#define SDHCI_PRESENT_STATE 0x24
+#define SDHCI_CMD_INHIBIT 0x00000001
+#define SDHCI_DATA_INHIBIT 0x00000002
+#define SDHCI_DOING_WRITE 0x00000100
+#define SDHCI_DOING_READ 0x00000200
+#define SDHCI_SPACE_AVAILABLE 0x00000400
+#define SDHCI_DATA_AVAILABLE 0x00000800
+#define SDHCI_CARD_PRESENT 0x00010000
+#define SDHCI_WRITE_PROTECT 0x00080000
+#define SDHCI_DATA_LVL_MASK 0x00F00000
+#define SDHCI_DATA_LVL_SHIFT 20
+
+#define SDHCI_HOST_CONTROL 0x28
+#define SDHCI_CTRL_LED 0x01
+#define SDHCI_CTRL_4BITBUS 0x02
+#define SDHCI_CTRL_HISPD 0x04
+#define SDHCI_CTRL_DMA_MASK 0x18
+#define SDHCI_CTRL_SDMA 0x00
+#define SDHCI_CTRL_ADMA1 0x08
+#define SDHCI_CTRL_ADMA32 0x10
+#define SDHCI_CTRL_ADMA64 0x18
+#define SDHCI_CTRL_8BITBUS 0x20
+
+#define SDHCI_POWER_CONTROL 0x29
+#define SDHCI_POWER_ON 0x01
+#define SDHCI_POWER_180 0x0A
+#define SDHCI_POWER_300 0x0C
+#define SDHCI_POWER_330 0x0E
+
+#define SDHCI_BLOCK_GAP_CONTROL 0x2A
+
+#define SDHCI_WAKE_UP_CONTROL 0x2B
+#define SDHCI_WAKE_ON_INT 0x01
+#define SDHCI_WAKE_ON_INSERT 0x02
+#define SDHCI_WAKE_ON_REMOVE 0x04
+
+#define SDHCI_CLOCK_CONTROL 0x2C
+#define SDHCI_DIVIDER_SHIFT 8
+#define SDHCI_DIVIDER_HI_SHIFT 6
+#define SDHCI_DIV_MASK 0xFF
+#define SDHCI_DIV_MASK_LEN 8
+#define SDHCI_DIV_HI_MASK 0x300
+#define SDHCI_PROG_CLOCK_MODE 0x0020
+#define SDHCI_CLOCK_CARD_EN 0x0004
+#define SDHCI_CLOCK_INT_STABLE 0x0002
+#define SDHCI_CLOCK_INT_EN 0x0001
+
+#define SDHCI_TIMEOUT_CONTROL 0x2E
+
+#define SDHCI_SOFTWARE_RESET 0x2F
+#define SDHCI_RESET_ALL 0x01
+#define SDHCI_RESET_CMD 0x02
+#define SDHCI_RESET_DATA 0x04
+
+#define SDHCI_INT_STATUS 0x30
+#define SDHCI_INT_ENABLE 0x34
+#define SDHCI_SIGNAL_ENABLE 0x38
+#define SDHCI_INT_RESPONSE 0x00000001
+#define SDHCI_INT_DATA_END 0x00000002
+#define SDHCI_INT_DMA_END 0x00000008
+#define SDHCI_INT_SPACE_AVAIL 0x00000010
+#define SDHCI_INT_DATA_AVAIL 0x00000020
+#define SDHCI_INT_CARD_INSERT 0x00000040
+#define SDHCI_INT_CARD_REMOVE 0x00000080
+#define SDHCI_INT_CARD_INT 0x00000100
+#define SDHCI_INT_ERROR 0x00008000
+#define SDHCI_INT_TIMEOUT 0x00010000
+#define SDHCI_INT_CRC 0x00020000
+#define SDHCI_INT_END_BIT 0x00040000
+#define SDHCI_INT_INDEX 0x00080000
+#define SDHCI_INT_DATA_TIMEOUT 0x00100000
+#define SDHCI_INT_DATA_CRC 0x00200000
+#define SDHCI_INT_DATA_END_BIT 0x00400000
+#define SDHCI_INT_BUS_POWER 0x00800000
+#define SDHCI_INT_ACMD12ERR 0x01000000
+#define SDHCI_INT_ADMA_ERROR 0x02000000
+
+#define SDHCI_INT_NORMAL_MASK 0x00007FFF
+#define SDHCI_INT_ERROR_MASK 0xFFFF8000
+
+#define SDHCI_INT_CMD_MASK (SDHCI_INT_RESPONSE | SDHCI_INT_TIMEOUT | \
+ SDHCI_INT_CRC | SDHCI_INT_END_BIT | SDHCI_INT_INDEX)
+#define SDHCI_INT_DATA_MASK (SDHCI_INT_DATA_END | SDHCI_INT_DMA_END | \
+ SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL | \
+ SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_DATA_CRC | \
+ SDHCI_INT_DATA_END_BIT | SDHCI_INT_ADMA_ERROR)
+#define SDHCI_INT_ALL_MASK ((unsigned int)-1)
+
+#define SDHCI_ACMD12_ERR 0x3C
+
+#define SDHCI_HOST_CONTROL2 0x3E
+#define SDHCI_CTRL_UHS_MASK 0x0007
+#define SDHCI_CTRL_UHS_SDR12 0x0000
+#define SDHCI_CTRL_UHS_SDR25 0x0001
+#define SDHCI_CTRL_UHS_SDR50 0x0002
+#define SDHCI_CTRL_UHS_SDR104 0x0003
+#define SDHCI_CTRL_UHS_DDR50 0x0004
+#define SDHCI_CTRL_VDD_180 0x0008
+#define SDHCI_CTRL_DRV_TYPE_MASK 0x0030
+#define SDHCI_CTRL_DRV_TYPE_B 0x0000
+#define SDHCI_CTRL_DRV_TYPE_A 0x0010
+#define SDHCI_CTRL_DRV_TYPE_C 0x0020
+#define SDHCI_CTRL_DRV_TYPE_D 0x0030
+#define SDHCI_CTRL_EXEC_TUNING 0x0040
+#define SDHCI_CTRL_TUNED_CLK 0x0080
+#define SDHCI_CTRL_PRESET_VAL_ENABLE 0x8000
+
+#define SDHCI_CAPABILITIES 0x40
+#define SDHCI_TIMEOUT_CLK_MASK 0x0000003F
+#define SDHCI_TIMEOUT_CLK_SHIFT 0
+#define SDHCI_TIMEOUT_CLK_UNIT 0x00000080
+#define SDHCI_CLOCK_BASE_MASK 0x00003F00
+#define SDHCI_CLOCK_V3_BASE_MASK 0x0000FF00
+#define SDHCI_CLOCK_BASE_SHIFT 8
+#define SDHCI_MAX_BLOCK_MASK 0x00030000
+#define SDHCI_MAX_BLOCK_SHIFT 16
+#define SDHCI_CAN_DO_8BIT 0x00040000
+#define SDHCI_CAN_DO_ADMA2 0x00080000
+#define SDHCI_CAN_DO_ADMA1 0x00100000
+#define SDHCI_CAN_DO_HISPD 0x00200000
+#define SDHCI_CAN_DO_SDMA 0x00400000
+#define SDHCI_CAN_VDD_330 0x01000000
+#define SDHCI_CAN_VDD_300 0x02000000
+#define SDHCI_CAN_VDD_180 0x04000000
+#define SDHCI_CAN_64BIT 0x10000000
+
+#define SDHCI_SUPPORT_SDR50 0x00000001
+#define SDHCI_SUPPORT_SDR104 0x00000002
+#define SDHCI_SUPPORT_DDR50 0x00000004
+#define SDHCI_DRIVER_TYPE_A 0x00000010
+#define SDHCI_DRIVER_TYPE_C 0x00000020
+#define SDHCI_DRIVER_TYPE_D 0x00000040
+#define SDHCI_RETUNING_TIMER_COUNT_MASK 0x00000F00
+#define SDHCI_RETUNING_TIMER_COUNT_SHIFT 8
+#define SDHCI_USE_SDR50_TUNING 0x00002000
+#define SDHCI_RETUNING_MODE_MASK 0x0000C000
+#define SDHCI_RETUNING_MODE_SHIFT 14
+#define SDHCI_CLOCK_MUL_MASK 0x00FF0000
+#define SDHCI_CLOCK_MUL_SHIFT 16
+
+#define SDHCI_CAPABILITIES_1 0x44
+
+#define SDHCI_MAX_CURRENT 0x48
+#define SDHCI_MAX_CURRENT_330_MASK 0x0000FF
+#define SDHCI_MAX_CURRENT_330_SHIFT 0
+#define SDHCI_MAX_CURRENT_300_MASK 0x00FF00
+#define SDHCI_MAX_CURRENT_300_SHIFT 8
+#define SDHCI_MAX_CURRENT_180_MASK 0xFF0000
+#define SDHCI_MAX_CURRENT_180_SHIFT 16
+#define SDHCI_MAX_CURRENT_MULTIPLIER 4
+
+/* 4C-4F reserved for more max current */
+
+#define SDHCI_SET_ACMD12_ERROR 0x50
+#define SDHCI_SET_INT_ERROR 0x52
+
+#define SDHCI_ADMA_ERROR 0x54
+
+/* 55-57 reserved */
+
+#define SDHCI_ADMA_ADDRESS 0x58
+
+/* 60-FB reserved */
+
+#define SDHCI_SLOT_INT_STATUS 0xFC
+
+#define SDHCI_HOST_VERSION 0xFE
+#define SDHCI_VENDOR_VER_MASK 0xFF00
+#define SDHCI_VENDOR_VER_SHIFT 8
+#define SDHCI_SPEC_VER_MASK 0x00FF
+#define SDHCI_SPEC_VER_SHIFT 0
+#define SDHCI_SPEC_100 0
+#define SDHCI_SPEC_200 1
+#define SDHCI_SPEC_300 2
+
+/*
+ * End of controller registers.
+ */
+
+#define SDHCI_MAX_DIV_SPEC_200 256
+#define SDHCI_MAX_DIV_SPEC_300 2046
+
+/*
+ * Host SDMA buffer boundary. Valid values from 4K to 512K in powers of 2.
+ */
+#define SDHCI_DEFAULT_BOUNDARY_SIZE (512 * 1024)
+#define SDHCI_DEFAULT_BOUNDARY_ARG (ilog2(SDHCI_DEFAULT_BOUNDARY_SIZE) - 12)
+
+struct sdhci_ops {
+#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
+ u32 (*read_l)(struct sdhci_host *host, int reg);
+ u16 (*read_w)(struct sdhci_host *host, int reg);
+ u8 (*read_b)(struct sdhci_host *host, int reg);
+ void (*write_l)(struct sdhci_host *host, u32 val, int reg);
+ void (*write_w)(struct sdhci_host *host, u16 val, int reg);
+ void (*write_b)(struct sdhci_host *host, u8 val, int reg);
+#endif
+
+ void (*set_clock)(struct sdhci_host *host, unsigned int clock);
+
+ int (*enable_dma)(struct sdhci_host *host);
+ unsigned int (*get_max_clock)(struct sdhci_host *host);
+ unsigned int (*get_min_clock)(struct sdhci_host *host);
+ unsigned int (*get_timeout_clock)(struct sdhci_host *host);
+ int (*platform_8bit_width)(struct sdhci_host *host,
+ int width);
+ void (*platform_send_init_74_clocks)(struct sdhci_host *host,
+ u8 power_mode);
+ unsigned int (*get_ro)(struct sdhci_host *host);
+ void (*platform_reset_enter)(struct sdhci_host *host, u8 mask);
+ void (*platform_reset_exit)(struct sdhci_host *host, u8 mask);
+ int (*set_uhs_signaling)(struct sdhci_host *host, unsigned int uhs);
+
+ void (*platform_clk_ctrl)(struct sdhci_host *host, bool enable);
+ int (*platform_execute_tuning)(struct sdhci_host *host);
+};
+
+#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
+
+static inline void sdhci_writel(struct sdhci_host *host, u32 val, int reg)
+{
+ if (unlikely(host->ops->write_l))
+ host->ops->write_l(host, val, reg);
+ else
+ writel(val, host->ioaddr + reg);
+}
+
+static inline void sdhci_writew(struct sdhci_host *host, u16 val, int reg)
+{
+ if (unlikely(host->ops->write_w))
+ host->ops->write_w(host, val, reg);
+ else
+ writew(val, host->ioaddr + reg);
+}
+
+static inline void sdhci_writeb(struct sdhci_host *host, u8 val, int reg)
+{
+ if (unlikely(host->ops->write_b))
+ host->ops->write_b(host, val, reg);
+ else
+ writeb(val, host->ioaddr + reg);
+}
+
+static inline u32 sdhci_readl(struct sdhci_host *host, int reg)
+{
+ if (unlikely(host->ops->read_l))
+ return host->ops->read_l(host, reg);
+ else
+ return readl(host->ioaddr + reg);
+}
+
+static inline u16 sdhci_readw(struct sdhci_host *host, int reg)
+{
+ if (unlikely(host->ops->read_w))
+ return host->ops->read_w(host, reg);
+ else
+ return readw(host->ioaddr + reg);
+}
+
+static inline u8 sdhci_readb(struct sdhci_host *host, int reg)
+{
+ if (unlikely(host->ops->read_b))
+ return host->ops->read_b(host, reg);
+ else
+ return readb(host->ioaddr + reg);
+}
+
+#else
+
+static inline void sdhci_writel(struct sdhci_host *host, u32 val, int reg)
+{
+ writel(val, host->ioaddr + reg);
+}
+
+static inline void sdhci_writew(struct sdhci_host *host, u16 val, int reg)
+{
+ writew(val, host->ioaddr + reg);
+}
+
+static inline void sdhci_writeb(struct sdhci_host *host, u8 val, int reg)
+{
+ writeb(val, host->ioaddr + reg);
+}
+
+static inline u32 sdhci_readl(struct sdhci_host *host, int reg)
+{
+ return readl(host->ioaddr + reg);
+}
+
+static inline u16 sdhci_readw(struct sdhci_host *host, int reg)
+{
+ return readw(host->ioaddr + reg);
+}
+
+static inline u8 sdhci_readb(struct sdhci_host *host, int reg)
+{
+ return readb(host->ioaddr + reg);
+}
+
+#endif /* CONFIG_MMC_SDHCI_IO_ACCESSORS */
+
+extern struct sdhci_host *sdhci_alloc_host(struct device *dev,
+ size_t priv_size);
+extern void sdhci_free_host(struct sdhci_host *host);
+
+static inline void *sdhci_priv(struct sdhci_host *host)
+{
+ return (void *)host->private;
+}
+
+extern void sdhci_card_detect(struct sdhci_host *host);
+extern int sdhci_add_host(struct sdhci_host *host);
+extern void sdhci_remove_host(struct sdhci_host *host, int dead);
+
+#ifdef CONFIG_PM
+extern int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state);
+extern int sdhci_resume_host(struct sdhci_host *host);
+extern void sdhci_enable_irq_wakeups(struct sdhci_host *host);
+#endif
+
+void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq);
+
+#endif /* __SDHCI_HW_H */
diff --git a/drivers/mmc/host/sdricoh_cs.c b/drivers/mmc/host/sdricoh_cs.c
new file mode 100644
index 00000000..496b7efb
--- /dev/null
+++ b/drivers/mmc/host/sdricoh_cs.c
@@ -0,0 +1,572 @@
+/*
+ * sdricoh_cs.c - driver for Ricoh Secure Digital Card Readers that can be
+ * found on some Ricoh RL5c476 II cardbus bridge
+ *
+ * Copyright (C) 2006 - 2008 Sascha Sommer <saschasommer@freenet.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+/*
+#define DEBUG
+#define VERBOSE_DEBUG
+*/
+#include <linux/delay.h>
+#include <linux/highmem.h>
+#include <linux/pci.h>
+#include <linux/ioport.h>
+#include <linux/scatterlist.h>
+
+#include <pcmcia/cistpl.h>
+#include <pcmcia/ds.h>
+#include <linux/io.h>
+
+#include <linux/mmc/host.h>
+
+#define DRIVER_NAME "sdricoh_cs"
+
+static unsigned int switchlocked;
+
+/* i/o region */
+#define SDRICOH_PCI_REGION 0
+#define SDRICOH_PCI_REGION_SIZE 0x1000
+
+/* registers */
+#define R104_VERSION 0x104
+#define R200_CMD 0x200
+#define R204_CMD_ARG 0x204
+#define R208_DATAIO 0x208
+#define R20C_RESP 0x20c
+#define R21C_STATUS 0x21c
+#define R2E0_INIT 0x2e0
+#define R2E4_STATUS_RESP 0x2e4
+#define R2F0_RESET 0x2f0
+#define R224_MODE 0x224
+#define R226_BLOCKSIZE 0x226
+#define R228_POWER 0x228
+#define R230_DATA 0x230
+
+/* flags for the R21C_STATUS register */
+#define STATUS_CMD_FINISHED 0x00000001
+#define STATUS_TRANSFER_FINISHED 0x00000004
+#define STATUS_CARD_INSERTED 0x00000020
+#define STATUS_CARD_LOCKED 0x00000080
+#define STATUS_CMD_TIMEOUT 0x00400000
+#define STATUS_READY_TO_READ 0x01000000
+#define STATUS_READY_TO_WRITE 0x02000000
+#define STATUS_BUSY 0x40000000
+
+/* timeouts */
+#define INIT_TIMEOUT 100
+#define CMD_TIMEOUT 100000
+#define TRANSFER_TIMEOUT 100000
+#define BUSY_TIMEOUT 32767
+
+/* list of supported pcmcia devices */
+static const struct pcmcia_device_id pcmcia_ids[] = {
+ /* vendor and device strings followed by their crc32 hashes */
+ PCMCIA_DEVICE_PROD_ID12("RICOH", "Bay1Controller", 0xd9f522ed,
+ 0xc3901202),
+ PCMCIA_DEVICE_PROD_ID12("RICOH", "Bay Controller", 0xd9f522ed,
+ 0xace80909),
+ PCMCIA_DEVICE_NULL,
+};
+
+MODULE_DEVICE_TABLE(pcmcia, pcmcia_ids);
+
+/* mmc privdata */
+struct sdricoh_host {
+ struct device *dev;
+ struct mmc_host *mmc; /* MMC structure */
+ unsigned char __iomem *iobase;
+ struct pci_dev *pci_dev;
+ int app_cmd;
+};
+
+/***************** register i/o helper functions *****************************/
+
+static inline unsigned int sdricoh_readl(struct sdricoh_host *host,
+ unsigned int reg)
+{
+ unsigned int value = readl(host->iobase + reg);
+ dev_vdbg(host->dev, "rl %x 0x%x\n", reg, value);
+ return value;
+}
+
+static inline void sdricoh_writel(struct sdricoh_host *host, unsigned int reg,
+ unsigned int value)
+{
+ writel(value, host->iobase + reg);
+ dev_vdbg(host->dev, "wl %x 0x%x\n", reg, value);
+
+}
+
+static inline unsigned int sdricoh_readw(struct sdricoh_host *host,
+ unsigned int reg)
+{
+ unsigned int value = readw(host->iobase + reg);
+ dev_vdbg(host->dev, "rb %x 0x%x\n", reg, value);
+ return value;
+}
+
+static inline void sdricoh_writew(struct sdricoh_host *host, unsigned int reg,
+ unsigned short value)
+{
+ writew(value, host->iobase + reg);
+ dev_vdbg(host->dev, "ww %x 0x%x\n", reg, value);
+}
+
+static inline unsigned int sdricoh_readb(struct sdricoh_host *host,
+ unsigned int reg)
+{
+ unsigned int value = readb(host->iobase + reg);
+ dev_vdbg(host->dev, "rb %x 0x%x\n", reg, value);
+ return value;
+}
+
+static int sdricoh_query_status(struct sdricoh_host *host, unsigned int wanted,
+ unsigned int timeout){
+ unsigned int loop;
+ unsigned int status = 0;
+ struct device *dev = host->dev;
+ for (loop = 0; loop < timeout; loop++) {
+ status = sdricoh_readl(host, R21C_STATUS);
+ sdricoh_writel(host, R2E4_STATUS_RESP, status);
+ if (status & wanted)
+ break;
+ }
+
+ if (loop == timeout) {
+ dev_err(dev, "query_status: timeout waiting for %x\n", wanted);
+ return -ETIMEDOUT;
+ }
+
+ /* do not do this check in the loop as some commands fail otherwise */
+ if (status & 0x7F0000) {
+ dev_err(dev, "waiting for status bit %x failed\n", wanted);
+ return -EINVAL;
+ }
+ return 0;
+
+}
+
+static int sdricoh_mmc_cmd(struct sdricoh_host *host, unsigned char opcode,
+ unsigned int arg)
+{
+ unsigned int status;
+ int result = 0;
+ unsigned int loop = 0;
+ /* reset status reg? */
+ sdricoh_writel(host, R21C_STATUS, 0x18);
+ /* fill parameters */
+ sdricoh_writel(host, R204_CMD_ARG, arg);
+ sdricoh_writel(host, R200_CMD, (0x10000 << 8) | opcode);
+ /* wait for command completion */
+ if (opcode) {
+ for (loop = 0; loop < CMD_TIMEOUT; loop++) {
+ status = sdricoh_readl(host, R21C_STATUS);
+ sdricoh_writel(host, R2E4_STATUS_RESP, status);
+ if (status & STATUS_CMD_FINISHED)
+ break;
+ }
+ /* don't check for timeout in the loop it is not always
+ reset correctly
+ */
+ if (loop == CMD_TIMEOUT || status & STATUS_CMD_TIMEOUT)
+ result = -ETIMEDOUT;
+
+ }
+
+ return result;
+
+}
+
+static int sdricoh_reset(struct sdricoh_host *host)
+{
+ dev_dbg(host->dev, "reset\n");
+ sdricoh_writel(host, R2F0_RESET, 0x10001);
+ sdricoh_writel(host, R2E0_INIT, 0x10000);
+ if (sdricoh_readl(host, R2E0_INIT) != 0x10000)
+ return -EIO;
+ sdricoh_writel(host, R2E0_INIT, 0x10007);
+
+ sdricoh_writel(host, R224_MODE, 0x2000000);
+ sdricoh_writel(host, R228_POWER, 0xe0);
+
+
+ /* status register ? */
+ sdricoh_writel(host, R21C_STATUS, 0x18);
+
+ return 0;
+}
+
+static int sdricoh_blockio(struct sdricoh_host *host, int read,
+ u8 *buf, int len)
+{
+ int size;
+ u32 data = 0;
+ /* wait until the data is available */
+ if (read) {
+ if (sdricoh_query_status(host, STATUS_READY_TO_READ,
+ TRANSFER_TIMEOUT))
+ return -ETIMEDOUT;
+ sdricoh_writel(host, R21C_STATUS, 0x18);
+ /* read data */
+ while (len) {
+ data = sdricoh_readl(host, R230_DATA);
+ size = min(len, 4);
+ len -= size;
+ while (size) {
+ *buf = data & 0xFF;
+ buf++;
+ data >>= 8;
+ size--;
+ }
+ }
+ } else {
+ if (sdricoh_query_status(host, STATUS_READY_TO_WRITE,
+ TRANSFER_TIMEOUT))
+ return -ETIMEDOUT;
+ sdricoh_writel(host, R21C_STATUS, 0x18);
+ /* write data */
+ while (len) {
+ size = min(len, 4);
+ len -= size;
+ while (size) {
+ data >>= 8;
+ data |= (u32)*buf << 24;
+ buf++;
+ size--;
+ }
+ sdricoh_writel(host, R230_DATA, data);
+ }
+ }
+
+ if (len)
+ return -EIO;
+
+ return 0;
+}
+
+static void sdricoh_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct sdricoh_host *host = mmc_priv(mmc);
+ struct mmc_command *cmd = mrq->cmd;
+ struct mmc_data *data = cmd->data;
+ struct device *dev = host->dev;
+ unsigned char opcode = cmd->opcode;
+ int i;
+
+ dev_dbg(dev, "=============================\n");
+ dev_dbg(dev, "sdricoh_request opcode=%i\n", opcode);
+
+ sdricoh_writel(host, R21C_STATUS, 0x18);
+
+ /* MMC_APP_CMDs need some special handling */
+ if (host->app_cmd) {
+ opcode |= 64;
+ host->app_cmd = 0;
+ } else if (opcode == 55)
+ host->app_cmd = 1;
+
+ /* read/write commands seem to require this */
+ if (data) {
+ sdricoh_writew(host, R226_BLOCKSIZE, data->blksz);
+ sdricoh_writel(host, R208_DATAIO, 0);
+ }
+
+ cmd->error = sdricoh_mmc_cmd(host, opcode, cmd->arg);
+
+ /* read response buffer */
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ if (cmd->flags & MMC_RSP_136) {
+ /* CRC is stripped so we need to do some shifting. */
+ for (i = 0; i < 4; i++) {
+ cmd->resp[i] =
+ sdricoh_readl(host,
+ R20C_RESP + (3 - i) * 4) << 8;
+ if (i != 3)
+ cmd->resp[i] |=
+ sdricoh_readb(host, R20C_RESP +
+ (3 - i) * 4 - 1);
+ }
+ } else
+ cmd->resp[0] = sdricoh_readl(host, R20C_RESP);
+ }
+
+ /* transfer data */
+ if (data && cmd->error == 0) {
+ dev_dbg(dev, "transfer: blksz %i blocks %i sg_len %i "
+ "sg length %i\n", data->blksz, data->blocks,
+ data->sg_len, data->sg->length);
+
+ /* enter data reading mode */
+ sdricoh_writel(host, R21C_STATUS, 0x837f031e);
+ for (i = 0; i < data->blocks; i++) {
+ size_t len = data->blksz;
+ u8 *buf;
+ struct page *page;
+ int result;
+ page = sg_page(data->sg);
+
+ buf = kmap(page) + data->sg->offset + (len * i);
+ result =
+ sdricoh_blockio(host,
+ data->flags & MMC_DATA_READ, buf, len);
+ kunmap(page);
+ flush_dcache_page(page);
+ if (result) {
+ dev_err(dev, "sdricoh_request: cmd %i "
+ "block transfer failed\n", cmd->opcode);
+ cmd->error = result;
+ break;
+ } else
+ data->bytes_xfered += len;
+ }
+
+ sdricoh_writel(host, R208_DATAIO, 1);
+
+ if (sdricoh_query_status(host, STATUS_TRANSFER_FINISHED,
+ TRANSFER_TIMEOUT)) {
+ dev_err(dev, "sdricoh_request: transfer end error\n");
+ cmd->error = -EINVAL;
+ }
+ }
+ /* FIXME check busy flag */
+
+ mmc_request_done(mmc, mrq);
+ dev_dbg(dev, "=============================\n");
+}
+
+static void sdricoh_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct sdricoh_host *host = mmc_priv(mmc);
+ dev_dbg(host->dev, "set_ios\n");
+
+ if (ios->power_mode == MMC_POWER_ON) {
+ sdricoh_writel(host, R228_POWER, 0xc0e0);
+
+ if (ios->bus_width == MMC_BUS_WIDTH_4) {
+ sdricoh_writel(host, R224_MODE, 0x2000300);
+ sdricoh_writel(host, R228_POWER, 0x40e0);
+ } else {
+ sdricoh_writel(host, R224_MODE, 0x2000340);
+ }
+
+ } else if (ios->power_mode == MMC_POWER_UP) {
+ sdricoh_writel(host, R224_MODE, 0x2000320);
+ sdricoh_writel(host, R228_POWER, 0xe0);
+ }
+}
+
+static int sdricoh_get_ro(struct mmc_host *mmc)
+{
+ struct sdricoh_host *host = mmc_priv(mmc);
+ unsigned int status;
+
+ status = sdricoh_readl(host, R21C_STATUS);
+ sdricoh_writel(host, R2E4_STATUS_RESP, status);
+
+ /* some notebooks seem to have the locked flag switched */
+ if (switchlocked)
+ return !(status & STATUS_CARD_LOCKED);
+
+ return (status & STATUS_CARD_LOCKED);
+}
+
+static struct mmc_host_ops sdricoh_ops = {
+ .request = sdricoh_request,
+ .set_ios = sdricoh_set_ios,
+ .get_ro = sdricoh_get_ro,
+};
+
+/* initialize the control and register it to the mmc framework */
+static int sdricoh_init_mmc(struct pci_dev *pci_dev,
+ struct pcmcia_device *pcmcia_dev)
+{
+ int result = 0;
+ void __iomem *iobase = NULL;
+ struct mmc_host *mmc = NULL;
+ struct sdricoh_host *host = NULL;
+ struct device *dev = &pcmcia_dev->dev;
+ /* map iomem */
+ if (pci_resource_len(pci_dev, SDRICOH_PCI_REGION) !=
+ SDRICOH_PCI_REGION_SIZE) {
+ dev_dbg(dev, "unexpected pci resource len\n");
+ return -ENODEV;
+ }
+ iobase =
+ pci_iomap(pci_dev, SDRICOH_PCI_REGION, SDRICOH_PCI_REGION_SIZE);
+ if (!iobase) {
+ dev_err(dev, "unable to map iobase\n");
+ return -ENODEV;
+ }
+ /* check version? */
+ if (readl(iobase + R104_VERSION) != 0x4000) {
+ dev_dbg(dev, "no supported mmc controller found\n");
+ result = -ENODEV;
+ goto err;
+ }
+ /* allocate privdata */
+ mmc = pcmcia_dev->priv =
+ mmc_alloc_host(sizeof(struct sdricoh_host), &pcmcia_dev->dev);
+ if (!mmc) {
+ dev_err(dev, "mmc_alloc_host failed\n");
+ result = -ENOMEM;
+ goto err;
+ }
+ host = mmc_priv(mmc);
+
+ host->iobase = iobase;
+ host->dev = dev;
+ host->pci_dev = pci_dev;
+
+ mmc->ops = &sdricoh_ops;
+
+ /* FIXME: frequency and voltage handling is done by the controller
+ */
+ mmc->f_min = 450000;
+ mmc->f_max = 24000000;
+ mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+ mmc->caps |= MMC_CAP_4_BIT_DATA;
+
+ mmc->max_seg_size = 1024 * 512;
+ mmc->max_blk_size = 512;
+
+ /* reset the controller */
+ if (sdricoh_reset(host)) {
+ dev_dbg(dev, "could not reset\n");
+ result = -EIO;
+ goto err;
+
+ }
+
+ result = mmc_add_host(mmc);
+
+ if (!result) {
+ dev_dbg(dev, "mmc host registered\n");
+ return 0;
+ }
+
+err:
+ if (iobase)
+ pci_iounmap(pci_dev, iobase);
+ if (mmc)
+ mmc_free_host(mmc);
+
+ return result;
+}
+
+/* search for supported mmc controllers */
+static int sdricoh_pcmcia_probe(struct pcmcia_device *pcmcia_dev)
+{
+ struct pci_dev *pci_dev = NULL;
+
+ dev_info(&pcmcia_dev->dev, "Searching MMC controller for pcmcia device"
+ " %s %s ...\n", pcmcia_dev->prod_id[0], pcmcia_dev->prod_id[1]);
+
+ /* search pci cardbus bridge that contains the mmc controller */
+ /* the io region is already claimed by yenta_socket... */
+ while ((pci_dev =
+ pci_get_device(PCI_VENDOR_ID_RICOH, PCI_DEVICE_ID_RICOH_RL5C476,
+ pci_dev))) {
+ /* try to init the device */
+ if (!sdricoh_init_mmc(pci_dev, pcmcia_dev)) {
+ dev_info(&pcmcia_dev->dev, "MMC controller found\n");
+ return 0;
+ }
+
+ }
+ dev_err(&pcmcia_dev->dev, "No MMC controller was found.\n");
+ return -ENODEV;
+}
+
+static void sdricoh_pcmcia_detach(struct pcmcia_device *link)
+{
+ struct mmc_host *mmc = link->priv;
+
+ dev_dbg(&link->dev, "detach\n");
+
+ /* remove mmc host */
+ if (mmc) {
+ struct sdricoh_host *host = mmc_priv(mmc);
+ mmc_remove_host(mmc);
+ pci_iounmap(host->pci_dev, host->iobase);
+ pci_dev_put(host->pci_dev);
+ mmc_free_host(mmc);
+ }
+ pcmcia_disable_device(link);
+
+}
+
+#ifdef CONFIG_PM
+static int sdricoh_pcmcia_suspend(struct pcmcia_device *link)
+{
+ struct mmc_host *mmc = link->priv;
+ dev_dbg(&link->dev, "suspend\n");
+ mmc_suspend_host(mmc);
+ return 0;
+}
+
+static int sdricoh_pcmcia_resume(struct pcmcia_device *link)
+{
+ struct mmc_host *mmc = link->priv;
+ dev_dbg(&link->dev, "resume\n");
+ sdricoh_reset(mmc_priv(mmc));
+ mmc_resume_host(mmc);
+ return 0;
+}
+#else
+#define sdricoh_pcmcia_suspend NULL
+#define sdricoh_pcmcia_resume NULL
+#endif
+
+static struct pcmcia_driver sdricoh_driver = {
+ .name = DRIVER_NAME,
+ .probe = sdricoh_pcmcia_probe,
+ .remove = sdricoh_pcmcia_detach,
+ .id_table = pcmcia_ids,
+ .suspend = sdricoh_pcmcia_suspend,
+ .resume = sdricoh_pcmcia_resume,
+};
+
+/*****************************************************************************\
+ * *
+ * Driver init/exit *
+ * *
+\*****************************************************************************/
+
+static int __init sdricoh_drv_init(void)
+{
+ return pcmcia_register_driver(&sdricoh_driver);
+}
+
+static void __exit sdricoh_drv_exit(void)
+{
+ pcmcia_unregister_driver(&sdricoh_driver);
+}
+
+module_init(sdricoh_drv_init);
+module_exit(sdricoh_drv_exit);
+
+module_param(switchlocked, uint, 0444);
+
+MODULE_AUTHOR("Sascha Sommer <saschasommer@freenet.de>");
+MODULE_DESCRIPTION("Ricoh PCMCIA Secure Digital Interface driver");
+MODULE_LICENSE("GPL");
+
+MODULE_PARM_DESC(switchlocked, "Switch the cards locked status."
+ "Use this when unlocked cards are shown readonly (default 0)");
diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c
new file mode 100644
index 00000000..14f8edba
--- /dev/null
+++ b/drivers/mmc/host/sh_mmcif.c
@@ -0,0 +1,1204 @@
+/*
+ * MMCIF eMMC driver.
+ *
+ * Copyright (C) 2010 Renesas Solutions Corp.
+ * Yusuke Goda <yusuke.goda.sx@renesas.com>
+ *
+ * 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.
+ *
+ *
+ * TODO
+ * 1. DMA
+ * 2. Power management
+ * 3. Handle MMC errors better
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/core.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/sh_mmcif.h>
+#include <linux/pagemap.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/spinlock.h>
+
+#define DRIVER_NAME "sh_mmcif"
+#define DRIVER_VERSION "2010-04-28"
+
+/* CE_CMD_SET */
+#define CMD_MASK 0x3f000000
+#define CMD_SET_RTYP_NO ((0 << 23) | (0 << 22))
+#define CMD_SET_RTYP_6B ((0 << 23) | (1 << 22)) /* R1/R1b/R3/R4/R5 */
+#define CMD_SET_RTYP_17B ((1 << 23) | (0 << 22)) /* R2 */
+#define CMD_SET_RBSY (1 << 21) /* R1b */
+#define CMD_SET_CCSEN (1 << 20)
+#define CMD_SET_WDAT (1 << 19) /* 1: on data, 0: no data */
+#define CMD_SET_DWEN (1 << 18) /* 1: write, 0: read */
+#define CMD_SET_CMLTE (1 << 17) /* 1: multi block trans, 0: single */
+#define CMD_SET_CMD12EN (1 << 16) /* 1: CMD12 auto issue */
+#define CMD_SET_RIDXC_INDEX ((0 << 15) | (0 << 14)) /* index check */
+#define CMD_SET_RIDXC_BITS ((0 << 15) | (1 << 14)) /* check bits check */
+#define CMD_SET_RIDXC_NO ((1 << 15) | (0 << 14)) /* no check */
+#define CMD_SET_CRC7C ((0 << 13) | (0 << 12)) /* CRC7 check*/
+#define CMD_SET_CRC7C_BITS ((0 << 13) | (1 << 12)) /* check bits check*/
+#define CMD_SET_CRC7C_INTERNAL ((1 << 13) | (0 << 12)) /* internal CRC7 check*/
+#define CMD_SET_CRC16C (1 << 10) /* 0: CRC16 check*/
+#define CMD_SET_CRCSTE (1 << 8) /* 1: not receive CRC status */
+#define CMD_SET_TBIT (1 << 7) /* 1: tran mission bit "Low" */
+#define CMD_SET_OPDM (1 << 6) /* 1: open/drain */
+#define CMD_SET_CCSH (1 << 5)
+#define CMD_SET_DATW_1 ((0 << 1) | (0 << 0)) /* 1bit */
+#define CMD_SET_DATW_4 ((0 << 1) | (1 << 0)) /* 4bit */
+#define CMD_SET_DATW_8 ((1 << 1) | (0 << 0)) /* 8bit */
+
+/* CE_CMD_CTRL */
+#define CMD_CTRL_BREAK (1 << 0)
+
+/* CE_BLOCK_SET */
+#define BLOCK_SIZE_MASK 0x0000ffff
+
+/* CE_INT */
+#define INT_CCSDE (1 << 29)
+#define INT_CMD12DRE (1 << 26)
+#define INT_CMD12RBE (1 << 25)
+#define INT_CMD12CRE (1 << 24)
+#define INT_DTRANE (1 << 23)
+#define INT_BUFRE (1 << 22)
+#define INT_BUFWEN (1 << 21)
+#define INT_BUFREN (1 << 20)
+#define INT_CCSRCV (1 << 19)
+#define INT_RBSYE (1 << 17)
+#define INT_CRSPE (1 << 16)
+#define INT_CMDVIO (1 << 15)
+#define INT_BUFVIO (1 << 14)
+#define INT_WDATERR (1 << 11)
+#define INT_RDATERR (1 << 10)
+#define INT_RIDXERR (1 << 9)
+#define INT_RSPERR (1 << 8)
+#define INT_CCSTO (1 << 5)
+#define INT_CRCSTO (1 << 4)
+#define INT_WDATTO (1 << 3)
+#define INT_RDATTO (1 << 2)
+#define INT_RBSYTO (1 << 1)
+#define INT_RSPTO (1 << 0)
+#define INT_ERR_STS (INT_CMDVIO | INT_BUFVIO | INT_WDATERR | \
+ INT_RDATERR | INT_RIDXERR | INT_RSPERR | \
+ INT_CCSTO | INT_CRCSTO | INT_WDATTO | \
+ INT_RDATTO | INT_RBSYTO | INT_RSPTO)
+
+/* CE_INT_MASK */
+#define MASK_ALL 0x00000000
+#define MASK_MCCSDE (1 << 29)
+#define MASK_MCMD12DRE (1 << 26)
+#define MASK_MCMD12RBE (1 << 25)
+#define MASK_MCMD12CRE (1 << 24)
+#define MASK_MDTRANE (1 << 23)
+#define MASK_MBUFRE (1 << 22)
+#define MASK_MBUFWEN (1 << 21)
+#define MASK_MBUFREN (1 << 20)
+#define MASK_MCCSRCV (1 << 19)
+#define MASK_MRBSYE (1 << 17)
+#define MASK_MCRSPE (1 << 16)
+#define MASK_MCMDVIO (1 << 15)
+#define MASK_MBUFVIO (1 << 14)
+#define MASK_MWDATERR (1 << 11)
+#define MASK_MRDATERR (1 << 10)
+#define MASK_MRIDXERR (1 << 9)
+#define MASK_MRSPERR (1 << 8)
+#define MASK_MCCSTO (1 << 5)
+#define MASK_MCRCSTO (1 << 4)
+#define MASK_MWDATTO (1 << 3)
+#define MASK_MRDATTO (1 << 2)
+#define MASK_MRBSYTO (1 << 1)
+#define MASK_MRSPTO (1 << 0)
+
+/* CE_HOST_STS1 */
+#define STS1_CMDSEQ (1 << 31)
+
+/* CE_HOST_STS2 */
+#define STS2_CRCSTE (1 << 31)
+#define STS2_CRC16E (1 << 30)
+#define STS2_AC12CRCE (1 << 29)
+#define STS2_RSPCRC7E (1 << 28)
+#define STS2_CRCSTEBE (1 << 27)
+#define STS2_RDATEBE (1 << 26)
+#define STS2_AC12REBE (1 << 25)
+#define STS2_RSPEBE (1 << 24)
+#define STS2_AC12IDXE (1 << 23)
+#define STS2_RSPIDXE (1 << 22)
+#define STS2_CCSTO (1 << 15)
+#define STS2_RDATTO (1 << 14)
+#define STS2_DATBSYTO (1 << 13)
+#define STS2_CRCSTTO (1 << 12)
+#define STS2_AC12BSYTO (1 << 11)
+#define STS2_RSPBSYTO (1 << 10)
+#define STS2_AC12RSPTO (1 << 9)
+#define STS2_RSPTO (1 << 8)
+#define STS2_CRC_ERR (STS2_CRCSTE | STS2_CRC16E | \
+ STS2_AC12CRCE | STS2_RSPCRC7E | STS2_CRCSTEBE)
+#define STS2_TIMEOUT_ERR (STS2_CCSTO | STS2_RDATTO | \
+ STS2_DATBSYTO | STS2_CRCSTTO | \
+ STS2_AC12BSYTO | STS2_RSPBSYTO | \
+ STS2_AC12RSPTO | STS2_RSPTO)
+
+#define CLKDEV_EMMC_DATA 52000000 /* 52MHz */
+#define CLKDEV_MMC_DATA 20000000 /* 20MHz */
+#define CLKDEV_INIT 400000 /* 400 KHz */
+
+enum mmcif_state {
+ STATE_IDLE,
+ STATE_REQUEST,
+ STATE_IOS,
+};
+
+struct sh_mmcif_host {
+ struct mmc_host *mmc;
+ struct mmc_data *data;
+ struct platform_device *pd;
+ struct clk *hclk;
+ unsigned int clk;
+ int bus_width;
+ bool sd_error;
+ long timeout;
+ void __iomem *addr;
+ struct completion intr_wait;
+ enum mmcif_state state;
+ spinlock_t lock;
+ bool power;
+
+ /* DMA support */
+ struct dma_chan *chan_rx;
+ struct dma_chan *chan_tx;
+ struct completion dma_complete;
+ bool dma_active;
+};
+
+static inline void sh_mmcif_bitset(struct sh_mmcif_host *host,
+ unsigned int reg, u32 val)
+{
+ writel(val | readl(host->addr + reg), host->addr + reg);
+}
+
+static inline void sh_mmcif_bitclr(struct sh_mmcif_host *host,
+ unsigned int reg, u32 val)
+{
+ writel(~val & readl(host->addr + reg), host->addr + reg);
+}
+
+static void mmcif_dma_complete(void *arg)
+{
+ struct sh_mmcif_host *host = arg;
+ dev_dbg(&host->pd->dev, "Command completed\n");
+
+ if (WARN(!host->data, "%s: NULL data in DMA completion!\n",
+ dev_name(&host->pd->dev)))
+ return;
+
+ if (host->data->flags & MMC_DATA_READ)
+ dma_unmap_sg(host->chan_rx->device->dev,
+ host->data->sg, host->data->sg_len,
+ DMA_FROM_DEVICE);
+ else
+ dma_unmap_sg(host->chan_tx->device->dev,
+ host->data->sg, host->data->sg_len,
+ DMA_TO_DEVICE);
+
+ complete(&host->dma_complete);
+}
+
+static void sh_mmcif_start_dma_rx(struct sh_mmcif_host *host)
+{
+ struct scatterlist *sg = host->data->sg;
+ struct dma_async_tx_descriptor *desc = NULL;
+ struct dma_chan *chan = host->chan_rx;
+ dma_cookie_t cookie = -EINVAL;
+ int ret;
+
+ ret = dma_map_sg(chan->device->dev, sg, host->data->sg_len,
+ DMA_FROM_DEVICE);
+ if (ret > 0) {
+ host->dma_active = true;
+ desc = chan->device->device_prep_slave_sg(chan, sg, ret,
+ DMA_FROM_DEVICE, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ }
+
+ if (desc) {
+ desc->callback = mmcif_dma_complete;
+ desc->callback_param = host;
+ cookie = dmaengine_submit(desc);
+ sh_mmcif_bitset(host, MMCIF_CE_BUF_ACC, BUF_ACC_DMAREN);
+ dma_async_issue_pending(chan);
+ }
+ dev_dbg(&host->pd->dev, "%s(): mapped %d -> %d, cookie %d\n",
+ __func__, host->data->sg_len, ret, cookie);
+
+ if (!desc) {
+ /* DMA failed, fall back to PIO */
+ if (ret >= 0)
+ ret = -EIO;
+ host->chan_rx = NULL;
+ host->dma_active = false;
+ dma_release_channel(chan);
+ /* Free the Tx channel too */
+ chan = host->chan_tx;
+ if (chan) {
+ host->chan_tx = NULL;
+ dma_release_channel(chan);
+ }
+ dev_warn(&host->pd->dev,
+ "DMA failed: %d, falling back to PIO\n", ret);
+ sh_mmcif_bitclr(host, MMCIF_CE_BUF_ACC, BUF_ACC_DMAREN | BUF_ACC_DMAWEN);
+ }
+
+ dev_dbg(&host->pd->dev, "%s(): desc %p, cookie %d, sg[%d]\n", __func__,
+ desc, cookie, host->data->sg_len);
+}
+
+static void sh_mmcif_start_dma_tx(struct sh_mmcif_host *host)
+{
+ struct scatterlist *sg = host->data->sg;
+ struct dma_async_tx_descriptor *desc = NULL;
+ struct dma_chan *chan = host->chan_tx;
+ dma_cookie_t cookie = -EINVAL;
+ int ret;
+
+ ret = dma_map_sg(chan->device->dev, sg, host->data->sg_len,
+ DMA_TO_DEVICE);
+ if (ret > 0) {
+ host->dma_active = true;
+ desc = chan->device->device_prep_slave_sg(chan, sg, ret,
+ DMA_TO_DEVICE, DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ }
+
+ if (desc) {
+ desc->callback = mmcif_dma_complete;
+ desc->callback_param = host;
+ cookie = dmaengine_submit(desc);
+ sh_mmcif_bitset(host, MMCIF_CE_BUF_ACC, BUF_ACC_DMAWEN);
+ dma_async_issue_pending(chan);
+ }
+ dev_dbg(&host->pd->dev, "%s(): mapped %d -> %d, cookie %d\n",
+ __func__, host->data->sg_len, ret, cookie);
+
+ if (!desc) {
+ /* DMA failed, fall back to PIO */
+ if (ret >= 0)
+ ret = -EIO;
+ host->chan_tx = NULL;
+ host->dma_active = false;
+ dma_release_channel(chan);
+ /* Free the Rx channel too */
+ chan = host->chan_rx;
+ if (chan) {
+ host->chan_rx = NULL;
+ dma_release_channel(chan);
+ }
+ dev_warn(&host->pd->dev,
+ "DMA failed: %d, falling back to PIO\n", ret);
+ sh_mmcif_bitclr(host, MMCIF_CE_BUF_ACC, BUF_ACC_DMAREN | BUF_ACC_DMAWEN);
+ }
+
+ dev_dbg(&host->pd->dev, "%s(): desc %p, cookie %d\n", __func__,
+ desc, cookie);
+}
+
+static bool sh_mmcif_filter(struct dma_chan *chan, void *arg)
+{
+ dev_dbg(chan->device->dev, "%s: slave data %p\n", __func__, arg);
+ chan->private = arg;
+ return true;
+}
+
+static void sh_mmcif_request_dma(struct sh_mmcif_host *host,
+ struct sh_mmcif_plat_data *pdata)
+{
+ host->dma_active = false;
+
+ /* We can only either use DMA for both Tx and Rx or not use it at all */
+ if (pdata->dma) {
+ dma_cap_mask_t mask;
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+
+ host->chan_tx = dma_request_channel(mask, sh_mmcif_filter,
+ &pdata->dma->chan_priv_tx);
+ dev_dbg(&host->pd->dev, "%s: TX: got channel %p\n", __func__,
+ host->chan_tx);
+
+ if (!host->chan_tx)
+ return;
+
+ host->chan_rx = dma_request_channel(mask, sh_mmcif_filter,
+ &pdata->dma->chan_priv_rx);
+ dev_dbg(&host->pd->dev, "%s: RX: got channel %p\n", __func__,
+ host->chan_rx);
+
+ if (!host->chan_rx) {
+ dma_release_channel(host->chan_tx);
+ host->chan_tx = NULL;
+ return;
+ }
+
+ init_completion(&host->dma_complete);
+ }
+}
+
+static void sh_mmcif_release_dma(struct sh_mmcif_host *host)
+{
+ sh_mmcif_bitclr(host, MMCIF_CE_BUF_ACC, BUF_ACC_DMAREN | BUF_ACC_DMAWEN);
+ /* Descriptors are freed automatically */
+ if (host->chan_tx) {
+ struct dma_chan *chan = host->chan_tx;
+ host->chan_tx = NULL;
+ dma_release_channel(chan);
+ }
+ if (host->chan_rx) {
+ struct dma_chan *chan = host->chan_rx;
+ host->chan_rx = NULL;
+ dma_release_channel(chan);
+ }
+
+ host->dma_active = false;
+}
+
+static void sh_mmcif_clock_control(struct sh_mmcif_host *host, unsigned int clk)
+{
+ struct sh_mmcif_plat_data *p = host->pd->dev.platform_data;
+
+ sh_mmcif_bitclr(host, MMCIF_CE_CLK_CTRL, CLK_ENABLE);
+ sh_mmcif_bitclr(host, MMCIF_CE_CLK_CTRL, CLK_CLEAR);
+
+ if (!clk)
+ return;
+ if (p->sup_pclk && clk == host->clk)
+ sh_mmcif_bitset(host, MMCIF_CE_CLK_CTRL, CLK_SUP_PCLK);
+ else
+ sh_mmcif_bitset(host, MMCIF_CE_CLK_CTRL, CLK_CLEAR &
+ (ilog2(__rounddown_pow_of_two(host->clk / clk)) << 16));
+
+ sh_mmcif_bitset(host, MMCIF_CE_CLK_CTRL, CLK_ENABLE);
+}
+
+static void sh_mmcif_sync_reset(struct sh_mmcif_host *host)
+{
+ u32 tmp;
+
+ tmp = 0x010f0000 & sh_mmcif_readl(host->addr, MMCIF_CE_CLK_CTRL);
+
+ sh_mmcif_writel(host->addr, MMCIF_CE_VERSION, SOFT_RST_ON);
+ sh_mmcif_writel(host->addr, MMCIF_CE_VERSION, SOFT_RST_OFF);
+ sh_mmcif_bitset(host, MMCIF_CE_CLK_CTRL, tmp |
+ SRSPTO_256 | SRBSYTO_29 | SRWDTO_29 | SCCSTO_29);
+ /* byte swap on */
+ sh_mmcif_bitset(host, MMCIF_CE_BUF_ACC, BUF_ACC_ATYP);
+}
+
+static int sh_mmcif_error_manage(struct sh_mmcif_host *host)
+{
+ u32 state1, state2;
+ int ret, timeout = 10000000;
+
+ host->sd_error = false;
+
+ state1 = sh_mmcif_readl(host->addr, MMCIF_CE_HOST_STS1);
+ state2 = sh_mmcif_readl(host->addr, MMCIF_CE_HOST_STS2);
+ dev_dbg(&host->pd->dev, "ERR HOST_STS1 = %08x\n", state1);
+ dev_dbg(&host->pd->dev, "ERR HOST_STS2 = %08x\n", state2);
+
+ if (state1 & STS1_CMDSEQ) {
+ sh_mmcif_bitset(host, MMCIF_CE_CMD_CTRL, CMD_CTRL_BREAK);
+ sh_mmcif_bitset(host, MMCIF_CE_CMD_CTRL, ~CMD_CTRL_BREAK);
+ while (1) {
+ timeout--;
+ if (timeout < 0) {
+ dev_err(&host->pd->dev,
+ "Forceed end of command sequence timeout err\n");
+ return -EIO;
+ }
+ if (!(sh_mmcif_readl(host->addr, MMCIF_CE_HOST_STS1)
+ & STS1_CMDSEQ))
+ break;
+ mdelay(1);
+ }
+ sh_mmcif_sync_reset(host);
+ dev_dbg(&host->pd->dev, "Forced end of command sequence\n");
+ return -EIO;
+ }
+
+ if (state2 & STS2_CRC_ERR) {
+ dev_dbg(&host->pd->dev, ": Happened CRC error\n");
+ ret = -EIO;
+ } else if (state2 & STS2_TIMEOUT_ERR) {
+ dev_dbg(&host->pd->dev, ": Happened Timeout error\n");
+ ret = -ETIMEDOUT;
+ } else {
+ dev_dbg(&host->pd->dev, ": Happened End/Index error\n");
+ ret = -EIO;
+ }
+ return ret;
+}
+
+static int sh_mmcif_single_read(struct sh_mmcif_host *host,
+ struct mmc_request *mrq)
+{
+ struct mmc_data *data = mrq->data;
+ long time;
+ u32 blocksize, i, *p = sg_virt(data->sg);
+
+ /* buf read enable */
+ sh_mmcif_bitset(host, MMCIF_CE_INT_MASK, MASK_MBUFREN);
+ time = wait_for_completion_interruptible_timeout(&host->intr_wait,
+ host->timeout);
+ if (time <= 0 || host->sd_error)
+ return sh_mmcif_error_manage(host);
+
+ blocksize = (BLOCK_SIZE_MASK &
+ sh_mmcif_readl(host->addr, MMCIF_CE_BLOCK_SET)) + 3;
+ for (i = 0; i < blocksize / 4; i++)
+ *p++ = sh_mmcif_readl(host->addr, MMCIF_CE_DATA);
+
+ /* buffer read end */
+ sh_mmcif_bitset(host, MMCIF_CE_INT_MASK, MASK_MBUFRE);
+ time = wait_for_completion_interruptible_timeout(&host->intr_wait,
+ host->timeout);
+ if (time <= 0 || host->sd_error)
+ return sh_mmcif_error_manage(host);
+
+ return 0;
+}
+
+static int sh_mmcif_multi_read(struct sh_mmcif_host *host,
+ struct mmc_request *mrq)
+{
+ struct mmc_data *data = mrq->data;
+ long time;
+ u32 blocksize, i, j, sec, *p;
+
+ blocksize = BLOCK_SIZE_MASK & sh_mmcif_readl(host->addr,
+ MMCIF_CE_BLOCK_SET);
+ for (j = 0; j < data->sg_len; j++) {
+ p = sg_virt(data->sg);
+ for (sec = 0; sec < data->sg->length / blocksize; sec++) {
+ sh_mmcif_bitset(host, MMCIF_CE_INT_MASK, MASK_MBUFREN);
+ /* buf read enable */
+ time = wait_for_completion_interruptible_timeout(&host->intr_wait,
+ host->timeout);
+
+ if (time <= 0 || host->sd_error)
+ return sh_mmcif_error_manage(host);
+
+ for (i = 0; i < blocksize / 4; i++)
+ *p++ = sh_mmcif_readl(host->addr,
+ MMCIF_CE_DATA);
+ }
+ if (j < data->sg_len - 1)
+ data->sg++;
+ }
+ return 0;
+}
+
+static int sh_mmcif_single_write(struct sh_mmcif_host *host,
+ struct mmc_request *mrq)
+{
+ struct mmc_data *data = mrq->data;
+ long time;
+ u32 blocksize, i, *p = sg_virt(data->sg);
+
+ sh_mmcif_bitset(host, MMCIF_CE_INT_MASK, MASK_MBUFWEN);
+
+ /* buf write enable */
+ time = wait_for_completion_interruptible_timeout(&host->intr_wait,
+ host->timeout);
+ if (time <= 0 || host->sd_error)
+ return sh_mmcif_error_manage(host);
+
+ blocksize = (BLOCK_SIZE_MASK &
+ sh_mmcif_readl(host->addr, MMCIF_CE_BLOCK_SET)) + 3;
+ for (i = 0; i < blocksize / 4; i++)
+ sh_mmcif_writel(host->addr, MMCIF_CE_DATA, *p++);
+
+ /* buffer write end */
+ sh_mmcif_bitset(host, MMCIF_CE_INT_MASK, MASK_MDTRANE);
+
+ time = wait_for_completion_interruptible_timeout(&host->intr_wait,
+ host->timeout);
+ if (time <= 0 || host->sd_error)
+ return sh_mmcif_error_manage(host);
+
+ return 0;
+}
+
+static int sh_mmcif_multi_write(struct sh_mmcif_host *host,
+ struct mmc_request *mrq)
+{
+ struct mmc_data *data = mrq->data;
+ long time;
+ u32 i, sec, j, blocksize, *p;
+
+ blocksize = BLOCK_SIZE_MASK & sh_mmcif_readl(host->addr,
+ MMCIF_CE_BLOCK_SET);
+
+ for (j = 0; j < data->sg_len; j++) {
+ p = sg_virt(data->sg);
+ for (sec = 0; sec < data->sg->length / blocksize; sec++) {
+ sh_mmcif_bitset(host, MMCIF_CE_INT_MASK, MASK_MBUFWEN);
+ /* buf write enable*/
+ time = wait_for_completion_interruptible_timeout(&host->intr_wait,
+ host->timeout);
+
+ if (time <= 0 || host->sd_error)
+ return sh_mmcif_error_manage(host);
+
+ for (i = 0; i < blocksize / 4; i++)
+ sh_mmcif_writel(host->addr,
+ MMCIF_CE_DATA, *p++);
+ }
+ if (j < data->sg_len - 1)
+ data->sg++;
+ }
+ return 0;
+}
+
+static void sh_mmcif_get_response(struct sh_mmcif_host *host,
+ struct mmc_command *cmd)
+{
+ if (cmd->flags & MMC_RSP_136) {
+ cmd->resp[0] = sh_mmcif_readl(host->addr, MMCIF_CE_RESP3);
+ cmd->resp[1] = sh_mmcif_readl(host->addr, MMCIF_CE_RESP2);
+ cmd->resp[2] = sh_mmcif_readl(host->addr, MMCIF_CE_RESP1);
+ cmd->resp[3] = sh_mmcif_readl(host->addr, MMCIF_CE_RESP0);
+ } else
+ cmd->resp[0] = sh_mmcif_readl(host->addr, MMCIF_CE_RESP0);
+}
+
+static void sh_mmcif_get_cmd12response(struct sh_mmcif_host *host,
+ struct mmc_command *cmd)
+{
+ cmd->resp[0] = sh_mmcif_readl(host->addr, MMCIF_CE_RESP_CMD12);
+}
+
+static u32 sh_mmcif_set_cmd(struct sh_mmcif_host *host,
+ struct mmc_request *mrq, struct mmc_command *cmd, u32 opc)
+{
+ u32 tmp = 0;
+
+ /* Response Type check */
+ switch (mmc_resp_type(cmd)) {
+ case MMC_RSP_NONE:
+ tmp |= CMD_SET_RTYP_NO;
+ break;
+ case MMC_RSP_R1:
+ case MMC_RSP_R1B:
+ case MMC_RSP_R3:
+ tmp |= CMD_SET_RTYP_6B;
+ break;
+ case MMC_RSP_R2:
+ tmp |= CMD_SET_RTYP_17B;
+ break;
+ default:
+ dev_err(&host->pd->dev, "Unsupported response type.\n");
+ break;
+ }
+ switch (opc) {
+ /* RBSY */
+ case MMC_SWITCH:
+ case MMC_STOP_TRANSMISSION:
+ case MMC_SET_WRITE_PROT:
+ case MMC_CLR_WRITE_PROT:
+ case MMC_ERASE:
+ case MMC_GEN_CMD:
+ tmp |= CMD_SET_RBSY;
+ break;
+ }
+ /* WDAT / DATW */
+ if (host->data) {
+ tmp |= CMD_SET_WDAT;
+ switch (host->bus_width) {
+ case MMC_BUS_WIDTH_1:
+ tmp |= CMD_SET_DATW_1;
+ break;
+ case MMC_BUS_WIDTH_4:
+ tmp |= CMD_SET_DATW_4;
+ break;
+ case MMC_BUS_WIDTH_8:
+ tmp |= CMD_SET_DATW_8;
+ break;
+ default:
+ dev_err(&host->pd->dev, "Unsupported bus width.\n");
+ break;
+ }
+ }
+ /* DWEN */
+ if (opc == MMC_WRITE_BLOCK || opc == MMC_WRITE_MULTIPLE_BLOCK)
+ tmp |= CMD_SET_DWEN;
+ /* CMLTE/CMD12EN */
+ if (opc == MMC_READ_MULTIPLE_BLOCK || opc == MMC_WRITE_MULTIPLE_BLOCK) {
+ tmp |= CMD_SET_CMLTE | CMD_SET_CMD12EN;
+ sh_mmcif_bitset(host, MMCIF_CE_BLOCK_SET,
+ mrq->data->blocks << 16);
+ }
+ /* RIDXC[1:0] check bits */
+ if (opc == MMC_SEND_OP_COND || opc == MMC_ALL_SEND_CID ||
+ opc == MMC_SEND_CSD || opc == MMC_SEND_CID)
+ tmp |= CMD_SET_RIDXC_BITS;
+ /* RCRC7C[1:0] check bits */
+ if (opc == MMC_SEND_OP_COND)
+ tmp |= CMD_SET_CRC7C_BITS;
+ /* RCRC7C[1:0] internal CRC7 */
+ if (opc == MMC_ALL_SEND_CID ||
+ opc == MMC_SEND_CSD || opc == MMC_SEND_CID)
+ tmp |= CMD_SET_CRC7C_INTERNAL;
+
+ return opc = ((opc << 24) | tmp);
+}
+
+static int sh_mmcif_data_trans(struct sh_mmcif_host *host,
+ struct mmc_request *mrq, u32 opc)
+{
+ int ret;
+
+ switch (opc) {
+ case MMC_READ_MULTIPLE_BLOCK:
+ ret = sh_mmcif_multi_read(host, mrq);
+ break;
+ case MMC_WRITE_MULTIPLE_BLOCK:
+ ret = sh_mmcif_multi_write(host, mrq);
+ break;
+ case MMC_WRITE_BLOCK:
+ ret = sh_mmcif_single_write(host, mrq);
+ break;
+ case MMC_READ_SINGLE_BLOCK:
+ case MMC_SEND_EXT_CSD:
+ ret = sh_mmcif_single_read(host, mrq);
+ break;
+ default:
+ dev_err(&host->pd->dev, "UNSUPPORTED CMD = d'%08d\n", opc);
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static void sh_mmcif_start_cmd(struct sh_mmcif_host *host,
+ struct mmc_request *mrq, struct mmc_command *cmd)
+{
+ long time;
+ int ret = 0, mask = 0;
+ u32 opc = cmd->opcode;
+
+ switch (opc) {
+ /* respons busy check */
+ case MMC_SWITCH:
+ case MMC_STOP_TRANSMISSION:
+ case MMC_SET_WRITE_PROT:
+ case MMC_CLR_WRITE_PROT:
+ case MMC_ERASE:
+ case MMC_GEN_CMD:
+ mask = MASK_MRBSYE;
+ break;
+ default:
+ mask = MASK_MCRSPE;
+ break;
+ }
+ mask |= MASK_MCMDVIO | MASK_MBUFVIO | MASK_MWDATERR |
+ MASK_MRDATERR | MASK_MRIDXERR | MASK_MRSPERR |
+ MASK_MCCSTO | MASK_MCRCSTO | MASK_MWDATTO |
+ MASK_MRDATTO | MASK_MRBSYTO | MASK_MRSPTO;
+
+ if (host->data) {
+ sh_mmcif_writel(host->addr, MMCIF_CE_BLOCK_SET, 0);
+ sh_mmcif_writel(host->addr, MMCIF_CE_BLOCK_SET,
+ mrq->data->blksz);
+ }
+ opc = sh_mmcif_set_cmd(host, mrq, cmd, opc);
+
+ sh_mmcif_writel(host->addr, MMCIF_CE_INT, 0xD80430C0);
+ sh_mmcif_writel(host->addr, MMCIF_CE_INT_MASK, mask);
+ /* set arg */
+ sh_mmcif_writel(host->addr, MMCIF_CE_ARG, cmd->arg);
+ /* set cmd */
+ sh_mmcif_writel(host->addr, MMCIF_CE_CMD_SET, opc);
+
+ time = wait_for_completion_interruptible_timeout(&host->intr_wait,
+ host->timeout);
+ if (time <= 0) {
+ cmd->error = sh_mmcif_error_manage(host);
+ return;
+ }
+ if (host->sd_error) {
+ switch (cmd->opcode) {
+ case MMC_ALL_SEND_CID:
+ case MMC_SELECT_CARD:
+ case MMC_APP_CMD:
+ cmd->error = -ETIMEDOUT;
+ break;
+ default:
+ dev_dbg(&host->pd->dev, "Cmd(d'%d) err\n",
+ cmd->opcode);
+ cmd->error = sh_mmcif_error_manage(host);
+ break;
+ }
+ host->sd_error = false;
+ return;
+ }
+ if (!(cmd->flags & MMC_RSP_PRESENT)) {
+ cmd->error = 0;
+ return;
+ }
+ sh_mmcif_get_response(host, cmd);
+ if (host->data) {
+ if (!host->dma_active) {
+ ret = sh_mmcif_data_trans(host, mrq, cmd->opcode);
+ } else {
+ long time =
+ wait_for_completion_interruptible_timeout(&host->dma_complete,
+ host->timeout);
+ if (!time)
+ ret = -ETIMEDOUT;
+ else if (time < 0)
+ ret = time;
+ sh_mmcif_bitclr(host, MMCIF_CE_BUF_ACC,
+ BUF_ACC_DMAREN | BUF_ACC_DMAWEN);
+ host->dma_active = false;
+ }
+ if (ret < 0)
+ mrq->data->bytes_xfered = 0;
+ else
+ mrq->data->bytes_xfered =
+ mrq->data->blocks * mrq->data->blksz;
+ }
+ cmd->error = ret;
+}
+
+static void sh_mmcif_stop_cmd(struct sh_mmcif_host *host,
+ struct mmc_request *mrq, struct mmc_command *cmd)
+{
+ long time;
+
+ if (mrq->cmd->opcode == MMC_READ_MULTIPLE_BLOCK)
+ sh_mmcif_bitset(host, MMCIF_CE_INT_MASK, MASK_MCMD12DRE);
+ else if (mrq->cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK)
+ sh_mmcif_bitset(host, MMCIF_CE_INT_MASK, MASK_MCMD12RBE);
+ else {
+ dev_err(&host->pd->dev, "unsupported stop cmd\n");
+ cmd->error = sh_mmcif_error_manage(host);
+ return;
+ }
+
+ time = wait_for_completion_interruptible_timeout(&host->intr_wait,
+ host->timeout);
+ if (time <= 0 || host->sd_error) {
+ cmd->error = sh_mmcif_error_manage(host);
+ return;
+ }
+ sh_mmcif_get_cmd12response(host, cmd);
+ cmd->error = 0;
+}
+
+static void sh_mmcif_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct sh_mmcif_host *host = mmc_priv(mmc);
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+ if (host->state != STATE_IDLE) {
+ spin_unlock_irqrestore(&host->lock, flags);
+ mrq->cmd->error = -EAGAIN;
+ mmc_request_done(mmc, mrq);
+ return;
+ }
+
+ host->state = STATE_REQUEST;
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ switch (mrq->cmd->opcode) {
+ /* MMCIF does not support SD/SDIO command */
+ case SD_IO_SEND_OP_COND:
+ case MMC_APP_CMD:
+ host->state = STATE_IDLE;
+ mrq->cmd->error = -ETIMEDOUT;
+ mmc_request_done(mmc, mrq);
+ return;
+ case MMC_SEND_EXT_CSD: /* = SD_SEND_IF_COND (8) */
+ if (!mrq->data) {
+ /* send_if_cond cmd (not support) */
+ host->state = STATE_IDLE;
+ mrq->cmd->error = -ETIMEDOUT;
+ mmc_request_done(mmc, mrq);
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+ host->data = mrq->data;
+ if (mrq->data) {
+ if (mrq->data->flags & MMC_DATA_READ) {
+ if (host->chan_rx)
+ sh_mmcif_start_dma_rx(host);
+ } else {
+ if (host->chan_tx)
+ sh_mmcif_start_dma_tx(host);
+ }
+ }
+ sh_mmcif_start_cmd(host, mrq, mrq->cmd);
+ host->data = NULL;
+
+ if (!mrq->cmd->error && mrq->stop)
+ sh_mmcif_stop_cmd(host, mrq, mrq->stop);
+ host->state = STATE_IDLE;
+ mmc_request_done(mmc, mrq);
+}
+
+static void sh_mmcif_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct sh_mmcif_host *host = mmc_priv(mmc);
+ struct sh_mmcif_plat_data *p = host->pd->dev.platform_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+ if (host->state != STATE_IDLE) {
+ spin_unlock_irqrestore(&host->lock, flags);
+ return;
+ }
+
+ host->state = STATE_IOS;
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ if (ios->power_mode == MMC_POWER_UP) {
+ if (p->set_pwr)
+ p->set_pwr(host->pd, ios->power_mode);
+ if (!host->power) {
+ /* See if we also get DMA */
+ sh_mmcif_request_dma(host, host->pd->dev.platform_data);
+ pm_runtime_get_sync(&host->pd->dev);
+ host->power = true;
+ }
+ } else if (ios->power_mode == MMC_POWER_OFF || !ios->clock) {
+ /* clock stop */
+ sh_mmcif_clock_control(host, 0);
+ if (ios->power_mode == MMC_POWER_OFF) {
+ if (host->power) {
+ pm_runtime_put(&host->pd->dev);
+ sh_mmcif_release_dma(host);
+ host->power = false;
+ }
+ if (p->down_pwr)
+ p->down_pwr(host->pd);
+ }
+ host->state = STATE_IDLE;
+ return;
+ }
+
+ if (ios->clock)
+ sh_mmcif_clock_control(host, ios->clock);
+
+ host->bus_width = ios->bus_width;
+ host->state = STATE_IDLE;
+}
+
+static int sh_mmcif_get_cd(struct mmc_host *mmc)
+{
+ struct sh_mmcif_host *host = mmc_priv(mmc);
+ struct sh_mmcif_plat_data *p = host->pd->dev.platform_data;
+
+ if (!p->get_cd)
+ return -ENOSYS;
+ else
+ return p->get_cd(host->pd);
+}
+
+static struct mmc_host_ops sh_mmcif_ops = {
+ .request = sh_mmcif_request,
+ .set_ios = sh_mmcif_set_ios,
+ .get_cd = sh_mmcif_get_cd,
+};
+
+static void sh_mmcif_detect(struct mmc_host *mmc)
+{
+ mmc_detect_change(mmc, 0);
+}
+
+static irqreturn_t sh_mmcif_intr(int irq, void *dev_id)
+{
+ struct sh_mmcif_host *host = dev_id;
+ u32 state;
+ int err = 0;
+
+ state = sh_mmcif_readl(host->addr, MMCIF_CE_INT);
+
+ if (state & INT_RBSYE) {
+ sh_mmcif_writel(host->addr, MMCIF_CE_INT,
+ ~(INT_RBSYE | INT_CRSPE));
+ sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, MASK_MRBSYE);
+ } else if (state & INT_CRSPE) {
+ sh_mmcif_writel(host->addr, MMCIF_CE_INT, ~INT_CRSPE);
+ sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, MASK_MCRSPE);
+ } else if (state & INT_BUFREN) {
+ sh_mmcif_writel(host->addr, MMCIF_CE_INT, ~INT_BUFREN);
+ sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, MASK_MBUFREN);
+ } else if (state & INT_BUFWEN) {
+ sh_mmcif_writel(host->addr, MMCIF_CE_INT, ~INT_BUFWEN);
+ sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, MASK_MBUFWEN);
+ } else if (state & INT_CMD12DRE) {
+ sh_mmcif_writel(host->addr, MMCIF_CE_INT,
+ ~(INT_CMD12DRE | INT_CMD12RBE |
+ INT_CMD12CRE | INT_BUFRE));
+ sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, MASK_MCMD12DRE);
+ } else if (state & INT_BUFRE) {
+ sh_mmcif_writel(host->addr, MMCIF_CE_INT, ~INT_BUFRE);
+ sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, MASK_MBUFRE);
+ } else if (state & INT_DTRANE) {
+ sh_mmcif_writel(host->addr, MMCIF_CE_INT, ~INT_DTRANE);
+ sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, MASK_MDTRANE);
+ } else if (state & INT_CMD12RBE) {
+ sh_mmcif_writel(host->addr, MMCIF_CE_INT,
+ ~(INT_CMD12RBE | INT_CMD12CRE));
+ sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, MASK_MCMD12RBE);
+ } else if (state & INT_ERR_STS) {
+ /* err interrupts */
+ sh_mmcif_writel(host->addr, MMCIF_CE_INT, ~state);
+ sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, state);
+ err = 1;
+ } else {
+ dev_dbg(&host->pd->dev, "Unsupported interrupt: 0x%x\n", state);
+ sh_mmcif_writel(host->addr, MMCIF_CE_INT, ~state);
+ sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, state);
+ err = 1;
+ }
+ if (err) {
+ host->sd_error = true;
+ dev_dbg(&host->pd->dev, "int err state = %08x\n", state);
+ }
+ if (state & ~(INT_CMD12RBE | INT_CMD12CRE))
+ complete(&host->intr_wait);
+ else
+ dev_dbg(&host->pd->dev, "Unexpected IRQ 0x%x\n", state);
+
+ return IRQ_HANDLED;
+}
+
+static int __devinit sh_mmcif_probe(struct platform_device *pdev)
+{
+ int ret = 0, irq[2];
+ struct mmc_host *mmc;
+ struct sh_mmcif_host *host;
+ struct sh_mmcif_plat_data *pd;
+ struct resource *res;
+ void __iomem *reg;
+ char clk_name[8];
+
+ irq[0] = platform_get_irq(pdev, 0);
+ irq[1] = platform_get_irq(pdev, 1);
+ if (irq[0] < 0 || irq[1] < 0) {
+ dev_err(&pdev->dev, "Get irq error\n");
+ return -ENXIO;
+ }
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "platform_get_resource error.\n");
+ return -ENXIO;
+ }
+ reg = ioremap(res->start, resource_size(res));
+ if (!reg) {
+ dev_err(&pdev->dev, "ioremap error.\n");
+ return -ENOMEM;
+ }
+ pd = pdev->dev.platform_data;
+ if (!pd) {
+ dev_err(&pdev->dev, "sh_mmcif plat data error.\n");
+ ret = -ENXIO;
+ goto clean_up;
+ }
+ mmc = mmc_alloc_host(sizeof(struct sh_mmcif_host), &pdev->dev);
+ if (!mmc) {
+ ret = -ENOMEM;
+ goto clean_up;
+ }
+ host = mmc_priv(mmc);
+ host->mmc = mmc;
+ host->addr = reg;
+ host->timeout = 1000;
+
+ snprintf(clk_name, sizeof(clk_name), "mmc%d", pdev->id);
+ host->hclk = clk_get(&pdev->dev, clk_name);
+ if (IS_ERR(host->hclk)) {
+ dev_err(&pdev->dev, "cannot get clock \"%s\"\n", clk_name);
+ ret = PTR_ERR(host->hclk);
+ goto clean_up1;
+ }
+ clk_enable(host->hclk);
+ host->clk = clk_get_rate(host->hclk);
+ host->pd = pdev;
+
+ init_completion(&host->intr_wait);
+ spin_lock_init(&host->lock);
+
+ mmc->ops = &sh_mmcif_ops;
+ mmc->f_max = host->clk;
+ /* close to 400KHz */
+ if (mmc->f_max < 51200000)
+ mmc->f_min = mmc->f_max / 128;
+ else if (mmc->f_max < 102400000)
+ mmc->f_min = mmc->f_max / 256;
+ else
+ mmc->f_min = mmc->f_max / 512;
+ if (pd->ocr)
+ mmc->ocr_avail = pd->ocr;
+ mmc->caps = MMC_CAP_MMC_HIGHSPEED;
+ if (pd->caps)
+ mmc->caps |= pd->caps;
+ mmc->max_segs = 32;
+ mmc->max_blk_size = 512;
+ mmc->max_req_size = PAGE_CACHE_SIZE * mmc->max_segs;
+ mmc->max_blk_count = mmc->max_req_size / mmc->max_blk_size;
+ mmc->max_seg_size = mmc->max_req_size;
+
+ sh_mmcif_sync_reset(host);
+ platform_set_drvdata(pdev, host);
+
+ pm_runtime_enable(&pdev->dev);
+ host->power = false;
+
+ ret = pm_runtime_resume(&pdev->dev);
+ if (ret < 0)
+ goto clean_up2;
+
+ mmc_add_host(mmc);
+
+ sh_mmcif_writel(host->addr, MMCIF_CE_INT_MASK, MASK_ALL);
+
+ ret = request_irq(irq[0], sh_mmcif_intr, 0, "sh_mmc:error", host);
+ if (ret) {
+ dev_err(&pdev->dev, "request_irq error (sh_mmc:error)\n");
+ goto clean_up3;
+ }
+ ret = request_irq(irq[1], sh_mmcif_intr, 0, "sh_mmc:int", host);
+ if (ret) {
+ free_irq(irq[0], host);
+ dev_err(&pdev->dev, "request_irq error (sh_mmc:int)\n");
+ goto clean_up3;
+ }
+
+ sh_mmcif_detect(host->mmc);
+
+ dev_info(&pdev->dev, "driver version %s\n", DRIVER_VERSION);
+ dev_dbg(&pdev->dev, "chip ver H'%04x\n",
+ sh_mmcif_readl(host->addr, MMCIF_CE_VERSION) & 0x0000ffff);
+ return ret;
+
+clean_up3:
+ mmc_remove_host(mmc);
+ pm_runtime_suspend(&pdev->dev);
+clean_up2:
+ pm_runtime_disable(&pdev->dev);
+ clk_disable(host->hclk);
+clean_up1:
+ mmc_free_host(mmc);
+clean_up:
+ if (reg)
+ iounmap(reg);
+ return ret;
+}
+
+static int __devexit sh_mmcif_remove(struct platform_device *pdev)
+{
+ struct sh_mmcif_host *host = platform_get_drvdata(pdev);
+ int irq[2];
+
+ pm_runtime_get_sync(&pdev->dev);
+
+ mmc_remove_host(host->mmc);
+ sh_mmcif_writel(host->addr, MMCIF_CE_INT_MASK, MASK_ALL);
+
+ if (host->addr)
+ iounmap(host->addr);
+
+ irq[0] = platform_get_irq(pdev, 0);
+ irq[1] = platform_get_irq(pdev, 1);
+
+ free_irq(irq[0], host);
+ free_irq(irq[1], host);
+
+ platform_set_drvdata(pdev, NULL);
+
+ clk_disable(host->hclk);
+ mmc_free_host(host->mmc);
+ pm_runtime_put_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int sh_mmcif_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct sh_mmcif_host *host = platform_get_drvdata(pdev);
+ int ret = mmc_suspend_host(host->mmc);
+
+ if (!ret) {
+ sh_mmcif_writel(host->addr, MMCIF_CE_INT_MASK, MASK_ALL);
+ clk_disable(host->hclk);
+ }
+
+ return ret;
+}
+
+static int sh_mmcif_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct sh_mmcif_host *host = platform_get_drvdata(pdev);
+
+ clk_enable(host->hclk);
+
+ return mmc_resume_host(host->mmc);
+}
+#else
+#define sh_mmcif_suspend NULL
+#define sh_mmcif_resume NULL
+#endif /* CONFIG_PM */
+
+static const struct dev_pm_ops sh_mmcif_dev_pm_ops = {
+ .suspend = sh_mmcif_suspend,
+ .resume = sh_mmcif_resume,
+};
+
+static struct platform_driver sh_mmcif_driver = {
+ .probe = sh_mmcif_probe,
+ .remove = sh_mmcif_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .pm = &sh_mmcif_dev_pm_ops,
+ },
+};
+
+static int __init sh_mmcif_init(void)
+{
+ return platform_driver_register(&sh_mmcif_driver);
+}
+
+static void __exit sh_mmcif_exit(void)
+{
+ platform_driver_unregister(&sh_mmcif_driver);
+}
+
+module_init(sh_mmcif_init);
+module_exit(sh_mmcif_exit);
+
+
+MODULE_DESCRIPTION("SuperH on-chip MMC/eMMC interface driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRIVER_NAME);
+MODULE_AUTHOR("Yusuke Goda <yusuke.goda.sx@renesas.com>");
diff --git a/drivers/mmc/host/sh_mobile_sdhi.c b/drivers/mmc/host/sh_mobile_sdhi.c
new file mode 100644
index 00000000..ce500f03
--- /dev/null
+++ b/drivers/mmc/host/sh_mobile_sdhi.c
@@ -0,0 +1,216 @@
+/*
+ * SuperH Mobile SDHI
+ *
+ * Copyright (C) 2009 Magnus Damm
+ *
+ * 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.
+ *
+ * Based on "Compaq ASIC3 support":
+ *
+ * Copyright 2001 Compaq Computer Corporation.
+ * Copyright 2004-2005 Phil Blundell
+ * Copyright 2007-2008 OpenedHand Ltd.
+ *
+ * Authors: Phil Blundell <pb@handhelds.org>,
+ * Samuel Ortiz <sameo@openedhand.com>
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sh_mobile_sdhi.h>
+#include <linux/mfd/tmio.h>
+#include <linux/sh_dma.h>
+
+#include "tmio_mmc.h"
+
+struct sh_mobile_sdhi {
+ struct clk *clk;
+ struct tmio_mmc_data mmc_data;
+ struct sh_dmae_slave param_tx;
+ struct sh_dmae_slave param_rx;
+ struct tmio_mmc_dma dma_priv;
+};
+
+static void sh_mobile_sdhi_set_pwr(struct platform_device *pdev, int state)
+{
+ struct sh_mobile_sdhi_info *p = pdev->dev.platform_data;
+
+ if (p && p->set_pwr)
+ p->set_pwr(pdev, state);
+}
+
+static int sh_mobile_sdhi_get_cd(struct platform_device *pdev)
+{
+ struct sh_mobile_sdhi_info *p = pdev->dev.platform_data;
+
+ if (p && p->get_cd)
+ return p->get_cd(pdev);
+ else
+ return -ENOSYS;
+}
+
+static int __devinit sh_mobile_sdhi_probe(struct platform_device *pdev)
+{
+ struct sh_mobile_sdhi *priv;
+ struct tmio_mmc_data *mmc_data;
+ struct sh_mobile_sdhi_info *p = pdev->dev.platform_data;
+ struct tmio_mmc_host *host;
+ char clk_name[8];
+ int i, irq, ret;
+
+ priv = kzalloc(sizeof(struct sh_mobile_sdhi), GFP_KERNEL);
+ if (priv == NULL) {
+ dev_err(&pdev->dev, "kzalloc failed\n");
+ return -ENOMEM;
+ }
+
+ mmc_data = &priv->mmc_data;
+ p->pdata = mmc_data;
+
+ snprintf(clk_name, sizeof(clk_name), "sdhi%d", pdev->id);
+ priv->clk = clk_get(&pdev->dev, clk_name);
+ if (IS_ERR(priv->clk)) {
+ dev_err(&pdev->dev, "cannot get clock \"%s\"\n", clk_name);
+ ret = PTR_ERR(priv->clk);
+ goto eclkget;
+ }
+
+ clk_enable(priv->clk);
+
+ mmc_data->hclk = clk_get_rate(priv->clk);
+ mmc_data->set_pwr = sh_mobile_sdhi_set_pwr;
+ mmc_data->get_cd = sh_mobile_sdhi_get_cd;
+ mmc_data->capabilities = MMC_CAP_MMC_HIGHSPEED;
+ if (p) {
+ mmc_data->flags = p->tmio_flags;
+ mmc_data->ocr_mask = p->tmio_ocr_mask;
+ mmc_data->capabilities |= p->tmio_caps;
+
+ if (p->dma_slave_tx > 0 && p->dma_slave_rx > 0) {
+ priv->param_tx.slave_id = p->dma_slave_tx;
+ priv->param_rx.slave_id = p->dma_slave_rx;
+ priv->dma_priv.chan_priv_tx = &priv->param_tx;
+ priv->dma_priv.chan_priv_rx = &priv->param_rx;
+ priv->dma_priv.alignment_shift = 1; /* 2-byte alignment */
+ mmc_data->dma = &priv->dma_priv;
+ }
+ }
+
+ /*
+ * All SDHI blocks support 2-byte and larger block sizes in 4-bit
+ * bus width mode.
+ */
+ mmc_data->flags |= TMIO_MMC_BLKSZ_2BYTES;
+
+ /*
+ * All SDHI blocks support SDIO IRQ signalling.
+ */
+ mmc_data->flags |= TMIO_MMC_SDIO_IRQ;
+
+ ret = tmio_mmc_host_probe(&host, pdev, mmc_data);
+ if (ret < 0)
+ goto eprobe;
+
+ for (i = 0; i < 3; i++) {
+ irq = platform_get_irq(pdev, i);
+ if (irq < 0) {
+ if (i) {
+ continue;
+ } else {
+ ret = irq;
+ goto eirq;
+ }
+ }
+ ret = request_irq(irq, tmio_mmc_irq, 0,
+ dev_name(&pdev->dev), host);
+ if (ret) {
+ while (i--) {
+ irq = platform_get_irq(pdev, i);
+ if (irq >= 0)
+ free_irq(irq, host);
+ }
+ goto eirq;
+ }
+ }
+ dev_info(&pdev->dev, "%s base at 0x%08lx clock rate %u MHz\n",
+ mmc_hostname(host->mmc), (unsigned long)
+ (platform_get_resource(pdev,IORESOURCE_MEM, 0)->start),
+ mmc_data->hclk / 1000000);
+
+ return ret;
+
+eirq:
+ tmio_mmc_host_remove(host);
+eprobe:
+ clk_disable(priv->clk);
+ clk_put(priv->clk);
+eclkget:
+ kfree(priv);
+ return ret;
+}
+
+static int sh_mobile_sdhi_remove(struct platform_device *pdev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(pdev);
+ struct tmio_mmc_host *host = mmc_priv(mmc);
+ struct sh_mobile_sdhi *priv = container_of(host->pdata, struct sh_mobile_sdhi, mmc_data);
+ struct sh_mobile_sdhi_info *p = pdev->dev.platform_data;
+ int i, irq;
+
+ p->pdata = NULL;
+
+ tmio_mmc_host_remove(host);
+
+ for (i = 0; i < 3; i++) {
+ irq = platform_get_irq(pdev, i);
+ if (irq >= 0)
+ free_irq(irq, host);
+ }
+
+ clk_disable(priv->clk);
+ clk_put(priv->clk);
+ kfree(priv);
+
+ return 0;
+}
+
+static const struct dev_pm_ops tmio_mmc_dev_pm_ops = {
+ .suspend = tmio_mmc_host_suspend,
+ .resume = tmio_mmc_host_resume,
+ .runtime_suspend = tmio_mmc_host_runtime_suspend,
+ .runtime_resume = tmio_mmc_host_runtime_resume,
+};
+
+static struct platform_driver sh_mobile_sdhi_driver = {
+ .driver = {
+ .name = "sh_mobile_sdhi",
+ .owner = THIS_MODULE,
+ .pm = &tmio_mmc_dev_pm_ops,
+ },
+ .probe = sh_mobile_sdhi_probe,
+ .remove = __devexit_p(sh_mobile_sdhi_remove),
+};
+
+static int __init sh_mobile_sdhi_init(void)
+{
+ return platform_driver_register(&sh_mobile_sdhi_driver);
+}
+
+static void __exit sh_mobile_sdhi_exit(void)
+{
+ platform_driver_unregister(&sh_mobile_sdhi_driver);
+}
+
+module_init(sh_mobile_sdhi_init);
+module_exit(sh_mobile_sdhi_exit);
+
+MODULE_DESCRIPTION("SuperH Mobile SDHI driver");
+MODULE_AUTHOR("Magnus Damm");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:sh_mobile_sdhi");
diff --git a/drivers/mmc/host/tifm_sd.c b/drivers/mmc/host/tifm_sd.c
new file mode 100644
index 00000000..457c26ea
--- /dev/null
+++ b/drivers/mmc/host/tifm_sd.c
@@ -0,0 +1,1094 @@
+/*
+ * tifm_sd.c - TI FlashMedia driver
+ *
+ * Copyright (C) 2006 Alex Dubov <oakad@yahoo.com>
+ *
+ * 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.
+ *
+ * Special thanks to Brad Campbell for extensive testing of this driver.
+ *
+ */
+
+
+#include <linux/tifm.h>
+#include <linux/mmc/host.h>
+#include <linux/highmem.h>
+#include <linux/scatterlist.h>
+#include <asm/io.h>
+
+#define DRIVER_NAME "tifm_sd"
+#define DRIVER_VERSION "0.8"
+
+static int no_dma = 0;
+static int fixed_timeout = 0;
+module_param(no_dma, bool, 0644);
+module_param(fixed_timeout, bool, 0644);
+
+/* Constants here are mostly from OMAP5912 datasheet */
+#define TIFM_MMCSD_RESET 0x0002
+#define TIFM_MMCSD_CLKMASK 0x03ff
+#define TIFM_MMCSD_POWER 0x0800
+#define TIFM_MMCSD_4BBUS 0x8000
+#define TIFM_MMCSD_RXDE 0x8000 /* rx dma enable */
+#define TIFM_MMCSD_TXDE 0x0080 /* tx dma enable */
+#define TIFM_MMCSD_BUFINT 0x0c00 /* set bits: AE, AF */
+#define TIFM_MMCSD_DPE 0x0020 /* data timeout counted in kilocycles */
+#define TIFM_MMCSD_INAB 0x0080 /* abort / initialize command */
+#define TIFM_MMCSD_READ 0x8000
+
+#define TIFM_MMCSD_ERRMASK 0x01e0 /* set bits: CCRC, CTO, DCRC, DTO */
+#define TIFM_MMCSD_EOC 0x0001 /* end of command phase */
+#define TIFM_MMCSD_CD 0x0002 /* card detect */
+#define TIFM_MMCSD_CB 0x0004 /* card enter busy state */
+#define TIFM_MMCSD_BRS 0x0008 /* block received/sent */
+#define TIFM_MMCSD_EOFB 0x0010 /* card exit busy state */
+#define TIFM_MMCSD_DTO 0x0020 /* data time-out */
+#define TIFM_MMCSD_DCRC 0x0040 /* data crc error */
+#define TIFM_MMCSD_CTO 0x0080 /* command time-out */
+#define TIFM_MMCSD_CCRC 0x0100 /* command crc error */
+#define TIFM_MMCSD_AF 0x0400 /* fifo almost full */
+#define TIFM_MMCSD_AE 0x0800 /* fifo almost empty */
+#define TIFM_MMCSD_OCRB 0x1000 /* OCR busy */
+#define TIFM_MMCSD_CIRQ 0x2000 /* card irq (cmd40/sdio) */
+#define TIFM_MMCSD_CERR 0x4000 /* card status error */
+
+#define TIFM_MMCSD_ODTO 0x0040 /* open drain / extended timeout */
+#define TIFM_MMCSD_CARD_RO 0x0200 /* card is read-only */
+
+#define TIFM_MMCSD_FIFO_SIZE 0x0020
+
+#define TIFM_MMCSD_RSP_R0 0x0000
+#define TIFM_MMCSD_RSP_R1 0x0100
+#define TIFM_MMCSD_RSP_R2 0x0200
+#define TIFM_MMCSD_RSP_R3 0x0300
+#define TIFM_MMCSD_RSP_R4 0x0400
+#define TIFM_MMCSD_RSP_R5 0x0500
+#define TIFM_MMCSD_RSP_R6 0x0600
+
+#define TIFM_MMCSD_RSP_BUSY 0x0800
+
+#define TIFM_MMCSD_CMD_BC 0x0000
+#define TIFM_MMCSD_CMD_BCR 0x1000
+#define TIFM_MMCSD_CMD_AC 0x2000
+#define TIFM_MMCSD_CMD_ADTC 0x3000
+
+#define TIFM_MMCSD_MAX_BLOCK_SIZE 0x0800UL
+
+enum {
+ CMD_READY = 0x0001,
+ FIFO_READY = 0x0002,
+ BRS_READY = 0x0004,
+ SCMD_ACTIVE = 0x0008,
+ SCMD_READY = 0x0010,
+ CARD_BUSY = 0x0020,
+ DATA_CARRY = 0x0040
+};
+
+struct tifm_sd {
+ struct tifm_dev *dev;
+
+ unsigned short eject:1,
+ open_drain:1,
+ no_dma:1;
+ unsigned short cmd_flags;
+
+ unsigned int clk_freq;
+ unsigned int clk_div;
+ unsigned long timeout_jiffies;
+
+ struct tasklet_struct finish_tasklet;
+ struct timer_list timer;
+ struct mmc_request *req;
+
+ int sg_len;
+ int sg_pos;
+ unsigned int block_pos;
+ struct scatterlist bounce_buf;
+ unsigned char bounce_buf_data[TIFM_MMCSD_MAX_BLOCK_SIZE];
+};
+
+/* for some reason, host won't respond correctly to readw/writew */
+static void tifm_sd_read_fifo(struct tifm_sd *host, struct page *pg,
+ unsigned int off, unsigned int cnt)
+{
+ struct tifm_dev *sock = host->dev;
+ unsigned char *buf;
+ unsigned int pos = 0, val;
+
+ buf = kmap_atomic(pg, KM_BIO_DST_IRQ) + off;
+ if (host->cmd_flags & DATA_CARRY) {
+ buf[pos++] = host->bounce_buf_data[0];
+ host->cmd_flags &= ~DATA_CARRY;
+ }
+
+ while (pos < cnt) {
+ val = readl(sock->addr + SOCK_MMCSD_DATA);
+ buf[pos++] = val & 0xff;
+ if (pos == cnt) {
+ host->bounce_buf_data[0] = (val >> 8) & 0xff;
+ host->cmd_flags |= DATA_CARRY;
+ break;
+ }
+ buf[pos++] = (val >> 8) & 0xff;
+ }
+ kunmap_atomic(buf - off, KM_BIO_DST_IRQ);
+}
+
+static void tifm_sd_write_fifo(struct tifm_sd *host, struct page *pg,
+ unsigned int off, unsigned int cnt)
+{
+ struct tifm_dev *sock = host->dev;
+ unsigned char *buf;
+ unsigned int pos = 0, val;
+
+ buf = kmap_atomic(pg, KM_BIO_SRC_IRQ) + off;
+ if (host->cmd_flags & DATA_CARRY) {
+ val = host->bounce_buf_data[0] | ((buf[pos++] << 8) & 0xff00);
+ writel(val, sock->addr + SOCK_MMCSD_DATA);
+ host->cmd_flags &= ~DATA_CARRY;
+ }
+
+ while (pos < cnt) {
+ val = buf[pos++];
+ if (pos == cnt) {
+ host->bounce_buf_data[0] = val & 0xff;
+ host->cmd_flags |= DATA_CARRY;
+ break;
+ }
+ val |= (buf[pos++] << 8) & 0xff00;
+ writel(val, sock->addr + SOCK_MMCSD_DATA);
+ }
+ kunmap_atomic(buf - off, KM_BIO_SRC_IRQ);
+}
+
+static void tifm_sd_transfer_data(struct tifm_sd *host)
+{
+ struct mmc_data *r_data = host->req->cmd->data;
+ struct scatterlist *sg = r_data->sg;
+ unsigned int off, cnt, t_size = TIFM_MMCSD_FIFO_SIZE * 2;
+ unsigned int p_off, p_cnt;
+ struct page *pg;
+
+ if (host->sg_pos == host->sg_len)
+ return;
+ while (t_size) {
+ cnt = sg[host->sg_pos].length - host->block_pos;
+ if (!cnt) {
+ host->block_pos = 0;
+ host->sg_pos++;
+ if (host->sg_pos == host->sg_len) {
+ if ((r_data->flags & MMC_DATA_WRITE)
+ && (host->cmd_flags & DATA_CARRY))
+ writel(host->bounce_buf_data[0],
+ host->dev->addr
+ + SOCK_MMCSD_DATA);
+
+ return;
+ }
+ cnt = sg[host->sg_pos].length;
+ }
+ off = sg[host->sg_pos].offset + host->block_pos;
+
+ pg = nth_page(sg_page(&sg[host->sg_pos]), off >> PAGE_SHIFT);
+ p_off = offset_in_page(off);
+ p_cnt = PAGE_SIZE - p_off;
+ p_cnt = min(p_cnt, cnt);
+ p_cnt = min(p_cnt, t_size);
+
+ if (r_data->flags & MMC_DATA_READ)
+ tifm_sd_read_fifo(host, pg, p_off, p_cnt);
+ else if (r_data->flags & MMC_DATA_WRITE)
+ tifm_sd_write_fifo(host, pg, p_off, p_cnt);
+
+ t_size -= p_cnt;
+ host->block_pos += p_cnt;
+ }
+}
+
+static void tifm_sd_copy_page(struct page *dst, unsigned int dst_off,
+ struct page *src, unsigned int src_off,
+ unsigned int count)
+{
+ unsigned char *src_buf = kmap_atomic(src, KM_BIO_SRC_IRQ) + src_off;
+ unsigned char *dst_buf = kmap_atomic(dst, KM_BIO_DST_IRQ) + dst_off;
+
+ memcpy(dst_buf, src_buf, count);
+
+ kunmap_atomic(dst_buf - dst_off, KM_BIO_DST_IRQ);
+ kunmap_atomic(src_buf - src_off, KM_BIO_SRC_IRQ);
+}
+
+static void tifm_sd_bounce_block(struct tifm_sd *host, struct mmc_data *r_data)
+{
+ struct scatterlist *sg = r_data->sg;
+ unsigned int t_size = r_data->blksz;
+ unsigned int off, cnt;
+ unsigned int p_off, p_cnt;
+ struct page *pg;
+
+ dev_dbg(&host->dev->dev, "bouncing block\n");
+ while (t_size) {
+ cnt = sg[host->sg_pos].length - host->block_pos;
+ if (!cnt) {
+ host->block_pos = 0;
+ host->sg_pos++;
+ if (host->sg_pos == host->sg_len)
+ return;
+ cnt = sg[host->sg_pos].length;
+ }
+ off = sg[host->sg_pos].offset + host->block_pos;
+
+ pg = nth_page(sg_page(&sg[host->sg_pos]), off >> PAGE_SHIFT);
+ p_off = offset_in_page(off);
+ p_cnt = PAGE_SIZE - p_off;
+ p_cnt = min(p_cnt, cnt);
+ p_cnt = min(p_cnt, t_size);
+
+ if (r_data->flags & MMC_DATA_WRITE)
+ tifm_sd_copy_page(sg_page(&host->bounce_buf),
+ r_data->blksz - t_size,
+ pg, p_off, p_cnt);
+ else if (r_data->flags & MMC_DATA_READ)
+ tifm_sd_copy_page(pg, p_off, sg_page(&host->bounce_buf),
+ r_data->blksz - t_size, p_cnt);
+
+ t_size -= p_cnt;
+ host->block_pos += p_cnt;
+ }
+}
+
+static int tifm_sd_set_dma_data(struct tifm_sd *host, struct mmc_data *r_data)
+{
+ struct tifm_dev *sock = host->dev;
+ unsigned int t_size = TIFM_DMA_TSIZE * r_data->blksz;
+ unsigned int dma_len, dma_blk_cnt, dma_off;
+ struct scatterlist *sg = NULL;
+ unsigned long flags;
+
+ if (host->sg_pos == host->sg_len)
+ return 1;
+
+ if (host->cmd_flags & DATA_CARRY) {
+ host->cmd_flags &= ~DATA_CARRY;
+ local_irq_save(flags);
+ tifm_sd_bounce_block(host, r_data);
+ local_irq_restore(flags);
+ if (host->sg_pos == host->sg_len)
+ return 1;
+ }
+
+ dma_len = sg_dma_len(&r_data->sg[host->sg_pos]) - host->block_pos;
+ if (!dma_len) {
+ host->block_pos = 0;
+ host->sg_pos++;
+ if (host->sg_pos == host->sg_len)
+ return 1;
+ dma_len = sg_dma_len(&r_data->sg[host->sg_pos]);
+ }
+
+ if (dma_len < t_size) {
+ dma_blk_cnt = dma_len / r_data->blksz;
+ dma_off = host->block_pos;
+ host->block_pos += dma_blk_cnt * r_data->blksz;
+ } else {
+ dma_blk_cnt = TIFM_DMA_TSIZE;
+ dma_off = host->block_pos;
+ host->block_pos += t_size;
+ }
+
+ if (dma_blk_cnt)
+ sg = &r_data->sg[host->sg_pos];
+ else if (dma_len) {
+ if (r_data->flags & MMC_DATA_WRITE) {
+ local_irq_save(flags);
+ tifm_sd_bounce_block(host, r_data);
+ local_irq_restore(flags);
+ } else
+ host->cmd_flags |= DATA_CARRY;
+
+ sg = &host->bounce_buf;
+ dma_off = 0;
+ dma_blk_cnt = 1;
+ } else
+ return 1;
+
+ dev_dbg(&sock->dev, "setting dma for %d blocks\n", dma_blk_cnt);
+ writel(sg_dma_address(sg) + dma_off, sock->addr + SOCK_DMA_ADDRESS);
+ if (r_data->flags & MMC_DATA_WRITE)
+ writel((dma_blk_cnt << 8) | TIFM_DMA_TX | TIFM_DMA_EN,
+ sock->addr + SOCK_DMA_CONTROL);
+ else
+ writel((dma_blk_cnt << 8) | TIFM_DMA_EN,
+ sock->addr + SOCK_DMA_CONTROL);
+
+ return 0;
+}
+
+static unsigned int tifm_sd_op_flags(struct mmc_command *cmd)
+{
+ unsigned int rc = 0;
+
+ switch (mmc_resp_type(cmd)) {
+ case MMC_RSP_NONE:
+ rc |= TIFM_MMCSD_RSP_R0;
+ break;
+ case MMC_RSP_R1B:
+ rc |= TIFM_MMCSD_RSP_BUSY; // deliberate fall-through
+ case MMC_RSP_R1:
+ rc |= TIFM_MMCSD_RSP_R1;
+ break;
+ case MMC_RSP_R2:
+ rc |= TIFM_MMCSD_RSP_R2;
+ break;
+ case MMC_RSP_R3:
+ rc |= TIFM_MMCSD_RSP_R3;
+ break;
+ default:
+ BUG();
+ }
+
+ switch (mmc_cmd_type(cmd)) {
+ case MMC_CMD_BC:
+ rc |= TIFM_MMCSD_CMD_BC;
+ break;
+ case MMC_CMD_BCR:
+ rc |= TIFM_MMCSD_CMD_BCR;
+ break;
+ case MMC_CMD_AC:
+ rc |= TIFM_MMCSD_CMD_AC;
+ break;
+ case MMC_CMD_ADTC:
+ rc |= TIFM_MMCSD_CMD_ADTC;
+ break;
+ default:
+ BUG();
+ }
+ return rc;
+}
+
+static void tifm_sd_exec(struct tifm_sd *host, struct mmc_command *cmd)
+{
+ struct tifm_dev *sock = host->dev;
+ unsigned int cmd_mask = tifm_sd_op_flags(cmd);
+
+ if (host->open_drain)
+ cmd_mask |= TIFM_MMCSD_ODTO;
+
+ if (cmd->data && (cmd->data->flags & MMC_DATA_READ))
+ cmd_mask |= TIFM_MMCSD_READ;
+
+ dev_dbg(&sock->dev, "executing opcode 0x%x, arg: 0x%x, mask: 0x%x\n",
+ cmd->opcode, cmd->arg, cmd_mask);
+
+ writel((cmd->arg >> 16) & 0xffff, sock->addr + SOCK_MMCSD_ARG_HIGH);
+ writel(cmd->arg & 0xffff, sock->addr + SOCK_MMCSD_ARG_LOW);
+ writel(cmd->opcode | cmd_mask, sock->addr + SOCK_MMCSD_COMMAND);
+}
+
+static void tifm_sd_fetch_resp(struct mmc_command *cmd, struct tifm_dev *sock)
+{
+ cmd->resp[0] = (readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x1c) << 16)
+ | readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x18);
+ cmd->resp[1] = (readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x14) << 16)
+ | readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x10);
+ cmd->resp[2] = (readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x0c) << 16)
+ | readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x08);
+ cmd->resp[3] = (readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x04) << 16)
+ | readl(sock->addr + SOCK_MMCSD_RESPONSE + 0x00);
+}
+
+static void tifm_sd_check_status(struct tifm_sd *host)
+{
+ struct tifm_dev *sock = host->dev;
+ struct mmc_command *cmd = host->req->cmd;
+
+ if (cmd->error)
+ goto finish_request;
+
+ if (!(host->cmd_flags & CMD_READY))
+ return;
+
+ if (cmd->data) {
+ if (cmd->data->error) {
+ if ((host->cmd_flags & SCMD_ACTIVE)
+ && !(host->cmd_flags & SCMD_READY))
+ return;
+
+ goto finish_request;
+ }
+
+ if (!(host->cmd_flags & BRS_READY))
+ return;
+
+ if (!(host->no_dma || (host->cmd_flags & FIFO_READY)))
+ return;
+
+ if (cmd->data->flags & MMC_DATA_WRITE) {
+ if (host->req->stop) {
+ if (!(host->cmd_flags & SCMD_ACTIVE)) {
+ host->cmd_flags |= SCMD_ACTIVE;
+ writel(TIFM_MMCSD_EOFB
+ | readl(sock->addr
+ + SOCK_MMCSD_INT_ENABLE),
+ sock->addr
+ + SOCK_MMCSD_INT_ENABLE);
+ tifm_sd_exec(host, host->req->stop);
+ return;
+ } else {
+ if (!(host->cmd_flags & SCMD_READY)
+ || (host->cmd_flags & CARD_BUSY))
+ return;
+ writel((~TIFM_MMCSD_EOFB)
+ & readl(sock->addr
+ + SOCK_MMCSD_INT_ENABLE),
+ sock->addr
+ + SOCK_MMCSD_INT_ENABLE);
+ }
+ } else {
+ if (host->cmd_flags & CARD_BUSY)
+ return;
+ writel((~TIFM_MMCSD_EOFB)
+ & readl(sock->addr
+ + SOCK_MMCSD_INT_ENABLE),
+ sock->addr + SOCK_MMCSD_INT_ENABLE);
+ }
+ } else {
+ if (host->req->stop) {
+ if (!(host->cmd_flags & SCMD_ACTIVE)) {
+ host->cmd_flags |= SCMD_ACTIVE;
+ tifm_sd_exec(host, host->req->stop);
+ return;
+ } else {
+ if (!(host->cmd_flags & SCMD_READY))
+ return;
+ }
+ }
+ }
+ }
+finish_request:
+ tasklet_schedule(&host->finish_tasklet);
+}
+
+/* Called from interrupt handler */
+static void tifm_sd_data_event(struct tifm_dev *sock)
+{
+ struct tifm_sd *host;
+ unsigned int fifo_status = 0;
+ struct mmc_data *r_data = NULL;
+
+ spin_lock(&sock->lock);
+ host = mmc_priv((struct mmc_host*)tifm_get_drvdata(sock));
+ fifo_status = readl(sock->addr + SOCK_DMA_FIFO_STATUS);
+ dev_dbg(&sock->dev, "data event: fifo_status %x, flags %x\n",
+ fifo_status, host->cmd_flags);
+
+ if (host->req) {
+ r_data = host->req->cmd->data;
+
+ if (r_data && (fifo_status & TIFM_FIFO_READY)) {
+ if (tifm_sd_set_dma_data(host, r_data)) {
+ host->cmd_flags |= FIFO_READY;
+ tifm_sd_check_status(host);
+ }
+ }
+ }
+
+ writel(fifo_status, sock->addr + SOCK_DMA_FIFO_STATUS);
+ spin_unlock(&sock->lock);
+}
+
+/* Called from interrupt handler */
+static void tifm_sd_card_event(struct tifm_dev *sock)
+{
+ struct tifm_sd *host;
+ unsigned int host_status = 0;
+ int cmd_error = 0;
+ struct mmc_command *cmd = NULL;
+ unsigned long flags;
+
+ spin_lock(&sock->lock);
+ host = mmc_priv((struct mmc_host*)tifm_get_drvdata(sock));
+ host_status = readl(sock->addr + SOCK_MMCSD_STATUS);
+ dev_dbg(&sock->dev, "host event: host_status %x, flags %x\n",
+ host_status, host->cmd_flags);
+
+ if (host->req) {
+ cmd = host->req->cmd;
+
+ if (host_status & TIFM_MMCSD_ERRMASK) {
+ writel(host_status & TIFM_MMCSD_ERRMASK,
+ sock->addr + SOCK_MMCSD_STATUS);
+ if (host_status & TIFM_MMCSD_CTO)
+ cmd_error = -ETIMEDOUT;
+ else if (host_status & TIFM_MMCSD_CCRC)
+ cmd_error = -EILSEQ;
+
+ if (cmd->data) {
+ if (host_status & TIFM_MMCSD_DTO)
+ cmd->data->error = -ETIMEDOUT;
+ else if (host_status & TIFM_MMCSD_DCRC)
+ cmd->data->error = -EILSEQ;
+ }
+
+ writel(TIFM_FIFO_INT_SETALL,
+ sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR);
+ writel(TIFM_DMA_RESET, sock->addr + SOCK_DMA_CONTROL);
+
+ if (host->req->stop) {
+ if (host->cmd_flags & SCMD_ACTIVE) {
+ host->req->stop->error = cmd_error;
+ host->cmd_flags |= SCMD_READY;
+ } else {
+ cmd->error = cmd_error;
+ host->cmd_flags |= SCMD_ACTIVE;
+ tifm_sd_exec(host, host->req->stop);
+ goto done;
+ }
+ } else
+ cmd->error = cmd_error;
+ } else {
+ if (host_status & (TIFM_MMCSD_EOC | TIFM_MMCSD_CERR)) {
+ if (!(host->cmd_flags & CMD_READY)) {
+ host->cmd_flags |= CMD_READY;
+ tifm_sd_fetch_resp(cmd, sock);
+ } else if (host->cmd_flags & SCMD_ACTIVE) {
+ host->cmd_flags |= SCMD_READY;
+ tifm_sd_fetch_resp(host->req->stop,
+ sock);
+ }
+ }
+ if (host_status & TIFM_MMCSD_BRS)
+ host->cmd_flags |= BRS_READY;
+ }
+
+ if (host->no_dma && cmd->data) {
+ if (host_status & TIFM_MMCSD_AE)
+ writel(host_status & TIFM_MMCSD_AE,
+ sock->addr + SOCK_MMCSD_STATUS);
+
+ if (host_status & (TIFM_MMCSD_AE | TIFM_MMCSD_AF
+ | TIFM_MMCSD_BRS)) {
+ local_irq_save(flags);
+ tifm_sd_transfer_data(host);
+ local_irq_restore(flags);
+ host_status &= ~TIFM_MMCSD_AE;
+ }
+ }
+
+ if (host_status & TIFM_MMCSD_EOFB)
+ host->cmd_flags &= ~CARD_BUSY;
+ else if (host_status & TIFM_MMCSD_CB)
+ host->cmd_flags |= CARD_BUSY;
+
+ tifm_sd_check_status(host);
+ }
+done:
+ writel(host_status, sock->addr + SOCK_MMCSD_STATUS);
+ spin_unlock(&sock->lock);
+}
+
+static void tifm_sd_set_data_timeout(struct tifm_sd *host,
+ struct mmc_data *data)
+{
+ struct tifm_dev *sock = host->dev;
+ unsigned int data_timeout = data->timeout_clks;
+
+ if (fixed_timeout)
+ return;
+
+ data_timeout += data->timeout_ns /
+ ((1000000000UL / host->clk_freq) * host->clk_div);
+
+ if (data_timeout < 0xffff) {
+ writel(data_timeout, sock->addr + SOCK_MMCSD_DATA_TO);
+ writel((~TIFM_MMCSD_DPE)
+ & readl(sock->addr + SOCK_MMCSD_SDIO_MODE_CONFIG),
+ sock->addr + SOCK_MMCSD_SDIO_MODE_CONFIG);
+ } else {
+ data_timeout = (data_timeout >> 10) + 1;
+ if (data_timeout > 0xffff)
+ data_timeout = 0; /* set to unlimited */
+ writel(data_timeout, sock->addr + SOCK_MMCSD_DATA_TO);
+ writel(TIFM_MMCSD_DPE
+ | readl(sock->addr + SOCK_MMCSD_SDIO_MODE_CONFIG),
+ sock->addr + SOCK_MMCSD_SDIO_MODE_CONFIG);
+ }
+}
+
+static void tifm_sd_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct tifm_sd *host = mmc_priv(mmc);
+ struct tifm_dev *sock = host->dev;
+ unsigned long flags;
+ struct mmc_data *r_data = mrq->cmd->data;
+
+ spin_lock_irqsave(&sock->lock, flags);
+ if (host->eject) {
+ mrq->cmd->error = -ENOMEDIUM;
+ goto err_out;
+ }
+
+ if (host->req) {
+ printk(KERN_ERR "%s : unfinished request detected\n",
+ dev_name(&sock->dev));
+ mrq->cmd->error = -ETIMEDOUT;
+ goto err_out;
+ }
+
+ host->cmd_flags = 0;
+ host->block_pos = 0;
+ host->sg_pos = 0;
+
+ if (mrq->data && !is_power_of_2(mrq->data->blksz))
+ host->no_dma = 1;
+ else
+ host->no_dma = no_dma ? 1 : 0;
+
+ if (r_data) {
+ tifm_sd_set_data_timeout(host, r_data);
+
+ if ((r_data->flags & MMC_DATA_WRITE) && !mrq->stop)
+ writel(TIFM_MMCSD_EOFB
+ | readl(sock->addr + SOCK_MMCSD_INT_ENABLE),
+ sock->addr + SOCK_MMCSD_INT_ENABLE);
+
+ if (host->no_dma) {
+ writel(TIFM_MMCSD_BUFINT
+ | readl(sock->addr + SOCK_MMCSD_INT_ENABLE),
+ sock->addr + SOCK_MMCSD_INT_ENABLE);
+ writel(((TIFM_MMCSD_FIFO_SIZE - 1) << 8)
+ | (TIFM_MMCSD_FIFO_SIZE - 1),
+ sock->addr + SOCK_MMCSD_BUFFER_CONFIG);
+
+ host->sg_len = r_data->sg_len;
+ } else {
+ sg_init_one(&host->bounce_buf, host->bounce_buf_data,
+ r_data->blksz);
+
+ if(1 != tifm_map_sg(sock, &host->bounce_buf, 1,
+ r_data->flags & MMC_DATA_WRITE
+ ? PCI_DMA_TODEVICE
+ : PCI_DMA_FROMDEVICE)) {
+ printk(KERN_ERR "%s : scatterlist map failed\n",
+ dev_name(&sock->dev));
+ mrq->cmd->error = -ENOMEM;
+ goto err_out;
+ }
+ host->sg_len = tifm_map_sg(sock, r_data->sg,
+ r_data->sg_len,
+ r_data->flags
+ & MMC_DATA_WRITE
+ ? PCI_DMA_TODEVICE
+ : PCI_DMA_FROMDEVICE);
+ if (host->sg_len < 1) {
+ printk(KERN_ERR "%s : scatterlist map failed\n",
+ dev_name(&sock->dev));
+ tifm_unmap_sg(sock, &host->bounce_buf, 1,
+ r_data->flags & MMC_DATA_WRITE
+ ? PCI_DMA_TODEVICE
+ : PCI_DMA_FROMDEVICE);
+ mrq->cmd->error = -ENOMEM;
+ goto err_out;
+ }
+
+ writel(TIFM_FIFO_INT_SETALL,
+ sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR);
+ writel(ilog2(r_data->blksz) - 2,
+ sock->addr + SOCK_FIFO_PAGE_SIZE);
+ writel(TIFM_FIFO_ENABLE,
+ sock->addr + SOCK_FIFO_CONTROL);
+ writel(TIFM_FIFO_INTMASK,
+ sock->addr + SOCK_DMA_FIFO_INT_ENABLE_SET);
+
+ if (r_data->flags & MMC_DATA_WRITE)
+ writel(TIFM_MMCSD_TXDE,
+ sock->addr + SOCK_MMCSD_BUFFER_CONFIG);
+ else
+ writel(TIFM_MMCSD_RXDE,
+ sock->addr + SOCK_MMCSD_BUFFER_CONFIG);
+
+ tifm_sd_set_dma_data(host, r_data);
+ }
+
+ writel(r_data->blocks - 1,
+ sock->addr + SOCK_MMCSD_NUM_BLOCKS);
+ writel(r_data->blksz - 1,
+ sock->addr + SOCK_MMCSD_BLOCK_LEN);
+ }
+
+ host->req = mrq;
+ mod_timer(&host->timer, jiffies + host->timeout_jiffies);
+ writel(TIFM_CTRL_LED | readl(sock->addr + SOCK_CONTROL),
+ sock->addr + SOCK_CONTROL);
+ tifm_sd_exec(host, mrq->cmd);
+ spin_unlock_irqrestore(&sock->lock, flags);
+ return;
+
+err_out:
+ spin_unlock_irqrestore(&sock->lock, flags);
+ mmc_request_done(mmc, mrq);
+}
+
+static void tifm_sd_end_cmd(unsigned long data)
+{
+ struct tifm_sd *host = (struct tifm_sd*)data;
+ struct tifm_dev *sock = host->dev;
+ struct mmc_host *mmc = tifm_get_drvdata(sock);
+ struct mmc_request *mrq;
+ struct mmc_data *r_data = NULL;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sock->lock, flags);
+
+ del_timer(&host->timer);
+ mrq = host->req;
+ host->req = NULL;
+
+ if (!mrq) {
+ printk(KERN_ERR " %s : no request to complete?\n",
+ dev_name(&sock->dev));
+ spin_unlock_irqrestore(&sock->lock, flags);
+ return;
+ }
+
+ r_data = mrq->cmd->data;
+ if (r_data) {
+ if (host->no_dma) {
+ writel((~TIFM_MMCSD_BUFINT)
+ & readl(sock->addr + SOCK_MMCSD_INT_ENABLE),
+ sock->addr + SOCK_MMCSD_INT_ENABLE);
+ } else {
+ tifm_unmap_sg(sock, &host->bounce_buf, 1,
+ (r_data->flags & MMC_DATA_WRITE)
+ ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE);
+ tifm_unmap_sg(sock, r_data->sg, r_data->sg_len,
+ (r_data->flags & MMC_DATA_WRITE)
+ ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE);
+ }
+
+ r_data->bytes_xfered = r_data->blocks
+ - readl(sock->addr + SOCK_MMCSD_NUM_BLOCKS) - 1;
+ r_data->bytes_xfered *= r_data->blksz;
+ r_data->bytes_xfered += r_data->blksz
+ - readl(sock->addr + SOCK_MMCSD_BLOCK_LEN) + 1;
+ }
+
+ writel((~TIFM_CTRL_LED) & readl(sock->addr + SOCK_CONTROL),
+ sock->addr + SOCK_CONTROL);
+
+ spin_unlock_irqrestore(&sock->lock, flags);
+ mmc_request_done(mmc, mrq);
+}
+
+static void tifm_sd_abort(unsigned long data)
+{
+ struct tifm_sd *host = (struct tifm_sd*)data;
+
+ printk(KERN_ERR
+ "%s : card failed to respond for a long period of time "
+ "(%x, %x)\n",
+ dev_name(&host->dev->dev), host->req->cmd->opcode, host->cmd_flags);
+
+ tifm_eject(host->dev);
+}
+
+static void tifm_sd_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct tifm_sd *host = mmc_priv(mmc);
+ struct tifm_dev *sock = host->dev;
+ unsigned int clk_div1, clk_div2;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sock->lock, flags);
+
+ dev_dbg(&sock->dev, "ios: clock = %u, vdd = %x, bus_mode = %x, "
+ "chip_select = %x, power_mode = %x, bus_width = %x\n",
+ ios->clock, ios->vdd, ios->bus_mode, ios->chip_select,
+ ios->power_mode, ios->bus_width);
+
+ if (ios->bus_width == MMC_BUS_WIDTH_4) {
+ writel(TIFM_MMCSD_4BBUS | readl(sock->addr + SOCK_MMCSD_CONFIG),
+ sock->addr + SOCK_MMCSD_CONFIG);
+ } else {
+ writel((~TIFM_MMCSD_4BBUS)
+ & readl(sock->addr + SOCK_MMCSD_CONFIG),
+ sock->addr + SOCK_MMCSD_CONFIG);
+ }
+
+ if (ios->clock) {
+ clk_div1 = 20000000 / ios->clock;
+ if (!clk_div1)
+ clk_div1 = 1;
+
+ clk_div2 = 24000000 / ios->clock;
+ if (!clk_div2)
+ clk_div2 = 1;
+
+ if ((20000000 / clk_div1) > ios->clock)
+ clk_div1++;
+ if ((24000000 / clk_div2) > ios->clock)
+ clk_div2++;
+ if ((20000000 / clk_div1) > (24000000 / clk_div2)) {
+ host->clk_freq = 20000000;
+ host->clk_div = clk_div1;
+ writel((~TIFM_CTRL_FAST_CLK)
+ & readl(sock->addr + SOCK_CONTROL),
+ sock->addr + SOCK_CONTROL);
+ } else {
+ host->clk_freq = 24000000;
+ host->clk_div = clk_div2;
+ writel(TIFM_CTRL_FAST_CLK
+ | readl(sock->addr + SOCK_CONTROL),
+ sock->addr + SOCK_CONTROL);
+ }
+ } else {
+ host->clk_div = 0;
+ }
+ host->clk_div &= TIFM_MMCSD_CLKMASK;
+ writel(host->clk_div
+ | ((~TIFM_MMCSD_CLKMASK)
+ & readl(sock->addr + SOCK_MMCSD_CONFIG)),
+ sock->addr + SOCK_MMCSD_CONFIG);
+
+ host->open_drain = (ios->bus_mode == MMC_BUSMODE_OPENDRAIN);
+
+ /* chip_select : maybe later */
+ //vdd
+ //power is set before probe / after remove
+
+ spin_unlock_irqrestore(&sock->lock, flags);
+}
+
+static int tifm_sd_ro(struct mmc_host *mmc)
+{
+ int rc = 0;
+ struct tifm_sd *host = mmc_priv(mmc);
+ struct tifm_dev *sock = host->dev;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sock->lock, flags);
+ if (TIFM_MMCSD_CARD_RO & readl(sock->addr + SOCK_PRESENT_STATE))
+ rc = 1;
+ spin_unlock_irqrestore(&sock->lock, flags);
+ return rc;
+}
+
+static const struct mmc_host_ops tifm_sd_ops = {
+ .request = tifm_sd_request,
+ .set_ios = tifm_sd_ios,
+ .get_ro = tifm_sd_ro
+};
+
+static int tifm_sd_initialize_host(struct tifm_sd *host)
+{
+ int rc;
+ unsigned int host_status = 0;
+ struct tifm_dev *sock = host->dev;
+
+ writel(0, sock->addr + SOCK_MMCSD_INT_ENABLE);
+ mmiowb();
+ host->clk_div = 61;
+ host->clk_freq = 20000000;
+ writel(TIFM_MMCSD_RESET, sock->addr + SOCK_MMCSD_SYSTEM_CONTROL);
+ writel(host->clk_div | TIFM_MMCSD_POWER,
+ sock->addr + SOCK_MMCSD_CONFIG);
+
+ /* wait up to 0.51 sec for reset */
+ for (rc = 32; rc <= 256; rc <<= 1) {
+ if (1 & readl(sock->addr + SOCK_MMCSD_SYSTEM_STATUS)) {
+ rc = 0;
+ break;
+ }
+ msleep(rc);
+ }
+
+ if (rc) {
+ printk(KERN_ERR "%s : controller failed to reset\n",
+ dev_name(&sock->dev));
+ return -ENODEV;
+ }
+
+ writel(0, sock->addr + SOCK_MMCSD_NUM_BLOCKS);
+ writel(host->clk_div | TIFM_MMCSD_POWER,
+ sock->addr + SOCK_MMCSD_CONFIG);
+ writel(TIFM_MMCSD_RXDE, sock->addr + SOCK_MMCSD_BUFFER_CONFIG);
+
+ // command timeout fixed to 64 clocks for now
+ writel(64, sock->addr + SOCK_MMCSD_COMMAND_TO);
+ writel(TIFM_MMCSD_INAB, sock->addr + SOCK_MMCSD_COMMAND);
+
+ for (rc = 16; rc <= 64; rc <<= 1) {
+ host_status = readl(sock->addr + SOCK_MMCSD_STATUS);
+ writel(host_status, sock->addr + SOCK_MMCSD_STATUS);
+ if (!(host_status & TIFM_MMCSD_ERRMASK)
+ && (host_status & TIFM_MMCSD_EOC)) {
+ rc = 0;
+ break;
+ }
+ msleep(rc);
+ }
+
+ if (rc) {
+ printk(KERN_ERR
+ "%s : card not ready - probe failed on initialization\n",
+ dev_name(&sock->dev));
+ return -ENODEV;
+ }
+
+ writel(TIFM_MMCSD_CERR | TIFM_MMCSD_BRS | TIFM_MMCSD_EOC
+ | TIFM_MMCSD_ERRMASK,
+ sock->addr + SOCK_MMCSD_INT_ENABLE);
+ mmiowb();
+
+ return 0;
+}
+
+static int tifm_sd_probe(struct tifm_dev *sock)
+{
+ struct mmc_host *mmc;
+ struct tifm_sd *host;
+ int rc = -EIO;
+
+ if (!(TIFM_SOCK_STATE_OCCUPIED
+ & readl(sock->addr + SOCK_PRESENT_STATE))) {
+ printk(KERN_WARNING "%s : card gone, unexpectedly\n",
+ dev_name(&sock->dev));
+ return rc;
+ }
+
+ mmc = mmc_alloc_host(sizeof(struct tifm_sd), &sock->dev);
+ if (!mmc)
+ return -ENOMEM;
+
+ host = mmc_priv(mmc);
+ tifm_set_drvdata(sock, mmc);
+ host->dev = sock;
+ host->timeout_jiffies = msecs_to_jiffies(1000);
+
+ tasklet_init(&host->finish_tasklet, tifm_sd_end_cmd,
+ (unsigned long)host);
+ setup_timer(&host->timer, tifm_sd_abort, (unsigned long)host);
+
+ mmc->ops = &tifm_sd_ops;
+ mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+ mmc->caps = MMC_CAP_4_BIT_DATA;
+ mmc->f_min = 20000000 / 60;
+ mmc->f_max = 24000000;
+
+ mmc->max_blk_count = 2048;
+ mmc->max_segs = mmc->max_blk_count;
+ mmc->max_blk_size = min(TIFM_MMCSD_MAX_BLOCK_SIZE, PAGE_SIZE);
+ mmc->max_seg_size = mmc->max_blk_count * mmc->max_blk_size;
+ mmc->max_req_size = mmc->max_seg_size;
+
+ sock->card_event = tifm_sd_card_event;
+ sock->data_event = tifm_sd_data_event;
+ rc = tifm_sd_initialize_host(host);
+
+ if (!rc)
+ rc = mmc_add_host(mmc);
+ if (!rc)
+ return 0;
+
+ mmc_free_host(mmc);
+ return rc;
+}
+
+static void tifm_sd_remove(struct tifm_dev *sock)
+{
+ struct mmc_host *mmc = tifm_get_drvdata(sock);
+ struct tifm_sd *host = mmc_priv(mmc);
+ unsigned long flags;
+
+ spin_lock_irqsave(&sock->lock, flags);
+ host->eject = 1;
+ writel(0, sock->addr + SOCK_MMCSD_INT_ENABLE);
+ mmiowb();
+ spin_unlock_irqrestore(&sock->lock, flags);
+
+ tasklet_kill(&host->finish_tasklet);
+
+ spin_lock_irqsave(&sock->lock, flags);
+ if (host->req) {
+ writel(TIFM_FIFO_INT_SETALL,
+ sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR);
+ writel(0, sock->addr + SOCK_DMA_FIFO_INT_ENABLE_SET);
+ host->req->cmd->error = -ENOMEDIUM;
+ if (host->req->stop)
+ host->req->stop->error = -ENOMEDIUM;
+ tasklet_schedule(&host->finish_tasklet);
+ }
+ spin_unlock_irqrestore(&sock->lock, flags);
+ mmc_remove_host(mmc);
+ dev_dbg(&sock->dev, "after remove\n");
+
+ mmc_free_host(mmc);
+}
+
+#ifdef CONFIG_PM
+
+static int tifm_sd_suspend(struct tifm_dev *sock, pm_message_t state)
+{
+ return mmc_suspend_host(tifm_get_drvdata(sock));
+}
+
+static int tifm_sd_resume(struct tifm_dev *sock)
+{
+ struct mmc_host *mmc = tifm_get_drvdata(sock);
+ struct tifm_sd *host = mmc_priv(mmc);
+ int rc;
+
+ rc = tifm_sd_initialize_host(host);
+ dev_dbg(&sock->dev, "resume initialize %d\n", rc);
+
+ if (rc)
+ host->eject = 1;
+ else
+ rc = mmc_resume_host(mmc);
+
+ return rc;
+}
+
+#else
+
+#define tifm_sd_suspend NULL
+#define tifm_sd_resume NULL
+
+#endif /* CONFIG_PM */
+
+static struct tifm_device_id tifm_sd_id_tbl[] = {
+ { TIFM_TYPE_SD }, { }
+};
+
+static struct tifm_driver tifm_sd_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE
+ },
+ .id_table = tifm_sd_id_tbl,
+ .probe = tifm_sd_probe,
+ .remove = tifm_sd_remove,
+ .suspend = tifm_sd_suspend,
+ .resume = tifm_sd_resume
+};
+
+static int __init tifm_sd_init(void)
+{
+ return tifm_register_driver(&tifm_sd_driver);
+}
+
+static void __exit tifm_sd_exit(void)
+{
+ tifm_unregister_driver(&tifm_sd_driver);
+}
+
+MODULE_AUTHOR("Alex Dubov");
+MODULE_DESCRIPTION("TI FlashMedia SD driver");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(tifm, tifm_sd_id_tbl);
+MODULE_VERSION(DRIVER_VERSION);
+
+module_init(tifm_sd_init);
+module_exit(tifm_sd_exit);
diff --git a/drivers/mmc/host/tmio_mmc.c b/drivers/mmc/host/tmio_mmc.c
new file mode 100644
index 00000000..8d185de9
--- /dev/null
+++ b/drivers/mmc/host/tmio_mmc.c
@@ -0,0 +1,160 @@
+/*
+ * linux/drivers/mmc/host/tmio_mmc.c
+ *
+ * Copyright (C) 2007 Ian Molton
+ * Copyright (C) 2004 Ian Molton
+ *
+ * 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.
+ *
+ * Driver for the MMC / SD / SDIO cell found in:
+ *
+ * TC6393XB TC6391XB TC6387XB T7L66XB ASIC3
+ */
+
+#include <linux/device.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/tmio.h>
+#include <linux/mmc/host.h>
+#include <linux/module.h>
+#include <linux/pagemap.h>
+#include <linux/scatterlist.h>
+
+#include "tmio_mmc.h"
+
+#ifdef CONFIG_PM
+static int tmio_mmc_suspend(struct platform_device *dev, pm_message_t state)
+{
+ const struct mfd_cell *cell = mfd_get_cell(dev);
+ struct mmc_host *mmc = platform_get_drvdata(dev);
+ int ret;
+
+ ret = tmio_mmc_host_suspend(&dev->dev);
+
+ /* Tell MFD core it can disable us now.*/
+ if (!ret && cell->disable)
+ cell->disable(dev);
+
+ return ret;
+}
+
+static int tmio_mmc_resume(struct platform_device *dev)
+{
+ const struct mfd_cell *cell = mfd_get_cell(dev);
+ struct mmc_host *mmc = platform_get_drvdata(dev);
+ int ret = 0;
+
+ /* Tell the MFD core we are ready to be enabled */
+ if (cell->resume)
+ ret = cell->resume(dev);
+
+ if (!ret)
+ ret = tmio_mmc_host_resume(&dev->dev);
+
+ return ret;
+}
+#else
+#define tmio_mmc_suspend NULL
+#define tmio_mmc_resume NULL
+#endif
+
+static int __devinit tmio_mmc_probe(struct platform_device *pdev)
+{
+ const struct mfd_cell *cell = mfd_get_cell(pdev);
+ struct tmio_mmc_data *pdata;
+ struct tmio_mmc_host *host;
+ int ret = -EINVAL, irq;
+
+ if (pdev->num_resources != 2)
+ goto out;
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata || !pdata->hclk)
+ goto out;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ ret = irq;
+ goto out;
+ }
+
+ /* Tell the MFD core we are ready to be enabled */
+ if (cell->enable) {
+ ret = cell->enable(pdev);
+ if (ret)
+ goto out;
+ }
+
+ ret = tmio_mmc_host_probe(&host, pdev, pdata);
+ if (ret)
+ goto cell_disable;
+
+ ret = request_irq(irq, tmio_mmc_irq, IRQF_DISABLED |
+ IRQF_TRIGGER_FALLING, dev_name(&pdev->dev), host);
+ if (ret)
+ goto host_remove;
+
+ pr_info("%s at 0x%08lx irq %d\n", mmc_hostname(host->mmc),
+ (unsigned long)host->ctl, irq);
+
+ return 0;
+
+host_remove:
+ tmio_mmc_host_remove(host);
+cell_disable:
+ if (cell->disable)
+ cell->disable(pdev);
+out:
+ return ret;
+}
+
+static int __devexit tmio_mmc_remove(struct platform_device *pdev)
+{
+ const struct mfd_cell *cell = mfd_get_cell(pdev);
+ struct mmc_host *mmc = platform_get_drvdata(pdev);
+
+ platform_set_drvdata(pdev, NULL);
+
+ if (mmc) {
+ struct tmio_mmc_host *host = mmc_priv(mmc);
+ free_irq(platform_get_irq(pdev, 0), host);
+ tmio_mmc_host_remove(host);
+ if (cell->disable)
+ cell->disable(pdev);
+ }
+
+ return 0;
+}
+
+/* ------------------- device registration ----------------------- */
+
+static struct platform_driver tmio_mmc_driver = {
+ .driver = {
+ .name = "tmio-mmc",
+ .owner = THIS_MODULE,
+ },
+ .probe = tmio_mmc_probe,
+ .remove = __devexit_p(tmio_mmc_remove),
+ .suspend = tmio_mmc_suspend,
+ .resume = tmio_mmc_resume,
+};
+
+
+static int __init tmio_mmc_init(void)
+{
+ return platform_driver_register(&tmio_mmc_driver);
+}
+
+static void __exit tmio_mmc_exit(void)
+{
+ platform_driver_unregister(&tmio_mmc_driver);
+}
+
+module_init(tmio_mmc_init);
+module_exit(tmio_mmc_exit);
+
+MODULE_DESCRIPTION("Toshiba TMIO SD/MMC driver");
+MODULE_AUTHOR("Ian Molton <spyro@f2s.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:tmio-mmc");
diff --git a/drivers/mmc/host/tmio_mmc.h b/drivers/mmc/host/tmio_mmc.h
new file mode 100644
index 00000000..8260bc2c
--- /dev/null
+++ b/drivers/mmc/host/tmio_mmc.h
@@ -0,0 +1,137 @@
+/*
+ * linux/drivers/mmc/host/tmio_mmc.h
+ *
+ * Copyright (C) 2007 Ian Molton
+ * Copyright (C) 2004 Ian Molton
+ *
+ * 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.
+ *
+ * Driver for the MMC / SD / SDIO cell found in:
+ *
+ * TC6393XB TC6391XB TC6387XB T7L66XB ASIC3
+ */
+
+#ifndef TMIO_MMC_H
+#define TMIO_MMC_H
+
+#include <linux/highmem.h>
+#include <linux/mmc/tmio.h>
+#include <linux/pagemap.h>
+#include <linux/spinlock.h>
+
+/* Definitions for values the CTRL_SDIO_STATUS register can take. */
+#define TMIO_SDIO_STAT_IOIRQ 0x0001
+#define TMIO_SDIO_STAT_EXPUB52 0x4000
+#define TMIO_SDIO_STAT_EXWT 0x8000
+#define TMIO_SDIO_MASK_ALL 0xc007
+
+/* Define some IRQ masks */
+/* This is the mask used at reset by the chip */
+#define TMIO_MASK_ALL 0x837f031d
+#define TMIO_MASK_READOP (TMIO_STAT_RXRDY | TMIO_STAT_DATAEND)
+#define TMIO_MASK_WRITEOP (TMIO_STAT_TXRQ | TMIO_STAT_DATAEND)
+#define TMIO_MASK_CMD (TMIO_STAT_CMDRESPEND | TMIO_STAT_CMDTIMEOUT | \
+ TMIO_STAT_CARD_REMOVE | TMIO_STAT_CARD_INSERT)
+#define TMIO_MASK_IRQ (TMIO_MASK_READOP | TMIO_MASK_WRITEOP | TMIO_MASK_CMD)
+
+struct tmio_mmc_data;
+
+struct tmio_mmc_host {
+ void __iomem *ctl;
+ unsigned long bus_shift;
+ struct mmc_command *cmd;
+ struct mmc_request *mrq;
+ struct mmc_data *data;
+ struct mmc_host *mmc;
+ unsigned int sdio_irq_enabled;
+
+ /* Callbacks for clock / power control */
+ void (*set_pwr)(struct platform_device *host, int state);
+ void (*set_clk_div)(struct platform_device *host, int state);
+
+ int pm_error;
+
+ /* pio related stuff */
+ struct scatterlist *sg_ptr;
+ struct scatterlist *sg_orig;
+ unsigned int sg_len;
+ unsigned int sg_off;
+
+ struct platform_device *pdev;
+ struct tmio_mmc_data *pdata;
+
+ /* DMA support */
+ bool force_pio;
+ struct dma_chan *chan_rx;
+ struct dma_chan *chan_tx;
+ struct tasklet_struct dma_complete;
+ struct tasklet_struct dma_issue;
+ struct scatterlist bounce_sg;
+ u8 *bounce_buf;
+
+ /* Track lost interrupts */
+ struct delayed_work delayed_reset_work;
+ spinlock_t lock;
+ unsigned long last_req_ts;
+};
+
+int tmio_mmc_host_probe(struct tmio_mmc_host **host,
+ struct platform_device *pdev,
+ struct tmio_mmc_data *pdata);
+void tmio_mmc_host_remove(struct tmio_mmc_host *host);
+void tmio_mmc_do_data_irq(struct tmio_mmc_host *host);
+
+void tmio_mmc_enable_mmc_irqs(struct tmio_mmc_host *host, u32 i);
+void tmio_mmc_disable_mmc_irqs(struct tmio_mmc_host *host, u32 i);
+irqreturn_t tmio_mmc_irq(int irq, void *devid);
+
+static inline char *tmio_mmc_kmap_atomic(struct scatterlist *sg,
+ unsigned long *flags)
+{
+ local_irq_save(*flags);
+ return kmap_atomic(sg_page(sg), KM_BIO_SRC_IRQ) + sg->offset;
+}
+
+static inline void tmio_mmc_kunmap_atomic(struct scatterlist *sg,
+ unsigned long *flags, void *virt)
+{
+ kunmap_atomic(virt - sg->offset, KM_BIO_SRC_IRQ);
+ local_irq_restore(*flags);
+}
+
+#if defined(CONFIG_MMC_SDHI) || defined(CONFIG_MMC_SDHI_MODULE)
+void tmio_mmc_start_dma(struct tmio_mmc_host *host, struct mmc_data *data);
+void tmio_mmc_request_dma(struct tmio_mmc_host *host, struct tmio_mmc_data *pdata);
+void tmio_mmc_release_dma(struct tmio_mmc_host *host);
+#else
+static inline void tmio_mmc_start_dma(struct tmio_mmc_host *host,
+ struct mmc_data *data)
+{
+}
+
+static inline void tmio_mmc_request_dma(struct tmio_mmc_host *host,
+ struct tmio_mmc_data *pdata)
+{
+ host->chan_tx = NULL;
+ host->chan_rx = NULL;
+}
+
+static inline void tmio_mmc_release_dma(struct tmio_mmc_host *host)
+{
+}
+#endif
+
+#ifdef CONFIG_PM
+int tmio_mmc_host_suspend(struct device *dev);
+int tmio_mmc_host_resume(struct device *dev);
+#else
+#define tmio_mmc_host_suspend NULL
+#define tmio_mmc_host_resume NULL
+#endif
+
+int tmio_mmc_host_runtime_suspend(struct device *dev);
+int tmio_mmc_host_runtime_resume(struct device *dev);
+
+#endif
diff --git a/drivers/mmc/host/tmio_mmc_dma.c b/drivers/mmc/host/tmio_mmc_dma.c
new file mode 100644
index 00000000..bf12513d
--- /dev/null
+++ b/drivers/mmc/host/tmio_mmc_dma.c
@@ -0,0 +1,321 @@
+/*
+ * linux/drivers/mmc/tmio_mmc_dma.c
+ *
+ * Copyright (C) 2010-2011 Guennadi Liakhovetski
+ *
+ * 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.
+ *
+ * DMA function for TMIO MMC implementations
+ */
+
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/mfd/tmio.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/tmio.h>
+#include <linux/pagemap.h>
+#include <linux/scatterlist.h>
+
+#include "tmio_mmc.h"
+
+#define TMIO_MMC_MIN_DMA_LEN 8
+
+static void tmio_mmc_enable_dma(struct tmio_mmc_host *host, bool enable)
+{
+#if defined(CONFIG_SUPERH) || defined(CONFIG_ARCH_SHMOBILE)
+ /* Switch DMA mode on or off - SuperH specific? */
+ writew(enable ? 2 : 0, host->ctl + (0xd8 << host->bus_shift));
+#endif
+}
+
+static void tmio_mmc_start_dma_rx(struct tmio_mmc_host *host)
+{
+ struct scatterlist *sg = host->sg_ptr, *sg_tmp;
+ struct dma_async_tx_descriptor *desc = NULL;
+ struct dma_chan *chan = host->chan_rx;
+ struct tmio_mmc_data *pdata = host->pdata;
+ dma_cookie_t cookie;
+ int ret, i;
+ bool aligned = true, multiple = true;
+ unsigned int align = (1 << pdata->dma->alignment_shift) - 1;
+
+ for_each_sg(sg, sg_tmp, host->sg_len, i) {
+ if (sg_tmp->offset & align)
+ aligned = false;
+ if (sg_tmp->length & align) {
+ multiple = false;
+ break;
+ }
+ }
+
+ if ((!aligned && (host->sg_len > 1 || sg->length > PAGE_CACHE_SIZE ||
+ (align & PAGE_MASK))) || !multiple) {
+ ret = -EINVAL;
+ goto pio;
+ }
+
+ if (sg->length < TMIO_MMC_MIN_DMA_LEN) {
+ host->force_pio = true;
+ return;
+ }
+
+ tmio_mmc_disable_mmc_irqs(host, TMIO_STAT_RXRDY);
+
+ /* The only sg element can be unaligned, use our bounce buffer then */
+ if (!aligned) {
+ sg_init_one(&host->bounce_sg, host->bounce_buf, sg->length);
+ host->sg_ptr = &host->bounce_sg;
+ sg = host->sg_ptr;
+ }
+
+ ret = dma_map_sg(chan->device->dev, sg, host->sg_len, DMA_FROM_DEVICE);
+ if (ret > 0)
+ desc = chan->device->device_prep_slave_sg(chan, sg, ret,
+ DMA_FROM_DEVICE, DMA_CTRL_ACK);
+
+ if (desc) {
+ cookie = dmaengine_submit(desc);
+ if (cookie < 0) {
+ desc = NULL;
+ ret = cookie;
+ }
+ }
+ dev_dbg(&host->pdev->dev, "%s(): mapped %d -> %d, cookie %d, rq %p\n",
+ __func__, host->sg_len, ret, cookie, host->mrq);
+
+pio:
+ if (!desc) {
+ /* DMA failed, fall back to PIO */
+ if (ret >= 0)
+ ret = -EIO;
+ host->chan_rx = NULL;
+ dma_release_channel(chan);
+ /* Free the Tx channel too */
+ chan = host->chan_tx;
+ if (chan) {
+ host->chan_tx = NULL;
+ dma_release_channel(chan);
+ }
+ dev_warn(&host->pdev->dev,
+ "DMA failed: %d, falling back to PIO\n", ret);
+ tmio_mmc_enable_dma(host, false);
+ }
+
+ dev_dbg(&host->pdev->dev, "%s(): desc %p, cookie %d, sg[%d]\n", __func__,
+ desc, cookie, host->sg_len);
+}
+
+static void tmio_mmc_start_dma_tx(struct tmio_mmc_host *host)
+{
+ struct scatterlist *sg = host->sg_ptr, *sg_tmp;
+ struct dma_async_tx_descriptor *desc = NULL;
+ struct dma_chan *chan = host->chan_tx;
+ struct tmio_mmc_data *pdata = host->pdata;
+ dma_cookie_t cookie;
+ int ret, i;
+ bool aligned = true, multiple = true;
+ unsigned int align = (1 << pdata->dma->alignment_shift) - 1;
+
+ for_each_sg(sg, sg_tmp, host->sg_len, i) {
+ if (sg_tmp->offset & align)
+ aligned = false;
+ if (sg_tmp->length & align) {
+ multiple = false;
+ break;
+ }
+ }
+
+ if ((!aligned && (host->sg_len > 1 || sg->length > PAGE_CACHE_SIZE ||
+ (align & PAGE_MASK))) || !multiple) {
+ ret = -EINVAL;
+ goto pio;
+ }
+
+ if (sg->length < TMIO_MMC_MIN_DMA_LEN) {
+ host->force_pio = true;
+ return;
+ }
+
+ tmio_mmc_disable_mmc_irqs(host, TMIO_STAT_TXRQ);
+
+ /* The only sg element can be unaligned, use our bounce buffer then */
+ if (!aligned) {
+ unsigned long flags;
+ void *sg_vaddr = tmio_mmc_kmap_atomic(sg, &flags);
+ sg_init_one(&host->bounce_sg, host->bounce_buf, sg->length);
+ memcpy(host->bounce_buf, sg_vaddr, host->bounce_sg.length);
+ tmio_mmc_kunmap_atomic(sg, &flags, sg_vaddr);
+ host->sg_ptr = &host->bounce_sg;
+ sg = host->sg_ptr;
+ }
+
+ ret = dma_map_sg(chan->device->dev, sg, host->sg_len, DMA_TO_DEVICE);
+ if (ret > 0)
+ desc = chan->device->device_prep_slave_sg(chan, sg, ret,
+ DMA_TO_DEVICE, DMA_CTRL_ACK);
+
+ if (desc) {
+ cookie = dmaengine_submit(desc);
+ if (cookie < 0) {
+ desc = NULL;
+ ret = cookie;
+ }
+ }
+ dev_dbg(&host->pdev->dev, "%s(): mapped %d -> %d, cookie %d, rq %p\n",
+ __func__, host->sg_len, ret, cookie, host->mrq);
+
+pio:
+ if (!desc) {
+ /* DMA failed, fall back to PIO */
+ if (ret >= 0)
+ ret = -EIO;
+ host->chan_tx = NULL;
+ dma_release_channel(chan);
+ /* Free the Rx channel too */
+ chan = host->chan_rx;
+ if (chan) {
+ host->chan_rx = NULL;
+ dma_release_channel(chan);
+ }
+ dev_warn(&host->pdev->dev,
+ "DMA failed: %d, falling back to PIO\n", ret);
+ tmio_mmc_enable_dma(host, false);
+ }
+
+ dev_dbg(&host->pdev->dev, "%s(): desc %p, cookie %d\n", __func__,
+ desc, cookie);
+}
+
+void tmio_mmc_start_dma(struct tmio_mmc_host *host,
+ struct mmc_data *data)
+{
+ if (data->flags & MMC_DATA_READ) {
+ if (host->chan_rx)
+ tmio_mmc_start_dma_rx(host);
+ } else {
+ if (host->chan_tx)
+ tmio_mmc_start_dma_tx(host);
+ }
+}
+
+static void tmio_mmc_issue_tasklet_fn(unsigned long priv)
+{
+ struct tmio_mmc_host *host = (struct tmio_mmc_host *)priv;
+ struct dma_chan *chan = NULL;
+
+ spin_lock_irq(&host->lock);
+
+ if (host && host->data) {
+ if (host->data->flags & MMC_DATA_READ)
+ chan = host->chan_rx;
+ else
+ chan = host->chan_tx;
+ }
+
+ spin_unlock_irq(&host->lock);
+
+ tmio_mmc_enable_mmc_irqs(host, TMIO_STAT_DATAEND);
+
+ if (chan)
+ dma_async_issue_pending(chan);
+}
+
+static void tmio_mmc_tasklet_fn(unsigned long arg)
+{
+ struct tmio_mmc_host *host = (struct tmio_mmc_host *)arg;
+
+ spin_lock_irq(&host->lock);
+
+ if (!host->data)
+ goto out;
+
+ if (host->data->flags & MMC_DATA_READ)
+ dma_unmap_sg(host->chan_rx->device->dev,
+ host->sg_ptr, host->sg_len,
+ DMA_FROM_DEVICE);
+ else
+ dma_unmap_sg(host->chan_tx->device->dev,
+ host->sg_ptr, host->sg_len,
+ DMA_TO_DEVICE);
+
+ tmio_mmc_do_data_irq(host);
+out:
+ spin_unlock_irq(&host->lock);
+}
+
+/* It might be necessary to make filter MFD specific */
+static bool tmio_mmc_filter(struct dma_chan *chan, void *arg)
+{
+ dev_dbg(chan->device->dev, "%s: slave data %p\n", __func__, arg);
+ chan->private = arg;
+ return true;
+}
+
+void tmio_mmc_request_dma(struct tmio_mmc_host *host, struct tmio_mmc_data *pdata)
+{
+ /* We can only either use DMA for both Tx and Rx or not use it at all */
+ if (!pdata->dma)
+ return;
+
+ if (!host->chan_tx && !host->chan_rx) {
+ dma_cap_mask_t mask;
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+
+ host->chan_tx = dma_request_channel(mask, tmio_mmc_filter,
+ pdata->dma->chan_priv_tx);
+ dev_dbg(&host->pdev->dev, "%s: TX: got channel %p\n", __func__,
+ host->chan_tx);
+
+ if (!host->chan_tx)
+ return;
+
+ host->chan_rx = dma_request_channel(mask, tmio_mmc_filter,
+ pdata->dma->chan_priv_rx);
+ dev_dbg(&host->pdev->dev, "%s: RX: got channel %p\n", __func__,
+ host->chan_rx);
+
+ if (!host->chan_rx)
+ goto ereqrx;
+
+ host->bounce_buf = (u8 *)__get_free_page(GFP_KERNEL | GFP_DMA);
+ if (!host->bounce_buf)
+ goto ebouncebuf;
+
+ tasklet_init(&host->dma_complete, tmio_mmc_tasklet_fn, (unsigned long)host);
+ tasklet_init(&host->dma_issue, tmio_mmc_issue_tasklet_fn, (unsigned long)host);
+ }
+
+ tmio_mmc_enable_dma(host, true);
+
+ return;
+
+ebouncebuf:
+ dma_release_channel(host->chan_rx);
+ host->chan_rx = NULL;
+ereqrx:
+ dma_release_channel(host->chan_tx);
+ host->chan_tx = NULL;
+}
+
+void tmio_mmc_release_dma(struct tmio_mmc_host *host)
+{
+ if (host->chan_tx) {
+ struct dma_chan *chan = host->chan_tx;
+ host->chan_tx = NULL;
+ dma_release_channel(chan);
+ }
+ if (host->chan_rx) {
+ struct dma_chan *chan = host->chan_rx;
+ host->chan_rx = NULL;
+ dma_release_channel(chan);
+ }
+ if (host->bounce_buf) {
+ free_pages((unsigned long)host->bounce_buf, 0);
+ host->bounce_buf = NULL;
+ }
+}
diff --git a/drivers/mmc/host/tmio_mmc_pio.c b/drivers/mmc/host/tmio_mmc_pio.c
new file mode 100644
index 00000000..0b09e823
--- /dev/null
+++ b/drivers/mmc/host/tmio_mmc_pio.c
@@ -0,0 +1,1037 @@
+/*
+ * linux/drivers/mmc/host/tmio_mmc_pio.c
+ *
+ * Copyright (C) 2011 Guennadi Liakhovetski
+ * Copyright (C) 2007 Ian Molton
+ * Copyright (C) 2004 Ian Molton
+ *
+ * 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.
+ *
+ * Driver for the MMC / SD / SDIO IP found in:
+ *
+ * TC6393XB, TC6391XB, TC6387XB, T7L66XB, ASIC3, SH-Mobile SoCs
+ *
+ * This driver draws mainly on scattered spec sheets, Reverse engineering
+ * of the toshiba e800 SD driver and some parts of the 2.4 ASIC3 driver (4 bit
+ * support). (Further 4 bit support from a later datasheet).
+ *
+ * TODO:
+ * Investigate using a workqueue for PIO transfers
+ * Eliminate FIXMEs
+ * SDIO support
+ * Better Power management
+ * Handle MMC errors better
+ * double buffer support
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/highmem.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/mfd/tmio.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/tmio.h>
+#include <linux/module.h>
+#include <linux/pagemap.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/scatterlist.h>
+#include <linux/workqueue.h>
+#include <linux/spinlock.h>
+
+#include "tmio_mmc.h"
+
+static u16 sd_ctrl_read16(struct tmio_mmc_host *host, int addr)
+{
+ return readw(host->ctl + (addr << host->bus_shift));
+}
+
+static void sd_ctrl_read16_rep(struct tmio_mmc_host *host, int addr,
+ u16 *buf, int count)
+{
+ readsw(host->ctl + (addr << host->bus_shift), buf, count);
+}
+
+static u32 sd_ctrl_read32(struct tmio_mmc_host *host, int addr)
+{
+ return readw(host->ctl + (addr << host->bus_shift)) |
+ readw(host->ctl + ((addr + 2) << host->bus_shift)) << 16;
+}
+
+static void sd_ctrl_write16(struct tmio_mmc_host *host, int addr, u16 val)
+{
+ writew(val, host->ctl + (addr << host->bus_shift));
+}
+
+static void sd_ctrl_write16_rep(struct tmio_mmc_host *host, int addr,
+ u16 *buf, int count)
+{
+ writesw(host->ctl + (addr << host->bus_shift), buf, count);
+}
+
+static void sd_ctrl_write32(struct tmio_mmc_host *host, int addr, u32 val)
+{
+ writew(val, host->ctl + (addr << host->bus_shift));
+ writew(val >> 16, host->ctl + ((addr + 2) << host->bus_shift));
+}
+
+void tmio_mmc_enable_mmc_irqs(struct tmio_mmc_host *host, u32 i)
+{
+ u32 mask = sd_ctrl_read32(host, CTL_IRQ_MASK) & ~(i & TMIO_MASK_IRQ);
+ sd_ctrl_write32(host, CTL_IRQ_MASK, mask);
+}
+
+void tmio_mmc_disable_mmc_irqs(struct tmio_mmc_host *host, u32 i)
+{
+ u32 mask = sd_ctrl_read32(host, CTL_IRQ_MASK) | (i & TMIO_MASK_IRQ);
+ sd_ctrl_write32(host, CTL_IRQ_MASK, mask);
+}
+
+static void tmio_mmc_ack_mmc_irqs(struct tmio_mmc_host *host, u32 i)
+{
+ sd_ctrl_write32(host, CTL_STATUS, ~i);
+}
+
+static void tmio_mmc_init_sg(struct tmio_mmc_host *host, struct mmc_data *data)
+{
+ host->sg_len = data->sg_len;
+ host->sg_ptr = data->sg;
+ host->sg_orig = data->sg;
+ host->sg_off = 0;
+}
+
+static int tmio_mmc_next_sg(struct tmio_mmc_host *host)
+{
+ host->sg_ptr = sg_next(host->sg_ptr);
+ host->sg_off = 0;
+ return --host->sg_len;
+}
+
+#ifdef CONFIG_MMC_DEBUG
+
+#define STATUS_TO_TEXT(a, status, i) \
+ do { \
+ if (status & TMIO_STAT_##a) { \
+ if (i++) \
+ printk(" | "); \
+ printk(#a); \
+ } \
+ } while (0)
+
+static void pr_debug_status(u32 status)
+{
+ int i = 0;
+ printk(KERN_DEBUG "status: %08x = ", status);
+ STATUS_TO_TEXT(CARD_REMOVE, status, i);
+ STATUS_TO_TEXT(CARD_INSERT, status, i);
+ STATUS_TO_TEXT(SIGSTATE, status, i);
+ STATUS_TO_TEXT(WRPROTECT, status, i);
+ STATUS_TO_TEXT(CARD_REMOVE_A, status, i);
+ STATUS_TO_TEXT(CARD_INSERT_A, status, i);
+ STATUS_TO_TEXT(SIGSTATE_A, status, i);
+ STATUS_TO_TEXT(CMD_IDX_ERR, status, i);
+ STATUS_TO_TEXT(STOPBIT_ERR, status, i);
+ STATUS_TO_TEXT(ILL_FUNC, status, i);
+ STATUS_TO_TEXT(CMD_BUSY, status, i);
+ STATUS_TO_TEXT(CMDRESPEND, status, i);
+ STATUS_TO_TEXT(DATAEND, status, i);
+ STATUS_TO_TEXT(CRCFAIL, status, i);
+ STATUS_TO_TEXT(DATATIMEOUT, status, i);
+ STATUS_TO_TEXT(CMDTIMEOUT, status, i);
+ STATUS_TO_TEXT(RXOVERFLOW, status, i);
+ STATUS_TO_TEXT(TXUNDERRUN, status, i);
+ STATUS_TO_TEXT(RXRDY, status, i);
+ STATUS_TO_TEXT(TXRQ, status, i);
+ STATUS_TO_TEXT(ILL_ACCESS, status, i);
+ printk("\n");
+}
+
+#else
+#define pr_debug_status(s) do { } while (0)
+#endif
+
+static void tmio_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+ struct tmio_mmc_host *host = mmc_priv(mmc);
+
+ if (enable) {
+ host->sdio_irq_enabled = 1;
+ sd_ctrl_write16(host, CTL_TRANSACTION_CTL, 0x0001);
+ sd_ctrl_write16(host, CTL_SDIO_IRQ_MASK,
+ (TMIO_SDIO_MASK_ALL & ~TMIO_SDIO_STAT_IOIRQ));
+ } else {
+ sd_ctrl_write16(host, CTL_SDIO_IRQ_MASK, TMIO_SDIO_MASK_ALL);
+ sd_ctrl_write16(host, CTL_TRANSACTION_CTL, 0x0000);
+ host->sdio_irq_enabled = 0;
+ }
+}
+
+static void tmio_mmc_set_clock(struct tmio_mmc_host *host, int new_clock)
+{
+ u32 clk = 0, clock;
+
+ if (new_clock) {
+ for (clock = host->mmc->f_min, clk = 0x80000080;
+ new_clock >= (clock<<1); clk >>= 1)
+ clock <<= 1;
+ clk |= 0x100;
+ }
+
+ if (host->set_clk_div)
+ host->set_clk_div(host->pdev, (clk>>22) & 1);
+
+ sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, clk & 0x1ff);
+}
+
+static void tmio_mmc_clk_stop(struct tmio_mmc_host *host)
+{
+ struct resource *res = platform_get_resource(host->pdev, IORESOURCE_MEM, 0);
+
+ /* implicit BUG_ON(!res) */
+ if (resource_size(res) > 0x100) {
+ sd_ctrl_write16(host, CTL_CLK_AND_WAIT_CTL, 0x0000);
+ msleep(10);
+ }
+
+ sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, ~0x0100 &
+ sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
+ msleep(10);
+}
+
+static void tmio_mmc_clk_start(struct tmio_mmc_host *host)
+{
+ struct resource *res = platform_get_resource(host->pdev, IORESOURCE_MEM, 0);
+
+ sd_ctrl_write16(host, CTL_SD_CARD_CLK_CTL, 0x0100 |
+ sd_ctrl_read16(host, CTL_SD_CARD_CLK_CTL));
+ msleep(10);
+
+ /* implicit BUG_ON(!res) */
+ if (resource_size(res) > 0x100) {
+ sd_ctrl_write16(host, CTL_CLK_AND_WAIT_CTL, 0x0100);
+ msleep(10);
+ }
+}
+
+static void tmio_mmc_reset(struct tmio_mmc_host *host)
+{
+ struct resource *res = platform_get_resource(host->pdev, IORESOURCE_MEM, 0);
+
+ /* FIXME - should we set stop clock reg here */
+ sd_ctrl_write16(host, CTL_RESET_SD, 0x0000);
+ /* implicit BUG_ON(!res) */
+ if (resource_size(res) > 0x100)
+ sd_ctrl_write16(host, CTL_RESET_SDIO, 0x0000);
+ msleep(10);
+ sd_ctrl_write16(host, CTL_RESET_SD, 0x0001);
+ if (resource_size(res) > 0x100)
+ sd_ctrl_write16(host, CTL_RESET_SDIO, 0x0001);
+ msleep(10);
+}
+
+static void tmio_mmc_reset_work(struct work_struct *work)
+{
+ struct tmio_mmc_host *host = container_of(work, struct tmio_mmc_host,
+ delayed_reset_work.work);
+ struct mmc_request *mrq;
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+ mrq = host->mrq;
+
+ /*
+ * is request already finished? Since we use a non-blocking
+ * cancel_delayed_work(), it can happen, that a .set_ios() call preempts
+ * us, so, have to check for IS_ERR(host->mrq)
+ */
+ if (IS_ERR_OR_NULL(mrq)
+ || time_is_after_jiffies(host->last_req_ts +
+ msecs_to_jiffies(2000))) {
+ spin_unlock_irqrestore(&host->lock, flags);
+ return;
+ }
+
+ dev_warn(&host->pdev->dev,
+ "timeout waiting for hardware interrupt (CMD%u)\n",
+ mrq->cmd->opcode);
+
+ if (host->data)
+ host->data->error = -ETIMEDOUT;
+ else if (host->cmd)
+ host->cmd->error = -ETIMEDOUT;
+ else
+ mrq->cmd->error = -ETIMEDOUT;
+
+ host->cmd = NULL;
+ host->data = NULL;
+ host->force_pio = false;
+
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ tmio_mmc_reset(host);
+
+ /* Ready for new calls */
+ host->mrq = NULL;
+
+ mmc_request_done(host->mmc, mrq);
+}
+
+/* called with host->lock held, interrupts disabled */
+static void tmio_mmc_finish_request(struct tmio_mmc_host *host)
+{
+ struct mmc_request *mrq = host->mrq;
+
+ if (!mrq)
+ return;
+
+ host->cmd = NULL;
+ host->data = NULL;
+ host->force_pio = false;
+
+ cancel_delayed_work(&host->delayed_reset_work);
+
+ host->mrq = NULL;
+
+ /* FIXME: mmc_request_done() can schedule! */
+ mmc_request_done(host->mmc, mrq);
+}
+
+/* These are the bitmasks the tmio chip requires to implement the MMC response
+ * types. Note that R1 and R6 are the same in this scheme. */
+#define APP_CMD 0x0040
+#define RESP_NONE 0x0300
+#define RESP_R1 0x0400
+#define RESP_R1B 0x0500
+#define RESP_R2 0x0600
+#define RESP_R3 0x0700
+#define DATA_PRESENT 0x0800
+#define TRANSFER_READ 0x1000
+#define TRANSFER_MULTI 0x2000
+#define SECURITY_CMD 0x4000
+
+static int tmio_mmc_start_command(struct tmio_mmc_host *host, struct mmc_command *cmd)
+{
+ struct mmc_data *data = host->data;
+ int c = cmd->opcode;
+
+ /* Command 12 is handled by hardware */
+ if (cmd->opcode == 12 && !cmd->arg) {
+ sd_ctrl_write16(host, CTL_STOP_INTERNAL_ACTION, 0x001);
+ return 0;
+ }
+
+ switch (mmc_resp_type(cmd)) {
+ case MMC_RSP_NONE: c |= RESP_NONE; break;
+ case MMC_RSP_R1: c |= RESP_R1; break;
+ case MMC_RSP_R1B: c |= RESP_R1B; break;
+ case MMC_RSP_R2: c |= RESP_R2; break;
+ case MMC_RSP_R3: c |= RESP_R3; break;
+ default:
+ pr_debug("Unknown response type %d\n", mmc_resp_type(cmd));
+ return -EINVAL;
+ }
+
+ host->cmd = cmd;
+
+/* FIXME - this seems to be ok commented out but the spec suggest this bit
+ * should be set when issuing app commands.
+ * if(cmd->flags & MMC_FLAG_ACMD)
+ * c |= APP_CMD;
+ */
+ if (data) {
+ c |= DATA_PRESENT;
+ if (data->blocks > 1) {
+ sd_ctrl_write16(host, CTL_STOP_INTERNAL_ACTION, 0x100);
+ c |= TRANSFER_MULTI;
+ }
+ if (data->flags & MMC_DATA_READ)
+ c |= TRANSFER_READ;
+ }
+
+ tmio_mmc_enable_mmc_irqs(host, TMIO_MASK_CMD);
+
+ /* Fire off the command */
+ sd_ctrl_write32(host, CTL_ARG_REG, cmd->arg);
+ sd_ctrl_write16(host, CTL_SD_CMD, c);
+
+ return 0;
+}
+
+/*
+ * This chip always returns (at least?) as much data as you ask for.
+ * I'm unsure what happens if you ask for less than a block. This should be
+ * looked into to ensure that a funny length read doesn't hose the controller.
+ */
+static void tmio_mmc_pio_irq(struct tmio_mmc_host *host)
+{
+ struct mmc_data *data = host->data;
+ void *sg_virt;
+ unsigned short *buf;
+ unsigned int count;
+ unsigned long flags;
+
+ if ((host->chan_tx || host->chan_rx) && !host->force_pio) {
+ pr_err("PIO IRQ in DMA mode!\n");
+ return;
+ } else if (!data) {
+ pr_debug("Spurious PIO IRQ\n");
+ return;
+ }
+
+ sg_virt = tmio_mmc_kmap_atomic(host->sg_ptr, &flags);
+ buf = (unsigned short *)(sg_virt + host->sg_off);
+
+ count = host->sg_ptr->length - host->sg_off;
+ if (count > data->blksz)
+ count = data->blksz;
+
+ pr_debug("count: %08x offset: %08x flags %08x\n",
+ count, host->sg_off, data->flags);
+
+ /* Transfer the data */
+ if (data->flags & MMC_DATA_READ)
+ sd_ctrl_read16_rep(host, CTL_SD_DATA_PORT, buf, count >> 1);
+ else
+ sd_ctrl_write16_rep(host, CTL_SD_DATA_PORT, buf, count >> 1);
+
+ host->sg_off += count;
+
+ tmio_mmc_kunmap_atomic(host->sg_ptr, &flags, sg_virt);
+
+ if (host->sg_off == host->sg_ptr->length)
+ tmio_mmc_next_sg(host);
+
+ return;
+}
+
+static void tmio_mmc_check_bounce_buffer(struct tmio_mmc_host *host)
+{
+ if (host->sg_ptr == &host->bounce_sg) {
+ unsigned long flags;
+ void *sg_vaddr = tmio_mmc_kmap_atomic(host->sg_orig, &flags);
+ memcpy(sg_vaddr, host->bounce_buf, host->bounce_sg.length);
+ tmio_mmc_kunmap_atomic(host->sg_orig, &flags, sg_vaddr);
+ }
+}
+
+/* needs to be called with host->lock held */
+void tmio_mmc_do_data_irq(struct tmio_mmc_host *host)
+{
+ struct mmc_data *data = host->data;
+ struct mmc_command *stop;
+
+ host->data = NULL;
+
+ if (!data) {
+ dev_warn(&host->pdev->dev, "Spurious data end IRQ\n");
+ return;
+ }
+ stop = data->stop;
+
+ /* FIXME - return correct transfer count on errors */
+ if (!data->error)
+ data->bytes_xfered = data->blocks * data->blksz;
+ else
+ data->bytes_xfered = 0;
+
+ pr_debug("Completed data request\n");
+
+ /*
+ * FIXME: other drivers allow an optional stop command of any given type
+ * which we dont do, as the chip can auto generate them.
+ * Perhaps we can be smarter about when to use auto CMD12 and
+ * only issue the auto request when we know this is the desired
+ * stop command, allowing fallback to the stop command the
+ * upper layers expect. For now, we do what works.
+ */
+
+ if (data->flags & MMC_DATA_READ) {
+ if (host->chan_rx && !host->force_pio)
+ tmio_mmc_check_bounce_buffer(host);
+ dev_dbg(&host->pdev->dev, "Complete Rx request %p\n",
+ host->mrq);
+ } else {
+ dev_dbg(&host->pdev->dev, "Complete Tx request %p\n",
+ host->mrq);
+ }
+
+ if (stop) {
+ if (stop->opcode == 12 && !stop->arg)
+ sd_ctrl_write16(host, CTL_STOP_INTERNAL_ACTION, 0x000);
+ else
+ BUG();
+ }
+
+ tmio_mmc_finish_request(host);
+}
+
+static void tmio_mmc_data_irq(struct tmio_mmc_host *host)
+{
+ struct mmc_data *data;
+ spin_lock(&host->lock);
+ data = host->data;
+
+ if (!data)
+ goto out;
+
+ if (host->chan_tx && (data->flags & MMC_DATA_WRITE) && !host->force_pio) {
+ /*
+ * Has all data been written out yet? Testing on SuperH showed,
+ * that in most cases the first interrupt comes already with the
+ * BUSY status bit clear, but on some operations, like mount or
+ * in the beginning of a write / sync / umount, there is one
+ * DATAEND interrupt with the BUSY bit set, in this cases
+ * waiting for one more interrupt fixes the problem.
+ */
+ if (!(sd_ctrl_read32(host, CTL_STATUS) & TMIO_STAT_CMD_BUSY)) {
+ tmio_mmc_disable_mmc_irqs(host, TMIO_STAT_DATAEND);
+ tasklet_schedule(&host->dma_complete);
+ }
+ } else if (host->chan_rx && (data->flags & MMC_DATA_READ) && !host->force_pio) {
+ tmio_mmc_disable_mmc_irqs(host, TMIO_STAT_DATAEND);
+ tasklet_schedule(&host->dma_complete);
+ } else {
+ tmio_mmc_do_data_irq(host);
+ tmio_mmc_disable_mmc_irqs(host, TMIO_MASK_READOP | TMIO_MASK_WRITEOP);
+ }
+out:
+ spin_unlock(&host->lock);
+}
+
+static void tmio_mmc_cmd_irq(struct tmio_mmc_host *host,
+ unsigned int stat)
+{
+ struct mmc_command *cmd = host->cmd;
+ int i, addr;
+
+ spin_lock(&host->lock);
+
+ if (!host->cmd) {
+ pr_debug("Spurious CMD irq\n");
+ goto out;
+ }
+
+ host->cmd = NULL;
+
+ /* This controller is sicker than the PXA one. Not only do we need to
+ * drop the top 8 bits of the first response word, we also need to
+ * modify the order of the response for short response command types.
+ */
+
+ for (i = 3, addr = CTL_RESPONSE ; i >= 0 ; i--, addr += 4)
+ cmd->resp[i] = sd_ctrl_read32(host, addr);
+
+ if (cmd->flags & MMC_RSP_136) {
+ cmd->resp[0] = (cmd->resp[0] << 8) | (cmd->resp[1] >> 24);
+ cmd->resp[1] = (cmd->resp[1] << 8) | (cmd->resp[2] >> 24);
+ cmd->resp[2] = (cmd->resp[2] << 8) | (cmd->resp[3] >> 24);
+ cmd->resp[3] <<= 8;
+ } else if (cmd->flags & MMC_RSP_R3) {
+ cmd->resp[0] = cmd->resp[3];
+ }
+
+ if (stat & TMIO_STAT_CMDTIMEOUT)
+ cmd->error = -ETIMEDOUT;
+ else if (stat & TMIO_STAT_CRCFAIL && cmd->flags & MMC_RSP_CRC)
+ cmd->error = -EILSEQ;
+
+ /* If there is data to handle we enable data IRQs here, and
+ * we will ultimatley finish the request in the data_end handler.
+ * If theres no data or we encountered an error, finish now.
+ */
+ if (host->data && !cmd->error) {
+ if (host->data->flags & MMC_DATA_READ) {
+ if (host->force_pio || !host->chan_rx)
+ tmio_mmc_enable_mmc_irqs(host, TMIO_MASK_READOP);
+ else
+ tasklet_schedule(&host->dma_issue);
+ } else {
+ if (host->force_pio || !host->chan_tx)
+ tmio_mmc_enable_mmc_irqs(host, TMIO_MASK_WRITEOP);
+ else
+ tasklet_schedule(&host->dma_issue);
+ }
+ } else {
+ tmio_mmc_finish_request(host);
+ }
+
+out:
+ spin_unlock(&host->lock);
+}
+
+irqreturn_t tmio_mmc_irq(int irq, void *devid)
+{
+ struct tmio_mmc_host *host = devid;
+ struct tmio_mmc_data *pdata = host->pdata;
+ unsigned int ireg, irq_mask, status;
+ unsigned int sdio_ireg, sdio_irq_mask, sdio_status;
+
+ pr_debug("MMC IRQ begin\n");
+
+ status = sd_ctrl_read32(host, CTL_STATUS);
+ irq_mask = sd_ctrl_read32(host, CTL_IRQ_MASK);
+ ireg = status & TMIO_MASK_IRQ & ~irq_mask;
+
+ sdio_ireg = 0;
+ if (!ireg && pdata->flags & TMIO_MMC_SDIO_IRQ) {
+ sdio_status = sd_ctrl_read16(host, CTL_SDIO_STATUS);
+ sdio_irq_mask = sd_ctrl_read16(host, CTL_SDIO_IRQ_MASK);
+ sdio_ireg = sdio_status & TMIO_SDIO_MASK_ALL & ~sdio_irq_mask;
+
+ sd_ctrl_write16(host, CTL_SDIO_STATUS, sdio_status & ~TMIO_SDIO_MASK_ALL);
+
+ if (sdio_ireg && !host->sdio_irq_enabled) {
+ pr_warning("tmio_mmc: Spurious SDIO IRQ, disabling! 0x%04x 0x%04x 0x%04x\n",
+ sdio_status, sdio_irq_mask, sdio_ireg);
+ tmio_mmc_enable_sdio_irq(host->mmc, 0);
+ goto out;
+ }
+
+ if (host->mmc->caps & MMC_CAP_SDIO_IRQ &&
+ sdio_ireg & TMIO_SDIO_STAT_IOIRQ)
+ mmc_signal_sdio_irq(host->mmc);
+
+ if (sdio_ireg)
+ goto out;
+ }
+
+ pr_debug_status(status);
+ pr_debug_status(ireg);
+
+ if (!ireg) {
+ tmio_mmc_disable_mmc_irqs(host, status & ~irq_mask);
+
+ pr_warning("tmio_mmc: Spurious irq, disabling! "
+ "0x%08x 0x%08x 0x%08x\n", status, irq_mask, ireg);
+ pr_debug_status(status);
+
+ goto out;
+ }
+
+ while (ireg) {
+ /* Card insert / remove attempts */
+ if (ireg & (TMIO_STAT_CARD_INSERT | TMIO_STAT_CARD_REMOVE)) {
+ tmio_mmc_ack_mmc_irqs(host, TMIO_STAT_CARD_INSERT |
+ TMIO_STAT_CARD_REMOVE);
+ mmc_detect_change(host->mmc, msecs_to_jiffies(100));
+ }
+
+ /* CRC and other errors */
+/* if (ireg & TMIO_STAT_ERR_IRQ)
+ * handled |= tmio_error_irq(host, irq, stat);
+ */
+
+ /* Command completion */
+ if (ireg & (TMIO_STAT_CMDRESPEND | TMIO_STAT_CMDTIMEOUT)) {
+ tmio_mmc_ack_mmc_irqs(host,
+ TMIO_STAT_CMDRESPEND |
+ TMIO_STAT_CMDTIMEOUT);
+ tmio_mmc_cmd_irq(host, status);
+ }
+
+ /* Data transfer */
+ if (ireg & (TMIO_STAT_RXRDY | TMIO_STAT_TXRQ)) {
+ tmio_mmc_ack_mmc_irqs(host, TMIO_STAT_RXRDY | TMIO_STAT_TXRQ);
+ tmio_mmc_pio_irq(host);
+ }
+
+ /* Data transfer completion */
+ if (ireg & TMIO_STAT_DATAEND) {
+ tmio_mmc_ack_mmc_irqs(host, TMIO_STAT_DATAEND);
+ tmio_mmc_data_irq(host);
+ }
+
+ /* Check status - keep going until we've handled it all */
+ status = sd_ctrl_read32(host, CTL_STATUS);
+ irq_mask = sd_ctrl_read32(host, CTL_IRQ_MASK);
+ ireg = status & TMIO_MASK_IRQ & ~irq_mask;
+
+ pr_debug("Status at end of loop: %08x\n", status);
+ pr_debug_status(status);
+ }
+ pr_debug("MMC IRQ end\n");
+
+out:
+ return IRQ_HANDLED;
+}
+EXPORT_SYMBOL(tmio_mmc_irq);
+
+static int tmio_mmc_start_data(struct tmio_mmc_host *host,
+ struct mmc_data *data)
+{
+ struct tmio_mmc_data *pdata = host->pdata;
+
+ pr_debug("setup data transfer: blocksize %08x nr_blocks %d\n",
+ data->blksz, data->blocks);
+
+ /* Some hardware cannot perform 2 byte requests in 4 bit mode */
+ if (host->mmc->ios.bus_width == MMC_BUS_WIDTH_4) {
+ int blksz_2bytes = pdata->flags & TMIO_MMC_BLKSZ_2BYTES;
+
+ if (data->blksz < 2 || (data->blksz < 4 && !blksz_2bytes)) {
+ pr_err("%s: %d byte block unsupported in 4 bit mode\n",
+ mmc_hostname(host->mmc), data->blksz);
+ return -EINVAL;
+ }
+ }
+
+ tmio_mmc_init_sg(host, data);
+ host->data = data;
+
+ /* Set transfer length / blocksize */
+ sd_ctrl_write16(host, CTL_SD_XFER_LEN, data->blksz);
+ sd_ctrl_write16(host, CTL_XFER_BLK_COUNT, data->blocks);
+
+ tmio_mmc_start_dma(host, data);
+
+ return 0;
+}
+
+/* Process requests from the MMC layer */
+static void tmio_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct tmio_mmc_host *host = mmc_priv(mmc);
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ if (host->mrq) {
+ pr_debug("request not null\n");
+ if (IS_ERR(host->mrq)) {
+ spin_unlock_irqrestore(&host->lock, flags);
+ mrq->cmd->error = -EAGAIN;
+ mmc_request_done(mmc, mrq);
+ return;
+ }
+ }
+
+ host->last_req_ts = jiffies;
+ wmb();
+ host->mrq = mrq;
+
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ if (mrq->data) {
+ ret = tmio_mmc_start_data(host, mrq->data);
+ if (ret)
+ goto fail;
+ }
+
+ ret = tmio_mmc_start_command(host, mrq->cmd);
+ if (!ret) {
+ schedule_delayed_work(&host->delayed_reset_work,
+ msecs_to_jiffies(2000));
+ return;
+ }
+
+fail:
+ host->force_pio = false;
+ host->mrq = NULL;
+ mrq->cmd->error = ret;
+ mmc_request_done(mmc, mrq);
+}
+
+/* Set MMC clock / power.
+ * Note: This controller uses a simple divider scheme therefore it cannot
+ * run a MMC card at full speed (20MHz). The max clock is 24MHz on SD, but as
+ * MMC wont run that fast, it has to be clocked at 12MHz which is the next
+ * slowest setting.
+ */
+static void tmio_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct tmio_mmc_host *host = mmc_priv(mmc);
+ struct tmio_mmc_data *pdata = host->pdata;
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+ if (host->mrq) {
+ if (IS_ERR(host->mrq)) {
+ dev_dbg(&host->pdev->dev,
+ "%s.%d: concurrent .set_ios(), clk %u, mode %u\n",
+ current->comm, task_pid_nr(current),
+ ios->clock, ios->power_mode);
+ host->mrq = ERR_PTR(-EINTR);
+ } else {
+ dev_dbg(&host->pdev->dev,
+ "%s.%d: CMD%u active since %lu, now %lu!\n",
+ current->comm, task_pid_nr(current),
+ host->mrq->cmd->opcode, host->last_req_ts, jiffies);
+ }
+ spin_unlock_irqrestore(&host->lock, flags);
+ return;
+ }
+
+ host->mrq = ERR_PTR(-EBUSY);
+
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ if (ios->clock)
+ tmio_mmc_set_clock(host, ios->clock);
+
+ /* Power sequence - OFF -> UP -> ON */
+ if (ios->power_mode == MMC_POWER_UP) {
+ if ((pdata->flags & TMIO_MMC_HAS_COLD_CD) && !pdata->power) {
+ pm_runtime_get_sync(&host->pdev->dev);
+ pdata->power = true;
+ }
+ /* power up SD bus */
+ if (host->set_pwr)
+ host->set_pwr(host->pdev, 1);
+ } else if (ios->power_mode == MMC_POWER_OFF || !ios->clock) {
+ /* power down SD bus */
+ if (ios->power_mode == MMC_POWER_OFF) {
+ if (host->set_pwr)
+ host->set_pwr(host->pdev, 0);
+ if ((pdata->flags & TMIO_MMC_HAS_COLD_CD) &&
+ pdata->power) {
+ pdata->power = false;
+ pm_runtime_put(&host->pdev->dev);
+ }
+ }
+ tmio_mmc_clk_stop(host);
+ } else {
+ /* start bus clock */
+ tmio_mmc_clk_start(host);
+ }
+
+ switch (ios->bus_width) {
+ case MMC_BUS_WIDTH_1:
+ sd_ctrl_write16(host, CTL_SD_MEM_CARD_OPT, 0x80e0);
+ break;
+ case MMC_BUS_WIDTH_4:
+ sd_ctrl_write16(host, CTL_SD_MEM_CARD_OPT, 0x00e0);
+ break;
+ }
+
+ /* Let things settle. delay taken from winCE driver */
+ udelay(140);
+ if (PTR_ERR(host->mrq) == -EINTR)
+ dev_dbg(&host->pdev->dev,
+ "%s.%d: IOS interrupted: clk %u, mode %u",
+ current->comm, task_pid_nr(current),
+ ios->clock, ios->power_mode);
+ host->mrq = NULL;
+}
+
+static int tmio_mmc_get_ro(struct mmc_host *mmc)
+{
+ struct tmio_mmc_host *host = mmc_priv(mmc);
+ struct tmio_mmc_data *pdata = host->pdata;
+
+ return !((pdata->flags & TMIO_MMC_WRPROTECT_DISABLE) ||
+ (sd_ctrl_read32(host, CTL_STATUS) & TMIO_STAT_WRPROTECT));
+}
+
+static int tmio_mmc_get_cd(struct mmc_host *mmc)
+{
+ struct tmio_mmc_host *host = mmc_priv(mmc);
+ struct tmio_mmc_data *pdata = host->pdata;
+
+ if (!pdata->get_cd)
+ return -ENOSYS;
+ else
+ return pdata->get_cd(host->pdev);
+}
+
+static const struct mmc_host_ops tmio_mmc_ops = {
+ .request = tmio_mmc_request,
+ .set_ios = tmio_mmc_set_ios,
+ .get_ro = tmio_mmc_get_ro,
+ .get_cd = tmio_mmc_get_cd,
+ .enable_sdio_irq = tmio_mmc_enable_sdio_irq,
+};
+
+int __devinit tmio_mmc_host_probe(struct tmio_mmc_host **host,
+ struct platform_device *pdev,
+ struct tmio_mmc_data *pdata)
+{
+ struct tmio_mmc_host *_host;
+ struct mmc_host *mmc;
+ struct resource *res_ctl;
+ int ret;
+ u32 irq_mask = TMIO_MASK_CMD;
+
+ res_ctl = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res_ctl)
+ return -EINVAL;
+
+ mmc = mmc_alloc_host(sizeof(struct tmio_mmc_host), &pdev->dev);
+ if (!mmc)
+ return -ENOMEM;
+
+ pdata->dev = &pdev->dev;
+ _host = mmc_priv(mmc);
+ _host->pdata = pdata;
+ _host->mmc = mmc;
+ _host->pdev = pdev;
+ platform_set_drvdata(pdev, mmc);
+
+ _host->set_pwr = pdata->set_pwr;
+ _host->set_clk_div = pdata->set_clk_div;
+
+ /* SD control register space size is 0x200, 0x400 for bus_shift=1 */
+ _host->bus_shift = resource_size(res_ctl) >> 10;
+
+ _host->ctl = ioremap(res_ctl->start, resource_size(res_ctl));
+ if (!_host->ctl) {
+ ret = -ENOMEM;
+ goto host_free;
+ }
+
+ mmc->ops = &tmio_mmc_ops;
+ mmc->caps = MMC_CAP_4_BIT_DATA | pdata->capabilities;
+ mmc->f_max = pdata->hclk;
+ mmc->f_min = mmc->f_max / 512;
+ mmc->max_segs = 32;
+ mmc->max_blk_size = 512;
+ mmc->max_blk_count = (PAGE_CACHE_SIZE / mmc->max_blk_size) *
+ mmc->max_segs;
+ mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
+ mmc->max_seg_size = mmc->max_req_size;
+ if (pdata->ocr_mask)
+ mmc->ocr_avail = pdata->ocr_mask;
+ else
+ mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+
+ pdata->power = false;
+ pm_runtime_enable(&pdev->dev);
+ ret = pm_runtime_resume(&pdev->dev);
+ if (ret < 0)
+ goto pm_disable;
+
+ tmio_mmc_clk_stop(_host);
+ tmio_mmc_reset(_host);
+
+ tmio_mmc_disable_mmc_irqs(_host, TMIO_MASK_ALL);
+ if (pdata->flags & TMIO_MMC_SDIO_IRQ)
+ tmio_mmc_enable_sdio_irq(mmc, 0);
+
+ spin_lock_init(&_host->lock);
+
+ /* Init delayed work for request timeouts */
+ INIT_DELAYED_WORK(&_host->delayed_reset_work, tmio_mmc_reset_work);
+
+ /* See if we also get DMA */
+ tmio_mmc_request_dma(_host, pdata);
+
+ /* We have to keep the device powered for its card detection to work */
+ if (!(pdata->flags & TMIO_MMC_HAS_COLD_CD))
+ pm_runtime_get_noresume(&pdev->dev);
+
+ mmc_add_host(mmc);
+
+ /* Unmask the IRQs we want to know about */
+ if (!_host->chan_rx)
+ irq_mask |= TMIO_MASK_READOP;
+ if (!_host->chan_tx)
+ irq_mask |= TMIO_MASK_WRITEOP;
+
+ tmio_mmc_enable_mmc_irqs(_host, irq_mask);
+
+ *host = _host;
+
+ return 0;
+
+pm_disable:
+ pm_runtime_disable(&pdev->dev);
+ iounmap(_host->ctl);
+host_free:
+ mmc_free_host(mmc);
+
+ return ret;
+}
+EXPORT_SYMBOL(tmio_mmc_host_probe);
+
+void tmio_mmc_host_remove(struct tmio_mmc_host *host)
+{
+ struct platform_device *pdev = host->pdev;
+
+ /*
+ * We don't have to manipulate pdata->power here: if there is a card in
+ * the slot, the runtime PM is active and our .runtime_resume() will not
+ * be run. If there is no card in the slot and the platform can suspend
+ * the controller, the runtime PM is suspended and pdata->power == false,
+ * so, our .runtime_resume() will not try to detect a card in the slot.
+ */
+ if (host->pdata->flags & TMIO_MMC_HAS_COLD_CD)
+ pm_runtime_get_sync(&pdev->dev);
+
+ mmc_remove_host(host->mmc);
+ cancel_delayed_work_sync(&host->delayed_reset_work);
+ tmio_mmc_release_dma(host);
+
+ pm_runtime_put_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ iounmap(host->ctl);
+ mmc_free_host(host->mmc);
+}
+EXPORT_SYMBOL(tmio_mmc_host_remove);
+
+#ifdef CONFIG_PM
+int tmio_mmc_host_suspend(struct device *dev)
+{
+ struct mmc_host *mmc = dev_get_drvdata(dev);
+ struct tmio_mmc_host *host = mmc_priv(mmc);
+ int ret = mmc_suspend_host(mmc);
+
+ if (!ret)
+ tmio_mmc_disable_mmc_irqs(host, TMIO_MASK_ALL);
+
+ host->pm_error = pm_runtime_put_sync(dev);
+
+ return ret;
+}
+EXPORT_SYMBOL(tmio_mmc_host_suspend);
+
+int tmio_mmc_host_resume(struct device *dev)
+{
+ struct mmc_host *mmc = dev_get_drvdata(dev);
+ struct tmio_mmc_host *host = mmc_priv(mmc);
+
+ /* The MMC core will perform the complete set up */
+ host->pdata->power = false;
+
+ if (!host->pm_error)
+ pm_runtime_get_sync(dev);
+
+ tmio_mmc_reset(mmc_priv(mmc));
+ tmio_mmc_request_dma(host, host->pdata);
+
+ return mmc_resume_host(mmc);
+}
+EXPORT_SYMBOL(tmio_mmc_host_resume);
+
+#endif /* CONFIG_PM */
+
+int tmio_mmc_host_runtime_suspend(struct device *dev)
+{
+ return 0;
+}
+EXPORT_SYMBOL(tmio_mmc_host_runtime_suspend);
+
+int tmio_mmc_host_runtime_resume(struct device *dev)
+{
+ struct mmc_host *mmc = dev_get_drvdata(dev);
+ struct tmio_mmc_host *host = mmc_priv(mmc);
+ struct tmio_mmc_data *pdata = host->pdata;
+
+ tmio_mmc_reset(host);
+
+ if (pdata->power) {
+ /* Only entered after a card-insert interrupt */
+ tmio_mmc_set_ios(mmc, &mmc->ios);
+ mmc_detect_change(mmc, msecs_to_jiffies(100));
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(tmio_mmc_host_runtime_resume);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mmc/host/ushc.c b/drivers/mmc/host/ushc.c
new file mode 100644
index 00000000..f08f944a
--- /dev/null
+++ b/drivers/mmc/host/ushc.c
@@ -0,0 +1,579 @@
+/*
+ * USB SD Host Controller (USHC) controller driver.
+ *
+ * Copyright (C) 2010 Cambridge Silicon Radio Ltd.
+ *
+ * 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.
+ *
+ * Notes:
+ * - Only version 2 devices are supported.
+ * - Version 2 devices only support SDIO cards/devices (R2 response is
+ * unsupported).
+ *
+ * References:
+ * [USHC] USB SD Host Controller specification (CS-118793-SP)
+ */
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/mmc/host.h>
+
+enum ushc_request {
+ USHC_GET_CAPS = 0x00,
+ USHC_HOST_CTRL = 0x01,
+ USHC_PWR_CTRL = 0x02,
+ USHC_CLK_FREQ = 0x03,
+ USHC_EXEC_CMD = 0x04,
+ USHC_READ_RESP = 0x05,
+ USHC_RESET = 0x06,
+};
+
+enum ushc_request_type {
+ USHC_GET_CAPS_TYPE = USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ USHC_HOST_CTRL_TYPE = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ USHC_PWR_CTRL_TYPE = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ USHC_CLK_FREQ_TYPE = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ USHC_EXEC_CMD_TYPE = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ USHC_READ_RESP_TYPE = USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ USHC_RESET_TYPE = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+};
+
+#define USHC_GET_CAPS_VERSION_MASK 0xff
+#define USHC_GET_CAPS_3V3 (1 << 8)
+#define USHC_GET_CAPS_3V0 (1 << 9)
+#define USHC_GET_CAPS_1V8 (1 << 10)
+#define USHC_GET_CAPS_HIGH_SPD (1 << 16)
+
+#define USHC_HOST_CTRL_4BIT (1 << 1)
+#define USHC_HOST_CTRL_HIGH_SPD (1 << 0)
+
+#define USHC_PWR_CTRL_OFF 0x00
+#define USHC_PWR_CTRL_3V3 0x01
+#define USHC_PWR_CTRL_3V0 0x02
+#define USHC_PWR_CTRL_1V8 0x03
+
+#define USHC_READ_RESP_BUSY (1 << 4)
+#define USHC_READ_RESP_ERR_TIMEOUT (1 << 3)
+#define USHC_READ_RESP_ERR_CRC (1 << 2)
+#define USHC_READ_RESP_ERR_DAT (1 << 1)
+#define USHC_READ_RESP_ERR_CMD (1 << 0)
+#define USHC_READ_RESP_ERR_MASK 0x0f
+
+struct ushc_cbw {
+ __u8 signature;
+ __u8 cmd_idx;
+ __le16 block_size;
+ __le32 arg;
+} __attribute__((packed));
+
+#define USHC_CBW_SIGNATURE 'C'
+
+struct ushc_csw {
+ __u8 signature;
+ __u8 status;
+ __le32 response;
+} __attribute__((packed));
+
+#define USHC_CSW_SIGNATURE 'S'
+
+struct ushc_int_data {
+ u8 status;
+ u8 reserved[3];
+};
+
+#define USHC_INT_STATUS_SDIO_INT (1 << 1)
+#define USHC_INT_STATUS_CARD_PRESENT (1 << 0)
+
+
+struct ushc_data {
+ struct usb_device *usb_dev;
+ struct mmc_host *mmc;
+
+ struct urb *int_urb;
+ struct ushc_int_data *int_data;
+
+ struct urb *cbw_urb;
+ struct ushc_cbw *cbw;
+
+ struct urb *data_urb;
+
+ struct urb *csw_urb;
+ struct ushc_csw *csw;
+
+ spinlock_t lock;
+ struct mmc_request *current_req;
+ u32 caps;
+ u16 host_ctrl;
+ unsigned long flags;
+ u8 last_status;
+ int clock_freq;
+};
+
+#define DISCONNECTED 0
+#define INT_EN 1
+#define IGNORE_NEXT_INT 2
+
+static void data_callback(struct urb *urb);
+
+static int ushc_hw_reset(struct ushc_data *ushc)
+{
+ return usb_control_msg(ushc->usb_dev, usb_sndctrlpipe(ushc->usb_dev, 0),
+ USHC_RESET, USHC_RESET_TYPE,
+ 0, 0, NULL, 0, 100);
+}
+
+static int ushc_hw_get_caps(struct ushc_data *ushc)
+{
+ int ret;
+ int version;
+
+ ret = usb_control_msg(ushc->usb_dev, usb_rcvctrlpipe(ushc->usb_dev, 0),
+ USHC_GET_CAPS, USHC_GET_CAPS_TYPE,
+ 0, 0, &ushc->caps, sizeof(ushc->caps), 100);
+ if (ret < 0)
+ return ret;
+
+ ushc->caps = le32_to_cpu(ushc->caps);
+
+ version = ushc->caps & USHC_GET_CAPS_VERSION_MASK;
+ if (version != 0x02) {
+ dev_err(&ushc->usb_dev->dev, "controller version %d is not supported\n", version);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ushc_hw_set_host_ctrl(struct ushc_data *ushc, u16 mask, u16 val)
+{
+ u16 host_ctrl;
+ int ret;
+
+ host_ctrl = (ushc->host_ctrl & ~mask) | val;
+ ret = usb_control_msg(ushc->usb_dev, usb_sndctrlpipe(ushc->usb_dev, 0),
+ USHC_HOST_CTRL, USHC_HOST_CTRL_TYPE,
+ host_ctrl, 0, NULL, 0, 100);
+ if (ret < 0)
+ return ret;
+ ushc->host_ctrl = host_ctrl;
+ return 0;
+}
+
+static void int_callback(struct urb *urb)
+{
+ struct ushc_data *ushc = urb->context;
+ u8 status, last_status;
+
+ if (urb->status < 0)
+ return;
+
+ status = ushc->int_data->status;
+ last_status = ushc->last_status;
+ ushc->last_status = status;
+
+ /*
+ * Ignore the card interrupt status on interrupt transfers that
+ * were submitted while card interrupts where disabled.
+ *
+ * This avoid occasional spurious interrupts when enabling
+ * interrupts immediately after clearing the source on the card.
+ */
+
+ if (!test_and_clear_bit(IGNORE_NEXT_INT, &ushc->flags)
+ && test_bit(INT_EN, &ushc->flags)
+ && status & USHC_INT_STATUS_SDIO_INT) {
+ mmc_signal_sdio_irq(ushc->mmc);
+ }
+
+ if ((status ^ last_status) & USHC_INT_STATUS_CARD_PRESENT)
+ mmc_detect_change(ushc->mmc, msecs_to_jiffies(100));
+
+ if (!test_bit(INT_EN, &ushc->flags))
+ set_bit(IGNORE_NEXT_INT, &ushc->flags);
+ usb_submit_urb(ushc->int_urb, GFP_ATOMIC);
+}
+
+static void cbw_callback(struct urb *urb)
+{
+ struct ushc_data *ushc = urb->context;
+
+ if (urb->status != 0) {
+ usb_unlink_urb(ushc->data_urb);
+ usb_unlink_urb(ushc->csw_urb);
+ }
+}
+
+static void data_callback(struct urb *urb)
+{
+ struct ushc_data *ushc = urb->context;
+
+ if (urb->status != 0)
+ usb_unlink_urb(ushc->csw_urb);
+}
+
+static void csw_callback(struct urb *urb)
+{
+ struct ushc_data *ushc = urb->context;
+ struct mmc_request *req = ushc->current_req;
+ int status;
+
+ status = ushc->csw->status;
+
+ if (urb->status != 0) {
+ req->cmd->error = urb->status;
+ } else if (status & USHC_READ_RESP_ERR_CMD) {
+ if (status & USHC_READ_RESP_ERR_CRC)
+ req->cmd->error = -EIO;
+ else
+ req->cmd->error = -ETIMEDOUT;
+ }
+ if (req->data) {
+ if (status & USHC_READ_RESP_ERR_DAT) {
+ if (status & USHC_READ_RESP_ERR_CRC)
+ req->data->error = -EIO;
+ else
+ req->data->error = -ETIMEDOUT;
+ req->data->bytes_xfered = 0;
+ } else {
+ req->data->bytes_xfered = req->data->blksz * req->data->blocks;
+ }
+ }
+
+ req->cmd->resp[0] = le32_to_cpu(ushc->csw->response);
+
+ mmc_request_done(ushc->mmc, req);
+}
+
+static void ushc_request(struct mmc_host *mmc, struct mmc_request *req)
+{
+ struct ushc_data *ushc = mmc_priv(mmc);
+ int ret;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ushc->lock, flags);
+
+ if (test_bit(DISCONNECTED, &ushc->flags)) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ /* Version 2 firmware doesn't support the R2 response format. */
+ if (req->cmd->flags & MMC_RSP_136) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* The Astoria's data FIFOs don't work with clock speeds < 5MHz so
+ limit commands with data to 6MHz or more. */
+ if (req->data && ushc->clock_freq < 6000000) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ushc->current_req = req;
+
+ /* Start cmd with CBW. */
+ ushc->cbw->cmd_idx = cpu_to_le16(req->cmd->opcode);
+ if (req->data)
+ ushc->cbw->block_size = cpu_to_le16(req->data->blksz);
+ else
+ ushc->cbw->block_size = 0;
+ ushc->cbw->arg = cpu_to_le32(req->cmd->arg);
+
+ ret = usb_submit_urb(ushc->cbw_urb, GFP_ATOMIC);
+ if (ret < 0)
+ goto out;
+
+ /* Submit data (if any). */
+ if (req->data) {
+ struct mmc_data *data = req->data;
+ int pipe;
+
+ if (data->flags & MMC_DATA_READ)
+ pipe = usb_rcvbulkpipe(ushc->usb_dev, 6);
+ else
+ pipe = usb_sndbulkpipe(ushc->usb_dev, 2);
+
+ usb_fill_bulk_urb(ushc->data_urb, ushc->usb_dev, pipe,
+ sg_virt(data->sg), data->sg->length,
+ data_callback, ushc);
+ ret = usb_submit_urb(ushc->data_urb, GFP_ATOMIC);
+ if (ret < 0)
+ goto out;
+ }
+
+ /* Submit CSW. */
+ ret = usb_submit_urb(ushc->csw_urb, GFP_ATOMIC);
+ if (ret < 0)
+ goto out;
+
+out:
+ spin_unlock_irqrestore(&ushc->lock, flags);
+ if (ret < 0) {
+ usb_unlink_urb(ushc->cbw_urb);
+ usb_unlink_urb(ushc->data_urb);
+ req->cmd->error = ret;
+ mmc_request_done(mmc, req);
+ }
+}
+
+static int ushc_set_power(struct ushc_data *ushc, unsigned char power_mode)
+{
+ u16 voltage;
+
+ switch (power_mode) {
+ case MMC_POWER_OFF:
+ voltage = USHC_PWR_CTRL_OFF;
+ break;
+ case MMC_POWER_UP:
+ case MMC_POWER_ON:
+ voltage = USHC_PWR_CTRL_3V3;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return usb_control_msg(ushc->usb_dev, usb_sndctrlpipe(ushc->usb_dev, 0),
+ USHC_PWR_CTRL, USHC_PWR_CTRL_TYPE,
+ voltage, 0, NULL, 0, 100);
+}
+
+static int ushc_set_bus_width(struct ushc_data *ushc, int bus_width)
+{
+ return ushc_hw_set_host_ctrl(ushc, USHC_HOST_CTRL_4BIT,
+ bus_width == 4 ? USHC_HOST_CTRL_4BIT : 0);
+}
+
+static int ushc_set_bus_freq(struct ushc_data *ushc, int clk, bool enable_hs)
+{
+ int ret;
+
+ /* Hardware can't detect interrupts while the clock is off. */
+ if (clk == 0)
+ clk = 400000;
+
+ ret = ushc_hw_set_host_ctrl(ushc, USHC_HOST_CTRL_HIGH_SPD,
+ enable_hs ? USHC_HOST_CTRL_HIGH_SPD : 0);
+ if (ret < 0)
+ return ret;
+
+ ret = usb_control_msg(ushc->usb_dev, usb_sndctrlpipe(ushc->usb_dev, 0),
+ USHC_CLK_FREQ, USHC_CLK_FREQ_TYPE,
+ clk & 0xffff, (clk >> 16) & 0xffff, NULL, 0, 100);
+ if (ret < 0)
+ return ret;
+
+ ushc->clock_freq = clk;
+ return 0;
+}
+
+static void ushc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct ushc_data *ushc = mmc_priv(mmc);
+
+ ushc_set_power(ushc, ios->power_mode);
+ ushc_set_bus_width(ushc, 1 << ios->bus_width);
+ ushc_set_bus_freq(ushc, ios->clock, ios->timing == MMC_TIMING_SD_HS);
+}
+
+static int ushc_get_cd(struct mmc_host *mmc)
+{
+ struct ushc_data *ushc = mmc_priv(mmc);
+
+ return !!(ushc->last_status & USHC_INT_STATUS_CARD_PRESENT);
+}
+
+static void ushc_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+ struct ushc_data *ushc = mmc_priv(mmc);
+
+ if (enable)
+ set_bit(INT_EN, &ushc->flags);
+ else
+ clear_bit(INT_EN, &ushc->flags);
+}
+
+static void ushc_clean_up(struct ushc_data *ushc)
+{
+ usb_free_urb(ushc->int_urb);
+ usb_free_urb(ushc->csw_urb);
+ usb_free_urb(ushc->data_urb);
+ usb_free_urb(ushc->cbw_urb);
+
+ kfree(ushc->int_data);
+ kfree(ushc->cbw);
+ kfree(ushc->csw);
+
+ mmc_free_host(ushc->mmc);
+}
+
+static const struct mmc_host_ops ushc_ops = {
+ .request = ushc_request,
+ .set_ios = ushc_set_ios,
+ .get_cd = ushc_get_cd,
+ .enable_sdio_irq = ushc_enable_sdio_irq,
+};
+
+static int ushc_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+ struct usb_device *usb_dev = interface_to_usbdev(intf);
+ struct mmc_host *mmc;
+ struct ushc_data *ushc;
+ int ret;
+
+ mmc = mmc_alloc_host(sizeof(struct ushc_data), &intf->dev);
+ if (mmc == NULL)
+ return -ENOMEM;
+ ushc = mmc_priv(mmc);
+ usb_set_intfdata(intf, ushc);
+
+ ushc->usb_dev = usb_dev;
+ ushc->mmc = mmc;
+
+ spin_lock_init(&ushc->lock);
+
+ ret = ushc_hw_reset(ushc);
+ if (ret < 0)
+ goto err;
+
+ /* Read capabilities. */
+ ret = ushc_hw_get_caps(ushc);
+ if (ret < 0)
+ goto err;
+
+ mmc->ops = &ushc_ops;
+
+ mmc->f_min = 400000;
+ mmc->f_max = 50000000;
+ mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+ mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;
+ mmc->caps |= (ushc->caps & USHC_GET_CAPS_HIGH_SPD) ? MMC_CAP_SD_HIGHSPEED : 0;
+
+ mmc->max_seg_size = 512*511;
+ mmc->max_segs = 1;
+ mmc->max_req_size = 512*511;
+ mmc->max_blk_size = 512;
+ mmc->max_blk_count = 511;
+
+ ushc->int_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (ushc->int_urb == NULL) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ ushc->int_data = kzalloc(sizeof(struct ushc_int_data), GFP_KERNEL);
+ if (ushc->int_data == NULL) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ usb_fill_int_urb(ushc->int_urb, ushc->usb_dev,
+ usb_rcvintpipe(usb_dev,
+ intf->cur_altsetting->endpoint[0].desc.bEndpointAddress),
+ ushc->int_data, sizeof(struct ushc_int_data),
+ int_callback, ushc,
+ intf->cur_altsetting->endpoint[0].desc.bInterval);
+
+ ushc->cbw_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (ushc->cbw_urb == NULL) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ ushc->cbw = kzalloc(sizeof(struct ushc_cbw), GFP_KERNEL);
+ if (ushc->cbw == NULL) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ ushc->cbw->signature = USHC_CBW_SIGNATURE;
+
+ usb_fill_bulk_urb(ushc->cbw_urb, ushc->usb_dev, usb_sndbulkpipe(usb_dev, 2),
+ ushc->cbw, sizeof(struct ushc_cbw),
+ cbw_callback, ushc);
+
+ ushc->data_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (ushc->data_urb == NULL) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ ushc->csw_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (ushc->csw_urb == NULL) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ ushc->csw = kzalloc(sizeof(struct ushc_cbw), GFP_KERNEL);
+ if (ushc->csw == NULL) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ usb_fill_bulk_urb(ushc->csw_urb, ushc->usb_dev, usb_rcvbulkpipe(usb_dev, 6),
+ ushc->csw, sizeof(struct ushc_csw),
+ csw_callback, ushc);
+
+ ret = mmc_add_host(ushc->mmc);
+ if (ret)
+ goto err;
+
+ ret = usb_submit_urb(ushc->int_urb, GFP_KERNEL);
+ if (ret < 0) {
+ mmc_remove_host(ushc->mmc);
+ goto err;
+ }
+
+ return 0;
+
+err:
+ ushc_clean_up(ushc);
+ return ret;
+}
+
+static void ushc_disconnect(struct usb_interface *intf)
+{
+ struct ushc_data *ushc = usb_get_intfdata(intf);
+
+ spin_lock_irq(&ushc->lock);
+ set_bit(DISCONNECTED, &ushc->flags);
+ spin_unlock_irq(&ushc->lock);
+
+ usb_kill_urb(ushc->int_urb);
+ usb_kill_urb(ushc->cbw_urb);
+ usb_kill_urb(ushc->data_urb);
+ usb_kill_urb(ushc->csw_urb);
+
+ mmc_remove_host(ushc->mmc);
+
+ ushc_clean_up(ushc);
+}
+
+static struct usb_device_id ushc_id_table[] = {
+ /* CSR USB SD Host Controller */
+ { USB_DEVICE(0x0a12, 0x5d10) },
+ { },
+};
+MODULE_DEVICE_TABLE(usb, ushc_id_table);
+
+static struct usb_driver ushc_driver = {
+ .name = "ushc",
+ .id_table = ushc_id_table,
+ .probe = ushc_probe,
+ .disconnect = ushc_disconnect,
+};
+
+static int __init ushc_init(void)
+{
+ return usb_register(&ushc_driver);
+}
+module_init(ushc_init);
+
+static void __exit ushc_exit(void)
+{
+ usb_deregister(&ushc_driver);
+}
+module_exit(ushc_exit);
+
+MODULE_DESCRIPTION("USB SD Host Controller driver");
+MODULE_AUTHOR("David Vrabel <david.vrabel@csr.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mmc/host/via-sdmmc.c b/drivers/mmc/host/via-sdmmc.c
new file mode 100644
index 00000000..4dfe2c02
--- /dev/null
+++ b/drivers/mmc/host/via-sdmmc.c
@@ -0,0 +1,1357 @@
+/*
+ * drivers/mmc/host/via-sdmmc.c - VIA SD/MMC Card Reader driver
+ * Copyright (c) 2008, VIA Technologies Inc. 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.
+ */
+
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/highmem.h>
+#include <linux/delay.h>
+
+#include <linux/mmc/host.h>
+
+#define DRV_NAME "via_sdmmc"
+
+#define PCI_DEVICE_ID_VIA_9530 0x9530
+
+#define VIA_CRDR_SDC_OFF 0x200
+#define VIA_CRDR_DDMA_OFF 0x400
+#define VIA_CRDR_PCICTRL_OFF 0x600
+
+#define VIA_CRDR_MIN_CLOCK 375000
+#define VIA_CRDR_MAX_CLOCK 48000000
+
+/*
+ * PCI registers
+ */
+
+#define VIA_CRDR_PCI_WORK_MODE 0x40
+#define VIA_CRDR_PCI_DBG_MODE 0x41
+
+/*
+ * SDC MMIO Registers
+ */
+
+#define VIA_CRDR_SDCTRL 0x0
+#define VIA_CRDR_SDCTRL_START 0x01
+#define VIA_CRDR_SDCTRL_WRITE 0x04
+#define VIA_CRDR_SDCTRL_SINGLE_WR 0x10
+#define VIA_CRDR_SDCTRL_SINGLE_RD 0x20
+#define VIA_CRDR_SDCTRL_MULTI_WR 0x30
+#define VIA_CRDR_SDCTRL_MULTI_RD 0x40
+#define VIA_CRDR_SDCTRL_STOP 0x70
+
+#define VIA_CRDR_SDCTRL_RSP_NONE 0x0
+#define VIA_CRDR_SDCTRL_RSP_R1 0x10000
+#define VIA_CRDR_SDCTRL_RSP_R2 0x20000
+#define VIA_CRDR_SDCTRL_RSP_R3 0x30000
+#define VIA_CRDR_SDCTRL_RSP_R1B 0x90000
+
+#define VIA_CRDR_SDCARG 0x4
+
+#define VIA_CRDR_SDBUSMODE 0x8
+#define VIA_CRDR_SDMODE_4BIT 0x02
+#define VIA_CRDR_SDMODE_CLK_ON 0x40
+
+#define VIA_CRDR_SDBLKLEN 0xc
+/*
+ * Bit 0 -Bit 10 : Block length. So, the maximum block length should be 2048.
+ * Bit 11 - Bit 13 : Reserved.
+ * GPIDET : Select GPI pin to detect card, GPI means CR_CD# in top design.
+ * INTEN : Enable SD host interrupt.
+ * Bit 16 - Bit 31 : Block count. So, the maximun block count should be 65536.
+ */
+#define VIA_CRDR_SDBLKLEN_GPIDET 0x2000
+#define VIA_CRDR_SDBLKLEN_INTEN 0x8000
+#define VIA_CRDR_MAX_BLOCK_COUNT 65536
+#define VIA_CRDR_MAX_BLOCK_LENGTH 2048
+
+#define VIA_CRDR_SDRESP0 0x10
+#define VIA_CRDR_SDRESP1 0x14
+#define VIA_CRDR_SDRESP2 0x18
+#define VIA_CRDR_SDRESP3 0x1c
+
+#define VIA_CRDR_SDCURBLKCNT 0x20
+
+#define VIA_CRDR_SDINTMASK 0x24
+/*
+ * MBDIE : Multiple Blocks transfer Done Interrupt Enable
+ * BDDIE : Block Data transfer Done Interrupt Enable
+ * CIRIE : Card Insertion or Removal Interrupt Enable
+ * CRDIE : Command-Response transfer Done Interrupt Enable
+ * CRTOIE : Command-Response response TimeOut Interrupt Enable
+ * ASCRDIE : Auto Stop Command-Response transfer Done Interrupt Enable
+ * DTIE : Data access Timeout Interrupt Enable
+ * SCIE : reSponse CRC error Interrupt Enable
+ * RCIE : Read data CRC error Interrupt Enable
+ * WCIE : Write data CRC error Interrupt Enable
+ */
+#define VIA_CRDR_SDINTMASK_MBDIE 0x10
+#define VIA_CRDR_SDINTMASK_BDDIE 0x20
+#define VIA_CRDR_SDINTMASK_CIRIE 0x80
+#define VIA_CRDR_SDINTMASK_CRDIE 0x200
+#define VIA_CRDR_SDINTMASK_CRTOIE 0x400
+#define VIA_CRDR_SDINTMASK_ASCRDIE 0x800
+#define VIA_CRDR_SDINTMASK_DTIE 0x1000
+#define VIA_CRDR_SDINTMASK_SCIE 0x2000
+#define VIA_CRDR_SDINTMASK_RCIE 0x4000
+#define VIA_CRDR_SDINTMASK_WCIE 0x8000
+
+#define VIA_CRDR_SDACTIVE_INTMASK \
+ (VIA_CRDR_SDINTMASK_MBDIE | VIA_CRDR_SDINTMASK_CIRIE \
+ | VIA_CRDR_SDINTMASK_CRDIE | VIA_CRDR_SDINTMASK_CRTOIE \
+ | VIA_CRDR_SDINTMASK_DTIE | VIA_CRDR_SDINTMASK_SCIE \
+ | VIA_CRDR_SDINTMASK_RCIE | VIA_CRDR_SDINTMASK_WCIE)
+
+#define VIA_CRDR_SDSTATUS 0x28
+/*
+ * CECC : Reserved
+ * WP : SD card Write Protect status
+ * SLOTD : Reserved
+ * SLOTG : SD SLOT status(Gpi pin status)
+ * MBD : Multiple Blocks transfer Done interrupt status
+ * BDD : Block Data transfer Done interrupt status
+ * CD : Reserved
+ * CIR : Card Insertion or Removal interrupt detected on GPI pin
+ * IO : Reserved
+ * CRD : Command-Response transfer Done interrupt status
+ * CRTO : Command-Response response TimeOut interrupt status
+ * ASCRDIE : Auto Stop Command-Response transfer Done interrupt status
+ * DT : Data access Timeout interrupt status
+ * SC : reSponse CRC error interrupt status
+ * RC : Read data CRC error interrupt status
+ * WC : Write data CRC error interrupt status
+ */
+#define VIA_CRDR_SDSTS_CECC 0x01
+#define VIA_CRDR_SDSTS_WP 0x02
+#define VIA_CRDR_SDSTS_SLOTD 0x04
+#define VIA_CRDR_SDSTS_SLOTG 0x08
+#define VIA_CRDR_SDSTS_MBD 0x10
+#define VIA_CRDR_SDSTS_BDD 0x20
+#define VIA_CRDR_SDSTS_CD 0x40
+#define VIA_CRDR_SDSTS_CIR 0x80
+#define VIA_CRDR_SDSTS_IO 0x100
+#define VIA_CRDR_SDSTS_CRD 0x200
+#define VIA_CRDR_SDSTS_CRTO 0x400
+#define VIA_CRDR_SDSTS_ASCRDIE 0x800
+#define VIA_CRDR_SDSTS_DT 0x1000
+#define VIA_CRDR_SDSTS_SC 0x2000
+#define VIA_CRDR_SDSTS_RC 0x4000
+#define VIA_CRDR_SDSTS_WC 0x8000
+
+#define VIA_CRDR_SDSTS_IGN_MASK\
+ (VIA_CRDR_SDSTS_BDD | VIA_CRDR_SDSTS_ASCRDIE | VIA_CRDR_SDSTS_IO)
+#define VIA_CRDR_SDSTS_INT_MASK \
+ (VIA_CRDR_SDSTS_MBD | VIA_CRDR_SDSTS_BDD | VIA_CRDR_SDSTS_CD \
+ | VIA_CRDR_SDSTS_CIR | VIA_CRDR_SDSTS_IO | VIA_CRDR_SDSTS_CRD \
+ | VIA_CRDR_SDSTS_CRTO | VIA_CRDR_SDSTS_ASCRDIE | VIA_CRDR_SDSTS_DT \
+ | VIA_CRDR_SDSTS_SC | VIA_CRDR_SDSTS_RC | VIA_CRDR_SDSTS_WC)
+#define VIA_CRDR_SDSTS_W1C_MASK \
+ (VIA_CRDR_SDSTS_CECC | VIA_CRDR_SDSTS_MBD | VIA_CRDR_SDSTS_BDD \
+ | VIA_CRDR_SDSTS_CD | VIA_CRDR_SDSTS_CIR | VIA_CRDR_SDSTS_CRD \
+ | VIA_CRDR_SDSTS_CRTO | VIA_CRDR_SDSTS_ASCRDIE | VIA_CRDR_SDSTS_DT \
+ | VIA_CRDR_SDSTS_SC | VIA_CRDR_SDSTS_RC | VIA_CRDR_SDSTS_WC)
+#define VIA_CRDR_SDSTS_CMD_MASK \
+ (VIA_CRDR_SDSTS_CRD | VIA_CRDR_SDSTS_CRTO | VIA_CRDR_SDSTS_SC)
+#define VIA_CRDR_SDSTS_DATA_MASK\
+ (VIA_CRDR_SDSTS_MBD | VIA_CRDR_SDSTS_DT \
+ | VIA_CRDR_SDSTS_RC | VIA_CRDR_SDSTS_WC)
+
+#define VIA_CRDR_SDSTATUS2 0x2a
+/*
+ * CFE : Enable SD host automatic Clock FReezing
+ */
+#define VIA_CRDR_SDSTS_CFE 0x80
+
+#define VIA_CRDR_SDRSPTMO 0x2C
+
+#define VIA_CRDR_SDCLKSEL 0x30
+
+#define VIA_CRDR_SDEXTCTRL 0x34
+#define VIS_CRDR_SDEXTCTRL_AUTOSTOP_SD 0x01
+#define VIS_CRDR_SDEXTCTRL_SHIFT_9 0x02
+#define VIS_CRDR_SDEXTCTRL_MMC_8BIT 0x04
+#define VIS_CRDR_SDEXTCTRL_RELD_BLK 0x08
+#define VIS_CRDR_SDEXTCTRL_BAD_CMDA 0x10
+#define VIS_CRDR_SDEXTCTRL_BAD_DATA 0x20
+#define VIS_CRDR_SDEXTCTRL_AUTOSTOP_SPI 0x40
+#define VIA_CRDR_SDEXTCTRL_HISPD 0x80
+/* 0x38-0xFF reserved */
+
+/*
+ * Data DMA Control Registers
+ */
+
+#define VIA_CRDR_DMABASEADD 0x0
+#define VIA_CRDR_DMACOUNTER 0x4
+
+#define VIA_CRDR_DMACTRL 0x8
+/*
+ * DIR :Transaction Direction
+ * 0 : From card to memory
+ * 1 : From memory to card
+ */
+#define VIA_CRDR_DMACTRL_DIR 0x100
+#define VIA_CRDR_DMACTRL_ENIRQ 0x10000
+#define VIA_CRDR_DMACTRL_SFTRST 0x1000000
+
+#define VIA_CRDR_DMASTS 0xc
+
+#define VIA_CRDR_DMASTART 0x10
+/*0x14-0xFF reserved*/
+
+/*
+ * PCI Control Registers
+ */
+
+/*0x0 - 0x1 reserved*/
+#define VIA_CRDR_PCICLKGATT 0x2
+/*
+ * SFTRST :
+ * 0 : Soft reset all the controller and it will be de-asserted automatically
+ * 1 : Soft reset is de-asserted
+ */
+#define VIA_CRDR_PCICLKGATT_SFTRST 0x01
+/*
+ * 3V3 : Pad power select
+ * 0 : 1.8V
+ * 1 : 3.3V
+ * NOTE : No mater what the actual value should be, this bit always
+ * read as 0. This is a hardware bug.
+ */
+#define VIA_CRDR_PCICLKGATT_3V3 0x10
+/*
+ * PAD_PWRON : Pad Power on/off select
+ * 0 : Power off
+ * 1 : Power on
+ * NOTE : No mater what the actual value should be, this bit always
+ * read as 0. This is a hardware bug.
+ */
+#define VIA_CRDR_PCICLKGATT_PAD_PWRON 0x20
+
+#define VIA_CRDR_PCISDCCLK 0x5
+
+#define VIA_CRDR_PCIDMACLK 0x7
+#define VIA_CRDR_PCIDMACLK_SDC 0x2
+
+#define VIA_CRDR_PCIINTCTRL 0x8
+#define VIA_CRDR_PCIINTCTRL_SDCIRQEN 0x04
+
+#define VIA_CRDR_PCIINTSTATUS 0x9
+#define VIA_CRDR_PCIINTSTATUS_SDC 0x04
+
+#define VIA_CRDR_PCITMOCTRL 0xa
+#define VIA_CRDR_PCITMOCTRL_NO 0x0
+#define VIA_CRDR_PCITMOCTRL_32US 0x1
+#define VIA_CRDR_PCITMOCTRL_256US 0x2
+#define VIA_CRDR_PCITMOCTRL_1024US 0x3
+#define VIA_CRDR_PCITMOCTRL_256MS 0x4
+#define VIA_CRDR_PCITMOCTRL_512MS 0x5
+#define VIA_CRDR_PCITMOCTRL_1024MS 0x6
+
+/*0xB-0xFF reserved*/
+
+enum PCI_HOST_CLK_CONTROL {
+ PCI_CLK_375K = 0x03,
+ PCI_CLK_8M = 0x04,
+ PCI_CLK_12M = 0x00,
+ PCI_CLK_16M = 0x05,
+ PCI_CLK_24M = 0x01,
+ PCI_CLK_33M = 0x06,
+ PCI_CLK_48M = 0x02
+};
+
+struct sdhcreg {
+ u32 sdcontrol_reg;
+ u32 sdcmdarg_reg;
+ u32 sdbusmode_reg;
+ u32 sdblklen_reg;
+ u32 sdresp_reg[4];
+ u32 sdcurblkcnt_reg;
+ u32 sdintmask_reg;
+ u32 sdstatus_reg;
+ u32 sdrsptmo_reg;
+ u32 sdclksel_reg;
+ u32 sdextctrl_reg;
+};
+
+struct pcictrlreg {
+ u8 reserve[2];
+ u8 pciclkgat_reg;
+ u8 pcinfcclk_reg;
+ u8 pcimscclk_reg;
+ u8 pcisdclk_reg;
+ u8 pcicaclk_reg;
+ u8 pcidmaclk_reg;
+ u8 pciintctrl_reg;
+ u8 pciintstatus_reg;
+ u8 pcitmoctrl_reg;
+ u8 Resv;
+};
+
+struct via_crdr_mmc_host {
+ struct mmc_host *mmc;
+ struct mmc_request *mrq;
+ struct mmc_command *cmd;
+ struct mmc_data *data;
+
+ void __iomem *mmiobase;
+ void __iomem *sdhc_mmiobase;
+ void __iomem *ddma_mmiobase;
+ void __iomem *pcictrl_mmiobase;
+
+ struct pcictrlreg pm_pcictrl_reg;
+ struct sdhcreg pm_sdhc_reg;
+
+ struct work_struct carddet_work;
+ struct tasklet_struct finish_tasklet;
+
+ struct timer_list timer;
+ spinlock_t lock;
+ u8 power;
+ int reject;
+ unsigned int quirks;
+};
+
+/* some devices need a very long delay for power to stabilize */
+#define VIA_CRDR_QUIRK_300MS_PWRDELAY 0x0001
+
+static struct pci_device_id via_ids[] = {
+ {PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_9530,
+ PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0,},
+ {0,}
+};
+
+MODULE_DEVICE_TABLE(pci, via_ids);
+
+static void via_print_sdchc(struct via_crdr_mmc_host *host)
+{
+ void __iomem *addrbase = host->sdhc_mmiobase;
+
+ pr_debug("SDC MMIO Registers:\n");
+ pr_debug("SDCONTROL=%08x, SDCMDARG=%08x, SDBUSMODE=%08x\n",
+ readl(addrbase + VIA_CRDR_SDCTRL),
+ readl(addrbase + VIA_CRDR_SDCARG),
+ readl(addrbase + VIA_CRDR_SDBUSMODE));
+ pr_debug("SDBLKLEN=%08x, SDCURBLKCNT=%08x, SDINTMASK=%08x\n",
+ readl(addrbase + VIA_CRDR_SDBLKLEN),
+ readl(addrbase + VIA_CRDR_SDCURBLKCNT),
+ readl(addrbase + VIA_CRDR_SDINTMASK));
+ pr_debug("SDSTATUS=%08x, SDCLKSEL=%08x, SDEXTCTRL=%08x\n",
+ readl(addrbase + VIA_CRDR_SDSTATUS),
+ readl(addrbase + VIA_CRDR_SDCLKSEL),
+ readl(addrbase + VIA_CRDR_SDEXTCTRL));
+}
+
+static void via_print_pcictrl(struct via_crdr_mmc_host *host)
+{
+ void __iomem *addrbase = host->pcictrl_mmiobase;
+
+ pr_debug("PCI Control Registers:\n");
+ pr_debug("PCICLKGATT=%02x, PCISDCCLK=%02x, PCIDMACLK=%02x\n",
+ readb(addrbase + VIA_CRDR_PCICLKGATT),
+ readb(addrbase + VIA_CRDR_PCISDCCLK),
+ readb(addrbase + VIA_CRDR_PCIDMACLK));
+ pr_debug("PCIINTCTRL=%02x, PCIINTSTATUS=%02x\n",
+ readb(addrbase + VIA_CRDR_PCIINTCTRL),
+ readb(addrbase + VIA_CRDR_PCIINTSTATUS));
+}
+
+static void via_save_pcictrlreg(struct via_crdr_mmc_host *host)
+{
+ struct pcictrlreg *pm_pcictrl_reg;
+ void __iomem *addrbase;
+
+ pm_pcictrl_reg = &(host->pm_pcictrl_reg);
+ addrbase = host->pcictrl_mmiobase;
+
+ pm_pcictrl_reg->pciclkgat_reg = readb(addrbase + VIA_CRDR_PCICLKGATT);
+ pm_pcictrl_reg->pciclkgat_reg |=
+ VIA_CRDR_PCICLKGATT_3V3 | VIA_CRDR_PCICLKGATT_PAD_PWRON;
+ pm_pcictrl_reg->pcisdclk_reg = readb(addrbase + VIA_CRDR_PCISDCCLK);
+ pm_pcictrl_reg->pcidmaclk_reg = readb(addrbase + VIA_CRDR_PCIDMACLK);
+ pm_pcictrl_reg->pciintctrl_reg = readb(addrbase + VIA_CRDR_PCIINTCTRL);
+ pm_pcictrl_reg->pciintstatus_reg =
+ readb(addrbase + VIA_CRDR_PCIINTSTATUS);
+ pm_pcictrl_reg->pcitmoctrl_reg = readb(addrbase + VIA_CRDR_PCITMOCTRL);
+}
+
+static void via_restore_pcictrlreg(struct via_crdr_mmc_host *host)
+{
+ struct pcictrlreg *pm_pcictrl_reg;
+ void __iomem *addrbase;
+
+ pm_pcictrl_reg = &(host->pm_pcictrl_reg);
+ addrbase = host->pcictrl_mmiobase;
+
+ writeb(pm_pcictrl_reg->pciclkgat_reg, addrbase + VIA_CRDR_PCICLKGATT);
+ writeb(pm_pcictrl_reg->pcisdclk_reg, addrbase + VIA_CRDR_PCISDCCLK);
+ writeb(pm_pcictrl_reg->pcidmaclk_reg, addrbase + VIA_CRDR_PCIDMACLK);
+ writeb(pm_pcictrl_reg->pciintctrl_reg, addrbase + VIA_CRDR_PCIINTCTRL);
+ writeb(pm_pcictrl_reg->pciintstatus_reg,
+ addrbase + VIA_CRDR_PCIINTSTATUS);
+ writeb(pm_pcictrl_reg->pcitmoctrl_reg, addrbase + VIA_CRDR_PCITMOCTRL);
+}
+
+static void via_save_sdcreg(struct via_crdr_mmc_host *host)
+{
+ struct sdhcreg *pm_sdhc_reg;
+ void __iomem *addrbase;
+
+ pm_sdhc_reg = &(host->pm_sdhc_reg);
+ addrbase = host->sdhc_mmiobase;
+
+ pm_sdhc_reg->sdcontrol_reg = readl(addrbase + VIA_CRDR_SDCTRL);
+ pm_sdhc_reg->sdcmdarg_reg = readl(addrbase + VIA_CRDR_SDCARG);
+ pm_sdhc_reg->sdbusmode_reg = readl(addrbase + VIA_CRDR_SDBUSMODE);
+ pm_sdhc_reg->sdblklen_reg = readl(addrbase + VIA_CRDR_SDBLKLEN);
+ pm_sdhc_reg->sdcurblkcnt_reg = readl(addrbase + VIA_CRDR_SDCURBLKCNT);
+ pm_sdhc_reg->sdintmask_reg = readl(addrbase + VIA_CRDR_SDINTMASK);
+ pm_sdhc_reg->sdstatus_reg = readl(addrbase + VIA_CRDR_SDSTATUS);
+ pm_sdhc_reg->sdrsptmo_reg = readl(addrbase + VIA_CRDR_SDRSPTMO);
+ pm_sdhc_reg->sdclksel_reg = readl(addrbase + VIA_CRDR_SDCLKSEL);
+ pm_sdhc_reg->sdextctrl_reg = readl(addrbase + VIA_CRDR_SDEXTCTRL);
+}
+
+static void via_restore_sdcreg(struct via_crdr_mmc_host *host)
+{
+ struct sdhcreg *pm_sdhc_reg;
+ void __iomem *addrbase;
+
+ pm_sdhc_reg = &(host->pm_sdhc_reg);
+ addrbase = host->sdhc_mmiobase;
+
+ writel(pm_sdhc_reg->sdcontrol_reg, addrbase + VIA_CRDR_SDCTRL);
+ writel(pm_sdhc_reg->sdcmdarg_reg, addrbase + VIA_CRDR_SDCARG);
+ writel(pm_sdhc_reg->sdbusmode_reg, addrbase + VIA_CRDR_SDBUSMODE);
+ writel(pm_sdhc_reg->sdblklen_reg, addrbase + VIA_CRDR_SDBLKLEN);
+ writel(pm_sdhc_reg->sdcurblkcnt_reg, addrbase + VIA_CRDR_SDCURBLKCNT);
+ writel(pm_sdhc_reg->sdintmask_reg, addrbase + VIA_CRDR_SDINTMASK);
+ writel(pm_sdhc_reg->sdstatus_reg, addrbase + VIA_CRDR_SDSTATUS);
+ writel(pm_sdhc_reg->sdrsptmo_reg, addrbase + VIA_CRDR_SDRSPTMO);
+ writel(pm_sdhc_reg->sdclksel_reg, addrbase + VIA_CRDR_SDCLKSEL);
+ writel(pm_sdhc_reg->sdextctrl_reg, addrbase + VIA_CRDR_SDEXTCTRL);
+}
+
+static void via_pwron_sleep(struct via_crdr_mmc_host *sdhost)
+{
+ if (sdhost->quirks & VIA_CRDR_QUIRK_300MS_PWRDELAY)
+ msleep(300);
+ else
+ msleep(3);
+}
+
+static void via_set_ddma(struct via_crdr_mmc_host *host,
+ dma_addr_t dmaaddr, u32 count, int dir, int enirq)
+{
+ void __iomem *addrbase;
+ u32 ctrl_data = 0;
+
+ if (enirq)
+ ctrl_data |= VIA_CRDR_DMACTRL_ENIRQ;
+
+ if (dir)
+ ctrl_data |= VIA_CRDR_DMACTRL_DIR;
+
+ addrbase = host->ddma_mmiobase;
+
+ writel(dmaaddr, addrbase + VIA_CRDR_DMABASEADD);
+ writel(count, addrbase + VIA_CRDR_DMACOUNTER);
+ writel(ctrl_data, addrbase + VIA_CRDR_DMACTRL);
+ writel(0x01, addrbase + VIA_CRDR_DMASTART);
+
+ /* It seems that our DMA can not work normally with 375kHz clock */
+ /* FIXME: don't brute-force 8MHz but use PIO at 375kHz !! */
+ addrbase = host->pcictrl_mmiobase;
+ if (readb(addrbase + VIA_CRDR_PCISDCCLK) == PCI_CLK_375K) {
+ dev_info(host->mmc->parent, "forcing card speed to 8MHz\n");
+ writeb(PCI_CLK_8M, addrbase + VIA_CRDR_PCISDCCLK);
+ }
+}
+
+static void via_sdc_preparedata(struct via_crdr_mmc_host *host,
+ struct mmc_data *data)
+{
+ void __iomem *addrbase;
+ u32 blk_reg;
+ int count;
+
+ WARN_ON(host->data);
+
+ /* Sanity checks */
+ BUG_ON(data->blksz > host->mmc->max_blk_size);
+ BUG_ON(data->blocks > host->mmc->max_blk_count);
+
+ host->data = data;
+
+ count = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+ ((data->flags & MMC_DATA_READ) ?
+ PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE));
+ BUG_ON(count != 1);
+
+ via_set_ddma(host, sg_dma_address(data->sg), sg_dma_len(data->sg),
+ (data->flags & MMC_DATA_WRITE) ? 1 : 0, 1);
+
+ addrbase = host->sdhc_mmiobase;
+
+ blk_reg = data->blksz - 1;
+ blk_reg |= VIA_CRDR_SDBLKLEN_GPIDET | VIA_CRDR_SDBLKLEN_INTEN;
+ blk_reg |= (data->blocks) << 16;
+
+ writel(blk_reg, addrbase + VIA_CRDR_SDBLKLEN);
+}
+
+static void via_sdc_get_response(struct via_crdr_mmc_host *host,
+ struct mmc_command *cmd)
+{
+ void __iomem *addrbase = host->sdhc_mmiobase;
+ u32 dwdata0 = readl(addrbase + VIA_CRDR_SDRESP0);
+ u32 dwdata1 = readl(addrbase + VIA_CRDR_SDRESP1);
+ u32 dwdata2 = readl(addrbase + VIA_CRDR_SDRESP2);
+ u32 dwdata3 = readl(addrbase + VIA_CRDR_SDRESP3);
+
+ if (cmd->flags & MMC_RSP_136) {
+ cmd->resp[0] = ((u8) (dwdata1)) |
+ (((u8) (dwdata0 >> 24)) << 8) |
+ (((u8) (dwdata0 >> 16)) << 16) |
+ (((u8) (dwdata0 >> 8)) << 24);
+
+ cmd->resp[1] = ((u8) (dwdata2)) |
+ (((u8) (dwdata1 >> 24)) << 8) |
+ (((u8) (dwdata1 >> 16)) << 16) |
+ (((u8) (dwdata1 >> 8)) << 24);
+
+ cmd->resp[2] = ((u8) (dwdata3)) |
+ (((u8) (dwdata2 >> 24)) << 8) |
+ (((u8) (dwdata2 >> 16)) << 16) |
+ (((u8) (dwdata2 >> 8)) << 24);
+
+ cmd->resp[3] = 0xff |
+ ((((u8) (dwdata3 >> 24))) << 8) |
+ (((u8) (dwdata3 >> 16)) << 16) |
+ (((u8) (dwdata3 >> 8)) << 24);
+ } else {
+ dwdata0 >>= 8;
+ cmd->resp[0] = ((dwdata0 & 0xff) << 24) |
+ (((dwdata0 >> 8) & 0xff) << 16) |
+ (((dwdata0 >> 16) & 0xff) << 8) | (dwdata1 & 0xff);
+
+ dwdata1 >>= 8;
+ cmd->resp[1] = ((dwdata1 & 0xff) << 24) |
+ (((dwdata1 >> 8) & 0xff) << 16) |
+ (((dwdata1 >> 16) & 0xff) << 8);
+ }
+}
+
+static void via_sdc_send_command(struct via_crdr_mmc_host *host,
+ struct mmc_command *cmd)
+{
+ void __iomem *addrbase;
+ struct mmc_data *data;
+ u32 cmdctrl = 0;
+
+ WARN_ON(host->cmd);
+
+ data = cmd->data;
+ mod_timer(&host->timer, jiffies + HZ);
+ host->cmd = cmd;
+
+ /*Command index*/
+ cmdctrl = cmd->opcode << 8;
+
+ /*Response type*/
+ switch (mmc_resp_type(cmd)) {
+ case MMC_RSP_NONE:
+ cmdctrl |= VIA_CRDR_SDCTRL_RSP_NONE;
+ break;
+ case MMC_RSP_R1:
+ cmdctrl |= VIA_CRDR_SDCTRL_RSP_R1;
+ break;
+ case MMC_RSP_R1B:
+ cmdctrl |= VIA_CRDR_SDCTRL_RSP_R1B;
+ break;
+ case MMC_RSP_R2:
+ cmdctrl |= VIA_CRDR_SDCTRL_RSP_R2;
+ break;
+ case MMC_RSP_R3:
+ cmdctrl |= VIA_CRDR_SDCTRL_RSP_R3;
+ break;
+ default:
+ pr_err("%s: cmd->flag is not valid\n", mmc_hostname(host->mmc));
+ break;
+ }
+
+ if (!(cmd->data))
+ goto nodata;
+
+ via_sdc_preparedata(host, data);
+
+ /*Command control*/
+ if (data->blocks > 1) {
+ if (data->flags & MMC_DATA_WRITE) {
+ cmdctrl |= VIA_CRDR_SDCTRL_WRITE;
+ cmdctrl |= VIA_CRDR_SDCTRL_MULTI_WR;
+ } else {
+ cmdctrl |= VIA_CRDR_SDCTRL_MULTI_RD;
+ }
+ } else {
+ if (data->flags & MMC_DATA_WRITE) {
+ cmdctrl |= VIA_CRDR_SDCTRL_WRITE;
+ cmdctrl |= VIA_CRDR_SDCTRL_SINGLE_WR;
+ } else {
+ cmdctrl |= VIA_CRDR_SDCTRL_SINGLE_RD;
+ }
+ }
+
+nodata:
+ if (cmd == host->mrq->stop)
+ cmdctrl |= VIA_CRDR_SDCTRL_STOP;
+
+ cmdctrl |= VIA_CRDR_SDCTRL_START;
+
+ addrbase = host->sdhc_mmiobase;
+ writel(cmd->arg, addrbase + VIA_CRDR_SDCARG);
+ writel(cmdctrl, addrbase + VIA_CRDR_SDCTRL);
+}
+
+static void via_sdc_finish_data(struct via_crdr_mmc_host *host)
+{
+ struct mmc_data *data;
+
+ BUG_ON(!host->data);
+
+ data = host->data;
+ host->data = NULL;
+
+ if (data->error)
+ data->bytes_xfered = 0;
+ else
+ data->bytes_xfered = data->blocks * data->blksz;
+
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+ ((data->flags & MMC_DATA_READ) ?
+ PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE));
+
+ if (data->stop)
+ via_sdc_send_command(host, data->stop);
+ else
+ tasklet_schedule(&host->finish_tasklet);
+}
+
+static void via_sdc_finish_command(struct via_crdr_mmc_host *host)
+{
+ via_sdc_get_response(host, host->cmd);
+
+ host->cmd->error = 0;
+
+ if (!host->cmd->data)
+ tasklet_schedule(&host->finish_tasklet);
+
+ host->cmd = NULL;
+}
+
+static void via_sdc_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ void __iomem *addrbase;
+ struct via_crdr_mmc_host *host;
+ unsigned long flags;
+ u16 status;
+
+ host = mmc_priv(mmc);
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ addrbase = host->pcictrl_mmiobase;
+ writeb(VIA_CRDR_PCIDMACLK_SDC, addrbase + VIA_CRDR_PCIDMACLK);
+
+ status = readw(host->sdhc_mmiobase + VIA_CRDR_SDSTATUS);
+ status &= VIA_CRDR_SDSTS_W1C_MASK;
+ writew(status, host->sdhc_mmiobase + VIA_CRDR_SDSTATUS);
+
+ WARN_ON(host->mrq != NULL);
+ host->mrq = mrq;
+
+ status = readw(host->sdhc_mmiobase + VIA_CRDR_SDSTATUS);
+ if (!(status & VIA_CRDR_SDSTS_SLOTG) || host->reject) {
+ host->mrq->cmd->error = -ENOMEDIUM;
+ tasklet_schedule(&host->finish_tasklet);
+ } else {
+ via_sdc_send_command(host, mrq->cmd);
+ }
+
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void via_sdc_set_power(struct via_crdr_mmc_host *host,
+ unsigned short power, unsigned int on)
+{
+ unsigned long flags;
+ u8 gatt;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ host->power = (1 << power);
+
+ gatt = readb(host->pcictrl_mmiobase + VIA_CRDR_PCICLKGATT);
+ if (host->power == MMC_VDD_165_195)
+ gatt &= ~VIA_CRDR_PCICLKGATT_3V3;
+ else
+ gatt |= VIA_CRDR_PCICLKGATT_3V3;
+ if (on)
+ gatt |= VIA_CRDR_PCICLKGATT_PAD_PWRON;
+ else
+ gatt &= ~VIA_CRDR_PCICLKGATT_PAD_PWRON;
+ writeb(gatt, host->pcictrl_mmiobase + VIA_CRDR_PCICLKGATT);
+
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ via_pwron_sleep(host);
+}
+
+static void via_sdc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct via_crdr_mmc_host *host;
+ unsigned long flags;
+ void __iomem *addrbase;
+ u32 org_data, sdextctrl;
+ u8 clock;
+
+ host = mmc_priv(mmc);
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ addrbase = host->sdhc_mmiobase;
+ org_data = readl(addrbase + VIA_CRDR_SDBUSMODE);
+ sdextctrl = readl(addrbase + VIA_CRDR_SDEXTCTRL);
+
+ if (ios->bus_width == MMC_BUS_WIDTH_1)
+ org_data &= ~VIA_CRDR_SDMODE_4BIT;
+ else
+ org_data |= VIA_CRDR_SDMODE_4BIT;
+
+ if (ios->power_mode == MMC_POWER_OFF)
+ org_data &= ~VIA_CRDR_SDMODE_CLK_ON;
+ else
+ org_data |= VIA_CRDR_SDMODE_CLK_ON;
+
+ if (ios->timing == MMC_TIMING_SD_HS)
+ sdextctrl |= VIA_CRDR_SDEXTCTRL_HISPD;
+ else
+ sdextctrl &= ~VIA_CRDR_SDEXTCTRL_HISPD;
+
+ writel(org_data, addrbase + VIA_CRDR_SDBUSMODE);
+ writel(sdextctrl, addrbase + VIA_CRDR_SDEXTCTRL);
+
+ if (ios->clock >= 48000000)
+ clock = PCI_CLK_48M;
+ else if (ios->clock >= 33000000)
+ clock = PCI_CLK_33M;
+ else if (ios->clock >= 24000000)
+ clock = PCI_CLK_24M;
+ else if (ios->clock >= 16000000)
+ clock = PCI_CLK_16M;
+ else if (ios->clock >= 12000000)
+ clock = PCI_CLK_12M;
+ else if (ios->clock >= 8000000)
+ clock = PCI_CLK_8M;
+ else
+ clock = PCI_CLK_375K;
+
+ addrbase = host->pcictrl_mmiobase;
+ if (readb(addrbase + VIA_CRDR_PCISDCCLK) != clock)
+ writeb(clock, addrbase + VIA_CRDR_PCISDCCLK);
+
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ if (ios->power_mode != MMC_POWER_OFF)
+ via_sdc_set_power(host, ios->vdd, 1);
+ else
+ via_sdc_set_power(host, ios->vdd, 0);
+}
+
+static int via_sdc_get_ro(struct mmc_host *mmc)
+{
+ struct via_crdr_mmc_host *host;
+ unsigned long flags;
+ u16 status;
+
+ host = mmc_priv(mmc);
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ status = readw(host->sdhc_mmiobase + VIA_CRDR_SDSTATUS);
+
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ return !(status & VIA_CRDR_SDSTS_WP);
+}
+
+static const struct mmc_host_ops via_sdc_ops = {
+ .request = via_sdc_request,
+ .set_ios = via_sdc_set_ios,
+ .get_ro = via_sdc_get_ro,
+};
+
+static void via_reset_pcictrl(struct via_crdr_mmc_host *host)
+{
+ unsigned long flags;
+ u8 gatt;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ via_save_pcictrlreg(host);
+ via_save_sdcreg(host);
+
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ gatt = VIA_CRDR_PCICLKGATT_PAD_PWRON;
+ if (host->power == MMC_VDD_165_195)
+ gatt &= VIA_CRDR_PCICLKGATT_3V3;
+ else
+ gatt |= VIA_CRDR_PCICLKGATT_3V3;
+ writeb(gatt, host->pcictrl_mmiobase + VIA_CRDR_PCICLKGATT);
+ via_pwron_sleep(host);
+ gatt |= VIA_CRDR_PCICLKGATT_SFTRST;
+ writeb(gatt, host->pcictrl_mmiobase + VIA_CRDR_PCICLKGATT);
+ msleep(3);
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ via_restore_pcictrlreg(host);
+ via_restore_sdcreg(host);
+
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void via_sdc_cmd_isr(struct via_crdr_mmc_host *host, u16 intmask)
+{
+ BUG_ON(intmask == 0);
+
+ if (!host->cmd) {
+ pr_err("%s: Got command interrupt 0x%x even "
+ "though no command operation was in progress.\n",
+ mmc_hostname(host->mmc), intmask);
+ return;
+ }
+
+ if (intmask & VIA_CRDR_SDSTS_CRTO)
+ host->cmd->error = -ETIMEDOUT;
+ else if (intmask & VIA_CRDR_SDSTS_SC)
+ host->cmd->error = -EILSEQ;
+
+ if (host->cmd->error)
+ tasklet_schedule(&host->finish_tasklet);
+ else if (intmask & VIA_CRDR_SDSTS_CRD)
+ via_sdc_finish_command(host);
+}
+
+static void via_sdc_data_isr(struct via_crdr_mmc_host *host, u16 intmask)
+{
+ BUG_ON(intmask == 0);
+
+ if (intmask & VIA_CRDR_SDSTS_DT)
+ host->data->error = -ETIMEDOUT;
+ else if (intmask & (VIA_CRDR_SDSTS_RC | VIA_CRDR_SDSTS_WC))
+ host->data->error = -EILSEQ;
+
+ via_sdc_finish_data(host);
+}
+
+static irqreturn_t via_sdc_isr(int irq, void *dev_id)
+{
+ struct via_crdr_mmc_host *sdhost = dev_id;
+ void __iomem *addrbase;
+ u8 pci_status;
+ u16 sd_status;
+ irqreturn_t result;
+
+ if (!sdhost)
+ return IRQ_NONE;
+
+ spin_lock(&sdhost->lock);
+
+ addrbase = sdhost->pcictrl_mmiobase;
+ pci_status = readb(addrbase + VIA_CRDR_PCIINTSTATUS);
+ if (!(pci_status & VIA_CRDR_PCIINTSTATUS_SDC)) {
+ result = IRQ_NONE;
+ goto out;
+ }
+
+ addrbase = sdhost->sdhc_mmiobase;
+ sd_status = readw(addrbase + VIA_CRDR_SDSTATUS);
+ sd_status &= VIA_CRDR_SDSTS_INT_MASK;
+ sd_status &= ~VIA_CRDR_SDSTS_IGN_MASK;
+ if (!sd_status) {
+ result = IRQ_NONE;
+ goto out;
+ }
+
+ if (sd_status & VIA_CRDR_SDSTS_CIR) {
+ writew(sd_status & VIA_CRDR_SDSTS_CIR,
+ addrbase + VIA_CRDR_SDSTATUS);
+
+ schedule_work(&sdhost->carddet_work);
+ }
+
+ sd_status &= ~VIA_CRDR_SDSTS_CIR;
+ if (sd_status & VIA_CRDR_SDSTS_CMD_MASK) {
+ writew(sd_status & VIA_CRDR_SDSTS_CMD_MASK,
+ addrbase + VIA_CRDR_SDSTATUS);
+ via_sdc_cmd_isr(sdhost, sd_status & VIA_CRDR_SDSTS_CMD_MASK);
+ }
+ if (sd_status & VIA_CRDR_SDSTS_DATA_MASK) {
+ writew(sd_status & VIA_CRDR_SDSTS_DATA_MASK,
+ addrbase + VIA_CRDR_SDSTATUS);
+ via_sdc_data_isr(sdhost, sd_status & VIA_CRDR_SDSTS_DATA_MASK);
+ }
+
+ sd_status &= ~(VIA_CRDR_SDSTS_CMD_MASK | VIA_CRDR_SDSTS_DATA_MASK);
+ if (sd_status) {
+ pr_err("%s: Unexpected interrupt 0x%x\n",
+ mmc_hostname(sdhost->mmc), sd_status);
+ writew(sd_status, addrbase + VIA_CRDR_SDSTATUS);
+ }
+
+ result = IRQ_HANDLED;
+
+ mmiowb();
+out:
+ spin_unlock(&sdhost->lock);
+
+ return result;
+}
+
+static void via_sdc_timeout(unsigned long ulongdata)
+{
+ struct via_crdr_mmc_host *sdhost;
+ unsigned long flags;
+
+ sdhost = (struct via_crdr_mmc_host *)ulongdata;
+
+ spin_lock_irqsave(&sdhost->lock, flags);
+
+ if (sdhost->mrq) {
+ pr_err("%s: Timeout waiting for hardware interrupt."
+ "cmd:0x%x\n", mmc_hostname(sdhost->mmc),
+ sdhost->mrq->cmd->opcode);
+
+ if (sdhost->data) {
+ writel(VIA_CRDR_DMACTRL_SFTRST,
+ sdhost->ddma_mmiobase + VIA_CRDR_DMACTRL);
+ sdhost->data->error = -ETIMEDOUT;
+ via_sdc_finish_data(sdhost);
+ } else {
+ if (sdhost->cmd)
+ sdhost->cmd->error = -ETIMEDOUT;
+ else
+ sdhost->mrq->cmd->error = -ETIMEDOUT;
+ tasklet_schedule(&sdhost->finish_tasklet);
+ }
+ }
+
+ mmiowb();
+ spin_unlock_irqrestore(&sdhost->lock, flags);
+}
+
+static void via_sdc_tasklet_finish(unsigned long param)
+{
+ struct via_crdr_mmc_host *host;
+ unsigned long flags;
+ struct mmc_request *mrq;
+
+ host = (struct via_crdr_mmc_host *)param;
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ del_timer(&host->timer);
+ mrq = host->mrq;
+ host->mrq = NULL;
+ host->cmd = NULL;
+ host->data = NULL;
+
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ mmc_request_done(host->mmc, mrq);
+}
+
+static void via_sdc_card_detect(struct work_struct *work)
+{
+ struct via_crdr_mmc_host *host;
+ void __iomem *addrbase;
+ unsigned long flags;
+ u16 status;
+
+ host = container_of(work, struct via_crdr_mmc_host, carddet_work);
+
+ addrbase = host->ddma_mmiobase;
+ writel(VIA_CRDR_DMACTRL_SFTRST, addrbase + VIA_CRDR_DMACTRL);
+
+ spin_lock_irqsave(&host->lock, flags);
+
+ addrbase = host->pcictrl_mmiobase;
+ writeb(VIA_CRDR_PCIDMACLK_SDC, addrbase + VIA_CRDR_PCIDMACLK);
+
+ addrbase = host->sdhc_mmiobase;
+ status = readw(addrbase + VIA_CRDR_SDSTATUS);
+ if (!(status & VIA_CRDR_SDSTS_SLOTG)) {
+ if (host->mrq) {
+ pr_err("%s: Card removed during transfer!\n",
+ mmc_hostname(host->mmc));
+ host->mrq->cmd->error = -ENOMEDIUM;
+ tasklet_schedule(&host->finish_tasklet);
+ }
+
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ via_reset_pcictrl(host);
+
+ spin_lock_irqsave(&host->lock, flags);
+ }
+
+ mmiowb();
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ via_print_pcictrl(host);
+ via_print_sdchc(host);
+
+ mmc_detect_change(host->mmc, msecs_to_jiffies(500));
+}
+
+static void via_init_mmc_host(struct via_crdr_mmc_host *host)
+{
+ struct mmc_host *mmc = host->mmc;
+ void __iomem *addrbase;
+ u32 lenreg;
+ u32 status;
+
+ init_timer(&host->timer);
+ host->timer.data = (unsigned long)host;
+ host->timer.function = via_sdc_timeout;
+
+ spin_lock_init(&host->lock);
+
+ mmc->f_min = VIA_CRDR_MIN_CLOCK;
+ mmc->f_max = VIA_CRDR_MAX_CLOCK;
+ mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195;
+ mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SD_HIGHSPEED;
+ mmc->ops = &via_sdc_ops;
+
+ /*Hardware cannot do scatter lists*/
+ mmc->max_segs = 1;
+
+ mmc->max_blk_size = VIA_CRDR_MAX_BLOCK_LENGTH;
+ mmc->max_blk_count = VIA_CRDR_MAX_BLOCK_COUNT;
+
+ mmc->max_seg_size = mmc->max_blk_size * mmc->max_blk_count;
+ mmc->max_req_size = mmc->max_seg_size;
+
+ INIT_WORK(&host->carddet_work, via_sdc_card_detect);
+
+ tasklet_init(&host->finish_tasklet, via_sdc_tasklet_finish,
+ (unsigned long)host);
+
+ addrbase = host->sdhc_mmiobase;
+ writel(0x0, addrbase + VIA_CRDR_SDINTMASK);
+ msleep(1);
+
+ lenreg = VIA_CRDR_SDBLKLEN_GPIDET | VIA_CRDR_SDBLKLEN_INTEN;
+ writel(lenreg, addrbase + VIA_CRDR_SDBLKLEN);
+
+ status = readw(addrbase + VIA_CRDR_SDSTATUS);
+ status &= VIA_CRDR_SDSTS_W1C_MASK;
+ writew(status, addrbase + VIA_CRDR_SDSTATUS);
+
+ status = readw(addrbase + VIA_CRDR_SDSTATUS2);
+ status |= VIA_CRDR_SDSTS_CFE;
+ writew(status, addrbase + VIA_CRDR_SDSTATUS2);
+
+ writeb(0x0, addrbase + VIA_CRDR_SDEXTCTRL);
+
+ writel(VIA_CRDR_SDACTIVE_INTMASK, addrbase + VIA_CRDR_SDINTMASK);
+ msleep(1);
+}
+
+static int __devinit via_sd_probe(struct pci_dev *pcidev,
+ const struct pci_device_id *id)
+{
+ struct mmc_host *mmc;
+ struct via_crdr_mmc_host *sdhost;
+ u32 base, len;
+ u8 gatt;
+ int ret;
+
+ pr_info(DRV_NAME
+ ": VIA SDMMC controller found at %s [%04x:%04x] (rev %x)\n",
+ pci_name(pcidev), (int)pcidev->vendor, (int)pcidev->device,
+ (int)pcidev->revision);
+
+ ret = pci_enable_device(pcidev);
+ if (ret)
+ return ret;
+
+ ret = pci_request_regions(pcidev, DRV_NAME);
+ if (ret)
+ goto disable;
+
+ pci_write_config_byte(pcidev, VIA_CRDR_PCI_WORK_MODE, 0);
+ pci_write_config_byte(pcidev, VIA_CRDR_PCI_DBG_MODE, 0);
+
+ mmc = mmc_alloc_host(sizeof(struct via_crdr_mmc_host), &pcidev->dev);
+ if (!mmc) {
+ ret = -ENOMEM;
+ goto release;
+ }
+
+ sdhost = mmc_priv(mmc);
+ sdhost->mmc = mmc;
+ dev_set_drvdata(&pcidev->dev, sdhost);
+
+ len = pci_resource_len(pcidev, 0);
+ base = pci_resource_start(pcidev, 0);
+ sdhost->mmiobase = ioremap_nocache(base, len);
+ if (!sdhost->mmiobase) {
+ ret = -ENOMEM;
+ goto free_mmc_host;
+ }
+
+ sdhost->sdhc_mmiobase =
+ sdhost->mmiobase + VIA_CRDR_SDC_OFF;
+ sdhost->ddma_mmiobase =
+ sdhost->mmiobase + VIA_CRDR_DDMA_OFF;
+ sdhost->pcictrl_mmiobase =
+ sdhost->mmiobase + VIA_CRDR_PCICTRL_OFF;
+
+ sdhost->power = MMC_VDD_165_195;
+
+ gatt = VIA_CRDR_PCICLKGATT_3V3 | VIA_CRDR_PCICLKGATT_PAD_PWRON;
+ writeb(gatt, sdhost->pcictrl_mmiobase + VIA_CRDR_PCICLKGATT);
+ via_pwron_sleep(sdhost);
+ gatt |= VIA_CRDR_PCICLKGATT_SFTRST;
+ writeb(gatt, sdhost->pcictrl_mmiobase + VIA_CRDR_PCICLKGATT);
+ msleep(3);
+
+ via_init_mmc_host(sdhost);
+
+ ret =
+ request_irq(pcidev->irq, via_sdc_isr, IRQF_SHARED, DRV_NAME,
+ sdhost);
+ if (ret)
+ goto unmap;
+
+ writeb(VIA_CRDR_PCIINTCTRL_SDCIRQEN,
+ sdhost->pcictrl_mmiobase + VIA_CRDR_PCIINTCTRL);
+ writeb(VIA_CRDR_PCITMOCTRL_1024MS,
+ sdhost->pcictrl_mmiobase + VIA_CRDR_PCITMOCTRL);
+
+ /* device-specific quirks */
+ if (pcidev->subsystem_vendor == PCI_VENDOR_ID_LENOVO &&
+ pcidev->subsystem_device == 0x3891)
+ sdhost->quirks = VIA_CRDR_QUIRK_300MS_PWRDELAY;
+
+ mmc_add_host(mmc);
+
+ return 0;
+
+unmap:
+ iounmap(sdhost->mmiobase);
+free_mmc_host:
+ dev_set_drvdata(&pcidev->dev, NULL);
+ mmc_free_host(mmc);
+release:
+ pci_release_regions(pcidev);
+disable:
+ pci_disable_device(pcidev);
+
+ return ret;
+}
+
+static void __devexit via_sd_remove(struct pci_dev *pcidev)
+{
+ struct via_crdr_mmc_host *sdhost = pci_get_drvdata(pcidev);
+ unsigned long flags;
+ u8 gatt;
+
+ spin_lock_irqsave(&sdhost->lock, flags);
+
+ /* Ensure we don't accept more commands from mmc layer */
+ sdhost->reject = 1;
+
+ /* Disable generating further interrupts */
+ writeb(0x0, sdhost->pcictrl_mmiobase + VIA_CRDR_PCIINTCTRL);
+ mmiowb();
+
+ if (sdhost->mrq) {
+ printk(KERN_ERR "%s: Controller removed during "
+ "transfer\n", mmc_hostname(sdhost->mmc));
+
+ /* make sure all DMA is stopped */
+ writel(VIA_CRDR_DMACTRL_SFTRST,
+ sdhost->ddma_mmiobase + VIA_CRDR_DMACTRL);
+ mmiowb();
+ sdhost->mrq->cmd->error = -ENOMEDIUM;
+ if (sdhost->mrq->stop)
+ sdhost->mrq->stop->error = -ENOMEDIUM;
+ tasklet_schedule(&sdhost->finish_tasklet);
+ }
+ spin_unlock_irqrestore(&sdhost->lock, flags);
+
+ mmc_remove_host(sdhost->mmc);
+
+ free_irq(pcidev->irq, sdhost);
+
+ del_timer_sync(&sdhost->timer);
+
+ tasklet_kill(&sdhost->finish_tasklet);
+
+ /* switch off power */
+ gatt = readb(sdhost->pcictrl_mmiobase + VIA_CRDR_PCICLKGATT);
+ gatt &= ~VIA_CRDR_PCICLKGATT_PAD_PWRON;
+ writeb(gatt, sdhost->pcictrl_mmiobase + VIA_CRDR_PCICLKGATT);
+
+ iounmap(sdhost->mmiobase);
+ dev_set_drvdata(&pcidev->dev, NULL);
+ mmc_free_host(sdhost->mmc);
+ pci_release_regions(pcidev);
+ pci_disable_device(pcidev);
+
+ pr_info(DRV_NAME
+ ": VIA SDMMC controller at %s [%04x:%04x] has been removed\n",
+ pci_name(pcidev), (int)pcidev->vendor, (int)pcidev->device);
+}
+
+#ifdef CONFIG_PM
+
+static void via_init_sdc_pm(struct via_crdr_mmc_host *host)
+{
+ struct sdhcreg *pm_sdhcreg;
+ void __iomem *addrbase;
+ u32 lenreg;
+ u16 status;
+
+ pm_sdhcreg = &(host->pm_sdhc_reg);
+ addrbase = host->sdhc_mmiobase;
+
+ writel(0x0, addrbase + VIA_CRDR_SDINTMASK);
+
+ lenreg = VIA_CRDR_SDBLKLEN_GPIDET | VIA_CRDR_SDBLKLEN_INTEN;
+ writel(lenreg, addrbase + VIA_CRDR_SDBLKLEN);
+
+ status = readw(addrbase + VIA_CRDR_SDSTATUS);
+ status &= VIA_CRDR_SDSTS_W1C_MASK;
+ writew(status, addrbase + VIA_CRDR_SDSTATUS);
+
+ status = readw(addrbase + VIA_CRDR_SDSTATUS2);
+ status |= VIA_CRDR_SDSTS_CFE;
+ writew(status, addrbase + VIA_CRDR_SDSTATUS2);
+
+ writel(pm_sdhcreg->sdcontrol_reg, addrbase + VIA_CRDR_SDCTRL);
+ writel(pm_sdhcreg->sdcmdarg_reg, addrbase + VIA_CRDR_SDCARG);
+ writel(pm_sdhcreg->sdintmask_reg, addrbase + VIA_CRDR_SDINTMASK);
+ writel(pm_sdhcreg->sdrsptmo_reg, addrbase + VIA_CRDR_SDRSPTMO);
+ writel(pm_sdhcreg->sdclksel_reg, addrbase + VIA_CRDR_SDCLKSEL);
+ writel(pm_sdhcreg->sdextctrl_reg, addrbase + VIA_CRDR_SDEXTCTRL);
+
+ via_print_pcictrl(host);
+ via_print_sdchc(host);
+}
+
+static int via_sd_suspend(struct pci_dev *pcidev, pm_message_t state)
+{
+ struct via_crdr_mmc_host *host;
+ int ret = 0;
+
+ host = pci_get_drvdata(pcidev);
+
+ via_save_pcictrlreg(host);
+ via_save_sdcreg(host);
+
+ ret = mmc_suspend_host(host->mmc);
+
+ pci_save_state(pcidev);
+ pci_enable_wake(pcidev, pci_choose_state(pcidev, state), 0);
+ pci_disable_device(pcidev);
+ pci_set_power_state(pcidev, pci_choose_state(pcidev, state));
+
+ return ret;
+}
+
+static int via_sd_resume(struct pci_dev *pcidev)
+{
+ struct via_crdr_mmc_host *sdhost;
+ int ret = 0;
+ u8 gatt;
+
+ sdhost = pci_get_drvdata(pcidev);
+
+ gatt = VIA_CRDR_PCICLKGATT_PAD_PWRON;
+ if (sdhost->power == MMC_VDD_165_195)
+ gatt &= ~VIA_CRDR_PCICLKGATT_3V3;
+ else
+ gatt |= VIA_CRDR_PCICLKGATT_3V3;
+ writeb(gatt, sdhost->pcictrl_mmiobase + VIA_CRDR_PCICLKGATT);
+ via_pwron_sleep(sdhost);
+ gatt |= VIA_CRDR_PCICLKGATT_SFTRST;
+ writeb(gatt, sdhost->pcictrl_mmiobase + VIA_CRDR_PCICLKGATT);
+ msleep(3);
+
+ msleep(100);
+
+ pci_set_power_state(pcidev, PCI_D0);
+ pci_restore_state(pcidev);
+ ret = pci_enable_device(pcidev);
+ if (ret)
+ return ret;
+
+ via_restore_pcictrlreg(sdhost);
+ via_init_sdc_pm(sdhost);
+
+ ret = mmc_resume_host(sdhost->mmc);
+
+ return ret;
+}
+
+#else /* CONFIG_PM */
+
+#define via_sd_suspend NULL
+#define via_sd_resume NULL
+
+#endif /* CONFIG_PM */
+
+static struct pci_driver via_sd_driver = {
+ .name = DRV_NAME,
+ .id_table = via_ids,
+ .probe = via_sd_probe,
+ .remove = __devexit_p(via_sd_remove),
+ .suspend = via_sd_suspend,
+ .resume = via_sd_resume,
+};
+
+static int __init via_sd_drv_init(void)
+{
+ pr_info(DRV_NAME ": VIA SD/MMC Card Reader driver "
+ "(C) 2008 VIA Technologies, Inc.\n");
+
+ return pci_register_driver(&via_sd_driver);
+}
+
+static void __exit via_sd_drv_exit(void)
+{
+ pci_unregister_driver(&via_sd_driver);
+}
+
+module_init(via_sd_drv_init);
+module_exit(via_sd_drv_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("VIA Technologies Inc.");
+MODULE_DESCRIPTION("VIA SD/MMC Card Interface driver");
diff --git a/drivers/mmc/host/vub300.c b/drivers/mmc/host/vub300.c
new file mode 100644
index 00000000..52f4b644
--- /dev/null
+++ b/drivers/mmc/host/vub300.c
@@ -0,0 +1,2503 @@
+/*
+ * Remote VUB300 SDIO/SDmem Host Controller Driver
+ *
+ * Copyright (C) 2010 Elan Digital Systems Limited
+ *
+ * based on USB Skeleton driver - 2.2
+ *
+ * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com)
+ *
+ * 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, version 2
+ *
+ * VUB300: is a USB 2.0 client device with a single SDIO/SDmem/MMC slot
+ * Any SDIO/SDmem/MMC device plugged into the VUB300 will appear,
+ * by virtue of this driver, to have been plugged into a local
+ * SDIO host controller, similar to, say, a PCI Ricoh controller
+ * This is because this kernel device driver is both a USB 2.0
+ * client device driver AND an MMC host controller driver. Thus
+ * if there is an existing driver for the inserted SDIO/SDmem/MMC
+ * device then that driver will be used by the kernel to manage
+ * the device in exactly the same fashion as if it had been
+ * directly plugged into, say, a local pci bus Ricoh controller
+ *
+ * RANT: this driver was written using a display 128x48 - converting it
+ * to a line width of 80 makes it very difficult to support. In
+ * particular functions have been broken down into sub functions
+ * and the original meaningful names have been shortened into
+ * cryptic ones.
+ * The problem is that executing a fragment of code subject to
+ * two conditions means an indentation of 24, thus leaving only
+ * 56 characters for a C statement. And that is quite ridiculous!
+ *
+ * Data types: data passed to/from the VUB300 is fixed to a number of
+ * bits and driver data fields reflect that limit by using
+ * u8, u16, u32
+ */
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kref.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/mutex.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/sdio_ids.h>
+#include <linux/workqueue.h>
+#include <linux/ctype.h>
+#include <linux/firmware.h>
+#include <linux/scatterlist.h>
+
+struct host_controller_info {
+ u8 info_size;
+ u16 firmware_version;
+ u8 number_of_ports;
+} __packed;
+
+#define FIRMWARE_BLOCK_BOUNDARY 1024
+struct sd_command_header {
+ u8 header_size;
+ u8 header_type;
+ u8 port_number;
+ u8 command_type; /* Bit7 - Rd/Wr */
+ u8 command_index;
+ u8 transfer_size[4]; /* ReadSize + ReadSize */
+ u8 response_type;
+ u8 arguments[4];
+ u8 block_count[2];
+ u8 block_size[2];
+ u8 block_boundary[2];
+ u8 reserved[44]; /* to pad out to 64 bytes */
+} __packed;
+
+struct sd_irqpoll_header {
+ u8 header_size;
+ u8 header_type;
+ u8 port_number;
+ u8 command_type; /* Bit7 - Rd/Wr */
+ u8 padding[16]; /* don't ask why !! */
+ u8 poll_timeout_msb;
+ u8 poll_timeout_lsb;
+ u8 reserved[42]; /* to pad out to 64 bytes */
+} __packed;
+
+struct sd_common_header {
+ u8 header_size;
+ u8 header_type;
+ u8 port_number;
+} __packed;
+
+struct sd_response_header {
+ u8 header_size;
+ u8 header_type;
+ u8 port_number;
+ u8 command_type;
+ u8 command_index;
+ u8 command_response[0];
+} __packed;
+
+struct sd_status_header {
+ u8 header_size;
+ u8 header_type;
+ u8 port_number;
+ u16 port_flags;
+ u32 sdio_clock;
+ u16 host_header_size;
+ u16 func_header_size;
+ u16 ctrl_header_size;
+} __packed;
+
+struct sd_error_header {
+ u8 header_size;
+ u8 header_type;
+ u8 port_number;
+ u8 error_code;
+} __packed;
+
+struct sd_interrupt_header {
+ u8 header_size;
+ u8 header_type;
+ u8 port_number;
+} __packed;
+
+struct offload_registers_access {
+ u8 command_byte[4];
+ u8 Respond_Byte[4];
+} __packed;
+
+#define INTERRUPT_REGISTER_ACCESSES 15
+struct sd_offloaded_interrupt {
+ u8 header_size;
+ u8 header_type;
+ u8 port_number;
+ struct offload_registers_access reg[INTERRUPT_REGISTER_ACCESSES];
+} __packed;
+
+struct sd_register_header {
+ u8 header_size;
+ u8 header_type;
+ u8 port_number;
+ u8 command_type;
+ u8 command_index;
+ u8 command_response[6];
+} __packed;
+
+#define PIGGYBACK_REGISTER_ACCESSES 14
+struct sd_offloaded_piggyback {
+ struct sd_register_header sdio;
+ struct offload_registers_access reg[PIGGYBACK_REGISTER_ACCESSES];
+} __packed;
+
+union sd_response {
+ struct sd_common_header common;
+ struct sd_status_header status;
+ struct sd_error_header error;
+ struct sd_interrupt_header interrupt;
+ struct sd_response_header response;
+ struct sd_offloaded_interrupt irq;
+ struct sd_offloaded_piggyback pig;
+} __packed;
+
+union sd_command {
+ struct sd_command_header head;
+ struct sd_irqpoll_header poll;
+} __packed;
+
+enum SD_RESPONSE_TYPE {
+ SDRT_UNSPECIFIED = 0,
+ SDRT_NONE,
+ SDRT_1,
+ SDRT_1B,
+ SDRT_2,
+ SDRT_3,
+ SDRT_4,
+ SDRT_5,
+ SDRT_5B,
+ SDRT_6,
+ SDRT_7,
+};
+
+#define RESPONSE_INTERRUPT 0x01
+#define RESPONSE_ERROR 0x02
+#define RESPONSE_STATUS 0x03
+#define RESPONSE_IRQ_DISABLED 0x05
+#define RESPONSE_IRQ_ENABLED 0x06
+#define RESPONSE_PIGGYBACKED 0x07
+#define RESPONSE_NO_INTERRUPT 0x08
+#define RESPONSE_PIG_DISABLED 0x09
+#define RESPONSE_PIG_ENABLED 0x0A
+#define SD_ERROR_1BIT_TIMEOUT 0x01
+#define SD_ERROR_4BIT_TIMEOUT 0x02
+#define SD_ERROR_1BIT_CRC_WRONG 0x03
+#define SD_ERROR_4BIT_CRC_WRONG 0x04
+#define SD_ERROR_1BIT_CRC_ERROR 0x05
+#define SD_ERROR_4BIT_CRC_ERROR 0x06
+#define SD_ERROR_NO_CMD_ENDBIT 0x07
+#define SD_ERROR_NO_1BIT_DATEND 0x08
+#define SD_ERROR_NO_4BIT_DATEND 0x09
+#define SD_ERROR_1BIT_UNEXPECTED_TIMEOUT 0x0A
+#define SD_ERROR_4BIT_UNEXPECTED_TIMEOUT 0x0B
+#define SD_ERROR_ILLEGAL_COMMAND 0x0C
+#define SD_ERROR_NO_DEVICE 0x0D
+#define SD_ERROR_TRANSFER_LENGTH 0x0E
+#define SD_ERROR_1BIT_DATA_TIMEOUT 0x0F
+#define SD_ERROR_4BIT_DATA_TIMEOUT 0x10
+#define SD_ERROR_ILLEGAL_STATE 0x11
+#define SD_ERROR_UNKNOWN_ERROR 0x12
+#define SD_ERROR_RESERVED_ERROR 0x13
+#define SD_ERROR_INVALID_FUNCTION 0x14
+#define SD_ERROR_OUT_OF_RANGE 0x15
+#define SD_ERROR_STAT_CMD 0x16
+#define SD_ERROR_STAT_DATA 0x17
+#define SD_ERROR_STAT_CMD_TIMEOUT 0x18
+#define SD_ERROR_SDCRDY_STUCK 0x19
+#define SD_ERROR_UNHANDLED 0x1A
+#define SD_ERROR_OVERRUN 0x1B
+#define SD_ERROR_PIO_TIMEOUT 0x1C
+
+#define FUN(c) (0x000007 & (c->arg>>28))
+#define REG(c) (0x01FFFF & (c->arg>>9))
+
+static int limit_speed_to_24_MHz;
+module_param(limit_speed_to_24_MHz, bool, 0644);
+MODULE_PARM_DESC(limit_speed_to_24_MHz, "Limit Max SDIO Clock Speed to 24 MHz");
+
+static int pad_input_to_usb_pkt;
+module_param(pad_input_to_usb_pkt, bool, 0644);
+MODULE_PARM_DESC(pad_input_to_usb_pkt,
+ "Pad USB data input transfers to whole USB Packet");
+
+static int disable_offload_processing;
+module_param(disable_offload_processing, bool, 0644);
+MODULE_PARM_DESC(disable_offload_processing, "Disable Offload Processing");
+
+static int force_1_bit_data_xfers;
+module_param(force_1_bit_data_xfers, bool, 0644);
+MODULE_PARM_DESC(force_1_bit_data_xfers,
+ "Force SDIO Data Transfers to 1-bit Mode");
+
+static int force_polling_for_irqs;
+module_param(force_polling_for_irqs, bool, 0644);
+MODULE_PARM_DESC(force_polling_for_irqs, "Force Polling for SDIO interrupts");
+
+static int firmware_irqpoll_timeout = 1024;
+module_param(firmware_irqpoll_timeout, int, 0644);
+MODULE_PARM_DESC(firmware_irqpoll_timeout, "VUB300 firmware irqpoll timeout");
+
+static int force_max_req_size = 128;
+module_param(force_max_req_size, int, 0644);
+MODULE_PARM_DESC(force_max_req_size, "set max request size in kBytes");
+
+#ifdef SMSC_DEVELOPMENT_BOARD
+static int firmware_rom_wait_states = 0x04;
+#else
+static int firmware_rom_wait_states = 0x1C;
+#endif
+
+module_param(firmware_rom_wait_states, int, 0644);
+MODULE_PARM_DESC(firmware_rom_wait_states,
+ "ROM wait states byte=RRRIIEEE (Reserved Internal External)");
+
+#define ELAN_VENDOR_ID 0x2201
+#define VUB300_VENDOR_ID 0x0424
+#define VUB300_PRODUCT_ID 0x012C
+static struct usb_device_id vub300_table[] = {
+ {USB_DEVICE(ELAN_VENDOR_ID, VUB300_PRODUCT_ID)},
+ {USB_DEVICE(VUB300_VENDOR_ID, VUB300_PRODUCT_ID)},
+ {} /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, vub300_table);
+
+static struct workqueue_struct *cmndworkqueue;
+static struct workqueue_struct *pollworkqueue;
+static struct workqueue_struct *deadworkqueue;
+
+static inline int interface_to_InterfaceNumber(struct usb_interface *interface)
+{
+ if (!interface)
+ return -1;
+ if (!interface->cur_altsetting)
+ return -1;
+ return interface->cur_altsetting->desc.bInterfaceNumber;
+}
+
+struct sdio_register {
+ unsigned func_num:3;
+ unsigned sdio_reg:17;
+ unsigned activate:1;
+ unsigned prepared:1;
+ unsigned regvalue:8;
+ unsigned response:8;
+ unsigned sparebit:26;
+};
+
+struct vub300_mmc_host {
+ struct usb_device *udev;
+ struct usb_interface *interface;
+ struct kref kref;
+ struct mutex cmd_mutex;
+ struct mutex irq_mutex;
+ char vub_name[3 + (9 * 8) + 4 + 1]; /* max of 7 sdio fn's */
+ u8 cmnd_out_ep; /* EndPoint for commands */
+ u8 cmnd_res_ep; /* EndPoint for responses */
+ u8 data_out_ep; /* EndPoint for out data */
+ u8 data_inp_ep; /* EndPoint for inp data */
+ bool card_powered;
+ bool card_present;
+ bool read_only;
+ bool large_usb_packets;
+ bool app_spec; /* ApplicationSpecific */
+ bool irq_enabled; /* by the MMC CORE */
+ bool irq_disabled; /* in the firmware */
+ unsigned bus_width:4;
+ u8 total_offload_count;
+ u8 dynamic_register_count;
+ u8 resp_len;
+ u32 datasize;
+ int errors;
+ int usb_transport_fail;
+ int usb_timed_out;
+ int irqs_queued;
+ struct sdio_register sdio_register[16];
+ struct offload_interrupt_function_register {
+#define MAXREGBITS 4
+#define MAXREGS (1<<MAXREGBITS)
+#define MAXREGMASK (MAXREGS-1)
+ u8 offload_count;
+ u32 offload_point;
+ struct offload_registers_access reg[MAXREGS];
+ } fn[8];
+ u16 fbs[8]; /* Function Block Size */
+ struct mmc_command *cmd;
+ struct mmc_request *req;
+ struct mmc_data *data;
+ struct mmc_host *mmc;
+ struct urb *urb;
+ struct urb *command_out_urb;
+ struct urb *command_res_urb;
+ struct completion command_complete;
+ struct completion irqpoll_complete;
+ union sd_command cmnd;
+ union sd_response resp;
+ struct timer_list sg_transfer_timer;
+ struct usb_sg_request sg_request;
+ struct timer_list inactivity_timer;
+ struct work_struct deadwork;
+ struct work_struct cmndwork;
+ struct delayed_work pollwork;
+ struct host_controller_info hc_info;
+ struct sd_status_header system_port_status;
+ u8 padded_buffer[64];
+};
+
+#define kref_to_vub300_mmc_host(d) container_of(d, struct vub300_mmc_host, kref)
+#define SET_TRANSFER_PSEUDOCODE 21
+#define SET_INTERRUPT_PSEUDOCODE 20
+#define SET_FAILURE_MODE 18
+#define SET_ROM_WAIT_STATES 16
+#define SET_IRQ_ENABLE 13
+#define SET_CLOCK_SPEED 11
+#define SET_FUNCTION_BLOCK_SIZE 9
+#define SET_SD_DATA_MODE 6
+#define SET_SD_POWER 4
+#define ENTER_DFU_MODE 3
+#define GET_HC_INF0 1
+#define GET_SYSTEM_PORT_STATUS 0
+
+static void vub300_delete(struct kref *kref)
+{ /* kref callback - softirq */
+ struct vub300_mmc_host *vub300 = kref_to_vub300_mmc_host(kref);
+ struct mmc_host *mmc = vub300->mmc;
+ usb_free_urb(vub300->command_out_urb);
+ vub300->command_out_urb = NULL;
+ usb_free_urb(vub300->command_res_urb);
+ vub300->command_res_urb = NULL;
+ usb_put_dev(vub300->udev);
+ mmc_free_host(mmc);
+ /*
+ * and hence also frees vub300
+ * which is contained at the end of struct mmc
+ */
+}
+
+static void vub300_queue_cmnd_work(struct vub300_mmc_host *vub300)
+{
+ kref_get(&vub300->kref);
+ if (queue_work(cmndworkqueue, &vub300->cmndwork)) {
+ /*
+ * then the cmndworkqueue was not previously
+ * running and the above get ref is obvious
+ * required and will be put when the thread
+ * terminates by a specific call
+ */
+ } else {
+ /*
+ * the cmndworkqueue was already running from
+ * a previous invocation and thus to keep the
+ * kref counts correct we must undo the get
+ */
+ kref_put(&vub300->kref, vub300_delete);
+ }
+}
+
+static void vub300_queue_poll_work(struct vub300_mmc_host *vub300, int delay)
+{
+ kref_get(&vub300->kref);
+ if (queue_delayed_work(pollworkqueue, &vub300->pollwork, delay)) {
+ /*
+ * then the pollworkqueue was not previously
+ * running and the above get ref is obvious
+ * required and will be put when the thread
+ * terminates by a specific call
+ */
+ } else {
+ /*
+ * the pollworkqueue was already running from
+ * a previous invocation and thus to keep the
+ * kref counts correct we must undo the get
+ */
+ kref_put(&vub300->kref, vub300_delete);
+ }
+}
+
+static void vub300_queue_dead_work(struct vub300_mmc_host *vub300)
+{
+ kref_get(&vub300->kref);
+ if (queue_work(deadworkqueue, &vub300->deadwork)) {
+ /*
+ * then the deadworkqueue was not previously
+ * running and the above get ref is obvious
+ * required and will be put when the thread
+ * terminates by a specific call
+ */
+ } else {
+ /*
+ * the deadworkqueue was already running from
+ * a previous invocation and thus to keep the
+ * kref counts correct we must undo the get
+ */
+ kref_put(&vub300->kref, vub300_delete);
+ }
+}
+
+static void irqpoll_res_completed(struct urb *urb)
+{ /* urb completion handler - hardirq */
+ struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context;
+ if (urb->status)
+ vub300->usb_transport_fail = urb->status;
+ complete(&vub300->irqpoll_complete);
+}
+
+static void irqpoll_out_completed(struct urb *urb)
+{ /* urb completion handler - hardirq */
+ struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context;
+ if (urb->status) {
+ vub300->usb_transport_fail = urb->status;
+ complete(&vub300->irqpoll_complete);
+ return;
+ } else {
+ int ret;
+ unsigned int pipe =
+ usb_rcvbulkpipe(vub300->udev, vub300->cmnd_res_ep);
+ usb_fill_bulk_urb(vub300->command_res_urb, vub300->udev, pipe,
+ &vub300->resp, sizeof(vub300->resp),
+ irqpoll_res_completed, vub300);
+ vub300->command_res_urb->actual_length = 0;
+ ret = usb_submit_urb(vub300->command_res_urb, GFP_ATOMIC);
+ if (ret) {
+ vub300->usb_transport_fail = ret;
+ complete(&vub300->irqpoll_complete);
+ }
+ return;
+ }
+}
+
+static void send_irqpoll(struct vub300_mmc_host *vub300)
+{
+ /* cmd_mutex is held by vub300_pollwork_thread */
+ int retval;
+ int timeout = 0xFFFF & (0x0001FFFF - firmware_irqpoll_timeout);
+ vub300->cmnd.poll.header_size = 22;
+ vub300->cmnd.poll.header_type = 1;
+ vub300->cmnd.poll.port_number = 0;
+ vub300->cmnd.poll.command_type = 2;
+ vub300->cmnd.poll.poll_timeout_lsb = 0xFF & (unsigned)timeout;
+ vub300->cmnd.poll.poll_timeout_msb = 0xFF & (unsigned)(timeout >> 8);
+ usb_fill_bulk_urb(vub300->command_out_urb, vub300->udev,
+ usb_sndbulkpipe(vub300->udev, vub300->cmnd_out_ep)
+ , &vub300->cmnd, sizeof(vub300->cmnd)
+ , irqpoll_out_completed, vub300);
+ retval = usb_submit_urb(vub300->command_out_urb, GFP_KERNEL);
+ if (0 > retval) {
+ vub300->usb_transport_fail = retval;
+ vub300_queue_poll_work(vub300, 1);
+ complete(&vub300->irqpoll_complete);
+ return;
+ } else {
+ return;
+ }
+}
+
+static void new_system_port_status(struct vub300_mmc_host *vub300)
+{
+ int old_card_present = vub300->card_present;
+ int new_card_present =
+ (0x0001 & vub300->system_port_status.port_flags) ? 1 : 0;
+ vub300->read_only =
+ (0x0010 & vub300->system_port_status.port_flags) ? 1 : 0;
+ if (new_card_present && !old_card_present) {
+ dev_info(&vub300->udev->dev, "card just inserted\n");
+ vub300->card_present = 1;
+ vub300->bus_width = 0;
+ if (disable_offload_processing)
+ strncpy(vub300->vub_name, "EMPTY Processing Disabled",
+ sizeof(vub300->vub_name));
+ else
+ vub300->vub_name[0] = 0;
+ mmc_detect_change(vub300->mmc, 1);
+ } else if (!new_card_present && old_card_present) {
+ dev_info(&vub300->udev->dev, "card just ejected\n");
+ vub300->card_present = 0;
+ mmc_detect_change(vub300->mmc, 0);
+ } else {
+ /* no change */
+ }
+}
+
+static void __add_offloaded_reg_to_fifo(struct vub300_mmc_host *vub300,
+ struct offload_registers_access
+ *register_access, u8 func)
+{
+ u8 r = vub300->fn[func].offload_point + vub300->fn[func].offload_count;
+ memcpy(&vub300->fn[func].reg[MAXREGMASK & r], register_access,
+ sizeof(struct offload_registers_access));
+ vub300->fn[func].offload_count += 1;
+ vub300->total_offload_count += 1;
+}
+
+static void add_offloaded_reg(struct vub300_mmc_host *vub300,
+ struct offload_registers_access *register_access)
+{
+ u32 Register = ((0x03 & register_access->command_byte[0]) << 15)
+ | ((0xFF & register_access->command_byte[1]) << 7)
+ | ((0xFE & register_access->command_byte[2]) >> 1);
+ u8 func = ((0x70 & register_access->command_byte[0]) >> 4);
+ u8 regs = vub300->dynamic_register_count;
+ u8 i = 0;
+ while (0 < regs-- && 1 == vub300->sdio_register[i].activate) {
+ if (vub300->sdio_register[i].func_num == func &&
+ vub300->sdio_register[i].sdio_reg == Register) {
+ if (vub300->sdio_register[i].prepared == 0)
+ vub300->sdio_register[i].prepared = 1;
+ vub300->sdio_register[i].response =
+ register_access->Respond_Byte[2];
+ vub300->sdio_register[i].regvalue =
+ register_access->Respond_Byte[3];
+ return;
+ } else {
+ i += 1;
+ continue;
+ }
+ };
+ __add_offloaded_reg_to_fifo(vub300, register_access, func);
+}
+
+static void check_vub300_port_status(struct vub300_mmc_host *vub300)
+{
+ /*
+ * cmd_mutex is held by vub300_pollwork_thread,
+ * vub300_deadwork_thread or vub300_cmndwork_thread
+ */
+ int retval;
+ retval =
+ usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0),
+ GET_SYSTEM_PORT_STATUS,
+ USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ 0x0000, 0x0000, &vub300->system_port_status,
+ sizeof(vub300->system_port_status), HZ);
+ if (sizeof(vub300->system_port_status) == retval)
+ new_system_port_status(vub300);
+}
+
+static void __vub300_irqpoll_response(struct vub300_mmc_host *vub300)
+{
+ /* cmd_mutex is held by vub300_pollwork_thread */
+ if (vub300->command_res_urb->actual_length == 0)
+ return;
+
+ switch (vub300->resp.common.header_type) {
+ case RESPONSE_INTERRUPT:
+ mutex_lock(&vub300->irq_mutex);
+ if (vub300->irq_enabled)
+ mmc_signal_sdio_irq(vub300->mmc);
+ else
+ vub300->irqs_queued += 1;
+ vub300->irq_disabled = 1;
+ mutex_unlock(&vub300->irq_mutex);
+ break;
+ case RESPONSE_ERROR:
+ if (vub300->resp.error.error_code == SD_ERROR_NO_DEVICE)
+ check_vub300_port_status(vub300);
+ break;
+ case RESPONSE_STATUS:
+ vub300->system_port_status = vub300->resp.status;
+ new_system_port_status(vub300);
+ if (!vub300->card_present)
+ vub300_queue_poll_work(vub300, HZ / 5);
+ break;
+ case RESPONSE_IRQ_DISABLED:
+ {
+ int offloaded_data_length = vub300->resp.common.header_size - 3;
+ int register_count = offloaded_data_length >> 3;
+ int ri = 0;
+ while (register_count--) {
+ add_offloaded_reg(vub300, &vub300->resp.irq.reg[ri]);
+ ri += 1;
+ }
+ mutex_lock(&vub300->irq_mutex);
+ if (vub300->irq_enabled)
+ mmc_signal_sdio_irq(vub300->mmc);
+ else
+ vub300->irqs_queued += 1;
+ vub300->irq_disabled = 1;
+ mutex_unlock(&vub300->irq_mutex);
+ break;
+ }
+ case RESPONSE_IRQ_ENABLED:
+ {
+ int offloaded_data_length = vub300->resp.common.header_size - 3;
+ int register_count = offloaded_data_length >> 3;
+ int ri = 0;
+ while (register_count--) {
+ add_offloaded_reg(vub300, &vub300->resp.irq.reg[ri]);
+ ri += 1;
+ }
+ mutex_lock(&vub300->irq_mutex);
+ if (vub300->irq_enabled)
+ mmc_signal_sdio_irq(vub300->mmc);
+ else if (vub300->irqs_queued)
+ vub300->irqs_queued += 1;
+ else
+ vub300->irqs_queued += 1;
+ vub300->irq_disabled = 0;
+ mutex_unlock(&vub300->irq_mutex);
+ break;
+ }
+ case RESPONSE_NO_INTERRUPT:
+ vub300_queue_poll_work(vub300, 1);
+ break;
+ default:
+ break;
+ }
+}
+
+static void __do_poll(struct vub300_mmc_host *vub300)
+{
+ /* cmd_mutex is held by vub300_pollwork_thread */
+ long commretval;
+ mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+ init_completion(&vub300->irqpoll_complete);
+ send_irqpoll(vub300);
+ commretval = wait_for_completion_timeout(&vub300->irqpoll_complete,
+ msecs_to_jiffies(500));
+ if (vub300->usb_transport_fail) {
+ /* no need to do anything */
+ } else if (commretval == 0) {
+ vub300->usb_timed_out = 1;
+ usb_kill_urb(vub300->command_out_urb);
+ usb_kill_urb(vub300->command_res_urb);
+ } else if (commretval < 0) {
+ vub300_queue_poll_work(vub300, 1);
+ } else { /* commretval > 0 */
+ __vub300_irqpoll_response(vub300);
+ }
+}
+
+/* this thread runs only when the driver
+ * is trying to poll the device for an IRQ
+ */
+static void vub300_pollwork_thread(struct work_struct *work)
+{ /* NOT irq */
+ struct vub300_mmc_host *vub300 = container_of(work,
+ struct vub300_mmc_host, pollwork.work);
+ if (!vub300->interface) {
+ kref_put(&vub300->kref, vub300_delete);
+ return;
+ }
+ mutex_lock(&vub300->cmd_mutex);
+ if (vub300->cmd) {
+ vub300_queue_poll_work(vub300, 1);
+ } else if (!vub300->card_present) {
+ /* no need to do anything */
+ } else { /* vub300->card_present */
+ mutex_lock(&vub300->irq_mutex);
+ if (!vub300->irq_enabled) {
+ mutex_unlock(&vub300->irq_mutex);
+ } else if (vub300->irqs_queued) {
+ vub300->irqs_queued -= 1;
+ mmc_signal_sdio_irq(vub300->mmc);
+ mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+ mutex_unlock(&vub300->irq_mutex);
+ } else { /* NOT vub300->irqs_queued */
+ mutex_unlock(&vub300->irq_mutex);
+ __do_poll(vub300);
+ }
+ }
+ mutex_unlock(&vub300->cmd_mutex);
+ kref_put(&vub300->kref, vub300_delete);
+}
+
+static void vub300_deadwork_thread(struct work_struct *work)
+{ /* NOT irq */
+ struct vub300_mmc_host *vub300 =
+ container_of(work, struct vub300_mmc_host, deadwork);
+ if (!vub300->interface) {
+ kref_put(&vub300->kref, vub300_delete);
+ return;
+ }
+ mutex_lock(&vub300->cmd_mutex);
+ if (vub300->cmd) {
+ /*
+ * a command got in as the inactivity
+ * timer expired - so we just let the
+ * processing of the command show if
+ * the device is dead
+ */
+ } else if (vub300->card_present) {
+ check_vub300_port_status(vub300);
+ } else if (vub300->mmc && vub300->mmc->card &&
+ mmc_card_present(vub300->mmc->card)) {
+ /*
+ * the MMC core must not have responded
+ * to the previous indication - lets
+ * hope that it eventually does so we
+ * will just ignore this for now
+ */
+ } else {
+ check_vub300_port_status(vub300);
+ }
+ mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+ mutex_unlock(&vub300->cmd_mutex);
+ kref_put(&vub300->kref, vub300_delete);
+}
+
+static void vub300_inactivity_timer_expired(unsigned long data)
+{ /* softirq */
+ struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)data;
+ if (!vub300->interface) {
+ kref_put(&vub300->kref, vub300_delete);
+ } else if (vub300->cmd) {
+ mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+ } else {
+ vub300_queue_dead_work(vub300);
+ mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+ }
+}
+
+static int vub300_response_error(u8 error_code)
+{
+ switch (error_code) {
+ case SD_ERROR_PIO_TIMEOUT:
+ case SD_ERROR_1BIT_TIMEOUT:
+ case SD_ERROR_4BIT_TIMEOUT:
+ return -ETIMEDOUT;
+ case SD_ERROR_STAT_DATA:
+ case SD_ERROR_OVERRUN:
+ case SD_ERROR_STAT_CMD:
+ case SD_ERROR_STAT_CMD_TIMEOUT:
+ case SD_ERROR_SDCRDY_STUCK:
+ case SD_ERROR_UNHANDLED:
+ case SD_ERROR_1BIT_CRC_WRONG:
+ case SD_ERROR_4BIT_CRC_WRONG:
+ case SD_ERROR_1BIT_CRC_ERROR:
+ case SD_ERROR_4BIT_CRC_ERROR:
+ case SD_ERROR_NO_CMD_ENDBIT:
+ case SD_ERROR_NO_1BIT_DATEND:
+ case SD_ERROR_NO_4BIT_DATEND:
+ case SD_ERROR_1BIT_DATA_TIMEOUT:
+ case SD_ERROR_4BIT_DATA_TIMEOUT:
+ case SD_ERROR_1BIT_UNEXPECTED_TIMEOUT:
+ case SD_ERROR_4BIT_UNEXPECTED_TIMEOUT:
+ return -EILSEQ;
+ case 33:
+ return -EILSEQ;
+ case SD_ERROR_ILLEGAL_COMMAND:
+ return -EINVAL;
+ case SD_ERROR_NO_DEVICE:
+ return -ENOMEDIUM;
+ default:
+ return -ENODEV;
+ }
+}
+
+static void command_res_completed(struct urb *urb)
+{ /* urb completion handler - hardirq */
+ struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context;
+ if (urb->status) {
+ /* we have to let the initiator handle the error */
+ } else if (vub300->command_res_urb->actual_length == 0) {
+ /*
+ * we have seen this happen once or twice and
+ * we suspect a buggy USB host controller
+ */
+ } else if (!vub300->data) {
+ /* this means that the command (typically CMD52) suceeded */
+ } else if (vub300->resp.common.header_type != 0x02) {
+ /*
+ * this is an error response from the VUB300 chip
+ * and we let the initiator handle it
+ */
+ } else if (vub300->urb) {
+ vub300->cmd->error =
+ vub300_response_error(vub300->resp.error.error_code);
+ usb_unlink_urb(vub300->urb);
+ } else {
+ vub300->cmd->error =
+ vub300_response_error(vub300->resp.error.error_code);
+ usb_sg_cancel(&vub300->sg_request);
+ }
+ complete(&vub300->command_complete); /* got_response_in */
+}
+
+static void command_out_completed(struct urb *urb)
+{ /* urb completion handler - hardirq */
+ struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context;
+ if (urb->status) {
+ complete(&vub300->command_complete);
+ } else {
+ int ret;
+ unsigned int pipe =
+ usb_rcvbulkpipe(vub300->udev, vub300->cmnd_res_ep);
+ usb_fill_bulk_urb(vub300->command_res_urb, vub300->udev, pipe,
+ &vub300->resp, sizeof(vub300->resp),
+ command_res_completed, vub300);
+ vub300->command_res_urb->actual_length = 0;
+ ret = usb_submit_urb(vub300->command_res_urb, GFP_ATOMIC);
+ if (ret == 0) {
+ /*
+ * the urb completion handler will call
+ * our completion handler
+ */
+ } else {
+ /*
+ * and thus we only call it directly
+ * when it will not be called
+ */
+ complete(&vub300->command_complete);
+ }
+ }
+}
+
+/*
+ * the STUFF bits are masked out for the comparisons
+ */
+static void snoop_block_size_and_bus_width(struct vub300_mmc_host *vub300,
+ u32 cmd_arg)
+{
+ if ((0xFBFFFE00 & cmd_arg) == 0x80022200)
+ vub300->fbs[1] = (cmd_arg << 8) | (0x00FF & vub300->fbs[1]);
+ else if ((0xFBFFFE00 & cmd_arg) == 0x80022000)
+ vub300->fbs[1] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[1]);
+ else if ((0xFBFFFE00 & cmd_arg) == 0x80042200)
+ vub300->fbs[2] = (cmd_arg << 8) | (0x00FF & vub300->fbs[2]);
+ else if ((0xFBFFFE00 & cmd_arg) == 0x80042000)
+ vub300->fbs[2] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[2]);
+ else if ((0xFBFFFE00 & cmd_arg) == 0x80062200)
+ vub300->fbs[3] = (cmd_arg << 8) | (0x00FF & vub300->fbs[3]);
+ else if ((0xFBFFFE00 & cmd_arg) == 0x80062000)
+ vub300->fbs[3] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[3]);
+ else if ((0xFBFFFE00 & cmd_arg) == 0x80082200)
+ vub300->fbs[4] = (cmd_arg << 8) | (0x00FF & vub300->fbs[4]);
+ else if ((0xFBFFFE00 & cmd_arg) == 0x80082000)
+ vub300->fbs[4] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[4]);
+ else if ((0xFBFFFE00 & cmd_arg) == 0x800A2200)
+ vub300->fbs[5] = (cmd_arg << 8) | (0x00FF & vub300->fbs[5]);
+ else if ((0xFBFFFE00 & cmd_arg) == 0x800A2000)
+ vub300->fbs[5] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[5]);
+ else if ((0xFBFFFE00 & cmd_arg) == 0x800C2200)
+ vub300->fbs[6] = (cmd_arg << 8) | (0x00FF & vub300->fbs[6]);
+ else if ((0xFBFFFE00 & cmd_arg) == 0x800C2000)
+ vub300->fbs[6] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[6]);
+ else if ((0xFBFFFE00 & cmd_arg) == 0x800E2200)
+ vub300->fbs[7] = (cmd_arg << 8) | (0x00FF & vub300->fbs[7]);
+ else if ((0xFBFFFE00 & cmd_arg) == 0x800E2000)
+ vub300->fbs[7] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[7]);
+ else if ((0xFBFFFE03 & cmd_arg) == 0x80000E00)
+ vub300->bus_width = 1;
+ else if ((0xFBFFFE03 & cmd_arg) == 0x80000E02)
+ vub300->bus_width = 4;
+}
+
+static void send_command(struct vub300_mmc_host *vub300)
+{
+ /* cmd_mutex is held by vub300_cmndwork_thread */
+ struct mmc_command *cmd = vub300->cmd;
+ struct mmc_data *data = vub300->data;
+ int retval;
+ int i;
+ u8 response_type;
+ if (vub300->app_spec) {
+ switch (cmd->opcode) {
+ case 6:
+ response_type = SDRT_1;
+ vub300->resp_len = 6;
+ if (0x00000000 == (0x00000003 & cmd->arg))
+ vub300->bus_width = 1;
+ else if (0x00000002 == (0x00000003 & cmd->arg))
+ vub300->bus_width = 4;
+ else
+ dev_err(&vub300->udev->dev,
+ "unexpected ACMD6 bus_width=%d\n",
+ 0x00000003 & cmd->arg);
+ break;
+ case 13:
+ response_type = SDRT_1;
+ vub300->resp_len = 6;
+ break;
+ case 22:
+ response_type = SDRT_1;
+ vub300->resp_len = 6;
+ break;
+ case 23:
+ response_type = SDRT_1;
+ vub300->resp_len = 6;
+ break;
+ case 41:
+ response_type = SDRT_3;
+ vub300->resp_len = 6;
+ break;
+ case 42:
+ response_type = SDRT_1;
+ vub300->resp_len = 6;
+ break;
+ case 51:
+ response_type = SDRT_1;
+ vub300->resp_len = 6;
+ break;
+ case 55:
+ response_type = SDRT_1;
+ vub300->resp_len = 6;
+ break;
+ default:
+ vub300->resp_len = 0;
+ cmd->error = -EINVAL;
+ complete(&vub300->command_complete);
+ return;
+ }
+ vub300->app_spec = 0;
+ } else {
+ switch (cmd->opcode) {
+ case 0:
+ response_type = SDRT_NONE;
+ vub300->resp_len = 0;
+ break;
+ case 1:
+ response_type = SDRT_3;
+ vub300->resp_len = 6;
+ break;
+ case 2:
+ response_type = SDRT_2;
+ vub300->resp_len = 17;
+ break;
+ case 3:
+ response_type = SDRT_6;
+ vub300->resp_len = 6;
+ break;
+ case 4:
+ response_type = SDRT_NONE;
+ vub300->resp_len = 0;
+ break;
+ case 5:
+ response_type = SDRT_4;
+ vub300->resp_len = 6;
+ break;
+ case 6:
+ response_type = SDRT_1;
+ vub300->resp_len = 6;
+ break;
+ case 7:
+ response_type = SDRT_1B;
+ vub300->resp_len = 6;
+ break;
+ case 8:
+ response_type = SDRT_7;
+ vub300->resp_len = 6;
+ break;
+ case 9:
+ response_type = SDRT_2;
+ vub300->resp_len = 17;
+ break;
+ case 10:
+ response_type = SDRT_2;
+ vub300->resp_len = 17;
+ break;
+ case 12:
+ response_type = SDRT_1B;
+ vub300->resp_len = 6;
+ break;
+ case 13:
+ response_type = SDRT_1;
+ vub300->resp_len = 6;
+ break;
+ case 15:
+ response_type = SDRT_NONE;
+ vub300->resp_len = 0;
+ break;
+ case 16:
+ for (i = 0; i < ARRAY_SIZE(vub300->fbs); i++)
+ vub300->fbs[i] = 0xFFFF & cmd->arg;
+ response_type = SDRT_1;
+ vub300->resp_len = 6;
+ break;
+ case 17:
+ case 18:
+ case 24:
+ case 25:
+ case 27:
+ response_type = SDRT_1;
+ vub300->resp_len = 6;
+ break;
+ case 28:
+ case 29:
+ response_type = SDRT_1B;
+ vub300->resp_len = 6;
+ break;
+ case 30:
+ case 32:
+ case 33:
+ response_type = SDRT_1;
+ vub300->resp_len = 6;
+ break;
+ case 38:
+ response_type = SDRT_1B;
+ vub300->resp_len = 6;
+ break;
+ case 42:
+ response_type = SDRT_1;
+ vub300->resp_len = 6;
+ break;
+ case 52:
+ response_type = SDRT_5;
+ vub300->resp_len = 6;
+ snoop_block_size_and_bus_width(vub300, cmd->arg);
+ break;
+ case 53:
+ response_type = SDRT_5;
+ vub300->resp_len = 6;
+ break;
+ case 55:
+ response_type = SDRT_1;
+ vub300->resp_len = 6;
+ vub300->app_spec = 1;
+ break;
+ case 56:
+ response_type = SDRT_1;
+ vub300->resp_len = 6;
+ break;
+ default:
+ vub300->resp_len = 0;
+ cmd->error = -EINVAL;
+ complete(&vub300->command_complete);
+ return;
+ }
+ }
+ /*
+ * it is a shame that we can not use "sizeof(struct sd_command_header)"
+ * this is because the packet _must_ be padded to 64 bytes
+ */
+ vub300->cmnd.head.header_size = 20;
+ vub300->cmnd.head.header_type = 0x00;
+ vub300->cmnd.head.port_number = 0; /* "0" means port 1 */
+ vub300->cmnd.head.command_type = 0x00; /* standard read command */
+ vub300->cmnd.head.response_type = response_type;
+ vub300->cmnd.head.command_index = cmd->opcode;
+ vub300->cmnd.head.arguments[0] = cmd->arg >> 24;
+ vub300->cmnd.head.arguments[1] = cmd->arg >> 16;
+ vub300->cmnd.head.arguments[2] = cmd->arg >> 8;
+ vub300->cmnd.head.arguments[3] = cmd->arg >> 0;
+ if (cmd->opcode == 52) {
+ int fn = 0x7 & (cmd->arg >> 28);
+ vub300->cmnd.head.block_count[0] = 0;
+ vub300->cmnd.head.block_count[1] = 0;
+ vub300->cmnd.head.block_size[0] = (vub300->fbs[fn] >> 8) & 0xFF;
+ vub300->cmnd.head.block_size[1] = (vub300->fbs[fn] >> 0) & 0xFF;
+ vub300->cmnd.head.command_type = 0x00;
+ vub300->cmnd.head.transfer_size[0] = 0;
+ vub300->cmnd.head.transfer_size[1] = 0;
+ vub300->cmnd.head.transfer_size[2] = 0;
+ vub300->cmnd.head.transfer_size[3] = 0;
+ } else if (!data) {
+ vub300->cmnd.head.block_count[0] = 0;
+ vub300->cmnd.head.block_count[1] = 0;
+ vub300->cmnd.head.block_size[0] = (vub300->fbs[0] >> 8) & 0xFF;
+ vub300->cmnd.head.block_size[1] = (vub300->fbs[0] >> 0) & 0xFF;
+ vub300->cmnd.head.command_type = 0x00;
+ vub300->cmnd.head.transfer_size[0] = 0;
+ vub300->cmnd.head.transfer_size[1] = 0;
+ vub300->cmnd.head.transfer_size[2] = 0;
+ vub300->cmnd.head.transfer_size[3] = 0;
+ } else if (cmd->opcode == 53) {
+ int fn = 0x7 & (cmd->arg >> 28);
+ if (0x08 & vub300->cmnd.head.arguments[0]) { /* BLOCK MODE */
+ vub300->cmnd.head.block_count[0] =
+ (data->blocks >> 8) & 0xFF;
+ vub300->cmnd.head.block_count[1] =
+ (data->blocks >> 0) & 0xFF;
+ vub300->cmnd.head.block_size[0] =
+ (data->blksz >> 8) & 0xFF;
+ vub300->cmnd.head.block_size[1] =
+ (data->blksz >> 0) & 0xFF;
+ } else { /* BYTE MODE */
+ vub300->cmnd.head.block_count[0] = 0;
+ vub300->cmnd.head.block_count[1] = 0;
+ vub300->cmnd.head.block_size[0] =
+ (vub300->datasize >> 8) & 0xFF;
+ vub300->cmnd.head.block_size[1] =
+ (vub300->datasize >> 0) & 0xFF;
+ }
+ vub300->cmnd.head.command_type =
+ (MMC_DATA_READ & data->flags) ? 0x00 : 0x80;
+ vub300->cmnd.head.transfer_size[0] =
+ (vub300->datasize >> 24) & 0xFF;
+ vub300->cmnd.head.transfer_size[1] =
+ (vub300->datasize >> 16) & 0xFF;
+ vub300->cmnd.head.transfer_size[2] =
+ (vub300->datasize >> 8) & 0xFF;
+ vub300->cmnd.head.transfer_size[3] =
+ (vub300->datasize >> 0) & 0xFF;
+ if (vub300->datasize < vub300->fbs[fn]) {
+ vub300->cmnd.head.block_count[0] = 0;
+ vub300->cmnd.head.block_count[1] = 0;
+ }
+ } else {
+ vub300->cmnd.head.block_count[0] = (data->blocks >> 8) & 0xFF;
+ vub300->cmnd.head.block_count[1] = (data->blocks >> 0) & 0xFF;
+ vub300->cmnd.head.block_size[0] = (data->blksz >> 8) & 0xFF;
+ vub300->cmnd.head.block_size[1] = (data->blksz >> 0) & 0xFF;
+ vub300->cmnd.head.command_type =
+ (MMC_DATA_READ & data->flags) ? 0x00 : 0x80;
+ vub300->cmnd.head.transfer_size[0] =
+ (vub300->datasize >> 24) & 0xFF;
+ vub300->cmnd.head.transfer_size[1] =
+ (vub300->datasize >> 16) & 0xFF;
+ vub300->cmnd.head.transfer_size[2] =
+ (vub300->datasize >> 8) & 0xFF;
+ vub300->cmnd.head.transfer_size[3] =
+ (vub300->datasize >> 0) & 0xFF;
+ if (vub300->datasize < vub300->fbs[0]) {
+ vub300->cmnd.head.block_count[0] = 0;
+ vub300->cmnd.head.block_count[1] = 0;
+ }
+ }
+ if (vub300->cmnd.head.block_size[0] || vub300->cmnd.head.block_size[1]) {
+ u16 block_size = vub300->cmnd.head.block_size[1] |
+ (vub300->cmnd.head.block_size[0] << 8);
+ u16 block_boundary = FIRMWARE_BLOCK_BOUNDARY -
+ (FIRMWARE_BLOCK_BOUNDARY % block_size);
+ vub300->cmnd.head.block_boundary[0] =
+ (block_boundary >> 8) & 0xFF;
+ vub300->cmnd.head.block_boundary[1] =
+ (block_boundary >> 0) & 0xFF;
+ } else {
+ vub300->cmnd.head.block_boundary[0] = 0;
+ vub300->cmnd.head.block_boundary[1] = 0;
+ }
+ usb_fill_bulk_urb(vub300->command_out_urb, vub300->udev,
+ usb_sndbulkpipe(vub300->udev, vub300->cmnd_out_ep),
+ &vub300->cmnd, sizeof(vub300->cmnd),
+ command_out_completed, vub300);
+ retval = usb_submit_urb(vub300->command_out_urb, GFP_KERNEL);
+ if (retval < 0) {
+ cmd->error = retval;
+ complete(&vub300->command_complete);
+ return;
+ } else {
+ return;
+ }
+}
+
+/*
+ * timer callback runs in atomic mode
+ * so it cannot call usb_kill_urb()
+ */
+static void vub300_sg_timed_out(unsigned long data)
+{
+ struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)data;
+ vub300->usb_timed_out = 1;
+ usb_sg_cancel(&vub300->sg_request);
+ usb_unlink_urb(vub300->command_out_urb);
+ usb_unlink_urb(vub300->command_res_urb);
+}
+
+static u16 roundup_to_multiple_of_64(u16 number)
+{
+ return 0xFFC0 & (0x3F + number);
+}
+
+/*
+ * this is a separate function to solve the 80 column width restriction
+ */
+static void __download_offload_pseudocode(struct vub300_mmc_host *vub300,
+ const struct firmware *fw)
+{
+ u8 register_count = 0;
+ u16 ts = 0;
+ u16 interrupt_size = 0;
+ const u8 *data = fw->data;
+ int size = fw->size;
+ u8 c;
+ dev_info(&vub300->udev->dev, "using %s for SDIO offload processing\n",
+ vub300->vub_name);
+ do {
+ c = *data++;
+ } while (size-- && c); /* skip comment */
+ dev_info(&vub300->udev->dev, "using offload firmware %s %s\n", fw->data,
+ vub300->vub_name);
+ if (size < 4) {
+ dev_err(&vub300->udev->dev,
+ "corrupt offload pseudocode in firmware %s\n",
+ vub300->vub_name);
+ strncpy(vub300->vub_name, "corrupt offload pseudocode",
+ sizeof(vub300->vub_name));
+ return;
+ }
+ interrupt_size += *data++;
+ size -= 1;
+ interrupt_size <<= 8;
+ interrupt_size += *data++;
+ size -= 1;
+ if (interrupt_size < size) {
+ u16 xfer_length = roundup_to_multiple_of_64(interrupt_size);
+ u8 *xfer_buffer = kmalloc(xfer_length, GFP_KERNEL);
+ if (xfer_buffer) {
+ int retval;
+ memcpy(xfer_buffer, data, interrupt_size);
+ memset(xfer_buffer + interrupt_size, 0,
+ xfer_length - interrupt_size);
+ size -= interrupt_size;
+ data += interrupt_size;
+ retval =
+ usb_control_msg(vub300->udev,
+ usb_sndctrlpipe(vub300->udev, 0),
+ SET_INTERRUPT_PSEUDOCODE,
+ USB_DIR_OUT | USB_TYPE_VENDOR |
+ USB_RECIP_DEVICE, 0x0000, 0x0000,
+ xfer_buffer, xfer_length, HZ);
+ kfree(xfer_buffer);
+ if (retval < 0) {
+ strncpy(vub300->vub_name,
+ "SDIO pseudocode download failed",
+ sizeof(vub300->vub_name));
+ return;
+ }
+ } else {
+ dev_err(&vub300->udev->dev,
+ "not enough memory for xfer buffer to send"
+ " INTERRUPT_PSEUDOCODE for %s %s\n", fw->data,
+ vub300->vub_name);
+ strncpy(vub300->vub_name,
+ "SDIO interrupt pseudocode download failed",
+ sizeof(vub300->vub_name));
+ return;
+ }
+ } else {
+ dev_err(&vub300->udev->dev,
+ "corrupt interrupt pseudocode in firmware %s %s\n",
+ fw->data, vub300->vub_name);
+ strncpy(vub300->vub_name, "corrupt interrupt pseudocode",
+ sizeof(vub300->vub_name));
+ return;
+ }
+ ts += *data++;
+ size -= 1;
+ ts <<= 8;
+ ts += *data++;
+ size -= 1;
+ if (ts < size) {
+ u16 xfer_length = roundup_to_multiple_of_64(ts);
+ u8 *xfer_buffer = kmalloc(xfer_length, GFP_KERNEL);
+ if (xfer_buffer) {
+ int retval;
+ memcpy(xfer_buffer, data, ts);
+ memset(xfer_buffer + ts, 0,
+ xfer_length - ts);
+ size -= ts;
+ data += ts;
+ retval =
+ usb_control_msg(vub300->udev,
+ usb_sndctrlpipe(vub300->udev, 0),
+ SET_TRANSFER_PSEUDOCODE,
+ USB_DIR_OUT | USB_TYPE_VENDOR |
+ USB_RECIP_DEVICE, 0x0000, 0x0000,
+ xfer_buffer, xfer_length, HZ);
+ kfree(xfer_buffer);
+ if (retval < 0) {
+ strncpy(vub300->vub_name,
+ "SDIO pseudocode download failed",
+ sizeof(vub300->vub_name));
+ return;
+ }
+ } else {
+ dev_err(&vub300->udev->dev,
+ "not enough memory for xfer buffer to send"
+ " TRANSFER_PSEUDOCODE for %s %s\n", fw->data,
+ vub300->vub_name);
+ strncpy(vub300->vub_name,
+ "SDIO transfer pseudocode download failed",
+ sizeof(vub300->vub_name));
+ return;
+ }
+ } else {
+ dev_err(&vub300->udev->dev,
+ "corrupt transfer pseudocode in firmware %s %s\n",
+ fw->data, vub300->vub_name);
+ strncpy(vub300->vub_name, "corrupt transfer pseudocode",
+ sizeof(vub300->vub_name));
+ return;
+ }
+ register_count += *data++;
+ size -= 1;
+ if (register_count * 4 == size) {
+ int I = vub300->dynamic_register_count = register_count;
+ int i = 0;
+ while (I--) {
+ unsigned int func_num = 0;
+ vub300->sdio_register[i].func_num = *data++;
+ size -= 1;
+ func_num += *data++;
+ size -= 1;
+ func_num <<= 8;
+ func_num += *data++;
+ size -= 1;
+ func_num <<= 8;
+ func_num += *data++;
+ size -= 1;
+ vub300->sdio_register[i].sdio_reg = func_num;
+ vub300->sdio_register[i].activate = 1;
+ vub300->sdio_register[i].prepared = 0;
+ i += 1;
+ }
+ dev_info(&vub300->udev->dev,
+ "initialized %d dynamic pseudocode registers\n",
+ vub300->dynamic_register_count);
+ return;
+ } else {
+ dev_err(&vub300->udev->dev,
+ "corrupt dynamic registers in firmware %s\n",
+ vub300->vub_name);
+ strncpy(vub300->vub_name, "corrupt dynamic registers",
+ sizeof(vub300->vub_name));
+ return;
+ }
+}
+
+/*
+ * if the binary containing the EMPTY PseudoCode can not be found
+ * vub300->vub_name is set anyway in order to prevent an automatic retry
+ */
+static void download_offload_pseudocode(struct vub300_mmc_host *vub300)
+{
+ struct mmc_card *card = vub300->mmc->card;
+ int sdio_funcs = card->sdio_funcs;
+ const struct firmware *fw = NULL;
+ int l = snprintf(vub300->vub_name, sizeof(vub300->vub_name),
+ "vub_%04X%04X", card->cis.vendor, card->cis.device);
+ int n = 0;
+ int retval;
+ for (n = 0; n < sdio_funcs; n++) {
+ struct sdio_func *sf = card->sdio_func[n];
+ l += snprintf(vub300->vub_name + l,
+ sizeof(vub300->vub_name) - l, "_%04X%04X",
+ sf->vendor, sf->device);
+ };
+ snprintf(vub300->vub_name + l, sizeof(vub300->vub_name) - l, ".bin");
+ dev_info(&vub300->udev->dev, "requesting offload firmware %s\n",
+ vub300->vub_name);
+ retval = request_firmware(&fw, vub300->vub_name, &card->dev);
+ if (retval < 0) {
+ strncpy(vub300->vub_name, "vub_default.bin",
+ sizeof(vub300->vub_name));
+ retval = request_firmware(&fw, vub300->vub_name, &card->dev);
+ if (retval < 0) {
+ strncpy(vub300->vub_name,
+ "no SDIO offload firmware found",
+ sizeof(vub300->vub_name));
+ } else {
+ __download_offload_pseudocode(vub300, fw);
+ release_firmware(fw);
+ }
+ } else {
+ __download_offload_pseudocode(vub300, fw);
+ release_firmware(fw);
+ }
+}
+
+static void vub300_usb_bulk_msg_completion(struct urb *urb)
+{ /* urb completion handler - hardirq */
+ complete((struct completion *)urb->context);
+}
+
+static int vub300_usb_bulk_msg(struct vub300_mmc_host *vub300,
+ unsigned int pipe, void *data, int len,
+ int *actual_length, int timeout_msecs)
+{
+ /* cmd_mutex is held by vub300_cmndwork_thread */
+ struct usb_device *usb_dev = vub300->udev;
+ struct completion done;
+ int retval;
+ vub300->urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!vub300->urb)
+ return -ENOMEM;
+ usb_fill_bulk_urb(vub300->urb, usb_dev, pipe, data, len,
+ vub300_usb_bulk_msg_completion, NULL);
+ init_completion(&done);
+ vub300->urb->context = &done;
+ vub300->urb->actual_length = 0;
+ retval = usb_submit_urb(vub300->urb, GFP_KERNEL);
+ if (unlikely(retval))
+ goto out;
+ if (!wait_for_completion_timeout
+ (&done, msecs_to_jiffies(timeout_msecs))) {
+ retval = -ETIMEDOUT;
+ usb_kill_urb(vub300->urb);
+ } else {
+ retval = vub300->urb->status;
+ }
+out:
+ *actual_length = vub300->urb->actual_length;
+ usb_free_urb(vub300->urb);
+ vub300->urb = NULL;
+ return retval;
+}
+
+static int __command_read_data(struct vub300_mmc_host *vub300,
+ struct mmc_command *cmd, struct mmc_data *data)
+{
+ /* cmd_mutex is held by vub300_cmndwork_thread */
+ int linear_length = vub300->datasize;
+ int padded_length = vub300->large_usb_packets ?
+ ((511 + linear_length) >> 9) << 9 :
+ ((63 + linear_length) >> 6) << 6;
+ if ((padded_length == linear_length) || !pad_input_to_usb_pkt) {
+ int result;
+ unsigned pipe;
+ pipe = usb_rcvbulkpipe(vub300->udev, vub300->data_inp_ep);
+ result = usb_sg_init(&vub300->sg_request, vub300->udev,
+ pipe, 0, data->sg,
+ data->sg_len, 0, GFP_KERNEL);
+ if (result < 0) {
+ usb_unlink_urb(vub300->command_out_urb);
+ usb_unlink_urb(vub300->command_res_urb);
+ cmd->error = result;
+ data->bytes_xfered = 0;
+ return 0;
+ } else {
+ vub300->sg_transfer_timer.expires =
+ jiffies + msecs_to_jiffies(2000 +
+ (linear_length / 16384));
+ add_timer(&vub300->sg_transfer_timer);
+ usb_sg_wait(&vub300->sg_request);
+ del_timer(&vub300->sg_transfer_timer);
+ if (vub300->sg_request.status < 0) {
+ cmd->error = vub300->sg_request.status;
+ data->bytes_xfered = 0;
+ return 0;
+ } else {
+ data->bytes_xfered = vub300->datasize;
+ return linear_length;
+ }
+ }
+ } else {
+ u8 *buf = kmalloc(padded_length, GFP_KERNEL);
+ if (buf) {
+ int result;
+ unsigned pipe = usb_rcvbulkpipe(vub300->udev,
+ vub300->data_inp_ep);
+ int actual_length = 0;
+ result = vub300_usb_bulk_msg(vub300, pipe, buf,
+ padded_length, &actual_length,
+ 2000 + (padded_length / 16384));
+ if (result < 0) {
+ cmd->error = result;
+ data->bytes_xfered = 0;
+ kfree(buf);
+ return 0;
+ } else if (actual_length < linear_length) {
+ cmd->error = -EREMOTEIO;
+ data->bytes_xfered = 0;
+ kfree(buf);
+ return 0;
+ } else {
+ sg_copy_from_buffer(data->sg, data->sg_len, buf,
+ linear_length);
+ kfree(buf);
+ data->bytes_xfered = vub300->datasize;
+ return linear_length;
+ }
+ } else {
+ cmd->error = -ENOMEM;
+ data->bytes_xfered = 0;
+ return 0;
+ }
+ }
+}
+
+static int __command_write_data(struct vub300_mmc_host *vub300,
+ struct mmc_command *cmd, struct mmc_data *data)
+{
+ /* cmd_mutex is held by vub300_cmndwork_thread */
+ unsigned pipe = usb_sndbulkpipe(vub300->udev, vub300->data_out_ep);
+ int linear_length = vub300->datasize;
+ int modulo_64_length = linear_length & 0x003F;
+ int modulo_512_length = linear_length & 0x01FF;
+ if (linear_length < 64) {
+ int result;
+ int actual_length;
+ sg_copy_to_buffer(data->sg, data->sg_len,
+ vub300->padded_buffer,
+ sizeof(vub300->padded_buffer));
+ memset(vub300->padded_buffer + linear_length, 0,
+ sizeof(vub300->padded_buffer) - linear_length);
+ result = vub300_usb_bulk_msg(vub300, pipe, vub300->padded_buffer,
+ sizeof(vub300->padded_buffer),
+ &actual_length, 2000 +
+ (sizeof(vub300->padded_buffer) /
+ 16384));
+ if (result < 0) {
+ cmd->error = result;
+ data->bytes_xfered = 0;
+ } else {
+ data->bytes_xfered = vub300->datasize;
+ }
+ } else if ((!vub300->large_usb_packets && (0 < modulo_64_length)) ||
+ (vub300->large_usb_packets && (64 > modulo_512_length))
+ ) { /* don't you just love these work-rounds */
+ int padded_length = ((63 + linear_length) >> 6) << 6;
+ u8 *buf = kmalloc(padded_length, GFP_KERNEL);
+ if (buf) {
+ int result;
+ int actual_length;
+ sg_copy_to_buffer(data->sg, data->sg_len, buf,
+ padded_length);
+ memset(buf + linear_length, 0,
+ padded_length - linear_length);
+ result =
+ vub300_usb_bulk_msg(vub300, pipe, buf,
+ padded_length, &actual_length,
+ 2000 + padded_length / 16384);
+ kfree(buf);
+ if (result < 0) {
+ cmd->error = result;
+ data->bytes_xfered = 0;
+ } else {
+ data->bytes_xfered = vub300->datasize;
+ }
+ } else {
+ cmd->error = -ENOMEM;
+ data->bytes_xfered = 0;
+ }
+ } else { /* no data padding required */
+ int result;
+ unsigned char buf[64 * 4];
+ sg_copy_to_buffer(data->sg, data->sg_len, buf, sizeof(buf));
+ result = usb_sg_init(&vub300->sg_request, vub300->udev,
+ pipe, 0, data->sg,
+ data->sg_len, 0, GFP_KERNEL);
+ if (result < 0) {
+ usb_unlink_urb(vub300->command_out_urb);
+ usb_unlink_urb(vub300->command_res_urb);
+ cmd->error = result;
+ data->bytes_xfered = 0;
+ } else {
+ vub300->sg_transfer_timer.expires =
+ jiffies + msecs_to_jiffies(2000 +
+ linear_length / 16384);
+ add_timer(&vub300->sg_transfer_timer);
+ usb_sg_wait(&vub300->sg_request);
+ if (cmd->error) {
+ data->bytes_xfered = 0;
+ } else {
+ del_timer(&vub300->sg_transfer_timer);
+ if (vub300->sg_request.status < 0) {
+ cmd->error = vub300->sg_request.status;
+ data->bytes_xfered = 0;
+ } else {
+ data->bytes_xfered = vub300->datasize;
+ }
+ }
+ }
+ }
+ return linear_length;
+}
+
+static void __vub300_command_response(struct vub300_mmc_host *vub300,
+ struct mmc_command *cmd,
+ struct mmc_data *data, int data_length)
+{
+ /* cmd_mutex is held by vub300_cmndwork_thread */
+ long respretval;
+ int msec_timeout = 1000 + data_length / 4;
+ respretval =
+ wait_for_completion_timeout(&vub300->command_complete,
+ msecs_to_jiffies(msec_timeout));
+ if (respretval == 0) { /* TIMED OUT */
+ /* we don't know which of "out" and "res" if any failed */
+ int result;
+ vub300->usb_timed_out = 1;
+ usb_kill_urb(vub300->command_out_urb);
+ usb_kill_urb(vub300->command_res_urb);
+ cmd->error = -ETIMEDOUT;
+ result = usb_lock_device_for_reset(vub300->udev,
+ vub300->interface);
+ if (result == 0) {
+ result = usb_reset_device(vub300->udev);
+ usb_unlock_device(vub300->udev);
+ }
+ } else if (respretval < 0) {
+ /* we don't know which of "out" and "res" if any failed */
+ usb_kill_urb(vub300->command_out_urb);
+ usb_kill_urb(vub300->command_res_urb);
+ cmd->error = respretval;
+ } else if (cmd->error) {
+ /*
+ * the error occured sending the command
+ * or recieving the response
+ */
+ } else if (vub300->command_out_urb->status) {
+ vub300->usb_transport_fail = vub300->command_out_urb->status;
+ cmd->error = -EPROTO == vub300->command_out_urb->status ?
+ -ESHUTDOWN : vub300->command_out_urb->status;
+ } else if (vub300->command_res_urb->status) {
+ vub300->usb_transport_fail = vub300->command_res_urb->status;
+ cmd->error = -EPROTO == vub300->command_res_urb->status ?
+ -ESHUTDOWN : vub300->command_res_urb->status;
+ } else if (vub300->resp.common.header_type == 0x00) {
+ /*
+ * the command completed successfully
+ * and there was no piggybacked data
+ */
+ } else if (vub300->resp.common.header_type == RESPONSE_ERROR) {
+ cmd->error =
+ vub300_response_error(vub300->resp.error.error_code);
+ if (vub300->data)
+ usb_sg_cancel(&vub300->sg_request);
+ } else if (vub300->resp.common.header_type == RESPONSE_PIGGYBACKED) {
+ int offloaded_data_length =
+ vub300->resp.common.header_size -
+ sizeof(struct sd_register_header);
+ int register_count = offloaded_data_length >> 3;
+ int ri = 0;
+ while (register_count--) {
+ add_offloaded_reg(vub300, &vub300->resp.pig.reg[ri]);
+ ri += 1;
+ }
+ vub300->resp.common.header_size =
+ sizeof(struct sd_register_header);
+ vub300->resp.common.header_type = 0x00;
+ cmd->error = 0;
+ } else if (vub300->resp.common.header_type == RESPONSE_PIG_DISABLED) {
+ int offloaded_data_length =
+ vub300->resp.common.header_size -
+ sizeof(struct sd_register_header);
+ int register_count = offloaded_data_length >> 3;
+ int ri = 0;
+ while (register_count--) {
+ add_offloaded_reg(vub300, &vub300->resp.pig.reg[ri]);
+ ri += 1;
+ }
+ mutex_lock(&vub300->irq_mutex);
+ if (vub300->irqs_queued) {
+ vub300->irqs_queued += 1;
+ } else if (vub300->irq_enabled) {
+ vub300->irqs_queued += 1;
+ vub300_queue_poll_work(vub300, 0);
+ } else {
+ vub300->irqs_queued += 1;
+ }
+ vub300->irq_disabled = 1;
+ mutex_unlock(&vub300->irq_mutex);
+ vub300->resp.common.header_size =
+ sizeof(struct sd_register_header);
+ vub300->resp.common.header_type = 0x00;
+ cmd->error = 0;
+ } else if (vub300->resp.common.header_type == RESPONSE_PIG_ENABLED) {
+ int offloaded_data_length =
+ vub300->resp.common.header_size -
+ sizeof(struct sd_register_header);
+ int register_count = offloaded_data_length >> 3;
+ int ri = 0;
+ while (register_count--) {
+ add_offloaded_reg(vub300, &vub300->resp.pig.reg[ri]);
+ ri += 1;
+ }
+ mutex_lock(&vub300->irq_mutex);
+ if (vub300->irqs_queued) {
+ vub300->irqs_queued += 1;
+ } else if (vub300->irq_enabled) {
+ vub300->irqs_queued += 1;
+ vub300_queue_poll_work(vub300, 0);
+ } else {
+ vub300->irqs_queued += 1;
+ }
+ vub300->irq_disabled = 0;
+ mutex_unlock(&vub300->irq_mutex);
+ vub300->resp.common.header_size =
+ sizeof(struct sd_register_header);
+ vub300->resp.common.header_type = 0x00;
+ cmd->error = 0;
+ } else {
+ cmd->error = -EINVAL;
+ }
+}
+
+static void construct_request_response(struct vub300_mmc_host *vub300,
+ struct mmc_command *cmd)
+{
+ int resp_len = vub300->resp_len;
+ int less_cmd = (17 == resp_len) ? resp_len : resp_len - 1;
+ int bytes = 3 & less_cmd;
+ int words = less_cmd >> 2;
+ u8 *r = vub300->resp.response.command_response;
+ if (bytes == 3) {
+ cmd->resp[words] = (r[1 + (words << 2)] << 24)
+ | (r[2 + (words << 2)] << 16)
+ | (r[3 + (words << 2)] << 8);
+ } else if (bytes == 2) {
+ cmd->resp[words] = (r[1 + (words << 2)] << 24)
+ | (r[2 + (words << 2)] << 16);
+ } else if (bytes == 1) {
+ cmd->resp[words] = (r[1 + (words << 2)] << 24);
+ }
+ while (words-- > 0) {
+ cmd->resp[words] = (r[1 + (words << 2)] << 24)
+ | (r[2 + (words << 2)] << 16)
+ | (r[3 + (words << 2)] << 8)
+ | (r[4 + (words << 2)] << 0);
+ }
+ if ((cmd->opcode == 53) && (0x000000FF & cmd->resp[0]))
+ cmd->resp[0] &= 0xFFFFFF00;
+}
+
+/* this thread runs only when there is an upper level command req outstanding */
+static void vub300_cmndwork_thread(struct work_struct *work)
+{
+ struct vub300_mmc_host *vub300 =
+ container_of(work, struct vub300_mmc_host, cmndwork);
+ if (!vub300->interface) {
+ kref_put(&vub300->kref, vub300_delete);
+ return;
+ } else {
+ struct mmc_request *req = vub300->req;
+ struct mmc_command *cmd = vub300->cmd;
+ struct mmc_data *data = vub300->data;
+ int data_length;
+ mutex_lock(&vub300->cmd_mutex);
+ init_completion(&vub300->command_complete);
+ if (likely(vub300->vub_name[0]) || !vub300->mmc->card ||
+ !mmc_card_present(vub300->mmc->card)) {
+ /*
+ * the name of the EMPTY Pseudo firmware file
+ * is used as a flag to indicate that the file
+ * has been already downloaded to the VUB300 chip
+ */
+ } else if (0 == vub300->mmc->card->sdio_funcs) {
+ strncpy(vub300->vub_name, "SD memory device",
+ sizeof(vub300->vub_name));
+ } else {
+ download_offload_pseudocode(vub300);
+ }
+ send_command(vub300);
+ if (!data)
+ data_length = 0;
+ else if (MMC_DATA_READ & data->flags)
+ data_length = __command_read_data(vub300, cmd, data);
+ else
+ data_length = __command_write_data(vub300, cmd, data);
+ __vub300_command_response(vub300, cmd, data, data_length);
+ vub300->req = NULL;
+ vub300->cmd = NULL;
+ vub300->data = NULL;
+ if (cmd->error) {
+ if (cmd->error == -ENOMEDIUM)
+ check_vub300_port_status(vub300);
+ mutex_unlock(&vub300->cmd_mutex);
+ mmc_request_done(vub300->mmc, req);
+ kref_put(&vub300->kref, vub300_delete);
+ return;
+ } else {
+ construct_request_response(vub300, cmd);
+ vub300->resp_len = 0;
+ mutex_unlock(&vub300->cmd_mutex);
+ kref_put(&vub300->kref, vub300_delete);
+ mmc_request_done(vub300->mmc, req);
+ return;
+ }
+ }
+}
+
+static int examine_cyclic_buffer(struct vub300_mmc_host *vub300,
+ struct mmc_command *cmd, u8 Function)
+{
+ /* cmd_mutex is held by vub300_mmc_request */
+ u8 cmd0 = 0xFF & (cmd->arg >> 24);
+ u8 cmd1 = 0xFF & (cmd->arg >> 16);
+ u8 cmd2 = 0xFF & (cmd->arg >> 8);
+ u8 cmd3 = 0xFF & (cmd->arg >> 0);
+ int first = MAXREGMASK & vub300->fn[Function].offload_point;
+ struct offload_registers_access *rf = &vub300->fn[Function].reg[first];
+ if (cmd0 == rf->command_byte[0] &&
+ cmd1 == rf->command_byte[1] &&
+ cmd2 == rf->command_byte[2] &&
+ cmd3 == rf->command_byte[3]) {
+ u8 checksum = 0x00;
+ cmd->resp[1] = checksum << 24;
+ cmd->resp[0] = (rf->Respond_Byte[0] << 24)
+ | (rf->Respond_Byte[1] << 16)
+ | (rf->Respond_Byte[2] << 8)
+ | (rf->Respond_Byte[3] << 0);
+ vub300->fn[Function].offload_point += 1;
+ vub300->fn[Function].offload_count -= 1;
+ vub300->total_offload_count -= 1;
+ return 1;
+ } else {
+ int delta = 1; /* because it does not match the first one */
+ u8 register_count = vub300->fn[Function].offload_count - 1;
+ u32 register_point = vub300->fn[Function].offload_point + 1;
+ while (0 < register_count) {
+ int point = MAXREGMASK & register_point;
+ struct offload_registers_access *r =
+ &vub300->fn[Function].reg[point];
+ if (cmd0 == r->command_byte[0] &&
+ cmd1 == r->command_byte[1] &&
+ cmd2 == r->command_byte[2] &&
+ cmd3 == r->command_byte[3]) {
+ u8 checksum = 0x00;
+ cmd->resp[1] = checksum << 24;
+ cmd->resp[0] = (r->Respond_Byte[0] << 24)
+ | (r->Respond_Byte[1] << 16)
+ | (r->Respond_Byte[2] << 8)
+ | (r->Respond_Byte[3] << 0);
+ vub300->fn[Function].offload_point += delta;
+ vub300->fn[Function].offload_count -= delta;
+ vub300->total_offload_count -= delta;
+ return 1;
+ } else {
+ register_point += 1;
+ register_count -= 1;
+ delta += 1;
+ continue;
+ }
+ }
+ return 0;
+ }
+}
+
+static int satisfy_request_from_offloaded_data(struct vub300_mmc_host *vub300,
+ struct mmc_command *cmd)
+{
+ /* cmd_mutex is held by vub300_mmc_request */
+ u8 regs = vub300->dynamic_register_count;
+ u8 i = 0;
+ u8 func = FUN(cmd);
+ u32 reg = REG(cmd);
+ while (0 < regs--) {
+ if ((vub300->sdio_register[i].func_num == func) &&
+ (vub300->sdio_register[i].sdio_reg == reg)) {
+ if (!vub300->sdio_register[i].prepared) {
+ return 0;
+ } else if ((0x80000000 & cmd->arg) == 0x80000000) {
+ /*
+ * a write to a dynamic register
+ * nullifies our offloaded value
+ */
+ vub300->sdio_register[i].prepared = 0;
+ return 0;
+ } else {
+ u8 checksum = 0x00;
+ u8 rsp0 = 0x00;
+ u8 rsp1 = 0x00;
+ u8 rsp2 = vub300->sdio_register[i].response;
+ u8 rsp3 = vub300->sdio_register[i].regvalue;
+ vub300->sdio_register[i].prepared = 0;
+ cmd->resp[1] = checksum << 24;
+ cmd->resp[0] = (rsp0 << 24)
+ | (rsp1 << 16)
+ | (rsp2 << 8)
+ | (rsp3 << 0);
+ return 1;
+ }
+ } else {
+ i += 1;
+ continue;
+ }
+ };
+ if (vub300->total_offload_count == 0)
+ return 0;
+ else if (vub300->fn[func].offload_count == 0)
+ return 0;
+ else
+ return examine_cyclic_buffer(vub300, cmd, func);
+}
+
+static void vub300_mmc_request(struct mmc_host *mmc, struct mmc_request *req)
+{ /* NOT irq */
+ struct mmc_command *cmd = req->cmd;
+ struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+ if (!vub300->interface) {
+ cmd->error = -ESHUTDOWN;
+ mmc_request_done(mmc, req);
+ return;
+ } else {
+ struct mmc_data *data = req->data;
+ if (!vub300->card_powered) {
+ cmd->error = -ENOMEDIUM;
+ mmc_request_done(mmc, req);
+ return;
+ }
+ if (!vub300->card_present) {
+ cmd->error = -ENOMEDIUM;
+ mmc_request_done(mmc, req);
+ return;
+ }
+ if (vub300->usb_transport_fail) {
+ cmd->error = vub300->usb_transport_fail;
+ mmc_request_done(mmc, req);
+ return;
+ }
+ if (!vub300->interface) {
+ cmd->error = -ENODEV;
+ mmc_request_done(mmc, req);
+ return;
+ }
+ kref_get(&vub300->kref);
+ mutex_lock(&vub300->cmd_mutex);
+ mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+ /*
+ * for performance we have to return immediately
+ * if the requested data has been offloaded
+ */
+ if (cmd->opcode == 52 &&
+ satisfy_request_from_offloaded_data(vub300, cmd)) {
+ cmd->error = 0;
+ mutex_unlock(&vub300->cmd_mutex);
+ kref_put(&vub300->kref, vub300_delete);
+ mmc_request_done(mmc, req);
+ return;
+ } else {
+ vub300->cmd = cmd;
+ vub300->req = req;
+ vub300->data = data;
+ if (data)
+ vub300->datasize = data->blksz * data->blocks;
+ else
+ vub300->datasize = 0;
+ vub300_queue_cmnd_work(vub300);
+ mutex_unlock(&vub300->cmd_mutex);
+ kref_put(&vub300->kref, vub300_delete);
+ /*
+ * the kernel lock diagnostics complain
+ * if the cmd_mutex * is "passed on"
+ * to the cmndwork thread,
+ * so we must release it now
+ * and re-acquire it in the cmndwork thread
+ */
+ }
+ }
+}
+
+static void __set_clock_speed(struct vub300_mmc_host *vub300, u8 buf[8],
+ struct mmc_ios *ios)
+{
+ int buf_array_size = 8; /* ARRAY_SIZE(buf) does not work !!! */
+ int retval;
+ u32 kHzClock;
+ if (ios->clock >= 48000000)
+ kHzClock = 48000;
+ else if (ios->clock >= 24000000)
+ kHzClock = 24000;
+ else if (ios->clock >= 20000000)
+ kHzClock = 20000;
+ else if (ios->clock >= 15000000)
+ kHzClock = 15000;
+ else if (ios->clock >= 200000)
+ kHzClock = 200;
+ else
+ kHzClock = 0;
+ {
+ int i;
+ u64 c = kHzClock;
+ for (i = 0; i < buf_array_size; i++) {
+ buf[i] = c;
+ c >>= 8;
+ }
+ }
+ retval =
+ usb_control_msg(vub300->udev, usb_sndctrlpipe(vub300->udev, 0),
+ SET_CLOCK_SPEED,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ 0x00, 0x00, buf, buf_array_size, HZ);
+ if (retval != 8) {
+ dev_err(&vub300->udev->dev, "SET_CLOCK_SPEED"
+ " %dkHz failed with retval=%d\n", kHzClock, retval);
+ } else {
+ dev_dbg(&vub300->udev->dev, "SET_CLOCK_SPEED"
+ " %dkHz\n", kHzClock);
+ }
+}
+
+static void vub300_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{ /* NOT irq */
+ struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+ if (!vub300->interface)
+ return;
+ kref_get(&vub300->kref);
+ mutex_lock(&vub300->cmd_mutex);
+ if ((ios->power_mode == MMC_POWER_OFF) && vub300->card_powered) {
+ vub300->card_powered = 0;
+ usb_control_msg(vub300->udev, usb_sndctrlpipe(vub300->udev, 0),
+ SET_SD_POWER,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ 0x0000, 0x0000, NULL, 0, HZ);
+ /* must wait for the VUB300 u-proc to boot up */
+ msleep(600);
+ } else if ((ios->power_mode == MMC_POWER_UP) && !vub300->card_powered) {
+ usb_control_msg(vub300->udev, usb_sndctrlpipe(vub300->udev, 0),
+ SET_SD_POWER,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ 0x0001, 0x0000, NULL, 0, HZ);
+ msleep(600);
+ vub300->card_powered = 1;
+ } else if (ios->power_mode == MMC_POWER_ON) {
+ u8 *buf = kmalloc(8, GFP_KERNEL);
+ if (buf) {
+ __set_clock_speed(vub300, buf, ios);
+ kfree(buf);
+ }
+ } else {
+ /* this should mean no change of state */
+ }
+ mutex_unlock(&vub300->cmd_mutex);
+ kref_put(&vub300->kref, vub300_delete);
+}
+
+static int vub300_mmc_get_ro(struct mmc_host *mmc)
+{
+ struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+ return vub300->read_only;
+}
+
+static void vub300_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{ /* NOT irq */
+ struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+ if (!vub300->interface)
+ return;
+ kref_get(&vub300->kref);
+ if (enable) {
+ mutex_lock(&vub300->irq_mutex);
+ if (vub300->irqs_queued) {
+ vub300->irqs_queued -= 1;
+ mmc_signal_sdio_irq(vub300->mmc);
+ } else if (vub300->irq_disabled) {
+ vub300->irq_disabled = 0;
+ vub300->irq_enabled = 1;
+ vub300_queue_poll_work(vub300, 0);
+ } else if (vub300->irq_enabled) {
+ /* this should not happen, so we will just ignore it */
+ } else {
+ vub300->irq_enabled = 1;
+ vub300_queue_poll_work(vub300, 0);
+ }
+ mutex_unlock(&vub300->irq_mutex);
+ } else {
+ vub300->irq_enabled = 0;
+ }
+ kref_put(&vub300->kref, vub300_delete);
+}
+
+void vub300_init_card(struct mmc_host *mmc, struct mmc_card *card)
+{ /* NOT irq */
+ struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+ dev_info(&vub300->udev->dev, "NO host QUIRKS for this card\n");
+}
+
+static struct mmc_host_ops vub300_mmc_ops = {
+ .request = vub300_mmc_request,
+ .set_ios = vub300_mmc_set_ios,
+ .get_ro = vub300_mmc_get_ro,
+ .enable_sdio_irq = vub300_enable_sdio_irq,
+ .init_card = vub300_init_card,
+};
+
+static int vub300_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{ /* NOT irq */
+ struct vub300_mmc_host *vub300;
+ struct usb_host_interface *iface_desc;
+ struct usb_device *udev = usb_get_dev(interface_to_usbdev(interface));
+ int i;
+ int retval = -ENOMEM;
+ struct urb *command_out_urb;
+ struct urb *command_res_urb;
+ struct mmc_host *mmc;
+ char manufacturer[48];
+ char product[32];
+ char serial_number[32];
+ usb_string(udev, udev->descriptor.iManufacturer, manufacturer,
+ sizeof(manufacturer));
+ usb_string(udev, udev->descriptor.iProduct, product, sizeof(product));
+ usb_string(udev, udev->descriptor.iSerialNumber, serial_number,
+ sizeof(serial_number));
+ dev_info(&udev->dev, "probing VID:PID(%04X:%04X) %s %s %s\n",
+ udev->descriptor.idVendor, udev->descriptor.idProduct,
+ manufacturer, product, serial_number);
+ command_out_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!command_out_urb) {
+ retval = -ENOMEM;
+ dev_err(&udev->dev, "not enough memory for command_out_urb\n");
+ goto error0;
+ }
+ command_res_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!command_res_urb) {
+ retval = -ENOMEM;
+ dev_err(&udev->dev, "not enough memory for command_res_urb\n");
+ goto error1;
+ }
+ /* this also allocates memory for our VUB300 mmc host device */
+ mmc = mmc_alloc_host(sizeof(struct vub300_mmc_host), &udev->dev);
+ if (!mmc) {
+ retval = -ENOMEM;
+ dev_err(&udev->dev, "not enough memory for the mmc_host\n");
+ goto error4;
+ }
+ /* MMC core transfer sizes tunable parameters */
+ mmc->caps = 0;
+ if (!force_1_bit_data_xfers)
+ mmc->caps |= MMC_CAP_4_BIT_DATA;
+ if (!force_polling_for_irqs)
+ mmc->caps |= MMC_CAP_SDIO_IRQ;
+ mmc->caps &= ~MMC_CAP_NEEDS_POLL;
+ /*
+ * MMC_CAP_NEEDS_POLL causes core.c:mmc_rescan() to poll
+ * for devices which results in spurious CMD7's being
+ * issued which stops some SDIO cards from working
+ */
+ if (limit_speed_to_24_MHz) {
+ mmc->caps |= MMC_CAP_MMC_HIGHSPEED;
+ mmc->caps |= MMC_CAP_SD_HIGHSPEED;
+ mmc->f_max = 24000000;
+ dev_info(&udev->dev, "limiting SDIO speed to 24_MHz\n");
+ } else {
+ mmc->caps |= MMC_CAP_MMC_HIGHSPEED;
+ mmc->caps |= MMC_CAP_SD_HIGHSPEED;
+ mmc->f_max = 48000000;
+ }
+ mmc->f_min = 200000;
+ mmc->max_blk_count = 511;
+ mmc->max_blk_size = 512;
+ mmc->max_segs = 128;
+ if (force_max_req_size)
+ mmc->max_req_size = force_max_req_size * 1024;
+ else
+ mmc->max_req_size = 64 * 1024;
+ mmc->max_seg_size = mmc->max_req_size;
+ mmc->ocr_avail = 0;
+ mmc->ocr_avail |= MMC_VDD_165_195;
+ mmc->ocr_avail |= MMC_VDD_20_21;
+ mmc->ocr_avail |= MMC_VDD_21_22;
+ mmc->ocr_avail |= MMC_VDD_22_23;
+ mmc->ocr_avail |= MMC_VDD_23_24;
+ mmc->ocr_avail |= MMC_VDD_24_25;
+ mmc->ocr_avail |= MMC_VDD_25_26;
+ mmc->ocr_avail |= MMC_VDD_26_27;
+ mmc->ocr_avail |= MMC_VDD_27_28;
+ mmc->ocr_avail |= MMC_VDD_28_29;
+ mmc->ocr_avail |= MMC_VDD_29_30;
+ mmc->ocr_avail |= MMC_VDD_30_31;
+ mmc->ocr_avail |= MMC_VDD_31_32;
+ mmc->ocr_avail |= MMC_VDD_32_33;
+ mmc->ocr_avail |= MMC_VDD_33_34;
+ mmc->ocr_avail |= MMC_VDD_34_35;
+ mmc->ocr_avail |= MMC_VDD_35_36;
+ mmc->ops = &vub300_mmc_ops;
+ vub300 = mmc_priv(mmc);
+ vub300->mmc = mmc;
+ vub300->card_powered = 0;
+ vub300->bus_width = 0;
+ vub300->cmnd.head.block_size[0] = 0x00;
+ vub300->cmnd.head.block_size[1] = 0x00;
+ vub300->app_spec = 0;
+ mutex_init(&vub300->cmd_mutex);
+ mutex_init(&vub300->irq_mutex);
+ vub300->command_out_urb = command_out_urb;
+ vub300->command_res_urb = command_res_urb;
+ vub300->usb_timed_out = 0;
+ vub300->dynamic_register_count = 0;
+
+ for (i = 0; i < ARRAY_SIZE(vub300->fn); i++) {
+ vub300->fn[i].offload_point = 0;
+ vub300->fn[i].offload_count = 0;
+ }
+
+ vub300->total_offload_count = 0;
+ vub300->irq_enabled = 0;
+ vub300->irq_disabled = 0;
+ vub300->irqs_queued = 0;
+
+ for (i = 0; i < ARRAY_SIZE(vub300->sdio_register); i++)
+ vub300->sdio_register[i++].activate = 0;
+
+ vub300->udev = udev;
+ vub300->interface = interface;
+ vub300->cmnd_res_ep = 0;
+ vub300->cmnd_out_ep = 0;
+ vub300->data_inp_ep = 0;
+ vub300->data_out_ep = 0;
+
+ for (i = 0; i < ARRAY_SIZE(vub300->fbs); i++)
+ vub300->fbs[i] = 512;
+
+ /*
+ * set up the endpoint information
+ *
+ * use the first pair of bulk-in and bulk-out
+ * endpoints for Command/Response+Interrupt
+ *
+ * use the second pair of bulk-in and bulk-out
+ * endpoints for Data In/Out
+ */
+ vub300->large_usb_packets = 0;
+ iface_desc = interface->cur_altsetting;
+ for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
+ struct usb_endpoint_descriptor *endpoint =
+ &iface_desc->endpoint[i].desc;
+ dev_info(&vub300->udev->dev,
+ "vub300 testing %s EndPoint(%d) %02X\n",
+ usb_endpoint_is_bulk_in(endpoint) ? "BULK IN" :
+ usb_endpoint_is_bulk_out(endpoint) ? "BULK OUT" :
+ "UNKNOWN", i, endpoint->bEndpointAddress);
+ if (endpoint->wMaxPacketSize > 64)
+ vub300->large_usb_packets = 1;
+ if (usb_endpoint_is_bulk_in(endpoint)) {
+ if (!vub300->cmnd_res_ep) {
+ vub300->cmnd_res_ep =
+ endpoint->bEndpointAddress;
+ } else if (!vub300->data_inp_ep) {
+ vub300->data_inp_ep =
+ endpoint->bEndpointAddress;
+ } else {
+ dev_warn(&vub300->udev->dev,
+ "ignoring"
+ " unexpected bulk_in endpoint");
+ }
+ } else if (usb_endpoint_is_bulk_out(endpoint)) {
+ if (!vub300->cmnd_out_ep) {
+ vub300->cmnd_out_ep =
+ endpoint->bEndpointAddress;
+ } else if (!vub300->data_out_ep) {
+ vub300->data_out_ep =
+ endpoint->bEndpointAddress;
+ } else {
+ dev_warn(&vub300->udev->dev,
+ "ignoring"
+ " unexpected bulk_out endpoint");
+ }
+ } else {
+ dev_warn(&vub300->udev->dev,
+ "vub300 ignoring EndPoint(%d) %02X", i,
+ endpoint->bEndpointAddress);
+ }
+ }
+ if (vub300->cmnd_res_ep && vub300->cmnd_out_ep &&
+ vub300->data_inp_ep && vub300->data_out_ep) {
+ dev_info(&vub300->udev->dev,
+ "vub300 %s packets"
+ " using EndPoints %02X %02X %02X %02X\n",
+ vub300->large_usb_packets ? "LARGE" : "SMALL",
+ vub300->cmnd_out_ep, vub300->cmnd_res_ep,
+ vub300->data_out_ep, vub300->data_inp_ep);
+ /* we have the expected EndPoints */
+ } else {
+ dev_err(&vub300->udev->dev,
+ "Could not find two sets of bulk-in/out endpoint pairs\n");
+ retval = -EINVAL;
+ goto error5;
+ }
+ retval =
+ usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0),
+ GET_HC_INF0,
+ USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ 0x0000, 0x0000, &vub300->hc_info,
+ sizeof(vub300->hc_info), HZ);
+ if (retval < 0)
+ goto error5;
+ retval =
+ usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0),
+ SET_ROM_WAIT_STATES,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ firmware_rom_wait_states, 0x0000, NULL, 0, HZ);
+ if (retval < 0)
+ goto error5;
+ dev_info(&vub300->udev->dev,
+ "operating_mode = %s %s %d MHz %s %d byte USB packets\n",
+ (mmc->caps & MMC_CAP_SDIO_IRQ) ? "IRQs" : "POLL",
+ (mmc->caps & MMC_CAP_4_BIT_DATA) ? "4-bit" : "1-bit",
+ mmc->f_max / 1000000,
+ pad_input_to_usb_pkt ? "padding input data to" : "with",
+ vub300->large_usb_packets ? 512 : 64);
+ retval =
+ usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0),
+ GET_SYSTEM_PORT_STATUS,
+ USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ 0x0000, 0x0000, &vub300->system_port_status,
+ sizeof(vub300->system_port_status), HZ);
+ if (retval < 0) {
+ goto error4;
+ } else if (sizeof(vub300->system_port_status) == retval) {
+ vub300->card_present =
+ (0x0001 & vub300->system_port_status.port_flags) ? 1 : 0;
+ vub300->read_only =
+ (0x0010 & vub300->system_port_status.port_flags) ? 1 : 0;
+ } else {
+ goto error4;
+ }
+ usb_set_intfdata(interface, vub300);
+ INIT_DELAYED_WORK(&vub300->pollwork, vub300_pollwork_thread);
+ INIT_WORK(&vub300->cmndwork, vub300_cmndwork_thread);
+ INIT_WORK(&vub300->deadwork, vub300_deadwork_thread);
+ kref_init(&vub300->kref);
+ init_timer(&vub300->sg_transfer_timer);
+ vub300->sg_transfer_timer.data = (unsigned long)vub300;
+ vub300->sg_transfer_timer.function = vub300_sg_timed_out;
+ kref_get(&vub300->kref);
+ init_timer(&vub300->inactivity_timer);
+ vub300->inactivity_timer.data = (unsigned long)vub300;
+ vub300->inactivity_timer.function = vub300_inactivity_timer_expired;
+ vub300->inactivity_timer.expires = jiffies + HZ;
+ add_timer(&vub300->inactivity_timer);
+ if (vub300->card_present)
+ dev_info(&vub300->udev->dev,
+ "USB vub300 remote SDIO host controller[%d]"
+ "connected with SD/SDIO card inserted\n",
+ interface_to_InterfaceNumber(interface));
+ else
+ dev_info(&vub300->udev->dev,
+ "USB vub300 remote SDIO host controller[%d]"
+ "connected with no SD/SDIO card inserted\n",
+ interface_to_InterfaceNumber(interface));
+ mmc_add_host(mmc);
+ return 0;
+error5:
+ mmc_free_host(mmc);
+ /*
+ * and hence also frees vub300
+ * which is contained at the end of struct mmc
+ */
+error4:
+ usb_free_urb(command_out_urb);
+error1:
+ usb_free_urb(command_res_urb);
+error0:
+ return retval;
+}
+
+static void vub300_disconnect(struct usb_interface *interface)
+{ /* NOT irq */
+ struct vub300_mmc_host *vub300 = usb_get_intfdata(interface);
+ if (!vub300 || !vub300->mmc) {
+ return;
+ } else {
+ struct mmc_host *mmc = vub300->mmc;
+ if (!vub300->mmc) {
+ return;
+ } else {
+ int ifnum = interface_to_InterfaceNumber(interface);
+ usb_set_intfdata(interface, NULL);
+ /* prevent more I/O from starting */
+ vub300->interface = NULL;
+ kref_put(&vub300->kref, vub300_delete);
+ mmc_remove_host(mmc);
+ pr_info("USB vub300 remote SDIO host controller[%d]"
+ " now disconnected", ifnum);
+ return;
+ }
+ }
+}
+
+#ifdef CONFIG_PM
+static int vub300_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct vub300_mmc_host *vub300 = usb_get_intfdata(intf);
+ if (!vub300 || !vub300->mmc) {
+ return 0;
+ } else {
+ struct mmc_host *mmc = vub300->mmc;
+ mmc_suspend_host(mmc);
+ return 0;
+ }
+}
+
+static int vub300_resume(struct usb_interface *intf)
+{
+ struct vub300_mmc_host *vub300 = usb_get_intfdata(intf);
+ if (!vub300 || !vub300->mmc) {
+ return 0;
+ } else {
+ struct mmc_host *mmc = vub300->mmc;
+ mmc_resume_host(mmc);
+ return 0;
+ }
+}
+#else
+#define vub300_suspend NULL
+#define vub300_resume NULL
+#endif
+static int vub300_pre_reset(struct usb_interface *intf)
+{ /* NOT irq */
+ struct vub300_mmc_host *vub300 = usb_get_intfdata(intf);
+ mutex_lock(&vub300->cmd_mutex);
+ return 0;
+}
+
+static int vub300_post_reset(struct usb_interface *intf)
+{ /* NOT irq */
+ struct vub300_mmc_host *vub300 = usb_get_intfdata(intf);
+ /* we are sure no URBs are active - no locking needed */
+ vub300->errors = -EPIPE;
+ mutex_unlock(&vub300->cmd_mutex);
+ return 0;
+}
+
+static struct usb_driver vub300_driver = {
+ .name = "vub300",
+ .probe = vub300_probe,
+ .disconnect = vub300_disconnect,
+ .suspend = vub300_suspend,
+ .resume = vub300_resume,
+ .pre_reset = vub300_pre_reset,
+ .post_reset = vub300_post_reset,
+ .id_table = vub300_table,
+ .supports_autosuspend = 1,
+};
+
+static int __init vub300_init(void)
+{ /* NOT irq */
+ int result;
+
+ pr_info("VUB300 Driver rom wait states = %02X irqpoll timeout = %04X",
+ firmware_rom_wait_states, 0x0FFFF & firmware_irqpoll_timeout);
+ cmndworkqueue = create_singlethread_workqueue("kvub300c");
+ if (!cmndworkqueue) {
+ pr_err("not enough memory for the REQUEST workqueue");
+ result = -ENOMEM;
+ goto out1;
+ }
+ pollworkqueue = create_singlethread_workqueue("kvub300p");
+ if (!pollworkqueue) {
+ pr_err("not enough memory for the IRQPOLL workqueue");
+ result = -ENOMEM;
+ goto out2;
+ }
+ deadworkqueue = create_singlethread_workqueue("kvub300d");
+ if (!deadworkqueue) {
+ pr_err("not enough memory for the EXPIRED workqueue");
+ result = -ENOMEM;
+ goto out3;
+ }
+ result = usb_register(&vub300_driver);
+ if (result) {
+ pr_err("usb_register failed. Error number %d", result);
+ goto out4;
+ }
+ return 0;
+out4:
+ destroy_workqueue(deadworkqueue);
+out3:
+ destroy_workqueue(pollworkqueue);
+out2:
+ destroy_workqueue(cmndworkqueue);
+out1:
+ return result;
+}
+
+static void __exit vub300_exit(void)
+{
+ usb_deregister(&vub300_driver);
+ flush_workqueue(cmndworkqueue);
+ flush_workqueue(pollworkqueue);
+ flush_workqueue(deadworkqueue);
+ destroy_workqueue(cmndworkqueue);
+ destroy_workqueue(pollworkqueue);
+ destroy_workqueue(deadworkqueue);
+}
+
+module_init(vub300_init);
+module_exit(vub300_exit);
+
+MODULE_AUTHOR("Tony Olech <tony.olech@elandigitalsystems.com>");
+MODULE_DESCRIPTION("VUB300 USB to SD/MMC/SDIO adapter driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mmc/host/wbsd.c b/drivers/mmc/host/wbsd.c
new file mode 100644
index 00000000..62e5a4d1
--- /dev/null
+++ b/drivers/mmc/host/wbsd.c
@@ -0,0 +1,2047 @@
+/*
+ * linux/drivers/mmc/host/wbsd.c - Winbond W83L51xD SD/MMC driver
+ *
+ * Copyright (C) 2004-2007 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.
+ *
+ *
+ * Warning!
+ *
+ * Changes to the FIFO system should be done with extreme care since
+ * the hardware is full of bugs related to the FIFO. Known issues are:
+ *
+ * - FIFO size field in FSR is always zero.
+ *
+ * - FIFO interrupts tend not to work as they should. Interrupts are
+ * triggered only for full/empty events, not for threshold values.
+ *
+ * - On APIC systems the FIFO empty interrupt is sometimes lost.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <linux/pnp.h>
+#include <linux/highmem.h>
+#include <linux/mmc/host.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+
+#include <asm/io.h>
+#include <asm/dma.h>
+
+#include "wbsd.h"
+
+#define DRIVER_NAME "wbsd"
+
+#define DBG(x...) \
+ pr_debug(DRIVER_NAME ": " x)
+#define DBGF(f, x...) \
+ pr_debug(DRIVER_NAME " [%s()]: " f, __func__ , ##x)
+
+/*
+ * Device resources
+ */
+
+#ifdef CONFIG_PNP
+
+static const struct pnp_device_id pnp_dev_table[] = {
+ { "WEC0517", 0 },
+ { "WEC0518", 0 },
+ { "", 0 },
+};
+
+MODULE_DEVICE_TABLE(pnp, pnp_dev_table);
+
+#endif /* CONFIG_PNP */
+
+static const int config_ports[] = { 0x2E, 0x4E };
+static const int unlock_codes[] = { 0x83, 0x87 };
+
+static const int valid_ids[] = {
+ 0x7112,
+};
+
+#ifdef CONFIG_PNP
+static unsigned int param_nopnp = 0;
+#else
+static const unsigned int param_nopnp = 1;
+#endif
+static unsigned int param_io = 0x248;
+static unsigned int param_irq = 6;
+static int param_dma = 2;
+
+/*
+ * Basic functions
+ */
+
+static inline void wbsd_unlock_config(struct wbsd_host *host)
+{
+ BUG_ON(host->config == 0);
+
+ outb(host->unlock_code, host->config);
+ outb(host->unlock_code, host->config);
+}
+
+static inline void wbsd_lock_config(struct wbsd_host *host)
+{
+ BUG_ON(host->config == 0);
+
+ outb(LOCK_CODE, host->config);
+}
+
+static inline void wbsd_write_config(struct wbsd_host *host, u8 reg, u8 value)
+{
+ BUG_ON(host->config == 0);
+
+ outb(reg, host->config);
+ outb(value, host->config + 1);
+}
+
+static inline u8 wbsd_read_config(struct wbsd_host *host, u8 reg)
+{
+ BUG_ON(host->config == 0);
+
+ outb(reg, host->config);
+ return inb(host->config + 1);
+}
+
+static inline void wbsd_write_index(struct wbsd_host *host, u8 index, u8 value)
+{
+ outb(index, host->base + WBSD_IDXR);
+ outb(value, host->base + WBSD_DATAR);
+}
+
+static inline u8 wbsd_read_index(struct wbsd_host *host, u8 index)
+{
+ outb(index, host->base + WBSD_IDXR);
+ return inb(host->base + WBSD_DATAR);
+}
+
+/*
+ * Common routines
+ */
+
+static void wbsd_init_device(struct wbsd_host *host)
+{
+ u8 setup, ier;
+
+ /*
+ * Reset chip (SD/MMC part) and fifo.
+ */
+ setup = wbsd_read_index(host, WBSD_IDX_SETUP);
+ setup |= WBSD_FIFO_RESET | WBSD_SOFT_RESET;
+ wbsd_write_index(host, WBSD_IDX_SETUP, setup);
+
+ /*
+ * Set DAT3 to input
+ */
+ setup &= ~WBSD_DAT3_H;
+ wbsd_write_index(host, WBSD_IDX_SETUP, setup);
+ host->flags &= ~WBSD_FIGNORE_DETECT;
+
+ /*
+ * Read back default clock.
+ */
+ host->clk = wbsd_read_index(host, WBSD_IDX_CLK);
+
+ /*
+ * Power down port.
+ */
+ outb(WBSD_POWER_N, host->base + WBSD_CSR);
+
+ /*
+ * Set maximum timeout.
+ */
+ wbsd_write_index(host, WBSD_IDX_TAAC, 0x7F);
+
+ /*
+ * Test for card presence
+ */
+ if (inb(host->base + WBSD_CSR) & WBSD_CARDPRESENT)
+ host->flags |= WBSD_FCARD_PRESENT;
+ else
+ host->flags &= ~WBSD_FCARD_PRESENT;
+
+ /*
+ * Enable interesting interrupts.
+ */
+ ier = 0;
+ ier |= WBSD_EINT_CARD;
+ ier |= WBSD_EINT_FIFO_THRE;
+ ier |= WBSD_EINT_CRC;
+ ier |= WBSD_EINT_TIMEOUT;
+ ier |= WBSD_EINT_TC;
+
+ outb(ier, host->base + WBSD_EIR);
+
+ /*
+ * Clear interrupts.
+ */
+ inb(host->base + WBSD_ISR);
+}
+
+static void wbsd_reset(struct wbsd_host *host)
+{
+ u8 setup;
+
+ printk(KERN_ERR "%s: Resetting chip\n", mmc_hostname(host->mmc));
+
+ /*
+ * Soft reset of chip (SD/MMC part).
+ */
+ setup = wbsd_read_index(host, WBSD_IDX_SETUP);
+ setup |= WBSD_SOFT_RESET;
+ wbsd_write_index(host, WBSD_IDX_SETUP, setup);
+}
+
+static void wbsd_request_end(struct wbsd_host *host, struct mmc_request *mrq)
+{
+ unsigned long dmaflags;
+
+ if (host->dma >= 0) {
+ /*
+ * Release ISA DMA controller.
+ */
+ dmaflags = claim_dma_lock();
+ disable_dma(host->dma);
+ clear_dma_ff(host->dma);
+ release_dma_lock(dmaflags);
+
+ /*
+ * Disable DMA on host.
+ */
+ wbsd_write_index(host, WBSD_IDX_DMA, 0);
+ }
+
+ host->mrq = NULL;
+
+ /*
+ * MMC layer might call back into the driver so first unlock.
+ */
+ spin_unlock(&host->lock);
+ mmc_request_done(host->mmc, mrq);
+ spin_lock(&host->lock);
+}
+
+/*
+ * Scatter/gather functions
+ */
+
+static inline void wbsd_init_sg(struct wbsd_host *host, struct mmc_data *data)
+{
+ /*
+ * Get info. about SG list from data structure.
+ */
+ host->cur_sg = data->sg;
+ host->num_sg = data->sg_len;
+
+ host->offset = 0;
+ host->remain = host->cur_sg->length;
+}
+
+static inline int wbsd_next_sg(struct wbsd_host *host)
+{
+ /*
+ * Skip to next SG entry.
+ */
+ host->cur_sg++;
+ host->num_sg--;
+
+ /*
+ * Any entries left?
+ */
+ if (host->num_sg > 0) {
+ host->offset = 0;
+ host->remain = host->cur_sg->length;
+ }
+
+ return host->num_sg;
+}
+
+static inline char *wbsd_sg_to_buffer(struct wbsd_host *host)
+{
+ return sg_virt(host->cur_sg);
+}
+
+static inline void wbsd_sg_to_dma(struct wbsd_host *host, struct mmc_data *data)
+{
+ unsigned int len, i;
+ struct scatterlist *sg;
+ char *dmabuf = host->dma_buffer;
+ char *sgbuf;
+
+ sg = data->sg;
+ len = data->sg_len;
+
+ for (i = 0; i < len; i++) {
+ sgbuf = sg_virt(&sg[i]);
+ memcpy(dmabuf, sgbuf, sg[i].length);
+ dmabuf += sg[i].length;
+ }
+}
+
+static inline void wbsd_dma_to_sg(struct wbsd_host *host, struct mmc_data *data)
+{
+ unsigned int len, i;
+ struct scatterlist *sg;
+ char *dmabuf = host->dma_buffer;
+ char *sgbuf;
+
+ sg = data->sg;
+ len = data->sg_len;
+
+ for (i = 0; i < len; i++) {
+ sgbuf = sg_virt(&sg[i]);
+ memcpy(sgbuf, dmabuf, sg[i].length);
+ dmabuf += sg[i].length;
+ }
+}
+
+/*
+ * Command handling
+ */
+
+static inline void wbsd_get_short_reply(struct wbsd_host *host,
+ struct mmc_command *cmd)
+{
+ /*
+ * Correct response type?
+ */
+ if (wbsd_read_index(host, WBSD_IDX_RSPLEN) != WBSD_RSP_SHORT) {
+ cmd->error = -EILSEQ;
+ return;
+ }
+
+ cmd->resp[0] = wbsd_read_index(host, WBSD_IDX_RESP12) << 24;
+ cmd->resp[0] |= wbsd_read_index(host, WBSD_IDX_RESP13) << 16;
+ cmd->resp[0] |= wbsd_read_index(host, WBSD_IDX_RESP14) << 8;
+ cmd->resp[0] |= wbsd_read_index(host, WBSD_IDX_RESP15) << 0;
+ cmd->resp[1] = wbsd_read_index(host, WBSD_IDX_RESP16) << 24;
+}
+
+static inline void wbsd_get_long_reply(struct wbsd_host *host,
+ struct mmc_command *cmd)
+{
+ int i;
+
+ /*
+ * Correct response type?
+ */
+ if (wbsd_read_index(host, WBSD_IDX_RSPLEN) != WBSD_RSP_LONG) {
+ cmd->error = -EILSEQ;
+ return;
+ }
+
+ for (i = 0; i < 4; i++) {
+ cmd->resp[i] =
+ wbsd_read_index(host, WBSD_IDX_RESP1 + i * 4) << 24;
+ cmd->resp[i] |=
+ wbsd_read_index(host, WBSD_IDX_RESP2 + i * 4) << 16;
+ cmd->resp[i] |=
+ wbsd_read_index(host, WBSD_IDX_RESP3 + i * 4) << 8;
+ cmd->resp[i] |=
+ wbsd_read_index(host, WBSD_IDX_RESP4 + i * 4) << 0;
+ }
+}
+
+static void wbsd_send_command(struct wbsd_host *host, struct mmc_command *cmd)
+{
+ int i;
+ u8 status, isr;
+
+ /*
+ * Clear accumulated ISR. The interrupt routine
+ * will fill this one with events that occur during
+ * transfer.
+ */
+ host->isr = 0;
+
+ /*
+ * Send the command (CRC calculated by host).
+ */
+ outb(cmd->opcode, host->base + WBSD_CMDR);
+ for (i = 3; i >= 0; i--)
+ outb((cmd->arg >> (i * 8)) & 0xff, host->base + WBSD_CMDR);
+
+ cmd->error = 0;
+
+ /*
+ * Wait for the request to complete.
+ */
+ do {
+ status = wbsd_read_index(host, WBSD_IDX_STATUS);
+ } while (status & WBSD_CARDTRAFFIC);
+
+ /*
+ * Do we expect a reply?
+ */
+ if (cmd->flags & MMC_RSP_PRESENT) {
+ /*
+ * Read back status.
+ */
+ isr = host->isr;
+
+ /* Card removed? */
+ if (isr & WBSD_INT_CARD)
+ cmd->error = -ENOMEDIUM;
+ /* Timeout? */
+ else if (isr & WBSD_INT_TIMEOUT)
+ cmd->error = -ETIMEDOUT;
+ /* CRC? */
+ else if ((cmd->flags & MMC_RSP_CRC) && (isr & WBSD_INT_CRC))
+ cmd->error = -EILSEQ;
+ /* All ok */
+ else {
+ if (cmd->flags & MMC_RSP_136)
+ wbsd_get_long_reply(host, cmd);
+ else
+ wbsd_get_short_reply(host, cmd);
+ }
+ }
+}
+
+/*
+ * Data functions
+ */
+
+static void wbsd_empty_fifo(struct wbsd_host *host)
+{
+ struct mmc_data *data = host->mrq->cmd->data;
+ char *buffer;
+ int i, fsr, fifo;
+
+ /*
+ * Handle excessive data.
+ */
+ if (host->num_sg == 0)
+ return;
+
+ buffer = wbsd_sg_to_buffer(host) + host->offset;
+
+ /*
+ * Drain the fifo. This has a tendency to loop longer
+ * than the FIFO length (usually one block).
+ */
+ while (!((fsr = inb(host->base + WBSD_FSR)) & WBSD_FIFO_EMPTY)) {
+ /*
+ * The size field in the FSR is broken so we have to
+ * do some guessing.
+ */
+ if (fsr & WBSD_FIFO_FULL)
+ fifo = 16;
+ else if (fsr & WBSD_FIFO_FUTHRE)
+ fifo = 8;
+ else
+ fifo = 1;
+
+ for (i = 0; i < fifo; i++) {
+ *buffer = inb(host->base + WBSD_DFR);
+ buffer++;
+ host->offset++;
+ host->remain--;
+
+ data->bytes_xfered++;
+
+ /*
+ * End of scatter list entry?
+ */
+ if (host->remain == 0) {
+ /*
+ * Get next entry. Check if last.
+ */
+ if (!wbsd_next_sg(host))
+ return;
+
+ buffer = wbsd_sg_to_buffer(host);
+ }
+ }
+ }
+
+ /*
+ * This is a very dirty hack to solve a
+ * hardware problem. The chip doesn't trigger
+ * FIFO threshold interrupts properly.
+ */
+ if ((data->blocks * data->blksz - data->bytes_xfered) < 16)
+ tasklet_schedule(&host->fifo_tasklet);
+}
+
+static void wbsd_fill_fifo(struct wbsd_host *host)
+{
+ struct mmc_data *data = host->mrq->cmd->data;
+ char *buffer;
+ int i, fsr, fifo;
+
+ /*
+ * Check that we aren't being called after the
+ * entire buffer has been transferred.
+ */
+ if (host->num_sg == 0)
+ return;
+
+ buffer = wbsd_sg_to_buffer(host) + host->offset;
+
+ /*
+ * Fill the fifo. This has a tendency to loop longer
+ * than the FIFO length (usually one block).
+ */
+ while (!((fsr = inb(host->base + WBSD_FSR)) & WBSD_FIFO_FULL)) {
+ /*
+ * The size field in the FSR is broken so we have to
+ * do some guessing.
+ */
+ if (fsr & WBSD_FIFO_EMPTY)
+ fifo = 0;
+ else if (fsr & WBSD_FIFO_EMTHRE)
+ fifo = 8;
+ else
+ fifo = 15;
+
+ for (i = 16; i > fifo; i--) {
+ outb(*buffer, host->base + WBSD_DFR);
+ buffer++;
+ host->offset++;
+ host->remain--;
+
+ data->bytes_xfered++;
+
+ /*
+ * End of scatter list entry?
+ */
+ if (host->remain == 0) {
+ /*
+ * Get next entry. Check if last.
+ */
+ if (!wbsd_next_sg(host))
+ return;
+
+ buffer = wbsd_sg_to_buffer(host);
+ }
+ }
+ }
+
+ /*
+ * The controller stops sending interrupts for
+ * 'FIFO empty' under certain conditions. So we
+ * need to be a bit more pro-active.
+ */
+ tasklet_schedule(&host->fifo_tasklet);
+}
+
+static void wbsd_prepare_data(struct wbsd_host *host, struct mmc_data *data)
+{
+ u16 blksize;
+ u8 setup;
+ unsigned long dmaflags;
+ unsigned int size;
+
+ /*
+ * Calculate size.
+ */
+ size = data->blocks * data->blksz;
+
+ /*
+ * Check timeout values for overflow.
+ * (Yes, some cards cause this value to overflow).
+ */
+ if (data->timeout_ns > 127000000)
+ wbsd_write_index(host, WBSD_IDX_TAAC, 127);
+ else {
+ wbsd_write_index(host, WBSD_IDX_TAAC,
+ data->timeout_ns / 1000000);
+ }
+
+ if (data->timeout_clks > 255)
+ wbsd_write_index(host, WBSD_IDX_NSAC, 255);
+ else
+ wbsd_write_index(host, WBSD_IDX_NSAC, data->timeout_clks);
+
+ /*
+ * Inform the chip of how large blocks will be
+ * sent. It needs this to determine when to
+ * calculate CRC.
+ *
+ * Space for CRC must be included in the size.
+ * Two bytes are needed for each data line.
+ */
+ if (host->bus_width == MMC_BUS_WIDTH_1) {
+ blksize = data->blksz + 2;
+
+ wbsd_write_index(host, WBSD_IDX_PBSMSB, (blksize >> 4) & 0xF0);
+ wbsd_write_index(host, WBSD_IDX_PBSLSB, blksize & 0xFF);
+ } else if (host->bus_width == MMC_BUS_WIDTH_4) {
+ blksize = data->blksz + 2 * 4;
+
+ wbsd_write_index(host, WBSD_IDX_PBSMSB,
+ ((blksize >> 4) & 0xF0) | WBSD_DATA_WIDTH);
+ wbsd_write_index(host, WBSD_IDX_PBSLSB, blksize & 0xFF);
+ } else {
+ data->error = -EINVAL;
+ return;
+ }
+
+ /*
+ * Clear the FIFO. This is needed even for DMA
+ * transfers since the chip still uses the FIFO
+ * internally.
+ */
+ setup = wbsd_read_index(host, WBSD_IDX_SETUP);
+ setup |= WBSD_FIFO_RESET;
+ wbsd_write_index(host, WBSD_IDX_SETUP, setup);
+
+ /*
+ * DMA transfer?
+ */
+ if (host->dma >= 0) {
+ /*
+ * The buffer for DMA is only 64 kB.
+ */
+ BUG_ON(size > 0x10000);
+ if (size > 0x10000) {
+ data->error = -EINVAL;
+ return;
+ }
+
+ /*
+ * Transfer data from the SG list to
+ * the DMA buffer.
+ */
+ if (data->flags & MMC_DATA_WRITE)
+ wbsd_sg_to_dma(host, data);
+
+ /*
+ * Initialise the ISA DMA controller.
+ */
+ dmaflags = claim_dma_lock();
+ disable_dma(host->dma);
+ clear_dma_ff(host->dma);
+ if (data->flags & MMC_DATA_READ)
+ set_dma_mode(host->dma, DMA_MODE_READ & ~0x40);
+ else
+ set_dma_mode(host->dma, DMA_MODE_WRITE & ~0x40);
+ set_dma_addr(host->dma, host->dma_addr);
+ set_dma_count(host->dma, size);
+
+ enable_dma(host->dma);
+ release_dma_lock(dmaflags);
+
+ /*
+ * Enable DMA on the host.
+ */
+ wbsd_write_index(host, WBSD_IDX_DMA, WBSD_DMA_ENABLE);
+ } else {
+ /*
+ * This flag is used to keep printk
+ * output to a minimum.
+ */
+ host->firsterr = 1;
+
+ /*
+ * Initialise the SG list.
+ */
+ wbsd_init_sg(host, data);
+
+ /*
+ * Turn off DMA.
+ */
+ wbsd_write_index(host, WBSD_IDX_DMA, 0);
+
+ /*
+ * Set up FIFO threshold levels (and fill
+ * buffer if doing a write).
+ */
+ if (data->flags & MMC_DATA_READ) {
+ wbsd_write_index(host, WBSD_IDX_FIFOEN,
+ WBSD_FIFOEN_FULL | 8);
+ } else {
+ wbsd_write_index(host, WBSD_IDX_FIFOEN,
+ WBSD_FIFOEN_EMPTY | 8);
+ wbsd_fill_fifo(host);
+ }
+ }
+
+ data->error = 0;
+}
+
+static void wbsd_finish_data(struct wbsd_host *host, struct mmc_data *data)
+{
+ unsigned long dmaflags;
+ int count;
+ u8 status;
+
+ WARN_ON(host->mrq == NULL);
+
+ /*
+ * Send a stop command if needed.
+ */
+ if (data->stop)
+ wbsd_send_command(host, data->stop);
+
+ /*
+ * Wait for the controller to leave data
+ * transfer state.
+ */
+ do {
+ status = wbsd_read_index(host, WBSD_IDX_STATUS);
+ } while (status & (WBSD_BLOCK_READ | WBSD_BLOCK_WRITE));
+
+ /*
+ * DMA transfer?
+ */
+ if (host->dma >= 0) {
+ /*
+ * Disable DMA on the host.
+ */
+ wbsd_write_index(host, WBSD_IDX_DMA, 0);
+
+ /*
+ * Turn of ISA DMA controller.
+ */
+ dmaflags = claim_dma_lock();
+ disable_dma(host->dma);
+ clear_dma_ff(host->dma);
+ count = get_dma_residue(host->dma);
+ release_dma_lock(dmaflags);
+
+ data->bytes_xfered = host->mrq->data->blocks *
+ host->mrq->data->blksz - count;
+ data->bytes_xfered -= data->bytes_xfered % data->blksz;
+
+ /*
+ * Any leftover data?
+ */
+ if (count) {
+ printk(KERN_ERR "%s: Incomplete DMA transfer. "
+ "%d bytes left.\n",
+ mmc_hostname(host->mmc), count);
+
+ if (!data->error)
+ data->error = -EIO;
+ } else {
+ /*
+ * Transfer data from DMA buffer to
+ * SG list.
+ */
+ if (data->flags & MMC_DATA_READ)
+ wbsd_dma_to_sg(host, data);
+ }
+
+ if (data->error) {
+ if (data->bytes_xfered)
+ data->bytes_xfered -= data->blksz;
+ }
+ }
+
+ wbsd_request_end(host, host->mrq);
+}
+
+/*****************************************************************************\
+ * *
+ * MMC layer callbacks *
+ * *
+\*****************************************************************************/
+
+static void wbsd_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+ struct wbsd_host *host = mmc_priv(mmc);
+ struct mmc_command *cmd;
+
+ /*
+ * Disable tasklets to avoid a deadlock.
+ */
+ spin_lock_bh(&host->lock);
+
+ BUG_ON(host->mrq != NULL);
+
+ cmd = mrq->cmd;
+
+ host->mrq = mrq;
+
+ /*
+ * Check that there is actually a card in the slot.
+ */
+ if (!(host->flags & WBSD_FCARD_PRESENT)) {
+ cmd->error = -ENOMEDIUM;
+ goto done;
+ }
+
+ if (cmd->data) {
+ /*
+ * The hardware is so delightfully stupid that it has a list
+ * of "data" commands. If a command isn't on this list, it'll
+ * just go back to the idle state and won't send any data
+ * interrupts.
+ */
+ switch (cmd->opcode) {
+ case 11:
+ case 17:
+ case 18:
+ case 20:
+ case 24:
+ case 25:
+ case 26:
+ case 27:
+ case 30:
+ case 42:
+ case 56:
+ break;
+
+ /* ACMDs. We don't keep track of state, so we just treat them
+ * like any other command. */
+ case 51:
+ break;
+
+ default:
+#ifdef CONFIG_MMC_DEBUG
+ printk(KERN_WARNING "%s: Data command %d is not "
+ "supported by this controller.\n",
+ mmc_hostname(host->mmc), cmd->opcode);
+#endif
+ cmd->error = -EINVAL;
+
+ goto done;
+ };
+ }
+
+ /*
+ * Does the request include data?
+ */
+ if (cmd->data) {
+ wbsd_prepare_data(host, cmd->data);
+
+ if (cmd->data->error)
+ goto done;
+ }
+
+ wbsd_send_command(host, cmd);
+
+ /*
+ * If this is a data transfer the request
+ * will be finished after the data has
+ * transferred.
+ */
+ if (cmd->data && !cmd->error) {
+ /*
+ * Dirty fix for hardware bug.
+ */
+ if (host->dma == -1)
+ tasklet_schedule(&host->fifo_tasklet);
+
+ spin_unlock_bh(&host->lock);
+
+ return;
+ }
+
+done:
+ wbsd_request_end(host, mrq);
+
+ spin_unlock_bh(&host->lock);
+}
+
+static void wbsd_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct wbsd_host *host = mmc_priv(mmc);
+ u8 clk, setup, pwr;
+
+ spin_lock_bh(&host->lock);
+
+ /*
+ * Reset the chip on each power off.
+ * Should clear out any weird states.
+ */
+ if (ios->power_mode == MMC_POWER_OFF)
+ wbsd_init_device(host);
+
+ if (ios->clock >= 24000000)
+ clk = WBSD_CLK_24M;
+ else if (ios->clock >= 16000000)
+ clk = WBSD_CLK_16M;
+ else if (ios->clock >= 12000000)
+ clk = WBSD_CLK_12M;
+ else
+ clk = WBSD_CLK_375K;
+
+ /*
+ * Only write to the clock register when
+ * there is an actual change.
+ */
+ if (clk != host->clk) {
+ wbsd_write_index(host, WBSD_IDX_CLK, clk);
+ host->clk = clk;
+ }
+
+ /*
+ * Power up card.
+ */
+ if (ios->power_mode != MMC_POWER_OFF) {
+ pwr = inb(host->base + WBSD_CSR);
+ pwr &= ~WBSD_POWER_N;
+ outb(pwr, host->base + WBSD_CSR);
+ }
+
+ /*
+ * MMC cards need to have pin 1 high during init.
+ * It wreaks havoc with the card detection though so
+ * that needs to be disabled.
+ */
+ setup = wbsd_read_index(host, WBSD_IDX_SETUP);
+ if (ios->chip_select == MMC_CS_HIGH) {
+ BUG_ON(ios->bus_width != MMC_BUS_WIDTH_1);
+ setup |= WBSD_DAT3_H;
+ host->flags |= WBSD_FIGNORE_DETECT;
+ } else {
+ if (setup & WBSD_DAT3_H) {
+ setup &= ~WBSD_DAT3_H;
+
+ /*
+ * We cannot resume card detection immediately
+ * because of capacitance and delays in the chip.
+ */
+ mod_timer(&host->ignore_timer, jiffies + HZ / 100);
+ }
+ }
+ wbsd_write_index(host, WBSD_IDX_SETUP, setup);
+
+ /*
+ * Store bus width for later. Will be used when
+ * setting up the data transfer.
+ */
+ host->bus_width = ios->bus_width;
+
+ spin_unlock_bh(&host->lock);
+}
+
+static int wbsd_get_ro(struct mmc_host *mmc)
+{
+ struct wbsd_host *host = mmc_priv(mmc);
+ u8 csr;
+
+ spin_lock_bh(&host->lock);
+
+ csr = inb(host->base + WBSD_CSR);
+ csr |= WBSD_MSLED;
+ outb(csr, host->base + WBSD_CSR);
+
+ mdelay(1);
+
+ csr = inb(host->base + WBSD_CSR);
+ csr &= ~WBSD_MSLED;
+ outb(csr, host->base + WBSD_CSR);
+
+ spin_unlock_bh(&host->lock);
+
+ return !!(csr & WBSD_WRPT);
+}
+
+static const struct mmc_host_ops wbsd_ops = {
+ .request = wbsd_request,
+ .set_ios = wbsd_set_ios,
+ .get_ro = wbsd_get_ro,
+};
+
+/*****************************************************************************\
+ * *
+ * Interrupt handling *
+ * *
+\*****************************************************************************/
+
+/*
+ * Helper function to reset detection ignore
+ */
+
+static void wbsd_reset_ignore(unsigned long data)
+{
+ struct wbsd_host *host = (struct wbsd_host *)data;
+
+ BUG_ON(host == NULL);
+
+ DBG("Resetting card detection ignore\n");
+
+ spin_lock_bh(&host->lock);
+
+ host->flags &= ~WBSD_FIGNORE_DETECT;
+
+ /*
+ * Card status might have changed during the
+ * blackout.
+ */
+ tasklet_schedule(&host->card_tasklet);
+
+ spin_unlock_bh(&host->lock);
+}
+
+/*
+ * Tasklets
+ */
+
+static inline struct mmc_data *wbsd_get_data(struct wbsd_host *host)
+{
+ WARN_ON(!host->mrq);
+ if (!host->mrq)
+ return NULL;
+
+ WARN_ON(!host->mrq->cmd);
+ if (!host->mrq->cmd)
+ return NULL;
+
+ WARN_ON(!host->mrq->cmd->data);
+ if (!host->mrq->cmd->data)
+ return NULL;
+
+ return host->mrq->cmd->data;
+}
+
+static void wbsd_tasklet_card(unsigned long param)
+{
+ struct wbsd_host *host = (struct wbsd_host *)param;
+ u8 csr;
+ int delay = -1;
+
+ spin_lock(&host->lock);
+
+ if (host->flags & WBSD_FIGNORE_DETECT) {
+ spin_unlock(&host->lock);
+ return;
+ }
+
+ csr = inb(host->base + WBSD_CSR);
+ WARN_ON(csr == 0xff);
+
+ if (csr & WBSD_CARDPRESENT) {
+ if (!(host->flags & WBSD_FCARD_PRESENT)) {
+ DBG("Card inserted\n");
+ host->flags |= WBSD_FCARD_PRESENT;
+
+ delay = 500;
+ }
+ } else if (host->flags & WBSD_FCARD_PRESENT) {
+ DBG("Card removed\n");
+ host->flags &= ~WBSD_FCARD_PRESENT;
+
+ if (host->mrq) {
+ printk(KERN_ERR "%s: Card removed during transfer!\n",
+ mmc_hostname(host->mmc));
+ wbsd_reset(host);
+
+ host->mrq->cmd->error = -ENOMEDIUM;
+ tasklet_schedule(&host->finish_tasklet);
+ }
+
+ delay = 0;
+ }
+
+ /*
+ * Unlock first since we might get a call back.
+ */
+
+ spin_unlock(&host->lock);
+
+ if (delay != -1)
+ mmc_detect_change(host->mmc, msecs_to_jiffies(delay));
+}
+
+static void wbsd_tasklet_fifo(unsigned long param)
+{
+ struct wbsd_host *host = (struct wbsd_host *)param;
+ struct mmc_data *data;
+
+ spin_lock(&host->lock);
+
+ if (!host->mrq)
+ goto end;
+
+ data = wbsd_get_data(host);
+ if (!data)
+ goto end;
+
+ if (data->flags & MMC_DATA_WRITE)
+ wbsd_fill_fifo(host);
+ else
+ wbsd_empty_fifo(host);
+
+ /*
+ * Done?
+ */
+ if (host->num_sg == 0) {
+ wbsd_write_index(host, WBSD_IDX_FIFOEN, 0);
+ tasklet_schedule(&host->finish_tasklet);
+ }
+
+end:
+ spin_unlock(&host->lock);
+}
+
+static void wbsd_tasklet_crc(unsigned long param)
+{
+ struct wbsd_host *host = (struct wbsd_host *)param;
+ struct mmc_data *data;
+
+ spin_lock(&host->lock);
+
+ if (!host->mrq)
+ goto end;
+
+ data = wbsd_get_data(host);
+ if (!data)
+ goto end;
+
+ DBGF("CRC error\n");
+
+ data->error = -EILSEQ;
+
+ tasklet_schedule(&host->finish_tasklet);
+
+end:
+ spin_unlock(&host->lock);
+}
+
+static void wbsd_tasklet_timeout(unsigned long param)
+{
+ struct wbsd_host *host = (struct wbsd_host *)param;
+ struct mmc_data *data;
+
+ spin_lock(&host->lock);
+
+ if (!host->mrq)
+ goto end;
+
+ data = wbsd_get_data(host);
+ if (!data)
+ goto end;
+
+ DBGF("Timeout\n");
+
+ data->error = -ETIMEDOUT;
+
+ tasklet_schedule(&host->finish_tasklet);
+
+end:
+ spin_unlock(&host->lock);
+}
+
+static void wbsd_tasklet_finish(unsigned long param)
+{
+ struct wbsd_host *host = (struct wbsd_host *)param;
+ struct mmc_data *data;
+
+ spin_lock(&host->lock);
+
+ WARN_ON(!host->mrq);
+ if (!host->mrq)
+ goto end;
+
+ data = wbsd_get_data(host);
+ if (!data)
+ goto end;
+
+ wbsd_finish_data(host, data);
+
+end:
+ spin_unlock(&host->lock);
+}
+
+/*
+ * Interrupt handling
+ */
+
+static irqreturn_t wbsd_irq(int irq, void *dev_id)
+{
+ struct wbsd_host *host = dev_id;
+ int isr;
+
+ isr = inb(host->base + WBSD_ISR);
+
+ /*
+ * Was it actually our hardware that caused the interrupt?
+ */
+ if (isr == 0xff || isr == 0x00)
+ return IRQ_NONE;
+
+ host->isr |= isr;
+
+ /*
+ * Schedule tasklets as needed.
+ */
+ if (isr & WBSD_INT_CARD)
+ tasklet_schedule(&host->card_tasklet);
+ if (isr & WBSD_INT_FIFO_THRE)
+ tasklet_schedule(&host->fifo_tasklet);
+ if (isr & WBSD_INT_CRC)
+ tasklet_hi_schedule(&host->crc_tasklet);
+ if (isr & WBSD_INT_TIMEOUT)
+ tasklet_hi_schedule(&host->timeout_tasklet);
+ if (isr & WBSD_INT_TC)
+ tasklet_schedule(&host->finish_tasklet);
+
+ return IRQ_HANDLED;
+}
+
+/*****************************************************************************\
+ * *
+ * Device initialisation and shutdown *
+ * *
+\*****************************************************************************/
+
+/*
+ * Allocate/free MMC structure.
+ */
+
+static int __devinit wbsd_alloc_mmc(struct device *dev)
+{
+ struct mmc_host *mmc;
+ struct wbsd_host *host;
+
+ /*
+ * Allocate MMC structure.
+ */
+ mmc = mmc_alloc_host(sizeof(struct wbsd_host), dev);
+ if (!mmc)
+ return -ENOMEM;
+
+ host = mmc_priv(mmc);
+ host->mmc = mmc;
+
+ host->dma = -1;
+
+ /*
+ * Set host parameters.
+ */
+ mmc->ops = &wbsd_ops;
+ mmc->f_min = 375000;
+ mmc->f_max = 24000000;
+ mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+ mmc->caps = MMC_CAP_4_BIT_DATA;
+
+ spin_lock_init(&host->lock);
+
+ /*
+ * Set up timers
+ */
+ init_timer(&host->ignore_timer);
+ host->ignore_timer.data = (unsigned long)host;
+ host->ignore_timer.function = wbsd_reset_ignore;
+
+ /*
+ * Maximum number of segments. Worst case is one sector per segment
+ * so this will be 64kB/512.
+ */
+ mmc->max_segs = 128;
+
+ /*
+ * Maximum request size. Also limited by 64KiB buffer.
+ */
+ mmc->max_req_size = 65536;
+
+ /*
+ * Maximum segment size. Could be one segment with the maximum number
+ * of bytes.
+ */
+ mmc->max_seg_size = mmc->max_req_size;
+
+ /*
+ * Maximum block size. We have 12 bits (= 4095) but have to subtract
+ * space for CRC. So the maximum is 4095 - 4*2 = 4087.
+ */
+ mmc->max_blk_size = 4087;
+
+ /*
+ * Maximum block count. There is no real limit so the maximum
+ * request size will be the only restriction.
+ */
+ mmc->max_blk_count = mmc->max_req_size;
+
+ dev_set_drvdata(dev, mmc);
+
+ return 0;
+}
+
+static void wbsd_free_mmc(struct device *dev)
+{
+ struct mmc_host *mmc;
+ struct wbsd_host *host;
+
+ mmc = dev_get_drvdata(dev);
+ if (!mmc)
+ return;
+
+ host = mmc_priv(mmc);
+ BUG_ON(host == NULL);
+
+ del_timer_sync(&host->ignore_timer);
+
+ mmc_free_host(mmc);
+
+ dev_set_drvdata(dev, NULL);
+}
+
+/*
+ * Scan for known chip id:s
+ */
+
+static int __devinit wbsd_scan(struct wbsd_host *host)
+{
+ int i, j, k;
+ int id;
+
+ /*
+ * Iterate through all ports, all codes to
+ * find hardware that is in our known list.
+ */
+ for (i = 0; i < ARRAY_SIZE(config_ports); i++) {
+ if (!request_region(config_ports[i], 2, DRIVER_NAME))
+ continue;
+
+ for (j = 0; j < ARRAY_SIZE(unlock_codes); j++) {
+ id = 0xFFFF;
+
+ host->config = config_ports[i];
+ host->unlock_code = unlock_codes[j];
+
+ wbsd_unlock_config(host);
+
+ outb(WBSD_CONF_ID_HI, config_ports[i]);
+ id = inb(config_ports[i] + 1) << 8;
+
+ outb(WBSD_CONF_ID_LO, config_ports[i]);
+ id |= inb(config_ports[i] + 1);
+
+ wbsd_lock_config(host);
+
+ for (k = 0; k < ARRAY_SIZE(valid_ids); k++) {
+ if (id == valid_ids[k]) {
+ host->chip_id = id;
+
+ return 0;
+ }
+ }
+
+ if (id != 0xFFFF) {
+ DBG("Unknown hardware (id %x) found at %x\n",
+ id, config_ports[i]);
+ }
+ }
+
+ release_region(config_ports[i], 2);
+ }
+
+ host->config = 0;
+ host->unlock_code = 0;
+
+ return -ENODEV;
+}
+
+/*
+ * Allocate/free io port ranges
+ */
+
+static int __devinit wbsd_request_region(struct wbsd_host *host, int base)
+{
+ if (base & 0x7)
+ return -EINVAL;
+
+ if (!request_region(base, 8, DRIVER_NAME))
+ return -EIO;
+
+ host->base = base;
+
+ return 0;
+}
+
+static void wbsd_release_regions(struct wbsd_host *host)
+{
+ if (host->base)
+ release_region(host->base, 8);
+
+ host->base = 0;
+
+ if (host->config)
+ release_region(host->config, 2);
+
+ host->config = 0;
+}
+
+/*
+ * Allocate/free DMA port and buffer
+ */
+
+static void __devinit wbsd_request_dma(struct wbsd_host *host, int dma)
+{
+ if (dma < 0)
+ return;
+
+ if (request_dma(dma, DRIVER_NAME))
+ goto err;
+
+ /*
+ * We need to allocate a special buffer in
+ * order for ISA to be able to DMA to it.
+ */
+ host->dma_buffer = kmalloc(WBSD_DMA_SIZE,
+ GFP_NOIO | GFP_DMA | __GFP_REPEAT | __GFP_NOWARN);
+ if (!host->dma_buffer)
+ goto free;
+
+ /*
+ * Translate the address to a physical address.
+ */
+ host->dma_addr = dma_map_single(mmc_dev(host->mmc), host->dma_buffer,
+ WBSD_DMA_SIZE, DMA_BIDIRECTIONAL);
+
+ /*
+ * ISA DMA must be aligned on a 64k basis.
+ */
+ if ((host->dma_addr & 0xffff) != 0)
+ goto kfree;
+ /*
+ * ISA cannot access memory above 16 MB.
+ */
+ else if (host->dma_addr >= 0x1000000)
+ goto kfree;
+
+ host->dma = dma;
+
+ return;
+
+kfree:
+ /*
+ * If we've gotten here then there is some kind of alignment bug
+ */
+ BUG_ON(1);
+
+ dma_unmap_single(mmc_dev(host->mmc), host->dma_addr,
+ WBSD_DMA_SIZE, DMA_BIDIRECTIONAL);
+ host->dma_addr = 0;
+
+ kfree(host->dma_buffer);
+ host->dma_buffer = NULL;
+
+free:
+ free_dma(dma);
+
+err:
+ printk(KERN_WARNING DRIVER_NAME ": Unable to allocate DMA %d. "
+ "Falling back on FIFO.\n", dma);
+}
+
+static void wbsd_release_dma(struct wbsd_host *host)
+{
+ if (host->dma_addr) {
+ dma_unmap_single(mmc_dev(host->mmc), host->dma_addr,
+ WBSD_DMA_SIZE, DMA_BIDIRECTIONAL);
+ }
+ kfree(host->dma_buffer);
+ if (host->dma >= 0)
+ free_dma(host->dma);
+
+ host->dma = -1;
+ host->dma_buffer = NULL;
+ host->dma_addr = 0;
+}
+
+/*
+ * Allocate/free IRQ.
+ */
+
+static int __devinit wbsd_request_irq(struct wbsd_host *host, int irq)
+{
+ int ret;
+
+ /*
+ * Set up tasklets. Must be done before requesting interrupt.
+ */
+ tasklet_init(&host->card_tasklet, wbsd_tasklet_card,
+ (unsigned long)host);
+ tasklet_init(&host->fifo_tasklet, wbsd_tasklet_fifo,
+ (unsigned long)host);
+ tasklet_init(&host->crc_tasklet, wbsd_tasklet_crc,
+ (unsigned long)host);
+ tasklet_init(&host->timeout_tasklet, wbsd_tasklet_timeout,
+ (unsigned long)host);
+ tasklet_init(&host->finish_tasklet, wbsd_tasklet_finish,
+ (unsigned long)host);
+
+ /*
+ * Allocate interrupt.
+ */
+ ret = request_irq(irq, wbsd_irq, IRQF_SHARED, DRIVER_NAME, host);
+ if (ret)
+ return ret;
+
+ host->irq = irq;
+
+ return 0;
+}
+
+static void wbsd_release_irq(struct wbsd_host *host)
+{
+ if (!host->irq)
+ return;
+
+ free_irq(host->irq, host);
+
+ host->irq = 0;
+
+ tasklet_kill(&host->card_tasklet);
+ tasklet_kill(&host->fifo_tasklet);
+ tasklet_kill(&host->crc_tasklet);
+ tasklet_kill(&host->timeout_tasklet);
+ tasklet_kill(&host->finish_tasklet);
+}
+
+/*
+ * Allocate all resources for the host.
+ */
+
+static int __devinit wbsd_request_resources(struct wbsd_host *host,
+ int base, int irq, int dma)
+{
+ int ret;
+
+ /*
+ * Allocate I/O ports.
+ */
+ ret = wbsd_request_region(host, base);
+ if (ret)
+ return ret;
+
+ /*
+ * Allocate interrupt.
+ */
+ ret = wbsd_request_irq(host, irq);
+ if (ret)
+ return ret;
+
+ /*
+ * Allocate DMA.
+ */
+ wbsd_request_dma(host, dma);
+
+ return 0;
+}
+
+/*
+ * Release all resources for the host.
+ */
+
+static void wbsd_release_resources(struct wbsd_host *host)
+{
+ wbsd_release_dma(host);
+ wbsd_release_irq(host);
+ wbsd_release_regions(host);
+}
+
+/*
+ * Configure the resources the chip should use.
+ */
+
+static void wbsd_chip_config(struct wbsd_host *host)
+{
+ wbsd_unlock_config(host);
+
+ /*
+ * Reset the chip.
+ */
+ wbsd_write_config(host, WBSD_CONF_SWRST, 1);
+ wbsd_write_config(host, WBSD_CONF_SWRST, 0);
+
+ /*
+ * Select SD/MMC function.
+ */
+ wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD);
+
+ /*
+ * Set up card detection.
+ */
+ wbsd_write_config(host, WBSD_CONF_PINS, WBSD_PINS_DETECT_GP11);
+
+ /*
+ * Configure chip
+ */
+ wbsd_write_config(host, WBSD_CONF_PORT_HI, host->base >> 8);
+ wbsd_write_config(host, WBSD_CONF_PORT_LO, host->base & 0xff);
+
+ wbsd_write_config(host, WBSD_CONF_IRQ, host->irq);
+
+ if (host->dma >= 0)
+ wbsd_write_config(host, WBSD_CONF_DRQ, host->dma);
+
+ /*
+ * Enable and power up chip.
+ */
+ wbsd_write_config(host, WBSD_CONF_ENABLE, 1);
+ wbsd_write_config(host, WBSD_CONF_POWER, 0x20);
+
+ wbsd_lock_config(host);
+}
+
+/*
+ * Check that configured resources are correct.
+ */
+
+static int wbsd_chip_validate(struct wbsd_host *host)
+{
+ int base, irq, dma;
+
+ wbsd_unlock_config(host);
+
+ /*
+ * Select SD/MMC function.
+ */
+ wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD);
+
+ /*
+ * Read configuration.
+ */
+ base = wbsd_read_config(host, WBSD_CONF_PORT_HI) << 8;
+ base |= wbsd_read_config(host, WBSD_CONF_PORT_LO);
+
+ irq = wbsd_read_config(host, WBSD_CONF_IRQ);
+
+ dma = wbsd_read_config(host, WBSD_CONF_DRQ);
+
+ wbsd_lock_config(host);
+
+ /*
+ * Validate against given configuration.
+ */
+ if (base != host->base)
+ return 0;
+ if (irq != host->irq)
+ return 0;
+ if ((dma != host->dma) && (host->dma != -1))
+ return 0;
+
+ return 1;
+}
+
+/*
+ * Powers down the SD function
+ */
+
+static void wbsd_chip_poweroff(struct wbsd_host *host)
+{
+ wbsd_unlock_config(host);
+
+ wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD);
+ wbsd_write_config(host, WBSD_CONF_ENABLE, 0);
+
+ wbsd_lock_config(host);
+}
+
+/*****************************************************************************\
+ * *
+ * Devices setup and shutdown *
+ * *
+\*****************************************************************************/
+
+static int __devinit wbsd_init(struct device *dev, int base, int irq, int dma,
+ int pnp)
+{
+ struct wbsd_host *host = NULL;
+ struct mmc_host *mmc = NULL;
+ int ret;
+
+ ret = wbsd_alloc_mmc(dev);
+ if (ret)
+ return ret;
+
+ mmc = dev_get_drvdata(dev);
+ host = mmc_priv(mmc);
+
+ /*
+ * Scan for hardware.
+ */
+ ret = wbsd_scan(host);
+ if (ret) {
+ if (pnp && (ret == -ENODEV)) {
+ printk(KERN_WARNING DRIVER_NAME
+ ": Unable to confirm device presence. You may "
+ "experience lock-ups.\n");
+ } else {
+ wbsd_free_mmc(dev);
+ return ret;
+ }
+ }
+
+ /*
+ * Request resources.
+ */
+ ret = wbsd_request_resources(host, base, irq, dma);
+ if (ret) {
+ wbsd_release_resources(host);
+ wbsd_free_mmc(dev);
+ return ret;
+ }
+
+ /*
+ * See if chip needs to be configured.
+ */
+ if (pnp) {
+ if ((host->config != 0) && !wbsd_chip_validate(host)) {
+ printk(KERN_WARNING DRIVER_NAME
+ ": PnP active but chip not configured! "
+ "You probably have a buggy BIOS. "
+ "Configuring chip manually.\n");
+ wbsd_chip_config(host);
+ }
+ } else
+ wbsd_chip_config(host);
+
+ /*
+ * Power Management stuff. No idea how this works.
+ * Not tested.
+ */
+#ifdef CONFIG_PM
+ if (host->config) {
+ wbsd_unlock_config(host);
+ wbsd_write_config(host, WBSD_CONF_PME, 0xA0);
+ wbsd_lock_config(host);
+ }
+#endif
+ /*
+ * Allow device to initialise itself properly.
+ */
+ mdelay(5);
+
+ /*
+ * Reset the chip into a known state.
+ */
+ wbsd_init_device(host);
+
+ mmc_add_host(mmc);
+
+ printk(KERN_INFO "%s: W83L51xD", mmc_hostname(mmc));
+ if (host->chip_id != 0)
+ printk(" id %x", (int)host->chip_id);
+ printk(" at 0x%x irq %d", (int)host->base, (int)host->irq);
+ if (host->dma >= 0)
+ printk(" dma %d", (int)host->dma);
+ else
+ printk(" FIFO");
+ if (pnp)
+ printk(" PnP");
+ printk("\n");
+
+ return 0;
+}
+
+static void __devexit wbsd_shutdown(struct device *dev, int pnp)
+{
+ struct mmc_host *mmc = dev_get_drvdata(dev);
+ struct wbsd_host *host;
+
+ if (!mmc)
+ return;
+
+ host = mmc_priv(mmc);
+
+ mmc_remove_host(mmc);
+
+ /*
+ * Power down the SD/MMC function.
+ */
+ if (!pnp)
+ wbsd_chip_poweroff(host);
+
+ wbsd_release_resources(host);
+
+ wbsd_free_mmc(dev);
+}
+
+/*
+ * Non-PnP
+ */
+
+static int __devinit wbsd_probe(struct platform_device *dev)
+{
+ /* Use the module parameters for resources */
+ return wbsd_init(&dev->dev, param_io, param_irq, param_dma, 0);
+}
+
+static int __devexit wbsd_remove(struct platform_device *dev)
+{
+ wbsd_shutdown(&dev->dev, 0);
+
+ return 0;
+}
+
+/*
+ * PnP
+ */
+
+#ifdef CONFIG_PNP
+
+static int __devinit
+wbsd_pnp_probe(struct pnp_dev *pnpdev, const struct pnp_device_id *dev_id)
+{
+ int io, irq, dma;
+
+ /*
+ * Get resources from PnP layer.
+ */
+ io = pnp_port_start(pnpdev, 0);
+ irq = pnp_irq(pnpdev, 0);
+ if (pnp_dma_valid(pnpdev, 0))
+ dma = pnp_dma(pnpdev, 0);
+ else
+ dma = -1;
+
+ DBGF("PnP resources: port %3x irq %d dma %d\n", io, irq, dma);
+
+ return wbsd_init(&pnpdev->dev, io, irq, dma, 1);
+}
+
+static void __devexit wbsd_pnp_remove(struct pnp_dev *dev)
+{
+ wbsd_shutdown(&dev->dev, 1);
+}
+
+#endif /* CONFIG_PNP */
+
+/*
+ * Power management
+ */
+
+#ifdef CONFIG_PM
+
+static int wbsd_suspend(struct wbsd_host *host, pm_message_t state)
+{
+ BUG_ON(host == NULL);
+
+ return mmc_suspend_host(host->mmc);
+}
+
+static int wbsd_resume(struct wbsd_host *host)
+{
+ BUG_ON(host == NULL);
+
+ wbsd_init_device(host);
+
+ return mmc_resume_host(host->mmc);
+}
+
+static int wbsd_platform_suspend(struct platform_device *dev,
+ pm_message_t state)
+{
+ struct mmc_host *mmc = platform_get_drvdata(dev);
+ struct wbsd_host *host;
+ int ret;
+
+ if (mmc == NULL)
+ return 0;
+
+ DBGF("Suspending...\n");
+
+ host = mmc_priv(mmc);
+
+ ret = wbsd_suspend(host, state);
+ if (ret)
+ return ret;
+
+ wbsd_chip_poweroff(host);
+
+ return 0;
+}
+
+static int wbsd_platform_resume(struct platform_device *dev)
+{
+ struct mmc_host *mmc = platform_get_drvdata(dev);
+ struct wbsd_host *host;
+
+ if (mmc == NULL)
+ return 0;
+
+ DBGF("Resuming...\n");
+
+ host = mmc_priv(mmc);
+
+ wbsd_chip_config(host);
+
+ /*
+ * Allow device to initialise itself properly.
+ */
+ mdelay(5);
+
+ return wbsd_resume(host);
+}
+
+#ifdef CONFIG_PNP
+
+static int wbsd_pnp_suspend(struct pnp_dev *pnp_dev, pm_message_t state)
+{
+ struct mmc_host *mmc = dev_get_drvdata(&pnp_dev->dev);
+ struct wbsd_host *host;
+
+ if (mmc == NULL)
+ return 0;
+
+ DBGF("Suspending...\n");
+
+ host = mmc_priv(mmc);
+
+ return wbsd_suspend(host, state);
+}
+
+static int wbsd_pnp_resume(struct pnp_dev *pnp_dev)
+{
+ struct mmc_host *mmc = dev_get_drvdata(&pnp_dev->dev);
+ struct wbsd_host *host;
+
+ if (mmc == NULL)
+ return 0;
+
+ DBGF("Resuming...\n");
+
+ host = mmc_priv(mmc);
+
+ /*
+ * See if chip needs to be configured.
+ */
+ if (host->config != 0) {
+ if (!wbsd_chip_validate(host)) {
+ printk(KERN_WARNING DRIVER_NAME
+ ": PnP active but chip not configured! "
+ "You probably have a buggy BIOS. "
+ "Configuring chip manually.\n");
+ wbsd_chip_config(host);
+ }
+ }
+
+ /*
+ * Allow device to initialise itself properly.
+ */
+ mdelay(5);
+
+ return wbsd_resume(host);
+}
+
+#endif /* CONFIG_PNP */
+
+#else /* CONFIG_PM */
+
+#define wbsd_platform_suspend NULL
+#define wbsd_platform_resume NULL
+
+#define wbsd_pnp_suspend NULL
+#define wbsd_pnp_resume NULL
+
+#endif /* CONFIG_PM */
+
+static struct platform_device *wbsd_device;
+
+static struct platform_driver wbsd_driver = {
+ .probe = wbsd_probe,
+ .remove = __devexit_p(wbsd_remove),
+
+ .suspend = wbsd_platform_suspend,
+ .resume = wbsd_platform_resume,
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+#ifdef CONFIG_PNP
+
+static struct pnp_driver wbsd_pnp_driver = {
+ .name = DRIVER_NAME,
+ .id_table = pnp_dev_table,
+ .probe = wbsd_pnp_probe,
+ .remove = __devexit_p(wbsd_pnp_remove),
+
+ .suspend = wbsd_pnp_suspend,
+ .resume = wbsd_pnp_resume,
+};
+
+#endif /* CONFIG_PNP */
+
+/*
+ * Module loading/unloading
+ */
+
+static int __init wbsd_drv_init(void)
+{
+ int result;
+
+ printk(KERN_INFO DRIVER_NAME
+ ": Winbond W83L51xD SD/MMC card interface driver\n");
+ printk(KERN_INFO DRIVER_NAME ": Copyright(c) Pierre Ossman\n");
+
+#ifdef CONFIG_PNP
+
+ if (!param_nopnp) {
+ result = pnp_register_driver(&wbsd_pnp_driver);
+ if (result < 0)
+ return result;
+ }
+#endif /* CONFIG_PNP */
+
+ if (param_nopnp) {
+ result = platform_driver_register(&wbsd_driver);
+ if (result < 0)
+ return result;
+
+ wbsd_device = platform_device_alloc(DRIVER_NAME, -1);
+ if (!wbsd_device) {
+ platform_driver_unregister(&wbsd_driver);
+ return -ENOMEM;
+ }
+
+ result = platform_device_add(wbsd_device);
+ if (result) {
+ platform_device_put(wbsd_device);
+ platform_driver_unregister(&wbsd_driver);
+ return result;
+ }
+ }
+
+ return 0;
+}
+
+static void __exit wbsd_drv_exit(void)
+{
+#ifdef CONFIG_PNP
+
+ if (!param_nopnp)
+ pnp_unregister_driver(&wbsd_pnp_driver);
+
+#endif /* CONFIG_PNP */
+
+ if (param_nopnp) {
+ platform_device_unregister(wbsd_device);
+
+ platform_driver_unregister(&wbsd_driver);
+ }
+
+ DBG("unloaded\n");
+}
+
+module_init(wbsd_drv_init);
+module_exit(wbsd_drv_exit);
+#ifdef CONFIG_PNP
+module_param_named(nopnp, param_nopnp, uint, 0444);
+#endif
+module_param_named(io, param_io, uint, 0444);
+module_param_named(irq, param_irq, uint, 0444);
+module_param_named(dma, param_dma, int, 0444);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Pierre Ossman <pierre@ossman.eu>");
+MODULE_DESCRIPTION("Winbond W83L51xD SD/MMC card interface driver");
+
+#ifdef CONFIG_PNP
+MODULE_PARM_DESC(nopnp, "Scan for device instead of relying on PNP. (default 0)");
+#endif
+MODULE_PARM_DESC(io, "I/O base to allocate. Must be 8 byte aligned. (default 0x248)");
+MODULE_PARM_DESC(irq, "IRQ to allocate. (default 6)");
+MODULE_PARM_DESC(dma, "DMA channel to allocate. -1 for no DMA. (default 2)");
diff --git a/drivers/mmc/host/wbsd.h b/drivers/mmc/host/wbsd.h
new file mode 100644
index 00000000..0877866f
--- /dev/null
+++ b/drivers/mmc/host/wbsd.h
@@ -0,0 +1,185 @@
+/*
+ * linux/drivers/mmc/host/wbsd.h - Winbond W83L51xD SD/MMC driver
+ *
+ * Copyright (C) 2004-2007 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.
+ */
+
+#define LOCK_CODE 0xAA
+
+#define WBSD_CONF_SWRST 0x02
+#define WBSD_CONF_DEVICE 0x07
+#define WBSD_CONF_ID_HI 0x20
+#define WBSD_CONF_ID_LO 0x21
+#define WBSD_CONF_POWER 0x22
+#define WBSD_CONF_PME 0x23
+#define WBSD_CONF_PMES 0x24
+
+#define WBSD_CONF_ENABLE 0x30
+#define WBSD_CONF_PORT_HI 0x60
+#define WBSD_CONF_PORT_LO 0x61
+#define WBSD_CONF_IRQ 0x70
+#define WBSD_CONF_DRQ 0x74
+
+#define WBSD_CONF_PINS 0xF0
+
+#define DEVICE_SD 0x03
+
+#define WBSD_PINS_DAT3_HI 0x20
+#define WBSD_PINS_DAT3_OUT 0x10
+#define WBSD_PINS_GP11_HI 0x04
+#define WBSD_PINS_DETECT_GP11 0x02
+#define WBSD_PINS_DETECT_DAT3 0x01
+
+#define WBSD_CMDR 0x00
+#define WBSD_DFR 0x01
+#define WBSD_EIR 0x02
+#define WBSD_ISR 0x03
+#define WBSD_FSR 0x04
+#define WBSD_IDXR 0x05
+#define WBSD_DATAR 0x06
+#define WBSD_CSR 0x07
+
+#define WBSD_EINT_CARD 0x40
+#define WBSD_EINT_FIFO_THRE 0x20
+#define WBSD_EINT_CRC 0x10
+#define WBSD_EINT_TIMEOUT 0x08
+#define WBSD_EINT_PROGEND 0x04
+#define WBSD_EINT_BUSYEND 0x02
+#define WBSD_EINT_TC 0x01
+
+#define WBSD_INT_PENDING 0x80
+#define WBSD_INT_CARD 0x40
+#define WBSD_INT_FIFO_THRE 0x20
+#define WBSD_INT_CRC 0x10
+#define WBSD_INT_TIMEOUT 0x08
+#define WBSD_INT_PROGEND 0x04
+#define WBSD_INT_BUSYEND 0x02
+#define WBSD_INT_TC 0x01
+
+#define WBSD_FIFO_EMPTY 0x80
+#define WBSD_FIFO_FULL 0x40
+#define WBSD_FIFO_EMTHRE 0x20
+#define WBSD_FIFO_FUTHRE 0x10
+#define WBSD_FIFO_SZMASK 0x0F
+
+#define WBSD_MSLED 0x20
+#define WBSD_POWER_N 0x10
+#define WBSD_WRPT 0x04
+#define WBSD_CARDPRESENT 0x01
+
+#define WBSD_IDX_CLK 0x01
+#define WBSD_IDX_PBSMSB 0x02
+#define WBSD_IDX_TAAC 0x03
+#define WBSD_IDX_NSAC 0x04
+#define WBSD_IDX_PBSLSB 0x05
+#define WBSD_IDX_SETUP 0x06
+#define WBSD_IDX_DMA 0x07
+#define WBSD_IDX_FIFOEN 0x08
+#define WBSD_IDX_STATUS 0x10
+#define WBSD_IDX_RSPLEN 0x1E
+#define WBSD_IDX_RESP0 0x1F
+#define WBSD_IDX_RESP1 0x20
+#define WBSD_IDX_RESP2 0x21
+#define WBSD_IDX_RESP3 0x22
+#define WBSD_IDX_RESP4 0x23
+#define WBSD_IDX_RESP5 0x24
+#define WBSD_IDX_RESP6 0x25
+#define WBSD_IDX_RESP7 0x26
+#define WBSD_IDX_RESP8 0x27
+#define WBSD_IDX_RESP9 0x28
+#define WBSD_IDX_RESP10 0x29
+#define WBSD_IDX_RESP11 0x2A
+#define WBSD_IDX_RESP12 0x2B
+#define WBSD_IDX_RESP13 0x2C
+#define WBSD_IDX_RESP14 0x2D
+#define WBSD_IDX_RESP15 0x2E
+#define WBSD_IDX_RESP16 0x2F
+#define WBSD_IDX_CRCSTATUS 0x30
+#define WBSD_IDX_ISR 0x3F
+
+#define WBSD_CLK_375K 0x00
+#define WBSD_CLK_12M 0x01
+#define WBSD_CLK_16M 0x02
+#define WBSD_CLK_24M 0x03
+
+#define WBSD_DATA_WIDTH 0x01
+
+#define WBSD_DAT3_H 0x08
+#define WBSD_FIFO_RESET 0x04
+#define WBSD_SOFT_RESET 0x02
+#define WBSD_INC_INDEX 0x01
+
+#define WBSD_DMA_SINGLE 0x02
+#define WBSD_DMA_ENABLE 0x01
+
+#define WBSD_FIFOEN_EMPTY 0x20
+#define WBSD_FIFOEN_FULL 0x10
+#define WBSD_FIFO_THREMASK 0x0F
+
+#define WBSD_BLOCK_READ 0x80
+#define WBSD_BLOCK_WRITE 0x40
+#define WBSD_BUSY 0x20
+#define WBSD_CARDTRAFFIC 0x04
+#define WBSD_SENDCMD 0x02
+#define WBSD_RECVRES 0x01
+
+#define WBSD_RSP_SHORT 0x00
+#define WBSD_RSP_LONG 0x01
+
+#define WBSD_CRC_MASK 0x1F
+#define WBSD_CRC_OK 0x05 /* S010E (00101) */
+#define WBSD_CRC_FAIL 0x0B /* S101E (01011) */
+
+#define WBSD_DMA_SIZE 65536
+
+struct wbsd_host
+{
+ struct mmc_host* mmc; /* MMC structure */
+
+ spinlock_t lock; /* Mutex */
+
+ int flags; /* Driver states */
+
+#define WBSD_FCARD_PRESENT (1<<0) /* Card is present */
+#define WBSD_FIGNORE_DETECT (1<<1) /* Ignore card detection */
+
+ struct mmc_request* mrq; /* Current request */
+
+ u8 isr; /* Accumulated ISR */
+
+ struct scatterlist* cur_sg; /* Current SG entry */
+ unsigned int num_sg; /* Number of entries left */
+
+ unsigned int offset; /* Offset into current entry */
+ unsigned int remain; /* Data left in curren entry */
+
+ char* dma_buffer; /* ISA DMA buffer */
+ dma_addr_t dma_addr; /* Physical address for same */
+
+ int firsterr; /* See fifo functions */
+
+ u8 clk; /* Current clock speed */
+ unsigned char bus_width; /* Current bus width */
+
+ int config; /* Config port */
+ u8 unlock_code; /* Code to unlock config */
+
+ int chip_id; /* ID of controller */
+
+ int base; /* I/O port base */
+ int irq; /* Interrupt */
+ int dma; /* DMA channel */
+
+ struct tasklet_struct card_tasklet; /* Tasklet structures */
+ struct tasklet_struct fifo_tasklet;
+ struct tasklet_struct crc_tasklet;
+ struct tasklet_struct timeout_tasklet;
+ struct tasklet_struct finish_tasklet;
+
+ struct timer_list ignore_timer; /* Ignore detection timer */
+};