Module Name: src Committed By: pgoyette Date: Wed Mar 24 00:31:41 UTC 2010
Modified Files: src/sys/conf: files src/sys/dev/i2c: files.i2c Added Files: src/sys/dev/i2c: spdmem_i2c.c src/sys/dev/ic: spdmem.c spdmemreg.h spdmemvar.h Removed Files: src/sys/dev/i2c: spdmem.c spdmemreg.h spdmemvar.h Log Message: Split spdmem driver into a bus attachment and a common back-end probe and ROM decode, to follow similar changes made in OpenBSD: http://article.gmane.org/gmane.os.openbsd.cvs/94948 http://article.gmane.org/gmane.os.openbsd.cvs/94956 There exists at least one alternative attachment (for SGI IP35 systems; see http://article.gmane.org/gmane.os.openbsd.cvs/94947), and there could be more in the future. Thanks to Christoph Egger for pointing out the OpenBSD activity. To generate a diff of this commit: cvs rdiff -u -r1.983 -r1.984 src/sys/conf/files cvs rdiff -u -r1.26 -r1.27 src/sys/dev/i2c/files.i2c cvs rdiff -u -r1.18 -r0 src/sys/dev/i2c/spdmem.c cvs rdiff -u -r0 -r1.1 src/sys/dev/i2c/spdmem_i2c.c cvs rdiff -u -r1.3 -r0 src/sys/dev/i2c/spdmemreg.h cvs rdiff -u -r1.7 -r0 src/sys/dev/i2c/spdmemvar.h cvs rdiff -u -r0 -r1.1 src/sys/dev/ic/spdmem.c src/sys/dev/ic/spdmemreg.h \ src/sys/dev/ic/spdmemvar.h Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/conf/files diff -u src/sys/conf/files:1.983 src/sys/conf/files:1.984 --- src/sys/conf/files:1.983 Mon Mar 15 20:35:19 2010 +++ src/sys/conf/files Wed Mar 24 00:31:41 2010 @@ -1,4 +1,4 @@ -# $NetBSD: files,v 1.983 2010/03/15 20:35:19 christos Exp $ +# $NetBSD: files,v 1.984 2010/03/24 00:31:41 pgoyette Exp $ # @(#)files.newconf 7.5 (Berkeley) 5/10/93 version 20090313 @@ -328,6 +328,13 @@ device lm: sysmon_envsys file dev/ic/nslm7x.c lm needs-flag +# JEDEC standard SPD EPROM +# +# (included here so files.i2c can define an attachment) + +device spdmem +file dev/ic/spdmem.c spdmem + # I2C device support include "dev/i2c/files.i2c" Index: src/sys/dev/i2c/files.i2c diff -u src/sys/dev/i2c/files.i2c:1.26 src/sys/dev/i2c/files.i2c:1.27 --- src/sys/dev/i2c/files.i2c:1.26 Mon Feb 22 03:50:56 2010 +++ src/sys/dev/i2c/files.i2c Wed Mar 24 00:31:41 2010 @@ -1,4 +1,4 @@ -# $NetBSD: files.i2c,v 1.26 2010/02/22 03:50:56 pgoyette Exp $ +# $NetBSD: files.i2c,v 1.27 2010/03/24 00:31:41 pgoyette Exp $ defflag opt_i2cbus.h I2C_SCAN define i2cbus { } @@ -102,9 +102,8 @@ file dev/i2c/xbseeprom.c xbseeprom # Memory Serial Presence Detect -device spdmem -attach spdmem at iic -file dev/i2c/spdmem.c spdmem +attach spdmem at iic with spdmem_iic +file dev/i2c/spdmem_i2c.c spdmem_iic # Memory Temp Sensor device sdtemp: sysmon_envsys Added files: Index: src/sys/dev/i2c/spdmem_i2c.c diff -u /dev/null src/sys/dev/i2c/spdmem_i2c.c:1.1 --- /dev/null Wed Mar 24 00:31:41 2010 +++ src/sys/dev/i2c/spdmem_i2c.c Wed Mar 24 00:31:41 2010 @@ -0,0 +1,122 @@ +/* $NetBSD: spdmem_i2c.c,v 1.1 2010/03/24 00:31:41 pgoyette Exp $ */ + +/* + * Copyright (c) 2007 Nicolas Joly + * Copyright (c) 2007 Paul Goyette + * Copyright (c) 2007 Tobias Nygren + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Serial Presence Detect (SPD) memory identification + */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: spdmem_i2c.c,v 1.1 2010/03/24 00:31:41 pgoyette Exp $"); + +#include <sys/param.h> +#include <sys/device.h> +#include <sys/endian.h> +#include <sys/sysctl.h> +#include <machine/bswap.h> + +#include <dev/i2c/i2cvar.h> +#include <dev/ic/spdmemreg.h> +#include <dev/ic/spdmemvar.h> + +/* Constants for matching i2c bus address */ +#define SPDMEM_I2C_ADDRMASK 0x78 +#define SPDMEM_I2C_ADDR 0x50 + +struct spdmem_i2c_softc { + struct spdmem_softc sc_base; + i2c_tag_t sc_tag; + i2c_addr_t sc_addr; +}; + +static int spdmem_i2c_match(device_t, cfdata_t, void *); +static void spdmem_i2c_attach(device_t, device_t, void *); +SYSCTL_SETUP_PROTO(sysctl_spdmem_setup); + +static uint8_t spdmem_i2c_read(struct spdmem_softc *, uint8_t); + +CFATTACH_DECL_NEW(spdmem_iic, sizeof(struct spdmem_i2c_softc), + spdmem_i2c_match, spdmem_i2c_attach, NULL, NULL); + +static int +spdmem_i2c_match(device_t parent, cfdata_t match, void *aux) +{ + struct i2c_attach_args *ia = aux; + struct spdmem_i2c_softc sc; + + if (ia->ia_name) { + /* add other names as we find more firmware variations */ + if (strcmp(ia->ia_name, "dimm-spd")) + return 0; + } + + /* only do this lame test when not using direct config */ + if (ia->ia_name == NULL) { + if ((ia->ia_addr & SPDMEM_I2C_ADDRMASK) != SPDMEM_I2C_ADDR) + return 0; + } + + sc.sc_tag = ia->ia_tag; + sc.sc_addr = ia->ia_addr; + sc.sc_base.sc_read = spdmem_i2c_read; + + return spdmem_common_probe(&sc.sc_base); +} + +static void +spdmem_i2c_attach(device_t parent, device_t self, void *aux) +{ + struct spdmem_i2c_softc *sc = device_private(self); + struct i2c_attach_args *ia = aux; + + sc->sc_tag = ia->ia_tag; + sc->sc_addr = ia->ia_addr; + sc->sc_base.sc_read = spdmem_i2c_read; + + if (!pmf_device_register(self, NULL, NULL)) + aprint_error_dev(self, "couldn't establish power handler\n"); + + spdmem_common_attach(&sc->sc_base, self); +} + +static uint8_t +spdmem_i2c_read(struct spdmem_softc *softc, uint8_t reg) +{ + uint8_t val; + struct spdmem_i2c_softc *sc = (struct spdmem_i2c_softc *)softc; + + iic_acquire_bus(sc->sc_tag, 0); + iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, ®, 1, + &val, 1, 0); + iic_release_bus(sc->sc_tag, 0); + + return val; +} Index: src/sys/dev/ic/spdmem.c diff -u /dev/null src/sys/dev/ic/spdmem.c:1.1 --- /dev/null Wed Mar 24 00:31:41 2010 +++ src/sys/dev/ic/spdmem.c Wed Mar 24 00:31:41 2010 @@ -0,0 +1,723 @@ +/* $NetBSD: spdmem.c,v 1.1 2010/03/24 00:31:41 pgoyette Exp $ */ + +/* + * Copyright (c) 2007 Nicolas Joly + * Copyright (c) 2007 Paul Goyette + * Copyright (c) 2007 Tobias Nygren + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Serial Presence Detect (SPD) memory identification + */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: spdmem.c,v 1.1 2010/03/24 00:31:41 pgoyette Exp $"); + +#include <sys/param.h> +#include <sys/device.h> +#include <sys/endian.h> +#include <sys/sysctl.h> +#include <machine/bswap.h> + +#include <dev/i2c/i2cvar.h> +#include <dev/ic/spdmemreg.h> +#include <dev/ic/spdmemvar.h> + +SYSCTL_SETUP_PROTO(sysctl_spdmem_setup); + +/* Routines for decoding spd data */ +static void decode_edofpm(const struct sysctlnode *, device_t, struct spdmem *); +static void decode_rom(const struct sysctlnode *, device_t, struct spdmem *); +static void decode_sdram(const struct sysctlnode *, device_t, struct spdmem *, + int); +static void decode_ddr(const struct sysctlnode *, device_t, struct spdmem *); +static void decode_ddr2(const struct sysctlnode *, device_t, struct spdmem *); +static void decode_ddr3(const struct sysctlnode *, device_t, struct spdmem *); +static void decode_fbdimm(const struct sysctlnode *, device_t, struct spdmem *); + +static void decode_size_speed(const struct sysctlnode *, int, int, int, int, + bool, const char *, int); +static void decode_voltage_refresh(device_t, struct spdmem *); + +#define IS_RAMBUS_TYPE (s->sm_len < 4) + +static const char* spdmem_basic_types[] = { + "unknown", + "FPM", + "EDO", + "Pipelined Nibble", + "SDRAM", + "ROM", + "DDR SGRAM", + "DDR SDRAM", + "DDR2 SDRAM", + "DDR2 SDRAM FB", + "DDR2 SDRAM FB Probe", + "DDR3 SDRAM" +}; + +static const char* spdmem_superset_types[] = { + "unknown", + "ESDRAM", + "DDR ESDRAM", + "PEM EDO", + "PEM SDRAM" +}; + +static const char* spdmem_voltage_types[] = { + "TTL (5V tolerant)", + "LvTTL (not 5V tolerant)", + "HSTL 1.5V", + "SSTL 3.3V", + "SSTL 2.5V", + "SSTL 1.8V" +}; + +static const char* spdmem_refresh_types[] = { + "15.625us", + "3.9us", + "7.8us", + "31.3us", + "62.5us", + "125us" +}; + +static const char* spdmem_parity_types[] = { + "no parity or ECC", + "data parity", + "data ECC", + "data parity and ECC", + "cmd/addr parity", + "cmd/addr/data parity", + "cmd/addr parity, data ECC", + "cmd/addr/data parity, data ECC" +}; + +/* Cycle time fractional values (units of .001 ns) for DDR2 SDRAM */ +static const uint16_t spdmem_cycle_frac[] = { + 0, 100, 200, 300, 400, 500, 600, 700, 800, 900, + 250, 333, 667, 750, 999, 999 +}; + +/* Format string for timing info */ +static const char* latency="tAA-tRCD-tRP-tRAS: %d-%d-%d-%d\n"; + +/* sysctl stuff */ +static int hw_node = CTL_EOL; + +/* CRC functions used for certain memory types */ + +static uint16_t spdcrc16 (struct spdmem_softc *sc, int count) +{ + uint16_t crc; + int i, j; + uint8_t val; + crc = 0; + for (j = 0; j <= count; j++) { + val = (sc->sc_read)(sc, j); + crc = crc ^ val << 8; + for (i = 0; i < 8; ++i) + if (crc & 0x8000) + crc = crc << 1 ^ 0x1021; + else + crc = crc << 1; + } + return (crc & 0xFFFF); +} + +int +spdmem_common_probe(struct spdmem_softc *sc) +{ + int cksum = 0; + uint8_t i, val, spd_type; + int spd_len, spd_crc_cover; + uint16_t crc_calc, crc_spd; + + spd_type = (sc->sc_read)(sc, 2); + + /* For older memory types, validate the checksum over 1st 63 bytes */ + if (spd_type <= SPDMEM_MEMTYPE_DDR2SDRAM) { + for (i = 0; i < 63; i++) + cksum += (sc->sc_read)(sc, i); + + val = (sc->sc_read)(sc, 63); + + if (cksum == 0 || (cksum & 0xff) != val) { + aprint_debug("spd checksum failed, calc = 0x%02x, " + "spd = 0x%02x\n", cksum, val); + return 0; + } else + return 1; + } + + /* For DDR3 and FBDIMM, verify the CRC */ + else if (spd_type <= SPDMEM_MEMTYPE_DDR3SDRAM) { + spd_len = (sc->sc_read)(sc, 0); + if (spd_len && SPDMEM_SPDCRC_116) + spd_crc_cover = 116; + else + spd_crc_cover = 125; + switch (spd_len & SPDMEM_SPDLEN_MASK) { + case SPDMEM_SPDLEN_128: + spd_len = 128; + break; + case SPDMEM_SPDLEN_176: + spd_len = 176; + break; + case SPDMEM_SPDLEN_256: + spd_len = 256; + break; + default: + return 0; + } + if (spd_crc_cover > spd_len) + return 0; + crc_calc = spdcrc16(sc, spd_crc_cover); + crc_spd = (sc->sc_read)(sc, 127) << 8; + crc_spd |= (sc->sc_read)(sc, 126); + if (crc_calc != crc_spd) { + aprint_debug("crc16 failed, covers %d bytes, " + "calc = 0x%04x, spd = 0x%04x\n", + spd_crc_cover, crc_calc, crc_spd); + return 0; + } + return 1; + } + + /* For unrecognized memory types, don't match at all */ + return 0; +} + +void +spdmem_common_attach(struct spdmem_softc *sc, device_t self) +{ + struct spdmem *s = &(sc->sc_spd_data); + const char *type; + const char *rambus_rev = "Reserved"; + int dimm_size; + int i; + unsigned int spd_len, spd_size; + const struct sysctlnode *node = NULL; + + /* + * FBDIMM and DDR3 (and probably all newer) have a different + * encoding of the SPD EEPROM used/total sizes + */ + s->sm_len = (sc->sc_read)(sc, 0); + s->sm_size = (sc->sc_read)(sc, 1); + s->sm_type = (sc->sc_read)(sc, 2); + + if (s->sm_type >= SPDMEM_MEMTYPE_FBDIMM) { + spd_size = 64 << (s->sm_len & SPDMEM_SPDSIZE_MASK); + switch (s->sm_len & SPDMEM_SPDLEN_MASK) { + case SPDMEM_SPDLEN_128: + spd_len = 128; + break; + case SPDMEM_SPDLEN_176: + spd_len = 176; + break; + case SPDMEM_SPDLEN_256: + spd_len = 256; + break; + default: + spd_len = 64; + break; + } + } else { + spd_size = 1 << s->sm_size; + spd_len = s->sm_len; + if (spd_len < 64) + spd_len = 64; + } + if (spd_len > spd_size) + spd_len = spd_size; + if (spd_len > sizeof(struct spdmem)) + spd_len = sizeof(struct spdmem); + for (i = 3; i < spd_len; i++) + ((uint8_t *)s)[i] = (sc->sc_read)(sc, i); + +#ifdef DEBUG + for (i = 0; i < spd_len; i += 16) { + int j, k; + aprint_debug("\n"); + aprint_debug_dev(self, "0x%02x:", i); + k = (spd_len > i + 16) ? spd_len : i + 16; + for (j = i; j < k; j++) + aprint_debug(" %02x", ((uint8_t *)s)[j]); + } + aprint_debug("\n"); + aprint_debug_dev(self, ""); +#endif + + /* + * Setup our sysctl subtree, hw.spdmemN + */ + if (hw_node != CTL_EOL) + sysctl_createv(NULL, 0, NULL, &node, + 0, CTLTYPE_NODE, + device_xname(self), NULL, NULL, 0, NULL, 0, + CTL_HW, CTL_CREATE, CTL_EOL); + if (node != NULL && spd_len != 0) + sysctl_createv(NULL, 0, NULL, NULL, + 0, + CTLTYPE_STRUCT, "spd_data", + SYSCTL_DESCR("raw spd data"), NULL, + 0, s, spd_len, + CTL_HW, node->sysctl_num, CTL_CREATE, CTL_EOL); + + /* + * Decode and print key SPD contents + */ + if (IS_RAMBUS_TYPE) { + if (s->sm_type == SPDMEM_MEMTYPE_RAMBUS) + type = "Rambus"; + else if (s->sm_type == SPDMEM_MEMTYPE_DIRECTRAMBUS) + type = "Direct Rambus"; + else + type = "Rambus (unknown)"; + + switch (s->sm_len) { + case 0: + rambus_rev = "Invalid"; + break; + case 1: + rambus_rev = "0.7"; + break; + case 2: + rambus_rev = "1.0"; + break; + default: + rambus_rev = "Reserved"; + break; + } + } else { + if (s->sm_type < __arraycount(spdmem_basic_types)) + type = spdmem_basic_types[s->sm_type]; + else + type = "unknown memory type"; + + if (s->sm_type == SPDMEM_MEMTYPE_EDO && + s->sm_fpm.fpm_superset == SPDMEM_SUPERSET_EDO_PEM) + type = spdmem_superset_types[SPDMEM_SUPERSET_EDO_PEM]; + if (s->sm_type == SPDMEM_MEMTYPE_SDRAM && + s->sm_sdr.sdr_superset == SPDMEM_SUPERSET_SDRAM_PEM) + type = spdmem_superset_types[SPDMEM_SUPERSET_SDRAM_PEM]; + if (s->sm_type == SPDMEM_MEMTYPE_DDRSDRAM && + s->sm_ddr.ddr_superset == SPDMEM_SUPERSET_DDR_ESDRAM) + type = + spdmem_superset_types[SPDMEM_SUPERSET_DDR_ESDRAM]; + if (s->sm_type == SPDMEM_MEMTYPE_SDRAM && + s->sm_sdr.sdr_superset == SPDMEM_SUPERSET_ESDRAM) { + type = spdmem_superset_types[SPDMEM_SUPERSET_ESDRAM]; + } + } + + aprint_naive("\n"); + aprint_normal("\n"); + aprint_normal_dev(self, "%s", type); + strlcpy(sc->sc_type, type, SPDMEM_TYPE_MAXLEN); + if (node != NULL) + sysctl_createv(NULL, 0, NULL, NULL, + 0, + CTLTYPE_STRING, "mem_type", + SYSCTL_DESCR("memory module type"), NULL, + 0, sc->sc_type, 0, + CTL_HW, node->sysctl_num, CTL_CREATE, CTL_EOL); + + if (IS_RAMBUS_TYPE) { + aprint_normal(", SPD Revision %s", rambus_rev); + dimm_size = 1 << (s->sm_rdr.rdr_rows + s->sm_rdr.rdr_cols - 13); + if (dimm_size >= 1024) + aprint_normal(", %dGB\n", dimm_size / 1024); + else + aprint_normal(", %dMB\n", dimm_size); + + /* No further decode for RAMBUS memory */ + return; + } + switch (s->sm_type) { + case SPDMEM_MEMTYPE_EDO: + case SPDMEM_MEMTYPE_FPM: + decode_edofpm(node, self, s); + break; + case SPDMEM_MEMTYPE_ROM: + decode_rom(node, self, s); + break; + case SPDMEM_MEMTYPE_SDRAM: + decode_sdram(node, self, s, spd_len); + break; + case SPDMEM_MEMTYPE_DDRSDRAM: + decode_ddr(node, self, s); + break; + case SPDMEM_MEMTYPE_DDR2SDRAM: + decode_ddr2(node, self, s); + break; + case SPDMEM_MEMTYPE_DDR3SDRAM: + decode_ddr3(node, self, s); + break; + case SPDMEM_MEMTYPE_FBDIMM: + case SPDMEM_MEMTYPE_FBDIMM_PROBE: + decode_fbdimm(node, self, s); + break; + } +} + +SYSCTL_SETUP(sysctl_spdmem_setup, "sysctl hw.spdmem subtree setup") +{ + const struct sysctlnode *node; + + if (sysctl_createv(clog, 0, NULL, &node, + CTLFLAG_PERMANENT, + CTLTYPE_NODE, "hw", NULL, + NULL, 0, NULL, 0, + CTL_HW, CTL_EOL) != 0) + return; + + hw_node = node->sysctl_num; +} + +static void +decode_size_speed(const struct sysctlnode *node, int dimm_size, int cycle_time, + int d_clk, int bits, bool round, const char *ddr_type_string, + int speed) +{ + int p_clk; + + if (dimm_size < 1024) + aprint_normal("%dMB", dimm_size); + else + aprint_normal("%dGB", dimm_size / 1024); + if (node != NULL) + sysctl_createv(NULL, 0, NULL, NULL, + CTLFLAG_IMMEDIATE, + CTLTYPE_INT, "size", + SYSCTL_DESCR("module size in MB"), NULL, + dimm_size, NULL, 0, + CTL_HW, node->sysctl_num, CTL_CREATE, CTL_EOL); + + if (cycle_time == 0) { + aprint_normal("\n"); + return; + } + + /* + * Calculate p_clk first, since for DDR3 we need maximum significance. + * DDR3 rating is not rounded to a multiple of 100. This results in + * cycle_time of 1.5ns displayed as PC3-10666. + * + * For SDRAM, the speed is provided by the caller so we use it. + */ + d_clk *= 1000 * 1000; + if (speed) + p_clk = speed; + else + p_clk = (d_clk * bits) / 8 / cycle_time; + d_clk = ((d_clk + cycle_time / 2) ) / cycle_time; + if (round) { + if ((p_clk % 100) >= 50) + p_clk += 50; + p_clk -= p_clk % 100; + } + aprint_normal(", %dMHz (%s-%d)\n", + d_clk, ddr_type_string, p_clk); + if (node != NULL) + sysctl_createv(NULL, 0, NULL, NULL, + CTLFLAG_IMMEDIATE, + CTLTYPE_INT, "speed", + SYSCTL_DESCR("memory speed in MHz"), + NULL, d_clk, NULL, 0, + CTL_HW, node->sysctl_num, CTL_CREATE, CTL_EOL); +} + +static void +decode_voltage_refresh(device_t self, struct spdmem *s) +{ + const char *voltage, *refresh; + + if (s->sm_voltage < __arraycount(spdmem_voltage_types)) + voltage = spdmem_voltage_types[s->sm_voltage]; + else + voltage = "unknown"; + + if (s->sm_refresh < __arraycount(spdmem_refresh_types)) + refresh = spdmem_refresh_types[s->sm_refresh]; + else + refresh = "unknown"; + + aprint_verbose_dev(self, "voltage %s, refresh time %s%s\n", + voltage, refresh, + s->sm_selfrefresh?" (self-refreshing)":""); +} + +static void +decode_edofpm(const struct sysctlnode *node, device_t self, struct spdmem *s) { + aprint_normal("\n"); + aprint_verbose_dev(self, + "%d rows, %d cols, %d banks, %dns tRAC, %dns tCAC\n", + s->sm_fpm.fpm_rows, s->sm_fpm.fpm_cols, s->sm_fpm.fpm_banks, + s->sm_fpm.fpm_tRAC, s->sm_fpm.fpm_tCAC); +} + +static void +decode_rom(const struct sysctlnode *node, device_t self, struct spdmem *s) { + aprint_normal("\n"); + aprint_verbose_dev(self, "%d rows, %d cols, %d banks\n", + s->sm_rom.rom_rows, s->sm_rom.rom_cols, s->sm_rom.rom_banks); +} + +static void +decode_sdram(const struct sysctlnode *node, device_t self, struct spdmem *s, + int spd_len) { + int dimm_size, cycle_time, bits, tAA, i, speed, freq; + + aprint_normal("%s, %s, ", + (s->sm_sdr.sdr_mod_attrs & SPDMEM_SDR_MASK_REG)? + " (registered)":"", + (s->sm_config < __arraycount(spdmem_parity_types))? + spdmem_parity_types[s->sm_config]:"invalid parity"); + + dimm_size = 1 << (s->sm_sdr.sdr_rows + s->sm_sdr.sdr_cols - 17); + dimm_size *= s->sm_sdr.sdr_banks * s->sm_sdr.sdr_banks_per_chip; + + cycle_time = s->sm_sdr.sdr_cycle_whole * 1000 + + s->sm_sdr.sdr_cycle_tenths * 100; + bits = le16toh(s->sm_sdr.sdr_datawidth); + if (s->sm_config == 1 || s->sm_config == 2) + bits -= 8; + + /* Calculate speed here - from OpenBSD */ + if (spd_len >= 128) + freq = ((uint8_t *)s)[126]; + else + freq = 0; + switch (freq) { + /* + * Must check cycle time since some PC-133 DIMMs + * actually report PC-100 + */ + case 100: + case 133: + if (cycle_time < 8000) + speed = 133; + else + speed = 100; + break; + case 0x66: /* Legacy DIMMs use _hex_ 66! */ + default: + speed = 66; + } + decode_size_speed(node, dimm_size, cycle_time, 1, bits, FALSE, "PC", + speed); + + aprint_verbose_dev(self, + "%d rows, %d cols, %d banks, %d banks/chip, %d.%dns cycle time\n", + s->sm_sdr.sdr_rows, s->sm_sdr.sdr_cols, s->sm_sdr.sdr_banks, + s->sm_sdr.sdr_banks_per_chip, cycle_time/1000, + (cycle_time % 1000) / 100); + + tAA = 0; + for (i = 0; i < 8; i++) + if (s->sm_sdr.sdr_tCAS & (1 << i)) + tAA = i; + tAA++; + aprint_verbose_dev(self, latency, tAA, s->sm_sdr.sdr_tRCD, + s->sm_sdr.sdr_tRP, s->sm_sdr.sdr_tRAS); + + decode_voltage_refresh(self, s); +} + +static void +decode_ddr(const struct sysctlnode *node, device_t self, struct spdmem *s) { + int dimm_size, cycle_time, bits, tAA, i; + + aprint_normal("%s, %s, ", + (s->sm_ddr.ddr_mod_attrs & SPDMEM_DDR_MASK_REG)? + " (registered)":"", + (s->sm_config < __arraycount(spdmem_parity_types))? + spdmem_parity_types[s->sm_config]:"invalid parity"); + + dimm_size = 1 << (s->sm_ddr.ddr_rows + s->sm_ddr.ddr_cols - 17); + dimm_size *= s->sm_ddr.ddr_ranks * s->sm_ddr.ddr_banks_per_chip; + + cycle_time = s->sm_ddr.ddr_cycle_whole * 1000 + + spdmem_cycle_frac[s->sm_ddr.ddr_cycle_tenths]; + bits = le16toh(s->sm_ddr.ddr_datawidth); + if (s->sm_config == 1 || s->sm_config == 2) + bits -= 8; + decode_size_speed(node, dimm_size, cycle_time, 2, bits, TRUE, "PC", 0); + + aprint_verbose_dev(self, + "%d rows, %d cols, %d ranks, %d banks/chip, %d.%dns cycle time\n", + s->sm_ddr.ddr_rows, s->sm_ddr.ddr_cols, s->sm_ddr.ddr_ranks, + s->sm_ddr.ddr_banks_per_chip, cycle_time/1000, + (cycle_time % 1000 + 50) / 100); + + tAA = 0; + for (i = 2; i < 8; i++) + if (s->sm_ddr.ddr_tCAS & (1 << i)) + tAA = i; + tAA /= 2; + +#define __DDR_ROUND(scale, field) \ + ((scale * s->sm_ddr.field + cycle_time - 1) / cycle_time) + + aprint_verbose_dev(self, latency, tAA, __DDR_ROUND(250, ddr_tRCD), + __DDR_ROUND(250, ddr_tRP), __DDR_ROUND(1000, ddr_tRAS)); + +#undef __DDR_ROUND + + decode_voltage_refresh(self, s); +} + +static void +decode_ddr2(const struct sysctlnode *node, device_t self, struct spdmem *s) { + int dimm_size, cycle_time, bits, tAA, i; + + aprint_normal("%s, %s, ", + (s->sm_ddr2.ddr2_mod_attrs & SPDMEM_DDR2_MASK_REG)? + " (registered)":"", + (s->sm_config < __arraycount(spdmem_parity_types))? + spdmem_parity_types[s->sm_config]:"invalid parity"); + + dimm_size = 1 << (s->sm_ddr2.ddr2_rows + s->sm_ddr2.ddr2_cols - 17); + dimm_size *= (s->sm_ddr2.ddr2_ranks + 1) * + s->sm_ddr2.ddr2_banks_per_chip; + + cycle_time = s->sm_ddr2.ddr2_cycle_whole * 1000 + + spdmem_cycle_frac[s->sm_ddr2.ddr2_cycle_frac]; + bits = s->sm_ddr2.ddr2_datawidth; + if ((s->sm_config & 0x03) != 0) + bits -= 8; + decode_size_speed(node, dimm_size, cycle_time, 2, bits, TRUE, "PC2", 0); + + aprint_verbose_dev(self, + "%d rows, %d cols, %d ranks, %d banks/chip, %d.%02dns cycle time\n", + s->sm_ddr2.ddr2_rows, s->sm_ddr2.ddr2_cols, + s->sm_ddr2.ddr2_ranks + 1, s->sm_ddr2.ddr2_banks_per_chip, + cycle_time / 1000, (cycle_time % 1000 + 5) /10 ); + + tAA = 0; + for (i = 2; i < 8; i++) + if (s->sm_ddr2.ddr2_tCAS & (1 << i)) + tAA = i; + +#define __DDR2_ROUND(scale, field) \ + ((scale * s->sm_ddr2.field + cycle_time - 1) / cycle_time) + + aprint_verbose_dev(self, latency, tAA, __DDR2_ROUND(250, ddr2_tRCD), + __DDR2_ROUND(250, ddr2_tRP), __DDR2_ROUND(1000, ddr2_tRAS)); + +#undef __DDR_ROUND + + decode_voltage_refresh(self, s); +} + +static void +decode_ddr3(const struct sysctlnode *node, device_t self, struct spdmem *s) { + int dimm_size, cycle_time, bits; + + if (s->sm_ddr3.ddr3_mod_type == + SPDMEM_DDR3_TYPE_MINI_RDIMM || + s->sm_ddr3.ddr3_mod_type == SPDMEM_DDR3_TYPE_RDIMM) + aprint_normal(" (registered)"); + aprint_normal(", %sECC, %stemp-sensor, ", + (s->sm_ddr3.ddr3_hasECC)?"":"no ", + (s->sm_ddr3.ddr3_has_therm_sensor)?"":"no "); + + /* + * DDR3 size specification is quite different from others + * + * Module capacity is defined as + * Chip_Capacity_in_bits / 8bits-per-byte * + * external_bus_width / internal_bus_width + * We further divide by 2**20 to get our answer in MB + */ + dimm_size = (s->sm_ddr3.ddr3_chipsize + 28 - 20) - 3 + + (s->sm_ddr3.ddr3_datawidth + 3) - + (s->sm_ddr3.ddr3_chipwidth + 2); + dimm_size = (1 << dimm_size) * (s->sm_ddr3.ddr3_physbanks + 1); + + cycle_time = (1000 * s->sm_ddr3.ddr3_mtb_dividend + + (s->sm_ddr3.ddr3_mtb_divisor / 2)) / + s->sm_ddr3.ddr3_mtb_divisor; + cycle_time *= s->sm_ddr3.ddr3_tCKmin; + bits = 1 << (s->sm_ddr3.ddr3_datawidth + 3); + decode_size_speed(node, dimm_size, cycle_time, 2, bits, FALSE, "PC3", 0); + + aprint_verbose_dev(self, + "%d rows, %d cols, %d log. banks, %d phys. banks, " + "%d.%03dns cycle time\n", + s->sm_ddr3.ddr3_rows + 9, s->sm_ddr3.ddr3_cols + 12, + 1 << (s->sm_ddr3.ddr3_logbanks + 3), + s->sm_ddr3.ddr3_physbanks + 1, + cycle_time/1000, cycle_time % 1000); + +#define __DDR3_CYCLES(field) (s->sm_ddr3.field / s->sm_ddr3.ddr3_tCKmin) + + aprint_verbose_dev(self, latency, __DDR3_CYCLES(ddr3_tAAmin), + __DDR3_CYCLES(ddr3_tRCDmin), __DDR3_CYCLES(ddr3_tRPmin), + (s->sm_ddr3.ddr3_tRAS_msb * 256 + s->sm_ddr3.ddr3_tRAS_lsb) / + s->sm_ddr3.ddr3_tCKmin); + +#undef __DDR3_CYCLES +} + +static void +decode_fbdimm(const struct sysctlnode *node, device_t self, struct spdmem *s) { + int dimm_size, cycle_time, bits; + + /* + * FB-DIMM module size calculation is very much like DDR3 + */ + dimm_size = s->sm_fbd.fbdimm_rows + 12 + + s->sm_fbd.fbdimm_cols + 9 - 20 - 3; + dimm_size = (1 << dimm_size) * (1 << (s->sm_fbd.fbdimm_banks + 2)); + + cycle_time = (1000 * s->sm_fbd.fbdimm_mtb_dividend + + (s->sm_fbd.fbdimm_mtb_divisor / 2)) / + s->sm_fbd.fbdimm_mtb_divisor; + bits = 1 << (s->sm_fbd.fbdimm_dev_width + 2); + decode_size_speed(node, dimm_size, cycle_time, 2, bits, TRUE, "PC2", 0); + + aprint_verbose_dev(self, + "%d rows, %d cols, %d banks, %d.%02dns cycle time\n", + s->sm_fbd.fbdimm_rows, s->sm_fbd.fbdimm_cols, + 1 << (s->sm_fbd.fbdimm_banks + 2), + cycle_time / 1000, (cycle_time % 1000 + 5) /10 ); + +#define __FBDIMM_CYCLES(field) (s->sm_fbd.field / s->sm_fbd.fbdimm_tCKmin) + + aprint_verbose_dev(self, latency, __FBDIMM_CYCLES(fbdimm_tAAmin), + __FBDIMM_CYCLES(fbdimm_tRCDmin), __FBDIMM_CYCLES(fbdimm_tRPmin), + (s->sm_fbd.fbdimm_tRAS_msb * 256 + + s->sm_fbd.fbdimm_tRAS_lsb) / + s->sm_fbd.fbdimm_tCKmin); + +#undef __FBDIMM_CYCLES + + decode_voltage_refresh(self, s); +} Index: src/sys/dev/ic/spdmemreg.h diff -u /dev/null src/sys/dev/ic/spdmemreg.h:1.1 --- /dev/null Wed Mar 24 00:31:41 2010 +++ src/sys/dev/ic/spdmemreg.h Wed Mar 24 00:31:41 2010 @@ -0,0 +1,101 @@ +/* $NetBSD: spdmemreg.h,v 1.1 2010/03/24 00:31:41 pgoyette Exp $ */ + +/* + * Copyright (c) 2007 Paul Goyette + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* possible values for the memory type */ +#define SPDMEM_MEMTYPE_FPM 0x01 +#define SPDMEM_MEMTYPE_EDO 0x02 +#define SPDMEM_MEMTYPE_PIPE_NIBBLE 0x03 +#define SPDMEM_MEMTYPE_SDRAM 0x04 +#define SPDMEM_MEMTYPE_ROM 0x05 +#define SPDMEM_MEMTYPE_DDRSGRAM 0x06 +#define SPDMEM_MEMTYPE_DDRSDRAM 0x07 +#define SPDMEM_MEMTYPE_DDR2SDRAM 0x08 +#define SPDMEM_MEMTYPE_FBDIMM 0x09 +#define SPDMEM_MEMTYPE_FBDIMM_PROBE 0x0A +#define SPDMEM_MEMTYPE_DDR3SDRAM 0x0B + +#define SPDMEM_MEMTYPE_RAMBUS 0x11 +#define SPDMEM_MEMTYPE_DIRECTRAMBUS 0x01 + +/* Encodings of the size used/total byte for certain memory types */ +#define SPDMEM_SPDSIZE_MASK 0x0F /* SPD EEPROM Size */ + +#define SPDMEM_SPDLEN_128 0x00 /* SPD EEPROM Sizes */ +#define SPDMEM_SPDLEN_176 0x10 +#define SPDMEM_SPDLEN_256 0x20 +#define SPDMEM_SPDLEN_MASK 0x70 /* Bits 4 - 6 */ + +#define SPDMEM_SPDCRC_116 0x80 /* CRC Bytes covered */ +#define SPDMEM_SPDCRC_125 0x00 +#define SPDMEM_SPDCRC_MASK 0x80 /* Bit 7 */ + +/* possible values for the supply voltage */ +#define SPDMEM_VOLTAGE_TTL_5V 0x00 +#define SPDMEM_VOLTAGE_TTL_LV 0x01 +#define SPDMEM_VOLTAGE_HSTTL_1_5V 0x02 +#define SPDMEM_VOLTAGE_SSTL_3_3V 0x03 +#define SPDMEM_VOLTAGE_SSTL_2_5V 0x04 +#define SPDMEM_VOLTAGE_SSTL_1_8V 0x05 + +/* possible values for module configuration */ +#define SPDMEM_MODCONFIG_PARITY 0x01 +#define SPDMEM_MODCONFIG_ECC 0x02 + +/* for DDR2, module configuration is a bit-mask field */ +#define SPDMEM_MODCONFIG_HAS_DATA_PARITY 0x01 +#define SPDMEM_MODCONFIG_HAS_DATA_ECC 0x02 +#define SPDMEM_MODCONFIG_HAS_ADDR_CMD_PARITY 0x04 + +/* possible values for the refresh field */ +#define SPDMEM_REFRESH_STD 0x00 +#define SPDMEM_REFRESH_QUARTER 0x01 +#define SPDMEM_REFRESH_HALF 0x02 +#define SPDMEM_REFRESH_TWOX 0x03 +#define SPDMEM_REFRESH_FOURX 0x04 +#define SPDMEM_REFRESH_EIGHTX 0x05 +#define SPDMEM_REFRESH_SELFREFRESH 0x80 + +/* superset types */ +#define SPDMEM_SUPERSET_ESDRAM 0x01 +#define SPDMEM_SUPERSET_DDR_ESDRAM 0x02 +#define SPDMEM_SUPERSET_EDO_PEM 0x03 +#define SPDMEM_SUPERSET_SDRAM_PEM 0x04 + +/* bit masks for "registered" module attribute */ +#define SPDMEM_SDR_MASK_REG 0x02 +#define SPDMEM_DDR_MASK_REG 0x02 +#define SPDMEM_DDR2_MASK_REG 0x05 + +#define SPDMEM_DDR3_TYPE_RDIMM 0x01 +#define SPDMEM_DDR3_TYPE_UDIMM 0x02 +#define SPDMEM_DDR3_TYPE_SODIMM 0x03 +#define SPDMEM_DDR3_TYPE_MICRODIMM 0x04 +#define SPDMEM_DDR3_TYPE_MINI_RDIMM 0x05 +#define SPDMEM_DDR3_TYPE_MINI_UDIMM 0x06 Index: src/sys/dev/ic/spdmemvar.h diff -u /dev/null src/sys/dev/ic/spdmemvar.h:1.1 --- /dev/null Wed Mar 24 00:31:41 2010 +++ src/sys/dev/ic/spdmemvar.h Wed Mar 24 00:31:41 2010 @@ -0,0 +1,525 @@ +/* $NetBSD: spdmemvar.h,v 1.1 2010/03/24 00:31:41 pgoyette Exp $ */ + +/* + * Copyright (c) 2007 Paul Goyette + * Copyright (c) 2007 Tobias Nygren + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This information is extracted from JEDEC standard SPD4_01 (www.jedec.org) + */ + +#if BYTE_ORDER == BIG_ENDIAN +#define SPD_BITFIELD(a, b, c, d) d; c; b; a +#else +#define SPD_BITFIELD(a, b, c, d) a; b; c; d +#endif + +struct spdmem_fpm { /* FPM and EDO DIMMS */ + uint8_t fpm_rows; + uint8_t fpm_cols; + uint8_t fpm_banks; + uint16_t fpm_datawidth; /* endian-sensitive */ + uint8_t fpm_voltage; + uint8_t fpm_tRAC; + uint8_t fpm_tCAC; + uint8_t fpm_config; + SPD_BITFIELD( \ + uint8_t fpm_refresh:7, \ + uint8_t fpm_selfrefresh:1, , \ + ); + uint8_t fpm_dram_dramwidth; + uint8_t fpm_dram_eccwidth; + uint8_t fpm_unused2[17]; + uint8_t fpm_superset; + uint8_t fpm_unused3[30]; + uint8_t fpm_cksum; +} __packed; + +struct spdmem_sdram { /* PC66/PC100/PC133 SDRAM */ + SPD_BITFIELD( \ + uint8_t sdr_rows:4, \ + uint8_t sdr_rows2:4, , \ + ); + SPD_BITFIELD( \ + uint8_t sdr_cols:4, \ + uint8_t sdr_cols2:4, , \ + ); + uint8_t sdr_banks; + uint16_t sdr_datawidth; /* endian-sensitive */ + uint8_t sdr_voltage; + SPD_BITFIELD( \ + uint8_t sdr_cycle_tenths:4, \ + uint8_t sdr_cycle_whole:4, , \ + ); + SPD_BITFIELD( + uint8_t sdr_tAC_tenths:4, \ + uint8_t sdr_tAC_whole:4, , \ + ); + uint8_t sdr_config; + SPD_BITFIELD( \ + uint8_t sdr_refresh:7, \ + uint8_t sdr_selfrefresh:1, , \ + ); + SPD_BITFIELD( \ + uint8_t sdr_dramwidth:7, \ + uint8_t sdr_dram_asym_bank2:1, ,\ + ); + SPD_BITFIELD( \ + uint8_t sdr_eccwidth:7, \ + uint8_t sdr_ecc_asym_bank2:1, , \ + ); + uint8_t sdr_min_clk_delay; + SPD_BITFIELD( \ + uint8_t sdr_burstlengths:4, \ + uint8_t sdr_unused1:4, , \ + ); + uint8_t sdr_banks_per_chip; + uint8_t sdr_tCAS; + uint8_t sdr_tCS; + uint8_t sdr_tWE; + uint8_t sdr_mod_attrs; + uint8_t sdr_dev_attrs; + uint8_t sdr_min_cc_1; + uint8_t sdr_max_tAC_1; + uint8_t sdr_min_cc_2; + uint8_t sdr_max_tAC_2; + uint8_t sdr_tRP; + uint8_t sdr_tRRD; + uint8_t sdr_tRCD; + uint8_t sdr_tRAS; + uint8_t sdr_module_rank_density; + uint8_t sdr_tIS; +#define sdr_superset sdr_tIS + uint8_t sdr_tIH; + uint8_t sdr_tDS; + uint8_t sdr_tDH; + uint8_t sdr_unused2[5]; + uint8_t sdr_tRC; + uint8_t sdr_unused3[18]; + uint8_t sdr_esdram; + uint8_t sdr_super_tech; + uint8_t sdr_spdrev; + uint8_t sdr_cksum; +} __packed; + +struct spdmem_rom { + uint8_t rom_rows; + uint8_t rom_cols; + uint8_t rom_banks; + uint16_t rom_datawidth; /* endian-sensitive */ + uint8_t rom_voltage; + uint16_t rom_tAA; /* endian-sensitive */ + uint8_t rom_config; + uint8_t rom_unused1; + uint8_t rom_tPA; + uint8_t rom_tOE; + uint16_t rom_tCE; /* endian-sensitive */ + uint8_t rom_burstlength; + uint8_t rom_unused2[14]; + uint8_t rom_superset[31]; + uint8_t rom_cksum; +} __packed; + + +struct spdmem_ddr { /* Dual Data Rate SDRAM */ + SPD_BITFIELD( \ + uint8_t ddr_rows:4, \ + uint8_t ddr_rows2:4, , \ + ); + SPD_BITFIELD( \ + uint8_t ddr_cols:4, \ + uint8_t ddr_cols2:4, , \ + ); + uint8_t ddr_ranks; + uint16_t ddr_datawidth; /* endian-sensitive */ + uint8_t ddr_voltage; + SPD_BITFIELD( \ + uint8_t ddr_cycle_tenths:4, \ + uint8_t ddr_cycle_whole:4, , \ + ); + SPD_BITFIELD( \ + uint8_t ddr_tAC_hundredths:4, \ + uint8_t ddr_tAC_tenths:4, , \ + ); + uint8_t ddr_config; + SPD_BITFIELD( \ + uint8_t ddr_refresh:7, \ + uint8_t ddr_selfrefresh:1, , \ + ); + SPD_BITFIELD( \ + uint8_t ddr_dramwidth:7, \ + uint8_t ddr_dram_asym_bank2:1, ,\ + ); + SPD_BITFIELD( \ + uint8_t ddr_eccwidth:7, \ + uint8_t ddr_ecc_asym_bank2:1, , \ + ); + uint8_t ddr_min_clk_delay; + SPD_BITFIELD( \ + uint8_t ddr_burstlengths:4, \ + uint8_t ddr_unused1:4, , \ + ); + uint8_t ddr_banks_per_chip; + uint8_t ddr_tCAS; + uint8_t ddr_tCS; + uint8_t ddr_tWE; + uint8_t ddr_mod_attrs; + uint8_t ddr_dev_attrs; + uint8_t ddr_min_cc_05; + uint8_t ddr_max_tAC_05; + uint8_t ddr_min_cc_1; + uint8_t ddr_max_tAC_1; + uint8_t ddr_tRP; + uint8_t ddr_tRRD; + uint8_t ddr_tRCD; + uint8_t ddr_tRAS; + uint8_t ddr_module_rank_density; + uint8_t ddr_tIS; +#define ddr_superset ddr_tIS + uint8_t ddr_tIH; + uint8_t ddr_tDS; + uint8_t ddr_tDH; + uint8_t ddr_unused2[5]; + uint8_t ddr_tRC; + uint8_t ddr_tRFC; + uint8_t ddr_tCK; + uint8_t ddr_tDQSQ; + uint8_t ddr_tQHS; + uint8_t ddr_unused3; + uint8_t ddr_height; + uint8_t ddr_unused4[15]; + uint8_t ddr_cksum; +} __packed; + +struct spdmem_ddr2 { /* Dual Data Rate 2 SDRAM */ + SPD_BITFIELD( \ + uint8_t ddr2_rows:5, \ + uint8_t ddr2_unused1:3, , \ + ); + SPD_BITFIELD( \ + uint8_t ddr2_cols:4, \ + uint8_t ddr2_unused2:4, , \ + ); + SPD_BITFIELD( \ + uint8_t ddr2_ranks:3, + uint8_t ddr2_cardoncard:1, \ + uint8_t ddr2_package:1, \ + uint8_t ddr2_height:3 \ + ); + uint8_t ddr2_datawidth; + uint8_t ddr2_unused3; + uint8_t ddr2_voltage; + SPD_BITFIELD( \ + uint8_t ddr2_cycle_frac:4, \ + uint8_t ddr2_cycle_whole:4, , \ + ); + SPD_BITFIELD( \ + uint8_t ddr2_tAC_hundredths:4, \ + uint8_t ddr2_tAC_tenths:4, , \ + ); + uint8_t ddr2_config; + SPD_BITFIELD( \ + uint8_t ddr2_refresh:7, \ + uint8_t ddr2_selfrefresh:1, , \ + ); + uint8_t ddr2_dramwidth; + uint8_t ddr2_eccwidth; + uint8_t ddr2_unused4; + SPD_BITFIELD( \ + uint8_t ddr2_burstlengths:4, \ + uint8_t ddr2_unused5:4, , \ + ); + uint8_t ddr2_banks_per_chip; + uint8_t ddr2_tCAS; + uint8_t ddr2_mechanical; + uint8_t ddr2_dimm_type; + uint8_t ddr2_mod_attrs; + uint8_t ddr2_dev_attrs; + uint8_t ddr2_min_cc_1; + uint8_t ddr2_max_tAC_1; + uint8_t ddr2_min_cc_2; + uint8_t ddr2_max_tAC_2; + uint8_t ddr2_tRP; + uint8_t ddr2_tRRD; + uint8_t ddr2_tRCD; + uint8_t ddr2_tRAS; + uint8_t ddr2_module_rank_density; + uint8_t ddr2_tIS; + uint8_t ddr2_tIH; + uint8_t ddr2_tDS; + uint8_t ddr2_tDH; + uint8_t ddr2_tWR; + uint8_t ddr2_tWTR; + uint8_t ddr2_tRTP; + uint8_t ddr2_probe; + uint8_t ddr2_extensions; + uint8_t ddr2_tRC; + uint8_t ddr2_tRFC; + uint8_t ddr2_tCK; + uint8_t ddr2_tDQSQ; + uint8_t ddr2_tQHS; + uint8_t ddr2_pll_relock; + uint8_t ddr2_Tcasemax; + uint8_t ddr2_Psi_TA_DRAM; + uint8_t ddr2_dt0; + uint8_t ddr2_dt2NQ; + uint8_t ddr2_dr2P; + uint8_t ddr2_dt3N; + uint8_t ddr2_dt3Pfast; + uint8_t ddr2_dt3Pslow; + uint8_t ddr2_dt4R_4R4W_mode; + uint8_t ddr2_dt5B; + uint8_t ddr2_dt7; + uint8_t ddr2_Psi_TA_PLL; + uint8_t ddr2_Psi_TA_Reg; + uint8_t ddr2_dt_PLL_Active; + uint8_t ddr2_dt_Reg_Active; + uint8_t ddr2_spdrev; + uint8_t ddr2_cksum; +} __packed; + +struct spdmem_fbdimm { /* Fully-buffered DIMM */ + SPD_BITFIELD( \ + uint8_t fbdimm_ps1_voltage:4, \ + uint8_t fbdimm_ps2_voltage:4, , \ + ); + SPD_BITFIELD( \ + uint8_t fbdimm_banks:2, \ + uint8_t fbdimm_cols:3, \ + uint8_t fbdimm_rows:3, \ + ); + SPD_BITFIELD( \ + uint8_t fbdimm_thick:3, \ + uint8_t fbdimm_height:3, \ + uint8_t fbdimm_unused1:2, \ + ); + uint8_t fbdimm_mod_type; + SPD_BITFIELD( \ + uint8_t fbdimm_dev_width:3, \ + uint8_t fbdimm_ranks:3, \ + uint8_t fbdimm_unused2:2, \ + ); + SPD_BITFIELD( \ + uint8_t fbdimm_ftb_divisor:4, \ + uint8_t fbdimm_ftp_dividend:4, ,\ + ); + uint8_t fbdimm_mtb_dividend; + uint8_t fbdimm_mtb_divisor; + uint8_t fbdimm_tCKmin; + uint8_t fbdimm_tCKmax; + uint8_t fbdimm_tCAS; + uint8_t fbdimm_tAAmin; + SPD_BITFIELD( \ + uint8_t fbdimm_tWR_min:4, \ + uint8_t fbdimm_WR_range:4, , \ + ); + uint8_t fbdimm_tWR; + SPD_BITFIELD( \ + uint8_t fbdimm_tWL_min:4, \ + uint8_t fbdimm_tWL_range:4, , \ + ); + SPD_BITFIELD( \ + uint8_t fbdimm_tAL_min:4, \ + uint8_t fbdimm_tAL_range:4, , \ + ); + uint8_t fbdimm_tRCDmin; + uint8_t fbdimm_tRRDmin; + uint8_t fbdimm_tRPmin; + SPD_BITFIELD( \ + uint8_t fbdimm_tRAS_msb:4, \ + uint8_t fbdimm_tRC_msb:4, , \ + ); + uint8_t fbdimm_tRAS_lsb; + uint8_t fbdimm_tRC_lsb; + uint16_t fbdimm_tRFC; /* endian-sensitive */ + uint8_t fbdimm_tWTR; + uint8_t fbdimm_tRTP; + SPD_BITFIELD( \ + uint8_t fbdimm_burst_4:1, \ + uint8_t fbdimm_burst_8:1, \ + uint8_t fbdimm_unused3:6, \ + ); + uint8_t fbdimm_terms; + uint8_t fbdimm_drivers; + uint8_t fbdimm_tREFI; + uint8_t fbdimm_Tcasemax; + uint8_t fbdimm_Psi_TA_SDRAM; + uint8_t fbdimm_DT0; + uint8_t fbdimm_DT2N_DT2Q; + uint8_t fbdimm_DT2P; + uint8_t fbdimm_DT3N; + uint8_t fbdimm_DT4R_DT4R4W; + uint8_t fbdimm_DT5B; + uint8_t fbdimm_DT7; + uint8_t fbdimm_unused4[84]; + uint16_t fbdimm_crc; +} __packed; + +struct spdmem_rambus { /* Direct Rambus DRAM */ + SPD_BITFIELD( \ + uint8_t rdr_rows:4, \ + uint8_t rdr_cols:4, , \ + ); +} __packed; + +struct spdmem_ddr3 { /* Dual Data Rate 3 SDRAM */ + uint8_t ddr3_mod_type; + SPD_BITFIELD( \ + /* chipsize is offset by 28: 0 = 256M, 1 = 512M, ... */ \ + uint8_t ddr3_chipsize:4, \ + /* logbanks is offset by 3 */ \ + uint8_t ddr3_logbanks:3, \ + uint8_t ddr3_unused1:1, \ + ); + /* cols is offset by 9, rows offset by 12 */ + SPD_BITFIELD( \ + uint8_t ddr3_cols:3, \ + uint8_t ddr3_rows:5, , \ + ); + SPD_BITFIELD( \ + uint8_t ddr3_NOT15V:1, \ + uint8_t ddr3_135V:1, \ + uint8_t ddr3_12XV:1, \ + uint8_t ddr3_unused2:5 \ + ); + /* chipwidth in bits offset by 2: 0 = X4, 1 = X8, 2 = X16 */ + /* physbanks is offset by 1 */ + SPD_BITFIELD( \ + uint8_t ddr3_chipwidth:3, \ + uint8_t ddr3_physbanks:5, , \ + ); + /* datawidth in bits offset by 3: 1 = 16b, 2 = 32b, 3 = 64b */ + SPD_BITFIELD( \ + uint8_t ddr3_datawidth:3, \ + uint8_t ddr3_hasECC:2, \ + uint8_t ddr3_unused2a:3 , \ + ); + /* Fine time base, in pico-seconds */ + SPD_BITFIELD( \ + uint8_t ddr3_ftb_divisor:4, \ + uint8_t ddr3_ftb_dividend:4, , \ + ); + uint8_t ddr3_mtb_dividend; /* 0x0108 = 0.1250ns */ + uint8_t ddr3_mtb_divisor; /* 0x010f = 0.0625ns */ + uint8_t ddr3_tCKmin; /* in terms of mtb */ + uint8_t ddr3_unused3; + uint16_t ddr3_CAS_sup; /* Bit 0 ==> CAS 4 cycles */ + uint8_t ddr3_tAAmin; /* in terms of mtb */ + uint8_t ddr3_tWRmin; + uint8_t ddr3_tRCDmin; + uint8_t ddr3_tRRDmin; + uint8_t ddr3_tRPmin; + SPD_BITFIELD( \ + uint8_t ddr3_tRAS_msb:4, \ + uint8_t ddr3_tRC_msb:4, , \ + ); + uint8_t ddr3_tRAS_lsb; + uint8_t ddr3_tRC_lsb; + uint8_t ddr3_tRFCmin_lsb; + uint8_t ddr3_tRFCmin_msb; + uint8_t ddr3_tWTRmin; + uint8_t ddr3_tRTPmin; + SPD_BITFIELD( \ + uint8_t ddr3_tFAW_msb:4, , , \ + ); + uint8_t ddr3_tFAW_lsb; + uint8_t ddr3_output_drvrs; + SPD_BITFIELD( \ + uint8_t ddr3_ext_temp_range:1, \ + uint8_t ddr3_ext_temp_2x_refresh:1, \ + uint8_t ddr3_asr_refresh:1, \ + /* Bit 4 indicates on-die thermal sensor */ + /* Bit 7 indicates Partial-Array Self-Refresh (PASR) */ + uint8_t ddr3_unused7:5 \ + ); + SPD_BITFIELD( \ + uint8_t ddr3_therm_sensor_acc:7,\ + uint8_t ddr3_has_therm_sensor:1, , \ + ); + SPD_BITFIELD( \ + uint8_t ddr3_non_std_devtype:7, \ + uint8_t ddr3_std_device:1, , \ + ); + uint8_t ddr3_unused4[26]; + uint8_t ddr3_mod_height; + uint8_t ddr3_mod_thickness; + uint8_t ddr3_ref_card; + uint8_t ddr3_mapping; + uint8_t ddr3_unused5[53]; + uint8_t ddr3_mfgID_lsb; + uint8_t ddr3_mfgID_msb; + uint8_t ddr3_mfgloc; + uint8_t ddr3_mfg_year; + uint8_t ddr3_mfg_week; + uint8_t ddr3_serial[4]; + uint16_t ddr3_crc; +} __packed; + +struct spdmem { + uint8_t sm_len; + uint8_t sm_size; + uint8_t sm_type; + union { + struct spdmem_fbdimm u1_fbd; + struct spdmem_fpm u1_fpm; + struct spdmem_ddr u1_ddr; + struct spdmem_ddr2 u1_ddr2; + struct spdmem_sdram u1_sdr; + struct spdmem_rambus u1_rdr; + struct spdmem_rom u1_rom; + struct spdmem_ddr3 u1_ddr3; + } sm_u1; + uint8_t sm_extension[128]; +} __packed; +#define sm_fbd sm_u1.u1_fbd +#define sm_fpm sm_u1.u1_fpm +#define sm_ddr sm_u1.u1_ddr +#define sm_ddr2 sm_u1.u1_ddr2 +#define sm_rdr sm_u1.u1_rdr +#define sm_rom sm_u1.u1_rom +#define sm_ddr3 sm_u1.u1_ddr3 +#define sm_sdr sm_u1.u1_sdr + +/* some fields are in the same place for all memory types */ + +#define sm_cksum sm_fpm.fpm_cksum +#define sm_config sm_fpm.fpm_config +#define sm_voltage sm_fpm.fpm_voltage +#define sm_refresh sm_fpm.fpm_refresh +#define sm_selfrefresh sm_fpm.fpm_selfrefresh + +#define SPDMEM_TYPE_MAXLEN 16 + +struct spdmem_softc { + uint8_t (*sc_read)(struct spdmem_softc *, uint8_t); + struct spdmem sc_spd_data; + char sc_type[SPDMEM_TYPE_MAXLEN]; +}; + +int spdmem_common_probe(struct spdmem_softc *); +void spdmem_common_attach(struct spdmem_softc *, device_t);