diff options
author | Kumar Gala <galak@kernel.crashing.org> | 2008-08-26 15:01:29 -0500 |
---|---|---|
committer | Wolfgang Denk <wd@denx.de> | 2008-08-27 02:05:58 +0200 |
commit | 58e5e9aff147e8c7e2bc1406bf9384f65f020ffa (patch) | |
tree | a246c5ded0e1cdc32687c52b15483b11a0966795 | |
parent | f784e32b4bce0013983506b11af4b85b8ca3d36e (diff) |
FSL DDR: Rewrite the FSL mpc8xxx DDR controller setup code.
The main purpose of this rewrite it to be able to share the same
initialization code on all FSL PowerPC products that have DDR
controllers. (83xx, 85xx, 86xx).
The code is broken up into the following steps:
GET_SPD
COMPUTE_DIMM_PARMS
COMPUTE_COMMON_PARMS
GATHER_OPTS
ASSIGN_ADDRESSES
COMPUTE_REGS
PROGRAM_REGS
This allows us to share more code an easily allow for board specific code
overrides.
Additionally this code base adds support for >4G of DDR and provides a
foundation for supporting interleaving on processors with more than one
controller.
Signed-off-by: James Yang <James.Yang@freescale.com>
Signed-off-by: Jon Loeliger <jdl@freescale.com>
Signed-off-by: Becky Bruce <becky.bruce@freescale.com>
Signed-off-by: Ed Swarthout <Ed.Swarthout@freescale.com>
Signed-off-by: Kumar Gala <galak@kernel.crashing.org>
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | cpu/mpc85xx/Makefile | 11 | ||||
-rw-r--r-- | cpu/mpc86xx/Makefile | 5 | ||||
-rw-r--r-- | cpu/mpc8xxx/ddr/Makefile | 31 | ||||
-rw-r--r-- | cpu/mpc8xxx/ddr/common_timing_params.h | 53 | ||||
-rw-r--r-- | cpu/mpc8xxx/ddr/ctrl_regs.c | 993 | ||||
-rw-r--r-- | cpu/mpc8xxx/ddr/ddr.h | 80 | ||||
-rw-r--r-- | cpu/mpc8xxx/ddr/ddr1_2_dimm_params.h | 84 | ||||
-rw-r--r-- | cpu/mpc8xxx/ddr/lc_common_dimm_params.c | 404 | ||||
-rw-r--r-- | cpu/mpc8xxx/ddr/main.c | 473 | ||||
-rw-r--r-- | cpu/mpc8xxx/ddr/options.c | 197 | ||||
-rw-r--r-- | cpu/mpc8xxx/ddr/util.c | 103 | ||||
-rw-r--r-- | include/asm-ppc/fsl_ddr_sdram.h | 137 |
13 files changed, 2573 insertions, 2 deletions
@@ -236,6 +236,10 @@ LIBS += drivers/qe/qe.a endif ifeq ($(CPU),mpc85xx) LIBS += drivers/qe/qe.a +LIBS += cpu/mpc8xxx/ddr/libddr.a +endif +ifeq ($(CPU),mpc86xx) +LIBS += cpu/mpc8xxx/ddr/libddr.a endif LIBS += drivers/rtc/librtc.a LIBS += drivers/serial/libserial.a diff --git a/cpu/mpc85xx/Makefile b/cpu/mpc85xx/Makefile index adbc585827a..d51a6dd79f6 100644 --- a/cpu/mpc85xx/Makefile +++ b/cpu/mpc85xx/Makefile @@ -33,8 +33,17 @@ SOBJS-$(CONFIG_MP) += release.o SOBJS = $(SOBJS-y) COBJS-$(CONFIG_MP) += mp.o COBJS-$(CONFIG_OF_LIBFDT) += fdt.o + +ifneq ($(CONFIG_FSL_DDR3),y) +ifneq ($(CONFIG_FSL_DDR2),y) +ifneq ($(CONFIG_FSL_DDR1),y) +COBJS-y += spd_sdram.o +endif +endif +endif + COBJS = traps.o cpu.o cpu_init.o speed.o interrupts.o tlb.o \ - pci.o serial_scc.o commproc.o ether_fcc.o spd_sdram.o qe_io.o \ + pci.o serial_scc.o commproc.o ether_fcc.o qe_io.o \ $(COBJS-y) SRCS := $(START:.o=.S) $(SOBJS:.o=.S) $(COBJS:.o=.c) diff --git a/cpu/mpc86xx/Makefile b/cpu/mpc86xx/Makefile index 537f62a3238..454c7284091 100644 --- a/cpu/mpc86xx/Makefile +++ b/cpu/mpc86xx/Makefile @@ -36,10 +36,13 @@ COBJS-y += cpu.o COBJS-y += cpu_init.o COBJS-y += speed.o COBJS-y += interrupts.o -COBJS-y += spd_sdram.o COBJS-$(CONFIG_OF_LIBFDT) += fdt.o +ifneq ($(CONFIG_FSL_DDR2),y) +COBJS-y += spd_sdram.o +endif + SRCS := $(START:.o=.S) $(SOBJS:.o=.S) $(COBJS-y:.o=.c) OBJS := $(addprefix $(obj),$(SOBJS) $(COBJS-y)) START := $(addprefix $(obj),$(START)) diff --git a/cpu/mpc8xxx/ddr/Makefile b/cpu/mpc8xxx/ddr/Makefile new file mode 100644 index 00000000000..b7f8d8cf571 --- /dev/null +++ b/cpu/mpc8xxx/ddr/Makefile @@ -0,0 +1,31 @@ +# +# Copyright 2008 Freescale Semiconductor, Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# Version 2 as published by the Free Software Foundation. +# + +include $(TOPDIR)/config.mk + +LIB = $(obj)libddr.a + +COBJS-$(CONFIG_FSL_DDR1) += main.o util.o ctrl_regs.o options.o \ + lc_common_dimm_params.o +COBJS-$(CONFIG_FSL_DDR1) += ddr1_dimm_params.o + +COBJS-$(CONFIG_FSL_DDR2) += main.o util.o ctrl_regs.o options.o \ + lc_common_dimm_params.o +COBJS-$(CONFIG_FSL_DDR2) += ddr2_dimm_params.o + +SRCS := $(START:.o=.S) $(SOBJS-y:.o=.S) $(COBJS-y:.o=.c) +OBJS := $(addprefix $(obj),$(SOBJS-y) $(COBJS-y)) + +all: $(obj).depend $(LIB) + +$(LIB): $(OBJS) + $(AR) $(ARFLAGS) $@ $(OBJS) + +include $(SRCTREE)/rules.mk + +sinclude $(obj).depend diff --git a/cpu/mpc8xxx/ddr/common_timing_params.h b/cpu/mpc8xxx/ddr/common_timing_params.h new file mode 100644 index 00000000000..5aea517f251 --- /dev/null +++ b/cpu/mpc8xxx/ddr/common_timing_params.h @@ -0,0 +1,53 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 as published by the Free Software Foundation. + */ + +#ifndef COMMON_TIMING_PARAMS_H +#define COMMON_TIMING_PARAMS_H + +typedef struct { + /* parameters to constrict */ + + unsigned int tCKmin_X_ps; + unsigned int tCKmax_ps; + unsigned int tCKmax_max_ps; + unsigned int tRCD_ps; + unsigned int tRP_ps; + unsigned int tRAS_ps; + + unsigned int tWR_ps; /* maximum = 63750 ps */ + unsigned int tWTR_ps; /* maximum = 63750 ps */ + unsigned int tRFC_ps; /* maximum = 255 ns + 256 ns + .75 ns + = 511750 ps */ + + unsigned int tRRD_ps; /* maximum = 63750 ps */ + unsigned int tRC_ps; /* maximum = 254 ns + .75 ns = 254750 ps */ + + unsigned int refresh_rate_ps; + + unsigned int tIS_ps; /* byte 32, spd->ca_setup */ + unsigned int tIH_ps; /* byte 33, spd->ca_hold */ + unsigned int tDS_ps; /* byte 34, spd->data_setup */ + unsigned int tDH_ps; /* byte 35, spd->data_hold */ + unsigned int tRTP_ps; /* byte 38, spd->trtp */ + unsigned int tDQSQ_max_ps; /* byte 44, spd->tdqsq */ + unsigned int tQHS_ps; /* byte 45, spd->tqhs */ + + unsigned int ndimms_present; + unsigned int lowest_common_SPD_caslat; + unsigned int highest_common_derated_caslat; + unsigned int additive_latency; + unsigned int all_DIMMs_burst_lengths_bitmask; + unsigned int all_DIMMs_registered; + unsigned int all_DIMMs_unbuffered; + unsigned int all_DIMMs_ECC_capable; + + unsigned long long total_mem; + unsigned long long base_address; +} common_timing_params_t; + +#endif diff --git a/cpu/mpc8xxx/ddr/ctrl_regs.c b/cpu/mpc8xxx/ddr/ctrl_regs.c new file mode 100644 index 00000000000..ca675512a29 --- /dev/null +++ b/cpu/mpc8xxx/ddr/ctrl_regs.c @@ -0,0 +1,993 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 as published by the Free Software Foundation. + */ + +/* + * Generic driver for Freescale DDR/DDR2/DDR3 memory controller. + * Based on code from spd_sdram.c + * Author: James Yang [at freescale.com] + */ + +#include <common.h> +#include <asm/fsl_ddr_sdram.h> + +#include "ddr.h" + +extern unsigned int picos_to_mclk(unsigned int picos); +/* + * Determine Rtt value. + * + * This should likely be either board or controller specific. + * + * Rtt(nominal): + * 0 = Rtt disabled + * 1 = 75 ohm + * 2 = 150 ohm + * 3 = 50 ohm + * + * FIXME: Apparently 8641 needs a value of 2 + * FIXME: Old code seys if 667 MHz or higher, use 3 on 8572 + * + * FIXME: There was some effort down this line earlier: + * + * unsigned int i; + * for (i = 0; i < CONFIG_CHIP_SELECTS_PER_CTRL/2; i++) { + * if (popts->dimmslot[i].num_valid_cs + * && (popts->cs_local_opts[2*i].odt_rd_cfg + * || popts->cs_local_opts[2*i].odt_wr_cfg)) { + * rtt = 2; + * break; + * } + * } + */ +static inline int fsl_ddr_get_rtt(void) +{ + int rtt; + +#if defined(CONFIG_FSL_DDR1) + rtt = 0; +#elif defined(CONFIG_FSL_DDR2) + rtt = 3; +#else +#error "Need Rtt value for DDR3" +#endif + + return rtt; +} + +/* Chip Select Configuration (CSn_CONFIG) */ +static void set_csn_config(int i, fsl_ddr_cfg_regs_t *ddr, + const memctl_options_t *popts, + const dimm_params_t *dimm_params) +{ + unsigned int cs_n_en = 0; /* Chip Select enable */ + unsigned int intlv_en = 0; /* Memory controller interleave enable */ + unsigned int intlv_ctl = 0; /* Interleaving control */ + unsigned int ap_n_en = 0; /* Chip select n auto-precharge enable */ + unsigned int odt_rd_cfg = 0; /* ODT for reads configuration */ + unsigned int odt_wr_cfg = 0; /* ODT for writes configuration */ + unsigned int ba_bits_cs_n = 0; /* Num of bank bits for SDRAM on CSn */ + unsigned int row_bits_cs_n = 0; /* Num of row bits for SDRAM on CSn */ + unsigned int col_bits_cs_n = 0; /* Num of ocl bits for SDRAM on CSn */ + + /* Compute CS_CONFIG only for existing ranks of each DIMM. */ + if ((((i&1) == 0) + && (dimm_params[i/2].n_ranks == 1)) + || (dimm_params[i/2].n_ranks == 2)) { + unsigned int n_banks_per_sdram_device; + cs_n_en = 1; + if (i == 0) { + /* These fields only available in CS0_CONFIG */ + intlv_en = popts->memctl_interleaving; + intlv_ctl = popts->memctl_interleaving_mode; + } + ap_n_en = popts->cs_local_opts[i].auto_precharge; + odt_rd_cfg = popts->cs_local_opts[i].odt_rd_cfg; + odt_wr_cfg = popts->cs_local_opts[i].odt_wr_cfg; + n_banks_per_sdram_device + = dimm_params[i/2].n_banks_per_sdram_device; + ba_bits_cs_n = __ilog2(n_banks_per_sdram_device) - 2; + row_bits_cs_n = dimm_params[i/2].n_row_addr - 12; + col_bits_cs_n = dimm_params[i/2].n_col_addr - 8; + } + + /* FIXME: intlv_en, intlv_ctl only on CS0_CONFIG */ + if (i != 0) { + intlv_en = 0; + intlv_ctl = 0; + } + + ddr->cs[i].config = (0 + | ((cs_n_en & 0x1) << 31) + | ((intlv_en & 0x3) << 29) + | ((intlv_en & 0xf) << 24) + | ((ap_n_en & 0x1) << 23) + + /* XXX: some implementation only have 1 bit starting at left */ + | ((odt_rd_cfg & 0x7) << 20) + + /* XXX: Some implementation only have 1 bit starting at left */ + | ((odt_wr_cfg & 0x7) << 16) + + | ((ba_bits_cs_n & 0x3) << 14) + | ((row_bits_cs_n & 0x7) << 8) + | ((col_bits_cs_n & 0x7) << 0) + ); +} + +/* Chip Select Configuration 2 (CSn_CONFIG_2) */ +/* FIXME: 8572 */ +static void set_csn_config_2(int i, fsl_ddr_cfg_regs_t *ddr) +{ + unsigned int pasr_cfg = 0; /* Partial array self refresh config */ + + ddr->cs[i].config_2 = ((pasr_cfg & 7) << 24); +} + +/* -3E = 667 CL5, -25 = CL6 800, -25E = CL5 800 */ + +#if defined(CONFIG_FSL_DDR2) +/* + * DDR SDRAM Timing Configuration 0 (TIMING_CFG_0) + * + * Avoid writing for DDR I. The new PQ38 DDR controller + * dreams up non-zero default values to be backwards compatible. + */ +static void set_timing_cfg_0(fsl_ddr_cfg_regs_t *ddr) +{ + unsigned char trwt_mclk = 0; /* Read-to-write turnaround */ + unsigned char twrt_mclk = 0; /* Write-to-read turnaround */ + /* 7.5 ns on -3E; 0 means WL - CL + BL/2 + 1 */ + unsigned char trrt_mclk = 0; /* Read-to-read turnaround */ + unsigned char twwt_mclk = 0; /* Write-to-write turnaround */ + + /* Active powerdown exit timing (tXARD and tXARDS). */ + unsigned char act_pd_exit_mclk; + /* Precharge powerdown exit timing (tXP). */ + unsigned char pre_pd_exit_mclk; + /* Precharge powerdown exit timing (tAXPD). */ + unsigned char taxpd_mclk; + /* Mode register set cycle time (tMRD). */ + unsigned char tmrd_mclk; + + /* (tXARD and tXARDS). Empirical? */ + act_pd_exit_mclk = 2; + + /* XXX: tXARD = 2, tXARDS = 7 - AL. * Empirical? */ + pre_pd_exit_mclk = 6; + + /* FIXME: tXP = 2 on Micron 667 MHz DIMM */ + taxpd_mclk = 8; + + tmrd_mclk = 2; + + ddr->timing_cfg_0 = (0 + | ((trwt_mclk & 0x3) << 30) /* RWT */ + | ((twrt_mclk & 0x3) << 28) /* WRT */ + | ((trrt_mclk & 0x3) << 26) /* RRT */ + | ((twwt_mclk & 0x3) << 24) /* WWT */ + | ((act_pd_exit_mclk & 0x7) << 20) /* ACT_PD_EXIT */ + | ((pre_pd_exit_mclk & 0x7) << 16) /* PRE_PD_EXIT */ + | ((taxpd_mclk & 0xf) << 8) /* ODT_PD_EXIT */ + | ((tmrd_mclk & 0xf) << 0) /* MRS_CYC */ + ); + debug("FSLDDR: timing_cfg_0 = 0x%08x\n", ddr->timing_cfg_0); +} +#endif /* defined(CONFIG_FSL_DDR2) */ + +/* DDR SDRAM Timing Configuration 3 (TIMING_CFG_3) */ +static void set_timing_cfg_3(fsl_ddr_cfg_regs_t *ddr, + const common_timing_params_t *common_dimm) +{ + /* Extended Activate to precharge interval (tRAS) */ + unsigned int ext_acttopre = 0; + unsigned int ext_refrec; /* Extended refresh recovery time (tRFC) */ + unsigned int ext_caslat = 0; /* Extended MCAS latency from READ cmd */ + unsigned int cntl_adj = 0; /* Control Adjust */ + + ext_refrec = (picos_to_mclk(common_dimm->tRFC_ps) - 8) >> 4; + ddr->timing_cfg_3 = (0 + | ((ext_acttopre & 0x1) << 24) + | ((ext_refrec & 0x7) << 16) + | ((ext_caslat & 0x1) << 12) + | ((cntl_adj & 0x7) << 0) + ); +} + +/* DDR SDRAM Timing Configuration 1 (TIMING_CFG_1) */ +static void set_timing_cfg_1(fsl_ddr_cfg_regs_t *ddr, + const common_timing_params_t *common_dimm, + unsigned int cas_latency) +{ + /* Precharge-to-activate interval (tRP) */ + unsigned char pretoact_mclk; + /* Activate to precharge interval (tRAS) */ + unsigned char acttopre_mclk; + /* Activate to read/write interval (tRCD) */ + unsigned char acttorw_mclk; + /* CASLAT */ + unsigned char caslat_ctrl; + /* Refresh recovery time (tRFC) ; trfc_low */ + unsigned char refrec_ctrl; + /* Last data to precharge minimum interval (tWR) */ + unsigned char wrrec_mclk; + /* Activate-to-activate interval (tRRD) */ + unsigned char acttoact_mclk; + /* Last write data pair to read command issue interval (tWTR) */ + unsigned char wrtord_mclk; + + pretoact_mclk = picos_to_mclk(common_dimm->tRP_ps); + acttopre_mclk = picos_to_mclk(common_dimm->tRAS_ps); + acttorw_mclk = picos_to_mclk(common_dimm->tRCD_ps); + + /* + * Translate CAS Latency to a DDR controller field value: + * + * CAS Lat DDR I DDR II Ctrl + * Clocks SPD Bit SPD Bit Value + * ------- ------- ------- ----- + * 1.0 0 0001 + * 1.5 1 0010 + * 2.0 2 2 0011 + * 2.5 3 0100 + * 3.0 4 3 0101 + * 3.5 5 0110 + * 4.0 4 0111 + * 4.5 1000 + * 5.0 5 1001 + */ +#if defined(CONFIG_FSL_DDR1) + caslat_ctrl = (cas_latency + 1) & 0x07; +#elif defined(CONFIG_FSL_DDR2) + caslat_ctrl = 2 * cas_latency - 1; +#else +#error "Need CAS Latency help for DDR3 in fsl_ddr_sdram.c" +#endif + + refrec_ctrl = picos_to_mclk(common_dimm->tRFC_ps) - 8; + wrrec_mclk = picos_to_mclk(common_dimm->tWR_ps); + acttoact_mclk = picos_to_mclk(common_dimm->tRRD_ps); + wrtord_mclk = picos_to_mclk(common_dimm->tWTR_ps); + + ddr->timing_cfg_1 = (0 + | ((pretoact_mclk & 0x07) << 28) + | ((acttopre_mclk & 0x0F) << 24) + | ((acttorw_mclk & 0x7) << 20) + | ((caslat_ctrl & 0xF) << 16) + | ((refrec_ctrl & 0xF) << 12) + | ((wrrec_mclk & 0x07) << 8) + | ((acttoact_mclk & 0x07) << 4) + | ((wrtord_mclk & 0x07) << 0) + ); +} + +/* DDR SDRAM Timing Configuration 2 (TIMING_CFG_2) */ +static void set_timing_cfg_2(fsl_ddr_cfg_regs_t *ddr, + const memctl_options_t *popts, + const common_timing_params_t *common_dimm, + unsigned int cas_latency, + unsigned int additive_latency) +{ + /* Additive latency */ + unsigned char add_lat_mclk; + /* CAS-to-preamble override */ + unsigned short cpo; + /* Write latency */ + unsigned char wr_lat; + /* Read to precharge (tRTP) */ + unsigned char rd_to_pre; + /* Write command to write data strobe timing adjustment */ + unsigned char wr_data_delay; + /* Minimum CKE pulse width (tCKE) */ + unsigned char cke_pls; + /* Window for four activates (tFAW) */ + unsigned short four_act; + + /* FIXME add check that this must be less than acttorw_mclk */ + add_lat_mclk = additive_latency; + cpo = popts->cpo_override; + +#if defined(CONFIG_FSL_DDR1) + /* + * This is a lie. It should really be 1, but if it is + * set to 1, bits overlap into the old controller's + * otherwise unused ACSM field. If we leave it 0, then + * the HW will magically treat it as 1 for DDR 1. Oh Yea. + */ + wr_lat = 0; +#elif defined(CONFIG_FSL_DDR2) + wr_lat = cas_latency + additive_latency - 1; +#else +#error "Fix WR_LAT for DDR3" +#endif + + rd_to_pre = picos_to_mclk(common_dimm->tRTP_ps); + wr_data_delay = popts->write_data_delay; + cke_pls = picos_to_mclk(popts->tCKE_clock_pulse_width_ps); + four_act = picos_to_mclk(popts->tFAW_window_four_activates_ps); + + ddr->timing_cfg_2 = (0 + | ((add_lat_mclk & 0x7) << 28) + | ((cpo & 0x1f) << 23) + | ((wr_lat & 0x7) << 19) + | ((rd_to_pre & 0x7) << 13) + | ((wr_data_delay & 0x7) << 10) + | ((cke_pls & 0x7) << 6) + | ((four_act & 0x1f) << 0) + ); +} + +/* DDR SDRAM control configuration (DDR_SDRAM_CFG) */ +static void set_ddr_sdram_cfg(fsl_ddr_cfg_regs_t *ddr, + const memctl_options_t *popts, + const common_timing_params_t *common_dimm) +{ + unsigned int mem_en; /* DDR SDRAM interface logic enable */ + unsigned int sren; /* Self refresh enable (during sleep) */ + unsigned int ecc_en; /* ECC enable. */ + unsigned int rd_en; /* Registered DIMM enable */ + unsigned int sdram_type; /* Type of SDRAM */ + unsigned int dyn_pwr; /* Dynamic power management mode */ + unsigned int dbw; /* DRAM dta bus width */ + unsigned int eight_be; /* 8-beat burst enable */ + unsigned int ncap = 0; /* Non-concurrent auto-precharge */ + unsigned int threeT_en; /* Enable 3T timing */ + unsigned int twoT_en; /* Enable 2T timing */ + unsigned int ba_intlv_ctl; /* Bank (CS) interleaving control */ + unsigned int x32_en = 0; /* x32 enable */ + unsigned int pchb8 = 0; /* precharge bit 8 enable */ + unsigned int hse; /* Global half strength override */ + unsigned int mem_halt = 0; /* memory controller halt */ + unsigned int bi = 0; /* Bypass initialization */ + + mem_en = 1; + sren = popts->self_refresh_in_sleep; + if (common_dimm->all_DIMMs_ECC_capable) { + /* Allow setting of ECC only if all DIMMs are ECC. */ + ecc_en = popts->ECC_mode; + } else { + ecc_en = 0; + } + + rd_en = (common_dimm->all_DIMMs_registered + && !common_dimm->all_DIMMs_unbuffered); + + sdram_type = CONFIG_FSL_SDRAM_TYPE; + + dyn_pwr = popts->dynamic_power; + dbw = popts->data_bus_width; + eight_be = 0; /* always 0 for DDR2 */ + threeT_en = popts->threeT_en; + twoT_en = popts->twoT_en; + ba_intlv_ctl = popts->ba_intlv_ctl; + hse = popts->half_strength_driver_enable; + + ddr->ddr_sdram_cfg = (0 + | ((mem_en & 0x1) << 31) + | ((sren & 0x1) << 30) + | ((ecc_en & 0x1) << 29) + | ((rd_en & 0x1) << 28) + | ((sdram_type & 0x7) << 24) + | ((dyn_pwr & 0x1) << 21) + | ((dbw & 0x3) << 19) + | ((eight_be & 0x1) << 18) + | ((ncap & 0x1) << 17) + | ((threeT_en & 0x1) << 16) + | ((twoT_en & 0x1) << 15) + | ((ba_intlv_ctl & 0x7F) << 8) + | ((x32_en & 0x1) << 5) + | ((pchb8 & 0x1) << 4) + | ((hse & 0x1) << 3) + | ((mem_halt & 0x1) << 1) + | ((bi & 0x1) << 0) + ); +} + +/* DDR SDRAM control configuration 2 (DDR_SDRAM_CFG_2) */ +static void set_ddr_sdram_cfg_2(fsl_ddr_cfg_regs_t *ddr, + const memctl_options_t *popts) +{ + unsigned int frc_sr = 0; /* Force self refresh */ + unsigned int sr_ie = 0; /* Self-refresh interrupt enable */ + unsigned int dll_rst_dis; /* DLL reset disable */ + unsigned int dqs_cfg; /* DQS configuration */ + unsigned int odt_cfg; /* ODT configuration */ + unsigned int num_pr; /* Number of posted refreshes */ + unsigned int obc_cfg; /* On-The-Fly Burst Chop Cfg */ + unsigned int ap_en; /* Address Parity Enable */ + unsigned int d_init; /* DRAM data initialization */ + unsigned int rcw_en = 0; /* Register Control Word Enable */ + unsigned int md_en = 0; /* Mirrored DIMM Enable */ + + dll_rst_dis = 1; /* Make this configurable */ + dqs_cfg = popts->DQS_config; + if (popts->cs_local_opts[0].odt_rd_cfg + || popts->cs_local_opts[0].odt_wr_cfg) { + /* FIXME */ + odt_cfg = 2; + } else { + odt_cfg = 0; + } + + num_pr = 1; /* Make this configurable */ + + /* + * 8572 manual says + * {TIMING_CFG_1[PRETOACT] + * + [DDR_SDRAM_CFG_2[NUM_PR] + * * ({EXT_REFREC || REFREC} + 8 + 2)]} + * << DDR_SDRAM_INTERVAL[REFINT] + */ + + obc_cfg = 0; /* Make this configurable? */ + ap_en = 0; /* Make this configurable? */ + +#if defined(CONFIG_ECC_INIT_VIA_DDRCONTROLLER) + /* Use the DDR controller to auto initialize memory. */ + d_init = 1; + ddr->ddr_data_init = CONFIG_MEM_INIT_VALUE; + debug("DDR: ddr_data_init = 0x%08x\n", ddr->ddr_data_init); +#else + /* Memory will be initialized via DMA, or not at all. */ + d_init = 0; +#endif + + ddr->ddr_sdram_cfg_2 = (0 + | ((frc_sr & 0x1) << 31) + | ((sr_ie & 0x1) << 30) + | ((dll_rst_dis & 0x1) << 29) + | ((dqs_cfg & 0x3) << 26) + | ((odt_cfg & 0x3) << 21) + | ((num_pr & 0xf) << 12) + | ((obc_cfg & 0x1) << 6) + | ((ap_en & 0x1) << 5) + | ((d_init & 0x1) << 4) + | ((rcw_en & 0x1) << 2) + | ((md_en & 0x1) << 0) + ); +} + +/* DDR SDRAM Mode configuration 2 (DDR_SDRAM_MODE_2) */ +static void set_ddr_sdram_mode_2(fsl_ddr_cfg_regs_t *ddr) +{ + unsigned short esdmode2 = 0; /* Extended SDRAM mode 2 */ + unsigned short esdmode3 = 0; /* Extended SDRAM mode 3 */ + + ddr->ddr_sdram_mode_2 = (0 + | ((esdmode2 & 0xFFFF) << 16) + | ((esdmode3 & 0xFFFF) << 0) + ); +} + +/* DDR SDRAM Interval Configuration (DDR_SDRAM_INTERVAL) */ +static void set_ddr_sdram_interval(fsl_ddr_cfg_regs_t *ddr, + const memctl_options_t *popts, + const common_timing_params_t *common_dimm) +{ + unsigned int refint; /* Refresh interval */ + unsigned int bstopre; /* Precharge interval */ + + refint = picos_to_mclk(common_dimm->refresh_rate_ps); + + bstopre = popts->bstopre; + + /* refint field used 0x3FFF in earlier controllers */ + ddr->ddr_sdram_interval = (0 + | ((refint & 0xFFFF) << 16) + | ((bstopre & 0x3FFF) << 0) + ); +} + +/* DDR SDRAM Mode configuration set (DDR_SDRAM_MODE) */ +static void set_ddr_sdram_mode(fsl_ddr_cfg_regs_t *ddr, + const memctl_options_t *popts, + const common_timing_params_t *common_dimm, + unsigned int cas_latency, + unsigned int additive_latency) +{ + unsigned short esdmode; /* Extended SDRAM mode */ + unsigned short sdmode; /* SDRAM mode */ + + /* + * FIXME: This ought to be pre-calculated in a + * technology-specific routine, + * e.g. compute_DDR2_mode_register(), and then the + * sdmode and esdmode passed in as part of common_dimm. + */ + + /* Extended Mode Register */ + unsigned int mrs = 0; /* Mode Register Set */ + unsigned int outputs = 0; /* 0=Enabled, 1=Disabled */ + unsigned int rdqs_en = 0; /* RDQS Enable: 0=no, 1=yes */ + unsigned int dqs_en = 0; /* DQS# Enable: 0=enable, 1=disable */ + unsigned int ocd = 0; /* 0x0=OCD not supported, + 0x7=OCD default state */ + unsigned int rtt; + unsigned int al; /* Posted CAS# additive latency (AL) */ + unsigned int ods = 0; /* Output Drive Strength: + 0 = Full strength (18ohm) + 1 = Reduced strength (4ohm) */ + unsigned int dll_en = 0; /* DLL Enable 0=Enable (Normal), + 1=Disable (Test/Debug) */ + + /* Mode Register (MR) */ + unsigned int mr; /* Mode Register Definition */ + unsigned int pd; /* Power-Down Mode */ + unsigned int wr; /* Write Recovery */ + unsigned int dll_res; /* DLL Reset */ + unsigned int mode; /* Normal=0 or Test=1 */ + unsigned int caslat; /* CAS# latency */ + /* BT: Burst Type (0=Sequential, 1=Interleaved) */ + unsigned int bt; + unsigned int bl; /* BL: Burst Length */ + +#if defined(CONFIG_FSL_DDR2) + const unsigned int mclk_ps = get_memory_clk_period_ps(); +#endif + + rtt = fsl_ddr_get_rtt(); + + al = additive_latency; + + esdmode = (0 + | ((mrs & 0x3) << 14) + | ((outputs & 0x1) << 12) + | ((rdqs_en & 0x1) << 11) + | ((dqs_en & 0x1) << 10) + | ((ocd & 0x7) << 7) + | ((rtt & 0x2) << 5) /* rtt field is split */ + | ((al & 0x7) << 3) + | ((rtt & 0x1) << 2) /* rtt field is split */ + | ((ods & 0x1) << 1) + | ((dll_en & 0x1) << 0) + ); + + mr = 0; /* FIXME: CHECKME */ + + /* + * 0 = Fast Exit (Normal) + * 1 = Slow Exit (Low Power) + */ + pd = 0; + +#if defined(CONFIG_FSL_DDR1) + wr = 0; /* Historical */ +#elif defined(CONFIG_FSL_DDR2) + wr = (common_dimm->tWR_ps + mclk_ps - 1) / mclk_ps - 1; +#else +#error "Write tWR_auto for DDR3" +#endif + dll_res = 0; + mode = 0; + +#if defined(CONFIG_FSL_DDR1) + if (1 <= cas_latency && cas_latency <= 4) { + unsigned char mode_caslat_table[4] = { + 0x5, /* 1.5 clocks */ + 0x2, /* 2.0 clocks */ + 0x6, /* 2.5 clocks */ + 0x3 /* 3.0 clocks */ + }; + caslat = mode_caslat_table[cas_latency - 1]; + } +#elif defined(CONFIG_FSL_DDR2) + caslat = cas_latency; +#else +#error "Fix the mode CAS Latency for DDR3" +#endif + bt = 0; + + switch (popts->burst_length) { + case 4: + bl = 2; + break; + case 8: + bl = 3; + break; + default: + printf("Error: invalid burst length of %u specified. " + " Defaulting to 4 beats.\n", + popts->burst_length); + bl = 2; + break; + } + + sdmode = (0 + | ((mr & 0x3) << 14) + | ((pd & 0x1) << 12) + | ((wr & 0x7) << 9) + | ((dll_res & 0x1) << 8) + | ((mode & 0x1) << 7) + | ((caslat & 0x7) << 4) + | ((bt & 0x1) << 3) + | ((bl & 0x7) << 0) + ); + + ddr->ddr_sdram_mode = (0 + | ((esdmode & 0xFFFF) << 16) + | ((sdmode & 0xFFFF) << 0) + ); +} + + +/* DDR SDRAM Data Initialization (DDR_DATA_INIT) */ +static void set_ddr_data_init(fsl_ddr_cfg_regs_t *ddr) +{ + unsigned int init_value; /* Initialization value */ + + init_value = 0xDEADBEEF; + ddr->ddr_data_init = init_value; +} + +/* + * DDR SDRAM Clock Control (DDR_SDRAM_CLK_CNTL) + * The old controller on the 8540/60 doesn't have this register. + * Hope it's OK to set it (to 0) anyway. + */ +static void set_ddr_sdram_clk_cntl(fsl_ddr_cfg_regs_t *ddr, + const memctl_options_t *popts) +{ + unsigned int clk_adjust; /* Clock adjust */ + + clk_adjust = popts->clk_adjust; + ddr->ddr_sdram_clk_cntl = (clk_adjust & 0xF) << 23; +} + +/* DDR Initialization Address (DDR_INIT_ADDR) */ +static void set_ddr_init_addr(fsl_ddr_cfg_regs_t *ddr) +{ + unsigned int init_addr = 0; /* Initialization address */ + + ddr->ddr_init_addr = init_addr; +} + +/* DDR Initialization Address (DDR_INIT_EXT_ADDR) */ +static void set_ddr_init_ext_addr(fsl_ddr_cfg_regs_t *ddr) +{ + unsigned int uia = 0; /* Use initialization address */ + unsigned int init_ext_addr = 0; /* Initialization address */ + + ddr->ddr_init_ext_addr = (0 + | ((uia & 0x1) << 31) + | (init_ext_addr & 0xF) + ); +} + +/* DDR SDRAM Timing Configuration 4 (TIMING_CFG_4) */ +static void set_timing_cfg_4(fsl_ddr_cfg_regs_t *ddr) +{ + unsigned int rwt = 0; /* Read-to-write turnaround for same CS */ + unsigned int wrt = 0; /* Write-to-read turnaround for same CS */ + unsigned int rrt = 0; /* Read-to-read turnaround for same CS */ + unsigned int wwt = 0; /* Write-to-write turnaround for same CS */ + unsigned int dll_lock = 0; /* DDR SDRAM DLL Lock Time */ + + ddr->timing_cfg_4 = (0 + | ((rwt & 0xf) << 28) + | ((wrt & 0xf) << 24) + | ((rrt & 0xf) << 20) + | ((wwt & 0xf) << 16) + | (dll_lock & 0x3) + ); +} + +/* DDR SDRAM Timing Configuration 5 (TIMING_CFG_5) */ +static void set_timing_cfg_5(fsl_ddr_cfg_regs_t *ddr) +{ + unsigned int rodt_on = 0; /* Read to ODT on */ + unsigned int rodt_off = 0; /* Read to ODT off */ + unsigned int wodt_on = 0; /* Write to ODT on */ + unsigned int wodt_off = 0; /* Write to ODT off */ + + ddr->timing_cfg_5 = (0 + | ((rodt_on & 0xf) << 24) + | ((rodt_off & 0xf) << 20) + | ((wodt_on & 0xf) << 12) + | ((wodt_off & 0xf) << 8) + ); +} + +/* DDR ZQ Calibration Control (DDR_ZQ_CNTL) */ +static void set_ddr_zq_cntl(fsl_ddr_cfg_regs_t *ddr) +{ + unsigned int zq_en = 0; /* ZQ Calibration Enable */ + unsigned int zqinit = 0;/* POR ZQ Calibration Time (tZQinit) */ + /* Normal Operation Full Calibration Time (tZQoper) */ + unsigned int zqoper = 0; + /* Normal Operation Short Calibration Time (tZQCS) */ + unsigned int zqcs = 0; + + ddr->ddr_zq_cntl = (0 + | ((zq_en & 0x1) << 31) + | ((zqinit & 0xF) << 24) + | ((zqoper & 0xF) << 16) + | ((zqcs & 0xF) << 8) + ); +} + +/* DDR Write Leveling Control (DDR_WRLVL_CNTL) */ +static void set_ddr_wrlvl_cntl(fsl_ddr_cfg_regs_t *ddr) +{ + unsigned int wrlvl_en = 0; /* Write Leveling Enable */ + /* + * First DQS pulse rising edge after margining mode + * is programmed (tWL_MRD) + */ + unsigned int wrlvl_mrd = 0; + /* ODT delay after margining mode is programmed (tWL_ODTEN) */ + unsigned int wrlvl_odten = 0; + /* DQS/DQS_ delay after margining mode is programmed (tWL_DQSEN) */ + unsigned int wrlvl_dqsen = 0; + /* WRLVL_SMPL: Write leveling sample time */ + unsigned int wrlvl_smpl = 0; + /* WRLVL_WLR: Write leveling repeition time */ + unsigned int wrlvl_wlr = 0; + /* WRLVL_START: Write leveling start time */ + unsigned int wrlvl_start = 0; + + ddr->ddr_wrlvl_cntl = (0 + | ((wrlvl_en & 0x1) << 31) + | ((wrlvl_mrd & 0x7) << 24) + | ((wrlvl_odten & 0x7) << 20) + | ((wrlvl_dqsen & 0x7) << 16) + | ((wrlvl_smpl & 0xf) << 12) + | ((wrlvl_wlr & 0x7) << 8) + | ((wrlvl_start & 0xF) << 0) + ); +} + +/* DDR Self Refresh Counter (DDR_SR_CNTR) */ +static void set_ddr_sr_cntr(fsl_ddr_cfg_regs_t *ddr) +{ + unsigned int sr_it = 0; /* Self Refresh Idle Threshold */ + + ddr->ddr_sr_cntr = (sr_it & 0xF) << 16; +} + +/* DDR Pre-Drive Conditioning Control (DDR_PD_CNTL) */ +static void set_ddr_pd_cntl(fsl_ddr_cfg_regs_t *ddr) +{ + /* Termination value during pre-drive conditioning */ + unsigned int tvpd = 0; + unsigned int pd_en = 0; /* Pre-Drive Conditioning Enable */ + unsigned int pdar = 0; /* Pre-Drive After Read */ + unsigned int pdaw = 0; /* Pre-Drive After Write */ + unsigned int pd_on = 0; /* Pre-Drive Conditioning On */ + unsigned int pd_off = 0; /* Pre-Drive Conditioning Off */ + + ddr->ddr_pd_cntl = (0 + | ((pd_en & 0x1) << 31) + | ((tvpd & 0x7) << 28) + | ((pdar & 0x7F) << 20) + | ((pdaw & 0x7F) << 12) + | ((pd_on & 0x1F) << 6) + | ((pd_off & 0x1F) << 0) + ); +} + + +/* DDR SDRAM Register Control Word 1 (DDR_SDRAM_RCW_1) */ +static void set_ddr_sdram_rcw_1(fsl_ddr_cfg_regs_t *ddr) +{ + unsigned int rcw0 = 0; /* RCW0: Register Control Word 0 */ + unsigned int rcw1 = 0; /* RCW1: Register Control Word 1 */ + unsigned int rcw2 = 0; /* RCW2: Register Control Word 2 */ + unsigned int rcw3 = 0; /* RCW3: Register Control Word 3 */ + unsigned int rcw4 = 0; /* RCW4: Register Control Word 4 */ + unsigned int rcw5 = 0; /* RCW5: Register Control Word 5 */ + unsigned int rcw6 = 0; /* RCW6: Register Control Word 6 */ + unsigned int rcw7 = 0; /* RCW7: Register Control Word 7 */ + + ddr->ddr_sdram_rcw_1 = (0 + | ((rcw0 & 0xF) << 28) + | ((rcw1 & 0xF) << 24) + | ((rcw2 & 0xF) << 20) + | ((rcw3 & 0xF) << 16) + | ((rcw4 & 0xF) << 12) + | ((rcw5 & 0xF) << 8) + | ((rcw6 & 0xF) << 4) + | ((rcw7 & 0xF) << 0) + ); +} + +/* DDR SDRAM Register Control Word 2 (DDR_SDRAM_RCW_2) */ +static void set_ddr_sdram_rcw_2(fsl_ddr_cfg_regs_t *ddr) +{ + unsigned int rcw8 = 0; /* RCW0: Register Control Word 8 */ + unsigned int rcw9 = 0; /* RCW1: Register Control Word 9 */ + unsigned int rcw10 = 0; /* RCW2: Register Control Word 10 */ + unsigned int rcw11 = 0; /* RCW3: Register Control Word 11 */ + unsigned int rcw12 = 0; /* RCW4: Register Control Word 12 */ + unsigned int rcw13 = 0; /* RCW5: Register Control Word 13 */ + unsigned int rcw14 = 0; /* RCW6: Register Control Word 14 */ + unsigned int rcw15 = 0; /* RCW7: Register Control Word 15 */ + + ddr->ddr_sdram_rcw_2 = (0 + | ((rcw8 & 0xF) << 28) + | ((rcw9 & 0xF) << 24) + | ((rcw10 & 0xF) << 20) + | ((rcw11 & 0xF) << 16) + | ((rcw12 & 0xF) << 12) + | ((rcw13 & 0xF) << 8) + | ((rcw14 & 0xF) << 4) + | ((rcw15 & 0xF) << 0) + ); +} + +unsigned int +check_fsl_memctl_config_regs(const fsl_ddr_cfg_regs_t *ddr) +{ + unsigned int res = 0; + + /* + * Check that DDR_SDRAM_CFG[RD_EN] and DDR_SDRAM_CFG[2T_EN] are + * not set at the same time. + */ + if (ddr->ddr_sdram_cfg & 0x10000000 + && ddr->ddr_sdram_cfg & 0x00008000) { + printf("Error: DDR_SDRAM_CFG[RD_EN] and DDR_SDRAM_CFG[2T_EN] " + " should not be set at the same time.\n"); + res++; + } + + return res; +} + +unsigned int +compute_fsl_memctl_config_regs(const memctl_options_t *popts, + fsl_ddr_cfg_regs_t *ddr, + const common_timing_params_t *common_dimm, + const dimm_params_t *dimm_params, + unsigned int dbw_cap_adj) +{ + unsigned int i; + unsigned int cas_latency; + unsigned int additive_latency; + + memset(ddr, 0, sizeof(fsl_ddr_cfg_regs_t)); + + if (common_dimm == NULL) { + printf("Error: subset DIMM params struct null pointer\n"); + return 1; + } + + /* + * Process overrides first. + * + * FIXME: somehow add dereated caslat to this + */ + cas_latency = (popts->cas_latency_override) + ? popts->cas_latency_override_value + : common_dimm->lowest_common_SPD_caslat; + + additive_latency = (popts->additive_latency_override) + ? popts->additive_latency_override_value + : common_dimm->additive_latency; + + /* Chip Select Memory Bounds (CSn_BNDS) */ + for (i = 0; i < CONFIG_CHIP_SELECTS_PER_CTRL; i++) { + phys_size_t sa = 0; + phys_size_t ea = 0; + if (popts->ba_intlv_ctl && i > 0) { + /* Don't set up boundaries if bank interleaving */ + break; + } + + if (dimm_params[i/2].n_ranks == 0) { + debug("Skipping setup of CS%u " + "because n_ranks on DIMM %u is 0\n", i, i/2); + continue; + } + if (popts->memctl_interleaving && popts->ba_intlv_ctl) { + /* + * This works superbank 2CS + * There are 2 memory controllers configured + * identically, memory is interleaved between them, + * and each controller uses rank interleaving within + * itself. Therefore the starting and ending address + * on each controller is twice the amount present on + * each controller. + */ + ea = (2 * common_dimm->total_mem >> dbw_cap_adj) - 1; + } + else if (!popts->memctl_interleaving && popts->ba_intlv_ctl) { + /* + * If memory interleaving between controllers is NOT + * enabled, the starting address for each memory + * controller is distinct. However, because rank + * interleaving is enabled, the starting and ending + * addresses of the total memory on that memory + * controller needs to be programmed into its + * respective CS0_BNDS. + */ + sa = common_dimm->base_address; + ea = sa + (common_dimm->total_mem >> dbw_cap_adj) - 1; + } + else if (popts->memctl_interleaving && !popts->ba_intlv_ctl) { + /* + * Only the rank on CS0 of each memory controller may + * be used if memory controller interleaving is used + * without rank interleaving within each memory + * controller. However, the ending address programmed + * into each CS0 must be the sum of the amount of + * memory in the two CS0 ranks. + */ + if (i == 0) { + unsigned long long rank_density + = dimm_params[0].rank_density; + ea = (2 * (rank_density >> dbw_cap_adj)) - 1; + } + + } + else if (!popts->memctl_interleaving && !popts->ba_intlv_ctl) { + /* + * No rank interleaving and no memory controller + * interleaving. + */ + unsigned long long rank_density + = dimm_params[i/2].rank_density; + sa = dimm_params[i/2].base_address; + ea = sa + (rank_density >> dbw_cap_adj) - 1; + if (i&1) { + if ((dimm_params[i/2].n_ranks == 1)) { + /* Odd chip select, single-rank dimm */ + sa = 0; + ea = 0; + } else { + /* Odd chip select, dual-rank DIMM */ + sa += rank_density >> dbw_cap_adj; + ea += rank_density >> dbw_cap_adj; + } + } + } + + sa >>= 24; + ea >>= 24; + + ddr->cs[i].bnds = (0 + | ((sa & 0xFFF) << 16) /* starting address MSB */ + | ((ea & 0xFFF) << 0) /* ending address MSB */ + ); + + set_csn_config(i, ddr, popts, dimm_params); + set_csn_config_2(i, ddr); + } + +#if defined(CONFIG_FSL_DDR2) + set_timing_cfg_0(ddr); +#endif + + set_timing_cfg_3(ddr, common_dimm); + set_timing_cfg_1(ddr, common_dimm, cas_latency); + set_timing_cfg_2(ddr, popts, common_dimm, + cas_latency, additive_latency); + + set_ddr_sdram_cfg(ddr, popts, common_dimm); + + set_ddr_sdram_cfg_2(ddr, popts); + set_ddr_sdram_mode(ddr, popts, common_dimm, + cas_latency, additive_latency); + set_ddr_sdram_mode_2(ddr); + set_ddr_sdram_interval(ddr, popts, common_dimm); + set_ddr_data_init(ddr); + set_ddr_sdram_clk_cntl(ddr, popts); + set_ddr_init_addr(ddr); + set_ddr_init_ext_addr(ddr); + set_timing_cfg_4(ddr); + set_timing_cfg_5(ddr); + + set_ddr_zq_cntl(ddr); + set_ddr_wrlvl_cntl(ddr); + + set_ddr_pd_cntl(ddr); + set_ddr_sr_cntr(ddr); + + set_ddr_sdram_rcw_1(ddr); + set_ddr_sdram_rcw_2(ddr); + + return check_fsl_memctl_config_regs(ddr); +} diff --git a/cpu/mpc8xxx/ddr/ddr.h b/cpu/mpc8xxx/ddr/ddr.h new file mode 100644 index 00000000000..f5dc40a537c --- /dev/null +++ b/cpu/mpc8xxx/ddr/ddr.h @@ -0,0 +1,80 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 as published by the Free Software Foundation. + */ + +#ifndef FSL_DDR_MAIN_H +#define FSL_DDR_MAIN_H + +#include <asm/fsl_ddr_sdram.h> + +#include "ddr1_2_dimm_params.h" +#include "common_timing_params.h" + +/* + * Bind the main DDR setup driver's generic names + * to this specific DDR technology. + */ +static __inline__ int +compute_dimm_parameters(const generic_spd_eeprom_t *spd, + dimm_params_t *pdimm, + unsigned int dimm_number) +{ + return ddr_compute_dimm_parameters(spd, pdimm, dimm_number); +} + +/* + * Data Structures + * + * All data structures have to be on the stack + */ +#define CFG_NUM_DDR_CTLRS CONFIG_NUM_DDR_CONTROLLERS +#define CFG_DIMM_SLOTS_PER_CTLR CONFIG_DIMM_SLOTS_PER_CTLR + +typedef struct { + generic_spd_eeprom_t + spd_installed_dimms[CFG_NUM_DDR_CTLRS][CFG_DIMM_SLOTS_PER_CTLR]; + struct dimm_params_s + dimm_params[CFG_NUM_DDR_CTLRS][CFG_DIMM_SLOTS_PER_CTLR]; + memctl_options_t memctl_opts[CFG_NUM_DDR_CTLRS]; + common_timing_params_t common_timing_params[CFG_NUM_DDR_CTLRS]; + fsl_ddr_cfg_regs_t fsl_ddr_config_reg[CFG_NUM_DDR_CTLRS]; +} fsl_ddr_info_t; + +/* Compute steps */ +#define STEP_GET_SPD (1 << 0) +#define STEP_COMPUTE_DIMM_PARMS (1 << 1) +#define STEP_COMPUTE_COMMON_PARMS (1 << 2) +#define STEP_GATHER_OPTS (1 << 3) +#define STEP_ASSIGN_ADDRESSES (1 << 4) +#define STEP_COMPUTE_REGS (1 << 5) +#define STEP_PROGRAM_REGS (1 << 6) +#define STEP_ALL 0xFFF + +extern phys_size_t +fsl_ddr_compute(fsl_ddr_info_t *pinfo, unsigned int start_step); + +extern const char * step_to_string(unsigned int step); + +extern unsigned int +compute_fsl_memctl_config_regs(const memctl_options_t *popts, + fsl_ddr_cfg_regs_t *ddr, + const common_timing_params_t *common_dimm, + const dimm_params_t *dimm_parameters, + unsigned int dbw_capacity_adjust); +extern unsigned int +compute_lowest_common_dimm_parameters(const dimm_params_t *dimm_params, + common_timing_params_t *outpdimm, + unsigned int number_of_dimms); +extern unsigned int populate_memctl_options(int all_DIMMs_registered, + memctl_options_t *popts, + unsigned int ctrl_num); + +extern unsigned int mclk_to_picos(unsigned int mclk); +extern unsigned int get_memory_clk_period_ps(void); +extern unsigned int picos_to_mclk(unsigned int picos); + +#endif diff --git a/cpu/mpc8xxx/ddr/ddr1_2_dimm_params.h b/cpu/mpc8xxx/ddr/ddr1_2_dimm_params.h new file mode 100644 index 00000000000..c794eedfe42 --- /dev/null +++ b/cpu/mpc8xxx/ddr/ddr1_2_dimm_params.h @@ -0,0 +1,84 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 as published by the Free Software Foundation. + */ + +#ifndef DDR2_DIMM_PARAMS_H +#define DDR2_DIMM_PARAMS_H + +/* Parameters for a DDR2 dimm computed from the SPD */ +typedef struct dimm_params_s { + + /* DIMM organization parameters */ + char mpart[19]; /* guaranteed null terminated */ + + unsigned int n_ranks; + unsigned long long rank_density; + unsigned long long capacity; + unsigned int data_width; + unsigned int primary_sdram_width; + unsigned int ec_sdram_width; + unsigned int registered_dimm; + + /* SDRAM device parameters */ + unsigned int n_row_addr; + unsigned int n_col_addr; + unsigned int edc_config; /* 0 = none, 1 = parity, 2 = ECC */ + unsigned int n_banks_per_sdram_device; + unsigned int burst_lengths_bitmask; /* BL=4 bit 2, BL=8 = bit 3 */ + unsigned int row_density; + + /* used in computing base address of DIMMs */ + unsigned long long base_address; + + /* DIMM timing parameters */ + + /* + * SDRAM clock periods + * The range for these are 1000-10000 so a short should be sufficient + */ + unsigned int tCKmin_X_ps; + unsigned int tCKmin_X_minus_1_ps; + unsigned int tCKmin_X_minus_2_ps; + unsigned int tCKmax_ps; + + /* SPD-defined CAS latencies */ + unsigned int caslat_X; + unsigned int caslat_X_minus_1; + unsigned int caslat_X_minus_2; + + unsigned int caslat_lowest_derated; /* Derated CAS latency */ + + /* basic timing parameters */ + unsigned int tRCD_ps; + unsigned int tRP_ps; + unsigned int tRAS_ps; + + unsigned int tWR_ps; /* maximum = 63750 ps */ + unsigned int tWTR_ps; /* maximum = 63750 ps */ + unsigned int tRFC_ps; /* max = 255 ns + 256 ns + .75 ns + = 511750 ps */ + + unsigned int tRRD_ps; /* maximum = 63750 ps */ + unsigned int tRC_ps; /* maximum = 254 ns + .75 ns = 254750 ps */ + + unsigned int refresh_rate_ps; + + unsigned int tIS_ps; /* byte 32, spd->ca_setup */ + unsigned int tIH_ps; /* byte 33, spd->ca_hold */ + unsigned int tDS_ps; /* byte 34, spd->data_setup */ + unsigned int tDH_ps; /* byte 35, spd->data_hold */ + unsigned int tRTP_ps; /* byte 38, spd->trtp */ + unsigned int tDQSQ_max_ps; /* byte 44, spd->tdqsq */ + unsigned int tQHS_ps; /* byte 45, spd->tqhs */ +} dimm_params_t; + +extern unsigned int ddr_compute_dimm_parameters( + const generic_spd_eeprom_t *spd, + dimm_params_t *pdimm, + unsigned int dimm_number); + +#endif diff --git a/cpu/mpc8xxx/ddr/lc_common_dimm_params.c b/cpu/mpc8xxx/ddr/lc_common_dimm_params.c new file mode 100644 index 00000000000..fbeb236d952 --- /dev/null +++ b/cpu/mpc8xxx/ddr/lc_common_dimm_params.c @@ -0,0 +1,404 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 as published by the Free Software Foundation. + */ + +#include <common.h> +#include <asm/fsl_ddr_sdram.h> + +#include "ddr.h" + +/* + * compute_lowest_common_dimm_parameters() + * + * Determine the worst-case DIMM timing parameters from the set of DIMMs + * whose parameters have been computed into the array pointed to + * by dimm_params. + */ +unsigned int +compute_lowest_common_dimm_parameters(const dimm_params_t *dimm_params, + common_timing_params_t *outpdimm, + unsigned int number_of_dimms) +{ + unsigned int i; + + unsigned int tCKmin_X_ps = 0; + unsigned int tCKmax_ps = 0xFFFFFFFF; + unsigned int tCKmax_max_ps = 0; + unsigned int tRCD_ps = 0; + unsigned int tRP_ps = 0; + unsigned int tRAS_ps = 0; + unsigned int tWR_ps = 0; + unsigned int tWTR_ps = 0; + unsigned int tRFC_ps = 0; + unsigned int tRRD_ps = 0; + unsigned int tRC_ps = 0; + unsigned int refresh_rate_ps = 0; + unsigned int tIS_ps = 0; + unsigned int tIH_ps = 0; + unsigned int tDS_ps = 0; + unsigned int tDH_ps = 0; + unsigned int tRTP_ps = 0; + unsigned int tDQSQ_max_ps = 0; + unsigned int tQHS_ps = 0; + + unsigned int temp1, temp2; + unsigned int lowest_good_caslat; + unsigned int additive_latency = 0; + const unsigned int mclk_ps = get_memory_clk_period_ps(); + unsigned int not_ok; + + debug("using mclk_ps = %u\n", mclk_ps); + + temp1 = 0; + for (i = 0; i < number_of_dimms; i++) { + /* + * If there are no ranks on this DIMM, + * it probably doesn't exist, so skip it. + */ + if (dimm_params[i].n_ranks == 0) { + temp1++; + continue; + } + + /* + * Find minimum tCKmax_ps to find fastest slow speed, + * i.e., this is the slowest the whole system can go. + */ + tCKmax_ps = min(tCKmax_ps, dimm_params[i].tCKmax_ps); + + /* Either find maximum value to determine slowest + * speed, delay, time, period, etc */ + tCKmin_X_ps = max(tCKmin_X_ps, dimm_params[i].tCKmin_X_ps); + tCKmax_max_ps = max(tCKmax_max_ps, dimm_params[i].tCKmax_ps); + tRCD_ps = max(tRCD_ps, dimm_params[i].tRCD_ps); + tRP_ps = max(tRP_ps, dimm_params[i].tRP_ps); + tRAS_ps = max(tRAS_ps, dimm_params[i].tRAS_ps); + tWR_ps = max(tWR_ps, dimm_params[i].tWR_ps); + tWTR_ps = max(tWTR_ps, dimm_params[i].tWTR_ps); + tRFC_ps = max(tRFC_ps, dimm_params[i].tRFC_ps); + tRRD_ps = max(tRRD_ps, dimm_params[i].tRRD_ps); + tRC_ps = max(tRC_ps, dimm_params[i].tRC_ps); + tIS_ps = max(tIS_ps, dimm_params[i].tIS_ps); + tIH_ps = max(tIH_ps, dimm_params[i].tIH_ps); + tDS_ps = max(tDS_ps, dimm_params[i].tDS_ps); + tDH_ps = max(tDH_ps, dimm_params[i].tDH_ps); + tRTP_ps = max(tRTP_ps, dimm_params[i].tRTP_ps); + tQHS_ps = max(tQHS_ps, dimm_params[i].tQHS_ps); + refresh_rate_ps = max(refresh_rate_ps, + dimm_params[i].refresh_rate_ps); + + /* + * Find maximum tDQSQ_max_ps to find slowest. + * + * FIXME: is finding the slowest value the correct + * strategy for this parameter? + */ + tDQSQ_max_ps = max(tDQSQ_max_ps, dimm_params[i].tDQSQ_max_ps); + } + + outpdimm->ndimms_present = number_of_dimms - temp1; + + if (temp1 == number_of_dimms) { + debug("no dimms this memory controller\n"); + return 0; + } + + outpdimm->tCKmin_X_ps = tCKmin_X_ps; + outpdimm->tCKmax_ps = tCKmax_ps; + outpdimm->tCKmax_max_ps = tCKmax_max_ps; + outpdimm->tRCD_ps = tRCD_ps; + outpdimm->tRP_ps = tRP_ps; + outpdimm->tRAS_ps = tRAS_ps; + outpdimm->tWR_ps = tWR_ps; + outpdimm->tWTR_ps = tWTR_ps; + outpdimm->tRFC_ps = tRFC_ps; + outpdimm->tRRD_ps = tRRD_ps; + outpdimm->tRC_ps = tRC_ps; + outpdimm->refresh_rate_ps = refresh_rate_ps; + outpdimm->tIS_ps = tIS_ps; + outpdimm->tIH_ps = tIH_ps; + outpdimm->tDS_ps = tDS_ps; + outpdimm->tDH_ps = tDH_ps; + outpdimm->tRTP_ps = tRTP_ps; + outpdimm->tDQSQ_max_ps = tDQSQ_max_ps; + outpdimm->tQHS_ps = tQHS_ps; + + /* Determine common burst length for all DIMMs. */ + temp1 = 0xff; + for (i = 0; i < number_of_dimms; i++) { + if (dimm_params[i].n_ranks) { + temp1 &= dimm_params[i].burst_lengths_bitmask; + } + } + outpdimm->all_DIMMs_burst_lengths_bitmask = temp1; + + /* Determine if all DIMMs registered buffered. */ + temp1 = temp2 = 0; + for (i = 0; i < number_of_dimms; i++) { + if (dimm_params[i].n_ranks) { + if (dimm_params[i].registered_dimm) + temp1 = 1; + if (!dimm_params[i].registered_dimm) + temp2 = 1; + } + } + + outpdimm->all_DIMMs_registered = 0; + if (temp1 && !temp2) { + outpdimm->all_DIMMs_registered = 1; + } + + outpdimm->all_DIMMs_unbuffered = 0; + if (!temp1 && temp2) { + outpdimm->all_DIMMs_unbuffered = 1; + } + + /* CHECKME: */ + if (!outpdimm->all_DIMMs_registered + && !outpdimm->all_DIMMs_unbuffered) { + printf("ERROR: Mix of registered buffered and unbuffered " + "DIMMs detected!\n"); + } + + /* + * Compute a CAS latency suitable for all DIMMs + * + * Strategy for SPD-defined latencies: compute only + * CAS latency defined by all DIMMs. + */ + + /* + * Step 1: find CAS latency common to all DIMMs using bitwise + * operation. + */ + temp1 = 0xFF; + for (i = 0; i < number_of_dimms; i++) { + if (dimm_params[i].n_ranks) { + temp2 = 0; + temp2 |= 1 << dimm_params[i].caslat_X; + temp2 |= 1 << dimm_params[i].caslat_X_minus_1; + temp2 |= 1 << dimm_params[i].caslat_X_minus_2; + /* + * FIXME: If there was no entry for X-2 (X-1) in + * the SPD, then caslat_X_minus_2 + * (caslat_X_minus_1) contains either 255 or + * 0xFFFFFFFF because that's what the glorious + * __ilog2 function returns for an input of 0. + * On 32-bit PowerPC, left shift counts with bit + * 26 set (that the value of 255 or 0xFFFFFFFF + * will have), cause the destination register to + * be 0. That is why this works. + */ + temp1 &= temp2; + } + } + + /* + * Step 2: check each common CAS latency against tCK of each + * DIMM's SPD. + */ + lowest_good_caslat = 0; + temp2 = 0; + while (temp1) { + not_ok = 0; + temp2 = __ilog2(temp1); + debug("checking common caslat = %u\n", temp2); + + /* Check if this CAS latency will work on all DIMMs at tCK. */ + for (i = 0; i < number_of_dimms; i++) { + if (!dimm_params[i].n_ranks) { + continue; + } + if (dimm_params[i].caslat_X == temp2) { + if (mclk_ps >= dimm_params[i].tCKmin_X_ps) { + debug("CL = %u ok on DIMM %u at tCK=%u" + " ps with its tCKmin_X_ps of %u\n", + temp2, i, mclk_ps, + dimm_params[i].tCKmin_X_ps); + continue; + } else { + not_ok++; + } + } + + if (dimm_params[i].caslat_X_minus_1 == temp2) { + unsigned int tCKmin_X_minus_1_ps + = dimm_params[i].tCKmin_X_minus_1_ps; + if (mclk_ps >= tCKmin_X_minus_1_ps) { + debug("CL = %u ok on DIMM %u at " + "tCK=%u ps with its " + "tCKmin_X_minus_1_ps of %u\n", + temp2, i, mclk_ps, + tCKmin_X_minus_1_ps); + continue; + } else { + not_ok++; + } + } + + if (dimm_params[i].caslat_X_minus_2 == temp2) { + unsigned int tCKmin_X_minus_2_ps + = dimm_params[i].tCKmin_X_minus_2_ps; + if (mclk_ps >= tCKmin_X_minus_2_ps) { + debug("CL = %u ok on DIMM %u at " + "tCK=%u ps with its " + "tCKmin_X_minus_2_ps of %u\n", + temp2, i, mclk_ps, + tCKmin_X_minus_2_ps); + continue; + } else { + not_ok++; + } + } + } + + if (!not_ok) { + lowest_good_caslat = temp2; + } + + temp1 &= ~(1 << temp2); + } + + debug("lowest common SPD-defined CAS latency = %u\n", + lowest_good_caslat); + outpdimm->lowest_common_SPD_caslat = lowest_good_caslat; + + + /* + * Compute a common 'de-rated' CAS latency. + * + * The strategy here is to find the *highest* dereated cas latency + * with the assumption that all of the DIMMs will support a dereated + * CAS latency higher than or equal to their lowest dereated value. + */ + temp1 = 0; + for (i = 0; i < number_of_dimms; i++) { + temp1 = max(temp1, dimm_params[i].caslat_lowest_derated); + } + outpdimm->highest_common_derated_caslat = temp1; + debug("highest common dereated CAS latency = %u\n", temp1); + + /* Determine if all DIMMs ECC capable. */ + temp1 = 1; + for (i = 0; i < number_of_dimms; i++) { + if (dimm_params[i].n_ranks && dimm_params[i].edc_config != 2) { + temp1 = 0; + break; + } + } + if (temp1) { + debug("all DIMMs ECC capable\n"); + } else { + debug("Warning: not all DIMMs ECC capable, cant enable ECC\n"); + } + outpdimm->all_DIMMs_ECC_capable = temp1; + + + /* FIXME: move to somewhere else to validate. */ + if (mclk_ps > tCKmax_max_ps) { + printf("Warning: some of the installed DIMMs " + "can not operate this slowly.\n"); + return 1; + } + + /* + * Compute additive latency. + * + * For DDR1, additive latency should be 0. + * + * For DDR2, with ODT enabled, use "a value" less than ACTTORW, + * which comes from Trcd, and also note that: + * add_lat + caslat must be >= 4 + * + * For DDR3, FIXME additive latency determination + * + * When to use additive latency for DDR2: + * + * I. Because you are using CL=3 and need to do ODT on writes and + * want functionality. + * 1. Are you going to use ODT? (Does your board not have + * additional termination circuitry for DQ, DQS, DQS_, + * DM, RDQS, RDQS_ for x4/x8 configs?) + * 2. If so, is your lowest supported CL going to be 3? + * 3. If so, then you must set AL=1 because + * + * WL >= 3 for ODT on writes + * RL = AL + CL + * WL = RL - 1 + * -> + * WL = AL + CL - 1 + * AL + CL - 1 >= 3 + * AL + CL >= 4 + * QED + * + * RL >= 3 for ODT on reads + * RL = AL + CL + * + * Since CL aren't usually less than 2, AL=0 is a minimum, + * so the WL-derived AL should be the -- FIXME? + * + * II. Because you are using auto-precharge globally and want to + * use additive latency (posted CAS) to get more bandwidth. + * 1. Are you going to use auto-precharge mode globally? + * + * Use addtivie latency and compute AL to be 1 cycle less than + * tRCD, i.e. the READ or WRITE command is in the cycle + * immediately following the ACTIVATE command.. + * + * III. Because you feel like it or want to do some sort of + * degraded-performance experiment. + * 1. Do you just want to use additive latency because you feel + * like it? + * + * Validation: AL is less than tRCD, and within the other + * read-to-precharge constraints. + */ + + additive_latency = 0; + +#if defined(CONFIG_FSL_DDR2) + if (lowest_good_caslat < 4) { + additive_latency = picos_to_mclk(tRCD_ps) - lowest_good_caslat; + if (mclk_to_picos(additive_latency) > tRCD_ps) { + additive_latency = picos_to_mclk(tRCD_ps); + debug("setting additive_latency to %u because it was " + " greater than tRCD_ps\n", additive_latency); + } + } + +#elif defined(CONFIG_FSL_DDR3) +error "FIXME determine additive latency for DDR3" +#endif + + /* + * Validate additive latency + * FIXME: move to somewhere else to validate + * + * AL <= tRCD(min) + */ + if (mclk_to_picos(additive_latency) > tRCD_ps) { + printf("Error: invalid additive latency exceeds tRCD(min).\n"); + return 1; + } + + /* + * RL = CL + AL; RL >= 3 for ODT_RD_CFG to be enabled + * WL = RL - 1; WL >= 3 for ODT_WL_CFG to be enabled + * ADD_LAT (the register) must be set to a value less + * than ACTTORW if WL = 1, then AL must be set to 1 + * RD_TO_PRE (the register) must be set to a minimum + * tRTP + AL if AL is nonzero + */ + + /* + * Additive latency will be applied only if the memctl option to + * use it. + */ + outpdimm->additive_latency = additive_latency; + + return 0; +} diff --git a/cpu/mpc8xxx/ddr/main.c b/cpu/mpc8xxx/ddr/main.c new file mode 100644 index 00000000000..ac4b80c3aca --- /dev/null +++ b/cpu/mpc8xxx/ddr/main.c @@ -0,0 +1,473 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 as published by the Free Software Foundation. + */ + +/* + * Generic driver for Freescale DDR/DDR2/DDR3 memory controller. + * Based on code from spd_sdram.c + * Author: James Yang [at freescale.com] + */ + +#include <common.h> +#include <asm/fsl_ddr_sdram.h> + +#include "ddr.h" + +extern void fsl_ddr_set_lawbar( + const common_timing_params_t *memctl_common_params, + unsigned int memctl_interleaved, + unsigned int ctrl_num); + +/* processor specific function */ +extern void fsl_ddr_set_memctl_regs(const fsl_ddr_cfg_regs_t *regs, + unsigned int ctrl_num); + +/* Board-specific functions defined in each board's ddr.c */ +extern void fsl_ddr_get_spd(generic_spd_eeprom_t *ctrl_dimms_spd, + unsigned int ctrl_num); + +/* + * ASSUMPTIONS: + * - Same number of CONFIG_DIMM_SLOTS_PER_CTLR on each controller + * - Same memory data bus width on all controllers + * + * NOTES: + * + * The memory controller and associated documentation use confusing + * terminology when referring to the orgranization of DRAM. + * + * Here is a terminology translation table: + * + * memory controller/documention |industry |this code |signals + * -------------------------------|-----------|-----------|----------------- + * physical bank/bank |rank |rank |chip select (CS) + * logical bank/sub-bank |bank |bank |bank address (BA) + * page/row |row |page |row address + * ??? |column |column |column address + * + * The naming confusion is further exacerbated by the descriptions of the + * memory controller interleaving feature, where accesses are interleaved + * _BETWEEN_ two seperate memory controllers. This is configured only in + * CS0_CONFIG[INTLV_CTL] of each memory controller. + * + * memory controller documentation | number of chip selects + * | per memory controller supported + * --------------------------------|----------------------------------------- + * cache line interleaving | 1 (CS0 only) + * page interleaving | 1 (CS0 only) + * bank interleaving | 1 (CS0 only) + * superbank interleraving | depends on bank (chip select) + * | interleraving [rank interleaving] + * | mode used on every memory controller + * + * Even further confusing is the existence of the interleaving feature + * _WITHIN_ each memory controller. The feature is referred to in + * documentation as chip select interleaving or bank interleaving, + * although it is configured in the DDR_SDRAM_CFG field. + * + * Name of field | documentation name | this code + * -----------------------------|-----------------------|------------------ + * DDR_SDRAM_CFG[BA_INTLV_CTL] | Bank (chip select) | rank interleaving + * | interleaving + */ + +#ifdef DEBUG +const char *step_string_tbl[] = { + "STEP_GET_SPD", + "STEP_COMPUTE_DIMM_PARMS", + "STEP_COMPUTE_COMMON_PARMS", + "STEP_GATHER_OPTS", + "STEP_ASSIGN_ADDRESSES", + "STEP_COMPUTE_REGS", + "STEP_PROGRAM_REGS", + "STEP_ALL" +}; + +const char * step_to_string(unsigned int step) { + + unsigned int s = __ilog2(step); + + if ((1 << s) != step) + return step_string_tbl[7]; + + return step_string_tbl[s]; +} +#endif + +int step_assign_addresses(fsl_ddr_info_t *pinfo, + unsigned int dbw_cap_adj[], + unsigned int *memctl_interleaving, + unsigned int *rank_interleaving) +{ + int i, j; + + /* + * If a reduced data width is requested, but the SPD + * specifies a physically wider device, adjust the + * computed dimm capacities accordingly before + * assigning addresses. + */ + for (i = 0; i < CONFIG_NUM_DDR_CONTROLLERS; i++) { + unsigned int found = 0; + + switch (pinfo->memctl_opts[i].data_bus_width) { + case 2: + /* 16-bit */ + printf("can't handle 16-bit mode yet\n"); + break; + + case 1: + /* 32-bit */ + for (j = 0; j < CONFIG_DIMM_SLOTS_PER_CTLR; j++) { + unsigned int dw; + dw = pinfo->dimm_params[i][j].data_width; + if (pinfo->dimm_params[i][j].n_ranks + && (dw == 72 || dw == 64)) { + /* + * FIXME: can't really do it + * like this because this just + * further reduces the memory + */ + found = 1; + break; + } + } + if (found) { + dbw_cap_adj[i] = 1; + } + break; + + case 0: + /* 64-bit */ + break; + + default: + printf("unexpected data bus width " + "specified controller %u\n", i); + return 1; + } + } + + /* + * Check if all controllers are configured for memory + * controller interleaving. + */ + j = 0; + for (i = 0; i < CONFIG_NUM_DDR_CONTROLLERS; i++) { + if (pinfo->memctl_opts[i].memctl_interleaving) { + j++; + } + } + if (j == 2) { + *memctl_interleaving = 1; + } + + /* Check that all controllers are rank interleaving. */ + j = 0; + for (i = 0; i < CONFIG_NUM_DDR_CONTROLLERS; i++) { + if (pinfo->memctl_opts[i].ba_intlv_ctl) { + j++; + } + } + if (j == 2) { + *rank_interleaving = 1; + } + + if (*memctl_interleaving) { + phys_addr_t addr; + + /* + * If interleaving between memory controllers, + * make each controller start at a base address + * of 0. + * + * Also, if bank interleaving (chip select + * interleaving) is enabled on each memory + * controller, CS0 needs to be programmed to + * cover the entire memory range on that memory + * controller + * + * Bank interleaving also implies that each + * addressed chip select is identical in size. + */ + + for (i = 0; i < CONFIG_NUM_DDR_CONTROLLERS; i++) { + addr = 0; + for (j = 0; j < CONFIG_DIMM_SLOTS_PER_CTLR; j++) { + unsigned long long cap + = pinfo->dimm_params[i][j].capacity; + + pinfo->dimm_params[i][j].base_address = addr; + addr += (phys_addr_t)(cap >> dbw_cap_adj[i]); + } + } + } else { + /* + * Simple linear assignment if memory + * controllers are not interleaved. + */ + phys_size_t cur_memsize = 0; + for (i = 0; i < CONFIG_NUM_DDR_CONTROLLERS; i++) { + phys_size_t total_mem_per_ctlr = 0; + pinfo->common_timing_params[i].base_address = + (phys_addr_t)cur_memsize; + for (j = 0; j < CONFIG_DIMM_SLOTS_PER_CTLR; j++) { + /* Compute DIMM base addresses. */ + unsigned long long cap = + pinfo->dimm_params[i][j].capacity; + + pinfo->dimm_params[i][j].base_address = + (phys_addr_t)cur_memsize; + cur_memsize += cap >> dbw_cap_adj[i]; + total_mem_per_ctlr += cap >> dbw_cap_adj[i]; + } + pinfo->common_timing_params[i].total_mem = + total_mem_per_ctlr; + } + } + + return 0; +} + +phys_size_t +fsl_ddr_compute(fsl_ddr_info_t *pinfo, unsigned int start_step) +{ + unsigned int i, j; + unsigned int all_controllers_memctl_interleaving = 0; + unsigned int all_controllers_rank_interleaving = 0; + phys_size_t total_mem = 0; + + fsl_ddr_cfg_regs_t *ddr_reg = pinfo->fsl_ddr_config_reg; + common_timing_params_t *timing_params = pinfo->common_timing_params; + + /* data bus width capacity adjust shift amount */ + unsigned int dbw_capacity_adjust[CONFIG_NUM_DDR_CONTROLLERS]; + + for (i = 0; i < CONFIG_NUM_DDR_CONTROLLERS; i++) { + dbw_capacity_adjust[i] = 0; + } + + debug("starting at step %u (%s)\n", + start_step, step_to_string(start_step)); + + switch (start_step) { + case STEP_GET_SPD: + /* STEP 1: Gather all DIMM SPD data */ + for (i = 0; i < CONFIG_NUM_DDR_CONTROLLERS; i++) { + fsl_ddr_get_spd(pinfo->spd_installed_dimms[i], i); + } + + case STEP_COMPUTE_DIMM_PARMS: + /* STEP 2: Compute DIMM parameters from SPD data */ + + for (i = 0; i < CONFIG_NUM_DDR_CONTROLLERS; i++) { + for (j = 0; j < CONFIG_DIMM_SLOTS_PER_CTLR; j++) { + unsigned int retval; + generic_spd_eeprom_t *spd = + &(pinfo->spd_installed_dimms[i][j]); + dimm_params_t *pdimm = + &(pinfo->dimm_params[i][j]); + + retval = compute_dimm_parameters(spd, pdimm, i); + if (retval == 2) { + printf("Error: compute_dimm_parameters" + " non-zero returned FATAL value " + "for memctl=%u dimm=%u\n", i, j); + return 0; + } + if (retval) { + debug("Warning: compute_dimm_parameters" + " non-zero return value for memctl=%u " + "dimm=%u\n", i, j); + } + } + } + + case STEP_COMPUTE_COMMON_PARMS: + /* + * STEP 3: Compute a common set of timing parameters + * suitable for all of the DIMMs on each memory controller + */ + for (i = 0; i < CONFIG_NUM_DDR_CONTROLLERS; i++) { + debug("Computing lowest common DIMM" + " parameters for memctl=%u\n", i); + compute_lowest_common_dimm_parameters( + pinfo->dimm_params[i], + &timing_params[i], + CONFIG_DIMM_SLOTS_PER_CTLR); + } + + case STEP_GATHER_OPTS: + /* STEP 4: Gather configuration requirements from user */ + for (i = 0; i < CONFIG_NUM_DDR_CONTROLLERS; i++) { + debug("Reloading memory controller " + "configuration options for memctl=%u\n", i); + /* + * This "reloads" the memory controller options + * to defaults. If the user "edits" an option, + * next_step points to the step after this, + * which is currently STEP_ASSIGN_ADDRESSES. + */ + populate_memctl_options( + timing_params[i].all_DIMMs_registered, + &pinfo->memctl_opts[i], i); + } + + case STEP_ASSIGN_ADDRESSES: + /* STEP 5: Assign addresses to chip selects */ + step_assign_addresses(pinfo, + dbw_capacity_adjust, + &all_controllers_memctl_interleaving, + &all_controllers_rank_interleaving); + + case STEP_COMPUTE_REGS: + /* STEP 6: compute controller register values */ + debug("FSL Memory ctrl cg register computation\n"); + for (i = 0; i < CONFIG_NUM_DDR_CONTROLLERS; i++) { + if (timing_params[i].ndimms_present == 0) { + memset(&ddr_reg[i], 0, + sizeof(fsl_ddr_cfg_regs_t)); + continue; + } + + compute_fsl_memctl_config_regs( + &pinfo->memctl_opts[i], + &ddr_reg[i], &timing_params[i], + pinfo->dimm_params[i], + dbw_capacity_adjust[i]); + } + + default: + break; + } + + /* Compute the total amount of memory. */ + + /* + * If bank interleaving but NOT memory controller interleaving + * CS_BNDS describe the quantity of memory on each memory + * controller, so the total is the sum across. + */ + if (!all_controllers_memctl_interleaving + && all_controllers_rank_interleaving) { + total_mem = 0; + for (i = 0; i < CONFIG_NUM_DDR_CONTROLLERS; i++) { + total_mem += timing_params[i].total_mem; + } + + } else { + /* + * Compute the amount of memory available just by + * looking for the highest valid CSn_BNDS value. + * This allows us to also experiment with using + * only CS0 when using dual-rank DIMMs. + */ + unsigned int max_end = 0; + + for (i = 0; i < CONFIG_NUM_DDR_CONTROLLERS; i++) { + for (j = 0; j < CONFIG_CHIP_SELECTS_PER_CTRL; j++) { + fsl_ddr_cfg_regs_t *reg = &ddr_reg[i]; + if (reg->cs[j].config & 0x80000000) { + unsigned int end; + end = reg->cs[j].bnds & 0xFFF; + if (end > max_end) { + max_end = end; + } + } + } + } + +#if !defined(CONFIG_PHYS_64BIT) + /* Check for 4G or more with a 32-bit phys_addr_t. Bad. */ + if (max_end >= 0xff) { + printf("This U-Boot only supports < 4G of DDR\n"); + printf("You could rebuild it with CONFIG_PHYS_64BIT\n"); + return 0; /* Ensure DDR setup failure. */ + } +#endif + + total_mem = 1 + (((unsigned long long)max_end << 24ULL) + | 0xFFFFFFULL); + } + + return total_mem; +} + +/* + * fsl_ddr_sdram() -- this is the main function to be called by + * initdram() in the board file. + * + * It returns amount of memory configured in bytes. + */ +phys_size_t fsl_ddr_sdram(void) +{ + unsigned int i; + unsigned int memctl_interleaved; + phys_size_t total_memory; + fsl_ddr_info_t info; + + /* Reset info structure. */ + memset(&info, 0, sizeof(fsl_ddr_info_t)); + + /* Compute it once normally. */ + total_memory = fsl_ddr_compute(&info, STEP_GET_SPD); + + /* Check for memory controller interleaving. */ + memctl_interleaved = 0; + for (i = 0; i < CONFIG_NUM_DDR_CONTROLLERS; i++) { + memctl_interleaved += + info.memctl_opts[i].memctl_interleaving; + } + + if (memctl_interleaved) { + if (memctl_interleaved == CONFIG_NUM_DDR_CONTROLLERS) { + debug("memctl interleaving\n"); + /* + * Change the meaning of memctl_interleaved + * to be "boolean". + */ + memctl_interleaved = 1; + } else { + printf("Error: memctl interleaving not " + "properly configured on all controllers\n"); + while (1); + } + } + + /* Program configuration registers. */ + for (i = 0; i < CONFIG_NUM_DDR_CONTROLLERS; i++) { + debug("Programming controller %u\n", i); + if (info.common_timing_params[i].ndimms_present == 0) { + debug("No dimms present on controller %u; " + "skipping programming\n", i); + continue; + } + + fsl_ddr_set_memctl_regs(&(info.fsl_ddr_config_reg[i]), i); + } + + if (memctl_interleaved) { + const unsigned int ctrl_num = 0; + + /* Only set LAWBAR1 if memory controller interleaving is on. */ + fsl_ddr_set_lawbar(&info.common_timing_params[0], + memctl_interleaved, ctrl_num); + } else { + /* + * Memory controller interleaving is NOT on; + * set each lawbar individually. + */ + for (i = 0; i < CONFIG_NUM_DDR_CONTROLLERS; i++) { + fsl_ddr_set_lawbar(&info.common_timing_params[i], + 0, i); + } + } + + debug("total_memory = %llu\n", (u64)total_memory); + + return total_memory; +} diff --git a/cpu/mpc8xxx/ddr/options.c b/cpu/mpc8xxx/ddr/options.c new file mode 100644 index 00000000000..6c2b43c0c59 --- /dev/null +++ b/cpu/mpc8xxx/ddr/options.c @@ -0,0 +1,197 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 as published by the Free Software Foundation. + */ + +#include <common.h> +#include <asm/fsl_ddr_sdram.h> + +#include "ddr.h" + +/* Board-specific functions defined in each board's ddr.c */ +extern void fsl_ddr_board_options(memctl_options_t *popts, + unsigned int ctrl_num); + +unsigned int populate_memctl_options(int all_DIMMs_registered, + memctl_options_t *popts, + unsigned int ctrl_num) +{ + unsigned int i; + + /* Chip select options. */ + + /* Pick chip-select local options. */ + for (i = 0; i < CONFIG_CHIP_SELECTS_PER_CTRL; i++) { + /* If not DDR2, odt_rd_cfg and odt_wr_cfg need to be 0. */ + + /* only for single CS? */ + popts->cs_local_opts[i].odt_rd_cfg = 0; + + popts->cs_local_opts[i].odt_wr_cfg = 1; + popts->cs_local_opts[i].auto_precharge = 0; + } + + /* Pick interleaving mode. */ + + /* + * 0 = no interleaving + * 1 = interleaving between 2 controllers + */ + popts->memctl_interleaving = 0; + + /* + * 0 = cacheline + * 1 = page + * 2 = (logical) bank + * 3 = superbank (only if CS interleaving is enabled) + */ + popts->memctl_interleaving_mode = 0; + + /* + * 0: cacheline: bit 30 of the 36-bit physical addr selects the memctl + * 1: page: bit to the left of the column bits selects the memctl + * 2: bank: bit to the left of the bank bits selects the memctl + * 3: superbank: bit to the left of the chip select selects the memctl + * + * NOTE: ba_intlv (rank interleaving) is independent of memory + * controller interleaving; it is only within a memory controller. + * Must use superbank interleaving if rank interleaving is used and + * memory controller interleaving is enabled. + */ + + /* + * 0 = no + * 0x40 = CS0,CS1 + * 0x20 = CS2,CS3 + * 0x60 = CS0,CS1 + CS2,CS3 + * 0x04 = CS0,CS1,CS2,CS3 + */ + popts->ba_intlv_ctl = 0; + + /* Memory Organization Parameters */ + popts->registered_dimm_en = all_DIMMs_registered; + + /* Operational Mode Paramters */ + + /* Pick ECC modes */ +#ifdef CONFIG_DDR_ECC + popts->ECC_mode = 1; /* 0 = disabled, 1 = enabled */ +#else + popts->ECC_mode = 0; /* 0 = disabled, 1 = enabled */ +#endif + popts->ECC_init_using_memctl = 1; /* 0 = use DMA, 1 = use memctl */ + + /* + * Choose DQS config + * 0 for DDR1 + * 1 for DDR2 + */ +#if defined(CONFIG_FSL_DDR1) + popts->DQS_config = 0; +#elif defined(CONFIG_FSL_DDR2) + popts->DQS_config = 1; +#else +#error "Fix DQS for DDR3" +#endif + + /* Choose self-refresh during sleep. */ + popts->self_refresh_in_sleep = 1; + + /* Choose dynamic power management mode. */ + popts->dynamic_power = 0; + + /* 0 = 64-bit, 1 = 32-bit, 2 = 16-bit */ + popts->data_bus_width = 0; + + /* Choose burst length. */ + popts->burst_length = 4; /* has to be 4 for DDR2 */ + + /* Global Timing Parameters. */ + debug("mclk_ps = %u ps\n", get_memory_clk_period_ps()); + + /* Pick a caslat override. */ + popts->cas_latency_override = 0; + popts->cas_latency_override_value = 3; + if (popts->cas_latency_override) { + debug("using caslat override value = %u\n", + popts->cas_latency_override_value); + } + + /* Decide whether to use the computed derated latency */ + popts->use_derated_caslat = 0; + + /* Choose an additive latency. */ + popts->additive_latency_override = 0; + popts->additive_latency_override_value = 3; + if (popts->additive_latency_override) { + debug("using additive latency override value = %u\n", + popts->additive_latency_override_value); + } + + /* + * 2T_EN setting + * + * Factors to consider for 2T_EN: + * - number of DIMMs installed + * - number of components, number of active ranks + * - how much time you want to spend playing around + */ + popts->twoT_en = 1; + popts->threeT_en = 0; + + /* + * BSTTOPRE precharge interval + * + * Set this to 0 for global auto precharge + * + * FIXME: Should this be configured in picoseconds? + * Why it should be in ps: better understanding of this + * relative to actual DRAM timing parameters such as tRAS. + * e.g. tRAS(min) = 40 ns + */ + popts->bstopre = 0x100; + + /* Minimum CKE pulse width -- tCKE(MIN) */ + popts->tCKE_clock_pulse_width_ps + = mclk_to_picos(FSL_DDR_MIN_TCKE_PULSE_WIDTH_DDR); + + /* + * Window for four activates -- tFAW + * + * FIXME: UM: applies only to DDR2/DDR3 with eight logical banks only + * FIXME: varies depending upon number of column addresses or data + * FIXME: width, was considering looking at pdimm->primary_sdram_width + */ +#if defined(CONFIG_FSL_DDR1) + popts->tFAW_window_four_activates_ps = mclk_to_picos(1); + +#elif defined(CONFIG_FSL_DDR2) + /* + * x4/x8; some datasheets have 35000 + * x16 wide columns only? Use 50000? + */ + popts->tFAW_window_four_activates_ps = 37500; + +#elif defined(CONFIG_FSL_DDR3) +#error "FIXME determine four activates for DDR3" +#endif + + /* ODT should only be used for DDR2 */ + + /* FIXME? */ + + /* + * Interleaving checks. + * + * If memory controller interleaving is enabled, then the data + * bus widths must be programmed identically for the 2 memory + * controllers. + */ + + fsl_ddr_board_options(popts, ctrl_num); + + return 0; +} diff --git a/cpu/mpc8xxx/ddr/util.c b/cpu/mpc8xxx/ddr/util.c new file mode 100644 index 00000000000..27c135b112c --- /dev/null +++ b/cpu/mpc8xxx/ddr/util.c @@ -0,0 +1,103 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 as published by the Free Software Foundation. + */ + +#include <common.h> +#include <asm/fsl_law.h> + +#include "ddr.h" + +unsigned int fsl_ddr_get_mem_data_rate(void); + +/* + * Round mclk_ps to nearest 10 ps in memory controller code. + * + * If an imprecise data rate is too high due to rounding error + * propagation, compute a suitably rounded mclk_ps to compute + * a working memory controller configuration. + */ +unsigned int get_memory_clk_period_ps(void) +{ + unsigned int mclk_ps; + + mclk_ps = 2000000000000ULL / fsl_ddr_get_mem_data_rate(); + /* round to nearest 10 ps */ + return 10 * ((mclk_ps + 5) / 10); +} + +/* Convert picoseconds into DRAM clock cycles (rounding up if needed). */ +unsigned int picos_to_mclk(unsigned int picos) +{ + const unsigned long long ULL_2e12 = 2000000000000ULL; + const unsigned long long ULL_8Fs = 0xFFFFFFFFULL; + unsigned long long clks; + unsigned long long clks_temp; + + if (!picos) + return 0; + + clks = fsl_ddr_get_mem_data_rate() * (unsigned long long) picos; + clks_temp = clks; + clks = clks / ULL_2e12; + if (clks_temp % ULL_2e12) { + clks++; + } + + if (clks > ULL_8Fs) { + clks = ULL_8Fs; + } + + return (unsigned int) clks; +} + +unsigned int mclk_to_picos(unsigned int mclk) +{ + return get_memory_clk_period_ps() * mclk; +} + +void +__fsl_ddr_set_lawbar(const common_timing_params_t *memctl_common_params, + unsigned int memctl_interleaved, + unsigned int ctrl_num) +{ + /* + * If no DIMMs on this controller, do not proceed any further. + */ + if (!memctl_common_params->ndimms_present) { + return; + } + + if (ctrl_num == 0) { + /* + * Set up LAW for DDR controller 1 space. + */ + unsigned int lawbar1_target_id = memctl_interleaved + ? LAW_TRGT_IF_DDR_INTRLV : LAW_TRGT_IF_DDR_1; + + if (set_ddr_laws(memctl_common_params->base_address, + memctl_common_params->total_mem, + lawbar1_target_id) < 0) { + printf("ERROR\n"); + return ; + } + } else if (ctrl_num == 1) { + if (set_ddr_laws(memctl_common_params->base_address, + memctl_common_params->total_mem, + LAW_TRGT_IF_DDR_2) < 0) { + printf("ERROR\n"); + return ; + } + } else { + printf("unexpected controller number %u in %s\n", + ctrl_num, __FUNCTION__); + } +} + +__attribute__((weak, alias("__fsl_ddr_set_lawbar"))) void +fsl_ddr_set_lawbar(const common_timing_params_t *memctl_common_params, + unsigned int memctl_interleaved, + unsigned int ctrl_num); diff --git a/include/asm-ppc/fsl_ddr_sdram.h b/include/asm-ppc/fsl_ddr_sdram.h new file mode 100644 index 00000000000..8adde34247b --- /dev/null +++ b/include/asm-ppc/fsl_ddr_sdram.h @@ -0,0 +1,137 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * Version 2 as published by the Free Software Foundation. + */ + +#ifndef FSL_DDR_MEMCTL_H +#define FSL_DDR_MEMCTL_H + +/* + * Pick a basic DDR Technology. + */ +#include <ddr_spd.h> + +#define SDRAM_TYPE_DDR1 2 +#define SDRAM_TYPE_DDR2 3 +#define SDRAM_TYPE_LPDDR1 6 +#define SDRAM_TYPE_DDR3 7 + +#if defined(CONFIG_FSL_DDR1) +#define FSL_DDR_MIN_TCKE_PULSE_WIDTH_DDR (1) +typedef ddr1_spd_eeprom_t generic_spd_eeprom_t; +#ifndef CONFIG_FSL_SDRAM_TYPE +#define CONFIG_FSL_SDRAM_TYPE SDRAM_TYPE_DDR1 +#endif +#elif defined(CONFIG_FSL_DDR2) +#define FSL_DDR_MIN_TCKE_PULSE_WIDTH_DDR (3) +typedef ddr2_spd_eeprom_t generic_spd_eeprom_t; +#ifndef CONFIG_FSL_SDRAM_TYPE +#define CONFIG_FSL_SDRAM_TYPE SDRAM_TYPE_DDR2 +#endif +#elif defined(CONFIG_FSL_DDR3) +#define FSL_DDR_MIN_TCKE_PULSE_WIDTH_DDR (3) /* FIXME */ +typedef ddr3_spd_eeprom_t generic_spd_eeprom_t; +#endif + +/* Record of register values computed */ +typedef struct fsl_ddr_cfg_regs_s { + struct { + unsigned int bnds; + unsigned int config; + unsigned int config_2; + } cs[CONFIG_CHIP_SELECTS_PER_CTRL]; + unsigned int timing_cfg_3; + unsigned int timing_cfg_0; + unsigned int timing_cfg_1; + unsigned int timing_cfg_2; + unsigned int ddr_sdram_cfg; + unsigned int ddr_sdram_cfg_2; + unsigned int ddr_sdram_mode; + unsigned int ddr_sdram_mode_2; + unsigned int ddr_sdram_md_cntl; + unsigned int ddr_sdram_interval; + unsigned int ddr_data_init; + unsigned int ddr_sdram_clk_cntl; + unsigned int ddr_init_addr; + unsigned int ddr_init_ext_addr; + unsigned int timing_cfg_4; + unsigned int timing_cfg_5; + unsigned int ddr_zq_cntl; + unsigned int ddr_wrlvl_cntl; + unsigned int ddr_pd_cntl; + unsigned int ddr_sr_cntr; + unsigned int ddr_sdram_rcw_1; + unsigned int ddr_sdram_rcw_2; +} fsl_ddr_cfg_regs_t; + +typedef struct memctl_options_partial_s { + unsigned int all_DIMMs_ECC_capable; + unsigned int all_DIMMs_tCKmax_ps; + unsigned int all_DIMMs_burst_lengths_bitmask; + unsigned int all_DIMMs_registered; + unsigned int all_DIMMs_unbuffered; + /* unsigned int lowest_common_SPD_caslat; */ + unsigned int all_DIMMs_minimum_tRCD_ps; +} memctl_options_partial_t; + +/* + * Generalized parameters for memory controller configuration, + * might be a little specific to the FSL memory controller + */ +typedef struct memctl_options_s { + /* + * Memory organization parameters + * + * if DIMM is present in the system + * where DIMMs are with respect to chip select + * where chip selects are with respect to memory boundaries + */ + unsigned int registered_dimm_en; /* use registered DIMM support */ + + /* Options local to a Chip Select */ + struct cs_local_opts_s { + unsigned int auto_precharge; + unsigned int odt_rd_cfg; + unsigned int odt_wr_cfg; + } cs_local_opts[CONFIG_CHIP_SELECTS_PER_CTRL]; + + /* Special configurations for chip select */ + unsigned int memctl_interleaving; + unsigned int memctl_interleaving_mode; + unsigned int ba_intlv_ctl; + + /* Operational mode parameters */ + unsigned int ECC_mode; /* Use ECC? */ + /* Initialize ECC using memory controller? */ + unsigned int ECC_init_using_memctl; + unsigned int DQS_config; /* Use DQS? maybe only with DDR2? */ + /* SREN - self-refresh during sleep */ + unsigned int self_refresh_in_sleep; + unsigned int dynamic_power; /* DYN_PWR */ + /* memory data width to use (16-bit, 32-bit, 64-bit) */ + unsigned int data_bus_width; + unsigned int burst_length; /* 4, 8 */ + + /* Global Timing Parameters */ + unsigned int cas_latency_override; + unsigned int cas_latency_override_value; + unsigned int use_derated_caslat; + unsigned int additive_latency_override; + unsigned int additive_latency_override_value; + + unsigned int clk_adjust; /* */ + unsigned int cpo_override; + unsigned int write_data_delay; /* DQS adjust */ + unsigned int half_strength_driver_enable; + unsigned int twoT_en; + unsigned int threeT_en; + unsigned int bstopre; + unsigned int tCKE_clock_pulse_width_ps; /* tCKE */ + unsigned int tFAW_window_four_activates_ps; /* tFAW -- FOUR_ACT */ +} memctl_options_t; + +extern phys_size_t fsl_ddr_sdram(void); +#endif |