smu.c revision 232482
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 232482 2012-03-04 08:43:33Z andreast $");
30194679Snwhitehorn
31194679Snwhitehorn#include <sys/param.h>
32194679Snwhitehorn#include <sys/bus.h>
33194679Snwhitehorn#include <sys/systm.h>
34194679Snwhitehorn#include <sys/module.h>
35194679Snwhitehorn#include <sys/conf.h>
36194679Snwhitehorn#include <sys/cpu.h>
37205506Snwhitehorn#include <sys/clock.h>
38204082Snwhitehorn#include <sys/ctype.h>
39194679Snwhitehorn#include <sys/kernel.h>
40204692Snwhitehorn#include <sys/kthread.h>
41204180Snwhitehorn#include <sys/reboot.h>
42194679Snwhitehorn#include <sys/rman.h>
43194679Snwhitehorn#include <sys/sysctl.h>
44204692Snwhitehorn#include <sys/unistd.h>
45194679Snwhitehorn
46194679Snwhitehorn#include <machine/bus.h>
47204692Snwhitehorn#include <machine/intr_machdep.h>
48194679Snwhitehorn#include <machine/md_var.h>
49194679Snwhitehorn
50208841Snwhitehorn#include <dev/iicbus/iicbus.h>
51208841Snwhitehorn#include <dev/iicbus/iiconf.h>
52204218Snwhitehorn#include <dev/led/led.h>
53194679Snwhitehorn#include <dev/ofw/openfirm.h>
54194679Snwhitehorn#include <dev/ofw/ofw_bus.h>
55208841Snwhitehorn#include <dev/ofw/ofw_bus_subr.h>
56194679Snwhitehorn#include <powerpc/powermac/macgpiovar.h>
57222429Snwhitehorn#include <powerpc/powermac/powermac_thermal.h>
58194679Snwhitehorn
59205506Snwhitehorn#include "clock_if.h"
60208841Snwhitehorn#include "iicbus_if.h"
61205506Snwhitehorn
62194679Snwhitehornstruct smu_cmd {
63204082Snwhitehorn	volatile uint8_t cmd;
64194679Snwhitehorn	uint8_t		len;
65194679Snwhitehorn	uint8_t		data[254];
66204692Snwhitehorn
67204692Snwhitehorn	STAILQ_ENTRY(smu_cmd) cmd_q;
68194679Snwhitehorn};
69194679Snwhitehorn
70204692SnwhitehornSTAILQ_HEAD(smu_cmdq, smu_cmd);
71204692Snwhitehorn
72204082Snwhitehornstruct smu_fan {
73222429Snwhitehorn	struct pmac_fan fan;
74222429Snwhitehorn	device_t dev;
75204082Snwhitehorn	cell_t	reg;
76204180Snwhitehorn
77232482Sandreast	enum {
78232482Sandreast		SMU_FAN_RPM,
79232482Sandreast		SMU_FAN_PWM
80232482Sandreast	} type;
81204179Snwhitehorn	int	old_style;
82204180Snwhitehorn	int	setpoint;
83232482Sandreast	int     rpm;
84204082Snwhitehorn};
85204082Snwhitehorn
86232482Sandreast/* We can read the PWM and the RPM from a PWM controlled fan.
87232482Sandreast * Offer both values via sysctl.
88232482Sandreast */
89232482Sandreastenum {
90232482Sandreast	SMU_PWM_SYSCTL_PWM   = 1 << 8,
91232482Sandreast	SMU_PWM_SYSCTL_RPM   = 2 << 8
92232482Sandreast};
93232482Sandreast
94204082Snwhitehornstruct smu_sensor {
95222429Snwhitehorn	struct pmac_therm therm;
96222429Snwhitehorn	device_t dev;
97222429Snwhitehorn
98204082Snwhitehorn	cell_t	reg;
99204082Snwhitehorn	enum {
100204082Snwhitehorn		SMU_CURRENT_SENSOR,
101204082Snwhitehorn		SMU_VOLTAGE_SENSOR,
102204082Snwhitehorn		SMU_POWER_SENSOR,
103204082Snwhitehorn		SMU_TEMP_SENSOR
104204082Snwhitehorn	} type;
105204082Snwhitehorn};
106204082Snwhitehorn
107194679Snwhitehornstruct smu_softc {
108194679Snwhitehorn	device_t	sc_dev;
109194679Snwhitehorn	struct mtx	sc_mtx;
110194679Snwhitehorn
111194679Snwhitehorn	struct resource	*sc_memr;
112194679Snwhitehorn	int		sc_memrid;
113215100Snwhitehorn	int		sc_u3;
114194679Snwhitehorn
115194679Snwhitehorn	bus_dma_tag_t	sc_dmatag;
116194679Snwhitehorn	bus_space_tag_t	sc_bt;
117194679Snwhitehorn	bus_space_handle_t sc_mailbox;
118194679Snwhitehorn
119204692Snwhitehorn	struct smu_cmd	*sc_cmd, *sc_cur_cmd;
120194679Snwhitehorn	bus_addr_t	sc_cmd_phys;
121194679Snwhitehorn	bus_dmamap_t	sc_cmd_dmamap;
122204692Snwhitehorn	struct smu_cmdq	sc_cmdq;
123204082Snwhitehorn
124204082Snwhitehorn	struct smu_fan	*sc_fans;
125204082Snwhitehorn	int		sc_nfans;
126204082Snwhitehorn	struct smu_sensor *sc_sensors;
127204082Snwhitehorn	int		sc_nsensors;
128204082Snwhitehorn
129204692Snwhitehorn	int		sc_doorbellirqid;
130204692Snwhitehorn	struct resource	*sc_doorbellirq;
131204692Snwhitehorn	void		*sc_doorbellirqcookie;
132204692Snwhitehorn
133204692Snwhitehorn	struct proc	*sc_fanmgt_proc;
134204180Snwhitehorn	time_t		sc_lastuserchange;
135204180Snwhitehorn
136204082Snwhitehorn	/* Calibration data */
137204082Snwhitehorn	uint16_t	sc_cpu_diode_scale;
138204082Snwhitehorn	int16_t		sc_cpu_diode_offset;
139204082Snwhitehorn
140204082Snwhitehorn	uint16_t	sc_cpu_volt_scale;
141204082Snwhitehorn	int16_t		sc_cpu_volt_offset;
142204082Snwhitehorn	uint16_t	sc_cpu_curr_scale;
143204082Snwhitehorn	int16_t		sc_cpu_curr_offset;
144204082Snwhitehorn
145204082Snwhitehorn	uint16_t	sc_slots_pow_scale;
146204082Snwhitehorn	int16_t		sc_slots_pow_offset;
147204180Snwhitehorn
148204218Snwhitehorn	struct cdev 	*sc_leddev;
149194679Snwhitehorn};
150194679Snwhitehorn
151194679Snwhitehorn/* regular bus attachment functions */
152194679Snwhitehorn
153194679Snwhitehornstatic int	smu_probe(device_t);
154194679Snwhitehornstatic int	smu_attach(device_t);
155208841Snwhitehornstatic const struct ofw_bus_devinfo *
156208841Snwhitehorn    smu_get_devinfo(device_t bus, device_t dev);
157194679Snwhitehorn
158194679Snwhitehorn/* cpufreq notification hooks */
159194679Snwhitehorn
160194679Snwhitehornstatic void	smu_cpufreq_pre_change(device_t, const struct cf_level *level);
161194679Snwhitehornstatic void	smu_cpufreq_post_change(device_t, const struct cf_level *level);
162194679Snwhitehorn
163205506Snwhitehorn/* clock interface */
164205506Snwhitehornstatic int	smu_gettime(device_t dev, struct timespec *ts);
165205506Snwhitehornstatic int	smu_settime(device_t dev, struct timespec *ts);
166205506Snwhitehorn
167204082Snwhitehorn/* utility functions */
168204692Snwhitehornstatic int	smu_run_cmd(device_t dev, struct smu_cmd *cmd, int wait);
169204082Snwhitehornstatic int	smu_get_datablock(device_t dev, int8_t id, uint8_t *buf,
170204082Snwhitehorn		    size_t len);
171208841Snwhitehornstatic void	smu_attach_i2c(device_t dev, phandle_t i2croot);
172204082Snwhitehornstatic void	smu_attach_fans(device_t dev, phandle_t fanroot);
173204082Snwhitehornstatic void	smu_attach_sensors(device_t dev, phandle_t sensroot);
174204218Snwhitehornstatic void	smu_set_sleepled(void *xdev, int onoff);
175204270Snwhitehornstatic int	smu_server_mode(SYSCTL_HANDLER_ARGS);
176204692Snwhitehornstatic void	smu_doorbell_intr(void *xdev);
177212054Snwhitehornstatic void	smu_shutdown(void *xdev, int howto);
178204082Snwhitehorn
179194679Snwhitehorn/* where to find the doorbell GPIO */
180194679Snwhitehorn
181194679Snwhitehornstatic device_t	smu_doorbell = NULL;
182194679Snwhitehorn
183194679Snwhitehornstatic device_method_t  smu_methods[] = {
184194679Snwhitehorn	/* Device interface */
185194679Snwhitehorn	DEVMETHOD(device_probe,		smu_probe),
186194679Snwhitehorn	DEVMETHOD(device_attach,	smu_attach),
187205506Snwhitehorn
188205506Snwhitehorn	/* Clock interface */
189205506Snwhitehorn	DEVMETHOD(clock_gettime,	smu_gettime),
190205506Snwhitehorn	DEVMETHOD(clock_settime,	smu_settime),
191208841Snwhitehorn
192208841Snwhitehorn	/* ofw_bus interface */
193208841Snwhitehorn	DEVMETHOD(bus_child_pnpinfo_str,ofw_bus_gen_child_pnpinfo_str),
194208841Snwhitehorn	DEVMETHOD(ofw_bus_get_devinfo,	smu_get_devinfo),
195208841Snwhitehorn	DEVMETHOD(ofw_bus_get_compat,	ofw_bus_gen_get_compat),
196208841Snwhitehorn	DEVMETHOD(ofw_bus_get_model,	ofw_bus_gen_get_model),
197208841Snwhitehorn	DEVMETHOD(ofw_bus_get_name,	ofw_bus_gen_get_name),
198208841Snwhitehorn	DEVMETHOD(ofw_bus_get_node,	ofw_bus_gen_get_node),
199208841Snwhitehorn	DEVMETHOD(ofw_bus_get_type,	ofw_bus_gen_get_type),
200208841Snwhitehorn
201194679Snwhitehorn	{ 0, 0 },
202194679Snwhitehorn};
203194679Snwhitehorn
204194679Snwhitehornstatic driver_t smu_driver = {
205194679Snwhitehorn	"smu",
206194679Snwhitehorn	smu_methods,
207194679Snwhitehorn	sizeof(struct smu_softc)
208194679Snwhitehorn};
209194679Snwhitehorn
210194679Snwhitehornstatic devclass_t smu_devclass;
211194679Snwhitehorn
212194679SnwhitehornDRIVER_MODULE(smu, nexus, smu_driver, smu_devclass, 0, 0);
213227293Sedstatic MALLOC_DEFINE(M_SMU, "smu", "SMU Sensor Information");
214194679Snwhitehorn
215204082Snwhitehorn#define SMU_MAILBOX		0x8000860c
216204692Snwhitehorn#define SMU_FANMGT_INTERVAL	1000 /* ms */
217194679Snwhitehorn
218194679Snwhitehorn/* Command types */
219204082Snwhitehorn#define SMU_ADC			0xd8
220204082Snwhitehorn#define SMU_FAN			0x4a
221232482Sandreast#define SMU_RPM_STATUS		0x01
222232482Sandreast#define SMU_RPM_SETPOINT	0x02
223232482Sandreast#define SMU_PWM_STATUS		0x11
224232482Sandreast#define SMU_PWM_SETPOINT	0x12
225204082Snwhitehorn#define SMU_I2C			0x9a
226204179Snwhitehorn#define  SMU_I2C_SIMPLE		0x00
227204179Snwhitehorn#define  SMU_I2C_NORMAL		0x01
228204179Snwhitehorn#define  SMU_I2C_COMBINED	0x02
229204082Snwhitehorn#define SMU_MISC		0xee
230204179Snwhitehorn#define  SMU_MISC_GET_DATA	0x02
231204179Snwhitehorn#define  SMU_MISC_LED_CTRL	0x04
232204082Snwhitehorn#define SMU_POWER		0xaa
233204270Snwhitehorn#define SMU_POWER_EVENTS	0x8f
234204270Snwhitehorn#define  SMU_PWR_GET_POWERUP	0x00
235204270Snwhitehorn#define  SMU_PWR_SET_POWERUP	0x01
236204270Snwhitehorn#define  SMU_PWR_CLR_POWERUP	0x02
237205506Snwhitehorn#define SMU_RTC			0x8e
238205506Snwhitehorn#define  SMU_RTC_GET		0x81
239205506Snwhitehorn#define  SMU_RTC_SET		0x80
240194679Snwhitehorn
241204270Snwhitehorn/* Power event types */
242204270Snwhitehorn#define SMU_WAKEUP_KEYPRESS	0x01
243204270Snwhitehorn#define SMU_WAKEUP_AC_INSERT	0x02
244204270Snwhitehorn#define SMU_WAKEUP_AC_CHANGE	0x04
245204270Snwhitehorn#define SMU_WAKEUP_RING		0x10
246204270Snwhitehorn
247204082Snwhitehorn/* Data blocks */
248204082Snwhitehorn#define SMU_CPUTEMP_CAL		0x18
249204082Snwhitehorn#define SMU_CPUVOLT_CAL		0x21
250204082Snwhitehorn#define SMU_SLOTPW_CAL		0x78
251204082Snwhitehorn
252204082Snwhitehorn/* Partitions */
253204082Snwhitehorn#define SMU_PARTITION		0x3e
254204082Snwhitehorn#define SMU_PARTITION_LATEST	0x01
255204082Snwhitehorn#define SMU_PARTITION_BASE	0x02
256204082Snwhitehorn#define SMU_PARTITION_UPDATE	0x03
257204082Snwhitehorn
258194679Snwhitehornstatic int
259194679Snwhitehornsmu_probe(device_t dev)
260194679Snwhitehorn{
261194679Snwhitehorn	const char *name = ofw_bus_get_name(dev);
262194679Snwhitehorn
263194679Snwhitehorn	if (strcmp(name, "smu") != 0)
264194679Snwhitehorn		return (ENXIO);
265194679Snwhitehorn
266194679Snwhitehorn	device_set_desc(dev, "Apple System Management Unit");
267194679Snwhitehorn	return (0);
268194679Snwhitehorn}
269194679Snwhitehorn
270194679Snwhitehornstatic void
271194679Snwhitehornsmu_phys_callback(void *xsc, bus_dma_segment_t *segs, int nsegs, int error)
272194679Snwhitehorn{
273194679Snwhitehorn	struct smu_softc *sc = xsc;
274194679Snwhitehorn
275194679Snwhitehorn	sc->sc_cmd_phys = segs[0].ds_addr;
276194679Snwhitehorn}
277194679Snwhitehorn
278194679Snwhitehornstatic int
279194679Snwhitehornsmu_attach(device_t dev)
280194679Snwhitehorn{
281194679Snwhitehorn	struct smu_softc *sc;
282204082Snwhitehorn	phandle_t	node, child;
283204082Snwhitehorn	uint8_t		data[12];
284194679Snwhitehorn
285194679Snwhitehorn	sc = device_get_softc(dev);
286194679Snwhitehorn
287194679Snwhitehorn	mtx_init(&sc->sc_mtx, "smu", NULL, MTX_DEF);
288204692Snwhitehorn	sc->sc_cur_cmd = NULL;
289204692Snwhitehorn	sc->sc_doorbellirqid = -1;
290194679Snwhitehorn
291215100Snwhitehorn	sc->sc_u3 = 0;
292215100Snwhitehorn	if (OF_finddevice("/u3") != -1)
293215100Snwhitehorn		sc->sc_u3 = 1;
294215100Snwhitehorn
295194679Snwhitehorn	/*
296194679Snwhitehorn	 * Map the mailbox area. This should be determined from firmware,
297194679Snwhitehorn	 * but I have not found a simple way to do that.
298194679Snwhitehorn	 */
299194679Snwhitehorn	bus_dma_tag_create(NULL, 16, 0, BUS_SPACE_MAXADDR_32BIT,
300194679Snwhitehorn	    BUS_SPACE_MAXADDR, NULL, NULL, PAGE_SIZE, 1, PAGE_SIZE, 0, NULL,
301194679Snwhitehorn	    NULL, &(sc->sc_dmatag));
302204082Snwhitehorn	sc->sc_bt = &bs_le_tag;
303194679Snwhitehorn	bus_space_map(sc->sc_bt, SMU_MAILBOX, 4, 0, &sc->sc_mailbox);
304194679Snwhitehorn
305194679Snwhitehorn	/*
306194679Snwhitehorn	 * Allocate the command buffer. This can be anywhere in the low 4 GB
307194679Snwhitehorn	 * of memory.
308194679Snwhitehorn	 */
309194679Snwhitehorn	bus_dmamem_alloc(sc->sc_dmatag, (void **)&sc->sc_cmd, BUS_DMA_WAITOK |
310194679Snwhitehorn	    BUS_DMA_ZERO, &sc->sc_cmd_dmamap);
311194679Snwhitehorn	bus_dmamap_load(sc->sc_dmatag, sc->sc_cmd_dmamap,
312194679Snwhitehorn	    sc->sc_cmd, PAGE_SIZE, smu_phys_callback, sc, 0);
313204692Snwhitehorn	STAILQ_INIT(&sc->sc_cmdq);
314194679Snwhitehorn
315194679Snwhitehorn	/*
316194679Snwhitehorn	 * Set up handlers to change CPU voltage when CPU frequency is changed.
317194679Snwhitehorn	 */
318194679Snwhitehorn	EVENTHANDLER_REGISTER(cpufreq_pre_change, smu_cpufreq_pre_change, dev,
319194679Snwhitehorn	    EVENTHANDLER_PRI_ANY);
320194679Snwhitehorn	EVENTHANDLER_REGISTER(cpufreq_post_change, smu_cpufreq_post_change, dev,
321194679Snwhitehorn	    EVENTHANDLER_PRI_ANY);
322194679Snwhitehorn
323232482Sandreast	node = ofw_bus_get_node(dev);
324232482Sandreast
325232482Sandreast	/* Some SMUs have RPM and PWM controlled fans which do not sit
326232482Sandreast	 * under the same node. So we have to attach them separately.
327232482Sandreast	 */
328232482Sandreast	smu_attach_fans(dev, node);
329232482Sandreast
330204082Snwhitehorn	/*
331232482Sandreast	 * Now detect and attach the other child devices.
332204082Snwhitehorn	 */
333204082Snwhitehorn	for (child = OF_child(node); child != 0; child = OF_peer(child)) {
334204082Snwhitehorn		char name[32];
335204082Snwhitehorn		memset(name, 0, sizeof(name));
336204082Snwhitehorn		OF_getprop(child, "name", name, sizeof(name));
337204082Snwhitehorn
338204082Snwhitehorn		if (strncmp(name, "sensors", 8) == 0)
339204082Snwhitehorn			smu_attach_sensors(dev, child);
340208841Snwhitehorn
341208841Snwhitehorn		if (strncmp(name, "smu-i2c-control", 15) == 0)
342208841Snwhitehorn			smu_attach_i2c(dev, child);
343204082Snwhitehorn	}
344204082Snwhitehorn
345208841Snwhitehorn	/* Some SMUs have the I2C children directly under the bus. */
346208841Snwhitehorn	smu_attach_i2c(dev, node);
347208841Snwhitehorn
348204082Snwhitehorn	/*
349204082Snwhitehorn	 * Collect calibration constants.
350204082Snwhitehorn	 */
351204082Snwhitehorn	smu_get_datablock(dev, SMU_CPUTEMP_CAL, data, sizeof(data));
352204082Snwhitehorn	sc->sc_cpu_diode_scale = (data[4] << 8) + data[5];
353204082Snwhitehorn	sc->sc_cpu_diode_offset = (data[6] << 8) + data[7];
354204082Snwhitehorn
355204082Snwhitehorn	smu_get_datablock(dev, SMU_CPUVOLT_CAL, data, sizeof(data));
356204082Snwhitehorn	sc->sc_cpu_volt_scale = (data[4] << 8) + data[5];
357204082Snwhitehorn	sc->sc_cpu_volt_offset = (data[6] << 8) + data[7];
358204082Snwhitehorn	sc->sc_cpu_curr_scale = (data[8] << 8) + data[9];
359204082Snwhitehorn	sc->sc_cpu_curr_offset = (data[10] << 8) + data[11];
360204082Snwhitehorn
361204082Snwhitehorn	smu_get_datablock(dev, SMU_SLOTPW_CAL, data, sizeof(data));
362204082Snwhitehorn	sc->sc_slots_pow_scale = (data[4] << 8) + data[5];
363204082Snwhitehorn	sc->sc_slots_pow_offset = (data[6] << 8) + data[7];
364204082Snwhitehorn
365204180Snwhitehorn	/*
366204218Snwhitehorn	 * Set up LED interface
367204218Snwhitehorn	 */
368204218Snwhitehorn	sc->sc_leddev = led_create(smu_set_sleepled, dev, "sleepled");
369204218Snwhitehorn
370204270Snwhitehorn	/*
371204270Snwhitehorn	 * Reset on power loss behavior
372204270Snwhitehorn	 */
373204270Snwhitehorn
374204270Snwhitehorn	SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
375204270Snwhitehorn            SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
376204270Snwhitehorn	    "server_mode", CTLTYPE_INT | CTLFLAG_RW, dev, 0,
377204270Snwhitehorn	    smu_server_mode, "I", "Enable reboot after power failure");
378204270Snwhitehorn
379204692Snwhitehorn	/*
380204692Snwhitehorn	 * Set up doorbell interrupt.
381204692Snwhitehorn	 */
382204692Snwhitehorn	sc->sc_doorbellirqid = 0;
383204692Snwhitehorn	sc->sc_doorbellirq = bus_alloc_resource_any(smu_doorbell, SYS_RES_IRQ,
384204692Snwhitehorn	    &sc->sc_doorbellirqid, RF_ACTIVE);
385204692Snwhitehorn	bus_setup_intr(smu_doorbell, sc->sc_doorbellirq,
386204692Snwhitehorn	    INTR_TYPE_MISC | INTR_MPSAFE, NULL, smu_doorbell_intr, dev,
387204692Snwhitehorn	    &sc->sc_doorbellirqcookie);
388204692Snwhitehorn	powerpc_config_intr(rman_get_start(sc->sc_doorbellirq),
389204692Snwhitehorn	    INTR_TRIGGER_EDGE, INTR_POLARITY_LOW);
390204692Snwhitehorn
391205506Snwhitehorn	/*
392205506Snwhitehorn	 * Connect RTC interface.
393205506Snwhitehorn	 */
394205506Snwhitehorn	clock_register(dev, 1000);
395205506Snwhitehorn
396212054Snwhitehorn	/*
397212054Snwhitehorn	 * Learn about shutdown events
398212054Snwhitehorn	 */
399212054Snwhitehorn	EVENTHANDLER_REGISTER(shutdown_final, smu_shutdown, dev,
400212054Snwhitehorn	    SHUTDOWN_PRI_LAST);
401212054Snwhitehorn
402208841Snwhitehorn	return (bus_generic_attach(dev));
403194679Snwhitehorn}
404194679Snwhitehorn
405208841Snwhitehornstatic const struct ofw_bus_devinfo *
406208841Snwhitehornsmu_get_devinfo(device_t bus, device_t dev)
407208841Snwhitehorn{
408208841Snwhitehorn
409208841Snwhitehorn	return (device_get_ivars(dev));
410208841Snwhitehorn}
411208841Snwhitehorn
412204692Snwhitehornstatic void
413204692Snwhitehornsmu_send_cmd(device_t dev, struct smu_cmd *cmd)
414194679Snwhitehorn{
415194679Snwhitehorn	struct smu_softc *sc;
416194679Snwhitehorn
417194679Snwhitehorn	sc = device_get_softc(dev);
418194679Snwhitehorn
419204692Snwhitehorn	mtx_assert(&sc->sc_mtx, MA_OWNED);
420194679Snwhitehorn
421215100Snwhitehorn	if (sc->sc_u3)
422215100Snwhitehorn		powerpc_pow_enabled = 0; /* SMU cannot work if we go to NAP */
423215100Snwhitehorn
424204692Snwhitehorn	sc->sc_cur_cmd = cmd;
425204082Snwhitehorn
426194679Snwhitehorn	/* Copy the command to the mailbox */
427204692Snwhitehorn	sc->sc_cmd->cmd = cmd->cmd;
428204692Snwhitehorn	sc->sc_cmd->len = cmd->len;
429204692Snwhitehorn	memcpy(sc->sc_cmd->data, cmd->data, sizeof(cmd->data));
430194679Snwhitehorn	bus_dmamap_sync(sc->sc_dmatag, sc->sc_cmd_dmamap, BUS_DMASYNC_PREWRITE);
431194679Snwhitehorn	bus_space_write_4(sc->sc_bt, sc->sc_mailbox, 0, sc->sc_cmd_phys);
432194679Snwhitehorn
433204082Snwhitehorn	/* Flush the cacheline it is in -- SMU bypasses the cache */
434204082Snwhitehorn	__asm __volatile("sync; dcbf 0,%0; sync" :: "r"(sc->sc_cmd): "memory");
435194679Snwhitehorn
436194679Snwhitehorn	/* Ring SMU doorbell */
437194679Snwhitehorn	macgpio_write(smu_doorbell, GPIO_DDR_OUTPUT);
438204692Snwhitehorn}
439194679Snwhitehorn
440204692Snwhitehornstatic void
441204692Snwhitehornsmu_doorbell_intr(void *xdev)
442204692Snwhitehorn{
443204692Snwhitehorn	device_t smu;
444204692Snwhitehorn	struct smu_softc *sc;
445204692Snwhitehorn	int doorbell_ack;
446194679Snwhitehorn
447204692Snwhitehorn	smu = xdev;
448204692Snwhitehorn	doorbell_ack = macgpio_read(smu_doorbell);
449204692Snwhitehorn	sc = device_get_softc(smu);
450204692Snwhitehorn
451204692Snwhitehorn	if (doorbell_ack != (GPIO_DDR_OUTPUT | GPIO_LEVEL_RO | GPIO_DATA))
452204692Snwhitehorn		return;
453204692Snwhitehorn
454204692Snwhitehorn	mtx_lock(&sc->sc_mtx);
455204692Snwhitehorn
456204692Snwhitehorn	if (sc->sc_cur_cmd == NULL)	/* spurious */
457204692Snwhitehorn		goto done;
458204692Snwhitehorn
459194679Snwhitehorn	/* Check result. First invalidate the cache again... */
460194679Snwhitehorn	__asm __volatile("dcbf 0,%0; sync" :: "r"(sc->sc_cmd) : "memory");
461194679Snwhitehorn
462194679Snwhitehorn	bus_dmamap_sync(sc->sc_dmatag, sc->sc_cmd_dmamap, BUS_DMASYNC_POSTREAD);
463194679Snwhitehorn
464204692Snwhitehorn	sc->sc_cur_cmd->cmd = sc->sc_cmd->cmd;
465204692Snwhitehorn	sc->sc_cur_cmd->len = sc->sc_cmd->len;
466204692Snwhitehorn	memcpy(sc->sc_cur_cmd->data, sc->sc_cmd->data,
467204692Snwhitehorn	    sizeof(sc->sc_cmd->data));
468204692Snwhitehorn	wakeup(sc->sc_cur_cmd);
469204692Snwhitehorn	sc->sc_cur_cmd = NULL;
470215100Snwhitehorn	if (sc->sc_u3)
471215100Snwhitehorn		powerpc_pow_enabled = 1;
472194679Snwhitehorn
473204692Snwhitehorn    done:
474204692Snwhitehorn	/* Queue next command if one is pending */
475204692Snwhitehorn	if (STAILQ_FIRST(&sc->sc_cmdq) != NULL) {
476204692Snwhitehorn		sc->sc_cur_cmd = STAILQ_FIRST(&sc->sc_cmdq);
477204692Snwhitehorn		STAILQ_REMOVE_HEAD(&sc->sc_cmdq, cmd_q);
478204692Snwhitehorn		smu_send_cmd(smu, sc->sc_cur_cmd);
479204692Snwhitehorn	}
480204082Snwhitehorn
481204692Snwhitehorn	mtx_unlock(&sc->sc_mtx);
482204692Snwhitehorn}
483204082Snwhitehorn
484204692Snwhitehornstatic int
485204692Snwhitehornsmu_run_cmd(device_t dev, struct smu_cmd *cmd, int wait)
486204692Snwhitehorn{
487204692Snwhitehorn	struct smu_softc *sc;
488204692Snwhitehorn	uint8_t cmd_code;
489204692Snwhitehorn	int error;
490204692Snwhitehorn
491204692Snwhitehorn	sc = device_get_softc(dev);
492204692Snwhitehorn	cmd_code = cmd->cmd;
493204692Snwhitehorn
494204692Snwhitehorn	mtx_lock(&sc->sc_mtx);
495204692Snwhitehorn	if (sc->sc_cur_cmd != NULL) {
496204692Snwhitehorn		STAILQ_INSERT_TAIL(&sc->sc_cmdq, cmd, cmd_q);
497204692Snwhitehorn	} else
498204692Snwhitehorn		smu_send_cmd(dev, cmd);
499194679Snwhitehorn	mtx_unlock(&sc->sc_mtx);
500194679Snwhitehorn
501204692Snwhitehorn	if (!wait)
502204692Snwhitehorn		return (0);
503204692Snwhitehorn
504204692Snwhitehorn	if (sc->sc_doorbellirqid < 0) {
505204692Snwhitehorn		/* Poll if the IRQ has not been set up yet */
506204692Snwhitehorn		do {
507204692Snwhitehorn			DELAY(50);
508204692Snwhitehorn			smu_doorbell_intr(dev);
509204692Snwhitehorn		} while (sc->sc_cur_cmd != NULL);
510204692Snwhitehorn	} else {
511204692Snwhitehorn		/* smu_doorbell_intr will wake us when the command is ACK'ed */
512204692Snwhitehorn		error = tsleep(cmd, 0, "smu", 800 * hz / 1000);
513204692Snwhitehorn		if (error != 0)
514204692Snwhitehorn			smu_doorbell_intr(dev);	/* One last chance */
515204692Snwhitehorn
516204692Snwhitehorn		if (error != 0) {
517204692Snwhitehorn		    mtx_lock(&sc->sc_mtx);
518204692Snwhitehorn		    if (cmd->cmd == cmd_code) {	/* Never processed */
519204692Snwhitehorn			/* Abort this command if we timed out */
520204692Snwhitehorn			if (sc->sc_cur_cmd == cmd)
521204692Snwhitehorn				sc->sc_cur_cmd = NULL;
522204692Snwhitehorn			else
523204692Snwhitehorn				STAILQ_REMOVE(&sc->sc_cmdq, cmd, smu_cmd,
524204692Snwhitehorn				    cmd_q);
525204692Snwhitehorn			mtx_unlock(&sc->sc_mtx);
526204692Snwhitehorn			return (error);
527204692Snwhitehorn		    }
528204692Snwhitehorn		    error = 0;
529204692Snwhitehorn		    mtx_unlock(&sc->sc_mtx);
530204692Snwhitehorn		}
531204692Snwhitehorn	}
532204692Snwhitehorn
533204692Snwhitehorn	/* SMU acks the command by inverting the command bits */
534204692Snwhitehorn	if (cmd->cmd == ((~cmd_code) & 0xff))
535204692Snwhitehorn		error = 0;
536204692Snwhitehorn	else
537204692Snwhitehorn		error = EIO;
538204692Snwhitehorn
539204692Snwhitehorn	return (error);
540194679Snwhitehorn}
541194679Snwhitehorn
542204082Snwhitehornstatic int
543204082Snwhitehornsmu_get_datablock(device_t dev, int8_t id, uint8_t *buf, size_t len)
544204082Snwhitehorn{
545204082Snwhitehorn	struct smu_cmd cmd;
546204082Snwhitehorn	uint8_t addr[4];
547204082Snwhitehorn
548204082Snwhitehorn	cmd.cmd = SMU_PARTITION;
549204082Snwhitehorn	cmd.len = 2;
550204082Snwhitehorn	cmd.data[0] = SMU_PARTITION_LATEST;
551204082Snwhitehorn	cmd.data[1] = id;
552204082Snwhitehorn
553204692Snwhitehorn	smu_run_cmd(dev, &cmd, 1);
554204082Snwhitehorn
555204082Snwhitehorn	addr[0] = addr[1] = 0;
556204082Snwhitehorn	addr[2] = cmd.data[0];
557204082Snwhitehorn	addr[3] = cmd.data[1];
558204082Snwhitehorn
559204082Snwhitehorn	cmd.cmd = SMU_MISC;
560204082Snwhitehorn	cmd.len = 7;
561204082Snwhitehorn	cmd.data[0] = SMU_MISC_GET_DATA;
562204082Snwhitehorn	cmd.data[1] = sizeof(addr);
563204082Snwhitehorn	memcpy(&cmd.data[2], addr, sizeof(addr));
564204082Snwhitehorn	cmd.data[6] = len;
565204082Snwhitehorn
566204692Snwhitehorn	smu_run_cmd(dev, &cmd, 1);
567204082Snwhitehorn	memcpy(buf, cmd.data, len);
568204082Snwhitehorn	return (0);
569204082Snwhitehorn}
570204082Snwhitehorn
571194679Snwhitehornstatic void
572194679Snwhitehornsmu_slew_cpu_voltage(device_t dev, int to)
573194679Snwhitehorn{
574194679Snwhitehorn	struct smu_cmd cmd;
575194679Snwhitehorn
576194679Snwhitehorn	cmd.cmd = SMU_POWER;
577194679Snwhitehorn	cmd.len = 8;
578194679Snwhitehorn	cmd.data[0] = 'V';
579194679Snwhitehorn	cmd.data[1] = 'S';
580194679Snwhitehorn	cmd.data[2] = 'L';
581194679Snwhitehorn	cmd.data[3] = 'E';
582194679Snwhitehorn	cmd.data[4] = 'W';
583194679Snwhitehorn	cmd.data[5] = 0xff;
584194679Snwhitehorn	cmd.data[6] = 1;
585194679Snwhitehorn	cmd.data[7] = to;
586194679Snwhitehorn
587204692Snwhitehorn	smu_run_cmd(dev, &cmd, 1);
588194679Snwhitehorn}
589194679Snwhitehorn
590194679Snwhitehornstatic void
591194679Snwhitehornsmu_cpufreq_pre_change(device_t dev, const struct cf_level *level)
592194679Snwhitehorn{
593194679Snwhitehorn	/*
594194679Snwhitehorn	 * Make sure the CPU voltage is raised before we raise
595194679Snwhitehorn	 * the clock.
596194679Snwhitehorn	 */
597194679Snwhitehorn
598194679Snwhitehorn	if (level->rel_set[0].freq == 10000 /* max */)
599194679Snwhitehorn		smu_slew_cpu_voltage(dev, 0);
600194679Snwhitehorn}
601194679Snwhitehorn
602194679Snwhitehornstatic void
603194679Snwhitehornsmu_cpufreq_post_change(device_t dev, const struct cf_level *level)
604194679Snwhitehorn{
605194679Snwhitehorn	/* We are safe to reduce CPU voltage after a downward transition */
606194679Snwhitehorn
607194679Snwhitehorn	if (level->rel_set[0].freq < 10000 /* max */)
608194679Snwhitehorn		smu_slew_cpu_voltage(dev, 1); /* XXX: 1/4 voltage for 970MP? */
609194679Snwhitehorn}
610194679Snwhitehorn
611194679Snwhitehorn/* Routines for probing the SMU doorbell GPIO */
612194679Snwhitehornstatic int doorbell_probe(device_t dev);
613194679Snwhitehornstatic int doorbell_attach(device_t dev);
614194679Snwhitehorn
615194679Snwhitehornstatic device_method_t  doorbell_methods[] = {
616194679Snwhitehorn	/* Device interface */
617194679Snwhitehorn	DEVMETHOD(device_probe,		doorbell_probe),
618194679Snwhitehorn	DEVMETHOD(device_attach,	doorbell_attach),
619194679Snwhitehorn	{ 0, 0 },
620194679Snwhitehorn};
621194679Snwhitehorn
622194679Snwhitehornstatic driver_t doorbell_driver = {
623194679Snwhitehorn	"smudoorbell",
624194679Snwhitehorn	doorbell_methods,
625194679Snwhitehorn	0
626194679Snwhitehorn};
627194679Snwhitehorn
628194679Snwhitehornstatic devclass_t doorbell_devclass;
629194679Snwhitehorn
630194679SnwhitehornDRIVER_MODULE(smudoorbell, macgpio, doorbell_driver, doorbell_devclass, 0, 0);
631194679Snwhitehorn
632194679Snwhitehornstatic int
633194679Snwhitehorndoorbell_probe(device_t dev)
634194679Snwhitehorn{
635194679Snwhitehorn	const char *name = ofw_bus_get_name(dev);
636194679Snwhitehorn
637194679Snwhitehorn	if (strcmp(name, "smu-doorbell") != 0)
638194679Snwhitehorn		return (ENXIO);
639194679Snwhitehorn
640194679Snwhitehorn	device_set_desc(dev, "SMU Doorbell GPIO");
641194679Snwhitehorn	device_quiet(dev);
642194679Snwhitehorn	return (0);
643194679Snwhitehorn}
644194679Snwhitehorn
645194679Snwhitehornstatic int
646194679Snwhitehorndoorbell_attach(device_t dev)
647194679Snwhitehorn{
648194679Snwhitehorn	smu_doorbell = dev;
649194679Snwhitehorn	return (0);
650194679Snwhitehorn}
651204082Snwhitehorn
652204082Snwhitehorn/*
653204082Snwhitehorn * Sensor and fan management
654204082Snwhitehorn */
655204082Snwhitehorn
656204082Snwhitehornstatic int
657222429Snwhitehornsmu_fan_set_rpm(struct smu_fan *fan, int rpm)
658204082Snwhitehorn{
659222429Snwhitehorn	device_t smu = fan->dev;
660204082Snwhitehorn	struct smu_cmd cmd;
661204179Snwhitehorn	int error;
662204082Snwhitehorn
663204082Snwhitehorn	cmd.cmd = SMU_FAN;
664204179Snwhitehorn	error = EIO;
665204082Snwhitehorn
666204179Snwhitehorn	/* Clamp to allowed range */
667222429Snwhitehorn	rpm = max(fan->fan.min_rpm, rpm);
668222429Snwhitehorn	rpm = min(fan->fan.max_rpm, rpm);
669204179Snwhitehorn
670204082Snwhitehorn	/*
671204179Snwhitehorn	 * Apple has two fan control mechanisms. We can't distinguish
672204179Snwhitehorn	 * them except by seeing if the new one fails. If the new one
673204179Snwhitehorn	 * fails, use the old one.
674204082Snwhitehorn	 */
675204179Snwhitehorn
676204179Snwhitehorn	if (!fan->old_style) {
677204179Snwhitehorn		cmd.len = 4;
678204179Snwhitehorn		cmd.data[0] = 0x30;
679204179Snwhitehorn		cmd.data[1] = fan->reg;
680204179Snwhitehorn		cmd.data[2] = (rpm >> 8) & 0xff;
681204179Snwhitehorn		cmd.data[3] = rpm & 0xff;
682232482Sandreast
683204692Snwhitehorn		error = smu_run_cmd(smu, &cmd, 1);
684222430Snwhitehorn		if (error && error != EWOULDBLOCK)
685204179Snwhitehorn			fan->old_style = 1;
686204179Snwhitehorn	}
687204082Snwhitehorn
688204179Snwhitehorn	if (fan->old_style) {
689204179Snwhitehorn		cmd.len = 14;
690232482Sandreast		cmd.data[0] = 0x00; /* RPM fan. */
691204179Snwhitehorn		cmd.data[1] = 1 << fan->reg;
692204179Snwhitehorn		cmd.data[2 + 2*fan->reg] = (rpm >> 8) & 0xff;
693204179Snwhitehorn		cmd.data[3 + 2*fan->reg] = rpm & 0xff;
694204692Snwhitehorn		error = smu_run_cmd(smu, &cmd, 1);
695204179Snwhitehorn	}
696204082Snwhitehorn
697204180Snwhitehorn	if (error == 0)
698204180Snwhitehorn		fan->setpoint = rpm;
699204180Snwhitehorn
700204179Snwhitehorn	return (error);
701204082Snwhitehorn}
702204082Snwhitehorn
703204082Snwhitehornstatic int
704222429Snwhitehornsmu_fan_read_rpm(struct smu_fan *fan)
705204082Snwhitehorn{
706222429Snwhitehorn	device_t smu = fan->dev;
707204082Snwhitehorn	struct smu_cmd cmd;
708208167Snwhitehorn	int rpm, error;
709204082Snwhitehorn
710208167Snwhitehorn	if (!fan->old_style) {
711208167Snwhitehorn		cmd.cmd = SMU_FAN;
712208167Snwhitehorn		cmd.len = 2;
713208167Snwhitehorn		cmd.data[0] = 0x31;
714208167Snwhitehorn		cmd.data[1] = fan->reg;
715204082Snwhitehorn
716208167Snwhitehorn		error = smu_run_cmd(smu, &cmd, 1);
717222430Snwhitehorn		if (error && error != EWOULDBLOCK)
718208167Snwhitehorn			fan->old_style = 1;
719204082Snwhitehorn
720208167Snwhitehorn		rpm = (cmd.data[0] << 8) | cmd.data[1];
721208167Snwhitehorn	}
722208167Snwhitehorn
723208167Snwhitehorn	if (fan->old_style) {
724208167Snwhitehorn		cmd.cmd = SMU_FAN;
725208167Snwhitehorn		cmd.len = 1;
726232482Sandreast		cmd.data[0] = SMU_RPM_STATUS;
727208167Snwhitehorn
728208167Snwhitehorn		error = smu_run_cmd(smu, &cmd, 1);
729208167Snwhitehorn		if (error)
730208167Snwhitehorn			return (error);
731208167Snwhitehorn
732208167Snwhitehorn		rpm = (cmd.data[fan->reg*2+1] << 8) | cmd.data[fan->reg*2+2];
733208167Snwhitehorn	}
734208167Snwhitehorn
735208167Snwhitehorn	return (rpm);
736204082Snwhitehorn}
737232482Sandreaststatic int
738232482Sandreastsmu_fan_set_pwm(struct smu_fan *fan, int pwm)
739232482Sandreast{
740232482Sandreast	device_t smu = fan->dev;
741232482Sandreast	struct smu_cmd cmd;
742232482Sandreast	int error;
743204082Snwhitehorn
744232482Sandreast	cmd.cmd = SMU_FAN;
745232482Sandreast	error = EIO;
746232482Sandreast
747232482Sandreast	/* Clamp to allowed range */
748232482Sandreast	pwm = max(fan->fan.min_rpm, pwm);
749232482Sandreast	pwm = min(fan->fan.max_rpm, pwm);
750232482Sandreast
751232482Sandreast	/*
752232482Sandreast	 * Apple has two fan control mechanisms. We can't distinguish
753232482Sandreast	 * them except by seeing if the new one fails. If the new one
754232482Sandreast	 * fails, use the old one.
755232482Sandreast	 */
756232482Sandreast
757232482Sandreast	if (!fan->old_style) {
758232482Sandreast		cmd.len = 4;
759232482Sandreast		cmd.data[0] = 0x30;
760232482Sandreast		cmd.data[1] = fan->reg;
761232482Sandreast		cmd.data[2] = (pwm >> 8) & 0xff;
762232482Sandreast		cmd.data[3] = pwm & 0xff;
763232482Sandreast
764232482Sandreast		error = smu_run_cmd(smu, &cmd, 1);
765232482Sandreast		if (error && error != EWOULDBLOCK)
766232482Sandreast			fan->old_style = 1;
767232482Sandreast	}
768232482Sandreast
769232482Sandreast	if (fan->old_style) {
770232482Sandreast		cmd.len = 14;
771232482Sandreast		cmd.data[0] = 0x10; /* PWM fan. */
772232482Sandreast		cmd.data[1] = 1 << fan->reg;
773232482Sandreast		cmd.data[2 + 2*fan->reg] = (pwm >> 8) & 0xff;
774232482Sandreast		cmd.data[3 + 2*fan->reg] = pwm & 0xff;
775232482Sandreast		error = smu_run_cmd(smu, &cmd, 1);
776232482Sandreast	}
777232482Sandreast
778232482Sandreast	if (error == 0)
779232482Sandreast		fan->setpoint = pwm;
780232482Sandreast
781232482Sandreast	return (error);
782232482Sandreast}
783232482Sandreast
784204082Snwhitehornstatic int
785232482Sandreastsmu_fan_read_pwm(struct smu_fan *fan, int *pwm, int *rpm)
786232482Sandreast{
787232482Sandreast	device_t smu = fan->dev;
788232482Sandreast	struct smu_cmd cmd;
789232482Sandreast	int error;
790232482Sandreast
791232482Sandreast	if (!fan->old_style) {
792232482Sandreast		cmd.cmd = SMU_FAN;
793232482Sandreast		cmd.len = 2;
794232482Sandreast		cmd.data[0] = 0x31;
795232482Sandreast		cmd.data[1] = fan->reg;
796232482Sandreast
797232482Sandreast		error = smu_run_cmd(smu, &cmd, 1);
798232482Sandreast		if (error && error != EWOULDBLOCK)
799232482Sandreast			fan->old_style = 1;
800232482Sandreast
801232482Sandreast		*rpm = (cmd.data[0] << 8) | cmd.data[1];
802232482Sandreast	}
803232482Sandreast
804232482Sandreast	if (fan->old_style) {
805232482Sandreast		cmd.cmd = SMU_FAN;
806232482Sandreast		cmd.len = 1;
807232482Sandreast		cmd.data[0] = SMU_PWM_STATUS;
808232482Sandreast
809232482Sandreast		error = smu_run_cmd(smu, &cmd, 1);
810232482Sandreast		if (error)
811232482Sandreast			return (error);
812232482Sandreast
813232482Sandreast		*rpm = (cmd.data[fan->reg*2+1] << 8) | cmd.data[fan->reg*2+2];
814232482Sandreast	}
815232482Sandreast	if (fan->old_style) {
816232482Sandreast		cmd.cmd = SMU_FAN;
817232482Sandreast		cmd.len = 14;
818232482Sandreast		cmd.data[0] = SMU_PWM_SETPOINT;
819232482Sandreast		cmd.data[1] = 1 << fan->reg;
820232482Sandreast
821232482Sandreast		error = smu_run_cmd(smu, &cmd, 1);
822232482Sandreast		if (error)
823232482Sandreast			return (error);
824232482Sandreast
825232482Sandreast		*pwm = cmd.data[fan->reg*2+2];
826232482Sandreast	}
827232482Sandreast	return (0);
828232482Sandreast}
829232482Sandreast
830232482Sandreaststatic int
831204082Snwhitehornsmu_fanrpm_sysctl(SYSCTL_HANDLER_ARGS)
832204082Snwhitehorn{
833204082Snwhitehorn	device_t smu;
834204082Snwhitehorn	struct smu_softc *sc;
835204082Snwhitehorn	struct smu_fan *fan;
836232482Sandreast	int pwm = 0, rpm, error = 0;
837204082Snwhitehorn
838204082Snwhitehorn	smu = arg1;
839204082Snwhitehorn	sc = device_get_softc(smu);
840232482Sandreast	fan = &sc->sc_fans[arg2 & 0xff];
841204082Snwhitehorn
842232482Sandreast	if (fan->type == SMU_FAN_RPM) {
843232482Sandreast		rpm = smu_fan_read_rpm(fan);
844232482Sandreast		if (rpm < 0)
845232482Sandreast			return (rpm);
846208167Snwhitehorn
847232482Sandreast		error = sysctl_handle_int(oidp, &rpm, 0, req);
848232482Sandreast	} else {
849232482Sandreast		error = smu_fan_read_pwm(fan, &pwm, &rpm);
850232482Sandreast		if (error < 0)
851232482Sandreast			return (EIO);
852204082Snwhitehorn
853232482Sandreast		switch (arg2 & 0xff00) {
854232482Sandreast		case SMU_PWM_SYSCTL_PWM:
855232482Sandreast			error = sysctl_handle_int(oidp, &pwm, 0, req);
856232482Sandreast			break;
857232482Sandreast		case SMU_PWM_SYSCTL_RPM:
858232482Sandreast			error = sysctl_handle_int(oidp, &rpm, 0, req);
859232482Sandreast			break;
860232482Sandreast		default:
861232482Sandreast			/* This should never happen */
862232482Sandreast			return (EINVAL);
863232482Sandreast		};
864232482Sandreast	}
865232482Sandreast	/* We can only read the RPM from a PWM controlled fan, so return. */
866232482Sandreast	if ((arg2 & 0xff00) == SMU_PWM_SYSCTL_RPM)
867232482Sandreast		return (0);
868232482Sandreast
869204082Snwhitehorn	if (error || !req->newptr)
870204082Snwhitehorn		return (error);
871204082Snwhitehorn
872204180Snwhitehorn	sc->sc_lastuserchange = time_uptime;
873204082Snwhitehorn
874232482Sandreast	if (fan->type == SMU_FAN_RPM)
875232482Sandreast		return (smu_fan_set_rpm(fan, rpm));
876232482Sandreast	else
877232482Sandreast		return (smu_fan_set_pwm(fan, pwm));
878204082Snwhitehorn}
879204082Snwhitehorn
880204082Snwhitehornstatic void
881232482Sandreastsmu_fill_fan_prop(device_t dev, phandle_t child, int id)
882232482Sandreast{
883232482Sandreast	struct smu_fan *fan;
884232482Sandreast	struct smu_softc *sc;
885232482Sandreast	char type[32];
886232482Sandreast
887232482Sandreast	sc = device_get_softc(dev);
888232482Sandreast	fan = &sc->sc_fans[id];
889232482Sandreast
890232482Sandreast	OF_getprop(child, "device_type", type, sizeof(type));
891232482Sandreast	/* We have either RPM or PWM controlled fans. */
892232482Sandreast	if (strcmp(type, "fan-rpm-control") == 0)
893232482Sandreast		fan->type = SMU_FAN_RPM;
894232482Sandreast	else
895232482Sandreast		fan->type = SMU_FAN_PWM;
896232482Sandreast
897232482Sandreast	fan->dev = dev;
898232482Sandreast	fan->old_style = 0;
899232482Sandreast	OF_getprop(child, "reg", &fan->reg,
900232482Sandreast		   sizeof(cell_t));
901232482Sandreast	OF_getprop(child, "min-value", &fan->fan.min_rpm,
902232482Sandreast		   sizeof(int));
903232482Sandreast	OF_getprop(child, "max-value", &fan->fan.max_rpm,
904232482Sandreast		   sizeof(int));
905232482Sandreast	OF_getprop(child, "zone", &fan->fan.zone,
906232482Sandreast		   sizeof(int));
907232482Sandreast
908232482Sandreast	if (OF_getprop(child, "unmanaged-value",
909232482Sandreast		       &fan->fan.default_rpm,
910232482Sandreast		       sizeof(int)) != sizeof(int))
911232482Sandreast		fan->fan.default_rpm = fan->fan.max_rpm;
912232482Sandreast
913232482Sandreast	OF_getprop(child, "location", fan->fan.name,
914232482Sandreast		   sizeof(fan->fan.name));
915232482Sandreast
916232482Sandreast	if (fan->type == SMU_FAN_RPM)
917232482Sandreast		fan->setpoint = smu_fan_read_rpm(fan);
918232482Sandreast	else
919232482Sandreast		smu_fan_read_pwm(fan, &fan->setpoint, &fan->rpm);
920232482Sandreast}
921232482Sandreast
922232482Sandreast/* On the first call count the number of fans. In the second call,
923232482Sandreast * after allocating the fan struct, fill the properties of the fans.
924232482Sandreast */
925232482Sandreaststatic int
926232482Sandreastsmu_count_fans(device_t dev)
927232482Sandreast{
928232482Sandreast	struct smu_softc *sc;
929232482Sandreast	phandle_t child, node, root;
930232482Sandreast	int nfans = 0;
931232482Sandreast
932232482Sandreast	node = ofw_bus_get_node(dev);
933232482Sandreast	sc = device_get_softc(dev);
934232482Sandreast
935232482Sandreast	/* First find the fanroots and count the number of fans. */
936232482Sandreast	for (root = OF_child(node); root != 0; root = OF_peer(root)) {
937232482Sandreast		char name[32];
938232482Sandreast		memset(name, 0, sizeof(name));
939232482Sandreast		OF_getprop(root, "name", name, sizeof(name));
940232482Sandreast		if (strncmp(name, "rpm-fans", 9) == 0 ||
941232482Sandreast		    strncmp(name, "pwm-fans", 9) == 0 ||
942232482Sandreast		    strncmp(name, "fans", 5) == 0)
943232482Sandreast			for (child = OF_child(root); child != 0;
944232482Sandreast			     child = OF_peer(child)) {
945232482Sandreast				nfans++;
946232482Sandreast				/* When allocated, fill the fan properties. */
947232482Sandreast				if (sc->sc_fans != NULL)
948232482Sandreast					smu_fill_fan_prop(dev, child,
949232482Sandreast							  nfans - 1);
950232482Sandreast			}
951232482Sandreast	}
952232482Sandreast	if (nfans == 0) {
953232482Sandreast		device_printf(dev, "WARNING: No fans detected!\n");
954232482Sandreast		return (0);
955232482Sandreast	}
956232482Sandreast	return (nfans);
957232482Sandreast}
958232482Sandreast
959232482Sandreaststatic void
960204082Snwhitehornsmu_attach_fans(device_t dev, phandle_t fanroot)
961204082Snwhitehorn{
962204082Snwhitehorn	struct smu_fan *fan;
963204082Snwhitehorn	struct smu_softc *sc;
964204082Snwhitehorn	struct sysctl_oid *oid, *fanroot_oid;
965204082Snwhitehorn	struct sysctl_ctx_list *ctx;
966232482Sandreast	char sysctl_name[32];
967232482Sandreast	int i, j;
968204082Snwhitehorn
969204082Snwhitehorn	sc = device_get_softc(dev);
970204082Snwhitehorn
971232482Sandreast	/* Get the number of fans. */
972232482Sandreast	sc->sc_nfans = smu_count_fans(dev);
973232482Sandreast	if (sc->sc_nfans == 0)
974204082Snwhitehorn		return;
975204082Snwhitehorn
976232482Sandreast	/* Now we're able to allocate memory for the fans struct. */
977204082Snwhitehorn	sc->sc_fans = malloc(sc->sc_nfans * sizeof(struct smu_fan), M_SMU,
978204082Snwhitehorn	    M_WAITOK | M_ZERO);
979204082Snwhitehorn
980232482Sandreast	/* Now fill in the properties. */
981232482Sandreast	smu_count_fans(dev);
982232482Sandreast
983232482Sandreast	/* Register fans with pmac_thermal */
984232482Sandreast	for (i = 0; i < sc->sc_nfans; i++)
985232482Sandreast		pmac_thermal_fan_register(&sc->sc_fans[i].fan);
986204082Snwhitehorn
987204082Snwhitehorn	ctx = device_get_sysctl_ctx(dev);
988204082Snwhitehorn	fanroot_oid = SYSCTL_ADD_NODE(ctx,
989204082Snwhitehorn	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "fans",
990204082Snwhitehorn	    CTLFLAG_RD, 0, "SMU Fan Information");
991204082Snwhitehorn
992232482Sandreast	/* Add sysctls */
993232482Sandreast	for (i = 0; i < sc->sc_nfans; i++) {
994232482Sandreast		fan = &sc->sc_fans[i];
995232482Sandreast		for (j = 0; j < strlen(fan->fan.name); j++) {
996232482Sandreast			sysctl_name[j] = tolower(fan->fan.name[j]);
997232482Sandreast			if (isspace(sysctl_name[j]))
998232482Sandreast				sysctl_name[j] = '_';
999232482Sandreast		}
1000232482Sandreast		sysctl_name[j] = 0;
1001232482Sandreast		if (fan->type == SMU_FAN_RPM) {
1002232482Sandreast			oid = SYSCTL_ADD_NODE(ctx,
1003232482Sandreast					      SYSCTL_CHILDREN(fanroot_oid),
1004232482Sandreast					      OID_AUTO, sysctl_name,
1005232482Sandreast					      CTLFLAG_RD, 0, "Fan Information");
1006232482Sandreast			SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(oid), OID_AUTO,
1007232482Sandreast				       "minrpm", CTLTYPE_INT | CTLFLAG_RD,
1008232482Sandreast				       &fan->fan.min_rpm, sizeof(int),
1009232482Sandreast				       "Minimum allowed RPM");
1010232482Sandreast			SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(oid), OID_AUTO,
1011232482Sandreast				       "maxrpm", CTLTYPE_INT | CTLFLAG_RD,
1012232482Sandreast				       &fan->fan.max_rpm, sizeof(int),
1013232482Sandreast				       "Maximum allowed RPM");
1014232482Sandreast			SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO,
1015232482Sandreast					"rpm",CTLTYPE_INT | CTLFLAG_RW |
1016232482Sandreast					CTLFLAG_MPSAFE, dev, i,
1017232482Sandreast					smu_fanrpm_sysctl, "I", "Fan RPM");
1018204082Snwhitehorn
1019232482Sandreast			fan->fan.read = (int (*)(struct pmac_fan *))smu_fan_read_rpm;
1020232482Sandreast			fan->fan.set = (int (*)(struct pmac_fan *, int))smu_fan_set_rpm;
1021204179Snwhitehorn
1022232482Sandreast		} else {
1023232482Sandreast			oid = SYSCTL_ADD_NODE(ctx,
1024232482Sandreast					      SYSCTL_CHILDREN(fanroot_oid),
1025232482Sandreast					      OID_AUTO, sysctl_name,
1026232482Sandreast					      CTLFLAG_RD, 0, "Fan Information");
1027232482Sandreast			SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(oid), OID_AUTO,
1028232482Sandreast				       "minpwm", CTLTYPE_INT | CTLFLAG_RD,
1029232482Sandreast				       &fan->fan.min_rpm, sizeof(int),
1030232482Sandreast				       "Minimum allowed PWM in %");
1031232482Sandreast			SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(oid), OID_AUTO,
1032232482Sandreast				       "maxpwm", CTLTYPE_INT | CTLFLAG_RD,
1033232482Sandreast				       &fan->fan.max_rpm, sizeof(int),
1034232482Sandreast				       "Maximum allowed PWM in %");
1035232482Sandreast			SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO,
1036232482Sandreast					"pwm",CTLTYPE_INT | CTLFLAG_RW |
1037232482Sandreast					CTLFLAG_MPSAFE, dev,
1038232482Sandreast					SMU_PWM_SYSCTL_PWM | i,
1039232482Sandreast					smu_fanrpm_sysctl, "I", "Fan PWM in %");
1040232482Sandreast			SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO,
1041232482Sandreast					"rpm",CTLTYPE_INT | CTLFLAG_RD |
1042232482Sandreast					CTLFLAG_MPSAFE, dev,
1043232482Sandreast					SMU_PWM_SYSCTL_RPM | i,
1044232482Sandreast					smu_fanrpm_sysctl, "I", "Fan RPM");
1045232482Sandreast			fan->fan.read = NULL;
1046232482Sandreast			fan->fan.set = (int (*)(struct pmac_fan *, int))smu_fan_set_pwm;
1047204179Snwhitehorn
1048204082Snwhitehorn		}
1049232482Sandreast		if (bootverbose)
1050232482Sandreast			device_printf(dev, "Fan: %s type: %d\n",
1051232482Sandreast				      fan->fan.name, fan->type);
1052204082Snwhitehorn	}
1053204082Snwhitehorn}
1054204082Snwhitehorn
1055204082Snwhitehornstatic int
1056222429Snwhitehornsmu_sensor_read(struct smu_sensor *sens)
1057204082Snwhitehorn{
1058222429Snwhitehorn	device_t smu = sens->dev;
1059204082Snwhitehorn	struct smu_cmd cmd;
1060204082Snwhitehorn	struct smu_softc *sc;
1061204082Snwhitehorn	int64_t value;
1062204692Snwhitehorn	int error;
1063204082Snwhitehorn
1064204082Snwhitehorn	cmd.cmd = SMU_ADC;
1065204082Snwhitehorn	cmd.len = 1;
1066204082Snwhitehorn	cmd.data[0] = sens->reg;
1067204692Snwhitehorn	error = 0;
1068204082Snwhitehorn
1069204692Snwhitehorn	error = smu_run_cmd(smu, &cmd, 1);
1070204692Snwhitehorn	if (error != 0)
1071222429Snwhitehorn		return (-1);
1072204082Snwhitehorn
1073204082Snwhitehorn	sc = device_get_softc(smu);
1074204082Snwhitehorn	value = (cmd.data[0] << 8) | cmd.data[1];
1075204082Snwhitehorn
1076204082Snwhitehorn	switch (sens->type) {
1077204082Snwhitehorn	case SMU_TEMP_SENSOR:
1078204082Snwhitehorn		value *= sc->sc_cpu_diode_scale;
1079204082Snwhitehorn		value >>= 3;
1080204082Snwhitehorn		value += ((int64_t)sc->sc_cpu_diode_offset) << 9;
1081204082Snwhitehorn		value <<= 1;
1082204082Snwhitehorn
1083222429Snwhitehorn		/* Convert from 16.16 fixed point degC into integer 0.1 K. */
1084222462Snwhitehorn		value = 10*(value >> 16) + ((10*(value & 0xffff)) >> 16) + 2732;
1085204082Snwhitehorn		break;
1086204082Snwhitehorn	case SMU_VOLTAGE_SENSOR:
1087204082Snwhitehorn		value *= sc->sc_cpu_volt_scale;
1088204082Snwhitehorn		value += sc->sc_cpu_volt_offset;
1089204082Snwhitehorn		value <<= 4;
1090204082Snwhitehorn
1091204082Snwhitehorn		/* Convert from 16.16 fixed point V into mV. */
1092204082Snwhitehorn		value *= 15625;
1093204082Snwhitehorn		value /= 1024;
1094204082Snwhitehorn		value /= 1000;
1095204082Snwhitehorn		break;
1096204082Snwhitehorn	case SMU_CURRENT_SENSOR:
1097204082Snwhitehorn		value *= sc->sc_cpu_curr_scale;
1098204082Snwhitehorn		value += sc->sc_cpu_curr_offset;
1099204082Snwhitehorn		value <<= 4;
1100204082Snwhitehorn
1101204082Snwhitehorn		/* Convert from 16.16 fixed point A into mA. */
1102204082Snwhitehorn		value *= 15625;
1103204082Snwhitehorn		value /= 1024;
1104204082Snwhitehorn		value /= 1000;
1105204082Snwhitehorn		break;
1106204082Snwhitehorn	case SMU_POWER_SENSOR:
1107204082Snwhitehorn		value *= sc->sc_slots_pow_scale;
1108204082Snwhitehorn		value += sc->sc_slots_pow_offset;
1109204082Snwhitehorn		value <<= 4;
1110204082Snwhitehorn
1111204082Snwhitehorn		/* Convert from 16.16 fixed point W into mW. */
1112204082Snwhitehorn		value *= 15625;
1113204082Snwhitehorn		value /= 1024;
1114204082Snwhitehorn		value /= 1000;
1115204082Snwhitehorn		break;
1116204082Snwhitehorn	}
1117204082Snwhitehorn
1118222429Snwhitehorn	return (value);
1119204082Snwhitehorn}
1120204082Snwhitehorn
1121204082Snwhitehornstatic int
1122204082Snwhitehornsmu_sensor_sysctl(SYSCTL_HANDLER_ARGS)
1123204082Snwhitehorn{
1124204082Snwhitehorn	device_t smu;
1125204082Snwhitehorn	struct smu_softc *sc;
1126204082Snwhitehorn	struct smu_sensor *sens;
1127204082Snwhitehorn	int value, error;
1128204082Snwhitehorn
1129204082Snwhitehorn	smu = arg1;
1130204082Snwhitehorn	sc = device_get_softc(smu);
1131204082Snwhitehorn	sens = &sc->sc_sensors[arg2];
1132204082Snwhitehorn
1133222429Snwhitehorn	value = smu_sensor_read(sens);
1134222429Snwhitehorn	if (value < 0)
1135222429Snwhitehorn		return (EBUSY);
1136204692Snwhitehorn
1137204082Snwhitehorn	error = sysctl_handle_int(oidp, &value, 0, req);
1138204082Snwhitehorn
1139204082Snwhitehorn	return (error);
1140204082Snwhitehorn}
1141204082Snwhitehorn
1142204082Snwhitehornstatic void
1143204082Snwhitehornsmu_attach_sensors(device_t dev, phandle_t sensroot)
1144204082Snwhitehorn{
1145204082Snwhitehorn	struct smu_sensor *sens;
1146204082Snwhitehorn	struct smu_softc *sc;
1147204082Snwhitehorn	struct sysctl_oid *sensroot_oid;
1148204082Snwhitehorn	struct sysctl_ctx_list *ctx;
1149204082Snwhitehorn	phandle_t child;
1150204082Snwhitehorn	char type[32];
1151204082Snwhitehorn	int i;
1152204082Snwhitehorn
1153204082Snwhitehorn	sc = device_get_softc(dev);
1154204082Snwhitehorn	sc->sc_nsensors = 0;
1155204082Snwhitehorn
1156204082Snwhitehorn	for (child = OF_child(sensroot); child != 0; child = OF_peer(child))
1157204082Snwhitehorn		sc->sc_nsensors++;
1158204082Snwhitehorn
1159204082Snwhitehorn	if (sc->sc_nsensors == 0) {
1160204082Snwhitehorn		device_printf(dev, "WARNING: No sensors detected!\n");
1161204082Snwhitehorn		return;
1162204082Snwhitehorn	}
1163204082Snwhitehorn
1164204179Snwhitehorn	sc->sc_sensors = malloc(sc->sc_nsensors * sizeof(struct smu_sensor),
1165204179Snwhitehorn	    M_SMU, M_WAITOK | M_ZERO);
1166204082Snwhitehorn
1167204082Snwhitehorn	sens = sc->sc_sensors;
1168204082Snwhitehorn	sc->sc_nsensors = 0;
1169204082Snwhitehorn
1170204082Snwhitehorn	ctx = device_get_sysctl_ctx(dev);
1171204082Snwhitehorn	sensroot_oid = SYSCTL_ADD_NODE(ctx,
1172204082Snwhitehorn	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensors",
1173204082Snwhitehorn	    CTLFLAG_RD, 0, "SMU Sensor Information");
1174204082Snwhitehorn
1175204082Snwhitehorn	for (child = OF_child(sensroot); child != 0; child = OF_peer(child)) {
1176204082Snwhitehorn		char sysctl_name[40], sysctl_desc[40];
1177204082Snwhitehorn		const char *units;
1178204082Snwhitehorn
1179222429Snwhitehorn		sens->dev = dev;
1180204082Snwhitehorn		OF_getprop(child, "device_type", type, sizeof(type));
1181204082Snwhitehorn
1182204082Snwhitehorn		if (strcmp(type, "current-sensor") == 0) {
1183204082Snwhitehorn			sens->type = SMU_CURRENT_SENSOR;
1184204082Snwhitehorn			units = "mA";
1185204082Snwhitehorn		} else if (strcmp(type, "temp-sensor") == 0) {
1186204082Snwhitehorn			sens->type = SMU_TEMP_SENSOR;
1187204082Snwhitehorn			units = "C";
1188204082Snwhitehorn		} else if (strcmp(type, "voltage-sensor") == 0) {
1189204082Snwhitehorn			sens->type = SMU_VOLTAGE_SENSOR;
1190204082Snwhitehorn			units = "mV";
1191204082Snwhitehorn		} else if (strcmp(type, "power-sensor") == 0) {
1192204082Snwhitehorn			sens->type = SMU_POWER_SENSOR;
1193204082Snwhitehorn			units = "mW";
1194204082Snwhitehorn		} else {
1195204082Snwhitehorn			continue;
1196204082Snwhitehorn		}
1197204082Snwhitehorn
1198204082Snwhitehorn		OF_getprop(child, "reg", &sens->reg, sizeof(cell_t));
1199222429Snwhitehorn		OF_getprop(child, "zone", &sens->therm.zone, sizeof(int));
1200222429Snwhitehorn		OF_getprop(child, "location", sens->therm.name,
1201222429Snwhitehorn		    sizeof(sens->therm.name));
1202204082Snwhitehorn
1203222429Snwhitehorn		for (i = 0; i < strlen(sens->therm.name); i++) {
1204222429Snwhitehorn			sysctl_name[i] = tolower(sens->therm.name[i]);
1205204082Snwhitehorn			if (isspace(sysctl_name[i]))
1206204082Snwhitehorn				sysctl_name[i] = '_';
1207204082Snwhitehorn		}
1208204082Snwhitehorn		sysctl_name[i] = 0;
1209204082Snwhitehorn
1210222429Snwhitehorn		sprintf(sysctl_desc,"%s (%s)", sens->therm.name, units);
1211204082Snwhitehorn
1212204082Snwhitehorn		SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(sensroot_oid), OID_AUTO,
1213208841Snwhitehorn		    sysctl_name, CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE,
1214222429Snwhitehorn		    dev, sc->sc_nsensors, smu_sensor_sysctl,
1215222429Snwhitehorn		    (sens->type == SMU_TEMP_SENSOR) ? "IK" : "I", sysctl_desc);
1216204082Snwhitehorn
1217222429Snwhitehorn		if (sens->type == SMU_TEMP_SENSOR) {
1218222429Snwhitehorn			/* Make up some numbers */
1219222429Snwhitehorn			sens->therm.target_temp = 500 + 2732; /* 50 C */
1220222429Snwhitehorn			sens->therm.max_temp = 900 + 2732; /* 90 C */
1221222429Snwhitehorn
1222222429Snwhitehorn			sens->therm.read =
1223222429Snwhitehorn			    (int (*)(struct pmac_therm *))smu_sensor_read;
1224222429Snwhitehorn			pmac_thermal_sensor_register(&sens->therm);
1225222429Snwhitehorn		}
1226222429Snwhitehorn
1227204082Snwhitehorn		sens++;
1228204082Snwhitehorn		sc->sc_nsensors++;
1229204082Snwhitehorn	}
1230204082Snwhitehorn}
1231204082Snwhitehorn
1232204692Snwhitehornstatic void
1233204218Snwhitehornsmu_set_sleepled(void *xdev, int onoff)
1234204218Snwhitehorn{
1235204692Snwhitehorn	static struct smu_cmd cmd;
1236204218Snwhitehorn	device_t smu = xdev;
1237204218Snwhitehorn
1238204218Snwhitehorn	cmd.cmd = SMU_MISC;
1239204218Snwhitehorn	cmd.len = 3;
1240204218Snwhitehorn	cmd.data[0] = SMU_MISC_LED_CTRL;
1241204218Snwhitehorn	cmd.data[1] = 0;
1242204218Snwhitehorn	cmd.data[2] = onoff;
1243204218Snwhitehorn
1244204692Snwhitehorn	smu_run_cmd(smu, &cmd, 0);
1245204218Snwhitehorn}
1246204218Snwhitehorn
1247204270Snwhitehornstatic int
1248204270Snwhitehornsmu_server_mode(SYSCTL_HANDLER_ARGS)
1249204270Snwhitehorn{
1250204270Snwhitehorn	struct smu_cmd cmd;
1251204270Snwhitehorn	u_int server_mode;
1252204270Snwhitehorn	device_t smu = arg1;
1253204270Snwhitehorn	int error;
1254204270Snwhitehorn
1255204270Snwhitehorn	cmd.cmd = SMU_POWER_EVENTS;
1256204270Snwhitehorn	cmd.len = 1;
1257204270Snwhitehorn	cmd.data[0] = SMU_PWR_GET_POWERUP;
1258204270Snwhitehorn
1259204692Snwhitehorn	error = smu_run_cmd(smu, &cmd, 1);
1260204270Snwhitehorn
1261204270Snwhitehorn	if (error)
1262204270Snwhitehorn		return (error);
1263204270Snwhitehorn
1264204270Snwhitehorn	server_mode = (cmd.data[1] & SMU_WAKEUP_AC_INSERT) ? 1 : 0;
1265204270Snwhitehorn
1266204270Snwhitehorn	error = sysctl_handle_int(oidp, &server_mode, 0, req);
1267204270Snwhitehorn
1268204270Snwhitehorn	if (error || !req->newptr)
1269204270Snwhitehorn		return (error);
1270204270Snwhitehorn
1271204270Snwhitehorn	if (server_mode == 1)
1272204270Snwhitehorn		cmd.data[0] = SMU_PWR_SET_POWERUP;
1273204270Snwhitehorn	else if (server_mode == 0)
1274204270Snwhitehorn		cmd.data[0] = SMU_PWR_CLR_POWERUP;
1275204270Snwhitehorn	else
1276204270Snwhitehorn		return (EINVAL);
1277204270Snwhitehorn
1278204270Snwhitehorn	cmd.len = 3;
1279204270Snwhitehorn	cmd.data[1] = 0;
1280204270Snwhitehorn	cmd.data[2] = SMU_WAKEUP_AC_INSERT;
1281204270Snwhitehorn
1282204692Snwhitehorn	return (smu_run_cmd(smu, &cmd, 1));
1283204270Snwhitehorn}
1284204270Snwhitehorn
1285212054Snwhitehornstatic void
1286212054Snwhitehornsmu_shutdown(void *xdev, int howto)
1287212054Snwhitehorn{
1288212054Snwhitehorn	device_t smu = xdev;
1289212054Snwhitehorn	struct smu_cmd cmd;
1290212054Snwhitehorn
1291212054Snwhitehorn	cmd.cmd = SMU_POWER;
1292212054Snwhitehorn	if (howto & RB_HALT)
1293212054Snwhitehorn		strcpy(cmd.data, "SHUTDOWN");
1294212054Snwhitehorn	else
1295212054Snwhitehorn		strcpy(cmd.data, "RESTART");
1296212054Snwhitehorn
1297212054Snwhitehorn	cmd.len = strlen(cmd.data);
1298212054Snwhitehorn
1299212054Snwhitehorn	smu_run_cmd(smu, &cmd, 1);
1300212054Snwhitehorn
1301212054Snwhitehorn	for (;;);
1302212054Snwhitehorn}
1303212054Snwhitehorn
1304205506Snwhitehornstatic int
1305205506Snwhitehornsmu_gettime(device_t dev, struct timespec *ts)
1306205506Snwhitehorn{
1307205506Snwhitehorn	struct smu_cmd cmd;
1308205506Snwhitehorn	struct clocktime ct;
1309205506Snwhitehorn
1310205506Snwhitehorn	cmd.cmd = SMU_RTC;
1311205506Snwhitehorn	cmd.len = 1;
1312205506Snwhitehorn	cmd.data[0] = SMU_RTC_GET;
1313205506Snwhitehorn
1314205506Snwhitehorn	if (smu_run_cmd(dev, &cmd, 1) != 0)
1315205506Snwhitehorn		return (ENXIO);
1316205506Snwhitehorn
1317205506Snwhitehorn	ct.nsec	= 0;
1318205506Snwhitehorn	ct.sec	= bcd2bin(cmd.data[0]);
1319205506Snwhitehorn	ct.min	= bcd2bin(cmd.data[1]);
1320205506Snwhitehorn	ct.hour	= bcd2bin(cmd.data[2]);
1321205506Snwhitehorn	ct.dow	= bcd2bin(cmd.data[3]);
1322205506Snwhitehorn	ct.day	= bcd2bin(cmd.data[4]);
1323205506Snwhitehorn	ct.mon	= bcd2bin(cmd.data[5]);
1324205506Snwhitehorn	ct.year	= bcd2bin(cmd.data[6]) + 2000;
1325205506Snwhitehorn
1326205506Snwhitehorn	return (clock_ct_to_ts(&ct, ts));
1327205506Snwhitehorn}
1328205506Snwhitehorn
1329205506Snwhitehornstatic int
1330205506Snwhitehornsmu_settime(device_t dev, struct timespec *ts)
1331205506Snwhitehorn{
1332219624Snwhitehorn	static struct smu_cmd cmd;
1333205506Snwhitehorn	struct clocktime ct;
1334205506Snwhitehorn
1335205506Snwhitehorn	cmd.cmd = SMU_RTC;
1336205506Snwhitehorn	cmd.len = 8;
1337205506Snwhitehorn	cmd.data[0] = SMU_RTC_SET;
1338205506Snwhitehorn
1339205506Snwhitehorn	clock_ts_to_ct(ts, &ct);
1340205506Snwhitehorn
1341205506Snwhitehorn	cmd.data[1] = bin2bcd(ct.sec);
1342205506Snwhitehorn	cmd.data[2] = bin2bcd(ct.min);
1343205506Snwhitehorn	cmd.data[3] = bin2bcd(ct.hour);
1344205506Snwhitehorn	cmd.data[4] = bin2bcd(ct.dow);
1345205506Snwhitehorn	cmd.data[5] = bin2bcd(ct.day);
1346205506Snwhitehorn	cmd.data[6] = bin2bcd(ct.mon);
1347205506Snwhitehorn	cmd.data[7] = bin2bcd(ct.year - 2000);
1348205506Snwhitehorn
1349219624Snwhitehorn	return (smu_run_cmd(dev, &cmd, 0));
1350205506Snwhitehorn}
1351205506Snwhitehorn
1352208841Snwhitehorn/* SMU I2C Interface */
1353208841Snwhitehorn
1354208841Snwhitehornstatic int smuiic_probe(device_t dev);
1355208841Snwhitehornstatic int smuiic_attach(device_t dev);
1356208841Snwhitehornstatic int smuiic_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs);
1357208841Snwhitehornstatic phandle_t smuiic_get_node(device_t bus, device_t dev);
1358208841Snwhitehorn
1359208841Snwhitehornstatic device_method_t smuiic_methods[] = {
1360208841Snwhitehorn	/* device interface */
1361208841Snwhitehorn	DEVMETHOD(device_probe,         smuiic_probe),
1362208841Snwhitehorn	DEVMETHOD(device_attach,        smuiic_attach),
1363208841Snwhitehorn
1364208841Snwhitehorn	/* iicbus interface */
1365208841Snwhitehorn	DEVMETHOD(iicbus_callback,      iicbus_null_callback),
1366208841Snwhitehorn	DEVMETHOD(iicbus_transfer,      smuiic_transfer),
1367208841Snwhitehorn
1368208841Snwhitehorn	/* ofw_bus interface */
1369208841Snwhitehorn	DEVMETHOD(ofw_bus_get_node,     smuiic_get_node),
1370208841Snwhitehorn
1371208841Snwhitehorn	{ 0, 0 }
1372208841Snwhitehorn};
1373208841Snwhitehorn
1374208841Snwhitehornstruct smuiic_softc {
1375208841Snwhitehorn	struct mtx	sc_mtx;
1376208841Snwhitehorn	volatile int	sc_iic_inuse;
1377208841Snwhitehorn	int		sc_busno;
1378208841Snwhitehorn};
1379208841Snwhitehorn
1380208841Snwhitehornstatic driver_t smuiic_driver = {
1381208841Snwhitehorn	"iichb",
1382208841Snwhitehorn	smuiic_methods,
1383208841Snwhitehorn	sizeof(struct smuiic_softc)
1384208841Snwhitehorn};
1385208841Snwhitehornstatic devclass_t smuiic_devclass;
1386208841Snwhitehorn
1387208841SnwhitehornDRIVER_MODULE(smuiic, smu, smuiic_driver, smuiic_devclass, 0, 0);
1388208841Snwhitehorn
1389208841Snwhitehornstatic void
1390208841Snwhitehornsmu_attach_i2c(device_t smu, phandle_t i2croot)
1391208841Snwhitehorn{
1392208841Snwhitehorn	phandle_t child;
1393208841Snwhitehorn	device_t cdev;
1394208841Snwhitehorn	struct ofw_bus_devinfo *dinfo;
1395208841Snwhitehorn	char name[32];
1396208841Snwhitehorn
1397208841Snwhitehorn	for (child = OF_child(i2croot); child != 0; child = OF_peer(child)) {
1398208841Snwhitehorn		if (OF_getprop(child, "name", name, sizeof(name)) <= 0)
1399208841Snwhitehorn			continue;
1400208841Snwhitehorn
1401208841Snwhitehorn		if (strcmp(name, "i2c-bus") != 0 && strcmp(name, "i2c") != 0)
1402208841Snwhitehorn			continue;
1403208841Snwhitehorn
1404208841Snwhitehorn		dinfo = malloc(sizeof(struct ofw_bus_devinfo), M_SMU,
1405208841Snwhitehorn		    M_WAITOK | M_ZERO);
1406208841Snwhitehorn		if (ofw_bus_gen_setup_devinfo(dinfo, child) != 0) {
1407208841Snwhitehorn			free(dinfo, M_SMU);
1408208841Snwhitehorn			continue;
1409208841Snwhitehorn		}
1410208841Snwhitehorn
1411208841Snwhitehorn		cdev = device_add_child(smu, NULL, -1);
1412208841Snwhitehorn		if (cdev == NULL) {
1413208841Snwhitehorn			device_printf(smu, "<%s>: device_add_child failed\n",
1414208841Snwhitehorn			    dinfo->obd_name);
1415208841Snwhitehorn			ofw_bus_gen_destroy_devinfo(dinfo);
1416208841Snwhitehorn			free(dinfo, M_SMU);
1417208841Snwhitehorn			continue;
1418208841Snwhitehorn		}
1419208841Snwhitehorn		device_set_ivars(cdev, dinfo);
1420208841Snwhitehorn	}
1421208841Snwhitehorn}
1422208841Snwhitehorn
1423208841Snwhitehornstatic int
1424208841Snwhitehornsmuiic_probe(device_t dev)
1425208841Snwhitehorn{
1426208841Snwhitehorn	const char *name;
1427208841Snwhitehorn
1428208841Snwhitehorn	name = ofw_bus_get_name(dev);
1429208841Snwhitehorn	if (name == NULL)
1430208841Snwhitehorn		return (ENXIO);
1431208841Snwhitehorn
1432208841Snwhitehorn	if (strcmp(name, "i2c-bus") == 0 || strcmp(name, "i2c") == 0) {
1433208841Snwhitehorn		device_set_desc(dev, "SMU I2C controller");
1434208841Snwhitehorn		return (0);
1435208841Snwhitehorn	}
1436208841Snwhitehorn
1437208841Snwhitehorn	return (ENXIO);
1438208841Snwhitehorn}
1439208841Snwhitehorn
1440208841Snwhitehornstatic int
1441208841Snwhitehornsmuiic_attach(device_t dev)
1442208841Snwhitehorn{
1443208841Snwhitehorn	struct smuiic_softc *sc = device_get_softc(dev);
1444208841Snwhitehorn	mtx_init(&sc->sc_mtx, "smuiic", NULL, MTX_DEF);
1445208841Snwhitehorn	sc->sc_iic_inuse = 0;
1446208841Snwhitehorn
1447208841Snwhitehorn	/* Get our bus number */
1448208841Snwhitehorn	OF_getprop(ofw_bus_get_node(dev), "reg", &sc->sc_busno,
1449208841Snwhitehorn	    sizeof(sc->sc_busno));
1450208841Snwhitehorn
1451208841Snwhitehorn	/* Add the IIC bus layer */
1452208841Snwhitehorn	device_add_child(dev, "iicbus", -1);
1453208841Snwhitehorn
1454208841Snwhitehorn	return (bus_generic_attach(dev));
1455208841Snwhitehorn}
1456208841Snwhitehorn
1457208841Snwhitehornstatic int
1458208841Snwhitehornsmuiic_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs)
1459208841Snwhitehorn{
1460208841Snwhitehorn	struct smuiic_softc *sc = device_get_softc(dev);
1461208841Snwhitehorn	struct smu_cmd cmd;
1462208841Snwhitehorn	int i, j, error;
1463208841Snwhitehorn
1464208841Snwhitehorn	mtx_lock(&sc->sc_mtx);
1465208841Snwhitehorn	while (sc->sc_iic_inuse)
1466208841Snwhitehorn		mtx_sleep(sc, &sc->sc_mtx, 0, "smuiic", 100);
1467208841Snwhitehorn
1468208841Snwhitehorn	sc->sc_iic_inuse = 1;
1469208841Snwhitehorn	error = 0;
1470208841Snwhitehorn
1471208841Snwhitehorn	for (i = 0; i < nmsgs; i++) {
1472208841Snwhitehorn		cmd.cmd = SMU_I2C;
1473208841Snwhitehorn		cmd.data[0] = sc->sc_busno;
1474208841Snwhitehorn		if (msgs[i].flags & IIC_M_NOSTOP)
1475208841Snwhitehorn			cmd.data[1] = SMU_I2C_COMBINED;
1476208841Snwhitehorn		else
1477208841Snwhitehorn			cmd.data[1] = SMU_I2C_SIMPLE;
1478208841Snwhitehorn
1479208841Snwhitehorn		cmd.data[2] = msgs[i].slave;
1480208841Snwhitehorn		if (msgs[i].flags & IIC_M_RD)
1481208841Snwhitehorn			cmd.data[2] |= 1;
1482208841Snwhitehorn
1483208841Snwhitehorn		if (msgs[i].flags & IIC_M_NOSTOP) {
1484208841Snwhitehorn			KASSERT(msgs[i].len < 4,
1485208841Snwhitehorn			    ("oversize I2C combined message"));
1486208841Snwhitehorn
1487208841Snwhitehorn			cmd.data[3] = min(msgs[i].len, 3);
1488208841Snwhitehorn			memcpy(&cmd.data[4], msgs[i].buf, min(msgs[i].len, 3));
1489208841Snwhitehorn			i++; /* Advance to next part of message */
1490208841Snwhitehorn		} else {
1491208841Snwhitehorn			cmd.data[3] = 0;
1492208841Snwhitehorn			memset(&cmd.data[4], 0, 3);
1493208841Snwhitehorn		}
1494208841Snwhitehorn
1495208841Snwhitehorn		cmd.data[7] = msgs[i].slave;
1496208841Snwhitehorn		if (msgs[i].flags & IIC_M_RD)
1497208841Snwhitehorn			cmd.data[7] |= 1;
1498208841Snwhitehorn
1499208841Snwhitehorn		cmd.data[8] = msgs[i].len;
1500208841Snwhitehorn		if (msgs[i].flags & IIC_M_RD) {
1501208841Snwhitehorn			memset(&cmd.data[9], 0xff, msgs[i].len);
1502208841Snwhitehorn			cmd.len = 9;
1503208841Snwhitehorn		} else {
1504208841Snwhitehorn			memcpy(&cmd.data[9], msgs[i].buf, msgs[i].len);
1505208841Snwhitehorn			cmd.len = 9 + msgs[i].len;
1506208841Snwhitehorn		}
1507208841Snwhitehorn
1508208841Snwhitehorn		mtx_unlock(&sc->sc_mtx);
1509208841Snwhitehorn		smu_run_cmd(device_get_parent(dev), &cmd, 1);
1510208841Snwhitehorn		mtx_lock(&sc->sc_mtx);
1511208841Snwhitehorn
1512208841Snwhitehorn		for (j = 0; j < 10; j++) {
1513208841Snwhitehorn			cmd.cmd = SMU_I2C;
1514208841Snwhitehorn			cmd.len = 1;
1515208841Snwhitehorn			cmd.data[0] = 0;
1516208841Snwhitehorn			memset(&cmd.data[1], 0xff, msgs[i].len);
1517208841Snwhitehorn
1518208841Snwhitehorn			mtx_unlock(&sc->sc_mtx);
1519208841Snwhitehorn			smu_run_cmd(device_get_parent(dev), &cmd, 1);
1520208841Snwhitehorn			mtx_lock(&sc->sc_mtx);
1521208841Snwhitehorn
1522208841Snwhitehorn			if (!(cmd.data[0] & 0x80))
1523208841Snwhitehorn				break;
1524208841Snwhitehorn
1525208841Snwhitehorn			mtx_sleep(sc, &sc->sc_mtx, 0, "smuiic", 10);
1526208841Snwhitehorn		}
1527208841Snwhitehorn
1528208841Snwhitehorn		if (cmd.data[0] & 0x80) {
1529208841Snwhitehorn			error = EIO;
1530208841Snwhitehorn			msgs[i].len = 0;
1531208841Snwhitehorn			goto exit;
1532208841Snwhitehorn		}
1533208841Snwhitehorn		memcpy(msgs[i].buf, &cmd.data[1], msgs[i].len);
1534208841Snwhitehorn		msgs[i].len = cmd.len - 1;
1535208841Snwhitehorn	}
1536208841Snwhitehorn
1537208841Snwhitehorn    exit:
1538208841Snwhitehorn	sc->sc_iic_inuse = 0;
1539208841Snwhitehorn	mtx_unlock(&sc->sc_mtx);
1540208841Snwhitehorn	wakeup(sc);
1541208841Snwhitehorn	return (error);
1542208841Snwhitehorn}
1543208841Snwhitehorn
1544208841Snwhitehornstatic phandle_t
1545208841Snwhitehornsmuiic_get_node(device_t bus, device_t dev)
1546208841Snwhitehorn{
1547208841Snwhitehorn
1548208841Snwhitehorn	return (ofw_bus_get_node(bus));
1549208841Snwhitehorn}
1550208841Snwhitehorn
1551