--- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -130,7 +130,7 @@ mmc_blk_getgeo(struct block_device *bdev return 0; } -static struct block_device_operations mmc_bdops = { +static const struct block_device_operations mmc_bdops = { .open = mmc_blk_open, .release = mmc_blk_release, .getgeo = mmc_blk_getgeo, @@ -392,13 +392,9 @@ static int mmc_blk_issue_rq(struct mmc_q } while (!(cmd.resp[0] & R1_READY_FOR_DATA) || (R1_CURRENT_STATE(cmd.resp[0]) == 7)); -#if 0 if (cmd.resp[0] & ~0x00000900) printk(KERN_ERR "%s: status = %08x\n", req->rq_disk->disk_name, cmd.resp[0]); - if (mmc_decode_status(cmd.resp)) - goto cmd_err; -#endif } if (brq.cmd.error || brq.stop.error || brq.data.error) { --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -37,6 +37,9 @@ #include "sd_ops.h" #include "sdio_ops.h" +/* scott.trace */ +//#define MMC_DEBUG + static struct workqueue_struct *workqueue; /* @@ -90,17 +93,30 @@ void mmc_request_done(struct mmc_host *h cmd->error = 0; host->ops->request(host, mrq); } else { +#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) led_trigger_event(host->led, LED_OFF); +#endif pr_debug("%s: req done (CMD%u): %d: %08x %08x %08x %08x\n", mmc_hostname(host), cmd->opcode, err, cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3]); +#ifdef MMC_DEBUG + printk("[MMC_DEBUG] %s: req done (CMD%u): %d: %08x %08x %08x %08x\n", + mmc_hostname(host), cmd->opcode, err, + cmd->resp[0], cmd->resp[1], + cmd->resp[2], cmd->resp[3]); +#endif if (mrq->data) { pr_debug("%s: %d bytes transferred: %d\n", mmc_hostname(host), mrq->data->bytes_xfered, mrq->data->error); +#ifdef MMC_DEBUG + printk("[MMC_DEBUG] %s: %d bytes transferred: %d\n", + mmc_hostname(host), + mrq->data->bytes_xfered, mrq->data->error); +#endif } if (mrq->stop) { @@ -109,6 +125,13 @@ void mmc_request_done(struct mmc_host *h mrq->stop->error, mrq->stop->resp[0], mrq->stop->resp[1], mrq->stop->resp[2], mrq->stop->resp[3]); +#ifdef MMC_DEBUG + printk("[MMC_DEBUG] %s: (CMD%u): %d: %08x %08x %08x %08x\n", + mmc_hostname(host), mrq->stop->opcode, + mrq->stop->error, + mrq->stop->resp[0], mrq->stop->resp[1], + mrq->stop->resp[2], mrq->stop->resp[3]); +#endif } if (mrq->done) @@ -129,6 +152,11 @@ mmc_start_request(struct mmc_host *host, pr_debug("%s: starting CMD%u arg %08x flags %08x\n", mmc_hostname(host), mrq->cmd->opcode, mrq->cmd->arg, mrq->cmd->flags); +#ifdef MMC_DEBUG + printk("[MMC_DEBUG] %s: starting CMD%u arg %08x flags %08x\n", + mmc_hostname(host), mrq->cmd->opcode, + mrq->cmd->arg, mrq->cmd->flags); +#endif if (mrq->data) { pr_debug("%s: blksz %d blocks %d flags %08x " @@ -137,17 +165,32 @@ mmc_start_request(struct mmc_host *host, mrq->data->blocks, mrq->data->flags, mrq->data->timeout_ns / 1000000, mrq->data->timeout_clks); +#ifdef MMC_DEBUG + printk("[MMC_DEBUG] %s: blksz %d blocks %d flags %08x " + "tsac %d ms nsac %d\n", + mmc_hostname(host), mrq->data->blksz, + mrq->data->blocks, mrq->data->flags, + mrq->data->timeout_ns / 1000000, + mrq->data->timeout_clks); +#endif } if (mrq->stop) { pr_debug("%s: CMD%u arg %08x flags %08x\n", mmc_hostname(host), mrq->stop->opcode, mrq->stop->arg, mrq->stop->flags); +#ifdef MMC_DEBUG + printk("[MMC_DEBUG] %s: CMD%u arg %08x flags %08x\n", + mmc_hostname(host), mrq->stop->opcode, + mrq->stop->arg, mrq->stop->flags); +#endif } WARN_ON(!host->claimed); +#if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) led_trigger_event(host->led, LED_FULL); +#endif mrq->cmd->error = 0; mrq->cmd->mrq = mrq; @@ -286,9 +329,9 @@ void mmc_set_data_timeout(struct mmc_dat * The limit is really 250 ms, but that is * insufficient for some crappy cards. */ - limit_us = 300000; + limit_us = 500000; else - limit_us = 100000; + limit_us = 200000; /* * SDHC cards always use these fixed values. @@ -344,6 +387,101 @@ unsigned int mmc_align_data_size(struct 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 +504,111 @@ int __mmc_claim_host(struct mmc_host *ho 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 +617,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 +907,13 @@ void mmc_set_timing(struct mmc_host *hos */ 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)) { @@ -947,6 +1173,8 @@ 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(); @@ -958,6 +1186,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); @@ -966,6 +1196,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 /** @@ -975,27 +1279,36 @@ 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); + err = 0; } } mmc_bus_put(host); - mmc_power_off(host); + if (!err) + mmc_power_off(host); - return 0; + return err; } EXPORT_SYMBOL(mmc_suspend_host); @@ -1006,12 +1319,26 @@ 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); 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); + if (host->bus_ops->remove) + host->bus_ops->remove(host); + mmc_claim_host(host); + mmc_detach_bus(host); + mmc_release_host(host); + /* no need to bother upper layers */ + err = 0; + } } mmc_bus_put(host); @@ -1021,7 +1348,7 @@ int mmc_resume_host(struct mmc_host *hos */ mmc_detect_change(host, 1); - return 0; + return err; } EXPORT_SYMBOL(mmc_resume_host); --- 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); --- a/drivers/mmc/core/debugfs.c +++ b/drivers/mmc/core/debugfs.c @@ -240,7 +240,7 @@ static int mmc_ext_csd_release(struct in return 0; } -static struct file_operations mmc_dbg_ext_csd_fops = { +static const struct file_operations mmc_dbg_ext_csd_fops = { .open = mmc_ext_csd_open, .read = mmc_ext_csd_read, .release = mmc_ext_csd_release, --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -83,6 +83,7 @@ struct mmc_host *mmc_alloc_host(int extr 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); /* * By default, hosts do not support SGIO or large requests. --- 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 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -160,7 +160,6 @@ static int mmc_read_ext_csd(struct mmc_c { 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_c goto out; } - ext_csd_struct = ext_csd[EXT_CSD_REV]; - if (ext_csd_struct > 3) { + 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 | @@ -241,6 +240,15 @@ static int mmc_read_ext_csd(struct mmc_c 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); @@ -276,7 +284,7 @@ static struct attribute_group mmc_std_at .attrs = mmc_std_attrs, }; -static struct attribute_group *mmc_attr_groups[] = { +static const struct attribute_group *mmc_attr_groups[] = { &mmc_std_attr_group, NULL, }; @@ -408,12 +416,17 @@ static int mmc_init_card(struct mmc_host (host->caps & MMC_CAP_MMC_HIGHSPEED)) { err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_HS_TIMING, 1); - if (err) + if (err && err != -EBADMSG) goto free_card; - mmc_card_set_highspeed(card); - - mmc_set_timing(card->host, MMC_TIMING_MMC_HS); + if (err) { + printk(KERN_WARNING "%s: switch to highspeed failed\n", + mmc_hostname(card->host)); + err = 0; + } else { + mmc_card_set_highspeed(card); + mmc_set_timing(card->host, MMC_TIMING_MMC_HS); + } } /* @@ -448,10 +461,17 @@ static int mmc_init_card(struct mmc_host err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BUS_WIDTH, ext_csd_bit); - if (err) + if (err && err != -EBADMSG) goto free_card; - mmc_set_bus_width(card->host, bus_width); + if (err) { + printk(KERN_WARNING "%s: switch to bus width %d " + "failed\n", mmc_hostname(card->host), + 1 << bus_width); + err = 0; + } else { + mmc_set_bus_width(card->host, bus_width); + } } if (!oldcard) @@ -507,12 +527,10 @@ static void mmc_detect(struct mmc_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 +540,8 @@ static void mmc_suspend(struct mmc_host mmc_deselect_cards(host); host->card->state &= ~MMC_STATE_HIGHSPEED; mmc_release_host(host); + + return 0; } /* @@ -530,7 +550,7 @@ static void mmc_suspend(struct mmc_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 +561,99 @@ static void mmc_resume(struct mmc_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 +static int mmc_awake(struct mmc_host *host) +{ + struct mmc_card *card = host->card; + int err = -ENOSYS; -#define mmc_suspend NULL -#define mmc_resume NULL + 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; +} + +#ifdef CONFIG_MMC_UNSAFE_RESUME + +static const struct mmc_bus_ops mmc_ops = { + .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) +{ + mmc_attach_bus(host, &mmc_ops); +} + +#else 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) + bus_ops = &mmc_ops_unsafe; + else + bus_ops = &mmc_ops; + mmc_attach_bus(host, bus_ops); +} + +#endif + /* * Starting point for MMC card init. */ @@ -575,7 +664,7 @@ int mmc_attach_mmc(struct mmc_host *host 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. --- 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 * 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; @@ -354,6 +390,7 @@ int mmc_switch(struct mmc_card *card, u8 { int err; struct mmc_command cmd; + u32 status; BUG_ON(!card); BUG_ON(!card->host); @@ -371,6 +408,28 @@ int mmc_switch(struct mmc_card *card, u8 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; } --- 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 *car 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 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -314,7 +314,7 @@ static struct attribute_group sd_std_att .attrs = sd_std_attrs, }; -static struct attribute_group *sd_attr_groups[] = { +static const struct attribute_group *sd_attr_groups[] = { &sd_std_attr_group, NULL, }; @@ -561,12 +561,10 @@ static void mmc_sd_detect(struct mmc_hos } } -#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_ho 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_ho * 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,63 @@ static void mmc_sd_resume(struct mmc_hos 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; +} +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); } -#else +#ifdef CONFIG_MMC_UNSAFE_RESUME -#define mmc_sd_suspend NULL -#define mmc_sd_resume NULL +static const struct mmc_bus_ops mmc_sd_ops = { + .remove = mmc_sd_remove, + .detect = mmc_sd_detect, + .suspend = mmc_sd_suspend, + .resume = mmc_sd_resume, + .power_restore = mmc_sd_power_restore, +}; -#endif +static void mmc_sd_attach_bus_ops(struct mmc_host *host) +{ + mmc_attach_bus(host, &mmc_sd_ops); +} + +#else 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) + bus_ops = &mmc_sd_ops_unsafe; + else + bus_ops = &mmc_sd_ops; + mmc_attach_bus(host, bus_ops); +} + +#endif + /* * Starting point for SD card init. */ @@ -629,7 +662,7 @@ int mmc_attach_sd(struct mmc_host *host, 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. --- a/drivers/mmc/core/sdio_bus.c +++ b/drivers/mmc/core/sdio_bus.c @@ -20,9 +20,6 @@ #include "sdio_cis.h" #include "sdio_bus.h" -#define dev_to_sdio_func(d) container_of(d, struct sdio_func, dev) -#define to_sdio_driver(d) container_of(d, struct sdio_driver, drv) - /* show configuration fields */ #define sdio_config_attr(field, format_string) \ static ssize_t \ @@ -251,12 +248,15 @@ int sdio_add_func(struct sdio_func *func /* * Unregister a SDIO function with the driver model, and * (eventually) free it. + * This function can be called through error paths where sdio_add_func() was + * never executed (because a failure occurred at an earlier point). */ void sdio_remove_func(struct sdio_func *func) { - if (sdio_func_present(func)) - device_del(&func->dev); + if (!sdio_func_present(func)) + return; + device_del(&func->dev); put_device(&func->dev); } --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -165,6 +165,29 @@ static int sdio_enable_wide(struct mmc_c } /* + * If desired, disconnect the pull-up resistor on CD/DAT[3] (pin 1) + * of the card. This may be required on certain setups of boards, + * controllers and embedded sdio device which do not need the card's + * pull-up. As a result, card detection is disabled and power is saved. + */ +static int sdio_disable_cd(struct mmc_card *card) +{ + int ret; + u8 ctrl; + + if (!card->cccr.disable_cd) + return 0; + + ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_IF, 0, &ctrl); + if (ret) + return ret; + + ctrl |= SDIO_BUS_CD_DISABLE; + + return mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_IF, ctrl, NULL); +} + +/* * Test if the card supports high-speed mode and, if so, switch to it. */ static int sdio_enable_hs(struct mmc_card *card) @@ -195,6 +218,135 @@ static int sdio_enable_hs(struct mmc_car } /* + * 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 +395,77 @@ static void mmc_sdio_detect(struct mmc_h } } +/* + * 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, }; @@ -275,13 +494,6 @@ int mmc_attach_sdio(struct mmc_host *hos ocr &= ~0x7F; } - if (ocr & MMC_VDD_165_195) { - printk(KERN_WARNING "%s: SDIO card claims to support the " - "incompletely defined 'low voltage range'. This " - "will be ignored.\n", mmc_hostname(host)); - ocr &= ~MMC_VDD_165_195; - } - host->ocr = mmc_select_voltage(host, ocr); /* @@ -293,108 +505,31 @@ int mmc_attach_sdio(struct mmc_host *hos } /* - * Inform the card of the voltage + * Detect and init the card. */ - err = mmc_send_io_op_cond(host, host->ocr, &ocr); + err = mmc_sdio_init_card(host, host->ocr, NULL); 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; - } + card = host->card; /* * The number of functions on the card is encoded inside * the ocr. */ funcs = (ocr & 0x70000000) >> 28; + card->sdio_funcs = 0; /* - * Allocate card structure. - */ - card = mmc_alloc_card(host, NULL); - if (IS_ERR(card)) { - err = PTR_ERR(card); - goto err; - } - - card->type = MMC_TYPE_SDIO; - card->sdio_funcs = funcs; - - host->card = card; - - /* - * For native busses: set card RCA and quit open drain mode. + * If needed, disconnect card detection pull-up resistor. */ - 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; - - /* - * 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); + err = sdio_disable_cd(card); if (err) goto remove; /* * Initialize (but don't add) all present functions. */ - for (i = 0;i < funcs;i++) { + for (i = 0; i < funcs; i++, card->sdio_funcs++) { err = sdio_init_func(host->card, i + 1); if (err) goto remove; --- a/drivers/mmc/core/sdio_cis.c +++ b/drivers/mmc/core/sdio_cis.c @@ -29,6 +29,8 @@ static int cistpl_vers_1(struct mmc_card unsigned i, nr_strings; char **buffer, *string; + /* Find all null-terminated (including zero length) strings in + the TPLLV1_INFO field. Trailing garbage is ignored. */ buf += 2; size -= 2; @@ -39,11 +41,8 @@ static int cistpl_vers_1(struct mmc_card if (buf[i] == 0) nr_strings++; } - - if (buf[i-1] != '\0') { - printk(KERN_WARNING "SDIO: ignoring broken CISTPL_VERS_1\n"); + if (nr_strings == 0) return 0; - } size = i; @@ -98,6 +97,22 @@ static const unsigned char speed_val[16] static const unsigned int speed_unit[8] = { 10000, 100000, 1000000, 10000000, 0, 0, 0, 0 }; +/* FUNCE tuples with these types get passed to SDIO drivers */ +static const unsigned char funce_type_whitelist[] = { + 4 /* CISTPL_FUNCE_LAN_NODE_ID used in Broadcom cards */ +}; + +static int cistpl_funce_whitelisted(unsigned char type) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(funce_type_whitelist); i++) { + if (funce_type_whitelist[i] == type) + return 1; + } + return 0; +} + static int cistpl_funce_common(struct mmc_card *card, const unsigned char *buf, unsigned size) { @@ -120,6 +135,10 @@ static int cistpl_funce_func(struct sdio unsigned vsn; unsigned min_size; + /* let SDIO drivers take care of whitelisted FUNCE tuples */ + if (cistpl_funce_whitelisted(buf[0])) + return -EILSEQ; + vsn = func->card->cccr.sdio_vsn; min_size = (vsn == SDIO_SDIO_REV_1_00) ? 28 : 42; @@ -154,13 +173,12 @@ static int cistpl_funce(struct mmc_card else ret = cistpl_funce_common(card, buf, size); - if (ret) { + if (ret && ret != -EILSEQ) { printk(KERN_ERR "%s: bad CISTPL_FUNCE size %u " "type %u\n", mmc_hostname(card->host), size, buf[0]); - return ret; } - return 0; + return ret; } typedef int (tpl_parse_t)(struct mmc_card *, struct sdio_func *, @@ -253,21 +271,12 @@ static int sdio_read_cis(struct mmc_card for (i = 0; i < ARRAY_SIZE(cis_tpl_list); i++) if (cis_tpl_list[i].code == tpl_code) break; - if (i >= ARRAY_SIZE(cis_tpl_list)) { - /* this tuple is unknown to the core */ - this->next = NULL; - this->code = tpl_code; - this->size = tpl_link; - *prev = this; - prev = &this->next; - printk(KERN_DEBUG - "%s: queuing CIS tuple 0x%02x length %u\n", - mmc_hostname(card->host), tpl_code, tpl_link); - } else { + if (i < ARRAY_SIZE(cis_tpl_list)) { const struct cis_tpl *tpl = cis_tpl_list + i; if (tpl_link < tpl->min_size) { printk(KERN_ERR - "%s: bad CIS tuple 0x%02x (length = %u, expected >= %u)\n", + "%s: bad CIS tuple 0x%02x" + " (length = %u, expected >= %u)\n", mmc_hostname(card->host), tpl_code, tpl_link, tpl->min_size); ret = -EINVAL; @@ -275,7 +284,30 @@ static int sdio_read_cis(struct mmc_card ret = tpl->parse(card, func, this->data, tpl_link); } - kfree(this); + /* + * We don't need the tuple anymore if it was + * successfully parsed by the SDIO core or if it is + * not going to be parsed by SDIO drivers. + */ + if (!ret || ret != -EILSEQ) + kfree(this); + } else { + /* unknown tuple */ + ret = -EILSEQ; + } + + if (ret == -EILSEQ) { + /* this tuple is unknown to the core or whitelisted */ + this->next = NULL; + this->code = tpl_code; + this->size = tpl_link; + *prev = this; + prev = &this->next; + printk(KERN_DEBUG + "%s: queuing CIS tuple 0x%02x length %u\n", + mmc_hostname(card->host), tpl_code, tpl_link); + /* keep on analyzing tuples */ + ret = 0; } ptr += tpl_link; --- a/drivers/mmc/core/sdio_io.c +++ b/drivers/mmc/core/sdio_io.c @@ -624,7 +624,7 @@ void sdio_f0_writeb(struct sdio_func *fu BUG_ON(!func); - if (addr < 0xF0 || addr > 0xFF) { + if ((addr < 0xF0 || addr > 0xFF) && (!mmc_card_lenient_fn0(func->card))) { if (err_ret) *err_ret = -EINVAL; return; --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -55,6 +55,17 @@ config MMC_SDHCI_PCI If unsure, say N. +config MMC_SDHCI_CNS3XXX + tristate "SDHCI support on CNS3XXX" + depends on MMC_SDHCI && ARCH_CNS3XXX + help + This selects the Secure Digital Host Controller Interface (SDHCI) + in Cavium Networks CNS3XXX SOCs. + + If you have a controller with this interface, say Y or M here. + + If unsure, say N. + config MMC_RICOH_MMC tristate "Ricoh MMC Controller Disabler (EXPERIMENTAL)" depends on MMC_SDHCI_PCI --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_MMC_IMX) += imxmmc.o obj-$(CONFIG_MMC_MXC) += mxcmmc.o obj-$(CONFIG_MMC_SDHCI) += sdhci.o obj-$(CONFIG_MMC_SDHCI_PCI) += sdhci-pci.o +obj-$(CONFIG_MMC_SDHCI_CNS3XXX) += sdhci-cns3xxx.o obj-$(CONFIG_MMC_RICOH_MMC) += ricoh_mmc.o obj-$(CONFIG_MMC_SDHCI_OF) += sdhci-of.o obj-$(CONFIG_MMC_SDHCI_PLTFM) += sdhci-pltfm.o --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -27,6 +27,15 @@ #define DRIVER_NAME "sdhci" +#define SDHCI_DEBUG +#undef SDHCI_DEBUG + +#ifdef SDHCI_DEBUG +#define sd_printk(x...) printk(x) +#else +#define sd_printk(x...) do { } while(0) +#endif + #define DBG(f, x...) \ pr_debug(DRIVER_NAME " [%s()]: " f, __func__,## x) @@ -43,6 +52,39 @@ static void sdhci_finish_data(struct sdh static void sdhci_send_command(struct sdhci_host *, struct mmc_command *); static void sdhci_finish_command(struct sdhci_host *); +static void sdhci_dumpallregs(struct sdhci_host *host) +{ +#ifdef SDHCI_DEBUG + printk(" _______________________________________________\n"); + + printk(" 0x00: 0x%08x | 0x04: 0x%08x\n", sdhci_readl(host, 0x00), sdhci_readl(host, 0x04)); + printk(" 0x08: 0x%08x | 0x0C: 0x%08x\n", sdhci_readl(host, 0x08), sdhci_readl(host, 0x0C)); + printk(" 0x10: 0x%08x | 0x14: 0x%08x\n", sdhci_readl(host, 0x10), sdhci_readl(host, 0x14)); + printk(" 0x18: 0x%08x | 0x1C: 0x%08x\n", sdhci_readl(host, 0x18), sdhci_readl(host, 0x1C)); + printk(" -----------------| 0x24: 0x%08x\n", sdhci_readl(host, 0x24)); + printk(" 0x28: 0x%08x | 0x2C: 0x%08x\n", sdhci_readl(host, 0x28), sdhci_readl(host, 0x2C)); + printk(" 0x30: 0x%08x | 0x34: 0x%08x\n", sdhci_readl(host, 0x30), sdhci_readl(host, 0x34)); + printk(" 0x38: 0x%08x | 0x3C: 0x%08x\n", sdhci_readl(host, 0x38), sdhci_readl(host, 0x3C)); + printk(" 0x40: 0x%08x | 0x44: 0x%08x\n", sdhci_readl(host, 0x40), sdhci_readl(host, 0x44)); + printk(" 0x48: 0x%08x | 0x4C: 0x%08x\n", sdhci_readl(host, 0x48), sdhci_readl(host, 0x4C)); + printk(" 0x50: 0x%08x | 0xFC: 0x%08x\n", sdhci_readl(host, 0x50), sdhci_readl(host, 0xFC)); +//#else + printk(KERN_DEBUG " _______________________________________________\n"); + + printk(KERN_DEBUG " 0x00: 0x%08x | 0x04: 0x%08x\n", sdhci_readl(host, 0x00), sdhci_readl(host, 0x04)); + printk(KERN_DEBUG " 0x08: 0x%08x | 0x0C: 0x%08x\n", sdhci_readl(host, 0x08), sdhci_readl(host, 0x0C)); + printk(KERN_DEBUG " 0x10: 0x%08x | 0x14: 0x%08x\n", sdhci_readl(host, 0x10), sdhci_readl(host, 0x14)); + printk(KERN_DEBUG " 0x18: 0x%08x | 0x1C: 0x%08x\n", sdhci_readl(host, 0x18), sdhci_readl(host, 0x1C)); + printk(KERN_DEBUG " -----------------| 0x24: 0x%08x\n", sdhci_readl(host, 0x24)); + printk(KERN_DEBUG " 0x28: 0x%08x | 0x2C: 0x%08x\n", sdhci_readl(host, 0x28), sdhci_readl(host, 0x2C)); + printk(KERN_DEBUG " 0x30: 0x%08x | 0x34: 0x%08x\n", sdhci_readl(host, 0x30), sdhci_readl(host, 0x34)); + printk(KERN_DEBUG " 0x38: 0x%08x | 0x3C: 0x%08x\n", sdhci_readl(host, 0x38), sdhci_readl(host, 0x3C)); + printk(KERN_DEBUG " 0x40: 0x%08x | 0x44: 0x%08x\n", sdhci_readl(host, 0x40), sdhci_readl(host, 0x44)); + printk(KERN_DEBUG " 0x48: 0x%08x | 0x4C: 0x%08x\n", sdhci_readl(host, 0x48), sdhci_readl(host, 0x4C)); + printk(KERN_DEBUG " 0x50: 0x%08x | 0xFC: 0x%08x\n", sdhci_readl(host, 0x50), sdhci_readl(host, 0xFC)); +#endif +} + static void sdhci_dumpregs(struct sdhci_host *host) { printk(KERN_DEBUG DRIVER_NAME ": ============== REGISTER DUMP ==============\n"); @@ -591,6 +633,9 @@ static u8 sdhci_calc_timeout(struct sdhc target_timeout = data->timeout_ns / 1000 + data->timeout_clks / host->clock; + if (host->quirks & SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK) + host->timeout_clk = host->clock / 1000; + /* * Figure out needed cycles. * We do this in steps in order to fit inside a 32 bit int. @@ -622,7 +667,7 @@ static u8 sdhci_calc_timeout(struct sdhc static void sdhci_set_transfer_irqs(struct sdhci_host *host) { u32 pio_irqs = SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL; - u32 dma_irqs = SDHCI_INT_DMA_END | SDHCI_INT_ADMA_ERROR; + u32 dma_irqs = SDHCI_INT_DMA_END | SDHCI_INT_ACMD12ERR | SDHCI_INT_ADMA_ERROR; if (host->flags & SDHCI_REQ_USE_DMA) sdhci_clear_set_irqs(host, pio_irqs, dma_irqs); @@ -652,7 +697,7 @@ static void sdhci_prepare_data(struct sd count = sdhci_calc_timeout(host, data); sdhci_writeb(host, count, SDHCI_TIMEOUT_CONTROL); - if (host->flags & SDHCI_USE_DMA) + if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) host->flags |= SDHCI_REQ_USE_DMA; /* @@ -736,11 +781,21 @@ static void sdhci_prepare_data(struct sd } else { int sg_cnt; + sd_printk("[SDHCI_DEBUG] dma_map_sg(), mmc_dev(host->mmc) = %p \n", mmc_dev(host->mmc)); + sd_printk("[SDHCI_DEBUG] dma_map_sg(), data->sg = %p \n", data->sg); + sd_printk("[SDHCI_DEBUG] dma_map_sg(), data->sg_len = %d \n", data->sg_len); sg_cnt = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, (data->flags & MMC_DATA_READ) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + if (data->sg == NULL) { + sd_printk("[SDHCI_DEBUG] dma_map_sg(), data->sg = (NULL) \n"); + return; + } + sd_printk("[SDHCI_DEBUG] dma_map_sg(), data->sg = %p \n", data->sg); + sd_printk("[SDHCI_DEBUG] dma_map_sg(), sg_cnt = %d \n", sg_cnt); + if (sg_cnt == 0) { /* * This only happens when someone fed @@ -750,6 +805,7 @@ static void sdhci_prepare_data(struct sd host->flags &= ~SDHCI_REQ_USE_DMA; } else { WARN_ON(sg_cnt != 1); + sd_printk("[SDHCI_DEBUG] sg_dma_address() => %08x \n", sg_dma_address(data->sg)); sdhci_writel(host, sg_dma_address(data->sg), SDHCI_DMA_ADDRESS); } @@ -763,14 +819,32 @@ static void sdhci_prepare_data(struct sd */ if (host->version >= SDHCI_SPEC_200) { ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); +#ifdef SDHCI_USE_LEDS_CLASS + ctrl |= SDHCI_CTRL_LED; +#else + ctrl &= ~SDHCI_CTRL_LED; +#endif ctrl &= ~SDHCI_CTRL_DMA_MASK; if ((host->flags & SDHCI_REQ_USE_DMA) && (host->flags & SDHCI_USE_ADMA)) ctrl |= SDHCI_CTRL_ADMA32; else ctrl |= SDHCI_CTRL_SDMA; + sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); - } + } else if (host->version == SDHCI_SPEC_100) { + ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); +#ifdef SDHCI_USE_LEDS_CLASS + ctrl |= SDHCI_CTRL_LED; +#else + ctrl &= ~SDHCI_CTRL_LED; +#endif + ctrl &= ~SDHCI_CTRL_DMA_MASK; + if (host->flags & SDHCI_REQ_USE_DMA) + ctrl |= SDHCI_CTRL_SDMA; + + sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); + } if (!(host->flags & SDHCI_REQ_USE_DMA)) { int flags; @@ -795,15 +869,26 @@ static void sdhci_set_transfer_mode(stru struct mmc_data *data) { u16 mode; + u8 bgctrl; if (data == NULL) return; WARN_ON(!host->data); + bgctrl = sdhci_readb(host, SDHCI_BLOCK_GAP_CONTROL); + if (host->quirks & SDHCI_QUIRK_READ_WAIT_CTRL) + bgctrl |= SDHCI_READ_WAIT_CTRL; + sdhci_writeb(host, bgctrl, SDHCI_BLOCK_GAP_CONTROL); + mode = SDHCI_TRNS_BLK_CNT_EN; - if (data->blocks > 1) + + if (data->blocks > 1) { mode |= SDHCI_TRNS_MULTI; + + if (host->quirks & SDHCI_QUIRK_AUTO_CMD12) + mode |= SDHCI_TRNS_ACMD12; + } if (data->flags & MMC_DATA_READ) mode |= SDHCI_TRNS_READ; if (host->flags & SDHCI_REQ_USE_DMA) @@ -812,6 +897,20 @@ static void sdhci_set_transfer_mode(stru sdhci_writew(host, mode, SDHCI_TRANSFER_MODE); } +static void shdci_check_dma_overrun(struct sdhci_host *host, struct mmc_data *data) +{ + u32 dma_pos = sdhci_readl(host, SDHCI_DMA_ADDRESS); + u32 dma_start = sg_dma_address(data->sg); + u32 dma_end = dma_start + data->sg->length; + + /* Test whether we ended up moving more data than was originally requested. */ + if (dma_pos <= dma_end) + return; + + printk(KERN_ERR "%s: dma overrun, dma %08x, req %08x..%08x\n", + mmc_hostname(host->mmc), dma_pos, dma_start, dma_end); +} + static void sdhci_finish_data(struct sdhci_host *host) { struct mmc_data *data; @@ -825,6 +924,9 @@ static void sdhci_finish_data(struct sdh if (host->flags & SDHCI_USE_ADMA) sdhci_adma_table_post(host, data); else { + shdci_check_dma_overrun(host, data); + + sd_printk("[SDHCI_DEBUG] dma_unmap_sg(), data->sg_len = %d \n", data->sg_len); dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, (data->flags & MMC_DATA_READ) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); @@ -866,12 +968,16 @@ static void sdhci_send_command(struct sd WARN_ON(host->cmd); + sd_printk("[SDHCI_DEBUG] sdhci_send_command() \n"); + /* Wait max 10 ms */ timeout = 10; mask = SDHCI_CMD_INHIBIT; if ((cmd->data != NULL) || (cmd->flags & MMC_RSP_BUSY)) mask |= SDHCI_DATA_INHIBIT; + if ((cmd->data != NULL)) + mask |= SDHCI_DATA_INHIBIT; /* We shouldn't wait for data inihibit for stop commands, even though they might use busy signaling */ @@ -925,7 +1031,11 @@ static void sdhci_send_command(struct sd if (cmd->data) flags |= SDHCI_CMD_DATA; + sd_printk("[SDHCI_DEBUG] sdhci_send_command() => %08x \n", SDHCI_MAKE_CMD(cmd->opcode, flags)); + sdhci_dumpallregs(host); sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND); + sd_printk("[SDHCI_DEBUG] sdhci_send_command(): After issue command \n"); + sdhci_dumpallregs(host); } static void sdhci_finish_command(struct sdhci_host *host) @@ -934,6 +1044,8 @@ static void sdhci_finish_command(struct BUG_ON(host->cmd == NULL); + sd_printk("[SDHCI_DEBUG] sdhci_finish_command() \n"); + if (host->cmd->flags & MMC_RSP_PRESENT) { if (host->cmd->flags & MMC_RSP_136) { /* CRC is stripped so we need to do some shifting. */ @@ -991,8 +1103,8 @@ static void sdhci_set_clock(struct sdhci clk |= SDHCI_CLOCK_INT_EN; sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); - /* Wait max 10 ms */ - timeout = 10; + /* Wait max 20 ms */ + timeout = 20; while (!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL)) & SDHCI_CLOCK_INT_STABLE)) { if (timeout == 0) { @@ -1154,6 +1266,12 @@ static void sdhci_set_ios(struct mmc_hos else ctrl &= ~SDHCI_CTRL_HISPD; +#ifdef SDHCI_USE_LEDS_CLASS + ctrl |= SDHCI_CTRL_LED; +#else + ctrl &= ~SDHCI_CTRL_LED; +#endif + sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); /* @@ -1321,7 +1439,11 @@ static void sdhci_timeout_timer(unsigned if (host->mrq) { printk(KERN_ERR "%s: Timeout waiting for hardware " "interrupt.\n", mmc_hostname(host->mmc)); +#ifdef SDHCI_DEBUG + sdhci_dumpallregs(host); +#else sdhci_dumpregs(host); +#endif if (host->data) { host->data->error = -ETIMEDOUT; @@ -1508,6 +1630,10 @@ static irqreturn_t sdhci_irq(int irq, vo DBG("*** %s got interrupt: 0x%08x\n", mmc_hostname(host->mmc), intmask); +#ifdef SDHCI_DEBUG + printk("*** %s got interrupt: 0x%08x\n", mmc_hostname(host->mmc), intmask); +#endif + if (intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) { sdhci_writel(host, intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE), SDHCI_INT_STATUS); @@ -1597,7 +1723,7 @@ int sdhci_resume_host(struct sdhci_host { int ret; - if (host->flags & SDHCI_USE_DMA) { + if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) { if (host->ops->enable_dma) host->ops->enable_dma(host); } @@ -1678,23 +1804,20 @@ int sdhci_add_host(struct sdhci_host *ho caps = sdhci_readl(host, SDHCI_CAPABILITIES); if (host->quirks & SDHCI_QUIRK_FORCE_DMA) - host->flags |= SDHCI_USE_DMA; - else if (!(caps & SDHCI_CAN_DO_DMA)) - DBG("Controller doesn't have DMA capability\n"); + host->flags |= SDHCI_USE_SDMA; + else if (!(caps & SDHCI_CAN_DO_SDMA)) + DBG("Controller doesn't have SDMA capability\n"); else - host->flags |= SDHCI_USE_DMA; + host->flags |= SDHCI_USE_SDMA; if ((host->quirks & SDHCI_QUIRK_BROKEN_DMA) && - (host->flags & SDHCI_USE_DMA)) { + (host->flags & SDHCI_USE_SDMA)) { DBG("Disabling DMA as it is marked broken\n"); - host->flags &= ~SDHCI_USE_DMA; + host->flags &= ~SDHCI_USE_SDMA; } - if (host->flags & SDHCI_USE_DMA) { - if ((host->version >= SDHCI_SPEC_200) && - (caps & SDHCI_CAN_DO_ADMA2)) - host->flags |= SDHCI_USE_ADMA; - } + if ((host->version >= SDHCI_SPEC_200) && (caps & SDHCI_CAN_DO_ADMA2)) + host->flags |= SDHCI_USE_ADMA; if ((host->quirks & SDHCI_QUIRK_BROKEN_ADMA) && (host->flags & SDHCI_USE_ADMA)) { @@ -1702,13 +1825,14 @@ int sdhci_add_host(struct sdhci_host *ho host->flags &= ~SDHCI_USE_ADMA; } - if (host->flags & SDHCI_USE_DMA) { + if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) { if (host->ops->enable_dma) { if (host->ops->enable_dma(host)) { printk(KERN_WARNING "%s: No suitable DMA " "available. Falling back to PIO.\n", mmc_hostname(mmc)); - host->flags &= ~(SDHCI_USE_DMA | SDHCI_USE_ADMA); + host->flags &= + ~(SDHCI_USE_SDMA | SDHCI_USE_ADMA); } } } @@ -1736,7 +1860,7 @@ int sdhci_add_host(struct sdhci_host *ho * mask, but PIO does not need the hw shim so we set a new * mask here in that case. */ - if (!(host->flags & SDHCI_USE_DMA)) { + if (!(host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA))) { host->dma_mask = DMA_BIT_MASK(64); mmc_dev(host->mmc)->dma_mask = &host->dma_mask; } @@ -1757,13 +1881,15 @@ int sdhci_add_host(struct sdhci_host *ho host->timeout_clk = (caps & SDHCI_TIMEOUT_CLK_MASK) >> SDHCI_TIMEOUT_CLK_SHIFT; if (host->timeout_clk == 0) { - if (!host->ops->get_timeout_clock) { + if (host->ops->get_timeout_clock) { + host->timeout_clk = host->ops->get_timeout_clock(host); + } else if (!(host->quirks & + SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK)) { printk(KERN_ERR "%s: Hardware doesn't specify timeout clock " "frequency.\n", mmc_hostname(mmc)); return -ENODEV; } - host->timeout_clk = host->ops->get_timeout_clock(host); } if (caps & SDHCI_TIMEOUT_CLK_UNIT) host->timeout_clk *= 1000; @@ -1772,7 +1898,8 @@ int sdhci_add_host(struct sdhci_host *ho * Set host parameters. */ mmc->ops = &sdhci_ops; - if (host->ops->get_min_clock) + if (host->quirks & SDHCI_QUIRK_NONSTANDARD_CLOCK && + host->ops->set_clock && host->ops->get_min_clock) mmc->f_min = host->ops->get_min_clock(host); else mmc->f_min = host->max_clk / 256; @@ -1810,7 +1937,7 @@ int sdhci_add_host(struct sdhci_host *ho */ if (host->flags & SDHCI_USE_ADMA) mmc->max_hw_segs = 128; - else if (host->flags & SDHCI_USE_DMA) + else if (host->flags & SDHCI_USE_SDMA) mmc->max_hw_segs = 1; else /* PIO */ mmc->max_hw_segs = 128; @@ -1893,10 +2020,10 @@ int sdhci_add_host(struct sdhci_host *ho mmc_add_host(mmc); - printk(KERN_INFO "%s: SDHCI controller on %s [%s] using %s%s\n", + printk(KERN_INFO "%s: SDHCI controller on %s [%s] using %s\n", mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)), - (host->flags & SDHCI_USE_ADMA)?"A":"", - (host->flags & SDHCI_USE_DMA)?"DMA":"PIO"); + (host->flags & SDHCI_USE_ADMA) ? "ADMA" : + (host->flags & SDHCI_USE_SDMA) ? "SDMA" : "PIO"); sdhci_enable_card_detection(host); --- /dev/null +++ b/drivers/mmc/host/sdhci-cns3xxx.c @@ -0,0 +1,313 @@ +/******************************************************************************* + * + * drivers/mmc/host/sdhci-cns3xxx.c + * + * SDHCI support for the CNS3XXX SOCs + * + * Author: Scott Shu + * + * Copyright (c) 2008 Cavium Networks + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, Version 2, as + * published by the Free Software Foundation. + * + * This file is distributed in the hope that it will be useful, + * but AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or + * NONINFRINGEMENT. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA or + * visit http://www.gnu.org/licenses/. + * + * This file may also be available under a different license from Cavium. + * Contact Cavium Networks for more information + * + ******************************************************************************/ + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include "sdhci.h" + +//#define DEBUG + +#define MAX_BUS_CLK (4) + +static unsigned __initdata use_dma = 0; + +struct sdhci_cns3xxx { + struct sdhci_host *host; + struct platform_device *pdev; + struct resource *ioarea; + struct cns3xxx_sdhci_platdata *pdata; + struct clk *clk_io; + struct clk *clk_bus[MAX_BUS_CLK]; +}; + +static unsigned int sdhci_cns3xxx_get_max_clk(struct sdhci_host *host) +{ + int clk = 50000000; + + return clk; +} + +static unsigned int sdhci_cns3xxx_get_timeout_clk(struct sdhci_host *host) +{ + return sdhci_cns3xxx_get_max_clk(host) / 100000; +} + +/* + * sdhci_cns3xxx_set_clock - callback on clock change + * + * When the card's clock is going to be changed, look at the new frequency + * and find the best clock source to go with it. + */ +static void sdhci_cns3xxx_set_clock(struct sdhci_host *host, unsigned int clock) +{ + u16 clk; + unsigned long timeout; + + if (clock == host->clock) + return; + + sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL); + + if (clock == 0) + goto out; +#if 1 + clk = 0x03 << SDHCI_DIVIDER_SHIFT; /* base clock divided by 3 */ +#else + /* high speed mode or normal speed mode */ + if (0x4 & sdhci_readw(host, 0x28)) { + clk = 0x03 << SDHCI_DIVIDER_SHIFT; /* base clock divided by 3 */ + } else { + clk = 0x02 << SDHCI_DIVIDER_SHIFT; /* base clock divided by 4 */ + } +#endif + clk |= SDHCI_CLOCK_INT_EN; + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); + + timeout = 10; + while (!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL)) + & SDHCI_CLOCK_INT_STABLE)) { + if (timeout == 0) { + return; + } + timeout--; + mdelay(1); + } + + clk |= SDHCI_CLOCK_CARD_EN; + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); + + host->timeout_clk = sdhci_cns3xxx_get_timeout_clk(host); +out: + host->clock = clock; +} + +static struct sdhci_ops sdhci_cns3xxx_ops = { + .get_max_clock = sdhci_cns3xxx_get_max_clk, + .get_timeout_clock = sdhci_cns3xxx_get_timeout_clk, + .set_clock = sdhci_cns3xxx_set_clock, +}; + +static int __devinit sdhci_cns3xxx_probe(struct platform_device *pdev) +{ + struct cns3xxx_sdhci_platdata *pdata = pdev->dev.platform_data; + struct device *dev = &pdev->dev; + struct sdhci_host *host; + struct sdhci_cns3xxx *sc; + struct resource *res; + int ret, irq; + + if (!pdata) { + dev_err(dev, "no device data specified\n"); + return -ENOENT; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "no irq specified\n"); + return irq; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "no memory specified\n"); + return -ENOENT; + } + + host = sdhci_alloc_host(dev, sizeof(*sc)); + if (IS_ERR(host)) { + dev_err(dev, "sdhci_alloc_host() failed\n"); + return PTR_ERR(host); + } + + sc = sdhci_priv(host); + + sc->host = host; + sc->pdev = pdev; + sc->pdata = pdata; + + platform_set_drvdata(pdev, host); + + sc->ioarea = request_mem_region(res->start, resource_size(res), mmc_hostname(host->mmc)); + if (!sc->ioarea) { + dev_err(dev, "failed to reserve register area\n"); + ret = -ENXIO; + goto err_req_regs; + } + + host->ioaddr = ioremap_nocache(res->start, resource_size(res)); + if (!host->ioaddr) { + dev_err(dev, "failed to map registers\n"); + ret = -ENXIO; + goto err_req_regs; + } + + host->hw_name = "cns3xxx"; + host->ops = &sdhci_cns3xxx_ops; + host->quirks = 0; + host->irq = irq; + + if (use_dma != 1) { + host->quirks |= SDHCI_QUIRK_BROKEN_DMA; + host->quirks |= SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK; + } else { + host->quirks |= SDHCI_QUIRK_FORCE_DMA; + host->quirks |= SDHCI_QUIRK_BROKEN_ADMA; + host->quirks |= (SDHCI_QUIRK_32BIT_DMA_ADDR | SDHCI_QUIRK_32BIT_DMA_SIZE); + host->quirks |= SDHCI_QUIRK_NO_BUSY_IRQ; + //host->quirks |= SDHCI_QUIRK_FORCE_BLK_SZ_2048; + //host->quirks |= SDHCI_QUIRK_NO_MULTIBLOCK; + host->quirks |= SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK; + host->quirks |= SDHCI_QUIRK_AUTO_CMD12; + host->quirks |= SDHCI_QUIRK_READ_WAIT_CTRL; + } + + //host->quirks |= SDHCI_QUIRK_INVERTED_WRITE_PROTECT; + + host->quirks |= SDHCI_QUIRK_NONSTANDARD_CLOCK; + + host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; + + ret = sdhci_add_host(host); + if (ret) { + dev_err(dev, "sdhci_add_host() failed (%d)\n", ret); + goto err_add_host; + } + + return 0; + +err_add_host: + free_irq(host->irq, host); + iounmap(host->ioaddr); + release_resource(sc->ioarea); + kfree(sc->ioarea); + +err_req_regs: + sdhci_free_host(host); + + return ret; +} + +static int __devexit sdhci_cns3xxx_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sdhci_host *host = dev_get_drvdata(dev); + struct resource *res; + + pr_debug("%s: remove=%p\n", __func__, pdev); + + sdhci_remove_host(host, 0); + sdhci_free_host(host); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, resource_size(res)); + + return 0; +} + +#ifdef CONFIG_PM + +static int sdhci_cns3xxx_suspend(struct platform_device *dev, pm_message_t state) +{ + + return 0; +} + +static int sdhci_cns3xxx_resume(struct platform_device *dev) +{ + + return 0; +} + +#else +#define sdhci_cns3xxx_suspend NULL +#define sdhci_cns3xxx_resume NULL +#endif /* CONFIG_PM */ + +static struct platform_driver sdhci_cns3xxx_driver = { + .probe = sdhci_cns3xxx_probe, + .remove = __devexit_p(sdhci_cns3xxx_remove), + .suspend = sdhci_cns3xxx_suspend, + .resume = sdhci_cns3xxx_resume, + .driver = { + .name = "cns3xxx-sdhci", + .owner = THIS_MODULE, + }, +}; + +static char banner[] __initdata = KERN_INFO "cns3xxx-sdhci, (c) 2009 Cavium Networks\n"; + +static int __init sdhci_cns3xxx_init(void) +{ +#ifdef CONFIG_SILICON + unsigned long gpioapin = __raw_readl((void __iomem *)(CNS3XXX_MISC_BASE_VIRT + 0x0014));; +#else + unsigned long status = __raw_readl((void __iomem *)(CNS3XXX_MISC_BASE_VIRT + 0x0700)); +#endif + + printk(banner); + +#ifdef CONFIG_SILICON + /* MMC/SD pins share with GPIOA */ + __raw_writel(gpioapin | (0x1fff0004), (void __iomem *)(CNS3XXX_MISC_BASE_VIRT + 0x0014)); + cns3xxx_pwr_clk_en(CNS3XXX_PWR_CLK_EN(SDIO)); + cns3xxx_pwr_soft_rst(CNS3XXX_PWR_SOFTWARE_RST(SDIO)); +#else + /* insert a delay on SDIO output interface (only for FPGA mode & high-speed mode) */ + __raw_writel(status | (1 << 4), (void __iomem *)(CNS3XXX_MISC_BASE_VIRT + 0x0700)); +#endif + return platform_driver_register(&sdhci_cns3xxx_driver); +} + +static void __exit sdhci_cns3xxx_exit(void) +{ + platform_driver_unregister(&sdhci_cns3xxx_driver); +} + +module_init(sdhci_cns3xxx_init); +module_exit(sdhci_cns3xxx_exit); + +module_param(use_dma, uint, 0); + +MODULE_AUTHOR("Scott Shu"); +MODULE_DESCRIPTION("Cavium Networks CNS3XXX SDHCI glue"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:cns3xxx-sdhci"); + +MODULE_PARM_DESC(use_dma, "Whether to use DMA or not. Default = 0"); --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -8,6 +8,8 @@ * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. */ +#ifndef __SDHCI_H +#define __SDHCI_H #include #include @@ -78,6 +80,7 @@ #define SDHCI_POWER_330 0x0E #define SDHCI_BLOCK_GAP_CONTROL 0x2A +#define SDHCI_READ_WAIT_CTRL 0x04 #define SDHCI_WAKE_UP_CONTROL 0x2B @@ -143,7 +146,7 @@ #define SDHCI_CAN_DO_ADMA2 0x00080000 #define SDHCI_CAN_DO_ADMA1 0x00100000 #define SDHCI_CAN_DO_HISPD 0x00200000 -#define SDHCI_CAN_DO_DMA 0x00400000 +#define SDHCI_CAN_DO_SDMA 0x00400000 #define SDHCI_CAN_VDD_330 0x01000000 #define SDHCI_CAN_VDD_300 0x02000000 #define SDHCI_CAN_VDD_180 0x04000000 @@ -232,6 +235,12 @@ struct sdhci_host { #define SDHCI_QUIRK_FORCE_1_BIT_DATA (1<<22) /* Controller needs 10ms delay between applying power and clock */ #define SDHCI_QUIRK_DELAY_AFTER_POWER (1<<23) +/* Controller uses SDCLK instead of TMCLK for data timeouts */ +#define SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK (1<<24) +/* Controller uses Auto CMD12 */ +#define SDHCI_QUIRK_AUTO_CMD12 (1<<25) +/* Controller uses read wait control protocol */ +#define SDHCI_QUIRK_READ_WAIT_CTRL (1<<26) int irq; /* Device IRQ */ void __iomem * ioaddr; /* Mapped address */ @@ -250,7 +259,7 @@ struct sdhci_host { spinlock_t lock; /* Mutex */ int flags; /* Host attributes */ -#define SDHCI_USE_DMA (1<<0) /* Host is DMA capable */ +#define SDHCI_USE_SDMA (1<<0) /* Host is SDMA capable */ #define SDHCI_USE_ADMA (1<<1) /* Host is ADMA capable */ #define SDHCI_REQ_USE_DMA (1<<2) /* Use DMA for this req. */ #define SDHCI_DEVICE_DEAD (1<<3) /* Device unresponsive */ @@ -406,3 +415,5 @@ extern void sdhci_remove_host(struct sdh extern int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state); extern int sdhci_resume_host(struct sdhci_host *host); #endif + +#endif /* __SDHCI_H */ --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -40,6 +40,8 @@ struct mmc_csd { }; struct mmc_ext_csd { + u8 rev; + unsigned int sa_timeout; /* Units: 100ns */ unsigned int hs_max_dtr; unsigned int sectors; }; @@ -62,7 +64,8 @@ struct sdio_cccr { low_speed:1, wide_bus:1, high_power:1, - high_speed:1; + high_speed:1, + disable_cd:1; }; struct sdio_cis { @@ -94,6 +97,8 @@ struct mmc_card { #define MMC_STATE_READONLY (1<<1) /* card is read-only */ #define MMC_STATE_HIGHSPEED (1<<2) /* card is in high speed mode */ #define MMC_STATE_BLOCKADDR (1<<3) /* card uses block-addressing */ + unsigned int quirks; /* card quirks */ +#define MMC_QUIRK_LENIENT_FN0 (1<<0) /* allow SDIO FN0 writes outside of the VS CCCR range */ u32 raw_cid[4]; /* raw card CID */ u32 raw_csd[4]; /* raw card CSD */ @@ -129,6 +134,11 @@ struct mmc_card { #define mmc_card_set_highspeed(c) ((c)->state |= MMC_STATE_HIGHSPEED) #define mmc_card_set_blockaddr(c) ((c)->state |= MMC_STATE_BLOCKADDR) +static inline int mmc_card_lenient_fn0(const struct mmc_card *c) +{ + return c->quirks & MMC_QUIRK_LENIENT_FN0; +} + #define mmc_card_name(c) ((c)->cid.prod_name) #define mmc_card_id(c) (dev_name(&(c)->dev)) --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -139,6 +139,7 @@ extern unsigned int mmc_align_data_size( extern int __mmc_claim_host(struct mmc_host *host, atomic_t *abort); extern void mmc_release_host(struct mmc_host *host); +extern int mmc_try_claim_host(struct mmc_host *host); /** * mmc_claim_host - exclusively claim a host --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -11,6 +11,7 @@ #define LINUX_MMC_HOST_H #include +#include #include @@ -51,6 +52,35 @@ struct mmc_ios { }; struct mmc_host_ops { + /* + * Hosts that support power saving can use the 'enable' and 'disable' + * methods to exit and enter power saving states. 'enable' is called + * when the host is claimed and 'disable' is called (or scheduled with + * a delay) when the host is released. The 'disable' is scheduled if + * the disable delay set by 'mmc_set_disable_delay()' is non-zero, + * otherwise 'disable' is called immediately. 'disable' may be + * scheduled repeatedly, to permit ever greater power saving at the + * expense of ever greater latency to re-enable. Rescheduling is + * determined by the return value of the 'disable' method. A positive + * value gives the delay in milliseconds. + * + * In the case where a host function (like set_ios) may be called + * with or without the host claimed, enabling and disabling can be + * done directly and will nest correctly. Call 'mmc_host_enable()' and + * 'mmc_host_lazy_disable()' for this purpose, but note that these + * functions must be paired. + * + * Alternatively, 'mmc_host_enable()' may be paired with + * 'mmc_host_disable()' which calls 'disable' immediately. In this + * case the 'disable' method will be called with 'lazy' set to 0. + * This is mainly useful for error paths. + * + * Because lazy disable may be called from a work queue, the 'disable' + * method must claim the host when 'lazy' != 0, which will work + * correctly because recursion is detected and handled. + */ + int (*enable)(struct mmc_host *host); + int (*disable)(struct mmc_host *host, int lazy); void (*request)(struct mmc_host *host, struct mmc_request *req); /* * Avoid calling these three functions too often or in a "fast path", @@ -118,6 +148,9 @@ struct mmc_host { #define MMC_CAP_SPI (1 << 4) /* Talks only SPI protocols */ #define MMC_CAP_NEEDS_POLL (1 << 5) /* Needs polling for card-detection */ #define MMC_CAP_8_BIT_DATA (1 << 6) /* Can the host do 8 bit transfers */ +#define MMC_CAP_DISABLE (1 << 7) /* Can the host be disabled */ +#define MMC_CAP_NONREMOVABLE (1 << 8) /* Nonremovable e.g. eMMC */ +#define MMC_CAP_WAIT_WHILE_BUSY (1 << 9) /* Waits while card is busy */ /* host specific block data */ unsigned int max_seg_size; /* see blk_queue_max_segment_size */ @@ -142,9 +175,18 @@ struct mmc_host { unsigned int removed:1; /* host is being removed */ #endif + /* Only used with MMC_CAP_DISABLE */ + int enabled; /* host is enabled */ + int nesting_cnt; /* "enable" nesting count */ + int en_dis_recurs; /* detect recursion */ + unsigned int disable_delay; /* disable delay in msecs */ + struct delayed_work disable; /* disabling work */ + struct mmc_card *card; /* device attached to this host */ wait_queue_head_t wq; + struct task_struct *claimer; /* task that has host claimed */ + int claim_cnt; /* "claim" nesting count */ struct delayed_work detect; @@ -183,6 +225,9 @@ static inline void *mmc_priv(struct mmc_ extern int mmc_suspend_host(struct mmc_host *, pm_message_t); extern int mmc_resume_host(struct mmc_host *); +extern void mmc_power_save_host(struct mmc_host *host); +extern void mmc_power_restore_host(struct mmc_host *host); + extern void mmc_detect_change(struct mmc_host *, unsigned long delay); extern void mmc_request_done(struct mmc_host *, struct mmc_request *); @@ -197,5 +242,19 @@ struct regulator; int mmc_regulator_get_ocrmask(struct regulator *supply); int mmc_regulator_set_ocr(struct regulator *supply, unsigned short vdd_bit); +int mmc_card_awake(struct mmc_host *host); +int mmc_card_sleep(struct mmc_host *host); +int mmc_card_can_sleep(struct mmc_host *host); + +int mmc_host_enable(struct mmc_host *host); +int mmc_host_disable(struct mmc_host *host); +int mmc_host_lazy_disable(struct mmc_host *host); + +static inline void mmc_set_disable_delay(struct mmc_host *host, + unsigned int disable_delay) +{ + host->disable_delay = disable_delay; +} + #endif --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -31,6 +31,7 @@ #define MMC_ALL_SEND_CID 2 /* bcr R2 */ #define MMC_SET_RELATIVE_ADDR 3 /* ac [31:16] RCA R1 */ #define MMC_SET_DSR 4 /* bc [31:16] RCA */ +#define MMC_SLEEP_AWAKE 5 /* ac [31:16] RCA 15:flg R1b */ #define MMC_SWITCH 6 /* ac [31:0] See below R1b */ #define MMC_SELECT_CARD 7 /* ac [31:16] RCA R1 */ #define MMC_SEND_EXT_CSD 8 /* adtc R1 */ @@ -127,6 +128,7 @@ #define R1_STATUS(x) (x & 0xFFFFE000) #define R1_CURRENT_STATE(x) ((x & 0x00001E00) >> 9) /* sx, b (4 bits) */ #define R1_READY_FOR_DATA (1 << 8) /* sx, a */ +#define R1_SWITCH_ERROR (1 << 7) /* sx, c */ #define R1_APP_CMD (1 << 5) /* sr, c */ /* @@ -254,6 +256,7 @@ struct _mmc_csd { #define EXT_CSD_CARD_TYPE 196 /* RO */ #define EXT_CSD_REV 192 /* RO */ #define EXT_CSD_SEC_CNT 212 /* RO, 4 bytes */ +#define EXT_CSD_S_A_TIMEOUT 217 /* * EXT_CSD field definitions --- a/include/linux/mmc/sdio_func.h +++ b/include/linux/mmc/sdio_func.h @@ -67,6 +67,7 @@ struct sdio_func { #define sdio_get_drvdata(f) dev_get_drvdata(&(f)->dev) #define sdio_set_drvdata(f,d) dev_set_drvdata(&(f)->dev, d) +#define dev_to_sdio_func(d) container_of(d, struct sdio_func, dev) /* * SDIO function device driver @@ -81,6 +82,8 @@ struct sdio_driver { struct device_driver drv; }; +#define to_sdio_driver(d) container_of(d, struct sdio_driver, drv) + /** * SDIO_DEVICE - macro used to describe a specific SDIO device * @vend: the 16 bit manufacturer code --- a/include/linux/mmc/sdio_ids.h +++ b/include/linux/mmc/sdio_ids.h @@ -22,6 +22,12 @@ /* * Vendors and devices. Sort key: vendor first, device next. */ +#define SDIO_VENDOR_ID_INTEL 0x0089 +#define SDIO_DEVICE_ID_INTEL_IWMC3200WIMAX 0x1402 +#define SDIO_DEVICE_ID_INTEL_IWMC3200WIFI 0x1403 +#define SDIO_DEVICE_ID_INTEL_IWMC3200TOP 0x1404 +#define SDIO_DEVICE_ID_INTEL_IWMC3200GPS 0x1405 +#define SDIO_DEVICE_ID_INTEL_IWMC3200BT 0x1406 #define SDIO_VENDOR_ID_MARVELL 0x02df #define SDIO_DEVICE_ID_MARVELL_LIBERTAS 0x9103