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