Patchwork [v2] mtd/maps: gpio-addr-flash: new driver for GPIO assisted flash addressing

login
register
mail settings
Submitter Mike Frysinger
Date June 3, 2009, 12:51 p.m.
Message ID <1244033509-31586-1-git-send-email-vapier@gentoo.org>
Download mbox | patch
Permalink /patch/28058/
State Superseded, archived
Headers show

Comments

Mike Frysinger - June 3, 2009, 12:51 p.m.
This driver lets people use GPIO's for additional address lines in case
their processor does not have enough address lines already.

Signed-off-by: Mike Frysinger <vapier@gentoo.org>
Signed-off-by: Bryan Wu <cooloney@kernel.org>
---
v2
	- add kernel doc suggested by Ben
	- touchup style pointed out by Ben
	- avoid duplication of gpio array pointed out by Ben
	- optimize code a bit

 drivers/mtd/maps/Kconfig           |   10 ++
 drivers/mtd/maps/Makefile          |    1 +
 drivers/mtd/maps/gpio-addr-flash.c |  311 ++++++++++++++++++++++++++++++++++++
 3 files changed, 322 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mtd/maps/gpio-addr-flash.c
David Woodhouse - Sept. 20, 2009, 10:04 p.m.
On Wed, 2009-06-03 at 08:51 -0400, Mike Frysinger wrote:
> This driver lets people use GPIO's for additional address lines in case
> their processor does not have enough address lines already.
> 
> Signed-off-by: Mike Frysinger <vapier@gentoo.org>
> Signed-off-by: Bryan Wu <cooloney@kernel.org>

In file included from /home/dwmw2/git/mtd-2.6/arch/x86/include/asm/gpio.h:20,
                 from drivers/mtd/maps/gpio-addr-flash.c:27:
include/asm-generic/gpio.h: In function ‘gpio_get_value_cansleep’:
include/asm-generic/gpio.h:168: error: implicit declaration of function ‘gpio_get_value’
include/asm-generic/gpio.h: In function ‘gpio_set_value_cansleep’:
include/asm-generic/gpio.h:174: error: implicit declaration of function ‘gpio_set_value’
drivers/mtd/maps/gpio-addr-flash.c: In function ‘gpio_flash_probe’:
drivers/mtd/maps/gpio-addr-flash.c:232: error: implicit declaration of function ‘gpio_request’
drivers/mtd/maps/gpio-addr-flash.c:236: error: implicit declaration of function ‘gpio_free’
drivers/mtd/maps/gpio-addr-flash.c:240: error: implicit declaration of function ‘gpio_direction_output’
make[2]: *** [drivers/mtd/maps/gpio-addr-flash.o] Error 1
make[1]: *** [drivers/mtd/maps] Error 2
make: *** [_module_drivers/mtd] Error 2

Setting CONFIG_GPIOLIB seems to fix it -- but we can't make it depend on
that, can we? Maybe it should be GPIOLIB || !GENERIC_GPIO?
Mike Frysinger - Sept. 20, 2009, 10:49 p.m.
On Sun, Sep 20, 2009 at 18:04, David Woodhouse wrote:
> On Wed, 2009-06-03 at 08:51 -0400, Mike Frysinger wrote:
>> This driver lets people use GPIO's for additional address lines in case
>> their processor does not have enough address lines already.
>>
>> Signed-off-by: Mike Frysinger <vapier@gentoo.org>
>> Signed-off-by: Bryan Wu <cooloney@kernel.org>
>
> In file included from /home/dwmw2/git/mtd-2.6/arch/x86/include/asm/gpio.h:20,
>                 from drivers/mtd/maps/gpio-addr-flash.c:27:
> include/asm-generic/gpio.h: In function ‘gpio_get_value_cansleep’:
> include/asm-generic/gpio.h:168: error: implicit declaration of function ‘gpio_get_value’
> include/asm-generic/gpio.h: In function ‘gpio_set_value_cansleep’:
> include/asm-generic/gpio.h:174: error: implicit declaration of function ‘gpio_set_value’
> drivers/mtd/maps/gpio-addr-flash.c: In function ‘gpio_flash_probe’:
> drivers/mtd/maps/gpio-addr-flash.c:232: error: implicit declaration of function ‘gpio_request’
> drivers/mtd/maps/gpio-addr-flash.c:236: error: implicit declaration of function ‘gpio_free’
> drivers/mtd/maps/gpio-addr-flash.c:240: error: implicit declaration of function ‘gpio_direction_output’
> make[2]: *** [drivers/mtd/maps/gpio-addr-flash.o] Error 1
> make[1]: *** [drivers/mtd/maps] Error 2
> make: *** [_module_drivers/mtd] Error 2
>
> Setting CONFIG_GPIOLIB seems to fix it -- but we can't make it depend on
> that, can we? Maybe it should be GPIOLIB || !GENERIC_GPIO?

it works fine on Blackfin systems w/out gpiolib, so yes, forcing a
depend on that would be awful.

i think you mean:
depend GPIOLIB || GENERIC_GPIO
-mike
David Woodhouse - Sept. 21, 2009, 1:25 a.m.
On Sun, 2009-09-20 at 18:49 -0400, Mike Frysinger wrote:
> 
> i think you mean:
> depend GPIOLIB || GENERIC_GPIO

No, I think the failing configuration was GENERIC_GPIO && !GPIOLIB

With GENERIC_GPIO, we include <asm-generic/gpio.h>, and _without_
GPIOLIB that header doesn't actually do anything useful.
Mike Frysinger - Sept. 21, 2009, 1:35 a.m.
On Sun, Sep 20, 2009 at 21:25, David Woodhouse wrote:
> On Sun, 2009-09-20 at 18:49 -0400, Mike Frysinger wrote:
>> i think you mean:
>> depend GPIOLIB || GENERIC_GPIO
>
> No, I think the failing configuration was GENERIC_GPIO && !GPIOLIB
>
> With GENERIC_GPIO, we include <asm-generic/gpio.h>, and _without_
> GPIOLIB that header doesn't actually do anything useful.

i dont know what you mean by "we".  GENERIC_GPIO means asm/gpio.h is
useful and so provides the basic gpio functions (which this driver
uses).  the driver, nor common gpio.h headers, pull in
asm-generic/gpio.h.

that is how the Blackfin arch works.  GENERIC_GPIO -> basic gpio
functions work fine as the on-chip gpio functions are supported.
-mike
David Woodhouse - Sept. 21, 2009, 1:56 a.m.
On Sun, 2009-09-20 at 21:35 -0400, Mike Frysinger wrote:
> On Sun, Sep 20, 2009 at 21:25, David Woodhouse wrote:
> > On Sun, 2009-09-20 at 18:49 -0400, Mike Frysinger wrote:
> >> i think you mean:
> >> depend GPIOLIB || GENERIC_GPIO
> >
> > No, I think the failing configuration was GENERIC_GPIO && !GPIOLIB
> >
> > With GENERIC_GPIO, we include <asm-generic/gpio.h>, and _without_
> > GPIOLIB that header doesn't actually do anything useful.
> 
> i dont know what you mean by "we". GENERIC_GPIO means asm/gpio.h is
> useful and so provides the basic gpio functions (which this driver
> uses).  the driver, nor common gpio.h headers, pull in
> asm-generic/gpio.h.
> 
> that is how the Blackfin arch works.  GENERIC_GPIO -> basic gpio
> functions work fine as the on-chip gpio functions are supported.
> -mike

On i386, <linux/gpio.h> includes <asm/gpio.h>. And that includes
<asm-generic/gpio.h> -- which only does anything useful if
CONFIG_GPIOLIB is set.

The failed compilation I showed you earlier was with CONFIG_GENERIC_GPIO
set, but not CONFIG_GPIOLIB.
Mike Frysinger - Sept. 21, 2009, 2:16 a.m.
On Sun, Sep 20, 2009 at 21:56, David Woodhouse wrote:
> On Sun, 2009-09-20 at 21:35 -0400, Mike Frysinger wrote:
>> On Sun, Sep 20, 2009 at 21:25, David Woodhouse wrote:
>> > On Sun, 2009-09-20 at 18:49 -0400, Mike Frysinger wrote:
>> >> i think you mean:
>> >> depend GPIOLIB || GENERIC_GPIO
>> >
>> > No, I think the failing configuration was GENERIC_GPIO && !GPIOLIB
>> >
>> > With GENERIC_GPIO, we include <asm-generic/gpio.h>, and _without_
>> > GPIOLIB that header doesn't actually do anything useful.
>>
>> i dont know what you mean by "we". GENERIC_GPIO means asm/gpio.h is
>> useful and so provides the basic gpio functions (which this driver
>> uses).  the driver, nor common gpio.h headers, pull in
>> asm-generic/gpio.h.
>>
>> that is how the Blackfin arch works.  GENERIC_GPIO -> basic gpio
>> functions work fine as the on-chip gpio functions are supported.
>
> On i386, <linux/gpio.h> includes <asm/gpio.h>. And that includes
> <asm-generic/gpio.h> -- which only does anything useful if
> CONFIG_GPIOLIB is set.
>
> The failed compilation I showed you earlier was with CONFIG_GENERIC_GPIO
> set, but not CONFIG_GPIOLIB.

that isnt a valid GENERIC_GPIO implementation.  x86 cant support it
w/out GPIOLIB.  so the depend still should be "(GENERIC_GPIO ||
GPIOLIB)".
-mike

Patch

diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig
index 82923bd..05f6c08 100644
--- a/drivers/mtd/maps/Kconfig
+++ b/drivers/mtd/maps/Kconfig
@@ -500,6 +500,16 @@  config MTD_BFIN_ASYNC
 
 	  If compiled as a module, it will be called bfin-async-flash.
 
+config MTD_GPIO_ADDR
+	tristate "GPIO-assisted Flash Chip Support"
+	depends on MTD_COMPLEX_MAPPINGS
+	select MTD_PARTITIONS
+	help
+	  Map driver which allows flashes to be partially physically addressed
+	  and assisted by GPIOs.
+
+	  If compiled as a module, it will be called gpio-addr-flash.
+
 config MTD_UCLINUX
 	tristate "Generic uClinux RAM/ROM filesystem support"
 	depends on MTD_PARTITIONS && MTD_RAM && !MMU
diff --git a/drivers/mtd/maps/Makefile b/drivers/mtd/maps/Makefile
index 2dbc1be..d9cbd8a 100644
--- a/drivers/mtd/maps/Makefile
+++ b/drivers/mtd/maps/Makefile
@@ -62,3 +62,4 @@  obj-$(CONFIG_MTD_INTEL_VR_NOR)	+= intel_vr_nor.o
 obj-$(CONFIG_MTD_BFIN_ASYNC)	+= bfin-async-flash.o
 obj-$(CONFIG_MTD_RBTX4939)	+= rbtx4939-flash.o
 obj-$(CONFIG_MTD_VMU)		+= vmu-flash.o
+obj-$(CONFIG_MTD_GPIO_ADDR)	+= gpio-addr-flash.o
diff --git a/drivers/mtd/maps/gpio-addr-flash.c b/drivers/mtd/maps/gpio-addr-flash.c
new file mode 100644
index 0000000..6724255
--- /dev/null
+++ b/drivers/mtd/maps/gpio-addr-flash.c
@@ -0,0 +1,311 @@ 
+/*
+ * drivers/mtd/maps/gpio-addr-flash.c
+ *
+ * Handle the case where a flash device is mostly addressed using physical
+ * line and supplemented by GPIOs.  This way you can hook up say a 8meg flash
+ * to a 2meg memory range and use the GPIOs to select a particular range.
+ *
+ * Copyright 2000 Nicolas Pitre <nico@cam.org>
+ * Copyright 2005-2009 Analog Devices Inc.
+ *
+ * Enter bugs at http://blackfin.uclinux.org/
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/physmap.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+
+#include <asm/gpio.h>
+#include <asm/io.h>
+
+#define pr_devinit(fmt, args...) ({ static const __devinitconst char __fmt[] = fmt; printk(__fmt, ## args); })
+
+#define DRIVER_NAME "gpio-addr-flash"
+#define PFX DRIVER_NAME ": "
+
+/**
+ * struct async_state - keep GPIO flash state
+ *	@mtd:         MTD state for this mapping
+ *	@map:         MTD map state for this flash
+ *	@gpio_count:  number of GPIOs used to address
+ *	@gpio_addrs:  array of GPIOs to twiddle
+ *	@gpio_values: cached GPIO values
+ *	@win_size:    dedicated memory size (if no GPIOs)
+ */
+struct async_state {
+	struct mtd_info *mtd;
+	struct map_info map;
+	size_t gpio_count;
+	unsigned *gpio_addrs;
+	int *gpio_values;
+	unsigned long win_size;
+};
+#define gf_map_info_to_state(mi) ((struct async_state *)(mi)->map_priv_1)
+
+/**
+ * gf_set_gpios() - set GPIO address lines to access specified flash offset
+ *	@state: GPIO flash state
+ *	@ofs:   desired offset to access
+ *
+ * Rather than call the GPIO framework every time, cache the last-programmed
+ * value.  This speeds up sequential accesses (which are by far the most common
+ * type).  We rely on the GPIO framework to treat non-zero value as high so
+ * that we don't have to normalize the bits.
+ */
+static void gf_set_gpios(struct async_state *state, unsigned long ofs)
+{
+	size_t i = 0;
+	int value;
+	ofs /= state->win_size;
+	do {
+		value = ofs & (1 << i);
+		if (state->gpio_values[i] != value) {
+			gpio_set_value(state->gpio_addrs[i], value);
+			state->gpio_values[i] = value;
+		}
+	} while (++i < state->gpio_count);
+}
+
+/**
+ * gf_read() - read a word at the specified offset
+ *	@map: MTD map state
+ *	@ofs: desired offset to read
+ */
+static map_word gf_read(struct map_info *map, unsigned long ofs)
+{
+	struct async_state *state = gf_map_info_to_state(map);
+	u16 word;
+	map_word test;
+
+	gf_set_gpios(state, ofs);
+
+	word = readw(map->virt + (ofs % state->win_size));
+	test.x[0] = word;
+	return test;
+}
+
+/**
+ * gf_copy_from() - copy a chunk of data from the flash
+ *	@map:  MTD map state
+ *	@to:   memory to copy to
+ *	@from: flash offset to copy from
+ *	@len:  how much to copy
+ *
+ * We rely on the MTD layer to chunk up copies such that a single request here
+ * will not cross a window size.  This allows us to only wiggle the GPIOs once
+ * before falling back to a normal memcpy.  Reading the higher layer code shows
+ * that this is indeed the case, but add a BUG_ON() to future proof.
+ */
+static void gf_copy_from(struct map_info *map, void *to, unsigned long from, ssize_t len)
+{
+	struct async_state *state = gf_map_info_to_state(map);
+
+	gf_set_gpios(state, from);
+
+	/* BUG if operation crosses the win_size */
+	BUG_ON(!((from + len) % state->win_size <= (from + len)));
+
+	/* operation does not cross the win_size, so one shot it */
+	memcpy_fromio(to, map->virt + (from % state->win_size), len);
+}
+
+/**
+ * gf_write() - write a word at the specified offset
+ *	@map: MTD map state
+ *	@ofs: desired offset to write
+ */
+static void gf_write(struct map_info *map, map_word d1, unsigned long ofs)
+{
+	struct async_state *state = gf_map_info_to_state(map);
+	u16 d;
+
+	gf_set_gpios(state, ofs);
+
+	d = d1.x[0];
+	writew(d, map->virt + (ofs % state->win_size));
+}
+
+/**
+ * gf_copy_to() - copy a chunk of data to the flash
+ *	@map:  MTD map state
+ *	@to:   flash offset to copy to
+ *	@from: memory to copy from
+ *	@len:  how much to copy
+ *
+ * See gf_copy_from() caveat.
+ */
+static void gf_copy_to(struct map_info *map, unsigned long to, const void *from, ssize_t len)
+{
+	struct async_state *state = gf_map_info_to_state(map);
+
+	gf_set_gpios(state, to);
+
+	/* BUG if operation crosses the win_size */
+	BUG_ON(!((to + len) % state->win_size <= (to + len)));
+
+	/* operation does not cross the win_size, so one shot it */
+	memcpy_toio(map->virt + (to % state->win_size), from, len);
+}
+
+#ifdef CONFIG_MTD_PARTITIONS
+static const char *part_probe_types[] = { "cmdlinepart", "RedBoot", NULL };
+#endif
+
+/**
+ * gpio_flash_probe() - setup a mapping for a GPIO assisted flash
+ *	@pdev: platform device
+ *
+ * The platform resource layout expected looks something like:
+ * struct mtd_partition partitions[] = { ... };
+ * struct physmap_flash_data flash_data = { ... };
+ * unsigned flash_gpios[] = { GPIO_XX, GPIO_XX, ... };
+ * struct resource flash_resource[] = {
+ *	{
+ *		.name  = "cfi_probe",
+ *		.start = 0x20000000,
+ *		.end   = 0x201fffff,
+ *		.flags = IORESOURCE_MEM,
+ *	}, {
+ *		.start = (unsigned long)flash_gpios,
+ *		.end   = ARRAY_SIZE(flash_gpios),
+ *		.flags = IORESOURCE_IRQ,
+ *	}
+ * };
+ * struct platform_device flash_device = {
+ *	.name          = "gpio-addr-flash",
+ *	.dev           = { .platform_data = &flash_data, },
+ *	.num_resources = ARRAY_SIZE(flash_resource),
+ *	.resource      = flash_resource,
+ *	...
+ * };
+ */
+static int __devinit gpio_flash_probe(struct platform_device *pdev)
+{
+	int ret;
+	size_t i, arr_size;
+	struct physmap_flash_data *pdata;
+	struct resource *memory;
+	struct resource *gpios;
+	struct async_state *state;
+
+	pdata = pdev->dev.platform_data;
+	memory = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	gpios = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+
+	if (!memory || !gpios || !gpios->end)
+		return -EINVAL;
+
+	arr_size = sizeof(int) * gpios->end;
+	state = kzalloc(sizeof(*state) + arr_size, GFP_KERNEL);
+	if (!state)
+		return -ENOMEM;
+
+	state->gpio_count     = gpios->end;
+	state->gpio_addrs     = (void *)gpios->start;
+	state->gpio_values    = (void *)(state + 1);
+	state->win_size       = memory->end - memory->start + 1;
+	memset(state->gpio_values, 0xff, arr_size);
+
+	state->map.name       = DRIVER_NAME;
+	state->map.read       = gf_read;
+	state->map.copy_from  = gf_copy_from;
+	state->map.write      = gf_write;
+	state->map.copy_to    = gf_copy_to;
+	state->map.bankwidth  = pdata->width;
+	state->map.size       = state->win_size * (1 << state->gpio_count);
+	state->map.virt       = (void __iomem *)memory->start;
+	state->map.phys       = NO_XIP;
+	state->map.map_priv_1 = (unsigned long)state;
+
+	platform_set_drvdata(pdev, state);
+
+	i = 0;
+	do {
+		if (gpio_request(state->gpio_addrs[i], DRIVER_NAME)) {
+			pr_devinit(KERN_ERR PFX "failed to request gpio %d\n",
+				state->gpio_addrs[i]);
+			while (i--)
+				gpio_free(state->gpio_addrs[i]);
+			kfree(state);
+			return -EBUSY;
+		}
+		gpio_direction_output(state->gpio_addrs[i], 0);
+	} while (++i < state->gpio_count);
+
+	pr_devinit(KERN_NOTICE PFX "probing %d-bit flash bus\n",
+		state->map.bankwidth * 8);
+	state->mtd = do_map_probe(memory->name, &state->map);
+	if (!state->mtd) {
+		for (i = 0; i < state->gpio_count; ++i)
+			gpio_free(state->gpio_addrs[i]);
+		kfree(state);
+		return -ENXIO;
+	}
+
+#ifdef CONFIG_MTD_PARTITIONS
+	ret = parse_mtd_partitions(state->mtd, part_probe_types, &pdata->parts, 0);
+	if (ret > 0) {
+		pr_devinit(KERN_NOTICE PFX "Using commandline partition definition\n");
+		add_mtd_partitions(state->mtd, pdata->parts, ret);
+		kfree(pdata->parts);
+
+	} else if (pdata->nr_parts) {
+		pr_devinit(KERN_NOTICE PFX "Using board partition definition\n");
+		add_mtd_partitions(state->mtd, pdata->parts, pdata->nr_parts);
+
+	} else
+#endif
+	{
+		pr_devinit(KERN_NOTICE PFX "no partition info available, registering whole flash at once\n");
+		add_mtd_device(state->mtd);
+	}
+
+	return 0;
+}
+
+static int __devexit gpio_flash_remove(struct platform_device *pdev)
+{
+	struct async_state *state = platform_get_drvdata(pdev);
+	size_t i = 0;
+	do {
+		gpio_free(state->gpio_addrs[i]);
+	} while (++i < state->gpio_count);
+#ifdef CONFIG_MTD_PARTITIONS
+	del_mtd_partitions(state->mtd);
+#endif
+	map_destroy(state->mtd);
+	kfree(state);
+	return 0;
+}
+
+static struct platform_driver gpio_flash_driver = {
+	.probe		= gpio_flash_probe,
+	.remove		= __devexit_p(gpio_flash_remove),
+	.driver		= {
+		.name	= DRIVER_NAME,
+	},
+};
+
+static int __init gpio_flash_init(void)
+{
+	return platform_driver_register(&gpio_flash_driver);
+}
+module_init(gpio_flash_init);
+
+static void __exit gpio_flash_exit(void)
+{
+	platform_driver_unregister(&gpio_flash_driver);
+}
+module_exit(gpio_flash_exit);
+
+MODULE_AUTHOR("Mike Frysinger <vapier@gentoo.org>");
+MODULE_DESCRIPTION("MTD map driver for flashes addressed physically and with gpios");
+MODULE_LICENSE("GPL");