linux-2.6: Add a driver to run an MMC or SD card over a GPIO based SPI interface.
[openwrt-10.03/.git] / package / mmc_over_gpio / src / mmc_over_spigpio.c
diff --git a/package/mmc_over_gpio/src/mmc_over_spigpio.c b/package/mmc_over_gpio/src/mmc_over_spigpio.c
new file mode 100644 (file)
index 0000000..36dbd0f
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ * Driver for driving an MMC card over a bitbanging GPIO SPI bus.
+ *
+ * Copyright 2008 Michael Buesch <mb@bu3sch.de>
+ *
+ * Licensed under the GNU/GPL. See COPYING for details.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include "linux/spi/spi_gpio.h" //XXX
+
+
+/* This is the maximum speed in Hz */
+#define GPIOMMC_MAXSPEED       5000000 /* Hz */
+
+
+#define DRIVER_NAME            "spi-gpio-mmc"
+#define PFX                    DRIVER_NAME ": "
+
+
+#define GPIOMMC_MAX_NAMELEN            15
+#define GPIOMMC_MAX_NAMELEN_STR                __stringify(GPIOMMC_MAX_NAMELEN)
+
+struct gpiommc_pins {
+       unsigned int gpio_di;   /* Card DI pin */
+       unsigned int gpio_do;   /* Card DO pin */
+       unsigned int gpio_clk;  /* Card CLK pin */
+       unsigned int gpio_cs;   /* Card CS pin */
+};
+
+struct gpiommc_device {
+       char name[GPIOMMC_MAX_NAMELEN + 1];
+       struct platform_device *pdev;
+       struct platform_device *spi_pdev;
+       struct gpiommc_pins pins;
+       u8 mode; /* SPI_MODE_X */
+       struct spi_board_info boardinfo;
+
+       struct list_head list;
+};
+
+
+static LIST_HEAD(gpiommc_devices_list);
+static DEFINE_MUTEX(gpiommc_mutex);
+
+
+MODULE_DESCRIPTION("SPI-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;
+
+       /* Bind the SPI master to the MMC-SPI host driver. */
+       strlcpy(bi->modalias, "mmc_spi", sizeof(bi->modalias));
+
+       bi->max_speed_hz = GPIOMMC_MAXSPEED;
+       bi->bus_num = master->bus_num;
+       bi->mode = d->mode;
+
+       return 0;
+}
+
+static int gpiommc_probe(struct platform_device *pdev)
+{
+       static int instance;
+       struct gpiommc_device *d = platform_get_drvdata(pdev);
+       struct spi_gpio_platform_data pdata;
+       int err = -ENOMEM;
+
+       d->spi_pdev = platform_device_alloc("spi-gpio", instance++);
+       if (!d->spi_pdev)
+               goto out;
+
+       memset(&pdata, 0, sizeof(pdata));
+       pdata.pin_clk = d->pins.gpio_clk;
+       pdata.pin_miso = d->pins.gpio_do;
+       pdata.pin_mosi = d->pins.gpio_di;
+       pdata.pin_cs = d->pins.gpio_cs;
+       pdata.cs_activelow = 1;
+       pdata.no_spi_delay = 1;
+       pdata.boardinfo_setup = gpiommc_boardinfo_setup;
+       pdata.boardinfo_setup_data = d;
+
+       err = platform_device_add_data(d->spi_pdev, &pdata, sizeof(pdata));
+       if (err)
+               goto err_free_pdev;
+       err = platform_device_register(d->spi_pdev);
+       if (err)
+               goto err_free_pdata;
+
+       printk(KERN_INFO PFX "MMC-Card \"%s\" "
+              "attached to GPIO pins %u,%u,%u,%u\n",
+              d->name, d->pins.gpio_di, d->pins.gpio_do,
+              d->pins.gpio_clk, d->pins.gpio_cs);
+out:
+       return err;
+
+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);
+       return err;
+}
+
+static int gpiommc_remove(struct platform_device *pdev)
+{
+       struct gpiommc_device *d = platform_get_drvdata(pdev);
+
+       platform_device_unregister(d->spi_pdev);
+       printk(KERN_INFO PFX "MMC-Card \"%s\" removed\n", d->name);
+
+       return 0;
+}
+
+static void gpiommc_free(struct gpiommc_device *d)
+{
+       kfree(d);
+}
+
+static struct gpiommc_device * gpiommc_alloc(struct platform_device *pdev,
+                                            const char *name,
+                                            const struct gpiommc_pins *pins,
+                                            u8 mode)
+{
+       struct gpiommc_device *d;
+
+       d = kmalloc(sizeof(*d), GFP_KERNEL);
+       if (!d)
+               return NULL;
+
+       strcpy(d->name, name);
+       memcpy(&d->pins, pins, sizeof(d->pins));
+       d->mode = mode;
+       INIT_LIST_HEAD(&d->list);
+
+       return d;
+}
+
+/* List must be locked. */
+static struct gpiommc_device * gpiommc_find_device(const char *name)
+{
+       struct gpiommc_device *d;
+
+       list_for_each_entry(d, &gpiommc_devices_list, list) {
+               if (strcmp(d->name, name) == 0)
+                       return d;
+       }
+
+       return NULL;
+}
+
+static void gpiommc_do_destroy_device(struct gpiommc_device *d)
+{
+       list_del(&d->list);
+       platform_device_unregister(d->pdev);
+       gpiommc_free(d);
+}
+
+static int gpiommc_destroy_device(const char *name)
+{
+       struct gpiommc_device *d;
+       int err = -ENODEV;
+
+       mutex_lock(&gpiommc_mutex);
+       d = gpiommc_find_device(name);
+       if (!d)
+               goto out_unlock;
+       gpiommc_do_destroy_device(d);
+       err = 0;
+out_unlock:
+       mutex_unlock(&gpiommc_mutex);
+
+       return err;
+}
+
+static int gpiommc_create_device(const char *name,
+                                const struct gpiommc_pins *pins,
+                                u8 mode)
+{
+       static int instance;
+       struct platform_device *pdev;
+       struct gpiommc_device *d;
+       int err;
+
+       mutex_lock(&gpiommc_mutex);
+       err = -EEXIST;
+       if (gpiommc_find_device(name))
+               goto out_unlock;
+       err = -ENOMEM;
+       pdev = platform_device_alloc(DRIVER_NAME, instance++);
+       if (!pdev)
+               goto out_unlock;
+       d = gpiommc_alloc(pdev, name, pins, mode);
+       if (!d)
+               goto err_free_pdev;
+       platform_set_drvdata(pdev, d);
+       d->pdev = pdev;
+       err = platform_device_register(pdev);
+       if (err)
+               goto err_free_mdev;
+       list_add(&d->list, &gpiommc_devices_list);
+
+       err = 0;
+out_unlock:
+       mutex_unlock(&gpiommc_mutex);
+
+       return err;
+
+err_free_mdev:
+       gpiommc_free(d);
+err_free_pdev:
+       platform_device_put(pdev);
+       goto out_unlock;
+}
+
+static ssize_t gpiommc_add_show(struct device_driver *drv,
+                               char *buf)
+{
+       return snprintf(buf, PAGE_SIZE, "NAME  DI_pin,DO_pin,CLK_pin,CS_pin  [MODE]\n");
+}
+
+static ssize_t gpiommc_add_store(struct device_driver *drv,
+                                const char *buf, size_t count)
+{
+       int res, err;
+       char name[GPIOMMC_MAX_NAMELEN + 1];
+       struct gpiommc_pins pins;
+       unsigned int mode;
+
+       res = sscanf(buf, "%" GPIOMMC_MAX_NAMELEN_STR "s %u,%u,%u,%u %u",
+                    name, &pins.gpio_di, &pins.gpio_do,
+                    &pins.gpio_clk, &pins.gpio_cs, &mode);
+       if (res == 5)
+               mode = 0;
+       else if (res != 6)
+               return -EINVAL;
+       switch (mode) {
+       case 0:
+               mode = SPI_MODE_0;
+               break;
+       case 1:
+               mode = SPI_MODE_1;
+               break;
+       case 2:
+               mode = SPI_MODE_2;
+               break;
+       case 3:
+               mode = SPI_MODE_3;
+               break;
+       default:
+               return -EINVAL;
+       }
+       err = gpiommc_create_device(name, &pins, mode);
+
+       return err ? err : count;
+}
+
+static ssize_t gpiommc_remove_show(struct device_driver *drv,
+                                  char *buf)
+{
+       return snprintf(buf, PAGE_SIZE, "write device-name to remove the device\n");
+}
+
+static ssize_t gpiommc_remove_store(struct device_driver *drv,
+                                   const char *buf, size_t count)
+{
+       int err;
+
+       err = gpiommc_destroy_device(buf);
+
+       return err ? err : count;
+}
+
+static DRIVER_ATTR(add, 0600,
+                  gpiommc_add_show, gpiommc_add_store);
+static DRIVER_ATTR(remove, 0600,
+                  gpiommc_remove_show, gpiommc_remove_store);
+
+static struct platform_driver gpiommc_plat_driver = {
+       .probe  = gpiommc_probe,
+       .remove = gpiommc_remove,
+       .driver = {
+               .name   = DRIVER_NAME,
+               .owner  = THIS_MODULE,
+       },
+};
+
+static int __init gpiommc_modinit(void)
+{
+       int err;
+
+       err = platform_driver_register(&gpiommc_plat_driver);
+       if (err)
+               return err;
+       err = driver_create_file(&gpiommc_plat_driver.driver,
+                                &driver_attr_add);
+       if (err)
+               goto err_drv_unreg;
+       err = driver_create_file(&gpiommc_plat_driver.driver,
+                                &driver_attr_remove);
+       if (err)
+               goto err_remove_add;
+
+       return 0;
+
+err_remove_add:
+       driver_remove_file(&gpiommc_plat_driver.driver,
+                          &driver_attr_add);
+err_drv_unreg:
+       platform_driver_unregister(&gpiommc_plat_driver);
+       return err;
+}
+module_init(gpiommc_modinit);
+
+static void __exit gpiommc_modexit(void)
+{
+       struct gpiommc_device *d, *tmp;
+
+       driver_remove_file(&gpiommc_plat_driver.driver,
+                          &driver_attr_remove);
+       driver_remove_file(&gpiommc_plat_driver.driver,
+                          &driver_attr_add);
+
+       mutex_lock(&gpiommc_mutex);
+       list_for_each_entry_safe(d, tmp, &gpiommc_devices_list, list)
+               gpiommc_do_destroy_device(d);
+       mutex_unlock(&gpiommc_mutex);
+
+       platform_driver_unregister(&gpiommc_plat_driver);
+}
+module_exit(gpiommc_modexit);