summaryrefslogtreecommitdiffstats
path: root/package/mmc_over_gpio/src/mmc_over_spigpio.c
diff options
context:
space:
mode:
Diffstat (limited to 'package/mmc_over_gpio/src/mmc_over_spigpio.c')
-rw-r--r--package/mmc_over_gpio/src/mmc_over_spigpio.c339
1 files changed, 339 insertions, 0 deletions
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
index 000000000..36dbd0f6a
--- /dev/null
+++ b/package/mmc_over_gpio/src/mmc_over_spigpio.c
@@ -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);