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