/***************************************************************************
 *
 * 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.
 *
 ****************************************************************************/
#include "plat_addr_map.h"
#include "hal_cmu.h"
#include CHIP_SPECIFIC_HDR(reg_cmu)
#ifdef AON_CMU_BASE
#include CHIP_SPECIFIC_HDR(reg_aoncmu)
#endif
#include "cmsis.h"
#include "hal_analogif.h"
#include "hal_bootmode.h"
#include "hal_cache.h"
#include "hal_chipid.h"
#include "hal_iomux.h"
#include "hal_location.h"
#include "hal_norflash.h"
#include "hal_sleep.h"
#include "hal_sysfreq.h"
#include "hal_timer.h"
#include "hal_trace.h"

#if defined(CHIP_HAS_USB) && (defined(MCU_HIGH_PERFORMANCE_MODE) && !(defined(ULTRA_LOW_POWER) || defined(OSC_26M_X4_AUD2BB)))
#define USB_PLL_INIT_ON
#endif
#if (!defined(ULTRA_LOW_POWER) && !defined(OSC_26M_X4_AUD2BB)) || \
        (!defined(FLASH_LOW_SPEED) && !defined(OSC_26M_X4_AUD2BB)) || \
        (defined(PSRAM_ENABLE) && !defined(PSRAM_LOW_SPEED))
#define AUD_PLL_INIT_ON
#endif

// SIMU_RES
#define CMU_SIMU_RES_PASSED                 (0x9A55)
#define CMU_SIMU_RES_FAILED                 (0xFA11)

typedef void (*HAL_POWER_DOWN_WAKEUP_HANDLER)(void);

static struct CMU_T * const cmu = (struct CMU_T *)CMU_BASE;
#ifdef AON_CMU_BASE
static struct AONCMU_T * const POSSIBLY_UNUSED aoncmu = (struct AONCMU_T *)AON_CMU_BASE;
#endif

#ifdef HAL_CMU_VALID_CRYSTAL_FREQ
static const uint32_t valid_crystal_freq_list[] = HAL_CMU_VALID_CRYSTAL_FREQ;
#define CRYSTAL_FREQ_ATTR                   BOOT_DATA_LOC
#else
#define CRYSTAL_FREQ_ATTR                   const
#endif

static uint32_t CRYSTAL_FREQ_ATTR crystal_freq = HAL_CMU_DEFAULT_CRYSTAL_FREQ;

void BOOT_TEXT_FLASH_LOC hal_cmu_set_crystal_freq_index(uint32_t index)
{
#ifdef HAL_CMU_VALID_CRYSTAL_FREQ
    if (index >= ARRAY_SIZE(valid_crystal_freq_list)) {
        index %= ARRAY_SIZE(valid_crystal_freq_list);
    }
    crystal_freq = valid_crystal_freq_list[index];
#endif
}

uint32_t BOOT_TEXT_SRAM_LOC hal_cmu_get_crystal_freq(void)
{
    return crystal_freq;
}

uint32_t BOOT_TEXT_FLASH_LOC hal_cmu_get_default_crystal_freq(void)
{
    return HAL_CMU_DEFAULT_CRYSTAL_FREQ;
}

void hal_cmu_write_lock(void)
{
    cmu->WRITE_UNLOCK = 0xCAFE0000;
}

void hal_cmu_write_unlock(void)
{
    cmu->WRITE_UNLOCK = 0xCAFE0001;
}

void hal_cmu_sys_reboot(void)
{
    hal_cmu_reset_set(HAL_CMU_MOD_GLOBAL);
}

void hal_cmu_simu_init(void)
{
    cmu->SIMU_RES = 0;
}

void hal_cmu_simu_pass(void)
{
    cmu->SIMU_RES = CMU_SIMU_RES_PASSED;
}

void hal_cmu_simu_fail(void)
{
    cmu->SIMU_RES = CMU_SIMU_RES_FAILED;
}

void hal_cmu_simu_tag(uint8_t shift)
{
    cmu->SIMU_RES |= (1 << shift);
}

void hal_cmu_simu_set_val(uint32_t val)
{
    cmu->SIMU_RES = val;
}

uint32_t hal_cmu_simu_get_val(void)
{
    return cmu->SIMU_RES;
}

void hal_cmu_set_wakeup_pc(uint32_t pc)
{
#ifdef RAMRET_BASE
    uint32_t *wake_pc =
#ifdef CHIP_BEST2000
        (uint32_t *)RAMRET_BASE;
#else
        (uint32_t *)&aoncmu->WAKEUP_PC;

    STATIC_ASSERT(sizeof(HAL_POWER_DOWN_WAKEUP_HANDLER) <= sizeof(uint32_t), "Invalid func ptr size");
#endif

    *wake_pc = pc;
#endif
}

void hal_cmu_rom_wakeup_check(void)
{
#ifdef RAMRET_BASE
    union HAL_HW_BOOTMODE_T hw;
    uint32_t sw;
    HAL_POWER_DOWN_WAKEUP_HANDLER *wake_fn =
#ifdef CHIP_BEST2000
        (HAL_POWER_DOWN_WAKEUP_HANDLER *)RAMRET_BASE;
#else
        (HAL_POWER_DOWN_WAKEUP_HANDLER *)&aoncmu->WAKEUP_PC;
#endif

    hw = hal_rom_hw_bootmode_get();
    if (hw.watchdog == 0 && hw.global == 0) {
        sw = hal_sw_bootmode_get();
        if ((sw & HAL_SW_BOOTMODE_POWER_DOWN_WAKEUP) && *wake_fn) {
            (*wake_fn)();
        }
    }

    *wake_fn = NULL;
#endif
}

#ifndef HAL_CMU_PLL_T
void hal_cmu_rom_enable_pll(void)
{
#ifdef CHIP_HAS_USB
    hal_cmu_pll_enable(HAL_CMU_PLL_USB, HAL_CMU_PLL_USER_SYS);
    hal_cmu_sys_select_pll(HAL_CMU_PLL_USB);
    hal_cmu_flash_select_pll(HAL_CMU_PLL_USB);
#else
    hal_cmu_pll_enable(HAL_CMU_PLL_AUD, HAL_CMU_PLL_USER_SYS);
    hal_cmu_sys_select_pll(HAL_CMU_PLL_AUD);
    hal_cmu_flash_select_pll(HAL_CMU_PLL_AUD);
#endif
}

void hal_cmu_programmer_enable_pll(void)
{
    hal_cmu_pll_enable(HAL_CMU_PLL_AUD, HAL_CMU_PLL_USER_SYS);
    hal_cmu_flash_select_pll(HAL_CMU_PLL_AUD);
    hal_cmu_sys_select_pll(HAL_CMU_PLL_AUD);
}

void BOOT_TEXT_FLASH_LOC hal_cmu_init_pll_selection(void)
{
    // !!!!!!
    // CAUTION:
    // hal_cmu_pll_enable()/hal_cmu_pll_disable() must be called after hal_chipid_init(),
    // for the init div values are extracted in hal_chipid_init().
    // !!!!!!

#if defined(CHIP_BEST1000) || defined(CHIP_BEST2000)
#ifdef CHIP_HAS_USB
    // Enable USB PLL before switching (clock mux requirement)
    // -- USB PLL might not be started in ROM
    hal_cmu_pll_enable(HAL_CMU_PLL_USB, HAL_CMU_PLL_USER_SYS);
#endif
    hal_cmu_pll_enable(HAL_CMU_PLL_AUD, HAL_CMU_PLL_USER_SYS);
#else // !(best1000 || best2000)
    // Disable the PLL which might be enabled in ROM
#ifdef CHIP_HAS_USB
    hal_cmu_pll_disable(HAL_CMU_PLL_USB, HAL_CMU_PLL_USER_ALL);
#else
    hal_cmu_pll_disable(HAL_CMU_PLL_AUD, HAL_CMU_PLL_USER_ALL);
#endif
#endif // !(best1000 || best2000)

#ifdef FLASH_LOW_SPEED
#ifdef CHIP_HAS_USB
    // Switch flash clock to USB PLL, and then shutdown USB PLL,
    // to save power consumed in clock divider
    hal_cmu_flash_select_pll(HAL_CMU_PLL_USB);
#endif
#else
    // Switch flash clock to audio PLL
    hal_cmu_flash_select_pll(HAL_CMU_PLL_AUD);
#endif

#ifdef CHIP_HAS_PSRAM
#ifdef PSRAM_LOW_SPEED
#ifdef CHIP_HAS_USB
    // Switch psram clock to USB PLL, and then shutdown USB PLL,
    // to save power consumed in clock divider
    hal_cmu_mem_select_pll(HAL_CMU_PLL_USB);
#endif
#else
    // Switch psram clock to audio PLL
    hal_cmu_mem_select_pll(HAL_CMU_PLL_AUD);
#endif
#endif

    // Select system PLL after selecting flash/psram PLLs
#ifdef ULTRA_LOW_POWER
    hal_cmu_low_freq_mode_init();
#else
#if defined(MCU_HIGH_PERFORMANCE_MODE) && defined(CHIP_HAS_USB)
    // Switch system clocks to USB PLL
    hal_cmu_sys_select_pll(HAL_CMU_PLL_USB);
#else
    // Switch system clocks to audio PLL
    hal_cmu_sys_select_pll(HAL_CMU_PLL_AUD);
#endif
#endif

#if defined(CHIP_BEST1000) || defined(CHIP_BEST2000)
#ifndef USB_PLL_INIT_ON
    // Disable USB PLL after switching (clock mux requirement)
    hal_cmu_pll_disable(HAL_CMU_PLL_USB, HAL_CMU_PLL_USER_SYS);
#endif
#ifndef AUD_PLL_INIT_ON
    hal_cmu_pll_disable(HAL_CMU_PLL_AUD, HAL_CMU_PLL_USER_SYS);
#endif
#else // !(best1000 || best2000)
#ifdef USB_PLL_INIT_ON
    hal_cmu_pll_enable(HAL_CMU_PLL_USB, HAL_CMU_PLL_USER_SYS);
#endif
#ifdef AUD_PLL_INIT_ON
    hal_cmu_pll_enable(HAL_CMU_PLL_AUD, HAL_CMU_PLL_USER_SYS);
#endif
#endif // !(best1000 || best2000)

#if defined(MCU_HIGH_PERFORMANCE_MODE) && !defined(ULTRA_LOW_POWER) && defined(OSC_26M_X4_AUD2BB)
#error "Error configuration: MCU_HIGH_PERFORMANCE_MODE has no effect"
#endif
}
#endif // !HAL_CMU_PLL_T

static void BOOT_TEXT_FLASH_LOC hal_cmu_init_periph_clock(void)
{
#ifdef PERIPH_PLL_FREQ
    hal_cmu_periph_set_div(1);
#endif

    // TODO: Move the following SDIO freq setting to hal_sdio.c
#ifdef CHIP_HAS_SDIO
    hal_cmu_sdio_set_freq(HAL_CMU_PERIPH_FREQ_26M);
#endif
}

void hal_cmu_rom_setup(void)
{
    hal_cmu_lpu_wait_26m_ready();
    hal_cmu_simu_init();
    hal_cmu_rom_clock_init();
    hal_cmu_timer0_select_slow();
#ifdef TIMER1_BASE
    hal_cmu_timer1_select_fast();
#endif
    hal_sys_timer_open();

    // Init sys clock
    hal_cmu_sys_set_freq(HAL_CMU_FREQ_26M);

    // Init flash clock (this should be done before load_boot_settings, for security register read)
    hal_cmu_flash_set_freq(HAL_CMU_FREQ_26M);
    // Reset flash controller (for JTAG reset and run)
    // Enable flash controller (flash controller is reset by default since BEST1400)
    hal_cmu_reset_set(HAL_CMU_MOD_O_FLASH);
    hal_cmu_reset_set(HAL_CMU_MOD_H_FLASH);
    hal_cmu_reset_clear(HAL_CMU_MOD_H_FLASH);
    hal_cmu_reset_clear(HAL_CMU_MOD_O_FLASH);

    // Disable cache (for JTAG reset and run)
    hal_cache_disable(HAL_CACHE_ID_I_CACHE);
    hal_cache_disable(HAL_CACHE_ID_D_CACHE);

    // Init APB clock
    hal_cmu_apb_init_div();
}

void hal_cmu_programmer_setup(void)
{
    hal_cmu_ema_init();
    hal_sys_timer_open();

#ifdef JTAG_ENABLE
    hal_iomux_set_jtag();
    hal_cmu_jtag_clock_enable();
#endif

#ifndef FPGA
    int ret;
    // Open analogif (ISPI)
    ret = hal_analogif_open();
    if (ret) {
        hal_cmu_simu_tag(31);
        do { volatile int i = 0; i++; } while (1);
    }
    // Init chip id
    // 1) Read id from ana/rf/pmu
    // 2) Init clock settings in ana/rf/pmu if the default h/w register values are bad
    hal_chipid_init();

    // Enable OSC X2/X4 in cmu after enabling their source in hal_chipid_init()
    hal_cmu_osc_x2_enable();
    hal_cmu_osc_x4_enable();
#endif
}

#ifdef FPGA

void BOOT_TEXT_FLASH_LOC hal_cmu_fpga_setup(void)
{
    hal_cmu_timer0_select_slow();
    hal_sys_timer_open();

    hal_sysfreq_req(HAL_SYSFREQ_USER_INIT, HAL_CMU_FREQ_52M);

    hal_cmu_apb_init_div();
    hal_cmu_ispi_set_freq(HAL_CMU_PERIPH_FREQ_26M);

    // Init peripheral clocks
    hal_cmu_init_periph_clock();

    hal_norflash_init();
#if defined(CHIP_HAS_PSRAM) && defined(PSRAM_ENABLE)
    hal_psram_init();
#endif
}

#else // !FPGA

void BOOT_TEXT_FLASH_LOC hal_cmu_setup(void)
{
    int ret;
    enum HAL_CMU_FREQ_T freq;

    hal_iomux_set_default_config();
#ifdef JTAG_ENABLE
    hal_iomux_set_jtag();
    hal_cmu_jtag_clock_enable();
#endif
    hal_cmu_module_init_state();
    hal_cmu_ema_init();
    hal_cmu_timer0_select_slow();
#ifdef TIMER1_BASE
    hal_cmu_timer1_select_fast();
#endif
    hal_sys_timer_open();
    hal_hw_bootmode_init();

    // Init system/flash/memory clocks before initializing clock setting
    // and before switching PLL
    hal_norflash_set_freq(HAL_CMU_FREQ_26M);
    hal_cmu_mem_set_freq(HAL_CMU_FREQ_26M);
    hal_cmu_sys_set_freq(HAL_CMU_FREQ_26M);

    // Set ISPI module freq
    hal_cmu_ispi_set_freq(HAL_CMU_PERIPH_FREQ_26M);
    // Open analogif (ISPI)
    ret = hal_analogif_open();
    if (ret) {
        hal_cmu_simu_tag(31);
        do { volatile int i = 0; i++; } while (1);
    }
    // Init chip id
    // 1) Read id from ana/rf/pmu
    // 2) Init clock settings in ana/rf/pmu if the default h/w register values are bad
    hal_chipid_init();

#ifdef CALIB_SLOW_TIMER
    // Calib slow timer after determining the crystal freq
    hal_sys_timer_calib();
#endif

    // Enable OSC X2/X4 in cmu after enabling their source in hal_chipid_init()
    hal_cmu_osc_x2_enable();
    hal_cmu_osc_x4_enable();

    // Init PLL selection
    hal_cmu_init_pll_selection();

    // Init peripheral clocks
    hal_cmu_init_periph_clock();

    // Sleep setting
#ifdef NO_LPU_26M
    while (hal_cmu_lpu_init(HAL_CMU_LPU_CLK_NONE) == -1);
#else
    while (hal_cmu_lpu_init(HAL_CMU_LPU_CLK_26M) == -1);
#endif
    // Init sys freq after applying the sleep setting (which might change sys freq)
#ifdef NO_LPU_26M
   hal_sys_timer_delay(MS_TO_TICKS(20));
#endif

    // Init system clock
#ifdef ULTRA_LOW_POWER
    freq = HAL_CMU_FREQ_52M;
#else
    freq = HAL_CMU_FREQ_104M;
#endif
    hal_sysfreq_req(HAL_SYSFREQ_USER_INIT, freq);

    // Init flash
    hal_norflash_init();

}

#endif // !FPGA

