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