summaryrefslogtreecommitdiff
path: root/sys/arch/arm64
diff options
context:
space:
mode:
authorMark Kettenis <kettenis@cvs.openbsd.org>2021-10-30 14:50:55 +0000
committerMark Kettenis <kettenis@cvs.openbsd.org>2021-10-30 14:50:55 +0000
commitfdd3b63244d75968427160bd33b13d8675976523 (patch)
tree0ec04682f18610551282c18c7f509807f5459dd2 /sys/arch/arm64
parent5f1ce79155a4ed7be219449075923a0d9071ae3b (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.c338
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);
+}