xref: /netbsd-src/sys/dev/pci/viaenv.c (revision 8b0f9554ff8762542c4defc4f70e1eb76fb508fa)
1 /*	$NetBSD: viaenv.c,v 1.26 2007/11/19 12:16:44 njoly Exp $	*/
2 
3 /*
4  * Copyright (c) 2000 Johan Danielsson
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  *
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * 3. Neither the name of author nor the names of any contributors may
19  *    be used to endorse or promote products derived from this
20  *    software without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS
23  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
24  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
25  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
26  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  */
34 
35 /*
36  * Driver for the hardware monitoring and power management timer
37  * in the VIA VT82C686A and VT8231 South Bridges.
38  */
39 
40 #include <sys/cdefs.h>
41 __KERNEL_RCSID(0, "$NetBSD: viaenv.c,v 1.26 2007/11/19 12:16:44 njoly Exp $");
42 
43 #include <sys/param.h>
44 #include <sys/systm.h>
45 #include <sys/kernel.h>
46 #include <sys/device.h>
47 
48 #include <sys/bus.h>
49 #include <dev/ic/acpipmtimer.h>
50 
51 #include <dev/pci/pcivar.h>
52 #include <dev/pci/pcireg.h>
53 #include <dev/pci/pcidevs.h>
54 
55 #include <dev/sysmon/sysmonvar.h>
56 
57 #ifdef VIAENV_DEBUG
58 unsigned int viaenv_debug = 0;
59 #define DPRINTF(X) do { if (viaenv_debug) printf X ; } while(0)
60 #else
61 #define DPRINTF(X)
62 #endif
63 
64 #define VIANUMSENSORS 10	/* three temp, two fan, five voltage */
65 
66 struct viaenv_softc {
67 	struct device sc_dev;
68 	bus_space_tag_t sc_iot;
69 	bus_space_handle_t sc_ioh;
70 	bus_space_handle_t sc_pm_ioh;
71 
72 	int     sc_fan_div[2];	/* fan RPM divisor */
73 
74 	struct sysmon_envsys *sc_sme;
75 	envsys_data_t sc_sensor[VIANUMSENSORS];
76 
77 	struct timeval sc_lastread;
78 };
79 
80 /* autoconf(9) glue */
81 static int viaenv_match(struct device *, struct cfdata *, void *);
82 static void viaenv_attach(struct device *, struct device *, void *);
83 
84 CFATTACH_DECL(viaenv, sizeof(struct viaenv_softc),
85     viaenv_match, viaenv_attach, NULL, NULL);
86 
87 /* envsys(4) glue */
88 static void viaenv_refresh(struct sysmon_envsys *, envsys_data_t *);
89 
90 static int val_to_uK(unsigned int);
91 static int val_to_rpm(unsigned int, int);
92 static long val_to_uV(unsigned int, int);
93 
94 static int
95 viaenv_match(struct device *parent, struct cfdata *match, void *aux)
96 {
97 	struct pci_attach_args *pa = (struct pci_attach_args *)aux;
98 
99 	if (PCI_VENDOR(pa->pa_id) != PCI_VENDOR_VIATECH)
100 		return 0;
101 
102 	switch (PCI_PRODUCT(pa->pa_id)) {
103 	case PCI_PRODUCT_VIATECH_VT82C686A_SMB:
104 	case PCI_PRODUCT_VIATECH_VT8231_PWR:
105 		return 1;
106 	default:
107 		return 0;
108 	}
109 }
110 
111 /*
112  * XXX there doesn't seem to exist much hard documentation on how to
113  * convert the raw values to usable units, this code is more or less
114  * stolen from the Linux driver, but changed to suit our conditions
115  */
116 
117 /*
118  * lookup-table to translate raw values to uK, this is the same table
119  * used by the Linux driver (modulo units); there is a fifth degree
120  * polynomial that supposedly been used to generate this table, but I
121  * haven't been able to figure out how -- it doesn't give the same values
122  */
123 
124 static const long val_to_temp[] = {
125 	20225, 20435, 20645, 20855, 21045, 21245, 21425, 21615, 21785, 21955,
126 	22125, 22285, 22445, 22605, 22755, 22895, 23035, 23175, 23315, 23445,
127 	23565, 23695, 23815, 23925, 24045, 24155, 24265, 24365, 24465, 24565,
128 	24665, 24765, 24855, 24945, 25025, 25115, 25195, 25275, 25355, 25435,
129 	25515, 25585, 25655, 25725, 25795, 25865, 25925, 25995, 26055, 26115,
130 	26175, 26235, 26295, 26355, 26405, 26465, 26515, 26575, 26625, 26675,
131 	26725, 26775, 26825, 26875, 26925, 26975, 27025, 27065, 27115, 27165,
132 	27205, 27255, 27295, 27345, 27385, 27435, 27475, 27515, 27565, 27605,
133 	27645, 27685, 27735, 27775, 27815, 27855, 27905, 27945, 27985, 28025,
134 	28065, 28105, 28155, 28195, 28235, 28275, 28315, 28355, 28405, 28445,
135 	28485, 28525, 28565, 28615, 28655, 28695, 28735, 28775, 28825, 28865,
136 	28905, 28945, 28995, 29035, 29075, 29125, 29165, 29205, 29245, 29295,
137 	29335, 29375, 29425, 29465, 29505, 29555, 29595, 29635, 29685, 29725,
138 	29765, 29815, 29855, 29905, 29945, 29985, 30035, 30075, 30125, 30165,
139 	30215, 30255, 30305, 30345, 30385, 30435, 30475, 30525, 30565, 30615,
140 	30655, 30705, 30755, 30795, 30845, 30885, 30935, 30975, 31025, 31075,
141 	31115, 31165, 31215, 31265, 31305, 31355, 31405, 31455, 31505, 31545,
142 	31595, 31645, 31695, 31745, 31805, 31855, 31905, 31955, 32005, 32065,
143 	32115, 32175, 32225, 32285, 32335, 32395, 32455, 32515, 32575, 32635,
144 	32695, 32755, 32825, 32885, 32955, 33025, 33095, 33155, 33235, 33305,
145 	33375, 33455, 33525, 33605, 33685, 33765, 33855, 33935, 34025, 34115,
146 	34205, 34295, 34395, 34495, 34595, 34695, 34805, 34905, 35015, 35135,
147 	35245, 35365, 35495, 35615, 35745, 35875, 36015, 36145, 36295, 36435,
148 	36585, 36745, 36895, 37065, 37225, 37395, 37575, 37755, 37935, 38125,
149 	38325, 38525, 38725, 38935, 39155, 39375, 39605, 39835, 40075, 40325,
150 	40575, 40835, 41095, 41375, 41655, 41935,
151 };
152 
153 /* use above table to convert values to temperatures in micro-Kelvins */
154 static int
155 val_to_uK(unsigned int val)
156 {
157 	int     i = val / 4;
158 	int     j = val % 4;
159 
160 	assert(i >= 0 && i <= 255);
161 
162 	if (j == 0 || i == 255)
163 		return val_to_temp[i] * 10000;
164 
165 	/* is linear interpolation ok? */
166 	return (val_to_temp[i] * (4 - j) +
167 	    val_to_temp[i + 1] * j) * 2500 /* really: / 4 * 10000 */ ;
168 }
169 
170 static int
171 val_to_rpm(unsigned int val, int div)
172 {
173 
174 	if (val == 0)
175 		return 0;
176 
177 	return 1350000 / val / div;
178 }
179 
180 static long
181 val_to_uV(unsigned int val, int index)
182 {
183 	static const long mult[] =
184 	    {1250000, 1250000, 1670000, 2600000, 6300000};
185 
186 	assert(index >= 0 && index <= 4);
187 
188 	return (25LL * val + 133) * mult[index] / 2628;
189 }
190 
191 #define VIAENV_TSENS3	0x1f
192 #define VIAENV_TSENS1	0x20
193 #define VIAENV_TSENS2	0x21
194 #define VIAENV_VSENS1	0x22
195 #define VIAENV_VSENS2	0x23
196 #define VIAENV_VCORE	0x24
197 #define VIAENV_VSENS3	0x25
198 #define VIAENV_VSENS4	0x26
199 #define VIAENV_FAN1	0x29
200 #define VIAENV_FAN2	0x2a
201 #define VIAENV_FANCONF	0x47	/* fan configuration */
202 #define VIAENV_TLOW	0x49	/* temperature low order value */
203 #define VIAENV_TIRQ	0x4b	/* temperature interrupt configuration */
204 
205 #define VIAENV_GENCFG	0x40	/* general configuration */
206 #define VIAENV_GENCFG_TMR32	(1 << 11)	/* 32-bit PM timer */
207 #define VIAENV_GENCFG_PMEN	(1 << 15)	/* enable PM I/O space */
208 #define VIAENV_PMBASE	0x48	/* power management I/O space base */
209 #define VIAENV_PMSIZE	128	/* HWM and power management I/O space size */
210 #define VIAENV_PM_TMR	0x08	/* PM timer */
211 #define VIAENV_HWMON_CONF	0x70	/* HWMon I/O base */
212 #define VIAENV_HWMON_CTL	0x74	/* HWMon control register */
213 
214 static void
215 viaenv_refresh_sensor_data(struct viaenv_softc *sc, envsys_data_t *edata)
216 {
217 	static const struct timeval onepointfive =  { 1, 500000 };
218 	static int old_sensor = -1;
219 	struct timeval t, utv;
220 	uint8_t v, v2;
221 	int i;
222 
223 	/* Read new values at most once every 1.5 seconds. */
224 	timeradd(&sc->sc_lastread, &onepointfive, &t);
225 	getmicrouptime(&utv);
226 	i = timercmp(&utv, &t, >);
227 	if (i)
228 		sc->sc_lastread = utv;
229 
230 	if (i == 0 && old_sensor == edata->sensor)
231 		return;
232 
233 	old_sensor = edata->sensor;
234 
235 	/* temperature */
236 	if (edata->sensor == 0) {
237 		v = bus_space_read_1(sc->sc_iot, sc->sc_ioh, VIAENV_TIRQ);
238 		v2 = bus_space_read_1(sc->sc_iot, sc->sc_ioh, VIAENV_TSENS1);
239 		DPRINTF(("TSENS1 = %d\n", (v2 << 2) | (v >> 6)));
240 		edata->value_cur = val_to_uK((v2 << 2) | (v >> 6));
241 		edata->state = ENVSYS_SVALID;
242 	} else if (edata->sensor == 1) {
243 		v = bus_space_read_1(sc->sc_iot, sc->sc_ioh, VIAENV_TLOW);
244 		v2 = bus_space_read_1(sc->sc_iot, sc->sc_ioh, VIAENV_TSENS2);
245 		DPRINTF(("TSENS2 = %d\n", (v2 << 2) | ((v >> 4) & 0x3)));
246 		edata->value_cur = val_to_uK((v2 << 2) | ((v >> 4) & 0x3));
247 		edata->state = ENVSYS_SVALID;
248 	} else if (edata->sensor == 2) {
249 		v = bus_space_read_1(sc->sc_iot, sc->sc_ioh, VIAENV_TLOW);
250 		v2 = bus_space_read_1(sc->sc_iot, sc->sc_ioh, VIAENV_TSENS3);
251 		DPRINTF(("TSENS3 = %d\n", (v2 << 2) | (v >> 6)));
252 		edata->value_cur = val_to_uK((v2 << 2) | (v >> 6));
253 		edata->state = ENVSYS_SVALID;
254 	} else if (edata->sensor > 2 && edata->sensor < 5) {
255 		/* fans */
256 		v = bus_space_read_1(sc->sc_iot, sc->sc_ioh, VIAENV_FANCONF);
257 
258 		sc->sc_fan_div[0] = 1 << ((v >> 4) & 0x3);
259 		sc->sc_fan_div[1] = 1 << ((v >> 6) & 0x3);
260 
261 		v = bus_space_read_1(sc->sc_iot, sc->sc_ioh,
262 		    VIAENV_FAN1 + edata->sensor - 3);
263 		DPRINTF(("FAN%d = %d / %d\n", edata->sensor - 3, v,
264 		    sc->sc_fan_div[edata->sensor - 3]));
265 		edata->value_cur = val_to_rpm(v,
266 		    sc->sc_fan_div[edata->sensor - 3]);
267 		edata->state = ENVSYS_SVALID;
268 	} else {
269 		v = bus_space_read_1(sc->sc_iot, sc->sc_ioh,
270 		    VIAENV_VSENS1 + edata->sensor - 5);
271 		DPRINTF(("V%d = %d\n", edata->sensor - 5, v));
272 		edata->value_cur = val_to_uV(v, edata->sensor - 5);
273 		edata->state = ENVSYS_SVALID;
274 	}
275 }
276 
277 static void
278 viaenv_attach(struct device *parent, struct device *self, void *aux)
279 {
280 	struct viaenv_softc *sc = (struct viaenv_softc *)self;
281 	struct pci_attach_args *pa = (struct pci_attach_args *)aux;
282 	pcireg_t iobase, control;
283 	int i;
284 
285 	aprint_naive("\n");
286 	aprint_normal(": VIA Technologies ");
287 	switch (PCI_PRODUCT(pa->pa_id)) {
288 	case PCI_PRODUCT_VIATECH_VT82C686A_SMB:
289 		aprint_normal("VT82C686A Hardware Monitor\n");
290 		break;
291 	case PCI_PRODUCT_VIATECH_VT8231_PWR:
292 		aprint_normal("VT8231 Hardware Monitor\n");
293 		break;
294 	default:
295 		aprint_normal("Unknown Hardware Monitor\n");
296 		break;
297 	}
298 
299 	iobase = pci_conf_read(pa->pa_pc, pa->pa_tag, VIAENV_HWMON_CONF);
300 	DPRINTF(("%s: iobase 0x%x\n", sc->sc_dev.dv_xname, iobase));
301 	control = pci_conf_read(pa->pa_pc, pa->pa_tag, VIAENV_HWMON_CTL);
302 
303 	/* Check if the Hardware Monitor enable bit is set */
304 	if ((control & 1) == 0) {
305 		aprint_normal("%s : Hardware Monitor disabled\n",
306 		    sc->sc_dev.dv_xname);
307 		goto nohwm;
308 	}
309 
310 	/* Map Hardware Monitor I/O space */
311 	sc->sc_iot = pa->pa_iot;
312 	if (bus_space_map(sc->sc_iot, iobase & 0xff80,
313 	    VIAENV_PMSIZE, 0, &sc->sc_ioh)) {
314 		aprint_error("%s: failed to map I/O space\n",
315 		    sc->sc_dev.dv_xname);
316 		goto nohwm;
317 	}
318 
319 	for (i = 0; i < 3; i++)
320 		sc->sc_sensor[i].units = ENVSYS_STEMP;
321 
322 #define COPYDESCR(x, y) 				\
323 	do {						\
324 		strlcpy((x), (y), sizeof(x));		\
325 	} while (0)
326 
327 	COPYDESCR(sc->sc_sensor[0].desc, "TSENS1");
328 	COPYDESCR(sc->sc_sensor[1].desc, "TSENS2");
329 	COPYDESCR(sc->sc_sensor[2].desc, "TSENS3");
330 
331 	for (i = 3; i < 5; i++)
332 		sc->sc_sensor[i].units = ENVSYS_SFANRPM;
333 
334 	COPYDESCR(sc->sc_sensor[3].desc, "FAN1");
335 	COPYDESCR(sc->sc_sensor[4].desc, "FAN2");
336 
337 	for (i = 5; i < 10; i++)
338 		sc->sc_sensor[i].units = ENVSYS_SVOLTS_DC;
339 
340 	COPYDESCR(sc->sc_sensor[5].desc, "VSENS1");	/* CPU core (2V) */
341 	COPYDESCR(sc->sc_sensor[6].desc, "VSENS2");	/* NB core? (2.5V) */
342 	COPYDESCR(sc->sc_sensor[7].desc, "Vcore");	/* Vcore (3.3V) */
343 	COPYDESCR(sc->sc_sensor[8].desc, "VSENS3");	/* VSENS3 (5V) */
344 	COPYDESCR(sc->sc_sensor[9].desc, "VSENS4");	/* VSENS4 (12V) */
345 
346 #undef COPYDESCR
347 
348 	sc->sc_sme = sysmon_envsys_create();
349 
350 	/* Initialize sensors */
351 	for (i = 0; i < VIANUMSENSORS; i++) {
352 		if (sysmon_envsys_sensor_attach(sc->sc_sme,
353 						&sc->sc_sensor[i])) {
354 			sysmon_envsys_destroy(sc->sc_sme);
355 			return;
356 		}
357 	}
358 
359 	/*
360 	 * Hook into the System Monitor.
361 	 */
362 	sc->sc_sme->sme_name = sc->sc_dev.dv_xname;
363 	sc->sc_sme->sme_cookie = sc;
364 	sc->sc_sme->sme_refresh = viaenv_refresh;
365 
366 	if (sysmon_envsys_register(sc->sc_sme)) {
367 		printf("%s: unable to register with sysmon\n",
368 		    sc->sc_dev.dv_xname);
369 		sysmon_envsys_destroy(sc->sc_sme);
370 		return;
371 	}
372 
373 nohwm:
374 	/* Check if power management I/O space is enabled */
375 	control = pci_conf_read(pa->pa_pc, pa->pa_tag, VIAENV_GENCFG);
376 	if ((control & VIAENV_GENCFG_PMEN) == 0) {
377                 aprint_normal("%s: Power Managament controller disabled\n",
378 		    sc->sc_dev.dv_xname);
379                 goto nopm;
380         }
381 
382         /* Map power management I/O space */
383         iobase = pci_conf_read(pa->pa_pc, pa->pa_tag, VIAENV_PMBASE);
384         if (bus_space_map(sc->sc_iot, PCI_MAPREG_IO_ADDR(iobase),
385             VIAENV_PMSIZE, 0, &sc->sc_pm_ioh)) {
386                 aprint_error("%s: failed to map PM I/O space\n",
387 		    sc->sc_dev.dv_xname);
388                 goto nopm;
389         }
390 
391 	/* Attach our PM timer with the generic acpipmtimer function */
392 	acpipmtimer_attach(&sc->sc_dev, sc->sc_iot, sc->sc_pm_ioh,
393 	    VIAENV_PM_TMR,
394 	    ((control & VIAENV_GENCFG_TMR32) ? ACPIPMT_32BIT : 0));
395 
396 nopm:
397 	return;
398 }
399 
400 static void
401 viaenv_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
402 {
403 	struct viaenv_softc *sc = sme->sme_cookie;
404 
405 	viaenv_refresh_sensor_data(sc, edata);
406 }
407