1 /*        $NetBSD: viaenv.c,v 1.34 2018/03/04 13:24:17 jdolecek 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.34 2018/03/04 13:24:17 jdolecek 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_PWR:
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_PWR:
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