text: /* ----------------------------------------------------------------------------
   *         SAM Software Package License
   * ----------------------------------------------------------------------------
   * Copyright (c) 2015, Atmel Corporation
   *
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *
   * - Redistributions of source code must retain the above copyright notice,
   * this list of conditions and the disclaimer below.
   *
   * Atmel's name may not be used to endorse or promote products derived from
   * this software without specific prior written permission.
   *
   * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
   * DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
   * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
   * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   * ----------------------------------------------------------------------------
   */
  
  #include "chip.h"
  #include "peripherals/mpddrc.h"
  #include "peripherals/sfrbu.h"
  #include "peripherals/pmc.h"
  #include "trace.h"
  #include "timer.h"
  #include "compiler.h"
  #include <stdlib.h>
  
  static void _set_ddr_timings(struct _mpddrc_desc* desc)
  {
  	MPDDRC->MPDDRC_TPR0 = desc->tpr0;
  	MPDDRC->MPDDRC_TPR1 = desc->tpr1;
  	MPDDRC->MPDDRC_TPR2 = desc->tpr2;
  }
  
  static uint32_t _compute_ba_offset(void)
  {
  	/* Compute BA[] offset according to CR configuration */
  	uint32_t offset = (MPDDRC->MPDDRC_CR & MPDDRC_CR_NC_Msk) + 9;
  	if (!(MPDDRC->MPDDRC_CR & MPDDRC_CR_DECOD_INTERLEAVED))
  		offset += ((MPDDRC->MPDDRC_CR & MPDDRC_CR_NR_Msk) >> 2) + 11;
  
  	offset += (MPDDRC->MPDDRC_MD & MPDDRC_MD_DBW) ? 1 : 2;
  
  	return offset;
  }
  
  /**
   * \brief Send a NOP command
   */
  static void _send_nop_cmd(void)
  {
  	MPDDRC->MPDDRC_MR = MPDDRC_MR_MODE_NOP_CMD;
  	/* Perform a write to a DDR memory access to acknoledge the command */
  	*((uint32_t *)DDR_CS_ADDR) = 0;
  }
  
  static void _send_lmr_cmd(void)
  {
  	MPDDRC->MPDDRC_MR = MPDDRC_MR_MODE_LMR_CMD;
  	/* Perform a write to a DDR memory access to acknoledge the command */
  	*((uint32_t *)DDR_CS_ADDR) = 0u;
  }
  
  static void _send_ext_lmr_cmd(uint32_t opcode, uint32_t ba_offset)
  {
  	MPDDRC->MPDDRC_MR = MPDDRC_MR_MODE_EXT_LMR_CMD;
  	/* Perform a write to a DDR memory access to acknoledge the command */
  	*((uint32_t *)(DDR_CS_ADDR + (opcode << ba_offset))) = 0u;
  }
  
  static void _send_normal_cmd(void)
  {
  	MPDDRC->MPDDRC_MR = MPDDRC_MR_MODE_NORMAL_CMD;
  	/* Perform a write to a DDR memory access to acknoledge the command */
  	*((uint32_t *)DDR_CS_ADDR) = 0;
  }
  
  static void _send_precharge_cmd(void)
  {
  	MPDDRC->MPDDRC_MR = MPDDRC_MR_MODE_PRCGALL_CMD;
  	/* Perform a write to a DDR memory access to acknoledge the command */
  	*((uint32_t *)DDR_CS_ADDR) = 0;
  }
  
  static void _send_refresh_cmd(void)
  {
  	MPDDRC->MPDDRC_MR = MPDDRC_MR_MODE_RFSH_CMD;
  	/* Perform a write to a DDR memory access to acknoledge the command */
  	*((uint32_t *)DDR_CS_ADDR) = 0;
  }
  
  #ifdef CONFIG_HAVE_DDR3
  
  static void _send_calib_cmd(void)
  {
  	MPDDRC->MPDDRC_MR = MPDDRC_MR_MODE_DEEP_CALIB_MD;
  	/* Perform a write to a DDR memory access to acknoledge the command */
  	*((uint32_t *)DDR_CS_ADDR) = 0;
  }
  
  static void _configure_ddr3(struct _mpddrc_desc* desc)
  {
  	/* Timings */
  	_set_ddr_timings(desc);
  	uint32_t ba_offset = _compute_ba_offset();
  
  	/*
  	 * Step 3: Issue a NOP command to the memory controller using
  	 * its mode register (MPDDRC_MR).
  	 */
  	_send_nop_cmd();
  
  	/*
  	 * Step 4: A pause of at least 500us must be observed before a
  	 * single toggle.
  	 */
  	timer_sleep(50);
  	/*
  	 * Step 5: Issue a NOP command to the memory controller using
  	 * its mode register (MPDDRC_MR). CKE is now driven high.
  	 */
  	_send_nop_cmd();
  	timer_sleep(1);
  	/*
  	 * Step 6: Issue Extended Mode Register Set 2 (EMRS2) cycle to
  	 * choose between commercial or high temperature
  	 * operations.
  	 */
  	_send_ext_lmr_cmd(0x2, ba_offset);
  	timer_sleep(1);
  	/*
  	 * Step 7: Issue Extended Mode Register Set 3 (EMRS3) cycle to set
  	 * the Extended Mode Register to 0.
  	 */
  	_send_ext_lmr_cmd(0x3, ba_offset);
  	timer_sleep(1);
  	/*
  	 * Step 8: Issue Extended Mode Register Set 1 (EMRS1) cycle to
  	 * disable and to program O.D.S. (Output Driver Strength).
  	 */
  	_send_ext_lmr_cmd(0x1, ba_offset);
  	timer_sleep(1);
  	/*
  	 * Step 9: Write a one to the DLL bit (enable DLL reset) in the MPDDRC
  	 * Configuration Register (MPDDRC_CR)
  	 */
  	/* Not done for DDR3 */
  
  	/*
  	 * Step 10: Issue a Mode Register Set (MRS) cycle to reset DLL.
  	 */
  	_send_lmr_cmd();
  	timer_sleep(5);
  	/*
  	 * Step 11: Issue a Calibration command (MRS) cycle to calibrate RTT and
  	 * RON values for the Process Voltage Temperature (PVT).
  	 */
  	_send_calib_cmd();
  	timer_sleep(1);
  
  	/*
  	 * Step 12: A Normal Mode command is provided.
  	 * Program the Normal mode in the MPDDRC_MR and perform a write access
  	 * to any DDR3-SDRAM address to acknowledge this command.
  	 */
  	_send_normal_cmd();
  	/*
  	 * Step 13: Perform a write access to any DDR3-SDRAM address.
  	 */
  	*((uint32_t *)(DDR_CS_ADDR)) = 0;
  }
  
  #endif
  
  static void _configure_ddr2(struct _mpddrc_desc* desc)
  {
  	/* Timings */
  	_set_ddr_timings(desc);
  	uint32_t ba_offset = _compute_ba_offset();
  
  	/* Step 3: An NOP command is issued to the DDR2-SDRAM. Program
  	 * the NOP command into the Mode Register and wait minimum 200
  	 * us */
  	_send_nop_cmd();
  	timer_sleep(20);
  
  	/* Step 4:  Issue a NOP command. */
  	_send_nop_cmd();
  	timer_sleep(1);
  
  	/* Step 5: Issue all banks precharge command. */
  	_send_precharge_cmd();
  	timer_sleep(1);
  
  	/* Step 6: Issue an Extended Mode Register set (EMRS2) cycle
  	 * to chose between commercialor high  temperature
  	 * operations. */
  	_send_ext_lmr_cmd(0x2, ba_offset);
  	timer_sleep(1);
  
  	/* Step 7: Issue an Extended Mode Register set (EMRS3) cycle
  	 * to set all registers to 0. */
  	_send_ext_lmr_cmd(0x3, ba_offset);
  	timer_sleep(1);
  
  	/* Step 8:  Issue an Extended Mode Register set (EMRS1) cycle
  	 * to enable DLL. */
  	_send_ext_lmr_cmd(0x1, ba_offset);
  	timer_sleep(1);
  
  	/* Step 9:  Program DLL field into the Configuration Register. */
  	MPDDRC->MPDDRC_CR |= MPDDRC_CR_DLL_RESET_ENABLED;
  
  	/* Step 10: A Mode Register set (MRS) cycle is issued to reset DLL. */
  	MPDDRC->MPDDRC_MR = MPDDRC_MR_MODE_LMR_CMD;
  	/* Perform a write to a DDR memory access to acknoledge the command */
  	*((uint32_t *)DDR_CS_ADDR) = 0;
  
  	timer_sleep(1);
  
  	/* Step 11: Issue all banks precharge command to the DDR2-SDRAM. */
  	_send_precharge_cmd();
  	timer_sleep(1);
  
  	/* Step 12: Two auto-refresh (CBR) cycles are
  	 * provided. Program the auto refresh command (CBR) into the
  	 * Mode Register. */
  	_send_refresh_cmd();
  	timer_sleep(1);
  	_send_refresh_cmd();
  	timer_sleep(1);
  
  	/* Step 13: Program DLL field into the Configuration Register
  	 * to low(Disable DLL reset). */
  	MPDDRC->MPDDRC_CR &= ~MPDDRC_CR_DLL_RESET_ENABLED;
  
  	/* Step 14: Issue a Mode Register set (MRS) cycle to program
  	 * the parameters of the DDR2-SDRAM devices. */
  	_send_lmr_cmd();
  	timer_sleep(1);
  
  	/* Step 15: Program OCD field into the Configuration Register
  	 * to high (OCD calibration default). */
  	MPDDRC->MPDDRC_CR |= MPDDRC_CR_OCD_DDR2_DEFAULT_CALIB;
  
  	/* Step 16: An Extended Mode Register set (EMRS1) cycle is
  	 * issued to OCD default value. */
  	_send_ext_lmr_cmd(0x1, ba_offset);
  	timer_sleep(1);
  
  	/* Step 19,20: A mode Normal command is provided. Program the
  	 * Normal mode into Mode Register. */
  	_send_normal_cmd();
  	timer_sleep(1);
  }
  
  extern void mpddrc_configure(struct _mpddrc_desc* desc)
  {
  	/* Retrieve the current resolution to put it back later */
  	uint32_t resolution = timer_get_resolution();
  	/* Configure time to have 10 microseconds resolution */
  	timer_configure(10);
  
  	/* controller and DDR clock */
  	pmc_enable_peripheral(ID_MPDDRC);
  	pmc_enable_system_clock(PMC_SYSTEM_CLOCK_DDR);
  
  	/* Step1: Program memory device type */
  	MPDDRC->MPDDRC_MD = desc->mode;
  
  	/* set driver impedance */
  	uint32_t value = MPDDRC->MPDDRC_IO_CALIBR;
  	value &= ~MPDDRC_IO_CALIBR_RDIV_Msk;
  	value &= ~MPDDRC_IO_CALIBR_TZQIO_Msk;
  	value &= ~MPDDRC_IO_CALIBR_CALCODEP_Msk;
  	value &= ~MPDDRC_IO_CALIBR_CALCODEN_Msk;
  	value |= desc->io_calibr;
  	MPDDRC->MPDDRC_IO_CALIBR = value;
  
  	MPDDRC->MPDDRC_RD_DATA_PATH = desc->data_path;
  
  	/* Step 2: Program features of the DDR3-SDRAM device in the
  	 * configuration register and timing parameter registers (TPR0
  	 * ans TPR1) */
  
  	/* Configurations */
  	MPDDRC->MPDDRC_CR = desc->control;
  
  #ifdef CONFIG_HAVE_DDR3_SELFREFRESH
  	if (sfrbu_is_ddr_backup_enabled())
  		/* DDR memory had been initilized and in backup mode */
  		MPDDRC->MPDDRC_LPR =
  			MPDDRC_LPR_LPCB_SELFREFRESH |
  			MPDDRC_LPR_CLK_FR_ENABLED |
  			MPDDRC_LPR_PASR(0) |
  			MPDDRC_LPR_DS(2) |
  			MPDDRC_LPR_TIMEOUT_NONE |
  			MPDDRC_LPR_APDE_DDR2_FAST_EXIT |
  			MPDDRC_LPR_UPD_MR(0);
  	else
  		/* DDR memory is not in backup mode */
  		MPDDRC->MPDDRC_LPR =
  			MPDDRC_LPR_LPCB_SELFREFRESH |
  			MPDDRC_LPR_CLK_FR_ENABLED |
  			MPDDRC_LPR_PASR(0) |
  			MPDDRC_LPR_DS(2) |
  			MPDDRC_LPR_TIMEOUT_DELAY_128_CLK |
  			MPDDRC_LPR_APDE_DDR2_SLOW_EXIT |
  			MPDDRC_LPR_UPD_MR(0);
  #endif
  
  	switch(desc->type) {
  #ifdef CONFIG_HAVE_DDR3
  	case MPDDRC_TYPE_DDR3:
  #ifdef CONFIG_HAVE_DDR3_SELFREFRESH
  		_set_ddr_timings(desc);
  		/* Initialize DDR chip when needed */
  		if (!sfrbu_is_ddr_backup_enabled())
  #endif
  		_configure_ddr3(desc);
  		break;
  #endif
  	case MPDDRC_TYPE_DDR2:
  		_configure_ddr2(desc);
  		break;
  	default:
  		trace_error("Device not handled\r\n");
  		abort();
  	}
  
  	/* Last step: Write the refresh rate */
  	/* Refresh Timer is (64ms / (bank_size)) * master_clock */
  	uint32_t master_clock = pmc_get_master_clock()/1000000;
  	MPDDRC->MPDDRC_RTR = MPDDRC_RTR_COUNT(64000*master_clock/desc->bank);
  
  	/* wait for end of calibration */
  	timer_sleep(1);
  
  	/* Restore resolution or put the default one if not already set */
  	timer_configure(resolution);
  
  #ifdef CONFIG_HAVE_DDR3_SELFREFRESH
  	if (sfrbu_is_ddr_backup_enabled()) {
  		MPDDRC->MPDDRC_MR = MPDDRC_MR_MODE_NORMAL_CMD;
  		sfrbu_disable_ddr_backup();
  	}
  #endif
  }
  