diff --git a/sys/arm/allwinner/aw_gpio.c b/sys/arm/allwinner/aw_gpio.c index 1229d5bb146d..b180ef226c15 100644 --- a/sys/arm/allwinner/aw_gpio.c +++ b/sys/arm/allwinner/aw_gpio.c @@ -1,1495 +1,1495 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2013 Ganbold Tsagaankhuu * Copyright (c) 2012 Oleksandr Tymoshenko * Copyright (c) 2012 Luiz Otavio O Souza. * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__aarch64__) #include "opt_soc.h" #endif #include "pic_if.h" #include "gpio_if.h" #define AW_GPIO_DEFAULT_CAPS (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | \ GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN); #define AW_GPIO_INTR_CAPS (GPIO_INTR_LEVEL_LOW | GPIO_INTR_LEVEL_HIGH | \ GPIO_INTR_EDGE_RISING | GPIO_INTR_EDGE_FALLING | GPIO_INTR_EDGE_BOTH) #define AW_GPIO_NONE 0 #define AW_GPIO_PULLUP 1 #define AW_GPIO_PULLDOWN 2 #define AW_GPIO_INPUT 0 #define AW_GPIO_OUTPUT 1 #define AW_GPIO_DRV_MASK 0x3 #define AW_GPIO_PUD_MASK 0x3 #define AW_PINCTRL 1 #define AW_R_PINCTRL 2 struct aw_gpio_conf { struct allwinner_padconf *padconf; const char *banks; }; /* Defined in aw_padconf.c */ #ifdef SOC_ALLWINNER_A10 extern struct allwinner_padconf a10_padconf; struct aw_gpio_conf a10_gpio_conf = { .padconf = &a10_padconf, .banks = "abcdefghi", }; #endif /* Defined in a13_padconf.c */ #ifdef SOC_ALLWINNER_A13 extern struct allwinner_padconf a13_padconf; struct aw_gpio_conf a13_gpio_conf = { .padconf = &a13_padconf, .banks = "bcdefg", }; #endif /* Defined in a20_padconf.c */ #ifdef SOC_ALLWINNER_A20 extern struct allwinner_padconf a20_padconf; struct aw_gpio_conf a20_gpio_conf = { .padconf = &a20_padconf, .banks = "abcdefghi", }; #endif /* Defined in a31_padconf.c */ #ifdef SOC_ALLWINNER_A31 extern struct allwinner_padconf a31_padconf; struct aw_gpio_conf a31_gpio_conf = { .padconf = &a31_padconf, .banks = "abcdefgh", }; #endif /* Defined in a31s_padconf.c */ #ifdef SOC_ALLWINNER_A31S extern struct allwinner_padconf a31s_padconf; struct aw_gpio_conf a31s_gpio_conf = { .padconf = &a31s_padconf, .banks = "abcdefgh", }; #endif #if defined(SOC_ALLWINNER_A31) || defined(SOC_ALLWINNER_A31S) extern struct allwinner_padconf a31_r_padconf; struct aw_gpio_conf a31_r_gpio_conf = { .padconf = &a31_r_padconf, .banks = "lm", }; #endif /* Defined in a33_padconf.c */ #ifdef SOC_ALLWINNER_A33 extern struct allwinner_padconf a33_padconf; struct aw_gpio_conf a33_gpio_conf = { .padconf = &a33_padconf, .banks = "bcdefgh", }; #endif /* Defined in h3_padconf.c */ #if defined(SOC_ALLWINNER_H3) || defined(SOC_ALLWINNER_H5) extern struct allwinner_padconf h3_padconf; extern struct allwinner_padconf h3_r_padconf; struct aw_gpio_conf h3_gpio_conf = { .padconf = &h3_padconf, .banks = "acdefg", }; struct aw_gpio_conf h3_r_gpio_conf = { .padconf = &h3_r_padconf, .banks = "l", }; #endif /* Defined in a83t_padconf.c */ #ifdef SOC_ALLWINNER_A83T extern struct allwinner_padconf a83t_padconf; extern struct allwinner_padconf a83t_r_padconf; struct aw_gpio_conf a83t_gpio_conf = { .padconf = &a83t_padconf, .banks = "bcdefgh" }; struct aw_gpio_conf a83t_r_gpio_conf = { .padconf = &a83t_r_padconf, .banks = "l", }; #endif /* Defined in a64_padconf.c */ #ifdef SOC_ALLWINNER_A64 extern struct allwinner_padconf a64_padconf; extern struct allwinner_padconf a64_r_padconf; struct aw_gpio_conf a64_gpio_conf = { .padconf = &a64_padconf, .banks = "bcdefgh", }; struct aw_gpio_conf a64_r_gpio_conf = { .padconf = &a64_r_padconf, .banks = "l", }; #endif /* Defined in h6_padconf.c */ #ifdef SOC_ALLWINNER_H6 extern struct allwinner_padconf h6_padconf; extern struct allwinner_padconf h6_r_padconf; struct aw_gpio_conf h6_gpio_conf = { .padconf = &h6_padconf, .banks = "cdfgh", }; struct aw_gpio_conf h6_r_gpio_conf = { .padconf = &h6_r_padconf, .banks = "lm", }; #endif static struct ofw_compat_data compat_data[] = { #ifdef SOC_ALLWINNER_A10 {"allwinner,sun4i-a10-pinctrl", (uintptr_t)&a10_gpio_conf}, #endif #ifdef SOC_ALLWINNER_A13 {"allwinner,sun5i-a13-pinctrl", (uintptr_t)&a13_gpio_conf}, #endif #ifdef SOC_ALLWINNER_A20 {"allwinner,sun7i-a20-pinctrl", (uintptr_t)&a20_gpio_conf}, #endif #ifdef SOC_ALLWINNER_A31 {"allwinner,sun6i-a31-pinctrl", (uintptr_t)&a31_gpio_conf}, #endif #ifdef SOC_ALLWINNER_A31S {"allwinner,sun6i-a31s-pinctrl", (uintptr_t)&a31s_gpio_conf}, #endif #if defined(SOC_ALLWINNER_A31) || defined(SOC_ALLWINNER_A31S) {"allwinner,sun6i-a31-r-pinctrl", (uintptr_t)&a31_r_gpio_conf}, #endif #ifdef SOC_ALLWINNER_A33 {"allwinner,sun6i-a33-pinctrl", (uintptr_t)&a33_gpio_conf}, #endif #ifdef SOC_ALLWINNER_A83T {"allwinner,sun8i-a83t-pinctrl", (uintptr_t)&a83t_gpio_conf}, {"allwinner,sun8i-a83t-r-pinctrl", (uintptr_t)&a83t_r_gpio_conf}, #endif #if defined(SOC_ALLWINNER_H3) || defined(SOC_ALLWINNER_H5) {"allwinner,sun8i-h3-pinctrl", (uintptr_t)&h3_gpio_conf}, {"allwinner,sun50i-h5-pinctrl", (uintptr_t)&h3_gpio_conf}, {"allwinner,sun8i-h3-r-pinctrl", (uintptr_t)&h3_r_gpio_conf}, #endif #ifdef SOC_ALLWINNER_A64 {"allwinner,sun50i-a64-pinctrl", (uintptr_t)&a64_gpio_conf}, {"allwinner,sun50i-a64-r-pinctrl", (uintptr_t)&a64_r_gpio_conf}, #endif #ifdef SOC_ALLWINNER_H6 {"allwinner,sun50i-h6-pinctrl", (uintptr_t)&h6_gpio_conf}, {"allwinner,sun50i-h6-r-pinctrl", (uintptr_t)&h6_r_gpio_conf}, #endif {NULL, 0} }; struct clk_list { TAILQ_ENTRY(clk_list) next; clk_t clk; }; struct gpio_irqsrc { struct intr_irqsrc isrc; u_int irq; uint32_t mode; uint32_t pin; uint32_t bank; uint32_t intnum; uint32_t intfunc; uint32_t oldfunc; bool enabled; }; #define AW_GPIO_MEMRES 0 #define AW_GPIO_IRQRES 1 #define AW_GPIO_RESSZ 2 struct aw_gpio_softc { device_t sc_dev; device_t sc_busdev; struct resource * sc_res[AW_GPIO_RESSZ]; struct mtx sc_mtx; struct resource * sc_mem_res; struct resource * sc_irq_res; void * sc_intrhand; struct aw_gpio_conf *conf; TAILQ_HEAD(, clk_list) clk_list; struct gpio_irqsrc *gpio_pic_irqsrc; int nirqs; }; static struct resource_spec aw_gpio_res_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE }, { -1, 0, 0 } }; #define AW_GPIO_LOCK(_sc) mtx_lock_spin(&(_sc)->sc_mtx) #define AW_GPIO_UNLOCK(_sc) mtx_unlock_spin(&(_sc)->sc_mtx) #define AW_GPIO_LOCK_ASSERT(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED) #define AW_GPIO_GP_CFG(_bank, _idx) 0x00 + ((_bank) * 0x24) + ((_idx) << 2) #define AW_GPIO_GP_DAT(_bank) 0x10 + ((_bank) * 0x24) #define AW_GPIO_GP_DRV(_bank, _idx) 0x14 + ((_bank) * 0x24) + ((_idx) << 2) #define AW_GPIO_GP_PUL(_bank, _idx) 0x1c + ((_bank) * 0x24) + ((_idx) << 2) #define AW_GPIO_GP_INT_BASE(_bank) (0x200 + 0x20 * _bank) #define AW_GPIO_GP_INT_CFG(_bank, _pin) (AW_GPIO_GP_INT_BASE(_bank) + (0x4 * ((_pin) / 8))) #define AW_GPIO_GP_INT_CTL(_bank) (AW_GPIO_GP_INT_BASE(_bank) + 0x10) #define AW_GPIO_GP_INT_STA(_bank) (AW_GPIO_GP_INT_BASE(_bank) + 0x14) #define AW_GPIO_GP_INT_DEB(_bank) (AW_GPIO_GP_INT_BASE(_bank) + 0x18) #define AW_GPIO_INT_EDGE_POSITIVE 0x0 #define AW_GPIO_INT_EDGE_NEGATIVE 0x1 #define AW_GPIO_INT_LEVEL_HIGH 0x2 #define AW_GPIO_INT_LEVEL_LOW 0x3 #define AW_GPIO_INT_EDGE_BOTH 0x4 static char *aw_gpio_parse_function(phandle_t node); static const char **aw_gpio_parse_pins(phandle_t node, int *pins_nb); static uint32_t aw_gpio_parse_bias(phandle_t node); static int aw_gpio_parse_drive_strength(phandle_t node, uint32_t *drive); static int aw_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *value); static int aw_gpio_pin_set(device_t dev, uint32_t pin, unsigned int value); static int aw_gpio_pin_get_locked(struct aw_gpio_softc *sc, uint32_t pin, unsigned int *value); static int aw_gpio_pin_set_locked(struct aw_gpio_softc *sc, uint32_t pin, unsigned int value); static void aw_gpio_intr(void *arg); static void aw_gpio_pic_disable_intr(device_t dev, struct intr_irqsrc *isrc); static void aw_gpio_pic_disable_intr_locked(struct aw_gpio_softc *sc, struct intr_irqsrc *isrc); static void aw_gpio_pic_post_filter(device_t dev, struct intr_irqsrc *isrc); static int aw_gpio_register_isrcs(struct aw_gpio_softc *sc); #define AW_GPIO_WRITE(_sc, _off, _val) \ bus_write_4((_sc)->sc_res[AW_GPIO_MEMRES], _off, _val) #define AW_GPIO_READ(_sc, _off) \ bus_read_4((_sc)->sc_res[AW_GPIO_MEMRES], _off) static uint32_t aw_gpio_get_function(struct aw_gpio_softc *sc, uint32_t pin) { uint32_t bank, func, offset; /* Must be called with lock held. */ AW_GPIO_LOCK_ASSERT(sc); if (pin > sc->conf->padconf->npins) return (0); bank = sc->conf->padconf->pins[pin].port; pin = sc->conf->padconf->pins[pin].pin; offset = ((pin & 0x07) << 2); func = AW_GPIO_READ(sc, AW_GPIO_GP_CFG(bank, pin >> 3)); return ((func >> offset) & 0x7); } static int aw_gpio_set_function(struct aw_gpio_softc *sc, uint32_t pin, uint32_t f) { uint32_t bank, data, offset; /* Check if the function exists in the padconf data */ if (sc->conf->padconf->pins[pin].functions[f] == NULL) return (EINVAL); /* Must be called with lock held. */ AW_GPIO_LOCK_ASSERT(sc); bank = sc->conf->padconf->pins[pin].port; pin = sc->conf->padconf->pins[pin].pin; offset = ((pin & 0x07) << 2); data = AW_GPIO_READ(sc, AW_GPIO_GP_CFG(bank, pin >> 3)); data &= ~(7 << offset); data |= (f << offset); AW_GPIO_WRITE(sc, AW_GPIO_GP_CFG(bank, pin >> 3), data); return (0); } static uint32_t aw_gpio_get_pud(struct aw_gpio_softc *sc, uint32_t pin) { uint32_t bank, offset, val; /* Must be called with lock held. */ AW_GPIO_LOCK_ASSERT(sc); bank = sc->conf->padconf->pins[pin].port; pin = sc->conf->padconf->pins[pin].pin; offset = ((pin & 0x0f) << 1); val = AW_GPIO_READ(sc, AW_GPIO_GP_PUL(bank, pin >> 4)); return ((val >> offset) & AW_GPIO_PUD_MASK); } static void aw_gpio_set_pud(struct aw_gpio_softc *sc, uint32_t pin, uint32_t state) { uint32_t bank, offset, val; if (aw_gpio_get_pud(sc, pin) == state) return; /* Must be called with lock held. */ AW_GPIO_LOCK_ASSERT(sc); bank = sc->conf->padconf->pins[pin].port; pin = sc->conf->padconf->pins[pin].pin; offset = ((pin & 0x0f) << 1); val = AW_GPIO_READ(sc, AW_GPIO_GP_PUL(bank, pin >> 4)); val &= ~(AW_GPIO_PUD_MASK << offset); val |= (state << offset); AW_GPIO_WRITE(sc, AW_GPIO_GP_PUL(bank, pin >> 4), val); } static uint32_t aw_gpio_get_drv(struct aw_gpio_softc *sc, uint32_t pin) { uint32_t bank, offset, val; /* Must be called with lock held. */ AW_GPIO_LOCK_ASSERT(sc); bank = sc->conf->padconf->pins[pin].port; pin = sc->conf->padconf->pins[pin].pin; offset = ((pin & 0x0f) << 1); val = AW_GPIO_READ(sc, AW_GPIO_GP_DRV(bank, pin >> 4)); return ((val >> offset) & AW_GPIO_DRV_MASK); } static void aw_gpio_set_drv(struct aw_gpio_softc *sc, uint32_t pin, uint32_t drive) { uint32_t bank, offset, val; if (aw_gpio_get_drv(sc, pin) == drive) return; /* Must be called with lock held. */ AW_GPIO_LOCK_ASSERT(sc); bank = sc->conf->padconf->pins[pin].port; pin = sc->conf->padconf->pins[pin].pin; offset = ((pin & 0x0f) << 1); val = AW_GPIO_READ(sc, AW_GPIO_GP_DRV(bank, pin >> 4)); val &= ~(AW_GPIO_DRV_MASK << offset); val |= (drive << offset); AW_GPIO_WRITE(sc, AW_GPIO_GP_DRV(bank, pin >> 4), val); } static int aw_gpio_pin_configure(struct aw_gpio_softc *sc, uint32_t pin, uint32_t flags) { u_int val; int err = 0; /* Must be called with lock held. */ AW_GPIO_LOCK_ASSERT(sc); if (pin > sc->conf->padconf->npins) return (EINVAL); /* Manage input/output. */ if (flags & GPIO_PIN_INPUT) { err = aw_gpio_set_function(sc, pin, AW_GPIO_INPUT); } else if ((flags & GPIO_PIN_OUTPUT) && aw_gpio_get_function(sc, pin) != AW_GPIO_OUTPUT) { if (flags & GPIO_PIN_PRESET_LOW) { aw_gpio_pin_set_locked(sc, pin, 0); } else if (flags & GPIO_PIN_PRESET_HIGH) { aw_gpio_pin_set_locked(sc, pin, 1); } else { /* Read the pin and preset output to current state. */ err = aw_gpio_set_function(sc, pin, AW_GPIO_INPUT); if (err == 0) { aw_gpio_pin_get_locked(sc, pin, &val); aw_gpio_pin_set_locked(sc, pin, val); } } if (err == 0) err = aw_gpio_set_function(sc, pin, AW_GPIO_OUTPUT); } if (err) return (err); /* Manage Pull-up/pull-down. */ if (flags & GPIO_PIN_PULLUP) aw_gpio_set_pud(sc, pin, AW_GPIO_PULLUP); else if (flags & GPIO_PIN_PULLDOWN) aw_gpio_set_pud(sc, pin, AW_GPIO_PULLDOWN); else aw_gpio_set_pud(sc, pin, AW_GPIO_NONE); return (0); } static device_t aw_gpio_get_bus(device_t dev) { struct aw_gpio_softc *sc; sc = device_get_softc(dev); return (sc->sc_busdev); } static int aw_gpio_pin_max(device_t dev, int *maxpin) { struct aw_gpio_softc *sc; sc = device_get_softc(dev); *maxpin = sc->conf->padconf->npins - 1; return (0); } static int aw_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { struct aw_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->conf->padconf->npins) return (EINVAL); *caps = AW_GPIO_DEFAULT_CAPS; if (sc->conf->padconf->pins[pin].eint_func != 0) *caps |= AW_GPIO_INTR_CAPS; return (0); } static int aw_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) { struct aw_gpio_softc *sc; uint32_t func; uint32_t pud; sc = device_get_softc(dev); if (pin >= sc->conf->padconf->npins) return (EINVAL); AW_GPIO_LOCK(sc); func = aw_gpio_get_function(sc, pin); switch (func) { case AW_GPIO_INPUT: *flags = GPIO_PIN_INPUT; break; case AW_GPIO_OUTPUT: *flags = GPIO_PIN_OUTPUT; break; default: *flags = 0; break; } pud = aw_gpio_get_pud(sc, pin); switch (pud) { case AW_GPIO_PULLDOWN: *flags |= GPIO_PIN_PULLDOWN; break; case AW_GPIO_PULLUP: *flags |= GPIO_PIN_PULLUP; break; default: break; } AW_GPIO_UNLOCK(sc); return (0); } static int aw_gpio_pin_getname(device_t dev, uint32_t pin, char *name) { struct aw_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= sc->conf->padconf->npins) return (EINVAL); snprintf(name, GPIOMAXNAME - 1, "%s", sc->conf->padconf->pins[pin].name); name[GPIOMAXNAME - 1] = '\0'; return (0); } static int aw_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct aw_gpio_softc *sc; int err; sc = device_get_softc(dev); if (pin > sc->conf->padconf->npins) return (EINVAL); AW_GPIO_LOCK(sc); err = aw_gpio_pin_configure(sc, pin, flags); AW_GPIO_UNLOCK(sc); return (err); } static int aw_gpio_pin_set_locked(struct aw_gpio_softc *sc, uint32_t pin, unsigned int value) { uint32_t bank, data; AW_GPIO_LOCK_ASSERT(sc); if (pin > sc->conf->padconf->npins) return (EINVAL); bank = sc->conf->padconf->pins[pin].port; pin = sc->conf->padconf->pins[pin].pin; data = AW_GPIO_READ(sc, AW_GPIO_GP_DAT(bank)); if (value) data |= (1 << pin); else data &= ~(1 << pin); AW_GPIO_WRITE(sc, AW_GPIO_GP_DAT(bank), data); return (0); } static int aw_gpio_pin_set(device_t dev, uint32_t pin, unsigned int value) { struct aw_gpio_softc *sc; int ret; sc = device_get_softc(dev); AW_GPIO_LOCK(sc); ret = aw_gpio_pin_set_locked(sc, pin, value); AW_GPIO_UNLOCK(sc); return (ret); } static int aw_gpio_pin_get_locked(struct aw_gpio_softc *sc,uint32_t pin, unsigned int *val) { uint32_t bank, reg_data; int32_t func; int err; AW_GPIO_LOCK_ASSERT(sc); if (pin > sc->conf->padconf->npins) return (EINVAL); func = aw_gpio_get_function(sc, pin); if (func == sc->conf->padconf->pins[pin].eint_func) { /* "pl_eintX */ err = aw_gpio_set_function(sc, pin, AW_GPIO_INPUT); if (err != 0) return (err); } bank = sc->conf->padconf->pins[pin].port; pin = sc->conf->padconf->pins[pin].pin; reg_data = AW_GPIO_READ(sc, AW_GPIO_GP_DAT(bank)); *val = (reg_data & (1 << pin)) ? 1 : 0; if (func == sc->conf->padconf->pins[pin].eint_func) (void)aw_gpio_set_function(sc, pin, func); return (0); } static char * aw_gpio_parse_function(phandle_t node) { char *function; if (OF_getprop_alloc(node, "function", (void **)&function) != -1) return (function); if (OF_getprop_alloc(node, "allwinner,function", (void **)&function) != -1) return (function); return (NULL); } static const char ** aw_gpio_parse_pins(phandle_t node, int *pins_nb) { const char **pinlist; *pins_nb = ofw_bus_string_list_to_array(node, "pins", &pinlist); if (*pins_nb > 0) return (pinlist); *pins_nb = ofw_bus_string_list_to_array(node, "allwinner,pins", &pinlist); if (*pins_nb > 0) return (pinlist); return (NULL); } static uint32_t aw_gpio_parse_bias(phandle_t node) { uint32_t bias; if (OF_getencprop(node, "pull", &bias, sizeof(bias)) != -1) return (bias); if (OF_getencprop(node, "allwinner,pull", &bias, sizeof(bias)) != -1) return (bias); if (OF_hasprop(node, "bias-disable")) return (AW_GPIO_NONE); if (OF_hasprop(node, "bias-pull-up")) return (AW_GPIO_PULLUP); if (OF_hasprop(node, "bias-pull-down")) return (AW_GPIO_PULLDOWN); return (AW_GPIO_NONE); } static int aw_gpio_parse_drive_strength(phandle_t node, uint32_t *drive) { uint32_t drive_str; if (OF_getencprop(node, "drive", drive, sizeof(*drive)) != -1) return (0); if (OF_getencprop(node, "allwinner,drive", drive, sizeof(*drive)) != -1) return (0); if (OF_getencprop(node, "drive-strength", &drive_str, sizeof(drive_str)) != -1) { *drive = (drive_str / 10) - 1; return (0); } return (1); } static int aw_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *val) { struct aw_gpio_softc *sc; int ret; sc = device_get_softc(dev); AW_GPIO_LOCK(sc); ret = aw_gpio_pin_get_locked(sc, pin, val); AW_GPIO_UNLOCK(sc); return (ret); } static int aw_gpio_pin_toggle(device_t dev, uint32_t pin) { struct aw_gpio_softc *sc; uint32_t bank, data; sc = device_get_softc(dev); if (pin > sc->conf->padconf->npins) return (EINVAL); bank = sc->conf->padconf->pins[pin].port; pin = sc->conf->padconf->pins[pin].pin; AW_GPIO_LOCK(sc); data = AW_GPIO_READ(sc, AW_GPIO_GP_DAT(bank)); if (data & (1 << pin)) data &= ~(1 << pin); else data |= (1 << pin); AW_GPIO_WRITE(sc, AW_GPIO_GP_DAT(bank), data); AW_GPIO_UNLOCK(sc); return (0); } static int aw_gpio_pin_access_32(device_t dev, uint32_t first_pin, uint32_t clear_pins, uint32_t change_pins, uint32_t *orig_pins) { struct aw_gpio_softc *sc; uint32_t bank, data, pin; sc = device_get_softc(dev); if (first_pin > sc->conf->padconf->npins) return (EINVAL); /* * We require that first_pin refers to the first pin in a bank, because * this API is not about convenience, it's for making a set of pins * change simultaneously (required) with reasonably high performance * (desired); we need to do a read-modify-write on a single register. */ bank = sc->conf->padconf->pins[first_pin].port; pin = sc->conf->padconf->pins[first_pin].pin; if (pin != 0) return (EINVAL); AW_GPIO_LOCK(sc); data = AW_GPIO_READ(sc, AW_GPIO_GP_DAT(bank)); if ((clear_pins | change_pins) != 0) AW_GPIO_WRITE(sc, AW_GPIO_GP_DAT(bank), (data & ~clear_pins) ^ change_pins); AW_GPIO_UNLOCK(sc); if (orig_pins != NULL) *orig_pins = data; return (0); } static int aw_gpio_pin_config_32(device_t dev, uint32_t first_pin, uint32_t num_pins, uint32_t *pin_flags) { struct aw_gpio_softc *sc; uint32_t pin; int err; sc = device_get_softc(dev); if (first_pin > sc->conf->padconf->npins) return (EINVAL); if (sc->conf->padconf->pins[first_pin].pin != 0) return (EINVAL); /* * The configuration for a bank of pins is scattered among several * registers; we cannot g'tee to simultaneously change the state of all * the pins in the flags array. So just loop through the array * configuring each pin for now. If there was a strong need, it might * be possible to support some limited simultaneous config, such as * adjacent groups of 8 pins that line up the same as the config regs. */ for (err = 0, pin = first_pin; err == 0 && pin < num_pins; ++pin) { if (pin_flags[pin] & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) err = aw_gpio_pin_configure(sc, pin, pin_flags[pin]); } return (err); } static int aw_gpio_map_gpios(device_t bus, phandle_t dev, phandle_t gparent, int gcells, pcell_t *gpios, uint32_t *pin, uint32_t *flags) { struct aw_gpio_softc *sc; int i; sc = device_get_softc(bus); /* The GPIO pins are mapped as: . */ for (i = 0; i < sc->conf->padconf->npins; i++) if (sc->conf->padconf->pins[i].port == gpios[0] && sc->conf->padconf->pins[i].pin == gpios[1]) { *pin = i; break; } *flags = gpios[gcells - 1]; return (0); } static int aw_find_pinnum_by_name(struct aw_gpio_softc *sc, const char *pinname) { int i; for (i = 0; i < sc->conf->padconf->npins; i++) if (!strcmp(pinname, sc->conf->padconf->pins[i].name)) return i; return (-1); } static int aw_find_pin_func(struct aw_gpio_softc *sc, int pin, const char *func) { int i; for (i = 0; i < AW_MAX_FUNC_BY_PIN; i++) if (sc->conf->padconf->pins[pin].functions[i] && !strcmp(func, sc->conf->padconf->pins[pin].functions[i])) return (i); return (-1); } static int aw_fdt_configure_pins(device_t dev, phandle_t cfgxref) { struct aw_gpio_softc *sc; phandle_t node; const char **pinlist = NULL; char *pin_function = NULL; uint32_t pin_drive, pin_pull; int pins_nb, pin_num, pin_func, i, ret; bool set_drive; sc = device_get_softc(dev); node = OF_node_from_xref(cfgxref); ret = 0; set_drive = false; /* Getting all prop for configuring pins */ pinlist = aw_gpio_parse_pins(node, &pins_nb); if (pinlist == NULL) return (ENOENT); pin_function = aw_gpio_parse_function(node); if (pin_function == NULL) { ret = ENOENT; goto out; } if (aw_gpio_parse_drive_strength(node, &pin_drive) == 0) set_drive = true; pin_pull = aw_gpio_parse_bias(node); /* Configure each pin to the correct function, drive and pull */ for (i = 0; i < pins_nb; i++) { pin_num = aw_find_pinnum_by_name(sc, pinlist[i]); if (pin_num == -1) { ret = ENOENT; goto out; } pin_func = aw_find_pin_func(sc, pin_num, pin_function); if (pin_func == -1) { ret = ENOENT; goto out; } AW_GPIO_LOCK(sc); if (aw_gpio_get_function(sc, pin_num) != pin_func) aw_gpio_set_function(sc, pin_num, pin_func); if (set_drive) aw_gpio_set_drv(sc, pin_num, pin_drive); if (pin_pull != AW_GPIO_NONE) aw_gpio_set_pud(sc, pin_num, pin_pull); AW_GPIO_UNLOCK(sc); } out: OF_prop_free(pinlist); OF_prop_free(pin_function); return (ret); } static void aw_gpio_enable_bank_supply(void *arg) { struct aw_gpio_softc *sc = arg; regulator_t vcc_supply; char bank_reg_name[16]; int i, nbanks; nbanks = strlen(sc->conf->banks); for (i = 0; i < nbanks; i++) { snprintf(bank_reg_name, sizeof(bank_reg_name), "vcc-p%c-supply", sc->conf->banks[i]); if (regulator_get_by_ofw_property(sc->sc_dev, 0, bank_reg_name, &vcc_supply) == 0) { if (bootverbose) device_printf(sc->sc_dev, "Enabling regulator for gpio bank %c\n", sc->conf->banks[i]); if (regulator_enable(vcc_supply) != 0) { device_printf(sc->sc_dev, "Cannot enable regulator for bank %c\n", sc->conf->banks[i]); } } } } static int aw_gpio_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Allwinner GPIO/Pinmux controller"); return (BUS_PROBE_DEFAULT); } static int aw_gpio_attach(device_t dev) { int error; phandle_t gpio; struct aw_gpio_softc *sc; struct clk_list *clkp, *clkp_tmp; clk_t clk; hwreset_t rst = NULL; int off, err, clkret; sc = device_get_softc(dev); sc->sc_dev = dev; mtx_init(&sc->sc_mtx, "aw gpio", "gpio", MTX_SPIN); if (bus_alloc_resources(dev, aw_gpio_res_spec, sc->sc_res) != 0) { device_printf(dev, "cannot allocate device resources\n"); return (ENXIO); } if (bus_setup_intr(dev, sc->sc_res[AW_GPIO_IRQRES], INTR_TYPE_CLK | INTR_MPSAFE, NULL, aw_gpio_intr, sc, &sc->sc_intrhand)) { device_printf(dev, "cannot setup interrupt handler\n"); goto fail; } /* Find our node. */ gpio = ofw_bus_get_node(sc->sc_dev); if (!OF_hasprop(gpio, "gpio-controller")) /* Node is not a GPIO controller. */ goto fail; /* Use the right pin data for the current SoC */ sc->conf = (struct aw_gpio_conf *)ofw_bus_search_compatible(dev, compat_data)->ocd_data; if (hwreset_get_by_ofw_idx(dev, 0, 0, &rst) == 0) { error = hwreset_deassert(rst); if (error != 0) { device_printf(dev, "cannot de-assert reset\n"); goto fail; } } TAILQ_INIT(&sc->clk_list); for (off = 0, clkret = 0; clkret == 0; off++) { clkret = clk_get_by_ofw_index(dev, 0, off, &clk); if (clkret != 0) break; err = clk_enable(clk); if (err != 0) { device_printf(dev, "Could not enable clock %s\n", clk_get_name(clk)); goto fail; } clkp = malloc(sizeof(*clkp), M_DEVBUF, M_WAITOK | M_ZERO); clkp->clk = clk; TAILQ_INSERT_TAIL(&sc->clk_list, clkp, next); } if (clkret != 0 && clkret != ENOENT) { device_printf(dev, "Could not find clock at offset %d (%d)\n", off, clkret); goto fail; } aw_gpio_register_isrcs(sc); intr_pic_register(dev, OF_xref_from_node(ofw_bus_get_node(dev))); - sc->sc_busdev = gpiobus_attach_bus(dev); - if (sc->sc_busdev == NULL) - goto fail; - /* * Register as a pinctrl device */ fdt_pinctrl_register(dev, "pins"); fdt_pinctrl_configure_tree(dev); fdt_pinctrl_register(dev, "allwinner,pins"); fdt_pinctrl_configure_tree(dev); + sc->sc_busdev = gpiobus_attach_bus(dev); + if (sc->sc_busdev == NULL) + goto fail; + config_intrhook_oneshot(aw_gpio_enable_bank_supply, sc); return (0); fail: if (sc->sc_irq_res) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res); if (sc->sc_mem_res) bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res); mtx_destroy(&sc->sc_mtx); /* Disable clock */ TAILQ_FOREACH_SAFE(clkp, &sc->clk_list, next, clkp_tmp) { err = clk_disable(clkp->clk); if (err != 0) device_printf(dev, "Could not disable clock %s\n", clk_get_name(clkp->clk)); err = clk_release(clkp->clk); if (err != 0) device_printf(dev, "Could not release clock %s\n", clk_get_name(clkp->clk)); TAILQ_REMOVE(&sc->clk_list, clkp, next); free(clkp, M_DEVBUF); } /* Assert resets */ if (rst) { hwreset_assert(rst); hwreset_release(rst); } return (ENXIO); } static int aw_gpio_detach(device_t dev) { return (EBUSY); } static void aw_gpio_intr(void *arg) { struct aw_gpio_softc *sc; struct intr_irqsrc *isrc; uint32_t reg; int irq; sc = (struct aw_gpio_softc *)arg; AW_GPIO_LOCK(sc); for (irq = 0; irq < sc->nirqs; irq++) { if (!sc->gpio_pic_irqsrc[irq].enabled) continue; reg = AW_GPIO_READ(sc, AW_GPIO_GP_INT_STA(sc->gpio_pic_irqsrc[irq].bank)); if (!(reg & (1 << sc->gpio_pic_irqsrc[irq].intnum))) continue; isrc = &sc->gpio_pic_irqsrc[irq].isrc; if (intr_isrc_dispatch(isrc, curthread->td_intr_frame) != 0) { aw_gpio_pic_disable_intr_locked(sc, isrc); aw_gpio_pic_post_filter(sc->sc_dev, isrc); device_printf(sc->sc_dev, "Stray irq %u disabled\n", irq); } } AW_GPIO_UNLOCK(sc); } /* * Interrupts support */ static int aw_gpio_register_isrcs(struct aw_gpio_softc *sc) { const char *name; int nirqs; int pin; int err; name = device_get_nameunit(sc->sc_dev); for (nirqs = 0, pin = 0; pin < sc->conf->padconf->npins; pin++) { if (sc->conf->padconf->pins[pin].eint_func == 0) continue; nirqs++; } sc->gpio_pic_irqsrc = malloc(sizeof(*sc->gpio_pic_irqsrc) * nirqs, M_DEVBUF, M_WAITOK | M_ZERO); for (nirqs = 0, pin = 0; pin < sc->conf->padconf->npins; pin++) { if (sc->conf->padconf->pins[pin].eint_func == 0) continue; sc->gpio_pic_irqsrc[nirqs].pin = pin; sc->gpio_pic_irqsrc[nirqs].bank = sc->conf->padconf->pins[pin].eint_bank; sc->gpio_pic_irqsrc[nirqs].intnum = sc->conf->padconf->pins[pin].eint_num; sc->gpio_pic_irqsrc[nirqs].intfunc = sc->conf->padconf->pins[pin].eint_func; sc->gpio_pic_irqsrc[nirqs].irq = nirqs; sc->gpio_pic_irqsrc[nirqs].mode = GPIO_INTR_CONFORM; err = intr_isrc_register(&sc->gpio_pic_irqsrc[nirqs].isrc, sc->sc_dev, 0, "%s,%s", name, sc->conf->padconf->pins[pin].functions[sc->conf->padconf->pins[pin].eint_func]); if (err) { device_printf(sc->sc_dev, "intr_isrs_register failed for irq %d\n", nirqs); } nirqs++; } sc->nirqs = nirqs; return (0); } static void aw_gpio_pic_disable_intr_locked(struct aw_gpio_softc *sc, struct intr_irqsrc *isrc) { u_int irq; uint32_t reg; AW_GPIO_LOCK_ASSERT(sc); irq = ((struct gpio_irqsrc *)isrc)->irq; reg = AW_GPIO_READ(sc, AW_GPIO_GP_INT_CTL(sc->gpio_pic_irqsrc[irq].bank)); reg &= ~(1 << sc->gpio_pic_irqsrc[irq].intnum); AW_GPIO_WRITE(sc, AW_GPIO_GP_INT_CTL(sc->gpio_pic_irqsrc[irq].bank), reg); sc->gpio_pic_irqsrc[irq].enabled = false; } static void aw_gpio_pic_disable_intr(device_t dev, struct intr_irqsrc *isrc) { struct aw_gpio_softc *sc; sc = device_get_softc(dev); AW_GPIO_LOCK(sc); aw_gpio_pic_disable_intr_locked(sc, isrc); AW_GPIO_UNLOCK(sc); } static void aw_gpio_pic_enable_intr(device_t dev, struct intr_irqsrc *isrc) { struct aw_gpio_softc *sc; u_int irq; uint32_t reg; sc = device_get_softc(dev); irq = ((struct gpio_irqsrc *)isrc)->irq; AW_GPIO_LOCK(sc); reg = AW_GPIO_READ(sc, AW_GPIO_GP_INT_CTL(sc->gpio_pic_irqsrc[irq].bank)); reg |= 1 << sc->gpio_pic_irqsrc[irq].intnum; AW_GPIO_WRITE(sc, AW_GPIO_GP_INT_CTL(sc->gpio_pic_irqsrc[irq].bank), reg); AW_GPIO_UNLOCK(sc); sc->gpio_pic_irqsrc[irq].enabled = true; } static int aw_gpio_pic_map_gpio(struct aw_gpio_softc *sc, struct intr_map_data_gpio *dag, u_int *irqp, u_int *mode) { u_int irq; int pin; irq = dag->gpio_pin_num; for (pin = 0; pin < sc->nirqs; pin++) if (sc->gpio_pic_irqsrc[pin].pin == irq) break; if (pin == sc->nirqs) { device_printf(sc->sc_dev, "Invalid interrupt number %u\n", irq); return (EINVAL); } switch (dag->gpio_intr_mode) { case GPIO_INTR_LEVEL_LOW: case GPIO_INTR_LEVEL_HIGH: case GPIO_INTR_EDGE_RISING: case GPIO_INTR_EDGE_FALLING: case GPIO_INTR_EDGE_BOTH: break; default: device_printf(sc->sc_dev, "Unsupported interrupt mode 0x%8x\n", dag->gpio_intr_mode); return (EINVAL); } *irqp = pin; if (mode != NULL) *mode = dag->gpio_intr_mode; return (0); } static int aw_gpio_pic_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { struct aw_gpio_softc *sc; u_int irq; int err; sc = device_get_softc(dev); switch (data->type) { case INTR_MAP_DATA_GPIO: err = aw_gpio_pic_map_gpio(sc, (struct intr_map_data_gpio *)data, &irq, NULL); break; default: return (ENOTSUP); }; if (err == 0) *isrcp = &sc->gpio_pic_irqsrc[irq].isrc; return (0); } static int aw_gpio_pic_setup_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { struct aw_gpio_softc *sc; uint32_t irqcfg; uint32_t pinidx, reg; u_int irq, mode; int err; sc = device_get_softc(dev); err = 0; switch (data->type) { case INTR_MAP_DATA_GPIO: err = aw_gpio_pic_map_gpio(sc, (struct intr_map_data_gpio *)data, &irq, &mode); if (err != 0) return (err); break; default: return (ENOTSUP); }; pinidx = (sc->gpio_pic_irqsrc[irq].intnum % 8) * 4; AW_GPIO_LOCK(sc); switch (mode) { case GPIO_INTR_LEVEL_LOW: irqcfg = AW_GPIO_INT_LEVEL_LOW << pinidx; break; case GPIO_INTR_LEVEL_HIGH: irqcfg = AW_GPIO_INT_LEVEL_HIGH << pinidx; break; case GPIO_INTR_EDGE_RISING: irqcfg = AW_GPIO_INT_EDGE_POSITIVE << pinidx; break; case GPIO_INTR_EDGE_FALLING: irqcfg = AW_GPIO_INT_EDGE_NEGATIVE << pinidx; break; case GPIO_INTR_EDGE_BOTH: irqcfg = AW_GPIO_INT_EDGE_BOTH << pinidx; break; } /* Switch the pin to interrupt mode */ sc->gpio_pic_irqsrc[irq].oldfunc = aw_gpio_get_function(sc, sc->gpio_pic_irqsrc[irq].pin); aw_gpio_set_function(sc, sc->gpio_pic_irqsrc[irq].pin, sc->gpio_pic_irqsrc[irq].intfunc); /* Write interrupt mode */ reg = AW_GPIO_READ(sc, AW_GPIO_GP_INT_CFG(sc->gpio_pic_irqsrc[irq].bank, sc->gpio_pic_irqsrc[irq].intnum)); reg &= ~(0xF << pinidx); reg |= irqcfg; AW_GPIO_WRITE(sc, AW_GPIO_GP_INT_CFG(sc->gpio_pic_irqsrc[irq].bank, sc->gpio_pic_irqsrc[irq].intnum), reg); AW_GPIO_UNLOCK(sc); return (0); } static int aw_gpio_pic_teardown_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { struct aw_gpio_softc *sc; struct gpio_irqsrc *gi; sc = device_get_softc(dev); gi = (struct gpio_irqsrc *)isrc; /* Switch back the pin to it's original function */ AW_GPIO_LOCK(sc); aw_gpio_set_function(sc, gi->pin, gi->oldfunc); AW_GPIO_UNLOCK(sc); return (0); } static void aw_gpio_pic_post_filter(device_t dev, struct intr_irqsrc *isrc) { struct aw_gpio_softc *sc; struct gpio_irqsrc *gi; sc = device_get_softc(dev); gi = (struct gpio_irqsrc *)isrc; arm_irq_memory_barrier(0); AW_GPIO_WRITE(sc, AW_GPIO_GP_INT_STA(gi->bank), 1 << gi->intnum); } static void aw_gpio_pic_post_ithread(device_t dev, struct intr_irqsrc *isrc) { struct aw_gpio_softc *sc; struct gpio_irqsrc *gi; sc = device_get_softc(dev); gi = (struct gpio_irqsrc *)isrc; arm_irq_memory_barrier(0); AW_GPIO_WRITE(sc, AW_GPIO_GP_INT_STA(gi->bank), 1 << gi->intnum); aw_gpio_pic_enable_intr(dev, isrc); } static void aw_gpio_pic_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { struct aw_gpio_softc *sc; sc = device_get_softc(dev); aw_gpio_pic_disable_intr_locked(sc, isrc); } /* * OFWBUS Interface */ static phandle_t aw_gpio_get_node(device_t dev, device_t bus) { /* We only have one child, the GPIO bus, which needs our own node. */ return (ofw_bus_get_node(dev)); } static device_method_t aw_gpio_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aw_gpio_probe), DEVMETHOD(device_attach, aw_gpio_attach), DEVMETHOD(device_detach, aw_gpio_detach), /* Interrupt controller interface */ DEVMETHOD(pic_disable_intr, aw_gpio_pic_disable_intr), DEVMETHOD(pic_enable_intr, aw_gpio_pic_enable_intr), DEVMETHOD(pic_map_intr, aw_gpio_pic_map_intr), DEVMETHOD(pic_setup_intr, aw_gpio_pic_setup_intr), DEVMETHOD(pic_teardown_intr, aw_gpio_pic_teardown_intr), DEVMETHOD(pic_post_filter, aw_gpio_pic_post_filter), DEVMETHOD(pic_post_ithread, aw_gpio_pic_post_ithread), DEVMETHOD(pic_pre_ithread, aw_gpio_pic_pre_ithread), /* GPIO protocol */ DEVMETHOD(gpio_get_bus, aw_gpio_get_bus), DEVMETHOD(gpio_pin_max, aw_gpio_pin_max), DEVMETHOD(gpio_pin_getname, aw_gpio_pin_getname), DEVMETHOD(gpio_pin_getflags, aw_gpio_pin_getflags), DEVMETHOD(gpio_pin_getcaps, aw_gpio_pin_getcaps), DEVMETHOD(gpio_pin_setflags, aw_gpio_pin_setflags), DEVMETHOD(gpio_pin_get, aw_gpio_pin_get), DEVMETHOD(gpio_pin_set, aw_gpio_pin_set), DEVMETHOD(gpio_pin_toggle, aw_gpio_pin_toggle), DEVMETHOD(gpio_pin_access_32, aw_gpio_pin_access_32), DEVMETHOD(gpio_pin_config_32, aw_gpio_pin_config_32), DEVMETHOD(gpio_map_gpios, aw_gpio_map_gpios), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_node, aw_gpio_get_node), /* fdt_pinctrl interface */ DEVMETHOD(fdt_pinctrl_configure,aw_fdt_configure_pins), DEVMETHOD_END }; static driver_t aw_gpio_driver = { "gpio", aw_gpio_methods, sizeof(struct aw_gpio_softc), }; EARLY_DRIVER_MODULE(aw_gpio, simplebus, aw_gpio_driver, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE); diff --git a/sys/arm/broadcom/bcm2835/bcm2835_gpio.c b/sys/arm/broadcom/bcm2835/bcm2835_gpio.c index e4fc57b79ba5..48d1d2af5abc 100644 --- a/sys/arm/broadcom/bcm2835/bcm2835_gpio.c +++ b/sys/arm/broadcom/bcm2835/bcm2835_gpio.c @@ -1,1360 +1,1360 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012 Oleksandr Tymoshenko * Copyright (c) 2012-2015 Luiz Otavio O Souza * 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 #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" #include "pic_if.h" #ifdef DEBUG #define dprintf(fmt, args...) do { printf("%s(): ", __func__); \ printf(fmt,##args); } while (0) #else #define dprintf(fmt, args...) #endif #define BCM_GPIO_IRQS 4 #define BCM_GPIO_PINS_PER_BANK 32 #define BCM2835_GPIO_PINS 54 #define BCM2711_GPIO_PINS 58 #define BCM_GPIO_PINS BCM2711_GPIO_PINS #define BCM_GPIO_DEFAULT_CAPS (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | \ GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN | GPIO_INTR_LEVEL_LOW | \ GPIO_INTR_LEVEL_HIGH | GPIO_INTR_EDGE_RISING | \ GPIO_INTR_EDGE_FALLING | GPIO_INTR_EDGE_BOTH) #define BCM2835_FSEL_GPIO_IN 0 #define BCM2835_FSEL_GPIO_OUT 1 #define BCM2835_FSEL_ALT5 2 #define BCM2835_FSEL_ALT4 3 #define BCM2835_FSEL_ALT0 4 #define BCM2835_FSEL_ALT1 5 #define BCM2835_FSEL_ALT2 6 #define BCM2835_FSEL_ALT3 7 #define BCM2835_PUD_OFF 0 #define BCM2835_PUD_DOWN 1 #define BCM2835_PUD_UP 2 #define BCM2711_PUD_OFF 0 #define BCM2711_PUD_DOWN 2 #define BCM2711_PUD_UP 1 static struct resource_spec bcm_gpio_res_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, /* bank 0 interrupt */ { SYS_RES_IRQ, 1, RF_ACTIVE }, /* bank 1 interrupt */ { -1, 0, 0 } }; struct bcm_gpio_sysctl { struct bcm_gpio_softc *sc; uint32_t pin; }; struct bcm_gpio_irqsrc { struct intr_irqsrc bgi_isrc; uint32_t bgi_irq; uint32_t bgi_mode; uint32_t bgi_mask; }; struct bcm_gpio_softc { device_t sc_dev; device_t sc_busdev; struct mtx sc_mtx; struct resource * sc_res[BCM_GPIO_IRQS + 1]; bus_space_tag_t sc_bst; bus_space_handle_t sc_bsh; void * sc_intrhand[BCM_GPIO_IRQS]; bool sc_is2711; u_int sc_maxpins; int sc_gpio_npins; int sc_ro_npins; int sc_ro_pins[BCM_GPIO_PINS]; struct gpio_pin sc_gpio_pins[BCM_GPIO_PINS]; struct bcm_gpio_sysctl sc_sysctl[BCM_GPIO_PINS]; struct bcm_gpio_irqsrc sc_isrcs[BCM_GPIO_PINS]; }; enum bcm_gpio_pud { BCM_GPIO_NONE, BCM_GPIO_PULLDOWN, BCM_GPIO_PULLUP, }; #define BCM_GPIO_LOCK(_sc) mtx_lock_spin(&(_sc)->sc_mtx) #define BCM_GPIO_UNLOCK(_sc) mtx_unlock_spin(&(_sc)->sc_mtx) #define BCM_GPIO_LOCK_ASSERT(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED) #define BCM_GPIO_WRITE(_sc, _off, _val) \ bus_space_write_4((_sc)->sc_bst, (_sc)->sc_bsh, _off, _val) #define BCM_GPIO_READ(_sc, _off) \ bus_space_read_4((_sc)->sc_bst, (_sc)->sc_bsh, _off) #define BCM_GPIO_CLEAR_BITS(_sc, _off, _bits) \ BCM_GPIO_WRITE(_sc, _off, BCM_GPIO_READ(_sc, _off) & ~(_bits)) #define BCM_GPIO_SET_BITS(_sc, _off, _bits) \ BCM_GPIO_WRITE(_sc, _off, BCM_GPIO_READ(_sc, _off) | _bits) #define BCM_GPIO_BANK(a) (a / BCM_GPIO_PINS_PER_BANK) #define BCM_GPIO_MASK(a) (1U << (a % BCM_GPIO_PINS_PER_BANK)) #define BCM_GPIO_GPFSEL(_bank) (0x00 + _bank * 4) /* Function Select */ #define BCM_GPIO_GPSET(_bank) (0x1c + _bank * 4) /* Pin Out Set */ #define BCM_GPIO_GPCLR(_bank) (0x28 + _bank * 4) /* Pin Out Clear */ #define BCM_GPIO_GPLEV(_bank) (0x34 + _bank * 4) /* Pin Level */ #define BCM_GPIO_GPEDS(_bank) (0x40 + _bank * 4) /* Event Status */ #define BCM_GPIO_GPREN(_bank) (0x4c + _bank * 4) /* Rising Edge irq */ #define BCM_GPIO_GPFEN(_bank) (0x58 + _bank * 4) /* Falling Edge irq */ #define BCM_GPIO_GPHEN(_bank) (0x64 + _bank * 4) /* High Level irq */ #define BCM_GPIO_GPLEN(_bank) (0x70 + _bank * 4) /* Low Level irq */ #define BCM_GPIO_GPAREN(_bank) (0x7c + _bank * 4) /* Async Rising Edge */ #define BCM_GPIO_GPAFEN(_bank) (0x88 + _bank * 4) /* Async Falling Egde */ #define BCM2835_GPIO_GPPUD(_bank) (0x94) /* Pin Pull up/down */ #define BCM2835_GPIO_GPPUDCLK(_bank) (0x98 + _bank * 4) /* Pin Pull up clock */ #define BCM2711_GPIO_GPPUD(x) (0x0e4 + (x) * sizeof(uint32_t)) /* Pin Pull up/down */ #define BCM2711_GPIO_MASK (0x3) #define BCM2711_GPIO_SHIFT(n) (((n) % 16) * 2) #define BCM2711_GPIO_REGID(n) ((n) / 16) static struct ofw_compat_data compat_data[] = { {"broadcom,bcm2835-gpio", 1}, {"brcm,bcm2835-gpio", 1}, {"brcm,bcm2711-gpio", 1}, {NULL, 0} }; static struct bcm_gpio_softc *bcm_gpio_sc = NULL; static int bcm_gpio_intr_bank0(void *arg); static int bcm_gpio_intr_bank1(void *arg); static int bcm_gpio_pic_attach(struct bcm_gpio_softc *sc); static int bcm_gpio_pic_detach(struct bcm_gpio_softc *sc); static int bcm_gpio_pin_is_ro(struct bcm_gpio_softc *sc, int pin) { int i; for (i = 0; i < sc->sc_ro_npins; i++) if (pin == sc->sc_ro_pins[i]) return (1); return (0); } static uint32_t bcm_gpio_get_function(struct bcm_gpio_softc *sc, uint32_t pin) { uint32_t bank, func, offset; /* Five banks, 10 pins per bank, 3 bits per pin. */ bank = pin / 10; offset = (pin - bank * 10) * 3; BCM_GPIO_LOCK(sc); func = (BCM_GPIO_READ(sc, BCM_GPIO_GPFSEL(bank)) >> offset) & 7; BCM_GPIO_UNLOCK(sc); return (func); } static void bcm_gpio_func_str(uint32_t nfunc, char *buf, int bufsize) { switch (nfunc) { case BCM2835_FSEL_GPIO_IN: strncpy(buf, "input", bufsize); break; case BCM2835_FSEL_GPIO_OUT: strncpy(buf, "output", bufsize); break; case BCM2835_FSEL_ALT0: strncpy(buf, "alt0", bufsize); break; case BCM2835_FSEL_ALT1: strncpy(buf, "alt1", bufsize); break; case BCM2835_FSEL_ALT2: strncpy(buf, "alt2", bufsize); break; case BCM2835_FSEL_ALT3: strncpy(buf, "alt3", bufsize); break; case BCM2835_FSEL_ALT4: strncpy(buf, "alt4", bufsize); break; case BCM2835_FSEL_ALT5: strncpy(buf, "alt5", bufsize); break; default: strncpy(buf, "invalid", bufsize); } } static int bcm_gpio_str_func(char *func, uint32_t *nfunc) { if (strcasecmp(func, "input") == 0) *nfunc = BCM2835_FSEL_GPIO_IN; else if (strcasecmp(func, "output") == 0) *nfunc = BCM2835_FSEL_GPIO_OUT; else if (strcasecmp(func, "alt0") == 0) *nfunc = BCM2835_FSEL_ALT0; else if (strcasecmp(func, "alt1") == 0) *nfunc = BCM2835_FSEL_ALT1; else if (strcasecmp(func, "alt2") == 0) *nfunc = BCM2835_FSEL_ALT2; else if (strcasecmp(func, "alt3") == 0) *nfunc = BCM2835_FSEL_ALT3; else if (strcasecmp(func, "alt4") == 0) *nfunc = BCM2835_FSEL_ALT4; else if (strcasecmp(func, "alt5") == 0) *nfunc = BCM2835_FSEL_ALT5; else return (-1); return (0); } static uint32_t bcm_gpio_func_flag(uint32_t nfunc) { switch (nfunc) { case BCM2835_FSEL_GPIO_IN: return (GPIO_PIN_INPUT); case BCM2835_FSEL_GPIO_OUT: return (GPIO_PIN_OUTPUT); } return (0); } static void bcm_gpio_set_function(struct bcm_gpio_softc *sc, uint32_t pin, uint32_t f) { uint32_t bank, data, offset; /* Must be called with lock held. */ BCM_GPIO_LOCK_ASSERT(sc); /* Five banks, 10 pins per bank, 3 bits per pin. */ bank = pin / 10; offset = (pin - bank * 10) * 3; data = BCM_GPIO_READ(sc, BCM_GPIO_GPFSEL(bank)); data &= ~(7 << offset); data |= (f << offset); BCM_GPIO_WRITE(sc, BCM_GPIO_GPFSEL(bank), data); } static void bcm_gpio_set_pud(struct bcm_gpio_softc *sc, uint32_t pin, uint32_t state) { /* Must be called with lock held. */ BCM_GPIO_LOCK_ASSERT(sc); if (sc->sc_is2711) { /* BCM2711 */ u_int regid = BCM2711_GPIO_REGID(pin); u_int shift = BCM2711_GPIO_SHIFT(pin); uint32_t reg; switch (state) { case BCM2835_PUD_OFF: state = BCM2711_PUD_OFF; break; case BCM2835_PUD_DOWN: state = BCM2711_PUD_DOWN; break; case BCM2835_PUD_UP: state = BCM2711_PUD_UP; break; } reg = BCM_GPIO_READ(sc, BCM2711_GPIO_GPPUD(regid)); reg &= ~(BCM2711_GPIO_MASK << shift); reg |= (state << shift); BCM_GPIO_WRITE(sc, BCM2711_GPIO_GPPUD(regid), reg); } else { /* BCM2835 */ uint32_t bank; bank = BCM_GPIO_BANK(pin); BCM_GPIO_WRITE(sc, BCM2835_GPIO_GPPUD(0), state); BCM_GPIO_WRITE(sc, BCM2835_GPIO_GPPUDCLK(bank), BCM_GPIO_MASK(pin)); BCM_GPIO_WRITE(sc, BCM2835_GPIO_GPPUD(0), 0); BCM_GPIO_WRITE(sc, BCM2835_GPIO_GPPUDCLK(bank), 0); } } static void bcm_gpio_set_alternate(device_t dev, uint32_t pin, uint32_t nfunc) { struct bcm_gpio_softc *sc; int i; sc = device_get_softc(dev); BCM_GPIO_LOCK(sc); /* Set the pin function. */ bcm_gpio_set_function(sc, pin, nfunc); /* Update the pin flags. */ for (i = 0; i < sc->sc_gpio_npins; i++) { if (sc->sc_gpio_pins[i].gp_pin == pin) break; } if (i < sc->sc_gpio_npins) sc->sc_gpio_pins[i].gp_flags = bcm_gpio_func_flag(nfunc); BCM_GPIO_UNLOCK(sc); } static void bcm_gpio_pin_configure(struct bcm_gpio_softc *sc, struct gpio_pin *pin, unsigned int flags) { BCM_GPIO_LOCK(sc); /* * Manage input/output. */ if (flags & (GPIO_PIN_INPUT|GPIO_PIN_OUTPUT)) { pin->gp_flags &= ~(GPIO_PIN_INPUT|GPIO_PIN_OUTPUT); if (flags & GPIO_PIN_OUTPUT) { pin->gp_flags |= GPIO_PIN_OUTPUT; bcm_gpio_set_function(sc, pin->gp_pin, BCM2835_FSEL_GPIO_OUT); } else { pin->gp_flags |= GPIO_PIN_INPUT; bcm_gpio_set_function(sc, pin->gp_pin, BCM2835_FSEL_GPIO_IN); } } /* Manage Pull-up/pull-down. */ pin->gp_flags &= ~(GPIO_PIN_PULLUP|GPIO_PIN_PULLDOWN); if (flags & (GPIO_PIN_PULLUP|GPIO_PIN_PULLDOWN)) { if (flags & GPIO_PIN_PULLUP) { pin->gp_flags |= GPIO_PIN_PULLUP; bcm_gpio_set_pud(sc, pin->gp_pin, BCM_GPIO_PULLUP); } else { pin->gp_flags |= GPIO_PIN_PULLDOWN; bcm_gpio_set_pud(sc, pin->gp_pin, BCM_GPIO_PULLDOWN); } } else bcm_gpio_set_pud(sc, pin->gp_pin, BCM_GPIO_NONE); BCM_GPIO_UNLOCK(sc); } static device_t bcm_gpio_get_bus(device_t dev) { struct bcm_gpio_softc *sc; sc = device_get_softc(dev); return (sc->sc_busdev); } static int bcm_gpio_pin_max(device_t dev, int *maxpin) { struct bcm_gpio_softc *sc; sc = device_get_softc(dev); *maxpin = sc->sc_maxpins - 1; return (0); } static int bcm_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { struct bcm_gpio_softc *sc = device_get_softc(dev); int i; for (i = 0; i < sc->sc_gpio_npins; i++) { if (sc->sc_gpio_pins[i].gp_pin == pin) break; } if (i >= sc->sc_gpio_npins) return (EINVAL); BCM_GPIO_LOCK(sc); *caps = sc->sc_gpio_pins[i].gp_caps; BCM_GPIO_UNLOCK(sc); return (0); } static int bcm_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) { struct bcm_gpio_softc *sc = device_get_softc(dev); int i; for (i = 0; i < sc->sc_gpio_npins; i++) { if (sc->sc_gpio_pins[i].gp_pin == pin) break; } if (i >= sc->sc_gpio_npins) return (EINVAL); BCM_GPIO_LOCK(sc); *flags = sc->sc_gpio_pins[i].gp_flags; BCM_GPIO_UNLOCK(sc); return (0); } static int bcm_gpio_pin_getname(device_t dev, uint32_t pin, char *name) { struct bcm_gpio_softc *sc = device_get_softc(dev); int i; for (i = 0; i < sc->sc_gpio_npins; i++) { if (sc->sc_gpio_pins[i].gp_pin == pin) break; } if (i >= sc->sc_gpio_npins) return (EINVAL); BCM_GPIO_LOCK(sc); memcpy(name, sc->sc_gpio_pins[i].gp_name, GPIOMAXNAME); BCM_GPIO_UNLOCK(sc); return (0); } static int bcm_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct bcm_gpio_softc *sc = device_get_softc(dev); int i; for (i = 0; i < sc->sc_gpio_npins; i++) { if (sc->sc_gpio_pins[i].gp_pin == pin) break; } if (i >= sc->sc_gpio_npins) return (EINVAL); /* We never touch on read-only/reserved pins. */ if (bcm_gpio_pin_is_ro(sc, pin)) return (EINVAL); bcm_gpio_pin_configure(sc, &sc->sc_gpio_pins[i], flags); return (0); } static int bcm_gpio_pin_set(device_t dev, uint32_t pin, unsigned int value) { struct bcm_gpio_softc *sc = device_get_softc(dev); uint32_t bank, reg; int i; for (i = 0; i < sc->sc_gpio_npins; i++) { if (sc->sc_gpio_pins[i].gp_pin == pin) break; } if (i >= sc->sc_gpio_npins) return (EINVAL); /* We never write to read-only/reserved pins. */ if (bcm_gpio_pin_is_ro(sc, pin)) return (EINVAL); BCM_GPIO_LOCK(sc); bank = BCM_GPIO_BANK(pin); if (value) reg = BCM_GPIO_GPSET(bank); else reg = BCM_GPIO_GPCLR(bank); BCM_GPIO_WRITE(sc, reg, BCM_GPIO_MASK(pin)); BCM_GPIO_UNLOCK(sc); return (0); } static int bcm_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *val) { struct bcm_gpio_softc *sc = device_get_softc(dev); uint32_t bank, reg_data; int i; for (i = 0; i < sc->sc_gpio_npins; i++) { if (sc->sc_gpio_pins[i].gp_pin == pin) break; } if (i >= sc->sc_gpio_npins) return (EINVAL); bank = BCM_GPIO_BANK(pin); BCM_GPIO_LOCK(sc); reg_data = BCM_GPIO_READ(sc, BCM_GPIO_GPLEV(bank)); BCM_GPIO_UNLOCK(sc); *val = (reg_data & BCM_GPIO_MASK(pin)) ? 1 : 0; return (0); } static int bcm_gpio_pin_toggle(device_t dev, uint32_t pin) { struct bcm_gpio_softc *sc = device_get_softc(dev); uint32_t bank, data, reg; int i; for (i = 0; i < sc->sc_gpio_npins; i++) { if (sc->sc_gpio_pins[i].gp_pin == pin) break; } if (i >= sc->sc_gpio_npins) return (EINVAL); /* We never write to read-only/reserved pins. */ if (bcm_gpio_pin_is_ro(sc, pin)) return (EINVAL); BCM_GPIO_LOCK(sc); bank = BCM_GPIO_BANK(pin); data = BCM_GPIO_READ(sc, BCM_GPIO_GPLEV(bank)); if (data & BCM_GPIO_MASK(pin)) reg = BCM_GPIO_GPCLR(bank); else reg = BCM_GPIO_GPSET(bank); BCM_GPIO_WRITE(sc, reg, BCM_GPIO_MASK(pin)); BCM_GPIO_UNLOCK(sc); return (0); } static int bcm_gpio_func_proc(SYSCTL_HANDLER_ARGS) { char buf[16]; struct bcm_gpio_softc *sc; struct bcm_gpio_sysctl *sc_sysctl; uint32_t nfunc; int error; sc_sysctl = arg1; sc = sc_sysctl->sc; /* Get the current pin function. */ nfunc = bcm_gpio_get_function(sc, sc_sysctl->pin); bcm_gpio_func_str(nfunc, buf, sizeof(buf)); error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); /* Ignore changes on read-only pins. */ if (bcm_gpio_pin_is_ro(sc, sc_sysctl->pin)) return (0); /* Parse the user supplied string and check for a valid pin function. */ if (bcm_gpio_str_func(buf, &nfunc) != 0) return (EINVAL); /* Update the pin alternate function. */ bcm_gpio_set_alternate(sc->sc_dev, sc_sysctl->pin, nfunc); return (0); } static void bcm_gpio_sysctl_init(struct bcm_gpio_softc *sc) { char pinbuf[3]; struct bcm_gpio_sysctl *sc_sysctl; struct sysctl_ctx_list *ctx; struct sysctl_oid *tree_node, *pin_node, *pinN_node; struct sysctl_oid_list *tree, *pin_tree, *pinN_tree; int i; /* * Add per-pin sysctl tree/handlers. */ ctx = device_get_sysctl_ctx(sc->sc_dev); tree_node = device_get_sysctl_tree(sc->sc_dev); tree = SYSCTL_CHILDREN(tree_node); pin_node = SYSCTL_ADD_NODE(ctx, tree, OID_AUTO, "pin", CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "GPIO Pins"); pin_tree = SYSCTL_CHILDREN(pin_node); for (i = 0; i < sc->sc_gpio_npins; i++) { snprintf(pinbuf, sizeof(pinbuf), "%d", i); pinN_node = SYSCTL_ADD_NODE(ctx, pin_tree, OID_AUTO, pinbuf, CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "GPIO Pin"); pinN_tree = SYSCTL_CHILDREN(pinN_node); sc->sc_sysctl[i].sc = sc; sc_sysctl = &sc->sc_sysctl[i]; sc_sysctl->sc = sc; sc_sysctl->pin = sc->sc_gpio_pins[i].gp_pin; SYSCTL_ADD_PROC(ctx, pinN_tree, OID_AUTO, "function", CTLFLAG_RW | CTLTYPE_STRING | CTLFLAG_NEEDGIANT, sc_sysctl, sizeof(struct bcm_gpio_sysctl), bcm_gpio_func_proc, "A", "Pin Function"); } } static int bcm_gpio_get_ro_pins(struct bcm_gpio_softc *sc, phandle_t node, const char *propname, const char *label) { int i, need_comma, npins, range_start, range_stop; pcell_t *pins; /* Get the property data. */ npins = OF_getencprop_alloc_multi(node, propname, sizeof(*pins), (void **)&pins); if (npins < 0) return (-1); if (npins == 0) { OF_prop_free(pins); return (0); } for (i = 0; i < npins; i++) sc->sc_ro_pins[i + sc->sc_ro_npins] = pins[i]; sc->sc_ro_npins += npins; need_comma = 0; device_printf(sc->sc_dev, "%s pins: ", label); range_start = range_stop = pins[0]; for (i = 1; i < npins; i++) { if (pins[i] != range_stop + 1) { if (need_comma) printf(","); if (range_start != range_stop) printf("%d-%d", range_start, range_stop); else printf("%d", range_start); range_start = range_stop = pins[i]; need_comma = 1; } else range_stop++; } if (need_comma) printf(","); if (range_start != range_stop) printf("%d-%d.\n", range_start, range_stop); else printf("%d.\n", range_start); OF_prop_free(pins); return (0); } static int bcm_gpio_get_reserved_pins(struct bcm_gpio_softc *sc) { char *name; phandle_t gpio, node, reserved; ssize_t len; /* Get read-only pins if they're provided */ gpio = ofw_bus_get_node(sc->sc_dev); if (bcm_gpio_get_ro_pins(sc, gpio, "broadcom,read-only", "read-only") != 0) return (0); /* Traverse the GPIO subnodes to find the reserved pins node. */ reserved = 0; node = OF_child(gpio); while ((node != 0) && (reserved == 0)) { len = OF_getprop_alloc(node, "name", (void **)&name); if (len == -1) return (-1); if (strcmp(name, "reserved") == 0) reserved = node; OF_prop_free(name); node = OF_peer(node); } if (reserved == 0) return (-1); /* Get the reserved pins. */ if (bcm_gpio_get_ro_pins(sc, reserved, "broadcom,pins", "reserved") != 0) return (-1); return (0); } static int bcm_gpio_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "BCM2708/2835 GPIO controller"); return (BUS_PROBE_DEFAULT); } static int bcm_gpio_intr_attach(device_t dev) { struct bcm_gpio_softc *sc; /* * Only first two interrupt lines are used. Third line is * mirrored second line and forth line is common for all banks. */ sc = device_get_softc(dev); if (sc->sc_res[1] == NULL || sc->sc_res[2] == NULL) return (-1); if (bcm_gpio_pic_attach(sc) != 0) { device_printf(dev, "unable to attach PIC\n"); return (-1); } if (bus_setup_intr(dev, sc->sc_res[1], INTR_TYPE_MISC | INTR_MPSAFE, bcm_gpio_intr_bank0, NULL, sc, &sc->sc_intrhand[0]) != 0) return (-1); if (bus_setup_intr(dev, sc->sc_res[2], INTR_TYPE_MISC | INTR_MPSAFE, bcm_gpio_intr_bank1, NULL, sc, &sc->sc_intrhand[1]) != 0) return (-1); return (0); } static void bcm_gpio_intr_detach(device_t dev) { struct bcm_gpio_softc *sc; sc = device_get_softc(dev); if (sc->sc_intrhand[0] != NULL) bus_teardown_intr(dev, sc->sc_res[1], sc->sc_intrhand[0]); if (sc->sc_intrhand[1] != NULL) bus_teardown_intr(dev, sc->sc_res[2], sc->sc_intrhand[1]); bcm_gpio_pic_detach(sc); } static int bcm_gpio_attach(device_t dev) { int i, j; phandle_t gpio; struct bcm_gpio_softc *sc; uint32_t func; if (bcm_gpio_sc != NULL) return (ENXIO); bcm_gpio_sc = sc = device_get_softc(dev); sc->sc_dev = dev; mtx_init(&sc->sc_mtx, "bcm gpio", "gpio", MTX_SPIN); if (bus_alloc_resources(dev, bcm_gpio_res_spec, sc->sc_res) != 0) { device_printf(dev, "cannot allocate resources\n"); goto fail; } sc->sc_bst = rman_get_bustag(sc->sc_res[0]); sc->sc_bsh = rman_get_bushandle(sc->sc_res[0]); /* Find our node. */ gpio = ofw_bus_get_node(sc->sc_dev); if (!OF_hasprop(gpio, "gpio-controller")) /* Node is not a GPIO controller. */ goto fail; /* Guess I'm BCM2711 or not. */ sc->sc_is2711 = ofw_bus_node_is_compatible(gpio, "brcm,bcm2711-gpio"); sc->sc_maxpins = sc->sc_is2711 ? BCM2711_GPIO_PINS : BCM2835_GPIO_PINS; /* Setup the GPIO interrupt handler. */ if (bcm_gpio_intr_attach(dev)) { device_printf(dev, "unable to setup the gpio irq handler\n"); goto fail; } /* * Find the read-only pins. These are pins we never touch or bad * things could happen. */ if (bcm_gpio_get_reserved_pins(sc) == -1) goto fail; /* Initialize the software controlled pins. */ for (i = 0, j = 0; j < sc->sc_maxpins; j++) { snprintf(sc->sc_gpio_pins[i].gp_name, GPIOMAXNAME, "pin %d", j); func = bcm_gpio_get_function(sc, j); sc->sc_gpio_pins[i].gp_pin = j; sc->sc_gpio_pins[i].gp_caps = BCM_GPIO_DEFAULT_CAPS; sc->sc_gpio_pins[i].gp_flags = bcm_gpio_func_flag(func); i++; } sc->sc_gpio_npins = i; bcm_gpio_sysctl_init(sc); - sc->sc_busdev = gpiobus_attach_bus(dev); - if (sc->sc_busdev == NULL) - goto fail; fdt_pinctrl_register(dev, "brcm,pins"); fdt_pinctrl_configure_tree(dev); + sc->sc_busdev = gpiobus_attach_bus(dev); + if (sc->sc_busdev == NULL) + goto fail; return (0); fail: bcm_gpio_intr_detach(dev); bus_release_resources(dev, bcm_gpio_res_spec, sc->sc_res); mtx_destroy(&sc->sc_mtx); return (ENXIO); } static int bcm_gpio_detach(device_t dev) { return (EBUSY); } static inline void bcm_gpio_modify(struct bcm_gpio_softc *sc, uint32_t reg, uint32_t mask, bool set_bits) { if (set_bits) BCM_GPIO_SET_BITS(sc, reg, mask); else BCM_GPIO_CLEAR_BITS(sc, reg, mask); } static inline void bcm_gpio_isrc_eoi(struct bcm_gpio_softc *sc, struct bcm_gpio_irqsrc *bgi) { uint32_t bank; /* Write 1 to clear. */ bank = BCM_GPIO_BANK(bgi->bgi_irq); BCM_GPIO_WRITE(sc, BCM_GPIO_GPEDS(bank), bgi->bgi_mask); } static inline bool bcm_gpio_isrc_is_level(struct bcm_gpio_irqsrc *bgi) { return (bgi->bgi_mode == GPIO_INTR_LEVEL_LOW || bgi->bgi_mode == GPIO_INTR_LEVEL_HIGH); } static inline void bcm_gpio_isrc_mask(struct bcm_gpio_softc *sc, struct bcm_gpio_irqsrc *bgi) { uint32_t bank; bank = BCM_GPIO_BANK(bgi->bgi_irq); BCM_GPIO_LOCK(sc); switch (bgi->bgi_mode) { case GPIO_INTR_LEVEL_LOW: BCM_GPIO_CLEAR_BITS(sc, BCM_GPIO_GPLEN(bank), bgi->bgi_mask); break; case GPIO_INTR_LEVEL_HIGH: BCM_GPIO_CLEAR_BITS(sc, BCM_GPIO_GPHEN(bank), bgi->bgi_mask); break; case GPIO_INTR_EDGE_RISING: BCM_GPIO_CLEAR_BITS(sc, BCM_GPIO_GPREN(bank), bgi->bgi_mask); break; case GPIO_INTR_EDGE_FALLING: BCM_GPIO_CLEAR_BITS(sc, BCM_GPIO_GPFEN(bank), bgi->bgi_mask); break; case GPIO_INTR_EDGE_BOTH: BCM_GPIO_CLEAR_BITS(sc, BCM_GPIO_GPREN(bank), bgi->bgi_mask); BCM_GPIO_CLEAR_BITS(sc, BCM_GPIO_GPFEN(bank), bgi->bgi_mask); break; } BCM_GPIO_UNLOCK(sc); } static inline void bcm_gpio_isrc_unmask(struct bcm_gpio_softc *sc, struct bcm_gpio_irqsrc *bgi) { uint32_t bank; bank = BCM_GPIO_BANK(bgi->bgi_irq); BCM_GPIO_LOCK(sc); switch (bgi->bgi_mode) { case GPIO_INTR_LEVEL_LOW: BCM_GPIO_SET_BITS(sc, BCM_GPIO_GPLEN(bank), bgi->bgi_mask); break; case GPIO_INTR_LEVEL_HIGH: BCM_GPIO_SET_BITS(sc, BCM_GPIO_GPHEN(bank), bgi->bgi_mask); break; case GPIO_INTR_EDGE_RISING: BCM_GPIO_SET_BITS(sc, BCM_GPIO_GPREN(bank), bgi->bgi_mask); break; case GPIO_INTR_EDGE_FALLING: BCM_GPIO_SET_BITS(sc, BCM_GPIO_GPFEN(bank), bgi->bgi_mask); break; case GPIO_INTR_EDGE_BOTH: BCM_GPIO_SET_BITS(sc, BCM_GPIO_GPREN(bank), bgi->bgi_mask); BCM_GPIO_SET_BITS(sc, BCM_GPIO_GPFEN(bank), bgi->bgi_mask); break; } BCM_GPIO_UNLOCK(sc); } static int bcm_gpio_intr_internal(struct bcm_gpio_softc *sc, uint32_t bank) { u_int irq; struct bcm_gpio_irqsrc *bgi; uint32_t reg; /* Do not care of spurious interrupt on GPIO. */ reg = BCM_GPIO_READ(sc, BCM_GPIO_GPEDS(bank)); while (reg != 0) { irq = BCM_GPIO_PINS_PER_BANK * bank + ffs(reg) - 1; bgi = sc->sc_isrcs + irq; if (!bcm_gpio_isrc_is_level(bgi)) bcm_gpio_isrc_eoi(sc, bgi); if (intr_isrc_dispatch(&bgi->bgi_isrc, curthread->td_intr_frame) != 0) { bcm_gpio_isrc_mask(sc, bgi); if (bcm_gpio_isrc_is_level(bgi)) bcm_gpio_isrc_eoi(sc, bgi); device_printf(sc->sc_dev, "Stray irq %u disabled\n", irq); } reg &= ~bgi->bgi_mask; } return (FILTER_HANDLED); } static int bcm_gpio_intr_bank0(void *arg) { return (bcm_gpio_intr_internal(arg, 0)); } static int bcm_gpio_intr_bank1(void *arg) { return (bcm_gpio_intr_internal(arg, 1)); } static int bcm_gpio_pic_attach(struct bcm_gpio_softc *sc) { int error; uint32_t irq; const char *name; name = device_get_nameunit(sc->sc_dev); for (irq = 0; irq < sc->sc_maxpins; irq++) { sc->sc_isrcs[irq].bgi_irq = irq; sc->sc_isrcs[irq].bgi_mask = BCM_GPIO_MASK(irq); sc->sc_isrcs[irq].bgi_mode = GPIO_INTR_CONFORM; error = intr_isrc_register(&sc->sc_isrcs[irq].bgi_isrc, sc->sc_dev, 0, "%s,%u", name, irq); if (error != 0) return (error); /* XXX deregister ISRCs */ } if (intr_pic_register(sc->sc_dev, OF_xref_from_node(ofw_bus_get_node(sc->sc_dev))) == NULL) return (ENXIO); return (0); } static int bcm_gpio_pic_detach(struct bcm_gpio_softc *sc) { /* * There has not been established any procedure yet * how to detach PIC from living system correctly. */ device_printf(sc->sc_dev, "%s: not implemented yet\n", __func__); return (EBUSY); } static void bcm_gpio_pic_config_intr(struct bcm_gpio_softc *sc, struct bcm_gpio_irqsrc *bgi, uint32_t mode) { uint32_t bank; bank = BCM_GPIO_BANK(bgi->bgi_irq); BCM_GPIO_LOCK(sc); bcm_gpio_modify(sc, BCM_GPIO_GPREN(bank), bgi->bgi_mask, mode == GPIO_INTR_EDGE_RISING || mode == GPIO_INTR_EDGE_BOTH); bcm_gpio_modify(sc, BCM_GPIO_GPFEN(bank), bgi->bgi_mask, mode == GPIO_INTR_EDGE_FALLING || mode == GPIO_INTR_EDGE_BOTH); bcm_gpio_modify(sc, BCM_GPIO_GPHEN(bank), bgi->bgi_mask, mode == GPIO_INTR_LEVEL_HIGH); bcm_gpio_modify(sc, BCM_GPIO_GPLEN(bank), bgi->bgi_mask, mode == GPIO_INTR_LEVEL_LOW); bgi->bgi_mode = mode; BCM_GPIO_UNLOCK(sc); } static void bcm_gpio_pic_disable_intr(device_t dev, struct intr_irqsrc *isrc) { struct bcm_gpio_softc *sc = device_get_softc(dev); struct bcm_gpio_irqsrc *bgi = (struct bcm_gpio_irqsrc *)isrc; bcm_gpio_isrc_mask(sc, bgi); } static void bcm_gpio_pic_enable_intr(device_t dev, struct intr_irqsrc *isrc) { struct bcm_gpio_softc *sc = device_get_softc(dev); struct bcm_gpio_irqsrc *bgi = (struct bcm_gpio_irqsrc *)isrc; arm_irq_memory_barrier(bgi->bgi_irq); bcm_gpio_isrc_unmask(sc, bgi); } static int bcm_gpio_pic_map_fdt(struct bcm_gpio_softc *sc, struct intr_map_data_fdt *daf, u_int *irqp, uint32_t *modep) { u_int irq; uint32_t mode; /* * The first cell is the interrupt number. * The second cell is used to specify flags: * bits[3:0] trigger type and level flags: * 1 = low-to-high edge triggered. * 2 = high-to-low edge triggered. * 4 = active high level-sensitive. * 8 = active low level-sensitive. */ if (daf->ncells != 2) return (EINVAL); irq = daf->cells[0]; if (irq >= sc->sc_maxpins || bcm_gpio_pin_is_ro(sc, irq)) return (EINVAL); /* Only reasonable modes are supported. */ if (daf->cells[1] == 1) mode = GPIO_INTR_EDGE_RISING; else if (daf->cells[1] == 2) mode = GPIO_INTR_EDGE_FALLING; else if (daf->cells[1] == 3) mode = GPIO_INTR_EDGE_BOTH; else if (daf->cells[1] == 4) mode = GPIO_INTR_LEVEL_HIGH; else if (daf->cells[1] == 8) mode = GPIO_INTR_LEVEL_LOW; else return (EINVAL); *irqp = irq; if (modep != NULL) *modep = mode; return (0); } static int bcm_gpio_pic_map_gpio(struct bcm_gpio_softc *sc, struct intr_map_data_gpio *dag, u_int *irqp, uint32_t *modep) { u_int irq; uint32_t mode; irq = dag->gpio_pin_num; if (irq >= sc->sc_maxpins || bcm_gpio_pin_is_ro(sc, irq)) return (EINVAL); mode = dag->gpio_intr_mode; if (mode != GPIO_INTR_LEVEL_LOW && mode != GPIO_INTR_LEVEL_HIGH && mode != GPIO_INTR_EDGE_RISING && mode != GPIO_INTR_EDGE_FALLING && mode != GPIO_INTR_EDGE_BOTH) return (EINVAL); *irqp = irq; if (modep != NULL) *modep = mode; return (0); } static int bcm_gpio_pic_map(struct bcm_gpio_softc *sc, struct intr_map_data *data, u_int *irqp, uint32_t *modep) { switch (data->type) { case INTR_MAP_DATA_FDT: return (bcm_gpio_pic_map_fdt(sc, (struct intr_map_data_fdt *)data, irqp, modep)); case INTR_MAP_DATA_GPIO: return (bcm_gpio_pic_map_gpio(sc, (struct intr_map_data_gpio *)data, irqp, modep)); default: return (ENOTSUP); } } static int bcm_gpio_pic_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { int error; u_int irq; struct bcm_gpio_softc *sc = device_get_softc(dev); error = bcm_gpio_pic_map(sc, data, &irq, NULL); if (error == 0) *isrcp = &sc->sc_isrcs[irq].bgi_isrc; return (error); } static void bcm_gpio_pic_post_filter(device_t dev, struct intr_irqsrc *isrc) { struct bcm_gpio_softc *sc = device_get_softc(dev); struct bcm_gpio_irqsrc *bgi = (struct bcm_gpio_irqsrc *)isrc; if (bcm_gpio_isrc_is_level(bgi)) bcm_gpio_isrc_eoi(sc, bgi); } static void bcm_gpio_pic_post_ithread(device_t dev, struct intr_irqsrc *isrc) { bcm_gpio_pic_enable_intr(dev, isrc); } static void bcm_gpio_pic_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { struct bcm_gpio_softc *sc = device_get_softc(dev); struct bcm_gpio_irqsrc *bgi = (struct bcm_gpio_irqsrc *)isrc; bcm_gpio_isrc_mask(sc, bgi); if (bcm_gpio_isrc_is_level(bgi)) bcm_gpio_isrc_eoi(sc, bgi); } static int bcm_gpio_pic_setup_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { u_int irq; uint32_t mode; struct bcm_gpio_softc *sc; struct bcm_gpio_irqsrc *bgi; if (data == NULL) return (ENOTSUP); sc = device_get_softc(dev); bgi = (struct bcm_gpio_irqsrc *)isrc; /* Get and check config for an interrupt. */ if (bcm_gpio_pic_map(sc, data, &irq, &mode) != 0 || bgi->bgi_irq != irq) return (EINVAL); /* * If this is a setup for another handler, * only check that its configuration match. */ if (isrc->isrc_handlers != 0) return (bgi->bgi_mode == mode ? 0 : EINVAL); bcm_gpio_pic_config_intr(sc, bgi, mode); return (0); } static int bcm_gpio_pic_teardown_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { struct bcm_gpio_softc *sc = device_get_softc(dev); struct bcm_gpio_irqsrc *bgi = (struct bcm_gpio_irqsrc *)isrc; if (isrc->isrc_handlers == 0) bcm_gpio_pic_config_intr(sc, bgi, GPIO_INTR_CONFORM); return (0); } static phandle_t bcm_gpio_get_node(device_t bus, device_t dev) { /* We only have one child, the GPIO bus, which needs our own node. */ return (ofw_bus_get_node(bus)); } static int bcm_gpio_configure_pins(device_t dev, phandle_t cfgxref) { phandle_t cfgnode; int i, pintuples, pulltuples; uint32_t pin; uint32_t *pins; uint32_t *pulls; uint32_t function; cfgnode = OF_node_from_xref(cfgxref); pins = NULL; pintuples = OF_getencprop_alloc_multi(cfgnode, "brcm,pins", sizeof(*pins), (void **)&pins); char name[32]; OF_getprop(cfgnode, "name", &name, sizeof(name)); if (pintuples < 0) return (ENOENT); if (pintuples == 0) return (0); /* Empty property is not an error. */ if (OF_getencprop(cfgnode, "brcm,function", &function, sizeof(function)) <= 0) { OF_prop_free(pins); return (EINVAL); } pulls = NULL; pulltuples = OF_getencprop_alloc_multi(cfgnode, "brcm,pull", sizeof(*pulls), (void **)&pulls); if ((pulls != NULL) && (pulltuples != pintuples)) { OF_prop_free(pins); OF_prop_free(pulls); return (EINVAL); } for (i = 0; i < pintuples; i++) { pin = pins[i]; bcm_gpio_set_alternate(dev, pin, function); if (bootverbose) device_printf(dev, "set pin %d to func %d", pin, function); if (pulls) { if (bootverbose) printf(", pull %d", pulls[i]); switch (pulls[i]) { /* Convert to gpio(4) flags */ case BCM2835_PUD_OFF: bcm_gpio_pin_setflags(dev, pin, 0); break; case BCM2835_PUD_UP: bcm_gpio_pin_setflags(dev, pin, GPIO_PIN_PULLUP); break; case BCM2835_PUD_DOWN: bcm_gpio_pin_setflags(dev, pin, GPIO_PIN_PULLDOWN); break; default: printf("%s: invalid pull value for pin %d: %d\n", name, pin, pulls[i]); } } if (bootverbose) printf("\n"); } OF_prop_free(pins); if (pulls) OF_prop_free(pulls); return (0); } static device_method_t bcm_gpio_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bcm_gpio_probe), DEVMETHOD(device_attach, bcm_gpio_attach), DEVMETHOD(device_detach, bcm_gpio_detach), /* GPIO protocol */ DEVMETHOD(gpio_get_bus, bcm_gpio_get_bus), DEVMETHOD(gpio_pin_max, bcm_gpio_pin_max), DEVMETHOD(gpio_pin_getname, bcm_gpio_pin_getname), DEVMETHOD(gpio_pin_getflags, bcm_gpio_pin_getflags), DEVMETHOD(gpio_pin_getcaps, bcm_gpio_pin_getcaps), DEVMETHOD(gpio_pin_setflags, bcm_gpio_pin_setflags), DEVMETHOD(gpio_pin_get, bcm_gpio_pin_get), DEVMETHOD(gpio_pin_set, bcm_gpio_pin_set), DEVMETHOD(gpio_pin_toggle, bcm_gpio_pin_toggle), /* Interrupt controller interface */ DEVMETHOD(pic_disable_intr, bcm_gpio_pic_disable_intr), DEVMETHOD(pic_enable_intr, bcm_gpio_pic_enable_intr), DEVMETHOD(pic_map_intr, bcm_gpio_pic_map_intr), DEVMETHOD(pic_post_filter, bcm_gpio_pic_post_filter), DEVMETHOD(pic_post_ithread, bcm_gpio_pic_post_ithread), DEVMETHOD(pic_pre_ithread, bcm_gpio_pic_pre_ithread), DEVMETHOD(pic_setup_intr, bcm_gpio_pic_setup_intr), DEVMETHOD(pic_teardown_intr, bcm_gpio_pic_teardown_intr), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_node, bcm_gpio_get_node), /* fdt_pinctrl interface */ DEVMETHOD(fdt_pinctrl_configure, bcm_gpio_configure_pins), DEVMETHOD_END }; static driver_t bcm_gpio_driver = { "gpio", bcm_gpio_methods, sizeof(struct bcm_gpio_softc), }; EARLY_DRIVER_MODULE(bcm_gpio, simplebus, bcm_gpio_driver, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE); diff --git a/sys/arm/nvidia/as3722_gpio.c b/sys/arm/nvidia/as3722_gpio.c index f7ae3c99d0f4..0f823ce3e0c1 100644 --- a/sys/arm/nvidia/as3722_gpio.c +++ b/sys/arm/nvidia/as3722_gpio.c @@ -1,568 +1,568 @@ /*- * 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 #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", (void **)pins); if (*lpins <= 0) return (ENOENT); /* Read function (mux) settings. */ rv = OF_getprop_alloc(node, "function", (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 (!ofw_bus_node_status_okay(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) { int flags; 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 |= mode << 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); + sc->gpio_busdev = gpiobus_add_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); } diff --git a/sys/arm64/nvidia/tegra210/max77620_gpio.c b/sys/arm64/nvidia/tegra210/max77620_gpio.c index 0021aab151de..b60d62a3e0c3 100644 --- a/sys/arm64/nvidia/tegra210/max77620_gpio.c +++ b/sys/arm64/nvidia/tegra210/max77620_gpio.c @@ -1,713 +1,713 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright 2020 Michal Meloun * * 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 #include #include #include #include #include #include #include #include #include #include #include "max77620.h" MALLOC_DEFINE(M_MAX77620_GPIO, "MAX77620 gpio", "MAX77620 GPIO"); #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) enum prop_id { CFG_BIAS_PULL_UP, CFG_BIAS_PULL_DOWN, CFG_OPEN_DRAIN, CFG_PUSH_PULL, CFG_ACTIVE_FPS_SRC, CFG_ACTIVE_PWRUP_SLOT, CFG_ACTIVE_PWRDOWN_SLOT, CFG_SUSPEND_FPS_SRC, CFG_SUSPEND_PWRUP_SLOT, CFG_SUSPEND_PWRDOWN_SLOT, PROP_ID_MAX_ID }; static const struct { const char *name; enum prop_id id; } max77620_prop_names[] = { {"bias-pull-up", CFG_BIAS_PULL_UP}, {"bias-pull-down", CFG_BIAS_PULL_DOWN}, {"drive-open-drain", CFG_OPEN_DRAIN}, {"drive-push-pull", CFG_PUSH_PULL}, {"maxim,active-fps-source", CFG_ACTIVE_FPS_SRC}, {"maxim,active-fps-power-up-slot", CFG_ACTIVE_PWRUP_SLOT}, {"maxim,active-fps-power-down-slot", CFG_ACTIVE_PWRDOWN_SLOT}, {"maxim,suspend-fps-source", CFG_SUSPEND_FPS_SRC}, {"maxim,suspend-fps-power-up-slot", CFG_SUSPEND_PWRUP_SLOT}, {"maxim,suspend-fps-power-down-slot", CFG_SUSPEND_PWRDOWN_SLOT}, }; /* Configuration for one pin group. */ struct max77620_pincfg { bool alt_func; int params[PROP_ID_MAX_ID]; }; static char *altfnc_table[] = { "lpm-control-in", "fps-out", "32k-out1", "sd0-dvs-in", "sd1-dvs-in", "reference-out", }; struct max77620_gpio_pin { int pin_caps; char pin_name[GPIOMAXNAME]; uint8_t reg; /* Runtime data */ bool alt_func; /* GPIO or alternate function */ }; /* -------------------------------------------------------------------------- * * Pinmux functions. */ static int max77620_pinmux_get_function(struct max77620_softc *sc, char *name, struct max77620_pincfg *cfg) { int i; if (strcmp("gpio", name) == 0) { cfg->alt_func = false; return (0); } for (i = 0; i < nitems(altfnc_table); i++) { if (strcmp(altfnc_table[i], name) == 0) { cfg->alt_func = true; return (0); } } return (-1); } static int max77620_pinmux_set_fps(struct max77620_softc *sc, int pin_num, struct max77620_gpio_pin *pin) { #if 0 struct max77620_fps_config *fps_config = &mpci->fps_config[pin]; int addr, ret; int param_val; int mask, shift; if ((pin < 1) || (pin > 3)) return (0); switch (param) { case MAX77620_ACTIVE_FPS_SOURCE: case MAX77620_SUSPEND_FPS_SOURCE: mask = MAX77620_FPS_SRC_MASK; shift = MAX77620_FPS_SRC_SHIFT; param_val = fps_config->active_fps_src; if (param == MAX77620_SUSPEND_FPS_SOURCE) param_val = fps_config->suspend_fps_src; break; case MAX77620_ACTIVE_FPS_POWER_ON_SLOTS: case MAX77620_SUSPEND_FPS_POWER_ON_SLOTS: mask = MAX77620_FPS_PU_PERIOD_MASK; shift = MAX77620_FPS_PU_PERIOD_SHIFT; param_val = fps_config->active_power_up_slots; if (param == MAX77620_SUSPEND_FPS_POWER_ON_SLOTS) param_val = fps_config->suspend_power_up_slots; break; case MAX77620_ACTIVE_FPS_POWER_DOWN_SLOTS: case MAX77620_SUSPEND_FPS_POWER_DOWN_SLOTS: mask = MAX77620_FPS_PD_PERIOD_MASK; shift = MAX77620_FPS_PD_PERIOD_SHIFT; param_val = fps_config->active_power_down_slots; if (param == MAX77620_SUSPEND_FPS_POWER_DOWN_SLOTS) param_val = fps_config->suspend_power_down_slots; break; default: dev_err(mpci->dev, "Invalid parameter %d for pin %d\n", param, pin); return -EINVAL; } if (param_val < 0) return 0; ret = regmap_update_bits(mpci->rmap, addr, mask, param_val << shift); if (ret < 0) dev_err(mpci->dev, "Reg 0x%02x update failed %d\n", addr, ret); return ret; #endif return (0); } static int max77620_pinmux_config_node(struct max77620_softc *sc, char *pin_name, struct max77620_pincfg *cfg) { struct max77620_gpio_pin *pin; uint8_t reg; int pin_num, rv; for (pin_num = 0; pin_num < sc->gpio_npins; pin_num++) { if (strcmp(sc->gpio_pins[pin_num]->pin_name, pin_name) == 0) break; } if (pin_num >= sc->gpio_npins) { device_printf(sc->dev, "Unknown pin: %s\n", pin_name); return (ENXIO); } pin = sc->gpio_pins[pin_num]; rv = max77620_pinmux_set_fps(sc, pin_num, pin); if (rv != 0) return (rv); rv = RD1(sc, pin->reg, ®); if (rv != 0) { device_printf(sc->dev, "Cannot read GIPO_CFG register\n"); return (ENXIO); } if (cfg->alt_func) { pin->alt_func = true; sc->gpio_reg_ame |= 1 << pin_num; } else { pin->alt_func = false; sc->gpio_reg_ame &= ~(1 << pin_num); } /* Pull up/down. */ switch (cfg->params[CFG_BIAS_PULL_UP]) { case 1: sc->gpio_reg_pue |= 1 << pin_num; break; case 0: sc->gpio_reg_pue &= ~(1 << pin_num); break; default: break; } switch (cfg->params[CFG_BIAS_PULL_DOWN]) { case 1: sc->gpio_reg_pde |= 1 << pin_num; break; case 0: sc->gpio_reg_pde &= ~(1 << pin_num); break; default: break; } /* Open drain/push-pull modes. */ if (cfg->params[CFG_OPEN_DRAIN] == 1) { reg &= ~MAX77620_REG_GPIO_DRV(~0); reg |= MAX77620_REG_GPIO_DRV(MAX77620_REG_GPIO_DRV_OPENDRAIN); } if (cfg->params[CFG_PUSH_PULL] == 1) { reg &= ~MAX77620_REG_GPIO_DRV(~0); reg |= MAX77620_REG_GPIO_DRV(MAX77620_REG_GPIO_DRV_PUSHPULL); } rv = WR1(sc, pin->reg, reg); if (rv != 0) { device_printf(sc->dev, "Cannot read GIPO_CFG register\n"); return (ENXIO); } return (0); } static int max77620_pinmux_read_node(struct max77620_softc *sc, phandle_t node, struct max77620_pincfg *cfg, char **pins, int *lpins) { char *function; int rv, i; *lpins = OF_getprop_alloc(node, "pins", (void **)pins); if (*lpins <= 0) return (ENOENT); /* Read function (mux) settings. */ rv = OF_getprop_alloc(node, "function", (void **)&function); if (rv > 0) { rv = max77620_pinmux_get_function(sc, function, cfg); if (rv == -1) { device_printf(sc->dev, "Unknown function %s\n", function); OF_prop_free(function); return (ENXIO); } } /* Read numeric properties. */ for (i = 0; i < PROP_ID_MAX_ID; i++) { rv = OF_getencprop(node, max77620_prop_names[i].name, &cfg->params[i], sizeof(cfg->params[i])); if (rv <= 0) cfg->params[i] = -1; } OF_prop_free(function); return (0); } static int max77620_pinmux_process_node(struct max77620_softc *sc, phandle_t node) { struct max77620_pincfg cfg; char *pins, *pname; int i, len, lpins, rv; rv = max77620_pinmux_read_node(sc, node, &cfg, &pins, &lpins); if (rv != 0) return (rv); len = 0; pname = pins; do { i = strlen(pname) + 1; rv = max77620_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); return (rv); } int max77620_pinmux_configure(device_t dev, phandle_t cfgxref) { struct max77620_softc *sc; phandle_t node, cfgnode; uint8_t old_reg_pue, old_reg_pde, old_reg_ame; int rv; sc = device_get_softc(dev); cfgnode = OF_node_from_xref(cfgxref); old_reg_pue = sc->gpio_reg_pue; old_reg_pde = sc->gpio_reg_pde; old_reg_ame = sc->gpio_reg_ame; for (node = OF_child(cfgnode); node != 0; node = OF_peer(node)) { if (!ofw_bus_node_status_okay(node)) continue; rv = max77620_pinmux_process_node(sc, node); if (rv != 0) device_printf(dev, "Failed to process pinmux"); } if (old_reg_pue != sc->gpio_reg_pue) { rv = WR1(sc, MAX77620_REG_PUE_GPIO, sc->gpio_reg_pue); if (rv != 0) { device_printf(sc->dev, "Cannot update PUE_GPIO register\n"); return (ENXIO); } } if (old_reg_pde != sc->gpio_reg_pde) { rv = WR1(sc, MAX77620_REG_PDE_GPIO, sc->gpio_reg_pde); if (rv != 0) { device_printf(sc->dev, "Cannot update PDE_GPIO register\n"); return (ENXIO); } } if (old_reg_ame != sc->gpio_reg_ame) { rv = WR1(sc, MAX77620_REG_AME_GPIO, sc->gpio_reg_ame); if (rv != 0) { device_printf(sc->dev, "Cannot update PDE_GPIO register\n"); return (ENXIO); } } return (0); } /* -------------------------------------------------------------------------- * * GPIO */ device_t max77620_gpio_get_bus(device_t dev) { struct max77620_softc *sc; sc = device_get_softc(dev); return (sc->gpio_busdev); } int max77620_gpio_pin_max(device_t dev, int *maxpin) { *maxpin = NGPIO - 1; return (0); } int max77620_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { struct max77620_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 max77620_gpio_pin_getname(device_t dev, uint32_t pin, char *name) { struct max77620_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); } static int max77620_gpio_get_mode(struct max77620_softc *sc, uint32_t pin_num, uint32_t *out_flags) { struct max77620_gpio_pin *pin; uint8_t reg; int rv; pin = sc->gpio_pins[pin_num]; *out_flags = 0; rv = RD1(sc, pin->reg, ®); if (rv != 0) { device_printf(sc->dev, "Cannot read GIPO_CFG register\n"); return (ENXIO); } /* Pin function */ pin->alt_func = sc->gpio_reg_ame & (1 << pin_num); /* Pull up/down. */ if (sc->gpio_reg_pue & (1 << pin_num)) *out_flags |= GPIO_PIN_PULLUP; if (sc->gpio_reg_pde & (1 << pin_num)) *out_flags |= GPIO_PIN_PULLDOWN; /* Open drain/push-pull modes. */ if (MAX77620_REG_GPIO_DRV_GET(reg) == MAX77620_REG_GPIO_DRV_PUSHPULL) *out_flags |= GPIO_PIN_PUSHPULL; else *out_flags |= GPIO_PIN_OPENDRAIN; /* Input/output modes. */ if (MAX77620_REG_GPIO_DRV_GET(reg) == MAX77620_REG_GPIO_DRV_PUSHPULL) *out_flags |= GPIO_PIN_OUTPUT; else *out_flags |= GPIO_PIN_OUTPUT | GPIO_PIN_INPUT; return (0); } int max77620_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *out_flags) { struct max77620_softc *sc; int rv; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); GPIO_LOCK(sc); #if 0 /* It colide with GPIO regulators */ /* Is pin in GPIO mode ? */ if (sc->gpio_pins[pin]->alt_func) { GPIO_UNLOCK(sc); return (ENXIO); } #endif rv = max77620_gpio_get_mode(sc, pin, out_flags); GPIO_UNLOCK(sc); return (rv); } int max77620_gpio_pin_setflags(device_t dev, uint32_t pin_num, uint32_t flags) { struct max77620_softc *sc; struct max77620_gpio_pin *pin; uint8_t reg; uint8_t old_reg_pue, old_reg_pde; int rv; sc = device_get_softc(dev); if (pin_num >= sc->gpio_npins) return (EINVAL); pin = sc->gpio_pins[pin_num]; GPIO_LOCK(sc); #if 0 /* It colide with GPIO regulators */ /* Is pin in GPIO mode ? */ if (pin->alt_func) { GPIO_UNLOCK(sc); return (ENXIO); } #endif old_reg_pue = sc->gpio_reg_pue; old_reg_pde = sc->gpio_reg_pde; rv = RD1(sc, pin->reg, ®); if (rv != 0) { device_printf(sc->dev, "Cannot read GIPO_CFG register\n"); GPIO_UNLOCK(sc); return (ENXIO); } if (flags & GPIO_PIN_PULLUP) sc->gpio_reg_pue |= 1 << pin_num; else sc->gpio_reg_pue &= ~(1 << pin_num); if (flags & GPIO_PIN_PULLDOWN) sc->gpio_reg_pde |= 1 << pin_num; else sc->gpio_reg_pde &= ~(1 << pin_num); if (flags & GPIO_PIN_INPUT) { reg &= ~MAX77620_REG_GPIO_DRV(~0); reg |= MAX77620_REG_GPIO_DRV(MAX77620_REG_GPIO_DRV_OPENDRAIN); reg &= ~MAX77620_REG_GPIO_OUTPUT_VAL(~0); reg |= MAX77620_REG_GPIO_OUTPUT_VAL(1); } else if (((flags & GPIO_PIN_OUTPUT) && (flags & GPIO_PIN_OPENDRAIN) == 0) || (flags & GPIO_PIN_PUSHPULL)) { reg &= ~MAX77620_REG_GPIO_DRV(~0); reg |= MAX77620_REG_GPIO_DRV(MAX77620_REG_GPIO_DRV_PUSHPULL); } else { reg &= ~MAX77620_REG_GPIO_DRV(~0); reg |= MAX77620_REG_GPIO_DRV(MAX77620_REG_GPIO_DRV_OPENDRAIN); } rv = WR1(sc, pin->reg, reg); if (rv != 0) { device_printf(sc->dev, "Cannot read GIPO_CFG register\n"); return (ENXIO); } if (old_reg_pue != sc->gpio_reg_pue) { rv = WR1(sc, MAX77620_REG_PUE_GPIO, sc->gpio_reg_pue); if (rv != 0) { device_printf(sc->dev, "Cannot update PUE_GPIO register\n"); GPIO_UNLOCK(sc); return (ENXIO); } } if (old_reg_pde != sc->gpio_reg_pde) { rv = WR1(sc, MAX77620_REG_PDE_GPIO, sc->gpio_reg_pde); if (rv != 0) { device_printf(sc->dev, "Cannot update PDE_GPIO register\n"); GPIO_UNLOCK(sc); return (ENXIO); } } GPIO_UNLOCK(sc); return (0); } int max77620_gpio_pin_set(device_t dev, uint32_t pin, uint32_t val) { struct max77620_softc *sc; int rv; sc = device_get_softc(dev); if (pin >= sc->gpio_npins) return (EINVAL); GPIO_LOCK(sc); rv = RM1(sc, sc->gpio_pins[pin]->reg, MAX77620_REG_GPIO_OUTPUT_VAL(~0), MAX77620_REG_GPIO_OUTPUT_VAL(val)); GPIO_UNLOCK(sc); return (rv); } int max77620_gpio_pin_get(device_t dev, uint32_t pin, uint32_t *val) { struct max77620_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, sc->gpio_pins[pin]->reg, &tmp); if (MAX77620_REG_GPIO_DRV_GET(tmp) == MAX77620_REG_GPIO_DRV_PUSHPULL) *val = MAX77620_REG_GPIO_OUTPUT_VAL_GET(tmp); else *val = MAX77620_REG_GPIO_INPUT_VAL_GET(tmp); GPIO_UNLOCK(sc); if (rv != 0) return (rv); return (0); } int max77620_gpio_pin_toggle(device_t dev, uint32_t pin) { struct max77620_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, sc->gpio_pins[pin]->reg, &tmp); if (rv != 0) { GPIO_UNLOCK(sc); return (rv); } tmp ^= MAX77620_REG_GPIO_OUTPUT_VAL(~0); rv = RM1(sc, sc->gpio_pins[pin]->reg, MAX77620_REG_GPIO_OUTPUT_VAL(~0), tmp); GPIO_UNLOCK(sc); return (0); } int max77620_gpio_map_gpios(device_t dev, phandle_t pdev, phandle_t gparent, int gcells, pcell_t *gpios, uint32_t *pin, uint32_t *flags) { if (gcells != 2) return (ERANGE); *pin = gpios[0]; *flags= gpios[1]; return (0); } int max77620_gpio_attach(struct max77620_softc *sc, phandle_t node) { struct max77620_gpio_pin *pin; int i, rv; sx_init(&sc->gpio_lock, "MAX77620 GPIO lock"); - sc->gpio_busdev = gpiobus_attach_bus(sc->dev); + sc->gpio_busdev = gpiobus_add_bus(sc->dev); if (sc->gpio_busdev == NULL) return (ENXIO); rv = RD1(sc, MAX77620_REG_PUE_GPIO, &sc->gpio_reg_pue); if (rv != 0) { device_printf(sc->dev, "Cannot read PUE_GPIO register\n"); return (ENXIO); } rv = RD1(sc, MAX77620_REG_PDE_GPIO, &sc->gpio_reg_pde); if (rv != 0) { device_printf(sc->dev, "Cannot read PDE_GPIO register\n"); return (ENXIO); } rv = RD1(sc, MAX77620_REG_AME_GPIO, &sc->gpio_reg_ame); if (rv != 0) { device_printf(sc->dev, "Cannot read AME_GPIO register\n"); return (ENXIO); } sc->gpio_npins = NGPIO; sc->gpio_pins = malloc(sizeof(struct max77620_gpio_pin *) * sc->gpio_npins, M_MAX77620_GPIO, M_WAITOK | M_ZERO); for (i = 0; i < sc->gpio_npins; i++) { sc->gpio_pins[i] = malloc(sizeof(struct max77620_gpio_pin), M_MAX77620_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_PULLUP | GPIO_PIN_PULLDOWN; pin->reg = MAX77620_REG_GPIO0 + i; } return (0); } diff --git a/sys/arm64/rockchip/rk_gpio.c b/sys/arm64/rockchip/rk_gpio.c index b1175963a5e4..5a96ea2d0cb4 100644 --- a/sys/arm64/rockchip/rk_gpio.c +++ b/sys/arm64/rockchip/rk_gpio.c @@ -1,820 +1,820 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2018 Emmanuel Vadot * Copyright (c) 2021 Soren Schmidt * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" #include "pic_if.h" #include "fdt_pinctrl_if.h" enum gpio_regs { RK_GPIO_SWPORTA_DR = 1, /* Data register */ RK_GPIO_SWPORTA_DDR, /* Data direction register */ RK_GPIO_INTEN, /* Interrupt enable register */ RK_GPIO_INTMASK, /* Interrupt mask register */ RK_GPIO_INTTYPE_LEVEL, /* Interrupt level register */ RK_GPIO_INTTYPE_BOTH, /* Both rise and falling edge */ RK_GPIO_INT_POLARITY, /* Interrupt polarity register */ RK_GPIO_INT_STATUS, /* Interrupt status register */ RK_GPIO_INT_RAWSTATUS, /* Raw Interrupt status register */ RK_GPIO_DEBOUNCE, /* Debounce enable register */ RK_GPIO_PORTA_EOI, /* Clear interrupt register */ RK_GPIO_EXT_PORTA, /* External port register */ RK_GPIO_REGNUM }; #define RK_GPIO_LS_SYNC 0x60 /* Level sensitive syncronization enable register */ #define RK_GPIO_DEFAULT_CAPS (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | \ GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN | GPIO_INTR_EDGE_BOTH | \ GPIO_INTR_EDGE_RISING | GPIO_INTR_EDGE_FALLING | \ GPIO_INTR_LEVEL_HIGH | GPIO_INTR_LEVEL_LOW) #define GPIO_FLAGS_PINCTRL (GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN) #define RK_GPIO_MAX_PINS 32 struct pin_cached { uint8_t is_gpio; uint32_t flags; }; struct rk_pin_irqsrc { struct intr_irqsrc isrc; uint32_t irq; uint32_t mode; }; struct rk_gpio_softc { device_t sc_dev; device_t sc_busdev; struct mtx sc_mtx; struct resource *sc_res[2]; bus_space_tag_t sc_bst; bus_space_handle_t sc_bsh; clk_t clk; device_t pinctrl; uint32_t swporta; uint32_t swporta_ddr; uint32_t version; struct pin_cached pin_cached[RK_GPIO_MAX_PINS]; uint8_t regs[RK_GPIO_REGNUM]; void *ihandle; struct rk_pin_irqsrc isrcs[RK_GPIO_MAX_PINS]; }; static struct ofw_compat_data compat_data[] = { {"rockchip,gpio-bank", 1}, {NULL, 0} }; static struct resource_spec rk_gpio_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, { -1, 0 } }; #define RK_GPIO_VERSION 0x78 #define RK_GPIO_TYPE_V1 0x00000000 #define RK_GPIO_TYPE_V2 0x01000c2b #define RK_GPIO_ISRC(sc, irq) (&(sc->isrcs[irq].isrc)) static int rk_gpio_detach(device_t dev); #define RK_GPIO_LOCK(_sc) mtx_lock_spin(&(_sc)->sc_mtx) #define RK_GPIO_UNLOCK(_sc) mtx_unlock_spin(&(_sc)->sc_mtx) #define RK_GPIO_LOCK_ASSERT(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED) #define RK_GPIO_WRITE(_sc, _off, _val) \ bus_space_write_4(_sc->sc_bst, _sc->sc_bsh, _off, _val) #define RK_GPIO_READ(_sc, _off) \ bus_space_read_4(_sc->sc_bst, _sc->sc_bsh, _off) static int rk_gpio_read_bit(struct rk_gpio_softc *sc, int reg, int bit) { int offset = sc->regs[reg]; uint32_t value; if (sc->version == RK_GPIO_TYPE_V1) { value = RK_GPIO_READ(sc, offset); value >>= bit; } else { value = RK_GPIO_READ(sc, bit > 15 ? offset + 4 : offset); value >>= (bit % 16); } return (value & 1); } static void rk_gpio_write_bit(struct rk_gpio_softc *sc, int reg, int bit, int data) { int offset = sc->regs[reg]; uint32_t value; if (sc->version == RK_GPIO_TYPE_V1) { value = RK_GPIO_READ(sc, offset); if (data) value |= (1 << bit); else value &= ~(1 << bit); RK_GPIO_WRITE(sc, offset, value); } else { if (data) value = (1 << (bit % 16)); else value = 0; value |= (1 << ((bit % 16) + 16)); RK_GPIO_WRITE(sc, bit > 15 ? offset + 4 : offset, value); } } static uint32_t rk_gpio_read_4(struct rk_gpio_softc *sc, int reg) { int offset = sc->regs[reg]; uint32_t value; if (sc->version == RK_GPIO_TYPE_V1) value = RK_GPIO_READ(sc, offset); else value = (RK_GPIO_READ(sc, offset) & 0xffff) | (RK_GPIO_READ(sc, offset + 4) << 16); return (value); } static void rk_gpio_write_4(struct rk_gpio_softc *sc, int reg, uint32_t value) { int offset = sc->regs[reg]; if (sc->version == RK_GPIO_TYPE_V1) RK_GPIO_WRITE(sc, offset, value); else { RK_GPIO_WRITE(sc, offset, (value & 0xffff) | 0xffff0000); RK_GPIO_WRITE(sc, offset + 4, (value >> 16) | 0xffff0000); } } static int rk_gpio_intr(void *arg) { struct rk_gpio_softc *sc = (struct rk_gpio_softc *)arg;; struct trapframe *tf = curthread->td_intr_frame; uint32_t status; RK_GPIO_LOCK(sc); status = rk_gpio_read_4(sc, RK_GPIO_INT_STATUS); rk_gpio_write_4(sc, RK_GPIO_PORTA_EOI, status); RK_GPIO_UNLOCK(sc); while (status) { int pin = ffs(status) - 1; status &= ~(1 << pin); if (intr_isrc_dispatch(RK_GPIO_ISRC(sc, pin), tf)) { device_printf(sc->sc_dev, "Interrupt pin=%d unhandled\n", pin); continue; } if ((sc->version == RK_GPIO_TYPE_V1) && (sc->isrcs[pin].mode & GPIO_INTR_EDGE_BOTH)) { RK_GPIO_LOCK(sc); if (rk_gpio_read_bit(sc, RK_GPIO_EXT_PORTA, pin)) rk_gpio_write_bit(sc, RK_GPIO_INT_POLARITY, (1 << pin), 0); else rk_gpio_write_bit(sc, RK_GPIO_INT_POLARITY, (1 << pin), 1); RK_GPIO_UNLOCK(sc); } } return (FILTER_HANDLED); } static int rk_gpio_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "RockChip GPIO Bank controller"); return (BUS_PROBE_DEFAULT); } static int rk_gpio_attach(device_t dev) { struct rk_gpio_softc *sc; phandle_t parent_node, node; int err, i; sc = device_get_softc(dev); sc->sc_dev = dev; sc->pinctrl = device_get_parent(dev); parent_node = ofw_bus_get_node(sc->pinctrl); node = ofw_bus_get_node(sc->sc_dev); if (!OF_hasprop(node, "gpio-controller")) return (ENXIO); mtx_init(&sc->sc_mtx, "rk gpio", "gpio", MTX_SPIN); if (bus_alloc_resources(dev, rk_gpio_spec, sc->sc_res)) { device_printf(dev, "could not allocate resources\n"); bus_release_resources(dev, rk_gpio_spec, sc->sc_res); mtx_destroy(&sc->sc_mtx); return (ENXIO); } sc->sc_bst = rman_get_bustag(sc->sc_res[0]); sc->sc_bsh = rman_get_bushandle(sc->sc_res[0]); if (clk_get_by_ofw_index(dev, 0, 0, &sc->clk) != 0) { device_printf(dev, "Cannot get clock\n"); rk_gpio_detach(dev); return (ENXIO); } err = clk_enable(sc->clk); if (err != 0) { device_printf(dev, "Could not enable clock %s\n", clk_get_name(sc->clk)); rk_gpio_detach(dev); return (ENXIO); } if ((err = bus_setup_intr(dev, sc->sc_res[1], INTR_TYPE_MISC | INTR_MPSAFE, rk_gpio_intr, NULL, sc, &sc->ihandle))) { device_printf(dev, "Can not setup IRQ\n"); rk_gpio_detach(dev); return (ENXIO); } /* * RK3568 has GPIO_VER_ID register, however both * RK3328 and RK3399 doesn't have. So choose the * version based on parent's compat string. */ if (ofw_bus_node_is_compatible(parent_node, "rockchip,rk3568-pinctrl")) sc->version = RK_GPIO_TYPE_V2; else sc->version = RK_GPIO_TYPE_V1; switch (sc->version) { case RK_GPIO_TYPE_V1: sc->regs[RK_GPIO_SWPORTA_DR] = 0x00; sc->regs[RK_GPIO_SWPORTA_DDR] = 0x04; sc->regs[RK_GPIO_INTEN] = 0x30; sc->regs[RK_GPIO_INTMASK] = 0x34; sc->regs[RK_GPIO_INTTYPE_LEVEL] = 0x38; sc->regs[RK_GPIO_INT_POLARITY] = 0x3c; sc->regs[RK_GPIO_INT_STATUS] = 0x40; sc->regs[RK_GPIO_INT_RAWSTATUS] = 0x44; sc->regs[RK_GPIO_DEBOUNCE] = 0x48; sc->regs[RK_GPIO_PORTA_EOI] = 0x4c; sc->regs[RK_GPIO_EXT_PORTA] = 0x50; break; case RK_GPIO_TYPE_V2: sc->regs[RK_GPIO_SWPORTA_DR] = 0x00; sc->regs[RK_GPIO_SWPORTA_DDR] = 0x08; sc->regs[RK_GPIO_INTEN] = 0x10; sc->regs[RK_GPIO_INTMASK] = 0x18; sc->regs[RK_GPIO_INTTYPE_LEVEL] = 0x20; sc->regs[RK_GPIO_INTTYPE_BOTH] = 0x30; sc->regs[RK_GPIO_INT_POLARITY] = 0x28; sc->regs[RK_GPIO_INT_STATUS] = 0x50; sc->regs[RK_GPIO_INT_RAWSTATUS] = 0x58; sc->regs[RK_GPIO_DEBOUNCE] = 0x38; sc->regs[RK_GPIO_PORTA_EOI] = 0x60; sc->regs[RK_GPIO_EXT_PORTA] = 0x70; break; default: device_printf(dev, "Unknown gpio version %08x\n", sc->version); rk_gpio_detach(dev); return (ENXIO); } for (i = 0; i < RK_GPIO_MAX_PINS; i++) { sc->isrcs[i].irq = i; sc->isrcs[i].mode = GPIO_INTR_CONFORM; if ((err = intr_isrc_register(RK_GPIO_ISRC(sc, i), dev, 0, "%s", device_get_nameunit(dev)))) { device_printf(dev, "Can not register isrc %d\n", err); rk_gpio_detach(dev); return (ENXIO); } } if (intr_pic_register(dev, OF_xref_from_node(node)) == NULL) { device_printf(dev, "Can not register pic\n"); rk_gpio_detach(dev); return (ENXIO); } - sc->sc_busdev = gpiobus_attach_bus(dev); - if (sc->sc_busdev == NULL) { - rk_gpio_detach(dev); - return (ENXIO); - } - /* Set the cached value to unknown */ for (i = 0; i < RK_GPIO_MAX_PINS; i++) sc->pin_cached[i].is_gpio = 2; RK_GPIO_LOCK(sc); sc->swporta = rk_gpio_read_4(sc, RK_GPIO_SWPORTA_DR); sc->swporta_ddr = rk_gpio_read_4(sc, RK_GPIO_SWPORTA_DDR); RK_GPIO_UNLOCK(sc); + sc->sc_busdev = gpiobus_attach_bus(dev); + if (sc->sc_busdev == NULL) { + rk_gpio_detach(dev); + return (ENXIO); + } + return (0); } static int rk_gpio_detach(device_t dev) { struct rk_gpio_softc *sc; sc = device_get_softc(dev); if (sc->sc_busdev) gpiobus_detach_bus(dev); bus_release_resources(dev, rk_gpio_spec, sc->sc_res); mtx_destroy(&sc->sc_mtx); clk_disable(sc->clk); return(0); } static device_t rk_gpio_get_bus(device_t dev) { struct rk_gpio_softc *sc; sc = device_get_softc(dev); return (sc->sc_busdev); } static int rk_gpio_pin_max(device_t dev, int *maxpin) { /* Each bank have always 32 pins */ /* XXX not true*/ *maxpin = 31; return (0); } static int rk_gpio_pin_getname(device_t dev, uint32_t pin, char *name) { struct rk_gpio_softc *sc; uint32_t bank; sc = device_get_softc(dev); if (pin >= 32) return (EINVAL); bank = pin / 8; pin = pin - (bank * 8); RK_GPIO_LOCK(sc); snprintf(name, GPIOMAXNAME, "P%c%d", bank + 'A', pin); RK_GPIO_UNLOCK(sc); return (0); } static int rk_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) { struct rk_gpio_softc *sc; int rv; sc = device_get_softc(dev); if (__predict_false(sc->pin_cached[pin].is_gpio != 1)) { rv = FDT_PINCTRL_IS_GPIO(sc->pinctrl, dev, pin, (bool *)&sc->pin_cached[pin].is_gpio); if (rv != 0) return (rv); if (sc->pin_cached[pin].is_gpio == 0) return (EINVAL); } *flags = 0; rv = FDT_PINCTRL_GET_FLAGS(sc->pinctrl, dev, pin, flags); if (rv != 0) return (rv); sc->pin_cached[pin].flags = *flags; if (sc->swporta_ddr & (1 << pin)) *flags |= GPIO_PIN_OUTPUT; else *flags |= GPIO_PIN_INPUT; return (0); } static int rk_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { if (pin >= RK_GPIO_MAX_PINS) return EINVAL; *caps = RK_GPIO_DEFAULT_CAPS; return (0); } static int rk_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct rk_gpio_softc *sc; int rv; sc = device_get_softc(dev); if (pin >= RK_GPIO_MAX_PINS) return (EINVAL); if (__predict_false(sc->pin_cached[pin].is_gpio != 1)) { rv = FDT_PINCTRL_IS_GPIO(sc->pinctrl, dev, pin, (bool *)&sc->pin_cached[pin].is_gpio); if (rv != 0) return (rv); if (sc->pin_cached[pin].is_gpio == 0) return (EINVAL); } if (__predict_false((flags & GPIO_PIN_INPUT) && ((flags & GPIO_FLAGS_PINCTRL) != sc->pin_cached[pin].flags))) { rv = FDT_PINCTRL_SET_FLAGS(sc->pinctrl, dev, pin, flags); sc->pin_cached[pin].flags = flags & GPIO_FLAGS_PINCTRL; if (rv != 0) return (rv); } RK_GPIO_LOCK(sc); if (flags & GPIO_PIN_INPUT) sc->swporta_ddr &= ~(1 << pin); else if (flags & GPIO_PIN_OUTPUT) sc->swporta_ddr |= (1 << pin); rk_gpio_write_4(sc, RK_GPIO_SWPORTA_DDR, sc->swporta_ddr); RK_GPIO_UNLOCK(sc); return (0); } static int rk_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *val) { struct rk_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= RK_GPIO_MAX_PINS) return (EINVAL); RK_GPIO_LOCK(sc); *val = rk_gpio_read_bit(sc, RK_GPIO_EXT_PORTA, pin); RK_GPIO_UNLOCK(sc); return (0); } static int rk_gpio_pin_set(device_t dev, uint32_t pin, unsigned int value) { struct rk_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= RK_GPIO_MAX_PINS) return (EINVAL); RK_GPIO_LOCK(sc); if (value) sc->swporta |= (1 << pin); else sc->swporta &= ~(1 << pin); rk_gpio_write_4(sc, RK_GPIO_SWPORTA_DR, sc->swporta); RK_GPIO_UNLOCK(sc); return (0); } static int rk_gpio_pin_toggle(device_t dev, uint32_t pin) { struct rk_gpio_softc *sc; sc = device_get_softc(dev); if (pin >= RK_GPIO_MAX_PINS) return (EINVAL); RK_GPIO_LOCK(sc); if (sc->swporta & (1 << pin)) sc->swporta &= ~(1 << pin); else sc->swporta |= (1 << pin); rk_gpio_write_4(sc, RK_GPIO_SWPORTA_DR, sc->swporta); RK_GPIO_UNLOCK(sc); return (0); } static int rk_gpio_pin_access_32(device_t dev, uint32_t first_pin, uint32_t clear_pins, uint32_t change_pins, uint32_t *orig_pins) { struct rk_gpio_softc *sc; uint32_t reg; sc = device_get_softc(dev); RK_GPIO_LOCK(sc); reg = rk_gpio_read_4(sc, RK_GPIO_SWPORTA_DR); if (orig_pins) *orig_pins = reg; sc->swporta = reg; if ((clear_pins | change_pins) != 0) { reg = (reg & ~clear_pins) ^ change_pins; rk_gpio_write_4(sc, RK_GPIO_SWPORTA_DR, reg); } RK_GPIO_UNLOCK(sc); return (0); } static int rk_gpio_pin_config_32(device_t dev, uint32_t first_pin, uint32_t num_pins, uint32_t *pin_flags) { struct rk_gpio_softc *sc; uint32_t reg, set, mask, flags; int i; sc = device_get_softc(dev); if (first_pin != 0 || num_pins > 32) return (EINVAL); set = 0; mask = 0; for (i = 0; i < num_pins; i++) { mask = (mask << 1) | 1; flags = pin_flags[i]; if (flags & GPIO_PIN_INPUT) { set &= ~(1 << i); } else if (flags & GPIO_PIN_OUTPUT) { set |= (1 << i); } } RK_GPIO_LOCK(sc); reg = rk_gpio_read_4(sc, RK_GPIO_SWPORTA_DDR); reg &= ~mask; reg |= set; rk_gpio_write_4(sc, RK_GPIO_SWPORTA_DDR, reg); sc->swporta_ddr = reg; RK_GPIO_UNLOCK(sc); return (0); } static int rk_gpio_map_gpios(device_t bus, phandle_t dev, phandle_t gparent, int gcells, pcell_t *gpios, uint32_t *pin, uint32_t *flags) { /* The gpios are mapped as */ *pin = gpios[0]; *flags = gpios[1]; return (0); } static phandle_t rk_gpio_get_node(device_t bus, device_t dev) { /* We only have one child, the GPIO bus, which needs our own node. */ return (ofw_bus_get_node(bus)); } static int rk_pic_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { struct rk_gpio_softc *sc = device_get_softc(dev); struct intr_map_data_gpio *gdata; uint32_t irq; if (data->type != INTR_MAP_DATA_GPIO) { device_printf(dev, "Wrong type\n"); return (ENOTSUP); } gdata = (struct intr_map_data_gpio *)data; irq = gdata->gpio_pin_num; if (irq >= RK_GPIO_MAX_PINS) { device_printf(dev, "Invalid interrupt %u\n", irq); return (EINVAL); } *isrcp = RK_GPIO_ISRC(sc, irq); return (0); } static int rk_pic_setup_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { struct rk_gpio_softc *sc = device_get_softc(dev); struct rk_pin_irqsrc *rkisrc = (struct rk_pin_irqsrc *)isrc; struct intr_map_data_gpio *gdata; uint32_t mode; uint8_t pin; if (!data) { device_printf(dev, "No map data\n"); return (ENOTSUP); } gdata = (struct intr_map_data_gpio *)data; mode = gdata->gpio_intr_mode; pin = gdata->gpio_pin_num; if (rkisrc->irq != gdata->gpio_pin_num) { device_printf(dev, "Interrupts don't match\n"); return (EINVAL); } if (isrc->isrc_handlers != 0) { device_printf(dev, "Handler already attached\n"); return (rkisrc->mode == mode ? 0 : EINVAL); } rkisrc->mode = mode; RK_GPIO_LOCK(sc); switch (mode & GPIO_INTR_MASK) { case GPIO_INTR_EDGE_RISING: rk_gpio_write_bit(sc, RK_GPIO_SWPORTA_DDR, pin, 0); rk_gpio_write_bit(sc, RK_GPIO_INTTYPE_LEVEL, pin, 1); rk_gpio_write_bit(sc, RK_GPIO_INT_POLARITY, pin, 1); break; case GPIO_INTR_EDGE_FALLING: rk_gpio_write_bit(sc, RK_GPIO_SWPORTA_DDR, pin, 0); rk_gpio_write_bit(sc, RK_GPIO_INTTYPE_LEVEL, pin, 1); rk_gpio_write_bit(sc, RK_GPIO_INT_POLARITY, pin, 0); break; case GPIO_INTR_EDGE_BOTH: rk_gpio_write_bit(sc, RK_GPIO_SWPORTA_DDR, pin, 0); rk_gpio_write_bit(sc, RK_GPIO_INTTYPE_LEVEL, pin, 1); if (sc->version == RK_GPIO_TYPE_V1) { if (rk_gpio_read_bit(sc, RK_GPIO_EXT_PORTA, pin)) rk_gpio_write_bit(sc, RK_GPIO_INT_POLARITY, pin, 0); else rk_gpio_write_bit(sc, RK_GPIO_INT_POLARITY, pin, 1); } else rk_gpio_write_bit(sc, RK_GPIO_INTTYPE_BOTH, pin, 1); break; case GPIO_INTR_LEVEL_HIGH: rk_gpio_write_bit(sc, RK_GPIO_SWPORTA_DDR, pin, 0); rk_gpio_write_bit(sc, RK_GPIO_INTTYPE_LEVEL, pin, 0); rk_gpio_write_bit(sc, RK_GPIO_INT_POLARITY, pin, 1); break; case GPIO_INTR_LEVEL_LOW: rk_gpio_write_bit(sc, RK_GPIO_SWPORTA_DDR, pin, 0); rk_gpio_write_bit(sc, RK_GPIO_INTTYPE_LEVEL, pin, 0); rk_gpio_write_bit(sc, RK_GPIO_INT_POLARITY, pin, 0); break; default: rk_gpio_write_bit(sc, RK_GPIO_INTMASK, pin, 1); rk_gpio_write_bit(sc, RK_GPIO_INTEN, pin, 0); RK_GPIO_UNLOCK(sc); return (EINVAL); } rk_gpio_write_bit(sc, RK_GPIO_DEBOUNCE, pin, 1); rk_gpio_write_bit(sc, RK_GPIO_INTMASK, pin, 0); rk_gpio_write_bit(sc, RK_GPIO_INTEN, pin, 1); RK_GPIO_UNLOCK(sc); return (0); } static int rk_pic_teardown_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { struct rk_gpio_softc *sc = device_get_softc(dev); struct rk_pin_irqsrc *irqsrc; irqsrc = (struct rk_pin_irqsrc *)isrc; if (isrc->isrc_handlers == 0) { irqsrc->mode = GPIO_INTR_CONFORM; RK_GPIO_LOCK(sc); rk_gpio_write_bit(sc, RK_GPIO_INTEN, irqsrc->irq, 0); rk_gpio_write_bit(sc, RK_GPIO_INTMASK, irqsrc->irq, 0); rk_gpio_write_bit(sc, RK_GPIO_DEBOUNCE, irqsrc->irq, 0); RK_GPIO_UNLOCK(sc); } return (0); } static device_method_t rk_gpio_methods[] = { /* Device interface */ DEVMETHOD(device_probe, rk_gpio_probe), DEVMETHOD(device_attach, rk_gpio_attach), DEVMETHOD(device_detach, rk_gpio_detach), /* GPIO protocol */ DEVMETHOD(gpio_get_bus, rk_gpio_get_bus), DEVMETHOD(gpio_pin_max, rk_gpio_pin_max), DEVMETHOD(gpio_pin_getname, rk_gpio_pin_getname), DEVMETHOD(gpio_pin_getflags, rk_gpio_pin_getflags), DEVMETHOD(gpio_pin_getcaps, rk_gpio_pin_getcaps), DEVMETHOD(gpio_pin_setflags, rk_gpio_pin_setflags), DEVMETHOD(gpio_pin_get, rk_gpio_pin_get), DEVMETHOD(gpio_pin_set, rk_gpio_pin_set), DEVMETHOD(gpio_pin_toggle, rk_gpio_pin_toggle), DEVMETHOD(gpio_pin_access_32, rk_gpio_pin_access_32), DEVMETHOD(gpio_pin_config_32, rk_gpio_pin_config_32), DEVMETHOD(gpio_map_gpios, rk_gpio_map_gpios), /* Interrupt controller interface */ DEVMETHOD(pic_map_intr, rk_pic_map_intr), DEVMETHOD(pic_setup_intr, rk_pic_setup_intr), DEVMETHOD(pic_teardown_intr, rk_pic_teardown_intr), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_node, rk_gpio_get_node), DEVMETHOD_END }; static driver_t rk_gpio_driver = { "gpio", rk_gpio_methods, sizeof(struct rk_gpio_softc), }; /* * GPIO driver is always a child of rk_pinctrl driver and should be probed * and attached within rk_pinctrl_attach function. Due to this, bus pass order * must be same as bus pass order of rk_pinctrl driver. */ EARLY_DRIVER_MODULE(rk_gpio, simplebus, rk_gpio_driver, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); diff --git a/sys/dev/gpio/pl061.c b/sys/dev/gpio/pl061.c index 1a12710ababa..1d7a1439ea76 100644 --- a/sys/dev/gpio/pl061.c +++ b/sys/dev/gpio/pl061.c @@ -1,577 +1,585 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020 Amazon.com, Inc. or its affiliates. * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pl061.h" #include "gpio_if.h" #include "pic_if.h" #define PL061_LOCK(_sc) mtx_lock_spin(&(_sc)->sc_mtx) #define PL061_UNLOCK(_sc) mtx_unlock_spin(&(_sc)->sc_mtx) #define PL061_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->sc_mtx) #define PL061_ASSERT_LOCKED(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED) #define PL061_ASSERT_UNLOCKED(_sc) mtx_assert(&(_sc)->sc_mtx, MA_NOTOWNED) #if 0 #define dprintf(fmt, args...) do { \ printf(fmt, ##args); \ } while (0) #else #define dprintf(fmt, args...) #endif #define PL061_PIN_TO_ADDR(pin) (1 << (pin + 2)) #define PL061_DATA 0x3FC #define PL061_DIR 0x400 #define PL061_INTSENSE 0x404 #define PL061_INTBOTHEDGES 0x408 #define PL061_INTEVENT 0x40C #define PL061_INTMASK 0x410 #define PL061_RAWSTATUS 0x414 #define PL061_STATUS 0x418 #define PL061_INTCLR 0x41C #define PL061_MODECTRL 0x420 #define PL061_ALLOWED_CAPS (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | GPIO_INTR_EDGE_BOTH | \ GPIO_INTR_EDGE_RISING | GPIO_INTR_EDGE_FALLING | \ GPIO_INTR_LEVEL_HIGH | GPIO_INTR_LEVEL_LOW ) #define PIC_INTR_ISRC(sc, irq) (&(sc->sc_isrcs[irq].isrc)) static device_t pl061_get_bus(device_t dev) { struct pl061_softc *sc; sc = device_get_softc(dev); return (sc->sc_busdev); } static int pl061_pin_max(device_t dev, int *maxpin) { *maxpin = PL061_NUM_GPIO - 1; return (0); } static int pl061_pin_getname(device_t dev, uint32_t pin, char *name) { if (pin >= PL061_NUM_GPIO) return (EINVAL); snprintf(name, GPIOMAXNAME, "p%u", pin); name[GPIOMAXNAME - 1] = '\0'; return (0); } static int pl061_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) { struct pl061_softc *sc; uint8_t mask = 1 << pin; sc = device_get_softc(dev); if (pin >= PL061_NUM_GPIO) return (EINVAL); PL061_LOCK(sc); *flags = 0; if (mask & bus_read_1(sc->sc_mem_res, PL061_DIR)) *flags |= GPIO_PIN_OUTPUT; else *flags |= GPIO_PIN_INPUT; PL061_UNLOCK(sc); return (0); } static int pl061_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { if (pin >= PL061_NUM_GPIO) return (EINVAL); *caps = PL061_ALLOWED_CAPS; return (0); } static void mask_and_set(struct pl061_softc *sc, long a, uint8_t m, uint8_t b) { uint8_t tmp; tmp = bus_read_1(sc->sc_mem_res, a); tmp &= ~m; tmp |= b; bus_write_1(sc->sc_mem_res, a, tmp); dprintf("%s: writing %#x to register %#lx\n", __func__, tmp, a); } static int pl061_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct pl061_softc *sc; uint8_t mask = 1 << pin; const uint32_t in_out = (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT); sc = device_get_softc(dev); if (pin >= PL061_NUM_GPIO) return (EINVAL); if (flags & ~PL061_ALLOWED_CAPS) return (EINVAL); /* can't be both input and output */ if ((flags & in_out) == in_out) return (EINVAL); PL061_LOCK(sc); mask_and_set(sc, PL061_DIR, mask, flags & GPIO_PIN_OUTPUT ? mask : 0); PL061_UNLOCK(sc); return (0); } static int pl061_pin_get(device_t dev, uint32_t pin, uint32_t *value) { struct pl061_softc *sc; sc = device_get_softc(dev); if (pin >= PL061_NUM_GPIO) return (EINVAL); PL061_LOCK(sc); if (bus_read_1(sc->sc_mem_res, PL061_PIN_TO_ADDR(pin))) *value = GPIO_PIN_HIGH; else *value = GPIO_PIN_LOW; PL061_UNLOCK(sc); return (0); } static int pl061_pin_set(device_t dev, uint32_t pin, uint32_t value) { struct pl061_softc *sc; uint8_t d = (value == GPIO_PIN_HIGH) ? 0xff : 0x00; sc = device_get_softc(dev); if (pin >= PL061_NUM_GPIO) return (EINVAL); PL061_LOCK(sc); bus_write_1(sc->sc_mem_res, PL061_PIN_TO_ADDR(pin), d); PL061_UNLOCK(sc); return (0); } static int pl061_pin_toggle(device_t dev, uint32_t pin) { struct pl061_softc *sc; uint8_t d; sc = device_get_softc(dev); if (pin >= PL061_NUM_GPIO) return (EINVAL); PL061_LOCK(sc); d = ~bus_read_1(sc->sc_mem_res, PL061_PIN_TO_ADDR(pin)); bus_write_1(sc->sc_mem_res, PL061_PIN_TO_ADDR(pin), d); PL061_UNLOCK(sc); return (0); } static void pl061_pic_disable_intr(device_t dev, struct intr_irqsrc *isrc) { struct pl061_softc *sc; uint8_t mask; sc = device_get_softc(dev); mask = 1 << ((struct pl061_pin_irqsrc *)isrc)->irq; dprintf("%s: calling disable interrupt %#x\n", __func__, mask); PL061_LOCK(sc); mask_and_set(sc, PL061_INTMASK, mask, 0); PL061_UNLOCK(sc); } static void pl061_pic_enable_intr(device_t dev, struct intr_irqsrc *isrc) { struct pl061_softc *sc; uint8_t mask; sc = device_get_softc(dev); mask = 1 << ((struct pl061_pin_irqsrc *)isrc)->irq; dprintf("%s: calling enable interrupt %#x\n", __func__, mask); PL061_LOCK(sc); mask_and_set(sc, PL061_INTMASK, mask, mask); PL061_UNLOCK(sc); } static int pl061_pic_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { struct pl061_softc *sc; struct intr_map_data_gpio *gdata; uint32_t irq; sc = device_get_softc(dev); if (data->type != INTR_MAP_DATA_GPIO) return (ENOTSUP); gdata = (struct intr_map_data_gpio *)data; irq = gdata->gpio_pin_num; if (irq >= PL061_NUM_GPIO) { device_printf(dev, "invalid interrupt number %u\n", irq); return (EINVAL); } dprintf("%s: calling map interrupt %u\n", __func__, irq); *isrcp = PIC_INTR_ISRC(sc, irq); return (0); } static int pl061_pic_setup_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { struct pl061_softc *sc; struct intr_map_data_gpio *gdata; struct pl061_pin_irqsrc *irqsrc; uint32_t mode; uint8_t mask; if (data == NULL) return (ENOTSUP); sc = device_get_softc(dev); gdata = (struct intr_map_data_gpio *)data; irqsrc = (struct pl061_pin_irqsrc *)isrc; mode = gdata->gpio_intr_mode; mask = 1 << gdata->gpio_pin_num; dprintf("%s: calling setup interrupt %u mode %#x\n", __func__, irqsrc->irq, mode); if (irqsrc->irq != gdata->gpio_pin_num) { dprintf("%s: interrupts don't match\n", __func__); return (EINVAL); } if (isrc->isrc_handlers != 0) { dprintf("%s: handler already attached\n", __func__); return (irqsrc->mode == mode ? 0 : EINVAL); } irqsrc->mode = mode; PL061_LOCK(sc); if (mode & GPIO_INTR_EDGE_BOTH) { mask_and_set(sc, PL061_INTBOTHEDGES, mask, mask); mask_and_set(sc, PL061_INTSENSE, mask, 0); } else if (mode & GPIO_INTR_EDGE_RISING) { mask_and_set(sc, PL061_INTBOTHEDGES, mask, 0); mask_and_set(sc, PL061_INTSENSE, mask, 0); mask_and_set(sc, PL061_INTEVENT, mask, mask); } else if (mode & GPIO_INTR_EDGE_FALLING) { mask_and_set(sc, PL061_INTBOTHEDGES, mask, 0); mask_and_set(sc, PL061_INTSENSE, mask, 0); mask_and_set(sc, PL061_INTEVENT, mask, 0); } else if (mode & GPIO_INTR_LEVEL_HIGH) { mask_and_set(sc, PL061_INTBOTHEDGES, mask, 0); mask_and_set(sc, PL061_INTSENSE, mask, mask); mask_and_set(sc, PL061_INTEVENT, mask, mask); } else if (mode & GPIO_INTR_LEVEL_LOW) { mask_and_set(sc, PL061_INTBOTHEDGES, mask, 0); mask_and_set(sc, PL061_INTSENSE, mask, mask); mask_and_set(sc, PL061_INTEVENT, mask, 0); } PL061_UNLOCK(sc); return (0); } static int pl061_pic_teardown_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res, struct intr_map_data *data) { struct pl061_softc *sc; struct pl061_pin_irqsrc *irqsrc; uint8_t mask; irqsrc = (struct pl061_pin_irqsrc *)isrc; mask = 1 << irqsrc->irq; dprintf("%s: calling teardown interrupt %#x\n", __func__, mask); sc = device_get_softc(dev); if (isrc->isrc_handlers == 0) { irqsrc->mode = GPIO_INTR_CONFORM; PL061_LOCK(sc); mask_and_set(sc, PL061_INTMASK, mask, 0); PL061_UNLOCK(sc); } return (0); } static void pl061_pic_post_filter(device_t dev, struct intr_irqsrc *isrc) { struct pl061_softc *sc; uint8_t mask; sc = device_get_softc(dev); mask = 1 << ((struct pl061_pin_irqsrc *)isrc)->irq; dprintf("%s: calling post filter %#x\n", __func__, mask); bus_write_1(sc->sc_mem_res, PL061_INTCLR, mask); } static void pl061_pic_post_ithread(device_t dev, struct intr_irqsrc *isrc) { struct pl061_softc *sc; uint8_t mask; sc = device_get_softc(dev); mask = 1 << ((struct pl061_pin_irqsrc *)isrc)->irq; dprintf("%s: calling post ithread %#x\n", __func__, mask); bus_write_1(sc->sc_mem_res, PL061_INTCLR, mask); pl061_pic_enable_intr(dev, isrc); } static void pl061_pic_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { pl061_pic_disable_intr(dev, isrc); } static int pl061_intr(void *arg) { struct pl061_softc *sc; struct trapframe *tf; uint8_t status; int pin; sc = (struct pl061_softc *)arg; tf = curthread->td_intr_frame; status = bus_read_1(sc->sc_mem_res, PL061_STATUS); while (status != 0) { pin = ffs(status) - 1; status &= ~(1 << pin); if (intr_isrc_dispatch(PIC_INTR_ISRC(sc, pin), tf) != 0) device_printf(sc->sc_dev, "spurious interrupt %d\n", pin); dprintf("got IRQ on %d\n", pin); } return (FILTER_HANDLED); } int pl061_attach(device_t dev) { struct pl061_softc *sc; int ret; int irq; const char *name; sc = device_get_softc(dev); sc->sc_dev = dev; sc->sc_mem_rid = 0; sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->sc_mem_rid, RF_ACTIVE); if (sc->sc_mem_res == NULL) { device_printf(dev, "can't allocate memory resource\n"); return (ENXIO); } sc->sc_irq_rid = 0; sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->sc_irq_rid, RF_ACTIVE); if (sc->sc_irq_res == NULL) { device_printf(dev, "can't allocate IRQ resource\n"); goto free_mem; } ret = bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_MISC | INTR_MPSAFE, pl061_intr, NULL, sc, &sc->sc_irq_hdlr); if (ret) { device_printf(dev, "can't setup IRQ\n"); goto free_pic; } name = device_get_nameunit(dev); for (irq = 0; irq < PL061_NUM_GPIO; irq++) { if (bootverbose) { device_printf(dev, "trying to register pin %d name %s\n", irq, name); } sc->sc_isrcs[irq].irq = irq; sc->sc_isrcs[irq].mode = GPIO_INTR_CONFORM; ret = intr_isrc_register(PIC_INTR_ISRC(sc, irq), dev, 0, "%s", name); if (ret) { device_printf(dev, "can't register isrc %d\n", ret); goto free_isrc; } } + mtx_init(&sc->sc_mtx, device_get_nameunit(dev), "pl061", MTX_SPIN); + + if (sc->sc_xref != 0 && !intr_pic_register(dev, sc->sc_xref)) { + device_printf(dev, "couldn't register PIC\n"); + PL061_LOCK_DESTROY(sc); + goto free_isrc; + } + sc->sc_busdev = gpiobus_attach_bus(dev); if (sc->sc_busdev == NULL) { device_printf(dev, "couldn't attach gpio bus\n"); + PL061_LOCK_DESTROY(sc); goto free_isrc; } - mtx_init(&sc->sc_mtx, device_get_nameunit(dev), "pl061", MTX_SPIN); - return (0); free_isrc: /* * XXX isrc_release_counters() not implemented * for (irq = 0; irq < PL061_NUM_GPIO; irq++) * intr_isrc_deregister(PIC_INTR_ISRC(sc, irq)); */ + bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_irq_hdlr); bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid, sc->sc_irq_res); free_pic: /* * XXX intr_pic_deregister: not implemented * intr_pic_deregister(dev, 0); */ free_mem: bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid, sc->sc_mem_res); return (ENXIO); } int pl061_detach(device_t dev) { struct pl061_softc *sc; sc = device_get_softc(dev); if (sc->sc_busdev) gpiobus_detach_bus(dev); if (sc->sc_irq_hdlr != NULL) bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_irq_hdlr); if (sc->sc_irq_res != NULL) bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid, sc->sc_irq_res); if (sc->sc_mem_res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid, sc->sc_mem_res); PL061_LOCK_DESTROY(sc); return (0); } static device_method_t pl061_methods[] = { /* Device interface */ DEVMETHOD(device_attach, pl061_attach), DEVMETHOD(device_detach, pl061_detach), /* Bus interface */ DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), /* GPIO protocol */ DEVMETHOD(gpio_get_bus, pl061_get_bus), DEVMETHOD(gpio_pin_max, pl061_pin_max), DEVMETHOD(gpio_pin_getname, pl061_pin_getname), DEVMETHOD(gpio_pin_getflags, pl061_pin_getflags), DEVMETHOD(gpio_pin_getcaps, pl061_pin_getcaps), DEVMETHOD(gpio_pin_setflags, pl061_pin_setflags), DEVMETHOD(gpio_pin_get, pl061_pin_get), DEVMETHOD(gpio_pin_set, pl061_pin_set), DEVMETHOD(gpio_pin_toggle, pl061_pin_toggle), /* Interrupt controller interface */ DEVMETHOD(pic_disable_intr, pl061_pic_disable_intr), DEVMETHOD(pic_enable_intr, pl061_pic_enable_intr), DEVMETHOD(pic_map_intr, pl061_pic_map_intr), DEVMETHOD(pic_setup_intr, pl061_pic_setup_intr), DEVMETHOD(pic_teardown_intr, pl061_pic_teardown_intr), DEVMETHOD(pic_post_filter, pl061_pic_post_filter), DEVMETHOD(pic_post_ithread, pl061_pic_post_ithread), DEVMETHOD(pic_pre_ithread, pl061_pic_pre_ithread), DEVMETHOD_END }; DEFINE_CLASS_0(gpio, pl061_driver, pl061_methods, sizeof(struct pl061_softc)); diff --git a/sys/dev/gpio/pl061.h b/sys/dev/gpio/pl061.h index 809a1168493d..d9fe23e502b1 100644 --- a/sys/dev/gpio/pl061.h +++ b/sys/dev/gpio/pl061.h @@ -1,57 +1,58 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020 Amazon.com, Inc. or its affiliates. * 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. */ #ifndef __DEV_PL061_H__ #define __DEV_PL061_H__ DECLARE_CLASS(pl061_driver); #define PL061_NUM_GPIO 8 struct pl061_pin_irqsrc { struct intr_irqsrc isrc; uint32_t irq; uint32_t mode; }; struct pl061_softc { device_t sc_dev; device_t sc_busdev; struct mtx sc_mtx; struct resource *sc_mem_res; struct resource *sc_irq_res; void *sc_irq_hdlr; + intptr_t sc_xref; int sc_mem_rid; int sc_irq_rid; struct pl061_pin_irqsrc sc_isrcs[PL061_NUM_GPIO]; }; int pl061_attach(device_t); int pl061_detach(device_t); #endif /* __DEV_PL061_H__ */ diff --git a/sys/dev/gpio/pl061_acpi.c b/sys/dev/gpio/pl061_acpi.c index d2531fa94faf..ba927af27f1c 100644 --- a/sys/dev/gpio/pl061_acpi.c +++ b/sys/dev/gpio/pl061_acpi.c @@ -1,100 +1,93 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020 Amazon.com, Inc. or its affiliates. * 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 "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pl061.h" static char *gpio_ids[] = { "ARMH0061", NULL }; static int pl061_acpi_probe(device_t dev) { int rv; if (acpi_disabled("gpio")) return (ENXIO); rv = ACPI_ID_PROBE(device_get_parent(dev), dev, gpio_ids, NULL); if (rv <= 0) device_set_desc(dev, "Arm PL061 GPIO Controller"); return (rv); } static int pl061_acpi_attach(device_t dev) { - int error; + struct pl061_softc *sc; - error = pl061_attach(dev); - if (error != 0) - return (error); + sc = device_get_softc(dev); + sc->sc_xref = ACPI_GPIO_XREF; - if (!intr_pic_register(dev, ACPI_GPIO_XREF)) { - device_printf(dev, "couldn't register PIC\n"); - pl061_detach(dev); - error = ENXIO; - } - - return (error); + return (pl061_attach(dev)); } static device_method_t pl061_acpi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, pl061_acpi_probe), DEVMETHOD(device_attach, pl061_acpi_attach), DEVMETHOD_END }; DEFINE_CLASS_1(gpio, pl061_acpi_driver, pl061_acpi_methods, sizeof(struct pl061_softc), pl061_driver); EARLY_DRIVER_MODULE(pl061, acpi, pl061_acpi_driver, NULL, NULL, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE); MODULE_DEPEND(pl061, acpi, 1, 1, 1); MODULE_DEPEND(pl061, gpiobus, 1, 1, 1); diff --git a/sys/dev/gpio/pl061_fdt.c b/sys/dev/gpio/pl061_fdt.c index 352f21560d72..c001b8eb232e 100644 --- a/sys/dev/gpio/pl061_fdt.c +++ b/sys/dev/gpio/pl061_fdt.c @@ -1,93 +1,86 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020 Andrew Turner * * This work was supported by Innovate UK project 105694, "Digital Security * by Design (DSbD) Technology Platform Prototype". * * 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 #include #include #include #include #include #include #include #include #include #include #include #include "pl061.h" static int pl061_fdt_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "arm,pl061")) return (ENXIO); device_set_desc(dev, "Arm PL061 GPIO Controller"); return (BUS_PROBE_DEFAULT); } static int pl061_fdt_attach(device_t dev) { - int error; + struct pl061_softc *sc; - error = pl061_attach(dev); - if (error != 0) - return (error); + sc = device_get_softc(dev); + sc->sc_xref = OF_xref_from_node(ofw_bus_get_node(dev)); - if (!intr_pic_register(dev, OF_xref_from_node(ofw_bus_get_node(dev)))) { - device_printf(dev, "couldn't register PIC\n"); - pl061_detach(dev); - error = ENXIO; - } - - return (error); + return (pl061_attach(dev)); } static device_method_t pl061_fdt_methods[] = { /* Device interface */ DEVMETHOD(device_probe, pl061_fdt_probe), DEVMETHOD(device_attach, pl061_fdt_attach), DEVMETHOD_END }; DEFINE_CLASS_1(gpio, pl061_fdt_driver, pl061_fdt_methods, sizeof(struct pl061_softc), pl061_driver); EARLY_DRIVER_MODULE(pl061, ofwbus, pl061_fdt_driver, NULL, NULL, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE); MODULE_DEPEND(pl061, gpiobus, 1, 1, 1); diff --git a/sys/dev/gpio/qoriq_gpio.c b/sys/dev/gpio/qoriq_gpio.c index 5dabe03b775a..a82bd88757e5 100644 --- a/sys/dev/gpio/qoriq_gpio.c +++ b/sys/dev/gpio/qoriq_gpio.c @@ -1,437 +1,438 @@ /*- * Copyright (c) 2020 Alstom Group. * Copyright (c) 2020 Semihalf. * Copyright (c) 2015 Justin Hibbits * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" static device_t qoriq_gpio_get_bus(device_t dev) { struct qoriq_gpio_softc *sc; sc = device_get_softc(dev); return (sc->busdev); } static int qoriq_gpio_pin_max(device_t dev, int *maxpin) { *maxpin = MAXPIN; return (0); } /* Get a specific pin's capabilities. */ static int qoriq_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { struct qoriq_gpio_softc *sc; sc = device_get_softc(dev); if (!VALID_PIN(pin)) return (EINVAL); GPIO_LOCK(sc); *caps = sc->sc_pins[pin].gp_caps; GPIO_UNLOCK(sc); return (0); } /* Get a specific pin's name. */ static int qoriq_gpio_pin_getname(device_t dev, uint32_t pin, char *name) { if (!VALID_PIN(pin)) return (EINVAL); snprintf(name, GPIOMAXNAME, "qoriq_gpio%d.%d", device_get_unit(dev), pin); name[GPIOMAXNAME-1] = '\0'; return (0); } static int qoriq_gpio_pin_configure(device_t dev, uint32_t pin, uint32_t flags) { struct qoriq_gpio_softc *sc; uint32_t reg; sc = device_get_softc(dev); if ((flags & sc->sc_pins[pin].gp_caps) != flags) { return (EINVAL); } if (flags & GPIO_PIN_INPUT) { reg = bus_read_4(sc->sc_mem, GPIO_GPDIR); reg &= ~(1 << (31 - pin)); bus_write_4(sc->sc_mem, GPIO_GPDIR, reg); } else if (flags & GPIO_PIN_OUTPUT) { reg = bus_read_4(sc->sc_mem, GPIO_GPDIR); reg |= (1 << (31 - pin)); bus_write_4(sc->sc_mem, GPIO_GPDIR, reg); reg = bus_read_4(sc->sc_mem, GPIO_GPODR); if (flags & GPIO_PIN_OPENDRAIN) reg |= (1 << (31 - pin)); else reg &= ~(1 << (31 - pin)); bus_write_4(sc->sc_mem, GPIO_GPODR, reg); } sc->sc_pins[pin].gp_flags = flags; return (0); } /* Set flags for the pin. */ static int qoriq_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct qoriq_gpio_softc *sc = device_get_softc(dev); uint32_t ret; if (!VALID_PIN(pin)) return (EINVAL); if ((flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) == (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) return (EINVAL); GPIO_LOCK(sc); ret = qoriq_gpio_pin_configure(dev, pin, flags); GPIO_UNLOCK(sc); return (ret); } static int qoriq_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *pflags) { struct qoriq_gpio_softc *sc; if (!VALID_PIN(pin)) return (EINVAL); sc = device_get_softc(dev); GPIO_LOCK(sc); *pflags = sc->sc_pins[pin].gp_flags; GPIO_UNLOCK(sc); return (0); } /* Set a specific output pin's value. */ static int qoriq_gpio_pin_set(device_t dev, uint32_t pin, unsigned int value) { struct qoriq_gpio_softc *sc = device_get_softc(dev); uint32_t outvals; uint8_t pinbit; if (!VALID_PIN(pin) || value > 1) return (EINVAL); GPIO_LOCK(sc); pinbit = 31 - pin; outvals = bus_read_4(sc->sc_mem, GPIO_GPDAT); outvals &= ~(1 << pinbit); outvals |= (value << pinbit); bus_write_4(sc->sc_mem, GPIO_GPDAT, outvals); GPIO_UNLOCK(sc); return (0); } /* Get a specific pin's input value. */ static int qoriq_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *value) { struct qoriq_gpio_softc *sc = device_get_softc(dev); if (!VALID_PIN(pin)) return (EINVAL); *value = (bus_read_4(sc->sc_mem, GPIO_GPDAT) >> (31 - pin)) & 1; return (0); } /* Toggle a pin's output value. */ static int qoriq_gpio_pin_toggle(device_t dev, uint32_t pin) { struct qoriq_gpio_softc *sc = device_get_softc(dev); uint32_t val; if (!VALID_PIN(pin)) return (EINVAL); GPIO_LOCK(sc); val = bus_read_4(sc->sc_mem, GPIO_GPDAT); val ^= (1 << (31 - pin)); bus_write_4(sc->sc_mem, GPIO_GPDAT, val); GPIO_UNLOCK(sc); return (0); } static struct ofw_compat_data gpio_matches[] = { {"fsl,pq3-gpio", 1}, {"fsl,mpc8572-gpio", 1}, {"fsl,qoriq-gpio", 1}, {0, 0} }; static int qoriq_gpio_probe(device_t dev) { if (ofw_bus_search_compatible(dev, gpio_matches)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Freescale QorIQ GPIO driver"); return (0); } static int qoriq_gpio_pin_access_32(device_t dev, uint32_t first_pin, uint32_t clear_pins, uint32_t change_pins, uint32_t *orig_pins) { struct qoriq_gpio_softc *sc; uint32_t hwstate; sc = device_get_softc(dev); if (first_pin != 0) return (EINVAL); GPIO_LOCK(sc); hwstate = bus_read_4(sc->sc_mem, GPIO_GPDAT); bus_write_4(sc->sc_mem, GPIO_GPDAT, (hwstate & ~clear_pins) ^ change_pins); GPIO_UNLOCK(sc); if (orig_pins != NULL) *orig_pins = hwstate; return (0); } static int qoriq_gpio_pin_config_32(device_t dev, uint32_t first_pin, uint32_t num_pins, uint32_t *pin_flags) { uint32_t dir, odr, mask, reg; struct qoriq_gpio_softc *sc; uint32_t newflags[32]; int i; if (first_pin != 0 || !VALID_PIN(num_pins)) return (EINVAL); sc = device_get_softc(dev); dir = odr = mask = 0; for (i = 0; i < num_pins; i++) { newflags[i] = 0; mask |= (1 << i); if (pin_flags[i] & GPIO_PIN_INPUT) { newflags[i] = GPIO_PIN_INPUT; dir &= ~(1 << i); } else { newflags[i] = GPIO_PIN_OUTPUT; dir |= (1 << i); if (pin_flags[i] & GPIO_PIN_OPENDRAIN) { newflags[i] |= GPIO_PIN_OPENDRAIN; odr |= (1 << i); } else { newflags[i] |= GPIO_PIN_PUSHPULL; odr &= ~(1 << i); } } } GPIO_LOCK(sc); reg = (bus_read_4(sc->sc_mem, GPIO_GPDIR) & ~mask) | dir; bus_write_4(sc->sc_mem, GPIO_GPDIR, reg); reg = (bus_read_4(sc->sc_mem, GPIO_GPODR) & ~mask) | odr; bus_write_4(sc->sc_mem, GPIO_GPODR, reg); for (i = 0; i < num_pins; i++) sc->sc_pins[i].gp_flags = newflags[i]; GPIO_UNLOCK(sc); return (0); } static int qoriq_gpio_map_gpios(device_t bus, phandle_t dev, phandle_t gparent, int gcells, pcell_t *gpios, uint32_t *pin, uint32_t *flags) { struct qoriq_gpio_softc *sc; int err; if (!VALID_PIN(gpios[0])) return (EINVAL); sc = device_get_softc(bus); GPIO_LOCK(sc); err = qoriq_gpio_pin_configure(bus, gpios[0], gpios[1]); GPIO_UNLOCK(sc); if (err == 0) { *pin = gpios[0]; *flags = gpios[1]; } return (err); } int qoriq_gpio_attach(device_t dev) { struct qoriq_gpio_softc *sc = device_get_softc(dev); int i, rid; sc->dev = dev; GPIO_LOCK_INIT(sc); /* Allocate memory. */ rid = 0; sc->sc_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->sc_mem == NULL) { device_printf(dev, "Can't allocate memory for device output port"); qoriq_gpio_detach(dev); return (ENOMEM); } for (i = 0; i <= MAXPIN; i++) sc->sc_pins[i].gp_caps = DEFAULT_CAPS; - sc->busdev = gpiobus_attach_bus(dev); - if (sc->busdev == NULL) { - qoriq_gpio_detach(dev); - return (ENOMEM); - } /* * Enable the GPIO Input Buffer for all GPIOs. * This is safe on devices without a GPIBE register, because those * devices ignore writes and read 0's in undefined portions of the map. */ if (ofw_bus_is_compatible(dev, "fsl,qoriq-gpio")) bus_write_4(sc->sc_mem, GPIO_GPIBE, 0xffffffff); OF_device_register_xref(OF_xref_from_node(ofw_bus_get_node(dev)), dev); + sc->busdev = gpiobus_attach_bus(dev); + if (sc->busdev == NULL) { + qoriq_gpio_detach(dev); + return (ENOMEM); + } + return (0); } int qoriq_gpio_detach(device_t dev) { struct qoriq_gpio_softc *sc = device_get_softc(dev); gpiobus_detach_bus(dev); if (sc->sc_mem != NULL) { /* Release output port resource. */ bus_release_resource(dev, SYS_RES_MEMORY, rman_get_rid(sc->sc_mem), sc->sc_mem); } GPIO_LOCK_DESTROY(sc); return (0); } static device_method_t qoriq_gpio_methods[] = { /* device_if */ DEVMETHOD(device_probe, qoriq_gpio_probe), DEVMETHOD(device_attach, qoriq_gpio_attach), DEVMETHOD(device_detach, qoriq_gpio_detach), /* GPIO protocol */ DEVMETHOD(gpio_get_bus, qoriq_gpio_get_bus), DEVMETHOD(gpio_pin_max, qoriq_gpio_pin_max), DEVMETHOD(gpio_pin_getname, qoriq_gpio_pin_getname), DEVMETHOD(gpio_pin_getcaps, qoriq_gpio_pin_getcaps), DEVMETHOD(gpio_pin_get, qoriq_gpio_pin_get), DEVMETHOD(gpio_pin_set, qoriq_gpio_pin_set), DEVMETHOD(gpio_pin_getflags, qoriq_gpio_pin_getflags), DEVMETHOD(gpio_pin_setflags, qoriq_gpio_pin_setflags), DEVMETHOD(gpio_pin_toggle, qoriq_gpio_pin_toggle), DEVMETHOD(gpio_map_gpios, qoriq_gpio_map_gpios), DEVMETHOD(gpio_pin_access_32, qoriq_gpio_pin_access_32), DEVMETHOD(gpio_pin_config_32, qoriq_gpio_pin_config_32), DEVMETHOD_END }; DEFINE_CLASS_0(gpio, qoriq_gpio_driver, qoriq_gpio_methods, sizeof(struct qoriq_gpio_softc)); EARLY_DRIVER_MODULE(qoriq_gpio, simplebus, qoriq_gpio_driver, NULL, NULL, BUS_PASS_RESOURCE + BUS_PASS_ORDER_MIDDLE); diff --git a/sys/dev/iicbus/gpio/tca64xx.c b/sys/dev/iicbus/gpio/tca64xx.c index a129c8727c8a..90c38438cdf3 100644 --- a/sys/dev/iicbus/gpio/tca64xx.c +++ b/sys/dev/iicbus/gpio/tca64xx.c @@ -1,640 +1,639 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020 Alstom Group. * Copyright (c) 2020 Semihalf. * * 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. */ /* * Driver for TI TCA64XX I2C GPIO expander module. * * This driver only supports basic functionality * (interrupt handling and polarity inversion were omitted). */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" /* Base addresses of registers. LSB omitted. */ #define TCA64XX_PINS_PER_REG 8 #define TCA64XX_BIT_FROM_PIN(pin) (pin % TCA64XX_PINS_PER_REG) #define TCA64XX_REG_ADDR(pin, baseaddr) (baseaddr | (pin / \ TCA64XX_PINS_PER_REG)) #define TCA64XX_PIN_CAPS (GPIO_PIN_OUTPUT | GPIO_PIN_INPUT \ | GPIO_PIN_PUSHPULL | GPIO_PIN_INVIN) #define TCA6416_IN_PORT_REG 0x0 #define TCA6416_OUT_PORT_REG 0x2 #define TCA6416_POLARITY_INV_REG 0x4 #define TCA6416_CONF_REG 0x6 #define TCA6416_NUM_PINS 16 #define TCA6408_IN_PORT_REG 0x0 #define TCA6408_OUT_PORT_REG 0x1 #define TCA6408_POLARITY_INV_REG 0x2 #define TCA6408_CONF_REG 0x3 #define TCA6408_NUM_PINS 8 #ifdef DEBUG #define dbg_dev_printf(dev, fmt, args...) \ device_printf(dev, fmt, ##args) #else #define dbg_dev_printf(dev, fmt, args...) #endif enum chip_type{ TCA6416_TYPE = 1, TCA6408_TYPE }; struct tca64xx_softc { device_t dev; device_t busdev; enum chip_type chip; struct mtx mtx; uint32_t addr; uint8_t num_pins; uint8_t in_port_reg; uint8_t out_port_reg; uint8_t polarity_inv_reg; uint8_t conf_reg; uint8_t pin_caps; }; static int tca64xx_read(device_t, uint8_t, uint8_t *); static int tca64xx_write(device_t, uint8_t, uint8_t); static int tca64xx_probe(device_t); static int tca64xx_attach(device_t); static int tca64xx_detach(device_t); static device_t tca64xx_get_bus(device_t); static int tca64xx_pin_max(device_t, int *); static int tca64xx_pin_getcaps(device_t, uint32_t, uint32_t *); static int tca64xx_pin_getflags(device_t, uint32_t, uint32_t *); static int tca64xx_pin_setflags(device_t, uint32_t, uint32_t); static int tca64xx_pin_getname(device_t, uint32_t, char *); static int tca64xx_pin_get(device_t, uint32_t, unsigned int *); static int tca64xx_pin_set(device_t, uint32_t, unsigned int); static int tca64xx_pin_toggle(device_t, uint32_t); #ifdef DEBUG static void tca6408_regdump_setup(device_t dev); static void tca6416_regdump_setup(device_t dev); static int tca64xx_regdump_sysctl(SYSCTL_HANDLER_ARGS); #endif static device_method_t tca64xx_methods[] = { DEVMETHOD(device_probe, tca64xx_probe), DEVMETHOD(device_attach, tca64xx_attach), DEVMETHOD(device_detach, tca64xx_detach), /* GPIO methods */ DEVMETHOD(gpio_get_bus, tca64xx_get_bus), DEVMETHOD(gpio_pin_max, tca64xx_pin_max), DEVMETHOD(gpio_pin_getcaps, tca64xx_pin_getcaps), DEVMETHOD(gpio_pin_getflags, tca64xx_pin_getflags), DEVMETHOD(gpio_pin_setflags, tca64xx_pin_setflags), DEVMETHOD(gpio_pin_getname, tca64xx_pin_getname), DEVMETHOD(gpio_pin_get, tca64xx_pin_get), DEVMETHOD(gpio_pin_set, tca64xx_pin_set), DEVMETHOD(gpio_pin_toggle, tca64xx_pin_toggle), DEVMETHOD_END }; static driver_t tca64xx_driver = { "gpio", tca64xx_methods, sizeof(struct tca64xx_softc) }; DRIVER_MODULE(tca64xx, iicbus, tca64xx_driver, 0, 0); MODULE_VERSION(tca64xx, 1); static struct ofw_compat_data compat_data[] = { {"nxp,pca9555", TCA6416_TYPE}, {"ti,tca6408", TCA6408_TYPE}, {"ti,tca6416", TCA6416_TYPE}, {"ti,tca9539", TCA6416_TYPE}, {0,0} }; static int tca64xx_read(device_t dev, uint8_t reg, uint8_t *data) { struct iic_msg msgs[2]; struct tca64xx_softc *sc; int error; sc = device_get_softc(dev); if (data == NULL) return (EINVAL); msgs[0].slave = sc->addr; msgs[0].flags = IIC_M_WR | IIC_M_NOSTOP; msgs[0].len = 1; msgs[0].buf = ® msgs[1].slave = sc->addr; msgs[1].flags = IIC_M_RD; msgs[1].len = 1; msgs[1].buf = data; error = iicbus_transfer_excl(dev, msgs, 2, IIC_WAIT); return (iic2errno(error)); } static int tca64xx_write(device_t dev, uint8_t reg, uint8_t val) { struct iic_msg msg; struct tca64xx_softc *sc; int error; uint8_t buffer[2] = {reg, val}; sc = device_get_softc(dev); msg.slave = sc->addr; msg.flags = IIC_M_WR; msg.len = 2; msg.buf = buffer; error = iicbus_transfer_excl(dev, &msg, 1, IIC_WAIT); return (iic2errno(error)); } static int tca64xx_probe(device_t dev) { const struct ofw_compat_data *compat_ptr; if (!ofw_bus_status_okay(dev)) return (ENXIO); compat_ptr = ofw_bus_search_compatible(dev, compat_data); switch (compat_ptr->ocd_data) { case TCA6416_TYPE: device_set_desc(dev, "TCA6416 I/O expander"); break; case TCA6408_TYPE: device_set_desc(dev, "TCA6408 I/O expander"); break; default: return (ENXIO); } return (BUS_PROBE_DEFAULT); } static int tca64xx_attach(device_t dev) { struct tca64xx_softc *sc; const struct ofw_compat_data *compat_ptr; sc = device_get_softc(dev); compat_ptr = ofw_bus_search_compatible(dev, compat_data); switch (compat_ptr->ocd_data) { case TCA6416_TYPE: sc->in_port_reg = TCA6416_IN_PORT_REG; sc->out_port_reg = TCA6416_OUT_PORT_REG; sc->polarity_inv_reg = TCA6416_POLARITY_INV_REG; sc->conf_reg = TCA6416_CONF_REG; sc->num_pins = TCA6416_NUM_PINS; break; case TCA6408_TYPE: sc->in_port_reg = TCA6408_IN_PORT_REG; sc->out_port_reg = TCA6408_OUT_PORT_REG; sc->polarity_inv_reg = TCA6408_POLARITY_INV_REG; sc->conf_reg = TCA6408_CONF_REG; sc->num_pins = TCA6408_NUM_PINS; break; default: __assert_unreachable(); } sc->pin_caps = TCA64XX_PIN_CAPS; sc->chip = compat_ptr->ocd_data; sc->dev = dev; sc->addr = iicbus_get_addr(dev); mtx_init(&sc->mtx, "tca64xx gpio", "gpio", MTX_DEF); + OF_device_register_xref(OF_xref_from_node(ofw_bus_get_node(dev)), dev); sc->busdev = gpiobus_attach_bus(dev); if (sc->busdev == NULL) { device_printf(dev, "Could not create busdev child\n"); return (ENXIO); } - OF_device_register_xref(OF_xref_from_node(ofw_bus_get_node(dev)), dev); - #ifdef DEBUG switch (sc->chip) { case TCA6416_TYPE: tca6416_regdump_setup(dev); break; case TCA6408_TYPE: tca6408_regdump_setup(dev); break; default: __assert_unreachable(); } #endif return (0); } static int tca64xx_detach(device_t dev) { struct tca64xx_softc *sc; sc = device_get_softc(dev); if (sc->busdev != NULL) gpiobus_detach_bus(sc->busdev); mtx_destroy(&sc->mtx); return (0); } static device_t tca64xx_get_bus(device_t dev) { struct tca64xx_softc *sc; sc = device_get_softc(dev); return (sc->busdev); } static int tca64xx_pin_max(device_t dev __unused, int *maxpin) { struct tca64xx_softc *sc; sc = device_get_softc(dev); if (maxpin == NULL) return (EINVAL); *maxpin = sc->num_pins-1; return (0); } static int tca64xx_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { struct tca64xx_softc *sc; sc = device_get_softc(dev); if (pin >= sc->num_pins || caps == NULL) return (EINVAL); *caps = sc->pin_caps; return (0); } static int tca64xx_pin_getflags(device_t dev, uint32_t pin, uint32_t *pflags) { int error; uint8_t bit, val, addr; struct tca64xx_softc *sc; sc = device_get_softc(dev); bit = TCA64XX_BIT_FROM_PIN(pin); if (pin >= sc->num_pins || pflags == NULL) return (EINVAL); addr = TCA64XX_REG_ADDR(pin, sc->conf_reg); error = tca64xx_read(dev, addr, &val); if (error != 0) return (error); *pflags = (val & (1 << bit)) ? GPIO_PIN_INPUT : GPIO_PIN_OUTPUT; addr = TCA64XX_REG_ADDR(pin, sc->polarity_inv_reg); error = tca64xx_read(dev, addr, &val); if (error != 0) return (error); if (val & (1 << bit)) *pflags |= GPIO_PIN_INVIN; return (0); } static int tca64xx_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { uint8_t bit, val, addr, pins, inv_val; int error; struct tca64xx_softc *sc; sc = device_get_softc(dev); pins = sc->num_pins; bit = TCA64XX_BIT_FROM_PIN(pin); if (pin >= pins) return (EINVAL); mtx_lock(&sc->mtx); addr = TCA64XX_REG_ADDR(pin, sc->conf_reg); error = tca64xx_read(dev, addr, &val); if (error != 0) goto fail; addr = TCA64XX_REG_ADDR(pin, sc->polarity_inv_reg); error = tca64xx_read(dev, addr, &inv_val); if (error != 0) goto fail; if (flags & GPIO_PIN_INPUT) val |= (1 << bit); else if (flags & GPIO_PIN_OUTPUT) val &= ~(1 << bit); if (flags & GPIO_PIN_INVIN) inv_val |= (1 << bit); else inv_val &= ~(1 << bit); addr = TCA64XX_REG_ADDR(pin, sc->conf_reg); error = tca64xx_write(dev, addr, val); if (error != 0) goto fail; addr = TCA64XX_REG_ADDR(pin, sc->polarity_inv_reg); error = tca64xx_write(dev, addr, inv_val); fail: mtx_unlock(&sc->mtx); return (error); } static int tca64xx_pin_getname(device_t dev, uint32_t pin, char *name) { struct tca64xx_softc *sc; sc = device_get_softc(dev); if (pin >= sc->num_pins || name == NULL) return (EINVAL); snprintf(name, GPIOMAXNAME, "gpio_P%d%d", pin / TCA64XX_PINS_PER_REG, pin % TCA64XX_PINS_PER_REG); return (0); } static int tca64xx_pin_get(device_t dev, uint32_t pin, unsigned int *pval) { uint8_t bit, addr, pins, reg_pvalue; int error; struct tca64xx_softc *sc; sc = device_get_softc(dev); pins = sc->num_pins; addr = TCA64XX_REG_ADDR(pin, sc->in_port_reg); bit = TCA64XX_BIT_FROM_PIN(pin); if (pin >= pins || pval == NULL) return (EINVAL); dbg_dev_printf(dev, "Reading pin %u pvalue.", pin); error = tca64xx_read(dev, addr, ®_pvalue); if (error != 0) return (error); *pval = reg_pvalue & (1 << bit) ? 1 : 0; return (0); } static int tca64xx_pin_set(device_t dev, uint32_t pin, unsigned int val) { uint8_t bit, addr, pins, value; int error; struct tca64xx_softc *sc; sc = device_get_softc(dev); pins = sc->num_pins; addr = TCA64XX_REG_ADDR(pin, sc->out_port_reg); bit = TCA64XX_BIT_FROM_PIN(pin); if (pin >= pins) return (EINVAL); dbg_dev_printf(dev, "Setting pin: %u to %u\n", pin, val); mtx_lock(&sc->mtx); error = tca64xx_read(dev, addr, &value); if (error != 0) { mtx_unlock(&sc->mtx); dbg_dev_printf(dev, "Failed to read from register.\n"); return (error); } if (val != 0) value |= (1 << bit); else value &= ~(1 << bit); error = tca64xx_write(dev, addr, value); if (error != 0) { mtx_unlock(&sc->mtx); dbg_dev_printf(dev, "Could not write to register.\n"); return (error); } mtx_unlock(&sc->mtx); return (0); } static int tca64xx_pin_toggle(device_t dev, uint32_t pin) { int error; uint8_t bit, addr, pins, value; struct tca64xx_softc *sc; sc = device_get_softc(dev); pins = sc->num_pins; addr = TCA64XX_REG_ADDR(pin, sc->out_port_reg); bit = TCA64XX_BIT_FROM_PIN(pin); if (pin >= pins) return (EINVAL); dbg_dev_printf(dev, "Toggling pin: %d\n", pin); mtx_lock(&sc->mtx); error = tca64xx_read(dev, addr, &value); if (error != 0) { mtx_unlock(&sc->mtx); dbg_dev_printf(dev, "Cannot read from register.\n"); return (error); } value ^= (1 << bit); error = tca64xx_write(dev, addr, value); if (error != 0) { mtx_unlock(&sc->mtx); dbg_dev_printf(dev, "Cannot write to register.\n"); return (error); } mtx_unlock(&sc->mtx); return (0); } #ifdef DEBUG static void tca6416_regdump_setup(device_t dev) { struct sysctl_ctx_list *ctx; struct sysctl_oid *node; ctx = device_get_sysctl_ctx(dev); node = device_get_sysctl_tree(dev); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "in_reg_1", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, TCA6416_IN_PORT_REG, tca64xx_regdump_sysctl, "A", "Input port 1"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "in_reg_2", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, TCA6416_IN_PORT_REG | 1, tca64xx_regdump_sysctl, "A", "Input port 2"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "out_reg_1", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, TCA6416_OUT_PORT_REG, tca64xx_regdump_sysctl, "A", "Output port 1"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "out_reg_2", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, TCA6416_OUT_PORT_REG | 1, tca64xx_regdump_sysctl, "A", "Output port 2"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "pol_inv_1", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, TCA6416_POLARITY_INV_REG, tca64xx_regdump_sysctl, "A", "Polarity inv 1"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "pol_inv_2", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, TCA6416_POLARITY_INV_REG | 1, tca64xx_regdump_sysctl, "A", "Polarity inv 2"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "conf_reg_1", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, TCA6416_CONF_REG, tca64xx_regdump_sysctl, "A", "Configuration 1"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "conf_reg_2", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, TCA6416_CONF_REG | 1, tca64xx_regdump_sysctl, "A", "Configuration 2"); } static void tca6408_regdump_setup(device_t dev) { struct sysctl_ctx_list *ctx; struct sysctl_oid *node; ctx = device_get_sysctl_ctx(dev); node = device_get_sysctl_tree(dev); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "in_reg_1", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, TCA6408_IN_PORT_REG, tca64xx_regdump_sysctl, "A", "Input port 1"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "out_reg_1", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, TCA6408_OUT_PORT_REG, tca64xx_regdump_sysctl, "A", "Output port 1"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "pol_inv_1", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, TCA6408_POLARITY_INV_REG, tca64xx_regdump_sysctl, "A", "Polarity inv 1"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "conf_reg_1", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, TCA6408_CONF_REG, tca64xx_regdump_sysctl, "A", "Configuration 1"); } static int tca64xx_regdump_sysctl(SYSCTL_HANDLER_ARGS) { device_t dev; char buf[5]; int len, error; uint8_t reg, regval; dev = (device_t)arg1; reg = (uint8_t)arg2; error = tca64xx_read(dev, reg, ®val); if (error != 0) { return (error); } len = snprintf(buf, 5, "0x%02x", regval); error = sysctl_handle_string(oidp, buf, len, req); return (error); } #endif diff --git a/sys/powerpc/mpc85xx/mpc85xx_gpio.c b/sys/powerpc/mpc85xx/mpc85xx_gpio.c index f8c46a9975ae..43ac63ab7031 100644 --- a/sys/powerpc/mpc85xx/mpc85xx_gpio.c +++ b/sys/powerpc/mpc85xx/mpc85xx_gpio.c @@ -1,290 +1,290 @@ /*- * Copyright (c) 2015 Justin Hibbits * Copyright (c) 2013 Thomas Skibo * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" #define MAXPIN (7) #define VALID_PIN(u) ((u) >= 0 && (u) <= MAXPIN) #define GPIO_LOCK(sc) mtx_lock(&(sc)->sc_mtx) #define GPIO_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx) #define GPIO_LOCK_INIT(sc) \ mtx_init(&(sc)->sc_mtx, device_get_nameunit((sc)->dev), \ "gpio", MTX_DEF) #define GPIO_LOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx); struct mpc85xx_gpio_softc { device_t dev; device_t busdev; struct mtx sc_mtx; struct resource *out_res; /* Memory resource */ struct resource *in_res; }; static device_t mpc85xx_gpio_get_bus(device_t dev) { struct mpc85xx_gpio_softc *sc; sc = device_get_softc(dev); return (sc->busdev); } static int mpc85xx_gpio_pin_max(device_t dev, int *maxpin) { *maxpin = MAXPIN; return (0); } /* Get a specific pin's capabilities. */ static int mpc85xx_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { if (!VALID_PIN(pin)) return (EINVAL); *caps = (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT); return (0); } /* Get a specific pin's name. */ static int mpc85xx_gpio_pin_getname(device_t dev, uint32_t pin, char *name) { if (!VALID_PIN(pin)) return (EINVAL); snprintf(name, GPIOMAXNAME, "GPIO%d", pin); name[GPIOMAXNAME-1] = '\0'; return (0); } /* Set a specific output pin's value. */ static int mpc85xx_gpio_pin_set(device_t dev, uint32_t pin, unsigned int value) { struct mpc85xx_gpio_softc *sc = device_get_softc(dev); uint32_t outvals; uint8_t pinbit; if (!VALID_PIN(pin) || value > 1) return (EINVAL); GPIO_LOCK(sc); pinbit = 31 - pin; outvals = bus_read_4(sc->out_res, 0); outvals &= ~(1 << pinbit); outvals |= (value << pinbit); bus_write_4(sc->out_res, 0, outvals); GPIO_UNLOCK(sc); return (0); } /* Get a specific pin's input value. */ static int mpc85xx_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *value) { struct mpc85xx_gpio_softc *sc = device_get_softc(dev); if (!VALID_PIN(pin)) return (EINVAL); *value = (bus_read_4(sc->in_res, 0) >> (31 - pin)) & 1; return (0); } /* Toggle a pin's output value. */ static int mpc85xx_gpio_pin_toggle(device_t dev, uint32_t pin) { struct mpc85xx_gpio_softc *sc = device_get_softc(dev); uint32_t val; if (!VALID_PIN(pin)) return (EINVAL); GPIO_LOCK(sc); val = bus_read_4(sc->out_res, 0); val ^= (1 << (31 - pin)); bus_write_4(sc->out_res, 0, val); GPIO_UNLOCK(sc); return (0); } static int mpc85xx_gpio_probe(device_t dev) { uint32_t svr; if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "gpio")) return (ENXIO); svr = mfspr(SPR_SVR); switch (SVR_VER(svr)) { case SVR_MPC8533: case SVR_MPC8533E: break; default: return (ENXIO); } device_set_desc(dev, "MPC85xx GPIO driver"); return (0); } static int mpc85xx_gpio_detach(device_t dev); static int mpc85xx_gpio_attach(device_t dev) { struct mpc85xx_gpio_softc *sc = device_get_softc(dev); int rid; sc->dev = dev; GPIO_LOCK_INIT(sc); /* Allocate memory. */ rid = 0; sc->out_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->out_res == NULL) { device_printf(dev, "Can't allocate memory for device output port"); mpc85xx_gpio_detach(dev); return (ENOMEM); } rid = 1; sc->in_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->in_res == NULL) { device_printf(dev, "Can't allocate memory for device input port"); mpc85xx_gpio_detach(dev); return (ENOMEM); } + OF_device_register_xref(OF_xref_from_node(ofw_bus_get_node(dev)), dev); + sc->busdev = gpiobus_attach_bus(dev); if (sc->busdev == NULL) { mpc85xx_gpio_detach(dev); return (ENOMEM); } - OF_device_register_xref(OF_xref_from_node(ofw_bus_get_node(dev)), dev); - return (0); } static int mpc85xx_gpio_detach(device_t dev) { struct mpc85xx_gpio_softc *sc = device_get_softc(dev); gpiobus_detach_bus(dev); if (sc->out_res != NULL) { /* Release output port resource. */ bus_release_resource(dev, SYS_RES_MEMORY, rman_get_rid(sc->out_res), sc->out_res); } if (sc->in_res != NULL) { /* Release input port resource. */ bus_release_resource(dev, SYS_RES_MEMORY, rman_get_rid(sc->in_res), sc->in_res); } GPIO_LOCK_DESTROY(sc); return (0); } static device_method_t mpc85xx_gpio_methods[] = { /* device_if */ DEVMETHOD(device_probe, mpc85xx_gpio_probe), DEVMETHOD(device_attach, mpc85xx_gpio_attach), DEVMETHOD(device_detach, mpc85xx_gpio_detach), /* GPIO protocol */ DEVMETHOD(gpio_get_bus, mpc85xx_gpio_get_bus), DEVMETHOD(gpio_pin_max, mpc85xx_gpio_pin_max), DEVMETHOD(gpio_pin_getname, mpc85xx_gpio_pin_getname), DEVMETHOD(gpio_pin_getcaps, mpc85xx_gpio_pin_getcaps), DEVMETHOD(gpio_pin_get, mpc85xx_gpio_pin_get), DEVMETHOD(gpio_pin_set, mpc85xx_gpio_pin_set), DEVMETHOD(gpio_pin_toggle, mpc85xx_gpio_pin_toggle), DEVMETHOD_END }; static driver_t mpc85xx_gpio_driver = { "gpio", mpc85xx_gpio_methods, sizeof(struct mpc85xx_gpio_softc), }; EARLY_DRIVER_MODULE(mpc85xx_gpio, simplebus, mpc85xx_gpio_driver, NULL, NULL, BUS_PASS_RESOURCE + BUS_PASS_ORDER_MIDDLE);