aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/ABI/testing/sysfs-devices-mmc21
-rw-r--r--arch/arm/mach-mxs/include/mach/mmc.h18
-rw-r--r--arch/arm/plat-mxc/include/mach/esdhc.h12
-rw-r--r--drivers/mmc/card/Kconfig3
-rw-r--r--drivers/mmc/card/block.c1
-rw-r--r--drivers/mmc/card/mmc_test.c271
-rw-r--r--drivers/mmc/core/Makefile3
-rw-r--r--drivers/mmc/core/core.c26
-rw-r--r--drivers/mmc/core/core.h2
-rw-r--r--drivers/mmc/core/host.c5
-rw-r--r--drivers/mmc/core/mmc.c86
-rw-r--r--drivers/mmc/core/quirks.c84
-rw-r--r--drivers/mmc/core/sd.c1
-rw-r--r--drivers/mmc/core/sdio.c9
-rw-r--r--drivers/mmc/host/Kconfig11
-rw-r--r--drivers/mmc/host/Makefile1
-rw-r--r--drivers/mmc/host/atmel-mci.c19
-rw-r--r--drivers/mmc/host/cb710-mmc.c2
-rw-r--r--drivers/mmc/host/dw_mmc.c83
-rw-r--r--drivers/mmc/host/dw_mmc.h2
-rw-r--r--drivers/mmc/host/msm_sdcc.c8
-rw-r--r--drivers/mmc/host/mxcmmc.c181
-rw-r--r--drivers/mmc/host/mxs-mmc.c874
-rw-r--r--drivers/mmc/host/sdhci-esdhc-imx.c134
-rw-r--r--drivers/mmc/host/sdhci-esdhc.h1
-rw-r--r--drivers/mmc/host/sdhci-of-esdhc.c3
-rw-r--r--drivers/mmc/host/sdhci-pci.c12
-rw-r--r--drivers/mmc/host/sdhci-s3c.c3
-rw-r--r--drivers/mmc/host/sdhci-tegra.c6
-rw-r--r--drivers/mmc/host/sh_mmcif.c62
-rw-r--r--drivers/mmc/host/tmio_mmc.c96
-rw-r--r--drivers/mmc/host/via-sdmmc.c3
-rw-r--r--include/linux/mmc/card.h6
-rw-r--r--include/linux/mmc/core.h1
-rw-r--r--include/linux/mmc/dw_mmc.h15
-rw-r--r--include/linux/mmc/mmc.h3
36 files changed, 1810 insertions, 258 deletions
diff --git a/Documentation/ABI/testing/sysfs-devices-mmc b/Documentation/ABI/testing/sysfs-devices-mmc
new file mode 100644
index 00000000000..5a50ab65584
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-devices-mmc
@@ -0,0 +1,21 @@
+What: /sys/devices/.../mmc_host/mmcX/mmcX:XXXX/enhanced_area_offset
+Date: January 2011
+Contact: Chuanxiao Dong <chuanxiao.dong@intel.com>
+Description:
+ Enhanced area is a new feature defined in eMMC4.4 standard.
+ eMMC4.4 or later card can support such feature. This kind of
+ area can help to improve the card performance. If the feature
+ is enabled, this attribute will indicate the start address of
+ enhanced data area. If not, this attribute will be -EINVAL.
+ Unit Byte. Format decimal.
+
+What: /sys/devices/.../mmc_host/mmcX/mmcX:XXXX/enhanced_area_size
+Date: January 2011
+Contact: Chuanxiao Dong <chuanxiao.dong@intel.com>
+Description:
+ Enhanced area is a new feature defined in eMMC4.4 standard.
+ eMMC4.4 or later card can support such feature. This kind of
+ area can help to improve the card performance. If the feature
+ is enabled, this attribute will indicate the size of enhanced
+ data area. If not, this attribute will be -EINVAL.
+ Unit KByte. Format decimal.
diff --git a/arch/arm/mach-mxs/include/mach/mmc.h b/arch/arm/mach-mxs/include/mach/mmc.h
new file mode 100644
index 00000000000..211547a0556
--- /dev/null
+++ b/arch/arm/mach-mxs/include/mach/mmc.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2011 Freescale Semiconductor, 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 version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __MACH_MXS_MMC_H__
+#define __MACH_MXS_MMC_H__
+
+struct mxs_mmc_platform_data {
+ int wp_gpio; /* write protect pin */
+ unsigned int flags;
+#define SLOTF_4_BIT_CAPABLE (1 << 0)
+#define SLOTF_8_BIT_CAPABLE (1 << 1)
+};
+#endif /* __MACH_MXS_MMC_H__ */
diff --git a/arch/arm/plat-mxc/include/mach/esdhc.h b/arch/arm/plat-mxc/include/mach/esdhc.h
index a48a9aaa56b..86003f41175 100644
--- a/arch/arm/plat-mxc/include/mach/esdhc.h
+++ b/arch/arm/plat-mxc/include/mach/esdhc.h
@@ -10,7 +10,17 @@
#ifndef __ASM_ARCH_IMX_ESDHC_H
#define __ASM_ARCH_IMX_ESDHC_H
+/**
+ * struct esdhc_platform_data - optional platform data for esdhc on i.MX
+ *
+ * strongly recommended for i.MX25/35, not needed for other variants
+ *
+ * @wp_gpio: gpio for write_protect (-EINVAL if unused)
+ * @cd_gpio: gpio for card_detect interrupt (-EINVAL if unused)
+ */
+
struct esdhc_platform_data {
- unsigned int wp_gpio; /* write protect pin */
+ unsigned int wp_gpio;
+ unsigned int cd_gpio;
};
#endif /* __ASM_ARCH_IMX_ESDHC_H */
diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig
index 2a876c4099c..3b1f783bf92 100644
--- a/drivers/mmc/card/Kconfig
+++ b/drivers/mmc/card/Kconfig
@@ -58,12 +58,11 @@ config SDIO_UART
config MMC_TEST
tristate "MMC host test driver"
- default n
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 sysfs under each card. Note that whatever is
+ "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
diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c
index bfc8a8ae55d..61d233a7c11 100644
--- a/drivers/mmc/card/block.c
+++ b/drivers/mmc/card/block.c
@@ -621,6 +621,7 @@ static struct mmc_blk_data *mmc_blk_alloc(struct mmc_card *card)
md->disk->private_data = md;
md->disk->queue = md->queue.queue;
md->disk->driverfs_dev = &card->dev;
+ set_disk_ro(md->disk, md->read_only);
/*
* As discussed on lkml, GENHD_FL_REMOVABLE should:
diff --git a/drivers/mmc/card/mmc_test.c b/drivers/mmc/card/mmc_test.c
index 21adc27f413..5ec8eddfcf6 100644
--- a/drivers/mmc/card/mmc_test.c
+++ b/drivers/mmc/card/mmc_test.c
@@ -88,6 +88,7 @@ struct mmc_test_area {
* @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;
@@ -95,6 +96,7 @@ struct mmc_test_transfer_result {
unsigned int sectors;
struct timespec ts;
unsigned int rate;
+ unsigned int iops;
};
/**
@@ -226,9 +228,10 @@ static int mmc_test_wait_busy(struct mmc_test_card *test)
if (!busy && mmc_test_busy(&cmd)) {
busy = 1;
- printk(KERN_INFO "%s: Warning: Host did not "
- "wait for busy state to end.\n",
- mmc_hostname(test->card->host));
+ if (test->card->host->caps & MMC_CAP_WAIT_WHILE_BUSY)
+ printk(KERN_INFO "%s: Warning: Host did not "
+ "wait for busy state to end.\n",
+ mmc_hostname(test->card->host));
}
} while (mmc_test_busy(&cmd));
@@ -494,7 +497,7 @@ static unsigned int mmc_test_rate(uint64_t bytes, struct timespec *ts)
*/
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 rate, unsigned int iops)
{
struct mmc_test_transfer_result *tr;
@@ -509,6 +512,7 @@ static void mmc_test_save_transfer_result(struct mmc_test_card *test,
tr->sectors = sectors;
tr->ts = ts;
tr->rate = rate;
+ tr->iops = iops;
list_add_tail(&tr->link, &test->gr->tr_lst);
}
@@ -519,20 +523,22 @@ static void mmc_test_save_transfer_result(struct mmc_test_card *test,
static void mmc_test_print_rate(struct mmc_test_card *test, uint64_t bytes,
struct timespec *ts1, struct timespec *ts2)
{
- unsigned int rate, sectors = bytes >> 9;
+ unsigned int rate, iops, sectors = bytes >> 9;
struct timespec ts;
ts = timespec_sub(*ts2, *ts1);
rate = mmc_test_rate(bytes, &ts);
+ iops = mmc_test_rate(100, &ts); /* I/O ops per sec x 100 */
printk(KERN_INFO "%s: Transfer of %u sectors (%u%s KiB) took %lu.%09lu "
- "seconds (%u kB/s, %u KiB/s)\n",
+ "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);
+ (unsigned long)ts.tv_nsec, rate / 1000, rate / 1024,
+ iops / 100, iops % 100);
- mmc_test_save_transfer_result(test, 1, sectors, ts, rate);
+ mmc_test_save_transfer_result(test, 1, sectors, ts, rate, iops);
}
/*
@@ -542,22 +548,24 @@ 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, sectors = bytes >> 9;
+ unsigned int rate, iops, sectors = bytes >> 9;
uint64_t tot = bytes * count;
struct timespec ts;
ts = timespec_sub(*ts2, *ts1);
rate = mmc_test_rate(tot, &ts);
+ iops = mmc_test_rate(count * 100, &ts); /* I/O ops per sec x 100 */
printk(KERN_INFO "%s: Transfer of %u x %u sectors (%u x %u%s KiB) took "
- "%lu.%09lu seconds (%u kB/s, %u KiB/s)\n",
+ "%lu.%09lu seconds (%u kB/s, %u KiB/s, "
+ "%u.%02u IOPS)\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);
+ rate / 1000, rate / 1024, iops / 100, iops % 100);
- mmc_test_save_transfer_result(test, count, sectors, ts, rate);
+ mmc_test_save_transfer_result(test, count, sectors, ts, rate, iops);
}
/*
@@ -1425,28 +1433,29 @@ static int mmc_test_area_cleanup(struct mmc_test_card *test)
}
/*
- * Initialize an area for testing large transfers. The size of the area is the
- * preferred erase size which is a good size for optimal transfer speed. Note
- * that is typically 4MiB for modern cards. 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.
+ * 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;
+ unsigned long min_sz = 64 * 1024, sz;
int ret;
ret = mmc_test_set_blksize(test, 512);
if (ret)
return ret;
- if (test->card->pref_erase > TEST_AREA_MAX_SIZE >> 9)
- t->max_sz = TEST_AREA_MAX_SIZE;
- else
- t->max_sz = (unsigned long)test->card->pref_erase << 9;
+ /* 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;
@@ -1766,6 +1775,188 @@ static int mmc_test_profile_seq_trim_perf(struct mmc_test_card *test)
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)
+{
+ unsigned int next;
+ unsigned long sz;
+ int ret;
+
+ for (sz = 512; sz < test->area.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 = test->area.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)
+{
+ unsigned int dev_addr, i, cnt, sz, ssz;
+ struct timespec ts1, ts2, ts;
+ int ret;
+
+ sz = test->area.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) {
+ 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;
+ }
+
+ 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);
+
+ ts = timespec_sub(ts2, ts1);
+ 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 const struct mmc_test_case mmc_test_cases[] = {
{
.name = "Basic write (no data verification)",
@@ -2005,6 +2196,34 @@ static const struct mmc_test_case mmc_test_cases[] = {
.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,
+ },
+
};
static DEFINE_MUTEX(mmc_test_lock);
@@ -2148,11 +2367,11 @@ static int mtf_test_show(struct seq_file *sf, void *data)
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\n",
+ 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->rate, tr->iops / 100, tr->iops % 100);
}
}
diff --git a/drivers/mmc/core/Makefile b/drivers/mmc/core/Makefile
index 86b47911933..639501970b4 100644
--- a/drivers/mmc/core/Makefile
+++ b/drivers/mmc/core/Makefile
@@ -6,6 +6,7 @@ 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
+ sdio_cis.o sdio_io.o sdio_irq.o \
+ quirks.o
mmc_core-$(CONFIG_DEBUG_FS) += debugfs.o
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index 150b5f3cd40..1f453acc868 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -167,8 +167,6 @@ mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
WARN_ON(!host->claimed);
- led_trigger_event(host->led, LED_FULL);
-
mrq->cmd->error = 0;
mrq->cmd->mrq = mrq;
if (mrq->data) {
@@ -194,6 +192,7 @@ mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
}
}
mmc_host_clk_ungate(host);
+ led_trigger_event(host->led, LED_FULL);
host->ops->request(host, mrq);
}
@@ -528,7 +527,14 @@ int mmc_try_claim_host(struct mmc_host *host)
}
EXPORT_SYMBOL(mmc_try_claim_host);
-static void mmc_do_release_host(struct mmc_host *host)
+/**
+ * mmc_do_release_host - release a claimed host
+ * @host: mmc host to release
+ *
+ * If you successfully claimed a host, this function will
+ * release it again.
+ */
+void mmc_do_release_host(struct mmc_host *host)
{
unsigned long flags;
@@ -543,6 +549,7 @@ static void mmc_do_release_host(struct mmc_host *host)
wake_up(&host->wq);
}
}
+EXPORT_SYMBOL(mmc_do_release_host);
void mmc_host_deeper_disable(struct work_struct *work)
{
@@ -1002,6 +1009,13 @@ static void mmc_power_off(struct mmc_host *host)
{
host->ios.clock = 0;
host->ios.vdd = 0;
+
+ /*
+ * Reset ocr mask to be the highest possible voltage supported for
+ * this mmc host. This value will be used at next power up.
+ */
+ host->ocr = 1 << (fls(host->ocr_avail) - 1);
+
if (!mmc_host_is_spi(host)) {
host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN;
host->ios.chip_select = MMC_CS_DONTCARE;
@@ -1495,6 +1509,12 @@ static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
mmc_hostname(host), __func__, host->f_init);
#endif
mmc_power_up(host);
+
+ /*
+ * sdio_reset sends CMD52 to reset card. Since we do not know
+ * if the card is being re-initialized, just send it. CMD52
+ * should be ignored by SD/eMMC cards.
+ */
sdio_reset(host);
mmc_go_idle(host);
diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h
index ca1fdde29df..20b1c0831ea 100644
--- a/drivers/mmc/core/core.h
+++ b/drivers/mmc/core/core.h
@@ -61,6 +61,8 @@ int mmc_attach_mmc(struct mmc_host *host);
int mmc_attach_sd(struct mmc_host *host);
int mmc_attach_sdio(struct mmc_host *host);
+void mmc_fixup_device(struct mmc_card *card);
+
/* Module parameters */
extern int use_spi_crc;
diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c
index b3ac6c5bc5c..461e6a17fb9 100644
--- a/drivers/mmc/core/host.c
+++ b/drivers/mmc/core/host.c
@@ -160,10 +160,7 @@ static bool mmc_host_may_gate_card(struct mmc_card *card)
* gate the clock, because there is somebody out there that may still
* be using it.
*/
- if (mmc_card_sdio(card))
- return false;
-
- return true;
+ return !(card->quirks & MMC_QUIRK_BROKEN_CLK_GATING);
}
/**
diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
index 16006ef153f..14e95f39a7b 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -302,6 +302,44 @@ static int mmc_read_ext_csd(struct mmc_card *card)
}
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.
+ */
+ if ((ext_csd[EXT_CSD_PARTITION_SUPPORT] & 0x2) &&
+ (ext_csd[EXT_CSD_PARTITION_ATTRIBUTE] & 0x1)) {
+ u8 hc_erase_grp_sz =
+ ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE];
+ u8 hc_wp_grp_sz =
+ ext_csd[EXT_CSD_HC_WP_GRP_SIZE];
+
+ card->ext_csd.enhanced_area_en = 1;
+ /*
+ * calculate the enhanced data area offset, in bytes
+ */
+ card->ext_csd.enhanced_area_offset =
+ (ext_csd[139] << 24) + (ext_csd[138] << 16) +
+ (ext_csd[137] << 8) + ext_csd[136];
+ if (mmc_card_blockaddr(card))
+ card->ext_csd.enhanced_area_offset <<= 9;
+ /*
+ * calculate the enhanced data area size, in kilobytes
+ */
+ card->ext_csd.enhanced_area_size =
+ (ext_csd[142] << 16) + (ext_csd[141] << 8) +
+ ext_csd[140];
+ card->ext_csd.enhanced_area_size *=
+ (size_t)(hc_erase_grp_sz * hc_wp_grp_sz);
+ card->ext_csd.enhanced_area_size <<= 9;
+ } else {
+ /*
+ * If the enhanced area is not enabled, disable these
+ * device attributes.
+ */
+ card->ext_csd.enhanced_area_offset = -EINVAL;
+ card->ext_csd.enhanced_area_size = -EINVAL;
+ }
card->ext_csd.sec_trim_mult =
ext_csd[EXT_CSD_SEC_TRIM_MULT];
card->ext_csd.sec_erase_mult =
@@ -336,6 +374,9 @@ 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,
@@ -349,6 +390,8 @@ static struct attribute *mmc_std_attrs[] = {
&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,
};
@@ -378,6 +421,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
int err, ddr = 0;
u32 cid[4];
unsigned int max_dtr;
+ u32 rocr;
BUG_ON(!host);
WARN_ON(!host->claimed);
@@ -391,7 +435,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
mmc_go_idle(host);
/* The extra bit indicates that we support high capacity */
- err = mmc_send_op_cond(host, ocr | (1 << 30), NULL);
+ err = mmc_send_op_cond(host, ocr | (1 << 30), &rocr);
if (err)
goto err;
@@ -479,11 +523,51 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
err = mmc_read_ext_csd(card);
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 everytime after a reset or power off.
+ */
+ if (card->ext_csd.enhanced_area_en) {
+ err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_ERASE_GROUP_DEF, 1);
+
+ 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);
+ }
+ }
+
+ /*
* Activate high speed (if supported)
*/
if ((card->ext_csd.hs_max_dtr != 0) &&
diff --git a/drivers/mmc/core/quirks.c b/drivers/mmc/core/quirks.c
new file mode 100644
index 00000000000..11118b74eb2
--- /dev/null
+++ b/drivers/mmc/core/quirks.c
@@ -0,0 +1,84 @@
+/*
+ * This file contains work-arounds for many known sdio hardware
+ * bugs.
+ *
+ * Copyright (c) 2011 Pierre Tardy <tardyp@gmail.com>
+ * Inspired from pci fixup code:
+ * Copyright (c) 1999 Martin Mares <mj@ucw.cz>
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/mmc/card.h>
+#include <linux/mod_devicetable.h>
+
+/*
+ * The world is not perfect and supplies us with broken mmc/sdio devices.
+ * For at least a part of these bugs we need a work-around
+ */
+
+struct mmc_fixup {
+ u16 vendor, device; /* You can use SDIO_ANY_ID here of course */
+ void (*vendor_fixup)(struct mmc_card *card, int data);
+ int data;
+};
+
+/*
+ * This hook just adds a quirk unconditionnally
+ */
+static void __maybe_unused add_quirk(struct mmc_card *card, int data)
+{
+ card->quirks |= data;
+}
+
+/*
+ * This hook just removes a quirk unconditionnally
+ */
+static void __maybe_unused remove_quirk(struct mmc_card *card, int data)
+{
+ card->quirks &= ~data;
+}
+
+/*
+ * 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;
+}
+
+#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
+
+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_ANY_ID, SDIO_ANY_ID,
+ add_quirk_for_sdio_devices, MMC_QUIRK_BROKEN_CLK_GATING },
+ { SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271,
+ remove_quirk, MMC_QUIRK_BROKEN_CLK_GATING },
+ { 0 }
+};
+
+void mmc_fixup_device(struct mmc_card *card)
+{
+ const struct mmc_fixup *f;
+
+ for (f = mmc_fixup_methods; f->vendor_fixup; f++) {
+ if ((f->vendor == card->cis.vendor
+ || f->vendor == (u16) SDIO_ANY_ID) &&
+ (f->device == card->cis.device
+ ||