root/sys/dev/spdmem.c
/*      $OpenBSD: spdmem.c,v 1.7 2019/12/21 12:33:03 kettenis Exp $     */
/* $NetBSD: spdmem.c,v 1.3 2007/09/20 23:09:59 xtraeme Exp $ */

/*
 * Copyright (c) 2007 Jonathan Gray <jsg@openbsd.org>
 *
 * 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 <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>

#include <dev/spdmemvar.h>

/* 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_DDR4_SPDLEN_128          0x01    /* SPD EEPROM Sizes  */
#define SPDMEM_DDR4_SPDLEN_256          0x02
#define SPDMEM_DDR4_SPDLEN_384          0x03
#define SPDMEM_DDR4_SPDLEN_512          0x04
#define SPDMEM_DDR4_SPDLEN_MASK         0x0f    /* 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 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_DDR4SDRAM        0x0c
                                        /* 0xd reserved */
#define SPDMEM_MEMTYPE_DDR4ESDRAM       0x0e
#define SPDMEM_MEMTYPE_LPDDR3SDRAM      0x0f
#define SPDMEM_MEMTYPE_LPDDR4SDRAM      0x10
#define SPDMEM_MEMTYPE_LPDDR4XSDRAM     0x11
#define SPDMEM_MEMTYPE_DDR5SDRAM        0x12
#define SPDMEM_MEMTYPE_LPDDR5SDRAM      0x13

#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)

/* DDR2 FB-DIMM SDRAM */
#define SPDMEM_FBDIMM_ADDR              0x01
#define SPDMEM_FBDIMM_RANKS             0x04
#define SPDMEM_FBDIMM_MTB_DIVIDEND      0x06
#define SPDMEM_FBDIMM_MTB_DIVISOR       0x07
#define SPDMEM_FBDIMM_PROTO             0x4e

#define SPDMEM_FBDIMM_RANKS_WIDTH               0x07
#define SPDMEM_FBDIMM_ADDR_BANKS                0x02
#define SPDMEM_FBDIMM_ADDR_COL                  0x0c
#define SPDMEM_FBDIMM_ADDR_COL_SHIFT            2
#define SPDMEM_FBDIMM_ADDR_ROW                  0xe0
#define SPDMEM_FBDIMM_ADDR_ROW_SHIFT            5
#define SPDMEM_FBDIMM_PROTO_ECC                 (1 << 1)


/* Dual Data Rate 3 SDRAM */
#define SPDMEM_DDR3_MODTYPE             0x00
#define SPDMEM_DDR3_DENSITY             0x01
#define SPDMEM_DDR3_MOD_ORG             0x04
#define SPDMEM_DDR3_DATAWIDTH           0x05
#define SPDMEM_DDR3_MTB_DIVIDEND        0x07
#define SPDMEM_DDR3_MTB_DIVISOR         0x08
#define SPDMEM_DDR3_TCKMIN              0x09
#define SPDMEM_DDR3_THERMAL             0x1d

#define SPDMEM_DDR3_DENSITY_CAPMASK             0x0f
#define SPDMEM_DDR3_MOD_ORG_CHIPWIDTH_MASK      0x07
#define SPDMEM_DDR3_MOD_ORG_BANKS_SHIFT         3
#define SPDMEM_DDR3_MOD_ORG_BANKS_MASK          0x07
#define SPDMEM_DDR3_DATAWIDTH_ECCMASK           (1 << 3)
#define SPDMEM_DDR3_DATAWIDTH_PRIMASK           0x07
#define SPDMEM_DDR3_THERMAL_PRESENT             (1 << 7)

#define SPDMEM_DDR3_RDIMM               0x01
#define SPDMEM_DDR3_UDIMM               0x02
#define SPDMEM_DDR3_SODIMM              0x03
#define SPDMEM_DDR3_MICRO_DIMM          0x04
#define SPDMEM_DDR3_MINI_RDIMM          0x05
#define SPDMEM_DDR3_MINI_UDIMM          0x06

/* Dual Data Rate 4 SDRAM */
#define SPDMEM_DDR4_MODTYPE             0x00
#define SPDMEM_DDR4_DENSITY             0x01
#define SPDMEM_DDR4_PACK_TYPE           0x03
#define SPDMEM_DDR4_MOD_ORG             0x09
#define SPDMEM_DDR4_DATAWIDTH           0x0a
#define SPDMEM_DDR4_THERMAL             0x0b
#define SPDMEM_DDR4_TCKMIN_MTB          0x0f
#define SPDMEM_DDR4_TCKMIN_FTB          0x7d    /* not offset by 3 */

#define SPDMEM_DDR4_DENSITY_CAPMASK             0x0f
#define SPDMEM_DDR4_PACK_TYPE_SIG_LOAD_MASK     0x03
#define SPDMEM_DDR4_PACK_TYPE_SIG_SINGLE_LOAD   0x02
#define SPDMEM_DDR4_PACK_TYPE_DIE_COUNT_SHIFT   4
#define SPDMEM_DDR4_PACK_TYPE_DIE_COUNT_MASK    0x07
#define SPDMEM_DDR4_MOD_ORG_CHIPWIDTH_MASK      0x07
#define SPDMEM_DDR4_MOD_ORG_BANKS_SHIFT         3
#define SPDMEM_DDR4_MOD_ORG_BANKS_MASK          0x07
#define SPDMEM_DDR4_DATAWIDTH_ECCMASK           (1 << 3)
#define SPDMEM_DDR4_DATAWIDTH_PRIMASK           0x07
#define SPDMEM_DDR4_THERMAL_PRESENT             (1 << 7)

#define SPDMEM_DDR4_RDIMM               0x01
#define SPDMEM_DDR4_UDIMM               0x02
#define SPDMEM_DDR4_SODIMM              0x03
#define SPDMEM_DDR4_LRDIMM              0x04
#define SPDMEM_DDR4_MINI_RDIMM          0x05
#define SPDMEM_DDR4_MINI_UDIMM          0x06
#define SPDMEM_DDR4_LP_DIMM             0x07
#define SPDMEM_DDR4_72B_SO_RDIMM        0x08
#define SPDMEM_DDR4_72B_SO_UDIMM        0x09
#define SPDMEM_DDR4_16B_SO_DIMM         0x0c
#define SPDMEM_DDR4_32B_SO_DIMM         0x0d
#define SPDMEM_DDR4_NON_DIMM            0x0e
#define SPDMEM_DDR4_MODTYPE_MASK        0x0f
#define SPDMEM_DDR4_MODTYPE_HYBRID      0x80

static const uint8_t ddr2_cycle_tenths[] = {
        0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 25, 33, 66, 75, 0, 0
};

#define SPDMEM_TYPE_MAXLEN 16

uint16_t        spdmem_crc16(struct spdmem_softc *, int);
static inline
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 *);
void            spdmem_fbdimm_decode(struct spdmem_softc *, struct spdmem *);
void            spdmem_ddr3_decode(struct spdmem_softc *, struct spdmem *);

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-DIMM",
        "DDR2 SDRAM FB-DIMM Probe",
        "DDR3 SDRAM",
        "DDR4 SDRAM",
        "unknown",
        "DDR4E SDRAM",
        "LPDDR3 SDRAM",
        "LPDDR4 SDRAM",
        "LPDDR4X SDRAM",
        "DDR5 SDRAM",
        "LPDDR5 SDRAM"
};

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"
};

static inline uint8_t
spdmem_read(struct spdmem_softc *sc, uint8_t reg)
{
        return (*sc->sc_read)(sc, reg);
}

/* CRC functions used for certain memory types */
uint16_t
spdmem_crc16(struct spdmem_softc *sc, int count)
{
        uint16_t crc;
        int i, j;
        uint8_t val;
        crc = 0;
        for (j = 0; j <= count; j++) {
                val = spdmem_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);
}

void
spdmem_sdram_decode(struct spdmem_softc *sc, struct spdmem *s)
{
        const char *type;
        int dimm_size, p_clk;
        int num_banks, per_chip;
        uint8_t 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];

        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);

        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]]);

        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];

        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);

        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.
                 * DDR 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];

        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);

        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 = 7; i >= 2; i--) {
                if (s->sm_data[SPDMEM_DDR_CAS] & (1 << 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_fbdimm_decode(struct spdmem_softc *sc, struct spdmem *s)
{
        int dimm_size, cycle_time, d_clk, p_clk, bits;
        uint8_t rows, cols, dividend, divisor;
        /*
         * FB-DIMM is very much like DDR3
         */

        cols = (s->sm_data[SPDMEM_FBDIMM_ADDR] & SPDMEM_FBDIMM_ADDR_COL) >>
            SPDMEM_FBDIMM_ADDR_COL_SHIFT;
        rows = (s->sm_data[SPDMEM_FBDIMM_ADDR] & SPDMEM_FBDIMM_ADDR_ROW) >>
            SPDMEM_FBDIMM_ADDR_ROW_SHIFT;
        dimm_size = rows + 12 + cols +  9 - 20 - 3;

        if (dimm_size < 1024)
                printf(" %dMB", dimm_size);
        else
                printf(" %dGB", dimm_size / 1024);

        dividend = s->sm_data[SPDMEM_FBDIMM_MTB_DIVIDEND];
        divisor = s->sm_data[SPDMEM_FBDIMM_MTB_DIVISOR];

        cycle_time = (1000 * dividend + (divisor / 2)) / divisor;

        if (cycle_time != 0) {
                /*
                 * cycle time is scaled by a factor of 1000 to avoid using
                 * floating point.  Calculate memory speed as the number
                 * of cycles per microsecond.
                 */
                d_clk = 1000 * 1000;

                /* DDR2 FB-DIMM uses a dual-pumped clock */
                d_clk *= 2;
                bits = 1 << ((s->sm_data[SPDMEM_FBDIMM_RANKS] &
                    SPDMEM_FBDIMM_RANKS_WIDTH) + 2);

                p_clk = (d_clk * bits) / 8 / cycle_time;
                p_clk -= p_clk % 100;
                printf(" PC2-%d", p_clk);
        }
}

void
spdmem_ddr3_decode(struct spdmem_softc *sc, struct spdmem *s)
{
        const char *type;
        int dimm_size, cycle_time, d_clk, p_clk, bits;
        uint8_t mtype, chipsize, dividend, divisor;
        uint8_t datawidth, chipwidth, physbanks;

        type = spdmem_basic_types[s->sm_type];

        chipsize = s->sm_data[SPDMEM_DDR3_DENSITY] &
            SPDMEM_DDR3_DENSITY_CAPMASK;
        datawidth = s->sm_data[SPDMEM_DDR3_DATAWIDTH] &
            SPDMEM_DDR3_DATAWIDTH_PRIMASK;
        chipwidth = s->sm_data[SPDMEM_DDR3_MOD_ORG] &
            SPDMEM_DDR3_MOD_ORG_CHIPWIDTH_MASK;
        physbanks = (s->sm_data[SPDMEM_DDR3_MOD_ORG] >> 
            SPDMEM_DDR3_MOD_ORG_BANKS_SHIFT) & SPDMEM_DDR3_MOD_ORG_BANKS_MASK;

        dimm_size = (chipsize + 28 - 20) - 3 + (datawidth + 3) -
            (chipwidth + 2);
        dimm_size = (1 << dimm_size) * (physbanks + 1);

        if (dimm_size < 1024)
                printf(" %dMB", dimm_size);
        else
                printf(" %dGB", dimm_size / 1024);

        printf(" %s", type);

        mtype = s->sm_data[SPDMEM_DDR3_MODTYPE];
        if (mtype == SPDMEM_DDR3_RDIMM || mtype == SPDMEM_DDR3_MINI_RDIMM)
                printf(" registered");

        if (s->sm_data[SPDMEM_DDR3_DATAWIDTH] & SPDMEM_DDR3_DATAWIDTH_ECCMASK) 
                printf(" ECC");

        dividend = s->sm_data[SPDMEM_DDR3_MTB_DIVIDEND];
        divisor = s->sm_data[SPDMEM_DDR3_MTB_DIVISOR];
        cycle_time = (1000 * dividend +  (divisor / 2)) / divisor;
        cycle_time *= s->sm_data[SPDMEM_DDR3_TCKMIN];

        if (cycle_time != 0) {
                /*
                 * cycle time is scaled by a factor of 1000 to avoid using
                 * floating point.  Calculate memory speed as the number
                 * of cycles per microsecond.
                 * DDR3 uses a dual-pumped clock
                 */
                d_clk = 1000 * 1000;
                d_clk *= 2;
                bits = 1 << ((s->sm_data[SPDMEM_DDR3_DATAWIDTH] &
                    SPDMEM_DDR3_DATAWIDTH_PRIMASK) + 3);
                /*
                 * 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 p_clk PC3-10666 (d_clk DDR3-1333)
                 */
                p_clk = (d_clk * bits) / 8 / cycle_time;
                p_clk -= (p_clk % 100);
                d_clk = ((d_clk + cycle_time / 2) ) / cycle_time;
                printf(" PC3-%d", p_clk);
        }

        switch (s->sm_data[SPDMEM_DDR3_MODTYPE]) {
        case SPDMEM_DDR3_SODIMM:
                printf(" SO-DIMM");
                break;
        case SPDMEM_DDR3_MICRO_DIMM:
                printf(" Micro-DIMM");
                break;
        case SPDMEM_DDR3_MINI_RDIMM:
        case SPDMEM_DDR3_MINI_UDIMM:
                printf(" Mini-DIMM");
                break;
        }

        if (s->sm_data[SPDMEM_DDR3_THERMAL] & SPDMEM_DDR3_THERMAL_PRESENT)
                printf(" with thermal sensor");
}

void
spdmem_ddr4_decode(struct spdmem_softc *sc, struct spdmem *s)
{
        static const int ddr4_chipsize[16] = { 256, 512, 1024, 2048, 4096,
            8 * 1024, 16 * 1024, 32 * 1024, 12 * 1024, 24 * 1024,
            3 * 1024, 6 * 1024, 18 * 1024 };
        const char *type;
        int dimm_size, cycle_time, d_clk, p_clk, bits;
        uint8_t mtype, chipsize, mtb;
        int8_t ftb;
        uint8_t datawidth, chipwidth, physbanks, diecount = 0;

        type = spdmem_basic_types[s->sm_type];

        chipsize = s->sm_data[SPDMEM_DDR4_DENSITY] &
            SPDMEM_DDR4_DENSITY_CAPMASK;
        datawidth = s->sm_data[SPDMEM_DDR4_DATAWIDTH] &
            SPDMEM_DDR4_DATAWIDTH_PRIMASK;
        chipwidth = s->sm_data[SPDMEM_DDR4_MOD_ORG] &
            SPDMEM_DDR4_MOD_ORG_CHIPWIDTH_MASK;
        physbanks = (s->sm_data[SPDMEM_DDR4_MOD_ORG] >> 
            SPDMEM_DDR4_MOD_ORG_BANKS_SHIFT) & SPDMEM_DDR4_MOD_ORG_BANKS_MASK;

        if ((s->sm_data[SPDMEM_DDR4_PACK_TYPE] &
            SPDMEM_DDR4_PACK_TYPE_SIG_LOAD_MASK) ==
            SPDMEM_DDR4_PACK_TYPE_SIG_SINGLE_LOAD) {
                diecount = (s->sm_data[SPDMEM_DDR4_PACK_TYPE] >>
                    SPDMEM_DDR4_PACK_TYPE_DIE_COUNT_SHIFT) &
                    SPDMEM_DDR4_PACK_TYPE_DIE_COUNT_MASK;
        }

        dimm_size = (datawidth + 3) - (chipwidth + 2);
        dimm_size = (ddr4_chipsize[chipsize] / 8) * (1 << dimm_size) *
            (physbanks + 1) * (diecount + 1);

        if (dimm_size < 1024)
                printf(" %dMB", dimm_size);
        else
                printf(" %dGB", dimm_size / 1024);

        printf(" %s", type);

        mtype = s->sm_data[SPDMEM_DDR4_MODTYPE];
        if (mtype & SPDMEM_DDR4_MODTYPE_HYBRID)
                printf(" hybrid");
        mtype &= SPDMEM_DDR4_MODTYPE_MASK;
        if (mtype == SPDMEM_DDR4_RDIMM || mtype == SPDMEM_DDR4_MINI_RDIMM ||
            mtype == SPDMEM_DDR4_72B_SO_RDIMM)
                printf(" registered");
        if (mtype == SPDMEM_DDR4_72B_SO_UDIMM ||
            mtype == SPDMEM_DDR4_72B_SO_RDIMM)
                printf(" 72-bit");
        if (mtype == SPDMEM_DDR4_32B_SO_DIMM)
                printf(" 32-bit");
        if (mtype == SPDMEM_DDR4_16B_SO_DIMM)
                printf(" 16-bit");

        if (s->sm_data[SPDMEM_DDR4_DATAWIDTH] & SPDMEM_DDR4_DATAWIDTH_ECCMASK) 
                printf(" ECC");

        mtb = s->sm_data[SPDMEM_DDR4_TCKMIN_MTB];
        /* SPDMEM_DDR4_TCKMIN_FTB (addr 125) is outside of s->sm_data */
        ftb = spdmem_read(sc, SPDMEM_DDR4_TCKMIN_FTB);
        cycle_time = mtb * 125 + ftb; /* in ps */

        if (cycle_time != 0) {
                /*
                 * cycle time is scaled by a factor of 1000 to avoid using
                 * floating point.  Calculate memory speed as the number
                 * of cycles per microsecond.
                 * DDR4 uses a dual-pumped clock
                 */
                d_clk = 1000 * 1000;
                d_clk *= 2;
                bits = 1 << ((s->sm_data[SPDMEM_DDR4_DATAWIDTH] &
                    SPDMEM_DDR4_DATAWIDTH_PRIMASK) + 3);

                p_clk = (d_clk * bits) / 8 / cycle_time;
                p_clk -= (p_clk % 100);
                printf(" PC4-%d", p_clk);
        }

        switch (s->sm_data[SPDMEM_DDR4_MODTYPE] & SPDMEM_DDR4_MODTYPE_MASK) {
        case SPDMEM_DDR4_SODIMM:
        case SPDMEM_DDR4_72B_SO_RDIMM:
        case SPDMEM_DDR4_72B_SO_UDIMM:
        case SPDMEM_DDR4_16B_SO_DIMM:
        case SPDMEM_DDR4_32B_SO_DIMM:
                printf(" SO-DIMM");
                break;
        case SPDMEM_DDR4_LRDIMM:
                printf(" LR-DIMM");
                break;
        case SPDMEM_DDR4_MINI_RDIMM:
        case SPDMEM_DDR4_MINI_UDIMM:
                printf(" Mini-DIMM");
                break;
        case SPDMEM_DDR4_LP_DIMM:
                printf(" LP-DIMM");
                break;
        case SPDMEM_DDR4_NON_DIMM:
                printf(" non-DIMM solution");
                break;
        }

        if (s->sm_data[SPDMEM_DDR4_THERMAL] & SPDMEM_DDR4_THERMAL_PRESENT)
                printf(" with thermal sensor");
}

int
spdmem_probe(struct spdmem_softc *sc)
{
        uint8_t i, val, type;
        int cksum = 0;
        int spd_len, spd_crc_cover;
        uint16_t crc_calc, crc_spd;

        type = spdmem_read(sc, 2);
        /* For older memory types, validate the checksum over 1st 63 bytes */
        if (type <= SPDMEM_MEMTYPE_DDR2SDRAM) {
                for (i = 0; i < 63; i++)
                        cksum += spdmem_read(sc, i);

                val = spdmem_read(sc, 63);

                if (cksum == 0 || (cksum & 0xff) != val) {
                        return 0;
                } else
                        return 1;
        }

        /* For DDR3 and FBDIMM, verify the CRC */
        else if (type <= SPDMEM_MEMTYPE_DDR3SDRAM) {
                spd_len = spdmem_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;
                }
calc_crc:
                if (spd_crc_cover > spd_len)
                        return 0;
                crc_calc = spdmem_crc16(sc, spd_crc_cover);
                crc_spd = spdmem_read(sc, 127) << 8;
                crc_spd |= spdmem_read(sc, 126);
                if (crc_calc != crc_spd) {
                        return 0;
                }
                return 1;
        } else if (type <= SPDMEM_MEMTYPE_LPDDR4SDRAM) {
                spd_len = spdmem_read(sc, 0);
                spd_crc_cover = 125;
                switch (spd_len & SPDMEM_DDR4_SPDLEN_MASK) {
                case SPDMEM_DDR4_SPDLEN_128:
                        spd_len = 128;
                        break;
                case SPDMEM_DDR4_SPDLEN_256:
                        spd_len = 256;
                        break;
                case SPDMEM_DDR4_SPDLEN_384:
                        spd_len = 384;
                        break;
                case SPDMEM_DDR4_SPDLEN_512:
                        spd_len = 512;
                        break;
                default:
                        return 0;
                }
                goto calc_crc;
        }

        return 0;
}

void
spdmem_attach_common(struct spdmem_softc *sc)
{
        struct spdmem *s = &(sc->sc_spd_data);
        int i;

        /* 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_FBDIMM:
                case SPDMEM_MEMTYPE_FBDIMM_PROBE:
                        spdmem_fbdimm_decode(sc, s);
                        break;
                case SPDMEM_MEMTYPE_DDR3SDRAM:
                        spdmem_ddr3_decode(sc, s);
                        break;
                case SPDMEM_MEMTYPE_DDR4SDRAM:
                case SPDMEM_MEMTYPE_DDR4ESDRAM:
                case SPDMEM_MEMTYPE_LPDDR3SDRAM:
                case SPDMEM_MEMTYPE_LPDDR4SDRAM:
                        spdmem_ddr4_decode(sc, s);
                        break;
                case SPDMEM_MEMTYPE_NONE:
                        printf(" no EEPROM found");
                        break;
                default:
                        if (s->sm_type <= SPDMEM_MEMTYPE_LPDDR5SDRAM)
                                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");
}