xref: /freebsd/sys/powerpc/powermac/smu.c (revision 4ed925457ab06e83238a5db33e89ccc94b99a713)
1 /*-
2  * Copyright (c) 2009 Nathan Whitehorn
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
19  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
21  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  */
27 
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
30 
31 #include <sys/param.h>
32 #include <sys/bus.h>
33 #include <sys/systm.h>
34 #include <sys/module.h>
35 #include <sys/conf.h>
36 #include <sys/cpu.h>
37 #include <sys/ctype.h>
38 #include <sys/kernel.h>
39 #include <sys/rman.h>
40 #include <sys/sysctl.h>
41 
42 #include <machine/bus.h>
43 #include <machine/md_var.h>
44 
45 #include <dev/ofw/openfirm.h>
46 #include <dev/ofw/ofw_bus.h>
47 #include <powerpc/powermac/macgpiovar.h>
48 
49 struct smu_cmd {
50 	volatile uint8_t cmd;
51 	uint8_t		len;
52 	uint8_t		data[254];
53 };
54 
55 struct smu_fan {
56 	cell_t	reg;
57 	cell_t	min_rpm;
58 	cell_t	max_rpm;
59 	cell_t	unmanaged_rpm;
60 	char	location[32];
61 };
62 
63 struct smu_sensor {
64 	cell_t	reg;
65 	char	location[32];
66 	enum {
67 		SMU_CURRENT_SENSOR,
68 		SMU_VOLTAGE_SENSOR,
69 		SMU_POWER_SENSOR,
70 		SMU_TEMP_SENSOR
71 	} type;
72 };
73 
74 struct smu_softc {
75 	device_t	sc_dev;
76 	struct mtx	sc_mtx;
77 
78 	struct resource	*sc_memr;
79 	int		sc_memrid;
80 
81 	bus_dma_tag_t	sc_dmatag;
82 	bus_space_tag_t	sc_bt;
83 	bus_space_handle_t sc_mailbox;
84 
85 	struct smu_cmd	*sc_cmd;
86 	bus_addr_t	sc_cmd_phys;
87 	bus_dmamap_t	sc_cmd_dmamap;
88 
89 	struct smu_fan	*sc_fans;
90 	int		sc_nfans;
91 	struct smu_sensor *sc_sensors;
92 	int		sc_nsensors;
93 
94 	/* Calibration data */
95 	uint16_t	sc_cpu_diode_scale;
96 	int16_t		sc_cpu_diode_offset;
97 
98 	uint16_t	sc_cpu_volt_scale;
99 	int16_t		sc_cpu_volt_offset;
100 	uint16_t	sc_cpu_curr_scale;
101 	int16_t		sc_cpu_curr_offset;
102 
103 	uint16_t	sc_slots_pow_scale;
104 	int16_t		sc_slots_pow_offset;
105 };
106 
107 /* regular bus attachment functions */
108 
109 static int	smu_probe(device_t);
110 static int	smu_attach(device_t);
111 
112 /* cpufreq notification hooks */
113 
114 static void	smu_cpufreq_pre_change(device_t, const struct cf_level *level);
115 static void	smu_cpufreq_post_change(device_t, const struct cf_level *level);
116 
117 /* utility functions */
118 static int	smu_get_datablock(device_t dev, int8_t id, uint8_t *buf,
119 		    size_t len);
120 static void	smu_attach_fans(device_t dev, phandle_t fanroot);
121 static void	smu_attach_sensors(device_t dev, phandle_t sensroot);
122 
123 /* where to find the doorbell GPIO */
124 
125 static device_t	smu_doorbell = NULL;
126 
127 static device_method_t  smu_methods[] = {
128 	/* Device interface */
129 	DEVMETHOD(device_probe,		smu_probe),
130 	DEVMETHOD(device_attach,	smu_attach),
131 	{ 0, 0 },
132 };
133 
134 static driver_t smu_driver = {
135 	"smu",
136 	smu_methods,
137 	sizeof(struct smu_softc)
138 };
139 
140 static devclass_t smu_devclass;
141 
142 DRIVER_MODULE(smu, nexus, smu_driver, smu_devclass, 0, 0);
143 MALLOC_DEFINE(M_SMU, "smu", "SMU Sensor Information");
144 
145 #define SMU_MAILBOX		0x8000860c
146 
147 /* Command types */
148 #define SMU_ADC			0xd8
149 #define SMU_FAN			0x4a
150 #define SMU_I2C			0x9a
151 #define SMU_I2C_SIMPLE		0x00
152 #define SMU_I2C_NORMAL		0x01
153 #define SMU_I2C_COMBINED	0x02
154 #define SMU_MISC		0xee
155 #define SMU_MISC_GET_DATA	0x02
156 #define SMU_POWER		0xaa
157 
158 /* Data blocks */
159 #define SMU_CPUTEMP_CAL		0x18
160 #define SMU_CPUVOLT_CAL		0x21
161 #define SMU_SLOTPW_CAL		0x78
162 
163 /* Partitions */
164 #define SMU_PARTITION		0x3e
165 #define SMU_PARTITION_LATEST	0x01
166 #define SMU_PARTITION_BASE	0x02
167 #define SMU_PARTITION_UPDATE	0x03
168 
169 static int
170 smu_probe(device_t dev)
171 {
172 	const char *name = ofw_bus_get_name(dev);
173 
174 	if (strcmp(name, "smu") != 0)
175 		return (ENXIO);
176 
177 	device_set_desc(dev, "Apple System Management Unit");
178 	return (0);
179 }
180 
181 static void
182 smu_phys_callback(void *xsc, bus_dma_segment_t *segs, int nsegs, int error)
183 {
184 	struct smu_softc *sc = xsc;
185 
186 	sc->sc_cmd_phys = segs[0].ds_addr;
187 }
188 
189 static int
190 smu_attach(device_t dev)
191 {
192 	struct smu_softc *sc;
193 	phandle_t	node, child;
194 	uint8_t		data[12];
195 
196 	sc = device_get_softc(dev);
197 
198 	mtx_init(&sc->sc_mtx, "smu", NULL, MTX_DEF);
199 
200 	/*
201 	 * Map the mailbox area. This should be determined from firmware,
202 	 * but I have not found a simple way to do that.
203 	 */
204 	bus_dma_tag_create(NULL, 16, 0, BUS_SPACE_MAXADDR_32BIT,
205 	    BUS_SPACE_MAXADDR, NULL, NULL, PAGE_SIZE, 1, PAGE_SIZE, 0, NULL,
206 	    NULL, &(sc->sc_dmatag));
207 	sc->sc_bt = &bs_le_tag;
208 	bus_space_map(sc->sc_bt, SMU_MAILBOX, 4, 0, &sc->sc_mailbox);
209 
210 	/*
211 	 * Allocate the command buffer. This can be anywhere in the low 4 GB
212 	 * of memory.
213 	 */
214 	bus_dmamem_alloc(sc->sc_dmatag, (void **)&sc->sc_cmd, BUS_DMA_WAITOK |
215 	    BUS_DMA_ZERO, &sc->sc_cmd_dmamap);
216 	bus_dmamap_load(sc->sc_dmatag, sc->sc_cmd_dmamap,
217 	    sc->sc_cmd, PAGE_SIZE, smu_phys_callback, sc, 0);
218 
219 	/*
220 	 * Set up handlers to change CPU voltage when CPU frequency is changed.
221 	 */
222 	EVENTHANDLER_REGISTER(cpufreq_pre_change, smu_cpufreq_pre_change, dev,
223 	    EVENTHANDLER_PRI_ANY);
224 	EVENTHANDLER_REGISTER(cpufreq_post_change, smu_cpufreq_post_change, dev,
225 	    EVENTHANDLER_PRI_ANY);
226 
227 	/*
228 	 * Detect and attach child devices.
229 	 */
230 	node = ofw_bus_get_node(dev);
231 	for (child = OF_child(node); child != 0; child = OF_peer(child)) {
232 		char name[32];
233 		memset(name, 0, sizeof(name));
234 		OF_getprop(child, "name", name, sizeof(name));
235 
236 		if (strncmp(name, "rpm-fans", 9) == 0 ||
237 		    strncmp(name, "fans", 5) == 0)
238 			smu_attach_fans(dev, child);
239 
240 		if (strncmp(name, "sensors", 8) == 0)
241 			smu_attach_sensors(dev, child);
242 	}
243 
244 	/*
245 	 * Collect calibration constants.
246 	 */
247 	smu_get_datablock(dev, SMU_CPUTEMP_CAL, data, sizeof(data));
248 	sc->sc_cpu_diode_scale = (data[4] << 8) + data[5];
249 	sc->sc_cpu_diode_offset = (data[6] << 8) + data[7];
250 
251 	smu_get_datablock(dev, SMU_CPUVOLT_CAL, data, sizeof(data));
252 	sc->sc_cpu_volt_scale = (data[4] << 8) + data[5];
253 	sc->sc_cpu_volt_offset = (data[6] << 8) + data[7];
254 	sc->sc_cpu_curr_scale = (data[8] << 8) + data[9];
255 	sc->sc_cpu_curr_offset = (data[10] << 8) + data[11];
256 
257 	smu_get_datablock(dev, SMU_SLOTPW_CAL, data, sizeof(data));
258 	sc->sc_slots_pow_scale = (data[4] << 8) + data[5];
259 	sc->sc_slots_pow_offset = (data[6] << 8) + data[7];
260 
261 	return (0);
262 }
263 
264 static int
265 smu_run_cmd(device_t dev, struct smu_cmd *cmd)
266 {
267 	struct smu_softc *sc;
268 	int doorbell_ack, result, oldpow;
269 
270 	sc = device_get_softc(dev);
271 
272 	mtx_lock(&sc->sc_mtx);
273 
274 	oldpow = powerpc_pow_enabled;
275 	powerpc_pow_enabled = 0;
276 
277 	/* Copy the command to the mailbox */
278 	memcpy(sc->sc_cmd, cmd, sizeof(*cmd));
279 	bus_dmamap_sync(sc->sc_dmatag, sc->sc_cmd_dmamap, BUS_DMASYNC_PREWRITE);
280 	bus_space_write_4(sc->sc_bt, sc->sc_mailbox, 0, sc->sc_cmd_phys);
281 
282 	/* Flush the cacheline it is in -- SMU bypasses the cache */
283 	__asm __volatile("sync; dcbf 0,%0; sync" :: "r"(sc->sc_cmd): "memory");
284 
285 	/* Ring SMU doorbell */
286 	macgpio_write(smu_doorbell, GPIO_DDR_OUTPUT);
287 
288 	/* Wait for the doorbell GPIO to go high, signaling completion */
289 	do {
290 		/* XXX: timeout */
291 		DELAY(50);
292 		doorbell_ack = macgpio_read(smu_doorbell);
293 	} while (doorbell_ack != (GPIO_DDR_OUTPUT | GPIO_LEVEL_RO | GPIO_DATA));
294 
295 	/* Check result. First invalidate the cache again... */
296 	__asm __volatile("dcbf 0,%0; sync" :: "r"(sc->sc_cmd) : "memory");
297 
298 	bus_dmamap_sync(sc->sc_dmatag, sc->sc_cmd_dmamap, BUS_DMASYNC_POSTREAD);
299 
300 	/* SMU acks the command by inverting the command bits */
301 	if (sc->sc_cmd->cmd == ((~cmd->cmd) & 0xff))
302 		result = 0;
303 	else
304 		result = EIO;
305 
306 	powerpc_pow_enabled = oldpow;
307 
308 	memcpy(cmd->data, sc->sc_cmd->data, sizeof(cmd->data));
309 
310 	mtx_unlock(&sc->sc_mtx);
311 
312 	return (result);
313 }
314 
315 static int
316 smu_get_datablock(device_t dev, int8_t id, uint8_t *buf, size_t len)
317 {
318 	struct smu_cmd cmd;
319 	uint8_t addr[4];
320 
321 	cmd.cmd = SMU_PARTITION;
322 	cmd.len = 2;
323 	cmd.data[0] = SMU_PARTITION_LATEST;
324 	cmd.data[1] = id;
325 
326 	smu_run_cmd(dev, &cmd);
327 
328 	addr[0] = addr[1] = 0;
329 	addr[2] = cmd.data[0];
330 	addr[3] = cmd.data[1];
331 
332 	cmd.cmd = SMU_MISC;
333 	cmd.len = 7;
334 	cmd.data[0] = SMU_MISC_GET_DATA;
335 	cmd.data[1] = sizeof(addr);
336 	memcpy(&cmd.data[2], addr, sizeof(addr));
337 	cmd.data[6] = len;
338 
339 	smu_run_cmd(dev, &cmd);
340 	memcpy(buf, cmd.data, len);
341 	return (0);
342 }
343 
344 static void
345 smu_slew_cpu_voltage(device_t dev, int to)
346 {
347 	struct smu_cmd cmd;
348 
349 	cmd.cmd = SMU_POWER;
350 	cmd.len = 8;
351 	cmd.data[0] = 'V';
352 	cmd.data[1] = 'S';
353 	cmd.data[2] = 'L';
354 	cmd.data[3] = 'E';
355 	cmd.data[4] = 'W';
356 	cmd.data[5] = 0xff;
357 	cmd.data[6] = 1;
358 	cmd.data[7] = to;
359 
360 	smu_run_cmd(dev, &cmd);
361 }
362 
363 static void
364 smu_cpufreq_pre_change(device_t dev, const struct cf_level *level)
365 {
366 	/*
367 	 * Make sure the CPU voltage is raised before we raise
368 	 * the clock.
369 	 */
370 
371 	if (level->rel_set[0].freq == 10000 /* max */)
372 		smu_slew_cpu_voltage(dev, 0);
373 }
374 
375 static void
376 smu_cpufreq_post_change(device_t dev, const struct cf_level *level)
377 {
378 	/* We are safe to reduce CPU voltage after a downward transition */
379 
380 	if (level->rel_set[0].freq < 10000 /* max */)
381 		smu_slew_cpu_voltage(dev, 1); /* XXX: 1/4 voltage for 970MP? */
382 }
383 
384 /* Routines for probing the SMU doorbell GPIO */
385 static int doorbell_probe(device_t dev);
386 static int doorbell_attach(device_t dev);
387 
388 static device_method_t  doorbell_methods[] = {
389 	/* Device interface */
390 	DEVMETHOD(device_probe,		doorbell_probe),
391 	DEVMETHOD(device_attach,	doorbell_attach),
392 	{ 0, 0 },
393 };
394 
395 static driver_t doorbell_driver = {
396 	"smudoorbell",
397 	doorbell_methods,
398 	0
399 };
400 
401 static devclass_t doorbell_devclass;
402 
403 DRIVER_MODULE(smudoorbell, macgpio, doorbell_driver, doorbell_devclass, 0, 0);
404 
405 static int
406 doorbell_probe(device_t dev)
407 {
408 	const char *name = ofw_bus_get_name(dev);
409 
410 	if (strcmp(name, "smu-doorbell") != 0)
411 		return (ENXIO);
412 
413 	device_set_desc(dev, "SMU Doorbell GPIO");
414 	device_quiet(dev);
415 	return (0);
416 }
417 
418 static int
419 doorbell_attach(device_t dev)
420 {
421 	smu_doorbell = dev;
422 	return (0);
423 }
424 
425 /*
426  * Sensor and fan management
427  */
428 
429 static int
430 smu_fan_set_rpm(device_t smu, struct smu_fan *fan, int rpm)
431 {
432 	struct smu_cmd cmd;
433 
434 	cmd.cmd = SMU_FAN;
435 	cmd.len = 14;
436 	cmd.data[0] = 0;
437 	cmd.data[1] = 1 << fan->reg;
438 
439 	/*
440 	 * There are two locations used for the fan speed.
441 	 * Store it in both.
442 	 */
443 
444 	cmd.data[2] = cmd.data[2 + 2*fan->reg] = (rpm >> 8) & 0xff;
445 	cmd.data[3] = cmd.data[3 + 2*fan->reg] = rpm & 0xff;
446 
447 	return (smu_run_cmd(smu, &cmd));
448 }
449 
450 static int
451 smu_fan_read_rpm(device_t smu, struct smu_fan *fan)
452 {
453 	struct smu_cmd cmd;
454 
455 	cmd.cmd = SMU_FAN;
456 	cmd.len = 2;
457 	cmd.data[0] = 1;
458 	cmd.data[1] = 1 << fan->reg;
459 
460 	smu_run_cmd(smu, &cmd);
461 
462 	return ((cmd.data[1] << 8) | cmd.data[2]);
463 }
464 
465 static int
466 smu_fanrpm_sysctl(SYSCTL_HANDLER_ARGS)
467 {
468 	device_t smu;
469 	struct smu_softc *sc;
470 	struct smu_fan *fan;
471 	int rpm, error;
472 
473 	smu = arg1;
474 	sc = device_get_softc(smu);
475 	fan = &sc->sc_fans[arg2];
476 
477 	rpm = smu_fan_read_rpm(smu, fan);
478 	error = sysctl_handle_int(oidp, &rpm, 0, req);
479 
480 	if (error || !req->newptr)
481 		return (error);
482 
483 
484 	return (smu_fan_set_rpm(smu, fan, rpm));
485 }
486 
487 static void
488 smu_attach_fans(device_t dev, phandle_t fanroot)
489 {
490 	struct smu_fan *fan;
491 	struct smu_softc *sc;
492 	struct sysctl_oid *oid, *fanroot_oid;
493 	struct sysctl_ctx_list *ctx;
494 	phandle_t child;
495 	char type[32], sysctl_name[32];
496 	int i;
497 
498 	sc = device_get_softc(dev);
499 	sc->sc_nfans = 0;
500 
501 	for (child = OF_child(fanroot); child != 0; child = OF_peer(child))
502 		sc->sc_nfans++;
503 
504 	if (sc->sc_nfans == 0) {
505 		device_printf(dev, "WARNING: No fans detected!\n");
506 		return;
507 	}
508 
509 	sc->sc_fans = malloc(sc->sc_nfans * sizeof(struct smu_fan), M_SMU,
510 	    M_WAITOK | M_ZERO);
511 
512 	fan = sc->sc_fans;
513 	sc->sc_nfans = 0;
514 
515 	ctx = device_get_sysctl_ctx(dev);
516 	fanroot_oid = SYSCTL_ADD_NODE(ctx,
517 	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "fans",
518 	    CTLFLAG_RD, 0, "SMU Fan Information");
519 
520 	for (child = OF_child(fanroot); child != 0; child = OF_peer(child)) {
521 		OF_getprop(child, "device_type", type, sizeof(type));
522 		if (strcmp(type, "fan-rpm-control") != 0)
523 			continue;
524 
525 		OF_getprop(child, "reg", &fan->reg, sizeof(cell_t));
526 		OF_getprop(child, "min-value", &fan->min_rpm, sizeof(cell_t));
527 		OF_getprop(child, "max-value", &fan->max_rpm, sizeof(cell_t));
528 		OF_getprop(child, "unmanaged-value", &fan->unmanaged_rpm,
529 		    sizeof(cell_t));
530 		OF_getprop(child, "location", fan->location,
531 		    sizeof(fan->location));
532 
533 		/* Make sure it is at a safe value initially */
534 		//smu_fan_set_rpm(dev, fan, fan->unmanaged_rpm);
535 
536 		/* Add sysctls */
537 		for (i = 0; i < strlen(fan->location); i++) {
538 			sysctl_name[i] = tolower(fan->location[i]);
539 			if (isspace(sysctl_name[i]))
540 				sysctl_name[i] = '_';
541 		}
542 		sysctl_name[i] = 0;
543 
544 		oid = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(fanroot_oid),
545 		    OID_AUTO, sysctl_name, CTLFLAG_RD, 0, "Fan Information");
546 		SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "minrpm",
547 		    CTLTYPE_INT | CTLFLAG_RD, &fan->min_rpm, sizeof(cell_t),
548 		    "Minimum allowed RPM");
549 		SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "maxrpm",
550 		    CTLTYPE_INT | CTLFLAG_RD, &fan->max_rpm, sizeof(cell_t),
551 		    "Maximum allowed RPM");
552 		SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "rpm",
553 		    CTLTYPE_INT | CTLFLAG_RW, dev, sc->sc_nfans,
554 		    smu_fanrpm_sysctl, "I", "Fan RPM");
555 
556 		fan++;
557 		sc->sc_nfans++;
558 	}
559 }
560 
561 static int
562 smu_sensor_read(device_t smu, struct smu_sensor *sens)
563 {
564 	struct smu_cmd cmd;
565 	struct smu_softc *sc;
566 	int64_t value;
567 
568 	cmd.cmd = SMU_ADC;
569 	cmd.len = 1;
570 	cmd.data[0] = sens->reg;
571 
572 	smu_run_cmd(smu, &cmd);
573 
574 	sc = device_get_softc(smu);
575 	value = (cmd.data[0] << 8) | cmd.data[1];
576 
577 	switch (sens->type) {
578 	case SMU_TEMP_SENSOR:
579 		value *= sc->sc_cpu_diode_scale;
580 		value >>= 3;
581 		value += ((int64_t)sc->sc_cpu_diode_offset) << 9;
582 		value <<= 1;
583 
584 		/* Convert from 16.16 fixed point degC into integer C. */
585 		value *= 15625;
586 		value /= 1024;
587 		value /= 1000000;
588 		break;
589 	case SMU_VOLTAGE_SENSOR:
590 		value *= sc->sc_cpu_volt_scale;
591 		value += sc->sc_cpu_volt_offset;
592 		value <<= 4;
593 
594 		/* Convert from 16.16 fixed point V into mV. */
595 		value *= 15625;
596 		value /= 1024;
597 		value /= 1000;
598 		break;
599 	case SMU_CURRENT_SENSOR:
600 		value *= sc->sc_cpu_curr_scale;
601 		value += sc->sc_cpu_curr_offset;
602 		value <<= 4;
603 
604 		/* Convert from 16.16 fixed point A into mA. */
605 		value *= 15625;
606 		value /= 1024;
607 		value /= 1000;
608 		break;
609 	case SMU_POWER_SENSOR:
610 		value *= sc->sc_slots_pow_scale;
611 		value += sc->sc_slots_pow_offset;
612 		value <<= 4;
613 
614 		/* Convert from 16.16 fixed point W into mW. */
615 		value *= 15625;
616 		value /= 1024;
617 		value /= 1000;
618 		break;
619 	}
620 
621 	return (value);
622 }
623 
624 static int
625 smu_sensor_sysctl(SYSCTL_HANDLER_ARGS)
626 {
627 	device_t smu;
628 	struct smu_softc *sc;
629 	struct smu_sensor *sens;
630 	int value, error;
631 
632 	smu = arg1;
633 	sc = device_get_softc(smu);
634 	sens = &sc->sc_sensors[arg2];
635 
636 	value = smu_sensor_read(smu, sens);
637 	error = sysctl_handle_int(oidp, &value, 0, req);
638 
639 	return (error);
640 }
641 
642 static void
643 smu_attach_sensors(device_t dev, phandle_t sensroot)
644 {
645 	struct smu_sensor *sens;
646 	struct smu_softc *sc;
647 	struct sysctl_oid *sensroot_oid;
648 	struct sysctl_ctx_list *ctx;
649 	phandle_t child;
650 	char type[32];
651 	int i;
652 
653 	sc = device_get_softc(dev);
654 	sc->sc_nsensors = 0;
655 
656 	for (child = OF_child(sensroot); child != 0; child = OF_peer(child))
657 		sc->sc_nsensors++;
658 
659 	if (sc->sc_nsensors == 0) {
660 		device_printf(dev, "WARNING: No sensors detected!\n");
661 		return;
662 	}
663 
664 	sc->sc_fans = malloc(sc->sc_nsensors * sizeof(struct smu_sensor), M_SMU,
665 	    M_WAITOK | M_ZERO);
666 
667 	sens = sc->sc_sensors;
668 	sc->sc_nsensors = 0;
669 
670 	ctx = device_get_sysctl_ctx(dev);
671 	sensroot_oid = SYSCTL_ADD_NODE(ctx,
672 	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensors",
673 	    CTLFLAG_RD, 0, "SMU Sensor Information");
674 
675 	for (child = OF_child(sensroot); child != 0; child = OF_peer(child)) {
676 		char sysctl_name[40], sysctl_desc[40];
677 		const char *units;
678 
679 		OF_getprop(child, "device_type", type, sizeof(type));
680 
681 		if (strcmp(type, "current-sensor") == 0) {
682 			sens->type = SMU_CURRENT_SENSOR;
683 			units = "mA";
684 		} else if (strcmp(type, "temp-sensor") == 0) {
685 			sens->type = SMU_TEMP_SENSOR;
686 			units = "C";
687 		} else if (strcmp(type, "voltage-sensor") == 0) {
688 			sens->type = SMU_VOLTAGE_SENSOR;
689 			units = "mV";
690 		} else if (strcmp(type, "power-sensor") == 0) {
691 			sens->type = SMU_POWER_SENSOR;
692 			units = "mW";
693 		} else {
694 			continue;
695 		}
696 
697 		OF_getprop(child, "reg", &sens->reg, sizeof(cell_t));
698 		OF_getprop(child, "location", sens->location,
699 		    sizeof(sens->location));
700 
701 		for (i = 0; i < strlen(sens->location); i++) {
702 			sysctl_name[i] = tolower(sens->location[i]);
703 			if (isspace(sysctl_name[i]))
704 				sysctl_name[i] = '_';
705 		}
706 		sysctl_name[i] = 0;
707 
708 		sprintf(sysctl_desc,"%s (%s)", sens->location, units);
709 
710 		SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(sensroot_oid), OID_AUTO,
711 		    sysctl_name, CTLTYPE_INT | CTLFLAG_RD, dev, sc->sc_nsensors,
712 		    smu_sensor_sysctl, "I", sysctl_desc);
713 
714 		sens++;
715 		sc->sc_nsensors++;
716 	}
717 }
718 
719