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