From bac1959b76f55a7845b0bb20eafd32e40c49dd74 Mon Sep 17 00:00:00 2001 From: JiebingLi Date: Wed, 25 Nov 2009 19:43:03 +0800 Subject: [PATCH 077/104] MMC driver Beta 1.0 It is a consolidated patch against upstream Linux 2.6.31.6 Beta features: 1. removed CEATA function from MMC bus driver 2. Added 25MHz support at High-Speed mode for SDIO Comms devices 3. Added eMMC support for Moorestown platform 4. Fixed sighting 3469229 and 3452438 Kernel configuration: CONFIG_MMC=y CONFIG_MRST_LNW_A2_WR=y CONFIG_SDIO_SUSPEND=y CONFIG_MMC_BLOCK=y CONFIG_MMC_BLOCK_BOUNCE=y CONFIG_MMC_SDHCI=y CONFIG_MMC_SDHCI_PCI=y Note: Please DO NOT enable CONFIG_MMC_SDHCI_MRST_EMMC unless you are sure that you want to enable eMMC device for Moorestown. Signed-off-by: JiebingLi --- drivers/mmc/Kconfig | 14 ++ drivers/mmc/card/block.c | 3 +- drivers/mmc/core/Kconfig | 11 + drivers/mmc/core/core.c | 6 + drivers/mmc/core/mmc.c | 41 ++++- drivers/mmc/core/sd.c | 8 + drivers/mmc/core/sdio.c | 464 +++++++++++++++++++++++++++++++++++++++++ drivers/mmc/core/sdio_bus.c | 38 ++++ drivers/mmc/core/sdio_bus.h | 4 + drivers/mmc/host/Kconfig | 10 + drivers/mmc/host/sdhci-pci.c | 81 ++++++-- drivers/mmc/host/sdhci.c | 125 ++++++++++- drivers/mmc/host/sdhci.h | 3 + include/linux/mmc/card.h | 12 + include/linux/mmc/host.h | 2 + include/linux/mmc/sdio_func.h | 14 ++ include/linux/pci_ids.h | 2 + 17 files changed, 810 insertions(+), 28 deletions(-) diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig index f2eeb38..2c19682 100644 --- a/drivers/mmc/Kconfig +++ b/drivers/mmc/Kconfig @@ -19,6 +19,20 @@ config MMC_DEBUG This is an option for use by developers; most people should say N here. This enables MMC core and driver debugging. +config MRST_LNW_A1_WR + bool "software workaround for Moorestown LNW A-1" + depends on MMC + help + This is an option for Moorestown developers to add workaround + in the code due to LNW A-1 Silicon restrictions. + +config MRST_LNW_A2_WR + bool "software workaround for Moorestown LNW A-2" + depends on MMC + help + This is an option for Moorestown developers to add workaround + in the code due to LNW A-2 Silicon restrictions. + if MMC source "drivers/mmc/core/Kconfig" diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 1f552c6..f4ebc68 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -534,7 +534,8 @@ static struct mmc_blk_data *mmc_blk_alloc(struct mmc_card *card) * messages to tell when the card is present. */ - sprintf(md->disk->disk_name, "mmcblk%d", devidx); + snprintf(md->disk->disk_name, sizeof(md->disk->disk_name), + "mmcblk%d", devidx); blk_queue_logical_block_size(md->queue.queue, 512); diff --git a/drivers/mmc/core/Kconfig b/drivers/mmc/core/Kconfig index bb22ffd..205186c 100644 --- a/drivers/mmc/core/Kconfig +++ b/drivers/mmc/core/Kconfig @@ -16,3 +16,14 @@ config MMC_UNSAFE_RESUME This option sets a default which can be overridden by the module parameter "removable=0" or "removable=1". + +config SDIO_SUSPEND + bool "SDIO selective suspend/resume" + depends on MMC && PM + help + If you say Y here, you can use driver calls or the sysfs + "power/level" file to suspend or resume the SDIO + peripherals. + + If you are unsure about this, say N here. + diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 30acd52..951561d 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -213,9 +213,15 @@ void mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq) mrq->done_data = &complete; mrq->done = mmc_wait_done; + if (host->port_mutex) + mutex_lock(host->port_mutex); + mmc_start_request(host, mrq); wait_for_completion(&complete); + + if (host->port_mutex) + mutex_unlock(host->port_mutex); } EXPORT_SYMBOL(mmc_wait_for_req); diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 0eac6c8..15aaa66 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -179,8 +179,10 @@ static int mmc_read_ext_csd(struct mmc_card *card) err = mmc_send_ext_csd(card, ext_csd); if (err) { - /* If the host or the card can't do the switch, - * fail more gracefully. */ + /* + * If the host or the card can't do the switch, + * fail more gracefully. + */ if ((err != -EINVAL) && (err != -ENOSYS) && (err != -EFAULT)) @@ -294,6 +296,28 @@ static struct device_type mmc_type = { }; /* + * Distinguish the fake MMCA4 MMC card. + * + * Transcend 2GB MMC card is a kind of MMCA3.31 MMC card. + * However, it makes up itself as a MMCA4 one via SPEC_VERS + * field of its CSD register. Once it's treated as MMCA4 by + * driver, 4 bit bus is activated which leads to data error. + */ +static bool fake_mmca4_card(struct mmc_card *card) +{ + if (card->cid.manfid == 0x1e && + card->cid.oemid == 0xffff && + card->cid.prod_name[0] == 'M' && + card->cid.prod_name[1] == 'M' && + card->cid.prod_name[2] == 'C' && + card->cid.month == 9 && + card->cid.year == 2008) + return true; + else + return false; +} + +/* * Handle the detection and initialisation of a card. * * In the case of a resume, "oldcard" will contain the card @@ -389,6 +413,12 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, err = mmc_decode_cid(card); if (err) goto free_card; + + /* + * Get card's true specification version + */ + if (fake_mmca4_card(card)) + card->csd.mmca_vsn = CSD_SPEC_VER_3; } /* @@ -409,6 +439,11 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, goto free_card; } +/* + * avoid MMC cards to switch to HS timing + * which doesn't work yet due to LNW A-1 Silicon bug + */ +#if !defined CONFIG_MRST_LNW_A1_WR && !defined CONFIG_MRST_LNW_A2_WR /* * Activate high speed (if supported) */ @@ -428,7 +463,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, mmc_set_timing(card->host, MMC_TIMING_MMC_HS); } } - +#endif /* * Compute bus speed. */ diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index fdd414e..5aae661 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -234,6 +234,7 @@ out: return err; } +#if !defined CONFIG_MRST_LNW_A1_WR && !defined CONFIG_MRST_LNW_A2_WR /* * Test if the card supports high-speed mode and, if so, switch to it. */ @@ -281,6 +282,7 @@ out: return err; } +#endif 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]); @@ -460,12 +462,18 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, goto free_card; } +/* + * avoid SD cards to switch to HS timing + * which doesn't work yet due to LNW A-1 Silicon bug + */ +#if !defined CONFIG_MRST_LNW_A1_WR && !defined CONFIG_MRST_LNW_A2_WR /* * Attempt to change to high-speed (if supported) */ err = mmc_switch_hs(card); if (err) goto free_card; +#endif /* * Compute bus speed. diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 06b6408..42977e1 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -24,6 +24,262 @@ #include "sdio_ops.h" #include "sdio_cis.h" +#define to_sdio_driver(d) container_of(d, struct sdio_driver, drv) + +#ifdef CONFIG_SDIO_SUSPEND + +static int sdio_suspend_func(struct mmc_card *card, + struct sdio_func *func, pm_message_t msg) +{ + struct device *dev; + int error = 0; + + dev = &func->dev; + BUG_ON(!dev); + + if (dev->bus) + if (dev->bus->suspend) + error = dev->bus->suspend(dev, msg); + + return error; +} + +static int sdio_resume_func(struct mmc_card *card, struct sdio_func *func) +{ + struct device *dev; + int error = 0; + + dev = &func->dev; + BUG_ON(!dev); + + if (dev->bus) + if (dev->bus->resume) + error = dev->bus->resume(dev); + + return error; +} + +int sdio_suspend_host(struct mmc_card *card, pm_message_t msg) +{ + int ret = 0; + int i = 0; + struct device *dev; + struct sdio_driver *drv; + + mutex_lock(&card->pm_mutex); + + if (!mmc_card_present(card) || + mmc_card_suspended(card)) + goto done; + + for (i = 0; i < card->sdio_funcs; i++) + if (!sdio_func_suspended(card->sdio_func[i])) { + dev = &(card->sdio_func[i])->dev; + BUG_ON(!dev); + + drv = to_sdio_driver(dev->driver); + + if (dev->driver && drv->suspend) + goto done; + } + + ret = mmc_suspend_host(card->host, msg); + + if (ret == 0) + mmc_card_set_suspended(card); + +done: + mutex_unlock(&card->pm_mutex); + + return ret; +} + +int sdio_resume_host(struct mmc_card *card) +{ + int ret = 0; + + mutex_lock(&card->pm_mutex); + + if (!mmc_card_present(card)) { + ret = -ENODEV; + goto done; + } + + if (mmc_card_suspended(card)) { + ret = mmc_resume_host(card->host); + + if (ret == 0) + mmc_card_clear_suspended(card); + else + goto done; + } + +done: + mutex_unlock(&card->pm_mutex); + + return ret; +} + +/* + * This routine handles external suspend request coming from sysfs + */ +int sdio_external_suspend_device(struct sdio_func *func, pm_message_t msg) +{ + int ret = 0; + struct mmc_card *card = func->card; + + BUG_ON(!card); + BUG_ON(!card->host); + + mutex_lock(&card->pm_mutex); + + if (!sdio_func_present(func) || + sdio_func_suspended(func)) { + mutex_unlock(&card->pm_mutex); + goto done; + } + + /* suspend the function of the SDIO device */ + ret = sdio_suspend_func(card, func, msg); + + if (ret != 0) { + mutex_unlock(&card->pm_mutex); + goto done; + } + + sdio_func_set_suspended(func); + + mutex_unlock(&card->pm_mutex); + + ret = sdio_suspend_host(card, msg); + +done: + return ret; +} + +/* + * This routine handles external resume request coming from sysfs + */ +int sdio_external_resume_device(struct sdio_func *func) +{ + int ret = 0; + struct mmc_card *card = func->card; + + BUG_ON(!card); + BUG_ON(!card->host); + + ret = sdio_resume_host(card); + if (ret) + goto done; + + mutex_lock(&card->pm_mutex); + + if (sdio_func_suspended(func)) { + ret = sdio_resume_func(card, func); + + if (ret != 0) { + mutex_unlock(&card->pm_mutex); + goto done; + } else + sdio_func_clear_suspended(func); + } + + mutex_unlock(&card->pm_mutex); +done: + + return ret; +} + +static const char power_group[] = "power"; + +static const char resume_string[] = "resume"; +static const char suspend_string[] = "suspend"; + +static ssize_t +show_level(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sdio_func *func = container_of(dev, struct sdio_func, dev); + const char *p = suspend_string; + + BUG_ON(!func); + + if (sdio_func_suspended(func)) + p = suspend_string; + else + p = resume_string; + + return sprintf(buf, "%s\n", p); +} + +static ssize_t +set_level(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sdio_func *func = container_of(dev, struct sdio_func, dev); + int len = count; + char *cp; + int ret = 0; + + BUG_ON(!func); + + cp = memchr(buf, '\n', count); + if (cp) + len = cp - buf; + + down(&dev->sem); + + if (len == sizeof resume_string - 1 && + strncmp(buf, resume_string, len) == 0) { + ret = sdio_external_resume_device(func); + } else if (len == sizeof suspend_string - 1 && + strncmp(buf, suspend_string, len) == 0) { + ret = sdio_external_suspend_device(func, PMSG_SUSPEND); + } else { + ret = -EINVAL; + } + + up(&dev->sem); + + return (ret < 0 ? ret : count); +} + +static DEVICE_ATTR(level, S_IRUGO | S_IWUSR, show_level, set_level); + +void sdio_remove_sysfs_file(struct sdio_func *func) +{ + struct device *dev = &func->dev; + struct sdio_driver *drv = to_sdio_driver(dev->driver); + + if (dev->driver && drv->suspend) + sysfs_remove_file_from_group(&dev->kobj, + &dev_attr_level.attr, + power_group); +} + +int sdio_create_sysfs_file(struct sdio_func *func) +{ + int ret; + struct device *dev = &func->dev; + struct sdio_driver *drv = to_sdio_driver(dev->driver); + + if (dev->driver && drv->suspend) { + ret = sysfs_add_file_to_group(&dev->kobj, + &dev_attr_level.attr, + power_group); + + if (ret) + goto error; + } + + return 0; + +error: + sdio_remove_sysfs_file(func); + return ret; +} + +#endif /* CONFIG_SDIO_SUSPEND */ + static int sdio_read_fbr(struct sdio_func *func) { int ret; @@ -187,6 +443,7 @@ static int sdio_disable_cd(struct mmc_card *card) return mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_IF, ctrl, NULL); } +#if !defined(CONFIG_MRST_LNW_A1_WR) && !defined(CONFIG_MRST_LNW_A2_WR) /* * Test if the card supports high-speed mode and, if so, switch to it. */ @@ -216,6 +473,128 @@ static int sdio_enable_hs(struct mmc_card *card) return 0; } +#else +static int sdio_enable_hs(struct mmc_card *card) +{ + return 0; +} +#endif + +/* + * Handle the re-initialization of a SDIO card. + */ +static int mmc_sdio_reinit_card(struct mmc_host *host, + struct mmc_card *oldcard) +{ + int err = 0; + u16 funcs; + u32 ocr; + struct mmc_card *card; + +#if !defined(CONFIG_MRST_LNW_A1_WR) && !defined(CONFIG_MRST_LNW_A2_WR) + unsigned int max_dtr; +#endif + + BUG_ON(!host); + WARN_ON(!host->claimed); + + if (!oldcard) + goto err; + + card = oldcard; + + err = mmc_send_io_op_cond(host, 0, &ocr); + if (err) + goto remove; + + /* + * Inform the card of the voltage + */ + err = mmc_send_io_op_cond(host, host->ocr, &ocr); + if (err) + goto remove; + + /* + * For SPI, enable CRC as appropriate. + */ + if (mmc_host_is_spi(host)) { + err = mmc_spi_set_crc(host, use_spi_crc); + if (err) + goto remove; + } + + funcs = (ocr & 0x70000000) >> 28; + + if (funcs != card->sdio_funcs) + printk(KERN_INFO "funcs number is changed from OCR register after suspend!\n"); + + 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 CIS tuples. + */ + err = sdio_read_cccr(card); + if (err) + goto remove; + +#if defined(CONFIG_MRST_LNW_A1_WR) || defined(CONFIG_MRST_LNW_A2_WR) + /* restricting to 24MHz for Langwell A0 */ + if (card->cis.max_dtr > 24000000) + card->cis.max_dtr = 24000000; + + mmc_set_clock(host, card->cis.max_dtr); +#else + /* + * Switch to high-speed (if supported). + */ + err = sdio_enable_hs(card); + if (err) + goto remove; + + max_dtr = card->cis.max_dtr; + + /* + * Change to the card's maximum speed. + */ + if (mmc_card_highspeed(card)) { + if (max_dtr > 50000000) + max_dtr = 50000000; + } else if (max_dtr > 25000000) + max_dtr = 25000000; + + mmc_set_clock(host, max_dtr); +#endif + + /* + * Switch to wider bus (if supported). + */ + err = sdio_enable_wide(card); + if (err) + goto remove; + + host->card = card; + + return 0; + +remove: +err: + return err; +} /* * Handle the detection and initialisation of a card. @@ -478,6 +857,10 @@ int mmc_attach_sdio(struct mmc_host *host, u32 ocr) int i, funcs; struct mmc_card *card; +#if !defined(CONFIG_MRST_LNW_A1_WR) && !defined(CONFIG_MRST_LNW_A2_WR) + unsigned int max_dtr; +#endif + BUG_ON(!host); WARN_ON(!host->claimed); @@ -574,3 +957,84 @@ err: return err; } +/* + * warn device driver and perform a SDIO device reset. + * Assume that device driver knows hot to handle resets. + */ +int sdio_reset_device(struct mmc_card *card) +{ + int ret = 0; + int i = 0; + u8 reg = 0; + + BUG_ON(!card); + BUG_ON(!card->host); + BUG_ON(!card->sdio_func); + + if (!mmc_card_present(card) || + mmc_card_suspended(card)) { + dev_dbg(&card->dev, "device reset not allowed\n"); + return -EINVAL; + } + + for (; i < card->sdio_funcs; i++) { + struct sdio_func *func = card->sdio_func[i]; + struct sdio_driver *drv; + + if (func && func->dev.driver) { + drv = to_sdio_driver(func->dev.driver); + if (drv->pre_reset) { + ret = (drv->pre_reset)(func); + if (ret) + break; + } + } + } + + if (ret) + goto err; + + /* reset SDIO card via CMD52 */ + mmc_claim_host(card->host); + + ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_ABORT, 0, ®); + + if (ret) + reg = 0x08; + else + reg |= 0x08; + + mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_ABORT, reg, NULL); + + /* re-enumerate the device */ + ret = mmc_sdio_reinit_card(card->host, card); + + mmc_release_host(card->host); + + if (ret) + goto err; + + for (i = card->sdio_funcs - 1; i >= 0; i--) { + struct sdio_func *func = card->sdio_func[i]; + struct sdio_driver *drv; + + if (func && func->dev.driver) { + drv = to_sdio_driver(func->dev.driver); + if (drv->post_reset) { + ret = (drv->post_reset)(func); + if (ret) + break; + } + } + } + + if (ret) + goto err; + + return 0; + +err: + return -EINVAL; + +} +EXPORT_SYMBOL_GPL(sdio_reset_device); diff --git a/drivers/mmc/core/sdio_bus.c b/drivers/mmc/core/sdio_bus.c index 9e060c8..84414e8 100644 --- a/drivers/mmc/core/sdio_bus.c +++ b/drivers/mmc/core/sdio_bus.c @@ -124,6 +124,14 @@ static int sdio_bus_probe(struct device *dev) if (!id) return -ENODEV; + /* + * create the user interface to call suspend/resume + * from susfs + */ +#ifdef CONFIG_SDIO_SUSPEND + sdio_create_sysfs_file(func); +#endif + /* Set the default block size so the driver is sure it's something * sensible. */ sdio_claim_host(func); @@ -140,6 +148,10 @@ static int sdio_bus_remove(struct device *dev) struct sdio_driver *drv = to_sdio_driver(dev->driver); struct sdio_func *func = dev_to_sdio_func(dev); +#ifdef CONFIG_SDIO_SUSPEND + sdio_remove_sysfs_file(func); +#endif + drv->remove(func); if (func->irq_handler) { @@ -153,6 +165,30 @@ static int sdio_bus_remove(struct device *dev) return 0; } +static int sdio_bus_suspend(struct device *dev, pm_message_t state) +{ + struct sdio_driver *drv = to_sdio_driver(dev->driver); + struct sdio_func *func = dev_to_sdio_func(dev); + int ret = 0; + + if (dev->driver && drv->suspend) + ret = drv->suspend(func, state); + + return ret; +} + +static int sdio_bus_resume(struct device *dev) +{ + struct sdio_driver *drv = to_sdio_driver(dev->driver); + struct sdio_func *func = dev_to_sdio_func(dev); + int ret = 0; + + if (dev->driver && drv->resume) + ret = drv->resume(func); + + return ret; +} + static struct bus_type sdio_bus_type = { .name = "sdio", .dev_attrs = sdio_dev_attrs, @@ -160,6 +196,8 @@ static struct bus_type sdio_bus_type = { .uevent = sdio_bus_uevent, .probe = sdio_bus_probe, .remove = sdio_bus_remove, + .suspend = sdio_bus_suspend, + .resume = sdio_bus_resume, }; int sdio_register_bus(void) diff --git a/drivers/mmc/core/sdio_bus.h b/drivers/mmc/core/sdio_bus.h index 567a768..18616b2 100644 --- a/drivers/mmc/core/sdio_bus.h +++ b/drivers/mmc/core/sdio_bus.h @@ -18,5 +18,9 @@ void sdio_remove_func(struct sdio_func *func); int sdio_register_bus(void); void sdio_unregister_bus(void); +#ifdef CONFIG_SDIO_SUSPEND +int sdio_create_sysfs_file(struct sdio_func *func); +void sdio_remove_sysfs_file(struct sdio_func *func); +#endif #endif diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index ce1d288..521cf2e 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -68,6 +68,16 @@ config MMC_SDHCI_PCI If unsure, say N. +config MMC_SDHCI_MRST_EMMC + tristate "Enable eMMC device on MRST" + depends on MMC_SDHCI && PCI + help + This enables eMMC device for MRST platform. + + If you're using eMMC device on Moorestown, say Y or M here. + + If unsure, say N. + config MMC_RICOH_MMC tristate "Ricoh MMC Controller Disabler (EXPERIMENTAL)" depends on MMC_SDHCI_PCI diff --git a/drivers/mmc/host/sdhci-pci.c b/drivers/mmc/host/sdhci-pci.c index 5c3a176..53f3719 100644 --- a/drivers/mmc/host/sdhci-pci.c +++ b/drivers/mmc/host/sdhci-pci.c @@ -38,6 +38,8 @@ #define MAX_SLOTS 8 +static struct mutex port_mutex; + struct sdhci_pci_chip; struct sdhci_pci_slot; @@ -364,6 +366,17 @@ static const struct sdhci_pci_fixes sdhci_via = { .probe = via_probe, }; +/* + * ADMA operation is disabled for Moorestown platform. + */ +static const struct sdhci_pci_fixes sdhci_intel_mrst = { + .quirks = SDHCI_QUIRK_BROKEN_ADMA | +#ifdef CONFIG_MMC_SDHCI_MRST_EMMC + SDHCI_QUIRK_BROKEN_CARD_DETECTION | +#endif + SDHCI_QUIRK_MRST_RESTRICTION, +}; + static const struct pci_device_id pci_ids[] __devinitdata = { { .vendor = PCI_VENDOR_ID_RICOH, @@ -445,6 +458,22 @@ static const struct pci_device_id pci_ids[] __devinitdata = { .driver_data = (kernel_ulong_t)&sdhci_via, }, + { + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_MRST_SD0, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = (kernel_ulong_t)&sdhci_intel_mrst, + }, + + { + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_MRST_SD1, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = (kernel_ulong_t)&sdhci_intel_mrst, + }, + { /* Generic SD host controller */ PCI_DEVICE_CLASS((PCI_CLASS_SYSTEM_SDHCI << 8), 0xFFFF00) }, @@ -469,11 +498,14 @@ static int sdhci_pci_enable_dma(struct sdhci_host *host) slot = sdhci_priv(host); pdev = slot->chip->pdev; - if (((pdev->class & 0xFFFF00) == (PCI_CLASS_SYSTEM_SDHCI << 8)) && - ((pdev->class & 0x0000FF) != PCI_SDHCI_IFDMA) && - (host->flags & SDHCI_USE_SDMA)) { - dev_warn(&pdev->dev, "Will use DMA mode even though HW " - "doesn't fully claim to support it.\n"); + if (!(host->quirks & SDHCI_QUIRK_MRST_RESTRICTION)) { + if (((pdev->class & 0xFFFF00) == + (PCI_CLASS_SYSTEM_SDHCI << 8)) && + ((pdev->class & 0x0000FF) != PCI_SDHCI_IFDMA) && + (host->flags & SDHCI_USE_SDMA)) { + dev_warn(&pdev->dev, "Will use DMA mode even though HW " + "doesn't fully claim to support it.\n"); + } } ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); @@ -622,6 +654,9 @@ static struct sdhci_pci_slot * __devinit sdhci_pci_probe_slot( return ERR_PTR(PTR_ERR(host)); } + if (pdev->device == PCI_DEVICE_ID_INTEL_MRST_SD0) + host->mmc->port_mutex = &port_mutex; + slot = sdhci_priv(host); slot->chip = chip; @@ -712,22 +747,42 @@ static int __devinit sdhci_pci_probe(struct pci_dev *pdev, dev_info(&pdev->dev, "SDHCI controller found [%04x:%04x] (rev %x)\n", (int)pdev->vendor, (int)pdev->device, (int)rev); - ret = pci_read_config_byte(pdev, PCI_SLOT_INFO, &slots); - if (ret) - return ret; + /* + * slots number is fixed to 2 by Moorestown architecture + */ + if (pdev->device == PCI_DEVICE_ID_INTEL_MRST_SD0) { + slots = 2; + mutex_init(&port_mutex); + } else if (pdev->device == PCI_DEVICE_ID_INTEL_MRST_SD1) + slots = 1; + else { + ret = pci_read_config_byte(pdev, PCI_SLOT_INFO, &slots); + + if (ret) + return ret; + + slots = PCI_SLOT_INFO_SLOTS(slots) + 1; + } - slots = PCI_SLOT_INFO_SLOTS(slots) + 1; dev_dbg(&pdev->dev, "found %d slot(s)\n", slots); if (slots == 0) return -ENODEV; BUG_ON(slots > MAX_SLOTS); - ret = pci_read_config_byte(pdev, PCI_SLOT_INFO, &first_bar); - if (ret) - return ret; + /* + * first BAR is fixed to 0 by Moorestown architecture + */ + if (pdev->device == PCI_DEVICE_ID_INTEL_MRST_SD0 || + pdev->device == PCI_DEVICE_ID_INTEL_MRST_SD1) { + first_bar = 0; + } else { + ret = pci_read_config_byte(pdev, PCI_SLOT_INFO, &first_bar); + if (ret) + return ret; - first_bar &= PCI_SLOT_INFO_FIRST_BAR_MASK; + first_bar &= PCI_SLOT_INFO_FIRST_BAR_MASK; + } if (first_bar > 5) { dev_err(&pdev->dev, "Invalid first BAR. Aborting.\n"); diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index c279fbc..ff26db0 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -161,9 +161,11 @@ static void sdhci_reset(struct sdhci_host *host, u8 mask) /* hw clears the bit when it's done */ while (sdhci_readb(host, SDHCI_SOFTWARE_RESET) & mask) { if (timeout == 0) { +#ifndef CONFIG_MRST_LNW_A2_WR printk(KERN_ERR "%s: Reset 0x%x never completed.\n", mmc_hostname(host->mmc), (int)mask); sdhci_dumpregs(host); +#endif return; } timeout--; @@ -176,13 +178,25 @@ static void sdhci_reset(struct sdhci_host *host, u8 mask) static void sdhci_init(struct sdhci_host *host) { + u32 intmask; + + intmask = sdhci_readl(host, SDHCI_INT_STATUS); + sdhci_writel(host, + intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE), + SDHCI_INT_STATUS); + +#ifndef CONFIG_MRST_LNW_A2_WR sdhci_reset(host, SDHCI_RESET_ALL); +#endif sdhci_clear_set_irqs(host, SDHCI_INT_ALL_MASK, SDHCI_INT_BUS_POWER | SDHCI_INT_DATA_END_BIT | SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_INDEX | SDHCI_INT_END_BIT | SDHCI_INT_CRC | SDHCI_INT_TIMEOUT | SDHCI_INT_DATA_END | SDHCI_INT_RESPONSE); + + /* disable wakeup signal during initialization */ + sdhci_writeb(host, 0x0, SDHCI_WAKE_UP_CONTROL); } static void sdhci_reinit(struct sdhci_host *host) @@ -465,6 +479,54 @@ static int sdhci_adma_table_pre(struct sdhci_host *host, len -= offset; } +#if defined(CONFIG_MRST_LNW_A1_WR) || defined(CONFIG_MRST_LNW_A2_WR) + if (len != 65536) { + desc[7] = (addr >> 24) & 0xff; + desc[6] = (addr >> 16) & 0xff; + desc[5] = (addr >> 8) & 0xff; + desc[4] = (addr >> 0) & 0xff; + + BUG_ON(len > 65536); + + desc[3] = (len >> 8) & 0xff; + desc[2] = (len >> 0) & 0xff; + + desc[1] = 0x00; + desc[0] = 0x21; /* tran, valid */ + + desc += 8; + } else { + desc[7] = (addr >> 24) & 0xff; + desc[6] = (addr >> 16) & 0xff; + desc[5] = (addr >> 8) & 0xff; + desc[4] = (addr >> 0) & 0xff; + + desc[3] = (32768 >> 8) & 0xff; + desc[2] = (32768 >> 0) & 0xff; + + desc[1] = 0x00; + desc[0] = 0x21; /* tran, valid */ + + desc += 8; + + /* 2nd */ + addr += 32768; + + desc[7] = (addr >> 24) & 0xff; + desc[6] = (addr >> 16) & 0xff; + desc[5] = (addr >> 8) & 0xff; + desc[4] = (addr >> 0) & 0xff; + + desc[3] = (32768 >> 8) & 0xff; + desc[2] = (32768 >> 0) & 0xff; + + desc[1] = 0x00; + desc[0] = 0x21; /* tran, valid */ + + desc += 8; + } +#else + desc[7] = (addr >> 24) & 0xff; desc[6] = (addr >> 16) & 0xff; desc[5] = (addr >> 8) & 0xff; @@ -479,7 +541,7 @@ static int sdhci_adma_table_pre(struct sdhci_host *host, desc[0] = 0x21; /* tran, valid */ desc += 8; - +#endif /* * If this triggers then we have a calculation bug * somewhere. :/ @@ -487,6 +549,11 @@ static int sdhci_adma_table_pre(struct sdhci_host *host, WARN_ON((desc - host->adma_desc) > (128 * 2 + 1) * 4); } + +#if defined(CONFIG_MRST_LNW_A1_WR) || defined(CONFIG_MRST_LNW_A2_WR) + desc -= 8; + desc[0] = 0x23; +#else /* * Add a terminating entry. */ @@ -500,7 +567,7 @@ static int sdhci_adma_table_pre(struct sdhci_host *host, desc[1] = 0x00; desc[0] = 0x03; /* nop, end, valid */ - +#endif /* * Resync align buffer as we might have changed it. */ @@ -613,11 +680,8 @@ static u8 sdhci_calc_timeout(struct sdhci_host *host, struct mmc_data *data) break; } - if (count >= 0xF) { - printk(KERN_WARNING "%s: Too large timeout requested!\n", - mmc_hostname(host->mmc)); + if (count >= 0xF) count = 0xE; - } return count; } @@ -928,6 +992,30 @@ static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) if (cmd->data) flags |= SDHCI_CMD_DATA; + if (host->quirks & SDHCI_QUIRK_MRST_RESTRICTION) { + u16 clk; + + clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); + + clk |= SDHCI_CLOCK_CARD_EN; + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); + + /* Wait max 10 ms */ + timeout = 10; + while (!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL)) + & SDHCI_CLOCK_INT_STABLE)) { + if (timeout == 0) { + printk(KERN_ERR "%s: Internal clock never " + "stabilised.\n", + mmc_hostname(host->mmc)); + sdhci_dumpregs(host); + return; + } + timeout--; + mdelay(1); + } + } + sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND); } @@ -1147,14 +1235,22 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); - if (ios->bus_width == MMC_BUS_WIDTH_4) + if (ios->bus_width == MMC_BUS_WIDTH_8) { + ctrl |= SDHCI_CTRL_8BITBUS; ctrl |= SDHCI_CTRL_4BITBUS; - else + } else if (ios->bus_width == MMC_BUS_WIDTH_4) { + ctrl &= ~SDHCI_CTRL_8BITBUS; + ctrl |= SDHCI_CTRL_4BITBUS; + } else { + ctrl &= ~SDHCI_CTRL_8BITBUS; ctrl &= ~SDHCI_CTRL_4BITBUS; + } - if (ios->timing == MMC_TIMING_SD_HS) +#ifndef CONFIG_MRST_LNW_A2_WR + if (ios->timing == MMC_TIMING_SD_HS || ios->timing == MMC_TIMING_MMC_HS) ctrl |= SDHCI_CTRL_HISPD; else +#endif ctrl &= ~SDHCI_CTRL_HISPD; sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); @@ -1354,6 +1450,10 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask) BUG_ON(intmask == 0); if (!host->cmd) { + if (host->quirks & SDHCI_QUIRK_MRST_RESTRICTION && + !(strncmp(mmc_hostname(host->mmc), "mmc1", 4))) + return; + printk(KERN_ERR "%s: Got command interrupt 0x%08x even " "though no command operation was in progress.\n", mmc_hostname(host->mmc), (unsigned)intmask); @@ -1667,7 +1767,9 @@ int sdhci_add_host(struct sdhci_host *host) if (debug_quirks) host->quirks = debug_quirks; +#ifndef CONFIG_MRST_LNW_A2_WR sdhci_reset(host, SDHCI_RESET_ALL); +#endif host->version = sdhci_readw(host, SDHCI_HOST_VERSION); host->version = (host->version & SDHCI_SPEC_VER_MASK) @@ -1787,7 +1889,7 @@ int sdhci_add_host(struct sdhci_host *host) mmc->caps |= MMC_CAP_4_BIT_DATA; if (caps & SDHCI_CAN_DO_HISPD) - mmc->caps |= MMC_CAP_SD_HIGHSPEED; + mmc->caps |= MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED; if (host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION) mmc->caps |= MMC_CAP_NEEDS_POLL; @@ -1845,7 +1947,8 @@ int sdhci_add_host(struct sdhci_host *host) } else { mmc->max_blk_size = (caps & SDHCI_MAX_BLOCK_MASK) >> SDHCI_MAX_BLOCK_SHIFT; - if (mmc->max_blk_size >= 3) { + if ((mmc->max_blk_size >= 3) && + !(host->quirks & SDHCI_QUIRK_MRST_RESTRICTION)) { printk(KERN_WARNING "%s: Invalid maximum block size, " "assuming 512 bytes\n", mmc_hostname(mmc)); mmc->max_blk_size = 0; diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 842f46f..f7ba4a2 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -67,6 +67,7 @@ #define SDHCI_CTRL_LED 0x01 #define SDHCI_CTRL_4BITBUS 0x02 #define SDHCI_CTRL_HISPD 0x04 +#define SDHCI_CTRL_8BITBUS 0x20 #define SDHCI_CTRL_DMA_MASK 0x18 #define SDHCI_CTRL_SDMA 0x00 #define SDHCI_CTRL_ADMA1 0x08 @@ -236,6 +237,8 @@ struct sdhci_host { #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 of Moorestown specific restriction */ +#define SDHCI_QUIRK_MRST_RESTRICTION (1<<25) int irq; /* Device IRQ */ void __iomem * ioaddr; /* Mapped address */ diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 2ee22e8..731f984 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -97,6 +97,7 @@ 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 */ +#define MMC_STATE_SUSPENDED (1<<4) /* 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 */ @@ -109,6 +110,7 @@ struct mmc_card { struct sd_scr scr; /* extra SD information */ struct sd_switch_caps sw_caps; /* switch (CMD6) caps */ + /* sdio related info */ unsigned int sdio_funcs; /* number of SDIO functions */ struct sdio_cccr cccr; /* common card info */ struct sdio_cis cis; /* common tuple info */ @@ -118,6 +120,10 @@ struct mmc_card { struct sdio_func_tuple *tuples; /* unknown common tuples */ struct dentry *debugfs_root; + +#ifdef CONFIG_SDIO_SUSPEND + struct mutex pm_mutex; +#endif }; #define mmc_card_mmc(c) ((c)->type == MMC_TYPE_MMC) @@ -128,6 +134,7 @@ struct mmc_card { #define mmc_card_readonly(c) ((c)->state & MMC_STATE_READONLY) #define mmc_card_highspeed(c) ((c)->state & MMC_STATE_HIGHSPEED) #define mmc_card_blockaddr(c) ((c)->state & MMC_STATE_BLOCKADDR) +#define mmc_card_suspended(c) ((c)->state & MMC_STATE_SUSPENDED) #define mmc_card_set_present(c) ((c)->state |= MMC_STATE_PRESENT) #define mmc_card_set_readonly(c) ((c)->state |= MMC_STATE_READONLY) @@ -139,6 +146,11 @@ static inline int mmc_card_lenient_fn0(const struct mmc_card *c) return c->quirks & MMC_QUIRK_LENIENT_FN0; } +#ifdef CONFIG_SDIO_SUSPEND +#define mmc_card_set_suspended(c) ((c)->state |= MMC_STATE_SUSPENDED) +#define mmc_card_clear_suspended(c) ((c)->state &= ~MMC_STATE_SUSPENDED) +#endif + #define mmc_card_name(c) ((c)->cid.prod_name) #define mmc_card_id(c) (dev_name(&(c)->dev)) diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index eaf3636..814450a 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -203,6 +203,8 @@ struct mmc_host { struct dentry *debugfs_root; + struct mutex *port_mutex; + unsigned long private[0] ____cacheline_aligned; }; diff --git a/include/linux/mmc/sdio_func.h b/include/linux/mmc/sdio_func.h index ac3ab68..a863a9a 100644 --- a/include/linux/mmc/sdio_func.h +++ b/include/linux/mmc/sdio_func.h @@ -50,6 +50,7 @@ struct sdio_func { unsigned int state; /* function state */ #define SDIO_STATE_PRESENT (1<<0) /* present in sysfs */ +#define SDIO_STATE_SUSPENDED (1<<1) /* present in sysfs */ u8 tmpbuf[4]; /* DMA:able scratch buffer */ @@ -60,9 +61,13 @@ struct sdio_func { }; #define sdio_func_present(f) ((f)->state & SDIO_STATE_PRESENT) +#define sdio_func_suspended(f) ((f)->state & SDIO_STATE_SUSPENDED) #define sdio_func_set_present(f) ((f)->state |= SDIO_STATE_PRESENT) +#define sdio_func_set_suspended(f) ((f)->state |= SDIO_STATE_SUSPENDED) +#define sdio_func_clear_suspended(f) ((f)->state &= ~SDIO_STATE_SUSPENDED) + #define sdio_func_id(f) (dev_name(&(f)->dev)) #define sdio_get_drvdata(f) dev_get_drvdata(&(f)->dev) @@ -78,6 +83,11 @@ struct sdio_driver { int (*probe)(struct sdio_func *, const struct sdio_device_id *); void (*remove)(struct sdio_func *); + int (*suspend)(struct sdio_func *, pm_message_t); + int (*resume)(struct sdio_func *); + + int (*pre_reset)(struct sdio_func *); + int (*post_reset)(struct sdio_func *); struct device_driver drv; }; @@ -153,5 +163,9 @@ extern unsigned char sdio_f0_readb(struct sdio_func *func, extern void sdio_f0_writeb(struct sdio_func *func, unsigned char b, unsigned int addr, int *err_ret); +extern int sdio_reset_device(struct mmc_card *card); + +extern int sdio_suspend_host(struct mmc_card *card, pm_message_t msg); +extern int sdio_resume_host(struct mmc_card *card); #endif diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index cca8a04..2ec7f6c 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -2396,6 +2396,8 @@ #define PCI_DEVICE_ID_INTEL_82375 0x0482 #define PCI_DEVICE_ID_INTEL_82424 0x0483 #define PCI_DEVICE_ID_INTEL_82378 0x0484 +#define PCI_DEVICE_ID_INTEL_MRST_SD0 0x0807 +#define PCI_DEVICE_ID_INTEL_MRST_SD1 0x0808 #define PCI_DEVICE_ID_INTEL_I960 0x0960 #define PCI_DEVICE_ID_INTEL_I960RM 0x0962 #define PCI_DEVICE_ID_INTEL_8257X_SOL 0x1062 -- 1.6.2.5