1 /* $NetBSD: amdzentemp.c,v 1.14 2021/06/06 11:35:22 nonaka Exp $ */ 2 /* $OpenBSD: kate.c,v 1.2 2008/03/27 04:52:03 cnst Exp $ */ 3 4 /* 5 * Copyright (c) 2008, 2020 The NetBSD Foundation, Inc. 6 * All rights reserved. 7 * 8 * Copyright (c) 2019 Conrad Meyer <cem@FreeBSD.org> 9 * All rights reserved. 10 * 11 * This code is derived from software contributed to The NetBSD Foundation 12 * by Christoph Egger. 13 * 14 * NetBSD port by Ian Clark <mrrooster@gmail.com> 15 * 16 * Redistribution and use in source and binary forms, with or without 17 * modification, are permitted provided that the following conditions 18 * are met: 19 * 1. Redistributions of source code must retain the above copyright 20 * notice, this list of conditions and the following disclaimer. 21 * 2. Redistributions in binary form must reproduce the above copyright 22 * notice, this list of conditions and the following disclaimer in the 23 * documentation and/or other materials provided with the distribution. 24 * 25 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 26 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 27 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 28 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 29 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 30 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 31 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 32 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 35 * POSSIBILITY OF SUCH DAMAGE. 36 */ 37 38 /* 39 * Copyright (c) 2008 Constantine A. Murenin <cnst+openbsd@bugmail.mojo.ru> 40 * 41 * Permission to use, copy, modify, and distribute this software for any 42 * purpose with or without fee is hereby granted, provided that the above 43 * copyright notice and this permission notice appear in all copies. 44 * 45 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 46 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 47 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 48 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 49 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 50 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 51 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 52 */ 53 54 55 #include <sys/cdefs.h> 56 __KERNEL_RCSID(0, "$NetBSD: amdzentemp.c,v 1.14 2021/06/06 11:35:22 nonaka Exp $ "); 57 58 #include <sys/param.h> 59 #include <sys/bus.h> 60 #include <sys/cpu.h> 61 #include <sys/systm.h> 62 #include <sys/device.h> 63 #include <sys/kmem.h> 64 #include <sys/module.h> 65 66 #include <machine/specialreg.h> 67 68 #include <dev/pci/pcireg.h> 69 #include <dev/pci/pcivar.h> 70 #include <dev/pci/pcidevs.h> 71 72 #include <dev/sysmon/sysmonvar.h> 73 74 #include "amdsmn.h" 75 76 #define AMD_CURTMP_RANGE_ADJUST 49000000 /* in microKelvins (ie, 49C) */ 77 #define AMD_CURTMP_RANGE_CHECK __BIT(19) 78 #define F10_TEMP_CURTMP __BITS(31,21) /* XXX same as amdtemp.c */ 79 #define F10_TEMP_CURTMP_MASK 0x7ff 80 #define F15M60_CURTMP_TJSEL __BITS(17,16) 81 82 /* 83 * Reported Temperature, Family 15h, M60+ 84 * 85 * Same register bit definitions as other Family 15h CPUs, but access is 86 * indirect via SMN, like Family 17h. 87 */ 88 #define AMD_15H_M60H_REPTMP_CTRL 0xd8200ca4 89 90 /* 91 * Reported Temperature, Family 17h 92 * 93 * According to AMD OSRR for 17H, section 4.2.1, bits 31-21 of this register 94 * provide the current temp. bit 19, when clear, means the temp is reported in 95 * a range 0.."225C" (probable typo for 255C), and when set changes the range 96 * to -49..206C. 97 */ 98 #define AMD_17H_CUR_TMP 0x59800 99 100 /* 101 * The following register set was discovered experimentally by Ondrej Čerman 102 * and collaborators, but is not (yet) documented in a PPR/OSRR (other than 103 * the M70H PPR SMN memory map showing [0x59800, +0x314] as allocated to 104 * SMU::THM). It seems plausible and the Linux sensor folks have adopted it. 105 */ 106 #define AMD_17H_CCD_TMP_BASE 0x59954 107 #define AMD_17H_CCD_TMP_VALID __BIT(11) 108 109 struct amdzentemp_softc { 110 device_t sc_dev; 111 struct sysmon_envsys *sc_sme; 112 device_t sc_smn; 113 envsys_data_t *sc_sensor; 114 size_t sc_sensor_len; 115 size_t sc_numsensors; 116 int32_t sc_offset; 117 }; 118 119 enum { 120 NOSENSOR = 0, 121 CORE0_SENSOR0, 122 CCD_BASE, 123 CCD0 = CCD_BASE, 124 CCD1, 125 CCD2, 126 CCD3, 127 CCD4, 128 CCD5, 129 CCD6, 130 CCD7, 131 CCD_MAX, 132 NUM_CCDS = CCD_MAX - CCD_BASE 133 }; 134 135 136 static int amdzentemp_match(device_t, cfdata_t, void *); 137 static void amdzentemp_attach(device_t, device_t, void *); 138 static int amdzentemp_detach(device_t, int); 139 140 static void amdzentemp_init(struct amdzentemp_softc *, int, int); 141 static void amdzentemp_setup_sensors(struct amdzentemp_softc *); 142 static void amdzentemp_family15_refresh(struct sysmon_envsys *, envsys_data_t *); 143 static void amdzentemp_family17_refresh(struct sysmon_envsys *, envsys_data_t *); 144 static int amdzentemp_probe_ccd_sensors(struct amdzentemp_softc *, int, int); 145 static void amdzentemp_setup_ccd_sensors(struct amdzentemp_softc *); 146 147 CFATTACH_DECL_NEW(amdzentemp, sizeof(struct amdzentemp_softc), 148 amdzentemp_match, amdzentemp_attach, amdzentemp_detach, NULL); 149 150 static int 151 amdzentemp_match(device_t parent, cfdata_t match, void *aux) 152 { 153 struct pci_attach_args *pa __diagused = aux; 154 155 KASSERT(PCI_VENDOR(pa->pa_id) == PCI_VENDOR_AMD); 156 157 cfdata_t parent_cfdata = device_cfdata(parent); 158 159 /* Got AMD family 17h system management network */ 160 return parent_cfdata->cf_name && 161 memcmp(parent_cfdata->cf_name, "amdsmn", 6) == 0; 162 } 163 164 static void 165 amdzentemp_attach(device_t parent, device_t self, void *aux) 166 { 167 struct amdzentemp_softc *sc = device_private(self); 168 struct cpu_info *ci = curcpu(); 169 int family, model; 170 int error; 171 size_t i; 172 173 sc->sc_dev = self; 174 175 family = CPUID_TO_FAMILY(ci->ci_signature); 176 model = CPUID_TO_MODEL(ci->ci_signature); 177 aprint_naive("\n"); 178 aprint_normal(": AMD CPU Temperature Sensors (Family%xh)", family); 179 180 sc->sc_smn = parent; 181 182 amdzentemp_init(sc, family, model); 183 184 aprint_normal("\n"); 185 186 sc->sc_sme = sysmon_envsys_create(); 187 sc->sc_sensor_len = sizeof(envsys_data_t) * sc->sc_numsensors; 188 sc->sc_sensor = kmem_zalloc(sc->sc_sensor_len, KM_SLEEP); 189 190 amdzentemp_setup_sensors(sc); 191 192 /* 193 * Set properties in sensors. 194 */ 195 for (i = 0; i < sc->sc_numsensors; i++) { 196 if (sc->sc_sensor[i].private == NOSENSOR) 197 continue; 198 if (sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[i])) 199 goto bad; 200 } 201 202 /* 203 * Register the sysmon_envsys device. 204 */ 205 sc->sc_sme->sme_name = device_xname(self); 206 sc->sc_sme->sme_cookie = sc; 207 208 switch (family) { 209 case 0x15: 210 sc->sc_sme->sme_refresh = amdzentemp_family15_refresh; 211 break; 212 case 0x17: 213 case 0x19: 214 sc->sc_sme->sme_refresh = amdzentemp_family17_refresh; 215 break; 216 default: 217 /* XXX panic */ 218 break; 219 } 220 221 error = sysmon_envsys_register(sc->sc_sme); 222 if (error) { 223 aprint_error_dev(self, "unable to register with sysmon " 224 "(error=%d)\n", error); 225 goto bad; 226 } 227 228 (void)pmf_device_register(self, NULL, NULL); 229 230 return; 231 232 bad: 233 if (sc->sc_sme != NULL) { 234 sysmon_envsys_destroy(sc->sc_sme); 235 sc->sc_sme = NULL; 236 } 237 238 kmem_free(sc->sc_sensor, sc->sc_sensor_len); 239 sc->sc_sensor = NULL; 240 } 241 242 static int 243 amdzentemp_detach(device_t self, int flags) 244 { 245 struct amdzentemp_softc *sc = device_private(self); 246 247 pmf_device_deregister(self); 248 if (sc->sc_sme != NULL) 249 sysmon_envsys_unregister(sc->sc_sme); 250 251 if (sc->sc_sensor != NULL) 252 kmem_free(sc->sc_sensor, sc->sc_sensor_len); 253 254 return 0; 255 } 256 257 258 static void 259 amdzentemp_init(struct amdzentemp_softc *sc, int family, int model) 260 { 261 262 sc->sc_numsensors = 1 + amdzentemp_probe_ccd_sensors(sc, family, model); 263 sc->sc_offset = 0; 264 265 if (strstr(cpu_brand_string, "AMD Ryzen 5 1600X") 266 || strstr(cpu_brand_string, "AMD Ryzen 7 1700X") 267 || strstr(cpu_brand_string, "AMD Ryzen 7 1800X")) 268 sc->sc_offset = -20000000; 269 else if (strstr(cpu_brand_string, "AMD Ryzen 7 2700X")) 270 sc->sc_offset = -10000000; 271 else if (strstr(cpu_brand_string, "AMD Ryzen Threadripper 19") 272 || strstr(cpu_brand_string, "AMD Ryzen Threadripper 29")) 273 sc->sc_offset = -27000000; 274 } 275 276 static void 277 amdzentemp_setup_sensors(struct amdzentemp_softc *sc) 278 { 279 sc->sc_sensor[0].units = ENVSYS_STEMP; 280 sc->sc_sensor[0].state = ENVSYS_SVALID; 281 sc->sc_sensor[0].flags = ENVSYS_FHAS_ENTROPY; 282 sc->sc_sensor[0].private = CORE0_SENSOR0; 283 284 snprintf(sc->sc_sensor[0].desc, sizeof(sc->sc_sensor[0].desc), 285 "cpu%u temperature", device_unit(sc->sc_dev)); 286 287 if (sc->sc_numsensors > 1) 288 amdzentemp_setup_ccd_sensors(sc); 289 } 290 291 static void 292 amdzentemp_family15_refresh(struct sysmon_envsys *sme, 293 envsys_data_t *edata) 294 { 295 struct amdzentemp_softc *sc = sme->sme_cookie; 296 uint32_t val, temp; 297 int error; 298 299 error = amdsmn_read(sc->sc_smn, AMD_15H_M60H_REPTMP_CTRL, &val); 300 if (error) { 301 edata->state = ENVSYS_SINVALID; 302 return; 303 } 304 305 /* from amdtemp.c:amdtemp_family10_refresh() */ 306 temp = __SHIFTOUT(val, F10_TEMP_CURTMP); 307 308 /* From Celsius to micro-Kelvin. */ 309 edata->value_cur = (temp * 125000) + 273150000; 310 311 /* 312 * On Family 15h and higher, if CurTmpTjSel is 11b, the range is 313 * adjusted down by 49.0 degrees Celsius. (This adjustment is not 314 * documented in BKDGs prior to family 15h model 00h.) 315 * 316 * XXX should be in amdtemp.c:amdtemp_family10_refresh() for f15 317 * as well?? 318 */ 319 if (__SHIFTOUT(val, F15M60_CURTMP_TJSEL) == 0x3) 320 edata->value_cur -= AMD_CURTMP_RANGE_ADJUST; 321 322 edata->state = ENVSYS_SVALID; 323 } 324 325 static void 326 amdzentemp_family17_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) 327 { 328 struct amdzentemp_softc *sc = sme->sme_cookie; 329 uint32_t temp; 330 bool minus49; 331 int i, error; 332 333 switch (edata->private) { 334 case CORE0_SENSOR0: 335 /* Tctl */ 336 error = amdsmn_read(sc->sc_smn, AMD_17H_CUR_TMP, &temp); 337 if (error) { 338 edata->state = ENVSYS_SINVALID; 339 return; 340 } 341 minus49 = (temp & AMD_CURTMP_RANGE_CHECK) ? true : false; 342 temp = __SHIFTOUT(temp, F10_TEMP_CURTMP); 343 break; 344 case CCD_BASE ... (CCD_MAX - 1): 345 /* Tccd */ 346 i = edata->private - CCD_BASE; 347 error = amdsmn_read(sc->sc_smn, 348 AMD_17H_CCD_TMP_BASE + (i * sizeof(temp)), &temp); 349 if (error || !ISSET(temp, AMD_17H_CCD_TMP_VALID)) { 350 edata->state = ENVSYS_SINVALID; 351 return; 352 } 353 minus49 = true; 354 temp &= F10_TEMP_CURTMP_MASK; 355 break; 356 default: 357 edata->state = ENVSYS_SINVALID; 358 return; 359 } 360 edata->state = ENVSYS_SVALID; 361 /* From C to uK. */ 362 edata->value_cur = (temp * 125000) + 273150000; 363 /* adjust for possible offset of 49K */ 364 if (minus49) 365 edata->value_cur -= AMD_CURTMP_RANGE_ADJUST; 366 edata->value_cur += sc->sc_offset; 367 } 368 369 static int 370 amdzentemp_probe_ccd_sensors17h(struct amdzentemp_softc *sc, int model) 371 { 372 int maxreg; 373 374 switch (model) { 375 case 0x00 ... 0x2f: /* Zen1, Zen+ */ 376 maxreg = 4; 377 break; 378 case 0x30 ... 0x3f: /* Zen2 TR (Castle Peak)/EPYC (Rome) */ 379 case 0x60 ... 0x7f: /* Zen2 Ryzen (Renoir APU, Matisse) */ 380 case 0x90 ... 0x9f: /* Zen2 Ryzen (Van Gogh APU) */ 381 maxreg = 8; 382 break; 383 default: 384 aprint_error_dev(sc->sc_dev, 385 "Unrecognized Family 17h Model: %02xh\n", model); 386 return 0; 387 } 388 389 return maxreg; 390 } 391 392 static int 393 amdzentemp_probe_ccd_sensors19h(struct amdzentemp_softc *sc, int model) 394 { 395 int maxreg; 396 397 switch (model) { 398 case 0x00 ... 0x0f: /* Zen3 EPYC "Milan" */ 399 case 0x20 ... 0x2f: /* Zen3 Ryzen "Vermeer" */ 400 maxreg = 8; 401 break; 402 default: 403 aprint_error_dev(sc->sc_dev, 404 "Unrecognized Family 19h Model: %02xh\n", model); 405 return 0; 406 } 407 408 return maxreg; 409 } 410 411 static int 412 amdzentemp_probe_ccd_sensors(struct amdzentemp_softc *sc, int family, int model) 413 { 414 int nccd; 415 416 switch (family) { 417 case 0x17: 418 nccd = amdzentemp_probe_ccd_sensors17h(sc, model); 419 break; 420 case 0x19: 421 nccd = amdzentemp_probe_ccd_sensors19h(sc, model); 422 break; 423 default: 424 return 0; 425 } 426 427 return nccd; 428 } 429 430 static void 431 amdzentemp_setup_ccd_sensors(struct amdzentemp_softc *sc) 432 { 433 envsys_data_t *edata; 434 size_t i; 435 uint32_t temp; 436 int error; 437 438 for (i = 0; i < sc->sc_numsensors - 1; i++) { 439 error = amdsmn_read(sc->sc_smn, 440 AMD_17H_CCD_TMP_BASE + (i * sizeof(temp)), &temp); 441 if (error || !ISSET(temp, AMD_17H_CCD_TMP_VALID)) 442 continue; 443 444 edata = &sc->sc_sensor[1 + i]; 445 edata->units = ENVSYS_STEMP; 446 edata->state = ENVSYS_SVALID; 447 edata->flags = ENVSYS_FHAS_ENTROPY; 448 edata->private = CCD_BASE + i; 449 snprintf(edata->desc, sizeof(edata->desc), 450 "cpu%u ccd%zu temperature", device_unit(sc->sc_dev), i); 451 } 452 } 453 454 MODULE(MODULE_CLASS_DRIVER, amdzentemp, "sysmon_envsys,amdsmn"); 455 456 #ifdef _MODULE 457 #include "ioconf.c" 458 #endif 459 460 static int 461 amdzentemp_modcmd(modcmd_t cmd, void *aux) 462 { 463 int error = 0; 464 465 switch (cmd) { 466 case MODULE_CMD_INIT: 467 #ifdef _MODULE 468 error = config_init_component(cfdriver_ioconf_amdzentemp, 469 cfattach_ioconf_amdzentemp, cfdata_ioconf_amdzentemp); 470 #endif 471 return error; 472 case MODULE_CMD_FINI: 473 #ifdef _MODULE 474 error = config_fini_component(cfdriver_ioconf_amdzentemp, 475 cfattach_ioconf_amdzentemp, cfdata_ioconf_amdzentemp); 476 #endif 477 return error; 478 default: 479 return ENOTTY; 480 } 481 } 482