/****************************************************************************
 * configs/olimex-lpc-h3131/src/lp31_mem.c
 *
 *   Copyright (C) 2013 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Based on the EA3131 SDRAM initialization logic with adjustments to the
 * timing parameters taken from Olimex LPC-H3131 sample code.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS 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.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <stdint.h>
#include <stdbool.h>
#include <debug.h>

#include <nuttx/arch.h>
#include <arch/board/board.h>

#include "chip.h"
#include "up_arch.h"

#include "lpc31_syscreg.h"
#include "lpc31_cgudrvr.h"
#include "lpc31_mpmc.h"
#include "lpc_h3131.h"

#ifdef CONFIG_LPC31_EXTDRAM

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* My LPC-H3131 is fitted with a Samsung K4S561632J-UC/L75 256Mbit DRAM.
 * The FLASH organization is 16Mbit x 16
 */

/* Command, address, and data delay (DEL2) */

#define H3131_MPMC_DELAY          ((0x00 << SYSCREG_MPMC_DELAYMODES_DEL1_SHIFT) | \
                                   (0x20 << SYSCREG_MPMC_DELAYMODES_DEL2_SHIFT) | \
                                   (0x24 << SYSCREG_MPMC_DELAYMODES_DEL3_SHIFT))


/* Delay constants in nanosecondss for K4S561632J-UC/L75 SDRAM on board */
/* 90MHz SDRAM Clock */

#define H3131_SDRAM_REFRESH        (15625)

#define H3131_SDRAM_TRP            (20)  /* ns */
#define H3131_SDRAM_TRAS           (48)  /* ns */
#define H3131_SDRAM_TAPR           (2)   /* clocks */
#define H3131_SDRAM_TWR            (15)  /* ns */
#define H3131_SDRAM_TRC            (72)  /* ns */
#define H3131_SDRAM_TRFC           (80)  /* ns */
#define H3131_SDRAM_TREX           (80)  /* ns */
#define H3131_SDRAM_TXSR           (80)  /* ns */
#define H3131_SDRAM_TDAL           (5)   /* clocks */
#define H3131_SDRAM_TRRD           (2)   /* clocks */
#define H3131_SDRAM_TMRD           (2)   /* clocks */

/****************************************************************************
 * Private Data
 ****************************************************************************/

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: lpc31_ns2clk
 *
 * Description:
 *   Convert nanoseconds to units of HCLK clocks
 *
 ****************************************************************************/

static uint32_t lpc31_ns2clk(uint32_t ns, uint32_t hclk2)
{
 /* delay (ns) * hclk (Hz) / scale (ns/sec) = cycles
  *
  * Example: ns=80, hclk2=90000000
  * clocks = 80 * 90000000 / 1000000000 + 1 = 8 (actual 7.2 cycles)
  */

  uint64_t tmp = (uint64_t)ns * (uint64_t)hclk2 / 1000000000ull;
  if (tmp > 0)
    {
      tmp++;
    }

  return (uint32_t)tmp;
}

/****************************************************************************
 * Name: lpc31_sdraminitialize
 *
 * Description:
 *   Configure SDRAM on the Olimex LPC-H3131 board
 *
 *   My LPC-H3131 is fitted with a Samsung K4S561632J-UC/L75 256Mbit DRAM.
 *   The FLASH organization is 16Mbit x 16
 *
 ****************************************************************************/

static inline void lpc31_sdraminitialize(void)
{
  uint32_t regval;

  /* These run-time calculations can be reduced dramatically if hclk is
   * replaced with an apriori value.
   */

#ifdef CONFIG_LPC31_SDRAMHCLK
# define HCLK CONFIG_LPC31_SDRAMHCLK
#else
  uint32_t hclk = lpc31_clkfreq(CLKID_MPMCCFGCLK2, DOMAINID_SYS);
# define HCLK hclk
#endif

  /* Check RTL for divide by 2 possible. If so change then enable the following logic */
#if 0
  uint32_t hclk2 = hclk;

  if (((getreg32(LPC31_MPMC_CONFIG) & MPMC_CONFIG_CLK)) != 0)
    {
      hclk2 >>= 1;
    }
# define HCLK2 hclk2
#else
# define HCLK2 hclk
#endif
  up_udelay(100);

  /* Set command delay strategy */

  putreg32(MPMC_DYNREADCONFIG_CMDDEL, LPC31_MPMC_DYNREADCONFIG);

  /* Configure device config register nSDCE0 for proper width SDRAM:
   * Type: 256Mb (16Mx16), 4 banks, row length=13, column length=9
   * Buffer disabled, writes not protected.
   */

  putreg32((MPMC_DYNCONFIG0_MDSDRAM | MPMC_DYNCONFIG_HP16_16MX16),
           LPC31_MPMC_DYNCONFIG0);

  /* Disable buffers + writes not protected */

  regval  = getreg32(LPC31_MPMC_DYNCONFIG0);
  regval &= ~(MPMC_DYNCONFIG0_B | MPMC_DYNCONFIG0_P);
  putreg32(regval, LPC31_MPMC_DYNCONFIG0);

  /* Set RAS/CAS delays*/

  putreg32((MPMC_DYNRASCAS0_RAS2CLK | MPMC_DYNRASCAS0_CAS2CLK),
           LPC31_MPMC_DYNRASCAS0);

  /* Configure SDRAM timing */

  putreg32(lpc31_ns2clk(H3131_SDRAM_TRP,  HCLK2), LPC31_MPMC_DYNTRP);
  putreg32(lpc31_ns2clk(H3131_SDRAM_TRAS, HCLK2), LPC31_MPMC_DYNTRAS);
  putreg32(lpc31_ns2clk(H3131_SDRAM_TREX, HCLK2), LPC31_MPMC_DYNTSREX);
  putreg32(H3131_SDRAM_TAPR, LPC31_MPMC_DYNTAPR);
  putreg32(H3131_SDRAM_TDAL + lpc31_ns2clk(H3131_SDRAM_TRP, HCLK2),
           LPC31_MPMC_DYNTDAL);
  putreg32(lpc31_ns2clk(H3131_SDRAM_TWR,  HCLK2), LPC31_MPMC_DYNTWR);
  putreg32(lpc31_ns2clk(H3131_SDRAM_TRC,  HCLK2), LPC31_MPMC_DYNTRC);
  putreg32(lpc31_ns2clk(H3131_SDRAM_TRFC, HCLK2), LPC31_MPMC_DYNTRFC);
  putreg32(lpc31_ns2clk(H3131_SDRAM_TXSR, HCLK2), LPC31_MPMC_DYNTXSR);
  putreg32(H3131_SDRAM_TRRD, LPC31_MPMC_DYNTRRD);
  putreg32(H3131_SDRAM_TMRD, LPC31_MPMC_DYNTMRD);

  /* JEDEC General SDRAM Initialization Sequence DELAY to allow power and
   * clocks to stabilize ~100 us NOP
   */

  up_udelay(100);

  /* Issue continuous NOP commands  */

  putreg32((MPMC_DYNCONTROL_CE|MPMC_DYNCONTROL_CS|MPMC_DYNCONTROL_INOP),
           LPC31_MPMC_DYNCONTROL);

  /* Wait ~200us */

  up_udelay(200);

  /* Issue a "pre-charge all" command */

  putreg32((MPMC_DYNCONTROL_CE|MPMC_DYNCONTROL_CS|MPMC_DYNCONTROL_IPALL),
           LPC31_MPMC_DYNCONTROL);

  /* Minimum refresh pulse interval (tRFC) for MT48LC32M16A2=80nsec,
   * 100nsec provides more than adequate interval.
   */

  putreg32(1, LPC31_MPMC_DYNREFRESH);

  /* Wait ~250us */

  up_udelay(250);

  /* Recommended refresh interval for normal operation of the Micron
   * MT48LC16LFFG = 7.8125usec (128KHz rate). ((HCLK / 128000) - 1) =
   * refresh counter interval rate, (subtract one for safety margin).
   *
   * REVISIT:  Is this okay for the Samsung part?
   */

  putreg32(lpc31_ns2clk(H3131_SDRAM_REFRESH, HCLK) >> 4,
           LPC31_MPMC_DYNREFRESH);

  /* Select mode register update mode */

  putreg32((MPMC_DYNCONTROL_CE|MPMC_DYNCONTROL_CS|MPMC_DYNCONTROL_IMODE),
           LPC31_MPMC_DYNCONTROL);

  /* Program the SDRAM internal mode registers on bank nSDCE0 and reconfigure
   * the SDRAM chips.  Bus speeds up to 90MHz requires use of a CAS latency = 2.
   * To get correct value on address bus CAS cycle, requires a shift by 12 for
   * 16bit mode
   */

  (void)getreg32(LPC31_EXTSDRAM0_VSECTION | (0x23 << 12));

  /* Select normal operating mode */

  putreg32(MPMC_DYNCONTROL_INORMAL, LPC31_MPMC_DYNCONTROL);

  /* Enable buffers */

  regval  = getreg32(LPC31_MPMC_DYNCONFIG0);
  regval |= MPMC_DYNCONFIG0_B;
  putreg32(regval, LPC31_MPMC_DYNCONFIG0);
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: lpc31_meminitialize
 *
 * Description:
 *   Initialize external memory resources (sram, sdram, nand, nor, etc.)
 *
 ****************************************************************************/

void lpc31_meminitialize(void)
{
  /* Configure the LCD pins in external bus interface (EBI/MPMC) memory mode.
   *
   * LCD_CSB   -> MPMC_NSTCS_0
   * LCD_DB_1  -> MPMC_NSTCS_1
   * LCD_DB_0  -> MPMC_CLKOUT
   * LCD_E_RD  -> MPMC_CKE
   * LCD_RS    -> MPMC_NDYCS
   * LCD_RW_WR -> MPMC_DQM_1
   * LCD_DB_2  -> EBI_A_2
   * LCD_DB_3  -> EBI_A_3 l
   * LCD_DB_4  -> EBI_A_4 l
   * LCD_DB_5  -> EBI_A_5 l
   * LCD_DB_6  -> EBI_A_6
   * LCD_DB_7  -> EBI_A_7
   * LCD_DB_8  -> EBI_A_8
   * LCD_DB_9  -> EBI_A_9
   * LCD_DB_10 -> EBI_A_10
   * LCD_DB_11 -> EBI_A_11
   * LCD_DB_12 -> EBI_A_12
   * LCD_DB_13 -> EBI_A_13
   * LCD_DB_14 -> EBI_A_14
   * LCD_DB_15 -> EBI_A_15
   */

  putreg32(SYSCREG_MUX_LCDEBISEL_EBIMPMC, LPC31_SYSCREG_MUX_LCDEBISEL);

  /* Enable EBI clock */

  lpc31_enableclock(CLKID_EBICLK);

  /* Enable MPMC controller clocks */

  lpc31_enableclock(CLKID_MPMCCFGCLK);
  lpc31_enableclock(CLKID_MPMCCFGCLK2);
  lpc31_enableclock(CLKID_MPMCCFGCLK3);

  /* Enable the external memory controller */

  putreg32(MPMC_CONTROL_E, LPC31_MPMC_CONTROL);

  /* Force HCLK to MPMC_CLK to 1:1 ratio, little-endian mode */

  putreg32(0, LPC31_MPMC_CONFIG);

  /* Set MPMC delay based on trace lengths between SDRAM and the chip
   * and on the delay strategy used for SDRAM.
   */

  putreg32(H3131_MPMC_DELAY, LPC31_SYSCREG_MPMC_DELAYMODES);

  /* Configure Samsung K4S561632J-UC/L75 DRAM on the H3131 board */

  lpc31_sdraminitialize();
}
#endif /* CONFIG_LPC31_EXTDRAM */
