Add GPIO-based MMC/SD driver
Subsystems (2)
PATCH v3 1/1 Add GPIO-based MMC/SD driver
This driver hooks up the mmc_spi and spi_gpio modules so that MMC/SD cards can be used on a GPIO based bus by bitbanging the SPI protocol in software.
This driver provides a configfs interface to dynamically create and destroy GPIO-based MMC/SD card interfaces. It also provides a platform device interface API.
See Documentation/gpiommc.txt for details.
---
This driver is used in OpenWrt since quite some time, so please consider for inclusion in mainline.
Changes since v2: The sysfs interface has been replaced by a configfs interface. Although I don't really like the bloat this adds (this is a driver for embedded machines), I do like the much cleaner interface and I think it's worth the bloat.
drivers/mmc/host/gpiommc.c new file +608 -0
@@ -0,0 +1,608 @@ +/* +* Driver an MMC/SD card on a bitbanging GPIO SPI bus. +* This module hooks up the mmc_spi and spi_gpio modules and also +* provides a configfs interface. +* +* Copyright 2008 Michael Buesch <[email protected]> +* +* Licensed under the GNU/GPL. See COPYING for details. +*/ + +#include <linux/mmc/gpiommc.h> +#include <linux/platform_device.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/spi/spi_gpio.h> +#include <linux/configfs.h> +#include <linux/gpio.h> +#include <asm/atomic.h> + + +#define PFX "gpio-mmc: " + + +struct gpiommc_device { +struct platform_device *pdev; +struct platform_device *spi_pdev; +struct spi_board_info boardinfo; +}; + + +MODULE_DESCRIPTION ("GPIO based MMC driver" ); +MODULE_AUTHOR ("Michael Buesch" ); +MODULE_LICENSE ("GPL" ); + + +static int gpiommc_boardinfo_setup (struct spi_board_info *bi, +struct spi_master *master, +void *data) +{ +struct gpiommc_device *d = data; +struct gpiommc_platform_data *pdata = d->pdev->dev.platform_data; + +/* Bind the SPI master to the MMC-SPI host driver. */ +strlcpy (bi->modalias,"mmc_spi" ,sizeof (bi->modalias)); + + bi->max_speed_hz = pdata->max_bus_speed; + bi->bus_num = master->bus_num; + bi->mode = pdata->mode; + +return 0 ; +} + +static int gpiommc_probe (struct platform_device *pdev) +{ +struct gpiommc_platform_data *mmc_pdata = pdev->dev.platform_data; +struct spi_gpio_platform_data spi_pdata; +struct gpiommc_device *d; +int err; + + err = -ENXIO ; +if (!mmc_pdata) + goto error; + +#ifdef CONFIG_MMC_SPI_MODULE + err =request_module ("mmc_spi" ); +if (err) { + printk(KERN_WARNING PFX + "Failed to request mmc_spi module.\n"); + } +#endif /* CONFIG_MMC_SPI_MODULE */ + +/* Allocate the GPIO-MMC device */ + err = -ENOMEM ; + d =kzalloc (sizeof (*d),GFP_KERNEL ); +if (!d) + goto error; + d->pdev = pdev; + +/* Create the SPI-GPIO device */ + d->spi_pdev =platform_device_alloc (SPI_GPIO_PLATDEV_NAME , +spi_gpio_next_id ()); +if (!d->spi_pdev) + goto err_free_d; + +memset (&spi_pdata,0 ,sizeof (spi_pdata)); + spi_pdata.pin_clk = mmc_pdata->pins.gpio_clk; + spi_pdata.pin_miso = mmc_pdata->pins.gpio_do; + spi_pdata.pin_mosi = mmc_pdata->pins.gpio_di; + spi_pdata.pin_cs = mmc_pdata->pins.gpio_cs; + spi_pdata.cs_activelow = mmc_pdata->pins.cs_activelow; + spi_pdata.no_spi_delay = mmc_pdata->no_spi_delay; + spi_pdata.boardinfo_setup = gpiommc_boardinfo_setup; + spi_pdata.boardinfo_setup_data = d; + + err =platform_device_add_data (d->spi_pdev, &spi_pdata, +sizeof (spi_pdata)); +if (err) + goto err_free_pdev; + err =platform_device_add (d->spi_pdev); +if (err) + goto err_free_pdata; +platform_set_drvdata (pdev, d); + + printk(KERN_INFO PFX "MMC-Card \"%s\" " +"attached to GPIO pins di=%u, do=%u, clk=%u, cs=%u\n" , + mmc_pdata->name, mmc_pdata->pins.gpio_di, + mmc_pdata->pins.gpio_do, + mmc_pdata->pins.gpio_clk, + mmc_pdata->pins.gpio_cs); + +return 0 ; + +err_free_pdata: +kfree (d->spi_pdev->dev.platform_data); + d->spi_pdev->dev.platform_data =NULL ; +err_free_pdev: +platform_device_put (d->spi_pdev); +err_free_d: +kfree (d); +error: +return err; +} + +static int gpiommc_remove (struct platform_device *pdev) +{ +struct gpiommc_device *d =platform_get_drvdata (pdev); +struct gpiommc_platform_data *pdata = d->pdev->dev.platform_data; + +platform_device_unregister (d->spi_pdev); + printk(KERN_INFO PFX "GPIO based MMC-Card \"%s\" removed\n", + pdata->name); +platform_device_put (d->spi_pdev); + +return 0 ; +} + +#ifdef CONFIG_GPIOMMC_CONFIGFS + +/* A device that was created through configfs */ +struct gpiommc_configfs_device { +struct config_item item; +/* The platform device, after registration. */ +struct platform_device *pdev; +/* The configuration */ +struct gpiommc_platform_data pdata; +}; + +#define GPIO_INVALID -1 + +static inline bool gpiommc_is_registered (struct gpiommc_configfs_device *dev) +{ +return (dev->pdev !=NULL ); +} + +static inline struct gpiommc_configfs_device *ci_to_gpiommc (struct config_item *item) +{ + return item ? container_of(item, struct gpiommc_configfs_device, item) : NULL; +} + +static struct configfs_attribute gpiommc_attr_DI = { + .ca_owner =THIS_MODULE , + .ca_name ="gpio_data_in" , + .ca_mode =S_IRUGO |S_IWUSR , +}; + +static struct configfs_attribute gpiommc_attr_DO = { + .ca_owner =THIS_MODULE , + .ca_name ="gpio_data_out" , + .ca_mode =S_IRUGO |S_IWUSR , +}; + +static struct configfs_attribute gpiommc_attr_CLK = { + .ca_owner =THIS_MODULE , + .ca_name ="gpio_clock" , + .ca_mode =S_IRUGO |S_IWUSR , +}; + +static struct configfs_attribute gpiommc_attr_CS = { + .ca_owner =THIS_MODULE , + .ca_name ="gpio_chipselect" , + .ca_mode =S_IRUGO |S_IWUSR , +}; + +static struct configfs_attribute gpiommc_attr_CS_activelow = { + .ca_owner =THIS_MODULE , + .ca_name ="gpio_chipselect_activelow" , + .ca_mode =S_IRUGO |S_IWUSR , +}; + +static struct configfs_attribute gpiommc_attr_spimode = { + .ca_owner =THIS_MODULE , + .ca_name ="spi_mode" , + .ca_mode =S_IRUGO |S_IWUSR , +}; + +static struct configfs_attribute gpiommc_attr_spidelay = { + .ca_owner =THIS_MODULE , + .ca_name ="spi_delay" , + .ca_mode =S_IRUGO |S_IWUSR , +}; + +static struct configfs_attribute gpiommc_attr_max_bus_speed = { + .ca_owner =THIS_MODULE , + .ca_name ="max_bus_speed" , + .ca_mode =S_IRUGO |S_IWUSR , +}; + +static struct configfs_attribute gpiommc_attr_register = { + .ca_owner =THIS_MODULE , + .ca_name ="register" , + .ca_mode =S_IRUGO |S_IWUSR , +}; + +static struct configfs_attribute *gpiommc_config_attrs[] = { + &gpiommc_attr_DI, + &gpiommc_attr_DO, + &gpiommc_attr_CLK, + &gpiommc_attr_CS, + &gpiommc_attr_CS_activelow, + &gpiommc_attr_spimode, + &gpiommc_attr_spidelay, + &gpiommc_attr_max_bus_speed, + &gpiommc_attr_register, +NULL , +}; + +static ssize_t gpiommc_config_attr_show (struct config_item *item, +struct configfs_attribute *attr, +char *page) +{ +struct gpiommc_configfs_device *dev =ci_to_gpiommc (item); +ssize_t count =0 ; +unsigned int gpio; +int err =0 ; + +if (attr == &gpiommc_attr_DI) { + gpio = dev->pdata.pins.gpio_di; +if (gpio ==GPIO_INVALID ) + count =snprintf (page,PAGE_SIZE ,"not configured\n" ); +else + count =snprintf (page,PAGE_SIZE ,"%u\n" , gpio); + goto out; + } +if (attr == &gpiommc_attr_DO) { + gpio = dev->pdata.pins.gpio_do; +if (gpio ==GPIO_INVALID ) + count =snprintf (page,PAGE_SIZE ,"not configured\n" ); +else + count =snprintf (page,PAGE_SIZE ,"%u\n" , gpio); + goto out; + } +if (attr == &gpiommc_attr_CLK) { + gpio = dev->pdata.pins.gpio_clk; +if (gpio ==GPIO_INVALID ) + count =snprintf (page,PAGE_SIZE ,"not configured\n" ); +else + count =snprintf (page,PAGE_SIZE ,"%u\n" , gpio); + goto out; + } +if (attr == &gpiommc_attr_CS) { + gpio = dev->pdata.pins.gpio_cs; +if (gpio ==GPIO_INVALID ) + count =snprintf (page,PAGE_SIZE ,"not configured\n" ); +else + count =snprintf (page,PAGE_SIZE ,"%u\n" , gpio); + goto out; + } +if (attr == &gpiommc_attr_CS_activelow) { + count =snprintf (page,PAGE_SIZE ,"%u\n" , + dev->pdata.pins.cs_activelow); + goto out; + } +if (attr == &gpiommc_attr_spimode) { + count =snprintf (page,PAGE_SIZE ,"%u\n" , + dev->pdata.mode); + goto out; + } +if (attr == &gpiommc_attr_spidelay) { + count =snprintf (page,PAGE_SIZE ,"%u\n" , + !dev->pdata.no_spi_delay); + goto out; + } +if (attr == &gpiommc_attr_max_bus_speed) { + count =snprintf (page,PAGE_SIZE ,"%u\n" , + dev->pdata.max_bus_speed); + goto out; + } +if (attr == &gpiommc_attr_register) { + count =snprintf (page,PAGE_SIZE ,"%u\n" , +gpiommc_is_registered (dev)); + goto out; + } +WARN_ON (1 ); + err = -ENOSYS ; +out: +return err ? err : count; +} + +static int gpiommc_do_register (struct gpiommc_configfs_device *dev, +const char *name) +{ +int err; + +if (gpiommc_is_registered (dev)) +return 0 ; + +if (!gpio_is_valid (dev->pdata.pins.gpio_di) || + !gpio_is_valid (dev->pdata.pins.gpio_do) || + !gpio_is_valid (dev->pdata.pins.gpio_clk) || + !gpio_is_valid (dev->pdata.pins.gpio_cs)) { + printk(KERN_ERR PFX + "configfs: Invalid GPIO pin number(s)\n"); +return -EINVAL ; + } + +strlcpy (dev->pdata.name, name, +sizeof (dev->pdata.name)); + + dev->pdev =platform_device_alloc (GPIOMMC_PLATDEV_NAME , +gpiommc_next_id ()); +if (!dev->pdev) +return -ENOMEM ; + err =platform_device_add_data (dev->pdev, &dev->pdata, +sizeof (dev->pdata)); +if (err) { +platform_device_put (dev->pdev); +return err; + } + err =platform_device_add (dev->pdev); +if (err) { +platform_device_put (dev->pdev); +return err; + } + +return 0 ; +} + +static void gpiommc_do_unregister (struct gpiommc_configfs_device *dev) +{ +if (!gpiommc_is_registered (dev)) +return ; + +platform_device_unregister (dev->pdev); + dev->pdev =NULL ; +} + +static ssize_t gpiommc_config_attr_store (struct config_item *item, +struct configfs_attribute *attr, +const char *page,size_t count) +{ +struct gpiommc_configfs_device *dev =ci_to_gpiommc (item); +int err = -EINVAL ; +unsigned long data; + +if (attr == &gpiommc_attr_register) { + err =strict_strtoul (page,10 , &data); +if (err) + goto out; + err = -EINVAL ; +if (data ==1 ) + err =gpiommc_do_register (dev, item->ci_name); +if (data ==0 ) { +gpiommc_do_unregister (dev); + err =0 ; + } + goto out; + } + +if (gpiommc_is_registered (dev)) { +/* The rest of the config parameters can only be set +* as long as the device is not registered, yet. */ + err = -EBUSY ; + goto out; + } + +if (attr == &gpiommc_attr_DI) { + err =strict_strtoul (page,10 , &data); +if (err) + goto out; + err = -EINVAL ; +if (!gpio_is_valid (data)) + goto out; + dev->pdata.pins.gpio_di = data; + err =0 ; + goto out; + } +if (attr == &gpiommc_attr_DO) { + err =strict_strtoul (page,10 , &data); +if (err) + goto out; + err = -EINVAL ; +if (!gpio_is_valid (data)) + goto out; + dev->pdata.pins.gpio_do = data; + err =0 ; + goto out; + } +if (attr == &gpiommc_attr_CLK) { + err =strict_strtoul (page,10 , &data); +if (err) + goto out; + err = -EINVAL ; +if (!gpio_is_valid (data)) + goto out; + dev->pdata.pins.gpio_clk = data; + err =0 ; + goto out; + } +if (attr == &gpiommc_attr_CS) { + err =strict_strtoul (page,10 , &data); +if (err) + goto out; + err = -EINVAL ; +if (!gpio_is_valid (data)) + goto out; + dev->pdata.pins.gpio_cs = data; + err =0 ; + goto out; + } +if (attr == &gpiommc_attr_CS_activelow) { + err =strict_strtoul (page,10 , &data); +if (err) + goto out; + err = -EINVAL ; +if (data !=0 && data !=1 ) + goto out; + dev->pdata.pins.cs_activelow = data; + err =0 ; + goto out; + } +if (attr == &gpiommc_attr_spimode) { + err =strict_strtoul (page,10 , &data); +if (err) + goto out; + err = -EINVAL ; +switch (data) { +case 0 : + dev->pdata.mode =SPI_MODE_0 ; +break ; +case 1 : + dev->pdata.mode =SPI_MODE_1 ; +break ; +case 2 : + dev->pdata.mode =SPI_MODE_2 ; +break ; +case 3 : + dev->pdata.mode =SPI_MODE_3 ; +break ; +default : + goto out; + } + err =0 ; + goto out; + } +if (attr == &gpiommc_attr_spidelay) { + err =strict_strtoul (page,10 , &data); +if (err) + goto out; + err = -EINVAL ; +if (data !=0 && data !=1 ) + goto out; + dev->pdata.no_spi_delay = !data; + err =0 ; + goto out; + } +if (attr == &gpiommc_attr_max_bus_speed) { + err =strict_strtoul (page,10 , &data); +if (err) + goto out; + err = -EINVAL ; +if (data >UINT_MAX ) + goto out; + dev->pdata.max_bus_speed = data; + err =0 ; + goto out; + } +WARN_ON (1 ); + err = -ENOSYS ; +out: +return err ? err : count; +} + +static void gpiommc_config_item_release (struct config_item *item) +{ +struct gpiommc_configfs_device *dev =ci_to_gpiommc (item); + +kfree (dev); +} + +static struct configfs_item_operations gpiommc_config_item_ops = { + .release = gpiommc_config_item_release, + .show_attribute = gpiommc_config_attr_show, + .store_attribute = gpiommc_config_attr_store, +}; + +static struct config_item_type gpiommc_dev_ci_type = { + .ct_item_ops = &gpiommc_config_item_ops, + .ct_attrs = gpiommc_config_attrs, + .ct_owner =THIS_MODULE , +}; + +static struct config_item *gpiommc_make_item (struct config_group *group, +const char *name) +{ +struct gpiommc_configfs_device *dev; + +if (strlen (name) >GPIOMMC_MAX_NAMELEN ) { + printk(KERN_ERR PFX "configfs: device name too long\n"); +return NULL ; + } + + dev =kzalloc (sizeof (*dev),GFP_KERNEL ); +if (!dev) +return NULL ; + +config_item_init_type_name (&dev->item, name, + &gpiommc_dev_ci_type); + +/* Assign default configuration */ + dev->pdata.pins.gpio_di =GPIO_INVALID ; + dev->pdata.pins.gpio_do =GPIO_INVALID ; + dev->pdata.pins.gpio_clk =GPIO_INVALID ; + dev->pdata.pins.gpio_cs =GPIO_INVALID ; + dev->pdata.pins.cs_activelow =1 ; + dev->pdata.mode =SPI_MODE_0 ; + dev->pdata.no_spi_delay =0 ; + dev->pdata.max_bus_speed =5000000 ;/* 5 MHz */ + +return &(dev->item); +} + +static void gpiommc_drop_item (struct config_group *group, +struct config_item *item) +{ +struct gpiommc_configfs_device *dev =ci_to_gpiommc (item); + +gpiommc_do_unregister (dev); +kfree (dev); +} + +static struct configfs_group_operations gpiommc_ct_group_ops = { + .make_item = gpiommc_make_item, + .drop_item = gpiommc_drop_item, +}; + +static struct config_item_type gpiommc_ci_type = { + .ct_group_ops = &gpiommc_ct_group_ops, + .ct_owner =THIS_MODULE , +}; + +static struct configfs_subsystem gpiommc_subsys = { + .su_group = { + .cg_item = { + .ci_namebuf =GPIOMMC_PLATDEV_NAME , + .ci_type = &gpiommc_ci_type, + }, + }, + .su_mutex =__MUTEX_INITIALIZER (gpiommc_subsys.su_mutex), +}; + +#endif /* CONFIG_GPIOMMC_CONFIGFS */ + +static struct platform_driver gpiommc_plat_driver = { + .probe = gpiommc_probe, + .remove = gpiommc_remove, + .driver = { + .name =GPIOMMC_PLATDEV_NAME , + .owner =THIS_MODULE , + }, +}; + +int gpiommc_next_id (void ) +{ +static atomic_t counter =ATOMIC_INIT (-1 ); + +return atomic_inc_return (&counter); +} +EXPORT_SYMBOL (gpiommc_next_id); + +static int __init gpiommc_modinit (void ) +{ +int err; + + err =platform_driver_register (&gpiommc_plat_driver); +if (err) +return err; + +#ifdef CONFIG_GPIOMMC_CONFIGFS +config_group_init (&gpiommc_subsys.su_group); + err =configfs_register_subsystem (&gpiommc_subsys); +if (err) { +platform_driver_unregister (&gpiommc_plat_driver); +return err; + } +#endif /* CONFIG_GPIOMMC_CONFIGFS */ + +return 0 ; +} +module_init (gpiommc_modinit); + +static void __exit gpiommc_modexit(void) +{ +#ifdef CONFIG_GPIOMMC_CONFIGFS +configfs_unregister_subsystem (&gpiommc_subsys); +#endif +platform_driver_unregister (&gpiommc_plat_driver); +} +module_exit (gpiommc_modexit);
drivers/mmc/host/Kconfig +24 -0
@@ -174,3 +174,27 @@ config MMC_SDRICOH_CS To compile this driver as a module, choose M here: the module will be called sdricoh_cs. +config GPIOMMC + tristate "MMC/SD over GPIO-based SPI" + depends on MMC && MMC_SPI && SPI_GPIO + help + This driver hooks up the mmc_spi and spi_gpio modules so that + MMC/SD cards can be used on a GPIO based bus by bitbanging + the SPI protocol in software. + + This driver provides a configfs interface to dynamically create + and destroy GPIO-based MMC/SD card devices. It also provides + a platform device interface API. + See Documentation/gpiommc.txt for details. + + The module will be called gpiommc. + + If unsure, say N. + +config GPIOMMC_CONFIGFS + bool + depends on GPIOMMC && CONFIGFS_FS + default y + help + This option automatically enables configfs support for gpiommc + if configfs is available.
drivers/mmc/host/Makefile +1 -1
@@ -21,4 +21,4 @@ obj-$(CONFIG_MMC_TIFM_SD) += tifm_sd.o obj-$(CONFIG_MMC_SPI) += mmc_spi.o obj-$(CONFIG_MMC_S3C) += s3cmci.o obj-$(CONFIG_MMC_SDRICOH_CS) += sdricoh_cs.o - +obj-$(CONFIG_GPIOMMC) += gpiommc.o
include/linux/mmc/gpiommc.h new file +71 -0
71 lines not on this page · raw message
Documentation/gpiommc.txt new file +97 -0
97 lines not on this page · raw message
MAINTAINERS +5 -0
@@ -1880,6 +1880,11 @@ L: [email protected]. W: http://gigaset307x.sourceforge.net/ S: Maintained +GPIOMMC DRIVER +P: Michael Buesch +M: [email protected] +S: Maintained + HARDWARE MONITORING P: Mark M. Hoffman M: [email protected]
Signature · 1 line
Greetings Michael.
Documentation/gpiommc.txt
Quoted 25 lines · On Sun, 20 Jul 2008 21:48:58 +0200 Michael Buesch wrote:
This driver hooks up the
mmc_spiandspi_gpiomodules so that MMC/SD cards can be used on a GPIO based bus by bitbanging the SPI protocol in software.This driver provides a configfs interface to dynamically create and destroy GPIO-based MMC/SD card interfaces. It also provides a platform device interface API.
SeeDocumentation/gpiommc.txtfor details.Signed-off-by Michael Buesch <[email protected]>---
This driver is used in OpenWrt since quite some time, so please consider for inclusion in mainline.
Changes since v2: The sysfs interface has been replaced by a configfs interface. Although I don't really like the bloat this adds (this is a driver for embedded machines), I do like the much cleaner interface and I think it's worth the bloat.
Index:
linux-next/Documentation/gpiommc.txt
===================================================================Documentation/gpiommc.txt new file +0
-0@@ -0,0 +1,97 @@
...
One minor nit below, otherwise looks good to me.
Thanks.
Quoted 38 lines
Documentation/gpiommc.txt new file +36
-0+Registering devices via configfs +================================ + +MMC/SD cards connected via GPIO often are a pretty dynamic thing, as for example +selfmade hacks for soldering an MMC/SD card to standard GPIO pins on embedded +hardware are a common situation. +So we provide a dynamic interface to conveniently handle adding and removing +devices from userspace, without the need to recompile the kernel. + +The "gpiommc" subdirectory at the configfs mountpoint is used for handling +the dynamic configuration. + +To create a new device, it must first be allocated with mkdir. +The following command will allocate a device named "my_mmc": + mkdir /config/gpiommc/my_mmc + +There are several configuration files available in the new +/config/gpiommc/my_mmc/ directory: + +gpio_data_in = The SPI data-IN GPIO pin number. +gpio_data_out = The SPI data-OUT GPIO pin number. +gpio_clock = The SPI Clock GPIO pin number. +gpio_chipselect = The SPI Chipselect GPIO pin number. +gpio_chipselect_activelow = Boolean. If 0, Chipselect is active-HIGH. + If 1, Chipselect is active-LOW. +spi_mode = The SPI data mode. Can be 0-3. +spi_delay = Enable all delays in the lowlevel bitbanging. +max_bus_speed = The maximum SPI bus speed. In Hertz. + +register = Not a configuration parameter. + Used to register the configured card + with the kernel. + +The device must first get configured and then registered by writing "1" to +the "register" file. +The configuration parameters "gpio_data_in", "gpio_data_out", "gpio_clock"
Documentation/gpiommc.txt line 90
Quoted 10 lines
Documentation/gpiommc.txt new file +10
-0+and "gpio_chipselect" are essential and _must_ be configured before writing +"1" to the "register" file. The registration will fail, otherwise. + +The default values for the other parameters are: +gpio_chipselect_activelow = 1 (CS active-LOW) +spi_mode = 0 (SPI_MODE_0) +spi_delay = 1 (enabled) +max_bus_speed = 5000000 (5 Mhz) + +Configuration values can not be changed after registration. To unregister
cannot
Documentation/gpiommc.txt line 92
Quoted 2 lines
Documentation/gpiommc.txt new file +2
-0+the device, write a "0" to the "register" file. The configuration can be +changed again after unregistering.
---
~Randy
Linux Plumbers Conference, 17-19 September 2008, Portland, Oregon USA
http://linuxplumbersconf.org/
Quoted 8 lines · Michael Buesch
This driver hooks up the
mmc_spiandspi_gpiomodules so that MMC/SD cards can be used on a GPIO based bus by bitbanging the SPI protocol in software.This driver provides a configfs interface to dynamically create and destroy GPIO-based MMC/SD card interfaces. It also provides a platform device interface API.
SeeDocumentation/gpiommc.txtfor details.
configfs stuff should still go in Documentation/ABI as it is a userspace/kernelspace interface.
thanks,
greg k-h