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