diff --git a/src/northbridge/intel/haswell/native_raminit/Makefile.mk b/src/northbridge/intel/haswell/native_raminit/Makefile.mk index c125d84f0b..2769e0bbb4 100644 --- a/src/northbridge/intel/haswell/native_raminit/Makefile.mk +++ b/src/northbridge/intel/haswell/native_raminit/Makefile.mk @@ -1,5 +1,6 @@ ## SPDX-License-Identifier: GPL-2.0-or-later +romstage-y += lookup_timings.c romstage-y += init_mpll.c romstage-y += io_comp_control.c romstage-y += raminit_main.c diff --git a/src/northbridge/intel/haswell/native_raminit/lookup_timings.c b/src/northbridge/intel/haswell/native_raminit/lookup_timings.c new file mode 100644 index 0000000000..8b81c7c341 --- /dev/null +++ b/src/northbridge/intel/haswell/native_raminit/lookup_timings.c @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include +#include + +#include "raminit_native.h" + +struct timing_lookup { + uint32_t clock; + uint32_t value; +}; + +static uint32_t lookup_timing( + const uint32_t mem_clock_mhz, + const struct timing_lookup *const lookup, + const size_t length) +{ + /* Fall back to the last index */ + size_t i; + for (i = 0; i < length - 1; i++) { + /* Account for imprecise frequency values */ + if ((mem_clock_mhz - 5) <= lookup[i].clock) + break; + } + return lookup[i].value; +} + +static const uint32_t fmax = UINT32_MAX; + +uint8_t get_tCWL(const uint32_t mem_clock_mhz) +{ + const struct timing_lookup lut[] = { + { 400, 5 }, + { 533, 6 }, + { 666, 7 }, + { 800, 8 }, + { 933, 9 }, + { 1066, 10 }, + { 1200, 11 }, + { fmax, 12 }, + }; + return lookup_timing(mem_clock_mhz, lut, ARRAY_SIZE(lut)); +} + +/* tREFI = 7800 ns * DDR MHz */ +uint32_t get_tREFI(const uint32_t mem_clock_mhz) +{ + return (mem_clock_mhz * 7800) / 1000; +} + +uint32_t get_tXP(const uint32_t mem_clock_mhz) +{ + const struct timing_lookup lut[] = { + { 400, 3 }, + { 666, 4 }, + { 800, 5 }, + { 933, 6 }, + { 1066, 7 }, + { fmax, 8 }, + }; + return lookup_timing(mem_clock_mhz, lut, ARRAY_SIZE(lut)); +} diff --git a/src/northbridge/intel/haswell/native_raminit/raminit_main.c b/src/northbridge/intel/haswell/native_raminit/raminit_main.c index ab462e08a6..28a7895ce3 100644 --- a/src/northbridge/intel/haswell/native_raminit/raminit_main.c +++ b/src/northbridge/intel/haswell/native_raminit/raminit_main.c @@ -22,6 +22,7 @@ struct task_entry { static const struct task_entry cold_boot[] = { { collect_spd_info, true, "PROCSPD", }, { initialise_mpll, true, "INITMPLL", }, + { convert_timings, true, "CONVTIM", }, }; /* Return a generic stepping value to make stepping checks simpler */ diff --git a/src/northbridge/intel/haswell/native_raminit/raminit_native.h b/src/northbridge/intel/haswell/native_raminit/raminit_native.h index 15a1550424..e0ebd3a2a7 100644 --- a/src/northbridge/intel/haswell/native_raminit/raminit_native.h +++ b/src/northbridge/intel/haswell/native_raminit/raminit_native.h @@ -79,6 +79,9 @@ struct sysinfo { uint32_t tCWL; uint32_t tCMD; + uint32_t tREFI; + uint32_t tXP; + uint8_t lanes; /* 8 or 9 */ uint8_t chanmap; uint8_t dpc[NUM_CHANNELS]; /* DIMMs per channel */ @@ -97,7 +100,12 @@ void raminit_main(enum raminit_boot_mode bootmode); enum raminit_status collect_spd_info(struct sysinfo *ctrl); enum raminit_status initialise_mpll(struct sysinfo *ctrl); +enum raminit_status convert_timings(struct sysinfo *ctrl); enum raminit_status wait_for_first_rcomp(void); +uint8_t get_tCWL(uint32_t mem_clock_mhz); +uint32_t get_tREFI(uint32_t mem_clock_mhz); +uint32_t get_tXP(uint32_t mem_clock_mhz); + #endif diff --git a/src/northbridge/intel/haswell/native_raminit/spd_bitmunching.c b/src/northbridge/intel/haswell/native_raminit/spd_bitmunching.c index a4225913b6..556f5666dd 100644 --- a/src/northbridge/intel/haswell/native_raminit/spd_bitmunching.c +++ b/src/northbridge/intel/haswell/native_raminit/spd_bitmunching.c @@ -207,3 +207,103 @@ enum raminit_status collect_spd_info(struct sysinfo *ctrl) get_spd_data(ctrl); return find_common_spd_parameters(ctrl); } + +#define MIN_CWL 5 +#define MAX_CWL 12 + +/* Except for tCK, hardware expects all timing values in DCLKs, not nanoseconds */ +enum raminit_status convert_timings(struct sysinfo *ctrl) +{ + /* + * Obtain all required timing values, in DCLKs. + */ + + /* Convert primary timings from nanoseconds to DCLKs */ + ctrl->tAA = DIV_ROUND_UP(ctrl->tAA, ctrl->tCK); + ctrl->tWR = DIV_ROUND_UP(ctrl->tWR, ctrl->tCK); + ctrl->tRCD = DIV_ROUND_UP(ctrl->tRCD, ctrl->tCK); + ctrl->tRRD = DIV_ROUND_UP(ctrl->tRRD, ctrl->tCK); + ctrl->tRP = DIV_ROUND_UP(ctrl->tRP, ctrl->tCK); + ctrl->tRAS = DIV_ROUND_UP(ctrl->tRAS, ctrl->tCK); + ctrl->tRC = DIV_ROUND_UP(ctrl->tRC, ctrl->tCK); + ctrl->tRFC = DIV_ROUND_UP(ctrl->tRFC, ctrl->tCK); + ctrl->tWTR = DIV_ROUND_UP(ctrl->tWTR, ctrl->tCK); + ctrl->tRTP = DIV_ROUND_UP(ctrl->tRTP, ctrl->tCK); + ctrl->tFAW = DIV_ROUND_UP(ctrl->tFAW, ctrl->tCK); + ctrl->tCWL = DIV_ROUND_UP(ctrl->tCWL, ctrl->tCK); + ctrl->tCMD = DIV_ROUND_UP(ctrl->tCMD, ctrl->tCK); + + /* Constrain primary timings to hardware limits */ + /** TODO: complain when clamping? **/ + ctrl->tAA = clamp_u32(4, ctrl->tAA, 24); + ctrl->tWR = clamp_u32(5, ctrl->tWR, 16); + ctrl->tRCD = clamp_u32(4, ctrl->tRCD, 20); + ctrl->tRRD = clamp_u32(4, ctrl->tRRD, 65535); + ctrl->tRP = clamp_u32(4, ctrl->tRP, 15); + ctrl->tRAS = clamp_u32(10, ctrl->tRAS, 40); + ctrl->tRC = clamp_u32(1, ctrl->tRC, 4095); + ctrl->tRFC = clamp_u32(1, ctrl->tRFC, 511); + ctrl->tWTR = clamp_u32(4, ctrl->tWTR, 10); + ctrl->tRTP = clamp_u32(4, ctrl->tRTP, 15); + ctrl->tFAW = clamp_u32(10, ctrl->tFAW, 54); + + /** TODO: Honor tREFI from XMP **/ + ctrl->tREFI = get_tREFI(ctrl->mem_clock_mhz); + ctrl->tXP = get_tXP(ctrl->mem_clock_mhz); + + /* + * Check some values, and adjust them if necessary. + */ + + /* If tWR cannot be written into DDR3 MR0, adjust it */ + switch (ctrl->tWR) { + case 9: + case 11: + case 13: + case 15: + ctrl->tWR++; + } + + /* If tCWL is not supported or unspecified, look up a reasonable default */ + if (ctrl->tCWL < MIN_CWL || ctrl->tCWL > MAX_CWL) + ctrl->tCWL = get_tCWL(ctrl->mem_clock_mhz); + + /* This is needed to support ODT properly on 2DPC */ + if (ctrl->tAA - ctrl->tCWL > 4) + ctrl->tCWL = ctrl->tAA - 4; + + /* If tCMD is invalid, use a guesstimate default */ + if (!ctrl->tCMD) { + ctrl->tCMD = MAX(ctrl->dpc[0], ctrl->dpc[1]); + printk(RAM_DEBUG, "tCMD was zero, picking a guesstimate value\n"); + } + ctrl->tCMD = clamp_u32(1, ctrl->tCMD, 3); + + /* + * Print final timings. + */ + + /* tCK is special */ + printk(BIOS_DEBUG, "Selected tCK : %u ps\n", ctrl->tCK * 1000 / 256); + + /* Primary timings */ + printk(BIOS_DEBUG, "Selected tAA : %uT\n", ctrl->tAA); + printk(BIOS_DEBUG, "Selected tWR : %uT\n", ctrl->tWR); + printk(BIOS_DEBUG, "Selected tRCD : %uT\n", ctrl->tRCD); + printk(BIOS_DEBUG, "Selected tRRD : %uT\n", ctrl->tRRD); + printk(BIOS_DEBUG, "Selected tRP : %uT\n", ctrl->tRP); + printk(BIOS_DEBUG, "Selected tRAS : %uT\n", ctrl->tRAS); + printk(BIOS_DEBUG, "Selected tRC : %uT\n", ctrl->tRC); + printk(BIOS_DEBUG, "Selected tRFC : %uT\n", ctrl->tRFC); + printk(BIOS_DEBUG, "Selected tWTR : %uT\n", ctrl->tWTR); + printk(BIOS_DEBUG, "Selected tRTP : %uT\n", ctrl->tRTP); + printk(BIOS_DEBUG, "Selected tFAW : %uT\n", ctrl->tFAW); + printk(BIOS_DEBUG, "Selected tCWL : %uT\n", ctrl->tCWL); + printk(BIOS_DEBUG, "Selected tCMD : %uT\n", ctrl->tCMD); + + /* Derived timings */ + printk(BIOS_DEBUG, "Selected tREFI : %uT\n", ctrl->tREFI); + printk(BIOS_DEBUG, "Selected tXP : %uT\n", ctrl->tXP); + + return RAMINIT_STATUS_SUCCESS; +}