diff options
Diffstat (limited to 'drivers/mmc/core')
-rw-r--r-- | drivers/mmc/core/Makefile | 4 | ||||
-rw-r--r-- | drivers/mmc/core/bus.c | 67 | ||||
-rw-r--r-- | drivers/mmc/core/core.c | 167 | ||||
-rw-r--r-- | drivers/mmc/core/core.h | 2 | ||||
-rw-r--r-- | drivers/mmc/core/host.c | 8 | ||||
-rw-r--r-- | drivers/mmc/core/mmc.c | 134 | ||||
-rw-r--r-- | drivers/mmc/core/mmc_ops.c | 200 | ||||
-rw-r--r-- | drivers/mmc/core/mmc_ops.h | 3 | ||||
-rw-r--r-- | drivers/mmc/core/sd.c | 126 | ||||
-rw-r--r-- | drivers/mmc/core/sd_ops.c | 107 | ||||
-rw-r--r-- | drivers/mmc/core/sdio.c | 395 | ||||
-rw-r--r-- | drivers/mmc/core/sdio_bus.c | 270 | ||||
-rw-r--r-- | drivers/mmc/core/sdio_bus.h | 22 | ||||
-rw-r--r-- | drivers/mmc/core/sdio_cis.c | 346 | ||||
-rw-r--r-- | drivers/mmc/core/sdio_cis.h | 23 | ||||
-rw-r--r-- | drivers/mmc/core/sdio_io.c | 548 | ||||
-rw-r--r-- | drivers/mmc/core/sdio_irq.c | 267 | ||||
-rw-r--r-- | drivers/mmc/core/sdio_ops.c | 176 | ||||
-rw-r--r-- | drivers/mmc/core/sdio_ops.h | 22 |
19 files changed, 2653 insertions, 234 deletions
diff --git a/drivers/mmc/core/Makefile b/drivers/mmc/core/Makefile index 3fdd08c7f14..4985807257a 100644 --- a/drivers/mmc/core/Makefile +++ b/drivers/mmc/core/Makefile @@ -8,5 +8,7 @@ endif obj-$(CONFIG_MMC) += mmc_core.o mmc_core-y := core.o sysfs.o bus.o host.o \ - mmc.o mmc_ops.o sd.o sd_ops.o + mmc.o mmc_ops.o sd.o sd_ops.o \ + sdio.o sdio_ops.o sdio_bus.o \ + sdio_cis.o sdio_io.o sdio_irq.o diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index 817a79462b3..8d6f6014870 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -19,6 +19,7 @@ #include "sysfs.h" #include "core.h" +#include "sdio_cis.h" #include "bus.h" #define dev_to_mmc_card(d) container_of(d, struct mmc_card, dev) @@ -34,6 +35,8 @@ static ssize_t mmc_type_show(struct device *dev, return sprintf(buf, "MMC\n"); case MMC_TYPE_SD: return sprintf(buf, "SD\n"); + case MMC_TYPE_SDIO: + return sprintf(buf, "SDIO\n"); default: return -EFAULT; } @@ -59,28 +62,34 @@ mmc_bus_uevent(struct device *dev, char **envp, int num_envp, char *buf, int buf_size) { struct mmc_card *card = dev_to_mmc_card(dev); - int retval = 0, i = 0, length = 0; - -#define add_env(fmt,val) do { \ - retval = add_uevent_var(envp, num_envp, &i, \ - buf, buf_size, &length, \ - fmt, val); \ - if (retval) \ - return retval; \ -} while (0); + const char *type; + int i = 0, length = 0; switch (card->type) { case MMC_TYPE_MMC: - add_env("MMC_TYPE=%s", "MMC"); + type = "MMC"; break; case MMC_TYPE_SD: - add_env("MMC_TYPE=%s", "SD"); + type = "SD"; + break; + case MMC_TYPE_SDIO: + type = "SDIO"; break; + default: + type = NULL; } - add_env("MMC_NAME=%s", mmc_card_name(card)); + if (type) { + if (add_uevent_var(envp, num_envp, &i, + buf, buf_size, &length, + "MMC_TYPE=%s", type)) + return -ENOMEM; + } -#undef add_env + if (add_uevent_var(envp, num_envp, &i, + buf, buf_size, &length, + "MMC_NAME=%s", mmc_card_name(card))) + return -ENOMEM; envp[i] = NULL; @@ -176,6 +185,11 @@ static void mmc_release_card(struct device *dev) { struct mmc_card *card = dev_to_mmc_card(dev); + sdio_free_common_cis(card); + + if (card->info) + kfree(card->info); + kfree(card); } @@ -221,15 +235,25 @@ int mmc_add_card(struct mmc_card *card) if (mmc_card_blockaddr(card)) type = "SDHC"; break; + case MMC_TYPE_SDIO: + type = "SDIO"; + break; default: type = "?"; break; } - printk(KERN_INFO "%s: new %s%s card at address %04x\n", - mmc_hostname(card->host), - mmc_card_highspeed(card) ? "high speed " : "", - type, card->rca); + if (mmc_host_is_spi(card->host)) { + printk(KERN_INFO "%s: new %s%s card on SPI\n", + mmc_hostname(card->host), + mmc_card_highspeed(card) ? "high speed " : "", + type); + } else { + printk(KERN_INFO "%s: new %s%s card at address %04x\n", + mmc_hostname(card->host), + mmc_card_highspeed(card) ? "high speed " : "", + type, card->rca); + } card->dev.uevent_suppress = 1; @@ -261,8 +285,13 @@ int mmc_add_card(struct mmc_card *card) void mmc_remove_card(struct mmc_card *card) { if (mmc_card_present(card)) { - printk(KERN_INFO "%s: card %04x removed\n", - mmc_hostname(card->host), card->rca); + if (mmc_host_is_spi(card->host)) { + printk(KERN_INFO "%s: SPI card removed\n", + mmc_hostname(card->host)); + } else { + printk(KERN_INFO "%s: card %04x removed\n", + mmc_hostname(card->host), card->rca); + } if (card->host->bus_ops->sysfs_remove) card->host->bus_ops->sysfs_remove(card->host, card); diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index bfd2ae5bd66..09435e0ec68 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -18,6 +18,7 @@ #include <linux/delay.h> #include <linux/pagemap.h> #include <linux/err.h> +#include <linux/leds.h> #include <asm/scatterlist.h> #include <linux/scatterlist.h> @@ -29,16 +30,27 @@ #include "core.h" #include "bus.h" #include "host.h" +#include "sdio_bus.h" #include "mmc_ops.h" #include "sd_ops.h" +#include "sdio_ops.h" extern int mmc_attach_mmc(struct mmc_host *host, u32 ocr); extern int mmc_attach_sd(struct mmc_host *host, u32 ocr); +extern int mmc_attach_sdio(struct mmc_host *host, u32 ocr); static struct workqueue_struct *workqueue; /* + * Enabling software CRCs on the data blocks can be a significant (30%) + * performance cost, and for other reasons may not always be desired. + * So we allow it it to be disabled. + */ +int use_spi_crc = 1; +module_param(use_spi_crc, bool, 0); + +/* * Internal function. Schedule delayed work in the MMC work queue. */ static int mmc_schedule_delayed_work(struct delayed_work *work, @@ -68,6 +80,11 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) struct mmc_command *cmd = mrq->cmd; int err = cmd->error; + if (err && cmd->retries && mmc_host_is_spi(host)) { + if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND) + cmd->retries = 0; + } + if (err && cmd->retries) { pr_debug("%s: req failed (CMD%u): %d, retrying...\n", mmc_hostname(host), cmd->opcode, err); @@ -76,6 +93,8 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) cmd->error = 0; host->ops->request(host, mrq); } else { + led_trigger_event(host->led, LED_OFF); + 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], @@ -118,7 +137,7 @@ mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) "tsac %d ms nsac %d\n", mmc_hostname(host), mrq->data->blksz, mrq->data->blocks, mrq->data->flags, - mrq->data->timeout_ns / 10000000, + mrq->data->timeout_ns / 1000000, mrq->data->timeout_clks); } @@ -130,6 +149,8 @@ mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) WARN_ON(!host->claimed); + led_trigger_event(host->led, LED_FULL); + mrq->cmd->error = 0; mrq->cmd->mrq = mrq; if (mrq->data) { @@ -199,7 +220,7 @@ int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries { struct mmc_request mrq; - BUG_ON(!host->claimed); + WARN_ON(!host->claimed); memset(&mrq, 0, sizeof(struct mmc_request)); @@ -220,17 +241,24 @@ EXPORT_SYMBOL(mmc_wait_for_cmd); * mmc_set_data_timeout - set the timeout for a data command * @data: data phase for command * @card: the MMC card associated with the data transfer - * @write: flag to differentiate reads from writes * * Computes the data timeout parameters according to the * correct algorithm given the card type. */ -void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card, - int write) +void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card) { unsigned int mult; /* + * SDIO cards only define an upper 1 s limit on access. + */ + if (mmc_card_sdio(card)) { + data->timeout_ns = 1000000000; + data->timeout_clks = 0; + return; + } + + /* * SD cards use a 100 multiplier rather than 10 */ mult = mmc_card_sd(card) ? 100 : 10; @@ -239,7 +267,7 @@ void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card, * Scale up the multiplier (and therefore the timeout) by * the r2w factor for writes. */ - if (write) + if (data->flags & MMC_DATA_WRITE) mult <<= card->csd.r2w_factor; data->timeout_ns = card->csd.tacc_ns * mult; @@ -255,7 +283,7 @@ void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card, timeout_us += data->timeout_clks * 1000 / (card->host->ios.clock / 1000); - if (write) + if (data->flags & MMC_DATA_WRITE) limit_us = 250000; else limit_us = 100000; @@ -272,15 +300,20 @@ void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card, EXPORT_SYMBOL(mmc_set_data_timeout); /** - * mmc_claim_host - exclusively claim a host + * __mmc_claim_host - exclusively claim a host * @host: mmc host to claim + * @abort: whether or not the operation should be aborted * - * Claim a host for a set of operations. + * Claim a host for a set of operations. If @abort is non null and + * dereference a non-zero value then this will return prematurely with + * that non-zero value without acquiring the lock. Returns zero + * with the lock held otherwise. */ -void mmc_claim_host(struct mmc_host *host) +int __mmc_claim_host(struct mmc_host *host, atomic_t *abort) { DECLARE_WAITQUEUE(wait, current); unsigned long flags; + int stop; might_sleep(); @@ -288,19 +321,24 @@ void mmc_claim_host(struct mmc_host *host) spin_lock_irqsave(&host->lock, flags); while (1) { set_current_state(TASK_UNINTERRUPTIBLE); - if (!host->claimed) + stop = abort ? atomic_read(abort) : 0; + if (stop || !host->claimed) break; spin_unlock_irqrestore(&host->lock, flags); schedule(); spin_lock_irqsave(&host->lock, flags); } set_current_state(TASK_RUNNING); - host->claimed = 1; + if (!stop) + host->claimed = 1; + else + wake_up(&host->wq); spin_unlock_irqrestore(&host->lock, flags); remove_wait_queue(&host->wq, &wait); + return stop; } -EXPORT_SYMBOL(mmc_claim_host); +EXPORT_SYMBOL(__mmc_claim_host); /** * mmc_release_host - release a host @@ -313,7 +351,7 @@ void mmc_release_host(struct mmc_host *host) { unsigned long flags; - BUG_ON(!host->claimed); + WARN_ON(!host->claimed); spin_lock_irqsave(&host->lock, flags); host->claimed = 0; @@ -433,19 +471,32 @@ static void mmc_power_up(struct mmc_host *host) int bit = fls(host->ocr_avail) - 1; host->ios.vdd = bit; - host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN; - host->ios.chip_select = MMC_CS_DONTCARE; + if (mmc_host_is_spi(host)) { + host->ios.chip_select = MMC_CS_HIGH; + host->ios.bus_mode = MMC_BUSMODE_PUSHPULL; + } else { + host->ios.chip_select = MMC_CS_DONTCARE; + host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN; + } host->ios.power_mode = MMC_POWER_UP; host->ios.bus_width = MMC_BUS_WIDTH_1; host->ios.timing = MMC_TIMING_LEGACY; mmc_set_ios(host); - mmc_delay(1); + /* + * This delay should be sufficient to allow the power supply + * to reach the minimum voltage. + */ + mmc_delay(2); host->ios.clock = host->f_min; host->ios.power_mode = MMC_POWER_ON; mmc_set_ios(host); + /* + * This delay must be at least 74 clock sizes, or 1 ms, or the + * time required to reach a stable voltage. + */ mmc_delay(2); } @@ -453,8 +504,10 @@ static void mmc_power_off(struct mmc_host *host) { host->ios.clock = 0; host->ios.vdd = 0; - host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN; - host->ios.chip_select = MMC_CS_DONTCARE; + if (!mmc_host_is_spi(host)) { + host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN; + host->ios.chip_select = MMC_CS_DONTCARE; + } host->ios.power_mode = MMC_POWER_OFF; host->ios.bus_width = MMC_BUS_WIDTH_1; host->ios.timing = MMC_TIMING_LEGACY; @@ -511,7 +564,7 @@ void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops) BUG_ON(!host); BUG_ON(!ops); - BUG_ON(!host->claimed); + WARN_ON(!host->claimed); spin_lock_irqsave(&host->lock, flags); @@ -535,8 +588,8 @@ void mmc_detach_bus(struct mmc_host *host) BUG_ON(!host); - BUG_ON(!host->claimed); - BUG_ON(!host->bus_ops); + WARN_ON(!host->claimed); + WARN_ON(!host->bus_ops); spin_lock_irqsave(&host->lock, flags); @@ -564,7 +617,7 @@ void mmc_detect_change(struct mmc_host *host, unsigned long delay) #ifdef CONFIG_MMC_DEBUG unsigned long flags; spin_lock_irqsave(&host->lock, flags); - BUG_ON(host->removed); + WARN_ON(host->removed); spin_unlock_irqrestore(&host->lock, flags); #endif @@ -597,24 +650,38 @@ void mmc_rescan(struct work_struct *work) mmc_send_if_cond(host, host->ocr_avail); + /* + * First we search for SDIO... + */ + err = mmc_send_io_op_cond(host, 0, &ocr); + if (!err) { + if (mmc_attach_sdio(host, ocr)) + mmc_power_off(host); + return; + } + + /* + * ...then normal SD... + */ err = mmc_send_app_op_cond(host, 0, &ocr); - if (err == MMC_ERR_NONE) { + if (!err) { if (mmc_attach_sd(host, ocr)) mmc_power_off(host); - } else { - /* - * If we fail to detect any SD cards then try - * searching for MMC cards. - */ - err = mmc_send_op_cond(host, 0, &ocr); - if (err == MMC_ERR_NONE) { - if (mmc_attach_mmc(host, ocr)) - mmc_power_off(host); - } else { + return; + } + + /* + * ...and finally MMC. + */ + err = mmc_send_op_cond(host, 0, &ocr); + if (!err) { + if (mmc_attach_mmc(host, ocr)) mmc_power_off(host); - mmc_release_host(host); - } + return; } + + mmc_release_host(host); + mmc_power_off(host); } else { if (host->bus_ops->detect && !host->bus_dead) host->bus_ops->detect(host); @@ -725,22 +792,38 @@ static int __init mmc_init(void) return -ENOMEM; ret = mmc_register_bus(); - if (ret == 0) { - ret = mmc_register_host_class(); - if (ret) - mmc_unregister_bus(); - } + if (ret) + goto destroy_workqueue; + + ret = mmc_register_host_class(); + if (ret) + goto unregister_bus; + + ret = sdio_register_bus(); + if (ret) + goto unregister_host_class; + + return 0; + +unregister_host_class: + mmc_unregister_host_class(); +unregister_bus: + mmc_unregister_bus(); +destroy_workqueue: + destroy_workqueue(workqueue); + return ret; } static void __exit mmc_exit(void) { + sdio_unregister_bus(); mmc_unregister_host_class(); mmc_unregister_bus(); destroy_workqueue(workqueue); } -module_init(mmc_init); +subsys_initcall(mmc_init); module_exit(mmc_exit); MODULE_LICENSE("GPL"); diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h index bb2774af9ea..39daf2fb5dc 100644 --- a/drivers/mmc/core/core.h +++ b/drivers/mmc/core/core.h @@ -48,5 +48,7 @@ void mmc_rescan(struct work_struct *work); void mmc_start_host(struct mmc_host *host); void mmc_stop_host(struct mmc_host *host); +extern int use_spi_crc; + #endif diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 2c7ce8f43a9..64fbc9759a3 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -15,6 +15,7 @@ #include <linux/err.h> #include <linux/idr.h> #include <linux/pagemap.h> +#include <linux/leds.h> #include <linux/mmc/host.h> @@ -100,6 +101,9 @@ int mmc_add_host(struct mmc_host *host) { int err; + WARN_ON((host->caps & MMC_CAP_SDIO_IRQ) && + !host->ops->enable_sdio_irq); + if (!idr_pre_get(&mmc_host_idr, GFP_KERNEL)) return -ENOMEM; @@ -112,6 +116,8 @@ int mmc_add_host(struct mmc_host *host) snprintf(host->class_dev.bus_id, BUS_ID_SIZE, "mmc%d", host->index); + led_trigger_register_simple(host->class_dev.bus_id, &host->led); + err = device_add(&host->class_dev); if (err) return err; @@ -137,6 +143,8 @@ void mmc_remove_host(struct mmc_host *host) device_del(&host->class_dev); + led_trigger_unregister(host->led); + spin_lock(&mmc_host_lock); idr_remove(&mmc_host_idr, host->index); spin_unlock(&mmc_host_lock); diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 21d7f48e1d4..65fe28860f5 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -161,13 +161,12 @@ static int mmc_read_ext_csd(struct mmc_card *card) { int err; u8 *ext_csd; + unsigned int ext_csd_struct; BUG_ON(!card); - err = MMC_ERR_FAILED; - if (card->csd.mmca_vsn < CSD_SPEC_VER_4) - return MMC_ERR_NONE; + return 0; /* * As the ext_csd is so large and mostly unused, we don't store the @@ -176,13 +175,19 @@ static int mmc_read_ext_csd(struct mmc_card *card) ext_csd = kmalloc(512, GFP_KERNEL); if (!ext_csd) { printk(KERN_ERR "%s: could not allocate a buffer to " - "receive the ext_csd. mmc v4 cards will be " - "treated as v3.\n", mmc_hostname(card->host)); - return MMC_ERR_FAILED; + "receive the ext_csd.\n", mmc_hostname(card->host)); + return -ENOMEM; } err = mmc_send_ext_csd(card, ext_csd); - if (err != MMC_ERR_NONE) { + if (err) { + /* + * We all hosts that cannot perform the command + * to fail more gracefully + */ + if (err != -EINVAL) + goto out; + /* * High capacity cards should have this "magic" size * stored in their CSD. @@ -197,18 +202,29 @@ static int mmc_read_ext_csd(struct mmc_card *card) "EXT_CSD, performance might " "suffer.\n", mmc_hostname(card->host)); - err = MMC_ERR_NONE; + err = 0; } + goto out; } - card->ext_csd.sectors = - ext_csd[EXT_CSD_SEC_CNT + 0] << 0 | - ext_csd[EXT_CSD_SEC_CNT + 1] << 8 | - ext_csd[EXT_CSD_SEC_CNT + 2] << 16 | - ext_csd[EXT_CSD_SEC_CNT + 3] << 24; - if (card->ext_csd.sectors) - mmc_card_set_blockaddr(card); + ext_csd_struct = ext_csd[EXT_CSD_REV]; + if (ext_csd_struct > 2) { + printk(KERN_ERR "%s: unrecognised EXT_CSD structure " + "version %d\n", mmc_hostname(card->host), + ext_csd_struct); + return -EINVAL; + } + + if (ext_csd_struct >= 2) { + card->ext_csd.sectors = + ext_csd[EXT_CSD_SEC_CNT + 0] << 0 | + ext_csd[EXT_CSD_SEC_CNT + 1] << 8 | + ext_csd[EXT_CSD_SEC_CNT + 2] << 16 | + ext_csd[EXT_CSD_SEC_CNT + 3] << 24; + if (card->ext_csd.sectors) + mmc_card_set_blockaddr(card); + } switch (ext_csd[EXT_CSD_CARD_TYPE]) { case EXT_CSD_CARD_TYPE_52 | EXT_CSD_CARD_TYPE_26: @@ -246,7 +262,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, unsigned int max_dtr; BUG_ON(!host); - BUG_ON(!host->claimed); + WARN_ON(!host->claimed); /* * Since we're changing the OCR value, we seem to @@ -258,19 +274,33 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, /* The extra bit indicates that we support high capacity */ err = mmc_send_op_cond(host, ocr | (1 << 30), NULL); - if (err != MMC_ERR_NONE) + 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; + } + + /* * Fetch CID from card. */ - err = mmc_all_send_cid(host, cid); - if (err != MMC_ERR_NONE) + if (mmc_host_is_spi(host)) + err = mmc_send_cid(host, cid); + else + err = mmc_all_send_cid(host, cid); + if (err) goto err; if (oldcard) { - if (memcmp(cid, oldcard->raw_cid, sizeof(cid)) != 0) + if (memcmp(cid, oldcard->raw_cid, sizeof(cid)) != 0) { + err = -ENOENT; goto err; + } card = oldcard; } else { @@ -278,8 +308,10 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, * Allocate card structure. */ card = mmc_alloc_card(host); - if (IS_ERR(card)) + if (IS_ERR(card)) { + err = PTR_ERR(card); goto err; + } card->type = MMC_TYPE_MMC; card->rca = 1; @@ -287,43 +319,47 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, } /* - * Set card RCA. + * For native busses: set card RCA and quit open drain mode. */ - err = mmc_set_relative_addr(card); - if (err != MMC_ERR_NONE) - goto free_card; + if (!mmc_host_is_spi(host)) { + err = mmc_set_relative_addr(card); + if (err) + goto free_card; - mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); + mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); + } if (!oldcard) { /* * Fetch CSD from card. */ err = mmc_send_csd(card, card->raw_csd); - if (err != MMC_ERR_NONE) + if (err) goto free_card; err = mmc_decode_csd(card); - if (err < 0) + if (err) goto free_card; err = mmc_decode_cid(card); - if (err < 0) + if (err) goto free_card; } /* * Select card, as all following commands rely on that. */ - err = mmc_select_card(card); - if (err != MMC_ERR_NONE) - goto free_card; + if (!mmc_host_is_spi(host)) { + err = mmc_select_card(card); + if (err) + goto free_card; + } if (!oldcard) { /* - * Fetch and process extened CSD. + * Fetch and process extended CSD. */ err = mmc_read_ext_csd(card); - if (err != MMC_ERR_NONE) + if (err) goto free_card; } @@ -334,7 +370,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, (host->caps & MMC_CAP_MMC_HIGHSPEED)) { err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_HS_TIMING, 1); - if (err != MMC_ERR_NONE) + if (err) goto free_card; mmc_card_set_highspeed(card); @@ -363,7 +399,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, (host->caps & MMC_CAP_4_BIT_DATA)) { err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BUS_WIDTH, EXT_CSD_BUS_WIDTH_4); - if (err != MMC_ERR_NONE) + if (err) goto free_card; mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4); @@ -372,14 +408,14 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, if (!oldcard) host->card = card; - return MMC_ERR_NONE; + return 0; free_card: if (!oldcard) mmc_remove_card(card); err: - return MMC_ERR_FAILED; + return err; } /* @@ -413,7 +449,7 @@ static void mmc_detect(struct mmc_host *host) mmc_release_host(host); - if (err != MMC_ERR_NONE) { + if (err) { mmc_remove(host); mmc_claim_host(host); @@ -480,7 +516,8 @@ static void mmc_suspend(struct mmc_host *host) BUG_ON(!host->card); mmc_claim_host(host); - mmc_deselect_cards(host); + if (!mmc_host_is_spi(host)) + mmc_deselect_cards(host); host->card->state &= ~MMC_STATE_HIGHSPEED; mmc_release_host(host); } @@ -502,7 +539,7 @@ static void mmc_resume(struct mmc_host *host) err = mmc_init_card(host, host->ocr, host->card); mmc_release_host(host); - if (err != MMC_ERR_NONE) { + if (err) { mmc_remove(host); mmc_claim_host(host); @@ -536,11 +573,20 @@ int mmc_attach_mmc(struct mmc_host *host, u32 ocr) int err; BUG_ON(!host); - BUG_ON(!host->claimed); + WARN_ON(!host->claimed); mmc_attach_bus(host, &mmc_ops); /* + * We need to get OCR a different way for SPI. + */ + if (mmc_host_is_spi(host)) { + err = mmc_spi_read_ocr(host, 1, &ocr); + if (err) + goto err; + } + + /* * Sanity check the voltages that the card claims to * support. */ @@ -565,7 +611,7 @@ int mmc_attach_mmc(struct mmc_host *host, u32 ocr) * Detect and init the card. */ err = mmc_init_card(host, host->ocr, NULL); - if (err != MMC_ERR_NONE) + if (err) goto err; mmc_release_host(host); @@ -587,6 +633,6 @@ err: printk(KERN_ERR "%s: error %d whilst initialising MMC card\n", mmc_hostname(host), err); - return 0; + return err; } diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index 913e75f0084..bf4bc6adcfe 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -40,10 +40,10 @@ static int _mmc_select_card(struct mmc_host *host, struct mmc_card *card) } err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) return err; - return MMC_ERR_NONE; + return 0; } int mmc_select_card(struct mmc_card *card) @@ -63,23 +63,36 @@ int mmc_go_idle(struct mmc_host *host) int err; struct mmc_command cmd; - mmc_set_chip_select(host, MMC_CS_HIGH); - - mmc_delay(1); + /* + * Non-SPI hosts need to prevent chipselect going active during + * GO_IDLE; that would put chips into SPI mode. Remind them of + * that in case of hardware that won't pull up DAT3/nCS otherwise. + * + * SPI hosts ignore ios.chip_select; it's managed according to + * rules that must accomodate non-MMC slaves which this layer + * won't even know about. + */ + if (!mmc_host_is_spi(host)) { + mmc_set_chip_select(host, MMC_CS_HIGH); + mmc_delay(1); + } memset(&cmd, 0, sizeof(struct mmc_command)); cmd.opcode = MMC_GO_IDLE_STATE; cmd.arg = 0; - cmd.flags = MMC_RSP_NONE | MMC_CMD_BC; + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_NONE | MMC_CMD_BC; err = mmc_wait_for_cmd(host, &cmd, 0); mmc_delay(1); - mmc_set_chip_select(host, MMC_CS_DONTCARE); + if (!mmc_host_is_spi(host)) { + mmc_set_chip_select(host, MMC_CS_DONTCARE); + mmc_delay(1); + } - mmc_delay(1); + host->use_spi_crc = 0; return err; } @@ -94,23 +107,33 @@ int mmc_send_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr) memset(&cmd, 0, sizeof(struct mmc_command)); cmd.opcode = MMC_SEND_OP_COND; - cmd.arg = ocr; - cmd.flags = MMC_RSP_R3 | MMC_CMD_BCR; + cmd.arg = mmc_host_is_spi(host) ? 0 : ocr; + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R3 | MMC_CMD_BCR; for (i = 100; i; i--) { err = mmc_wait_for_cmd(host, &cmd, 0); - if (err != MMC_ERR_NONE) + if (err) break; - if (cmd.resp[0] & MMC_CARD_BUSY || ocr == 0) + /* if we're just probing, do a single pass */ + if (ocr == 0) break; - err = MMC_ERR_TIMEOUT; + /* otherwise wait until reset completes */ + if (mmc_host_is_spi(host)) { + if (!(cmd.resp[0] & R1_SPI_IDLE)) + break; + } else { + if (cmd.resp[0] & MMC_CARD_BUSY) + break; + } + + err = -ETIMEDOUT; mmc_delay(10); } - if (rocr) + if (rocr && !mmc_host_is_spi(host)) *rocr = cmd.resp[0]; return err; @@ -131,12 +154,12 @@ int mmc_all_send_cid(struct mmc_host *host, u32 *cid) cmd.flags = MMC_RSP_R2 | MMC_CMD_BCR; err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) return err; memcpy(cid, cmd.resp, sizeof(u32) * 4); - return MMC_ERR_NONE; + return 0; } int mmc_set_relative_addr(struct mmc_card *card) @@ -154,46 +177,52 @@ int mmc_set_relative_addr(struct mmc_card *card) cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) return err; - return MMC_ERR_NONE; + return 0; } -int mmc_send_csd(struct mmc_card *card, u32 *csd) +static int +mmc_send_cxd_native(struct mmc_host *host, u32 arg, u32 *cxd, int opcode) { int err; struct mmc_command cmd; - BUG_ON(!card); - BUG_ON(!card->host); - BUG_ON(!csd); + BUG_ON(!host); + BUG_ON(!cxd); memset(&cmd, 0, sizeof(struct mmc_command)); - cmd.opcode = MMC_SEND_CSD; - cmd.arg = card->rca << 16; + cmd.opcode = opcode; + cmd.arg = arg; cmd.flags = MMC_RSP_R2 | MMC_CMD_AC; - err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES); + if (err) return err; - memcpy(csd, cmd.resp, sizeof(u32) * 4); + memcpy(cxd, cmd.resp, sizeof(u32) * 4); - return MMC_ERR_NONE; + return 0; } -int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd) +static int +mmc_send_cxd_data(struct mmc_card *card, struct mmc_host *host, + u32 opcode, void *buf, unsigned len) { struct mmc_request mrq; struct mmc_command cmd; struct mmc_data data; struct scatterlist sg; + void *data_buf; - BUG_ON(!card); - BUG_ON(!card->host); - BUG_ON(!ext_csd); + /* dma onto stack is unsafe/nonportable, but callers to this + * routine normally provide temporary on-stack buffers ... + */ + data_buf = kmalloc(len, GFP_KERNEL); + if (data_buf == NULL) + return -ENOMEM; memset(&mrq, 0, sizeof(struct mmc_request)); memset(&cmd, 0, sizeof(struct mmc_command)); @@ -202,28 +231,99 @@ int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd) mrq.cmd = &cmd; mrq.data = &data; - cmd.opcode = MMC_SEND_EXT_CSD; + cmd.opcode = opcode; cmd.arg = 0; - cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; - data.blksz = 512; + /* NOTE HACK: the MMC_RSP_SPI_R1 is always correct here, but we + * rely on callers to never use this with "native" calls for reading + * CSD or CID. Native versions of those commands use the R2 type, + * not R1 plus a data block. + */ + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; + + data.blksz = len; data.blocks = 1; data.flags = MMC_DATA_READ; data.sg = &sg; data.sg_len = 1; - sg_init_one(&sg, ext_csd, 512); + sg_init_one(&sg, data_buf, len); - mmc_set_data_timeout(&data, card, 0); + if (card) + mmc_set_data_timeout(&data, card); - mmc_wait_for_req(card->host, &mrq); + mmc_wait_for_req(host, &mrq); - if (cmd.error != MMC_ERR_NONE) + memcpy(buf, data_buf, len); + kfree(data_buf); + + if (cmd.error) return cmd.error; - if (data.error != MMC_ERR_NONE) + if (data.error) return data.error; - return MMC_ERR_NONE; + return 0; +} + +int mmc_send_csd(struct mmc_card *card, u32 *csd) +{ + if (!mmc_host_is_spi(card->host)) + return mmc_send_cxd_native(card->host, card->rca << 16, + csd, MMC_SEND_CSD); + + return mmc_send_cxd_data(card, card->host, MMC_SEND_CSD, csd, 16); +} + +int mmc_send_cid(struct mmc_host *host, u32 *cid) +{ + if (!mmc_host_is_spi(host)) { + if (!host->card) + return -EINVAL; + return mmc_send_cxd_native(host, host->card->rca << 16, + cid, MMC_SEND_CID); + } + + return mmc_send_cxd_data(NULL, host, MMC_SEND_CID, cid, 16); +} + +int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd) +{ + return mmc_send_cxd_data(card, card->host, MMC_SEND_EXT_CSD, + ext_csd, 512); +} + +int mmc_spi_read_ocr(struct mmc_host *host, int highcap, u32 *ocrp) +{ + struct mmc_command cmd; + int err; + + memset(&cmd, 0, sizeof(struct mmc_command)); + + cmd.opcode = MMC_SPI_READ_OCR; + cmd.arg = highcap ? (1 << 30) : 0; + cmd.flags = MMC_RSP_SPI_R3; + + err = mmc_wait_for_cmd(host, &cmd, 0); + + *ocrp = cmd.resp[1]; + return err; +} + +int mmc_spi_set_crc(struct mmc_host *host, int use_crc) +{ + struct mmc_command cmd; + int err; + + memset(&cmd, 0, sizeof(struct mmc_command)); + + cmd.opcode = MMC_SPI_CRC_ON_OFF; + cmd.flags = MMC_RSP_SPI_R1; + cmd.arg = use_crc; + + err = mmc_wait_for_cmd(host, &cmd, 0); + if (!err) + host->use_spi_crc = use_crc; + return err; } int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value) @@ -241,13 +341,13 @@ int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value) (index << 16) | (value << 8) | set; - cmd.flags = MMC_RSP_R1B | MMC_CMD_AC; + cmd.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC; err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) return err; - return MMC_ERR_NONE; + return 0; } int mmc_send_status(struct mmc_card *card, u32 *status) @@ -261,16 +361,20 @@ int mmc_send_status(struct mmc_card *card, u32 *status) memset(&cmd, 0, sizeof(struct mmc_command)); cmd.opcode = MMC_SEND_STATUS; - cmd.arg = card->rca << 16; - cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + if (!mmc_host_is_spi(card->host)) + cmd.arg = card->rca << 16; + cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC; err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) return err; + /* NOTE: callers are required to understand the difference + * between "native" and SPI format status words! + */ if (status) *status = cmd.resp[0]; - return MMC_ERR_NONE; + return 0; } diff --git a/drivers/mmc/core/mmc_ops.h b/drivers/mmc/core/mmc_ops.h index 76d09a93c5d..17854bf7cf0 100644 --- a/drivers/mmc/core/mmc_ops.h +++ b/drivers/mmc/core/mmc_ops.h @@ -22,6 +22,9 @@ int mmc_send_csd(struct mmc_card *card, u32 *csd); int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd); int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value); int mmc_send_status(struct mmc_card *card, u32 *status); +int mmc_send_cid(struct mmc_host *host, u32 *cid); +int mmc_spi_read_ocr(struct mmc_host *host, int highcap, u32 *ocrp); +int mmc_spi_set_crc(struct mmc_host *host, int use_crc); #endif diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 1edc62b1e5c..d1c1e0f592f 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -166,8 +166,6 @@ static int mmc_decode_scr(struct mmc_card *card) unsigned int scr_struct; u32 resp[4]; - BUG_ON(!mmc_card_sd(card)); - resp[3] = card->raw_scr[1]; resp[2] = card->raw_scr[0]; @@ -193,30 +191,38 @@ static int mmc_read_switch(struct mmc_card *card) u8 *status; if (card->scr.sda_vsn < SCR_SPEC_VER_1) - return MMC_ERR_NONE; + return 0; if (!(card->csd.cmdclass & CCC_SWITCH)) { printk(KERN_WARNING "%s: card lacks mandatory switch " "function, performance might suffer.\n", mmc_hostname(card->host)); - return MMC_ERR_NONE; + return 0; } - err = MMC_ERR_FAILED; + err = -EIO; status = kmalloc(64, GFP_KERNEL); if (!status) { printk(KERN_ERR "%s: could not allocate a buffer for " "switch capabilities.\n", mmc_hostname(card->host)); - return err; + return -ENOMEM; } err = mmc_sd_switch(card, 0, 0, 1, status); - if (err != MMC_ERR_NONE) { + if (err) { + /* + * We all hosts that cannot perform the command + * to fail more gracefully + */ + if (err != -EINVAL) + goto out; + printk(KERN_WARNING "%s: problem reading switch " "capabilities, performance might suffer.\n", mmc_hostname(card->host)); - err = MMC_ERR_NONE; + err = 0; + goto out; } @@ -238,28 +244,28 @@ static int mmc_switch_hs(struct mmc_card *card) u8 *status; if (card->scr.sda_vsn < SCR_SPEC_VER_1) - return MMC_ERR_NONE; + return 0; if (!(card->csd.cmdclass & CCC_SWITCH)) - return MMC_ERR_NONE; + return 0; if (!(card->host->caps & MMC_CAP_SD_HIGHSPEED)) - return MMC_ERR_NONE; + return 0; if (card->sw_caps.hs_max_dtr == 0) - return MMC_ERR_NONE; + return 0; - err = MMC_ERR_FAILED; + err = -EIO; status = kmalloc(64, GFP_KERNEL); if (!status) { printk(KERN_ERR "%s: could not allocate a buffer for " "switch capabilities.\n", mmc_hostname(card->host)); - return err; + return -ENOMEM; } err = mmc_sd_switch(card, 1, 0, 1, status); - if (err != MMC_ERR_NONE) + if (err) goto out; if ((status[16] & 0xF) != 1) { @@ -292,7 +298,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, unsigned int max_dtr; BUG_ON(!host); - BUG_ON(!host->claimed); + WARN_ON(!host->claimed); /* * Since we're changing the OCR value, we seem to @@ -309,23 +315,37 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, * block-addressed SDHC cards. */ err = mmc_send_if_cond(host, ocr); - if (err == MMC_ERR_NONE) + if (!err) ocr |= 1 << 30; err = mmc_send_app_op_cond(host, ocr, NULL); - if (err != MMC_ERR_NONE) + 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; + } + + /* * Fetch CID from card. */ - err = mmc_all_send_cid(host, cid); - if (err != MMC_ERR_NONE) + if (mmc_host_is_spi(host)) + err = mmc_send_cid(host, cid); + else + err = mmc_all_send_cid(host, cid); + if (err) goto err; if (oldcard) { - if (memcmp(cid, oldcard->raw_cid, sizeof(cid)) != 0) + if (memcmp(cid, oldcard->raw_cid, sizeof(cid)) != 0) { + err = -ENOENT; goto err; + } card = oldcard; } else { @@ -333,32 +353,36 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, * Allocate card structure. */ card = mmc_alloc_card(host); - if (IS_ERR(card)) + if (IS_ERR(card)) { + err = PTR_ERR(card); goto err; + } card->type = MMC_TYPE_SD; memcpy(card->raw_cid, cid, sizeof(card->raw_cid)); } /* - * Set card RCA. + * For native busses: get card RCA and quit open drain mode. */ - err = mmc_send_relative_addr(host, &card->rca); - if (err != MMC_ERR_NONE) - goto free_card; + if (!mmc_host_is_spi(host)) { + err = mmc_send_relative_addr(host, &card->rca); + if (err) + goto free_card; - mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); + mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); + } if (!oldcard) { /* * Fetch CSD from card. */ err = mmc_send_csd(card, card->raw_csd); - if (err != MMC_ERR_NONE) + if (err) goto free_card; err = mmc_decode_csd(card); - if (err < 0) + if (err) goto free_card; mmc_decode_cid(card); @@ -367,16 +391,18 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, /* * Select card, as all following commands rely on that. */ - err = mmc_select_card(card); - if (err != MMC_ERR_NONE) - goto free_card; + if (!mmc_host_is_spi(host)) { + err = mmc_select_card(card); + if (err) + goto free_card; + } if (!oldcard) { /* * Fetch SCR from card. */ err = mmc_app_send_scr(card, card->raw_scr); - if (err != MMC_ERR_NONE) + if (err) goto free_card; err = mmc_decode_scr(card); @@ -387,7 +413,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, * Fetch switch information from card. */ err = mmc_read_switch(card); - if (err != MMC_ERR_NONE) + if (err) goto free_card; } @@ -395,7 +421,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, * Attempt to change to high-speed (if supported) */ err = mmc_switch_hs(card); - if (err != MMC_ERR_NONE) + if (err) goto free_card; /* @@ -418,7 +444,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, if ((host->caps & MMC_CAP_4_BIT_DATA) && (card->scr.bus_widths & SD_SCR_BUS_WIDTH_4)) { err = mmc_app_set_bus_width(card, MMC_BUS_WIDTH_4); - if (err != MMC_ERR_NONE) + if (err) goto free_card; mmc_set_bus_width(host, MMC_BUS_WIDTH_4); @@ -442,14 +468,14 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, if (!oldcard) host->card = card; - return MMC_ERR_NONE; + return 0; free_card: if (!oldcard) mmc_remove_card(card); err: - return MMC_ERR_FAILED; + return err; } /* @@ -483,7 +509,7 @@ static void mmc_sd_detect(struct mmc_host *host) mmc_release_host(host); - if (err != MMC_ERR_NONE) { + if (err) { mmc_sd_remove(host); mmc_claim_host(host); @@ -552,7 +578,8 @@ static void mmc_sd_suspend(struct mmc_host *host) BUG_ON(!host->card); mmc_claim_host(host); - mmc_deselect_cards(host); + if (!mmc_host_is_spi(host)) + mmc_deselect_cards(host); host->card->state &= ~MMC_STATE_HIGHSPEED; mmc_release_host(host); } @@ -574,7 +601,7 @@ static void mmc_sd_resume(struct mmc_host *host) err = mmc_sd_init_card(host, host->ocr, host->card); mmc_release_host(host); - if (err != MMC_ERR_NONE) { + if (err) { mmc_sd_remove(host); mmc_claim_host(host); @@ -608,11 +635,22 @@ int mmc_attach_sd(struct mmc_host *host, u32 ocr) int err; BUG_ON(!host); - BUG_ON(!host->claimed); + WARN_ON(!host->claimed); mmc_attach_bus(host, &mmc_sd_ops); /* + * We need to get OCR a different way for SPI. + */ + if (mmc_host_is_spi(host)) { + mmc_go_idle(host); + + err = mmc_spi_read_ocr(host, 0, &ocr); + if (err) + goto err; + } + + /* * Sanity check the voltages that the card claims to * support. */ @@ -644,7 +682,7 @@ int mmc_attach_sd(struct mmc_host *host, u32 ocr) * Detect and init the card. */ err = mmc_sd_init_card(host, host->ocr, NULL); - if (err != MMC_ERR_NONE) + if (err) goto err; mmc_release_host(host); @@ -666,6 +704,6 @@ err: printk(KERN_ERR "%s: error %d whilst initialising SD card\n", mmc_hostname(host), err); - return 0; + return err; } diff --git a/drivers/mmc/core/sd_ops.c b/drivers/mmc/core/sd_ops.c index 342f340ebc2..ee4029a24ef 100644 --- a/drivers/mmc/core/sd_ops.c +++ b/drivers/mmc/core/sd_ops.c @@ -33,21 +33,21 @@ static int mmc_app_cmd(struct mmc_host *host, struct mmc_card *card) if (card) { cmd.arg = card->rca << 16; - cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC; } else { cmd.arg = 0; - cmd.flags = MMC_RSP_R1 | MMC_CMD_BCR; + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_BCR; } err = mmc_wait_for_cmd(host, &cmd, 0); - if (err != MMC_ERR_NONE) + if (err) return err; /* Check that card supported application commands */ - if (!(cmd.resp[0] & R1_APP_CMD)) - return MMC_ERR_FAILED; + if (!mmc_host_is_spi(host) && !(cmd.resp[0] & R1_APP_CMD)) + return -EOPNOTSUPP; - return MMC_ERR_NONE; + return 0; } /** @@ -73,7 +73,7 @@ int mmc_wait_for_app_cmd(struct mmc_host *host, struct mmc_card *card, BUG_ON(!cmd); BUG_ON(retries < 0); - err = MMC_ERR_INVALID; + err = -EIO; /* * We have to resend MMC_APP_CMD for each attempt so @@ -83,8 +83,14 @@ int mmc_wait_for_app_cmd(struct mmc_host *host, struct mmc_card *card, memset(&mrq, 0, sizeof(struct mmc_request)); err = mmc_app_cmd(host, card); - if (err != MMC_ERR_NONE) + if (err) { + /* no point in retrying; no APP commands allowed */ + if (mmc_host_is_spi(host)) { + if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND) + break; + } continue; + } memset(&mrq, 0, sizeof(struct mmc_request)); @@ -97,8 +103,14 @@ int mmc_wait_for_app_cmd(struct mmc_host *host, struct mmc_card *card, mmc_wait_for_req(host, &mrq); err = cmd->error; - if (cmd->error == MMC_ERR_NONE) + if (!cmd->error) break; + + /* no point in retrying illegal APP commands */ + if (mmc_host_is_spi(host)) { + if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND) + break; + } } return err; @@ -127,14 +139,14 @@ int mmc_app_set_bus_width(struct mmc_card *card, int width) cmd.arg = SD_BUS_WIDTH_4; break; default: - return MMC_ERR_INVALID; + return -EINVAL; } err = mmc_wait_for_app_cmd(card->host, card, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) return err; - return MMC_ERR_NONE; + return 0; } int mmc_send_app_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr) @@ -147,23 +159,36 @@ int mmc_send_app_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr) memset(&cmd, 0, sizeof(struct mmc_command)); cmd.opcode = SD_APP_OP_COND; - cmd.arg = ocr; - cmd.flags = MMC_RSP_R3 | MMC_CMD_BCR; + if (mmc_host_is_spi(host)) + cmd.arg = ocr & (1 << 30); /* SPI only defines one bit */ + else + cmd.arg = ocr; + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R3 | MMC_CMD_BCR; for (i = 100; i; i--) { err = mmc_wait_for_app_cmd(host, NULL, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) break; - if (cmd.resp[0] & MMC_CARD_BUSY || ocr == 0) + /* if we're just probing, do a single pass */ + if (ocr == 0) break; - err = MMC_ERR_TIMEOUT; + /* otherwise wait until reset completes */ + if (mmc_host_is_spi(host)) { + if (!(cmd.resp[0] & R1_SPI_IDLE)) + break; + } else { + if (cmd.resp[0] & MMC_CARD_BUSY) + break; + } + + err = -ETIMEDOUT; mmc_delay(10); } - if (rocr) + if (rocr && !mmc_host_is_spi(host)) *rocr = cmd.resp[0]; return err; @@ -174,6 +199,7 @@ int mmc_send_if_cond(struct mmc_host *host, u32 ocr) struct mmc_command cmd; int err; static const u8 test_pattern = 0xAA; + u8 result_pattern; /* * To support SD 2.0 cards, we must always invoke SD_SEND_IF_COND @@ -182,16 +208,21 @@ int mmc_send_if_cond(struct mmc_host *host, u32 ocr) */ cmd.opcode = SD_SEND_IF_COND; cmd.arg = ((ocr & 0xFF8000) != 0) << 8 | test_pattern; - cmd.flags = MMC_RSP_R7 | MMC_CMD_BCR; + cmd.flags = MMC_RSP_SPI_R7 | MMC_RSP_R7 | MMC_CMD_BCR; err = mmc_wait_for_cmd(host, &cmd, 0); - if (err != MMC_ERR_NONE) + if (err) return err; - if ((cmd.resp[0] & 0xFF) != test_pattern) - return MMC_ERR_FAILED; + if (mmc_host_is_spi(host)) + result_pattern = cmd.resp[1] & 0xFF; + else + result_pattern = cmd.resp[0] & 0xFF; + + if (result_pattern != test_pattern) + return -EIO; - return MMC_ERR_NONE; + return 0; } int mmc_send_relative_addr(struct mmc_host *host, unsigned int *rca) @@ -209,12 +240,12 @@ int mmc_send_relative_addr(struct mmc_host *host, unsigned int *rca) cmd.flags = MMC_RSP_R6 | MMC_CMD_BCR; err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) return err; *rca = cmd.resp[0] >> 16; - return MMC_ERR_NONE; + return 0; } int mmc_app_send_scr(struct mmc_card *card, u32 *scr) @@ -229,8 +260,10 @@ int mmc_app_send_scr(struct mmc_card *card, u32 *scr) BUG_ON(!card->host); BUG_ON(!scr); + /* NOTE: caller guarantees scr is heap-allocated */ + err = mmc_app_cmd(card->host, card); - if (err != MMC_ERR_NONE) + if (err) return err; memset(&mrq, 0, sizeof(struct mmc_request)); @@ -242,7 +275,7 @@ int mmc_app_send_scr(struct mmc_card *card, u32 *scr) cmd.opcode = SD_APP_SEND_SCR; cmd.arg = 0; - cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; data.blksz = 8; data.blocks = 1; @@ -252,19 +285,19 @@ int mmc_app_send_scr(struct mmc_card *card, u32 *scr) sg_init_one(&sg, scr, 8); - mmc_set_data_timeout(&data, card, 0); + mmc_set_data_timeout(&data, card); mmc_wait_for_req(card->host, &mrq); - if (cmd.error != MMC_ERR_NONE) + if (cmd.error) return cmd.error; - if (data.error != MMC_ERR_NONE) + if (data.error) return data.error; scr[0] = ntohl(scr[0]); scr[1] = ntohl(scr[1]); - return MMC_ERR_NONE; + return 0; } int mmc_sd_switch(struct mmc_card *card, int mode, int group, @@ -278,6 +311,8 @@ int mmc_sd_switch(struct mmc_card *card, int mode, int group, BUG_ON(!card); BUG_ON(!card->host); + /* NOTE: caller guarantees resp is heap-allocated */ + mode = !!mode; value &= 0xF; @@ -292,7 +327,7 @@ int mmc_sd_switch(struct mmc_card *card, int mode, int group, cmd.arg = mode << 31 | 0x00FFFFFF; cmd.arg &= ~(0xF << (group * 4)); cmd.arg |= value << (group * 4); - cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; data.blksz = 64; data.blocks = 1; @@ -302,15 +337,15 @@ int mmc_sd_switch(struct mmc_card *card, int mode, int group, sg_init_one(&sg, resp, 64); - mmc_set_data_timeout(&data, card, 0); + mmc_set_data_timeout(&data, card); mmc_wait_for_req(card->host, &mrq); - if (cmd.error != MMC_ERR_NONE) + if (cmd.error) return cmd.error; - if (data.error != MMC_ERR_NONE) + if (data.error) return data.error; - return MMC_ERR_NONE; + return 0; } diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c new file mode 100644 index 00000000000..87a50f456ef --- /dev/null +++ b/drivers/mmc/core/sdio.c @@ -0,0 +1,395 @@ +/* + * linux/drivers/mmc/sdio.c + * + * Copyright 2006-2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#include <linux/err.h> + +#include <linux/mmc/host.h> +#include <linux/mmc/card.h> +#include <linux/mmc/sdio.h> +#include <linux/mmc/sdio_func.h> + +#include "core.h" +#include "bus.h" +#include "sdio_bus.h" +#include "mmc_ops.h" +#include "sd_ops.h" +#include "sdio_ops.h" +#include "sdio_cis.h" + +static int sdio_read_fbr(struct sdio_func *func) +{ + int ret; + unsigned char data; + + ret = mmc_io_rw_direct(func->card, 0, 0, + SDIO_FBR_BASE(func->num) + SDIO_FBR_STD_IF, 0, &data); + if (ret) + goto out; + + data &= 0x0f; + + if (data == 0x0f) { + ret = mmc_io_rw_direct(func->card, 0, 0, + SDIO_FBR_BASE(func->num) + SDIO_FBR_STD_IF_EXT, 0, &data); + if (ret) + goto out; + } + + func->class = data; + +out: + return ret; +} + +static int sdio_init_func(struct mmc_card *card, unsigned int fn) +{ + int ret; + struct sdio_func *func; + + BUG_ON(fn > SDIO_MAX_FUNCS); + + func = sdio_alloc_func(card); + if (IS_ERR(func)) + return PTR_ERR(func); + + func->num = fn; + + ret = sdio_read_fbr(func); + if (ret) + goto fail; + + ret = sdio_read_func_cis(func); + if (ret) + goto fail; + + card->sdio_func[fn - 1] = func; + + return 0; + +fail: + /* + * It is okay to remove the function here even though we hold + * the host lock as we haven't registered the device yet. + */ + sdio_remove_func(func); + return ret; +} + +static int sdio_read_cccr(struct mmc_card *card) +{ + int ret; + int cccr_vsn; + unsigned char data; + + memset(&card->cccr, 0, sizeof(struct sdio_cccr)); + + ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_CCCR, 0, &data); + if (ret) + goto out; + + cccr_vsn = data & 0x0f; + + if (cccr_vsn > SDIO_CCCR_REV_1_20) { + printk(KERN_ERR "%s: unrecognised CCCR structure version %d\n", + mmc_hostname(card->host), cccr_vsn); + return -EINVAL; + } + + card->cccr.sdio_vsn = (data & 0xf0) >> 4; + + ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_CAPS, 0, &data); + if (ret) + goto out; + + if (data & SDIO_CCCR_CAP_SMB) + card->cccr.multi_block = 1; + if (data & SDIO_CCCR_CAP_LSC) + card->cccr.low_speed = 1; + if (data & SDIO_CCCR_CAP_4BLS) + card->cccr.wide_bus = 1; + + if (cccr_vsn >= SDIO_CCCR_REV_1_10) { + ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_POWER, 0, &data); + if (ret) + goto out; + + if (data & SDIO_POWER_SMPC) + card->cccr.high_power = 1; + } + + if (cccr_vsn >= SDIO_CCCR_REV_1_20) { + ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_SPEED, 0, &data); + if (ret) + goto out; + + if (data & SDIO_SPEED_SHS) + card->cccr.high_speed = 1; + } + +out: + return ret; +} + +static int sdio_enable_wide(struct mmc_card *card) +{ + int ret; + u8 ctrl; + + if (!(card->host->caps & MMC_CAP_4_BIT_DATA)) + return 0; + + if (card->cccr.low_speed && !card->cccr.wide_bus) + return 0; + + ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_IF, 0, &ctrl); + if (ret) + return ret; + + ctrl |= SDIO_BUS_WIDTH_4BIT; + + ret = mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_IF, ctrl, NULL); + if (ret) + return ret; + + mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4); + + return 0; +} + +/* + * Host is being removed. Free up the current card. + */ +static void mmc_sdio_remove(struct mmc_host *host) +{ + int i; + + BUG_ON(!host); + BUG_ON(!host->card); + + for (i = 0;i < host->card->sdio_funcs;i++) { + if (host->card->sdio_func[i]) { + sdio_remove_func(host->card->sdio_func[i]); + host->card->sdio_func[i] = NULL; + } + } + + mmc_remove_card(host->card); + host->card = NULL; +} + +/* + * Card detection callback from host. + */ +static void mmc_sdio_detect(struct mmc_host *host) +{ + int err; + + BUG_ON(!host); + BUG_ON(!host->card); + + mmc_claim_host(host); + + /* + * Just check if our card has been removed. + */ + err = mmc_select_card(host->card); + + mmc_release_host(host); + + if (err) { + mmc_sdio_remove(host); + + mmc_claim_host(host); + mmc_detach_bus(host); + mmc_release_host(host); + } +} + + +static const struct mmc_bus_ops mmc_sdio_ops = { + .remove = mmc_sdio_remove, + .detect = mmc_sdio_detect, +}; + + +/* + * Starting point for SDIO card init. + */ +int mmc_attach_sdio(struct mmc_host *host, u32 ocr) +{ + int err; + int i, funcs; + struct mmc_card *card; + + BUG_ON(!host); + WARN_ON(!host->claimed); + + mmc_attach_bus(host, &mmc_sdio_ops); + + /* + * Sanity check the voltages that the card claims to + * support. + */ + if (ocr & 0x7F) { + printk(KERN_WARNING "%s: card claims to support voltages " + "below the defined range. These will be ignored.\n", + mmc_hostname(host)); + 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); + + /* + * Can we support the voltage(s) of the card(s)? + */ + if (!host->ocr) { + err = -EINVAL; + goto err; + } + + /* + * 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; + } + + /* + * The number of functions on the card is encoded inside + * the ocr. + */ + funcs = (ocr & 0x70000000) >> 28; + + /* + * Allocate card structure. + */ + card = mmc_alloc_card(host); + 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 (!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; + + /* + * No support for high-speed yet, so just set + * the card's maximum speed. + */ + mmc_set_clock(host, card->cis.max_dtr); + + /* + * Switch to wider bus (if supported). + */ + err = sdio_enable_wide(card); + if (err) + goto remove; + + /* + * Initialize (but don't add) all present functions. + */ + for (i = 0;i < funcs;i++) { + err = sdio_init_func(host->card, i + 1); + if (err) + goto remove; + } + + mmc_release_host(host); + + /* + * First add the card to the driver model... + */ + err = mmc_add_card(host->card); + if (err) + goto remove_added; + + /* + * ...then the SDIO functions. + */ + for (i = 0;i < funcs;i++) { + err = sdio_add_func(host->card->sdio_func[i]); + if (err) + goto remove_added; + } + + return 0; + + +remove_added: + /* Remove without lock if the device has been added. */ + mmc_sdio_remove(host); + mmc_claim_host(host); +remove: + /* And with lock if it hasn't been added. */ + if (host->card) + mmc_sdio_remove(host); +err: + mmc_detach_bus(host); + mmc_release_host(host); + + printk(KERN_ERR "%s: error %d whilst initialising SDIO card\n", + mmc_hostname(host), err); + + return err; +} + diff --git a/drivers/mmc/core/sdio_bus.c b/drivers/mmc/core/sdio_bus.c new file mode 100644 index 00000000000..0713a8c71e5 --- /dev/null +++ b/drivers/mmc/core/sdio_bus.c @@ -0,0 +1,270 @@ +/* + * linux/drivers/mmc/core/sdio_bus.c + * + * Copyright 2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * SDIO function driver model + */ + +#include <linux/device.h> +#include <linux/err.h> + +#include <linux/mmc/card.h> +#include <linux/mmc/sdio_func.h> + +#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 \ +field##_show(struct device *dev, struct device_attribute *attr, char *buf) \ +{ \ + struct sdio_func *func; \ + \ + func = dev_to_sdio_func (dev); \ + return sprintf (buf, format_string, func->field); \ +} + +sdio_config_attr(class, "0x%02x\n"); +sdio_config_attr(vendor, "0x%04x\n"); +sdio_config_attr(device, "0x%04x\n"); + +static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sdio_func *func = dev_to_sdio_func (dev); + + return sprintf(buf, "sdio:c%02Xv%04Xd%04X\n", + func->class, func->vendor, func->device); +} + +static struct device_attribute sdio_dev_attrs[] = { + __ATTR_RO(class), + __ATTR_RO(vendor), + __ATTR_RO(device), + __ATTR_RO(modalias), + __ATTR_NULL, +}; + +static const struct sdio_device_id *sdio_match_one(struct sdio_func *func, + const struct sdio_device_id *id) +{ + if (id->class != (__u8)SDIO_ANY_ID && id->class != func->class) + return NULL; + if (id->vendor != (__u16)SDIO_ANY_ID && id->vendor != func->vendor) + return NULL; + if (id->device != (__u16)SDIO_ANY_ID && id->device != func->device) + return NULL; + return id; +} + +static const struct sdio_device_id *sdio_match_device(struct sdio_func *func, + struct sdio_driver *sdrv) +{ + const struct sdio_device_id *ids; + + ids = sdrv->id_table; + + if (ids) { + while (ids->class || ids->vendor || ids->device) { + if (sdio_match_one(func, ids)) + return ids; + ids++; + } + } + + return NULL; +} + +static int sdio_bus_match(struct device *dev, struct device_driver *drv) +{ + struct sdio_func *func = dev_to_sdio_func(dev); + struct sdio_driver *sdrv = to_sdio_driver(drv); + + if (sdio_match_device(func, sdrv)) + return 1; + + return 0; +} + +static int +sdio_bus_uevent(struct device *dev, char **envp, int num_envp, char *buf, + int buf_size) +{ + struct sdio_func *func = dev_to_sdio_func(dev); + int i = 0, length = 0; + + if (add_uevent_var(envp, num_envp, &i, + buf, buf_size, &length, + "SDIO_CLASS=%02X", func->class)) + return -ENOMEM; + + if (add_uevent_var(envp, num_envp, &i, + buf, buf_size, &length, + "SDIO_ID=%04X:%04X", func->vendor, func->device)) + return -ENOMEM; + + if (add_uevent_var(envp, num_envp, &i, + buf, buf_size, &length, + "MODALIAS=sdio:c%02Xv%04Xd%04X", + func->class, func->vendor, func->device)) + return -ENOMEM; + + envp[i] = NULL; + + return 0; +} + +static int sdio_bus_probe(struct device *dev) +{ + struct sdio_driver *drv = to_sdio_driver(dev->driver); + struct sdio_func *func = dev_to_sdio_func(dev); + const struct sdio_device_id *id; + int ret; + + id = sdio_match_device(func, drv); + if (!id) + return -ENODEV; + + /* Set the default block size so the driver is sure it's something + * sensible. */ + sdio_claim_host(func); + ret = sdio_set_block_size(func, 0); + sdio_release_host(func); + if (ret) + return ret; + + return drv->probe(func, id); +} + +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); + + drv->remove(func); + + if (func->irq_handler) { + printk(KERN_WARNING "WARNING: driver %s did not remove " + "its interrupt handler!\n", drv->name); + sdio_claim_host(func); + sdio_release_irq(func); + sdio_release_host(func); + } + + return 0; +} + +static struct bus_type sdio_bus_type = { + .name = "sdio", + .dev_attrs = sdio_dev_attrs, + .match = sdio_bus_match, + .uevent = sdio_bus_uevent, + .probe = sdio_bus_probe, + .remove = sdio_bus_remove, +}; + +int sdio_register_bus(void) +{ + return bus_register(&sdio_bus_type); +} + +void sdio_unregister_bus(void) +{ + bus_unregister(&sdio_bus_type); +} + +/** + * sdio_register_driver - register a function driver + * @drv: SDIO function driver + */ +int sdio_register_driver(struct sdio_driver *drv) +{ + drv->drv.name = drv->name; + drv->drv.bus = &sdio_bus_type; + return driver_register(&drv->drv); +} +EXPORT_SYMBOL_GPL(sdio_register_driver); + +/** + * sdio_unregister_driver - unregister a function driver + * @drv: SDIO function driver + */ +void sdio_unregister_driver(struct sdio_driver *drv) +{ + drv->drv.bus = &sdio_bus_type; + driver_unregister(&drv->drv); +} +EXPORT_SYMBOL_GPL(sdio_unregister_driver); + +static void sdio_release_func(struct device *dev) +{ + struct sdio_func *func = dev_to_sdio_func(dev); + + sdio_free_func_cis(func); + + if (func->info) + kfree(func->info); + + kfree(func); +} + +/* + * Allocate and initialise a new SDIO function structure. + */ +struct sdio_func *sdio_alloc_func(struct mmc_card *card) +{ + struct sdio_func *func; + + func = kzalloc(sizeof(struct sdio_func), GFP_KERNEL); + if (!func) + return ERR_PTR(-ENOMEM); + + func->card = card; + + device_initialize(&func->dev); + + func->dev.parent = &card->dev; + func->dev.bus = &sdio_bus_type; + func->dev.release = sdio_release_func; + + return func; +} + +/* + * Register a new SDIO function with the driver model. + */ +int sdio_add_func(struct sdio_func *func) +{ + int ret; + + snprintf(func->dev.bus_id, sizeof(func->dev.bus_id), + "%s:%d", mmc_card_id(func->card), func->num); + + ret = device_add(&func->dev); + if (ret == 0) + sdio_func_set_present(func); + + return ret; +} + +/* + * Unregister a SDIO function with the driver model, and + * (eventually) free it. + */ +void sdio_remove_func(struct sdio_func *func) +{ + if (sdio_func_present(func)) + device_del(&func->dev); + + put_device(&func->dev); +} + diff --git a/drivers/mmc/core/sdio_bus.h b/drivers/mmc/core/sdio_bus.h new file mode 100644 index 00000000000..567a76821ba --- /dev/null +++ b/drivers/mmc/core/sdio_bus.h @@ -0,0 +1,22 @@ +/* + * linux/drivers/mmc/core/sdio_bus.h + * + * Copyright 2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ +#ifndef _MMC_CORE_SDIO_BUS_H +#define _MMC_CORE_SDIO_BUS_H + +struct sdio_func *sdio_alloc_func(struct mmc_card *card); +int sdio_add_func(struct sdio_func *func); +void sdio_remove_func(struct sdio_func *func); + +int sdio_register_bus(void); +void sdio_unregister_bus(void); + +#endif + diff --git a/drivers/mmc/core/sdio_cis.c b/drivers/mmc/core/sdio_cis.c new file mode 100644 index 00000000000..d5e51b1c7b3 --- /dev/null +++ b/drivers/mmc/core/sdio_cis.c @@ -0,0 +1,346 @@ +/* + * linux/drivers/mmc/core/sdio_cis.c + * + * Author: Nicolas Pitre + * Created: June 11, 2007 + * Copyright: MontaVista Software Inc. + * + * Copyright 2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#include <linux/kernel.h> + +#include <linux/mmc/host.h> +#include <linux/mmc/card.h> +#include <linux/mmc/sdio.h> +#include <linux/mmc/sdio_func.h> + +#include "sdio_cis.h" +#include "sdio_ops.h" + +static int cistpl_vers_1(struct mmc_card *card, struct sdio_func *func, + const unsigned char *buf, unsigned size) +{ + unsigned i, nr_strings; + char **buffer, *string; + + buf += 2; + size -= 2; + + nr_strings = 0; + for (i = 0; i < size; i++) { + if (buf[i] == 0xff) + break; + if (buf[i] == 0) + nr_strings++; + } + + if (buf[i-1] != '\0') { + printk(KERN_WARNING "SDIO: ignoring broken CISTPL_VERS_1\n"); + return 0; + } + + size = i; + + buffer = kzalloc(sizeof(char*) * nr_strings + size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + string = (char*)(buffer + nr_strings); + + for (i = 0; i < nr_strings; i++) { + buffer[i] = string; + strcpy(string, buf); + string += strlen(string) + 1; + buf += strlen(buf) + 1; + } + + if (func) { + func->num_info = nr_strings; + func->info = (const char**)buffer; + } else { + card->num_info = nr_strings; + card->info = (const char**)buffer; + } + + return 0; +} + +static int cistpl_manfid(struct mmc_card *card, struct sdio_func *func, + const unsigned char *buf, unsigned size) +{ + unsigned int vendor, device; + + /* TPLMID_MANF */ + vendor = buf[0] | (buf[1] << 8); + + /* TPLMID_CARD */ + device = buf[2] | (buf[3] << 8); + + if (func) { + func->vendor = vendor; + func->device = device; + } else { + card->cis.vendor = vendor; + card->cis.device = device; + } + + return 0; +} + +static const unsigned char speed_val[16] = + { 0, 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80 }; +static const unsigned int speed_unit[8] = + { 10000, 100000, 1000000, 10000000, 0, 0, 0, 0 }; + +static int cistpl_funce_common(struct mmc_card *card, + const unsigned char *buf, unsigned size) +{ + if (size < 0x04 || buf[0] != 0) + return -EINVAL; + + /* TPLFE_FN0_BLK_SIZE */ + card->cis.blksize = buf[1] | (buf[2] << 8); + + /* TPLFE_MAX_TRAN_SPEED */ + card->cis.max_dtr = speed_val[(buf[3] >> 3) & 15] * + speed_unit[buf[3] & 7]; + + return 0; +} + +static int cistpl_funce_func(struct sdio_func *func, + const unsigned char *buf, unsigned size) +{ + unsigned vsn; + unsigned min_size; + + vsn = func->card->cccr.sdio_vsn; + min_size = (vsn == SDIO_SDIO_REV_1_00) ? 28 : 42; + + if (size < min_size || buf[0] != 1) + return -EINVAL; + + /* TPLFE_MAX_BLK_SIZE */ + func->max_blksize = buf[12] | (buf[13] << 8); + + return 0; +} + +static int cistpl_funce(struct mmc_card *card, struct sdio_func *func, + const unsigned char *buf, unsigned size) +{ + int ret; + + /* + * There should be two versions of the CISTPL_FUNCE tuple, + * one for the common CIS (function 0) and a version used by + * the individual function's CIS (1-7). Yet, the later has a + * different length depending on the SDIO spec version. + */ + if (func) + ret = cistpl_funce_func(func, buf, size); + else + ret = cistpl_funce_common(card, buf, size); + + if (ret) { + printk(KERN_ERR "%s: bad CISTPL_FUNCE size %u " + "type %u\n", mmc_hostname(card->host), size, buf[0]); + return ret; + } + + return 0; +} + +typedef int (tpl_parse_t)(struct mmc_card *, struct sdio_func *, + const unsigned char *, unsigned); + +struct cis_tpl { + unsigned char code; + unsigned char min_size; + tpl_parse_t *parse; +}; + +static const struct cis_tpl cis_tpl_list[] = { + { 0x15, 3, cistpl_vers_1 }, + { 0x20, 4, cistpl_manfid }, + { 0x21, 2, /* cistpl_funcid */ }, + { 0x22, 0, cistpl_funce }, +}; + +static int sdio_read_cis(struct mmc_card *card, struct sdio_func *func) +{ + int ret; + struct sdio_func_tuple *this, **prev; + unsigned i, ptr = 0; + + /* + * Note that this works for the common CIS (function number 0) as + * well as a function's CIS * since SDIO_CCCR_CIS and SDIO_FBR_CIS + * have the same offset. + */ + for (i = 0; i < 3; i++) { + unsigned char x, fn; + + if (func) + fn = func->num; + else + fn = 0; + + ret = mmc_io_rw_direct(card, 0, 0, + SDIO_FBR_BASE(fn) + SDIO_FBR_CIS + i, 0, &x); + if (ret) + return ret; + ptr |= x << (i * 8); + } + + if (func) + prev = &func->tuples; + else + prev = &card->tuples; + + BUG_ON(*prev); + + do { + unsigned char tpl_code, tpl_link; + + ret = mmc_io_rw_direct(card, 0, 0, ptr++, 0, &tpl_code); + if (ret) + break; + + /* 0xff means we're done */ + if (tpl_code == 0xff) + break; + + ret = mmc_io_rw_direct(card, 0, 0, ptr++, 0, &tpl_link); + if (ret) + break; + + this = kmalloc(sizeof(*this) + tpl_link, GFP_KERNEL); + if (!this) + return -ENOMEM; + + for (i = 0; i < tpl_link; i++) { + ret = mmc_io_rw_direct(card, 0, 0, + ptr + i, 0, &this->data[i]); + if (ret) + break; + } + if (ret) { + kfree(this); + break; + } + + 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 { + 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", + mmc_hostname(card->host), + tpl_code, tpl_link, tpl->min_size); + ret = -EINVAL; + } else if (tpl->parse) { + ret = tpl->parse(card, func, + this->data, tpl_link); + } + kfree(this); + } + + ptr += tpl_link; + } while (!ret); + + /* + * Link in all unknown tuples found in the common CIS so that + * drivers don't have to go digging in two places. + */ + if (func) + *prev = card->tuples; + + return ret; +} + +int sdio_read_common_cis(struct mmc_card *card) +{ + return sdio_read_cis(card, NULL); +} + +void sdio_free_common_cis(struct mmc_card *card) +{ + struct sdio_func_tuple *tuple, *victim; + + tuple = card->tuples; + + while (tuple) { + victim = tuple; + tuple = tuple->next; + kfree(victim); + } + + card->tuples = NULL; +} + +int sdio_read_func_cis(struct sdio_func *func) +{ + int ret; + + ret = sdio_read_cis(func->card, func); + if (ret) + return ret; + + /* + * Since we've linked to tuples in the card structure, + * we must make sure we have a reference to it. + */ + get_device(&func->card->dev); + + /* + * Vendor/device id is optional for function CIS, so + * copy it from the card structure as needed. + */ + if (func->vendor == 0) { + func->vendor = func->card->cis.vendor; + func->device = func->card->cis.device; + } + + return 0; +} + +void sdio_free_func_cis(struct sdio_func *func) +{ + struct sdio_func_tuple *tuple, *victim; + + tuple = func->tuples; + + while (tuple && tuple != func->card->tuples) { + victim = tuple; + tuple = tuple->next; + kfree(victim); + } + + func->tuples = NULL; + + /* + * We have now removed the link to the tuples in the + * card structure, so remove the reference. + */ + put_device(&func->card->dev); +} + diff --git a/drivers/mmc/core/sdio_cis.h b/drivers/mmc/core/sdio_cis.h new file mode 100644 index 00000000000..4d903c2e425 --- /dev/null +++ b/drivers/mmc/core/sdio_cis.h @@ -0,0 +1,23 @@ +/* + * linux/drivers/mmc/core/sdio_cis.h + * + * Author: Nicolas Pitre + * Created: June 11, 2007 + * Copyright: MontaVista Software Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#ifndef _MMC_SDIO_CIS_H +#define _MMC_SDIO_CIS_H + +int sdio_read_common_cis(struct mmc_card *card); +void sdio_free_common_cis(struct mmc_card *card); + +int sdio_read_func_cis(struct sdio_func *func); +void sdio_free_func_cis(struct sdio_func *func); + +#endif diff --git a/drivers/mmc/core/sdio_io.c b/drivers/mmc/core/sdio_io.c new file mode 100644 index 00000000000..625b92ce9ce --- /dev/null +++ b/drivers/mmc/core/sdio_io.c @@ -0,0 +1,548 @@ +/* + * linux/drivers/mmc/core/sdio_io.c + * + * Copyright 2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#include <linux/mmc/host.h> +#include <linux/mmc/card.h> +#include <linux/mmc/sdio.h> +#include <linux/mmc/sdio_func.h> + +#include "sdio_ops.h" + +/** + * sdio_claim_host - exclusively claim a bus for a certain SDIO function + * @func: SDIO function that will be accessed + * + * Claim a bus for a set of operations. The SDIO function given + * is used to figure out which bus is relevant. + */ +void sdio_claim_host(struct sdio_func *func) +{ + BUG_ON(!func); + BUG_ON(!func->card); + + mmc_claim_host(func->card->host); +} +EXPORT_SYMBOL_GPL(sdio_claim_host); + +/** + * sdio_release_host - release a bus for a certain SDIO function + * @func: SDIO function that was accessed + * + * Release a bus, allowing others to claim the bus for their + * operations. + */ +void sdio_release_host(struct sdio_func *func) +{ + BUG_ON(!func); + BUG_ON(!func->card); + + mmc_release_host(func->card->host); +} +EXPORT_SYMBOL_GPL(sdio_release_host); + +/** + * sdio_enable_func - enables a SDIO function for usage + * @func: SDIO function to enable + * + * Powers up and activates a SDIO function so that register + * access is possible. + */ +int sdio_enable_func(struct sdio_func *func) +{ + int ret; + unsigned char reg; + unsigned long timeout; + + BUG_ON(!func); + BUG_ON(!func->card); + + pr_debug("SDIO: Enabling device %s...\n", sdio_func_id(func)); + + ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IOEx, 0, ®); + if (ret) + goto err; + + reg |= 1 << func->num; + + ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IOEx, reg, NULL); + if (ret) + goto err; + + /* + * FIXME: This should timeout based on information in the CIS, + * but we don't have card to parse that yet. + */ + timeout = jiffies + HZ; + + while (1) { + ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IORx, 0, ®); + if (ret) + goto err; + if (reg & (1 << func->num)) + break; + ret = -ETIME; + if (time_after(jiffies, timeout)) + goto err; + } + + pr_debug("SDIO: Enabled device %s\n", sdio_func_id(func)); + + return 0; + +err: + pr_debug("SDIO: Failed to enable device %s\n", sdio_func_id(func)); + return ret; +} +EXPORT_SYMBOL_GPL(sdio_enable_func); + +/** + * sdio_disable_func - disable a SDIO function + * @func: SDIO function to disable + * + * Powers down and deactivates a SDIO function. Register access + * to this function will fail until the function is reenabled. + */ +int sdio_disable_func(struct sdio_func *func) +{ + int ret; + unsigned char reg; + + BUG_ON(!func); + BUG_ON(!func->card); + + pr_debug("SDIO: Disabling device %s...\n", sdio_func_id(func)); + + ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IOEx, 0, ®); + if (ret) + goto err; + + reg &= ~(1 << func->num); + + ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IOEx, reg, NULL); + if (ret) + goto err; + + pr_debug("SDIO: Disabled device %s\n", sdio_func_id(func)); + + return 0; + +err: + pr_debug("SDIO: Failed to disable device %s\n", sdio_func_id(func)); + return -EIO; +} +EXPORT_SYMBOL_GPL(sdio_disable_func); + +/** + * sdio_set_block_size - set the block size of an SDIO function + * @func: SDIO function to change + * @blksz: new block size or 0 to use the default. + * + * The default block size is the largest supported by both the function + * and the host, with a maximum of 512 to ensure that arbitrarily sized + * data transfer use the optimal (least) number of commands. + * + * A driver may call this to override the default block size set by the + * core. This can be used to set a block size greater than the maximum + * that reported by the card; it is the driver's responsibility to ensure + * it uses a value that the card supports. + * + * Returns 0 on success, -EINVAL if the host does not support the + * requested block size, or -EIO (etc.) if one of the resultant FBR block + * size register writes failed. + * + */ +int sdio_set_block_size(struct sdio_func *func, unsigned blksz) +{ + int ret; + + if (blksz > func->card->host->max_blk_size) + return -EINVAL; + + if (blksz == 0) { + blksz = min(min( + func->max_blksize, + func->card->host->max_blk_size), + 512u); + } + + ret = mmc_io_rw_direct(func->card, 1, 0, + SDIO_FBR_BASE(func->num) + SDIO_FBR_BLKSIZE, + blksz & 0xff, NULL); + if (ret) + return ret; + ret = mmc_io_rw_direct(func->card, 1, 0, + SDIO_FBR_BASE(func->num) + SDIO_FBR_BLKSIZE + 1, + (blksz >> 8) & 0xff, NULL); + if (ret) + return ret; + func->cur_blksize = blksz; + return 0; +} + +EXPORT_SYMBOL_GPL(sdio_set_block_size); + +/* Split an arbitrarily sized data transfer into several + * IO_RW_EXTENDED commands. */ +static int sdio_io_rw_ext_helper(struct sdio_func *func, int write, + unsigned addr, int incr_addr, u8 *buf, unsigned size) +{ + unsigned remainder = size; + unsigned max_blocks; + int ret; + + /* Do the bulk of the transfer using block mode (if supported). */ + if (func->card->cccr.multi_block) { + /* Blocks per command is limited by host count, host transfer + * size (we only use a single sg entry) and the maximum for + * IO_RW_EXTENDED of 511 blocks. */ + max_blocks = min(min( + func->card->host->max_blk_count, + func->card->host->max_seg_size / func->cur_blksize), + 511u); + + while (remainder > func->cur_blksize) { + unsigned blocks; + + blocks = remainder / func->cur_blksize; + if (blocks > max_blocks) + blocks = max_blocks; + size = blocks * func->cur_blksize; + + ret = mmc_io_rw_extended(func->card, write, + func->num, addr, incr_addr, buf, + blocks, func->cur_blksize); + if (ret) + return ret; + + remainder -= size; + buf += size; + if (incr_addr) + addr += size; + } + } + + /* Write the remainder using byte mode. */ + while (remainder > 0) { + size = remainder; + if (size > func->cur_blksize) + size = func->cur_blksize; + if (size > 512) + size = 512; /* maximum size for byte mode */ + + ret = mmc_io_rw_extended(func->card, write, func->num, addr, + incr_addr, buf, 1, size); + if (ret) + return ret; + + remainder -= size; + buf += size; + if (incr_addr) + addr += size; + } + return 0; +} + +/** + * sdio_readb - read a single byte from a SDIO function + * @func: SDIO function to access + * @addr: address to read + * @err_ret: optional status value from transfer + * + * Reads a single byte from the address space of a given SDIO + * function. If there is a problem reading the address, 0xff + * is returned and @err_ret will contain the error code. + */ +unsigned char sdio_readb(struct sdio_func *func, unsigned int addr, + int *err_ret) +{ + int ret; + unsigned char val; + + BUG_ON(!func); + + if (err_ret) + *err_ret = 0; + + ret = mmc_io_rw_direct(func->card, 0, func->num, addr, 0, &val); + if (ret) { + if (err_ret) + *err_ret = ret; + return 0xFF; + } + + return val; +} +EXPORT_SYMBOL_GPL(sdio_readb); + +/** + * sdio_writeb - write a single byte to a SDIO function + * @func: SDIO function to access + * @b: byte to write + * @addr: address to write to + * @err_ret: optional status value from transfer + * + * Writes a single byte to the address space of a given SDIO + * function. @err_ret will contain the status of the actual + * transfer. + */ +void sdio_writeb(struct sdio_func *func, unsigned char b, unsigned int addr, + int *err_ret) +{ + int ret; + + BUG_ON(!func); + + ret = mmc_io_rw_direct(func->card, 1, func->num, addr, b, NULL); + if (err_ret) + *err_ret = ret; +} +EXPORT_SYMBOL_GPL(sdio_writeb); + +/** + * sdio_memcpy_fromio - read a chunk of memory from a SDIO function + * @func: SDIO function to access + * @dst: buffer to store the data + * @addr: address to begin reading from + * @count: number of bytes to read + * + * Reads from the address space of a given SDIO function. Return + * value indicates if the transfer succeeded or not. + */ +int sdio_memcpy_fromio(struct sdio_func *func, void *dst, + unsigned int addr, int count) +{ + return sdio_io_rw_ext_helper(func, 0, addr, 1, dst, count); +} +EXPORT_SYMBOL_GPL(sdio_memcpy_fromio); + +/** + * sdio_memcpy_toio - write a chunk of memory to a SDIO function + * @func: SDIO function to access + * @addr: address to start writing to + * @src: buffer that contains the data to write + * @count: number of bytes to write + * + * Writes to the address space of a given SDIO function. Return + * value indicates if the transfer succeeded or not. + */ +int sdio_memcpy_toio(struct sdio_func *func, unsigned int addr, + void *src, int count) +{ + return sdio_io_rw_ext_helper(func, 1, addr, 1, src, count); +} +EXPORT_SYMBOL_GPL(sdio_memcpy_toio); + +/** + * sdio_readsb - read from a FIFO on a SDIO function + * @func: SDIO function to access + * @dst: buffer to store the data + * @addr: address of (single byte) FIFO + * @count: number of bytes to read + * + * Reads from the specified FIFO of a given SDIO function. Return + * value indicates if the transfer succeeded or not. + */ +int sdio_readsb(struct sdio_func *func, void *dst, unsigned int addr, + int count) +{ + return sdio_io_rw_ext_helper(func, 0, addr, 0, dst, count); +} + +EXPORT_SYMBOL_GPL(sdio_readsb); + +/** + * sdio_writesb - write to a FIFO of a SDIO function + * @func: SDIO function to access + * @addr: address of (single byte) FIFO + * @src: buffer that contains the data to write + * @count: number of bytes to write + * + * Writes to the specified FIFO of a given SDIO function. Return + * value indicates if the transfer succeeded or not. + */ +int sdio_writesb(struct sdio_func *func, unsigned int addr, void *src, + int count) +{ + return sdio_io_rw_ext_helper(func, 1, addr, 0, src, count); +} +EXPORT_SYMBOL_GPL(sdio_writesb); + +/** + * sdio_readw - read a 16 bit integer from a SDIO function + * @func: SDIO function to access + * @addr: address to read + * @err_ret: optional status value from transfer + * + * Reads a 16 bit integer from the address space of a given SDIO + * function. If there is a problem reading the address, 0xffff + * is returned and @err_ret will contain the error code. + */ +unsigned short sdio_readw(struct sdio_func *func, unsigned int addr, + int *err_ret) +{ + int ret; + + if (err_ret) + *err_ret = 0; + + ret = sdio_memcpy_fromio(func, func->tmpbuf, addr, 2); + if (ret) { + if (err_ret) + *err_ret = ret; + return 0xFFFF; + } + + return le16_to_cpu(*(u16*)func->tmpbuf); +} +EXPORT_SYMBOL_GPL(sdio_readw); + +/** + * sdio_writew - write a 16 bit integer to a SDIO function + * @func: SDIO function to access + * @b: integer to write + * @addr: address to write to + * @err_ret: optional status value from transfer + * + * Writes a 16 bit integer to the address space of a given SDIO + * function. @err_ret will contain the status of the actual + * transfer. + */ +void sdio_writew(struct sdio_func *func, unsigned short b, unsigned int addr, + int *err_ret) +{ + int ret; + + *(u16*)func->tmpbuf = cpu_to_le16(b); + + ret = sdio_memcpy_toio(func, addr, func->tmpbuf, 2); + if (err_ret) + *err_ret = ret; +} +EXPORT_SYMBOL_GPL(sdio_writew); + +/** + * sdio_readl - read a 32 bit integer from a SDIO function + * @func: SDIO function to access + * @addr: address to read + * @err_ret: optional status value from transfer + * + * Reads a 32 bit integer from the address space of a given SDIO + * function. If there is a problem reading the address, + * 0xffffffff is returned and @err_ret will contain the error + * code. + */ +unsigned long sdio_readl(struct sdio_func *func, unsigned int addr, + int *err_ret) +{ + int ret; + + if (err_ret) + *err_ret = 0; + + ret = sdio_memcpy_fromio(func, func->tmpbuf, addr, 4); + if (ret) { + if (err_ret) + *err_ret = ret; + return 0xFFFFFFFF; + } + + return le32_to_cpu(*(u32*)func->tmpbuf); +} +EXPORT_SYMBOL_GPL(sdio_readl); + +/** + * sdio_writel - write a 32 bit integer to a SDIO function + * @func: SDIO function to access + * @b: integer to write + * @addr: address to write to + * @err_ret: optional status value from transfer + * + * Writes a 32 bit integer to the address space of a given SDIO + * function. @err_ret will contain the status of the actual + * transfer. + */ +void sdio_writel(struct sdio_func *func, unsigned long b, unsigned int addr, + int *err_ret) +{ + int ret; + + *(u32*)func->tmpbuf = cpu_to_le32(b); + + ret = sdio_memcpy_toio(func, addr, func->tmpbuf, 4); + if (err_ret) + *err_ret = ret; +} +EXPORT_SYMBOL_GPL(sdio_writel); + +/** + * sdio_f0_readb - read a single byte from SDIO function 0 + * @func: an SDIO function of the card + * @addr: address to read + * @err_ret: optional status value from transfer + * + * Reads a single byte from the address space of SDIO function 0. + * If there is a problem reading the address, 0xff is returned + * and @err_ret will contain the error code. + */ +unsigned char sdio_f0_readb(struct sdio_func *func, unsigned int addr, + int *err_ret) +{ + int ret; + unsigned char val; + + BUG_ON(!func); + + if (err_ret) + *err_ret = 0; + + ret = mmc_io_rw_direct(func->card, 0, 0, addr, 0, &val); + if (ret) { + if (err_ret) + *err_ret = ret; + return 0xFF; + } + + return val; +} +EXPORT_SYMBOL_GPL(sdio_f0_readb); + +/** + * sdio_f0_writeb - write a single byte to SDIO function 0 + * @func: an SDIO function of the card + * @b: byte to write + * @addr: address to write to + * @err_ret: optional status value from transfer + * + * Writes a single byte to the address space of SDIO function 0. + * @err_ret will contain the status of the actual transfer. + * + * Only writes to the vendor specific CCCR registers (0xF0 - + * 0xFF) are permiited; @err_ret will be set to -EINVAL for * + * writes outside this range. + */ +void sdio_f0_writeb(struct sdio_func *func, unsigned char b, unsigned int addr, + int *err_ret) +{ + int ret; + + BUG_ON(!func); + + if (addr < 0xF0 || addr > 0xFF) { + if (err_ret) + *err_ret = -EINVAL; + return; + } + + ret = mmc_io_rw_direct(func->card, 1, 0, addr, b, NULL); + if (err_ret) + *err_ret = ret; +} +EXPORT_SYMBOL_GPL(sdio_f0_writeb); diff --git a/drivers/mmc/core/sdio_irq.c b/drivers/mmc/core/sdio_irq.c new file mode 100644 index 00000000000..3bd3021f5e8 --- /dev/null +++ b/drivers/mmc/core/sdio_irq.c @@ -0,0 +1,267 @@ +/* + * linux/drivers/mmc/core/sdio_irq.c + * + * Author: Nicolas Pitre + * Created: June 18, 2007 + * Copyright: MontaVista Software Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/kthread.h> +#include <linux/wait.h> +#include <linux/delay.h> + +#include <linux/mmc/core.h> +#include <linux/mmc/host.h> +#include <linux/mmc/card.h> +#include <linux/mmc/sdio.h> +#include <linux/mmc/sdio_func.h> + +#include "sdio_ops.h" + +static int process_sdio_pending_irqs(struct mmc_card *card) +{ + int i, ret, count; + unsigned char pending; + + ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_INTx, 0, &pending); + if (ret) { + printk(KERN_DEBUG "%s: error %d reading SDIO_CCCR_INTx\n", + mmc_card_id(card), ret); + return ret; + } + + count = 0; + for (i = 1; i <= 7; i++) { + if (pending & (1 << i)) { + struct sdio_func *func = card->sdio_func[i - 1]; + if (!func) { + printk(KERN_WARNING "%s: pending IRQ for " + "non-existant function\n", + mmc_card_id(card)); + ret = -EINVAL; + } else if (func->irq_handler) { + func->irq_handler(func); + count++; + } else { + printk(KERN_WARNING "%s: pending IRQ with no handler\n", + sdio_func_id(func)); + ret = -EINVAL; + } + } + } + + if (count) + return count; + + return ret; +} + +static int sdio_irq_thread(void *_host) +{ + struct mmc_host *host = _host; + struct sched_param param = { .sched_priority = 1 }; + unsigned long period, idle_period; + int ret; + + sched_setscheduler(current, SCHED_FIFO, ¶m); + + /* + * We want to allow for SDIO cards to work even on non SDIO + * aware hosts. One thing that non SDIO host cannot do is + * asynchronous notification of pending SDIO card interrupts + * hence we poll for them in that case. + */ + idle_period = msecs_to_jiffies(10); + period = (host->caps & MMC_CAP_SDIO_IRQ) ? + MAX_SCHEDULE_TIMEOUT : idle_period; + + pr_debug("%s: IRQ thread started (poll period = %lu jiffies)\n", + mmc_hostname(host), period); + + do { + /* + * We claim the host here on drivers behalf for a couple + * reasons: + * + * 1) it is already needed to retrieve the CCCR_INTx; + * 2) we want the driver(s) to clear the IRQ condition ASAP; + * 3) we need to control the abort condition locally. + * + * Just like traditional hard IRQ handlers, we expect SDIO + * IRQ handlers to be quick and to the point, so that the + * holding of the host lock does not cover too much work + * that doesn't require that lock to be held. + */ + ret = __mmc_claim_host(host, &host->sdio_irq_thread_abort); + if (ret) + break; + ret = process_sdio_pending_irqs(host->card); + mmc_release_host(host); + + /* + * Give other threads a chance to run in the presence of + * errors. FIXME: determine if due to card removal and + * possibly exit this thread if so. + */ + if (ret < 0) + ssleep(1); + + /* + * Adaptive polling frequency based on the assumption + * that an interrupt will be closely followed by more. + * This has a substantial benefit for network devices. + */ + if (!(host->caps & MMC_CAP_SDIO_IRQ)) { + if (ret > 0) + period /= 2; + else { + period++; + if (period > idle_period) + period = idle_period; + } + } + + set_task_state(current, TASK_INTERRUPTIBLE); + if (host->caps & MMC_CAP_SDIO_IRQ) + host->ops->enable_sdio_irq(host, 1); + if (!kthread_should_stop()) + schedule_timeout(period); + set_task_state(current, TASK_RUNNING); + } while (!kthread_should_stop()); + + if (host->caps & MMC_CAP_SDIO_IRQ) + host->ops->enable_sdio_irq(host, 0); + + pr_debug("%s: IRQ thread exiting with code %d\n", + mmc_hostname(host), ret); + + return ret; +} + +static int sdio_card_irq_get(struct mmc_card *card) +{ + struct mmc_host *host = card->host; + + WARN_ON(!host->claimed); + + if (!host->sdio_irqs++) { + atomic_set(&host->sdio_irq_thread_abort, 0); + host->sdio_irq_thread = + kthread_run(sdio_irq_thread, host, "ksdiorqd"); + if (IS_ERR(host->sdio_irq_thread)) { + int err = PTR_ERR(host->sdio_irq_thread); + host->sdio_irqs--; + return err; + } + } + + return 0; +} + +static int sdio_card_irq_put(struct mmc_card *card) +{ + struct mmc_host *host = card->host; + + WARN_ON(!host->claimed); + BUG_ON(host->sdio_irqs < 1); + + if (!--host->sdio_irqs) { + atomic_set(&host->sdio_irq_thread_abort, 1); + kthread_stop(host->sdio_irq_thread); + } + + return 0; +} + +/** + * sdio_claim_irq - claim the IRQ for a SDIO function + * @func: SDIO function + * @handler: IRQ handler callback + * + * Claim and activate the IRQ for the given SDIO function. The provided + * handler will be called when that IRQ is asserted. The host is always + * claimed already when the handler is called so the handler must not + * call sdio_claim_host() nor sdio_release_host(). + */ +int sdio_claim_irq(struct sdio_func *func, sdio_irq_handler_t *handler) +{ + int ret; + unsigned char reg; + + BUG_ON(!func); + BUG_ON(!func->card); + + pr_debug("SDIO: Enabling IRQ for %s...\n", sdio_func_id(func)); + + if (func->irq_handler) { + pr_debug("SDIO: IRQ for %s already in use.\n", sdio_func_id(func)); + return -EBUSY; + } + + ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IENx, 0, ®); + if (ret) + return ret; + + reg |= 1 << func->num; + + reg |= 1; /* Master interrupt enable */ + + ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, reg, NULL); + if (ret) + return ret; + + func->irq_handler = handler; + ret = sdio_card_irq_get(func->card); + if (ret) + func->irq_handler = NULL; + + return ret; +} +EXPORT_SYMBOL_GPL(sdio_claim_irq); + +/** + * sdio_release_irq - release the IRQ for a SDIO function + * @func: SDIO function + * + * Disable and release the IRQ for the given SDIO function. + */ +int sdio_release_irq(struct sdio_func *func) +{ + int ret; + unsigned char reg; + + BUG_ON(!func); + BUG_ON(!func->card); + + pr_debug("SDIO: Disabling IRQ for %s...\n", sdio_func_id(func)); + + if (func->irq_handler) { + func->irq_handler = NULL; + sdio_card_irq_put(func->card); + } + + ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IENx, 0, ®); + if (ret) + return ret; + + reg &= ~(1 << func->num); + + /* Disable master interrupt with the last function interrupt */ + if (!(reg & 0xFE)) + reg = 0; + + ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, reg, NULL); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(sdio_release_irq); + diff --git a/drivers/mmc/core/sdio_ops.c b/drivers/mmc/core/sdio_ops.c new file mode 100644 index 00000000000..4d289b27503 --- /dev/null +++ b/drivers/mmc/core/sdio_ops.c @@ -0,0 +1,176 @@ +/* + * linux/drivers/mmc/sdio_ops.c + * + * Copyright 2006-2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#include <asm/scatterlist.h> +#include <linux/scatterlist.h> + +#include <linux/mmc/host.h> +#include <linux/mmc/card.h> +#include <linux/mmc/mmc.h> +#include <linux/mmc/sdio.h> + +#include "core.h" + +int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr) +{ + struct mmc_command cmd; + int i, err = 0; + + BUG_ON(!host); + + memset(&cmd, 0, sizeof(struct mmc_command)); + + cmd.opcode = SD_IO_SEND_OP_COND; + cmd.arg = ocr; + cmd.flags = MMC_RSP_SPI_R4 | MMC_RSP_R4 | MMC_CMD_BCR; + + for (i = 100; i; i--) { + err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES); + if (err) + break; + + /* if we're just probing, do a single pass */ + if (ocr == 0) + break; + + /* otherwise wait until reset completes */ + if (mmc_host_is_spi(host)) { + /* + * Both R1_SPI_IDLE and MMC_CARD_BUSY indicate + * an initialized card under SPI, but some cards + * (Marvell's) only behave when looking at this + * one. + */ + if (cmd.resp[1] & MMC_CARD_BUSY) + break; + } else { + if (cmd.resp[0] & MMC_CARD_BUSY) + break; + } + + err = -ETIMEDOUT; + + mmc_delay(10); + } + + if (rocr) + *rocr = cmd.resp[mmc_host_is_spi(host) ? 1 : 0]; + + return err; +} + +int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn, + unsigned addr, u8 in, u8* out) +{ + struct mmc_command cmd; + int err; + + BUG_ON(!card); + BUG_ON(fn > 7); + + memset(&cmd, 0, sizeof(struct mmc_command)); + + cmd.opcode = SD_IO_RW_DIRECT; + cmd.arg = write ? 0x80000000 : 0x00000000; + cmd.arg |= fn << 28; + cmd.arg |= (write && out) ? 0x08000000 : 0x00000000; + cmd.arg |= addr << 9; + cmd.arg |= in; + cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_AC; + + err = mmc_wait_for_cmd(card->host, &cmd, 0); + if (err) + return err; + + if (mmc_host_is_spi(card->host)) { + /* host driver already reported errors */ + } else { + if (cmd.resp[0] & R5_ERROR) + return -EIO; + if (cmd.resp[0] & R5_FUNCTION_NUMBER) + return -EINVAL; + if (cmd.resp[0] & R5_OUT_OF_RANGE) + return -ERANGE; + } + + if (out) { + if (mmc_host_is_spi(card->host)) + *out = (cmd.resp[0] >> 8) & 0xFF; + else + *out = cmd.resp[0] & 0xFF; + } + + return 0; +} + +int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn, + unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz) +{ + struct mmc_request mrq; + struct mmc_command cmd; + struct mmc_data data; + struct scatterlist sg; + + BUG_ON(!card); + BUG_ON(fn > 7); + BUG_ON(blocks == 1 && blksz > 512); + WARN_ON(blocks == 0); + WARN_ON(blksz == 0); + + memset(&mrq, 0, sizeof(struct mmc_request)); + memset(&cmd, 0, sizeof(struct mmc_command)); + memset(&data, 0, sizeof(struct mmc_data)); + + mrq.cmd = &cmd; + mrq.data = &data; + + cmd.opcode = SD_IO_RW_EXTENDED; + cmd.arg = write ? 0x80000000 : 0x00000000; + cmd.arg |= fn << 28; + cmd.arg |= incr_addr ? 0x04000000 : 0x00000000; + cmd.arg |= addr << 9; + if (blocks == 1 && blksz <= 512) + cmd.arg |= (blksz == 512) ? 0 : blksz; /* byte mode */ + else + cmd.arg |= 0x08000000 | blocks; /* block mode */ + cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC; + + data.blksz = blksz; + data.blocks = blocks; + data.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ; + data.sg = &sg; + data.sg_len = 1; + + sg_init_one(&sg, buf, blksz * blocks); + + mmc_set_data_timeout(&data, card); + + mmc_wait_for_req(card->host, &mrq); + + if (cmd.error) + return cmd.error; + if (data.error) + return data.error; + + if (mmc_host_is_spi(card->host)) { + /* host driver already reported errors */ + } else { + if (cmd.resp[0] & R5_ERROR) + return -EIO; + if (cmd.resp[0] & R5_FUNCTION_NUMBER) + return -EINVAL; + if (cmd.resp[0] & R5_OUT_OF_RANGE) + return -ERANGE; + } + + return 0; +} + diff --git a/drivers/mmc/core/sdio_ops.h b/drivers/mmc/core/sdio_ops.h new file mode 100644 index 00000000000..e2e74b0d17d --- /dev/null +++ b/drivers/mmc/core/sdio_ops.h @@ -0,0 +1,22 @@ +/* + * linux/drivers/mmc/sdio_ops.c + * + * Copyright 2006-2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#ifndef _MMC_SDIO_OPS_H +#define _MMC_SDIO_OPS_H + +int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr); +int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn, + unsigned addr, u8 in, u8* out); +int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn, + unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz); + +#endif + |