summaryrefslogtreecommitdiffstats
path: root/drivers/ddr/fsl/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/ddr/fsl/main.c')
-rw-r--r--drivers/ddr/fsl/main.c444
1 files changed, 444 insertions, 0 deletions
diff --git a/drivers/ddr/fsl/main.c b/drivers/ddr/fsl/main.c
new file mode 100644
index 0000000000..b0df34c933
--- /dev/null
+++ b/drivers/ddr/fsl/main.c
@@ -0,0 +1,444 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2008-2014 Freescale Semiconductor, Inc.
+ */
+
+/*
+ * 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 <soc/fsl/fsl_ddr_sdram.h>
+#include <linux/log2.h>
+#include "fsl_ddr.h"
+
+/*
+ * 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
+ */
+
+static unsigned long long step_assign_addresses_linear(struct fsl_ddr_info *pinfo,
+ unsigned long long current_mem_base)
+{
+ int i, j;
+ unsigned long long total_mem = 0;
+
+ /*
+ * Simple linear assignment if memory
+ * controllers are not interleaved.
+ */
+ for (i = 0; i < pinfo->num_ctrls; i++) {
+ struct fsl_ddr_controller *c = &pinfo->c[i];
+ unsigned long long total_ctlr_mem = 0;
+
+ c->common_timing_params.base_address = current_mem_base;
+
+ for (j = 0; j < c->dimm_slots_per_ctrl; j++) {
+ /* Compute DIMM base addresses. */
+ unsigned long long cap = c->dimm_params[j].capacity >>
+ pinfo->c[i].dbw_capacity_adjust;
+
+ c->dimm_params[j].base_address = current_mem_base;
+ debug("ctrl %d dimm %d base 0x%llx\n", i, j, current_mem_base);
+ current_mem_base += cap;
+ total_ctlr_mem += cap;
+ }
+ debug("ctrl %d total 0x%llx\n", i, total_ctlr_mem);
+ c->common_timing_params.total_mem = total_ctlr_mem;
+ total_mem += total_ctlr_mem;
+ }
+
+ return total_mem;
+}
+
+static unsigned long long step_assign_addresses_interleaved(struct fsl_ddr_info *pinfo,
+ unsigned long long current_mem_base)
+{
+ unsigned long long total_mem, total_ctlr_mem;
+ unsigned long long rank_density, ctlr_density = 0;
+ int i;
+
+ rank_density = pinfo->c[0].dimm_params[0].rank_density >>
+ pinfo->c[0].dbw_capacity_adjust;
+
+ switch (pinfo->c[0].memctl_opts.ba_intlv_ctl &
+ FSL_DDR_CS0_CS1_CS2_CS3) {
+ case FSL_DDR_CS0_CS1_CS2_CS3:
+ ctlr_density = 4 * rank_density;
+ break;
+ case FSL_DDR_CS0_CS1:
+ case FSL_DDR_CS0_CS1_AND_CS2_CS3:
+ ctlr_density = 2 * rank_density;
+ break;
+ case FSL_DDR_CS2_CS3:
+ default:
+ ctlr_density = rank_density;
+ break;
+ }
+
+ debug("rank density is 0x%llx, ctlr density is 0x%llx\n",
+ rank_density, ctlr_density);
+
+ for (i = 0; i < pinfo->num_ctrls; i++) {
+ struct fsl_ddr_controller *c = &pinfo->c[i];
+
+ if (c->memctl_opts.memctl_interleaving) {
+ switch (c->memctl_opts.memctl_interleaving_mode) {
+ case FSL_DDR_256B_INTERLEAVING:
+ case FSL_DDR_CACHE_LINE_INTERLEAVING:
+ case FSL_DDR_PAGE_INTERLEAVING:
+ case FSL_DDR_BANK_INTERLEAVING:
+ case FSL_DDR_SUPERBANK_INTERLEAVING:
+ total_ctlr_mem = 2 * ctlr_density;
+ break;
+ case FSL_DDR_3WAY_1KB_INTERLEAVING:
+ case FSL_DDR_3WAY_4KB_INTERLEAVING:
+ case FSL_DDR_3WAY_8KB_INTERLEAVING:
+ total_ctlr_mem = 3 * ctlr_density;
+ break;
+ case FSL_DDR_4WAY_1KB_INTERLEAVING:
+ case FSL_DDR_4WAY_4KB_INTERLEAVING:
+ case FSL_DDR_4WAY_8KB_INTERLEAVING:
+ total_ctlr_mem = 4 * ctlr_density;
+ break;
+ default:
+ panic("Unknown interleaving mode");
+ }
+ c->common_timing_params.base_address = current_mem_base;
+ c->common_timing_params.total_mem = total_ctlr_mem;
+ total_mem = current_mem_base + total_ctlr_mem;
+ debug("ctrl %d base 0x%llx\n", i, current_mem_base);
+ debug("ctrl %d total 0x%llx\n", i, total_ctlr_mem);
+ } else {
+ total_mem += step_assign_addresses_linear(pinfo, current_mem_base);
+ }
+ }
+
+ return total_mem;
+}
+
+static unsigned long long step_assign_addresses(struct fsl_ddr_info *pinfo)
+{
+ unsigned int i, j;
+ unsigned long long total_mem;
+
+ /*
+ * 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 < pinfo->num_ctrls; i++) {
+ struct fsl_ddr_controller *c = &pinfo->c[i];
+ unsigned int found = 0;
+
+ switch (c->memctl_opts.data_bus_width) {
+ case 2:
+ /* 16-bit */
+ for (j = 0; j < c->dimm_slots_per_ctrl; j++) {
+ unsigned int dw;
+ if (!c->dimm_params[j].n_ranks)
+ continue;
+ dw = c->dimm_params[j].primary_sdram_width;
+ if ((dw == 72 || dw == 64)) {
+ pinfo->c[i].dbw_capacity_adjust = 2;
+ break;
+ } else if ((dw == 40 || dw == 32)) {
+ pinfo->c[i].dbw_capacity_adjust = 1;
+ break;
+ }
+ }
+ break;
+
+ case 1:
+ /* 32-bit */
+ for (j = 0; j < c->dimm_slots_per_ctrl; j++) {
+ unsigned int dw;
+ dw = c->dimm_params[j].data_width;
+ if (c->dimm_params[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)
+ pinfo->c[i].dbw_capacity_adjust = 1;
+
+ break;
+
+ case 0:
+ /* 64-bit */
+ break;
+
+ default:
+ printf("unexpected data bus width "
+ "specified controller %u\n", i);
+ return 1;
+ }
+ debug("dbw_cap_adj[%d]=%d\n", i, pinfo->c[i].dbw_capacity_adjust);
+ }
+
+ if (pinfo->c[0].memctl_opts.memctl_interleaving)
+ total_mem = step_assign_addresses_interleaved(pinfo, pinfo->mem_base);
+ else
+ total_mem = step_assign_addresses_linear(pinfo, pinfo->mem_base);
+
+ debug("Total mem by %s is 0x%llx\n", __func__, total_mem);
+
+ return total_mem;
+}
+
+static int compute_dimm_parameters(struct fsl_ddr_controller *c,
+ struct spd_eeprom *spd,
+ struct dimm_params *pdimm)
+{
+ const memctl_options_t *popts = &c->memctl_opts;
+ int ret = -EINVAL;
+
+ memset(pdimm, 0, sizeof(*pdimm));
+
+ if (is_ddr1(popts))
+ ret = ddr1_compute_dimm_parameters(c, (void *)spd, pdimm);
+ else if (is_ddr2(popts))
+ ret = ddr2_compute_dimm_parameters(c, (void *)spd, pdimm);
+ else if (is_ddr3(popts))
+ ret = ddr3_compute_dimm_parameters(c, (void *)spd, pdimm);
+ else if (is_ddr4(popts))
+ ret = ddr4_compute_dimm_parameters(c, (void *)spd, pdimm);
+
+ return ret;
+}
+
+static unsigned long long fsl_ddr_compute(struct fsl_ddr_info *pinfo)
+{
+ unsigned int i, j;
+ unsigned long long total_mem = 0;
+ int assert_reset = 0;
+ int retval;
+ unsigned int max_end = 0;
+
+ /* STEP 2: Compute DIMM parameters from SPD data */
+ for (i = 0; i < pinfo->num_ctrls; i++) {
+ struct fsl_ddr_controller *c = &pinfo->c[i];
+
+ if (!c->spd_installed_dimms)
+ continue;
+
+ for (j = 0; j < c->dimm_slots_per_ctrl; j++) {
+ struct spd_eeprom *spd = &c->spd_installed_dimms[j];
+ struct dimm_params *pdimm = &c->dimm_params[j];
+
+ retval = compute_dimm_parameters(c, spd, pdimm);
+ 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);
+ }
+ }
+ }
+
+ /*
+ * STEP 3: Compute a common set of timing parameters
+ * suitable for all of the DIMMs on each memory controller
+ */
+ for (i = 0; i < pinfo->num_ctrls; i++) {
+ struct fsl_ddr_controller *c = &pinfo->c[i];
+
+ debug("Computing lowest common DIMM parameters for memctl=%u\n",
+ i);
+ compute_lowest_common_dimm_parameters(c);
+ }
+
+ /* STEP 4: Gather configuration requirements from user */
+ for (i = 0; i < pinfo->num_ctrls; i++) {
+ struct fsl_ddr_controller *c = &pinfo->c[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(c);
+ /*
+ * For RDIMMs, JEDEC spec requires clocks to be stable
+ * before reset signal is deasserted. For the boards
+ * using fixed parameters, this function should be
+ * be called from board init file.
+ */
+ if (c->common_timing_params.all_dimms_registered)
+ assert_reset = 1;
+ }
+
+ /* STEP 5: Assign addresses to chip selects */
+ check_interleaving_options(pinfo);
+ total_mem = step_assign_addresses(pinfo);
+ debug("Total mem %llu assigned\n", total_mem);
+
+ /* STEP 6: compute controller register values */
+ debug("FSL Memory ctrl register computation\n");
+ for (i = 0; i < pinfo->num_ctrls; i++) {
+ struct fsl_ddr_controller *c = &pinfo->c[i];
+
+ if (c->common_timing_params.ndimms_present == 0) {
+ memset(&c->fsl_ddr_config_reg, 0,
+ sizeof(fsl_ddr_cfg_regs_t));
+ continue;
+ }
+
+ compute_fsl_memctl_config_regs(c);
+ }
+
+ /*
+ * 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.
+ */
+
+ for (i = 0; i < pinfo->num_ctrls; i++) {
+ struct fsl_ddr_controller *c = &pinfo->c[i];
+
+ for (j = 0; j < c->chip_selects_per_ctrl; j++) {
+ fsl_ddr_cfg_regs_t *reg = &c->fsl_ddr_config_reg;
+ if (reg->cs[j].config & 0x80000000) {
+ unsigned int end;
+ /*
+ * 0xfffffff is a special value we put
+ * for unused bnds
+ */
+ if (reg->cs[j].bnds == 0xffffffff)
+ continue;
+ end = reg->cs[j].bnds & 0xffff;
+ if (end > max_end) {
+ max_end = end;
+ }
+ }
+ }
+ }
+
+ total_mem = 1 + (((unsigned long long)max_end << 24ULL) |
+ 0xFFFFFFULL) - pinfo->mem_base;
+
+ return total_mem;
+}
+
+phys_size_t fsl_ddr_sdram(struct fsl_ddr_info *pinfo)
+{
+ unsigned int i;
+ unsigned long long total_memory;
+ int deassert_reset = 0;
+
+ total_memory = fsl_ddr_compute(pinfo);
+
+ /* setup 3-way interleaving before enabling DDRC */
+ switch (pinfo->c[0].memctl_opts.memctl_interleaving_mode) {
+ case FSL_DDR_3WAY_1KB_INTERLEAVING:
+ case FSL_DDR_3WAY_4KB_INTERLEAVING:
+ case FSL_DDR_3WAY_8KB_INTERLEAVING:
+ fsl_ddr_set_intl3r(
+ pinfo->c[0].memctl_opts.
+ memctl_interleaving_mode);
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Program configuration registers.
+ * JEDEC specs requires clocks to be stable before deasserting reset
+ * for RDIMMs. Clocks start after chip select is enabled and clock
+ * control register is set. During step 1, all controllers have their
+ * registers set but not enabled. Step 2 proceeds after deasserting
+ * reset through board FPGA or GPIO.
+ * For non-registered DIMMs, initialization can go through but it is
+ * also OK to follow the same flow.
+ */
+ for (i = 0; i < pinfo->num_ctrls; i++) {
+ struct fsl_ddr_controller *c = &pinfo->c[i];
+
+ if (c->common_timing_params.all_dimms_registered)
+ deassert_reset = 1;
+ }
+ for (i = 0; i < pinfo->num_ctrls; i++) {
+ struct fsl_ddr_controller *c = &pinfo->c[i];
+
+ debug("Programming controller %u\n", i);
+ if (c->common_timing_params.ndimms_present == 0) {
+ debug("No dimms present on controller %u; "
+ "skipping programming\n", i);
+ continue;
+ }
+ /*
+ * The following call with step = 1 returns before enabling
+ * the controller. It has to finish with step = 2 later.
+ */
+ fsl_ddr_set_memctl_regs(c, deassert_reset ? 1 : 0);
+ }
+ if (deassert_reset) {
+ for (i = 0; i < pinfo->num_ctrls; i++) {
+ struct fsl_ddr_controller *c = &pinfo->c[i];
+
+ /* Call with step = 2 to continue initialization */
+ fsl_ddr_set_memctl_regs(c, 2);
+ }
+ }
+
+ debug("total_memory by %s = %llu\n", __func__, total_memory);
+
+ return total_memory;
+}