amdtemp.c revision 185434
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 185434 2008-11-29 14:26:22Z 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 if (config_intrhook_establish(&sc->sc_ich) != 0) { 191 device_printf(dev, "config_intrhook_establish " 192 "failed!\n"); 193 return (ENXIO); 194 } 195 196 /* 197 * dev.k8temp.N tree. 198 */ 199 sysctlctx = device_get_sysctl_ctx(dev); 200 sysctlnode = SYSCTL_ADD_NODE(sysctlctx, 201 SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensor0", 202 CTLFLAG_RD, 0, "Sensor 0"); 203 204 SYSCTL_ADD_PROC(sysctlctx, 205 SYSCTL_CHILDREN(sysctlnode), 206 OID_AUTO, "core0", CTLTYPE_INT | CTLFLAG_RD, 207 dev, SENSOR0_CORE0, k8temp_sysctl, "I", 208 "Sensor 0 / Core 0 temperature"); 209 210 SYSCTL_ADD_PROC(sysctlctx, 211 SYSCTL_CHILDREN(sysctlnode), 212 OID_AUTO, "core1", CTLTYPE_INT | CTLFLAG_RD, 213 dev, SENSOR0_CORE1, k8temp_sysctl, "I", 214 "Sensor 0 / Core 1 temperature"); 215 216 sysctlnode = SYSCTL_ADD_NODE(sysctlctx, 217 SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensor1", 218 CTLFLAG_RD, 0, "Sensor 1"); 219 220 SYSCTL_ADD_PROC(sysctlctx, 221 SYSCTL_CHILDREN(sysctlnode), 222 OID_AUTO, "core0", CTLTYPE_INT | CTLFLAG_RD, 223 dev, SENSOR1_CORE0, k8temp_sysctl, "I", 224 "Sensor 1 / Core 0 temperature"); 225 226 SYSCTL_ADD_PROC(sysctlctx, 227 SYSCTL_CHILDREN(sysctlnode), 228 OID_AUTO, "core1", CTLTYPE_INT | CTLFLAG_RD, 229 dev, SENSOR1_CORE1, k8temp_sysctl, "I", 230 "Sensor 1 / Core 1 temperature"); 231 232 return (0); 233} 234 235void 236k8temp_intrhook(void *arg) 237{ 238 int i; 239 device_t nexus, acpi, cpu; 240 device_t dev = (device_t) arg; 241 struct k8temp_softc *sc; 242 struct sysctl_ctx_list *sysctlctx; 243 244 sc = device_get_softc(dev); 245 246 /* 247 * dev.cpu.N.temperature. 248 */ 249 nexus = device_find_child(root_bus, "nexus", 0); 250 acpi = device_find_child(nexus, "acpi", 0); 251 252 for (i = 0; i < 2; i++) { 253 cpu = device_find_child(acpi, "cpu", 254 device_get_unit(dev) * 2 + i); 255 if (cpu) { 256 sysctlctx = device_get_sysctl_ctx(cpu); 257 258 sc->sc_sysctl_cpu[i] = SYSCTL_ADD_PROC(sysctlctx, 259 SYSCTL_CHILDREN(device_get_sysctl_tree(cpu)), 260 OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD, 261 dev, CORE0, k8temp_sysctl, "I", 262 "Max of sensor 0 / 1"); 263 } 264 } 265 config_intrhook_disestablish(&sc->sc_ich); 266} 267 268int 269k8temp_detach(device_t dev) 270{ 271 int i; 272 struct k8temp_softc *sc = device_get_softc(dev); 273 274 for (i = 0; i < 2; i++) { 275 if (sc->sc_sysctl_cpu[i]) 276 sysctl_remove_oid(sc->sc_sysctl_cpu[i], 1, 0); 277 } 278 279 /* NewBus removes the dev.k8temp.N tree by itself. */ 280 281 return (0); 282} 283 284static int 285k8temp_sysctl(SYSCTL_HANDLER_ARGS) 286{ 287 device_t dev = (device_t) arg1; 288 int error; 289 int32_t temp, auxtemp[2]; 290 291 switch (arg2) { 292 case CORE0: 293 auxtemp[0] = k8temp_gettemp(dev, SENSOR0_CORE0); 294 auxtemp[1] = k8temp_gettemp(dev, SENSOR1_CORE0); 295 temp = imax(auxtemp[0], auxtemp[1]); 296 break; 297 case CORE1: 298 auxtemp[0] = k8temp_gettemp(dev, SENSOR0_CORE1); 299 auxtemp[1] = k8temp_gettemp(dev, SENSOR1_CORE1); 300 temp = imax(auxtemp[0], auxtemp[1]); 301 break; 302 default: 303 temp = k8temp_gettemp(dev, arg2); 304 break; 305 } 306 error = sysctl_handle_int(oidp, &temp, 0, req); 307 308 return (error); 309} 310 311static int32_t 312k8temp_gettemp(device_t dev, k8sensor_t sensor) 313{ 314 uint8_t cfg; 315 uint32_t temp; 316 317 cfg = pci_read_config(dev, K8TEMP_REG, 1); 318 switch (sensor) { 319 case SENSOR0_CORE0: 320 cfg &= ~(K8TEMP_REG_SELSENSOR | K8TEMP_REG_SELCORE); 321 break; 322 case SENSOR0_CORE1: 323 cfg &= ~K8TEMP_REG_SELSENSOR; 324 cfg |= K8TEMP_REG_SELCORE; 325 break; 326 case SENSOR1_CORE0: 327 cfg &= ~K8TEMP_REG_SELCORE; 328 cfg |= K8TEMP_REG_SELSENSOR; 329 break; 330 case SENSOR1_CORE1: 331 cfg |= (K8TEMP_REG_SELSENSOR | K8TEMP_REG_SELCORE); 332 break; 333 default: 334 cfg = 0; 335 break; 336 } 337 pci_write_config(dev, K8TEMP_REG, cfg, 1); 338 temp = pci_read_config(dev, K8TEMP_REG, 4); 339 temp = ((temp >> 16) & 0xff) - K8TEMP_MINTEMP; 340 341 return (temp); 342} 343