From d1f1e397b9e6dfac0812bec87cec3f714a9a2539 Mon Sep 17 00:00:00 2001
From: mb <mb@3c298f89-4303-0410-b956-a3cf2f4a3e73>
Date: Fri, 21 Mar 2008 16:52:27 +0000
Subject: linux-2.6: Add a driver to run an MMC or SD card over a GPIO based
 SPI interface.

git-svn-id: svn://svn.openwrt.org/openwrt/trunk@10637 3c298f89-4303-0410-b956-a3cf2f4a3e73
---
 package/mmc_over_gpio/src/Makefile             |   2 +
 package/mmc_over_gpio/src/linux/spi/spi_gpio.h |  53 ++++
 package/mmc_over_gpio/src/mmc_over_spigpio.c   | 339 +++++++++++++++++++++++++
 package/mmc_over_gpio/src/spi_gpio.c           | 242 ++++++++++++++++++
 4 files changed, 636 insertions(+)
 create mode 100644 package/mmc_over_gpio/src/Makefile
 create mode 100644 package/mmc_over_gpio/src/linux/spi/spi_gpio.h
 create mode 100644 package/mmc_over_gpio/src/mmc_over_spigpio.c
 create mode 100644 package/mmc_over_gpio/src/spi_gpio.c

(limited to 'package/mmc_over_gpio/src')

diff --git a/package/mmc_over_gpio/src/Makefile b/package/mmc_over_gpio/src/Makefile
new file mode 100644
index 000000000..d2258bb95
--- /dev/null
+++ b/package/mmc_over_gpio/src/Makefile
@@ -0,0 +1,2 @@
+obj-m += spi_gpio.o
+obj-m += mmc_over_spigpio.o
diff --git a/package/mmc_over_gpio/src/linux/spi/spi_gpio.h b/package/mmc_over_gpio/src/linux/spi/spi_gpio.h
new file mode 100644
index 000000000..8e7d4b189
--- /dev/null
+++ b/package/mmc_over_gpio/src/linux/spi/spi_gpio.h
@@ -0,0 +1,53 @@
+/*
+ * spi_gpio interface to platform code
+ *
+ * Copyright (c) 2008 Piotr Skamruk
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _LINUX_SPI_SPI_GPIO
+#define _LINUX_SPI_SPI_GPIO
+
+#include <linux/types.h>
+#include <linux/spi/spi.h>
+
+
+/** struct spi_gpio_platform_data - Data definitions for a SPI-GPIO device.
+ * This structure holds information about a GPIO-based SPI device.
+ *
+ * @pin_clk: The GPIO pin number of the CLOCK pin.
+ *
+ * @pin_miso: The GPIO pin number of the MISO pin.
+ *
+ * @pin_mosi: The GPIO pin number of the MOSI pin.
+ *
+ * @pin_cs: The GPIO pin number of the CHIPSELECT pin.
+ *
+ * @cs_activelow: If true, the chip is selected when the CS line is low.
+ *
+ * @no_spi_delay: If true, no delay is done in the lowlevel bitbanging.
+ *                Note that doing no delay is not standards compliant,
+ *                but it might be needed to speed up transfers on some
+ *                slow embedded machines.
+ *
+ * @boardinfo_setup: This callback is called after the
+ *                   SPI master device was registered, but before the
+ *                   device is registered.
+ * @boardinfo_setup_data: Data argument passed to boardinfo_setup().
+ */
+struct spi_gpio_platform_data {
+	unsigned int pin_clk;
+	unsigned int pin_miso;
+	unsigned int pin_mosi;
+	unsigned int pin_cs;
+	bool cs_activelow;
+	bool no_spi_delay;
+	int (*boardinfo_setup)(struct spi_board_info *bi,
+			       struct spi_master *master,
+			       void *data);
+	void *boardinfo_setup_data;
+};
+
+#endif /* _LINUX_SPI_SPI_GPIO */
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);
diff --git a/package/mmc_over_gpio/src/spi_gpio.c b/package/mmc_over_gpio/src/spi_gpio.c
new file mode 100644
index 000000000..31048acc3
--- /dev/null
+++ b/package/mmc_over_gpio/src/spi_gpio.c
@@ -0,0 +1,242 @@
+/*
+ * Bitbanging SPI bus driver using GPIO API
+ *
+ * Copyright (c) 2008 Piotr Skamruk
+ * Copyright (c) 2008 Michael Buesch
+ *
+ * based on spi_s3c2410_gpio.c
+ *   Copyright (c) 2006 Ben Dooks
+ *   Copyright (c) 2006 Simtec Electronics
+ * and on i2c-gpio.c
+ *   Copyright (C) 2007 Atmel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi_bitbang.h>
+#include "linux/spi/spi_gpio.h" //XXX
+#include <asm/gpio.h>
+
+
+struct spi_gpio {
+	struct spi_bitbang bitbang;
+	struct spi_gpio_platform_data *info;
+	struct platform_device *pdev;
+	struct spi_board_info bi;
+};
+
+
+static inline struct spi_gpio *spidev_to_sg(struct spi_device *dev)
+{
+	return dev->controller_data;
+}
+
+static inline void setsck(struct spi_device *dev, int val)
+{
+	struct spi_gpio *sp = spidev_to_sg(dev);
+	gpio_set_value(sp->info->pin_clk, val ? 1 : 0);
+}
+
+static inline void setmosi(struct spi_device *dev, int val )
+{
+	struct spi_gpio *sp = spidev_to_sg(dev);
+	gpio_set_value(sp->info->pin_mosi, val ? 1 : 0);
+}
+
+static inline u32 getmiso(struct spi_device *dev)
+{
+	struct spi_gpio *sp = spidev_to_sg(dev);
+	return gpio_get_value(sp->info->pin_miso) ? 1 : 0;
+}
+
+static inline void do_spidelay(struct spi_device *dev, unsigned nsecs)
+{
+	struct spi_gpio *sp = spidev_to_sg(dev);
+
+	if (!sp->info->no_spi_delay)
+		ndelay(nsecs);
+}
+
+#define spidelay(nsecs) do {					\
+	/* Steal the spi_device pointer from our caller.	\
+	 * The bitbang-API should probably get fixed here... */	\
+	do_spidelay(spi, nsecs);				\
+  } while (0)
+
+#define EXPAND_BITBANG_TXRX
+#include <linux/spi/spi_bitbang.h>
+
+static u32 spi_gpio_txrx_mode0(struct spi_device *spi,
+			       unsigned nsecs, u32 word, u8 bits)
+{
+        return bitbang_txrx_be_cpha0(spi, nsecs, 0, word, bits);
+}
+
+static u32 spi_gpio_txrx_mode1(struct spi_device *spi,
+			       unsigned nsecs, u32 word, u8 bits)
+{
+        return bitbang_txrx_be_cpha1(spi, nsecs, 0, word, bits);
+}
+
+static u32 spi_gpio_txrx_mode2(struct spi_device *spi,
+			       unsigned nsecs, u32 word, u8 bits)
+{
+        return bitbang_txrx_be_cpha0(spi, nsecs, 1, word, bits);
+}
+
+static u32 spi_gpio_txrx_mode3(struct spi_device *spi,
+			       unsigned nsecs, u32 word, u8 bits)
+{
+        return bitbang_txrx_be_cpha1(spi, nsecs, 1, word, bits);
+}
+
+static void spi_gpio_chipselect(struct spi_device *dev, int on)
+{
+	struct spi_gpio *sp = spidev_to_sg(dev);
+
+	if (sp->info->cs_activelow)
+		on = !on;
+	gpio_set_value(sp->info->pin_cs, on ? 1 : 0);
+}
+
+static int spi_gpio_probe(struct platform_device *pdev)
+{
+	struct spi_master *master;
+	struct spi_gpio_platform_data *pdata;
+	struct spi_gpio *sp;
+	struct spi_device *spidev;
+	int err;
+
+	pdata = pdev->dev.platform_data;
+	if (!pdata)
+		return -ENXIO;
+
+	err = -ENOMEM;
+	master = spi_alloc_master(&pdev->dev, sizeof(struct spi_gpio));
+	if (!master)
+		goto err_alloc_master;
+
+	sp = spi_master_get_devdata(master);
+	platform_set_drvdata(pdev, sp);
+	sp->info = pdata;
+
+	err = gpio_request(pdata->pin_clk, "spi_clock");
+	if (err)
+		goto err_request_clk;
+	err = gpio_request(pdata->pin_mosi, "spi_mosi");
+	if (err)
+		goto err_request_mosi;
+	err = gpio_request(pdata->pin_miso, "spi_miso");
+	if (err)
+		goto err_request_miso;
+	err = gpio_request(pdata->pin_cs, "spi_cs");
+	if (err)
+		goto err_request_cs;
+
+	sp->bitbang.master = spi_master_get(master);
+	sp->bitbang.master->bus_num = -1;
+	sp->bitbang.master->num_chipselect = 1;
+	sp->bitbang.chipselect = spi_gpio_chipselect;
+	sp->bitbang.txrx_word[SPI_MODE_0] = spi_gpio_txrx_mode0;
+	sp->bitbang.txrx_word[SPI_MODE_1] = spi_gpio_txrx_mode1;
+	sp->bitbang.txrx_word[SPI_MODE_2] = spi_gpio_txrx_mode2;
+	sp->bitbang.txrx_word[SPI_MODE_3] = spi_gpio_txrx_mode3;
+
+	gpio_direction_output(pdata->pin_clk, 0);
+	gpio_direction_output(pdata->pin_mosi, 0);
+	gpio_direction_output(pdata->pin_cs,
+			      pdata->cs_activelow ? 1 : 0);
+	gpio_direction_input(pdata->pin_miso);
+
+	err = spi_bitbang_start(&sp->bitbang);
+	if (err)
+		goto err_no_bitbang;
+	err = pdata->boardinfo_setup(&sp->bi, master,
+				     pdata->boardinfo_setup_data);
+	if (err)
+		goto err_bi_setup;
+	sp->bi.controller_data = sp;
+	spidev = spi_new_device(master, &sp->bi);
+	if (!spidev)
+		goto err_new_dev;
+
+	return 0;
+
+err_new_dev:
+err_bi_setup:
+	spi_bitbang_stop(&sp->bitbang);
+err_no_bitbang:
+	spi_master_put(sp->bitbang.master);
+	gpio_free(pdata->pin_cs);
+err_request_cs:
+	gpio_free(pdata->pin_miso);
+err_request_miso:
+	gpio_free(pdata->pin_mosi);
+err_request_mosi:
+	gpio_free(pdata->pin_clk);
+err_request_clk:
+	kfree(master);
+
+err_alloc_master:
+	return err;
+}
+
+static int __devexit spi_gpio_remove(struct platform_device *pdev)
+{
+	struct spi_gpio *sp;
+	struct spi_gpio_platform_data *pdata;
+
+	pdata = pdev->dev.platform_data;
+	sp = platform_get_drvdata(pdev);
+
+	gpio_free(pdata->pin_clk);
+	gpio_free(pdata->pin_mosi);
+	gpio_free(pdata->pin_miso);
+	gpio_free(pdata->pin_cs);
+	spi_bitbang_stop(&sp->bitbang);
+	spi_master_put(sp->bitbang.master);
+
+	return 0;
+}
+
+static struct platform_driver spi_gpio_driver = {
+	.driver		= {
+		.name	= "spi-gpio",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= spi_gpio_probe,
+	.remove		= __devexit_p(spi_gpio_remove),
+};
+
+static int __init spi_gpio_init(void)
+{
+	int err;
+
+	err = platform_driver_register(&spi_gpio_driver);
+	if (err)
+		printk(KERN_ERR "spi-gpio: register failed: %d\n", err);
+
+	return err;
+}
+module_init(spi_gpio_init);
+
+static void __exit spi_gpio_exit(void)
+{
+	platform_driver_unregister(&spi_gpio_driver);
+}
+module_exit(spi_gpio_exit);
+
+MODULE_AUTHOR("Piot Skamruk <piotr.skamruk at gmail.com>");
+MODULE_AUTHOR("Michael Buesch");
+MODULE_DESCRIPTION("Platform independent GPIO bitbangling SPI driver");
+MODULE_LICENSE("GPL v2");
-- 
cgit v1.2.3