DDR3: Add utilities for creating MRS commands

MRS commands are used to tell the DRAM chip what timing and what
termination and drive strength to use, along with other parameters.
The MRS commands are defined by the DDR3 specification [1]. This
makes MRS commands hardware-independent.

MRS command creation is duplicated in various shapes and forms in any
chipset that does DDR3. This is an effort to create a generic MRS API
that can be used with any chipset.

This is used in the VX900 branch.

[1] www.jedec.org/sites/default/files/docs/JESD79-3E.pdf

Change-Id: Ia8bb593e3e28a5923a866042327243d798c3b793
Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
Reviewed-on: http://review.coreboot.org/3354
Tested-by: build bot (Jenkins)
Reviewed-by: Ronald G. Minnich <rminnich@gmail.com>
This commit is contained in:
Alexandru Gagniuc 2013-06-03 13:58:10 -05:00 committed by Ronald G. Minnich
parent 775551c69e
commit 78706fd61f
2 changed files with 337 additions and 1 deletions

View File

@ -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;
}

View File

@ -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 */