/*- * Copyright (c) 2013 Oleksandr Tymoshenko * 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 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. */ #include __FBSDID("$FreeBSD: head/sys/i386/i386/pc87366.c 208111 2010-05-15 10:31:11Z phk $"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" #define SUPERIO_INDEX 0x2e #define SUPERIO_DATA 0x2f #define SUPERIO_ID_ADD 0x20 #define SUPERIO_ACTIVATE 0x30 #define SUPERIO_ID 0xe9 #define SUPERIO_DEVICE_REG 0x07 #define SUPERIO_GPIO_DEV 0x07 #define SUPERIO_IOPORT0HI 0x60 #define SUPERIO_IOPORT0LO 0x61 #define SUPERIO_GPIOPINSEL 0xf0 #define SUPERIO_GPIOPINCONF 0xf1 #define GPIOPINCONF_OUTPUT (1 << 0) #define GPIOPINCONF_PUSHPULL (1 << 1) #define GPIOPINCONF_PULLUP (1 << 2) #define _GPIO_PINS 29 #define GPIO_REG_DO0 0x00 #define GPIO_REG_DI0 0x01 #define GPIO_REG_DO1 0x04 #define GPIO_REG_DI1 0x05 #define GPIO_REG_DO2 0x08 #define GPIO_REG_DI2 0x09 #define GPIO_REG_DO3 0x0A #define GPIO_REG_DI3 0x0B #define GPIO_DEFAULT_CAPS (GPIO_PIN_INPUT | \ GPIO_PIN_OUTPUT | GPIO_PIN_OPENDRAIN | \ GPIO_PIN_PUSHPULL | GPIO_PIN_PULLUP) #define GPIO_LOCK(sc) do { mtx_lock(&(sc)->gpio_mtx); } while(0) #define GPIO_UNLOCK(sc) do { mtx_unlock(&(sc)->gpio_mtx); } while(0) #if 0 static int super_io_pins[_GPIO_PINS] = {0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x04, 0x05, 0x13, 0x12}; #endif #define GPIO_PORTNUM(pin) ((pin)/8) #define GPIO_PORTPIN(pin) ((pin) & 7) #define GPIO_PINSELECT(port, pin) ((port << 4) | (pin)) struct pc87366_gpio_softc { device_t dev; struct mtx gpio_mtx; struct resource *gpio_io_res; int gpio_reg_offset; }; static int pc87366_gpio_pin_max(device_t dev, int *maxpin) { *maxpin = _GPIO_PINS - 1; return (0); } static int pc87366_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { if (pin >= _GPIO_PINS) return (EINVAL); *caps = GPIO_DEFAULT_CAPS; return (0); } static int pc87366_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) { struct pc87366_gpio_softc *sc = device_get_softc(dev); uint8_t pinselect, pinconf; int portnum, portpin; if (pin >= _GPIO_PINS) return (EINVAL); GPIO_LOCK(sc); portnum = GPIO_PORTNUM(pin); portpin = GPIO_PORTPIN(pin); pinselect = GPIO_PINSELECT(portnum, portpin); outb(SUPERIO_INDEX, SUPERIO_GPIOPINSEL); outb(SUPERIO_DATA, pinselect); outb(SUPERIO_INDEX, SUPERIO_GPIOPINCONF); pinconf = inb(SUPERIO_DATA); if (pinconf & GPIOPINCONF_OUTPUT) *flags = GPIO_PIN_OUTPUT; else *flags = GPIO_PIN_INPUT; if (pinconf & GPIOPINCONF_PUSHPULL) *flags |= GPIO_PIN_PUSHPULL; else *flags |= GPIO_PIN_OPENDRAIN; if (pinconf & GPIOPINCONF_PULLUP) *flags |= GPIO_PIN_PULLUP; GPIO_UNLOCK(sc); return (0); } static int pc87366_gpio_pin_getname(device_t dev, uint32_t pin, char *name) { if (pin >= _GPIO_PINS) return (EINVAL); snprintf(name, GPIOMAXNAME - 1, "gpio%02d", pin); return (0); } static int pc87366_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct pc87366_gpio_softc *sc = device_get_softc(dev); uint8_t pinconf; uint8_t pinselect; int portnum, portpin; if (pin >= _GPIO_PINS) return (EINVAL); /* Filter out unwanted flags */ if ((flags & GPIO_DEFAULT_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); /* Can't mix open-drain/push-pull */ if ((flags & (GPIO_PIN_OPENDRAIN|GPIO_PIN_PUSHPULL)) == (GPIO_PIN_OPENDRAIN|GPIO_PIN_PUSHPULL)) return (EINVAL); GPIO_LOCK(sc); pinconf = 0; portnum = GPIO_PORTNUM(pin); portpin = GPIO_PORTPIN(pin); pinselect = GPIO_PINSELECT(portnum, portpin); if (flags & GPIO_PIN_OUTPUT) pinconf |= GPIOPINCONF_OUTPUT; if (flags & GPIO_PIN_PUSHPULL) pinconf |= GPIOPINCONF_PUSHPULL; if (flags & GPIO_PIN_PULLUP) pinconf |= GPIOPINCONF_PULLUP; outb(SUPERIO_INDEX, SUPERIO_GPIOPINSEL); outb(SUPERIO_DATA, pinselect); outb(SUPERIO_INDEX, SUPERIO_GPIOPINCONF); outb(SUPERIO_DATA, pinconf); GPIO_UNLOCK(sc); return (0); } static int pc87366_gpio_pin_set(device_t dev, uint32_t pin, unsigned int val) { struct pc87366_gpio_softc *sc = device_get_softc(dev); int portnum, portpin; int offset; uint8_t reg; if (pin >= _GPIO_PINS) return (EINVAL); portnum = GPIO_PORTNUM(pin); portpin = GPIO_PORTPIN(pin); /* Get port offsetess */ switch (portnum) { case 0: offset = GPIO_REG_DO0; break; case 1: offset = GPIO_REG_DO1; break; case 2: offset = GPIO_REG_DO2; break; case 3: offset = GPIO_REG_DO3; break; } GPIO_LOCK(sc); reg = inb(sc->gpio_reg_offset + offset); if (val == GPIO_PIN_HIGH) reg |= (1 << portpin); else reg &= ~(1 << portpin); outb(sc->gpio_reg_offset + offset, reg); GPIO_UNLOCK(sc); return (0); } static int pc87366_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *val) { struct pc87366_gpio_softc *sc = device_get_softc(dev); int portnum, portpin; int offset; uint8_t reg; if (pin >= _GPIO_PINS) return (EINVAL); portnum = GPIO_PORTNUM(pin); portpin = GPIO_PORTPIN(pin); /* Get port offsetess */ switch (portnum) { case 0: offset = GPIO_REG_DI0; break; case 1: offset = GPIO_REG_DI1; break; case 2: offset = GPIO_REG_DI2; break; case 3: offset = GPIO_REG_DI3; break; } GPIO_LOCK(sc); reg = inb(sc->gpio_reg_offset + offset); if (reg & (1 << portpin)) *val = GPIO_PIN_HIGH; else *val = GPIO_PIN_LOW; GPIO_UNLOCK(sc); return (0); } static int pc87366_gpio_pin_toggle(device_t dev, uint32_t pin) { struct pc87366_gpio_softc *sc = device_get_softc(dev); int portnum, portpin; int offset; uint8_t reg; if (pin >= _GPIO_PINS) return (EINVAL); portnum = GPIO_PORTNUM(pin); portpin = GPIO_PORTPIN(pin); /* Get port offsetess */ switch (portnum) { case 0: offset = GPIO_REG_DO0; break; case 1: offset = GPIO_REG_DO1; break; case 2: offset = GPIO_REG_DO2; break; case 3: offset = GPIO_REG_DO3; break; } GPIO_LOCK(sc); reg = inb(sc->gpio_reg_offset + offset); if (reg & (1 << portpin)) reg &= ~(1 << portpin); else reg |= (1 << portpin); outb(sc->gpio_reg_offset + offset, reg); GPIO_UNLOCK(sc); return (0); } static int pc87366_gpio_probe(device_t dev) { printf("Trying: %08x\n", pci_get_devid(dev)); if (pci_get_devid(dev) == 0x0510100b) { device_set_desc(dev, "PCM87366 GPIO"); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int pc87366_gpio_attach(device_t dev) { struct pc87366_gpio_softc *sc = device_get_softc(dev); int rid; uint8_t reg; sc->dev = dev; mtx_init(&sc->gpio_mtx, "PCM87366 GPIO", "gpio", MTX_DEF); rid = 0; sc->gpio_io_res = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE); if (!sc->gpio_io_res) { device_printf(dev, "cannot allocate I/O resource\n"); goto fail; } device_printf(dev, "IO resource: %lu (%lu)\n", rman_get_start(sc->gpio_io_res), rman_get_size(sc->gpio_io_res)); outb(SUPERIO_INDEX, SUPERIO_ID_ADD); reg = inb(SUPERIO_DATA); if (reg != SUPERIO_ID) { printf("Invalid SuperIO ID: %02x\n", reg); goto fail; } outb(SUPERIO_INDEX, SUPERIO_DEVICE_REG); /* Point to GPIO Device */ outb(SUPERIO_DATA, SUPERIO_GPIO_DEV); /* get GPIO address */ outb(SUPERIO_INDEX, SUPERIO_IOPORT0HI); sc->gpio_reg_offset = inb(SUPERIO_DATA) << 8; outb(SUPERIO_INDEX, SUPERIO_IOPORT0LO); sc->gpio_reg_offset |= inb(SUPERIO_DATA); device_printf(dev, "Geode SuperIO GPIO at %x\n",sc->gpio_reg_offset); outb(SUPERIO_INDEX, SUPERIO_ACTIVATE); reg = inb(SUPERIO_DATA); if(!(reg & 0x01)) { device_printf(dev, "GPIO is not enabled\n"); goto fail; } device_add_child(dev, "gpioc", device_get_unit(dev)); device_add_child(dev, "gpiobus", device_get_unit(dev)); return (bus_generic_attach(dev)); fail: if (sc->gpio_io_res != NULL) bus_release_resource(dev, SYS_RES_IOPORT, 0, sc->gpio_io_res); mtx_destroy(&sc->gpio_mtx); return (ENXIO); } static int pc87366_gpio_detach(device_t dev) { struct pc87366_gpio_softc *sc = device_get_softc(dev); bus_generic_detach(dev); /* Release the IO resource */ if (sc->gpio_io_res != NULL) bus_release_resource(dev, SYS_RES_IOPORT, 0, sc->gpio_io_res); mtx_destroy(&sc->gpio_mtx); return (0); } static device_method_t pc87366_gpio_methods[] = { /* Device interface */ DEVMETHOD(device_probe, pc87366_gpio_probe), DEVMETHOD(device_attach, pc87366_gpio_attach), DEVMETHOD(device_detach, pc87366_gpio_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), /* GPIO protocol */ DEVMETHOD(gpio_pin_max, pc87366_gpio_pin_max), DEVMETHOD(gpio_pin_getname, pc87366_gpio_pin_getname), DEVMETHOD(gpio_pin_getflags, pc87366_gpio_pin_getflags), DEVMETHOD(gpio_pin_getcaps, pc87366_gpio_pin_getcaps), DEVMETHOD(gpio_pin_setflags, pc87366_gpio_pin_setflags), DEVMETHOD(gpio_pin_get, pc87366_gpio_pin_get), DEVMETHOD(gpio_pin_set, pc87366_gpio_pin_set), DEVMETHOD(gpio_pin_toggle, pc87366_gpio_pin_toggle), {0, 0} }; static driver_t pc87366_gpio_driver = { "gpio", pc87366_gpio_methods, sizeof(struct pc87366_gpio_softc), }; static devclass_t pc87366_gpio_devclass; DRIVER_MODULE(gpio, pci, pc87366_gpio_driver, pc87366_gpio_devclass, 0, 0);