/*- * Copyright (c) 2011 Henrik Brix Andersen * 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, 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 ``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 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. */ #include __FBSDID("$FreeBSD$"); /* * Texas Instruments PCF8574/PCF8575 I2C I/O expander. */ #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" #include "iicbus_if.h" #define PCF857X_PINS_MAX 16 static char* pcf857x_pin_names[PCF857X_PINS_MAX] = { "P00", "P01", "P02", "P03", "P04", "P05", "P06", "P07", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", }; struct pcf857x_pin { uint32_t flags; /* Input or output. */ boolean_t state; /* High or low. */ }; typedef struct pcf857x_pin pcf857x_pin_t; struct pcf857x_softc { device_t dev; /* Myself. */ struct sx lock; /* Lock. */ device_t gpioc; /* GPIO controller. */ device_t gpiobus; /* GPIO bus. */ uint8_t addr; /* Address. */ int size; /* Number of GPIOs. */ pcf857x_pin_t pins[PCF857X_PINS_MAX]; }; static int pcf857x_probe(device_t dev); static int pcf857x_attach(device_t dev); static int pcf857x_detach(device_t dev); static int pcf857x_write_locked(struct pcf857x_softc *sc); static int pcf857x_read_locked(struct pcf857x_softc *sc); static int pcf857x_pin_max(device_t dev, int *npins); static int pcf857x_pin_getname(device_t dev, uint32_t pin_num, char *name); static int pcf857x_pin_getcaps(device_t dev, uint32_t pin_num, uint32_t *caps); static int pcf857x_pin_setflags(device_t dev, uint32_t pin_num, uint32_t flags); static int pcf857x_pin_getflags(device_t dev, uint32_t pin_num, uint32_t *flags); static int pcf857x_pin_set(device_t dev, uint32_t pin_num, uint32_t pin_value); static int pcf857x_pin_get(device_t dev, uint32_t pin_num, uint32_t *pin_value); static int pcf857x_pin_toggle(device_t dev, uint32_t pin_num); static device_method_t pcf857x_methods[] = { DEVMETHOD(device_probe, pcf857x_probe), DEVMETHOD(device_attach, pcf857x_attach), DEVMETHOD(device_detach, pcf857x_detach), DEVMETHOD(gpio_pin_max, pcf857x_pin_max), DEVMETHOD(gpio_pin_getname, pcf857x_pin_getname), DEVMETHOD(gpio_pin_getcaps, pcf857x_pin_getcaps), DEVMETHOD(gpio_pin_setflags, pcf857x_pin_setflags), DEVMETHOD(gpio_pin_getflags, pcf857x_pin_getflags), DEVMETHOD(gpio_pin_set, pcf857x_pin_set), DEVMETHOD(gpio_pin_get, pcf857x_pin_get), DEVMETHOD(gpio_pin_toggle, pcf857x_pin_toggle), { 0, 0 } }; static driver_t pcf857x_driver = { "pcf857x", pcf857x_methods, sizeof(struct pcf857x_softc), }; static devclass_t pcf857x_devclass; extern driver_t gpioc_driver, gpiobus_driver; extern devclass_t gpioc_devclass, gpiobus_devclass; DRIVER_MODULE(pcf857x, iicbus, pcf857x_driver, pcf857x_devclass, 0, 0); DRIVER_MODULE(gpiobus, pcf857x, gpiobus_driver, gpiobus_devclass, 0, 0); DRIVER_MODULE(gpioc, pcf857x, gpioc_driver, gpioc_devclass, 0, 0); MODULE_DEPEND(pcf857x, iicbus, 1, 1, 1); static int pcf857x_probe(device_t dev) { int unit, size; unit = device_get_unit(dev); if (resource_disabled("pcf857x", unit)) return (ENXIO); if (resource_int_value("pcf857x", unit, "size", &size) == 0) { switch (size) { case 8: device_set_desc(dev, "PCF8574 Remote 8-bit I/O Expander"); break; case 16: device_set_desc(dev, "PCF8575 Remote 16-bit I/O Expander"); break; default: device_printf(dev, "Bad value %d for pcf857x.%d.size\n", size, unit); return (ENXIO); } } else { device_printf(dev, "Missing pcf857x.%d.size hint\n", unit); return (ENXIO); } return (BUS_PROBE_NOWILDCARD); } static int pcf857x_attach(device_t dev) { struct pcf857x_softc *sc; int error, i; sc = device_get_softc(dev); sc->dev = dev; sc->addr = iicbus_get_addr(dev); error = 0; sx_init(&sc->lock, "pcf857x"); /* Size was already verified by pcf857x_probe(). */ resource_int_value("pcf857x", device_get_unit(dev), "size", &sc->size); for (i = 0; i < sc->size; i++) { /* This is the power-on configuration of the chip. */ sc->pins[i].flags = GPIO_PIN_INPUT; sc->pins[i].state = TRUE; } sc->gpioc = device_add_child(dev, "gpioc", device_get_unit(dev)); if (sc->gpioc == NULL) { device_printf(dev, "Could not allocate gpioc instance\n"); error = ENXIO; goto out; } sc->gpiobus = device_add_child(dev, "gpiobus", device_get_unit(dev)); if (sc->gpiobus == NULL) { device_printf(dev, "Could not allocate gpiobus instance\n"); error = ENXIO; goto out; } error = bus_generic_attach(dev); if (error != 0) device_printf(dev, "Could not probe and attach children\n"); out: if (error != 0) { if (sc->gpiobus != NULL) device_delete_child(dev, sc->gpiobus); if (sc->gpioc != NULL) device_delete_child(dev, sc->gpioc); sx_destroy(&sc->lock); } return (error); } static int pcf857x_detach(device_t dev) { struct pcf857x_softc *sc; int error; sc = device_get_softc(dev); error = bus_generic_detach(dev); if (error != 0) goto out; if (sc->gpiobus != NULL) error = device_delete_child(dev, sc->gpiobus); if (error != 0) goto out; if (sc->gpioc != NULL) error = device_delete_child(dev, sc->gpioc); out: if (error != 0) sx_destroy(&sc->lock); return (error); } static int pcf857x_write_locked(struct pcf857x_softc *sc) { struct iic_msg msg; uint16_t data; uint8_t buf[2]; int i, error; sx_assert(&sc->lock, SA_XLOCKED); msg.slave = sc->addr; msg.flags = IIC_M_WR; msg.buf = buf; msg.len = sc->size / 8; data = 0; for (i = 0; i < sc->size; i++) { if ((sc->pins[i].flags & GPIO_PIN_INPUT) != 0 || sc->pins[i].state == TRUE) { data |= (1 << i); } } buf[0] = data & 0xff; buf[1] = (data >> 8) & 0xff; error = iicbus_transfer(sc->dev, &msg, 1); if (error == IIC_EBUSBSY) return (EAGAIN); if (error == IIC_ETIMEOUT) return (EBUSY); if (error != 0) return (EIO); return (0); } static int pcf857x_read_locked(struct pcf857x_softc *sc) { struct iic_msg msg; uint16_t data; uint8_t buf[2]; int i, error; sx_assert(&sc->lock, SA_XLOCKED); msg.slave = sc->addr; msg.flags = IIC_M_RD; msg.buf = buf; msg.len = sc->size / 8; error = iicbus_transfer(sc->dev, &msg, 1); if (error == IIC_EBUSBSY) return (EAGAIN); if (error == IIC_ETIMEOUT) return (EBUSY); if (error != 0) return (EIO); data = buf[0] | (buf[1] << 8); for (i = 0; i < sc->size; i++) { if ((sc->pins[i].flags & GPIO_PIN_INPUT) != 0) sc->pins[i].state = data & (1 << i); } return (0); } static int pcf857x_pin_max(device_t dev, int *maxpin) { struct pcf857x_softc *sc; sc = device_get_softc(dev); sx_slock(&sc->lock); *maxpin = sc->size - 1; sx_sunlock(&sc->lock); return (0); } static int pcf857x_pin_getname(device_t dev, uint32_t pin_num, char *name) { struct pcf857x_softc *sc; int error; sc = device_get_softc(dev); error = 0; sx_slock(&sc->lock); if (pin_num >= sc->size) { error = EINVAL; goto out; } memcpy(name, pcf857x_pin_names[pin_num], GPIOMAXNAME); out: sx_sunlock(&sc->lock); return (error); } static int pcf857x_pin_getcaps(device_t dev, uint32_t pin_num, uint32_t *caps) { struct pcf857x_softc *sc; int error; sc = device_get_softc(dev); error = 0; sx_slock(&sc->lock); if (pin_num >= sc->size) { error = EINVAL; goto out; } /* XXX implement II and IO in software? */ *caps = (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT); out: sx_sunlock(&sc->lock); return (error); } static int pcf857x_pin_setflags(device_t dev, uint32_t pin_num, uint32_t flags) { struct pcf857x_softc *sc; uint32_t oldflags; int error; sc = device_get_softc(dev); sx_xlock(&sc->lock); if (pin_num >= sc->size) { error = EINVAL; goto out; } if ((flags & ~(GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) != 0) { error = EINVAL; goto out; } if ((flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) == (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) { error = EINVAL; goto out; } oldflags = sc->pins[pin_num].flags; sc->pins[pin_num].flags = flags; error = pcf857x_write_locked(sc); if (error != 0) sc->pins[pin_num].flags = oldflags; out: sx_xunlock(&sc->lock); return (error); } static int pcf857x_pin_getflags(device_t dev, uint32_t pin_num, uint32_t *flags) { struct pcf857x_softc *sc; int error; sc = device_get_softc(dev); error = 0; sx_slock(&sc->lock); if (pin_num >= sc->size) { error = EINVAL; goto out; } *flags = sc->pins[pin_num].flags; out: sx_sunlock(&sc->lock); return (error); } static int pcf857x_pin_set(device_t dev, uint32_t pin_num, uint32_t pin_value) { struct pcf857x_softc *sc; boolean_t oldstate; int error; sc = device_get_softc(dev); sx_xlock(&sc->lock); if (pin_num >= sc->size) { error = EINVAL; goto out; } if (sc->pins[pin_num].flags != GPIO_PIN_OUTPUT) { error = EINVAL; goto out; } oldstate = sc->pins[pin_num].state; sc->pins[pin_num].state = (pin_value == GPIO_PIN_HIGH ? TRUE : FALSE); error = pcf857x_write_locked(sc); if (error != 0) sc->pins[pin_num].state = oldstate; out: sx_xunlock(&sc->lock); return (error); } static int pcf857x_pin_get(device_t dev, uint32_t pin_num, uint32_t *pin_value) { struct pcf857x_softc *sc; int error; sc = device_get_softc(dev); error = 0; sx_xlock(&sc->lock); if (pin_num >= sc->size) { error = EINVAL; goto out; } if ((sc->pins[pin_num].flags & GPIO_PIN_INPUT) != 0) { error = pcf857x_read_locked(sc); if (error != 0) goto out; } *pin_value = (sc->pins[pin_num].state ? GPIO_PIN_HIGH : GPIO_PIN_LOW); out: sx_xunlock(&sc->lock); return (error); } static int pcf857x_pin_toggle(device_t dev, uint32_t pin_num) { struct pcf857x_softc *sc; boolean_t oldstate; int error; sc = device_get_softc(dev); sx_xlock(&sc->lock); if (pin_num >= sc->size) { error = EINVAL; goto out; } if (sc->pins[pin_num].flags != GPIO_PIN_OUTPUT) { error = EINVAL; goto out; } oldstate = sc->pins[pin_num].state; sc->pins[pin_num].state = !sc->pins[pin_num].state; error = pcf857x_write_locked(sc); if (error != 0) sc->pins[pin_num].state = oldstate; out: sx_xunlock(&sc->lock); return (error); }