summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/device/dram/ddr3.c217
-rw-r--r--src/include/device/dram/ddr3.h119
2 files changed, 336 insertions, 0 deletions
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 <device/device.h>
#include <device/dram/ddr3.h>
+/*==============================================================================
+ * = 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 */