1/*	$NetBSD: tda.c,v 1.3 2010/03/11 04:19:56 mrg Exp $	*/
2/*	$OpenBSD: tda.c,v 1.4 2008/02/27 17:25:00 robert Exp $ */
3
4/*
5 * Copyright (c) 2008 Robert Nagy <robert@openbsd.org>
6 * Copyright (c) 2008 Mark Kettenis <kettenis@openbsd.org>
7 *
8 * Permission to use, copy, modify, and distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
11 *
12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 */
20
21#include <sys/param.h>
22#include <sys/systm.h>
23#include <sys/kernel.h>
24#include <sys/device.h>
25#include <dev/sysmon/sysmonvar.h>
26#include <dev/sysmon/sysmon_taskq.h>
27
28#include <machine/autoconf.h>
29#include <machine/openfirm.h>
30
31#include <dev/i2c/i2cvar.h>
32
33/* fan control registers */
34#define TDA_SYSFAN_REG		0xf0
35#define TDA_CPUFAN_REG		0xf2
36#define TDA_PSFAN_REG		0xf4
37
38#define TDA_FANSPEED_MIN        0x0c
39#define TDA_FANSPEED_MAX        0x3f
40
41#define TDA_PSFAN_ON            0x1f
42#define TDA_PSFAN_OFF           0x00
43
44/* Internal and External temperature senor numbers */
45#define SENSOR_TEMP_EXT		0
46#define SENSOR_TEMP_INT		1
47
48#define CPU_TEMP_MAX		(67 * 1000000 + 273150000)
49#define CPU_TEMP_MIN		(57 * 1000000 + 273150000)
50#define SYS_TEMP_MAX		(30 * 1000000 + 273150000)
51#define SYS_TEMP_MIN		(20 * 1000000 + 273150000)
52
53struct tda_softc {
54	device_t		sc_dev;
55	i2c_tag_t		sc_tag;
56	i2c_addr_t		sc_addr;
57
58	u_int16_t		sc_cfan_speed;	/* current CPU fan speed */
59	u_int16_t		sc_sfan_speed;	/* current SYS fan speed */
60
61	callout_t		sc_timer;
62};
63
64int	tda_match(struct device *, struct cfdata *, void *);
65void	tda_attach(struct device *, struct device *, void *);
66
67void	tda_setspeed(struct tda_softc *);
68static void	tda_adjust(void *);
69static void	tda_timeout(void *);
70
71
72CFATTACH_DECL_NEW(tda, sizeof(struct tda_softc),
73	tda_match, tda_attach, NULL, NULL);
74
75int
76tda_match(struct device *parent, struct cfdata *match, void *aux)
77{
78	struct i2c_attach_args *ia = aux;
79	char name[32];
80
81	/* Only attach on the Sun Blade 1000/2000. */
82	if (OF_getprop(findroot(), "name", name, sizeof(name)) <= 0)
83		return (0);
84	if (strcmp(name, "SUNW,Sun-Blade-1000") != 0)
85		return (0);
86
87	/*
88	 * No need for "compatible" matching, we know exactly what
89	 * firmware calls us.
90	 */
91	if (ia->ia_name == NULL)
92		return(0);
93	return strcmp(ia->ia_name, "fan-control") == 0;
94}
95
96void
97tda_attach(struct device *parent, struct device *self, void *aux)
98{
99	struct tda_softc *sc = device_private(self);
100	struct i2c_attach_args *ia = aux;
101
102	sc->sc_dev = self;
103	sc->sc_tag = ia->ia_tag;
104	sc->sc_addr = ia->ia_addr;
105
106	aprint_normal(": %s\n", ia->ia_name);
107	aprint_naive(": Environment sensor\n");
108
109	/*
110	 * Set the fans to maximum speed and save the power levels;
111	 * the controller is write-only.
112	 */
113	sc->sc_cfan_speed = sc->sc_sfan_speed = (TDA_FANSPEED_MAX+TDA_FANSPEED_MIN)/2;
114	tda_setspeed(sc);
115
116	callout_init(&sc->sc_timer, CALLOUT_MPSAFE);
117	callout_reset(&sc->sc_timer, hz*20, tda_timeout, sc);
118}
119
120static void
121tda_timeout(void *v)
122{
123	struct tda_softc *sc = v;
124
125	sysmon_task_queue_sched(0, tda_adjust, sc);
126	callout_reset(&sc->sc_timer, hz*60, tda_timeout, sc);
127}
128
129void
130tda_setspeed(struct tda_softc *sc)
131{
132	u_int8_t cmd[2];
133
134	if (sc->sc_cfan_speed < TDA_FANSPEED_MIN)
135		sc->sc_cfan_speed = TDA_FANSPEED_MIN;
136	if (sc->sc_sfan_speed < TDA_FANSPEED_MIN)
137		sc->sc_sfan_speed = TDA_FANSPEED_MIN;
138	if (sc->sc_cfan_speed > TDA_FANSPEED_MAX)
139		sc->sc_cfan_speed = TDA_FANSPEED_MAX;
140	if (sc->sc_sfan_speed > TDA_FANSPEED_MAX)
141		sc->sc_sfan_speed = TDA_FANSPEED_MAX;
142
143	iic_acquire_bus(sc->sc_tag, 0);
144
145	cmd[0] = TDA_CPUFAN_REG;
146	cmd[1] = sc->sc_cfan_speed;
147	if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
148	    sc->sc_addr, &cmd, sizeof(cmd), NULL, 0, 0)) {
149		aprint_error_dev(sc->sc_dev, "cannot write cpu-fan register\n");
150		iic_release_bus(sc->sc_tag, 0);
151		return;
152        }
153
154	cmd[0] = TDA_SYSFAN_REG;
155	cmd[1] = sc->sc_sfan_speed;
156	if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
157	    sc->sc_addr, &cmd, sizeof(cmd), NULL, 0, 0)) {
158		aprint_error_dev(sc->sc_dev, "cannot write system-fan register\n");
159		iic_release_bus(sc->sc_tag, 0);
160		return;
161        }
162
163	iic_release_bus(sc->sc_tag, 0);
164
165	aprint_debug_dev(sc->sc_dev, "changed fan speed to cpu=%d system=%d\n",
166		sc->sc_cfan_speed, sc->sc_sfan_speed);
167}
168
169static bool
170is_cpu_sensor(const envsys_data_t *edata)
171{
172	if (edata->units != ENVSYS_STEMP)
173		return false;
174	return strcmp(edata->desc, "external") == 0;
175}
176
177static bool
178is_system_sensor(const envsys_data_t *edata)
179{
180	if (edata->units != ENVSYS_STEMP)
181		return false;
182	return strcmp(edata->desc, "internal") == 0;
183}
184
185static void
186tda_adjust(void *v)
187{
188	struct tda_softc *sc = v;
189	u_int64_t ctemp, stemp;
190	u_int16_t cspeed, sspeed;
191
192	/* Default to running the fans at maximum speed. */
193	sspeed = cspeed = TDA_FANSPEED_MAX;
194
195	/* fetch maximum current temperature */
196	ctemp = sysmon_envsys_get_max_value(is_cpu_sensor, true);
197	stemp = sysmon_envsys_get_max_value(is_system_sensor, true);
198
199	/* the predicates for selecting sensors must have gone wrong */
200	if (ctemp == 0 || stemp == 0) {
201		aprint_error_dev(sc->sc_dev, "skipping temp adjustment"
202			" - no sensor values\n");
203		return;
204	}
205
206	aprint_debug_dev(sc->sc_dev, "current temperature: cpu %" PRIu64
207		" system %" PRIu64 "\n",
208		ctemp, stemp);
209
210	if (ctemp < CPU_TEMP_MIN)
211		cspeed = TDA_FANSPEED_MIN;
212	else if (ctemp < CPU_TEMP_MAX)
213		cspeed = TDA_FANSPEED_MIN +
214			(ctemp - CPU_TEMP_MIN) *
215			(TDA_FANSPEED_MAX - TDA_FANSPEED_MIN) /
216			(CPU_TEMP_MAX - CPU_TEMP_MIN);
217
218	if (stemp < SYS_TEMP_MIN)
219		sspeed = TDA_FANSPEED_MIN;
220	else if (stemp < SYS_TEMP_MAX)
221		sc->sc_sfan_speed = TDA_FANSPEED_MIN +
222			(stemp - SYS_TEMP_MIN) *
223			(TDA_FANSPEED_MAX - TDA_FANSPEED_MIN) /
224			(SYS_TEMP_MAX - SYS_TEMP_MIN);
225
226	if (sspeed == sc->sc_sfan_speed && cspeed == sc->sc_cfan_speed)
227		return;
228
229	sc->sc_sfan_speed = sspeed;
230	sc->sc_cfan_speed = cspeed;
231	tda_setspeed(sc);
232}
233