/***************************************************************************
 *
 * Copyright 2015-2019 BES.
 * All rights reserved. All unpublished rights reserved.
 *
 * No part of this work may be used or reproduced in any form or by any
 * means, or stored in a database or retrieval system, without prior written
 * permission of BES.
 *
 * Use of this work is governed by a license granted by BES.
 * This work contains confidential and proprietary information of
 * BES. which is protected by copyright, trade secret,
 * trademark and other intellectual property rights.
 *
 ****************************************************************************/
#if defined(CHIP_HAS_PSRAM) && (CHIP_PSRAM_CTRL_VER == 1)

#include "hal_cmu.h"
#include "hal_location.h"
#include "hal_psram.h"
#include "hal_psramip_v1.h"
#include "hal_timer.h"
#include "hal_trace.h"
#include "hal_uart.h"
#include "plat_addr_map.h"
#include "plat_types.h"
#include "pmu.h"
#define PSRAM_CALIB 0

#define HAL_PSRAM_YES 1
#define HAL_PSRAM_NO 0

#define HAL_PSRAM_CMD_REG_READ 0x40
#define HAL_PSRAM_CMD_REG_WRITE 0xc0
#define HAL_PSRAM_CMD_RAM_READ 0x00
#define HAL_PSRAM_CMD_RAM_WRITE 0x80

enum HAL_PSRAM_SPEED {
  HAL_PSRAM_SPEED_13M = 13000000,
  HAL_PSRAM_SPEED_26M = 26000000,
  HAL_PSRAM_SPEED_50M = 50000000,
  HAL_PSRAM_SPEED_52M = 52000000,
  HAL_PSRAM_SPEED_78M = 78000000,
  HAL_PSRAM_SPEED_104M = 104000000,
  HAL_PSRAM_SPEED_130M = 130000000,
  HAL_PSRAM_SPEED_156M = 156000000,
  HAL_PSRAM_SPEED_182M = 182000000,
  HAL_PSRAM_SPEED_208M = 208000000,
  HAL_PSRAM_SPEED_234M = 234000000,
};

struct HAL_PSRAM_CONFIG_T {
  uint32_t source_clk;
  uint32_t speed;
  uint32_t dqs_rd_sel;
  uint32_t dqs_wr_sel;

  /* internal use : can be config if need to (useful for rom) */
  uint8_t override_config : 1;
  uint8_t div;
};

#define PSRAM_DEVICE_ID_LEN_MAX 8
struct HAL_Psram_Context {
  uint8_t device_id[PSRAM_DEVICE_ID_LEN_MAX];
  uint32_t total_size;
  uint32_t read_size;
  uint32_t write_size;
};

/* hal api */
#if 0
uint8_t hal_psram_open(enum HAL_PSRAM_ID_T id, struct HAL_PSRAM_CONFIG_T *cfg);
uint8_t hal_psram_close(enum HAL_PSRAM_ID_T id);
void hal_psram_reg_dump(enum HAL_PSRAM_ID_T id);
#endif
void hal_psram_suspend(enum HAL_PSRAM_ID_T id);
void hal_psram_resume(enum HAL_PSRAM_ID_T id);
void hal_psram_wakeup_init(void);

struct HAL_Psram_Context psram_ctx[HAL_PSRAM_ID_NUM];

// static const char * const invalid_drv = "psram drv invalid";

#define DIGITAL_REG(a) *(volatile uint32_t *)(a)

FLASH_TEXT_LOC static uint32_t _psram_get_reg_base(enum HAL_PSRAM_ID_T id) {
  switch (id) {
  case HAL_PSRAM_ID_0:
  default:
    return PSRAM_CTRL_BASE;
    break;
  }
}

FLASH_TEXT_LOC static void
_psram_exitsleep_onprocess_wait(enum HAL_PSRAM_ID_T id) {
  uint32_t reg_base = 0;
  reg_base = _psram_get_reg_base(id);
  while (psramip_r_exit_sleep_onprocess(reg_base))
    ;

  while (!psramip_r_sleep_wakeup_state(reg_base))
    ;
}
FLASH_TEXT_LOC static void _psram_busy_wait(enum HAL_PSRAM_ID_T id) {
  uint32_t reg_base = 0;
  reg_base = _psram_get_reg_base(id);
  while (psramip_r_busy(reg_base))
    ;
}
#if 0
static void _psram_div(enum HAL_PSRAM_ID_T id)
{
    /* TODO */
}
/* hal api */
uint8_t hal_psram_open(enum HAL_PSRAM_ID_T id, struct HAL_PSRAM_CONFIG_T *cfg)
{
    uint32_t div = 0, reg_base = 0;
    //uint32_t psram_id = 0;
    reg_base = _psram_get_reg_base(id);

    /* over write config */
    if (cfg->override_config) {
        /* div */
        _psram_div(cfg->div);
    }
    else {
        div = cfg->source_clk/cfg->speed;
        _psram_div(div);
    }

    /* 0. dqs config */
    psramip_w_dqs_rd_sel(reg_base, cfg->dqs_rd_sel);
    psramip_w_dqs_wr_sel(reg_base, cfg->dqs_wr_sel);

    /* 1. high speed mode */
    if (cfg->speed >= HAL_PSRAM_SPEED_50M)
        psramip_w_high_speed_enable(reg_base, HAL_PSRAM_YES);
    else
        psramip_w_high_speed_enable(reg_base, HAL_PSRAM_NO);

    _psram_busy_wait(id);

    /* 2. wait calib done or FIXME timeout */
    psramip_w_enable_and_trigger_calib(reg_base);
    while (!psramip_r_calibst(reg_base));

    psramip_w_wrap_mode_enable(reg_base, HAL_PSRAM_YES);
    //psramip_w_wrap_mode_enable(reg_base, HAL_PSRAM_NO);

    //psramip_w_32bytewrap_mode(reg_base);
    psramip_w_1kwrap_mode(reg_base);
#if 0
    /* psram device register read 1 or 2 or 3 */
    psramip_w_acc_size(reg_base, 1);
    psramip_w_cmd_addr(reg_base, HAL_PSRAM_CMD_REG_READ, 2);
    _psram_busy_wait(id);
    psram_id = psramip_r_rx_fifo(reg_base);

    uart_printf("psram id 0x%x\n", psram_id);
#endif

    return 0;
}
uint8_t hal_psram_close(enum HAL_PSRAM_ID_T id)
{
    return 0;
}
void hal_psram_reg_dump(enum HAL_PSRAM_ID_T id)
{
    uint32_t reg_base = 0;
    uint32_t psram_id = 0;
    reg_base = _psram_get_reg_base(id);

    /* psram device register read 1 or 2 or 3 */
    psramip_w_acc_size(reg_base, 1);
    psramip_w_cmd_addr(reg_base, HAL_PSRAM_CMD_REG_READ, 2);
    _psram_busy_wait(id);
    psram_id = psramip_r_rx_fifo(reg_base);

    uart_printf("psram id 0x%x\n", psram_id);
}

#endif

FLASH_TEXT_LOC uint8_t hal_psramip_read_reg(uint8_t reg) {
  uint32_t mr;
  psramip_clear_fifo(_psram_get_reg_base(HAL_PSRAM_ID_0));
  psramip_w_cmd_addr(0x40150000, HAL_PSRAM_CMD_REG_READ, (uint32_t)reg);
  while ((psramip_r_status(0x40150000) & PSRAM_STAT_BUSY))
    ;

  mr = psramip_r_rx_fifo(0x40150000);
  return mr & 0xff;
}

FLASH_TEXT_LOC void hal_psramip_write_reg(uint8_t reg, uint8_t val) {
  enum HAL_PSRAM_ID_T id = HAL_PSRAM_ID_0;
  psramip_clear_fifo(_psram_get_reg_base(HAL_PSRAM_ID_0));
  psramip_w_tx_fifo(0x40150000, (uint32_t)val);
  psramip_w_cmd_addr(0x40150000, HAL_PSRAM_CMD_REG_WRITE, (uint32_t)reg);
  _psram_busy_wait(id);
}

FLASH_TEXT_LOC void hal_psram_suspend(enum HAL_PSRAM_ID_T id) {
  uint32_t reg_base = 0;
  reg_base = _psram_get_reg_base(id);

  psramip_w_acc_size(reg_base, 1);
  hal_psramip_write_reg(0x6, 0xf0);
}

FLASH_TEXT_LOC void hal_psram_resume(enum HAL_PSRAM_ID_T id) {
  uint32_t reg_base = 0;
  reg_base = _psram_get_reg_base(id);

  psramip_w_exit_sleep(reg_base);

  _psram_exitsleep_onprocess_wait(id);
}
// uint32_t test_ctrl =0;

#ifdef CHIP_BEST2000
void pmu_open_bbpll_for_psram();
/*psram 120M use bbpll(div 4), 160M use bbpll(dir4), 100M use audio pll*/
FLASH_TEXT_LOC void hal_psram_init(void) {
#ifndef PSRAM_LOW_SPEED
  uint8_t mr = 0;
//    enum HAL_PSRAM_ID_T id = HAL_PSRAM_ID_0;
#if defined(PSRAM_120M) || defined(PSRAM_160M)
  pmu_open_bbpll_for_psram();
  hal_cmu_mem_set_freq(HAL_CMU_FREQ_208M);
#else /*PSRAM_160M*/
#ifdef MCU_ULTRAHIGH_PERFORMANCE_MODE
  hal_cmu_mem_set_freq(HAL_CMU_FREQ_104M); // 104*2
#else
  hal_cmu_mem_set_freq(HAL_CMU_FREQ_208M);
#endif
#endif /*PSRAM_160M*/
  pmu_psram_freq_config(HAL_PSRAM_SPEED_208M);
#else
  hal_cmu_mem_set_freq(HAL_CMU_FREQ_26M);
  pmu_psram_freq_config(HAL_PSRAM_SPEED_26M);
#endif

  hal_cmu_clock_enable(HAL_CMU_MOD_O_PSRAM);
  hal_cmu_clock_enable(HAL_CMU_MOD_H_PSRAM);
  hal_cmu_reset_clear(HAL_CMU_MOD_O_PSRAM);
  hal_cmu_reset_clear(HAL_CMU_MOD_H_PSRAM);
  DIGITAL_REG(0x4008004c) |= 0x1 << 21;
#if 0 // for debug
    DIGITAL_REG(0x40150020) = 0x01;
    DIGITAL_REG(0x400000ac) &= ~(0xc0000);

    DIGITAL_REG(0x400000ac) |= (0x80000);

    DIGITAL_REG(0x40087008) = 0xFFFFFFAF;
    DIGITAL_REG(0x4008701c) = 0x2;
#endif

#ifndef PSRAM_LOW_SPEED
  DIGITAL_REG(0x40150040) = 0x44712133;

  DIGITAL_REG(0x40150044) = 0x200040b1;
  DIGITAL_REG(0x40150044) |= (1 << 1) | (1 << 30);

  while (((DIGITAL_REG(0x40150044)) & (0x1 << 30)))
    ;
  // hal_sys_timer_delay(MS_TO_TICKS(10));
  DIGITAL_REG(0x40150044) &= ~(1 << 0);

  while (!((DIGITAL_REG(0x40150044)) & (0x1 << 0)))
    ;
  // hal_sys_timer_delay(MS_TO_TICKS(10));

  DIGITAL_REG(0x40000090) = 0x0001ffff;
  while (!((DIGITAL_REG(0x40000090)) & (0x1 << 20)))
    ;
  // hal_sys_timer_delay(MS_TO_TICKS(10));

  // lp delay
  // DIGITAL_REG(0x4015001C)=0xffffffff;//c
  DIGITAL_REG(0x4015001C) =
      ((4 * 200) << 0) | ((70 * 200) << 16) |
      ((70 * 200 / 1000) << 10); // 208MHZ: tHS 10us, tXHS 100us, tXPHS 100ns
  // ramp=1k select low speed
  DIGITAL_REG(0x4015002C) = 0x0000000d; // c
  // tx/rx Phase
// #error "6666666666666666"
// DIGITAL_REG(0x40150024)= 0x55000713;//0x55000373;//223
// DIGITAL_REG(0x40150024)= 0x55000373;//0x55000373;//223
#if PSRAM_CALIB

  DIGITAL_REG(0x40150024) = 0x55000373; // 0x55000373;//223
#else
#if defined(PSRAM_200M)
  DIGITAL_REG(0x40150024) = 0x55000653;    // 0x55000373;//223
#elif defined(PSRAM_160M)
  DIGITAL_REG(0x40150024) = 0x55000463; //;//0x55000373;//223
#elif defined(PSRAM_120M)
  DIGITAL_REG(0x40150024) = 0x55000113; // 0x55000373;//223
#else /*100M*/
  DIGITAL_REG(0x40150024) = 0x55000113; // 0x55000373;//223
#endif
#endif
  while (!((DIGITAL_REG(0x40150024)) & (0x1 << 3)))
    ;
#if 1
  // reset psram chip
  psramip_write32(0xff, 0x40150000, 0x00);
  while ((psramip_r_status(0x40150000) & PSRAM_STAT_BUSY))
    ;
  // clear fifo
  psramip_write32(0x03, 0x40150000, 0x18);

  while ((psramip_r_status(0x40150000) & PSRAM_STAT_BUSY))
    ;
  hal_sys_timer_delay(US_TO_TICKS(10));
  // test_ctrl = 1;
  // while (test_ctrl ==1);

  mr = hal_psramip_read_reg(0x0);
  mr &= ~(0x3 << 6); // MR[7:6] must set 0
  mr &= ~(0x1 << 5); // MR0[5] = 0, latency type 0->variable
  mr &= ~(0x7 << 2);
  mr |= (0x6 << 2); // MR0[4:2] = '110 Latency =6 for 200M (variable latency)
  mr &= ~(0x3 << 0);
  mr |= (0x1 << 0); // driver strength '01 -> half
  hal_psramip_write_reg(0x0, mr);
  mr = hal_psramip_read_reg(0x0);
  // TRACE(2,"PSRAM MODE REG 0 = 0x%x, status = 0x%x", mr,
  // psramip_r_status(0x40150000));

  mr = hal_psramip_read_reg(0x4);
  mr &= ~(0x01 << 7);
  mr |= (0x01 << 7); // MR[7] '1 -> write latency 1 for clk > 166MHz
  // hal_psramip_write_reg(0x4, mr);
  mr = hal_psramip_read_reg(0x4);
  // TRACE(2,"PSRAM MODE REG 4 = 0x%x, status = 0x%x", mr,
  // psramip_r_status(0x40150000));

#endif
  hal_sys_timer_delay(US_TO_TICKS(10));
#else  /*PSRAM_LOW_SPEED*/
  // open psram phy power
  DIGITAL_REG(0x4008004c) |= (1 << 21);
  // init psram
  // lp delay
  DIGITAL_REG(0x4015001C) = 0x3fff3cff; // c
  // ramp=1k select low speed
  DIGITAL_REG(0x4015002C) = 0x0000000c;
  // tx/rx Phase
  DIGITAL_REG(0x40150024) = 0x55000223;
#endif /*PSRAM_LOW_SPEED*/

  psramip_w_acc_size(_psram_get_reg_base(HAL_PSRAM_ID_0), 1);
#ifdef PSRAM_200M
  hal_cmu_mem_set_freq(HAL_CMU_FREQ_208M);
#endif
}
FLASH_TEXT_LOC void hal_psram_wakeup_init(void) {
#ifndef PSRAM_LOW_SPEED
  DIGITAL_REG(0x40150040) = 0x44712133;

  DIGITAL_REG(0x40150044) = 0x200040b1;
  DIGITAL_REG(0x40150044) |= (1 << 1) | (1 << 30);

  // TRACE(1,"0x40150044 = 0x%x \n",DIGITAL_REG(0x40150044));
  while (((DIGITAL_REG(0x40150044)) & (0x1 << 30)))
    ;
  // hal_sys_timer_delay(MS_TO_TICKS(10));
  DIGITAL_REG(0x40150044) &= ~(1 << 0);

  // TRACE(1,"0x40150044 = 0x%x \n", DIGITAL_REG(0x40150044));
  while (!((DIGITAL_REG(0x40150044)) & (0x1 << 0)))
    ;
  // hal_sys_timer_delay(MS_TO_TICKS(10));

  DIGITAL_REG(0x40000090) = 0x0001ffff;
  // TRACE(1,"0x40000090 = 0x%x \n", DIGITAL_REG(0x40000090));
  while (!((DIGITAL_REG(0x40000090)) & (0x1 << 20)))
    ;
    // hal_sys_timer_delay(MS_TO_TICKS(10));
#endif

  hal_psram_resume(HAL_PSRAM_ID_0);
}

FLASH_TEXT_LOC void hal_psram_sleep(void) {
#ifndef PSRAM_LOW_SPEED
  hal_psram_suspend(HAL_PSRAM_ID_0);
  /* dll power off */
  DIGITAL_REG(0x40150044) &= ~(1 << 1);
  /* phy power off */
  /* DIGITAL_REG(0x4008004c) &= ~(1 << 21) */
#endif
}

#else

FLASH_TEXT_LOC void hal_psram_init(void) {
#ifndef PSRAM_LOW_SPEED
  unsigned int mr = 0;
  enum HAL_PSRAM_ID_T id = HAL_PSRAM_ID_0;
#endif
  hal_cmu_mem_set_freq(HAL_CMU_FREQ_208M);

  hal_cmu_clock_enable(HAL_CMU_MOD_O_PSRAM);
  hal_cmu_clock_enable(HAL_CMU_MOD_H_PSRAM);
  hal_cmu_reset_clear(HAL_CMU_MOD_O_PSRAM);
  hal_cmu_reset_clear(HAL_CMU_MOD_H_PSRAM);

#ifndef PSRAM_LOW_SPEED
  DIGITAL_REG(0x4001f038) = 0x77783533;

  DIGITAL_REG(0x4001f03c) = 0x3ffffff4;
  DIGITAL_REG(0x4001f03c) |= (1 << 1) | (1 << 30);
  while (((DIGITAL_REG(0x4001f03c)) & (0x1 << 30)))
    ;
  hal_sys_timer_delay(MS_TO_TICKS(10));
  DIGITAL_REG(0x4001f03c) &= ~(1 << 0);
  while (!((DIGITAL_REG(0x4001f03c)) & (0x1 << 0)))
    ;
  hal_sys_timer_delay(MS_TO_TICKS(10));

  DIGITAL_REG(0x40000090) = 0x0001ffff;
  while (!((DIGITAL_REG(0x40000090)) & (0x1 << 20)))
    ;
  hal_sys_timer_delay(MS_TO_TICKS(10));

  // lp delay
  DIGITAL_REG(0x4015001C) = 0xffffffff; // c
  // ramp=1k select low speed
  DIGITAL_REG(0x4015002C) = 0x0000000d; // c
  // tx/rx Phase
  DIGITAL_REG(0x40150024) = 0x55000563; // 223
  while (!((DIGITAL_REG(0x40150024)) & (0x1 << 3)))
    ;
#if 1
  // reset psram chip
  psramip_write32(0xff, 0x40150000, 0x00);
  while ((psramip_r_status(0x40150000) & PSRAM_STAT_BUSY))
    ;
  // clear fifo
  psramip_write32(0x03, 0x40150000, 0x18);
  while ((psramip_r_status(0x40150000) & PSRAM_STAT_BUSY))
    ;
  hal_sys_timer_delay(MS_TO_TICKS(10));

  // read MR0
  psramip_w_cmd_addr(0x40150000, HAL_PSRAM_CMD_REG_READ, 0);
  while ((psramip_r_status(0x40150000) & PSRAM_STAT_BUSY))
    ;
  // need read twice
  mr = psramip_r_rx_fifo(0x40150000);
  mr = psramip_r_rx_fifo(0x40150000);
  // hal_uart_printf("count=0x%x mr0 = 0x%x \n", psramip_r_status(0x40150000),
  // mr);

  mr &= 0xff;
  mr &= ~(0x03 << 6); // MR[7:6] must set 0
  // driver strength 00:1/16 01:1/2 10:1/4 11:1/8 MR0[1:0]
  mr &= ~0x03;
  mr |= 0x01;
  // for >> 200M MR0[4:2]
  mr &= ~(0x07 << 2);
  mr |= (0x06 << 2);
  psramip_w_tx_fifo(0x40150000, mr);
  psramip_w_cmd_addr(0x40150000, HAL_PSRAM_CMD_REG_WRITE, 0);
  _psram_busy_wait(id);

  // read MR0 again
  psramip_w_cmd_addr(0x40150000, HAL_PSRAM_CMD_REG_READ, 0);
  while ((psramip_r_status(0x40150000) & PSRAM_STAT_BUSY))
    ;
  mr = psramip_r_rx_fifo(0x40150000);
  mr = psramip_r_rx_fifo(0x40150000);
  //  hal_uart_printf("count=0x%x mr0 = 0x%x \n", psramip_r_status(0x40150000),
  //  mr);
  // read MR4
  psramip_w_cmd_addr(0x40150000, HAL_PSRAM_CMD_REG_READ, 4);
  _psram_busy_wait(id);
  mr = psramip_r_rx_fifo(0x40150000);
  mr = psramip_r_rx_fifo(0x40150000);
  //  hal_uart_printf("mr4 = 0x%x\n", mr);

  _psram_busy_wait(id);
#endif
  hal_sys_timer_delay(MS_TO_TICKS(10));
#else
  // open psram phy power
  DIGITAL_REG(0x4001f02c) |= (1 << 16);
  // init psram
  // lp delay
  DIGITAL_REG(0x4015001C) = 0x3fff3cff; // c
  // ramp=1k select low speed
  DIGITAL_REG(0x4015002C) = 0x0000000c;
  // tx/rx Phase
  DIGITAL_REG(0x40150024) = 0x55000223;
#endif
}

FLASH_TEXT_LOC void hal_psram_wakeup_init(void) {
#ifndef PSRAM_LOW_SPEED
  // dll
  DIGITAL_REG(0x4001f038) = 0x77783533;

  DIGITAL_REG(0x4001f03c) = 0x3ffffff4;
  DIGITAL_REG(0x4001f03c) |= (1 << 1) | (1 << 30);
  while (((DIGITAL_REG(0x4001f03c)) & (0x1 << 30)))
    ;
  hal_sys_timer_delay(MS_TO_TICKS(10));
  DIGITAL_REG(0x4001f03c) &= ~(1 << 0);
  while (!((DIGITAL_REG(0x4001f03c)) & (0x1 << 0)))
    ;
  hal_sys_timer_delay(MS_TO_TICKS(10));

  DIGITAL_REG(0x40000090) = 0x0001ffff;
  while (!((DIGITAL_REG(0x40000090)) & (0x1 << 20)))
    ;
  hal_sys_timer_delay(MS_TO_TICKS(10));
#endif

  hal_psram_resume(HAL_PSRAM_ID_0);
}

FLASH_TEXT_LOC void hal_psram_sleep(void) {
#ifndef PSRAM_LOW_SPEED
  hal_psram_suspend(HAL_PSRAM_ID_0);
  /* dll power off */
  DIGITAL_REG(0x4001f03c) &= ~(1 << 1);
  /* phy power off */
  /* DIGITAL_REG(0x4001f02c) &= ~(1 << 16) */
#endif
}

#endif

FLASH_TEXT_LOC void hal_psram_wakeup(void) {
#ifndef PSRAM_LOW_SPEED
  hal_psram_wakeup_init();
#endif
}

#endif
