xref: /openbsd-src/sys/arch/macppc/dev/smu.c (revision 89ed722c33692ad18452b9d394c650f8adc43407)
1*89ed722cSmpi /*	$OpenBSD: smu.c,v 1.35 2022/03/13 12:33:01 mpi Exp $	*/
226c1ffa3Skettenis 
326c1ffa3Skettenis /*
426c1ffa3Skettenis  * Copyright (c) 2005 Mark Kettenis
526c1ffa3Skettenis  *
626c1ffa3Skettenis  * Permission to use, copy, modify, and distribute this software for any
726c1ffa3Skettenis  * purpose with or without fee is hereby granted, provided that the above
826c1ffa3Skettenis  * copyright notice and this permission notice appear in all copies.
926c1ffa3Skettenis  *
1026c1ffa3Skettenis  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1126c1ffa3Skettenis  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1226c1ffa3Skettenis  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1326c1ffa3Skettenis  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1426c1ffa3Skettenis  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1526c1ffa3Skettenis  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1626c1ffa3Skettenis  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1726c1ffa3Skettenis  */
1826c1ffa3Skettenis 
1926c1ffa3Skettenis #include <sys/param.h>
2026c1ffa3Skettenis #include <sys/systm.h>
2126c1ffa3Skettenis #include <sys/device.h>
221e8e22a4Skettenis #include <sys/kernel.h>
23769768baSthib #include <sys/rwlock.h>
241e8e22a4Skettenis #include <sys/proc.h>
251e8e22a4Skettenis #include <sys/sensors.h>
2626c1ffa3Skettenis 
2726c1ffa3Skettenis #include <machine/autoconf.h>
28b6032f37Skettenis #include <machine/cpu.h>
2926c1ffa3Skettenis 
3026c1ffa3Skettenis #include <dev/clock_subr.h>
312e975dd8Skettenis #include <dev/i2c/i2cvar.h>
3226c1ffa3Skettenis #include <dev/ofw/openfirm.h>
3326c1ffa3Skettenis 
3439dd359fSmpi #include <macppc/dev/maci2cvar.h>
35309d5690Smglocker #include <macppc/dev/thermal.h>
3639dd359fSmpi #include <macppc/pci/macobio.h>
372e975dd8Skettenis 
3826c1ffa3Skettenis int     smu_match(struct device *, void *, void *);
3926c1ffa3Skettenis void    smu_attach(struct device *, struct device *, void *);
4026c1ffa3Skettenis 
41309d5690Smglocker /* Target and Max. temperature in muK. */
42309d5690Smglocker #define TEMP_TRG	38 * 1000000 + 273150000
43309d5690Smglocker #define TEMP_MAX	70 * 1000000 + 273150000
44309d5690Smglocker 
45e26d369eSmpi #define SMU_MAXFANS	8
461e8e22a4Skettenis 
471e8e22a4Skettenis struct smu_fan {
48309d5690Smglocker 	struct thermal_fan fan;
491e8e22a4Skettenis 	u_int8_t	reg;
501e8e22a4Skettenis 	u_int16_t	min_rpm;
511e8e22a4Skettenis 	u_int16_t	max_rpm;
521e8e22a4Skettenis 	u_int16_t	unmanaged_rpm;
53528e43b2Smglocker 	u_int16_t	min_pwm;
54528e43b2Smglocker 	u_int16_t	max_pwm;
55528e43b2Smglocker 	u_int16_t	unmanaged_pwm;
56275cbf62Sderaadt 	struct ksensor	sensor;
571e8e22a4Skettenis };
581e8e22a4Skettenis 
59e242de35Skettenis #define SMU_MAXSENSORS	4
601e8e22a4Skettenis 
611e8e22a4Skettenis struct smu_sensor {
62309d5690Smglocker 	struct thermal_temp therm;
631e8e22a4Skettenis 	u_int8_t	reg;
64275cbf62Sderaadt 	struct ksensor	sensor;
651e8e22a4Skettenis };
661e8e22a4Skettenis 
6726c1ffa3Skettenis struct smu_softc {
6826c1ffa3Skettenis         struct device   sc_dev;
6926c1ffa3Skettenis 
7026c1ffa3Skettenis 	/* SMU command buffer. */
7126c1ffa3Skettenis         bus_dma_tag_t   sc_dmat;
7226c1ffa3Skettenis         bus_dmamap_t    sc_cmdmap;
7326c1ffa3Skettenis         bus_dma_segment_t sc_cmdseg[1];
7426c1ffa3Skettenis         caddr_t         sc_cmd;
75769768baSthib 	struct rwlock	sc_lock;
7626c1ffa3Skettenis 
7726c1ffa3Skettenis 	/* Doorbell and mailbox. */
7826c1ffa3Skettenis 	struct ppc_bus_space sc_mem_bus_space;
7926c1ffa3Skettenis 	bus_space_tag_t sc_memt;
8026c1ffa3Skettenis 	bus_space_handle_t sc_gpioh;
8126c1ffa3Skettenis 	bus_space_handle_t sc_buffh;
821e8e22a4Skettenis 
83d5fb2debSmglocker 	uint8_t		sc_firmware_old;
84d5fb2debSmglocker 
851e8e22a4Skettenis 	struct smu_fan	sc_fans[SMU_MAXFANS];
861e8e22a4Skettenis 	int		sc_num_fans;
871e8e22a4Skettenis 
881e8e22a4Skettenis 	struct smu_sensor sc_sensors[SMU_MAXSENSORS];
891e8e22a4Skettenis 	int		sc_num_sensors;
901e8e22a4Skettenis 
91275cbf62Sderaadt 	struct ksensordev sc_sensordev;
9227515a6bSderaadt 
931e8e22a4Skettenis 	u_int16_t	sc_cpu_diode_scale;
941e8e22a4Skettenis 	int16_t		sc_cpu_diode_offset;
951e8e22a4Skettenis 	u_int16_t	sc_cpu_volt_scale;
961e8e22a4Skettenis 	int16_t		sc_cpu_volt_offset;
971e8e22a4Skettenis 	u_int16_t	sc_cpu_curr_scale;
981e8e22a4Skettenis 	int16_t		sc_cpu_curr_offset;
992e975dd8Skettenis 
1000367fc9dSkettenis 	u_int16_t	sc_slots_pow_scale;
1010367fc9dSkettenis 	int16_t		sc_slots_pow_offset;
1020367fc9dSkettenis 
1032e975dd8Skettenis 	struct i2c_controller sc_i2c_tag;
10426c1ffa3Skettenis };
10526c1ffa3Skettenis 
106*89ed722cSmpi const struct cfattach smu_ca = {
10726c1ffa3Skettenis         sizeof(struct smu_softc), smu_match, smu_attach
10826c1ffa3Skettenis };
1091e8e22a4Skettenis 
11026c1ffa3Skettenis struct cfdriver smu_cd = {
11126c1ffa3Skettenis         NULL, "smu", DV_DULL,
11226c1ffa3Skettenis };
11326c1ffa3Skettenis 
1141e8e22a4Skettenis /* SMU command */
11526c1ffa3Skettenis struct smu_cmd {
11626c1ffa3Skettenis         u_int8_t        cmd;
11726c1ffa3Skettenis         u_int8_t        len;
11826c1ffa3Skettenis         u_int8_t        data[254];
11926c1ffa3Skettenis };
12026c1ffa3Skettenis #define SMU_CMDSZ       sizeof(struct smu_cmd)
12126c1ffa3Skettenis 
1221e8e22a4Skettenis /* RTC */
12326c1ffa3Skettenis #define SMU_RTC			0x8e
12426c1ffa3Skettenis #define SMU_RTC_SET_DATETIME	0x80
12526c1ffa3Skettenis #define SMU_RTC_GET_DATETIME	0x81
12626c1ffa3Skettenis 
1271e8e22a4Skettenis /* ADC */
1281e8e22a4Skettenis #define SMU_ADC			0xd8
1291e8e22a4Skettenis 
1301e8e22a4Skettenis /* Fan control */
1311e8e22a4Skettenis #define SMU_FAN			0x4a
1321e8e22a4Skettenis 
1331e8e22a4Skettenis /* Data partitions */
1341e8e22a4Skettenis #define SMU_PARTITION		0x3e
1351e8e22a4Skettenis #define SMU_PARTITION_LATEST	0x01
1361e8e22a4Skettenis #define SMU_PARTITION_BASE	0x02
1371e8e22a4Skettenis #define SMU_PARTITION_UPDATE	0x03
1381e8e22a4Skettenis 
1392e975dd8Skettenis /* I2C */
1402e975dd8Skettenis #define SMU_I2C			0x9a
1412e975dd8Skettenis #define SMU_I2C_SIMPLE		0x00
1422e975dd8Skettenis #define SMU_I2C_NORMAL		0x01
1432e975dd8Skettenis #define SMU_I2C_COMBINED	0x02
1442e975dd8Skettenis 
145b6032f37Skettenis /* Power Management */
146b6032f37Skettenis #define SMU_POWER		0xaa
147b6032f37Skettenis 
1481e8e22a4Skettenis /* Miscellaneous */
1491e8e22a4Skettenis #define SMU_MISC		0xee
1501e8e22a4Skettenis #define SMU_MISC_GET_DATA	0x02
1511e8e22a4Skettenis 
1521e8e22a4Skettenis int	smu_intr(void *);
1531e8e22a4Skettenis 
15426c1ffa3Skettenis int	smu_do_cmd(struct smu_softc *, int);
15526c1ffa3Skettenis int	smu_time_read(time_t *);
15626c1ffa3Skettenis int	smu_time_write(time_t);
1571e8e22a4Skettenis int	smu_get_datablock(struct smu_softc *sc, u_int8_t, u_int8_t *, size_t);
158d5fb2debSmglocker void	smu_firmware_probe(struct smu_softc *, struct smu_fan *);
1591e8e22a4Skettenis int	smu_fan_set_rpm(struct smu_softc *, struct smu_fan *, u_int16_t);
160528e43b2Smglocker int	smu_fan_set_pwm(struct smu_softc *, struct smu_fan *, u_int16_t);
161a16ae0caSmglocker int	smu_fan_read_rpm(struct smu_softc *, struct smu_fan *, u_int16_t *);
162528e43b2Smglocker int	smu_fan_read_pwm(struct smu_softc *, struct smu_fan *, u_int16_t *,
163528e43b2Smglocker 	    u_int16_t *);
1641e8e22a4Skettenis int	smu_fan_refresh(struct smu_softc *, struct smu_fan *);
165309d5690Smglocker int	smu_sensor_refresh(struct smu_softc *, struct smu_sensor *, int);
1661e8e22a4Skettenis void	smu_refresh_sensors(void *);
16726c1ffa3Skettenis 
168309d5690Smglocker int	smu_fan_set_rpm_thermal(struct smu_fan *, int);
169309d5690Smglocker int	smu_fan_set_pwm_thermal(struct smu_fan *, int);
170309d5690Smglocker int	smu_sensor_refresh_thermal(struct smu_sensor *);
171309d5690Smglocker 
1722e975dd8Skettenis int	smu_i2c_acquire_bus(void *, int);
1732e975dd8Skettenis void	smu_i2c_release_bus(void *, int);
1742e975dd8Skettenis int	smu_i2c_exec(void *, i2c_op_t, i2c_addr_t,
1752e975dd8Skettenis 	    const void *, size_t, void *buf, size_t, int);
1762e975dd8Skettenis 
177b6032f37Skettenis void	smu_slew_voltage(u_int);
178b6032f37Skettenis 
17926c1ffa3Skettenis int
smu_match(struct device * parent,void * cf,void * aux)18026c1ffa3Skettenis smu_match(struct device *parent, void *cf, void *aux)
18126c1ffa3Skettenis {
18226c1ffa3Skettenis         struct confargs *ca = aux;
18326c1ffa3Skettenis 
18426c1ffa3Skettenis         if (strcmp(ca->ca_name, "smu") == 0)
18526c1ffa3Skettenis                 return (1);
18626c1ffa3Skettenis         return (0);
18726c1ffa3Skettenis }
18826c1ffa3Skettenis 
18926c1ffa3Skettenis /* XXX */
19026c1ffa3Skettenis extern struct powerpc_bus_dma_tag pci_bus_dma_tag;
19126c1ffa3Skettenis 
19226c1ffa3Skettenis void
smu_attach(struct device * parent,struct device * self,void * aux)19326c1ffa3Skettenis smu_attach(struct device *parent, struct device *self, void *aux)
19426c1ffa3Skettenis {
19526c1ffa3Skettenis         struct smu_softc *sc = (struct smu_softc *)self;
1961e8e22a4Skettenis 	struct confargs *ca = aux;
197a5a63700Sderaadt 	struct i2cbus_attach_args iba;
1981e8e22a4Skettenis 	struct smu_fan *fan;
1991e8e22a4Skettenis 	struct smu_sensor *sensor;
2001e8e22a4Skettenis 	int nseg, node;
2011e8e22a4Skettenis 	char type[32], loc[32];
2021e8e22a4Skettenis 	u_int32_t reg, intr, gpio, val;
203865897fdSderaadt #ifndef SMALL_KERNEL
2041e8e22a4Skettenis 	u_int8_t data[12];
205865897fdSderaadt #endif
20626c1ffa3Skettenis 
20726c1ffa3Skettenis 	/* XXX */
20826c1ffa3Skettenis 	sc->sc_mem_bus_space.bus_base = 0x80000000;
20926c1ffa3Skettenis 	sc->sc_mem_bus_space.bus_size = 0;
21026c1ffa3Skettenis 	sc->sc_mem_bus_space.bus_io = 0;
21126c1ffa3Skettenis 	sc->sc_memt = &sc->sc_mem_bus_space;
21226c1ffa3Skettenis 
2131e8e22a4Skettenis 	/* Map smu-doorbell gpio. */
2141e8e22a4Skettenis 	if (OF_getprop(ca->ca_node, "platform-doorbell-ack",
2151e8e22a4Skettenis 	        &node, sizeof node) <= 0 ||
2161e8e22a4Skettenis 	    OF_getprop(node, "reg", &reg, sizeof reg) <= 0 ||
2171e8e22a4Skettenis 	    OF_getprop(node, "interrupts", &intr, sizeof intr) <= 0 ||
2181e8e22a4Skettenis 	    OF_getprop(OF_parent(node), "reg", &gpio, sizeof gpio) <= 0) {
2191e8e22a4Skettenis 		printf(": cannot find smu-doorbell gpio\n");
2201e8e22a4Skettenis 		return;
2211e8e22a4Skettenis 	}
2221e8e22a4Skettenis 	if (bus_space_map(sc->sc_memt, gpio + reg, 1, 0, &sc->sc_gpioh)) {
22326c1ffa3Skettenis 		printf(": cannot map smu-doorbell gpio\n");
22426c1ffa3Skettenis 		return;
22526c1ffa3Skettenis 	}
22626c1ffa3Skettenis 
22726c1ffa3Skettenis 	/* XXX Should get this from OF. */
22826c1ffa3Skettenis 	if (bus_space_map(sc->sc_memt, 0x860c, 4, 0, &sc->sc_buffh)) {
22926c1ffa3Skettenis 		printf(": cannot map smu-doorbell buffer\n");
23026c1ffa3Skettenis 		return;
23126c1ffa3Skettenis 	}
23226c1ffa3Skettenis 
23326c1ffa3Skettenis 	/* XXX */
23426c1ffa3Skettenis         sc->sc_dmat = &pci_bus_dma_tag;
23526c1ffa3Skettenis 
23626c1ffa3Skettenis 	/* Allocate and map SMU command buffer.  */
23726c1ffa3Skettenis 	if (bus_dmamem_alloc(sc->sc_dmat, SMU_CMDSZ, 0, 0,
23826c1ffa3Skettenis             sc->sc_cmdseg, 1, &nseg, BUS_DMA_NOWAIT)) {
23926c1ffa3Skettenis                 printf(": cannot allocate cmd buffer\n");
24026c1ffa3Skettenis                 return;
24126c1ffa3Skettenis         }
24226c1ffa3Skettenis         if (bus_dmamem_map(sc->sc_dmat, sc->sc_cmdseg, nseg,
24326c1ffa3Skettenis             SMU_CMDSZ, &sc->sc_cmd, BUS_DMA_NOWAIT)) {
24426c1ffa3Skettenis                 printf(": cannot map cmd buffer\n");
24526c1ffa3Skettenis                 bus_dmamem_free(sc->sc_dmat, sc->sc_cmdseg, 1);
24626c1ffa3Skettenis                 return;
24726c1ffa3Skettenis         }
24826c1ffa3Skettenis         if (bus_dmamap_create(sc->sc_dmat, SMU_CMDSZ, 1, SMU_CMDSZ, 0,
24926c1ffa3Skettenis             BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &sc->sc_cmdmap)) {
25026c1ffa3Skettenis                 printf(": cannot create cmd dmamap\n");
25126c1ffa3Skettenis                 bus_dmamem_unmap(sc->sc_dmat, sc->sc_cmd, SMU_CMDSZ);
25226c1ffa3Skettenis                 bus_dmamem_free(sc->sc_dmat, sc->sc_cmdseg, 1);
25326c1ffa3Skettenis                 return;
25426c1ffa3Skettenis         }
25526c1ffa3Skettenis         if (bus_dmamap_load(sc->sc_dmat, sc->sc_cmdmap, sc->sc_cmd,
25626c1ffa3Skettenis             SMU_CMDSZ, NULL, BUS_DMA_NOWAIT)) {
25726c1ffa3Skettenis                 printf(": cannot load cmd dmamap\n");
25826c1ffa3Skettenis                 bus_dmamap_destroy(sc->sc_dmat, sc->sc_cmdmap);
25926c1ffa3Skettenis                 bus_dmamem_unmap(sc->sc_dmat, sc->sc_cmd, SMU_CMDSZ);
26026c1ffa3Skettenis                 bus_dmamem_free(sc->sc_dmat, sc->sc_cmdseg, nseg);
26126c1ffa3Skettenis                 return;
26226c1ffa3Skettenis         }
26326c1ffa3Skettenis 
264769768baSthib 	rw_init(&sc->sc_lock, sc->sc_dev.dv_xname);
2651e8e22a4Skettenis 
2661e8e22a4Skettenis 	/* Establish smu-doorbell interrupt. */
2671e8e22a4Skettenis 	mac_intr_establish(parent, intr, IST_EDGE, IPL_BIO,
2681ac74572Sderaadt 	    smu_intr, sc, sc->sc_dev.dv_xname);
2691e8e22a4Skettenis 
27026c1ffa3Skettenis 	/* Initialize global variables that control RTC functionality. */
27126c1ffa3Skettenis 	time_read = smu_time_read;
27226c1ffa3Skettenis 	time_write = smu_time_write;
27326c1ffa3Skettenis 
274528e43b2Smglocker 	/* RPM Fans */
275ded34fa7Skettenis 	node = OF_getnodebyname(ca->ca_node, "rpm-fans");
27610fd3003Skettenis 	if (node == 0)
27710fd3003Skettenis 		node = OF_getnodebyname(ca->ca_node, "fans");
2781e8e22a4Skettenis 	for (node = OF_child(node); node; node = OF_peer(node)) {
2791e8e22a4Skettenis 		if (OF_getprop(node, "reg", &reg, sizeof reg) <= 0 ||
2801e8e22a4Skettenis 		    OF_getprop(node, "device_type", type, sizeof type) <= 0)
2811e8e22a4Skettenis 			continue;
2821e8e22a4Skettenis 
2831e8e22a4Skettenis 		if (strcmp(type, "fan-rpm-control") != 0) {
284528e43b2Smglocker 			printf(": unsupported rpm-fan type: %s\n", type);
2851e8e22a4Skettenis 			return;
2861e8e22a4Skettenis 		}
2871e8e22a4Skettenis 
2881e8e22a4Skettenis 		if (sc->sc_num_fans >= SMU_MAXFANS) {
2891e8e22a4Skettenis 			printf(": too many fans\n");
2901e8e22a4Skettenis 			return;
2911e8e22a4Skettenis 		}
2921e8e22a4Skettenis 
2931e8e22a4Skettenis 		fan = &sc->sc_fans[sc->sc_num_fans++];
2941e8e22a4Skettenis 		fan->sensor.type = SENSOR_FANRPM;
2951e8e22a4Skettenis 		fan->sensor.flags = SENSOR_FINVALID;
2961e8e22a4Skettenis 		fan->reg = reg;
2971e8e22a4Skettenis 
2981e8e22a4Skettenis 		if (OF_getprop(node, "min-value", &val, sizeof val) <= 0)
2991e8e22a4Skettenis 			val = 0;
3001e8e22a4Skettenis 		fan->min_rpm = val;
3011e8e22a4Skettenis 		if (OF_getprop(node, "max-value", &val, sizeof val) <= 0)
3021e8e22a4Skettenis 			val = 0xffff;
3031e8e22a4Skettenis 		fan->max_rpm = val;
304d01d0224Smpi 		if (OF_getprop(node, "unmanage-value", &val, sizeof val) > 0)
3051e8e22a4Skettenis 			fan->unmanaged_rpm = val;
306d01d0224Smpi 		else if (OF_getprop(node, "safe-value", &val, sizeof val) > 0)
307d01d0224Smpi 			fan->unmanaged_rpm = val;
308d01d0224Smpi 		else
309d01d0224Smpi 			fan->unmanaged_rpm = fan->max_rpm;
3101e8e22a4Skettenis 
3111e8e22a4Skettenis 		if (OF_getprop(node, "location", loc, sizeof loc) <= 0)
3121e8e22a4Skettenis 			strlcpy(loc, "Unknown", sizeof loc);
3131e8e22a4Skettenis 		strlcpy(fan->sensor.desc, loc, sizeof sensor->sensor.desc);
3141e8e22a4Skettenis 
3151e8e22a4Skettenis 		/* Start running fans at their "unmanaged" speed. */
3161e8e22a4Skettenis 		smu_fan_set_rpm(sc, fan, fan->unmanaged_rpm);
3171e8e22a4Skettenis 
318309d5690Smglocker 		/* Register fan at thermal management framework. */
319309d5690Smglocker 		fan->fan.min_rpm = fan->min_rpm;
320309d5690Smglocker 		fan->fan.max_rpm = fan->max_rpm;
321309d5690Smglocker 		fan->fan.default_rpm = fan->unmanaged_rpm;
322309d5690Smglocker 		strlcpy(fan->fan.name, loc, sizeof fan->fan.name);
323309d5690Smglocker 		OF_getprop(node, "zone", &fan->fan.zone, sizeof fan->fan.zone);
324309d5690Smglocker 		fan->fan.set = (int (*)(struct thermal_fan *, int))
325309d5690Smglocker 		    smu_fan_set_rpm_thermal;
326309d5690Smglocker 		thermal_fan_register(&fan->fan);
327865897fdSderaadt #ifndef SMALL_KERNEL
32827515a6bSderaadt 		sensor_attach(&sc->sc_sensordev, &fan->sensor);
329865897fdSderaadt #endif
3301e8e22a4Skettenis 	}
3311e8e22a4Skettenis 
332528e43b2Smglocker 	/* PWM Fans */
333528e43b2Smglocker 	node = OF_getnodebyname(ca->ca_node, "pwm-fans");
334528e43b2Smglocker 	for (node = OF_child(node); node; node = OF_peer(node)) {
335528e43b2Smglocker 		if (OF_getprop(node, "reg", &reg, sizeof reg) <= 0 ||
336528e43b2Smglocker 		    OF_getprop(node, "device_type", type, sizeof type) <= 0)
337528e43b2Smglocker 			continue;
338528e43b2Smglocker 
339528e43b2Smglocker 		if (strcmp(type, "fan-pwm-control") != 0) {
340528e43b2Smglocker 			printf(": unsupported pwm-fan type: %s\n", type);
341528e43b2Smglocker 			return;
342528e43b2Smglocker 		}
343528e43b2Smglocker 
344528e43b2Smglocker 		if (sc->sc_num_fans >= SMU_MAXFANS) {
345528e43b2Smglocker 			printf(": too many fans\n");
346528e43b2Smglocker 			return;
347528e43b2Smglocker 		}
348528e43b2Smglocker 
349528e43b2Smglocker 		fan = &sc->sc_fans[sc->sc_num_fans++];
350528e43b2Smglocker 		fan->sensor.type = SENSOR_PERCENT;
351528e43b2Smglocker 		fan->sensor.flags = SENSOR_FINVALID;
352528e43b2Smglocker 		fan->reg = reg;
353528e43b2Smglocker 
354528e43b2Smglocker 		if (OF_getprop(node, "min-value", &val, sizeof val) <= 0)
355528e43b2Smglocker 			val = 0;
356528e43b2Smglocker 		fan->min_pwm = val;
357528e43b2Smglocker 		if (OF_getprop(node, "max-value", &val, sizeof val) <= 0)
358528e43b2Smglocker 			val = 0xffff;
359528e43b2Smglocker 		fan->max_pwm = val;
360528e43b2Smglocker 		if (OF_getprop(node, "unmanage-value", &val, sizeof val) > 0)
361528e43b2Smglocker 			fan->unmanaged_pwm = val;
362528e43b2Smglocker 		else if (OF_getprop(node, "safe-value", &val, sizeof val) > 0)
363528e43b2Smglocker 			fan->unmanaged_pwm = val;
364528e43b2Smglocker 		else
365528e43b2Smglocker 			fan->unmanaged_pwm = fan->min_pwm;
366528e43b2Smglocker 
367528e43b2Smglocker 		if (OF_getprop(node, "location", loc, sizeof loc) <= 0)
368528e43b2Smglocker 			strlcpy(loc, "Unknown", sizeof loc);
369528e43b2Smglocker 		strlcpy(fan->sensor.desc, loc, sizeof sensor->sensor.desc);
370528e43b2Smglocker 
371528e43b2Smglocker 		/* Start running fans at their "unmanaged" speed. */
372528e43b2Smglocker 		smu_fan_set_pwm(sc, fan, fan->unmanaged_pwm);
373528e43b2Smglocker 
374309d5690Smglocker 		/* Register fan at thermal management framework. */
375309d5690Smglocker 		fan->fan.min_rpm = fan->min_pwm;
376309d5690Smglocker 		fan->fan.max_rpm = fan->max_pwm;
377309d5690Smglocker 		fan->fan.default_rpm = fan->unmanaged_pwm;
378309d5690Smglocker 		strlcpy(fan->fan.name, loc, sizeof fan->fan.name);
379309d5690Smglocker 		OF_getprop(node, "zone", &fan->fan.zone, sizeof fan->fan.zone);
380309d5690Smglocker 		fan->fan.set = (int (*)(struct thermal_fan *, int))
381309d5690Smglocker 		    smu_fan_set_pwm_thermal;
382309d5690Smglocker 		thermal_fan_register(&fan->fan);
383528e43b2Smglocker #ifndef SMALL_KERNEL
384528e43b2Smglocker 		sensor_attach(&sc->sc_sensordev, &fan->sensor);
385528e43b2Smglocker #endif
386528e43b2Smglocker 	}
387528e43b2Smglocker 
388cb2ccc16Skettenis 	/*
389cb2ccc16Skettenis 	 * Bail out if we didn't find any fans.  If we don't set the
390cb2ccc16Skettenis 	 * fans to a safe speed, but tickle the SMU periodically by
391cb2ccc16Skettenis 	 * reading sensors, the fans will never spin up and the
392cb2ccc16Skettenis 	 * machine might overheat.
393cb2ccc16Skettenis 	 */
394cb2ccc16Skettenis 	if (sc->sc_num_fans == 0) {
395cb2ccc16Skettenis 		printf(": no fans\n");
396cb2ccc16Skettenis 		return;
397cb2ccc16Skettenis 	}
398cb2ccc16Skettenis 
399d5fb2debSmglocker 	/* Probe the smu firmware version */
400d5fb2debSmglocker 	smu_firmware_probe(sc, &sc->sc_fans[0]);
401d5fb2debSmglocker 
402865897fdSderaadt #ifndef SMALL_KERNEL
4031e8e22a4Skettenis 	/* Sensors */
4041e8e22a4Skettenis 	node = OF_getnodebyname(ca->ca_node, "sensors");
4051e8e22a4Skettenis 	for (node = OF_child(node); node; node = OF_peer(node)) {
4061e8e22a4Skettenis 		if (OF_getprop(node, "reg", &val, sizeof val) <= 0 ||
4071e8e22a4Skettenis 		    OF_getprop(node, "device_type", type, sizeof type) <= 0)
4081e8e22a4Skettenis 			continue;
4091e8e22a4Skettenis 
410e242de35Skettenis 		if (sc->sc_num_sensors >= SMU_MAXSENSORS) {
411e242de35Skettenis 			printf(": too many sensors\n");
412e242de35Skettenis 			return;
413e242de35Skettenis 		}
414e242de35Skettenis 
4151e8e22a4Skettenis 		sensor = &sc->sc_sensors[sc->sc_num_sensors++];
4161e8e22a4Skettenis 		sensor->sensor.flags = SENSOR_FINVALID;
4171e8e22a4Skettenis 		sensor->reg = val;
4181e8e22a4Skettenis 
4191e8e22a4Skettenis 		if (strcmp(type, "current-sensor") == 0) {
4201e8e22a4Skettenis 			sensor->sensor.type = SENSOR_AMPS;
4211e8e22a4Skettenis 		} else if (strcmp(type, "temp-sensor") == 0) {
4221e8e22a4Skettenis 			sensor->sensor.type = SENSOR_TEMP;
4231e8e22a4Skettenis 		} else if (strcmp(type, "voltage-sensor") == 0) {
4241e8e22a4Skettenis 			sensor->sensor.type = SENSOR_VOLTS_DC;
4250367fc9dSkettenis 		} else if (strcmp(type, "power-sensor") == 0) {
4260367fc9dSkettenis 			sensor->sensor.type = SENSOR_WATTS;
4271e8e22a4Skettenis 		} else {
4281e8e22a4Skettenis 			sensor->sensor.type = SENSOR_INTEGER;
4291e8e22a4Skettenis 		}
4301e8e22a4Skettenis 
4311e8e22a4Skettenis 		if (OF_getprop(node, "location", loc, sizeof loc) <= 0)
4321e8e22a4Skettenis 			strlcpy(loc, "Unknown", sizeof loc);
4331e8e22a4Skettenis 		strlcpy(sensor->sensor.desc, loc, sizeof sensor->sensor.desc);
4341e8e22a4Skettenis 
435309d5690Smglocker 		/* Register temp. sensor at thermal management framework. */
436309d5690Smglocker 		if (sensor->sensor.type == SENSOR_TEMP) {
437309d5690Smglocker 			sensor->therm.target_temp = TEMP_TRG;
438309d5690Smglocker 			sensor->therm.max_temp = TEMP_MAX;
439309d5690Smglocker 			strlcpy(sensor->therm.name, loc,
440309d5690Smglocker 			    sizeof sensor->therm.name);
441309d5690Smglocker 			OF_getprop(node, "zone", &sensor->therm.zone,
442309d5690Smglocker 			    sizeof sensor->therm.zone);
443309d5690Smglocker 			sensor->therm.read = (int (*)
444309d5690Smglocker 			    (struct thermal_temp *))smu_sensor_refresh_thermal;
445309d5690Smglocker 			thermal_sensor_register(&sensor->therm);
446309d5690Smglocker 		}
447309d5690Smglocker 
44827515a6bSderaadt 		sensor_attach(&sc->sc_sensordev, &sensor->sensor);
4491e8e22a4Skettenis 	}
4501e8e22a4Skettenis 
45127515a6bSderaadt 	/* Register sensor device with sysctl */
45227515a6bSderaadt 	strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname,
45327515a6bSderaadt 	    sizeof(sc->sc_sensordev.xname));
45427515a6bSderaadt 	sensordev_install(&sc->sc_sensordev);
45527515a6bSderaadt 
4561e8e22a4Skettenis 	/* CPU temperature diode calibration */
4571e8e22a4Skettenis 	smu_get_datablock(sc, 0x18, data, sizeof data);
4581e8e22a4Skettenis 	sc->sc_cpu_diode_scale = (data[4] << 8) + data[5];
4591e8e22a4Skettenis 	sc->sc_cpu_diode_offset = (data[6] << 8) + data[7];
4601e8e22a4Skettenis 
4611e8e22a4Skettenis 	/* CPU power (voltage and current) calibration */
4621e8e22a4Skettenis 	smu_get_datablock(sc, 0x21, data, sizeof data);
4631e8e22a4Skettenis 	sc->sc_cpu_volt_scale = (data[4] << 8) + data[5];
4641e8e22a4Skettenis 	sc->sc_cpu_volt_offset = (data[6] << 8) + data[7];
4651e8e22a4Skettenis 	sc->sc_cpu_curr_scale = (data[8] << 8) + data[9];
4661e8e22a4Skettenis 	sc->sc_cpu_curr_offset = (data[10] << 8) + data[11];
4671e8e22a4Skettenis 
4680367fc9dSkettenis 	/* Slots power calibration */
4690367fc9dSkettenis 	smu_get_datablock(sc, 0x78, data, sizeof data);
4700367fc9dSkettenis 	sc->sc_slots_pow_scale = (data[4] << 8) + data[5];
4710367fc9dSkettenis 	sc->sc_slots_pow_offset = (data[6] << 8) + data[7];
4720367fc9dSkettenis 
473aa803ae9Skettenis 	sensor_task_register(sc, smu_refresh_sensors, 5);
474865897fdSderaadt #endif /* !SMALL_KERNEL */
47526c1ffa3Skettenis 	printf("\n");
4762e975dd8Skettenis 
477b6032f37Skettenis 	ppc64_slew_voltage = smu_slew_voltage;
478b6032f37Skettenis 
4792e975dd8Skettenis 	sc->sc_i2c_tag.ic_cookie = sc;
4802e975dd8Skettenis 	sc->sc_i2c_tag.ic_acquire_bus = smu_i2c_acquire_bus;
4812e975dd8Skettenis 	sc->sc_i2c_tag.ic_release_bus = smu_i2c_release_bus;
4822e975dd8Skettenis 	sc->sc_i2c_tag.ic_exec = smu_i2c_exec;
4832e975dd8Skettenis 
484ca764d7fSkettenis 	/*
485ca764d7fSkettenis 	 * Early versions of the SMU have the i2c bus node directly
486ca764d7fSkettenis 	 * below the "smu" node, while later models have an
487ca764d7fSkettenis 	 * intermediate "smu-i2c-control" node.
488ca764d7fSkettenis 	 */
4892e975dd8Skettenis 	node = OF_getnodebyname(ca->ca_node, "smu-i2c-control");
490ca764d7fSkettenis 	if (node)
491a5a63700Sderaadt 		node = OF_child(node);
492ca764d7fSkettenis 	else
493ca764d7fSkettenis 		node = OF_getnodebyname(ca->ca_node, "i2c");
4942e975dd8Skettenis 
495a5a63700Sderaadt 	bzero(&iba, sizeof iba);
496a5a63700Sderaadt 	iba.iba_name = "iic";
4972e975dd8Skettenis 	iba.iba_tag = &sc->sc_i2c_tag;
498a5a63700Sderaadt 	iba.iba_bus_scan = maciic_scan;
499a5a63700Sderaadt 	iba.iba_bus_scan_arg = &node;
5002e975dd8Skettenis 	config_found(&sc->sc_dev, &iba, NULL);
50126c1ffa3Skettenis }
50226c1ffa3Skettenis 
5031e8e22a4Skettenis int
smu_intr(void * arg)5041e8e22a4Skettenis smu_intr(void *arg)
5051e8e22a4Skettenis {
5061e8e22a4Skettenis 	wakeup(arg);
5071e8e22a4Skettenis 	return 1;
5081e8e22a4Skettenis }
5091e8e22a4Skettenis 
51026c1ffa3Skettenis int
smu_do_cmd(struct smu_softc * sc,int msecs)5113bf93282Scheloha smu_do_cmd(struct smu_softc *sc, int msecs)
51226c1ffa3Skettenis {
51326c1ffa3Skettenis 	struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd;
51426c1ffa3Skettenis 	u_int8_t gpio, ack = ~cmd->cmd;
5151e8e22a4Skettenis 	int error;
51626c1ffa3Skettenis 
51726c1ffa3Skettenis 	/* Write to mailbox.  */
51826c1ffa3Skettenis 	bus_space_write_4(sc->sc_memt, sc->sc_buffh, 0,
51926c1ffa3Skettenis 	    sc->sc_cmdmap->dm_segs->ds_addr);
52026c1ffa3Skettenis 
52126c1ffa3Skettenis 	/* Flush to RAM. */
5222df76cc2Sguenther 	asm volatile ("dcbst 0,%0; sync" :: "r"(sc->sc_cmd): "memory");
52326c1ffa3Skettenis 
52426c1ffa3Skettenis 	/* Ring doorbell.  */
52526c1ffa3Skettenis 	bus_space_write_1(sc->sc_memt, sc->sc_gpioh, 0, GPIO_DDR_OUTPUT);
5261e8e22a4Skettenis 
52726c1ffa3Skettenis 	do {
5283bf93282Scheloha 		error = tsleep_nsec(sc, PWAIT, "smu", MSEC_TO_NSEC(msecs));
5291e8e22a4Skettenis 		if (error)
5301e8e22a4Skettenis 			return (error);
53126c1ffa3Skettenis 		gpio = bus_space_read_1(sc->sc_memt, sc->sc_gpioh, 0);
53226c1ffa3Skettenis 	} while (!(gpio & (GPIO_DATA)));
53326c1ffa3Skettenis 
53426c1ffa3Skettenis 	/* CPU might have brought back the cache line. */
5352df76cc2Sguenther 	asm volatile ("dcbf 0,%0; sync" :: "r"(sc->sc_cmd) : "memory");
53626c1ffa3Skettenis 
53726c1ffa3Skettenis 	if (cmd->cmd != ack)
53826c1ffa3Skettenis 		return (EIO);
53926c1ffa3Skettenis 	return (0);
54026c1ffa3Skettenis }
54126c1ffa3Skettenis 
54226c1ffa3Skettenis int
smu_time_read(time_t * secs)54326c1ffa3Skettenis smu_time_read(time_t *secs)
54426c1ffa3Skettenis {
54526c1ffa3Skettenis 	struct smu_softc *sc = smu_cd.cd_devs[0];
54626c1ffa3Skettenis 	struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd;
54726c1ffa3Skettenis 	struct clock_ymdhms dt;
54826c1ffa3Skettenis 	int error;
54926c1ffa3Skettenis 
550769768baSthib 	rw_enter_write(&sc->sc_lock);
5511e8e22a4Skettenis 
55226c1ffa3Skettenis 	cmd->cmd = SMU_RTC;
55326c1ffa3Skettenis 	cmd->len = 1;
55426c1ffa3Skettenis 	cmd->data[0] = SMU_RTC_GET_DATETIME;
55526c1ffa3Skettenis 	error = smu_do_cmd(sc, 800);
55626c1ffa3Skettenis 	if (error) {
557769768baSthib 		rw_exit_write(&sc->sc_lock);
558d9e828b2Skettenis 
55926c1ffa3Skettenis 		*secs = 0;
56026c1ffa3Skettenis 		return (error);
56126c1ffa3Skettenis 	}
56226c1ffa3Skettenis 
56326c1ffa3Skettenis 	dt.dt_year = 2000 + FROMBCD(cmd->data[6]);
56426c1ffa3Skettenis 	dt.dt_mon = FROMBCD(cmd->data[5]);
56526c1ffa3Skettenis 	dt.dt_day = FROMBCD(cmd->data[4]);
56626c1ffa3Skettenis 	dt.dt_hour = FROMBCD(cmd->data[2]);
56726c1ffa3Skettenis 	dt.dt_min = FROMBCD(cmd->data[1]);
56826c1ffa3Skettenis 	dt.dt_sec = FROMBCD(cmd->data[0]);
5691e8e22a4Skettenis 
570769768baSthib 	rw_exit_write(&sc->sc_lock);
5711e8e22a4Skettenis 
57226c1ffa3Skettenis 	*secs = clock_ymdhms_to_secs(&dt);
57326c1ffa3Skettenis 	return (0);
57426c1ffa3Skettenis }
57526c1ffa3Skettenis 
57626c1ffa3Skettenis int
smu_time_write(time_t secs)57726c1ffa3Skettenis smu_time_write(time_t secs)
57826c1ffa3Skettenis {
57926c1ffa3Skettenis 	struct smu_softc *sc = smu_cd.cd_devs[0];
58026c1ffa3Skettenis 	struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd;
58126c1ffa3Skettenis 	struct clock_ymdhms dt;
5821e8e22a4Skettenis 	int error;
58326c1ffa3Skettenis 
58426c1ffa3Skettenis 	clock_secs_to_ymdhms(secs, &dt);
58526c1ffa3Skettenis 
586769768baSthib 	rw_enter_write(&sc->sc_lock);
5871e8e22a4Skettenis 
58826c1ffa3Skettenis 	cmd->cmd = SMU_RTC;
58926c1ffa3Skettenis 	cmd->len = 8;
59026c1ffa3Skettenis 	cmd->data[0] = SMU_RTC_SET_DATETIME;
59126c1ffa3Skettenis 	cmd->data[1] = TOBCD(dt.dt_sec);
59226c1ffa3Skettenis 	cmd->data[2] = TOBCD(dt.dt_min);
59326c1ffa3Skettenis 	cmd->data[3] = TOBCD(dt.dt_hour);
59426c1ffa3Skettenis 	cmd->data[4] = TOBCD(dt.dt_wday);
59526c1ffa3Skettenis 	cmd->data[5] = TOBCD(dt.dt_day);
59626c1ffa3Skettenis 	cmd->data[6] = TOBCD(dt.dt_mon);
59726c1ffa3Skettenis 	cmd->data[7] = TOBCD(dt.dt_year - 2000);
5981e8e22a4Skettenis 	error = smu_do_cmd(sc, 800);
59926c1ffa3Skettenis 
600769768baSthib 	rw_exit_write(&sc->sc_lock);
6011e8e22a4Skettenis 
6021e8e22a4Skettenis 	return (error);
6031e8e22a4Skettenis }
6041e8e22a4Skettenis 
6051e8e22a4Skettenis 
6061e8e22a4Skettenis int
smu_get_datablock(struct smu_softc * sc,u_int8_t id,u_int8_t * buf,size_t len)6071e8e22a4Skettenis smu_get_datablock(struct smu_softc *sc, u_int8_t id, u_int8_t *buf, size_t len)
6081e8e22a4Skettenis {
6091e8e22a4Skettenis 	struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd;
6101e8e22a4Skettenis 	u_int8_t addr[4];
6111e8e22a4Skettenis 	int error;
6121e8e22a4Skettenis 
6131e8e22a4Skettenis 	cmd->cmd = SMU_PARTITION;
6141e8e22a4Skettenis 	cmd->len = 2;
6151e8e22a4Skettenis 	cmd->data[0] = SMU_PARTITION_LATEST;
6161e8e22a4Skettenis 	cmd->data[1] = id;
6171e8e22a4Skettenis 	error = smu_do_cmd(sc, 800);
6181e8e22a4Skettenis 	if (error)
6191e8e22a4Skettenis 		return (error);
6201e8e22a4Skettenis 
6211e8e22a4Skettenis 	addr[0] = 0x00;
6221e8e22a4Skettenis 	addr[1] = 0x00;
6231e8e22a4Skettenis 	addr[2] = cmd->data[0];
6241e8e22a4Skettenis 	addr[3] = cmd->data[1];
6251e8e22a4Skettenis 
6261e8e22a4Skettenis 	cmd->cmd = SMU_MISC;
6271e8e22a4Skettenis 	cmd->len = 7;
6281e8e22a4Skettenis 	cmd->data[0] = SMU_MISC_GET_DATA;
6291e8e22a4Skettenis 	cmd->data[1] = sizeof(u_int32_t);
6301e8e22a4Skettenis 	cmd->data[2] = addr[0];
6311e8e22a4Skettenis 	cmd->data[3] = addr[1];
6321e8e22a4Skettenis 	cmd->data[4] = addr[2];
6331e8e22a4Skettenis 	cmd->data[5] = addr[3];
6341e8e22a4Skettenis 	cmd->data[6] = len;
6351e8e22a4Skettenis 	error = smu_do_cmd(sc, 800);
6361e8e22a4Skettenis 	if (error)
6371e8e22a4Skettenis 		return (error);
6381e8e22a4Skettenis 
6391e8e22a4Skettenis 	memcpy(buf, cmd->data, len);
6401e8e22a4Skettenis 	return (0);
6411e8e22a4Skettenis }
6421e8e22a4Skettenis 
643d5fb2debSmglocker void
smu_firmware_probe(struct smu_softc * sc,struct smu_fan * fan)644d5fb2debSmglocker smu_firmware_probe(struct smu_softc *sc, struct smu_fan *fan)
645d5fb2debSmglocker {
646d5fb2debSmglocker 	struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd;
647d5fb2debSmglocker 	int error;
648d5fb2debSmglocker 
649d5fb2debSmglocker 	/*
650d5fb2debSmglocker 	 * Find out if the smu runs an old or new firmware version
651d5fb2debSmglocker 	 * by sending a new firmware command to read the fan speed.
652d5fb2debSmglocker 	 * If it fails we assume to have an old firmware version.
653d5fb2debSmglocker 	 */
654d5fb2debSmglocker 	cmd->cmd = SMU_FAN;
655d5fb2debSmglocker 	cmd->len = 2;
656d5fb2debSmglocker 	cmd->data[0] = 0x31;
657d5fb2debSmglocker 	cmd->data[1] = fan->reg;
658d5fb2debSmglocker 	error = smu_do_cmd(sc, 800);
659d5fb2debSmglocker 	if (error)
660d5fb2debSmglocker 		sc->sc_firmware_old = 1;
661d5fb2debSmglocker }
662d5fb2debSmglocker 
6631e8e22a4Skettenis int
smu_fan_set_rpm(struct smu_softc * sc,struct smu_fan * fan,u_int16_t rpm)6641e8e22a4Skettenis smu_fan_set_rpm(struct smu_softc *sc, struct smu_fan *fan, u_int16_t rpm)
6651e8e22a4Skettenis {
6661e8e22a4Skettenis 	struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd;
6671e8e22a4Skettenis 
66810fd3003Skettenis 	/*
66910fd3003Skettenis 	 * On the PowerMac8,2 this command expects the requested fan
67010fd3003Skettenis 	 * speed at a different location in the command block than on
67110fd3003Skettenis 	 * the PowerMac8,1.  We simply store the value at both
67210fd3003Skettenis 	 * locations.
67310fd3003Skettenis 	 */
674d5fb2debSmglocker 	if (sc->sc_firmware_old) {
6751e8e22a4Skettenis 		cmd->cmd = SMU_FAN;
67610fd3003Skettenis 		cmd->len = 14;
6771e8e22a4Skettenis 		cmd->data[0] = 0x00;	/* fan-rpm-control */
6781e8e22a4Skettenis 		cmd->data[1] = 0x01 << fan->reg;
67910fd3003Skettenis 		cmd->data[2] = cmd->data[2 + fan->reg * 2] = (rpm >> 8) & 0xff;
68010fd3003Skettenis 		cmd->data[3] = cmd->data[3 + fan->reg * 2] = (rpm & 0xff);
681d5fb2debSmglocker 	} else {
682d5fb2debSmglocker 		cmd->cmd = SMU_FAN;
683d5fb2debSmglocker 		cmd->len = 4;
684d5fb2debSmglocker 		cmd->data[0] = 0x30;
685d5fb2debSmglocker 		cmd->data[1] = fan->reg;
686d5fb2debSmglocker 		cmd->data[2] = (rpm >> 8) & 0xff;
687d5fb2debSmglocker 		cmd->data[3] = rpm & 0xff;
688d5fb2debSmglocker 	}
6891e8e22a4Skettenis 	return smu_do_cmd(sc, 800);
6901e8e22a4Skettenis }
6911e8e22a4Skettenis 
6921e8e22a4Skettenis int
smu_fan_set_pwm(struct smu_softc * sc,struct smu_fan * fan,u_int16_t pwm)693528e43b2Smglocker smu_fan_set_pwm(struct smu_softc *sc, struct smu_fan *fan, u_int16_t pwm)
694528e43b2Smglocker {
695528e43b2Smglocker 	struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd;
696528e43b2Smglocker 
697d5fb2debSmglocker 	if (sc->sc_firmware_old) {
698528e43b2Smglocker 		cmd->cmd = SMU_FAN;
699528e43b2Smglocker 		cmd->len = 14;
700528e43b2Smglocker 		cmd->data[0] = 0x10;	/* fan-pwm-control */
701528e43b2Smglocker 		cmd->data[1] = 0x01 << fan->reg;
702528e43b2Smglocker 		cmd->data[2] = cmd->data[2 + fan->reg * 2] = (pwm >> 8) & 0xff;
703528e43b2Smglocker 		cmd->data[3] = cmd->data[3 + fan->reg * 2] = (pwm & 0xff);
704d5fb2debSmglocker 	} else {
705d5fb2debSmglocker 		cmd->cmd = SMU_FAN;
706d5fb2debSmglocker 		cmd->len = 4;
707d5fb2debSmglocker 		cmd->data[0] = 0x30;
708d5fb2debSmglocker 		cmd->data[1] = fan->reg;
709d5fb2debSmglocker 		cmd->data[2] = (pwm >> 8) & 0xff;
710d5fb2debSmglocker 		cmd->data[3] = pwm & 0xff;
711d5fb2debSmglocker 	}
712528e43b2Smglocker 	return smu_do_cmd(sc, 800);
713528e43b2Smglocker }
714528e43b2Smglocker 
715528e43b2Smglocker int
smu_fan_read_rpm(struct smu_softc * sc,struct smu_fan * fan,u_int16_t * rpm)716a16ae0caSmglocker smu_fan_read_rpm(struct smu_softc *sc, struct smu_fan *fan, u_int16_t *rpm)
717a16ae0caSmglocker {
718a16ae0caSmglocker 	struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd;
719a16ae0caSmglocker 	int error;
720a16ae0caSmglocker 
721d5fb2debSmglocker 	if (sc->sc_firmware_old) {
722a16ae0caSmglocker 		cmd->cmd = SMU_FAN;
723a16ae0caSmglocker 		cmd->len = 1;
724a16ae0caSmglocker 		cmd->data[0] = 0x01;	/* fan-rpm-control */
725a16ae0caSmglocker 		error = smu_do_cmd(sc, 800);
726a16ae0caSmglocker 		if (error)
727a16ae0caSmglocker 			return (error);
728d5fb2debSmglocker 		*rpm = (cmd->data[fan->reg * 2 + 1] << 8) |
729d5fb2debSmglocker 		        cmd->data[fan->reg * 2 + 2];
730d5fb2debSmglocker 	} else {
731d5fb2debSmglocker 		cmd->cmd = SMU_FAN;
732d5fb2debSmglocker 		cmd->len = 2;
733d5fb2debSmglocker 		cmd->data[0] = 0x31;
734d5fb2debSmglocker 		cmd->data[1] = fan->reg;
735d5fb2debSmglocker 		error = smu_do_cmd(sc, 800);
736d5fb2debSmglocker 		if (error)
737d5fb2debSmglocker 			return (error);
738d5fb2debSmglocker 		*rpm = (cmd->data[0] << 8) | cmd->data[1];
739d5fb2debSmglocker 	}
740a16ae0caSmglocker 
741a16ae0caSmglocker 	return (0);
742a16ae0caSmglocker }
743a16ae0caSmglocker 
744a16ae0caSmglocker int
smu_fan_read_pwm(struct smu_softc * sc,struct smu_fan * fan,u_int16_t * pwm,u_int16_t * rpm)745528e43b2Smglocker smu_fan_read_pwm(struct smu_softc *sc, struct smu_fan *fan, u_int16_t *pwm,
746528e43b2Smglocker     u_int16_t *rpm)
747528e43b2Smglocker {
748528e43b2Smglocker 	struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd;
749528e43b2Smglocker 	int error;
750528e43b2Smglocker 
751d5fb2debSmglocker 	if (sc->sc_firmware_old) {
752528e43b2Smglocker 		/* read PWM value */
753528e43b2Smglocker 		cmd->cmd = SMU_FAN;
754528e43b2Smglocker 		cmd->len = 14;
755528e43b2Smglocker 		cmd->data[0] = 0x12;
756528e43b2Smglocker 		cmd->data[1] = 0x01 << fan->reg;
757528e43b2Smglocker 		error = smu_do_cmd(sc, 800);
758528e43b2Smglocker 		if (error)
759528e43b2Smglocker 			return (error);
760528e43b2Smglocker 		*pwm = cmd->data[fan->reg * 2 + 2];
761528e43b2Smglocker 
762528e43b2Smglocker 		/* read RPM value */
763528e43b2Smglocker 		cmd->cmd = SMU_FAN;
764528e43b2Smglocker 		cmd->len = 1;
765528e43b2Smglocker 		cmd->data[0] = 0x11;
766528e43b2Smglocker 		error = smu_do_cmd(sc, 800);
767528e43b2Smglocker 		if (error)
768528e43b2Smglocker 			return (error);
769d5fb2debSmglocker 		*rpm = (cmd->data[fan->reg * 2 + 1] << 8) |
770d5fb2debSmglocker 		        cmd->data[fan->reg * 2 + 2];
771d5fb2debSmglocker 	} else {
772d5fb2debSmglocker 		cmd->cmd = SMU_FAN;
773d5fb2debSmglocker 		cmd->len = 2;
774d5fb2debSmglocker 		cmd->data[0] = 0x31;
775d5fb2debSmglocker 		cmd->data[1] = fan->reg;
776d5fb2debSmglocker 		error = smu_do_cmd(sc, 800);
777d5fb2debSmglocker 		if (error)
778d5fb2debSmglocker 			return (error);
779d5fb2debSmglocker 		*rpm = (cmd->data[0] << 8) | cmd->data[1];
780d5fb2debSmglocker 
781d5fb2debSmglocker 		/* XXX
782d5fb2debSmglocker 		 * We don't know currently if there is a pwm read command
783d5fb2debSmglocker 		 * for the new firmware as well.  Therefore lets calculate
784d5fb2debSmglocker 		 * the pwm value for now based on the rpm.
785d5fb2debSmglocker 		 */
786d5fb2debSmglocker 		*pwm = *rpm * 100 / fan->max_rpm;
787d5fb2debSmglocker 	}
788528e43b2Smglocker 
789528e43b2Smglocker 	return (0);
790528e43b2Smglocker }
791528e43b2Smglocker 
792528e43b2Smglocker int
smu_fan_refresh(struct smu_softc * sc,struct smu_fan * fan)7931e8e22a4Skettenis smu_fan_refresh(struct smu_softc *sc, struct smu_fan *fan)
7941e8e22a4Skettenis {
7951e8e22a4Skettenis 	int error;
796528e43b2Smglocker 	u_int16_t rpm, pwm;
797528e43b2Smglocker 
798528e43b2Smglocker 	if (fan->sensor.type == SENSOR_PERCENT) {
799528e43b2Smglocker 		error = smu_fan_read_pwm(sc, fan, &pwm, &rpm);
800528e43b2Smglocker 		if (error) {
801528e43b2Smglocker 			fan->sensor.flags = SENSOR_FINVALID;
802528e43b2Smglocker 			return (error);
803528e43b2Smglocker 		}
804528e43b2Smglocker 		fan->sensor.value = pwm * 1000;
805528e43b2Smglocker 		fan->sensor.flags = 0;
806a16ae0caSmglocker 	} else {
807a16ae0caSmglocker 		error = smu_fan_read_rpm(sc, fan, &rpm);
8081e8e22a4Skettenis 		if (error) {
8091e8e22a4Skettenis 			fan->sensor.flags = SENSOR_FINVALID;
8101e8e22a4Skettenis 			return (error);
8111e8e22a4Skettenis 		}
812a16ae0caSmglocker 		fan->sensor.value = rpm;
8131e8e22a4Skettenis 		fan->sensor.flags = 0;
814a16ae0caSmglocker 	}
815a16ae0caSmglocker 
8161e8e22a4Skettenis 	return (0);
8171e8e22a4Skettenis }
8181e8e22a4Skettenis 
8191e8e22a4Skettenis int
smu_sensor_refresh(struct smu_softc * sc,struct smu_sensor * sensor,int update_sysctl)820309d5690Smglocker smu_sensor_refresh(struct smu_softc *sc, struct smu_sensor *sensor,
821309d5690Smglocker     int update_sysctl)
8221e8e22a4Skettenis {
8231e8e22a4Skettenis 	struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd;
8241e8e22a4Skettenis 	int64_t value;
8251e8e22a4Skettenis 	int error;
8261e8e22a4Skettenis 
8271e8e22a4Skettenis 	cmd->cmd = SMU_ADC;
8281e8e22a4Skettenis 	cmd->len = 1;
8291e8e22a4Skettenis 	cmd->data[0] = sensor->reg;
8301e8e22a4Skettenis 	error = smu_do_cmd(sc, 800);
8311e8e22a4Skettenis 	if (error) {
8321e8e22a4Skettenis 		sensor->sensor.flags = SENSOR_FINVALID;
8331e8e22a4Skettenis 		return (error);
8341e8e22a4Skettenis 	}
8351e8e22a4Skettenis 	value = (cmd->data[0] << 8) + cmd->data[1];
836d783d3b4Sdlg 	switch (sensor->sensor.type) {
837d783d3b4Sdlg 	case SENSOR_TEMP:
8381e8e22a4Skettenis 		value *= sc->sc_cpu_diode_scale;
8391e8e22a4Skettenis 		value >>= 3;
8401e8e22a4Skettenis 		value += ((int64_t)sc->sc_cpu_diode_offset) << 9;
8411e8e22a4Skettenis 		value <<= 1;
8421e8e22a4Skettenis 
8431e8e22a4Skettenis 		/* Convert from 16.16 fixed point degC into muK. */
8441e8e22a4Skettenis 		value *= 15625;
8451e8e22a4Skettenis 		value /= 1024;
8461e8e22a4Skettenis 		value += 273150000;
847d783d3b4Sdlg 		break;
848d783d3b4Sdlg 
849d783d3b4Sdlg 	case SENSOR_VOLTS_DC:
8501e8e22a4Skettenis 		value *= sc->sc_cpu_volt_scale;
8511e8e22a4Skettenis 		value += sc->sc_cpu_volt_offset;
8521e8e22a4Skettenis 		value <<= 4;
8531e8e22a4Skettenis 
8541e8e22a4Skettenis 		/* Convert from 16.16 fixed point V into muV. */
8551e8e22a4Skettenis 		value *= 15625;
8561e8e22a4Skettenis 		value /= 1024;
857d783d3b4Sdlg 		break;
858d783d3b4Sdlg 
859d783d3b4Sdlg 	case SENSOR_AMPS:
8601e8e22a4Skettenis 		value *= sc->sc_cpu_curr_scale;
8611e8e22a4Skettenis 		value += sc->sc_cpu_curr_offset;
8621e8e22a4Skettenis 		value <<= 4;
8631e8e22a4Skettenis 
8641e8e22a4Skettenis 		/* Convert from 16.16 fixed point A into muA. */
8651e8e22a4Skettenis 		value *= 15625;
8661e8e22a4Skettenis 		value /= 1024;
867d783d3b4Sdlg 		break;
868d783d3b4Sdlg 
8690367fc9dSkettenis 	case SENSOR_WATTS:
8700367fc9dSkettenis 		value *= sc->sc_slots_pow_scale;
8710367fc9dSkettenis 		value += sc->sc_slots_pow_offset;
8720367fc9dSkettenis 		value <<= 4;
8730367fc9dSkettenis 
8740367fc9dSkettenis 		/* Convert from 16.16 fixed point W into muW. */
8750367fc9dSkettenis 		value *= 15625;
8760367fc9dSkettenis 		value /= 1024;
8770367fc9dSkettenis 		break;
8780367fc9dSkettenis 
879d783d3b4Sdlg 	default:
880d783d3b4Sdlg 		break;
8811e8e22a4Skettenis 	}
882309d5690Smglocker 	if (update_sysctl) {
8831e8e22a4Skettenis 		sensor->sensor.value = value;
8841e8e22a4Skettenis 		sensor->sensor.flags = 0;
885309d5690Smglocker 	}
886309d5690Smglocker 	return (value);
8871e8e22a4Skettenis }
8881e8e22a4Skettenis 
8891e8e22a4Skettenis void
smu_refresh_sensors(void * arg)8901e8e22a4Skettenis smu_refresh_sensors(void *arg)
8911e8e22a4Skettenis {
8921e8e22a4Skettenis 	struct smu_softc *sc = arg;
8931e8e22a4Skettenis 	int i;
8941e8e22a4Skettenis 
895769768baSthib 	rw_enter_write(&sc->sc_lock);
8961e8e22a4Skettenis 	for (i = 0; i < sc->sc_num_sensors; i++)
897309d5690Smglocker 		smu_sensor_refresh(sc, &sc->sc_sensors[i], 1);
8981e8e22a4Skettenis 	for (i = 0; i < sc->sc_num_fans; i++)
8991e8e22a4Skettenis 		smu_fan_refresh(sc, &sc->sc_fans[i]);
900769768baSthib 	rw_exit_write(&sc->sc_lock);
90126c1ffa3Skettenis }
9022e975dd8Skettenis 
903309d5690Smglocker /*
904309d5690Smglocker  * Wrapper functions for the thermal management framework.
905309d5690Smglocker  */
906309d5690Smglocker int
smu_fan_set_rpm_thermal(struct smu_fan * fan,int rpm)907309d5690Smglocker smu_fan_set_rpm_thermal(struct smu_fan *fan, int rpm)
908309d5690Smglocker {
909309d5690Smglocker 	struct smu_softc *sc = smu_cd.cd_devs[0];
910309d5690Smglocker 
911309d5690Smglocker 	rw_enter_write(&sc->sc_lock);
912309d5690Smglocker 	(void)smu_fan_set_rpm(sc, fan, rpm);
913309d5690Smglocker 	rw_exit_write(&sc->sc_lock);
914309d5690Smglocker 
915309d5690Smglocker 	return (0);
916309d5690Smglocker }
917309d5690Smglocker 
918309d5690Smglocker int
smu_fan_set_pwm_thermal(struct smu_fan * fan,int pwm)919309d5690Smglocker smu_fan_set_pwm_thermal(struct smu_fan *fan, int pwm)
920309d5690Smglocker {
921309d5690Smglocker 	struct smu_softc *sc = smu_cd.cd_devs[0];
922309d5690Smglocker 
923309d5690Smglocker 	rw_enter_write(&sc->sc_lock);
924309d5690Smglocker 	(void)smu_fan_set_pwm(sc, fan, pwm);
925309d5690Smglocker 	rw_exit_write(&sc->sc_lock);
926309d5690Smglocker 
927309d5690Smglocker 	return (0);
928309d5690Smglocker }
929309d5690Smglocker 
930309d5690Smglocker int
smu_sensor_refresh_thermal(struct smu_sensor * sensor)931309d5690Smglocker smu_sensor_refresh_thermal(struct smu_sensor *sensor)
932309d5690Smglocker {
933309d5690Smglocker 	struct smu_softc *sc = smu_cd.cd_devs[0];
934309d5690Smglocker 	int value;
935309d5690Smglocker 
936309d5690Smglocker 	rw_enter_write(&sc->sc_lock);
937309d5690Smglocker 	value = smu_sensor_refresh(sc, sensor, 0);
938309d5690Smglocker 	rw_exit_write(&sc->sc_lock);
939309d5690Smglocker 
940309d5690Smglocker 	return (value);
941309d5690Smglocker }
942309d5690Smglocker 
9432e975dd8Skettenis int
smu_i2c_acquire_bus(void * cookie,int flags)9442e975dd8Skettenis smu_i2c_acquire_bus(void *cookie, int flags)
9452e975dd8Skettenis {
9462e975dd8Skettenis 	struct smu_softc *sc = cookie;
9472e975dd8Skettenis 
9482e975dd8Skettenis 	if (flags & I2C_F_POLL)
9492e975dd8Skettenis 		return (0);
9502e975dd8Skettenis 
951769768baSthib 	return (rw_enter(&sc->sc_lock, RW_WRITE));
9522e975dd8Skettenis }
9532e975dd8Skettenis 
9542e975dd8Skettenis void
smu_i2c_release_bus(void * cookie,int flags)9552e975dd8Skettenis smu_i2c_release_bus(void *cookie, int flags)
9562e975dd8Skettenis {
9572e975dd8Skettenis 	struct smu_softc *sc = cookie;
9582e975dd8Skettenis 
9592e975dd8Skettenis         if (flags & I2C_F_POLL)
9602e975dd8Skettenis                 return;
9612e975dd8Skettenis 
962769768baSthib 	rw_exit(&sc->sc_lock);
9632e975dd8Skettenis }
9642e975dd8Skettenis 
9652e975dd8Skettenis int
smu_i2c_exec(void * cookie,i2c_op_t op,i2c_addr_t addr,const void * cmdbuf,size_t cmdlen,void * buf,size_t len,int flags)9662e975dd8Skettenis smu_i2c_exec(void *cookie, i2c_op_t op, i2c_addr_t addr,
9672e975dd8Skettenis     const void *cmdbuf, size_t cmdlen, void *buf, size_t len, int flags)
9682e975dd8Skettenis {
9692e975dd8Skettenis 	struct smu_softc *sc = cookie;
9702e975dd8Skettenis 	struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd;
9712e975dd8Skettenis 	u_int8_t smu_op = SMU_I2C_NORMAL;
9722e975dd8Skettenis 	int error, retries = 10;
9732e975dd8Skettenis 
9742e975dd8Skettenis 	if (!I2C_OP_STOP_P(op) || cmdlen > 3 || len > 5)
9752e975dd8Skettenis 		return (EINVAL);
9762e975dd8Skettenis 
9772e975dd8Skettenis 	if(cmdlen == 0)
9782e975dd8Skettenis 		smu_op = SMU_I2C_SIMPLE;
9792e975dd8Skettenis 	else if (I2C_OP_READ_P(op))
9802e975dd8Skettenis 		smu_op = SMU_I2C_COMBINED;
9812e975dd8Skettenis 
9822e975dd8Skettenis 	cmd->cmd = SMU_I2C;
9832e975dd8Skettenis 	cmd->len = 9 + len;
9842e975dd8Skettenis 	cmd->data[0] = 0xb;
9852e975dd8Skettenis 	cmd->data[1] = smu_op;
9862e975dd8Skettenis 	cmd->data[2] = addr << 1;
9872e975dd8Skettenis 	cmd->data[3] = cmdlen;
9882e975dd8Skettenis 	memcpy (&cmd->data[4], cmdbuf, cmdlen);
9892e975dd8Skettenis 	cmd->data[7] = addr << 1 | I2C_OP_READ_P(op);
9902e975dd8Skettenis 	cmd->data[8] = len;
9912e975dd8Skettenis 	memcpy(&cmd->data[9], buf, len);
9922e975dd8Skettenis 
9932e975dd8Skettenis 	error = smu_do_cmd(sc, 250);
9942e975dd8Skettenis 	if (error)
9952e975dd8Skettenis 		return error;
9962e975dd8Skettenis 
9972e975dd8Skettenis 	while (retries--) {
9982e975dd8Skettenis 		cmd->cmd = SMU_I2C;
9992e975dd8Skettenis 		cmd->len = 1;
10002e975dd8Skettenis 		cmd->data[0] = 0;
10012e975dd8Skettenis 		memset(&cmd->data[1], 0xff, len);
10022e975dd8Skettenis 
10032e975dd8Skettenis 		error = smu_do_cmd(sc, 250);
10042e975dd8Skettenis 		if (error)
10052e975dd8Skettenis 			return error;
10062e975dd8Skettenis 
10072e975dd8Skettenis 		if ((cmd->data[0] & 0x80) == 0)
10082e975dd8Skettenis 			break;
10092e975dd8Skettenis 		if (cmd->data[0] == 0xfd)
10102e975dd8Skettenis 			break;
10112e975dd8Skettenis 
10122e975dd8Skettenis 		DELAY(15 * 1000);
10132e975dd8Skettenis 	}
10142e975dd8Skettenis 
10152e975dd8Skettenis 	if (cmd->data[0] & 0x80)
10162e975dd8Skettenis 		return (EIO);
10172e975dd8Skettenis 
10182e975dd8Skettenis 	if (I2C_OP_READ_P(op))
10192e975dd8Skettenis 		memcpy(buf, &cmd->data[1], len);
10202e975dd8Skettenis 	return (0);
10212e975dd8Skettenis }
1022b6032f37Skettenis 
1023b6032f37Skettenis void
smu_slew_voltage(u_int freq_scale)1024b6032f37Skettenis smu_slew_voltage(u_int freq_scale)
1025b6032f37Skettenis {
1026b6032f37Skettenis 	struct smu_softc *sc = smu_cd.cd_devs[0];
1027b6032f37Skettenis 	struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd;
1028b6032f37Skettenis 
1029be3e4ce7Skettenis 	rw_enter_write(&sc->sc_lock);
1030be3e4ce7Skettenis 
1031b6032f37Skettenis 	cmd->cmd = SMU_POWER;
1032b6032f37Skettenis 	cmd->len = 8;
1033b6032f37Skettenis 	memcpy(cmd->data, "VSLEW", 5);
1034b6032f37Skettenis 	cmd->data[5] = 0xff;
1035b6032f37Skettenis 	cmd->data[6] = 1;
1036b6032f37Skettenis 	cmd->data[7] = freq_scale;
1037b6032f37Skettenis 
1038b6032f37Skettenis 	smu_do_cmd(sc, 250);
1039be3e4ce7Skettenis 
1040be3e4ce7Skettenis 	rw_exit_write(&sc->sc_lock);
1041b6032f37Skettenis }
1042