xref: /freebsd/sys/dev/acpi_support/atk0110.c (revision a0ee8cc6)
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 
32 #include <contrib/dev/acpica/include/acpi.h>
33 #include <dev/acpica/acpivar.h>
34 
35 /*
36  * ASUSTeK AI Booster (ACPI ASOC ATK0110).
37  *
38  * This code was originally written for OpenBSD after the techniques
39  * described in the Linux's asus_atk0110.c and FreeBSD's Takanori Watanabe's
40  * acpi_aiboost.c were verified to be accurate on the actual hardware kindly
41  * provided by Sam Fourman Jr.  It was subsequently ported from OpenBSD to
42  * DragonFly BSD, to NetBSD's sysmon_envsys(9) and to FreeBSD's sysctl(9).
43  *
44  *				  -- Constantine A. Murenin <http://cnst.su/>
45  */
46 
47 #define _COMPONENT	ACPI_OEM
48 ACPI_MODULE_NAME("aibs");
49 ACPI_SERIAL_DECL(aibs, "aibs");
50 
51 #define AIBS_MORE_SENSORS
52 #define AIBS_VERBOSE
53 
54 enum aibs_type {
55 	AIBS_VOLT,
56 	AIBS_TEMP,
57 	AIBS_FAN
58 };
59 
60 struct aibs_sensor {
61 	ACPI_INTEGER	v;
62 	ACPI_INTEGER	i;
63 	ACPI_INTEGER	l;
64 	ACPI_INTEGER	h;
65 	enum aibs_type	t;
66 };
67 
68 struct aibs_softc {
69 	struct device		*sc_dev;
70 	ACPI_HANDLE		sc_ah;
71 
72 	struct aibs_sensor	*sc_asens_volt;
73 	struct aibs_sensor	*sc_asens_temp;
74 	struct aibs_sensor	*sc_asens_fan;
75 };
76 
77 static int aibs_probe(device_t);
78 static int aibs_attach(device_t);
79 static int aibs_detach(device_t);
80 static int aibs_sysctl(SYSCTL_HANDLER_ARGS);
81 
82 static void aibs_attach_sif(struct aibs_softc *, enum aibs_type);
83 
84 static device_method_t aibs_methods[] = {
85 	DEVMETHOD(device_probe,		aibs_probe),
86 	DEVMETHOD(device_attach,	aibs_attach),
87 	DEVMETHOD(device_detach,	aibs_detach),
88 	{ NULL, NULL }
89 };
90 
91 static driver_t aibs_driver = {
92 	"aibs",
93 	aibs_methods,
94 	sizeof(struct aibs_softc)
95 };
96 
97 static devclass_t aibs_devclass;
98 
99 DRIVER_MODULE(aibs, acpi, aibs_driver, aibs_devclass, NULL, NULL);
100 MODULE_DEPEND(aibs, acpi, 1, 1, 1);
101 
102 static char* aibs_hids[] = {
103 	"ATK0110",
104 	NULL
105 };
106 
107 static int
108 aibs_probe(device_t dev)
109 {
110 	if (acpi_disabled("aibs") ||
111 	    ACPI_ID_PROBE(device_get_parent(dev), dev, aibs_hids) == NULL)
112 		return ENXIO;
113 
114 	device_set_desc(dev, "ASUSTeK AI Booster (ACPI ASOC ATK0110)");
115 	return 0;
116 }
117 
118 static int
119 aibs_attach(device_t dev)
120 {
121 	struct aibs_softc *sc = device_get_softc(dev);
122 
123 	sc->sc_dev = dev;
124 	sc->sc_ah = acpi_get_handle(dev);
125 
126 	aibs_attach_sif(sc, AIBS_VOLT);
127 	aibs_attach_sif(sc, AIBS_TEMP);
128 	aibs_attach_sif(sc, AIBS_FAN);
129 
130 	return 0;
131 }
132 
133 static void
134 aibs_attach_sif(struct aibs_softc *sc, enum aibs_type st)
135 {
136 	ACPI_STATUS		s;
137 	ACPI_BUFFER		b;
138 	ACPI_OBJECT		*bp, *o;
139 	int			i, n;
140 	const char		*node;
141 	char			name[] = "?SIF";
142 	struct aibs_sensor	*as;
143 	struct sysctl_oid	*so;
144 
145 	switch (st) {
146 	case AIBS_VOLT:
147 		node = "volt";
148 		name[0] = 'V';
149 		break;
150 	case AIBS_TEMP:
151 		node = "temp";
152 		name[0] = 'T';
153 		break;
154 	case AIBS_FAN:
155 		node = "fan";
156 		name[0] = 'F';
157 		break;
158 	default:
159 		return;
160 	}
161 
162 	b.Length = ACPI_ALLOCATE_BUFFER;
163 	s = AcpiEvaluateObjectTyped(sc->sc_ah, name, NULL, &b,
164 	    ACPI_TYPE_PACKAGE);
165 	if (ACPI_FAILURE(s)) {
166 		device_printf(sc->sc_dev, "%s not found\n", name);
167 		return;
168 	}
169 
170 	bp = b.Pointer;
171 	o = bp->Package.Elements;
172 	if (o[0].Type != ACPI_TYPE_INTEGER) {
173 		device_printf(sc->sc_dev, "%s[0]: invalid type\n", name);
174 		AcpiOsFree(b.Pointer);
175 		return;
176 	}
177 
178 	n = o[0].Integer.Value;
179 	if (bp->Package.Count - 1 < n) {
180 		device_printf(sc->sc_dev, "%s: invalid package\n", name);
181 		AcpiOsFree(b.Pointer);
182 		return;
183 	} else if (bp->Package.Count - 1 > n) {
184 		int on = n;
185 
186 #ifdef AIBS_MORE_SENSORS
187 		n = bp->Package.Count - 1;
188 #endif
189 		device_printf(sc->sc_dev, "%s: malformed package: %i/%i"
190 		    ", assume %i\n", name, on, bp->Package.Count - 1, n);
191 	}
192 	if (n < 1) {
193 		device_printf(sc->sc_dev, "%s: no members in the package\n",
194 		    name);
195 		AcpiOsFree(b.Pointer);
196 		return;
197 	}
198 
199 	as = malloc(sizeof(*as) * n, M_DEVBUF, M_NOWAIT | M_ZERO);
200 	if (as == NULL) {
201 		device_printf(sc->sc_dev, "%s: malloc fail\n", name);
202 		AcpiOsFree(b.Pointer);
203 		return;
204 	}
205 	switch (st) {
206 	case AIBS_VOLT:
207 		sc->sc_asens_volt = as;
208 		break;
209 	case AIBS_TEMP:
210 		sc->sc_asens_temp = as;
211 		break;
212 	case AIBS_FAN:
213 		sc->sc_asens_fan = as;
214 		break;
215 	}
216 
217 	/* sysctl subtree for sensors of this type */
218 	so = SYSCTL_ADD_NODE(device_get_sysctl_ctx(sc->sc_dev),
219 	    SYSCTL_CHILDREN(device_get_sysctl_tree(sc->sc_dev)), st,
220 	    node, CTLFLAG_RD, NULL, NULL);
221 
222 	for (i = 0, o++; i < n; i++, o++) {
223 		ACPI_OBJECT	*oi;
224 		char		si[3];
225 		const char	*desc;
226 
227 		/* acpica5 automatically evaluates the referenced package */
228 		if (o[0].Type != ACPI_TYPE_PACKAGE) {
229 			device_printf(sc->sc_dev,
230 			    "%s: %i: not a package: %i type\n",
231 			    name, i, o[0].Type);
232 			continue;
233 		}
234 		oi = o[0].Package.Elements;
235 		if (o[0].Package.Count != 5 ||
236 		    oi[0].Type != ACPI_TYPE_INTEGER ||
237 		    oi[1].Type != ACPI_TYPE_STRING ||
238 		    oi[2].Type != ACPI_TYPE_INTEGER ||
239 		    oi[3].Type != ACPI_TYPE_INTEGER ||
240 		    oi[4].Type != ACPI_TYPE_INTEGER) {
241 			device_printf(sc->sc_dev,
242 			    "%s: %i: invalid package\n",
243 			    name, i);
244 			continue;
245 		}
246 		as[i].i = oi[0].Integer.Value;
247 		desc = oi[1].String.Pointer;
248 		as[i].l = oi[2].Integer.Value;
249 		as[i].h = oi[3].Integer.Value;
250 		as[i].t = st;
251 #ifdef AIBS_VERBOSE
252 		device_printf(sc->sc_dev, "%c%i: "
253 		    "0x%08"PRIx64" %20s %5"PRIi64" / %5"PRIi64"  "
254 		    "0x%"PRIx64"\n",
255 		    name[0], i,
256 		    (uint64_t)as[i].i, desc, (int64_t)as[i].l,
257 		    (int64_t)as[i].h, (uint64_t)oi[4].Integer.Value);
258 #endif
259 		snprintf(si, sizeof(si), "%i", i);
260 		SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->sc_dev),
261 		    SYSCTL_CHILDREN(so), i, si, CTLTYPE_INT | CTLFLAG_RD,
262 		    sc, st, aibs_sysctl, st == AIBS_TEMP ? "IK" : "I", desc);
263 	}
264 
265 	AcpiOsFree(b.Pointer);
266 }
267 
268 static int
269 aibs_detach(device_t dev)
270 {
271 	struct aibs_softc	*sc = device_get_softc(dev);
272 
273 	if (sc->sc_asens_volt != NULL)
274 		free(sc->sc_asens_volt, M_DEVBUF);
275 	if (sc->sc_asens_temp != NULL)
276 		free(sc->sc_asens_temp, M_DEVBUF);
277 	if (sc->sc_asens_fan != NULL)
278 		free(sc->sc_asens_fan, M_DEVBUF);
279 	return 0;
280 }
281 
282 #ifdef AIBS_VERBOSE
283 #define ddevice_printf(x...) device_printf(x)
284 #else
285 #define ddevice_printf(x...)
286 #endif
287 
288 static int
289 aibs_sysctl(SYSCTL_HANDLER_ARGS)
290 {
291 	struct aibs_softc	*sc = arg1;
292 	enum aibs_type		st = arg2;
293 	int			i = oidp->oid_number;
294 	ACPI_STATUS		rs;
295 	ACPI_OBJECT		p, *bp;
296 	ACPI_OBJECT_LIST	mp;
297 	ACPI_BUFFER		b;
298 	char			*name;
299 	struct aibs_sensor	*as;
300 	ACPI_INTEGER		v, l, h;
301 	int			so[3];
302 
303 	switch (st) {
304 	case AIBS_VOLT:
305 		name = "RVLT";
306 		as = sc->sc_asens_volt;
307 		break;
308 	case AIBS_TEMP:
309 		name = "RTMP";
310 		as = sc->sc_asens_temp;
311 		break;
312 	case AIBS_FAN:
313 		name = "RFAN";
314 		as = sc->sc_asens_fan;
315 		break;
316 	default:
317 		return ENOENT;
318 	}
319 	if (as == NULL)
320 		return ENOENT;
321 	l = as[i].l;
322 	h = as[i].h;
323 	p.Type = ACPI_TYPE_INTEGER;
324 	p.Integer.Value = as[i].i;
325 	mp.Count = 1;
326 	mp.Pointer = &p;
327 	b.Length = ACPI_ALLOCATE_BUFFER;
328 	ACPI_SERIAL_BEGIN(aibs);
329 	rs = AcpiEvaluateObjectTyped(sc->sc_ah, name, &mp, &b,
330 	    ACPI_TYPE_INTEGER);
331 	if (ACPI_FAILURE(rs)) {
332 		ddevice_printf(sc->sc_dev,
333 		    "%s: %i: evaluation failed\n",
334 		    name, i);
335 		ACPI_SERIAL_END(aibs);
336 		return EIO;
337 	}
338 	bp = b.Pointer;
339 	v = bp->Integer.Value;
340 	AcpiOsFree(b.Pointer);
341 	ACPI_SERIAL_END(aibs);
342 
343 	switch (st) {
344 	case AIBS_VOLT:
345 		break;
346 	case AIBS_TEMP:
347 		v += 2732;
348 		l += 2732;
349 		h += 2732;
350 		break;
351 	case AIBS_FAN:
352 		break;
353 	}
354 	so[0] = v;
355 	so[1] = l;
356 	so[2] = h;
357 	return sysctl_handle_opaque(oidp, &so, sizeof(so), req);
358 }
359