Patrick Rudolph (siro@das-labor.org) just uploaded a new patch set to gerrit, which you can find at https://review.coreboot.org/18273
-gerrit
commit 8cf1ac241194d94a2ef4bb0db175be3191a86cc1 Author: Patrick Rudolph siro@das-labor.org Date: Tue Jan 31 19:43:17 2017 +0100
device/dram/ddr2: Add common ddr2 spd decoder
Decode DDR2 SPD similar to DDR3 SPD decoder to ease readability, reduce code complexity and reduce size of maintainable code.
Change-Id: I741f0e61ab23e3999ae9e31f57228ba034c2509e Signed-off-by: Patrick Rudolph siro@das-labor.org --- src/device/dram/ddr2.c | 568 +++++++++++++++++++++++++++++++++++++++++ src/include/device/dram/ddr2.h | 201 +++++++++++++++ 2 files changed, 769 insertions(+)
diff --git a/src/device/dram/ddr2.c b/src/device/dram/ddr2.c new file mode 100644 index 0000000..4871fa0 --- /dev/null +++ b/src/device/dram/ddr2.c @@ -0,0 +1,568 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2017 Patrick Rudolph siro@das-labor.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/** + * @file ddr2.c + * + * \brief Utilities for decoding DDR2 SPDs + */ + +#include <console/console.h> +#include <device/device.h> +#include <device/dram/ddr2.h> +#include <string.h> + +/*============================================================================== + * = DDR2 SPD decoding helpers + *----------------------------------------------------------------------------*/ + +/** + * \brief Checks if the DIMM is Registered based on byte[20] of the SPD + * + * Tells if the DIMM type is registered or not. + * + * @param type DIMM type. This is byte[20] of the SPD. + */ +int dimm_is_registered(enum spd_dimm_type type) +{ + if ((type == SPD_DIMM_TYPE_RDIMM) + | (type == SPD_DIMM_TYPE_72B_SO_RDIMM)) + return 1; + + return 0; +} + +/** + * \brief Calculate the checksum of a DDR2 SPD unique identifier + * + * @param spd pointer to raw SPD data + * @param len length of data in SPD + * + * @return the checksum of SPD data bytes 63, or 0 when spd data is truncated. + */ +u8 spd_ddr2_calc_checksum(u8 *spd, int len) +{ + int i; + u8 c = 0; + + if (len < 63) + /* Not enough bytes available to get the checksum */ + return 0; + + for (i = 0; i < 63; i++) + c += spd[i]; + + return c; +} + +/** + * \brief Return size of SPD. + * + * Returns size of SPD. Usually 128 Byte. + */ +u32 spd_decode_spd_size_ddr2(u8 byte0) +{ + return byte0; +} + +/** + * \brief Return size of eeprom. + * + * Returns size of eeprom. Usually 256 Byte. + */ +u32 spd_decode_eeprom_size_ddr2(u8 byte1) +{ + if (!byte1) + return 0; + + if (byte1 > 0x0e) + return 0x3fff; + + return 1 << byte1; +} + +/** + * \brief Return index of MSB set + * + * Returns the index fof MSB set. + */ +static u8 spd_get_msbs(u8 c) +{ + int i; + for (i = 7; i >= 0; i--) + if (c & (1 << i)) + return i; + + return 0; +} + +/** + * \brief Decode SPD tck cycle time + * + * Decodes a raw SPD data from a DDR2 DIMM. + * Returns cycle time in 1/256th ns. + */ +static u32 spd_decode_tck_time(u8 c) +{ + u8 high, low; + + high = c >> 4; + + low = c & 0xf; + if (low == 0x0A) + low = 25; + else if (low == 0x0B) + low = 33; + else if (low == 0x0C) + low = 66; + else if (low == 0x0D) + low = 75; + + return (high << 8) + ((low << 8) / 10); +} + +/** + * \brief Decode SPD bcd style timings + * + * Decodes a raw SPD data from a DDR2 DIMM. + * Returns cycle time in 1/256th ns. + */ +static u32 spd_decode_bcd_time(u8 c) +{ + u8 high, low; + + high = c >> 4; + low = c & 0xf; + + return (high << 8) + ((low << 8) / 10); +} + +/** + * \brief Decode SPD tRP, tRRP cycle time + * + * Decodes a raw SPD data from a DDR2 DIMM. + * Returns cycle time in 1/256th ns. + */ +static u32 spd_decode_quarter_time(u8 c) +{ + u8 high, low; + + high = c >> 6; + + low = c & 0x3; + + if (low == 1) + low = 25; + else if (low == 2) + low = 50; + else if (low == 3) + low = 75; + + return (high << 8) + ((low << 8) / 10); +} + +/** + * \brief Decode SPD tRR time + * + * Decodes a raw SPD data from a DDR2 DIMM. + * Returns cycle time in 1/256th us. + */ +static u32 spd_decode_tRR_time(u8 c) +{ + switch (c) { + default: + case 0: + return 15625 << 8; + case 1: + return 15625 << 6; + case 2: + return 15625 << 7; + case 3: + return 15625 << 9; + case 4: + return 15625 << 10; + case 5: + return 15625 << 11; + } +} + +/** + * \brief Decode SPD tRP,tRFC time + * + * Decodes a raw SPD data from a DDR2 DIMM. + * Returns cycle time in 1/256th us. + */ +static void spd_decode_tRPtRFC_time(u8 *spd_40_41_42, u32 *tRC, u32 *tRFC) +{ + u8 b40, b41, b42; + + b40 = spd_40_41_42[0]; + b41 = spd_40_41_42[1]; + b42 = spd_40_41_42[2]; + + *tRC = b41 << 8; + *tRFC = b42 << 8; + + if (b40 & 0x01) + *tRFC += 256 << 8; + + switch ((b40 >> 1) & 0x07) { + case 1: + *tRFC += (25 << 8) / 100; + break; + case 2: + *tRFC += (33 << 8) / 100; + break; + case 3: + *tRFC += (50 << 8) / 100; + break; + case 4: + *tRFC += (66 << 8) / 100; + break; + case 5: + *tRFC += (75 << 8) / 100; + break; + default: + break; + } + + switch ((b40 >> 4) & 0x07) { + case 1: + *tRC += (25 << 8) / 100; + break; + case 2: + *tRC += (33 << 8) / 100; + break; + case 3: + *tRC += (50 << 8) / 100; + break; + case 4: + *tRC += (66 << 8) / 100; + break; + case 5: + *tRC += (75 << 8) / 100; + break; + default: + break; + } +} + +/** + * \brief Decode the raw SPD data + * + * Decodes a raw SPD data from a DDR2 DIMM, and organizes it into a + * @ref dimm_attr structure. The SPD data must first be read in a contiguous + * array, and passed to this function. + * + * @param dimm pointer to @ref dimm_attr structure where the decoded data is to + * be stored + * @param spd array of raw data previously read from the SPD. + * + * @return @ref spd_status enumerator + * SPD_STATUS_OK -- decoding was successful + * SPD_STATUS_INVALID -- invalid SPD or not a DDR2 SPD + * SPD_STATUS_CRC_ERROR -- CRC did not verify + * SPD_STATUS_INVALID_FIELD -- A field with an invalid value was + * detected. + */ +int spd_decode_ddr2(dimm_attr *dimm, spd_raw_data spd) +{ + int ret, i; + u16 eeprom_size; + u8 spd_size; + u8 cl; + u8 reg8; + + spd_size = spd_decode_spd_size_ddr2(spd[0]); + eeprom_size = spd_decode_eeprom_size_ddr2(spd[1]); + + printram("EEPROM with 0x%04x bytes\n", eeprom_size); + printram("SPD contains 0x%02x bytes\n", spd_size); + + if (spd_size < 64 || eeprom_size < 64) { + printram("ERROR: SPD to small\n"); + dimm->dram_type = SPD_MEMORY_TYPE_UNDEFINED; + return SPD_STATUS_INVALID; + } + + if (spd_ddr2_calc_checksum(spd, spd_size) != spd[63]) { + printram("ERROR: SPD checksum error\n"); + dimm->dram_type = SPD_MEMORY_TYPE_UNDEFINED; + return SPD_STATUS_INVALID; + } + + reg8 = spd[62]; + if ((reg8 & 0xf0) != 0x10) { + printram("ERROR: Unsupported SPD revision %01x.%01x\n", + reg8 >> 4, reg8 & 0xf); + dimm->dram_type = SPD_MEMORY_TYPE_UNDEFINED; + return SPD_STATUS_INVALID; + } + printram(" Revision : %01x.%01x\n", reg8 >> 4, reg8 & 0xf); + + reg8 = spd[2]; + if (reg8 != 0x08) { + printram("ERROR: Unsupported SPD type %x\n", reg8); + dimm->dram_type = SPD_MEMORY_TYPE_UNDEFINED; + return SPD_STATUS_INVALID; + } + printram(" Type : %x\n", reg8); + dimm->dram_type = SPD_MEMORY_TYPE_SDRAM_DDR2; + + dimm->row_bits = spd[3]; + if (dimm->row_bits > 31) { + printram(" Invalid number of memory rows\n"); + ret = SPD_STATUS_INVALID_FIELD; + } + printram(" Rows : %x\n", dimm->row_bits); + + dimm->col_bits = spd[4]; + if (dimm->col_bits > 16) { + printram(" Invalid number of memory columns\n"); + ret = SPD_STATUS_INVALID_FIELD; + } + printram(" Columns : %x\n", dimm->col_bits); + + dimm->ranks = spd[5] & 0x7; + printram(" Ranks : %x\n", dimm->ranks); + + dimm->mod_width = spd[6]; + printram(" Module width : x%u\n", dimm->mod_width); + + dimm->width = spd[13]; + printram(" Width : x%u\n", dimm->width); + + dimm->banks = spd[17]; + printram(" Banks : %x\n", dimm->banks); + + switch (spd[8]) { + case 0: + dimm->flags.operable_5_00V = 1; + printram(" Voltage : 5.0V\n"); + break; + case 1: + dimm->flags.operable_3_33V = 1; + printram(" Voltage : 3.3V\n"); + break; + case 2: + dimm->flags.operable_1_50V = 1; + printram(" Voltage : 1.5V\n"); + break; + case 3: + dimm->flags.operable_3_33V = 1; + printram(" Voltage : 3.3V\n"); + break; + case 4: + dimm->flags.operable_2_50V = 1; + printram(" Voltage : 2.5V\n"); + break; + case 5: + dimm->flags.operable_1_80V = 1; + printram(" Voltage : 1.8V\n"); + break; + default: + printram(" Unknown voltage level.\n"); + ret = SPD_STATUS_INVALID_FIELD; + } + + dimm->cas_supported = spd[18]; + if (dimm->cas_supported & 0x3) { + printram(" Invalid CAS support advertised.\n"); + ret = SPD_STATUS_INVALID_FIELD; + } + printram(" Supported CAS : 0x%x\n", dimm->cas_supported); + + cl = spd_get_msbs(dimm->cas_supported); + + dimm->cycle_time[cl] = spd_decode_tck_time(spd[9]); + dimm->access_time[cl] = spd_decode_bcd_time(spd[10]); + + if (dimm->cas_supported & (1 << (cl - 1))) { + dimm->cycle_time[cl - 1] = spd_decode_tck_time(spd[23]); + dimm->access_time[cl - 1] = spd_decode_bcd_time(spd[24]); + } + if (dimm->cas_supported & (1 << (cl - 2))) { + dimm->cycle_time[cl - 2] = spd_decode_tck_time(spd[25]); + dimm->access_time[cl - 2] = spd_decode_bcd_time(spd[26]); + } + + for (i = 2; i < 8; i++) { + printram(" Cycle time at CL%d : %u\n", i, dimm->cycle_time[i]); + printram(" Accesstime at CL%d : %u\n", i, dimm->access_time[i]); + } + + reg8 = (spd[31] >> 5) | (spd[31] << 3); + dimm->size_mb = 64 << reg8; + + if (reg8 < 0x08) + printram(" Capacity : %u Mb\n", 64 << reg8); + else + printram(" Capacity : %u Gb\n", 1 << (reg8 - 8)); + + + /* SDRAM Minimum Cycle Time (tCKmin) */ + dimm->tCK = spd_decode_tck_time(spd[43]); + /* Minimum Write Recovery Time (tWRmin) */ + dimm->tWR = spd_decode_quarter_time(spd[36]); + /* Minimum RAS# to CAS# Delay Time (tRCDmin) */ + dimm->tRCD = spd_decode_quarter_time(spd[29]); + /* Minimum Row Active to Row Active Delay Time (tRRDmin) */ + dimm->tRRD = spd_decode_quarter_time(spd[28]); + /* Minimum Row Precharge Delay Time (tRPmin) */ + dimm->tRP = spd_decode_quarter_time(spd[27]); + /* Minimum Active to Precharge Delay Time (tRASmin) */ + dimm->tRAS = spd[30] << 8; + /* Minimum Active to Active/Refresh Delay Time (tRCmin) */ + /* Minimum Refresh Recovery Delay Time (tRFCmin) */ + spd_decode_tRPtRFC_time(&spd[40], &dimm->tRC, &dimm->tRFC); + /* Minimum Internal Write to Read Command Delay Time (tWTRmin) */ + dimm->tWTR = spd_decode_quarter_time(spd[37]); + /* Minimum Internal Read to Precharge Command Delay Time (tRTPmin) */ + dimm->tRTP = spd_decode_quarter_time(spd[38]); + /* Data Input Setup Time Before Strobe */ + dimm->tDS = spd_decode_bcd_time(spd[34]); + /* Data Input Hold Time After Strobe */ + dimm->tDH = spd_decode_bcd_time(spd[35]); + /* SDRAM Device DQS-DQ Skew for DQS and associated DQ signals */ + dimm->tDQSQ = spd[44]; + /* SDRAM Device Read Data Hold Skew Factor */ + dimm->tQHS = spd[45]; + /* PLL Relock Time */ + dimm->tPLL = spd[46] << 8; + /* Refresh rate */ + dimm->tRR = spd_decode_tRR_time(spd[12]); + + /* SDRAM Thermal and Refresh Options */ + printram(" General features :"); + if (spd[22] & 0x04) { + dimm->flags.pasr = 1; + printram(" PASR"); + } + if (spd[22] & 0x02) { + dimm->flags.terminate_50ohms = 1; + printram(" 50Ohm"); + } + if (spd[22] & 0x01) { + dimm->flags.weak_driver = 1; + printram(" WEAK DRIVER"); + } + printram("\n"); + + /* SDRAM Supported Burst length */ + printram(" Burst length :"); + if (spd[16] & 0x06) { + dimm->flags.bl8 = 1; + printram(" BL8"); + } + if (spd[22] & 0x04) { + dimm->flags.bl4 = 1; + printram(" BL4"); + } + printram("\n"); + + dimm->dimm_type = spd[20] & SPD_DIMM_TYPE_MASK; + printram(" Dimm type : %x\n", dimm->dimm_type); + + dimm->flags.is_ecc = !!(spd[11] & 0x3); + printram(" ECC support : %x\n", dimm->flags.is_ecc); + + if (spd_size > 71) { + memcpy(&dimm->manufacturer_id, &spd[64], 4); + printram(" Manufacturer ID : %x\n", dimm->manufacturer_id); + } + + if (spd_size > 90) { + dimm->part_number[16] = 0; + memcpy(&dimm->part_number, &spd[73], 16); + printram(" Part number : %s\n", dimm->part_number); + } + + return ret; +} + +/* + * The information printed below has a more informational character, and is not + * necessarily tied in to RAM init debugging. Hence, we stop using printram(), + * and use the standard printk()'s below. + */ + +static void print_ns(const char *msg, u32 val) +{ + u32 mant, fp; + mant = val / 256; + fp = (val % 256) * 1000 / 256; + + printk(BIOS_INFO, "%s%3u.%.3u ns\n", msg, mant, fp); +} + +static void print_us(const char *msg, u32 val) +{ + u32 mant, fp; + mant = val / 256; + fp = (val % 256) * 1000 / 256; + + printk(BIOS_INFO, "%s%3u.%.3u us\n", msg, mant, fp); +} + +/** +* \brief Print the info in DIMM +* +* Print info about the DIMM. Useful to use when CONFIG_DEBUG_RAM_SETUP is +* selected, or for a purely informative output. +* +* @param dimm pointer to already decoded @ref dimm_attr structure +*/ +void dram_print_spd_ddr2(const dimm_attr *dimm) +{ + u8 val8; + int i; + + printk(BIOS_INFO, " Row addr bits : %u\n", dimm->row_bits); + printk(BIOS_INFO, " Column addr bits : %u\n", dimm->col_bits); + printk(BIOS_INFO, " Number of ranks : %u\n", dimm->ranks); + printk(BIOS_INFO, " DIMM Capacity : %u MB\n", dimm->size_mb); + printk(BIOS_INFO, " Width : x%u\n", dimm->width); + printk(BIOS_INFO, " Banks : %u\n", dimm->banks); + + /* CAS Latencies Supported */ + val8 = dimm->cas_supported; + printk(BIOS_INFO, " CAS latencies :"); + i = 2; + do { + if (val8 & 1) + printk(BIOS_INFO, " %u", i); + i++; + val8 >>= 1; + } while (val8); + printk(BIOS_INFO, "\n"); + + print_ns(" tCKmin : ", dimm->tCK); + print_ns(" tWRmin : ", dimm->tWR); + print_ns(" tRCDmin : ", dimm->tRCD); + print_ns(" tRRDmin : ", dimm->tRRD); + print_ns(" tRPmin : ", dimm->tRP); + print_ns(" tRASmin : ", dimm->tRAS); + print_ns(" tRCmin : ", dimm->tRC); + print_ns(" tRFCmin : ", dimm->tRFC); + print_ns(" tWTRmin : ", dimm->tWTR); + print_ns(" tRTPmin : ", dimm->tRTP); + print_ns(" tDS : ", dimm->tDS); + print_ns(" tDH : ", dimm->tDH); + print_ns(" tDQSQ : ", dimm->tDQSQ); + print_ns(" tQHS : ", dimm->tQHS); + print_ns(" tPLL : ", dimm->tPLL); + print_us(" tRR : ", dimm->tRR); +} diff --git a/src/include/device/dram/ddr2.h b/src/include/device/dram/ddr2.h new file mode 100644 index 0000000..6a20c12 --- /dev/null +++ b/src/include/device/dram/ddr2.h @@ -0,0 +1,201 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2017 Patrick Rudolph siro@das-labor.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * JEDEC Standard No. 21-C + * Annex J: Annex J: Serial Presence Detects for DDR2 SDRAM (Revision 1.3) + */ + +#ifndef DEVICE_DRAM_DDR2L_H +#define DEVICE_DRAM_DDR2L_H + +/** + * @file ddr2.h + * + * \brief Utilities for decoding DDR2 SPDs + */ + +#include <stdint.h> +#include <spd.h> + +/** + * \brief Convenience definitions for TCK values + * + * Different values for tCK, representing standard DDR2 frequencies. + * These values are in 1/256 ns units. + * @{ + */ +#define TCK_800MHZ 320 +#define TCK_700MHZ 365 +#define TCK_666MHZ 384 +#define TCK_533MHZ 480 +#define TCK_400MHZ 640 +#define TCK_333MHZ 768 +#define TCK_266MHZ 960 +#define TCK_200MHZ 1280 +/** @} */ + +/** + * \brief Convenience macro for enabling printk with CONFIG_DEBUG_RAM_SETUP + * + * Use this macro instead of printk(); for verbose RAM initialization messages. + * When CONFIG_DEBUG_RAM_SETUP is not selected, these messages are automatically + * disabled. + * @{ + */ +#if IS_ENABLED(CONFIG_DEBUG_RAM_SETUP) +#define printram(x, ...) printk(BIOS_DEBUG, x, ##__VA_ARGS__) +#else +#define printram(x, ...) +#endif +/** @} */ + +/* + * Module type (byte 20, bits 5:0) of SPD + * This definition is specific to DDR2. DDR3 SPDs have a different structure. + */ +enum spd_dimm_type { + SPD_DIMM_TYPE_UNDEFINED = 0x00, + SPD_DIMM_TYPE_RDIMM = 0x01, + SPD_DIMM_TYPE_UDIMM = 0x02, + SPD_DIMM_TYPE_SO_DIMM = 0x04, + SPD_DIMM_TYPE_72B_SO_CDIMM = 0x06, + SPD_DIMM_TYPE_72B_SO_RDIMM = 0x07, + SPD_DIMM_TYPE_MICRO_DIMM = 0x08, + SPD_DIMM_TYPE_MINI_DIMM = 0x10, + SPD_DIMM_TYPE_MINI_UDIMM = 0x20, + /* Masks to bits 5:0 to give the dimm type */ + SPD_DIMM_TYPE_MASK = 0x3f, +}; + +/** + * \brief DIMM flags + * + * Characteristic flags for the DIMM, as presented by the SPD + */ +typedef union dimm_flags_st { + /* The whole point of the union/struct construct is to allow us to clear + * all the bits with one line: flags.raw = 0. + * We do not care how these bits are ordered */ + struct { + /* Module can work at 5.00V */ + unsigned operable_5_00V:1; + /* Module can work at 3.33V */ + unsigned operable_3_33V:1; + /* Module can work at 2.50V */ + unsigned operable_2_50V:1; + /* Module can work at 1.80V - All DIMMS must be 1.8V operable */ + unsigned operable_1_80V:1; + /* Module can work at 1.50V */ + unsigned operable_1_50V:1; + /* Module can work at 1.35V */ + unsigned operable_1_35V:1; + /* Module can work at 1.20V */ + unsigned operable_1_25V:1; + /* Has an 8-bit bus extension, meaning the DIMM supports ECC */ + unsigned is_ecc:1; + /* Supports weak driver */ + unsigned weak_driver:1; + /* Supports terminating at 50 Ohm */ + unsigned terminate_50ohms:1; + /* Partial Array Self Refresh */ + unsigned pasr:1; + /* Supports burst length 8 */ + unsigned bl8:1; + /* Supports burst length 4 */ + unsigned bl4:1; + }; + unsigned int raw; +} dimm_flags_t; + +/** + * \brief DIMM characteristics + * + * The characteristics of each DIMM, as presented by the SPD + */ +typedef struct dimm_attr_st { + enum spd_memory_type dram_type; + enum spd_dimm_type dimm_type; + u8 cas_supported; + + /* Cycle times for various CAS */ + u32 cycle_time[8]; + /* Maximum data access times for various CAS */ + u32 access_time[8]; + /* Flags extracted from SPD */ + dimm_flags_t flags; + /* Number of banks */ + u8 banks; + /* SDRAM width */ + u8 width; + /* Module width */ + u8 mod_width; + /* Number of ranks */ + u8 ranks; + /* Number or row address bits */ + u8 row_bits; + /* Number or column address bits */ + u8 col_bits; + /* Size of module in MiB */ + u32 size_mb; + /* Latencies are in units of 1/256 ns */ + u32 tCK; + u32 tWR; + u32 tRCD; + u32 tRRD; + u32 tRP; + u32 tRAS; + u32 tIS; + u32 tIH; + u32 tDS; + u32 tDH; + + u32 tRC; + u32 tRFC; + u32 tWTR; + u32 tRTP; + u32 tDQSQ; + u32 tQHS; + u32 tPLL; + + /* Latencies are in units of 1/256 us */ + u32 tRR; + + /* Manufacturer ID */ + u32 manufacturer_id; // + /* ASCII part number - NULL terminated */ + u8 part_number[17]; +} dimm_attr; + +/** Result of the SPD decoding process */ +enum spd_status { + SPD_STATUS_OK = 0, + SPD_STATUS_INVALID, + SPD_STATUS_CRC_ERROR, + SPD_STATUS_INVALID_FIELD, +}; + +typedef u8 spd_raw_data[128]; + +int dimm_is_registered(enum spd_dimm_type type); +u8 spd_ddr2_calc_checksum(u8 *spd, int len); +u32 spd_decode_spd_size_ddr2(u8 byte0); +u32 spd_decode_eeprom_size_ddr2(u8 byte1); +int spd_decode_ddr2(dimm_attr *dimm, spd_raw_data spd); +void dram_print_spd_ddr2(const dimm_attr *dimm); + + +#endif /* DEVICE_DRAM_DDR2L_H */