smbus_acpi.c revision 1.14 1 /* $NetBSD: smbus_acpi.c,v 1.14 2019/12/22 23:23:32 thorpej 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.14 2019/12/22 23:23:32 thorpej 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 char * const smbus_acpi_ids[] = {
116 "SMBUS01", /* SMBus CMI v1.0 */
117 NULL
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
124 acpi_smbus_match(device_t parent, cfdata_t match, void *aux)
125 {
126 struct acpi_attach_args *aa = aux;
127
128 if (aa->aa_node->ad_type != ACPI_TYPE_DEVICE)
129 return 0;
130
131 if (acpi_match_hid(aa->aa_node->ad_devinfo, smbus_acpi_ids) == 0)
132 return 0;
133
134 return acpi_smbus_poll_alert(aa->aa_node->ad_handle, NULL);
135 }
136
137 static void
138 acpi_smbus_attach(device_t parent, device_t self, void *aux)
139 {
140 struct acpi_smbus_softc *sc = device_private(self);
141 struct acpi_attach_args *aa = aux;
142 struct i2cbus_attach_args iba;
143
144 aprint_naive("\n");
145
146 sc->sc_devnode = aa->aa_node;
147 sc->sc_dv = self;
148 sc->sc_poll_alert = 2;
149
150 /* Attach I2C bus. */
151 iic_tag_init(&sc->sc_i2c_tag);
152 sc->sc_i2c_tag.ic_cookie = sc;
153 sc->sc_i2c_tag.ic_exec = acpi_smbus_exec;
154
155 (void)acpi_smbus_poll_alert(aa->aa_node->ad_handle,&sc->sc_poll_alert);
156
157 /* If failed, fall-back to polling. */
158 if (acpi_register_notify(sc->sc_devnode,
159 acpi_smbus_notify_handler) != true)
160 sc->sc_poll_alert = 2;
161
162 callout_init(&sc->sc_callout, 0);
163 callout_setfunc(&sc->sc_callout, acpi_smbus_tick, self);
164
165 if (sc->sc_poll_alert != 0) {
166 aprint_debug(": alert_poll %d sec", sc->sc_poll_alert);
167 callout_schedule(&sc->sc_callout, sc->sc_poll_alert * hz);
168 }
169
170 aprint_normal("\n");
171
172 (void)memset(&iba, 0, sizeof(iba));
173 (void)pmf_device_register(self, NULL, NULL);
174
175 iba.iba_tag = &sc->sc_i2c_tag;
176
177 (void)config_found_ia(self, "i2cbus", &iba, iicbus_print);
178 }
179
180 static int
181 acpi_smbus_detach(device_t self, int flags)
182 {
183 struct acpi_smbus_softc *sc = device_private(self);
184
185 pmf_device_deregister(self);
186 acpi_deregister_notify(sc->sc_devnode);
187
188 callout_halt(&sc->sc_callout, NULL);
189 callout_destroy(&sc->sc_callout);
190
191 iic_tag_fini(&sc->sc_i2c_tag);
192
193 return 0;
194 }
195
196 static int
197 acpi_smbus_poll_alert(ACPI_HANDLE hdl, int *alert)
198 {
199 struct SMB_INFO *info;
200 ACPI_BUFFER smi_buf;
201 ACPI_OBJECT *e, *p;
202 ACPI_STATUS rv;
203
204 /*
205 * Retrieve polling interval for SMBus Alerts.
206 */
207 rv = acpi_eval_struct(hdl, "_SBI", &smi_buf);
208
209 if (ACPI_FAILURE(rv))
210 return 0;
211
212 p = smi_buf.Pointer;
213
214 if (p->Type != ACPI_TYPE_PACKAGE) {
215 rv = AE_TYPE;
216 goto out;
217 }
218
219 if (p->Package.Count == 0) {
220 rv = AE_LIMIT;
221 goto out;
222 }
223
224 e = p->Package.Elements;
225
226 if (e[0].Type != ACPI_TYPE_INTEGER) {
227 rv = AE_TYPE;
228 goto out;
229 }
230
231 /* Verify CMI version. */
232 if (e[0].Integer.Value != 0x10) {
233 rv = AE_SUPPORT;
234 goto out;
235 }
236
237 if (alert != NULL) {
238
239 if (p->Package.Count < 2)
240 goto out;
241
242 if (e[1].Type != ACPI_TYPE_BUFFER)
243 goto out;
244
245 info = (struct SMB_INFO *)(e[1].Buffer.Pointer);
246 *alert = info->poll_int;
247 }
248
249 out:
250 if (smi_buf.Pointer != NULL)
251 ACPI_FREE(smi_buf.Pointer);
252
253 return (ACPI_FAILURE(rv)) ? 0 : 1;
254 }
255
256 static int
257 acpi_smbus_exec(void *cookie, i2c_op_t op, i2c_addr_t addr,
258 const void *cmdbuf, size_t cmdlen, void *buf, size_t len, int flags)
259 {
260 struct acpi_smbus_softc *sc = cookie;
261 const uint8_t *c = cmdbuf;
262 uint8_t *b = buf, *xb;
263 const char *path;
264 ACPI_OBJECT_LIST args;
265 ACPI_OBJECT arg[5];
266 ACPI_OBJECT *p, *e;
267 ACPI_BUFFER smbuf;
268 ACPI_STATUS rv;
269 int i, r, xlen;
270
271 /*
272 * arg[0] : protocol
273 * arg[1] : slave address
274 * arg[2] : command
275 * arg[3] : data length
276 * arg[4] : data
277 */
278 for (i = r = 0; i < __arraycount(arg); i++)
279 arg[i].Type = ACPI_TYPE_INTEGER;
280
281 args.Pointer = arg;
282
283 smbuf.Pointer = NULL;
284 smbuf.Length = ACPI_ALLOCATE_LOCAL_BUFFER;
285
286 arg[1].Integer.Value = addr;
287
288 if (I2C_OP_READ_P(op)) {
289
290 path = "_SBR";
291 args.Count = 3;
292
293 switch (len) {
294
295 case 0:
296 arg[0].Integer.Value = (cmdlen != 0) ?
297 ACPI_SMBUS_RCV_BYTE : ACPI_SMBUS_RD_QUICK;
298
299 arg[2].Integer.Value = 0;
300 break;
301
302 case 1:
303 arg[0].Integer.Value = ACPI_SMBUS_RD_BYTE;
304 arg[2].Integer.Value = *c;
305 break;
306
307 case 2:
308 arg[0].Integer.Value = ACPI_SMBUS_RD_WORD;
309 arg[2].Integer.Value = *c;
310 break;
311
312 default:
313 arg[0].Integer.Value = ACPI_SMBUS_RD_BLOCK;
314 arg[2].Integer.Value = *c;
315 break;
316 }
317
318 } else {
319
320 path = "_SBW";
321 args.Count = 5;
322
323 arg[3].Integer.Value = len;
324
325 switch (len) {
326
327 case 0:
328 if (cmdlen == 0) {
329 arg[2].Integer.Value = 0;
330 arg[0].Integer.Value = ACPI_SMBUS_WR_QUICK;
331 } else {
332 arg[2].Integer.Value = *c;
333 arg[0].Integer.Value = ACPI_SMBUS_SND_BYTE;
334 }
335
336 arg[4].Integer.Value = 0;
337 break;
338
339 case 1:
340 arg[0].Integer.Value = ACPI_SMBUS_WR_BYTE;
341 arg[2].Integer.Value = *c;
342 arg[4].Integer.Value = *b;
343 break;
344
345 case 2:
346 arg[0].Integer.Value = ACPI_SMBUS_WR_WORD;
347 arg[2].Integer.Value = *c;
348 arg[4].Integer.Value = *b++;
349 arg[4].Integer.Value += (*b--) << 8;
350 break;
351
352 default:
353 arg[0].Integer.Value = ACPI_SMBUS_WR_BLOCK;
354 arg[2].Integer.Value = *c;
355 arg[4].Type = ACPI_TYPE_BUFFER;
356 arg[4].Buffer.Pointer = buf;
357 arg[4].Buffer.Length = (len < 32) ? len : 32;
358 break;
359 }
360 }
361
362 rv = AcpiEvaluateObject(sc->sc_devnode->ad_handle, path, &args,&smbuf);
363
364 if (ACPI_FAILURE(rv))
365 goto out;
366
367 p = smbuf.Pointer;
368
369 if (p->Type != ACPI_TYPE_PACKAGE) {
370 rv = AE_TYPE;
371 goto out;
372 }
373
374 if (p->Package.Count < 1) {
375 rv = AE_LIMIT;
376 goto out;
377 }
378
379 e = p->Package.Elements;
380
381 if (e->Type != ACPI_TYPE_INTEGER) {
382 rv = AE_TYPE;
383 goto out;
384 }
385
386 ACPI_DEBUG_PRINT((ACPI_DB_DEBUG_OBJECT,
387 "return status: %"PRIu64"\n", e[0].Integer.Value));
388
389 if (e[0].Integer.Value != 0) {
390 rv = AE_BAD_VALUE;
391 goto out;
392 }
393
394 /*
395 * For read operations, copy data to user buffer.
396 */
397 if (I2C_OP_READ_P(op)) {
398
399 if (p->Package.Count < 3) {
400 rv = AE_LIMIT;
401 goto out;
402 }
403
404 if (e[1].Type != ACPI_TYPE_INTEGER) {
405 rv = AE_TYPE;
406 goto out;
407 }
408
409 xlen = e[1].Integer.Value;
410
411 if (xlen > len)
412 xlen = len;
413
414 switch (e[2].Type) {
415
416 case ACPI_TYPE_BUFFER:
417
418 if (xlen == 0) {
419 rv = AE_LIMIT;
420 goto out;
421 }
422
423 xb = e[2].Buffer.Pointer;
424
425 if (xb == NULL) {
426 rv = AE_NULL_OBJECT;
427 goto out;
428 }
429
430 (void)memcpy(b, xb, xlen);
431 break;
432
433 case ACPI_TYPE_INTEGER:
434
435 if (xlen > 0)
436 *b++ = e[2].Integer.Value & 0xff;
437
438 if (xlen > 1)
439 *b = e[2].Integer.Value >> 8;
440
441 break;
442
443 default:
444 rv = AE_TYPE;
445 goto out;
446 }
447 }
448
449 out:
450 if (smbuf.Pointer != NULL)
451 ACPI_FREE(smbuf.Pointer);
452
453 if (ACPI_SUCCESS(rv))
454 return 0;
455
456 ACPI_DEBUG_PRINT((ACPI_DB_DEBUG_OBJECT, "failed to "
457 "evaluate %s: %s\n", path, AcpiFormatException(rv)));
458
459 return 1;
460 }
461
462 /*
463 * Whether triggered by periodic polling or a Notify(),
464 * retrieve all pending SMBus device alerts.
465 */
466 static void
467 acpi_smbus_alerts(struct acpi_smbus_softc *sc)
468 {
469 const ACPI_HANDLE hdl = sc->sc_devnode->ad_handle;
470 ACPI_OBJECT *e, *p;
471 ACPI_BUFFER alert;
472 ACPI_STATUS rv;
473 int status = 0;
474 uint8_t addr;
475
476 do {
477 rv = acpi_eval_struct(hdl, "_SBA", &alert);
478
479 if (ACPI_FAILURE(rv)) {
480 status = 1;
481 goto done;
482 }
483
484 p = alert.Pointer;
485
486 if (p->Type == ACPI_TYPE_PACKAGE && p->Package.Count >= 2) {
487
488 status = 1;
489
490 e = p->Package.Elements;
491
492 if (e[0].Type == ACPI_TYPE_INTEGER)
493 status = e[0].Integer.Value;
494
495 if (status == 0 && e[1].Type == ACPI_TYPE_INTEGER) {
496 addr = e[1].Integer.Value;
497
498 aprint_debug_dev(sc->sc_dv,
499 "alert for 0x%x\n", addr);
500
501 (void)iic_smbus_intr(&sc->sc_i2c_tag);
502 }
503 }
504 done:
505 if (alert.Pointer != NULL)
506 ACPI_FREE(alert.Pointer);
507
508 } while (status == 0);
509 }
510
511 static void
512 acpi_smbus_tick(void *opaque)
513 {
514 device_t dv = opaque;
515 struct acpi_smbus_softc *sc = device_private(dv);
516
517 acpi_smbus_alerts(sc);
518
519 callout_schedule(&sc->sc_callout, sc->sc_poll_alert * hz);
520 }
521
522 static void
523 acpi_smbus_notify_handler(ACPI_HANDLE hdl, uint32_t notify, void *opaque)
524 {
525 device_t dv = opaque;
526 struct acpi_smbus_softc *sc = device_private(dv);
527
528 aprint_debug_dev(dv, "received notify message 0x%x\n", notify);
529
530 acpi_smbus_alerts(sc);
531 }
532