| /* |
| * This file is part of the coreboot project. |
| * |
| * Copyright (C) 2017 Patrick Rudolph <siro@das-labor.org> |
| * Copyright (C) 2017 Arthur Heymans <arthur@aheymans.xyz> |
| * |
| * 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 <lib.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 spd_dimm_is_registered_ddr2(enum spd_dimm_type_ddr2 type) |
| { |
| if ((type == SPD_DDR2_DIMM_TYPE_RDIMM) |
| || (type == SPD_DDR2_DIMM_TYPE_72B_SO_RDIMM) |
| || (type == SPD_DDR2_DIMM_TYPE_MINI_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 MIN(byte0, SPD_SIZE_MAX_DDR2); |
| } |
| |
| /** |
| * \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. |
| */ |
| u8 spd_get_msbs(u8 c) |
| { |
| return log2(c); |
| } |
| |
| /** |
| * \brief Decode SPD tck cycle time |
| * |
| * Decodes a raw SPD data from a DDR2 DIMM. |
| * Returns cycle time in 1/256th ns. |
| */ |
| static int spd_decode_tck_time(u32 *tck, u8 c) |
| { |
| u8 high, low; |
| |
| high = c >> 4; |
| |
| switch (c & 0xf) { |
| case 0xa: |
| low = 25; |
| break; |
| case 0xb: |
| low = 33; |
| break; |
| case 0xc: |
| low = 66; |
| break; |
| case 0xd: |
| low = 75; |
| break; |
| case 0xe: |
| case 0xf: |
| printk(BIOS_WARNING, "Invalid tck setting. " |
| "lower nibble is 0x%x\n", c & 0xf); |
| return CB_ERR; |
| default: |
| low = (c & 0xf) * 10; |
| } |
| |
| *tck = ((high * 100 + low) << 8) / 100; |
| return CB_SUCCESS; |
| } |
| |
| /** |
| * \brief Decode SPD bcd style timings |
| * |
| * Decodes a raw SPD data from a DDR2 DIMM. |
| * Returns cycle time in 1/256th ns. |
| */ |
| static int spd_decode_bcd_time(u32 *bcd, u8 c) |
| { |
| u8 high, low; |
| |
| high = c >> 4; |
| low = c & 0xf; |
| if (high >= 10 || low >= 10) |
| return CB_ERR; |
| |
| *bcd = ((high * 10 + low) << 8) / 100; |
| return CB_SUCCESS; |
| } |
| |
| /** |
| * \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 >> 2; |
| low = 25 * (c & 0x3); |
| |
| return ((high * 100 + low) << 8) / 100; |
| } |
| |
| /** |
| * \brief Decode SPD tRR time |
| * |
| * Decodes a raw SPD data from a DDR2 DIMM. |
| * Returns cycle time in 1/256th us. |
| */ |
| static int spd_decode_tRR_time(u32 *tRR, u8 c) |
| { |
| switch (c & ~0x80) { |
| default: |
| printk(BIOS_WARNING, "Invalid tRR value 0x%x\n", c); |
| return CB_ERR; |
| case 0x0: |
| *tRR = 15625 << 8; |
| break; |
| case 0x1: |
| *tRR = 15625 << 6; |
| break; |
| case 0x2: |
| *tRR = 15625 << 7; |
| break; |
| case 0x3: |
| *tRR = 15625 << 9; |
| break; |
| case 0x4: |
| *tRR = 15625 << 10; |
| break; |
| case 0x5: |
| *tRR = 15625 << 11; |
| break; |
| } |
| return CB_SUCCESS; |
| } |
| |
| /** |
| * \brief Decode SPD tRC,tRFC time |
| * |
| * Decodes a raw SPD data from a DDR2 DIMM. |
| * Returns cycle time in 1/256th us. |
| */ |
| static void spd_decode_tRCtRFC_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 * 100; |
| *tRFC = b42 * 100; |
| |
| if (b40 & 0x01) |
| *tRFC += 256 * 100; |
| |
| switch ((b40 >> 1) & 0x07) { |
| case 1: |
| *tRFC += 25; |
| break; |
| case 2: |
| *tRFC += 33; |
| break; |
| case 3: |
| *tRFC += 50; |
| break; |
| case 4: |
| *tRFC += 66; |
| break; |
| case 5: |
| *tRFC += 75; |
| break; |
| default: |
| break; |
| } |
| |
| switch ((b40 >> 4) & 0x07) { |
| case 1: |
| *tRC += 25; |
| break; |
| case 2: |
| *tRC += 33; |
| break; |
| case 3: |
| *tRC += 50; |
| break; |
| case 4: |
| *tRC += 66; |
| break; |
| case 5: |
| *tRC += 75; |
| break; |
| default: |
| break; |
| } |
| |
| /* Convert to 1/256th us */ |
| *tRC = (*tRC << 8) / 100; |
| *tRFC = (*tRFC << 8) / 100; |
| } |
| |
| /** |
| * \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(struct dimm_attr_ddr2_st *dimm, u8 spd[SPD_SIZE_MAX_DDR2]) |
| { |
| u8 spd_size, cl, reg8; |
| u16 eeprom_size; |
| int ret = SPD_STATUS_OK; |
| |
| memset(dimm, 0, sizeof(*dimm)); |
| |
| 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) { |
| printk(BIOS_WARNING, "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]) { |
| printk(BIOS_WARNING, "ERROR: SPD checksum error\n"); |
| dimm->dram_type = SPD_MEMORY_TYPE_UNDEFINED; |
| return SPD_STATUS_CRC_ERROR; |
| } |
| dimm->checksum = spd[63]; |
| |
| reg8 = spd[62]; |
| if ((reg8 & 0xf0) != 0x10) { |
| printk(BIOS_WARNING, |
| "ERROR: Unsupported SPD revision %01x.%01x\n", |
| reg8 >> 4, reg8 & 0xf); |
| dimm->dram_type = SPD_MEMORY_TYPE_UNDEFINED; |
| return SPD_STATUS_INVALID; |
| } |
| dimm->rev = reg8; |
| printram(" Revision : %01x.%01x\n", dimm->rev >> 4, dimm->rev & 0xf); |
| |
| reg8 = spd[2]; |
| printram(" Type : 0x%02x\n", reg8); |
| if (reg8 != 0x08) { |
| printk(BIOS_WARNING, "ERROR: Unsupported SPD type %x\n", reg8); |
| dimm->dram_type = SPD_MEMORY_TYPE_UNDEFINED; |
| return SPD_STATUS_INVALID; |
| } |
| dimm->dram_type = SPD_MEMORY_TYPE_SDRAM_DDR2; |
| |
| dimm->row_bits = spd[3]; |
| printram(" Rows : %u\n", dimm->row_bits); |
| if ((dimm->row_bits > 31) || |
| ((dimm->row_bits > 15) && (dimm->rev < 0x13))) { |
| printk(BIOS_WARNING, |
| "SPD decode: invalid number of memory rows\n"); |
| ret = SPD_STATUS_INVALID_FIELD; |
| } |
| |
| dimm->col_bits = spd[4]; |
| printram(" Columns : %u\n", dimm->col_bits); |
| if (dimm->col_bits > 15) { |
| printk(BIOS_WARNING, |
| "SPD decode: invalid number of memory columns\n"); |
| ret = SPD_STATUS_INVALID_FIELD; |
| } |
| |
| dimm->ranks = (spd[5] & 0x7) + 1; |
| printram(" Ranks : %u\n", dimm->ranks); |
| |
| dimm->mod_width = spd[6]; |
| printram(" Module data width : x%u\n", dimm->mod_width); |
| if (!dimm->mod_width) { |
| printk(BIOS_WARNING, "SPD decode: invalid module data width\n"); |
| ret = SPD_STATUS_INVALID_FIELD; |
| } |
| |
| dimm->width = spd[13]; |
| printram(" SDRAM width : x%u\n", dimm->width); |
| if (!dimm->width) { |
| printk(BIOS_WARNING, "SPD decode: invalid SDRAM width\n"); |
| ret = SPD_STATUS_INVALID_FIELD; |
| } |
| |
| dimm->banks = spd[17]; |
| printram(" Banks : %u\n", dimm->banks); |
| if (!dimm->banks) { |
| printk(BIOS_WARNING, |
| "SPD decode: invalid module banks count\n"); |
| ret = SPD_STATUS_INVALID_FIELD; |
| } |
| |
| 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: |
| printk(BIOS_WARNING, "SPD decode: unknown voltage level.\n"); |
| ret = SPD_STATUS_INVALID_FIELD; |
| } |
| |
| dimm->cas_supported = spd[18]; |
| if ((dimm->cas_supported & 0x3) || !dimm->cas_supported) { |
| printk(BIOS_WARNING, |
| "SPD decode: invalid CAS support advertised.\n"); |
| ret = SPD_STATUS_INVALID_FIELD; |
| } |
| printram(" Supported CAS mask : 0x%x\n", dimm->cas_supported); |
| |
| if ((dimm->rev < 0x13) && (dimm->cas_supported & 0x80)) { |
| printk(BIOS_WARNING, |
| "SPD decode: invalid CAS support advertised.\n"); |
| ret = SPD_STATUS_INVALID_FIELD; |
| } |
| if ((dimm->rev < 0x12) && (dimm->cas_supported & 0x40)) { |
| printk(BIOS_WARNING, |
| "SPD decode: invalid CAS support advertised.\n"); |
| ret = SPD_STATUS_INVALID_FIELD; |
| } |
| |
| /* CL=X */ |
| cl = spd_get_msbs(dimm->cas_supported); |
| |
| /* SDRAM Cycle time at Maximum Supported CAS Latency (CL), CL=X */ |
| if (spd_decode_tck_time(&dimm->cycle_time[cl], spd[9]) != CB_SUCCESS) { |
| printk(BIOS_WARNING, |
| "SPD decode: invalid min tCL for CAS%d\n", cl); |
| ret = SPD_STATUS_INVALID_FIELD; |
| } |
| /* SDRAM Access from Clock */ |
| if (spd_decode_bcd_time(&dimm->access_time[cl], spd[10]) |
| != CB_SUCCESS) { |
| printk(BIOS_WARNING, |
| "SPD decode: invalid min tAC for CAS%d\n", cl); |
| ret = SPD_STATUS_INVALID_FIELD; |
| } |
| |
| if (dimm->cas_supported & (1 << (cl - 1))) { |
| /* Minimum Clock Cycle at CLX-1 */ |
| if (spd_decode_tck_time(&dimm->cycle_time[cl - 1], spd[23]) |
| != CB_SUCCESS) { |
| printk(BIOS_WARNING, |
| "SPD decode: invalid min tCL for CAS%d\n", |
| cl - 1); |
| ret = SPD_STATUS_INVALID_FIELD; |
| } |
| /* Maximum Data Access Time (tAC) from Clock at CLX-1 */ |
| if (spd_decode_bcd_time(&dimm->access_time[cl - 1], spd[24]) |
| != CB_SUCCESS) { |
| printk(BIOS_WARNING, |
| "SPD decode: invalid min tAC for CAS%d\n", |
| cl - 1); |
| ret = SPD_STATUS_INVALID_FIELD; |
| } |
| } |
| if (dimm->cas_supported & (1 << (cl - 2))) { |
| /* Minimum Clock Cycle at CLX-2 */ |
| if (spd_decode_tck_time(&dimm->cycle_time[cl - 2], spd[25]) |
| != CB_SUCCESS) { |
| printk(BIOS_WARNING, |
| "SPD decode: invalid min tCL for CAS%d\n", |
| cl - 2); |
| ret = SPD_STATUS_INVALID_FIELD; |
| } |
| /* Maximum Data Access Time (tAC) from Clock at CLX-2 */ |
| if (spd_decode_bcd_time(&dimm->access_time[cl - 2], spd[26]) |
| != CB_SUCCESS) { |
| printk(BIOS_WARNING, |
| "SPD decode: invalid min tAC for CAS%d\n", |
| cl - 2); |
| ret = SPD_STATUS_INVALID_FIELD; |
| } |
| } |
| |
| reg8 = (spd[31] >> 5) | (spd[31] << 3); |
| if (!reg8) { |
| printk(BIOS_WARNING, |
| "SPD decode: invalid rank density.\n"); |
| ret = SPD_STATUS_INVALID_FIELD; |
| } |
| |
| /* Rank density */ |
| dimm->ranksize_mb = 128 * reg8; |
| /* Module density */ |
| dimm->size_mb = dimm->ranksize_mb * dimm->ranks; |
| if (dimm->size_mb < 1024) |
| printram(" Capacity : %u MB\n", dimm->size_mb); |
| else |
| printram(" Capacity : %u GB\n", dimm->size_mb >> 10); |
| |
| /* SDRAM Maximum Cycle Time (tCKmax) */ |
| if (spd_decode_bcd_time(&dimm->tCK, spd[43]) != CB_SUCCESS) { |
| printk(BIOS_WARNING, "SPD decode: invalid Max tCK\n"); |
| ret = SPD_STATUS_INVALID_FIELD; |
| } |
| /* 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_tRCtRFC_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 */ |
| if (spd_decode_bcd_time(&dimm->tDS, spd[34]) != CB_SUCCESS) { |
| printk(BIOS_WARNING, "SPD decode: invalid tDS\n"); |
| ret = SPD_STATUS_INVALID_FIELD; |
| } |
| /* Data Input Hold Time After Strobe */ |
| if (spd_decode_bcd_time(&dimm->tDH, spd[35]) != CB_SUCCESS) { |
| printk(BIOS_WARNING, "SPD decode: invalid tDH\n"); |
| ret = SPD_STATUS_INVALID_FIELD; |
| } |
| /* SDRAM Device DQS-DQ Skew for DQS and associated DQ signals */ |
| dimm->tDQSQ = (spd[44] << 8) / 100; |
| /* SDRAM Device Maximum Read Data Hold Skew Factor */ |
| dimm->tQHS = (spd[45] << 8) / 100; |
| /* PLL Relock Time in us */ |
| dimm->tPLL = spd[46] << 8; |
| /* Refresh rate in us */ |
| if (spd_decode_tRR_time(&dimm->tRR, spd[12]) != CB_SUCCESS) |
| ret = SPD_STATUS_INVALID_FIELD; |
| dimm->flags.self_refresh = (spd[12] >> 7) & 1; |
| printram("The assembly supports self refresh: %s\n", |
| dimm->flags.self_refresh ? "true" : "false"); |
| |
| /* Number of PLLs on DIMM */ |
| if (dimm->rev >= 0x11) |
| dimm->plls = (spd[21] >> 2) & 0x3; |
| |
| /* SDRAM Thermal and Refresh Options */ |
| printram(" General features :"); |
| if ((dimm->rev >= 0x12) && (spd[22] & 0x04)) { |
| dimm->flags.pasr = 1; |
| printram(" PASR"); |
| } |
| if ((dimm->rev >= 0x12) && (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_DDR2_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); |
| |
| dimm->flags.stacked = !!(spd[5] & 0x10); |
| printram(" Package : %s\n", |
| dimm->flags.stacked ? "stack" : "planar"); |
| |
| 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); |
| } |
| |
| if (spd_size > 94) { |
| dimm->year = spd[93] + 2000; |
| dimm->weeks = spd[94]; |
| printram(" Date : %d week %d\n", dimm->year, dimm->weeks); |
| } |
| |
| if (spd_size > 98) { |
| memcpy(&dimm->serial, &spd[95], 4); |
| printram(" Serial number : 0x%08x\n", dimm->serial); |
| } |
| 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 struct dimm_attr_ddr2_st *dimm) |
| { |
| char buf[32]; |
| 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 */ |
| printk(BIOS_INFO, " CAS latencies :"); |
| for (i = 2; i < 8; i++) { |
| if (dimm->cas_supported & (1 << i)) |
| printk(BIOS_INFO, " %u", i); |
| } |
| printk(BIOS_INFO, "\n"); |
| |
| for (i = 2; i < 8; i++) { |
| if (!(dimm->cas_supported & (1 << i))) |
| continue; |
| |
| strcpy(buf, " tCK at CLx : "); |
| /* Simple snprintf replacement */ |
| buf[11] = '0' + i; |
| print_ns(buf, dimm->cycle_time[i]); |
| |
| strcpy(buf, " tAC at CLx : "); |
| /* Simple snprintf replacement */ |
| buf[11] = '0' + i; |
| print_ns(buf, dimm->access_time[i]); |
| } |
| print_ns(" tCKmax : ", 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(" tDQSQmax : ", dimm->tDQSQ); |
| print_ns(" tQHSmax : ", dimm->tQHS); |
| print_us(" tPLL : ", dimm->tPLL); |
| print_us(" tRR : ", dimm->tRR); |
| } |
| |
| void normalize_tck(u32 *tclk) |
| { |
| if (*tclk <= TCK_800MHZ) { |
| *tclk = TCK_800MHZ; |
| } else if (*tclk <= TCK_666MHZ) { |
| *tclk = TCK_666MHZ; |
| } else if (*tclk <= TCK_533MHZ) { |
| *tclk = TCK_533MHZ; |
| } else if (*tclk <= TCK_400MHZ) { |
| *tclk = TCK_400MHZ; |
| } else if (*tclk <= TCK_333MHZ) { |
| *tclk = TCK_333MHZ; |
| } else if (*tclk <= TCK_266MHZ) { |
| *tclk = TCK_266MHZ; |
| } else if (*tclk <= TCK_200MHZ) { |
| *tclk = TCK_200MHZ; |
| } else { |
| *tclk = 0; |
| printk(BIOS_ERR, "Too slow common tCLK found\n"); |
| } |
| } |