xref: /freebsd/sys/dev/acpi_support/atk0110.c (revision e17f5b1d)
1 /*	$NetBSD: atk0110.c,v 1.4 2010/02/11 06:54:57 cnst Exp $	*/
2 /*	$OpenBSD: atk0110.c,v 1.1 2009/07/23 01:38:16 cnst Exp $	*/
3 
4 /*
5  * Copyright (c) 2009, 2010 Constantine A. Murenin <cnst++@FreeBSD.org>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #include <sys/cdefs.h>
21 __FBSDID("$FreeBSD$");
22 
23 #include <machine/_inttypes.h>
24 #include <sys/param.h>
25 #include <sys/systm.h>
26 #include <sys/kernel.h>
27 #include <sys/bus.h>
28 #include <sys/module.h>
29 #include <sys/malloc.h>
30 #include <sys/sysctl.h>
31 #include <sys/stdint.h>
32 
33 #include <contrib/dev/acpica/include/acpi.h>
34 #include <dev/acpica/acpivar.h>
35 
36 /*
37  * ASUSTeK AI Booster (ACPI ASOC ATK0110).
38  *
39  * This code was originally written for OpenBSD after the techniques
40  * described in the Linux's asus_atk0110.c and FreeBSD's Takanori Watanabe's
41  * acpi_aiboost.c were verified to be accurate on the actual hardware kindly
42  * provided by Sam Fourman Jr.  It was subsequently ported from OpenBSD to
43  * DragonFly BSD, to NetBSD's sysmon_envsys(9) and to FreeBSD's sysctl(9).
44  *
45  *				  -- Constantine A. Murenin <http://cnst.su/>
46  */
47 
48 #define _COMPONENT	ACPI_OEM
49 ACPI_MODULE_NAME("aibs");
50 ACPI_SERIAL_DECL(aibs, "aibs");
51 
52 #define AIBS_MORE_SENSORS
53 #define AIBS_VERBOSE
54 
55 #define	AIBS_GROUP_SENSORS	0x06
56 
57 #define AIBS_SENS_TYPE(x)	(((x) >> 16) & 0xff)
58 #define AIBS_SENS_TYPE_VOLT	2
59 #define AIBS_SENS_TYPE_TEMP	3
60 #define AIBS_SENS_TYPE_FAN	4
61 
62 #define	AIBS_SENS_TYPE_VOLT_NAME		"volt"
63 #define	AIBS_SENS_TYPE_VOLT_TEMP		"temp"
64 #define	AIBS_SENS_TYPE_VOLT_FAN		"fan"
65 
66 struct aibs_sensor {
67 	ACPI_INTEGER	v;
68 	ACPI_INTEGER	i;
69 	ACPI_INTEGER	l;
70 	ACPI_INTEGER	h;
71 	int		t;
72 };
73 
74 struct aibs_softc {
75 	device_t		sc_dev;
76 	ACPI_HANDLE		sc_ah;
77 
78 	struct aibs_sensor	*sc_asens_volt;
79 	struct aibs_sensor	*sc_asens_temp;
80 	struct aibs_sensor	*sc_asens_fan;
81 	struct aibs_sensor	*sc_asens_all;
82 
83 	struct sysctl_oid	*sc_volt_sysctl;
84 	struct sysctl_oid	*sc_temp_sysctl;
85 	struct sysctl_oid	*sc_fan_sysctl;
86 
87 	bool			sc_ggrp_method;
88 };
89 
90 static int aibs_probe(device_t);
91 static int aibs_attach(device_t);
92 static int aibs_detach(device_t);
93 static int aibs_sysctl(SYSCTL_HANDLER_ARGS);
94 static int aibs_sysctl_ggrp(SYSCTL_HANDLER_ARGS);
95 
96 static int aibs_attach_ggrp(struct aibs_softc *);
97 static int aibs_attach_sif(struct aibs_softc *, int);
98 
99 static device_method_t aibs_methods[] = {
100 	DEVMETHOD(device_probe,		aibs_probe),
101 	DEVMETHOD(device_attach,	aibs_attach),
102 	DEVMETHOD(device_detach,	aibs_detach),
103 	{ NULL, NULL }
104 };
105 
106 static driver_t aibs_driver = {
107 	"aibs",
108 	aibs_methods,
109 	sizeof(struct aibs_softc)
110 };
111 
112 static devclass_t aibs_devclass;
113 
114 DRIVER_MODULE(aibs, acpi, aibs_driver, aibs_devclass, NULL, NULL);
115 MODULE_DEPEND(aibs, acpi, 1, 1, 1);
116 
117 static char* aibs_hids[] = {
118 	"ATK0110",
119 	NULL
120 };
121 
122 static int
123 aibs_probe(device_t dev)
124 {
125 	int rv;
126 
127 	if (acpi_disabled("aibs"))
128 		return (ENXIO);
129 	rv = ACPI_ID_PROBE(device_get_parent(dev), dev, aibs_hids, NULL);
130 	if (rv <= 0 )
131 		device_set_desc(dev, "ASUSTeK AI Booster (ACPI ASOC ATK0110)");
132 	return (rv);
133 }
134 
135 static int
136 aibs_attach(device_t dev)
137 {
138 	struct aibs_softc *sc = device_get_softc(dev);
139 	int err;
140 
141 	sc->sc_dev = dev;
142 	sc->sc_ah = acpi_get_handle(dev);
143 
144 	sc->sc_ggrp_method = false;
145 	err = aibs_attach_sif(sc, AIBS_SENS_TYPE_VOLT);
146 	if (err == 0)
147 		err = aibs_attach_sif(sc, AIBS_SENS_TYPE_TEMP);
148 	if (err == 0)
149 		err = aibs_attach_sif(sc, AIBS_SENS_TYPE_FAN);
150 
151 	if (err == 0)
152 		return (0);
153 
154 	/* Clean up whatever was allocated earlier. */
155 	if (sc->sc_volt_sysctl != NULL)
156 		sysctl_remove_oid(sc->sc_volt_sysctl, true, true);
157 	if (sc->sc_temp_sysctl != NULL)
158 		sysctl_remove_oid(sc->sc_temp_sysctl, true, true);
159 	if (sc->sc_fan_sysctl != NULL)
160 		sysctl_remove_oid(sc->sc_fan_sysctl, true, true);
161 	aibs_detach(dev);
162 
163 	sc->sc_ggrp_method = true;
164 	err = aibs_attach_ggrp(sc);
165 	return (err);
166 }
167 
168 static int
169 aibs_add_sensor(struct aibs_softc *sc, ACPI_OBJECT *o,
170     struct aibs_sensor* sensor, const char ** descr)
171 {
172 	int		off;
173 
174 	/*
175 	 * Packages for the old and new methods are quite
176 	 * similar except that the new package has two
177 	 * new (unknown / unused) fields after the name field.
178 	 */
179 	if (sc->sc_ggrp_method)
180 		off = 4;
181 	else
182 		off = 2;
183 
184 	if (o->Type != ACPI_TYPE_PACKAGE) {
185 		device_printf(sc->sc_dev,
186 		    "sensor object is not a package: %i type\n",
187 		     o->Type);
188 		return (ENXIO);
189 	}
190 	if (o[0].Package.Count != (off + 3) ||
191 	    o->Package.Elements[0].Type != ACPI_TYPE_INTEGER ||
192 	    o->Package.Elements[1].Type != ACPI_TYPE_STRING ||
193 	    o->Package.Elements[off].Type != ACPI_TYPE_INTEGER ||
194 	    o->Package.Elements[off + 1].Type != ACPI_TYPE_INTEGER ||
195 	    o->Package.Elements[off + 2].Type != ACPI_TYPE_INTEGER) {
196 		device_printf(sc->sc_dev, "unexpected package content\n");
197 		return (ENXIO);
198 	}
199 
200 	sensor->i = o->Package.Elements[0].Integer.Value;
201 	*descr = o->Package.Elements[1].String.Pointer;
202 	sensor->l = o->Package.Elements[off].Integer.Value;
203 	sensor->h = o->Package.Elements[off + 1].Integer.Value;
204 	/* For the new method the second value is a range size. */
205 	if (sc->sc_ggrp_method)
206 		sensor->h += sensor->l;
207 	sensor->t = AIBS_SENS_TYPE(sensor->i);
208 
209 	switch (sensor->t) {
210 	case AIBS_SENS_TYPE_VOLT:
211 	case AIBS_SENS_TYPE_TEMP:
212 	case AIBS_SENS_TYPE_FAN:
213 		return (0);
214 	default:
215 		device_printf(sc->sc_dev, "unknown sensor type 0x%x",
216 		    sensor->t);
217 		return (ENXIO);
218 	}
219 }
220 
221 static void
222 aibs_sensor_added(struct aibs_softc *sc, struct sysctl_oid *so,
223     const char *type_name, int idx, struct aibs_sensor *sensor,
224     const char *descr)
225 {
226 	char	sysctl_name[8];
227 
228 	snprintf(sysctl_name, sizeof(sysctl_name), "%i", idx);
229 #ifdef AIBS_VERBOSE
230 	device_printf(sc->sc_dev, "%c%i: 0x%08jx %20s %5jd / %5jd\n",
231 	    type_name[0], idx,
232 	    (uintmax_t)sensor->i, descr, (intmax_t)sensor->l,
233 	    (intmax_t)sensor->h);
234 #endif
235 	SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->sc_dev),
236 	    SYSCTL_CHILDREN(so), idx, sysctl_name,
237 	    CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_NEEDGIANT, sc, (uintptr_t)sensor,
238 	    sc->sc_ggrp_method ? aibs_sysctl_ggrp : aibs_sysctl,
239 	    sensor->t == AIBS_SENS_TYPE_TEMP ? "IK" : "I", descr);
240 }
241 
242 static int
243 aibs_attach_ggrp(struct aibs_softc *sc)
244 {
245 	ACPI_STATUS		s;
246 	ACPI_BUFFER		buf;
247 	ACPI_HANDLE		h;
248 	ACPI_OBJECT		id;
249 	ACPI_OBJECT		*bp;
250 	ACPI_OBJECT_LIST	arg;
251 	int			i;
252 	int			t, v, f;
253 	int			err;
254 	int			*s_idx;
255 	const char		*name;
256 	const char		*descr;
257 	struct aibs_sensor	*sensor;
258 	struct sysctl_oid	**so;
259 
260 	/* First see if GITM is available. */
261 	s = AcpiGetHandle(sc->sc_ah, "GITM", &h);
262 	if (ACPI_FAILURE(s)) {
263 		if (bootverbose)
264 			device_printf(sc->sc_dev, "GITM not found\n");
265 		return (ENXIO);
266 	}
267 
268 	/*
269 	 * Now call GGRP with the appropriate argument to list sensors.
270 	 * The method lists different groups of entities depending on
271 	 * the argument.
272 	 */
273 	id.Integer.Value = AIBS_GROUP_SENSORS;
274 	id.Type = ACPI_TYPE_INTEGER;
275 	arg.Count = 1;
276 	arg.Pointer = &id;
277 	buf.Length = ACPI_ALLOCATE_BUFFER;
278 	buf.Pointer = NULL;
279 	s = AcpiEvaluateObjectTyped(sc->sc_ah, "GGRP", &arg, &buf,
280 	    ACPI_TYPE_PACKAGE);
281 	if (ACPI_FAILURE(s)) {
282 		device_printf(sc->sc_dev, "GGRP not found\n");
283 		return (ENXIO);
284 	}
285 
286 	bp = buf.Pointer;
287 	sc->sc_asens_all = malloc(sizeof(*sc->sc_asens_all) * bp->Package.Count,
288 	    M_DEVBUF, M_WAITOK | M_ZERO);
289 	v = t = f = 0;
290 	for (i = 0; i < bp->Package.Count; i++) {
291 		sensor = &sc->sc_asens_all[i];
292 		err = aibs_add_sensor(sc, &bp->Package.Elements[i], sensor,
293 		    &descr);
294 		if (err != 0)
295 			continue;
296 
297 		switch (sensor->t) {
298 		case AIBS_SENS_TYPE_VOLT:
299 			name = "volt";
300 			so = &sc->sc_volt_sysctl;
301 			s_idx = &v;
302 			break;
303 		case AIBS_SENS_TYPE_TEMP:
304 			name = "temp";
305 			so = &sc->sc_temp_sysctl;
306 			s_idx = &t;
307 			break;
308 		case AIBS_SENS_TYPE_FAN:
309 			name = "fan";
310 			so = &sc->sc_fan_sysctl;
311 			s_idx = &f;
312 			break;
313 		default:
314 			panic("add_sensor succeeded for unknown sensor type %d",
315 			    sensor->t);
316 		}
317 
318 		if (*so == NULL) {
319 			/* sysctl subtree for sensors of this type */
320 			*so = SYSCTL_ADD_NODE(device_get_sysctl_ctx(sc->sc_dev),
321 			    SYSCTL_CHILDREN(device_get_sysctl_tree(sc->sc_dev)),
322 			    sensor->t, name, CTLFLAG_RD | CTLFLAG_MPSAFE,
323 			    NULL, NULL);
324 		}
325 		aibs_sensor_added(sc, *so, name, *s_idx, sensor, descr);
326 		*s_idx += 1;
327 	}
328 
329 	AcpiOsFree(buf.Pointer);
330 	return (0);
331 }
332 
333 static int
334 aibs_attach_sif(struct aibs_softc *sc, int st)
335 {
336 	char			name[] = "?SIF";
337 	ACPI_STATUS		s;
338 	ACPI_BUFFER		b;
339 	ACPI_OBJECT		*bp, *o;
340 	const char		*node;
341 	struct aibs_sensor	*as;
342 	struct sysctl_oid	**so;
343 	int			i, n;
344 	int err;
345 
346 	switch (st) {
347 	case AIBS_SENS_TYPE_VOLT:
348 		node = "volt";
349 		name[0] = 'V';
350 		so = &sc->sc_volt_sysctl;
351 		break;
352 	case AIBS_SENS_TYPE_TEMP:
353 		node = "temp";
354 		name[0] = 'T';
355 		so = &sc->sc_temp_sysctl;
356 		break;
357 	case AIBS_SENS_TYPE_FAN:
358 		node = "fan";
359 		name[0] = 'F';
360 		so = &sc->sc_fan_sysctl;
361 		break;
362 	default:
363 		panic("Unsupported sensor type %d", st);
364 	}
365 
366 	b.Length = ACPI_ALLOCATE_BUFFER;
367 	s = AcpiEvaluateObjectTyped(sc->sc_ah, name, NULL, &b,
368 	    ACPI_TYPE_PACKAGE);
369 	if (ACPI_FAILURE(s)) {
370 		device_printf(sc->sc_dev, "%s not found\n", name);
371 		return (ENXIO);
372 	}
373 
374 	bp = b.Pointer;
375 	o = bp->Package.Elements;
376 	if (o[0].Type != ACPI_TYPE_INTEGER) {
377 		device_printf(sc->sc_dev, "%s[0]: invalid type\n", name);
378 		AcpiOsFree(b.Pointer);
379 		return (ENXIO);
380 	}
381 
382 	n = o[0].Integer.Value;
383 	if (bp->Package.Count - 1 < n) {
384 		device_printf(sc->sc_dev, "%s: invalid package\n", name);
385 		AcpiOsFree(b.Pointer);
386 		return (ENXIO);
387 	} else if (bp->Package.Count - 1 > n) {
388 		int on = n;
389 
390 #ifdef AIBS_MORE_SENSORS
391 		n = bp->Package.Count - 1;
392 #endif
393 		device_printf(sc->sc_dev, "%s: malformed package: %i/%i"
394 		    ", assume %i\n", name, on, bp->Package.Count - 1, n);
395 	}
396 	if (n < 1) {
397 		device_printf(sc->sc_dev, "%s: no members in the package\n",
398 		    name);
399 		AcpiOsFree(b.Pointer);
400 		return (ENXIO);
401 	}
402 
403 	as = malloc(sizeof(*as) * n, M_DEVBUF, M_WAITOK | M_ZERO);
404 	switch (st) {
405 	case AIBS_SENS_TYPE_VOLT:
406 		sc->sc_asens_volt = as;
407 		break;
408 	case AIBS_SENS_TYPE_TEMP:
409 		sc->sc_asens_temp = as;
410 		break;
411 	case AIBS_SENS_TYPE_FAN:
412 		sc->sc_asens_fan = as;
413 		break;
414 	}
415 
416 	/* sysctl subtree for sensors of this type */
417 	*so = SYSCTL_ADD_NODE(device_get_sysctl_ctx(sc->sc_dev),
418 	    SYSCTL_CHILDREN(device_get_sysctl_tree(sc->sc_dev)), st,
419 	    node, CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, NULL);
420 
421 	for (i = 0, o++; i < n; i++, o++) {
422 		const char	*descr;
423 
424 		err = aibs_add_sensor(sc, o, &as[i], &descr);
425 		if (err == 0)
426 			aibs_sensor_added(sc, *so, node, i, &as[i], descr);
427 	}
428 
429 	AcpiOsFree(b.Pointer);
430 	return (0);
431 }
432 
433 static int
434 aibs_detach(device_t dev)
435 {
436 	struct aibs_softc	*sc = device_get_softc(dev);
437 
438 	if (sc->sc_asens_volt != NULL)
439 		free(sc->sc_asens_volt, M_DEVBUF);
440 	if (sc->sc_asens_temp != NULL)
441 		free(sc->sc_asens_temp, M_DEVBUF);
442 	if (sc->sc_asens_fan != NULL)
443 		free(sc->sc_asens_fan, M_DEVBUF);
444 	if (sc->sc_asens_all != NULL)
445 		free(sc->sc_asens_all, M_DEVBUF);
446 	return (0);
447 }
448 
449 #ifdef AIBS_VERBOSE
450 #define ddevice_printf(x...) device_printf(x)
451 #else
452 #define ddevice_printf(x...)
453 #endif
454 
455 static int
456 aibs_sysctl(SYSCTL_HANDLER_ARGS)
457 {
458 	struct aibs_softc	*sc = arg1;
459 	struct aibs_sensor	*sensor = (void *)(intptr_t)arg2;
460 	int			i = oidp->oid_number;
461 	ACPI_STATUS		rs;
462 	ACPI_OBJECT		p, *bp;
463 	ACPI_OBJECT_LIST	mp;
464 	ACPI_BUFFER		b;
465 	char			*name;
466 	ACPI_INTEGER		v, l, h;
467 	int			so[3];
468 
469 	switch (sensor->t) {
470 	case AIBS_SENS_TYPE_VOLT:
471 		name = "RVLT";
472 		break;
473 	case AIBS_SENS_TYPE_TEMP:
474 		name = "RTMP";
475 		break;
476 	case AIBS_SENS_TYPE_FAN:
477 		name = "RFAN";
478 		break;
479 	default:
480 		return (ENOENT);
481 	}
482 	l = sensor->l;
483 	h = sensor->h;
484 	p.Type = ACPI_TYPE_INTEGER;
485 	p.Integer.Value = sensor->i;
486 	mp.Count = 1;
487 	mp.Pointer = &p;
488 	b.Length = ACPI_ALLOCATE_BUFFER;
489 	ACPI_SERIAL_BEGIN(aibs);
490 	rs = AcpiEvaluateObjectTyped(sc->sc_ah, name, &mp, &b,
491 	    ACPI_TYPE_INTEGER);
492 	if (ACPI_FAILURE(rs)) {
493 		ddevice_printf(sc->sc_dev,
494 		    "%s: %i: evaluation failed\n",
495 		    name, i);
496 		ACPI_SERIAL_END(aibs);
497 		return (EIO);
498 	}
499 	bp = b.Pointer;
500 	v = bp->Integer.Value;
501 	AcpiOsFree(b.Pointer);
502 	ACPI_SERIAL_END(aibs);
503 
504 	switch (sensor->t) {
505 	case AIBS_SENS_TYPE_VOLT:
506 		break;
507 	case AIBS_SENS_TYPE_TEMP:
508 		v += 2731;
509 		l += 2731;
510 		h += 2731;
511 		break;
512 	case AIBS_SENS_TYPE_FAN:
513 		break;
514 	}
515 	so[0] = v;
516 	so[1] = l;
517 	so[2] = h;
518 	return (sysctl_handle_opaque(oidp, &so, sizeof(so), req));
519 }
520 
521 static int
522 aibs_sysctl_ggrp(SYSCTL_HANDLER_ARGS)
523 {
524 	struct aibs_softc	*sc = arg1;
525 	struct aibs_sensor	*sensor = (void *)(intptr_t)arg2;
526 	ACPI_STATUS		rs;
527 	ACPI_OBJECT		p, *bp;
528 	ACPI_OBJECT_LIST	arg;
529 	ACPI_BUFFER		buf;
530 	ACPI_INTEGER		v, l, h;
531 	int			so[3];
532 	uint32_t		*ret;
533 	uint32_t		cmd[3];
534 
535 	cmd[0] = sensor->i;
536 	cmd[1] = 0;
537 	cmd[2] = 0;
538 	p.Type = ACPI_TYPE_BUFFER;
539 	p.Buffer.Pointer = (void *)cmd;
540 	p.Buffer.Length = sizeof(cmd);
541 	arg.Count = 1;
542 	arg.Pointer = &p;
543 	buf.Pointer = NULL;
544 	buf.Length = ACPI_ALLOCATE_BUFFER;
545 	ACPI_SERIAL_BEGIN(aibs);
546 	rs = AcpiEvaluateObjectTyped(sc->sc_ah, "GITM", &arg, &buf,
547 	    ACPI_TYPE_BUFFER);
548 	ACPI_SERIAL_END(aibs);
549 	if (ACPI_FAILURE(rs)) {
550 		device_printf(sc->sc_dev, "GITM evaluation failed\n");
551 		return (EIO);
552 	}
553 	bp = buf.Pointer;
554 	if (bp->Buffer.Length < 8) {
555 		device_printf(sc->sc_dev, "GITM returned short buffer\n");
556 		return (EIO);
557 	}
558 	ret = (uint32_t *)bp->Buffer.Pointer;
559 	if (ret[0] == 0) {
560 		device_printf(sc->sc_dev, "GITM returned error status\n");
561 		return (EINVAL);
562 	}
563 	v = ret[1];
564 	AcpiOsFree(buf.Pointer);
565 
566 	l = sensor->l;
567 	h = sensor->h;
568 
569 	switch (sensor->t) {
570 	case AIBS_SENS_TYPE_VOLT:
571 		break;
572 	case AIBS_SENS_TYPE_TEMP:
573 		v += 2731;
574 		l += 2731;
575 		h += 2731;
576 		break;
577 	case AIBS_SENS_TYPE_FAN:
578 		break;
579 	}
580 	so[0] = v;
581 	so[1] = l;
582 	so[2] = h;
583 	return (sysctl_handle_opaque(oidp, &so, sizeof(so), req));
584 }
585