#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/bus.h>
#include <sys/endian.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <dev/jedec_dimm/jedec_dimm.h>
#include <dev/smbus/smbconf.h>
#include <dev/smbus/smbus.h>
#include "smbus_if.h"
struct jedec_dimm_softc {
device_t dev;
device_t smbus;
uint8_t spd_addr;
uint8_t tsod_addr;
uint8_t mfg_year;
uint8_t mfg_week;
uint32_t capacity_mb;
char type_str[5];
char part_str[21];
char serial_str[9];
char *slotid_str;
};
const struct jedec_dimm_tsod_dev {
uint16_t vendor_id;
uint8_t device_id;
const char *description;
} known_tsod_devices[] = {
{ 0x11d4, 0x08, "Analog Devices TSOD" },
{ 0x001f, 0x82, "Atmel TSOD" },
{ 0x1114, 0x22, "Atmel TSOD" },
{ 0x00b3, 0x29, "IDT TSOD" },
{ 0x00b3, 0x22, "IDT TSOD" },
{ 0x004d, 0x3e, "Maxim Integrated TSOD" },
{ 0x004d, 0x54, "Maxim Integrated TSOD" },
{ 0x0054, 0x00, "Microchip TSOD" },
{ 0x0054, 0x20, "Microchip TSOD" },
{ 0x0054, 0x21, "Microchip TSOD" },
{ 0x1055, 0x08, "Microchip TSOD" },
{ 0x1131, 0xa1, "NXP TSOD" },
{ 0x1131, 0xa2, "NXP TSOD" },
{ 0x1b09, 0x08, "ON Semiconductor TSOD" },
{ 0x1b09, 0x0a, "ON Semiconductor TSOD" },
{ 0x104a, 0x00, "ST Microelectronics TSOD" },
{ 0x104a, 0x03, "ST Microelectronics TSOD" },
};
static int jedec_dimm_adjust_offset(struct jedec_dimm_softc *sc,
uint16_t orig_offset, uint16_t *new_offset, bool *page_changed);
static int jedec_dimm_attach(device_t dev);
static int jedec_dimm_capacity(struct jedec_dimm_softc *sc, enum dram_type type,
uint32_t *capacity_mb);
static int jedec_dimm_detach(device_t dev);
static int jedec_dimm_dump(struct jedec_dimm_softc *sc, enum dram_type type);
static int jedec_dimm_field_to_str(struct jedec_dimm_softc *sc, char *dst,
size_t dstsz, uint16_t offset, uint16_t len, bool ascii);
static int jedec_dimm_mfg_date(struct jedec_dimm_softc *sc, enum dram_type type,
uint8_t *year, uint8_t *week);
static int jedec_dimm_probe(device_t dev);
static int jedec_dimm_readw_be(struct jedec_dimm_softc *sc, uint8_t reg,
uint16_t *val);
static int jedec_dimm_reset_page0(struct jedec_dimm_softc *sc);
static int jedec_dimm_temp_sysctl(SYSCTL_HANDLER_ARGS);
static const char *jedec_dimm_tsod_match(uint16_t vid, uint16_t did);
static int
jedec_dimm_adjust_offset(struct jedec_dimm_softc *sc, uint16_t orig_offset,
uint16_t *new_offset, bool *page_changed)
{
int rc;
*new_offset = orig_offset;
*page_changed = false;
if (orig_offset < JEDEC_SPD_PAGE_SIZE) {
rc = 0;
goto out;
} else if (orig_offset >= (2 * JEDEC_SPD_PAGE_SIZE)) {
rc = EINVAL;
device_printf(sc->dev, "invalid offset 0x%04x\n", orig_offset);
goto out;
}
rc = smbus_writeb(sc->smbus, (JEDEC_DTI_PAGE | JEDEC_LSA_PAGE_SET1),
0, 0);
if (rc != 0) {
device_printf(sc->dev,
"unable to change page for offset 0x%04x: %d\n",
orig_offset, rc);
goto out;
}
*page_changed = true;
*new_offset = orig_offset - JEDEC_SPD_PAGE_SIZE;
out:
return (rc);
}
static int
jedec_dimm_attach(device_t dev)
{
uint8_t byte;
uint16_t devid;
uint16_t partnum_len;
uint16_t partnum_offset;
uint16_t serial_len;
uint16_t serial_offset;
uint16_t tsod_present_offset;
uint16_t vendorid;
bool tsod_present;
int rc;
enum dram_type type;
struct jedec_dimm_softc *sc;
struct sysctl_ctx_list *ctx;
struct sysctl_oid *oid;
struct sysctl_oid_list *children;
const char *tsod_match;
const char *slotid_str;
sc = device_get_softc(dev);
ctx = device_get_sysctl_ctx(dev);
oid = device_get_sysctl_tree(dev);
children = SYSCTL_CHILDREN(oid);
bzero(sc, sizeof(*sc));
sc->dev = dev;
sc->smbus = device_get_parent(dev);
sc->spd_addr = smbus_get_addr(dev);
sc->tsod_addr = JEDEC_DTI_TSOD | (sc->spd_addr & 0x0f);
rc = smbus_readb(sc->smbus, sc->spd_addr, SPD_OFFSET_DRAM_TYPE, &byte);
if (rc != 0) {
device_printf(dev, "failed to read dram_type: %d\n", rc);
goto out;
}
type = (enum dram_type) byte;
switch (type) {
case DRAM_TYPE_DDR3_SDRAM:
(void) snprintf(sc->type_str, sizeof(sc->type_str), "DDR3");
partnum_len = SPD_LEN_DDR3_PARTNUM;
partnum_offset = SPD_OFFSET_DDR3_PARTNUM;
serial_len = SPD_LEN_DDR3_SERIAL;
serial_offset = SPD_OFFSET_DDR3_SERIAL;
tsod_present_offset = SPD_OFFSET_DDR3_TSOD_PRESENT;
break;
case DRAM_TYPE_DDR4_SDRAM:
(void) snprintf(sc->type_str, sizeof(sc->type_str), "DDR4");
partnum_len = SPD_LEN_DDR4_PARTNUM;
partnum_offset = SPD_OFFSET_DDR4_PARTNUM;
serial_len = SPD_LEN_DDR4_SERIAL;
serial_offset = SPD_OFFSET_DDR4_SERIAL;
tsod_present_offset = SPD_OFFSET_DDR4_TSOD_PRESENT;
break;
default:
device_printf(dev, "unsupported dram_type 0x%02x\n", type);
rc = EINVAL;
goto out;
}
if (bootverbose) {
(void) jedec_dimm_dump(sc, type);
}
rc = jedec_dimm_capacity(sc, type, &sc->capacity_mb);
if (rc != 0) {
goto out;
}
rc = jedec_dimm_mfg_date(sc, type, &sc->mfg_year, &sc->mfg_week);
if (rc != 0) {
goto out;
}
rc = jedec_dimm_field_to_str(sc, sc->part_str, sizeof(sc->part_str),
partnum_offset, partnum_len, true);
if (rc != 0) {
goto out;
}
rc = jedec_dimm_field_to_str(sc, sc->serial_str, sizeof(sc->serial_str),
serial_offset, serial_len, false);
if (rc != 0) {
goto out;
}
rc = smbus_readb(sc->smbus, sc->spd_addr, tsod_present_offset, &byte);
if (rc != 0) {
device_printf(dev, "failed to read TSOD-present byte: %d\n",
rc);
goto out;
}
if (byte & 0x80) {
tsod_present = true;
rc = jedec_dimm_readw_be(sc, TSOD_REG_MANUFACTURER, &vendorid);
if (rc != 0) {
device_printf(dev,
"failed to read TSOD Manufacturer ID\n");
rc = 0;
goto no_tsod;
}
rc = jedec_dimm_readw_be(sc, TSOD_REG_DEV_REV, &devid);
if (rc != 0) {
device_printf(dev, "failed to read TSOD Device ID\n");
rc = 0;
goto no_tsod;
}
tsod_match = jedec_dimm_tsod_match(vendorid, devid);
if (bootverbose) {
if (tsod_match == NULL) {
device_printf(dev,
"Unknown TSOD Manufacturer and Device IDs,"
" 0x%x and 0x%x\n", vendorid, devid);
} else {
device_printf(dev,
"TSOD: %s\n", tsod_match);
}
}
} else {
no_tsod:
tsod_match = NULL;
tsod_present = false;
}
SYSCTL_ADD_STRING(ctx, children, OID_AUTO, "type",
CTLFLAG_RD | CTLFLAG_MPSAFE, sc->type_str, 0,
"DIMM type");
SYSCTL_ADD_UINT(ctx, children, OID_AUTO, "capacity",
CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, sc->capacity_mb,
"DIMM capacity (MB)");
SYSCTL_ADD_STRING(ctx, children, OID_AUTO, "part",
CTLFLAG_RD | CTLFLAG_MPSAFE, sc->part_str, 0,
"DIMM Part Number");
SYSCTL_ADD_STRING(ctx, children, OID_AUTO, "serial",
CTLFLAG_RD | CTLFLAG_MPSAFE, sc->serial_str, 0,
"DIMM Serial Number");
SYSCTL_ADD_U8(ctx, children, OID_AUTO, "mfg_year",
CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, sc->mfg_year,
"DIMM manufacturing year (20xx)");
SYSCTL_ADD_U8(ctx, children, OID_AUTO, "mfg_week",
CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, sc->mfg_week,
"DIMM manufacturing week");
if (tsod_present && (tsod_match != NULL)) {
SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "temp",
CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 0,
jedec_dimm_temp_sysctl, "IK", "DIMM temperature (deg C)");
}
if (resource_string_value(device_get_name(dev), device_get_unit(dev),
"slotid", &slotid_str) == 0) {
if (slotid_str != NULL) {
sc->slotid_str = strdup(slotid_str, M_DEVBUF);
SYSCTL_ADD_STRING(ctx, children, OID_AUTO, "slotid",
CTLFLAG_RD | CTLFLAG_MPSAFE, sc->slotid_str, 0,
"DIMM Slot Identifier");
}
}
if ((tsod_match != NULL) || (sc->slotid_str != NULL)) {
device_set_descf(dev, "%s%s%s%s%s%s",
device_get_desc(dev),
(tsod_match ? " w/ " : ""),
(tsod_match ? tsod_match : ""),
(sc->slotid_str ? " (" : ""),
(sc->slotid_str ? sc->slotid_str : ""),
(sc->slotid_str ? ")" : ""));
}
out:
return (rc);
}
static int
jedec_dimm_capacity(struct jedec_dimm_softc *sc, enum dram_type type,
uint32_t *capacity_mb)
{
uint8_t bus_width_byte;
uint8_t bus_width_offset;
uint8_t dimm_ranks_byte;
uint8_t dimm_ranks_offset;
uint8_t sdram_capacity_byte;
uint8_t sdram_capacity_offset;
uint8_t sdram_pkg_type_byte;
uint8_t sdram_pkg_type_offset;
uint8_t sdram_width_byte;
uint8_t sdram_width_offset;
uint32_t bus_width;
uint32_t dimm_ranks;
uint32_t sdram_capacity;
uint32_t sdram_pkg_type;
uint32_t sdram_width;
int rc;
switch (type) {
case DRAM_TYPE_DDR3_SDRAM:
bus_width_offset = SPD_OFFSET_DDR3_BUS_WIDTH;
dimm_ranks_offset = SPD_OFFSET_DDR3_DIMM_RANKS;
sdram_capacity_offset = SPD_OFFSET_DDR3_SDRAM_CAPACITY;
sdram_width_offset = SPD_OFFSET_DDR3_SDRAM_WIDTH;
break;
case DRAM_TYPE_DDR4_SDRAM:
bus_width_offset = SPD_OFFSET_DDR4_BUS_WIDTH;
dimm_ranks_offset = SPD_OFFSET_DDR4_DIMM_RANKS;
sdram_capacity_offset = SPD_OFFSET_DDR4_SDRAM_CAPACITY;
sdram_pkg_type_offset = SPD_OFFSET_DDR4_SDRAM_PKG_TYPE;
sdram_width_offset = SPD_OFFSET_DDR4_SDRAM_WIDTH;
break;
default:
__assert_unreachable();
}
rc = smbus_readb(sc->smbus, sc->spd_addr, bus_width_offset,
&bus_width_byte);
if (rc != 0) {
device_printf(sc->dev, "failed to read bus_width: %d\n", rc);
goto out;
}
rc = smbus_readb(sc->smbus, sc->spd_addr, dimm_ranks_offset,
&dimm_ranks_byte);
if (rc != 0) {
device_printf(sc->dev, "failed to read dimm_ranks: %d\n", rc);
goto out;
}
rc = smbus_readb(sc->smbus, sc->spd_addr, sdram_capacity_offset,
&sdram_capacity_byte);
if (rc != 0) {
device_printf(sc->dev, "failed to read sdram_capacity: %d\n",
rc);
goto out;
}
rc = smbus_readb(sc->smbus, sc->spd_addr, sdram_width_offset,
&sdram_width_byte);
if (rc != 0) {
device_printf(sc->dev, "failed to read sdram_width: %d\n", rc);
goto out;
}
if (type == DRAM_TYPE_DDR4_SDRAM) {
rc = smbus_readb(sc->smbus, sc->spd_addr, sdram_pkg_type_offset,
&sdram_pkg_type_byte);
if (rc != 0) {
device_printf(sc->dev,
"failed to read sdram_pkg_type: %d\n", rc);
goto out;
}
}
bus_width_byte &= 0x07;
if (bus_width_byte <= 3) {
bus_width = 1 << bus_width_byte;
bus_width *= 8;
} else {
device_printf(sc->dev, "invalid bus width info\n");
rc = EINVAL;
goto out;
}
dimm_ranks_byte >>= 3;
dimm_ranks_byte &= 0x07;
if (dimm_ranks_byte <= 7) {
dimm_ranks = dimm_ranks_byte + 1;
} else {
device_printf(sc->dev, "invalid DIMM Rank info\n");
rc = EINVAL;
goto out;
}
if ((dimm_ranks_byte >= 4) && (type != DRAM_TYPE_DDR4_SDRAM)) {
device_printf(sc->dev, "invalid DIMM Rank info\n");
rc = EINVAL;
goto out;
}
sdram_capacity_byte &= 0x0f;
if (sdram_capacity_byte <= 7) {
sdram_capacity = 1 << sdram_capacity_byte;
sdram_capacity *= 256;
} else if (sdram_capacity_byte <= 9) {
sdram_capacity = 12 << (sdram_capacity_byte - 8);
sdram_capacity *= 1024;
} else {
device_printf(sc->dev, "invalid SDRAM capacity info\n");
rc = EINVAL;
goto out;
}
if ((sdram_capacity_byte >= 7) && (type != DRAM_TYPE_DDR4_SDRAM)) {
device_printf(sc->dev, "invalid SDRAM capacity info\n");
rc = EINVAL;
goto out;
}
sdram_width_byte &= 0x7;
if (sdram_width_byte <= 3) {
sdram_width = 1 << sdram_width_byte;
sdram_width *= 4;
} else {
device_printf(sc->dev, "invalid SDRAM width info\n");
rc = EINVAL;
goto out;
}
if ((type == DRAM_TYPE_DDR4_SDRAM) &&
((sdram_pkg_type_byte & 0x3) == 2)) {
sdram_pkg_type_byte >>= 4;
sdram_pkg_type_byte &= 0x07;
sdram_pkg_type = sdram_pkg_type_byte + 1;
dimm_ranks *= sdram_pkg_type;
}
*capacity_mb = sdram_capacity / 8 * bus_width / sdram_width *
dimm_ranks;
out:
return (rc);
}
static int
jedec_dimm_detach(device_t dev)
{
struct jedec_dimm_softc *sc;
sc = device_get_softc(dev);
free(sc->slotid_str, M_DEVBUF);
return (0);
}
static int
jedec_dimm_dump(struct jedec_dimm_softc *sc, enum dram_type type)
{
int i;
int rc;
bool page_changed;
uint8_t bytes[512];
page_changed = false;
for (i = 0; i < 256; i++) {
rc = smbus_readb(sc->smbus, sc->spd_addr, i, &bytes[i]);
if (rc != 0) {
device_printf(sc->dev,
"unable to read page0:0x%02x: %d\n", i, rc);
goto out;
}
}
if (type == DRAM_TYPE_DDR4_SDRAM) {
page_changed = true;
rc = smbus_writeb(sc->smbus,
(JEDEC_DTI_PAGE | JEDEC_LSA_PAGE_SET1), 0, 0);
if (rc != 0) {
device_printf(sc->dev, "unable to change page: %d\n",
rc);
}
for (i = 0; i < 256; i++) {
rc = smbus_readb(sc->smbus, sc->spd_addr, i,
&bytes[256 + i]);
if (rc != 0) {
device_printf(sc->dev,
"unable to read page1:0x%02x: %d\n", i, rc);
goto out;
}
}
}
hexdump(bytes, (page_changed ? 512 : 256), NULL, 0);
out:
if (page_changed) {
int rc2;
rc2 = jedec_dimm_reset_page0(sc);
if ((rc2 != 0) && (rc == 0)) {
rc = rc2;
}
}
return (rc);
}
static int
jedec_dimm_field_to_str(struct jedec_dimm_softc *sc, char *dst, size_t dstsz,
uint16_t offset, uint16_t len, bool ascii)
{
uint8_t byte;
uint16_t new_offset;
int i;
int rc;
bool page_changed;
rc = jedec_dimm_adjust_offset(sc, offset, &new_offset, &page_changed);
if (rc != 0) {
goto out;
}
if (new_offset >= JEDEC_SPD_PAGE_SIZE) {
rc = EINVAL;
device_printf(sc->dev, "invalid offset 0x%04x\n", new_offset);
goto out;
}
if ((new_offset + len) >= JEDEC_SPD_PAGE_SIZE) {
rc = EINVAL;
device_printf(sc->dev,
"(new_offset + len) would cross page (0x%04x + 0x%04x)\n",
new_offset, len);
goto out;
}
if (ascii) {
if (dstsz < (len + 1)) {
rc = EINVAL;
device_printf(sc->dev,
"destination too short (%u < %u)\n",
(uint16_t) dstsz, (len + 1));
goto out;
}
} else {
if (dstsz < ((2 * len) + 1)) {
rc = EINVAL;
device_printf(sc->dev,
"destination too short (%u < %u)\n",
(uint16_t) dstsz, ((2 * len) + 1));
goto out;
}
}
for (i = 0; i < len; i++) {
rc = smbus_readb(sc->smbus, sc->spd_addr, (new_offset + i),
&byte);
if (rc != 0) {
device_printf(sc->dev,
"failed to read byte at 0x%02x: %d\n",
(new_offset + i), rc);
goto out;
}
if (ascii) {
dst[i] = byte;
} else {
(void) snprintf(&dst[(2 * i)], 3, "%02x", byte);
}
}
if (ascii) {
for (i = dstsz - 1; i > 0; i--) {
if (dst[i] == ' ') {
dst[i] = 0;
} else if (dst[i] == 0) {
continue;
} else {
break;
}
}
}
out:
if (page_changed) {
int rc2;
rc2 = jedec_dimm_reset_page0(sc);
if ((rc2 != 0) && (rc == 0)) {
rc = rc2;
}
}
return (rc);
}
static int
jedec_dimm_mfg_date(struct jedec_dimm_softc *sc, enum dram_type type,
uint8_t *year, uint8_t *week)
{
uint8_t year_bcd;
uint8_t week_bcd;
uint16_t year_offset;
uint16_t week_offset;
bool page_changed;
int rc;
switch (type) {
case DRAM_TYPE_DDR3_SDRAM:
year_offset = SPD_OFFSET_DDR3_MOD_MFG_YEAR;
week_offset = SPD_OFFSET_DDR3_MOD_MFG_WEEK;
break;
case DRAM_TYPE_DDR4_SDRAM:
year_offset = SPD_OFFSET_DDR4_MOD_MFG_YEAR;
week_offset = SPD_OFFSET_DDR4_MOD_MFG_WEEK;
break;
default:
__assert_unreachable();
}
if (year_offset < JEDEC_SPD_PAGE_SIZE) {
page_changed = false;
} else if (year_offset < (2 * JEDEC_SPD_PAGE_SIZE)) {
page_changed = true;
rc = smbus_writeb(sc->smbus,
(JEDEC_DTI_PAGE | JEDEC_LSA_PAGE_SET1), 0, 0);
if (rc != 0) {
device_printf(sc->dev,
"unable to change page for offset 0x%04x: %d\n",
year_offset, rc);
}
year_offset -= JEDEC_SPD_PAGE_SIZE;
week_offset -= JEDEC_SPD_PAGE_SIZE;
} else {
device_printf(sc->dev, "invalid offset 0x%04x\n", year_offset);
rc = EINVAL;
page_changed = false;
goto out;
}
rc = smbus_readb(sc->smbus, sc->spd_addr, year_offset, &year_bcd);
if (rc != 0) {
device_printf(sc->dev, "failed to read mfg year: %d\n", rc);
goto out;
}
rc = smbus_readb(sc->smbus, sc->spd_addr, week_offset, &week_bcd);
if (rc != 0) {
device_printf(sc->dev, "failed to read mfg week: %d\n", rc);
goto out;
}
*year = (((year_bcd & 0xf0) >> 4) * 10) + (year_bcd & 0x0f);
*week = (((week_bcd & 0xf0) >> 4) * 10) + (week_bcd & 0x0f);
out:
if (page_changed) {
int rc2;
rc2 = smbus_writeb(sc->smbus,
(JEDEC_DTI_PAGE | JEDEC_LSA_PAGE_SET0), 0, 0);
if (rc2 != 0) {
device_printf(sc->dev,
"unable to restore page for offset 0x%04x: %d\n",
year_offset, rc2);
}
}
return (rc);
}
static int
jedec_dimm_probe(device_t dev)
{
uint8_t addr;
uint8_t byte;
int rc;
enum dram_type type;
device_t smbus;
smbus = device_get_parent(dev);
addr = smbus_get_addr(dev);
if (((addr & 0xf0) != JEDEC_DTI_SPD) ||
((addr & 0x01) != 0)) {
device_printf(dev,
"invalid \"addr\" hint; address must start with \"0x%x\","
" and the least-significant bit must be 0\n",
JEDEC_DTI_SPD);
rc = ENXIO;
goto out;
}
rc = smbus_readb(smbus, addr, SPD_OFFSET_DRAM_TYPE, &byte);
if (rc != 0) {
device_printf(dev, "failed to read dram_type\n");
goto out;
}
type = (enum dram_type) byte;
switch (type) {
case DRAM_TYPE_DDR3_SDRAM:
rc = BUS_PROBE_DEFAULT;
device_set_desc(dev, "DDR3 DIMM");
break;
case DRAM_TYPE_DDR4_SDRAM:
rc = BUS_PROBE_DEFAULT;
device_set_desc(dev, "DDR4 DIMM");
break;
default:
rc = ENXIO;
break;
}
out:
return (rc);
}
static int
jedec_dimm_readw_be(struct jedec_dimm_softc *sc, uint8_t reg, uint16_t *val)
{
int rc;
rc = smbus_readw(sc->smbus, sc->tsod_addr, reg, val);
if (rc != 0) {
goto out;
}
*val = be16toh(*val);
out:
return (rc);
}
static int
jedec_dimm_reset_page0(struct jedec_dimm_softc *sc)
{
int rc;
rc = smbus_writeb(sc->smbus, (JEDEC_DTI_PAGE | JEDEC_LSA_PAGE_SET0),
0, 0);
if (rc != 0) {
device_printf(sc->dev, "unable to restore page: %d\n", rc);
}
return (rc);
}
static int
jedec_dimm_temp_sysctl(SYSCTL_HANDLER_ARGS)
{
uint16_t val;
int rc;
int temp;
device_t dev = arg1;
struct jedec_dimm_softc *sc;
sc = device_get_softc(dev);
rc = jedec_dimm_readw_be(sc, TSOD_REG_TEMPERATURE, &val);
if (rc != 0) {
goto out;
}
temp = val & 0xfff;
if ((val & 0x1000) != 0)
temp = -temp;
temp *= 625;
temp += 2731500;
temp = (temp + 500) / 1000;
rc = sysctl_handle_int(oidp, &temp, 0, req);
out:
return (rc);
}
static const char *
jedec_dimm_tsod_match(uint16_t vid, uint16_t did)
{
const struct jedec_dimm_tsod_dev *d;
int i;
for (i = 0; i < nitems(known_tsod_devices); i++) {
d = &known_tsod_devices[i];
if ((vid == d->vendor_id) && ((did >> 8) == d->device_id)) {
return (d->description);
}
}
if ((did >> 8) == 0x22) {
return ("TSE2004av compliant TSOD");
}
return (NULL);
}
static device_method_t jedec_dimm_methods[] = {
DEVMETHOD(device_probe, jedec_dimm_probe),
DEVMETHOD(device_attach, jedec_dimm_attach),
DEVMETHOD(device_detach, jedec_dimm_detach),
DEVMETHOD_END
};
static driver_t jedec_dimm_driver = {
.name = "jedec_dimm",
.methods = jedec_dimm_methods,
.size = sizeof(struct jedec_dimm_softc),
};
DRIVER_MODULE(jedec_dimm, smbus, jedec_dimm_driver, 0, 0);
MODULE_DEPEND(jedec_dimm, smbus, SMBUS_MINVER, SMBUS_PREFVER, SMBUS_MAXVER);
MODULE_VERSION(jedec_dimm, 1);