svn commit: r215142 - in head/sys: arm/conf arm/xscale/ixp425 dev/gpio

Andrew Thompson thompsa at FreeBSD.org
Thu Nov 11 20:18:33 UTC 2010


Author: thompsa
Date: Thu Nov 11 20:18:33 2010
New Revision: 215142
URL: http://svn.freebsd.org/changeset/base/215142

Log:
  Add a GPIO driver for the Gateworks Cambria platform.
  
  The external gpio pins are connected to a PLD on the i2c bus, unfortunatley
  this device does not conform by failing to send an ack after each byte written.
  The iicbb driver will abort the transfer when the address is not ack'd and it
  would introduce a lot of churn to be able to pass a flag down to
  iicbb_start/iicbb_write. Instead we do bad things by grabbing the iicbus but
  then doing our own bit banging.

Added:
  head/sys/arm/xscale/ixp425/cambria_gpio.c   (contents, props changed)
Modified:
  head/sys/arm/conf/CAMBRIA
  head/sys/arm/conf/CAMBRIA.hints
  head/sys/arm/xscale/ixp425/files.avila
  head/sys/dev/gpio/gpiobus.c
  head/sys/dev/gpio/gpioc.c

Modified: head/sys/arm/conf/CAMBRIA
==============================================================================
--- head/sys/arm/conf/CAMBRIA	Thu Nov 11 19:39:38 2010	(r215141)
+++ head/sys/arm/conf/CAMBRIA	Thu Nov 11 20:18:33 2010	(r215142)
@@ -90,6 +90,10 @@ device		ad7418		# AD7418 on I2C bus
 device		cambria_fled	# Font Panel LED on I2C bus
 device		cambria_led	# 8-LED latch
 
+device		gpio
+device		gpioled
+device		cambria_gpio	# GPIO pins on J11
+
 device		ata
 device		atadisk		# ATA disk drives
 device		avila_ata	# Gateworks CF/IDE support

Modified: head/sys/arm/conf/CAMBRIA.hints
==============================================================================
--- head/sys/arm/conf/CAMBRIA.hints	Thu Nov 11 19:39:38 2010	(r215141)
+++ head/sys/arm/conf/CAMBRIA.hints	Thu Nov 11 20:18:33 2010	(r215142)
@@ -54,6 +54,10 @@ hint.fled.0.addr=0x5a
 # Octal LED Latch
 hint.led_cambria.0.at="ixp0"
 
+# GPIO pins
+hint.gpio_cambria.0.at="iicbus0"
+hint.gpio_cambria.0.addr=0x56
+
 # Analog Devices AD7418 temperature sensor
 hint.ad7418.0.at="iicbus0"
 hint.ad7418.0.addr=0x50

Added: head/sys/arm/xscale/ixp425/cambria_gpio.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/sys/arm/xscale/ixp425/cambria_gpio.c	Thu Nov 11 20:18:33 2010	(r215142)
@@ -0,0 +1,471 @@
+/*-
+ * Copyright (c) 2010, Andrew Thompson <thompsa at FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice unmodified, this list of conditions, and the following
+ *    disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * GPIO driver for Gateworks Cambria
+ *
+ * Note:
+ * The Cambria PLD does not set the i2c ack bit after each write, if we used the
+ * regular iicbus interface it would abort the xfer after the address byte
+ * times out and not write our latch. To get around this we grab the iicbus and
+ * then do our own bit banging. This is a comprimise to changing all the iicbb
+ * device methods to allow a flag to be passed down and is similir to how Linux
+ * does it.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/rman.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/gpio.h>
+
+#include <arm/xscale/ixp425/ixp425reg.h>
+#include <arm/xscale/ixp425/ixp425var.h>
+#include <arm/xscale/ixp425/ixdp425reg.h>
+
+#include <dev/iicbus/iiconf.h>
+#include <dev/iicbus/iicbus.h>
+
+#include "iicbb_if.h"
+#include "gpio_if.h"
+
+#define	IIC_M_WR	0	/* write operation */
+#define	PLD_ADDR	0xac	/* slave address */
+
+#define	I2C_DELAY	10
+
+#define	GPIO_CONF_CLR(sc, reg, mask)	\
+	GPIO_CONF_WRITE_4(sc, reg, GPIO_CONF_READ_4(sc, reg) &~ (mask))
+#define	GPIO_CONF_SET(sc, reg, mask)	\
+	GPIO_CONF_WRITE_4(sc, reg, GPIO_CONF_READ_4(sc, reg) | (mask))
+
+#define	GPIO_LOCK(_sc)		mtx_lock(&(_sc)->sc_mtx)
+#define	GPIO_UNLOCK(_sc)	mtx_unlock(&(_sc)->sc_mtx)
+#define	GPIO_LOCK_ASSERT(_sc)	mtx_assert(&(_sc)->sc_mtx, MA_OWNED)
+
+#define	GPIO_PINS		5
+struct cambria_gpio_softc {
+	device_t		sc_dev;
+	bus_space_tag_t		sc_iot;
+	bus_space_handle_t	sc_gpio_ioh;
+        struct mtx		sc_mtx;
+	struct gpio_pin		sc_pins[GPIO_PINS];
+	uint8_t			sc_latch;
+};
+
+struct cambria_gpio_pin {
+	const char *name;
+	int pin;
+	int flags;
+};
+
+extern struct ixp425_softc *ixp425_softc;
+
+static struct cambria_gpio_pin cambria_gpio_pins[GPIO_PINS] = {
+	{ "GPIO0", 0, GPIO_PIN_OUTPUT },
+	{ "GPIO1", 1, GPIO_PIN_OUTPUT },
+	{ "GPIO2", 2, GPIO_PIN_OUTPUT },
+	{ "GPIO3", 3, GPIO_PIN_OUTPUT },
+	{ "GPIO4", 4, GPIO_PIN_OUTPUT },
+};
+
+/*
+ * Helpers
+ */
+static int cambria_gpio_read(struct cambria_gpio_softc *, uint32_t, unsigned int *);
+static int cambria_gpio_write(struct cambria_gpio_softc *);
+
+/*
+ * Driver stuff
+ */
+static int cambria_gpio_probe(device_t dev);
+static int cambria_gpio_attach(device_t dev);
+static int cambria_gpio_detach(device_t dev);
+
+/*
+ * GPIO interface
+ */
+static int cambria_gpio_pin_max(device_t dev, int *maxpin);
+static int cambria_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps);
+static int cambria_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t
+    *flags);
+static int cambria_gpio_pin_getname(device_t dev, uint32_t pin, char *name);
+static int cambria_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags);
+static int cambria_gpio_pin_set(device_t dev, uint32_t pin, unsigned int value);
+static int cambria_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *val);
+static int cambria_gpio_pin_toggle(device_t dev, uint32_t pin);
+
+static int
+i2c_getsda(struct cambria_gpio_softc *sc)
+{
+	uint32_t reg;
+
+	mtx_lock(&Giant);
+	GPIO_CONF_SET(sc, IXP425_GPIO_GPOER, GPIO_I2C_SDA_BIT);
+
+	reg = GPIO_CONF_READ_4(sc, IXP425_GPIO_GPINR);
+	mtx_unlock(&Giant);
+	return (reg & GPIO_I2C_SDA_BIT);
+}
+
+static void
+i2c_setsda(struct cambria_gpio_softc *sc, int val)
+{
+
+	mtx_lock(&Giant);
+	GPIO_CONF_CLR(sc, IXP425_GPIO_GPOUTR, GPIO_I2C_SDA_BIT);
+	if (val)
+		GPIO_CONF_SET(sc, IXP425_GPIO_GPOER, GPIO_I2C_SDA_BIT);
+	else
+		GPIO_CONF_CLR(sc, IXP425_GPIO_GPOER, GPIO_I2C_SDA_BIT);
+	mtx_unlock(&Giant);
+	DELAY(I2C_DELAY);
+}
+
+static void
+i2c_setscl(struct cambria_gpio_softc *sc, int val)
+{
+
+	mtx_lock(&Giant);
+	GPIO_CONF_CLR(sc, IXP425_GPIO_GPOUTR, GPIO_I2C_SCL_BIT);
+	if (val)
+		GPIO_CONF_SET(sc, IXP425_GPIO_GPOER, GPIO_I2C_SCL_BIT);
+	else
+		GPIO_CONF_CLR(sc, IXP425_GPIO_GPOER, GPIO_I2C_SCL_BIT);
+	mtx_unlock(&Giant);
+	DELAY(I2C_DELAY);
+}
+
+static void
+i2c_sendstart(struct cambria_gpio_softc *sc)
+{
+	i2c_setsda(sc, 1);
+	i2c_setscl(sc, 1);
+	i2c_setsda(sc, 0);
+	i2c_setscl(sc, 0);
+}
+
+static void
+i2c_sendstop(struct cambria_gpio_softc *sc)
+{
+	i2c_setscl(sc, 1);
+	i2c_setsda(sc, 1);
+	i2c_setscl(sc, 0);
+	i2c_setsda(sc, 0);
+}
+
+static void
+i2c_sendbyte(struct cambria_gpio_softc *sc, u_char data)
+{
+	int i;
+
+	for (i=7; i>=0; i--) {
+		i2c_setsda(sc, data & (1<<i));
+		i2c_setscl(sc, 1);
+		i2c_setscl(sc, 0);
+	}
+	i2c_setscl(sc, 1);
+	i2c_getsda(sc);
+	i2c_setscl(sc, 0);
+}
+
+static u_char
+i2c_readbyte(struct cambria_gpio_softc *sc)
+{
+	int i;
+	unsigned char data=0;
+
+	for (i=7; i>=0; i--)
+	{
+		i2c_setscl(sc, 1);
+		if (i2c_getsda(sc))
+			data |= (1<<i);
+		i2c_setscl(sc, 0);
+	}
+	return data;
+}
+
+static int
+cambria_gpio_read(struct cambria_gpio_softc *sc, uint32_t pin, unsigned int *val)
+{
+	device_t dev = sc->sc_dev;
+	int error;
+
+	error = iicbus_request_bus(device_get_parent(dev), dev,
+	    IIC_DONTWAIT);
+	if (error)
+		return (error);
+
+	i2c_sendstart(sc);
+	i2c_sendbyte(sc, PLD_ADDR | LSB);
+	*val = (i2c_readbyte(sc) & (1 << pin)) != 0;
+	i2c_sendstop(sc);
+
+	iicbus_release_bus(device_get_parent(dev), dev);
+
+	return (0);
+}
+
+static int
+cambria_gpio_write(struct cambria_gpio_softc *sc)
+{
+	device_t dev = sc->sc_dev;
+	int error;
+
+	error = iicbus_request_bus(device_get_parent(dev), dev,
+	    IIC_DONTWAIT);
+	if (error)
+		return (error);
+
+	i2c_sendstart(sc);
+	i2c_sendbyte(sc, PLD_ADDR & ~LSB);
+	i2c_sendbyte(sc, sc->sc_latch);
+	i2c_sendstop(sc);
+
+	iicbus_release_bus(device_get_parent(dev), dev);
+
+	return (0);
+}
+
+static int
+cambria_gpio_pin_max(device_t dev, int *maxpin)
+{
+
+	*maxpin = GPIO_PINS - 1;
+	return (0);
+}
+
+static int
+cambria_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps)
+{
+	struct cambria_gpio_softc *sc = device_get_softc(dev);
+
+	if (pin >= GPIO_PINS)
+		return (EINVAL);
+
+	*caps = sc->sc_pins[pin].gp_caps;
+	return (0);
+}
+
+static int
+cambria_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags)
+{
+	struct cambria_gpio_softc *sc = device_get_softc(dev);
+
+	if (pin >= GPIO_PINS)
+		return (EINVAL);
+
+	*flags = sc->sc_pins[pin].gp_flags;
+	return (0);
+}
+
+static int
+cambria_gpio_pin_getname(device_t dev, uint32_t pin, char *name)
+{
+	struct cambria_gpio_softc *sc = device_get_softc(dev);
+
+	if (pin >= GPIO_PINS)
+		return (EINVAL);
+
+	memcpy(name, sc->sc_pins[pin].gp_name, GPIOMAXNAME);
+	return (0);
+}
+
+static int
+cambria_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags)
+{
+	struct cambria_gpio_softc *sc = device_get_softc(dev);
+	int error;
+
+	if (pin >= GPIO_PINS)
+		return (EINVAL);
+
+	/* Filter out unwanted flags */
+	if ((flags &= sc->sc_pins[pin].gp_caps) != flags)
+		return (EINVAL);
+
+	/* Can't mix input/output together */
+	if ((flags & (GPIO_PIN_INPUT|GPIO_PIN_OUTPUT)) ==
+	    (GPIO_PIN_INPUT|GPIO_PIN_OUTPUT))
+		return (EINVAL);
+
+	GPIO_LOCK(sc);
+	sc->sc_pins[pin].gp_flags = flags;
+
+	sc->sc_latch |= (1 << pin);
+	error = cambria_gpio_write(sc);
+	GPIO_UNLOCK(sc);
+
+	return (error);
+}
+
+static int
+cambria_gpio_pin_set(device_t dev, uint32_t pin, unsigned int value)
+{
+	struct cambria_gpio_softc *sc = device_get_softc(dev);
+	int error;
+
+	if (pin >= GPIO_PINS || sc->sc_pins[pin].gp_flags != GPIO_PIN_OUTPUT)
+		return (EINVAL);
+
+	GPIO_LOCK(sc);
+	if (value)
+		sc->sc_latch |= (1 << pin);
+	else
+		sc->sc_latch &= ~(1 << pin);
+	error = cambria_gpio_write(sc);
+	GPIO_UNLOCK(sc);
+
+	return (error);
+}
+
+static int
+cambria_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *val)
+{
+	struct cambria_gpio_softc *sc = device_get_softc(dev);
+	int error = 0;
+
+	if (pin >= GPIO_PINS)
+		return (EINVAL);
+
+	GPIO_LOCK(sc);
+	if (sc->sc_pins[pin].gp_flags == GPIO_PIN_OUTPUT)
+		*val = (sc->sc_latch & (1 << pin)) ? 1 : 0;
+	else
+		error = cambria_gpio_read(sc, pin, val);
+	GPIO_UNLOCK(sc);
+
+	return (error);
+}
+
+static int
+cambria_gpio_pin_toggle(device_t dev, uint32_t pin)
+{
+	struct cambria_gpio_softc *sc = device_get_softc(dev);
+	int error;
+
+	if (pin >= GPIO_PINS || sc->sc_pins[pin].gp_flags != GPIO_PIN_OUTPUT)
+		return (EINVAL);
+
+	GPIO_LOCK(sc);
+	sc->sc_latch ^= (1 << pin);
+	error = cambria_gpio_write(sc);
+	GPIO_UNLOCK(sc);
+
+	return (error);
+}
+
+static int
+cambria_gpio_probe(device_t dev)
+{
+
+	device_set_desc(dev, "Gateworks Cambria GPIO driver");
+	return (0);
+}
+
+static int
+cambria_gpio_attach(device_t dev)
+{
+	struct cambria_gpio_softc *sc = device_get_softc(dev);
+	int pin;
+
+	sc->sc_dev = dev;
+	sc->sc_iot = ixp425_softc->sc_iot;
+	sc->sc_gpio_ioh = ixp425_softc->sc_gpio_ioh;
+
+	mtx_init(&sc->sc_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK,
+	    MTX_DEF);
+
+	for (pin = 0; pin < GPIO_PINS; pin++) {
+		struct cambria_gpio_pin *p = &cambria_gpio_pins[pin];
+
+		strncpy(sc->sc_pins[pin].gp_name, p->name, GPIOMAXNAME);
+		sc->sc_pins[pin].gp_pin = pin;
+		sc->sc_pins[pin].gp_caps = GPIO_PIN_INPUT|GPIO_PIN_OUTPUT;
+		sc->sc_pins[pin].gp_flags = 0;
+		cambria_gpio_pin_setflags(dev, pin, p->flags);
+	}
+
+	device_add_child(dev, "gpioc", device_get_unit(dev));
+	device_add_child(dev, "gpiobus", device_get_unit(dev));
+	return (bus_generic_attach(dev));
+}
+
+static int
+cambria_gpio_detach(device_t dev)
+{
+	struct cambria_gpio_softc *sc = device_get_softc(dev);
+
+	KASSERT(mtx_initialized(&sc->sc_mtx), ("gpio mutex not initialized"));
+
+	bus_generic_detach(dev);
+
+	mtx_destroy(&sc->sc_mtx);
+
+	return(0);
+}
+
+static device_method_t cambria_gpio_methods[] = {
+	DEVMETHOD(device_probe, cambria_gpio_probe),
+	DEVMETHOD(device_attach, cambria_gpio_attach),
+	DEVMETHOD(device_detach, cambria_gpio_detach),
+
+	/* GPIO protocol */
+	DEVMETHOD(gpio_pin_max, cambria_gpio_pin_max),
+	DEVMETHOD(gpio_pin_getname, cambria_gpio_pin_getname),
+	DEVMETHOD(gpio_pin_getflags, cambria_gpio_pin_getflags),
+	DEVMETHOD(gpio_pin_getcaps, cambria_gpio_pin_getcaps),
+	DEVMETHOD(gpio_pin_setflags, cambria_gpio_pin_setflags),
+	DEVMETHOD(gpio_pin_get, cambria_gpio_pin_get),
+	DEVMETHOD(gpio_pin_set, cambria_gpio_pin_set),
+	DEVMETHOD(gpio_pin_toggle, cambria_gpio_pin_toggle),
+	{0, 0},
+};
+
+static driver_t cambria_gpio_driver = {
+	"gpio_cambria",
+	cambria_gpio_methods,
+	sizeof(struct cambria_gpio_softc),
+};
+static devclass_t cambria_gpio_devclass;
+extern devclass_t gpiobus_devclass, gpioc_devclass;
+extern driver_t gpiobus_driver, gpioc_driver;
+
+DRIVER_MODULE(gpio_cambria, iicbus, cambria_gpio_driver, cambria_gpio_devclass, 0, 0);
+DRIVER_MODULE(gpiobus, gpio_cambria, gpiobus_driver, gpiobus_devclass, 0, 0);
+DRIVER_MODULE(gpioc, gpio_cambria, gpioc_driver, gpioc_devclass, 0, 0);
+MODULE_VERSION(gpio_cambria, 1);
+MODULE_DEPEND(gpio_cambria, iicbus, 1, 1, 1);

Modified: head/sys/arm/xscale/ixp425/files.avila
==============================================================================
--- head/sys/arm/xscale/ixp425/files.avila	Thu Nov 11 19:39:38 2010	(r215141)
+++ head/sys/arm/xscale/ixp425/files.avila	Thu Nov 11 20:18:33 2010	(r215142)
@@ -6,4 +6,5 @@ arm/xscale/ixp425/avila_gpio.c		optional
 arm/xscale/ixp425/cambria_exp_space.c	standard
 arm/xscale/ixp425/cambria_fled.c	optional cambria_fled
 arm/xscale/ixp425/cambria_led.c		optional cambria_led
+arm/xscale/ixp425/cambria_gpio.c	optional cambria_gpio
 arm/xscale/ixp425/ixdp425_pci.c		optional pci

Modified: head/sys/dev/gpio/gpiobus.c
==============================================================================
--- head/sys/dev/gpio/gpiobus.c	Thu Nov 11 19:39:38 2010	(r215141)
+++ head/sys/dev/gpio/gpiobus.c	Thu Nov 11 20:18:33 2010	(r215142)
@@ -481,7 +481,7 @@ static device_method_t gpiobus_methods[]
 	{ 0, 0 }
 };
 
-static driver_t gpiobus_driver = {
+driver_t gpiobus_driver = {
 	"gpiobus",
 	gpiobus_methods,
 	sizeof(struct gpiobus_softc)

Modified: head/sys/dev/gpio/gpioc.c
==============================================================================
--- head/sys/dev/gpio/gpioc.c	Thu Nov 11 19:39:38 2010	(r215141)
+++ head/sys/dev/gpio/gpioc.c	Thu Nov 11 20:18:33 2010	(r215142)
@@ -188,7 +188,7 @@ static device_method_t gpioc_methods[] =
 	{ 0, 0 }
 };
 
-static driver_t gpioc_driver = {
+driver_t gpioc_driver = {
 	"gpioc",
 	gpioc_methods,
 	sizeof(struct gpioc_softc)


More information about the svn-src-head mailing list