1 /*	$NetBSD: viaenv.c,v 1.33 2014/08/11 06:02:38 ozaki-r 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.33 2014/08/11 06:02:38 ozaki-r 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 	bus_space_tag_t sc_iot;
68 	bus_space_handle_t sc_ioh;
69 	bus_space_handle_t sc_pm_ioh;
70 
71 	int     sc_fan_div[2];	/* fan RPM divisor */
72 
73 	struct sysmon_envsys *sc_sme;
74 	envsys_data_t sc_sensor[VIANUMSENSORS];
75 
76 	struct timeval sc_lastread;
77 };
78 
79 /* autoconf(9) glue */
80 static int 	viaenv_match(device_t, cfdata_t, void *);
81 static void 	viaenv_attach(device_t, device_t, void *);
82 
83 CFATTACH_DECL_NEW(viaenv, sizeof(struct viaenv_softc),
84     viaenv_match, viaenv_attach, NULL, NULL);
85 
86 /* envsys(4) glue */
87 static void viaenv_refresh(struct sysmon_envsys *, envsys_data_t *);
88 
89 static int val_to_uK(unsigned int);
90 static int val_to_rpm(unsigned int, int);
91 static long val_to_uV(unsigned int, int);
92 
93 static int
viaenv_match(device_t parent,cfdata_t match,void * aux)94 viaenv_match(device_t parent, cfdata_t match, void *aux)
95 {
96 	struct pci_attach_args *pa = aux;
97 
98 	if (PCI_VENDOR(pa->pa_id) != PCI_VENDOR_VIATECH)
99 		return 0;
100 
101 	switch (PCI_PRODUCT(pa->pa_id)) {
102 	case PCI_PRODUCT_VIATECH_VT82C686A_SMB:
103 	case PCI_PRODUCT_VIATECH_VT8231_PWR:
104 		return 1;
105 	default:
106 		return 0;
107 	}
108 }
109 
110 /*
111  * XXX there doesn't seem to exist much hard documentation on how to
112  * convert the raw values to usable units, this code is more or less
113  * stolen from the Linux driver, but changed to suit our conditions
114  */
115 
116 /*
117  * lookup-table to translate raw values to uK, this is the same table
118  * used by the Linux driver (modulo units); there is a fifth degree
119  * polynomial that supposedly been used to generate this table, but I
120  * haven't been able to figure out how -- it doesn't give the same values
121  */
122 
123 static const long val_to_temp[] = {
124 	20225, 20435, 20645, 20855, 21045, 21245, 21425, 21615, 21785, 21955,
125 	22125, 22285, 22445, 22605, 22755, 22895, 23035, 23175, 23315, 23445,
126 	23565, 23695, 23815, 23925, 24045, 24155, 24265, 24365, 24465, 24565,
127 	24665, 24765, 24855, 24945, 25025, 25115, 25195, 25275, 25355, 25435,
128 	25515, 25585, 25655, 25725, 25795, 25865, 25925, 25995, 26055, 26115,
129 	26175, 26235, 26295, 26355, 26405, 26465, 26515, 26575, 26625, 26675,
130 	26725, 26775, 26825, 26875, 26925, 26975, 27025, 27065, 27115, 27165,
131 	27205, 27255, 27295, 27345, 27385, 27435, 27475, 27515, 27565, 27605,
132 	27645, 27685, 27735, 27775, 27815, 27855, 27905, 27945, 27985, 28025,
133 	28065, 28105, 28155, 28195, 28235, 28275, 28315, 28355, 28405, 28445,
134 	28485, 28525, 28565, 28615, 28655, 28695, 28735, 28775, 28825, 28865,
135 	28905, 28945, 28995, 29035, 29075, 29125, 29165, 29205, 29245, 29295,
136 	29335, 29375, 29425, 29465, 29505, 29555, 29595, 29635, 29685, 29725,
137 	29765, 29815, 29855, 29905, 29945, 29985, 30035, 30075, 30125, 30165,
138 	30215, 30255, 30305, 30345, 30385, 30435, 30475, 30525, 30565, 30615,
139 	30655, 30705, 30755, 30795, 30845, 30885, 30935, 30975, 31025, 31075,
140 	31115, 31165, 31215, 31265, 31305, 31355, 31405, 31455, 31505, 31545,
141 	31595, 31645, 31695, 31745, 31805, 31855, 31905, 31955, 32005, 32065,
142 	32115, 32175, 32225, 32285, 32335, 32395, 32455, 32515, 32575, 32635,
143 	32695, 32755, 32825, 32885, 32955, 33025, 33095, 33155, 33235, 33305,
144 	33375, 33455, 33525, 33605, 33685, 33765, 33855, 33935, 34025, 34115,
145 	34205, 34295, 34395, 34495, 34595, 34695, 34805, 34905, 35015, 35135,
146 	35245, 35365, 35495, 35615, 35745, 35875, 36015, 36145, 36295, 36435,
147 	36585, 36745, 36895, 37065, 37225, 37395, 37575, 37755, 37935, 38125,
148 	38325, 38525, 38725, 38935, 39155, 39375, 39605, 39835, 40075, 40325,
149 	40575, 40835, 41095, 41375, 41655, 41935,
150 };
151 
152 /* use above table to convert values to temperatures in micro-Kelvins */
153 static int
val_to_uK(unsigned int val)154 val_to_uK(unsigned int val)
155 {
156 	int     i = val / 4;
157 	int     j = val % 4;
158 
159 	assert(i >= 0 && i <= 255);
160 
161 	if (j == 0 || i == 255)
162 		return val_to_temp[i] * 10000;
163 
164 	/* is linear interpolation ok? */
165 	return (val_to_temp[i] * (4 - j) +
166 	    val_to_temp[i + 1] * j) * 2500 /* really: / 4 * 10000 */ ;
167 }
168 
169 static int
val_to_rpm(unsigned int val,int div)170 val_to_rpm(unsigned int val, int div)
171 {
172 
173 	if (val == 0)
174 		return 0;
175 
176 	return 1350000 / val / div;
177 }
178 
179 static long
val_to_uV(unsigned int val,int index)180 val_to_uV(unsigned int val, int index)
181 {
182 	static const long mult[] =
183 	    {1250000, 1250000, 1670000, 2600000, 6300000};
184 
185 	assert(index >= 0 && index <= 4);
186 
187 	return (25LL * val + 133) * mult[index] / 2628;
188 }
189 
190 #define VIAENV_TSENS3	0x1f
191 #define VIAENV_TSENS1	0x20
192 #define VIAENV_TSENS2	0x21
193 #define VIAENV_VSENS1	0x22
194 #define VIAENV_VSENS2	0x23
195 #define VIAENV_VCORE	0x24
196 #define VIAENV_VSENS3	0x25
197 #define VIAENV_VSENS4	0x26
198 #define VIAENV_FAN1	0x29
199 #define VIAENV_FAN2	0x2a
200 #define VIAENV_FANCONF	0x47	/* fan configuration */
201 #define VIAENV_TLOW	0x49	/* temperature low order value */
202 #define VIAENV_TIRQ	0x4b	/* temperature interrupt configuration */
203 
204 #define VIAENV_GENCFG	0x40	/* general configuration */
205 #define VIAENV_GENCFG_TMR32	(1 << 11)	/* 32-bit PM timer */
206 #define VIAENV_GENCFG_PMEN	(1 << 15)	/* enable PM I/O space */
207 #define VIAENV_PMBASE	0x48	/* power management I/O space base */
208 #define VIAENV_PMSIZE	128	/* HWM and power management I/O space size */
209 #define VIAENV_PM_TMR	0x08	/* PM timer */
210 #define VIAENV_HWMON_CONF	0x70	/* HWMon I/O base */
211 #define VIAENV_HWMON_CTL	0x74	/* HWMon control register */
212 
213 static void
viaenv_refresh_sensor_data(struct viaenv_softc * sc,envsys_data_t * edata)214 viaenv_refresh_sensor_data(struct viaenv_softc *sc, envsys_data_t *edata)
215 {
216 	static const struct timeval onepointfive =  { 1, 500000 };
217 	static int old_sensor = -1;
218 	struct timeval t, utv;
219 	uint8_t v, v2;
220 	int i;
221 
222 	/* Read new values at most once every 1.5 seconds. */
223 	timeradd(&sc->sc_lastread, &onepointfive, &t);
224 	getmicrouptime(&utv);
225 	i = timercmp(&utv, &t, >);
226 	if (i)
227 		sc->sc_lastread = utv;
228 
229 	if (i == 0 && old_sensor == edata->sensor)
230 		return;
231 
232 	old_sensor = edata->sensor;
233 
234 	/* temperature */
235 	if (edata->sensor == 0) {
236 		v = bus_space_read_1(sc->sc_iot, sc->sc_ioh, VIAENV_TIRQ);
237 		v2 = bus_space_read_1(sc->sc_iot, sc->sc_ioh, VIAENV_TSENS1);
238 		DPRINTF(("TSENS1 = %d\n", (v2 << 2) | (v >> 6)));
239 		edata->value_cur = val_to_uK((v2 << 2) | (v >> 6));
240 		edata->state = ENVSYS_SVALID;
241 	} else if (edata->sensor == 1) {
242 		v = bus_space_read_1(sc->sc_iot, sc->sc_ioh, VIAENV_TLOW);
243 		v2 = bus_space_read_1(sc->sc_iot, sc->sc_ioh, VIAENV_TSENS2);
244 		DPRINTF(("TSENS2 = %d\n", (v2 << 2) | ((v >> 4) & 0x3)));
245 		edata->value_cur = val_to_uK((v2 << 2) | ((v >> 4) & 0x3));
246 		edata->state = ENVSYS_SVALID;
247 	} else if (edata->sensor == 2) {
248 		v = bus_space_read_1(sc->sc_iot, sc->sc_ioh, VIAENV_TLOW);
249 		v2 = bus_space_read_1(sc->sc_iot, sc->sc_ioh, VIAENV_TSENS3);
250 		DPRINTF(("TSENS3 = %d\n", (v2 << 2) | (v >> 6)));
251 		edata->value_cur = val_to_uK((v2 << 2) | (v >> 6));
252 		edata->state = ENVSYS_SVALID;
253 	} else if (edata->sensor > 2 && edata->sensor < 5) {
254 		/* fans */
255 		v = bus_space_read_1(sc->sc_iot, sc->sc_ioh, VIAENV_FANCONF);
256 
257 		sc->sc_fan_div[0] = 1 << ((v >> 4) & 0x3);
258 		sc->sc_fan_div[1] = 1 << ((v >> 6) & 0x3);
259 
260 		v = bus_space_read_1(sc->sc_iot, sc->sc_ioh,
261 		    VIAENV_FAN1 + edata->sensor - 3);
262 		DPRINTF(("FAN%d = %d / %d\n", edata->sensor - 3, v,
263 		    sc->sc_fan_div[edata->sensor - 3]));
264 		edata->value_cur = val_to_rpm(v,
265 		    sc->sc_fan_div[edata->sensor - 3]);
266 		edata->state = ENVSYS_SVALID;
267 	} else {
268 		v = bus_space_read_1(sc->sc_iot, sc->sc_ioh,
269 		    VIAENV_VSENS1 + edata->sensor - 5);
270 		DPRINTF(("V%d = %d\n", edata->sensor - 5, v));
271 		edata->value_cur = val_to_uV(v, edata->sensor - 5);
272 		edata->state = ENVSYS_SVALID;
273 	}
274 }
275 
276 static void
viaenv_attach(device_t parent,device_t self,void * aux)277 viaenv_attach(device_t parent, device_t self, void *aux)
278 {
279 	struct viaenv_softc *sc = device_private(self);
280 	struct pci_attach_args *pa = aux;
281 	pcireg_t iobase, control;
282 	int i;
283 
284 	aprint_naive("\n");
285 	aprint_normal(": VIA Technologies ");
286 	switch (PCI_PRODUCT(pa->pa_id)) {
287 	case PCI_PRODUCT_VIATECH_VT82C686A_SMB:
288 		aprint_normal("VT82C686A Hardware Monitor\n");
289 		break;
290 	case PCI_PRODUCT_VIATECH_VT8231_PWR:
291 		aprint_normal("VT8231 Hardware Monitor\n");
292 		break;
293 	default:
294 		aprint_normal("Unknown Hardware Monitor\n");
295 		break;
296 	}
297 
298 	sc->sc_iot = pa->pa_iot;
299 
300 	iobase = pci_conf_read(pa->pa_pc, pa->pa_tag, VIAENV_HWMON_CONF);
301 	DPRINTF(("%s: iobase 0x%x\n", device_xname(self), iobase));
302 	control = pci_conf_read(pa->pa_pc, pa->pa_tag, VIAENV_HWMON_CTL);
303 
304 	/* Check if the Hardware Monitor enable bit is set */
305 	if ((control & 1) == 0) {
306 		aprint_normal_dev(self, "Hardware Monitor disabled\n");
307 		goto nohwm;
308 	}
309 
310 	/* Map Hardware Monitor I/O space */
311 	if (bus_space_map(sc->sc_iot, iobase & 0xff80,
312 	    VIAENV_PMSIZE, 0, &sc->sc_ioh)) {
313 		aprint_error_dev(self, "failed to map I/O space\n");
314 		goto nohwm;
315 	}
316 
317 	for (i = 0; i < 3; i++)
318 		sc->sc_sensor[i].units = ENVSYS_STEMP;
319 
320 #define COPYDESCR(x, y) 				\
321 	do {						\
322 		strlcpy((x), (y), sizeof(x));		\
323 	} while (0)
324 
325 	COPYDESCR(sc->sc_sensor[0].desc, "TSENS1");
326 	COPYDESCR(sc->sc_sensor[1].desc, "TSENS2");
327 	COPYDESCR(sc->sc_sensor[2].desc, "TSENS3");
328 
329 	for (i = 3; i < 5; i++)
330 		sc->sc_sensor[i].units = ENVSYS_SFANRPM;
331 
332 	COPYDESCR(sc->sc_sensor[3].desc, "FAN1");
333 	COPYDESCR(sc->sc_sensor[4].desc, "FAN2");
334 
335 	for (i = 5; i < 10; i++)
336 		sc->sc_sensor[i].units = ENVSYS_SVOLTS_DC;
337 
338 	COPYDESCR(sc->sc_sensor[5].desc, "VSENS1");	/* CPU core (2V) */
339 	COPYDESCR(sc->sc_sensor[6].desc, "VSENS2");	/* NB core? (2.5V) */
340 	COPYDESCR(sc->sc_sensor[7].desc, "Vcore");	/* Vcore (3.3V) */
341 	COPYDESCR(sc->sc_sensor[8].desc, "VSENS3");	/* VSENS3 (5V) */
342 	COPYDESCR(sc->sc_sensor[9].desc, "VSENS4");	/* VSENS4 (12V) */
343 
344 #undef COPYDESCR
345 
346 	for (i = 0; i < 10; i++) {
347 		sc->sc_sensor[i].state = ENVSYS_SINVALID;
348 		sc->sc_sensor[i].flags |= ENVSYS_FHAS_ENTROPY;
349 	}
350 
351 	sc->sc_sme = sysmon_envsys_create();
352 
353 	/* Initialize sensors */
354 	for (i = 0; i < VIANUMSENSORS; i++) {
355 		if (sysmon_envsys_sensor_attach(sc->sc_sme,
356 						&sc->sc_sensor[i])) {
357 			sysmon_envsys_destroy(sc->sc_sme);
358 			return;
359 		}
360 	}
361 
362 	/*
363 	 * Hook into the System Monitor.
364 	 */
365 	sc->sc_sme->sme_name = device_xname(self);
366 	sc->sc_sme->sme_cookie = sc;
367 	sc->sc_sme->sme_refresh = viaenv_refresh;
368 
369 	if (sysmon_envsys_register(sc->sc_sme)) {
370 		aprint_error_dev(self, "unable to register with sysmon\n");
371 		sysmon_envsys_destroy(sc->sc_sme);
372 		return;
373 	}
374 
375 nohwm:
376 	/* Check if power management I/O space is enabled */
377 	control = pci_conf_read(pa->pa_pc, pa->pa_tag, VIAENV_GENCFG);
378 	if ((control & VIAENV_GENCFG_PMEN) == 0) {
379                 aprint_normal_dev(self,
380 		    "Power Managament controller disabled\n");
381                 goto nopm;
382         }
383 
384         /* Map power management I/O space */
385         iobase = pci_conf_read(pa->pa_pc, pa->pa_tag, VIAENV_PMBASE);
386         if (bus_space_map(sc->sc_iot, PCI_MAPREG_IO_ADDR(iobase),
387             VIAENV_PMSIZE, 0, &sc->sc_pm_ioh)) {
388                 aprint_error_dev(self, "failed to map PM I/O space\n");
389                 goto nopm;
390         }
391 
392 	/* Attach our PM timer with the generic acpipmtimer function */
393 	acpipmtimer_attach(self, sc->sc_iot, sc->sc_pm_ioh,
394 	    VIAENV_PM_TMR,
395 	    ((control & VIAENV_GENCFG_TMR32) ? ACPIPMT_32BIT : 0));
396 
397 nopm:
398 	return;
399 }
400 
401 static void
viaenv_refresh(struct sysmon_envsys * sme,envsys_data_t * edata)402 viaenv_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
403 {
404 	struct viaenv_softc *sc = sme->sme_cookie;
405 
406 	viaenv_refresh_sensor_data(sc, edata);
407 }
408