1 /* $NetBSD: ibmhawk.c,v 1.5 2018/05/09 02:46:22 thorpej Exp $ */ 2 3 /*- 4 * Copyright (c) 2011 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Juergen Hannken-Illjes. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 #include <sys/systm.h> 33 #include <sys/param.h> 34 #include <sys/kernel.h> 35 #include <sys/device.h> 36 #include <sys/bswap.h> 37 38 #include <dev/sysmon/sysmonvar.h> 39 #include <dev/i2c/i2cvar.h> 40 #include <dev/i2c/ibmhawkreg.h> 41 #include <dev/i2c/ibmhawkvar.h> 42 43 #if !defined(IBMHAWK_DEBUG) /* Set to 2 for verbose debug. */ 44 #if defined(DEBUG) 45 #define IBMHAWK_DEBUG 1 46 #else 47 #define IBMHAWK_DEBUG 0 48 #endif 49 #endif 50 51 /* 52 * Known sensors. 53 */ 54 static struct ibmhawk_sensordesc { 55 const char *desc; 56 uint32_t units; 57 int offset; 58 } ibmhawk_sensors[] = { 59 { "Ambient temperature", ENVSYS_STEMP, IBMHAWK_T_AMBIENT }, 60 { "CPU 1 temperature", ENVSYS_STEMP, IBMHAWK_T_CPU }, 61 { "CPU 2 temperature", ENVSYS_STEMP, IBMHAWK_T_CPU+1 }, 62 { "12 Voltage sensor", ENVSYS_SVOLTS_DC, IBMHAWK_V_VOLTAGE }, 63 { "5 Voltage sensor", ENVSYS_SVOLTS_DC, IBMHAWK_V_VOLTAGE+1 }, 64 { "3.3 Voltage sensor", ENVSYS_SVOLTS_DC, IBMHAWK_V_VOLTAGE+2 }, 65 { "2.5 Voltage sensor", ENVSYS_SVOLTS_DC, IBMHAWK_V_VOLTAGE+3 }, 66 { "1.5 Voltage sensor", ENVSYS_SVOLTS_DC, IBMHAWK_V_VOLTAGE+4 }, 67 { "1.25 Voltage sensor", ENVSYS_SVOLTS_DC, IBMHAWK_V_VOLTAGE+5 }, 68 { "VRM 1", ENVSYS_SVOLTS_DC, IBMHAWK_V_VOLTAGE+6 }, 69 { "Fan 1", ENVSYS_SFANRPM, IBMHAWK_F_FAN }, 70 { "Fan 2", ENVSYS_SFANRPM, IBMHAWK_F_FAN+1 }, 71 { "Fan 3", ENVSYS_SFANRPM, IBMHAWK_F_FAN+2 }, 72 { "Fan 4", ENVSYS_SFANRPM, IBMHAWK_F_FAN+3 }, 73 { "Fan 5", ENVSYS_SFANRPM, IBMHAWK_F_FAN+4 }, 74 { "Fan 6", ENVSYS_SFANRPM, IBMHAWK_F_FAN+5 }, 75 }; 76 static const int ibmhawk_num_sensors = 77 (sizeof(ibmhawk_sensors)/sizeof(ibmhawk_sensors[0])); 78 79 static int ibmhawk_match(device_t, cfdata_t, void *); 80 static void ibmhawk_attach(device_t, device_t, void *); 81 static int ibmhawk_detach(device_t, int); 82 static uint8_t ibmhawk_cksum(uint8_t *); 83 static int ibmhawk_request(struct ibmhawk_softc *, 84 uint8_t, ibmhawk_response_t *); 85 static uint32_t ibmhawk_normalize(int, uint32_t); 86 static void ibmhawk_set(struct ibmhawk_softc *, int, int, bool, bool); 87 static void ibmhawk_refreshall(struct ibmhawk_softc *, bool); 88 static void ibmhawk_refresh(struct sysmon_envsys *, envsys_data_t *); 89 static void ibmhawk_get_limits(struct sysmon_envsys *, envsys_data_t *, 90 sysmon_envsys_lim_t *, uint32_t *); 91 92 CFATTACH_DECL_NEW(ibmhawk, sizeof(struct ibmhawk_softc), 93 ibmhawk_match, ibmhawk_attach, ibmhawk_detach, NULL); 94 95 static int 96 ibmhawk_match(device_t parent, cfdata_t match, void *aux) 97 { 98 struct i2c_attach_args *ia = aux; 99 ibmhawk_response_t resp; 100 static struct ibmhawk_softc sc; 101 102 /* There is an expected address for this device: */ 103 if (ia->ia_addr != 0x37) 104 return 0; 105 106 /* XXX Probe is potentially destructive. */ 107 108 sc.sc_tag = ia->ia_tag; 109 sc.sc_addr = ia->ia_addr; 110 if (ibmhawk_request(&sc, IHR_EQUIP, &resp)) 111 return 0; 112 return 1; 113 } 114 115 static void 116 ibmhawk_attach(device_t parent, device_t self, void *aux) 117 { 118 struct ibmhawk_softc *sc = device_private(self); 119 struct i2c_attach_args *ia = aux; 120 ibmhawk_response_t resp; 121 int i; 122 123 sc->sc_dev = self; 124 sc->sc_tag = ia->ia_tag; 125 sc->sc_addr = ia->ia_addr; 126 127 if (!pmf_device_register(self, NULL, NULL)) 128 aprint_error_dev(self, "couldn't establish power handler\n"); 129 if (ibmhawk_request(sc, IHR_NAME, &resp)) { 130 aprint_error_dev(self, "communication failed\n"); 131 return; 132 } 133 aprint_normal(": IBM Hawk \"%.16s\"\n", resp.ihr_name); 134 if (ibmhawk_request(sc, IHR_EQUIP, &resp)) { 135 aprint_error_dev(sc->sc_dev, "equip query failed\n"); 136 return; 137 } 138 sc->sc_numcpus = min(resp.ihr_numcpus, IBMHAWK_MAX_CPU); 139 sc->sc_numfans = min(resp.ihr_numfans, IBMHAWK_MAX_FAN); 140 #if IBMHAWK_DEBUG > 0 141 aprint_normal_dev(sc->sc_dev, "monitoring %d/%d cpu(s) %d/%d fan(s)\n", 142 sc->sc_numcpus, resp.ihr_numcpus, sc->sc_numfans, resp.ihr_numfans); 143 #endif 144 /* Request and set sensor thresholds. */ 145 if (ibmhawk_request(sc, IHR_TEMP_THR, &resp)) { 146 aprint_error_dev(sc->sc_dev, "temp threshold query failed\n"); 147 return; 148 } 149 for (i = 0; i < sc->sc_numcpus; i++) 150 sc->sc_sensordata[IBMHAWK_T_CPU+i].ihs_warnmax = 151 resp.ihr_t_warn_thr; 152 if (ibmhawk_request(sc, IHR_VOLT_THR, &resp)) { 153 aprint_error_dev(sc->sc_dev, "volt threshold query failed\n"); 154 return; 155 } 156 for (i = 0; i < IBMHAWK_MAX_VOLTAGE; i++) { 157 sc->sc_sensordata[IBMHAWK_V_VOLTAGE+i].ihs_warnmax = 158 bswap16(resp.ihr_v_voltage_thr[i*2]); 159 sc->sc_sensordata[IBMHAWK_V_VOLTAGE+i].ihs_warnmin = 160 bswap16(resp.ihr_v_voltage_thr[i*2+1]); 161 } 162 if ((sc->sc_sme = sysmon_envsys_create()) == NULL) { 163 aprint_error_dev(sc->sc_dev, "sysmon_envsys_create failed\n"); 164 return; 165 } 166 ibmhawk_refreshall(sc, true); 167 sc->sc_sme->sme_name = device_xname(sc->sc_dev); 168 sc->sc_sme->sme_cookie = sc; 169 sc->sc_sme->sme_refresh = ibmhawk_refresh; 170 sc->sc_sme->sme_get_limits = ibmhawk_get_limits; 171 if (sysmon_envsys_register(sc->sc_sme)) { 172 aprint_error_dev(sc->sc_dev, "sysmon_envsys_register failed\n"); 173 sysmon_envsys_destroy(sc->sc_sme); 174 sc->sc_sme = NULL; 175 return; 176 } 177 } 178 179 static int 180 ibmhawk_detach(device_t self, int flags) 181 { 182 struct ibmhawk_softc *sc = device_private(self); 183 184 if (sc->sc_sme) 185 sysmon_envsys_destroy(sc->sc_sme); 186 return 0; 187 } 188 189 190 /* 191 * Compute the message checksum. 192 */ 193 static uint8_t 194 ibmhawk_cksum(uint8_t *buf) 195 { 196 int len = *buf++; 197 int s = 0; 198 199 while (--len > 0) 200 s += *buf++; 201 return -s; 202 } 203 204 /* 205 * Request information from the management processor. 206 * The response will be zeroed on error. 207 * Request and response have the form <n> <data 0:n-2> <checksum>. 208 */ 209 static int 210 ibmhawk_request(struct ibmhawk_softc *sc, uint8_t request, 211 ibmhawk_response_t *response) 212 { 213 int i, error, retries;; 214 uint8_t buf[sizeof(ibmhawk_response_t)+3], dummy; 215 216 error = EIO; /* Fail until we have a valid response. */ 217 retries = 0; 218 219 if (iic_acquire_bus(sc->sc_tag, 0)) 220 return error; 221 222 again: 223 memset(response, 0, sizeof(*response)); 224 225 /* Build and send the request. */ 226 buf[0] = 2; 227 buf[1] = request; 228 buf[2] = ibmhawk_cksum(buf); 229 #if IBMHAWK_DEBUG > 1 230 printf("["); 231 for (i = 0; i < 3; i++) 232 printf(" %02x", buf[i]); 233 printf(" ]"); 234 #endif 235 for (i = 0; i < 3; i++) 236 if (iic_smbus_send_byte(sc->sc_tag, sc->sc_addr, buf[i], 0)) 237 goto bad; 238 239 /* Receive and check the response. */ 240 #if IBMHAWK_DEBUG > 1 241 printf(" => ["); 242 #endif 243 if (iic_smbus_receive_byte(sc->sc_tag, sc->sc_addr, &buf[0], 0)) 244 goto bad; 245 if (buf[0] == 0 || buf[0] == 255) 246 goto bad; 247 for (i = 1; i < buf[0]+1; i++) 248 if (iic_smbus_receive_byte(sc->sc_tag, sc->sc_addr, 249 (i < sizeof buf ? &buf[i] : &dummy), 0)) 250 goto bad; 251 if (buf[0] >= sizeof(buf) || buf[1] != request || 252 ibmhawk_cksum(buf) != buf[buf[0]]) 253 goto bad; 254 if (buf[0] > 2) 255 memcpy(response, buf+2, buf[0]-2); 256 error = 0; 257 258 bad: 259 #if IBMHAWK_DEBUG > 1 260 for (i = 0; i < min(buf[0]+1, sizeof buf); i++) 261 printf(" %02x", buf[i]); 262 printf(" ] => %d\n", error); 263 #endif 264 if (error != 0 && retries++ < 3) 265 goto again; 266 267 iic_release_bus(sc->sc_tag, 0); 268 return error; 269 } 270 271 static uint32_t 272 ibmhawk_normalize(int value, uint32_t units) 273 { 274 275 if (value == 0) 276 return 0; 277 278 switch (units) { 279 case ENVSYS_STEMP: 280 return 273150000+1000000*value; 281 case ENVSYS_SVOLTS_DC: 282 return 10000*value; 283 default: 284 return value; 285 } 286 } 287 288 static void 289 ibmhawk_set(struct ibmhawk_softc *sc, 290 int offset, int value, bool valid, bool create) 291 { 292 int i; 293 struct ibmhawk_sensordesc *sp; 294 struct ibmhawk_sensordata *sd; 295 envsys_data_t *dp; 296 297 sd = &sc->sc_sensordata[offset]; 298 dp = &sd->ihs_edata; 299 sp = NULL; 300 if (create) { 301 for (i = 0; i < ibmhawk_num_sensors; i++) 302 if (ibmhawk_sensors[i].offset == offset) { 303 sp = ibmhawk_sensors+i; 304 break; 305 } 306 if (sp == NULL) { 307 #if IBMHAWK_DEBUG > 0 308 aprint_error_dev(sc->sc_dev, 309 "offset %d: no sensor found\n", offset); 310 #endif 311 return; 312 } 313 strlcpy(dp->desc, sp->desc, sizeof(dp->desc)); 314 dp->units = sp->units; 315 if (sd->ihs_warnmin != 0 || sd->ihs_warnmax != 0) { 316 sd->ihs_warnmin = 317 ibmhawk_normalize(sd->ihs_warnmin, dp->units); 318 sd->ihs_warnmax = 319 ibmhawk_normalize(sd->ihs_warnmax, dp->units); 320 dp->flags |= ENVSYS_FMONLIMITS; 321 } 322 } 323 324 if (valid) { 325 dp->value_cur = ibmhawk_normalize(value, dp->units); 326 dp->state = ENVSYS_SVALID; 327 } else 328 dp->state = ENVSYS_SINVALID; 329 330 if (create) { 331 if (sysmon_envsys_sensor_attach(sc->sc_sme, dp)) 332 aprint_error_dev(sc->sc_dev, 333 "failed to attach \"%s\"\n", dp->desc); 334 } 335 } 336 337 static void 338 ibmhawk_refreshall(struct ibmhawk_softc *sc, bool create) 339 { 340 int i; 341 bool valid; 342 ibmhawk_response_t resp; 343 344 valid = (ibmhawk_request(sc, IHR_TEMP, &resp) == 0); 345 ibmhawk_set(sc, IBMHAWK_T_AMBIENT, resp.ihr_t_ambient, valid, create); 346 for (i = 0; i < sc->sc_numcpus; i++) 347 ibmhawk_set(sc, IBMHAWK_T_CPU+i, 348 resp.ihr_t_cpu[i], valid, create); 349 350 valid = (ibmhawk_request(sc, IHR_FANRPM, &resp) == 0); 351 for (i = 0; i < sc->sc_numfans; i++) 352 ibmhawk_set(sc, IBMHAWK_F_FAN+i, 353 bswap16(resp.ihr_fanrpm[i]), valid, create); 354 355 valid = (ibmhawk_request(sc, IHR_VOLT, &resp) == 0); 356 for (i = 0; i < IBMHAWK_MAX_VOLTAGE; i++) 357 ibmhawk_set(sc, IBMHAWK_V_VOLTAGE+i, 358 bswap16(resp.ihr_v_voltage[i]), valid, create); 359 } 360 361 static void 362 ibmhawk_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) 363 { 364 struct ibmhawk_softc *sc = sme->sme_cookie; 365 366 /* No more than two refreshes per second. */ 367 if (hardclock_ticks-sc->sc_refresh < hz/2) 368 return; 369 #if IBMHAWK_DEBUG > 1 370 aprint_normal_dev(sc->sc_dev, "refresh \"%s\" delta %d\n", 371 edata->desc, hardclock_ticks-sc->sc_refresh); 372 #endif 373 sc->sc_refresh = hardclock_ticks; 374 ibmhawk_refreshall(sc, false); 375 } 376 377 static void 378 ibmhawk_get_limits(struct sysmon_envsys *sme, envsys_data_t *edata, 379 sysmon_envsys_lim_t *limits, uint32_t *props) 380 { 381 struct ibmhawk_sensordata *sd = (struct ibmhawk_sensordata *)edata; 382 383 if (sd->ihs_warnmin != 0) { 384 limits->sel_warnmin = sd->ihs_warnmin; 385 *props |= PROP_WARNMIN; 386 } 387 if (sd->ihs_warnmax != 0) { 388 limits->sel_warnmax = sd->ihs_warnmax; 389 *props |= PROP_WARNMAX; 390 } 391 } 392