diff --git a/src/device/dram/ddr3.c b/src/device/dram/ddr3.c index c745bd74b7..6e3fc2a58d 100644 --- a/src/device/dram/ddr3.c +++ b/src/device/dram/ddr3.c @@ -27,6 +27,10 @@ #include #include +/*============================================================================== + * = DDR3 SPD decoding helpers + *----------------------------------------------------------------------------*/ + /** * \brief Checks if the DIMM is Registered based on byte[3] of the SPD * @@ -368,5 +372,218 @@ void dram_print_spd_ddr3(const dimm_attr * dimm) print_ns(" tWTRmin : ", dimm->tWTR); print_ns(" tRTPmin : ", dimm->tRTP); print_ns(" tFAWmin : ", dimm->tFAW); - +} + +/*============================================================================== + *= DDR3 MRS helpers + *----------------------------------------------------------------------------*/ + +/* + * MRS command structure: + * cmd[15:0] = Address pins MA[15:0] + * cmd[18:16] = Bank address BA[2:0] + */ + +/* Map tWR value to a bitmask of the MR0 cycle */ +static u16 ddr3_twr_to_mr0_map(u8 twr) +{ + if ((twr >= 5) && (twr <= 8)) + return (twr - 4) << 9; + + /* + * From 8T onwards, we can only use even values. Round up if we are + * given an odd value. + */ + if ((twr >= 9) && (twr <= 14)) + return ((twr + 1) >> 1) << 9; + + /* tWR == 16T is [000] */ + return 0; +} + +/* Map the CAS latency to a bitmask for the MR0 cycle */ +static u16 ddr3_cas_to_mr0_map(u8 cas) +{ + u16 mask = 0; + /* A[6:4] are bits [2:0] of (CAS - 4) */ + mask = ((cas - 4) & 0x07) << 4; + + /* A2 is the MSB of (CAS - 4) */ + if ((cas - 4) & (1 << 3)) + mask |= (1 << 2); + + return mask; +} + +/** + * \brief Get command address for a DDR3 MR0 command + * + * The DDR3 specification only covers odd write_recovery up to 7T. If an odd + * write_recovery greater than 7 is specified, it will be rounded up. If a tWR + * greater than 8 is specified, it is recommended to explicitly round it up or + * down before calling this function. + * + * write_recovery and cas are given in clock cycles. For example, a CAS of 7T + * should be given as 7. + * + * @param write_recovery Write recovery latency, tWR in clock cycles. + * @param cas CAS latency in clock cycles. + */ +mrs_cmd_t ddr3_get_mr0(enum ddr3_mr0_precharge precharge_pd, + u8 write_recovery, + enum ddr3_mr0_dll_reset dll_reset, + enum ddr3_mr0_mode mode, + u8 cas, + enum ddr3_mr0_burst_type burst_type, + enum ddr3_mr0_burst_length burst_length) +{ + mrs_cmd_t cmd = 0 << 16; + + if (precharge_pd == DDR3_MR0_PRECHARGE_FAST) + cmd |= (1 << 12); + + cmd |= ddr3_twr_to_mr0_map(write_recovery); + + if (dll_reset == DDR3_MR0_DLL_RESET_YES) + cmd |= (1 << 8); + + if (mode == DDR3_MR0_MODE_TEST) + cmd |= (1 << 7); + + cmd |= ddr3_cas_to_mr0_map(cas); + + if (burst_type == DDR3_MR0_BURST_TYPE_INTERLEAVED) + cmd |= (1 << 3); + + cmd |= (burst_length & 0x03) << 0; + + return cmd; +} + +static u16 ddr3_rtt_nom_to_mr1_map(enum ddr3_mr1_rtt_nom rtt_nom) +{ + u16 mask = 0; + /* A9 <-> rtt_nom[2] */ + if (rtt_nom & (1 << 2)) + mask |= (1 << 9); + /* A6 <-> rtt_nom[1] */ + if (rtt_nom & (1 << 1)) + mask |= (1 << 6); + /* A2 <-> rtt_nom[0] */ + if (rtt_nom & (1 << 0)) + mask |= (1 << 2); + + return mask; +} + +static u16 ddr3_ods_to_mr1_map(enum ddr3_mr1_ods ods) +{ + u16 mask = 0; + /* A5 <-> ods[1] */ + if (ods & (1 << 1)) + mask |= (1 << 5); + /* A1 <-> ods[0] */ + if (ods & (1 << 0)) + mask |= (1 << 1); + + return mask; +} + +/** + * \brief Get command address for a DDR3 MR1 command + */ +mrs_cmd_t ddr3_get_mr1(enum ddr3_mr1_qoff qoff, + enum ddr3_mr1_tqds tqds, + enum ddr3_mr1_rtt_nom rtt_nom, + enum ddr3_mr1_write_leveling write_leveling, + enum ddr3_mr1_ods ods, + enum ddr3_mr1_additive_latency additive_latency, + enum ddr3_mr1_dll dll_disable) +{ + mrs_cmd_t cmd = 1 << 16; + + if (qoff == DDR3_MR1_QOFF_DISABLE) + cmd |= (1 << 12); + + if (tqds == DDR3_MR1_TQDS_ENABLE) + cmd |= (1 << 11); + + cmd |= ddr3_rtt_nom_to_mr1_map(rtt_nom); + + if (write_leveling == DDR3_MR1_WRLVL_ENABLE) + cmd |= (1 << 7); + + cmd |= ddr3_ods_to_mr1_map(ods); + + cmd |= (additive_latency & 0x03) << 3; + + if (dll_disable == DDR3_MR1_DLL_DISABLE) + cmd |= (1 << 0); + + return cmd; +} + +/** + * \brief Get command address for a DDR3 MR2 command + * + * cas_cwl is given in clock cycles. For example, a cas_cwl of 7T should be + * given as 7. + * + * @param cas_cwl CAS write latency in clock cycles. + */ +mrs_cmd_t ddr3_get_mr2(enum ddr3_mr2_rttwr rtt_wr, + enum ddr3_mr2_srt_range extended_temp, + enum ddr3_mr2_asr self_refresh, u8 cas_cwl) +{ + mrs_cmd_t cmd = 2 << 16; + + cmd |= (rtt_wr & 0x03) << 9; + + if (extended_temp == DDR3_MR2_SRT_EXTENDED) + cmd |= (1 << 7); + + if (self_refresh == DDR3_MR2_ASR_AUTO) + cmd |= (1 << 6); + + cmd |= ((cas_cwl - 5) & 0x07) << 3; + + return cmd; +} + +/** + * \brief Get command address for a DDR3 MR3 command + * + * @param dataflow_from_mpr Specify a non-zero value to put DRAM in read + * leveling mode. Zero for normal operation. + */ +mrs_cmd_t ddr3_get_mr3(char dataflow_from_mpr) +{ + mrs_cmd_t cmd = 3 << 16; + + if (dataflow_from_mpr) + cmd |= (1 << 2); + + return cmd; +} + +/** + * \brief Mirror the address bits for this MRS command + * + * Swap the following bits in the MRS command: + * - MA3 <-> MA4 + * - MA5 <-> MA6 + * - MA7 <-> MA8 + * - BA0 <-> BA1 + */ +mrs_cmd_t ddr3_mrs_mirror_pins(mrs_cmd_t cmd) +{ + u32 downshift, upshift; + /* High bits= A4 | A6 | A8 | BA1 */ + /* Low bits = A3 | A5 | A7 | BA0 */ + u32 lowbits = (1 << 3) | (1 << 5) | (1 << 7) | (1 << 16); + downshift = (cmd & (lowbits << 1)); + upshift = (cmd & lowbits); + cmd &= ~(lowbits | (lowbits << 1)); + cmd |= (downshift >> 1) | (upshift << 1); + return cmd; } diff --git a/src/include/device/dram/ddr3.h b/src/include/device/dram/ddr3.h index 926f7b9db0..69c072bb08 100644 --- a/src/include/device/dram/ddr3.h +++ b/src/include/device/dram/ddr3.h @@ -186,4 +186,123 @@ static inline u32 volatile_read(volatile u32 addr) return result; } +/** + * \brief Representation of an MRS command + * + * This represents an MRS command as seen by the DIMM. This is not a memory + * address that can be read to generate an MRS command. The mapping of CPU + * to memory pins is hardware-dependent. + * \n + * The idea is to generalize the MRS code, and only need a hardware-specific + * function to map the MRS bits to CPU address bits. An MRS command can be + * sent like: + * @code{.c} + * u32 addr; + * mrs_cmd_t mrs; + * chipset_enable_mrs_command_mode(); + * mrs = ddr3_get_mr2(rtt_wr, srt, asr, cwl) + * if (rank_has_mirrorred_pins) + * mrs = ddr3_mrs_mirror_pins(mrs); + * addr = chipset_specific_get_mrs_addr(mrs); + * volatile_read(addr); + * @endcode + * + * The MRS representation has the following structure: + * - cmd[15:0] = Address pins MA[15:0] + * - cmd[18:16] = Bank address BA[2:0] + */ +typedef u32 mrs_cmd_t; + +enum ddr3_mr0_precharge { + DDR3_MR0_PRECHARGE_SLOW = 0, + DDR3_MR0_PRECHARGE_FAST = 1, +}; +enum ddr3_mr0_mode { + DDR3_MR0_MODE_NORMAL = 0, + DDR3_MR0_MODE_TEST = 1, +}; +enum ddr3_mr0_dll_reset { + DDR3_MR0_DLL_RESET_NO = 0, + DDR3_MR0_DLL_RESET_YES = 1, +}; +enum ddr3_mr0_burst_type { + DDR3_MR0_BURST_TYPE_SEQUENTIAL = 0, + DDR3_MR0_BURST_TYPE_INTERLEAVED = 1, +}; +enum ddr3_mr0_burst_length { + DDR3_MR0_BURST_LENGTH_8 = 0, + DDR3_MR0_BURST_LENGTH_CHOP = 1, + DDR3_MR0_BURST_LENGTH_4 = 2, +}; +mrs_cmd_t ddr3_get_mr0(enum ddr3_mr0_precharge precharge_pd, + u8 write_recovery, + enum ddr3_mr0_dll_reset dll_reset, + enum ddr3_mr0_mode mode, + u8 cas, + enum ddr3_mr0_burst_type interleaved_burst, + enum ddr3_mr0_burst_length burst_length); + +enum ddr3_mr1_qoff { + DDR3_MR1_QOFF_ENABLE = 0, + DDR3_MR1_QOFF_DISABLE = 1, +}; +enum ddr3_mr1_tqds { + DDR3_MR1_TQDS_DISABLE = 0, + DDR3_MR1_TQDS_ENABLE = 1, +}; +enum ddr3_mr1_write_leveling { + DDR3_MR1_WRLVL_DISABLE = 0, + DDR3_MR1_WRLVL_ENABLE = 1, +}; +enum ddr3_mr1_rtt_nom { + DDR3_MR1_RTT_NOM_OFF = 0, + DDR3_MR1_RTT_NOM_RZQ4 = 1, + DDR3_MR1_RTT_NOM_RZQ2 = 2, + DDR3_MR1_RTT_NOM_RZQ6 = 3, + DDR3_MR1_RTT_NOM_RZQ12 = 4, + DDR3_MR1_RTT_NOM_RZQ8 = 5, +}; +enum ddr3_mr1_additive_latency { + DDR3_MR1_AL_DISABLE = 0, + DDR3_MR1_AL_CL_MINUS_1 = 1, + DDR3_MR1_AL_CL_MINUS_2 = 2, +}; +enum ddr3_mr1_ods { + DDR3_MR1_ODS_RZQ6 = 0, + DDR3_MR1_ODS_RZQ7 = 1, +}; +enum ddr3_mr1_dll { + DDR3_MR1_DLL_ENABLE = 0, + DDR3_MR1_DLL_DISABLE = 1, +}; + +mrs_cmd_t ddr3_get_mr1(enum ddr3_mr1_qoff qoff, + enum ddr3_mr1_tqds tqds, + enum ddr3_mr1_rtt_nom rtt_nom, + enum ddr3_mr1_write_leveling write_leveling, + enum ddr3_mr1_ods output_drive_strenght, + enum ddr3_mr1_additive_latency additive_latency, + enum ddr3_mr1_dll dll_disable); + +enum ddr3_mr2_rttwr { + DDR3_MR2_RTTWR_OFF = 0, + DDR3_MR2_RTTWR_RZQ4 = 1, + DDR3_MR2_RTTWR_RZQ2 = 2, +}; +enum ddr3_mr2_srt_range { + DDR3_MR2_SRT_NORMAL = 0, + DDR3_MR2_SRT_EXTENDED = 1, +}; +enum ddr3_mr2_asr { + DDR3_MR2_ASR_MANUAL = 0, + DDR3_MR2_ASR_AUTO = 1, +}; + +mrs_cmd_t ddr3_get_mr2(enum ddr3_mr2_rttwr rtt_wr, + enum ddr3_mr2_srt_range extended_temp, + enum ddr3_mr2_asr self_refresh, u8 cas_cwl); + +mrs_cmd_t ddr3_get_mr3(char dataflow_from_mpr); +mrs_cmd_t ddr3_mrs_mirror_pins(mrs_cmd_t cmd); + #endif /* DEVICE_DRAM_DDR3_H */