summaryrefslogtreecommitdiff
path: root/drivers/mmc/core
diff options
context:
space:
mode:
authorBenn Pörscke <benn.porscke@stericsson.com>2011-10-07 15:31:57 +0200
committerBenn Pörscke <benn.porscke@stericsson.com>2011-10-07 15:31:57 +0200
commit47a4dbf83a75014d6b3467be18997894f1c617db (patch)
tree7f5d116db48205309fbc4ae0954f20ab8a651e46 /drivers/mmc/core
parentea8a52f9f4bcc3420c38ae07f8378a2f18443970 (diff)
Change-Id: If0ae9fa8067740ab2ede33703c79ec134f204a5e
Diffstat (limited to 'drivers/mmc/core')
-rw-r--r--drivers/mmc/core/Kconfig17
-rw-r--r--drivers/mmc/core/bus.c6
-rw-r--r--drivers/mmc/core/core.c156
-rw-r--r--drivers/mmc/core/core.h2
-rw-r--r--drivers/mmc/core/host.c15
-rw-r--r--drivers/mmc/core/mmc.c82
-rw-r--r--drivers/mmc/core/mmc_ops.c23
-rw-r--r--drivers/mmc/core/sd.c88
-rw-r--r--drivers/mmc/core/sdio.c175
-rw-r--r--drivers/mmc/core/sdio_bus.c13
-rwxr-xr-x[-rw-r--r--]drivers/mmc/core/sdio_io.c37
-rw-r--r--drivers/mmc/core/sdio_irq.c150
-rw-r--r--drivers/mmc/core/sdio_ops.c9
13 files changed, 630 insertions, 143 deletions
diff --git a/drivers/mmc/core/Kconfig b/drivers/mmc/core/Kconfig
index bb22ffd76ef..0eba6658233 100644
--- a/drivers/mmc/core/Kconfig
+++ b/drivers/mmc/core/Kconfig
@@ -16,3 +16,20 @@ config MMC_UNSAFE_RESUME
This option sets a default which can be overridden by the
module parameter "removable=0" or "removable=1".
+
+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/bus.c b/drivers/mmc/core/bus.c
index 49d9dcaeca4..58adef173ba 100644
--- a/drivers/mmc/core/bus.c
+++ b/drivers/mmc/core/bus.c
@@ -245,14 +245,16 @@ int mmc_add_card(struct mmc_card *card)
}
if (mmc_host_is_spi(card->host)) {
- printk(KERN_INFO "%s: new %s%s card on SPI\n",
+ printk(KERN_INFO "%s: new %s%s%s card on SPI\n",
mmc_hostname(card->host),
mmc_card_highspeed(card) ? "high speed " : "",
+ mmc_card_ddr_mode(card) ? "DDR " : "",
type);
} else {
- printk(KERN_INFO "%s: new %s%s card at address %04x\n",
+ printk(KERN_INFO "%s: new %s%s%s card at address %04x\n",
mmc_hostname(card->host),
mmc_card_highspeed(card) ? "high speed " : "",
+ mmc_card_ddr_mode(card) ? "DDR " : "",
type, card->rca);
}
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index 83e7543b55d..221e0e76384 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -22,6 +22,7 @@
#include <linux/scatterlist.h>
#include <linux/log2.h>
#include <linux/regulator/consumer.h>
+#include <linux/wakelock.h>
#include <linux/mmc/card.h>
#include <linux/mmc/host.h>
@@ -386,6 +387,8 @@ int mmc_host_enable(struct mmc_host *host)
if (host->ops->enable) {
int err;
+ wake_lock(&host->wakelock);
+
host->en_dis_recurs = 1;
err = host->ops->enable(host);
host->en_dis_recurs = 0;
@@ -393,6 +396,7 @@ int mmc_host_enable(struct mmc_host *host)
if (err) {
pr_debug("%s: enable error %d\n",
mmc_hostname(host), err);
+ wake_unlock(&host->wakelock);
return err;
}
}
@@ -420,6 +424,8 @@ static int mmc_host_do_disable(struct mmc_host *host, int lazy)
mmc_schedule_delayed_work(&host->disable, delay);
}
+ if (err == 0)
+ wake_unlock(&host->wakelock);
}
host->enabled = 0;
return 0;
@@ -522,7 +528,7 @@ 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)
+void mmc_do_release_host(struct mmc_host *host)
{
unsigned long flags;
@@ -537,6 +543,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)
{
@@ -650,14 +657,24 @@ void mmc_set_bus_mode(struct mmc_host *host, unsigned int mode)
}
/*
- * Change data bus width of a host.
+ * Change data bus width and DDR mode of a host.
*/
-void mmc_set_bus_width(struct mmc_host *host, unsigned int width)
+void mmc_set_bus_width_ddr(struct mmc_host *host, unsigned int width,
+ unsigned int ddr)
{
host->ios.bus_width = width;
+ host->ios.ddr = ddr;
mmc_set_ios(host);
}
+/*
+ * Change data bus width of a host.
+ */
+void mmc_set_bus_width(struct mmc_host *host, unsigned int width)
+{
+ mmc_set_bus_width_ddr(host, width, MMC_SDR_MODE);
+}
+
/**
* mmc_vdd_to_ocrbitnum - Convert a voltage to the OCR bit number
* @vdd: voltage (mV)
@@ -771,8 +788,9 @@ EXPORT_SYMBOL(mmc_regulator_get_ocrmask);
/**
* mmc_regulator_set_ocr - set regulator to match host->ios voltage
- * @vdd_bit: zero for power off, else a bit number (host->ios.vdd)
+ * @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.
*
@@ -780,15 +798,12 @@ EXPORT_SYMBOL(mmc_regulator_get_ocrmask);
* a particular supply voltage. This would normally be called from the
* set_ios() method.
*/
-int mmc_regulator_set_ocr(struct regulator *supply, unsigned short vdd_bit)
+int mmc_regulator_set_ocr(struct mmc_host *mmc,
+ struct regulator *supply,
+ unsigned short vdd_bit)
{
int result = 0;
int min_uV, max_uV;
- int enabled;
-
- enabled = regulator_is_enabled(supply);
- if (enabled < 0)
- return enabled;
if (vdd_bit) {
int tmp;
@@ -819,17 +834,25 @@ int mmc_regulator_set_ocr(struct regulator *supply, unsigned short vdd_bit)
else
result = 0;
- if (result == 0 && !enabled)
+ if (result == 0 && !mmc->regulator_enabled) {
result = regulator_enable(supply);
- } else if (enabled) {
+ if (!result)
+ mmc->regulator_enabled = true;
+ }
+ } else if (mmc->regulator_enabled) {
result = regulator_disable(supply);
+ if (result == 0)
+ mmc->regulator_enabled = false;
}
+ if (result)
+ dev_err(mmc_dev(mmc),
+ "could not set regulator OCR (%d)\n", result);
return result;
}
EXPORT_SYMBOL(mmc_regulator_set_ocr);
-#endif
+#endif /* CONFIG_REGULATOR */
/*
* Mask off any voltages we don't support and select
@@ -907,12 +930,7 @@ static void mmc_power_up(struct mmc_host *host)
*/
mmc_delay(10);
- if (host->f_min > 400000) {
- pr_warning("%s: Minimum clock frequency too high for "
- "identification mode\n", mmc_hostname(host));
- host->ios.clock = host->f_min;
- } else
- host->ios.clock = 400000;
+ host->ios.clock = host->f_min;
host->ios.power_mode = MMC_POWER_ON;
mmc_set_ios(host);
@@ -928,6 +946,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;
@@ -977,6 +1002,36 @@ static inline void mmc_bus_put(struct mmc_host *host)
spin_unlock_irqrestore(&host->lock, flags);
}
+int mmc_resume_bus(struct mmc_host *host)
+{
+ unsigned long flags;
+
+ if (!mmc_bus_needs_resume(host))
+ return -EINVAL;
+
+ printk("%s: Starting deferred resume\n", mmc_hostname(host));
+ spin_lock_irqsave(&host->lock, flags);
+ host->bus_resume_flags &= ~MMC_BUSRESUME_NEEDS_RESUME;
+ host->rescan_disable = 0;
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ mmc_bus_get(host);
+ if (host->bus_ops && !host->bus_dead) {
+ mmc_power_up(host);
+ BUG_ON(!host->bus_ops->resume);
+ host->bus_ops->resume(host);
+ }
+
+ if (host->bus_ops->detect && !host->bus_dead)
+ host->bus_ops->detect(host);
+
+ mmc_bus_put(host);
+ printk("%s: Deferred resume completed\n", mmc_hostname(host));
+ return 0;
+}
+
+EXPORT_SYMBOL(mmc_resume_bus);
+
/*
* Assign a mmc bus handler to a host. Only one bus handler may control a
* host at any given time.
@@ -1050,6 +1105,20 @@ void mmc_detect_change(struct mmc_host *host, unsigned long delay)
EXPORT_SYMBOL(mmc_detect_change);
+int mmc_set_blocklen(struct mmc_card *card, unsigned int blocklen)
+{
+ struct mmc_command cmd;
+
+ if (mmc_card_blockaddr(card) || mmc_card_ddr_mode(card))
+ return 0;
+
+ memset(&cmd, 0, sizeof(struct mmc_command));
+ cmd.opcode = MMC_SET_BLOCKLEN;
+ cmd.arg = blocklen;
+ cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC;
+ return mmc_wait_for_cmd(card->host, &cmd, 5);
+}
+EXPORT_SYMBOL(mmc_set_blocklen);
void mmc_rescan(struct work_struct *work)
{
@@ -1058,6 +1127,7 @@ void mmc_rescan(struct work_struct *work)
u32 ocr;
int err;
unsigned long flags;
+ int extend_wakelock = 0;
spin_lock_irqsave(&host->lock, flags);
@@ -1075,6 +1145,12 @@ void mmc_rescan(struct work_struct *work)
if ((host->bus_ops != NULL) && host->bus_ops->detect && !host->bus_dead)
host->bus_ops->detect(host);
+ /* If the card was removed the bus will be marked
+ * as dead - extend the wakelock so userspace
+ * can respond */
+ if (host->bus_dead)
+ extend_wakelock = 1;
+
mmc_bus_put(host);
@@ -1112,6 +1188,7 @@ void mmc_rescan(struct work_struct *work)
if (!err) {
if (mmc_attach_sdio(host, ocr))
mmc_power_off(host);
+ extend_wakelock = 1;
goto out;
}
@@ -1122,6 +1199,7 @@ void mmc_rescan(struct work_struct *work)
if (!err) {
if (mmc_attach_sd(host, ocr))
mmc_power_off(host);
+ extend_wakelock = 1;
goto out;
}
@@ -1132,6 +1210,7 @@ void mmc_rescan(struct work_struct *work)
if (!err) {
if (mmc_attach_mmc(host, ocr))
mmc_power_off(host);
+ extend_wakelock = 1;
goto out;
}
@@ -1139,6 +1218,9 @@ void mmc_rescan(struct work_struct *work)
mmc_power_off(host);
out:
+ if (extend_wakelock)
+ wake_lock_timeout(&host->wakelock, HZ / 2);
+
if (host->caps & MMC_CAP_NEEDS_POLL)
mmc_schedule_delayed_work(&host->detect, HZ);
}
@@ -1268,6 +1350,9 @@ int mmc_suspend_host(struct mmc_host *host)
{
int err = 0;
+ if (mmc_bus_needs_resume(host))
+ return 0;
+
if (host->caps & MMC_CAP_DISABLE)
cancel_delayed_work(&host->disable);
cancel_delayed_work(&host->detect);
@@ -1275,7 +1360,8 @@ int mmc_suspend_host(struct mmc_host *host)
mmc_bus_get(host);
if (host->bus_ops && !host->bus_dead) {
- if (host->bus_ops->suspend)
+ if (host->bus_ops->suspend &&
+ (host->bus_resume_flags & MMC_NEEDS_UNSAFE_RESUME))
err = host->bus_ops->suspend(host);
}
mmc_bus_put(host);
@@ -1297,6 +1383,12 @@ int mmc_resume_host(struct mmc_host *host)
int err = 0;
mmc_bus_get(host);
+ if (mmc_bus_manual_resume(host)) {
+ host->bus_resume_flags |= MMC_BUSRESUME_NEEDS_RESUME;
+ mmc_bus_put(host);
+ return 0;
+ }
+
if (host->bus_ops && !host->bus_dead) {
if (!(host->pm_flags & MMC_PM_KEEP_POWER)) {
mmc_power_up(host);
@@ -1334,6 +1426,10 @@ int mmc_pm_notify(struct notifier_block *notify_block,
case PM_SUSPEND_PREPARE:
spin_lock_irqsave(&host->lock, flags);
+ if (mmc_bus_needs_resume(host)) {
+ spin_unlock_irqrestore(&host->lock, flags);
+ break;
+ }
host->rescan_disable = 1;
spin_unlock_irqrestore(&host->lock, flags);
cancel_delayed_work_sync(&host->detect);
@@ -1355,6 +1451,10 @@ int mmc_pm_notify(struct notifier_block *notify_block,
case PM_POST_HIBERNATION:
spin_lock_irqsave(&host->lock, flags);
+ if (mmc_bus_manual_resume(host)) {
+ spin_unlock_irqrestore(&host->lock, flags);
+ break;
+ }
host->rescan_disable = 0;
spin_unlock_irqrestore(&host->lock, flags);
mmc_detect_change(host, 0);
@@ -1365,6 +1465,22 @@ int mmc_pm_notify(struct notifier_block *notify_block,
}
#endif
+#ifdef CONFIG_MMC_EMBEDDED_SDIO
+void mmc_set_embedded_sdio_data(struct mmc_host *host,
+ struct sdio_cis *cis,
+ struct sdio_cccr *cccr,
+ struct sdio_embedded_func *funcs,
+ int num_funcs)
+{
+ host->embedded_sdio_data.cis = cis;
+ host->embedded_sdio_data.cccr = cccr;
+ host->embedded_sdio_data.funcs = funcs;
+ host->embedded_sdio_data.num_funcs = num_funcs;
+}
+
+EXPORT_SYMBOL(mmc_set_embedded_sdio_data);
+#endif
+
static int __init mmc_init(void)
{
int ret;
diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h
index a811c52a165..eccd0f4e52c 100644
--- a/drivers/mmc/core/core.h
+++ b/drivers/mmc/core/core.h
@@ -33,6 +33,8 @@ void mmc_set_chip_select(struct mmc_host *host, int mode);
void mmc_set_clock(struct mmc_host *host, unsigned int hz);
void mmc_set_bus_mode(struct mmc_host *host, unsigned int mode);
void mmc_set_bus_width(struct mmc_host *host, unsigned int width);
+void mmc_set_bus_width_ddr(struct mmc_host *host, unsigned int width,
+ unsigned int ddr);
u32 mmc_select_voltage(struct mmc_host *host, u32 ocr);
void mmc_set_timing(struct mmc_host *host, unsigned int timing);
diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c
index d80cfdc8edd..53351f29860 100644
--- a/drivers/mmc/core/host.c
+++ b/drivers/mmc/core/host.c
@@ -90,12 +90,15 @@ struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
host->pm_notify.notifier_call = mmc_pm_notify;
#endif
+ snprintf(host->wakelock_name, sizeof(host->wakelock_name),
+ "mmc%d_delay_work", host->index);
+ wake_lock_init(&host->wakelock, WAKE_LOCK_SUSPEND, host->wakelock_name);
+
/*
* By default, hosts do not support SGIO or large requests.
* They have to set these according to their abilities.
*/
- host->max_hw_segs = 1;
- host->max_phys_segs = 1;
+ host->max_segs = 1;
host->max_seg_size = PAGE_CACHE_SIZE;
host->max_req_size = PAGE_CACHE_SIZE;
@@ -137,7 +140,8 @@ int mmc_add_host(struct mmc_host *host)
#endif
mmc_start_host(host);
- register_pm_notifier(&host->pm_notify);
+ if (!(host->pm_flags & MMC_PM_IGNORE_PM_NOTIFY))
+ register_pm_notifier(&host->pm_notify);
return 0;
}
@@ -154,7 +158,9 @@ EXPORT_SYMBOL(mmc_add_host);
*/
void mmc_remove_host(struct mmc_host *host)
{
- unregister_pm_notifier(&host->pm_notify);
+ if (!(host->pm_flags & MMC_PM_IGNORE_PM_NOTIFY))
+ unregister_pm_notifier(&host->pm_notify);
+
mmc_stop_host(host);
#ifdef CONFIG_DEBUG_FS
@@ -176,6 +182,7 @@ EXPORT_SYMBOL(mmc_remove_host);
*/
void mmc_free_host(struct mmc_host *host)
{
+ wake_lock_destroy(&host->wakelock);
spin_lock(&mmc_host_lock);
idr_remove(&mmc_host_idr, host->index);
spin_unlock(&mmc_host_lock);
diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
index 89f7a25b7ac..eddba13ced0 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -114,17 +114,18 @@ static int mmc_decode_cid(struct mmc_card *card)
static int mmc_decode_csd(struct mmc_card *card)
{
struct mmc_csd *csd = &card->csd;
- unsigned int e, m, csd_struct;
+ unsigned int e, m;
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_struct = UNSTUFF_BITS(resp, 126, 2);
- if (csd_struct != 1 && csd_struct != 2) {
+ csd->structure = UNSTUFF_BITS(resp, 126, 2);
+ if (csd->structure == 0) {
printk(KERN_ERR "%s: unrecognised CSD structure version %d\n",
- mmc_hostname(card->host), csd_struct);
+ mmc_hostname(card->host), csd->structure);
return -EINVAL;
}
@@ -207,11 +208,22 @@ static int mmc_read_ext_csd(struct mmc_card *card)
goto out;
}
+ /* Version is coded in the CSD_STRUCTURE byte in the EXT_CSD register */
+ if (card->csd.structure == 3) {
+ int ext_csd_struct = ext_csd[EXT_CSD_STRUCTURE];
+ if (ext_csd_struct > 2) {
+ printk(KERN_ERR "%s: unrecognised EXT_CSD structure "
+ "version %d\n", mmc_hostname(card->host),
+ ext_csd_struct);
+ err = -EINVAL;
+ goto out;
+ }
+ }
+
card->ext_csd.rev = ext_csd[EXT_CSD_REV];
if (card->ext_csd.rev > 5) {
- printk(KERN_ERR "%s: unrecognised EXT_CSD structure "
- "version %d\n", mmc_hostname(card->host),
- card->ext_csd.rev);
+ printk(KERN_ERR "%s: unrecognised EXT_CSD revision %d\n",
+ mmc_hostname(card->host), card->ext_csd.rev);
err = -EINVAL;
goto out;
}
@@ -222,11 +234,28 @@ static int mmc_read_ext_csd(struct mmc_card *card)
ext_csd[EXT_CSD_SEC_CNT + 1] << 8 |
ext_csd[EXT_CSD_SEC_CNT + 2] << 16 |
ext_csd[EXT_CSD_SEC_CNT + 3] << 24;
- if (card->ext_csd.sectors)
+
+ /* Cards with density > 2GiB are sector addressed */
+ if (card->ext_csd.sectors > (2u * 1024 * 1024 * 1024) / 512)
mmc_card_set_blockaddr(card);
}
switch (ext_csd[EXT_CSD_CARD_TYPE] & EXT_CSD_CARD_TYPE_MASK) {
+ case EXT_CSD_CARD_TYPE_DDR_52 | EXT_CSD_CARD_TYPE_52 |
+ EXT_CSD_CARD_TYPE_26:
+ card->ext_csd.hs_max_dtr = 52000000;
+ card->ext_csd.card_type = EXT_CSD_CARD_TYPE_DDR_52;
+ break;
+ case EXT_CSD_CARD_TYPE_DDR_1_2V | EXT_CSD_CARD_TYPE_52 |
+ EXT_CSD_CARD_TYPE_26:
+ card->ext_csd.hs_max_dtr = 52000000;
+ card->ext_csd.card_type = EXT_CSD_CARD_TYPE_DDR_1_2V;
+ break;
+ case EXT_CSD_CARD_TYPE_DDR_1_8V | EXT_CSD_CARD_TYPE_52 |
+ EXT_CSD_CARD_TYPE_26:
+ card->ext_csd.hs_max_dtr = 52000000;
+ card->ext_csd.card_type = EXT_CSD_CARD_TYPE_DDR_1_8V;
+ break;
case EXT_CSD_CARD_TYPE_52 | EXT_CSD_CARD_TYPE_26:
card->ext_csd.hs_max_dtr = 52000000;
break;
@@ -303,7 +332,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
struct mmc_card *oldcard)
{
struct mmc_card *card;
- int err;
+ int err, ddr = 0;
u32 cid[4];
unsigned int max_dtr;
@@ -444,17 +473,35 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
mmc_set_clock(host, max_dtr);
/*
- * Activate wide bus (if supported).
+ * 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)))
+ 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)))
+ ddr = MMC_1_2V_DDR_MODE;
+ }
+
+ /*
+ * Activate wide bus and DDR (if supported).
*/
if ((card->csd.mmca_vsn >= CSD_SPEC_VER_4) &&
(host->caps & (MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA))) {
unsigned ext_csd_bit, bus_width;
if (host->caps & MMC_CAP_8_BIT_DATA) {
- ext_csd_bit = EXT_CSD_BUS_WIDTH_8;
+ if (ddr)
+ ext_csd_bit = EXT_CSD_DDR_BUS_WIDTH_8;
+ else
+ ext_csd_bit = EXT_CSD_BUS_WIDTH_8;
bus_width = MMC_BUS_WIDTH_8;
} else {
- ext_csd_bit = EXT_CSD_BUS_WIDTH_4;
+ if (ddr)
+ ext_csd_bit = EXT_CSD_DDR_BUS_WIDTH_4;
+ else
+ ext_csd_bit = EXT_CSD_BUS_WIDTH_4;
bus_width = MMC_BUS_WIDTH_4;
}
@@ -465,12 +512,17 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
goto free_card;
if (err) {
- printk(KERN_WARNING "%s: switch to bus width %d "
+ printk(KERN_WARNING "%s: switch to bus width %d ddr %d "
"failed\n", mmc_hostname(card->host),
- 1 << bus_width);
+ 1 << bus_width, ddr);
err = 0;
} else {
- mmc_set_bus_width(card->host, bus_width);
+ if (ddr)
+ mmc_card_set_ddr_mode(card);
+ else
+ ddr = MMC_SDR_MODE;
+
+ mmc_set_bus_width_ddr(card->host, bus_width, ddr);
}
}
diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c
index 326447c9ede..91ab05aa1d4 100644
--- a/drivers/mmc/core/mmc_ops.c
+++ b/drivers/mmc/core/mmc_ops.c
@@ -202,6 +202,7 @@ int mmc_set_relative_addr(struct mmc_card *card)
{
int err;
struct mmc_command cmd;
+ u32 status;
BUG_ON(!card);
BUG_ON(!card->host);
@@ -216,6 +217,28 @@ int mmc_set_relative_addr(struct mmc_card *card)
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) == 7);
+
+ if (mmc_host_is_spi(card->host)) {
+ if (status & R1_SPI_ILLEGAL_COMMAND)
+ return -EBADMSG;
+ } else {
+ if (status & 0xFDFFA000)
+ printk(KERN_WARNING "%s: unexpected status %#x after "
+ "switch", mmc_hostname(card->host), status);
+ if (status & R1_SWITCH_ERROR)
+ return -EBADMSG;
+ }
+
return 0;
}
diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c
index 5eac21df480..f5e29edbec0 100644
--- a/drivers/mmc/core/sd.c
+++ b/drivers/mmc/core/sd.c
@@ -337,7 +337,9 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
int err;
u32 cid[4];
unsigned int max_dtr;
-
+#ifdef CONFIG_MMC_PARANOID_SD_INIT
+ int retries;
+#endif
BUG_ON(!host);
WARN_ON(!host->claimed);
@@ -440,11 +442,29 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
err = mmc_decode_scr(card);
if (err < 0)
goto free_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)
goto free_card;
}
@@ -539,18 +559,36 @@ static void mmc_sd_remove(struct mmc_host *host)
*/
static void mmc_sd_detect(struct mmc_host *host)
{
- int err;
+ int err = 0;
+#ifdef CONFIG_MMC_PARANOID_SD_INIT
+ int retries = 5;
+#endif
BUG_ON(!host);
BUG_ON(!host->card);
-
+
mmc_claim_host(host);
/*
* Just check if our card has been removed.
*/
+#ifdef CONFIG_MMC_PARANOID_SD_INIT
+ while(retries) {
+ err = mmc_send_status(host->card, NULL);
+ if (err) {
+ retries--;
+ udelay(5);
+ continue;
+ }
+ break;
+ }
+ if (!retries) {
+ printk(KERN_ERR "%s(%s): Unable to re-detect card (%d)\n",
+ __func__, mmc_hostname(host), err);
+ }
+#else
err = mmc_send_status(host->card, NULL);
-
+#endif
mmc_release_host(host);
if (err) {
@@ -588,12 +626,31 @@ static int mmc_sd_suspend(struct mmc_host *host)
static int mmc_sd_resume(struct mmc_host *host)
{
int err;
+#ifdef CONFIG_MMC_PARANOID_SD_INIT
+ int retries;
+#endif
BUG_ON(!host);
BUG_ON(!host->card);
mmc_claim_host(host);
+#ifdef CONFIG_MMC_PARANOID_SD_INIT
+ retries = 5;
+ while (retries) {
+ err = mmc_sd_init_card(host, host->ocr, host->card);
+
+ if (err) {
+ printk(KERN_ERR "%s: Re-init card rc = %d (retries = %d)\n",
+ mmc_hostname(host), err, retries);
+ mdelay(5);
+ retries--;
+ continue;
+ }
+ break;
+ }
+#else
err = mmc_sd_init_card(host, host->ocr, host->card);
+#endif
mmc_release_host(host);
return err;
@@ -640,6 +697,9 @@ static void mmc_sd_attach_bus_ops(struct mmc_host *host)
int mmc_attach_sd(struct mmc_host *host, u32 ocr)
{
int err;
+#ifdef CONFIG_MMC_PARANOID_SD_INIT
+ int retries;
+#endif
BUG_ON(!host);
WARN_ON(!host->claimed);
@@ -688,9 +748,27 @@ int mmc_attach_sd(struct mmc_host *host, u32 ocr)
/*
* 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);
diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c
index b9dee28ee7d..04e68fe3bcc 100644
--- a/drivers/mmc/core/sdio.c
+++ b/drivers/mmc/core/sdio.c
@@ -24,6 +24,10 @@
#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;
@@ -309,6 +313,13 @@ static int mmc_sdio_init_card(struct mmc_host *host, u32 ocr,
if (err)
goto remove;
+ /*
+ * Update oldcard with the new RCA received from the
+ * SDIO device.
+ */
+ if (oldcard)
+ oldcard->rca = card->rca;
+
mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
}
@@ -321,19 +332,35 @@ static int mmc_sdio_init_card(struct mmc_host *host, u32 ocr,
goto remove;
}
- /*
- * Read the common registers.
- */
- err = sdio_read_cccr(card);
- if (err)
- goto remove;
+#ifdef CONFIG_MMC_EMBEDDED_SDIO
+ if (host->embedded_sdio_data.cccr)
+ memcpy(&card->cccr, host->embedded_sdio_data.cccr, sizeof(struct sdio_cccr));
+ else {
+#endif
+ /*
+ * Read the common registers.
+ */
+ err = sdio_read_cccr(card);
+ if (err)
+ goto remove;
+#ifdef CONFIG_MMC_EMBEDDED_SDIO
+ }
+#endif
- /*
- * Read the common CIS tuples.
- */
- err = sdio_read_common_cis(card);
- if (err)
- goto remove;
+#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 &&
@@ -344,7 +371,6 @@ static int mmc_sdio_init_card(struct mmc_host *host, u32 ocr,
goto err;
}
card = oldcard;
- return 0;
}
/*
@@ -515,11 +541,26 @@ static int mmc_sdio_resume(struct mmc_host *host)
return err;
}
+static void mmc_sdio_power_restore(struct mmc_host *host)
+{
+ int err;
+ BUG_ON(!host);
+ BUG_ON(!host->card);
+
+ mmc_claim_host(host);
+ err = mmc_sdio_init_card(host, host->ocr, host->card,
+ (host->pm_flags & MMC_PM_KEEP_POWER));
+ if (!err && host->sdio_irqs)
+ mmc_signal_sdio_irq(host);
+ mmc_release_host(host);
+}
+
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,
};
@@ -573,6 +614,11 @@ int mmc_attach_sdio(struct mmc_host *host, u32 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
+
/*
* If needed, disconnect card detection pull-up resistor.
*/
@@ -584,9 +630,27 @@ int mmc_attach_sdio(struct mmc_host *host, u32 ocr)
* Initialize (but don't add) all present functions.
*/
for (i = 0; i < funcs; i++, card->sdio_funcs++) {
- err = sdio_init_func(host->card, i + 1);
- if (err)
- goto remove;
+#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
}
mmc_release_host(host);
@@ -628,3 +692,82 @@ err:
return err;
}
+int sdio_reset_comm(struct mmc_card *card)
+{
+ struct mmc_host *host = card->host;
+ u32 ocr;
+ int err;
+
+ printk("%s():\n", __func__);
+ mmc_claim_host(host);
+
+ mmc_go_idle(host);
+
+ mmc_set_clock(host, host->f_min);
+
+ err = mmc_send_io_op_cond(host, 0, &ocr);
+ if (err)
+ goto err;
+
+ host->ocr = mmc_select_voltage(host, ocr);
+ if (!host->ocr) {
+ err = -EINVAL;
+ goto err;
+ }
+
+ err = mmc_send_io_op_cond(host, host->ocr, &ocr);
+ if (err)
+ goto err;
+
+ if (mmc_host_is_spi(host)) {
+ err = mmc_spi_set_crc(host, use_spi_crc);
+ if (err)
+ goto err;
+ }
+
+ if (!mmc_host_is_spi(host)) {
+ err = mmc_send_relative_addr(host, &card->rca);
+ if (err)
+ goto err;
+ mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
+ }
+ if (!mmc_host_is_spi(host)) {
+ err = mmc_select_card(card);
+ if (err)
+ goto err;
+ }
+
+ /*
+ * Switch to high-speed (if supported).
+ */
+ err = sdio_enable_hs(card);
+ if (err)
+ goto err;
+
+ /*
+ * Change to the card's maximum speed.
+ */
+ 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.
+ */
+ mmc_set_clock(host, 50000000);
+ } else {
+ mmc_set_clock(host, card->cis.max_dtr);
+ }
+
+ err = sdio_enable_wide(card);
+ 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
index 4a890dcb95a..f59f9255448 100644
--- a/drivers/mmc/core/sdio_bus.c
+++ b/drivers/mmc/core/sdio_bus.c
@@ -21,6 +21,10 @@
#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 \
@@ -200,7 +204,14 @@ static void sdio_release_func(struct device *dev)
{
struct sdio_func *func = dev_to_sdio_func(dev);
- sdio_free_func_cis(func);
+#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);
diff --git a/drivers/mmc/core/sdio_io.c b/drivers/mmc/core/sdio_io.c
index 0f687cdeb06..a2767401182 100644..100755
--- a/drivers/mmc/core/sdio_io.c
+++ b/drivers/mmc/core/sdio_io.c
@@ -338,6 +338,10 @@ static int sdio_io_rw_ext_helper(struct sdio_func *func, int write,
while (remainder > 0) {
size = min(remainder, sdio_max_byte_size(func));
+ // Some of host controllers does not support non-power-of-two block sizes
+ if (func->card->host->caps & MMC_CAP_POWER_OF_TWO_BLKSIZE)
+ size = 1 << (fls(size) - 1);
+
ret = mmc_io_rw_extended(func->card, write, func->num, addr,
incr_addr, buf, 1, size);
if (ret)
@@ -383,6 +387,39 @@ u8 sdio_readb(struct sdio_func *func, unsigned int addr, int *err_ret)
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
diff --git a/drivers/mmc/core/sdio_irq.c b/drivers/mmc/core/sdio_irq.c
index bb192f90e8e..96f81237d75 100644
--- a/drivers/mmc/core/sdio_irq.c
+++ b/drivers/mmc/core/sdio_irq.c
@@ -27,11 +27,29 @@
#include "sdio_ops.h"
+#define SDIO_IDLE_POLL_PERIOD 10
+
static int process_sdio_pending_irqs(struct mmc_card *card)
{
int i, ret, count;
unsigned char pending;
+ /*
+ * Optimization, if there is only 1 function registered
+ * and IRQ:s are supported and currently enabled, then
+ * we can assume that this actually is an IRQ and we can
+ * call the registered IRQ handler directly without
+ * checking the CCCR registers.
+ */
+ if ((card->host->caps & MMC_CAP_SDIO_IRQ) &&
+ card->host->sdio_irqs && (card->sdio_funcs == 1)) {
+ struct sdio_func *func = card->sdio_func[0];
+ if (func && func->irq_handler) {
+ func->irq_handler(func);
+ return 1;
+ }
+ }
+
ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_INTx, 0, &pending);
if (ret) {
printk(KERN_DEBUG "%s: error %d reading SDIO_CCCR_INTx\n",
@@ -65,89 +83,50 @@ static int process_sdio_pending_irqs(struct mmc_card *card)
return ret;
}
-static int sdio_irq_thread(void *_host)
+static void sdio_irq_work_func(struct work_struct *work)
{
- struct mmc_host *host = _host;
- struct sched_param param = { .sched_priority = 1 };
- unsigned long period, idle_period;
+ struct mmc_host *host = (struct mmc_host *)
+ container_of(work, struct mmc_host, sdio_irq_work.work);
int ret;
- sched_setscheduler(current, SCHED_FIFO, &param);
-
/*
- * We want to allow for SDIO cards to work even on non SDIO
- * aware hosts. One thing that non SDIO host cannot do is
- * asynchronous notification of pending SDIO card interrupts
- * hence we poll for them in that case.
+ * 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.
*/
- idle_period = msecs_to_jiffies(10);
- period = (host->caps & MMC_CAP_SDIO_IRQ) ?
- MAX_SCHEDULE_TIMEOUT : idle_period;
+ mmc_claim_host(host);
- pr_debug("%s: IRQ thread started (poll period = %lu jiffies)\n",
- mmc_hostname(host), period);
+ ret = process_sdio_pending_irqs(host->card);
- 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->card);
- 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);
- }
+ mmc_release_host(host);
+ if (host->caps & MMC_CAP_SDIO_IRQ)
+ host->ops->enable_sdio_irq(host, true);
+ else {
/*
* 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;
- }
+ if (ret > 0)
+ host->sdio_poll_period /= 2;
+ else {
+ host->sdio_poll_period++;
+ if (host->sdio_poll_period > SDIO_IDLE_POLL_PERIOD)
+ host->sdio_poll_period = SDIO_IDLE_POLL_PERIOD;
}
-
- set_current_state(TASK_INTERRUPTIBLE);
- if (host->caps & MMC_CAP_SDIO_IRQ)
- host->ops->enable_sdio_irq(host, 1);
- if (!kthread_should_stop())
- schedule_timeout(period);
- set_current_state(TASK_RUNNING);
- } while (!kthread_should_stop());
-
- if (host->caps & MMC_CAP_SDIO_IRQ)
- host->ops->enable_sdio_irq(host, 0);
-
- pr_debug("%s: IRQ thread exiting with code %d\n",
- mmc_hostname(host), ret);
-
- return ret;
+ queue_delayed_work(host->sdio_irq_workqueue,
+ &host->sdio_irq_work,
+ msecs_to_jiffies(host->sdio_poll_period));
+ }
}
static int sdio_card_irq_get(struct mmc_card *card)
@@ -157,14 +136,28 @@ static int sdio_card_irq_get(struct mmc_card *card)
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_irq_workqueue =
+ create_singlethread_workqueue("sdio_irq_workqueue");
+ if (host->sdio_irq_workqueue == NULL) {
host->sdio_irqs--;
- return err;
+ return -ENOMEM;
+ }
+
+ INIT_DELAYED_WORK(&host->sdio_irq_work, sdio_irq_work_func);
+
+ /*
+ * 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.
+ */
+ if (host->caps & MMC_CAP_SDIO_IRQ)
+ host->ops->enable_sdio_irq(host, true);
+ else {
+ host->sdio_poll_period = SDIO_IDLE_POLL_PERIOD;
+ queue_delayed_work(host->sdio_irq_workqueue,
+ &host->sdio_irq_work,
+ msecs_to_jiffies(host->sdio_poll_period));
}
}
@@ -179,8 +172,9 @@ static int sdio_card_irq_put(struct mmc_card *card)
BUG_ON(host->sdio_irqs < 1);
if (!--host->sdio_irqs) {
- atomic_set(&host->sdio_irq_thread_abort, 1);
- kthread_stop(host->sdio_irq_thread);
+ host->ops->enable_sdio_irq(host, false);
+ destroy_workqueue(host->sdio_irq_workqueue);
+ host->sdio_irq_workqueue = NULL;
}
return 0;
diff --git a/drivers/mmc/core/sdio_ops.c b/drivers/mmc/core/sdio_ops.c
index dea36d9c22e..4e3b3004013 100644
--- a/drivers/mmc/core/sdio_ops.c
+++ b/drivers/mmc/core/sdio_ops.c
@@ -152,10 +152,15 @@ int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn,
cmd.arg |= fn << 28;
cmd.arg |= incr_addr ? 0x04000000 : 0x00000000;
cmd.arg |= addr << 9;
- if (blocks == 1 && blksz <= 512)
- cmd.arg |= (blksz == 512) ? 0 : blksz; /* byte mode */
+
+ if (blocks == 1 && blksz < 512)
+ cmd.arg |= blksz; /* byte mode */
+ else if (blocks == 1 && blksz == 512 &&
+ !(card->host->caps & MMC_CAP_BROKEN_SDIO_CMD53))
+ cmd.arg |= 0; /* byte mode, 0=512 */
else
cmd.arg |= 0x08000000 | blocks; /* block mode */
+
cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC;
data.blksz = blksz;