/* $OpenBSD: sxipio.c,v 1.19 2024/02/08 00:00:16 jsg Exp $ */ /* * Copyright (c) 2010 Miodrag Vallat. * Copyright (c) 2013 Artturi Alm * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio.h" #define SXIPIO_NPORT 9 struct sxipio_softc; struct sxipio_gpio { struct sxipio_softc *sc; int port; }; struct intrhand { int (*ih_func)(void *); /* handler */ void *ih_arg; /* arg for handler */ int ih_ipl; /* IPL_* */ int ih_irq; /* IRQ number */ int ih_gpio; /* gpio pin */ struct evcount ih_count; char *ih_name; }; struct sxipio_softc { struct device sc_dev; bus_space_tag_t sc_iot; bus_space_handle_t sc_ioh; int sc_node; void *sc_ih_h; void *sc_ih_l; int sc_max_il; int sc_min_il; const struct sxipio_pin *sc_pins; int sc_npins; struct gpio_controller sc_gc; struct sxipio_gpio sc_gpio[SXIPIO_NPORT]; struct gpio_chipset_tag sc_gpio_tag[SXIPIO_NPORT]; gpio_pin_t sc_gpio_pins[SXIPIO_NPORT][32]; struct intrhand *sc_handlers[32]; void (*sc_bias_cfg)(struct sxipio_softc *, int, uint32_t); }; #define SXIPIO_CFG(port, pin) 0x00 + ((port) * 0x24) + (((pin) >> 3) * 0x04) #define SXIPIO_DAT(port) 0x10 + ((port) * 0x24) #define SXIPIO_DRV(port, pin) 0x14 + ((port) * 0x24) + (((pin) >> 4) * 0x04) #define SXIPIO_PUL(port, pin) 0x1c + ((port) * 0x24) + (((pin) >> 4) * 0x04) #define SXIPIO_INT_CFG0(port) 0x0200 + ((port) * 0x04) #define SXIPIO_INT_CTL 0x0210 #define SXIPIO_INT_STA 0x0214 #define SXIPIO_INT_DEB 0x0218 /* debounce register */ #define SXIPIO_GRP_CFG(port) 0x0300 + ((port) * 0x04) #define SXIPIO_IO_BIAS_MASK (0xf << 0) #define SXIPIO_IO_BIAS_1_8V 0x0 #define SXIPIO_IO_BIAS_2_5V 0x6 #define SXIPIO_IO_BIAS_2_8V 0x9 #define SXIPIO_IO_BIAS_3_0V 0xa #define SXIPIO_IO_BIAS_3_3V 0xd #define SXIPIO_GPIO_IN 0 #define SXIPIO_GPIO_OUT 1 #define SXIPIO_DISABLED 7 int sxipio_match(struct device *, void *, void *); void sxipio_attach(struct device *, struct device *, void *); const struct cfattach sxipio_ca = { sizeof (struct sxipio_softc), sxipio_match, sxipio_attach }; struct cfdriver sxipio_cd = { NULL, "sxipio", DV_DULL }; void sxipio_attach_gpio(struct device *); int sxipio_pinctrl(uint32_t, void *); void sxipio_config_pin(void *, uint32_t *, int); int sxipio_get_pin(void *, uint32_t *); void sxipio_set_pin(void *, uint32_t *, int); void sxipio_a80_bias_cfg(struct sxipio_softc *, int, uint32_t); #include "sxipio_pins.h" struct sxipio_pins { const char *compat; const struct sxipio_pin *pins; int npins; }; const struct sxipio_pins sxipio_pins[] = { { "allwinner,sun4i-a10-pinctrl", sun4i_a10_pins, nitems(sun4i_a10_pins) }, { "allwinner,sun5i-a10s-pinctrl", sun5i_a10s_pins, nitems(sun5i_a10s_pins) }, { "allwinner,sun5i-a13-pinctrl", sun5i_a13_pins, nitems(sun5i_a13_pins) }, { "allwinner,sun5i-gr8-pinctrl", sun5i_gr8_pins, nitems(sun5i_gr8_pins) }, { "allwinner,sun7i-a20-pinctrl", sun7i_a20_pins, nitems(sun7i_a20_pins) }, { "allwinner,sun8i-r40-pinctrl", sun8i_r40_pins, nitems(sun8i_r40_pins) }, { "allwinner,sun8i-a33-pinctrl", sun8i_a33_pins, nitems(sun8i_a33_pins) }, { "allwinner,sun8i-h3-pinctrl", sun8i_h3_pins, nitems(sun8i_h3_pins) }, { "allwinner,sun8i-h3-r-pinctrl", sun8i_h3_r_pins, nitems(sun8i_h3_r_pins) }, { "allwinner,sun8i-v3-pinctrl", sun8i_v3_pins, nitems(sun8i_v3_pins) }, { "allwinner,sun8i-v3s-pinctrl", sun8i_v3s_pins, nitems(sun8i_v3s_pins) }, { "allwinner,sun9i-a80-pinctrl", sun9i_a80_pins, nitems(sun9i_a80_pins) }, { "allwinner,sun9i-a80-r-pinctrl", sun9i_a80_r_pins, nitems(sun9i_a80_r_pins) }, { "allwinner,sun20i-d1-pinctrl", sun20i_d1_pins, nitems(sun20i_d1_pins) }, { "allwinner,sun50i-a64-pinctrl", sun50i_a64_pins, nitems(sun50i_a64_pins) }, { "allwinner,sun50i-a64-r-pinctrl", sun50i_a64_r_pins, nitems(sun50i_a64_r_pins) }, { "allwinner,sun50i-h5-pinctrl", sun50i_h5_pins, nitems(sun50i_h5_pins) }, { "allwinner,sun50i-h6-pinctrl", sun50i_h6_pins, nitems(sun50i_h6_pins) }, { "allwinner,sun50i-h6-r-pinctrl", sun50i_h6_r_pins, nitems(sun50i_h6_r_pins) }, { "allwinner,sun50i-h616-pinctrl", sun50i_h616_pins, nitems(sun50i_h616_pins) }, { "allwinner,sun50i-h616-r-pinctrl", sun50i_h616_r_pins, nitems(sun50i_h616_r_pins) }, }; int sxipio_match(struct device *parent, void *match, void *aux) { struct fdt_attach_args *faa = aux; int i; for (i = 0; i < nitems(sxipio_pins); i++) { if (OF_is_compatible(faa->fa_node, sxipio_pins[i].compat)) return 1; } return 0; } void sxipio_attach(struct device *parent, struct device *self, void *aux) { struct sxipio_softc *sc = (struct sxipio_softc *)self; struct fdt_attach_args *faa = aux; int i; sc->sc_iot = faa->fa_iot; if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr, faa->fa_reg[0].size, 0, &sc->sc_ioh)) panic("%s: bus_space_map failed!", __func__); sc->sc_node = faa->fa_node; clock_enable_all(faa->fa_node); reset_deassert_all(faa->fa_node); for (i = 0; i < nitems(sxipio_pins); i++) { if (OF_is_compatible(faa->fa_node, sxipio_pins[i].compat)) { sc->sc_pins = sxipio_pins[i].pins; sc->sc_npins = sxipio_pins[i].npins; break; } } /* Allwinner A80 needs IO pad bias configuration. */ if (OF_is_compatible(faa->fa_node, "allwinner,sun9i-a80-pinctrl") || OF_is_compatible(faa->fa_node, "allwinner,sun9i-a80-r-pinctrl")) sc->sc_bias_cfg = sxipio_a80_bias_cfg; KASSERT(sc->sc_pins); pinctrl_register(faa->fa_node, sxipio_pinctrl, sc); sc->sc_gc.gc_node = faa->fa_node; sc->sc_gc.gc_cookie = sc; sc->sc_gc.gc_config_pin = sxipio_config_pin; sc->sc_gc.gc_get_pin = sxipio_get_pin; sc->sc_gc.gc_set_pin = sxipio_set_pin; gpio_controller_register(&sc->sc_gc); config_defer(self, sxipio_attach_gpio); printf(": %d pins\n", sc->sc_npins); } int sxipio_drive(int node) { int drive; drive = OF_getpropint(node, "allwinner,drive", -1); if (drive >= 0) return drive; drive = OF_getpropint(node, "drive-strength", 0) - 10; if (drive >= 0) return (drive / 10); return -1; } int sxipio_pull(int node) { int pull; pull = OF_getpropint(node, "allwinner,pull", -1); if (pull >= 0) return pull; if (OF_getproplen(node, "bias-disable") == 0) return 0; if (OF_getproplen(node, "bias-pull-up") == 0) return 1; if (OF_getproplen(node, "bias-pull-down") == 0) return 2; return -1; } int sxipio_pinctrl(uint32_t phandle, void *cookie) { struct sxipio_softc *sc = cookie; char func[32]; char vcc[16]; char *names, *name; uint32_t supply; int group, port, pin, off, mask; int mux, drive, pull; int node; int len; int i, j; int s; node = OF_getnodebyphandle(phandle); if (node == 0) return -1; len = OF_getprop(node, "allwinner,function", func, sizeof(func)); if (len <= 0 || len >= sizeof(func)) { len = OF_getprop(node, "function", func, sizeof(func)); if (len <= 0 || len >= sizeof(func)) return -1; } len = OF_getproplen(node, "allwinner,pins"); if (len <= 0) { len = OF_getproplen(node, "pins"); if (len <= 0) return -1; } names = malloc(len, M_TEMP, M_WAITOK); if (OF_getprop(node, "allwinner,pins", names, len) <= 0) OF_getprop(node, "pins", names, len); drive = sxipio_drive(node); pull = sxipio_pull(node); name = names; while (len > 0) { /* Lookup the pin. */ for (i = 0; i < sc->sc_npins; i++) { if (strcmp(name, sc->sc_pins[i].name) == 0) break; } if (i >= sc->sc_npins) goto err; /* Lookup the function of the pin. */ for (j = 0; j < nitems(sc->sc_pins[i].funcs); j++) { if (sc->sc_pins[i].funcs[j].name == NULL) continue; if (strcmp(func, sc->sc_pins[i].funcs[j].name) == 0) break; } if (j >= nitems(sc->sc_pins[i].funcs)) goto err; group = sc->sc_pins[i].name[1] - 'A'; port = sc->sc_pins[i].port; pin = sc->sc_pins[i].pin; mux = sc->sc_pins[i].funcs[j].mux; snprintf(vcc, sizeof(vcc), "vcc-p%c-supply", 'a' + group); supply = OF_getpropint(sc->sc_node, vcc, 0); if (supply) { regulator_enable(supply); if (sc->sc_bias_cfg) sc->sc_bias_cfg(sc, port, supply); } s = splhigh(); off = (pin & 0x7) << 2, mask = (0x7 << off); SXICMS4(sc, SXIPIO_CFG(port, pin), mask, mux << off); off = (pin & 0xf) << 1, mask = (0x3 << off); if (drive >= 0 && drive < 4) SXICMS4(sc, SXIPIO_DRV(port, pin), mask, drive << off); if (pull >= 0 && pull < 3) SXICMS4(sc, SXIPIO_PUL(port, pin), mask, pull << off); splx(s); len -= strlen(name) + 1; name += strlen(name) + 1; } free(names, M_TEMP, len); return 0; err: free(names, M_TEMP, len); return -1; } void sxipio_config_pin(void *cookie, uint32_t *cells, int config) { struct sxipio_softc *sc = cookie; uint32_t port = cells[0]; uint32_t pin = cells[1]; int mux, off; if (port > SXIPIO_NPORT || pin >= 32) return; mux = (config & GPIO_CONFIG_OUTPUT) ? 1 : 0; off = (pin & 0x7) << 2; SXICMS4(sc, SXIPIO_CFG(port, pin), 0x7 << off, mux << off); } int sxipio_get_pin(void *cookie, uint32_t *cells) { struct sxipio_softc *sc = cookie; uint32_t port = cells[0]; uint32_t pin = cells[1]; uint32_t flags = cells[2]; uint32_t reg; int val; if (port > SXIPIO_NPORT || pin >= 32) return 0; reg = SXIREAD4(sc, SXIPIO_DAT(port)); reg &= (1 << pin); val = (reg >> pin) & 1; if (flags & GPIO_ACTIVE_LOW) val = !val; return val; } void sxipio_set_pin(void *cookie, uint32_t *cells, int val) { struct sxipio_softc *sc = cookie; uint32_t port = cells[0]; uint32_t pin = cells[1]; uint32_t flags = cells[2]; uint32_t reg; if (port > SXIPIO_NPORT || pin >= 32) return; reg = SXIREAD4(sc, SXIPIO_DAT(port)); if (flags & GPIO_ACTIVE_LOW) val = !val; if (val) reg |= (1 << pin); else reg &= ~(1 << pin); SXIWRITE4(sc, SXIPIO_DAT(port), reg); } void sxipio_a80_bias_cfg(struct sxipio_softc *sc, int port, uint32_t supply) { uint32_t voltage; uint32_t bias; voltage = regulator_get_voltage(supply); if (voltage <= 1800000) bias = SXIPIO_IO_BIAS_1_8V; else if (voltage <= 2500000) bias = SXIPIO_IO_BIAS_2_5V; else if (voltage <= 2800000) bias = SXIPIO_IO_BIAS_2_8V; else if (voltage <= 3000000) bias = SXIPIO_IO_BIAS_3_0V; else bias = SXIPIO_IO_BIAS_3_3V; SXICMS4(sc, SXIPIO_GRP_CFG(port), SXIPIO_IO_BIAS_MASK, bias); } /* * GPIO support code */ int sxipio_pin_read(void *, int); void sxipio_pin_write(void *, int, int); void sxipio_pin_ctl(void *, int, int); static const struct gpio_chipset_tag sxipio_gpio_tag = { .gp_pin_read = sxipio_pin_read, .gp_pin_write = sxipio_pin_write, .gp_pin_ctl = sxipio_pin_ctl }; int sxipio_pin_read(void *cookie, int pin) { struct sxipio_gpio *gpio = cookie; uint32_t cells[3]; cells[0] = gpio->port; cells[1] = pin; cells[2] = 0; return sxipio_get_pin(gpio->sc, cells) ? GPIO_PIN_HIGH : GPIO_PIN_LOW; } void sxipio_pin_write(void *cookie, int pin, int val) { struct sxipio_gpio *gpio = cookie; uint32_t cells[3]; cells[0] = gpio->port; cells[1] = pin; cells[2] = 0; sxipio_set_pin(gpio->sc, cells, val); } void sxipio_pin_ctl(void *cookie, int pin, int flags) { struct sxipio_gpio *gpio = cookie; uint32_t cells[3]; cells[0] = gpio->port; cells[1] = pin; cells[2] = 0; if (ISSET(flags, GPIO_PIN_OUTPUT)) sxipio_config_pin(gpio->sc, cells, GPIO_CONFIG_OUTPUT); else sxipio_config_pin(gpio->sc, cells, 0); } void sxipio_attach_gpio(struct device *parent) { struct sxipio_softc *sc = (struct sxipio_softc *)parent; struct gpiobus_attach_args gba; uint32_t reg; int port, pin; int off, mux; int state, flags; int i; for (i = 0; i < sc->sc_npins; i++) { /* Skip pins that have no gpio function. */ if (strcmp(sc->sc_pins[i].funcs[0].name, "gpio_in") != 0 || strcmp(sc->sc_pins[i].funcs[1].name, "gpio_out") != 0) continue; port = sc->sc_pins[i].port; pin = sc->sc_pins[i].pin; /* Get pin configuration. */ reg = SXIREAD4(sc, SXIPIO_CFG(port, pin)); off = (pin & 0x7) << 2; mux = (reg >> off) & 0x7; /* Skip pins that have been assigned other functions. */ if (mux != SXIPIO_GPIO_IN && mux != SXIPIO_GPIO_OUT && mux != SXIPIO_DISABLED) continue; switch (mux) { case SXIPIO_GPIO_IN: flags = GPIO_PIN_SET | GPIO_PIN_INPUT; break; case SXIPIO_GPIO_OUT: flags = GPIO_PIN_SET | GPIO_PIN_OUTPUT; break; default: flags = GPIO_PIN_SET; } /* Get pin state. */ reg = SXIREAD4(sc, SXIPIO_DAT(port)); state = (reg >> pin) & 1; sc->sc_gpio_pins[port][pin].pin_caps = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT; sc->sc_gpio_pins[port][pin].pin_flags = flags; sc->sc_gpio_pins[port][pin].pin_state = state; sc->sc_gpio_pins[port][pin].pin_num = pin; } for (i = 0; i <= port; i++) { memcpy(&sc->sc_gpio_tag[i], &sxipio_gpio_tag, sizeof(sxipio_gpio_tag)); sc->sc_gpio_tag[i].gp_cookie = &sc->sc_gpio[i]; sc->sc_gpio[i].sc = sc; sc->sc_gpio[i].port = i; gba.gba_name = "gpio"; gba.gba_gc = &sc->sc_gpio_tag[i]; gba.gba_pins = &sc->sc_gpio_pins[i][0]; gba.gba_npins = 32; #if NGPIO > 0 config_found(&sc->sc_dev, &gba, gpiobus_print); #endif } }