mmc: detect SDIO cards

Really basic init sequence for SDIO cards.

Signed-off-by: Pierre Ossman <drzeus@drzeus.cx>
diff --git a/drivers/mmc/core/Makefile b/drivers/mmc/core/Makefile
index 3fdd08c..2fa5ebb 100644
--- a/drivers/mmc/core/Makefile
+++ b/drivers/mmc/core/Makefile
@@ -8,5 +8,6 @@
 
 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
 
diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c
index 817a794..87a6070 100644
--- a/drivers/mmc/core/bus.c
+++ b/drivers/mmc/core/bus.c
@@ -34,6 +34,8 @@
 		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;
 	}
@@ -76,6 +78,9 @@
 	case MMC_TYPE_SD:
 		add_env("MMC_TYPE=%s", "SD");
 		break;
+	case MMC_TYPE_SDIO:
+		add_env("MMC_TYPE=%s", "SDIO");
+		break;
 	}
 
 	add_env("MMC_NAME=%s", mmc_card_name(card));
@@ -221,6 +226,9 @@
 		if (mmc_card_blockaddr(card))
 			type = "SDHC";
 		break;
+	case MMC_TYPE_SDIO:
+		type = "SDIO";
+		break;
 	default:
 		type = "?";
 		break;
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index 51e611f2..092fa90 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -32,9 +32,11 @@
 
 #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;
 
@@ -595,24 +597,38 @@
 
 		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) {
 			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) {
-				if (mmc_attach_mmc(host, ocr))
-					mmc_power_off(host);
-			} else {
-				mmc_power_off(host);
-				mmc_release_host(host);
-			}
+			return;
 		}
+
+		/*
+		 * ...and finally MMC.
+		 */
+		err = mmc_send_op_cond(host, 0, &ocr);
+		if (!err) {
+			if (mmc_attach_mmc(host, ocr))
+				mmc_power_off(host);
+			return;
+		}
+
+		mmc_release_host(host);
+		mmc_power_off(host);
 	} else {
 		if (host->bus_ops->detect && !host->bus_dead)
 			host->bus_ops->detect(host);
diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c
new file mode 100644
index 0000000..ac0dd68
--- /dev/null
+++ b/drivers/mmc/core/sdio.c
@@ -0,0 +1,176 @@
+/*
+ *  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 "core.h"
+#include "bus.h"
+#include "mmc_ops.h"
+#include "sd_ops.h"
+#include "sdio_ops.h"
+
+/*
+ * Host is being removed. Free up the current card.
+ */
+static void mmc_sdio_remove(struct mmc_host *host)
+{
+	BUG_ON(!host);
+	BUG_ON(!host->card);
+
+	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 funcs;
+	struct mmc_card *card;
+
+	BUG_ON(!host);
+	BUG_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;
+
+	/*
+	 * 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;
+
+	/*
+	 * Set card RCA.
+	 */
+	err = mmc_send_relative_addr(host, &card->rca);
+	if (err)
+		goto free_card;
+
+	mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
+
+	/*
+	 * Select card, as all following commands rely on that.
+	 */
+	err = mmc_select_card(card);
+	if (err)
+		goto free_card;
+
+	host->card = card;
+
+	mmc_release_host(host);
+
+	err = mmc_add_card(host->card);
+	if (err)
+		goto reclaim_host;
+
+	return 0;
+
+reclaim_host:
+	mmc_claim_host(host);
+free_card:
+	mmc_remove_card(card);
+	host->card = NULL;
+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_ops.c b/drivers/mmc/core/sdio_ops.c
new file mode 100644
index 0000000..d6f9f9d
--- /dev/null
+++ b/drivers/mmc/core/sdio_ops.c
@@ -0,0 +1,49 @@
+/*
+ *  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 <linux/mmc/host.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_R4 | MMC_CMD_BCR;
+
+	for (i = 100; i; i--) {
+		err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);
+		if (err)
+			break;
+
+		if (cmd.resp[0] & MMC_CARD_BUSY || ocr == 0)
+			break;
+
+		err = -ETIMEDOUT;
+
+		mmc_delay(10);
+	}
+
+	if (rocr)
+		*rocr = cmd.resp[0];
+
+	return err;
+}
+
diff --git a/drivers/mmc/core/sdio_ops.h b/drivers/mmc/core/sdio_ops.h
new file mode 100644
index 0000000..d8c9829
--- /dev/null
+++ b/drivers/mmc/core/sdio_ops.h
@@ -0,0 +1,18 @@
+/*
+ *  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);
+
+#endif
+