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