xref: /netbsd-src/sys/dev/acpi/acpi_i2c.c (revision 84b6468db257bd512ee8a1ad7a66d1ff757f2049)
1 /* $NetBSD: acpi_i2c.c,v 1.19 2025/01/11 11:40:43 jmcneill Exp $ */
2 
3 /*-
4  * Copyright (c) 2017, 2021 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Manuel Bouyer.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include "iic.h"
33 
34 #include <sys/cdefs.h>
35 __KERNEL_RCSID(0, "$NetBSD: acpi_i2c.c,v 1.19 2025/01/11 11:40:43 jmcneill Exp $");
36 
37 #include <sys/device.h>
38 
39 #include <dev/acpi/acpireg.h>
40 #include <dev/acpi/acpivar.h>
41 #include <dev/acpi/acpi_i2c.h>
42 #include <external/bsd/acpica/dist/include/acinterp.h>
43 #include <external/bsd/acpica/dist/include/amlcode.h>
44 #include <dev/i2c/i2cvar.h>
45 
46 #include <sys/kmem.h>
47 
48 #define _COMPONENT	ACPI_BUS_COMPONENT
49 ACPI_MODULE_NAME	("acpi_i2c")
50 
51 struct acpi_i2c_address_space_context {
52 	ACPI_CONNECTION_INFO conn_info;	/* must be first */
53 	i2c_tag_t tag;
54 };
55 
56 static const struct device_compatible_entry hid_compat_data[] = {
57 	{ .compat = "PNP0C50" },
58 	DEVICE_COMPAT_EOL
59 };
60 
61 #if NIIC > 0
62 struct acpi_i2c_context {
63 	uint16_t i2c_addr;
64 	struct acpi_devnode *res_src;
65 };
66 #endif
67 
68 static struct acpi_devnode *
69 acpi_i2c_resource_find_source(ACPI_RESOURCE_SOURCE *rs)
70 {
71 	ACPI_STATUS rv;
72 	ACPI_HANDLE hdl;
73 	struct acpi_devnode *ad;
74 
75 	if (rs->StringPtr == NULL) {
76 		return NULL;
77 	}
78 
79 	rv = AcpiGetHandle(NULL, rs->StringPtr, &hdl);
80 	if (ACPI_FAILURE(rv)) {
81 		printf("%s: couldn't lookup '%s': %s\n", __func__,
82 		    rs->StringPtr, AcpiFormatException(rv));
83 		return NULL;
84 	}
85 
86 	SIMPLEQ_FOREACH(ad, &acpi_softc->sc_head, ad_list) {
87 		if (ad->ad_handle == hdl) {
88 			return ad;
89 		}
90 	}
91 
92 	printf("%s: no acpi devnode matching resource source '%s'\n",
93 	    __func__, rs->StringPtr);
94 	return NULL;
95 }
96 
97 static ACPI_STATUS
98 acpi_i2c_resource_parse_callback(ACPI_RESOURCE *res, void *context)
99 {
100 	struct acpi_i2c_context *i2cc = context;
101 
102 	switch (res->Type) {
103 	case ACPI_RESOURCE_TYPE_END_TAG:
104 		break;
105 	case ACPI_RESOURCE_TYPE_SERIAL_BUS:
106 		switch (res->Data.I2cSerialBus.Type) {
107 		case ACPI_RESOURCE_SERIAL_TYPE_I2C:
108 			i2cc->i2c_addr = res->Data.I2cSerialBus.SlaveAddress;
109 			i2cc->res_src = acpi_i2c_resource_find_source(
110 			    &res->Data.I2cSerialBus.ResourceSource);
111 			break;
112 		}
113 		break;
114 	case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:
115 		break;
116 	default:
117 		break;
118 	}
119 	return_ACPI_STATUS(AE_OK);
120 }
121 
122 static void
123 acpi_enter_i2c_device(struct acpi_devnode *ad, prop_array_t array)
124 {
125 	prop_dictionary_t dev;
126 	struct acpi_i2c_context i2cc;
127 	ACPI_STATUS rv;
128 	char *clist;
129 	size_t clist_size;
130 
131 	memset(&i2cc, 0, sizeof(i2cc));
132 	rv = AcpiWalkResources(ad->ad_handle, "_CRS",
133 	     acpi_i2c_resource_parse_callback, &i2cc);
134 	if (ACPI_FAILURE(rv)) {
135 		return;
136 	}
137 	if (i2cc.i2c_addr == 0)
138 		return;
139 	dev = prop_dictionary_create();
140 	if (dev == NULL) {
141 		aprint_error("ignoring device %s (no memory)\n",
142 		    ad->ad_name);
143 		return;
144 	}
145 	clist = acpi_pack_compat_list(ad, &clist_size);
146 	if (clist == NULL) {
147 		prop_object_release(dev);
148 		aprint_error("ignoring device %s (no _HID or _CID)\n",
149 		    ad->ad_name);
150 		return;
151 	}
152 	prop_dictionary_set_string(dev, "name", ad->ad_name);
153 	prop_dictionary_set_uint32(dev, "addr", i2cc.i2c_addr);
154 	prop_dictionary_set_uint64(dev, "cookie", (uintptr_t)ad->ad_handle);
155 	prop_dictionary_set_uint32(dev, "cookietype", I2C_COOKIE_ACPI);
156 	prop_dictionary_set_data(dev, "compatible", clist, clist_size);
157 	kmem_free(clist, clist_size);
158 
159 	prop_array_add(array, dev);
160 	prop_object_release(dev);
161 }
162 
163 static void
164 acpi_enter_i2chid_devs(device_t dev, struct acpi_devnode *devnode,
165     prop_array_t array)
166 {
167 	struct acpi_devnode *ad;
168 
169 	KASSERT(dev != NULL);
170 
171 	SIMPLEQ_FOREACH(ad, &acpi_softc->sc_head, ad_list) {
172 		struct acpi_attach_args aa = {
173 			.aa_node = ad
174 		};
175 		struct acpi_i2c_context i2cc;
176 		ACPI_STATUS rv;
177 
178 		if (!acpi_device_present(ad->ad_handle))
179 			continue;
180 		if (ad->ad_device != NULL)
181 			continue;
182 		if (acpi_compatible_match(&aa, hid_compat_data) == 0)
183 			continue;
184 
185 		memset(&i2cc, 0, sizeof(i2cc));
186 		rv = AcpiWalkResources(ad->ad_handle, "_CRS",
187 		    acpi_i2c_resource_parse_callback, &i2cc);
188 		if (ACPI_SUCCESS(rv) &&
189 		    i2cc.i2c_addr != 0 &&
190 		    i2cc.res_src == devnode) {
191 			aprint_debug_dev(dev, "claiming %s\n", ad->ad_name);
192 			ad->ad_device = dev;
193 			acpi_claim_childdevs(dev, ad, NULL);
194 			acpi_enter_i2c_device(ad, array);
195 		}
196 	}
197 }
198 
199 prop_array_t
200 acpi_enter_i2c_devs(device_t dev, struct acpi_devnode *devnode)
201 {
202 	struct acpi_devnode *ad;
203 	prop_array_t array = prop_array_create();
204 
205 	if (array == NULL)
206 		return NULL;
207 
208 	SIMPLEQ_FOREACH(ad, &devnode->ad_child_head, ad_child_list) {
209 		if (ad->ad_devinfo->Type != ACPI_TYPE_DEVICE)
210 			continue;
211 		if (!acpi_device_present(ad->ad_handle))
212 			continue;
213 		acpi_enter_i2c_device(ad, array);
214 	}
215 
216 	if (dev != NULL) {
217 		acpi_claim_childdevs(dev, devnode, "_CRS");
218 		acpi_claim_childdevs(dev, devnode, "_ADR");
219 		acpi_enter_i2chid_devs(dev, devnode, array);
220 	}
221 
222 	return array;
223 }
224 
225 #if NIIC > 0
226 static ACPI_STATUS
227 acpi_i2c_gsb_init(ACPI_HANDLE region_hdl, UINT32 function,
228     void *handler_ctx, void **region_ctx)
229 {
230 	if (function == ACPI_REGION_DEACTIVATE) {
231 		*region_ctx = NULL;
232 	} else {
233 		*region_ctx = region_hdl;
234 	}
235 	return AE_OK;
236 }
237 
238 static ACPI_STATUS
239 acpi_i2c_gsb_handler(UINT32 function, ACPI_PHYSICAL_ADDRESS address,
240     UINT32 bit_width, UINT64 *value, void *handler_ctx,
241     void *region_ctx)
242 {
243 	ACPI_OPERAND_OBJECT *region_obj = region_ctx;
244 	struct acpi_i2c_address_space_context *context = handler_ctx;
245 	UINT8 *buf = ACPI_CAST_PTR(uint8_t, value);
246 	ACPI_PHYSICAL_ADDRESS base_address;
247 	ACPI_RESOURCE *res;
248 	ACPI_STATUS rv;
249 	ACPI_CONNECTION_INFO *conn_info = &context->conn_info;
250 	i2c_tag_t tag = context->tag;
251 	i2c_addr_t i2c_addr;
252 	i2c_op_t op;
253 	union {
254 		uint8_t cmd8;
255 		uint16_t cmd16;
256 		uint32_t cmd32;
257 	} cmd;
258 	size_t buflen;
259 	size_t cmdlen;
260 	bool do_xfer = true;
261 
262 	if (region_obj->Region.Type != ACPI_TYPE_REGION) {
263 		return AE_OK;
264 	}
265 
266 	base_address = region_obj->Region.Address;
267 	KASSERT(region_obj->Region.SpaceId == ACPI_ADR_SPACE_GSBUS);
268 
269 	rv = AcpiBufferToResource(conn_info->Connection, conn_info->Length,
270 	    &res);
271 	if (ACPI_FAILURE(rv)) {
272 		return rv;
273 	}
274 	if (res->Type != ACPI_RESOURCE_TYPE_SERIAL_BUS ||
275 	    res->Data.CommonSerialBus.Type != ACPI_RESOURCE_SERIAL_TYPE_I2C) {
276 		return AE_TYPE;
277 	}
278 
279 	i2c_addr = res->Data.I2cSerialBus.SlaveAddress;
280 	if ((function & ACPI_IO_MASK) != 0) {
281 		op = I2C_OP_WRITE_WITH_STOP;
282 	} else {
283 		op = I2C_OP_READ_WITH_STOP;
284 	}
285 
286 #ifdef ACPI_I2C_DEBUG
287 	UINT32 length;
288 	rv = AcpiExGetProtocolBufferLength(function >> 16, &length);
289 	if (ACPI_FAILURE(rv)) {
290 		printf("%s AcpiExGetProtocolBufferLength failed: %s\n",
291 		    __func__, AcpiFormatException(rv));
292 		length = UINT32_MAX;
293 	}
294 	printf("%s %s: %s Attr %X Addr %.4X BaseAddr %.4X Length %.2X BitWidth %X BufLen %X",
295 	       __func__, AcpiUtGetRegionName(region_obj->Region.SpaceId),
296 	       (function & ACPI_IO_MASK) ? "Write" : "Read ",
297 	       (UINT32) (function >> 16),
298 	       (UINT32) address, (UINT32) base_address,
299 	       length, bit_width, buf[1]);
300 	printf(" [AccessLength %.2X Connection %p]\n",
301 	       conn_info->AccessLength, conn_info->Connection);
302 #endif
303 
304 	switch ((UINT32)(function >> 16)) {
305 	case AML_FIELD_ATTRIB_QUICK:
306 		cmdlen = 0;
307 		buflen = 0;
308 		break;
309 	case AML_FIELD_ATTRIB_SEND_RECEIVE:
310 		cmdlen = 0;
311 		buflen = 1;
312 		break;
313 	case AML_FIELD_ATTRIB_BYTE:
314 		cmdlen = bit_width / NBBY;
315 		buflen = 1;
316 		break;
317 	case AML_FIELD_ATTRIB_WORD:
318 		cmdlen = bit_width / NBBY;
319 		buflen = 2;
320 		break;
321 	case AML_FIELD_ATTRIB_BYTES:
322 		cmdlen = bit_width / NBBY;
323 		buflen = buf[1];
324 		break;
325 	case AML_FIELD_ATTRIB_BLOCK:
326 		cmdlen = bit_width / NBBY;
327 		buflen = buf[1];
328 		op |= I2C_OPMASK_BLKMODE;
329 		break;
330 	case AML_FIELD_ATTRIB_RAW_BYTES:
331 	case AML_FIELD_ATTRIB_RAW_PROCESS_BYTES:
332 	case AML_FIELD_ATTRIB_PROCESS_CALL:
333 	default:
334 		cmdlen = 0;
335 		do_xfer = false;
336 #ifdef ACPI_I2C_DEBUG
337 		printf("field attrib 0x%x not supported\n",
338 		    (UINT32)(function >> 16));
339 #endif
340 		break;
341 	}
342 
343 	switch (cmdlen) {
344 	case 0:
345 	case 1:
346 		cmd.cmd8 = (uint8_t)(base_address + address);
347 		break;
348 	case 2:
349 		cmd.cmd16 = (uint16_t)(base_address + address);
350 		break;
351 	case 4:
352 		cmd.cmd32 = (uint32_t)(base_address + address);
353 		break;
354 	default:
355 		do_xfer = false;
356 #ifdef ACPI_I2C_DEBUG
357 		printf("cmdlen %zu not supported\n", cmdlen);
358 #endif
359 		break;
360 	}
361 
362 	if (!do_xfer) {
363 		buf[0] = EINVAL;
364 	} else {
365 		const int flags = I2C_F_POLL;
366 		iic_acquire_bus(tag, flags);
367 		buf[0] = iic_exec(tag, op, i2c_addr,
368 				  &cmd, cmdlen, &buf[2], buflen, flags);
369 		iic_release_bus(tag, flags);
370 		if (buf[0] == 0) {
371 			buf[1] = buflen;
372 		}
373 #ifdef ACPI_I2C_DEBUG
374 		printf("%s iic_exec op %u addr 0x%x len %zu/%zu returned %d\n",
375 		    __func__, op, res->Data.I2cSerialBus.SlaveAddress, cmdlen,
376 		    buflen, buf[0]);
377 #endif
378 	}
379 
380 	ACPI_FREE(res);
381 
382 	return AE_OK;
383 }
384 #endif
385 
386 ACPI_STATUS
387 acpi_i2c_register(struct acpi_devnode *devnode, device_t dev, i2c_tag_t tag)
388 {
389 #if NIIC > 0
390 	struct acpi_i2c_address_space_context *context;
391 	ACPI_STATUS rv;
392 
393 	context = kmem_zalloc(sizeof(*context), KM_SLEEP);
394 	context->tag = tag;
395 
396 	rv = AcpiInstallAddressSpaceHandler(devnode->ad_handle,
397 	    ACPI_ADR_SPACE_GSBUS, acpi_i2c_gsb_handler, acpi_i2c_gsb_init,
398 	    context);
399 	if (ACPI_FAILURE(rv)) {
400 		aprint_error_dev(dev,
401 		    "couldn't install address space handler: %s",
402 		    AcpiFormatException(rv));
403 	}
404 
405 	return rv;
406 #else
407 	return AE_NOT_CONFIGURED;
408 #endif
409 }
410