diff options
Diffstat (limited to 'drivers/mmc')
-rw-r--r-- | drivers/mmc/core/Kconfig | 4 | ||||
-rw-r--r-- | drivers/mmc/core/core.c | 405 | ||||
-rw-r--r-- | drivers/mmc/core/core.h | 10 | ||||
-rw-r--r-- | drivers/mmc/core/host.c | 6 | ||||
-rw-r--r-- | drivers/mmc/core/host.h | 2 | ||||
-rw-r--r-- | drivers/mmc/core/mmc.c | 407 | ||||
-rw-r--r-- | drivers/mmc/core/mmc_ops.c | 36 | ||||
-rw-r--r-- | drivers/mmc/core/mmc_ops.h | 1 | ||||
-rw-r--r-- | drivers/mmc/core/sd.c | 52 | ||||
-rw-r--r-- | drivers/mmc/core/sdio.c | 196 | ||||
-rw-r--r-- | drivers/mmc/core/sdio_io.c | 49 | ||||
-rw-r--r-- | drivers/mmc/core/sdio_ops.c | 112 | ||||
-rw-r--r-- | drivers/mmc/core/sdio_ops.h | 1 | ||||
-rw-r--r-- | drivers/mmc/host/mx_sdhci.c | 127 | ||||
-rw-r--r-- | drivers/mmc/host/mx_sdhci.h | 17 | ||||
-rw-r--r-- | drivers/mmc/host/mxc_mmc.c | 4 | ||||
-rw-r--r-- | drivers/mmc/host/mxs-mmc.c | 92 |
17 files changed, 1327 insertions, 194 deletions
diff --git a/drivers/mmc/core/Kconfig b/drivers/mmc/core/Kconfig index ab37a6d9d32a..bb22ffd76ef8 100644 --- a/drivers/mmc/core/Kconfig +++ b/drivers/mmc/core/Kconfig @@ -3,7 +3,7 @@ # config MMC_UNSAFE_RESUME - bool "Allow unsafe resume (DANGEROUS)" + 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 @@ -14,3 +14,5 @@ config MMC_UNSAFE_RESUME 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". diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 50b208253440..91ba2f812d73 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -48,6 +48,22 @@ int use_spi_crc = 1; module_param(use_spi_crc, bool, 0); /* + * We normally treat cards as removed during suspend if they are not + * known to be on a non-removable bus, to avoid the risk of writing + * back data to a different card after resume. Allow this to be + * overridden if necessary. + */ +#ifdef CONFIG_MMC_UNSAFE_RESUME +int mmc_assume_removable; +#else +int mmc_assume_removable = 1; +#endif +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, @@ -344,6 +360,101 @@ unsigned int mmc_align_data_size(struct mmc_card *card, unsigned int sz) EXPORT_SYMBOL(mmc_align_data_size); /** + * mmc_host_enable - enable a host. + * @host: mmc host to enable + * + * Hosts that support power saving can use the 'enable' and 'disable' + * methods to exit and enter power saving states. For more information + * see comments for struct mmc_host_ops. + */ +int mmc_host_enable(struct mmc_host *host) +{ + if (!(host->caps & MMC_CAP_DISABLE)) + return 0; + + if (host->en_dis_recurs) + return 0; + + if (host->nesting_cnt++) + return 0; + + cancel_delayed_work_sync(&host->disable); + + if (host->enabled) + return 0; + + if (host->ops->enable) { + int err; + + host->en_dis_recurs = 1; + err = host->ops->enable(host); + host->en_dis_recurs = 0; + + if (err) { + pr_debug("%s: enable error %d\n", + mmc_hostname(host), err); + return err; + } + } + host->enabled = 1; + return 0; +} +EXPORT_SYMBOL(mmc_host_enable); + +static int mmc_host_do_disable(struct mmc_host *host, int lazy) +{ + if (host->ops->disable) { + int err; + + host->en_dis_recurs = 1; + err = host->ops->disable(host, lazy); + host->en_dis_recurs = 0; + + if (err < 0) { + pr_debug("%s: disable error %d\n", + mmc_hostname(host), err); + return err; + } + if (err > 0) { + unsigned long delay = msecs_to_jiffies(err); + + mmc_schedule_delayed_work(&host->disable, delay); + } + } + host->enabled = 0; + return 0; +} + +/** + * mmc_host_disable - disable a host. + * @host: mmc host to disable + * + * Hosts that support power saving can use the 'enable' and 'disable' + * methods to exit and enter power saving states. For more information + * see comments for struct mmc_host_ops. + */ +int mmc_host_disable(struct mmc_host *host) +{ + int err; + + if (!(host->caps & MMC_CAP_DISABLE)) + return 0; + + if (host->en_dis_recurs) + return 0; + + if (--host->nesting_cnt) + return 0; + + if (!host->enabled) + return 0; + + err = mmc_host_do_disable(host, 0); + return err; +} +EXPORT_SYMBOL(mmc_host_disable); + +/** * __mmc_claim_host - exclusively claim a host * @host: mmc host to claim * @abort: whether or not the operation should be aborted @@ -366,25 +477,111 @@ int __mmc_claim_host(struct mmc_host *host, atomic_t *abort) while (1) { set_current_state(TASK_UNINTERRUPTIBLE); stop = abort ? atomic_read(abort) : 0; - if (stop || !host->claimed) + 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) + if (!stop) { host->claimed = 1; - else + host->claimer = current; + host->claim_cnt += 1; + } else wake_up(&host->wq); spin_unlock_irqrestore(&host->lock, flags); remove_wait_queue(&host->wq, &wait); + if (!stop) + mmc_host_enable(host); return stop; } EXPORT_SYMBOL(__mmc_claim_host); /** + * mmc_try_claim_host - try exclusively to claim a host + * @host: mmc host to claim + * + * Returns %1 if the host is claimed, %0 otherwise. + */ +int mmc_try_claim_host(struct mmc_host *host) +{ + int claimed_host = 0; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + if (!host->claimed || host->claimer == current) { + host->claimed = 1; + host->claimer = current; + host->claim_cnt += 1; + claimed_host = 1; + } + spin_unlock_irqrestore(&host->lock, flags); + return claimed_host; +} +EXPORT_SYMBOL(mmc_try_claim_host); + +static void mmc_do_release_host(struct mmc_host *host) +{ + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + if (--host->claim_cnt) { + /* Release for nested claim */ + spin_unlock_irqrestore(&host->lock, flags); + } else { + host->claimed = 0; + host->claimer = NULL; + spin_unlock_irqrestore(&host->lock, flags); + wake_up(&host->wq); + } +} + +void mmc_host_deeper_disable(struct work_struct *work) +{ + struct mmc_host *host = + container_of(work, struct mmc_host, disable.work); + + /* If the host is claimed then we do not want to disable it anymore */ + if (!mmc_try_claim_host(host)) + return; + mmc_host_do_disable(host, 1); + mmc_do_release_host(host); +} + +/** + * mmc_host_lazy_disable - lazily disable a host. + * @host: mmc host to disable + * + * Hosts that support power saving can use the 'enable' and 'disable' + * methods to exit and enter power saving states. For more information + * see comments for struct mmc_host_ops. + */ +int mmc_host_lazy_disable(struct mmc_host *host) +{ + if (!(host->caps & MMC_CAP_DISABLE)) + return 0; + + if (host->en_dis_recurs) + return 0; + + if (--host->nesting_cnt) + return 0; + + if (!host->enabled) + return 0; + + if (host->disable_delay) { + mmc_schedule_delayed_work(&host->disable, + msecs_to_jiffies(host->disable_delay)); + return 0; + } else + return mmc_host_do_disable(host, 1); +} +EXPORT_SYMBOL(mmc_host_lazy_disable); + +/** * mmc_release_host - release a host * @host: mmc host to release * @@ -393,15 +590,11 @@ EXPORT_SYMBOL(__mmc_claim_host); */ void mmc_release_host(struct mmc_host *host) { - unsigned long flags; - WARN_ON(!host->claimed); - spin_lock_irqsave(&host->lock, flags); - host->claimed = 0; - spin_unlock_irqrestore(&host->lock, flags); + mmc_host_lazy_disable(host); - wake_up(&host->wq); + mmc_do_release_host(host); } EXPORT_SYMBOL(mmc_release_host); @@ -687,7 +880,13 @@ void mmc_set_timing(struct mmc_host *host, unsigned int timing) */ static void mmc_power_up(struct mmc_host *host) { - int bit = fls(host->ocr_avail) - 1; + int bit; + + /* 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)) { @@ -858,6 +1057,17 @@ void mmc_rescan(struct work_struct *work) container_of(work, struct mmc_host, detect.work); u32 ocr; int err; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + + if (host->rescan_disable) { + spin_unlock_irqrestore(&host->lock, flags); + return; + } + + spin_unlock_irqrestore(&host->lock, flags); + mmc_bus_get(host); @@ -890,8 +1100,7 @@ void mmc_rescan(struct work_struct *work) mmc_claim_host(host); mmc_power_up(host); - sdio_go_idle(host); - + sdio_reset(host); mmc_go_idle(host); mmc_send_if_cond(host, host->ocr_avail); @@ -949,9 +1158,14 @@ void mmc_stop_host(struct mmc_host *host) spin_unlock_irqrestore(&host->lock, flags); #endif + if (host->caps & MMC_CAP_DISABLE) + cancel_delayed_work(&host->disable); cancel_delayed_work(&host->detect); mmc_flush_scheduled_work(); + /* clear pm flags now and let card drivers set them as needed */ + host->pm_flags = 0; + mmc_bus_get(host); if (host->bus_ops && !host->bus_dead) { if (host->bus_ops->remove) @@ -960,6 +1174,8 @@ void mmc_stop_host(struct mmc_host *host) mmc_claim_host(host); mmc_detach_bus(host); mmc_release_host(host); + mmc_bus_put(host); + return; } mmc_bus_put(host); @@ -968,6 +1184,80 @@ void mmc_stop_host(struct mmc_host *host) mmc_power_off(host); } +void mmc_power_save_host(struct mmc_host *host) +{ + mmc_bus_get(host); + + if (!host->bus_ops || host->bus_dead || !host->bus_ops->power_restore) { + mmc_bus_put(host); + return; + } + + if (host->bus_ops->power_save) + host->bus_ops->power_save(host); + + mmc_bus_put(host); + + mmc_power_off(host); +} +EXPORT_SYMBOL(mmc_power_save_host); + +void mmc_power_restore_host(struct mmc_host *host) +{ + mmc_bus_get(host); + + if (!host->bus_ops || host->bus_dead || !host->bus_ops->power_restore) { + mmc_bus_put(host); + return; + } + + mmc_power_up(host); + host->bus_ops->power_restore(host); + + mmc_bus_put(host); +} +EXPORT_SYMBOL(mmc_power_restore_host); + +int mmc_card_awake(struct mmc_host *host) +{ + int err = -ENOSYS; + + mmc_bus_get(host); + + if (host->bus_ops && !host->bus_dead && host->bus_ops->awake) + err = host->bus_ops->awake(host); + + mmc_bus_put(host); + + return err; +} +EXPORT_SYMBOL(mmc_card_awake); + +int mmc_card_sleep(struct mmc_host *host) +{ + int err = -ENOSYS; + + mmc_bus_get(host); + + if (host->bus_ops && !host->bus_dead && host->bus_ops->awake) + err = host->bus_ops->sleep(host); + + mmc_bus_put(host); + + return err; +} +EXPORT_SYMBOL(mmc_card_sleep); + +int mmc_card_can_sleep(struct mmc_host *host) +{ + struct mmc_card *card = host->card; + + if (card && mmc_card_mmc(card) && card->ext_csd.rev >= 3) + return 1; + return 0; +} +EXPORT_SYMBOL(mmc_card_can_sleep); + #ifdef CONFIG_PM /** @@ -977,27 +1267,37 @@ void mmc_stop_host(struct mmc_host *host) */ int mmc_suspend_host(struct mmc_host *host, pm_message_t state) { + int err = 0; + + if (host->caps & MMC_CAP_DISABLE) + cancel_delayed_work(&host->disable); cancel_delayed_work(&host->detect); mmc_flush_scheduled_work(); mmc_bus_get(host); if (host->bus_ops && !host->bus_dead) { if (host->bus_ops->suspend) - host->bus_ops->suspend(host); - if (!host->bus_ops->resume) { + err = host->bus_ops->suspend(host); + if (err == -ENOSYS || !host->bus_ops->resume) { + /* + * We simply "remove" the card in this case. + * It will be redetected on resume. + */ if (host->bus_ops->remove) host->bus_ops->remove(host); - mmc_claim_host(host); mmc_detach_bus(host); mmc_release_host(host); + host->pm_flags = 0; + err = 0; } } mmc_bus_put(host); - mmc_power_off(host); + if (!err && !(host->pm_flags & MMC_PM_KEEP_POWER)) + mmc_power_off(host); - return 0; + return err; } EXPORT_SYMBOL(mmc_suspend_host); @@ -1008,26 +1308,75 @@ EXPORT_SYMBOL(mmc_suspend_host); */ int mmc_resume_host(struct mmc_host *host) { + int err = 0; + mmc_bus_get(host); if (host->bus_ops && !host->bus_dead) { - mmc_power_up(host); - mmc_select_voltage(host, host->ocr); + if (!(host->pm_flags & MMC_PM_KEEP_POWER)) { + mmc_power_up(host); + mmc_select_voltage(host, host->ocr); + } BUG_ON(!host->bus_ops->resume); - host->bus_ops->resume(host); + err = host->bus_ops->resume(host); + if (err) { + printk(KERN_WARNING "%s: error %d during resume " + "(card was removed?)\n", + mmc_hostname(host), err); + err = 0; + } } mmc_bus_put(host); - /* - * We add a slight delay here so that resume can progress - * in parallel. - */ - mmc_detect_change(host, 1); - - return 0; + return err; } - EXPORT_SYMBOL(mmc_resume_host); +/* Do the card removal on suspend if card is assumed removeable + * Do that in pm notifier while userspace isn't yet frozen, so we will be able + to sync the card. +*/ +int mmc_pm_notify(struct notifier_block *notify_block, + unsigned long mode, void *unused) +{ + struct mmc_host *host = container_of( + notify_block, struct mmc_host, pm_notify); + unsigned long flags; + + + switch (mode) { + case PM_HIBERNATION_PREPARE: + case PM_SUSPEND_PREPARE: + + spin_lock_irqsave(&host->lock, flags); + host->rescan_disable = 1; + spin_unlock_irqrestore(&host->lock, flags); + cancel_delayed_work_sync(&host->detect); + + if (!host->bus_ops || host->bus_ops->suspend) + break; + + mmc_claim_host(host); + + if (host->bus_ops->remove) + host->bus_ops->remove(host); + + mmc_detach_bus(host); + mmc_release_host(host); + host->pm_flags = 0; + break; + + case PM_POST_SUSPEND: + case PM_POST_HIBERNATION: + + spin_lock_irqsave(&host->lock, flags); + host->rescan_disable = 0; + spin_unlock_irqrestore(&host->lock, flags); + mmc_detect_change(host, 0); + + } + + return 0; +} #endif static int __init mmc_init(void) diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h index c819effa1032..a811c52a1659 100644 --- a/drivers/mmc/core/core.h +++ b/drivers/mmc/core/core.h @@ -16,10 +16,14 @@ #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 *); - void (*suspend)(struct mmc_host *); - void (*resume)(struct mmc_host *); + int (*suspend)(struct mmc_host *); + int (*resume)(struct mmc_host *); + void (*power_save)(struct mmc_host *); + void (*power_restore)(struct mmc_host *); }; void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops); @@ -50,7 +54,9 @@ int mmc_attach_mmc(struct mmc_host *host, u32 ocr); int mmc_attach_sd(struct mmc_host *host, u32 ocr); int mmc_attach_sdio(struct mmc_host *host, u32 ocr); +/* Module parameters */ extern int use_spi_crc; +extern int mmc_assume_removable; /* Debugfs information for hosts and cards */ void mmc_add_host_debugfs(struct mmc_host *host); diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 5e945e64ead7..0efe631e50ca 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -16,6 +16,8 @@ #include <linux/idr.h> #include <linux/pagemap.h> #include <linux/leds.h> +#include <linux/slab.h> +#include <linux/suspend.h> #include <linux/mmc/host.h> @@ -83,6 +85,8 @@ struct mmc_host *mmc_alloc_host(int extra, struct device *dev) spin_lock_init(&host->lock); init_waitqueue_head(&host->wq); INIT_DELAYED_WORK(&host->detect, mmc_rescan); + INIT_DELAYED_WORK_DEFERRABLE(&host->disable, mmc_host_deeper_disable); + host->pm_notify.notifier_call = mmc_pm_notify; /* * By default, hosts do not support SGIO or large requests. @@ -131,6 +135,7 @@ int mmc_add_host(struct mmc_host *host) #endif mmc_start_host(host); + register_pm_notifier(&host->pm_notify); return 0; } @@ -147,6 +152,7 @@ EXPORT_SYMBOL(mmc_add_host); */ void mmc_remove_host(struct mmc_host *host) { + unregister_pm_notifier(&host->pm_notify); mmc_stop_host(host); #ifdef CONFIG_DEBUG_FS diff --git a/drivers/mmc/core/host.h b/drivers/mmc/core/host.h index c2dc3d2d9f9a..8c87e1109a34 100644 --- a/drivers/mmc/core/host.h +++ b/drivers/mmc/core/host.h @@ -14,5 +14,7 @@ int mmc_register_host_class(void); void mmc_unregister_host_class(void); +void mmc_host_deeper_disable(struct work_struct *work); + #endif diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index e207dcf9e754..2338c761c74f 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -160,7 +160,6 @@ static int mmc_read_ext_csd(struct mmc_card *card) { int err; u8 *ext_csd; - unsigned int ext_csd_struct; BUG_ON(!card); @@ -207,16 +206,16 @@ static int mmc_read_ext_csd(struct mmc_card *card) goto out; } - ext_csd_struct = ext_csd[EXT_CSD_REV]; - if (ext_csd_struct > 5) { + card->ext_csd.rev = ext_csd[EXT_CSD_REV]; + if (card->ext_csd.rev > 3) { printk(KERN_ERR "%s: unrecognised EXT_CSD structure " "version %d\n", mmc_hostname(card->host), - ext_csd_struct); + card->ext_csd.rev); err = -EINVAL; goto out; } - if (ext_csd_struct >= 2) { + 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 | @@ -226,7 +225,17 @@ static int mmc_read_ext_csd(struct mmc_card *card) mmc_card_set_blockaddr(card); } + card->ext_csd.boot_info = ext_csd[EXT_CSD_BOOT_INFO]; + card->ext_csd.boot_size_mult = ext_csd[EXT_CSD_BOOT_SIZE_MULT]; + card->ext_csd.boot_config = ext_csd[EXT_CSD_BOOT_CONFIG]; + card->ext_csd.boot_bus_width = ext_csd[EXT_CSD_BOOT_BUS_WIDTH]; + card->ext_csd.card_type = ext_csd[EXT_CSD_CARD_TYPE]; + switch (ext_csd[EXT_CSD_CARD_TYPE]) { + case EXT_CSD_CARD_TYPE_DDR_52 | EXT_CSD_CARD_TYPE_52 + | EXT_CSD_CARD_TYPE_26: + card->ext_csd.hs_max_dtr = 52000000; + break; case EXT_CSD_CARD_TYPE_52 | EXT_CSD_CARD_TYPE_26: card->ext_csd.hs_max_dtr = 52000000; break; @@ -238,15 +247,301 @@ static int mmc_read_ext_csd(struct mmc_card *card) printk(KERN_WARNING "%s: card is mmc v4 but doesn't " "support any high-speed modes.\n", mmc_hostname(card->host)); + printk(KERN_WARNING "%s: card type is 0x%x\n", + mmc_hostname(card->host), ext_csd[EXT_CSD_CARD_TYPE]); goto out; } + if (card->ext_csd.rev >= 3) { + u8 sa_shift = ext_csd[EXT_CSD_S_A_TIMEOUT]; + + /* 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]; + } + out: kfree(ext_csd); return err; } +/* configure the boot partitions */ +static ssize_t +setup_boot_partitions(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int err, busy = 0; + u32 part, new_part; + u8 *ext_csd, boot_config; + struct mmc_command cmd; + struct mmc_card *card = container_of(dev, struct mmc_card, dev); + + BUG_ON(!card); + + sscanf(buf, "%d\n", &part); + + if (card->csd.mmca_vsn < CSD_SPEC_VER_4) { + printk(KERN_ERR "%s: invalid mmc version" + " mmc version is below version 4!)\n", + mmc_hostname(card->host)); + return -EINVAL; + } + + /* it's a normal SD/MMC but user request to configure boot partition */ + if (card->ext_csd.boot_size_mult <= 0) { + printk(KERN_ERR "%s: this is a normal SD/MMC card" + " but you request to access boot partition!\n", + mmc_hostname(card->host)); + return -EINVAL; + } + + ext_csd = kmalloc(512, GFP_KERNEL); + if (!ext_csd) { + printk(KERN_ERR "%s: could not allocate a buffer to " + "receive the ext_csd.\n", mmc_hostname(card->host)); + return -ENOMEM; + } + + mmc_claim_host(card->host); + err = mmc_send_ext_csd(card, ext_csd); + if (err) { + printk(KERN_ERR "%s: unable to read EXT_CSD.\n", + mmc_hostname(card->host)); + goto err_rtn; + } + + /* enable the boot partition in boot mode */ + /* boot enable be - + * 0x00 - disable boot enable. + * 0x08 - boot partition 1 is enabled for boot. + * 0x10 - boot partition 2 is enabled for boot. + * 0x38 - User area is enabled for boot. + */ + switch (part & EXT_CSD_BOOT_PARTITION_ENABLE_MASK) { + case 0: + boot_config = (ext_csd[EXT_CSD_BOOT_CONFIG] + & ~EXT_CSD_BOOT_PARTITION_ENABLE_MASK + & ~EXT_CSD_BOOT_ACK_ENABLE); + break; + case EXT_CSD_BOOT_PARTITION_PART1: + boot_config = ((ext_csd[EXT_CSD_BOOT_CONFIG] + & ~EXT_CSD_BOOT_PARTITION_ENABLE_MASK) + | EXT_CSD_BOOT_PARTITION_PART1 + | EXT_CSD_BOOT_ACK_ENABLE); + break; + case EXT_CSD_BOOT_PARTITION_PART2: + boot_config = ((ext_csd[EXT_CSD_BOOT_CONFIG] + & ~EXT_CSD_BOOT_PARTITION_ENABLE_MASK) + | EXT_CSD_BOOT_PARTITION_PART2 + | EXT_CSD_BOOT_ACK_ENABLE); + break; + case EXT_CSD_BOOT_PARTITION_ENABLE_MASK: + boot_config = ((ext_csd[EXT_CSD_BOOT_CONFIG] + | EXT_CSD_BOOT_PARTITION_ENABLE_MASK) + & ~EXT_CSD_BOOT_ACK_ENABLE); + break; + default: + printk(KERN_ERR "%s: wrong boot config parameter" + " 00 (disable boot), 08 (enable boot1)," + "16 (enable boot2), 56 (User area)\n", + mmc_hostname(card->host)); + err = -EINVAL; + goto err_rtn; + } + + /* switch the partitions that used to be accessed in OS layer */ + /* partition must be - + * 0 - user area + * 1 - boot partition 1 + * 2 - boot partition 2 + */ + if ((part & EXT_CSD_BOOT_PARTITION_ACCESS_MASK) > 2) { + printk(KERN_ERR "%s: wrong partition id" + " 0 (user area), 1 (boot1), 2 (boot2)\n", + mmc_hostname(card->host)); + err = -EINVAL; + goto err_rtn; + } + + /* Send SWITCH command to change partition for access */ + boot_config &= ~EXT_CSD_BOOT_PARTITION_ACCESS_MASK; + boot_config |= (part & EXT_CSD_BOOT_PARTITION_ACCESS_MASK); + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_BOOT_CONFIG, boot_config); + if (err) { + printk(KERN_ERR "%s: fail to send SWITCH command" + " to card to swich partition for access!\n", + mmc_hostname(card->host)); + goto err_rtn; + } + + /* waiting for the card to finish the busy state */ + do { + memset(&cmd, 0, sizeof(struct mmc_command)); + + cmd.opcode = MMC_SEND_STATUS; + cmd.arg = card->rca << 16; + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + + err = mmc_wait_for_cmd(card->host, &cmd, 0); + if (err || busy > 100) { + printk(KERN_ERR "%s: failed to wait for" + "the busy state to end.\n", + mmc_hostname(card->host)); + break; + } + + if (!busy && !(cmd.resp[0] & R1_READY_FOR_DATA)) { + printk(KERN_INFO "%s: card is in busy state" + "pls wait for busy state to end.\n", + mmc_hostname(card->host)); + } + busy++; + } while (!(cmd.resp[0] & R1_READY_FOR_DATA)); + + /* Now check whether it works */ + err = mmc_send_ext_csd(card, ext_csd); + if (err) { + printk(KERN_ERR "%s: %d unable to re-read EXT_CSD.\n", + mmc_hostname(card->host), err); + goto err_rtn; + } + + new_part = ext_csd[EXT_CSD_BOOT_CONFIG] & + EXT_CSD_BOOT_PARTITION_ACCESS_MASK; + if ((part & EXT_CSD_BOOT_PARTITION_ACCESS_MASK) != new_part) { + printk(KERN_ERR "%s: after SWITCH, current part id %d is not" + " same as requested partition %d!\n", + mmc_hostname(card->host), new_part, part); + goto err_rtn; + } + card->ext_csd.boot_config = ext_csd[EXT_CSD_BOOT_CONFIG]; + +err_rtn: + mmc_release_host(card->host); + kfree(ext_csd); + if (err) + return err; + else + return count; +} + +/* configure the boot bus */ +static ssize_t +setup_boot_bus(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int err, busy = 0; + u32 boot_bus, new_bus; + u8 *ext_csd; + struct mmc_command cmd; + struct mmc_card *card = container_of(dev, struct mmc_card, dev); + + BUG_ON(!card); + + sscanf(buf, "%d\n", &boot_bus); + + if (card->csd.mmca_vsn < CSD_SPEC_VER_4) { + printk(KERN_ERR "%s: invalid mmc version" + " mmc version is below version 4!)\n", + mmc_hostname(card->host)); + return -EINVAL; + } + + /* it's a normal SD/MMC but user request to configure boot bus */ + if (card->ext_csd.boot_size_mult <= 0) { + printk(KERN_ERR "%s: this is a normal SD/MMC card" + " but you request to configure boot bus !\n", + mmc_hostname(card->host)); + return -EINVAL; + } + + ext_csd = kmalloc(512, GFP_KERNEL); + if (!ext_csd) { + printk(KERN_ERR "%s: could not allocate a buffer to " + "receive the ext_csd.\n", mmc_hostname(card->host)); + return -ENOMEM; + } + + mmc_claim_host(card->host); + err = mmc_send_ext_csd(card, ext_csd); + if (err) { + printk(KERN_ERR "%s: unable to read EXT_CSD.\n", + mmc_hostname(card->host)); + goto err_rtn; + } + + /* Configure the boot bus width when boot partition is enabled */ + if (((boot_bus & EXT_CSD_BOOT_BUS_WIDTH_MODE_MASK) >> 3) > 2 + || (boot_bus & EXT_CSD_BOOT_BUS_WIDTH_WIDTH_MASK) > 2 + || (boot_bus & ~EXT_CSD_BOOT_BUS_WIDTH_MASK) > 0) { + printk(KERN_ERR "%s: Invalid inputs!\n", + mmc_hostname(card->host)); + err = -EINVAL; + goto err_rtn; + } + + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_BOOT_BUS_WIDTH, boot_bus); + if (err) { + printk(KERN_ERR "%s: fail to send SWITCH command to " + "card to swich partition for access!\n", + mmc_hostname(card->host)); + goto err_rtn; + } + + /* waiting for the card to finish the busy state */ + do { + memset(&cmd, 0, sizeof(struct mmc_command)); + + cmd.opcode = MMC_SEND_STATUS; + cmd.arg = card->rca << 16; + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + + err = mmc_wait_for_cmd(card->host, &cmd, 0); + if (err || busy > 100) { + printk(KERN_ERR "%s: failed to wait for" + "the busy state to end.\n", + mmc_hostname(card->host)); + break; + } + + if (!busy && !(cmd.resp[0] & R1_READY_FOR_DATA)) { + printk(KERN_INFO "%s: card is in busy state" + "pls wait for busy state to end.\n", + mmc_hostname(card->host)); + } + busy++; + } while (!(cmd.resp[0] & R1_READY_FOR_DATA)); + + /* Now check whether it works */ + err = mmc_send_ext_csd(card, ext_csd); + if (err) { + printk(KERN_ERR "%s: %d unable to re-read EXT_CSD.\n", + mmc_hostname(card->host), err); + goto err_rtn; + } + + new_bus = ext_csd[EXT_CSD_BOOT_BUS_WIDTH]; + if (boot_bus != new_bus) { + printk(KERN_ERR "%s: after SWITCH, current boot bus mode %d" + " is not same as requested bus mode %d!\n", + mmc_hostname(card->host), new_bus, boot_bus); + goto err_rtn; + } + card->ext_csd.boot_bus_width = ext_csd[EXT_CSD_BOOT_BUS_WIDTH]; + +err_rtn: + mmc_release_host(card->host); + kfree(ext_csd); + if (err) + return err; + else + return count; +} + MMC_DEV_ATTR(cid, "%08x%08x%08x%08x\n", card->raw_cid[0], card->raw_cid[1], card->raw_cid[2], card->raw_cid[3]); MMC_DEV_ATTR(csd, "%08x%08x%08x%08x\n", card->raw_csd[0], card->raw_csd[1], @@ -258,6 +553,12 @@ 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(boot_info, "boot_info:0x%02x; boot_size:%04dKB;" + " boot_partition:0x%02x; boot_bus:0x%02x\n", + card->ext_csd.boot_info, card->ext_csd.boot_size_mult * 128, + card->ext_csd.boot_config, card->ext_csd.boot_bus_width); +DEVICE_ATTR(boot_config, S_IWUGO, NULL, setup_boot_partitions); +DEVICE_ATTR(boot_bus_config, S_IWUGO, NULL, setup_boot_bus); static struct attribute *mmc_std_attrs[] = { &dev_attr_cid.attr, @@ -269,6 +570,9 @@ static struct attribute *mmc_std_attrs[] = { &dev_attr_name.attr, &dev_attr_oemid.attr, &dev_attr_serial.attr, + &dev_attr_boot_info.attr, + &dev_attr_boot_config.attr, + &dev_attr_boot_bus_config.attr, NULL, }; @@ -434,10 +738,21 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, * Activate wide bus (if supported). */ if ((card->csd.mmca_vsn >= CSD_SPEC_VER_4) && - (host->caps & (MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA))) { + (host->caps & (MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA + | MMC_CAP_DATA_DDR))) { unsigned ext_csd_bit, bus_width; - if (host->caps & MMC_CAP_8_BIT_DATA) { + if ((host->caps & MMC_CAP_8_BIT_DATA) && + (host->caps & MMC_CAP_DATA_DDR) && + (card->ext_csd.card_type & MMC_DDR_MODE_MASK)) { + ext_csd_bit = EXT_CSD_BUS_WIDTH_8_DDR; + bus_width = MMC_BUS_WIDTH_8 | MMC_BUS_WIDTH_DDR; + } else if ((host->caps & MMC_CAP_4_BIT_DATA) && + (host->caps & MMC_CAP_DATA_DDR) && + (card->ext_csd.card_type & MMC_DDR_MODE_MASK)) { + ext_csd_bit = EXT_CSD_BUS_WIDTH_4_DDR; + bus_width = MMC_BUS_WIDTH_4 | MMC_BUS_WIDTH_DDR; + } else if (host->caps & MMC_CAP_8_BIT_DATA) { ext_csd_bit = EXT_CSD_BUS_WIDTH_8; bus_width = MMC_BUS_WIDTH_8; } else { @@ -507,12 +822,10 @@ static void mmc_detect(struct mmc_host *host) } } -#ifdef CONFIG_MMC_UNSAFE_RESUME - /* * Suspend callback from host. */ -static void mmc_suspend(struct mmc_host *host) +static int mmc_suspend(struct mmc_host *host) { BUG_ON(!host); BUG_ON(!host->card); @@ -522,6 +835,8 @@ static void mmc_suspend(struct mmc_host *host) mmc_deselect_cards(host); host->card->state &= ~MMC_STATE_HIGHSPEED; mmc_release_host(host); + + return 0; } /* @@ -530,7 +845,7 @@ static void mmc_suspend(struct mmc_host *host) * This function tries to determine if the same card is still present * and, if so, restore all state to it. */ -static void mmc_resume(struct mmc_host *host) +static int mmc_resume(struct mmc_host *host) { int err; @@ -541,30 +856,78 @@ static void mmc_resume(struct mmc_host *host) err = mmc_init_card(host, host->ocr, host->card); mmc_release_host(host); - if (err) { - mmc_remove(host); + return err; +} - mmc_claim_host(host); - mmc_detach_bus(host); - mmc_release_host(host); +static void mmc_power_restore(struct mmc_host *host) +{ + host->card->state &= ~MMC_STATE_HIGHSPEED; + mmc_claim_host(host); + mmc_init_card(host, host->ocr, host->card); + mmc_release_host(host); +} + +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; } -#else - -#define mmc_suspend NULL -#define mmc_resume NULL +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); + } -#endif + return err; +} static const struct mmc_bus_ops mmc_ops = { + .awake = mmc_awake, + .sleep = mmc_sleep, + .remove = mmc_remove, + .detect = mmc_detect, + .suspend = NULL, + .resume = NULL, + .power_restore = mmc_power_restore, +}; + +static const struct mmc_bus_ops mmc_ops_unsafe = { + .awake = mmc_awake, + .sleep = mmc_sleep, .remove = mmc_remove, .detect = mmc_detect, .suspend = mmc_suspend, .resume = mmc_resume, + .power_restore = mmc_power_restore, }; +static void mmc_attach_bus_ops(struct mmc_host *host) +{ + const struct mmc_bus_ops *bus_ops; + + if (host->caps & MMC_CAP_NONREMOVABLE || !mmc_assume_removable) + bus_ops = &mmc_ops_unsafe; + else + bus_ops = &mmc_ops; + mmc_attach_bus(host, bus_ops); +} + /* * Starting point for MMC card init. */ @@ -575,7 +938,7 @@ int mmc_attach_mmc(struct mmc_host *host, u32 ocr) BUG_ON(!host); WARN_ON(!host->claimed); - mmc_attach_bus(host, &mmc_ops); + mmc_attach_bus_ops(host); /* * We need to get OCR a different way for SPI. diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index 34ce2703d29a..355c6042cf65 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -57,6 +57,42 @@ 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; + struct mmc_card *card = host->card; + int err; + + if (sleep) + mmc_deselect_cards(host); + + memset(&cmd, 0, sizeof(struct mmc_command)); + + 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; diff --git a/drivers/mmc/core/mmc_ops.h b/drivers/mmc/core/mmc_ops.h index 17854bf7cf0d..653eb8e84178 100644 --- a/drivers/mmc/core/mmc_ops.h +++ b/drivers/mmc/core/mmc_ops.h @@ -25,6 +25,7 @@ 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); #endif diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index cd81c395e164..4a73e34f9200 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -561,12 +561,10 @@ static void mmc_sd_detect(struct mmc_host *host) } } -#ifdef CONFIG_MMC_UNSAFE_RESUME - /* * Suspend callback from host. */ -static void mmc_sd_suspend(struct mmc_host *host) +static int mmc_sd_suspend(struct mmc_host *host) { BUG_ON(!host); BUG_ON(!host->card); @@ -576,6 +574,8 @@ static void mmc_sd_suspend(struct mmc_host *host) mmc_deselect_cards(host); host->card->state &= ~MMC_STATE_HIGHSPEED; mmc_release_host(host); + + return 0; } /* @@ -584,7 +584,7 @@ static void mmc_sd_suspend(struct mmc_host *host) * This function tries to determine if the same card is still present * and, if so, restore all state to it. */ -static void mmc_sd_resume(struct mmc_host *host) +static int mmc_sd_resume(struct mmc_host *host) { int err; @@ -595,30 +595,44 @@ static void mmc_sd_resume(struct mmc_host *host) err = mmc_sd_init_card(host, host->ocr, host->card); mmc_release_host(host); - if (err) { - mmc_sd_remove(host); - - mmc_claim_host(host); - mmc_detach_bus(host); - mmc_release_host(host); - } - + return err; } -#else - -#define mmc_sd_suspend NULL -#define mmc_sd_resume NULL - -#endif +static void mmc_sd_power_restore(struct mmc_host *host) +{ + host->card->state &= ~MMC_STATE_HIGHSPEED; + mmc_claim_host(host); + mmc_sd_init_card(host, host->ocr, host->card); + mmc_release_host(host); +} static const struct mmc_bus_ops mmc_sd_ops = { .remove = mmc_sd_remove, .detect = mmc_sd_detect, + .suspend = NULL, + .resume = NULL, + .power_restore = mmc_sd_power_restore, +}; + +static const struct mmc_bus_ops mmc_sd_ops_unsafe = { + .remove = mmc_sd_remove, + .detect = mmc_sd_detect, .suspend = mmc_sd_suspend, .resume = mmc_sd_resume, + .power_restore = mmc_sd_power_restore, }; +static void mmc_sd_attach_bus_ops(struct mmc_host *host) +{ + const struct mmc_bus_ops *bus_ops; + + if (host->caps & MMC_CAP_NONREMOVABLE || !mmc_assume_removable) + bus_ops = &mmc_sd_ops_unsafe; + else + bus_ops = &mmc_sd_ops; + mmc_attach_bus(host, bus_ops); +} + /* * Starting point for SD card init. */ @@ -629,7 +643,7 @@ int mmc_attach_sd(struct mmc_host *host, u32 ocr) BUG_ON(!host); WARN_ON(!host->claimed); - mmc_attach_bus(host, &mmc_sd_ops); + mmc_sd_attach_bus_ops(host); /* * We need to get OCR a different way for SPI. diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index fb99ccff9080..7f3093dd2b49 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -195,6 +195,135 @@ static int sdio_enable_hs(struct mmc_card *card) } /* + * 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) +{ + struct mmc_card *card; + int err; + + BUG_ON(!host); + WARN_ON(!host->claimed); + + /* + * Inform the card of the voltage + */ + 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; + } + + card->type = MMC_TYPE_SDIO; + + /* + * For native busses: set card RCA and quit open drain mode. + */ + if (!mmc_host_is_spi(host)) { + err = mmc_send_relative_addr(host, &card->rca); + if (err) + goto remove; + + mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); + } + + /* + * Select card, as all following commands rely on that. + */ + if (!mmc_host_is_spi(host)) { + err = mmc_select_card(card); + if (err) + goto remove; + } + + /* + * Read the common registers. + */ + err = sdio_read_cccr(card); + if (err) + goto remove; + + /* + * Read the common CIS tuples. + */ + err = sdio_read_common_cis(card); + if (err) + goto remove; + + if (oldcard) { + int same = (card->cis.vendor == oldcard->cis.vendor && + card->cis.device == oldcard->cis.device); + mmc_remove_card(card); + if (!same) { + err = -ENOENT; + goto err; + } + card = oldcard; + return 0; + } + + /* + * Switch to high-speed (if supported). + */ + err = sdio_enable_hs(card); + if (err) + goto remove; + + /* + * 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); + } + + /* + * Switch to wider bus (if supported). + */ + err = sdio_enable_wide(card); + if (err) + goto remove; + + 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) @@ -243,10 +372,77 @@ static void mmc_sdio_detect(struct mmc_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); + } + } + + return err; +} + +static int mmc_sdio_resume(struct mmc_host *host) +{ + int i, err; + + BUG_ON(!host); + BUG_ON(!host->card); + + /* Basic card reinitialization. */ + mmc_claim_host(host); + err = mmc_sdio_init_card(host, host->ocr, host->card); + 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 const struct mmc_bus_ops mmc_sdio_ops = { .remove = mmc_sdio_remove, .detect = mmc_sdio_detect, + .suspend = mmc_sdio_suspend, + .resume = mmc_sdio_resume, }; diff --git a/drivers/mmc/core/sdio_io.c b/drivers/mmc/core/sdio_io.c index f61fc2d4cd0a..ff69a2feecd5 100644 --- a/drivers/mmc/core/sdio_io.c +++ b/drivers/mmc/core/sdio_io.c @@ -635,3 +635,52 @@ void sdio_f0_writeb(struct sdio_func *func, unsigned char b, unsigned int addr, *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_ops.c b/drivers/mmc/core/sdio_ops.c index 208beeb23ed6..b6304fb8bf90 100644 --- a/drivers/mmc/core/sdio_ops.c +++ b/drivers/mmc/core/sdio_ops.c @@ -67,6 +67,61 @@ int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr) 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; + int err; + + BUG_ON(!host); + BUG_ON(fn > 7); + + /* sanity check */ + if (addr & ~0x1FFFF) + return -EINVAL; + + memset(&cmd, 0, sizeof(struct mmc_command)); + + 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) { @@ -134,62 +189,7 @@ int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn, return 0; } -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; - int err; - - BUG_ON(!host); - BUG_ON(fn > 7); - - /* sanity check */ - if (addr & ~0x1FFFF) - return -EINVAL; - - memset(&cmd, 0, sizeof(struct mmc_command)); - - 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 sdio_go_idle(struct mmc_host *host) +int sdio_reset(struct mmc_host *host) { int ret; u8 abort; diff --git a/drivers/mmc/core/sdio_ops.h b/drivers/mmc/core/sdio_ops.h index 9b546c71eb5e..85c7ecf809f4 100644 --- a/drivers/mmc/core/sdio_ops.h +++ b/drivers/mmc/core/sdio_ops.h @@ -18,6 +18,7 @@ int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn, 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_go_idle(struct mmc_host *host); +int sdio_reset(struct mmc_host *host); #endif diff --git a/drivers/mmc/host/mx_sdhci.c b/drivers/mmc/host/mx_sdhci.c index 5a68a8e02fe2..732135308eb6 100644 --- a/drivers/mmc/host/mx_sdhci.c +++ b/drivers/mmc/host/mx_sdhci.c @@ -142,32 +142,32 @@ EXPORT_SYMBOL(mxc_mmc_force_detect); static void sdhci_dumpregs(struct sdhci_host *host) { - printk(KERN_DEBUG DRIVER_NAME + printk(KERN_INFO DRIVER_NAME ": ============== REGISTER DUMP ==============\n"); - printk(KERN_DEBUG DRIVER_NAME ": Sys addr: 0x%08x | Version: 0x%08x\n", + printk(KERN_INFO DRIVER_NAME ": Sys addr: 0x%08x | Version: 0x%08x\n", readl(host->ioaddr + SDHCI_DMA_ADDRESS), readl(host->ioaddr + SDHCI_HOST_VERSION)); - printk(KERN_DEBUG DRIVER_NAME ": Blk size: 0x%08x | Blk cnt: 0x%08x\n", + printk(KERN_INFO DRIVER_NAME ": Blk size: 0x%08x | Blk cnt: 0x%08x\n", (readl(host->ioaddr + SDHCI_BLOCK_SIZE) & 0xFFFF), (readl(host->ioaddr + SDHCI_BLOCK_COUNT) >> 16)); - printk(KERN_DEBUG DRIVER_NAME ": Argument: 0x%08x | Trn mode: 0x%08x\n", + printk(KERN_INFO DRIVER_NAME ": Argument: 0x%08x | Trn mode: 0x%08x\n", readl(host->ioaddr + SDHCI_ARGUMENT), readl(host->ioaddr + SDHCI_TRANSFER_MODE)); - printk(KERN_DEBUG DRIVER_NAME ": Present: 0x%08x | Host ctl: 0x%08x\n", + printk(KERN_INFO DRIVER_NAME ": Present: 0x%08x | Host ctl: 0x%08x\n", readl(host->ioaddr + SDHCI_PRESENT_STATE), readl(host->ioaddr + SDHCI_HOST_CONTROL)); - printk(KERN_DEBUG DRIVER_NAME ": Clock: 0x%08x\n", + printk(KERN_INFO DRIVER_NAME ": Clock: 0x%08x\n", readl(host->ioaddr + SDHCI_CLOCK_CONTROL)); - printk(KERN_DEBUG DRIVER_NAME ": Int stat: 0x%08x\n", + printk(KERN_INFO DRIVER_NAME ": Int stat: 0x%08x\n", readl(host->ioaddr + SDHCI_INT_STATUS)); - printk(KERN_DEBUG DRIVER_NAME ": Int enab: 0x%08x | Sig enab: 0x%08x\n", + printk(KERN_INFO DRIVER_NAME ": Int enab: 0x%08x | Sig enab: 0x%08x\n", readl(host->ioaddr + SDHCI_INT_ENABLE), readl(host->ioaddr + SDHCI_SIGNAL_ENABLE)); - printk(KERN_DEBUG DRIVER_NAME ": Caps: 0x%08x\n", + printk(KERN_INFO DRIVER_NAME ": Caps: 0x%08x\n", readl(host->ioaddr + SDHCI_CAPABILITIES)); - printk(KERN_DEBUG DRIVER_NAME + printk(KERN_INFO DRIVER_NAME ": ===========================================\n"); } @@ -506,6 +506,30 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_data *data) host->flags &= ~SDHCI_REQ_USE_DMA; } + if (cpu_is_mx25() && (data->blksz * data->blocks < 0x10)) { + host->flags &= ~SDHCI_REQ_USE_DMA; + DBG("Reverting to PIO in small data transfer.\n"); + writel(readl(host->ioaddr + SDHCI_INT_ENABLE) + | SDHCI_INT_DATA_AVAIL + | SDHCI_INT_SPACE_AVAIL, + host->ioaddr + SDHCI_INT_ENABLE); + writel(readl(host->ioaddr + SDHCI_SIGNAL_ENABLE) + | SDHCI_INT_DATA_AVAIL + | SDHCI_INT_SPACE_AVAIL, + host->ioaddr + SDHCI_SIGNAL_ENABLE); + } else if (cpu_is_mx25() && (host->flags & SDHCI_USE_DMA)) { + host->flags |= SDHCI_REQ_USE_DMA; + DBG("Reverting to DMA in large data transfer.\n"); + writel(readl(host->ioaddr + SDHCI_INT_ENABLE) + & ~(SDHCI_INT_DATA_AVAIL + | SDHCI_INT_SPACE_AVAIL), + host->ioaddr + SDHCI_INT_ENABLE); + writel(readl(host->ioaddr + SDHCI_SIGNAL_ENABLE) + & ~(SDHCI_INT_DATA_AVAIL + | SDHCI_INT_SPACE_AVAIL), + host->ioaddr + SDHCI_SIGNAL_ENABLE); + } + if (host->flags & SDHCI_REQ_USE_DMA) { int i; struct scatterlist *tsg; @@ -644,7 +668,7 @@ static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) WARN_ON(host->cmd); /* Wait max 10 ms */ - timeout = 5000; + timeout = 500; mask = SDHCI_CMD_INHIBIT; if ((cmd->data != NULL) || (cmd->flags & MMC_RSP_BUSY)) @@ -695,7 +719,7 @@ static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) mode |= SDHCI_TRNS_READ; else mode &= ~SDHCI_TRNS_READ; - if (host->flags & SDHCI_USE_DMA) + if (host->flags & SDHCI_REQ_USE_DMA) mode |= SDHCI_TRNS_DMA; if (host->flags & SDHCI_USE_EXTERNAL_DMA) DBG("Prepare data completely in %s transfer mode.\n", @@ -727,6 +751,11 @@ static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) flags |= SDHCI_CMD_DATA; mode |= SDHCI_MAKE_CMD(cmd->opcode, flags); + if (host->mmc->ios.bus_width & MMC_BUS_WIDTH_DDR) { + /* Eanble the DDR mode */ + mode |= SDHCI_TRNS_DDR_EN; + } else + mode &= ~SDHCI_TRNS_DDR_EN; DBG("Complete sending cmd, transfer mode would be 0x%x.\n", mode); writel(mode, host->ioaddr + SDHCI_TRANSFER_MODE); } @@ -775,6 +804,7 @@ static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock) int clk_rate = 0; u32 clk; unsigned long timeout; + struct mmc_ios ios = host->mmc->ios; if (clock == 0) { goto out; @@ -784,17 +814,21 @@ static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock) host->plat_data->clk_flg = 1; } } - if (clock == host->clock) + + if (clock == host->clock && !(ios.bus_width & MMC_BUS_WIDTH_DDR)) return; clk_rate = clk_get_rate(host->clk); clk = readl(host->ioaddr + SDHCI_CLOCK_CONTROL) & ~SDHCI_CLOCK_MASK; - if (!cpu_is_mx53()) + if (cpu_is_mx53() || cpu_is_mx50()) + writel(clk | SDHCI_CLOCK_SDCLKFS1, + host->ioaddr + SDHCI_CLOCK_CONTROL); + else writel(clk, host->ioaddr + SDHCI_CLOCK_CONTROL); if (clock == host->min_clk) prescaler = 16; - else if (cpu_is_mx53()) + else if (cpu_is_mx53() || cpu_is_mx50()) prescaler = 1; else prescaler = 0; @@ -820,6 +854,21 @@ static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock) DBG("prescaler = 0x%x, divider = 0x%x\n", prescaler, div); clk |= (prescaler << 8) | (div << 4); + if (host->plat_data->clk_always_on + | (host->mmc->card && mmc_card_sdio(host->mmc->card))) + clk |= SDHCI_CLOCK_PER_EN | SDHCI_CLOCK_HLK_EN + | SDHCI_CLOCK_IPG_EN; + else + clk &= ~(SDHCI_CLOCK_PER_EN | SDHCI_CLOCK_HLK_EN + | SDHCI_CLOCK_IPG_EN); + + /* Configure the clock delay line */ + if ((host->plat_data->vendor_ver >= ESDHC_VENDOR_V3) + && host->plat_data->dll_override_en) + writel((host->plat_data->dll_delay_cells << 10) + | DLL_CTRL_SLV_OVERRIDE, + host->ioaddr + SDHCI_DLL_CONTROL); + /* Configure the clock control register */ clk |= (readl(host->ioaddr + SDHCI_CLOCK_CONTROL) & (~SDHCI_CLOCK_MASK)); @@ -830,7 +879,7 @@ static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock) host->ioaddr + SDHCI_CLOCK_CONTROL); /* Wait max 10 ms */ - timeout = 5000; + timeout = 500; while (timeout > 0) { timeout--; udelay(20); @@ -933,8 +982,8 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) u32 tmp; mxc_dma_device_t dev_id = 0; - DBG("%s: clock %u, bus %lu, power %u, vdd %u\n", DRIVER_NAME, - ios->clock, 1UL << ios->bus_width, ios->power_mode, ios->vdd); + DBG("%s: clock %u, bus %u, power %u, vdd %u\n", DRIVER_NAME, + ios->clock, ios->bus_width, ios->power_mode, ios->vdd); host = mmc_priv(mmc); @@ -1000,10 +1049,10 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) tmp = readl(host->ioaddr + SDHCI_HOST_CONTROL); - if (ios->bus_width == MMC_BUS_WIDTH_4) { + if ((ios->bus_width & ~MMC_BUS_WIDTH_DDR) == MMC_BUS_WIDTH_4) { tmp &= ~SDHCI_CTRL_8BITBUS; tmp |= SDHCI_CTRL_4BITBUS; - } else if (ios->bus_width == MMC_BUS_WIDTH_8) { + } else if ((ios->bus_width & ~MMC_BUS_WIDTH_DDR) == MMC_BUS_WIDTH_8) { tmp &= ~SDHCI_CTRL_4BITBUS; tmp |= SDHCI_CTRL_8BITBUS; } else if (ios->bus_width == MMC_BUS_WIDTH_1) { @@ -1044,7 +1093,7 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable) { struct sdhci_host *host; unsigned long flags; - u32 ier, prot, clk, present; + u32 ier, prot, present; host = mmc_priv(mmc); @@ -1057,19 +1106,12 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable) if (--(host->sdio_enable)) goto exit_unlock; } - /* Enable the clock */ - if (!host->plat_data->clk_flg) { - clk_enable(host->clk); - host->plat_data->clk_flg = 1; - } - ier = readl(host->ioaddr + SDHCI_SIGNAL_ENABLE); + + ier = readl(host->ioaddr + SDHCI_INT_ENABLE); prot = readl(host->ioaddr + SDHCI_HOST_CONTROL); - clk = readl(host->ioaddr + SDHCI_CLOCK_CONTROL); if (enable) { ier |= SDHCI_INT_CARD_INT; - prot |= SDHCI_CTRL_D3CD; - clk |= SDHCI_CLOCK_PER_EN | SDHCI_CLOCK_IPG_EN; present = readl(host->ioaddr + SDHCI_PRESENT_STATE); if ((present & SDHCI_CARD_INT_MASK) != SDHCI_CARD_INT_ID) writel(SDHCI_INT_CARD_INT, @@ -1077,12 +1119,24 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable) } else { ier &= ~SDHCI_INT_CARD_INT; prot &= ~SDHCI_CTRL_D3CD; - clk &= ~(SDHCI_CLOCK_PER_EN | SDHCI_CLOCK_IPG_EN); } writel(prot, host->ioaddr + SDHCI_HOST_CONTROL); + writel(ier, host->ioaddr + SDHCI_INT_ENABLE); writel(ier, host->ioaddr + SDHCI_SIGNAL_ENABLE); - writel(clk, host->ioaddr + SDHCI_CLOCK_CONTROL); + + /* + * Using D3CD to manually driver the HW to re-sample the SDIO interrupt + * on bus one more time to guarantee the SDIO interrupt signal sent + * from card during the interrupt signal disabled period will not + * be lost. + */ + prot |= SDHCI_CTRL_CDSS; + writel(prot, host->ioaddr + SDHCI_HOST_CONTROL); + prot &= ~SDHCI_CTRL_D3CD; + writel(prot, host->ioaddr + SDHCI_HOST_CONTROL); + prot |= SDHCI_CTRL_D3CD; + writel(prot, host->ioaddr + SDHCI_HOST_CONTROL); mmiowb(); exit_unlock: @@ -1208,7 +1262,7 @@ static void sdhci_tasklet_finish(unsigned long param) * The root cause is that the ROM code don't ensure * the SD/MMC clk is running when boot system. * */ - if (!machine_is_mx35_3ds() && req_done && host->plat_data->clk_flg && + if (req_done && host->plat_data->clk_flg && !(host->mmc && host->mmc->card && mmc_card_sdio(host->mmc->card))) { clk_disable(host->clk); host->plat_data->clk_flg = 0; @@ -1786,8 +1840,10 @@ static int __devinit sdhci_probe_slot(struct platform_device /* Get the SDHC clock from clock system APIs */ host->clk = clk_get(&pdev->dev, mmc_plat->clock_mmc); - if (NULL == host->clk) + if (NULL == host->clk) { printk(KERN_ERR "MXC MMC can't get clock.\n"); + goto out1; + } DBG("SDHC:%d clock:%lu\n", pdev->id, clk_get_rate(host->clk)); host->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -2001,9 +2057,6 @@ static int __devinit sdhci_probe_slot(struct platform_device } mxc_dma_callback_set(host->dma, sdhci_dma_irq, (void *)host); } -#ifdef CONFIG_MMC_DEBUG - sdhci_dumpregs(host); -#endif mmiowb(); diff --git a/drivers/mmc/host/mx_sdhci.h b/drivers/mmc/host/mx_sdhci.h index 0bd79934952e..83d02975ecd1 100644 --- a/drivers/mmc/host/mx_sdhci.h +++ b/drivers/mmc/host/mx_sdhci.h @@ -2,7 +2,6 @@ * linux/drivers/mmc/host/mx_sdhci.h - Secure Digital Host * Controller Interface driver * - * Copyright (C) 2005-2007 Pierre Ossman, All Rights Reserved. * Copyright (C) 2008-2010 Freescale Semiconductor, Inc. All Rights Reserved. * * This program is free software; you can redistribute it and/or modify @@ -28,6 +27,7 @@ #define SDHCI_TRNS_DMA 0x00000001 #define SDHCI_TRNS_BLK_CNT_EN 0x00000002 #define SDHCI_TRNS_ACMD12 0x00000004 +#define SDHCI_TRNS_DDR_EN 0x00000008 #define SDHCI_TRNS_READ 0x00000010 #define SDHCI_TRNS_MULTI 0x00000020 #define SDHCI_TRNS_DPSEL 0x00200000 @@ -69,6 +69,7 @@ #define SDHCI_CTRL_4BITBUS 0x00000002 #define SDHCI_CTRL_8BITBUS 0x00000004 #define SDHCI_CTRL_HISPD 0x00000004 +#define SDHCI_CTRL_CDSS 0x80 #define SDHCI_CTRL_DMA_MASK 0x18 #define SDHCI_CTRL_SDMA 0x00 #define SDHCI_CTRL_ADMA1 0x08 @@ -95,6 +96,7 @@ #define SDHCI_CLOCK_PER_EN 0x00000004 #define SDHCI_CLOCK_HLK_EN 0x00000002 #define SDHCI_CLOCK_IPG_EN 0x00000001 +#define SDHCI_CLOCK_SDCLKFS1 0x00000100 #define SDHCI_CLOCK_MASK 0x0000FFFF #define SDHCI_TIMEOUT_CONTROL 0x2E @@ -188,6 +190,18 @@ #define SDHCI_ADMA_ADDRESS 0x58 /* 60-FB reserved */ +#define SDHCI_DLL_CONTROL 0x60 +#define DLL_CTRL_ENABLE 0x00000001 +#define DLL_CTRL_RESET 0x00000002 +#define DLL_CTRL_SLV_FORCE_UPD 0x00000004 +#define DLL_CTRL_SLV_OVERRIDE 0x00000200 +#define DLL_CTRL_SLV_DLY_TAR 0x00000000 +#define DLL_CTRL_SLV_UP_INT 0x00200000 +#define DLL_CTRL_REF_UP_INT 0x20000000 + +#define SDHCI_DLL_STATUS 0x64 +#define DLL_STS_SLV_LOCK 0x00000001 +#define DLL_STS_REF_LOCK 0x00000002 /* ADMA Addr Descriptor Attribute Filed */ enum { @@ -207,6 +221,7 @@ enum { #define SDHCI_SPEC_100 0 #define SDHCI_SPEC_200 1 #define ESDHC_VENDOR_V22 0x12 +#define ESDHC_VENDOR_V3 0x13 struct sdhci_chip; diff --git a/drivers/mmc/host/mxc_mmc.c b/drivers/mmc/host/mxc_mmc.c index 980dc98c9b5f..9cb492f40145 100644 --- a/drivers/mmc/host/mxc_mmc.c +++ b/drivers/mmc/host/mxc_mmc.c @@ -13,7 +13,7 @@ */ /* - * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2004-2010 Freescale Semiconductor, Inc. All Rights Reserved. */ /* @@ -1232,7 +1232,7 @@ static int mxcmci_probe(struct platform_device *pdev) mmc->f_max = mmc_plat->max_clk; mmc->max_req_size = 32 * 1024; mmc->max_seg_size = mmc->max_req_size; - mmc->max_blk_count = 65536; + mmc->max_blk_count = 32; spin_lock_init(&host->lock); host->mmc = mmc; diff --git a/drivers/mmc/host/mxs-mmc.c b/drivers/mmc/host/mxs-mmc.c index c60352247c49..b7210b7d7af3 100644 --- a/drivers/mmc/host/mxs-mmc.c +++ b/drivers/mmc/host/mxs-mmc.c @@ -49,7 +49,7 @@ #define MXS_MMC_DETECT_TIMEOUT (HZ/2) /* Max value supported for XFER_COUNT */ -#define SSP_BUFFER_SIZE (65536) +#define SSP_BUFFER_SIZE (65535) #ifndef BF #define BF(value, field) (((value) << BP_##field) & BM_##field) @@ -93,6 +93,9 @@ #define BF_SSP_BLOCK_SIZE_BLOCK_SIZE(v) \ (((v) << 16) & BM_SSP_BLOCK_SIZE_BLOCK_SIZE) #endif +#ifndef BM_SSP_CMD0_DBL_DATA_RATE_EN +#define BM_SSP_CMD0_DBL_DATA_RATE_EN 0x02000000 +#endif struct mxs_mmc_host { struct device *dev; @@ -159,6 +162,7 @@ static inline int mxs_mmc_is_plugged(struct mxs_mmc_host *host) return !(status & BM_SSP_STATUS_CARD_DETECT); } +static void mxs_mmc_reset(struct mxs_mmc_host *host); /* Card detection polling function */ static void mxs_mmc_detect_poll(unsigned long arg) { @@ -167,6 +171,8 @@ static void mxs_mmc_detect_poll(unsigned long arg) card_status = mxs_mmc_is_plugged(host); if (card_status != host->present) { + /* Reset MMC block */ + mxs_mmc_reset(host); host->present = card_status; mmc_detect_change(host->mmc, 0); } @@ -183,6 +189,12 @@ static void mxs_mmc_detect_poll(unsigned long arg) BM_SSP_CTRL1_RECV_TIMEOUT_IRQ | \ BM_SSP_CTRL1_FIFO_OVERRUN_IRQ) +#define MXS_MMC_ERR_BITS (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_RECV_TIMEOUT_IRQ) + /* SSP DMA interrupt handler */ static irqreturn_t mmc_irq_handler(int irq, void *dev_id) { @@ -198,19 +210,17 @@ static irqreturn_t mmc_irq_handler(int irq, void *dev_id) /* STOP the dma transfer here. */ mxs_dma_cooked(host->dmach, NULL); } - host->status = - __raw_readl(host->ssp_base + HW_SSP_STATUS); - if (host->cmd) /* else it is a bogus interrupt */ - complete(&host->dma_done); + if ((irq == host->dmairq) || (c1 & MXS_MMC_ERR_BITS)) + if (host->cmd) { + host->status = + __raw_readl(host->ssp_base + HW_SSP_STATUS); + complete(&host->dma_done); + } - if ((c1 & BM_SSP_CTRL1_SDIO_IRQ) && (c1 & BM_SSP_CTRL1_SDIO_IRQ_EN)) { - __raw_writel(BM_SSP_CTRL0_SDIO_IRQ_CHECK, host->ssp_base + \ - HW_SSP_CTRL0_CLR); - __raw_writel(BM_SSP_CTRL1_SDIO_IRQ_EN, host->ssp_base + \ - HW_SSP_CTRL1_CLR); + if ((c1 & BM_SSP_CTRL1_SDIO_IRQ) && (c1 & BM_SSP_CTRL1_SDIO_IRQ_EN)) mmc_signal_sdio_irq(host->mmc); - } + return IRQ_HANDLED; } @@ -239,6 +249,7 @@ static void mxs_mmc_bc(struct mxs_mmc_host *host) { struct mmc_command *cmd = host->cmd; struct mxs_dma_desc *dma_desc = host->dma_desc; + unsigned long flags; dma_desc->cmd.cmd.bits.command = NO_DMA_XFER; dma_desc->cmd.cmd.bits.irq = 1; @@ -255,7 +266,8 @@ static void mxs_mmc_bc(struct mxs_mmc_host *host) if (host->sdio_irq_en) { dma_desc->cmd.pio_words[0] |= BM_SSP_CTRL0_SDIO_IRQ_CHECK; - dma_desc->cmd.pio_words[1] |= BM_SSP_CMD0_CONT_CLKING_EN; + dma_desc->cmd.pio_words[1] |= BM_SSP_CMD0_CONT_CLKING_EN \ + | BM_SSP_CMD0_SLOW_CLKING_EN; } init_completion(&host->dma_done); @@ -265,6 +277,7 @@ static void mxs_mmc_bc(struct mxs_mmc_host *host) dev_dbg(host->dev, "%s start DMA.\n", __func__); if (mxs_dma_enable(host->dmach) < 0) dev_err(host->dev, "mmc_dma_enable failed\n"); + wait_for_completion(&host->dma_done); cmd->error = mxs_mmc_cmd_error(host->status); @@ -273,6 +286,7 @@ static void mxs_mmc_bc(struct mxs_mmc_host *host) dev_dbg(host->dev, "Command error 0x%x\n", cmd->error); mxs_dma_reset(host->dmach); } + mxs_dma_disable(host->dmach); } /* Send the ac command to the device */ @@ -284,6 +298,7 @@ static void mxs_mmc_ac(struct mxs_mmc_host *host) u32 ssp_ctrl0; u32 ssp_cmd0; u32 ssp_cmd1; + unsigned long flags; ignore_crc = (mmc_resp_type(cmd) & MMC_RSP_CRC) ? 0 : BM_SSP_CTRL0_IGNORE_CRC; @@ -305,7 +320,8 @@ static void mxs_mmc_ac(struct mxs_mmc_host *host) if (host->sdio_irq_en) { ssp_ctrl0 |= BM_SSP_CTRL0_SDIO_IRQ_CHECK; - ssp_cmd0 |= BM_SSP_CMD0_CONT_CLKING_EN; + ssp_cmd0 |= BM_SSP_CMD0_CONT_CLKING_EN \ + | BM_SSP_CMD0_SLOW_CLKING_EN; } dma_desc->cmd.pio_words[0] = ssp_ctrl0; @@ -356,6 +372,7 @@ static void mxs_mmc_ac(struct mxs_mmc_host *host) dev_dbg(host->dev, "Command error 0x%x\n", cmd->error); mxs_dma_reset(host->dmach); } + mxs_dma_disable(host->dmach); } /* Copy data between sg list and dma buffer */ @@ -451,6 +468,7 @@ static void mxs_mmc_adtc(struct mxs_mmc_host *host) u32 data_size = cmd->data->blksz * cmd->data->blocks; u32 log2_block_size; + unsigned long flags; ignore_crc = mmc_resp_type(cmd) & MMC_RSP_CRC ? 0 : 1; resp = mmc_resp_type(cmd) & MMC_RSP_PRESENT ? 1 : 0; @@ -540,6 +558,9 @@ static void mxs_mmc_adtc(struct mxs_mmc_host *host) dev_dbg(host->dev, "%s blksz is 0x%x.\n", __func__, log2_block_size); if (ssp_ver_major > 3) { + /* Configure the CMD0 */ + ssp_cmd0 = BF(cmd->opcode, SSP_CMD0_CMD); + /* Configure the BLOCK SIZE and BLOCK COUNT */ if ((1<<log2_block_size) != cmd->data->blksz) { BUG_ON(cmd->data->blocks > 1); @@ -548,20 +569,32 @@ static void mxs_mmc_adtc(struct mxs_mmc_host *host) val = BF(log2_block_size, SSP_BLOCK_SIZE_BLOCK_SIZE) | BF(cmd->data->blocks - 1, SSP_BLOCK_SIZE_BLOCK_COUNT); __raw_writel(val, host->ssp_base + HW_SSP_BLOCK_SIZE); - } - /* Configure the CMD0 */ - ssp_cmd0 = BF(cmd->opcode, SSP_CMD0_CMD); - } else - ssp_cmd0 = - BF(log2_block_size, SSP_BLOCK_SIZE_BLOCK_SIZE) | - BF(cmd->opcode, SSP_CMD0_CMD) | - BF(cmd->data->blocks - 1, SSP_BLOCK_SIZE_BLOCK_COUNT); + if (host->mmc->ios.bus_width & MMC_BUS_WIDTH_DDR) + /* Enable the DDR mode */ + ssp_cmd0 |= BM_SSP_CMD0_DBL_DATA_RATE_EN; + else + ssp_cmd0 &= ~BM_SSP_CMD0_DBL_DATA_RATE_EN; + } + } else { + if ((1<<log2_block_size) != cmd->data->blksz) { + BUG_ON(cmd->data->blocks > 1); + ssp_cmd0 = + BF(0, SSP_BLOCK_SIZE_BLOCK_SIZE) | + BF(cmd->opcode, SSP_CMD0_CMD) | + BF(0, SSP_BLOCK_SIZE_BLOCK_COUNT); + } else + ssp_cmd0 = + BF(log2_block_size, SSP_BLOCK_SIZE_BLOCK_SIZE) | + BF(cmd->opcode, SSP_CMD0_CMD) | + BF(cmd->data->blocks - 1, SSP_BLOCK_SIZE_BLOCK_COUNT); + } if (host->sdio_irq_en) { ssp_ctrl0 |= BM_SSP_CTRL0_SDIO_IRQ_CHECK; - ssp_cmd0 |= BM_SSP_CMD0_CONT_CLKING_EN; + ssp_cmd0 |= BM_SSP_CMD0_CONT_CLKING_EN \ + | BM_SSP_CMD0_SLOW_CLKING_EN; } - if (cmd->opcode == 12) + if ((cmd->opcode == 12) || (cmd->opcode == 53)) ssp_cmd0 |= BM_SSP_CMD0_APPEND_8CYC; ssp_cmd1 = BF(cmd->arg, SSP_CMD1_CMD_ARG); @@ -628,6 +661,7 @@ static void mxs_mmc_adtc(struct mxs_mmc_host *host) dev_dbg(host->dev, "Transferred %u bytes\n", cmd->data->bytes_xfered); } + mxs_dma_disable(host->dmach); } /* Begin sedning a command to the card */ @@ -672,6 +706,13 @@ static void mxs_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) dev_dbg(host->dev, "MMC request\n"); + if (!host->present) { + mrq->cmd->error = -ETIMEDOUT; + mmc_request_done(mmc, mrq); + return; + } + + BUG_ON(host->mrq != NULL); host->mrq = mrq; mxs_mmc_start_cmd(host, mrq->cmd); @@ -777,9 +818,9 @@ static void mxs_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) dev_warn(host->dev, "Platform does not support CMD pin pullup control\n"); - if (ios->bus_width == MMC_BUS_WIDTH_8) + if ((ios->bus_width & ~MMC_BUS_WIDTH_DDR) == MMC_BUS_WIDTH_8) host->bus_width = 2; - else if (ios->bus_width == MMC_BUS_WIDTH_4) + else if ((ios->bus_width & ~MMC_BUS_WIDTH_DDR) == MMC_BUS_WIDTH_4) host->bus_width = 1; else host->bus_width = 0; @@ -851,7 +892,6 @@ static void mxs_mmc_reset(struct mxs_mmc_host *host) /* Configure SSP Control Register 1 */ ssp_ctrl1 = 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 | |