1 /* $NetBSD: amdzentemp.c,v 1.15 2022/10/01 15:50:05 msaitoh 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.15 2022/10/01 15:50:05 msaitoh 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 uint32_t sc_ccd_tmp_base; 118 }; 119 120 enum { 121 NOSENSOR = 0, 122 CORE0_SENSOR0, 123 CCD_BASE, 124 CCD0 = CCD_BASE, 125 CCD1, 126 CCD2, 127 CCD3, 128 CCD4, 129 CCD5, 130 CCD6, 131 CCD7, 132 CCD_MAX, 133 NUM_CCDS = CCD_MAX - CCD_BASE 134 }; 135 136 137 static int amdzentemp_match(device_t, cfdata_t, void *); 138 static void amdzentemp_attach(device_t, device_t, void *); 139 static int amdzentemp_detach(device_t, int); 140 141 static void amdzentemp_init(struct amdzentemp_softc *, int, int); 142 static void amdzentemp_setup_sensors(struct amdzentemp_softc *); 143 static void amdzentemp_family15_refresh(struct sysmon_envsys *, envsys_data_t *); 144 static void amdzentemp_family17_refresh(struct sysmon_envsys *, envsys_data_t *); 145 static int amdzentemp_probe_ccd_sensors(struct amdzentemp_softc *, int, int); 146 static void amdzentemp_setup_ccd_sensors(struct amdzentemp_softc *); 147 148 CFATTACH_DECL_NEW(amdzentemp, sizeof(struct amdzentemp_softc), 149 amdzentemp_match, amdzentemp_attach, amdzentemp_detach, NULL); 150 151 static int 152 amdzentemp_match(device_t parent, cfdata_t match, void *aux) 153 { 154 struct pci_attach_args *pa __diagused = aux; 155 156 KASSERT(PCI_VENDOR(pa->pa_id) == PCI_VENDOR_AMD); 157 158 cfdata_t parent_cfdata = device_cfdata(parent); 159 160 /* Got AMD family 17h system management network */ 161 return parent_cfdata->cf_name && 162 memcmp(parent_cfdata->cf_name, "amdsmn", 6) == 0; 163 } 164 165 static void 166 amdzentemp_attach(device_t parent, device_t self, void *aux) 167 { 168 struct amdzentemp_softc *sc = device_private(self); 169 struct cpu_info *ci = curcpu(); 170 int family, model; 171 int error; 172 size_t i; 173 174 sc->sc_dev = self; 175 176 family = CPUID_TO_FAMILY(ci->ci_signature); 177 model = CPUID_TO_MODEL(ci->ci_signature); 178 aprint_naive("\n"); 179 aprint_normal(": AMD CPU Temperature Sensors (Family%xh)", family); 180 181 sc->sc_smn = parent; 182 183 amdzentemp_init(sc, family, model); 184 185 aprint_normal("\n"); 186 187 sc->sc_sme = sysmon_envsys_create(); 188 sc->sc_sensor_len = sizeof(envsys_data_t) * sc->sc_numsensors; 189 sc->sc_sensor = kmem_zalloc(sc->sc_sensor_len, KM_SLEEP); 190 191 amdzentemp_setup_sensors(sc); 192 193 /* 194 * Set properties in sensors. 195 */ 196 for (i = 0; i < sc->sc_numsensors; i++) { 197 if (sc->sc_sensor[i].private == NOSENSOR) 198 continue; 199 if (sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[i])) 200 goto bad; 201 } 202 203 /* 204 * Register the sysmon_envsys device. 205 */ 206 sc->sc_sme->sme_name = device_xname(self); 207 sc->sc_sme->sme_cookie = sc; 208 209 switch (family) { 210 case 0x15: 211 sc->sc_sme->sme_refresh = amdzentemp_family15_refresh; 212 break; 213 case 0x17: 214 case 0x19: 215 sc->sc_sme->sme_refresh = amdzentemp_family17_refresh; 216 break; 217 default: 218 /* XXX panic */ 219 break; 220 } 221 222 error = sysmon_envsys_register(sc->sc_sme); 223 if (error) { 224 aprint_error_dev(self, "unable to register with sysmon " 225 "(error=%d)\n", error); 226 goto bad; 227 } 228 229 (void)pmf_device_register(self, NULL, NULL); 230 231 return; 232 233 bad: 234 if (sc->sc_sme != NULL) { 235 sysmon_envsys_destroy(sc->sc_sme); 236 sc->sc_sme = NULL; 237 } 238 239 kmem_free(sc->sc_sensor, sc->sc_sensor_len); 240 sc->sc_sensor = NULL; 241 } 242 243 static int 244 amdzentemp_detach(device_t self, int flags) 245 { 246 struct amdzentemp_softc *sc = device_private(self); 247 248 pmf_device_deregister(self); 249 if (sc->sc_sme != NULL) 250 sysmon_envsys_unregister(sc->sc_sme); 251 252 if (sc->sc_sensor != NULL) 253 kmem_free(sc->sc_sensor, sc->sc_sensor_len); 254 255 return 0; 256 } 257 258 259 static void 260 amdzentemp_init(struct amdzentemp_softc *sc, int family, int model) 261 { 262 263 sc->sc_numsensors = 1 + amdzentemp_probe_ccd_sensors(sc, family, model); 264 sc->sc_offset = 0; 265 266 if (strstr(cpu_brand_string, "AMD Ryzen 5 1600X") 267 || strstr(cpu_brand_string, "AMD Ryzen 7 1700X") 268 || strstr(cpu_brand_string, "AMD Ryzen 7 1800X")) 269 sc->sc_offset = -20000000; 270 else if (strstr(cpu_brand_string, "AMD Ryzen 7 2700X")) 271 sc->sc_offset = -10000000; 272 else if (strstr(cpu_brand_string, "AMD Ryzen Threadripper 19") 273 || strstr(cpu_brand_string, "AMD Ryzen Threadripper 29")) 274 sc->sc_offset = -27000000; 275 } 276 277 static void 278 amdzentemp_setup_sensors(struct amdzentemp_softc *sc) 279 { 280 sc->sc_sensor[0].units = ENVSYS_STEMP; 281 sc->sc_sensor[0].state = ENVSYS_SVALID; 282 sc->sc_sensor[0].flags = ENVSYS_FHAS_ENTROPY; 283 sc->sc_sensor[0].private = CORE0_SENSOR0; 284 285 snprintf(sc->sc_sensor[0].desc, sizeof(sc->sc_sensor[0].desc), 286 "cpu%u temperature", device_unit(sc->sc_dev)); 287 288 if (sc->sc_numsensors > 1) 289 amdzentemp_setup_ccd_sensors(sc); 290 } 291 292 static void 293 amdzentemp_family15_refresh(struct sysmon_envsys *sme, 294 envsys_data_t *edata) 295 { 296 struct amdzentemp_softc *sc = sme->sme_cookie; 297 uint32_t val, temp; 298 int error; 299 300 error = amdsmn_read(sc->sc_smn, AMD_15H_M60H_REPTMP_CTRL, &val); 301 if (error) { 302 edata->state = ENVSYS_SINVALID; 303 return; 304 } 305 306 /* from amdtemp.c:amdtemp_family10_refresh() */ 307 temp = __SHIFTOUT(val, F10_TEMP_CURTMP); 308 309 /* From Celsius to micro-Kelvin. */ 310 edata->value_cur = (temp * 125000) + 273150000; 311 312 /* 313 * On Family 15h and higher, if CurTmpTjSel is 11b, the range is 314 * adjusted down by 49.0 degrees Celsius. (This adjustment is not 315 * documented in BKDGs prior to family 15h model 00h.) 316 * 317 * XXX should be in amdtemp.c:amdtemp_family10_refresh() for f15 318 * as well?? 319 */ 320 if (__SHIFTOUT(val, F15M60_CURTMP_TJSEL) == 0x3) 321 edata->value_cur -= AMD_CURTMP_RANGE_ADJUST; 322 323 edata->state = ENVSYS_SVALID; 324 } 325 326 static void 327 amdzentemp_family17_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) 328 { 329 struct amdzentemp_softc *sc = sme->sme_cookie; 330 uint32_t temp; 331 bool minus49; 332 int i, error; 333 334 switch (edata->private) { 335 case CORE0_SENSOR0: 336 /* Tctl */ 337 error = amdsmn_read(sc->sc_smn, AMD_17H_CUR_TMP, &temp); 338 if (error) { 339 edata->state = ENVSYS_SINVALID; 340 return; 341 } 342 minus49 = (temp & AMD_CURTMP_RANGE_CHECK) ? true : false; 343 temp = __SHIFTOUT(temp, F10_TEMP_CURTMP); 344 break; 345 case CCD_BASE ... (CCD_MAX - 1): 346 /* Tccd */ 347 i = edata->private - CCD_BASE; 348 error = amdsmn_read(sc->sc_smn, 349 sc->sc_ccd_tmp_base + (i * sizeof(temp)), &temp); 350 if (error || !ISSET(temp, AMD_17H_CCD_TMP_VALID)) { 351 edata->state = ENVSYS_SINVALID; 352 return; 353 } 354 minus49 = true; 355 temp &= F10_TEMP_CURTMP_MASK; 356 break; 357 default: 358 edata->state = ENVSYS_SINVALID; 359 return; 360 } 361 edata->state = ENVSYS_SVALID; 362 /* From C to uK. */ 363 edata->value_cur = (temp * 125000) + 273150000; 364 /* adjust for possible offset of 49K */ 365 if (minus49) 366 edata->value_cur -= AMD_CURTMP_RANGE_ADJUST; 367 edata->value_cur += sc->sc_offset; 368 } 369 370 static int 371 amdzentemp_probe_ccd_sensors17h(struct amdzentemp_softc *sc, int model) 372 { 373 int maxreg; 374 375 switch (model) { 376 case 0x00 ... 0x2f: /* Zen1, Zen+ */ 377 maxreg = 4; 378 break; 379 case 0x30 ... 0x3f: /* Zen2 TR (Castle Peak)/EPYC (Rome) */ 380 case 0x60 ... 0x7f: /* Zen2 Ryzen (Renoir APU, Matisse) */ 381 case 0x90 ... 0x9f: /* Zen2 Ryzen (Van Gogh APU) */ 382 maxreg = 8; 383 break; 384 default: 385 aprint_error_dev(sc->sc_dev, 386 "Unrecognized Family 17h Model: %02xh\n", model); 387 return 0; 388 } 389 390 return maxreg; 391 } 392 393 static int 394 amdzentemp_probe_ccd_sensors19h(struct amdzentemp_softc *sc, int model) 395 { 396 int maxreg; 397 398 switch (model) { 399 case 0x00 ... 0x0f: /* Zen3 EPYC "Milan" */ 400 case 0x20 ... 0x2f: /* Zen3 Ryzen "Vermeer" */ 401 maxreg = 8; 402 break; 403 case 0x60 ... 0x6f: /* Zen4 Ryzen "Raphael" */ 404 sc->sc_ccd_tmp_base = 0x59b08; 405 maxreg = 8; 406 break; 407 default: 408 aprint_error_dev(sc->sc_dev, 409 "Unrecognized Family 19h Model: %02xh\n", model); 410 return 0; 411 } 412 413 return maxreg; 414 } 415 416 static int 417 amdzentemp_probe_ccd_sensors(struct amdzentemp_softc *sc, int family, int model) 418 { 419 int nccd; 420 421 /* Set default CCD temp sensor base address. */ 422 sc->sc_ccd_tmp_base = 0x59954; 423 424 switch (family) { 425 case 0x17: 426 nccd = amdzentemp_probe_ccd_sensors17h(sc, model); 427 break; 428 case 0x19: 429 nccd = amdzentemp_probe_ccd_sensors19h(sc, model); 430 break; 431 default: 432 return 0; 433 } 434 435 return nccd; 436 } 437 438 static void 439 amdzentemp_setup_ccd_sensors(struct amdzentemp_softc *sc) 440 { 441 envsys_data_t *edata; 442 size_t i; 443 uint32_t temp; 444 int error; 445 446 for (i = 0; i < sc->sc_numsensors - 1; i++) { 447 error = amdsmn_read(sc->sc_smn, 448 sc->sc_ccd_tmp_base + (i * sizeof(temp)), &temp); 449 if (error || !ISSET(temp, AMD_17H_CCD_TMP_VALID)) 450 continue; 451 452 edata = &sc->sc_sensor[1 + i]; 453 edata->units = ENVSYS_STEMP; 454 edata->state = ENVSYS_SVALID; 455 edata->flags = ENVSYS_FHAS_ENTROPY; 456 edata->private = CCD_BASE + i; 457 snprintf(edata->desc, sizeof(edata->desc), 458 "cpu%u ccd%zu temperature", device_unit(sc->sc_dev), i); 459 } 460 } 461 462 MODULE(MODULE_CLASS_DRIVER, amdzentemp, "sysmon_envsys,amdsmn"); 463 464 #ifdef _MODULE 465 #include "ioconf.c" 466 #endif 467 468 static int 469 amdzentemp_modcmd(modcmd_t cmd, void *aux) 470 { 471 int error = 0; 472 473 switch (cmd) { 474 case MODULE_CMD_INIT: 475 #ifdef _MODULE 476 error = config_init_component(cfdriver_ioconf_amdzentemp, 477 cfattach_ioconf_amdzentemp, cfdata_ioconf_amdzentemp); 478 #endif 479 return error; 480 case MODULE_CMD_FINI: 481 #ifdef _MODULE 482 error = config_fini_component(cfdriver_ioconf_amdzentemp, 483 cfattach_ioconf_amdzentemp, cfdata_ioconf_amdzentemp); 484 #endif 485 return error; 486 default: 487 return ENOTTY; 488 } 489 } 490