summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/mmc/card/mmc_test.c10
-rw-r--r--drivers/mmc/core/Kconfig12
-rw-r--r--drivers/mmc/core/Makefile1
-rw-r--r--drivers/mmc/core/bus.c12
-rw-r--r--drivers/mmc/core/core.c20
-rw-r--r--drivers/mmc/core/lock.c193
-rw-r--r--drivers/mmc/core/lock.h41
-rw-r--r--drivers/mmc/core/mmc.c16
-rw-r--r--drivers/mmc/core/mmc_ops.c117
-rw-r--r--drivers/mmc/core/mmc_ops.h3
-rw-r--r--drivers/mmc/core/sd.c20
-rw-r--r--drivers/mmc/host/Kconfig18
-rw-r--r--drivers/mmc/host/Makefile1
-rw-r--r--drivers/mmc/host/at91_mci.c2
-rw-r--r--drivers/mmc/host/imxmmc.c9
-rw-r--r--drivers/mmc/host/mmc_spi.c28
-rw-r--r--drivers/mmc/host/pxamci.c9
-rw-r--r--drivers/mmc/host/sdhci-pci.c699
-rw-r--r--drivers/mmc/host/sdhci.c541
-rw-r--r--drivers/mmc/host/sdhci.h79
-rw-r--r--include/linux/mmc/card.h3
-rw-r--r--include/linux/mmc/host.h11
-rw-r--r--include/linux/mmc/mmc.h9
-rw-r--r--include/linux/pci_ids.h1
-rw-r--r--include/linux/spi/mmc_spi.h9
25 files changed, 1406 insertions, 458 deletions
diff --git a/drivers/mmc/card/mmc_test.c b/drivers/mmc/card/mmc_test.c
index ffadee549a41..371d922d9ee2 100644
--- a/drivers/mmc/card/mmc_test.c
+++ b/drivers/mmc/card/mmc_test.c
@@ -743,7 +743,7 @@ static const struct mmc_test_case mmc_test_cases[] = {
static struct mutex mmc_test_lock;
-static void mmc_test_run(struct mmc_test_card *test)
+static void mmc_test_run(struct mmc_test_card *test, int testcase)
{
int i, ret;
@@ -753,6 +753,9 @@ static void mmc_test_run(struct mmc_test_card *test)
mmc_claim_host(test->card->host);
for (i = 0;i < ARRAY_SIZE(mmc_test_cases);i++) {
+ if (testcase && ((i + 1) != testcase))
+ continue;
+
printk(KERN_INFO "%s: Test case %d. %s...\n",
mmc_hostname(test->card->host), i + 1,
mmc_test_cases[i].name);
@@ -824,9 +827,12 @@ static ssize_t mmc_test_store(struct device *dev,
{
struct mmc_card *card;
struct mmc_test_card *test;
+ int testcase;
card = container_of(dev, struct mmc_card, dev);
+ testcase = simple_strtol(buf, NULL, 10);
+
test = kzalloc(sizeof(struct mmc_test_card), GFP_KERNEL);
if (!test)
return -ENOMEM;
@@ -836,7 +842,7 @@ static ssize_t mmc_test_store(struct device *dev,
test->buffer = kzalloc(BUFFER_SIZE, GFP_KERNEL);
if (test->buffer) {
mutex_lock(&mmc_test_lock);
- mmc_test_run(test);
+ mmc_test_run(test, testcase);
mutex_unlock(&mmc_test_lock);
}
diff --git a/drivers/mmc/core/Kconfig b/drivers/mmc/core/Kconfig
index ab37a6d9d32a..f0ae75e64a48 100644
--- a/drivers/mmc/core/Kconfig
+++ b/drivers/mmc/core/Kconfig
@@ -14,3 +14,15 @@ config MMC_UNSAFE_RESUME
This option is usually just for embedded systems which use
a MMC/SD card for rootfs. Most people should say N here.
+config MMC_PASSWORDS
+ boolean "MMC card lock/unlock passwords (EXPERIMENTAL)"
+ depends on EXPERIMENTAL
+ select KEYS
+ help
+ Say Y here to enable the use of passwords to lock and unlock
+ MMC cards. This uses the access key retention support, using
+ request_key to look up the key associated with each card.
+
+ For example, if you have an MMC card that was locked using
+ Symbian OS on your cell phone, you won't be able to read it
+ on Linux without this support.
diff --git a/drivers/mmc/core/Makefile b/drivers/mmc/core/Makefile
index 19a1a254a0c5..7f91545a9464 100644
--- a/drivers/mmc/core/Makefile
+++ b/drivers/mmc/core/Makefile
@@ -11,4 +11,5 @@ mmc_core-y := core.o bus.o host.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
+mmc_core-$(CONFIG_MMC_PASSWORDS) += lock.o
diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c
index fd95b18e988b..f36edd0dd9fb 100644
--- a/drivers/mmc/core/bus.c
+++ b/drivers/mmc/core/bus.c
@@ -50,9 +50,21 @@ static struct device_attribute mmc_dev_attrs[] = {
* This currently matches any MMC driver to any MMC card - drivers
* themselves make the decision whether to drive this card in their
* probe method.
+ *
+ * We also fail for all locked cards; drivers expect to be able to do block
+ * I/O still on probe(), which is not possible while the card is locked.
+ * Device probing must be triggered sometime later to make the card available
+ * to the block driver.
*/
static int mmc_bus_match(struct device *dev, struct device_driver *drv)
{
+ struct mmc_card *card = dev_to_mmc_card(dev);
+
+ if (mmc_card_locked(card)) {
+ dev_dbg(&card->dev, "card is locked; binding is deferred\n");
+ return 0;
+ }
+
return 1;
}
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index 01ced4c5a61d..0d0fdeea3371 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -30,6 +30,7 @@
#include "bus.h"
#include "host.h"
#include "sdio_bus.h"
+#include "lock.h"
#include "mmc_ops.h"
#include "sd_ops.h"
@@ -638,6 +639,9 @@ void mmc_rescan(struct work_struct *work)
*/
mmc_bus_put(host);
+ if (host->ops->get_cd && host->ops->get_cd(host) == 0)
+ goto out;
+
mmc_claim_host(host);
mmc_power_up(host);
@@ -652,7 +656,7 @@ void mmc_rescan(struct work_struct *work)
if (!err) {
if (mmc_attach_sdio(host, ocr))
mmc_power_off(host);
- return;
+ goto out;
}
/*
@@ -662,7 +666,7 @@ void mmc_rescan(struct work_struct *work)
if (!err) {
if (mmc_attach_sd(host, ocr))
mmc_power_off(host);
- return;
+ goto out;
}
/*
@@ -672,7 +676,7 @@ void mmc_rescan(struct work_struct *work)
if (!err) {
if (mmc_attach_mmc(host, ocr))
mmc_power_off(host);
- return;
+ goto out;
}
mmc_release_host(host);
@@ -683,6 +687,9 @@ void mmc_rescan(struct work_struct *work)
mmc_bus_put(host);
}
+out:
+ if (host->caps & MMC_CAP_NEEDS_POLL)
+ mmc_schedule_delayed_work(&host->detect, HZ);
}
void mmc_start_host(struct mmc_host *host)
@@ -798,8 +805,14 @@ static int __init mmc_init(void)
if (ret)
goto unregister_host_class;
+ ret = mmc_register_key_type();
+ if (ret)
+ goto unregister_sdio;
+
return 0;
+unregister_sdio:
+ sdio_unregister_bus();
unregister_host_class:
mmc_unregister_host_class();
unregister_bus:
@@ -812,6 +825,7 @@ destroy_workqueue:
static void __exit mmc_exit(void)
{
+ mmc_unregister_key_type();
sdio_unregister_bus();
mmc_unregister_host_class();
mmc_unregister_bus();
diff --git a/drivers/mmc/core/lock.c b/drivers/mmc/core/lock.c
new file mode 100644
index 000000000000..cc22cc204962
--- /dev/null
+++ b/drivers/mmc/core/lock.c
@@ -0,0 +1,193 @@
+/*
+ * linux/drivers/mmc/core/lock.h
+ *
+ * Copyright 2006 Instituto Nokia de Tecnologia (INdT), All Rights Reserved.
+ * 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 version 2 as
+ * published by the Free Software Foundation.
+ *
+ * MMC password key handling.
+ */
+
+#include <linux/device.h>
+#include <linux/key-type.h>
+#include <linux/err.h>
+
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+
+#include "mmc_ops.h"
+#include "lock.h"
+
+#define MMC_KEYLEN_MAXBYTES 32
+
+#define dev_to_mmc_card(d) container_of(d, struct mmc_card, dev)
+
+static int mmc_key_instantiate(struct key *key, const void *data, size_t datalen)
+{
+ struct mmc_key_payload *mpayload;
+ int ret;
+
+ ret = -EINVAL;
+ if (datalen <= 0 || datalen > MMC_KEYLEN_MAXBYTES || !data) {
+ pr_debug("Invalid data\n");
+ goto error;
+ }
+
+ ret = key_payload_reserve(key, datalen);
+ if (ret < 0) {
+ pr_debug("ret = %d\n", ret);
+ goto error;
+ }
+
+ ret = -ENOMEM;
+ mpayload = kmalloc(sizeof(*mpayload) + datalen, GFP_KERNEL);
+ if (!mpayload) {
+ pr_debug("Unable to allocate mpayload structure\n");
+ goto error;
+ }
+ mpayload->datalen = datalen;
+ memcpy(mpayload->data, data, datalen);
+
+ rcu_assign_pointer(key->payload.data, mpayload);
+
+ /* ret = 0 if there is no error */
+ ret = 0;
+
+error:
+ return ret;
+}
+
+static int mmc_key_match(const struct key *key, const void *description)
+{
+ return strcmp(key->description, description) == 0;
+}
+
+/*
+ * dispose of the data dangling from the corpse of a mmc key
+ */
+static void mmc_key_destroy(struct key *key)
+{
+ struct mmc_key_payload *mpayload = key->payload.data;
+
+ kfree(mpayload);
+}
+
+static struct key_type mmc_key_type = {
+ .name = "mmc",
+ .def_datalen = MMC_KEYLEN_MAXBYTES,
+ .instantiate = mmc_key_instantiate,
+ .match = mmc_key_match,
+ .destroy = mmc_key_destroy,
+};
+
+int mmc_register_key_type(void)
+{
+ return register_key_type(&mmc_key_type);
+}
+
+void mmc_unregister_key_type(void)
+{
+ unregister_key_type(&mmc_key_type);
+}
+
+static ssize_t
+mmc_lockable_show(struct device *dev, struct device_attribute *att, char *buf)
+{
+ struct mmc_card *card = dev_to_mmc_card(dev);
+
+ return sprintf(buf, "%slocked\n", mmc_card_locked(card) ? "" : "un");
+}
+
+/*
+ * implement MMC password functions: force erase, remove password, change
+ * password, unlock card and assign password.
+ */
+static ssize_t
+mmc_lockable_store(struct device *dev, struct device_attribute *att,
+ const char *data, size_t len)
+{
+ struct mmc_card *card = dev_to_mmc_card(dev);
+ int ret;
+ struct key *mmc_key;
+
+ WARN_ON(card->type != MMC_TYPE_MMC);
+ WARN_ON(!(card->csd.cmdclass & CCC_LOCK_CARD));
+
+ if(card->type != MMC_TYPE_MMC)
+ return -EINVAL;
+ if(!(card->csd.cmdclass & CCC_LOCK_CARD))
+ return -EINVAL;
+
+ mmc_claim_host(card->host);
+
+ ret = -EINVAL;
+ if (mmc_card_locked(card) && !strncmp(data, "erase", 5)) {
+ /* forced erase only works while card is locked */
+ mmc_lock_unlock(card, NULL, MMC_LOCK_MODE_ERASE);
+ ret = len;
+ } else if (!mmc_card_locked(card) && !strncmp(data, "remove", 6)) {
+ /* remove password only works while card is unlocked */
+ mmc_key = request_key(&mmc_key_type, "mmc:key", "remove");
+
+ if (!IS_ERR(mmc_key)) {
+ ret = mmc_lock_unlock(card, mmc_key, MMC_LOCK_MODE_CLR_PWD);
+ if (!ret)
+ ret = len;
+ } else
+ dev_dbg(&card->dev, "request_key returned error %ld\n", PTR_ERR(mmc_key));
+ } else if (!mmc_card_locked(card) && ((!strncmp(data, "assign", 6)) ||
+ (!strncmp(data, "change", 6)))) {
+ /* assign or change */
+ if(!(strncmp(data, "assign", 6)))
+ mmc_key = request_key(&mmc_key_type, "mmc:key", "assign");
+ else
+ mmc_key = request_key(&mmc_key_type, "mmc:key", "change");
+
+ if (!IS_ERR(mmc_key)) {
+ ret = mmc_lock_unlock(card, mmc_key, MMC_LOCK_MODE_SET_PWD);
+ if (!ret)
+ ret = len;
+ } else
+ dev_dbg(&card->dev, "request_key returned error %ld\n", PTR_ERR(mmc_key));
+ } else if (mmc_card_locked(card) && !strncmp(data, "unlock", 6)) {
+ /* unlock */
+ mmc_key = request_key(&mmc_key_type, "mmc:key", "unlock");
+ if (!IS_ERR(mmc_key)) {
+ ret = mmc_lock_unlock(card, mmc_key, MMC_LOCK_MODE_UNLOCK);
+ if (ret) {
+ dev_dbg(&card->dev, "Wrong password\n");
+ ret = -EINVAL;
+ }
+ else {
+ mmc_release_host(card->host);
+ device_release_driver(dev);
+ ret = device_attach(dev);
+ if(!ret)
+ return -EINVAL;
+ else
+ return len;
+ }
+ } else
+ dev_dbg(&card->dev, "request_key returned error %ld\n", PTR_ERR(mmc_key));
+ }
+
+ mmc_release_host(card->host);
+ return ret;
+}
+
+static DEVICE_ATTR(lockable, S_IWUSR | S_IRUGO,
+ mmc_lockable_show, mmc_lockable_store);
+
+static struct attribute *mmc_lock_attrs[] = {
+ &dev_attr_lockable.attr,
+ NULL,
+};
+
+struct attribute_group mmc_lock_attr_group = {
+ .attrs = mmc_lock_attrs,
+};
+
diff --git a/drivers/mmc/core/lock.h b/drivers/mmc/core/lock.h
new file mode 100644
index 000000000000..364139e510bc
--- /dev/null
+++ b/drivers/mmc/core/lock.h
@@ -0,0 +1,41 @@
+/*
+ * linux/drivers/mmc/core/lock.h
+ *
+ * Copyright 2006 Instituto Nokia de Tecnologia (INdT), All Rights Reserved.
+ * Copyright 2007-2008 Pierre Ossman
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _MMC_CORE_LOCK_H
+#define _MMC_CORE_LOCK_H
+
+#ifdef CONFIG_MMC_PASSWORDS
+
+/* core-internal data */
+struct mmc_key_payload {
+ struct rcu_head rcu; /* RCU destructor */
+ unsigned short datalen; /* length of this data */
+ char data[0]; /* actual data */
+};
+
+extern struct attribute_group mmc_lock_attr_group;
+
+int mmc_register_key_type(void);
+void mmc_unregister_key_type(void);
+
+#else
+
+static inline int mmc_register_key_type(void)
+{
+ return 0;
+}
+
+static inline void mmc_unregister_key_type(void)
+{
+}
+
+#endif
+
+#endif
diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
index 3da29eef8f7d..97e982625ab3 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -18,6 +18,7 @@
#include "core.h"
#include "bus.h"
+#include "lock.h"
#include "mmc_ops.h"
static const unsigned int tran_exp[] = {
@@ -278,6 +279,9 @@ static struct attribute_group mmc_std_attr_group = {
static struct attribute_group *mmc_attr_groups[] = {
&mmc_std_attr_group,
+#ifdef CONFIG_MMC_PASSWORDS
+ &mmc_lock_attr_group,
+#endif
NULL,
};
@@ -298,6 +302,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
int err;
u32 cid[4];
unsigned int max_dtr;
+ u32 status;
BUG_ON(!host);
WARN_ON(!host->claimed);
@@ -367,6 +372,15 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
}
+ /*
+ * Check if card is locked.
+ */
+ err = mmc_send_status(card, &status);
+ if (err)
+ goto free_card;
+ if (status & R1_CARD_IS_LOCKED)
+ mmc_card_set_locked(card);
+
if (!oldcard) {
/*
* Fetch CSD from card.
@@ -509,7 +523,7 @@ static void mmc_suspend(struct mmc_host *host)
mmc_claim_host(host);
if (!mmc_host_is_spi(host))
mmc_deselect_cards(host);
- host->card->state &= ~MMC_STATE_HIGHSPEED;
+ host->card->state &= ~(MMC_STATE_HIGHSPEED | MMC_STATE_LOCKED);
mmc_release_host(host);
}
diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c
index 64b05c6270f2..e6703e509cf6 100644
--- a/drivers/mmc/core/mmc_ops.c
+++ b/drivers/mmc/core/mmc_ops.c
@@ -2,6 +2,8 @@
* linux/drivers/mmc/core/mmc_ops.h
*
* Copyright 2006-2007 Pierre Ossman
+ * MMC password protection (C) 2006 Instituto Nokia de Tecnologia (INdT),
+ * All Rights Reserved.
*
* 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
@@ -11,12 +13,14 @@
#include <linux/types.h>
#include <linux/scatterlist.h>
+#include <linux/key.h>
#include <linux/mmc/host.h>
#include <linux/mmc/card.h>
#include <linux/mmc/mmc.h>
#include "core.h"
+#include "lock.h"
#include "mmc_ops.h"
static int _mmc_select_card(struct mmc_host *host, struct mmc_card *card)
@@ -395,3 +399,116 @@ int mmc_send_status(struct mmc_card *card, u32 *status)
return 0;
}
+#ifdef CONFIG_MMC_PASSWORDS
+
+int mmc_lock_unlock(struct mmc_card *card, struct key *key, int mode)
+{
+ struct mmc_request mrq;
+ struct mmc_command cmd;
+ struct mmc_data data;
+ struct scatterlist sg;
+ struct mmc_key_payload *mpayload;
+ unsigned long erase_timeout;
+ int err, data_size;
+ u8 *data_buf;
+
+ mpayload = NULL;
+ data_size = 1;
+ if (!(mode & MMC_LOCK_MODE_ERASE)) {
+ mpayload = rcu_dereference(key->payload.data);
+ data_size = 2 + mpayload->datalen;
+ }
+
+ data_buf = kzalloc(data_size, GFP_KERNEL);
+ if (!data_buf)
+ return -ENOMEM;
+
+ data_buf[0] |= mode;
+ if (mode & MMC_LOCK_MODE_UNLOCK)
+ data_buf[0] &= ~MMC_LOCK_MODE_UNLOCK;
+
+ if (!(mode & MMC_LOCK_MODE_ERASE)) {
+ data_buf[1] = mpayload->datalen;
+ memcpy(data_buf + 2, mpayload->data, mpayload->datalen);
+ }
+
+ memset(&cmd, 0, sizeof(struct mmc_command));
+
+ cmd.opcode = MMC_SET_BLOCKLEN;
+ cmd.arg = data_size;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
+ err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES);
+ if (err)
+ goto out;
+
+ memset(&cmd, 0, sizeof(struct mmc_command));
+
+ cmd.opcode = MMC_LOCK_UNLOCK;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_R1B | MMC_CMD_ADTC;
+
+ memset(&data, 0, sizeof(struct mmc_data));
+
+ data.blksz = data_size;
+ data.blocks = 1;
+ data.flags = MMC_DATA_WRITE;
+ data.sg = &sg;
+ data.sg_len = 1;
+
+ mmc_set_data_timeout(&data, card);
+
+ memset(&mrq, 0, sizeof(struct mmc_request));
+
+ mrq.cmd = &cmd;
+ mrq.data = &data;
+
+ sg_init_one(&sg, data_buf, data_size);
+ mmc_wait_for_req(card->host, &mrq);
+ err = cmd.error;
+ if (err)
+ goto out;
+ err = data.error;
+ if (err)
+ goto out;
+
+ 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;
+
+ /* set timeout for forced erase operation to 3 min. (see MMC spec) */
+ erase_timeout = jiffies + 180 * HZ;
+ do {
+ /* we cannot use "retries" here because the
+ * R1_LOCK_UNLOCK_FAILED bit is cleared by subsequent reads to
+ * the status register, hiding the error condition */
+ err = mmc_wait_for_cmd(card->host, &cmd, 0);
+ if (err)
+ break;
+ /* the other modes don't need timeout checking */
+ if (!(mode & MMC_LOCK_MODE_ERASE))
+ continue;
+ if (time_after(jiffies, erase_timeout)) {
+ dev_dbg(&card->dev, "forced erase timed out\n");
+ err = -ETIMEDOUT;
+ break;
+ }
+ } while (!(cmd.resp[0] & R1_READY_FOR_DATA));
+ if (cmd.resp[0] & R1_LOCK_UNLOCK_FAILED) {
+ dev_dbg(&card->dev, "LOCK_UNLOCK operation failed\n");
+ err = -EIO;
+ }
+
+ if (cmd.resp[0] & R1_CARD_IS_LOCKED)
+ mmc_card_set_locked(card);
+ else
+ card->state &= ~MMC_STATE_LOCKED;
+
+out:
+ kfree(data_buf);
+
+ return err;
+}
+
+#endif /* CONFIG_MMC_PASSWORDS */
diff --git a/drivers/mmc/core/mmc_ops.h b/drivers/mmc/core/mmc_ops.h
index 17854bf7cf0d..15cd1821c5e4 100644
--- a/drivers/mmc/core/mmc_ops.h
+++ b/drivers/mmc/core/mmc_ops.h
@@ -12,6 +12,8 @@
#ifndef _MMC_MMC_OPS_H
#define _MMC_MMC_OPS_H
+struct key;
+
int mmc_select_card(struct mmc_card *card);
int mmc_deselect_cards(struct mmc_host *host);
int mmc_go_idle(struct mmc_host *host);
@@ -25,6 +27,7 @@ int mmc_send_status(struct mmc_card *card, u32 *status);
int mmc_send_cid(struct mmc_host *host, u32 *cid);
int mmc_spi_read_ocr(struct mmc_host *host, int highcap, u32 *ocrp);
int mmc_spi_set_crc(struct mmc_host *host, int use_crc);
+int mmc_lock_unlock(struct mmc_card *card, struct key *key, int mode);
#endif
diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c
index 7ef3b15c5e3d..c029f6e532d8 100644
--- a/drivers/mmc/core/sd.c
+++ b/drivers/mmc/core/sd.c
@@ -19,6 +19,7 @@
#include "core.h"
#include "bus.h"
+#include "lock.h"
#include "mmc_ops.h"
#include "sd_ops.h"
@@ -316,6 +317,9 @@ static struct attribute_group sd_std_attr_group = {
static struct attribute_group *sd_attr_groups[] = {
&sd_std_attr_group,
+#ifdef CONFIG_MMC_PASSWORDS
+ &mmc_lock_attr_group,
+#endif
NULL,
};
@@ -336,6 +340,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
int err;
u32 cid[4];
unsigned int max_dtr;
+ u32 status;
BUG_ON(!host);
WARN_ON(!host->claimed);
@@ -413,6 +418,15 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
}
+ /*
+ * Check if card is locked.
+ */
+ err = mmc_send_status(card, &status);
+ if (err)
+ goto free_card;
+ if (status & R1_CARD_IS_LOCKED)
+ mmc_card_set_locked(card);
+
if (!oldcard) {
/*
* Fetch CSD from card.
@@ -494,13 +508,13 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
* Check if read-only switch is active.
*/
if (!oldcard) {
- if (!host->ops->get_ro) {
+ if (!host->ops->get_ro || host->ops->get_ro(host) < 0) {
printk(KERN_WARNING "%s: host does not "
"support reading read-only "
"switch. assuming write-enable.\n",
mmc_hostname(host));
} else {
- if (host->ops->get_ro(host))
+ if (host->ops->get_ro(host) > 0)
mmc_card_set_readonly(card);
}
}
@@ -571,7 +585,7 @@ static void mmc_sd_suspend(struct mmc_host *host)
mmc_claim_host(host);
if (!mmc_host_is_spi(host))
mmc_deselect_cards(host);
- host->card->state &= ~MMC_STATE_HIGHSPEED;
+ host->card->state &= ~(MMC_STATE_HIGHSPEED | MMC_STATE_LOCKED);
mmc_release_host(host);
}
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index dead61754ad7..1bbea8556c4d 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -26,18 +26,30 @@ config MMC_PXA
config MMC_SDHCI
tristate "Secure Digital Host Controller Interface support"
- depends on PCI
help
- This select the generic Secure Digital Host Controller Interface.
+ This selects the generic Secure Digital Host Controller Interface.
It is used by manufacturers such as Texas Instruments(R), Ricoh(R)
and Toshiba(R). Most controllers found in laptops are of this type.
+
+ If you have a controller with this interface, say Y or M here. You
+ also need to enable an appropriate bus interface.
+
+ If unsure, say N.
+
+config MMC_SDHCI_PCI
+ tristate "SDHCI support on PCI bus"
+ depends on MMC_SDHCI && PCI
+ help
+ This selects the PCI Secure Digital Host Controller Interface.
+ Most controllers found today are PCI devices.
+
If you have a controller with this interface, say Y or M here.
If unsure, say N.
config MMC_RICOH_MMC
tristate "Ricoh MMC Controller Disabler (EXPERIMENTAL)"
- depends on PCI && EXPERIMENTAL && MMC_SDHCI
+ depends on MMC_SDHCI_PCI
help
This selects the disabler for the Ricoh MMC Controller. This
proprietary controller is unnecessary because the SDHCI driver
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index 3877c87e6da2..3027250b8555 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_MMC_ARMMMCI) += mmci.o
obj-$(CONFIG_MMC_PXA) += pxamci.o
obj-$(CONFIG_MMC_IMX) += imxmmc.o
obj-$(CONFIG_MMC_SDHCI) += sdhci.o
+obj-$(CONFIG_MMC_SDHCI_PCI) += sdhci-pci.o
obj-$(CONFIG_MMC_RICOH_MMC) += ricoh_mmc.o
obj-$(CONFIG_MMC_WBSD) += wbsd.o
obj-$(CONFIG_MMC_AU1X) += au1xmmc.o
diff --git a/drivers/mmc/host/at91_mci.c b/drivers/mmc/host/at91_mci.c
index 8979ad330a4d..140c2b87c6bb 100644
--- a/drivers/mmc/host/at91_mci.c
+++ b/drivers/mmc/host/at91_mci.c
@@ -797,7 +797,7 @@ static int at91_mci_get_ro(struct mmc_host *mmc)
struct at91mci_host *host = mmc_priv(mmc);
if (host->board->wp_pin) {
- read_only = gpio_get_value(host->board->wp_pin);
+ read_only = !!gpio_get_value(host->board->wp_pin);
printk(KERN_WARNING "%s: card is %s\n", mmc_hostname(mmc),
(read_only ? "read-only" : "read-write") );
}
diff --git a/drivers/mmc/host/imxmmc.c b/drivers/mmc/host/imxmmc.c
index 95f33e87a99c..51676791c871 100644
--- a/drivers/mmc/host/imxmmc.c
+++ b/drivers/mmc/host/imxmmc.c
@@ -889,9 +889,12 @@ static int imxmci_get_ro(struct mmc_host *mmc)
struct imxmci_host *host = mmc_priv(mmc);
if (host->pdata && host->pdata->get_ro)
- return host->pdata->get_ro(mmc_dev(mmc));
- /* Host doesn't support read only detection so assume writeable */
- return 0;
+ return !!host->pdata->get_ro(mmc_dev(mmc));
+ /*
+ * Board doesn't support read only detection; let the mmc core
+ * decide what to do.
+ */
+ return -ENOSYS;
}
diff --git a/drivers/mmc/host/mmc_spi.c b/drivers/mmc/host/mmc_spi.c
index 35508584ac2a..85d98534b56e 100644
--- a/drivers/mmc/host/mmc_spi.c
+++ b/drivers/mmc/host/mmc_spi.c
@@ -1126,16 +1126,28 @@ static int mmc_spi_get_ro(struct mmc_host *mmc)
struct mmc_spi_host *host = mmc_priv(mmc);
if (host->pdata && host->pdata->get_ro)
- return host->pdata->get_ro(mmc->parent);
- /* board doesn't support read only detection; assume writeable */
- return 0;
+ return !!host->pdata->get_ro(mmc->parent);
+ /*
+ * Board doesn't support read only detection; let the mmc core
+ * decide what to do.
+ */
+ return -ENOSYS;
}
+static int mmc_spi_get_cd(struct mmc_host *mmc)
+{
+ struct mmc_spi_host *host = mmc_priv(mmc);
+
+ if (host->pdata && host->pdata->get_cd)
+ return host->pdata->get_cd(mmc->parent);
+ return -ENOSYS;
+}
static const struct mmc_host_ops mmc_spi_ops = {
.request = mmc_spi_request,
.set_ios = mmc_spi_set_ios,
.get_ro = mmc_spi_get_ro,
+ .get_cd = mmc_spi_get_cd,
};
@@ -1319,17 +1331,23 @@ static int mmc_spi_probe(struct spi_device *spi)
goto fail_glue_init;
}
+ /* pass platform capabilities, if any */
+ if (host->pdata)
+ mmc->caps |= host->pdata->caps;
+
status = mmc_add_host(mmc);
if (status != 0)
goto fail_add_host;
- dev_info(&spi->dev, "SD/MMC host %s%s%s%s\n",
+ dev_info(&spi->dev, "SD/MMC host %s%s%s%s%s\n",
mmc->class_dev.bus_id,
host->dma_dev ? "" : ", no DMA",
(host->pdata && host->pdata->get_ro)
? "" : ", no WP",
(host->pdata && host->pdata->setpower)
- ? "" : ", no poweroff");
+ ? "" : ", no poweroff",
+ (mmc->caps & MMC_CAP_NEEDS_POLL)
+ ? ", cd polling" : "");
return 0;
fail_add_host:
diff --git a/drivers/mmc/host/pxamci.c b/drivers/mmc/host/pxamci.c
index 65210fca37ed..b6056bdc76f5 100644
--- a/drivers/mmc/host/pxamci.c
+++ b/drivers/mmc/host/pxamci.c
@@ -361,9 +361,12 @@ static int pxamci_get_ro(struct mmc_host *mmc)
struct pxamci_host *host = mmc_priv(mmc);
if (host->pdata && host->pdata->get_ro)
- return host->pdata->get_ro(mmc_dev(mmc));
- /* Host doesn't support read only detection so assume writeable */
- return 0;
+ return !!host->pdata->get_ro(mmc_dev(mmc));
+ /*
+ * Board doesn't support read only detection; let the mmc core
+ * decide what to do.
+ */
+ return -ENOSYS;
}
static void pxamci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
diff --git a/drivers/mmc/host/sdhci-pci.c b/drivers/mmc/host/sdhci-pci.c
new file mode 100644
index 000000000000..22aab6a607ce
--- /dev/null
+++ b/drivers/mmc/host/sdhci-pci.c
@@ -0,0 +1,699 @@
+/* linux/drivers/mmc/host/sdhci-pci.c - SDHCI on PCI bus interface
+ *
+ * Copyright (C) 2005-2008 Pierre Ossman, All Rights Reserved.
+ *
+ * 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.
+ *
+ * Thanks to the following companies for their support:
+ *
+ * - JMicron (hardware and technical support)
+ */
+
+#include <linux/delay.h>
+#include <linux/highmem.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+
+#include <linux/mmc/host.h>
+
+#include <asm/scatterlist.h>
+#include <asm/io.h>
+
+#include "sdhci.h"
+
+/*
+ * PCI registers
+ */
+
+#define PCI_SDHCI_IFPIO 0x00
+#define PCI_SDHCI_IFDMA 0x01
+#define PCI_SDHCI_IFVENDOR 0x02
+
+#define PCI_SLOT_INFO 0x40 /* 8 bits */
+#define PCI_SLOT_INFO_SLOTS(x) ((x >> 4) & 7)
+#define PCI_SLOT_INFO_FIRST_BAR_MASK 0x07
+
+#define MAX_SLOTS 8
+
+struct sdhci_pci_chip;
+struct sdhci_pci_slot;
+
+struct sdhci_pci_fixes {
+ unsigned int quirks;
+
+ int (*probe)(struct sdhci_pci_chip*);
+
+ int (*probe_slot)(struct sdhci_pci_slot*);
+ void (*remove_slot)(struct sdhci_pci_slot*, int);
+
+ int (*suspend)(struct sdhci_pci_chip*,
+ pm_message_t);
+ int (*resume)(struct sdhci_pci_chip*);
+};
+
+struct sdhci_pci_slot {
+ struct sdhci_pci_chip *chip;
+ struct sdhci_host *host;
+
+ int pci_bar;
+};
+
+struct sdhci_pci_chip {
+ struct pci_dev *pdev;
+
+ unsigned int quirks;
+ const struct sdhci_pci_fixes *fixes;
+
+ int num_slots; /* Slots on controller */
+ struct sdhci_pci_slot *slots[MAX_SLOTS]; /* Pointers to host slots */
+};
+
+
+/*****************************************************************************\
+ * *
+ * Hardware specific quirk handling *
+ * *
+\*****************************************************************************/
+
+static int ricoh_probe(struct sdhci_pci_chip *chip)
+{
+ if (chip->pdev->subsystem_vendor == PCI_VENDOR_ID_IBM)
+ chip->quirks |= SDHCI_QUIRK_CLOCK_BEFORE_RESET;
+
+ if (chip->pdev->subsystem_vendor == PCI_VENDOR_ID_SAMSUNG)
+ chip->quirks |= SDHCI_QUIRK_NO_CARD_NO_RESET;
+
+ return 0;
+}
+
+static const struct sdhci_pci_fixes sdhci_ricoh = {
+ .probe = ricoh_probe,
+ .quirks = SDHCI_QUIRK_32BIT_DMA_ADDR,
+};
+
+static const struct sdhci_pci_fixes sdhci_ene_712 = {
+ .quirks = SDHCI_QUIRK_SINGLE_POWER_WRITE |
+ SDHCI_QUIRK_BROKEN_DMA,
+};
+
+static const struct sdhci_pci_fixes sdhci_ene_714 = {
+ .quirks = SDHCI_QUIRK_SINGLE_POWER_WRITE |
+ SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS,
+};
+
+static int jmicron_pmos(struct sdhci_pci_chip *chip, int on)
+{
+ u8 scratch;
+ int ret;
+
+ ret = pci_read_config_byte(chip->pdev, 0xAE, &scratch);
+ if (ret)
+ return ret;
+
+ /*
+ * Turn PMOS on [bit 0], set over current detection to 2.4 V
+ * [bit 1:2] and enable over current debouncing [bit 6].
+ */
+ if (on)
+ scratch |= 0x47;
+ else
+ scratch &= ~0x47;
+
+ ret = pci_write_config_byte(chip->pdev, 0xAE, scratch);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int jmicron_probe(struct sdhci_pci_chip *chip)
+{
+ int ret;
+
+ /*
+ * JMicron chips can have two interfaces to the same hardware
+ * in order to work around limitations in Microsoft's driver.
+ * We need to make sure we only bind to one of them.
+ *
+ * This code assumes two things:
+ *
+ * 1. The PCI code adds subfunctions in order.
+ *
+ * 2. The MMC interface has a lower subfunction number
+ * than the SD interface.
+ */
+ if (chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_SD) {
+ struct pci_dev *sd_dev;
+
+ sd_dev = NULL;
+ while ((sd_dev = pci_get_device(PCI_VENDOR_ID_JMICRON,
+ PCI_DEVICE_ID_JMICRON_JMB38X_MMC, sd_dev)) != NULL) {
+ if ((PCI_SLOT(chip->pdev->devfn) ==
+ PCI_SLOT(sd_dev->devfn)) &&
+ (chip->pdev->bus == sd_dev->bus))
+ break;
+ }
+
+ if (sd_dev) {
+ pci_dev_put(sd_dev);
+ dev_info(&chip->pdev->dev, "Refusing to bind to "
+ "secondary interface.\n");
+ return -ENODEV;
+ }
+ }
+
+ /*
+ * JMicron chips need a bit of a nudge to enable the power
+ * output pins.
+ */
+ ret = jmicron_pmos(chip, 1);
+ if (ret) {
+ dev_err(&chip->pdev->dev, "Failure enabling card power\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void jmicron_enable_mmc(struct sdhci_host *host, int on)
+{
+ u8 scratch;
+
+ scratch = readb(host->ioaddr + 0xC0);
+
+ if (on)
+ scratch |= 0x01;
+ else
+ scratch &= ~0x01;
+
+ writeb(scratch, host->ioaddr + 0xC0);
+}
+
+static int jmicron_probe_slot(struct sdhci_pci_slot *slot)
+{
+ /*
+ * The secondary interface requires a bit set to get the
+ * interrupts.
+ */
+ if (slot->chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC)
+ jmicron_enable_mmc(slot->host, 1);
+
+ return 0;
+}
+
+static void jmicron_remove_slot(struct sdhci_pci_slot *slot, int dead)
+{
+ if (dead)
+ return;
+
+ if (slot->chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC)
+ jmicron_enable_mmc(slot->host, 0);
+}
+
+static int jmicron_suspend(struct sdhci_pci_chip *chip, pm_message_t state)
+{
+ int i;
+
+ if (chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC) {
+ for (i = 0;i < chip->num_slots;i++)
+ jmicron_enable_mmc(chip->slots[i]->host, 0);
+ }
+
+ return 0;
+}
+
+static int jmicron_resume(struct sdhci_pci_chip *chip)
+{
+ int ret, i;
+
+ if (chip->pdev->device == PCI_DEVICE_ID_JMICRON_JMB38X_MMC) {
+ for (i = 0;i < chip->num_slots;i++)
+ jmicron_enable_mmc(chip->slots[i]->host, 1);
+ }
+
+ ret = jmicron_pmos(chip, 1);
+ if (ret) {
+ dev_err(&chip->pdev->dev, "Failure enabling card power\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct sdhci_pci_fixes sdhci_jmicron = {
+ .quirks = SDHCI_QUIRK_32BIT_DMA_ADDR |
+ SDHCI_QUIRK_32BIT_DMA_SIZE |
+ SDHCI_QUIRK_RESET_AFTER_REQUEST,
+
+ .probe = jmicron_probe,
+
+ .probe_slot = jmicron_probe_slot,
+ .remove_slot = jmicron_remove_slot,
+
+ .suspend = jmicron_suspend,
+ .resume = jmicron_resume,
+};
+
+static const struct pci_device_id pci_ids[] __devinitdata = {
+ {
+ .vendor = PCI_VENDOR_ID_RICOH,
+ .device = PCI_DEVICE_ID_RICOH_R5C822,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_ricoh,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_ENE,
+ .device = PCI_DEVICE_ID_ENE_CB712_SD,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_ene_712,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_ENE,
+ .device = PCI_DEVICE_ID_ENE_CB712_SD_2,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_ene_712,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_ENE,
+ .device = PCI_DEVICE_ID_ENE_CB714_SD,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_ene_714,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_ENE,
+ .device = PCI_DEVICE_ID_ENE_CB714_SD_2,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_ene_714,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_JMICRON,
+ .device = PCI_DEVICE_ID_JMICRON_JMB38X_SD,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_jmicron,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_JMICRON,
+ .device = PCI_DEVICE_ID_JMICRON_JMB38X_MMC,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .driver_data = (kernel_ulong_t)&sdhci_jmicron,
+ },
+
+ { /* Generic SD host controller */
+ PCI_DEVICE_CLASS((PCI_CLASS_SYSTEM_SDHCI << 8), 0xFFFF00)
+ },
+
+ { /* end: all zeroes */ },
+};
+
+MODULE_DEVICE_TABLE(pci, pci_ids);
+
+/*****************************************************************************\
+ * *
+ * SDHCI core callbacks *
+ * *
+\*****************************************************************************/
+
+static int sdhci_pci_enable_dma(struct sdhci_host *host)
+{
+ struct sdhci_pci_slot *slot;
+ struct pci_dev *pdev;
+ int ret;
+
+ slot = sdhci_priv(host);
+ pdev = slot->chip->pdev;
+
+ if (((pdev->class & 0xFFFF00) == (PCI_CLASS_SYSTEM_SDHCI << 8)) &&
+ ((pdev->class & 0x0000FF) != PCI_SDHCI_IFDMA) &&
+ (host->flags & SDHCI_USE_DMA)) {
+ dev_warn(&pdev->dev, "Will use DMA mode even though HW "
+ "doesn't fully claim to support it.\n");
+ }
+
+ ret = pci_set_dma_mask(pdev, DMA_32BIT_MASK);
+ if (ret)
+ return ret;
+
+ pci_set_master(pdev);
+
+ return 0;
+}
+
+static struct sdhci_ops sdhci_pci_ops = {
+ .enable_dma = sdhci_pci_enable_dma,
+};
+
+/*****************************************************************************\
+ * *
+ * Suspend/resume *
+ * *
+\*****************************************************************************/
+
+#ifdef CONFIG_PM
+
+static int sdhci_pci_suspend (struct pci_dev *pdev, pm_message_t state)
+{
+ struct sdhci_pci_chip *chip;
+ struct sdhci_pci_slot *slot;
+ int i, ret;
+
+ chip = pci_get_drvdata(pdev);
+ if (!chip)
+ return 0;
+
+ for (i = 0;i < chip->num_slots;i++) {
+ slot = chip->slots[i];
+ if (!slot)
+ continue;
+
+ ret = sdhci_suspend_host(slot->host, state);
+
+ if (ret) {
+ for (i--;i >= 0;i--)
+ sdhci_resume_host(chip->slots[i]->host);
+ return ret;
+ }
+ }
+
+ if (chip->fixes && chip->fixes->suspend) {
+ ret = chip->fixes->suspend(chip, state);
+ if (ret) {
+ for (i = chip->num_slots - 1;i >= 0;i--)
+ sdhci_resume_host(chip->slots[i]->host);
+ return ret;
+ }
+ }
+
+ pci_save_state(pdev);
+ pci_enable_wake(pdev, pci_choose_state(pdev, state), 0);
+ pci_disable_device(pdev);
+ pci_set_power_state(pdev, pci_choose_state(pdev, state));
+
+ return 0;
+}
+
+static int sdhci_pci_resume (struct pci_dev *pdev)
+{
+ struct sdhci_pci_chip *chip;
+ struct sdhci_pci_slot *slot;
+ int i, ret;
+
+ chip = pci_get_drvdata(pdev);
+ if (!chip)
+ return 0;
+
+ pci_set_power_state(pdev, PCI_D0);
+ pci_restore_state(pdev);
+ ret = pci_enable_device(pdev);
+ if (ret)
+ return ret;
+
+ if (chip->fixes && chip->fixes->resume) {
+ ret = chip->fixes->resume(chip);
+ if (ret)
+ return ret;
+ }
+
+ for (i = 0;i < chip->num_slots;i++) {
+ slot = chip->slots[i];
+ if (!slot)
+ continue;
+
+ ret = sdhci_resume_host(slot->host);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+#else /* CONFIG_PM */
+
+#define sdhci_pci_suspend NULL
+#define sdhci_pci_resume NULL
+
+#endif /* CONFIG_PM */
+
+/*****************************************************************************\
+ * *
+ * Device probing/removal *
+ * *
+\*****************************************************************************/
+
+static struct sdhci_pci_slot * __devinit sdhci_pci_probe_slot(
+ struct pci_dev *pdev, struct sdhci_pci_chip *chip, int bar)
+{
+ struct sdhci_pci_slot *slot;
+ struct sdhci_host *host;
+
+ resource_size_t addr;
+
+ int ret;
+
+ if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM)) {
+ dev_err(&pdev->dev, "BAR %d is not iomem. Aborting.\n", bar);
+ return ERR_PTR(-ENODEV);
+ }
+
+ if (pci_resource_len(pdev, bar) != 0x100) {
+ dev_err(&pdev->dev, "Invalid iomem size. You may "
+ "experience problems.\n");
+ }
+
+ if ((pdev->class & 0x0000FF) == PCI_SDHCI_IFVENDOR) {
+ dev_err(&pdev->dev, "Vendor specific interface. Aborting.\n");
+ return ERR_PTR(-ENODEV);
+ }
+
+ if ((pdev->class & 0x0000FF) > PCI_SDHCI_IFVENDOR) {
+ dev_err(&pdev->dev, "Unknown interface. Aborting.\n");
+ return ERR_PTR(-ENODEV);
+ }
+
+ host = sdhci_alloc_host(&pdev->dev, sizeof(struct sdhci_pci_slot));
+ if (IS_ERR(host)) {
+ ret = PTR_ERR(host);
+ goto unmap;
+ }
+
+ slot = sdhci_priv(host);
+
+ slot->chip = chip;
+ slot->host = host;
+ slot->pci_bar = bar;
+
+ host->hw_name = "PCI";
+ host->ops = &sdhci_pci_ops;
+ host->quirks = chip->quirks;
+
+ host->irq = pdev->irq;
+
+ ret = pci_request_region(pdev, bar, mmc_hostname(host->mmc));
+ if (ret) {
+ dev_err(&pdev->dev, "cannot request region\n");
+ return ERR_PTR(ret);
+ }
+
+ addr = pci_resource_start(pdev, bar);
+ host->ioaddr = ioremap_nocache(addr, pci_resource_len(pdev, bar));
+ if (!host->ioaddr) {
+ dev_err(&pdev->dev, "failed to remap registers\n");
+ goto release;
+ }
+
+ if (chip->fixes && chip->fixes->probe_slot) {
+ ret = chip->fixes->probe_slot(slot);
+ if (ret)
+ goto unmap;
+ }
+
+ ret = sdhci_add_host(host);
+ if (ret)
+ goto remove;
+
+ return slot;
+
+remove:
+ if (chip->fixes && chip->fixes->remove_slot)
+ chip->fixes->remove_slot(slot, 0);
+
+unmap:
+ iounmap(host->ioaddr);
+
+release:
+ pci_release_region(pdev, bar);
+ sdhci_free_host(host);
+
+ return ERR_PTR(ret);
+}
+
+static void sdhci_pci_remove_slot(struct sdhci_pci_slot *slot)
+{
+ int dead;
+ u32 scratch;
+
+ dead = 0;
+ scratch = readl(slot->host->ioaddr + SDHCI_INT_STATUS);
+ if (scratch == (u32)-1)
+ dead = 1;
+
+ sdhci_remove_host(slot->host, dead);
+
+ if (slot->chip->fixes && slot->chip->fixes->remove_slot)
+ slot->chip->fixes->remove_slot(slot, dead);
+
+ pci_release_region(slot->chip->pdev, slot->pci_bar);
+
+ sdhci_free_host(slot->host);
+}
+
+static int __devinit sdhci_pci_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ struct sdhci_pci_chip *chip;
+ struct sdhci_pci_slot *slot;
+
+ u8 slots, rev, first_bar;
+ int ret, i;
+
+ BUG_ON(pdev == NULL);
+ BUG_ON(ent == NULL);
+
+ pci_read_config_byte(pdev, PCI_CLASS_REVISION, &rev);
+
+ dev_info(&pdev->dev, "SDHCI controller found [%04x:%04x] (rev %x)\n",
+ (int)pdev->vendor, (int)pdev->device, (int)rev);
+
+ ret = pci_read_config_byte(pdev, PCI_SLOT_INFO, &slots);
+ if (ret)
+ return ret;
+
+ slots = PCI_SLOT_INFO_SLOTS(slots) + 1;
+ dev_dbg(&pdev->dev, "found %d slot(s)\n", slots);
+ if (slots == 0)
+ return -ENODEV;
+
+ BUG_ON(slots > MAX_SLOTS);
+
+ ret = pci_read_config_byte(pdev, PCI_SLOT_INFO, &first_bar);
+ if (ret)
+ return ret;
+
+ first_bar &= PCI_SLOT_INFO_FIRST_BAR_MASK;
+
+ if (first_bar > 5) {
+ dev_err(&pdev->dev, "Invalid first BAR. Aborting.\n");
+ return -ENODEV;
+ }
+
+ ret = pci_enable_device(pdev);
+ if (ret)
+ return ret;
+
+ chip = kzalloc(sizeof(struct sdhci_pci_chip), GFP_KERNEL);
+ if (!chip) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ chip->pdev = pdev;
+ chip->fixes = (const struct sdhci_pci_fixes*)ent->driver_data;
+ if (chip->fixes)
+ chip->quirks = chip->fixes->quirks;
+ chip->num_slots = slots;
+
+ pci_set_drvdata(pdev, chip);
+
+ if (chip->fixes && chip->fixes->probe) {
+ ret = chip->fixes->probe(chip);
+ if (ret)
+ goto free;
+ }
+
+ for (i = 0;i < slots;i++) {
+ slot = sdhci_pci_probe_slot(pdev, chip, first_bar + i);
+ if (IS_ERR(slot)) {
+ for (i--;i >= 0;i--)
+ sdhci_pci_remove_slot(chip->slots[i]);
+ ret = PTR_ERR(slot);
+ goto free;
+ }
+
+ chip->slots[i] = slot;
+ }
+
+ return 0;
+
+free:
+ pci_set_drvdata(pdev, NULL);
+ kfree(chip);
+
+err:
+ pci_disable_device(pdev);
+ return ret;
+}
+
+static void __devexit sdhci_pci_remove(struct pci_dev *pdev)
+{
+ int i;
+ struct sdhci_pci_chip *chip;
+
+ chip = pci_get_drvdata(pdev);
+
+ if (chip) {
+ for (i = 0;i < chip->num_slots; i++)
+ sdhci_pci_remove_slot(chip->slots[i]);
+
+ pci_set_drvdata(pdev, NULL);
+ kfree(chip);
+ }
+
+ pci_disable_device(pdev);
+}
+
+static struct pci_driver sdhci_driver = {
+ .name = "sdhci-pci",
+ .id_table = pci_ids,
+ .probe = sdhci_pci_probe,
+ .remove = __devexit_p(sdhci_pci_remove),
+ .suspend = sdhci_pci_suspend,
+ .resume = sdhci_pci_resume,
+};
+
+/*****************************************************************************\
+ * *
+ * Driver init/exit *
+ * *
+\*****************************************************************************/
+
+static int __init sdhci_drv_init(void)
+{
+ return pci_register_driver(&sdhci_driver);
+}
+
+static void __exit sdhci_drv_exit(void)
+{
+ pci_unregister_driver(&sdhci_driver);
+}
+
+module_init(sdhci_drv_init);
+module_exit(sdhci_drv_exit);
+
+MODULE_AUTHOR("Pierre Ossman <drzeus@drzeus.cx>");
+MODULE_DESCRIPTION("Secure Digital Host Controller Interface PCI driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 07c2048b230b..6441e449419e 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -15,7 +15,7 @@
#include <linux/delay.h>
#include <linux/highmem.h>
-#include <linux/pci.h>
+#include <linux/io.h>
#include <linux/dma-mapping.h>
#include <linux/scatterlist.h>
@@ -32,120 +32,6 @@
static unsigned int debug_quirks = 0;
-/*
- * Different quirks to handle when the hardware deviates from a strict
- * interpretation of the SDHCI specification.
- */
-
-/* Controller doesn't honor resets unless we touch the clock register */
-#define SDHCI_QUIRK_CLOCK_BEFORE_RESET (1<<0)
-/* Controller has bad caps bits, but really supports DMA */
-#define SDHCI_QUIRK_FORCE_DMA (1<<1)
-/* Controller doesn't like to be reset when there is no card inserted. */
-#define SDHCI_QUIRK_NO_CARD_NO_RESET (1<<2)
-/* Controller doesn't like clearing the power reg before a change */
-#define SDHCI_QUIRK_SINGLE_POWER_WRITE (1<<3)
-/* Controller has flaky internal state so reset it on each ios change */
-#define SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS (1<<4)
-/* Controller has an unusable DMA engine */
-#define SDHCI_QUIRK_BROKEN_DMA (1<<5)
-/* Controller can only DMA from 32-bit aligned addresses */
-#define SDHCI_QUIRK_32BIT_DMA_ADDR (1<<6)
-/* Controller can only DMA chunk sizes that are a multiple of 32 bits */
-#define SDHCI_QUIRK_32BIT_DMA_SIZE (1<<7)
-/* Controller needs to be reset after each request to stay stable */
-#define SDHCI_QUIRK_RESET_AFTER_REQUEST (1<<8)
-
-static const struct pci_device_id pci_ids[] __devinitdata = {
- {
- .vendor = PCI_VENDOR_ID_RICOH,
- .device = PCI_DEVICE_ID_RICOH_R5C822,
- .subvendor = PCI_VENDOR_ID_IBM,
- .subdevice = PCI_ANY_ID,
- .driver_data = SDHCI_QUIRK_CLOCK_BEFORE_RESET |
- SDHCI_QUIRK_FORCE_DMA,
- },
-
- {
- .vendor = PCI_VENDOR_ID_RICOH,
- .device = PCI_DEVICE_ID_RICOH_R5C822,
- .subvendor = PCI_VENDOR_ID_SAMSUNG,
- .subdevice = PCI_ANY_ID,
- .driver_data = SDHCI_QUIRK_FORCE_DMA |
- SDHCI_QUIRK_NO_CARD_NO_RESET,
- },
-
- {
- .vendor = PCI_VENDOR_ID_RICOH,
- .device = PCI_DEVICE_ID_RICOH_R5C822,
- .subvendor = PCI_ANY_ID,
- .subdevice = PCI_ANY_ID,
- .driver_data = SDHCI_QUIRK_FORCE_DMA,
- },
-
- {
- .vendor = PCI_VENDOR_ID_TI,
- .device = PCI_DEVICE_ID_TI_XX21_XX11_SD,
- .subvendor = PCI_ANY_ID,
- .subdevice = PCI_ANY_ID,
- .driver_data = SDHCI_QUIRK_FORCE_DMA,
- },
-
- {
- .vendor = PCI_VENDOR_ID_ENE,
- .device = PCI_DEVICE_ID_ENE_CB712_SD,
- .subvendor = PCI_ANY_ID,
- .subdevice = PCI_ANY_ID,
- .driver_data = SDHCI_QUIRK_SINGLE_POWER_WRITE |
- SDHCI_QUIRK_BROKEN_DMA,
- },
-
- {
- .vendor = PCI_VENDOR_ID_ENE,
- .device = PCI_DEVICE_ID_ENE_CB712_SD_2,
- .subvendor = PCI_ANY_ID,
- .subdevice = PCI_ANY_ID,
- .driver_data = SDHCI_QUIRK_SINGLE_POWER_WRITE |
- SDHCI_QUIRK_BROKEN_DMA,
- },
-
- {
- .vendor = PCI_VENDOR_ID_ENE,
- .device = PCI_DEVICE_ID_ENE_CB714_SD,
- .subvendor = PCI_ANY_ID,
- .subdevice = PCI_ANY_ID,
- .driver_data = SDHCI_QUIRK_SINGLE_POWER_WRITE |
- SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS,
- },
-
- {
- .vendor = PCI_VENDOR_ID_ENE,
- .device = PCI_DEVICE_ID_ENE_CB714_SD_2,
- .subvendor = PCI_ANY_ID,
- .subdevice = PCI_ANY_ID,
- .driver_data = SDHCI_QUIRK_SINGLE_POWER_WRITE |
- SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS,
- },
-
- {
- .vendor = PCI_VENDOR_ID_JMICRON,
- .device = PCI_DEVICE_ID_JMICRON_JMB38X_SD,
- .subvendor = PCI_ANY_ID,
- .subdevice = PCI_ANY_ID,
- .driver_data = SDHCI_QUIRK_32BIT_DMA_ADDR |
- SDHCI_QUIRK_32BIT_DMA_SIZE |
- SDHCI_QUIRK_RESET_AFTER_REQUEST,
- },
-
- { /* Generic SD host controller */
- PCI_DEVICE_CLASS((PCI_CLASS_SYSTEM_SDHCI << 8), 0xFFFF00)
- },
-
- { /* end: all zeroes */ },
-};
-
-MODULE_DEVICE_TABLE(pci, pci_ids);
-
static void sdhci_prepare_data(struct sdhci_host *, struct mmc_data *);
static void sdhci_finish_data(struct sdhci_host *);
@@ -200,7 +86,7 @@ static void sdhci_reset(struct sdhci_host *host, u8 mask)
{
unsigned long timeout;
- if (host->chip->quirks & SDHCI_QUIRK_NO_CARD_NO_RESET) {
+ if (host->quirks & SDHCI_QUIRK_NO_CARD_NO_RESET) {
if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) &
SDHCI_CARD_PRESENT))
return;
@@ -481,7 +367,7 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_data *data)
host->flags |= SDHCI_REQ_USE_DMA;
if (unlikely((host->flags & SDHCI_REQ_USE_DMA) &&
- (host->chip->quirks & SDHCI_QUIRK_32BIT_DMA_SIZE) &&
+ (host->quirks & SDHCI_QUIRK_32BIT_DMA_SIZE) &&
((data->blksz * data->blocks) & 0x3))) {
DBG("Reverting to PIO because of transfer size (%d)\n",
data->blksz * data->blocks);
@@ -493,7 +379,7 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_data *data)
* translation to device address space.
*/
if (unlikely((host->flags & SDHCI_REQ_USE_DMA) &&
- (host->chip->quirks & SDHCI_QUIRK_32BIT_DMA_ADDR) &&
+ (host->quirks & SDHCI_QUIRK_32BIT_DMA_ADDR) &&
(data->sg->offset & 0x3))) {
DBG("Reverting to PIO because of bad alignment\n");
host->flags &= ~SDHCI_REQ_USE_DMA;
@@ -502,11 +388,13 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_data *data)
if (host->flags & SDHCI_REQ_USE_DMA) {
int count;
- count = pci_map_sg(host->chip->pdev, data->sg, data->sg_len,
- (data->flags & MMC_DATA_READ)?PCI_DMA_FROMDEVICE:PCI_DMA_TODEVICE);
- BUG_ON(count != 1);
+ count = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+ (data->flags & MMC_DATA_READ) ?
+ DMA_FROM_DEVICE : DMA_TO_DEVICE);
+ WARN_ON(count != 1);
- writel(sg_dma_address(data->sg), host->ioaddr + SDHCI_DMA_ADDRESS);
+ writel(sg_dma_address(data->sg),
+ host->ioaddr + SDHCI_DMA_ADDRESS);
} else {
host->cur_sg = data->sg;
host->num_sg = data->sg_len;
@@ -545,7 +433,6 @@ static void sdhci_set_transfer_mode(struct sdhci_host *host,
static void sdhci_finish_data(struct sdhci_host *host)
{
struct mmc_data *data;
- u16 blocks;
BUG_ON(!host->data);
@@ -553,25 +440,22 @@ static void sdhci_finish_data(struct sdhci_host *host)
host->data = NULL;
if (host->flags & SDHCI_REQ_USE_DMA) {
- pci_unmap_sg(host->chip->pdev, data->sg, data->sg_len,
- (data->flags & MMC_DATA_READ)?PCI_DMA_FROMDEVICE:PCI_DMA_TODEVICE);
+ dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+ (data->flags & MMC_DATA_READ) ?
+ DMA_FROM_DEVICE : DMA_TO_DEVICE);
}
/*
- * Controller doesn't count down when in single block mode.
+ * The specification states that the block count register must
+ * be updated, but it does not specify at what point in the
+ * data flow. That makes the register entirely useless to read
+ * back so we have to assume that nothing made it to the card
+ * in the event of an error.
*/
- if (data->blocks == 1)
- blocks = (data->error == 0) ? 0 : 1;
+ if (data->error)
+ data->bytes_xfered = 0;
else
- blocks = readw(host->ioaddr + SDHCI_BLOCK_COUNT);
- data->bytes_xfered = data->blksz * (data->blocks - blocks);
-
- if (!data->error && blocks) {
- printk(KERN_ERR "%s: Controller signalled completion even "
- "though there were blocks left.\n",
- mmc_hostname(host->mmc));
- data->error = -EIO;
- }
+ data->bytes_xfered = data->blksz * data->blocks;
if (data->stop) {
/*
@@ -753,7 +637,7 @@ static void sdhci_set_power(struct sdhci_host *host, unsigned short power)
* Spec says that we should clear the power reg before setting
* a new value. Some controllers don't seem to like this though.
*/
- if (!(host->chip->quirks & SDHCI_QUIRK_SINGLE_POWER_WRITE))
+ if (!(host->quirks & SDHCI_QUIRK_SINGLE_POWER_WRITE))
writeb(0, host->ioaddr + SDHCI_POWER_CONTROL);
pwr = SDHCI_POWER_ON;
@@ -803,7 +687,8 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
host->mrq = mrq;
- if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) {
+ if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)
+ || (host->flags & SDHCI_DEVICE_DEAD)) {
host->mrq->cmd->error = -ENOMEDIUM;
tasklet_schedule(&host->finish_tasklet);
} else
@@ -823,6 +708,9 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
spin_lock_irqsave(&host->lock, flags);
+ if (host->flags & SDHCI_DEVICE_DEAD)
+ goto out;
+
/*
* Reset the chip on each power off.
* Should clear out any weird states.
@@ -858,9 +746,10 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
* signalling timeout and CRC errors even on CMD0. Resetting
* it on each ios seems to solve the problem.
*/
- if(host->chip->quirks & SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS)
+ if(host->quirks & SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS)
sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA);
+out:
mmiowb();
spin_unlock_irqrestore(&host->lock, flags);
}
@@ -875,7 +764,10 @@ static int sdhci_get_ro(struct mmc_host *mmc)
spin_lock_irqsave(&host->lock, flags);
- present = readl(host->ioaddr + SDHCI_PRESENT_STATE);
+ if (host->flags & SDHCI_DEVICE_DEAD)
+ present = 0;
+ else
+ present = readl(host->ioaddr + SDHCI_PRESENT_STATE);
spin_unlock_irqrestore(&host->lock, flags);
@@ -892,6 +784,9 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable)
spin_lock_irqsave(&host->lock, flags);
+ if (host->flags & SDHCI_DEVICE_DEAD)
+ goto out;
+
ier = readl(host->ioaddr + SDHCI_INT_ENABLE);
ier &= ~SDHCI_INT_CARD_INT;
@@ -901,6 +796,7 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable)
writel(ier, host->ioaddr + SDHCI_INT_ENABLE);
writel(ier, host->ioaddr + SDHCI_SIGNAL_ENABLE);
+out:
mmiowb();
spin_unlock_irqrestore(&host->lock, flags);
@@ -966,13 +862,14 @@ static void sdhci_tasklet_finish(unsigned long param)
* The controller needs a reset of internal state machines
* upon error conditions.
*/
- if (mrq->cmd->error ||
- (mrq->data && (mrq->data->error ||
- (mrq->data->stop && mrq->data->stop->error))) ||
- (host->chip->quirks & SDHCI_QUIRK_RESET_AFTER_REQUEST)) {
+ if (!(host->flags & SDHCI_DEVICE_DEAD) &&
+ (mrq->cmd->error ||
+ (mrq->data && (mrq->data->error ||
+ (mrq->data->stop && mrq->data->stop->error))) ||
+ (host->quirks & SDHCI_QUIRK_RESET_AFTER_REQUEST))) {
/* Some controllers need this kick or reset won't work here */
- if (host->chip->quirks & SDHCI_QUIRK_CLOCK_BEFORE_RESET) {
+ if (host->quirks & SDHCI_QUIRK_CLOCK_BEFORE_RESET) {
unsigned int clock;
/* This is to force an update */
@@ -1204,165 +1101,90 @@ out:
#ifdef CONFIG_PM
-static int sdhci_suspend (struct pci_dev *pdev, pm_message_t state)
+int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state)
{
- struct sdhci_chip *chip;
- int i, ret;
-
- chip = pci_get_drvdata(pdev);
- if (!chip)
- return 0;
-
- DBG("Suspending...\n");
-
- for (i = 0;i < chip->num_slots;i++) {
- if (!chip->hosts[i])
- continue;
- ret = mmc_suspend_host(chip->hosts[i]->mmc, state);
- if (ret) {
- for (i--;i >= 0;i--)
- mmc_resume_host(chip->hosts[i]->mmc);
- return ret;
- }
- }
-
- pci_save_state(pdev);
- pci_enable_wake(pdev, pci_choose_state(pdev, state), 0);
+ int ret;
- for (i = 0;i < chip->num_slots;i++) {
- if (!chip->hosts[i])
- continue;
- free_irq(chip->hosts[i]->irq, chip->hosts[i]);
- }
+ ret = mmc_suspend_host(host->mmc, state);
+ if (ret)
+ return ret;
- pci_disable_device(pdev);
- pci_set_power_state(pdev, pci_choose_state(pdev, state));
+ free_irq(host->irq, host);
return 0;
}
-static int sdhci_resume (struct pci_dev *pdev)
-{
- struct sdhci_chip *chip;
- int i, ret;
+EXPORT_SYMBOL_GPL(sdhci_suspend_host);
- chip = pci_get_drvdata(pdev);
- if (!chip)
- return 0;
+int sdhci_resume_host(struct sdhci_host *host)
+{
+ int ret;
- DBG("Resuming...\n");
+ if (host->flags & SDHCI_USE_DMA) {
+ if (host->ops->enable_dma)
+ host->ops->enable_dma(host);
+ }
- pci_set_power_state(pdev, PCI_D0);
- pci_restore_state(pdev);
- ret = pci_enable_device(pdev);
+ ret = request_irq(host->irq, sdhci_irq, IRQF_SHARED,
+ mmc_hostname(host->mmc), host);
if (ret)
return ret;
- for (i = 0;i < chip->num_slots;i++) {
- if (!chip->hosts[i])
- continue;
- if (chip->hosts[i]->flags & SDHCI_USE_DMA)
- pci_set_master(pdev);
- ret = request_irq(chip->hosts[i]->irq, sdhci_irq,
- IRQF_SHARED, mmc_hostname(chip->hosts[i]->mmc),
- chip->hosts[i]);
- if (ret)
- return ret;
- sdhci_init(chip->hosts[i]);
- mmiowb();
- ret = mmc_resume_host(chip->hosts[i]->mmc);
- if (ret)
- return ret;
- }
+ sdhci_init(host);
+ mmiowb();
+
+ ret = mmc_resume_host(host->mmc);
+ if (ret)
+ return ret;
return 0;
}
-#else /* CONFIG_PM */
-
-#define sdhci_suspend NULL
-#define sdhci_resume NULL
+EXPORT_SYMBOL_GPL(sdhci_resume_host);
#endif /* CONFIG_PM */
/*****************************************************************************\
* *
- * Device probing/removal *
+ * Device allocation/registration *
* *
\*****************************************************************************/
-static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot)
+struct sdhci_host *sdhci_alloc_host(struct device *dev,
+ size_t priv_size)
{
- int ret;
- unsigned int version;
- struct sdhci_chip *chip;
struct mmc_host *mmc;
struct sdhci_host *host;
- u8 first_bar;
- unsigned int caps;
-
- chip = pci_get_drvdata(pdev);
- BUG_ON(!chip);
+ WARN_ON(dev == NULL);
- ret = pci_read_config_byte(pdev, PCI_SLOT_INFO, &first_bar);
- if (ret)
- return ret;
-
- first_bar &= PCI_SLOT_INFO_FIRST_BAR_MASK;
-
- if (first_bar > 5) {
- printk(KERN_ERR DRIVER_NAME ": Invalid first BAR. Aborting.\n");
- return -ENODEV;
- }
-
- if (!(pci_resource_flags(pdev, first_bar + slot) & IORESOURCE_MEM)) {
- printk(KERN_ERR DRIVER_NAME ": BAR is not iomem. Aborting.\n");
- return -ENODEV;
- }
-
- if (pci_resource_len(pdev, first_bar + slot) != 0x100) {
- printk(KERN_ERR DRIVER_NAME ": Invalid iomem size. "
- "You may experience problems.\n");
- }
-
- if ((pdev->class & 0x0000FF) == PCI_SDHCI_IFVENDOR) {
- printk(KERN_ERR DRIVER_NAME ": Vendor specific interface. Aborting.\n");
- return -ENODEV;
- }
-
- if ((pdev->class & 0x0000FF) > PCI_SDHCI_IFVENDOR) {
- printk(KERN_ERR DRIVER_NAME ": Unknown interface. Aborting.\n");
- return -ENODEV;
- }
-
- mmc = mmc_alloc_host(sizeof(struct sdhci_host), &pdev->dev);
+ mmc = mmc_alloc_host(sizeof(struct sdhci_host) + priv_size, dev);
if (!mmc)
- return -ENOMEM;
+ return ERR_PTR(-ENOMEM);
host = mmc_priv(mmc);
host->mmc = mmc;
- host->chip = chip;
- chip->hosts[slot] = host;
+ return host;
+}
- host->bar = first_bar + slot;
+EXPORT_SYMBOL_GPL(sdhci_alloc_host);
- host->addr = pci_resource_start(pdev, host->bar);
- host->irq = pdev->irq;
+int sdhci_add_host(struct sdhci_host *host)
+{
+ struct mmc_host *mmc;
+ unsigned int caps;
+ unsigned int version;
+ int ret;
- DBG("slot %d at 0x%08lx, irq %d\n", slot, host->addr, host->irq);
+ WARN_ON(host == NULL);
+ if (host == NULL)
+ return -EINVAL;
- ret = pci_request_region(pdev, host->bar, mmc_hostname(mmc));
- if (ret)
- goto free;
+ mmc = host->mmc;
- host->ioaddr = ioremap_nocache(host->addr,
- pci_resource_len(pdev, host->bar));
- if (!host->ioaddr) {
- ret = -ENOMEM;
- goto release;
- }
+ if (debug_quirks)
+ host->quirks = debug_quirks;
sdhci_reset(host, SDHCI_RESET_ALL);
@@ -1376,46 +1198,40 @@ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot)
caps = readl(host->ioaddr + SDHCI_CAPABILITIES);
- if (chip->quirks & SDHCI_QUIRK_FORCE_DMA)
+ if (host->quirks & SDHCI_QUIRK_FORCE_DMA)
host->flags |= SDHCI_USE_DMA;
else if (!(caps & SDHCI_CAN_DO_DMA))
DBG("Controller doesn't have DMA capability\n");
else
host->flags |= SDHCI_USE_DMA;
- if ((chip->quirks & SDHCI_QUIRK_BROKEN_DMA) &&
+ if ((host->quirks & SDHCI_QUIRK_BROKEN_DMA) &&
(host->flags & SDHCI_USE_DMA)) {
DBG("Disabling DMA as it is marked broken\n");
host->flags &= ~SDHCI_USE_DMA;
}
- if (((pdev->class & 0x0000FF) != PCI_SDHCI_IFDMA) &&
- (host->flags & SDHCI_USE_DMA)) {
- printk(KERN_WARNING "%s: Will use DMA "
- "mode even though HW doesn't fully "
- "claim to support it.\n", mmc_hostname(mmc));
- }
-
if (host->flags & SDHCI_USE_DMA) {
- if (pci_set_dma_mask(pdev, DMA_32BIT_MASK)) {
- printk(KERN_WARNING "%s: No suitable DMA available. "
- "Falling back to PIO.\n", mmc_hostname(mmc));
- host->flags &= ~SDHCI_USE_DMA;
+ if (host->ops->enable_dma) {
+ if (host->ops->enable_dma(host)) {
+ printk(KERN_WARNING "%s: No suitable DMA "
+ "available. Falling back to PIO.\n",
+ mmc_hostname(mmc));
+ host->flags &= ~SDHCI_USE_DMA;
+ }
}
}
- if (host->flags & SDHCI_USE_DMA)
- pci_set_master(pdev);
- else /* XXX: Hack to get MMC layer to avoid highmem */
- pdev->dma_mask = 0;
+ /* XXX: Hack to get MMC layer to avoid highmem */
+ if (!(host->flags & SDHCI_USE_DMA))
+ mmc_dev(host->mmc)->dma_mask = 0;
host->max_clk =
(caps & SDHCI_CLOCK_BASE_MASK) >> SDHCI_CLOCK_BASE_SHIFT;
if (host->max_clk == 0) {
printk(KERN_ERR "%s: Hardware doesn't specify base clock "
"frequency.\n", mmc_hostname(mmc));
- ret = -ENODEV;
- goto unmap;
+ return -ENODEV;
}
host->max_clk *= 1000000;
@@ -1424,8 +1240,7 @@ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot)
if (host->timeout_clk == 0) {
printk(KERN_ERR "%s: Hardware doesn't specify timeout clock "
"frequency.\n", mmc_hostname(mmc));
- ret = -ENODEV;
- goto unmap;
+ return -ENODEV;
}
if (caps & SDHCI_TIMEOUT_CLK_UNIT)
host->timeout_clk *= 1000;
@@ -1436,7 +1251,7 @@ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot)
mmc->ops = &sdhci_ops;
mmc->f_min = host->max_clk / 256;
mmc->f_max = host->max_clk;
- mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_MULTIWRITE | MMC_CAP_SDIO_IRQ;
+ mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;
if (caps & SDHCI_CAN_DO_HISPD)
mmc->caps |= MMC_CAP_SD_HIGHSPEED;
@@ -1452,8 +1267,7 @@ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot)
if (mmc->ocr_avail == 0) {
printk(KERN_ERR "%s: Hardware doesn't report any "
"support voltages.\n", mmc_hostname(mmc));
- ret = -ENODEV;
- goto unmap;
+ return -ENODEV;
}
spin_lock_init(&host->lock);
@@ -1523,7 +1337,7 @@ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot)
host->led.default_trigger = mmc_hostname(mmc);
host->led.brightness_set = sdhci_led_control;
- ret = led_classdev_register(&pdev->dev, &host->led);
+ ret = led_classdev_register(mmc_dev(mmc), &host->led);
if (ret)
goto reset;
#endif
@@ -1532,8 +1346,8 @@ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot)
mmc_add_host(mmc);
- printk(KERN_INFO "%s: SDHCI at 0x%08lx irq %d %s\n",
- mmc_hostname(mmc), host->addr, host->irq,
+ printk(KERN_INFO "%s: SDHCI controller on %s [%s] using %s\n",
+ mmc_hostname(mmc), host->hw_name, mmc_dev(mmc)->bus_id,
(host->flags & SDHCI_USE_DMA)?"DMA":"PIO");
return 0;
@@ -1546,35 +1360,40 @@ reset:
untasklet:
tasklet_kill(&host->card_tasklet);
tasklet_kill(&host->finish_tasklet);
-unmap:
- iounmap(host->ioaddr);
-release:
- pci_release_region(pdev, host->bar);
-free:
- mmc_free_host(mmc);
return ret;
}
-static void sdhci_remove_slot(struct pci_dev *pdev, int slot)
+EXPORT_SYMBOL_GPL(sdhci_add_host);
+
+void sdhci_remove_host(struct sdhci_host *host, int dead)
{
- struct sdhci_chip *chip;
- struct mmc_host *mmc;
- struct sdhci_host *host;
+ unsigned long flags;
- chip = pci_get_drvdata(pdev);
- host = chip->hosts[slot];
- mmc = host->mmc;
+ if (dead) {
+ spin_lock_irqsave(&host->lock, flags);
- chip->hosts[slot] = NULL;
+ host->flags |= SDHCI_DEVICE_DEAD;
- mmc_remove_host(mmc);
+ if (host->mrq) {
+ printk(KERN_ERR "%s: Controller removed during "
+ " transfer!\n", mmc_hostname(host->mmc));
+
+ host->mrq->cmd->error = -ENOMEDIUM;
+ tasklet_schedule(&host->finish_tasklet);
+ }
+
+ spin_unlock_irqrestore(&host->lock, flags);
+ }
+
+ mmc_remove_host(host->mmc);
#ifdef CONFIG_LEDS_CLASS
led_classdev_unregister(&host->led);
#endif
- sdhci_reset(host, SDHCI_RESET_ALL);
+ if (!dead)
+ sdhci_reset(host, SDHCI_RESET_ALL);
free_irq(host->irq, host);
@@ -1582,107 +1401,16 @@ static void sdhci_remove_slot(struct pci_dev *pdev, int slot)
tasklet_kill(&host->card_tasklet);
tasklet_kill(&host->finish_tasklet);
-
- iounmap(host->ioaddr);
-
- pci_release_region(pdev, host->bar);
-
- mmc_free_host(mmc);
}
-static int __devinit sdhci_probe(struct pci_dev *pdev,
- const struct pci_device_id *ent)
-{
- int ret, i;
- u8 slots, rev;
- struct sdhci_chip *chip;
-
- BUG_ON(pdev == NULL);
- BUG_ON(ent == NULL);
-
- pci_read_config_byte(pdev, PCI_CLASS_REVISION, &rev);
-
- printk(KERN_INFO DRIVER_NAME
- ": SDHCI controller found at %s [%04x:%04x] (rev %x)\n",
- pci_name(pdev), (int)pdev->vendor, (int)pdev->device,
- (int)rev);
-
- ret = pci_read_config_byte(pdev, PCI_SLOT_INFO, &slots);
- if (ret)
- return ret;
-
- slots = PCI_SLOT_INFO_SLOTS(slots) + 1;
- DBG("found %d slot(s)\n", slots);
- if (slots == 0)
- return -ENODEV;
-
- ret = pci_enable_device(pdev);
- if (ret)
- return ret;
-
- chip = kzalloc(sizeof(struct sdhci_chip) +
- sizeof(struct sdhci_host*) * slots, GFP_KERNEL);
- if (!chip) {
- ret = -ENOMEM;
- goto err;
- }
-
- chip->pdev = pdev;
- chip->quirks = ent->driver_data;
-
- if (debug_quirks)
- chip->quirks = debug_quirks;
-
- chip->num_slots = slots;
- pci_set_drvdata(pdev, chip);
-
- for (i = 0;i < slots;i++) {
- ret = sdhci_probe_slot(pdev, i);
- if (ret) {
- for (i--;i >= 0;i--)
- sdhci_remove_slot(pdev, i);
- goto free;
- }
- }
+EXPORT_SYMBOL_GPL(sdhci_remove_host);
- return 0;
-
-free:
- pci_set_drvdata(pdev, NULL);
- kfree(chip);
-
-err:
- pci_disable_device(pdev);
- return ret;
-}
-
-static void __devexit sdhci_remove(struct pci_dev *pdev)
+void sdhci_free_host(struct sdhci_host *host)
{
- int i;
- struct sdhci_chip *chip;
-
- chip = pci_get_drvdata(pdev);
-
- if (chip) {
- for (i = 0;i < chip->num_slots;i++)
- sdhci_remove_slot(pdev, i);
-
- pci_set_drvdata(pdev, NULL);
-
- kfree(chip);
- }
-
- pci_disable_device(pdev);
+ mmc_free_host(host->mmc);
}
-static struct pci_driver sdhci_driver = {
- .name = DRIVER_NAME,
- .id_table = pci_ids,
- .probe = sdhci_probe,
- .remove = __devexit_p(sdhci_remove),
- .suspend = sdhci_suspend,
- .resume = sdhci_resume,
-};
+EXPORT_SYMBOL_GPL(sdhci_free_host);
/*****************************************************************************\
* *
@@ -1696,14 +1424,11 @@ static int __init sdhci_drv_init(void)
": Secure Digital Host Controller Interface driver\n");
printk(KERN_INFO DRIVER_NAME ": Copyright(c) Pierre Ossman\n");
- return pci_register_driver(&sdhci_driver);
+ return 0;
}
static void __exit sdhci_drv_exit(void)
{
- DBG("Exiting\n");
-
- pci_unregister_driver(&sdhci_driver);
}
module_init(sdhci_drv_init);
@@ -1712,7 +1437,7 @@ module_exit(sdhci_drv_exit);
module_param(debug_quirks, uint, 0444);
MODULE_AUTHOR("Pierre Ossman <drzeus@drzeus.cx>");
-MODULE_DESCRIPTION("Secure Digital Host Controller Interface driver");
+MODULE_DESCRIPTION("Secure Digital Host Controller Interface core driver");
MODULE_LICENSE("GPL");
MODULE_PARM_DESC(debug_quirks, "Force certain quirks.");
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index 299118de8933..3d0a2c2963f5 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -10,18 +10,6 @@
*/
/*
- * PCI registers
- */
-
-#define PCI_SDHCI_IFPIO 0x00
-#define PCI_SDHCI_IFDMA 0x01
-#define PCI_SDHCI_IFVENDOR 0x02
-
-#define PCI_SLOT_INFO 0x40 /* 8 bits */
-#define PCI_SLOT_INFO_SLOTS(x) ((x >> 4) & 7)
-#define PCI_SLOT_INFO_FIRST_BAR_MASK 0x07
-
-/*
* Controller registers
*/
@@ -162,10 +150,39 @@
#define SDHCI_SPEC_VER_MASK 0x00FF
#define SDHCI_SPEC_VER_SHIFT 0
-struct sdhci_chip;
+struct sdhci_ops;
struct sdhci_host {
- struct sdhci_chip *chip;
+ /* Data set by hardware interface driver */
+ const char *hw_name; /* Hardware bus name */
+
+ unsigned int quirks; /* Deviations from spec. */
+
+/* Controller doesn't honor resets unless we touch the clock register */
+#define SDHCI_QUIRK_CLOCK_BEFORE_RESET (1<<0)
+/* Controller has bad caps bits, but really supports DMA */
+#define SDHCI_QUIRK_FORCE_DMA (1<<1)
+/* Controller doesn't like to be reset when there is no card inserted. */
+#define SDHCI_QUIRK_NO_CARD_NO_RESET (1<<2)
+/* Controller doesn't like clearing the power reg before a change */
+#define SDHCI_QUIRK_SINGLE_POWER_WRITE (1<<3)
+/* Controller has flaky internal state so reset it on each ios change */
+#define SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS (1<<4)
+/* Controller has an unusable DMA engine */
+#define SDHCI_QUIRK_BROKEN_DMA (1<<5)
+/* Controller can only DMA from 32-bit aligned addresses */
+#define SDHCI_QUIRK_32BIT_DMA_ADDR (1<<6)
+/* Controller can only DMA chunk sizes that are a multiple of 32 bits */
+#define SDHCI_QUIRK_32BIT_DMA_SIZE (1<<7)
+/* Controller needs to be reset after each request to stay stable */
+#define SDHCI_QUIRK_RESET_AFTER_REQUEST (1<<8)
+
+ int irq; /* Device IRQ */
+ void __iomem * ioaddr; /* Mapped address */
+
+ const struct sdhci_ops *ops; /* Low level hw interface */
+
+ /* Internal data */
struct mmc_host *mmc; /* MMC structure */
#ifdef CONFIG_LEDS_CLASS
@@ -177,6 +194,7 @@ struct sdhci_host {
int flags; /* Host attributes */
#define SDHCI_USE_DMA (1<<0) /* Host is DMA capable */
#define SDHCI_REQ_USE_DMA (1<<1) /* Use DMA for this req. */
+#define SDHCI_DEVICE_DEAD (1<<2) /* Device unresponsive */
unsigned int max_clk; /* Max possible freq (MHz) */
unsigned int timeout_clk; /* Timeout freq (KHz) */
@@ -194,22 +212,33 @@ struct sdhci_host {
int offset; /* Offset into current sg */
int remain; /* Bytes left in current */
- int irq; /* Device IRQ */
- int bar; /* PCI BAR index */
- unsigned long addr; /* Bus address */
- void __iomem * ioaddr; /* Mapped address */
-
struct tasklet_struct card_tasklet; /* Tasklet structures */
struct tasklet_struct finish_tasklet;
struct timer_list timer; /* Timer for timeouts */
-};
-struct sdhci_chip {
- struct pci_dev *pdev;
+ unsigned long private[0] ____cacheline_aligned;
+};
- unsigned long quirks;
- int num_slots; /* Slots on controller */
- struct sdhci_host *hosts[0]; /* Pointers to hosts */
+struct sdhci_ops {
+ int (*enable_dma)(struct sdhci_host *host);
};
+
+
+extern struct sdhci_host *sdhci_alloc_host(struct device *dev,
+ size_t priv_size);
+extern void sdhci_free_host(struct sdhci_host *host);
+
+static inline void *sdhci_priv(struct sdhci_host *host)
+{
+ return (void *)host->private;
+}
+
+extern int sdhci_add_host(struct sdhci_host *host);
+extern void sdhci_remove_host(struct sdhci_host *host, int dead);
+
+#ifdef CONFIG_PM
+extern int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state);
+extern int sdhci_resume_host(struct sdhci_host *host);
+#endif
diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h
index 0d508ac17d64..85dda43eb904 100644
--- a/include/linux/mmc/card.h
+++ b/include/linux/mmc/card.h
@@ -94,6 +94,7 @@ struct mmc_card {
#define MMC_STATE_READONLY (1<<1) /* card is read-only */
#define MMC_STATE_HIGHSPEED (1<<2) /* card is in high speed mode */
#define MMC_STATE_BLOCKADDR (1<<3) /* card uses block-addressing */
+#define MMC_STATE_LOCKED (1<<4) /* card is currently locked */
u32 raw_cid[4]; /* raw card CID */
u32 raw_csd[4]; /* raw card CSD */
@@ -121,11 +122,13 @@ struct mmc_card {
#define mmc_card_readonly(c) ((c)->state & MMC_STATE_READONLY)
#define mmc_card_highspeed(c) ((c)->state & MMC_STATE_HIGHSPEED)
#define mmc_card_blockaddr(c) ((c)->state & MMC_STATE_BLOCKADDR)
+#define mmc_card_locked(c) ((c)->state & MMC_STATE_LOCKED)
#define mmc_card_set_present(c) ((c)->state |= MMC_STATE_PRESENT)
#define mmc_card_set_readonly(c) ((c)->state |= MMC_STATE_READONLY)
#define mmc_card_set_highspeed(c) ((c)->state |= MMC_STATE_HIGHSPEED)
#define mmc_card_set_blockaddr(c) ((c)->state |= MMC_STATE_BLOCKADDR)
+#define mmc_card_set_locked(c) ((c)->state |= MMC_STATE_LOCKED)
#define mmc_card_name(c) ((c)->cid.prod_name)
#define mmc_card_id(c) ((c)->dev.bus_id)
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index 7ab962fa1d73..ef3b773cec91 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -51,8 +51,18 @@ struct mmc_ios {
struct mmc_host_ops {
void (*request)(struct mmc_host *host, struct mmc_request *req);
+ /*
+ * Avoid calling these three functions too often or in a "fast path",
+ * since underlaying controller might implement them in an expensive
+ * and/or slow way.
+ *
+ * .get_ro and .get_cd should return >= 0 for their logical values,
+ * or negative errno value in case of error.
+ */
void (*set_ios)(struct mmc_host *host, struct mmc_ios *ios);
int (*get_ro)(struct mmc_host *host);
+ int (*get_cd)(struct mmc_host *host);
+
void (*enable_sdio_irq)(struct mmc_host *host, int enable);
};
@@ -94,6 +104,7 @@ struct mmc_host {
#define MMC_CAP_SD_HIGHSPEED (1 << 3) /* Can do SD high-speed timing */
#define MMC_CAP_SDIO_IRQ (1 << 4) /* Can signal pending SDIO IRQs */
#define MMC_CAP_SPI (1 << 5) /* Talks only SPI protocols */
+#define MMC_CAP_NEEDS_POLL (1 << 6) /* Needs polling for card-detection */
/* host specific block data */
unsigned int max_seg_size; /* see blk_queue_max_segment_size */
diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h
index 4236fbf0b6fb..a9de8e988798 100644
--- a/include/linux/mmc/mmc.h
+++ b/include/linux/mmc/mmc.h
@@ -16,7 +16,6 @@
* Based strongly on code by:
*
* Author: Yong-iL Joh <tolkien@mizi.com>
- * Date : $Date: 2002/06/18 12:37:30 $
*
* Author: Andrew Christian
* 15 May 2002
@@ -280,5 +279,13 @@ struct _mmc_csd {
#define MMC_SWITCH_MODE_CLEAR_BITS 0x02 /* Clear bits which are 1 in value */
#define MMC_SWITCH_MODE_WRITE_BYTE 0x03 /* Set target to value */
+/*
+ * MMC_LOCK_UNLOCK modes
+ */
+#define MMC_LOCK_MODE_ERASE (1<<3)
+#define MMC_LOCK_MODE_UNLOCK (1<<2)
+#define MMC_LOCK_MODE_CLR_PWD (1<<1)
+#define MMC_LOCK_MODE_SET_PWD (1<<0)
+
#endif /* MMC_MMC_PROTOCOL_H */
diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h
index caa000596b25..ebf32717b35d 100644
--- a/include/linux/pci_ids.h
+++ b/include/linux/pci_ids.h
@@ -2188,6 +2188,7 @@
#define PCI_DEVICE_ID_JMICRON_JMB366 0x2366
#define PCI_DEVICE_ID_JMICRON_JMB368 0x2368
#define PCI_DEVICE_ID_JMICRON_JMB38X_SD 0x2381
+#define PCI_DEVICE_ID_JMICRON_JMB38X_MMC 0x2382
#define PCI_DEVICE_ID_JMICRON_JMB38X_MS 0x2383
#define PCI_VENDOR_ID_KORENIX 0x1982
diff --git a/include/linux/spi/mmc_spi.h b/include/linux/spi/mmc_spi.h
index d5ca78b93a3b..a3626aedaec9 100644
--- a/include/linux/spi/mmc_spi.h
+++ b/include/linux/spi/mmc_spi.h
@@ -23,6 +23,15 @@ struct mmc_spi_platform_data {
/* sense switch on sd cards */
int (*get_ro)(struct device *);
+ /*
+ * If board does not use CD interrupts, driver can optimize polling
+ * using this function.
+ */
+ int (*get_cd)(struct device *);
+
+ /* Capabilities to pass into mmc core (e.g. MMC_CAP_NEEDS_POLL). */
+ unsigned long caps;
+
/* how long to debounce card detect, in msecs */
u16 detect_delay;