Module Name: src Committed By: jmcneill Date: Wed Oct 30 21:38:28 UTC 2019
Modified Files: src/sys/dev/i2c: files.i2c Added Files: src/sys/dev/i2c: twl4030.c Log Message: Add driver for TI TWL4030 Power Management IC To generate a diff of this commit: cvs rdiff -u -r1.101 -r1.102 src/sys/dev/i2c/files.i2c cvs rdiff -u -r0 -r1.1 src/sys/dev/i2c/twl4030.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/dev/i2c/files.i2c diff -u src/sys/dev/i2c/files.i2c:1.101 src/sys/dev/i2c/files.i2c:1.102 --- src/sys/dev/i2c/files.i2c:1.101 Sun Oct 27 20:11:13 2019 +++ src/sys/dev/i2c/files.i2c Wed Oct 30 21:38:28 2019 @@ -1,4 +1,4 @@ -# $NetBSD: files.i2c,v 1.101 2019/10/27 20:11:13 jmcneill Exp $ +# $NetBSD: files.i2c,v 1.102 2019/10/30 21:38:28 jmcneill Exp $ obsolete defflag opt_i2cbus.h I2C_SCAN define i2cbus { } @@ -373,3 +373,8 @@ file dev/i2c/anxedp.c anxedp device pcapwm: pwm attach pcapwm at iic file dev/i2c/pca9685.c pcapwm + +# TI TWL4030 Power Management IC +device twl +attach twl at iic +file dev/i2c/twl4030.c twl Added files: Index: src/sys/dev/i2c/twl4030.c diff -u /dev/null src/sys/dev/i2c/twl4030.c:1.1 --- /dev/null Wed Oct 30 21:38:28 2019 +++ src/sys/dev/i2c/twl4030.c Wed Oct 30 21:38:28 2019 @@ -0,0 +1,281 @@ +/* $NetBSD: twl4030.c,v 1.1 2019/10/30 21:38:28 jmcneill Exp $ */ + +/*- + * Copyright (c) 2019 Jared McNeill <jmcne...@invisible.ca> + * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: twl4030.c,v 1.1 2019/10/30 21:38:28 jmcneill Exp $"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/device.h> +#include <sys/conf.h> +#include <sys/bus.h> +#include <sys/kmem.h> +#include <sys/gpio.h> + +#include <dev/i2c/i2cvar.h> + +#include <dev/fdt/fdtvar.h> + +#define TWL_PIN_COUNT 16 + +/* TWL4030 is a multi-function IC. Each module is at a separate I2C address */ +#define ADDR_USB 0x00 +#define ADDR_INT 0x01 +#define ADDR_AUX 0x02 +#define ADDR_POWER 0x03 + +/* GPIO registers */ +#define GPIOBASE 0x98 +#define GPIODATAIN(pin) (GPIOBASE + 0x00 + (pin) / 8) +#define GPIODATADIR(pin) (GPIOBASE + 0x03 + (pin) / 8) +#define CLEARGPIODATAOUT(pin) (GPIOBASE + 0x09 + (pin) / 8) +#define SETGPIODATAOUT(pin) (GPIOBASE + 0x0c + (pin) / 8) +#define PIN_BIT(pin) __BIT((pin) % 8) +#define GPIOPUPDCTR(pin) (GPIOBASE + 0x13 + (n) / 4) +#define PUPD_BITS(pin) __BITS((pin) % 4 + 1, (pin) % 4) + +struct twl_softc { + device_t sc_dev; + i2c_tag_t sc_i2c; + i2c_addr_t sc_addr; + int sc_phandle; + + int sc_npins; +}; + +struct twl_pin { + struct twl_softc *pin_sc; + int pin_num; + int pin_flags; + bool pin_actlo; +}; + +static const struct device_compatible_entry compat_data[] = { + { "ti,twl4030", 0 }, + { NULL, 0 } +}; + +static const char * const gpio_compatible[] = { "ti,twl4030-gpio", NULL }; + +static uint8_t +twl_read(struct twl_softc *sc, uint8_t mod, uint8_t reg, int flags) +{ + uint8_t val = 0; + int error; + + error = iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP, sc->sc_addr + mod, + ®, 1, &val, 1, flags); + if (error != 0) + aprint_error_dev(sc->sc_dev, "error reading reg %#x: %d\n", reg, error); + + return val; +} + +static void +twl_write(struct twl_softc *sc, uint8_t mod, uint8_t reg, uint8_t val, int flags) +{ + uint8_t buf[2]; + int error; + + buf[0] = reg; + buf[1] = val; + + error = iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP, sc->sc_addr + mod, + NULL, 0, buf, 2, flags); + if (error != 0) + aprint_error_dev(sc->sc_dev, "error writing reg %#x: %d\n", reg, error); +} + +#define I2C_LOCK(sc) iic_acquire_bus((sc)->sc_i2c, I2C_F_POLL) +#define I2C_UNLOCK(sc) iic_release_bus((sc)->sc_i2c, I2C_F_POLL) + +#define INT_READ(sc, reg) twl_read((sc), ADDR_INT, (reg), I2C_F_POLL) +#define INT_WRITE(sc, reg, val) twl_write((sc), ADDR_INT, (reg), (val), I2C_F_POLL) + +static int +twl_gpio_config(struct twl_softc *sc, int pin, int flags) +{ + uint8_t dir; + + KASSERT(pin >= 0 && pin < sc->sc_npins); + + dir = INT_READ(sc, GPIODATADIR(pin)); + + switch (flags & (GPIO_PIN_INPUT|GPIO_PIN_OUTPUT)) { + case GPIO_PIN_INPUT: + dir &= ~PIN_BIT(pin); + break; + case GPIO_PIN_OUTPUT: + dir |= PIN_BIT(pin); + break; + default: + return EINVAL; + } + + INT_WRITE(sc, GPIODATADIR(pin), dir); + + return 0; +} + +static void * +twl_gpio_acquire(device_t dev, const void *data, size_t len, int flags) +{ + struct twl_softc * const sc = device_private(dev); + struct twl_pin *gpin; + const u_int *gpio = data; + int error; + + if (len != 12) + return NULL; + + const uint8_t pin = be32toh(gpio[1]) & 0xff; + const bool actlo = (be32toh(gpio[2]) & __BIT(0)) != 0; + + if (pin >= sc->sc_npins) + return NULL; + + I2C_LOCK(sc); + error = twl_gpio_config(sc, pin, flags); + I2C_UNLOCK(sc); + + if (error != 0) { + device_printf(dev, "bad pin %d config %#x\n", pin, flags); + return NULL; + } + + gpin = kmem_zalloc(sizeof(*gpin), KM_SLEEP); + gpin->pin_sc = sc; + gpin->pin_num = pin; + gpin->pin_flags = flags; + gpin->pin_actlo = actlo; + + return gpin; +} + +static void +twl_gpio_release(device_t dev, void *priv) +{ + struct twl_softc * const sc = device_private(dev); + struct twl_pin *gpin = priv; + + I2C_LOCK(sc); + twl_gpio_config(sc, gpin->pin_num, GPIO_PIN_INPUT); + I2C_UNLOCK(sc); + + kmem_free(gpin, sizeof(*gpin)); +} + +static int +twl_gpio_read(device_t dev, void *priv, bool raw) +{ + struct twl_softc * const sc = device_private(dev); + struct twl_pin *gpin = priv; + uint8_t gpio; + int val; + + I2C_LOCK(sc); + gpio = INT_READ(sc, GPIODATAIN(gpin->pin_num)); + I2C_UNLOCK(sc); + + val = __SHIFTOUT(gpio, PIN_BIT(gpin->pin_num)); + if (!raw && gpin->pin_actlo) + val = !val; + + return val; +} + +static void +twl_gpio_write(device_t dev, void *priv, int val, bool raw) +{ + struct twl_softc * const sc = device_private(dev); + struct twl_pin *gpin = priv; + + if (!raw && gpin->pin_actlo) + val = !val; + + I2C_LOCK(sc); + if (val) + INT_WRITE(sc, SETGPIODATAOUT(gpin->pin_num), PIN_BIT(gpin->pin_num)); + else + INT_WRITE(sc, CLEARGPIODATAOUT(gpin->pin_num), PIN_BIT(gpin->pin_num)); + I2C_UNLOCK(sc); +} + +static struct fdtbus_gpio_controller_func twl_gpio_funcs = { + .acquire = twl_gpio_acquire, + .release = twl_gpio_release, + .read = twl_gpio_read, + .write = twl_gpio_write, +}; + +static void +twl_gpio_attach(struct twl_softc *sc, const int phandle) +{ + fdtbus_register_gpio_controller(sc->sc_dev, phandle, &twl_gpio_funcs); +} + +static int +twl_match(device_t parent, cfdata_t match, void *aux) +{ + struct i2c_attach_args *ia = aux; + int match_result; + + if (iic_use_direct_match(ia, match, compat_data, &match_result)) + return match_result; + + return 0; +} + +static void +twl_attach(device_t parent, device_t self, void *aux) +{ + struct twl_softc * const sc = device_private(self); + struct i2c_attach_args *ia = aux; + int child; + + sc->sc_dev = self; + sc->sc_i2c = ia->ia_tag; + sc->sc_addr = ia->ia_addr; + sc->sc_phandle = ia->ia_cookie; + sc->sc_npins = TWL_PIN_COUNT; + + aprint_naive("\n"); + aprint_normal(": TWL4030"); + + for (child = OF_child(sc->sc_phandle); child; child = OF_peer(child)) { + if (of_match_compatible(child, gpio_compatible)) { + aprint_normal(", GPIO"); + twl_gpio_attach(sc, child); + } + } + aprint_normal("\n"); +} + +CFATTACH_DECL_NEW(twl, sizeof(struct twl_softc), + twl_match, twl_attach, NULL, NULL);