xref: /freebsd/sys/dev/gpio/gpioaei.c (revision ed60e694f4a975eaf269ae3784aa6b4fd4aa582d)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2024 Colin Percival
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/types.h>
29 #include <sys/bus.h>
30 #include <sys/gpio.h>
31 #include <sys/kernel.h>
32 #include <sys/module.h>
33 
34 #include "gpiobus_if.h"
35 
36 #include <contrib/dev/acpica/include/acpi.h>
37 #include <dev/acpica/acpivar.h>
38 
39 #include <dev/gpio/gpiobusvar.h>
40 #include <dev/gpio/acpi_gpiobusvar.h>
41 
42 enum gpio_aei_type {
43 	ACPI_AEI_TYPE_UNKNOWN,
44 	ACPI_AEI_TYPE_ELX,
45 	ACPI_AEI_TYPE_EVT
46 };
47 
48 struct gpio_aei_ctx {
49 	SLIST_ENTRY(gpio_aei_ctx) next;
50 	struct resource * intr_res;
51 	void * intr_cookie;
52 	ACPI_HANDLE handle;
53 	gpio_pin_t gpio;
54 	uint32_t pin;
55 	int intr_rid;
56 	enum gpio_aei_type type;
57 };
58 
59 struct gpio_aei_softc {
60 	SLIST_HEAD(, gpio_aei_ctx) aei_ctx;
61 	ACPI_HANDLE dev_handle;
62 	device_t dev;
63 };
64 
65 static int
gpio_aei_probe(device_t dev)66 gpio_aei_probe(device_t dev)
67 {
68 
69 	/* We only match when gpiobus explicitly requested gpio_aei. */
70 	return (BUS_PROBE_NOWILDCARD);
71 }
72 
73 static void
gpio_aei_intr(void * arg)74 gpio_aei_intr(void * arg)
75 {
76 	struct gpio_aei_ctx * ctx = arg;
77 
78 	/* Ask ACPI to run the appropriate _EVT, _Exx or _Lxx method. */
79 	if (ctx->type == ACPI_AEI_TYPE_EVT)
80 		acpi_SetInteger(ctx->handle, NULL, ctx->pin);
81 	else
82 		AcpiEvaluateObject(ctx->handle, NULL, NULL, NULL);
83 }
84 
85 static ACPI_STATUS
gpio_aei_enumerate(ACPI_RESOURCE * res,void * context)86 gpio_aei_enumerate(ACPI_RESOURCE * res, void * context)
87 {
88 	ACPI_RESOURCE_GPIO * gpio_res = &res->Data.Gpio;
89 	struct gpio_aei_softc * sc = context;
90 	uint32_t flags, maxpin;
91 	device_t busdev;
92 	int err;
93 
94 	/*
95 	 * Check that we have a GpioInt object.
96 	 * Note that according to the spec this
97 	 * should always be the case.
98 	 */
99 	if (res->Type != ACPI_RESOURCE_TYPE_GPIO)
100 		return (AE_OK);
101 	if (gpio_res->ConnectionType != ACPI_RESOURCE_GPIO_TYPE_INT)
102 		return (AE_OK);
103 
104 	flags = acpi_gpiobus_convflags(gpio_res);
105 	if (acpi_quirks & ACPI_Q_AEI_NOPULL)
106 		flags &= ~GPIO_PIN_PULLUP;
107 
108 	err = GPIO_PIN_MAX(acpi_get_device(sc->dev_handle), &maxpin);
109 	if (err != 0)
110 		return (AE_ERROR);
111 
112 	busdev = GPIO_GET_BUS(acpi_get_device(sc->dev_handle));
113 	for (int i = 0; i < gpio_res->PinTableLength; i++) {
114 		struct gpio_aei_ctx * ctx;
115 		uint32_t pin = gpio_res->PinTable[i];
116 
117 		if (__predict_false(pin > maxpin)) {
118 			device_printf(sc->dev,
119 			    "Invalid pin 0x%x, max: 0x%x (bad ACPI tables?)\n",
120 			    pin, maxpin);
121 			continue;
122 		}
123 
124 		ctx = malloc(sizeof(struct gpio_aei_ctx), M_DEVBUF, M_WAITOK);
125 		ctx->type = ACPI_AEI_TYPE_UNKNOWN;
126 		if (pin <= 255) {
127 			char objname[5];	/* "_EXX" or "_LXX" */
128 			sprintf(objname, "_%c%02X",
129 			    (flags & GPIO_INTR_EDGE_MASK) ? 'E' : 'L', pin);
130 			if (ACPI_SUCCESS(AcpiGetHandle(sc->dev_handle, objname,
131 			    &ctx->handle)))
132 				ctx->type = ACPI_AEI_TYPE_ELX;
133 		}
134 
135 		if (ctx->type == ACPI_AEI_TYPE_UNKNOWN) {
136 			if (ACPI_SUCCESS(AcpiGetHandle(sc->dev_handle, "_EVT",
137 			    &ctx->handle)))
138 				ctx->type = ACPI_AEI_TYPE_EVT;
139 			else {
140 				device_printf(sc->dev,
141 				    "AEI Device type is unknown for pin 0x%x\n",
142 				    pin);
143 
144 				free(ctx, M_DEVBUF);
145 				continue;
146 			}
147 		}
148 
149 		err = gpio_pin_get_by_bus_pinnum(busdev, pin, &ctx->gpio);
150 		if (err != 0) {
151 			device_printf(sc->dev, "Cannot acquire pin 0x%x\n",
152 			    pin);
153 
154 			free(ctx, M_DEVBUF);
155 			continue;
156 		}
157 
158 		err = gpio_pin_setflags(ctx->gpio, flags & ~GPIO_INTR_MASK);
159 		if (err != 0) {
160 			device_printf(sc->dev,
161 			    "Cannot set pin flags for pin 0x%x\n", pin);
162 
163 			gpio_pin_release(ctx->gpio);
164 			free(ctx, M_DEVBUF);
165 			continue;
166 		}
167 
168 		ctx->intr_rid = 0;
169 		ctx->intr_res = gpio_alloc_intr_resource(sc->dev,
170 		    &ctx->intr_rid, RF_ACTIVE, ctx->gpio,
171 		    flags & GPIO_INTR_MASK);
172 		if (ctx->intr_res == NULL) {
173 			device_printf(sc->dev,
174 			    "Cannot allocate an IRQ for pin 0x%x\n", pin);
175 
176 			gpio_pin_release(ctx->gpio);
177 			free(ctx, M_DEVBUF);
178 			continue;
179 		}
180 
181 		err = bus_setup_intr(sc->dev, ctx->intr_res, INTR_TYPE_MISC |
182 		    INTR_MPSAFE | INTR_EXCL | INTR_SLEEPABLE, NULL,
183 		    gpio_aei_intr, ctx, &ctx->intr_cookie);
184 		if (err != 0) {
185 			device_printf(sc->dev,
186 			    "Cannot set up an IRQ for pin 0x%x\n", pin);
187 
188 			bus_release_resource(sc->dev, ctx->intr_res);
189 			gpio_pin_release(ctx->gpio);
190 			free(ctx, M_DEVBUF);
191 			continue;
192 		}
193 
194 		ctx->pin = pin;
195 		SLIST_INSERT_HEAD(&sc->aei_ctx, ctx, next);
196 	}
197 
198 	return (AE_OK);
199 }
200 
201 static int
gpio_aei_attach(device_t dev)202 gpio_aei_attach(device_t dev)
203 {
204 	struct gpio_aei_softc * sc = device_get_softc(dev);
205 	ACPI_HANDLE handle;
206 	ACPI_STATUS status;
207 
208 	/* This is us. */
209 	device_set_desc(dev, "ACPI Event Information Device");
210 
211 	handle = acpi_gpiobus_get_handle(dev);
212 	status = AcpiGetParent(handle, &sc->dev_handle);
213 	if (ACPI_FAILURE(status)) {
214 		device_printf(dev, "Cannot get parent of %s\n",
215 		    acpi_name(handle));
216 		return (ENXIO);
217 	}
218 
219 	SLIST_INIT(&sc->aei_ctx);
220 	sc->dev = dev;
221 
222 	status = AcpiWalkResources(sc->dev_handle, "_AEI",
223 	    gpio_aei_enumerate, sc);
224 	if (ACPI_FAILURE(status)) {
225 		device_printf(dev, "Failed to enumerate AEI resources\n");
226 		return (ENXIO);
227 	}
228 
229 	return (0);
230 }
231 
232 static int
gpio_aei_detach(device_t dev)233 gpio_aei_detach(device_t dev)
234 {
235 	struct gpio_aei_softc * sc = device_get_softc(dev);
236 	struct gpio_aei_ctx * ctx, * tctx;
237 
238 	SLIST_FOREACH_SAFE(ctx, &sc->aei_ctx, next, tctx) {
239 		bus_teardown_intr(dev, ctx->intr_res, ctx->intr_cookie);
240 		bus_release_resource(dev, ctx->intr_res);
241 		gpio_pin_release(ctx->gpio);
242 		free(ctx, M_DEVBUF);
243 	}
244 
245 	return (0);
246 }
247 
248 static device_method_t gpio_aei_methods[] = {
249 	/* Device interface. */
250 	DEVMETHOD(device_probe,		gpio_aei_probe),
251 	DEVMETHOD(device_attach,	gpio_aei_attach),
252 	DEVMETHOD(device_detach,	gpio_aei_detach),
253 
254 	DEVMETHOD_END
255 };
256 
257 DEFINE_CLASS_0(gpio_aei, gpio_aei_driver, gpio_aei_methods, sizeof(struct gpio_aei_softc));
258 DRIVER_MODULE(gpio_aei, gpiobus, gpio_aei_driver, NULL, NULL);
259 MODULE_DEPEND(gpio_aei, acpi_gpiobus, 1, 1, 1);
260