mmc: basic SDIO device model

Add the sdio bus type and basic device handling.

Signed-off-by: Pierre Ossman <drzeus@drzeus.cx>
diff --git a/drivers/mmc/core/Makefile b/drivers/mmc/core/Makefile
index 2fa5ebb..71ab3d1 100644
--- a/drivers/mmc/core/Makefile
+++ b/drivers/mmc/core/Makefile
@@ -9,5 +9,5 @@
 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 \
-				   sdio.o sdio_ops.o
+				   sdio.o sdio_ops.o sdio_bus.o
 
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index 092fa90..9747455 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -29,6 +29,7 @@
 #include "core.h"
 #include "bus.h"
 #include "host.h"
+#include "sdio_bus.h"
 
 #include "mmc_ops.h"
 #include "sd_ops.h"
@@ -739,16 +740,32 @@
 		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);
diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c
index ac0dd68..4443285 100644
--- a/drivers/mmc/core/sdio.c
+++ b/drivers/mmc/core/sdio.c
@@ -13,21 +13,49 @@
 
 #include <linux/mmc/host.h>
 #include <linux/mmc/card.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"
 
+static int sdio_init_func(struct mmc_card *card, unsigned int fn)
+{
+	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;
+
+	card->sdio_func[fn - 1] = func;
+
+	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;
 }
@@ -73,7 +101,7 @@
 int mmc_attach_sdio(struct mmc_host *host, u32 ocr)
 {
 	int err;
-	int funcs;
+	int i, funcs;
 	struct mmc_card *card;
 
 	BUG_ON(!host);
@@ -132,13 +160,16 @@
 	}
 
 	card->type = MMC_TYPE_SDIO;
+	card->sdio_funcs = funcs;
+
+	host->card = card;
 
 	/*
 	 * Set card RCA.
 	 */
 	err = mmc_send_relative_addr(host, &card->rca);
 	if (err)
-		goto free_card;
+		goto remove;
 
 	mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
 
@@ -147,23 +178,46 @@
 	 */
 	err = mmc_select_card(card);
 	if (err)
-		goto free_card;
+		goto remove;
 
-	host->card = card;
+	/*
+	 * 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 reclaim_host;
+		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;
 
-reclaim_host:
+
+remove_added:
+	/* Remove without lock if the device has been added. */
+	mmc_sdio_remove(host);
 	mmc_claim_host(host);
-free_card:
-	mmc_remove_card(card);
-	host->card = NULL;
+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);
diff --git a/drivers/mmc/core/sdio_bus.c b/drivers/mmc/core/sdio_bus.c
new file mode 100644
index 0000000..59c909e
--- /dev/null
+++ b/drivers/mmc/core/sdio_bus.c
@@ -0,0 +1,129 @@
+/*
+ *  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_bus.h"
+
+#define dev_to_sdio_func(d)	container_of(d, struct sdio_func, dev)
+
+/*
+ * This currently matches any SDIO function to any driver in order
+ * to help initial development and testing.
+ */
+static int sdio_bus_match(struct device *dev, struct device_driver *drv)
+{
+	return 1;
+}
+
+static int
+sdio_bus_uevent(struct device *dev, char **envp, int num_envp, char *buf,
+		int buf_size)
+{
+	envp[0] = NULL;
+
+	return 0;
+}
+
+static int sdio_bus_probe(struct device *dev)
+{
+	return -ENODEV;
+}
+
+static int sdio_bus_remove(struct device *dev)
+{
+	return 0;
+}
+
+static struct bus_type sdio_bus_type = {
+	.name		= "sdio",
+	.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);
+}
+
+static void sdio_release_func(struct device *dev)
+{
+	struct sdio_func *func = dev_to_sdio_func(dev);
+
+	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 = kmalloc(sizeof(struct sdio_func), GFP_KERNEL);
+	if (!func)
+		return ERR_PTR(-ENOMEM);
+
+	memset(func, 0, sizeof(struct sdio_func));
+
+	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 0000000..567a768
--- /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/include/linux/mmc/card.h b/include/linux/mmc/card.h
index 43480eb..9f5f744 100644
--- a/include/linux/mmc/card.h
+++ b/include/linux/mmc/card.h
@@ -56,6 +56,9 @@
 };
 
 struct mmc_host;
+struct sdio_func;
+
+#define SDIO_MAX_FUNCS		7
 
 /*
  * MMC device
@@ -73,6 +76,7 @@
 #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 */
+
 	u32			raw_cid[4];	/* raw card CID */
 	u32			raw_csd[4];	/* raw card CSD */
 	u32			raw_scr[2];	/* raw card SCR */
@@ -81,6 +85,9 @@
 	struct mmc_ext_csd	ext_csd;	/* mmc v4 extended card specific */
 	struct sd_scr		scr;		/* extra SD information */
 	struct sd_switch_caps	sw_caps;	/* switch (CMD6) caps */
+
+	unsigned int		sdio_funcs;	/* number of SDIO functions */
+	struct sdio_func	*sdio_func[SDIO_MAX_FUNCS]; /* SDIO functions (devices) */
 };
 
 #define mmc_card_mmc(c)		((c)->type == MMC_TYPE_MMC)
diff --git a/include/linux/mmc/sdio_func.h b/include/linux/mmc/sdio_func.h
new file mode 100644
index 0000000..50c78db
--- /dev/null
+++ b/include/linux/mmc/sdio_func.h
@@ -0,0 +1,35 @@
+/*
+ *  include/linux/mmc/sdio_func.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_SDIO_FUNC_H
+#define MMC_SDIO_FUNC_H
+
+struct mmc_card;
+
+/*
+ * SDIO function devices
+ */
+struct sdio_func {
+	struct mmc_card		*card;		/* the card this device belongs to */
+	struct device		dev;		/* the device */
+	unsigned int		num;		/* function number */
+	unsigned int		state;		/* function state */
+#define SDIO_STATE_PRESENT	(1<<0)		/* present in sysfs */
+};
+
+#define sdio_func_present(f)	((f)->state & SDIO_STATE_PRESENT)
+
+#define sdio_func_set_present(f) ((f)->state |= SDIO_STATE_PRESENT)
+
+#define sdio_func_id(f)		((f)->dev.bus_id)
+
+#endif
+