amdtemp.c revision 180326
1/*- 2 * Copyright (c) 2008 Rui Paulo <rpaulo@FreeBSD.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 18 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 22 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 23 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 * POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27/* 28 * Driver for the AMD K8 thermal sensors. Based on a Linux driver by the 29 * same name. 30 */ 31 32#include <sys/cdefs.h> 33__FBSDID("$FreeBSD: head/sys/dev/k8temp/k8temp.c 180326 2008-07-06 16:18:58Z rpaulo $"); 34 35#include <sys/param.h> 36#include <sys/bus.h> 37#include <sys/systm.h> 38#include <sys/types.h> 39#include <sys/module.h> 40#include <sys/conf.h> 41#include <sys/kernel.h> 42#include <sys/sysctl.h> 43 44#include <machine/specialreg.h> 45#include <machine/cpufunc.h> 46#include <machine/md_var.h> 47 48#include <dev/pci/pcireg.h> 49#include <dev/pci/pcivar.h> 50 51struct k8temp_softc { 52 device_t sc_dev; 53 int sc_temps[4]; 54 int sc_ntemps; 55 struct sysctl_oid *sc_oid; 56 struct sysctl_oid *sc_sysctl_cpu[2]; 57 struct intr_config_hook sc_ich; 58}; 59 60#define VENDORID_AMD 0x1022 61#define DEVICEID_AMD_MISC 0x1103 62 63static struct k8temp_product { 64 uint16_t k8temp_vendorid; 65 uint16_t k8temp_deviceid; 66} k8temp_products[] = { 67 { VENDORID_AMD, DEVICEID_AMD_MISC }, 68 { 0, 0 } 69}; 70 71/* 72 * Register control 73 */ 74#define K8TEMP_REG 0xe4 75#define K8TEMP_REG_SELSENSOR 0x40 76#define K8TEMP_REG_SELCORE 0x04 77 78#define K8TEMP_MINTEMP 49 /* -49 C is the mininum temperature */ 79 80typedef enum { 81 SENSOR0_CORE0, 82 SENSOR0_CORE1, 83 SENSOR1_CORE0, 84 SENSOR1_CORE1, 85 CORE0, 86 CORE1 87} k8sensor_t; 88 89/* 90 * Device methods. 91 */ 92static void k8temp_identify(driver_t *driver, device_t parent); 93static int k8temp_probe(device_t dev); 94static int k8temp_attach(device_t dev); 95static void k8temp_intrhook(void *arg); 96static int k8temp_detach(device_t dev); 97static int k8temp_match(device_t dev); 98static int32_t k8temp_gettemp(device_t dev, k8sensor_t sensor); 99static int k8temp_sysctl(SYSCTL_HANDLER_ARGS); 100 101static device_method_t k8temp_methods[] = { 102 /* Device interface */ 103 DEVMETHOD(device_identify, k8temp_identify), 104 DEVMETHOD(device_probe, k8temp_probe), 105 DEVMETHOD(device_attach, k8temp_attach), 106 DEVMETHOD(device_detach, k8temp_detach), 107 108 {0, 0} 109}; 110 111static driver_t k8temp_driver = { 112 "k8temp", 113 k8temp_methods, 114 sizeof(struct k8temp_softc), 115}; 116 117static devclass_t k8temp_devclass; 118DRIVER_MODULE(k8temp, hostb, k8temp_driver, k8temp_devclass, NULL, NULL); 119 120static int 121k8temp_match(device_t dev) 122{ 123 int i; 124 uint16_t vendor, devid; 125 126 vendor = pci_get_vendor(dev); 127 devid = pci_get_device(dev); 128 129 for (i = 0; k8temp_products[i].k8temp_vendorid != 0; i++) { 130 if (vendor == k8temp_products[i].k8temp_vendorid && 131 devid == k8temp_products[i].k8temp_deviceid) 132 return (1); 133 } 134 135 return (0); 136} 137 138static void 139k8temp_identify(driver_t *driver, device_t parent) 140{ 141 device_t child; 142 143 /* Make sure we're not being doubly invoked. */ 144 if (device_find_child(parent, "k8temp", -1) != NULL) 145 return; 146 147 if (k8temp_match(parent)) { 148 child = device_add_child(parent, "k8temp", -1); 149 if (child == NULL) 150 device_printf(parent, "add k8temp child failed\n"); 151 } 152 153} 154 155static int 156k8temp_probe(device_t dev) 157{ 158 uint32_t regs[4]; 159 160 if (resource_disabled("k8temp", 0)) 161 return (ENXIO); 162 163 do_cpuid(1, regs); 164 switch (regs[0]) { 165 case 0xf40: 166 case 0xf50: 167 case 0xf51: 168 return (ENXIO); 169 } 170 device_set_desc(dev, "AMD K8 Thermal Sensors"); 171 172 return (BUS_PROBE_GENERIC); 173} 174 175static int 176k8temp_attach(device_t dev) 177{ 178 struct k8temp_softc *sc = device_get_softc(dev); 179 struct sysctl_ctx_list *sysctlctx; 180 struct sysctl_oid *sysctlnode; 181 182 183 /* 184 * Setup intrhook function to create dev.cpu sysctl entries. This is 185 * needed because the cpu driver may be loaded late on boot, after 186 * us. 187 */ 188 sc->sc_ich.ich_func = k8temp_intrhook; 189 sc->sc_ich.ich_arg = dev; 190 config_intrhook_establish(&sc->sc_ich); 191 192 /* 193 * dev.k8temp.N tree. 194 */ 195 sysctlctx = device_get_sysctl_ctx(dev); 196 sysctlnode = SYSCTL_ADD_NODE(sysctlctx, 197 SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensor0", 198 CTLFLAG_RD, 0, "Sensor 0"); 199 200 SYSCTL_ADD_PROC(sysctlctx, 201 SYSCTL_CHILDREN(sysctlnode), 202 OID_AUTO, "core0", CTLTYPE_INT | CTLFLAG_RD, 203 dev, SENSOR0_CORE0, k8temp_sysctl, "I", 204 "Sensor 0 / Core 0 temperature"); 205 206 SYSCTL_ADD_PROC(sysctlctx, 207 SYSCTL_CHILDREN(sysctlnode), 208 OID_AUTO, "core1", CTLTYPE_INT | CTLFLAG_RD, 209 dev, SENSOR0_CORE1, k8temp_sysctl, "I", 210 "Sensor 0 / Core 1 temperature"); 211 212 sysctlnode = SYSCTL_ADD_NODE(sysctlctx, 213 SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensor1", 214 CTLFLAG_RD, 0, "Sensor 1"); 215 216 SYSCTL_ADD_PROC(sysctlctx, 217 SYSCTL_CHILDREN(sysctlnode), 218 OID_AUTO, "core0", CTLTYPE_INT | CTLFLAG_RD, 219 dev, SENSOR0_CORE0, k8temp_sysctl, "I", 220 "Sensor 1 / Core 0 temperature"); 221 222 SYSCTL_ADD_PROC(sysctlctx, 223 SYSCTL_CHILDREN(sysctlnode), 224 OID_AUTO, "core1", CTLTYPE_INT | CTLFLAG_RD, 225 dev, SENSOR0_CORE0, k8temp_sysctl, "I", 226 "Sensor 1 / Core 1 temperature"); 227 228 return (0); 229} 230 231void 232k8temp_intrhook(void *arg) 233{ 234 int i; 235 device_t nexus, acpi, cpu; 236 device_t dev = (device_t) arg; 237 struct k8temp_softc *sc; 238 struct sysctl_ctx_list *sysctlctx; 239 240 sc = device_get_softc(dev); 241 242 /* 243 * dev.cpu.N.temperature. 244 */ 245 nexus = device_find_child(root_bus, "nexus", 0); 246 acpi = device_find_child(nexus, "acpi", 0); 247 248 for (i = 0; i < 2; i++) { 249 cpu = device_find_child(acpi, "cpu", 250 device_get_unit(dev) * 2 + i); 251 if (cpu) { 252 sysctlctx = device_get_sysctl_ctx(cpu); 253 254 sc->sc_sysctl_cpu[i] = SYSCTL_ADD_PROC(sysctlctx, 255 SYSCTL_CHILDREN(device_get_sysctl_tree(cpu)), 256 OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD, 257 dev, CORE0, k8temp_sysctl, "I", 258 "Max of sensor 0 / 1"); 259 } 260 } 261 config_intrhook_disestablish(&sc->sc_ich); 262} 263 264int 265k8temp_detach(device_t dev) 266{ 267 int i; 268 struct k8temp_softc *sc = device_get_softc(dev); 269 270 for (i = 0; i < 2; i++) { 271 if (sc->sc_sysctl_cpu[i]) 272 sysctl_remove_oid(sc->sc_sysctl_cpu[i], 1, 0); 273 } 274 275 /* NewBus removes the dev.k8temp.N tree by itself. */ 276 277 return (0); 278} 279 280static int 281k8temp_sysctl(SYSCTL_HANDLER_ARGS) 282{ 283 device_t dev = (device_t) arg1; 284 int error; 285 int32_t temp, auxtemp[2]; 286 287 switch (arg2) { 288 case CORE0: 289 auxtemp[0] = k8temp_gettemp(dev, SENSOR0_CORE0); 290 auxtemp[1] = k8temp_gettemp(dev, SENSOR1_CORE0); 291 temp = imax(auxtemp[0], auxtemp[1]); 292 break; 293 case CORE1: 294 auxtemp[0] = k8temp_gettemp(dev, SENSOR0_CORE1); 295 auxtemp[1] = k8temp_gettemp(dev, SENSOR1_CORE1); 296 temp = imax(auxtemp[0], auxtemp[1]); 297 break; 298 default: 299 temp = k8temp_gettemp(dev, arg2); 300 break; 301 } 302 error = sysctl_handle_int(oidp, &temp, 0, req); 303 304 return (error); 305} 306 307static int32_t 308k8temp_gettemp(device_t dev, k8sensor_t sensor) 309{ 310 uint8_t cfg; 311 uint32_t temp; 312 313 cfg = pci_read_config(dev, K8TEMP_REG, 1); 314 switch (sensor) { 315 case SENSOR0_CORE0: 316 cfg &= ~(K8TEMP_REG_SELSENSOR | K8TEMP_REG_SELCORE); 317 break; 318 case SENSOR0_CORE1: 319 cfg &= ~K8TEMP_REG_SELSENSOR; 320 cfg |= K8TEMP_REG_SELCORE; 321 break; 322 case SENSOR1_CORE0: 323 cfg &= ~K8TEMP_REG_SELCORE; 324 cfg |= K8TEMP_REG_SELSENSOR; 325 break; 326 case SENSOR1_CORE1: 327 cfg |= (K8TEMP_REG_SELSENSOR | K8TEMP_REG_SELCORE); 328 break; 329 default: 330 cfg = 0; 331 break; 332 } 333 pci_write_config(dev, K8TEMP_REG, cfg, 1); 334 temp = pci_read_config(dev, K8TEMP_REG, 4); 335 temp = ((temp >> 16) & 0xff) - K8TEMP_MINTEMP; 336 337 return (temp); 338} 339