1 /* $NetBSD: pchtemp.c,v 1.1 2026/02/20 07:54:26 yamt Exp $ */ 2 3 /*- 4 * Copyright (c)2026 YAMAMOTO Takashi, 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 AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 __KERNEL_RCSID(0, "$NetBSD: pchtemp.c,v 1.1 2026/02/20 07:54:26 yamt Exp $"); 31 32 #include <sys/param.h> 33 34 #include <sys/bus.h> 35 #include <sys/module.h> 36 37 #include <dev/pci/pcidevs.h> 38 #include <dev/pci/pcireg.h> 39 #include <dev/pci/pcivar.h> 40 41 #include <dev/sysmon/sysmonvar.h> 42 43 /* 44 * references: 45 * 46 * 200-series-chipset-pch-datasheet-vol-2.pdf, downloaded from: 47 * https://www.intel.com/content/www/us/en/content-details/335193/intel-200-series-chipset-family-platform-controller-hub-pch-datasheet-volume-2-of-2.html 48 */ 49 50 /* Thermal Reporting Configuration Registers */ 51 #define PCHTEMP_CONF_TBAR 0x10 52 #define PCHTEMP_CONF_TBARH 0x14 53 54 /* Thermal Reporting Memory Mapped Registers */ 55 #define PCHTEMP_TEMP 0x00 56 #define PCHTEMP_TSEL 0x08 57 58 /* PCHTEMP_TEMP */ 59 #define PCHTEMP_TEMP_TSR_MASK 0x1ff 60 61 /* PCHTEMP_TSEL */ 62 #define PCHTEMP_TSEL_PLDB 0x80 63 #define PCHTEMP_TSEL_ETS 0x01 64 65 struct pchtemp_softc { 66 device_t sc_dev; 67 68 /* Thermal Reporting Memory Mapped Registers */ 69 bus_space_tag_t sc_iot; 70 bus_space_handle_t sc_ioh; 71 bus_size_t sc_size; 72 73 /* envsys stuff */ 74 struct sysmon_envsys *sc_sme; 75 envsys_data_t sc_sensor; 76 }; 77 78 static int32_t 79 pchtemp_convert_to_envsys_temp(uint16_t tsr) 80 { 81 /* 82 * the tsr value is in 0.5 degC resolution, offset -50C. 83 * ie. degC = tsr / 2 - 50 84 * 85 * ENVSYS_STEMP is in microkelvin. (uK) 86 * 87 * uK = K * 1000000 88 * = (degC + 273.15) * 1000000 89 * = ((tsr / 2 - 50) + 273.15) * 1000000 90 * = tsr * 500000 + 223150000 91 * 92 * this does never overflow int32_t as 0 <= tsr <= 0x1ff. 93 */ 94 return (int32_t)tsr * 500000 + 223150000; 95 } 96 97 static void 98 pchtemp_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) 99 { 100 struct pchtemp_softc *sc = sme->sme_cookie; 101 uint16_t temp = bus_space_read_2(sc->sc_iot, sc->sc_ioh, PCHTEMP_TEMP); 102 uint16_t tsr = temp & PCHTEMP_TEMP_TSR_MASK; 103 104 sc->sc_sensor.value_cur = pchtemp_convert_to_envsys_temp(tsr); 105 sc->sc_sensor.state = ENVSYS_SVALID; 106 } 107 108 static int 109 pchtemp_match(device_t parent, cfdata_t match, void *aux) 110 { 111 const struct pci_attach_args *pa = aux; 112 if (PCI_VENDOR(pa->pa_id) != PCI_VENDOR_INTEL) { 113 return 0; 114 } 115 switch (PCI_PRODUCT(pa->pa_id)) { 116 case PCI_PRODUCT_INTEL_8SERIES_THERM: 117 case PCI_PRODUCT_INTEL_CORE4G_M_THERM: 118 case PCI_PRODUCT_INTEL_CORE5G_M_THERM: 119 case PCI_PRODUCT_INTEL_100SERIES_THERM: 120 case PCI_PRODUCT_INTEL_100SERIES_LP_THERM: 121 case PCI_PRODUCT_INTEL_2HS_THERM: 122 case PCI_PRODUCT_INTEL_3HS_THERM: 123 case PCI_PRODUCT_INTEL_3HS_U_THERM: 124 case PCI_PRODUCT_INTEL_4HS_H_THERM: 125 case PCI_PRODUCT_INTEL_CMTLK_THERM: 126 case PCI_PRODUCT_INTEL_C610_THERM: 127 case PCI_PRODUCT_INTEL_C620_THERM: 128 return 1; 129 } 130 return 0; 131 } 132 133 static void 134 pchtemp_envsys_attach(struct pchtemp_softc *sc) 135 { 136 const char *xname = device_xname(sc->sc_dev); 137 struct sysmon_envsys *sme; 138 envsys_data_t *sensor = &sc->sc_sensor; 139 int error; 140 141 sensor->units = ENVSYS_STEMP; 142 sensor->state = ENVSYS_SINVALID; 143 sensor->flags = 0; 144 (void)snprintf(sensor->desc, sizeof(sensor->desc), "%s temperature", 145 xname); 146 147 sme = sysmon_envsys_create(); 148 error = sysmon_envsys_sensor_attach(sme, sensor); 149 if (error != 0) { 150 aprint_error_dev(sc->sc_dev, 151 "sysmon_envsys_sensor_attach failed (error %d)\n", error); 152 goto fail; 153 } 154 sme->sme_cookie = sc; 155 sme->sme_name = xname; 156 sme->sme_refresh = pchtemp_refresh; 157 error = sysmon_envsys_register(sme); 158 if (error != 0) { 159 aprint_error_dev(sc->sc_dev, 160 "sysmon_envsys_register failed (error %d)\n", error); 161 goto fail; 162 } 163 sc->sc_sme = sme; 164 return; 165 fail: 166 sysmon_envsys_destroy(sme); 167 } 168 169 static void 170 pchtemp_attach(device_t parent, device_t self, void *aux) 171 { 172 struct pchtemp_softc *sc = device_private(self); 173 struct pci_attach_args *pa = aux; 174 uint8_t tsel; 175 176 KASSERT( 177 sc->sc_sme == NULL && sc->sc_size == 0); /* zeroed by autoconf */ 178 sc->sc_dev = self; 179 180 aprint_naive("\n"); 181 aprint_normal(": Intel PCH Temperature Sensor\n"); 182 183 if (pci_mapreg_map(pa, PCHTEMP_CONF_TBAR, 184 PCI_MAPREG_TYPE_MEM | PCI_MAPREG_MEM_TYPE_64BIT, 0, &sc->sc_iot, 185 &sc->sc_ioh, NULL, &sc->sc_size)) { 186 aprint_error_dev(self, "can't map I/O space\n"); 187 goto out; 188 } 189 190 /* try to enable the sensor if it isn't already enabled */ 191 tsel = bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCHTEMP_TSEL); 192 if ((tsel & PCHTEMP_TSEL_ETS) == 0) { 193 aprint_normal_dev(sc->sc_dev, "disabled by BIOS\n"); 194 if ((tsel & PCHTEMP_TSEL_PLDB) != 0) { 195 aprint_normal_dev(sc->sc_dev, 196 "can't enable the sensor as it's locked\n"); 197 goto out; 198 } 199 tsel |= PCHTEMP_TSEL_ETS; 200 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCHTEMP_TSEL, tsel); 201 aprint_normal_dev(sc->sc_dev, "enabled by the driver\n"); 202 } 203 204 pchtemp_envsys_attach(sc); 205 out:; 206 } 207 208 static int 209 pchtemp_detach(device_t self, int flags) 210 { 211 struct pchtemp_softc *sc = device_private(self); 212 213 if (sc->sc_sme != NULL) { 214 sysmon_envsys_unregister(sc->sc_sme); 215 } 216 if (sc->sc_size != 0) { 217 bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_size); 218 } 219 return 0; 220 } 221 222 CFATTACH_DECL_NEW(pchtemp, sizeof(struct pchtemp_softc), pchtemp_match, 223 pchtemp_attach, pchtemp_detach, NULL); 224 225 MODULE(MODULE_CLASS_DRIVER, pchtemp, "sysmon_envsys"); 226 227 #ifdef _MODULE 228 #include "ioconf.c" 229 #endif 230 231 static int 232 pchtemp_modcmd(modcmd_t cmd, void *aux) 233 { 234 int error = 0; 235 236 switch (cmd) { 237 case MODULE_CMD_INIT: 238 #ifdef _MODULE 239 error = config_init_component(cfdriver_ioconf_pchtemp, 240 cfattach_ioconf_pchtemp, cfdata_ioconf_pchtemp); 241 #endif 242 break; 243 case MODULE_CMD_FINI: 244 #ifdef _MODULE 245 error = config_fini_component(cfdriver_ioconf_pchtemp, 246 cfattach_ioconf_pchtemp, cfdata_ioconf_pchtemp); 247 #endif 248 break; 249 default: 250 error = ENOTTY; 251 break; 252 } 253 return error; 254 } 255