aboutsummaryrefslogtreecommitdiff
path: root/src/vendorcode/cavium/bdk/libdram/dram-spd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/vendorcode/cavium/bdk/libdram/dram-spd.c')
-rw-r--r--src/vendorcode/cavium/bdk/libdram/dram-spd.c583
1 files changed, 583 insertions, 0 deletions
diff --git a/src/vendorcode/cavium/bdk/libdram/dram-spd.c b/src/vendorcode/cavium/bdk/libdram/dram-spd.c
new file mode 100644
index 0000000000..3717ca1109
--- /dev/null
+++ b/src/vendorcode/cavium/bdk/libdram/dram-spd.c
@@ -0,0 +1,583 @@
+/***********************license start***********************************
+* Copyright (c) 2003-2017 Cavium Inc. (support@cavium.com). All rights
+* reserved.
+*
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are
+* met:
+*
+* * Redistributions of source code must retain the above copyright
+* notice, this list of conditions and the following disclaimer.
+*
+* * 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.
+*
+* * Neither the name of Cavium Inc. nor the names of
+* its contributors may be used to endorse or promote products
+* derived from this software without specific prior written
+* permission.
+*
+* This Software, including technical data, may be subject to U.S. export
+* control laws, including the U.S. Export Administration Act and its
+* associated regulations, and may be subject to export or import
+* regulations in other countries.
+*
+* TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS"
+* AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR
+* WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT
+* TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY
+* REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT
+* DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES
+* OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR
+* PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT,
+* QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK
+* ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU.
+***********************license end**************************************/
+#include <bdk.h>
+#include <ctype.h>
+#include "dram-internal.h"
+
+/**
+ * Read the entire contents of a DIMM SPD and store it in the device tree. The
+ * current DRAM config is also updated, so future SPD accesses used the cached
+ * copy.
+ *
+ * @param node Node the DRAM config is for
+ * @param cfg Current DRAM config. Updated with SPD data
+ * @param lmc LMC to read DIMM for
+ * @param dimm DIMM slot for SPD to read
+ *
+ * @return Zero on success, negative on failure
+ */
+int read_entire_spd(bdk_node_t node, dram_config_t *cfg, int lmc, int dimm)
+{
+ /* If pointer to data is provided, use it, otherwise read from SPD over twsi */
+ if (cfg->config[lmc].dimm_config_table[dimm].spd_ptr)
+ return 0;
+ if (!cfg->config[lmc].dimm_config_table[dimm].spd_addr)
+ return -1;
+
+ /* Figure out how to access the SPD */
+ int spd_addr = cfg->config[lmc].dimm_config_table[dimm].spd_addr;
+ int bus = spd_addr >> 12;
+ int address = spd_addr & 0x7f;
+
+ /* Figure out the size we will read */
+ int64_t dev_type = bdk_twsix_read_ia(node, bus, address, DDR4_SPD_KEY_BYTE_DEVICE_TYPE, 1, 1);
+ if (dev_type < 0)
+ return -1; /* No DIMM */
+ int spd_size = (dev_type == 0x0c) ? 512 : 256;
+
+ /* Allocate storage */
+ uint32_t *spd_buf = malloc(spd_size);
+ if (!spd_buf)
+ return -1;
+ uint32_t *ptr = spd_buf;
+
+ for (int bank = 0; bank < (spd_size >> 8); bank++)
+ {
+ /* this should only happen for DDR4, which has a second bank of 256 bytes */
+ if (bank)
+ bdk_twsix_write_ia(node, bus, 0x36 | bank, 0, 2, 1, 0);
+ int bank_size = 256;
+ for (int i = 0; i < bank_size; i += 4)
+ {
+ int64_t data = bdk_twsix_read_ia(node, bus, address, i, 4, 1);
+ if (data < 0)
+ {
+ free(spd_buf);
+ bdk_error("Failed to read SPD data at 0x%x\n", i + (bank << 8));
+ /* Restore the bank to zero */
+ if (bank)
+ bdk_twsix_write_ia(node, bus, 0x36 | 0, 0, 2, 1, 0);
+ return -1;
+ }
+ else
+ *ptr++ = bdk_be32_to_cpu(data);
+ }
+ /* Restore the bank to zero */
+ if (bank)
+ bdk_twsix_write_ia(node, bus, 0x36 | 0, 0, 2, 1, 0);
+ }
+
+ /* Store the SPD in the device tree */
+ bdk_config_set_blob(spd_size, spd_buf, BDK_CONFIG_DDR_SPD_DATA, dimm, lmc, node);
+ cfg->config[lmc].dimm_config_table[dimm].spd_ptr = (void*)spd_buf;
+
+ return 0;
+}
+
+/* Read an DIMM SPD value, either using TWSI to read it from the DIMM, or
+ * from a provided array.
+ */
+int read_spd(bdk_node_t node, const dimm_config_t *dimm_config, int spd_field)
+{
+ /* If pointer to data is provided, use it, otherwise read from SPD over twsi */
+ if (dimm_config->spd_ptr)
+ return dimm_config->spd_ptr[spd_field];
+ else if (dimm_config->spd_addr)
+ {
+ int data;
+ int bus = dimm_config->spd_addr >> 12;
+ int address = dimm_config->spd_addr & 0x7f;
+
+ /* this should only happen for DDR4, which has a second bank of 256 bytes */
+ int bank = (spd_field >> 8) & 1;
+ if (bank) {
+ bdk_twsix_write_ia(node, bus, 0x36 | bank, 0, 2, 1, 0);
+ spd_field %= 256;
+ }
+
+ data = bdk_twsix_read_ia(node, bus, address, spd_field, 1, 1);
+
+ /* Restore the bank to zero */
+ if (bank) {
+ bdk_twsix_write_ia(node, bus, 0x36 | 0, 0, 2, 1, 0);
+ }
+
+ return data;
+ }
+ else
+ return -1;
+}
+
+static uint16_t ddr3_crc16(uint8_t *ptr, int count)
+{
+ /* From DDR3 spd specification */
+ int crc, i;
+ crc = 0;
+ while (--count >= 0)
+ {
+ crc = crc ^ (int)*ptr++ << 8;
+ for (i = 0; i < 8; ++i)
+ if (crc & 0x8000)
+ crc = crc << 1 ^ 0x1021;
+ else
+ crc = crc << 1;
+ }
+ return crc & 0xFFFF;
+}
+
+static int validate_spd_checksum_ddr3(bdk_node_t node, int twsi_addr, int silent)
+{
+ uint8_t spd_data[128];
+ int crc_bytes = 126;
+ uint16_t crc_comp;
+ int i;
+ int rv;
+ int ret = 1;
+ for (i = 0; i < 128; i++)
+ {
+ rv = bdk_twsix_read_ia(node, twsi_addr >> 12, twsi_addr & 0x7f, i, 1, 1);
+ if (rv < 0)
+ return 0; /* TWSI read error */
+ spd_data[i] = (uint8_t)rv;
+ }
+ /* Check byte 0 to see how many bytes checksum is over */
+ if (spd_data[0] & 0x80)
+ crc_bytes = 117;
+
+ crc_comp = ddr3_crc16(spd_data, crc_bytes);
+
+ if (spd_data[DDR3_SPD_CYCLICAL_REDUNDANCY_CODE_LOWER_NIBBLE] != (crc_comp & 0xff) ||
+ spd_data[DDR3_SPD_CYCLICAL_REDUNDANCY_CODE_UPPER_NIBBLE] != (crc_comp >> 8))
+ {
+ if (!silent) {
+ printf("DDR3 SPD CRC error, spd addr: 0x%x, calculated crc: 0x%04x, read crc: 0x%02x%02x\n",
+ twsi_addr, crc_comp,
+ spd_data[DDR3_SPD_CYCLICAL_REDUNDANCY_CODE_UPPER_NIBBLE],
+ spd_data[DDR3_SPD_CYCLICAL_REDUNDANCY_CODE_LOWER_NIBBLE]);
+ }
+ ret = 0;
+ }
+ return ret;
+}
+
+static int validate_spd_checksum(bdk_node_t node, int twsi_addr, int silent)
+{
+ int rv;
+
+ debug_print("Validating DIMM at address 0x%x\n", twsi_addr);
+
+ if (!twsi_addr) return 1; /* return OK if we are not doing real DIMMs */
+
+ /* Look up module type to determine if DDR3 or DDR4 */
+ rv = bdk_twsix_read_ia(node, twsi_addr >> 12, twsi_addr & 0x7f, 2, 1, 1);
+
+ if (rv >= 0xB && rv <= 0xC) /* this is DDR3 or DDR4, do same */
+ return validate_spd_checksum_ddr3(node, twsi_addr, silent);
+
+ if (!silent)
+ printf("Unrecognized DIMM type: 0x%x at spd address: 0x%x\n",
+ rv, twsi_addr);
+
+ return 0;
+}
+
+
+int validate_dimm(bdk_node_t node, const dimm_config_t *dimm_config)
+{
+ int spd_addr;
+
+ spd_addr = dimm_config->spd_addr;
+
+ debug_print("Validating dimm spd addr: 0x%02x spd ptr: %x\n",
+ spd_addr, dimm_config->spd_ptr);
+
+ // if the slot is not possible
+ if (!spd_addr && !dimm_config->spd_ptr)
+ return -1;
+
+ {
+ int val0, val1;
+ int ddr_type = get_ddr_type(node, dimm_config);
+
+ switch (ddr_type)
+ {
+ case DDR3_DRAM: /* DDR3 */
+ case DDR4_DRAM: /* DDR4 */
+
+ debug_print("Validating DDR%d DIMM\n", ((dimm_type >> 2) & 3) + 1);
+
+#define DENSITY_BANKS DDR4_SPD_DENSITY_BANKS // same for DDR3 and DDR4
+#define ROW_COL_BITS DDR4_SPD_ADDRESSING_ROW_COL_BITS // same for DDR3 and DDR4
+
+ val0 = read_spd(node, dimm_config, DENSITY_BANKS);
+ val1 = read_spd(node, dimm_config, ROW_COL_BITS);
+ if (val0 < 0 && val1 < 0) {
+ debug_print("Error reading SPD for DIMM\n");
+ return 0; /* Failed to read dimm */
+ }
+ if (val0 == 0xff && val1 == 0xff) {
+ ddr_print("Blank or unreadable SPD for DIMM\n");
+ return 0; /* Blank SPD or otherwise unreadable device */
+ }
+
+ /* Don't treat bad checksums as fatal. */
+ validate_spd_checksum(node, spd_addr, 0);
+ break;
+
+ case 0x00: /* Terminator detected. Fail silently. */
+ return 0;
+
+ default:
+ debug_print("Unknown DIMM type 0x%x for DIMM @ 0x%x\n",
+ dimm_type, dimm_config->spd_addr);
+ return 0; /* Failed to read dimm */
+ }
+ }
+
+ return 1;
+}
+
+int get_dimm_part_number(char *buffer, bdk_node_t node,
+ const dimm_config_t *dimm_config,
+ int ddr_type)
+{
+ int i;
+ int c;
+ int skipping = 1;
+ int strlen = 0;
+
+#define PART_LIMIT(t) (((t) == DDR4_DRAM) ? 19 : 18)
+#define PART_NUMBER(t) (((t) == DDR4_DRAM) ? DDR4_SPD_MODULE_PART_NUMBER : DDR3_SPD_MODULE_PART_NUMBER)
+
+ int limit = PART_LIMIT(ddr_type);
+ int offset = PART_NUMBER(ddr_type);
+
+ for (i = 0; i < limit; ++i) {
+
+ c = (read_spd(node, dimm_config, offset+i) & 0xff);
+ if (c == 0) // any null, we are done
+ break;
+
+ /* Skip leading spaces. */
+ if (skipping) {
+ if (isspace(c))
+ continue;
+ else
+ skipping = 0;
+ }
+
+ /* Put non-null non-leading-space-skipped char into buffer */
+ buffer[strlen] = c;
+ ++strlen;
+ }
+
+ if (strlen > 0) {
+ i = strlen - 1; // last char put into buf
+ while (i >= 0 && isspace((int)buffer[i])) { // still in buf and a space
+ --i;
+ --strlen;
+ }
+ }
+ buffer[strlen] = 0; /* Insure that the string is terminated */
+
+ return strlen;
+}
+
+uint32_t get_dimm_serial_number(bdk_node_t node, const dimm_config_t *dimm_config, int ddr_type)
+{
+ uint32_t serial_number = 0;
+ int offset;
+
+#define SERIAL_NUMBER(t) (((t) == DDR4_DRAM) ? DDR4_SPD_MODULE_SERIAL_NUMBER : DDR3_SPD_MODULE_SERIAL_NUMBER)
+
+ offset = SERIAL_NUMBER(ddr_type);
+
+ for (int i = 0, j = 24; i < 4; ++i, j -= 8) {
+ serial_number |= ((read_spd(node, dimm_config, offset + i) & 0xff) << j);
+ }
+
+ return serial_number;
+}
+
+static uint32_t get_dimm_checksum(bdk_node_t node, const dimm_config_t *dimm_config, int ddr_type)
+{
+ uint32_t spd_chksum;
+
+#define LOWER_NIBBLE(t) (((t) == DDR4_DRAM) ? DDR4_SPD_CYCLICAL_REDUNDANCY_CODE_LOWER_NIBBLE : DDR3_SPD_CYCLICAL_REDUNDANCY_CODE_LOWER_NIBBLE)
+#define UPPER_NIBBLE(t) (((t) == DDR4_DRAM) ? DDR4_SPD_CYCLICAL_REDUNDANCY_CODE_UPPER_NIBBLE : DDR3_SPD_CYCLICAL_REDUNDANCY_CODE_UPPER_NIBBLE)
+
+ spd_chksum = 0xff & read_spd(node, dimm_config, LOWER_NIBBLE(ddr_type));
+ spd_chksum |= ((0xff & read_spd(node, dimm_config, UPPER_NIBBLE(ddr_type))) << 8);
+
+ return spd_chksum;
+}
+
+static
+void report_common_dimm(bdk_node_t node, const dimm_config_t *dimm_config, int dimm,
+ const char **dimm_types, int ddr_type, char *volt_str,
+ int ddr_interface_num, int num_ranks, int dram_width, int dimm_size_mb)
+{
+ int spd_ecc;
+ unsigned spd_module_type;
+ uint32_t serial_number;
+ char part_number[21]; /* 20 bytes plus string terminator is big enough for either */
+ char *sn_str;
+
+ spd_module_type = get_dimm_module_type(node, dimm_config, ddr_type);
+ spd_ecc = get_dimm_ecc(node, dimm_config, ddr_type);
+
+ (void) get_dimm_part_number(part_number, node, dimm_config, ddr_type);
+
+ serial_number = get_dimm_serial_number(node, dimm_config, ddr_type);
+ if ((serial_number != 0) && (serial_number != 0xffffffff)) {
+ sn_str = "s/n";
+ } else {
+ serial_number = get_dimm_checksum(node, dimm_config, ddr_type);
+ sn_str = "chksum";
+ }
+
+ // FIXME: add output of DIMM rank/width, as in: 2Rx4, 1Rx8, etc
+ printf("N%d.LMC%d.DIMM%d: %d MB, DDR%d %s %dRx%d %s, p/n: %s, %s: %u, %s\n",
+ node, ddr_interface_num, dimm, dimm_size_mb, ddr_type,
+ dimm_types[spd_module_type], num_ranks, dram_width,
+ (spd_ecc ? "ECC" : "non-ECC"), part_number,
+ sn_str, serial_number, volt_str);
+}
+
+const char *ddr3_dimm_types[16] = {
+ /* 0000 */ "Undefined",
+ /* 0001 */ "RDIMM",
+ /* 0010 */ "UDIMM",
+ /* 0011 */ "SO-DIMM",
+ /* 0100 */ "Micro-DIMM",
+ /* 0101 */ "Mini-RDIMM",
+ /* 0110 */ "Mini-UDIMM",
+ /* 0111 */ "Mini-CDIMM",
+ /* 1000 */ "72b-SO-UDIMM",
+ /* 1001 */ "72b-SO-RDIMM",
+ /* 1010 */ "72b-SO-CDIMM"
+ /* 1011 */ "LRDIMM",
+ /* 1100 */ "16b-SO-DIMM",
+ /* 1101 */ "32b-SO-DIMM",
+ /* 1110 */ "Reserved",
+ /* 1111 */ "Reserved"
+};
+
+static
+void report_ddr3_dimm(bdk_node_t node, const dimm_config_t *dimm_config,
+ int dimm, int ddr_interface_num, int num_ranks,
+ int dram_width, int dimm_size_mb)
+{
+ int spd_voltage;
+ char *volt_str;
+
+ spd_voltage = read_spd(node, dimm_config, DDR3_SPD_NOMINAL_VOLTAGE);
+ if ((spd_voltage == 0) || (spd_voltage & 3))
+ volt_str = "1.5V";
+ if (spd_voltage & 2)
+ volt_str = "1.35V";
+ if (spd_voltage & 4)
+ volt_str = "1.2xV";
+
+ report_common_dimm(node, dimm_config, dimm, ddr3_dimm_types,
+ DDR3_DRAM, volt_str, ddr_interface_num,
+ num_ranks, dram_width, dimm_size_mb);
+}
+
+const char *ddr4_dimm_types[16] = {
+ /* 0000 */ "Extended",
+ /* 0001 */ "RDIMM",
+ /* 0010 */ "UDIMM",
+ /* 0011 */ "SO-DIMM",
+ /* 0100 */ "LRDIMM",
+ /* 0101 */ "Mini-RDIMM",
+ /* 0110 */ "Mini-UDIMM",
+ /* 0111 */ "Reserved",
+ /* 1000 */ "72b-SO-RDIMM",
+ /* 1001 */ "72b-SO-UDIMM",
+ /* 1010 */ "Reserved",
+ /* 1011 */ "Reserved",
+ /* 1100 */ "16b-SO-DIMM",
+ /* 1101 */ "32b-SO-DIMM",
+ /* 1110 */ "Reserved",
+ /* 1111 */ "Reserved"
+};
+
+static
+void report_ddr4_dimm(bdk_node_t node, const dimm_config_t *dimm_config,
+ int dimm, int ddr_interface_num, int num_ranks,
+ int dram_width, int dimm_size_mb)
+{
+ int spd_voltage;
+ char *volt_str;
+
+ spd_voltage = read_spd(node, dimm_config, DDR4_SPD_MODULE_NOMINAL_VOLTAGE);
+ if ((spd_voltage == 0x01) || (spd_voltage & 0x02))
+ volt_str = "1.2V";
+ if ((spd_voltage == 0x04) || (spd_voltage & 0x08))
+ volt_str = "TBD1 V";
+ if ((spd_voltage == 0x10) || (spd_voltage & 0x20))
+ volt_str = "TBD2 V";
+
+ report_common_dimm(node, dimm_config, dimm, ddr4_dimm_types,
+ DDR4_DRAM, volt_str, ddr_interface_num,
+ num_ranks, dram_width, dimm_size_mb);
+}
+
+void report_dimm(bdk_node_t node, const dimm_config_t *dimm_config,
+ int dimm, int ddr_interface_num, int num_ranks,
+ int dram_width, int dimm_size_mb)
+{
+ int ddr_type;
+
+ /* ddr_type only indicates DDR4 or DDR3 */
+ ddr_type = get_ddr_type(node, dimm_config);
+
+ if (ddr_type == DDR4_DRAM)
+ report_ddr4_dimm(node, dimm_config, dimm, ddr_interface_num,
+ num_ranks, dram_width, dimm_size_mb);
+ else
+ report_ddr3_dimm(node, dimm_config, dimm, ddr_interface_num,
+ num_ranks, dram_width, dimm_size_mb);
+}
+
+static int
+get_ddr4_spd_speed(bdk_node_t node, const dimm_config_t *dimm_config)
+{
+ int spdMTB = 125;
+ int spdFTB = 1;
+
+ int tCKAVGmin
+ = spdMTB * read_spd(node, dimm_config, DDR4_SPD_MINIMUM_CYCLE_TIME_TCKAVGMIN)
+ + spdFTB * (signed char) read_spd(node, dimm_config, DDR4_SPD_MIN_CYCLE_TIME_FINE_TCKAVGMIN);
+
+ return pretty_psecs_to_mts(tCKAVGmin);
+}
+
+static int
+get_ddr3_spd_speed(bdk_node_t node, const dimm_config_t *dimm_config)
+{
+ int spd_mtb_dividend = 0xff & read_spd(node, dimm_config, DDR3_SPD_MEDIUM_TIMEBASE_DIVIDEND);
+ int spd_mtb_divisor = 0xff & read_spd(node, dimm_config, DDR3_SPD_MEDIUM_TIMEBASE_DIVISOR);
+ int spd_tck_min = 0xff & read_spd(node, dimm_config, DDR3_SPD_MINIMUM_CYCLE_TIME_TCKMIN);
+
+ short ftb_Dividend = read_spd(node, dimm_config, DDR3_SPD_FINE_TIMEBASE_DIVIDEND_DIVISOR) >> 4;
+ short ftb_Divisor = read_spd(node, dimm_config, DDR3_SPD_FINE_TIMEBASE_DIVIDEND_DIVISOR) & 0xf;
+
+ ftb_Divisor = (ftb_Divisor == 0) ? 1 : ftb_Divisor; /* Make sure that it is not 0 */
+
+ int mtb_psec = spd_mtb_dividend * 1000 / spd_mtb_divisor;
+ int tCKmin = mtb_psec * spd_tck_min;
+ tCKmin += ftb_Dividend *
+ (signed char) read_spd(node, dimm_config, DDR3_SPD_MINIMUM_CYCLE_TIME_FINE_TCKMIN)
+ / ftb_Divisor;
+
+ return pretty_psecs_to_mts(tCKmin);
+}
+
+static int
+speed_bin_down(int speed)
+{
+ if (speed == 2133)
+ return 1866;
+ else if (speed == 1866)
+ return 1600;
+ else
+ return speed;
+}
+
+int
+dram_get_default_spd_speed(bdk_node_t node, const ddr_configuration_t *ddr_config)
+{
+ int lmc, dimm;
+ int speed, ret_speed = 0;
+ int ddr_type = get_ddr_type(node, &ddr_config[0].dimm_config_table[0]);
+ int dimm_speed[8], dimm_count = 0;
+ int dimms_per_lmc = 0;
+
+ for (lmc = 0; lmc < 4; lmc++) {
+ for (dimm = 0; dimm < DDR_CFG_T_MAX_DIMMS; dimm++) {
+ const dimm_config_t *dimm_config = &ddr_config[lmc].dimm_config_table[dimm];
+ if (/*dimm_config->spd_addr ||*/ dimm_config->spd_ptr)
+ {
+ speed = (ddr_type == DDR4_DRAM)
+ ? get_ddr4_spd_speed(node, dimm_config)
+ : get_ddr3_spd_speed(node, dimm_config);
+ //printf("N%d.LMC%d.DIMM%d: SPD speed %d\n", node, lmc, dimm, speed);
+ dimm_speed[dimm_count] = speed;
+ dimm_count++;
+ if (lmc == 0)
+ dimms_per_lmc++;
+ }
+ }
+ }
+
+ // all DIMMs must be same speed
+ speed = dimm_speed[0];
+ for (dimm = 1; dimm < dimm_count; dimm++) {
+ if (dimm_speed[dimm] != speed) {
+ ret_speed = -1;
+ goto finish_up;
+ }
+ }
+
+ // if 2400 or greater, use 2133
+ if (speed >= 2400)
+ speed = 2133;
+
+ // use next speed down if 2DPC...
+ if (dimms_per_lmc > 1)
+ speed = speed_bin_down(speed);
+
+ // Update the in memory config to match the automatically calculated speed
+ bdk_config_set_int(speed, BDK_CONFIG_DDR_SPEED, node);
+
+ // do filtering for our jittery PLL
+ if (speed == 2133)
+ speed = 2100;
+ else if (speed == 1866)
+ speed = 1880;
+
+ // OK, return what we have...
+ ret_speed = mts_to_hertz(speed);
+
+ finish_up:
+ //printf("N%d: Returning default SPD speed %d\n", node, ret_speed);
+ return ret_speed;
+}