/*- * Copyright (c) 2016 Michal Meloun * 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$"); #include #include #include #include #include #include #include #include #include #include #include "as3722.h" MALLOC_DEFINE(M_AS3722_GPIO, "AS3722 gpio", "AS3722 GPIO"); /* AS3722_GPIOx_CONTROL MODE and IOSF definition. */ #define AS3722_IOSF_GPIO 0x00 #define AS3722_IOSF_INTERRUPT_OUT 0x01 #define AS3722_IOSF_VSUP_VBAT_LOW_UNDEBOUNCE_OUT 0x02 #define AS3722_IOSF_GPIO_IN_INTERRUPT 0x03 #define AS3722_IOSF_PWM_IN 0x04 #define AS3722_IOSF_VOLTAGE_IN_STANDBY 0x05 #define AS3722_IOSF_OC_PG_SD0 0x06 #define AS3722_IOSF_POWERGOOD_OUT 0x07 #define AS3722_IOSF_CLK32K_OUT 0x08 #define AS3722_IOSF_WATCHDOG_IN 0x09 #define AS3722_IOSF_SOFT_RESET_IN 0x0b #define AS3722_IOSF_PWM_OUT 0x0c #define AS3722_IOSF_VSUP_VBAT_LOW_DEBOUNCE_OUT 0x0d #define AS3722_IOSF_OC_PG_SD6 0x0e #define AS3722_MODE_INPUT 0 #define AS3722_MODE_PUSH_PULL 1 #define AS3722_MODE_OPEN_DRAIN 2 #define AS3722_MODE_TRISTATE 3 #define AS3722_MODE_INPUT_PULL_UP_LV 4 #define AS3722_MODE_INPUT_PULL_DOWN 5 #define AS3722_MODE_OPEN_DRAIN_LV 6 #define AS3722_MODE_PUSH_PULL_LV 7 #define NGPIO 8 #define GPIO_LOCK(_sc) sx_slock(&(_sc)->gpio_lock) #define GPIO_UNLOCK(_sc) sx_unlock(&(_sc)->gpio_lock) #define GPIO_ASSERT(_sc) sx_assert(&(_sc)->gpio_lock, SA_LOCKED) #define AS3722_CFG_BIAS_DISABLE 0x0001 #define AS3722_CFG_BIAS_PULL_UP 0x0002 #define AS3722_CFG_BIAS_PULL_DOWN 0x0004 #define AS3722_CFG_BIAS_HIGH_IMPEDANCE 0x0008 #define AS3722_CFG_OPEN_DRAIN 0x0010 static const struct { const char *name; int config; /* AS3722_CFG_ */ } as3722_cfg_names[] = { {"bias-disable", AS3722_CFG_BIAS_DISABLE}, {"bias-pull-up", AS3722_CFG_BIAS_PULL_UP}, {"bias-pull-down", AS3722_CFG_BIAS_PULL_DOWN}, {"bias-high-impedance", AS3722_CFG_BIAS_HIGH_IMPEDANCE}, {"drive-open-drain", AS3722_CFG_OPEN_DRAIN}, }; static struct { const char *name; int fnc_val; } as3722_fnc_table[] = { {"gpio", AS3722_IOSF_GPIO}, {"interrupt-out", AS3722_IOSF_INTERRUPT_OUT}, {"vsup-vbat-low-undebounce-out", AS3722_IOSF_VSUP_VBAT_LOW_UNDEBOUNCE_OUT}, {"gpio-in-interrupt", AS3722_IOSF_GPIO_IN_INTERRUPT}, {"pwm-in", AS3722_IOSF_PWM_IN}, {"voltage-in-standby", AS3722_IOSF_VOLTAGE_IN_STANDBY}, {"oc-pg-sd0", AS3722_IOSF_OC_PG_SD0}, {"powergood-out", AS3722_IOSF_POWERGOOD_OUT}, {"clk32k-out", AS3722_IOSF_CLK32K_OUT}, {"watchdog-in", AS3722_IOSF_WATCHDOG_IN}, {"soft-reset-in", AS3722_IOSF_SOFT_RESET_IN}, {"pwm-out", AS3722_IOSF_PWM_OUT}, {"vsup-vbat-low-debounce-out", AS3722_IOSF_VSUP_VBAT_LOW_DEBOUNCE_OUT}, {"oc-pg-sd6", AS3722_IOSF_OC_PG_SD6}, }; struct as3722_pincfg { char *function; int flags; }; struct as3722_gpio_pin { int pin_caps; uint8_t pin_ctrl_reg; char pin_name[GPIOMAXNAME]; int pin_cfg_flags; }; /* -------------------------------------------------------------------------- * * Pinmux functions. */ static int as3722_pinmux_get_function(struct as3722_softc *sc, char *name) { int i; for (i = 0; i < nitems(as3722_fnc_table); i++) { if (strcmp(as3722_fnc_table[i].name, name) == 0) return (as3722_fnc_table[i].fnc_val); } return (-1); } static int as3722_pinmux_config_node(struct as3722_softc *sc, char *pin_name, struct as3722_pincfg *cfg) { uint8_t ctrl; int rv, fnc, pin; for (pin = 0; pin < sc->gpio_npins; pin++) { if (strcmp(sc->gpio_pins[pin]->pin_name, pin_name) == 0) break; } if (pin >= sc->gpio_npins) { device_printf(sc->dev, "Unknown pin: %s\n", pin_name); return (ENXIO); } ctrl = sc->gpio_pins[pin]->pin_ctrl_reg; sc->gpio_pins[pin]->pin_cfg_flags = cfg->flags; if (cfg->function != NULL) { fnc = as3722_pinmux_get_function(sc, cfg->function); if (fnc == -1) { device_printf(sc->dev, "Unknown function %s for pin %s\n", cfg->function, sc->gpio_pins[pin]->pin_name); return (ENXIO); } switch (fnc) { case AS3722_IOSF_INTERRUPT_OUT: case AS3722_IOSF_VSUP_VBAT_LOW_UNDEBOUNCE_OUT: case AS3722_IOSF_OC_PG_SD0: case AS3722_IOSF_POWERGOOD_OUT: case AS3722_IOSF_CLK32K_OUT: case AS3722_IOSF_PWM_OUT: case AS3722_IOSF_OC_PG_SD6: ctrl &= ~(AS3722_GPIO_MODE_MASK << AS3722_GPIO_MODE_SHIFT); ctrl |= AS3722_MODE_PUSH_PULL << AS3722_GPIO_MODE_SHIFT; /* XXX Handle flags (OC + pullup) */ break; case AS3722_IOSF_GPIO_IN_INTERRUPT: case AS3722_IOSF_PWM_IN: case AS3722_IOSF_VOLTAGE_IN_STANDBY: case AS3722_IOSF_WATCHDOG_IN: case AS3722_IOSF_SOFT_RESET_IN: ctrl &= ~(AS3722_GPIO_MODE_MASK << AS3722_GPIO_MODE_SHIFT); ctrl |= AS3722_MODE_INPUT << AS3722_GPIO_MODE_SHIFT; /* XXX Handle flags (pulldown + pullup) */ default: break; } ctrl &= ~(AS3722_GPIO_IOSF_MASK << AS3722_GPIO_IOSF_SHIFT); ctrl |= fnc << AS3722_GPIO_IOSF_SHIFT; } rv = 0; if (ctrl != sc->gpio_pins[pin]->pin_ctrl_reg) { rv = WR1(sc, AS3722_GPIO0_CONTROL + pin, ctrl); sc->gpio_pins[pin]->pin_ctrl_reg = ctrl; } return (rv); } static int as3722_pinmux_read_node(struct as3722_softc *sc, phandle_t node, struct as3722_pincfg *cfg, char **pins, int *lpins) { int rv, i; *lpins = OF_getprop_alloc(node, "pins", 1, (void **)pins); if (*lpins <= 0) return (ENOENT); /* Read function (mux) settings. */ rv = OF_getprop_alloc(node, "function", 1, (void **)&cfg->function); if (rv <= 0) cfg->function = NULL; /* Read boolean properties. */ for (i = 0; i < nitems(as3722_cfg_names); i++) { if (OF_hasprop(node, as3722_cfg_names[i].name)) cfg->flags |= as3722_cfg_names[i].config; } return (0); } static int as3722_pinmux_process_node(struct as3722_softc *sc, phandle_t node) { struct as3722_pincfg cfg; char *pins, *pname; int i, len, lpins, rv; rv = as3722_pinmux_read_node(sc, node, &cfg, &pins, &lpins); if (rv != 0) return (rv); len = 0; pname = pins; do { i = strlen(pname) + 1; rv = as3722_pinmux_config_node(sc, pname, &cfg); if (rv != 0) { device_printf(sc->dev, "Cannot configure pin: %s: %d\n", pname, rv); } len += i; pname += i; } while (len < lpins); if (pins != NULL) OF_prop_free(pins); if (cfg.function != NULL) OF_prop_free(cfg.function); return (rv); } int as3722_pinmux_configure(device_t dev, phandle_t cfgxref) { struct as3722_softc *sc; phandle_t node, cfgnode; int rv; sc = device_get_softc(dev); cfgnode = OF_node_from_xref(cfgxref); for (node = OF_child(cfgnode); node != 0; node = OF_peer(node)) { if (!fdt_is_enabled(node)) continue; rv = as3722_pinmux_process_node(sc, node); if (rv != 0) device_printf(dev, "Failed to process pinmux"); } return (0); } /* -------------------------------------------------------------------------- * * GPIO */ device_t as3722_gpio_get_bus(device_t dev) { struct as3722_softc *sc; sc = device_get_softc(dev); return (sc->gpio_busdev); } int as3722_gpio_pin_max(device_t dev, int *maxpin) { *maxpin = NGPIO - 1; return (0); } int as3722_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { struct as3722_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); GPIO_LOCK(sc); *caps = sc->gpio_pins[pin]->pin_caps; GPIO_UNLOCK(sc); return (0); } int as3722_gpio_pin_getname(device_t dev, uint32_t pin, char *name) { struct as3722_softc *sc; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); GPIO_LOCK(sc); memcpy(name, sc->gpio_pins[pin]->pin_name, GPIOMAXNAME); GPIO_UNLOCK(sc); return (0); } int as3722_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *out_flags) { struct as3722_softc *sc; uint8_t tmp, mode, iosf; uint32_t flags; bool inverted; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); GPIO_LOCK(sc); tmp = sc->gpio_pins[pin]->pin_ctrl_reg; GPIO_UNLOCK(sc); iosf = (tmp >> AS3722_GPIO_IOSF_SHIFT) & AS3722_GPIO_IOSF_MASK; mode = (tmp >> AS3722_GPIO_MODE_SHIFT) & AS3722_GPIO_MODE_MASK; inverted = (tmp & AS3722_GPIO_INVERT) != 0; /* Is pin in GPIO mode ? */ if (iosf != AS3722_IOSF_GPIO) return (ENXIO); flags = 0; switch (mode) { case AS3722_MODE_INPUT: flags = GPIO_PIN_INPUT; break; case AS3722_MODE_PUSH_PULL: case AS3722_MODE_PUSH_PULL_LV: flags = GPIO_PIN_OUTPUT; break; case AS3722_MODE_OPEN_DRAIN: case AS3722_MODE_OPEN_DRAIN_LV: flags = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | GPIO_PIN_OPENDRAIN; break; case AS3722_MODE_TRISTATE: flags = GPIO_PIN_TRISTATE; break; case AS3722_MODE_INPUT_PULL_UP_LV: flags = GPIO_PIN_INPUT | GPIO_PIN_PULLUP; break; case AS3722_MODE_INPUT_PULL_DOWN: flags = GPIO_PIN_OUTPUT | GPIO_PIN_PULLDOWN; break; } if (inverted) flags |= GPIO_PIN_INVIN | GPIO_PIN_INVOUT; *out_flags = flags; return (0); } static int as3722_gpio_get_mode(struct as3722_softc *sc, uint32_t pin, uint32_t gpio_flags) { uint8_t ctrl; int flags; ctrl = sc->gpio_pins[pin]->pin_ctrl_reg; flags = sc->gpio_pins[pin]->pin_cfg_flags; /* Tristate mode. */ if (flags & AS3722_CFG_BIAS_HIGH_IMPEDANCE || gpio_flags & GPIO_PIN_TRISTATE) return (AS3722_MODE_TRISTATE); /* Open drain modes. */ if (flags & AS3722_CFG_OPEN_DRAIN || gpio_flags & GPIO_PIN_OPENDRAIN) { /* Only pull up have effect */ if (flags & AS3722_CFG_BIAS_PULL_UP || gpio_flags & GPIO_PIN_PULLUP) return (AS3722_MODE_OPEN_DRAIN_LV); return (AS3722_MODE_OPEN_DRAIN); } /* Input modes. */ if (gpio_flags & GPIO_PIN_INPUT) { /* Accept pull up or pull down. */ if (flags & AS3722_CFG_BIAS_PULL_UP || gpio_flags & GPIO_PIN_PULLUP) return (AS3722_MODE_INPUT_PULL_UP_LV); if (flags & AS3722_CFG_BIAS_PULL_DOWN || gpio_flags & GPIO_PIN_PULLDOWN) return (AS3722_MODE_INPUT_PULL_DOWN); return (AS3722_MODE_INPUT); } /* * Output modes. * Pull down is used as indicator of low voltage output. */ if (flags & AS3722_CFG_BIAS_PULL_DOWN || gpio_flags & GPIO_PIN_PULLDOWN) return (AS3722_MODE_PUSH_PULL_LV); return (AS3722_MODE_PUSH_PULL); } int as3722_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct as3722_softc *sc; uint8_t ctrl, mode, iosf; int rv; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); GPIO_LOCK(sc); ctrl = sc->gpio_pins[pin]->pin_ctrl_reg; iosf = (ctrl >> AS3722_GPIO_IOSF_SHIFT) & AS3722_GPIO_IOSF_MASK; /* Is pin in GPIO mode ? */ if (iosf != AS3722_IOSF_GPIO) { GPIO_UNLOCK(sc); return (ENXIO); } mode = as3722_gpio_get_mode(sc, pin, flags); ctrl &= ~(AS3722_GPIO_MODE_MASK << AS3722_GPIO_MODE_SHIFT); ctrl |= AS3722_MODE_PUSH_PULL << AS3722_GPIO_MODE_SHIFT; rv = 0; if (ctrl != sc->gpio_pins[pin]->pin_ctrl_reg) { rv = WR1(sc, AS3722_GPIO0_CONTROL + pin, ctrl); sc->gpio_pins[pin]->pin_ctrl_reg = ctrl; } GPIO_UNLOCK(sc); return (rv); } int as3722_gpio_pin_set(device_t dev, uint32_t pin, uint32_t val) { struct as3722_softc *sc; uint8_t tmp; int rv; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); tmp = (val != 0) ? 1 : 0; if (sc->gpio_pins[pin]->pin_ctrl_reg & AS3722_GPIO_INVERT) tmp ^= 1; GPIO_LOCK(sc); rv = RM1(sc, AS3722_GPIO_SIGNAL_OUT, (1 << pin), (tmp << pin)); GPIO_UNLOCK(sc); return (rv); } int as3722_gpio_pin_get(device_t dev, uint32_t pin, uint32_t *val) { struct as3722_softc *sc; uint8_t tmp, mode, ctrl; int rv; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); GPIO_LOCK(sc); ctrl = sc->gpio_pins[pin]->pin_ctrl_reg; mode = (ctrl >> AS3722_GPIO_MODE_SHIFT) & AS3722_GPIO_MODE_MASK; if ((mode == AS3722_MODE_PUSH_PULL) || (mode == AS3722_MODE_PUSH_PULL_LV)) rv = RD1(sc, AS3722_GPIO_SIGNAL_OUT, &tmp); else rv = RD1(sc, AS3722_GPIO_SIGNAL_IN, &tmp); GPIO_UNLOCK(sc); if (rv != 0) return (rv); *val = tmp & (1 << pin) ? 1 : 0; if (ctrl & AS3722_GPIO_INVERT) *val ^= 1; return (0); } int as3722_gpio_pin_toggle(device_t dev, uint32_t pin) { struct as3722_softc *sc; uint8_t tmp; int rv; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); GPIO_LOCK(sc); rv = RD1(sc, AS3722_GPIO_SIGNAL_OUT, &tmp); if (rv != 0) { GPIO_UNLOCK(sc); return (rv); } tmp ^= (1 <gpio_lock, "AS3722 GPIO lock"); sc->gpio_npins = NGPIO; sc->gpio_pins = malloc(sizeof(struct as3722_gpio_pin *) * sc->gpio_npins, M_AS3722_GPIO, M_WAITOK | M_ZERO); sc->gpio_busdev = gpiobus_attach_bus(sc->dev); if (sc->gpio_busdev == NULL) return (ENXIO); for (i = 0; i < sc->gpio_npins; i++) { sc->gpio_pins[i] = malloc(sizeof(struct as3722_gpio_pin), M_AS3722_GPIO, M_WAITOK | M_ZERO); pin = sc->gpio_pins[i]; sprintf(pin->pin_name, "gpio%d", i); pin->pin_caps = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL | GPIO_PIN_TRISTATE | GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN | GPIO_PIN_INVIN | GPIO_PIN_INVOUT; rv = RD1(sc, AS3722_GPIO0_CONTROL + i, &pin->pin_ctrl_reg); if (rv != 0) { device_printf(sc->dev, "Cannot read configuration for pin %s\n", sc->gpio_pins[i]->pin_name); } } return (0); }