xref: /netbsd-src/sys/dev/acpi/smbus_acpi.c (revision 4e6df137e8e14049b5a701d249962c480449c141)
1 /* $NetBSD: smbus_acpi.c,v 1.9 2010/03/05 14:00:17 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.9 2010/03/05 14:00:17 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 	/* Install notify handler if possible */
216 	rv = AcpiInstallNotifyHandler(sc->sc_devnode->ad_handle,
217 		ACPI_DEVICE_NOTIFY, acpi_smbus_notify_handler, self);
218 	if (ACPI_FAILURE(rv)) {
219 		aprint_error(": unable to install DEVICE NOTIFY handler: %s\n",
220 		    AcpiFormatException(rv));
221 		sc->sc_poll_alert = 2;	/* If failed, fall-back to polling */
222 	}
223 
224 	callout_init(&sc->sc_callout, 0);
225 	callout_setfunc(&sc->sc_callout, acpi_smbus_tick, self);
226 
227 	if (!pmf_device_register(self, NULL, NULL))
228 		aprint_error(": couldn't establish power handler\n");
229 
230 	if (sc->sc_poll_alert != 0) {
231 		aprint_debug(" alert_poll %d sec", sc->sc_poll_alert);
232 		callout_schedule(&sc->sc_callout, sc->sc_poll_alert * hz);
233 	}
234 	aprint_normal("\n");
235 
236 	/*
237 	 * Retrieve and display native controller info
238 	 */
239 	rv = AcpiGetParent(sc->sc_devnode->ad_handle, &native_dev);
240 
241 	native_bus_info = native_dev_info = NULL;
242 
243 	if (ACPI_SUCCESS(rv))
244 		rv = AcpiGetParent(native_dev, &native_bus);
245 
246 	if (ACPI_SUCCESS(rv))
247 		rv = AcpiGetObjectInfo(native_bus, &native_bus_info);
248 
249 	if (ACPI_SUCCESS(rv) &&
250 	    acpi_match_hid(native_bus_info, pcibus_acpi_ids) != 0) {
251 
252 		rv = AcpiGetObjectInfo(native_dev, &native_dev_info);
253 
254 		if (ACPI_SUCCESS(rv)) {
255 			pci_bus = native_bus_info->Address;
256 			pci_dev = ACPI_ADR_PCI_DEV(native_dev_info->Address);
257 			pci_func = ACPI_ADR_PCI_FUNC(native_dev_info->Address);
258 			aprint_debug_dev(self, "Native i2c host controller"
259 			    " is on PCI bus %d dev %d func %d\n",
260 			    pci_bus, pci_dev, pci_func);
261 			/*
262 			 * XXX We really need a mechanism to prevent the
263 			 * XXX native controller from attaching
264 			 */
265 		}
266 	}
267 
268 	if (native_bus_info != NULL)
269 		ACPI_FREE(native_bus_info);
270 
271 	if (native_dev_info != NULL)
272 		ACPI_FREE(native_dev_info);
273 
274 	memset(&iba, 0, sizeof(iba));
275 	iba.iba_tag = &sc->sc_i2c_tag;
276 	config_found_ia(self, "i2cbus", &iba, iicbus_print);
277 }
278 
279 static int
280 acpi_smbus_detach(device_t self, int flags)
281 {
282 	struct acpi_smbus_softc *sc = device_private(self);
283 	ACPI_STATUS rv;
284 
285 	rv = AcpiRemoveNotifyHandler(sc->sc_devnode->ad_handle,
286 	    ACPI_DEVICE_NOTIFY, acpi_smbus_notify_handler);
287 
288 	if (ACPI_FAILURE(rv))
289 		return EBUSY;
290 
291 	pmf_device_deregister(self);
292 
293 	callout_halt(&sc->sc_callout, NULL);
294 	callout_destroy(&sc->sc_callout);
295 
296 	mutex_destroy(&sc->sc_i2c_mutex);
297 
298 	return 0;
299 }
300 
301 static int
302 acpi_smbus_acquire_bus(void *cookie, int flags)
303 {
304         struct acpi_smbus_softc *sc = cookie;
305 
306         mutex_enter(&sc->sc_i2c_mutex);
307         return 0;
308 }
309 
310 static void
311 acpi_smbus_release_bus(void *cookie, int flags)
312 {
313         struct acpi_smbus_softc *sc = cookie;
314 
315         mutex_exit(&sc->sc_i2c_mutex);
316 }
317 static int
318 acpi_smbus_exec(void *cookie, i2c_op_t op, i2c_addr_t addr,
319 	const void *cmdbuf, size_t cmdlen, void *buf, size_t len, int flags)
320 {
321         struct acpi_smbus_softc *sc = cookie;
322 	const uint8_t *c = cmdbuf;
323 	uint8_t *b = buf, *xb;
324 	int xlen;
325 	int r = 0;
326 	ACPI_BUFFER smbuf;
327 	ACPI_STATUS rv;
328 	ACPI_OBJECT_LIST args;
329 	ACPI_OBJECT arg[5];
330 	ACPI_OBJECT *p, *e;
331 
332 	smbuf.Pointer = NULL;
333 	smbuf.Length = ACPI_ALLOCATE_LOCAL_BUFFER;
334 	args.Pointer = arg;
335 	arg[0].Type = ACPI_TYPE_INTEGER;	/* Protocol */
336 	arg[1].Type = ACPI_TYPE_INTEGER;	/* Slave Addr */
337 	arg[1].Integer.Value = addr;
338 	arg[2].Type = ACPI_TYPE_INTEGER;	/* Command */
339 	if (I2C_OP_READ_P(op)) {
340 		args.Count = 3;
341 		if (len == 0) {
342 			arg[2].Integer.Value = 0;
343 			if (cmdlen == 0)
344 				arg[0].Integer.Value = ACPI_SMBUS_RD_QUICK;
345 			else
346 				arg[0].Integer.Value = ACPI_SMBUS_RCV_BYTE;
347 		} else
348 			arg[2].Integer.Value = *c;
349 		if (len == 1)
350 			arg[0].Integer.Value = ACPI_SMBUS_RD_BYTE;
351 		else if (len == 2)
352 			arg[0].Integer.Value = ACPI_SMBUS_RD_WORD;
353 		else if (len > 2)
354 			arg[0].Integer.Value = ACPI_SMBUS_RD_BLOCK;
355 		rv = AcpiEvaluateObject(sc->sc_devnode->ad_handle, "_SBR",
356 				    &args, &smbuf);
357 	} else {
358 		args.Count = 5;
359 		arg[3].Type = ACPI_TYPE_INTEGER;	/* Data Len */
360 		arg[3].Integer.Value = len;
361 		arg[4].Type = ACPI_TYPE_INTEGER;	/* Data */
362 		if (len == 0) {
363 			arg[4].Integer.Value = 0;
364 			if (cmdlen == 0) {
365 				arg[2].Integer.Value = 0;
366 				arg[0].Integer.Value = ACPI_SMBUS_WR_QUICK;
367 			} else {
368 				arg[2].Integer.Value = *c;
369 				arg[0].Integer.Value = ACPI_SMBUS_SND_BYTE;
370 			}
371 		} else
372 			arg[2].Integer.Value = *c;
373 		if (len == 1) {
374 			arg[0].Integer.Value = ACPI_SMBUS_WR_BYTE;
375 			arg[4].Integer.Value = *b;
376 		} else if (len == 2) {
377 			arg[0].Integer.Value = ACPI_SMBUS_WR_WORD;
378 			arg[4].Integer.Value = *b++;
379 			arg[4].Integer.Value += (*b--) << 8;
380 		} else if (len > 2) {
381 			arg[0].Integer.Value = ACPI_SMBUS_WR_BLOCK;
382 			arg[4].Type = ACPI_TYPE_BUFFER;
383 			arg[4].Buffer.Pointer = buf;
384 			arg[4].Buffer.Length = (len < 32?len:32);
385 		}
386 		rv = AcpiEvaluateObject(sc->sc_devnode->ad_handle, "_SBW",
387 				    &args, &smbuf);
388 	}
389 	if (ACPI_FAILURE(rv))
390 		r = 1;
391 	else {
392 		p = smbuf.Pointer;
393 		if (p == NULL || p->Type != ACPI_TYPE_PACKAGE ||
394 		    p->Package.Count < 1)
395 			r = 1;
396 		else {
397 			e = p->Package.Elements;
398 			if (e->Type == ACPI_TYPE_INTEGER)
399 				r = e[0].Integer.Value;
400 			else
401 				r = 1;
402 		}
403 		if (r != 0)
404 			r = 1;
405 
406 		/* For read operations, copy data to user buffer */
407 		if (r == 0 && I2C_OP_READ_P(op)) {
408 			if (p->Package.Count >= 3 &&
409 			    e[1].Type == ACPI_TYPE_INTEGER) {
410 				xlen = e[1].Integer.Value;
411 				if (xlen > len)
412 					xlen = len;
413 				if (xlen != 0 &&
414 				    e[2].Type == ACPI_TYPE_BUFFER) {
415 					xb = e[2].Buffer.Pointer;
416 					if (xb != NULL)
417 						memcpy(b, xb, xlen);
418 					else
419 						r = 1;
420 				} else if (e[2].Type == ACPI_TYPE_INTEGER) {
421 					if (xlen > 0)
422 						*b++ = e[2].Integer.Value &
423 							0xff;
424 					if (xlen > 1)
425 						*b = (e[2].Integer.Value >> 8);
426 				} else
427 					r = 1;
428 			} else
429 				r = 1;
430 		}
431 	}
432 	if (smbuf.Pointer)
433 		ACPI_FREE(smbuf.Pointer);
434 
435 	return r;
436 }
437 
438 /*
439  * acpi_smbus_alerts
440  *
441  * Whether triggered by periodic polling or an AcpiNotify, retrieve
442  * all pending SMBus device alerts
443  */
444 static void
445 acpi_smbus_alerts(struct acpi_smbus_softc *sc)
446 {
447 	int status = 0;
448 	uint8_t slave_addr;
449 	ACPI_STATUS rv;
450 	ACPI_BUFFER alert;
451 	ACPI_OBJECT *e, *p;
452 
453 	do {
454 		rv = acpi_eval_struct(sc->sc_devnode->ad_handle, "_SBA",
455 				      &alert);
456 		if (ACPI_FAILURE(rv)) {
457 			status = 1;
458 			goto done;
459 		}
460 
461 		p = alert.Pointer;
462 		if (p != NULL && p->Type == ACPI_TYPE_PACKAGE &&
463 		    p->Package.Count >= 2) {
464 			e = p->Package.Elements;
465 			if (e[0].Type == ACPI_TYPE_INTEGER)
466 				status = e[0].Integer.Value;
467 			else
468 				status = 1;
469 			if (status == 0x0 && e[1].Type == ACPI_TYPE_INTEGER) {
470 				slave_addr = e[1].Integer.Value;
471 				aprint_debug_dev(sc->sc_dv, "Alert for 0x%x\n",
472 						 slave_addr);
473 				(void)iic_smbus_intr(&sc->sc_i2c_tag);
474 			}
475 		}
476 done:
477 		if (alert.Pointer != NULL)
478 			ACPI_FREE(alert.Pointer);
479 	} while (status == 0);
480 }
481 
482 static void
483 acpi_smbus_tick(void *opaque)
484 {
485 	device_t dv = opaque;
486 	struct acpi_smbus_softc *sc = device_private(dv);
487 
488 	acpi_smbus_alerts(sc);
489 
490 	callout_schedule(&sc->sc_callout, sc->sc_poll_alert * hz);
491 }
492 
493 static void
494 acpi_smbus_notify_handler(ACPI_HANDLE hdl, uint32_t notify, void *opaque)
495 {
496 	device_t dv = opaque;
497 	struct acpi_smbus_softc *sc = device_private(dv);
498 
499 	aprint_debug_dev(dv, "received notify message 0x%x\n", notify);
500 	acpi_smbus_alerts(sc);
501 }
502