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 <thom...@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)
_______________________________________________
svn-src-all@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "svn-src-all-unsubscr...@freebsd.org"

Reply via email to