smbus_acpi.c revision 1.10 1 /* $NetBSD: smbus_acpi.c,v 1.10 2010/04/15 07:02:24 jruoho 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.10 2010/04/15 07:02:24 jruoho 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 kmutex_t sc_i2c_mutex;
77 int sc_poll_alert;
78 };
79
80 static int acpi_smbus_match(device_t, cfdata_t, void *);
81 static void acpi_smbus_attach(device_t, device_t, void *);
82 static int acpi_smbus_detach(device_t, int);
83 static int acpi_smbus_acquire_bus(void *, int);
84 static void acpi_smbus_release_bus(void *, int);
85 static int acpi_smbus_exec(void *, i2c_op_t, i2c_addr_t, const void *,
86 size_t, void *, size_t, int);
87 static void acpi_smbus_alerts(struct acpi_smbus_softc *);
88 static void acpi_smbus_tick(void *);
89 static void acpi_smbus_notify_handler(ACPI_HANDLE, uint32_t, void *);
90
91 struct SMB_UDID {
92 uint8_t dev_cap;
93 uint8_t vers_rev;
94 uint16_t vendor;
95 uint16_t device;
96 uint16_t interface;
97 uint16_t subsys_vendor;
98 uint16_t subsys_device;
99 uint8_t reserved[4];
100 };
101
102 struct SMB_DEVICE {
103 uint8_t slave_addr;
104 uint8_t reserved;
105 struct SMB_UDID dev_id;
106 };
107
108 struct SMB_INFO {
109 uint8_t struct_ver;
110 uint8_t spec_ver;
111 uint8_t hw_cap;
112 uint8_t poll_int;
113 uint8_t dev_count;
114 struct SMB_DEVICE device[1];
115 };
116
117 static const char * const smbus_acpi_ids[] = {
118 "SMBUS01", /* SMBus CMI v1.0 */
119 NULL
120 };
121
122 static const char * const pcibus_acpi_ids[] = {
123 "PNP0A03",
124 "PNP0A08",
125 NULL
126 };
127
128 CFATTACH_DECL_NEW(acpismbus, sizeof(struct acpi_smbus_softc),
129 acpi_smbus_match, acpi_smbus_attach, acpi_smbus_detach, NULL);
130
131 /*
132 * acpi_smbus_match: autoconf(9) match routine
133 */
134 static int
135 acpi_smbus_match(device_t parent, cfdata_t match, void *aux)
136 {
137 struct acpi_attach_args *aa = aux;
138 int r = 0;
139 ACPI_STATUS rv;
140 ACPI_BUFFER smi_buf;
141 ACPI_OBJECT *e, *p;
142
143 if (aa->aa_node->ad_type != ACPI_TYPE_DEVICE)
144 return 0;
145
146 if (acpi_match_hid(aa->aa_node->ad_devinfo, smbus_acpi_ids) == 0)
147 return 0;
148
149 /* Ensure that device's CMI version is supported */
150 rv = acpi_eval_struct(aa->aa_node->ad_handle, "_SBI", &smi_buf);
151 if (ACPI_FAILURE(rv))
152 goto done;
153
154 p = smi_buf.Pointer;
155 if (p != NULL && p->Type == ACPI_TYPE_PACKAGE &&
156 p->Package.Count >= 1) {
157 e = p->Package.Elements;
158 if (e[0].Type == ACPI_TYPE_INTEGER &&
159 e[0].Integer.Value == 0x10)
160 r = 1;
161 }
162 done:
163 if (smi_buf.Pointer != NULL)
164 ACPI_FREE(smi_buf.Pointer);
165
166 return r;
167 }
168
169 /*
170 * acpitz_attach: autoconf(9) attach routine
171 */
172 static void
173 acpi_smbus_attach(device_t parent, device_t self, void *aux)
174 {
175 struct acpi_smbus_softc *sc = device_private(self);
176 struct acpi_attach_args *aa = aux;
177 struct i2cbus_attach_args iba;
178 ACPI_STATUS rv;
179 ACPI_HANDLE native_dev, native_bus;
180 ACPI_DEVICE_INFO *native_dev_info, *native_bus_info;
181 ACPI_BUFFER smi_buf;
182 ACPI_OBJECT *e, *p;
183 struct SMB_INFO *info;
184 int pci_bus, pci_dev, pci_func;
185
186 aprint_naive("\n");
187
188 sc->sc_devnode = aa->aa_node;
189 sc->sc_dv = self;
190 sc->sc_poll_alert = 2;
191
192 /* Attach I2C bus */
193 mutex_init(&sc->sc_i2c_mutex, MUTEX_DEFAULT, IPL_NONE);
194 sc->sc_i2c_tag.ic_cookie = sc;
195 sc->sc_i2c_tag.ic_acquire_bus = acpi_smbus_acquire_bus;
196 sc->sc_i2c_tag.ic_release_bus = acpi_smbus_release_bus;
197 sc->sc_i2c_tag.ic_exec = acpi_smbus_exec;
198
199 /* Retrieve polling interval for SMBus Alerts */
200 rv = acpi_eval_struct(aa->aa_node->ad_handle, "_SBI", &smi_buf);
201 if (ACPI_SUCCESS(rv)) {
202 p = smi_buf.Pointer;
203 if (p != NULL && p->Type == ACPI_TYPE_PACKAGE &&
204 p->Package.Count >= 2) {
205 e = p->Package.Elements;
206 if (e[1].Type == ACPI_TYPE_BUFFER) {
207 info = (struct SMB_INFO *)(e[1].Buffer.Pointer);
208 sc->sc_poll_alert = info->poll_int;
209 }
210 }
211 }
212 if (smi_buf.Pointer != NULL)
213 ACPI_FREE(smi_buf.Pointer);
214
215 /* If failed, fall-back to polling. */
216 if (acpi_register_notify(sc->sc_devnode,
217 acpi_smbus_notify_handler) != true)
218 sc->sc_poll_alert = 2;
219
220 callout_init(&sc->sc_callout, 0);
221 callout_setfunc(&sc->sc_callout, acpi_smbus_tick, self);
222
223 if (!pmf_device_register(self, NULL, NULL))
224 aprint_error(": couldn't establish power handler\n");
225
226 if (sc->sc_poll_alert != 0) {
227 aprint_debug(" alert_poll %d sec", sc->sc_poll_alert);
228 callout_schedule(&sc->sc_callout, sc->sc_poll_alert * hz);
229 }
230 aprint_normal("\n");
231
232 /*
233 * Retrieve and display native controller info
234 */
235 rv = AcpiGetParent(sc->sc_devnode->ad_handle, &native_dev);
236
237 native_bus_info = native_dev_info = NULL;
238
239 if (ACPI_SUCCESS(rv))
240 rv = AcpiGetParent(native_dev, &native_bus);
241
242 if (ACPI_SUCCESS(rv))
243 rv = AcpiGetObjectInfo(native_bus, &native_bus_info);
244
245 if (ACPI_SUCCESS(rv) &&
246 acpi_match_hid(native_bus_info, pcibus_acpi_ids) != 0) {
247
248 rv = AcpiGetObjectInfo(native_dev, &native_dev_info);
249
250 if (ACPI_SUCCESS(rv)) {
251 pci_bus = native_bus_info->Address;
252 pci_dev = ACPI_ADR_PCI_DEV(native_dev_info->Address);
253 pci_func = ACPI_ADR_PCI_FUNC(native_dev_info->Address);
254 aprint_debug_dev(self, "Native i2c host controller"
255 " is on PCI bus %d dev %d func %d\n",
256 pci_bus, pci_dev, pci_func);
257 /*
258 * XXX We really need a mechanism to prevent the
259 * XXX native controller from attaching
260 */
261 }
262 }
263
264 if (native_bus_info != NULL)
265 ACPI_FREE(native_bus_info);
266
267 if (native_dev_info != NULL)
268 ACPI_FREE(native_dev_info);
269
270 memset(&iba, 0, sizeof(iba));
271 iba.iba_tag = &sc->sc_i2c_tag;
272 config_found_ia(self, "i2cbus", &iba, iicbus_print);
273 }
274
275 static int
276 acpi_smbus_detach(device_t self, int flags)
277 {
278 struct acpi_smbus_softc *sc = device_private(self);
279
280 pmf_device_deregister(self);
281 acpi_deregister_notify(sc->sc_devnode);
282
283 callout_halt(&sc->sc_callout, NULL);
284 callout_destroy(&sc->sc_callout);
285
286 mutex_destroy(&sc->sc_i2c_mutex);
287
288 return 0;
289 }
290
291 static int
292 acpi_smbus_acquire_bus(void *cookie, int flags)
293 {
294 struct acpi_smbus_softc *sc = cookie;
295
296 mutex_enter(&sc->sc_i2c_mutex);
297 return 0;
298 }
299
300 static void
301 acpi_smbus_release_bus(void *cookie, int flags)
302 {
303 struct acpi_smbus_softc *sc = cookie;
304
305 mutex_exit(&sc->sc_i2c_mutex);
306 }
307 static int
308 acpi_smbus_exec(void *cookie, i2c_op_t op, i2c_addr_t addr,
309 const void *cmdbuf, size_t cmdlen, void *buf, size_t len, int flags)
310 {
311 struct acpi_smbus_softc *sc = cookie;
312 const uint8_t *c = cmdbuf;
313 uint8_t *b = buf, *xb;
314 int xlen;
315 int r = 0;
316 ACPI_BUFFER smbuf;
317 ACPI_STATUS rv;
318 ACPI_OBJECT_LIST args;
319 ACPI_OBJECT arg[5];
320 ACPI_OBJECT *p, *e;
321
322 smbuf.Pointer = NULL;
323 smbuf.Length = ACPI_ALLOCATE_LOCAL_BUFFER;
324 args.Pointer = arg;
325 arg[0].Type = ACPI_TYPE_INTEGER; /* Protocol */
326 arg[1].Type = ACPI_TYPE_INTEGER; /* Slave Addr */
327 arg[1].Integer.Value = addr;
328 arg[2].Type = ACPI_TYPE_INTEGER; /* Command */
329 if (I2C_OP_READ_P(op)) {
330 args.Count = 3;
331 if (len == 0) {
332 arg[2].Integer.Value = 0;
333 if (cmdlen == 0)
334 arg[0].Integer.Value = ACPI_SMBUS_RD_QUICK;
335 else
336 arg[0].Integer.Value = ACPI_SMBUS_RCV_BYTE;
337 } else
338 arg[2].Integer.Value = *c;
339 if (len == 1)
340 arg[0].Integer.Value = ACPI_SMBUS_RD_BYTE;
341 else if (len == 2)
342 arg[0].Integer.Value = ACPI_SMBUS_RD_WORD;
343 else if (len > 2)
344 arg[0].Integer.Value = ACPI_SMBUS_RD_BLOCK;
345 rv = AcpiEvaluateObject(sc->sc_devnode->ad_handle, "_SBR",
346 &args, &smbuf);
347 } else {
348 args.Count = 5;
349 arg[3].Type = ACPI_TYPE_INTEGER; /* Data Len */
350 arg[3].Integer.Value = len;
351 arg[4].Type = ACPI_TYPE_INTEGER; /* Data */
352 if (len == 0) {
353 arg[4].Integer.Value = 0;
354 if (cmdlen == 0) {
355 arg[2].Integer.Value = 0;
356 arg[0].Integer.Value = ACPI_SMBUS_WR_QUICK;
357 } else {
358 arg[2].Integer.Value = *c;
359 arg[0].Integer.Value = ACPI_SMBUS_SND_BYTE;
360 }
361 } else
362 arg[2].Integer.Value = *c;
363 if (len == 1) {
364 arg[0].Integer.Value = ACPI_SMBUS_WR_BYTE;
365 arg[4].Integer.Value = *b;
366 } else if (len == 2) {
367 arg[0].Integer.Value = ACPI_SMBUS_WR_WORD;
368 arg[4].Integer.Value = *b++;
369 arg[4].Integer.Value += (*b--) << 8;
370 } else if (len > 2) {
371 arg[0].Integer.Value = ACPI_SMBUS_WR_BLOCK;
372 arg[4].Type = ACPI_TYPE_BUFFER;
373 arg[4].Buffer.Pointer = buf;
374 arg[4].Buffer.Length = (len < 32?len:32);
375 }
376 rv = AcpiEvaluateObject(sc->sc_devnode->ad_handle, "_SBW",
377 &args, &smbuf);
378 }
379 if (ACPI_FAILURE(rv))
380 r = 1;
381 else {
382 p = smbuf.Pointer;
383 if (p == NULL || p->Type != ACPI_TYPE_PACKAGE ||
384 p->Package.Count < 1)
385 r = 1;
386 else {
387 e = p->Package.Elements;
388 if (e->Type == ACPI_TYPE_INTEGER)
389 r = e[0].Integer.Value;
390 else
391 r = 1;
392 }
393 if (r != 0)
394 r = 1;
395
396 /* For read operations, copy data to user buffer */
397 if (r == 0 && I2C_OP_READ_P(op)) {
398 if (p->Package.Count >= 3 &&
399 e[1].Type == ACPI_TYPE_INTEGER) {
400 xlen = e[1].Integer.Value;
401 if (xlen > len)
402 xlen = len;
403 if (xlen != 0 &&
404 e[2].Type == ACPI_TYPE_BUFFER) {
405 xb = e[2].Buffer.Pointer;
406 if (xb != NULL)
407 memcpy(b, xb, xlen);
408 else
409 r = 1;
410 } else if (e[2].Type == ACPI_TYPE_INTEGER) {
411 if (xlen > 0)
412 *b++ = e[2].Integer.Value &
413 0xff;
414 if (xlen > 1)
415 *b = (e[2].Integer.Value >> 8);
416 } else
417 r = 1;
418 } else
419 r = 1;
420 }
421 }
422 if (smbuf.Pointer)
423 ACPI_FREE(smbuf.Pointer);
424
425 return r;
426 }
427
428 /*
429 * acpi_smbus_alerts
430 *
431 * Whether triggered by periodic polling or an AcpiNotify, retrieve
432 * all pending SMBus device alerts
433 */
434 static void
435 acpi_smbus_alerts(struct acpi_smbus_softc *sc)
436 {
437 int status = 0;
438 uint8_t slave_addr;
439 ACPI_STATUS rv;
440 ACPI_BUFFER alert;
441 ACPI_OBJECT *e, *p;
442
443 do {
444 rv = acpi_eval_struct(sc->sc_devnode->ad_handle, "_SBA",
445 &alert);
446 if (ACPI_FAILURE(rv)) {
447 status = 1;
448 goto done;
449 }
450
451 p = alert.Pointer;
452 if (p != NULL && p->Type == ACPI_TYPE_PACKAGE &&
453 p->Package.Count >= 2) {
454 e = p->Package.Elements;
455 if (e[0].Type == ACPI_TYPE_INTEGER)
456 status = e[0].Integer.Value;
457 else
458 status = 1;
459 if (status == 0x0 && e[1].Type == ACPI_TYPE_INTEGER) {
460 slave_addr = e[1].Integer.Value;
461 aprint_debug_dev(sc->sc_dv, "Alert for 0x%x\n",
462 slave_addr);
463 (void)iic_smbus_intr(&sc->sc_i2c_tag);
464 }
465 }
466 done:
467 if (alert.Pointer != NULL)
468 ACPI_FREE(alert.Pointer);
469 } while (status == 0);
470 }
471
472 static void
473 acpi_smbus_tick(void *opaque)
474 {
475 device_t dv = opaque;
476 struct acpi_smbus_softc *sc = device_private(dv);
477
478 acpi_smbus_alerts(sc);
479
480 callout_schedule(&sc->sc_callout, sc->sc_poll_alert * hz);
481 }
482
483 static void
484 acpi_smbus_notify_handler(ACPI_HANDLE hdl, uint32_t notify, void *opaque)
485 {
486 device_t dv = opaque;
487 struct acpi_smbus_softc *sc = device_private(dv);
488
489 aprint_debug_dev(dv, "received notify message 0x%x\n", notify);
490 acpi_smbus_alerts(sc);
491 }
492