xref: /netbsd-src/sys/dev/acpi/smbus_acpi.c (revision c12151368a60cab2da2b1b2d21ed0cc68309593d)
1 /* $NetBSD: smbus_acpi.c,v 1.18 2022/10/24 10:17:27 riastradh Exp $ */
2 
3 /*-
4  * Copyright (c) 2009 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Paul Goyette
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 /*
33  * ACPI SMBus Controller driver
34  *
35  * See http://smbus.org/specs/smbus_cmi10.pdf for specifications
36  */
37 
38 #include <sys/cdefs.h>
39 __KERNEL_RCSID(0, "$NetBSD: smbus_acpi.c,v 1.18 2022/10/24 10:17:27 riastradh Exp $");
40 
41 #include <sys/param.h>
42 #include <sys/device.h>
43 #include <sys/callout.h>
44 #include <sys/kernel.h>
45 #include <sys/mutex.h>
46 #include <sys/systm.h>
47 
48 #include <dev/acpi/acpireg.h>
49 #include <dev/acpi/acpivar.h>
50 
51 #include <dev/i2c/i2cvar.h>
52 
53 #define _COMPONENT		ACPI_BUS_COMPONENT
54 ACPI_MODULE_NAME		("smbus_acpi")
55 
56 /*
57  * ACPI SMBus CMI protocol codes.
58  */
59 #define	ACPI_SMBUS_RD_QUICK	0x03
60 #define	ACPI_SMBUS_RCV_BYTE	0x05
61 #define	ACPI_SMBUS_RD_BYTE	0x07
62 #define	ACPI_SMBUS_RD_WORD	0x09
63 #define	ACPI_SMBUS_RD_BLOCK	0x0B
64 #define	ACPI_SMBUS_WR_QUICK	0x02
65 #define	ACPI_SMBUS_SND_BYTE	0x04
66 #define	ACPI_SMBUS_WR_BYTE	0x06
67 #define	ACPI_SMBUS_WR_WORD	0x08
68 #define	ACPI_SMBUS_WR_BLOCK	0x0A
69 #define	ACPI_SMBUS_PROCESS_CALL	0x0C
70 
71 struct acpi_smbus_softc {
72 	struct acpi_devnode 	*sc_devnode;
73 	struct callout		sc_callout;
74 	struct i2c_controller	sc_i2c_tag;
75 	device_t		sc_dv;
76 	int			sc_poll_alert;
77 };
78 
79 static int	acpi_smbus_match(device_t, cfdata_t, void *);
80 static void	acpi_smbus_attach(device_t, device_t, void *);
81 static int	acpi_smbus_detach(device_t, int);
82 static int	acpi_smbus_poll_alert(ACPI_HANDLE, int *);
83 static int	acpi_smbus_exec(void *, i2c_op_t, i2c_addr_t, const void *,
84 				size_t, void *, size_t, int);
85 static void	acpi_smbus_alerts(struct acpi_smbus_softc *);
86 static void	acpi_smbus_tick(void *);
87 static void	acpi_smbus_notify_handler(ACPI_HANDLE, uint32_t, void *);
88 
89 struct SMB_UDID {
90 	uint8_t		dev_cap;
91 	uint8_t		vers_rev;
92 	uint16_t	vendor;
93 	uint16_t	device;
94 	uint16_t	interface;
95 	uint16_t	subsys_vendor;
96 	uint16_t	subsys_device;
97 	uint8_t		reserved[4];
98 };
99 
100 struct SMB_DEVICE {
101 	uint8_t		slave_addr;
102 	uint8_t		reserved;
103 	struct SMB_UDID	dev_id;
104 };
105 
106 struct SMB_INFO {
107 	uint8_t		struct_ver;
108 	uint8_t		spec_ver;
109 	uint8_t		hw_cap;
110 	uint8_t		poll_int;
111 	uint8_t		dev_count;
112 	struct SMB_DEVICE device[1];
113 };
114 
115 static const struct device_compatible_entry compat_data[] = {
116 	{ .compat = "SMBUS01" },	/* SMBus CMI v1.0 */
117 	DEVICE_COMPAT_EOL
118 };
119 
120 CFATTACH_DECL_NEW(acpismbus, sizeof(struct acpi_smbus_softc),
121     acpi_smbus_match, acpi_smbus_attach, acpi_smbus_detach, NULL);
122 
123 static int
acpi_smbus_match(device_t parent,cfdata_t match,void * aux)124 acpi_smbus_match(device_t parent, cfdata_t match, void *aux)
125 {
126 	struct acpi_attach_args *aa = aux;
127 	int ret;
128 
129 	ret = acpi_compatible_match(aa, compat_data);
130 	if (ret == 0)
131 		return 0;
132 
133 	return acpi_smbus_poll_alert(aa->aa_node->ad_handle, NULL) ? ret : 0;
134 }
135 
136 static void
acpi_smbus_attach(device_t parent,device_t self,void * aux)137 acpi_smbus_attach(device_t parent, device_t self, void *aux)
138 {
139 	struct acpi_smbus_softc *sc = device_private(self);
140 	struct acpi_attach_args *aa = aux;
141 	struct i2cbus_attach_args iba;
142 
143 	aprint_naive("\n");
144 
145 	sc->sc_devnode = aa->aa_node;
146 	sc->sc_dv = self;
147 	sc->sc_poll_alert = 2;
148 
149 	/* Attach I2C bus. */
150 	iic_tag_init(&sc->sc_i2c_tag);
151 	sc->sc_i2c_tag.ic_cookie = sc;
152 	sc->sc_i2c_tag.ic_exec = acpi_smbus_exec;
153 
154 	(void)acpi_smbus_poll_alert(aa->aa_node->ad_handle,&sc->sc_poll_alert);
155 
156 	/* If failed, fall-back to polling. */
157 	if (acpi_register_notify(sc->sc_devnode,
158 		acpi_smbus_notify_handler) != true)
159 		sc->sc_poll_alert = 2;
160 
161 	callout_init(&sc->sc_callout, 0);
162 	callout_setfunc(&sc->sc_callout, acpi_smbus_tick, self);
163 
164 	if (sc->sc_poll_alert != 0) {
165 		aprint_debug(": alert_poll %d sec", sc->sc_poll_alert);
166 		callout_schedule(&sc->sc_callout, sc->sc_poll_alert * hz);
167 	}
168 
169 	aprint_normal("\n");
170 
171 	(void)memset(&iba, 0, sizeof(iba));
172 	(void)pmf_device_register(self, NULL, NULL);
173 
174 	iba.iba_tag = &sc->sc_i2c_tag;
175 
176 	config_found(self, &iba, iicbus_print, CFARGS_NONE);
177 }
178 
179 static int
acpi_smbus_detach(device_t self,int flags)180 acpi_smbus_detach(device_t self, int flags)
181 {
182 	struct acpi_smbus_softc *sc = device_private(self);
183 
184 	pmf_device_deregister(self);
185 	acpi_deregister_notify(sc->sc_devnode);
186 
187 	callout_halt(&sc->sc_callout, NULL);
188 	callout_destroy(&sc->sc_callout);
189 
190 	iic_tag_fini(&sc->sc_i2c_tag);
191 
192 	return 0;
193 }
194 
195 static int
acpi_smbus_poll_alert(ACPI_HANDLE hdl,int * alert)196 acpi_smbus_poll_alert(ACPI_HANDLE hdl, int *alert)
197 {
198 	struct SMB_INFO *info;
199 	ACPI_BUFFER smi_buf;
200 	ACPI_OBJECT *e, *p;
201 	ACPI_STATUS rv;
202 
203 	/*
204 	 * Retrieve polling interval for SMBus Alerts.
205 	 */
206 	rv = acpi_eval_struct(hdl, "_SBI", &smi_buf);
207 
208 	if (ACPI_FAILURE(rv))
209 		return 0;
210 
211 	p = smi_buf.Pointer;
212 
213 	if (p->Type != ACPI_TYPE_PACKAGE) {
214 		rv = AE_TYPE;
215 		goto out;
216 	}
217 
218 	if (p->Package.Count == 0) {
219 		rv = AE_LIMIT;
220 		goto out;
221 	}
222 
223 	e = p->Package.Elements;
224 
225 	if (e[0].Type != ACPI_TYPE_INTEGER) {
226 		rv = AE_TYPE;
227 		goto out;
228 	}
229 
230 	/* Verify CMI version. */
231 	if (e[0].Integer.Value != 0x10) {
232 		rv = AE_SUPPORT;
233 		goto out;
234 	}
235 
236 	if (alert != NULL) {
237 
238 		if (p->Package.Count < 2)
239 			goto out;
240 
241 		if (e[1].Type != ACPI_TYPE_BUFFER)
242 			goto out;
243 
244 		info = (struct SMB_INFO *)(e[1].Buffer.Pointer);
245 		*alert = info->poll_int;
246 	}
247 
248 out:
249 	if (smi_buf.Pointer != NULL)
250 		ACPI_FREE(smi_buf.Pointer);
251 
252 	return (ACPI_FAILURE(rv)) ? 0 : 1;
253 }
254 
255 static int
acpi_smbus_exec(void * cookie,i2c_op_t op,i2c_addr_t addr,const void * cmdbuf,size_t cmdlen,void * buf,size_t len,int flags)256 acpi_smbus_exec(void *cookie, i2c_op_t op, i2c_addr_t addr,
257 	const void *cmdbuf, size_t cmdlen, void *buf, size_t len, int flags)
258 {
259         struct acpi_smbus_softc *sc = cookie;
260 	const uint8_t *c = cmdbuf;
261 	uint8_t *b = buf, *xb;
262 	const char *path;
263 	ACPI_OBJECT_LIST args;
264 	ACPI_OBJECT arg[5];
265 	ACPI_OBJECT *p, *e;
266 	ACPI_BUFFER smbuf;
267 	ACPI_STATUS rv;
268 	int i, r, xlen;
269 
270 	/*
271 	 *	arg[0] : protocol
272 	 *	arg[1] : slave address
273 	 *	arg[2] : command
274 	 *	arg[3] : data length
275 	 *	arg[4] : data
276 	 */
277 	for (i = r = 0; i < __arraycount(arg); i++)
278 		arg[i].Type = ACPI_TYPE_INTEGER;
279 
280 	args.Pointer = arg;
281 
282 	smbuf.Pointer = NULL;
283 	smbuf.Length = ACPI_ALLOCATE_LOCAL_BUFFER;
284 
285 	arg[1].Integer.Value = addr;
286 
287 	if (I2C_OP_READ_P(op)) {
288 
289 		path = "_SBR";
290 		args.Count = 3;
291 
292 		switch (len) {
293 
294 		case 0:
295 			arg[0].Integer.Value = (cmdlen != 0) ?
296 			    ACPI_SMBUS_RCV_BYTE : ACPI_SMBUS_RD_QUICK;
297 
298 			arg[2].Integer.Value = 0;
299 			break;
300 
301 		case 1:
302 			arg[0].Integer.Value = ACPI_SMBUS_RD_BYTE;
303 			arg[2].Integer.Value = *c;
304 			break;
305 
306 		case 2:
307 			arg[0].Integer.Value = ACPI_SMBUS_RD_WORD;
308 			arg[2].Integer.Value = *c;
309 			break;
310 
311 		default:
312 			arg[0].Integer.Value = ACPI_SMBUS_RD_BLOCK;
313 			arg[2].Integer.Value = *c;
314 			break;
315 		}
316 
317 	} else {
318 
319 		path = "_SBW";
320 		args.Count = 5;
321 
322 		arg[3].Integer.Value = len;
323 
324 		switch (len) {
325 
326 		case 0:
327 			if (cmdlen == 0) {
328 				arg[2].Integer.Value = 0;
329 				arg[0].Integer.Value = ACPI_SMBUS_WR_QUICK;
330 			} else {
331 				arg[2].Integer.Value = *c;
332 				arg[0].Integer.Value = ACPI_SMBUS_SND_BYTE;
333 			}
334 
335 			arg[4].Integer.Value = 0;
336 			break;
337 
338 		case 1:
339 			arg[0].Integer.Value = ACPI_SMBUS_WR_BYTE;
340 			arg[2].Integer.Value = *c;
341 			arg[4].Integer.Value = *b;
342 			break;
343 
344 		case 2:
345 			arg[0].Integer.Value = ACPI_SMBUS_WR_WORD;
346 			arg[2].Integer.Value = *c;
347 			arg[4].Integer.Value = *b++;
348 			arg[4].Integer.Value += (*b--) << 8;
349 			break;
350 
351 		default:
352 			arg[0].Integer.Value = ACPI_SMBUS_WR_BLOCK;
353 			arg[2].Integer.Value = *c;
354 			arg[4].Type = ACPI_TYPE_BUFFER;
355 			arg[4].Buffer.Pointer = buf;
356 			arg[4].Buffer.Length = (len < 32) ? len : 32;
357 			break;
358 		}
359 	}
360 
361 	rv = AcpiEvaluateObject(sc->sc_devnode->ad_handle, path, &args,&smbuf);
362 
363 	if (ACPI_FAILURE(rv))
364 		goto out;
365 
366 	p = smbuf.Pointer;
367 
368 	if (p->Type != ACPI_TYPE_PACKAGE) {
369 		rv = AE_TYPE;
370 		goto out;
371 	}
372 
373 	if (p->Package.Count < 1) {
374 		rv = AE_LIMIT;
375 		goto out;
376 	}
377 
378 	e = p->Package.Elements;
379 
380 	if (e->Type != ACPI_TYPE_INTEGER) {
381 		rv = AE_TYPE;
382 		goto out;
383 	}
384 
385 	ACPI_DEBUG_PRINT((ACPI_DB_DEBUG_OBJECT,
386 		"return status: %"PRIu64"\n", e[0].Integer.Value));
387 
388 	if (e[0].Integer.Value != 0) {
389 		rv = AE_BAD_VALUE;
390 		goto out;
391 	}
392 
393 	/*
394 	 * For read operations, copy data to user buffer.
395 	 */
396 	if (I2C_OP_READ_P(op)) {
397 
398 		if (p->Package.Count < 3) {
399 			rv = AE_LIMIT;
400 			goto out;
401 		}
402 
403 		if (e[1].Type != ACPI_TYPE_INTEGER) {
404 			rv = AE_TYPE;
405 			goto out;
406 		}
407 
408 		xlen = e[1].Integer.Value;
409 
410 		if (xlen > len)
411 			xlen = len;
412 
413 		switch (e[2].Type) {
414 
415 		case ACPI_TYPE_BUFFER:
416 
417 			if (xlen == 0) {
418 				rv = AE_LIMIT;
419 				goto out;
420 			}
421 
422 			xb = e[2].Buffer.Pointer;
423 
424 			if (xb == NULL) {
425 				rv = AE_NULL_OBJECT;
426 				goto out;
427 			}
428 
429 			(void)memcpy(b, xb, xlen);
430 			break;
431 
432 		case ACPI_TYPE_INTEGER:
433 
434 			if (xlen > 0)
435 				*b++ = e[2].Integer.Value & 0xff;
436 
437 			if (xlen > 1)
438 				*b = e[2].Integer.Value >> 8;
439 
440 			break;
441 
442 		default:
443 			rv = AE_TYPE;
444 			goto out;
445 		}
446 	}
447 
448 out:
449 	if (smbuf.Pointer != NULL)
450 		ACPI_FREE(smbuf.Pointer);
451 
452 	if (ACPI_SUCCESS(rv))
453 		return 0;
454 
455 	ACPI_DEBUG_PRINT((ACPI_DB_DEBUG_OBJECT, "failed to "
456 		"evaluate %s: %s\n", path, AcpiFormatException(rv)));
457 
458 	return 1;
459 }
460 
461 /*
462  * Whether triggered by periodic polling or a Notify(),
463  * retrieve all pending SMBus device alerts.
464  */
465 static void
acpi_smbus_alerts(struct acpi_smbus_softc * sc)466 acpi_smbus_alerts(struct acpi_smbus_softc *sc)
467 {
468 	const ACPI_HANDLE hdl = sc->sc_devnode->ad_handle;
469 	ACPI_OBJECT *e, *p;
470 	ACPI_BUFFER alert;
471 	ACPI_STATUS rv;
472 	int status = 0;
473 	uint8_t addr;
474 
475 	do {
476 		rv = acpi_eval_struct(hdl, "_SBA", &alert);
477 
478 		if (ACPI_FAILURE(rv)) {
479 			status = 1;
480 			goto done;
481 		}
482 
483 		p = alert.Pointer;
484 
485 		if (p->Type == ACPI_TYPE_PACKAGE && p->Package.Count >= 2) {
486 
487 			status = 1;
488 
489 			e = p->Package.Elements;
490 
491 			if (e[0].Type == ACPI_TYPE_INTEGER)
492 				status = e[0].Integer.Value;
493 
494 			if (status == 0 && e[1].Type == ACPI_TYPE_INTEGER) {
495 				addr = e[1].Integer.Value;
496 
497 				aprint_debug_dev(sc->sc_dv,
498 				    "alert for 0x%x\n", addr);
499 			}
500 		}
501 done:
502 		if (alert.Pointer != NULL)
503 			ACPI_FREE(alert.Pointer);
504 
505 	} while (status == 0);
506 }
507 
508 static void
acpi_smbus_tick(void * opaque)509 acpi_smbus_tick(void *opaque)
510 {
511 	device_t dv = opaque;
512 	struct acpi_smbus_softc *sc = device_private(dv);
513 
514 	acpi_smbus_alerts(sc);
515 
516 	callout_schedule(&sc->sc_callout, sc->sc_poll_alert * hz);
517 }
518 
519 static void
acpi_smbus_notify_handler(ACPI_HANDLE hdl,uint32_t notify,void * opaque)520 acpi_smbus_notify_handler(ACPI_HANDLE hdl, uint32_t notify, void *opaque)
521 {
522 	device_t dv = opaque;
523 	struct acpi_smbus_softc *sc = device_private(dv);
524 
525 	aprint_debug_dev(dv, "received notify message 0x%x\n", notify);
526 
527 	acpi_smbus_alerts(sc);
528 }
529