1 /* $NetBSD: fcu.c,v 1.5 2022/04/08 10:17:53 andvar Exp $ */
2 
3 /*-
4  * Copyright (c) 2018 Michael Lorenz
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  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  * POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include <sys/cdefs.h>
30 __KERNEL_RCSID(0, "$NetBSD: fcu.c,v 1.5 2022/04/08 10:17:53 andvar Exp $");
31 
32 #include <sys/param.h>
33 #include <sys/systm.h>
34 #include <sys/device.h>
35 #include <sys/conf.h>
36 #include <sys/bus.h>
37 #include <sys/kthread.h>
38 #include <sys/sysctl.h>
39 
40 #include <dev/i2c/i2cvar.h>
41 
42 #include <dev/sysmon/sysmonvar.h>
43 
44 #include <dev/ofw/openfirm.h>
45 
46 #include <macppc/dev/fancontrolvar.h>
47 
48 //#define FCU_DEBUG
49 #ifdef FCU_DEBUG
50 #define DPRINTF printf
51 #else
52 #define DPRINTF if (0) printf
53 #endif
54 
55 /* FCU registers, from OpenBSD's fcu.c */
56 #define FCU_FAN_FAIL          0x0b                /* fans states in bits 0<1-6>7 */
57 #define FCU_FAN_ACTIVE        0x0d
58 #define FCU_FANREAD(x)        0x11 + (x)*2
59 #define FCU_FANSET(x)         0x10 + (x)*2
60 #define FCU_PWM_FAIL          0x2b
61 #define FCU_PWM_ACTIVE        0x2d
62 #define FCU_PWMREAD(x)        0x30 + (x)*2
63 
64 
65 typedef struct _fcu_fan {
66           int target;
67           int reg;
68           int base_rpm, max_rpm;
69           int step;
70           int duty; /* for pwm fans */
71 } fcu_fan_t;
72 
73 #define FCU_ZONE_CPU                    0
74 #define FCU_ZONE_CASE                   1
75 #define FCU_ZONE_DRIVEBAY     2
76 #define FCU_ZONE_COUNT                  3
77 
78 struct fcu_softc {
79           device_t  sc_dev;
80           i2c_tag_t sc_i2c;
81           i2c_addr_t          sc_addr;
82           struct sysctlnode   *sc_sysctl_me;
83           struct sysmon_envsys          *sc_sme;
84           envsys_data_t                 sc_sensors[32];
85           int                           sc_nsensors;
86           fancontrol_zone_t   sc_zones[FCU_ZONE_COUNT];
87           fcu_fan_t           sc_fans[FANCONTROL_MAX_FANS];
88           int                           sc_nfans;
89           lwp_t                         *sc_thread;
90           bool                          sc_dying, sc_pwm;
91           uint8_t                       sc_eeprom0[160];
92           uint8_t                       sc_eeprom1[160];
93 };
94 
95 static int          fcu_match(device_t, cfdata_t, void *);
96 static void         fcu_attach(device_t, device_t, void *);
97 
98 static void         fcu_sensors_refresh(struct sysmon_envsys *, envsys_data_t *);
99 
100 static bool is_cpu(const envsys_data_t *);
101 static bool is_case(const envsys_data_t *);
102 static bool is_drive(const envsys_data_t *);
103 
104 static int fcu_set_rpm(void *, int, int);
105 static int fcu_get_rpm(void *, int);
106 static void fcu_adjust(void *);
107 
108 CFATTACH_DECL_NEW(fcu, sizeof(struct fcu_softc),
109     fcu_match, fcu_attach, NULL, NULL);
110 
111 static const struct device_compatible_entry compat_data[] = {
112           { .compat = "fcu" },
113           DEVICE_COMPAT_EOL
114 };
115 
116 static int
fcu_match(device_t parent,cfdata_t match,void * aux)117 fcu_match(device_t parent, cfdata_t match, void *aux)
118 {
119           struct i2c_attach_args *ia = aux;
120           int match_result;
121 
122           if (iic_use_direct_match(ia, match, compat_data, &match_result))
123                     return match_result;
124 
125           if (ia->ia_addr == 0x2f)
126                     return I2C_MATCH_ADDRESS_ONLY;
127 
128           return 0;
129 }
130 
131 static void
fcu_attach(device_t parent,device_t self,void * aux)132 fcu_attach(device_t parent, device_t self, void *aux)
133 {
134           struct fcu_softc *sc = device_private(self);
135           struct i2c_attach_args *ia = aux;
136           int have_eeprom1 = 1, i;
137 
138           sc->sc_dev = self;
139           sc->sc_i2c = ia->ia_tag;
140           sc->sc_addr = ia->ia_addr;
141 
142           aprint_naive("\n");
143           aprint_normal(": Fan Control Unit\n");
144 
145           sysctl_createv(NULL, 0, NULL, (void *) &sc->sc_sysctl_me,
146               CTLFLAG_READWRITE,
147               CTLTYPE_NODE, device_xname(sc->sc_dev), NULL,
148               NULL, 0, NULL, 0,
149               CTL_MACHDEP, CTL_CREATE, CTL_EOL);
150 
151           if (get_cpuid(0, sc->sc_eeprom0) < 160) {
152                     /*
153                      * XXX this should never happen, we depend on the EEPROM for
154                      * calibration data to make sense of temperature and voltage
155                      * sensors elsewhere, and fan parameters here.
156                      */
157                     aprint_error_dev(self, "no EEPROM data for CPU 0\n");
158                     return;
159           }
160           if (get_cpuid(1, sc->sc_eeprom1) < 160)
161                     have_eeprom1 = 0;
162 
163           /* init zones */
164           sc->sc_zones[FCU_ZONE_CPU].name = "CPUs";
165           sc->sc_zones[FCU_ZONE_CPU].filter = is_cpu;
166           sc->sc_zones[FCU_ZONE_CPU].cookie = sc;
167           sc->sc_zones[FCU_ZONE_CPU].get_rpm = fcu_get_rpm;
168           sc->sc_zones[FCU_ZONE_CPU].set_rpm = fcu_set_rpm;
169           sc->sc_zones[FCU_ZONE_CPU].Tmin = 50;
170           sc->sc_zones[FCU_ZONE_CPU].Tmax = 85;
171           sc->sc_zones[FCU_ZONE_CPU].nfans = 0;
172           sc->sc_zones[FCU_ZONE_CASE].name = "Slots";
173           sc->sc_zones[FCU_ZONE_CASE].filter = is_case;
174           sc->sc_zones[FCU_ZONE_CASE].cookie = sc;
175           sc->sc_zones[FCU_ZONE_CASE].Tmin = 50;
176           sc->sc_zones[FCU_ZONE_CASE].Tmax = 75;
177           sc->sc_zones[FCU_ZONE_CASE].nfans = 0;
178           sc->sc_zones[FCU_ZONE_CASE].get_rpm = fcu_get_rpm;
179           sc->sc_zones[FCU_ZONE_CASE].set_rpm = fcu_set_rpm;
180           sc->sc_zones[FCU_ZONE_DRIVEBAY].name = "Drivebays";
181           sc->sc_zones[FCU_ZONE_DRIVEBAY].filter = is_drive;
182           sc->sc_zones[FCU_ZONE_DRIVEBAY].cookie = sc;
183           sc->sc_zones[FCU_ZONE_DRIVEBAY].get_rpm = fcu_get_rpm;
184           sc->sc_zones[FCU_ZONE_DRIVEBAY].set_rpm = fcu_set_rpm;
185           sc->sc_zones[FCU_ZONE_DRIVEBAY].Tmin = 30;
186           sc->sc_zones[FCU_ZONE_DRIVEBAY].Tmax = 50;
187           sc->sc_zones[FCU_ZONE_DRIVEBAY].nfans = 0;
188 
189           sc->sc_sme = sysmon_envsys_create();
190           sc->sc_sme->sme_name = device_xname(self);
191           sc->sc_sme->sme_cookie = sc;
192           sc->sc_sme->sme_refresh = fcu_sensors_refresh;
193 
194           sc->sc_sensors[0].units = ENVSYS_SFANRPM;
195           sc->sc_sensors[1].state = ENVSYS_SINVALID;
196           sc->sc_nfans = 0;
197 
198           /* round up sensors */
199           int ch;
200 
201           sc->sc_nsensors = 0;
202           ch = OF_child(ia->ia_cookie);
203           while (ch != 0) {
204                     char type[32], descr[32];
205                     uint32_t reg;
206 
207                     envsys_data_t *s = &sc->sc_sensors[sc->sc_nsensors];
208 
209                     s->state = ENVSYS_SINVALID;
210 
211                     if (OF_getprop(ch, "device_type", type, 32) <= 0)
212                               goto next;
213 
214                     if (strcmp(type, "fan-rpm-control") == 0) {
215                               s->units = ENVSYS_SFANRPM;
216                     } else if (strcmp(type, "fan-pwm-control") == 0) {
217                               /* XXX we get the type from the register number */
218                               s->units = ENVSYS_SFANRPM;
219 /* skip those for now since we don't really know how to interpret them */
220 #if 0
221                     } else if (strcmp(type, "power-sensor") == 0) {
222                               s->units = ENVSYS_SVOLTS_DC;
223 #endif
224                     } else if (strcmp(type, "gpi-sensor") == 0) {
225                               s->units = ENVSYS_INDICATOR;
226                     } else {
227                               /* ignore other types for now */
228                               goto next;
229                     }
230 
231                     if (OF_getprop(ch, "reg", &reg, sizeof(reg)) <= 0)
232                               goto next;
233                     s->private = reg;
234 
235                     if (OF_getprop(ch, "location", descr, 32) <= 0)
236                               goto next;
237                     strcpy(s->desc, descr);
238 
239                     if (s->units == ENVSYS_SFANRPM) {
240                               fcu_fan_t *fan = &sc->sc_fans[sc->sc_nfans];
241                               uint8_t *eeprom = NULL;
242                               uint16_t rmin, rmax;
243 
244                               if (strstr(descr, "CPU A") != NULL)
245                                         eeprom = sc->sc_eeprom0;
246                               if (strstr(descr, "CPU B") != NULL) {
247                                         /*
248                                          * XXX
249                                          * this should never happen
250                                          */
251                                         if (have_eeprom1 == 0) {
252                                                   eeprom = sc->sc_eeprom0;
253                                         } else
254                                                   eeprom = sc->sc_eeprom1;
255                               }
256 
257                               fan->reg = reg;
258                               fan->target = 0;
259                               fan->duty = 0x80;
260 
261                               /* speed settings from EEPROM */
262                               if (strstr(descr, "PUMP") != NULL) {
263                                         KASSERT(eeprom != NULL);
264                                         memcpy(&rmin, &eeprom[0x54], 2);
265                                         memcpy(&rmax, &eeprom[0x56], 2);
266                                         fan->base_rpm = rmin;
267                                         fan->max_rpm = rmax;
268                                         fan->step = (rmax - rmin) / 30;
269                               } else if (strstr(descr, "INTAKE") != NULL) {
270                                         KASSERT(eeprom != NULL);
271                                         memcpy(&rmin, &eeprom[0x4c], 2);
272                                         memcpy(&rmax, &eeprom[0x4e], 2);
273                                         fan->base_rpm = rmin;
274                                         fan->max_rpm = rmax;
275                                         fan->step = (rmax - rmin) / 30;
276                               } else if (strstr(descr, "EXHAUST") != NULL) {
277                                         KASSERT(eeprom != NULL);
278                                         memcpy(&rmin, &eeprom[0x50], 2);
279                                         memcpy(&rmax, &eeprom[0x52], 2);
280                                         fan->base_rpm = rmin;
281                                         fan->max_rpm = rmax;
282                                         fan->step = (rmax - rmin) / 30;
283                               } else if (strstr(descr, "DRIVE") != NULL ) {
284                                         fan->base_rpm = 1000;
285                                         fan->max_rpm = 3000;
286                                         fan->step = 100;
287                               } else {
288                                         fan->base_rpm = 1000;
289                                         fan->max_rpm = 3000;
290                                         fan->step = 100;
291                               }
292                               DPRINTF("fan %s: %d - %d rpm, step %d\n",
293                                  descr, fan->base_rpm, fan->max_rpm, fan->step);
294 
295                               /* now stuff them into zones */
296                               if (strstr(descr, "CPU") != NULL) {
297                                         fancontrol_zone_t *z = &sc->sc_zones[FCU_ZONE_CPU];
298                                         z->fans[z->nfans].num = sc->sc_nfans;
299                                         z->fans[z->nfans].min_rpm = fan->base_rpm;
300                                         z->fans[z->nfans].max_rpm = fan->max_rpm;
301                                         z->fans[z->nfans].name = s->desc;
302                                         z->nfans++;
303                               } else if ((strstr(descr, "BACKSIDE") != NULL) ||
304                                            (strstr(descr, "SLOT") != NULL))  {
305                                         fancontrol_zone_t *z = &sc->sc_zones[FCU_ZONE_CASE];
306                                         z->fans[z->nfans].num = sc->sc_nfans;
307                                         z->fans[z->nfans].min_rpm = fan->base_rpm;
308                                         z->fans[z->nfans].max_rpm = fan->max_rpm;
309                                         z->fans[z->nfans].name = s->desc;
310                                         z->nfans++;
311                               } else if (strstr(descr, "DRIVE") != NULL) {
312                                         fancontrol_zone_t *z = &sc->sc_zones[FCU_ZONE_DRIVEBAY];
313                                         z->fans[z->nfans].num = sc->sc_nfans;
314                                         z->fans[z->nfans].min_rpm = fan->base_rpm;
315                                         z->fans[z->nfans].max_rpm = fan->max_rpm;
316                                         z->fans[z->nfans].name = s->desc;
317                                         z->nfans++;
318                               }
319                               sc->sc_nfans++;
320                     }
321                     sysmon_envsys_sensor_attach(sc->sc_sme, s);
322                     sc->sc_nsensors++;
323 next:
324                     ch = OF_peer(ch);
325           }
326           sysmon_envsys_register(sc->sc_sme);
327 
328           /* setup sysctls for our zones etc. */
329           for (i = 0; i < FCU_ZONE_COUNT; i++) {
330                     fancontrol_init_zone(&sc->sc_zones[i], sc->sc_sysctl_me);
331           }
332 
333           sc->sc_dying = FALSE;
334           kthread_create(PRI_NONE, 0, curcpu(), fcu_adjust, sc, &sc->sc_thread,
335               "fan control");
336 }
337 
338 static void
fcu_sensors_refresh(struct sysmon_envsys * sme,envsys_data_t * edata)339 fcu_sensors_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
340 {
341           struct fcu_softc *sc = sme->sme_cookie;
342           uint8_t cmd;
343           uint16_t data = -1;
344           int error;
345 
346           if (edata->units == ENVSYS_SFANRPM) {
347                     cmd = edata->private + 1;
348           } else
349                     cmd = edata->private;
350 
351           /* fcu is a macppc only thing so we can safely assume big endian */
352           iic_acquire_bus(sc->sc_i2c, 0);
353           error = iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP,
354               sc->sc_addr, &cmd, 1, &data, 2, 0);
355           iic_release_bus(sc->sc_i2c, 0);
356 
357           if (error) {
358                     edata->state = ENVSYS_SINVALID;
359                     return;
360           }
361 
362           edata->state = ENVSYS_SVALID;
363 
364           switch (edata->units) {
365                     case ENVSYS_SFANRPM:
366                               edata->value_cur = data >> 3;
367                               break;
368                     case ENVSYS_SVOLTS_DC:
369                               /* XXX this reads bogus */
370                               edata->value_cur = data * 1000;
371                               break;
372                     case ENVSYS_INDICATOR:
373                               /* guesswork for now */
374                               edata->value_cur = data >> 8;
375                               break;
376                     default:
377                               edata->state = ENVSYS_SINVALID;
378           }
379 }
380 
381 static bool
is_cpu(const envsys_data_t * edata)382 is_cpu(const envsys_data_t *edata)
383 {
384           if (edata->units != ENVSYS_STEMP)
385                     return false;
386           if (strstr(edata->desc, "CPU") != NULL)
387                     return TRUE;
388           return false;
389 }
390 
391 static bool
is_case(const envsys_data_t * edata)392 is_case(const envsys_data_t *edata)
393 {
394           if (edata->units != ENVSYS_STEMP)
395                     return false;
396           if ((strstr(edata->desc, "MLB") != NULL) ||
397               (strstr(edata->desc, "BACKSIDE") != NULL) ||
398               (strstr(edata->desc, "U3") != NULL))
399                     return TRUE;
400           return false;
401 }
402 
403 static bool
is_drive(const envsys_data_t * edata)404 is_drive(const envsys_data_t *edata)
405 {
406           if (edata->units != ENVSYS_STEMP)
407                     return false;
408           if (strstr(edata->desc, "DRIVE") != NULL)
409                     return TRUE;
410           return false;
411 }
412 
413 static int
fcu_get_rpm(void * cookie,int which)414 fcu_get_rpm(void *cookie, int which)
415 {
416           struct fcu_softc *sc = cookie;
417           fcu_fan_t *f = &sc->sc_fans[which];
418           int error;
419           uint16_t data;
420           uint8_t cmd;
421 
422           iic_acquire_bus(sc->sc_i2c, 0);
423           cmd = f->reg + 1;
424           error = iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP,
425               sc->sc_addr, &cmd, 1, &data, 2, 0);
426           iic_release_bus(sc->sc_i2c, 0);
427           if (error != 0) return -1;
428           data = data >> 3;
429           return data;
430 }
431 
432 static int
fcu_set_rpm(void * cookie,int which,int speed)433 fcu_set_rpm(void *cookie, int which, int speed)
434 {
435           struct fcu_softc *sc = cookie;
436           fcu_fan_t *f = &sc->sc_fans[which];
437           int error = 0;
438           uint8_t cmd;
439 
440           if (speed > f->max_rpm) speed = f->max_rpm;
441           if (speed < f->base_rpm) speed = f->base_rpm;
442 
443           if (f->reg < 0x30) {
444                     uint16_t data;
445                     /* simple rpm fan, just poke the register */
446 
447                     if (f->target == speed) return 0;
448                     iic_acquire_bus(sc->sc_i2c, 0);
449                     cmd = f->reg;
450                     data = (speed << 3);
451                     error = iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP,
452                         sc->sc_addr, &cmd, 1, &data, 2, 0);
453                     iic_release_bus(sc->sc_i2c, 0);
454           } else {
455                     int diff;
456                     int nduty = f->duty;
457                     int current_speed;
458                     /* pwm fan, measure speed, then adjust duty cycle */
459                     DPRINTF("pwm fan ");
460                     current_speed = fcu_get_rpm(sc, which);
461                     diff = current_speed - speed;
462                     DPRINTF("d %d s %d t %d diff %d ", f->duty, current_speed, speed, diff);
463                     if (diff > 100) {
464                               nduty = uimax(20, nduty - 1);
465                     }
466                     if (diff < -100) {
467                               nduty = uimin(0xd0, nduty + 1);
468                     }
469                     cmd = f->reg;
470                     DPRINTF("%s nduty %d", __func__, nduty);
471                     if (nduty != f->duty) {
472                               uint8_t arg = nduty;
473                               iic_acquire_bus(sc->sc_i2c, 0);
474                               error = iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP,
475                                   sc->sc_addr, &cmd, 1, &arg, 1, 0);
476                               iic_release_bus(sc->sc_i2c, 0);
477                               f->duty = nduty;
478                               sc->sc_pwm = TRUE;
479 
480                     }
481                     DPRINTF("ok\n");
482           }
483           if (error) printf("boo\n");
484           f->target = speed;
485           return 0;
486 }
487 
488 static void
fcu_adjust(void * cookie)489 fcu_adjust(void *cookie)
490 {
491           struct fcu_softc *sc = cookie;
492           int i;
493           uint8_t cmd, data;
494 
495           while (!sc->sc_dying) {
496                     /* poke the FCU so we don't go 747 */
497                     iic_acquire_bus(sc->sc_i2c, 0);
498                     cmd = FCU_FAN_ACTIVE;
499                     iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP,
500                         sc->sc_addr, &cmd, 1, &data, 1, 0);
501                     iic_release_bus(sc->sc_i2c, 0);
502                     sc->sc_pwm = FALSE;
503                     for (i = 0; i < FCU_ZONE_COUNT; i++)
504                               fancontrol_adjust_zone(&sc->sc_zones[i]);
505                     /*
506                      * take a shorter nap if we're in the process of adjusting a
507                      * PWM fan, which relies on measuring speed and then changing
508                      * its duty cycle until we're reasonable close to the target
509                      * speed
510                      */
511                     kpause("fanctrl", true, mstohz(sc->sc_pwm ? 1000 : 2000), NULL);
512           }
513           kthread_exit(0);
514 }
515