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
48ACPI_MODULE_NAME("aibs");
49ACPI_SERIAL_DECL(aibs, "aibs");
50
51#define AIBS_MORE_SENSORS
52#define AIBS_VERBOSE
53
54enum aibs_type {
55	AIBS_VOLT,
56	AIBS_TEMP,
57	AIBS_FAN
58};
59
60struct 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
68struct 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
77static int aibs_probe(device_t);
78static int aibs_attach(device_t);
79static int aibs_detach(device_t);
80static int aibs_sysctl(SYSCTL_HANDLER_ARGS);
81
82static void aibs_attach_sif(struct aibs_softc *, enum aibs_type);
83
84static 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
91static driver_t aibs_driver = {
92	"aibs",
93	aibs_methods,
94	sizeof(struct aibs_softc)
95};
96
97static devclass_t aibs_devclass;
98
99DRIVER_MODULE(aibs, acpi, aibs_driver, aibs_devclass, NULL, NULL);
100MODULE_DEPEND(aibs, acpi, 1, 1, 1);
101
102static char* aibs_hids[] = {
103	"ATK0110",
104	NULL
105};
106
107static int
108aibs_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
118static int
119aibs_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
133static void
134aibs_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
268static int
269aibs_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
288static int
289aibs_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