smu.c revision 204270
1194679Snwhitehorn/*-
2194679Snwhitehorn * Copyright (c) 2009 Nathan Whitehorn
3194679Snwhitehorn * All rights reserved.
4194679Snwhitehorn *
5194679Snwhitehorn * Redistribution and use in source and binary forms, with or without
6194679Snwhitehorn * modification, are permitted provided that the following conditions
7194679Snwhitehorn * are met:
8194679Snwhitehorn * 1. Redistributions of source code must retain the above copyright
9194679Snwhitehorn *    notice, this list of conditions and the following disclaimer.
10194679Snwhitehorn * 2. Redistributions in binary form must reproduce the above copyright
11194679Snwhitehorn *    notice, this list of conditions and the following disclaimer in the
12194679Snwhitehorn *    documentation and/or other materials provided with the distribution.
13194679Snwhitehorn *
14194679Snwhitehorn * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15194679Snwhitehorn * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16194679Snwhitehorn * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17194679Snwhitehorn * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18194679Snwhitehorn * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
19194679Snwhitehorn * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20194679Snwhitehorn * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
21194679Snwhitehorn * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22194679Snwhitehorn * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23194679Snwhitehorn * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24194679Snwhitehorn * SUCH DAMAGE.
25194679Snwhitehorn *
26194679Snwhitehorn */
27194679Snwhitehorn
28194679Snwhitehorn#include <sys/cdefs.h>
29194679Snwhitehorn__FBSDID("$FreeBSD: head/sys/powerpc/powermac/smu.c 204270 2010-02-24 01:27:36Z nwhitehorn $");
30194679Snwhitehorn
31194679Snwhitehorn#include <sys/param.h>
32194679Snwhitehorn#include <sys/bus.h>
33194679Snwhitehorn#include <sys/systm.h>
34194679Snwhitehorn#include <sys/module.h>
35204180Snwhitehorn#include <sys/callout.h>
36194679Snwhitehorn#include <sys/conf.h>
37194679Snwhitehorn#include <sys/cpu.h>
38204082Snwhitehorn#include <sys/ctype.h>
39194679Snwhitehorn#include <sys/kernel.h>
40204180Snwhitehorn#include <sys/reboot.h>
41194679Snwhitehorn#include <sys/rman.h>
42194679Snwhitehorn#include <sys/sysctl.h>
43194679Snwhitehorn
44194679Snwhitehorn#include <machine/bus.h>
45194679Snwhitehorn#include <machine/md_var.h>
46194679Snwhitehorn
47204218Snwhitehorn#include <dev/led/led.h>
48194679Snwhitehorn#include <dev/ofw/openfirm.h>
49194679Snwhitehorn#include <dev/ofw/ofw_bus.h>
50194679Snwhitehorn#include <powerpc/powermac/macgpiovar.h>
51194679Snwhitehorn
52194679Snwhitehornstruct smu_cmd {
53204082Snwhitehorn	volatile uint8_t cmd;
54194679Snwhitehorn	uint8_t		len;
55194679Snwhitehorn	uint8_t		data[254];
56194679Snwhitehorn};
57194679Snwhitehorn
58204082Snwhitehornstruct smu_fan {
59204082Snwhitehorn	cell_t	reg;
60204082Snwhitehorn	cell_t	min_rpm;
61204082Snwhitehorn	cell_t	max_rpm;
62204082Snwhitehorn	cell_t	unmanaged_rpm;
63204082Snwhitehorn	char	location[32];
64204180Snwhitehorn
65204179Snwhitehorn	int	old_style;
66204180Snwhitehorn	int	setpoint;
67204082Snwhitehorn};
68204082Snwhitehorn
69204082Snwhitehornstruct smu_sensor {
70204082Snwhitehorn	cell_t	reg;
71204082Snwhitehorn	char	location[32];
72204082Snwhitehorn	enum {
73204082Snwhitehorn		SMU_CURRENT_SENSOR,
74204082Snwhitehorn		SMU_VOLTAGE_SENSOR,
75204082Snwhitehorn		SMU_POWER_SENSOR,
76204082Snwhitehorn		SMU_TEMP_SENSOR
77204082Snwhitehorn	} type;
78204082Snwhitehorn};
79204082Snwhitehorn
80194679Snwhitehornstruct smu_softc {
81194679Snwhitehorn	device_t	sc_dev;
82194679Snwhitehorn	struct mtx	sc_mtx;
83194679Snwhitehorn
84194679Snwhitehorn	struct resource	*sc_memr;
85194679Snwhitehorn	int		sc_memrid;
86194679Snwhitehorn
87194679Snwhitehorn	bus_dma_tag_t	sc_dmatag;
88194679Snwhitehorn	bus_space_tag_t	sc_bt;
89194679Snwhitehorn	bus_space_handle_t sc_mailbox;
90194679Snwhitehorn
91194679Snwhitehorn	struct smu_cmd	*sc_cmd;
92194679Snwhitehorn	bus_addr_t	sc_cmd_phys;
93194679Snwhitehorn	bus_dmamap_t	sc_cmd_dmamap;
94204082Snwhitehorn
95204082Snwhitehorn	struct smu_fan	*sc_fans;
96204082Snwhitehorn	int		sc_nfans;
97204082Snwhitehorn	struct smu_sensor *sc_sensors;
98204082Snwhitehorn	int		sc_nsensors;
99204082Snwhitehorn
100204180Snwhitehorn	struct callout	sc_fanmgt_callout;
101204180Snwhitehorn	time_t		sc_lastuserchange;
102204180Snwhitehorn
103204082Snwhitehorn	/* Calibration data */
104204082Snwhitehorn	uint16_t	sc_cpu_diode_scale;
105204082Snwhitehorn	int16_t		sc_cpu_diode_offset;
106204082Snwhitehorn
107204082Snwhitehorn	uint16_t	sc_cpu_volt_scale;
108204082Snwhitehorn	int16_t		sc_cpu_volt_offset;
109204082Snwhitehorn	uint16_t	sc_cpu_curr_scale;
110204082Snwhitehorn	int16_t		sc_cpu_curr_offset;
111204082Snwhitehorn
112204082Snwhitehorn	uint16_t	sc_slots_pow_scale;
113204082Snwhitehorn	int16_t		sc_slots_pow_offset;
114204180Snwhitehorn
115204180Snwhitehorn	/* Thermal management parameters */
116204180Snwhitehorn	int		sc_target_temp;		/* Default 55 C */
117204180Snwhitehorn	int		sc_critical_temp;	/* Default 90 C */
118204218Snwhitehorn
119204218Snwhitehorn	struct cdev 	*sc_leddev;
120194679Snwhitehorn};
121194679Snwhitehorn
122194679Snwhitehorn/* regular bus attachment functions */
123194679Snwhitehorn
124194679Snwhitehornstatic int	smu_probe(device_t);
125194679Snwhitehornstatic int	smu_attach(device_t);
126194679Snwhitehorn
127194679Snwhitehorn/* cpufreq notification hooks */
128194679Snwhitehorn
129194679Snwhitehornstatic void	smu_cpufreq_pre_change(device_t, const struct cf_level *level);
130194679Snwhitehornstatic void	smu_cpufreq_post_change(device_t, const struct cf_level *level);
131194679Snwhitehorn
132204082Snwhitehorn/* utility functions */
133204179Snwhitehornstatic int	smu_run_cmd(device_t dev, struct smu_cmd *cmd);
134204082Snwhitehornstatic int	smu_get_datablock(device_t dev, int8_t id, uint8_t *buf,
135204082Snwhitehorn		    size_t len);
136204082Snwhitehornstatic void	smu_attach_fans(device_t dev, phandle_t fanroot);
137204082Snwhitehornstatic void	smu_attach_sensors(device_t dev, phandle_t sensroot);
138204180Snwhitehornstatic void	smu_fanmgt_callout(void *xdev);
139204218Snwhitehornstatic void	smu_set_sleepled(void *xdev, int onoff);
140204270Snwhitehornstatic int	smu_server_mode(SYSCTL_HANDLER_ARGS);
141204082Snwhitehorn
142194679Snwhitehorn/* where to find the doorbell GPIO */
143194679Snwhitehorn
144194679Snwhitehornstatic device_t	smu_doorbell = NULL;
145194679Snwhitehorn
146194679Snwhitehornstatic device_method_t  smu_methods[] = {
147194679Snwhitehorn	/* Device interface */
148194679Snwhitehorn	DEVMETHOD(device_probe,		smu_probe),
149194679Snwhitehorn	DEVMETHOD(device_attach,	smu_attach),
150194679Snwhitehorn	{ 0, 0 },
151194679Snwhitehorn};
152194679Snwhitehorn
153194679Snwhitehornstatic driver_t smu_driver = {
154194679Snwhitehorn	"smu",
155194679Snwhitehorn	smu_methods,
156194679Snwhitehorn	sizeof(struct smu_softc)
157194679Snwhitehorn};
158194679Snwhitehorn
159194679Snwhitehornstatic devclass_t smu_devclass;
160194679Snwhitehorn
161194679SnwhitehornDRIVER_MODULE(smu, nexus, smu_driver, smu_devclass, 0, 0);
162204082SnwhitehornMALLOC_DEFINE(M_SMU, "smu", "SMU Sensor Information");
163194679Snwhitehorn
164204082Snwhitehorn#define SMU_MAILBOX		0x8000860c
165204180Snwhitehorn#define SMU_FANMGT_INTERVAL	500 /* ms */
166194679Snwhitehorn
167194679Snwhitehorn/* Command types */
168204082Snwhitehorn#define SMU_ADC			0xd8
169204082Snwhitehorn#define SMU_FAN			0x4a
170204082Snwhitehorn#define SMU_I2C			0x9a
171204179Snwhitehorn#define  SMU_I2C_SIMPLE		0x00
172204179Snwhitehorn#define  SMU_I2C_NORMAL		0x01
173204179Snwhitehorn#define  SMU_I2C_COMBINED	0x02
174204082Snwhitehorn#define SMU_MISC		0xee
175204179Snwhitehorn#define  SMU_MISC_GET_DATA	0x02
176204179Snwhitehorn#define  SMU_MISC_LED_CTRL	0x04
177204082Snwhitehorn#define SMU_POWER		0xaa
178204270Snwhitehorn#define SMU_POWER_EVENTS	0x8f
179204270Snwhitehorn#define  SMU_PWR_GET_POWERUP	0x00
180204270Snwhitehorn#define  SMU_PWR_SET_POWERUP	0x01
181204270Snwhitehorn#define  SMU_PWR_CLR_POWERUP	0x02
182194679Snwhitehorn
183204270Snwhitehorn/* Power event types */
184204270Snwhitehorn#define SMU_WAKEUP_KEYPRESS	0x01
185204270Snwhitehorn#define SMU_WAKEUP_AC_INSERT	0x02
186204270Snwhitehorn#define SMU_WAKEUP_AC_CHANGE	0x04
187204270Snwhitehorn#define SMU_WAKEUP_RING		0x10
188204270Snwhitehorn
189204082Snwhitehorn/* Data blocks */
190204082Snwhitehorn#define SMU_CPUTEMP_CAL		0x18
191204082Snwhitehorn#define SMU_CPUVOLT_CAL		0x21
192204082Snwhitehorn#define SMU_SLOTPW_CAL		0x78
193204082Snwhitehorn
194204082Snwhitehorn/* Partitions */
195204082Snwhitehorn#define SMU_PARTITION		0x3e
196204082Snwhitehorn#define SMU_PARTITION_LATEST	0x01
197204082Snwhitehorn#define SMU_PARTITION_BASE	0x02
198204082Snwhitehorn#define SMU_PARTITION_UPDATE	0x03
199204082Snwhitehorn
200194679Snwhitehornstatic int
201194679Snwhitehornsmu_probe(device_t dev)
202194679Snwhitehorn{
203194679Snwhitehorn	const char *name = ofw_bus_get_name(dev);
204194679Snwhitehorn
205194679Snwhitehorn	if (strcmp(name, "smu") != 0)
206194679Snwhitehorn		return (ENXIO);
207194679Snwhitehorn
208194679Snwhitehorn	device_set_desc(dev, "Apple System Management Unit");
209194679Snwhitehorn	return (0);
210194679Snwhitehorn}
211194679Snwhitehorn
212194679Snwhitehornstatic void
213194679Snwhitehornsmu_phys_callback(void *xsc, bus_dma_segment_t *segs, int nsegs, int error)
214194679Snwhitehorn{
215194679Snwhitehorn	struct smu_softc *sc = xsc;
216194679Snwhitehorn
217194679Snwhitehorn	sc->sc_cmd_phys = segs[0].ds_addr;
218194679Snwhitehorn}
219194679Snwhitehorn
220194679Snwhitehornstatic int
221194679Snwhitehornsmu_attach(device_t dev)
222194679Snwhitehorn{
223194679Snwhitehorn	struct smu_softc *sc;
224204082Snwhitehorn	phandle_t	node, child;
225204082Snwhitehorn	uint8_t		data[12];
226194679Snwhitehorn
227194679Snwhitehorn	sc = device_get_softc(dev);
228194679Snwhitehorn
229194679Snwhitehorn	mtx_init(&sc->sc_mtx, "smu", NULL, MTX_DEF);
230194679Snwhitehorn
231194679Snwhitehorn	/*
232194679Snwhitehorn	 * Map the mailbox area. This should be determined from firmware,
233194679Snwhitehorn	 * but I have not found a simple way to do that.
234194679Snwhitehorn	 */
235194679Snwhitehorn	bus_dma_tag_create(NULL, 16, 0, BUS_SPACE_MAXADDR_32BIT,
236194679Snwhitehorn	    BUS_SPACE_MAXADDR, NULL, NULL, PAGE_SIZE, 1, PAGE_SIZE, 0, NULL,
237194679Snwhitehorn	    NULL, &(sc->sc_dmatag));
238204082Snwhitehorn	sc->sc_bt = &bs_le_tag;
239194679Snwhitehorn	bus_space_map(sc->sc_bt, SMU_MAILBOX, 4, 0, &sc->sc_mailbox);
240194679Snwhitehorn
241194679Snwhitehorn	/*
242194679Snwhitehorn	 * Allocate the command buffer. This can be anywhere in the low 4 GB
243194679Snwhitehorn	 * of memory.
244194679Snwhitehorn	 */
245194679Snwhitehorn	bus_dmamem_alloc(sc->sc_dmatag, (void **)&sc->sc_cmd, BUS_DMA_WAITOK |
246194679Snwhitehorn	    BUS_DMA_ZERO, &sc->sc_cmd_dmamap);
247194679Snwhitehorn	bus_dmamap_load(sc->sc_dmatag, sc->sc_cmd_dmamap,
248194679Snwhitehorn	    sc->sc_cmd, PAGE_SIZE, smu_phys_callback, sc, 0);
249194679Snwhitehorn
250194679Snwhitehorn	/*
251194679Snwhitehorn	 * Set up handlers to change CPU voltage when CPU frequency is changed.
252194679Snwhitehorn	 */
253194679Snwhitehorn	EVENTHANDLER_REGISTER(cpufreq_pre_change, smu_cpufreq_pre_change, dev,
254194679Snwhitehorn	    EVENTHANDLER_PRI_ANY);
255194679Snwhitehorn	EVENTHANDLER_REGISTER(cpufreq_post_change, smu_cpufreq_post_change, dev,
256194679Snwhitehorn	    EVENTHANDLER_PRI_ANY);
257194679Snwhitehorn
258204082Snwhitehorn	/*
259204082Snwhitehorn	 * Detect and attach child devices.
260204082Snwhitehorn	 */
261204082Snwhitehorn	node = ofw_bus_get_node(dev);
262204082Snwhitehorn	for (child = OF_child(node); child != 0; child = OF_peer(child)) {
263204082Snwhitehorn		char name[32];
264204082Snwhitehorn		memset(name, 0, sizeof(name));
265204082Snwhitehorn		OF_getprop(child, "name", name, sizeof(name));
266204082Snwhitehorn
267204082Snwhitehorn		if (strncmp(name, "rpm-fans", 9) == 0 ||
268204082Snwhitehorn		    strncmp(name, "fans", 5) == 0)
269204082Snwhitehorn			smu_attach_fans(dev, child);
270204082Snwhitehorn
271204082Snwhitehorn		if (strncmp(name, "sensors", 8) == 0)
272204082Snwhitehorn			smu_attach_sensors(dev, child);
273204082Snwhitehorn	}
274204082Snwhitehorn
275204082Snwhitehorn	/*
276204082Snwhitehorn	 * Collect calibration constants.
277204082Snwhitehorn	 */
278204082Snwhitehorn	smu_get_datablock(dev, SMU_CPUTEMP_CAL, data, sizeof(data));
279204082Snwhitehorn	sc->sc_cpu_diode_scale = (data[4] << 8) + data[5];
280204082Snwhitehorn	sc->sc_cpu_diode_offset = (data[6] << 8) + data[7];
281204082Snwhitehorn
282204082Snwhitehorn	smu_get_datablock(dev, SMU_CPUVOLT_CAL, data, sizeof(data));
283204082Snwhitehorn	sc->sc_cpu_volt_scale = (data[4] << 8) + data[5];
284204082Snwhitehorn	sc->sc_cpu_volt_offset = (data[6] << 8) + data[7];
285204082Snwhitehorn	sc->sc_cpu_curr_scale = (data[8] << 8) + data[9];
286204082Snwhitehorn	sc->sc_cpu_curr_offset = (data[10] << 8) + data[11];
287204082Snwhitehorn
288204082Snwhitehorn	smu_get_datablock(dev, SMU_SLOTPW_CAL, data, sizeof(data));
289204082Snwhitehorn	sc->sc_slots_pow_scale = (data[4] << 8) + data[5];
290204082Snwhitehorn	sc->sc_slots_pow_offset = (data[6] << 8) + data[7];
291204082Snwhitehorn
292204180Snwhitehorn	/*
293204180Snwhitehorn	 * Set up simple-minded thermal management.
294204180Snwhitehorn	 */
295204180Snwhitehorn	sc->sc_target_temp = 55;
296204180Snwhitehorn	sc->sc_critical_temp = 90;
297204180Snwhitehorn
298204180Snwhitehorn	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
299204180Snwhitehorn	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
300204180Snwhitehorn	    "target_temp", CTLTYPE_INT | CTLFLAG_RW, &sc->sc_target_temp,
301204180Snwhitehorn	    sizeof(int), "Target temperature (C)");
302204180Snwhitehorn	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
303204180Snwhitehorn	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
304204180Snwhitehorn	    "critical_temp", CTLTYPE_INT | CTLFLAG_RW,
305204180Snwhitehorn	    &sc->sc_critical_temp, sizeof(int), "Critical temperature (C)");
306204180Snwhitehorn
307204180Snwhitehorn	callout_init(&sc->sc_fanmgt_callout, 1);
308204180Snwhitehorn	smu_fanmgt_callout(dev);
309204180Snwhitehorn
310204218Snwhitehorn	/*
311204218Snwhitehorn	 * Set up LED interface
312204218Snwhitehorn	 */
313204218Snwhitehorn	sc->sc_leddev = led_create(smu_set_sleepled, dev, "sleepled");
314204218Snwhitehorn
315204270Snwhitehorn	/*
316204270Snwhitehorn	 * Reset on power loss behavior
317204270Snwhitehorn	 */
318204270Snwhitehorn
319204270Snwhitehorn	SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
320204270Snwhitehorn            SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
321204270Snwhitehorn	    "server_mode", CTLTYPE_INT | CTLFLAG_RW, dev, 0,
322204270Snwhitehorn	    smu_server_mode, "I", "Enable reboot after power failure");
323204270Snwhitehorn
324194679Snwhitehorn	return (0);
325194679Snwhitehorn}
326194679Snwhitehorn
327194679Snwhitehornstatic int
328194679Snwhitehornsmu_run_cmd(device_t dev, struct smu_cmd *cmd)
329194679Snwhitehorn{
330194679Snwhitehorn	struct smu_softc *sc;
331204082Snwhitehorn	int doorbell_ack, result, oldpow;
332194679Snwhitehorn
333194679Snwhitehorn	sc = device_get_softc(dev);
334194679Snwhitehorn
335194679Snwhitehorn	mtx_lock(&sc->sc_mtx);
336194679Snwhitehorn
337204082Snwhitehorn	oldpow = powerpc_pow_enabled;
338204082Snwhitehorn	powerpc_pow_enabled = 0;
339204082Snwhitehorn
340194679Snwhitehorn	/* Copy the command to the mailbox */
341194679Snwhitehorn	memcpy(sc->sc_cmd, cmd, sizeof(*cmd));
342194679Snwhitehorn	bus_dmamap_sync(sc->sc_dmatag, sc->sc_cmd_dmamap, BUS_DMASYNC_PREWRITE);
343194679Snwhitehorn	bus_space_write_4(sc->sc_bt, sc->sc_mailbox, 0, sc->sc_cmd_phys);
344194679Snwhitehorn
345204082Snwhitehorn	/* Flush the cacheline it is in -- SMU bypasses the cache */
346204082Snwhitehorn	__asm __volatile("sync; dcbf 0,%0; sync" :: "r"(sc->sc_cmd): "memory");
347194679Snwhitehorn
348194679Snwhitehorn	/* Ring SMU doorbell */
349194679Snwhitehorn	macgpio_write(smu_doorbell, GPIO_DDR_OUTPUT);
350194679Snwhitehorn
351194679Snwhitehorn	/* Wait for the doorbell GPIO to go high, signaling completion */
352194679Snwhitehorn	do {
353194679Snwhitehorn		/* XXX: timeout */
354194679Snwhitehorn		DELAY(50);
355194679Snwhitehorn		doorbell_ack = macgpio_read(smu_doorbell);
356204082Snwhitehorn	} while (doorbell_ack != (GPIO_DDR_OUTPUT | GPIO_LEVEL_RO | GPIO_DATA));
357194679Snwhitehorn
358194679Snwhitehorn	/* Check result. First invalidate the cache again... */
359194679Snwhitehorn	__asm __volatile("dcbf 0,%0; sync" :: "r"(sc->sc_cmd) : "memory");
360194679Snwhitehorn
361194679Snwhitehorn	bus_dmamap_sync(sc->sc_dmatag, sc->sc_cmd_dmamap, BUS_DMASYNC_POSTREAD);
362194679Snwhitehorn
363194679Snwhitehorn	/* SMU acks the command by inverting the command bits */
364204082Snwhitehorn	if (sc->sc_cmd->cmd == ((~cmd->cmd) & 0xff))
365194679Snwhitehorn		result = 0;
366194679Snwhitehorn	else
367194679Snwhitehorn		result = EIO;
368194679Snwhitehorn
369204082Snwhitehorn	powerpc_pow_enabled = oldpow;
370204082Snwhitehorn
371204082Snwhitehorn	memcpy(cmd->data, sc->sc_cmd->data, sizeof(cmd->data));
372204179Snwhitehorn	cmd->len = sc->sc_cmd->len;
373204082Snwhitehorn
374194679Snwhitehorn	mtx_unlock(&sc->sc_mtx);
375194679Snwhitehorn
376194679Snwhitehorn	return (result);
377194679Snwhitehorn}
378194679Snwhitehorn
379204082Snwhitehornstatic int
380204082Snwhitehornsmu_get_datablock(device_t dev, int8_t id, uint8_t *buf, size_t len)
381204082Snwhitehorn{
382204082Snwhitehorn	struct smu_cmd cmd;
383204082Snwhitehorn	uint8_t addr[4];
384204082Snwhitehorn
385204082Snwhitehorn	cmd.cmd = SMU_PARTITION;
386204082Snwhitehorn	cmd.len = 2;
387204082Snwhitehorn	cmd.data[0] = SMU_PARTITION_LATEST;
388204082Snwhitehorn	cmd.data[1] = id;
389204082Snwhitehorn
390204082Snwhitehorn	smu_run_cmd(dev, &cmd);
391204082Snwhitehorn
392204082Snwhitehorn	addr[0] = addr[1] = 0;
393204082Snwhitehorn	addr[2] = cmd.data[0];
394204082Snwhitehorn	addr[3] = cmd.data[1];
395204082Snwhitehorn
396204082Snwhitehorn	cmd.cmd = SMU_MISC;
397204082Snwhitehorn	cmd.len = 7;
398204082Snwhitehorn	cmd.data[0] = SMU_MISC_GET_DATA;
399204082Snwhitehorn	cmd.data[1] = sizeof(addr);
400204082Snwhitehorn	memcpy(&cmd.data[2], addr, sizeof(addr));
401204082Snwhitehorn	cmd.data[6] = len;
402204082Snwhitehorn
403204082Snwhitehorn	smu_run_cmd(dev, &cmd);
404204082Snwhitehorn	memcpy(buf, cmd.data, len);
405204082Snwhitehorn	return (0);
406204082Snwhitehorn}
407204082Snwhitehorn
408194679Snwhitehornstatic void
409194679Snwhitehornsmu_slew_cpu_voltage(device_t dev, int to)
410194679Snwhitehorn{
411194679Snwhitehorn	struct smu_cmd cmd;
412194679Snwhitehorn
413194679Snwhitehorn	cmd.cmd = SMU_POWER;
414194679Snwhitehorn	cmd.len = 8;
415194679Snwhitehorn	cmd.data[0] = 'V';
416194679Snwhitehorn	cmd.data[1] = 'S';
417194679Snwhitehorn	cmd.data[2] = 'L';
418194679Snwhitehorn	cmd.data[3] = 'E';
419194679Snwhitehorn	cmd.data[4] = 'W';
420194679Snwhitehorn	cmd.data[5] = 0xff;
421194679Snwhitehorn	cmd.data[6] = 1;
422194679Snwhitehorn	cmd.data[7] = to;
423194679Snwhitehorn
424194679Snwhitehorn	smu_run_cmd(dev, &cmd);
425194679Snwhitehorn}
426194679Snwhitehorn
427194679Snwhitehornstatic void
428194679Snwhitehornsmu_cpufreq_pre_change(device_t dev, const struct cf_level *level)
429194679Snwhitehorn{
430194679Snwhitehorn	/*
431194679Snwhitehorn	 * Make sure the CPU voltage is raised before we raise
432194679Snwhitehorn	 * the clock.
433194679Snwhitehorn	 */
434194679Snwhitehorn
435194679Snwhitehorn	if (level->rel_set[0].freq == 10000 /* max */)
436194679Snwhitehorn		smu_slew_cpu_voltage(dev, 0);
437194679Snwhitehorn}
438194679Snwhitehorn
439194679Snwhitehornstatic void
440194679Snwhitehornsmu_cpufreq_post_change(device_t dev, const struct cf_level *level)
441194679Snwhitehorn{
442194679Snwhitehorn	/* We are safe to reduce CPU voltage after a downward transition */
443194679Snwhitehorn
444194679Snwhitehorn	if (level->rel_set[0].freq < 10000 /* max */)
445194679Snwhitehorn		smu_slew_cpu_voltage(dev, 1); /* XXX: 1/4 voltage for 970MP? */
446194679Snwhitehorn}
447194679Snwhitehorn
448194679Snwhitehorn/* Routines for probing the SMU doorbell GPIO */
449194679Snwhitehornstatic int doorbell_probe(device_t dev);
450194679Snwhitehornstatic int doorbell_attach(device_t dev);
451194679Snwhitehorn
452194679Snwhitehornstatic device_method_t  doorbell_methods[] = {
453194679Snwhitehorn	/* Device interface */
454194679Snwhitehorn	DEVMETHOD(device_probe,		doorbell_probe),
455194679Snwhitehorn	DEVMETHOD(device_attach,	doorbell_attach),
456194679Snwhitehorn	{ 0, 0 },
457194679Snwhitehorn};
458194679Snwhitehorn
459194679Snwhitehornstatic driver_t doorbell_driver = {
460194679Snwhitehorn	"smudoorbell",
461194679Snwhitehorn	doorbell_methods,
462194679Snwhitehorn	0
463194679Snwhitehorn};
464194679Snwhitehorn
465194679Snwhitehornstatic devclass_t doorbell_devclass;
466194679Snwhitehorn
467194679SnwhitehornDRIVER_MODULE(smudoorbell, macgpio, doorbell_driver, doorbell_devclass, 0, 0);
468194679Snwhitehorn
469194679Snwhitehornstatic int
470194679Snwhitehorndoorbell_probe(device_t dev)
471194679Snwhitehorn{
472194679Snwhitehorn	const char *name = ofw_bus_get_name(dev);
473194679Snwhitehorn
474194679Snwhitehorn	if (strcmp(name, "smu-doorbell") != 0)
475194679Snwhitehorn		return (ENXIO);
476194679Snwhitehorn
477194679Snwhitehorn	device_set_desc(dev, "SMU Doorbell GPIO");
478194679Snwhitehorn	device_quiet(dev);
479194679Snwhitehorn	return (0);
480194679Snwhitehorn}
481194679Snwhitehorn
482194679Snwhitehornstatic int
483194679Snwhitehorndoorbell_attach(device_t dev)
484194679Snwhitehorn{
485194679Snwhitehorn	smu_doorbell = dev;
486194679Snwhitehorn	return (0);
487194679Snwhitehorn}
488204082Snwhitehorn
489204082Snwhitehorn/*
490204082Snwhitehorn * Sensor and fan management
491204082Snwhitehorn */
492204082Snwhitehorn
493204082Snwhitehornstatic int
494204082Snwhitehornsmu_fan_set_rpm(device_t smu, struct smu_fan *fan, int rpm)
495204082Snwhitehorn{
496204082Snwhitehorn	struct smu_cmd cmd;
497204179Snwhitehorn	int error;
498204082Snwhitehorn
499204082Snwhitehorn	cmd.cmd = SMU_FAN;
500204179Snwhitehorn	error = EIO;
501204082Snwhitehorn
502204179Snwhitehorn	/* Clamp to allowed range */
503204179Snwhitehorn	rpm = max(fan->min_rpm, rpm);
504204179Snwhitehorn	rpm = min(fan->max_rpm, rpm);
505204179Snwhitehorn
506204082Snwhitehorn	/*
507204179Snwhitehorn	 * Apple has two fan control mechanisms. We can't distinguish
508204179Snwhitehorn	 * them except by seeing if the new one fails. If the new one
509204179Snwhitehorn	 * fails, use the old one.
510204082Snwhitehorn	 */
511204179Snwhitehorn
512204179Snwhitehorn	if (!fan->old_style) {
513204179Snwhitehorn		cmd.len = 4;
514204179Snwhitehorn		cmd.data[0] = 0x30;
515204179Snwhitehorn		cmd.data[1] = fan->reg;
516204179Snwhitehorn		cmd.data[2] = (rpm >> 8) & 0xff;
517204179Snwhitehorn		cmd.data[3] = rpm & 0xff;
518204179Snwhitehorn
519204179Snwhitehorn		error = smu_run_cmd(smu, &cmd);
520204179Snwhitehorn		if (error)
521204179Snwhitehorn			fan->old_style = 1;
522204179Snwhitehorn	}
523204082Snwhitehorn
524204179Snwhitehorn	if (fan->old_style) {
525204179Snwhitehorn		cmd.len = 14;
526204179Snwhitehorn		cmd.data[0] = 0;
527204179Snwhitehorn		cmd.data[1] = 1 << fan->reg;
528204179Snwhitehorn		cmd.data[2 + 2*fan->reg] = (rpm >> 8) & 0xff;
529204179Snwhitehorn		cmd.data[3 + 2*fan->reg] = rpm & 0xff;
530204179Snwhitehorn		error = smu_run_cmd(smu, &cmd);
531204179Snwhitehorn	}
532204082Snwhitehorn
533204180Snwhitehorn	if (error == 0)
534204180Snwhitehorn		fan->setpoint = rpm;
535204180Snwhitehorn
536204179Snwhitehorn	return (error);
537204082Snwhitehorn}
538204082Snwhitehorn
539204082Snwhitehornstatic int
540204082Snwhitehornsmu_fan_read_rpm(device_t smu, struct smu_fan *fan)
541204082Snwhitehorn{
542204082Snwhitehorn	struct smu_cmd cmd;
543204082Snwhitehorn
544204082Snwhitehorn	cmd.cmd = SMU_FAN;
545204179Snwhitehorn	cmd.len = 1;
546204082Snwhitehorn	cmd.data[0] = 1;
547204082Snwhitehorn
548204082Snwhitehorn	smu_run_cmd(smu, &cmd);
549204082Snwhitehorn
550204179Snwhitehorn	return ((cmd.data[fan->reg*2+1] << 8) | cmd.data[fan->reg*2+2]);
551204082Snwhitehorn}
552204082Snwhitehorn
553204082Snwhitehornstatic int
554204082Snwhitehornsmu_fanrpm_sysctl(SYSCTL_HANDLER_ARGS)
555204082Snwhitehorn{
556204082Snwhitehorn	device_t smu;
557204082Snwhitehorn	struct smu_softc *sc;
558204082Snwhitehorn	struct smu_fan *fan;
559204082Snwhitehorn	int rpm, error;
560204082Snwhitehorn
561204082Snwhitehorn	smu = arg1;
562204082Snwhitehorn	sc = device_get_softc(smu);
563204082Snwhitehorn	fan = &sc->sc_fans[arg2];
564204082Snwhitehorn
565204082Snwhitehorn	rpm = smu_fan_read_rpm(smu, fan);
566204082Snwhitehorn	error = sysctl_handle_int(oidp, &rpm, 0, req);
567204082Snwhitehorn
568204082Snwhitehorn	if (error || !req->newptr)
569204082Snwhitehorn		return (error);
570204082Snwhitehorn
571204180Snwhitehorn	sc->sc_lastuserchange = time_uptime;
572204082Snwhitehorn
573204082Snwhitehorn	return (smu_fan_set_rpm(smu, fan, rpm));
574204082Snwhitehorn}
575204082Snwhitehorn
576204082Snwhitehornstatic void
577204082Snwhitehornsmu_attach_fans(device_t dev, phandle_t fanroot)
578204082Snwhitehorn{
579204082Snwhitehorn	struct smu_fan *fan;
580204082Snwhitehorn	struct smu_softc *sc;
581204082Snwhitehorn	struct sysctl_oid *oid, *fanroot_oid;
582204082Snwhitehorn	struct sysctl_ctx_list *ctx;
583204082Snwhitehorn	phandle_t child;
584204082Snwhitehorn	char type[32], sysctl_name[32];
585204082Snwhitehorn	int i;
586204082Snwhitehorn
587204082Snwhitehorn	sc = device_get_softc(dev);
588204082Snwhitehorn	sc->sc_nfans = 0;
589204082Snwhitehorn
590204082Snwhitehorn	for (child = OF_child(fanroot); child != 0; child = OF_peer(child))
591204082Snwhitehorn		sc->sc_nfans++;
592204082Snwhitehorn
593204082Snwhitehorn	if (sc->sc_nfans == 0) {
594204082Snwhitehorn		device_printf(dev, "WARNING: No fans detected!\n");
595204082Snwhitehorn		return;
596204082Snwhitehorn	}
597204082Snwhitehorn
598204082Snwhitehorn	sc->sc_fans = malloc(sc->sc_nfans * sizeof(struct smu_fan), M_SMU,
599204082Snwhitehorn	    M_WAITOK | M_ZERO);
600204082Snwhitehorn
601204082Snwhitehorn	fan = sc->sc_fans;
602204082Snwhitehorn	sc->sc_nfans = 0;
603204082Snwhitehorn
604204082Snwhitehorn	ctx = device_get_sysctl_ctx(dev);
605204082Snwhitehorn	fanroot_oid = SYSCTL_ADD_NODE(ctx,
606204082Snwhitehorn	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "fans",
607204082Snwhitehorn	    CTLFLAG_RD, 0, "SMU Fan Information");
608204082Snwhitehorn
609204082Snwhitehorn	for (child = OF_child(fanroot); child != 0; child = OF_peer(child)) {
610204082Snwhitehorn		OF_getprop(child, "device_type", type, sizeof(type));
611204082Snwhitehorn		if (strcmp(type, "fan-rpm-control") != 0)
612204082Snwhitehorn			continue;
613204082Snwhitehorn
614204179Snwhitehorn		fan->old_style = 0;
615204082Snwhitehorn		OF_getprop(child, "reg", &fan->reg, sizeof(cell_t));
616204082Snwhitehorn		OF_getprop(child, "min-value", &fan->min_rpm, sizeof(cell_t));
617204082Snwhitehorn		OF_getprop(child, "max-value", &fan->max_rpm, sizeof(cell_t));
618204179Snwhitehorn
619204179Snwhitehorn		if (OF_getprop(child, "unmanaged-value", &fan->unmanaged_rpm,
620204179Snwhitehorn		    sizeof(cell_t)) != sizeof(cell_t))
621204179Snwhitehorn			fan->unmanaged_rpm = fan->max_rpm;
622204179Snwhitehorn
623204180Snwhitehorn		fan->setpoint = smu_fan_read_rpm(dev, fan);
624204180Snwhitehorn
625204082Snwhitehorn		OF_getprop(child, "location", fan->location,
626204082Snwhitehorn		    sizeof(fan->location));
627204180Snwhitehorn
628204082Snwhitehorn		/* Add sysctls */
629204082Snwhitehorn		for (i = 0; i < strlen(fan->location); i++) {
630204082Snwhitehorn			sysctl_name[i] = tolower(fan->location[i]);
631204082Snwhitehorn			if (isspace(sysctl_name[i]))
632204082Snwhitehorn				sysctl_name[i] = '_';
633204082Snwhitehorn		}
634204082Snwhitehorn		sysctl_name[i] = 0;
635204082Snwhitehorn
636204082Snwhitehorn		oid = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(fanroot_oid),
637204082Snwhitehorn		    OID_AUTO, sysctl_name, CTLFLAG_RD, 0, "Fan Information");
638204082Snwhitehorn		SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "minrpm",
639204082Snwhitehorn		    CTLTYPE_INT | CTLFLAG_RD, &fan->min_rpm, sizeof(cell_t),
640204082Snwhitehorn		    "Minimum allowed RPM");
641204082Snwhitehorn		SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "maxrpm",
642204082Snwhitehorn		    CTLTYPE_INT | CTLFLAG_RD, &fan->max_rpm, sizeof(cell_t),
643204082Snwhitehorn		    "Maximum allowed RPM");
644204082Snwhitehorn		SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "rpm",
645204082Snwhitehorn		    CTLTYPE_INT | CTLFLAG_RW, dev, sc->sc_nfans,
646204082Snwhitehorn		    smu_fanrpm_sysctl, "I", "Fan RPM");
647204082Snwhitehorn
648204082Snwhitehorn		fan++;
649204082Snwhitehorn		sc->sc_nfans++;
650204082Snwhitehorn	}
651204082Snwhitehorn}
652204082Snwhitehorn
653204082Snwhitehornstatic int
654204082Snwhitehornsmu_sensor_read(device_t smu, struct smu_sensor *sens)
655204082Snwhitehorn{
656204082Snwhitehorn	struct smu_cmd cmd;
657204082Snwhitehorn	struct smu_softc *sc;
658204082Snwhitehorn	int64_t value;
659204082Snwhitehorn
660204082Snwhitehorn	cmd.cmd = SMU_ADC;
661204082Snwhitehorn	cmd.len = 1;
662204082Snwhitehorn	cmd.data[0] = sens->reg;
663204082Snwhitehorn
664204082Snwhitehorn	smu_run_cmd(smu, &cmd);
665204082Snwhitehorn
666204082Snwhitehorn	sc = device_get_softc(smu);
667204082Snwhitehorn	value = (cmd.data[0] << 8) | cmd.data[1];
668204082Snwhitehorn
669204082Snwhitehorn	switch (sens->type) {
670204082Snwhitehorn	case SMU_TEMP_SENSOR:
671204082Snwhitehorn		value *= sc->sc_cpu_diode_scale;
672204082Snwhitehorn		value >>= 3;
673204082Snwhitehorn		value += ((int64_t)sc->sc_cpu_diode_offset) << 9;
674204082Snwhitehorn		value <<= 1;
675204082Snwhitehorn
676204082Snwhitehorn		/* Convert from 16.16 fixed point degC into integer C. */
677204082Snwhitehorn		value *= 15625;
678204082Snwhitehorn		value /= 1024;
679204082Snwhitehorn		value /= 1000000;
680204082Snwhitehorn		break;
681204082Snwhitehorn	case SMU_VOLTAGE_SENSOR:
682204082Snwhitehorn		value *= sc->sc_cpu_volt_scale;
683204082Snwhitehorn		value += sc->sc_cpu_volt_offset;
684204082Snwhitehorn		value <<= 4;
685204082Snwhitehorn
686204082Snwhitehorn		/* Convert from 16.16 fixed point V into mV. */
687204082Snwhitehorn		value *= 15625;
688204082Snwhitehorn		value /= 1024;
689204082Snwhitehorn		value /= 1000;
690204082Snwhitehorn		break;
691204082Snwhitehorn	case SMU_CURRENT_SENSOR:
692204082Snwhitehorn		value *= sc->sc_cpu_curr_scale;
693204082Snwhitehorn		value += sc->sc_cpu_curr_offset;
694204082Snwhitehorn		value <<= 4;
695204082Snwhitehorn
696204082Snwhitehorn		/* Convert from 16.16 fixed point A into mA. */
697204082Snwhitehorn		value *= 15625;
698204082Snwhitehorn		value /= 1024;
699204082Snwhitehorn		value /= 1000;
700204082Snwhitehorn		break;
701204082Snwhitehorn	case SMU_POWER_SENSOR:
702204082Snwhitehorn		value *= sc->sc_slots_pow_scale;
703204082Snwhitehorn		value += sc->sc_slots_pow_offset;
704204082Snwhitehorn		value <<= 4;
705204082Snwhitehorn
706204082Snwhitehorn		/* Convert from 16.16 fixed point W into mW. */
707204082Snwhitehorn		value *= 15625;
708204082Snwhitehorn		value /= 1024;
709204082Snwhitehorn		value /= 1000;
710204082Snwhitehorn		break;
711204082Snwhitehorn	}
712204082Snwhitehorn
713204082Snwhitehorn	return (value);
714204082Snwhitehorn}
715204082Snwhitehorn
716204082Snwhitehornstatic int
717204082Snwhitehornsmu_sensor_sysctl(SYSCTL_HANDLER_ARGS)
718204082Snwhitehorn{
719204082Snwhitehorn	device_t smu;
720204082Snwhitehorn	struct smu_softc *sc;
721204082Snwhitehorn	struct smu_sensor *sens;
722204082Snwhitehorn	int value, error;
723204082Snwhitehorn
724204082Snwhitehorn	smu = arg1;
725204082Snwhitehorn	sc = device_get_softc(smu);
726204082Snwhitehorn	sens = &sc->sc_sensors[arg2];
727204082Snwhitehorn
728204082Snwhitehorn	value = smu_sensor_read(smu, sens);
729204082Snwhitehorn	error = sysctl_handle_int(oidp, &value, 0, req);
730204082Snwhitehorn
731204082Snwhitehorn	return (error);
732204082Snwhitehorn}
733204082Snwhitehorn
734204082Snwhitehornstatic void
735204082Snwhitehornsmu_attach_sensors(device_t dev, phandle_t sensroot)
736204082Snwhitehorn{
737204082Snwhitehorn	struct smu_sensor *sens;
738204082Snwhitehorn	struct smu_softc *sc;
739204082Snwhitehorn	struct sysctl_oid *sensroot_oid;
740204082Snwhitehorn	struct sysctl_ctx_list *ctx;
741204082Snwhitehorn	phandle_t child;
742204082Snwhitehorn	char type[32];
743204082Snwhitehorn	int i;
744204082Snwhitehorn
745204082Snwhitehorn	sc = device_get_softc(dev);
746204082Snwhitehorn	sc->sc_nsensors = 0;
747204082Snwhitehorn
748204082Snwhitehorn	for (child = OF_child(sensroot); child != 0; child = OF_peer(child))
749204082Snwhitehorn		sc->sc_nsensors++;
750204082Snwhitehorn
751204082Snwhitehorn	if (sc->sc_nsensors == 0) {
752204082Snwhitehorn		device_printf(dev, "WARNING: No sensors detected!\n");
753204082Snwhitehorn		return;
754204082Snwhitehorn	}
755204082Snwhitehorn
756204179Snwhitehorn	sc->sc_sensors = malloc(sc->sc_nsensors * sizeof(struct smu_sensor),
757204179Snwhitehorn	    M_SMU, M_WAITOK | M_ZERO);
758204082Snwhitehorn
759204082Snwhitehorn	sens = sc->sc_sensors;
760204082Snwhitehorn	sc->sc_nsensors = 0;
761204082Snwhitehorn
762204082Snwhitehorn	ctx = device_get_sysctl_ctx(dev);
763204082Snwhitehorn	sensroot_oid = SYSCTL_ADD_NODE(ctx,
764204082Snwhitehorn	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensors",
765204082Snwhitehorn	    CTLFLAG_RD, 0, "SMU Sensor Information");
766204082Snwhitehorn
767204082Snwhitehorn	for (child = OF_child(sensroot); child != 0; child = OF_peer(child)) {
768204082Snwhitehorn		char sysctl_name[40], sysctl_desc[40];
769204082Snwhitehorn		const char *units;
770204082Snwhitehorn
771204082Snwhitehorn		OF_getprop(child, "device_type", type, sizeof(type));
772204082Snwhitehorn
773204082Snwhitehorn		if (strcmp(type, "current-sensor") == 0) {
774204082Snwhitehorn			sens->type = SMU_CURRENT_SENSOR;
775204082Snwhitehorn			units = "mA";
776204082Snwhitehorn		} else if (strcmp(type, "temp-sensor") == 0) {
777204082Snwhitehorn			sens->type = SMU_TEMP_SENSOR;
778204082Snwhitehorn			units = "C";
779204082Snwhitehorn		} else if (strcmp(type, "voltage-sensor") == 0) {
780204082Snwhitehorn			sens->type = SMU_VOLTAGE_SENSOR;
781204082Snwhitehorn			units = "mV";
782204082Snwhitehorn		} else if (strcmp(type, "power-sensor") == 0) {
783204082Snwhitehorn			sens->type = SMU_POWER_SENSOR;
784204082Snwhitehorn			units = "mW";
785204082Snwhitehorn		} else {
786204082Snwhitehorn			continue;
787204082Snwhitehorn		}
788204082Snwhitehorn
789204082Snwhitehorn		OF_getprop(child, "reg", &sens->reg, sizeof(cell_t));
790204082Snwhitehorn		OF_getprop(child, "location", sens->location,
791204082Snwhitehorn		    sizeof(sens->location));
792204082Snwhitehorn
793204082Snwhitehorn		for (i = 0; i < strlen(sens->location); i++) {
794204082Snwhitehorn			sysctl_name[i] = tolower(sens->location[i]);
795204082Snwhitehorn			if (isspace(sysctl_name[i]))
796204082Snwhitehorn				sysctl_name[i] = '_';
797204082Snwhitehorn		}
798204082Snwhitehorn		sysctl_name[i] = 0;
799204082Snwhitehorn
800204082Snwhitehorn		sprintf(sysctl_desc,"%s (%s)", sens->location, units);
801204082Snwhitehorn
802204082Snwhitehorn		SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(sensroot_oid), OID_AUTO,
803204082Snwhitehorn		    sysctl_name, CTLTYPE_INT | CTLFLAG_RD, dev, sc->sc_nsensors,
804204082Snwhitehorn		    smu_sensor_sysctl, "I", sysctl_desc);
805204082Snwhitehorn
806204082Snwhitehorn		sens++;
807204082Snwhitehorn		sc->sc_nsensors++;
808204082Snwhitehorn	}
809204082Snwhitehorn}
810204082Snwhitehorn
811204180Snwhitehornstatic int
812204180Snwhitehornms_to_ticks(int ms)
813204180Snwhitehorn{
814204180Snwhitehorn	if (hz > 1000)
815204180Snwhitehorn		return ms*(hz/1000);
816204180Snwhitehorn
817204180Snwhitehorn	return ms/(1000/hz);
818204180Snwhitehorn}
819204180Snwhitehorn
820204180Snwhitehornstatic void
821204180Snwhitehornsmu_fanmgt_callout(void *xdev) {
822204180Snwhitehorn	device_t smu = xdev;
823204180Snwhitehorn	struct smu_softc *sc;
824204180Snwhitehorn	int i, maxtemp, temp, factor;
825204180Snwhitehorn
826204180Snwhitehorn	sc = device_get_softc(smu);
827204180Snwhitehorn
828204180Snwhitehorn	if (time_uptime - sc->sc_lastuserchange < 3) {
829204180Snwhitehorn		/*
830204180Snwhitehorn		 * If we have heard from a user process in the last 3 seconds,
831204180Snwhitehorn		 * go away.
832204180Snwhitehorn		 */
833204180Snwhitehorn
834204180Snwhitehorn		callout_reset(&sc->sc_fanmgt_callout,
835204180Snwhitehorn		    ms_to_ticks(SMU_FANMGT_INTERVAL), smu_fanmgt_callout, smu);
836204180Snwhitehorn		return;
837204180Snwhitehorn	}
838204180Snwhitehorn
839204180Snwhitehorn	maxtemp = 0;
840204180Snwhitehorn	for (i = 0; i < sc->sc_nsensors; i++) {
841204180Snwhitehorn		if (sc->sc_sensors[i].type != SMU_TEMP_SENSOR)
842204180Snwhitehorn			continue;
843204180Snwhitehorn
844204180Snwhitehorn		temp = smu_sensor_read(smu, &sc->sc_sensors[i]);
845204180Snwhitehorn		if (temp > maxtemp)
846204180Snwhitehorn			maxtemp = temp;
847204180Snwhitehorn	}
848204180Snwhitehorn
849204180Snwhitehorn	if (maxtemp < 10) { /* Bail if no good sensors */
850204180Snwhitehorn		for (i = 0; i < sc->sc_nfans; i++)
851204180Snwhitehorn			smu_fan_set_rpm(smu, &sc->sc_fans[i],
852204180Snwhitehorn			    sc->sc_fans[i].unmanaged_rpm);
853204180Snwhitehorn		return;
854204180Snwhitehorn	}
855204180Snwhitehorn
856204180Snwhitehorn	if (maxtemp > sc->sc_critical_temp) {
857204180Snwhitehorn		device_printf(smu, "WARNING: Current system temperature (%d C) "
858204180Snwhitehorn		    "exceeds critical temperature (%d C)! Shutting down!\n",
859204180Snwhitehorn		    maxtemp, sc->sc_critical_temp);
860204180Snwhitehorn		shutdown_nice(RB_POWEROFF);
861204180Snwhitehorn	}
862204180Snwhitehorn
863204180Snwhitehorn	if (maxtemp - sc->sc_target_temp > 20)
864204180Snwhitehorn		device_printf(smu, "WARNING: Current system temperature (%d C) "
865204180Snwhitehorn		    "more than 20 degrees over target temperature (%d C)!\n",
866204180Snwhitehorn		    maxtemp, sc->sc_target_temp);
867204180Snwhitehorn
868204180Snwhitehorn	if (maxtemp > sc->sc_target_temp)
869204180Snwhitehorn		factor = 110;
870204180Snwhitehorn	else if (sc->sc_target_temp - maxtemp > 4)
871204180Snwhitehorn		factor = 90;
872204180Snwhitehorn	else if (sc->sc_target_temp - maxtemp > 1)
873204180Snwhitehorn		factor = 95;
874204180Snwhitehorn	else
875204180Snwhitehorn		factor = 100;
876204180Snwhitehorn
877204180Snwhitehorn	for (i = 0; i < sc->sc_nfans; i++)
878204180Snwhitehorn		smu_fan_set_rpm(smu, &sc->sc_fans[i],
879204180Snwhitehorn		    (sc->sc_fans[i].setpoint * factor) / 100);
880204180Snwhitehorn
881204180Snwhitehorn	callout_reset(&sc->sc_fanmgt_callout,
882204180Snwhitehorn	    ms_to_ticks(SMU_FANMGT_INTERVAL), smu_fanmgt_callout, smu);
883204180Snwhitehorn}
884204180Snwhitehorn
885204218Snwhitehornstatic void
886204218Snwhitehornsmu_set_sleepled(void *xdev, int onoff)
887204218Snwhitehorn{
888204218Snwhitehorn	struct smu_cmd cmd;
889204218Snwhitehorn	device_t smu = xdev;
890204218Snwhitehorn
891204218Snwhitehorn	cmd.cmd = SMU_MISC;
892204218Snwhitehorn	cmd.len = 3;
893204218Snwhitehorn	cmd.data[0] = SMU_MISC_LED_CTRL;
894204218Snwhitehorn	cmd.data[1] = 0;
895204218Snwhitehorn	cmd.data[2] = onoff;
896204218Snwhitehorn
897204218Snwhitehorn	smu_run_cmd(smu, &cmd);
898204218Snwhitehorn}
899204218Snwhitehorn
900204270Snwhitehornstatic int
901204270Snwhitehornsmu_server_mode(SYSCTL_HANDLER_ARGS)
902204270Snwhitehorn{
903204270Snwhitehorn	struct smu_cmd cmd;
904204270Snwhitehorn	u_int server_mode;
905204270Snwhitehorn	device_t smu = arg1;
906204270Snwhitehorn	int error;
907204270Snwhitehorn
908204270Snwhitehorn	cmd.cmd = SMU_POWER_EVENTS;
909204270Snwhitehorn	cmd.len = 1;
910204270Snwhitehorn	cmd.data[0] = SMU_PWR_GET_POWERUP;
911204270Snwhitehorn
912204270Snwhitehorn	error = smu_run_cmd(smu, &cmd);
913204270Snwhitehorn
914204270Snwhitehorn	if (error)
915204270Snwhitehorn		return (error);
916204270Snwhitehorn
917204270Snwhitehorn	server_mode = (cmd.data[1] & SMU_WAKEUP_AC_INSERT) ? 1 : 0;
918204270Snwhitehorn
919204270Snwhitehorn	error = sysctl_handle_int(oidp, &server_mode, 0, req);
920204270Snwhitehorn
921204270Snwhitehorn	if (error || !req->newptr)
922204270Snwhitehorn		return (error);
923204270Snwhitehorn
924204270Snwhitehorn	if (server_mode == 1)
925204270Snwhitehorn		cmd.data[0] = SMU_PWR_SET_POWERUP;
926204270Snwhitehorn	else if (server_mode == 0)
927204270Snwhitehorn		cmd.data[0] = SMU_PWR_CLR_POWERUP;
928204270Snwhitehorn	else
929204270Snwhitehorn		return (EINVAL);
930204270Snwhitehorn
931204270Snwhitehorn	cmd.len = 3;
932204270Snwhitehorn	cmd.data[1] = 0;
933204270Snwhitehorn	cmd.data[2] = SMU_WAKEUP_AC_INSERT;
934204270Snwhitehorn
935204270Snwhitehorn	return (smu_run_cmd(smu, &cmd));
936204270Snwhitehorn}
937204270Snwhitehorn
938