diff options
author | Mark Kettenis <kettenis@cvs.openbsd.org> | 2021-10-30 14:50:55 +0000 |
---|---|---|
committer | Mark Kettenis <kettenis@cvs.openbsd.org> | 2021-10-30 14:50:55 +0000 |
commit | fdd3b63244d75968427160bd33b13d8675976523 (patch) | |
tree | 0ec04682f18610551282c18c7f509807f5459dd2 /sys/arch/arm64 | |
parent | 5f1ce79155a4ed7be219449075923a0d9071ae3b (diff) |
Add GPIO functionality (including support for using GPIOs as interrupt pins).
Needed for upcoming Apple M1 laptop keyboard support.
ok patrick@
Diffstat (limited to 'sys/arch/arm64')
-rw-r--r-- | sys/arch/arm64/dev/aplpinctrl.c | 338 |
1 files changed, 335 insertions, 3 deletions
diff --git a/sys/arch/arm64/dev/aplpinctrl.c b/sys/arch/arm64/dev/aplpinctrl.c index e2951c3ba3b..d1c2cf6b841 100644 --- a/sys/arch/arm64/dev/aplpinctrl.c +++ b/sys/arch/arm64/dev/aplpinctrl.c @@ -1,4 +1,4 @@ -/* $OpenBSD: aplpinctrl.c,v 1.1 2021/08/31 15:20:06 kettenis Exp $ */ +/* $OpenBSD: aplpinctrl.c,v 1.2 2021/10/30 14:50:54 kettenis Exp $ */ /* * Copyright (c) 2021 Mark Kettenis <kettenis@openbsd.org> * @@ -18,31 +18,69 @@ #include <sys/param.h> #include <sys/systm.h> #include <sys/device.h> +#include <sys/evcount.h> #include <sys/malloc.h> #include <machine/bus.h> #include <machine/fdt.h> +#include <machine/intr.h> #include <dev/ofw/openfirm.h> +#include <dev/ofw/ofw_gpio.h> #include <dev/ofw/ofw_pinctrl.h> #include <dev/ofw/fdt.h> #define APPLE_PIN(pinmux) ((pinmux) & 0xffff) #define APPLE_FUNC(pinmux) ((pinmux) >> 16) -#define GPIO_PIN(reg) ((reg) * 4) -#define GPIO_PIN_FUNC_MASK (1 << 5) +#define GPIO_PIN(pin) ((pin) * 4) +#define GPIO_PIN_GROUP_MASK (7 << 16) +#define GPIO_PIN_INPUT_ENABLE (1 << 9) +#define GPIO_PIN_FUNC_MASK (3 << 5) #define GPIO_PIN_FUNC_SHIFT 5 +#define GPIO_PIN_MODE_MASK (7 << 1) +#define GPIO_PIN_MODE_INPUT (0 << 1) +#define GPIO_PIN_MODE_OUTPUT (1 << 1) +#define GPIO_PIN_MODE_IRQ_HI (2 << 1) +#define GPIO_PIN_MODE_IRQ_LO (3 << 1) +#define GPIO_PIN_MODE_IRQ_UP (4 << 1) +#define GPIO_PIN_MODE_IRQ_DN (5 << 1) +#define GPIO_PIN_MODE_IRQ_ANY (6 << 1) +#define GPIO_PIN_MODE_IRQ_OFF (7 << 1) +#define GPIO_PIN_DATA (1 << 0) +#define GPIO_IRQ(grp, pin) (0x800 + (grp) * 64 + ((pin) >> 5) * 4) #define HREAD4(sc, reg) \ (bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg))) #define HWRITE4(sc, reg, val) \ bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val)) +#define HSET4(sc, reg, bits) \ + HWRITE4((sc), (reg), HREAD4((sc), (reg)) | (bits)) +#define HCLR4(sc, reg, bits) \ + HWRITE4((sc), (reg), HREAD4((sc), (reg)) & ~(bits)) + +struct intrhand { + int (*ih_func)(void *); + void *ih_arg; + int ih_irq; + int ih_type; + int ih_ipl; + struct evcount ih_count; + char *ih_name; + void *ih_sc; +}; struct aplpinctrl_softc { struct device sc_dev; bus_space_tag_t sc_iot; bus_space_handle_t sc_ioh; + + int sc_ngpios; + struct gpio_controller sc_gc; + + void *sc_ih; + struct intrhand **sc_handler; + struct interrupt_controller sc_ic; }; int aplpinctrl_match(struct device *, void *, void *); @@ -57,6 +95,17 @@ struct cfdriver aplpinctrl_cd = { }; int aplpinctrl_pinctrl(uint32_t, void *); +void aplpinctrl_config_pin(void *, uint32_t *, int); +int aplpinctrl_get_pin(void *, uint32_t *); +void aplpinctrl_set_pin(void *, uint32_t *, int); + +int aplpinctrl_intr(void *); +void *aplpinctrl_intr_establish(void *, int *, int, struct cpu_info *, + int (*)(void *), void *, char *); +void aplpinctrl_intr_disestablish(void *); +void aplpinctrl_intr_enable(void *); +void aplpinctrl_intr_disable(void *); +void aplpinctrl_intr_barrier(void *); int aplpinctrl_match(struct device *parent, void *match, void *aux) @@ -71,6 +120,7 @@ aplpinctrl_attach(struct device *parent, struct device *self, void *aux) { struct aplpinctrl_softc *sc = (struct aplpinctrl_softc *)self; struct fdt_attach_args *faa = aux; + uint32_t gpio_ranges[4] = {}; if (faa->fa_nreg < 1) { printf(": no registers\n"); @@ -86,6 +136,40 @@ aplpinctrl_attach(struct device *parent, struct device *self, void *aux) pinctrl_register(faa->fa_node, aplpinctrl_pinctrl, sc); + OF_getpropintarray(faa->fa_node, "gpio-ranges", + gpio_ranges, sizeof(gpio_ranges)); + sc->sc_ngpios = gpio_ranges[3]; + if (sc->sc_ngpios == 0) { + printf("\n"); + return; + } + + sc->sc_gc.gc_node = faa->fa_node; + sc->sc_gc.gc_cookie = sc; + sc->sc_gc.gc_config_pin = aplpinctrl_config_pin; + sc->sc_gc.gc_get_pin = aplpinctrl_get_pin; + sc->sc_gc.gc_set_pin = aplpinctrl_set_pin; + gpio_controller_register(&sc->sc_gc); + + sc->sc_ih = fdt_intr_establish_idx(faa->fa_node, 0, IPL_BIO, + aplpinctrl_intr, sc, sc->sc_dev.dv_xname); + if (sc->sc_ih == NULL) { + printf(": can't establish interrupt\n"); + return; + } + + sc->sc_handler = mallocarray(sc->sc_ngpios, + sizeof(*sc->sc_handler), M_DEVBUF, M_ZERO | M_WAITOK); + + sc->sc_ic.ic_node = faa->fa_node; + sc->sc_ic.ic_cookie = sc; + sc->sc_ic.ic_establish = aplpinctrl_intr_establish; + sc->sc_ic.ic_disestablish = aplpinctrl_intr_disestablish; + sc->sc_ic.ic_enable = aplpinctrl_intr_enable; + sc->sc_ic.ic_disable = aplpinctrl_intr_disable; + sc->sc_ic.ic_barrier = aplpinctrl_intr_barrier; + fdt_intr_register(&sc->sc_ic); + printf("\n"); } @@ -121,3 +205,251 @@ aplpinctrl_pinctrl(uint32_t phandle, void *cookie) free(pinmux, M_TEMP, len); return 0; } + +void +aplpinctrl_config_pin(void *cookie, uint32_t *cells, int config) +{ + struct aplpinctrl_softc *sc = cookie; + uint32_t pin = cells[0]; + uint32_t reg; + + KASSERT(pin < sc->sc_ngpios); + + reg = HREAD4(sc, GPIO_PIN(pin)); + reg &= ~GPIO_PIN_FUNC_MASK; + reg &= ~GPIO_PIN_MODE_MASK; + if (config & GPIO_CONFIG_OUTPUT) + reg |= GPIO_PIN_MODE_OUTPUT; + else + reg |= GPIO_PIN_MODE_INPUT; + HWRITE4(sc, GPIO_PIN(pin), reg); +} + +int +aplpinctrl_get_pin(void *cookie, uint32_t *cells) +{ + struct aplpinctrl_softc *sc = cookie; + uint32_t pin = cells[0]; + uint32_t flags = cells[1]; + uint32_t reg; + int val; + + KASSERT(pin < sc->sc_ngpios); + + reg = HREAD4(sc, GPIO_PIN(pin)); + val = !!(reg & GPIO_PIN_DATA); + if (flags & GPIO_ACTIVE_LOW) + val = !val; + return val; +} + +void +aplpinctrl_set_pin(void *cookie, uint32_t *cells, int val) +{ + struct aplpinctrl_softc *sc = cookie; + uint32_t pin = cells[0]; + uint32_t flags = cells[1]; + + KASSERT(pin < sc->sc_ngpios); + + if (flags & GPIO_ACTIVE_LOW) + val = !val; + if (val) + HSET4(sc, GPIO_PIN(pin), GPIO_PIN_DATA); + else + HCLR4(sc, GPIO_PIN(pin), GPIO_PIN_DATA); +} + +int +aplpinctrl_intr(void *arg) +{ + struct aplpinctrl_softc *sc = arg; + struct intrhand *ih; + uint32_t status, pending; + int pin, s; + + for (pin = 0; pin < sc->sc_ngpios; pin += 32) { + status = HREAD4(sc, GPIO_IRQ(0, pin)); + pending = status; + + while (pending) { + pin = ffs(pending) - 1; + ih = sc->sc_handler[pin]; + + if (ih) { + s = splraise(ih->ih_ipl); + if (ih->ih_func(ih->ih_arg)) + ih->ih_count.ec_count++; + splx(s); + } + + pending &= ~(1 << pin); + } + + HWRITE4(sc, GPIO_IRQ(0, pin), status); + } + + return 1; +} + +void * +aplpinctrl_intr_establish(void *cookie, int *cells, int ipl, + struct cpu_info *ci, int (*func)(void *), void *arg, char *name) +{ + struct aplpinctrl_softc *sc = cookie; + struct intrhand *ih; + uint32_t pin = cells[0]; + uint32_t type = IST_NONE; + uint32_t reg; + + KASSERT(pin < sc->sc_ngpios); + KASSERT(sc->sc_handler[pin] == NULL); + + if (ci != NULL && !CPU_IS_PRIMARY(ci)) + return NULL; + + switch (cells[1]) { + case 1: + type = IST_EDGE_RISING; + break; + case 2: + type = IST_EDGE_FALLING; + break; + case 3: + type = IST_EDGE_BOTH; + break; + case 4: + type = IST_LEVEL_HIGH; + break; + case 8: + type = IST_LEVEL_LOW; + break; + } + + ih = malloc(sizeof(*ih), M_DEVBUF, M_WAITOK); + ih->ih_func = func; + ih->ih_arg = arg; + ih->ih_irq = pin; + ih->ih_type = type; + ih->ih_ipl = ipl; + ih->ih_name = name; + ih->ih_sc = sc; + + if (name != NULL) + evcount_attach(&ih->ih_count, name, &ih->ih_irq); + + sc->sc_handler[pin] = ih; + + reg = HREAD4(sc, GPIO_PIN(pin)); + reg &= ~GPIO_PIN_DATA; + reg &= ~GPIO_PIN_FUNC_MASK; + reg &= ~GPIO_PIN_MODE_MASK; + switch (type) { + case IST_NONE: + reg |= GPIO_PIN_MODE_IRQ_OFF; + break; + case IST_EDGE_RISING: + reg |= GPIO_PIN_MODE_IRQ_UP; + break; + case IST_EDGE_FALLING: + reg |= GPIO_PIN_MODE_IRQ_DN; + break; + case IST_EDGE_BOTH: + reg |= GPIO_PIN_MODE_IRQ_ANY; + break; + case IST_LEVEL_HIGH: + reg |= GPIO_PIN_MODE_IRQ_HI; + break; + case IST_LEVEL_LOW: + reg |= GPIO_PIN_MODE_IRQ_LO; + break; + } + reg |= GPIO_PIN_INPUT_ENABLE; + reg &= ~GPIO_PIN_GROUP_MASK; + HWRITE4(sc, GPIO_PIN(pin), reg); + + return ih; +} + +void +aplpinctrl_intr_disestablish(void *cookie) +{ + struct intrhand *ih = cookie; + struct aplpinctrl_softc *sc = ih->ih_sc; + uint32_t reg; + int s; + + s = splhigh(); + + reg = HREAD4(sc, GPIO_PIN(ih->ih_irq)); + reg &= ~GPIO_PIN_MODE_MASK; + reg |= GPIO_PIN_MODE_IRQ_OFF; + HWRITE4(sc, GPIO_PIN(ih->ih_irq), reg); + + sc->sc_handler[ih->ih_irq] = NULL; + if (ih->ih_name) + evcount_detach(&ih->ih_count); + free(ih, M_DEVBUF, sizeof(*ih)); + + splx(s); +} + +void +aplpinctrl_intr_enable(void *cookie) +{ + struct intrhand *ih = cookie; + struct aplpinctrl_softc *sc = ih->ih_sc; + uint32_t reg; + int s; + + s = splhigh(); + reg = HREAD4(sc, GPIO_PIN(ih->ih_irq)); + reg &= ~GPIO_PIN_MODE_MASK; + switch (ih->ih_type) { + case IST_NONE: + reg |= GPIO_PIN_MODE_IRQ_OFF; + break; + case IST_EDGE_RISING: + reg |= GPIO_PIN_MODE_IRQ_UP; + break; + case IST_EDGE_FALLING: + reg |= GPIO_PIN_MODE_IRQ_DN; + break; + case IST_EDGE_BOTH: + reg |= GPIO_PIN_MODE_IRQ_ANY; + break; + case IST_LEVEL_HIGH: + reg |= GPIO_PIN_MODE_IRQ_HI; + break; + case IST_LEVEL_LOW: + reg |= GPIO_PIN_MODE_IRQ_LO; + break; + } + HWRITE4(sc, GPIO_PIN(ih->ih_irq), reg); + splx(s); +} + +void +aplpinctrl_intr_disable(void *cookie) +{ + struct intrhand *ih = cookie; + struct aplpinctrl_softc *sc = ih->ih_sc; + uint32_t reg; + int s; + + s = splhigh(); + reg = HREAD4(sc, GPIO_PIN(ih->ih_irq)); + reg &= ~GPIO_PIN_MODE_MASK; + reg |= GPIO_PIN_MODE_IRQ_OFF; + HWRITE4(sc, GPIO_PIN(ih->ih_irq), reg); + splx(s); +} + +void +aplpinctrl_intr_barrier(void *cookie) +{ + struct intrhand *ih = cookie; + struct aplpinctrl_softc *sc = ih->ih_sc; + + intr_barrier(sc->sc_ih); +} |