/*	$OpenBSD: spdmem.c,v 1.1 2010/03/22 21:20:56 miod 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_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_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

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

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 = 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_fbdimm_decode(struct spdmem_softc *sc, struct spdmem *s)
{
	int dimm_size, num_banks, cycle_time, d_clk, p_clk, bits;
	uint8_t rows, cols, banks, dividend, divisor;
	/*
	 * FB-DIMM is very much like DDR3
	 */

	banks = s->sm_data[SPDMEM_FBDIMM_ADDR] & SPDMEM_FBDIMM_ADDR_BANKS;
	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;
	num_banks = 1 << (banks + 2);

	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;
		d_clk = ((d_clk + cycle_time / 2) ) / 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");
}

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

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