#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/bitops.h>
#include <bus/pci/pcivar.h>
#include <bus/pci/pcireg.h>
#include <bus/pci/pci_cfgreg.h>
#include <vm/pmap.h>
#include "coremctl_if.h"
#include "pcib_if.h"
#include <dev/misc/coremctl/coremctl_reg.h>
#define COREMCTL_VER_1 1
#define COREMCTL_VER_2 2
#define COREMCTL_VER_3 3
struct coremctl_type {
uint16_t did;
const char *desc;
int ver;
};
struct coremctl_softc {
device_t sc_dev;
int sc_ver;
device_t sc_ecc;
device_t sc_temp;
volatile uint8_t *sc_mch;
};
#define CSR_READ_4(sc, ofs) \
(*(volatile uint32_t *)((sc)->sc_mch + (ofs)))
#define CSR_WRITE_4(sc, ofs, val) \
(*(volatile uint32_t *)((sc)->sc_mch + (ofs)) = (val))
static void coremctl_identify(driver_t *, device_t);
static int coremctl_probe(device_t);
static int coremctl_attach(device_t);
static int coremctl_detach(device_t);
static int coremctl_mch_readreg(device_t, int, uint32_t *);
static int coremctl_mch_writereg(device_t, int, uint32_t);
static int coremctl_pci_read_ivar(device_t, device_t, int, uintptr_t *);
static uint32_t coremctl_pci_read_config(device_t, device_t, int, int);
static void coremctl_pci_write_config(device_t, device_t, int, uint32_t,
int);
static void coremctl_chaninfo(struct coremctl_softc *, uint32_t,
const char *);
static const struct coremctl_type coremctl_types[] = {
{ PCI_E3V1_MEMCTL_DID, "Intel E3 memory controller",
COREMCTL_VER_1 },
{ PCI_E3V2_MEMCTL_DID, "Intel E3 v2 memory controller",
COREMCTL_VER_2 },
{ PCI_E3V3_MEMCTL_DID, "Intel E3 v3 memory controller",
COREMCTL_VER_3 },
{ PCI_COREV3_MEMCTL_DID, "Intel i3/i5/i7 Haswell memory controller",
COREMCTL_VER_3 },
{ 0, NULL, 0 }
};
static device_method_t coremctl_methods[] = {
DEVMETHOD(device_identify, coremctl_identify),
DEVMETHOD(device_probe, coremctl_probe),
DEVMETHOD(device_attach, coremctl_attach),
DEVMETHOD(device_detach, coremctl_detach),
DEVMETHOD(device_shutdown, bus_generic_shutdown),
DEVMETHOD(device_suspend, bus_generic_suspend),
DEVMETHOD(device_resume, bus_generic_resume),
DEVMETHOD(bus_read_ivar, coremctl_pci_read_ivar),
DEVMETHOD(pci_read_config, coremctl_pci_read_config),
DEVMETHOD(pci_write_config, coremctl_pci_write_config),
DEVMETHOD(coremctl_mch_read, coremctl_mch_readreg),
DEVMETHOD(coremctl_mch_write, coremctl_mch_writereg),
DEVMETHOD_END
};
static driver_t coremctl_driver = {
"coremctl",
coremctl_methods,
sizeof(struct coremctl_softc)
};
static devclass_t coremctl_devclass;
DRIVER_MODULE(coremctl, hostb, coremctl_driver, coremctl_devclass, NULL, NULL);
MODULE_VERSION(coremctl, 1);
MODULE_DEPEND(coremctl, pci, 1, 1, 1);
static void
coremctl_identify(driver_t *driver, device_t parent)
{
const struct coremctl_type *t;
uint16_t did;
if (device_find_child(parent, "coremctl", -1) != NULL)
return;
if (pci_get_vendor(parent) != PCI_CORE_MEMCTL_VID)
return;
did = pci_get_device(parent);
for (t = coremctl_types; t->desc != NULL; ++t) {
if (t->did == did) {
if (device_add_child(parent, "coremctl", -1) == NULL)
device_printf(parent, "add coremctl failed\n");
return;
}
}
}
static int
coremctl_probe(device_t dev)
{
const struct coremctl_type *t;
uint16_t did;
if (pci_get_vendor(dev) != PCI_CORE_MEMCTL_VID)
return ENXIO;
did = pci_get_device(dev);
for (t = coremctl_types; t->desc != NULL; ++t) {
if (t->did == did) {
struct coremctl_softc *sc = device_get_softc(dev);
device_set_desc(dev, t->desc);
sc->sc_ver = t->ver;
return 0;
}
}
return ENXIO;
}
static int
coremctl_attach(device_t dev)
{
struct coremctl_softc *sc = device_get_softc(dev);
uint32_t capa, dmfc, mch_barlo, mch_barhi;
uint64_t mch_bar;
int dmfc_parsed = 1;
sc->sc_dev = dev;
capa = pci_read_config(dev, PCI_CORE_CAPID0_A, 4);
if (sc->sc_ver == COREMCTL_VER_1) {
dmfc = __SHIFTOUT(capa, PCI_CORE_CAPID0_A_DMFC);
} else {
uint32_t capb;
capb = pci_read_config(dev, PCI_CORE_CAPID0_B, 4);
dmfc = __SHIFTOUT(capb, PCI_CORE_CAPID0_B_DMFC);
}
if (dmfc == PCI_CORE_CAPID0_DMFC_1067) {
device_printf(dev, "CAP DDR3 1067 ");
} else if (dmfc == PCI_CORE_CAPID0_DMFC_1333) {
device_printf(dev, "CAP DDR3 1333 ");
} else {
if (sc->sc_ver == COREMCTL_VER_1) {
if (dmfc == PCI_CORE_CAPID0_DMFC_V1_ALL)
device_printf(dev, "no CAP ");
else
dmfc_parsed = 0;
} else {
if (dmfc == PCI_CORE_CAPID0_DMFC_1600)
device_printf(dev, "CAP DDR3 1600 ");
else if (dmfc == PCI_CORE_CAPID0_DMFC_1867)
device_printf(dev, "CAP DDR3 1867 ");
else if (dmfc == PCI_CORE_CAPID0_DMFC_2133)
device_printf(dev, "CAP DDR3 2133 ");
else if (dmfc == PCI_CORE_CAPID0_DMFC_2400)
device_printf(dev, "CAP DDR3 2400 ");
else if (dmfc == PCI_CORE_CAPID0_DMFC_2667)
device_printf(dev, "CAP DDR3 2667 ");
else if (dmfc == PCI_CORE_CAPID0_DMFC_2933)
device_printf(dev, "CAP DDR3 2933 ");
else
dmfc_parsed = 0;
}
}
if (!dmfc_parsed) {
device_printf(dev, "unknown DMFC %#x\n", dmfc);
return 0;
}
if (capa & PCI_CORE_CAPID0_A_ECCDIS) {
kprintf("NON-ECC\n");
} else {
kprintf("ECC\n");
sc->sc_ecc = device_add_child(dev, "ecc", -1);
if (sc->sc_ecc == NULL)
device_printf(dev, "add ecc failed\n");
}
mch_barlo = pci_read_config(dev, PCI_CORE_MCHBAR_LO, 4);
mch_barhi = pci_read_config(dev, PCI_CORE_MCHBAR_HI, 4);
mch_bar = (uint64_t)mch_barlo | (((uint64_t)mch_barhi) << 32);
if (bootverbose)
device_printf(dev, "MCHBAR 0x%jx\n", (uintmax_t)mch_bar);
if (mch_bar & PCI_CORE_MCHBAR_LO_EN) {
uint64_t map_addr = mch_bar & PCI_CORE_MCHBAR_ADDRMASK;
sc->sc_mch = pmap_mapdev_uncacheable(map_addr, MCH_CORE_SIZE);
if (bootverbose) {
uint32_t dimm_ch0, dimm_ch1;
dimm_ch0 = CSR_READ_4(sc, MCH_CORE_DIMM_CH0);
dimm_ch1 = CSR_READ_4(sc, MCH_CORE_DIMM_CH1);
coremctl_chaninfo(sc, dimm_ch0, "channel0");
coremctl_chaninfo(sc, dimm_ch1, "channel1");
}
} else {
device_printf(dev, "MCHBAR is not enabled\n");
}
if (sc->sc_ver == COREMCTL_VER_3 && sc->sc_mch != NULL) {
uint32_t ptm_ctl;
ptm_ctl = CSR_READ_4(sc, MCH_CORE_DDR_PTM_CTL0);
if (ptm_ctl & (MCH_CORE_DDR_PTM_CTL0_CLTM |
MCH_CORE_DDR_PTM_CTL0_EXTTS | MCH_CORE_DDR_PTM_CTL0_OLTM)) {
sc->sc_temp = device_add_child(dev, "memtemp", -1);
if (sc->sc_temp == NULL)
device_printf(dev, "add memtemp failed\n");
}
}
bus_generic_attach(dev);
return 0;
}
static void
coremctl_chaninfo(struct coremctl_softc *sc, uint32_t dimm_ch,
const char *desc)
{
int size_a, size_b;
int dimma_id, dimmb_id;
dimma_id = 0;
dimmb_id = 1;
if (dimm_ch & MCH_CORE_DIMM_A_SELECT) {
dimma_id = 1;
dimmb_id = 0;
}
size_a = __SHIFTOUT(dimm_ch, MCH_CORE_DIMM_A_SIZE);
if (size_a != 0) {
device_printf(sc->sc_dev, "%s, DIMM%d %dMB %dx%d\n", desc,
dimma_id, size_a * MCH_CORE_DIMM_SIZE_UNIT,
(dimm_ch & MCH_CORE_DIMM_A_DUAL_RANK) ? 2 : 1,
(dimm_ch & MCH_CORE_DIMM_A_X16) ? 16 : 8);
}
size_b = __SHIFTOUT(dimm_ch, MCH_CORE_DIMM_B_SIZE);
if (size_b != 0) {
device_printf(sc->sc_dev, "%s, DIMM%d %dMB %dx%d\n", desc,
dimmb_id, size_b * MCH_CORE_DIMM_SIZE_UNIT,
(dimm_ch & MCH_CORE_DIMM_B_DUAL_RANK) ? 2 : 1,
(dimm_ch & MCH_CORE_DIMM_B_X16) ? 16 : 8);
}
if (size_a == 0 && size_b == 0)
return;
if (sc->sc_ver == COREMCTL_VER_1 || sc->sc_ver == COREMCTL_VER_2) {
dimm_ch &= ~MCH_CORE_DIMM_HORI;
}
if (dimm_ch & (MCH_CORE_DIMM_ENHI | MCH_CORE_DIMM_RI |
MCH_CORE_DIMM_HORI)) {
device_printf(sc->sc_dev, "%s", desc);
if (dimm_ch & MCH_CORE_DIMM_RI)
kprintf(", rank interleave");
if (dimm_ch & MCH_CORE_DIMM_ENHI)
kprintf(", enhanced interleave");
if (dimm_ch & MCH_CORE_DIMM_HORI)
kprintf(", high order rank interleave");
kprintf("\n");
}
}
static int
coremctl_detach(device_t dev)
{
struct coremctl_softc *sc = device_get_softc(dev);
if (sc->sc_ecc != NULL)
device_delete_child(dev, sc->sc_ecc);
if (sc->sc_temp != NULL)
device_delete_child(dev, sc->sc_temp);
bus_generic_detach(dev);
if (sc->sc_mch != NULL)
pmap_unmapdev((vm_offset_t)sc->sc_mch, MCH_CORE_SIZE);
return 0;
}
static int
coremctl_mch_readreg(device_t dev, int reg, uint32_t *val)
{
struct coremctl_softc *sc = device_get_softc(dev);
if (sc->sc_mch == NULL)
return EOPNOTSUPP;
*val = CSR_READ_4(sc, reg);
return 0;
}
static int
coremctl_mch_writereg(device_t dev, int reg, uint32_t val)
{
struct coremctl_softc *sc = device_get_softc(dev);
if (sc->sc_mch == NULL)
return EOPNOTSUPP;
CSR_WRITE_4(sc, reg, val);
return 0;
}
static int
coremctl_pci_read_ivar(device_t dev, device_t child, int which,
uintptr_t *result)
{
return BUS_READ_IVAR(device_get_parent(dev), dev, which, result);
}
static uint32_t
coremctl_pci_read_config(device_t dev, device_t child, int reg, int width)
{
return pci_read_config(dev, reg, width);
}
static void
coremctl_pci_write_config(device_t dev, device_t child, int reg, uint32_t val,
int width)
{
pci_write_config(dev, reg, val, width);
}