#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/sensors.h>
#include <machine/specialreg.h>
#include <bus/pci/pcivar.h>
#include "pcidevs.h"
#define K_THERMTRIP_STAT_R 0xe4
#define K_NORTHBRIDGE_CAP_R 0xe8
#define K_CPUID_FAMILY_MODEL_R 0xfc
#define K_THERM_SENSE_SEL (1 << 6)
#define K_THERM_SENSE_CORE_SEL (1 << 2)
#define K_T_SEL_C0(v) (v |= K_THERM_SENSE_CORE_SEL)
#define K_T_SEL_C1(v) (v &= ~(K_THERM_SENSE_CORE_SEL))
#define K_T_SEL_S0(v) (v &= ~(K_THERM_SENSE_SEL))
#define K_T_SEL_S1(v) (v |= K_THERM_SENSE_SEL)
static const struct {
const char rev[5];
const uint32_t cpuid[5];
} kate_proc[] = {
{ "BH-F", { 0x00040FB0, 0x00040F80, 0, 0, 0 } },
{ "DH-F", { 0x00040FF0, 0x00050FF0, 0x00040FC0, 0, 0 } },
{ "JH-F", { 0x00040F10, 0x00040F30, 0x000C0F10, 0, 0 } },
{ "BH-G", { 0x00060FB0, 0x00060F80, 0, 0, 0 } },
{ "DH-G", { 0x00070FF0, 0x00060FF0,
0x00060FC0, 0x00070FC0, 0 } }
};
struct kate_softc {
device_t sc_dev;
struct ksensor sc_sensors[4];
struct ksensordev sc_sensordev;
char sc_rev;
int8_t sc_ii;
int8_t sc_in;
int32_t sc_flags;
#define KATE_FLAG_ALT_OFFSET 0x04
};
static void kate_identify(driver_t *, device_t);
static int kate_probe(device_t);
static int kate_attach(device_t);
static int kate_detach(device_t);
static void kate_refresh(void *);
static device_method_t kate_methods[] = {
DEVMETHOD(device_identify, kate_identify),
DEVMETHOD(device_probe, kate_probe),
DEVMETHOD(device_attach, kate_attach),
DEVMETHOD(device_detach, kate_detach),
{ NULL, NULL }
};
static driver_t kate_driver = {
"kate",
kate_methods,
sizeof(struct kate_softc)
};
static devclass_t kate_devclass;
DRIVER_MODULE(kate, hostb, kate_driver, kate_devclass, NULL, NULL);
static void
kate_identify(driver_t *driver, device_t parent)
{
if (kate_probe(parent) == ENXIO)
return;
if (device_find_child(parent, driver->name, -1) != NULL)
return;
device_add_child(parent, driver->name, -1);
}
static int
kate_probe(device_t dev)
{
#ifndef KATE_STRICT
struct kate_softc ks;
struct kate_softc *sc = &ks;
#endif
uint32_t c;
int i, j;
if (pci_get_vendor(dev) != PCI_VENDOR_AMD ||
pci_get_device(dev) != PCI_PRODUCT_AMD_AMD64_MISC)
return ENXIO;
if (device_get_desc(dev) == NULL)
device_set_desc(dev,
"AMD Family 0Fh temperature sensors");
c = pci_read_config(dev, K_CPUID_FAMILY_MODEL_R, 4);
for (i = 0; i < NELEM(kate_proc); i++)
for (j = 0; kate_proc[i].cpuid[j] != 0; j++)
if ((c & ~0xf) == kate_proc[i].cpuid[j])
return 0;
#ifndef KATE_STRICT
sc->sc_ii = 0;
sc->sc_in = 4;
sc->sc_dev = dev;
kate_refresh(sc);
for (i = 0; i < 4; i++)
if (!(sc->sc_sensors[i].flags & SENSOR_FINVALID))
return 0;
#endif
return ENXIO;
}
static int
kate_attach(device_t dev)
{
struct kate_softc *sc;
uint32_t c, d;
int i, j, cmpcap, model;
u_int regs[4], brand_id;
sc = device_get_softc(dev);
sc->sc_dev = dev;
c = pci_read_config(dev, K_CPUID_FAMILY_MODEL_R, 4);
for (i = 0; i < NELEM(kate_proc) && sc->sc_rev == '\0'; i++)
for (j = 0; kate_proc[i].cpuid[j] != 0; j++)
if ((c & ~0xf) == kate_proc[i].cpuid[j]) {
sc->sc_rev = kate_proc[i].rev[3];
device_printf(dev, "core rev %.4s%.1x\n",
kate_proc[i].rev, c & 0xf);
break;
}
if (c != 0x0 && sc->sc_rev == '\0') {
sc->sc_rev = 'G';
device_printf(dev, "cpuid 0x%x\n", c);
}
model = CPUID_TO_MODEL(c);
if (model >= 0x60 && model != 0xc1) {
do_cpuid(0x80000001, regs);
brand_id = (regs[1] >> 9) & 0x1f;
switch (model) {
case 0x68:
case 0x6c:
case 0x7c:
break;
case 0x6b:
if (brand_id != 0x0b && brand_id != 0x0c)
sc->sc_flags |= KATE_FLAG_ALT_OFFSET;
break;
case 0x6f:
case 0x7f:
if (brand_id != 0x07 && brand_id != 0x09 &&
brand_id != 0x0c)
sc->sc_flags |= KATE_FLAG_ALT_OFFSET;
break;
default:
sc->sc_flags |= KATE_FLAG_ALT_OFFSET;
}
}
d = pci_read_config(dev, K_NORTHBRIDGE_CAP_R, 4);
cmpcap = (d >> 12) & 0x3;
#ifndef KATE_STRICT
sc->sc_ii = 0;
sc->sc_in = 4;
kate_refresh(sc);
if (cmpcap == 0) {
if ((sc->sc_sensors[0].flags & SENSOR_FINVALID) &&
(sc->sc_sensors[1].flags & SENSOR_FINVALID))
sc->sc_ii = 2;
if ((sc->sc_sensors[3].flags & SENSOR_FINVALID))
sc->sc_in = 3;
}
#else
sc->sc_ii = cmpcap ? 0 : 2;
sc->sc_in = 4;
#endif
strlcpy(sc->sc_sensordev.xname, device_get_nameunit(dev),
sizeof(sc->sc_sensordev.xname));
for (i = sc->sc_ii; i < sc->sc_in; i++) {
sc->sc_sensors[i].type = SENSOR_TEMP;
sensor_attach(&sc->sc_sensordev, &sc->sc_sensors[i]);
}
sensor_task_register(sc, kate_refresh, 5);
sensordev_install(&sc->sc_sensordev);
return 0;
}
static int
kate_detach(device_t dev)
{
struct kate_softc *sc = device_get_softc(dev);
sensordev_deinstall(&sc->sc_sensordev);
sensor_task_unregister(sc);
return 0;
}
static void
kate_refresh(void *arg)
{
struct kate_softc *sc = arg;
struct ksensor *s = sc->sc_sensors;
uint32_t t, m;
int64_t temp;
int i, v;
t = pci_read_config(sc->sc_dev, K_THERMTRIP_STAT_R, 4);
for (i = sc->sc_ii; i < sc->sc_in; i++) {
switch(i) {
case 0:
K_T_SEL_C0(t);
K_T_SEL_S0(t);
break;
case 1:
K_T_SEL_C0(t);
K_T_SEL_S1(t);
break;
case 2:
K_T_SEL_C1(t);
K_T_SEL_S0(t);
break;
case 3:
K_T_SEL_C1(t);
K_T_SEL_S1(t);
break;
}
m = t & (K_THERM_SENSE_CORE_SEL | K_THERM_SENSE_SEL);
pci_write_config(sc->sc_dev, K_THERMTRIP_STAT_R, t, 4);
t = pci_read_config(sc->sc_dev, K_THERMTRIP_STAT_R, 4);
v = 0x3ff & (t >> 14);
#ifdef KATE_STRICT
if (sc->sc_rev != 'G')
v &= ~0x3;
#endif
if ((t & (K_THERM_SENSE_CORE_SEL | K_THERM_SENSE_SEL)) == m &&
(v & ~0x3) != 0)
s[i].flags &= ~SENSOR_FINVALID;
else
s[i].flags |= SENSOR_FINVALID;
temp = v * 250000;
temp -= (sc->sc_flags & KATE_FLAG_ALT_OFFSET) != 0 ?
28000000 : 49000000;
temp += 273150000;
s[i].value = temp;
}
}