summaryrefslogtreecommitdiffstats
path: root/sys/arm/xscale
diff options
context:
space:
mode:
authorthompsa <thompsa@FreeBSD.org>2010-11-11 20:18:33 +0000
committerthompsa <thompsa@FreeBSD.org>2010-11-11 20:18:33 +0000
commitc08375b9a103f50bb13e44df0adf57a6a504e01a (patch)
treebebdcfda44da8f10d45016143d5d26b87f3aa465 /sys/arm/xscale
parent56b80da7cac820e4575a4eca93a1c13c386ccec5 (diff)
downloadFreeBSD-src-c08375b9a103f50bb13e44df0adf57a6a504e01a.zip
FreeBSD-src-c08375b9a103f50bb13e44df0adf57a6a504e01a.tar.gz
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.
Diffstat (limited to 'sys/arm/xscale')
-rw-r--r--sys/arm/xscale/ixp425/cambria_gpio.c471
-rw-r--r--sys/arm/xscale/ixp425/files.avila1
2 files changed, 472 insertions, 0 deletions
diff --git a/sys/arm/xscale/ixp425/cambria_gpio.c b/sys/arm/xscale/ixp425/cambria_gpio.c
new file mode 100644
index 0000000..89b07d8
--- /dev/null
+++ b/sys/arm/xscale/ixp425/cambria_gpio.c
@@ -0,0 +1,471 @@
+/*-
+ * Copyright (c) 2010, Andrew Thompson <thompsa@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);
diff --git a/sys/arm/xscale/ixp425/files.avila b/sys/arm/xscale/ixp425/files.avila
index 38b9329..5008e09 100644
--- a/sys/arm/xscale/ixp425/files.avila
+++ b/sys/arm/xscale/ixp425/files.avila
@@ -6,4 +6,5 @@ arm/xscale/ixp425/avila_gpio.c optional avila_gpio
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
OpenPOWER on IntegriCloud