xref: /freebsd/sys/dev/gpio/acpi_gpiobus.c (revision 56b17de1e8360fe131d425de20b5e75ff3ea897c)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2024 Ahmad Khalifa <ahmadkhalifa570@gmail.com>
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 AUTHORS 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 AUTHORS 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/kernel.h>
31 #include <sys/module.h>
32 #include <sys/gpio.h>
33 
34 #include <contrib/dev/acpica/include/acpi.h>
35 #include <dev/acpica/acpivar.h>
36 
37 #include <dev/gpio/gpiobusvar.h>
38 
39 struct acpi_gpiobus_softc {
40 	struct gpiobus_softc	super_sc;
41 	ACPI_CONNECTION_INFO	handler_info;
42 };
43 
44 struct acpi_gpiobus_ctx {
45 	struct gpiobus_softc	*sc;
46 	ACPI_HANDLE		dev_handle;
47 };
48 
49 static uint32_t
50 acpi_gpiobus_convflags(ACPI_RESOURCE_GPIO *gpio_res)
51 {
52 	uint32_t flags = 0;
53 
54 	/* Figure out pin flags */
55 #ifdef NOT_YET
56 	/* These are currently unused. */
57 	if (gpio_res->ConnectionType == ACPI_RESOURCE_GPIO_TYPE_INT) {
58 		switch (gpio_res->Polarity) {
59 		case ACPI_ACTIVE_HIGH:
60 			flags = gpio_res->Triggering == ACPI_LEVEL_SENSITIVE ?
61 			    GPIO_INTR_LEVEL_HIGH : GPIO_INTR_EDGE_RISING;
62 			break;
63 		case ACPI_ACTIVE_LOW:
64 			flags = gpio_res->Triggering == ACPI_LEVEL_SENSITIVE ?
65 			    GPIO_INTR_LEVEL_LOW : GPIO_INTR_EDGE_FALLING;
66 			break;
67 		case ACPI_ACTIVE_BOTH:
68 			flags = GPIO_INTR_EDGE_BOTH;
69 			break;
70 		}
71 
72 		if (gpio_res->Shareable == ACPI_SHARED)
73 			flags |= GPIO_INTR_SHAREABLE;
74 	}
75 #endif
76 	switch (gpio_res->IoRestriction) {
77 	case ACPI_IO_RESTRICT_INPUT:
78 		flags |= GPIO_PIN_INPUT;
79 		break;
80 	case ACPI_IO_RESTRICT_OUTPUT:
81 		flags |= GPIO_PIN_OUTPUT;
82 		break;
83 	}
84 
85 	switch (gpio_res->PinConfig) {
86 	case ACPI_PIN_CONFIG_PULLUP:
87 		flags |= GPIO_PIN_PULLUP;
88 		break;
89 	case ACPI_PIN_CONFIG_PULLDOWN:
90 		flags |= GPIO_PIN_PULLDOWN;
91 		break;
92 	}
93 
94 	return (flags);
95 }
96 
97 static ACPI_STATUS
98 acpi_gpiobus_enumerate_res(ACPI_RESOURCE *res, void *context)
99 {
100 	ACPI_RESOURCE_GPIO *gpio_res = &res->Data.Gpio;
101 	struct acpi_gpiobus_ctx *ctx = context;
102 	struct gpiobus_softc *super_sc = ctx->sc;
103 	ACPI_HANDLE handle;
104 	uint32_t flags, i;
105 
106 	if (res->Type != ACPI_RESOURCE_TYPE_GPIO)
107 		return (AE_OK);
108 
109 	if (ACPI_FAILURE(AcpiGetHandle(ACPI_ROOT_OBJECT,
110 	    gpio_res->ResourceSource.StringPtr, &handle)) ||
111 	    handle != ctx->dev_handle)
112 		return (AE_OK);
113 
114 	if (__predict_false(gpio_res->PinTableLength > super_sc->sc_npins)) {
115 		device_printf(super_sc->sc_busdev,
116 		    "invalid pin table length %hu, max: %d (bad ACPI tables?)\n",
117 		    gpio_res->PinTableLength, super_sc->sc_npins);
118 		return (AE_LIMIT);
119 	}
120 
121 	flags = acpi_gpiobus_convflags(gpio_res);
122 	for (i = 0; i < gpio_res->PinTableLength; i++) {
123 		UINT16 pin = gpio_res->PinTable[i];
124 
125 		if (__predict_false(pin >= super_sc->sc_npins)) {
126 			device_printf(super_sc->sc_busdev,
127 			    "invalid pin 0x%x, max: 0x%x (bad ACPI tables?)\n",
128 			    pin, super_sc->sc_npins - 1);
129 			return (AE_LIMIT);
130 		}
131 
132 		GPIO_PIN_SETFLAGS(super_sc->sc_dev, pin, flags &
133 		    ~GPIO_INTR_MASK);
134 	}
135 
136 	return (AE_OK);
137 }
138 
139 static ACPI_STATUS
140 acpi_gpiobus_enumerate(ACPI_HANDLE handle, UINT32 depth, void *context,
141     void **result)
142 {
143 	UINT32 sta;
144 
145 	/*
146 	 * If no _STA method or if it failed, then assume that
147 	 * the device is present.
148 	 */
149 	if (!ACPI_FAILURE(acpi_GetInteger(handle, "_STA", &sta)) &&
150 	    !ACPI_DEVICE_PRESENT(sta))
151 		return (AE_OK);
152 
153 	if (!acpi_has_hid(handle))
154 		return (AE_OK);
155 
156 	/* Look for GPIO resources */
157 	AcpiWalkResources(handle, "_CRS", acpi_gpiobus_enumerate_res, context);
158 
159 	return (AE_OK);
160 }
161 
162 static ACPI_STATUS
163 acpi_gpiobus_space_handler(UINT32 function, ACPI_PHYSICAL_ADDRESS address,
164     UINT32 length, UINT64 *value, void *context, void *region_context)
165 {
166 	ACPI_CONNECTION_INFO *info = context;
167 	ACPI_RESOURCE_GPIO *gpio_res;
168 	device_t controller;
169 	ACPI_RESOURCE *res;
170 	ACPI_STATUS status;
171 
172 	status = AcpiBufferToResource(info->Connection, info->Length, &res);
173 	if (ACPI_FAILURE(status) || res->Type != ACPI_RESOURCE_TYPE_GPIO)
174 		goto err;
175 
176 	gpio_res = &res->Data.Gpio;
177 	controller = __containerof(info, struct acpi_gpiobus_softc,
178 	    handler_info)->super_sc.sc_dev;
179 
180 	switch (function) {
181 	case ACPI_WRITE:
182 		if (__predict_false(
183 		    gpio_res->IoRestriction == ACPI_IO_RESTRICT_INPUT))
184 			goto err;
185 
186 		for (int i = 0; i < length; i++)
187 			if (GPIO_PIN_SET(controller,
188 			    gpio_res->PinTable[address + i], (*value & 1 << i) ?
189 			    GPIO_PIN_HIGH : GPIO_PIN_LOW) != 0)
190 				goto err;
191 		break;
192 	case ACPI_READ:
193 		if (__predict_false(
194 		    gpio_res->IoRestriction == ACPI_IO_RESTRICT_OUTPUT))
195 			goto err;
196 
197 		for (int i = 0; i < length; i++) {
198 			uint32_t v;
199 
200 			if (GPIO_PIN_GET(controller,
201 			    gpio_res->PinTable[address + i], &v) != 0)
202 				goto err;
203 			*value |= v << i;
204 		}
205 		break;
206 	default:
207 		goto err;
208 	}
209 
210 	ACPI_FREE(res);
211 	return (AE_OK);
212 
213 err:
214 	ACPI_FREE(res);
215 	return (AE_BAD_PARAMETER);
216 }
217 
218 static int
219 acpi_gpiobus_probe(device_t dev)
220 {
221 	device_t controller;
222 
223 	if (acpi_disabled("gpiobus"))
224 		return (ENXIO);
225 
226 	controller = device_get_parent(dev);
227 	if (controller == NULL)
228 		return (ENXIO);
229 
230 	if (acpi_get_handle(controller) == NULL)
231 		return (ENXIO);
232 
233 	device_set_desc(dev, "GPIO bus (ACPI-hinted)");
234 	return (BUS_PROBE_DEFAULT);
235 }
236 
237 static int
238 acpi_gpiobus_attach(device_t dev)
239 {
240 	struct acpi_gpiobus_softc *sc;
241 	struct acpi_gpiobus_ctx ctx;
242 	ACPI_HANDLE handle;
243 	ACPI_STATUS status;
244 	int err;
245 
246 	if ((err = gpiobus_attach(dev)) != 0)
247 		return (err);
248 
249 	sc = device_get_softc(dev);
250 	handle = acpi_get_handle(sc->super_sc.sc_dev);
251 	if (handle == NULL) {
252 		gpiobus_detach(dev);
253 		return (ENXIO);
254 	}
255 
256 	status = AcpiInstallAddressSpaceHandler(handle, ACPI_ADR_SPACE_GPIO,
257 	    acpi_gpiobus_space_handler, NULL, &sc->handler_info);
258 
259 	if (ACPI_FAILURE(status)) {
260 		device_printf(dev,
261 		    "Failed to install GPIO address space handler\n");
262 		gpiobus_detach(dev);
263 		return (ENXIO);
264 	}
265 
266 	ctx.dev_handle = handle;
267 	ctx.sc = &sc->super_sc;
268 
269 	status = AcpiWalkNamespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
270 	    ACPI_UINT32_MAX, acpi_gpiobus_enumerate, NULL, &ctx, NULL);
271 
272 	if (ACPI_FAILURE(status))
273 		device_printf(dev, "Failed to enumerate GPIO resources\n");
274 
275 	return (0);
276 }
277 
278 static int
279 acpi_gpiobus_detach(device_t dev)
280 {
281 	struct gpiobus_softc *super_sc;
282 	ACPI_STATUS status;
283 
284 	super_sc = device_get_softc(dev);
285 	status = AcpiRemoveAddressSpaceHandler(
286 	    acpi_get_handle(super_sc->sc_dev), ACPI_ADR_SPACE_GPIO,
287 	    acpi_gpiobus_space_handler
288 	);
289 
290 	if (ACPI_FAILURE(status))
291 		device_printf(dev,
292 		    "Failed to remove GPIO address space handler\n");
293 
294 	return (gpiobus_detach(dev));
295 }
296 
297 static device_method_t acpi_gpiobus_methods[] = {
298 	/* Device interface */
299 	DEVMETHOD(device_probe,		acpi_gpiobus_probe),
300 	DEVMETHOD(device_attach,	acpi_gpiobus_attach),
301 	DEVMETHOD(device_detach,	acpi_gpiobus_detach),
302 
303 	DEVMETHOD_END
304 };
305 
306 DEFINE_CLASS_1(gpiobus, acpi_gpiobus_driver, acpi_gpiobus_methods,
307     sizeof(struct acpi_gpiobus_softc), gpiobus_driver);
308 EARLY_DRIVER_MODULE(acpi_gpiobus, gpio, acpi_gpiobus_driver, NULL, NULL,
309     BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE);
310 MODULE_VERSION(acpi_gpiobus, 1);
311 MODULE_DEPEND(acpi_gpiobus, acpi, 1, 1, 1);
312