linuxppc-dev.lists.ozlabs.org archive mirror
 help / color / mirror / Atom feed
* PPC440EPx gpio driver
@ 2008-10-08 20:56 Steven A. Falco
  2008-10-08 21:33 ` Steven A. Falco
                   ` (4 more replies)
  0 siblings, 5 replies; 14+ messages in thread
From: Steven A. Falco @ 2008-10-08 20:56 UTC (permalink / raw)
  To: linuxppc-dev

I have begun writing a driver for the GPIOs of the PPC440EPx.  I just
noticed that the PIKA Warp .dts references a device "ibm,gpio-440ep",
but so far I have not been able to find a driver for that device.

So, here is what I have so far (patch is off 2.6.27-rc9).  It is not
sufficiently general to be merged at this point, but it does appear
to function with my Sequoia board (only tested gpio input, so far).

Signed-off-by: Steve Falco <sfalco at harris.com>

diff --git a/arch/powerpc/platforms/44x/ppc4xx-gpio.c b/arch/powerpc/platforms/44x/ppc4xx-gpio.c
new file mode 100644
index 0000000..ce552b4
--- /dev/null
+++ b/arch/powerpc/platforms/44x/ppc4xx-gpio.c
@@ -0,0 +1,246 @@
+/*
+ * PPC4xx gpio driver
+ *
+ * Copyright (c) 2008 Harris Corporation
+ * Copyright (c) 2008 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/of.h>
+#include <linux/kernel.h>
+#include <linux/of_gpio.h>
+#include <linux/io.h>
+#include <linux/of_platform.h>
+
+#include <asm/gpio.h>
+#include <asm/ppc4xx.h>
+
+static DEFINE_SPINLOCK(gpio_lock);
+
+struct ppc4xx_gpiochip {
+	struct of_mm_gpio_chip mmchip;
+	unsigned int shadow_or;
+	unsigned int shadow_tcr;
+	unsigned int shadow_osrl;
+	unsigned int shadow_osrh;
+	unsigned int shadow_tsrl;
+	unsigned int shadow_tsrh;
+	unsigned int shadow_odr;
+};
+
+/*
+ * GPIO LIB API implementation for GPIOs
+ *
+ * There are a maximum of 64 gpios, spread over two sets of control registers.
+ * The sets are called GPIO0 and GPIO1.  Each set is managed separately, so
+ * there will be two entried in the .dts file.
+ */
+
+static int ppc4xx_gpio_get(struct gpio_chip *gc, unsigned int gpio)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
+	unsigned int ret;
+
+	ret = (in_be32(&regs->gpio_ir) >> (31 - gpio)) & 1;
+
+	return ret;
+}
+
+static inline void
+__ppc4xx_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct ppc4xx_gpiochip *chip = container_of(mm_gc,
+			struct ppc4xx_gpiochip, mmchip);
+	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
+
+	if (val)
+		chip->shadow_or |= 1 << (31 - gpio);
+	else
+		chip->shadow_or &= ~(1 << (31 - gpio));
+	out_be32(&regs->gpio_or, chip->shadow_or);
+}
+
+static void
+ppc4xx_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gpio_lock, flags);
+
+	__ppc4xx_gpio_set(gc, gpio, val);
+
+	spin_unlock_irqrestore(&gpio_lock, flags);
+
+	pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val);
+}
+
+static int ppc4xx_gpio_dir_in(struct gpio_chip *gc, unsigned int gpio)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct ppc4xx_gpiochip *chip = container_of(mm_gc,
+			struct ppc4xx_gpiochip, mmchip);
+	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
+	unsigned long flags;
+
+	spin_lock_irqsave(&gpio_lock, flags);
+
+	/* Disable open-drain function */
+	chip->shadow_odr &= ~(1 << (31 - gpio));
+	out_be32(&regs->gpio_odr, chip->shadow_odr);
+
+	/* Float the pin */
+	chip->shadow_tcr &= ~(1 << (31 - gpio));
+	out_be32(&regs->gpio_tcr, chip->shadow_tcr);
+
+	/* Bits 0-15 use TSRL/OSRL, bits 16-31 use TSRH/OSRH */
+	if(gpio < 16) {
+		chip->shadow_osrl &= ~(3 << ((15 - gpio) * 2));
+		out_be32(&regs->gpio_osrl, chip->shadow_osrl);
+
+		chip->shadow_tsrl &= ~(3 << ((15 - gpio) * 2));
+		out_be32(&regs->gpio_tsrl, chip->shadow_tsrl);
+	} else {
+		chip->shadow_osrh &= ~(3 << ((31 - gpio) * 2));
+		out_be32(&regs->gpio_osrh, chip->shadow_osrh);
+
+		chip->shadow_tsrh &= ~(3 << ((31 - gpio) * 2));
+		out_be32(&regs->gpio_tsrh, chip->shadow_tsrh);
+	}
+
+	spin_unlock_irqrestore(&gpio_lock, flags);
+
+	return 0;
+}
+
+static int
+ppc4xx_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct ppc4xx_gpiochip *chip = container_of(mm_gc,
+			struct ppc4xx_gpiochip, mmchip);
+	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
+	unsigned long flags;
+
+	spin_lock_irqsave(&gpio_lock, flags);
+
+	/* First set initial value */
+	__ppc4xx_gpio_set(gc, gpio, val);
+
+	/* Disable open-drain function */
+	chip->shadow_odr &= ~(1 << (31 - gpio));
+	out_be32(&regs->gpio_odr, chip->shadow_odr);
+
+	/* Drive the pin */
+	chip->shadow_tcr |= (1 << (31 - gpio));
+	out_be32(&regs->gpio_tcr, chip->shadow_tcr);
+
+	/* Bits 0-15 use TSRL, bits 16-31 use TSRH */
+	if(gpio < 16) {
+		chip->shadow_osrl &= ~(3 << ((15 - gpio) * 2));
+		out_be32(&regs->gpio_osrl, chip->shadow_osrl);
+
+		chip->shadow_tsrl &= ~(3 << ((15 - gpio) * 2));
+		out_be32(&regs->gpio_tsrl, chip->shadow_tsrl);
+	} else {
+		chip->shadow_osrh &= ~(3 << ((31 - gpio) * 2));
+		out_be32(&regs->gpio_osrh, chip->shadow_osrh);
+
+		chip->shadow_tsrh &= ~(3 << ((31 - gpio) * 2));
+		out_be32(&regs->gpio_tsrh, chip->shadow_tsrh);
+	}
+
+	spin_unlock_irqrestore(&gpio_lock, flags);
+
+	pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val);
+
+	return 0;
+}
+
+static int __devinit ppc4xx_gpiochip_probe(struct of_device *ofdev,
+					const struct of_device_id *match)
+{
+	struct ppc4xx_gpiochip *chip;
+	struct of_gpio_chip *ofchip;
+	struct ppc4xx_gpio __iomem *regs;
+	int ret;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	ofchip = &chip->mmchip.of_gc;
+
+	ofchip->gpio_cells          = 2;
+	ofchip->gc.ngpio            = 32;
+	ofchip->gc.direction_input  = ppc4xx_gpio_dir_in;
+	ofchip->gc.direction_output = ppc4xx_gpio_dir_out;
+	ofchip->gc.get              = ppc4xx_gpio_get;
+	ofchip->gc.set              = ppc4xx_gpio_set;
+
+	ret = of_mm_gpiochip_add(ofdev->node, &chip->mmchip);
+	if (ret)
+		return ret;
+
+	regs = chip->mmchip.regs;
+	chip->shadow_or = in_be32(&regs->gpio_or);
+	chip->shadow_tcr = in_be32(&regs->gpio_tcr);
+	chip->shadow_osrl = in_be32(&regs->gpio_osrl);
+	chip->shadow_osrh = in_be32(&regs->gpio_osrh);
+	chip->shadow_tsrl = in_be32(&regs->gpio_tsrl);
+	chip->shadow_tsrh = in_be32(&regs->gpio_tsrh);
+	chip->shadow_odr = in_be32(&regs->gpio_odr);
+
+	return 0;
+}
+
+static int ppc4xx_gpiochip_remove(struct of_device *ofdev)
+{
+	return -EBUSY;
+}
+
+static const struct of_device_id ppc4xx_gpiochip_match[] = {
+	{
+		.compatible = "amcc,ppc4xx-gpio",
+	},
+	{}
+};
+
+static struct of_platform_driver ppc4xx_gpiochip_driver = {
+	.name = "gpio",
+	.match_table = ppc4xx_gpiochip_match,
+	.probe = ppc4xx_gpiochip_probe,
+	.remove = ppc4xx_gpiochip_remove,
+};
+
+static int __init ppc4xx_gpio_init(void)
+{
+	if (of_register_platform_driver(&ppc4xx_gpiochip_driver))
+		printk(KERN_ERR "Unable to register GPIO driver\n");
+
+	return 0;
+}
+
+
+/* Make sure we get initialised before anyone else tries to use us */
+subsys_initcall(ppc4xx_gpio_init);
+
+/* No exit call at the moment as we cannot unregister of gpio chips */
+
+MODULE_DESCRIPTION("AMCC PPC4xx gpio driver");
+MODULE_AUTHOR("Steve Falco <sfalco@harris.com");
+MODULE_LICENSE("GPL v2");
+

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: PPC440EPx gpio driver
  2008-10-08 20:56 PPC440EPx gpio driver Steven A. Falco
@ 2008-10-08 21:33 ` Steven A. Falco
  2008-10-09 13:49 ` Anton Vorontsov
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 14+ messages in thread
From: Steven A. Falco @ 2008-10-08 21:33 UTC (permalink / raw)
  To: Steven A. Falco; +Cc: linuxppc-dev

Here is the remainder of the driver - just the structure of the
registers for the 440EPx.  I put the struct in this file because that
is similar to what was done for the mpc52xx, but the struct could go
elsewhere if more appropriate.

Anyway, I'd like some feedback on this driver, because I imagine other
folks would like to have a gpio driver for the 440 family.

Signed-off-by: Steve Falco <sfalco at harris.com>

diff --git a/arch/powerpc/include/asm/ppc4xx.h b/arch/powerpc/include/asm/ppc4xx.h
index 033039a..acbccb7 100644
--- a/arch/powerpc/include/asm/ppc4xx.h
+++ b/arch/powerpc/include/asm/ppc4xx.h
@@ -13,6 +13,28 @@
 #ifndef __ASM_POWERPC_PPC4xx_H__
 #define __ASM_POWERPC_PPC4xx_H__
 
+/* GPIO */
+struct ppc4xx_gpio {
+	unsigned int gpio_or;
+	unsigned int gpio_tcr;
+	unsigned int gpio_osrl;
+	unsigned int gpio_osrh;
+	unsigned int gpio_tsrl;
+	unsigned int gpio_tsrh;
+	unsigned int gpio_odr;
+	unsigned int gpio_ir;
+	unsigned int gpio_rr1;
+	unsigned int gpio_rr2;
+	unsigned int gpio_rr3;
+	unsigned int reserved1;
+	unsigned int gpio_isr1l;
+	unsigned int gpio_isr1h;
+	unsigned int gpio_isr2l;
+	unsigned int gpio_isr2h;
+	unsigned int gpio_isr3l;
+	unsigned int gpio_isr3h;
+};
+
 extern void ppc4xx_reset_system(char *cmd);
 
 #endif /* __ASM_POWERPC_PPC4xx_H__ */

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: PPC440EPx gpio driver
  2008-10-08 20:56 PPC440EPx gpio driver Steven A. Falco
  2008-10-08 21:33 ` Steven A. Falco
@ 2008-10-09 13:49 ` Anton Vorontsov
  2008-10-09 14:23 ` Stefan Roese
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 14+ messages in thread
From: Anton Vorontsov @ 2008-10-09 13:49 UTC (permalink / raw)
  To: Steven A. Falco; +Cc: linuxppc-dev

Hi Steven,

On Wed, Oct 08, 2008 at 04:56:54PM -0400, Steven A. Falco wrote:
> I have begun writing a driver for the GPIOs of the PPC440EPx.  I just
> noticed that the PIKA Warp .dts references a device "ibm,gpio-440ep",
> but so far I have not been able to find a driver for that device.
> 
> So, here is what I have so far (patch is off 2.6.27-rc9).  It is not
> sufficiently general to be merged at this point, but it does appear
> to function with my Sequoia board (only tested gpio input, so far).

It looks good. Few comments below.

> Signed-off-by: Steve Falco <sfalco at harris.com>
> 
> diff --git a/arch/powerpc/platforms/44x/ppc4xx-gpio.c b/arch/powerpc/platforms/44x/ppc4xx-gpio.c
> new file mode 100644
> index 0000000..ce552b4
> --- /dev/null
> +++ b/arch/powerpc/platforms/44x/ppc4xx-gpio.c
> @@ -0,0 +1,246 @@
> +/*
> + * PPC4xx gpio driver
> + *
> + * Copyright (c) 2008 Harris Corporation
> + * Copyright (c) 2008 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
> + *
> + * 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.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
> + */
> +
> +#include <linux/of.h>
> +#include <linux/kernel.h>
> +#include <linux/of_gpio.h>
> +#include <linux/io.h>
> +#include <linux/of_platform.h>
#include <linux/init.h> (for *_initcall)
#include <linux/compiler.h> (for __iomem)
#include <linux/types.h> (in case you'll use __be32).
#include <linux/spinlock.h>

> +
> +#include <asm/gpio.h>

Please don't include asm/gpio.h directly. There is a new header
to include: linux/gpio.h.

> +#include <asm/ppc4xx.h>
> +
> +static DEFINE_SPINLOCK(gpio_lock);

Why not move this lock into the ppc4xx_gpiochip? Per-bank
locks are better anyway, one bank won't block another.

> +struct ppc4xx_gpiochip {
> +	struct of_mm_gpio_chip mmchip;
> +	unsigned int shadow_or;

I'd suggest to use __be32 types here.

> +	unsigned int shadow_tcr;
> +	unsigned int shadow_osrl;
> +	unsigned int shadow_osrh;
> +	unsigned int shadow_tsrl;
> +	unsigned int shadow_tsrh;
> +	unsigned int shadow_odr;
> +};
> +
> +/*
> + * GPIO LIB API implementation for GPIOs
> + *
> + * There are a maximum of 64 gpios, spread over two sets of control registers.
> + * The sets are called GPIO0 and GPIO1.  Each set is managed separately, so
> + * there will be two entried in the .dts file.
> + */
> +
> +static int ppc4xx_gpio_get(struct gpio_chip *gc, unsigned int gpio)
> +{
> +	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
> +	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
> +	unsigned int ret;

u32 ret; would look better, I think.

> +
> +	ret = (in_be32(&regs->gpio_ir) >> (31 - gpio)) & 1;
> +
> +	return ret;
> +}
> +
> +static inline void
> +__ppc4xx_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
> +{
> +	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
> +	struct ppc4xx_gpiochip *chip = container_of(mm_gc,
> +			struct ppc4xx_gpiochip, mmchip);
> +	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
> +
> +	if (val)
> +		chip->shadow_or |= 1 << (31 - gpio);
> +	else
> +		chip->shadow_or &= ~(1 << (31 - gpio));
> +	out_be32(&regs->gpio_or, chip->shadow_or);
> +}
> +
> +static void
> +ppc4xx_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
> +{
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&gpio_lock, flags);
> +
> +	__ppc4xx_gpio_set(gc, gpio, val);
> +
> +	spin_unlock_irqrestore(&gpio_lock, flags);
> +
> +	pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val);
> +}
> +
> +static int ppc4xx_gpio_dir_in(struct gpio_chip *gc, unsigned int gpio)
> +{
> +	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
> +	struct ppc4xx_gpiochip *chip = container_of(mm_gc,
> +			struct ppc4xx_gpiochip, mmchip);
> +	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&gpio_lock, flags);
> +
> +	/* Disable open-drain function */
> +	chip->shadow_odr &= ~(1 << (31 - gpio));
> +	out_be32(&regs->gpio_odr, chip->shadow_odr);
> +
> +	/* Float the pin */
> +	chip->shadow_tcr &= ~(1 << (31 - gpio));
> +	out_be32(&regs->gpio_tcr, chip->shadow_tcr);
> +
> +	/* Bits 0-15 use TSRL/OSRL, bits 16-31 use TSRH/OSRH */
> +	if(gpio < 16) {
> +		chip->shadow_osrl &= ~(3 << ((15 - gpio) * 2));
> +		out_be32(&regs->gpio_osrl, chip->shadow_osrl);
> +
> +		chip->shadow_tsrl &= ~(3 << ((15 - gpio) * 2));
> +		out_be32(&regs->gpio_tsrl, chip->shadow_tsrl);
> +	} else {
> +		chip->shadow_osrh &= ~(3 << ((31 - gpio) * 2));
> +		out_be32(&regs->gpio_osrh, chip->shadow_osrh);
> +
> +		chip->shadow_tsrh &= ~(3 << ((31 - gpio) * 2));
> +		out_be32(&regs->gpio_tsrh, chip->shadow_tsrh);
> +	}
> +
> +	spin_unlock_irqrestore(&gpio_lock, flags);
> +
> +	return 0;
> +}
> +
> +static int
> +ppc4xx_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val)
> +{
> +	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
> +	struct ppc4xx_gpiochip *chip = container_of(mm_gc,
> +			struct ppc4xx_gpiochip, mmchip);
> +	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&gpio_lock, flags);
> +
> +	/* First set initial value */
> +	__ppc4xx_gpio_set(gc, gpio, val);
> +
> +	/* Disable open-drain function */
> +	chip->shadow_odr &= ~(1 << (31 - gpio));
> +	out_be32(&regs->gpio_odr, chip->shadow_odr);
> +
> +	/* Drive the pin */
> +	chip->shadow_tcr |= (1 << (31 - gpio));
> +	out_be32(&regs->gpio_tcr, chip->shadow_tcr);
> +
> +	/* Bits 0-15 use TSRL, bits 16-31 use TSRH */
> +	if(gpio < 16) {
> +		chip->shadow_osrl &= ~(3 << ((15 - gpio) * 2));
> +		out_be32(&regs->gpio_osrl, chip->shadow_osrl);
> +
> +		chip->shadow_tsrl &= ~(3 << ((15 - gpio) * 2));
> +		out_be32(&regs->gpio_tsrl, chip->shadow_tsrl);
> +	} else {
> +		chip->shadow_osrh &= ~(3 << ((31 - gpio) * 2));
> +		out_be32(&regs->gpio_osrh, chip->shadow_osrh);
> +
> +		chip->shadow_tsrh &= ~(3 << ((31 - gpio) * 2));
> +		out_be32(&regs->gpio_tsrh, chip->shadow_tsrh);
> +	}
> +
> +	spin_unlock_irqrestore(&gpio_lock, flags);
> +
> +	pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val);
> +
> +	return 0;
> +}
> +
> +static int __devinit ppc4xx_gpiochip_probe(struct of_device *ofdev,
> +					const struct of_device_id *match)
> +{
> +	struct ppc4xx_gpiochip *chip;
> +	struct of_gpio_chip *ofchip;
> +	struct ppc4xx_gpio __iomem *regs;
> +	int ret;
> +
> +	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
> +	if (!chip)
> +		return -ENOMEM;
> +
> +	ofchip = &chip->mmchip.of_gc;
> +
> +	ofchip->gpio_cells          = 2;
> +	ofchip->gc.ngpio            = 32;
> +	ofchip->gc.direction_input  = ppc4xx_gpio_dir_in;
> +	ofchip->gc.direction_output = ppc4xx_gpio_dir_out;
> +	ofchip->gc.get              = ppc4xx_gpio_get;
> +	ofchip->gc.set              = ppc4xx_gpio_set;
> +
> +	ret = of_mm_gpiochip_add(ofdev->node, &chip->mmchip);
> +	if (ret)
> +		return ret;

Leaking the allocated "chip".

> +
> +	regs = chip->mmchip.regs;
> +	chip->shadow_or = in_be32(&regs->gpio_or);

The chip is already added, but you set the shadow values just now.
I mean, this is racy.

There is special .save_regs() callback in the struct of_mm_gpio_chip,
made especially to solve this problem.

> +	chip->shadow_tcr = in_be32(&regs->gpio_tcr);
> +	chip->shadow_osrl = in_be32(&regs->gpio_osrl);
> +	chip->shadow_osrh = in_be32(&regs->gpio_osrh);
> +	chip->shadow_tsrl = in_be32(&regs->gpio_tsrl);
> +	chip->shadow_tsrh = in_be32(&regs->gpio_tsrh);
> +	chip->shadow_odr = in_be32(&regs->gpio_odr);
> +
> +	return 0;
> +}
> +
> +static int ppc4xx_gpiochip_remove(struct of_device *ofdev)
> +{
> +	return -EBUSY;
> +}
> +
> +static const struct of_device_id ppc4xx_gpiochip_match[] = {
> +	{
> +		.compatible = "amcc,ppc4xx-gpio",
> +	},
> +	{}
> +};
> +
> +static struct of_platform_driver ppc4xx_gpiochip_driver = {
> +	.name = "gpio",

The name seems too generic.

> +	.match_table = ppc4xx_gpiochip_match,
> +	.probe = ppc4xx_gpiochip_probe,
> +	.remove = ppc4xx_gpiochip_remove,
> +};
> +
> +static int __init ppc4xx_gpio_init(void)
> +{
> +	if (of_register_platform_driver(&ppc4xx_gpiochip_driver))
> +		printk(KERN_ERR "Unable to register GPIO driver\n");

printk(KERN_ERR "%s: ...", __func__); would make it much clearer which
GPIO driver failed.

> +
> +	return 0;
> +}
> +
> +
> +/* Make sure we get initialised before anyone else tries to use us */
> +subsys_initcall(ppc4xx_gpio_init);

You could get rid of the of_platform_driver and make this
arch_initcall()... I.e. like arch/powerpc/sysdev/qe_lib/gpio.c.

But the driver approach is also OK... I don't have any preference
for this.


Thanks,

-- 
Anton Vorontsov
email: cbouatmailru@gmail.com
irc://irc.freenode.net/bd2

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: PPC440EPx gpio driver
  2008-10-08 20:56 PPC440EPx gpio driver Steven A. Falco
  2008-10-08 21:33 ` Steven A. Falco
  2008-10-09 13:49 ` Anton Vorontsov
@ 2008-10-09 14:23 ` Stefan Roese
  2008-10-09 15:10 ` Steven A. Falco
  2008-10-09 22:08 ` Sean MacLennan
  4 siblings, 0 replies; 14+ messages in thread
From: Stefan Roese @ 2008-10-09 14:23 UTC (permalink / raw)
  To: linuxppc-dev

On Wednesday 08 October 2008, Steven A. Falco wrote:
> I have begun writing a driver for the GPIOs of the PPC440EPx.  I just
> noticed that the PIKA Warp .dts references a device "ibm,gpio-440ep",
> but so far I have not been able to find a driver for that device.
>
> So, here is what I have so far (patch is off 2.6.27-rc9).  It is not
> sufficiently general to be merged at this point, but it does appear
> to function with my Sequoia board (only tested gpio input, so far).

Thanks for this driver.

I have "another" 4xx-gpiolib driver version in my patch queue. It's also not 
polished yet. That's the reason why I didn't post it till now. I'll post my 
driver version in a short while. It's tested on a 405EP and an 440EPx system 
and "should" work on all other 4xx based platforms.

The community then can decide which driver version should be polished for 
upstream merge...

Best regards,
Stefan

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: PPC440EPx gpio driver
  2008-10-08 20:56 PPC440EPx gpio driver Steven A. Falco
                   ` (2 preceding siblings ...)
  2008-10-09 14:23 ` Stefan Roese
@ 2008-10-09 15:10 ` Steven A. Falco
  2008-10-09 15:19   ` Stefan Roese
  2008-10-09 16:08   ` Anton Vorontsov
  2008-10-09 22:08 ` Sean MacLennan
  4 siblings, 2 replies; 14+ messages in thread
From: Steven A. Falco @ 2008-10-09 15:10 UTC (permalink / raw)
  To: Steven A. Falco; +Cc: linuxppc-dev

Anton - thanks very much for your comments.  I learned a lot, and I've
incorporated your suggestions into the driver, copy attached.

Stefan - hope I didn't step on your toes, but I was not aware you were
also working on this.  Anyway, it seemed like a good place to make my
first "significant" contribution to the kernel source.  But I'll defer
to the choice of the community.

Signed-off-by: Steve Falco <sfalco at harris.com>

diff --git a/arch/powerpc/include/asm/ppc4xx.h b/arch/powerpc/include/asm/ppc4xx.h
index 033039a..acbccb7 100644
--- a/arch/powerpc/include/asm/ppc4xx.h
+++ b/arch/powerpc/include/asm/ppc4xx.h
@@ -13,6 +13,28 @@
 #ifndef __ASM_POWERPC_PPC4xx_H__
 #define __ASM_POWERPC_PPC4xx_H__
 
+/* GPIO */
+struct ppc4xx_gpio {
+	unsigned int gpio_or;
+	unsigned int gpio_tcr;
+	unsigned int gpio_osrl;
+	unsigned int gpio_osrh;
+	unsigned int gpio_tsrl;
+	unsigned int gpio_tsrh;
+	unsigned int gpio_odr;
+	unsigned int gpio_ir;
+	unsigned int gpio_rr1;
+	unsigned int gpio_rr2;
+	unsigned int gpio_rr3;
+	unsigned int reserved1;
+	unsigned int gpio_isr1l;
+	unsigned int gpio_isr1h;
+	unsigned int gpio_isr2l;
+	unsigned int gpio_isr2h;
+	unsigned int gpio_isr3l;
+	unsigned int gpio_isr3h;
+};
+
 extern void ppc4xx_reset_system(char *cmd);
 
 #endif /* __ASM_POWERPC_PPC4xx_H__ */
diff --git a/arch/powerpc/platforms/44x/ppc4xx-gpio.c b/arch/powerpc/platforms/44x/ppc4xx-gpio.c
new file mode 100644
index 0000000..fb80544
--- /dev/null
+++ b/arch/powerpc/platforms/44x/ppc4xx-gpio.c
@@ -0,0 +1,265 @@
+/*
+ * PPC4xx gpio driver
+ *
+ * Copyright (c) 2008 Harris Corporation
+ * Copyright (c) 2008 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/of.h>
+#include <linux/kernel.h>
+#include <linux/of_gpio.h>
+#include <linux/io.h>
+#include <linux/of_platform.h>
+#include <linux/init.h>
+#include <linux/compiler.h>
+#include <linux/types.h>
+#include <linux/spinlock.h>
+#include <linux/gpio.h>
+
+#include <asm/ppc4xx.h>
+
+struct ppc4xx_gpiochip {
+	struct of_mm_gpio_chip mmchip;
+	spinlock_t gpio_lock;
+	__be32 shadow_or;
+	__be32 shadow_tcr;
+	__be32 shadow_osrl;
+	__be32 shadow_osrh;
+	__be32 shadow_tsrl;
+	__be32 shadow_tsrh;
+	__be32 shadow_odr;
+};
+
+/*
+ * GPIO LIB API implementation for GPIOs
+ *
+ * There are a maximum of 64 gpios, spread over two sets of control registers.
+ * The sets are called GPIO0 and GPIO1.  Each set is managed separately, so
+ * there will be two entried in the .dts file.
+ */
+
+static int ppc4xx_gpio_get(struct gpio_chip *gc, unsigned int gpio)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
+	u32 ret;
+
+	ret = (in_be32(&regs->gpio_ir) >> (31 - gpio)) & 1;
+
+	return ret;
+}
+
+static inline void
+__ppc4xx_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct ppc4xx_gpiochip *chip = container_of(mm_gc,
+			struct ppc4xx_gpiochip, mmchip);
+	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
+
+	if (val)
+		chip->shadow_or |= 1 << (31 - gpio);
+	else
+		chip->shadow_or &= ~(1 << (31 - gpio));
+	out_be32(&regs->gpio_or, chip->shadow_or);
+}
+
+static void
+ppc4xx_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct ppc4xx_gpiochip *chip = container_of(mm_gc,
+			struct ppc4xx_gpiochip, mmchip);
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->gpio_lock, flags);
+
+	__ppc4xx_gpio_set(gc, gpio, val);
+
+	spin_unlock_irqrestore(&chip->gpio_lock, flags);
+
+	pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val);
+}
+
+static int ppc4xx_gpio_dir_in(struct gpio_chip *gc, unsigned int gpio)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct ppc4xx_gpiochip *chip = container_of(mm_gc,
+			struct ppc4xx_gpiochip, mmchip);
+	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->gpio_lock, flags);
+
+	/* Disable open-drain function */
+	chip->shadow_odr &= ~(1 << (31 - gpio));
+	out_be32(&regs->gpio_odr, chip->shadow_odr);
+
+	/* Float the pin */
+	chip->shadow_tcr &= ~(1 << (31 - gpio));
+	out_be32(&regs->gpio_tcr, chip->shadow_tcr);
+
+	/* Bits 0-15 use TSRL/OSRL, bits 16-31 use TSRH/OSRH */
+	if (gpio < 16) {
+		chip->shadow_osrl &= ~(3 << ((15 - gpio) * 2));
+		out_be32(&regs->gpio_osrl, chip->shadow_osrl);
+
+		chip->shadow_tsrl &= ~(3 << ((15 - gpio) * 2));
+		out_be32(&regs->gpio_tsrl, chip->shadow_tsrl);
+	} else {
+		chip->shadow_osrh &= ~(3 << ((31 - gpio) * 2));
+		out_be32(&regs->gpio_osrh, chip->shadow_osrh);
+
+		chip->shadow_tsrh &= ~(3 << ((31 - gpio) * 2));
+		out_be32(&regs->gpio_tsrh, chip->shadow_tsrh);
+	}
+
+	spin_unlock_irqrestore(&chip->gpio_lock, flags);
+
+	return 0;
+}
+
+static int
+ppc4xx_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct ppc4xx_gpiochip *chip = container_of(mm_gc,
+			struct ppc4xx_gpiochip, mmchip);
+	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->gpio_lock, flags);
+
+	/* First set initial value */
+	__ppc4xx_gpio_set(gc, gpio, val);
+
+	/* Disable open-drain function */
+	chip->shadow_odr &= ~(1 << (31 - gpio));
+	out_be32(&regs->gpio_odr, chip->shadow_odr);
+
+	/* Drive the pin */
+	chip->shadow_tcr |= (1 << (31 - gpio));
+	out_be32(&regs->gpio_tcr, chip->shadow_tcr);
+
+	/* Bits 0-15 use TSRL, bits 16-31 use TSRH */
+	if (gpio < 16) {
+		chip->shadow_osrl &= ~(3 << ((15 - gpio) * 2));
+		out_be32(&regs->gpio_osrl, chip->shadow_osrl);
+
+		chip->shadow_tsrl &= ~(3 << ((15 - gpio) * 2));
+		out_be32(&regs->gpio_tsrl, chip->shadow_tsrl);
+	} else {
+		chip->shadow_osrh &= ~(3 << ((31 - gpio) * 2));
+		out_be32(&regs->gpio_osrh, chip->shadow_osrh);
+
+		chip->shadow_tsrh &= ~(3 << ((31 - gpio) * 2));
+		out_be32(&regs->gpio_tsrh, chip->shadow_tsrh);
+	}
+
+	spin_unlock_irqrestore(&chip->gpio_lock, flags);
+
+	pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val);
+
+	return 0;
+}
+
+static void __devinit ppc4xx_gpiochip_save_regs(struct of_mm_gpio_chip *mm_gc)
+{
+	struct ppc4xx_gpiochip *chip = container_of(mm_gc,
+			struct ppc4xx_gpiochip, mmchip);
+	struct ppc4xx_gpio __iomem *regs;
+
+	regs = mm_gc->regs;
+	chip->shadow_or = in_be32(&regs->gpio_or);
+	chip->shadow_tcr = in_be32(&regs->gpio_tcr);
+	chip->shadow_osrl = in_be32(&regs->gpio_osrl);
+	chip->shadow_osrh = in_be32(&regs->gpio_osrh);
+	chip->shadow_tsrl = in_be32(&regs->gpio_tsrl);
+	chip->shadow_tsrh = in_be32(&regs->gpio_tsrh);
+	chip->shadow_odr = in_be32(&regs->gpio_odr);
+}
+
+static int __devinit ppc4xx_gpiochip_probe(struct of_device *ofdev,
+					const struct of_device_id *match)
+{
+	struct ppc4xx_gpiochip *chip;
+	struct of_gpio_chip *ofchip;
+	int ret;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	spin_lock_init(&chip->gpio_lock);
+
+	chip->mmchip.save_regs = ppc4xx_gpiochip_save_regs;
+
+	ofchip = &chip->mmchip.of_gc;
+
+	ofchip->gpio_cells          = 2;
+	ofchip->gc.ngpio            = 32;
+	ofchip->gc.direction_input  = ppc4xx_gpio_dir_in;
+	ofchip->gc.direction_output = ppc4xx_gpio_dir_out;
+	ofchip->gc.get              = ppc4xx_gpio_get;
+	ofchip->gc.set              = ppc4xx_gpio_set;
+
+	ret = of_mm_gpiochip_add(ofdev->node, &chip->mmchip);
+	if (ret) {
+		kfree(chip);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int ppc4xx_gpiochip_remove(struct of_device *ofdev)
+{
+	return -EBUSY;
+}
+
+static const struct of_device_id ppc4xx_gpiochip_match[] = {
+	{
+		.compatible = "amcc,ppc4xx-gpio",
+	},
+	{}
+};
+
+static struct of_platform_driver ppc4xx_gpiochip_driver = {
+	.name = "ppc4xx-gpio",
+	.match_table = ppc4xx_gpiochip_match,
+	.probe = ppc4xx_gpiochip_probe,
+	.remove = ppc4xx_gpiochip_remove,
+};
+
+static int __init ppc4xx_gpio_init(void)
+{
+	if (of_register_platform_driver(&ppc4xx_gpiochip_driver))
+		printk(KERN_ERR "%s: Unable to register GPIO driver\n",
+			       	__func__);
+
+	return 0;
+}
+
+
+/* Make sure we get initialised before anyone else tries to use us */
+subsys_initcall(ppc4xx_gpio_init);
+
+/* No exit call at the moment as we cannot unregister of gpio chips */
+
+MODULE_DESCRIPTION("AMCC PPC4xx gpio driver");
+MODULE_AUTHOR("Steve Falco <sfalco@harris.com");
+MODULE_LICENSE("GPL v2");
+

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: PPC440EPx gpio driver
  2008-10-09 15:10 ` Steven A. Falco
@ 2008-10-09 15:19   ` Stefan Roese
  2008-10-09 16:08   ` Anton Vorontsov
  1 sibling, 0 replies; 14+ messages in thread
From: Stefan Roese @ 2008-10-09 15:19 UTC (permalink / raw)
  To: linuxppc-dev

On Thursday 09 October 2008, Steven A. Falco wrote:
> Anton - thanks very much for your comments.  I learned a lot, and I've
> incorporated your suggestions into the driver, copy attached.
>
> Stefan - hope I didn't step on your toes, but I was not aware you were
> also working on this.

Not at all. I just wanted to drop my work now so that we all can decide which 
version should be "finetuned". It's no rocket science driver anyways. :)

Best regards,
Stefan

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: PPC440EPx gpio driver
  2008-10-09 15:10 ` Steven A. Falco
  2008-10-09 15:19   ` Stefan Roese
@ 2008-10-09 16:08   ` Anton Vorontsov
  2008-10-09 16:55     ` Steven A. Falco
  2008-10-09 17:09     ` Steven A. Falco
  1 sibling, 2 replies; 14+ messages in thread
From: Anton Vorontsov @ 2008-10-09 16:08 UTC (permalink / raw)
  To: Steven A. Falco; +Cc: linuxppc-dev

On Thu, Oct 09, 2008 at 11:10:25AM -0400, Steven A. Falco wrote:
> Anton - thanks very much for your comments.  I learned a lot, and I've
> incorporated your suggestions into the driver, copy attached.
> 
> Stefan - hope I didn't step on your toes, but I was not aware you were
> also working on this.  Anyway, it seemed like a good place to make my
> first "significant" contribution to the kernel source.  But I'll defer
> to the choice of the community.

Looks better, thanks.

> Signed-off-by: Steve Falco <sfalco at harris.com>
> 
> diff --git a/arch/powerpc/include/asm/ppc4xx.h b/arch/powerpc/include/asm/ppc4xx.h
> index 033039a..acbccb7 100644
> --- a/arch/powerpc/include/asm/ppc4xx.h
> +++ b/arch/powerpc/include/asm/ppc4xx.h
> @@ -13,6 +13,28 @@
>  #ifndef __ASM_POWERPC_PPC4xx_H__
>  #define __ASM_POWERPC_PPC4xx_H__

#include <linux/types.h>
here.

> +/* GPIO */
> +struct ppc4xx_gpio {

(ah.. here are the memory mapped registers)

> +	unsigned int gpio_or;

Then __be32 here.

> +	unsigned int gpio_tcr;
> +	unsigned int gpio_osrl;
> +	unsigned int gpio_osrh;
> +	unsigned int gpio_tsrl;
> +	unsigned int gpio_tsrh;
> +	unsigned int gpio_odr;
> +	unsigned int gpio_ir;
> +	unsigned int gpio_rr1;
> +	unsigned int gpio_rr2;
> +	unsigned int gpio_rr3;
> +	unsigned int reserved1;
> +	unsigned int gpio_isr1l;
> +	unsigned int gpio_isr1h;
> +	unsigned int gpio_isr2l;
> +	unsigned int gpio_isr2h;
> +	unsigned int gpio_isr3l;
> +	unsigned int gpio_isr3h;
> +};
> +
>  extern void ppc4xx_reset_system(char *cmd);
>  
>  #endif /* __ASM_POWERPC_PPC4xx_H__ */
> diff --git a/arch/powerpc/platforms/44x/ppc4xx-gpio.c b/arch/powerpc/platforms/44x/ppc4xx-gpio.c
> new file mode 100644
> index 0000000..fb80544
> --- /dev/null
> +++ b/arch/powerpc/platforms/44x/ppc4xx-gpio.c
> @@ -0,0 +1,265 @@
> +/*
> + * PPC4xx gpio driver
> + *
> + * Copyright (c) 2008 Harris Corporation
> + * Copyright (c) 2008 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
> + *
> + * 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.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
> + */
> +
> +#include <linux/of.h>
> +#include <linux/kernel.h>
> +#include <linux/of_gpio.h>
> +#include <linux/io.h>
> +#include <linux/of_platform.h>
> +#include <linux/init.h>
> +#include <linux/compiler.h>
> +#include <linux/types.h>
> +#include <linux/spinlock.h>
> +#include <linux/gpio.h>
> +
> +#include <asm/ppc4xx.h>
> +
> +struct ppc4xx_gpiochip {
> +	struct of_mm_gpio_chip mmchip;
> +	spinlock_t gpio_lock;
> +	__be32 shadow_or;

It appears that in my previous reply I was commenting on the
shadowed registers, not the memory mapped ones. :-/

Well, __be32 works here too, but the shadowed registers would
be better of just u32 type. Sorry for the confusion.

> +	__be32 shadow_tcr;
> +	__be32 shadow_osrl;
> +	__be32 shadow_osrh;
> +	__be32 shadow_tsrl;
> +	__be32 shadow_tsrh;
> +	__be32 shadow_odr;
> +};
> +
> +/*
> + * GPIO LIB API implementation for GPIOs
> + *
> + * There are a maximum of 64 gpios, spread over two sets of control registers.
> + * The sets are called GPIO0 and GPIO1.  Each set is managed separately, so
> + * there will be two entried in the .dts file.
> + */
> +
> +static int ppc4xx_gpio_get(struct gpio_chip *gc, unsigned int gpio)
> +{
> +	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
> +	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
> +	u32 ret;
> +
> +	ret = (in_be32(&regs->gpio_ir) >> (31 - gpio)) & 1;
> +
> +	return ret;
> +}
> +
> +static inline void
> +__ppc4xx_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
> +{
> +	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
> +	struct ppc4xx_gpiochip *chip = container_of(mm_gc,
> +			struct ppc4xx_gpiochip, mmchip);
> +	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
> +
> +	if (val)
> +		chip->shadow_or |= 1 << (31 - gpio);
> +	else
> +		chip->shadow_or &= ~(1 << (31 - gpio));
> +	out_be32(&regs->gpio_or, chip->shadow_or);
> +}
> +
> +static void
> +ppc4xx_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
> +{
> +	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
> +	struct ppc4xx_gpiochip *chip = container_of(mm_gc,
> +			struct ppc4xx_gpiochip, mmchip);
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&chip->gpio_lock, flags);
> +
> +	__ppc4xx_gpio_set(gc, gpio, val);
> +
> +	spin_unlock_irqrestore(&chip->gpio_lock, flags);
> +
> +	pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val);
> +}
> +
> +static int ppc4xx_gpio_dir_in(struct gpio_chip *gc, unsigned int gpio)
> +{
> +	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
> +	struct ppc4xx_gpiochip *chip = container_of(mm_gc,
> +			struct ppc4xx_gpiochip, mmchip);
> +	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&chip->gpio_lock, flags);
> +
> +	/* Disable open-drain function */
> +	chip->shadow_odr &= ~(1 << (31 - gpio));
> +	out_be32(&regs->gpio_odr, chip->shadow_odr);
> +
> +	/* Float the pin */
> +	chip->shadow_tcr &= ~(1 << (31 - gpio));
> +	out_be32(&regs->gpio_tcr, chip->shadow_tcr);
> +
> +	/* Bits 0-15 use TSRL/OSRL, bits 16-31 use TSRH/OSRH */
> +	if (gpio < 16) {
> +		chip->shadow_osrl &= ~(3 << ((15 - gpio) * 2));
> +		out_be32(&regs->gpio_osrl, chip->shadow_osrl);
> +
> +		chip->shadow_tsrl &= ~(3 << ((15 - gpio) * 2));
> +		out_be32(&regs->gpio_tsrl, chip->shadow_tsrl);
> +	} else {
> +		chip->shadow_osrh &= ~(3 << ((31 - gpio) * 2));
> +		out_be32(&regs->gpio_osrh, chip->shadow_osrh);
> +
> +		chip->shadow_tsrh &= ~(3 << ((31 - gpio) * 2));
> +		out_be32(&regs->gpio_tsrh, chip->shadow_tsrh);
> +	}
> +
> +	spin_unlock_irqrestore(&chip->gpio_lock, flags);
> +
> +	return 0;
> +}
> +
> +static int
> +ppc4xx_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val)
> +{
> +	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
> +	struct ppc4xx_gpiochip *chip = container_of(mm_gc,
> +			struct ppc4xx_gpiochip, mmchip);
> +	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&chip->gpio_lock, flags);
> +
> +	/* First set initial value */
> +	__ppc4xx_gpio_set(gc, gpio, val);
> +
> +	/* Disable open-drain function */
> +	chip->shadow_odr &= ~(1 << (31 - gpio));
> +	out_be32(&regs->gpio_odr, chip->shadow_odr);
> +
> +	/* Drive the pin */
> +	chip->shadow_tcr |= (1 << (31 - gpio));
> +	out_be32(&regs->gpio_tcr, chip->shadow_tcr);
> +
> +	/* Bits 0-15 use TSRL, bits 16-31 use TSRH */
> +	if (gpio < 16) {
> +		chip->shadow_osrl &= ~(3 << ((15 - gpio) * 2));
> +		out_be32(&regs->gpio_osrl, chip->shadow_osrl);
> +
> +		chip->shadow_tsrl &= ~(3 << ((15 - gpio) * 2));
> +		out_be32(&regs->gpio_tsrl, chip->shadow_tsrl);
> +	} else {
> +		chip->shadow_osrh &= ~(3 << ((31 - gpio) * 2));
> +		out_be32(&regs->gpio_osrh, chip->shadow_osrh);
> +
> +		chip->shadow_tsrh &= ~(3 << ((31 - gpio) * 2));
> +		out_be32(&regs->gpio_tsrh, chip->shadow_tsrh);
> +	}
> +
> +	spin_unlock_irqrestore(&chip->gpio_lock, flags);
> +
> +	pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val);
> +
> +	return 0;
> +}
> +
> +static void __devinit ppc4xx_gpiochip_save_regs(struct of_mm_gpio_chip *mm_gc)
> +{
> +	struct ppc4xx_gpiochip *chip = container_of(mm_gc,
> +			struct ppc4xx_gpiochip, mmchip);
> +	struct ppc4xx_gpio __iomem *regs;
> +
> +	regs = mm_gc->regs;
> +	chip->shadow_or = in_be32(&regs->gpio_or);
> +	chip->shadow_tcr = in_be32(&regs->gpio_tcr);
> +	chip->shadow_osrl = in_be32(&regs->gpio_osrl);
> +	chip->shadow_osrh = in_be32(&regs->gpio_osrh);
> +	chip->shadow_tsrl = in_be32(&regs->gpio_tsrl);
> +	chip->shadow_tsrh = in_be32(&regs->gpio_tsrh);
> +	chip->shadow_odr = in_be32(&regs->gpio_odr);
> +}
> +
> +static int __devinit ppc4xx_gpiochip_probe(struct of_device *ofdev,
> +					const struct of_device_id *match)
> +{
> +	struct ppc4xx_gpiochip *chip;
> +	struct of_gpio_chip *ofchip;
> +	int ret;
> +
> +	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
> +	if (!chip)
> +		return -ENOMEM;
> +
> +	spin_lock_init(&chip->gpio_lock);
> +
> +	chip->mmchip.save_regs = ppc4xx_gpiochip_save_regs;
> +
> +	ofchip = &chip->mmchip.of_gc;
> +
> +	ofchip->gpio_cells          = 2;
> +	ofchip->gc.ngpio            = 32;
> +	ofchip->gc.direction_input  = ppc4xx_gpio_dir_in;
> +	ofchip->gc.direction_output = ppc4xx_gpio_dir_out;
> +	ofchip->gc.get              = ppc4xx_gpio_get;
> +	ofchip->gc.set              = ppc4xx_gpio_set;
> +
> +	ret = of_mm_gpiochip_add(ofdev->node, &chip->mmchip);
> +	if (ret) {
> +		kfree(chip);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ppc4xx_gpiochip_remove(struct of_device *ofdev)
> +{
> +	return -EBUSY;
> +}
> +
> +static const struct of_device_id ppc4xx_gpiochip_match[] = {
> +	{
> +		.compatible = "amcc,ppc4xx-gpio",
> +	},
> +	{}
> +};
> +
> +static struct of_platform_driver ppc4xx_gpiochip_driver = {
> +	.name = "ppc4xx-gpio",
> +	.match_table = ppc4xx_gpiochip_match,
> +	.probe = ppc4xx_gpiochip_probe,
> +	.remove = ppc4xx_gpiochip_remove,
> +};
> +
> +static int __init ppc4xx_gpio_init(void)
> +{
> +	if (of_register_platform_driver(&ppc4xx_gpiochip_driver))
> +		printk(KERN_ERR "%s: Unable to register GPIO driver\n",
> +			       	__func__);
> +
> +	return 0;
> +}
> +
> +
> +/* Make sure we get initialised before anyone else tries to use us */
> +subsys_initcall(ppc4xx_gpio_init);

On the second thought, as I commented on the Stefan's patch,
there is no point of doing subsys_initcall() for the OF platform
driver, since platforms tend to probe the OF bus at device_initcall
time.

So, in the end I'd suggest to chose driver-less registration approach,
as in arch/powerpc/sysdev/qe_lib/gpio.c. That way GPIOs will appear
even earlier, at the arch_initcall time.


Thanks again,

-- 
Anton Vorontsov
email: cbouatmailru@gmail.com
irc://irc.freenode.net/bd2

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: PPC440EPx gpio driver
  2008-10-09 16:08   ` Anton Vorontsov
@ 2008-10-09 16:55     ` Steven A. Falco
  2008-10-09 17:09     ` Steven A. Falco
  1 sibling, 0 replies; 14+ messages in thread
From: Steven A. Falco @ 2008-10-09 16:55 UTC (permalink / raw)
  To: avorontsov; +Cc: linuxppc-dev

Thanks again for the comments.  I followed the structure of your
qe_lib/gpio.c; here is the result.  At this point, I've tested input
and output, so it is looking pretty good for me.

I added __init to the ppc4xx_gpio_save_regs because it looks like it is
only needed when first adding the gpios.

Signed-off-by: Steve Falco <sfalco at harris.com>

diff --git a/arch/powerpc/include/asm/ppc4xx.h b/arch/powerpc/include/asm/ppc4xx.h
index 033039a..589ff5c 100644
--- a/arch/powerpc/include/asm/ppc4xx.h
+++ b/arch/powerpc/include/asm/ppc4xx.h
@@ -13,6 +13,30 @@
 #ifndef __ASM_POWERPC_PPC4xx_H__
 #define __ASM_POWERPC_PPC4xx_H__
 
+#include <linux/types.h>
+
+/* GPIO */
+struct ppc4xx_gpio {
+	__be32 gpio_or;
+	__be32 gpio_tcr;
+	__be32 gpio_osrl;
+	__be32 gpio_osrh;
+	__be32 gpio_tsrl;
+	__be32 gpio_tsrh;
+	__be32 gpio_odr;
+	__be32 gpio_ir;
+	__be32 gpio_rr1;
+	__be32 gpio_rr2;
+	__be32 gpio_rr3;
+	__be32 reserved1;
+	__be32 gpio_isr1l;
+	__be32 gpio_isr1h;
+	__be32 gpio_isr2l;
+	__be32 gpio_isr2h;
+	__be32 gpio_isr3l;
+	__be32 gpio_isr3h;
+};
+
 extern void ppc4xx_reset_system(char *cmd);
 
 #endif /* __ASM_POWERPC_PPC4xx_H__ */
diff --git a/arch/powerpc/sysdev/Kconfig b/arch/powerpc/sysdev/Kconfig
index 72fb35b..f4a8edb 100644
--- a/arch/powerpc/sysdev/Kconfig
+++ b/arch/powerpc/sysdev/Kconfig
@@ -6,3 +6,11 @@ config PPC4xx_PCI_EXPRESS
 	bool
 	depends on PCI && 4xx
 	default n
+
+config PPC4xx_GPIO
+	bool "PPC4xx GPIO support"
+	depends on 4xx
+	select ARCH_REQUIRE_GPIOLIB
+	select GENERIC_GPIO
+	help
+	  Enable gpiolib support for PPC4xx based boards
diff --git a/arch/powerpc/sysdev/Makefile b/arch/powerpc/sysdev/Makefile
index a90054b..35d5765 100644
--- a/arch/powerpc/sysdev/Makefile
+++ b/arch/powerpc/sysdev/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_OF_RTC)		+= of_rtc.o
 ifeq ($(CONFIG_PCI),y)
 obj-$(CONFIG_4xx)		+= ppc4xx_pci.o
 endif
+obj-$(CONFIG_PPC4xx_GPIO)	+= ppc4xx_gpio.o
 
 # Temporary hack until we have migrated to asm-powerpc
 ifeq ($(ARCH),powerpc)
diff --git a/arch/powerpc/sysdev/ppc4xx_gpio.c b/arch/powerpc/sysdev/ppc4xx_gpio.c
new file mode 100644
index 0000000..95c4204
--- /dev/null
+++ b/arch/powerpc/sysdev/ppc4xx_gpio.c
@@ -0,0 +1,242 @@
+/*
+ * PPC4xx gpio driver
+ *
+ * Copyright (c) 2008 Harris Corporation
+ * Copyright (c) 2008 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
+ * Copyright (c) MontaVista Software, Inc. 2008.
+ *
+ * Author: Steve Falco <sfalco@harris.com>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/gpio.h>
+
+#include <asm/ppc4xx.h>
+
+struct ppc4xx_gpio_chip {
+	struct of_mm_gpio_chip mm_gc;
+	spinlock_t lock;
+	u32 shadow_or;
+	u32 shadow_tcr;
+	u32 shadow_osrl;
+	u32 shadow_osrh;
+	u32 shadow_tsrl;
+	u32 shadow_tsrh;
+	u32 shadow_odr;
+};
+
+/*
+ * GPIO LIB API implementation for GPIOs
+ *
+ * There are a maximum of 64 gpios, spread over two sets of control registers.
+ * The sets are called GPIO0 and GPIO1.  Each set is managed separately, so
+ * there will be two entried in the .dts file.
+ */
+
+static inline struct ppc4xx_gpio_chip *
+to_ppc4xx_gpiochip(struct of_mm_gpio_chip *mm_gc)
+{
+	return container_of(mm_gc, struct ppc4xx_gpio_chip, mm_gc);
+}
+
+static int ppc4xx_gpio_get(struct gpio_chip *gc, unsigned int gpio)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
+	u32 ret;
+
+	ret = (in_be32(&regs->gpio_ir) >> (31 - gpio)) & 1;
+
+	return ret;
+}
+
+static inline void
+__ppc4xx_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct ppc4xx_gpio_chip *chip = container_of(mm_gc,
+			struct ppc4xx_gpio_chip, mm_gc);
+	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
+
+	if (val)
+		chip->shadow_or |= 1 << (31 - gpio);
+	else
+		chip->shadow_or &= ~(1 << (31 - gpio));
+	out_be32(&regs->gpio_or, chip->shadow_or);
+}
+
+static void
+ppc4xx_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct ppc4xx_gpio_chip *chip = container_of(mm_gc,
+			struct ppc4xx_gpio_chip, mm_gc);
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	__ppc4xx_gpio_set(gc, gpio, val);
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val);
+}
+
+static int ppc4xx_gpio_dir_in(struct gpio_chip *gc, unsigned int gpio)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct ppc4xx_gpio_chip *chip = container_of(mm_gc,
+			struct ppc4xx_gpio_chip, mm_gc);
+	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	/* Disable open-drain function */
+	chip->shadow_odr &= ~(1 << (31 - gpio));
+	out_be32(&regs->gpio_odr, chip->shadow_odr);
+
+	/* Float the pin */
+	chip->shadow_tcr &= ~(1 << (31 - gpio));
+	out_be32(&regs->gpio_tcr, chip->shadow_tcr);
+
+	/* Bits 0-15 use TSRL/OSRL, bits 16-31 use TSRH/OSRH */
+	if (gpio < 16) {
+		chip->shadow_osrl &= ~(3 << ((15 - gpio) * 2));
+		out_be32(&regs->gpio_osrl, chip->shadow_osrl);
+
+		chip->shadow_tsrl &= ~(3 << ((15 - gpio) * 2));
+		out_be32(&regs->gpio_tsrl, chip->shadow_tsrl);
+	} else {
+		chip->shadow_osrh &= ~(3 << ((31 - gpio) * 2));
+		out_be32(&regs->gpio_osrh, chip->shadow_osrh);
+
+		chip->shadow_tsrh &= ~(3 << ((31 - gpio) * 2));
+		out_be32(&regs->gpio_tsrh, chip->shadow_tsrh);
+	}
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	return 0;
+}
+
+static int
+ppc4xx_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct ppc4xx_gpio_chip *chip = container_of(mm_gc,
+			struct ppc4xx_gpio_chip, mm_gc);
+	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	/* First set initial value */
+	__ppc4xx_gpio_set(gc, gpio, val);
+
+	/* Disable open-drain function */
+	chip->shadow_odr &= ~(1 << (31 - gpio));
+	out_be32(&regs->gpio_odr, chip->shadow_odr);
+
+	/* Drive the pin */
+	chip->shadow_tcr |= (1 << (31 - gpio));
+	out_be32(&regs->gpio_tcr, chip->shadow_tcr);
+
+	/* Bits 0-15 use TSRL, bits 16-31 use TSRH */
+	if (gpio < 16) {
+		chip->shadow_osrl &= ~(3 << ((15 - gpio) * 2));
+		out_be32(&regs->gpio_osrl, chip->shadow_osrl);
+
+		chip->shadow_tsrl &= ~(3 << ((15 - gpio) * 2));
+		out_be32(&regs->gpio_tsrl, chip->shadow_tsrl);
+	} else {
+		chip->shadow_osrh &= ~(3 << ((31 - gpio) * 2));
+		out_be32(&regs->gpio_osrh, chip->shadow_osrh);
+
+		chip->shadow_tsrh &= ~(3 << ((31 - gpio) * 2));
+		out_be32(&regs->gpio_tsrh, chip->shadow_tsrh);
+	}
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val);
+
+	return 0;
+}
+
+static void __init ppc4xx_gpio_save_regs(struct of_mm_gpio_chip *mm_gc)
+{
+	struct ppc4xx_gpio_chip *chip = to_ppc4xx_gpiochip(mm_gc);
+	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
+
+	chip->shadow_or = in_be32(&regs->gpio_or);
+	chip->shadow_tcr = in_be32(&regs->gpio_tcr);
+	chip->shadow_osrl = in_be32(&regs->gpio_osrl);
+	chip->shadow_osrh = in_be32(&regs->gpio_osrh);
+	chip->shadow_tsrl = in_be32(&regs->gpio_tsrl);
+	chip->shadow_tsrh = in_be32(&regs->gpio_tsrh);
+	chip->shadow_odr = in_be32(&regs->gpio_odr);
+}
+
+static int __init ppc4xx_add_gpiochips(void)
+{
+	struct device_node *np;
+
+	for_each_compatible_node(np, NULL, "amcc,ppc4xx-gpio") {
+		int ret;
+		struct ppc4xx_gpio_chip *ppc4xx_gc;
+		struct of_mm_gpio_chip *mm_gc;
+		struct of_gpio_chip *of_gc;
+		struct gpio_chip *gc;
+
+		ppc4xx_gc = kzalloc(sizeof(*ppc4xx_gc), GFP_KERNEL);
+		if (!ppc4xx_gc) {
+			ret = -ENOMEM;
+			goto err;
+		}
+
+		spin_lock_init(&ppc4xx_gc->lock);
+
+		mm_gc = &ppc4xx_gc->mm_gc;
+		of_gc = &mm_gc->of_gc;
+		gc = &of_gc->gc;
+
+		mm_gc->save_regs = ppc4xx_gpio_save_regs;
+		of_gc->gpio_cells = 2;
+		gc->ngpio = 32;
+		gc->direction_input = ppc4xx_gpio_dir_in;
+		gc->direction_output = ppc4xx_gpio_dir_out;
+		gc->get = ppc4xx_gpio_get;
+		gc->set = ppc4xx_gpio_set;
+
+		ret = of_mm_gpiochip_add(np, mm_gc);
+		if (ret)
+			goto err;
+		continue;
+err:
+		pr_err("%s: registration failed with status %d\n",
+		       np->full_name, ret);
+		kfree(ppc4xx_gc);
+		/* try others anyway */
+	}
+	return 0;
+}
+arch_initcall(ppc4xx_add_gpiochips);

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: PPC440EPx gpio driver
  2008-10-09 16:08   ` Anton Vorontsov
  2008-10-09 16:55     ` Steven A. Falco
@ 2008-10-09 17:09     ` Steven A. Falco
  2008-10-09 17:38       ` Anton Vorontsov
  1 sibling, 1 reply; 14+ messages in thread
From: Steven A. Falco @ 2008-10-09 17:09 UTC (permalink / raw)
  To: avorontsov; +Cc: linuxppc-dev

Please disregard the previous version, and consider this one instead.
The only difference is making better use of to_ppc4xx_gpiochip(), which
helps readability.

Signed-off-by: Steve Falco <sfalco at harris.com>

diff --git a/arch/powerpc/include/asm/ppc4xx.h b/arch/powerpc/include/asm/ppc4xx.h
index 033039a..589ff5c 100644
--- a/arch/powerpc/include/asm/ppc4xx.h
+++ b/arch/powerpc/include/asm/ppc4xx.h
@@ -13,6 +13,30 @@
 #ifndef __ASM_POWERPC_PPC4xx_H__
 #define __ASM_POWERPC_PPC4xx_H__
 
+#include <linux/types.h>
+
+/* GPIO */
+struct ppc4xx_gpio {
+	__be32 gpio_or;
+	__be32 gpio_tcr;
+	__be32 gpio_osrl;
+	__be32 gpio_osrh;
+	__be32 gpio_tsrl;
+	__be32 gpio_tsrh;
+	__be32 gpio_odr;
+	__be32 gpio_ir;
+	__be32 gpio_rr1;
+	__be32 gpio_rr2;
+	__be32 gpio_rr3;
+	__be32 reserved1;
+	__be32 gpio_isr1l;
+	__be32 gpio_isr1h;
+	__be32 gpio_isr2l;
+	__be32 gpio_isr2h;
+	__be32 gpio_isr3l;
+	__be32 gpio_isr3h;
+};
+
 extern void ppc4xx_reset_system(char *cmd);
 
 #endif /* __ASM_POWERPC_PPC4xx_H__ */
diff --git a/arch/powerpc/sysdev/Kconfig b/arch/powerpc/sysdev/Kconfig
index 72fb35b..f4a8edb 100644
--- a/arch/powerpc/sysdev/Kconfig
+++ b/arch/powerpc/sysdev/Kconfig
@@ -6,3 +6,11 @@ config PPC4xx_PCI_EXPRESS
 	bool
 	depends on PCI && 4xx
 	default n
+
+config PPC4xx_GPIO
+	bool "PPC4xx GPIO support"
+	depends on 4xx
+	select ARCH_REQUIRE_GPIOLIB
+	select GENERIC_GPIO
+	help
+	  Enable gpiolib support for PPC4xx based boards
diff --git a/arch/powerpc/sysdev/Makefile b/arch/powerpc/sysdev/Makefile
index a90054b..35d5765 100644
--- a/arch/powerpc/sysdev/Makefile
+++ b/arch/powerpc/sysdev/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_OF_RTC)		+= of_rtc.o
 ifeq ($(CONFIG_PCI),y)
 obj-$(CONFIG_4xx)		+= ppc4xx_pci.o
 endif
+obj-$(CONFIG_PPC4xx_GPIO)	+= ppc4xx_gpio.o
 
 # Temporary hack until we have migrated to asm-powerpc
 ifeq ($(ARCH),powerpc)
diff --git a/arch/powerpc/sysdev/ppc4xx_gpio.c b/arch/powerpc/sysdev/ppc4xx_gpio.c
new file mode 100644
index 0000000..93acd3f
--- /dev/null
+++ b/arch/powerpc/sysdev/ppc4xx_gpio.c
@@ -0,0 +1,238 @@
+/*
+ * PPC4xx gpio driver
+ *
+ * Copyright (c) 2008 Harris Corporation
+ * Copyright (c) 2008 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
+ * Copyright (c) MontaVista Software, Inc. 2008.
+ *
+ * Author: Steve Falco <sfalco@harris.com>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/gpio.h>
+
+#include <asm/ppc4xx.h>
+
+struct ppc4xx_gpio_chip {
+	struct of_mm_gpio_chip mm_gc;
+	spinlock_t lock;
+	u32 shadow_or;
+	u32 shadow_tcr;
+	u32 shadow_osrl;
+	u32 shadow_osrh;
+	u32 shadow_tsrl;
+	u32 shadow_tsrh;
+	u32 shadow_odr;
+};
+
+/*
+ * GPIO LIB API implementation for GPIOs
+ *
+ * There are a maximum of 64 gpios, spread over two sets of control registers.
+ * The sets are called GPIO0 and GPIO1.  Each set is managed separately, so
+ * there will be two entried in the .dts file.
+ */
+
+static inline struct ppc4xx_gpio_chip *
+to_ppc4xx_gpiochip(struct of_mm_gpio_chip *mm_gc)
+{
+	return container_of(mm_gc, struct ppc4xx_gpio_chip, mm_gc);
+}
+
+static int ppc4xx_gpio_get(struct gpio_chip *gc, unsigned int gpio)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
+	u32 ret;
+
+	ret = (in_be32(&regs->gpio_ir) >> (31 - gpio)) & 1;
+
+	return ret;
+}
+
+static inline void
+__ppc4xx_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct ppc4xx_gpio_chip *chip = to_ppc4xx_gpiochip(mm_gc);
+	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
+
+	if (val)
+		chip->shadow_or |= 1 << (31 - gpio);
+	else
+		chip->shadow_or &= ~(1 << (31 - gpio));
+	out_be32(&regs->gpio_or, chip->shadow_or);
+}
+
+static void
+ppc4xx_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct ppc4xx_gpio_chip *chip = to_ppc4xx_gpiochip(mm_gc);
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	__ppc4xx_gpio_set(gc, gpio, val);
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val);
+}
+
+static int ppc4xx_gpio_dir_in(struct gpio_chip *gc, unsigned int gpio)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct ppc4xx_gpio_chip *chip = to_ppc4xx_gpiochip(mm_gc);
+	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	/* Disable open-drain function */
+	chip->shadow_odr &= ~(1 << (31 - gpio));
+	out_be32(&regs->gpio_odr, chip->shadow_odr);
+
+	/* Float the pin */
+	chip->shadow_tcr &= ~(1 << (31 - gpio));
+	out_be32(&regs->gpio_tcr, chip->shadow_tcr);
+
+	/* Bits 0-15 use TSRL/OSRL, bits 16-31 use TSRH/OSRH */
+	if (gpio < 16) {
+		chip->shadow_osrl &= ~(3 << ((15 - gpio) * 2));
+		out_be32(&regs->gpio_osrl, chip->shadow_osrl);
+
+		chip->shadow_tsrl &= ~(3 << ((15 - gpio) * 2));
+		out_be32(&regs->gpio_tsrl, chip->shadow_tsrl);
+	} else {
+		chip->shadow_osrh &= ~(3 << ((31 - gpio) * 2));
+		out_be32(&regs->gpio_osrh, chip->shadow_osrh);
+
+		chip->shadow_tsrh &= ~(3 << ((31 - gpio) * 2));
+		out_be32(&regs->gpio_tsrh, chip->shadow_tsrh);
+	}
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	return 0;
+}
+
+static int
+ppc4xx_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct ppc4xx_gpio_chip *chip = to_ppc4xx_gpiochip(mm_gc);
+	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	/* First set initial value */
+	__ppc4xx_gpio_set(gc, gpio, val);
+
+	/* Disable open-drain function */
+	chip->shadow_odr &= ~(1 << (31 - gpio));
+	out_be32(&regs->gpio_odr, chip->shadow_odr);
+
+	/* Drive the pin */
+	chip->shadow_tcr |= (1 << (31 - gpio));
+	out_be32(&regs->gpio_tcr, chip->shadow_tcr);
+
+	/* Bits 0-15 use TSRL, bits 16-31 use TSRH */
+	if (gpio < 16) {
+		chip->shadow_osrl &= ~(3 << ((15 - gpio) * 2));
+		out_be32(&regs->gpio_osrl, chip->shadow_osrl);
+
+		chip->shadow_tsrl &= ~(3 << ((15 - gpio) * 2));
+		out_be32(&regs->gpio_tsrl, chip->shadow_tsrl);
+	} else {
+		chip->shadow_osrh &= ~(3 << ((31 - gpio) * 2));
+		out_be32(&regs->gpio_osrh, chip->shadow_osrh);
+
+		chip->shadow_tsrh &= ~(3 << ((31 - gpio) * 2));
+		out_be32(&regs->gpio_tsrh, chip->shadow_tsrh);
+	}
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val);
+
+	return 0;
+}
+
+static void __init ppc4xx_gpio_save_regs(struct of_mm_gpio_chip *mm_gc)
+{
+	struct ppc4xx_gpio_chip *chip = to_ppc4xx_gpiochip(mm_gc);
+	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
+
+	chip->shadow_or = in_be32(&regs->gpio_or);
+	chip->shadow_tcr = in_be32(&regs->gpio_tcr);
+	chip->shadow_osrl = in_be32(&regs->gpio_osrl);
+	chip->shadow_osrh = in_be32(&regs->gpio_osrh);
+	chip->shadow_tsrl = in_be32(&regs->gpio_tsrl);
+	chip->shadow_tsrh = in_be32(&regs->gpio_tsrh);
+	chip->shadow_odr = in_be32(&regs->gpio_odr);
+}
+
+static int __init ppc4xx_add_gpiochips(void)
+{
+	struct device_node *np;
+
+	for_each_compatible_node(np, NULL, "amcc,ppc4xx-gpio") {
+		int ret;
+		struct ppc4xx_gpio_chip *ppc4xx_gc;
+		struct of_mm_gpio_chip *mm_gc;
+		struct of_gpio_chip *of_gc;
+		struct gpio_chip *gc;
+
+		ppc4xx_gc = kzalloc(sizeof(*ppc4xx_gc), GFP_KERNEL);
+		if (!ppc4xx_gc) {
+			ret = -ENOMEM;
+			goto err;
+		}
+
+		spin_lock_init(&ppc4xx_gc->lock);
+
+		mm_gc = &ppc4xx_gc->mm_gc;
+		of_gc = &mm_gc->of_gc;
+		gc = &of_gc->gc;
+
+		mm_gc->save_regs = ppc4xx_gpio_save_regs;
+		of_gc->gpio_cells = 2;
+		gc->ngpio = 32;
+		gc->direction_input = ppc4xx_gpio_dir_in;
+		gc->direction_output = ppc4xx_gpio_dir_out;
+		gc->get = ppc4xx_gpio_get;
+		gc->set = ppc4xx_gpio_set;
+
+		ret = of_mm_gpiochip_add(np, mm_gc);
+		if (ret)
+			goto err;
+		continue;
+err:
+		pr_err("%s: registration failed with status %d\n",
+		       np->full_name, ret);
+		kfree(ppc4xx_gc);
+		/* try others anyway */
+	}
+	return 0;
+}
+arch_initcall(ppc4xx_add_gpiochips);

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: PPC440EPx gpio driver
  2008-10-09 17:09     ` Steven A. Falco
@ 2008-10-09 17:38       ` Anton Vorontsov
  2008-10-09 18:04         ` Steven A. Falco
  0 siblings, 1 reply; 14+ messages in thread
From: Anton Vorontsov @ 2008-10-09 17:38 UTC (permalink / raw)
  To: Steven A. Falco; +Cc: linuxppc-dev

On Thu, Oct 09, 2008 at 01:09:18PM -0400, Steven A. Falco wrote:
> Please disregard the previous version, and consider this one instead.
> The only difference is making better use of to_ppc4xx_gpiochip(), which
> helps readability.
> 
> Signed-off-by: Steve Falco <sfalco at harris.com>
[...]
>  #endif /* __ASM_POWERPC_PPC4xx_H__ */
> diff --git a/arch/powerpc/sysdev/Kconfig b/arch/powerpc/sysdev/Kconfig
> index 72fb35b..f4a8edb 100644
> --- a/arch/powerpc/sysdev/Kconfig
> +++ b/arch/powerpc/sysdev/Kconfig
> @@ -6,3 +6,11 @@ config PPC4xx_PCI_EXPRESS
>  	bool
>  	depends on PCI && 4xx
>  	default n
> +
> +config PPC4xx_GPIO
> +	bool "PPC4xx GPIO support"
> +	depends on 4xx
> +	select ARCH_REQUIRE_GPIOLIB
> +	select GENERIC_GPIO
> +	help
> +	  Enable gpiolib support for PPC4xx based boards

User-selectable options should go into the
arch/powerpc/platforms/Kconfig, otherwise you'll see the PPC4xx GPIO
support in the top-level menu in the menuconfig.

> diff --git a/arch/powerpc/sysdev/Makefile b/arch/powerpc/sysdev/Makefile
> index a90054b..35d5765 100644
> --- a/arch/powerpc/sysdev/Makefile
> +++ b/arch/powerpc/sysdev/Makefile
> @@ -35,6 +35,7 @@ obj-$(CONFIG_OF_RTC)		+= of_rtc.o
>  ifeq ($(CONFIG_PCI),y)
>  obj-$(CONFIG_4xx)		+= ppc4xx_pci.o
>  endif
> +obj-$(CONFIG_PPC4xx_GPIO)	+= ppc4xx_gpio.o

Though the driver and Makefile entries are OK to live in the the
sysdev/.

>  
>  # Temporary hack until we have migrated to asm-powerpc
>  ifeq ($(ARCH),powerpc)
> diff --git a/arch/powerpc/sysdev/ppc4xx_gpio.c b/arch/powerpc/sysdev/ppc4xx_gpio.c
> new file mode 100644
> index 0000000..93acd3f
> --- /dev/null
> +++ b/arch/powerpc/sysdev/ppc4xx_gpio.c
> @@ -0,0 +1,238 @@
> +/*
> + * PPC4xx gpio driver
> + *
> + * Copyright (c) 2008 Harris Corporation
> + * Copyright (c) 2008 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
> + * Copyright (c) MontaVista Software, Inc. 2008.
> + *
> + * Author: Steve Falco <sfalco@harris.com>
> + *
> + * 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.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/init.h>
> +#include <linux/spinlock.h>
> +#include <linux/io.h>
> +#include <linux/of.h>
> +#include <linux/of_gpio.h>
> +#include <linux/gpio.h>

We explicitly include all the headers, even if they're already
included in some header files. Thus you might want to add
#include <linux/types.h>


Reviewed-by: Anton Vorontsov <avorontsov@ru.mvista.com>

Thanks!

-- 
Anton Vorontsov
email: cbouatmailru@gmail.com
irc://irc.freenode.net/bd2

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: PPC440EPx gpio driver
  2008-10-09 17:38       ` Anton Vorontsov
@ 2008-10-09 18:04         ` Steven A. Falco
  2008-10-09 18:46           ` Stefan Roese
  0 siblings, 1 reply; 14+ messages in thread
From: Steven A. Falco @ 2008-10-09 18:04 UTC (permalink / raw)
  To: avorontsov; +Cc: linuxppc-dev

I've moved the config variable to platforms/44x, and added the #include
of linux/types.h.

Since I don't have any PPC405 boards, I am unable to test with that CPU.
So, I am reluctant to add the config variable to platforms/40x.  It
would be trivial to add, if someone else has a suitable setup and could
run a test.

Signed-off-by: Steve Falco <sfalco at harris.com>

diff --git a/arch/powerpc/include/asm/ppc4xx.h b/arch/powerpc/include/asm/ppc4xx.h
index 033039a..589ff5c 100644
--- a/arch/powerpc/include/asm/ppc4xx.h
+++ b/arch/powerpc/include/asm/ppc4xx.h
@@ -13,6 +13,30 @@
 #ifndef __ASM_POWERPC_PPC4xx_H__
 #define __ASM_POWERPC_PPC4xx_H__
 
+#include <linux/types.h>
+
+/* GPIO */
+struct ppc4xx_gpio {
+	__be32 gpio_or;
+	__be32 gpio_tcr;
+	__be32 gpio_osrl;
+	__be32 gpio_osrh;
+	__be32 gpio_tsrl;
+	__be32 gpio_tsrh;
+	__be32 gpio_odr;
+	__be32 gpio_ir;
+	__be32 gpio_rr1;
+	__be32 gpio_rr2;
+	__be32 gpio_rr3;
+	__be32 reserved1;
+	__be32 gpio_isr1l;
+	__be32 gpio_isr1h;
+	__be32 gpio_isr2l;
+	__be32 gpio_isr2h;
+	__be32 gpio_isr3l;
+	__be32 gpio_isr3h;
+};
+
 extern void ppc4xx_reset_system(char *cmd);
 
 #endif /* __ASM_POWERPC_PPC4xx_H__ */
diff --git a/arch/powerpc/platforms/44x/Kconfig b/arch/powerpc/platforms/44x/Kconfig
index 249ba01..eec5cb4 100644
--- a/arch/powerpc/platforms/44x/Kconfig
+++ b/arch/powerpc/platforms/44x/Kconfig
@@ -127,6 +143,14 @@ config XILINX_VIRTEX440_GENERIC_BOARD
 	  Most Virtex 5 designs should use this unless it needs to do some
 	  special configuration at board probe time.
 
+config PPC4xx_GPIO
+	bool "PPC4xx GPIO support"
+	depends on 44x
+	select ARCH_REQUIRE_GPIOLIB
+	select GENERIC_GPIO
+	help
+	  Enable gpiolib support for ppc440 based boards
+
 # 44x specific CPU modules, selected based on the board above.
 config 440EP
 	bool
diff --git a/arch/powerpc/sysdev/Makefile b/arch/powerpc/sysdev/Makefile
index a90054b..35d5765 100644
--- a/arch/powerpc/sysdev/Makefile
+++ b/arch/powerpc/sysdev/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_OF_RTC)		+= of_rtc.o
 ifeq ($(CONFIG_PCI),y)
 obj-$(CONFIG_4xx)		+= ppc4xx_pci.o
 endif
+obj-$(CONFIG_PPC4xx_GPIO)	+= ppc4xx_gpio.o
 
 # Temporary hack until we have migrated to asm-powerpc
 ifeq ($(ARCH),powerpc)
diff --git a/arch/powerpc/sysdev/ppc4xx_gpio.c b/arch/powerpc/sysdev/ppc4xx_gpio.c
new file mode 100644
index 0000000..d401eba
--- /dev/null
+++ b/arch/powerpc/sysdev/ppc4xx_gpio.c
@@ -0,0 +1,239 @@
+/*
+ * PPC4xx gpio driver
+ *
+ * Copyright (c) 2008 Harris Corporation
+ * Copyright (c) 2008 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
+ * Copyright (c) MontaVista Software, Inc. 2008.
+ *
+ * Author: Steve Falco <sfalco@harris.com>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/gpio.h>
+#include <linux/types.h>
+
+#include <asm/ppc4xx.h>
+
+struct ppc4xx_gpio_chip {
+	struct of_mm_gpio_chip mm_gc;
+	spinlock_t lock;
+	u32 shadow_or;
+	u32 shadow_tcr;
+	u32 shadow_osrl;
+	u32 shadow_osrh;
+	u32 shadow_tsrl;
+	u32 shadow_tsrh;
+	u32 shadow_odr;
+};
+
+/*
+ * GPIO LIB API implementation for GPIOs
+ *
+ * There are a maximum of 64 gpios, spread over two sets of control registers.
+ * The sets are called GPIO0 and GPIO1.  Each set is managed separately, so
+ * there will be two entried in the .dts file.
+ */
+
+static inline struct ppc4xx_gpio_chip *
+to_ppc4xx_gpiochip(struct of_mm_gpio_chip *mm_gc)
+{
+	return container_of(mm_gc, struct ppc4xx_gpio_chip, mm_gc);
+}
+
+static int ppc4xx_gpio_get(struct gpio_chip *gc, unsigned int gpio)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
+	u32 ret;
+
+	ret = (in_be32(&regs->gpio_ir) >> (31 - gpio)) & 1;
+
+	return ret;
+}
+
+static inline void
+__ppc4xx_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct ppc4xx_gpio_chip *chip = to_ppc4xx_gpiochip(mm_gc);
+	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
+
+	if (val)
+		chip->shadow_or |= 1 << (31 - gpio);
+	else
+		chip->shadow_or &= ~(1 << (31 - gpio));
+	out_be32(&regs->gpio_or, chip->shadow_or);
+}
+
+static void
+ppc4xx_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct ppc4xx_gpio_chip *chip = to_ppc4xx_gpiochip(mm_gc);
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	__ppc4xx_gpio_set(gc, gpio, val);
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val);
+}
+
+static int ppc4xx_gpio_dir_in(struct gpio_chip *gc, unsigned int gpio)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct ppc4xx_gpio_chip *chip = to_ppc4xx_gpiochip(mm_gc);
+	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	/* Disable open-drain function */
+	chip->shadow_odr &= ~(1 << (31 - gpio));
+	out_be32(&regs->gpio_odr, chip->shadow_odr);
+
+	/* Float the pin */
+	chip->shadow_tcr &= ~(1 << (31 - gpio));
+	out_be32(&regs->gpio_tcr, chip->shadow_tcr);
+
+	/* Bits 0-15 use TSRL/OSRL, bits 16-31 use TSRH/OSRH */
+	if (gpio < 16) {
+		chip->shadow_osrl &= ~(3 << ((15 - gpio) * 2));
+		out_be32(&regs->gpio_osrl, chip->shadow_osrl);
+
+		chip->shadow_tsrl &= ~(3 << ((15 - gpio) * 2));
+		out_be32(&regs->gpio_tsrl, chip->shadow_tsrl);
+	} else {
+		chip->shadow_osrh &= ~(3 << ((31 - gpio) * 2));
+		out_be32(&regs->gpio_osrh, chip->shadow_osrh);
+
+		chip->shadow_tsrh &= ~(3 << ((31 - gpio) * 2));
+		out_be32(&regs->gpio_tsrh, chip->shadow_tsrh);
+	}
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	return 0;
+}
+
+static int
+ppc4xx_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
+	struct ppc4xx_gpio_chip *chip = to_ppc4xx_gpiochip(mm_gc);
+	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+
+	/* First set initial value */
+	__ppc4xx_gpio_set(gc, gpio, val);
+
+	/* Disable open-drain function */
+	chip->shadow_odr &= ~(1 << (31 - gpio));
+	out_be32(&regs->gpio_odr, chip->shadow_odr);
+
+	/* Drive the pin */
+	chip->shadow_tcr |= (1 << (31 - gpio));
+	out_be32(&regs->gpio_tcr, chip->shadow_tcr);
+
+	/* Bits 0-15 use TSRL, bits 16-31 use TSRH */
+	if (gpio < 16) {
+		chip->shadow_osrl &= ~(3 << ((15 - gpio) * 2));
+		out_be32(&regs->gpio_osrl, chip->shadow_osrl);
+
+		chip->shadow_tsrl &= ~(3 << ((15 - gpio) * 2));
+		out_be32(&regs->gpio_tsrl, chip->shadow_tsrl);
+	} else {
+		chip->shadow_osrh &= ~(3 << ((31 - gpio) * 2));
+		out_be32(&regs->gpio_osrh, chip->shadow_osrh);
+
+		chip->shadow_tsrh &= ~(3 << ((31 - gpio) * 2));
+		out_be32(&regs->gpio_tsrh, chip->shadow_tsrh);
+	}
+
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val);
+
+	return 0;
+}
+
+static void __init ppc4xx_gpio_save_regs(struct of_mm_gpio_chip *mm_gc)
+{
+	struct ppc4xx_gpio_chip *chip = to_ppc4xx_gpiochip(mm_gc);
+	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
+
+	chip->shadow_or = in_be32(&regs->gpio_or);
+	chip->shadow_tcr = in_be32(&regs->gpio_tcr);
+	chip->shadow_osrl = in_be32(&regs->gpio_osrl);
+	chip->shadow_osrh = in_be32(&regs->gpio_osrh);
+	chip->shadow_tsrl = in_be32(&regs->gpio_tsrl);
+	chip->shadow_tsrh = in_be32(&regs->gpio_tsrh);
+	chip->shadow_odr = in_be32(&regs->gpio_odr);
+}
+
+static int __init ppc4xx_add_gpiochips(void)
+{
+	struct device_node *np;
+
+	for_each_compatible_node(np, NULL, "amcc,ppc4xx-gpio") {
+		int ret;
+		struct ppc4xx_gpio_chip *ppc4xx_gc;
+		struct of_mm_gpio_chip *mm_gc;
+		struct of_gpio_chip *of_gc;
+		struct gpio_chip *gc;
+
+		ppc4xx_gc = kzalloc(sizeof(*ppc4xx_gc), GFP_KERNEL);
+		if (!ppc4xx_gc) {
+			ret = -ENOMEM;
+			goto err;
+		}
+
+		spin_lock_init(&ppc4xx_gc->lock);
+
+		mm_gc = &ppc4xx_gc->mm_gc;
+		of_gc = &mm_gc->of_gc;
+		gc = &of_gc->gc;
+
+		mm_gc->save_regs = ppc4xx_gpio_save_regs;
+		of_gc->gpio_cells = 2;
+		gc->ngpio = 32;
+		gc->direction_input = ppc4xx_gpio_dir_in;
+		gc->direction_output = ppc4xx_gpio_dir_out;
+		gc->get = ppc4xx_gpio_get;
+		gc->set = ppc4xx_gpio_set;
+
+		ret = of_mm_gpiochip_add(np, mm_gc);
+		if (ret)
+			goto err;
+		continue;
+err:
+		pr_err("%s: registration failed with status %d\n",
+		       np->full_name, ret);
+		kfree(ppc4xx_gc);
+		/* try others anyway */
+	}
+	return 0;
+}
+arch_initcall(ppc4xx_add_gpiochips);

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: PPC440EPx gpio driver
  2008-10-09 18:04         ` Steven A. Falco
@ 2008-10-09 18:46           ` Stefan Roese
  2008-10-09 19:03             ` Anton Vorontsov
  0 siblings, 1 reply; 14+ messages in thread
From: Stefan Roese @ 2008-10-09 18:46 UTC (permalink / raw)
  To: linuxppc-dev

Steve,

On Thursday 09 October 2008, Steven A. Falco wrote:
> I've moved the config variable to platforms/44x, and added the #include
> of linux/types.h.
>
> Since I don't have any PPC405 boards, I am unable to test with that CPU.
> So, I am reluctant to add the config variable to platforms/40x.  It
> would be trivial to add, if someone else has a suitable setup and could
> run a test.

Yes, please add it and I'll run a test tomorrow on my 405EP system.

It would be great if you could generate a git style patch where you put the 
patch description on top of the mail and can put those comments (like changes 
between versions) below the "---" line. Those comments will not appear in the 
git history after applying. And then please add a version number to the 
subject. Example:

[PATCH v2] powerpc/4xx: Add 4xx gpio driver

Some more comments below.

> Signed-off-by: Steve Falco <sfalco at harris.com>
>
> diff --git a/arch/powerpc/include/asm/ppc4xx.h
> b/arch/powerpc/include/asm/ppc4xx.h index 033039a..589ff5c 100644
> --- a/arch/powerpc/include/asm/ppc4xx.h
> +++ b/arch/powerpc/include/asm/ppc4xx.h
> @@ -13,6 +13,30 @@
>  #ifndef __ASM_POWERPC_PPC4xx_H__
>  #define __ASM_POWERPC_PPC4xx_H__
>
> +#include <linux/types.h>
> +
> +/* GPIO */
> +struct ppc4xx_gpio {
> +	__be32 gpio_or;
> +	__be32 gpio_tcr;
> +	__be32 gpio_osrl;
> +	__be32 gpio_osrh;
> +	__be32 gpio_tsrl;
> +	__be32 gpio_tsrh;
> +	__be32 gpio_odr;
> +	__be32 gpio_ir;
> +	__be32 gpio_rr1;
> +	__be32 gpio_rr2;
> +	__be32 gpio_rr3;
> +	__be32 reserved1;
> +	__be32 gpio_isr1l;
> +	__be32 gpio_isr1h;
> +	__be32 gpio_isr2l;
> +	__be32 gpio_isr2h;
> +	__be32 gpio_isr3l;
> +	__be32 gpio_isr3h;
> +};
> +

Not sure if we really need those defines in this header. They are only 
referenced by the gpio driver so why not move it to the driver instead?

>  extern void ppc4xx_reset_system(char *cmd);
>
>  #endif /* __ASM_POWERPC_PPC4xx_H__ */
> diff --git a/arch/powerpc/platforms/44x/Kconfig
> b/arch/powerpc/platforms/44x/Kconfig index 249ba01..eec5cb4 100644
> --- a/arch/powerpc/platforms/44x/Kconfig
> +++ b/arch/powerpc/platforms/44x/Kconfig
> @@ -127,6 +143,14 @@ config XILINX_VIRTEX440_GENERIC_BOARD
>  	  Most Virtex 5 designs should use this unless it needs to do some
>  	  special configuration at board probe time.
>
> +config PPC4xx_GPIO
> +	bool "PPC4xx GPIO support"
> +	depends on 44x
> +	select ARCH_REQUIRE_GPIOLIB
> +	select GENERIC_GPIO
> +	help
> +	  Enable gpiolib support for ppc440 based boards
> +

Please add gpio support for 40x as well. I'll test it tomorrow.

>  # 44x specific CPU modules, selected based on the board above.
>  config 440EP
>  	bool
> diff --git a/arch/powerpc/sysdev/Makefile b/arch/powerpc/sysdev/Makefile
> index a90054b..35d5765 100644
> --- a/arch/powerpc/sysdev/Makefile
> +++ b/arch/powerpc/sysdev/Makefile
> @@ -35,6 +35,7 @@ obj-$(CONFIG_OF_RTC)		+= of_rtc.o
>  ifeq ($(CONFIG_PCI),y)
>  obj-$(CONFIG_4xx)		+= ppc4xx_pci.o
>  endif
> +obj-$(CONFIG_PPC4xx_GPIO)	+= ppc4xx_gpio.o
>
>  # Temporary hack until we have migrated to asm-powerpc
>  ifeq ($(ARCH),powerpc)
> diff --git a/arch/powerpc/sysdev/ppc4xx_gpio.c
> b/arch/powerpc/sysdev/ppc4xx_gpio.c new file mode 100644
> index 0000000..d401eba
> --- /dev/null
> +++ b/arch/powerpc/sysdev/ppc4xx_gpio.c
> @@ -0,0 +1,239 @@
> +/*
> + * PPC4xx gpio driver
> + *
> + * Copyright (c) 2008 Harris Corporation
> + * Copyright (c) 2008 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
> + * Copyright (c) MontaVista Software, Inc. 2008.
> + *
> + * Author: Steve Falco <sfalco@harris.com>
> + *
> + * 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.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 
> USA + */
> +
> +#include <linux/kernel.h>
> +#include <linux/init.h>
> +#include <linux/spinlock.h>
> +#include <linux/io.h>
> +#include <linux/of.h>
> +#include <linux/of_gpio.h>
> +#include <linux/gpio.h>
> +#include <linux/types.h>
> +
> +#include <asm/ppc4xx.h>
> +
> +struct ppc4xx_gpio_chip {
> +	struct of_mm_gpio_chip mm_gc;
> +	spinlock_t lock;
> +	u32 shadow_or;
> +	u32 shadow_tcr;
> +	u32 shadow_osrl;
> +	u32 shadow_osrh;
> +	u32 shadow_tsrl;
> +	u32 shadow_tsrh;
> +	u32 shadow_odr;
> +};

Do we really need all those shadow registers? Access to the "real" registers 
is done while holding the spinlock.

> +
> +/*
> + * GPIO LIB API implementation for GPIOs
> + *
> + * There are a maximum of 64 gpios, spread over two sets of control
> registers. + * The sets are called GPIO0 and GPIO1.  Each set is managed
> separately, so + * there will be two entried in the .dts file.
> + */
> +
> +static inline struct ppc4xx_gpio_chip *
> +to_ppc4xx_gpiochip(struct of_mm_gpio_chip *mm_gc)
> +{
> +	return container_of(mm_gc, struct ppc4xx_gpio_chip, mm_gc);
> +}
> +
> +static int ppc4xx_gpio_get(struct gpio_chip *gc, unsigned int gpio)
> +{
> +	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
> +	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
> +	u32 ret;
> +
> +	ret = (in_be32(&regs->gpio_ir) >> (31 - gpio)) & 1;
> +
> +	return ret;

	return in_be32(&regs->gpio_ir) >> (31 - gpio)) & 1;

should be enough.

> +}
> +
> +static inline void
> +__ppc4xx_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
> +{
> +	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
> +	struct ppc4xx_gpio_chip *chip = to_ppc4xx_gpiochip(mm_gc);
> +	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
> +
> +	if (val)
> +		chip->shadow_or |= 1 << (31 - gpio);

This "1 << (31 - gpio)" is used quite often. Perhaps it makes sense to use a 
macro here to make the code better readable.

> +	else
> +		chip->shadow_or &= ~(1 << (31 - gpio));
> +	out_be32(&regs->gpio_or, chip->shadow_or);
> +}
> +
> +static void
> +ppc4xx_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
> +{
> +	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
> +	struct ppc4xx_gpio_chip *chip = to_ppc4xx_gpiochip(mm_gc);
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&chip->lock, flags);
> +
> +	__ppc4xx_gpio_set(gc, gpio, val);
> +
> +	spin_unlock_irqrestore(&chip->lock, flags);
> +
> +	pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val);
> +}
> +
> +static int ppc4xx_gpio_dir_in(struct gpio_chip *gc, unsigned int gpio)
> +{
> +	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
> +	struct ppc4xx_gpio_chip *chip = to_ppc4xx_gpiochip(mm_gc);
> +	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&chip->lock, flags);
> +
> +	/* Disable open-drain function */
> +	chip->shadow_odr &= ~(1 << (31 - gpio));
> +	out_be32(&regs->gpio_odr, chip->shadow_odr);
> +
> +	/* Float the pin */
> +	chip->shadow_tcr &= ~(1 << (31 - gpio));
> +	out_be32(&regs->gpio_tcr, chip->shadow_tcr);
> +
> +	/* Bits 0-15 use TSRL/OSRL, bits 16-31 use TSRH/OSRH */
> +	if (gpio < 16) {
> +		chip->shadow_osrl &= ~(3 << ((15 - gpio) * 2));
> +		out_be32(&regs->gpio_osrl, chip->shadow_osrl);
> +
> +		chip->shadow_tsrl &= ~(3 << ((15 - gpio) * 2));
> +		out_be32(&regs->gpio_tsrl, chip->shadow_tsrl);
> +	} else {
> +		chip->shadow_osrh &= ~(3 << ((31 - gpio) * 2));
> +		out_be32(&regs->gpio_osrh, chip->shadow_osrh);
> +
> +		chip->shadow_tsrh &= ~(3 << ((31 - gpio) * 2));
> +		out_be32(&regs->gpio_tsrh, chip->shadow_tsrh);
> +	}
> +
> +	spin_unlock_irqrestore(&chip->lock, flags);
> +
> +	return 0;
> +}
> +
> +static int
> +ppc4xx_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val)
> +{
> +	struct of_mm_gpio_chip *mm_gc = to_of_mm_gpio_chip(gc);
> +	struct ppc4xx_gpio_chip *chip = to_ppc4xx_gpiochip(mm_gc);
> +	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&chip->lock, flags);
> +
> +	/* First set initial value */
> +	__ppc4xx_gpio_set(gc, gpio, val);
> +
> +	/* Disable open-drain function */
> +	chip->shadow_odr &= ~(1 << (31 - gpio));
> +	out_be32(&regs->gpio_odr, chip->shadow_odr);
> +
> +	/* Drive the pin */
> +	chip->shadow_tcr |= (1 << (31 - gpio));
> +	out_be32(&regs->gpio_tcr, chip->shadow_tcr);
> +
> +	/* Bits 0-15 use TSRL, bits 16-31 use TSRH */
> +	if (gpio < 16) {
> +		chip->shadow_osrl &= ~(3 << ((15 - gpio) * 2));
> +		out_be32(&regs->gpio_osrl, chip->shadow_osrl);
> +
> +		chip->shadow_tsrl &= ~(3 << ((15 - gpio) * 2));
> +		out_be32(&regs->gpio_tsrl, chip->shadow_tsrl);
> +	} else {
> +		chip->shadow_osrh &= ~(3 << ((31 - gpio) * 2));
> +		out_be32(&regs->gpio_osrh, chip->shadow_osrh);
> +
> +		chip->shadow_tsrh &= ~(3 << ((31 - gpio) * 2));
> +		out_be32(&regs->gpio_tsrh, chip->shadow_tsrh);
> +	}
> +
> +	spin_unlock_irqrestore(&chip->lock, flags);
> +
> +	pr_debug("%s: gpio: %d val: %d\n", __func__, gpio, val);
> +
> +	return 0;
> +}
> +
> +static void __init ppc4xx_gpio_save_regs(struct of_mm_gpio_chip *mm_gc)
> +{
> +	struct ppc4xx_gpio_chip *chip = to_ppc4xx_gpiochip(mm_gc);
> +	struct ppc4xx_gpio __iomem *regs = mm_gc->regs;
> +
> +	chip->shadow_or = in_be32(&regs->gpio_or);
> +	chip->shadow_tcr = in_be32(&regs->gpio_tcr);
> +	chip->shadow_osrl = in_be32(&regs->gpio_osrl);
> +	chip->shadow_osrh = in_be32(&regs->gpio_osrh);
> +	chip->shadow_tsrl = in_be32(&regs->gpio_tsrl);
> +	chip->shadow_tsrh = in_be32(&regs->gpio_tsrh);
> +	chip->shadow_odr = in_be32(&regs->gpio_odr);
> +}
> +
> +static int __init ppc4xx_add_gpiochips(void)
> +{
> +	struct device_node *np;
> +
> +	for_each_compatible_node(np, NULL, "amcc,ppc4xx-gpio") {
> +		int ret;
> +		struct ppc4xx_gpio_chip *ppc4xx_gc;
> +		struct of_mm_gpio_chip *mm_gc;
> +		struct of_gpio_chip *of_gc;
> +		struct gpio_chip *gc;
> +
> +		ppc4xx_gc = kzalloc(sizeof(*ppc4xx_gc), GFP_KERNEL);
> +		if (!ppc4xx_gc) {
> +			ret = -ENOMEM;
> +			goto err;
> +		}
> +
> +		spin_lock_init(&ppc4xx_gc->lock);
> +
> +		mm_gc = &ppc4xx_gc->mm_gc;
> +		of_gc = &mm_gc->of_gc;
> +		gc = &of_gc->gc;
> +
> +		mm_gc->save_regs = ppc4xx_gpio_save_regs;
> +		of_gc->gpio_cells = 2;
> +		gc->ngpio = 32;
> +		gc->direction_input = ppc4xx_gpio_dir_in;
> +		gc->direction_output = ppc4xx_gpio_dir_out;
> +		gc->get = ppc4xx_gpio_get;
> +		gc->set = ppc4xx_gpio_set;
> +
> +		ret = of_mm_gpiochip_add(np, mm_gc);
> +		if (ret)
> +			goto err;
> +		continue;
> +err:
> +		pr_err("%s: registration failed with status %d\n",
> +		       np->full_name, ret);
> +		kfree(ppc4xx_gc);

This probably crashes if the kzalloc() fails above.

> +		/* try others anyway */
> +	}
> +	return 0;
> +}
> +arch_initcall(ppc4xx_add_gpiochips);
> _______________________________________________
> Linuxppc-dev mailing list
> Linuxppc-dev@ozlabs.org
> https://ozlabs.org/mailman/listinfo/linuxppc-dev


Best regards,
Stefan

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: PPC440EPx gpio driver
  2008-10-09 18:46           ` Stefan Roese
@ 2008-10-09 19:03             ` Anton Vorontsov
  0 siblings, 0 replies; 14+ messages in thread
From: Anton Vorontsov @ 2008-10-09 19:03 UTC (permalink / raw)
  To: Stefan Roese; +Cc: linuxppc-dev

On Thu, Oct 09, 2008 at 08:46:44PM +0200, Stefan Roese wrote:
[...]
> > +struct ppc4xx_gpio_chip {
> > +	struct of_mm_gpio_chip mm_gc;
> > +	spinlock_t lock;
> > +	u32 shadow_or;
> > +	u32 shadow_tcr;
> > +	u32 shadow_osrl;
> > +	u32 shadow_osrh;
> > +	u32 shadow_tsrl;
> > +	u32 shadow_tsrh;
> > +	u32 shadow_odr;
> > +};
> 
> Do we really need all those shadow registers? Access to the "real" registers 
> is done while holding the spinlock.

The shadowed registers aren't about holding a spinlock, but are
about open drain pins and the fact that some GPIO controllers are
using single "data" register for "read state", "set state" and
"clear state" GPIO operations.

See Grant's reply:

http://ozlabs.org/pipermail/linuxppc-dev/2008-January/050826.html

(I didn't look for the hardware details of this driver, so I don't
know if the driver really needs to shadow the registers.)

[...]
> > +	for_each_compatible_node(np, NULL, "amcc,ppc4xx-gpio") {
> > +		int ret;
> > +		struct ppc4xx_gpio_chip *ppc4xx_gc;
> > +		struct of_mm_gpio_chip *mm_gc;
> > +		struct of_gpio_chip *of_gc;
> > +		struct gpio_chip *gc;
> > +
> > +		ppc4xx_gc = kzalloc(sizeof(*ppc4xx_gc), GFP_KERNEL);
> > +		if (!ppc4xx_gc) {
> > +			ret = -ENOMEM;
> > +			goto err;
> > +		}
> > +
> > +		spin_lock_init(&ppc4xx_gc->lock);
> > +
> > +		mm_gc = &ppc4xx_gc->mm_gc;
> > +		of_gc = &mm_gc->of_gc;
> > +		gc = &of_gc->gc;
> > +
> > +		mm_gc->save_regs = ppc4xx_gpio_save_regs;
> > +		of_gc->gpio_cells = 2;
> > +		gc->ngpio = 32;
> > +		gc->direction_input = ppc4xx_gpio_dir_in;
> > +		gc->direction_output = ppc4xx_gpio_dir_out;
> > +		gc->get = ppc4xx_gpio_get;
> > +		gc->set = ppc4xx_gpio_set;
> > +
> > +		ret = of_mm_gpiochip_add(np, mm_gc);
> > +		if (ret)
> > +			goto err;
> > +		continue;
> > +err:
> > +		pr_err("%s: registration failed with status %d\n",
> > +		       np->full_name, ret);
> > +		kfree(ppc4xx_gc);
> 
> This probably crashes if the kzalloc() fails above.

kfree(NULL); is defined to be a no-op.


Thanks,

-- 
Anton Vorontsov
email: cbouatmailru@gmail.com
irc://irc.freenode.net/bd2

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: PPC440EPx gpio driver
  2008-10-08 20:56 PPC440EPx gpio driver Steven A. Falco
                   ` (3 preceding siblings ...)
  2008-10-09 15:10 ` Steven A. Falco
@ 2008-10-09 22:08 ` Sean MacLennan
  4 siblings, 0 replies; 14+ messages in thread
From: Sean MacLennan @ 2008-10-09 22:08 UTC (permalink / raw)
  To: linuxppc-dev

On Wed, 08 Oct 2008 16:56:54 -0400
"Steven A. Falco" <sfalco@harris.com> wrote:

>  have begun writing a driver for the GPIOs of the PPC440EPx.  I just
> noticed that the PIKA Warp .dts references a device "ibm,gpio-440ep",
> but so far I have not been able to find a driver for that device.

It's just around the corner, through the blue door ;) That device string
was put in optimistically hoping somebody would write a gpio driver.

FYI the warp has two leds tied to gpios. It currently uses a custom led
driver. Once a gpio driver is written, the of gpio leds driver should
work and the custom driver will go away.

So I am very interested in your driver ;)

Cheers,
   Sean

^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2008-10-09 22:08 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2008-10-08 20:56 PPC440EPx gpio driver Steven A. Falco
2008-10-08 21:33 ` Steven A. Falco
2008-10-09 13:49 ` Anton Vorontsov
2008-10-09 14:23 ` Stefan Roese
2008-10-09 15:10 ` Steven A. Falco
2008-10-09 15:19   ` Stefan Roese
2008-10-09 16:08   ` Anton Vorontsov
2008-10-09 16:55     ` Steven A. Falco
2008-10-09 17:09     ` Steven A. Falco
2008-10-09 17:38       ` Anton Vorontsov
2008-10-09 18:04         ` Steven A. Falco
2008-10-09 18:46           ` Stefan Roese
2008-10-09 19:03             ` Anton Vorontsov
2008-10-09 22:08 ` Sean MacLennan

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).