Module Name:    src
Committed By:   jmcneill
Date:           Sun Oct 21 18:31:58 UTC 2018

Modified Files:
        src/sys/dev/acpi: plgpio_acpi.c

Log Message:
Add support for GPIO interrupt signaled ACPI events.


To generate a diff of this commit:
cvs rdiff -u -r1.1 -r1.2 src/sys/dev/acpi/plgpio_acpi.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/dev/acpi/plgpio_acpi.c
diff -u src/sys/dev/acpi/plgpio_acpi.c:1.1 src/sys/dev/acpi/plgpio_acpi.c:1.2
--- src/sys/dev/acpi/plgpio_acpi.c:1.1	Mon Oct 15 23:59:16 2018
+++ src/sys/dev/acpi/plgpio_acpi.c	Sun Oct 21 18:31:58 2018
@@ -1,4 +1,4 @@
-/* $NetBSD: plgpio_acpi.c,v 1.1 2018/10/15 23:59:16 jmcneill Exp $ */
+/* $NetBSD: plgpio_acpi.c,v 1.2 2018/10/21 18:31:58 jmcneill Exp $ */
 
 /*-
  * Copyright (c) 2018 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: plgpio_acpi.c,v 1.1 2018/10/15 23:59:16 jmcneill Exp $");
+__KERNEL_RCSID(0, "$NetBSD: plgpio_acpi.c,v 1.2 2018/10/21 18:31:58 jmcneill Exp $");
 
 #include <sys/param.h>
 #include <sys/bus.h>
@@ -43,11 +43,35 @@ __KERNEL_RCSID(0, "$NetBSD: plgpio_acpi.
 
 #include <dev/gpio/gpiovar.h>
 #include <dev/ic/pl061var.h>
+#include <dev/ic/pl061reg.h>
+
+struct plgpio_acpi_softc;
+
+struct plgpio_acpi_event {
+	struct plgpio_acpi_softc *ev_sc;
+	u_int			ev_pin;
+	ACPI_HANDLE		ev_method;
+	bool			ev_method_evt;
+};
+
+struct plgpio_acpi_softc {
+	struct plgpio_softc	sc_base;
+
+	ACPI_HANDLE		sc_handle;
+	uint32_t		sc_aei_pins;		/* bitmask */
+	ACPI_RESOURCE_GPIO	sc_aei_res[8];
+
+	struct plgpio_acpi_event sc_event[8];
+};
 
 static int	plgpio_acpi_match(device_t, cfdata_t, void *);
 static void	plgpio_acpi_attach(device_t, device_t, void *);
 
-CFATTACH_DECL_NEW(plgpio_acpi, sizeof(struct plgpio_softc), plgpio_acpi_match, plgpio_acpi_attach, NULL, NULL);
+static void	plgpio_acpi_init(struct plgpio_acpi_softc *);
+static void	plgpio_acpi_notify(void *);
+static int	plgpio_acpi_intr(void *);
+
+CFATTACH_DECL_NEW(plgpio_acpi, sizeof(struct plgpio_acpi_softc), plgpio_acpi_match, plgpio_acpi_attach, NULL, NULL);
 
 static const char * const compatible[] = {
 	"ARMH0061",
@@ -68,14 +92,18 @@ plgpio_acpi_match(device_t parent, cfdat
 static void
 plgpio_acpi_attach(device_t parent, device_t self, void *aux)
 {
-	struct plgpio_softc * const sc = device_private(self);
+	struct plgpio_acpi_softc * const asc = device_private(self);
+	struct plgpio_softc * const sc = &asc->sc_base;
 	struct acpi_attach_args *aa = aux;
 	struct acpi_resources res;
 	struct acpi_mem *mem;
+	struct acpi_irq *irq;
 	ACPI_STATUS rv;
 	int error;
+	void *ih;
 
 	sc->sc_dev = self;
+	asc->sc_handle = aa->aa_node->ad_handle;
 
 	rv = acpi_resource_parse(sc->sc_dev, aa->aa_node->ad_handle, "_CRS",
 	    &res, &acpi_resource_parse_ops_default);
@@ -88,6 +116,12 @@ plgpio_acpi_attach(device_t parent, devi
 		goto done;
 	}
 
+	irq = acpi_res_irq(&res, 0);
+	if (mem == NULL) {
+		aprint_error_dev(self, "couldn't find irq resource\n");
+		goto done;
+	}
+
 	sc->sc_dev = self;
 	sc->sc_bst = aa->aa_memt;
 	error = bus_space_map(sc->sc_bst, mem->ar_base, mem->ar_length, 0, &sc->sc_bsh);
@@ -98,6 +132,184 @@ plgpio_acpi_attach(device_t parent, devi
 
 	plgpio_attach(sc);
 
+	const int type = (irq->ar_type == ACPI_EDGE_SENSITIVE) ? IST_EDGE : IST_LEVEL;
+	ih = intr_establish(irq->ar_irq, IPL_VM, type, plgpio_acpi_intr, asc);
+	if (ih == NULL)
+		aprint_error_dev(self, "couldn't establish interrupt\n");
+
+	plgpio_acpi_init(asc);
+
 done:
 	acpi_resource_cleanup(&res);
 }
+
+static ACPI_STATUS
+plgpio_acpi_resource_cb(ACPI_RESOURCE *res, void *ctx)
+{
+	struct plgpio_acpi_softc * const asc = ctx;
+	ACPI_RESOURCE_GPIO *gpio;
+	UINT16 pin;
+
+	if (res->Type != ACPI_RESOURCE_TYPE_GPIO)
+		return AE_OK;
+
+	gpio = &res->Data.Gpio;
+	if (gpio->ConnectionType != ACPI_RESOURCE_GPIO_TYPE_INT ||
+	    gpio->PinTableLength != 1)
+		return AE_OK;
+
+	pin = gpio->PinTable[0];
+	if (pin >= __arraycount(asc->sc_aei_res)) {
+		aprint_error_dev(asc->sc_base.sc_dev, "_AEI pin %u out of range\n", pin);
+		return AE_OK;
+	}
+
+	asc->sc_aei_pins |= __BIT(pin);
+	asc->sc_aei_res[pin] = *gpio;
+
+	return AE_OK;
+}
+
+static void
+plgpio_acpi_init(struct plgpio_acpi_softc *asc)
+{
+	struct plgpio_softc * const sc = &asc->sc_base;
+	ACPI_RESOURCE_GPIO *gpio;
+	ACPI_HANDLE handle;
+	char namebuf[5];
+	ACPI_STATUS rv;
+	uint32_t ibe, iev, is, ie;
+	int pin;
+
+	rv = AcpiWalkResources(asc->sc_handle, "_AEI", plgpio_acpi_resource_cb, asc);
+	if (ACPI_FAILURE(rv)) {
+		if (rv != AE_NOT_FOUND)
+			aprint_error_dev(asc->sc_base.sc_dev, "failed to parse _AEI: %s\n",
+			    AcpiFormatException(rv));
+		return;
+	}
+
+	if (!asc->sc_aei_pins)
+		return;
+
+	aprint_verbose_dev(asc->sc_base.sc_dev, "ACPI event pins: %#x\n", asc->sc_aei_pins);
+
+	sc->sc_reserved_mask = asc->sc_aei_pins;
+
+	/*
+	 * For each event pin, find the corresponding event method (_Exx, _Lxx, or _EVT).
+	 */
+	for (pin = 0; pin < __arraycount(asc->sc_aei_res); pin++) {
+		if ((asc->sc_aei_pins & __BIT(pin)) == 0)
+			continue;
+
+		gpio = &asc->sc_aei_res[pin];
+
+		const char trig = gpio->Triggering == ACPI_LEVEL_SENSITIVE ? 'L' : 'E';
+		handle = NULL;
+		snprintf(namebuf, sizeof(namebuf), "_%c%02X", trig, pin);
+		if (ACPI_FAILURE(AcpiGetHandle(asc->sc_handle, namebuf, &handle))) {
+			(void)AcpiGetHandle(asc->sc_handle, "_EVT", &handle);
+			if (handle != NULL)
+				asc->sc_event[pin].ev_method_evt = true;
+		}
+
+		if (handle == NULL)
+			continue;
+
+		asc->sc_event[pin].ev_sc = asc;
+		asc->sc_event[pin].ev_pin = pin;
+		asc->sc_event[pin].ev_method = handle;
+
+		/*
+		 * Configure and enable interrupts for this pin.
+		 */
+
+		ibe = PLGPIO_READ(sc, PL061_GPIOIBE_REG);
+		iev = PLGPIO_READ(sc, PL061_GPIOIEV_REG);
+		switch (gpio->Polarity) {
+		case ACPI_ACTIVE_HIGH:
+			ibe &= ~__BIT(pin);
+			iev |= __BIT(pin);
+			break;
+		case ACPI_ACTIVE_LOW:
+			ibe &= ~__BIT(pin);
+			iev &= ~__BIT(pin);
+			break;
+		case ACPI_ACTIVE_BOTH:
+			ibe |= __BIT(pin);
+			break;
+		}
+		PLGPIO_WRITE(sc, PL061_GPIOIBE_REG, ibe);
+		PLGPIO_WRITE(sc, PL061_GPIOIEV_REG, iev);
+
+		is = PLGPIO_READ(sc, PL061_GPIOIS_REG);
+		switch (gpio->Triggering) {
+		case ACPI_LEVEL_SENSITIVE:
+			is |= __BIT(pin);
+			break;
+		case ACPI_EDGE_SENSITIVE:
+			is &= ~__BIT(pin);
+			break;
+		}
+		PLGPIO_WRITE(sc, PL061_GPIOIS_REG, is);
+
+		delay(20);
+
+		PLGPIO_WRITE(sc, PL061_GPIOIC_REG, __BIT(pin));
+
+		ie = PLGPIO_READ(sc, PL061_GPIOIE_REG);
+		ie |= __BIT(pin);
+		PLGPIO_WRITE(sc, PL061_GPIOIE_REG, ie);
+	}
+}
+
+static void
+plgpio_acpi_notify(void *priv)
+{
+	struct plgpio_acpi_event * const ev = priv;
+	struct plgpio_acpi_softc * const asc = ev->ev_sc;
+	struct plgpio_softc * const sc = &asc->sc_base;
+	ACPI_STATUS rv;
+
+	if (ev->ev_method_evt) {
+		ACPI_OBJECT_LIST objs;
+		ACPI_OBJECT obj[1];
+		objs.Count = 1;
+		objs.Pointer = obj;
+		obj[0].Type = ACPI_TYPE_INTEGER;
+		obj[0].Integer.Value = ev->ev_pin;
+		rv = AcpiEvaluateObject(ev->ev_method, NULL, &objs, NULL);
+	} else {
+		rv = AcpiEvaluateObject(ev->ev_method, NULL, NULL, NULL);
+	}
+
+	if (ACPI_FAILURE(rv))
+		device_printf(sc->sc_dev, "failed to handle %s event: %s\n",
+		    acpi_name(ev->ev_method), AcpiFormatException(rv));
+}
+
+static int
+plgpio_acpi_intr(void *priv)
+{
+	struct plgpio_acpi_softc * const asc = priv;
+	struct plgpio_softc * const sc = &asc->sc_base;
+	uint32_t mis;
+	int bit;
+
+	mis = PLGPIO_READ(sc, PL061_GPIOMIS_REG);
+	PLGPIO_WRITE(sc, PL061_GPIOIC_REG, mis);
+
+	while ((bit = __builtin_ffs(mis)) != 0) {
+		const int pin = bit - 1;
+		struct plgpio_acpi_event * const ev = &asc->sc_event[pin];
+
+		KASSERT(ev->ev_method != NULL);
+
+		AcpiOsExecute(OSL_NOTIFY_HANDLER, plgpio_acpi_notify, ev);
+
+		mis &= ~__BIT(pin);
+	}
+
+	return 1;
+}

Reply via email to