xref: /netbsd-src/sys/dev/acpi/aibs_acpi.c (revision 6a493d6bc668897c91594964a732d38505b70cbb)
1 /* $NetBSD: aibs_acpi.c,v 1.4 2012/08/14 14:36:43 jruoho Exp $ */
2 
3 /*-
4  * Copyright (c) 2011 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Jukka Ruohonen.
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 /*	$OpenBSD: atk0110.c,v 1.1 2009/07/23 01:38:16 cnst Exp $	*/
33 /*
34  * Copyright (c) 2009 Constantine A. Murenin <cnst+netbsd@bugmail.mojo.ru>
35  *
36  * Permission to use, copy, modify, and distribute this software for any
37  * purpose with or without fee is hereby granted, provided that the above
38  * copyright notice and this permission notice appear in all copies.
39  *
40  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
41  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
42  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
43  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
44  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
45  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
46  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
47  */
48 
49 #include <sys/cdefs.h>
50 __KERNEL_RCSID(0, "$NetBSD: aibs_acpi.c,v 1.4 2012/08/14 14:36:43 jruoho Exp $");
51 
52 #include <sys/param.h>
53 #include <sys/kmem.h>
54 #include <sys/module.h>
55 
56 #include <dev/acpi/acpireg.h>
57 #include <dev/acpi/acpivar.h>
58 
59 /*
60  * ASUSTeK AI Booster (ACPI ASOC ATK0110).
61  *
62  * This code was originally written for OpenBSD after the techniques
63  * described in the Linux's asus_atk0110.c and FreeBSD's acpi_aiboost.c
64  * were verified to be accurate on the actual hardware kindly provided by
65  * Sam Fourman Jr.  It was subsequently ported from OpenBSD to DragonFly BSD,
66  * and then to the NetBSD's sysmon_envsys(9) framework.
67  *
68  *				  -- Constantine A. Murenin <http://cnst.su/>
69  */
70 
71 #define _COMPONENT		 ACPI_RESOURCE_COMPONENT
72 ACPI_MODULE_NAME		 ("acpi_aibs")
73 
74 #define AIBS_MUX_HWMON		 0x00000006
75 #define AIBS_MUX_MGMT		 0x00000011
76 
77 #define AIBS_TYPE(x)		 (((x) >> 16) & 0xff)
78 #define AIBS_TYPE_VOLT		 2
79 #define AIBS_TYPE_TEMP		 3
80 #define AIBS_TYPE_FAN		 4
81 
82 struct aibs_sensor {
83 	envsys_data_t			 as_sensor;
84 	uint64_t			 as_type;
85 	uint64_t			 as_liml;
86 	uint64_t			 as_limh;
87 
88 	SIMPLEQ_ENTRY(aibs_sensor)	 as_list;
89 };
90 
91 struct aibs_softc {
92 	device_t			 sc_dev;
93 	struct acpi_devnode		*sc_node;
94 	struct sysmon_envsys		*sc_sme;
95 	bool				 sc_model;	/* new model = true */
96 
97 	SIMPLEQ_HEAD(, aibs_sensor)	 as_head;
98 };
99 
100 static int	aibs_match(device_t, cfdata_t, void *);
101 static void	aibs_attach(device_t, device_t, void *);
102 static int	aibs_detach(device_t, int);
103 
104 static void	aibs_init(device_t);
105 static void	aibs_init_new(device_t);
106 static void	aibs_init_old(device_t, int);
107 
108 static void	aibs_sensor_add(device_t, ACPI_OBJECT *);
109 static bool	aibs_sensor_value(device_t, struct aibs_sensor *, uint64_t *);
110 static void	aibs_sensor_refresh(struct sysmon_envsys *, envsys_data_t *);
111 static void	aibs_sensor_limits(struct sysmon_envsys *, envsys_data_t *,
112 				   sysmon_envsys_lim_t *, uint32_t *);
113 
114 CFATTACH_DECL_NEW(aibs, sizeof(struct aibs_softc),
115     aibs_match, aibs_attach, aibs_detach, NULL);
116 
117 static const char* const aibs_hid[] = {
118 	"ATK0110",
119 	NULL
120 };
121 
122 static int
123 aibs_match(device_t parent, cfdata_t match, void *aux)
124 {
125 	struct acpi_attach_args *aa = aux;
126 
127 	if (aa->aa_node->ad_type != ACPI_TYPE_DEVICE)
128 		return 0;
129 
130 	return acpi_match_hid(aa->aa_node->ad_devinfo, aibs_hid);
131 }
132 
133 static void
134 aibs_attach(device_t parent, device_t self, void *aux)
135 {
136 	struct aibs_softc *sc = device_private(self);
137 	struct acpi_attach_args *aa = aux;
138 
139 	sc->sc_dev = self;
140 	sc->sc_node = aa->aa_node;
141 
142 	aprint_naive("\n");
143 	aprint_normal(": ASUSTeK AI Booster\n");
144 
145 	sc->sc_sme = sysmon_envsys_create();
146 
147 	sc->sc_sme->sme_cookie = sc;
148 	sc->sc_sme->sme_name = device_xname(self);
149 	sc->sc_sme->sme_refresh = aibs_sensor_refresh;
150 	sc->sc_sme->sme_get_limits = aibs_sensor_limits;
151 
152 	aibs_init(self);
153 	SIMPLEQ_INIT(&sc->as_head);
154 
155 	if (sc->sc_model != false)
156 		aibs_init_new(self);
157 	else {
158 		aibs_init_old(self, AIBS_TYPE_FAN);
159 		aibs_init_old(self, AIBS_TYPE_TEMP);
160 		aibs_init_old(self, AIBS_TYPE_VOLT);
161 	}
162 
163 	(void)pmf_device_register(self, NULL, NULL);
164 
165 	if (sc->sc_sme->sme_nsensors == 0) {
166 		aprint_error_dev(self, "no sensors found\n");
167 		sysmon_envsys_destroy(sc->sc_sme);
168 		sc->sc_sme = NULL;
169 		return;
170 	}
171 
172 	if (sysmon_envsys_register(sc->sc_sme) != 0)
173 		aprint_error_dev(self, "failed to register with sysmon\n");
174 }
175 
176 static int
177 aibs_detach(device_t self, int flags)
178 {
179 	struct aibs_softc *sc = device_private(self);
180 	struct aibs_sensor *as;
181 
182 	pmf_device_deregister(self);
183 
184 	if (sc->sc_sme != NULL)
185 		sysmon_envsys_unregister(sc->sc_sme);
186 
187 	while (SIMPLEQ_FIRST(&sc->as_head) != NULL) {
188 		as = SIMPLEQ_FIRST(&sc->as_head);
189 		SIMPLEQ_REMOVE_HEAD(&sc->as_head, as_list);
190 		kmem_free(as, sizeof(*as));
191 	}
192 
193 	return 0;
194 }
195 
196 static void
197 aibs_init(device_t self)
198 {
199 	struct aibs_softc *sc = device_private(self);
200 	ACPI_HANDLE tmp;
201 	ACPI_STATUS rv;
202 
203 	/*
204 	 * Old model uses the tuple { TSIF, VSIF, FSIF } to
205 	 * enumerate the sensors and { RTMP, RVLT, RFAN }
206 	 * to obtain the values. New mode uses GGRP for the
207 	 * enumeration and { GITM, SITM } as accessors.
208 	 */
209 	rv = AcpiGetHandle(sc->sc_node->ad_handle, "GGRP", &tmp);
210 
211 	if (ACPI_FAILURE(rv)) {
212 		sc->sc_model = false;
213 		return;
214 	}
215 
216 	rv = AcpiGetHandle(sc->sc_node->ad_handle, "GITM", &tmp);
217 
218 	if (ACPI_FAILURE(rv)) {
219 		sc->sc_model = false;
220 		return;
221 	}
222 
223 	rv = AcpiGetHandle(sc->sc_node->ad_handle, "SITM", &tmp);
224 
225 	if (ACPI_FAILURE(rv)) {
226 		sc->sc_model = false;
227 		return;
228 	}
229 
230 	sc->sc_model = true;
231 
232 	/*
233 	 * If both the new and the old methods are present, prefer
234 	 * the old one; GGRP/GITM may not be functional in this case.
235 	 */
236 	rv = AcpiGetHandle(sc->sc_node->ad_handle, "FSIF", &tmp);
237 
238 	if (ACPI_FAILURE(rv))
239 		return;
240 
241 	rv = AcpiGetHandle(sc->sc_node->ad_handle, "TSIF", &tmp);
242 
243 	if (ACPI_FAILURE(rv))
244 		return;
245 
246 	rv = AcpiGetHandle(sc->sc_node->ad_handle, "VSIF", &tmp);
247 
248 	if (ACPI_FAILURE(rv))
249 		return;
250 
251 	rv = AcpiGetHandle(sc->sc_node->ad_handle, "RFAN", &tmp);
252 
253 	if (ACPI_FAILURE(rv))
254 		return;
255 
256 	rv = AcpiGetHandle(sc->sc_node->ad_handle, "RTMP", &tmp);
257 
258 	if (ACPI_FAILURE(rv))
259 		return;
260 
261 	rv = AcpiGetHandle(sc->sc_node->ad_handle, "RVLT", &tmp);
262 
263 	if (ACPI_FAILURE(rv))
264 		return;
265 
266 	sc->sc_model = false;
267 }
268 
269 static void
270 aibs_init_new(device_t self)
271 {
272 	struct aibs_softc *sc = device_private(self);
273 	ACPI_OBJECT_LIST arg;
274 	ACPI_OBJECT id, *obj;
275 	ACPI_BUFFER buf;
276 	ACPI_STATUS rv;
277 	uint32_t i, n;
278 
279 	arg.Count = 1;
280 	arg.Pointer = &id;
281 
282 	id.Type = ACPI_TYPE_INTEGER;
283 	id.Integer.Value = AIBS_MUX_HWMON;
284 
285 	buf.Pointer = NULL;
286 	buf.Length = ACPI_ALLOCATE_LOCAL_BUFFER;
287 
288 	rv = AcpiEvaluateObject(sc->sc_node->ad_handle, "GGRP", &arg, &buf);
289 
290 	if (ACPI_FAILURE(rv))
291 		goto out;
292 
293 	obj = buf.Pointer;
294 
295 	if (obj->Type != ACPI_TYPE_PACKAGE) {
296 		rv = AE_TYPE;
297 		goto out;
298 	}
299 
300 	if (obj->Package.Count > UINT32_MAX) {
301 		rv = AE_AML_NUMERIC_OVERFLOW;
302 		goto out;
303 	}
304 
305 	n = obj->Package.Count;
306 
307 	if (n == 0) {
308 		rv = AE_NOT_EXIST;
309 		goto out;
310 	}
311 
312 	for (i = 0; i < n; i++)
313 		aibs_sensor_add(self, &obj->Package.Elements[i]);
314 
315 out:
316 	if (buf.Pointer != NULL)
317 		ACPI_FREE(buf.Pointer);
318 
319 	if (ACPI_FAILURE(rv)) {
320 
321 		aprint_error_dev(self, "failed to evaluate "
322 		    "GGRP: %s\n", AcpiFormatException(rv));
323 	}
324 }
325 
326 static void
327 aibs_init_old(device_t self, int type)
328 {
329 	struct aibs_softc *sc = device_private(self);
330 	char path[] = "?SIF";
331 	ACPI_OBJECT *elm, *obj;
332 	ACPI_BUFFER buf;
333 	ACPI_STATUS rv;
334 	uint32_t i, n;
335 
336 	switch (type) {
337 
338 	case AIBS_TYPE_FAN:
339 		path[0] = 'F';
340 		break;
341 
342 	case AIBS_TYPE_TEMP:
343 		path[0] = 'T';
344 		break;
345 
346 	case AIBS_TYPE_VOLT:
347 		path[0] = 'V';
348 		break;
349 
350 	default:
351 		return;
352 	}
353 
354 	rv = acpi_eval_struct(sc->sc_node->ad_handle, path, &buf);
355 
356 	if (ACPI_FAILURE(rv))
357 		goto out;
358 
359 	obj = buf.Pointer;
360 
361 	if (obj->Type != ACPI_TYPE_PACKAGE) {
362 		rv = AE_TYPE;
363 		goto out;
364 	}
365 
366 	elm = obj->Package.Elements;
367 
368 	if (elm[0].Type != ACPI_TYPE_INTEGER) {
369 		rv = AE_TYPE;
370 		goto out;
371 	}
372 
373 	if (elm[0].Integer.Value > UINT32_MAX) {
374 		rv = AE_AML_NUMERIC_OVERFLOW;
375 		goto out;
376 	}
377 
378 	n = elm[0].Integer.Value;
379 
380 	if (n == 0) {
381 		rv = AE_NOT_EXIST;
382 		goto out;
383 	}
384 
385 	if (obj->Package.Count - 1 != n) {
386 		rv = AE_BAD_VALUE;
387 		goto out;
388 	}
389 
390 	for (i = 1; i < obj->Package.Count; i++) {
391 
392 		if (elm[i].Type != ACPI_TYPE_PACKAGE)
393 			continue;
394 
395 		aibs_sensor_add(self, &elm[i]);
396 	}
397 
398 out:
399 	if (buf.Pointer != NULL)
400 		ACPI_FREE(buf.Pointer);
401 
402 	if (ACPI_FAILURE(rv)) {
403 
404 		aprint_error_dev(self, "failed to evaluate "
405 		    "%s: %s\n", path, AcpiFormatException(rv));
406 	}
407 }
408 
409 static void
410 aibs_sensor_add(device_t self, ACPI_OBJECT *obj)
411 {
412 	struct aibs_softc *sc = device_private(self);
413 	struct aibs_sensor *as;
414 	int ena, len, lhi, llo;
415 	const char *name;
416 	ACPI_STATUS rv;
417 
418 	as = NULL;
419 	rv = AE_OK;
420 
421 	if (obj->Type != ACPI_TYPE_PACKAGE) {
422 		rv = AE_TYPE;
423 		goto out;
424 	}
425 
426 	/*
427 	 * The known formats are:
428 	 *
429 	 *	index		type		old		new
430 	 *	-----		----		---		---
431 	 *	0		integer		flags		flags
432 	 *	1		string		name		name
433 	 *	2		integer		limit1		unknown
434 	 *	3		integer		limit2		unknown
435 	 *	4		integer		enable		limit1
436 	 *	5		integer		-		limit2
437 	 *	6		integer		-		enable
438 	 */
439 	if (sc->sc_model != false) {
440 		len = 7;
441 		llo = 4;
442 		lhi = 5;
443 		ena = 6;
444 	} else {
445 		len = 5;
446 		llo = 2;
447 		lhi = 3;
448 		ena = 4;
449 	}
450 
451 	if (obj->Package.Count != (uint32_t)len) {
452 		rv = AE_LIMIT;
453 		goto out;
454 	}
455 
456 	if (obj->Package.Elements[0].Type != ACPI_TYPE_INTEGER ||
457 	    obj->Package.Elements[1].Type != ACPI_TYPE_STRING ||
458 	    obj->Package.Elements[llo].Type != ACPI_TYPE_INTEGER ||
459 	    obj->Package.Elements[lhi].Type != ACPI_TYPE_INTEGER ||
460 	    obj->Package.Elements[ena].Type != ACPI_TYPE_INTEGER) {
461 		rv = AE_TYPE;
462 		goto out;
463 	}
464 
465 	as = kmem_zalloc(sizeof(*as), KM_SLEEP);
466 
467 	if (as == NULL) {
468 		rv = AE_NO_MEMORY;
469 		goto out;
470 	}
471 
472 	name = obj->Package.Elements[1].String.Pointer;
473 
474 	as->as_type = obj->Package.Elements[0].Integer.Value;
475 	as->as_liml = obj->Package.Elements[llo].Integer.Value;
476 	as->as_limh = obj->Package.Elements[lhi].Integer.Value;
477 
478 	if (sc->sc_model != false)
479 		as->as_limh += as->as_liml;	/* A range in the new model. */
480 
481 	as->as_sensor.state = ENVSYS_SINVALID;
482 
483 	switch (AIBS_TYPE(as->as_type)) {
484 
485 	case AIBS_TYPE_FAN:
486 		as->as_sensor.units = ENVSYS_SFANRPM;
487 		as->as_sensor.flags = ENVSYS_FMONLIMITS | ENVSYS_FHAS_ENTROPY;
488 		break;
489 
490 	case AIBS_TYPE_TEMP:
491 		as->as_sensor.units = ENVSYS_STEMP;
492 		as->as_sensor.flags = ENVSYS_FMONLIMITS | ENVSYS_FHAS_ENTROPY;
493 		break;
494 
495 	case AIBS_TYPE_VOLT:
496 		as->as_sensor.units = ENVSYS_SVOLTS_DC;
497 		as->as_sensor.flags = ENVSYS_FMONLIMITS | ENVSYS_FHAS_ENTROPY;
498 		break;
499 
500 	default:
501 		rv = AE_TYPE;
502 		goto out;
503 	}
504 
505 	(void)strlcpy(as->as_sensor.desc, name, sizeof(as->as_sensor.desc));
506 
507 	if (sysmon_envsys_sensor_attach(sc->sc_sme, &as->as_sensor) != 0) {
508 		rv = AE_AML_INTERNAL;
509 		goto out;
510 	}
511 
512 	SIMPLEQ_INSERT_TAIL(&sc->as_head, as, as_list);
513 
514 out:
515 	if (ACPI_FAILURE(rv)) {
516 
517 		if (as != NULL)
518 			kmem_free(as, sizeof(*as));
519 
520 		aprint_error_dev(self, "failed to add "
521 		    "sensor: %s\n",  AcpiFormatException(rv));
522 	}
523 }
524 
525 static bool
526 aibs_sensor_value(device_t self, struct aibs_sensor *as, uint64_t *val)
527 {
528 	struct aibs_softc *sc = device_private(self);
529 	uint32_t type, *ret, cmb[3];
530 	ACPI_OBJECT_LIST arg;
531 	ACPI_OBJECT cmi, tmp;
532 	ACPI_OBJECT *obj;
533 	ACPI_BUFFER buf;
534 	ACPI_STATUS rv;
535 	const char *path;
536 
537 	if (sc->sc_model != false) {
538 
539 		path = "GITM";
540 
541 		cmb[0] = as->as_type;
542 		cmb[1] = 0;
543 		cmb[2] = 0;
544 
545 		arg.Count = 1;
546 		arg.Pointer = &tmp;
547 
548 		tmp.Buffer.Length = sizeof(cmb);
549 		tmp.Buffer.Pointer = (uint8_t *)cmb;
550 		tmp.Type = type = ACPI_TYPE_BUFFER;
551 
552 	} else {
553 
554 		arg.Count = 1;
555 		arg.Pointer = &cmi;
556 
557 		cmi.Integer.Value = as->as_type;
558 		cmi.Type = type = ACPI_TYPE_INTEGER;
559 
560 		switch (AIBS_TYPE(as->as_type)) {
561 
562 		case AIBS_TYPE_FAN:
563 			path = "RFAN";
564 			break;
565 
566 		case AIBS_TYPE_TEMP:
567 			path = "RTMP";
568 			break;
569 
570 		case AIBS_TYPE_VOLT:
571 			path = "RVLT";
572 			break;
573 
574 		default:
575 			return false;
576 		}
577 	}
578 
579 	buf.Pointer = NULL;
580 	buf.Length = ACPI_ALLOCATE_LOCAL_BUFFER;
581 
582 	rv = AcpiEvaluateObject(sc->sc_node->ad_handle, path, &arg, &buf);
583 
584 	if (ACPI_FAILURE(rv))
585 		goto out;
586 
587 	obj = buf.Pointer;
588 
589 	if (obj->Type != type) {
590 		rv = AE_TYPE;
591 		goto out;
592 	}
593 
594 	if (sc->sc_model != true)
595 		*val = obj->Integer.Value;
596 	else {
597 		/*
598 		 * The return buffer contains at least:
599 		 *
600 		 *	uint32_t buf[0]	 flags
601 		 *	uint32_t buf[1]	 return value
602 		 *	uint8_t  buf[2-] unknown
603 		 */
604 		if (obj->Buffer.Length < 8) {
605 			rv = AE_BUFFER_OVERFLOW;
606 			goto out;
607 		}
608 
609 		ret = (uint32_t *)obj->Buffer.Pointer;
610 
611 		if (ret[0] == 0) {
612 			rv = AE_BAD_VALUE;
613 			goto out;
614 		}
615 
616 		*val = ret[1];
617 	}
618 
619 out:
620 	if (buf.Pointer != NULL)
621 		ACPI_FREE(buf.Pointer);
622 
623 	if (ACPI_FAILURE(rv)) {
624 
625 		aprint_error_dev(self, "failed to evaluate "
626 		    "%s: %s\n", path, AcpiFormatException(rv));
627 
628 		return false;
629 	}
630 
631 	return true;
632 }
633 
634 static void
635 aibs_sensor_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
636 {
637 	struct aibs_softc *sc = sme->sme_cookie;
638 	struct aibs_sensor *tmp, *as = NULL;
639 	envsys_data_t *s = edata;
640 	uint64_t val = 0;
641 
642 	SIMPLEQ_FOREACH(tmp, &sc->as_head, as_list) {
643 
644 		if (tmp->as_sensor.sensor == s->sensor) {
645 			as = tmp;
646 			break;
647 		}
648 	}
649 
650 	if (as == NULL) {
651 		aprint_debug_dev(sc->sc_dev, "failed to find sensor\n");
652 		return;
653 	}
654 
655 	as->as_sensor.state = ENVSYS_SINVALID;
656 	as->as_sensor.flags |= ENVSYS_FMONNOTSUPP;
657 
658 	if (aibs_sensor_value(sc->sc_dev, as, &val) != true)
659 		return;
660 
661 	switch (as->as_sensor.units) {
662 
663 	case ENVSYS_SFANRPM:
664 		as->as_sensor.value_cur = val;
665 		break;
666 
667 	case ENVSYS_STEMP:
668 
669 		if (val == 0)
670 			return;
671 
672 		as->as_sensor.value_cur = val * 100 * 1000 + 273150000;
673 		break;
674 
675 	case ENVSYS_SVOLTS_DC:
676 		as->as_sensor.value_cur = val * 1000;
677 		break;
678 
679 	default:
680 		return;
681 	}
682 
683 	as->as_sensor.state = ENVSYS_SVALID;
684 	as->as_sensor.flags &= ~ENVSYS_FMONNOTSUPP;
685 }
686 
687 static void
688 aibs_sensor_limits(struct sysmon_envsys *sme, envsys_data_t *edata,
689     sysmon_envsys_lim_t *limits, uint32_t *props)
690 {
691 	struct aibs_softc *sc = sme->sme_cookie;
692 	struct aibs_sensor *tmp, *as = NULL;
693 	sysmon_envsys_lim_t *lim = limits;
694 	envsys_data_t *s = edata;
695 
696 	SIMPLEQ_FOREACH(tmp, &sc->as_head, as_list) {
697 
698 		if (tmp->as_sensor.sensor == s->sensor) {
699 			as = tmp;
700 			break;
701 		}
702 	}
703 
704 	if (as == NULL) {
705 		aprint_debug_dev(sc->sc_dev, "failed to find sensor\n");
706 		return;
707 	}
708 
709 	switch (as->as_sensor.units) {
710 
711 	case ENVSYS_SFANRPM:
712 
713 		/*
714 		 * Some boards have strange limits for fans.
715 		 */
716 		if (as->as_liml == 0) {
717 			lim->sel_warnmin = as->as_limh;
718 			*props = PROP_WARNMIN;
719 
720 		} else {
721 			lim->sel_warnmin = as->as_liml;
722 			lim->sel_warnmax = as->as_limh;
723 			*props = PROP_WARNMIN | PROP_WARNMAX;
724 		}
725 
726 		break;
727 
728 	case ENVSYS_STEMP:
729 		lim->sel_critmax = as->as_limh * 100 * 1000 + 273150000;
730 		lim->sel_warnmax = as->as_liml * 100 * 1000 + 273150000;
731 
732 		*props = PROP_CRITMAX | PROP_WARNMAX;
733 		break;
734 
735 	case ENVSYS_SVOLTS_DC:
736 		lim->sel_critmin = as->as_liml * 1000;
737 		lim->sel_critmax = as->as_limh * 1000;
738 		*props = PROP_CRITMIN | PROP_CRITMAX;
739 		break;
740 
741 	default:
742 		return;
743 	}
744 }
745 
746 MODULE(MODULE_CLASS_DRIVER, aibs, NULL);
747 
748 #ifdef _MODULE
749 #include "ioconf.c"
750 #endif
751 
752 static int
753 aibs_modcmd(modcmd_t cmd, void *aux)
754 {
755 	int rv = 0;
756 
757 	switch (cmd) {
758 
759 	case MODULE_CMD_INIT:
760 
761 #ifdef _MODULE
762 		rv = config_init_component(cfdriver_ioconf_aibs,
763 		    cfattach_ioconf_aibs, cfdata_ioconf_aibs);
764 #endif
765 		break;
766 
767 	case MODULE_CMD_FINI:
768 
769 #ifdef _MODULE
770 		rv = config_fini_component(cfdriver_ioconf_aibs,
771 		    cfattach_ioconf_aibs, cfdata_ioconf_aibs);
772 #endif
773 		break;
774 
775 	default:
776 		rv = ENOTTY;
777 	}
778 
779 	return rv;
780 }
781