/* $OpenBSD: spdmem.c,v 1.26 2008/05/21 12:51:45 jsg Exp $ */ /* $NetBSD: spdmem.c,v 1.3 2007/09/20 23:09:59 xtraeme Exp $ */ /* * Copyright (c) 2007 Jonathan Gray * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * 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 #include #include #include /* 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_NONE 0xff #define SPDMEM_MEMTYPE_DIRECT_RAMBUS 0x01 #define SPDMEM_MEMTYPE_RAMBUS 0x11 /* 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_SDR_PEM 0x04 /* FPM and EDO DIMMS */ #define SPDMEM_FPM_ROWS 0x00 #define SPDMEM_FPM_COLS 0x01 #define SPDMEM_FPM_BANKS 0x02 #define SPDMEM_FPM_CONFIG 0x08 #define SPDMEM_FPM_REFRESH 0x09 #define SPDMEM_FPM_SUPERSET 0x0c /* PC66/PC100/PC133 SDRAM */ #define SPDMEM_SDR_ROWS 0x00 #define SPDMEM_SDR_COLS 0x01 #define SPDMEM_SDR_BANKS 0x02 #define SPDMEM_SDR_CYCLE 0x06 #define SPDMEM_SDR_BANKS_PER_CHIP 0x0e #define SPDMEM_SDR_MOD_ATTRIB 0x12 #define SPDMEM_SDR_SUPERSET 0x1d #define SPDMEM_SDR_FREQUENCY 126 #define SPDMEM_SDR_CAS 127 #define SPDMEM_SDR_FREQ_66 0x66 #define SPDMEM_SDR_FREQ_100 0x64 #define SPDMEM_SDR_FREQ_133 0x85 #define SPDMEM_SDR_CAS2 (1 << 1) #define SPDMEM_SDR_CAS3 (1 << 2) /* Rambus Direct DRAM */ #define SPDMEM_RDR_MODULE_TYPE 0x00 #define SPDMEM_RDR_ROWS_COLS 0x01 #define SPDMEM_RDR_BANK 0x02 #define SPDMEM_RDR_TYPE_RIMM 1 #define SPDMEM_RDR_TYPE_SORIMM 2 #define SPDMEM_RDR_TYPE_EMBED 3 #define SPDMEM_RDR_TYPE_RIMM32 4 /* Dual Data Rate SDRAM */ #define SPDMEM_DDR_ROWS 0x00 #define SPDMEM_DDR_COLS 0x01 #define SPDMEM_DDR_RANKS 0x02 #define SPDMEM_DDR_DATAWIDTH 0x03 #define SPDMEM_DDR_VOLTAGE 0x05 #define SPDMEM_DDR_CYCLE 0x06 #define SPDMEM_DDR_REFRESH 0x09 #define SPDMEM_DDR_BANKS_PER_CHIP 0x0e #define SPDMEM_DDR_CAS 0x0f #define SPDMEM_DDR_MOD_ATTRIB 0x12 #define SPDMEM_DDR_SUPERSET 0x1d #define SPDMEM_DDR_ATTRIB_REG (1 << 1) /* Dual Data Rate 2 SDRAM */ #define SPDMEM_DDR2_ROWS 0x00 #define SPDMEM_DDR2_COLS 0x01 #define SPDMEM_DDR2_RANKS 0x02 #define SPDMEM_DDR2_DATAWIDTH 0x03 #define SPDMEM_DDR2_VOLTAGE 0x05 #define SPDMEM_DDR2_CYCLE 0x06 #define SPDMEM_DDR2_DIMMTYPE 0x11 #define SPDMEM_DDR2_RANK_DENSITY 0x1c #define SPDMEM_DDR2_TYPE_REGMASK ((1 << 4) | (1 << 0)) #define SPDMEM_DDR2_SODIMM (1 << 2) #define SPDMEM_DDR2_MICRO_DIMM (1 << 3) #define SPDMEM_DDR2_MINI_RDIMM (1 << 4) #define SPDMEM_DDR2_MINI_UDIMM (1 << 5) static const uint8_t ddr2_cycle_tenths[] = { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 25, 33, 66, 75, 0, 0 }; struct spdmem { uint8_t sm_len; uint8_t sm_size; uint8_t sm_type; uint8_t sm_data[60]; uint8_t sm_cksum; } __packed; #define SPDMEM_TYPE_MAXLEN 16 struct spdmem_softc { struct device sc_dev; i2c_tag_t sc_tag; i2c_addr_t sc_addr; struct spdmem sc_spd_data; char sc_type[SPDMEM_TYPE_MAXLEN]; }; int spdmem_match(struct device *, void *, void *); void spdmem_attach(struct device *, struct device *, void *); uint8_t spdmem_read(struct spdmem_softc *, uint8_t); void spdmem_sdram_decode(struct spdmem_softc *, struct spdmem *); void spdmem_rdr_decode(struct spdmem_softc *, struct spdmem *); void spdmem_ddr_decode(struct spdmem_softc *, struct spdmem *); void spdmem_ddr2_decode(struct spdmem_softc *, struct spdmem *); struct cfattach spdmem_ca = { sizeof(struct spdmem_softc), spdmem_match, spdmem_attach }; struct cfdriver spdmem_cd = { NULL, "spdmem", DV_DULL }; #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" }; static const char *spdmem_superset_types[] = { "unknown", "ESDRAM", "DDR ESDRAM", "PEM EDO", "PEM SDRAM" }; static const char *spdmem_parity_types[] = { "non-parity", "data parity", "ECC", "data parity and ECC", "cmd/addr parity", "cmd/addr/data parity", "cmd/addr parity, data ECC", "cmd/addr/data parity, data ECC" }; int spdmem_match(struct device *parent, void *match, void *aux) { struct i2c_attach_args *ia = aux; if (strcmp(ia->ia_name, "spd") == 0) return (1); return (0); } void spdmem_sdram_decode(struct spdmem_softc *sc, struct spdmem *s) { const char *type; int dimm_size, cycle_time, d_clk, p_clk, bits; int num_banks, per_chip; uint8_t config, rows, cols; type = spdmem_basic_types[s->sm_type]; if (s->sm_data[SPDMEM_SDR_SUPERSET] == SPDMEM_SUPERSET_SDR_PEM) type = spdmem_superset_types[SPDMEM_SUPERSET_SDR_PEM]; if (s->sm_data[SPDMEM_SDR_SUPERSET] == SPDMEM_SUPERSET_ESDRAM) type = spdmem_superset_types[SPDMEM_SUPERSET_ESDRAM]; dimm_size = 0; num_banks = s->sm_data[SPDMEM_SDR_BANKS]; per_chip = s->sm_data[SPDMEM_SDR_BANKS_PER_CHIP]; rows = s->sm_data[SPDMEM_SDR_ROWS] & 0x0f; cols = s->sm_data[SPDMEM_SDR_COLS] & 0x0f; dimm_size = (1 << (rows + cols - 17)) * num_banks * per_chip; if (dimm_size > 0) { if (dimm_size < 1024) printf(" %dMB", dimm_size); else printf(" %dGB", dimm_size / 1024); } printf(" %s", type); strlcpy(sc->sc_type, type, SPDMEM_TYPE_MAXLEN); if (s->sm_data[SPDMEM_DDR_MOD_ATTRIB] & SPDMEM_DDR_ATTRIB_REG) printf(" registered"); if (s->sm_data[SPDMEM_FPM_CONFIG] < 8) printf(" %s", spdmem_parity_types[s->sm_data[SPDMEM_FPM_CONFIG]]); /* cycle_time is expressed in units of 0.01 ns */ cycle_time = (s->sm_data[SPDMEM_DDR_CYCLE] >> 4) * 100 + (s->sm_data[SPDMEM_DDR_CYCLE] & 0x0f) * 10; if (cycle_time != 0) { /* * cycle time is scaled by a factor of 100 to avoid using * floating point. Calculate memory speed as the number * of cycles per microsecond. */ d_clk = 100 * 1000; config = s->sm_data[SPDMEM_FPM_CONFIG]; bits = s->sm_data[SPDMEM_DDR_DATAWIDTH] | (s->sm_data[SPDMEM_DDR_DATAWIDTH + 1] << 8); if (config == 1 || config == 2) bits -= 8; p_clk = 66; if (s->sm_len >= 128) { switch (spdmem_read(sc, SPDMEM_SDR_FREQUENCY)) { case SPDMEM_SDR_FREQ_100: case SPDMEM_SDR_FREQ_133: /* We need to check ns to decide here */ if (s->sm_data[SPDMEM_SDR_CYCLE] < 0x80) p_clk = 133; else p_clk = 100; break; case SPDMEM_SDR_FREQ_66: default: p_clk = 66; break; } } printf(" PC%d", p_clk); } /* Print CAS latency */ if (s->sm_len < 128) return; if (spdmem_read(sc, SPDMEM_SDR_CAS) & SPDMEM_SDR_CAS2) printf("CL2"); else if (spdmem_read(sc, SPDMEM_SDR_CAS) & SPDMEM_SDR_CAS3) printf("CL3"); } void spdmem_rdr_decode(struct spdmem_softc *sc, struct spdmem *s) { int rimm_size; uint8_t row_bits, col_bits, bank_bits; row_bits = s->sm_data[SPDMEM_RDR_ROWS_COLS] >> 4; col_bits = s->sm_data[SPDMEM_RDR_ROWS_COLS] & 0x0f; bank_bits = s->sm_data[SPDMEM_RDR_BANK] & 0x07; /* subtracting 13 here is a cheaper way of dividing by 8k later */ rimm_size = 1 << (row_bits + col_bits + bank_bits - 13); if (rimm_size < 1024) printf(" %dMB ", rimm_size); else printf(" %dGB ", rimm_size / 1024); switch(s->sm_data[SPDMEM_RDR_MODULE_TYPE]) { case SPDMEM_RDR_TYPE_RIMM: printf("RIMM"); break; case SPDMEM_RDR_TYPE_SORIMM: printf("SO-RIMM"); break; case SPDMEM_RDR_TYPE_EMBED: printf("Embedded Rambus"); break; case SPDMEM_RDR_TYPE_RIMM32: printf("RIMM32"); break; } } void spdmem_ddr_decode(struct spdmem_softc *sc, struct spdmem *s) { const char *type; int dimm_size, cycle_time, d_clk, p_clk, bits; int i, num_banks, per_chip; uint8_t config, rows, cols, cl; type = spdmem_basic_types[s->sm_type]; if (s->sm_data[SPDMEM_DDR_SUPERSET] == SPDMEM_SUPERSET_DDR_ESDRAM) type = spdmem_superset_types[SPDMEM_SUPERSET_DDR_ESDRAM]; dimm_size = 0; num_banks = s->sm_data[SPDMEM_SDR_BANKS]; per_chip = s->sm_data[SPDMEM_SDR_BANKS_PER_CHIP]; rows = s->sm_data[SPDMEM_SDR_ROWS] & 0x0f; cols = s->sm_data[SPDMEM_SDR_COLS] & 0x0f; dimm_size = (1 << (rows + cols - 17)) * num_banks * per_chip; if (dimm_size > 0) { if (dimm_size < 1024) printf(" %dMB", dimm_size); else printf(" %dGB", dimm_size / 1024); } printf(" %s", type); strlcpy(sc->sc_type, type, SPDMEM_TYPE_MAXLEN); if (s->sm_data[SPDMEM_DDR_MOD_ATTRIB] & SPDMEM_DDR_ATTRIB_REG) printf(" registered"); if (s->sm_data[SPDMEM_FPM_CONFIG] < 8) printf(" %s", spdmem_parity_types[s->sm_data[SPDMEM_FPM_CONFIG]]); /* cycle_time is expressed in units of 0.01 ns */ cycle_time = (s->sm_data[SPDMEM_DDR_CYCLE] >> 4) * 100 + (s->sm_data[SPDMEM_DDR_CYCLE] & 0x0f) * 10; if (cycle_time != 0) { /* * cycle time is scaled by a factor of 100 to avoid using * floating point. Calculate memory speed as the number * of cycles per microsecond. * DDR2 uses dual-pumped clock */ d_clk = 100 * 1000 * 2; config = s->sm_data[SPDMEM_FPM_CONFIG]; bits = s->sm_data[SPDMEM_DDR_DATAWIDTH] | (s->sm_data[SPDMEM_DDR_DATAWIDTH + 1] << 8); if (config == 1 || config == 2) bits -= 8; d_clk /= cycle_time; p_clk = d_clk * bits / 8; if ((p_clk % 100) >= 50) p_clk += 50; p_clk -= p_clk % 100; printf(" PC%d", p_clk); } /* Print CAS latency */ for (i = 6; i >= 0; i--) { if (s->sm_data[SPDMEM_DDR_CAS] & (1 << i)) { cl = ((i * 10) / 2) + 10; printf("CL%d.%d", cl / 10, cl % 10); break; } } } void spdmem_ddr2_decode(struct spdmem_softc *sc, struct spdmem *s) { const char *type; int dimm_size, cycle_time, d_clk, p_clk, bits; int i, num_ranks, density; uint8_t config; type = spdmem_basic_types[s->sm_type]; dimm_size = 0; num_ranks = (s->sm_data[SPDMEM_DDR2_RANKS] & 0x7) + 1; density = (s->sm_data[SPDMEM_DDR2_RANK_DENSITY] & 0xf0) | ((s->sm_data[SPDMEM_DDR2_RANK_DENSITY] & 0x0f) << 8); dimm_size = num_ranks * density * 4; if (dimm_size > 0) { if (dimm_size < 1024) printf(" %dMB", dimm_size); else printf(" %dGB", dimm_size / 1024); } printf(" %s", type); strlcpy(sc->sc_type, type, SPDMEM_TYPE_MAXLEN); if (s->sm_data[SPDMEM_DDR2_DIMMTYPE] & SPDMEM_DDR2_TYPE_REGMASK) printf(" registered"); if (s->sm_data[SPDMEM_FPM_CONFIG] < 8) printf(" %s", spdmem_parity_types[s->sm_data[SPDMEM_FPM_CONFIG]]); /* cycle_time is expressed in units of 0.01 ns */ cycle_time = (s->sm_data[SPDMEM_DDR2_CYCLE] >> 4) * 100 + ddr2_cycle_tenths[(s->sm_data[SPDMEM_DDR2_CYCLE] & 0x0f)]; if (cycle_time != 0) { /* * cycle time is scaled by a factor of 100 to avoid using * floating point. Calculate memory speed as the number * of cycles per microsecond. * DDR2 uses quad-pumped clock */ d_clk = 100 * 1000 * 4; config = s->sm_data[SPDMEM_FPM_CONFIG]; bits = s->sm_data[SPDMEM_DDR2_DATAWIDTH]; if ((config & 0x03) != 0) bits -= 8; d_clk /= cycle_time; d_clk = (d_clk + 1) / 2; p_clk = d_clk * bits / 8; p_clk -= p_clk % 100; printf(" PC2-%d", p_clk); } /* Print CAS latency */ for (i = 5; i >= 2; i--) { if (s->sm_data[SPDMEM_DDR_CAS] & (i << i)) { printf("CL%d", i); break; } } switch (s->sm_data[SPDMEM_DDR2_DIMMTYPE]) { case SPDMEM_DDR2_SODIMM: printf(" SO-DIMM"); break; case SPDMEM_DDR2_MICRO_DIMM: printf(" Micro-DIMM"); break; case SPDMEM_DDR2_MINI_RDIMM: case SPDMEM_DDR2_MINI_UDIMM: printf(" Mini-DIMM"); break; } } void spdmem_attach(struct device *parent, struct device *self, void *aux) { struct spdmem_softc *sc = (struct spdmem_softc *)self; struct i2c_attach_args *ia = aux; struct spdmem *s = &(sc->sc_spd_data); int i; sc->sc_tag = ia->ia_tag; sc->sc_addr = ia->ia_addr; printf(":"); /* All SPD have at least 64 bytes of data including checksum */ for (i = 0; i < 64; i++) { ((uint8_t *)s)[i] = spdmem_read(sc, i); } /* * Decode and print SPD contents */ if (s->sm_len < 4) { if (s->sm_type == SPDMEM_MEMTYPE_DIRECT_RAMBUS) spdmem_rdr_decode(sc, s); else printf(" no decode method for Rambus memory"); } else { switch(s->sm_type) { case SPDMEM_MEMTYPE_EDO: case SPDMEM_MEMTYPE_SDRAM: spdmem_sdram_decode(sc, s); break; case SPDMEM_MEMTYPE_DDRSDRAM: spdmem_ddr_decode(sc, s); break; case SPDMEM_MEMTYPE_DDR2SDRAM: spdmem_ddr2_decode(sc, s); break; case SPDMEM_MEMTYPE_NONE: printf(" no EEPROM found"); break; default: if (s->sm_type <= 10) printf(" no decode method for %s memory", spdmem_basic_types[s->sm_type]); else printf(" unknown memory type %d", s->sm_type); break; } } printf("\n"); } uint8_t spdmem_read(struct spdmem_softc *sc, uint8_t reg) { uint8_t val = 0xff; iic_acquire_bus(sc->sc_tag,0); iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, ®, sizeof reg, &val, sizeof val, 0); iic_release_bus(sc->sc_tag, 0); return val; }