/***************************************************************************
 *
 * 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_types.h"
#include "plat_addr_map.h"
#include "cmsis.h"
#include "hal_norflaship.h"
#include "hal_norflash.h"
#include "hal_bootmode.h"
#include "hal_cmu.h"
#include "hal_sysfreq.h"
#include "hal_timer.h"
#include "hal_trace.h"
#include "norflash_cfg.h"
#include "norflash_drv.h"
#include "pmu.h"
#include "string.h"

/* Demo:
 *
 *  uint8_t data[1024];
 *  hal_norflash_open(HAL_NORFLASH_ID_0, HAL_NORFLASH_SPEED_26M, 0);
 *  \/\/ hal_norflash_open(HAL_NORFLASH_ID_0, HAL_NORFLASH_SPEED_13M, HAL_NORFLASH_OP_MODE_QUAD);
 *  \/\/ hal_norflash_open(HAL_NORFLASH_ID_0, HAL_NORFLASH_SPEED_13M, HAL_NORFLASH_OP_MODE_QUAD|HAL_NORFLASH_OP_MODE_CONTINUOUS_READ);
 *  hal_norflash_erase(HAL_I2C_ID_0, 0, 4096);
 *  memset(data, 0xcc, 1024);
 *  hal_norflash_write(HAL_I2C_ID_0, 0, data, 1024);
 *  for (i = 0; i < 10; ++i) {
 *      TRACE(2,"[0x%x] - 0x%x\n", 0x08000000 + i, *((volatile uint8_t *)(0x08000000 + i)));
 *  }
*/

#define HAL_NORFLASH_CP_ID_LEN              2

#define HAL_NORFLASH_YES                    1
#define HAL_NORFLASH_NO                     0

// 64M Bytes
#define HAL_NORFLASH_ADDR_MASK              0x03FFFFFF

// Protect lower 1/2 by default
#ifndef FLASH_BLOCK_PROTECTION_64M_BIT
#define FLASH_BLOCK_PROTECTION_64M_BIT      0x0034
#endif
#ifndef FLASH_BLOCK_PROTECTION_32M_BIT
#define FLASH_BLOCK_PROTECTION_32M_BIT      0x0038
#endif
#ifndef FLASH_BLOCK_PROTECTION_16M_BIT
#define FLASH_BLOCK_PROTECTION_16M_BIT      0x0034
#endif
#ifndef FLASH_BLOCK_PROTECTION_8M_BIT
#define FLASH_BLOCK_PROTECTION_8M_BIT       0x0030
#endif

struct HAL_Norflash_Context {
    bool opened;
    uint8_t device_id[HAL_NORFLASH_DEVICE_ID_LEN];
#ifdef FLASH_UNIQUE_ID
    uint8_t unique_id[HAL_NORFLASH_UNIQUE_ID_LEN + HAL_NORFLASH_CP_ID_LEN];
#endif
    uint32_t total_size;
    uint32_t block_size;
    uint32_t sector_size;
    uint32_t page_size;
    enum HAL_NORFLASH_RET_T open_state;
};

static struct HAL_Norflash_Context norflash_ctx[HAL_NORFLASH_ID_NUM];

static const char * const err_not_opened = "norflash not opened";

static const struct HAL_NORFLASH_CONFIG_T norflash_cfg = {
#if defined(CHIP_BEST1400) || defined(CHIP_BEST1402)
    .source_clk = HAL_NORFLASH_SPEED_52M,
    .speed = HAL_NORFLASH_SPEED_52M,
#else
#ifdef FPGA
    .source_clk = HAL_NORFLASH_SPEED_13M * 2,
    .speed = HAL_NORFLASH_SPEED_13M,
#elif defined(FLASH_LOW_SPEED)
    .source_clk = HAL_NORFLASH_SPEED_26M * 2,
    .speed = HAL_NORFLASH_SPEED_26M,
#elif defined(OSC_26M_X4_AUD2BB)
    .source_clk = HAL_NORFLASH_SPEED_52M * 2,
    .speed = HAL_NORFLASH_SPEED_52M,
#else
    .source_clk = HAL_NORFLASH_SPEED_104M * 2,
    .speed = HAL_NORFLASH_SPEED_104M,
#endif
#endif
    .mode = HAL_NORFLASH_OP_MODE_STAND_SPI |
            HAL_NORFLASH_OP_MODE_FAST_SPI |
            HAL_NORFLASH_OP_MODE_DUAL_OUTPUT |
            HAL_NORFLASH_OP_MODE_DUAL_IO |
            HAL_NORFLASH_OP_MODE_QUAD_OUTPUT |
            HAL_NORFLASH_OP_MODE_QUAD_IO |
            HAL_NORFLASH_OP_MODE_CONTINUOUS_READ |
            HAL_NORFLASH_OP_MODE_READ_WRAP |
            HAL_NORFLASH_OP_MODE_PAGE_PROGRAM |
            HAL_NORFLASH_OP_MODE_DUAL_PAGE_PROGRAM |
            HAL_NORFLASH_OP_MODE_QUAD_PAGE_PROGRAM,
    .override_config = 0,
};

#ifdef FLASH_FREQ_DYNAMIC_ADJUST
struct HAL_NORFLASH_FREQ_DYNAMIC_ADJUST_CONFIG_T{
    uint32_t source_clk;
    uint8_t  timing_index;
};
static struct HAL_NORFLASH_FREQ_DYNAMIC_ADJUST_CONFIG_T norflash_freq_dynamic_adjust_cfg[2];
#endif

#ifdef FLASH_SUSPEND
enum SUSPEND_STATE_T {
    SUSPEND_STATE_NONE,
    SUSPEND_STATE_ERASE,
    SUSPEND_STATE_PROGRAM,
};

static enum SUSPEND_STATE_T suspend_state;
static uint32_t op_next_addr;
static const uint8_t *op_next_buf;
static uint32_t op_remain_len;
#endif

#ifdef FLASH_SECURITY_REGISTER
static uint32_t sec_reg_base;
static uint16_t sec_reg_size;
static uint16_t sec_reg_offset;
static uint16_t sec_reg_total_size;
static uint16_t sec_reg_pp_size;
static bool sec_reg_enabled;
#endif

enum HAL_CMU_FREQ_T hal_norflash_clk_to_cmu_freq(uint32_t clk)
{
    enum HAL_CMU_FREQ_T freq;

    if (clk >= HAL_NORFLASH_SPEED_208M) {
        freq = HAL_CMU_FREQ_208M;
    } else if (clk >= HAL_NORFLASH_SPEED_104M) {
        freq = HAL_CMU_FREQ_104M;
    } else if (clk >= HAL_NORFLASH_SPEED_78M) {
        freq = HAL_CMU_FREQ_78M;
    } else if (clk >= HAL_NORFLASH_SPEED_52M) {
        freq = HAL_CMU_FREQ_52M;
    } else {
        freq = HAL_CMU_FREQ_26M;
    }

#if defined(CHIP_BEST1000) && defined(AUD_PLL_DOUBLE)
    if (freq == HAL_CMU_FREQ_208M) {
        freq = HAL_CMU_FREQ_104M;
    }
#endif
#ifdef SIMU
#ifdef SIMU_FAST_FLASH
#define MAX_SIMU_FLASH_FREQ     HAL_CMU_FREQ_104M
#else
#define MAX_SIMU_FLASH_FREQ     HAL_CMU_FREQ_52M
#endif
    if (freq > MAX_SIMU_FLASH_FREQ) {
        freq = MAX_SIMU_FLASH_FREQ;
    }
#endif

    return freq;
}

enum HAL_NORFLASH_RET_T hal_norflash_get_size(enum HAL_NORFLASH_ID_T id, uint32_t *total_size,
                                              uint32_t *block_size, uint32_t *sector_size,
                                              uint32_t *page_size)
{
    ASSERT(norflash_ctx[id].opened, err_not_opened);

    if (total_size) {
        *total_size = norflash_ctx[id].total_size;
    }
    if (block_size) {
        *block_size = norflash_ctx[id].block_size;
    }
    if (sector_size) {
        *sector_size = norflash_ctx[id].sector_size;
    }
    if (page_size) {
        *page_size = norflash_ctx[id].page_size;
    }
    return HAL_NORFLASH_OK;
}

enum HAL_NORFLASH_RET_T hal_norflash_get_boundary(enum HAL_NORFLASH_ID_T id, uint32_t address,
                                                  uint32_t *block_boundary, uint32_t *sector_boundary)
{
    ASSERT(norflash_ctx[id].opened, err_not_opened);

    static const uint32_t flash_base[] = { FLASH_BASE, FLASHX_BASE, FLASH_NC_BASE, FLASHX_NC_BASE, };
    int i;

    for (i = 0; i < ARRAY_SIZE(flash_base); i++) {
        if (flash_base[i] <= address && address < flash_base[i] + norflash_ctx[id].total_size) {
            address -= flash_base[i];
            if (block_boundary)
                *block_boundary  = flash_base[i] + (address/norflash_ctx[id].block_size)*norflash_ctx[id].block_size;
            if (sector_boundary)
                *sector_boundary = flash_base[i] + (address/norflash_ctx[id].sector_size)*norflash_ctx[id].sector_size;
            break;
        }
    }

    if (i == ARRAY_SIZE(flash_base)) {
        return HAL_NORFLASH_BAD_ADDR;
    }

    return HAL_NORFLASH_OK;
}

enum HAL_NORFLASH_RET_T hal_norflash_get_id(enum HAL_NORFLASH_ID_T id, uint8_t *value, uint32_t len)
{
    len = MIN(len, sizeof(norflash_ctx[id].device_id));

    memcpy(value, norflash_ctx[id].device_id, len);

    return HAL_NORFLASH_OK;
}

#ifdef FLASH_UNIQUE_ID
enum HAL_NORFLASH_RET_T hal_norflash_get_unique_id(enum HAL_NORFLASH_ID_T id, uint8_t *value, uint32_t len)
{
    ASSERT(norflash_ctx[id].opened, err_not_opened);

    len = MIN(len, sizeof(norflash_ctx[id].unique_id));

    memcpy(value, norflash_ctx[id].unique_id, len);

    return HAL_NORFLASH_OK;
}
#endif

#ifdef FLASH_PROTECTION
static uint32_t hal_norflash_get_bp_cfg(enum HAL_NORFLASH_ID_T id)
{
    uint32_t val = 0;

    // Set BP and CMP bits
    // E.g., for 32M bit flash, BP=0b00010 CMP=1 can protect 0x000000 - 0x3DFFFF

    if (FLASH_SIZE != hal_norflash_get_flash_total_size(id)) {
        return val;
    }

    if (norflash_ctx[id].device_id[0] != 0xC8 &&        // GigaDevice
            norflash_ctx[id].device_id[0] != 0x85 &&    // Puya
            norflash_ctx[id].device_id[0] != 0x20       // Xinxin
            ) {
        return val;
    }

    if (FLASH_SIZE == 0x800000) {
        val = FLASH_BLOCK_PROTECTION_64M_BIT;
    } else if (FLASH_SIZE == 0x400000) {
        val = FLASH_BLOCK_PROTECTION_32M_BIT;
    } else if (FLASH_SIZE == 0x200000) {
        val = FLASH_BLOCK_PROTECTION_16M_BIT;
    } else if (FLASH_SIZE == 0x100000) {
        val = FLASH_BLOCK_PROTECTION_8M_BIT;
    } else {
        // CAUTION:
        // Different configuration for GD and Puya
    }

    return val;
}
#endif

enum HAL_NORFLASH_RET_T hal_norflash_enable_protection(enum HAL_NORFLASH_ID_T id)
{
    int ret = 0;

#ifdef FLASH_PROTECTION
    uint32_t lock;

    if (!norflash_ctx[id].opened) {
        return HAL_NORFLASH_NOT_OPENED;
    }

    lock = int_lock_global();

    norflash_pre_operation();

    ret = norflash_set_block_protection(hal_norflash_get_bp_cfg(id));

    norflash_post_operation();

    int_unlock_global(lock);
#endif

    return ret ? HAL_NORFLASH_BAD_OP : HAL_NORFLASH_OK;
}

enum HAL_NORFLASH_RET_T hal_norflash_disable_protection(enum HAL_NORFLASH_ID_T id)
{
    int ret = 0;

#ifdef FLASH_PROTECTION
    uint32_t lock;

    if (!norflash_ctx[id].opened) {
        return HAL_NORFLASH_NOT_OPENED;
    }

    lock = int_lock_global();

    norflash_pre_operation();

    ret = norflash_set_block_protection(0);

    norflash_post_operation();

    int_unlock_global(lock);
#endif

    return ret ? HAL_NORFLASH_BAD_OP : HAL_NORFLASH_OK;
}

#ifdef FLASH_SECURITY_REGISTER
enum HAL_NORFLASH_RET_T hal_norflash_security_register_lock(enum HAL_NORFLASH_ID_T id, uint32_t start_address, uint32_t len)
{
    uint32_t remain_len;
    int ret = 0;
    uint32_t reg_base;
    uint32_t reg_pos;
    uint32_t pos;
    uint32_t lock_size;
    uint32_t reg_id;

    ASSERT(norflash_ctx[id].opened, err_not_opened);

    if (!sec_reg_enabled) {
        return HAL_NORFLASH_BAD_OP;
    }
    // Check address and length
    if (sec_reg_total_size <= start_address) {
        return HAL_NORFLASH_BAD_ADDR;
    }
    remain_len = sec_reg_total_size - start_address;
    if (len > remain_len) {
        return HAL_NORFLASH_BAD_LEN;
    }
    // Align to register boundary
    remain_len = start_address & (sec_reg_size - 1);
    if (remain_len) {
        start_address -= remain_len;
        len += remain_len;
    }
    remain_len = len & (sec_reg_size - 1);
    if (remain_len) {
        len += sec_reg_size - remain_len;
    }

    pos = start_address;
    remain_len = len;

    reg_base = sec_reg_base;
    reg_pos = 0;
    reg_id = 0;

    norflash_pre_operation();

    while (remain_len > 0 && ret == 0) {
        if (reg_pos <= pos && pos < reg_pos + sec_reg_size) {
            // lock a register
            lock_size = sec_reg_size - (pos - reg_pos);
            ret = norflash_security_register_lock(reg_id);
            if (remain_len > lock_size) {
                remain_len -= lock_size;
            } else {
                remain_len = 0;
            }
            pos += lock_size;
        }
        reg_pos += sec_reg_size;
        reg_base += sec_reg_offset;
        reg_id++;
    }

    norflash_post_operation();

    return ret;
}

enum HAL_NORFLASH_RET_T hal_norflash_security_register_erase(enum HAL_NORFLASH_ID_T id, uint32_t start_address, uint32_t len)
{
    uint32_t remain_len;
    enum HAL_NORFLASH_RET_T ret = HAL_NORFLASH_OK;
    uint32_t reg_base;
    uint32_t reg_pos;
    uint32_t pos;
    uint32_t erase_size;

    ASSERT(norflash_ctx[id].opened, err_not_opened);

    if (!sec_reg_enabled) {
        return HAL_NORFLASH_BAD_OP;
    }
    // Check address and length
    if (sec_reg_total_size <= start_address) {
        return HAL_NORFLASH_BAD_ADDR;
    }
    remain_len = sec_reg_total_size - start_address;
    if (len > remain_len) {
        return HAL_NORFLASH_BAD_LEN;
    }
    // Align to register boundary
    remain_len = start_address & (sec_reg_size - 1);
    if (remain_len) {
        start_address -= remain_len;
        len += remain_len;
    }
    remain_len = len & (sec_reg_size - 1);
    if (remain_len) {
        len += sec_reg_size - remain_len;
    }

    pos = start_address;
    remain_len = len;

    reg_base = sec_reg_base;
    reg_pos = 0;

    norflash_pre_operation();

    while (remain_len > 0 && ret == HAL_NORFLASH_OK) {
        if (reg_pos <= pos && pos < reg_pos + sec_reg_size) {
            // erase a register
            erase_size = sec_reg_size - (pos - reg_pos);
            ret = norflash_security_register_erase(reg_base + (pos - reg_pos));
            if (remain_len > erase_size) {
                remain_len -= erase_size;
            } else {
                remain_len = 0;
            }
            pos += erase_size;
        }
        reg_pos += sec_reg_size;
        reg_base += sec_reg_offset;
    }

    norflash_post_operation();

    return ret;
}

enum HAL_NORFLASH_RET_T hal_norflash_security_register_write(enum HAL_NORFLASH_ID_T id, uint32_t start_address, const uint8_t *buffer, uint32_t len)
{
    const uint8_t *current_buffer;
    uint32_t remain_len;
    enum HAL_NORFLASH_RET_T ret = HAL_NORFLASH_OK;
    uint32_t reg_base;
    uint32_t reg_pos;
    uint32_t pos;
    uint32_t write_size;
    uint32_t each_write;
    uint32_t pp_remain;

    ASSERT(norflash_ctx[id].opened, err_not_opened);

    if (!sec_reg_enabled) {
        return HAL_NORFLASH_BAD_OP;
    }
    // Check address and length
    if (sec_reg_total_size <= start_address) {
        return HAL_NORFLASH_BAD_ADDR;
    }
    remain_len = sec_reg_total_size - start_address;
    if (len > remain_len) {
        return HAL_NORFLASH_BAD_LEN;
    }

    pos = start_address;
    current_buffer = buffer;
    remain_len = len;

    reg_base = sec_reg_base;
    reg_pos = 0;

    norflash_pre_operation();

    while (remain_len > 0 && ret == HAL_NORFLASH_OK) {
        if (reg_pos <= pos && pos < reg_pos + sec_reg_size) {
            // write a register
            if (pos + remain_len <= reg_pos + sec_reg_size) {
                write_size = remain_len;
            } else {
                write_size = sec_reg_size - (pos - reg_pos);
            }
            remain_len -= write_size;
            while (write_size > 0 && ret == HAL_NORFLASH_OK) {
                if (write_size > sec_reg_pp_size) {
                    each_write = sec_reg_pp_size;
                } else {
                    each_write = write_size;
                }
                // Align to security register program page size
                pp_remain = sec_reg_pp_size - ((pos - reg_pos) & (sec_reg_pp_size - 1));
                if (each_write > pp_remain) {
                    each_write = pp_remain;
                }
                ret = norflash_security_register_write(reg_base + (pos - reg_pos), current_buffer, each_write);
                write_size -= each_write;
                pos += each_write;
                current_buffer += each_write;
            }
        }
        reg_pos += sec_reg_size;
        reg_base += sec_reg_offset;
    }

    norflash_post_operation();

    return ret;
}

enum HAL_NORFLASH_RET_T hal_norflash_security_register_read(enum HAL_NORFLASH_ID_T id, uint32_t start_address, uint8_t *buffer, uint32_t len)
{
    uint8_t *current_buffer;
    uint32_t remain_len, read_size;
    int ret = 0;
    uint32_t reg_base;
    uint32_t reg_pos;
    uint32_t pos;
#ifdef FLASH_SEC_REG_FIFO_READ
    uint32_t each_read;
#endif

    ASSERT(norflash_ctx[id].opened, err_not_opened);

    if (!sec_reg_enabled) {
        return HAL_NORFLASH_BAD_OP;
    }
    // Check address and length
    if (sec_reg_total_size <= start_address) {
        return HAL_NORFLASH_BAD_ADDR;
    }
    remain_len = sec_reg_total_size - start_address;
    if (len > remain_len) {
        return HAL_NORFLASH_BAD_LEN;
    }

    pos = start_address;
    current_buffer = buffer;
    remain_len = len;

    reg_base = sec_reg_base;
    reg_pos = 0;

    norflash_pre_operation();

#ifndef FLASH_SEC_REG_FIFO_READ
    uint32_t mode = norflash_security_register_enable_read();
#endif

    while (remain_len > 0 && ret == 0) {
        if (reg_pos <= pos && pos < reg_pos + sec_reg_size) {
            // read a register
            if (pos + remain_len <= reg_pos + sec_reg_size) {
                read_size = remain_len;
            } else {
                read_size = sec_reg_size - (pos - reg_pos);
            }
            remain_len -= read_size;
#ifdef FLASH_SEC_REG_FIFO_READ
            while (read_size > 0 && ret == 0) {
                if (read_size > NORFLASHIP_RXFIFO_SIZE) {
                    each_read = NORFLASHIP_RXFIFO_SIZE;
                } else {
                    each_read = read_size;
                }
                ret = norflash_security_register_read(reg_base + (pos - reg_pos), current_buffer, each_read);
                read_size -= each_read;
                pos += each_read;
                current_buffer += each_read;
            }
#else
            memcpy(current_buffer, (void *)(reg_base + (pos - reg_pos)), read_size);
            pos += read_size;
            current_buffer += read_size;
#endif
        }
        reg_pos += sec_reg_size;
        reg_base += sec_reg_offset;
    }

#ifndef FLASH_SEC_REG_FIFO_READ
    norflash_security_register_disable_read(mode);
#endif

    norflash_post_operation();

    return (ret ? HAL_NORFLASH_ERR : HAL_NORFLASH_OK);
}

static enum HAL_NORFLASH_RET_T hal_norflash_parse_security_register_config(void)
{
    union DRV_NORFLASH_SEC_REG_CFG_T cfg;
    uint32_t reg_cnt;

    cfg = norflash_get_security_register_config();

    if (!cfg.s.enabled) {
        return HAL_NORFLASH_BAD_OP;
    }

    if (cfg.s.base == SEC_REG_BASE_0X1000) {
        sec_reg_base = 0x1000;
    } else if (cfg.s.base == SEC_REG_BASE_0X0000) {
        sec_reg_base = 0;
    } else {
        return HAL_NORFLASH_BAD_CFG;
    }
    sec_reg_base += FLASH_NC_BASE;

    if (cfg.s.size == SEC_REG_SIZE_1024) {
        sec_reg_size = 1024;
    } else if (cfg.s.size == SEC_REG_SIZE_512) {
        sec_reg_size = 512;
    } else if (cfg.s.size == SEC_REG_SIZE_256) {
        sec_reg_size = 256;
    } else {
        return HAL_NORFLASH_BAD_CFG;
    }

    if (cfg.s.offset == SEC_REG_OFFSET_0X1000) {
        sec_reg_offset = 0x1000;
    } else if (cfg.s.offset == SEC_REG_OFFSET_0X0100) {
        sec_reg_offset = 0x0100;
    } else {
        return HAL_NORFLASH_BAD_CFG;
    }

    if (sec_reg_size > sec_reg_offset) {
        return HAL_NORFLASH_BAD_CFG;
    }

    if (cfg.s.cnt == SEC_REG_CNT_3) {
        reg_cnt = 3;
    } else if (cfg.s.cnt == SEC_REG_CNT_4) {
        reg_cnt = 4;
    } else {
        return HAL_NORFLASH_BAD_CFG;
    }

    if (cfg.s.pp == SEC_REG_PP_256) {
        sec_reg_pp_size = 256;
    } else if (cfg.s.pp == SEC_REG_PP_1024) {
        sec_reg_pp_size = 1024;
    } else {
        return HAL_NORFLASH_BAD_CFG;
    }
#if (CHIP_FLASH_CTRL_VER <= 1)
#ifdef FLASH_SEC_REG_PP_1024
    // To write more than 256 bytes on flash controller V1, SPI rate must be lowered to avoid tx FIFO underflow.
    // Otherwise, the data must be split into pieces with size no more than 256 bytes.
#else
    sec_reg_pp_size = 256;
#endif
#endif

    sec_reg_total_size = sec_reg_size * reg_cnt;

    return HAL_NORFLASH_OK;
}
#endif

static void hal_norflash_reset_timing(void)
{
    const uint32_t default_div = 8;

    // Restore default divider
    norflaship_div(default_div);
    norflash_init_sample_delay_by_div(default_div);
}

static enum HAL_NORFLASH_RET_T _norflash_open(enum HAL_NORFLASH_ID_T id, const struct HAL_NORFLASH_CONFIG_T *cfg,
                                              bool reopen, bool calib, uint32_t timing_idx)
{
    struct HAL_NORFLASH_CONFIG_T norcfg;
    enum HAL_CMU_FREQ_T source_freq;
    int result;
    uint32_t op;
    int found;

    if (reopen) {
        ASSERT(norflash_ctx[id].opened, err_not_opened);
    }

    // Place the config into ram
    if (cfg == NULL) {
        return HAL_NORFLASH_CFG_NULL;
    }
    memcpy(&norcfg, cfg, sizeof(norcfg));

    norflaship_busy_wait();

    // Reset states
    norflash_ctx[id].opened = false;
    norflash_ctx[id].open_state = HAL_NORFLASH_NOT_OPENED;
#ifdef FLASH_SUSPEND
    suspend_state = SUSPEND_STATE_NONE;
#endif
#ifdef FLASH_SECURITY_REGISTER
    sec_reg_enabled = false;
#endif

    if (!reopen) {
#if (CHIP_FLASH_CTRL_VER >= 2)
        // Set the direction of 4 IO pins to output when in idle
        norflaship_set_idle_io_dir(0);
#endif
#if (CHIP_FLASH_CTRL_VER >= 3)
        // Set the dummy cycles for security register read command
        norflaship_dummy_others(8);
#endif

        // Reset norflash source clock
        hal_cmu_flash_set_freq(HAL_CMU_FREQ_26M);

        // Reset controller timing
        hal_norflash_reset_timing();

        // Reset norflash in slow clock configuration
        norflash_reset();

        // Get device ID
        norflash_get_id(norflash_ctx[id].device_id, sizeof(norflash_ctx[id].device_id));

        // For each driver in array, match chip and select drv_ops
        found = norflash_match_chip(norflash_ctx[id].device_id, sizeof(norflash_ctx[id].device_id));
        if (!found) {
            result = HAL_NORFLASH_BAD_ID;
            goto _exit;
        }

#ifdef FLASH_UNIQUE_ID
        // 1) Flash controller V1:
        //    Use slow clock to avoid rx fifo overflow
        // 2) Flash controller V2/3/4/5:
        //    Use slow clock to avoid stopping SPI clock
        //    (One bit might be read as a fixed 1 at the SPI clock stopping edge in some timing combinations)
        // 3) Flash controller V6 or later:
        //    Can use fast clock
        norflash_get_unique_id(norflash_ctx[id].unique_id, sizeof(norflash_ctx[id].unique_id));
#endif
    }

    // Set norflash source clock
    source_freq = hal_norflash_clk_to_cmu_freq(norcfg.source_clk);
    hal_cmu_flash_set_freq(source_freq);

    /* over write config */
    if (norcfg.override_config) {
        /* div */
        norflaship_div(norcfg.div);
        /* cmd quad */
        norflaship_cmdquad(norcfg.cmdquad?HAL_NORFLASH_YES:HAL_NORFLASH_NO);
        /* sample delay */
        norflaship_samdly(norcfg.samdly);
#if 0
        /* dummy clc */
        norflaship_dummyclc(norcfg.dummyclc);

        /* dummy clc en */
        norflaship_dummyclcen(norcfg.dummyclcen);

        /* 4 byte address */
        norflaship_addrbyte4(norcfg.byte4byteaddr);
#endif
        /* ru en */
        norflaship_ruen(norcfg.spiruen);
        /* rd en */
        norflaship_rden(norcfg.spirden);
        /* rd cmd */
        norflaship_rdcmd(norcfg.rdcmd);
        /* frd cmd */
        norflaship_frdcmd(norcfg.frdcmd);
        /* qrd cmd */
        norflaship_qrdcmd(norcfg.qrdcmd);
    } else {
        // Init divider
        result = norflash_init_div(&norcfg);
        if (result != 0) {
            result = HAL_NORFLASH_BAD_DIV;
            goto _exit;
        }

        if (!reopen || calib) {
            if (reopen) {
                norflash_pre_operation();
            }
            // Calib with the new divider
            result = norflash_sample_delay_calib(DRV_NORFLASH_CALIB_FLASH_ID);
            if (result != 0) {
                result = HAL_NORFLASH_BAD_CALIB_ID;
                goto _exit;
            }
            if (reopen) {
                norflash_post_operation();
            }
        } else {
            norflash_set_sample_delay_index(timing_idx);
        }
    }

    if (!reopen) {
        norflash_get_size(&norflash_ctx[id].total_size, &norflash_ctx[id].block_size,
            &norflash_ctx[id].sector_size, &norflash_ctx[id].page_size);

#ifdef FLASH_SECURITY_REGISTER
        result = hal_norflash_parse_security_register_config();
        if (result == HAL_NORFLASH_OK) {
            sec_reg_enabled = true;
        } else if (result != HAL_NORFLASH_BAD_OP) {
            goto _exit;
        }
#endif

#if (CHIP_FLASH_CTRL_VER <= 1) && !defined(FLASH_LOW_SPEED) && !defined(OSC_26M_X4_AUD2BB)
        // 1) Flash controller V2 or later
        //    No requirement on system_freq
        // 2) Flash controller V1
        //    Requirement on system_freq when running in quad mode (4-line mode):
        //      Byte Access:     flash_line_speed < 2 * system_freq
        //      Halfword Access: flash_line_speed < 4 * system_freq
        //      Word Access:     flash_line_speed < 8 * system_freq
        //    The maximum flash_line_speed is 120M in spec, and PLL_FREQ / 2 in our system.
        //    Normally it is 24.576M * 8 / 2 ~= 100M.
        //    So the safe system_freq should be larger than 50M/25M/12.5M for byte/halfword/word access.
        //    Cached access to flash is always safe, because it is always word-aligned (system_freq is never below 26M).
        //    However, uncached access (e.g., access to audio/user/factory data sections) is under risk.
        hal_sysfreq_set_min_freq(HAL_CMU_FREQ_52M);
#endif

#if defined(PROGRAMMER) || defined(OTA_PROGRAMMER)
        norflash_init_status(0);
#elif defined(FLASH_PROTECTION)
        norflash_set_block_protection(hal_norflash_get_bp_cfg(id));
#endif
    }

    op = norcfg.mode;
    if (norcfg.speed >= HAL_NORFLASH_SPEED_104M) {
        op |= HAL_NORFLASH_OP_MODE_HIGH_PERFORMANCE;
    }

    // Divider will be set to normal read mode
    result = norflash_set_mode(op);
    if (result != 0) {
        result = HAL_NORFLASH_BAD_OP;
        goto _exit;
    }

    // -----------------------------
    // From now on, norflash_pre_operation() must be called before
    // sending any command to flash
    // -----------------------------

    if (!reopen) {
#if defined(CHIP_BEST1400) || defined(CHIP_BEST1402)
        if (norcfg.dec_enable && (norcfg.dec_size > 0)) {
            norflaship_dec_saddr(norcfg.dec_addr);
            norflaship_dec_eaddr(norcfg.dec_addr + norcfg.dec_size);
            norflaship_dec_index(norcfg.dec_idx);
            norflaship_dec_enable();
        } else {
            norflaship_dec_disable();
        }
#endif

#if defined(CHIP_BEST2001) && (defined(PSRAMUHS_ENABLE) || defined(PSRAM_ENABLE))
        norflaship_fetch_disable();
#endif
    }

    if (!norcfg.override_config && (!reopen || calib)) {
        result = norflash_sample_delay_calib(DRV_NORFLASH_CALIB_MAGIC_WORD);
        if (result != 0) {
            result = HAL_NORFLASH_BAD_CALIB_MAGIC;
            goto _exit;
        }
    }

    norflash_ctx[id].opened = true;

    result = HAL_NORFLASH_OK;

_exit:
    if (result != HAL_NORFLASH_OK) {
        hal_cmu_flash_set_freq(HAL_CMU_FREQ_26M);
        hal_norflash_reset_timing();
        // Flash might be accessed again
    }

    norflash_ctx[id].open_state = result;

    return result;
}

enum HAL_NORFLASH_RET_T hal_norflash_open(enum HAL_NORFLASH_ID_T id, const struct HAL_NORFLASH_CONFIG_T *cfg)
{
    return _norflash_open(id, cfg, false, true, 0);
}

enum HAL_NORFLASH_RET_T hal_norflash_reopen(enum HAL_NORFLASH_ID_T id, const struct HAL_NORFLASH_CONFIG_T *cfg)
{
    return _norflash_open(id, cfg, true, true, 0);
}

enum HAL_NORFLASH_RET_T hal_norflash_apply_config(enum HAL_NORFLASH_ID_T id, const struct HAL_NORFLASH_CONFIG_T *cfg, uint32_t timing_idx)
{
    return _norflash_open(id, cfg, true, false, timing_idx);
}

uint32_t hal_norflash_get_timing_index(enum HAL_NORFLASH_ID_T id)
{
    ASSERT(norflash_ctx[id].opened, err_not_opened);
    return norflash_get_sample_delay_index();
}

void hal_norflash_show_calib_result(void)
{
    norflash_show_calib_result();
}

enum HAL_NORFLASH_RET_T hal_norflash_erase_chip(enum HAL_NORFLASH_ID_T id)
{
    uint32_t total_size = 0;

    ASSERT(norflash_ctx[id].opened, err_not_opened);

    total_size = norflash_ctx[id].total_size;

    return hal_norflash_erase(id, 0, total_size);
}

static enum HAL_NORFLASH_RET_T hal_norflash_erase_int(enum HAL_NORFLASH_ID_T id, uint32_t start_address, uint32_t len, int suspend)
{
    uint32_t remain_len, current_address, total_size, block_size, sector_size;
    enum HAL_NORFLASH_RET_T ret = HAL_NORFLASH_OK;
    total_size      = norflash_ctx[id].total_size;
    block_size      = norflash_ctx[id].block_size;
    sector_size     = norflash_ctx[id].sector_size;

    // Check address and length
    if (total_size <= (start_address & HAL_NORFLASH_ADDR_MASK)) {
        return HAL_NORFLASH_BAD_ADDR;
    }
    remain_len      = total_size - (start_address & HAL_NORFLASH_ADDR_MASK);
    if (len > remain_len) {
        return HAL_NORFLASH_BAD_LEN;
    }

#ifdef PUYA_FLASH_ERASE_PAGE_ENABLE
    // Align to page boundary
    uint32_t page_size;

    page_size       = norflash_ctx[id].page_size;
    remain_len      = start_address & (page_size - 1);
    if (remain_len) {
        start_address -= remain_len;
        len         += remain_len;
    }
    remain_len      = len & (page_size - 1);
    if (remain_len) {
        len         += page_size - remain_len;
    }
#else
    // Align to sector boundary
    remain_len      = start_address & (sector_size - 1);
    if (remain_len) {
        start_address -= remain_len;
        len         += remain_len;
    }
    remain_len      = len & (sector_size - 1);
    if (remain_len) {
        len         += sector_size - remain_len;
    }
#endif
    current_address = start_address;
    remain_len      = len;

    norflash_pre_operation();

    if ((current_address & HAL_NORFLASH_ADDR_MASK) == 0 && remain_len >= total_size) {
        // erase whole chip
        ret = norflash_erase(current_address, DRV_NORFLASH_ERASE_CHIP, suspend);
    } else {
        while (remain_len > 0 && ret == HAL_NORFLASH_OK) {
            if (remain_len >= block_size && ((current_address & (block_size - 1)) == 0)) {
                // if large enough to erase a block and current_address is block boundary - erase a block
                ret = norflash_erase(current_address, DRV_NORFLASH_ERASE_BLOCK, suspend);
                remain_len -= block_size;
                current_address += block_size;
            } else {
#ifdef PUYA_FLASH_ERASE_PAGE_ENABLE
                if (remain_len >= sector_size && ((current_address & (sector_size - 1)) == 0)) {
                    // if large enough to erase a sector and current_address is sector boundary - erase a sector
                    ret = norflash_erase(current_address, DRV_NORFLASH_ERASE_SECTOR, suspend);
                    remain_len -= sector_size;
                    current_address += sector_size;
                } else {
                   // erase a page
                    ret = norflash_erase(current_address, DRV_NORFLASH_ERASE_PAGE, suspend);
                    if (remain_len > page_size) {
                        remain_len -= page_size;
                    } else {
                        remain_len = 0;
                    }
                    current_address += page_size;
                }
#else
                // erase a sector
                ret = norflash_erase(current_address, DRV_NORFLASH_ERASE_SECTOR, suspend);
                if (remain_len > sector_size) {
                    remain_len -= sector_size;
                } else {
                    remain_len = 0;
                }
                current_address += sector_size;
#endif
            }
        }
    }

    norflash_post_operation();

#ifdef FLASH_SUSPEND
    if (ret == HAL_NORFLASH_SUSPENDED) {
        suspend_state = SUSPEND_STATE_ERASE;
        op_next_addr = current_address;
        op_remain_len = remain_len;
    } else {
        suspend_state = SUSPEND_STATE_NONE;
    }
#endif

    return ret;
}

enum HAL_NORFLASH_RET_T hal_norflash_erase_suspend(enum HAL_NORFLASH_ID_T id, uint32_t start_address, uint32_t len, int suspend)
{
    ASSERT(norflash_ctx[id].opened, err_not_opened);

#ifdef FLASH_SUSPEND
    if (suspend_state != SUSPEND_STATE_NONE) {
        return HAL_NORFLASH_BAD_OP;
    }
    if ((norflash_get_supported_mode() & HAL_NORFLASH_OP_MODE_SUSPEND) == 0) {
        suspend = 0;
    }
#endif

    return hal_norflash_erase_int(id, start_address, len, suspend);
}

enum HAL_NORFLASH_RET_T hal_norflash_erase(enum HAL_NORFLASH_ID_T id, uint32_t start_address, uint32_t len)
{
    return hal_norflash_erase_suspend(id, start_address, len, 0);
}

enum HAL_NORFLASH_RET_T hal_norflash_erase_resume(enum HAL_NORFLASH_ID_T id, int suspend)
{
#ifdef FLASH_SUSPEND
    enum HAL_NORFLASH_RET_T ret;

    ASSERT(norflash_ctx[id].opened, err_not_opened);

    if (suspend_state != SUSPEND_STATE_ERASE) {
        return HAL_NORFLASH_BAD_OP;
    }
    if ((norflash_get_supported_mode() & HAL_NORFLASH_OP_MODE_SUSPEND) == 0) {
        return HAL_NORFLASH_BAD_OP;
    }

    ret = norflash_erase_resume(suspend);
    if (ret == HAL_NORFLASH_SUSPENDED) {
        return ret;
    }

    return hal_norflash_erase_int(id, op_next_addr, op_remain_len, suspend);
#else
    return HAL_NORFLASH_OK;
#endif
}

static enum HAL_NORFLASH_RET_T hal_norflash_write_int(enum HAL_NORFLASH_ID_T id, uint32_t start_address, const uint8_t *buffer, uint32_t len, int suspend)
{
    const uint8_t *current_buffer;
    uint32_t remain_len, current_address, total_size, page_size, write_size;
    uint32_t pp_remain;
    enum HAL_NORFLASH_RET_T ret = HAL_NORFLASH_OK;

    total_size      = norflash_ctx[id].total_size;
    page_size       = norflash_ctx[id].page_size;

    // Check address and length
    if (total_size <= (start_address & HAL_NORFLASH_ADDR_MASK)) {
        return HAL_NORFLASH_BAD_ADDR;
    }
    remain_len      = total_size - (start_address & HAL_NORFLASH_ADDR_MASK);
    if (len > remain_len) {
        return HAL_NORFLASH_BAD_LEN;
    }

    current_address = start_address;
    current_buffer  = buffer;
    remain_len      = len;

    norflash_pre_operation();

    while (remain_len > 0 && ret == HAL_NORFLASH_OK) {
        if (remain_len > page_size) {
            write_size = page_size;
        } else {
            write_size = remain_len;
        }
        // Align to program page
        pp_remain = page_size - (current_address & (page_size - 1));
        if (write_size > pp_remain) {
            write_size = pp_remain;
        }

        ret = norflash_write(current_address, current_buffer, write_size, suspend);

        current_address += write_size;
        current_buffer  += write_size;
        remain_len      -= write_size;
    }

    norflash_post_operation();

#ifdef FLASH_SUSPEND
    if (ret == HAL_NORFLASH_SUSPENDED) {
        suspend_state = SUSPEND_STATE_PROGRAM;
        op_next_addr = current_address;
        op_next_buf = current_buffer;
        op_remain_len = remain_len;
    } else {
        suspend_state = SUSPEND_STATE_NONE;
    }
#endif

    return ret;
}

enum HAL_NORFLASH_RET_T hal_norflash_write_suspend(enum HAL_NORFLASH_ID_T id, uint32_t start_address, const uint8_t *buffer, uint32_t len, int suspend)
{
    ASSERT(norflash_ctx[id].opened, err_not_opened);

#ifdef FLASH_SUSPEND
    if (suspend_state != SUSPEND_STATE_NONE) {
        return HAL_NORFLASH_BAD_OP;
    }
    if ((norflash_get_supported_mode() & HAL_NORFLASH_OP_MODE_SUSPEND) == 0) {
        suspend = 0;
    }
#endif

    return hal_norflash_write_int(id, start_address, buffer, len, suspend);
}

enum HAL_NORFLASH_RET_T hal_norflash_write(enum HAL_NORFLASH_ID_T id, uint32_t start_address, const uint8_t *buffer, uint32_t len)
{
    return hal_norflash_write_suspend(id, start_address, buffer, len, 0);
}

enum HAL_NORFLASH_RET_T hal_norflash_write_resume(enum HAL_NORFLASH_ID_T id, int suspend)
{
#ifdef FLASH_SUSPEND
    enum HAL_NORFLASH_RET_T ret;

    ASSERT(norflash_ctx[id].opened, err_not_opened);

    if (suspend_state != SUSPEND_STATE_PROGRAM) {
        return HAL_NORFLASH_BAD_OP;
    }
    if ((norflash_get_supported_mode() & HAL_NORFLASH_OP_MODE_SUSPEND) == 0) {
        return HAL_NORFLASH_BAD_OP;
    }

    ret = norflash_write_resume(suspend);
    if (ret == HAL_NORFLASH_SUSPENDED) {
        return ret;
    }

    return hal_norflash_write_int(id, op_next_addr, op_next_buf, op_remain_len, suspend);
#else
    return HAL_NORFLASH_OK;
#endif
}

enum HAL_NORFLASH_RET_T hal_norflash_suspend_check_irq(enum HAL_NORFLASH_ID_T id, uint32_t irq_num)
{
#ifdef FLASH_SUSPEND
    int ret;

    ret = norflash_suspend_check_irq(irq_num);
    if (ret) {
        return HAL_NORFLASH_ERR;
    }
#endif

    return HAL_NORFLASH_OK;
}

enum HAL_NORFLASH_RET_T hal_norflash_read(enum HAL_NORFLASH_ID_T id, uint32_t start_address, uint8_t *buffer, uint32_t len)
{
    uint8_t *current_buffer;
    uint32_t remain_len, current_address, total_size, read_size;

    ASSERT(norflash_ctx[id].opened, err_not_opened);

    total_size      = norflash_ctx[id].total_size;

    // Check address and length
    if (total_size <= (start_address & HAL_NORFLASH_ADDR_MASK)) {
        return HAL_NORFLASH_BAD_ADDR;
    }
    remain_len      = total_size - (start_address & HAL_NORFLASH_ADDR_MASK);
    if (len > remain_len) {
        return HAL_NORFLASH_BAD_LEN;
    }

    read_size       = NORFLASHIP_RXFIFO_SIZE;
    remain_len      = len;
    current_address = start_address;
    current_buffer  = buffer;

    norflash_pre_operation();

    while (remain_len > 0) {
        read_size = (remain_len > NORFLASHIP_RXFIFO_SIZE) ? NORFLASHIP_RXFIFO_SIZE : remain_len;
        norflash_read(current_address, current_buffer, read_size);

        current_address += read_size;
        current_buffer  += read_size;
        remain_len      -= read_size;
    }

    norflash_post_operation();

    return HAL_NORFLASH_OK;
}

enum HAL_NORFLASH_RET_T hal_norflash_close(enum HAL_NORFLASH_ID_T id)
{
    return HAL_NORFLASH_OK;
}

void hal_norflash_sleep(enum HAL_NORFLASH_ID_T id)
{
    if (!norflash_ctx[id].opened) {
        return;
    }

#ifdef FLASH_DEEP_SLEEP
    norflash_sleep();
#else
    norflash_pre_operation();
#endif

    norflaship_busy_wait();
    norflaship_sleep();
}

void hal_norflash_wakeup(enum HAL_NORFLASH_ID_T id)
{
    if (!norflash_ctx[id].opened) {
        return;
    }

    norflaship_wakeup();

#ifdef FLASH_DEEP_SLEEP
    norflash_wakeup();
#else
    norflash_post_operation();
#endif

#if 0
    // Flush prefetch buffer
    *(volatile uint32_t *)FLASH_NC_BASE;
    *(volatile uint32_t *)(FLASH_NC_BASE + 0x1000);
#else
    norflaship_clear_rxfifo();
    norflaship_busy_wait();
#endif
}

int hal_norflash_busy(void)
{
    return norflaship_is_busy();
}

static void hal_norflash_prefetch_idle(void)
{
    hal_sys_timer_delay(4);
    if (norflaship_is_busy()) {
        hal_sys_timer_delay(4);
    }
}

#ifdef FLASH_FREQ_DYNAMIC_ADJUST
int hal_norflash_freq_dynamic_adjust_save_config(struct HAL_NORFLASH_FREQ_DYNAMIC_ADJUST_CONFIG_T *adjust_cfg)
{
    int ret = -1;

    for (uint8_t i = 0; i<ARRAY_SIZE(norflash_freq_dynamic_adjust_cfg); i++){
        if (!norflash_freq_dynamic_adjust_cfg[i].source_clk){
            norflash_freq_dynamic_adjust_cfg[i] = *adjust_cfg;
            ret = 0;
            break;
        }
    }
    
    return ret;
}

int hal_norflash_freq_dynamic_adjust_set_freq(enum HAL_CMU_FREQ_T freq)
{
    int ret = -1;

    for (uint8_t i = 0; i<ARRAY_SIZE(norflash_freq_dynamic_adjust_cfg); i++){
        if (hal_norflash_clk_to_cmu_freq(norflash_freq_dynamic_adjust_cfg[i].source_clk) == freq){
            struct HAL_NORFLASH_CONFIG_T norflash_cfg = *(struct HAL_NORFLASH_CONFIG_T *)hal_norflash_get_init_config();
            norflash_cfg.source_clk = norflash_freq_dynamic_adjust_cfg[i].source_clk;
            hal_norflash_apply_config(HAL_NORFLASH_ID_0, &norflash_cfg, norflash_freq_dynamic_adjust_cfg[i].timing_index);
            ret = 0;
            break;
        }
    }
    
    return ret;
}
#endif

void hal_norflash_set_freq(enum HAL_CMU_FREQ_T freq)
{
    if (!hal_norflash_opened(HAL_NORFLASH_ID_0)){
        // At least best1000 and best2001 require the timing reset
#if 1
        // Make sure flash is still working after freq change

        // Wait until norflash becomes idle
        hal_norflash_prefetch_idle();

        // Reset flash timing
        hal_norflash_reset_timing();
#endif

        hal_cmu_flash_set_freq(freq);
    }
#ifdef FLASH_FREQ_DYNAMIC_ADJUST
    else{
        if (hal_cmu_flash_get_freq() != freq){
            hal_norflash_freq_dynamic_adjust_set_freq(freq);
        }
    }
#endif
}

const struct HAL_NORFLASH_CONFIG_T *hal_norflash_get_init_config(void)
{
    return &norflash_cfg;
}

enum HAL_NORFLASH_RET_T hal_norflash_init(void)
{
    enum HAL_NORFLASH_RET_T ret;
    struct HAL_NORFLASH_CONFIG_T norflash_cfg = *(struct HAL_NORFLASH_CONFIG_T *)hal_norflash_get_init_config();

    // Pmu codes might be located in flash
    pmu_flash_freq_config(norflash_cfg.speed);

    // Avoid flash access from here
    hal_norflash_prefetch_idle();

    ret = hal_norflash_open(HAL_NORFLASH_ID_0, &norflash_cfg);
    if (ret != HAL_NORFLASH_OK) {
        goto _exit;
    }
#ifdef FLASH_FREQ_DYNAMIC_ADJUST
    struct HAL_NORFLASH_FREQ_DYNAMIC_ADJUST_CONFIG_T dynamic_adjust_cfg;
    uint32_t source_clk_save;
    source_clk_save = norflash_cfg.source_clk;
    dynamic_adjust_cfg.source_clk = norflash_cfg.source_clk;
    dynamic_adjust_cfg.timing_index = hal_norflash_get_timing_index(HAL_NORFLASH_ID_0);
    hal_norflash_freq_dynamic_adjust_save_config(&dynamic_adjust_cfg);
    norflash_cfg.source_clk = HAL_NORFLASH_SPEED_26M;
    norflash_cfg.speed = HAL_NORFLASH_SPEED_26M;
    ret = hal_norflash_reopen(HAL_NORFLASH_ID_0, &norflash_cfg);
    if (ret != HAL_NORFLASH_OK) {
        goto _exit;
    }
    dynamic_adjust_cfg.source_clk = norflash_cfg.source_clk;
    dynamic_adjust_cfg.timing_index = hal_norflash_get_timing_index(HAL_NORFLASH_ID_0);
    hal_norflash_freq_dynamic_adjust_save_config(&dynamic_adjust_cfg);
    hal_norflash_set_freq(hal_norflash_clk_to_cmu_freq(source_clk_save));
#endif
_exit:
    return ret;
}

enum HAL_NORFLASH_RET_T hal_norflash_deinit(void)
{
    // Avoid flash access from here
    hal_norflash_prefetch_idle();

#if (CHIP_FLASH_CTRL_VER >= 2)
    // Set the direction of 4 IO pins to output when in idle
    norflaship_set_idle_io_dir(0);
#endif
#if (CHIP_FLASH_CTRL_VER >= 3)
    // Set the dummy cycles for security register read command
    norflaship_dummy_others(8);
#endif

    // Reset norflash source clock
    hal_cmu_flash_set_freq(HAL_CMU_FREQ_26M);

    // Reset controller timing
    hal_norflash_reset_timing();

    // Reset norflash in slow clock configuration
    norflash_reset();

    norflash_ctx[HAL_NORFLASH_ID_0].opened = false;

    return HAL_NORFLASH_OK;
}

uint32_t hal_norflash_get_flash_total_size(enum HAL_NORFLASH_ID_T id)
{
    return norflash_ctx[id].total_size;
}

int hal_norflash_opened(enum HAL_NORFLASH_ID_T id)
{
    return norflash_ctx[id].opened;
}

enum HAL_NORFLASH_RET_T hal_norflash_get_open_state(enum HAL_NORFLASH_ID_T id)
{
    return norflash_ctx[id].open_state;
}

enum HAL_NORFLASH_RET_T hal_norflash_enable_remap(enum HAL_NORFLASH_ID_T id, uint32_t addr, uint32_t len, uint32_t offset)
{
#if (CHIP_FLASH_CTRL_VER >= 2)
    uint32_t flash_size;
    uint32_t remap_from;
    int ret;

    STATIC_ASSERT((FLASH_BASE & HAL_NORFLASH_ADDR_MASK) == 0, "Bad FLASH_BASE");
    STATIC_ASSERT((FLASH_SIZE & (FLASH_SIZE - 1)) == 0, "Bad FLASH_SIZE");

#ifdef OTA_BARE_BOOT
    flash_size = FLASH_SIZE;
#else
    if (!norflash_ctx[id].opened) {
        return HAL_NORFLASH_NOT_OPENED;
    }

    flash_size = norflash_ctx[id].total_size;
#ifndef PROGRAMMER
    if (flash_size > FLASH_SIZE) {
        flash_size = FLASH_SIZE;
    }
#endif
#endif

    remap_from = addr & HAL_NORFLASH_ADDR_MASK;

#ifdef CHIP_BEST2300P
    uint32_t remap_to, remap_len, remap_end, region_len;
    uint8_t remap_id;
    uint8_t msb_pos;

    if (offset & (offset - 1)) {
        return HAL_NORFLASH_BAD_ADDR;
    }
    if (offset > flash_size / 2) {
        return HAL_NORFLASH_BAD_ADDR;
    }
    flash_size = offset * 2;

    if (remap_from < flash_size / (1 << (NORFLASHIP_REMAP_NUM + 1))) {
        return HAL_NORFLASH_BAD_ADDR;
    }
    if (remap_from % (flash_size / (1 << (NORFLASHIP_REMAP_NUM + 1)))) {
        return HAL_NORFLASH_BAD_ADDR;
    }

    if (remap_from + len > flash_size / 2) {
        return HAL_NORFLASH_BAD_LEN;
    }

    norflaship_busy_wait();

    remap_id = 0;
    remap_end = flash_size / 2;
    remap_len = remap_end - remap_from;
    while (remap_len && remap_id < NORFLASHIP_REMAP_NUM) {
        msb_pos = 31 - __CLZ(remap_len);
        region_len = 1 << msb_pos;
        remap_from = remap_end - region_len;
        remap_to = flash_size - remap_end; // OR: (flash_size / 2 + (flash_size / 2 - remap_end))
        ret = norflaship_config_remap_section(remap_id, remap_from, region_len, remap_to);
        if (ret) {
            return HAL_NORFLASH_BAD_OP;
        }
        remap_len -= region_len;
        remap_end -= region_len;
        remap_id++;
    }

    if (remap_len) {
        return HAL_NORFLASH_BAD_LEN;
    }
#else
    // Align len to sector size
    len = (len + (REMAP_SECTOR_SIZE - 1)) & ~(REMAP_SECTOR_SIZE - 1);
    if (len > offset) {
        return HAL_NORFLASH_BAD_LEN;
    }
    if (remap_from + len > offset) {
        return HAL_NORFLASH_BAD_LEN;
    }
    if (remap_from + len + offset > flash_size) {
        return HAL_NORFLASH_BAD_ADDR;
    }

    norflaship_busy_wait();

    ret = norflaship_config_remap_section(0, remap_from, len, remap_from + offset);
    if (ret) {
        return HAL_NORFLASH_BAD_OP;
    }
#endif

    hal_norflash_re_enable_remap(id);

    return HAL_NORFLASH_OK;
#else
    return HAL_NORFLASH_ERR;
#endif
}

enum HAL_NORFLASH_RET_T hal_norflash_disable_remap(enum HAL_NORFLASH_ID_T id)
{
#if (CHIP_FLASH_CTRL_VER >= 2)
    norflaship_busy_wait();

    norflaship_disable_remap();

    norflaship_busy_wait();
    norflaship_clear_fifos();

    return HAL_NORFLASH_OK;
#else
    return HAL_NORFLASH_ERR;
#endif
}

enum HAL_NORFLASH_RET_T hal_norflash_re_enable_remap(enum HAL_NORFLASH_ID_T id)
{
#if (CHIP_FLASH_CTRL_VER >= 2)
    norflaship_busy_wait();

    norflaship_enable_remap();

    norflaship_busy_wait();
    norflaship_clear_fifos();

    return HAL_NORFLASH_OK;
#else
    return HAL_NORFLASH_ERR;
#endif
}

int hal_norflash_get_remap_status(enum HAL_NORFLASH_ID_T id)
{
#if (CHIP_FLASH_CTRL_VER >= 2)
    return norflaship_get_remap_status();
#else
    return HAL_NORFLASH_ERR;
#endif
}

