diff options
author | Srikant Patnaik | 2015-01-11 12:28:04 +0530 |
---|---|---|
committer | Srikant Patnaik | 2015-01-11 12:28:04 +0530 |
commit | 871480933a1c28f8a9fed4c4d34d06c439a7a422 (patch) | |
tree | 8718f573808810c2a1e8cb8fb6ac469093ca2784 /drivers/mmc | |
parent | 9d40ac5867b9aefe0722bc1f110b965ff294d30d (diff) | |
download | FOSSEE-netbook-kernel-source-871480933a1c28f8a9fed4c4d34d06c439a7a422.tar.gz FOSSEE-netbook-kernel-source-871480933a1c28f8a9fed4c4d34d06c439a7a422.tar.bz2 FOSSEE-netbook-kernel-source-871480933a1c28f8a9fed4c4d34d06c439a7a422.zip |
Moved, renamed, and deleted files
The original directory structure was scattered and unorganized.
Changes are basically to make it look like kernel structure.
Diffstat (limited to 'drivers/mmc')
104 files changed, 77021 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..400756ec --- /dev/null +++ b/drivers/mmc/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for the kernel mmc device drivers. +# + +subdir-ccflags-$(CONFIG_MMC_DEBUG) := -DDEBUG + +obj-$(CONFIG_MMC) += core/ +obj-$(CONFIG_MMC) += card/ +obj-$(subst m,y,$(CONFIG_MMC)) += host/ diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig new file mode 100644 index 00000000..ebb4afe6 --- /dev/null +++ b/drivers/mmc/card/Kconfig @@ -0,0 +1,78 @@ +# +# 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. diff --git a/drivers/mmc/card/Makefile b/drivers/mmc/card/Makefile new file mode 100644 index 00000000..c73b406a --- /dev/null +++ b/drivers/mmc/card/Makefile @@ -0,0 +1,10 @@ +# +# 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 + diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c new file mode 100644 index 00000000..90a15016 --- /dev/null +++ b/drivers/mmc/card/block.c @@ -0,0 +1,2088 @@ +/* + * 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/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; + unsigned int reset_done; +#define MMC_BLK_READ BIT(0) +#define MMC_BLK_WRITE BIT(1) +#define MMC_BLK_DISCARD BIT(2) +#define MMC_BLK_SECDISCARD BIT(3) + + /* + * 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; + struct device_attribute power_ro_lock; + int area_type; +}; + +static DEFINE_MUTEX(open_lock); + +enum mmc_blk_status { + MMC_BLK_SUCCESS = 0, + MMC_BLK_PARTIAL, + MMC_BLK_CMD_ERR, + MMC_BLK_RETRY, + MMC_BLK_ABORT, + MMC_BLK_DATA_ERR, + MMC_BLK_ECC_ERR, + MMC_BLK_NOMEDIUM, +}; + +module_param(perdev_minors, int, 0444); +MODULE_PARM_DESC(perdev_minors, "Minors numbers to allocate per device"); + + +#if 0 +#define DBG(x...) printk(KERN_ALERT x) +#else +#define DBG(x...) do { } while (0) +#endif + + +static struct mmc_blk_data *mmc_blk_get(struct gendisk *disk) +{ + struct mmc_blk_data *md; + DBG("[%s] s\n",__func__); + mutex_lock(&open_lock); + md = disk->private_data; + if (md && md->usage == 0) + md = NULL; + if (md) + md->usage++; + mutex_unlock(&open_lock); + DBG("[%s] e\n",__func__); + return md; +} + +static inline int mmc_get_devidx(struct gendisk *disk) +{ + int devidx = disk->first_minor / perdev_minors; + DBG("[%s] s\n",__func__); + return devidx; +} + +static void mmc_blk_put(struct mmc_blk_data *md) +{ + DBG("[%s] s\n",__func__); + 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); + DBG("[%s] e\n",__func__); +} + +static ssize_t power_ro_lock_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); + struct mmc_card *card = md->queue.card; + int locked = 0; + DBG("[%s] s\n",__func__); + + if (card->ext_csd.boot_ro_lock & EXT_CSD_BOOT_WP_B_PERM_WP_EN) + locked = 2; + else if (card->ext_csd.boot_ro_lock & EXT_CSD_BOOT_WP_B_PWR_WP_EN) + locked = 1; + + ret = snprintf(buf, PAGE_SIZE, "%d\n", locked); + DBG("[%s] e\n",__func__); + return ret; +} + +static ssize_t power_ro_lock_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int ret; + struct mmc_blk_data *md, *part_md; + struct mmc_card *card; + unsigned long set; + DBG("[%s] s\n",__func__); + + if (kstrtoul(buf, 0, &set)) { + DBG("[%s] e1\n",__func__); + return -EINVAL; + } + + if (set != 1) { + DBG("[%s] e2\n",__func__); + return count; + } + + md = mmc_blk_get(dev_to_disk(dev)); + card = md->queue.card; + + mmc_claim_host(card->host); + + ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BOOT_WP, + card->ext_csd.boot_ro_lock | + EXT_CSD_BOOT_WP_B_PWR_WP_EN, + card->ext_csd.part_time); + if (ret) + pr_err("%s: Locking boot partition ro until next power on failed: %d\n", md->disk->disk_name, ret); + else + card->ext_csd.boot_ro_lock |= EXT_CSD_BOOT_WP_B_PWR_WP_EN; + + mmc_release_host(card->host); + + if (!ret) { + pr_info("%s: Locking boot partition ro until next power on\n", + md->disk->disk_name); + set_disk_ro(md->disk, 1); + + list_for_each_entry(part_md, &md->part, part) + if (part_md->area_type == MMC_BLK_DATA_AREA_BOOT) { + pr_info("%s: Locking boot partition ro until next power on\n", part_md->disk->disk_name); + set_disk_ro(part_md->disk, 1); + } + } + + mmc_blk_put(md); + DBG("[%s] e3\n",__func__); + return count; +} + +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)); + DBG("[%s] s\n",__func__); + + ret = snprintf(buf, PAGE_SIZE, "%d", + get_disk_ro(dev_to_disk(dev)) ^ + md->read_only); + mmc_blk_put(md); + DBG("[%s] e\n",__func__); + 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); + DBG("[%s] s\n",__func__); + + 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); + DBG("[%s] e\n",__func__); + 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; + DBG("[%s] s\n",__func__); + + 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); + DBG("[%s] e\n",__func__); + return ret; +} + +static int mmc_blk_release(struct gendisk *disk, fmode_t mode) +{ + struct mmc_blk_data *md = disk->private_data; + DBG("[%s] s\n",__func__); + + mutex_lock(&block_mutex); + mmc_blk_put(md); + mutex_unlock(&block_mutex); + DBG("[%s] e\n",__func__); + return 0; +} + +static int +mmc_blk_getgeo(struct block_device *bdev, struct hd_geometry *geo) +{ + DBG("[%s] s\n",__func__); + geo->cylinders = get_capacity(bdev->bd_disk) / (4 * 16); + geo->heads = 4; + geo->sectors = 16; + DBG("[%s] e\n",__func__); + 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; + DBG("[%s] s\n",__func__); + + 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) { + DBG("[%s] e1\n",__func__); + 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; + } + DBG("[%s] e2\n",__func__); + return idata; + +copy_err: + kfree(idata->buf); +idata_err: + kfree(idata); +out: + DBG("[%s] e3\n",__func__); + 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 = {NULL}; + struct scatterlist sg; + int err; + DBG("[%s] s\n",__func__); + /* + * 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)) { + DBG("[%s] e1\n",__func__); + return -EPERM; + } + + idata = mmc_blk_ioctl_copy_from_user(ic_ptr); + if (IS_ERR(idata)) { + DBG("[%s] e2\n",__func__); + 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); + DBG("[%s] e3\n",__func__); + return err; +} + +static int mmc_blk_ioctl(struct block_device *bdev, fmode_t mode, + unsigned int cmd, unsigned long arg) +{ + int ret = -EINVAL; + DBG("[%s] s\n",__func__); + if (cmd == MMC_IOC_CMD) + ret = mmc_blk_ioctl_cmd(bdev, (struct mmc_ioc_cmd __user *)arg); + DBG("[%s] e\n",__func__); + return ret; +} + +#ifdef CONFIG_COMPAT +static int mmc_blk_compat_ioctl(struct block_device *bdev, fmode_t mode, + unsigned int cmd, unsigned long arg) +{ + DBG("[%s] s\n",__func__); + 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); + DBG("[%s] s\n",__func__); + + if (main_md->part_curr == md->part_type) { + DBG("[%s] e1\n",__func__); + return 0; + } + + if (mmc_card_mmc(card)) { + u8 part_config = card->ext_csd.part_config; + + part_config &= ~EXT_CSD_PART_CONFIG_ACC_MASK; + part_config |= md->part_type; + + ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_PART_CONFIG, part_config, + card->ext_csd.part_time); + if (ret) { + DBG("[%s] e2\n",__func__); + return ret; + } + + card->ext_csd.part_config = part_config; + } + + main_md->part_curr = md->part_type; + DBG("[%s] e3\n",__func__); + return 0; +} + +static u32 mmc_sd_num_wr_blocks(struct mmc_card *card) +{ + int err; + u32 result; + __be32 *blocks; + struct mmc_request mrq = {NULL}; + struct mmc_command cmd = {0}; + struct mmc_data data = {0}; + unsigned int timeout_us; + + struct scatterlist sg; + DBG("[%s] s\n",__func__); + + cmd.opcode = MMC_APP_CMD; + cmd.arg = card->rca << 16; + //cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC; + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + + err = mmc_wait_for_cmd(card->host, &cmd, 0); + if (err) { + DBG("[%s] e1\n",__func__); + return (u32)-1; + } + if (!mmc_host_is_spi(card->host) && !(cmd.resp[0] & R1_APP_CMD)) { + DBG("[%s] e2\n",__func__); + 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; + cmd.flags = 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) { + DBG("[%s] e3\n",__func__); + 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; + DBG("[%s] e4\n",__func__); + return result; +} + +static int send_stop(struct mmc_card *card, u32 *status) +{ + struct mmc_command cmd = {0}; + int err; + DBG("[%s] s\n",__func__); + + cmd.opcode = MMC_STOP_TRANSMISSION; + //cmd.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC; + cmd.flags = MMC_RSP_R1B | MMC_CMD_AC; + err = mmc_wait_for_cmd(card->host, &cmd, 5); + if (err == 0) + *status = cmd.resp[0]; + DBG("[%s] e\n",__func__); + return err; +} + +static int get_card_status(struct mmc_card *card, u32 *status, int retries) +{ + struct mmc_command cmd = {0}; + int err; + DBG("[%s] s\n",__func__); + + 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; + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + err = mmc_wait_for_cmd(card->host, &cmd, retries); + if (err == 0) + *status = cmd.resp[0]; + DBG("[%s] e\n",__func__); + return err; +} + +#define ERR_NOMEDIUM 3 +#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) +{ + DBG("[%s] s\n",__func__); + 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); + DBG("[%s] e1\n",__func__); + 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) { + pr_err("%s: status not valid, retrying timeout\n", req->rq_disk->disk_name); + DBG("[%s] e2\n",__func__); + 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)) { + pr_err("%s: command error, retrying timeout\n", req->rq_disk->disk_name); + DBG("[%s] e3\n",__func__); + return ERR_RETRY; + } + + /* Otherwise abort the command */ + pr_err("%s: not retrying timeout\n", req->rq_disk->disk_name); + DBG("[%s] e4\n",__func__); + 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); + DBG("[%s] e5\n",__func__); + 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, int *ecc_err) +{ + bool prev_cmd_status_valid = true; + u32 status, stop_status = 0; + int err, retry; + DBG("[%s] s\n",__func__); + + if (mmc_card_removed(card)) { + DBG("[%s] e1\n",__func__); + return ERR_NOMEDIUM; + } + /* + * 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) { + /* Check if the card is removed */ + if (mmc_detect_card_removed(card->host)) { + DBG("[%s] e1\n",__func__); + return ERR_NOMEDIUM; + } + DBG("[%s] e2\n",__func__); + return ERR_ABORT; + } + + /* Flag ECC errors */ + if ((status & R1_CARD_ECC_FAILED) || + (brq->stop.resp[0] & R1_CARD_ECC_FAILED) || + (brq->cmd.resp[0] & R1_CARD_ECC_FAILED)) + *ecc_err = 1; + + /* + * 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) { + DBG("[%s] e4\n",__func__); + return ERR_ABORT; + } + if (stop_status & R1_CARD_ECC_FAILED) + *ecc_err = 1; + } + + /* Check for set block count errors */ + if (brq->sbc.error) { + DBG("[%s] e5\n",__func__); + 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) { + DBG("[%s] e6\n",__func__); + return mmc_blk_cmd_error(req, "r/w cmd", brq->cmd.error, + prev_cmd_status_valid, status); + } + /* Data errors */ + if (!brq->stop.error) { + DBG("[%s] e7\n",__func__); + return ERR_CONTINUE; + } + + /* 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; + } + DBG("[%s] e8\n",__func__); + return ERR_CONTINUE; +} + +static int mmc_blk_reset(struct mmc_blk_data *md, struct mmc_host *host, + int type) +{ + int err; + DBG("[%s] s\n",__func__); + + if (md->reset_done & type) { + DBG("[%s] e1\n",__func__); + return -EEXIST; + } + + md->reset_done |= type; + err = mmc_hw_reset(host); + /* Ensure we switch back to the correct partition */ + if (err != -EOPNOTSUPP) { + struct mmc_blk_data *main_md = mmc_get_drvdata(host->card); + int part_err; + + main_md->part_curr = main_md->part_type; + part_err = mmc_blk_part_switch(host->card, md); + if (part_err) { + /* + * We have failed to get back into the correct + * partition, so we need to abort the whole request. + */ + DBG("[%s] e2\n",__func__); + return -ENODEV; + } + } + DBG("[%s] e3\n",__func__); + return err; +} + +static inline void mmc_blk_reset_success(struct mmc_blk_data *md, int type) +{ + DBG("[%s] s\n",__func__); + md->reset_done &= ~type; + DBG("[%s] e\n",__func__); +} + +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, type = MMC_BLK_DISCARD; + DBG("[%s] s\n",__func__); + + if (!mmc_can_erase(card)) { + err = -EOPNOTSUPP; + goto out; + } + + from = blk_rq_pos(req); + nr = blk_rq_sectors(req); + + if (mmc_can_discard(card)) + arg = MMC_DISCARD_ARG; + else if (mmc_can_trim(card)) + arg = MMC_TRIM_ARG; + else + arg = MMC_ERASE_ARG; +retry: + 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: + if (err == -EIO && !mmc_blk_reset(md, card->host, type)) + goto retry; + if (!err) + mmc_blk_reset_success(md, type); + spin_lock_irq(&md->lock); + __blk_end_request(req, err, blk_rq_bytes(req)); + spin_unlock_irq(&md->lock); + DBG("[%s] e\n",__func__); + 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, trim_arg, erase_arg; + int err = 0, type = MMC_BLK_SECDISCARD; + DBG("[%s] s\n",__func__); + + if (!(mmc_can_secure_erase_trim(card) || mmc_can_sanitize(card))) { + err = -EOPNOTSUPP; + goto out; + } + + from = blk_rq_pos(req); + nr = blk_rq_sectors(req); + + /* The sanitize operation is supported at v4.5 only */ + if (mmc_can_sanitize(card)) { + erase_arg = MMC_ERASE_ARG; + trim_arg = MMC_TRIM_ARG; + } else { + erase_arg = MMC_SECURE_ERASE_ARG; + trim_arg = MMC_SECURE_TRIM1_ARG; + } + + if (mmc_erase_group_aligned(card, from, nr)) + arg = erase_arg; + else if (mmc_can_trim(card)) + arg = trim_arg; + else { + err = -EINVAL; + goto out; + } +retry: + 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_retry; + } + + err = mmc_erase(card, from, nr, arg); + if (err == -EIO) + goto out_retry; + if (err) + goto out; + + if (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_retry; + } + + err = mmc_erase(card, from, nr, MMC_SECURE_TRIM2_ARG); + if (err == -EIO) + goto out_retry; + if (err) + goto out; + } + + if (mmc_can_sanitize(card)) + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_SANITIZE_START, 1, 0); +out_retry: + if (err && !mmc_blk_reset(md, card->host, type)) + goto retry; + if (!err) + mmc_blk_reset_success(md, type); +out: + spin_lock_irq(&md->lock); + __blk_end_request(req, err, blk_rq_bytes(req)); + spin_unlock_irq(&md->lock); + DBG("[%s] e\n",__func__); + return err ? 0 : 1; +} + +static int mmc_blk_issue_flush(struct mmc_queue *mq, struct request *req) +{ + struct mmc_blk_data *md = mq->data; + struct mmc_card *card = md->queue.card; + int ret = 0; + DBG("[%s] s\n",__func__); + + ret = mmc_flush_cache(card); + if (ret) + ret = -EIO; + + spin_lock_irq(&md->lock); + __blk_end_request_all(req, ret); + spin_unlock_irq(&md->lock); + DBG("[%s] e\n",__func__); + return ret ? 0 : 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) +{ + DBG("[%s] s\n",__func__); + 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; + } + DBG("[%s] e\n",__func__); +} + +#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) +{ + 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; + int ecc_err = 0; + DBG("[%s] s\n",__func__); + /* + * 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 || + brq->data.error) { + switch (mmc_blk_cmd_recovery(card, req, brq, &ecc_err)) { + case ERR_RETRY: + return MMC_BLK_RETRY; + case ERR_ABORT: + return MMC_BLK_ABORT; + case ERR_NOMEDIUM: + return MMC_BLK_NOMEDIUM; + 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]); + DBG("[%s] e1\n",__func__); + 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) { + pr_err("%s: error %d requesting status\n", + req->rq_disk->disk_name, err); + DBG("[%s] e2\n",__func__); + 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 (ecc_err) { + DBG("[%s] e3\n",__func__); + return MMC_BLK_ECC_ERR; + } + DBG("[%s] e4\n",__func__); + return MMC_BLK_DATA_ERR; + } else { + DBG("[%s] e5\n",__func__); + return MMC_BLK_CMD_ERR; + } + } + + if (!brq->data.bytes_xfered) { + DBG("[%s] e6\n",__func__); + return MMC_BLK_RETRY; + } + + if (blk_rq_bytes(req) != brq->data.bytes_xfered) { + DBG("[%s] e7\n",__func__); + return MMC_BLK_PARTIAL; + } + + DBG("[%s] e8\n",__func__); + return MMC_BLK_SUCCESS; +} + +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; + bool do_data_tag; + + /* + * Reliable writes are used to implement Forced Unit Access and + * REQ_META accesses, and are supported only on MMCs. + * + * XXX: this really needs a good explanation of why REQ_META + * is treated special. + */ + 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); + DBG("[%s] s\n",__func__); + 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->cmd.flags = 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->stop.flags = 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; + + if (brq->data.blocks > 1) { + /* + * 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; + + /* Some controllers can't do multiblock reads due to hw bugs */ + if (card->host->caps2 & MMC_CAP2_NO_MULTI_READ && + rq_data_dir(req) == READ) + 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); + + /* + * Data tag is used only during writing meta data to speed + * up write and any subsequent read of this meta data + */ + do_data_tag = (card->ext_csd.data_tag_unit_size) && + (req->cmd_flags & REQ_META) && + (rq_data_dir(req) == WRITE) && + ((brq->data.blocks * brq->data.blksz) >= + card->ext_csd.data_tag_unit_size); + + /* + * 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) || + do_data_tag)) { + brq->sbc.opcode = MMC_SET_BLOCK_COUNT; + brq->sbc.arg = brq->data.blocks | + (do_rel_wr ? (1 << 31) : 0) | + (do_data_tag ? (1 << 29) : 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); + DBG("[%s] e\n",__func__); +} + +static int mmc_blk_cmd_err(struct mmc_blk_data *md, struct mmc_card *card, + struct mmc_blk_request *brq, struct request *req, + int ret) +{ + DBG("[%s] s\n",__func__); + /* + * 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); + } + DBG("[%s] e\n",__func__); + return ret; +} + +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, type; + enum mmc_blk_status status; + struct mmc_queue_req *mq_rq; + struct request *req; + struct mmc_async_req *areq; + DBG("[%s] s\n",__func__); + + if (!rqc && !mq->mqrq_prev->req) { + DBG("[%s] e1\n",__func__); + 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) { + DBG("[%s] e2\n",__func__); + return 0; + } + + mq_rq = container_of(areq, struct mmc_queue_req, mmc_active); + brq = &mq_rq->brq; + req = mq_rq->req; + type = rq_data_dir(req) == READ ? MMC_BLK_READ : MMC_BLK_WRITE; + mmc_queue_bounce_post(mq_rq); + + switch (status) { + case MMC_BLK_SUCCESS: + case MMC_BLK_PARTIAL: + /* + * A block was successfully transferred. + */ + mmc_blk_reset_success(md, type); + spin_lock_irq(&md->lock); + ret = __blk_end_request(req, 0, + brq->data.bytes_xfered); + spin_unlock_irq(&md->lock); + /* + * If the blk_end_request function returns non-zero even + * though all data has been transferred and no errors + * were returned by the host controller, it's a bug. + */ + if (status == MMC_BLK_SUCCESS && ret) { + pr_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: + ret = mmc_blk_cmd_err(md, card, brq, req, ret); + if (!mmc_blk_reset(md, card->host, type)) + break; + goto cmd_abort; + case MMC_BLK_RETRY: + if (retry++ < 5) + break; + /* Fall through */ + case MMC_BLK_ABORT: + if (!mmc_blk_reset(md, card->host, type)) + break; + goto cmd_abort; + case MMC_BLK_DATA_ERR: { + int err; + + err = mmc_blk_reset(md, card->host, type); + if (!err) + break; + if (err == -ENODEV) + goto cmd_abort; + /* Fall through */ + } + case MMC_BLK_ECC_ERR: + 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); + disable_multi = 1; + break; + } + /* + * 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; + case MMC_BLK_NOMEDIUM: + goto cmd_abort; + } + + if (ret) { + /* + * In case of a incomplete 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); + DBG("[%s] e3\n",__func__); + return 1; + + cmd_abort: + spin_lock_irq(&md->lock); + if (mmc_card_removed(card)) + req->cmd_flags |= REQ_QUIET; + 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); + } + DBG("[%s] e4\n",__func__); + 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; + DBG("[%s] s\n",__func__); + +#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); + +#ifdef CONFIG_MMC_UNSAFE_RESUME +/* + if(card->host->card_attath_status == card_attach_status_change) { + goto cmd_err; + } +*/ +#endif + + ret = mmc_blk_part_switch(card, md); + if (ret) { + if (req) { + spin_lock_irq(&md->lock); + __blk_end_request_all(req, -EIO); + spin_unlock_irq(&md->lock); + } + 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); + DBG("[%s] e\n",__func__); + return ret; +} + +static inline int mmc_blk_readonly(struct mmc_card *card) +{ + DBG("[%s] s\n",__func__); + 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, + int area_type) +{ + struct mmc_blk_data *md; + int devidx, ret; + DBG("[%s] s\n",__func__); + + devidx = find_first_zero_bit(dev_use, max_devices); + if (devidx >= max_devices) { + DBG("[%s] e1\n",__func__); + 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; + + md->area_type = area_type; + + /* + * 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 : "");*/ + + snprintf(md->disk->disk_name, sizeof(md->disk->disk_name), + "mmcblk%d%s", card->host->wmt_host_index, 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); + } + DBG("[%s] e2\n",__func__); + return md; + + err_putdisk: + put_disk(md->disk); + err_kfree: + kfree(md); + out: + DBG("[%s] e3\n",__func__); + return ERR_PTR(ret); +} + +static struct mmc_blk_data *mmc_blk_alloc(struct mmc_card *card) +{ + sector_t size; + struct mmc_blk_data *md; + DBG("[%s] s\n",__func__); + + 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, + MMC_BLK_DATA_AREA_MAIN); + DBG("[%s] e\n",__func__); + 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, + int area_type) +{ + char cap_str[10]; + struct mmc_blk_data *part_md; + DBG("[%s] s\n",__func__); + + part_md = mmc_blk_alloc_req(card, disk_to_dev(md->disk), size, default_ro, + subname, area_type); + if (IS_ERR(part_md)) { + DBG("[%s] e1\n",__func__); + 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)); + pr_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); + DBG("[%s] e2\n",__func__); + return 0; +} + +/* MMC Physical partitions consist of two boot partitions and + * up to four general purpose partitions. + * For each partition enabled in EXT_CSD a block device will be allocatedi + * to provide access to the partition. + */ + +static int mmc_blk_alloc_parts(struct mmc_card *card, struct mmc_blk_data *md) +{ + int idx, ret = 0; + DBG("[%s] s\n",__func__); + + if (!mmc_card_mmc(card)) { + DBG("[%s] e1\n",__func__); + return 0; + } + + for (idx = 0; idx < card->nr_parts; idx++) { + if (card->part[idx].size) { + ret = mmc_blk_alloc_part(card, md, + card->part[idx].part_cfg, + card->part[idx].size >> 9, + card->part[idx].force_ro, + card->part[idx].name, + card->part[idx].area_type); + if (ret) { + DBG("[%s] e2\n",__func__); + return ret; + } + } + } + DBG("[%s] e3\n",__func__); + return ret; +} + +static void mmc_blk_remove_req(struct mmc_blk_data *md) +{ + struct mmc_card *card; + DBG("[%s] s\n",__func__); + + if (md) { + card = md->queue.card; + if (md->disk->flags & GENHD_FL_UP) { + device_remove_file(disk_to_dev(md->disk), &md->force_ro); + if ((md->area_type & MMC_BLK_DATA_AREA_BOOT) && + card->ext_csd.boot_ro_lockable) + device_remove_file(disk_to_dev(md->disk), + &md->power_ro_lock); + + /* 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); + } + DBG("[%s] e\n",__func__); +} + +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; + DBG("[%s] s\n",__func__); + + __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); + } + DBG("[%s] e\n",__func__); +} + +static int mmc_add_disk(struct mmc_blk_data *md) +{ + int ret; + struct mmc_card *card = md->queue.card; + DBG("[%s] s\n",__func__); + + 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) + goto force_ro_fail; + + if ((md->area_type & MMC_BLK_DATA_AREA_BOOT) && + card->ext_csd.boot_ro_lockable) { + umode_t mode; + + if (card->ext_csd.boot_ro_lock & EXT_CSD_BOOT_WP_B_PWR_WP_DIS) + mode = S_IRUGO; + else + mode = S_IRUGO | S_IWUSR; + + md->power_ro_lock.show = power_ro_lock_show; + md->power_ro_lock.store = power_ro_lock_store; + sysfs_attr_init(&md->power_ro_lock.attr); + md->power_ro_lock.attr.mode = mode; + md->power_ro_lock.attr.name = + "ro_lock_until_next_power_on"; + ret = device_create_file(disk_to_dev(md->disk), + &md->power_ro_lock); + if (ret) + goto power_ro_lock_fail; + } + DBG("[%s] e1\n",__func__); + return ret; + +power_ro_lock_fail: + device_remove_file(disk_to_dev(md->disk), &md->force_ro); +force_ro_fail: + del_gendisk(md->disk); + DBG("[%s] e2\n",__func__); + return ret; +} + +#define CID_MANFID_SANDISK 0x2 +#define CID_MANFID_TOSHIBA 0x11 +#define CID_MANFID_MICRON 0x13 + +static const struct mmc_fixup blk_fixups[] = +{ + MMC_FIXUP("SEM02G", CID_MANFID_SANDISK, 0x100, add_quirk, + MMC_QUIRK_INAND_CMD38), + MMC_FIXUP("SEM04G", CID_MANFID_SANDISK, 0x100, add_quirk, + MMC_QUIRK_INAND_CMD38), + MMC_FIXUP("SEM08G", CID_MANFID_SANDISK, 0x100, add_quirk, + MMC_QUIRK_INAND_CMD38), + MMC_FIXUP("SEM16G", CID_MANFID_SANDISK, 0x100, add_quirk, + MMC_QUIRK_INAND_CMD38), + MMC_FIXUP("SEM32G", CID_MANFID_SANDISK, 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", CID_MANFID_TOSHIBA, CID_OEMID_ANY, add_quirk_mmc, + MMC_QUIRK_BLK_NO_CMD23), + MMC_FIXUP("MMC16G", CID_MANFID_TOSHIBA, CID_OEMID_ANY, add_quirk_mmc, + MMC_QUIRK_BLK_NO_CMD23), + MMC_FIXUP("MMC32G", CID_MANFID_TOSHIBA, CID_OEMID_ANY, add_quirk_mmc, + MMC_QUIRK_BLK_NO_CMD23), + + /* + * Some Micron MMC cards needs longer data read timeout than + * indicated in CSD. + */ + MMC_FIXUP(CID_NAME_ANY, CID_MANFID_MICRON, 0x200, add_quirk_mmc, + MMC_QUIRK_LONG_READ_TIME), + + END_FIXUP +}; + +static int mmc_blk_probe(struct mmc_card *card) +{ + struct mmc_blk_data *md, *part_md; + char cap_str[10]; + DBG("[%s] s\n",__func__); + /* + * Check that the card supports the command class(es) we need. + */ + if (!(card->csd.cmdclass & CCC_BLOCK_READ)) { + DBG("[%s] e1\n",__func__); + return -ENODEV; + } + md = mmc_blk_alloc(card); + if (IS_ERR(md)) { + DBG("[%s] e2\n",__func__); + return PTR_ERR(md); + } + + string_get_size((u64)get_capacity(md->disk) << 9, STRING_UNITS_2, + cap_str, sizeof(cap_str)); + pr_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; + + 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; + } + + DBG("[%s] e3\n",__func__); + return 0; + + out: + mmc_blk_remove_parts(card, md); + mmc_blk_remove_req(md); + DBG("[%s] e4\n",__func__); + return 0; +} + +static void mmc_blk_remove(struct mmc_card *card) +{ + struct mmc_blk_data *md = mmc_get_drvdata(card); + DBG("[%s] s\n",__func__); + + 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 + DBG("[%s] e\n",__func__); +} + +#ifdef CONFIG_PM +static int mmc_blk_suspend(struct mmc_card *card) +{ + struct mmc_blk_data *part_md; + struct mmc_blk_data *md = mmc_get_drvdata(card); + DBG("[%s] s\n",__func__); + + if (md) { + mmc_queue_suspend(&md->queue); + list_for_each_entry(part_md, &md->part, part) { + mmc_queue_suspend(&part_md->queue); + } + } + DBG("[%s] e\n",__func__); + 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); + DBG("[%s] s\n",__func__); + + if (md) { + /* + * 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); + } + } + DBG("[%s] e\n",__func__); + 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; + DBG("[%s] s\n",__func__); + + 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; + DBG("[%s] e1\n",__func__); + return 0; + out2: + unregister_blkdev(MMC_BLOCK_MAJOR, "mmc"); + out: + DBG("[%s] e2\n",__func__); + return res; +} + +static void __exit mmc_blk_exit(void) +{ + DBG("[%s] s\n",__func__); + mmc_unregister_driver(&mmc_driver); + unregister_blkdev(MMC_BLOCK_MAJOR, "mmc"); + DBG("[%s] e\n",__func__); +} + +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..759714ed --- /dev/null +++ b/drivers/mmc/card/mmc_test.c @@ -0,0 +1,3055 @@ +/* + * 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> +#include <linux/module.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]) == R1_STATE_PRG); +} + +/* + * 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) + pr_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 */ + + pr_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 */ + + pr_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) +{ + pr_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) + pr_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_seg_sz -= t->max_seg_sz % 512; + + 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: + pr_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))) { + pr_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); +} + +/* + * eMMC hardware reset. + */ +static int mmc_test_hw_reset(struct mmc_test_card *test) +{ + struct mmc_card *card = test->card; + struct mmc_host *host = card->host; + int err; + + err = mmc_hw_reset_check(host); + if (!err) + return RESULT_OK; + + if (err == -ENOSYS) + return RESULT_FAIL; + + if (err != -EOPNOTSUPP) + return err; + + if (!mmc_can_reset(card)) + return RESULT_UNSUP_CARD; + + return RESULT_UNSUP_HOST; +} + +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, + }, + + { + .name = "eMMC hardware reset", + .run = mmc_test_hw_reset, + }, +}; + +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; + + pr_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; + + pr_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) { + pr_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: + pr_info("%s: Result: OK\n", + mmc_hostname(test->card->host)); + break; + case RESULT_FAIL: + pr_info("%s: Result: FAILED\n", + mmc_hostname(test->card->host)); + break; + case RESULT_UNSUP_HOST: + pr_info("%s: Result: UNSUPPORTED " + "(by host)\n", + mmc_hostname(test->card->host)); + break; + case RESULT_UNSUP_CARD: + pr_info("%s: Result: UNSUPPORTED " + "(by card)\n", + mmc_hostname(test->card->host)); + break; + default: + pr_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) { + pr_info("%s: Warning: Cleanup " + "stage failed! (%d)\n", + mmc_hostname(test->card->host), + ret); + } + } + } + + mmc_release_host(test->card->host); + + pr_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_dbgfs_file(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_dbgfs_file(struct mmc_card *card, + const char *name, umode_t mode, const struct file_operations *fops) +{ + struct dentry *file = NULL; + struct mmc_test_dbgfs_file *df; + + if (card->debugfs_root) + file = debugfs_create_file(name, mode, card->debugfs_root, + card, fops); + + if (IS_ERR_OR_NULL(file)) { + dev_err(&card->dev, + "Can't create %s. Perhaps debugfs is disabled.\n", + name); + return -ENODEV; + } + + 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"); + return -ENOMEM; + } + + df->card = card; + df->file = file; + + list_add(&df->link, &mmc_test_file_test); + return 0; +} + +static int mmc_test_register_dbgfs_file(struct mmc_card *card) +{ + int ret; + + mutex_lock(&mmc_test_lock); + + ret = __mmc_test_register_dbgfs_file(card, "test", S_IWUSR | S_IRUGO, + &mmc_test_fops_test); + if (ret) + goto err; + + ret = __mmc_test_register_dbgfs_file(card, "testlist", S_IRUGO, + &mmc_test_fops_testlist); + if (ret) + goto err; + +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_dbgfs_file(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_dbgfs_file(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_dbgfs_file(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..996f8e36 --- /dev/null +++ b/drivers/mmc/card/queue.c @@ -0,0 +1,438 @@ +/* + * 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 65536 + +#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) +{ + struct mmc_queue *mq = q->queuedata; + + /* + * 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; + } + + if (mq && mmc_card_removed(mq->card)) + 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); +} + +static 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 && !mmc_can_discard(card)) + 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) || mmc_can_sanitize(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) { + pr_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) { + pr_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..5a2cbfac --- /dev/null +++ b/drivers/mmc/card/sdio_uart.c @@ -0,0 +1,1219 @@ +/* + * 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_standard_install(driver, tty); + + if (ret == 0) + /* This is the ref sdio_uart_port get provided */ + tty->driver_data = port; + 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) { + pr_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) { + pr_warning( + "%s: can't find tuple 0x91 subtuple 0 (SUBTPL_SIOREG) for GPS class\n", + sdio_func_id(func)); + kfree(port); + return -EINVAL; + } + pr_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); + pr_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; + pr_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->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/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..dca44283 --- /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 cd-gpio.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..e2cbbc3c --- /dev/null +++ b/drivers/mmc/core/bus.c @@ -0,0 +1,454 @@ +/* + * 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/export.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/stat.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) + +#if 0 +#define DBG(x...) printk(KERN_ALERT x) +#else +#define DBG(x...) do { } while (0) +#endif + +static ssize_t mmc_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmc_card *card = mmc_dev_to_card(dev); + + DBG("[%s] s\n",__func__); + + switch (card->type) { + case MMC_TYPE_MMC: + DBG("[%s] e1\n",__func__); + return sprintf(buf, "MMC\n"); + case MMC_TYPE_SD: + DBG("[%s] e2\n",__func__); + return sprintf(buf, "SD\n"); + case MMC_TYPE_SDIO: + DBG("[%s] e3\n",__func__); + return sprintf(buf, "SDIO\n"); + case MMC_TYPE_SD_COMBO: + DBG("[%s] e4\n",__func__); + return sprintf(buf, "SDcombo\n"); + default: + DBG("[%s] e5\n",__func__); + 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) +{ + DBG("[%s]\n",__func__); + 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; + + DBG("[%s] s\n",__func__); + + 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) { + DBG("[%s] e1\n",__func__); + return retval; + } + } + + retval = add_uevent_var(env, "MMC_NAME=%s", mmc_card_name(card)); + if (retval) { + DBG("[%s] e2\n",__func__); + 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"); + + DBG("[%s] e3\n",__func__); + 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); + + DBG("[%s]\n",__func__); + 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); + + DBG("[%s] s\n",__func__); + + drv->remove(card); + + DBG("[%s] e\n",__func__); + return 0; +} + +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); + pm_message_t state = { PM_EVENT_SUSPEND }; + int ret = 0; + + DBG("[%s] s\n",__func__); + + if (dev->driver && drv->suspend) + ret = drv->suspend(card); + + DBG("[%s] e\n",__func__); + 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; + + DBG("[%s] s\n",__func__); + + if (dev->driver && drv->resume) + ret = drv->resume(card); + + DBG("[%s] e\n",__func__); + return ret; +} + +#ifdef CONFIG_PM_RUNTIME + +static int mmc_runtime_suspend(struct device *dev) +{ + struct mmc_card *card = mmc_dev_to_card(dev); + int ret; + + DBG("[%s] s\n",__func__); + + ret = mmc_power_save_host(card->host); + + DBG("[%s] e\n",__func__); + /*return mmc_power_save_host(card->host);*/ + return ret; + +} + +static int mmc_runtime_resume(struct device *dev) +{ + struct mmc_card *card = mmc_dev_to_card(dev); + int ret; + + DBG("[%s] s\n",__func__); + + ret = mmc_power_restore_host(card->host); + + DBG("[%s] e\n",__func__); + /*return mmc_power_restore_host(card->host);*/ + return ret; + +} + +static int mmc_runtime_idle(struct device *dev) +{ + int ret; + + DBG("[%s] s\n",__func__); + + ret = pm_runtime_suspend(dev); + + DBG("[%s] e\n",__func__); + /*return pm_runtime_suspend(dev);*/ + return ret; +} + +#endif /* !CONFIG_PM_RUNTIME */ + +static const struct dev_pm_ops mmc_bus_pm_ops = { + SET_RUNTIME_PM_OPS(mmc_runtime_suspend, mmc_runtime_resume, + mmc_runtime_idle) + SET_SYSTEM_SLEEP_PM_OPS(mmc_bus_suspend, mmc_bus_resume) +}; + +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) +{ + int ret; + + DBG("[%s] s\n",__func__); + + ret = bus_register(&mmc_bus_type); + + DBG("[%s] e\n",__func__); + return ret; + /*return bus_register(&mmc_bus_type);*/ +} + +void mmc_unregister_bus(void) +{ + DBG("[%s] s\n",__func__); + + bus_unregister(&mmc_bus_type); + + DBG("[%s] e\n",__func__); +} + +/** + * mmc_register_driver - register a media driver + * @drv: MMC media driver + */ +int mmc_register_driver(struct mmc_driver *drv) +{ + int ret; + + DBG("[%s] s\n",__func__); + + drv->drv.bus = &mmc_bus_type; + ret = driver_register(&drv->drv); + + DBG("[%s] e\n",__func__); + /*return driver_register(&drv->drv);*/ + return ret; + +} + +EXPORT_SYMBOL(mmc_register_driver); + +/** + * mmc_unregister_driver - unregister a media driver + * @drv: MMC media driver + */ +void mmc_unregister_driver(struct mmc_driver *drv) +{ + DBG("[%s] s\n",__func__); + + drv->drv.bus = &mmc_bus_type; + driver_unregister(&drv->drv); + + DBG("[%s] e\n",__func__); +} + +EXPORT_SYMBOL(mmc_unregister_driver); + +static void mmc_release_card(struct device *dev) +{ + struct mmc_card *card = mmc_dev_to_card(dev); + + DBG("[%s] s\n",__func__); + + sdio_free_common_cis(card); + + if (card->info) + kfree(card->info); + + kfree(card); + + DBG("[%s] e\n",__func__); +} + +/* + * 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; + DBG("[%s] s\n",__func__); + + card = kzalloc(sizeof(struct mmc_card), GFP_KERNEL); + if (!card) { + DBG("[%s] e1\n",__func__); + 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; + + DBG("[%s] e2\n",__func__); + return card; +} + +/* + * Register a new MMC card with the driver model. + */ +int mmc_add_card(struct mmc_card *card) +{ + int ret; + const char *type; + const char *uhs_bus_speed_mode = ""; + static const char *const uhs_speeds[] = { + [UHS_SDR12_BUS_SPEED] = "SDR12 ", + [UHS_SDR25_BUS_SPEED] = "SDR25 ", + [UHS_SDR50_BUS_SPEED] = "SDR50 ", + [UHS_SDR104_BUS_SPEED] = "SDR104 ", + [UHS_DDR50_BUS_SPEED] = "DDR50 ", + }; + DBG("[%s] s\n",__func__); + + 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_sd_card_uhs(card) && + (card->sd_bus_speed < ARRAY_SIZE(uhs_speeds))) + uhs_bus_speed_mode = uhs_speeds[card->sd_bus_speed]; + + if (mmc_host_is_spi(card->host)) { + pr_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 { + pr_info("%s: new %s%s%s%s%s card at address %04x\n", + mmc_hostname(card->host), + mmc_card_uhs(card) ? "ultra high speed " : + (mmc_card_highspeed(card) ? "high speed " : ""), + (mmc_card_hs200(card) ? "HS200 " : ""), + mmc_card_ddr_mode(card) ? "DDR " : "", + uhs_bus_speed_mode, type, card->rca); + } + +#ifdef CONFIG_DEBUG_FS + mmc_add_card_debugfs(card); +#endif + + ret = device_add(&card->dev); + if (ret) { + DBG("[%s] e1\n",__func__); + return ret; + } + + mmc_card_set_present(card); + + DBG("[%s] e2\n",__func__); + return 0; +} + +/* + * Unregister a new MMC card with the driver model, and + * (eventually) free it. + */ +void mmc_remove_card(struct mmc_card *card) +{ + DBG("[%s] s\n",__func__); + +#ifdef CONFIG_DEBUG_FS + mmc_remove_card_debugfs(card); +#endif + + if (mmc_card_present(card)) { + if (mmc_host_is_spi(card->host)) { + pr_info("%s: SPI card removed\n", + mmc_hostname(card->host)); + } else { + pr_info("%s: card %04x removed\n", + mmc_hostname(card->host), card->rca); + } + device_del(&card->dev); + } + + put_device(&card->dev); + + DBG("[%s] e\n",__func__); +} + 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/cd-gpio.c b/drivers/mmc/core/cd-gpio.c new file mode 100644 index 00000000..f13e38de --- /dev/null +++ b/drivers/mmc/core/cd-gpio.c @@ -0,0 +1,83 @@ +/* + * Generic GPIO card-detect helper + * + * Copyright (C) 2011, Guennadi Liakhovetski <g.liakhovetski@gmx.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/mmc/cd-gpio.h> +#include <linux/mmc/host.h> +#include <linux/module.h> +#include <linux/slab.h> + +struct mmc_cd_gpio { + unsigned int gpio; + char label[0]; +}; + +static irqreturn_t mmc_cd_gpio_irqt(int irq, void *dev_id) +{ + /* Schedule a card detection after a debounce timeout */ + mmc_detect_change(dev_id, msecs_to_jiffies(100)); + return IRQ_HANDLED; +} + +int mmc_cd_gpio_request(struct mmc_host *host, unsigned int gpio) +{ + size_t len = strlen(dev_name(host->parent)) + 4; + struct mmc_cd_gpio *cd; + int irq = gpio_to_irq(gpio); + int ret; + + if (irq < 0) + return irq; + + cd = kmalloc(sizeof(*cd) + len, GFP_KERNEL); + if (!cd) + return -ENOMEM; + + snprintf(cd->label, len, "%s cd", dev_name(host->parent)); + + ret = gpio_request_one(gpio, GPIOF_DIR_IN, cd->label); + if (ret < 0) + goto egpioreq; + + ret = request_threaded_irq(irq, NULL, mmc_cd_gpio_irqt, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + cd->label, host); + if (ret < 0) + goto eirqreq; + + cd->gpio = gpio; + host->hotplug.irq = irq; + host->hotplug.handler_priv = cd; + + return 0; + +eirqreq: + gpio_free(gpio); +egpioreq: + kfree(cd); + return ret; +} +EXPORT_SYMBOL(mmc_cd_gpio_request); + +void mmc_cd_gpio_free(struct mmc_host *host) +{ + struct mmc_cd_gpio *cd = host->hotplug.handler_priv; + + if (!cd) + return; + + free_irq(host->hotplug.irq, host); + gpio_free(cd->gpio); + kfree(cd); +} +EXPORT_SYMBOL(mmc_cd_gpio_free); diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c new file mode 100644 index 00000000..7dc7edd5 --- /dev/null +++ b/drivers/mmc/core/core.c @@ -0,0 +1,2958 @@ +/* + * 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/suspend.h> +#include <linux/fault-inject.h> +#include <linux/random.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" +#include "../host/mmc_atsmb.h" +#include <mach/hardware.h> + + +static struct workqueue_struct *workqueue; + +#if 0 +#define DBG(x...) printk(KERN_ALERT x) +#else +#define DBG(x...) do { } while (0) +#endif + +/* + * 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. + */ +bool 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 +bool mmc_assume_removable; +#else +bool 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) +{ + int ret; + DBG("[%s] s\n",__func__); + + ret = queue_delayed_work(workqueue, work, delay); + + DBG("[%s] e\n",__func__); + return ret; + /*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) +{ + DBG("[%s] s\n",__func__); + + flush_workqueue(workqueue); + + DBG("[%s] e\n",__func__); +} + +#ifdef CONFIG_FAIL_MMC_REQUEST + +/* + * Internal function. Inject random data errors. + * If mmc_data is NULL no errors are injected. + */ +static void mmc_should_fail_request(struct mmc_host *host, + struct mmc_request *mrq) +{ + struct mmc_command *cmd = mrq->cmd; + struct mmc_data *data = mrq->data; + static const int data_errors[] = { + -ETIMEDOUT, + -EILSEQ, + -EIO, + }; + + if (!data) + return; + + if (cmd->error || data->error || + !should_fail(&host->fail_mmc_request, data->blksz * data->blocks)) + return; + + data->error = data_errors[random32() % ARRAY_SIZE(data_errors)]; + data->bytes_xfered = (random32() % (data->bytes_xfered >> 9)) << 9; +} + +#else /* CONFIG_FAIL_MMC_REQUEST */ + +static inline void mmc_should_fail_request(struct mmc_host *host, + struct mmc_request *mrq) +{ +} + +#endif /* CONFIG_FAIL_MMC_REQUEST */ + +/** + * 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; + DBG("[%s] s\n",__func__); + + if (err && cmd->retries && mmc_host_is_spi(host)) { + if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND) + cmd->retries = 0; + } + + if (err && cmd->retries && !mmc_card_removed(host->card)) { + /* + * Request starter must handle retries - see + * mmc_wait_for_req_done(). + */ + if (mrq->done) + mrq->done(mrq); + } else { + mmc_should_fail_request(host, mrq); + + 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); + } + DBG("[%s] e\n",__func__); +} + +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 + DBG("[%s] s\n",__func__); + + if (mrq->sbc) { + pr_debug("<%s: starting CMD%u arg %08x flags %08x>\n", + mmc_hostname(host), mrq->sbc->opcode, + mrq->sbc->arg, mrq->sbc->flags); + } + + 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); + + DBG("[%s] e\n",__func__); +} + +static void mmc_wait_done(struct mmc_request *mrq) +{ + DBG("[%s] s\n",__func__); + + complete(&mrq->completion); + + DBG("[%s] e\n",__func__); +} + +static int __mmc_start_req(struct mmc_host *host, struct mmc_request *mrq) +{ + DBG("[%s] s\n",__func__); + init_completion(&mrq->completion); + mrq->done = mmc_wait_done; + if (mmc_card_removed(host->card)) { + mrq->cmd->error = -ENOMEDIUM; + complete(&mrq->completion); + DBG("[%s] e1\n",__func__); + return -ENOMEDIUM; + } + mmc_start_request(host, mrq); + DBG("[%s] e2\n",__func__); + return 0; +} + +static void mmc_wait_for_req_done(struct mmc_host *host, + struct mmc_request *mrq) +{ + struct mmc_command *cmd; + DBG("[%s] s\n",__func__); + while (1) { + wait_for_completion(&mrq->completion); + + cmd = mrq->cmd; + if (!cmd->error || !cmd->retries || + mmc_card_removed(host->card)) + 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); + } + DBG("[%s] e\n",__func__); +} + +/** + * 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) +{ + DBG("[%s] s\n",__func__); + if (host->ops->pre_req) { + mmc_host_clk_hold(host); + host->ops->pre_req(host, mrq, is_first_req); + mmc_host_clk_release(host); + } + DBG("[%s] e\n",__func__); +} + +/** + * 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) +{ + DBG("[%s] s\n",__func__); + if (host->ops->post_req) { + mmc_host_clk_hold(host); + host->ops->post_req(host, mrq, err); + mmc_host_clk_release(host); + } + DBG("[%s] e\n",__func__); +} + +/** + * 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; + int start_err = 0; + struct mmc_async_req *data = host->areq; + DBG("[%s] s\n",__func__); + /* 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 && areq) + start_err = __mmc_start_req(host, areq->mrq); + + if (host->areq) + mmc_post_req(host, host->areq->mrq, 0); + + /* Cancel a prepared request if it was not started. */ + if ((err || start_err) && areq) + mmc_post_req(host, areq->mrq, -EINVAL); + + if (err) + host->areq = NULL; + else + host->areq = areq; + + if (error) + *error = err; + DBG("[%s] e\n",__func__); + 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) +{ + DBG("[%s] s\n",__func__); + __mmc_start_req(host, mrq); + mmc_wait_for_req_done(host, mrq); + DBG("[%s] e\n",__func__); +} +EXPORT_SYMBOL(mmc_wait_for_req); + +/** + * mmc_interrupt_hpi - Issue for High priority Interrupt + * @card: the MMC card associated with the HPI transfer + * + * Issued High Priority Interrupt, and check for card status + * util out-of prg-state. + */ +int mmc_interrupt_hpi(struct mmc_card *card) +{ + int err; + u32 status; + DBG("[%s] s\n",__func__); + BUG_ON(!card); + + if (!card->ext_csd.hpi_en) { + pr_info("%s: HPI enable bit unset\n", mmc_hostname(card->host)); + return 1; + } + + mmc_claim_host(card->host); + err = mmc_send_status(card, &status); + if (err) { + pr_err("%s: Get card status fail\n", mmc_hostname(card->host)); + goto out; + } + + /* + * If the card status is in PRG-state, we can send the HPI command. + */ + if (R1_CURRENT_STATE(status) == R1_STATE_PRG) { + do { + /* + * We don't know when the HPI command will finish + * processing, so we need to resend HPI until out + * of prg-state, and keep checking the card status + * with SEND_STATUS. If a timeout error occurs when + * sending the HPI command, we are already out of + * prg-state. + */ + err = mmc_send_hpi_cmd(card, &status); + if (err) + pr_debug("%s: abort HPI (%d error)\n", + mmc_hostname(card->host), err); + + err = mmc_send_status(card, &status); + if (err) + break; + } while (R1_CURRENT_STATE(status) == R1_STATE_PRG); + } else + pr_debug("%s: Left prg-state\n", mmc_hostname(card->host)); + +out: + mmc_release_host(card->host); + DBG("[%s] e\n",__func__); + return err; +} +EXPORT_SYMBOL(mmc_interrupt_hpi); + +/** + * 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 = {NULL}; + + DBG("[%s] s\n",__func__); + + 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); + + DBG("[%s] e\n",__func__); + 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; + DBG("[%s] s\n",__func__); + /* + * SDIO cards only define an upper 1 s limit on access. + */ + if (mmc_card_sdio(card)) { + data->timeout_ns = 1000000000; + data->timeout_clks = 0; + DBG("[%s] e1\n",__func__); + 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 MMC spec "It is strongly recommended + * for hosts to implement more than 500ms + * timeout value even if the card indicates + * the 250ms maximum busy length." Even the + * previous value of 300ms is known to be + * insufficient for some cards. + */ + limit_us = 3000000; + 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 require longer data read timeout than indicated in CSD. + * Address this by setting the read timeout to a "reasonably high" + * value. For the cards tested, 300ms has proven enough. If necessary, + * this value can be increased if other problematic cards require this. + */ + if (mmc_card_long_read_time(card) && data->flags & MMC_DATA_READ) { + data->timeout_ns = 300000000; + 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 */ + } + } + + DBG("[%s] e2\n",__func__); +} +EXPORT_SYMBOL(mmc_set_data_timeout); + +/** + * mmc_align_data_size - pads a transfer size to a more optimal value + * @card: the MMC card associated with the data transfer + * @sz: original transfer size + * + * Pads the original data size with a number of extra bytes in + * order to avoid controller bugs and/or performance hits + * (e.g. some controllers revert to PIO for certain sizes). + * + * Returns the improved size, which might be unmodified. + * + * Note that this function is only relevant when issuing a + * single scatter gather entry. + */ +unsigned int mmc_align_data_size(struct mmc_card *card, unsigned int sz) +{ + /* + * FIXME: We don't have a system for the controller to tell + * the core about its problems yet, so for now we just 32-bit + * align the size. + */ + sz = ((sz + 3) / 4) * 4; + + return sz; +} +EXPORT_SYMBOL(mmc_align_data_size); + +/** + * __mmc_claim_host - exclusively claim a host + * @host: mmc host to claim + * @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; + + DBG("[%s] s\n",__func__); + + 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 (host->ops->enable && !stop && host->claim_cnt == 1) + host->ops->enable(host); + + DBG("[%s] e\n",__func__); + 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; + DBG("[%s] s\n",__func__); + + 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); + if (host->ops->enable && claimed_host && host->claim_cnt == 1) + host->ops->enable(host); + + DBG("[%s] e\n",__func__); + return claimed_host; +} +EXPORT_SYMBOL(mmc_try_claim_host); + +/** + * 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) +{ + unsigned long flags; + DBG("[%s] s\n",__func__); + + WARN_ON(!host->claimed); + + if (host->ops->disable && host->claim_cnt == 1) + host->ops->disable(host); + + 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); + } + DBG("[%s] e\n",__func__); +} +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; + + DBG("[%s] s\n",__func__); + + 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); + + DBG("[%s] e\n",__func__); +} + +/* + * Control chip select pin on a host. + */ +void mmc_set_chip_select(struct mmc_host *host, int mode) +{ + DBG("[%s] s\n",__func__); + + mmc_host_clk_hold(host); + host->ios.chip_select = mode; + mmc_set_ios(host); + mmc_host_clk_release(host); + + DBG("[%s] e\n",__func__); +} + +/* + * 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) +{ + DBG("[%s] s\n",__func__); + + WARN_ON(hz < host->f_min); + + if (hz > host->f_max) + hz = host->f_max; + + host->ios.clock = hz; + mmc_set_ios(host); + + DBG("[%s] e\n",__func__); +} + +void mmc_set_clock(struct mmc_host *host, unsigned int hz) +{ + DBG("[%s] s\n",__func__); + mmc_host_clk_hold(host); + __mmc_set_clock(host, hz); + mmc_host_clk_release(host); + + DBG("[%s] e\n",__func__); +} + +#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; + DBG("[%s] s\n",__func__); + + 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); + + DBG("[%s] e\n",__func__); +} + +/* + * This restores the clock from gating by using the cached + * clock value. + */ +void mmc_ungate_clock(struct mmc_host *host) +{ + DBG("[%s] s\n",__func__); + /* + * 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); + } + + DBG("[%s] e\n",__func__); +} + +void mmc_set_ungated(struct mmc_host *host) +{ + unsigned long flags; + DBG("[%s] s\n",__func__); + /* + * 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); + + DBG("[%s] e\n",__func__); +} + +#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) +{ + DBG("[%s] s\n",__func__); + + mmc_host_clk_hold(host); + host->ios.bus_mode = mode; + mmc_set_ios(host); + mmc_host_clk_release(host); + + DBG("[%s] e\n",__func__); +} + +/* + * Change data bus width of a host. + */ +void mmc_set_bus_width(struct mmc_host *host, unsigned int width) +{ + DBG("[%s] s\n",__func__); + + mmc_host_clk_hold(host); + host->ios.bus_width = width; + mmc_set_ios(host); + mmc_host_clk_release(host); + + DBG("[%s] e\n",__func__); +} + +/** + * 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; + + DBG("[%s] s\n",__func__); + + if (vdd < 1650 || vdd > 3600) { + DBG("[%s] e1\n",__func__); + return -EINVAL; + } + + if (vdd >= 1650 && vdd <= 1950) { + DBG("[%s] e2\n",__func__); + 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) { + DBG("[%s] e3\n",__func__); + return max_bit; + } + DBG("[%s] e4\n",__func__); + 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; + + DBG("[%s] s\n",__func__); + + if (vdd_max < vdd_min) { + DBG("[%s] e1\n",__func__); + return 0; + } + + /* Prefer high bits for the boundary vdd_max values. */ + vdd_max = mmc_vdd_to_ocrbitnum(vdd_max, false); + if (vdd_max < 0) { + DBG("[%s] e2\n",__func__); + return 0; + } + + /* Prefer low bits for the boundary vdd_min values. */ + vdd_min = mmc_vdd_to_ocrbitnum(vdd_min, true); + if (vdd_min < 0) { + DBG("[%s] e3\n",__func__); + return 0; + } + + /* Fill the mask, from max bit to min bit. */ + while (vdd_max >= vdd_min) + mask |= 1 << vdd_max--; + + DBG("[%s] e4\n",__func__); + 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; + + DBG("[%s] s\n",__func__); + + count = regulator_count_voltages(supply); + if (count < 0) { + DBG("[%s] e1\n",__func__); + 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); + } + + DBG("[%s] e2\n",__func__); + 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; + + DBG("[%s] s\n",__func__); + + 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 (mmc->caps2 & MMC_CAP2_BROKEN_VOLTAGE) + min_uV = max_uV = voltage; + + 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); + + DBG("[%s] e\n",__func__); + 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; + + DBG("[%s] s\n",__func__); + + 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; + } + + DBG("[%s] e\n",__func__); + return ocr; +} + +int mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage, bool cmd11) +{ + struct mmc_command cmd = {0}; + int err = 0; + + DBG("[%s] s\n",__func__); + + 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) { + DBG("[%s] e1\n",__func__); + return err; + } + + if (!mmc_host_is_spi(host) && (cmd.resp[0] & R1_ERROR)) { + DBG("[%s] e2\n",__func__); + return -EIO; + } + } + + host->ios.signal_voltage = signal_voltage; + + if (host->ops->start_signal_voltage_switch) { + mmc_host_clk_hold(host); + err = host->ops->start_signal_voltage_switch(host, &host->ios); + mmc_host_clk_release(host); + } + DBG("[%s] e3\n",__func__); + return err; +} + +/* + * Select timing parameters for host. + */ +void mmc_set_timing(struct mmc_host *host, unsigned int timing) +{ + DBG("[%s] s\n",__func__); + + mmc_host_clk_hold(host); + host->ios.timing = timing; + mmc_set_ios(host); + mmc_host_clk_release(host); + + DBG("[%s] e\n",__func__); +} + +/* + * Select appropriate driver type for host. + */ +void mmc_set_driver_type(struct mmc_host *host, unsigned int drv_type) +{ + DBG("[%s] s\n",__func__); + + mmc_host_clk_hold(host); + host->ios.drv_type = drv_type; + mmc_set_ios(host); + mmc_host_clk_release(host); + + DBG("[%s] e\n",__func__); +} + +static void mmc_poweroff_notify(struct mmc_host *host) +{ + struct mmc_card *card; + unsigned int timeout; + unsigned int notify_type = EXT_CSD_NO_POWER_NOTIFICATION; + int err = 0; + + DBG("[%s] s\n",__func__); + + card = host->card; + mmc_claim_host(host); + + /* + * Send power notify command only if card + * is mmc and notify state is powered ON + */ + if (card && mmc_card_mmc(card) && + (card->poweroff_notify_state == MMC_POWERED_ON)) { + + if (host->power_notify_type == MMC_HOST_PW_NOTIFY_SHORT) { + notify_type = EXT_CSD_POWER_OFF_SHORT; + timeout = card->ext_csd.generic_cmd6_time; + card->poweroff_notify_state = MMC_POWEROFF_SHORT; + } else { + notify_type = EXT_CSD_POWER_OFF_LONG; + timeout = card->ext_csd.power_off_longtime; + card->poweroff_notify_state = MMC_POWEROFF_LONG; + } + + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_POWER_OFF_NOTIFICATION, + notify_type, timeout); + + if (err && err != -EBADMSG) + pr_err("Device failed to respond within %d poweroff " + "time. Forcefully powering down the device\n", + timeout); + + /* Set the card state to no notification after the poweroff */ + card->poweroff_notify_state = MMC_NO_POWER_NOTIFICATION; + } + mmc_release_host(host); + + DBG("[%s] e\n",__func__); +} + +/* + * 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; + + DBG("[%s] s\n",__func__); + + 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; + else + host->ios.chip_select = MMC_CS_DONTCARE; + host->ios.bus_mode = MMC_BUSMODE_PUSHPULL; + 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); + + DBG("[%s] e\n",__func__); +} + +void mmc_power_off(struct mmc_host *host) +{ + int err = 0; + DBG("[%s] s\n",__func__); + mmc_host_clk_hold(host); + + host->ios.clock = 0; + host->ios.vdd = 0; + + /* + * For eMMC 4.5 device send AWAKE command before + * POWER_OFF_NOTIFY command, because in sleep state + * eMMC 4.5 devices respond to only RESET and AWAKE cmd + */ + if (host->card && mmc_card_is_sleep(host->card) && + host->bus_ops->resume) { + err = host->bus_ops->resume(host); + + if (!err) + mmc_poweroff_notify(host); + else + pr_warning("%s: error %d during resume " + "(continue with poweroff sequence)\n", + mmc_hostname(host), err); + } + + /* + * 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); + + /* + * Some configurations, such as the 802.11 SDIO card in the OLPC + * XO-1.5, require a short delay after poweroff before the card + * can be successfully turned on again. + */ + mmc_delay(1); + + mmc_host_clk_release(host); + + DBG("[%s] e\n",__func__); +} + +/* + * Cleanup when the last reference to the bus operator is dropped. + */ +static void __mmc_release_bus(struct mmc_host *host) +{ + DBG("[%s] s\n",__func__); + BUG_ON(!host); + BUG_ON(host->bus_refs); + BUG_ON(!host->bus_dead); + + host->bus_ops = NULL; + + DBG("[%s] e\n",__func__); +} + +/* + * Increase reference count of bus operator + */ +static inline void mmc_bus_get(struct mmc_host *host) +{ + unsigned long flags; + DBG("[%s] s\n",__func__); + + spin_lock_irqsave(&host->lock, flags); + host->bus_refs++; + spin_unlock_irqrestore(&host->lock, flags); + + DBG("[%s] e\n",__func__); +} + +/* + * 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; + DBG("[%s] s\n",__func__); + + 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); + + DBG("[%s] e\n",__func__); +} + +int mmc_resume_bus(struct mmc_host *host) +{ + unsigned long flags; + DBG("[%s] s\n",__func__); + + if (!mmc_bus_needs_resume(host)) { + DBG("[%s] e1\n",__func__); + 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)); + DBG("[%s] e2\n",__func__); + 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; + + DBG("[%s] s\n",__func__); + + 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); + + DBG("[%s] e\n",__func__); +} + +/* + * Remove the current bus handler from a host. + */ +void mmc_detach_bus(struct mmc_host *host) +{ + unsigned long flags; + + DBG("[%s] s\n",__func__); + + 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); + + DBG("[%s] e\n",__func__); +} + +/** + * 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) +{ + DBG("[%s] s\n",__func__); + +#ifdef CONFIG_MMC_DEBUG + unsigned long flags; + spin_lock_irqsave(&host->lock, flags); + WARN_ON(host->removed); + spin_unlock_irqrestore(&host->lock, flags); +#endif + host->detect_change = 1; + + wake_lock(&host->detect_wake_lock); + mmc_schedule_delayed_work(&host->detect, delay); + + DBG("[%s] e\n",__func__); +} + +EXPORT_SYMBOL(mmc_detect_change); + +void mmc_init_erase(struct mmc_card *card) +{ + unsigned int sz; + + DBG("[%s] s\n",__func__); + + 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; + } + } + + DBG("[%s] e\n",__func__); +} + +static unsigned int mmc_mmc_erase_timeout(struct mmc_card *card, + unsigned int arg, unsigned int qty) +{ + unsigned int erase_timeout; + + DBG("[%s] s\n",__func__); + + if (arg == MMC_DISCARD_ARG || + (arg == MMC_TRIM_ARG && card->ext_csd.rev >= 6)) { + erase_timeout = card->ext_csd.trim_timeout; + } else 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; + + DBG("[%s] e\n",__func__); + return erase_timeout; +} + +static unsigned int mmc_sd_erase_timeout(struct mmc_card *card, + unsigned int arg, + unsigned int qty) +{ + unsigned int erase_timeout; + + DBG("[%s] s\n",__func__); + + 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; + + DBG("[%s] e\n",__func__); + return erase_timeout; +} + +static unsigned int mmc_erase_timeout(struct mmc_card *card, + unsigned int arg, + unsigned int qty) +{ + unsigned int ret; + + DBG("[%s] s\n",__func__); + + if (mmc_card_sd(card)) { + ret = mmc_sd_erase_timeout(card, arg, qty); + /*return mmc_sd_erase_timeout(card, arg, qty);*/ + DBG("[%s] e1\n",__func__); + return ret; + } else { + ret = mmc_mmc_erase_timeout(card, arg, qty); + /*return mmc_mmc_erase_timeout(card, arg, qty);*/ + DBG("[%s] e2\n",__func__); + return ret; + } + +} + +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; + + DBG("[%s] s\n",__func__); + /* + * 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; + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + err = mmc_wait_for_cmd(card->host, &cmd, 0); + if (err) { + pr_err("mmc_erase: group start error %d, " + "status %#x\n", err, cmd.resp[0]); + err = -EIO; + 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; + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + err = mmc_wait_for_cmd(card->host, &cmd, 0); + if (err) { + pr_err("mmc_erase: group end error %d, status %#x\n", + err, cmd.resp[0]); + err = -EIO; + 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.flags = 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) { + pr_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)) { + pr_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]) == R1_STATE_PRG); +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; + int ret; + + DBG("[%s] s\n",__func__); + + if (!(card->host->caps & MMC_CAP_ERASE) || + !(card->csd.cmdclass & CCC_ERASE)) { + DBG("[%s] e1\n",__func__); + return -EOPNOTSUPP; + } + + if (!card->erase_size) { + DBG("[%s] e2\n",__func__); + return -EOPNOTSUPP; + } + + if (mmc_card_sd(card) && arg != MMC_ERASE_ARG) { + DBG("[%s] e3\n",__func__); + return -EOPNOTSUPP; + } + + if ((arg & MMC_SECURE_ARGS) && + !(card->ext_csd.sec_feature_support & EXT_CSD_SEC_ER_EN)) { + DBG("[%s] e4\n",__func__); + return -EOPNOTSUPP; + } + + if ((arg & MMC_TRIM_ARGS) && + !(card->ext_csd.sec_feature_support & EXT_CSD_SEC_GB_CL_EN)) { + DBG("[%s] e5\n",__func__); + return -EOPNOTSUPP; + } + + if (arg == MMC_SECURE_ERASE_ARG) { + if (from % card->erase_size || nr % card->erase_size) { + DBG("[%s] e6\n",__func__); + 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 { + DBG("[%s] e7\n",__func__); + return 0; + } + } + rem = nr % card->erase_size; + if (rem) + nr -= rem; + } + + if (nr == 0) { + DBG("[%s] e8\n",__func__); + return 0; + } + + to = from + nr; + + if (to <= from) { + DBG("[%s] e9\n",__func__); + return -EINVAL; + } + + /* 'from' and 'to' are inclusive */ + to -= 1; + + ret = mmc_do_erase(card, from, to, arg); + DBG("[%s] e10\n",__func__); + /*return mmc_do_erase(card, from, to, arg);*/ + return ret; +} +EXPORT_SYMBOL(mmc_erase); + +int mmc_can_erase(struct mmc_card *card) +{ + DBG("[%s] s\n",__func__); + + if ((card->host->caps & MMC_CAP_ERASE) && + (card->csd.cmdclass & CCC_ERASE) && card->erase_size) { + DBG("[%s] e1\n",__func__); + return 1; + } + DBG("[%s] e2\n",__func__); + return 0; +} +EXPORT_SYMBOL(mmc_can_erase); + +int mmc_can_trim(struct mmc_card *card) +{ + DBG("[%s] s\n",__func__); + + if (card->ext_csd.sec_feature_support & EXT_CSD_SEC_GB_CL_EN) { + DBG("[%s] e1\n",__func__); + return 1; + } + + DBG("[%s] e2\n",__func__); + return 0; +} +EXPORT_SYMBOL(mmc_can_trim); + +int mmc_can_discard(struct mmc_card *card) +{ + DBG("[%s] s\n",__func__); + /* + * As there's no way to detect the discard support bit at v4.5 + * use the s/w feature support filed. + */ + if (card->ext_csd.feature_support & MMC_DISCARD_FEATURE) { + DBG("[%s] e1\n",__func__); + return 1; + } + DBG("[%s] e2\n",__func__); + return 0; +} +EXPORT_SYMBOL(mmc_can_discard); + +int mmc_can_sanitize(struct mmc_card *card) +{ + DBG("[%s] s\n",__func__); + if (!mmc_can_trim(card) && !mmc_can_erase(card)) { + DBG("[%s] e1\n",__func__); + return 0; + } + if (card->ext_csd.sec_feature_support & EXT_CSD_SEC_SANITIZE) { + DBG("[%s] e2\n",__func__); + return 1; + } + DBG("[%s] e3\n",__func__); + return 0; +} +EXPORT_SYMBOL(mmc_can_sanitize); + +int mmc_can_secure_erase_trim(struct mmc_card *card) +{ + DBG("[%s] s\n",__func__); + + if (card->ext_csd.sec_feature_support & EXT_CSD_SEC_ER_EN) { + DBG("[%s] e1\n",__func__); + return 1; + } + DBG("[%s] e2\n",__func__); + return 0; +} +EXPORT_SYMBOL(mmc_can_secure_erase_trim); + +int mmc_erase_group_aligned(struct mmc_card *card, unsigned int from, + unsigned int nr) +{ + DBG("[%s] s\n",__func__); + if (!card->erase_size) { + DBG("[%s] e1\n",__func__); + return 0; + } + if (from % card->erase_size || nr % card->erase_size) { + DBG("[%s] e2\n",__func__); + return 0; + } + DBG("[%s] e3\n",__func__); + 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; + + DBG("[%s] s\n",__func__); + + 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) { + DBG("[%s] e1\n",__func__); + return 0; + } + + if (qty == 1) { + DBG("[%s] e2\n",__func__); + 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; + + DBG("[%s] e3\n",__func__); + 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; + DBG("[%s] s\n",__func__); + if (!host->max_discard_to) { + DBG("[%s] e1\n",__func__); + 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)) { + DBG("[%s] e2\n",__func__); + 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); + DBG("[%s] e3\n",__func__); + 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}; + int ret; + + DBG("[%s] s\n",__func__); + + if (mmc_card_blockaddr(card) || mmc_card_ddr_mode(card)) { + DBG("[%s] e1\n",__func__); + return 0; + } + + cmd.opcode = MMC_SET_BLOCKLEN; + cmd.arg = blocklen; + //cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC; + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + ret = mmc_wait_for_cmd(card->host, &cmd, 5); + + /*return mmc_wait_for_cmd(card->host, &cmd, 5);*/ + DBG("[%s] e1\n",__func__); + return ret; +} +EXPORT_SYMBOL(mmc_set_blocklen); + +static void mmc_hw_reset_for_init(struct mmc_host *host) +{ + DBG("[%s] s\n",__func__); + + if (!(host->caps & MMC_CAP_HW_RESET) || !host->ops->hw_reset) { + DBG("[%s] e1\n",__func__); + return; + } + mmc_host_clk_hold(host); + host->ops->hw_reset(host); + mmc_host_clk_release(host); + DBG("[%s] e2\n",__func__); +} + +int mmc_can_reset(struct mmc_card *card) +{ + u8 rst_n_function; + + DBG("[%s] s\n",__func__); + + if (!mmc_card_mmc(card)) { + DBG("[%s] e1\n",__func__); + return 0; + } + rst_n_function = card->ext_csd.rst_n_function; + if ((rst_n_function & EXT_CSD_RST_N_EN_MASK) != EXT_CSD_RST_N_ENABLED) { + DBG("[%s] e2\n",__func__); + return 0; + } + DBG("[%s] e3\n",__func__); + return 1; +} +EXPORT_SYMBOL(mmc_can_reset); + +static int mmc_do_hw_reset(struct mmc_host *host, int check) +{ + struct mmc_card *card = host->card; + DBG("[%s] s\n",__func__); + if (!host->bus_ops->power_restore) { + DBG("[%s] e1\n",__func__); + return -EOPNOTSUPP; + } + + if (!(host->caps & MMC_CAP_HW_RESET) || !host->ops->hw_reset) { + DBG("[%s] e2\n",__func__); + return -EOPNOTSUPP; + } + + if (!card) { + DBG("[%s] e3\n",__func__); + return -EINVAL; + } + + if (!mmc_can_reset(card)) { + DBG("[%s] e4\n",__func__); + return -EOPNOTSUPP; + } + + mmc_host_clk_hold(host); + mmc_set_clock(host, host->f_init); + + host->ops->hw_reset(host); + + /* If the reset has happened, then a status command will fail */ + if (check) { + 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; + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + err = mmc_wait_for_cmd(card->host, &cmd, 0); + if (!err) { + mmc_host_clk_release(host); + DBG("[%s] e5\n",__func__); + return -ENOSYS; + } + } + + host->card->state &= ~(MMC_STATE_HIGHSPEED | MMC_STATE_HIGHSPEED_DDR); + 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.bus_width = MMC_BUS_WIDTH_1; + host->ios.timing = MMC_TIMING_LEGACY; + mmc_set_ios(host); + + mmc_host_clk_release(host); + DBG("[%s] e6\n",__func__); + return host->bus_ops->power_restore(host); +} + +int mmc_hw_reset(struct mmc_host *host) +{ + DBG("[%s] s\n",__func__); + return mmc_do_hw_reset(host, 0); +} +EXPORT_SYMBOL(mmc_hw_reset); + +int mmc_hw_reset_check(struct mmc_host *host) +{ + DBG("[%s] s\n",__func__); + return mmc_do_hw_reset(host, 1); +} +EXPORT_SYMBOL(mmc_hw_reset_check); + +static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq) +{ + DBG("[%s] s\n",__func__); + + 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); + + /* + * Some eMMCs (with VCCQ always on) may not be reset after power up, so + * do a hardware reset if possible. + */ + mmc_hw_reset_for_init(host); + + /* Initialization should be done at 3.3 V I/O voltage. */ + mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_330, 0); + + /* + * 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)) { + DBG("[%s] e1\n",__func__); + return 0; + } + if (!mmc_attach_sd(host)) { + DBG("[%s] e2\n",__func__); + return 0; + } + if (!mmc_attach_mmc(host)) { + DBG("[%s] e3\n",__func__); + return 0; + } + + mmc_power_off(host); + DBG("[%s] e4\n",__func__); + return -EIO; +} + +int _mmc_detect_card_removed(struct mmc_host *host) +{ + int ret; + + DBG("[%s] s\n",__func__); + if ((host->caps & MMC_CAP_NONREMOVABLE) || !host->bus_ops->alive) { + DBG("[%s] e1\n",__func__); + return 0; + } + + if (!host->card || mmc_card_removed(host->card)) { + DBG("[%s] e2\n",__func__); + return 1; + } + + ret = host->bus_ops->alive(host); + if (ret) { + mmc_card_set_removed(host->card); + pr_debug("%s: card remove detected\n", mmc_hostname(host)); + } + DBG("[%s] e3\n",__func__); + return ret; +} + +int mmc_detect_card_removed(struct mmc_host *host) +{ + struct mmc_card *card = host->card; + int ret; + + DBG("[%s] s\n",__func__); + + WARN_ON(!host->claimed); + + if (!card) { + DBG("[%s] e1\n",__func__); + return 1; + } + ret = mmc_card_removed(card); + /* + * The card will be considered unchanged unless we have been asked to + * detect a change or host requires polling to provide card detection. + */ + if (!host->detect_change && !(host->caps & MMC_CAP_NEEDS_POLL) && + !(host->caps2 & MMC_CAP2_DETECT_ON_ERR)) { + DBG("[%s] e2\n",__func__); + return ret; + } + + host->detect_change = 0; + if (!ret) { + ret = _mmc_detect_card_removed(host); + if (ret && (host->caps2 & MMC_CAP2_DETECT_ON_ERR)) { + /* + * Schedule a detect work as soon as possible to let a + * rescan handle the card removal. + */ + cancel_delayed_work(&host->detect); + mmc_detect_change(host, 0); + } + } + DBG("[%s] e3\n",__func__); + return ret; +} +EXPORT_SYMBOL(mmc_detect_card_removed); + +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; + struct atsmb_host *atsmb_host = mmc_priv(host); + int retry = 5; + + DBG("[%s] s\n",__func__); + host->card_scan_status = false; + + if (host->rescan_disable) + return; + + while(retry > 0) { + retry--; + 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); + + host->detect_change = 0; + + /* 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); + } + + DBG("[%s]retry = %d slot = %x power = %x\n", + __func__,retry,host->ops->get_slot_status(host), + host->ios.power_mode); + + if((host->ops->get_slot_status(host) == 0) || + (host->ios.power_mode != MMC_POWER_OFF)) + break; + + msleep(1000); + + } + if (host->ios.power_mode != MMC_POWER_OFF) + host->card_scan_status = true; + printk("SD%d Host Clock %dHz\n",host->wmt_host_index, atsmb_host->current_clock); + + DBG("[%s] e\n",__func__); +} + +void mmc_start_host(struct mmc_host *host,bool detect) +{ + DBG("[%s] s\n",__func__); + + mmc_power_off(host); + if (detect == true) + mmc_detect_change(host, 0); + + DBG("[%s] e\n",__func__); +} + +void mmc_stop_host(struct mmc_host *host) +{ + DBG("[%s] s\n",__func__); +#ifdef CONFIG_MMC_DEBUG + unsigned long flags; + spin_lock_irqsave(&host->lock, flags); + host->removed = 1; + spin_unlock_irqrestore(&host->lock, flags); +#endif + + 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) { + /* Calling bus_ops->remove() with a claimed host can deadlock */ + 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); + DBG("[%s] e1\n",__func__); + return; + } + mmc_bus_put(host); + + BUG_ON(host->card); + + mmc_power_off(host); + + DBG("[%s] e2\n",__func__); +} + +int mmc_power_save_host(struct mmc_host *host) +{ + int ret = 0; + + DBG("[%s] s\n",__func__); + +#ifdef CONFIG_MMC_DEBUG + pr_info("%s: %s: powering down\n", mmc_hostname(host), __func__); +#endif + + mmc_bus_get(host); + + if (!host->bus_ops || host->bus_dead || !host->bus_ops->power_restore) { + mmc_bus_put(host); + DBG("[%s] e1\n",__func__); + return -EINVAL; + } + + if (host->bus_ops->power_save) + ret = host->bus_ops->power_save(host); + + mmc_bus_put(host); + + mmc_power_off(host); + + DBG("[%s] e2\n",__func__); + return ret; +} +EXPORT_SYMBOL(mmc_power_save_host); + +int mmc_power_restore_host(struct mmc_host *host) +{ + int ret; + + DBG("[%s] s\n",__func__); + +#ifdef CONFIG_MMC_DEBUG + pr_info("%s: %s: powering up\n", mmc_hostname(host), __func__); +#endif + + mmc_bus_get(host); + + if (!host->bus_ops || host->bus_dead || !host->bus_ops->power_restore) { + mmc_bus_put(host); + DBG("[%s] e1\n",__func__); + return -EINVAL; + } + + mmc_power_up(host); + ret = host->bus_ops->power_restore(host); + + mmc_bus_put(host); + DBG("[%s] e2\n",__func__); + return ret; +} +EXPORT_SYMBOL(mmc_power_restore_host); + +int mmc_card_awake(struct mmc_host *host) +{ + int err = -ENOSYS; + + DBG("[%s] s\n",__func__); + + if (host->caps2 & MMC_CAP2_NO_SLEEP_CMD) { + DBG("[%s] e1\n",__func__); + return 0; + } + + 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); + + DBG("[%s] e2\n",__func__); + return err; +} +EXPORT_SYMBOL(mmc_card_awake); + +int mmc_card_sleep(struct mmc_host *host) +{ + int err = -ENOSYS; + + DBG("[%s] s\n",__func__); + + if (host->caps2 & MMC_CAP2_NO_SLEEP_CMD) { + DBG("[%s] e1\n",__func__); + return 0; + } + + mmc_bus_get(host); + + if (host->bus_ops && !host->bus_dead && host->bus_ops->sleep) + err = host->bus_ops->sleep(host); + + mmc_bus_put(host); + + DBG("[%s] e2\n",__func__); + return err; +} +EXPORT_SYMBOL(mmc_card_sleep); + +int mmc_card_can_sleep(struct mmc_host *host) +{ + struct mmc_card *card = host->card; + + DBG("[%s] s\n",__func__); + if (card && mmc_card_mmc(card) && card->ext_csd.rev >= 3) { + DBG("[%s] e1\n",__func__); + return 1; + } + + DBG("[%s] e2\n",__func__); + return 0; +} +EXPORT_SYMBOL(mmc_card_can_sleep); + +/* + * Flush the cache to the non-volatile storage. + */ +int mmc_flush_cache(struct mmc_card *card) +{ + struct mmc_host *host = card->host; + int err = 0; + DBG("[%s] s\n",__func__); + + if (!(host->caps2 & MMC_CAP2_CACHE_CTRL)) { + DBG("[%s] e1\n",__func__); + return err; + } + + if (mmc_card_mmc(card) && + (card->ext_csd.cache_size > 0) && + (card->ext_csd.cache_ctrl & 1)) { + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_FLUSH_CACHE, 1, 0); + if (err) + pr_err("%s: cache flush error %d\n", + mmc_hostname(card->host), err); + } + DBG("[%s] e2\n",__func__); + return err; +} +EXPORT_SYMBOL(mmc_flush_cache); + +/* + * Turn the cache ON/OFF. + * Turning the cache OFF shall trigger flushing of the data + * to the non-volatile storage. + */ +int mmc_cache_ctrl(struct mmc_host *host, u8 enable) +{ + struct mmc_card *card = host->card; + unsigned int timeout; + int err = 0; + + DBG("[%s] s\n",__func__); + + if (!(host->caps2 & MMC_CAP2_CACHE_CTRL) || + mmc_card_is_removable(host)) { + DBG("[%s] e1\n",__func__); + return err; + } + + mmc_claim_host(host); + if (card && mmc_card_mmc(card) && + (card->ext_csd.cache_size > 0)) { + enable = !!enable; + + if (card->ext_csd.cache_ctrl ^ enable) { + timeout = enable ? card->ext_csd.generic_cmd6_time : 0; + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_CACHE_CTRL, enable, timeout); + if (err) + pr_err("%s: cache %s error %d\n", + mmc_hostname(card->host), + enable ? "on" : "off", + err); + else + card->ext_csd.cache_ctrl = enable; + } + } + mmc_release_host(host); + DBG("[%s] e2\n",__func__); + return err; +} +EXPORT_SYMBOL(mmc_cache_ctrl); + +#ifdef CONFIG_PM + +/** + * mmc_suspend_host - suspend a host + * @host: mmc host + */ +int mmc_suspend_host(struct mmc_host *host) +{ + int err = 0; + + DBG("[%s] s\n",__func__); + + if (mmc_bus_needs_resume(host)) { + DBG("[%s] e1\n",__func__); + return 0; + } + + if (cancel_delayed_work(&host->detect)) + wake_unlock(&host->detect_wake_lock); + mmc_flush_scheduled_work(); + + err = mmc_cache_ctrl(host, 0); + if (err) + goto out; + + 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. (Calling + * bus_ops->remove() with a claimed host can + * deadlock.) + */ + 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); + +out: + DBG("[%s] e2\n",__func__); + 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; + + DBG("[%s] s\n",__func__); + + mmc_bus_get(host); + if (mmc_bus_manual_resume(host)) { + host->bus_resume_flags |= MMC_BUSRESUME_NEEDS_RESUME; + mmc_bus_put(host); + DBG("[%s] e1\n",__func__); + 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); + } + } + //kevin add for suspend&resume + mmc_set_ios(host); + + BUG_ON(!host->bus_ops->resume); + err = host->bus_ops->resume(host); + if (err) { + pr_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); + DBG("[%s] e2\n",__func__); + 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; + + DBG("[%s] s\n",__func__); + + 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; + host->power_notify_type = MMC_HOST_PW_NOTIFY_SHORT; + 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; + + /* Calling bus_ops->remove() with a claimed host can deadlock */ + 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; + 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; + host->power_notify_type = MMC_HOST_PW_NOTIFY_LONG; + spin_unlock_irqrestore(&host->lock, flags); + if(!(host->bus_dead)) + mmc_detect_change(host, 0); + + } + DBG("[%s] e\n",__func__); + return 0; +} +#endif + +void mmc_force_remove_card(struct mmc_host *host) +{ + DBG("[%s] s\n",__func__); + 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_release_host(host); + + } + mmc_bus_put(host); + mmc_power_off(host); + DBG("[%s] e\n",__func__); + return ; +} +EXPORT_SYMBOL(mmc_force_remove_card); + + +#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) +{ + DBG("[%s] s\n",__func__); + 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; + DBG("[%s] e\n",__func__); +} + +EXPORT_SYMBOL(mmc_set_embedded_sdio_data); +#endif + +static int __init mmc_init(void) +{ + int ret; + + DBG("[%s] s\n",__func__); + + workqueue = alloc_ordered_workqueue("kmmcd", 0); + if (!workqueue) { + DBG("[%s] e1\n",__func__); + 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; + + DBG("[%s] e2\n",__func__); + return 0; + +unregister_host_class: + mmc_unregister_host_class(); +unregister_bus: + mmc_unregister_bus(); +destroy_workqueue: + destroy_workqueue(workqueue); + + DBG("[%s] e3\n",__func__); + return ret; +} + +static void __exit mmc_exit(void) +{ + DBG("[%s] s\n",__func__); + sdio_unregister_bus(); + mmc_unregister_host_class(); + mmc_unregister_bus(); + destroy_workqueue(workqueue); + DBG("[%s] e\n",__func__); +} + +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..1e26016a --- /dev/null +++ b/drivers/mmc/core/core.h @@ -0,0 +1,80 @@ +/* + * 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 *); + int (*alive)(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_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, bool detect); +void mmc_stop_host(struct mmc_host *host); + +int _mmc_detect_card_removed(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 bool 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..9ab5b17d --- /dev/null +++ b/drivers/mmc/core/debugfs.c @@ -0,0 +1,364 @@ +/* + * 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/moduleparam.h> +#include <linux/export.h> +#include <linux/debugfs.h> +#include <linux/fs.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/stat.h> +#include <linux/fault-inject.h> + +#include <linux/mmc/card.h> +#include <linux/mmc/host.h> + +#include "core.h" +#include "mmc_ops.h" + +#ifdef CONFIG_FAIL_MMC_REQUEST + +static DECLARE_FAULT_ATTR(fail_default_attr); +static char *fail_request; +module_param(fail_request, charp, 0); + +#endif /* CONFIG_FAIL_MMC_REQUEST */ + +/* 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); + if (host->actual_clock) + seq_printf(s, "actual clock:\t%u Hz\n", host->actual_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; + case MMC_TIMING_UHS_SDR50: + str = "sd uhs SDR50"; + break; + case MMC_TIMING_UHS_SDR104: + str = "sd uhs SDR104"; + break; + case MMC_TIMING_UHS_DDR50: + str = "sd uhs DDR50"; + break; + case MMC_TIMING_MMC_HS200: + str = "mmc high-speed SDR200"; + 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 +#ifdef CONFIG_FAIL_MMC_REQUEST + if (fail_request) + setup_fault_attr(&fail_default_attr, fail_request); + host->fail_mmc_request = fail_default_attr; + if (IS_ERR(fault_create_debugfs_attr("fail_mmc_request", + root, + &host->fail_mmc_request))) + 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..37384d1c --- /dev/null +++ b/drivers/mmc/core/host.c @@ -0,0 +1,498 @@ +/* + * 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/export.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) + + +#if 0 +#define DBG(x...) printk(KERN_ALERT x) +#else +#define DBG(x...) do { } while (0) +#endif + +static void mmc_host_classdev_release(struct device *dev) +{ + struct mmc_host *host = cls_dev_to_mmc_host(dev); + DBG("[%s] s\n",__func__); + kfree(host); + DBG("[%s] e\n",__func__); +} + +static struct class mmc_host_class = { + .name = "mmc_host", + .dev_release = mmc_host_classdev_release, +}; + +int mmc_register_host_class(void) +{ + int ret; + + DBG("[%s] s\n",__func__); + ret = class_register(&mmc_host_class); + DBG("[%s] e\n",__func__); + //return class_register(&mmc_host_class); + return ret; +} + +void mmc_unregister_host_class(void) +{ + DBG("[%s] s\n",__func__); + class_unregister(&mmc_host_class); + DBG("[%s] e\n",__func__); +} + +static DEFINE_IDR(mmc_host_idr); +static DEFINE_SPINLOCK(mmc_host_lock); + +#ifdef CONFIG_MMC_CLKGATE +static ssize_t clkgate_delay_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmc_host *host = cls_dev_to_mmc_host(dev); + DBG("[%s] s\n",__func__); + return snprintf(buf, PAGE_SIZE, "%lu\n", host->clkgate_delay); +} + +static ssize_t clkgate_delay_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct mmc_host *host = cls_dev_to_mmc_host(dev); + unsigned long flags, value; + + DBG("[%s] s\n",__func__); + + if (kstrtoul(buf, 0, &value)) { + DBG("[%s] e1\n",__func__); + return -EINVAL; + } + + spin_lock_irqsave(&host->clk_lock, flags); + host->clkgate_delay = value; + spin_unlock_irqrestore(&host->clk_lock, flags); + DBG("[%s] e2\n",__func__); + return count; +} + +/* + * 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; + + DBG("[%s] s\n",__func__); + + if (!freq) { + pr_debug("%s: frequency set to 0 in disable function, " + "this means the clock is already disabled.\n", + mmc_hostname(host)); + DBG("[%s] e1\n",__func__); + 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); + DBG("[%s] e2\n",__func__); + 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); + + DBG("[%s] e3\n",__func__); +} + +/* + * 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.work); + DBG("[%s] s\n",__func__); + mmc_host_clk_gate_delayed(host); + DBG("[%s] e\n",__func__); +} + +/** + * 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; + DBG("[%s] s\n",__func__); + + /* cancel any clock gating work scheduled by mmc_host_clk_release() */ + cancel_delayed_work_sync(&host->clk_gate_work); + 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); + DBG("[%s] e\n",__func__); +} + +/** + * 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) +{ + DBG("[%s] s\n",__func__); + /* If there is no card we may gate it */ + if (!card) { + DBG("[%s] e1\n",__func__); + 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. + */ + DBG("[%s] e2\n",__func__); + 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; + DBG("[%s] s\n",__func__); + + spin_lock_irqsave(&host->clk_lock, flags); + host->clk_requests--; + if (mmc_host_may_gate_card(host->card) && + !host->clk_requests) + queue_delayed_work(system_nrt_wq, &host->clk_gate_work, + msecs_to_jiffies(host->clkgate_delay)); + spin_unlock_irqrestore(&host->clk_lock, flags); + + DBG("[%s] e\n",__func__); +} + +/** + * 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; + DBG("[%s] s\n",__func__); + + 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); + + DBG("[%s] e\n",__func__); + 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) +{ + DBG("[%s] s\n",__func__); + host->clk_requests = 0; + /* Hold MCI clock for 8 cycles by default */ + host->clk_delay = 8; + /* + * Default clock gating delay is 0ms to avoid wasting power. + * This value can be tuned by writing into sysfs entry. + */ + host->clkgate_delay = 0; + host->clk_gated = false; + INIT_DELAYED_WORK(&host->clk_gate_work, mmc_host_clk_gate_work); + spin_lock_init(&host->clk_lock); + mutex_init(&host->clk_gate_mutex); + DBG("[%s] e\n",__func__); +} + +/** + * 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) +{ + DBG("[%s] s\n",__func__); + /* + * Wait for any outstanding gate and then make sure we're + * ungated before exiting. + */ + if (cancel_delayed_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); + DBG("[%s] e\n",__func__); +} + +static inline void mmc_host_clk_sysfs_init(struct mmc_host *host) +{ + host->clkgate_delay_attr.show = clkgate_delay_show; + host->clkgate_delay_attr.store = clkgate_delay_store; + sysfs_attr_init(&host->clkgate_delay_attr.attr); + host->clkgate_delay_attr.attr.name = "clkgate_delay"; + host->clkgate_delay_attr.attr.mode = S_IRUGO | S_IWUSR; + if (device_create_file(&host->class_dev, &host->clkgate_delay_attr)) + pr_err("%s: Failed to create clkgate_delay sysfs entry\n", + mmc_hostname(host)); +} +#else + +static inline void mmc_host_clk_init(struct mmc_host *host) +{ +} + +static inline void mmc_host_clk_exit(struct mmc_host *host) +{ +} + +static inline void mmc_host_clk_sysfs_init(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; + DBG("[%s] s\n",__func__); + + if (!idr_pre_get(&mmc_host_idr, GFP_KERNEL)) { + DBG("[%s] e1\n",__func__); + return NULL; + } + + host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL); + if (!host) { + DBG("[%s] e2\n",__func__); + 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); +#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; + + DBG("[%s] e3\n",__func__); + return host; + +free: + kfree(host); + DBG("[%s] e4\n",__func__); + 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, bool detect) +{ + int err; + DBG("[%s] s\n",__func__); + WARN_ON((host->caps & MMC_CAP_SDIO_IRQ) && + !host->ops->enable_sdio_irq); + + err = device_add(&host->class_dev); + if (err) { + DBG("[%s] e1\n",__func__); + return err; + } + + led_trigger_register_simple(dev_name(&host->class_dev), &host->led); + +#ifdef CONFIG_DEBUG_FS + mmc_add_host_debugfs(host); +#endif + mmc_host_clk_sysfs_init(host); + + mmc_start_host(host, detect); + if (!(host->pm_flags & MMC_PM_IGNORE_PM_NOTIFY)) + register_pm_notifier(&host->pm_notify); + + DBG("[%s] e2\n",__func__); + 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) +{ + DBG("[%s] s\n",__func__); + 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); + DBG("[%s] e\n",__func__); +} + +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) +{ + DBG("[%s] s\n",__func__); + 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); + DBG("[%s] e\n",__func__); +} + +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..f2ab9e57 --- /dev/null +++ b/drivers/mmc/core/host.h @@ -0,0 +1,19 @@ +/* + * 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); + +#endif + diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c new file mode 100644 index 00000000..0eb26c64 --- /dev/null +++ b/drivers/mmc/core/mmc.c @@ -0,0 +1,1553 @@ +/* + * 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/stat.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: + pr_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) { + pr_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) { + pr_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)) { + pr_err("%s: unable to read EXT_CSD " + "on a possible high capacity card. " + "Card will be ignored.\n", + mmc_hostname(card->host)); + } else { + pr_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, idx; + unsigned int part_size; + u8 hc_erase_grp_sz = 0, hc_wp_grp_sz = 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) { + pr_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 (card->ext_csd.rev > 6) { + pr_err("%s: unrecognised EXT_CSD revision %d\n", + mmc_hostname(card->host), card->ext_csd.rev); + err = -EINVAL; + goto out; + } + + 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.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_SDR_ALL: + case EXT_CSD_CARD_TYPE_SDR_ALL_DDR_1_8V: + case EXT_CSD_CARD_TYPE_SDR_ALL_DDR_1_2V: + case EXT_CSD_CARD_TYPE_SDR_ALL_DDR_52: + card->ext_csd.hs_max_dtr = 200000000; + card->ext_csd.card_type = EXT_CSD_CARD_TYPE_SDR_200; + break; + case EXT_CSD_CARD_TYPE_SDR_1_2V_ALL: + case EXT_CSD_CARD_TYPE_SDR_1_2V_DDR_1_8V: + case EXT_CSD_CARD_TYPE_SDR_1_2V_DDR_1_2V: + case EXT_CSD_CARD_TYPE_SDR_1_2V_DDR_52: + card->ext_csd.hs_max_dtr = 200000000; + card->ext_csd.card_type = EXT_CSD_CARD_TYPE_SDR_1_2V; + break; + case EXT_CSD_CARD_TYPE_SDR_1_8V_ALL: + case EXT_CSD_CARD_TYPE_SDR_1_8V_DDR_1_8V: + case EXT_CSD_CARD_TYPE_SDR_1_8V_DDR_1_2V: + case EXT_CSD_CARD_TYPE_SDR_1_8V_DDR_52: + card->ext_csd.hs_max_dtr = 200000000; + card->ext_csd.card_type = EXT_CSD_CARD_TYPE_SDR_1_8V; + break; + 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 */ + pr_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. + */ + if (ext_csd[EXT_CSD_BOOT_MULT] && mmc_boot_partition_access(card->host)) { + for (idx = 0; idx < MMC_NUM_BOOT_PARTITION; idx++) { + part_size = ext_csd[EXT_CSD_BOOT_MULT] << 17; + mmc_part_add(card, part_size, + EXT_CSD_PART_CONFIG_ACC_BOOT0 + idx, + "boot%d", idx, true, + MMC_BLK_DATA_AREA_BOOT); + } + } + } + + card->ext_csd.raw_hc_erase_gap_size = + ext_csd[EXT_CSD_HC_WP_GRP_SIZE]; + 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)) { + hc_erase_grp_sz = + ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE]; + 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; + } + + /* + * General purpose partition feature support -- + * If ext_csd has the size of general purpose partitions, + * set size, part_cfg, partition name in mmc_part. + */ + if (ext_csd[EXT_CSD_PARTITION_SUPPORT] & + EXT_CSD_PART_SUPPORT_PART_EN) { + if (card->ext_csd.enhanced_area_en != 1) { + hc_erase_grp_sz = + ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE]; + hc_wp_grp_sz = + ext_csd[EXT_CSD_HC_WP_GRP_SIZE]; + + card->ext_csd.enhanced_area_en = 1; + } + + for (idx = 0; idx < MMC_NUM_GP_PARTITION; idx++) { + if (!ext_csd[EXT_CSD_GP_SIZE_MULT + idx * 3] && + !ext_csd[EXT_CSD_GP_SIZE_MULT + idx * 3 + 1] && + !ext_csd[EXT_CSD_GP_SIZE_MULT + idx * 3 + 2]) + continue; + part_size = + (ext_csd[EXT_CSD_GP_SIZE_MULT + idx * 3 + 2] + << 16) + + (ext_csd[EXT_CSD_GP_SIZE_MULT + idx * 3 + 1] + << 8) + + ext_csd[EXT_CSD_GP_SIZE_MULT + idx * 3]; + part_size *= (size_t)(hc_erase_grp_sz * + hc_wp_grp_sz); + mmc_part_add(card, part_size << 19, + EXT_CSD_PART_CONFIG_ACC_GP0 + idx, + "gp%d", idx, false, + MMC_BLK_DATA_AREA_GP); + } + } + 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]; + + /* + * Note that the call to mmc_part_add above defaults to read + * only. If this default assumption is changed, the call must + * take into account the value of boot_locked below. + */ + card->ext_csd.boot_ro_lock = ext_csd[EXT_CSD_BOOT_WP]; + card->ext_csd.boot_ro_lockable = true; + } + + if (card->ext_csd.rev >= 5) { + /* check whether the eMMC card supports HPI */ + if (ext_csd[EXT_CSD_HPI_FEATURES] & 0x1) { + card->ext_csd.hpi = 1; + if (ext_csd[EXT_CSD_HPI_FEATURES] & 0x2) + card->ext_csd.hpi_cmd = MMC_STOP_TRANSMISSION; + else + card->ext_csd.hpi_cmd = MMC_SEND_STATUS; + /* + * Indicate the maximum timeout to close + * a command interrupted by HPI + */ + card->ext_csd.out_of_int_time = + ext_csd[EXT_CSD_OUT_OF_INTERRUPT_TIME] * 10; + } + + card->ext_csd.rel_param = ext_csd[EXT_CSD_WR_REL_PARAM]; + card->ext_csd.rst_n_function = ext_csd[EXT_CSD_RST_N_FUNCTION]; + } + + 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; + + /* eMMC v4.5 or later */ + if (card->ext_csd.rev >= 6) { + card->ext_csd.feature_support |= MMC_DISCARD_FEATURE; + + card->ext_csd.generic_cmd6_time = 10 * + ext_csd[EXT_CSD_GENERIC_CMD6_TIME]; + card->ext_csd.power_off_longtime = 10 * + ext_csd[EXT_CSD_POWER_OFF_LONG_TIME]; + + card->ext_csd.cache_size = + ext_csd[EXT_CSD_CACHE_SIZE + 0] << 0 | + ext_csd[EXT_CSD_CACHE_SIZE + 1] << 8 | + ext_csd[EXT_CSD_CACHE_SIZE + 2] << 16 | + ext_csd[EXT_CSD_CACHE_SIZE + 3] << 24; + + if (ext_csd[EXT_CSD_DATA_SECTOR_SIZE] == 1) + card->ext_csd.data_sector_size = 4096; + else + card->ext_csd.data_sector_size = 512; + + if ((ext_csd[EXT_CSD_DATA_TAG_SUPPORT] & 1) && + (ext_csd[EXT_CSD_TAG_UNIT_SIZE] <= 8)) { + card->ext_csd.data_tag_unit_size = + ((unsigned int) 1 << ext_csd[EXT_CSD_TAG_UNIT_SIZE]) * + (card->ext_csd.data_sector_size); + } else { + card->ext_csd.data_tag_unit_size = 0; + } + } + +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; +} + +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); +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_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, +}; + +/* + * Select the PowerClass for the current bus width + * If power class is defined for 4/8 bit bus in the + * extended CSD register, select it by executing the + * mmc_switch command. + */ +static int mmc_select_powerclass(struct mmc_card *card, + unsigned int bus_width, u8 *ext_csd) +{ + int err = 0; + unsigned int pwrclass_val; + unsigned int index = 0; + struct mmc_host *host; + + BUG_ON(!card); + + host = card->host; + BUG_ON(!host); + + if (ext_csd == NULL) + return 0; + + /* Power class selection is supported for versions >= 4.0 */ + if (card->csd.mmca_vsn < CSD_SPEC_VER_4) + return 0; + + /* Power class values are defined only for 4/8 bit bus */ + if (bus_width == EXT_CSD_BUS_WIDTH_1) + return 0; + + switch (1 << host->ios.vdd) { + case MMC_VDD_165_195: + if (host->ios.clock <= 26000000) + index = EXT_CSD_PWR_CL_26_195; + else if (host->ios.clock <= 52000000) + index = (bus_width <= EXT_CSD_BUS_WIDTH_8) ? + EXT_CSD_PWR_CL_52_195 : + EXT_CSD_PWR_CL_DDR_52_195; + else if (host->ios.clock <= 200000000) + index = EXT_CSD_PWR_CL_200_195; + break; + case MMC_VDD_27_28: + case MMC_VDD_28_29: + case MMC_VDD_29_30: + case MMC_VDD_30_31: + case MMC_VDD_31_32: + case MMC_VDD_32_33: + case MMC_VDD_33_34: + case MMC_VDD_34_35: + case MMC_VDD_35_36: + if (host->ios.clock <= 26000000) + index = EXT_CSD_PWR_CL_26_360; + else if (host->ios.clock <= 52000000) + index = (bus_width <= EXT_CSD_BUS_WIDTH_8) ? + EXT_CSD_PWR_CL_52_360 : + EXT_CSD_PWR_CL_DDR_52_360; + else if (host->ios.clock <= 200000000) + index = EXT_CSD_PWR_CL_200_360; + break; + default: + pr_warning("%s: Voltage range not supported " + "for power class.\n", mmc_hostname(host)); + return -EINVAL; + } + + pwrclass_val = ext_csd[index]; + + if (bus_width & (EXT_CSD_BUS_WIDTH_8 | EXT_CSD_DDR_BUS_WIDTH_8)) + pwrclass_val = (pwrclass_val & EXT_CSD_PWR_CL_8BIT_MASK) >> + EXT_CSD_PWR_CL_8BIT_SHIFT; + else + pwrclass_val = (pwrclass_val & EXT_CSD_PWR_CL_4BIT_MASK) >> + EXT_CSD_PWR_CL_4BIT_SHIFT; + + /* If the power class is different from the default value */ + if (pwrclass_val > 0) { + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_POWER_CLASS, + pwrclass_val, + card->ext_csd.generic_cmd6_time); + } + + return err; +} + +/* + * Selects the desired buswidth and switch to the HS200 mode + * if bus width set without error + */ +static int mmc_select_hs200(struct mmc_card *card) +{ + int idx, err = 0; + struct mmc_host *host; + static unsigned ext_csd_bits[] = { + EXT_CSD_BUS_WIDTH_4, + EXT_CSD_BUS_WIDTH_8, + }; + static unsigned bus_widths[] = { + MMC_BUS_WIDTH_4, + MMC_BUS_WIDTH_8, + }; + + BUG_ON(!card); + + host = card->host; + + if (card->ext_csd.card_type & EXT_CSD_CARD_TYPE_SDR_1_2V && + host->caps2 & MMC_CAP2_HS200_1_2V_SDR) + if (mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_120, 0)) + err = mmc_set_signal_voltage(host, + MMC_SIGNAL_VOLTAGE_180, 0); + + /* If fails try again during next card power cycle */ + if (err) + goto err; + + idx = (host->caps & MMC_CAP_8_BIT_DATA) ? 1 : 0; + + /* + * Unlike SD, MMC cards dont have a configuration register to notify + * supported bus width. So bus test command should be run to identify + * the supported bus width or compare the ext csd values of current + * bus width and ext csd values of 1 bit mode read earlier. + */ + for (; idx >= 0; idx--) { + + /* + * Host is capable of 8bit transfer, then switch + * the device to work in 8bit transfer mode. If the + * mmc switch command returns error then switch to + * 4bit transfer mode. On success set the corresponding + * bus width on the host. + */ + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_BUS_WIDTH, + ext_csd_bits[idx], + card->ext_csd.generic_cmd6_time); + if (err) + continue; + + mmc_set_bus_width(card->host, bus_widths[idx]); + + if (!(host->caps & MMC_CAP_BUS_WIDTH_TEST)) + err = mmc_compare_ext_csds(card, bus_widths[idx]); + else + err = mmc_bus_test(card, bus_widths[idx]); + if (!err) + break; + } + + /* switch to HS200 mode if bus width set successfully */ + if (!err) + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_HS_TIMING, 2, 0); +err: + return err; +} + +/* + * 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); + + /* Set correct bus mode for MMC before attempting init */ + if (!mmc_host_is_spi(host)) + mmc_set_bus_mode(host, MMC_BUSMODE_OPENDRAIN); + + /* Initialization should be done at 3.3 V I/O voltage. */ + mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_330, 0); + + /* + * 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 is needed for eMMC that are asleep + */ + 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 || + (card->ext_csd.rev >= 3 && (host->caps2 & MMC_CAP2_HC_ERASE_SZ))) { + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_ERASE_GROUP_DEF, 1, + card->ext_csd.generic_cmd6_time); + + 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; + } + + /* + * If the host supports the power_off_notify capability then + * set the notification byte in the ext_csd register of device + */ + if ((host->caps2 & MMC_CAP2_POWEROFF_NOTIFY) && + (card->ext_csd.rev >= 6)) { + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_POWER_OFF_NOTIFICATION, + EXT_CSD_POWER_ON, + card->ext_csd.generic_cmd6_time); + if (err && err != -EBADMSG) + goto free_card; + + /* + * The err can be -EBADMSG or 0, + * so check for success and update the flag + */ + if (!err) + card->poweroff_notify_state = MMC_POWERED_ON; + } + + /* + * Activate high speed (if supported) + */ + if (card->ext_csd.hs_max_dtr != 0) { + err = 0; + if (card->ext_csd.hs_max_dtr > 52000000 && + host->caps2 & MMC_CAP2_HS200) + err = mmc_select_hs200(card); + else if (host->caps & MMC_CAP_MMC_HIGHSPEED) + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_HS_TIMING, 1, + card->ext_csd.generic_cmd6_time); + + if (err && err != -EBADMSG) + goto free_card; + + if (err) { + pr_warning("%s: switch to highspeed failed\n", + mmc_hostname(card->host)); + err = 0; + } else { + if (card->ext_csd.hs_max_dtr > 52000000 && + host->caps2 & MMC_CAP2_HS200) { + mmc_card_set_hs200(card); + mmc_set_timing(card->host, + MMC_TIMING_MMC_HS200); + } 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) || mmc_card_hs200(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; + } + + /* + * Indicate HS200 SDR mode (if supported). + */ + if (mmc_card_hs200(card)) { + u32 ext_csd_bits; + u32 bus_width = card->host->ios.bus_width; + + /* + * For devices supporting HS200 mode, the bus width has + * to be set before executing the tuning function. If + * set before tuning, then device will respond with CRC + * errors for responses on CMD line. So for HS200 the + * sequence will be + * 1. set bus width 4bit / 8 bit (1 bit not supported) + * 2. switch to HS200 mode + * 3. set the clock to > 52Mhz <=200MHz and + * 4. execute tuning for HS200 + */ + if ((host->caps2 & MMC_CAP2_HS200) && + card->host->ops->execute_tuning) { + mmc_host_clk_hold(card->host); + err = card->host->ops->execute_tuning(card->host, + MMC_SEND_TUNING_BLOCK_HS200); + mmc_host_clk_release(card->host); + } + if (err) { + pr_warning("%s: tuning execution failed\n", + mmc_hostname(card->host)); + goto err; + } + + ext_csd_bits = (bus_width == MMC_BUS_WIDTH_8) ? + EXT_CSD_BUS_WIDTH_8 : EXT_CSD_BUS_WIDTH_4; + err = mmc_select_powerclass(card, ext_csd_bits, ext_csd); + if (err) + pr_warning("%s: power class selection to bus width %d" + " failed\n", mmc_hostname(card->host), + 1 << bus_width); + } + + /* + * Activate wide bus and DDR (if supported). + */ + if (!mmc_card_hs200(card) && + (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_select_powerclass(card, ext_csd_bits[idx][0], + ext_csd); + if (err) + pr_warning("%s: power class selection to " + "bus width %d failed\n", + mmc_hostname(card->host), + 1 << bus_width); + + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_BUS_WIDTH, + ext_csd_bits[idx][0], + card->ext_csd.generic_cmd6_time); + 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_select_powerclass(card, ext_csd_bits[idx][1], + ext_csd); + if (err) + pr_warning("%s: power class selection to " + "bus width %d ddr %d failed\n", + mmc_hostname(card->host), + 1 << bus_width, ddr); + + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_BUS_WIDTH, + ext_csd_bits[idx][1], + card->ext_csd.generic_cmd6_time); + } + if (err) { + pr_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); + } + } + + /* + * Enable HPI feature (if supported) + */ + if (card->ext_csd.hpi) { + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_HPI_MGMT, 1, + card->ext_csd.generic_cmd6_time); + if (err && err != -EBADMSG) + goto free_card; + if (err) { + pr_warning("%s: Enabling HPI failed\n", + mmc_hostname(card->host)); + err = 0; + } else + card->ext_csd.hpi_en = 1; + } + + /* + * If cache size is higher than 0, this indicates + * the existence of cache and it can be turned on. + */ + if ((host->caps2 & MMC_CAP2_CACHE_CTRL) && + card->ext_csd.cache_size > 0) { + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_CACHE_CTRL, 1, + card->ext_csd.generic_cmd6_time); + if (err && err != -EBADMSG) + goto free_card; + + /* + * Only if no error, cache is turned on successfully. + */ + if (err) { + pr_warning("%s: Cache is supported, " + "but failed to turn on (%d)\n", + mmc_hostname(card->host), err); + card->ext_csd.cache_ctrl = 0; + err = 0; + } else { + card->ext_csd.cache_ctrl = 1; + } + } + + 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 - card is alive. + */ +static int mmc_alive(struct mmc_host *host) +{ + return mmc_send_status(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_detect_card_removed(host); + + mmc_release_host(host); +#ifdef CONFIG_MMC_UNSAFE_RESUME + if (err || (host->card_attath_status == card_attach_status_change)) { + host->card_attath_status = card_attach_status_unchange; + mmc_remove(host); + + mmc_claim_host(host); + mmc_detach_bus(host); + mmc_power_off(host); + mmc_release_host(host); + } +#else + if (err) { + mmc_remove(host); + + mmc_claim_host(host); + mmc_detach_bus(host); + mmc_power_off(host); + mmc_release_host(host); + } +#endif +} + +/* + * Suspend callback from host. + */ +static int mmc_suspend(struct mmc_host *host) +{ + int err = 0; + + BUG_ON(!host); + BUG_ON(!host->card); + + mmc_claim_host(host); + if (mmc_card_can_sleep(host)) { + err = mmc_card_sleep(host); + if (!err) + mmc_card_set_sleep(host->card); + } else if (!mmc_host_is_spi(host)) + mmc_deselect_cards(host); + host->card->state &= ~(MMC_STATE_HIGHSPEED | MMC_STATE_HIGHSPEED_200); + mmc_release_host(host); + + return err; +} + +/* + * 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); + if (mmc_card_is_sleep(host->card)) { + err = mmc_card_awake(host); + mmc_card_clr_sleep(host->card); + } else + err = mmc_init_card(host, host->ocr, host->card); + mmc_release_host(host); + +#ifdef CONFIG_MMC_UNSAFE_RESUME + if (err) + host->card_attath_status = card_attach_status_change; +#endif + + return err; +} + +static int mmc_power_restore(struct mmc_host *host) +{ + int ret; + + host->card->state &= ~(MMC_STATE_HIGHSPEED | MMC_STATE_HIGHSPEED_200); + mmc_card_clr_sleep(host->card); + 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, + .alive = mmc_alive, +}; + +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, + .alive = mmc_alive, +}; + +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); + + /* Set correct bus mode for MMC before attempting attach */ + if (!mmc_host_is_spi(host)) + mmc_set_bus_mode(host, MMC_BUSMODE_OPENDRAIN); + + 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) { + pr_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); + + pr_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..cdd16e5e --- /dev/null +++ b/drivers/mmc/core/mmc_ops.c @@ -0,0 +1,591 @@ +/* + * 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/export.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; + cmd.flags = 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; + cmd.flags = 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 = {NULL}; + 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; + cmd.flags = 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.flags = 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; + + /* 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) == R1_STATE_PRG); + + if (mmc_host_is_spi(card->host)) { + if (status & R1_SPI_ILLEGAL_COMMAND) + return -EBADMSG; + } else { + if (status & 0xFDFFA000) + pr_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; + cmd.flags = 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 = {NULL}; + 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 { + pr_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; + cmd.flags = 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; +} + +int mmc_send_hpi_cmd(struct mmc_card *card, u32 *status) +{ + struct mmc_command cmd = {0}; + unsigned int opcode; + int err; + + if (!card->ext_csd.hpi) { + pr_warning("%s: Card didn't support HPI command\n", + mmc_hostname(card->host)); + return -EINVAL; + } + + opcode = card->ext_csd.hpi_cmd; + if (opcode == MMC_STOP_TRANSMISSION) + cmd.flags = MMC_RSP_R1B | MMC_CMD_AC; + else if (opcode == MMC_SEND_STATUS) + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + + cmd.opcode = opcode; + cmd.arg = card->rca << 16 | 1; + cmd.cmd_timeout_ms = card->ext_csd.out_of_int_time; + + err = mmc_wait_for_cmd(card->host, &cmd, 0); + if (err) { + pr_warn("%s: error %d interrupting operation. " + "HPI command response %#x\n", mmc_hostname(card->host), + err, cmd.resp[0]); + return err; + } + if (status) + *status = cmd.resp[0]; + + return 0; +} diff --git a/drivers/mmc/core/mmc_ops.h b/drivers/mmc/core/mmc_ops.h new file mode 100644 index 00000000..3dd8941c --- /dev/null +++ b/drivers/mmc/core/mmc_ops.h @@ -0,0 +1,32 @@ +/* + * 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); +int mmc_send_hpi_cmd(struct mmc_card *card, u32 *status); + +#endif + diff --git a/drivers/mmc/core/quirks.c b/drivers/mmc/core/quirks.c new file mode 100644 index 00000000..06ee1aea --- /dev/null +++ b/drivers/mmc/core/quirks.c @@ -0,0 +1,91 @@ +/* + * 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/export.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 + +#ifndef SDIO_VENDOR_ID_STE +#define SDIO_VENDOR_ID_STE 0x0020 +#endif + +#ifndef SDIO_DEVICE_ID_STE_CW1200 +#define SDIO_DEVICE_ID_STE_CW1200 0x2280 +#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), + + SDIO_FIXUP(SDIO_VENDOR_ID_STE, SDIO_DEVICE_ID_STE_CW1200, + add_quirk, MMC_QUIRK_BROKEN_BYTE_MODE_512), + + 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..5dbafd8e --- /dev/null +++ b/drivers/mmc/core/sd.c @@ -0,0 +1,1341 @@ +/* + * 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/stat.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: + pr_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) { + pr_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)) { + pr_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) { + pr_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 { + pr_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)) { + pr_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) { + pr_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; + + pr_warning("%s: problem reading Bus Speed modes.\n", + mmc_hostname(card->host)); + err = 0; + + goto out; + } + + if (status[13] & SD_MODE_HIGH_SPEED) + card->sw_caps.hs_max_dtr = HIGH_SPEED_MAX_DTR; + + 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; + + pr_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; + + pr_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) { + pr_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) { + pr_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 = SD_DRIVER_TYPE_B; + int card_drv_type = SD_DRIVER_TYPE_B; + int drive_strength; + int err; + + /* + * If the host doesn't support any of the Driver Types A,C or D, + * or there is no board specific handler then 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->ops->select_drive_strength) + return 0; + + if (card->host->caps & MMC_CAP_DRIVER_TYPE_A) + host_drv_type |= SD_DRIVER_TYPE_A; + + if (card->host->caps & MMC_CAP_DRIVER_TYPE_C) + host_drv_type |= SD_DRIVER_TYPE_C; + + if (card->host->caps & MMC_CAP_DRIVER_TYPE_D) + host_drv_type |= SD_DRIVER_TYPE_D; + + if (card->sw_caps.sd3_drv_type & SD_DRIVER_TYPE_A) + card_drv_type |= SD_DRIVER_TYPE_A; + + if (card->sw_caps.sd3_drv_type & SD_DRIVER_TYPE_C) + card_drv_type |= SD_DRIVER_TYPE_C; + + if (card->sw_caps.sd3_drv_type & SD_DRIVER_TYPE_D) + card_drv_type |= SD_DRIVER_TYPE_D; + + /* + * The drive strength that the hardware can support + * depends on the board design. Pass the appropriate + * information and let the hardware specific code + * return what is possible given the options + */ + mmc_host_clk_hold(card->host); + drive_strength = card->host->ops->select_drive_strength( + card->sw_caps.uhs_max_dtr, + host_drv_type, card_drv_type); + mmc_host_clk_release(card->host); + + err = mmc_sd_switch(card, 1, 2, drive_strength, status); + if (err) + return err; + + if ((status[15] & 0xF) != drive_strength) { + pr_warning("%s: Problem setting drive strength!\n", + mmc_hostname(card->host)); + return 0; + } + + mmc_set_driver_type(card->host, drive_strength); + + return 0; +} + +static void sd_update_bus_speed_mode(struct mmc_card *card) +{ + /* + * 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))) { + card->sd_bus_speed = 0; + return; + } + + if ((card->host->caps & MMC_CAP_UHS_SDR104) && + (card->sw_caps.sd3_bus_mode & SD_MODE_UHS_SDR104)) { + card->sd_bus_speed = UHS_SDR104_BUS_SPEED; + } else if ((card->host->caps & MMC_CAP_UHS_DDR50) && + (card->sw_caps.sd3_bus_mode & SD_MODE_UHS_DDR50)) { + card->sd_bus_speed = UHS_DDR50_BUS_SPEED; + } else if ((card->host->caps & (MMC_CAP_UHS_SDR104 | + MMC_CAP_UHS_SDR50)) && (card->sw_caps.sd3_bus_mode & + SD_MODE_UHS_SDR50)) { + card->sd_bus_speed = UHS_SDR50_BUS_SPEED; + } 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)) { + card->sd_bus_speed = UHS_SDR25_BUS_SPEED; + } 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)) { + card->sd_bus_speed = UHS_SDR12_BUS_SPEED; + } +} + +static int sd_set_bus_speed_mode(struct mmc_card *card, u8 *status) +{ + int err; + unsigned int timing = 0; + + switch (card->sd_bus_speed) { + case UHS_SDR104_BUS_SPEED: + timing = MMC_TIMING_UHS_SDR104; + card->sw_caps.uhs_max_dtr = UHS_SDR104_MAX_DTR; + break; + case UHS_DDR50_BUS_SPEED: + timing = MMC_TIMING_UHS_DDR50; + card->sw_caps.uhs_max_dtr = UHS_DDR50_MAX_DTR; + break; + case UHS_SDR50_BUS_SPEED: + timing = MMC_TIMING_UHS_SDR50; + card->sw_caps.uhs_max_dtr = UHS_SDR50_MAX_DTR; + break; + case UHS_SDR25_BUS_SPEED: + timing = MMC_TIMING_UHS_SDR25; + card->sw_caps.uhs_max_dtr = UHS_SDR25_MAX_DTR; + break; + case UHS_SDR12_BUS_SPEED: + timing = MMC_TIMING_UHS_SDR12; + card->sw_caps.uhs_max_dtr = UHS_SDR12_MAX_DTR; + break; + default: + return 0; + } + + err = mmc_sd_switch(card, 1, 0, card->sd_bus_speed, status); + if (err) + return err; + + if ((status[16] & 0xF) != card->sd_bus_speed) + pr_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) + pr_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) { + pr_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); + } + + /* + * Select the bus speed mode depending on host + * and card capability. + */ + sd_update_bus_speed_mode(card); + + /* 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) { + mmc_host_clk_hold(card->host); + err = card->host->ops->execute_tuning(card->host, + MMC_SEND_TUNING_BLOCK); + mmc_host_clk_release(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)) + 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) { + mmc_host_clk_hold(card->host); + ro = host->ops->get_ro(host); + mmc_host_clk_release(card->host); + } + + if (ro < 0) { + pr_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); + + /* The initialization should be done at 3.3 V I/O voltage. */ + mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_330, 0); + + 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; + } + + 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_card_set_uhs(card); + + /* + * Since initialization is now complete, enable preset + * value registers for UHS-I cards. + */ + if (host->ops->enable_preset_value) { + mmc_host_clk_hold(card->host); + host->ops->enable_preset_value(host, true); + mmc_host_clk_release(card->host); + } + } 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 - card is alive. + */ +static int mmc_sd_alive(struct mmc_host *host) +{ + return mmc_send_status(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_detect_card_removed(host); +#endif + mmc_release_host(host); + +#ifdef CONFIG_MMC_UNSAFE_RESUME + if (err || (host->card_attath_status == card_attach_status_change)) { + host->card_attath_status = card_attach_status_unchange; + mmc_sd_remove(host); + mmc_claim_host(host); + mmc_detach_bus(host); + mmc_power_off(host); + mmc_release_host(host); + } +#else + if (err) { + mmc_sd_remove(host); + + mmc_claim_host(host); + mmc_detach_bus(host); + mmc_power_off(host); + mmc_release_host(host); + } +#endif +} + +/* + * 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); + +#ifdef CONFIG_MMC_UNSAFE_RESUME + if (err) + host->card_attath_status = card_attach_status_change; +#endif + + 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, + .alive = mmc_sd_alive, +}; + +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, + .alive = mmc_sd_alive, +}; + +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); + + /* Disable preset value enable if already set since last time */ + if (host->ops->enable_preset_value) { + mmc_host_clk_hold(host); + host->ops->enable_preset_value(host, false); + mmc_host_clk_release(host); + } + + 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) { + pr_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)) { + pr_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); + + pr_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..e01d12a3 --- /dev/null +++ b/drivers/mmc/core/sd_ops.c @@ -0,0 +1,399 @@ +/* + * 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/export.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; + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + } else { + cmd.arg = 0; + //cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_BCR; + cmd.flags = 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 = {NULL}; + + 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; + cmd.flags = 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; + cmd.flags = 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 = {NULL}; + 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; + cmd.flags = 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 = {NULL}; + 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; + cmd.flags = 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 = {NULL}; + 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; + cmd.flags = 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..341b4c0f --- /dev/null +++ b/drivers/mmc/core/sdio.c @@ -0,0 +1,1260 @@ +/* + * 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/module.h> +#include <linux/pm_runtime.h> + +#include <linux/mmc/host.h> +#include <linux/mmc/card.h> +#include <linux/mmc/mmc.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, u32 ocr) +{ + int ret; + int cccr_vsn; + int uhs = ocr & R4_18V_PRESENT; + unsigned char data; + unsigned char speed; + + 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_3_00) { + pr_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, &speed); + if (ret) + goto out; + + card->scr.sda_spec3 = 0; + card->sw_caps.sd3_bus_mode = 0; + card->sw_caps.sd3_drv_type = 0; + if (cccr_vsn >= SDIO_CCCR_REV_3_00 && uhs) { + card->scr.sda_spec3 = 1; + ret = mmc_io_rw_direct(card, 0, 0, + SDIO_CCCR_UHS, 0, &data); + if (ret) + goto out; + + if (card->host->caps & + (MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | + MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104 | + MMC_CAP_UHS_DDR50)) { + if (data & SDIO_UHS_DDR50) + card->sw_caps.sd3_bus_mode + |= SD_MODE_UHS_DDR50; + + if (data & SDIO_UHS_SDR50) + card->sw_caps.sd3_bus_mode + |= SD_MODE_UHS_SDR50; + + if (data & SDIO_UHS_SDR104) + card->sw_caps.sd3_bus_mode + |= SD_MODE_UHS_SDR104; + } + + ret = mmc_io_rw_direct(card, 0, 0, + SDIO_CCCR_DRIVE_STRENGTH, 0, &data); + if (ret) + goto out; + + if (data & SDIO_DRIVE_SDTA) + card->sw_caps.sd3_drv_type |= SD_DRIVER_TYPE_A; + if (data & SDIO_DRIVE_SDTC) + card->sw_caps.sd3_drv_type |= SD_DRIVER_TYPE_C; + if (data & SDIO_DRIVE_SDTD) + card->sw_caps.sd3_drv_type |= SD_DRIVER_TYPE_D; + } + + /* if no uhs mode ensure we check for high speed */ + if (!card->sw_caps.sd3_bus_mode) { + if (speed & SDIO_SPEED_SHS) { + card->cccr.high_speed = 1; + card->sw_caps.hs_max_dtr = 50000000; + } else { + card->cccr.high_speed = 0; + card->sw_caps.hs_max_dtr = 25000000; + } + } + } + +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 50 MHz is + * mandatory. + */ + max_dtr = 50000000; + } 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; +} + +static unsigned char host_drive_to_sdio_drive(int host_strength) +{ + switch (host_strength) { + case MMC_SET_DRIVER_TYPE_A: + return SDIO_DTSx_SET_TYPE_A; + case MMC_SET_DRIVER_TYPE_B: + return SDIO_DTSx_SET_TYPE_B; + case MMC_SET_DRIVER_TYPE_C: + return SDIO_DTSx_SET_TYPE_C; + case MMC_SET_DRIVER_TYPE_D: + return SDIO_DTSx_SET_TYPE_D; + default: + return SDIO_DTSx_SET_TYPE_B; + } +} + +static void sdio_select_driver_type(struct mmc_card *card) +{ + int host_drv_type = SD_DRIVER_TYPE_B; + int card_drv_type = SD_DRIVER_TYPE_B; + int drive_strength; + unsigned char card_strength; + int err; + + /* + * If the host doesn't support any of the Driver Types A,C or D, + * or there is no board specific handler then 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; + + if (!card->host->ops->select_drive_strength) + return; + + if (card->host->caps & MMC_CAP_DRIVER_TYPE_A) + host_drv_type |= SD_DRIVER_TYPE_A; + + if (card->host->caps & MMC_CAP_DRIVER_TYPE_C) + host_drv_type |= SD_DRIVER_TYPE_C; + + if (card->host->caps & MMC_CAP_DRIVER_TYPE_D) + host_drv_type |= SD_DRIVER_TYPE_D; + + if (card->sw_caps.sd3_drv_type & SD_DRIVER_TYPE_A) + card_drv_type |= SD_DRIVER_TYPE_A; + + if (card->sw_caps.sd3_drv_type & SD_DRIVER_TYPE_C) + card_drv_type |= SD_DRIVER_TYPE_C; + + if (card->sw_caps.sd3_drv_type & SD_DRIVER_TYPE_D) + card_drv_type |= SD_DRIVER_TYPE_D; + + /* + * The drive strength that the hardware can support + * depends on the board design. Pass the appropriate + * information and let the hardware specific code + * return what is possible given the options + */ + drive_strength = card->host->ops->select_drive_strength( + card->sw_caps.uhs_max_dtr, + host_drv_type, card_drv_type); + + /* if error just use default for drive strength B */ + err = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_DRIVE_STRENGTH, 0, + &card_strength); + if (err) + return; + + card_strength &= ~(SDIO_DRIVE_DTSx_MASK<<SDIO_DRIVE_DTSx_SHIFT); + card_strength |= host_drive_to_sdio_drive(drive_strength); + + err = mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_DRIVE_STRENGTH, + card_strength, NULL); + + /* if error default to drive strength B */ + if (!err) + mmc_set_driver_type(card->host, drive_strength); +} + + +static int sdio_set_bus_speed_mode(struct mmc_card *card) +{ + unsigned int bus_speed, timing; + int err; + unsigned char speed; + + /* + * 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; + + bus_speed = SDIO_SPEED_SDR12; + timing = MMC_TIMING_UHS_SDR12; + if ((card->host->caps & MMC_CAP_UHS_SDR104) && + (card->sw_caps.sd3_bus_mode & SD_MODE_UHS_SDR104)) { + bus_speed = SDIO_SPEED_SDR104; + 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 = SDIO_SPEED_DDR50; + 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 = SDIO_SPEED_SDR50; + 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 = SDIO_SPEED_SDR25; + 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 = SDIO_SPEED_SDR12; + timing = MMC_TIMING_UHS_SDR12; + card->sw_caps.uhs_max_dtr = UHS_SDR12_MAX_DTR; + } + + err = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_SPEED, 0, &speed); + if (err) + return err; + + speed &= ~SDIO_SPEED_BSS_MASK; + speed |= bus_speed; + err = mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_SPEED, speed, NULL); + if (err) + return err; + + if (bus_speed) { + mmc_set_timing(card->host, timing); + mmc_set_clock(card->host, card->sw_caps.uhs_max_dtr); + } + + return 0; +} + +/* + * UHS-I specific initialization procedure + */ +static int mmc_sdio_init_uhs_card(struct mmc_card *card) +{ + int err; + + if (!card->scr.sda_spec3) + return 0; + + /* + * Switch to wider bus (if supported). + */ + if (card->host->caps & MMC_CAP_4_BIT_DATA) { + err = sdio_enable_4bit_bus(card); + if (err > 0) { + mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4); + err = 0; + } + } + + /* Set the driver strength for the card */ + sdio_select_driver_type(card); + + /* Set bus speed mode of the card */ + err = sdio_set_bus_speed_mode(card); + if (err) + goto out; + + /* Initialize and start re-tuning timer */ + if (!mmc_host_is_spi(card->host) && card->host->ops->execute_tuning) + err = card->host->ops->execute_tuning(card->host, + MMC_SEND_TUNING_BLOCK); + +out: + + return err; +} + +/* + * 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) { + /* The initialization should be done at 3.3 V I/O voltage. */ + mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_330, 0); + + 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); + + /* + * If the host and card support UHS-I mode request the card + * to switch to 1.8V signaling level. No 1.8v signalling if + * UHS mode is not enabled to maintain compatibilty and some + * systems that claim 1.8v signalling in fact do not support + * it. + */ + if ((ocr & R4_18V_PRESENT) && + (host->caps & + (MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | + MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104 | + MMC_CAP_UHS_DDR50))) { + err = mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_180, + true); + if (err) { + ocr &= ~R4_18V_PRESENT; + host->ocr &= ~R4_18V_PRESENT; + } + err = 0; + } else { + ocr &= ~R4_18V_PRESENT; + host->ocr &= ~R4_18V_PRESENT; + } + + /* + * 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; + } + + /* + * 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, ocr); + 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; + + /* Initialization sequence for UHS-I cards */ + /* Only if card supports 1.8v and UHS signaling */ + if ((ocr & R4_18V_PRESENT) && card->sw_caps.sd3_bus_mode) { + err = mmc_sdio_init_uhs_card(card); + if (err) + goto remove; + + /* Card is an ultra-high-speed card */ + mmc_card_set_uhs(card); + } else { + /* + * 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 - card is alive. + */ +static int mmc_sdio_alive(struct mmc_host *host) +{ + return mmc_select_card(host->card); +} + +/* + * 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_detect_card_removed(host); + + 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); + } + + return err; +} + +static int mmc_sdio_resume(struct mmc_host *host) +{ + int i, err = 0; + + BUG_ON(!host); + BUG_ON(!host->card); + + /* Basic card reinitialization. */ + mmc_claim_host(host); + + /* 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. + */ + + /* The initialization should be done at 3.3 V I/O voltage. */ + if (!mmc_card_keep_power(host)) + mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_330, 0); + + 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, + .alive = mmc_sdio_alive, +}; + + +/* + * 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) { + pr_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) { + if (err == -EAGAIN) { + /* + * Retry initialization with S18R set to 0. + */ + host->ocr &= ~R4_18V_PRESENT; + 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); + + pr_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_sdio_init_card(host, host->ocr, card, 0); + 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..56d020e2 --- /dev/null +++ b/drivers/mmc/core/sdio_bus.c @@ -0,0 +1,337 @@ +/* + * 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/export.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) + pm_runtime_get_sync(dev); + + drv->remove(func); + + if (func->irq_handler) { + pr_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); + + return ret; +} + +#ifdef CONFIG_PM + +static int pm_no_operation(struct device *dev) +{ + return 0; +} + +static const struct dev_pm_ops sdio_bus_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_no_operation, pm_no_operation) + 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 */ + +#define SDIO_PM_OPS_PTR NULL + +#endif /* !CONFIG_PM */ + +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..f1c7ed8f --- /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) { + pr_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 */ + pr_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..01fa6e86 --- /dev/null +++ b/drivers/mmc/core/sdio_io.c @@ -0,0 +1,759 @@ +/* + * 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/export.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" + +/** + * 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, ®); + 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, ®); + 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, ®); + 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); + + if (mmc_card_broken_byte_mode_512(func->card)) + return min(mval, 511u); + + 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)); + + /* Indicate byte mode by setting "blocks" = 0 */ + ret = mmc_io_rw_extended(func->card, write, func->num, addr, + incr_addr, buf, 0, 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_readb_ext - read a single byte from a SDIO function + * @func: SDIO function to access + * @addr: address to read + * @err_ret: optional status value from transfer + * @in: value to add to argument + * + * 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. + */ +unsigned char sdio_readb_ext(struct sdio_func *func, unsigned int addr, + int *err_ret, unsigned in) +{ + int ret; + unsigned char val; + + BUG_ON(!func); + + if (err_ret) + *err_ret = 0; + + ret = mmc_io_rw_direct(func->card, 0, func->num, addr, (u8)in, &val); + if (ret) { + if (err_ret) + *err_ret = ret; + return 0xFF; + } + + return val; +} +EXPORT_SYMBOL_GPL(sdio_readb_ext); + +/** + * 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..3d8ceb40 --- /dev/null +++ b/drivers/mmc/core/sdio_irq.c @@ -0,0 +1,314 @@ +/* + * 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/export.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) { + pr_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) { + pr_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 { + pr_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, ¶m); + + /* + * 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) { + mmc_host_clk_hold(host); + host->ops->enable_sdio_irq(host, 1); + mmc_host_clk_release(host); + } + if (!kthread_should_stop()) + schedule_timeout(period); + set_current_state(TASK_RUNNING); + } while (!kthread_should_stop()); + + if (host->caps & MMC_CAP_SDIO_IRQ) { + mmc_host_clk_hold(host); + host->ops->enable_sdio_irq(host, 0); + mmc_host_clk_release(host); + } + + 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, ®); + 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, ®); + 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..d29e2063 --- /dev/null +++ b/drivers/mmc/core/sdio_ops.c @@ -0,0 +1,199 @@ +/* + * 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 = {NULL}; + struct mmc_command cmd = {0}; + struct mmc_data data = {0}; + struct scatterlist sg; + + BUG_ON(!card); + BUG_ON(fn > 7); + 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 == 0) + 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; + /* Code in host drivers/fwk assumes that "blocks" always is >=1 */ + data.blocks = blocks ? blocks : 1; + data.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ; + data.sg = &sg; + data.sg_len = 1; + + sg_init_one(&sg, buf, data.blksz * data.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..e7b73ea5 --- /dev/null +++ b/drivers/mmc/host/Kconfig @@ -0,0 +1,646 @@ +# +# 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_PLTFM + tristate "SDHCI platform and OF driver helper" + depends on MMC_SDHCI + help + This selects the common helper functions support for Secure Digital + Host Controller Interface based platform and OF drivers. + + If you have a controller with this interface, say Y or M here. + + If unsure, say N. + +config MMC_SDHCI_OF_ESDHC + tristate "SDHCI OF support for the Freescale eSDHC controller" + depends on MMC_SDHCI_PLTFM + depends on PPC_OF + select MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER + help + This selects the Freescale eSDHC controller support. + + If you have a controller with this interface, say Y or M here. + + If unsure, say N. + +config MMC_SDHCI_OF_HLWD + tristate "SDHCI OF support for the Nintendo Wii SDHCI controllers" + depends on MMC_SDHCI_PLTFM + 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 you have a controller with this interface, say Y or M here. + + If unsure, say N. + +config MMC_SDHCI_CNS3XXX + tristate "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 you have a controller with this interface, say Y or M here. + + If unsure, say N. + +config MMC_SDHCI_ESDHC_IMX + tristate "SDHCI support for the Freescale eSDHC/uSDHC i.MX controller" + depends on ARCH_MXC + depends on MMC_SDHCI_PLTFM + select MMC_SDHCI_IO_ACCESSORS + help + This selects the Freescale eSDHC/uSDHC controller support + found on i.MX25, i.MX35 i.MX5x and i.MX6x. + + If you have a controller with this interface, say Y or M here. + + If unsure, say N. + +config MMC_SDHCI_DOVE + tristate "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 you have a controller with this interface, say Y or M here. + + If unsure, say N. + +config MMC_SDHCI_TEGRA + tristate "SDHCI platform support for the Tegra SD/MMC Controller" + depends on ARCH_TEGRA + depends on MMC_SDHCI_PLTFM + 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_PXAV3 + tristate "Marvell MMP2 SD Host Controller support (PXAV3)" + depends on CLKDEV_LOOKUP + select MMC_SDHCI + select MMC_SDHCI_PLTFM + default CPU_MMP2 + help + This selects the Marvell(R) PXAV3 SD Host Controller. + If you have a MMP2 platform with SD Host Controller + and a card slot, say Y or M here. + + If unsure, say N. + +config MMC_SDHCI_PXAV2 + tristate "Marvell PXA9XX SD Host Controller support (PXAV2)" + depends on CLKDEV_LOOKUP + select MMC_SDHCI + select MMC_SDHCI_PLTFM + default CPU_PXA910 + help + This selects the Marvell(R) PXAV2 SD Host Controller. + If you have a PXA9XX 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 MIPS_ALCHEMY + 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" + depends on MMC_ATMELMCI && (AVR32 || ARCH_AT91SAM9G45) && DMA_ENGINE + 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. + + 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.MX21/27/31 Multimedia Card Interface support" + depends on ARCH_MXC + help + This selects the Freescale i.MX21, i.MX27 and i.MX31 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_S3C24XX + 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 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_DW_PLTFM + tristate "Synopsys Designware MCI Support as platform device" + depends on MMC_DW + default y + help + This selects the common helper functions support for Host Controller + Interface based platform driver. Please select this option if the IP + is present as a platform device. This is the common interface for the + Synopsys Designware IP. + + If you have a controller with this interface, say Y or M here. + + If unsure, say Y. + +config MMC_DW_PCI + tristate "Synopsys Designware MCI support on PCI bus" + depends on MMC_DW && PCI + help + This selects the PCI bus for the Synopsys Designware Mobile Storage IP. + Select this option if the IP is present on PCI platform. + + If you have a controller with this interface, say Y or M here. + + If unsure, say N. + +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 MMC_ATSMB + tristate "WonderMedia ATSMB(AHB To MMC/SD BUS)" + help + This selects the WMT Secure digital and Multimedia card Interface. + If you have a machine with WonderMedia ATSMB, say Y or M here. + + If unsure, say N. + +config MMC_ATSMB1 + tristate "WonderMedia ATSMB1(AHB To MMC/SD1 BUS)" + help + This selects the WMT Secure digital and Multimedia card Interface. + If you have a machine with WonderMedia ATSMB1, say Y or M here. + + If unsure, say N. + +config MMC_ATSMB2 + tristate "WonderMedia ATSMB2(AHB To MMC/SD2 BUS)" + help + This selects the WMT Secure digital and Multimedia card Interface. + If you have a machine with WonderMedia ATSMB2, say Y or M here. + + If unsure, say N. + diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile new file mode 100644 index 00000000..b46a087b --- /dev/null +++ b/drivers/mmc/host/Makefile @@ -0,0 +1,63 @@ +# +# 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-$(subst m,y,$(CONFIG_MMC_SDHCI_PCI)) += sdhci-pci-data.o +obj-$(CONFIG_MMC_SDHCI_PXAV3) += sdhci-pxav3.o +obj-$(CONFIG_MMC_SDHCI_PXAV2) += sdhci-pxav2.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 +tmio_mmc_core-$(subst m,y,$(CONFIG_MMC_SDHI)) += tmio_mmc_dma.o +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_DW_PLTFM) += dw_mmc-pltfm.o +obj-$(CONFIG_MMC_DW_PCI) += dw_mmc-pci.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-pltfm.o +obj-$(CONFIG_MMC_SDHCI_CNS3XXX) += sdhci-cns3xxx.o +obj-$(CONFIG_MMC_SDHCI_ESDHC_IMX) += sdhci-esdhc-imx.o +obj-$(CONFIG_MMC_SDHCI_DOVE) += sdhci-dove.o +obj-$(CONFIG_MMC_SDHCI_TEGRA) += sdhci-tegra.o +obj-$(CONFIG_MMC_SDHCI_OF_ESDHC) += sdhci-of-esdhc.o +obj-$(CONFIG_MMC_SDHCI_OF_HLWD) += sdhci-of-hlwd.o + +ifeq ($(CONFIG_CB710_DEBUG),y) + CFLAGS-cb710-mmc += -DDEBUG +endif + +obj-$(CONFIG_MMC_ATSMB) += mmc_atsmb.o +obj-$(CONFIG_MMC_ATSMB1) += mmc_atsmb1.o +obj-$(CONFIG_MMC_ATSMB2) += mmc_atsmb2.o diff --git a/drivers/mmc/host/at91_mci.c b/drivers/mmc/host/at91_mci.c new file mode 100644 index 00000000..efdb81d2 --- /dev/null +++ b/drivers/mmc/host/at91_mci.c @@ -0,0 +1,1219 @@ +/* + * 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 "at91_mci.h" + +#define DRIVER_NAME "at91_mci" + +static inline int at91mci_is_mci1rev2xx(void) +{ + return ( cpu_is_at91sam9260() + || cpu_is_at91sam9263() + || 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)) + 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); + + 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)) + 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); + 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 (gpio_is_valid(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; + + /* entering this ISR means that we have configured det_pin: + * we can use its value in board structure */ + present = !gpio_get_value(host->board->det_pin); + + /* + * 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 (gpio_is_valid(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 (gpio_is_valid(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 (gpio_is_valid(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 (gpio_is_valid(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 (gpio_is_valid(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 (gpio_is_valid(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 (gpio_is_valid(host->board->vcc_pin)) + gpio_free(host->board->vcc_pin); +fail3: + if (gpio_is_valid(host->board->wp_pin)) + gpio_free(host->board->wp_pin); +fail4: + if (gpio_is_valid(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 (gpio_is_valid(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 (gpio_is_valid(host->board->vcc_pin)) + gpio_free(host->board->vcc_pin); + if (gpio_is_valid(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 (gpio_is_valid(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 (gpio_is_valid(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/at91_mci.h b/drivers/mmc/host/at91_mci.h new file mode 100644 index 00000000..eec3a6b1 --- /dev/null +++ b/drivers/mmc/host/at91_mci.h @@ -0,0 +1,115 @@ +/* + * drivers/mmc/host/at91_mci.h + * + * Copyright (C) 2005 Ivan Kokshaysky + * Copyright (C) SAN People + * + * MultiMedia Card Interface (MCI) registers. + * Based on AT91RM9200 datasheet revision F. + * + * 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 AT91_MCI_H +#define AT91_MCI_H + +#define AT91_MCI_CR 0x00 /* Control Register */ +#define AT91_MCI_MCIEN (1 << 0) /* Multi-Media Interface Enable */ +#define AT91_MCI_MCIDIS (1 << 1) /* Multi-Media Interface Disable */ +#define AT91_MCI_PWSEN (1 << 2) /* Power Save Mode Enable */ +#define AT91_MCI_PWSDIS (1 << 3) /* Power Save Mode Disable */ +#define AT91_MCI_SWRST (1 << 7) /* Software Reset */ + +#define AT91_MCI_MR 0x04 /* Mode Register */ +#define AT91_MCI_CLKDIV (0xff << 0) /* Clock Divider */ +#define AT91_MCI_PWSDIV (7 << 8) /* Power Saving Divider */ +#define AT91_MCI_RDPROOF (1 << 11) /* Read Proof Enable [SAM926[03] only] */ +#define AT91_MCI_WRPROOF (1 << 12) /* Write Proof Enable [SAM926[03] only] */ +#define AT91_MCI_PDCFBYTE (1 << 13) /* PDC Force Byte Transfer [SAM926[03] only] */ +#define AT91_MCI_PDCPADV (1 << 14) /* PDC Padding Value */ +#define AT91_MCI_PDCMODE (1 << 15) /* PDC-orientated Mode */ +#define AT91_MCI_BLKLEN (0xfff << 18) /* Data Block Length */ + +#define AT91_MCI_DTOR 0x08 /* Data Timeout Register */ +#define AT91_MCI_DTOCYC (0xf << 0) /* Data Timeout Cycle Number */ +#define AT91_MCI_DTOMUL (7 << 4) /* Data Timeout Multiplier */ +#define AT91_MCI_DTOMUL_1 (0 << 4) +#define AT91_MCI_DTOMUL_16 (1 << 4) +#define AT91_MCI_DTOMUL_128 (2 << 4) +#define AT91_MCI_DTOMUL_256 (3 << 4) +#define AT91_MCI_DTOMUL_1K (4 << 4) +#define AT91_MCI_DTOMUL_4K (5 << 4) +#define AT91_MCI_DTOMUL_64K (6 << 4) +#define AT91_MCI_DTOMUL_1M (7 << 4) + +#define AT91_MCI_SDCR 0x0c /* SD Card Register */ +#define AT91_MCI_SDCSEL (3 << 0) /* SD Card Selector */ +#define AT91_MCI_SDCBUS (1 << 7) /* 1-bit or 4-bit bus */ + +#define AT91_MCI_ARGR 0x10 /* Argument Register */ + +#define AT91_MCI_CMDR 0x14 /* Command Register */ +#define AT91_MCI_CMDNB (0x3f << 0) /* Command Number */ +#define AT91_MCI_RSPTYP (3 << 6) /* Response Type */ +#define AT91_MCI_RSPTYP_NONE (0 << 6) +#define AT91_MCI_RSPTYP_48 (1 << 6) +#define AT91_MCI_RSPTYP_136 (2 << 6) +#define AT91_MCI_SPCMD (7 << 8) /* Special Command */ +#define AT91_MCI_SPCMD_NONE (0 << 8) +#define AT91_MCI_SPCMD_INIT (1 << 8) +#define AT91_MCI_SPCMD_SYNC (2 << 8) +#define AT91_MCI_SPCMD_ICMD (4 << 8) +#define AT91_MCI_SPCMD_IRESP (5 << 8) +#define AT91_MCI_OPDCMD (1 << 11) /* Open Drain Command */ +#define AT91_MCI_MAXLAT (1 << 12) /* Max Latency for Command to Response */ +#define AT91_MCI_TRCMD (3 << 16) /* Transfer Command */ +#define AT91_MCI_TRCMD_NONE (0 << 16) +#define AT91_MCI_TRCMD_START (1 << 16) +#define AT91_MCI_TRCMD_STOP (2 << 16) +#define AT91_MCI_TRDIR (1 << 18) /* Transfer Direction */ +#define AT91_MCI_TRTYP (3 << 19) /* Transfer Type */ +#define AT91_MCI_TRTYP_BLOCK (0 << 19) +#define AT91_MCI_TRTYP_MULTIPLE (1 << 19) +#define AT91_MCI_TRTYP_STREAM (2 << 19) +#define AT91_MCI_TRTYP_SDIO_BYTE (4 << 19) +#define AT91_MCI_TRTYP_SDIO_BLOCK (5 << 19) + +#define AT91_MCI_BLKR 0x18 /* Block Register */ +#define AT91_MCI_BLKR_BCNT(n) ((0xffff & (n)) << 0) /* Block count */ +#define AT91_MCI_BLKR_BLKLEN(n) ((0xffff & (n)) << 16) /* Block length */ + +#define AT91_MCI_RSPR(n) (0x20 + ((n) * 4)) /* Response Registers 0-3 */ +#define AT91_MCR_RDR 0x30 /* Receive Data Register */ +#define AT91_MCR_TDR 0x34 /* Transmit Data Register */ + +#define AT91_MCI_SR 0x40 /* Status Register */ +#define AT91_MCI_CMDRDY (1 << 0) /* Command Ready */ +#define AT91_MCI_RXRDY (1 << 1) /* Receiver Ready */ +#define AT91_MCI_TXRDY (1 << 2) /* Transmit Ready */ +#define AT91_MCI_BLKE (1 << 3) /* Data Block Ended */ +#define AT91_MCI_DTIP (1 << 4) /* Data Transfer in Progress */ +#define AT91_MCI_NOTBUSY (1 << 5) /* Data Not Busy */ +#define AT91_MCI_ENDRX (1 << 6) /* End of RX Buffer */ +#define AT91_MCI_ENDTX (1 << 7) /* End fo TX Buffer */ +#define AT91_MCI_SDIOIRQA (1 << 8) /* SDIO Interrupt for Slot A */ +#define AT91_MCI_SDIOIRQB (1 << 9) /* SDIO Interrupt for Slot B */ +#define AT91_MCI_RXBUFF (1 << 14) /* RX Buffer Full */ +#define AT91_MCI_TXBUFE (1 << 15) /* TX Buffer Empty */ +#define AT91_MCI_RINDE (1 << 16) /* Response Index Error */ +#define AT91_MCI_RDIRE (1 << 17) /* Response Direction Error */ +#define AT91_MCI_RCRCE (1 << 18) /* Response CRC Error */ +#define AT91_MCI_RENDE (1 << 19) /* Response End Bit Error */ +#define AT91_MCI_RTOE (1 << 20) /* Response Time-out Error */ +#define AT91_MCI_DCRCE (1 << 21) /* Data CRC Error */ +#define AT91_MCI_DTOE (1 << 22) /* Data Time-out Error */ +#define AT91_MCI_OVRE (1 << 30) /* Overrun */ +#define AT91_MCI_UNRE (1 << 31) /* Underrun */ + +#define AT91_MCI_IER 0x44 /* Interrupt Enable Register */ +#define AT91_MCI_IDR 0x48 /* Interrupt Disable Register */ +#define AT91_MCI_IMR 0x4c /* Interrupt Mask Register */ + +#endif diff --git a/drivers/mmc/host/atmel-mci-regs.h b/drivers/mmc/host/atmel-mci-regs.h new file mode 100644 index 00000000..787aba16 --- /dev/null +++ b/drivers/mmc/host/atmel-mci-regs.h @@ -0,0 +1,143 @@ +/* + * 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 ATMCI_CR 0x0000 /* Control */ +# define ATMCI_CR_MCIEN ( 1 << 0) /* MCI Enable */ +# define ATMCI_CR_MCIDIS ( 1 << 1) /* MCI Disable */ +# define ATMCI_CR_PWSEN ( 1 << 2) /* Power Save Enable */ +# define ATMCI_CR_PWSDIS ( 1 << 3) /* Power Save Disable */ +# define ATMCI_CR_SWRST ( 1 << 7) /* Software Reset */ +#define ATMCI_MR 0x0004 /* Mode */ +# define ATMCI_MR_CLKDIV(x) ((x) << 0) /* Clock Divider */ +# define ATMCI_MR_PWSDIV(x) ((x) << 8) /* Power Saving Divider */ +# define ATMCI_MR_RDPROOF ( 1 << 11) /* Read Proof */ +# define ATMCI_MR_WRPROOF ( 1 << 12) /* Write Proof */ +# define ATMCI_MR_PDCFBYTE ( 1 << 13) /* Force Byte Transfer */ +# define ATMCI_MR_PDCPADV ( 1 << 14) /* Padding Value */ +# define ATMCI_MR_PDCMODE ( 1 << 15) /* PDC-oriented Mode */ +# define ATMCI_MR_CLKODD(x) ((x) << 16) /* LSB of Clock Divider */ +#define ATMCI_DTOR 0x0008 /* Data Timeout */ +# define ATMCI_DTOCYC(x) ((x) << 0) /* Data Timeout Cycles */ +# define ATMCI_DTOMUL(x) ((x) << 4) /* Data Timeout Multiplier */ +#define ATMCI_SDCR 0x000c /* SD Card / SDIO */ +# define ATMCI_SDCSEL_SLOT_A ( 0 << 0) /* Select SD slot A */ +# define ATMCI_SDCSEL_SLOT_B ( 1 << 0) /* Select SD slot A */ +# define ATMCI_SDCSEL_MASK ( 3 << 0) +# define ATMCI_SDCBUS_1BIT ( 0 << 6) /* 1-bit data bus */ +# define ATMCI_SDCBUS_4BIT ( 2 << 6) /* 4-bit data bus */ +# define ATMCI_SDCBUS_8BIT ( 3 << 6) /* 8-bit data bus[2] */ +# define ATMCI_SDCBUS_MASK ( 3 << 6) +#define ATMCI_ARGR 0x0010 /* Command Argument */ +#define ATMCI_CMDR 0x0014 /* Command */ +# define ATMCI_CMDR_CMDNB(x) ((x) << 0) /* Command Opcode */ +# define ATMCI_CMDR_RSPTYP_NONE ( 0 << 6) /* No response */ +# define ATMCI_CMDR_RSPTYP_48BIT ( 1 << 6) /* 48-bit response */ +# define ATMCI_CMDR_RSPTYP_136BIT ( 2 << 6) /* 136-bit response */ +# define ATMCI_CMDR_SPCMD_INIT ( 1 << 8) /* Initialization command */ +# define ATMCI_CMDR_SPCMD_SYNC ( 2 << 8) /* Synchronized command */ +# define ATMCI_CMDR_SPCMD_INT ( 4 << 8) /* Interrupt command */ +# define ATMCI_CMDR_SPCMD_INTRESP ( 5 << 8) /* Interrupt response */ +# define ATMCI_CMDR_OPDCMD ( 1 << 11) /* Open Drain */ +# define ATMCI_CMDR_MAXLAT_5CYC ( 0 << 12) /* Max latency 5 cycles */ +# define ATMCI_CMDR_MAXLAT_64CYC ( 1 << 12) /* Max latency 64 cycles */ +# define ATMCI_CMDR_START_XFER ( 1 << 16) /* Start data transfer */ +# define ATMCI_CMDR_STOP_XFER ( 2 << 16) /* Stop data transfer */ +# define ATMCI_CMDR_TRDIR_WRITE ( 0 << 18) /* Write data */ +# define ATMCI_CMDR_TRDIR_READ ( 1 << 18) /* Read data */ +# define ATMCI_CMDR_BLOCK ( 0 << 19) /* Single-block transfer */ +# define ATMCI_CMDR_MULTI_BLOCK ( 1 << 19) /* Multi-block transfer */ +# define ATMCI_CMDR_STREAM ( 2 << 19) /* MMC Stream transfer */ +# define ATMCI_CMDR_SDIO_BYTE ( 4 << 19) /* SDIO Byte transfer */ +# define ATMCI_CMDR_SDIO_BLOCK ( 5 << 19) /* SDIO Block transfer */ +# define ATMCI_CMDR_SDIO_SUSPEND ( 1 << 24) /* SDIO Suspend Command */ +# define ATMCI_CMDR_SDIO_RESUME ( 2 << 24) /* SDIO Resume Command */ +#define ATMCI_BLKR 0x0018 /* Block */ +# define ATMCI_BCNT(x) ((x) << 0) /* Data Block Count */ +# define ATMCI_BLKLEN(x) ((x) << 16) /* Data Block Length */ +#define ATMCI_CSTOR 0x001c /* Completion Signal Timeout[2] */ +# define ATMCI_CSTOCYC(x) ((x) << 0) /* CST cycles */ +# define ATMCI_CSTOMUL(x) ((x) << 4) /* CST multiplier */ +#define ATMCI_RSPR 0x0020 /* Response 0 */ +#define ATMCI_RSPR1 0x0024 /* Response 1 */ +#define ATMCI_RSPR2 0x0028 /* Response 2 */ +#define ATMCI_RSPR3 0x002c /* Response 3 */ +#define ATMCI_RDR 0x0030 /* Receive Data */ +#define ATMCI_TDR 0x0034 /* Transmit Data */ +#define ATMCI_SR 0x0040 /* Status */ +#define ATMCI_IER 0x0044 /* Interrupt Enable */ +#define ATMCI_IDR 0x0048 /* Interrupt Disable */ +#define ATMCI_IMR 0x004c /* Interrupt Mask */ +# define ATMCI_CMDRDY ( 1 << 0) /* Command Ready */ +# define ATMCI_RXRDY ( 1 << 1) /* Receiver Ready */ +# define ATMCI_TXRDY ( 1 << 2) /* Transmitter Ready */ +# define ATMCI_BLKE ( 1 << 3) /* Data Block Ended */ +# define ATMCI_DTIP ( 1 << 4) /* Data Transfer In Progress */ +# define ATMCI_NOTBUSY ( 1 << 5) /* Data Not Busy */ +# define ATMCI_ENDRX ( 1 << 6) /* End of RX Buffer */ +# define ATMCI_ENDTX ( 1 << 7) /* End of TX Buffer */ +# define ATMCI_SDIOIRQA ( 1 << 8) /* SDIO IRQ in slot A */ +# define ATMCI_SDIOIRQB ( 1 << 9) /* SDIO IRQ in slot B */ +# define ATMCI_SDIOWAIT ( 1 << 12) /* SDIO Read Wait Operation Status */ +# define ATMCI_CSRCV ( 1 << 13) /* CE-ATA Completion Signal Received */ +# define ATMCI_RXBUFF ( 1 << 14) /* RX Buffer Full */ +# define ATMCI_TXBUFE ( 1 << 15) /* TX Buffer Empty */ +# define ATMCI_RINDE ( 1 << 16) /* Response Index Error */ +# define ATMCI_RDIRE ( 1 << 17) /* Response Direction Error */ +# define ATMCI_RCRCE ( 1 << 18) /* Response CRC Error */ +# define ATMCI_RENDE ( 1 << 19) /* Response End Bit Error */ +# define ATMCI_RTOE ( 1 << 20) /* Response Time-Out Error */ +# define ATMCI_DCRCE ( 1 << 21) /* Data CRC Error */ +# define ATMCI_DTOE ( 1 << 22) /* Data Time-Out Error */ +# define ATMCI_CSTOE ( 1 << 23) /* Completion Signal Time-out Error */ +# define ATMCI_BLKOVRE ( 1 << 24) /* DMA Block Overrun Error */ +# define ATMCI_DMADONE ( 1 << 25) /* DMA Transfer Done */ +# define ATMCI_FIFOEMPTY ( 1 << 26) /* FIFO Empty Flag */ +# define ATMCI_XFRDONE ( 1 << 27) /* Transfer Done Flag */ +# define ATMCI_ACKRCV ( 1 << 28) /* Boot Operation Acknowledge Received */ +# define ATMCI_ACKRCVE ( 1 << 29) /* Boot Operation Acknowledge Error */ +# define ATMCI_OVRE ( 1 << 30) /* RX Overrun Error */ +# define ATMCI_UNRE ( 1 << 31) /* TX Underrun Error */ +#define ATMCI_DMA 0x0050 /* DMA Configuration[2] */ +# define ATMCI_DMA_OFFSET(x) ((x) << 0) /* DMA Write Buffer Offset */ +# define ATMCI_DMA_CHKSIZE(x) ((x) << 4) /* DMA Channel Read and Write Chunk Size */ +# define ATMCI_DMAEN ( 1 << 8) /* DMA Hardware Handshaking Enable */ +#define ATMCI_CFG 0x0054 /* Configuration[2] */ +# define ATMCI_CFG_FIFOMODE_1DATA ( 1 << 0) /* MCI Internal FIFO control mode */ +# define ATMCI_CFG_FERRCTRL_COR ( 1 << 4) /* Flow Error flag reset control mode */ +# define ATMCI_CFG_HSMODE ( 1 << 8) /* High Speed Mode */ +# define ATMCI_CFG_LSYNC ( 1 << 12) /* Synchronize on the last block */ +#define ATMCI_WPMR 0x00e4 /* Write Protection Mode[2] */ +# define ATMCI_WP_EN ( 1 << 0) /* WP Enable */ +# define ATMCI_WP_KEY (0x4d4349 << 8) /* WP Key */ +#define ATMCI_WPSR 0x00e8 /* Write Protection Status[2] */ +# define ATMCI_GET_WP_VS(x) ((x) & 0x0f) +# define ATMCI_GET_WP_VSRC(x) (((x) >> 8) & 0xffff) +#define ATMCI_VERSION 0x00FC /* Version */ +#define ATMCI_FIFO_APERTURE 0x0200 /* FIFO Aperture[2] */ + +/* This is not including the FIFO Aperture on MCI2 */ +#define ATMCI_REGS_SIZE 0x100 + +/* Register access macros */ +#define atmci_readl(port,reg) \ + __raw_readl((port)->regs + reg) +#define atmci_writel(port,reg,value) \ + __raw_writel((value), (port)->regs + 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..e94476be --- /dev/null +++ b/drivers/mmc/host/atmel-mci.c @@ -0,0 +1,2291 @@ +/* + * 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/types.h> + +#include <linux/mmc/host.h> +#include <linux/mmc/sdio.h> + +#include <mach/atmel-mci.h> +#include <linux/atmel-mci.h> +#include <linux/atmel_pdc.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 (ATMCI_DCRCE | ATMCI_DTOE | ATMCI_OVRE | ATMCI_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, +}; + +enum atmci_xfer_dir { + XFER_RECEIVE = 0, + XFER_TRANSMIT, +}; + +enum atmci_pdc_buf { + PDC_FIRST_BUF = 0, + PDC_SECOND_BUF, +}; + +struct atmel_mci_caps { + bool has_dma; + bool has_pdc; + bool has_cfg_reg; + bool has_cstor_reg; + bool has_highspeed; + bool has_rwproof; + bool has_odd_clk_div; +}; + +struct atmel_mci_dma { + struct dma_chan *chan; + struct dma_async_tx_descriptor *data_desc; +}; + +/** + * 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 or PDC code. + * @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. + * @data_size: just data->blocks * data->blksz. + * @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. + * @caps: MCI capabilities depending on MCI version. + * @prepare_data: function to setup MCI before data transfer which + * depends on MCI capabilities. + * @submit_data: function to start data transfer which depends on MCI + * capabilities. + * @stop_transfer: function to stop data transfer which depends on MCI + * capabilities. + * + * 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; + unsigned int data_size; + + struct atmel_mci_dma dma; + struct dma_chan *data_chan; + struct dma_slave_config dma_conf; + + 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[ATMCI_MAX_NR_SLOTS]; + + struct atmel_mci_caps caps; + + u32 (*prepare_data)(struct atmel_mci *host, struct mmc_data *data); + void (*submit_data)(struct atmel_mci *host, struct mmc_data *data); + void (*stop_transfer)(struct atmel_mci *host); +}; + +/** + * 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 +#define ATMCI_SUSPENDED 3 + + 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) + +/* + * 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(ATMCI_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, ATMCI_REGS_SIZE); + clk_disable(host->mck); + spin_unlock_bh(&host->lock); + + seq_printf(s, "MR:\t0x%08x%s%s CLKDIV=%u\n", + buf[ATMCI_MR / 4], + buf[ATMCI_MR / 4] & ATMCI_MR_RDPROOF ? " RDPROOF" : "", + buf[ATMCI_MR / 4] & ATMCI_MR_WRPROOF ? " WRPROOF" : "", + buf[ATMCI_MR / 4] & 0xff); + seq_printf(s, "DTOR:\t0x%08x\n", buf[ATMCI_DTOR / 4]); + seq_printf(s, "SDCR:\t0x%08x\n", buf[ATMCI_SDCR / 4]); + seq_printf(s, "ARGR:\t0x%08x\n", buf[ATMCI_ARGR / 4]); + seq_printf(s, "BLKR:\t0x%08x BCNT=%u BLKLEN=%u\n", + buf[ATMCI_BLKR / 4], + buf[ATMCI_BLKR / 4] & 0xffff, + (buf[ATMCI_BLKR / 4] >> 16) & 0xffff); + if (host->caps.has_cstor_reg) + seq_printf(s, "CSTOR:\t0x%08x\n", buf[ATMCI_CSTOR / 4]); + + /* Don't read RSPR and RDR; it will consume the data there */ + + atmci_show_status_reg(s, "SR", buf[ATMCI_SR / 4]); + atmci_show_status_reg(s, "IMR", buf[ATMCI_IMR / 4]); + + if (host->caps.has_dma) { + u32 val; + + val = buf[ATMCI_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 & ATMCI_DMAEN ? " DMAEN" : ""); + } + if (host->caps.has_cfg_reg) { + u32 val; + + val = buf[ATMCI_CFG / 4]; + seq_printf(s, "CFG:\t0x%08x%s%s%s%s\n", + val, + val & ATMCI_CFG_FIFOMODE_1DATA ? " FIFOMODE_ONE_DATA" : "", + val & ATMCI_CFG_FERRCTRL_COR ? " FERRCTRL_CLEAR_ON_READ" : "", + val & ATMCI_CFG_HSMODE ? " HSMODE" : "", + val & ATMCI_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 atmci_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 = atmci_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]); + atmci_writel(host, ATMCI_DTOR, (ATMCI_DTOMUL(dtomul) | ATMCI_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 = ATMCI_CMDR_CMDNB(cmd->opcode); + + if (cmd->flags & MMC_RSP_PRESENT) { + if (cmd->flags & MMC_RSP_136) + cmdr |= ATMCI_CMDR_RSPTYP_136BIT; + else + cmdr |= ATMCI_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 |= ATMCI_CMDR_MAXLAT_64CYC; + + if (mmc->ios.bus_mode == MMC_BUSMODE_OPENDRAIN) + cmdr |= ATMCI_CMDR_OPDCMD; + + data = cmd->data; + if (data) { + cmdr |= ATMCI_CMDR_START_XFER; + + if (cmd->opcode == SD_IO_RW_EXTENDED) { + cmdr |= ATMCI_CMDR_SDIO_BLOCK; + } else { + if (data->flags & MMC_DATA_STREAM) + cmdr |= ATMCI_CMDR_STREAM; + else if (data->blocks > 1) + cmdr |= ATMCI_CMDR_MULTI_BLOCK; + else + cmdr |= ATMCI_CMDR_BLOCK; + } + + if (data->flags & MMC_DATA_READ) + cmdr |= ATMCI_CMDR_TRDIR_READ; + } + + return cmdr; +} + +static void atmci_send_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); + + atmci_writel(host, ATMCI_ARGR, cmd->arg); + atmci_writel(host, ATMCI_CMDR, cmd_flags); +} + +static void atmci_send_stop_cmd(struct atmel_mci *host, struct mmc_data *data) +{ + atmci_send_command(host, data->stop, host->stop_cmdr); + atmci_writel(host, ATMCI_IER, ATMCI_CMDRDY); +} + +/* + * Configure given PDC buffer taking care of alignement issues. + * Update host->data_size and host->sg. + */ +static void atmci_pdc_set_single_buf(struct atmel_mci *host, + enum atmci_xfer_dir dir, enum atmci_pdc_buf buf_nb) +{ + u32 pointer_reg, counter_reg; + + if (dir == XFER_RECEIVE) { + pointer_reg = ATMEL_PDC_RPR; + counter_reg = ATMEL_PDC_RCR; + } else { + pointer_reg = ATMEL_PDC_TPR; + counter_reg = ATMEL_PDC_TCR; + } + + if (buf_nb == PDC_SECOND_BUF) { + pointer_reg += ATMEL_PDC_SCND_BUF_OFF; + counter_reg += ATMEL_PDC_SCND_BUF_OFF; + } + + atmci_writel(host, pointer_reg, sg_dma_address(host->sg)); + if (host->data_size <= sg_dma_len(host->sg)) { + if (host->data_size & 0x3) { + /* If size is different from modulo 4, transfer bytes */ + atmci_writel(host, counter_reg, host->data_size); + atmci_writel(host, ATMCI_MR, host->mode_reg | ATMCI_MR_PDCFBYTE); + } else { + /* Else transfer 32-bits words */ + atmci_writel(host, counter_reg, host->data_size / 4); + } + host->data_size = 0; + } else { + /* We assume the size of a page is 32-bits aligned */ + atmci_writel(host, counter_reg, sg_dma_len(host->sg) / 4); + host->data_size -= sg_dma_len(host->sg); + if (host->data_size) + host->sg = sg_next(host->sg); + } +} + +/* + * Configure PDC buffer according to the data size ie configuring one or two + * buffers. Don't use this function if you want to configure only the second + * buffer. In this case, use atmci_pdc_set_single_buf. + */ +static void atmci_pdc_set_both_buf(struct atmel_mci *host, int dir) +{ + atmci_pdc_set_single_buf(host, dir, PDC_FIRST_BUF); + if (host->data_size) + atmci_pdc_set_single_buf(host, dir, PDC_SECOND_BUF); +} + +/* + * Unmap sg lists, called when transfer is finished. + */ +static void atmci_pdc_cleanup(struct atmel_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)); +} + +/* + * Disable PDC transfers. Update pending flags to EVENT_XFER_COMPLETE after + * having received ATMCI_TXBUFE or ATMCI_RXBUFF interrupt. Enable ATMCI_NOTBUSY + * interrupt needed for both transfer directions. + */ +static void atmci_pdc_complete(struct atmel_mci *host) +{ + atmci_writel(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTDIS | ATMEL_PDC_TXTDIS); + atmci_pdc_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 (host->data) { + atmci_set_pending(host, EVENT_XFER_COMPLETE); + tasklet_schedule(&host->tasklet); + atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY); + } +} + +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)); +} + +/* + * 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 (host->caps.has_dma) + /* Disable DMA hardware handshaking on MCI */ + atmci_writel(host, ATMCI_DMA, atmci_readl(host, ATMCI_DMA) & ~ATMCI_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. + */ + atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY); + } +} + +/* + * 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; + + host->sg = data->sg; + host->data = data; + host->data_chan = NULL; + + iflags = ATMCI_DATA_ERROR_FLAGS; + + /* + * 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->pio_offset = 0; + if (data->flags & MMC_DATA_READ) + iflags |= ATMCI_RXRDY; + else + iflags |= ATMCI_TXRDY; + + return iflags; +} + +/* + * Set interrupt flags and set block length into the MCI mode register even + * if this value is also accessible in the MCI block register. It seems to be + * necessary before the High Speed MCI version. It also map sg and configure + * PDC registers. + */ +static u32 +atmci_prepare_data_pdc(struct atmel_mci *host, struct mmc_data *data) +{ + u32 iflags, tmp; + unsigned int sg_len; + enum dma_data_direction dir; + + data->error = -EINPROGRESS; + + host->data = data; + host->sg = data->sg; + iflags = ATMCI_DATA_ERROR_FLAGS; + + /* Enable pdc mode */ + atmci_writel(host, ATMCI_MR, host->mode_reg | ATMCI_MR_PDCMODE); + + if (data->flags & MMC_DATA_READ) { + dir = DMA_FROM_DEVICE; + iflags |= ATMCI_ENDRX | ATMCI_RXBUFF; + } else { + dir = DMA_TO_DEVICE; + iflags |= ATMCI_ENDTX | ATMCI_TXBUFE; + } + + /* Set BLKLEN */ + tmp = atmci_readl(host, ATMCI_MR); + tmp &= 0x0000ffff; + tmp |= ATMCI_BLKLEN(data->blksz); + atmci_writel(host, ATMCI_MR, tmp); + + /* Configure PDC */ + host->data_size = data->blocks * data->blksz; + sg_len = dma_map_sg(&host->pdev->dev, data->sg, data->sg_len, dir); + if (host->data_size) + atmci_pdc_set_both_buf(host, + ((dir == DMA_FROM_DEVICE) ? XFER_RECEIVE : XFER_TRANSMIT)); + + return iflags; +} + +static u32 +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; + enum dma_transfer_direction slave_dirn; + unsigned int sglen; + u32 iflags; + + data->error = -EINPROGRESS; + + WARN_ON(host->data); + host->sg = NULL; + host->data = data; + + iflags = ATMCI_DATA_ERROR_FLAGS; + + /* + * 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 atmci_prepare_data(host, data); + if (data->blksz & 3) + return atmci_prepare_data(host, data); + + for_each_sg(data->sg, sg, data->sg_len, i) { + if (sg->offset & 3 || sg->length & 3) + return atmci_prepare_data(host, data); + } + + /* 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 (host->caps.has_dma) + atmci_writel(host, ATMCI_DMA, ATMCI_DMA_CHKSIZE(3) | ATMCI_DMAEN); + + if (data->flags & MMC_DATA_READ) { + direction = DMA_FROM_DEVICE; + host->dma_conf.direction = slave_dirn = DMA_DEV_TO_MEM; + } else { + direction = DMA_TO_DEVICE; + host->dma_conf.direction = slave_dirn = DMA_MEM_TO_DEV; + } + + sglen = dma_map_sg(chan->device->dev, data->sg, + data->sg_len, direction); + + dmaengine_slave_config(chan, &host->dma_conf); + desc = dmaengine_prep_slave_sg(chan, + data->sg, sglen, slave_dirn, + 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 iflags; +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 mmc_data *data) +{ + return; +} + +/* + * Start PDC according to transfer direction. + */ +static void +atmci_submit_data_pdc(struct atmel_mci *host, struct mmc_data *data) +{ + if (data->flags & MMC_DATA_READ) + atmci_writel(host, ATMEL_PDC_PTCR, ATMEL_PDC_RXTEN); + else + atmci_writel(host, ATMEL_PDC_PTCR, ATMEL_PDC_TXTEN); +} + +static void +atmci_submit_data_dma(struct atmel_mci *host, struct mmc_data *data) +{ + 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); + } +} + +static void atmci_stop_transfer(struct atmel_mci *host) +{ + atmci_set_pending(host, EVENT_XFER_COMPLETE); + atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY); +} + +/* + * Stop data transfer because error(s) occured. + */ +static void atmci_stop_transfer_pdc(struct atmel_mci *host) +{ + atmci_set_pending(host, EVENT_XFER_COMPLETE); + atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY); +} + +static void atmci_stop_transfer_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); + atmci_writel(host, ATMCI_IER, ATMCI_NOTBUSY); + } +} + +/* + * Start a request: prepare data if needed, prepare the command and activate + * interrupts. + */ +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) { + iflags = atmci_readl(host, ATMCI_IMR); + iflags &= (ATMCI_SDIOIRQA | ATMCI_SDIOIRQB); + atmci_writel(host, ATMCI_CR, ATMCI_CR_SWRST); + atmci_writel(host, ATMCI_CR, ATMCI_CR_MCIEN); + atmci_writel(host, ATMCI_MR, host->mode_reg); + if (host->caps.has_cfg_reg) + atmci_writel(host, ATMCI_CFG, host->cfg_reg); + atmci_writel(host, ATMCI_IER, iflags); + host->need_reset = false; + } + atmci_writel(host, ATMCI_SDCR, slot->sdc_reg); + + iflags = atmci_readl(host, ATMCI_IMR); + if (iflags & ~(ATMCI_SDIOIRQA | ATMCI_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) */ + atmci_writel(host, ATMCI_CMDR, ATMCI_CMDR_SPCMD_INIT); + while (!(atmci_readl(host, ATMCI_SR) & ATMCI_CMDRDY)) + cpu_relax(); + } + iflags = 0; + data = mrq->data; + if (data) { + atmci_set_timeout(host, slot, data); + + /* Must set block count/size before sending command */ + atmci_writel(host, ATMCI_BLKR, ATMCI_BCNT(data->blocks) + | ATMCI_BLKLEN(data->blksz)); + dev_vdbg(&slot->mmc->class_dev, "BLKR=0x%08x\n", + ATMCI_BCNT(data->blocks) | ATMCI_BLKLEN(data->blksz)); + + iflags |= host->prepare_data(host, data); + } + + iflags |= ATMCI_CMDRDY; + cmd = mrq->cmd; + cmdflags = atmci_prepare_command(slot->mmc, cmd); + atmci_send_command(host, cmd, cmdflags); + + if (data) + host->submit_data(host, data); + + if (mrq->stop) { + host->stop_cmdr = atmci_prepare_command(slot->mmc, mrq->stop); + host->stop_cmdr |= ATMCI_CMDR_STOP_XFER; + if (!(data->flags & MMC_DATA_WRITE)) + host->stop_cmdr |= ATMCI_CMDR_TRDIR_READ; + if (data->flags & MMC_DATA_STREAM) + host->stop_cmdr |= ATMCI_CMDR_STREAM; + else + host->stop_cmdr |= ATMCI_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.) + */ + atmci_writel(host, ATMCI_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 &= ~ATMCI_SDCBUS_MASK; + switch (ios->bus_width) { + case MMC_BUS_WIDTH_1: + slot->sdc_reg |= ATMCI_SDCBUS_1BIT; + break; + case MMC_BUS_WIDTH_4: + slot->sdc_reg |= ATMCI_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); + atmci_writel(host, ATMCI_CR, ATMCI_CR_SWRST); + atmci_writel(host, ATMCI_CR, ATMCI_CR_MCIEN); + if (host->caps.has_cfg_reg) + atmci_writel(host, ATMCI_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 < ATMCI_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 */ + if (host->caps.has_odd_clk_div) { + clkdiv = DIV_ROUND_UP(host->bus_hz, clock_min) - 2; + if (clkdiv > 511) { + dev_warn(&mmc->class_dev, + "clock %u too slow; using %lu\n", + clock_min, host->bus_hz / (511 + 2)); + clkdiv = 511; + } + host->mode_reg = ATMCI_MR_CLKDIV(clkdiv >> 1) + | ATMCI_MR_CLKODD(clkdiv & 1); + } else { + 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 = ATMCI_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 (host->caps.has_rwproof) + host->mode_reg |= (ATMCI_MR_WRPROOF | ATMCI_MR_RDPROOF); + + if (host->caps.has_cfg_reg) { + /* setup High Speed mode in relation with card capacity */ + if (ios->timing == MMC_TIMING_SD_HS) + host->cfg_reg |= ATMCI_CFG_HSMODE; + else + host->cfg_reg &= ~ATMCI_CFG_HSMODE; + } + + if (list_empty(&host->queue)) { + atmci_writel(host, ATMCI_MR, host->mode_reg); + if (host->caps.has_cfg_reg) + atmci_writel(host, ATMCI_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 < ATMCI_MAX_NR_SLOTS; i++) { + if (host->slot[i] && host->slot[i]->clock) { + any_slot_active = true; + break; + } + } + if (!any_slot_active) { + atmci_writel(host, ATMCI_CR, ATMCI_CR_MCIDIS); + if (host->mode_reg) { + atmci_readl(host, ATMCI_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) + atmci_writel(host, ATMCI_IER, slot->sdio_irq); + else + atmci_writel(host, ATMCI_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) { + atmci_writel(host, ATMCI_MR, host->mode_reg); + if (host->caps.has_cfg_reg) + atmci_writel(host, ATMCI_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] = atmci_readl(host, ATMCI_RSPR); + cmd->resp[1] = atmci_readl(host, ATMCI_RSPR); + cmd->resp[2] = atmci_readl(host, ATMCI_RSPR); + cmd->resp[3] = atmci_readl(host, ATMCI_RSPR); + + if (status & ATMCI_RTOE) + cmd->error = -ETIMEDOUT; + else if ((cmd->flags & MMC_RSP_CRC) && (status & ATMCI_RCRCE)) + cmd->error = -EILSEQ; + else if (status & (ATMCI_RINDE | ATMCI_RDIRE | ATMCI_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) { + host->stop_transfer(host); + host->data = NULL; + atmci_writel(host, ATMCI_IDR, ATMCI_NOTBUSY + | ATMCI_TXRDY | ATMCI_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. + */ + atmci_writel(host, ATMCI_CR, ATMCI_CR_SWRST); + atmci_writel(host, ATMCI_CR, ATMCI_CR_MCIEN); + atmci_writel(host, ATMCI_MR, host->mode_reg); + if (host->caps.has_cfg_reg) + atmci_writel(host, ATMCI_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; + host->stop_transfer(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, + atmci_readl(host, ATMCI_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)) { + host->stop_transfer(host); + if (data->stop) + atmci_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 & ATMCI_DTOE) { + dev_dbg(&host->pdev->dev, + "data timeout error\n"); + data->error = -ETIMEDOUT; + } else if (status & ATMCI_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; + atmci_writel(host, ATMCI_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) + atmci_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 = atmci_readl(host, ATMCI_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 = atmci_readl(host, ATMCI_SR); + if (status & ATMCI_DATA_ERROR_FLAGS) { + atmci_writel(host, ATMCI_IDR, (ATMCI_NOTBUSY | ATMCI_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 & ATMCI_RXRDY); + + host->pio_offset = offset; + data->bytes_xfered += nbytes; + + return; + +done: + atmci_writel(host, ATMCI_IDR, ATMCI_RXRDY); + atmci_writel(host, ATMCI_IER, ATMCI_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)); + atmci_writel(host, ATMCI_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) { + atmci_writel(host, ATMCI_TDR, value); + goto done; + } + + offset = 4 - remaining; + buf = sg_virt(sg); + memcpy((u8 *)&value + remaining, buf, offset); + atmci_writel(host, ATMCI_TDR, value); + nbytes += offset; + } + + status = atmci_readl(host, ATMCI_SR); + if (status & ATMCI_DATA_ERROR_FLAGS) { + atmci_writel(host, ATMCI_IDR, (ATMCI_NOTBUSY | ATMCI_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 & ATMCI_TXRDY); + + host->pio_offset = offset; + data->bytes_xfered += nbytes; + + return; + +done: + atmci_writel(host, ATMCI_IDR, ATMCI_TXRDY); + atmci_writel(host, ATMCI_IER, ATMCI_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) +{ + atmci_writel(host, ATMCI_IDR, ATMCI_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 < ATMCI_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 = atmci_readl(host, ATMCI_SR); + mask = atmci_readl(host, ATMCI_IMR); + pending = status & mask; + if (!pending) + break; + + if (pending & ATMCI_DATA_ERROR_FLAGS) { + atmci_writel(host, ATMCI_IDR, ATMCI_DATA_ERROR_FLAGS + | ATMCI_RXRDY | ATMCI_TXRDY); + pending &= atmci_readl(host, ATMCI_IMR); + + host->data_status = status; + smp_wmb(); + atmci_set_pending(host, EVENT_DATA_ERROR); + tasklet_schedule(&host->tasklet); + } + + if (pending & ATMCI_TXBUFE) { + atmci_writel(host, ATMCI_IDR, ATMCI_TXBUFE); + atmci_writel(host, ATMCI_IDR, ATMCI_ENDTX); + /* + * We can receive this interruption before having configured + * the second pdc buffer, so we need to reconfigure first and + * second buffers again + */ + if (host->data_size) { + atmci_pdc_set_both_buf(host, XFER_TRANSMIT); + atmci_writel(host, ATMCI_IER, ATMCI_ENDTX); + atmci_writel(host, ATMCI_IER, ATMCI_TXBUFE); + } else { + atmci_pdc_complete(host); + } + } else if (pending & ATMCI_ENDTX) { + atmci_writel(host, ATMCI_IDR, ATMCI_ENDTX); + + if (host->data_size) { + atmci_pdc_set_single_buf(host, + XFER_TRANSMIT, PDC_SECOND_BUF); + atmci_writel(host, ATMCI_IER, ATMCI_ENDTX); + } + } + + if (pending & ATMCI_RXBUFF) { + atmci_writel(host, ATMCI_IDR, ATMCI_RXBUFF); + atmci_writel(host, ATMCI_IDR, ATMCI_ENDRX); + /* + * We can receive this interruption before having configured + * the second pdc buffer, so we need to reconfigure first and + * second buffers again + */ + if (host->data_size) { + atmci_pdc_set_both_buf(host, XFER_RECEIVE); + atmci_writel(host, ATMCI_IER, ATMCI_ENDRX); + atmci_writel(host, ATMCI_IER, ATMCI_RXBUFF); + } else { + atmci_pdc_complete(host); + } + } else if (pending & ATMCI_ENDRX) { + atmci_writel(host, ATMCI_IDR, ATMCI_ENDRX); + + if (host->data_size) { + atmci_pdc_set_single_buf(host, + XFER_RECEIVE, PDC_SECOND_BUF); + atmci_writel(host, ATMCI_IER, ATMCI_ENDRX); + } + } + + + if (pending & ATMCI_NOTBUSY) { + atmci_writel(host, ATMCI_IDR, + ATMCI_DATA_ERROR_FLAGS | ATMCI_NOTBUSY); + if (!host->data_status) + host->data_status = status; + smp_wmb(); + atmci_set_pending(host, EVENT_DATA_COMPLETE); + tasklet_schedule(&host->tasklet); + } + if (pending & ATMCI_RXRDY) + atmci_read_data_pio(host); + if (pending & ATMCI_TXRDY) + atmci_write_data_pio(host); + + if (pending & ATMCI_CMDRDY) + atmci_cmd_interrupt(host, status); + + if (pending & (ATMCI_SDIOIRQA | ATMCI_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 (host->caps.has_highspeed) + 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); +} + +static bool atmci_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 bool atmci_configure_dma(struct atmel_mci *host) +{ + struct mci_platform_data *pdata; + + if (host == NULL) + return false; + + pdata = host->pdev->dev.platform_data; + + if (pdata && find_slave_dev(pdata->dma_slave)) { + dma_cap_mask_t mask; + + /* Try to grab a DMA channel */ + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + host->dma.chan = + dma_request_channel(mask, atmci_filter, pdata->dma_slave); + } + if (!host->dma.chan) { + dev_warn(&host->pdev->dev, "no DMA channel available\n"); + return false; + } else { + dev_info(&host->pdev->dev, + "using %s for DMA transfers\n", + dma_chan_name(host->dma.chan)); + + host->dma_conf.src_addr = host->mapbase + ATMCI_RDR; + host->dma_conf.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + host->dma_conf.src_maxburst = 1; + host->dma_conf.dst_addr = host->mapbase + ATMCI_TDR; + host->dma_conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + host->dma_conf.dst_maxburst = 1; + host->dma_conf.device_fc = false; + return true; + } +} + +static inline unsigned int atmci_get_version(struct atmel_mci *host) +{ + return atmci_readl(host, ATMCI_VERSION) & 0x00000fff; +} + +/* + * HSMCI (High Speed MCI) module is not fully compatible with MCI module. + * HSMCI provides DMA support and a new config register but no more supports + * PDC. + */ +static void __init atmci_get_cap(struct atmel_mci *host) +{ + unsigned int version; + + version = atmci_get_version(host); + dev_info(&host->pdev->dev, + "version: 0x%x\n", version); + + host->caps.has_dma = 0; + host->caps.has_pdc = 1; + host->caps.has_cfg_reg = 0; + host->caps.has_cstor_reg = 0; + host->caps.has_highspeed = 0; + host->caps.has_rwproof = 0; + host->caps.has_odd_clk_div = 0; + + /* keep only major version number */ + switch (version & 0xf00) { + case 0x500: + host->caps.has_odd_clk_div = 1; + case 0x400: + case 0x300: +#ifdef CONFIG_AT_HDMAC + host->caps.has_dma = 1; +#else + dev_info(&host->pdev->dev, + "has dma capability but dma engine is not selected, then use pio\n"); +#endif + host->caps.has_pdc = 0; + host->caps.has_cfg_reg = 1; + host->caps.has_cstor_reg = 1; + host->caps.has_highspeed = 1; + case 0x200: + host->caps.has_rwproof = 1; + case 0x100: + break; + default: + host->caps.has_pdc = 0; + dev_warn(&host->pdev->dev, + "Unmanaged mci version, set minimum capabilities\n"); + break; + } +} + +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); + atmci_writel(host, ATMCI_CR, ATMCI_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; + + /* Get MCI capabilities and set operations according to it */ + atmci_get_cap(host); + if (host->caps.has_dma && atmci_configure_dma(host)) { + host->prepare_data = &atmci_prepare_data_dma; + host->submit_data = &atmci_submit_data_dma; + host->stop_transfer = &atmci_stop_transfer_dma; + } else if (host->caps.has_pdc) { + dev_info(&pdev->dev, "using PDC\n"); + host->prepare_data = &atmci_prepare_data_pdc; + host->submit_data = &atmci_submit_data_pdc; + host->stop_transfer = &atmci_stop_transfer_pdc; + } else { + dev_info(&pdev->dev, "using PIO\n"); + host->prepare_data = &atmci_prepare_data; + host->submit_data = &atmci_submit_data; + host->stop_transfer = &atmci_stop_transfer; + } + + 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, ATMCI_SDCSEL_SLOT_A, ATMCI_SDIOIRQA); + if (!ret) + nr_slots++; + } + if (pdata->slot[1].bus_width) { + ret = atmci_init_slot(host, &pdata->slot[1], + 1, ATMCI_SDCSEL_SLOT_B, ATMCI_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: + if (host->dma.chan) + dma_release_channel(host->dma.chan); + 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 < ATMCI_MAX_NR_SLOTS; i++) { + if (host->slot[i]) + atmci_cleanup_slot(host->slot[i], i); + } + + clk_enable(host->mck); + atmci_writel(host, ATMCI_IDR, ~0UL); + atmci_writel(host, ATMCI_CR, ATMCI_CR_MCIDIS); + atmci_readl(host, ATMCI_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; +} + +#ifdef CONFIG_PM +static int atmci_suspend(struct device *dev) +{ + struct atmel_mci *host = dev_get_drvdata(dev); + int i; + + for (i = 0; i < ATMCI_MAX_NR_SLOTS; i++) { + struct atmel_mci_slot *slot = host->slot[i]; + int ret; + + if (!slot) + continue; + ret = mmc_suspend_host(slot->mmc); + if (ret < 0) { + while (--i >= 0) { + slot = host->slot[i]; + if (slot + && test_bit(ATMCI_SUSPENDED, &slot->flags)) { + mmc_resume_host(host->slot[i]->mmc); + clear_bit(ATMCI_SUSPENDED, &slot->flags); + } + } + return ret; + } else { + set_bit(ATMCI_SUSPENDED, &slot->flags); + } + } + + return 0; +} + +static int atmci_resume(struct device *dev) +{ + struct atmel_mci *host = dev_get_drvdata(dev); + int i; + int ret = 0; + + for (i = 0; i < ATMCI_MAX_NR_SLOTS; i++) { + struct atmel_mci_slot *slot = host->slot[i]; + int err; + + slot = host->slot[i]; + if (!slot) + continue; + if (!test_bit(ATMCI_SUSPENDED, &slot->flags)) + continue; + err = mmc_resume_host(slot->mmc); + if (err < 0) + ret = err; + else + clear_bit(ATMCI_SUSPENDED, &slot->flags); + } + + return ret; +} +static SIMPLE_DEV_PM_OPS(atmci_pm, atmci_suspend, atmci_resume); +#define ATMCI_PM_OPS (&atmci_pm) +#else +#define ATMCI_PM_OPS NULL +#endif + +static struct platform_driver atmci_driver = { + .remove = __exit_p(atmci_remove), + .driver = { + .name = "atmel_mci", + .pm = ATMCI_PM_OPS, + }, +}; + +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..dbd0c8a4 --- /dev/null +++ b/drivers/mmc/host/au1xmmc.c @@ -0,0 +1,1228 @@ +/* + * 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...) \ + pr_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 */ +#define AU1100_MMC_DESCRIPTOR_SIZE 0x0000ffff +#define AU1200_MMC_DESCRIPTOR_SIZE 0x003fffff + +#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_DBDMA 0x0020 +#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 int has_dbdma(void) +{ + switch (alchemy_get_cputype()) { + case ALCHEMY_CPU_AU1200: + case ALCHEMY_CPU_AU1300: + return 1; + default: + return 0; + } +} + +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: + pr_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 | HOST_F_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; + } 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 | HOST_F_DBDMA))) { + u32 channel = DMA_CHANNEL(host); + + /* Start the DBDMA as soon as the buffer gets something in it */ + + if (host->flags & HOST_F_RECV) { + u32 mask = SD_STATUS_DB | SD_STATUS_NE; + + while((status & mask) != mask) + status = au_readl(HOST_STATUS(host)); + } + + au1xxx_dbdma_start(channel); + } +} + +static void au1xmmc_set_clock(struct au1xmmc_host *host, int rate) +{ + unsigned int pbus = get_au1x00_speed(); + unsigned int divisor; + u32 config; + + /* From databook: + * divisor = ((((cpuclock / sbus_divisor) / 2) / mmcclock) / 2) - 1 + */ + pbus /= ((au_readl(SYS_POWERCTRL) & 0x3) + 2); + pbus /= 2; + divisor = ((pbus / rate) / 2) - 1; + + config = au_readl(HOST_CONFIG(host)); + + config &= ~(SD_CONFIG_DIV); + config |= (divisor & SD_CONFIG_DIV) | SD_CONFIG_DE; + + au_writel(config, HOST_CONFIG(host)); + au_sync(); +} + +static int au1xmmc_prepare_data(struct au1xmmc_host *host, + struct mmc_data *data) +{ + int datalen = data->blocks * 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 | HOST_F_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; + } + } 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_8: + config2 |= SD_CONFIG2_BB; + break; + case MMC_BUS_WIDTH_4: + config2 &= ~SD_CONFIG2_BB; + config2 |= SD_CONFIG2_WB; + break; + case MMC_BUS_WIDTH_1: + config2 &= ~(SD_CONFIG2_WB | SD_CONFIG2_BB); + 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; +} + +/* 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 | HOST_F_DBDMA; + + 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); + } +} + +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, iflag; + + 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; + + mmc->ops = &au1xmmc_ops; + + mmc->f_min = 450000; + mmc->f_max = 24000000; + + 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; + mmc->max_segs = AU1XMMC_DESCRIPTOR_COUNT; + + iflag = IRQF_SHARED; /* Au1100/Au1200: one int for both ctrls */ + + switch (alchemy_get_cputype()) { + case ALCHEMY_CPU_AU1100: + mmc->max_seg_size = AU1100_MMC_DESCRIPTOR_SIZE; + break; + case ALCHEMY_CPU_AU1200: + mmc->max_seg_size = AU1200_MMC_DESCRIPTOR_SIZE; + break; + case ALCHEMY_CPU_AU1300: + iflag = 0; /* nothing is shared */ + mmc->max_seg_size = AU1200_MMC_DESCRIPTOR_SIZE; + mmc->f_max = 52000000; + if (host->ioarea->start == AU1100_SD0_PHYS_ADDR) + mmc->caps |= MMC_CAP_8_BIT_DATA; + break; + } + + ret = request_irq(host->irq, au1xmmc_irq, iflag, DRIVER_NAME, host); + if (ret) { + dev_err(&pdev->dev, "cannot grab IRQ\n"); + goto out3; + } + + 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); + + if (has_dbdma()) { + ret = au1xmmc_dbdma_init(host); + if (ret) + pr_info(DRIVER_NAME ": DBDMA init failed; using PIO\n"); + } + +#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); + + pr_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(); + + if (host->flags & HOST_F_DBDMA) + au1xmmc_dbdma_shutdown(host); + + 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); + + if (host->flags & HOST_F_DBDMA) + au1xmmc_dbdma_shutdown(host); + + 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) +{ + if (has_dbdma()) { + /* 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) + pr_err("au1xmmc: cannot add memory dbdma\n"); + } + return platform_driver_register(&au1xmmc_driver); +} + +static void __exit au1xmmc_exit(void) +{ + if (has_dbdma() && memid) + au1xxx_ddma_del_device(memid); + + 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..03666174 --- /dev/null +++ b/drivers/mmc/host/bfin_sdh.c @@ -0,0 +1,634 @@ +/* + * 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, + }, +}; + +module_platform_driver(sdh_driver); + +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..83693fd7 --- /dev/null +++ b/drivers/mmc/host/cb710-mmc.c @@ -0,0 +1,788 @@ +/* + * 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 +}; + +module_platform_driver(cb710_mmc_driver); + +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..c1f3673a --- /dev/null +++ b/drivers/mmc/host/davinci_mmc.c @@ -0,0 +1,1536 @@ +/* + * 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 poll_threshold = 128; +module_param(poll_threshold, uint, S_IRUGO); +MODULE_PARM_DESC(poll_threshold, + "Polling transaction size threshold. Default = 128"); + +static unsigned poll_loopcount = 32; +module_param(poll_loopcount, uint, S_IRUGO); +MODULE_PARM_DESC(poll_loopcount, + "Maximum polling loop count. 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; + bool active_request; + + /* 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 +}; + +static irqreturn_t mmc_davinci_irq(int irq, void *dev_id); + +/* 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); + + host->active_request = true; + + if (!host->do_dma && host->bytes_left <= poll_threshold) { + u32 count = poll_loopcount; + + while (host->active_request && count--) { + mmc_davinci_irq(0, host); + cpu_relax(); + } + } + + if (host->active_request) + 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); + struct platform_device *pdev = to_platform_device(mmc->parent); + struct davinci_mmc_config *config = pdev->dev.platform_data; + + 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->power_mode) { + case MMC_POWER_OFF: + if (config && config->set_power) + config->set_power(pdev->id, false); + break; + case MMC_POWER_UP: + if (config && config->set_power) + config->set_power(pdev->id, true); + break; + } + + 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); + host->active_request = false; + } 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); + host->active_request = false; + } +} + +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. + */ + if (host->bytes_left && (status & (MMCST0_DXRDY | MMCST0_DRRDY))) { + unsigned long im_val; + + /* + * If interrupts fire during the following loop, they will be + * handled by the handler, but the PIC will still buffer these. + * As a result, the handler will be called again to serve these + * needlessly. In order to avoid these spurious interrupts, + * keep interrupts masked during the loop. + */ + im_val = readl(host->base + DAVINCI_MMCIM); + writel(0, host->base + DAVINCI_MMCIM); + + do { + davinci_fifo_data_trans(host, rw_threshold); + status = readl(host->base + DAVINCI_MMCST0); + qstatus |= status; + } while (host->bytes_left && + (status & (MMCST0_DXRDY | MMCST0_DRRDY))); + + /* + * If an interrupt is pending, it is assumed it will fire when + * it is unmasked. This assumption is also taken when the MMCIM + * is first set. Otherwise, writing to MMCIM after reading the + * status is race-prone. + */ + writel(im_val, host->base + DAVINCI_MMCIM); + } + + 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; + + ret = mmc_suspend_host(host->mmc); + if (!ret) { + writel(0, host->base + DAVINCI_MMCIM); + mmc_davinci_reset_ctrl(host, 1); + clk_disable(host->clk); + host->suspended = 1; + } else { + host->suspended = 0; + } + + 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_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-pci.c b/drivers/mmc/host/dw_mmc-pci.c new file mode 100644 index 00000000..dc0d25a0 --- /dev/null +++ b/drivers/mmc/host/dw_mmc-pci.c @@ -0,0 +1,158 @@ +/* + * Synopsys DesignWare Multimedia Card PCI Interface driver + * + * Copyright (C) 2012 Vayavya Labs Pvt. 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/interrupt.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/mmc/host.h> +#include <linux/mmc/mmc.h> +#include <linux/mmc/dw_mmc.h> +#include "dw_mmc.h" + +#define PCI_BAR_NO 2 +#define COMPLETE_BAR 0 +#define SYNOPSYS_DW_MCI_VENDOR_ID 0x700 +#define SYNOPSYS_DW_MCI_DEVICE_ID 0x1107 +/* Defining the Capabilities */ +#define DW_MCI_CAPABILITIES (MMC_CAP_4_BIT_DATA | MMC_CAP_MMC_HIGHSPEED |\ + MMC_CAP_SD_HIGHSPEED | MMC_CAP_8_BIT_DATA |\ + MMC_CAP_SDIO_IRQ) + +static struct dw_mci_board pci_board_data = { + .num_slots = 1, + .caps = DW_MCI_CAPABILITIES, + .bus_hz = 33 * 1000 * 1000, + .detect_delay_ms = 200, + .fifo_depth = 32, +}; + +static int __devinit dw_mci_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *entries) +{ + struct dw_mci *host; + int ret; + + ret = pci_enable_device(pdev); + if (ret) + return ret; + if (pci_request_regions(pdev, "dw_mmc_pci")) { + ret = -ENODEV; + goto err_disable_dev; + } + + host = kzalloc(sizeof(struct dw_mci), GFP_KERNEL); + if (!host) { + ret = -ENOMEM; + goto err_release; + } + + host->irq = pdev->irq; + host->irq_flags = IRQF_SHARED; + host->dev = pdev->dev; + host->pdata = &pci_board_data; + + host->regs = pci_iomap(pdev, PCI_BAR_NO, COMPLETE_BAR); + if (!host->regs) { + ret = -EIO; + goto err_unmap; + } + + pci_set_drvdata(pdev, host); + ret = dw_mci_probe(host); + if (ret) + goto err_probe_failed; + return ret; + +err_probe_failed: + pci_iounmap(pdev, host->regs); +err_unmap: + kfree(host); +err_release: + pci_release_regions(pdev); +err_disable_dev: + pci_disable_device(pdev); + return ret; +} + +static void __devexit dw_mci_pci_remove(struct pci_dev *pdev) +{ + struct dw_mci *host = pci_get_drvdata(pdev); + + dw_mci_remove(host); + pci_set_drvdata(pdev, NULL); + pci_release_regions(pdev); + pci_iounmap(pdev, host->regs); + kfree(host); + pci_disable_device(pdev); +} + +#ifdef CONFIG_PM_SLEEP +static int dw_mci_pci_suspend(struct device *dev) +{ + int ret; + struct pci_dev *pdev = to_pci_dev(dev); + struct dw_mci *host = pci_get_drvdata(pdev); + + ret = dw_mci_suspend(host); + return ret; +} + +static int dw_mci_pci_resume(struct device *dev) +{ + int ret; + struct pci_dev *pdev = to_pci_dev(dev); + struct dw_mci *host = pci_get_drvdata(pdev); + + ret = dw_mci_resume(host); + return ret; +} +#else +#define dw_mci_pci_suspend NULL +#define dw_mci_pci_resume NULL +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(dw_mci_pci_pmops, dw_mci_pci_suspend, dw_mci_pci_resume); + +static DEFINE_PCI_DEVICE_TABLE(dw_mci_pci_id) = { + { PCI_DEVICE(SYNOPSYS_DW_MCI_VENDOR_ID, SYNOPSYS_DW_MCI_DEVICE_ID) }, + {} +}; +MODULE_DEVICE_TABLE(pci, dw_mci_pci_id); + +static struct pci_driver dw_mci_pci_driver = { + .name = "dw_mmc_pci", + .id_table = dw_mci_pci_id, + .probe = dw_mci_pci_probe, + .remove = dw_mci_pci_remove, + .driver = { + .pm = &dw_mci_pci_pmops + }, +}; + +static int __init dw_mci_init(void) +{ + return pci_register_driver(&dw_mci_pci_driver); +} + +static void __exit dw_mci_exit(void) +{ + pci_unregister_driver(&dw_mci_pci_driver); +} + +module_init(dw_mci_init); +module_exit(dw_mci_exit); + +MODULE_DESCRIPTION("DW Multimedia Card PCI Interface driver"); +MODULE_AUTHOR("Shashidhar Hiremath <shashidharh@vayavyalabs.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mmc/host/dw_mmc-pltfm.c b/drivers/mmc/host/dw_mmc-pltfm.c new file mode 100644 index 00000000..92ec3eb3 --- /dev/null +++ b/drivers/mmc/host/dw_mmc-pltfm.c @@ -0,0 +1,134 @@ +/* + * Synopsys DesignWare Multimedia Card Interface driver + * + * 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/interrupt.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/mmc/host.h> +#include <linux/mmc/mmc.h> +#include <linux/mmc/dw_mmc.h> +#include "dw_mmc.h" + +static int dw_mci_pltfm_probe(struct platform_device *pdev) +{ + struct dw_mci *host; + struct resource *regs; + int ret; + + host = kzalloc(sizeof(struct dw_mci), GFP_KERNEL); + if (!host) + return -ENOMEM; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) { + ret = -ENXIO; + goto err_free; + } + + host->irq = platform_get_irq(pdev, 0); + if (host->irq < 0) { + ret = host->irq; + goto err_free; + } + + host->dev = pdev->dev; + host->irq_flags = 0; + host->pdata = pdev->dev.platform_data; + ret = -ENOMEM; + host->regs = ioremap(regs->start, resource_size(regs)); + if (!host->regs) + goto err_free; + platform_set_drvdata(pdev, host); + ret = dw_mci_probe(host); + if (ret) + goto err_out; + return ret; +err_out: + iounmap(host->regs); +err_free: + kfree(host); + return ret; +} + +static int __exit dw_mci_pltfm_remove(struct platform_device *pdev) +{ + struct dw_mci *host = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + dw_mci_remove(host); + iounmap(host->regs); + kfree(host); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +/* + * TODO: we should probably disable the clock to the card in the suspend path. + */ +static int dw_mci_pltfm_suspend(struct device *dev) +{ + int ret; + struct dw_mci *host = dev_get_drvdata(dev); + + ret = dw_mci_suspend(host); + if (ret) + return ret; + + return 0; +} + +static int dw_mci_pltfm_resume(struct device *dev) +{ + int ret; + struct dw_mci *host = dev_get_drvdata(dev); + + ret = dw_mci_resume(host); + if (ret) + return ret; + + return 0; +} +#else +#define dw_mci_pltfm_suspend NULL +#define dw_mci_pltfm_resume NULL +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(dw_mci_pltfm_pmops, dw_mci_pltfm_suspend, dw_mci_pltfm_resume); + +static struct platform_driver dw_mci_pltfm_driver = { + .remove = __exit_p(dw_mci_pltfm_remove), + .driver = { + .name = "dw_mmc", + .pm = &dw_mci_pltfm_pmops, + }, +}; + +static int __init dw_mci_init(void) +{ + return platform_driver_probe(&dw_mci_pltfm_driver, dw_mci_pltfm_probe); +} + +static void __exit dw_mci_exit(void) +{ + platform_driver_unregister(&dw_mci_pltfm_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.c b/drivers/mmc/host/dw_mmc.c new file mode 100644 index 00000000..ab3fc461 --- /dev/null +++ b/drivers/mmc/host/dw_mmc.c @@ -0,0 +1,2221 @@ +/* + * 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/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 <linux/workqueue.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 & 0x03ffe000) | ((s) & 0x1fff)) + + 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; +}; + +static struct workqueue_struct *dw_mci_card_workqueue; + +#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->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->using_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); + } +} + +static int dw_mci_get_dma_dir(struct mmc_data *data) +{ + if (data->flags & MMC_DATA_WRITE) + return DMA_TO_DEVICE; + else + return DMA_FROM_DEVICE; +} + +#ifdef CONFIG_MMC_DW_IDMAC +static void dw_mci_dma_cleanup(struct dw_mci *host) +{ + struct mmc_data *data = host->data; + + if (data) + if (!data->host_cookie) + dma_unmap_sg(&host->dev, + data->sg, + data->sg_len, + dw_mci_get_dma_dir(data)); +} + +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->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_pre_dma_transfer(struct dw_mci *host, + struct mmc_data *data, + bool next) +{ + struct scatterlist *sg; + unsigned int i, sg_len; + + if (!next && data->host_cookie) + return data->host_cookie; + + /* + * 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; + } + + sg_len = dma_map_sg(&host->dev, + data->sg, + data->sg_len, + dw_mci_get_dma_dir(data)); + if (sg_len == 0) + return -EINVAL; + + if (next) + data->host_cookie = sg_len; + + return sg_len; +} + +static void dw_mci_pre_req(struct mmc_host *mmc, + struct mmc_request *mrq, + bool is_first_req) +{ + struct dw_mci_slot *slot = mmc_priv(mmc); + struct mmc_data *data = mrq->data; + + if (!slot->host->use_dma || !data) + return; + + if (data->host_cookie) { + data->host_cookie = 0; + return; + } + + if (dw_mci_pre_dma_transfer(slot->host, mrq->data, 1) < 0) + data->host_cookie = 0; +} + +static void dw_mci_post_req(struct mmc_host *mmc, + struct mmc_request *mrq, + int err) +{ + struct dw_mci_slot *slot = mmc_priv(mmc); + struct mmc_data *data = mrq->data; + + if (!slot->host->use_dma || !data) + return; + + if (data->host_cookie) + dma_unmap_sg(&slot->host->dev, + data->sg, + data->sg_len, + dw_mci_get_dma_dir(data)); + data->host_cookie = 0; +} + +static int dw_mci_submit_data_dma(struct dw_mci *host, struct mmc_data *data) +{ + int sg_len; + u32 temp; + + host->using_dma = 0; + + /* If we don't have a channel, we can't do DMA */ + if (!host->use_dma) + return -ENODEV; + + sg_len = dw_mci_pre_dma_transfer(host, data, 0); + if (sg_len < 0) { + host->dma_ops->stop(host); + return sg_len; + } + + host->using_dma = 1; + + dev_vdbg(&host->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 (data->flags & MMC_DATA_READ) + host->dir_status = DW_MCI_RECV_STATUS; + else + host->dir_status = DW_MCI_SEND_STATUS; + + if (dw_mci_submit_data_dma(host, data)) { + int 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->sg = data->sg; + host->part_buf_start = 0; + host->part_buf_count = 0; + + mci_writel(host, RINTSTS, SDMMC_INT_TXDR | SDMMC_INT_RXDR); + 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) << slot->id)); + + /* 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 << slot->id)); +} + +static void __dw_mci_start_request(struct dw_mci *host, + struct dw_mci_slot *slot, + struct mmc_command *cmd) +{ + struct mmc_request *mrq; + 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 = cmd->data; + if (data) { + dw_mci_set_timeout(host); + mci_writel(host, BYTCNT, data->blksz*data->blocks); + mci_writel(host, BLKSIZ, data->blksz); + } + + 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_start_request(struct dw_mci *host, + struct dw_mci_slot *slot) +{ + struct mmc_request *mrq = slot->mrq; + struct mmc_command *cmd; + + cmd = mrq->sbc ? mrq->sbc : mrq->cmd; + __dw_mci_start_request(host, slot, cmd); +} + +/* must be called with host->lock held */ +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); + + 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); + } +} + +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); + + /* + * The check for card presence and queueing of the request must be + * atomic, otherwise the card could be removed in between and the + * request wouldn't fail until another card was inserted. + */ + spin_lock_bh(&host->lock); + + if (!test_bit(DW_MMC_CARD_PRESENT, &slot->flags)) { + spin_unlock_bh(&host->lock); + mrq->cmd->error = -ENOMEDIUM; + mmc_request_done(mmc, mrq); + return; + } + + dw_mci_queue_request(host, slot, mrq); + + spin_unlock_bh(&host->lock); +} + +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; + } + + regs = mci_readl(slot->host, UHS_REG); + + /* DDR mode set */ + if (ios->timing == MMC_TIMING_UHS_DDR50) + regs |= (0x1 << slot->id) << 16; + else + 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 void dw_mci_enable_sdio_irq(struct mmc_host *mmc, int enb) +{ + struct dw_mci_slot *slot = mmc_priv(mmc); + struct dw_mci *host = slot->host; + u32 int_mask; + + /* Enable/disable Slot Specific SDIO interrupt */ + int_mask = mci_readl(host, INTMASK); + if (enb) { + mci_writel(host, INTMASK, + (int_mask | (1 << SDMMC_INT_SDIO(slot->id)))); + } else { + mci_writel(host, INTMASK, + (int_mask & ~(1 << SDMMC_INT_SDIO(slot->id)))); + } +} + +static const struct mmc_host_ops dw_mci_ops = { + .request = dw_mci_request, + .pre_req = dw_mci_pre_req, + .post_req = dw_mci_post_req, + .set_ios = dw_mci_set_ios, + .get_ro = dw_mci_get_ro, + .get_cd = dw_mci_get_cd, + .enable_sdio_irq = dw_mci_enable_sdio_irq, +}; + +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->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->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, ctrl; + + 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, cmd); + if (cmd == host->mrq->sbc && !cmd->error) { + prev_state = state = STATE_SENDING_CMD; + __dw_mci_start_request(host, host->cur_slot, + host->mrq->cmd); + goto unlock; + } + + 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) { + data->error = -ETIMEDOUT; + } else if (status & SDMMC_INT_DCRC) { + data->error = -EILSEQ; + } else if (status & SDMMC_INT_EBE && + host->dir_status == + DW_MCI_SEND_STATUS) { + /* + * No data CRC status was returned. + * The number of bytes transferred will + * be exaggerated in PIO mode. + */ + data->bytes_xfered = 0; + data->error = -ETIMEDOUT; + } else { + dev_err(&host->dev, + "data FIFO error " + "(status=%08x)\n", + status); + data->error = -EIO; + } + /* + * After an error, there may be data lingering + * in the FIFO, so reset it - doing so + * generates a block interrupt, hence setting + * the scatter-gather pointer to NULL. + */ + sg_miter_stop(&host->sg_miter); + host->sg = NULL; + ctrl = mci_readl(host, CTRL); + ctrl |= SDMMC_CTRL_FIFO_RESET; + mci_writel(host, CTRL, ctrl); + } else { + data->bytes_xfered = data->blocks * data->blksz; + data->error = 0; + } + + if (!data->stop) { + dw_mci_request_end(host, host->mrq); + goto unlock; + } + + if (host->mrq->sbc && !data->error) { + data->stop->error = 0; + 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); + +} + +/* push final bytes to part_buf, only use during push */ +static void dw_mci_set_part_bytes(struct dw_mci *host, void *buf, int cnt) +{ + memcpy((void *)&host->part_buf, buf, cnt); + host->part_buf_count = cnt; +} + +/* append bytes to part_buf, only use during push */ +static int dw_mci_push_part_bytes(struct dw_mci *host, void *buf, int cnt) +{ + cnt = min(cnt, (1 << host->data_shift) - host->part_buf_count); + memcpy((void *)&host->part_buf + host->part_buf_count, buf, cnt); + host->part_buf_count += cnt; + return cnt; +} + +/* pull first bytes from part_buf, only use during pull */ +static int dw_mci_pull_part_bytes(struct dw_mci *host, void *buf, int cnt) +{ + cnt = min(cnt, (int)host->part_buf_count); + if (cnt) { + memcpy(buf, (void *)&host->part_buf + host->part_buf_start, + cnt); + host->part_buf_count -= cnt; + host->part_buf_start += cnt; + } + return cnt; +} + +/* pull final bytes from the part_buf, assuming it's just been filled */ +static void dw_mci_pull_final_bytes(struct dw_mci *host, void *buf, int cnt) +{ + memcpy(buf, &host->part_buf, cnt); + host->part_buf_start = cnt; + host->part_buf_count = (1 << host->data_shift) - cnt; +} + +static void dw_mci_push_data16(struct dw_mci *host, void *buf, int cnt) +{ + /* try and push anything in the part_buf */ + if (unlikely(host->part_buf_count)) { + int len = dw_mci_push_part_bytes(host, buf, cnt); + buf += len; + cnt -= len; + if (!sg_next(host->sg) || host->part_buf_count == 2) { + mci_writew(host, DATA(host->data_offset), + host->part_buf16); + host->part_buf_count = 0; + } + } +#ifndef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS + if (unlikely((unsigned long)buf & 0x1)) { + while (cnt >= 2) { + u16 aligned_buf[64]; + int len = min(cnt & -2, (int)sizeof(aligned_buf)); + int items = len >> 1; + int i; + /* memcpy from input buffer into aligned buffer */ + memcpy(aligned_buf, buf, len); + buf += len; + cnt -= len; + /* push data from aligned buffer into fifo */ + for (i = 0; i < items; ++i) + mci_writew(host, DATA(host->data_offset), + aligned_buf[i]); + } + } else +#endif + { + u16 *pdata = buf; + for (; cnt >= 2; cnt -= 2) + mci_writew(host, DATA(host->data_offset), *pdata++); + buf = pdata; + } + /* put anything remaining in the part_buf */ + if (cnt) { + dw_mci_set_part_bytes(host, buf, cnt); + if (!sg_next(host->sg)) + mci_writew(host, DATA(host->data_offset), + host->part_buf16); + } +} + +static void dw_mci_pull_data16(struct dw_mci *host, void *buf, int cnt) +{ +#ifndef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS + if (unlikely((unsigned long)buf & 0x1)) { + while (cnt >= 2) { + /* pull data from fifo into aligned buffer */ + u16 aligned_buf[64]; + int len = min(cnt & -2, (int)sizeof(aligned_buf)); + int items = len >> 1; + int i; + for (i = 0; i < items; ++i) + aligned_buf[i] = mci_readw(host, + DATA(host->data_offset)); + /* memcpy from aligned buffer into output buffer */ + memcpy(buf, aligned_buf, len); + buf += len; + cnt -= len; + } + } else +#endif + { + u16 *pdata = buf; + for (; cnt >= 2; cnt -= 2) + *pdata++ = mci_readw(host, DATA(host->data_offset)); + buf = pdata; + } + if (cnt) { + host->part_buf16 = mci_readw(host, DATA(host->data_offset)); + dw_mci_pull_final_bytes(host, buf, cnt); + } +} + +static void dw_mci_push_data32(struct dw_mci *host, void *buf, int cnt) +{ + /* try and push anything in the part_buf */ + if (unlikely(host->part_buf_count)) { + int len = dw_mci_push_part_bytes(host, buf, cnt); + buf += len; + cnt -= len; + if (!sg_next(host->sg) || host->part_buf_count == 4) { + mci_writel(host, DATA(host->data_offset), + host->part_buf32); + host->part_buf_count = 0; + } + } +#ifndef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS + if (unlikely((unsigned long)buf & 0x3)) { + while (cnt >= 4) { + u32 aligned_buf[32]; + int len = min(cnt & -4, (int)sizeof(aligned_buf)); + int items = len >> 2; + int i; + /* memcpy from input buffer into aligned buffer */ + memcpy(aligned_buf, buf, len); + buf += len; + cnt -= len; + /* push data from aligned buffer into fifo */ + for (i = 0; i < items; ++i) + mci_writel(host, DATA(host->data_offset), + aligned_buf[i]); + } + } else +#endif + { + u32 *pdata = buf; + for (; cnt >= 4; cnt -= 4) + mci_writel(host, DATA(host->data_offset), *pdata++); + buf = pdata; + } + /* put anything remaining in the part_buf */ + if (cnt) { + dw_mci_set_part_bytes(host, buf, cnt); + if (!sg_next(host->sg)) + mci_writel(host, DATA(host->data_offset), + host->part_buf32); + } +} + +static void dw_mci_pull_data32(struct dw_mci *host, void *buf, int cnt) +{ +#ifndef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS + if (unlikely((unsigned long)buf & 0x3)) { + while (cnt >= 4) { + /* pull data from fifo into aligned buffer */ + u32 aligned_buf[32]; + int len = min(cnt & -4, (int)sizeof(aligned_buf)); + int items = len >> 2; + int i; + for (i = 0; i < items; ++i) + aligned_buf[i] = mci_readl(host, + DATA(host->data_offset)); + /* memcpy from aligned buffer into output buffer */ + memcpy(buf, aligned_buf, len); + buf += len; + cnt -= len; + } + } else +#endif + { + u32 *pdata = buf; + for (; cnt >= 4; cnt -= 4) + *pdata++ = mci_readl(host, DATA(host->data_offset)); + buf = pdata; + } + if (cnt) { + host->part_buf32 = mci_readl(host, DATA(host->data_offset)); + dw_mci_pull_final_bytes(host, buf, cnt); + } +} + +static void dw_mci_push_data64(struct dw_mci *host, void *buf, int cnt) +{ + /* try and push anything in the part_buf */ + if (unlikely(host->part_buf_count)) { + int len = dw_mci_push_part_bytes(host, buf, cnt); + buf += len; + cnt -= len; + if (!sg_next(host->sg) || host->part_buf_count == 8) { + mci_writew(host, DATA(host->data_offset), + host->part_buf); + host->part_buf_count = 0; + } + } +#ifndef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS + if (unlikely((unsigned long)buf & 0x7)) { + while (cnt >= 8) { + u64 aligned_buf[16]; + int len = min(cnt & -8, (int)sizeof(aligned_buf)); + int items = len >> 3; + int i; + /* memcpy from input buffer into aligned buffer */ + memcpy(aligned_buf, buf, len); + buf += len; + cnt -= len; + /* push data from aligned buffer into fifo */ + for (i = 0; i < items; ++i) + mci_writeq(host, DATA(host->data_offset), + aligned_buf[i]); + } + } else +#endif + { + u64 *pdata = buf; + for (; cnt >= 8; cnt -= 8) + mci_writeq(host, DATA(host->data_offset), *pdata++); + buf = pdata; + } + /* put anything remaining in the part_buf */ + if (cnt) { + dw_mci_set_part_bytes(host, buf, cnt); + if (!sg_next(host->sg)) + mci_writeq(host, DATA(host->data_offset), + host->part_buf); + } +} + +static void dw_mci_pull_data64(struct dw_mci *host, void *buf, int cnt) +{ +#ifndef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS + if (unlikely((unsigned long)buf & 0x7)) { + while (cnt >= 8) { + /* pull data from fifo into aligned buffer */ + u64 aligned_buf[16]; + int len = min(cnt & -8, (int)sizeof(aligned_buf)); + int items = len >> 3; + int i; + for (i = 0; i < items; ++i) + aligned_buf[i] = mci_readq(host, + DATA(host->data_offset)); + /* memcpy from aligned buffer into output buffer */ + memcpy(buf, aligned_buf, len); + buf += len; + cnt -= len; + } + } else +#endif + { + u64 *pdata = buf; + for (; cnt >= 8; cnt -= 8) + *pdata++ = mci_readq(host, DATA(host->data_offset)); + buf = pdata; + } + if (cnt) { + host->part_buf = mci_readq(host, DATA(host->data_offset)); + dw_mci_pull_final_bytes(host, buf, cnt); + } +} + +static void dw_mci_pull_data(struct dw_mci *host, void *buf, int cnt) +{ + int len; + + /* get remaining partial bytes */ + len = dw_mci_pull_part_bytes(host, buf, cnt); + if (unlikely(len == cnt)) + return; + buf += len; + cnt -= len; + + /* get the rest of the data */ + host->pull_data(host, buf, cnt); +} + +static void dw_mci_read_data_pio(struct dw_mci *host) +{ + struct sg_mapping_iter *sg_miter = &host->sg_miter; + void *buf; + unsigned int offset; + struct mmc_data *data = host->data; + int shift = host->data_shift; + u32 status; + unsigned int nbytes = 0, len; + unsigned int remain, fcnt; + + do { + if (!sg_miter_next(sg_miter)) + goto done; + + host->sg = sg_miter->__sg; + buf = sg_miter->addr; + remain = sg_miter->length; + offset = 0; + + do { + fcnt = (SDMMC_GET_FCNT(mci_readl(host, STATUS)) + << shift) + host->part_buf_count; + len = min(remain, fcnt); + if (!len) + break; + dw_mci_pull_data(host, (void *)(buf + offset), len); + offset += len; + nbytes += len; + remain -= len; + } while (remain); + sg_miter->consumed = 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; + sg_miter_stop(sg_miter); + host->sg = NULL; + 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*/ + data->bytes_xfered += nbytes; + + if (!remain) { + if (!sg_miter_next(sg_miter)) + goto done; + sg_miter->consumed = 0; + } + sg_miter_stop(sg_miter); + return; + +done: + data->bytes_xfered += nbytes; + sg_miter_stop(sg_miter); + host->sg = NULL; + smp_wmb(); + set_bit(EVENT_XFER_COMPLETE, &host->pending_events); +} + +static void dw_mci_write_data_pio(struct dw_mci *host) +{ + struct sg_mapping_iter *sg_miter = &host->sg_miter; + void *buf; + unsigned int offset; + struct mmc_data *data = host->data; + int shift = host->data_shift; + u32 status; + unsigned int nbytes = 0, len; + unsigned int fifo_depth = host->fifo_depth; + unsigned int remain, fcnt; + + do { + if (!sg_miter_next(sg_miter)) + goto done; + + host->sg = sg_miter->__sg; + buf = sg_miter->addr; + remain = sg_miter->length; + offset = 0; + + do { + fcnt = ((fifo_depth - + SDMMC_GET_FCNT(mci_readl(host, STATUS))) + << shift) - host->part_buf_count; + len = min(remain, fcnt); + if (!len) + break; + host->push_data(host, (void *)(buf + offset), len); + offset += len; + nbytes += len; + remain -= len; + } while (remain); + sg_miter->consumed = 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; + sg_miter_stop(sg_miter); + host->sg = NULL; + + smp_wmb(); + + set_bit(EVENT_DATA_ERROR, &host->pending_events); + + tasklet_schedule(&host->tasklet); + return; + } + } while (status & SDMMC_INT_TXDR); /* if TXDR write again */ + data->bytes_xfered += nbytes; + + if (!remain) { + if (!sg_miter_next(sg_miter)) + goto done; + sg_miter->consumed = 0; + } + sg_miter_stop(sg_miter); + return; + +done: + data->bytes_xfered += nbytes; + sg_miter_stop(sg_miter); + host->sg = NULL; + 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; + int i; + + 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); + } + + 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); + if (!(pending & (SDMMC_INT_DTO | SDMMC_INT_DCRC | + SDMMC_INT_SBE | SDMMC_INT_EBE))) + 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->dir_status == DW_MCI_RECV_STATUS && host->sg) + dw_mci_read_data_pio(host); + } + + if (pending & SDMMC_INT_TXDR) { + mci_writel(host, RINTSTS, SDMMC_INT_TXDR); + if (host->dir_status == DW_MCI_SEND_STATUS && 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); + queue_work(dw_mci_card_workqueue, &host->card_work); + } + + /* Handle SDIO Interrupts */ + for (i = 0; i < host->num_slots; i++) { + struct dw_mci_slot *slot = host->slot[i]; + if (pending & SDMMC_INT_SDIO(i)) { + mci_writel(host, RINTSTS, SDMMC_INT_SDIO(i)); + mmc_signal_sdio_irq(slot->mmc); + } + } + + } 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_work_routine_card(struct work_struct *work) +{ + struct dw_mci *host = container_of(work, struct dw_mci, card_work); + 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) { + dev_dbg(&slot->mmc->class_dev, "card %s\n", + present ? "inserted" : "removed"); + + /* Power up slot (before spin_lock, may sleep) */ + if (present != 0 && host->pdata->setpower) + host->pdata->setpower(slot->id, mmc->ocr_avail); + + spin_lock_bh(&host->lock); + + /* Card change detected */ + slot->last_detect_state = present; + + /* Mark card as present if applicable */ + if (present != 0) + 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) { + 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. + */ + sg_miter_stop(&host->sg_miter); + 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_bh(&host->lock); + + /* Power down slot (after spin_unlock, may sleep) */ + if (present == 0 && host->pdata->setpower) + host->pdata->setpower(slot->id, 0); + + 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->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; + + if (host->pdata->caps2) + mmc->caps2 = host->pdata->caps2; + + 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 | MMC_CAP_MMC_HIGHSPEED; + + if (mmc->caps2 & MMC_CAP2_POWEROFF_NOTIFY) + mmc->power_notify_type = MMC_HOST_PW_NOTIFY_SHORT; + else + mmc->power_notify_type = MMC_HOST_PW_NOTIFY_NONE; + + 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. */ +#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 + 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)) { + pr_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 + */ + queue_work(dw_mci_card_workqueue, &host->card_work); + + 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->dev, PAGE_SIZE, + &host->sg_dma, GFP_KERNEL); + if (!host->sg_cpu) { + dev_err(&host->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->dev, "Using internal DMA controller.\n"); +#endif + + if (!host->dma_ops) + goto no_dma; + + if (host->dma_ops->init && host->dma_ops->start && + host->dma_ops->stop && host->dma_ops->cleanup) { + if (host->dma_ops->init(host)) { + dev_err(&host->dev, "%s: Unable to initialize " + "DMA Controller.\n", __func__); + goto no_dma; + } + } else { + dev_err(&host->dev, "DMA initialization not found.\n"); + goto no_dma; + } + + host->use_dma = 1; + return; + +no_dma: + dev_info(&host->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; +} + +int dw_mci_probe(struct dw_mci *host) +{ + int width, i, ret = 0; + u32 fifo_size; + + if (!host->pdata || !host->pdata->init) { + dev_err(&host->dev, + "Platform data must supply init function\n"); + return -ENODEV; + } + + if (!host->pdata->select_slot && host->pdata->num_slots > 1) { + dev_err(&host->dev, + "Platform data must supply select_slot function\n"); + return -ENODEV; + } + + if (!host->pdata->bus_hz) { + dev_err(&host->dev, + "Platform data must supply bus speed\n"); + return -ENODEV; + } + + host->bus_hz = host->pdata->bus_hz; + host->quirks = host->pdata->quirks; + + spin_lock_init(&host->lock); + INIT_LIST_HEAD(&host->queue); + + + host->dma_ops = host->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(&host->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 + */ + if (!host->pdata->fifo_depth) { + /* + * Power-on value of RX_WMark is FIFO_DEPTH-1, but this may + * have been overwritten by the bootloader, just like we're + * about to do, so if you know the value for your hardware, you + * should put it in the platform data. + */ + fifo_size = mci_readl(host, FIFOTH); + fifo_size = 1 + ((fifo_size >> 16) & 0xfff); + } else { + fifo_size = host->pdata->fifo_depth; + } + host->fifo_depth = fifo_size; + 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); + dw_mci_card_workqueue = alloc_workqueue("dw-mci-card", + WQ_MEM_RECLAIM | WQ_NON_REENTRANT, 1); + if (!dw_mci_card_workqueue) + goto err_dmaunmap; + INIT_WORK(&host->card_work, dw_mci_work_routine_card); + ret = request_irq(host->irq, dw_mci_interrupt, host->irq_flags, "dw-mci", host); + if (ret) + goto err_workqueue; + + 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; + } + } + + /* + * In 2.40a spec, Data offset is changed. + * Need to check the version-id and set data-offset for DATA register. + */ + host->verid = SDMMC_GET_VERID(mci_readl(host, VERID)); + dev_info(&host->dev, "Version ID is %04x\n", host->verid); + + if (host->verid < DW_MMC_240A) + host->data_offset = DATA_OFFSET; + else + host->data_offset = DATA_240A_OFFSET; + + /* + * 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(&host->dev, "DW MMC controller at irq %d, " + "%d bit host data width, " + "%u deep fifo\n", + host->irq, width, fifo_size); + if (host->quirks & DW_MCI_QUIRK_IDMAC_DTO) + dev_info(&host->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(host->irq, host); + +err_workqueue: + destroy_workqueue(dw_mci_card_workqueue); + +err_dmaunmap: + if (host->use_dma && host->dma_ops->exit) + host->dma_ops->exit(host); + dma_free_coherent(&host->dev, PAGE_SIZE, + host->sg_cpu, host->sg_dma); + + if (host->vmmc) { + regulator_disable(host->vmmc); + regulator_put(host->vmmc); + } + return ret; +} +EXPORT_SYMBOL(dw_mci_probe); + +void dw_mci_remove(struct dw_mci *host) +{ + int i; + + mci_writel(host, RINTSTS, 0xFFFFFFFF); + mci_writel(host, INTMASK, 0); /* disable all mmc interrupt first */ + + for (i = 0; i < host->num_slots; i++) { + dev_dbg(&host->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(host->irq, host); + destroy_workqueue(dw_mci_card_workqueue); + dma_free_coherent(&host->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); + } + +} +EXPORT_SYMBOL(dw_mci_remove); + + + +#ifdef CONFIG_PM_SLEEP +/* + * TODO: we should probably disable the clock to the card in the suspend path. + */ +int dw_mci_suspend(struct dw_mci *host) +{ + int i, ret = 0; + + 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; +} +EXPORT_SYMBOL(dw_mci_suspend); + +int dw_mci_resume(struct dw_mci *host) +{ + int i, ret; + + if (host->vmmc) + regulator_enable(host->vmmc); + + if (host->dma_ops->init) + host->dma_ops->init(host); + + if (!mci_wait_reset(&host->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; +} +EXPORT_SYMBOL(dw_mci_resume); +#endif /* CONFIG_PM_SLEEP */ + +static int __init dw_mci_init(void) +{ + printk(KERN_INFO "Synopsys Designware Multimedia Card Interface Driver"); + return 0; +} + +static void __exit dw_mci_exit(void) +{ +} + +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..15c27e17 --- /dev/null +++ b/drivers/mmc/host/dw_mmc.h @@ -0,0 +1,185 @@ +/* + * 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 DW_MMC_240A 0x240a + +#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(x) (x) + +/* + * Data offset is difference according to Version + * Lower than 2.40a : data register offest is 0x100 + */ +#define DATA_OFFSET 0x100 +#define DATA_240A_OFFSET 0x200 + +/* 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(n) BIT(16 + (n)) +#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) & 0x1FFF) +/* 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) +/* Version ID register define */ +#define SDMMC_GET_VERID(x) ((x) & 0xFFFF) + +/* 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 + +extern int dw_mci_probe(struct dw_mci *host); +extern void dw_mci_remove(struct dw_mci *host); +#ifdef CONFIG_PM +extern int dw_mci_suspend(struct dw_mci *host); +extern int dw_mci_resume(struct dw_mci *host); +#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..ea0f3ced --- /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; + + pr_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..c8852a81 --- /dev/null +++ b/drivers/mmc/host/jz4740_mmc.c @@ -0,0 +1,1019 @@ +/* + * 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, + }, +}; + +module_platform_driver(jz4740_mmc_driver); + +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_atsmb.c b/drivers/mmc/host/mmc_atsmb.c new file mode 100755 index 00000000..150b7b12 --- /dev/null +++ b/drivers/mmc/host/mmc_atsmb.c @@ -0,0 +1,2837 @@ +/*++ +linux/drivers/mmc/host/mmc_atsmb.c + +Copyright (c) 2008 WonderMedia Technologies, 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, see <http://www.gnu.org/licenses/>. + +WonderMedia Technologies, Inc. +10F, 529, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C. +--*/ + +//#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/blkdev.h> +#include <linux/mmc/host.h> +#include <linux/mmc/card.h> +#include <linux/mmc/sd.h> +#include <linux/mmc/mmc.h> +#include <linux/mmc/sdio.h> +#include <linux/completion.h> +#include <linux/pagemap.h> +#include <linux/dma-mapping.h> +#include <asm/dma.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/memory.h> +#include <mach/hardware.h> +#include <asm/scatterlist.h> +#include <asm/sizes.h> +#include "mmc_atsmb.h" +//#include <mach/multicard.h> +#include <mach/irqs.h> +#include <linux/regulator/consumer.h> + +#define mprintk + +#if 0 +#define DBG(host, fmt, args...) \ + printk("%s: %s: " fmt, mmc_hostname(host->mmc), __func__ , args) +#endif +#define ATSMB_TIMEOUT_TIME (HZ*2) + + +static struct delayed_work mmc_work; + +static int g_atsmb_regulator; +static struct regulator *atsmb_regulator; + + +//add by jay,for modules support +static u64 wmt_sdmmc_dma_mask = 0xffffffffUL; +static struct resource wmt_sdmmc_resources[] = { + [0] = { + .start = SD0_SDIO_MMC_BASE_ADDR, + .end = (SD0_SDIO_MMC_BASE_ADDR + 0x3FF), + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = IRQ_SDC0, + .end = IRQ_SDC0, + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = IRQ_SDC0_DMA, + .end = IRQ_SDC0_DMA, + .flags = IORESOURCE_IRQ, + }, + /*2008/10/6 RichardHsu-s*/ + [3] = { + .start = IRQ_PMC_WAKEUP, + .end = IRQ_PMC_WAKEUP, + .flags = IORESOURCE_IRQ, + }, + /*2008/10/6 RichardHsu-e*/ +}; + +struct kobject *atsmb_kobj; +struct mmc_host *mmc_host_attr = NULL; +// +static void atsmb_release(struct device * dev) {} + +/*#ifdef CONFIG_MMC_DEBUG*/ +#define MORE_INFO +#if 0 +#define DBG(x...) printk(KERN_ALERT x) +#define DBGR(x...) do { } while (0) +#else +#define DBG(x...) do { } while (0) +#define DBGR(x...) do { } while (0) +#endif + +/* when Read CRC error occur, and clear read CRC error by software reset.*/ +void atsmb_copy_reg(int direct) +{ + static u8 CTL, CMD_IDX, RSP_TYPE, BUS_MODE, INT_MASK_0, INT_MASK_1, SD_STS_0, SD_STS_1, SD_STS_2, SD_STS_3; + static u8 EXT_BUS_MODE, EXT_CTL_1, EXT_CTL_2, MAN_TUNE_VAL, SD_WRI_TUNE; + static u8 RSP_0, RSP_1, RSP_2, RSP_3, RSP_4, RSP_5, RSP_6, RSP_7; + static u8 RSP_8, RSP_9, RSP_10, RSP_11, RSP_12, RSP_13, RSP_14, RSP_15; + static u8 RSP_TOUT, CLK_SEL, EXT_CTL; + static u16 BLK_LEN, BLK_CNT, SHDW_BLKLEN, TIMER_VAL, CTL2; + static u32 CMD_ARG, CURBLK_CNT; + + /*direct 0: copy register to memory; 1: copy memory to register.*/ + if (direct == 0) { + CTL = *ATSMB0_CTL; + CMD_IDX = *ATSMB0_CMD_IDX; + RSP_TYPE = *ATSMB0_RSP_TYPE; + CMD_ARG = *ATSMB0_CMD_ARG; + BUS_MODE = *ATSMB0_BUS_MODE; + EXT_BUS_MODE = *ATSMB0_EXT_BUS_MODE; + CTL2 = *ATSMB0_CTL2; + BLK_LEN = *ATSMB0_BLK_LEN; + BLK_CNT = *ATSMB0_BLK_CNT; + RSP_0 = *ATSMB0_RSP_0; + RSP_1 = *ATSMB0_RSP_1; + RSP_2 = *ATSMB0_RSP_2; + RSP_3 = *ATSMB0_RSP_3; + RSP_4 = *ATSMB0_RSP_4; + RSP_5 = *ATSMB0_RSP_5; + RSP_6 = *ATSMB0_RSP_6; + RSP_7 = *ATSMB0_RSP_7; + RSP_8 = *ATSMB0_RSP_8; + RSP_9 = *ATSMB0_RSP_9; + RSP_10 = *ATSMB0_RSP_10; + RSP_11 = *ATSMB0_RSP_11; + RSP_12 = *ATSMB0_RSP_12; + RSP_13 = *ATSMB0_RSP_13; + RSP_14 = *ATSMB0_RSP_14; + RSP_15 = *ATSMB0_RSP_15; + CURBLK_CNT = *ATSMB0_CURBLK_CNT; + INT_MASK_0 = *ATSMB0_INT_MASK_0; + INT_MASK_1 = *ATSMB0_INT_MASK_1; + SD_STS_0 = *ATSMB0_SD_STS_0; + SD_STS_1 = *ATSMB0_SD_STS_1; + SD_STS_2 = *ATSMB0_SD_STS_2; + SD_STS_3 = *ATSMB0_SD_STS_3; + RSP_TOUT = *ATSMB0_RSP_TOUT; + CLK_SEL = *ATSMB0_CLK_SEL; + EXT_CTL = *ATSMB0_EXT_CTL; + EXT_CTL_1 = *ATSMB0_EXT_CTL_1; + EXT_CTL_2 = *ATSMB0_EXT_CTL_2; + SHDW_BLKLEN = *ATSMB0_SHDW_BLKLEN; + MAN_TUNE_VAL = *ATSMB0_MAN_TUNE_VAL; + SD_WRI_TUNE = *ATSMB0_SD_WRI_TUNE; + TIMER_VAL = *ATSMB0_TIMER_VAL; + } else { + *ATSMB0_CTL = CTL; + *ATSMB0_CMD_IDX = CMD_IDX; + *ATSMB0_RSP_TYPE = RSP_TYPE; + *ATSMB0_CMD_ARG = CMD_ARG; + *ATSMB0_BUS_MODE = BUS_MODE; + *ATSMB0_EXT_BUS_MODE = EXT_BUS_MODE; + *ATSMB0_CTL2 = CTL2; + *ATSMB0_BLK_LEN = BLK_LEN; + *ATSMB0_BLK_CNT = BLK_CNT; + *ATSMB0_RSP_0 = RSP_0; + *ATSMB0_RSP_1 = RSP_1; + *ATSMB0_RSP_2 = RSP_2; + *ATSMB0_RSP_3 = RSP_3; + *ATSMB0_RSP_4 = RSP_4; + *ATSMB0_RSP_5 = RSP_5; + *ATSMB0_RSP_6 = RSP_6; + *ATSMB0_RSP_7 = RSP_7; + *ATSMB0_RSP_8 = RSP_8; + *ATSMB0_RSP_9 = RSP_9; + *ATSMB0_RSP_10 = RSP_10; + *ATSMB0_RSP_11 = RSP_11; + *ATSMB0_RSP_12 = RSP_12; + *ATSMB0_RSP_13 = RSP_13; + *ATSMB0_RSP_14 = RSP_14; + *ATSMB0_RSP_15 = RSP_15; + *ATSMB0_CURBLK_CNT = CURBLK_CNT; + *ATSMB0_INT_MASK_0 = INT_MASK_0; + *ATSMB0_INT_MASK_1 = INT_MASK_1; + *ATSMB0_SD_STS_0 = SD_STS_0; + *ATSMB0_SD_STS_1 = SD_STS_1; + *ATSMB0_SD_STS_2 = SD_STS_2; + *ATSMB0_SD_STS_3 = SD_STS_3; + *ATSMB0_RSP_TOUT = RSP_TOUT; + *ATSMB0_CLK_SEL = CLK_SEL; + *ATSMB0_EXT_CTL = EXT_CTL; + *ATSMB0_EXT_CTL_1 = EXT_CTL_1; + *ATSMB0_EXT_CTL_2 = EXT_CTL_2; + *ATSMB0_SHDW_BLKLEN = SHDW_BLKLEN; + *ATSMB0_MAN_TUNE_VAL = MAN_TUNE_VAL; + *ATSMB0_SD_WRI_TUNE = SD_WRI_TUNE; + *ATSMB0_TIMER_VAL = TIMER_VAL; + } +} + +#if 0 +void atsmb_dump_reg(void) +{ + u8 CTL, CMD_IDX, RSP_TYPE, BUS_MODE, INT_MASK_0, INT_MASK_1, SD_STS_0, SD_STS_1, SD_STS_2, SD_STS_3; + u8 EXT_BUS_MODE, EXT_CTL_1, EXT_CTL_2, MAN_TUNE_VAL, SD_WRI_TUNE; + u8 RSP_0, RSP_1, RSP_2, RSP_3, RSP_4, RSP_5, RSP_6, RSP_7; + u8 RSP_8, RSP_9, RSP_10, RSP_11, RSP_12, RSP_13, RSP_14, RSP_15; + u8 RSP_TOUT, CLK_SEL, EXT_CTL; + u16 BLK_LEN, BLK_CNT, SHDW_BLKLEN, TIMER_VAL, CTL2; + u32 CMD_ARG, PDMA_GCR, PDMA_IER, PDMA_ISR, PDMA_DESPR, PDMA_RBR, PDMA_DAR, PDMA_BAR, PDMA_CPR, PDMA_CCR; + u32 CURBLK_CNT; + + CTL = *ATSMB0_CTL; + CMD_IDX = *ATSMB0_CMD_IDX; + RSP_TYPE = *ATSMB0_RSP_TYPE; + CMD_ARG = *ATSMB0_CMD_ARG; + BUS_MODE = *ATSMB0_BUS_MODE; + EXT_BUS_MODE = *ATSMB0_EXT_BUS_MODE; + CTL2 = *ATSMB0_CTL2; + BLK_LEN = *ATSMB0_BLK_LEN; + BLK_CNT = *ATSMB0_BLK_CNT; + RSP_0 = *ATSMB0_RSP_0; + RSP_1 = *ATSMB0_RSP_1; + RSP_2 = *ATSMB0_RSP_2; + RSP_3 = *ATSMB0_RSP_3; + RSP_4 = *ATSMB0_RSP_4; + RSP_5 = *ATSMB0_RSP_5; + RSP_6 = *ATSMB0_RSP_6; + RSP_7 = *ATSMB0_RSP_7; + RSP_8 = *ATSMB0_RSP_8; + RSP_9 = *ATSMB0_RSP_9; + RSP_10 = *ATSMB0_RSP_10; + RSP_11 = *ATSMB0_RSP_11; + RSP_12 = *ATSMB0_RSP_12; + RSP_13 = *ATSMB0_RSP_13; + RSP_14 = *ATSMB0_RSP_14; + RSP_15 = *ATSMB0_RSP_15; + CURBLK_CNT = *ATSMB0_CURBLK_CNT; + INT_MASK_0 = *ATSMB0_INT_MASK_0; + INT_MASK_1 = *ATSMB0_INT_MASK_1; + SD_STS_0 = *ATSMB0_SD_STS_0; + SD_STS_1 = *ATSMB0_SD_STS_1; + SD_STS_2 = *ATSMB0_SD_STS_2; + SD_STS_3 = *ATSMB0_SD_STS_3; + RSP_TOUT = *ATSMB0_RSP_TOUT; + CLK_SEL = *ATSMB0_CLK_SEL; + EXT_CTL = *ATSMB0_EXT_CTL; + EXT_CTL_1 = *ATSMB0_EXT_CTL_1; + EXT_CTL_2 = *ATSMB0_EXT_CTL_2; + SHDW_BLKLEN = *ATSMB0_SHDW_BLKLEN; + MAN_TUNE_VAL = *ATSMB0_MAN_TUNE_VAL; + SD_WRI_TUNE = *ATSMB0_SD_WRI_TUNE; + TIMER_VAL = *ATSMB0_TIMER_VAL; + + PDMA_GCR = *ATSMB0_PDMA_GCR; + PDMA_IER = *ATSMB0_PDMA_IER; + PDMA_ISR = *ATSMB0_PDMA_ISR; + PDMA_DESPR = *ATSMB0_PDMA_DESPR; + PDMA_RBR = *ATSMB0_PDMA_RBR; + PDMA_DAR = *ATSMB0_PDMA_DAR; + PDMA_BAR = *ATSMB0_PDMA_BAR; + PDMA_CPR = *ATSMB0_PDMA_CPR; + PDMA_CCR = *ATSMB0_PDMA_CCR; + + DBGR("\n+---------------------------Registers----------------------------+\n"); + + DBGR("%16s = 0x%8x |", "CTL", CTL); + DBGR("%16s = 0x%8x\n", "CMD_IDX", CMD_IDX); + + DBGR("%16s = 0x%8x |", "RSP_TYPE", RSP_TYPE); + DBGR("%16s = 0x%8x\n", "CMD_ARG", CMD_ARG); + + DBGR("%16s = 0x%8x |", "BUS_MODE", BUS_MODE); + DBGR("%16s = 0x%8x\n", "EXT_BUS_MODE", EXT_BUS_MODE); + + DBGR("%16s = 0x%8x |", "CTL2", CTL2); + DBGR("%16s = 0x%8x\n", "BLK_LEN", BLK_LEN); + + DBGR("%16s = 0x%8x |", "BLK_CNT", BLK_CNT); + DBGR("%16s = 0x%8x\n", "RSP_0", RSP_0); + + DBGR("%16s = 0x%8x |", "RSP_1", RSP_1); + DBGR("%16s = 0x%8x\n", "RSP_2", RSP_2); + + DBGR("%16s = 0x%8x |", "RSP_3", RSP_3); + DBGR("%16s = 0x%8x\n", "RSP_4", RSP_4); + + DBGR("%16s = 0x%8x |", "RSP_5", RSP_5); + DBGR("%16s = 0x%8x\n", "RSP_6", RSP_6); + + DBGR("%16s = 0x%8x |", "RSP_7", RSP_7); + DBGR("%16s = 0x%8x\n", "RSP_8", RSP_8); + + DBGR("%16s = 0x%8x |", "RSP_9", RSP_9); + DBGR("%16s = 0x%8x\n", "RSP_10", RSP_10); + + DBGR("%16s = 0x%8x |", "RSP_11", RSP_11); + DBGR("%16s = 0x%8x\n", "RSP_12", RSP_12); + + DBGR("%16s = 0x%8x |", "RSP_13", RSP_13); + DBGR("%16s = 0x%8x\n", "RSP_14", RSP_14); + + DBGR("%16s = 0x%8x\n", "RSP_15", RSP_15); + + DBGR("%16s = 0x%8x |", "CURBLK_CNT", CURBLK_CNT); + DBGR("%16s = 0x%8x\n", "INT_MASK_0", INT_MASK_0); + + DBGR("%16s = 0x%8x |", "INT_MASK_1", INT_MASK_1); + DBGR("%16s = 0x%8x\n", "SD_STS_0", SD_STS_0); + + DBGR("%16s = 0x%8x |", "SD_STS_1", SD_STS_1); + DBGR("%16s = 0x%8x\n", "SD_STS_2", SD_STS_2); + + DBGR("%16s = 0x%8x |", "SD_STS_3", SD_STS_3); + DBGR("%16s = 0x%8x\n", "RSP_TOUT", RSP_TOUT); + + DBGR("%16s = 0x%8x |", "CLK_SEL", CLK_SEL); + DBGR("%16s = 0x%8x\n", "EXT_CTL", EXT_CTL); + + DBGR("%16s = 0x%8x |", "EXT_CTL_1", EXT_CTL_1); + DBGR("%16s = 0x%8x\n", "EXT_CTL_2", EXT_CTL_2); + + DBGR("%16s = 0x%8x |", "SHDW_BLKLEN", SHDW_BLKLEN); + DBGR("%16s = 0x%8x\n", "MAN_TUNE_VAL", MAN_TUNE_VAL); + + DBGR("%16s = 0x%8x |", "SD_WRI_TUNE", SD_WRI_TUNE); + DBGR("%16s = 0x%8x\n", "TIMER_VAL", TIMER_VAL); + + DBGR("%16s = 0x%8x |", "PDMA_GCR", PDMA_GCR); + DBGR("%16s = 0x%8x\n", "PDMA_IER", PDMA_IER); + + DBGR("%16s = 0x%8x |", "PDMA_ISR", PDMA_ISR); + DBGR("%16s = 0x%8x\n", "PDMA_DESPR", PDMA_DESPR); + + DBGR("%16s = 0x%8x |", "PDMA_RBR", PDMA_RBR); + DBGR("%16s = 0x%8x\n", "PDMA_DAR", PDMA_DAR); + + DBGR("%16s = 0x%8x |", "PDMA_BAR", PDMA_BAR); + DBGR("%16s = 0x%8x\n", "PDMA_CPR", PDMA_CPR); + + DBGR("%16s = 0x%8x |", "PDMA_CCR", PDMA_CCR); + DBGR("\n+----------------------------------------------------------------+\n"); +} +#else +void atsmb_dump_reg(void) {} +#endif + +unsigned int fmax0 = 515633; +unsigned int MMC0_DRIVER_VERSION; +int SD0_function = 0; /*0: normal SD/MMC card reader , 1: internal SDIO wifi*/ +int SDXC0_function; +static int SD0_detect_pol = 0; +static int SD0_detect_pulldown = 0; +static int SD0_speed = 0; +//add by kevin guan +int SD0_ro_disable = 0; + +int SCC_ID(void){ + unsigned short val; + + val = REG16_VAL(SYSTEM_CFG_CTRL_BASE_ADDR + 0x2); + return val; +} + +int get_chip_version(void) /*2008/05/01 janshiue modify for A1 chip*/ +{ + u32 tmp; + + tmp = REG32_VAL(SYSTEM_CFG_CTRL_BASE_ADDR); + tmp = ((tmp & 0xF00) >> 4) + 0x90 + ((tmp & 0xFF) - 1); + return tmp; +} + +void get_driver_version(void) +{ + if (SCC_ID() == 0x3426) { + if (get_chip_version() < 0xA1) + MMC0_DRIVER_VERSION = MMC_DRV_3426_A0; + else if (get_chip_version() == 0xA1) + MMC0_DRIVER_VERSION = MMC_DRV_3426_A1; + else + MMC0_DRIVER_VERSION = MMC_DRV_3426_A2; + } else if (SCC_ID() == 0x3437) { + if (get_chip_version() < 0xA1) + MMC0_DRIVER_VERSION = MMC_DRV_3437_A0; + else + MMC0_DRIVER_VERSION = MMC_DRV_3437_A1; + } else if (SCC_ID() == 0x3429) { + MMC0_DRIVER_VERSION = MMC_DRV_3429; + } else if (SCC_ID() == 0x3451) { + if (get_chip_version() < 0xA1) + MMC0_DRIVER_VERSION = MMC_DRV_3451_A0; + } else if (SCC_ID() == 0x3465) { + MMC0_DRIVER_VERSION = MMC_DRV_3465; + } else if (SCC_ID() == 0x3445) { + MMC0_DRIVER_VERSION = MMC_DRV_3445; + } else if (SCC_ID() == 0x3481) { + MMC0_DRIVER_VERSION = MMC_DRV_3481; + } else if (SCC_ID() == 0x3498) { + MMC0_DRIVER_VERSION = MMC_DRV_3498; + } +} +/*2008/10/6 RichardHsu-e*/ + +/********************************************************************** +Name : atsmb_alloc_desc +Function : To config PDMA descriptor setting. +Calls : +Called by : +Parameter : +Author : Janshiue Wu +History : +***********************************************************************/ +static inline int atsmb_alloc_desc(struct atsmb_host *host, + unsigned int bytes) +{ + void *DescPool = NULL; + DBG("[%s] s\n",__func__); + + DescPool = dma_alloc_coherent(host->mmc->parent, bytes, &(host->DescPhyAddr), GFP_KERNEL); + if (!DescPool) { + DBG("can't allocal desc pool=%8X %8X\n", DescPool, host->DescPhyAddr); + DBG("[%s] e1\n",__func__); + return -1; + } + DBG("allocal desc pool=%8X %8X\n", DescPool, host->DescPhyAddr); + host->DescVirAddr = (unsigned long *)DescPool; + host->DescSize = bytes; + DBG("[%s] e2\n",__func__); + return 0; +} + +/********************************************************************** +Name : atsmb_config_desc +Function : To config PDMA descriptor setting. +Calls : +Called by : +Parameter : +Author : Janshiue Wu +History : +***********************************************************************/ +static inline void atsmb_config_desc(unsigned long *DescAddr, + unsigned long *BufferAddr, + unsigned long Blk_Cnt) +{ + + int i = 0 ; + unsigned long *CurDes = DescAddr; + DBG("[%s] s\n",__func__); + + /* the first Descriptor store for 1 Block data + * (512 bytes) + */ + for (i = 0 ; i < 3 ; i++) { + atsmb_init_short_desc(CurDes, 0x80, BufferAddr, 0); + BufferAddr += 0x20; + CurDes += sizeof(struct SD_PDMA_DESC_S)/4; + } + if (Blk_Cnt > 1) { + atsmb_init_long_desc(CurDes, 0x80, BufferAddr, CurDes + (sizeof(struct SD_PDMA_DESC_L)/4), 0); + BufferAddr += 0x20; + CurDes += sizeof(struct SD_PDMA_DESC_L)/4; + /* the following Descriptor store the rest Blocks data + * (Blk_Cnt - 1) * 512 bytes + */ + atsmb_init_long_desc(CurDes, (Blk_Cnt - 1) * 512, + BufferAddr, CurDes + (sizeof(struct SD_PDMA_DESC_L)/4), 1); + } else { + atsmb_init_long_desc(CurDes, 0x80, BufferAddr, CurDes + (sizeof(struct SD_PDMA_DESC_L)/4), 1); + } + DBG("[%s] e\n",__func__); +} +/********************************************************************** +Name : atsmb_config_dma +Function : To set des/src address, byte count to transfer, and DMA channel settings, + and DMA ctrl. register. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static inline void atsmb_config_dma(unsigned long config_dir, + unsigned long dma_mask, + struct atsmb_host *host) +{ + + DBG("[%s] s\n",__func__); + /* Enable DMA */ + *ATSMB0_PDMA_GCR = SD_PDMA_GCR_DMA_EN; + *ATSMB0_PDMA_GCR = SD_PDMA_GCR_SOFTRESET; + *ATSMB0_PDMA_GCR = SD_PDMA_GCR_DMA_EN; + /*open interrupt*/ + *ATSMB0_PDMA_IER = SD_PDMA_IER_INT_EN; + /*Make sure host could co-work with DMA*/ + *ATSMB0_SD_STS_2 |= ATSMB_CLK_FREEZ_EN; + + /*Set timer timeout value*/ + /*If clock is 390KHz*/ + if (host->current_clock < 400000) + *ATSMB0_TIMER_VAL = 0x200; /*1024*512*(1/390K) second*/ + else + *ATSMB0_TIMER_VAL = 0x1fff; /*why not to be 0xffff?*/ + + /*clear all DMA INT status for safety*/ + *ATSMB0_PDMA_ISR |= SD_PDMA_IER_INT_STS; + + /* hook desc */ + *ATSMB0_PDMA_DESPR = host->DescPhyAddr; + if (config_dir == DMA_CFG_WRITE) + *ATSMB0_PDMA_CCR &= SD_PDMA_CCR_IF_to_peripheral; + else + *ATSMB0_PDMA_CCR |= SD_PDMA_CCR_peripheral_to_IF; + + host->DmaIntMask = dma_mask; /*save success event*/ + + *ATSMB0_PDMA_CCR |= SD_PDMA_CCR_RUN; + DBG("[%s] e\n",__func__); +} + +/********************************************************************** +Name : atsmb_set_dma_end +Function : To set all descriptor end bit for removing card when PDMA + don't transfer data done. let PDMA transfer done +Calls : +Called by : +Parameter : +Author : Eason Chien +History : 2013/05/16 +***********************************************************************/ +void atsmb_set_dma_end(unsigned long *DescAddr, unsigned int bytes) +{ + int i = 0; + struct SD_PDMA_DESC_S *CurDes_S; + CurDes_S = (struct SD_PDMA_DESC_S *) DescAddr; + DBG("[%s] s\n", __func__); + DBG("[%s] byte = %d, size of %d\n", __func__, bytes, sizeof(struct SD_PDMA_DESC_S)); + + CurDes_S += 255; + + for (i = 255; i >=0 ;i--) { + /* Set end bit of all descriptor and let PDMA finish as soon as possible */ + CurDes_S->end = 1; + DBG("[%s] %d CurDes 0x%x, 0x%x, e=%x\n", __func__, i, CurDes_S, *CurDes_S); + CurDes_S -= 1; + } + DBG("[%s] e\n", __func__); +} + +/********************************************************************** +Name : atsmb_prep_cmd +Function : +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static inline void atsmb_prep_cmd(struct atsmb_host *host, + u32 opcode, + u32 arg, + unsigned int flags, + u16 blk_len, + u16 blk_cnt, + unsigned char int_maks_0, + unsigned char int_mask_1, + unsigned char cmd_type, + unsigned char op) +{ + DBG("[%s] s\n",__func__); + DBG("[%s] s opcode = %u arg = 0x%x flags = 0x%x\n",__func__,opcode,arg,flags); + /*set cmd operation code and arguments.*/ + host->opcode = opcode; + *ATSMB0_CMD_IDX = opcode; /* host->opcode is set for further use in ISR.*/ + *ATSMB0_CMD_ARG = arg; + +#if 0 /* Fixme to support SPI mode, James Tian*/ + if ((flags && MMC_RSP_NONE) == MMC_RSP_NONE) + *ATSMB0_RSP_TYPE = ATMSB_TYPE_R0; + else if ((flags && MMC_RSP_R1) == MMC_RSP_R1) + *ATSMB0_RSP_TYPE = ATMSB_TYPE_R1; + else if ((flags && MMC_RSP_R1B) == MMC_RSP_R1B) + *ATSMB0_RSP_TYPE = ATMSB_TYPE_R1b; + else if ((flags && MMC_RSP_R2) == MMC_RSP_R2) + *ATSMB0_RSP_TYPE = ATMSB_TYPE_R2; + else if ((flags && MMC_RSP_R3) == MMC_RSP_R3) + *ATSMB0_RSP_TYPE = ATMSB_TYPE_R3; + else if ((flags && MMC_RSP_R6) == MMC_RSP_R6) + *ATSMB0_RSP_TYPE = ((opcode != SD_SEND_IF_COND) ? ATMSB_TYPE_R6 : ATMSB_TYPE_R7); + else + *ATSMB0_RSP_TYPE = ATMSB_TYPE_R0; +#endif + +#if 1 + /*set cmd response type*/ + switch (flags) { + case MMC_RSP_NONE | MMC_CMD_AC: + case MMC_RSP_NONE | MMC_CMD_BC: + *ATSMB0_RSP_TYPE = ATMSB_TYPE_R0; + break; + case MMC_RSP_R1 | MMC_CMD_ADTC: + case MMC_RSP_R1 | MMC_CMD_AC: + *ATSMB0_RSP_TYPE = ATMSB_TYPE_R1; + break; + case MMC_RSP_R1B | MMC_CMD_AC: + *ATSMB0_RSP_TYPE = ATMSB_TYPE_R1b; + break; + case MMC_RSP_R2 | MMC_CMD_BCR: + case MMC_RSP_R2 | MMC_CMD_AC: + *ATSMB0_RSP_TYPE = ATMSB_TYPE_R2; + break; + case MMC_RSP_R3 | MMC_CMD_BCR: + *ATSMB0_RSP_TYPE = ATMSB_TYPE_R3; + break; + case MMC_RSP_R6 | MMC_CMD_BCR: /*MMC_RSP_R6 = MMC_RSP_R7.*/ + *ATSMB0_RSP_TYPE = ((opcode != SD_SEND_IF_COND) ? + ATMSB_TYPE_R6 : ATMSB_TYPE_R7); + break; + case MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_AC: + case MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC: + *ATSMB0_RSP_TYPE = ATMSB_TYPE_R5; + break; + case MMC_RSP_SPI_R4 | MMC_RSP_R4 | MMC_CMD_BCR: + *ATSMB0_RSP_TYPE = ATMSB_TYPE_R4; + break; + default: + *ATSMB0_RSP_TYPE = ATMSB_TYPE_R0; + break; + } +#endif + /*SDIO cmd 52 , 53*/ + if ((opcode == SD_IO_RW_DIRECT) || + (opcode == SD_IO_RW_EXTENDED)) { + *ATSMB0_RSP_TYPE = ATMSB_TYPE_R5; + *ATSMB0_RSP_TYPE |= BIT6; + } + /*SDIO cmd 5*/ + if ((opcode == SD_IO_SEND_OP_COND) && + ((flags & (MMC_RSP_PRESENT| + MMC_RSP_136| + MMC_RSP_CRC| + MMC_RSP_BUSY| + MMC_RSP_OPCODE)) == MMC_RSP_R4)) { + *ATSMB0_RSP_TYPE = ATMSB_TYPE_R4; + *ATSMB0_RSP_TYPE |= BIT6; + } + + /*reset Response FIFO*/ + *ATSMB0_CTL |= 0x08; + + /* SD Host enable Clock */ + *ATSMB0_BUS_MODE |= ATSMB_CST; + + /*Set Cmd-Rsp Timeout to be maximum value.*/ + *ATSMB0_RSP_TOUT = 0xFE; + + /*clear all status registers for safety*/ + *ATSMB0_SD_STS_0 |= 0xff; + *ATSMB0_SD_STS_1 |= 0xff; + *ATSMB0_SD_STS_2 |= 0xff; + //*ATSMB0_SD_STS_2 |= 0x7f; + *ATSMB0_SD_STS_3 |= 0xff; + + //set block length and block count for cmd requesting data + *ATSMB0_BLK_LEN &=~(0x0fff); + *ATSMB0_BLK_LEN |= blk_len; + //*ATSMB0_SHDW_BLKLEN = blk_len; + *ATSMB0_BLK_CNT = blk_cnt; + + + *ATSMB0_INT_MASK_0 |= int_maks_0; + *ATSMB0_INT_MASK_1 |= int_mask_1; + + //Set Auto stop for Multi-block access + if(cmd_type == 3 || cmd_type == 4) { + //auto stop command set. + *ATSMB0_EXT_CTL |= 0x01; + +/* + * Enable transaction abort. + * When CRC error occurs, CMD 12 would be automatically issued. + * That is why we cannot enable R/W CRC error INTs. + * If we enable CRC error INT, we would handle this INT in ISR and then issue CMD 12 via software. + */ +/* Don't auto issue stop command when write command error, +* because auto stop command can freezs PDMA. msut let PDMA transfer done. +* 2013/05/15 by Eason +*/ + if (cmd_type == 4) + *ATSMB0_BLK_LEN |= 0x0800; + } + + /*Set read or write*/ + if (op == 1) + *ATSMB0_CTL &= ~(0x04); + else if (op == 2) + *ATSMB0_CTL |= 0x04; + + /*for Non data access command, command type is 0.*/ + *ATSMB0_CTL &= 0x0F; + *ATSMB0_CTL |= (cmd_type<<4); + DBG("[%s] e\n",__func__); +} + +static inline void atsmb_issue_cmd(void) +{ + *ATSMB0_CTL |= ATSMB_START; + wmb(); +} +/********************************************************************** +Name : atsmb_request_end +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static void +atsmb_request_end(struct atsmb_host *host, struct mmc_request *mrq) +{ + DBG("[%s] s\n",__func__); + /* + * Need to drop the host lock here; mmc_request_done may call + * back into the driver... + */ + //kevin delete spin lock + //spin_unlock(&host->lock); + /*DBG("100");*/ + mmc_request_done(host->mmc, mrq); + /*DBG("101\n");*/ + //kevin delete spin lock + //spin_lock(&host->lock); + DBG("[%s] e\n",__func__); +} + +/********************************************************************** +Name : atsmb_data_done +Function : +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +void atsmb_wait_done(void *data) +{ + struct atsmb_host *host = (struct atsmb_host *) data; + DBG("[%s] s\n",__func__); + + WARN_ON(host->done_data == NULL); + complete(host->done_data); + host->done_data = NULL; + host->done = NULL; + DBG("[%s] e\n",__func__); +} + +/********************************************************************** +Name : atsmb_start_data +Function : If we start data, there must be only four cases. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static void atsmb_start_data(struct atsmb_host *host) +{ + DECLARE_COMPLETION(complete); + unsigned char cmd_type = 0; + unsigned char op = 0; /*0: non-operation; 1:read; 2: write*/ + unsigned char mask_0 = 0; + unsigned char mask_1 = 0; + unsigned long dma_mask = 0; + + struct mmc_data *data = host->data; + struct mmc_command *cmd = host->cmd; + + struct scatterlist *sg = NULL; + unsigned int sg_len = 0; + + unsigned int total_blks = 0; /*total block number to transfer*/ + u32 card_addr = 0; + unsigned long dma_len = 0; + unsigned long total_dma_len = 0; + dma_addr_t dma_phy = 0; /* physical address used for DMA*/ + unsigned int dma_times = 0; /*times dma need to transfer*/ + unsigned int dma_loop = 0; + unsigned int sg_num = 0; + int loop_cnt = 10000; + unsigned int sg_transfer_len = 0; /*record each time dma transfer sg length */ + + DBG("[%s] s\n",__func__); + data->bytes_xfered = 0; + cmd->error = 0; + data->error = 0; + + /*for loop*/ + sg = data->sg; + sg_len = data->sg_len; + + dma_times = (sg_len / MAX_DESC_NUM); + if (sg_len % MAX_DESC_NUM) + dma_times++; + DBG("dma_times = %d sg_len = %d sg = %x\n", dma_times, sg_len, sg); + card_addr = cmd->arg; /*may be it is block-addressed, or byte-addressed.*/ + total_blks = data->blocks; + dma_map_sg(&(host->mmc->class_dev), sg, sg_len, + ((data->flags)&MMC_DATA_READ) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + + + for (dma_loop = 1 ; dma_loop <= dma_times; dma_loop++, sg_len -= sg_transfer_len) { + DBG("dma_loop = %d sg_len = %d sg_transfer_len = %d\n", dma_loop, sg_len, sg_transfer_len); + if (dma_loop < dma_times) + sg_transfer_len = MAX_DESC_NUM; + else + sg_transfer_len = sg_len; + DBG("sg_transfer_len = %d\n", sg_transfer_len); + /* + *Firstly, check and wait till card is in the transfer state. + *For our hardware, we can not consider + *the card has successfully tranfered its state from data/rcv to trans, + *when auto stop INT occurs. + */ + loop_cnt = 10000; + do { + if (host->cmd->opcode == SD_IO_RW_EXTENDED) + break; + loop_cnt--; + WARN_ON(loop_cnt == 0); + host->done_data = &complete; + host->done = &atsmb_wait_done; + host->soft_timeout = 1; + atsmb_prep_cmd(host, + MMC_SEND_STATUS, + (host->mmc->card->rca)<<16, + MMC_RSP_R1 | MMC_CMD_AC, + 0, + 0, + 0, /*mask_0*/ + ATSMB_RSP_DONE_EN + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN, + 0, /*cmd type*/ + 0); /*read or write*/ + atsmb_issue_cmd(); + DBG("%16s = 0x%8x |", "INT_MASK_1", *ATSMB0_INT_MASK_1); + DBG("%16s = 0x%8x \n", "SD_STS_1", *ATSMB0_SD_STS_1); + /*ISR would completes it.*/ + wait_for_completion_timeout(&complete, ATSMB_TIMEOUT_TIME); + + WARN_ON(host->soft_timeout == 1); + if (host->soft_timeout == 1) { + DBG("%s soft_timeout.\n", __func__); + atsmb_dump_reg(); + } + if (cmd->error != MMC_ERR_NONE) { + cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("Getting Status failed.\n"); + goto end; + } + } while ((cmd->resp[0] & 0x1f00) != 0x900 && loop_cnt > 0); /*wait for trans state.*/ + + /* + * Now, we can safely issue read/write command. + * We can not consider this request as multi-block acess or single one via opcode, + * as request is splitted into sgs. + * Some sgs may be single one, some sgs may be multi one. + */ + + dma_phy = sg_dma_address(sg); + dma_len = sg_dma_len(sg); + DBG("dma_len = %d data->blksz = %d sg_len = %d\n", dma_len, data->blksz, sg_len); + /*SDIO read/write*/ + if (host->cmd->opcode == SD_IO_RW_EXTENDED) { + /*Single Block read/write*/ + if ((dma_len / (data->blksz)) == 1 && (sg_len == 1)) { + /* read operation*/ + if (data->flags & MMC_DATA_READ) { + host->opcode = SD_IO_RW_EXTENDED; + cmd_type = 2; + op = 1; + mask_0 = 0; /*BLOCK_XFER_DONE INT skipped, we use DMA TC INT*/ + mask_1 = (//ATSMB_SDIO_EN + ATSMB_READ_CRC_ERR_EN + |ATSMB_DATA_TIMEOUT_EN + /*Data Timeout and CRC error */ + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN); + /*Resp Timeout and CRC error */ + dma_mask = SD_PDMA_CCR_Evt_success; + DBG("[%s]SR opcode = %d type = 0x%x op = 0x%x m_0 = 0x%x m_1 = 0x%x d_m = 0x%x\n", + __func__,host->opcode,cmd_type,op,mask_0,mask_1,dma_mask); + } else { + /* write operation*/ + host->opcode = SD_IO_RW_EXTENDED; + cmd_type = 1; + op = 2; + /*====That is what we want===== DMA TC INT skipped*/ + mask_0 = ATSMB_BLOCK_XFER_DONE_EN; + mask_1 = (//ATSMB_SDIO_EN + ATSMB_WRITE_CRC_ERR_EN + |ATSMB_DATA_TIMEOUT_EN + /*Data Timeout and CRC error */ + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN); + /*Resp Timeout and CRC error */ + dma_mask = 0; + DBG("[%s]SW opcode = %d type = 0x%x op = 0x%x m_0 = 0x%x m_1 = 0x%x d_m = 0x%x\n", + __func__,host->opcode,cmd_type,op,mask_0,mask_1,dma_mask); + } + } else { + /*Multiple Block read/write*/ + /* read operation*/ + if (data->flags & MMC_DATA_READ) { + host->opcode = SD_IO_RW_EXTENDED; + cmd_type = 6; + op = 1; + mask_0 = 0; /*MULTI_XFER_DONE_EN skipped*/ + mask_1 = (//ATSMB_SDIO_EN /*====That is what we want=====*/ + ATSMB_READ_CRC_ERR_EN + |ATSMB_DATA_TIMEOUT_EN + /*Data Timeout and CRC error */ + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN); + /*Resp Timeout and CRC error */ + dma_mask = SD_PDMA_CCR_Evt_success; + DBG("[%s]MR opcode = %d type = 0x%x op = 0x%x m_0 = 0x%x m_1 = 0x%x d_m = 0x%x\n", + __func__,host->opcode,cmd_type,op,mask_0,mask_1,dma_mask); + } else { + /* write operation*/ + host->opcode = SD_IO_RW_EXTENDED; + cmd_type = 5; + op = 2; + mask_0 = ATSMB_MULTI_XFER_DONE_EN;//ATSMB_BLOCK_XFER_DONE_EN; /*MULTI_XFER_DONE INT skipped*/ + mask_1 = (//ATSMB_SDIO_EN /*====That is what we want=====*/ + ATSMB_WRITE_CRC_ERR_EN + |ATSMB_DATA_TIMEOUT_EN + /*Data Timeout and CRC error */ + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN); + /*Resp Timeout and CRC error */ + dma_mask = 0; + DBG("[%s]MR opcode = %d type = 0x%x op = 0x%x m_0 = 0x%x m_1 = 0x%x d_m = 0x%x\n", + __func__,host->opcode,cmd_type,op,mask_0,mask_1,dma_mask); + } + } + + } else { + if ((dma_len / (data->blksz)) == 1 && (sg_len == 1)) { + if (data->flags & MMC_DATA_READ) {/* read operation*/ + host->opcode = MMC_READ_SINGLE_BLOCK; + cmd_type = 2; + op = 1; + mask_0 = 0; /*BLOCK_XFER_DONE INT skipped, we use DMA TC INT*/ + mask_1 = (ATSMB_READ_CRC_ERR_EN + |ATSMB_DATA_TIMEOUT_EN + /*Data Timeout and CRC error */ + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN); + /*Resp Timeout and CRC error */ + dma_mask = SD_PDMA_CCR_Evt_success; + } else {/*write operation*/ + host->opcode = MMC_WRITE_BLOCK; + cmd_type = 1; + op = 2; + /*====That is what we want===== DMA TC INT skipped*/ + mask_0 = ATSMB_BLOCK_XFER_DONE_EN; + mask_1 = (ATSMB_WRITE_CRC_ERR_EN + |ATSMB_DATA_TIMEOUT_EN + /*Data Timeout and CRC error */ + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN); + /*Resp Timeout and CRC error */ + dma_mask = 0; + } + } else { /*more than one*/ + if (data->flags&MMC_DATA_READ) {/* read operation*/ + host->opcode = MMC_READ_MULTIPLE_BLOCK; + cmd_type = 4; + op = 1; + mask_0 = 0; /*MULTI_XFER_DONE_EN skipped*/ + mask_1 = (ATSMB_AUTO_STOP_EN /*====That is what we want=====*/ + |ATSMB_DATA_TIMEOUT_EN + /*Data Timeout and CRC error */ + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN); + /*Resp Timeout and CRC error */ + dma_mask = 0; + } else {/*write operation*/ + host->opcode = MMC_WRITE_MULTIPLE_BLOCK; + cmd_type = 3; + op = 2; + mask_0 = 0; /*MULTI_XFER_DONE INT skipped*/ + mask_1 = (ATSMB_AUTO_STOP_EN /*====That is what we want=====*/ + |ATSMB_DATA_TIMEOUT_EN + /*Data Timeout and CRC error */ + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN); + /*Resp Timeout and CRC error */ + dma_mask = 0; + } + } + } + /*To controller every sg done*/ + host->done_data = &complete; + host->done = &atsmb_wait_done; + /*sleep till ISR wakes us*/ + host->soft_timeout = 1; /*If INT comes early than software timer, it would be cleared.*/ + + total_dma_len = 0; + DBG("host->DescVirAddr = %x host->DescSize=%x\n", host->DescVirAddr, host->DescSize); + memset(host->DescVirAddr, 0, host->DescSize); + for (sg_num = 0 ; sg_num < sg_transfer_len ; sg++, sg_num++) { + + /* + * Now, we can safely issue read/write command. + * We can not consider this request as multi-block acess or single one via opcode, + * as request is splitted into sgs. + * Some sgs may be single one, some sgs may be multi one. + */ + + dma_phy = sg_dma_address(sg); + dma_len = sg_dma_len(sg); + total_dma_len = total_dma_len + dma_len; + DBG("sg_num=%d sg_transfer_len=%d sg=%x sg_len=%d total_dma_len=%d dma_len=%d\n", + sg_num, sg_transfer_len, sg, sg_len, total_dma_len, dma_len); + /*2009/01/15 janshiue add*/ + if (sg_num < sg_transfer_len - 1) { + /* means the last descporitor */ + atsmb_init_short_desc( + host->DescVirAddr + (sg_num * sizeof(struct SD_PDMA_DESC_S)/4), + dma_len, (unsigned long *)dma_phy, 0); + } else { + atsmb_init_short_desc( + host->DescVirAddr + (sg_num * sizeof(struct SD_PDMA_DESC_S)/4), + dma_len, (unsigned long *)dma_phy, 1); + } + /*2009/01/15 janshiue add*/ + + } + /*operate our hardware*/ + atsmb_prep_cmd(host, + host->opcode, + /*arg, may be byte addressed, may be block addressed.*/ + card_addr, + cmd->flags, + data->blksz - 1, /* in fact, it is useless.*/ + /* for single one, it is useless. but for multi one, */ + /* it would be used to tell auto stop function whether it is done.*/ + total_dma_len/(data->blksz), + mask_0, + mask_1, + cmd_type, + op); + + atsmb_config_dma((op == 1) ? DMA_CFG_READ : DMA_CFG_WRITE, + dma_mask, + host); + + atsmb_issue_cmd(); + wait_for_completion_timeout(&complete, + ATSMB_TIMEOUT_TIME*sg_transfer_len); /*ISR would completes it.*/ + + /* When the address of request plus length equal card bound, + * force this stop command response as pass. Eason 2012/4/20 */ + if (cmd->resp[0] == 0x80000b00) { + /*This caes used for SD2.0 and after MMC4.1 version*/ + if (card_addr+(total_dma_len/data->blksz) == host->mmc->card->csd.capacity) { + cmd->resp[0] = 0x00000b00; + /*printk("card_addr = %08X, card_length = %08X,card capacity = %08X\n", + card_addr,(total_dma_len/data->blksz),host->mmc->card->csd.capacity); + printk("card_resp[0]=%08X, addr = %08X\n",cmd->resp[0],cmd->resp);*/ + } + + /* This caes used for SD1.0 and before MMC 4.1 */ + if ((card_addr/data->blksz)+(total_dma_len/data->blksz) == host->mmc->card->csd.capacity) { + cmd->resp[0] = 0x00000b00; + /*printk("Eason test: cmd->arg = %08X, data->blksz = %08X, length = %08X\n", + card_addr,data->blksz,(total_dma_len/data->blksz));*/ + } + } + + if (host->soft_timeout == 1) { + atsmb_dump_reg(); + //*ATSMB0_BUS_MODE |= ATSMB_SFTRST; + *ATSMB0_PDMA_GCR = SD_PDMA_GCR_SOFTRESET; + /*disable INT */ + *ATSMB0_INT_MASK_0 &= ~(ATSMB_BLOCK_XFER_DONE_EN | ATSMB_MULTI_XFER_DONE_EN); + *ATSMB0_INT_MASK_1 &= ~(ATSMB_WRITE_CRC_ERR_EN|ATSMB_READ_CRC_ERR_EN|ATSMB_RSP_CRC_ERR_EN + |ATSMB_DATA_TIMEOUT_EN|ATSMB_AUTO_STOP_EN|ATSMB_RSP_TIMEOUT_EN|ATSMB_RSP_DONE_EN); + + cmd->error = -ETIMEDOUT; + data->error = -ETIMEDOUT; + } + + WARN_ON(host->soft_timeout == 1); + + /*check everything goes okay or not*/ + if (cmd->error != MMC_ERR_NONE + || data->error != MMC_ERR_NONE) { + DBG("CMD or Data failed error=%X DescVirAddr=%8X dma_phy=%8X dma_mask = %x\n", + cmd->error, host->DescVirAddr, dma_phy, host->DmaIntMask); + goto end; + } +// card_addr += total_dma_len>>(mmc_card_blockaddr(host->mmc->card_selected) ? 9 : 0); + card_addr += total_dma_len>>(mmc_card_blockaddr(host->mmc->card) ? 9 : 0); //zhf: modified by James Tian + data->bytes_xfered += total_dma_len; + + + } +// dma_unmap_sg(&(host->mmc->class_dev), data->sg, data->sg_len, +// ((data->flags)&MMC_DATA_READ) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + + WARN_ON(total_blks != (data->bytes_xfered / data->blksz)); + host->opcode = 0; +end: + dma_unmap_sg(&(host->mmc->class_dev), data->sg, data->sg_len, + ((data->flags)&MMC_DATA_READ) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + spin_lock(&host->lock); + atsmb_request_end(host, host->mrq); + spin_unlock(&host->lock); + DBG("[%s] e\n",__func__); +} + + +/********************************************************************** +Name : atsmb_cmd_with_data_back +Function : +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static void atsmb_cmd_with_data_back(struct atsmb_host *host) +{ + DECLARE_COMPLETION(complete); + + struct scatterlist *sg = NULL; + unsigned int sg_len = 0; + DBG("[%s] s\n",__func__); + /*for loop*/ + sg = host->data->sg; + sg_len = host->data->sg_len; + /*To controller every sg done*/ + host->done_data = &complete; + host->done = &atsmb_wait_done; + dma_map_sg(&(host->mmc->class_dev), sg, sg_len, DMA_FROM_DEVICE); + + /*2009/01/15 janshiue add*/ + memset(host->DescVirAddr, 0, host->DescSize); + atsmb_init_long_desc(host->DescVirAddr, sg_dma_len(sg), (unsigned long *)sg_dma_address(sg), 0, 1); + /*2009/01/15 janshiue add*/ + /*prepare for cmd*/ + atsmb_prep_cmd(host, /*host*/ + host->cmd->opcode, /*opcode*/ + host->cmd->arg, /*arg*/ + host->cmd->flags, /*flags*/ + sg_dma_len(sg)-1, /*block length*/ + 0, /*block size: It looks like useless*/ + 0, /*int_mask_0*/ + (ATSMB_RSP_CRC_ERR_EN|ATSMB_RSP_TIMEOUT_EN + |ATSMB_READ_CRC_ERR_EN |ATSMB_DATA_TIMEOUT_EN), /*int_mask_1*/ + 2, /*cmd_type*/ + 1); /*op*/ + + atsmb_config_dma(DMA_CFG_READ, + SD_PDMA_CCR_Evt_success, + host); + atsmb_issue_cmd(); + /*ISR would completes it.*/ + wait_for_completion_timeout(&complete, ATSMB_TIMEOUT_TIME); + + dma_unmap_sg(&(host->mmc->class_dev), host->data->sg, host->data->sg_len, + ((host->data->flags)&MMC_DATA_READ) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + spin_lock(&host->lock); + atsmb_request_end(host, host->mrq); + spin_unlock(&host->lock); + DBG("[%s] e\n",__func__); +} +/********************************************************************** +Name : atsmb_start_cmd +Function : +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static void atsmb_start_cmd(struct atsmb_host *host) +{ + unsigned char int_mask_0,int_mask_1; + int_mask_0 = 0; + int_mask_1 = ATSMB_RSP_DONE_EN|ATSMB_RSP_CRC_ERR_EN|ATSMB_RSP_TIMEOUT_EN; + + DBG("[%s] s\n",__func__); + + atsmb_prep_cmd(host, + host->cmd->opcode, + host->cmd->arg, + host->cmd->flags, + 0, /*useless*/ + 0, /*useless*/ + int_mask_0, /*mask_0*/ + int_mask_1, /*mask_1*/ + 0, /*cmd type*/ + 0); /*read or write*/ + atsmb_issue_cmd(); + DBG("[%s] e\n",__func__); +} +/********************************************************************** +Name : atsmb_fmt_check_rsp +Function : +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static inline void atsmb_fmt_check_rsp(struct atsmb_host *host) +{ + + u8 tmp_resp[4] = {0}; + int i, j, k; + DBG("[%s] s\n",__func__); + if (host->cmd->flags != (MMC_RSP_R2 | MMC_CMD_AC) + && host->cmd->flags != (MMC_RSP_R2 | MMC_CMD_BCR)) { + for (j = 0, k = 1; j < 4; j++, k++) + tmp_resp[j] = *(REG8_PTR(_RSP_0 + MMC_ATSMB0_BASE+k)); + + host->cmd->resp[0] = (tmp_resp[0] << 24) | + (tmp_resp[1]<<16) | (tmp_resp[2]<<8) | (tmp_resp[3]); + } else { + for (i = 0, k = 1; i < 4; i++) { /*R2 has 4 u32 response.*/ + for (j = 0; j < 4; j++) { + if (k < 16) + tmp_resp[j] = *(REG8_PTR(_RSP_0 + MMC_ATSMB0_BASE+k)); + else /* k =16*/ + tmp_resp[j] = *(ATSMB0_RSP_0); + k++; + } + host->cmd->resp[i] = (tmp_resp[0]<<24) | (tmp_resp[1]<<16) | + (tmp_resp[2]<<8) | (tmp_resp[3]); + } + } + + /* + * For the situation that we need response, + * but response registers give us all zeros, we consider this operation timeout. + */ + if (host->cmd->flags != (MMC_RSP_NONE | MMC_CMD_AC) + && host->cmd->flags != (MMC_RSP_NONE | MMC_CMD_BC)) { + if (host->cmd->resp[0] == 0 && host->cmd->resp[1] == 0 + && host->cmd->resp[2] == 0 && host->cmd->resp[3] == 0) { + host->cmd->error = -ETIMEDOUT; //zhf: modified by James Tian + } + } + DBG("[%s] e\n",__func__); +} +/********************************************************************** +Name : atsmb_get_slot_status +Function : Our host only supports one slot. +Calls : +Called by : +Parameter : +returns : 1: in slot; 0: not in slot. +Author : Leo Lee +History : +***********************************************************************/ +static int atsmb_get_slot_status(struct mmc_host *mmc) +{ +// struct atsmb_host *host = mmc_priv(mmc); + unsigned char status_0 = 0; +// unsigned long flags; + unsigned long ret = 0; + DBG("[%s] s\n",__func__); +// spin_lock_irqsave(&host->lock, flags); // Vincent Li mark out for CONFIG_PREEMPT_RT + status_0 = *ATSMB0_SD_STS_0; +// spin_unlock_irqrestore(&host->lock, flags); // Vincent Li mark out for CONFIG_PREEMPT_RT + /* after WM3400 A1 ATSMB_CARD_IN_SLOT_GPI = 1 means not in slot*/ + if (MMC0_DRIVER_VERSION >= MMC_DRV_3426_A0) { + if(SD0_detect_pol) + ret = ((status_0 & ATSMB_CARD_NOT_IN_SLOT_GPI) ? 1 : 0); + else + ret = ((status_0 & ATSMB_CARD_NOT_IN_SLOT_GPI) ? 0 : 1); + + DBG("[%s] e1\n",__func__); + return ret; + } else { + ret = ((status_0 & ATSMB_CARD_NOT_IN_SLOT_GPI) ? 1 : 0); + DBG("[%s] e2\n",__func__); + return ret; + } + DBG("[%s] e3\n",__func__); + return 0; +} + +/********************************************************************** +Name : atsmb_init_short_desc +Function : +Calls : +Called by : +Parameter : +Author : Janshiue Wu +History : +***********************************************************************/ +int atsmb_init_short_desc(unsigned long *DescAddr, unsigned int ReqCount, unsigned long *BufferAddr, int End) +{ + struct SD_PDMA_DESC_S *CurDes_S; + DBG("[%s] s\n",__func__); + CurDes_S = (struct SD_PDMA_DESC_S *) DescAddr; + CurDes_S->ReqCount = ReqCount; + CurDes_S->i = 0; + CurDes_S->format = 0; + CurDes_S->DataBufferAddr = BufferAddr; + if (End) { + CurDes_S->end = 1; + CurDes_S->i = 1; + } + DBG("[%s] e\n",__func__); + return 0; +} + +/********************************************************************** +Name : atsmb_init_long_desc +Function : +Calls : +Called by : +Parameter : +Author : Janshiue Wu +History : +***********************************************************************/ +int atsmb_init_long_desc(unsigned long *DescAddr, unsigned int ReqCount, + unsigned long *BufferAddr, unsigned long *BranchAddr, int End) +{ + struct SD_PDMA_DESC_L *CurDes_L; + DBG("[%s] s\n",__func__); + CurDes_L = (struct SD_PDMA_DESC_L *) DescAddr; + CurDes_L->ReqCount = ReqCount; + CurDes_L->i = 0; + CurDes_L->format = 1; + CurDes_L->DataBufferAddr = BufferAddr; + CurDes_L->BranchAddr = BranchAddr; + if (End) { + CurDes_L->end = 1; + CurDes_L->i = 1; + } + DBG("[%s] e\n",__func__); + return 0; +} + +/********************************************************************** +Name : atsmb_dma_isr +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static irqreturn_t atsmb_dma_isr(int irq, void *dev_id) +{ + + struct atsmb_host *host = dev_id; + u8 status_0, status_1, status_2, status_3; + u32 pdma_event_code = 0; + DBG("[%s] s\n",__func__); + + disable_irq_nosync(irq); + spin_lock(&host->lock); + /*Get INT status*/ + status_0 = *ATSMB0_SD_STS_0; + status_1 = *ATSMB0_SD_STS_1; + status_2 = *ATSMB0_SD_STS_2; + status_3 = *ATSMB0_SD_STS_3; + /* after WM3426 A0 using PDMA */ + if (MMC0_DRIVER_VERSION >= MMC_DRV_3426_A0) { + + pdma_event_code = *ATSMB0_PDMA_CCR & SD_PDMA_CCR_EvtCode; + + /* clear INT status to notify HW clear EventCode*/ + *ATSMB0_PDMA_ISR |= SD_PDMA_IER_INT_STS; + + /*printk("dma_isr event code = %X\n", *ATSMB0_PDMA_CCR);*/ + /*We expect cmd with data sending back or read single block cmd run here.*/ + if (pdma_event_code == SD_PDMA_CCR_Evt_success) { + /*means need to update the data->error and cmd->error*/ + if (host->DmaIntMask == SD_PDMA_CCR_Evt_success) { + if (host->data != NULL) { + host->data->error = MMC_ERR_NONE; + host->cmd->error = MMC_ERR_NONE; + } else { + DBG("dma_isr host->data is NULL\n"); + /*disable INT*/ + *ATSMB0_PDMA_IER &= ~SD_PDMA_IER_INT_EN; + goto out; + } + } + /*else do nothing*/ + DBG("dma_isr PDMA OK\n"); + /*atsmb_dump_reg();*/ + } + /*But unluckily, we should also be prepare for all kinds of error situation.*/ + else { + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("** dma_isr PDMA fail** event code = %X\n", *ATSMB0_PDMA_CCR); + atsmb_dump_reg(); + } + + if (host->DmaIntMask == SD_PDMA_CCR_Evt_success) + atsmb_fmt_check_rsp(host); + + /*disable INT*/ + *ATSMB0_PDMA_IER &= ~SD_PDMA_IER_INT_EN; + } + + /*wake up some one who is sleeping.*/ + if ((pdma_event_code != SD_PDMA_CCR_Evt_success) || (host->DmaIntMask == SD_PDMA_CCR_Evt_success)) { + if (host->done_data) {/* We only use done_data when requesting data.*/ + host->soft_timeout = 0; + host->done(host); + } else + atsmb_request_end(host, host->mrq); /*for cmd with data sending back.*/ + } + +out: + spin_unlock(&host->lock); + enable_irq(irq); + DBG("[%s] e\n",__func__); + return IRQ_HANDLED; +} + +/********************************************************************** +Name : atsmb_regular_isr +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +//static irqreturn_t atsmb_regular_isr(int irq, void *dev_id, struct pt_regs *regs) +irqreturn_t atsmb_regular_isr(int irq, void *dev_id) +{ + + struct atsmb_host *host = dev_id; + u8 status_0, status_1, status_2, status_3,mask_0,mask_1; + u32 pdma_sts; + + DBG("[%s] s\n",__func__); + WARN_ON(host == NULL); + + disable_irq_nosync(irq); + spin_lock(&host->lock); + + /*Get INT status*/ + status_0 = *ATSMB0_SD_STS_0; + status_1 = *ATSMB0_SD_STS_1; + status_2 = *ATSMB0_SD_STS_2; + status_3 = *ATSMB0_SD_STS_3; + + mask_0 = *ATSMB0_INT_MASK_0; + mask_1 = *ATSMB0_INT_MASK_1; + /******************************************************* + card insert interrupt + ********************************************************/ + if ((status_0 & ATSMB_DEVICE_INSERT) /*Status Set and IRQ enabled*/ + /*To aviod the situation that we intentionally disable IRQ to do rescan.*/ + && (*ATSMB0_INT_MASK_0 & 0x80)) { + + if (host->mmc->ops->get_slot_status(host->mmc)) { + host->mmc->scan_retry = 3; + host->mmc->card_scan_status = false; + } else { + host->mmc->scan_retry = 0; + host->mmc->card_scan_status = false; + } + + mmc_detect_change(host->mmc, HZ/2); + /*Taipei Side Request: Disable INSERT IRQ when doing rescan.*/ + //*ATSMB0_INT_MASK_0 &= (~0x80);/* or 40?*/ //zhf: marked by James Tian + /*Then we clear the INT status*/ + //iowrite32(ATSMB_DEVICE_INSERT, host->base+_SD_STS_0); + *ATSMB0_SD_STS_0 |= ATSMB_DEVICE_INSERT; + spin_unlock(&host->lock); + enable_irq(irq); + DBG("[%s] e1\n",__func__); + return IRQ_HANDLED; + } + + if ((status_1 & mask_1)& ATSMB_SDIO_INT) { + spin_unlock(&host->lock); + mmc_signal_sdio_irq(host->mmc); + + if (((status_1 & mask_1) == ATSMB_SDIO_INT) && ((status_0 & mask_0) == 0)) { + + enable_irq(irq); + DBG("[%s] e2\n",__func__); + + return IRQ_HANDLED; + } + spin_lock(&host->lock); + } + pdma_sts = *ATSMB0_PDMA_CCR; + + if (((status_0 & mask_0) | (status_1 & mask_1)) == 0) { + spin_unlock(&host->lock); + enable_irq(irq); + DBG("[%s] e3\n",__func__); + return IRQ_HANDLED; + } + /******************************************************* + command interrupt. + *******************************************************/ + /*for write single block*/ + if (host->opcode == MMC_WRITE_BLOCK) { + if ((status_0 & ATSMB_BLOCK_XFER_DONE) + && (status_1 & ATSMB_RSP_DONE)) { + host->data->error = MMC_ERR_NONE; + host->cmd->error = MMC_ERR_NONE; + /* okay, what we want.*/ + } else { + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("[%s] err1\n",__func__); + atsmb_dump_reg(); + } + } else if (host->opcode == MMC_WRITE_MULTIPLE_BLOCK + || host->opcode == MMC_READ_MULTIPLE_BLOCK) { + if ((status_1 & (ATSMB_AUTO_STOP|ATSMB_RSP_DONE)) + && (status_0 & ATSMB_MULTI_XFER_DONE)) { + /*If CRC error occurs, I think this INT would not occrs.*/ + /*okay, that is what we want.*/ + host->data->error = MMC_ERR_NONE; + host->cmd->error = MMC_ERR_NONE; + } else { + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("[%s] err2\n",__func__); + atsmb_dump_reg(); + + } + } else if (host->opcode == MMC_READ_SINGLE_BLOCK) {/* we want DMA TC, run here, must be error.*/ + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("[%s] err3\n",__func__); + atsmb_dump_reg(); + } else if (host->opcode == SD_IO_RW_EXTENDED){ + /*Write operation*/ + if (*ATSMB0_CTL & BIT2) { + if ((*ATSMB0_CTL & 0xf0) == 0x10) { /*single block write*/ + if ((status_0 & ATSMB_BLOCK_XFER_DONE) + && (status_1 & ATSMB_RSP_DONE)) { + host->data->error = MMC_ERR_NONE; + host->cmd->error = MMC_ERR_NONE; + /* okay, what we want.*/ + } else { + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("[%s] err4 status_0 = %x status_1 = %x\n",__func__,status_0,status_1); + } + + } else if ((*ATSMB0_CTL & 0xf0) == 0x50) { + if ((status_0 & ATSMB_MULTI_XFER_DONE) + && (status_1 & ATSMB_RSP_DONE)) { + host->data->error = MMC_ERR_NONE; + host->cmd->error = MMC_ERR_NONE; + /* okay, what we want.*/ + } else { + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("[%s] err4-2 status_0 = %x status_1 = %x\n",__func__,status_0,status_1); + } + } else { + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("[%s] err4-3 status_0 = %x status_1 = %x\n",__func__,status_0,status_1); + } + } + else { + /*Read operation*/ + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("[%s] err5\n",__func__); + } + + + } else { +/* command, not request data*/ +/* the command which need data sending back,*/ +/* like switch_function, send_ext_csd, send_scr, send_num_wr_blocks.*/ +/* NOTICE: we also send status before reading or writing data, so SEND_STATUS should be excluded.*/ + if (host->data && host->opcode != MMC_SEND_STATUS) { + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("[%s] err6\n",__func__); + atsmb_dump_reg(); + } else { /* Just command, no need data sending back.*/ + if (status_1 & ATSMB_RSP_DONE) { + /*Firstly, check data-response is busy or not.*/ + if (host->cmd->flags == (MMC_RSP_R1B | MMC_CMD_AC)) { + int i = 10000; + + while (status_2 & ATSMB_RSP_BUSY) { + status_2 = *ATSMB0_SD_STS_2; + if (--i == 0) + break; + DBG(" IRQ:Status_2 = %d, busy!\n", status_2); + } + if (i == 0) + printk("[MMC driver] Error :SD data-response always busy!"); + } +#if 1 +/*for our host, even no card in slot, for SEND_STATUS also returns no error.*/ +/*The protocol layer depends on SEND_STATUS to check whether card is in slot or not.*/ +/*In fact, we can also avoid this situation by checking the response whether they are all zeros.*/ + if (!atsmb_get_slot_status(host->mmc) && host->opcode == MMC_SEND_STATUS) { + host->cmd->retries = 0; /* No retry.*/ +// host->cmd->error = MMC_ERR_INVALID; + host->cmd->error = -EINVAL; + } else +#endif + host->cmd->error = MMC_ERR_NONE; + } else { + if (status_1 & ATSMB_RSP_TIMEOUT) {/* RSP_Timeout .*/ +// host->cmd->error = MMC_ERR_TIMEOUT; + host->cmd->error = -ETIMEDOUT; + DBG("[%s] err7\n",__func__); + } else {/*or RSP CRC error*/ +// host->cmd->error = MMC_ERR_BADCRC; + host->cmd->error = -EILSEQ; + DBG("[%s] err8\n",__func__); + } + atsmb_dump_reg(); + } + } + } + atsmb_fmt_check_rsp(host); + + /*disable INT */ + *ATSMB0_INT_MASK_0 &= ~(ATSMB_BLOCK_XFER_DONE_EN | ATSMB_MULTI_XFER_DONE_EN); + *ATSMB0_INT_MASK_1 &= ~(ATSMB_WRITE_CRC_ERR_EN|ATSMB_READ_CRC_ERR_EN|ATSMB_RSP_CRC_ERR_EN + |ATSMB_DATA_TIMEOUT_EN|ATSMB_AUTO_STOP_EN|ATSMB_RSP_TIMEOUT_EN|ATSMB_RSP_DONE_EN); + + + /*clear INT status. In fact, we will clear again before issuing new command.*/ + *ATSMB0_SD_STS_0 |= status_0; + *ATSMB0_SD_STS_1 |= status_1; + + /* when read CRC error occur, and the status can't write one to clear. + * To clear read CRC error status , can do software reset. This is HW bug. 2013/3/21*/ + if ((*ATSMB0_SD_STS_1 & BIT6) == 0x40) { + DBG("[%s] host0 CMD%d Read CRC error occur\n",__func__,host->cmd->opcode); + /* Save SD card register */ + atsmb_copy_reg(0); + /* Software reset */ + *ATSMB0_BUS_MODE |= BIT7; + /* restore SD card register */ + atsmb_copy_reg(1); + } + + if (*ATSMB0_PDMA_ISR & SD_PDMA_IER_INT_STS) + *ATSMB0_PDMA_ISR |= SD_PDMA_IER_INT_STS; + + /*wake up some one who is sleeping.*/ + if (host->done_data) { /* We only use done_data when requesting data.*/ + host->soft_timeout = 0; + host->done(host); + } else + atsmb_request_end(host, host->mrq); /*for cmd without data.*/ + + spin_unlock(&host->lock); + enable_irq(irq); + DBG("[%s] e4\n",__func__); + return IRQ_HANDLED; +} +EXPORT_SYMBOL(atsmb_regular_isr); + +/********************************************************************** +Name : atsmb_get_ro +Function :. +Calls : +Called by : +Parameter : +returns : 0 : write protection is disabled. 1: write protection is enabled. +Author : Leo Lee +History : +***********************************************************************/ +int atsmb_get_ro(struct mmc_host *mmc) +{ + struct atsmb_host *host = mmc_priv(mmc); + unsigned char status_0 = 0; + unsigned long flags; + unsigned long ret = 0; + DBG("[%s] s\n",__func__); + //add by kevin guan + if(SD0_ro_disable) + return 0; + spin_lock_irqsave(&host->lock, flags); + status_0 = *ATSMB0_SD_STS_0; + spin_unlock_irqrestore(&host->lock, flags); + DBG("[%s]\nstatus_0 = 0x%x\n", __func__,status_0); + ret = ((status_0 & ATSMB_WRITE_PROTECT) ? 0 : 1); + DBG("[%s] e\n",__func__); + return ret; +} +/********************************************************************** +Name : atsmb_dump_host_regs +Function : +Calls : +Called by : +Parameter : +returns : +Author : Leo Lee +History : +***********************************************************************/ +void atsmb_dump_host_regs(struct mmc_host *mmc) +{ + DBG("[%s] s\n",__func__); + atsmb_dump_reg(); + DBG("[%s] e\n",__func__); +} +EXPORT_SYMBOL(atsmb_dump_host_regs); + +/********************************************************************** +Name : atsmb_enable_sdio_irq +Function : +Calls : +Called by : +Parameter : +returns : +Author : Tommy Huang +History : +***********************************************************************/ +static void atsmb_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + struct atsmb_host *host = mmc_priv(mmc); + unsigned long flags; + + DBG("[%s] s enable = %d *ATSMB0_INT_MASK_1 = %x\n",__func__,enable,*ATSMB0_INT_MASK_1); + spin_lock_irqsave(&host->lock, flags); + + if (enable) { + *ATSMB0_INT_MASK_1 |= ATSMB_SDIO_EN; + } else { + *ATSMB0_INT_MASK_1 &= ~ATSMB_SDIO_EN; + } + + spin_unlock_irqrestore(&host->lock, flags); + DBG("[%s] e\n",__func__); + +} +/********************************************************************** +Name : atsmb_request +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static void atsmb_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + + struct atsmb_host *host = mmc_priv(mmc); + DBG("[%s] s\n",__func__); + + /* May retry process comes here.*/ + host->mrq = mrq; + host->data = mrq->data; + host->cmd = mrq->cmd; + host->done_data = NULL; + host->done = NULL; + + /*for data request*/ + if (host->data) { + if (host->cmd->opcode == MMC_WRITE_BLOCK + || host->cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK + || host->cmd->opcode == MMC_READ_SINGLE_BLOCK + || host->cmd->opcode == MMC_READ_MULTIPLE_BLOCK + || host->cmd->opcode == SD_IO_RW_EXTENDED) { + atsmb_start_data(host); + } else { + atsmb_cmd_with_data_back(host); + } + } else { + atsmb_start_cmd(host); + } + DBG("[%s] e\n",__func__); +} +/********************************************************************** +Name : atsmb_set_clock +Function :. +Calls : +Called by : +Parameter : +Author : Eason Chien +History :2012/7/19 +***********************************************************************/ +static int atsmb_set_clock(struct mmc_host *mmc, unsigned int clock) +{ + int clock_multiplier = 1; + DBG("clock = %u\n",clock); + + if (*ATSMB0_EXT_BUS_MODE & BIT4) /*Enable DDR50*/ + clock_multiplier = 2; + + if(SD0_speed){ + if (clock == mmc->f_min) { + DBG("[%s]ATSMB Host 390KHz\n", __func__); + if ((*ATSMB0_CTL2 & BIT3) == 0) { + DBG("[%s] 1.8V clock = %u\n",__func__,clock); + DBG("[%s] *ATSMB0_CTL2 = 0x%x \n", __func__, *ATSMB0_CTL2); + } + return auto_pll_divisor(DEV_SDMMC0, SET_DIV, 1, 390); + } + else if (clock >= 208000000) { + DBG("[%s]ATSMB Host 208MHz\n", __func__); + if ((*ATSMB0_CTL2 & BIT3) == 0) { + DBG("[%s] 1.8V clock = %u\n",__func__,clock); + + /*Set DPCTL 010 DNCTL 001*/ + SD0_DPCTL_4BYTE_VAL = (BIT1 | BIT4 | BIT7 | BIT10 | BIT13 | BIT16 | BIT19); + SD0_DNCTL_4BYTE_VAL = (BIT0 | BIT3 | BIT6 | BIT9 | BIT12 | BIT15 | BIT18); + + + DBG("[%s] *ATSMB0_CTL2 = 0x%x \n", __func__, *ATSMB0_CTL2); + DBG("[%s] *DPCTL = 0x%x DPCTL = 0x%x\n", + __func__, SD0_DPCTL_4BYTE_ADDR, SD0_DPCTL_4BYTE_VAL); + DBG("[%s] *DNCTL = 0x%x DNCTL = 0x%x\n", + __func__, SD0_DNCTL_4BYTE_ADDR, SD0_DNCTL_4BYTE_VAL); + } + return auto_pll_divisor(DEV_SDMMC0, SET_DIV, 2, 80); + } else if (clock >= 100000000) { + DBG("[%s]ATSMB Host 100MHz\n", __func__); + if ((*ATSMB0_CTL2 & BIT3) == 0) { + DBG("[%s] 1.8V clock = %u\n",__func__,clock); + + /*Set DPCTL 010 DNCTL 001*/ + SD0_DPCTL_4BYTE_VAL = (BIT1 | BIT4 | BIT7 | BIT10 | BIT13 | BIT16 | BIT19); + SD0_DNCTL_4BYTE_VAL = (BIT0 | BIT3 | BIT6 | BIT9 | BIT12 | BIT15 | BIT18); + + DBG("[%s] *ATSMB0_CTL2 = 0x%x \n", __func__, *ATSMB0_CTL2); + } + return auto_pll_divisor(DEV_SDMMC0, SET_DIV, 2, 80); + } else if (clock >= 50000000) { + DBG("[%s]ATSMB Host 50MHz\n", __func__); + if ((*ATSMB0_CTL2 & BIT3) == 0) { + DBG("[%s] 1.8V clock = %u\n",__func__,clock); + + /*Set DPCTL 010 DNCTL 001*/ + SD0_DPCTL_4BYTE_VAL = (BIT1 | BIT4 | BIT7 | BIT10 | BIT13 | BIT16 | BIT19); + SD0_DNCTL_4BYTE_VAL = (BIT0 | BIT3 | BIT6 | BIT9 | BIT12 | BIT15 | BIT18); + DBG("[%s] *ATSMB0_CTL2 = 0x%x \n", __func__, *ATSMB0_CTL2); + } + return auto_pll_divisor(DEV_SDMMC0, SET_DIV, 2, 45 * clock_multiplier); + } else if ((clock >= 25000000) && (clock < 50000000)) { + if ((*ATSMB0_CTL2 & BIT3) == 0) { + DBG("[%s] 1.8V clock = %u\n",__func__,clock); + + /*Set DPCTL 010 DNCTL 001*/ + SD0_DPCTL_4BYTE_VAL = (BIT1 | BIT4 | BIT7 | BIT10 | BIT13 | BIT16 | BIT19); + SD0_DNCTL_4BYTE_VAL = (BIT0 | BIT3 | BIT6 | BIT9 | BIT12 | BIT15 | BIT18); + DBG("[%s] *ATSMB0_CTL2 = 0x%x \n", __func__, *ATSMB0_CTL2); + } + DBG("[%s]ATSMB Host 25MHz\n", __func__); + return auto_pll_divisor(DEV_SDMMC0, SET_DIV, 2, 24 * clock_multiplier); + } else if ((clock >= 20000000) && (clock < 25000000)) { + DBG("[%s]ATSMB Host 20MHz\n", __func__); + if ((*ATSMB0_CTL2 & BIT3) == 0) { + DBG("[%s] 1.8V clock = %u\n",__func__,clock); + + /*Set DPCTL 010 DNCTL 001*/ + SD0_DPCTL_4BYTE_VAL = (BIT1 | BIT4 | BIT7 | BIT10 | BIT13 | BIT16 | BIT19); + SD0_DNCTL_4BYTE_VAL = (BIT0 | BIT3 | BIT6 | BIT9 | BIT12 | BIT15 | BIT18); + DBG("[%s] *ATSMB0_CTL2 = 0x%x \n", __func__, *ATSMB0_CTL2); + } + return auto_pll_divisor(DEV_SDMMC0, SET_DIV, 2, 20 * clock_multiplier); + } else { + DBG("[%s]ATSMB Host 390KHz\n", __func__); + if ((*ATSMB0_CTL2 & BIT3) == 0) { + DBG("[%s] 1.8V clock = %u\n",__func__,clock); + + /*Set DPCTL 010 DNCTL 001*/ + SD0_DPCTL_4BYTE_VAL = (BIT1 | BIT4 | BIT7 | BIT10 | BIT13 | BIT16 | BIT19); + SD0_DNCTL_4BYTE_VAL = (BIT0 | BIT3 | BIT6 | BIT9 | BIT12 | BIT15 | BIT18); + DBG("[%s] *ATSMB0_CTL2 = 0x%x \n", __func__, *ATSMB0_CTL2); + } + return auto_pll_divisor(DEV_SDMMC0, SET_DIV, 1, 390 * clock_multiplier); + } + }else{ + if (clock == mmc->f_min) { + DBG("[%s]ATSMB Host 390KHz\n", __func__); + if ((*ATSMB0_CTL2 & BIT3) == 0) { + DBG("[%s] 1.8V clock = %u\n",__func__,clock); + DBG("[%s] *ATSMB0_CTL2 = 0x%x \n", __func__, *ATSMB0_CTL2); + } + return auto_pll_divisor(DEV_SDMMC0, SET_DIV, 1, 390); + }else if (clock >= 25000000) { + if ((*ATSMB0_CTL2 & BIT3) == 0) { + DBG("[%s] 1.8V clock = %u\n",__func__,clock); + + /*Set DPCTL 010 DNCTL 001*/ + SD0_DPCTL_4BYTE_VAL = (BIT1 | BIT4 | BIT7 | BIT10 | BIT13 | BIT16 | BIT19); + SD0_DNCTL_4BYTE_VAL = (BIT0 | BIT3 | BIT6 | BIT9 | BIT12 | BIT15 | BIT18); + DBG("[%s] *ATSMB0_CTL2 = 0x%x \n", __func__, *ATSMB0_CTL2); + } + DBG("[%s]ATSMB Host 25MHz\n", __func__); + return auto_pll_divisor(DEV_SDMMC0, SET_DIV, 2, 24 * clock_multiplier); + } else if ((clock >= 20000000) && (clock < 25000000)) { + DBG("[%s]ATSMB Host 20MHz\n", __func__); + if ((*ATSMB0_CTL2 & BIT3) == 0) { + DBG("[%s] 1.8V clock = %u\n",__func__,clock); + + /*Set DPCTL 010 DNCTL 001*/ + SD0_DPCTL_4BYTE_VAL = (BIT1 | BIT4 | BIT7 | BIT10 | BIT13 | BIT16 | BIT19); + SD0_DNCTL_4BYTE_VAL = (BIT0 | BIT3 | BIT6 | BIT9 | BIT12 | BIT15 | BIT18); + DBG("[%s] *ATSMB0_CTL2 = 0x%x \n", __func__, *ATSMB0_CTL2); + } + return auto_pll_divisor(DEV_SDMMC0, SET_DIV, 2, 20 * clock_multiplier); + } else { + DBG("[%s]ATSMB Host 390KHz\n", __func__); + if ((*ATSMB0_CTL2 & BIT3) == 0) { + DBG("[%s] 1.8V clock = %u\n",__func__,clock); + + /*Set DPCTL 010 DNCTL 001*/ + SD0_DPCTL_4BYTE_VAL = (BIT1 | BIT4 | BIT7 | BIT10 | BIT13 | BIT16 | BIT19); + SD0_DNCTL_4BYTE_VAL = (BIT0 | BIT3 | BIT6 | BIT9 | BIT12 | BIT15 | BIT18); + DBG("[%s] *ATSMB0_CTL2 = 0x%x \n", __func__, *ATSMB0_CTL2); + } + return auto_pll_divisor(DEV_SDMMC0, SET_DIV, 1, 390 * clock_multiplier); + } + } +} + +static void atsmb_enable_power(struct atsmb_host *host, unsigned long flags) +{ + int result; + spin_unlock_irqrestore(&host->lock, flags); + if (g_atsmb_regulator) { + if (regulator_is_enabled(atsmb_regulator) == 0) { + DBG("[%s] Turn on power\n", __FUNCTION__); + result = regulator_enable(atsmb_regulator); + if (result != 0) + printk(KERN_ALERT "[%s] regulator_enable FAIL\n", __FUNCTION__); + } + } else { + GPIO_OD_GP13_SD0_BYTE_VAL &= ~GPIO_SD0_POWER; + } + spin_lock_irqsave(&host->lock, flags); +} + +static void atsmb_disable_power(struct atsmb_host *host, unsigned long flags) +{ + int result; + spin_unlock_irqrestore(&host->lock, flags); + if (g_atsmb_regulator) { + if (regulator_is_enabled(atsmb_regulator) == 1) { + DBG("[%s] Turn off power\n", __FUNCTION__); + result = regulator_disable(atsmb_regulator); + if (result != 0) + printk(KERN_ALERT "[%s] regulator_disable FAIL\n", __FUNCTION__); + } + } else { + GPIO_CTRL_GP13_SD0_BYTE_VAL |= GPIO_SD0_POWER; + GPIO_OC_GP13_SD0_BYTE_VAL |= GPIO_SD0_POWER; + /*set internal pull up*/ + PULL_CTRL_GP13_SD0_BYTE_VAL |= GPIO_SD0_POWER; + /*set internal pull enable*/ + PULL_EN_GP13_SD0_BYTE_VAL |= GPIO_SD0_POWER; + /*disable SD0 power*/ + GPIO_OD_GP13_SD0_BYTE_VAL |= GPIO_SD0_POWER; + } + spin_lock_irqsave(&host->lock, flags); +} + +/********************************************************************** +Name : atsmb_set_ios +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static void atsmb_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + + struct atsmb_host *host = mmc_priv(mmc); + unsigned long flags; + DBG("[%s] s\n",__func__); + spin_lock_irqsave(&host->lock, flags); + + if (ios->power_mode == MMC_POWER_OFF) { + if (MMC0_DRIVER_VERSION == MMC_DRV_3498) { + /* set all descriptor end bit for removing card when + * PDMA don't transfer data done. let PDMA transfer done + */ + atsmb_set_dma_end(host->DescVirAddr, host->DescSize); + + /* stop SD output clock */ + *ATSMB0_BUS_MODE &= ~(ATSMB_CST); + + /* disable SD Card power */ + /*set SD0 power pin as GPO pin*/ + atsmb_disable_power(host, flags); + + /* Disable Pull up/down resister of SD Bus */ + /*GPIO_PULL_CTRL_GP13_XDIO_BYTE_VAL &= ~SD0_PIN; marked by eason 2012/3/29*/ + + /* Config SD0 to GPIO */ + GPIO_CTRL_GP13_SD0_BYTE_VAL |= SD0_PIN; + + /* SD0 all pins output low */ + GPIO_OD_GP13_SD0_BYTE_VAL &= ~SD0_PIN; + + /* Config SD0 to GPO */ + GPIO_OC_GP13_SD0_BYTE_VAL |= SD0_PIN; + + } + + } else if (ios->power_mode == MMC_POWER_UP) { + if (MMC0_DRIVER_VERSION == MMC_DRV_3498) { + /* disable SD Card power */ + /*set SD0 power pin as GPO pin*/ + GPIO_CTRL_GP13_SD0_BYTE_VAL |= GPIO_SD0_POWER; + GPIO_OC_GP13_SD0_BYTE_VAL |= GPIO_SD0_POWER; + + /*set internal pull up*/ + PULL_CTRL_GP13_SD0_BYTE_VAL |= GPIO_SD0_POWER; + /*set internal pull enable*/ + PULL_EN_GP13_SD0_BYTE_VAL |= GPIO_SD0_POWER; + /*disable SD0 power*/ + GPIO_OD_GP13_SD0_BYTE_VAL |= GPIO_SD0_POWER; + + /* Config SD PIN share, WM3498 SD0 no share pin */ + /*PIN_SHARING_SEL_4BYTE_VAL &= ~GPIO_SD0_PinShare; */ + + /* do not config GPIO_SD0_CD because ISR has already run, + * config card detect will issue ISR storm. + */ + /* + GPIO_OD_GP12_SPI_BYTE_VAL |= GPIO_SD0_18SEL; + GPIO_OC_GP12_SPI_BYTE_VAL |= GPIO_SD0_18SEL; + GPIO_CTRL_GP12_SPI_BYTE_VAL |= GPIO_SD0_18SEL; + */ + *ATSMB0_CTL2 |= BIT3; /*Set 3.3V*/ + *ATSMB0_EXT_CTL &= ~BIT7; /*Disable High Speed Mode*/ + *ATSMB0_CTL2 &= ~(BIT0 | BIT1 | BIT2); + *ATSMB0_EXT_BUS_MODE &= ~BIT4; /*Disable DDR50*/ + + /*Set 18SEL as function pin*/ + GPIO_CTRL_GP12_SPI_BYTE_VAL &= ~GPIO_SD0_18SEL; + + + /* Config SD to GPIO */ + GPIO_CTRL_GP13_SD0_BYTE_VAL |= SD0_PIN; + + /* SD all pins output low */ + GPIO_OD_GP13_SD0_BYTE_VAL &= ~SD0_PIN; + + /* Config SD to GPO */ + GPIO_OC_GP13_SD0_BYTE_VAL |= SD0_PIN; + + /* Pull up/down resister of SD Bus */ + /*Disable Clock & CMD Pull enable*/ + PULL_EN_GP13_SD0_BYTE_VAL &= ~(GPIO_SD0_Clock | GPIO_SD0_Command); + + /*Set CD ,WP ,DATA pin pull up*/ + if (SD0_detect_pulldown) + PULL_CTRL_GP63_SD02_BYTE_VAL &= ~GPIO_SD0_CD; + else + PULL_CTRL_GP63_SD02_BYTE_VAL |= GPIO_SD0_CD; + PULL_CTRL_GP13_SD0_BYTE_VAL |= (GPIO_SD0_Data | GPIO_SD0_WriteProtect); + + /*Enable CD ,WP ,DATA internal pull*/ + PULL_EN_GP63_SD02_BYTE_VAL |= GPIO_SD0_CD; + PULL_EN_GP13_SD0_BYTE_VAL |= (GPIO_SD0_Data | GPIO_SD0_WriteProtect); + + /*Set SD0 PAD signal drive strength as 0*/ + SD0_DPCTL_4BYTE_VAL = 0; + SD0_DNCTL_4BYTE_VAL = 0; + + spin_unlock_irqrestore(&host->lock, flags); + msleep(100); + spin_lock_irqsave(&host->lock, flags); + + /* enable SD Card Power */ + atsmb_enable_power(host, flags); + + /* enable SD output clock */ + *ATSMB0_BUS_MODE |= ATSMB_CST; + + spin_unlock_irqrestore(&host->lock, flags); + msleep(10); + spin_lock_irqsave(&host->lock, flags); + + /* Config SD0 back to function */ + GPIO_CTRL_GP13_SD0_BYTE_VAL &= ~SD0_PIN; + GPIO_CTRL_GP63_SD02CD_BYTE_VAL &= ~GPIO_SD0_CD; + } + } else { + /*nothing to do when powering on.*/ + } + + host->current_clock = atsmb_set_clock(mmc,ios->clock); + + if (ios->bus_width == MMC_BUS_WIDTH_8) { + *ATSMB0_EXT_CTL |= BIT2; + } else if (ios->bus_width == MMC_BUS_WIDTH_4) { + *ATSMB0_BUS_MODE |= ATSMB_BUS_WIDTH_4; + *ATSMB0_EXT_CTL &= ~BIT2; + } else { + *ATSMB0_BUS_MODE &= ~(ATSMB_BUS_WIDTH_4); + *ATSMB0_EXT_CTL &= ~BIT2; + } + +/* +#define MMC_TIMING_LEGACY 0 +#define MMC_TIMING_MMC_HS 1 +#define MMC_TIMING_SD_HS 2 +#define MMC_TIMING_UHS_SDR12 MMC_TIMING_LEGACY +#define MMC_TIMING_UHS_SDR25 MMC_TIMING_SD_HS +#define MMC_TIMING_UHS_SDR50 3 +#define MMC_TIMING_UHS_SDR104 4 +#define MMC_TIMING_UHS_DDR50 5 +#define MMC_TIMING_MMC_HS200 6 +*/ + DBG("[%s]timing = 0x%x clock = %u\n", __func__, ios->timing, ios->clock); + + if (ios->timing == MMC_TIMING_LEGACY) { + *ATSMB0_EXT_CTL &= ~BIT7; /*Disable High Speed Mode*/ + *ATSMB0_CTL2 &= ~(BIT0 | BIT1 | BIT2); + } else if (ios->timing == MMC_TIMING_MMC_HS) { + *ATSMB0_EXT_CTL &= ~BIT7; /*Disable High Speed Mode*/ + *ATSMB0_CTL2 &= ~(BIT0 | BIT1 | BIT2); + } else if (ios->timing == MMC_TIMING_SD_HS) { + *ATSMB0_EXT_CTL |= BIT7; /*HIGH SPEED MODE*/ + *ATSMB0_CTL2 &= ~(BIT0 | BIT1 | BIT2); + } else if (ios->timing == MMC_TIMING_UHS_SDR12) { + *ATSMB0_EXT_CTL &= ~BIT7; /*Disable High Speed Mode*/ + /*UHS Mode Select = 000 SDR12*/ + *ATSMB0_CTL2 &= ~(BIT0 | BIT1 | BIT2); + } else if (ios->timing == MMC_TIMING_UHS_SDR25) { + *ATSMB0_EXT_CTL &= ~BIT7; /*Disable High Speed Mode*/ + /*UHS Mode Select = 001 SDR25*/ + *ATSMB0_CTL2 |= BIT0; + *ATSMB0_CTL2 &= ~(BIT1 | BIT2); + } else if (ios->timing == MMC_TIMING_UHS_SDR50) { + *ATSMB0_EXT_CTL &= ~BIT7; /*Disable High Speed Mode*/ + /*UHS Mode Select = 010 SDR50*/ + *ATSMB0_CTL2 |= BIT1; + *ATSMB0_CTL2 &= ~(BIT0 | BIT2); + } else if (ios->timing == MMC_TIMING_UHS_SDR104) { + *ATSMB0_EXT_CTL &= ~BIT7; /*Disable High Speed Mode*/ + /*UHS Mode Select = 011 SDR104*/ + *ATSMB0_CTL2 |= (BIT0 | BIT1); + *ATSMB0_CTL2 &= ~BIT2; + } else if (ios->timing == MMC_TIMING_UHS_DDR50) { + *ATSMB0_EXT_CTL &= ~BIT7; /*Disable High Speed Mode*/ + /*UHS Mode Select = 100 DDR50*/ + *ATSMB0_CTL2 |= BIT2; + *ATSMB0_CTL2 &= ~(BIT0 | BIT1); + *ATSMB0_EXT_BUS_MODE |= BIT4; /*Enable DDR50*/ + /* enable SD output clock */ + *ATSMB0_BUS_MODE &= ~ATSMB_CST; + spin_unlock_irqrestore(&host->lock, flags); + msleep(1); + spin_lock_irqsave(&host->lock, flags); + + *ATSMB0_EXT_BUS_MODE |= BIT4; /*Enable DDR50*/ + /* enable SD output clock */ + *ATSMB0_BUS_MODE |= ATSMB_CST; + } else + *ATSMB0_EXT_CTL &= ~BIT7; /*Disable High Speed Mode*/ + +/* spin_unlock_irqrestore(&host->lock, flags); + msleep(100); + spin_lock_irqsave(&host->lock, flags); */ + + DBG("[%s] *ATSMB0_CTL2 = 0x%x *ATSMB0_EXT_CTL = 0x%x *ATSMB0_EXT_BUS_MODE = 0x%x\n", + __func__, *ATSMB0_CTL2, *ATSMB0_EXT_CTL, *ATSMB0_EXT_BUS_MODE); + spin_unlock_irqrestore(&host->lock, flags); + DBG("[%s] e\n",__func__); +} + +int atsmb_start_signal_voltage_switch(struct mmc_host *host, struct mmc_ios *ios) +{ + DBG("[%s] s\n",__func__); + + if(ios->signal_voltage == MMC_SIGNAL_VOLTAGE_180) { + /*Set CMD ,DATA0~3 pin as GPO low*/ + /* SD0 all pins output low */ + GPIO_OD_GP13_SD0_BYTE_VAL &= ~(GPIO_SD0_Command | GPIO_SD0_Data); + /* Config SD0 to GPO */ + GPIO_OC_GP13_SD0_BYTE_VAL |= (GPIO_SD0_Command | GPIO_SD0_Data);; + /* Config SD0 to GPIO */ + GPIO_CTRL_GP13_SD0_BYTE_VAL |= (GPIO_SD0_Command | GPIO_SD0_Data); + + + /*Wait for 1ms*/ + msleep(1); + + /*Stop Clock*/ + *ATSMB0_BUS_MODE &= ~(ATSMB_CST); + + + /*Enable clock & Set 1.8V*/ + *ATSMB0_CTL2 &= ~BIT3; + + /*Wait for 10ms*/ + msleep(100); + + /*Enable Clock*/ + *ATSMB0_BUS_MODE |= ATSMB_CST; + + + /*Set CMD DATA0~3 pin as function pin*/ + GPIO_CTRL_GP13_SD0_BYTE_VAL &= ~GPIO_SD0_Command; + GPIO_CTRL_GP13_SD0_BYTE_VAL &= ~GPIO_SD0_Data; + + + DBG("Set SD0 1.8v = 0x%x\n",ios->signal_voltage); + + return 0; + } else if (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_330){ + *ATSMB0_CTL2 |= BIT3; + DBG("Set SD0 3.3v = 0x%x\n",ios->signal_voltage); + return 0; + } + + printk("Set voltage error = 0x%x\n",ios->signal_voltage); + return -EIO; + DBG("[%s] e\n",__func__); +} + +static const struct mmc_host_ops atsmb_ops = { + .request = atsmb_request, + .set_ios = atsmb_set_ios, + .get_ro = atsmb_get_ro, + .get_slot_status = atsmb_get_slot_status, + .dump_host_regs = atsmb_dump_host_regs, + .enable_sdio_irq = atsmb_enable_sdio_irq, + .start_signal_voltage_switch = atsmb_start_signal_voltage_switch, +}; + +extern int wmt_getsyspara(char *varname, unsigned char *varval, int *varlen); + +/********************************************************************** +Name :atsmb_probe +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static int __init atsmb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mmc_host *mmc_host = NULL; + struct atsmb_host *atsmb_host = NULL; + struct resource *resource = NULL; + int irq[2] = {0}; + int ret = 0; + + DBG("[%s] s\n",__func__); + if (!pdev) { + ret = -EINVAL; /* Invalid argument */ + goto the_end; + } + + /*Enable SD host clock*/ + auto_pll_divisor(DEV_SDMMC0, CLK_ENABLE, 0, 0); + + if (MMC0_DRIVER_VERSION == MMC_DRV_3498) { + /* Pull up/down resister of SD CD */ + if(SD0_detect_pulldown) + PULL_CTRL_GP63_SD02_BYTE_VAL &= ~GPIO_SD0_CD; /*pull down CD*/ + else + PULL_CTRL_GP63_SD02_BYTE_VAL |= GPIO_SD0_CD; /*pull up CD*/ + PULL_EN_GP63_SD02_BYTE_VAL |= GPIO_SD0_CD; + + /* config CardDetect pin to SD function */ + GPIO_CTRL_GP63_SD02CD_BYTE_VAL &= ~GPIO_SD0_CD; + } + + + resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!resource) { + ret = -ENXIO; /* No such device or address */ + printk(KERN_ALERT "[MMC/SD driver] Getting platform resources failed!\n"); + goto the_end; + } +#if 0 + if (!request_mem_region(resource->start, SZ_1K, MMC0_DRIVER_NAME)) { + ret = -EBUSY; + printk(KERN_ALERT "[MMC/SD driver] Request memory region failed!\n"); + goto the_end ; + } +#endif + irq[0] = platform_get_irq(pdev, 0); /*get IRQ for device*/; + irq[1] = platform_get_irq(pdev, 1); /*get IRQ for dma*/; + + if (irq[0] == NO_IRQ || irq[1] == NO_IRQ) { + ret = -ENXIO;/* No such device or address */ + printk(KERN_ALERT "[MMC/SD driver] Get platform IRQ failed!\n"); + goto rls_region; + } + + /*allocate a standard msp_host structure attached with a atsmb structure*/ + mmc_host = mmc_alloc_host(sizeof(struct atsmb_host), dev); + if (!mmc_host) { + ret = -ENOMEM; + printk(KERN_ALERT "[MMC/SD driver] Allocating driver's data failed!\n"); + goto rls_region; + } + mmc_host->wmt_host_index = 0; /*to identify host number*/ + + dev_set_drvdata(dev, (void *)mmc_host); /* mmc_host is driver data for the atsmb dev.*/ + atsmb_host = mmc_priv(mmc_host); + + mmc_host->ops = &atsmb_ops; + + mmc_host->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34; + + mmc_host->f_min = 390425; /*390.425Hz = 400MHz/64/16*/ + mmc_host->f_max = 100000000; /* Set max frequency 100MHz*/ + + if (SDXC0_function == 1) { + mmc_host->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SDIO_IRQ + | MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | MMC_CAP_UHS_DDR50 | MMC_CAP_1_8V_DDR; + /* |MMC_CAP_8_BIT_DATA;*/ //zhf: marked by James Tian + } else { + mmc_host->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SDIO_IRQ; + /* |MMC_CAP_8_BIT_DATA;*/ //zhf: marked by James Tian + } + + mmc_host->max_segs = 128; /*we use software sg. so we could manage even larger number.*/ + + /*1MB per each request */ + /*we have a 16 bit block number register, and block length is 512 bytes.*/ + mmc_host->max_req_size = 16*512*(mmc_host->max_segs); + mmc_host->max_seg_size = 65024; /* 0x7F*512 PDMA one descriptor can transfer 64K-1 byte*/ + mmc_host->max_blk_size = 2048; /* our block length register is 11 bits.*/ + mmc_host->max_blk_count = (mmc_host->max_req_size)/512; + + /*set the specified host -- ATSMB*/ +#ifdef CONFIG_MMC_UNSAFE_RESUME + mmc_host->card_attath_status = card_attach_status_unchange; +#endif + sema_init(&mmc_host->req_sema,1); /*initial request semaphore*/ +#if 0 + atsmb_host->base = ioremap(resource->start, SZ_1K); + if (!atsmb_host->base) { + printk(KERN_ALERT "[MMC/SD driver] IO remap failed!\n"); + ret = -ENOMEM; + goto fr_host; + } +#endif + atsmb_host->base = (void *)resource->start; + atsmb_host->mmc = mmc_host; + spin_lock_init(&atsmb_host->lock); + atsmb_host->res = resource;/* for atsmb_remove*/ + + /*disable all interrupt and clear status by resetting controller.*/ + *ATSMB0_BUS_MODE |= ATSMB_SFTRST; + *ATSMB0_BLK_LEN &= ~(0xa000); + *ATSMB0_SD_STS_0 |= 0xff; + *ATSMB0_SD_STS_1 |= 0xff; + + /* WM3437 A0 default not output clock, after SFTRST need to enable SD clock */ + //if (MMC0_DRIVER_VERSION >= MMC_DRV_3437_A0) /* including 3429 */ + *ATSMB0_BUS_MODE |= ATSMB_CST; + + atsmb_host->regular_irq = irq[0]; + atsmb_host->dma_irq = irq[1]; + + ret = request_irq(atsmb_host->regular_irq, + atsmb_regular_isr, + IRQF_SHARED, //SA_SHIRQ, /*SA_INTERRUPT, * that is okay?*/ //zhf: modified by James Tian, should be IRQF_SHARED? + MMC0_DRIVER_NAME, + (void *)atsmb_host); + if (ret) { + printk(KERN_ALERT "[MMC/SD driver] Failed to register regular ISR!\n"); + goto unmap; + } + + ret = request_irq(atsmb_host->dma_irq, + atsmb_dma_isr, + IRQF_DISABLED, // SA_INTERRUPT, //zhf: modified by James Tian + MMC0_DRIVER_NAME, + (void *)atsmb_host); + if (ret) { + printk(KERN_ALERT "[MMC/SD driver] Failed to register DMA ISR!\n"); + goto fr_regular_isr; + } + + /* config CardDetect pin to SD function */ + /*GPIO_CTRL_GP63_SD02CD_BYTE_VAL &= ~GPIO_SD0_CD; add by eason 2012/3/29*/ + + /*wait card detect status change*/ + //msleep(10); + + /*enable card insertion interrupt and enable DMA and its Global INT*/ + *ATSMB0_BLK_LEN |= (0xa000); /* also, we enable GPIO to detect card.*/ + *ATSMB0_SD_STS_0 |= 0xff; + *ATSMB0_INT_MASK_0 |= 0x80; /*or 0x40?*/ + + /*allocation dma descriptor*/ + ret = atsmb_alloc_desc(atsmb_host, sizeof(struct SD_PDMA_DESC_S) * MAX_DESC_NUM); + if (ret == -1) { + printk(KERN_ALERT "[MMC/SD driver] Failed to allocate DMA descriptor!\n"); + goto fr_dma_isr; + } + printk(KERN_INFO "WMT ATSMB (AHB To SD/MMC Bus) controller registered!\n"); + + if (SD0_function == SDIO_WIFI) + mmc_add_host(mmc_host,false); + else + mmc_add_host(mmc_host,true); + + mmc_host_attr = mmc_host; + + DBG("[%s] e1\n",__func__); + return 0; + +fr_dma_isr: + free_irq(atsmb_host->dma_irq, atsmb_host); +fr_regular_isr: + free_irq(atsmb_host->regular_irq, atsmb_host); +unmap: + //iounmap(atsmb_host->base); +//fr_host: + dev_set_drvdata(dev, NULL); + mmc_free_host(mmc_host); +rls_region: + //release_mem_region(resource->start, SZ_1K); +the_end: + printk(KERN_ALERT "[MMC/SD driver] ATSMB0 probe Failed!\n") ; + DBG("[%s] e2\n",__func__); + return ret; +} +/********************************************************************** +Name : atsmb_remove +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static int atsmb_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mmc_host *mmc_host = (struct mmc_host *)dev_get_drvdata(dev); + struct atsmb_host *atsmb_host; + + DBG("[%s] s\n",__func__); + atsmb_host = mmc_priv(mmc_host); + if (!mmc_host || !atsmb_host) { + printk(KERN_ALERT "[MMC/SD driver] ATSMB0 remove method failed!\n"); + DBG("[%s] e1\n",__func__); + return -ENXIO; + } + mmc_remove_host(mmc_host); + + /*disable interrupt by resetting controller -- for safey*/ + *ATSMB0_BUS_MODE |= ATSMB_SFTRST; + *ATSMB0_BLK_LEN &= ~(0xa000); + *ATSMB0_SD_STS_0 |= 0xff; + *ATSMB0_SD_STS_1 |= 0xff; + + (void)free_irq(atsmb_host->regular_irq, atsmb_host); + (void)free_irq(atsmb_host->dma_irq, atsmb_host); + (void)iounmap(atsmb_host->base); + (void)release_mem_region(atsmb_host->res->start, SZ_1K); + dev_set_drvdata(dev, NULL); + /*free dma descriptor*/ + dma_free_coherent(atsmb_host->mmc->parent, atsmb_host->DescSize, + atsmb_host->DescVirAddr, atsmb_host->DescPhyAddr); + (void)mmc_free_host(mmc_host);/* also free atsmb_host.*/ + DBG("[%s] e2\n",__func__); + return 0; +} + +/********************************************************************** +Name : atsmb_shutdown +Function :. +Calls : +Called by : +Parameter : +Author : Tommy Huang +History : +***********************************************************************/ +static void atsmb_shutdown(struct platform_device *pdev) +{ + /*atsmb_shutdown don't be used now.*/ + /*struct device *dev = &pdev->dev; + struct mmc_host *mmc_host = (struct mmc_host *)dev_get_drvdata(dev);*/ + + DBG("[%s] s\n",__func__); + if (SD0_function != SDIO_WIFI) { + /*Disable card detect interrupt*/ + *ATSMB0_INT_MASK_0 &= ~0x80; + } + DBG("[%s] e\n",__func__); + +} + +/********************************************************************** +Name : atsmb_suspend +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +#ifdef CONFIG_PM +static int atsmb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct device *dev = &pdev->dev; + struct mmc_host *mmc = (struct mmc_host *)dev_get_drvdata(dev); + int ret = 0; + DBG("[%s] s\n",__func__); + + if (mmc) { + /*struct atsmb_host *host = mmc_priv(mmc);*/ + ret = mmc_suspend_host(mmc); + if (ret == 0) { + /*disable all interrupt and clear status by resetting controller. */ + *ATSMB0_BUS_MODE |= ATSMB_SFTRST; + *ATSMB0_BLK_LEN &= ~(0xa000); + *ATSMB0_SD_STS_0 |= 0xff; + *ATSMB0_SD_STS_1 |= 0xff; + + } + /*disable source clock*/ + auto_pll_divisor(DEV_SDMMC0, CLK_DISABLE, 0, 0); +#ifdef CONFIG_MMC_UNSAFE_RESUME + /*clean SD card attatch status change*/ + PMCWS_VAL |= BIT19; + mmc->card_attath_status = card_attach_status_unchange; +#endif + } + + DBG("[%s] e\n",__func__); + return ret; +} +/********************************************************************** +Name : atsmb_resume +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static int atsmb_resume(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mmc_host *mmc = (struct mmc_host *)dev_get_drvdata(dev); + int ret = 0; + DBG("[%s] s\n",__func__); + + /* + * enable interrupt, DMA, etc. + * Supply power to slot. + */ + if (mmc) { + /*enable source clock*/ + auto_pll_divisor(DEV_SDMMC0, CLK_ENABLE, 0, 0); + + udelay(1); + /*enable card insertion interrupt and enable DMA and its Global INT*/ + *ATSMB0_BUS_MODE |= ATSMB_SFTRST; + *ATSMB0_BLK_LEN |= (0xa000); + *ATSMB0_INT_MASK_0 |= 0x80; /* or 40?*/ +#ifdef CONFIG_MMC_UNSAFE_RESUME + /*modify SD card attatch status change*/ + if ((PMCWS_VAL & BIT19) && !mmc->bus_dead) { + /*card change when suspend mode*/ + mmc->card_attath_status = card_attach_status_change; + /*clean SD card attatch status change*/ + PMCWS_VAL |= BIT19; + } +#endif + ret = mmc_resume_host(mmc); + } + + DBG("[%s] e\n",__func__); + return ret; +} +#else +#define atsmb_suspend NULL +#define atsmb_resume NULL +#endif + +static struct platform_driver atsmb_driver = { + .driver.name = "sdmmc", + //.probe = atsmb_probe, + .remove = atsmb_remove, + .shutdown = atsmb_shutdown, + .suspend = atsmb_suspend, + .resume = atsmb_resume, +}; + +static struct platform_device wmt_sdmmc_device = { + .name = "sdmmc", + .id = 0, + .dev = { + .dma_mask = &wmt_sdmmc_dma_mask, + .coherent_dma_mask = ~0, + .release = atsmb_release, + }, + .num_resources = ARRAY_SIZE(wmt_sdmmc_resources), + .resource = wmt_sdmmc_resources, +}; + + +static ssize_t atsmb_state_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + int ret = 0; + int card_state; + + DBG("[%s] s\n",__func__); + if (mmc_host_attr->card != NULL) + card_state = 1; + else + card_state = 0; + DBG("[%s]card_state = %d\n",__func__,card_state); + ret = sprintf(buf, "%d\n", card_state); + DBG("[%s] e\n",__func__); + return ret; +} + +static ssize_t atsmb_state_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t n) +{ + int val; + DBG("[%s] s\n",__func__); + if (sscanf(buf, "%d", &val) == 1) { + DBG("[%s] val = %d\n",__func__,val); + if ((val == 1) && (mmc_host_attr->card == NULL)) { + DBG("[%s]add card\n",__func__); + mmc_detect_change(mmc_host_attr, 0); + msleep(500); + } else if ((val == 0) && (mmc_host_attr->card != NULL)) { + DBG("[%s]remove card\n",__func__); + mmc_force_remove_card(mmc_host_attr); + } + DBG("[%s] e1\n",__func__); + return n; + } + DBG("[%s] e2\n",__func__); + return -EINVAL; +} + +static struct kobj_attribute atsmb_state_attr = { \ + .attr = { \ + .name = __stringify(state), \ + .mode = 0755, \ + }, \ + .show = atsmb_state_show, \ + .store = atsmb_state_store, \ +}; + +static struct attribute * g[] = { + &atsmb_state_attr.attr, + NULL, +}; + +static struct attribute_group attr_group = { + .attrs = g, +}; + //kevin add to check tf card stats every 1 sec + +static void wmt_mmc_work(struct work_struct *work) +{ + + static int card_state_save = -1; + if(mmc_host_attr!=NULL){ + struct atsmb_host *host = mmc_priv(mmc_host_attr); + int card_state; + if (mmc_host_attr->card != NULL) + card_state = 1; + else + card_state = 0; + + + //printk("check %d %d\n",atsmb_get_slot_status(host->mmc),card_state); + if(atsmb_get_slot_status(host->mmc)!=card_state){ + //the second time error,goto detect or remove card + if(card_state_save == card_state){ + + //printk("xxxxxxxxxxxxxxxxxxxxxxxxcatch error %dxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n",card_state); + mmc_detect_change(mmc_host_attr, 0); + + } + //mark status error + card_state_save =card_state; + }else + card_state_save = -1; + + } + schedule_delayed_work(&mmc_work, 1*HZ); +} +static int __init atsmb_init(void) +{ + int ret; + + int retval; + unsigned char buf[80]; + int varlen = 80; + char *varname = "wmt.sd0.param"; + int temp = 0, sd_enable = 1; /*0 :disable 1:enable*/ + unsigned char colon; + DBG("[%s] s\n",__func__); + +#ifdef CONFIG_MTD_WMT_SF + /*Read system param to identify host function 0: SD/MMC 1:SDIO wifi*/ + retval = wmt_getsyspara(varname, buf, &varlen); + if (retval == 0) { + sscanf(buf,"%x%c%d%c%d%c%d%c%d", &temp, &colon, &SD0_function,&colon,&SD0_detect_pol,&colon,&SD0_detect_pulldown,&colon,&SD0_speed); + printk(KERN_ALERT "wmt.sd0.param = %x%c%d%c%d%c%d%c%d\n", temp, colon, SD0_function,colon,SD0_detect_pol,colon,SD0_detect_pulldown,colon,SD0_speed); + sd_enable = temp & 0xf; + SDXC0_function = (temp >> 4) & 0xf; + printk(KERN_ALERT "SD0 ebable = %x, SDXC = %x, function = %x\n", + sd_enable, SDXC0_function, SD0_function); + + if (SD0_function < 0 || SD0_function >= SD_MAX_FUN) { + sd_enable = 1; + SD0_function = 0; + printk(KERN_ALERT "wmt.sd0.param func err\n"); + } + if (colon != ':') { + sd_enable = 1; + printk(KERN_ALERT "wmt.sd0.param colon err\n"); + } + + } else { + printk(KERN_ALERT "Default wmt.sd0.param = %x:%d\n", temp, SD0_function); + } + + //add by kevin guan + retval = wmt_getsyspara("wmt.sd0.ro.disable", buf, &varlen); + if (retval == 0) { + sscanf(buf,"%d", &SD0_ro_disable); + printk(KERN_ALERT "SD0_ro_disable %d\n",SD0_ro_disable); + } + +#endif + /*SD function disable*/ + if (sd_enable != 1) { + return -ENODEV; + } + + + get_driver_version(); + + if (platform_device_register(&wmt_sdmmc_device))//add by jay,for modules support + return -1; + //ret = platform_driver_register(&atsmb_driver); + + /* Register regulator for SD host power switch */ + atsmb_regulator = regulator_get(NULL, "ldo4"); + if (IS_ERR(atsmb_regulator)) + g_atsmb_regulator = 0; + else + g_atsmb_regulator = 1; + printk("[SD/MMC]use_regulator = %d\n", g_atsmb_regulator); + + /* Force to disable ldo4 regulator, because Power switch is turned on, default */ + if (g_atsmb_regulator) { + ret = regulator_force_disable(atsmb_regulator); + if (ret != 0) + printk(KERN_ALERT "[%s] get regulator Fail, ret = %d\n", __FUNCTION__, ret); + } + + ret = platform_driver_probe(&atsmb_driver, atsmb_probe); + + atsmb_kobj = kobject_create_and_add("mmc0", NULL); + if (!atsmb_kobj) + return -ENOMEM; + + + { + //add by kevin guan + int detect_disable = 0; + retval = wmt_getsyspara("wmt.sd0.detect.disable", buf, &varlen); + if (retval == 0) { + sscanf(buf,"%d", &detect_disable); + printk(KERN_ALERT "detect_disable %d\n",detect_disable); + } + + if(detect_disable){ + //tf slot without detect pin. + //do nothing; + }else{ + //kevin add to check tf card stats every 1 sec + INIT_DELAYED_WORK(&mmc_work, wmt_mmc_work); + schedule_delayed_work(&mmc_work, 1*HZ); + } + } + + return sysfs_create_group(atsmb_kobj, &attr_group); + + DBG("[%s] e\n",__func__); + return ret; +} + +static void __exit atsmb_exit(void) +{ + DBG("[%s] s\n",__func__); + (void)platform_driver_unregister(&atsmb_driver); + (void)platform_device_unregister(&wmt_sdmmc_device);//add by jay,for modules support + DBG("[%s] e\n",__func__); +} + +module_init(atsmb_init); +module_exit(atsmb_exit); +module_param(fmax0, uint, 0444); + +MODULE_AUTHOR("WonderMedia Technologies, Inc."); +MODULE_DESCRIPTION("WMT [AHB to SD/MMC Bridge] driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mmc/host/mmc_atsmb.h b/drivers/mmc/host/mmc_atsmb.h new file mode 100755 index 00000000..845b1116 --- /dev/null +++ b/drivers/mmc/host/mmc_atsmb.h @@ -0,0 +1,691 @@ +/*++ +linux/drivers/mmc/host/mmc_atsmb.h + +Copyright (c) 2008 WonderMedia Technologies, 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, see <http://www.gnu.org/licenses/>. + +WonderMedia Technologies, Inc. +10F, 529, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C. +--*/ + + + +#ifndef __MMC_ATSMB_H +#define __MMC_ATSMB_H + +#define MMC0_DRIVER_NAME "sdmmc" +#define MMC1_DRIVER_NAME "sdmmc1" +#define MMC2_DRIVER_NAME "sdmmc2" + + + +#define MMC_ERR_NONE 0 /* Only for Android kernel 2.6.29, in MVL5, this variable is defined in mmc.h */ +#define MMC_ERR_FAILED 4 /* Only for Android kernel 2.6.29, in MVL5, this variable is defined in mmc.h */ +/***************Driver version****************/ +#define MMC_DRV_3357_A0_A3 0 +#define MMC_DRV_3357_A4_A7 1 +#define MMC_DRV_3358 2 +#define MMC_DRV_3359 3 +#define MMC_DRV_3400_A0 4 +#define MMC_DRV_3400_A1 5 +#define MMC_DRV_3426_A0 6 +#define MMC_DRV_3426_A1 7 +#define MMC_DRV_3426_A2 8 +#define MMC_DRV_3437_A0 9 +#define MMC_DRV_3437_A1 10 +#define MMC_DRV_3429 11 +#define MMC_DRV_3451_A0 12 +#define MMC_DRV_3465 13 +#define MMC_DRV_3445 14 +#define MMC_DRV_3481 15 +#define MMC_DRV_3498 16 +/************** INT and Base address*****************/ +/*special settings for each one*/ + +#define MMC_ATSMB0_BASE SD0_SDIO_MMC_BASE_ADDR +#define MMC_ATMSB0_END (SD0_SDIO_MMC_BASE_ADDR + 0x3FF) + +#define MMC_ATSMB1_BASE SD1_SDIO_MMC_BASE_ADDR +#define MMC_ATMSB1_END (SD1_SDIO_MMC_BASE_ADDR + 0x3FF) + +#define MMC_ATSMB2_BASE SD2_SDIO_MMC_BASE_ADDR +#define MMC_ATMSB2_END (SD2_SDIO_MMC_BASE_ADDR + 0x3FF) + + +/************** INT and Base address*****************/ + +/* SD0 pin */ +#define GPIO_SD0_CD BIT4 +#define GPIO_SD0_Data (BIT6 | BIT5 | BIT4 | BIT3) +#define GPIO_SD0_WriteProtect BIT2 +#define GPIO_SD0_Command BIT1 +#define GPIO_SD0_Clock BIT0 +#define GPIO_SD0_POWER BIT7 +#define GPIO_SD0_18SEL BIT4 + +/* SD1 pin */ +#define GPIO_SD1_Data (BIT7 | BIT6 | BIT5 | BIT4 | BIT3 | BIT2 | BIT1 | BIT0) +#define GPIO_SD1_Command BIT1 +#define GPIO_SD1_Clock BIT0 +#define GPIO_SD1_CD BIT6 +#define GPIO_SD1_WriteProtect BIT5 +#define GPIO_SD1_POWER BIT4 +#define GPIO_SD1_RSTN BIT3 + +/* SD2 pin */ +#define GPIO_SD2_Data (BIT3 | BIT2 | BIT1 | BIT0) +#define GPIO_SD2_Command BIT4 +#define GPIO_SD2_Clock BIT5 +#define GPIO_SD2_CD BIT0 +#define GPIO_SD2_WriteProtect BIT7 +#define GPIO_SD2_POWER BIT6 + +/* SDIO Power pin */ +#define SDIO_WIFI_PWR BIT2 +#define SDIO_WIFI_INT BIT3 +#define SOIO_WIFI_WAKE_FUN BIT27 + +#define SD0_PIN (GPIO_SD0_Clock | GPIO_SD0_Command | GPIO_SD0_WriteProtect | GPIO_SD0_Data) +#define SD1_PIN (GPIO_SD1_Clock | GPIO_SD1_Command | GPIO_SD1_WriteProtect | GPIO_SD1_RSTN) +#define SD2_PIN (GPIO_SD2_Clock | GPIO_SD2_Command | GPIO_SD2_WriteProtect | GPIO_SD2_Data) + +/* PIN share switch */ +#define GPIO_SD1_PinShare BIT11 +#define GPIO_SD2_PinShare BIT28 + +#define SD0_CARD_PWR BIT1 +#define SD1_CARD_PWR BIT2 +#define SD2_CARD_PWR BIT3 + +/* IO Deive Strength and Slew Rate*/ +#define SD0_IO_Slew_Rate BIT1 +#define SD0_IO_Drive_Strength BIT0 + + + +/*common settings*/ + +#define NR_SG 128 + +/*=============================================*/ +/* WMT ATSMB Register Offset*/ +/*=============================================*/ +#define _CTL 0x00 /* 1 byte*/ +#define _CMD_IDX 0x01 /* 1 byte*/ +#define _RSP_TYPE 0x02 /* 1 byte*/ + +#define _CMD_ARG 0x04 /* 4 bytes*/ +#define _BUS_MODE 0x08 /* 1 byte*/ +#define _EXT_BUS_MODE 0x09 /* 1 byte*/ +#define _CTL2 0x0a /* 2 bytes*/ +#define _BLK_LEN 0x0c /* 2 bytes*/ +#define _BLK_CNT 0x0e /* 2 bytes*/ +#define _RSP_0 0x10 /* 1 bytes*/ +#define _RSP_1 0x11 /* 1 bytes*/ +#define _RSP_2 0x12 /* 1 bytes*/ +#define _RSP_3 0x13 /* 1 bytes*/ +#define _RSP_4 0x14 /* 1 bytes*/ +#define _RSP_5 0x15 /* 1 bytes*/ +#define _RSP_6 0x16 /* 1 bytes*/ +#define _RSP_7 0x17 /* 1 bytes*/ +#define _RSP_8 0x18 /* 1 bytes*/ +#define _RSP_9 0x19 /* 1 bytes*/ +#define _RSP_10 0x1a /* 1 bytes*/ +#define _RSP_11 0x1b /* 1 bytes*/ +#define _RSP_12 0x1c /* 1 bytes*/ +#define _RSP_13 0x1d /* 1 bytes*/ +#define _RSP_14 0x1e /* 1 bytes*/ +#define _RSP_15 0x1f /* 1 bytes*/ + +#define _CURBLK_CNT 0x20 /* 4 bytes*/ +#define _INT_MASK_0 0x24 /* 1 byte*/ +#define _INT_MASK_1 0x25 /* 1 byte*/ + +#define _SD_STS_0 0x28 /* 1 byte*/ +#define _SD_STS_1 0x29 /* 1 byte*/ +#define _SD_STS_2 0x2a /* 1 byte*/ +#define _SD_STS_3 0x2b /* 1 byte*/ +#define _RSP_TOUT 0x2c /* 1 byte*/ + +#define _CLK_SEL 0x30 /* 1 byte*/ /*Not used*/ + +#define _EXT_CTL 0x34 /* 1 byte*/ +#define _EXT_CTL_1 0x35 /* 1 byte*/ +#define _EXT_CTL_2 0x36 /* 1 byte*/ + +#define _SHDW_BLKLEN 0x38 /* 2 bytes*/ +#define _MAN_TUNE_VAL 0x3a /* 1 byte*/ +#define _SD_WRI_TUNE 0x3b /* 1 byte*/ +#define _TIMER_VAL 0x3c /* 2 bytes*/ + + +#define _PDMA_GCR 0x100 +#define _PDMA_IER 0x104 +#define _PDMA_ISR 0x108 +#define _PDMA_DESPR 0x10C +#define _PDMA_RBR 0x110 +#define _PDMA_DAR 0x114 +#define _PDMA_BAR 0x118 +#define _PDMA_CPR 0x11C +#define _PDMA_CCR 0X120 + + +/*=========================================*/ +/* SD0 PDMA related Registers */ +/*=========================================*/ +#define ATSMB0_PDMA_GCR (REG32_PTR(_PDMA_GCR + MMC_ATSMB0_BASE)) +#define ATSMB0_PDMA_IER (REG32_PTR(_PDMA_IER + MMC_ATSMB0_BASE)) +#define ATSMB0_PDMA_ISR (REG32_PTR(_PDMA_ISR + MMC_ATSMB0_BASE)) +#define ATSMB0_PDMA_DESPR (REG32_PTR(_PDMA_DESPR + MMC_ATSMB0_BASE)) +#define ATSMB0_PDMA_RBR (REG32_PTR(_PDMA_RBR + MMC_ATSMB0_BASE)) +#define ATSMB0_PDMA_DAR (REG32_PTR(_PDMA_DAR + MMC_ATSMB0_BASE)) +#define ATSMB0_PDMA_BAR (REG32_PTR(_PDMA_BAR + MMC_ATSMB0_BASE)) +#define ATSMB0_PDMA_CPR (REG32_PTR(_PDMA_CPR + MMC_ATSMB0_BASE)) +#define ATSMB0_PDMA_CCR (REG32_PTR(_PDMA_CCR + MMC_ATSMB0_BASE)) + + +/*=========================================*/ +/* SD0 Register pointer. */ +/*=========================================*/ +#define ATSMB0_CTL (REG8_PTR(_CTL + MMC_ATSMB0_BASE)) +#define ATSMB0_CMD_IDX (REG8_PTR(_CMD_IDX + MMC_ATSMB0_BASE)) +#define ATSMB0_RSP_TYPE (REG8_PTR(_RSP_TYPE + MMC_ATSMB0_BASE)) +#define ATSMB0_CMD_ARG (REG32_PTR(_CMD_ARG + MMC_ATSMB0_BASE)) +#define ATSMB0_BUS_MODE (REG8_PTR(_BUS_MODE + MMC_ATSMB0_BASE)) +#define ATSMB0_EXT_BUS_MODE (REG8_PTR(_EXT_BUS_MODE + MMC_ATSMB0_BASE)) +#define ATSMB0_CTL2 (REG16_PTR(_CTL2 + MMC_ATSMB0_BASE)) +#define ATSMB0_BLK_LEN (REG16_PTR(_BLK_LEN + MMC_ATSMB0_BASE)) +#define ATSMB0_BLK_CNT (REG16_PTR(_BLK_CNT + MMC_ATSMB0_BASE)) +#define ATSMB0_RSP_0 (REG8_PTR(_RSP_0 + MMC_ATSMB0_BASE)) +#define ATSMB0_RSP_1 (REG8_PTR(_RSP_1 + MMC_ATSMB0_BASE)) +#define ATSMB0_RSP_2 (REG8_PTR(_RSP_2 + MMC_ATSMB0_BASE)) +#define ATSMB0_RSP_3 (REG8_PTR(_RSP_3 + MMC_ATSMB0_BASE)) +#define ATSMB0_RSP_4 (REG8_PTR(_RSP_4 + MMC_ATSMB0_BASE)) +#define ATSMB0_RSP_5 (REG8_PTR(_RSP_5 + MMC_ATSMB0_BASE)) +#define ATSMB0_RSP_6 (REG8_PTR(_RSP_6 + MMC_ATSMB0_BASE)) +#define ATSMB0_RSP_7 (REG8_PTR(_RSP_7 + MMC_ATSMB0_BASE)) +#define ATSMB0_RSP_8 (REG8_PTR(_RSP_8 + MMC_ATSMB0_BASE)) +#define ATSMB0_RSP_9 (REG8_PTR(_RSP_9 + MMC_ATSMB0_BASE)) +#define ATSMB0_RSP_10 (REG8_PTR(_RSP_10 + MMC_ATSMB0_BASE)) +#define ATSMB0_RSP_11 (REG8_PTR(_RSP_11 + MMC_ATSMB0_BASE)) +#define ATSMB0_RSP_12 (REG8_PTR(_RSP_12 + MMC_ATSMB0_BASE)) +#define ATSMB0_RSP_13 (REG8_PTR(_RSP_13 + MMC_ATSMB0_BASE)) +#define ATSMB0_RSP_14 (REG8_PTR(_RSP_14 + MMC_ATSMB0_BASE)) +#define ATSMB0_RSP_15 (REG8_PTR(_RSP_15 + MMC_ATSMB0_BASE)) + +#define ATSMB0_CURBLK_CNT (REG32_PTR(_CURBLK_CNT + MMC_ATSMB0_BASE)) +#define ATSMB0_INT_MASK_0 (REG8_PTR(_INT_MASK_0 + MMC_ATSMB0_BASE)) +#define ATSMB0_INT_MASK_1 (REG8_PTR(_INT_MASK_1 + MMC_ATSMB0_BASE)) +#define ATSMB0_SD_STS_0 (REG8_PTR(_SD_STS_0 + MMC_ATSMB0_BASE)) +#define ATSMB0_SD_STS_1 (REG8_PTR(_SD_STS_1 + MMC_ATSMB0_BASE)) +#define ATSMB0_SD_STS_2 (REG8_PTR(_SD_STS_2 + MMC_ATSMB0_BASE)) +#define ATSMB0_SD_STS_3 (REG8_PTR(_SD_STS_3 + MMC_ATSMB0_BASE)) + +#define ATSMB0_RSP_TOUT (REG8_PTR(_RSP_TOUT + MMC_ATSMB0_BASE)) +#define ATSMB0_CLK_SEL (REG8_PTR(_CLK_SEL + MMC_ATSMB0_BASE)) +#define ATSMB0_EXT_CTL (REG8_PTR(_EXT_CTL + MMC_ATSMB0_BASE)) +#define ATSMB0_EXT_CTL_1 (REG8_PTR(_EXT_CTL_1 + MMC_ATSMB0_BASE)) +#define ATSMB0_EXT_CTL_2 (REG8_PTR(_EXT_CTL_2 + MMC_ATSMB0_BASE)) + +#define ATSMB0_SHDW_BLKLEN (REG16_PTR(_SHDW_BLKLEN + MMC_ATSMB0_BASE)) +#define ATSMB0_MAN_TUNE_VAL (REG8_PTR(_MAN_TUNE_VAL + MMC_ATSMB0_BASE)) +#define ATSMB0_SD_WRI_TUNE (REG8_PTR(_SD_WRI_TUNE+ MMC_ATSMB0_BASE)) +#define ATSMB0_TIMER_VAL (REG16_PTR(_TIMER_VAL + MMC_ATSMB0_BASE)) + +//#define ATSMB0_INT (REG8_PTR(0xd8140056)) + + + + +/*=========================================*/ +/* SD1 PDMA related Registers */ +/*=========================================*/ +#define ATSMB1_PDMA_GCR (REG32_PTR(_PDMA_GCR + MMC_ATSMB1_BASE)) +#define ATSMB1_PDMA_IER (REG32_PTR(_PDMA_IER + MMC_ATSMB1_BASE)) +#define ATSMB1_PDMA_ISR (REG32_PTR(_PDMA_ISR + MMC_ATSMB1_BASE)) +#define ATSMB1_PDMA_DESPR (REG32_PTR(_PDMA_DESPR + MMC_ATSMB1_BASE)) +#define ATSMB1_PDMA_RBR (REG32_PTR(_PDMA_RBR + MMC_ATSMB1_BASE)) +#define ATSMB1_PDMA_DAR (REG32_PTR(_PDMA_DAR + MMC_ATSMB1_BASE)) +#define ATSMB1_PDMA_BAR (REG32_PTR(_PDMA_BAR + MMC_ATSMB1_BASE)) +#define ATSMB1_PDMA_CPR (REG32_PTR(_PDMA_CPR + MMC_ATSMB1_BASE)) +#define ATSMB1_PDMA_CCR (REG32_PTR(_PDMA_CCR + MMC_ATSMB1_BASE)) + + +/*=========================================*/ +/* SD1 Register pointer. */ +/*=========================================*/ +#define ATSMB1_CTL (REG8_PTR(_CTL + MMC_ATSMB1_BASE)) +#define ATSMB1_CMD_IDX (REG8_PTR(_CMD_IDX + MMC_ATSMB1_BASE)) +#define ATSMB1_RSP_TYPE (REG8_PTR(_RSP_TYPE + MMC_ATSMB1_BASE)) +#define ATSMB1_CMD_ARG (REG32_PTR(_CMD_ARG + MMC_ATSMB1_BASE)) +#define ATSMB1_BUS_MODE (REG8_PTR(_BUS_MODE + MMC_ATSMB1_BASE)) +#define ATSMB1_EXT_BUS_MODE (REG8_PTR(_EXT_BUS_MODE + MMC_ATSMB1_BASE)) +#define ATSMB1_CTL2 (REG16_PTR(_CTL2 + MMC_ATSMB1_BASE)) +#define ATSMB1_BLK_LEN (REG16_PTR(_BLK_LEN + MMC_ATSMB1_BASE)) +#define ATSMB1_BLK_CNT (REG16_PTR(_BLK_CNT + MMC_ATSMB1_BASE)) +#define ATSMB1_RSP_0 (REG8_PTR(_RSP_0 + MMC_ATSMB1_BASE)) +#define ATSMB1_RSP_1 (REG8_PTR(_RSP_1 + MMC_ATSMB1_BASE)) +#define ATSMB1_RSP_2 (REG8_PTR(_RSP_2 + MMC_ATSMB1_BASE)) +#define ATSMB1_RSP_3 (REG8_PTR(_RSP_3 + MMC_ATSMB1_BASE)) +#define ATSMB1_RSP_4 (REG8_PTR(_RSP_4 + MMC_ATSMB1_BASE)) +#define ATSMB1_RSP_5 (REG8_PTR(_RSP_5 + MMC_ATSMB1_BASE)) +#define ATSMB1_RSP_6 (REG8_PTR(_RSP_6 + MMC_ATSMB1_BASE)) +#define ATSMB1_RSP_7 (REG8_PTR(_RSP_7 + MMC_ATSMB1_BASE)) +#define ATSMB1_RSP_8 (REG8_PTR(_RSP_8 + MMC_ATSMB1_BASE)) +#define ATSMB1_RSP_9 (REG8_PTR(_RSP_9 + MMC_ATSMB1_BASE)) +#define ATSMB1_RSP_10 (REG8_PTR(_RSP_10 + MMC_ATSMB1_BASE)) +#define ATSMB1_RSP_11 (REG8_PTR(_RSP_11 + MMC_ATSMB1_BASE)) +#define ATSMB1_RSP_12 (REG8_PTR(_RSP_12 + MMC_ATSMB1_BASE)) +#define ATSMB1_RSP_13 (REG8_PTR(_RSP_13 + MMC_ATSMB1_BASE)) +#define ATSMB1_RSP_14 (REG8_PTR(_RSP_14 + MMC_ATSMB1_BASE)) +#define ATSMB1_RSP_15 (REG8_PTR(_RSP_15 + MMC_ATSMB1_BASE)) + +#define ATSMB1_CURBLK_CNT (REG32_PTR(_CURBLK_CNT + MMC_ATSMB1_BASE)) +#define ATSMB1_INT_MASK_0 (REG8_PTR(_INT_MASK_0 + MMC_ATSMB1_BASE)) +#define ATSMB1_INT_MASK_1 (REG8_PTR(_INT_MASK_1 + MMC_ATSMB1_BASE)) +#define ATSMB1_SD_STS_0 (REG8_PTR(_SD_STS_0 + MMC_ATSMB1_BASE)) +#define ATSMB1_SD_STS_1 (REG8_PTR(_SD_STS_1 + MMC_ATSMB1_BASE)) +#define ATSMB1_SD_STS_2 (REG8_PTR(_SD_STS_2 + MMC_ATSMB1_BASE)) +#define ATSMB1_SD_STS_3 (REG8_PTR(_SD_STS_3 + MMC_ATSMB1_BASE)) + +#define ATSMB1_RSP_TOUT (REG8_PTR(_RSP_TOUT + MMC_ATSMB1_BASE)) +#define ATSMB1_CLK_SEL (REG8_PTR(_CLK_SEL + MMC_ATSMB1_BASE)) +#define ATSMB1_EXT_CTL (REG8_PTR(_EXT_CTL + MMC_ATSMB1_BASE)) +#define ATSMB1_EXT_CTL_1 (REG8_PTR(_EXT_CTL_1 + MMC_ATSMB1_BASE)) +#define ATSMB1_EXT_CTL_2 (REG8_PTR(_EXT_CTL_2 + MMC_ATSMB1_BASE)) + +#define ATSMB1_SHDW_BLKLEN (REG16_PTR(_SHDW_BLKLEN + MMC_ATSMB1_BASE)) +#define ATSMB1_MAN_TUNE_VAL (REG8_PTR(_MAN_TUNE_VAL + MMC_ATSMB1_BASE)) +#define ATSMB1_SD_WRI_TUNE (REG8_PTR(_SD_WRI_TUNE+ MMC_ATSMB1_BASE)) +#define ATSMB1_TIMER_VAL (REG16_PTR(_TIMER_VAL + MMC_ATSMB1_BASE)) + + + +/*=========================================*/ +/* SD2 PDMA related Registers */ +/*=========================================*/ +#define ATSMB2_PDMA_GCR (REG32_PTR(_PDMA_GCR + MMC_ATSMB2_BASE)) +#define ATSMB2_PDMA_IER (REG32_PTR(_PDMA_IER + MMC_ATSMB2_BASE)) +#define ATSMB2_PDMA_ISR (REG32_PTR(_PDMA_ISR + MMC_ATSMB2_BASE)) +#define ATSMB2_PDMA_DESPR (REG32_PTR(_PDMA_DESPR + MMC_ATSMB2_BASE)) +#define ATSMB2_PDMA_RBR (REG32_PTR(_PDMA_RBR + MMC_ATSMB2_BASE)) +#define ATSMB2_PDMA_DAR (REG32_PTR(_PDMA_DAR + MMC_ATSMB2_BASE)) +#define ATSMB2_PDMA_BAR (REG32_PTR(_PDMA_BAR + MMC_ATSMB2_BASE)) +#define ATSMB2_PDMA_CPR (REG32_PTR(_PDMA_CPR + MMC_ATSMB2_BASE)) +#define ATSMB2_PDMA_CCR (REG32_PTR(_PDMA_CCR + MMC_ATSMB2_BASE)) + + +/*=========================================*/ +/* SD2 Register pointer. */ +/*=========================================*/ +#define ATSMB2_CTL (REG8_PTR(_CTL + MMC_ATSMB2_BASE)) +#define ATSMB2_CMD_IDX (REG8_PTR(_CMD_IDX + MMC_ATSMB2_BASE)) +#define ATSMB2_RSP_TYPE (REG8_PTR(_RSP_TYPE + MMC_ATSMB2_BASE)) +#define ATSMB2_CMD_ARG (REG32_PTR(_CMD_ARG + MMC_ATSMB2_BASE)) +#define ATSMB2_BUS_MODE (REG8_PTR(_BUS_MODE + MMC_ATSMB2_BASE)) +#define ATSMB2_EXT_BUS_MODE (REG8_PTR(_EXT_BUS_MODE + MMC_ATSMB2_BASE)) +#define ATSMB2_CTL2 (REG16_PTR(_CTL2 + MMC_ATSMB2_BASE)) +#define ATSMB2_BLK_LEN (REG16_PTR(_BLK_LEN + MMC_ATSMB2_BASE)) +#define ATSMB2_BLK_CNT (REG16_PTR(_BLK_CNT + MMC_ATSMB2_BASE)) +#define ATSMB2_RSP_0 (REG8_PTR(_RSP_0 + MMC_ATSMB2_BASE)) +#define ATSMB2_RSP_1 (REG8_PTR(_RSP_1 + MMC_ATSMB2_BASE)) +#define ATSMB2_RSP_2 (REG8_PTR(_RSP_2 + MMC_ATSMB2_BASE)) +#define ATSMB2_RSP_3 (REG8_PTR(_RSP_3 + MMC_ATSMB2_BASE)) +#define ATSMB2_RSP_4 (REG8_PTR(_RSP_4 + MMC_ATSMB2_BASE)) +#define ATSMB2_RSP_5 (REG8_PTR(_RSP_5 + MMC_ATSMB2_BASE)) +#define ATSMB2_RSP_6 (REG8_PTR(_RSP_6 + MMC_ATSMB2_BASE)) +#define ATSMB2_RSP_7 (REG8_PTR(_RSP_7 + MMC_ATSMB2_BASE)) +#define ATSMB2_RSP_8 (REG8_PTR(_RSP_8 + MMC_ATSMB2_BASE)) +#define ATSMB2_RSP_9 (REG8_PTR(_RSP_9 + MMC_ATSMB2_BASE)) +#define ATSMB2_RSP_10 (REG8_PTR(_RSP_10 + MMC_ATSMB2_BASE)) +#define ATSMB2_RSP_11 (REG8_PTR(_RSP_11 + MMC_ATSMB2_BASE)) +#define ATSMB2_RSP_12 (REG8_PTR(_RSP_12 + MMC_ATSMB2_BASE)) +#define ATSMB2_RSP_13 (REG8_PTR(_RSP_13 + MMC_ATSMB2_BASE)) +#define ATSMB2_RSP_14 (REG8_PTR(_RSP_14 + MMC_ATSMB2_BASE)) +#define ATSMB2_RSP_15 (REG8_PTR(_RSP_15 + MMC_ATSMB2_BASE)) + +#define ATSMB2_CURBLK_CNT (REG32_PTR(_CURBLK_CNT + MMC_ATSMB2_BASE)) +#define ATSMB2_INT_MASK_0 (REG8_PTR(_INT_MASK_0 + MMC_ATSMB2_BASE)) +#define ATSMB2_INT_MASK_1 (REG8_PTR(_INT_MASK_1 + MMC_ATSMB2_BASE)) +#define ATSMB2_SD_STS_0 (REG8_PTR(_SD_STS_0 + MMC_ATSMB2_BASE)) +#define ATSMB2_SD_STS_1 (REG8_PTR(_SD_STS_1 + MMC_ATSMB2_BASE)) +#define ATSMB2_SD_STS_2 (REG8_PTR(_SD_STS_2 + MMC_ATSMB2_BASE)) +#define ATSMB2_SD_STS_3 (REG8_PTR(_SD_STS_3 + MMC_ATSMB2_BASE)) + +#define ATSMB2_RSP_TOUT (REG8_PTR(_RSP_TOUT + MMC_ATSMB2_BASE)) +#define ATSMB2_CLK_SEL (REG8_PTR(_CLK_SEL + MMC_ATSMB2_BASE)) +#define ATSMB2_EXT_CTL (REG8_PTR(_EXT_CTL + MMC_ATSMB2_BASE)) +#define ATSMB2_EXT_CTL_1 (REG8_PTR(_EXT_CTL_1 + MMC_ATSMB2_BASE)) +#define ATSMB2_EXT_CTL_2 (REG8_PTR(_EXT_CTL_2 + MMC_ATSMB2_BASE)) + +#define ATSMB2_SHDW_BLKLEN (REG16_PTR(_SHDW_BLKLEN + MMC_ATSMB2_BASE)) +#define ATSMB2_MAN_TUNE_VAL (REG8_PTR(_MAN_TUNE_VAL + MMC_ATSMB2_BASE)) +#define ATSMB2_SD_WRI_TUNE (REG8_PTR(_SD_WRI_TUNE+ MMC_ATSMB2_BASE)) +#define ATSMB2_TIMER_VAL (REG16_PTR(_TIMER_VAL + MMC_ATSMB2_BASE)) + + +/*=========================================*/ +/* Register usage const*/ +/*=========================================*/ +/*================================================================================*/ +/**/ +/* SD Host Register Bit Fields*/ +/**/ +/*================================================================================*/ +/**/ +/* Control Register*/ +#define ATSMB_START 0x1 +#define ATSMB_SPISTP BIT1 +#define ATSMB_RXTX BIT2 +#define ATSMB_FFRST BIT3 +#define ATSMB_CT (BIT4|BIT5|BIT6|BIT7) +/**/ +/* Command Index Register*/ + +/**/ +/*Response Type Register*/ +#define ATSMB_RT (BIT0|BIT1|BIT2|BIT3) +#define ATSMB_RY BIT4 + +/**/ +/* Command Argument Register 0,1,2,3*/ + +/**/ +/* Bus Mode Register*/ +#define ATSMB_SPI BIT0 +#define ATSMB_BUS_WIDTH_4 BIT1 +#define ATSMB_RW BIT2 +#define ATSMB_SPICRC BIT3 +#define ATSMB_CST BIT4 +#define ATSMB_SPICS BIT5 +#define ATSMB_SDPWR BIT6 +#define ATSMB_SFTRST BIT7 + +/**/ +/* Block Length Register 0,1*/ +#define ATSMB_BS_L (BIT0|BIT1|BIT2|BIT3|BIT4|BIT5|BIT6|BIT7) +#define ATSMB_BS_H (BIT8|BIT9|BIT10) +#define ATSMB_CFD BIT4 +#define ATSMB_INTEN BIT7 + +/**/ +/* Block Count Register 0,1*/ + +/**/ +/* Response Register*/ + +/**/ +/* Data Register*/ + +/**/ +/* Interrupt Mask Register 0*/ +#define ATSMB_THIE BIT0 +#define ATSMB_TEIE BIT1 +#define ATSMB_TAIE BIT2 +#define ATSMB_RHIE BIT3 +#define ATSMB_MULTI_XFER_DONE_EN BIT4 +#define ATSMB_BLOCK_XFER_DONE_EN BIT5 +#define ATSMB_CDIE BIT6 +#define ATSMB_DEVICE_INSERT_EN BIT7 + +/**/ +/* Interrupt Mask Register 1*/ +#define ATSMB_SDIO_EN BIT0 +#define ATSMB_RSP_DONE_EN BIT1 +#define ATSMB_RSP_TIMEOUT_EN BIT2 +#define ATSMB_AUTO_STOP_EN BIT3 +#define ATSMB_DATA_TIMEOUT_EN BIT4 +#define ATSMB_RSP_CRC_ERR_EN BIT5 +#define ATSMB_READ_CRC_ERR_EN BIT6 +#define ATSMB_WRITE_CRC_ERR_EN BIT7 + +/**/ +/* SD Status Register 0*/ +#define ATSMB_TH BIT0 +#define ATSMB_WRITE_PROTECT BIT1 /* 1. write protected is disabled.*/ +#define ATSMB_CARD_NOT_IN_SLOT BIT2 +#define ATSMB_CARD_NOT_IN_SLOT_GPI BIT3 +#define ATSMB_MULTI_XFER_DONE BIT4 +#define ATSMB_BLOCK_XFER_DONE BIT5 +#define ATSMB_SD_CD BIT6 /* what is its purpose?*/ +#define ATSMB_DEVICE_INSERT BIT7 + +/**/ +/* SD Status Register 1*/ +#define ATSMB_SDIO_INT BIT0 +#define ATSMB_RSP_DONE BIT1 +#define ATSMB_RSP_TIMEOUT BIT2 +#define ATSMB_AUTO_STOP BIT3 +#define ATSMB_DATA_TIMEOUT BIT4 +#define ATSMB_RSP_CRC_ERR BIT5 +#define ATSMB_READ_CRC_ERR BIT6 +#define ATSMB_WRITE_CRC_ERR BIT7 + +/**/ +/* SD Status Register 2*/ +#define ATSMB_RSP_BUSY BIT5 +#define ATSMB_CLK_FREEZ_STS BIT6 +#define ATSMB_CLK_FREEZ_EN BIT7 + + +/* SD Response types*/ +#define ATMSB_TYPE_R0 0 /* NONE response*/ +#define ATMSB_TYPE_R1 1 /* Basic response format*/ +#define ATMSB_TYPE_R2 2 /* R2 response. Used by ALL_SEND_CID(CMD2),*/ +/* SEND_CID(CMD10) and SEND_CSD(CMD9)*/ +#define ATMSB_TYPE_R3 3 /* R3 response. Used by SEND_APP_OP_COND(ACMD41)*/ +#define ATMSB_TYPE_R6 6 /* R6 response. Used by SEND_RELATIVE_ADDR(CMD3)*/ +#define ATMSB_TYPE_R7 7 /* R6 response. Used by SEND_RELATIVE_ADDR(CMD3)*/ +#define ATMSB_TYPE_R1b 9 +#define ATMSB_TYPE_R5 5 +#define ATMSB_TYPE_R4 4 + +/* + * SD PDMA + */ +struct SD_PDMA_REG { + unsigned long DMA_GCR; /* Rx00 */ + unsigned long DMA_IER; /* Rx04 */ + unsigned long DMA_ISR; /* Rx08 */ + unsigned long *DMA_DESPR; /* Rx0C */ + unsigned long DMA_RBR; /* Rx10 */ + unsigned long DMA_DAR; /* Rx14 */ + unsigned long DMA_BAR; /* Rx18 */ + unsigned long DMA_CPR; /* Rx1C */ + unsigned long DMA_CCR; /* RX20 */ + unsigned long resv[5]; /* RX2C-3C */ +}; +/* + * SD PDMA - DMA_GCR : DMA Global Control Register + */ +#define SD_PDMA_GCR_DMA_EN 0x00000001 /* [0] -- DMA controller enable*/ +#define SD_PDMA_GCR_SOFTRESET 0x00000100 /* [8] -- Software rest*/ + +/* + * SD PDMA - DMA_IER : DMA Interrupt Enable Register + */ +#define SD_PDMA_IER_INT_EN 0x00000001 /* [0] --DMA interrupt enable */ +/* + * SD PDMA - DMA_ISR : DMA Interrupt Status Register + */ +#define SD_PDMA_IER_INT_STS 0x00000001 /* [0] -- DMA interrupt status */ +/* + * SD PDMA - DMA_DESPR : DMA Descriptor base address Pointer Register + */ + +/* + * SD PDMA - DMA_RBR : DMA Residual Bytes Register + */ +#define SD_PDMA_RBR_End 0x80000000 /* [0] -- DMA interrupt status */ +#define SD_PDMA_RBR_Format 0x40000000 /* [0] -- DMA interrupt status */ +/* + * SD PDMA - DMA_DAR : DMA Data Address Register + */ + +/* + * SD PDMA - DMA_BAR : DMA Rbanch Address Register + */ + +/* + * SD PDMA - DMA_CPR : DMA Command Pointer Register + */ + +/* + * SD PDMA - DMA_CCR : DMAContext Control Register for Channel 0 + */ +#define SD_PDMA_READ 0x00 +#define SD_PDMA_WRITE 0x01 +#define SD_PDMA_CCR_RUN 0x00000080 +#define SD_PDMA_CCR_IF_to_peripheral 0x00000000 +#define SD_PDMA_CCR_peripheral_to_IF 0x00400000 +#define SD_PDMA_CCR_EvtCode 0x0000000f +#define SD_PDMA_CCR_Evt_no_status 0x00000000 +#define SD_PDMA_CCR_Evt_ff_underrun 0x00000001 +#define SD_PDMA_CCR_Evt_ff_overrun 0x00000002 +#define SD_PDMA_CCR_Evt_desp_read 0x00000003 +#define SD_PDMA_CCR_Evt_data_rw 0x00000004 +#define SD_PDMA_CCR_Evt_early_end 0x00000005 +#define SD_PDMA_CCR_Evt_success 0x0000000f + +/* + * PDMA Descriptor short + */ +struct SD_PDMA_DESC_S{ + unsigned long volatile ReqCount : 16; /* bit 0 -15 -Request count */ + unsigned long volatile i : 1; /* bit 16 -interrupt */ + unsigned long volatile reserve : 13; /* bit 17-29 -reserved */ + unsigned long volatile format : 1; /* bit 30 -The descriptor format */ + unsigned long volatile end : 1; /* bit 31 -End flag of descriptor list*/ + unsigned long volatile *DataBufferAddr; /* bit 31 -Data Buffer address */ +} ; + +/* + * PDMA Descriptor long + */ +struct SD_PDMA_DESC_L{ + unsigned long volatile ReqCount:16; /* bit 0-15-Request count */ + unsigned long volatile i:1; /* bit 16 -interrupt*/ + unsigned long volatile reserve:13; /* bit 17-29-reserved*/ + unsigned long volatile format:1; /* bit 30-The descriptor format */ + unsigned long volatile end:1; /* bit 31 -End flag of descriptor list*/ + unsigned long volatile *DataBufferAddr; /* bit 31-0 -Data Buffer address*/ + unsigned long volatile *BranchAddr; /* bit 31-2-Descriptor Branch address*/ + unsigned long volatile reserve0; /* bit 31-0-reserved*/ +}; + + +/**/ +/* DMA usage const for Rx08[Config]*/ +/**/ +#define DMA_CFG_WRITE 0x0 +#define DMA_CFG_READ 0x10000 +/* +#define DMA_CFG_8B 0x00 +#define DMA_CFG_16B 0x01 +#define DMA_CFG_32B 0x02 + +#define DMA_CFG_SINGLE 0x00 +#define DMA_CFG_INC4 0x10 +#define DMA_CFG_INC8 0x20 + +#define DMA_CFG_DATA 0x100 +#define DMA_CFG_PRIVILEDGE 0x200 +#define DMA_CFG_BUFFER 0x400 +#define DMA_CFG_CACHE 0x800 + +#define DMA_CFG_INC 0x1000 +#define DMA_CFG_READ 0x10000 +#define DMA_CFG_WRITE 0x0 + +#define DMA_CFG_NOINT 0x0 +#define DMA_CFG_ENTCINT 0x100000 +#define DMA_CFG_ENXFERINT 0x200000 +#define DMA_CFG_ENERRINT 0x400000 +#define DMA_CFG_ENFIFOINT 0x800000 + +// +// DMA usage const for RxC[Control] +// +#define DMA_CTL_ENABLE 0x1 +#define DMA_CTL_SOFTREQ 0x10 + +// +// DMA usage const for Rx10[Status] +// +#define DMA_STS_TC 0x1 +#define DMA_STS_AHB_ERROR 0x4 +#define DMA_STS_FIFO_EMPTY 0x8 +#define DMA_STS_BULK_COMPLETE 0x2 +*/ +/*=========================================*/ +/* structure definition.*/ +/*=========================================*/ +struct atsmb_host { + struct mmc_host *mmc; + spinlock_t lock; + struct resource *res; + void *base; + int regular_irq; + int dma_irq; + /* 2009/01/13 janshiue-s */ + unsigned long *DescVirAddr; + dma_addr_t DescPhyAddr; + unsigned int DescSize; + unsigned long *BufVirAddr; + dma_addr_t BufPhyAddr; + unsigned long DmaIntMask; + /* 2009/01/13 janshiue-s */ + struct mmc_request *mrq; + struct mmc_command *cmd; + struct mmc_data *data; + u32 pwr; + struct mmc_platform_data *plat; + unsigned int sg_len; + /* pio stuff */ + struct scatterlist *sg_ptr; + unsigned int sg_off; + unsigned int size; + /* to support sg, we need little loops when requesting data*/ + u32 opcode; + /*this code may different from the one in command. Eg. When we are accessing data*/ + unsigned char soft_timeout; + void *done_data; /* completion data */ + void (*done)(void *data);/* completion function */ + int current_clock; + +}; + +#define MAX_DESC_NUM 256 + +enum WMT_SD_FUN { + SD_MMC, + SDIO_WIFI, + SD_MAX_FUN +}; + +int atsmb_init_short_desc(unsigned long *DescAddr, unsigned int ReqCount, + unsigned long *BufferAddr, int End); +int atsmb_init_long_desc(unsigned long *DescAddr, unsigned int ReqCount, + unsigned long *BufferAddr, unsigned long *BranchAddr, int End); + +int atsmb1_init_short_desc(unsigned long *DescAddr, unsigned int ReqCount, + unsigned long *BufferAddr, int End); +int atsmb1_init_long_desc(unsigned long *DescAddr, unsigned int ReqCount, + unsigned long *BufferAddr, unsigned long *BranchAddr, int End); + +int atsmb2_init_short_desc(unsigned long *DescAddr, unsigned int ReqCount, + unsigned long *BufferAddr, int End); +int atsmb2_init_long_desc(unsigned long *DescAddr, unsigned int ReqCount, + unsigned long *BufferAddr, unsigned long *BranchAddr, int End); + + +#endif /* __MMC_ATSMB_H */ diff --git a/drivers/mmc/host/mmc_atsmb1.c b/drivers/mmc/host/mmc_atsmb1.c new file mode 100755 index 00000000..2cfefbcd --- /dev/null +++ b/drivers/mmc/host/mmc_atsmb1.c @@ -0,0 +1,2405 @@ +/*++ +linux/drivers/mmc/host/mmc_atsmb1.c + +Copyright (c) 2008 WonderMedia Technologies, 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, see <http://www.gnu.org/licenses/>. + +WonderMedia Technologies, Inc. +10F, 529, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C. +--*/ + +//#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/blkdev.h> +#include <linux/mmc/host.h> +#include <linux/mmc/card.h> +#include <linux/mmc/sd.h> +#include <linux/mmc/mmc.h> +#include <linux/mmc/sdio.h> +#include <linux/completion.h> +#include <linux/pagemap.h> +#include <linux/dma-mapping.h> +#include <asm/dma.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/memory.h> +#include <mach/hardware.h> +#include <asm/scatterlist.h> +#include <asm/sizes.h> +#include "mmc_atsmb.h" +//#include <mach/multicard.h> +#include <mach/irqs.h> + +#define mprintk + +/* chcek emmc card is fist boot, 1:fisrt boot, 0:suspend/resume */ +int atsmb1_first_boot; +#if 0 +#define DBG(host, fmt, args...) \ + pr_debug("%s: %s: " fmt, mmc_hostname(host->mmc), __func__ , args) +#endif +#define ATSMB_TIMEOUT_TIME (HZ*2) + +//add by jay,for modules support +static u64 wmt_sdmmc1_dma_mask = 0xffffffffUL; +static struct resource wmt_sdmmc1_resources[] = { + [0] = { + .start = SD1_SDIO_MMC_BASE_ADDR, + .end = (SD1_SDIO_MMC_BASE_ADDR + 0x3FF), + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = IRQ_SDC1, + .end = IRQ_SDC1, + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = IRQ_SDC1_DMA, + .end = IRQ_SDC1_DMA, + .flags = IORESOURCE_IRQ, + }, + /*2008/10/6 RichardHsu-s*/ + [3] = { + .start = IRQ_PMC_WAKEUP, + .end = IRQ_PMC_WAKEUP, + .flags = IORESOURCE_IRQ, + }, + /*2008/10/6 RichardHsu-e*/ +}; + + +// +static void atsmb1_release(struct device * dev) {} + +/*#ifdef CONFIG_MMC_DEBUG*/ +#define MORE_INFO +#if 0 +#define DBG(x...) printk(KERN_ALERT x) +#define DBGR(x...) printk(KERN_ALERT x) +#else +#define DBG(x...) do { } while (0) +#define DBGR(x...) do { } while (0) +#endif + +/* when Read CRC error occur, and clear read CRC error by software reset.*/ +void atsmb1_copy_reg(int direct) +{ + static u8 CTL, CMD_IDX, RSP_TYPE, BUS_MODE, INT_MASK_0, INT_MASK_1, SD_STS_0, SD_STS_1, SD_STS_2, SD_STS_3; + static u8 EXT_BUS_MODE, EXT_CTL_1, EXT_CTL_2, MAN_TUNE_VAL, SD_WRI_TUNE; + static u8 RSP_0, RSP_1, RSP_2, RSP_3, RSP_4, RSP_5, RSP_6, RSP_7; + static u8 RSP_8, RSP_9, RSP_10, RSP_11, RSP_12, RSP_13, RSP_14, RSP_15; + static u8 RSP_TOUT, CLK_SEL, EXT_CTL; + static u16 BLK_LEN, BLK_CNT, SHDW_BLKLEN, TIMER_VAL, CTL2; + static u32 CMD_ARG, CURBLK_CNT; + + /*direct 0: copy register to memory; 1: copy memory to register.*/ + if (direct == 0) { + CTL = *ATSMB1_CTL; + CMD_IDX = *ATSMB1_CMD_IDX; + RSP_TYPE = *ATSMB1_RSP_TYPE; + CMD_ARG = *ATSMB1_CMD_ARG; + BUS_MODE = *ATSMB1_BUS_MODE; + EXT_BUS_MODE = *ATSMB1_EXT_BUS_MODE; + CTL2 = *ATSMB1_CTL2; + BLK_LEN = *ATSMB1_BLK_LEN; + BLK_CNT = *ATSMB1_BLK_CNT; + RSP_0 = *ATSMB1_RSP_0; + RSP_1 = *ATSMB1_RSP_1; + RSP_2 = *ATSMB1_RSP_2; + RSP_3 = *ATSMB1_RSP_3; + RSP_4 = *ATSMB1_RSP_4; + RSP_5 = *ATSMB1_RSP_5; + RSP_6 = *ATSMB1_RSP_6; + RSP_7 = *ATSMB1_RSP_7; + RSP_8 = *ATSMB1_RSP_8; + RSP_9 = *ATSMB1_RSP_9; + RSP_10 = *ATSMB1_RSP_10; + RSP_11 = *ATSMB1_RSP_11; + RSP_12 = *ATSMB1_RSP_12; + RSP_13 = *ATSMB1_RSP_13; + RSP_14 = *ATSMB1_RSP_14; + RSP_15 = *ATSMB1_RSP_15; + CURBLK_CNT = *ATSMB1_CURBLK_CNT; + INT_MASK_0 = *ATSMB1_INT_MASK_0; + INT_MASK_1 = *ATSMB1_INT_MASK_1; + SD_STS_0 = *ATSMB1_SD_STS_0; + SD_STS_1 = *ATSMB1_SD_STS_1; + SD_STS_2 = *ATSMB1_SD_STS_2; + SD_STS_3 = *ATSMB1_SD_STS_3; + RSP_TOUT = *ATSMB1_RSP_TOUT; + CLK_SEL = *ATSMB1_CLK_SEL; + EXT_CTL = *ATSMB1_EXT_CTL; + EXT_CTL_1 = *ATSMB1_EXT_CTL_1; + EXT_CTL_2 = *ATSMB1_EXT_CTL_2; + SHDW_BLKLEN = *ATSMB1_SHDW_BLKLEN; + MAN_TUNE_VAL = *ATSMB1_MAN_TUNE_VAL; + SD_WRI_TUNE = *ATSMB1_SD_WRI_TUNE; + TIMER_VAL = *ATSMB1_TIMER_VAL; + } else { + *ATSMB1_CTL = CTL; + *ATSMB1_CMD_IDX = CMD_IDX; + *ATSMB1_RSP_TYPE = RSP_TYPE; + *ATSMB1_CMD_ARG = CMD_ARG; + *ATSMB1_BUS_MODE = BUS_MODE; + *ATSMB1_EXT_BUS_MODE = EXT_BUS_MODE; + *ATSMB1_CTL2 = CTL2; + *ATSMB1_BLK_LEN = BLK_LEN; + *ATSMB1_BLK_CNT = BLK_CNT; + *ATSMB1_RSP_0 = RSP_0; + *ATSMB1_RSP_1 = RSP_1; + *ATSMB1_RSP_2 = RSP_2; + *ATSMB1_RSP_3 = RSP_3; + *ATSMB1_RSP_4 = RSP_4; + *ATSMB1_RSP_5 = RSP_5; + *ATSMB1_RSP_6 = RSP_6; + *ATSMB1_RSP_7 = RSP_7; + *ATSMB1_RSP_8 = RSP_8; + *ATSMB1_RSP_9 = RSP_9; + *ATSMB1_RSP_10 = RSP_10; + *ATSMB1_RSP_11 = RSP_11; + *ATSMB1_RSP_12 = RSP_12; + *ATSMB1_RSP_13 = RSP_13; + *ATSMB1_RSP_14 = RSP_14; + *ATSMB1_RSP_15 = RSP_15; + *ATSMB1_CURBLK_CNT = CURBLK_CNT; + *ATSMB1_INT_MASK_0 = INT_MASK_0; + *ATSMB1_INT_MASK_1 = INT_MASK_1; + *ATSMB1_SD_STS_0 = SD_STS_0; + *ATSMB1_SD_STS_1 = SD_STS_1; + *ATSMB1_SD_STS_2 = SD_STS_2; + *ATSMB1_SD_STS_3 = SD_STS_3; + *ATSMB1_RSP_TOUT = RSP_TOUT; + *ATSMB1_CLK_SEL = CLK_SEL; + *ATSMB1_EXT_CTL = EXT_CTL; + *ATSMB1_EXT_CTL_1 = EXT_CTL_1; + *ATSMB1_EXT_CTL_2 = EXT_CTL_2; + *ATSMB1_SHDW_BLKLEN = SHDW_BLKLEN; + *ATSMB1_MAN_TUNE_VAL = MAN_TUNE_VAL; + *ATSMB1_SD_WRI_TUNE = SD_WRI_TUNE; + *ATSMB1_TIMER_VAL = TIMER_VAL; + } +} + +#if 0 +void atsmb1_dump_reg(struct atsmb_host *host) +{ + u8 CTL, CMD_IDX, RSP_TYPE, BUS_MODE, INT_MASK_0, INT_MASK_1, SD_STS_0, SD_STS_1, SD_STS_2, SD_STS_3; + u8 EXT_BUS_MODE, EXT_CTL_1, EXT_CTL_2, MAN_TUNE_VAL, SD_WRI_TUNE; + u8 RSP_0, RSP_1, RSP_2, RSP_3, RSP_4, RSP_5, RSP_6, RSP_7; + u8 RSP_8, RSP_9, RSP_10, RSP_11, RSP_12, RSP_13, RSP_14, RSP_15; + u8 RSP_TOUT, CLK_SEL, EXT_CTL; + u16 BLK_LEN, BLK_CNT, SHDW_BLKLEN, TIMER_VAL, CTL2; + u32 CMD_ARG, PDMA_GCR, PDMA_IER, PDMA_ISR, PDMA_DESPR, PDMA_RBR, PDMA_DAR, PDMA_BAR, PDMA_CPR, PDMA_CCR; + u32 CURBLK_CNT; + + CTL = *ATSMB1_CTL; + CMD_IDX = *ATSMB1_CMD_IDX; + RSP_TYPE = *ATSMB1_RSP_TYPE; + CMD_ARG = *ATSMB1_CMD_ARG; + BUS_MODE = *ATSMB1_BUS_MODE; + EXT_BUS_MODE = *ATSMB1_EXT_BUS_MODE; + CTL2 = *ATSMB1_CTL2; + BLK_LEN = *ATSMB1_BLK_LEN; + BLK_CNT = *ATSMB1_BLK_CNT; + RSP_0 = *ATSMB1_RSP_0; + RSP_1 = *ATSMB1_RSP_1; + RSP_2 = *ATSMB1_RSP_2; + RSP_3 = *ATSMB1_RSP_3; + RSP_4 = *ATSMB1_RSP_4; + RSP_5 = *ATSMB1_RSP_5; + RSP_6 = *ATSMB1_RSP_6; + RSP_7 = *ATSMB1_RSP_7; + RSP_8 = *ATSMB1_RSP_8; + RSP_9 = *ATSMB1_RSP_9; + RSP_10 = *ATSMB1_RSP_10; + RSP_11 = *ATSMB1_RSP_11; + RSP_12 = *ATSMB1_RSP_12; + RSP_13 = *ATSMB1_RSP_13; + RSP_14 = *ATSMB1_RSP_14; + RSP_15 = *ATSMB1_RSP_15; + CURBLK_CNT = *ATSMB1_CURBLK_CNT; + INT_MASK_0 = *ATSMB1_INT_MASK_0; + INT_MASK_1 = *ATSMB1_INT_MASK_1; + SD_STS_0 = *ATSMB1_SD_STS_0; + SD_STS_1 = *ATSMB1_SD_STS_1; + SD_STS_2 = *ATSMB1_SD_STS_2; + SD_STS_3 = *ATSMB1_SD_STS_3; + RSP_TOUT = *ATSMB1_RSP_TOUT; + CLK_SEL = *ATSMB1_CLK_SEL; + EXT_CTL = *ATSMB1_EXT_CTL; + EXT_CTL_1 = *ATSMB1_EXT_CTL_1; + EXT_CTL_2 = *ATSMB1_EXT_CTL_2; + SHDW_BLKLEN = *ATSMB1_SHDW_BLKLEN; + MAN_TUNE_VAL = *ATSMB1_MAN_TUNE_VAL; + SD_WRI_TUNE = *ATSMB1_SD_WRI_TUNE; + TIMER_VAL = *ATSMB1_TIMER_VAL; + + PDMA_GCR = *ATSMB1_PDMA_GCR; + PDMA_IER = *ATSMB1_PDMA_IER; + PDMA_ISR = *ATSMB1_PDMA_ISR; + PDMA_DESPR = *ATSMB1_PDMA_DESPR; + PDMA_RBR = *ATSMB1_PDMA_RBR; + PDMA_DAR = *ATSMB1_PDMA_DAR; + PDMA_BAR = *ATSMB1_PDMA_BAR; + PDMA_CPR = *ATSMB1_PDMA_CPR; + PDMA_CCR = *ATSMB1_PDMA_CCR; + + DBGR("\n+---------------------------Registers----------------------------+\n"); + + DBGR("%16s = 0x%8x |", "CTL", CTL); + DBGR("%16s = 0x%8x\n", "CMD_IDX", CMD_IDX); + + DBGR("%16s = 0x%8x |", "RSP_TYPE", RSP_TYPE); + DBGR("%16s = 0x%8x\n", "CMD_ARG", CMD_ARG); + + DBGR("%16s = 0x%8x |", "BUS_MODE", BUS_MODE); + DBGR("%16s = 0x%8x\n", "EXT_BUS_MODE", EXT_BUS_MODE); + + DBGR("%16s = 0x%8x |", "CTL2", CTL2); + DBGR("%16s = 0x%8x\n", "BLK_LEN", BLK_LEN); + + DBGR("%16s = 0x%8x |", "BLK_CNT", BLK_CNT); + DBGR("%16s = 0x%8x\n", "RSP_0", RSP_0); + + DBGR("%16s = 0x%8x |", "RSP_1", RSP_1); + DBGR("%16s = 0x%8x\n", "RSP_2", RSP_2); + + DBGR("%16s = 0x%8x |", "RSP_3", RSP_3); + DBGR("%16s = 0x%8x\n", "RSP_4", RSP_4); + + DBGR("%16s = 0x%8x |", "RSP_5", RSP_5); + DBGR("%16s = 0x%8x\n", "RSP_6", RSP_6); + + DBGR("%16s = 0x%8x |", "RSP_7", RSP_7); + DBGR("%16s = 0x%8x\n", "RSP_8", RSP_8); + + DBGR("%16s = 0x%8x |", "RSP_9", RSP_9); + DBGR("%16s = 0x%8x\n", "RSP_10", RSP_10); + + DBGR("%16s = 0x%8x |", "RSP_11", RSP_11); + DBGR("%16s = 0x%8x\n", "RSP_12", RSP_12); + + DBGR("%16s = 0x%8x |", "RSP_13", RSP_13); + DBGR("%16s = 0x%8x\n", "RSP_14", RSP_14); + + DBGR("%16s = 0x%8x\n", "RSP_15", RSP_15); + + DBGR("%16s = 0x%8x |", "CURBLK_CNT", CURBLK_CNT); + DBGR("%16s = 0x%8x\n", "INT_MASK_0", INT_MASK_0); + + DBGR("%16s = 0x%8x |", "INT_MASK_1", INT_MASK_1); + DBGR("%16s = 0x%8x\n", "SD_STS_0", SD_STS_0); + + DBGR("%16s = 0x%8x |", "SD_STS_1", SD_STS_1); + DBGR("%16s = 0x%8x\n", "SD_STS_2", SD_STS_2); + + DBGR("%16s = 0x%8x |", "SD_STS_3", SD_STS_3); + DBGR("%16s = 0x%8x\n", "RSP_TOUT", RSP_TOUT); + + DBGR("%16s = 0x%8x |", "CLK_SEL", CLK_SEL); + DBGR("%16s = 0x%8x\n", "EXT_CTL", EXT_CTL); + + DBGR("%16s = 0x%8x |", "EXT_CTL_1", EXT_CTL_1); + DBGR("%16s = 0x%8x\n", "EXT_CTL_2", EXT_CTL_2); + + DBGR("%16s = 0x%8x |", "SHDW_BLKLEN", SHDW_BLKLEN); + DBGR("%16s = 0x%8x\n", "MAN_TUNE_VAL", MAN_TUNE_VAL); + + DBGR("%16s = 0x%8x |", "SD_WRI_TUNE", SD_WRI_TUNE); + DBGR("%16s = 0x%8x\n", "TIMER_VAL", TIMER_VAL); + + DBGR("%16s = 0x%8x |", "PDMA_GCR", PDMA_GCR); + DBGR("%16s = 0x%8x\n", "PDMA_IER", PDMA_IER); + + DBGR("%16s = 0x%8x |", "PDMA_ISR", PDMA_ISR); + DBGR("%16s = 0x%8x\n", "PDMA_DESPR", PDMA_DESPR); + + DBGR("%16s = 0x%8x |", "PDMA_RBR", PDMA_RBR); + DBGR("%16s = 0x%8x\n", "PDMA_DAR", PDMA_DAR); + + DBGR("%16s = 0x%8x |", "PDMA_BAR", PDMA_BAR); + DBGR("%16s = 0x%8x\n", "PDMA_CPR", PDMA_CPR); + + DBGR("%16s = 0x%8x |", "PDMA_CCR", PDMA_CCR); + DBGR("\n+----------------------------------------------------------------+\n"); +} +#else +void atsmb1_dump_reg(struct atsmb_host *host) {} +#endif + +unsigned int fmax1 = 515633; +unsigned int MMC1_DRIVER_VERSION; +int SD1_function = 0; /*0: normal SD/MMC card reader*/ +int SDXC1_function; + + +int SCC1_ID(void){ + unsigned short val; + + val = REG16_VAL(SYSTEM_CFG_CTRL_BASE_ADDR + 0x2); + return val; +} + +int get_chip_version1(void) /*2008/05/01 janshiue modify for A1 chip*/ +{ + u32 tmp; + + tmp = REG32_VAL(SYSTEM_CFG_CTRL_BASE_ADDR); + tmp = ((tmp & 0xF00) >> 4) + 0x90 + ((tmp & 0xFF) - 1); + return tmp; +} + +void get_driver_version1(void) +{ + if (SCC1_ID() == 0x3426) { + if (get_chip_version1() < 0xA1) + MMC1_DRIVER_VERSION = MMC_DRV_3426_A0; + else if (get_chip_version1() == 0xA1) + MMC1_DRIVER_VERSION = MMC_DRV_3426_A1; + else + MMC1_DRIVER_VERSION = MMC_DRV_3426_A2; + } else if (SCC1_ID() == 0x3437) { + if (get_chip_version1() < 0xA1) + MMC1_DRIVER_VERSION = MMC_DRV_3437_A0; + else + MMC1_DRIVER_VERSION = MMC_DRV_3437_A1; + } else if (SCC1_ID() == 0x3429) { + MMC1_DRIVER_VERSION = MMC_DRV_3429; + } else if (SCC1_ID() == 0x3451) { + if (get_chip_version1() < 0xA1) + MMC1_DRIVER_VERSION = MMC_DRV_3451_A0; + } else if (SCC1_ID() == 0x3465) { + MMC1_DRIVER_VERSION = MMC_DRV_3465; + } else if (SCC1_ID() == 0x3445) { + MMC1_DRIVER_VERSION = MMC_DRV_3445; + } else if (SCC1_ID() == 0x3481) { + MMC1_DRIVER_VERSION = MMC_DRV_3481; + } else if (SCC1_ID() == 0x3498) { + MMC1_DRIVER_VERSION = MMC_DRV_3498; + } +} +/*2008/10/6 RichardHsu-e*/ + +/********************************************************************** +Name : atsmb1_alloc_desc +Function : To config PDMA descriptor setting. +Calls : +Called by : +Parameter : +Author : Janshiue Wu +History : +***********************************************************************/ +static inline int atsmb1_alloc_desc(struct atsmb_host *host, + unsigned int bytes) +{ + void *DescPool = NULL; + DBG("[%s] s\n",__func__); + + DescPool = dma_alloc_coherent(host->mmc->parent, bytes, &(host->DescPhyAddr), GFP_KERNEL); + if (!DescPool) { + DBG("can't allocal desc pool=%8X %8X\n", DescPool, host->DescPhyAddr); + DBG("[%s] e1\n",__func__); + return -1; + } + DBG("allocal desc pool=%8X %8X\n", DescPool, host->DescPhyAddr); + host->DescVirAddr = (unsigned long *)DescPool; + host->DescSize = bytes; + DBG("[%s] e2\n",__func__); + return 0; +} + +/********************************************************************** +Name : atsmb1_config_desc +Function : To config PDMA descriptor setting. +Calls : +Called by : +Parameter : +Author : Janshiue Wu +History : +***********************************************************************/ +static inline void atsmb1_config_desc(unsigned long *DescAddr, + unsigned long *BufferAddr, + unsigned long Blk_Cnt) +{ + + int i = 0 ; + unsigned long *CurDes = DescAddr; + DBG("[%s] s\n",__func__); + + /* the first Descriptor store for 1 Block data + * (512 bytes) + */ + for (i = 0 ; i < 3 ; i++) { + atsmb1_init_short_desc(CurDes, 0x80, BufferAddr, 0); + BufferAddr += 0x20; + CurDes += sizeof(struct SD_PDMA_DESC_S)/4; + } + if (Blk_Cnt > 1) { + atsmb1_init_long_desc(CurDes, 0x80, BufferAddr, CurDes + (sizeof(struct SD_PDMA_DESC_L)/4), 0); + BufferAddr += 0x20; + CurDes += sizeof(struct SD_PDMA_DESC_L)/4; + /* the following Descriptor store the rest Blocks data + * (Blk_Cnt - 1) * 512 bytes + */ + atsmb1_init_long_desc(CurDes, (Blk_Cnt - 1) * 512, + BufferAddr, CurDes + (sizeof(struct SD_PDMA_DESC_L)/4), 1); + } else { + atsmb1_init_long_desc(CurDes, 0x80, BufferAddr, CurDes + (sizeof(struct SD_PDMA_DESC_L)/4), 1); + } + DBG("[%s] e\n",__func__); +} +/********************************************************************** +Name : atsmb1_config_dma +Function : To set des/src address, byte count to transfer, and DMA channel settings, + and DMA ctrl. register. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static inline void atsmb1_config_dma(unsigned long config_dir, + unsigned long dma_mask, + struct atsmb_host *host) +{ + DBG("[%s] s\n",__func__); + + /* Enable DMA */ + *ATSMB1_PDMA_GCR = SD_PDMA_GCR_DMA_EN; + *ATSMB1_PDMA_GCR = SD_PDMA_GCR_SOFTRESET; + *ATSMB1_PDMA_GCR = SD_PDMA_GCR_DMA_EN; + /*open interrupt*/ + *ATSMB1_PDMA_IER = SD_PDMA_IER_INT_EN; + /*Make sure host could co-work with DMA*/ + *ATSMB1_SD_STS_2 |= ATSMB_CLK_FREEZ_EN; + /*Set timer timeout value*/ + + /*If clock is 390KHz*/ + if (host->current_clock < 400000) + *ATSMB1_TIMER_VAL = 0x200; /*1024*512*(1/390K) second*/ + else + *ATSMB1_TIMER_VAL = 0x1fff; /*why not to be 0xffff?*/ + + /*clear all DMA INT status for safety*/ + *ATSMB1_PDMA_ISR |= SD_PDMA_IER_INT_STS; + + /* hook desc */ + *ATSMB1_PDMA_DESPR = host->DescPhyAddr; + if (config_dir == DMA_CFG_WRITE) + *ATSMB1_PDMA_CCR &= SD_PDMA_CCR_IF_to_peripheral; + else + *ATSMB1_PDMA_CCR |= SD_PDMA_CCR_peripheral_to_IF; + + host->DmaIntMask = dma_mask; /*save success event*/ + + *ATSMB1_PDMA_CCR |= SD_PDMA_CCR_RUN; + DBG("[%s] e\n",__func__); +} + +/********************************************************************** +Name : atsmb1_prep_cmd +Function : +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static inline void atsmb1_prep_cmd(struct atsmb_host *host, + u32 opcode, + u32 arg, + unsigned int flags, + u16 blk_len, + u16 blk_cnt, + unsigned char int_maks_0, + unsigned char int_mask_1, + unsigned char cmd_type, + unsigned char op) +{ + DBG("[%s] s\n",__func__); + + /*set cmd operation code and arguments.*/ + host->opcode = opcode; + *ATSMB1_CMD_IDX = opcode; /* host->opcode is set for further use in ISR.*/ + *ATSMB1_CMD_ARG = arg; + +#if 0 /* Fixme to support SPI mode, James Tian*/ + if ((flags && MMC_RSP_NONE) == MMC_RSP_NONE) + *ATSMB1_RSP_TYPE = ATMSB_TYPE_R0; + else if ((flags && MMC_RSP_R1) == MMC_RSP_R1) + *ATSMB1_RSP_TYPE = ATMSB_TYPE_R1; + else if ((flags && MMC_RSP_R1B) == MMC_RSP_R1B) + *ATSMB1_RSP_TYPE = ATMSB_TYPE_R1b; + else if ((flags && MMC_RSP_R2) == MMC_RSP_R2) + *ATSMB1_RSP_TYPE = ATMSB_TYPE_R2; + else if ((flags && MMC_RSP_R3) == MMC_RSP_R3) + *ATSMB1_RSP_TYPE = ATMSB_TYPE_R3; + else if ((flags && MMC_RSP_R6) == MMC_RSP_R6) + *ATSMB1_RSP_TYPE = ((opcode != SD_SEND_IF_COND) ? ATMSB_TYPE_R6 : ATMSB_TYPE_R7); + else + *ATSMB1_RSP_TYPE = ATMSB_TYPE_R0; +#endif + +#if 1 + /*set cmd response type*/ + switch (flags) { + case MMC_RSP_NONE | MMC_CMD_AC: + case MMC_RSP_NONE | MMC_CMD_BC: + *ATSMB1_RSP_TYPE = ATMSB_TYPE_R0; + break; + case MMC_RSP_R1 | MMC_CMD_ADTC: + case MMC_RSP_R1 | MMC_CMD_AC: + *ATSMB1_RSP_TYPE = ATMSB_TYPE_R1; + break; + case MMC_RSP_R1B | MMC_CMD_AC: + *ATSMB1_RSP_TYPE = ATMSB_TYPE_R1b; + break; + case MMC_RSP_R2 | MMC_CMD_BCR: + case MMC_RSP_R2 | MMC_CMD_AC: + *ATSMB1_RSP_TYPE = ATMSB_TYPE_R2; + break; + case MMC_RSP_R3 | MMC_CMD_BCR: + *ATSMB1_RSP_TYPE = ATMSB_TYPE_R3; + break; + case MMC_RSP_R6 | MMC_CMD_BCR: /*MMC_RSP_R6 = MMC_RSP_R7.*/ + *ATSMB1_RSP_TYPE = ((opcode != SD_SEND_IF_COND) ? + ATMSB_TYPE_R6 : ATMSB_TYPE_R7); + break; + case MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_AC: + case MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC: + *ATSMB1_RSP_TYPE = ATMSB_TYPE_R5; + break; + case MMC_RSP_SPI_R4 | MMC_RSP_R4 | MMC_CMD_BCR: + *ATSMB1_RSP_TYPE = ATMSB_TYPE_R4; + break; + default: + *ATSMB1_RSP_TYPE = ATMSB_TYPE_R0; + break; + } +#endif + /*SDIO cmd 52 , 53*/ + if ((opcode == SD_IO_RW_DIRECT) || + (opcode == SD_IO_RW_EXTENDED)) { + *ATSMB1_RSP_TYPE = ATMSB_TYPE_R5; + *ATSMB1_RSP_TYPE |= BIT6; + } + /*SDIO cmd 5*/ + if ((opcode == SD_IO_SEND_OP_COND) && + ((flags & (MMC_RSP_PRESENT| + MMC_RSP_136| + MMC_RSP_CRC| + MMC_RSP_BUSY| + MMC_RSP_OPCODE)) == MMC_RSP_R4)) { + *ATSMB1_RSP_TYPE = ATMSB_TYPE_R4; + *ATSMB1_RSP_TYPE |= BIT6; + } + + /*reset Response FIFO*/ + *ATSMB1_CTL |= 0x08; + + /* SD Host enable Clock */ + *ATSMB1_BUS_MODE |= ATSMB_CST; + + /*Set Cmd-Rsp Timeout to be maximum value.*/ + *ATSMB1_RSP_TOUT = 0xFE; + + /*clear all status registers for safety*/ + *ATSMB1_SD_STS_0 |= 0xff; + *ATSMB1_SD_STS_1 |= 0xff; + *ATSMB1_SD_STS_2 |= 0xff; + //*ATSMB1_SD_STS_2 |= 0x7f; + *ATSMB1_SD_STS_3 |= 0xff; + + //set block length and block count for cmd requesting data + *ATSMB1_BLK_LEN &=~(0x07ff); + *ATSMB1_BLK_LEN |= blk_len; + //*ATSMB1_SHDW_BLKLEN = blk_len; + *ATSMB1_BLK_CNT = blk_cnt; + + + *ATSMB1_INT_MASK_0 |= int_maks_0; + *ATSMB1_INT_MASK_1 |= int_mask_1; + + //Set Auto stop for Multi-block access + if(cmd_type == 3 || cmd_type == 4) + { + //auto stop command set. + *ATSMB1_EXT_CTL |= 0x01; + +/* + * Enable transaction abort. + * When CRC error occurs, CMD 12 would be automatically issued. + * That is why we cannot enable R/W CRC error INTs. + * If we enable CRC error INT, we would handle this INT in ISR and then issue CMD 12 via software. + */ + *ATSMB1_BLK_LEN |= 0x0800; + } + + /*Set read or write*/ + if (op == 1) + *ATSMB1_CTL &= ~(0x04); + else if (op == 2) + *ATSMB1_CTL |= 0x04; + + /*for Non data access command, command type is 0.*/ + *ATSMB1_CTL &= 0x0F; + *ATSMB1_CTL |= (cmd_type<<4); + DBG("[%s] e\n",__func__); +} + +static inline void atsmb1_issue_cmd(void) +{ + *ATSMB1_CTL |= ATSMB_START; + wmb(); +} +/********************************************************************** +Name : atsmb1_request_end +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static void +atsmb1_request_end(struct atsmb_host *host, struct mmc_request *mrq) +{ + DBG("[%s] s\n",__func__); + /* + * Need to drop the host lock here; mmc_request_done may call + * back into the driver... + */ + spin_unlock(&host->lock); + /*DBG("100");*/ + mmc_request_done(host->mmc, mrq); + /*DBG("101\n");*/ + spin_lock(&host->lock); + DBG("[%s] e\n",__func__); +} + +/********************************************************************** +Name : atsmb1_data_done +Function : +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +void atsmb1_wait_done(void *data) +{ + struct atsmb_host *host = (struct atsmb_host *) data; + DBG("[%s] s\n",__func__); + + WARN_ON(host->done_data == NULL); + complete(host->done_data); + host->done_data = NULL; + host->done = NULL; + DBG("[%s] e\n",__func__); +} + +/********************************************************************** +Name : atsmb1_start_data +Function : If we start data, there must be only four cases. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static void atsmb1_start_data(struct atsmb_host *host) +{ + DECLARE_COMPLETION(complete); + unsigned char cmd_type = 0; + unsigned char op = 0; /*0: non-operation; 1:read; 2: write*/ + unsigned char mask_0 = 0; + unsigned char mask_1 = 0; + unsigned long dma_mask = 0; + + struct mmc_data *data = host->data; + struct mmc_command *cmd = host->cmd; + + struct scatterlist *sg = NULL; + unsigned int sg_len = 0; + + unsigned int total_blks = 0; /*total block number to transfer*/ + u32 card_addr = 0; + unsigned long dma_len = 0; + unsigned long total_dma_len = 0; + dma_addr_t dma_phy = 0; /* physical address used for DMA*/ + unsigned int dma_times = 0; /*times dma need to transfer*/ + unsigned int dma_loop = 0; + unsigned int sg_num = 0; + int loop_cnt = 10000; + unsigned int sg_transfer_len = 0; /*record each time dma transfer sg length */ + + DBG("[%s] s\n",__func__); + data->bytes_xfered = 0; + cmd->error = 0; + data->error = 0; + + /*for loop*/ + sg = data->sg; + sg_len = data->sg_len; + + dma_times = (sg_len / MAX_DESC_NUM); + if (sg_len % MAX_DESC_NUM) + dma_times++; + DBG("dma_times = %d sg_len = %d sg = %x\n", dma_times, sg_len, sg); + card_addr = cmd->arg; /*may be it is block-addressed, or byte-addressed.*/ + total_blks = data->blocks; + dma_map_sg(&(host->mmc->class_dev), sg, sg_len, + ((data->flags)&MMC_DATA_READ) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + + + for (dma_loop = 1 ; dma_loop <= dma_times; dma_loop++, sg_len -= sg_transfer_len) { + DBG("dma_loop = %d sg_len = %d sg_transfer_len = %d\n", dma_loop, sg_len, sg_transfer_len); + if (dma_loop < dma_times) + sg_transfer_len = MAX_DESC_NUM; + else + sg_transfer_len = sg_len; + DBG("sg_transfer_len = %d\n", sg_transfer_len); + /* + *Firstly, check and wait till card is in the transfer state. + *For our hardware, we can not consider + *the card has successfully tranfered its state from data/rcv to trans, + *when auto stop INT occurs. + */ + loop_cnt = 10000; + do { + if (host->cmd->opcode == SD_IO_RW_EXTENDED) + break; + loop_cnt--; + WARN_ON(loop_cnt == 0); + host->done_data = &complete; + host->done = &atsmb1_wait_done; + host->soft_timeout = 1; + atsmb1_prep_cmd(host, + MMC_SEND_STATUS, + (host->mmc->card->rca)<<16, + MMC_RSP_R1 | MMC_CMD_AC, + 0, + 0, + 0, /*mask_0*/ + ATSMB_RSP_DONE_EN + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN, + 0, /*cmd type*/ + 0); /*read or write*/ + atsmb1_issue_cmd(); + DBG("%16s = 0x%8x |", "INT_MASK_1", *ATSMB1_INT_MASK_1); + DBG("%16s = 0x%8x \n", "SD_STS_1", *ATSMB1_SD_STS_1); + /*ISR would completes it.*/ + wait_for_completion_timeout(&complete, ATSMB_TIMEOUT_TIME); + + WARN_ON(host->soft_timeout == 1); + if (host->soft_timeout == 1) { + DBG("%s soft_timeout.\n", __func__); + atsmb1_dump_reg(host); + } + if (cmd->error != MMC_ERR_NONE) { + cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("Getting Status failed.\n"); + goto end; + } + } while ((cmd->resp[0] & 0x1f00) != 0x900 && loop_cnt > 0); /*wait for trans state.*/ + + /* + * Now, we can safely issue read/write command. + * We can not consider this request as multi-block acess or single one via opcode, + * as request is splitted into sgs. + * Some sgs may be single one, some sgs may be multi one. + */ + + dma_phy = sg_dma_address(sg); + dma_len = sg_dma_len(sg); + DBG("dma_len = %d data->blksz = %d sg_len = %d\n", dma_len, data->blksz, sg_len); + /*SDIO read/write*/ + if (host->cmd->opcode == SD_IO_RW_EXTENDED) { + /*Single Block read/write*/ + if ((dma_len / (data->blksz)) == 1 && (sg_len == 1)) { + /* read operation*/ + if (data->flags & MMC_DATA_READ) { + host->opcode = SD_IO_RW_EXTENDED; + cmd_type = 2; + op = 1; + mask_0 = 0; /*BLOCK_XFER_DONE INT skipped, we use DMA TC INT*/ + mask_1 = (//ATSMB_SDIO_EN + ATSMB_READ_CRC_ERR_EN + |ATSMB_DATA_TIMEOUT_EN + /*Data Timeout and CRC error */ + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN); + /*Resp Timeout and CRC error */ + dma_mask = SD_PDMA_CCR_Evt_success; + DBG("[%s]SR opcode = %d type = 0x%x op = 0x%x m_0 = 0x%x m_1 = 0x%x d_m = 0x%x\n", + __func__,host->opcode,cmd_type,op,mask_0,mask_1,dma_mask); + } else { + /* write operation*/ + host->opcode = SD_IO_RW_EXTENDED; + cmd_type = 1; + op = 2; + /*====That is what we want===== DMA TC INT skipped*/ + mask_0 = ATSMB_BLOCK_XFER_DONE_EN; + mask_1 = (//ATSMB_SDIO_EN + ATSMB_WRITE_CRC_ERR_EN + |ATSMB_DATA_TIMEOUT_EN + /*Data Timeout and CRC error */ + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN); + /*Resp Timeout and CRC error */ + dma_mask = 0; + DBG("[%s]SW opcode = %d type = 0x%x op = 0x%x m_0 = 0x%x m_1 = 0x%x d_m = 0x%x\n", + __func__,host->opcode,cmd_type,op,mask_0,mask_1,dma_mask); + } + } else { + /*Multiple Block read/write*/ + /* read operation*/ + if (data->flags & MMC_DATA_READ) { + host->opcode = SD_IO_RW_EXTENDED; + cmd_type = 6; + op = 1; + mask_0 = 0; /*MULTI_XFER_DONE_EN skipped*/ + mask_1 = (//ATSMB_SDIO_EN /*====That is what we want=====*/ + ATSMB_READ_CRC_ERR_EN + |ATSMB_DATA_TIMEOUT_EN + /*Data Timeout and CRC error */ + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN); + /*Resp Timeout and CRC error */ + dma_mask = SD_PDMA_CCR_Evt_success; + DBG("[%s]MR opcode = %d type = 0x%x op = 0x%x m_0 = 0x%x m_1 = 0x%x d_m = 0x%x\n", + __func__,host->opcode,cmd_type,op,mask_0,mask_1,dma_mask); + } else { + /* write operation*/ + host->opcode = SD_IO_RW_EXTENDED; + cmd_type = 5; + op = 2; + mask_0 = ATSMB_MULTI_XFER_DONE_EN;//ATSMB_BLOCK_XFER_DONE_EN; /*MULTI_XFER_DONE INT skipped*/ + mask_1 = (//ATSMB_SDIO_EN /*====That is what we want=====*/ + ATSMB_WRITE_CRC_ERR_EN + |ATSMB_DATA_TIMEOUT_EN + /*Data Timeout and CRC error */ + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN); + /*Resp Timeout and CRC error */ + dma_mask = 0; + DBG("[%s]MR opcode = %d type = 0x%x op = 0x%x m_0 = 0x%x m_1 = 0x%x d_m = 0x%x\n", + __func__,host->opcode,cmd_type,op,mask_0,mask_1,dma_mask); + } + } + + } else { + if ((dma_len / (data->blksz)) == 1 && (sg_len == 1)) { + if (data->flags & MMC_DATA_READ) {/* read operation*/ + host->opcode = MMC_READ_SINGLE_BLOCK; + cmd_type = 2; + op = 1; + mask_0 = 0; /*BLOCK_XFER_DONE INT skipped, we use DMA TC INT*/ + mask_1 = (ATSMB_READ_CRC_ERR_EN + |ATSMB_DATA_TIMEOUT_EN + /*Data Timeout and CRC error */ + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN); + /*Resp Timeout and CRC error */ + dma_mask = SD_PDMA_CCR_Evt_success; + } else {/*write operation*/ + host->opcode = MMC_WRITE_BLOCK; + cmd_type = 1; + op = 2; + /*====That is what we want===== DMA TC INT skipped*/ + mask_0 = ATSMB_BLOCK_XFER_DONE_EN; + mask_1 = (ATSMB_WRITE_CRC_ERR_EN + |ATSMB_DATA_TIMEOUT_EN + /*Data Timeout and CRC error */ + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN); + /*Resp Timeout and CRC error */ + dma_mask = 0; + } + } else { /*more than one*/ + if (data->flags&MMC_DATA_READ) {/* read operation*/ + host->opcode = MMC_READ_MULTIPLE_BLOCK; + cmd_type = 4; + op = 1; + mask_0 = 0; /*MULTI_XFER_DONE_EN skipped*/ + mask_1 = (ATSMB_AUTO_STOP_EN /*====That is what we want=====*/ + |ATSMB_DATA_TIMEOUT_EN + /*Data Timeout and CRC error */ + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN); + /*Resp Timeout and CRC error */ + dma_mask = 0; + } else {/*write operation*/ + host->opcode = MMC_WRITE_MULTIPLE_BLOCK; + cmd_type = 3; + op = 2; + mask_0 = 0; /*MULTI_XFER_DONE INT skipped*/ + mask_1 = (ATSMB_AUTO_STOP_EN /*====That is what we want=====*/ + |ATSMB_DATA_TIMEOUT_EN + /*Data Timeout and CRC error */ + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN); + /*Resp Timeout and CRC error */ + dma_mask = 0; + } + } + } + /*To controller every sg done*/ + host->done_data = &complete; + host->done = &atsmb1_wait_done; + /*sleep till ISR wakes us*/ + host->soft_timeout = 1; /*If INT comes early than software timer, it would be cleared.*/ + + total_dma_len = 0; + DBG("host->DescVirAddr = %x host->DescSize=%x\n", host->DescVirAddr, host->DescSize); + memset(host->DescVirAddr, 0, host->DescSize); + for (sg_num = 0 ; sg_num < sg_transfer_len ; sg++, sg_num++) { + + /* + * Now, we can safely issue read/write command. + * We can not consider this request as multi-block acess or single one via opcode, + * as request is splitted into sgs. + * Some sgs may be single one, some sgs may be multi one. + */ + + dma_phy = sg_dma_address(sg); + dma_len = sg_dma_len(sg); + total_dma_len = total_dma_len + dma_len; + DBG("sg_num=%d sg_transfer_len=%d sg=%x sg_len=%d total_dma_len=%d dma_len=%d\n", + sg_num, sg_transfer_len, sg, sg_len, total_dma_len, dma_len); + /*2009/01/15 janshiue add*/ + if (sg_num < sg_transfer_len - 1) { + /* means the last descporitor */ + atsmb1_init_short_desc( + host->DescVirAddr + (sg_num * sizeof(struct SD_PDMA_DESC_S)/4), + dma_len, (unsigned long *)dma_phy, 0); + } else { + atsmb1_init_short_desc( + host->DescVirAddr + (sg_num * sizeof(struct SD_PDMA_DESC_S)/4), + dma_len, (unsigned long *)dma_phy, 1); + } + /*2009/01/15 janshiue add*/ + + } + /*operate our hardware*/ + atsmb1_prep_cmd(host, + host->opcode, + /*arg, may be byte addressed, may be block addressed.*/ + card_addr, + cmd->flags, + data->blksz - 1, /* in fact, it is useless.*/ + /* for single one, it is useless. but for multi one, */ + /* it would be used to tell auto stop function whether it is done.*/ + total_dma_len/(data->blksz), + mask_0, + mask_1, + cmd_type, + op); + + atsmb1_config_dma((op == 1) ? DMA_CFG_READ : DMA_CFG_WRITE, + dma_mask, + host); + + atsmb1_issue_cmd(); + wait_for_completion_timeout(&complete, + ATSMB_TIMEOUT_TIME*sg_transfer_len); /*ISR would completes it.*/ + + /* When the address of request plus length equal card bound, + * force this stop command response as pass. Eason 2012/4/20 */ + if (cmd->resp[0] == 0x80000b00) { + /*This caes used for SD2.0 and after MMC4.1 version*/ + if (card_addr+(total_dma_len/data->blksz) == host->mmc->card->csd.capacity) { + cmd->resp[0] = 0x00000b00; + /*printk("card_addr = %08X, card_length = %08X,card capacity = %08X\n", + card_addr,(total_dma_len/data->blksz),host->mmc->card->csd.capacity); + printk("card_resp[0]=%08X, addr = %08X\n",cmd->resp[0],cmd->resp);*/ + } + + /* This caes used for SD1.0 and before MMC 4.1 */ + if ((card_addr/data->blksz)+(total_dma_len/data->blksz) == host->mmc->card->csd.capacity) { + cmd->resp[0] = 0x00000b00; + /*printk("Eason test: cmd->arg = %08X, data->blksz = %08X, length = %08X\n", + card_addr,data->blksz,(total_dma_len/data->blksz));*/ + } + } + + if (host->soft_timeout == 1) { + atsmb1_dump_reg(host); + //*ATSMB1_BUS_MODE |= ATSMB_SFTRST; + *ATSMB1_PDMA_GCR = SD_PDMA_GCR_SOFTRESET; + /*disable INT */ + *ATSMB1_INT_MASK_0 &= ~(ATSMB_BLOCK_XFER_DONE_EN | ATSMB_MULTI_XFER_DONE_EN); + *ATSMB1_INT_MASK_1 &= ~(ATSMB_WRITE_CRC_ERR_EN|ATSMB_READ_CRC_ERR_EN|ATSMB_RSP_CRC_ERR_EN + |ATSMB_DATA_TIMEOUT_EN|ATSMB_AUTO_STOP_EN|ATSMB_RSP_TIMEOUT_EN|ATSMB_RSP_DONE_EN); + + cmd->error = -ETIMEDOUT; + data->error = -ETIMEDOUT; + } + + WARN_ON(host->soft_timeout == 1); + + /*check everything goes okay or not*/ + if (cmd->error != MMC_ERR_NONE + || data->error != MMC_ERR_NONE) { + DBG("CMD or Data failed error=%X DescVirAddr=%8X dma_phy=%8X dma_mask = %x\n", + cmd->error, host->DescVirAddr, dma_phy, host->DmaIntMask); + goto end; + } +// card_addr += total_dma_len>>(mmc_card_blockaddr(host->mmc->card_selected) ? 9 : 0); + card_addr += total_dma_len>>(mmc_card_blockaddr(host->mmc->card) ? 9 : 0); //zhf: modified by James Tian + data->bytes_xfered += total_dma_len; + + + } +// dma_unmap_sg(&(host->mmc->class_dev), data->sg, data->sg_len, +// ((data->flags)&MMC_DATA_READ) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + WARN_ON(total_blks != (data->bytes_xfered / data->blksz)); + host->opcode = 0; +end: + dma_unmap_sg(&(host->mmc->class_dev), data->sg, data->sg_len, + ((data->flags)&MMC_DATA_READ) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + spin_lock(&host->lock); + atsmb1_request_end(host, host->mrq); + spin_unlock(&host->lock); + DBG("[%s] e\n",__func__); +} + + +/********************************************************************** +Name : atsmb1_cmd_with_data_back +Function : +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static void atsmb1_cmd_with_data_back(struct atsmb_host *host) +{ + DECLARE_COMPLETION(complete); + + struct scatterlist *sg = NULL; + unsigned int sg_len = 0; + DBG("[%s] s\n",__func__); + /*for loop*/ + sg = host->data->sg; + sg_len = host->data->sg_len; + /*To controller every sg done*/ + host->done_data = &complete; + host->done = &atsmb1_wait_done; + dma_map_sg(&(host->mmc->class_dev), sg, sg_len, DMA_FROM_DEVICE); + + /*2009/01/15 janshiue add*/ + memset(host->DescVirAddr, 0, host->DescSize); + atsmb1_init_long_desc(host->DescVirAddr, sg_dma_len(sg), (unsigned long *)sg_dma_address(sg), 0, 1); + /*2009/01/15 janshiue add*/ + /*prepare for cmd*/ + atsmb1_prep_cmd(host, /*host*/ + host->cmd->opcode, /*opcode*/ + host->cmd->arg, /*arg*/ + host->cmd->flags, /*flags*/ + sg_dma_len(sg)-1, /*block length*/ + 0, /*block size: It looks like useless*/ + 0, /*int_mask_0*/ + (ATSMB_RSP_CRC_ERR_EN|ATSMB_RSP_TIMEOUT_EN + |ATSMB_READ_CRC_ERR_EN |ATSMB_DATA_TIMEOUT_EN), /*int_mask_1*/ + 2, /*cmd_type*/ + 1); /*op*/ + + atsmb1_config_dma(DMA_CFG_READ, + SD_PDMA_CCR_Evt_success, + host); + atsmb1_issue_cmd(); + /*ISR would completes it.*/ + wait_for_completion_timeout(&complete, ATSMB_TIMEOUT_TIME); + + dma_unmap_sg(&(host->mmc->class_dev), host->data->sg, host->data->sg_len, + ((host->data->flags)&MMC_DATA_READ) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + + spin_lock(&host->lock); + atsmb1_request_end(host, host->mrq); + spin_unlock(&host->lock); + DBG("[%s] e\n",__func__); +} +/********************************************************************** +Name : atsmb1_start_cmd +Function : +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static void atsmb1_start_cmd(struct atsmb_host *host) +{ + unsigned char int_mask_0,int_mask_1; + int_mask_0 = 0; + int_mask_1 = ATSMB_RSP_DONE_EN|ATSMB_RSP_CRC_ERR_EN|ATSMB_RSP_TIMEOUT_EN; + + DBG("[%s] s\n",__func__); + + atsmb1_prep_cmd(host, + host->cmd->opcode, + host->cmd->arg, + host->cmd->flags, + 0, /*useless*/ + 0, /*useless*/ + int_mask_0, /*mask_0*/ + int_mask_1, /*mask_1*/ + 0, /*cmd type*/ + 0); /*read or write*/ + atsmb1_issue_cmd(); + DBG("[%s] e\n",__func__); +} +/********************************************************************** +Name : atsmb1_fmt_check_rsp +Function : +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static inline void atsmb1_fmt_check_rsp(struct atsmb_host *host) +{ + + u8 tmp_resp[4] = {0}; + int i, j, k; + DBG("[%s] s\n",__func__); + if (host->cmd->flags != (MMC_RSP_R2 | MMC_CMD_AC) + && host->cmd->flags != (MMC_RSP_R2 | MMC_CMD_BCR)) { + for (j = 0, k = 1; j < 4; j++, k++) + tmp_resp[j] = *(REG8_PTR(_RSP_0 + MMC_ATSMB1_BASE+k)); + + host->cmd->resp[0] = (tmp_resp[0] << 24) | + (tmp_resp[1]<<16) | (tmp_resp[2]<<8) | (tmp_resp[3]); + } else { + for (i = 0, k = 1; i < 4; i++) { /*R2 has 4 u32 response.*/ + for (j = 0; j < 4; j++) { + if (k < 16) + tmp_resp[j] = *(REG8_PTR(_RSP_0 + MMC_ATSMB1_BASE+k)); + else /* k =16*/ + tmp_resp[j] = *(ATSMB1_RSP_0); + k++; + } + host->cmd->resp[i] = (tmp_resp[0]<<24) | (tmp_resp[1]<<16) | + (tmp_resp[2]<<8) | (tmp_resp[3]); + } + } + + /* + * For the situation that we need response, + * but response registers give us all zeros, we consider this operation timeout. + */ + if (host->cmd->flags != (MMC_RSP_NONE | MMC_CMD_AC) + && host->cmd->flags != (MMC_RSP_NONE | MMC_CMD_BC)) { + if (host->cmd->resp[0] == 0 && host->cmd->resp[1] == 0 + && host->cmd->resp[2] == 0 && host->cmd->resp[3] == 0) { + host->cmd->error = -ETIMEDOUT; //zhf: modified by James Tian + } + } + DBG("[%s] e\n",__func__); +} +/********************************************************************** +Name : atsmb1_get_slot_status +Function : Our host only supports one slot. +Calls : +Called by : +Parameter : +returns : 1: in slot; 0: not in slot. +Author : Leo Lee +History : +***********************************************************************/ +static int atsmb1_get_slot_status(struct mmc_host *mmc) +{ +// struct atsmb_host *host = mmc_priv(mmc); + unsigned char status_0 = 0; +// unsigned long flags; + unsigned long ret = 0; + DBG("[%s] s\n",__func__); +// spin_lock_irqsave(&host->lock, flags); // Vincent Li mark out for CONFIG_PREEMPT_RT + status_0 = *ATSMB1_SD_STS_0; +// spin_unlock_irqrestore(&host->lock, flags); // Vincent Li mark out for CONFIG_PREEMPT_RT + /* after WM3400 A1 ATSMB_CARD_IN_SLOT_GPI = 1 means not in slot*/ + if (MMC1_DRIVER_VERSION >= MMC_DRV_3426_A0) { + ret = ((status_0 & ATSMB_CARD_NOT_IN_SLOT_GPI) ? 0 : 1); + DBG("[%s] e1\n",__func__); + return ret; + } else { + ret = ((status_0 & ATSMB_CARD_NOT_IN_SLOT_GPI) ? 1 : 0); + DBG("[%s] e2\n",__func__); + return ret; + } + DBG("[%s] e3\n",__func__); + return 0; +} + +/********************************************************************** +Name : atsmb1_init_short_desc +Function : +Calls : +Called by : +Parameter : +Author : Janshiue Wu +History : +***********************************************************************/ +int atsmb1_init_short_desc(unsigned long *DescAddr, unsigned int ReqCount, unsigned long *BufferAddr, int End) +{ + struct SD_PDMA_DESC_S *CurDes_S; + DBG("[%s] s\n",__func__); + CurDes_S = (struct SD_PDMA_DESC_S *) DescAddr; + CurDes_S->ReqCount = ReqCount; + CurDes_S->i = 0; + CurDes_S->format = 0; + CurDes_S->DataBufferAddr = BufferAddr; + if (End) { + CurDes_S->end = 1; + CurDes_S->i = 1; + } + DBG("[%s] e\n",__func__); + return 0; +} + +/********************************************************************** +Name : atsmb_init_long_desc +Function : +Calls : +Called by : +Parameter : +Author : Janshiue Wu +History : +***********************************************************************/ +int atsmb1_init_long_desc(unsigned long *DescAddr, unsigned int ReqCount, + unsigned long *BufferAddr, unsigned long *BranchAddr, int End) +{ + struct SD_PDMA_DESC_L *CurDes_L; + DBG("[%s] s\n",__func__); + CurDes_L = (struct SD_PDMA_DESC_L *) DescAddr; + CurDes_L->ReqCount = ReqCount; + CurDes_L->i = 0; + CurDes_L->format = 1; + CurDes_L->DataBufferAddr = BufferAddr; + CurDes_L->BranchAddr = BranchAddr; + if (End) { + CurDes_L->end = 1; + CurDes_L->i = 1; + } + DBG("[%s] e\n",__func__); + return 0; +} + +/********************************************************************** +Name : atsmb1_dma_isr +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static irqreturn_t atsmb1_dma_isr(int irq, void *dev_id) +{ + + struct atsmb_host *host = dev_id; + u8 status_0, status_1, status_2, status_3; + u32 pdma_event_code = 0; + DBG("[%s] s\n",__func__); + + disable_irq_nosync(irq); + spin_lock(&host->lock); + /*Get INT status*/ + status_0 = *ATSMB1_SD_STS_0; + status_1 = *ATSMB1_SD_STS_1; + status_2 = *ATSMB1_SD_STS_2; + status_3 = *ATSMB1_SD_STS_3; + /* after WM3426 A0 using PDMA */ + if (MMC1_DRIVER_VERSION >= MMC_DRV_3426_A0) { + + pdma_event_code = *ATSMB1_PDMA_CCR & SD_PDMA_CCR_EvtCode; + + /* clear INT status to notify HW clear EventCode*/ + *ATSMB1_PDMA_ISR |= SD_PDMA_IER_INT_STS; + + /*printk("dma_isr event code = %X\n", *ATSMB1_PDMA_CCR);*/ + /*We expect cmd with data sending back or read single block cmd run here.*/ + if (pdma_event_code == SD_PDMA_CCR_Evt_success) { + /*means need to update the data->error and cmd->error*/ + if (host->DmaIntMask == SD_PDMA_CCR_Evt_success) { + if ( host->data != NULL) { + host->data->error = MMC_ERR_NONE; + host->cmd->error = MMC_ERR_NONE; + } else { + DBG("dma_isr1 host->data is NULL\n"); + /*disable INT*/ + *ATSMB1_PDMA_IER &= ~SD_PDMA_IER_INT_EN; + goto out; + } + } + /*else do nothing*/ + DBG("dma_isr PDMA OK\n"); + /*atsmb1_dump_reg(host);*/ + } + /*But unluckily, we should also be prepare for all kinds of error situation.*/ + else { + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBGR("** dma_isr PDMA fail** event code = %X\n", *ATSMB1_PDMA_CCR); + atsmb1_dump_reg(host); + } + if (host->DmaIntMask == SD_PDMA_CCR_Evt_success) + atsmb1_fmt_check_rsp(host); + + /*disable INT*/ + *ATSMB1_PDMA_IER &= ~SD_PDMA_IER_INT_EN; + } + + /*wake up some one who is sleeping.*/ + if ((pdma_event_code != SD_PDMA_CCR_Evt_success) || (host->DmaIntMask == SD_PDMA_CCR_Evt_success)) { + if (host->done_data) {/* We only use done_data when requesting data.*/ + host->soft_timeout = 0; + host->done(host); + } else + atsmb1_request_end(host, host->mrq); /*for cmd with data sending back.*/ + } + +out: + spin_unlock(&host->lock); + enable_irq(irq); + DBG("[%s] e\n",__func__); + return IRQ_HANDLED; +} + +/********************************************************************** +Name : atsmb1_regular_isr +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +//static irqreturn_t atsmb_regular_isr(int irq, void *dev_id, struct pt_regs *regs) +irqreturn_t atsmb1_regular_isr(int irq, void *dev_id) +{ + + struct atsmb_host *host = dev_id; + u8 status_0, status_1, status_2, status_3,mask_0,mask_1; + u32 pdma_sts; + + DBG("[%s] s\n",__func__); + WARN_ON(host == NULL); + + disable_irq_nosync(irq); + spin_lock(&host->lock); + + /*Get INT status*/ + status_0 = *ATSMB1_SD_STS_0; + status_1 = *ATSMB1_SD_STS_1; + status_2 = *ATSMB1_SD_STS_2; + status_3 = *ATSMB1_SD_STS_3; + + mask_0 = *ATSMB1_INT_MASK_0; + mask_1 = *ATSMB1_INT_MASK_1; + /******************************************************* + card insert interrupt + ********************************************************/ + if ((status_0 & ATSMB_DEVICE_INSERT) /*Status Set and IRQ enabled*/ + /*To aviod the situation that we intentionally disable IRQ to do rescan.*/ + && (*ATSMB1_INT_MASK_0 & 0x80)) { + + if (host->mmc->ops->get_slot_status(host->mmc)) { + host->mmc->scan_retry = 3; + host->mmc->card_scan_status = false; + } else { + host->mmc->scan_retry = 0; + host->mmc->card_scan_status = false; + } + + mmc_detect_change(host->mmc, HZ/2); + /*Taipei Side Request: Disable INSERT IRQ when doing rescan.*/ + //*ATSMB1_INT_MASK_0 &= (~0x80);/* or 40?*/ //zhf: marked by James Tian + /*Then we clear the INT status*/ + //iowrite32(ATSMB_DEVICE_INSERT, host->base+_SD_STS_0); + *ATSMB1_SD_STS_0 |= ATSMB_DEVICE_INSERT; + spin_unlock(&host->lock); + enable_irq(irq); + DBG("[%s] e1\n",__func__); + return IRQ_HANDLED; + } + + if ((status_1 & mask_1)& ATSMB_SDIO_INT) { + spin_unlock(&host->lock); + mmc_signal_sdio_irq(host->mmc); + + if (((status_1 & mask_1) == ATSMB_SDIO_INT) && ((status_0 & mask_0) == 0)) { + + enable_irq(irq); + DBG("[%s] e2\n",__func__); + + return IRQ_HANDLED; + } + spin_lock(&host->lock); + } + pdma_sts = *ATSMB1_PDMA_CCR; + + if (((status_0 & mask_0) | (status_1 & mask_1)) == 0) { + spin_unlock(&host->lock); + enable_irq(irq); + DBG("[%s] e3\n",__func__); + return IRQ_HANDLED; + } + /******************************************************* + command interrupt. + *******************************************************/ + /*for write single block*/ + if (host->opcode == MMC_WRITE_BLOCK) { + if ((status_0 & ATSMB_BLOCK_XFER_DONE) + && (status_1 & ATSMB_RSP_DONE)) { + host->data->error = MMC_ERR_NONE; + host->cmd->error = MMC_ERR_NONE; + /* okay, what we want.*/ + } else { + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("[%s] err1\n",__func__); + atsmb1_dump_reg(host); + } + } else if (host->opcode == MMC_WRITE_MULTIPLE_BLOCK + || host->opcode == MMC_READ_MULTIPLE_BLOCK) { + if ((status_1 & (ATSMB_AUTO_STOP|ATSMB_RSP_DONE)) + && (status_0 & ATSMB_MULTI_XFER_DONE)) { + /*If CRC error occurs, I think this INT would not occrs.*/ + /*okay, that is what we want.*/ + host->data->error = MMC_ERR_NONE; + host->cmd->error = MMC_ERR_NONE; + } else { + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("[%s] err2\n",__func__); + atsmb1_dump_reg(host); + + } + } else if (host->opcode == MMC_READ_SINGLE_BLOCK) {/* we want DMA TC, run here, must be error.*/ + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("[%s] err3\n",__func__); + atsmb1_dump_reg(host); + } else if (host->opcode == SD_IO_RW_EXTENDED){ + /*Write operation*/ + if (*ATSMB1_CTL & BIT2) { + if ((*ATSMB1_CTL & 0xf0) == 0x10) { /*single block write*/ + if ((status_0 & ATSMB_BLOCK_XFER_DONE) + && (status_1 & ATSMB_RSP_DONE)) { + host->data->error = MMC_ERR_NONE; + host->cmd->error = MMC_ERR_NONE; + /* okay, what we want.*/ + } else { + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("[%s] err4 status_0 = %x status_1 = %x\n",__func__,status_0,status_1); + } + + } else if ((*ATSMB1_CTL & 0xf0) == 0x50) { + if ((status_0 & ATSMB_MULTI_XFER_DONE) + && (status_1 & ATSMB_RSP_DONE)) { + host->data->error = MMC_ERR_NONE; + host->cmd->error = MMC_ERR_NONE; + /* okay, what we want.*/ + } else { + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("[%s] err4-2 status_0 = %x status_1 = %x\n",__func__,status_0,status_1); + } + } else { + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("[%s] err4-3 status_0 = %x status_1 = %x\n",__func__,status_0,status_1); + } + } + else { + /*Read operation*/ + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("[%s] err5\n",__func__); + } + + + } else { +/* command, not request data*/ +/* the command which need data sending back,*/ +/* like switch_function, send_ext_csd, send_scr, send_num_wr_blocks.*/ +/* NOTICE: we also send status before reading or writing data, so SEND_STATUS should be excluded.*/ + if (host->data && host->opcode != MMC_SEND_STATUS) { + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("[%s] err6\n",__func__); + atsmb1_dump_reg(host); + } else { /* Just command, no need data sending back.*/ + if (status_1 & ATSMB_RSP_DONE) { + /*Firstly, check data-response is busy or not.*/ + if (host->cmd->flags == (MMC_RSP_R1B | MMC_CMD_AC)) { + int i = 10000; + + while (status_2 & ATSMB_RSP_BUSY) { + status_2 = *ATSMB1_SD_STS_2; + if (--i == 0) + break; + DBG(" IRQ:Status_2 = %d, busy!\n", status_2); + } + if (i == 0) + printk("[MMC driver] Error :SD data-response always busy!"); + } +#if 1 +/*for our host, even no card in slot, for SEND_STATUS also returns no error.*/ +/*The protocol layer depends on SEND_STATUS to check whether card is in slot or not.*/ +/*In fact, we can also avoid this situation by checking the response whether they are all zeros.*/ + if (!atsmb1_get_slot_status(host->mmc) && host->opcode == MMC_SEND_STATUS) { + host->cmd->retries = 0; /* No retry.*/ +// host->cmd->error = MMC_ERR_INVALID; + host->cmd->error = -EINVAL; + } else +#endif + host->cmd->error = MMC_ERR_NONE; + } else { + if (status_1 & ATSMB_RSP_TIMEOUT) {/* RSP_Timeout .*/ +// host->cmd->error = MMC_ERR_TIMEOUT; + host->cmd->error = -ETIMEDOUT; + DBG("[%s] err7\n",__func__); + } else {/*or RSP CRC error*/ +// host->cmd->error = MMC_ERR_BADCRC; + host->cmd->error = -EILSEQ; + DBG("[%s] err8\n",__func__); + } + atsmb1_dump_reg(host); + } + } + } + atsmb1_fmt_check_rsp(host); + + /*disable INT */ + *ATSMB1_INT_MASK_0 &= ~(ATSMB_BLOCK_XFER_DONE_EN | ATSMB_MULTI_XFER_DONE_EN); + *ATSMB1_INT_MASK_1 &= ~(ATSMB_WRITE_CRC_ERR_EN|ATSMB_READ_CRC_ERR_EN|ATSMB_RSP_CRC_ERR_EN + |ATSMB_DATA_TIMEOUT_EN|ATSMB_AUTO_STOP_EN|ATSMB_RSP_TIMEOUT_EN|ATSMB_RSP_DONE_EN); + + + /*clear INT status. In fact, we will clear again before issuing new command.*/ + *ATSMB1_SD_STS_0 |= status_0; + *ATSMB1_SD_STS_1 |= status_1; + + /* when read CRC error occur, and the status can't write one to clear. + * To clear read CRC error status , can do software reset. This is HW bug. 2013/3/21*/ + if ((*ATSMB1_SD_STS_1 & BIT6) == 0x40) { + DBG("[%s] host1 CMD%d Read CRC error occur\n",__func__,host->cmd->opcode); + /* Save SD card register */ + atsmb1_copy_reg(0); + /* Software reset */ + *ATSMB1_BUS_MODE |= BIT7; + /* restore SD card register */ + atsmb1_copy_reg(1); + } + + if (*ATSMB1_PDMA_ISR & SD_PDMA_IER_INT_STS) + *ATSMB1_PDMA_ISR |= SD_PDMA_IER_INT_STS; + + /*wake up some one who is sleeping.*/ + if (host->done_data) { /* We only use done_data when requesting data.*/ + host->soft_timeout = 0; + host->done(host); + } else + atsmb1_request_end(host, host->mrq); /*for cmd without data.*/ + + spin_unlock(&host->lock); + enable_irq(irq); + DBG("[%s] e4\n",__func__); + return IRQ_HANDLED; +} +EXPORT_SYMBOL(atsmb1_regular_isr); + +/********************************************************************** +Name : atsmb1_get_ro +Function :. +Calls : +Called by : +Parameter : +returns : 0 : write protection is disabled. 1: write protection is enabled. +Author : Leo Lee +History : +***********************************************************************/ +int atsmb1_get_ro(struct mmc_host *mmc) +{ + struct atsmb_host *host = mmc_priv(mmc); + unsigned char status_0 = 0; + unsigned long flags; + unsigned long ret = 0; + DBG("[%s] s\n",__func__); + spin_lock_irqsave(&host->lock, flags); + status_0 = *ATSMB1_SD_STS_0; + spin_unlock_irqrestore(&host->lock, flags); + DBG("[%s]\nstatus_0 = 0x%x\n", __func__,status_0); + ret = ((status_0 & ATSMB_WRITE_PROTECT) ? 0 : 1); + DBG("[%s] e\n",__func__); + return ret; +} +/********************************************************************** +Name : atsmb1_dump_host_regs +Function : +Calls : +Called by : +Parameter : +returns : +Author : Leo Lee +History : +***********************************************************************/ +void atsmb1_dump_host_regs(struct mmc_host *mmc) +{ + struct atsmb_host *host = mmc_priv(mmc); + DBG("[%s] s\n",__func__); + atsmb1_dump_reg(host); + DBG("[%s] e\n",__func__); +} +EXPORT_SYMBOL(atsmb1_dump_host_regs); + +/********************************************************************** +Name : atsmb_enable_sdio_irq +Function : +Calls : +Called by : +Parameter : +returns : +Author : Tommy Huang +History : +***********************************************************************/ +static void atsmb1_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + struct atsmb_host *host = mmc_priv(mmc); + unsigned long flags; + + DBG("[%s] s enable = %d *ATSMB1_INT_MASK_1 = %x\n",__func__,enable,*ATSMB1_INT_MASK_1); + spin_lock_irqsave(&host->lock, flags); + + if (enable) { + *ATSMB1_INT_MASK_1 |= ATSMB_SDIO_EN; + } else { + *ATSMB1_INT_MASK_1 &= ~ATSMB_SDIO_EN; + } + + spin_unlock_irqrestore(&host->lock, flags); + DBG("[%s] e\n",__func__); + +} +/********************************************************************** +Name : atsmb_request +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static void atsmb1_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + + struct atsmb_host *host = mmc_priv(mmc); + DBG("[%s] s\n",__func__); + + /* May retry process comes here.*/ + host->mrq = mrq; + host->data = mrq->data; + host->cmd = mrq->cmd; + host->done_data = NULL; + host->done = NULL; + + /*for data request*/ + if (host->data) { + if (host->cmd->opcode == MMC_WRITE_BLOCK + || host->cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK + || host->cmd->opcode == MMC_READ_SINGLE_BLOCK + || host->cmd->opcode == MMC_READ_MULTIPLE_BLOCK + || host->cmd->opcode == SD_IO_RW_EXTENDED) { + atsmb1_start_data(host); + } else { + atsmb1_cmd_with_data_back(host); + } + } else { + atsmb1_start_cmd(host); + } + DBG("[%s] e\n",__func__); +} +/********************************************************************** +Name : atsmb1_set_clock +Function :. +Calls : +Called by : +Parameter : +Author : Eason Chien +History :2012/7/19 +***********************************************************************/ +static int atsmb1_set_clock(struct mmc_host *mmc, unsigned int clock) +{ + int clock_multiplier = 1; + DBG("clock = %u\n",clock); + + if (*ATSMB1_EXT_BUS_MODE & BIT4) /*Enable DDR50*/ + clock_multiplier = 2; + + if (clock == mmc->f_min) { + DBG("[%s]ATSMB1 Host 400KHz\n", __func__); + return auto_pll_divisor(DEV_SDMMC1, SET_DIV, 1, 390 * clock_multiplier); + } else if (clock >= 50000000) { + DBG("[%s]ATSMB1 Host 50MHz\n", __func__); + return auto_pll_divisor(DEV_SDMMC1, SET_DIV, 2, 45 * clock_multiplier); + } else if ((clock >= 25000000) && (clock < 50000000)) { + DBG("[%s]ATSMB1 Host 25MHz\n", __func__); + return auto_pll_divisor(DEV_SDMMC1, SET_DIV, 2, 24 * clock_multiplier); + } else if ((clock >= 20000000) && (clock < 25000000)) { + DBG("[%s]ATSMB1 Host 20MHz\n", __func__); + return auto_pll_divisor(DEV_SDMMC1, SET_DIV, 2, 20 * clock_multiplier); + } else { + DBG("[%s]ATSMB1 Host 390KHz\n", __func__); + return auto_pll_divisor(DEV_SDMMC1, SET_DIV, 1, 390 * clock_multiplier); + } +} + +/********************************************************************** +Name : atsmb1_set_ios +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static void atsmb1_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + + struct atsmb_host *host = mmc_priv(mmc); + unsigned long flags; + unsigned int strapping; + + DBG("[%s] s\n",__func__); + spin_lock_irqsave(&host->lock, flags); + + if (ios->power_mode == MMC_POWER_OFF) { + if (MMC1_DRIVER_VERSION == MMC_DRV_3498) { + if (atsmb1_first_boot) { + /*nothing to do for GPIO setting when first boot.*/ + } else { + /* stop SD output clock */ + *ATSMB1_BUS_MODE &= ~(ATSMB_CST); + + /* disable SD1 Card power */ + /*set SD1 power pin as GPO pin*/ + GPIO_CTRL_GP14_NAND_SD1_BYTE_VAL |= GPIO_SD1_POWER; + GPIO_OC_GP14_NAND_SD1_BYTE_VAL |= GPIO_SD1_POWER; + /*set internal pull up*/ + PULL_CTRL_GP14_NAND_BYTE_VAL |= GPIO_SD1_POWER; + /*set internal pull enable*/ + PULL_EN_GP14_NAND_BYTE_VAL |= GPIO_SD1_POWER; + /*disable SD1 power*/ + GPIO_OD_GP14_NAND_SD1_BYTE_VAL |= GPIO_SD1_POWER; + + /* Config SD1 to GPIO */ + GPIO_CTRL_GP16_NAND_SD1_BYTE_VAL |= GPIO_SD1_Data; + GPIO_CTRL_GP14_NAND_SD1_BYTE_VAL |= SD1_PIN; + + /* SD1 all pins output low */ + GPIO_OD_GP16_NAND_SD1_BYTE_VAL &= ~GPIO_SD1_Data; + GPIO_OD_GP14_NAND_SD1_BYTE_VAL &= ~SD1_PIN; + + /* Config SD1 to GPIO */ + GPIO_OC_GP16_NAND_SD1_BYTE_VAL |= GPIO_SD1_Data; + GPIO_OC_GP14_NAND_SD1_BYTE_VAL |= SD1_PIN; + + /*Set SD1 CMD pin pull low because SD1CMD has no gpio pin, check with Jim*/ + /*PULL_CTRL_GP14_NAND_BYTE_VAL &= ~GPIO_SD1_Command; + PULL_EN_GP14_NAND_BYTE_VAL |= GPIO_SD1_Command;*/ + } + } + } else if (ios->power_mode == MMC_POWER_UP) { + if (MMC1_DRIVER_VERSION == MMC_DRV_3498) { + if (atsmb1_first_boot) { + /*nothing to do for GPIO setting when first boot.*/ + } else { + /* disable SD Card power */ + /*set SD1 power pin as GPO pin*/ + GPIO_CTRL_GP14_NAND_SD1_BYTE_VAL |= GPIO_SD1_POWER; + GPIO_OC_GP14_NAND_SD1_BYTE_VAL |= GPIO_SD1_POWER; + /*set internal pull up*/ + PULL_CTRL_GP14_NAND_BYTE_VAL |= GPIO_SD1_POWER; + /*set internal pull enable*/ + PULL_EN_GP14_NAND_BYTE_VAL |= GPIO_SD1_POWER; + /*disable SD1 power*/ + GPIO_OD_GP14_NAND_SD1_BYTE_VAL |= GPIO_SD1_POWER; + + /* enable SD1 PIN share */ + PIN_SHARING_SEL_4BYTE_VAL |= GPIO_SD1_PinShare; + + /* do not config GPIO_SD1_CD because ISR has already run, + * config card detect will issue ISR storm. + */ + /* Config SD to GPIO */ + GPIO_CTRL_GP16_NAND_SD1_BYTE_VAL |= GPIO_SD1_Data; + GPIO_CTRL_GP14_NAND_SD1_BYTE_VAL |= SD1_PIN; + + /* SD all pins output low */ + GPIO_OD_GP16_NAND_SD1_BYTE_VAL &= ~GPIO_SD1_Data; + GPIO_OD_GP14_NAND_SD1_BYTE_VAL &= ~SD1_PIN; + + /* Config SD to GPO */ + GPIO_OC_GP16_NAND_SD1_BYTE_VAL |= GPIO_SD1_Data; + GPIO_OC_GP14_NAND_SD1_BYTE_VAL |= SD1_PIN; + + /* stop SD output clock */ + *ATSMB1_BUS_MODE &= ~ATSMB_CST; + + /* Pull up/down resister of SD Bus */ + /*Disable Clock & CMD Pull enable*/ + PULL_EN_GP14_NAND_BYTE_VAL &= ~(GPIO_SD1_Clock | GPIO_SD1_Command); + + /*Set CD ,WP ,DATA pin pull up*/ + PULL_CTRL_GP16_NANDIO_BYTE_VAL |= GPIO_SD1_Data; + PULL_CTRL_GP14_NAND_BYTE_VAL |= (GPIO_SD1_WriteProtect | GPIO_SD1_CD); + + /*Enable CD ,WP ,DATA internal pull*/ + PULL_EN_GP16_NANDIO_BYTE_VAL |= GPIO_SD1_Data; + PULL_EN_GP14_NAND_BYTE_VAL |= (GPIO_SD1_WriteProtect | GPIO_SD1_CD); + + spin_unlock_irqrestore(&host->lock, flags); + msleep(1); + spin_lock_irqsave(&host->lock, flags); + + /* enable SD1 power */ + GPIO_OD_GP14_NAND_SD1_BYTE_VAL &= ~GPIO_SD1_POWER; + + /* enable SD output clock */ + *ATSMB1_BUS_MODE |= ATSMB_CST; + + spin_unlock_irqrestore(&host->lock, flags); + msleep(1); + spin_lock_irqsave(&host->lock, flags); + + /* Config SD1 back to function */ + GPIO_CTRL_GP16_NAND_SD1_BYTE_VAL &= ~GPIO_SD1_Data; + GPIO_CTRL_GP14_NAND_SD1_BYTE_VAL &= ~SD1_PIN; + GPIO_CTRL_GP14_NAND_SD1_BYTE_VAL &= ~GPIO_SD1_CD; + + /* terminate emmc boot mode */ + DBG("[%s] STRAP_STATUS_VAL = 0x%x\n", __func__, STRAP_STATUS_VAL); + strapping = STRAP_STATUS_VAL & 0xE; + if (strapping == 0x2) { + printk("[%s] strapping = 0x%x, terminate boot mode\n", __func__, strapping); + *ATSMB1_EXT_BUS_MODE &= ~BIT0; + *ATSMB1_BUS_MODE &= ~BIT4; + *ATSMB1_BUS_MODE |= BIT4; + } + } + } + } else { + /*nothing to do when powering on.*/ + } + + host->current_clock = atsmb1_set_clock(mmc,ios->clock); + + if (ios->bus_width == MMC_BUS_WIDTH_8) { + *ATSMB1_EXT_CTL |= (0x04); + } else if (ios->bus_width == MMC_BUS_WIDTH_4) { + *ATSMB1_BUS_MODE |= ATSMB_BUS_WIDTH_4; + *ATSMB1_EXT_CTL &= ~(0x04); + } else { + *ATSMB1_BUS_MODE &= ~(ATSMB_BUS_WIDTH_4); + *ATSMB1_EXT_CTL &= ~(0x04); + } + +#if 1 + if (ios->timing == MMC_TIMING_SD_HS) + *ATSMB1_EXT_CTL &= ~0x80; /*HIGH SPEED MODE hsot1 H_S must be off for wm3498*/ + else if (ios->timing == MMC_TIMING_MMC_HS) + *ATSMB1_EXT_CTL &= ~0x80; + else if (ios->timing == MMC_TIMING_UHS_DDR50) { + /* enable emmc DDR mode */ + *ATSMB1_EXT_BUS_MODE |= BIT4; /*Enable DDR50*/ + /* enable SD output clock */ + *ATSMB1_BUS_MODE &= ~ATSMB_CST; + spin_unlock_irqrestore(&host->lock, flags); + msleep(1); + spin_lock_irqsave(&host->lock, flags); + + *ATSMB1_EXT_BUS_MODE |= BIT4; /*Enable DDR50*/ + /* enable SD output clock */ + *ATSMB0_BUS_MODE |= ATSMB_CST; + /*BIT4~7: PMOS driver strength, BIT8~11: NMOS driver strength */ + DRV_SPI_NAND_4BYTE_VAL |= (BIT4 |BIT5 | BIT6 | BIT7 | BIT8 | BIT9 | BIT10 | BIT11); + } +#endif +#if 0 //zhf: marked by James Tian + if (ios->ins_en == MMC_INSERT_IRQ_EN) + *ATSMB1_INT_MASK_0 |= (0x80);/* or 40?*/ + else + *ATSMB1_INT_MASK_0 &= (~0x80);/* or 40?*/ +#endif + + spin_unlock_irqrestore(&host->lock, flags); + DBG("[%s] e\n",__func__); +} + + +static const struct mmc_host_ops atsmb1_ops = { + .request = atsmb1_request, + .set_ios = atsmb1_set_ios, + .get_ro = atsmb1_get_ro, + .get_slot_status = atsmb1_get_slot_status, + .dump_host_regs = atsmb1_dump_host_regs, + .enable_sdio_irq = atsmb1_enable_sdio_irq, +}; + +extern int wmt_getsyspara(char *varname, unsigned char *varval, int *varlen); + +/********************************************************************** +Name :atsmb1_probe +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static int __init atsmb1_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mmc_host *mmc_host = NULL; + struct atsmb_host *atsmb_host = NULL; + struct resource *resource = NULL; + int irq[2] = {0}; + int ret = 0; + DBG("[%s] s\n",__func__); + if (!pdev) { + ret = -EINVAL; /* Invalid argument */ + goto the_end; + } + + + /*Enable SD host clock*/ + auto_pll_divisor(DEV_SDMMC1, CLK_ENABLE, 0, 0); + + if (MMC1_DRIVER_VERSION == MMC_DRV_3498) { + /* enable SD1 PIN share */ + PIN_SHARING_SEL_4BYTE_VAL |= GPIO_SD1_PinShare; + + /* Pull up/down resister of SD CD */ + PULL_CTRL_GP14_NAND_BYTE_VAL |= GPIO_SD1_CD; + PULL_EN_GP14_NAND_BYTE_VAL |= GPIO_SD1_CD; + + /* config CardDetect pin to SD function */ + GPIO_CTRL_GP14_NAND_SD1_BYTE_VAL &= ~GPIO_SD1_CD; + } + + /* support emmc fisrt boot don't setting GPIO*/ + atsmb1_first_boot = 1; //1:first boot, 0: suspend/resume + + resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!resource) { + ret = -ENXIO; /* No such device or address */ + printk(KERN_ALERT "[MMC/SD driver] Getting platform resources failed!\n"); + goto the_end; + } +#if 0 + if (!request_mem_region(resource->start, SZ_1K, MMC1_DRIVER_NAME)) { + ret = -EBUSY; + printk(KERN_ALERT "[MMC/SD driver] Request memory region failed!\n"); + goto the_end ; + } +#endif + irq[0] = platform_get_irq(pdev, 0); /*get IRQ for device*/; + irq[1] = platform_get_irq(pdev, 1); /*get IRQ for dma*/; + + if (irq[0] == NO_IRQ || irq[1] == NO_IRQ) { + ret = -ENXIO;/* No such device or address */ + printk(KERN_ALERT "[MMC/SD driver] Get platform IRQ failed!\n"); + goto rls_region; + } + + /*allocate a standard msp_host structure attached with a atsmb structure*/ + mmc_host = mmc_alloc_host(sizeof(struct atsmb_host), dev); + if (!mmc_host) { + ret = -ENOMEM; + printk(KERN_ALERT "[MMC/SD driver] Allocating driver's data failed!\n"); + goto rls_region; + } + mmc_host->wmt_host_index = 1; /*to identify host number*/ + + dev_set_drvdata(dev, (void *)mmc_host); /* mmc_host is driver data for the atsmb dev.*/ + atsmb_host = mmc_priv(mmc_host); + + mmc_host->ops = &atsmb1_ops; + + mmc_host->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34; + + mmc_host->f_min = 390425; /*390.425Hz = 400MHz/64/16*/ + mmc_host->f_max = 50000000; /* in fact, the max frequency is 400MHz( = 400MHz/1/1)*/ + + if (SDXC1_function == 1) { + mmc_host->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ + | MMC_CAP_MMC_HIGHSPEED | MMC_CAP_8_BIT_DATA | MMC_CAP_1_8V_DDR | MMC_CAP_UHS_DDR50; + } else if (SDXC1_function == 0) { + mmc_host->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SD_HIGHSPEED | MMC_CAP_SDIO_IRQ + | MMC_CAP_MMC_HIGHSPEED | MMC_CAP_8_BIT_DATA; /* */ //zhf: marked by James Tian + } + + mmc_host->max_segs = 128; /*we use software sg. so we could manage even larger number.*/ + + /*1MB per each request */ + /*we have a 16 bit block number register, and block length is 512 bytes.*/ + mmc_host->max_req_size = 16*512*(mmc_host->max_segs); + mmc_host->max_seg_size = 65024; /* 0x7F*512 PDMA one descriptor can transfer 64K-1 byte*/ + mmc_host->max_blk_size = 2048; /* our block length register is 11 bits.*/ + mmc_host->max_blk_count = (mmc_host->max_req_size)/512; + + /*set the specified host -- ATSMB*/ +#ifdef CONFIG_MMC_UNSAFE_RESUME + mmc_host->card_attath_status = card_attach_status_unchange; +#endif + sema_init(&mmc_host->req_sema,1); /*initial request semaphore*/ +#if 0 + atsmb_host->base = ioremap(resource->start, SZ_1K); + if (!atsmb_host->base) { + printk(KERN_ALERT "[MMC/SD driver] IO remap failed!\n"); + ret = -ENOMEM; + goto fr_host; + } +#endif + atsmb_host->base = (void *)resource->start; + atsmb_host->mmc = mmc_host; + spin_lock_init(&atsmb_host->lock); + atsmb_host->res = resource;/* for atsmb_remove*/ + + /*disable all interrupt and clear status by resetting controller.*/ + //*ATSMB1_BUS_MODE |= ATSMB_SFTRST; //emmc card don't execute SW reset. because uboot is done. + *ATSMB1_BLK_LEN &= ~(0xa000); + *ATSMB1_SD_STS_0 |= 0xff; + *ATSMB1_SD_STS_1 |= 0xff; + + /* WM3437 A0 default not output clock, after SFTRST need to enable SD clock */ + //if (MMC1_DRIVER_VERSION >= MMC_DRV_3437_A0) /* including 3429 */ + *ATSMB1_BUS_MODE |= ATSMB_CST; + + atsmb_host->regular_irq = irq[0]; + atsmb_host->dma_irq = irq[1]; + + ret = request_irq(atsmb_host->regular_irq, + atsmb1_regular_isr, + IRQF_SHARED, //SA_SHIRQ, /*SA_INTERRUPT, * that is okay?*/ //zhf: modified by James Tian, should be IRQF_SHARED? + MMC1_DRIVER_NAME, + (void *)atsmb_host); + if (ret) { + printk(KERN_ALERT "[MMC/SD driver] Failed to register regular ISR!\n"); + goto unmap; + } + + ret = request_irq(atsmb_host->dma_irq, + atsmb1_dma_isr, + IRQF_DISABLED, // SA_INTERRUPT, //zhf: modified by James Tian + MMC1_DRIVER_NAME, + (void *)atsmb_host); + if (ret) { + printk(KERN_ALERT "[MMC/SD driver] Failed to register DMA ISR!\n"); + goto fr_regular_isr; + } + + + + /*wait card detect status change*/ + //msleep(10); + /*enable card insertion interrupt and enable DMA and its Global INT*/ + *ATSMB1_BLK_LEN |= (0xa000); /* also, we enable GPIO to detect card.*/ + *ATSMB1_SD_STS_0 |= 0xff; + *ATSMB1_INT_MASK_0 |= 0x80; /*or 0x40?*/ + + /*allocation dma descriptor*/ + ret = atsmb1_alloc_desc(atsmb_host, sizeof(struct SD_PDMA_DESC_S) * MAX_DESC_NUM); + if (ret == -1) { + printk(KERN_ALERT "[MMC/SD driver] Failed to allocate DMA descriptor!\n"); + goto fr_dma_isr; + } + printk(KERN_INFO "WMT ATSMB1 (AHB To SD/MMC1 Bus) controller registered!\n"); + mmc_add_host(mmc_host,true); + + DBG("[%s] e1\n",__func__); + return 0; + +fr_dma_isr: + free_irq(atsmb_host->dma_irq, atsmb_host); +fr_regular_isr: + free_irq(atsmb_host->regular_irq, atsmb_host); +unmap: + //iounmap(atsmb_host->base); +//fr_host: + dev_set_drvdata(dev, NULL); + mmc_free_host(mmc_host); +rls_region: + //release_mem_region(resource->start, SZ_1K); +the_end: + printk(KERN_ALERT "[MMC/SD driver] ATSMB1 probe Failed!\n") ; + DBG("[%s] e2\n",__func__); + return ret; +} +/********************************************************************** +Name : atsmb1_remove +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static int atsmb1_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mmc_host *mmc_host = (struct mmc_host *)dev_get_drvdata(dev); + struct atsmb_host *atsmb_host; + + DBG("[%s] s\n",__func__); + atsmb_host = mmc_priv(mmc_host); + if (!mmc_host || !atsmb_host) { + printk(KERN_ALERT "[MMC/SD driver] ATSMB1 remove method failed!\n"); + DBG("[%s] e1\n",__func__); + return -ENXIO; + } + mmc_remove_host(mmc_host); + + /*disable interrupt by resetting controller -- for safey*/ + *ATSMB1_BUS_MODE |= ATSMB_SFTRST; + *ATSMB1_BLK_LEN &= ~(0xa000); + *ATSMB1_SD_STS_0 |= 0xff; + *ATSMB1_SD_STS_1 |= 0xff; + + (void)free_irq(atsmb_host->regular_irq, atsmb_host); + (void)free_irq(atsmb_host->dma_irq, atsmb_host); + (void)iounmap(atsmb_host->base); + (void)release_mem_region(atsmb_host->res->start, SZ_1K); + dev_set_drvdata(dev, NULL); + /*free dma descriptor*/ + dma_free_coherent(atsmb_host->mmc->parent, atsmb_host->DescSize, + atsmb_host->DescVirAddr, atsmb_host->DescPhyAddr); + (void)mmc_free_host(mmc_host);/* also free atsmb_host.*/ + DBG("[%s] e2\n",__func__); + return 0; +} + +/********************************************************************** +Name : atsmb_shutdown +Function :. +Calls : +Called by : +Parameter : +Author : Tommy Huang +History : +***********************************************************************/ +static void atsmb1_shutdown(struct platform_device *pdev) +{ + /*atsmb1_shutdown don't be used now.*/ + /*struct device *dev = &pdev->dev; + struct mmc_host *mmc_host = (struct mmc_host *)dev_get_drvdata(dev);*/ + + DBG("[%s] s\n",__func__); + + /* turn off clock before emmc card shutdown. */ + *ATSMB1_BUS_MODE &= ~(ATSMB_CST); + + /*Disable card detect interrupt*/ + *ATSMB1_INT_MASK_0 &= ~0x80; + DBG("[%s] e\n",__func__); + +} + +/********************************************************************** +Name : atsmb_suspend +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +#ifdef CONFIG_PM +static int atsmb1_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct device *dev = &pdev->dev; + struct mmc_host *mmc = (struct mmc_host *)dev_get_drvdata(dev); + int ret = 0; + DBG("[%s] s\n",__func__); + + /* after suspend/resume, it isn't first boot*/ + atsmb1_first_boot = 0; + + if (mmc) { + /*struct atsmb_host *host = mmc_priv(mmc);*/ + ret = mmc_suspend_host(mmc); + if (ret == 0) { + /*disable all interrupt and clear status by resetting controller. */ + *ATSMB1_BUS_MODE |= ATSMB_SFTRST; + *ATSMB1_BLK_LEN &= ~(0xa000); + *ATSMB1_SD_STS_0 |= 0xff; + *ATSMB1_SD_STS_1 |= 0xff; + + } + /*disable source clock*/ + //TODO: SD1 clock en or dis + auto_pll_divisor(DEV_SDMMC1, CLK_DISABLE, 0, 0); +#ifdef CONFIG_MMC_UNSAFE_RESUME + /*clean SD card attatch status change*/ + PMCWS_VAL |= BIT20; + mmc->card_attath_status = card_attach_status_unchange; +#endif + } + + DBG("[%s] e\n",__func__); + return ret; +} +/********************************************************************** +Name : atsmb_resume +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static int atsmb1_resume(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mmc_host *mmc = (struct mmc_host *)dev_get_drvdata(dev); + int ret = 0; + DBG("[%s] s\n",__func__); + + /* + * enable interrupt, DMA, etc. + * Supply power to slot. + */ + if (mmc) { + /*enable source clock*/ + auto_pll_divisor(DEV_SDMMC1, CLK_ENABLE, 0, 0); + + udelay(1); + /*enable card insertion interrupt and enable DMA and its Global INT*/ + *ATSMB1_BUS_MODE |= ATSMB_SFTRST; + *ATSMB1_BLK_LEN |= (0xa000); + *ATSMB1_INT_MASK_0 |= 0x80; /* or 40?*/ +#ifdef CONFIG_MMC_UNSAFE_RESUME + /*modify SD card attatch status change*/ + if ((PMCWS_VAL & BIT20) && !mmc->bus_dead) { + /*card change when suspend mode*/ + mmc->card_attath_status = card_attach_status_change; + /*clean SD card attatch status change*/ + PMCWS_VAL |= BIT20; + } +#endif + ret = mmc_resume_host(mmc); + } + + DBG("[%s] e\n",__func__); + return ret; +} +#else +#define atsmb1_suspend NULL +#define atsmb1_resume NULL +#endif + +static struct platform_driver atsmb1_driver = { + .driver.name = "sdmmc1", + //.probe = atsmb1_probe, + .remove = atsmb1_remove, + .shutdown = atsmb1_shutdown, + .suspend = atsmb1_suspend, + .resume = atsmb1_resume, +}; + +static struct platform_device wmt_sdmmc1_device = { + .name = "sdmmc1", + .id = 0, + .dev = { + .dma_mask = &wmt_sdmmc1_dma_mask, + .coherent_dma_mask = ~0, + .release = atsmb1_release, + }, + .num_resources = ARRAY_SIZE(wmt_sdmmc1_resources), + .resource = wmt_sdmmc1_resources, +}; + + +static int __init atsmb1_init(void) +{ + int ret; + int retval; + unsigned char buf[80]; + int varlen = 80; + char *varname = "wmt.sd1.param"; + int temp = 0, sd_enable = 0; /*0 :disable 1:enable*/ + + DBG("[%s] s\n",__func__); + +#ifdef CONFIG_MTD_WMT_SF + /*Read system param to identify host function 0: SD/MMC 1:SDIO wifi*/ + retval = wmt_getsyspara(varname, buf, &varlen); + if (retval == 0) { + sscanf(buf,"%x:%d", &temp, &SD1_function); + printk(KERN_ALERT "wmt.sd1.param = %x:%d\n", temp, SD1_function); + sd_enable = temp & 0xf; + SDXC1_function = (temp >> 4) & 0xf; + printk(KERN_ALERT "SD1 ebable = %x, SDXC = %x, function = %x\n", + sd_enable, SDXC1_function, SD1_function); + + if (SD1_function < 0 || SD1_function >= SD_MAX_FUN) + return -ENODEV; + } else { + printk(KERN_ALERT "Default wmt.sd1.param = %x:%d\n", temp, SD1_function); + } +#endif + /*SD function disable*/ + if (sd_enable != 1) + return -ENODEV; + + get_driver_version1(); + + if (platform_device_register(&wmt_sdmmc1_device))//add by jay,for modules support + return -1; + //ret = platform_driver_register(&atsmb1_driver); + ret = platform_driver_probe(&atsmb1_driver, atsmb1_probe); + + DBG("[%s] e\n",__func__); + return ret; +} + +static void __exit atsmb1_exit(void) +{ + DBG("[%s] s\n",__func__); + (void)platform_driver_unregister(&atsmb1_driver); + (void)platform_device_unregister(&wmt_sdmmc1_device);//add by jay,for modules support + DBG("[%s] e\n",__func__); +} + +module_init(atsmb1_init); +module_exit(atsmb1_exit); +module_param(fmax1, uint, 0444); + +MODULE_AUTHOR("WonderMedia Technologies, Inc."); +MODULE_DESCRIPTION("WMT [AHB to SD/MMC1 Bridge] driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mmc/host/mmc_atsmb2.c b/drivers/mmc/host/mmc_atsmb2.c new file mode 100755 index 00000000..944e3cc4 --- /dev/null +++ b/drivers/mmc/host/mmc_atsmb2.c @@ -0,0 +1,2855 @@ +/*++ +linux/drivers/mmc/host/mmc_atsmb.c + +Copyright (c) 2008 WonderMedia Technologies, 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, see <http://www.gnu.org/licenses/>. + +WonderMedia Technologies, Inc. +10F, 529, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C. +--*/ + +//#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/blkdev.h> +#include <linux/mmc/host.h> +#include <linux/mmc/card.h> +#include <linux/mmc/sd.h> +#include <linux/mmc/mmc.h> +#include <linux/mmc/sdio.h> +#include <linux/completion.h> +#include <linux/pagemap.h> +#include <linux/dma-mapping.h> +#include <asm/dma.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/memory.h> +#include <mach/hardware.h> +#include <asm/scatterlist.h> +#include <asm/sizes.h> +#include "mmc_atsmb.h" +//#include <mach/multicard.h> +#include <mach/irqs.h> +#include <linux/mmc/sdio.h> +#include <linux/gpio.h> +#include <mach/wmt_iomux.h> + +int enable_wifi_irq = 1; + +#define MMC2_SDIO_EXT_IRQ (1) /* use sdio ext irq or not */ + +#if MMC2_SDIO_EXT_IRQ +static int is_mtk6620 = 0; +static unsigned int mmc2_sdio_ext_irq = 0; +static unsigned long mmc2_sdio_ext_irq_flags = 0; +enum ext_irq_flag { + IN_SUSPEND = 0, + IRQ_IN_SUSPEND = 1, +}; +int wmt_mtk6620_intr=0xf; //gpio 15 +#endif + + +static int is_nmc1000 = 0x0; +static int is_ap6330 = 0; +int set_bus_dead = 1; +#define mprintk + +#if 0 +#define DBG(host, fmt, args...) \ + pr_debug("%s: %s: " fmt, mmc_hostname(host->mmc), __func__ , args) +#endif +#define ATSMB_TIMEOUT_TIME (HZ*2) + +//add by jay,for modules support +static u64 wmt_sdmmc2_dma_mask = 0xffffffffUL; +static struct resource wmt_sdmmc2_resources[] = { + [0] = { + .start = SD2_SDIO_MMC_BASE_ADDR, + .end = (SD2_SDIO_MMC_BASE_ADDR + 0x3FF), + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = IRQ_SDC2, + .end = IRQ_SDC2, + .flags = IORESOURCE_IRQ, + }, + [2] = { + .start = IRQ_SDC2_DMA, + .end = IRQ_SDC2_DMA, + .flags = IORESOURCE_IRQ, + }, + /*2008/10/6 RichardHsu-s*/ + [3] = { + .start = IRQ_PMC_WAKEUP, + .end = IRQ_PMC_WAKEUP, + .flags = IORESOURCE_IRQ, + }, + /*2008/10/6 RichardHsu-e*/ +}; + +struct kobject *atsmb2_kobj; +struct mmc_host *mmc2_host_attr = NULL; +// +static void atsmb2_release(struct device * dev) {} + +/*#ifdef CONFIG_MMC_DEBUG*/ +#define MORE_INFO +#if 0 +#define DBG(x...) printk(KERN_ALERT x) +#define DBGR(x...) printk(KERN_ALERT x) +#else +#define DBG(x...) do { } while (0) +#define DBGR(x...) do { } while (0) +#endif + +/* when Read CRC error occur, and clear read CRC error by software reset.*/ +void atsmb2_copy_reg(int direct) +{ + static u8 CTL, CMD_IDX, RSP_TYPE, BUS_MODE, INT_MASK_0, INT_MASK_1, SD_STS_0, SD_STS_1, SD_STS_2, SD_STS_3; + static u8 EXT_BUS_MODE, EXT_CTL_1, EXT_CTL_2, MAN_TUNE_VAL, SD_WRI_TUNE; + static u8 RSP_0, RSP_1, RSP_2, RSP_3, RSP_4, RSP_5, RSP_6, RSP_7; + static u8 RSP_8, RSP_9, RSP_10, RSP_11, RSP_12, RSP_13, RSP_14, RSP_15; + static u8 RSP_TOUT, CLK_SEL, EXT_CTL; + static u16 BLK_LEN, BLK_CNT, SHDW_BLKLEN, TIMER_VAL, CTL2; + static u32 CMD_ARG, CURBLK_CNT; + + /*direct 0: copy register to memory; 1: copy memory to register.*/ + if (direct == 0) { + CTL = *ATSMB2_CTL; + CMD_IDX = *ATSMB2_CMD_IDX; + RSP_TYPE = *ATSMB2_RSP_TYPE; + CMD_ARG = *ATSMB2_CMD_ARG; + BUS_MODE = *ATSMB2_BUS_MODE; + EXT_BUS_MODE = *ATSMB2_EXT_BUS_MODE; + CTL2 = *ATSMB2_CTL2; + BLK_LEN = *ATSMB2_BLK_LEN; + BLK_CNT = *ATSMB2_BLK_CNT; + RSP_0 = *ATSMB2_RSP_0; + RSP_1 = *ATSMB2_RSP_1; + RSP_2 = *ATSMB2_RSP_2; + RSP_3 = *ATSMB2_RSP_3; + RSP_4 = *ATSMB2_RSP_4; + RSP_5 = *ATSMB2_RSP_5; + RSP_6 = *ATSMB2_RSP_6; + RSP_7 = *ATSMB2_RSP_7; + RSP_8 = *ATSMB2_RSP_8; + RSP_9 = *ATSMB2_RSP_9; + RSP_10 = *ATSMB2_RSP_10; + RSP_11 = *ATSMB2_RSP_11; + RSP_12 = *ATSMB2_RSP_12; + RSP_13 = *ATSMB2_RSP_13; + RSP_14 = *ATSMB2_RSP_14; + RSP_15 = *ATSMB2_RSP_15; + CURBLK_CNT = *ATSMB2_CURBLK_CNT; + INT_MASK_0 = *ATSMB2_INT_MASK_0; + INT_MASK_1 = *ATSMB2_INT_MASK_1; + SD_STS_0 = *ATSMB2_SD_STS_0; + SD_STS_1 = *ATSMB2_SD_STS_1; + SD_STS_2 = *ATSMB2_SD_STS_2; + SD_STS_3 = *ATSMB2_SD_STS_3; + RSP_TOUT = *ATSMB2_RSP_TOUT; + CLK_SEL = *ATSMB2_CLK_SEL; + EXT_CTL = *ATSMB2_EXT_CTL; + EXT_CTL_1 = *ATSMB2_EXT_CTL_1; + EXT_CTL_2 = *ATSMB2_EXT_CTL_2; + SHDW_BLKLEN = *ATSMB2_SHDW_BLKLEN; + MAN_TUNE_VAL = *ATSMB2_MAN_TUNE_VAL; + SD_WRI_TUNE = *ATSMB2_SD_WRI_TUNE; + TIMER_VAL = *ATSMB2_TIMER_VAL; + } else { + *ATSMB2_CTL = CTL; + *ATSMB2_CMD_IDX = CMD_IDX; + *ATSMB2_RSP_TYPE = RSP_TYPE; + *ATSMB2_CMD_ARG = CMD_ARG; + *ATSMB2_BUS_MODE = BUS_MODE; + *ATSMB2_EXT_BUS_MODE = EXT_BUS_MODE; + *ATSMB2_CTL2 = CTL2; + *ATSMB2_BLK_LEN = BLK_LEN; + *ATSMB2_BLK_CNT = BLK_CNT; + *ATSMB2_RSP_0 = RSP_0; + *ATSMB2_RSP_1 = RSP_1; + *ATSMB2_RSP_2 = RSP_2; + *ATSMB2_RSP_3 = RSP_3; + *ATSMB2_RSP_4 = RSP_4; + *ATSMB2_RSP_5 = RSP_5; + *ATSMB2_RSP_6 = RSP_6; + *ATSMB2_RSP_7 = RSP_7; + *ATSMB2_RSP_8 = RSP_8; + *ATSMB2_RSP_9 = RSP_9; + *ATSMB2_RSP_10 = RSP_10; + *ATSMB2_RSP_11 = RSP_11; + *ATSMB2_RSP_12 = RSP_12; + *ATSMB2_RSP_13 = RSP_13; + *ATSMB2_RSP_14 = RSP_14; + *ATSMB2_RSP_15 = RSP_15; + *ATSMB2_CURBLK_CNT = CURBLK_CNT; + *ATSMB2_INT_MASK_0 = INT_MASK_0; + *ATSMB2_INT_MASK_1 = INT_MASK_1; + *ATSMB2_SD_STS_0 = SD_STS_0; + *ATSMB2_SD_STS_1 = SD_STS_1; + *ATSMB2_SD_STS_2 = SD_STS_2; + *ATSMB2_SD_STS_3 = SD_STS_3; + *ATSMB2_RSP_TOUT = RSP_TOUT; + *ATSMB2_CLK_SEL = CLK_SEL; + *ATSMB2_EXT_CTL = EXT_CTL; + *ATSMB2_EXT_CTL_1 = EXT_CTL_1; + *ATSMB2_EXT_CTL_2 = EXT_CTL_2; + *ATSMB2_SHDW_BLKLEN = SHDW_BLKLEN; + *ATSMB2_MAN_TUNE_VAL = MAN_TUNE_VAL; + *ATSMB2_SD_WRI_TUNE = SD_WRI_TUNE; + *ATSMB2_TIMER_VAL = TIMER_VAL; + } +} + +#if 0 +void atsmb2_dump_reg(struct atsmb_host *host) +{ + u8 CTL, CMD_IDX, RSP_TYPE, BUS_MODE, INT_MASK_0, INT_MASK_1, SD_STS_0, SD_STS_1, SD_STS_2, SD_STS_3; + u8 EXT_BUS_MODE, EXT_CTL_1, EXT_CTL_2, MAN_TUNE_VAL, SD_WRI_TUNE; + u8 RSP_0, RSP_1, RSP_2, RSP_3, RSP_4, RSP_5, RSP_6, RSP_7; + u8 RSP_8, RSP_9, RSP_10, RSP_11, RSP_12, RSP_13, RSP_14, RSP_15; + u8 RSP_TOUT, CLK_SEL, EXT_CTL; + u16 BLK_LEN, BLK_CNT, SHDW_BLKLEN, TIMER_VAL, CTL2; + u32 CMD_ARG, PDMA_GCR, PDMA_IER, PDMA_ISR, PDMA_DESPR, PDMA_RBR, PDMA_DAR, PDMA_BAR, PDMA_CPR, PDMA_CCR; + u32 CURBLK_CNT; + + CTL = *ATSMB2_CTL; + CMD_IDX = *ATSMB2_CMD_IDX; + RSP_TYPE = *ATSMB2_RSP_TYPE; + CMD_ARG = *ATSMB2_CMD_ARG; + BUS_MODE = *ATSMB2_BUS_MODE; + EXT_BUS_MODE = *ATSMB2_EXT_BUS_MODE; + CTL2 = *ATSMB2_CTL2; + BLK_LEN = *ATSMB2_BLK_LEN; + BLK_CNT = *ATSMB2_BLK_CNT; + RSP_0 = *ATSMB2_RSP_0; + RSP_1 = *ATSMB2_RSP_1; + RSP_2 = *ATSMB2_RSP_2; + RSP_3 = *ATSMB2_RSP_3; + RSP_4 = *ATSMB2_RSP_4; + RSP_5 = *ATSMB2_RSP_5; + RSP_6 = *ATSMB2_RSP_6; + RSP_7 = *ATSMB2_RSP_7; + RSP_8 = *ATSMB2_RSP_8; + RSP_9 = *ATSMB2_RSP_9; + RSP_10 = *ATSMB2_RSP_10; + RSP_11 = *ATSMB2_RSP_11; + RSP_12 = *ATSMB2_RSP_12; + RSP_13 = *ATSMB2_RSP_13; + RSP_14 = *ATSMB2_RSP_14; + RSP_15 = *ATSMB2_RSP_15; + CURBLK_CNT = *ATSMB2_CURBLK_CNT; + INT_MASK_0 = *ATSMB2_INT_MASK_0; + INT_MASK_1 = *ATSMB2_INT_MASK_1; + SD_STS_0 = *ATSMB2_SD_STS_0; + SD_STS_1 = *ATSMB2_SD_STS_1; + SD_STS_2 = *ATSMB2_SD_STS_2; + SD_STS_3 = *ATSMB2_SD_STS_3; + RSP_TOUT = *ATSMB2_RSP_TOUT; + CLK_SEL = *ATSMB2_CLK_SEL; + EXT_CTL = *ATSMB2_EXT_CTL; + EXT_CTL_1 = *ATSMB2_EXT_CTL_1; + EXT_CTL_2 = *ATSMB2_EXT_CTL_2; + SHDW_BLKLEN = *ATSMB2_SHDW_BLKLEN; + MAN_TUNE_VAL = *ATSMB2_MAN_TUNE_VAL; + SD_WRI_TUNE = *ATSMB2_SD_WRI_TUNE; + TIMER_VAL = *ATSMB2_TIMER_VAL; + + PDMA_GCR = *ATSMB2_PDMA_GCR; + PDMA_IER = *ATSMB2_PDMA_IER; + PDMA_ISR = *ATSMB2_PDMA_ISR; + PDMA_DESPR = *ATSMB2_PDMA_DESPR; + PDMA_RBR = *ATSMB2_PDMA_RBR; + PDMA_DAR = *ATSMB2_PDMA_DAR; + PDMA_BAR = *ATSMB2_PDMA_BAR; + PDMA_CPR = *ATSMB2_PDMA_CPR; + PDMA_CCR = *ATSMB2_PDMA_CCR; + + DBGR("\n+---------------------------Registers----------------------------+\n"); + + DBGR("%16s = 0x%8x |", "CTL", CTL); + DBGR("%16s = 0x%8x\n", "CMD_IDX", CMD_IDX); + + DBGR("%16s = 0x%8x |", "RSP_TYPE", RSP_TYPE); + DBGR("%16s = 0x%8x\n", "CMD_ARG", CMD_ARG); + + DBGR("%16s = 0x%8x |", "BUS_MODE", BUS_MODE); + DBGR("%16s = 0x%8x\n", "EXT_BUS_MODE", EXT_BUS_MODE); + + DBGR("%16s = 0x%8x |", "CTL2", CTL2); + DBGR("%16s = 0x%8x\n", "BLK_LEN", BLK_LEN); + + DBGR("%16s = 0x%8x |", "BLK_CNT", BLK_CNT); + DBGR("%16s = 0x%8x\n", "RSP_0", RSP_0); + + DBGR("%16s = 0x%8x |", "RSP_1", RSP_1); + DBGR("%16s = 0x%8x\n", "RSP_2", RSP_2); + + DBGR("%16s = 0x%8x |", "RSP_3", RSP_3); + DBGR("%16s = 0x%8x\n", "RSP_4", RSP_4); + + DBGR("%16s = 0x%8x |", "RSP_5", RSP_5); + DBGR("%16s = 0x%8x\n", "RSP_6", RSP_6); + + DBGR("%16s = 0x%8x |", "RSP_7", RSP_7); + DBGR("%16s = 0x%8x\n", "RSP_8", RSP_8); + + DBGR("%16s = 0x%8x |", "RSP_9", RSP_9); + DBGR("%16s = 0x%8x\n", "RSP_10", RSP_10); + + DBGR("%16s = 0x%8x |", "RSP_11", RSP_11); + DBGR("%16s = 0x%8x\n", "RSP_12", RSP_12); + + DBGR("%16s = 0x%8x |", "RSP_13", RSP_13); + DBGR("%16s = 0x%8x\n", "RSP_14", RSP_14); + + DBGR("%16s = 0x%8x\n", "RSP_15", RSP_15); + + DBGR("%16s = 0x%8x |", "CURBLK_CNT", CURBLK_CNT); + DBGR("%16s = 0x%8x\n", "INT_MASK_0", INT_MASK_0); + + DBGR("%16s = 0x%8x |", "INT_MASK_1", INT_MASK_1); + DBGR("%16s = 0x%8x\n", "SD_STS_0", SD_STS_0); + + DBGR("%16s = 0x%8x |", "SD_STS_1", SD_STS_1); + DBGR("%16s = 0x%8x\n", "SD_STS_2", SD_STS_2); + + DBGR("%16s = 0x%8x |", "SD_STS_3", SD_STS_3); + DBGR("%16s = 0x%8x\n", "RSP_TOUT", RSP_TOUT); + + DBGR("%16s = 0x%8x |", "CLK_SEL", CLK_SEL); + DBGR("%16s = 0x%8x\n", "EXT_CTL", EXT_CTL); + + DBGR("%16s = 0x%8x |", "EXT_CTL_1", EXT_CTL_1); + DBGR("%16s = 0x%8x\n", "EXT_CTL_2", EXT_CTL_2); + + DBGR("%16s = 0x%8x |", "SHDW_BLKLEN", SHDW_BLKLEN); + DBGR("%16s = 0x%8x\n", "MAN_TUNE_VAL", MAN_TUNE_VAL); + + DBGR("%16s = 0x%8x |", "SD_WRI_TUNE", SD_WRI_TUNE); + DBGR("%16s = 0x%8x\n", "TIMER_VAL", TIMER_VAL); + + DBGR("%16s = 0x%8x |", "PDMA_GCR", PDMA_GCR); + DBGR("%16s = 0x%8x\n", "PDMA_IER", PDMA_IER); + + DBGR("%16s = 0x%8x |", "PDMA_ISR", PDMA_ISR); + DBGR("%16s = 0x%8x\n", "PDMA_DESPR", PDMA_DESPR); + + DBGR("%16s = 0x%8x |", "PDMA_RBR", PDMA_RBR); + DBGR("%16s = 0x%8x\n", "PDMA_DAR", PDMA_DAR); + + DBGR("%16s = 0x%8x |", "PDMA_BAR", PDMA_BAR); + DBGR("%16s = 0x%8x\n", "PDMA_CPR", PDMA_CPR); + + DBGR("%16s = 0x%8x |", "PDMA_CCR", PDMA_CCR); + DBGR("\n+----------------------------------------------------------------+\n"); +} +#else +void atsmb2_dump_reg(struct atsmb_host *host) {} +#endif + +unsigned int fmax2 = 515633; +unsigned int MMC2_DRIVER_VERSION; +int SD2_function = 0; /*0: normal SD/MMC card reader , 1: internal SDIO wifi*/ +int SDXC2_function; + +int SCC2_ID(void){ + unsigned short val; + + val = REG16_VAL(SYSTEM_CFG_CTRL_BASE_ADDR + 0x2); + return val; +} + +int get_chip_version2(void) /*2008/05/01 janshiue modify for A1 chip*/ +{ + u32 tmp; + + tmp = REG32_VAL(SYSTEM_CFG_CTRL_BASE_ADDR); + tmp = ((tmp & 0xF00) >> 4) + 0x90 + ((tmp & 0xFF) - 1); + return tmp; +} + +void get_driver_version2(void) +{ + if (SCC2_ID() == 0x3426) { + if (get_chip_version2() < 0xA1) + MMC2_DRIVER_VERSION = MMC_DRV_3426_A0; + else if (get_chip_version2() == 0xA1) + MMC2_DRIVER_VERSION = MMC_DRV_3426_A1; + else + MMC2_DRIVER_VERSION = MMC_DRV_3426_A2; + } else if (SCC2_ID() == 0x3437) { + if (get_chip_version2() < 0xA1) + MMC2_DRIVER_VERSION = MMC_DRV_3437_A0; + else + MMC2_DRIVER_VERSION = MMC_DRV_3437_A1; + } else if (SCC2_ID() == 0x3429) { + MMC2_DRIVER_VERSION = MMC_DRV_3429; + } else if (SCC2_ID() == 0x3451) { + if (get_chip_version2() < 0xA1) + MMC2_DRIVER_VERSION = MMC_DRV_3451_A0; + } else if (SCC2_ID() == 0x3465) { + MMC2_DRIVER_VERSION = MMC_DRV_3465; + } else if (SCC2_ID() == 0x3445) { + MMC2_DRIVER_VERSION = MMC_DRV_3445; + } else if (SCC2_ID() == 0x3481) { + MMC2_DRIVER_VERSION = MMC_DRV_3481; + } else if (SCC2_ID() == 0x3498) { + MMC2_DRIVER_VERSION = MMC_DRV_3498; + } +} +/*2008/10/6 RichardHsu-e*/ + +/********************************************************************** +Name : atsmb2_alloc_desc +Function : To config PDMA descriptor setting. +Calls : +Called by : +Parameter : +Author : Janshiue Wu +History : +***********************************************************************/ +static inline int atsmb2_alloc_desc(struct atsmb_host *host, + unsigned int bytes) +{ + void *DescPool = NULL; + DBG("[%s] s\n",__func__); + + DescPool = dma_alloc_coherent(host->mmc->parent, bytes, &(host->DescPhyAddr), GFP_KERNEL); + if (!DescPool) { + DBG("can't allocal desc pool=%8X %8X\n", DescPool, host->DescPhyAddr); + DBG("[%s] e1\n",__func__); + return -1; + } + DBG("allocal desc pool=%8X %8X\n", DescPool, host->DescPhyAddr); + host->DescVirAddr = (unsigned long *)DescPool; + host->DescSize = bytes; + DBG("[%s] e2\n",__func__); + return 0; +} + +/********************************************************************** +Name : atsmb2_config_desc +Function : To config PDMA descriptor setting. +Calls : +Called by : +Parameter : +Author : Janshiue Wu +History : +***********************************************************************/ +static inline void atsmb2_config_desc(unsigned long *DescAddr, + unsigned long *BufferAddr, + unsigned long Blk_Cnt) +{ + + int i = 0 ; + unsigned long *CurDes = DescAddr; + DBG("[%s] s\n",__func__); + + /* the first Descriptor store for 1 Block data + * (512 bytes) + */ + for (i = 0 ; i < 3 ; i++) { + atsmb2_init_short_desc(CurDes, 0x80, BufferAddr, 0); + BufferAddr += 0x20; + CurDes += sizeof(struct SD_PDMA_DESC_S)/4; + } + if (Blk_Cnt > 1) { + atsmb2_init_long_desc(CurDes, 0x80, BufferAddr, CurDes + (sizeof(struct SD_PDMA_DESC_L)/4), 0); + BufferAddr += 0x20; + CurDes += sizeof(struct SD_PDMA_DESC_L)/4; + /* the following Descriptor store the rest Blocks data + * (Blk_Cnt - 1) * 512 bytes + */ + atsmb2_init_long_desc(CurDes, (Blk_Cnt - 1) * 512, + BufferAddr, CurDes + (sizeof(struct SD_PDMA_DESC_L)/4), 1); + } else { + atsmb2_init_long_desc(CurDes, 0x80, BufferAddr, CurDes + (sizeof(struct SD_PDMA_DESC_L)/4), 1); + } + DBG("[%s] e\n",__func__); +} +/********************************************************************** +Name : atsmb2_config_dma +Function : To set des/src address, byte count to transfer, and DMA channel settings, + and DMA ctrl. register. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static inline void atsmb2_config_dma(unsigned long config_dir, + unsigned long dma_mask, + struct atsmb_host *host) +{ + + DBG("[%s] s\n",__func__); + /* Enable DMA */ + *ATSMB2_PDMA_GCR = SD_PDMA_GCR_DMA_EN; + *ATSMB2_PDMA_GCR = SD_PDMA_GCR_SOFTRESET; + *ATSMB2_PDMA_GCR = SD_PDMA_GCR_DMA_EN; + /*open interrupt*/ + *ATSMB2_PDMA_IER = SD_PDMA_IER_INT_EN; + /*Make sure host could co-work with DMA*/ + *ATSMB2_SD_STS_2 |= ATSMB_CLK_FREEZ_EN; + + /*Set timer timeout value*/ + /*If clock is 390KHz*/ + if (host->current_clock < 400000) + *ATSMB2_TIMER_VAL = 0x200; /*1024*512*(1/390K) second*/ + else + *ATSMB2_TIMER_VAL = 0x1fff; /*why not to be 0xffff?*/ + + /*clear all DMA INT status for safety*/ + *ATSMB2_PDMA_ISR |= SD_PDMA_IER_INT_STS; + + /* hook desc */ + *ATSMB2_PDMA_DESPR = host->DescPhyAddr; + if (config_dir == DMA_CFG_WRITE) + *ATSMB2_PDMA_CCR &= SD_PDMA_CCR_IF_to_peripheral; + else + *ATSMB2_PDMA_CCR |= SD_PDMA_CCR_peripheral_to_IF; + + host->DmaIntMask = dma_mask; /*save success event*/ + + *ATSMB2_PDMA_CCR |= SD_PDMA_CCR_RUN; + DBG("[%s] e\n",__func__); +} + +/********************************************************************** +Name : atsmb2_prep_cmd +Function : +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static inline void atsmb2_prep_cmd(struct atsmb_host *host, + u32 opcode, + u32 arg, + unsigned int flags, + u16 blk_len, + u16 blk_cnt, + unsigned char int_maks_0, + unsigned char int_mask_1, + unsigned char cmd_type, + unsigned char op) +{ + unsigned long spl_flags; /* add by th */ + DBG("[%s] s\n",__func__); + + /*set cmd operation code and arguments.*/ + host->opcode = opcode; + *ATSMB2_CMD_IDX = opcode; /* host->opcode is set for further use in ISR.*/ + *ATSMB2_CMD_ARG = arg; + +#if 0 /* Fixme to support SPI mode, James Tian*/ + if ((flags && MMC_RSP_NONE) == MMC_RSP_NONE) + *ATSMB2_RSP_TYPE = ATMSB_TYPE_R0; + else if ((flags && MMC_RSP_R1) == MMC_RSP_R1) + *ATSMB2_RSP_TYPE = ATMSB_TYPE_R1; + else if ((flags && MMC_RSP_R1B) == MMC_RSP_R1B) + *ATSMB2_RSP_TYPE = ATMSB_TYPE_R1b; + else if ((flags && MMC_RSP_R2) == MMC_RSP_R2) + *ATSMB2_RSP_TYPE = ATMSB_TYPE_R2; + else if ((flags && MMC_RSP_R3) == MMC_RSP_R3) + *ATSMB2_RSP_TYPE = ATMSB_TYPE_R3; + else if ((flags && MMC_RSP_R6) == MMC_RSP_R6) + *ATSMB2_RSP_TYPE = ((opcode != SD_SEND_IF_COND) ? ATMSB_TYPE_R6 : ATMSB_TYPE_R7); + else + *ATSMB2_RSP_TYPE = ATMSB_TYPE_R0; +#endif + +#if 1 + /*set cmd response type*/ + switch (flags) { + case MMC_RSP_NONE | MMC_CMD_AC: + case MMC_RSP_NONE | MMC_CMD_BC: + *ATSMB2_RSP_TYPE = ATMSB_TYPE_R0; + break; + case MMC_RSP_R1 | MMC_CMD_ADTC: + case MMC_RSP_R1 | MMC_CMD_AC: + *ATSMB2_RSP_TYPE = ATMSB_TYPE_R1; + break; + case MMC_RSP_R1B | MMC_CMD_AC: + *ATSMB2_RSP_TYPE = ATMSB_TYPE_R1b; + break; + case MMC_RSP_R2 | MMC_CMD_BCR: + case MMC_RSP_R2 | MMC_CMD_AC: + *ATSMB2_RSP_TYPE = ATMSB_TYPE_R2; + break; + case MMC_RSP_R3 | MMC_CMD_BCR: + *ATSMB2_RSP_TYPE = ATMSB_TYPE_R3; + break; + case MMC_RSP_R6 | MMC_CMD_BCR: /*MMC_RSP_R6 = MMC_RSP_R7.*/ + *ATSMB2_RSP_TYPE = ((opcode != SD_SEND_IF_COND) ? + ATMSB_TYPE_R6 : ATMSB_TYPE_R7); + break; + case MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_AC: + case MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC: + *ATSMB2_RSP_TYPE = ATMSB_TYPE_R5; + break; + case MMC_RSP_SPI_R4 | MMC_RSP_R4 | MMC_CMD_BCR: + *ATSMB2_RSP_TYPE = ATMSB_TYPE_R4; + break; + default: + *ATSMB2_RSP_TYPE = ATMSB_TYPE_R0; + break; + } +#endif + /*SDIO cmd 52 , 53*/ + if ((opcode == SD_IO_RW_DIRECT) || + (opcode == SD_IO_RW_EXTENDED)) { + *ATSMB2_RSP_TYPE = ATMSB_TYPE_R5; + *ATSMB2_RSP_TYPE |= BIT6; + } + /*SDIO cmd 5*/ + if ((opcode == SD_IO_SEND_OP_COND) && + ((flags & (MMC_RSP_PRESENT| + MMC_RSP_136| + MMC_RSP_CRC| + MMC_RSP_BUSY| + MMC_RSP_OPCODE)) == MMC_RSP_R4)) { + *ATSMB2_RSP_TYPE = ATMSB_TYPE_R4; + *ATSMB2_RSP_TYPE |= BIT6; + } + + /*reset Response FIFO*/ + *ATSMB2_CTL |= 0x08; + + /* SD Host enable Clock */ + *ATSMB2_BUS_MODE |= ATSMB_CST; + + /*Set Cmd-Rsp Timeout to be maximum value.*/ + *ATSMB2_RSP_TOUT = 0xFE; + + /*clear all status registers for safety*/ + *ATSMB2_SD_STS_0 |= 0xff; + *ATSMB2_SD_STS_1 |= 0xff; + *ATSMB2_SD_STS_2 |= 0xff; + //*ATSMB2_SD_STS_2 |= 0x7f; + *ATSMB2_SD_STS_3 |= 0xff; + + //set block length and block count for cmd requesting data + *ATSMB2_BLK_LEN &=~(0x07ff); + *ATSMB2_BLK_LEN |= blk_len; + //*ATSMB2_SHDW_BLKLEN = blk_len; + *ATSMB2_BLK_CNT = blk_cnt; + + + *ATSMB2_INT_MASK_0 |= int_maks_0; + + /* spin_lock add by th for fix sdio irq*/ + spin_lock_irqsave(&host->lock, spl_flags); + *ATSMB2_INT_MASK_1 |= int_mask_1; + spin_unlock_irqrestore(&host->lock, spl_flags); + + //Set Auto stop for Multi-block access + if(cmd_type == 3 || cmd_type == 4) + { + //auto stop command set. + *ATSMB2_EXT_CTL |= 0x01; + +/* + * Enable transaction abort. + * When CRC error occurs, CMD 12 would be automatically issued. + * That is why we cannot enable R/W CRC error INTs. + * If we enable CRC error INT, we would handle this INT in ISR and then issue CMD 12 via software. + */ + *ATSMB2_BLK_LEN |= 0x0800; + } + + /*Set read or write*/ + if (op == 1) + *ATSMB2_CTL &= ~(0x04); + else if (op == 2) + *ATSMB2_CTL |= 0x04; + + /*for Non data access command, command type is 0.*/ + *ATSMB2_CTL &= 0x0F; + *ATSMB2_CTL |= (cmd_type<<4); + DBG("[%s] e\n",__func__); +} + +static inline void atsmb2_issue_cmd(void) +{ + *ATSMB2_CTL |= ATSMB_START; + wmb(); +} +/********************************************************************** +Name : atsmb2_request_end +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static void +atsmb2_request_end(struct atsmb_host *host, struct mmc_request *mrq) +{ + DBG("[%s] s\n",__func__); + /* + * Need to drop the host lock here; mmc_request_done may call + * back into the driver... + */ + //kevin delete spin lock + //spin_unlock(&host->lock); + /*DBG("100");*/ + mmc_request_done(host->mmc, mrq); + /*DBG("101\n");*/ + //spin_lock(&host->lock); + DBG("[%s] e\n",__func__); +} + +/********************************************************************** +Name : atsmb2_data_done +Function : +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +void atsmb2_wait_done(void *data) +{ + struct atsmb_host *host = (struct atsmb_host *) data; + DBG("[%s] s\n",__func__); + + WARN_ON(host->done_data == NULL); + complete(host->done_data); + host->done_data = NULL; + host->done = NULL; + DBG("[%s] e\n",__func__); +} + +/********************************************************************** +wait_data_busy +waiting for previous data transfer done +**********************************************************************/ +void wait_data_busy(struct mmc_command *cmd) +{ + int wait_count = 500000; + + switch(cmd->opcode) { + case MMC_GO_IDLE_STATE: + case MMC_STOP_TRANSMISSION: + case MMC_SEND_STATUS: + case MMC_GO_INACTIVE_STATE: + break; + default: + do { + if (*ATSMB2_CURBLK_CNT & (0x01<<24)) + break; + + udelay(1); + + } while (--wait_count); + } + +} +/********************************************************************** +Name : atsmb2_start_data +Function : If we start data, there must be only four cases. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static void atsmb2_start_data(struct atsmb_host *host) +{ + DECLARE_COMPLETION(complete); + unsigned char cmd_type = 0; + unsigned char op = 0; /*0: non-operation; 1:read; 2: write*/ + unsigned char mask_0 = 0; + unsigned char mask_1 = 0; + unsigned long dma_mask = 0; + + struct mmc_data *data = host->data; + struct mmc_command *cmd = host->cmd; + + struct scatterlist *sg = NULL; + unsigned int sg_len = 0; + + unsigned int total_blks = 0; /*total block number to transfer*/ + u32 card_addr = 0; + unsigned long dma_len = 0; + unsigned long total_dma_len = 0; + dma_addr_t dma_phy = 0; /* physical address used for DMA*/ + unsigned int dma_times = 0; /*times dma need to transfer*/ + unsigned int dma_loop = 0; + unsigned int sg_num = 0; + int loop_cnt = 10000; + unsigned int sg_transfer_len = 0; /*record each time dma transfer sg length */ + + DBG("[%s] s\n",__func__); + data->bytes_xfered = 0; + cmd->error = 0; + data->error = 0; + + /*for loop*/ + sg = data->sg; + sg_len = data->sg_len; + + dma_times = (sg_len / MAX_DESC_NUM); + if (sg_len % MAX_DESC_NUM) + dma_times++; + DBG("dma_times = %d sg_len = %d sg = %x\n", dma_times, sg_len, sg); + card_addr = cmd->arg; /*may be it is block-addressed, or byte-addressed.*/ + total_blks = data->blocks; + dma_map_sg(&(host->mmc->class_dev), sg, sg_len, + ((data->flags)&MMC_DATA_READ) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + + + for (dma_loop = 1 ; dma_loop <= dma_times; dma_loop++, sg_len -= sg_transfer_len) { + DBG("dma_loop = %d sg_len = %d sg_transfer_len = %d\n", dma_loop, sg_len, sg_transfer_len); + if (dma_loop < dma_times) + sg_transfer_len = MAX_DESC_NUM; + else + sg_transfer_len = sg_len; + DBG("sg_transfer_len = %d\n", sg_transfer_len); + /* + *Firstly, check and wait till card is in the transfer state. + *For our hardware, we can not consider + *the card has successfully tranfered its state from data/rcv to trans, + *when auto stop INT occurs. + */ + loop_cnt = 10000; + do { + if (host->cmd->opcode == SD_IO_RW_EXTENDED) + break; + loop_cnt--; + WARN_ON(loop_cnt == 0); + host->done_data = &complete; + host->done = &atsmb2_wait_done; + host->soft_timeout = 1; + atsmb2_prep_cmd(host, + MMC_SEND_STATUS, + (host->mmc->card->rca)<<16, + MMC_RSP_R1 | MMC_CMD_AC, + 0, + 0, + 0, /*mask_0*/ + ATSMB_RSP_DONE_EN + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN, + 0, /*cmd type*/ + 0); /*read or write*/ + atsmb2_issue_cmd(); + DBG("%16s = 0x%8x |", "INT_MASK_1", *ATSMB2_INT_MASK_1); + DBG("%16s = 0x%8x \n", "SD_STS_1", *ATSMB2_SD_STS_1); + /*ISR would completes it.*/ + wait_for_completion_timeout(&complete, ATSMB_TIMEOUT_TIME); + + WARN_ON(host->soft_timeout == 1); + if (host->soft_timeout == 1) { + DBG("%s soft_timeout.\n", __func__); + atsmb2_dump_reg(host); + } + if (cmd->error != MMC_ERR_NONE) { + cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("Getting Status failed.\n"); + goto end; + } + } while ((cmd->resp[0] & 0x1f00) != 0x900 && loop_cnt > 0); /*wait for trans state.*/ + + /* + * Now, we can safely issue read/write command. + * We can not consider this request as multi-block acess or single one via opcode, + * as request is splitted into sgs. + * Some sgs may be single one, some sgs may be multi one. + */ + + dma_phy = sg_dma_address(sg); + dma_len = sg_dma_len(sg); + DBG("dma_len = %d data->blksz = %d sg_len = %d\n", dma_len, data->blksz, sg_len); + /*SDIO read/write*/ + if (host->cmd->opcode == SD_IO_RW_EXTENDED) { + /*Single Block read/write*/ + if ((dma_len / (data->blksz)) == 1 && (sg_len == 1)) { + /* read operation*/ + if (data->flags & MMC_DATA_READ) { + host->opcode = SD_IO_RW_EXTENDED; + cmd_type = 2; + op = 1; + mask_0 = 0; /*BLOCK_XFER_DONE INT skipped, we use DMA TC INT*/ + mask_1 = (//ATSMB_SDIO_EN + ATSMB_READ_CRC_ERR_EN + |ATSMB_DATA_TIMEOUT_EN + /*Data Timeout and CRC error */ + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN); + /*Resp Timeout and CRC error */ + dma_mask = SD_PDMA_CCR_Evt_success; + DBG("[%s]SR opcode = %d type = 0x%x op = 0x%x m_0 = 0x%x m_1 = 0x%x d_m = 0x%x\n", + __func__,host->opcode,cmd_type,op,mask_0,mask_1,dma_mask); + } else { + /* write operation*/ + host->opcode = SD_IO_RW_EXTENDED; + cmd_type = 1; + op = 2; + /*====That is what we want===== DMA TC INT skipped*/ + mask_0 = ATSMB_BLOCK_XFER_DONE_EN; + mask_1 = (//ATSMB_SDIO_EN + ATSMB_WRITE_CRC_ERR_EN + |ATSMB_DATA_TIMEOUT_EN + /*Data Timeout and CRC error */ + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN); + /*Resp Timeout and CRC error */ + dma_mask = 0; + DBG("[%s]SW opcode = %d type = 0x%x op = 0x%x m_0 = 0x%x m_1 = 0x%x d_m = 0x%x\n", + __func__,host->opcode,cmd_type,op,mask_0,mask_1,dma_mask); + } + } else { + /*Multiple Block read/write*/ + /* read operation*/ + if (data->flags & MMC_DATA_READ) { + host->opcode = SD_IO_RW_EXTENDED; + cmd_type = 6; + op = 1; + mask_0 = 0; /*MULTI_XFER_DONE_EN skipped*/ + mask_1 = (//ATSMB_SDIO_EN /*====That is what we want=====*/ + ATSMB_READ_CRC_ERR_EN + |ATSMB_DATA_TIMEOUT_EN + /*Data Timeout and CRC error */ + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN); + /*Resp Timeout and CRC error */ + dma_mask = SD_PDMA_CCR_Evt_success; + DBG("[%s]MR opcode = %d type = 0x%x op = 0x%x m_0 = 0x%x m_1 = 0x%x d_m = 0x%x\n", + __func__,host->opcode,cmd_type,op,mask_0,mask_1,dma_mask); + } else { + /* write operation*/ + host->opcode = SD_IO_RW_EXTENDED; + cmd_type = 5; + op = 2; + mask_0 = ATSMB_MULTI_XFER_DONE_EN;//ATSMB_BLOCK_XFER_DONE_EN; /*MULTI_XFER_DONE INT skipped*/ + mask_1 = (//ATSMB_SDIO_EN /*====That is what we want=====*/ + ATSMB_WRITE_CRC_ERR_EN + |ATSMB_DATA_TIMEOUT_EN + /*Data Timeout and CRC error */ + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN); + /*Resp Timeout and CRC error */ + dma_mask = 0; + DBG("[%s]MR opcode = %d type = 0x%x op = 0x%x m_0 = 0x%x m_1 = 0x%x d_m = 0x%x\n", + __func__,host->opcode,cmd_type,op,mask_0,mask_1,dma_mask); + } + } + + } else { + if ((dma_len / (data->blksz)) == 1 && (sg_len == 1)) { + if (data->flags & MMC_DATA_READ) {/* read operation*/ + host->opcode = MMC_READ_SINGLE_BLOCK; + cmd_type = 2; + op = 1; + mask_0 = 0; /*BLOCK_XFER_DONE INT skipped, we use DMA TC INT*/ + mask_1 = (ATSMB_READ_CRC_ERR_EN + |ATSMB_DATA_TIMEOUT_EN + /*Data Timeout and CRC error */ + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN); + /*Resp Timeout and CRC error */ + dma_mask = SD_PDMA_CCR_Evt_success; + } else {/*write operation*/ + host->opcode = MMC_WRITE_BLOCK; + cmd_type = 1; + op = 2; + /*====That is what we want===== DMA TC INT skipped*/ + mask_0 = ATSMB_BLOCK_XFER_DONE_EN; + mask_1 = (ATSMB_WRITE_CRC_ERR_EN + |ATSMB_DATA_TIMEOUT_EN + /*Data Timeout and CRC error */ + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN); + /*Resp Timeout and CRC error */ + dma_mask = 0; + } + } else { /*more than one*/ + if (data->flags&MMC_DATA_READ) {/* read operation*/ + host->opcode = MMC_READ_MULTIPLE_BLOCK; + cmd_type = 4; + op = 1; + mask_0 = 0; /*MULTI_XFER_DONE_EN skipped*/ + mask_1 = (ATSMB_AUTO_STOP_EN /*====That is what we want=====*/ + |ATSMB_DATA_TIMEOUT_EN + /*Data Timeout and CRC error */ + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN); + /*Resp Timeout and CRC error */ + dma_mask = 0; + } else {/*write operation*/ + host->opcode = MMC_WRITE_MULTIPLE_BLOCK; + cmd_type = 3; + op = 2; + mask_0 = 0; /*MULTI_XFER_DONE INT skipped*/ + mask_1 = (ATSMB_AUTO_STOP_EN /*====That is what we want=====*/ + |ATSMB_DATA_TIMEOUT_EN + /*Data Timeout and CRC error */ + |ATSMB_RSP_CRC_ERR_EN + |ATSMB_RSP_TIMEOUT_EN); + /*Resp Timeout and CRC error */ + dma_mask = 0; + } + } + } + /*To controller every sg done*/ + host->done_data = &complete; + host->done = &atsmb2_wait_done; + /*sleep till ISR wakes us*/ + host->soft_timeout = 1; /*If INT comes early than software timer, it would be cleared.*/ + + total_dma_len = 0; + DBG("host->DescVirAddr = %x host->DescSize=%x\n", host->DescVirAddr, host->DescSize); + memset(host->DescVirAddr, 0, host->DescSize); + for (sg_num = 0 ; sg_num < sg_transfer_len ; sg++, sg_num++) { + + /* + * Now, we can safely issue read/write command. + * We can not consider this request as multi-block acess or single one via opcode, + * as request is splitted into sgs. + * Some sgs may be single one, some sgs may be multi one. + */ + + dma_phy = sg_dma_address(sg); + dma_len = sg_dma_len(sg); + total_dma_len = total_dma_len + dma_len; + DBG("sg_num=%d sg_transfer_len=%d sg=%x sg_len=%d total_dma_len=%d dma_len=%d\n", + sg_num, sg_transfer_len, sg, sg_len, total_dma_len, dma_len); + /*2009/01/15 janshiue add*/ + if (sg_num < sg_transfer_len - 1) { + /* means the last descporitor */ + atsmb2_init_short_desc( + host->DescVirAddr + (sg_num * sizeof(struct SD_PDMA_DESC_S)/4), + dma_len, (unsigned long *)dma_phy, 0); + } else { + atsmb2_init_short_desc( + host->DescVirAddr + (sg_num * sizeof(struct SD_PDMA_DESC_S)/4), + dma_len, (unsigned long *)dma_phy, 1); + } + /*2009/01/15 janshiue add*/ + + } + /*operate our hardware*/ + atsmb2_prep_cmd(host, + host->opcode, + /*arg, may be byte addressed, may be block addressed.*/ + card_addr, + cmd->flags, + data->blksz - 1, /* in fact, it is useless.*/ + /* for single one, it is useless. but for multi one, */ + /* it would be used to tell auto stop function whether it is done.*/ + total_dma_len/(data->blksz), + mask_0, + mask_1, + cmd_type, + op); + + atsmb2_config_dma((op == 1) ? DMA_CFG_READ : DMA_CFG_WRITE, + dma_mask, + host); + + wait_data_busy(cmd); + //sync from tp,fix broadcom 6181 wifi dongle crash. + //udelay(10); + atsmb2_issue_cmd(); + wait_for_completion_timeout(&complete, + ATSMB_TIMEOUT_TIME*sg_transfer_len); /*ISR would completes it.*/ + + /* When the address of request plus length equal card bound, + * force this stop command response as pass. Eason 2012/4/20 */ + if (cmd->resp[0] == 0x80000b00) { + /*This caes used for SD2.0 and after MMC4.1 version*/ + if (card_addr+(total_dma_len/data->blksz) == host->mmc->card->csd.capacity) { + cmd->resp[0] = 0x00000b00; + /*printk("card_addr = %08X, card_length = %08X,card capacity = %08X\n", + card_addr,(total_dma_len/data->blksz),host->mmc->card->csd.capacity); + printk("card_resp[0]=%08X, addr = %08X\n",cmd->resp[0],cmd->resp);*/ + } + + /* This caes used for SD1.0 and before MMC 4.1 */ + if ((card_addr/data->blksz)+(total_dma_len/data->blksz) == host->mmc->card->csd.capacity) { + cmd->resp[0] = 0x00000b00; + /*printk("Eason test: cmd->arg = %08X, data->blksz = %08X, length = %08X\n", + card_addr,data->blksz,(total_dma_len/data->blksz));*/ + } + } + + if (host->soft_timeout == 1) { + atsmb2_dump_reg(host); + //*ATSMB2_BUS_MODE |= ATSMB_SFTRST; + *ATSMB2_PDMA_GCR = SD_PDMA_GCR_SOFTRESET; + /*disable INT */ + *ATSMB2_INT_MASK_0 &= ~(ATSMB_BLOCK_XFER_DONE_EN | ATSMB_MULTI_XFER_DONE_EN); + *ATSMB2_INT_MASK_1 &= ~(ATSMB_WRITE_CRC_ERR_EN|ATSMB_READ_CRC_ERR_EN|ATSMB_RSP_CRC_ERR_EN + |ATSMB_DATA_TIMEOUT_EN|ATSMB_AUTO_STOP_EN|ATSMB_RSP_TIMEOUT_EN|ATSMB_RSP_DONE_EN); + + cmd->error = -ETIMEDOUT; + data->error = -ETIMEDOUT; + } + + WARN_ON(host->soft_timeout == 1); + + /*check everything goes okay or not*/ + if (cmd->error != MMC_ERR_NONE + || data->error != MMC_ERR_NONE) { + DBG("CMD or Data failed error=%X DescVirAddr=%8X dma_phy=%8X dma_mask = %x\n", + cmd->error, host->DescVirAddr, dma_phy, host->DmaIntMask); + goto end; + } +// card_addr += total_dma_len>>(mmc_card_blockaddr(host->mmc->card_selected) ? 9 : 0); + card_addr += total_dma_len>>(mmc_card_blockaddr(host->mmc->card) ? 9 : 0); //zhf: modified by James Tian + data->bytes_xfered += total_dma_len; + + + } +// dma_unmap_sg(&(host->mmc->class_dev), data->sg, data->sg_len, +// ((data->flags)&MMC_DATA_READ) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + WARN_ON(total_blks != (data->bytes_xfered / data->blksz)); + host->opcode = 0; +end: + dma_unmap_sg(&(host->mmc->class_dev), data->sg, data->sg_len, + ((data->flags)&MMC_DATA_READ) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + //kevin delete spin lock + //spin_lock(&host->lock); + atsmb2_request_end(host, host->mrq); + //spin_unlock(&host->lock); + DBG("[%s] e\n",__func__); +} + + +/********************************************************************** +Name : atsmb2_cmd_with_data_back +Function : +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static void atsmb2_cmd_with_data_back(struct atsmb_host *host) +{ + DECLARE_COMPLETION(complete); + + struct scatterlist *sg = NULL; + unsigned int sg_len = 0; + DBG("[%s] s\n",__func__); + /*for loop*/ + sg = host->data->sg; + sg_len = host->data->sg_len; + /*To controller every sg done*/ + host->done_data = &complete; + host->done = &atsmb2_wait_done; + dma_map_sg(&(host->mmc->class_dev), sg, sg_len, DMA_FROM_DEVICE); + + /*2009/01/15 janshiue add*/ + memset(host->DescVirAddr, 0, host->DescSize); + atsmb2_init_long_desc(host->DescVirAddr, sg_dma_len(sg), (unsigned long *)sg_dma_address(sg), 0, 1); + /*2009/01/15 janshiue add*/ + /*prepare for cmd*/ + atsmb2_prep_cmd(host, /*host*/ + host->cmd->opcode, /*opcode*/ + host->cmd->arg, /*arg*/ + host->cmd->flags, /*flags*/ + sg_dma_len(sg)-1, /*block length*/ + 0, /*block size: It looks like useless*/ + 0, /*int_mask_0*/ + (ATSMB_RSP_CRC_ERR_EN|ATSMB_RSP_TIMEOUT_EN + |ATSMB_READ_CRC_ERR_EN |ATSMB_DATA_TIMEOUT_EN), /*int_mask_1*/ + 2, /*cmd_type*/ + 1); /*op*/ + + atsmb2_config_dma(DMA_CFG_READ, + SD_PDMA_CCR_Evt_success, + host); + atsmb2_issue_cmd(); + /*ISR would completes it.*/ + wait_for_completion_timeout(&complete, ATSMB_TIMEOUT_TIME); + + dma_unmap_sg(&(host->mmc->class_dev), host->data->sg, host->data->sg_len, + ((host->data->flags)&MMC_DATA_READ) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + spin_lock(&host->lock); + atsmb2_request_end(host, host->mrq); + spin_unlock(&host->lock); + DBG("[%s] e\n",__func__); +} +/********************************************************************** +Name : atsmb2_start_cmd +Function : +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static void atsmb2_start_cmd(struct atsmb_host *host) +{ + unsigned char int_mask_0,int_mask_1; + int_mask_0 = 0; + int_mask_1 = ATSMB_RSP_DONE_EN|ATSMB_RSP_CRC_ERR_EN|ATSMB_RSP_TIMEOUT_EN; + + DBG("[%s] s\n",__func__); + + atsmb2_prep_cmd(host, + host->cmd->opcode, + host->cmd->arg, + host->cmd->flags, + 0, /*useless*/ + 0, /*useless*/ + int_mask_0, /*mask_0*/ + int_mask_1, /*mask_1*/ + 0, /*cmd type*/ + 0); /*read or write*/ + atsmb2_issue_cmd(); + DBG("[%s] e\n",__func__); +} +/********************************************************************** +Name : atsmb2_fmt_check_rsp +Function : +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static inline void atsmb2_fmt_check_rsp(struct atsmb_host *host) +{ + + u8 tmp_resp[4] = {0}; + int i, j, k; + DBG("[%s] s\n",__func__); + if (host->cmd->flags != (MMC_RSP_R2 | MMC_CMD_AC) + && host->cmd->flags != (MMC_RSP_R2 | MMC_CMD_BCR)) { + for (j = 0, k = 1; j < 4; j++, k++) + tmp_resp[j] = *(REG8_PTR(_RSP_0 + MMC_ATSMB2_BASE+k)); + + host->cmd->resp[0] = (tmp_resp[0] << 24) | + (tmp_resp[1]<<16) | (tmp_resp[2]<<8) | (tmp_resp[3]); + } else { + for (i = 0, k = 1; i < 4; i++) { /*R2 has 4 u32 response.*/ + for (j = 0; j < 4; j++) { + if (k < 16) + tmp_resp[j] = *(REG8_PTR(_RSP_0 + MMC_ATSMB2_BASE+k)); + else /* k =16*/ + tmp_resp[j] = *(ATSMB2_RSP_0); + k++; + } + host->cmd->resp[i] = (tmp_resp[0]<<24) | (tmp_resp[1]<<16) | + (tmp_resp[2]<<8) | (tmp_resp[3]); + } + } + + /* + * For the situation that we need response, + * but response registers give us all zeros, we consider this operation timeout. + */ + if (host->cmd->flags != (MMC_RSP_NONE | MMC_CMD_AC) + && host->cmd->flags != (MMC_RSP_NONE | MMC_CMD_BC)) { + if (host->cmd->resp[0] == 0 && host->cmd->resp[1] == 0 + && host->cmd->resp[2] == 0 && host->cmd->resp[3] == 0) { + host->cmd->error = -ETIMEDOUT; //zhf: modified by James Tian + } + } + DBG("[%s] e\n",__func__); +} +/********************************************************************** +Name : atsmb2_get_slot_status +Function : Our host only supports one slot. +Calls : +Called by : +Parameter : +returns : 1: in slot; 0: not in slot. +Author : Leo Lee +History : +***********************************************************************/ +static int atsmb2_get_slot_status(struct mmc_host *mmc) +{ +// struct atsmb_host *host = mmc_priv(mmc); + unsigned char status_0 = 0; +// unsigned long flags; + unsigned long ret = 0; + DBG("[%s] s\n",__func__); +// spin_lock_irqsave(&host->lock, flags); // Vincent Li mark out for CONFIG_PREEMPT_RT + status_0 = *ATSMB2_SD_STS_0; +// spin_unlock_irqrestore(&host->lock, flags); // Vincent Li mark out for CONFIG_PREEMPT_RT + /* after WM3400 A1 ATSMB_CARD_IN_SLOT_GPI = 1 means not in slot*/ + if (MMC2_DRIVER_VERSION >= MMC_DRV_3426_A0) { + ret = ((status_0 & ATSMB_CARD_NOT_IN_SLOT_GPI) ? 0 : 1); + DBG("[%s] e1\n",__func__); + return ret; + } else { + ret = ((status_0 & ATSMB_CARD_NOT_IN_SLOT_GPI) ? 1 : 0); + DBG("[%s] e2\n",__func__); + return ret; + } + DBG("[%s] e3\n",__func__); + return 0; +} + +/********************************************************************** +Name : atsmb2_init_short_desc +Function : +Calls : +Called by : +Parameter : +Author : Janshiue Wu +History : +***********************************************************************/ +int atsmb2_init_short_desc(unsigned long *DescAddr, unsigned int ReqCount, unsigned long *BufferAddr, int End) +{ + struct SD_PDMA_DESC_S *CurDes_S; + DBG("[%s] s\n",__func__); + CurDes_S = (struct SD_PDMA_DESC_S *) DescAddr; + CurDes_S->ReqCount = ReqCount; + CurDes_S->i = 0; + CurDes_S->format = 0; + CurDes_S->DataBufferAddr = BufferAddr; + if (End) { + CurDes_S->end = 1; + CurDes_S->i = 1; + } + DBG("[%s] e\n",__func__); + return 0; +} + +/********************************************************************** +Name : atsmb2_init_long_desc +Function : +Calls : +Called by : +Parameter : +Author : Janshiue Wu +History : +***********************************************************************/ +int atsmb2_init_long_desc(unsigned long *DescAddr, unsigned int ReqCount, + unsigned long *BufferAddr, unsigned long *BranchAddr, int End) +{ + struct SD_PDMA_DESC_L *CurDes_L; + DBG("[%s] s\n",__func__); + CurDes_L = (struct SD_PDMA_DESC_L *) DescAddr; + CurDes_L->ReqCount = ReqCount; + CurDes_L->i = 0; + CurDes_L->format = 1; + CurDes_L->DataBufferAddr = BufferAddr; + CurDes_L->BranchAddr = BranchAddr; + if (End) { + CurDes_L->end = 1; + CurDes_L->i = 1; + } + DBG("[%s] e\n",__func__); + return 0; +} + +/********************************************************************** +Name : atsmb2_dma_isr +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static irqreturn_t atsmb2_dma_isr(int irq, void *dev_id) +{ + + struct atsmb_host *host = dev_id; + u8 status_0, status_1, status_2, status_3; + u32 pdma_event_code = 0; + DBG("[%s] s\n",__func__); + + disable_irq_nosync(irq); + spin_lock(&host->lock); + /*Get INT status*/ + status_0 = *ATSMB2_SD_STS_0; + status_1 = *ATSMB2_SD_STS_1; + status_2 = *ATSMB2_SD_STS_2; + status_3 = *ATSMB2_SD_STS_3; + /* after WM3426 A0 using PDMA */ + if (MMC2_DRIVER_VERSION >= MMC_DRV_3426_A0) { + + pdma_event_code = *ATSMB2_PDMA_CCR & SD_PDMA_CCR_EvtCode; + + /* clear INT status to notify HW clear EventCode*/ + *ATSMB2_PDMA_ISR |= SD_PDMA_IER_INT_STS; + + /*printk("dma_isr event code = %X\n", *ATSMB2_PDMA_CCR);*/ + /*We expect cmd with data sending back or read single block cmd run here.*/ + if (pdma_event_code == SD_PDMA_CCR_Evt_success) { + /*means need to update the data->error and cmd->error*/ + if (host->DmaIntMask == SD_PDMA_CCR_Evt_success) { + if (host->data != NULL) { + host->data->error = MMC_ERR_NONE; + host->cmd->error = MMC_ERR_NONE; + } else { + DBG("dma_isr2 hodt->data is NULL\n"); + /*disable INT*/ + *ATSMB2_PDMA_IER &= ~SD_PDMA_IER_INT_EN; + goto out; + } + } + /*else do nothing*/ + DBG("dma_isr PDMA OK\n"); + /*atsmb2_dump_reg(host);*/ + } + /*But unluckily, we should also be prepare for all kinds of error situation.*/ + else { + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBGR("** dma_isr PDMA fail** event code = %X\n", *ATSMB2_PDMA_CCR); + atsmb2_dump_reg(host); + } + + if (host->DmaIntMask == SD_PDMA_CCR_Evt_success) + atsmb2_fmt_check_rsp(host); + + /*disable INT*/ + *ATSMB2_PDMA_IER &= ~SD_PDMA_IER_INT_EN; + } + + /*wake up some one who is sleeping.*/ + if ((pdma_event_code != SD_PDMA_CCR_Evt_success) || (host->DmaIntMask == SD_PDMA_CCR_Evt_success)) { + if (host->done_data) {/* We only use done_data when requesting data.*/ + host->soft_timeout = 0; + host->done(host); + } else + atsmb2_request_end(host, host->mrq); /*for cmd with data sending back.*/ + } + +out: + spin_unlock(&host->lock); + enable_irq(irq); + DBG("[%s] e\n",__func__); + return IRQ_HANDLED; +} + +/* add by th for ack interrupt */ +void eagle_ack_interrupt(void) +{ + *ATSMB2_SD_STS_1 |= ATSMB_SDIO_INT; +} +EXPORT_SYMBOL(eagle_ack_interrupt); + +/********************************************************************** +Name : atsmb2_regular_isr +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +//static irqreturn_t atsmb2_regular_isr(int irq, void *dev_id, struct pt_regs *regs) +irqreturn_t atsmb2_regular_isr(int irq, void *dev_id) +{ + + struct atsmb_host *host = dev_id; + u8 status_0, status_1, status_2, status_3,mask_0,mask_1; + u32 pdma_sts; + + DBG("[%s] s\n",__func__); + WARN_ON(host == NULL); + + disable_irq_nosync(irq); + spin_lock(&host->lock); + + /*Get INT status*/ + status_0 = *ATSMB2_SD_STS_0; + status_1 = *ATSMB2_SD_STS_1; + status_2 = *ATSMB2_SD_STS_2; + status_3 = *ATSMB2_SD_STS_3; + + mask_0 = *ATSMB2_INT_MASK_0; + mask_1 = *ATSMB2_INT_MASK_1; + /******************************************************* + card insert interrupt + ********************************************************/ + if ((status_0 & ATSMB_DEVICE_INSERT) /*Status Set and IRQ enabled*/ + /*To aviod the situation that we intentionally disable IRQ to do rescan.*/ + && (*ATSMB2_INT_MASK_0 & 0x80)) { + + if (host->mmc->ops->get_slot_status(host->mmc)) { + host->mmc->scan_retry = 3; + host->mmc->card_scan_status = false; + } else { + host->mmc->scan_retry = 0; + host->mmc->card_scan_status = false; + } + + mmc_detect_change(host->mmc, HZ/2); + /*Taipei Side Request: Disable INSERT IRQ when doing rescan.*/ + //*ATSMB2_INT_MASK_0 &= (~0x80);/* or 40?*/ //zhf: marked by James Tian + /*Then we clear the INT status*/ + //iowrite32(ATSMB_DEVICE_INSERT, host->base+_SD_STS_0); + *ATSMB2_SD_STS_0 |= ATSMB_DEVICE_INSERT; + spin_unlock(&host->lock); + enable_irq(irq); + DBG("[%s] e1\n",__func__); + return IRQ_HANDLED; + } + + if ((status_1 & mask_1)& ATSMB_SDIO_INT) { + spin_unlock(&host->lock); + mmc_signal_sdio_irq(host->mmc); + + if (((status_1 & mask_1) == ATSMB_SDIO_INT) && ((status_0 & mask_0) == 0)) { + + enable_irq(irq); + DBG("[%s] e2\n",__func__); + + return IRQ_HANDLED; + } + spin_lock(&host->lock); + } + pdma_sts = *ATSMB2_PDMA_CCR; + + if (((status_0 & mask_0) | (status_1 & mask_1)) == 0) { + spin_unlock(&host->lock); + enable_irq(irq); + DBG("[%s] e3\n",__func__); + return IRQ_HANDLED; + } + /******************************************************* + command interrupt. + *******************************************************/ + /*for write single block*/ + if (host->opcode == MMC_WRITE_BLOCK) { + if ((status_0 & ATSMB_BLOCK_XFER_DONE) + && (status_1 & ATSMB_RSP_DONE)) { + host->data->error = MMC_ERR_NONE; + host->cmd->error = MMC_ERR_NONE; + /* okay, what we want.*/ + } else { + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("[%s] err1\n",__func__); + atsmb2_dump_reg(host); + } + } else if (host->opcode == MMC_WRITE_MULTIPLE_BLOCK + || host->opcode == MMC_READ_MULTIPLE_BLOCK) { + if ((status_1 & (ATSMB_AUTO_STOP|ATSMB_RSP_DONE)) + && (status_0 & ATSMB_MULTI_XFER_DONE)) { + /*If CRC error occurs, I think this INT would not occrs.*/ + /*okay, that is what we want.*/ + host->data->error = MMC_ERR_NONE; + host->cmd->error = MMC_ERR_NONE; + } else { + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("[%s] err2\n",__func__); + atsmb2_dump_reg(host); + + } + } else if (host->opcode == MMC_READ_SINGLE_BLOCK) {/* we want DMA TC, run here, must be error.*/ + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("[%s] err3\n",__func__); + atsmb2_dump_reg(host); + } else if (host->opcode == SD_IO_RW_EXTENDED){ + /*Write operation*/ + if (*ATSMB2_CTL & BIT2) { + if ((*ATSMB2_CTL & 0xf0) == 0x10) { /*single block write*/ + if ((status_0 & ATSMB_BLOCK_XFER_DONE) + && (status_1 & ATSMB_RSP_DONE)) { + host->data->error = MMC_ERR_NONE; + host->cmd->error = MMC_ERR_NONE; + /* okay, what we want.*/ + } else { + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("[%s] err4 status_0 = %x status_1 = %x\n",__func__,status_0,status_1); + } + + } else if ((*ATSMB2_CTL & 0xf0) == 0x50) { + if ((status_0 & ATSMB_MULTI_XFER_DONE) + && (status_1 & ATSMB_RSP_DONE)) { + host->data->error = MMC_ERR_NONE; + host->cmd->error = MMC_ERR_NONE; + /* okay, what we want.*/ + } else { + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("[%s] err4-2 status_0 = %x status_1 = %x\n",__func__,status_0,status_1); + } + } else { + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("[%s] err4-3 status_0 = %x status_1 = %x\n",__func__,status_0,status_1); + } + } + else { + /*Read operation*/ + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("[%s] err5\n",__func__); + } + + + } else { +/* command, not request data*/ +/* the command which need data sending back,*/ +/* like switch_function, send_ext_csd, send_scr, send_num_wr_blocks.*/ +/* NOTICE: we also send status before reading or writing data, so SEND_STATUS should be excluded.*/ + if (host->data && host->opcode != MMC_SEND_STATUS) { + host->data->error = -EIO; //MMC_ERR_FAILED; + host->cmd->error = -EIO; //MMC_ERR_FAILED; + DBG("[%s] err6\n",__func__); + atsmb2_dump_reg(host); + } else { /* Just command, no need data sending back.*/ + if (status_1 & ATSMB_RSP_DONE) { + /*Firstly, check data-response is busy or not.*/ + if (host->cmd->flags == (MMC_RSP_R1B | MMC_CMD_AC)) { + int i = 10000; + + while (status_2 & ATSMB_RSP_BUSY) { + status_2 = *ATSMB2_SD_STS_2; + if (--i == 0) + break; + DBG(" IRQ:Status_2 = %d, busy!\n", status_2); + } + if (i == 0) + printk("[MMC driver] Error :SD data-response always busy!"); + } +#if 1 +/*for our host, even no card in slot, for SEND_STATUS also returns no error.*/ +/*The protocol layer depends on SEND_STATUS to check whether card is in slot or not.*/ +/*In fact, we can also avoid this situation by checking the response whether they are all zeros.*/ + if (!atsmb2_get_slot_status(host->mmc) && host->opcode == MMC_SEND_STATUS) { + host->cmd->retries = 0; /* No retry.*/ +// host->cmd->error = MMC_ERR_INVALID; + host->cmd->error = -EINVAL; + } else +#endif + host->cmd->error = MMC_ERR_NONE; + } else { + if (status_1 & ATSMB_RSP_TIMEOUT) {/* RSP_Timeout .*/ +// host->cmd->error = MMC_ERR_TIMEOUT; + host->cmd->error = -ETIMEDOUT; + DBG("[%s] err7\n",__func__); + } else {/*or RSP CRC error*/ +// host->cmd->error = MMC_ERR_BADCRC; + host->cmd->error = -EILSEQ; + DBG("[%s] err8\n",__func__); + } + atsmb2_dump_reg(host); + } + } + } + atsmb2_fmt_check_rsp(host); + + /*disable INT */ + *ATSMB2_INT_MASK_0 &= ~(ATSMB_BLOCK_XFER_DONE_EN | ATSMB_MULTI_XFER_DONE_EN); + *ATSMB2_INT_MASK_1 &= ~(ATSMB_WRITE_CRC_ERR_EN|ATSMB_READ_CRC_ERR_EN|ATSMB_RSP_CRC_ERR_EN + |ATSMB_DATA_TIMEOUT_EN|ATSMB_AUTO_STOP_EN|ATSMB_RSP_TIMEOUT_EN|ATSMB_RSP_DONE_EN); + + + /*clear INT status. In fact, we will clear again before issuing new command.*/ + *ATSMB2_SD_STS_0 |= status_0; + *ATSMB2_SD_STS_1 |= status_1; + //*ATSMB2_SD_STS_1 |= ((status_1 & mask_1) | (status_1 & (~ATSMB_SDIO_INT))); + + /* when read CRC error occur, and the status can't write one to clear. + * To clear read CRC error status , can do software reset. This is HW bug. 2013/3/21*/ + if ((*ATSMB2_SD_STS_1 & BIT6) == 0x40) { + DBG("[%s] host2 CMD%d Read CRC error occur\n",__func__,host->cmd->opcode); + /* Save SD card register */ + atsmb2_copy_reg(0); + /* Software reset */ + *ATSMB2_BUS_MODE |= BIT7; + /* restore SD card register */ + atsmb2_copy_reg(1); + } + + if (*ATSMB2_PDMA_ISR & SD_PDMA_IER_INT_STS) + *ATSMB2_PDMA_ISR |= SD_PDMA_IER_INT_STS; + + /*wake up some one who is sleeping.*/ + if (host->done_data) { /* We only use done_data when requesting data.*/ + host->soft_timeout = 0; + host->done(host); + } else + atsmb2_request_end(host, host->mrq); /*for cmd without data.*/ + + spin_unlock(&host->lock); + enable_irq(irq); + DBG("[%s] e4\n",__func__); + return IRQ_HANDLED; +} +EXPORT_SYMBOL(atsmb2_regular_isr); + +/********************************************************************** +Name : atsmb2_get_ro +Function :. +Calls : +Called by : +Parameter : +returns : 0 : write protection is disabled. 1: write protection is enabled. +Author : Leo Lee +History : +***********************************************************************/ +int atsmb2_get_ro(struct mmc_host *mmc) +{ + struct atsmb_host *host = mmc_priv(mmc); + unsigned char status_0 = 0; + unsigned long flags; + unsigned long ret = 0; + DBG("[%s] s\n",__func__); + spin_lock_irqsave(&host->lock, flags); + status_0 = *ATSMB2_SD_STS_0; + spin_unlock_irqrestore(&host->lock, flags); + DBG("[%s]\nstatus_0 = 0x%x\n", __func__,status_0); + ret = ((status_0 & ATSMB_WRITE_PROTECT) ? 0 : 1); + DBG("[%s] e\n",__func__); + return ret; +} +/********************************************************************** +Name : atsmb2_dump_host_regs +Function : +Calls : +Called by : +Parameter : +returns : +Author : Leo Lee +History : +***********************************************************************/ +void atsmb2_dump_host_regs(struct mmc_host *mmc) +{ + struct atsmb_host *host = mmc_priv(mmc); + DBG("[%s] s\n",__func__); + atsmb2_dump_reg(host); + DBG("[%s] e\n",__func__); +} +EXPORT_SYMBOL(atsmb2_dump_host_regs); + + + + +#if MMC2_SDIO_EXT_IRQ +static irqreturn_t mtk6620_sdio_ext_irq (int irq, void *dev_id) +{ + struct mmc_host *mmc = dev_id; + + if(!is_gpio_irqenable(wmt_mtk6620_intr) || !gpio_irqstatus(wmt_mtk6620_intr)) + return IRQ_NONE; + + smp_rmb(); + if (unlikely(test_bit(IN_SUSPEND, &mmc2_sdio_ext_irq_flags))) { + //disable_irq_nosync(mmc2_sdio_ext_irq); + wmt_gpio_mask_irq(wmt_mtk6620_intr); + + set_bit(IRQ_IN_SUSPEND, &mmc2_sdio_ext_irq_flags); + smp_wmb(); + + printk( "%s: irq in suspend, defer & disable_irq(%d)\n", + mmc_hostname(mmc), mmc2_sdio_ext_irq); + wmt_gpio_ack_irq(wmt_mtk6620_intr); + return IRQ_HANDLED; + } + //printk( "%s: irq happen, call mmc_signal_sdio_irq\n", mmc_hostname(mmc)); + mmc_signal_sdio_irq(mmc); + wmt_gpio_ack_irq(wmt_mtk6620_intr); + return IRQ_HANDLED; +} + +static void mtk6620_setup_sdio_ext_irq (struct mmc_host *mmc) +{ + int ret; + printk(KERN_WARNING "setup sdio ext irq but mmc2_sdio_ext_irq(%d) != 0!\n", mmc2_sdio_ext_irq); + + if (0 != mmc2_sdio_ext_irq) { + return; + } + + wmt_gpio_setpull(wmt_mtk6620_intr,WMT_GPIO_PULL_UP); + +// s3c_gpio_setpull(MMC2_SDIO_EINT_PIN, S3C_GPIO_PULL_NONE); +// s3c_gpio_cfgpin(MMC2_SDIO_EINT_PIN, S3C_GPIO_SFN(0xF)); + + mmc2_sdio_ext_irq = 1;//gpio_to_irq(MMC2_SDIO_EINT_PIN); + + wmt_gpio_mask_irq(wmt_mtk6620_intr); + //clear_gpio26_irq_state(); + wmt_gpio_ack_irq(wmt_mtk6620_intr); + + + ret = request_irq(IRQ_GPIO, mtk6620_sdio_ext_irq, IRQF_SHARED,"mmc2_sdio_ext_irq", mmc); + if (ret) { + printk( "request_irq(%d, 0x%p, IRQF_TRIGGER_LOW) fail(%d)!!\n", + mmc2_sdio_ext_irq, mtk6620_sdio_ext_irq, ret); + mmc2_sdio_ext_irq = 0; + return; + } + + /*clear int status register before enable this int pin*/ + wmt_gpio_ack_irq(wmt_mtk6620_intr); + /*enable this int pin*/ + wmt_gpio_unmask_irq(wmt_mtk6620_intr); + +// enable_irq_wake(mmc2_sdio_ext_irq); + printk("%s: using sdio_ext_irq and enable_irq_wake(%d) gpio(%d) IRQ_EINT(%d)\n", + mmc_hostname(mmc), mmc2_sdio_ext_irq, wmt_mtk6620_intr, + (IRQ_GPIO)); + return; +} +#endif /* MMC2_SDIO_EXT_IRQ */ + + + + +/********************************************************************** +Name : atsmb2_enable_sdio_irq +Function : +Calls : +Called by : +Parameter : +returns : +Author : Tommy Huang +History : +***********************************************************************/ +static void atsmb2_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + struct atsmb_host *host = mmc_priv(mmc); + unsigned long flags; + + DBG("[%s] s enable = %d *ATSMB2_INT_MASK_1 = %x\n",__func__,enable,*ATSMB2_INT_MASK_1); + spin_lock_irqsave(&host->lock, flags); + + +#if MMC2_SDIO_EXT_IRQ + if (is_mtk6620) { + if (enable&&enable_wifi_irq) { + if (likely(0 != mmc2_sdio_ext_irq)) { + //enable_irq(mmc2_sdio_ext_irq); + wmt_gpio_unmask_irq(wmt_mtk6620_intr); + } + else { + mtk6620_setup_sdio_ext_irq(mmc); + } + } + else { + smp_rmb(); + if (unlikely(test_and_clear_bit(IRQ_IN_SUSPEND, &mmc2_sdio_ext_irq_flags))) { + smp_wmb(); + printk("%s: irq already disabled when being deferred(%d)\n", + mmc_hostname(host->mmc), mmc2_sdio_ext_irq); + /* do nothing */ + } + else { + //disable_irq_nosync(mmc2_sdio_ext_irq); + wmt_gpio_mask_irq(wmt_mtk6620_intr); + } + } + goto out; + } +#endif /* MMC2_SDIO_EXT_IRQ */ + + + + if (enable) { + *ATSMB2_INT_MASK_1 |= ATSMB_SDIO_EN; + } else { + *ATSMB2_INT_MASK_1 &= ~ATSMB_SDIO_EN; + } +out: + spin_unlock_irqrestore(&host->lock, flags); + DBG("[%s] e\n",__func__); + +} +void wmt_detect_sdio2(void){ + + if(mmc2_host_attr!=NULL){ + + struct atsmb_host *host = mmc_priv(mmc2_host_attr); + + mmc_detect_change(host->mmc, HZ/2); + } +} + + +void force_remove_sdio2(void) +{ + if(mmc2_host_attr!=NULL) + mmc_force_remove_card(mmc2_host_attr); +} + +void rda_mci_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + enable_wifi_irq = (unsigned int )enable; + atsmb2_enable_sdio_irq(mmc, enable); +} + +EXPORT_SYMBOL(rda_mci_enable_sdio_irq); + +EXPORT_SYMBOL(wmt_detect_sdio2); +EXPORT_SYMBOL(force_remove_sdio2); +/********************************************************************** +Name : atsmb2_request +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static void atsmb2_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + + struct atsmb_host *host = mmc_priv(mmc); + DBG("[%s] s\n",__func__); + + /* May retry process comes here.*/ + host->mrq = mrq; + host->data = mrq->data; + host->cmd = mrq->cmd; + host->done_data = NULL; + host->done = NULL; + + /*for data request*/ + if (host->data) { + if (host->cmd->opcode == MMC_WRITE_BLOCK + || host->cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK + || host->cmd->opcode == MMC_READ_SINGLE_BLOCK + || host->cmd->opcode == MMC_READ_MULTIPLE_BLOCK + || host->cmd->opcode == SD_IO_RW_EXTENDED) { + atsmb2_start_data(host); + } else { + atsmb2_cmd_with_data_back(host); + } + } else { + atsmb2_start_cmd(host); + } + DBG("[%s] e\n",__func__); +} +/********************************************************************** +Name : atsmb2_set_clock +Function :. +Calls : +Called by : +Parameter : +Author : Eason Chien +History :2012/7/19 +***********************************************************************/ +static int atsmb2_set_clock(struct mmc_host *mmc, unsigned int clock) +{ + if (clock == mmc->f_min) { + DBG("[%s]ATSMB2 Host 400KHz\n", __func__); + return auto_pll_divisor(DEV_SDMMC2, SET_DIV, 1, 390); + } else if (clock >= 50000000) { + DBG("[%s]ATSMB2 Host 50MHz\n", __func__); + return auto_pll_divisor(DEV_SDMMC2, SET_DIV, 2, 45); + } else if ((clock >= 25000000) && (clock < 50000000)) { + DBG("[%s]ATSMB2 Host 25MHz\n", __func__); + return auto_pll_divisor(DEV_SDMMC2, SET_DIV, 2, 24); + } else if ((clock >= 20000000) && (clock < 25000000)) { + DBG("[%s]ATSMB2 Host 20MHz\n", __func__); + return auto_pll_divisor(DEV_SDMMC2, SET_DIV, 2, 20); + } else { + DBG("[%s]ATSMB2 Host 390KHz\n", __func__); + return auto_pll_divisor(DEV_SDMMC2, SET_DIV, 1, 390); + } +} + +/********************************************************************** +Name : atsmb2_set_ios +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static void atsmb2_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + + struct atsmb_host *host = mmc_priv(mmc); + unsigned long flags; + DBG("[%s] s\n",__func__); + spin_lock_irqsave(&host->lock, flags); +#if MMC2_SDIO_EXT_IRQ + if(is_mtk6620){ + if ((ios->power_mode == MMC_POWER_OFF)) { + if (0 != mmc2_sdio_ext_irq) { + printk("%s: set mmc2 MMC_POWER_OFF disable_irq_wake & free_irq(%d)!\n", __func__, mmc2_sdio_ext_irq); + //disable_irq_wake(mmc2_sdio_ext_irq); + wmt_gpio_mask_irq(wmt_mtk6620_intr); + free_irq(IRQ_GPIO, host->mmc); + mmc2_sdio_ext_irq = 0; + } + } + } +#endif /* MMC2_SDIO_EXT_IRQ */ + + + if (ios->power_mode == MMC_POWER_OFF) { + if (MMC2_DRIVER_VERSION == MMC_DRV_3498) { + /* stop SD output clock */ + *ATSMB2_BUS_MODE &= ~(ATSMB_CST); + + /* disable SD Card power */ + /*set SD2 power pin as GPO pin*/ + if (SD2_function == SDIO_WIFI) + ;//GPIO_OC_GP0_BYTE_VAL |= BIT6; + else { + GPIO_CTRL_GP19_SD2_BYTE_VAL |= GPIO_SD2_POWER; + GPIO_OC_GP19_SD2_BYTE_VAL|= GPIO_SD2_POWER; + } + + /*set internal pull up*/ + PULL_CTRL_GP19_SD2_BYTE_VAL |= GPIO_SD2_POWER; + /*set internal pull enable*/ + PULL_EN_GP19_SD2_BYTE_VAL |= GPIO_SD2_POWER; + /*disable SD2 power*/ + if (SD2_function == SDIO_WIFI) + GPIO_OD_GP0_BYTE_VAL &= ~BIT6; + else + GPIO_OD_GP19_SD2_BYTE_VAL |= GPIO_SD2_POWER; + + /* Disable Pull up/down resister of SD Bus */ + PULL_CTRL_GP19_SD2_BYTE_VAL &= ~SD2_PIN; + + /* Config SD2 to GPIO */ + GPIO_CTRL_GP19_SD2_BYTE_VAL |= SD2_PIN; + + /* SD2 all pins output low */ + GPIO_OD_GP19_SD2_BYTE_VAL &= ~SD2_PIN; + + /* Config SD2 to GPIO */ + GPIO_OC_GP19_SD2_BYTE_VAL |= SD2_PIN; + } + } else if (ios->power_mode == MMC_POWER_UP) { + if (MMC2_DRIVER_VERSION == MMC_DRV_3498) { + /* disable SD Card power */ + /*set SD2 power pin as GPO pin*/ + if (SD2_function == SDIO_WIFI) + ;//GPIO_OC_GP0_BYTE_VAL |= BIT6; + else { + GPIO_CTRL_GP19_SD2_BYTE_VAL |= GPIO_SD2_POWER; + GPIO_OC_GP19_SD2_BYTE_VAL |= GPIO_SD2_POWER; + } + + /*set internal pull up*/ + PULL_CTRL_GP19_SD2_BYTE_VAL |= GPIO_SD2_POWER; + /*set internal pull enable*/ + PULL_EN_GP19_SD2_BYTE_VAL |= GPIO_SD2_POWER; + /*disable SD2 power*/ + if (SD2_function == SDIO_WIFI) + GPIO_OD_GP0_BYTE_VAL &= ~BIT6; + else + GPIO_OD_GP19_SD2_BYTE_VAL |= GPIO_SD2_POWER; + + /* Config SD PIN share */ + //kevin delete for dvfs + //PIN_SHARING_SEL_4BYTE_VAL |= GPIO_SD2_PinShare; + + /* do not config GPIO_SD2_CD because ISR has already run, + * config card detect will issue ISR storm. + */ + /* Config SD to GPIO */ + GPIO_CTRL_GP19_SD2_BYTE_VAL |= SD2_PIN; + + /* SD all pins output low */ + GPIO_OD_GP19_SD2_BYTE_VAL &= ~SD2_PIN; + + /* Config SD to GPO */ + GPIO_OC_GP19_SD2_BYTE_VAL |= SD2_PIN; + + /* Pull up/down resister of SD Bus */ + /*Disable Clock & CMD Pull enable*/ + PULL_EN_GP19_SD2_BYTE_VAL &= ~(GPIO_SD2_Clock ); + + /*Set CD ,WP ,DATA pin pull up*/ + if (SD2_function == SDIO_WIFI) { + PULL_CTRL_GP23_I2C3_BYTE_VAL &= ~GPIO_SD2_CD; /*pull down card always in slot*/ + PULL_CTRL_GP19_SD2_BYTE_VAL |= (GPIO_SD2_Data | GPIO_SD2_WriteProtect|GPIO_SD2_Command); + } else { + PULL_CTRL_GP23_I2C3_BYTE_VAL |= GPIO_SD2_CD; + PULL_CTRL_GP19_SD2_BYTE_VAL |= (GPIO_SD2_Data | GPIO_SD2_WriteProtect|GPIO_SD2_Command); + } + + /*Enable CD ,WP ,DATA internal pull*/ + PULL_EN_GP23_I2C3_BYTE_VAL |= GPIO_SD2_CD; + PULL_EN_GP19_SD2_BYTE_VAL |= (GPIO_SD2_Data | GPIO_SD2_WriteProtect|GPIO_SD2_Command); + + spin_unlock_irqrestore(&host->lock, flags); + msleep(100); + spin_lock_irqsave(&host->lock, flags); + + /* enable SD Card Power */ + if (SD2_function == SDIO_WIFI) + GPIO_OD_GP0_BYTE_VAL |= BIT6; + else + GPIO_OD_GP19_SD2_BYTE_VAL &= ~GPIO_SD2_POWER; + + /* enable SD output clock */ + *ATSMB2_BUS_MODE |= ATSMB_CST; + + spin_unlock_irqrestore(&host->lock, flags); + msleep(10); + spin_lock_irqsave(&host->lock, flags); + + /* Config SD0 back to function */ + GPIO_CTRL_GP23_I2C3_BYTE_VAL &= ~GPIO_SD2_CD; + GPIO_CTRL_GP19_SD2_BYTE_VAL &= ~SD2_PIN; + } + } else { + /*nothing to do when powering on.*/ + } + + host->current_clock = atsmb2_set_clock(mmc,ios->clock); + + if (ios->bus_width == MMC_BUS_WIDTH_8) { + *ATSMB2_EXT_CTL |= (0x04); + } else if (ios->bus_width == MMC_BUS_WIDTH_4) { + *ATSMB2_BUS_MODE |= ATSMB_BUS_WIDTH_4; + *ATSMB2_EXT_CTL &= ~(0x04); + } else { + *ATSMB2_BUS_MODE &= ~(ATSMB_BUS_WIDTH_4); + *ATSMB2_EXT_CTL &= ~(0x04); + } +#if 1 + if (ios->timing == MMC_TIMING_SD_HS) + *ATSMB2_EXT_CTL |= 0x80; /*HIGH SPEED MODE*/ + else + *ATSMB2_EXT_CTL &= ~(0x80); +#endif +#if 0 //zhf: marked by James Tian + if (ios->ins_en == MMC_INSERT_IRQ_EN) + *ATSMB2_INT_MASK_0 |= (0x80);/* or 40?*/ + else + *ATSMB2_INT_MASK_0 &= (~0x80);/* or 40?*/ +#endif + + spin_unlock_irqrestore(&host->lock, flags); + DBG("[%s] e\n",__func__); +} + + +static const struct mmc_host_ops atsmb2_ops = { + .request = atsmb2_request, + .set_ios = atsmb2_set_ios, + .get_ro = atsmb2_get_ro, + .get_slot_status = atsmb2_get_slot_status, + .dump_host_regs = atsmb2_dump_host_regs, + .enable_sdio_irq = atsmb2_enable_sdio_irq, +}; + +extern int wmt_getsyspara(char *varname, unsigned char *varval, int *varlen); + +/********************************************************************** +Name :atsmb2_probe +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static int __init atsmb2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mmc_host *mmc_host = NULL; + struct atsmb_host *atsmb_host = NULL; + struct resource *resource = NULL; + int irq[2] = {0}; + int ret = 0; + + DBG("[%s] s\n",__func__); + if (!pdev) { + ret = -EINVAL; /* Invalid argument */ + goto the_end; + } + + /*Enable SD host clock*/ + auto_pll_divisor(DEV_SDMMC2, CLK_ENABLE, 0, 0); + + if (MMC2_DRIVER_VERSION == MMC_DRV_3498) { + /* enable SD1 PIN share */ + //kevin delete for dvfs + //PIN_SHARING_SEL_4BYTE_VAL |= GPIO_SD2_PinShare; + + /* Pull up/down resister of SD CD */ + if (SD2_function == SDIO_WIFI) { + PULL_CTRL_GP23_I2C3_BYTE_VAL &= ~GPIO_SD2_CD; /*pull down CD*/ + } else { + PULL_CTRL_GP23_I2C3_BYTE_VAL |= GPIO_SD2_CD; /*pull up CD*/ + } + PULL_EN_GP23_I2C3_BYTE_VAL |= GPIO_SD2_CD; + + /* config CardDetect pin to SD function */ + GPIO_CTRL_GP23_I2C3_BYTE_VAL &= ~GPIO_SD2_CD; + } + + + resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!resource) { + ret = -ENXIO; /* No such device or address */ + printk(KERN_ALERT "[MMC/SD driver] Getting platform resources failed!\n"); + goto the_end; + } +#if 0 + if (!request_mem_region(resource->start, SZ_1K, MMC2_DRIVER_NAME)) { + ret = -EBUSY; + printk(KERN_ALERT "[MMC/SD driver] Request memory region failed!\n"); + goto the_end ; + } +#endif + irq[0] = platform_get_irq(pdev, 0); /*get IRQ for device*/; + irq[1] = platform_get_irq(pdev, 1); /*get IRQ for dma*/; + + if (irq[0] == NO_IRQ || irq[1] == NO_IRQ) { + ret = -ENXIO;/* No such device or address */ + printk(KERN_ALERT "[MMC/SD driver] Get platform IRQ failed!\n"); + goto rls_region; + } + + /*allocate a standard msp_host structure attached with a atsmb structure*/ + mmc_host = mmc_alloc_host(sizeof(struct atsmb_host), dev); + if (!mmc_host) { + ret = -ENOMEM; + printk(KERN_ALERT "[MMC/SD driver] Allocating driver's data failed!\n"); + goto rls_region; + } + mmc_host->wmt_host_index = 2; /*to identify host number*/ + + dev_set_drvdata(dev, (void *)mmc_host); /* mmc_host is driver data for the atsmb dev.*/ + atsmb_host = mmc_priv(mmc_host); + + mmc_host->ops = &atsmb2_ops; + + mmc_host->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34; + + mmc_host->f_min = 390425; /*390.425Hz = 400MHz/64/16*/ + mmc_host->f_max = 50000000; /* in fact, the max frequency is 400MHz( = 400MHz/1/1)*/ + + if(!is_nmc1000){ + mmc_host->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SDIO_IRQ| MMC_CAP_NONREMOVABLE; + }else{ + mmc_host->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SDIO_IRQ| MMC_CAP_NONREMOVABLE; + } + /* |MMC_CAP_8_BIT_DATA;*/ //zhf: marked by James Tian + mmc_host->pm_caps |= MMC_PM_KEEP_POWER; + mmc_host->pm_caps |= MMC_PM_WAKE_SDIO_IRQ;//add by kevinguan for mtk6620 + + mmc_host->max_segs = 128; /*we use software sg. so we could manage even larger number.*/ + + /*1MB per each request */ + /*we have a 16 bit block number register, and block length is 512 bytes.*/ + mmc_host->max_req_size = 16*512*(mmc_host->max_segs); + mmc_host->max_seg_size = 65024; /* 0x7F*512 PDMA one descriptor can transfer 64K-1 byte*/ + mmc_host->max_blk_size = 2048; /* our block length register is 11 bits.*/ + mmc_host->max_blk_count = (mmc_host->max_req_size)/512; + + /*set the specified host -- ATSMB*/ +#ifdef CONFIG_MMC_UNSAFE_RESUME + mmc_host->card_attath_status = card_attach_status_unchange; +#endif + sema_init(&mmc_host->req_sema,1); /*initial request semaphore*/ +#if 0 + atsmb_host->base = ioremap(resource->start, SZ_1K); + if (!atsmb_host->base) { + printk(KERN_ALERT "[MMC/SD driver] IO remap failed!\n"); + ret = -ENOMEM; + goto fr_host; + } +#endif + atsmb_host->base = (void *)resource->start; + atsmb_host->mmc = mmc_host; + spin_lock_init(&atsmb_host->lock); + atsmb_host->res = resource;/* for atsmb2_remove*/ + + /*disable all interrupt and clear status by resetting controller.*/ + *ATSMB2_BUS_MODE |= ATSMB_SFTRST; + *ATSMB2_BLK_LEN &= ~(0xa000); + *ATSMB2_SD_STS_0 |= 0xff; + *ATSMB2_SD_STS_1 |= 0xff; + + /* WM3437 A0 default not output clock, after SFTRST need to enable SD clock */ + //if (MMC2_DRIVER_VERSION >= MMC_DRV_3437_A0) /* including 3429 */ + *ATSMB2_BUS_MODE |= ATSMB_CST; + + atsmb_host->regular_irq = irq[0]; + atsmb_host->dma_irq = irq[1]; + + ret = request_irq(atsmb_host->regular_irq, + atsmb2_regular_isr, + IRQF_SHARED, //SA_SHIRQ, /*SA_INTERRUPT, * that is okay?*/ //zhf: modified by James Tian, should be IRQF_SHARED? + MMC2_DRIVER_NAME, + (void *)atsmb_host); + if (ret) { + printk(KERN_ALERT "[MMC/SD driver] Failed to register regular ISR!\n"); + goto unmap; + } + + ret = request_irq(atsmb_host->dma_irq, + atsmb2_dma_isr, + IRQF_DISABLED, // SA_INTERRUPT, //zhf: modified by James Tian + MMC2_DRIVER_NAME, + (void *)atsmb_host); + if (ret) { + printk(KERN_ALERT "[MMC/SD driver] Failed to register DMA ISR!\n"); + goto fr_regular_isr; + } + + + /*wait card detect status change*/ + //msleep(10); + + /*enable card insertion interrupt and enable DMA and its Global INT*/ + *ATSMB2_BLK_LEN |= (0xa000); /* also, we enable GPIO to detect card.*/ + *ATSMB2_SD_STS_0 |= 0xff; + *ATSMB2_INT_MASK_0 |= 0x80; /*or 0x40?*/ + + /*allocation dma descriptor*/ + ret = atsmb2_alloc_desc(atsmb_host, sizeof(struct SD_PDMA_DESC_S) * MAX_DESC_NUM); + if (ret == -1) { + printk(KERN_ALERT "[MMC/SD driver] Failed to allocate DMA descriptor!\n"); + goto fr_dma_isr; + } + printk(KERN_INFO "WMT ATSMB2 (AHB To SD/MMC2 Bus) controller registered!\n"); + + if (SD2_function == SDIO_WIFI){ + if(set_bus_dead){ + mmc_host->bus_dead = 1; //fix this problem: mmc1: error -110 during resume (card was removed?) + }else + printk("dont set bus dead in probe function\n"); + mmc_add_host(mmc_host,false); + }else + mmc_add_host(mmc_host,true); + + mmc2_host_attr = mmc_host; + DBG("[%s] e1\n",__func__); + return 0; + +fr_dma_isr: + free_irq(atsmb_host->dma_irq, atsmb_host); +fr_regular_isr: + free_irq(atsmb_host->regular_irq, atsmb_host); +unmap: + //iounmap(atsmb_host->base); +//fr_host: + dev_set_drvdata(dev, NULL); + mmc_free_host(mmc_host); +rls_region: + //release_mem_region(resource->start, SZ_1K); +the_end: + printk(KERN_ALERT "[MMC/SD driver] ATSMB2 probe Failed!\n") ; + DBG("[%s] e2\n",__func__); + return ret; +} +/********************************************************************** +Name : atsmb2_remove +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static int atsmb2_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mmc_host *mmc_host = (struct mmc_host *)dev_get_drvdata(dev); + struct atsmb_host *atsmb_host; + + DBG("[%s] s\n",__func__); + atsmb_host = mmc_priv(mmc_host); + if (!mmc_host || !atsmb_host) { + printk(KERN_ALERT "[MMC/SD driver] ATSMB2 remove method failed!\n"); + DBG("[%s] e1\n",__func__); + return -ENXIO; + } + mmc_remove_host(mmc_host); + + /*disable interrupt by resetting controller -- for safey*/ + *ATSMB2_BUS_MODE |= ATSMB_SFTRST; + *ATSMB2_BLK_LEN &= ~(0xa000); + *ATSMB2_SD_STS_0 |= 0xff; + *ATSMB2_SD_STS_1 |= 0xff; + + (void)free_irq(atsmb_host->regular_irq, atsmb_host); + (void)free_irq(atsmb_host->dma_irq, atsmb_host); + (void)iounmap(atsmb_host->base); + (void)release_mem_region(atsmb_host->res->start, SZ_1K); + dev_set_drvdata(dev, NULL); + /*free dma descriptor*/ + dma_free_coherent(atsmb_host->mmc->parent, atsmb_host->DescSize, + atsmb_host->DescVirAddr, atsmb_host->DescPhyAddr); + (void)mmc_free_host(mmc_host);/* also free atsmb_host.*/ + DBG("[%s] e2\n",__func__); + return 0; +} + +/********************************************************************** +Name : atsmb2_shutdown +Function :. +Calls : +Called by : +Parameter : +Author : Tommy Huang +History : +***********************************************************************/ +static void atsmb2_shutdown(struct platform_device *pdev) +{ + /*atsmb2_shutdown don't be used now.*/ + /*struct device *dev = &pdev->dev; + struct mmc_host *mmc_host = (struct mmc_host *)dev_get_drvdata(dev);*/ + + DBG("[%s] s\n",__func__); + if (SD2_function != SDIO_WIFI) { + /*Disable card detect interrupt*/ + *ATSMB2_INT_MASK_0 &= ~0x80; + } + DBG("[%s] e\n",__func__); + +} + +/********************************************************************** +Name : atsmb2_suspend +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +int SD2_card_state = 0; /*0: card remove >=1: card enable*/ + +#ifdef CONFIG_PM +static int atsmb2_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct device *dev = &pdev->dev; + struct mmc_host *mmc = (struct mmc_host *)dev_get_drvdata(dev); + int ret = 0; + DBG("[%s] s\n",__func__); + + if (mmc) { + + if (SD2_function == SDIO_WIFI) { + if (SD2_card_state > 0) + mmc_force_remove_card(mmc2_host_attr); + } +#if MMC2_SDIO_EXT_IRQ + if (is_mtk6620) { + if (0 != mmc2_sdio_ext_irq) { + //if (mmc_try_claim_host(host->mmc)) { + if(true){ + set_bit(IN_SUSPEND, &mmc2_sdio_ext_irq_flags); + printk(KERN_INFO "%s:in_suspend++\n", mmc_hostname(mmc)); + } + else { + printk(KERN_INFO "%s:claim host fail in suspend, return -EBUSY\n", mmc_hostname(mmc)); + return -EBUSY; + } + } + } +#endif /* MMC2_SDIO_EXT_IRQ */ + + /*struct atsmb_host *host = mmc_priv(mmc);*/ + ret = mmc_suspend_host(mmc); + +#if MMC2_SDIO_EXT_IRQ + if (is_mtk6620) { + if (ret) { + printk("\n\n\n\n\n\n%s:mmc_suspend_host fail(%d)\n\n\n\n\n\n", + mmc_hostname(mmc), ret); + if (0 != mmc2_sdio_ext_irq) { + clear_bit(IN_SUSPEND, &mmc2_sdio_ext_irq_flags); + //mmc_release_host(host->mmc); + + } + return ret; + } + + } +#endif + + + if (ret == 0) { + /*disable all interrupt and clear status by resetting controller. */ + *ATSMB2_BUS_MODE |= ATSMB_SFTRST; + *ATSMB2_BLK_LEN &= ~(0xa000); + *ATSMB2_SD_STS_0 |= 0xff; + *ATSMB2_SD_STS_1 |= 0xff; + + } + /*disable source clock*/ + auto_pll_divisor(DEV_SDMMC2, CLK_DISABLE, 0, 0); +#ifdef CONFIG_MMC_UNSAFE_RESUME + /*clean SD card attatch status change*/ + //PMCWS_VAL |= BIT19; + mmc->card_attath_status = card_attach_status_unchange; +#endif + } + + DBG("[%s] e\n",__func__); + return ret; +} +/********************************************************************** +Name : atsmb2_resume +Function :. +Calls : +Called by : +Parameter : +Author : Leo Lee +History : +***********************************************************************/ +static int atsmb2_resume(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mmc_host *mmc = (struct mmc_host *)dev_get_drvdata(dev); + int ret = 0; + DBG("[%s] s\n",__func__); + + /* + * enable interrupt, DMA, etc. + * Supply power to slot. + */ + if (mmc) { + /*enable source clock*/ + auto_pll_divisor(DEV_SDMMC2, CLK_ENABLE, 0, 0); + + udelay(1); + /*enable card insertion interrupt and enable DMA and its Global INT*/ + *ATSMB2_BUS_MODE |= ATSMB_SFTRST; + *ATSMB2_BLK_LEN |= (0xa000); + *ATSMB2_INT_MASK_0 |= 0x80; /* or 40?*/ +#ifdef CONFIG_MMC_UNSAFE_RESUME + /*modify SD card attatch status change*/ + //if ((PMCWS_VAL & BIT19) && !mmc->bus_dead) { + /*card change when suspend mode*/ + mmc->card_attath_status = card_attach_status_change; + /*clean SD card attatch status change*/ + // PMCWS_VAL |= BIT19; + //} +#endif + if (SD2_function == SDIO_WIFI) { + PULL_CTRL_GP23_I2C3_BYTE_VAL &= ~GPIO_SD2_CD; /*pull down CD*/ + PULL_EN_GP23_I2C3_BYTE_VAL |= GPIO_SD2_CD; + + //kevin add for suspend&resume + PULL_CTRL_GP19_SD2_BYTE_VAL |= GPIO_SD2_Data | GPIO_SD2_Command; + PULL_CTRL_GP19_SD2_BYTE_VAL &= ~GPIO_SD2_WriteProtect; + PULL_EN_GP19_SD2_BYTE_VAL |= (GPIO_SD2_Data | GPIO_SD2_WriteProtect|GPIO_SD2_Command); + PULL_EN_GP19_SD2_BYTE_VAL &= ~GPIO_SD2_Clock; + GPIO_CTRL_GP19_SD2_BYTE_VAL &= ~(GPIO_SD2_Data | GPIO_SD2_WriteProtect | GPIO_SD2_Command | GPIO_SD2_Clock); + *ATSMB2_BUS_MODE |= ATSMB_CST; + msleep(20); + + + if (SD2_card_state > 0) { + printk("[%s] mmc_resume_host s\n",__func__); + mmc_detect_change(mmc2_host_attr, 1*HZ/2); + printk("[%s] mmc_resume_host e\n",__func__); + } + + +#if MMC2_SDIO_EXT_IRQ + if (is_mtk6620) { + if (0 != mmc2_sdio_ext_irq) { + /* sdhc goes back to normal working mode, after mtk6620_init() */ + clear_bit(IN_SUSPEND, &mmc2_sdio_ext_irq_flags); + //mmc_release_host(host->mmc); + printk(KERN_INFO "%s:in_suspend--\n", mmc_hostname(mmc)); + } + } +#endif /* MMC2_SDIO_EXT_IRQ */ + + + + //kevin add for suspend&resume + ret = mmc_resume_host(mmc); + + +#if MMC2_SDIO_EXT_IRQ + /* after mmc_resume_host(), sdio_func is also resumed */ + if (is_mtk6620) { + if (0 != mmc2_sdio_ext_irq) { + /* signal deferred irq if any */ + smp_rmb(); + if (unlikely(test_bit(IRQ_IN_SUSPEND, &mmc2_sdio_ext_irq_flags))) { + printk(KERN_INFO "%s: signal deferred irq(%d)\n", + mmc_hostname(mmc), mmc2_sdio_ext_irq); + /* signal the deferred irq */ + mmc_signal_sdio_irq(mmc); + } + } + } +#endif + + } else { + ret = mmc_resume_host(mmc); + } + } + + DBG("[%s] e\n",__func__); + return ret; +} +#else +#define atsmb2_suspend NULL +#define atsmb2_resume NULL +#endif + +static struct platform_driver atsmb2_driver = { + .driver.name = "sdmmc2", + //.probe = atsmb2_probe, + .remove = atsmb2_remove, + .shutdown = atsmb2_shutdown, + .suspend = atsmb2_suspend, + .resume = atsmb2_resume, +}; + +static struct platform_device wmt_sdmmc2_device = { + .name = "sdmmc2", + .id = 0, + .dev = { + .dma_mask = &wmt_sdmmc2_dma_mask, + .coherent_dma_mask = ~0, + .release = atsmb2_release, + }, + .num_resources = ARRAY_SIZE(wmt_sdmmc2_resources), + .resource = wmt_sdmmc2_resources, +}; + +static DEFINE_MUTEX(SD2_state_lock); + +static ssize_t atsmb2_state_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + int ret = 0; + + DBG("[%s] s\n",__func__); + + ret = sprintf(buf, "%d\n", SD2_card_state); + DBG("[%s]SD2_card_state = %d\n",__func__,SD2_card_state); + DBG("[%s] e\n",__func__); + return ret; +} + +static ssize_t atsmb2_state_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t n) +{ + int val; + DBG("[%s] s\n",__func__); + + mutex_lock(&SD2_state_lock); + if (sscanf(buf, "%d", &val) == 1) { + DBG("[%s] val = %d\n",__func__,val); + if (val == 1) { + SD2_card_state++; + DBG("[%s] add state SD2_card_state = %d\n",__func__,SD2_card_state); + if ((SD2_card_state == 1) && (mmc2_host_attr->card == NULL)) { + DBG("[%s]add card\n",__func__); + mmc_detect_change(mmc2_host_attr, 0); + msleep(1000); + } else { + msleep(1000); /*to prevent the card not init ready*/ + } + } else if (val == 0) { + SD2_card_state--; + printk("[%s] sub state SD2_card_state = %d\n",__func__,SD2_card_state); + if (SD2_card_state < 0) { + SD2_card_state = 0; + printk("[%s] set 0 SD2_card_state = %d\n",__func__,SD2_card_state); + } + + if ((SD2_card_state == 0) && (mmc2_host_attr->card != NULL)) { + DBG("[%s]remove card\n",__func__); + mmc_force_remove_card(mmc2_host_attr); + } + } + + DBG("[%s] e1\n",__func__); + mutex_unlock(&SD2_state_lock); + return n; + } + + mutex_unlock(&SD2_state_lock); + DBG("[%s] e2\n",__func__); + return -EINVAL; +} + +static struct kobj_attribute atsmb2_state_attr = { \ + .attr = { \ + .name = __stringify(state), \ + .mode = 0755, \ + }, \ + .show = atsmb2_state_show, \ + .store = atsmb2_state_store, \ +}; + +#if MMC2_SDIO_EXT_IRQ + +static ssize_t atsmb2_intr_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + int ret = 0; + + + ret = sprintf(buf, "%d\n", is_mtk6620); + printk("[%s]is_mtk6620 = %d\n",__func__,is_mtk6620); + return ret; +} + +static ssize_t atsmb2_intr_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t n) +{ + int val; + if (sscanf(buf, "%d", &val) == 1) { + printk("[%s] val = %d\n",__func__,val); + if (val == 1) { + if (gpio_request(wmt_mtk6620_intr, "mtk6620_wifi_intr") < 0) { + printk("gpio(%d) mtk6620_wifi_intr gpio request fail\n", wmt_mtk6620_intr); + is_mtk6620 = 0; + return -1; + } + } else if (val == 0) { + + gpio_free(wmt_mtk6620_intr); + + } + is_mtk6620 = val; + + return n; + } + return -EINVAL; +} + +static struct kobj_attribute atsmb2_intr_attr = { \ + .attr = { \ + .name = __stringify(intr), \ + .mode = 0755, \ + }, \ + .show = atsmb2_intr_show, \ + .store = atsmb2_intr_store, \ +}; +#endif + + +static struct attribute * g2[] = { + &atsmb2_state_attr.attr, +#if MMC2_SDIO_EXT_IRQ + &atsmb2_intr_attr.attr, +#endif + NULL, +}; + +static struct attribute_group attr2_group = { + .attrs = g2, +}; + +static int __init atsmb2_init(void) +{ + int ret; + + int retval; + unsigned char buf[80]; + unsigned char buf1[80]; + int varlen = 80; + char *varname = "wmt.sd2.param"; + int temp = 0, sd_enable = 0; /*0 :disable 1:enable*/ + + DBG("[%s] s\n",__func__); + +#ifdef CONFIG_MTD_WMT_SF + /*Read system param to identify host function 0: SD/MMC 1:SDIO wifi*/ + retval = wmt_getsyspara(varname, buf, &varlen); + if (retval == 0) { + sscanf(buf,"%x:%d", &temp, &SD2_function); + printk(KERN_ALERT "wmt.sd2.param = %x:%d\n", temp, SD2_function); + sd_enable = temp & 0xf; + SDXC2_function = (temp >> 4) & 0xf; + printk(KERN_ALERT "SD2 ebable = %x, SDXC = %x, function = %x\n", + sd_enable, SDXC2_function, SD2_function); + + if (SD2_function < 0 || SD2_function >= SD_MAX_FUN) + return -ENODEV; + } else { + //default is enable sdio wifi + temp = sd_enable = 1; + SD2_function = 1; + printk(KERN_ALERT "Default wmt.sd2.param = %x:%d\n", temp, SD2_function); + + } + memset(buf,0,sizeof(buf)); + varlen = 80; + retval = wmt_getsyspara("wmt.init.rc", buf, &varlen); + if (retval == 0) { + printk(KERN_ALERT"wmt.init.rc:%s\n",buf); + if(!strcmp(buf,"init_nmc1000.rc")){ + is_nmc1000 = 0x01; + printk("is_nmc1000:%d",is_nmc1000); + } + if (!strcmp(buf, "init.bcm6330.rc")) + { + is_ap6330 = 1; + printk("is_ap6330:%d\n", is_ap6330); + } + if (!strcmp(buf, "init.rda5991.rc")) + { + printk("is rda5991\n"); + set_bus_dead = 0; + } + if (!strcmp(buf, "init.mtk6620.rc")) + { + printk("is mtk6620\n"); + set_bus_dead = 0; + } + } else { + printk(KERN_ALERT "wmt.init.rc do not exit\n"); + } +#endif + /*SD function disable*/ + if (sd_enable != 1) + return -ENODEV; + + get_driver_version2(); + + if (platform_device_register(&wmt_sdmmc2_device))//add by jay,for modules support + return -1; + //ret = platform_driver_register(&atsmb2_driver); + ret = platform_driver_probe(&atsmb2_driver, atsmb2_probe); + + atsmb2_kobj = kobject_create_and_add("mmc2", NULL); + if (!atsmb2_kobj) + return -ENOMEM; + return sysfs_create_group(atsmb2_kobj, &attr2_group); + + DBG("[%s] e\n",__func__); + return ret; +} + +static void __exit atsmb2_exit(void) +{ + DBG("[%s] s\n",__func__); + (void)platform_driver_unregister(&atsmb2_driver); + (void)platform_device_unregister(&wmt_sdmmc2_device);//add by jay,for modules support + DBG("[%s] e\n",__func__); +} + +module_init(atsmb2_init); +module_exit(atsmb2_exit); +module_param(fmax2, uint, 0444); + +MODULE_AUTHOR("WonderMedia Technologies, Inc."); +MODULE_DESCRIPTION("WMT [AHB to SD/MMC2 Bridge] driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mmc/host/mmc_spi.c b/drivers/mmc/host/mmc_spi.c new file mode 100644 index 00000000..273306c6 --- /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/module.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", + .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..032b8479 --- /dev/null +++ b/drivers/mmc/host/mmci.c @@ -0,0 +1,1606 @@ +/* + * 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 <linux/pm_runtime.h> +#include <linux/types.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 + * @pwrreg_powerup: power up value for MMCIPOWER register + * @signal_direction: input/out direction of bus signals can be indicated + */ +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; + u32 pwrreg_powerup; + bool signal_direction; +}; + +static struct variant_data variant_arm = { + .fifosize = 16 * 4, + .fifohalfsize = 8 * 4, + .datalength_bits = 16, + .pwrreg_powerup = MCI_PWR_UP, +}; + +static struct variant_data variant_arm_extended_fifo = { + .fifosize = 128 * 4, + .fifohalfsize = 64 * 4, + .datalength_bits = 16, + .pwrreg_powerup = MCI_PWR_UP, +}; + +static struct variant_data variant_u300 = { + .fifosize = 16 * 4, + .fifohalfsize = 8 * 4, + .clkreg_enable = MCI_ST_U300_HWFCEN, + .datalength_bits = 16, + .sdio = true, + .pwrreg_powerup = MCI_PWR_ON, + .signal_direction = 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, + .pwrreg_powerup = MCI_PWR_ON, + .signal_direction = 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, + .pwrreg_powerup = MCI_PWR_ON, + .signal_direction = true, +}; + +/* + * This must be called with host->lock held + */ +static void mmci_write_clkreg(struct mmci_host *host, u32 clk) +{ + if (host->clk_reg != clk) { + host->clk_reg = clk; + writel(clk, host->base + MMCICLOCK); + } +} + +/* + * This must be called with host->lock held + */ +static void mmci_write_pwrreg(struct mmci_host *host, u32 pwr) +{ + if (host->pwr_reg != pwr) { + host->pwr_reg = pwr; + writel(pwr, host->base + MMCIPOWER); + } +} + +/* + * 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; + + mmci_write_clkreg(host, clk); +} + +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; + + mmc_request_done(host->mmc, mrq); + + pm_runtime_mark_last_busy(mmc_dev(host->mmc)); + pm_runtime_put_autosuspend(mmc_dev(host->mmc)); +} + +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 */ + .device_fc = false, + }; + struct dma_chan *chan; + struct dma_device *device; + struct dma_async_tx_descriptor *desc; + enum dma_data_direction buffer_dirn; + 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_DEV_TO_MEM; + buffer_dirn = DMA_FROM_DEVICE; + chan = host->dma_rx_channel; + } else { + conf.direction = DMA_MEM_TO_DEV; + buffer_dirn = 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, buffer_dirn); + if (nr_sg == 0) + return -EINVAL; + + dmaengine_slave_config(chan, &conf); + desc = dmaengine_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, buffer_dirn); + 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) { + pr_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 (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; + + /* 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; + + /* + * 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; + } + + 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; + + /* + * SDIO especially may want to send something that is + * not divisible by 4 (as opposed to card sectors + * etc). Therefore make sure to always read the last bytes + * while only doing full 32-bit reads towards the FIFO. + */ + if (unlikely(count & 0x3)) { + if (count < 4) { + unsigned char buf[4]; + readsl(base + MMCIFIFO, buf, 1); + memcpy(ptr, buf, count); + } else { + readsl(base + MMCIFIFO, ptr, count >> 2); + count &= ~0x3; + } + } else { + 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)) { + u32 clk; + if (count < 8) + clk = host->clk_reg & ~variant->clkreg_enable; + else + clk = host->clk_reg | variant->clkreg_enable; + + mmci_write_clkreg(host, clk); + } + + /* + * 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; + } + + pm_runtime_get_sync(mmc_dev(mmc)); + + 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); + struct variant_data *variant = host->variant; + u32 pwr = 0; + unsigned long flags; + int ret; + + pm_runtime_get_sync(mmc_dev(mmc)); + + if (host->plat->ios_handler && + host->plat->ios_handler(mmc_dev(mmc), ios)) + dev_err(mmc_dev(mmc), "platform ios_handler failed\n"); + + 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. + */ + goto out; + } + } + /* + * The ST Micro variant doesn't have the PL180s MCI_PWR_UP + * and instead uses MCI_PWR_ON so apply whatever value is + * configured in the variant data. + */ + pwr |= variant->pwrreg_powerup; + + break; + case MMC_POWER_ON: + pwr |= MCI_PWR_ON; + break; + } + + if (variant->signal_direction && ios->power_mode != MMC_POWER_OFF) { + /* + * The ST Micro variant has some additional bits + * indicating signal direction for the signals in + * the SD/MMC bus and feedback-clock usage. + */ + pwr |= host->plat->sigdir; + + if (ios->bus_width == MMC_BUS_WIDTH_4) + pwr &= ~MCI_ST_DATA74DIREN; + else if (ios->bus_width == MMC_BUS_WIDTH_1) + pwr &= (~MCI_ST_DATA74DIREN & + ~MCI_ST_DATA31DIREN & + ~MCI_ST_DATA2DIREN); + } + + 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); + mmci_write_pwrreg(host, pwr); + + spin_unlock_irqrestore(&host->lock, flags); + + out: + pm_runtime_mark_last_busy(mmc_dev(mmc)); + pm_runtime_put_autosuspend(mmc_dev(mmc)); +} + +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_prepare(host->clk); + if (ret) + goto clk_free; + + ret = clk_enable(host->clk); + if (ret) + goto clk_unprep; + + 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; + /* + * The ARM and ST versions of the block have slightly different + * clock divider equations which means that the minimum divider + * differs too. + */ + if (variant->st_clkdiv) + mmc->f_min = DIV_ROUND_UP(host->mclk, 257); + else + mmc->f_min = DIV_ROUND_UP(host->mclk, 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; + mmc->caps2 = plat->capabilities2; + + /* + * 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 = 1 << 11; + + /* + * Limit the number of blocks transferred so that we don't overflow + * the maximum request size. + */ + mmc->max_blk_count = mmc->max_req_size >> 11; + + 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 || !dev->irq[1]) + 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); + + pm_runtime_set_autosuspend_delay(&dev->dev, 50); + pm_runtime_use_autosuspend(&dev->dev); + pm_runtime_put(&dev->dev); + + 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_unprep: + clk_unprepare(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); + + /* + * Undo pm_runtime_put() in probe. We use the _sync + * version here so that we can access the primecell. + */ + pm_runtime_get_sync(&dev->dev); + + 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_unprepare(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_SUSPEND +static int mmci_suspend(struct device *dev) +{ + struct amba_device *adev = to_amba_device(dev); + struct mmc_host *mmc = amba_get_drvdata(adev); + int ret = 0; + + if (mmc) { + struct mmci_host *host = mmc_priv(mmc); + + ret = mmc_suspend_host(mmc); + if (ret == 0) { + pm_runtime_get_sync(dev); + writel(0, host->base + MMCIMASK0); + } + } + + return ret; +} + +static int mmci_resume(struct device *dev) +{ + struct amba_device *adev = to_amba_device(dev); + struct mmc_host *mmc = amba_get_drvdata(adev); + int ret = 0; + + if (mmc) { + struct mmci_host *host = mmc_priv(mmc); + + writel(MCI_IRQENABLE, host->base + MMCIMASK0); + pm_runtime_put(dev); + + ret = mmc_resume_host(mmc); + } + + return ret; +} +#endif + +static const struct dev_pm_ops mmci_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(mmci_suspend, mmci_resume) +}; + +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 }, +}; + +MODULE_DEVICE_TABLE(amba, mmci_ids); + +static struct amba_driver mmci_driver = { + .drv = { + .name = DRIVER_NAME, + .pm = &mmci_dev_pm_ops, + }, + .probe = mmci_probe, + .remove = __devexit_p(mmci_remove), + .id_table = mmci_ids, +}; + +module_amba_driver(mmci_driver); + +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..d437ccf6 --- /dev/null +++ b/drivers/mmc/host/mmci.h @@ -0,0 +1,211 @@ +/* + * 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) + +#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 128 + +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_reg; + u32 clk_reg; + 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..1d14cda9 --- /dev/null +++ b/drivers/mmc/host/msm_sdcc.c @@ -0,0 +1,1486 @@ +/* + * 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, (msmsdcc_readl(host, MMCIMASK0) & + (~MCI_IRQ_PIO)) | host->cmd_pio_irqmask, MMCIMASK0); + 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) { + pr_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) { + pr_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, (msmsdcc_readl(host, MMCIMASK0) & + (~MCI_IRQ_PIO)) | pio_irqmask, MMCIMASK0); + + 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; + u32 mci_mask0; + + status = msmsdcc_readl(host, MMCISTATUS); + mci_mask0 = msmsdcc_readl(host, MMCIMASK0); + + if (((mci_mask0 & status) & MCI_IRQ_PIO) == 0) + return IRQ_NONE; + + 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)) + + 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); + 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_mask0 & (~MCI_IRQ_PIO)) | + MCI_RXDATAAVLBLMASK, MMCIMASK0); + + if (!host->curr.xfer_remain) + msmsdcc_writel(host, (mci_mask0 & (~MCI_IRQ_PIO)) | 0, + MMCIMASK0); + + 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); + if ((status & (~MCI_IRQ_PIO)) == 0) + break; + 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 void msmsdcc_init_card(struct mmc_host *mmc, struct mmc_card *card) +{ + struct msmsdcc_host *host = mmc_priv(mmc); + + if (host->plat->init_card) + host->plat->init_card(card); +} + +static const struct mmc_host_ops msmsdcc_ops = { + .request = msmsdcc_request, + .set_ios = msmsdcc_set_ios, + .enable_sdio_irq = msmsdcc_enable_sdio_irq, + .init_card = msmsdcc_init_card, +}; + +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; + + pr_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; + + pr_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 *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"); + stat_irqres = platform_get_resource_byname(pdev, IORESOURCE_IRQ, + "status_irq"); + + if (!cmd_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; + init_timer(&host->busclk_timer); + host->busclk_timer.data = (unsigned long) host; + host->busclk_timer.function = msmsdcc_busclk_expired; + + + host->cmdpoll = 1; + + host->base = ioremap(memres->start, PAGE_SIZE); + if (!host->base) { + ret = -ENOMEM; + goto host_free; + } + + host->cmd_irqres = cmd_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 + */ + if (host->dmares) { + ret = msmsdcc_init_dma(host); + if (ret) + goto ioremap_free; + } else { + host->dma.channel = -1; + } + + /* Get our clocks */ + host->pclk = clk_get(&pdev->dev, "sdc_pclk"); + if (IS_ERR(host->pclk)) { + ret = PTR_ERR(host->pclk); + goto dma_free; + } + + host->clk = clk_get(&pdev->dev, "sdc_clk"); + if (IS_ERR(host->clk)) { + ret = PTR_ERR(host->clk); + goto pclk_put; + } + + ret = clk_set_rate(host->clk, msmsdcc_fmin); + if (ret) { + pr_err("%s: Clock rate set failed (%d)\n", __func__, ret); + goto clk_put; + } + + /* Enable clocks */ + ret = msmsdcc_enable_clocks(host); + if (ret) + goto clk_put; + + 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; + } + + ret = request_irq(cmd_irqres->start, msmsdcc_irq, IRQF_SHARED, + DRIVER_NAME " (cmd)", host); + if (ret) + goto stat_irq_free; + + ret = request_irq(cmd_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); +dma_free: + if (host->dmares) + dma_free_coherent(NULL, sizeof(struct msmsdcc_nc_dmadata), + host->dma.nc, host->dma.nc_busaddr); +ioremap_free: + tasklet_kill(&host->dma_tlet); + iounmap(host->base); + 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", + }, +}; + +module_platform_driver(msmsdcc_driver); + +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..402028d1 --- /dev/null +++ b/drivers/mmc/host/msm_sdcc.h @@ -0,0 +1,256 @@ +/* + * 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) + +#define MCI_IRQ_PIO \ + (MCI_RXDATAAVLBLMASK | MCI_TXDATAAVLBLMASK | MCI_RXFIFOEMPTYMASK | \ + MCI_TXFIFOEMPTYMASK | MCI_RXFIFOFULLMASK | MCI_TXFIFOFULLMASK | \ + MCI_RXFIFOHALFFULLMASK | MCI_TXFIFOHALFEMPTYMASK | \ + MCI_RXACTIVEMASK | MCI_TXACTIVEMASK) +/* + * 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 *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..eeb8cd12 --- /dev/null +++ b/drivers/mmc/host/mvsdio.c @@ -0,0 +1,921 @@ +/* + * 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) + pr_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) { + pr_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; + + pr_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) { + pr_err("%s: Timeout waiting for hardware interrupt.\n", + mmc_hostname(host->mmc)); + pr_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, + const 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++) { + const 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; + const struct mbus_dram_target_info *dram; + 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. */ + dram = mv_mbus_dram_info(); + if (dram) + mv_conf_mbus_windows(host, dram); + + mvsd_power_down(host); + + ret = request_irq(irq, mvsd_irq, 0, DRIVER_NAME, host); + if (ret) { + pr_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; + + pr_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..b2058b43 --- /dev/null +++ b/drivers/mmc/host/mxcmmc.c @@ -0,0 +1,1062 @@ +/* + * 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 <linux/types.h> + +#include <asm/dma.h> +#include <asm/irq.h> +#include <asm/sizes.h> +#include <mach/mmc.h> + +#include <mach/dma.h> +#include <mach/hardware.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; + enum dma_transfer_direction slave_dirn; + 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; + slave_dirn = DMA_DEV_TO_MEM; + } else { + host->dma_dir = DMA_TO_DEVICE; + slave_dirn = DMA_MEM_TO_DEV; + } + + 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 = dmaengine_prep_slave_sg(host->dma, + data->sg, data->sg_len, slave_dirn, + 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); + dma_async_issue_pending(host->dma); + + 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; + config->device_fc = false; + + 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 (16 words) in 4 bit mode (--> reg value 0) + * use burstlen of 16 (4 words) in 1 bit mode (--> reg value 16) + */ + if (ios->bus_width == MMC_BUS_WIDTH_4) + burstlen = 16; + else + burstlen = 4; + + 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; + + pr_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 + } +}; + +module_platform_driver(mxcmci_driver); + +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..e3f5af96 --- /dev/null +++ b/drivers/mmc/host/mxs-mmc.c @@ -0,0 +1,873 @@ +/* + * 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 <linux/module.h> +#include <linux/fsl/mxs-dma.h> + +#include <mach/mxs.h> +#include <mach/common.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; + enum dma_transfer_direction slave_dirn; + 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 long flags) +{ + 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 = dmaengine_prep_slave_sg(host->dmach, + sgl, sg_len, host->slave_dirn, flags); + 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; + host->slave_dirn = DMA_TRANS_NONE; + desc = mxs_mmc_prep_dma(host, DMA_CTRL_ACK); + if (!desc) + goto out; + + dmaengine_submit(desc); + dma_async_issue_pending(host->dmach); + 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; + host->slave_dirn = DMA_TRANS_NONE; + desc = mxs_mmc_prep_dma(host, DMA_CTRL_ACK); + if (!desc) + goto out; + + dmaengine_submit(desc); + dma_async_issue_pending(host->dmach); + 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; + enum dma_transfer_direction slave_dirn; + 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; + slave_dirn = DMA_MEM_TO_DEV; + read = 0; + } else { + dma_data_dir = DMA_FROM_DEVICE; + slave_dirn = DMA_DEV_TO_MEM; + 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; + host->slave_dirn = DMA_TRANS_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; + host->slave_dirn = slave_dirn; + desc = mxs_mmc_prep_dma(host, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) + goto out; + + dmaengine_submit(desc); + dma_async_issue_pending(host->dmach); + 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_prepare_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_unprepare(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_unprepare(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_unprepare(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_prepare_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 + }, +}; + +module_platform_driver(mxs_mmc_driver); + +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..1534b582 --- /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(be32_to_cpu(voltage_ranges[j]), + be32_to_cpu(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..887c0e59 --- /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 <asm/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..71a0c4ea --- /dev/null +++ b/drivers/mmc/host/omap_hsmmc.c @@ -0,0 +1,2209 @@ +/* + * 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/kernel.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/timer.h> +#include <linux/clk.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/of_device.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 <linux/pm_runtime.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) + +#define MMC_AUTOSUSPEND_DELAY 100 +#define MMC_TIMEOUT_MS 20 +#define OMAP_MMC_MIN_CLOCK 400000 +#define OMAP_MMC_MAX_CLOCK 52000000 +#define DRIVER_NAME "omap_hsmmc" + +/* + * 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 *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; + void __iomem *base; + resource_size_t mapbase; + spinlock_t irq_lock; /* Prevent races with irq handler */ + 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 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_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; + /* + * With DT, never turn OFF the regulator. This is because + * the pbias cell programming support is still missing when + * booting with Device tree + */ + if (dev->of_node && !vdd) + 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_reg_get(struct omap_hsmmc_host *host) +{ + struct regulator *reg; + int ocr_value = 0; + + mmc_slot(host).set_power = omap_hsmmc_set_power; + + reg = regulator_get(host->dev, "vmmc"); + if (IS_ERR(reg)) { + dev_dbg(host->dev, "vmmc regulator missing\n"); + } 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)) { + dev_err(host->dev, "ocrmask %x is not supported\n", + 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 || + (host->vcc_aux && regulator_is_enabled(host->vcc_aux))) { + int vdd = ffs(mmc_slot(host).ocr_mask) - 1; + + mmc_slot(host).set_power(host->dev, host->slot_id, + 1, vdd); + mmc_slot(host).set_power(host->dev, host->slot_id, + 0, 0); + } + } + + return 0; +} + +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; +} + +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); +} + +/* + * Start clock to the card + */ +static void omap_hsmmc_start_clock(struct omap_hsmmc_host *host) +{ + OMAP_HSMMC_WRITE(host->base, SYSCTL, + OMAP_HSMMC_READ(host->base, SYSCTL) | CEN); +} + +/* + * 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); +} + +/* Calculate divisor for the given clock frequency */ +static u16 calc_divisor(struct omap_hsmmc_host *host, struct mmc_ios *ios) +{ + u16 dsor = 0; + + if (ios->clock) { + dsor = DIV_ROUND_UP(clk_get_rate(host->fclk), ios->clock); + if (dsor > 250) + dsor = 250; + } + + return dsor; +} + +static void omap_hsmmc_set_clock(struct omap_hsmmc_host *host) +{ + struct mmc_ios *ios = &host->mmc->ios; + unsigned long regval; + unsigned long timeout; + + dev_dbg(mmc_dev(host->mmc), "Set clock to %uHz\n", ios->clock); + + omap_hsmmc_stop_clock(host); + + regval = OMAP_HSMMC_READ(host->base, SYSCTL); + regval = regval & ~(CLKD_MASK | DTO_MASK); + regval = regval | (calc_divisor(host, ios) << 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)) + cpu_relax(); + + omap_hsmmc_start_clock(host); +} + +static void omap_hsmmc_set_bus_width(struct omap_hsmmc_host *host) +{ + struct mmc_ios *ios = &host->mmc->ios; + u32 con; + + 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; + } +} + +static void omap_hsmmc_set_bus_mode(struct omap_hsmmc_host *host) +{ + struct mmc_ios *ios = &host->mmc->ios; + u32 con; + + 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); +} + +#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; + 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->pdata->controller_flags & OMAP_HSMMC_SUPPORTS_DUAL_VOLT) { + 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; + + omap_hsmmc_set_bus_width(host); + + omap_hsmmc_set_clock(host); + + omap_hsmmc_set_bus_mode(host); + +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->host_cookie = 0; + } + host->data = NULL; +} + +/* + * Readable error output + */ +#ifdef CONFIG_MMC_DEBUG +static void omap_hsmmc_dbg_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", "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); +} +#else +static inline void omap_hsmmc_dbg_report_irq(struct omap_hsmmc_host *host, + u32 status) +{ +} +#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) { + omap_hsmmc_dbg_report_irq(host, status); + 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 */ + pm_runtime_put_sync(host->dev); + 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); + pm_runtime_get_sync(host->dev); + 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) { + dev_info(host->dev, "%s: cover is closed, " + "card is now accessible\n", + mmc_hostname(host->mmc)); + host->protect_card = 0; + } + } else { + if (!host->protect_card) { + dev_info(host->dev, "%s: cover is open, " + "card is now inaccessible\n", + mmc_hostname(host->mmc)); + host->protect_card = 1; + } + } +} + +/* + * irq handler to notify the core about card insertion/removal + */ +static irqreturn_t omap_hsmmc_detect(int irq, void *dev_id) +{ + struct omap_hsmmc_host *host = dev_id; + struct omap_mmc_slot_data *slot = &mmc_slot(host); + int carddetect; + + if (host->suspended) + return IRQ_HANDLED; + + 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); + 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; + 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; + } + + data = host->mrq->data; + 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) { + dev_warn(host->dev, "[%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) { + if (data->host_cookie) + 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); + int do_send_init_stream = 0; + + pm_runtime_get_sync(host->dev); + + 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 */ + + omap_hsmmc_set_bus_width(host); + + 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) && + /* + * With pbias cell programming missing, this + * can't be allowed when booting with device + * tree. + */ + !host->dev->of_node) { + /* + * 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"); + } + } + + omap_hsmmc_set_clock(host); + + if (do_send_init_stream) + send_init_stream(host); + + omap_hsmmc_set_bus_mode(host); + + pm_runtime_put_autosuspend(host->dev); +} + +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); +} + +static int omap_hsmmc_enable_fclk(struct mmc_host *mmc) +{ + struct omap_hsmmc_host *host = mmc_priv(mmc); + + pm_runtime_get_sync(host->dev); + + return 0; +} + +static int omap_hsmmc_disable_fclk(struct mmc_host *mmc) +{ + struct omap_hsmmc_host *host = mmc_priv(mmc); + + pm_runtime_mark_last_busy(host->dev); + pm_runtime_put_autosuspend(host->dev); + + 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 */ +}; + +#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 ctx_loss:\t%d:%d\n\nregs:\n", + mmc->index, host->context_loss, context_loss); + + if (host->suspended) { + seq_printf(s, "host suspended, can't read registers\n"); + return 0; + } + + pm_runtime_get_sync(host->dev); + + 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)); + + pm_runtime_mark_last_busy(host->dev); + pm_runtime_put_autosuspend(host->dev); + + 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 + +#ifdef CONFIG_OF +static u16 omap4_reg_offset = 0x100; + +static const struct of_device_id omap_mmc_of_match[] = { + { + .compatible = "ti,omap2-hsmmc", + }, + { + .compatible = "ti,omap3-hsmmc", + }, + { + .compatible = "ti,omap4-hsmmc", + .data = &omap4_reg_offset, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, omap_mmc_of_match); + +static struct omap_mmc_platform_data *of_get_hsmmc_pdata(struct device *dev) +{ + struct omap_mmc_platform_data *pdata; + struct device_node *np = dev->of_node; + u32 bus_width; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; /* out of memory */ + + if (of_find_property(np, "ti,dual-volt", NULL)) + pdata->controller_flags |= OMAP_HSMMC_SUPPORTS_DUAL_VOLT; + + /* This driver only supports 1 slot */ + pdata->nr_slots = 1; + pdata->slots[0].switch_pin = of_get_named_gpio(np, "cd-gpios", 0); + pdata->slots[0].gpio_wp = of_get_named_gpio(np, "wp-gpios", 0); + + if (of_find_property(np, "ti,non-removable", NULL)) { + pdata->slots[0].nonremovable = true; + pdata->slots[0].no_regulator_off_init = true; + } + of_property_read_u32(np, "ti,bus-width", &bus_width); + if (bus_width == 4) + pdata->slots[0].caps |= MMC_CAP_4_BIT_DATA; + else if (bus_width == 8) + pdata->slots[0].caps |= MMC_CAP_8_BIT_DATA; + + if (of_find_property(np, "ti,needs-special-reset", NULL)) + pdata->slots[0].features |= HSMMC_HAS_UPDATED_RESET; + + return pdata; +} +#else +static inline struct omap_mmc_platform_data + *of_get_hsmmc_pdata(struct device *dev) +{ + return NULL; +} +#endif + +static int __devinit 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; + const struct of_device_id *match; + + match = of_match_device(of_match_ptr(omap_mmc_of_match), &pdev->dev); + if (match) { + pdata = of_get_hsmmc_pdata(&pdev->dev); + if (match->data) { + u16 *offsetp = match->data; + pdata->reg_offset = *offsetp; + } + } + + 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 = 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->slot_id = 0; + host->mapbase = res->start + pdata->reg_offset; + host->base = ioremap(host->mapbase, SZ_4K); + host->power_mode = MMC_POWER_OFF; + host->next_data.cookie = 1; + + platform_set_drvdata(pdev, host); + + 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 = OMAP_MMC_MIN_CLOCK; + + if (pdata->max_freq > 0) + mmc->f_max = pdata->max_freq; + else + mmc->f_max = OMAP_MMC_MAX_CLOCK; + + spin_lock_init(&host->irq_lock); + + host->fclk = clk_get(&pdev->dev, "fck"); + if (IS_ERR(host->fclk)) { + ret = PTR_ERR(host->fclk); + host->fclk = NULL; + goto err1; + } + + if (host->pdata->controller_flags & OMAP_HSMMC_BROKEN_MULTIBLOCK_READ) { + dev_info(&pdev->dev, "multiblock reads disabled due to 35xx erratum 2.1.1.128; MMC read performance may suffer\n"); + mmc->caps2 |= MMC_CAP2_NO_MULTI_READ; + } + + pm_runtime_enable(host->dev); + pm_runtime_get_sync(host->dev); + pm_runtime_set_autosuspend_delay(host->dev, MMC_AUTOSUSPEND_DELAY); + pm_runtime_use_autosuspend(host->dev); + + omap_hsmmc_context_save(host); + + 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; + + mmc->pm_caps = mmc_slot(host).pm_caps; + + omap_hsmmc_conf_bus_power(host); + + res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx"); + if (!res) { + dev_err(mmc_dev(host->mmc), "cannot get DMA TX channel\n"); + goto err_irq; + } + host->dma_line_tx = res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx"); + if (!res) { + dev_err(mmc_dev(host->mmc), "cannot get DMA RX channel\n"); + goto err_irq; + } + host->dma_line_rx = res->start; + + /* Request IRQ for MMC operations */ + ret = request_irq(host->irq, omap_hsmmc_irq, 0, + 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_threaded_irq(mmc_slot(host).card_detect_irq, + NULL, + omap_hsmmc_detect, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + 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); + + 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); + pm_runtime_mark_last_busy(host->dev); + pm_runtime_put_autosuspend(host->dev); + + 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: + pm_runtime_put_sync(host->dev); + pm_runtime_disable(host->dev); + clk_put(host->fclk); + 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 __devexit omap_hsmmc_remove(struct platform_device *pdev) +{ + struct omap_hsmmc_host *host = platform_get_drvdata(pdev); + struct resource *res; + + pm_runtime_get_sync(host->dev); + 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); + + pm_runtime_put_sync(host->dev); + pm_runtime_disable(host->dev); + clk_put(host->fclk); + 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 omap_hsmmc_host *host = dev_get_drvdata(dev); + + if (!host) + return 0; + + if (host && host->suspended) + return 0; + + pm_runtime_get_sync(host->dev); + host->suspended = 1; + if (host->pdata->suspend) { + ret = host->pdata->suspend(dev, host->slot_id); + if (ret) { + dev_dbg(dev, "Unable to handle MMC board" + " level suspend\n"); + host->suspended = 0; + return ret; + } + } + ret = mmc_suspend_host(host->mmc); + + if (ret) { + host->suspended = 0; + if (host->pdata->resume) { + ret = host->pdata->resume(dev, host->slot_id); + if (ret) + dev_dbg(dev, "Unmask interrupt failed\n"); + } + goto err; + } + + if (!(host->mmc->pm_flags & MMC_PM_KEEP_POWER)) { + omap_hsmmc_disable_irq(host); + OMAP_HSMMC_WRITE(host->base, HCTL, + OMAP_HSMMC_READ(host->base, HCTL) & ~SDBP); + } + + if (host->got_dbclk) + clk_disable(host->dbclk); +err: + pm_runtime_put_sync(host->dev); + return ret; +} + +/* Routine to resume the MMC device */ +static int omap_hsmmc_resume(struct device *dev) +{ + int ret = 0; + struct omap_hsmmc_host *host = dev_get_drvdata(dev); + + if (!host) + return 0; + + if (host && !host->suspended) + return 0; + + pm_runtime_get_sync(host->dev); + + if (host->got_dbclk) + clk_enable(host->dbclk); + + if (!(host->mmc->pm_flags & MMC_PM_KEEP_POWER)) + omap_hsmmc_conf_bus_power(host); + + if (host->pdata->resume) { + ret = host->pdata->resume(dev, host->slot_id); + if (ret) + dev_dbg(dev, "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; + + pm_runtime_mark_last_busy(host->dev); + pm_runtime_put_autosuspend(host->dev); + + return ret; + +} + +#else +#define omap_hsmmc_suspend NULL +#define omap_hsmmc_resume NULL +#endif + +static int omap_hsmmc_runtime_suspend(struct device *dev) +{ + struct omap_hsmmc_host *host; + + host = platform_get_drvdata(to_platform_device(dev)); + omap_hsmmc_context_save(host); + dev_dbg(dev, "disabled\n"); + + return 0; +} + +static int omap_hsmmc_runtime_resume(struct device *dev) +{ + struct omap_hsmmc_host *host; + + host = platform_get_drvdata(to_platform_device(dev)); + omap_hsmmc_context_restore(host); + dev_dbg(dev, "enabled\n"); + + return 0; +} + +static struct dev_pm_ops omap_hsmmc_dev_pm_ops = { + .suspend = omap_hsmmc_suspend, + .resume = omap_hsmmc_resume, + .runtime_suspend = omap_hsmmc_runtime_suspend, + .runtime_resume = omap_hsmmc_runtime_resume, +}; + +static struct platform_driver omap_hsmmc_driver = { + .probe = omap_hsmmc_probe, + .remove = __devexit_p(omap_hsmmc_remove), + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .pm = &omap_hsmmc_dev_pm_ops, + .of_match_table = of_match_ptr(omap_mmc_of_match), + }, +}; + +module_platform_driver(omap_hsmmc_driver); +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..cb2dc0e7 --- /dev/null +++ b/drivers/mmc/host/pxamci.c @@ -0,0 +1,879 @@ +/* + * 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 { + pr_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 + }, +}; + +module_platform_driver(pxamci_driver); + +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..c3622a69 --- /dev/null +++ b/drivers/mmc/host/s3cmci.c @@ -0,0 +1,1921 @@ +/* + * 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) { + pr_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); + + /* pr_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 dma_data_direction source) +{ + static enum dma_data_direction 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 ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + 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 resource.\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 resource.\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, +}; + +module_platform_driver(s3cmci_driver); + +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..28a87080 --- /dev/null +++ b/drivers/mmc/host/sdhci-cns3xxx.c @@ -0,0 +1,123 @@ +/* + * 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/module.h> +#include <mach/cns3xxx.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, +}; + +static 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, +}; + +static int __devinit sdhci_cns3xxx_probe(struct platform_device *pdev) +{ + return sdhci_pltfm_register(pdev, &sdhci_cns3xxx_pdata); +} + +static int __devexit sdhci_cns3xxx_remove(struct platform_device *pdev) +{ + return sdhci_pltfm_unregister(pdev); +} + +static struct platform_driver sdhci_cns3xxx_driver = { + .driver = { + .name = "sdhci-cns3xxx", + .owner = THIS_MODULE, + .pm = SDHCI_PLTFM_PMOPS, + }, + .probe = sdhci_cns3xxx_probe, + .remove = __devexit_p(sdhci_cns3xxx_remove), +}; + +module_platform_driver(sdhci_cns3xxx_driver); + +MODULE_DESCRIPTION("SDHCI driver for CNS3xxx"); +MODULE_AUTHOR("Scott Shu, " + "Anton Vorontsov <avorontsov@mvista.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mmc/host/sdhci-dove.c b/drivers/mmc/host/sdhci-dove.c new file mode 100644 index 00000000..177f697b --- /dev/null +++ b/drivers/mmc/host/sdhci-dove.c @@ -0,0 +1,97 @@ +/* + * 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/module.h> +#include <linux/mmc/host.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, +}; + +static 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, +}; + +static int __devinit sdhci_dove_probe(struct platform_device *pdev) +{ + return sdhci_pltfm_register(pdev, &sdhci_dove_pdata); +} + +static int __devexit sdhci_dove_remove(struct platform_device *pdev) +{ + return sdhci_pltfm_unregister(pdev); +} + +static struct platform_driver sdhci_dove_driver = { + .driver = { + .name = "sdhci-dove", + .owner = THIS_MODULE, + .pm = SDHCI_PLTFM_PMOPS, + }, + .probe = sdhci_dove_probe, + .remove = __devexit_p(sdhci_dove_remove), +}; + +module_platform_driver(sdhci_dove_driver); + +MODULE_DESCRIPTION("SDHCI driver for Dove"); +MODULE_AUTHOR("Saeed Bishara <saeed@marvell.com>, " + "Mike Rapoport <mike@compulab.co.il>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c new file mode 100644 index 00000000..8abdaf66 --- /dev/null +++ b/drivers/mmc/host/sdhci-esdhc-imx.c @@ -0,0 +1,613 @@ +/* + * 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/module.h> +#include <linux/slab.h> +#include <linux/mmc/host.h> +#include <linux/mmc/mmc.h> +#include <linux/mmc/sdio.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <mach/esdhc.h> +#include "sdhci-pltfm.h" +#include "sdhci-esdhc.h" + +#define SDHCI_CTRL_D3CD 0x08 +/* VENDOR SPEC register */ +#define SDHCI_VENDOR_SPEC 0xC0 +#define SDHCI_VENDOR_SPEC_SDIO_QUIRK 0x00000002 +#define SDHCI_WTMK_LVL 0x44 +#define SDHCI_MIX_CTRL 0x48 + +/* + * 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) + +enum imx_esdhc_type { + IMX25_ESDHC, + IMX35_ESDHC, + IMX51_ESDHC, + IMX53_ESDHC, + IMX6Q_USDHC, +}; + +struct pltfm_imx_data { + int flags; + u32 scratchpad; + enum imx_esdhc_type devtype; + struct esdhc_platform_data boarddata; +}; + +static struct platform_device_id imx_esdhc_devtype[] = { + { + .name = "sdhci-esdhc-imx25", + .driver_data = IMX25_ESDHC, + }, { + .name = "sdhci-esdhc-imx35", + .driver_data = IMX35_ESDHC, + }, { + .name = "sdhci-esdhc-imx51", + .driver_data = IMX51_ESDHC, + }, { + .name = "sdhci-esdhc-imx53", + .driver_data = IMX53_ESDHC, + }, { + .name = "sdhci-usdhc-imx6q", + .driver_data = IMX6Q_USDHC, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(platform, imx_esdhc_devtype); + +static const struct of_device_id imx_esdhc_dt_ids[] = { + { .compatible = "fsl,imx25-esdhc", .data = &imx_esdhc_devtype[IMX25_ESDHC], }, + { .compatible = "fsl,imx35-esdhc", .data = &imx_esdhc_devtype[IMX35_ESDHC], }, + { .compatible = "fsl,imx51-esdhc", .data = &imx_esdhc_devtype[IMX51_ESDHC], }, + { .compatible = "fsl,imx53-esdhc", .data = &imx_esdhc_devtype[IMX53_ESDHC], }, + { .compatible = "fsl,imx6q-usdhc", .data = &imx_esdhc_devtype[IMX6Q_USDHC], }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_esdhc_dt_ids); + +static inline int is_imx25_esdhc(struct pltfm_imx_data *data) +{ + return data->devtype == IMX25_ESDHC; +} + +static inline int is_imx35_esdhc(struct pltfm_imx_data *data) +{ + return data->devtype == IMX35_ESDHC; +} + +static inline int is_imx51_esdhc(struct pltfm_imx_data *data) +{ + return data->devtype == IMX51_ESDHC; +} + +static inline int is_imx53_esdhc(struct pltfm_imx_data *data) +{ + return data->devtype == IMX53_ESDHC; +} + +static inline int is_imx6q_usdhc(struct pltfm_imx_data *data) +{ + return data->devtype == IMX6Q_USDHC; +} + +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 = &imx_data->boarddata; + + /* fake CARD_PRESENT flag */ + u32 val = readl(host->ioaddr + reg); + + if (unlikely((reg == SDHCI_PRESENT_STATE) + && gpio_is_valid(boarddata->cd_gpio))) { + if (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 (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; + struct esdhc_platform_data *boarddata = &imx_data->boarddata; + u32 data; + + if (unlikely(reg == SDHCI_INT_ENABLE || reg == SDHCI_SIGNAL_ENABLE)) { + if (boarddata->cd_type == ESDHC_CD_GPIO) + /* + * 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) { + /* + * Clear and then set D3CD bit to avoid missing the + * card interrupt. This is a eSDHC controller problem + * so we need to apply the following workaround: clear + * and set D3CD bit will make eSDHC re-sample the card + * interrupt. In case a card interrupt was lost, + * re-sample it by the following steps. + */ + 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) +{ + if (unlikely(reg == SDHCI_HOST_VERSION)) { + u16 val = readw(host->ioaddr + (reg ^ 2)); + /* + * uSDHC supports SDHCI v3.0, but it's encoded as value + * 0x3 in host controller version register, which violates + * SDHCI_SPEC_300 definition. Work it around here. + */ + if ((val & SDHCI_SPEC_VER_MASK) == 3) + return --val; + } + + return readw(host->ioaddr + reg); +} + +static void esdhc_writew_le(struct sdhci_host *host, u16 val, int reg) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct pltfm_imx_data *imx_data = pltfm_host->priv; + + switch (reg) { + 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; + 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; + + if (is_imx6q_usdhc(imx_data)) { + u32 m = readl(host->ioaddr + SDHCI_MIX_CTRL); + m = imx_data->scratchpad | (m & 0xffff0000); + writel(m, 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 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 + */ + return; + case SDHCI_HOST_CONTROL: + /* FSL messed up here, so we can just keep those three */ + new_val = val & (SDHCI_CTRL_LED | \ + SDHCI_CTRL_4BITBUS | \ + SDHCI_CTRL_D3CD); + /* 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); + + /* + * The esdhc has a design violation to SDHC spec which tells + * that software reset should not affect card detection circuit. + * But esdhc clears its SYSCTL register bits [0..2] during the + * software reset. This will stop those clocks that card detection + * circuit relies on. To work around it, we turn the clocks on back + * to keep card detection circuit functional. + */ + if ((reg == SDHCI_SOFTWARE_RESET) && (val & 1)) + esdhc_clrset_le(host, 0x7, 0x7, ESDHC_SYSTEM_CONTROL); +} + +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 sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct pltfm_imx_data *imx_data = pltfm_host->priv; + struct esdhc_platform_data *boarddata = &imx_data->boarddata; + + switch (boarddata->wp_type) { + case ESDHC_WP_GPIO: + if (gpio_is_valid(boarddata->wp_gpio)) + return gpio_get_value(boarddata->wp_gpio); + case ESDHC_WP_CONTROLLER: + return !(readl(host->ioaddr + SDHCI_PRESENT_STATE) & + SDHCI_WRITE_PROTECT); + case ESDHC_WP_NONE: + break; + } + + return -ENOSYS; +} + +static struct sdhci_ops sdhci_esdhc_ops = { + .read_l = esdhc_readl_le, + .read_w = esdhc_readw_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, + .get_ro = esdhc_pltfm_get_ro, +}; + +static struct sdhci_pltfm_data sdhci_esdhc_imx_pdata = { + .quirks = ESDHC_DEFAULT_QUIRKS | SDHCI_QUIRK_NO_HISPD_BIT + | SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC + | SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC + | SDHCI_QUIRK_BROKEN_CARD_DETECTION, + .ops = &sdhci_esdhc_ops, +}; + +static irqreturn_t cd_irq(int irq, void *data) +{ + struct sdhci_host *sdhost = (struct sdhci_host *)data; + + tasklet_schedule(&sdhost->card_tasklet); + return IRQ_HANDLED; +}; + +#ifdef CONFIG_OF +static int __devinit +sdhci_esdhc_imx_probe_dt(struct platform_device *pdev, + struct esdhc_platform_data *boarddata) +{ + struct device_node *np = pdev->dev.of_node; + + if (!np) + return -ENODEV; + + if (of_get_property(np, "fsl,card-wired", NULL)) + boarddata->cd_type = ESDHC_CD_PERMANENT; + + if (of_get_property(np, "fsl,cd-controller", NULL)) + boarddata->cd_type = ESDHC_CD_CONTROLLER; + + if (of_get_property(np, "fsl,wp-controller", NULL)) + boarddata->wp_type = ESDHC_WP_CONTROLLER; + + boarddata->cd_gpio = of_get_named_gpio(np, "cd-gpios", 0); + if (gpio_is_valid(boarddata->cd_gpio)) + boarddata->cd_type = ESDHC_CD_GPIO; + + boarddata->wp_gpio = of_get_named_gpio(np, "wp-gpios", 0); + if (gpio_is_valid(boarddata->wp_gpio)) + boarddata->wp_type = ESDHC_WP_GPIO; + + return 0; +} +#else +static inline int +sdhci_esdhc_imx_probe_dt(struct platform_device *pdev, + struct esdhc_platform_data *boarddata) +{ + return -ENODEV; +} +#endif + +static int __devinit sdhci_esdhc_imx_probe(struct platform_device *pdev) +{ + const struct of_device_id *of_id = + of_match_device(imx_esdhc_dt_ids, &pdev->dev); + struct sdhci_pltfm_host *pltfm_host; + struct sdhci_host *host; + struct esdhc_platform_data *boarddata; + struct clk *clk; + int err; + struct pltfm_imx_data *imx_data; + + host = sdhci_pltfm_init(pdev, &sdhci_esdhc_imx_pdata); + if (IS_ERR(host)) + return PTR_ERR(host); + + pltfm_host = sdhci_priv(host); + + imx_data = kzalloc(sizeof(struct pltfm_imx_data), GFP_KERNEL); + if (!imx_data) { + err = -ENOMEM; + goto err_imx_data; + } + + if (of_id) + pdev->id_entry = of_id->data; + imx_data->devtype = pdev->id_entry->driver_data; + pltfm_host->priv = imx_data; + + clk = clk_get(mmc_dev(host->mmc), NULL); + if (IS_ERR(clk)) { + dev_err(mmc_dev(host->mmc), "clk err\n"); + err = PTR_ERR(clk); + goto err_clk_get; + } + clk_prepare_enable(clk); + pltfm_host->clk = clk; + + host->quirks |= SDHCI_QUIRK_BROKEN_TIMEOUT_VAL; + + if (is_imx25_esdhc(imx_data) || is_imx35_esdhc(imx_data)) + /* Fix errata ENGcm07207 present on i.MX25 and i.MX35 */ + host->quirks |= SDHCI_QUIRK_NO_MULTIBLOCK + | SDHCI_QUIRK_BROKEN_ADMA; + + if (is_imx53_esdhc(imx_data)) + imx_data->flags |= ESDHC_FLAG_MULTIBLK_NO_INT; + + /* + * The imx6q ROM code will change the default watermark level setting + * to something insane. Change it back here. + */ + if (is_imx6q_usdhc(imx_data)) + writel(0x08100810, host->ioaddr + SDHCI_WTMK_LVL); + + boarddata = &imx_data->boarddata; + if (sdhci_esdhc_imx_probe_dt(pdev, boarddata) < 0) { + if (!host->mmc->parent->platform_data) { + dev_err(mmc_dev(host->mmc), "no board data!\n"); + err = -EINVAL; + goto no_board_data; + } + imx_data->boarddata = *((struct esdhc_platform_data *) + host->mmc->parent->platform_data); + } + + /* write_protect */ + if (boarddata->wp_type == ESDHC_WP_GPIO) { + 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 = -EINVAL; + } + } else { + boarddata->wp_gpio = -EINVAL; + } + + /* card_detect */ + if (boarddata->cd_type != ESDHC_CD_GPIO) + boarddata->cd_gpio = -EINVAL; + + switch (boarddata->cd_type) { + case ESDHC_CD_GPIO: + err = gpio_request_one(boarddata->cd_gpio, GPIOF_IN, "ESDHC_CD"); + if (err) { + dev_err(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_err(mmc_dev(host->mmc), "request irq error\n"); + goto no_card_detect_irq; + } + /* fall through */ + + case ESDHC_CD_CONTROLLER: + /* we have a working card_detect back */ + host->quirks &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION; + break; + + case ESDHC_CD_PERMANENT: + host->mmc->caps = MMC_CAP_NONREMOVABLE; + break; + + case ESDHC_CD_NONE: + break; + } + + err = sdhci_add_host(host); + if (err) + goto err_add_host; + + return 0; + +err_add_host: + if (gpio_is_valid(boarddata->cd_gpio)) + free_irq(gpio_to_irq(boarddata->cd_gpio), host); +no_card_detect_irq: + if (gpio_is_valid(boarddata->cd_gpio)) + gpio_free(boarddata->cd_gpio); + if (gpio_is_valid(boarddata->wp_gpio)) + gpio_free(boarddata->wp_gpio); +no_card_detect_pin: +no_board_data: + clk_disable_unprepare(pltfm_host->clk); + clk_put(pltfm_host->clk); +err_clk_get: + kfree(imx_data); +err_imx_data: + sdhci_pltfm_free(pdev); + return err; +} + +static int __devexit sdhci_esdhc_imx_remove(struct platform_device *pdev) +{ + struct sdhci_host *host = platform_get_drvdata(pdev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct pltfm_imx_data *imx_data = pltfm_host->priv; + struct esdhc_platform_data *boarddata = &imx_data->boarddata; + int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); + + sdhci_remove_host(host, dead); + + if (gpio_is_valid(boarddata->wp_gpio)) + gpio_free(boarddata->wp_gpio); + + if (gpio_is_valid(boarddata->cd_gpio)) { + free_irq(gpio_to_irq(boarddata->cd_gpio), host); + gpio_free(boarddata->cd_gpio); + } + + clk_disable_unprepare(pltfm_host->clk); + clk_put(pltfm_host->clk); + kfree(imx_data); + + sdhci_pltfm_free(pdev); + + return 0; +} + +static struct platform_driver sdhci_esdhc_imx_driver = { + .driver = { + .name = "sdhci-esdhc-imx", + .owner = THIS_MODULE, + .of_match_table = imx_esdhc_dt_ids, + .pm = SDHCI_PLTFM_PMOPS, + }, + .id_table = imx_esdhc_devtype, + .probe = sdhci_esdhc_imx_probe, + .remove = __devexit_p(sdhci_esdhc_imx_remove), +}; + +module_platform_driver(sdhci_esdhc_imx_driver); + +MODULE_DESCRIPTION("SDHCI driver for Freescale i.MX eSDHC"); +MODULE_AUTHOR("Wolfram Sang <w.sang@pengutronix.de>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mmc/host/sdhci-esdhc.h b/drivers/mmc/host/sdhci-esdhc.h new file mode 100644 index 00000000..b97b2f5d --- /dev/null +++ b/drivers/mmc/host/sdhci-esdhc.h @@ -0,0 +1,81 @@ +/* + * Freescale eSDHC controller driver generics for OF and pltfm. + * + * Copyright (c) 2007 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 + +/* + * 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 + +static inline void esdhc_set_clock(struct sdhci_host *host, unsigned int clock) +{ + int pre_div = 2; + int div = 1; + u32 temp; + + 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; + 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); +out: + host->clock = clock; +} + +#endif /* _DRIVERS_MMC_SDHCI_ESDHC_H */ diff --git a/drivers/mmc/host/sdhci-of-esdhc.c b/drivers/mmc/host/sdhci-of-esdhc.c new file mode 100644 index 00000000..f8eb1fb0 --- /dev/null +++ b/drivers/mmc/host/sdhci-of-esdhc.c @@ -0,0 +1,204 @@ +/* + * Freescale eSDHC controller driver. + * + * Copyright (c) 2007, 2010, 2012 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/of.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/mmc/host.h> +#include "sdhci-pltfm.h" +#include "sdhci-esdhc.h" + +static u16 esdhc_readw(struct sdhci_host *host, int reg) +{ + u16 ret; + int base = reg & ~0x3; + int shift = (reg & 0x2) * 8; + + if (unlikely(reg == SDHCI_HOST_VERSION)) + ret = in_be32(host->ioaddr + base) & 0xffff; + else + ret = (in_be32(host->ioaddr + base) >> shift) & 0xffff; + return ret; +} + +static u8 esdhc_readb(struct sdhci_host *host, int reg) +{ + int base = reg & ~0x3; + int shift = (reg & 0x3) * 8; + u8 ret = (in_be32(host->ioaddr + base) >> shift) & 0xff; + + /* + * "DMA select" locates at offset 0x28 in SD specification, but on + * P5020 or P3041, it locates at 0x29. + */ + if (reg == SDHCI_HOST_CONTROL) { + u32 dma_bits; + + dma_bits = in_be32(host->ioaddr + reg); + /* DMA select is 22,23 bits in Protocol Control Register */ + dma_bits = (dma_bits >> 5) & SDHCI_CTRL_DMA_MASK; + + /* fixup the result */ + ret &= ~SDHCI_CTRL_DMA_MASK; + ret |= dma_bits; + } + + 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) +{ + /* + * "DMA select" location is offset 0x28 in SD specification, but on + * P5020 or P3041, it's located at 0x29. + */ + if (reg == SDHCI_HOST_CONTROL) { + u32 dma_bits; + + /* DMA select is 22,23 bits in Protocol Control Register */ + dma_bits = (val & SDHCI_CTRL_DMA_MASK) << 5; + clrsetbits_be32(host->ioaddr + reg , SDHCI_CTRL_DMA_MASK << 5, + dma_bits); + val &= ~SDHCI_CTRL_DMA_MASK; + val |= in_be32(host->ioaddr + reg) & SDHCI_CTRL_DMA_MASK; + } + + /* 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_pltfm_host *pltfm_host = sdhci_priv(host); + + return pltfm_host->clock; +} + +static unsigned int esdhc_of_get_min_clock(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + + return pltfm_host->clock / 256 / 16; +} + +static void esdhc_of_set_clock(struct sdhci_host *host, unsigned int clock) +{ + /* Workaround to reduce the clock frequency for p1010 esdhc */ + if (of_find_compatible_node(NULL, NULL, "fsl,p1010-esdhc")) { + if (clock > 20000000) + clock -= 5000000; + if (clock > 40000000) + clock -= 5000000; + } + + /* Set the clock */ + esdhc_set_clock(host, clock); +} + +#ifdef CONFIG_PM +static u32 esdhc_proctl; +static void esdhc_of_suspend(struct sdhci_host *host) +{ + esdhc_proctl = sdhci_be32bs_readl(host, SDHCI_HOST_CONTROL); +} + +static void esdhc_of_resume(struct sdhci_host *host) +{ + esdhc_of_enable_dma(host); + sdhci_be32bs_writel(host, esdhc_proctl, SDHCI_HOST_CONTROL); +} +#endif + +static struct sdhci_ops sdhci_esdhc_ops = { + .read_l = sdhci_be32bs_readl, + .read_w = esdhc_readw, + .read_b = esdhc_readb, + .write_l = sdhci_be32bs_writel, + .write_w = esdhc_writew, + .write_b = esdhc_writeb, + .set_clock = esdhc_of_set_clock, + .enable_dma = esdhc_of_enable_dma, + .get_max_clock = esdhc_of_get_max_clock, + .get_min_clock = esdhc_of_get_min_clock, +#ifdef CONFIG_PM + .platform_suspend = esdhc_of_suspend, + .platform_resume = esdhc_of_resume, +#endif +}; + +static struct sdhci_pltfm_data sdhci_esdhc_pdata = { + /* card detection could be handled via GPIO */ + .quirks = ESDHC_DEFAULT_QUIRKS | SDHCI_QUIRK_BROKEN_CARD_DETECTION + | SDHCI_QUIRK_NO_CARD_NO_RESET, + .ops = &sdhci_esdhc_ops, +}; + +static int __devinit sdhci_esdhc_probe(struct platform_device *pdev) +{ + return sdhci_pltfm_register(pdev, &sdhci_esdhc_pdata); +} + +static int __devexit sdhci_esdhc_remove(struct platform_device *pdev) +{ + return sdhci_pltfm_unregister(pdev); +} + +static const struct of_device_id sdhci_esdhc_of_match[] = { + { .compatible = "fsl,mpc8379-esdhc" }, + { .compatible = "fsl,mpc8536-esdhc" }, + { .compatible = "fsl,esdhc" }, + { } +}; +MODULE_DEVICE_TABLE(of, sdhci_esdhc_of_match); + +static struct platform_driver sdhci_esdhc_driver = { + .driver = { + .name = "sdhci-esdhc", + .owner = THIS_MODULE, + .of_match_table = sdhci_esdhc_of_match, + .pm = SDHCI_PLTFM_PMOPS, + }, + .probe = sdhci_esdhc_probe, + .remove = __devexit_p(sdhci_esdhc_remove), +}; + +module_platform_driver(sdhci_esdhc_driver); + +MODULE_DESCRIPTION("SDHCI OF driver for Freescale MPC eSDHC"); +MODULE_AUTHOR("Xiaobo Xie <X.Xie@freescale.com>, " + "Anton Vorontsov <avorontsov@ru.mvista.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mmc/host/sdhci-of-hlwd.c b/drivers/mmc/host/sdhci-of-hlwd.c new file mode 100644 index 00000000..0ce088ae --- /dev/null +++ b/drivers/mmc/host/sdhci-of-hlwd.c @@ -0,0 +1,100 @@ +/* + * 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/module.h> +#include <linux/mmc/host.h> +#include "sdhci-pltfm.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); +} + +static struct sdhci_ops sdhci_hlwd_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, +}; + +static struct sdhci_pltfm_data sdhci_hlwd_pdata = { + .quirks = SDHCI_QUIRK_32BIT_DMA_ADDR | + SDHCI_QUIRK_32BIT_DMA_SIZE, + .ops = &sdhci_hlwd_ops, +}; + +static int __devinit sdhci_hlwd_probe(struct platform_device *pdev) +{ + return sdhci_pltfm_register(pdev, &sdhci_hlwd_pdata); +} + +static int __devexit sdhci_hlwd_remove(struct platform_device *pdev) +{ + return sdhci_pltfm_unregister(pdev); +} + +static const struct of_device_id sdhci_hlwd_of_match[] = { + { .compatible = "nintendo,hollywood-sdhci" }, + { } +}; +MODULE_DEVICE_TABLE(of, sdhci_hlwd_of_match); + +static struct platform_driver sdhci_hlwd_driver = { + .driver = { + .name = "sdhci-hlwd", + .owner = THIS_MODULE, + .of_match_table = sdhci_hlwd_of_match, + .pm = SDHCI_PLTFM_PMOPS, + }, + .probe = sdhci_hlwd_probe, + .remove = __devexit_p(sdhci_hlwd_remove), +}; + +module_platform_driver(sdhci_hlwd_driver); + +MODULE_DESCRIPTION("Nintendo Wii SDHCI OF driver"); +MODULE_AUTHOR("The GameCube Linux Team, Albert Herranz"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mmc/host/sdhci-pci-data.c b/drivers/mmc/host/sdhci-pci-data.c new file mode 100644 index 00000000..a6112177 --- /dev/null +++ b/drivers/mmc/host/sdhci-pci-data.c @@ -0,0 +1,5 @@ +#include <linux/module.h> +#include <linux/mmc/sdhci-pci-data.h> + +struct sdhci_pci_data *(*sdhci_pci_get_data)(struct pci_dev *pdev, int slotno); +EXPORT_SYMBOL_GPL(sdhci_pci_get_data); diff --git a/drivers/mmc/host/sdhci-pci.c b/drivers/mmc/host/sdhci-pci.c new file mode 100644 index 00000000..69ef0bea --- /dev/null +++ b/drivers/mmc/host/sdhci-pci.c @@ -0,0 +1,1499 @@ +/* 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/module.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 <linux/gpio.h> +#include <linux/pm_runtime.h> +#include <linux/mmc/sdhci-pci-data.h> + +#include "sdhci.h" + +/* + * PCI device IDs + */ +#define PCI_DEVICE_ID_INTEL_PCH_SDIO0 0x8809 +#define PCI_DEVICE_ID_INTEL_PCH_SDIO1 0x880a + +/* + * 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; + unsigned int quirks2; + bool allow_runtime_pm; + + 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 *); + int (*resume) (struct sdhci_pci_chip *); +}; + +struct sdhci_pci_slot { + struct sdhci_pci_chip *chip; + struct sdhci_host *host; + struct sdhci_pci_data *data; + + int pci_bar; + int rst_n_gpio; + int cd_gpio; + int cd_irq; +}; + +struct sdhci_pci_chip { + struct pci_dev *pdev; + + unsigned int quirks; + unsigned int quirks2; + bool allow_runtime_pm; + 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, +}; + +static int mrst_hc_probe_slot(struct sdhci_pci_slot *slot) +{ + slot->host->mmc->caps |= MMC_CAP_8_BIT_DATA; + return 0; +} + +/* + * 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 int pch_hc_probe_slot(struct sdhci_pci_slot *slot) +{ + slot->host->mmc->caps |= MMC_CAP_8_BIT_DATA; + return 0; +} + +#ifdef CONFIG_PM_RUNTIME + +static irqreturn_t sdhci_pci_sd_cd(int irq, void *dev_id) +{ + struct sdhci_pci_slot *slot = dev_id; + struct sdhci_host *host = slot->host; + + mmc_detect_change(host->mmc, msecs_to_jiffies(200)); + return IRQ_HANDLED; +} + +static void sdhci_pci_add_own_cd(struct sdhci_pci_slot *slot) +{ + int err, irq, gpio = slot->cd_gpio; + + slot->cd_gpio = -EINVAL; + slot->cd_irq = -EINVAL; + + if (!gpio_is_valid(gpio)) + return; + + err = gpio_request(gpio, "sd_cd"); + if (err < 0) + goto out; + + err = gpio_direction_input(gpio); + if (err < 0) + goto out_free; + + irq = gpio_to_irq(gpio); + if (irq < 0) + goto out_free; + + err = request_irq(irq, sdhci_pci_sd_cd, IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, "sd_cd", slot); + if (err) + goto out_free; + + slot->cd_gpio = gpio; + slot->cd_irq = irq; + + return; + +out_free: + gpio_free(gpio); +out: + dev_warn(&slot->chip->pdev->dev, "failed to setup card detect wake up\n"); +} + +static void sdhci_pci_remove_own_cd(struct sdhci_pci_slot *slot) +{ + if (slot->cd_irq >= 0) + free_irq(slot->cd_irq, slot); + if (gpio_is_valid(slot->cd_gpio)) + gpio_free(slot->cd_gpio); +} + +#else + +static inline void sdhci_pci_add_own_cd(struct sdhci_pci_slot *slot) +{ +} + +static inline void sdhci_pci_remove_own_cd(struct sdhci_pci_slot *slot) +{ +} + +#endif + +static int mfd_emmc_probe_slot(struct sdhci_pci_slot *slot) +{ + slot->host->mmc->caps |= MMC_CAP_8_BIT_DATA | MMC_CAP_NONREMOVABLE; + slot->host->mmc->caps2 |= MMC_CAP2_BOOTPART_NOACC | + MMC_CAP2_HC_ERASE_SZ; + return 0; +} + +static int mfd_sdio_probe_slot(struct sdhci_pci_slot *slot) +{ + slot->host->mmc->caps |= MMC_CAP_POWER_OFF_CARD | MMC_CAP_NONREMOVABLE; + return 0; +} + +static const struct sdhci_pci_fixes sdhci_intel_mrst_hc0 = { + .quirks = SDHCI_QUIRK_BROKEN_ADMA | SDHCI_QUIRK_NO_HISPD_BIT, + .probe_slot = mrst_hc_probe_slot, +}; + +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, + .allow_runtime_pm = true, +}; + +static const struct sdhci_pci_fixes sdhci_intel_mfd_sdio = { + .quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC, + .quirks2 = SDHCI_QUIRK2_HOST_OFF_CARD_ON, + .allow_runtime_pm = true, + .probe_slot = mfd_sdio_probe_slot, +}; + +static const struct sdhci_pci_fixes sdhci_intel_mfd_emmc = { + .quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC, + .allow_runtime_pm = true, + .probe_slot = mfd_emmc_probe_slot, +}; + +static const struct sdhci_pci_fixes sdhci_intel_pch_sdio = { + .quirks = SDHCI_QUIRK_BROKEN_ADMA, + .probe_slot = pch_hc_probe_slot, +}; + +/* 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) +{ + 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_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_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, + }, + + { + .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, + }, + + { + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_PCH_SDIO0, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = (kernel_ulong_t)&sdhci_intel_pch_sdio, + }, + + { + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_PCH_SDIO1, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = (kernel_ulong_t)&sdhci_intel_pch_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 int sdhci_pci_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_8BITBUS | SDHCI_CTRL_4BITBUS); + break; + } + + sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); + + return 0; +} + +static void sdhci_pci_hw_reset(struct sdhci_host *host) +{ + struct sdhci_pci_slot *slot = sdhci_priv(host); + int rst_n_gpio = slot->rst_n_gpio; + + if (!gpio_is_valid(rst_n_gpio)) + return; + gpio_set_value_cansleep(rst_n_gpio, 0); + /* For eMMC, minimum is 1us but give it 10us for good measure */ + udelay(10); + gpio_set_value_cansleep(rst_n_gpio, 1); + /* For eMMC, minimum is 200us but give it 300us for good measure */ + usleep_range(300, 1000); +} + +static struct sdhci_ops sdhci_pci_ops = { + .enable_dma = sdhci_pci_enable_dma, + .platform_8bit_width = sdhci_pci_8bit_width, + .hw_reset = sdhci_pci_hw_reset, +}; + +/*****************************************************************************\ + * * + * Suspend/resume * + * * +\*****************************************************************************/ + +#ifdef CONFIG_PM + +static int sdhci_pci_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + 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); + + if (ret) + goto err_pci_suspend; + + 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); + if (ret) + goto err_pci_suspend; + } + + 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_D3hot, 0); + pci_disable_device(pdev); + pci_set_power_state(pdev, PCI_D3hot); + } + + return 0; + +err_pci_suspend: + while (--i >= 0) + sdhci_resume_host(chip->slots[i]->host); + return ret; +} + +static int sdhci_pci_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + 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 */ + +#ifdef CONFIG_PM_RUNTIME + +static int sdhci_pci_runtime_suspend(struct device *dev) +{ + struct pci_dev *pdev = container_of(dev, struct pci_dev, dev); + struct sdhci_pci_chip *chip; + struct sdhci_pci_slot *slot; + int i, ret; + + chip = pci_get_drvdata(pdev); + if (!chip) + return 0; + + for (i = 0; i < chip->num_slots; i++) { + slot = chip->slots[i]; + if (!slot) + continue; + + ret = sdhci_runtime_suspend_host(slot->host); + + if (ret) + goto err_pci_runtime_suspend; + } + + if (chip->fixes && chip->fixes->suspend) { + ret = chip->fixes->suspend(chip); + if (ret) + goto err_pci_runtime_suspend; + } + + return 0; + +err_pci_runtime_suspend: + while (--i >= 0) + sdhci_runtime_resume_host(chip->slots[i]->host); + return ret; +} + +static int sdhci_pci_runtime_resume(struct device *dev) +{ + struct pci_dev *pdev = container_of(dev, struct pci_dev, dev); + struct sdhci_pci_chip *chip; + struct sdhci_pci_slot *slot; + int i, ret; + + chip = pci_get_drvdata(pdev); + if (!chip) + return 0; + + 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_runtime_resume_host(slot->host); + if (ret) + return ret; + } + + return 0; +} + +static int sdhci_pci_runtime_idle(struct device *dev) +{ + return 0; +} + +#else + +#define sdhci_pci_runtime_suspend NULL +#define sdhci_pci_runtime_resume NULL +#define sdhci_pci_runtime_idle NULL + +#endif + +static const struct dev_pm_ops sdhci_pci_pm_ops = { + .suspend = sdhci_pci_suspend, + .resume = sdhci_pci_resume, + .runtime_suspend = sdhci_pci_runtime_suspend, + .runtime_resume = sdhci_pci_runtime_resume, + .runtime_idle = sdhci_pci_runtime_idle, +}; + +/*****************************************************************************\ + * * + * Device probing/removal * + * * +\*****************************************************************************/ + +static struct sdhci_pci_slot * __devinit sdhci_pci_probe_slot( + struct pci_dev *pdev, struct sdhci_pci_chip *chip, int first_bar, + int slotno) +{ + struct sdhci_pci_slot *slot; + struct sdhci_host *host; + int ret, bar = first_bar + slotno; + + 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; + slot->rst_n_gpio = -EINVAL; + slot->cd_gpio = -EINVAL; + + /* Retrieve platform data if there is any */ + if (*sdhci_pci_get_data) + slot->data = sdhci_pci_get_data(pdev, slotno); + + if (slot->data) { + if (slot->data->setup) { + ret = slot->data->setup(slot->data); + if (ret) { + dev_err(&pdev->dev, "platform setup failed\n"); + goto free; + } + } + slot->rst_n_gpio = slot->data->rst_n_gpio; + slot->cd_gpio = slot->data->cd_gpio; + } + + host->hw_name = "PCI"; + host->ops = &sdhci_pci_ops; + host->quirks = chip->quirks; + host->quirks2 = chip->quirks2; + + 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 cleanup; + } + + 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; + } + + if (gpio_is_valid(slot->rst_n_gpio)) { + if (!gpio_request(slot->rst_n_gpio, "eMMC_reset")) { + gpio_direction_output(slot->rst_n_gpio, 1); + slot->host->mmc->caps |= MMC_CAP_HW_RESET; + } else { + dev_warn(&pdev->dev, "failed to request rst_n_gpio\n"); + slot->rst_n_gpio = -EINVAL; + } + } + + host->mmc->pm_caps = MMC_PM_KEEP_POWER | MMC_PM_WAKE_SDIO_IRQ; + + ret = sdhci_add_host(host); + if (ret) + goto remove; + + sdhci_pci_add_own_cd(slot); + + return slot; + +remove: + if (gpio_is_valid(slot->rst_n_gpio)) + gpio_free(slot->rst_n_gpio); + + if (chip->fixes && chip->fixes->remove_slot) + chip->fixes->remove_slot(slot, 0); + +unmap: + iounmap(host->ioaddr); + +release: + pci_release_region(pdev, bar); + +cleanup: + if (slot->data && slot->data->cleanup) + slot->data->cleanup(slot->data); + +free: + sdhci_free_host(host); + + return ERR_PTR(ret); +} + +static void sdhci_pci_remove_slot(struct sdhci_pci_slot *slot) +{ + int dead; + u32 scratch; + + sdhci_pci_remove_own_cd(slot); + + dead = 0; + scratch = readl(slot->host->ioaddr + SDHCI_INT_STATUS); + if (scratch == (u32)-1) + dead = 1; + + sdhci_remove_host(slot->host, dead); + + if (gpio_is_valid(slot->rst_n_gpio)) + gpio_free(slot->rst_n_gpio); + + if (slot->chip->fixes && slot->chip->fixes->remove_slot) + slot->chip->fixes->remove_slot(slot, dead); + + if (slot->data && slot->data->cleanup) + slot->data->cleanup(slot->data); + + pci_release_region(slot->chip->pdev, slot->pci_bar); + + sdhci_free_host(slot->host); +} + +static void __devinit sdhci_pci_runtime_pm_allow(struct device *dev) +{ + pm_runtime_put_noidle(dev); + pm_runtime_allow(dev); + pm_runtime_set_autosuspend_delay(dev, 50); + pm_runtime_use_autosuspend(dev); + pm_suspend_ignore_children(dev, 1); +} + +static void __devexit sdhci_pci_runtime_pm_forbid(struct device *dev) +{ + pm_runtime_forbid(dev); + pm_runtime_get_noresume(dev); +} + +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->quirks2 = chip->fixes->quirks2; + chip->allow_runtime_pm = chip->fixes->allow_runtime_pm; + } + 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; + } + + if (chip->allow_runtime_pm) + sdhci_pci_runtime_pm_allow(&pdev->dev); + + 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) { + if (chip->allow_runtime_pm) + sdhci_pci_runtime_pm_forbid(&pdev->dev); + + 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), + .driver = { + .pm = &sdhci_pci_pm_ops + }, +}; + +/*****************************************************************************\ + * * + * 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..c5c2a48b --- /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 + * + * Copyright (c) 2007, 2011 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 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/err.h> +#include <linux/module.h> +#include <linux/of.h> +#ifdef CONFIG_PPC +#include <asm/machdep.h> +#endif +#include "sdhci-pltfm.h" + +static struct sdhci_ops sdhci_pltfm_ops = { +}; + +#ifdef CONFIG_OF +static bool 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 /* CONFIG_PPC */ +} + +void sdhci_get_of_property(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct sdhci_host *host = platform_get_drvdata(pdev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + const __be32 *clk; + int size; + + if (of_device_is_available(np)) { + 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; + + if (of_device_is_compatible(np, "fsl,p2020-rev1-esdhc")) + host->quirks |= SDHCI_QUIRK_BROKEN_DMA; + + if (of_device_is_compatible(np, "fsl,p2020-esdhc") || + of_device_is_compatible(np, "fsl,p1010-esdhc") || + of_device_is_compatible(np, "fsl,mpc8536-esdhc")) + host->quirks |= SDHCI_QUIRK_BROKEN_TIMEOUT_VAL; + + clk = of_get_property(np, "clock-frequency", &size); + if (clk && size == sizeof(*clk) && *clk) + pltfm_host->clock = be32_to_cpup(clk); + } +} +#else +void sdhci_get_of_property(struct platform_device *pdev) {} +#endif /* CONFIG_OF */ +EXPORT_SYMBOL_GPL(sdhci_get_of_property); + +struct sdhci_host *sdhci_pltfm_init(struct platform_device *pdev, + struct sdhci_pltfm_data *pdata) +{ + struct sdhci_host *host; + struct sdhci_pltfm_host *pltfm_host; + struct device_node *np = pdev->dev.of_node; + struct resource *iomem; + int ret; + + 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!\n"); + + /* Some PCI-based MFD need the parent here */ + if (pdev->dev.parent != &platform_bus && !np) + 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 = dev_name(&pdev->dev); + 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; + } + + platform_set_drvdata(pdev, host); + + return host; + +err_remap: + release_mem_region(iomem->start, resource_size(iomem)); +err_request: + sdhci_free_host(host); +err: + dev_err(&pdev->dev, "%s failed %d\n", __func__, ret); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(sdhci_pltfm_init); + +void sdhci_pltfm_free(struct platform_device *pdev) +{ + struct sdhci_host *host = platform_get_drvdata(pdev); + struct resource *iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + iounmap(host->ioaddr); + release_mem_region(iomem->start, resource_size(iomem)); + sdhci_free_host(host); + platform_set_drvdata(pdev, NULL); +} +EXPORT_SYMBOL_GPL(sdhci_pltfm_free); + +int sdhci_pltfm_register(struct platform_device *pdev, + struct sdhci_pltfm_data *pdata) +{ + struct sdhci_host *host; + int ret = 0; + + host = sdhci_pltfm_init(pdev, pdata); + if (IS_ERR(host)) + return PTR_ERR(host); + + sdhci_get_of_property(pdev); + + ret = sdhci_add_host(host); + if (ret) + sdhci_pltfm_free(pdev); + + return ret; +} +EXPORT_SYMBOL_GPL(sdhci_pltfm_register); + +int sdhci_pltfm_unregister(struct platform_device *pdev) +{ + struct sdhci_host *host = platform_get_drvdata(pdev); + int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); + + sdhci_remove_host(host, dead); + sdhci_pltfm_free(pdev); + + return 0; +} +EXPORT_SYMBOL_GPL(sdhci_pltfm_unregister); + +#ifdef CONFIG_PM +static int sdhci_pltfm_suspend(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + + return sdhci_suspend_host(host); +} + +static int sdhci_pltfm_resume(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + + return sdhci_resume_host(host); +} + +const struct dev_pm_ops sdhci_pltfm_pmops = { + .suspend = sdhci_pltfm_suspend, + .resume = sdhci_pltfm_resume, +}; +EXPORT_SYMBOL_GPL(sdhci_pltfm_pmops); +#endif /* CONFIG_PM */ + +static int __init sdhci_pltfm_drv_init(void) +{ + pr_info("sdhci-pltfm: SDHCI platform and OF driver helper\n"); + + return 0; +} +module_init(sdhci_pltfm_drv_init); + +static void __exit sdhci_pltfm_drv_exit(void) +{ +} +module_exit(sdhci_pltfm_drv_exit); + +MODULE_DESCRIPTION("SDHCI platform and OF driver helper"); +MODULE_AUTHOR("Intel Corporation"); +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..37e0e184 --- /dev/null +++ b/drivers/mmc/host/sdhci-pltfm.h @@ -0,0 +1,108 @@ +/* + * 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/platform_device.h> +#include "sdhci.h" + +struct sdhci_pltfm_data { + struct sdhci_ops *ops; + unsigned int quirks; +}; + +struct sdhci_pltfm_host { + struct clk *clk; + void *priv; /* to handle quirks across io-accessor calls */ + + /* migrate from sdhci_of_host */ + unsigned int clock; + u16 xfer_mode_shadow; +}; + +#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. + */ +static inline u32 sdhci_be32bs_readl(struct sdhci_host *host, int reg) +{ + return in_be32(host->ioaddr + reg); +} + +static inline u16 sdhci_be32bs_readw(struct sdhci_host *host, int reg) +{ + return in_be16(host->ioaddr + (reg ^ 0x2)); +} + +static inline u8 sdhci_be32bs_readb(struct sdhci_host *host, int reg) +{ + return in_8(host->ioaddr + (reg ^ 0x3)); +} + +static inline void sdhci_be32bs_writel(struct sdhci_host *host, + u32 val, int reg) +{ + out_be32(host->ioaddr + reg, val); +} + +static inline void sdhci_be32bs_writew(struct sdhci_host *host, + u16 val, int reg) +{ + struct sdhci_pltfm_host *pltfm_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. + */ + pltfm_host->xfer_mode_shadow = val; + return; + case SDHCI_COMMAND: + sdhci_be32bs_writel(host, + val << 16 | pltfm_host->xfer_mode_shadow, + SDHCI_TRANSFER_MODE); + return; + } + clrsetbits_be32(host->ioaddr + base, 0xffff << shift, val << shift); +} + +static inline 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 */ + +extern void sdhci_get_of_property(struct platform_device *pdev); + +extern struct sdhci_host *sdhci_pltfm_init(struct platform_device *pdev, + struct sdhci_pltfm_data *pdata); +extern void sdhci_pltfm_free(struct platform_device *pdev); + +extern int sdhci_pltfm_register(struct platform_device *pdev, + struct sdhci_pltfm_data *pdata); +extern int sdhci_pltfm_unregister(struct platform_device *pdev); + +#ifdef CONFIG_PM +extern const struct dev_pm_ops sdhci_pltfm_pmops; +#define SDHCI_PLTFM_PMOPS (&sdhci_pltfm_pmops) +#else +#define SDHCI_PLTFM_PMOPS NULL +#endif + +#endif /* _DRIVERS_MMC_SDHCI_PLTFM_H */ diff --git a/drivers/mmc/host/sdhci-pxav2.c b/drivers/mmc/host/sdhci-pxav2.c new file mode 100644 index 00000000..dbb75bfb --- /dev/null +++ b/drivers/mmc/host/sdhci-pxav2.c @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2010 Marvell International Ltd. + * Zhangfei Gao <zhangfei.gao@marvell.com> + * Kevin Wang <dwang4@marvell.com> + * Jun Nie <njun@marvell.com> + * Qiming Wu <wuqm@marvell.com> + * Philip Rakity <prakity@marvell.com> + * + * 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/module.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/mmc/card.h> +#include <linux/mmc/host.h> +#include <linux/platform_data/pxa_sdhci.h> +#include <linux/slab.h> +#include "sdhci.h" +#include "sdhci-pltfm.h" + +#define SD_FIFO_PARAM 0xe0 +#define DIS_PAD_SD_CLK_GATE 0x0400 /* Turn on/off Dynamic SD Clock Gating */ +#define CLK_GATE_ON 0x0200 /* Disable/enable Clock Gate */ +#define CLK_GATE_CTL 0x0100 /* Clock Gate Control */ +#define CLK_GATE_SETTING_BITS (DIS_PAD_SD_CLK_GATE | \ + CLK_GATE_ON | CLK_GATE_CTL) + +#define SD_CLOCK_BURST_SIZE_SETUP 0xe6 +#define SDCLK_SEL_SHIFT 8 +#define SDCLK_SEL_MASK 0x3 +#define SDCLK_DELAY_SHIFT 10 +#define SDCLK_DELAY_MASK 0x3c + +#define SD_CE_ATA_2 0xea +#define MMC_CARD 0x1000 +#define MMC_WIDTH 0x0100 + +static void pxav2_set_private_registers(struct sdhci_host *host, u8 mask) +{ + struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc)); + struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data; + + if (mask == SDHCI_RESET_ALL) { + u16 tmp = 0; + + /* + * tune timing of read data/command when crc error happen + * no performance impact + */ + if (pdata && pdata->clk_delay_sel == 1) { + tmp = readw(host->ioaddr + SD_CLOCK_BURST_SIZE_SETUP); + + tmp &= ~(SDCLK_DELAY_MASK << SDCLK_DELAY_SHIFT); + tmp |= (pdata->clk_delay_cycles & SDCLK_DELAY_MASK) + << SDCLK_DELAY_SHIFT; + tmp &= ~(SDCLK_SEL_MASK << SDCLK_SEL_SHIFT); + tmp |= (1 & SDCLK_SEL_MASK) << SDCLK_SEL_SHIFT; + + writew(tmp, host->ioaddr + SD_CLOCK_BURST_SIZE_SETUP); + } + + if (pdata && (pdata->flags & PXA_FLAG_ENABLE_CLOCK_GATING)) { + tmp = readw(host->ioaddr + SD_FIFO_PARAM); + tmp &= ~CLK_GATE_SETTING_BITS; + writew(tmp, host->ioaddr + SD_FIFO_PARAM); + } else { + tmp = readw(host->ioaddr + SD_FIFO_PARAM); + tmp &= ~CLK_GATE_SETTING_BITS; + tmp |= CLK_GATE_SETTING_BITS; + writew(tmp, host->ioaddr + SD_FIFO_PARAM); + } + } +} + +static int pxav2_mmc_set_width(struct sdhci_host *host, int width) +{ + u8 ctrl; + u16 tmp; + + ctrl = readb(host->ioaddr + SDHCI_HOST_CONTROL); + tmp = readw(host->ioaddr + SD_CE_ATA_2); + if (width == MMC_BUS_WIDTH_8) { + ctrl &= ~SDHCI_CTRL_4BITBUS; + tmp |= MMC_CARD | MMC_WIDTH; + } else { + tmp &= ~(MMC_CARD | MMC_WIDTH); + if (width == MMC_BUS_WIDTH_4) + ctrl |= SDHCI_CTRL_4BITBUS; + else + ctrl &= ~SDHCI_CTRL_4BITBUS; + } + writew(tmp, host->ioaddr + SD_CE_ATA_2); + writeb(ctrl, host->ioaddr + SDHCI_HOST_CONTROL); + + return 0; +} + +static u32 pxav2_get_max_clock(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + + return clk_get_rate(pltfm_host->clk); +} + +static struct sdhci_ops pxav2_sdhci_ops = { + .get_max_clock = pxav2_get_max_clock, + .platform_reset_exit = pxav2_set_private_registers, + .platform_8bit_width = pxav2_mmc_set_width, +}; + +static int __devinit sdhci_pxav2_probe(struct platform_device *pdev) +{ + struct sdhci_pltfm_host *pltfm_host; + struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data; + struct device *dev = &pdev->dev; + struct sdhci_host *host = NULL; + struct sdhci_pxa *pxa = NULL; + int ret; + struct clk *clk; + + pxa = kzalloc(sizeof(struct sdhci_pxa), GFP_KERNEL); + if (!pxa) + return -ENOMEM; + + host = sdhci_pltfm_init(pdev, NULL); + if (IS_ERR(host)) { + kfree(pxa); + return PTR_ERR(host); + } + pltfm_host = sdhci_priv(host); + pltfm_host->priv = pxa; + + clk = clk_get(dev, "PXA-SDHCLK"); + if (IS_ERR(clk)) { + dev_err(dev, "failed to get io clock\n"); + ret = PTR_ERR(clk); + goto err_clk_get; + } + pltfm_host->clk = clk; + clk_enable(clk); + + host->quirks = SDHCI_QUIRK_BROKEN_ADMA + | SDHCI_QUIRK_BROKEN_TIMEOUT_VAL + | SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN; + + if (pdata) { + if (pdata->flags & PXA_FLAG_CARD_PERMANENT) { + /* on-chip device */ + host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; + host->mmc->caps |= MMC_CAP_NONREMOVABLE; + } + + /* 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; + + if (pdata->quirks) + host->quirks |= pdata->quirks; + if (pdata->host_caps) + host->mmc->caps |= pdata->host_caps; + if (pdata->pm_caps) + host->mmc->pm_caps |= pdata->pm_caps; + } + + host->ops = &pxav2_sdhci_ops; + + ret = sdhci_add_host(host); + if (ret) { + dev_err(&pdev->dev, "failed to add host\n"); + goto err_add_host; + } + + platform_set_drvdata(pdev, host); + + return 0; + +err_add_host: + clk_disable(clk); + clk_put(clk); +err_clk_get: + sdhci_pltfm_free(pdev); + kfree(pxa); + return ret; +} + +static int __devexit sdhci_pxav2_remove(struct platform_device *pdev) +{ + struct sdhci_host *host = platform_get_drvdata(pdev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_pxa *pxa = pltfm_host->priv; + + sdhci_remove_host(host, 1); + + clk_disable(pltfm_host->clk); + clk_put(pltfm_host->clk); + sdhci_pltfm_free(pdev); + kfree(pxa); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver sdhci_pxav2_driver = { + .driver = { + .name = "sdhci-pxav2", + .owner = THIS_MODULE, + .pm = SDHCI_PLTFM_PMOPS, + }, + .probe = sdhci_pxav2_probe, + .remove = __devexit_p(sdhci_pxav2_remove), +}; + +module_platform_driver(sdhci_pxav2_driver); + +MODULE_DESCRIPTION("SDHCI driver for pxav2"); +MODULE_AUTHOR("Marvell International Ltd."); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/mmc/host/sdhci-pxav3.c b/drivers/mmc/host/sdhci-pxav3.c new file mode 100644 index 00000000..f2969568 --- /dev/null +++ b/drivers/mmc/host/sdhci-pxav3.c @@ -0,0 +1,278 @@ +/* + * 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 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 <linux/platform_data/pxa_sdhci.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/module.h> +#include "sdhci.h" +#include "sdhci-pltfm.h" + +#define SD_CLOCK_BURST_SIZE_SETUP 0x10A +#define SDCLK_SEL 0x100 +#define SDCLK_DELAY_SHIFT 9 +#define SDCLK_DELAY_MASK 0x1f + +#define SD_CFG_FIFO_PARAM 0x100 +#define SDCFG_GEN_PAD_CLK_ON (1<<6) +#define SDCFG_GEN_PAD_CLK_CNT_MASK 0xFF +#define SDCFG_GEN_PAD_CLK_CNT_SHIFT 24 + +#define SD_SPI_MODE 0x108 +#define SD_CE_ATA_1 0x10C + +#define SD_CE_ATA_2 0x10E +#define SDCE_MISC_INT (1<<2) +#define SDCE_MISC_INT_EN (1<<1) + +static void pxav3_set_private_registers(struct sdhci_host *host, u8 mask) +{ + struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc)); + struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data; + + if (mask == SDHCI_RESET_ALL) { + /* + * tune timing of read data/command when crc error happen + * no performance impact + */ + if (pdata && 0 != pdata->clk_delay_cycles) { + u16 tmp; + + tmp = readw(host->ioaddr + SD_CLOCK_BURST_SIZE_SETUP); + tmp |= (pdata->clk_delay_cycles & SDCLK_DELAY_MASK) + << SDCLK_DELAY_SHIFT; + tmp |= SDCLK_SEL; + writew(tmp, host->ioaddr + SD_CLOCK_BURST_SIZE_SETUP); + } + } +} + +#define MAX_WAIT_COUNT 5 +static void pxav3_gen_init_74_clocks(struct sdhci_host *host, u8 power_mode) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_pxa *pxa = pltfm_host->priv; + u16 tmp; + int count; + + if (pxa->power_mode == MMC_POWER_UP + && power_mode == MMC_POWER_ON) { + + dev_dbg(mmc_dev(host->mmc), + "%s: slot->power_mode = %d," + "ios->power_mode = %d\n", + __func__, + pxa->power_mode, + power_mode); + + /* set we want notice of when 74 clocks are sent */ + tmp = readw(host->ioaddr + SD_CE_ATA_2); + tmp |= SDCE_MISC_INT_EN; + writew(tmp, host->ioaddr + SD_CE_ATA_2); + + /* start sending the 74 clocks */ + tmp = readw(host->ioaddr + SD_CFG_FIFO_PARAM); + tmp |= SDCFG_GEN_PAD_CLK_ON; + writew(tmp, host->ioaddr + SD_CFG_FIFO_PARAM); + + /* slowest speed is about 100KHz or 10usec per clock */ + udelay(740); + count = 0; + + while (count++ < MAX_WAIT_COUNT) { + if ((readw(host->ioaddr + SD_CE_ATA_2) + & SDCE_MISC_INT) == 0) + break; + udelay(10); + } + + if (count == MAX_WAIT_COUNT) + dev_warn(mmc_dev(host->mmc), "74 clock interrupt not cleared\n"); + + /* clear the interrupt bit if posted */ + tmp = readw(host->ioaddr + SD_CE_ATA_2); + tmp |= SDCE_MISC_INT; + writew(tmp, host->ioaddr + SD_CE_ATA_2); + } + pxa->power_mode = power_mode; +} + +static int pxav3_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); + dev_dbg(mmc_dev(host->mmc), + "%s uhs = %d, ctrl_2 = %04X\n", + __func__, uhs, ctrl_2); + + return 0; +} + +static struct sdhci_ops pxav3_sdhci_ops = { + .platform_reset_exit = pxav3_set_private_registers, + .set_uhs_signaling = pxav3_set_uhs_signaling, + .platform_send_init_74_clocks = pxav3_gen_init_74_clocks, +}; + +static int __devinit sdhci_pxav3_probe(struct platform_device *pdev) +{ + struct sdhci_pltfm_host *pltfm_host; + struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data; + struct device *dev = &pdev->dev; + struct sdhci_host *host = NULL; + struct sdhci_pxa *pxa = NULL; + int ret; + struct clk *clk; + + pxa = kzalloc(sizeof(struct sdhci_pxa), GFP_KERNEL); + if (!pxa) + return -ENOMEM; + + host = sdhci_pltfm_init(pdev, NULL); + if (IS_ERR(host)) { + kfree(pxa); + return PTR_ERR(host); + } + pltfm_host = sdhci_priv(host); + pltfm_host->priv = pxa; + + clk = clk_get(dev, "PXA-SDHCLK"); + if (IS_ERR(clk)) { + dev_err(dev, "failed to get io clock\n"); + ret = PTR_ERR(clk); + goto err_clk_get; + } + pltfm_host->clk = clk; + clk_enable(clk); + + host->quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL + | SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC + | SDHCI_QUIRK_32BIT_ADMA_SIZE; + + /* enable 1/8V DDR capable */ + host->mmc->caps |= MMC_CAP_1_8V_DDR; + + if (pdata) { + if (pdata->flags & PXA_FLAG_CARD_PERMANENT) { + /* on-chip device */ + host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; + host->mmc->caps |= MMC_CAP_NONREMOVABLE; + } + + /* 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; + + if (pdata->quirks) + host->quirks |= pdata->quirks; + if (pdata->host_caps) + host->mmc->caps |= pdata->host_caps; + if (pdata->pm_caps) + host->mmc->pm_caps |= pdata->pm_caps; + } + + host->ops = &pxav3_sdhci_ops; + + ret = sdhci_add_host(host); + if (ret) { + dev_err(&pdev->dev, "failed to add host\n"); + goto err_add_host; + } + + platform_set_drvdata(pdev, host); + + return 0; + +err_add_host: + clk_disable(clk); + clk_put(clk); +err_clk_get: + sdhci_pltfm_free(pdev); + kfree(pxa); + return ret; +} + +static int __devexit sdhci_pxav3_remove(struct platform_device *pdev) +{ + struct sdhci_host *host = platform_get_drvdata(pdev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_pxa *pxa = pltfm_host->priv; + + sdhci_remove_host(host, 1); + + clk_disable(pltfm_host->clk); + clk_put(pltfm_host->clk); + sdhci_pltfm_free(pdev); + kfree(pxa); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver sdhci_pxav3_driver = { + .driver = { + .name = "sdhci-pxav3", + .owner = THIS_MODULE, + .pm = SDHCI_PLTFM_PMOPS, + }, + .probe = sdhci_pxav3_probe, + .remove = __devexit_p(sdhci_pxav3_remove), +}; + +module_platform_driver(sdhci_pxav3_driver); + +MODULE_DESCRIPTION("SDHCI driver for pxav3"); +MODULE_AUTHOR("Marvell International Ltd."); +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..55a164fc --- /dev/null +++ b/drivers/mmc/host/sdhci-s3c.c @@ -0,0 +1,756 @@ +/* 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/module.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/pm.h> +#include <linux/pm_runtime.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]; +}; + +/** + * struct sdhci_s3c_driver_data - S3C SDHCI platform specific driver data + * @sdhci_quirks: sdhci host specific quirks. + * + * Specifies platform specific configuration of sdhci controller. + * Note: A structure for driver specific platform data is used for future + * expansion of its usage. + */ +struct sdhci_s3c_drv_data { + unsigned int sdhci_quirks; +}; + +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 + S3C_SDHCI_CONTROL2); + } +} + +/** + * 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; + + /* + * If controller uses a non-standard clock division, find the best clock + * speed possible with selected clock source and skip the division. + */ + if (ourhost->host->quirks & SDHCI_QUIRK_NONSTANDARD_CLOCK) { + 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); + } + + /* reprogram default hardware configuration */ + writel(S3C64XX_SDHCI_CONTROL4_DRIVE_9mA, + host->ioaddr + S3C64XX_SDHCI_CONTROL4); + + ctrl = readl(host->ioaddr + S3C_SDHCI_CONTROL2); + ctrl |= (S3C64XX_SDHCI_CTRL2_ENSTAASYNCCLR | + S3C64XX_SDHCI_CTRL2_ENCMDCNFMSK | + S3C_SDHCI_CTRL2_ENFBCLKRX | + S3C_SDHCI_CTRL2_DFCNT_NONE | + S3C_SDHCI_CTRL2_ENCLKOUTHOLD); + writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL2); + + /* reconfigure the controller for new clock rate */ + ctrl = (S3C_SDHCI_CTRL3_FCSEL1 | S3C_SDHCI_CTRL3_FCSEL0); + if (clock < 25 * 1000000) + ctrl |= (S3C_SDHCI_CTRL3_FCSEL3 | S3C_SDHCI_CTRL3_FCSEL2); + writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL3); +} + +/** + * 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); + unsigned long timeout; + u16 clk = 0; + + /* 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; + + 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)); + return; + } + timeout--; + mdelay(1); + } + + clk |= SDHCI_CLOCK_CARD_EN; + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); +} + +/** + * 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 inline struct sdhci_s3c_drv_data *sdhci_s3c_get_driver_data( + struct platform_device *pdev) +{ + return (struct sdhci_s3c_drv_data *) + platform_get_device_id(pdev)->driver_data; +} + +static int __devinit sdhci_s3c_probe(struct platform_device *pdev) +{ + struct s3c_sdhci_platdata *pdata; + struct sdhci_s3c_drv_data *drv_data; + struct device *dev = &pdev->dev; + struct sdhci_host *host; + struct sdhci_s3c *sc; + struct resource *res; + int ret, irq, ptr, clks; + + if (!pdev->dev.platform_data) { + 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; + } + + 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); + } + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + ret = -ENOMEM; + goto err_io_clk; + } + memcpy(pdata, pdev->dev.platform_data, sizeof(*pdata)); + + drv_data = sdhci_s3c_get_driver_data(pdev); + 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[14]; + + snprintf(name, 14, "mmc_busclk.%d", ptr); + clk = clk_get(dev, name); + if (IS_ERR(clk)) { + 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; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + host->ioaddr = devm_request_and_ioremap(&pdev->dev, 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; + if (drv_data) + host->quirks |= drv_data->sdhci_quirks; + +#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; + + /* Samsung SoCs need BROKEN_ADMA_ZEROLEN_DESC */ + host->quirks |= SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC; + + 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; + + switch (pdata->max_width) { + case 8: + host->mmc->caps |= MMC_CAP_8_BIT_DATA; + case 4: + host->mmc->caps |= MMC_CAP_4_BIT_DATA; + break; + } + + if (pdata->pm_caps) + host->mmc->pm_caps |= pdata->pm_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 (host->quirks & SDHCI_QUIRK_NONSTANDARD_CLOCK) { + 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; + + if (pdata->host_caps2) + host->mmc->caps2 |= pdata->host_caps2; + + pm_runtime_enable(&pdev->dev); + pm_runtime_set_autosuspend_delay(&pdev->dev, 50); + pm_runtime_use_autosuspend(&pdev->dev); + pm_suspend_ignore_children(&pdev->dev, 1); + + ret = sdhci_add_host(host); + if (ret) { + dev_err(dev, "sdhci_add_host() failed\n"); + pm_runtime_forbid(&pdev->dev); + pm_runtime_get_noresume(&pdev->dev); + goto err_req_regs; + } + + /* 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_req_regs: + for (ptr = 0; ptr < MAX_BUS_CLK; ptr++) { + if (sc->clk_bus[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); + + pm_runtime_disable(&pdev->dev); + + 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); + + sdhci_free_host(host); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int sdhci_s3c_suspend(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + + return sdhci_suspend_host(host); +} + +static int sdhci_s3c_resume(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + + return sdhci_resume_host(host); +} +#endif + +#ifdef CONFIG_PM_RUNTIME +static int sdhci_s3c_runtime_suspend(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + + return sdhci_runtime_suspend_host(host); +} + +static int sdhci_s3c_runtime_resume(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + + return sdhci_runtime_resume_host(host); +} +#endif + +#ifdef CONFIG_PM +static const struct dev_pm_ops sdhci_s3c_pmops = { + SET_SYSTEM_SLEEP_PM_OPS(sdhci_s3c_suspend, sdhci_s3c_resume) + SET_RUNTIME_PM_OPS(sdhci_s3c_runtime_suspend, sdhci_s3c_runtime_resume, + NULL) +}; + +#define SDHCI_S3C_PMOPS (&sdhci_s3c_pmops) + +#else +#define SDHCI_S3C_PMOPS NULL +#endif + +#if defined(CONFIG_CPU_EXYNOS4210) || defined(CONFIG_SOC_EXYNOS4212) +static struct sdhci_s3c_drv_data exynos4_sdhci_drv_data = { + .sdhci_quirks = SDHCI_QUIRK_NONSTANDARD_CLOCK, +}; +#define EXYNOS4_SDHCI_DRV_DATA ((kernel_ulong_t)&exynos4_sdhci_drv_data) +#else +#define EXYNOS4_SDHCI_DRV_DATA ((kernel_ulong_t)NULL) +#endif + +static struct platform_device_id sdhci_s3c_driver_ids[] = { + { + .name = "s3c-sdhci", + .driver_data = (kernel_ulong_t)NULL, + }, { + .name = "exynos4-sdhci", + .driver_data = EXYNOS4_SDHCI_DRV_DATA, + }, + { } +}; +MODULE_DEVICE_TABLE(platform, sdhci_s3c_driver_ids); + +static struct platform_driver sdhci_s3c_driver = { + .probe = sdhci_s3c_probe, + .remove = __devexit_p(sdhci_s3c_remove), + .id_table = sdhci_s3c_driver_ids, + .driver = { + .owner = THIS_MODULE, + .name = "s3c-sdhci", + .pm = SDHCI_S3C_PMOPS, + }, +}; + +module_platform_driver(sdhci_s3c_driver); + +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..6dfa82e0 --- /dev/null +++ b/drivers/mmc/host/sdhci-spear.c @@ -0,0 +1,321 @@ +/* + * 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/module.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/platform_device.h> +#include <linux/pm.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; + } + } + + 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; +} + +#ifdef CONFIG_PM +static int sdhci_suspend(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct spear_sdhci *sdhci = dev_get_platdata(dev); + int ret; + + ret = sdhci_suspend_host(host); + if (!ret) + clk_disable(sdhci->clk); + + return ret; +} + +static int sdhci_resume(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct spear_sdhci *sdhci = dev_get_platdata(dev); + int ret; + + ret = clk_enable(sdhci->clk); + if (ret) { + dev_dbg(dev, "Resume: Error enabling clock\n"); + return ret; + } + + return sdhci_resume_host(host); +} +#endif + +static SIMPLE_DEV_PM_OPS(sdhci_pm_ops, sdhci_suspend, sdhci_resume); + +static struct platform_driver sdhci_driver = { + .driver = { + .name = "sdhci", + .owner = THIS_MODULE, + .pm = &sdhci_pm_ops, + }, + .probe = sdhci_probe, + .remove = __devexit_p(sdhci_remove), +}; + +module_platform_driver(sdhci_driver); + +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..53b26502 --- /dev/null +++ b/drivers/mmc/host/sdhci-tegra.c @@ -0,0 +1,404 @@ +/* + * 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/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/gpio.h> +#include <linux/mmc/card.h> +#include <linux/mmc/host.h> + +#include <asm/gpio.h> + +#include <mach/gpio-tegra.h> +#include <mach/sdhci.h> + +#include "sdhci-pltfm.h" + +#define NVQUIRK_FORCE_SDHCI_SPEC_200 BIT(0) +#define NVQUIRK_ENABLE_BLOCK_GAP_DET BIT(1) + +struct sdhci_tegra_soc_data { + struct sdhci_pltfm_data *pdata; + u32 nvquirks; +}; + +struct sdhci_tegra { + const struct tegra_sdhci_platform_data *plat; + const struct sdhci_tegra_soc_data *soc_data; +}; + +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) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_tegra *tegra_host = pltfm_host->priv; + const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; + + if (unlikely((soc_data->nvquirks & NVQUIRK_FORCE_SDHCI_SPEC_200) && + (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) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_tegra *tegra_host = pltfm_host->priv; + const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; + + /* 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((soc_data->nvquirks & NVQUIRK_ENABLE_BLOCK_GAP_DET) && + (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 *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_tegra *tegra_host = pltfm_host->priv; + const struct tegra_sdhci_platform_data *plat = tegra_host->plat; + + 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 sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_tegra *tegra_host = pltfm_host->priv; + const struct tegra_sdhci_platform_data *plat = tegra_host->plat; + u32 ctrl; + + 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 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, +}; + +#ifdef CONFIG_ARCH_TEGRA_2x_SOC +static struct sdhci_pltfm_data sdhci_tegra20_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, +}; + +static struct sdhci_tegra_soc_data soc_data_tegra20 = { + .pdata = &sdhci_tegra20_pdata, + .nvquirks = NVQUIRK_FORCE_SDHCI_SPEC_200 | + NVQUIRK_ENABLE_BLOCK_GAP_DET, +}; +#endif + +#ifdef CONFIG_ARCH_TEGRA_3x_SOC +static struct sdhci_pltfm_data sdhci_tegra30_pdata = { + .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | + SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | + SDHCI_QUIRK_SINGLE_POWER_WRITE | + SDHCI_QUIRK_NO_HISPD_BIT | + SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC, + .ops = &tegra_sdhci_ops, +}; + +static struct sdhci_tegra_soc_data soc_data_tegra30 = { + .pdata = &sdhci_tegra30_pdata, +}; +#endif + +static const struct of_device_id sdhci_tegra_dt_match[] __devinitdata = { +#ifdef CONFIG_ARCH_TEGRA_3x_SOC + { .compatible = "nvidia,tegra30-sdhci", .data = &soc_data_tegra30 }, +#endif +#ifdef CONFIG_ARCH_TEGRA_2x_SOC + { .compatible = "nvidia,tegra20-sdhci", .data = &soc_data_tegra20 }, +#endif + {} +}; +MODULE_DEVICE_TABLE(of, sdhci_dt_ids); + +static struct tegra_sdhci_platform_data * __devinit sdhci_tegra_dt_parse_pdata( + struct platform_device *pdev) +{ + struct tegra_sdhci_platform_data *plat; + struct device_node *np = pdev->dev.of_node; + + if (!np) + return NULL; + + plat = devm_kzalloc(&pdev->dev, sizeof(*plat), GFP_KERNEL); + if (!plat) { + dev_err(&pdev->dev, "Can't allocate platform data\n"); + return NULL; + } + + plat->cd_gpio = of_get_named_gpio(np, "cd-gpios", 0); + plat->wp_gpio = of_get_named_gpio(np, "wp-gpios", 0); + plat->power_gpio = of_get_named_gpio(np, "power-gpios", 0); + if (of_find_property(np, "support-8bit", NULL)) + plat->is_8bit = 1; + + return plat; +} + +static int __devinit sdhci_tegra_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + const struct sdhci_tegra_soc_data *soc_data; + struct sdhci_host *host; + struct sdhci_pltfm_host *pltfm_host; + struct tegra_sdhci_platform_data *plat; + struct sdhci_tegra *tegra_host; + struct clk *clk; + int rc; + + match = of_match_device(sdhci_tegra_dt_match, &pdev->dev); + if (match) + soc_data = match->data; + else + soc_data = &soc_data_tegra20; + + host = sdhci_pltfm_init(pdev, soc_data->pdata); + if (IS_ERR(host)) + return PTR_ERR(host); + + pltfm_host = sdhci_priv(host); + + plat = pdev->dev.platform_data; + + if (plat == NULL) + plat = sdhci_tegra_dt_parse_pdata(pdev); + + if (plat == NULL) { + dev_err(mmc_dev(host->mmc), "missing platform data\n"); + rc = -ENXIO; + goto err_no_plat; + } + + tegra_host = devm_kzalloc(&pdev->dev, sizeof(*tegra_host), GFP_KERNEL); + if (!tegra_host) { + dev_err(mmc_dev(host->mmc), "failed to allocate tegra_host\n"); + rc = -ENOMEM; + goto err_no_plat; + } + + tegra_host->plat = plat; + tegra_host->soc_data = soc_data; + + pltfm_host->priv = tegra_host; + + 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 err_power_req; + } + 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 err_cd_req; + } + 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 err_cd_irq_req; + } + + } + + 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 err_wp_req; + } + 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 err_clk_get; + } + 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; + + rc = sdhci_add_host(host); + if (rc) + goto err_add_host; + + return 0; + +err_add_host: + clk_disable(pltfm_host->clk); + clk_put(pltfm_host->clk); +err_clk_get: + if (gpio_is_valid(plat->wp_gpio)) { + tegra_gpio_disable(plat->wp_gpio); + gpio_free(plat->wp_gpio); + } +err_wp_req: + if (gpio_is_valid(plat->cd_gpio)) + free_irq(gpio_to_irq(plat->cd_gpio), host); +err_cd_irq_req: + if (gpio_is_valid(plat->cd_gpio)) { + tegra_gpio_disable(plat->cd_gpio); + gpio_free(plat->cd_gpio); + } +err_cd_req: + if (gpio_is_valid(plat->power_gpio)) { + tegra_gpio_disable(plat->power_gpio); + gpio_free(plat->power_gpio); + } +err_power_req: +err_no_plat: + sdhci_pltfm_free(pdev); + return rc; +} + +static int __devexit sdhci_tegra_remove(struct platform_device *pdev) +{ + struct sdhci_host *host = platform_get_drvdata(pdev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_tegra *tegra_host = pltfm_host->priv; + const struct tegra_sdhci_platform_data *plat = tegra_host->plat; + int dead = (readl(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); + + sdhci_remove_host(host, dead); + + 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); + + sdhci_pltfm_free(pdev); + + return 0; +} + +static struct platform_driver sdhci_tegra_driver = { + .driver = { + .name = "sdhci-tegra", + .owner = THIS_MODULE, + .of_match_table = sdhci_tegra_dt_match, + .pm = SDHCI_PLTFM_PMOPS, + }, + .probe = sdhci_tegra_probe, + .remove = __devexit_p(sdhci_tegra_remove), +}; + +module_platform_driver(sdhci_tegra_driver); + +MODULE_DESCRIPTION("SDHCI driver for Tegra"); +MODULE_AUTHOR("Google, Inc."); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c new file mode 100644 index 00000000..37601ec6 --- /dev/null +++ b/drivers/mmc/host/sdhci.c @@ -0,0 +1,3136 @@ +/* + * linux/drivers/mmc/host/sdhci.c - Secure Digital Host Controller Interface driver + * + * 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/io.h> +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <linux/scatterlist.h> +#include <linux/regulator/consumer.h> +#include <linux/pm_runtime.h> + +#include <linux/leds.h> + +#include <linux/mmc/mmc.h> +#include <linux/mmc/host.h> + +#include "sdhci.h" + +#define DRIVER_NAME "sdhci" + +#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)) +#define SDHCI_USE_LEDS_CLASS +#endif + +#define MAX_TUNING_LOOP 40 + +static unsigned int debug_quirks = 0; +static unsigned int debug_quirks2; + +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, u32 opcode); +static void sdhci_tuning_timer(unsigned long data); + +#ifdef CONFIG_PM_RUNTIME +static int sdhci_runtime_pm_get(struct sdhci_host *host); +static int sdhci_runtime_pm_put(struct sdhci_host *host); +#else +static inline int sdhci_runtime_pm_get(struct sdhci_host *host) +{ + return 0; +} +static inline int sdhci_runtime_pm_put(struct sdhci_host *host) +{ + return 0; +} +#endif + +static void sdhci_dumpregs(struct sdhci_host *host) +{ + pr_debug(DRIVER_NAME ": =========== REGISTER DUMP (%s)===========\n", + mmc_hostname(host->mmc)); + + pr_debug(DRIVER_NAME ": Sys addr: 0x%08x | Version: 0x%08x\n", + sdhci_readl(host, SDHCI_DMA_ADDRESS), + sdhci_readw(host, SDHCI_HOST_VERSION)); + pr_debug(DRIVER_NAME ": Blk size: 0x%08x | Blk cnt: 0x%08x\n", + sdhci_readw(host, SDHCI_BLOCK_SIZE), + sdhci_readw(host, SDHCI_BLOCK_COUNT)); + pr_debug(DRIVER_NAME ": Argument: 0x%08x | Trn mode: 0x%08x\n", + sdhci_readl(host, SDHCI_ARGUMENT), + sdhci_readw(host, SDHCI_TRANSFER_MODE)); + pr_debug(DRIVER_NAME ": Present: 0x%08x | Host ctl: 0x%08x\n", + sdhci_readl(host, SDHCI_PRESENT_STATE), + sdhci_readb(host, SDHCI_HOST_CONTROL)); + pr_debug(DRIVER_NAME ": Power: 0x%08x | Blk gap: 0x%08x\n", + sdhci_readb(host, SDHCI_POWER_CONTROL), + sdhci_readb(host, SDHCI_BLOCK_GAP_CONTROL)); + pr_debug(DRIVER_NAME ": Wake-up: 0x%08x | Clock: 0x%08x\n", + sdhci_readb(host, SDHCI_WAKE_UP_CONTROL), + sdhci_readw(host, SDHCI_CLOCK_CONTROL)); + pr_debug(DRIVER_NAME ": Timeout: 0x%08x | Int stat: 0x%08x\n", + sdhci_readb(host, SDHCI_TIMEOUT_CONTROL), + sdhci_readl(host, SDHCI_INT_STATUS)); + pr_debug(DRIVER_NAME ": Int enab: 0x%08x | Sig enab: 0x%08x\n", + sdhci_readl(host, SDHCI_INT_ENABLE), + sdhci_readl(host, SDHCI_SIGNAL_ENABLE)); + pr_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)); + pr_debug(DRIVER_NAME ": Caps: 0x%08x | Caps_1: 0x%08x\n", + sdhci_readl(host, SDHCI_CAPABILITIES), + sdhci_readl(host, SDHCI_CAPABILITIES_1)); + pr_debug(DRIVER_NAME ": Cmd: 0x%08x | Max curr: 0x%08x\n", + sdhci_readw(host, SDHCI_COMMAND), + sdhci_readl(host, SDHCI_MAX_CURRENT)); + pr_debug(DRIVER_NAME ": Host ctl2: 0x%08x\n", + sdhci_readw(host, SDHCI_HOST_CONTROL2)); + + if (host->flags & SDHCI_USE_ADMA) + pr_debug(DRIVER_NAME ": ADMA Err: 0x%08x | ADMA Ptr: 0x%08x\n", + readl(host->ioaddr + SDHCI_ADMA_ERROR), + readl(host->ioaddr + SDHCI_ADMA_ADDRESS)); + + pr_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 present, irqs; + + if ((host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION) || + (host->mmc->caps & MMC_CAP_NONREMOVABLE)) + return; + + present = sdhci_readl(host, SDHCI_PRESENT_STATE) & + SDHCI_CARD_PRESENT; + irqs = present ? SDHCI_INT_CARD_REMOVE : SDHCI_INT_CARD_INSERT; + + 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) { + pr_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); + + if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) { + if ((host->ops->enable_dma) && (mask & SDHCI_RESET_ALL)) + host->ops->enable_dma(host); + } +} + +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 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; + + spin_lock_irqsave(&host->lock, flags); + + if (host->runtime_suspended) + goto out; + + if (brightness == LED_OFF) + sdhci_deactivate_led(host); + else + sdhci_activate_led(host); +out: + spin_unlock_irqrestore(&host->lock, flags); +} +#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)) + sg->offset; +} + +static void sdhci_kunmap_atomic(void *buffer, unsigned long *flags) +{ + kunmap_atomic(buffer); + 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 = dma_map_sg(mmc_dev(host->mmc), + data->sg, data->sg_len, direction); + if (host->sg_count == 0) + goto unmap_align; + + desc = host->adma_desc; + align = host->align_buffer; + + align_addr = host->align_addr; + + for_each_sg(data->sg, sg, host->sg_count, i) { + addr = sg_dma_address(sg); + len = sg_dma_len(sg); + + /* + * The SDHCI specification states that ADMA + * addresses must be 32-bit aligned. If they + * aren't, then we use a bounce buffer for + * the (up to three) bytes that screw up the + * alignment. + */ + offset = (4 - (addr & 0x3)) & 0x3; + if (offset) { + if (data->flags & MMC_DATA_WRITE) { + buffer = sdhci_kmap_atomic(sg, &flags); + 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; + } + } + } + + 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; + if (host->clock) + target_timeout += data->timeout_clks / host->clock; + } + + /* + * 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 + */ + 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) + 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 = dma_map_sg(mmc_dev(host->mmc), + data->sg, data->sg_len, + (data->flags & MMC_DATA_READ) ? + DMA_FROM_DEVICE : + DMA_TO_DEVICE); + 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 { + 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) { + pr_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)) { + pr_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 || + cmd->opcode == MMC_SEND_TUNING_BLOCK_HS200) + 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 */ + int real_div = div, clk_mul = 1; + u16 clk = 0; + unsigned long timeout; + + if (clock && clock == host->clock) + return; + + host->mmc->actual_clock = 0; + + 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; + real_div = div; + clk_mul = host->clk_mul; + 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; + } + } + real_div = div; + 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; + } + real_div = div; + div >>= 1; + } + + if (real_div) + host->mmc->actual_clock = (host->max_clk * clk_mul) / real_div; + + 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) { + pr_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 int 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 -1; + + host->pwr = pwr; + + if (pwr == 0) { + sdhci_writeb(host, 0, SDHCI_POWER_CONTROL); + return 0; + } + + /* + * 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); + + return power; +} + +/*****************************************************************************\ + * * + * MMC callbacks * + * * +\*****************************************************************************/ + +static 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_runtime_pm_get(host); + + spin_lock_irqsave(&host->lock, flags); + + WARN_ON(host->mrq != NULL); + +#ifndef 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, mrq->cmd->opcode); + 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); +} + +static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) +{ + unsigned long flags; + int vdd_bit = -1; + u8 ctrl; + + spin_lock_irqsave(&host->lock, flags); + + if (host->flags & SDHCI_DEVICE_DEAD) { + spin_unlock_irqrestore(&host->lock, flags); + if (host->vmmc && ios->power_mode == MMC_POWER_OFF) + mmc_regulator_set_ocr(host->mmc, host->vmmc, 0); + return; + } + + /* + * 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) + vdd_bit = sdhci_set_power(host, -1); + else + vdd_bit = sdhci_set_power(host, ios->vdd); + + if (host->vmmc && vdd_bit != -1) { + spin_unlock_irqrestore(&host->lock, flags); + mmc_regulator_set_ocr(host->mmc, host->vmmc, vdd_bit); + spin_lock_irqsave(&host->lock, flags); + } + + 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_MMC_HS200) || + (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_MMC_HS200) + ctrl_2 |= SDHCI_CTRL_HS_SDR200; + else 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); + + mmiowb(); + spin_unlock_irqrestore(&host->lock, flags); +} + +static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct sdhci_host *host = mmc_priv(mmc); + + sdhci_runtime_pm_get(host); + sdhci_do_set_ios(host, ios); + sdhci_runtime_pm_put(host); +} + +static int sdhci_check_ro(struct sdhci_host *host) +{ + unsigned long flags; + int is_readonly; + + 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); + + /* 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_do_get_ro(struct sdhci_host *host) +{ + int i, ro_count; + + if (!(host->quirks & SDHCI_QUIRK_UNSTABLE_RO_DETECT)) + return sdhci_check_ro(host); + + ro_count = 0; + for (i = 0; i < SAMPLE_COUNT; i++) { + if (sdhci_check_ro(host)) { + if (++ro_count > SAMPLE_COUNT / 2) + return 1; + } + msleep(30); + } + return 0; +} + +static void sdhci_hw_reset(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + + if (host->ops && host->ops->hw_reset) + host->ops->hw_reset(host); +} + +static int sdhci_get_ro(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + int ret; + + sdhci_runtime_pm_get(host); + ret = sdhci_do_get_ro(host); + sdhci_runtime_pm_put(host); + return ret; +} + +static void sdhci_enable_sdio_irq_nolock(struct sdhci_host *host, int enable) +{ + if (host->flags & SDHCI_DEVICE_DEAD) + goto out; + + if (enable) + host->flags |= SDHCI_SDIO_IRQ_ENABLED; + else + host->flags &= ~SDHCI_SDIO_IRQ_ENABLED; + + /* SDIO IRQ will be enabled as appropriate in runtime resume */ + if (host->runtime_suspended) + goto out; + + if (enable) + sdhci_unmask_irqs(host, SDHCI_INT_CARD_INT); + else + sdhci_mask_irqs(host, SDHCI_INT_CARD_INT); +out: + mmiowb(); +} + +static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + struct sdhci_host *host = mmc_priv(mmc); + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + sdhci_enable_sdio_irq_nolock(host, enable); + spin_unlock_irqrestore(&host->lock, flags); +} + +static int sdhci_do_start_signal_voltage_switch(struct sdhci_host *host, + struct mmc_ios *ios) +{ + u8 pwr; + u16 clk, ctrl; + u32 present_state; + + /* + * 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 { + pr_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); + + pr_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_start_signal_voltage_switch(struct mmc_host *mmc, + struct mmc_ios *ios) +{ + struct sdhci_host *host = mmc_priv(mmc); + int err; + + if (host->version < SDHCI_SPEC_300) + return 0; + sdhci_runtime_pm_get(host); + err = sdhci_do_start_signal_voltage_switch(host, ios); + sdhci_runtime_pm_put(host); + return err; +} + +static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode) +{ + struct sdhci_host *host; + u16 ctrl; + u32 ier; + int tuning_loop_counter = MAX_TUNING_LOOP; + unsigned long timeout; + int err = 0; + bool requires_tuning_nonuhs = false; + + host = mmc_priv(mmc); + + sdhci_runtime_pm_get(host); + disable_irq(host->irq); + spin_lock(&host->lock); + + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); + + /* + * The Host Controller needs tuning only in case of SDR104 mode + * and for SDR50 mode when Use Tuning for SDR50 is set in the + * Capabilities register. + * If the Host Controller supports the HS200 mode then the + * tuning function has to be executed. + */ + if (((ctrl & SDHCI_CTRL_UHS_MASK) == SDHCI_CTRL_UHS_SDR50) && + (host->flags & SDHCI_SDR50_NEEDS_TUNING || + host->flags & SDHCI_HS200_NEEDS_TUNING)) + requires_tuning_nonuhs = true; + + if (((ctrl & SDHCI_CTRL_UHS_MASK) == SDHCI_CTRL_UHS_SDR104) || + requires_tuning_nonuhs) + ctrl |= SDHCI_CTRL_EXEC_TUNING; + else { + spin_unlock(&host->lock); + enable_irq(host->irq); + sdhci_runtime_pm_put(host); + return 0; + } + + 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 = {NULL}; + + if (!tuning_loop_counter && !timeout) + break; + + cmd.opcode = opcode; + 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. + */ + if (cmd.opcode == MMC_SEND_TUNING_BLOCK_HS200) { + if (mmc->ios.bus_width == MMC_BUS_WIDTH_8) + sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, 128), + SDHCI_BLOCK_SIZE); + else if (mmc->ios.bus_width == MMC_BUS_WIDTH_4) + sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, 64), + SDHCI_BLOCK_SIZE); + } else { + 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) { + pr_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)) { + pr_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); + sdhci_runtime_pm_put(host); + + return err; +} + +static void sdhci_do_enable_preset_value(struct sdhci_host *host, bool enable) +{ + u16 ctrl; + unsigned long flags; + + /* 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); + host->flags |= SDHCI_PV_ENABLED; + } else if (!enable && (ctrl & SDHCI_CTRL_PRESET_VAL_ENABLE)) { + ctrl &= ~SDHCI_CTRL_PRESET_VAL_ENABLE; + sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); + host->flags &= ~SDHCI_PV_ENABLED; + } + + spin_unlock_irqrestore(&host->lock, flags); +} + +static void sdhci_enable_preset_value(struct mmc_host *mmc, bool enable) +{ + struct sdhci_host *host = mmc_priv(mmc); + + sdhci_runtime_pm_get(host); + sdhci_do_enable_preset_value(host, enable); + sdhci_runtime_pm_put(host); +} + +static const struct mmc_host_ops sdhci_ops = { + .request = sdhci_request, + .set_ios = sdhci_set_ios, + .get_ro = sdhci_get_ro, + .hw_reset = sdhci_hw_reset, + .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); + + /* Check host->mrq first in case we are runtime suspended */ + if (host->mrq && + !(sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) { + pr_err("%s: Card removed during transfer!\n", + mmc_hostname(host->mmc)); + pr_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); + } + + spin_unlock_irqrestore(&host->lock, flags); + + mmc_detect_change(host->mmc, msecs_to_jiffies(200)); +} + +static void sdhci_tasklet_finish(unsigned long param) +{ + struct sdhci_host *host; + unsigned long flags; + struct mmc_request *mrq; + + host = (struct sdhci_host*)param; + + spin_lock_irqsave(&host->lock, flags); + + /* + * If this tasklet gets rescheduled while running, it will + * be run again afterwards but without any active request. + */ + if (!host->mrq) { + spin_unlock_irqrestore(&host->lock, flags); + return; + } + + 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 SDHCI_USE_LEDS_CLASS + sdhci_deactivate_led(host); +#endif + + mmiowb(); + spin_unlock_irqrestore(&host->lock, flags); + + mmc_request_done(host->mmc, mrq); + sdhci_runtime_pm_put(host); +} + +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) { + pr_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) { + pr_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) +{ + u32 command; + BUG_ON(intmask == 0); + + /* CMD19 generates _only_ Buffer Read Ready interrupt */ + if (intmask & SDHCI_INT_DATA_AVAIL) { + command = SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND)); + if (command == MMC_SEND_TUNING_BLOCK || + command == MMC_SEND_TUNING_BLOCK_HS200) { + 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; + } + } + + pr_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) { + pr_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, unexpected = 0; + int cardint = 0, max_loops = 16; + + spin_lock(&host->lock); + + if (host->runtime_suspended) { + spin_unlock(&host->lock); + pr_warning("%s: got irq while runtime suspended\n", + mmc_hostname(host->mmc)); + return IRQ_HANDLED; + } + + intmask = sdhci_readl(host, SDHCI_INT_STATUS); + + if (!intmask || intmask == 0xffffffff) { + result = IRQ_NONE; + goto out; + } + +again: + DBG("*** %s got interrupt: 0x%08x\n", + mmc_hostname(host->mmc), intmask); + + if (intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) { + u32 present = sdhci_readl(host, SDHCI_PRESENT_STATE) & + SDHCI_CARD_PRESENT; + + /* + * There is a observation on i.mx esdhc. INSERT bit will be + * immediately set again when it gets cleared, if a card is + * inserted. We have to mask the irq to prevent interrupt + * storm which will freeze the system. And the REMOVE gets + * the same situation. + * + * More testing are needed here to ensure it works for other + * platforms though. + */ + sdhci_mask_irqs(host, present ? SDHCI_INT_CARD_INSERT : + SDHCI_INT_CARD_REMOVE); + sdhci_unmask_irqs(host, present ? SDHCI_INT_CARD_REMOVE : + SDHCI_INT_CARD_INSERT); + + sdhci_writel(host, intmask & (SDHCI_INT_CARD_INSERT | + SDHCI_INT_CARD_REMOVE), SDHCI_INT_STATUS); + intmask &= ~(SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE); + tasklet_schedule(&host->card_tasklet); + } + + 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) { + pr_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) { + unexpected |= intmask; + sdhci_writel(host, intmask, SDHCI_INT_STATUS); + } + + result = IRQ_HANDLED; + + intmask = sdhci_readl(host, SDHCI_INT_STATUS); + if (intmask && --max_loops) + goto again; +out: + spin_unlock(&host->lock); + + if (unexpected) { + pr_err("%s: Unexpected interrupt 0x%08x.\n", + mmc_hostname(host->mmc), unexpected); + sdhci_dumpregs(host); + } + /* + * 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 + +int sdhci_suspend_host(struct sdhci_host *host) +{ + int ret; + bool has_tuning_timer; + + if (host->ops->platform_suspend) + host->ops->platform_suspend(host); + + sdhci_disable_card_detection(host); + + /* Disable tuning since we are suspending */ + has_tuning_timer = host->version >= SDHCI_SPEC_300 && + host->tuning_count && host->tuning_mode == SDHCI_TUNING_MODE_1; + if (has_tuning_timer) { + del_timer_sync(&host->tuning_timer); + host->flags &= ~SDHCI_NEEDS_RETUNING; + } + + ret = mmc_suspend_host(host->mmc); + if (ret) { + if (has_tuning_timer) { + host->flags |= SDHCI_NEEDS_RETUNING; + mod_timer(&host->tuning_timer, jiffies + + host->tuning_count * HZ); + } + + sdhci_enable_card_detection(host); + + return ret; + } + + free_irq(host->irq, host); + + return ret; +} + +EXPORT_SYMBOL_GPL(sdhci_suspend_host); + +int sdhci_resume_host(struct sdhci_host *host) +{ + int ret; + + 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) + return ret; + + if ((host->mmc->pm_flags & MMC_PM_KEEP_POWER) && + (host->quirks2 & SDHCI_QUIRK2_HOST_OFF_CARD_ON)) { + /* Card keeps power but host controller does not */ + sdhci_init(host, 0); + host->pwr = 0; + host->clock = 0; + sdhci_do_set_ios(host, &host->mmc->ios); + } else { + sdhci_init(host, (host->mmc->pm_flags & MMC_PM_KEEP_POWER)); + mmiowb(); + } + + ret = mmc_resume_host(host->mmc); + sdhci_enable_card_detection(host); + + if (host->ops->platform_resume) + host->ops->platform_resume(host); + + /* 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; + val = sdhci_readb(host, SDHCI_WAKE_UP_CONTROL); + val |= SDHCI_WAKE_ON_INT; + sdhci_writeb(host, val, SDHCI_WAKE_UP_CONTROL); +} + +EXPORT_SYMBOL_GPL(sdhci_enable_irq_wakeups); + +#endif /* CONFIG_PM */ + +#ifdef CONFIG_PM_RUNTIME + +static int sdhci_runtime_pm_get(struct sdhci_host *host) +{ + return pm_runtime_get_sync(host->mmc->parent); +} + +static int sdhci_runtime_pm_put(struct sdhci_host *host) +{ + pm_runtime_mark_last_busy(host->mmc->parent); + return pm_runtime_put_autosuspend(host->mmc->parent); +} + +int sdhci_runtime_suspend_host(struct sdhci_host *host) +{ + unsigned long flags; + int ret = 0; + + /* Disable tuning since we are suspending */ + if (host->version >= SDHCI_SPEC_300 && + host->tuning_mode == SDHCI_TUNING_MODE_1) { + del_timer_sync(&host->tuning_timer); + host->flags &= ~SDHCI_NEEDS_RETUNING; + } + + spin_lock_irqsave(&host->lock, flags); + sdhci_mask_irqs(host, SDHCI_INT_ALL_MASK); + spin_unlock_irqrestore(&host->lock, flags); + + synchronize_irq(host->irq); + + spin_lock_irqsave(&host->lock, flags); + host->runtime_suspended = true; + spin_unlock_irqrestore(&host->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(sdhci_runtime_suspend_host); + +int sdhci_runtime_resume_host(struct sdhci_host *host) +{ + unsigned long flags; + int ret = 0, host_flags = host->flags; + + if (host_flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) { + if (host->ops->enable_dma) + host->ops->enable_dma(host); + } + + sdhci_init(host, 0); + + /* Force clock and power re-program */ + host->pwr = 0; + host->clock = 0; + sdhci_do_set_ios(host, &host->mmc->ios); + + sdhci_do_start_signal_voltage_switch(host, &host->mmc->ios); + if (host_flags & SDHCI_PV_ENABLED) + sdhci_do_enable_preset_value(host, true); + + /* 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; + + spin_lock_irqsave(&host->lock, flags); + + host->runtime_suspended = false; + + /* Enable SDIO IRQ */ + if ((host->flags & SDHCI_SDIO_IRQ_ENABLED)) + sdhci_enable_sdio_irq_nolock(host, true); + + /* Enable Card Detection */ + sdhci_enable_card_detection(host); + + spin_unlock_irqrestore(&host->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(sdhci_runtime_resume_host); + +#endif + +/*****************************************************************************\ + * * + * 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 (debug_quirks2) + host->quirks2 = debug_quirks2; + + 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) { + pr_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)) { + pr_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); + pr_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) { + pr_err("%s: Hardware doesn't specify base clock " + "frequency.\n", mmc_hostname(mmc)); + return -ENODEV; + } + host->max_clk = host->ops->get_max_clock(host); + } + + /* + * 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; + + 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)) { + pr_err("%s: Hardware doesn't specify timeout clock " + "frequency.\n", mmc_hostname(mmc)); + return -ENODEV; + } + } + if (caps[0] & SDHCI_TIMEOUT_CLK_UNIT) + host->timeout_clk *= 1000; + + if (host->quirks & SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK) + host->timeout_clk = mmc->f_max / 1000; + + mmc->max_discard_to = (1 << 27) / host->timeout_clk; + + 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->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; + + /* Any UHS-I mode in caps implies SDR12 and SDR25 support. */ + if (caps[1] & (SDHCI_SUPPORT_SDR104 | SDHCI_SUPPORT_SDR50 | + SDHCI_SUPPORT_DDR50)) + 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 need tuning for SDR50? */ + if (caps[1] & SDHCI_USE_SDR50_TUNING) + host->flags |= SDHCI_SDR50_NEEDS_TUNING; + + /* Does the host need tuning for HS200? */ + if (mmc->caps2 & MMC_CAP2_HS200) + host->flags |= SDHCI_HS200_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; + + /* + * If Power Off Notify capability is enabled by the host, + * set notify to short power off notify timeout value. + */ + if (mmc->caps2 & MMC_CAP2_POWEROFF_NOTIFY) + mmc->power_notify_type = MMC_HOST_PW_NOTIFY_SHORT; + else + mmc->power_notify_type = MMC_HOST_PW_NOTIFY_NONE; + + /* 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) { + pr_err("%s: Hardware doesn't report any " + "support voltages.\n", mmc_hostname(mmc)); + 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) { + pr_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; + + /* + * 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; + } + + ret = request_irq(host->irq, sdhci_irq, IRQF_SHARED, + mmc_hostname(mmc), host); + if (ret) + goto untasklet; + + host->vmmc = regulator_get(mmc_dev(mmc), "vmmc"); + if (IS_ERR(host->vmmc)) { + pr_info("%s: no vmmc regulator found\n", mmc_hostname(mmc)); + host->vmmc = NULL; + } + + sdhci_init(host, 0); + +#ifdef CONFIG_MMC_DEBUG + sdhci_dumpregs(host); +#endif + +#ifdef 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); + + pr_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); + + return 0; + +#ifdef 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); + + return ret; +} + +EXPORT_SYMBOL_GPL(sdhci_add_host); + +void sdhci_remove_host(struct sdhci_host *host, int dead) +{ + unsigned long flags; + + if (dead) { + spin_lock_irqsave(&host->lock, flags); + + host->flags |= SDHCI_DEVICE_DEAD; + + if (host->mrq) { + pr_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_disable_card_detection(host); + + mmc_remove_host(host->mmc); + +#ifdef SDHCI_USE_LEDS_CLASS + led_classdev_unregister(&host->led); +#endif + + if (!dead) + sdhci_reset(host, SDHCI_RESET_ALL); + + 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_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) +{ + pr_info(DRIVER_NAME + ": Secure Digital Host Controller Interface driver\n"); + pr_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_param(debug_quirks2, 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."); +MODULE_PARM_DESC(debug_quirks2, "Force certain other quirks."); diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h new file mode 100644 index 00000000..f761f23d --- /dev/null +++ b/drivers/mmc/host/sdhci.h @@ -0,0 +1,390 @@ +/* + * 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_HS_SDR200 0x0005 /* reserved value in SDIO spec */ +#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 (*hw_reset)(struct sdhci_host *host); + void (*platform_suspend)(struct sdhci_host *host); + void (*platform_resume)(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); +extern int sdhci_resume_host(struct sdhci_host *host); +extern void sdhci_enable_irq_wakeups(struct sdhci_host *host); +#endif + +#ifdef CONFIG_PM_RUNTIME +extern int sdhci_runtime_suspend_host(struct sdhci_host *host); +extern int sdhci_runtime_resume_host(struct sdhci_host *host); +#endif + +#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..7009f17a --- /dev/null +++ b/drivers/mmc/host/sdricoh_cs.c @@ -0,0 +1,573 @@ +/* + * 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/module.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..724b35e8 --- /dev/null +++ b/drivers/mmc/host/sh_mmcif.c @@ -0,0 +1,1454 @@ +/* + * 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 + * + */ + +/* + * The MMCIF driver is now processing MMC requests asynchronously, according + * to the Linux MMC API requirement. + * + * The MMCIF driver processes MMC requests in up to 3 stages: command, optional + * data, and optional stop. To achieve asynchronous processing each of these + * stages is split into two halves: a top and a bottom half. The top half + * initialises the hardware, installs a timeout handler to handle completion + * timeouts, and returns. In case of the command stage this immediately returns + * control to the caller, leaving all further processing to run asynchronously. + * All further request processing is performed by the bottom halves. + * + * The bottom half further consists of a "hard" IRQ handler, an IRQ handler + * thread, a DMA completion callback, if DMA is used, a timeout work, and + * request- and stage-specific handler methods. + * + * Each bottom half run begins with either a hardware interrupt, a DMA callback + * invocation, or a timeout work run. In case of an error or a successful + * processing completion, the MMC core is informed and the request processing is + * finished. In case processing has to continue, i.e., if data has to be read + * from or written to the card, or if a stop command has to be sent, the next + * top half is called, which performs the necessary hardware handling and + * reschedules the timeout work. This returns the driver state machine into the + * bottom half waiting state. + */ + +#include <linux/bitops.h> +#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_qos.h> +#include <linux/pm_runtime.h> +#include <linux/spinlock.h> +#include <linux/module.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) + +#define MASK_START_CMD (MASK_MCMDVIO | MASK_MBUFVIO | MASK_MWDATERR | \ + MASK_MRDATERR | MASK_MRIDXERR | MASK_MRSPERR | \ + MASK_MCCSTO | MASK_MCRCSTO | MASK_MWDATTO | \ + MASK_MRDATTO | MASK_MRBSYTO | MASK_MRSPTO) + +/* 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, +}; + +enum mmcif_wait_for { + MMCIF_WAIT_FOR_REQUEST, + MMCIF_WAIT_FOR_CMD, + MMCIF_WAIT_FOR_MREAD, + MMCIF_WAIT_FOR_MWRITE, + MMCIF_WAIT_FOR_READ, + MMCIF_WAIT_FOR_WRITE, + MMCIF_WAIT_FOR_READ_END, + MMCIF_WAIT_FOR_WRITE_END, + MMCIF_WAIT_FOR_STOP, +}; + +struct sh_mmcif_host { + struct mmc_host *mmc; + struct mmc_request *mrq; + struct platform_device *pd; + struct sh_dmae_slave dma_slave_tx; + struct sh_dmae_slave dma_slave_rx; + struct clk *hclk; + unsigned int clk; + int bus_width; + bool sd_error; + bool dying; + long timeout; + void __iomem *addr; + u32 *pio_ptr; + spinlock_t lock; /* protect sh_mmcif_host::state */ + enum mmcif_state state; + enum mmcif_wait_for wait_for; + struct delayed_work timeout_work; + size_t blocksize; + int sg_idx; + int sg_blkidx; + bool power; + bool card_present; + + /* 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; + struct mmc_data *data = host->mrq->data; + + dev_dbg(&host->pd->dev, "Command completed\n"); + + if (WARN(!data, "%s: NULL data in DMA completion!\n", + dev_name(&host->pd->dev))) + return; + + if (data->flags & MMC_DATA_READ) + dma_unmap_sg(host->chan_rx->device->dev, + data->sg, data->sg_len, + DMA_FROM_DEVICE); + else + dma_unmap_sg(host->chan_tx->device->dev, + data->sg, data->sg_len, + DMA_TO_DEVICE); + + complete(&host->dma_complete); +} + +static void sh_mmcif_start_dma_rx(struct sh_mmcif_host *host) +{ + struct mmc_data *data = host->mrq->data; + struct scatterlist *sg = 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, data->sg_len, + DMA_FROM_DEVICE); + if (ret > 0) { + host->dma_active = true; + desc = dmaengine_prep_slave_sg(chan, sg, ret, + DMA_DEV_TO_MEM, 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__, 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, data->sg_len); +} + +static void sh_mmcif_start_dma_tx(struct sh_mmcif_host *host) +{ + struct mmc_data *data = host->mrq->data; + struct scatterlist *sg = 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, data->sg_len, + DMA_TO_DEVICE); + if (ret > 0) { + host->dma_active = true; + desc = dmaengine_prep_slave_sg(chan, sg, ret, + DMA_MEM_TO_DEV, 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__, 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) +{ + struct sh_dmae_slave *tx, *rx; + host->dma_active = false; + + /* We can only either use DMA for both Tx and Rx or not use it at all */ + if (pdata->dma) { + dev_warn(&host->pd->dev, + "Update your platform to use embedded DMA slave IDs\n"); + tx = &pdata->dma->chan_priv_tx; + rx = &pdata->dma->chan_priv_rx; + } else { + tx = &host->dma_slave_tx; + tx->slave_id = pdata->slave_id_tx; + rx = &host->dma_slave_rx; + rx->slave_id = pdata->slave_id_rx; + } + if (tx->slave_id > 0 && rx->slave_id > 0) { + 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, 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, 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 & + ((fls(DIV_ROUND_UP(host->clk, + clk) - 1) - 1) << 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; + + 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); + for (timeout = 10000000; timeout; timeout--) { + if (!(sh_mmcif_readl(host->addr, MMCIF_CE_HOST_STS1) + & STS1_CMDSEQ)) + break; + mdelay(1); + } + if (!timeout) { + dev_err(&host->pd->dev, + "Forced end of command sequence timeout err\n"); + return -EIO; + } + 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, ": CRC error\n"); + ret = -EIO; + } else if (state2 & STS2_TIMEOUT_ERR) { + dev_dbg(&host->pd->dev, ": Timeout\n"); + ret = -ETIMEDOUT; + } else { + dev_dbg(&host->pd->dev, ": End/Index error\n"); + ret = -EIO; + } + return ret; +} + +static bool sh_mmcif_next_block(struct sh_mmcif_host *host, u32 *p) +{ + struct mmc_data *data = host->mrq->data; + + host->sg_blkidx += host->blocksize; + + /* data->sg->length must be a multiple of host->blocksize? */ + BUG_ON(host->sg_blkidx > data->sg->length); + + if (host->sg_blkidx == data->sg->length) { + host->sg_blkidx = 0; + if (++host->sg_idx < data->sg_len) + host->pio_ptr = sg_virt(++data->sg); + } else { + host->pio_ptr = p; + } + + if (host->sg_idx == data->sg_len) + return false; + + return true; +} + +static void sh_mmcif_single_read(struct sh_mmcif_host *host, + struct mmc_request *mrq) +{ + host->blocksize = (sh_mmcif_readl(host->addr, MMCIF_CE_BLOCK_SET) & + BLOCK_SIZE_MASK) + 3; + + host->wait_for = MMCIF_WAIT_FOR_READ; + schedule_delayed_work(&host->timeout_work, host->timeout); + + /* buf read enable */ + sh_mmcif_bitset(host, MMCIF_CE_INT_MASK, MASK_MBUFREN); +} + +static bool sh_mmcif_read_block(struct sh_mmcif_host *host) +{ + struct mmc_data *data = host->mrq->data; + u32 *p = sg_virt(data->sg); + int i; + + if (host->sd_error) { + data->error = sh_mmcif_error_manage(host); + return false; + } + + for (i = 0; i < host->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); + host->wait_for = MMCIF_WAIT_FOR_READ_END; + + return true; +} + +static void sh_mmcif_multi_read(struct sh_mmcif_host *host, + struct mmc_request *mrq) +{ + struct mmc_data *data = mrq->data; + + if (!data->sg_len || !data->sg->length) + return; + + host->blocksize = sh_mmcif_readl(host->addr, MMCIF_CE_BLOCK_SET) & + BLOCK_SIZE_MASK; + + host->wait_for = MMCIF_WAIT_FOR_MREAD; + host->sg_idx = 0; + host->sg_blkidx = 0; + host->pio_ptr = sg_virt(data->sg); + schedule_delayed_work(&host->timeout_work, host->timeout); + sh_mmcif_bitset(host, MMCIF_CE_INT_MASK, MASK_MBUFREN); +} + +static bool sh_mmcif_mread_block(struct sh_mmcif_host *host) +{ + struct mmc_data *data = host->mrq->data; + u32 *p = host->pio_ptr; + int i; + + if (host->sd_error) { + data->error = sh_mmcif_error_manage(host); + return false; + } + + BUG_ON(!data->sg->length); + + for (i = 0; i < host->blocksize / 4; i++) + *p++ = sh_mmcif_readl(host->addr, MMCIF_CE_DATA); + + if (!sh_mmcif_next_block(host, p)) + return false; + + schedule_delayed_work(&host->timeout_work, host->timeout); + sh_mmcif_bitset(host, MMCIF_CE_INT_MASK, MASK_MBUFREN); + + return true; +} + +static void sh_mmcif_single_write(struct sh_mmcif_host *host, + struct mmc_request *mrq) +{ + host->blocksize = (sh_mmcif_readl(host->addr, MMCIF_CE_BLOCK_SET) & + BLOCK_SIZE_MASK) + 3; + + host->wait_for = MMCIF_WAIT_FOR_WRITE; + schedule_delayed_work(&host->timeout_work, host->timeout); + + /* buf write enable */ + sh_mmcif_bitset(host, MMCIF_CE_INT_MASK, MASK_MBUFWEN); +} + +static bool sh_mmcif_write_block(struct sh_mmcif_host *host) +{ + struct mmc_data *data = host->mrq->data; + u32 *p = sg_virt(data->sg); + int i; + + if (host->sd_error) { + data->error = sh_mmcif_error_manage(host); + return false; + } + + for (i = 0; i < host->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); + host->wait_for = MMCIF_WAIT_FOR_WRITE_END; + + return true; +} + +static void sh_mmcif_multi_write(struct sh_mmcif_host *host, + struct mmc_request *mrq) +{ + struct mmc_data *data = mrq->data; + + if (!data->sg_len || !data->sg->length) + return; + + host->blocksize = sh_mmcif_readl(host->addr, MMCIF_CE_BLOCK_SET) & + BLOCK_SIZE_MASK; + + host->wait_for = MMCIF_WAIT_FOR_MWRITE; + host->sg_idx = 0; + host->sg_blkidx = 0; + host->pio_ptr = sg_virt(data->sg); + schedule_delayed_work(&host->timeout_work, host->timeout); + sh_mmcif_bitset(host, MMCIF_CE_INT_MASK, MASK_MBUFWEN); +} + +static bool sh_mmcif_mwrite_block(struct sh_mmcif_host *host) +{ + struct mmc_data *data = host->mrq->data; + u32 *p = host->pio_ptr; + int i; + + if (host->sd_error) { + data->error = sh_mmcif_error_manage(host); + return false; + } + + BUG_ON(!data->sg->length); + + for (i = 0; i < host->blocksize / 4; i++) + sh_mmcif_writel(host->addr, MMCIF_CE_DATA, *p++); + + if (!sh_mmcif_next_block(host, p)) + return false; + + schedule_delayed_work(&host->timeout_work, host->timeout); + sh_mmcif_bitset(host, MMCIF_CE_INT_MASK, MASK_MBUFWEN); + + return true; +} + +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_data *data = mrq->data; + struct mmc_command *cmd = mrq->cmd; + u32 opc = cmd->opcode; + 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: + tmp |= CMD_SET_RBSY; + break; + } + /* WDAT / DATW */ + if (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, + 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 << 24) | tmp; +} + +static int sh_mmcif_data_trans(struct sh_mmcif_host *host, + struct mmc_request *mrq, u32 opc) +{ + switch (opc) { + case MMC_READ_MULTIPLE_BLOCK: + sh_mmcif_multi_read(host, mrq); + return 0; + case MMC_WRITE_MULTIPLE_BLOCK: + sh_mmcif_multi_write(host, mrq); + return 0; + case MMC_WRITE_BLOCK: + sh_mmcif_single_write(host, mrq); + return 0; + case MMC_READ_SINGLE_BLOCK: + case MMC_SEND_EXT_CSD: + sh_mmcif_single_read(host, mrq); + return 0; + default: + dev_err(&host->pd->dev, "UNSUPPORTED CMD = d'%08d\n", opc); + return -EINVAL; + } +} + +static void sh_mmcif_start_cmd(struct sh_mmcif_host *host, + struct mmc_request *mrq) +{ + struct mmc_command *cmd = mrq->cmd; + u32 opc = cmd->opcode; + u32 mask; + + switch (opc) { + /* response busy check */ + case MMC_SWITCH: + case MMC_STOP_TRANSMISSION: + case MMC_SET_WRITE_PROT: + case MMC_CLR_WRITE_PROT: + case MMC_ERASE: + mask = MASK_START_CMD | MASK_MRBSYE; + break; + default: + mask = MASK_START_CMD | MASK_MCRSPE; + break; + } + + if (mrq->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); + + 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); + + host->wait_for = MMCIF_WAIT_FOR_CMD; + schedule_delayed_work(&host->timeout_work, host->timeout); +} + +static void sh_mmcif_stop_cmd(struct sh_mmcif_host *host, + struct mmc_request *mrq) +{ + switch (mrq->cmd->opcode) { + case MMC_READ_MULTIPLE_BLOCK: + sh_mmcif_bitset(host, MMCIF_CE_INT_MASK, MASK_MCMD12DRE); + break; + case MMC_WRITE_MULTIPLE_BLOCK: + sh_mmcif_bitset(host, MMCIF_CE_INT_MASK, MASK_MCMD12RBE); + break; + default: + dev_err(&host->pd->dev, "unsupported stop cmd\n"); + mrq->stop->error = sh_mmcif_error_manage(host); + return; + } + + host->wait_for = MMCIF_WAIT_FOR_STOP; + schedule_delayed_work(&host->timeout_work, host->timeout); +} + +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->mrq = mrq; + + sh_mmcif_start_cmd(host, 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 (!host->card_present) { + /* See if we also get DMA */ + sh_mmcif_request_dma(host, host->pd->dev.platform_data); + host->card_present = 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->card_present) { + sh_mmcif_release_dma(host); + host->card_present = false; + } + } + if (host->power) { + pm_runtime_put(&host->pd->dev); + host->power = false; + if (p->down_pwr && ios->power_mode == MMC_POWER_OFF) + p->down_pwr(host->pd); + } + host->state = STATE_IDLE; + return; + } + + if (ios->clock) { + if (!host->power) { + if (p->set_pwr) + p->set_pwr(host->pd, ios->power_mode); + pm_runtime_get_sync(&host->pd->dev); + host->power = true; + sh_mmcif_sync_reset(host); + } + 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 bool sh_mmcif_end_cmd(struct sh_mmcif_host *host) +{ + struct mmc_command *cmd = host->mrq->cmd; + struct mmc_data *data = host->mrq->data; + long time; + + if (host->sd_error) { + switch (cmd->opcode) { + case MMC_ALL_SEND_CID: + case MMC_SELECT_CARD: + case MMC_APP_CMD: + cmd->error = -ETIMEDOUT; + host->sd_error = false; + break; + default: + cmd->error = sh_mmcif_error_manage(host); + dev_dbg(&host->pd->dev, "Cmd(d'%d) error %d\n", + cmd->opcode, cmd->error); + break; + } + return false; + } + if (!(cmd->flags & MMC_RSP_PRESENT)) { + cmd->error = 0; + return false; + } + + sh_mmcif_get_response(host, cmd); + + if (!data) + return false; + + if (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); + } + + if (!host->dma_active) { + data->error = sh_mmcif_data_trans(host, host->mrq, cmd->opcode); + if (!data->error) + return true; + return false; + } + + /* Running in the IRQ thread, can sleep */ + time = wait_for_completion_interruptible_timeout(&host->dma_complete, + host->timeout); + if (host->sd_error) { + dev_err(host->mmc->parent, + "Error IRQ while waiting for DMA completion!\n"); + /* Woken up by an error IRQ: abort DMA */ + if (data->flags & MMC_DATA_READ) + dmaengine_terminate_all(host->chan_rx); + else + dmaengine_terminate_all(host->chan_tx); + data->error = sh_mmcif_error_manage(host); + } else if (!time) { + data->error = -ETIMEDOUT; + } else if (time < 0) { + data->error = time; + } + sh_mmcif_bitclr(host, MMCIF_CE_BUF_ACC, + BUF_ACC_DMAREN | BUF_ACC_DMAWEN); + host->dma_active = false; + + if (data->error) + data->bytes_xfered = 0; + + return false; +} + +static irqreturn_t sh_mmcif_irqt(int irq, void *dev_id) +{ + struct sh_mmcif_host *host = dev_id; + struct mmc_request *mrq = host->mrq; + struct mmc_data *data = mrq->data; + + cancel_delayed_work_sync(&host->timeout_work); + + /* + * All handlers return true, if processing continues, and false, if the + * request has to be completed - successfully or not + */ + switch (host->wait_for) { + case MMCIF_WAIT_FOR_REQUEST: + /* We're too late, the timeout has already kicked in */ + return IRQ_HANDLED; + case MMCIF_WAIT_FOR_CMD: + if (sh_mmcif_end_cmd(host)) + /* Wait for data */ + return IRQ_HANDLED; + break; + case MMCIF_WAIT_FOR_MREAD: + if (sh_mmcif_mread_block(host)) + /* Wait for more data */ + return IRQ_HANDLED; + break; + case MMCIF_WAIT_FOR_READ: + if (sh_mmcif_read_block(host)) + /* Wait for data end */ + return IRQ_HANDLED; + break; + case MMCIF_WAIT_FOR_MWRITE: + if (sh_mmcif_mwrite_block(host)) + /* Wait data to write */ + return IRQ_HANDLED; + break; + case MMCIF_WAIT_FOR_WRITE: + if (sh_mmcif_write_block(host)) + /* Wait for data end */ + return IRQ_HANDLED; + break; + case MMCIF_WAIT_FOR_STOP: + if (host->sd_error) { + mrq->stop->error = sh_mmcif_error_manage(host); + break; + } + sh_mmcif_get_cmd12response(host, mrq->stop); + mrq->stop->error = 0; + break; + case MMCIF_WAIT_FOR_READ_END: + case MMCIF_WAIT_FOR_WRITE_END: + if (host->sd_error) + data->error = sh_mmcif_error_manage(host); + break; + default: + BUG(); + } + + if (host->wait_for != MMCIF_WAIT_FOR_STOP) { + if (!mrq->cmd->error && data && !data->error) + data->bytes_xfered = + data->blocks * data->blksz; + + if (mrq->stop && !mrq->cmd->error && (!data || !data->error)) { + sh_mmcif_stop_cmd(host, mrq); + if (!mrq->stop->error) + return IRQ_HANDLED; + } + } + + host->wait_for = MMCIF_WAIT_FOR_REQUEST; + host->state = STATE_IDLE; + host->mrq = NULL; + mmc_request_done(host->mmc, mrq); + + return IRQ_HANDLED; +} + +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_ERR_STS) { + /* error interrupts - process first */ + sh_mmcif_writel(host->addr, MMCIF_CE_INT, ~state); + sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, state); + err = 1; + } else 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 { + 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)) { + if (!host->dma_active) + return IRQ_WAKE_THREAD; + else if (host->sd_error) + mmcif_dma_complete(host); + } else { + dev_dbg(&host->pd->dev, "Unexpected IRQ 0x%x\n", state); + } + + return IRQ_HANDLED; +} + +static void mmcif_timeout_work(struct work_struct *work) +{ + struct delayed_work *d = container_of(work, struct delayed_work, work); + struct sh_mmcif_host *host = container_of(d, struct sh_mmcif_host, timeout_work); + struct mmc_request *mrq = host->mrq; + + if (host->dying) + /* Don't run after mmc_remove_host() */ + return; + + /* + * Handle races with cancel_delayed_work(), unless + * cancel_delayed_work_sync() is used + */ + switch (host->wait_for) { + case MMCIF_WAIT_FOR_CMD: + mrq->cmd->error = sh_mmcif_error_manage(host); + break; + case MMCIF_WAIT_FOR_STOP: + mrq->stop->error = sh_mmcif_error_manage(host); + break; + case MMCIF_WAIT_FOR_MREAD: + case MMCIF_WAIT_FOR_MWRITE: + case MMCIF_WAIT_FOR_READ: + case MMCIF_WAIT_FOR_WRITE: + case MMCIF_WAIT_FOR_READ_END: + case MMCIF_WAIT_FOR_WRITE_END: + mrq->data->error = sh_mmcif_error_manage(host); + break; + default: + BUG(); + } + + host->state = STATE_IDLE; + host->wait_for = MMCIF_WAIT_FOR_REQUEST; + host->mrq = NULL; + mmc_request_done(host->mmc, mrq); +} + +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; + + spin_lock_init(&host->lock); + + mmc->ops = &sh_mmcif_ops; + mmc->f_max = host->clk / 2; + mmc->f_min = host->clk / 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; + + INIT_DELAYED_WORK(&host->timeout_work, mmcif_timeout_work); + + sh_mmcif_writel(host->addr, MMCIF_CE_INT_MASK, MASK_ALL); + + ret = request_threaded_irq(irq[0], sh_mmcif_intr, sh_mmcif_irqt, 0, "sh_mmc:error", host); + if (ret) { + dev_err(&pdev->dev, "request_irq error (sh_mmc:error)\n"); + goto clean_up3; + } + ret = request_threaded_irq(irq[1], sh_mmcif_intr, sh_mmcif_irqt, 0, "sh_mmc:int", host); + if (ret) { + dev_err(&pdev->dev, "request_irq error (sh_mmc:int)\n"); + goto clean_up4; + } + + ret = mmc_add_host(mmc); + if (ret < 0) + goto clean_up5; + + dev_pm_qos_expose_latency_limit(&pdev->dev, 100); + + 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_up5: + free_irq(irq[1], host); +clean_up4: + free_irq(irq[0], host); +clean_up3: + 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]; + + host->dying = true; + pm_runtime_get_sync(&pdev->dev); + + dev_pm_qos_hide_latency_limit(&pdev->dev); + + mmc_remove_host(host->mmc); + sh_mmcif_writel(host->addr, MMCIF_CE_INT_MASK, MASK_ALL); + + /* + * FIXME: cancel_delayed_work(_sync)() and free_irq() race with the + * mmc_remove_host() call above. But swapping order doesn't help either + * (a query on the linux-mmc mailing list didn't bring any replies). + */ + cancel_delayed_work_sync(&host->timeout_work); + + 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, + }, +}; + +module_platform_driver(sh_mmcif_driver); + +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..934b68e9 --- /dev/null +++ b/drivers/mmc/host/sh_mobile_sdhi.c @@ -0,0 +1,309 @@ +/* + * 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/module.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 <linux/delay.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 sh_mobile_sdhi_wait_idle(struct tmio_mmc_host *host) +{ + int timeout = 1000; + + while (--timeout && !(sd_ctrl_read16(host, CTL_STATUS2) & (1 << 13))) + udelay(1); + + if (!timeout) { + dev_warn(host->pdata->dev, "timeout waiting for SD bus idle\n"); + return -EBUSY; + } + + return 0; +} + +static int sh_mobile_sdhi_write16_hook(struct tmio_mmc_host *host, int addr) +{ + switch (addr) + { + case CTL_SD_CMD: + case CTL_STOP_INTERNAL_ACTION: + case CTL_XFER_BLK_COUNT: + case CTL_SD_CARD_CLK_CTL: + case CTL_SD_XFER_LEN: + case CTL_SD_MEM_CARD_OPT: + case CTL_TRANSACTION_CTL: + case CTL_DMA_ENABLE: + return sh_mobile_sdhi_wait_idle(host); + } + + return 0; +} + +static void sh_mobile_sdhi_cd_wakeup(const struct platform_device *pdev) +{ + mmc_detect_change(dev_get_drvdata(&pdev->dev), msecs_to_jiffies(100)); +} + +static const struct sh_mobile_sdhi_ops sdhi_ops = { + .cd_wakeup = sh_mobile_sdhi_cd_wakeup, +}; + +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 irq, ret, i = 0; + bool multiplexed_isr = true; + + 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; + + if (p->init) { + ret = p->init(pdev, &sdhi_ops); + if (ret) + goto einit; + } + + 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; + } + + 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; + if (mmc_data->flags & TMIO_MMC_HAS_IDLE_WAIT) + mmc_data->write16_hook = sh_mobile_sdhi_write16_hook; + mmc_data->ocr_mask = p->tmio_ocr_mask; + mmc_data->capabilities |= p->tmio_caps; + mmc_data->cd_gpio = p->cd_gpio; + + 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; + + /* + * Allow one or more specific (named) ISRs or + * one or more multiplexed (un-named) ISRs. + */ + + irq = platform_get_irq_byname(pdev, SH_MOBILE_SDHI_IRQ_CARD_DETECT); + if (irq >= 0) { + multiplexed_isr = false; + ret = request_irq(irq, tmio_mmc_card_detect_irq, 0, + dev_name(&pdev->dev), host); + if (ret) + goto eirq_card_detect; + } + + irq = platform_get_irq_byname(pdev, SH_MOBILE_SDHI_IRQ_SDIO); + if (irq >= 0) { + multiplexed_isr = false; + ret = request_irq(irq, tmio_mmc_sdio_irq, 0, + dev_name(&pdev->dev), host); + if (ret) + goto eirq_sdio; + } + + irq = platform_get_irq_byname(pdev, SH_MOBILE_SDHI_IRQ_SDCARD); + if (irq >= 0) { + multiplexed_isr = false; + ret = request_irq(irq, tmio_mmc_sdcard_irq, 0, + dev_name(&pdev->dev), host); + if (ret) + goto eirq_sdcard; + } else if (!multiplexed_isr) { + dev_err(&pdev->dev, + "Principal SD-card IRQ is missing among named interrupts\n"); + ret = irq; + goto eirq_sdcard; + } + + if (multiplexed_isr) { + while (1) { + irq = platform_get_irq(pdev, i); + if (irq < 0) + break; + i++; + ret = request_irq(irq, tmio_mmc_irq, 0, + dev_name(&pdev->dev), host); + if (ret) + goto eirq_multiplexed; + } + + /* There must be at least one IRQ source */ + if (!i) + goto eirq_multiplexed; + } + + 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_multiplexed: + while (i--) { + irq = platform_get_irq(pdev, i); + free_irq(irq, host); + } +eirq_sdcard: + irq = platform_get_irq_byname(pdev, SH_MOBILE_SDHI_IRQ_SDIO); + if (irq >= 0) + free_irq(irq, host); +eirq_sdio: + irq = platform_get_irq_byname(pdev, SH_MOBILE_SDHI_IRQ_CARD_DETECT); + if (irq >= 0) + free_irq(irq, host); +eirq_card_detect: + tmio_mmc_host_remove(host); +eprobe: + clk_put(priv->clk); +eclkget: + if (p->cleanup) + p->cleanup(pdev); +einit: + 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 = 0, irq; + + p->pdata = NULL; + + tmio_mmc_host_remove(host); + + while (1) { + irq = platform_get_irq(pdev, i++); + if (irq < 0) + break; + free_irq(irq, host); + } + + clk_put(priv->clk); + + if (p->cleanup) + p->cleanup(pdev); + + 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), +}; + +module_platform_driver(sh_mobile_sdhi_driver); + +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..43d96282 --- /dev/null +++ b/drivers/mmc/host/tifm_sd.c @@ -0,0 +1,1093 @@ +/* + * 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 <linux/module.h> +#include <asm/io.h> + +#define DRIVER_NAME "tifm_sd" +#define DRIVER_VERSION "0.8" + +static bool no_dma = 0; +static bool 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) + 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); +} + +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) + 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); +} + +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) + src_off; + unsigned char *dst_buf = kmap_atomic(dst) + dst_off; + + memcpy(dst_buf, src_buf, count); + + kunmap_atomic(dst_buf - dst_off); + kunmap_atomic(src_buf - src_off); +} + +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) { + pr_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)) { + pr_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) { + pr_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) { + pr_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; + + pr_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) { + pr_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) { + pr_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))) { + pr_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..113ce6c9 --- /dev/null +++ b/drivers/mmc/host/tmio_mmc.c @@ -0,0 +1,146 @@ +/* + * 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); + 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); + 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_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, +}; + +module_platform_driver(tmio_mmc_driver); + +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..d857f5c6 --- /dev/null +++ b/drivers/mmc/host/tmio_mmc.h @@ -0,0 +1,200 @@ +/* + * 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/mutex.h> +#include <linux/pagemap.h> +#include <linux/scatterlist.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; + + /* Controller power state */ + bool power; + + /* Callbacks for clock / power control */ + void (*set_pwr)(struct platform_device *host, int state); + void (*set_clk_div)(struct platform_device *host, int state); + + /* 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; + struct work_struct done; + + /* Cache IRQ mask */ + u32 sdcard_irq_mask; + u32 sdio_irq_mask; + + spinlock_t lock; /* protect host private data */ + unsigned long last_req_ts; + struct mutex ios_lock; /* protect set_ios() context */ + bool native_hotplug; +}; + +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); +irqreturn_t tmio_mmc_sdcard_irq(int irq, void *devid); +irqreturn_t tmio_mmc_card_detect_irq(int irq, void *devid); +irqreturn_t tmio_mmc_sdio_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)) + sg->offset; +} + +static inline void tmio_mmc_kunmap_atomic(struct scatterlist *sg, + unsigned long *flags, void *virt) +{ + kunmap_atomic(virt - sg->offset); + 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_enable_dma(struct tmio_mmc_host *host, bool enable); +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); +void tmio_mmc_abort_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_enable_dma(struct tmio_mmc_host *host, bool enable) +{ +} + +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) +{ +} + +static inline void tmio_mmc_abort_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); + +static inline u16 sd_ctrl_read16(struct tmio_mmc_host *host, int addr) +{ + return readw(host->ctl + (addr << host->bus_shift)); +} + +static inline 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 inline 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 inline void sd_ctrl_write16(struct tmio_mmc_host *host, int addr, u16 val) +{ + /* If there is a hook and it returns non-zero then there + * is an error and the write should be skipped + */ + if (host->pdata->write16_hook && host->pdata->write16_hook(host, addr)) + return; + writew(val, host->ctl + (addr << host->bus_shift)); +} + +static inline 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 inline 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)); +} + + +#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..fff92860 --- /dev/null +++ b/drivers/mmc/host/tmio_mmc_dma.c @@ -0,0 +1,336 @@ +/* + * 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 + +void tmio_mmc_enable_dma(struct tmio_mmc_host *host, bool enable) +{ + if (!host->chan_tx || !host->chan_rx) + return; + +#if defined(CONFIG_SUPERH) || defined(CONFIG_ARCH_SHMOBILE) + /* Switch DMA mode on or off - SuperH specific? */ + sd_ctrl_write16(host, CTL_DMA_ENABLE, enable ? 2 : 0); +#endif +} + +void tmio_mmc_abort_dma(struct tmio_mmc_host *host) +{ + tmio_mmc_enable_dma(host, false); + + if (host->chan_rx) + dmaengine_terminate_all(host->chan_rx); + if (host->chan_tx) + dmaengine_terminate_all(host->chan_tx); + + tmio_mmc_enable_dma(host, true); +} + +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 = dmaengine_prep_slave_sg(chan, sg, ret, + DMA_DEV_TO_MEM, 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 = dmaengine_prep_slave_sg(chan, sg, ret, + DMA_MEM_TO_DEV, 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..9a7996ad --- /dev/null +++ b/drivers/mmc/host/tmio_mmc_pio.c @@ -0,0 +1,1078 @@ +/* + * 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/cd-gpio.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_qos.h> +#include <linux/pm_runtime.h> +#include <linux/scatterlist.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> + +#include "tmio_mmc.h" + +void tmio_mmc_enable_mmc_irqs(struct tmio_mmc_host *host, u32 i) +{ + host->sdcard_irq_mask &= ~(i & TMIO_MASK_IRQ); + sd_ctrl_write32(host, CTL_IRQ_MASK, host->sdcard_irq_mask); +} + +void tmio_mmc_disable_mmc_irqs(struct tmio_mmc_host *host, u32 i) +{ + host->sdcard_irq_mask |= (i & TMIO_MASK_IRQ); + sd_ctrl_write32(host, CTL_IRQ_MASK, host->sdcard_irq_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; + pr_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_mask = TMIO_SDIO_MASK_ALL & + ~TMIO_SDIO_STAT_IOIRQ; + sd_ctrl_write16(host, CTL_TRANSACTION_CTL, 0x0001); + sd_ctrl_write16(host, CTL_SDIO_IRQ_MASK, host->sdio_irq_mask); + } else { + host->sdio_irq_mask = TMIO_SDIO_MASK_ALL; + sd_ctrl_write16(host, CTL_SDIO_IRQ_MASK, host->sdio_irq_mask); + sd_ctrl_write16(host, CTL_TRANSACTION_CTL, 0x0000); + } +} + +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; + + tmio_mmc_abort_dma(host); + 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; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + + mrq = host->mrq; + if (IS_ERR_OR_NULL(mrq)) { + spin_unlock_irqrestore(&host->lock, flags); + return; + } + + host->cmd = NULL; + host->data = NULL; + host->force_pio = false; + + cancel_delayed_work(&host->delayed_reset_work); + + host->mrq = NULL; + spin_unlock_irqrestore(&host->lock, flags); + + if (mrq->cmd->error || (mrq->data && mrq->data->error)) + tmio_mmc_abort_dma(host); + + mmc_request_done(host->mmc, mrq); +} + +static void tmio_mmc_done_work(struct work_struct *work) +{ + struct tmio_mmc_host *host = container_of(work, struct tmio_mmc_host, + done); + tmio_mmc_finish_request(host); +} + +/* 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; + u32 irq_mask = TMIO_MASK_CMD; + + /* 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; + } + + if (!host->native_hotplug) + irq_mask &= ~(TMIO_STAT_CARD_REMOVE | TMIO_STAT_CARD_INSERT); + tmio_mmc_enable_mmc_irqs(host, irq_mask); + + /* 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(); + } + + schedule_work(&host->done); +} + +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 { + schedule_work(&host->done); + } + +out: + spin_unlock(&host->lock); +} + +static void tmio_mmc_card_irq_status(struct tmio_mmc_host *host, + int *ireg, int *status) +{ + *status = sd_ctrl_read32(host, CTL_STATUS); + *ireg = *status & TMIO_MASK_IRQ & ~host->sdcard_irq_mask; + + pr_debug_status(*status); + pr_debug_status(*ireg); +} + +static bool __tmio_mmc_card_detect_irq(struct tmio_mmc_host *host, + int ireg, int status) +{ + struct mmc_host *mmc = host->mmc; + + /* 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); + if ((((ireg & TMIO_STAT_CARD_REMOVE) && mmc->card) || + ((ireg & TMIO_STAT_CARD_INSERT) && !mmc->card)) && + !work_pending(&mmc->detect.work)) + mmc_detect_change(host->mmc, msecs_to_jiffies(100)); + return true; + } + + return false; +} + +irqreturn_t tmio_mmc_card_detect_irq(int irq, void *devid) +{ + unsigned int ireg, status; + struct tmio_mmc_host *host = devid; + + tmio_mmc_card_irq_status(host, &ireg, &status); + __tmio_mmc_card_detect_irq(host, ireg, status); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL(tmio_mmc_card_detect_irq); + +static bool __tmio_mmc_sdcard_irq(struct tmio_mmc_host *host, + int ireg, int status) +{ + /* 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); + return true; + } + + /* 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); + return true; + } + + /* Data transfer completion */ + if (ireg & TMIO_STAT_DATAEND) { + tmio_mmc_ack_mmc_irqs(host, TMIO_STAT_DATAEND); + tmio_mmc_data_irq(host); + return true; + } + + return false; +} + +irqreturn_t tmio_mmc_sdcard_irq(int irq, void *devid) +{ + unsigned int ireg, status; + struct tmio_mmc_host *host = devid; + + tmio_mmc_card_irq_status(host, &ireg, &status); + __tmio_mmc_sdcard_irq(host, ireg, status); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL(tmio_mmc_sdcard_irq); + +irqreturn_t tmio_mmc_sdio_irq(int irq, void *devid) +{ + struct tmio_mmc_host *host = devid; + struct mmc_host *mmc = host->mmc; + struct tmio_mmc_data *pdata = host->pdata; + unsigned int ireg, status; + + if (!(pdata->flags & TMIO_MMC_SDIO_IRQ)) + return IRQ_HANDLED; + + status = sd_ctrl_read16(host, CTL_SDIO_STATUS); + ireg = status & TMIO_SDIO_MASK_ALL & ~host->sdcard_irq_mask; + + sd_ctrl_write16(host, CTL_SDIO_STATUS, status & ~TMIO_SDIO_MASK_ALL); + + if (mmc->caps & MMC_CAP_SDIO_IRQ && ireg & TMIO_SDIO_STAT_IOIRQ) + mmc_signal_sdio_irq(mmc); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL(tmio_mmc_sdio_irq); + +irqreturn_t tmio_mmc_irq(int irq, void *devid) +{ + struct tmio_mmc_host *host = devid; + unsigned int ireg, status; + + pr_debug("MMC IRQ begin\n"); + + tmio_mmc_card_irq_status(host, &ireg, &status); + if (__tmio_mmc_card_detect_irq(host, ireg, status)) + return IRQ_HANDLED; + if (__tmio_mmc_sdcard_irq(host, ireg, status)) + return IRQ_HANDLED; + + tmio_mmc_sdio_irq(irq, devid); + + 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 device *dev = &host->pdev->dev; + unsigned long flags; + + mutex_lock(&host->ios_lock); + + spin_lock_irqsave(&host->lock, flags); + if (host->mrq) { + if (IS_ERR(host->mrq)) { + dev_dbg(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(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); + + mutex_unlock(&host->ios_lock); + return; + } + + host->mrq = ERR_PTR(-EBUSY); + + spin_unlock_irqrestore(&host->lock, flags); + + /* + * host->power toggles between false and true in both cases - either + * or not the controller can be runtime-suspended during inactivity. + * But if the controller has to be kept on, the runtime-pm usage_count + * is kept positive, so no suspending actually takes place. + */ + if (ios->power_mode == MMC_POWER_ON && ios->clock) { + if (!host->power) { + pm_runtime_get_sync(dev); + host->power = true; + } + tmio_mmc_set_clock(host, ios->clock); + /* power up SD bus */ + if (host->set_pwr) + host->set_pwr(host->pdev, 1); + /* start bus clock */ + tmio_mmc_clk_start(host); + } else if (ios->power_mode != MMC_POWER_UP) { + if (host->set_pwr && ios->power_mode == MMC_POWER_OFF) + host->set_pwr(host->pdev, 0); + if (host->power) { + host->power = false; + pm_runtime_put(dev); + } + tmio_mmc_clk_stop(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; + + mutex_unlock(&host->ios_lock); +} + +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; + + _host->native_hotplug = !(pdata->flags & TMIO_MMC_USE_GPIO_CD || + mmc->caps & MMC_CAP_NEEDS_POLL || + mmc->caps & MMC_CAP_NONREMOVABLE); + + _host->power = false; + pm_runtime_enable(&pdev->dev); + ret = pm_runtime_resume(&pdev->dev); + if (ret < 0) + goto pm_disable; + + /* + * There are 4 different scenarios for the card detection: + * 1) an external gpio irq handles the cd (best for power savings) + * 2) internal sdhi irq handles the cd + * 3) a worker thread polls the sdhi - indicated by MMC_CAP_NEEDS_POLL + * 4) the medium is non-removable - indicated by MMC_CAP_NONREMOVABLE + * + * While we increment the runtime PM counter for all scenarios when + * the mmc core activates us by calling an appropriate set_ios(), we + * must additionally ensure that in case 2) the tmio mmc hardware stays + * additionally ensure that in case 2) the tmio mmc hardware stays + * powered on during runtime for the card detection to work. + */ + if (_host->native_hotplug) + pm_runtime_get_noresume(&pdev->dev); + + tmio_mmc_clk_stop(_host); + tmio_mmc_reset(_host); + + _host->sdcard_irq_mask = sd_ctrl_read32(_host, CTL_IRQ_MASK); + 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); + mutex_init(&_host->ios_lock); + + /* Init delayed work for request timeouts */ + INIT_DELAYED_WORK(&_host->delayed_reset_work, tmio_mmc_reset_work); + INIT_WORK(&_host->done, tmio_mmc_done_work); + + /* See if we also get DMA */ + tmio_mmc_request_dma(_host, pdata); + + mmc_add_host(mmc); + + dev_pm_qos_expose_latency_limit(&pdev->dev, 100); + + /* 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; + if (!_host->native_hotplug) + irq_mask &= ~(TMIO_STAT_CARD_REMOVE | TMIO_STAT_CARD_INSERT); + + tmio_mmc_enable_mmc_irqs(_host, irq_mask); + + if (pdata->flags & TMIO_MMC_USE_GPIO_CD) { + ret = mmc_cd_gpio_request(mmc, pdata->cd_gpio); + if (ret < 0) { + tmio_mmc_host_remove(_host); + return ret; + } + } + + *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; + struct tmio_mmc_data *pdata = host->pdata; + struct mmc_host *mmc = host->mmc; + + if (pdata->flags & TMIO_MMC_USE_GPIO_CD) + /* + * This means we can miss a card-eject, but this is anyway + * possible, because of delayed processing of hotplug events. + */ + mmc_cd_gpio_free(mmc); + + if (!host->native_hotplug) + pm_runtime_get_sync(&pdev->dev); + + dev_pm_qos_hide_latency_limit(&pdev->dev); + + mmc_remove_host(mmc); + cancel_work_sync(&host->done); + 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(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); + + 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); + + tmio_mmc_reset(host); + tmio_mmc_enable_dma(host, true); + + /* The MMC core will perform the complete set up */ + 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); + + tmio_mmc_reset(host); + tmio_mmc_enable_dma(host, true); + + 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..c0105a2e --- /dev/null +++ b/drivers/mmc/host/ushc.c @@ -0,0 +1,569 @@ +/* + * 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, +}; + +module_usb_driver(ushc_driver); + +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..4b83c43f --- /dev/null +++ b/drivers/mmc/host/via-sdmmc.c @@ -0,0 +1,1358 @@ +/* + * 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/module.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) { + pr_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..3135a1a5 --- /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 bool 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 bool 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 bool disable_offload_processing; +module_param(disable_offload_processing, bool, 0644); +MODULE_PARM_DESC(disable_offload_processing, "Disable Offload Processing"); + +static bool 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 bool 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 occurred sending the command + * or receiving 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..64acd9ce --- /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; + + pr_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) { + pr_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 + pr_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) { + pr_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: + pr_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)) { + pr_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)) { + pr_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); + + pr_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)) { + pr_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; + + pr_info(DRIVER_NAME + ": Winbond W83L51xD SD/MMC card interface driver\n"); + pr_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 */ +}; |