/***************************************************************************
 *
 * 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 "cmsis.h"
#include "norflash_drv.h"
#include "cmsis.h"
#include "hal_cache.h"
#include "hal_norflaship.h"
#include "hal_norflash.h"
#include "hal_sleep.h"
#include "hal_trace.h"
#include "hal_timer.h"
#include "norflash_gd25q32c.h"
#include "plat_addr_map.h"
#include "tool_msg.h"

#ifdef PROGRAMMER
#include "task_schedule.h"
#else
#define TASK_SCHEDULE                           true
#endif

#if (CHIP_FLASH_CTRL_VER >= 2) && !defined(ROM_BUILD) && !defined(PROGRAMMER) && !(defined(CHIP_BEST2001) && (defined(PSRAMUHS_ENABLE) || defined(PSRAM_ENABLE)))
#define FLASH_BURST_WRAP
#endif

#define SAMP_DELAY_PRIO_FALLING_EDGE

#define NORFLASH_UNIQUE_ID_LEN                  18

#define NORFLASH_MAX_DIV                        0xFF

#define NORFLASH_DEFAULT_MAX_SPEED              (104 * 1000 * 1000)

#define NORFLASH_DIV1_MAX_SPEED                 (99 * 1000 * 1000)

#define NORFLASH_SPEED_RATIO_DENOMINATOR        8

#define NORFLASH_PUYA_ID_PREFIX                 0x85

#define NORFLASH_XTS_ID_PREFIX                  0x0B

#define XTS_UNIQUE_ID_LEN                       16
#define XTS_UNIQUE_ID_CMD                       0x5A
#define XTS_UNIQUE_ID_PARAM                     0x00019400

// GigaDevice
extern const struct NORFLASH_CFG_T gd25lq64c_cfg;
extern const struct NORFLASH_CFG_T gd25lq32c_cfg;
extern const struct NORFLASH_CFG_T gd25lq16c_cfg;
extern const struct NORFLASH_CFG_T gd25lq80c_cfg;
extern const struct NORFLASH_CFG_T gd25q32c_cfg;
extern const struct NORFLASH_CFG_T gd25q80c_cfg;
extern const struct NORFLASH_CFG_T gd25d40c_cfg;
extern const struct NORFLASH_CFG_T gd25d20c_cfg;

// Puya
extern const struct NORFLASH_CFG_T p25q128l_cfg;
extern const struct NORFLASH_CFG_T p25q64l_cfg;
extern const struct NORFLASH_CFG_T p25q32l_cfg;
extern const struct NORFLASH_CFG_T p25q16l_cfg;
extern const struct NORFLASH_CFG_T p25q80h_cfg;
extern const struct NORFLASH_CFG_T p25q21h_cfg;
extern const struct NORFLASH_CFG_T p25q40h_cfg;

// XTS
extern const struct NORFLASH_CFG_T xt25q08b_cfg;

// EON
extern const struct NORFLASH_CFG_T en25s80b_cfg;

// XMC
extern const struct NORFLASH_CFG_T xm25qh16c_cfg;
extern const struct NORFLASH_CFG_T xm25qh80b_cfg;

static const struct NORFLASH_CFG_T * const flash_list[] = {
    // ----------------------
    // GigaDevice
    // ----------------------
#if defined(__NORFLASH_ALL__) || defined(__NORFLASH_GD25LQ64C__)
    &gd25lq64c_cfg,
#endif
#if defined(__NORFLASH_ALL__) || defined(__NORFLASH_GD25LQ32C__)
    &gd25lq32c_cfg,
#endif
#if defined(__NORFLASH_ALL__) || defined(__NORFLASH_GD25LQ6C__)
    &gd25lq16c_cfg,
#endif
#if defined(__NORFLASH_ALL__) || defined(__NORFLASH_GD25LQ80C__)
    &gd25lq80c_cfg,
#endif
#if defined(__NORFLASH_ALL__) || defined(__NORFLASH_GD25Q32C__)
    &gd25q32c_cfg,
#endif
#if defined(__NORFLASH_ALL__) || defined(__NORFLASH_GD25Q80C__)
    &gd25q80c_cfg,
#endif
#if defined(__NORFLASH_ALL__) || defined(__NORFLASH_GD25D40C__)
    &gd25d40c_cfg,
#endif
#if defined(__NORFLASH_ALL__) || defined(__NORFLASH_GD25D20C__)
    &gd25d20c_cfg,
#endif

    // ----------------------
    // Puya
    // ----------------------
#if defined(__NORFLASH_ALL__) || defined(__NORFLASH_P25Q128L__)
    &p25q128l_cfg,
#endif
#if defined(__NORFLASH_ALL__) || defined(__NORFLASH_P25Q64L__)
    &p25q64l_cfg,
#endif
#if defined(__NORFLASH_ALL__) || defined(__NORFLASH_P25Q32L__)
    &p25q32l_cfg,
#endif
#if defined(__NORFLASH_ALL__) || defined(__NORFLASH_P25Q16L__)
    &p25q16l_cfg,
#endif
#if defined(__NORFLASH_ALL__) || defined(__NORFLASH_P25Q80H__)
    &p25q80h_cfg,
#endif
#if defined(__NORFLASH_ALL__) || defined(__NORFLASH_P25Q21H__)
    &p25q21h_cfg,
#endif
#if defined(__NORFLASH_ALL__) || defined(__NORFLASH_P25Q40H__)
    &p25q40h_cfg,
#endif

    // ----------------------
    // Xinxin
    // ----------------------
#if defined(__NORFLASH_XM25QH16C__)
    &xm25qh16c_cfg,
#endif
#if defined(__NORFLASH_ALL__) || defined(__NORFLASH_XM25QH80B__)
    &xm25qh80b_cfg,
#endif

    // ----------------------
    // XTS
    // ----------------------
#if defined(__NORFLASH_XT25Q08B__)
    &xt25q08b_cfg,
#endif

    // ----------------------
    // EON
    // ----------------------
#if defined(__NORFLASH_EN25S80B__)
    &en25s80b_cfg,
#endif

};

// Sample delay will be larger if:
// 1) flash speed is higher (major impact)
// 2) vcore voltage is lower (secondary major impact)
// 3) flash voltage is lower (minor impact)

// Sample delay unit:
// V1: 1/2 source_clk cycle when <= 2, 1 source_clk cycle when >= 2
// V2: 1/2 source_clk cycle when <= 4, 1 source_clk cycle when >= 4

// Flash clock low to output valid delay:
// T_clqv: 7 ns

// Flash IO latency:
// BEST1000/3001/1400: 4 ns
// BEST2000: 5 ns
// BEST2300: 2 ns

// Flash output time: T_clqv + T_io_latency
// Falling edge sample time: one spi_clk cycle (should > flash output time)

#ifdef CHIP_BEST2300
#define FALLING_EDGE_SAMPLE_ADJ_FREQ            (110 * 1000 * 1000) // about 9 ns
#else
#define FALLING_EDGE_SAMPLE_ADJ_FREQ            (77 * 1000 * 1000) // about 13 ns
#endif

#if (CHIP_FLASH_CTRL_VER <= 1)
#define SAM_EDGE_FALLING                        (1 << 4)
#define SAM_NEG_PHASE                           (1 << 5)
#define SAMDLY_MASK                             (0xF << 0)

#ifdef SAMP_DELAY_PRIO_FALLING_EDGE
#define DIV2_SAMP_DELAY_FALLING_EDGE_IDX        1
#define DIVN_SAMP_DELAY_FALLING_EDGE_IDX        2
// Sample delays: 1, 1.5, 2, 3
static const uint8_t samdly_list_divn[] = { 1, SAM_NEG_PHASE | SAM_EDGE_FALLING | 2, 2, 3, };
#else
// Sample delays: 0, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 7
static const uint8_t samdly_list_divn[] = { /*0,*/ SAM_EDGE_FALLING | 1, 1, SAM_NEG_PHASE | SAM_EDGE_FALLING | 2, 2, 3, 4, /*5, 6, 7,*/ };
#endif
#else
#ifdef SAMP_DELAY_PRIO_FALLING_EDGE
#ifdef CHIP_BEST1402
#define DIV1_SAMP_DELAY_FALLING_EDGE_IDX        2
#else
#define DIV1_SAMP_DELAY_FALLING_EDGE_IDX        1
#endif
static const uint8_t samdly_list_div1[] = { 0, 1, 2, 3, };
#define DIV2_SAMP_DELAY_FALLING_EDGE_IDX        1
#define DIVN_SAMP_DELAY_FALLING_EDGE_IDX        3
static const uint8_t samdly_list_divn[] = { 2, 3, 4, 5, 6, 7, 8, 9, };
#else
static const uint8_t samdly_list_divn[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, };
#endif
#endif

static uint8_t flash_idx;
static uint8_t div_read;
static uint8_t div_std_read;
static uint8_t div_others;
static uint32_t norflash_op_mode = 0;
static bool falling_edge_adj;
static uint8_t sample_delay_index;

#ifdef FLASH_SUSPEND
static uint32_t check_irq[(USER_IRQn_QTY + 31) / 32];
static bool specific_irq_check;
#endif

#ifdef FLASH_CALIB_DEBUG
static uint32_t norflash_source_clk;
static uint32_t norflash_speed;
static uint8_t calib_matched_idx[DRV_NORFLASH_CALIB_QTY];
static uint8_t calib_matched_cnt[DRV_NORFLASH_CALIB_QTY];
static uint8_t calib_final_idx[DRV_NORFLASH_CALIB_QTY];
#endif

static void norflash_delay(uint32_t us)
{
#ifdef CHIP_BEST1000
    hal_sys_timer_delay(US_TO_TICKS(us));
#else
    hal_sys_timer_delay_us(us);
#endif
}

#ifdef FLASH_HPM
static int norflash_set_hpm(uint8_t on)
{
    if (on) {
        norflaship_cmd_addr(GD25Q32C_CMD_HIGH_PERFORMANCE, 0);
    } else {
        norflaship_cmd_addr(GD25Q32C_CMD_RELEASE_FROM_DP, 0);
    }
    norflaship_busy_wait();

    return 0;
}
#endif

#ifdef FLASH_SUSPEND
static int norflash_suspend(void)
{
    norflaship_clear_fifos();
    norflaship_ext_tx_cmd(GD25Q32C_CMD_PROGRAM_ERASE_SUSPEND, 0);
    // Suspend time: 20~30 us
    norflash_delay(40);
    return 0;
}

static int norflash_resume(void)
{
    norflaship_clear_fifos();
    norflaship_ext_tx_cmd(GD25Q32C_CMD_PROGRAM_ERASE_RESUME, 0);
    if (flash_list[flash_idx]->id[0] == NORFLASH_PUYA_ID_PREFIX) {
        // PUYA flash requires the mean interval of resume to suspend >= 250us
        norflash_delay(250);
    } else {
        // At least resume the work for 100 us to avoid always staying in suspended state
        norflash_delay(100);
    }
    return 0;
}
#endif

uint8_t norflash_read_status_s0_s7(void)
{
    uint8_t val;
    norflash_read_reg(GD25Q32C_CMD_READ_STATUS_S0_S7, &val, 1);
    return val;
}

uint8_t norflash_read_status_s8_s15(void)
{
    uint8_t val;
    norflash_read_reg(GD25Q32C_CMD_READ_STATUS_S8_S15, &val, 1);
    return val;
}

static int norflash_status_WEL(void)
{
    uint32_t status;
    status = norflash_read_status_s0_s7();
    return !!(status & GD25Q32C_WEL_BIT_MASK);
}

static int norflash_status_WIP(void)
{
    uint32_t status;
    status = norflash_read_status_s0_s7();
    return !!(status & GD25Q32C_WIP_BIT_MASK);
}

void norflash_status_WEL_0_wait(void)
{
    while (norflash_status_WEL() == 0 && TASK_SCHEDULE);
}

#ifdef FLASH_SUSPEND
int norflash_suspend_check_irq(uint32_t irq_num)
{
    uint32_t idx;
    uint32_t offset;

    idx = irq_num / 32;
    offset = irq_num % 32;

    if (idx >= ARRAY_SIZE(check_irq)) {
        return 1;
    }

    check_irq[idx] |= (1 << offset);
    specific_irq_check = true;

    return 0;
}

static int norflash_system_active(void)
{
    if (specific_irq_check) {
        return hal_sleep_specific_irq_pending(check_irq, ARRAY_SIZE(check_irq));
    } else {
        return hal_sleep_irq_pending();
    }
}
#endif

enum HAL_NORFLASH_RET_T norflash_status_WIP_1_wait(int suspend)
{
    while (norflash_status_WIP() && TASK_SCHEDULE) {
#ifdef FLASH_SUSPEND
        if (suspend && norflash_system_active()) {
            norflash_suspend();
            return HAL_NORFLASH_SUSPENDED;
        }
#endif
    }

    return HAL_NORFLASH_OK;
}

#ifdef FLASH_BURST_WRAP
static int norflash_set_burst_wrap(uint32_t len)
{
    uint8_t val;

    if (len == 64) {
        val = (1 << 6) | (1 << 5);
    } else if (len == 32) {
        val = (1 << 6);
    } else if (len == 16) {
        val = (1 << 5);
    } else if (len == 8) {
        val = 0;
    } else if (len == 0) {
        // Disable wrap around
        val = (1 << 4);
    } else {
        return 1;
    }

    norflaship_clear_txfifo();
    norflaship_write_txfifo(&val, 1);
    norflaship_cmd_addr(GD25Q32C_CMD_SET_BURST_WRAP, 0);
    norflash_status_WIP_1_wait(0);

    return 0;
}
#endif

static int norflash_set_continuous_read(uint8_t on)
{
    uint8_t cmd;

    if (on) {
        norflaship_continuous_read_on();

        norflaship_continuous_read_mode_bit(flash_list[flash_idx]->crm_en_bits);

        // Continuous Read Mode takes effect after the first read
    } else {
        norflaship_continuous_read_off();

        norflaship_continuous_read_mode_bit(flash_list[flash_idx]->crm_dis_bits);

        if (norflash_op_mode & HAL_NORFLASH_OP_MODE_QUAD_IO) {
            cmd = GD25Q32C_CMD_FAST_QUAD_IO_READ;
        } else {
            norflaship_quad_mode(0);

            if (norflash_op_mode & HAL_NORFLASH_OP_MODE_DUAL_IO) {
                cmd = GD25Q32C_CMD_FAST_DUAL_IO_READ;
            } else {
                norflaship_dual_mode(0);

                // Command 0x03 and address 0xFFFFFE will make M4=1 (mode bit 4)
                // at both qual and dual continuous read modes, which will disable it.
                cmd = GD25Q32C_CMD_STANDARD_READ;
            }
        }

        norflaship_clear_rxfifo();
        norflaship_busy_wait();
        norflaship_blksize(2);
        if (cmd == GD25Q32C_CMD_STANDARD_READ) {
            if (div_std_read) {
                norflaship_div(div_std_read);
            }
        }
        norflaship_cmd_addr(cmd, 0xFFFFFE);
        // Now Continuous Read Mode has been disabled
        if (cmd == GD25Q32C_CMD_STANDARD_READ) {
            if (div_others) {
                norflaship_div(div_others);
            }
        }
        norflaship_clear_rxfifo();
    }

    norflaship_busy_wait();

    return 0;
}

static int norflash_set_quad(uint8_t on)
{
    if (flash_list[flash_idx]->write_status == NULL) {
        return -1;
    }

    if (on) {
        flash_list[flash_idx]->write_status(DRV_NORFLASH_W_STATUS_QE, 1);
    } else {
        flash_list[flash_idx]->write_status(DRV_NORFLASH_W_STATUS_QE, 0);
    }
    return 0;
}

static int norflash_set_quad_io_mode(uint8_t on)
{
    norflash_set_quad(on);
    if (on) {
        norflaship_quad_mode(1);
    } else {
        norflaship_quad_mode(0);
    }
    norflaship_busy_wait();
    return 0;
}

static int norflash_set_quad_output_mode(uint8_t on)
{
    norflash_set_quad(on);
    if (on) {
        norflaship_rdcmd(GD25Q32C_CMD_FAST_QUAD_OUTPUT_READ);
    } else {
        norflaship_rdcmd(GD25Q32C_CMD_STANDARD_READ);
    }
    norflaship_busy_wait();
    return 0;
}

static uint8_t norflash_set_dual_io_mode(uint8_t on)
{
    if (on) {
        norflaship_dual_mode(1);
    } else {
        norflaship_dual_mode(0);
    }
    norflaship_busy_wait();

    return 0;
}

static uint8_t norflash_set_dual_output_mode(uint8_t on)
{
    if (on) {
        norflaship_rdcmd(GD25Q32C_CMD_FAST_DUAL_OUTPUT_READ);
    } else {
        norflaship_rdcmd(GD25Q32C_CMD_STANDARD_READ);
    }
    norflaship_busy_wait();
    return 0;
}

static uint8_t norflash_set_fast_mode(uint8_t on)
{
    if (on) {
        norflaship_rdcmd(GD25Q32C_CMD_STANDARD_FAST_READ);
    } else {
        norflaship_rdcmd(GD25Q32C_CMD_STANDARD_READ);
    }
    norflaship_busy_wait();
    return 0;
}

static uint8_t norflash_set_stand_mode(void)
{
    norflaship_rdcmd(GD25Q32C_CMD_STANDARD_READ);
    norflaship_busy_wait();
    return 0;
}

uint32_t norflash_get_supported_mode(void)
{
     return flash_list[flash_idx]->mode;
}

uint32_t norflash_get_current_mode(void)
{
     return norflash_op_mode;
}

union DRV_NORFLASH_SEC_REG_CFG_T norflash_get_security_register_config(void)
{
    return flash_list[flash_idx]->sec_reg_cfg;
}

uint32_t norflash_get_block_protect_mask(void)
{
    return flash_list[flash_idx]->block_protect_mask;
}

void norflash_reset(void)
{
    norflaship_clear_fifos();

    // Disable continuous read mode
    norflaship_continuous_read_off();
    norflaship_busy_wait();
    norflaship_blksize(2);
    // Command 0x03 and address 0xFFFFFE will make M4=1 (mode bit 4)
    // at both qual and dual continuous read modes, which will disable it.
    norflaship_cmd_addr(GD25Q32C_CMD_STANDARD_READ, 0xFFFFFE);

    // Release from deep power-down
    norflaship_cmd_addr(GD25Q32C_CMD_RELEASE_FROM_DP, 0);
    // Wait 20us for flash to finish
    norflash_delay(40);

    // Software reset
    norflaship_ext_tx_cmd(GD25Q32C_CMD_ENABLE_RESET, 0);
    norflaship_ext_tx_cmd(GD25Q32C_CMD_RESET, 0);
    // Reset recovery time: 20~30 us
    norflash_delay(50);

    norflaship_cmd_done();

    // Reset cfg
    flash_idx = 0;
    div_read = 0;
    div_std_read = 0;
    div_others = 0;
    norflash_op_mode = 0;
    falling_edge_adj = false;
}

int norflash_get_size(uint32_t *total_size, uint32_t *block_size, uint32_t *sector_size, uint32_t *page_size)
{
    if (total_size) {
        *total_size  = flash_list[flash_idx]->total_size;
    }
    if (block_size) {
        *block_size  = flash_list[flash_idx]->block_size;
    }
    if (sector_size) {
        *sector_size = flash_list[flash_idx]->sector_size;
    }
    if (page_size) {
        *page_size   = flash_list[flash_idx]->page_size;
    }

    return 0;
}

static void norflash_set_cfg_div(void)
{
    if (div_others) {
        norflaship_div(div_others);
    }
}

static void norflash_set_read_div(void)
{
    if (norflash_op_mode & HAL_NORFLASH_OP_MODE_STAND_SPI) {
        if (div_std_read) {
            norflaship_div(div_std_read);
        }
    } else {
        if (div_read) {
            norflaship_div(div_read);
        }
    }
}

int norflash_set_mode(uint32_t op)
{
    uint32_t read_mode = 0;
    uint32_t ext_mode = 0;
    uint32_t program_mode = 0;
    uint32_t self_mode;
    uint32_t mode;

    self_mode = norflash_get_supported_mode();
    mode = (self_mode & op);

    if (mode & HAL_NORFLASH_OP_MODE_QUAD_IO) {
        read_mode = HAL_NORFLASH_OP_MODE_QUAD_IO;
    } else if (mode & HAL_NORFLASH_OP_MODE_QUAD_OUTPUT) {
        read_mode = HAL_NORFLASH_OP_MODE_QUAD_OUTPUT;
    } else if (mode & HAL_NORFLASH_OP_MODE_DUAL_IO) {
        read_mode = HAL_NORFLASH_OP_MODE_DUAL_IO;
    } else if (mode & HAL_NORFLASH_OP_MODE_DUAL_OUTPUT) {
        read_mode = HAL_NORFLASH_OP_MODE_DUAL_OUTPUT;
    } else if (mode & HAL_NORFLASH_OP_MODE_FAST_SPI) {
        read_mode = HAL_NORFLASH_OP_MODE_FAST_SPI;
    } else if(mode & HAL_NORFLASH_OP_MODE_STAND_SPI) {
        read_mode = HAL_NORFLASH_OP_MODE_STAND_SPI;
    } else {
        // Op error
        return  1;
    }

    if (mode & HAL_NORFLASH_OP_MODE_QUAD_PAGE_PROGRAM) {
        program_mode = HAL_NORFLASH_OP_MODE_QUAD_PAGE_PROGRAM;
    } else if (mode & HAL_NORFLASH_OP_MODE_DUAL_PAGE_PROGRAM) {
        program_mode = HAL_NORFLASH_OP_MODE_DUAL_PAGE_PROGRAM;
    } else if (mode & HAL_NORFLASH_OP_MODE_PAGE_PROGRAM) {
        program_mode = HAL_NORFLASH_OP_MODE_PAGE_PROGRAM;
    } else {
        // Op error
        return 1;
    }

#ifdef FLASH_BURST_WRAP
    if (mode & HAL_NORFLASH_OP_MODE_READ_WRAP) {
        ext_mode |= HAL_NORFLASH_OP_MODE_READ_WRAP;
    }
#endif

#ifdef FLASH_HPM
    if (mode & HAL_NORFLASH_OP_MODE_HIGH_PERFORMANCE) {
        ext_mode |= HAL_NORFLASH_OP_MODE_HIGH_PERFORMANCE;
    }
#endif

    if (mode & (HAL_NORFLASH_OP_MODE_QUAD_IO | HAL_NORFLASH_OP_MODE_DUAL_IO)) {
        if (mode & HAL_NORFLASH_OP_MODE_CONTINUOUS_READ) {
            ext_mode |= HAL_NORFLASH_OP_MODE_CONTINUOUS_READ;
        }
    }

    mode = (read_mode | ext_mode | program_mode);

    if (norflash_op_mode != mode) {
        norflash_op_mode = mode;

        // Continuous read off if flash supported
        if ((self_mode & (HAL_NORFLASH_OP_MODE_QUAD_IO | HAL_NORFLASH_OP_MODE_DUAL_IO)) &&
                (self_mode & HAL_NORFLASH_OP_MODE_CONTINUOUS_READ)) {
            norflash_set_continuous_read(0);
        }

        norflaship_quad_mode(0);

        norflaship_dual_mode(0);

#ifdef FLASH_HPM
        if (mode & HAL_NORFLASH_OP_MODE_HIGH_PERFORMANCE) {
            // High performance mode on
            norflash_set_hpm(1);
        }
#endif

        if (mode & HAL_NORFLASH_OP_MODE_QUAD_IO) {
            // Quad io mode
            norflash_set_quad_io_mode(1);
        } else if (mode & HAL_NORFLASH_OP_MODE_QUAD_OUTPUT) {
            // Quad output mode
            norflash_set_quad_output_mode(1);
        } else if (mode & HAL_NORFLASH_OP_MODE_DUAL_IO) {
            // Dual io mode
            norflash_set_dual_io_mode(1);
        } else if (mode & HAL_NORFLASH_OP_MODE_DUAL_OUTPUT) {
            // Dual output mode
            norflash_set_dual_output_mode(1);
        } else if (mode & HAL_NORFLASH_OP_MODE_FAST_SPI) {
            // Fast mode
            norflash_set_fast_mode(1);
        } else if (mode & HAL_NORFLASH_OP_MODE_STAND_SPI) {
            // Standard spi mode
            norflash_set_stand_mode();
        }

#ifdef FLASH_BURST_WRAP
        if (mode & HAL_NORFLASH_OP_MODE_READ_WRAP) {
            norflash_set_burst_wrap(32);
            hal_cache_wrap_enable(HAL_CACHE_ID_I_CACHE);
            hal_cache_wrap_enable(HAL_CACHE_ID_D_CACHE);
        }
#endif

#ifdef FLASH_HPM
        if ((self_mode & HAL_NORFLASH_OP_MODE_HIGH_PERFORMANCE) &&
                (mode & HAL_NORFLASH_OP_MODE_HIGH_PERFORMANCE) == 0) {
            // High performance mode off
            norflash_set_hpm(0);
        }
#endif

        if (mode & HAL_NORFLASH_OP_MODE_CONTINUOUS_READ) {
            // Continuous read on
            norflash_set_continuous_read(1);
        }

        norflaship_cmd_done();
    }

    norflash_set_read_div();

    return 0;
}

int norflash_pre_operation(void)
{
    norflash_set_cfg_div();

    if (norflash_op_mode & HAL_NORFLASH_OP_MODE_CONTINUOUS_READ) {
        norflash_set_continuous_read(0);
    }

    return 0;
}

int norflash_post_operation(void)
{
    if (norflash_op_mode & HAL_NORFLASH_OP_MODE_CONTINUOUS_READ) {
        norflash_set_continuous_read(1);
    }

    norflaship_cmd_done();

    norflash_set_read_div();

    return 0;
}

int norflash_read_reg(uint8_t cmd, uint8_t *val, uint32_t len)
{
    int i;

    norflaship_clear_fifos();
#ifdef TRY_EMBEDDED_CMD
    if ((cmd == GD25Q32C_CMD_READ_STATUS_S0_S7 || cmd == GD25Q32C_CMD_READ_STATUS_S8_S15) && len == 1) {
        norflaship_cmd_addr(cmd, 0);
    } else {
        if (cmd == GD25Q32C_CMD_ID) {
            norflaship_blksize(len);
            norflaship_cmd_addr(cmd, 0);
        } else {
            norflaship_ext_rx_cmd(cmd, 0, len);
        }
    }
#else
    norflaship_ext_rx_cmd(cmd, 0, len);
#endif
    norflaship_rxfifo_count_wait(len);
    for (i = 0; i < len; i++) {
        val[i] = norflaship_read_rxfifo();
    }

    return 0;
}

int norflash_read_reg_ex(uint8_t cmd, uint8_t *param, uint32_t param_len, uint8_t *val, uint32_t len)
{
    int i;

    norflaship_clear_fifos();
    if (param && param_len > 0) {
        norflaship_write_txfifo(param, param_len);
    } else {
        param_len = 0;
    }
    norflaship_ext_rx_cmd(cmd, param_len, len);
    for (i = 0; i < len; i++) {
        norflaship_rxfifo_empty_wait();
        val[i] = norflaship_read_rxfifo();
    }

    return 0;
}

int norflash_write_reg(uint8_t cmd, const uint8_t *val, uint32_t len)
{
    norflaship_cmd_addr(GD25Q32C_CMD_WRITE_ENABLE, 0);
    norflash_status_WIP_1_wait(0);
    norflash_status_WEL_0_wait();

    norflaship_clear_txfifo();
    norflaship_write_txfifo(val, len);

#ifdef TRY_EMBEDDED_CMD
    if (cmd == GD25Q32C_CMD_WRITE_STATUS_S0_S7) {
        norflaship_cmd_addr(GD25Q32C_CMD_WRITE_STATUS_S0_S7, 0);
    } else {
        norflaship_ext_tx_cmd(cmd, len);
    }
#else
    norflaship_ext_tx_cmd(cmd, len);
#endif

    norflash_status_WIP_1_wait(0);

    return 0;
}

int norflash_get_id(uint8_t *value, uint32_t len)
{
    norflash_pre_operation();

    if (len > NORFLASH_ID_LEN) {
        len = NORFLASH_ID_LEN;
    }
    norflash_read_reg(GD25Q32C_CMD_ID, value, len);

    norflash_post_operation();

    return 0;
}

int norflash_get_unique_id(uint8_t *value, uint32_t len)
{
    uint32_t param;
    uint8_t cmd;

    norflash_pre_operation();

    if (flash_list[flash_idx]->id[0] == NORFLASH_XTS_ID_PREFIX) {
        if (len > XTS_UNIQUE_ID_LEN) {
            len = XTS_UNIQUE_ID_LEN;
        }
        param = XTS_UNIQUE_ID_PARAM;
        cmd = XTS_UNIQUE_ID_CMD;
    } else {
        if (len > NORFLASH_UNIQUE_ID_LEN) {
            len = NORFLASH_UNIQUE_ID_LEN;
        }
        param = 0;
        cmd = GD25Q32C_CMD_UNIQUE_ID;
    }
    norflash_read_reg_ex(cmd, (uint8_t *)&param, sizeof(param), value, len);

    norflash_post_operation();

    return 0;
}

static void norflash_get_samdly_list(uint32_t div, const uint8_t **samdly_list_p, uint32_t *size_p)
{
    const uint8_t *samdly_list = NULL;
    uint32_t size = 0;

#if (CHIP_FLASH_CTRL_VER <= 1)
    if (div >= 2) {
        samdly_list = samdly_list_divn;
        size = ARRAY_SIZE(samdly_list_divn);
    }
#else
    if (div >= 1) {
        if (div == 1) {
            samdly_list = samdly_list_div1;
            size = ARRAY_SIZE(samdly_list_div1);
        } else {
            samdly_list = samdly_list_divn;
            size = ARRAY_SIZE(samdly_list_divn);
        }
    }
#endif

    if (samdly_list_p) {
        *samdly_list_p = samdly_list;
    }
    if (size_p) {
        *size_p = size;
    }
}

void norflash_set_sample_delay_index(uint32_t index)
{
    const uint8_t *samdly_list;
    uint32_t size;
    uint32_t div;

    sample_delay_index = index;

    div = norflaship_get_div();

    norflash_get_samdly_list(div, &samdly_list, &size);

    if (index < size) {
#if (CHIP_FLASH_CTRL_VER <= 1)
        norflaship_pos_neg(samdly_list[index] & SAM_EDGE_FALLING);
        norflaship_neg_phase(samdly_list[index] & SAM_NEG_PHASE);
        norflaship_samdly(samdly_list[index] & SAMDLY_MASK);
#else
        norflaship_samdly(samdly_list[index]);
#endif
    }
}

uint32_t norflash_get_sample_delay_index(void)
{
    return sample_delay_index;
}

static bool norflash_calib_flash_id_valid(void)
{
    uint8_t id[HAL_NORFLASH_DEVICE_ID_LEN];
    const uint8_t *cmp_id;

    norflash_get_id(id, sizeof(id));
    cmp_id = flash_list[flash_idx]->id;

    if (id[0] == cmp_id[0] && id[1] == cmp_id[1] && id[2] == cmp_id[2]) {
        return true;
    }
    return false;
}

static bool norflash_calib_magic_word_valid(void)
{
    uint32_t magic;

#if (CHIP_FLASH_CTRL_VER <= 1)
    norflash_read(FLASH_NC_BASE, NULL, 1);
#endif
    norflaship_clear_rxfifo();
#if (FLASH_NC_BASE == FLASH_BASE)
    hal_cache_invalidate(HAL_CACHE_ID_D_CACHE, FLASH_BASE, sizeof(magic));
    magic = *(volatile uint32_t *)FLASH_BASE;
#else
    magic = *(volatile uint32_t *)FLASH_NC_BASE;
#endif

    if (magic == BOOT_MAGIC_NUMBER) {
        return true;
    }
    return false;
}

int norflash_sample_delay_calib(enum DRV_NORFLASH_CALIB_T type)
{
    int i;
    uint32_t matched_cnt = 0;
    uint32_t matched_idx = 0;
    uint32_t div;
    uint32_t size;
    bool valid;

    if (type >= DRV_NORFLASH_CALIB_QTY) {
        return 1;
    }
#if defined(ROM_BUILD) || defined(PROGRAMMER)
    if (type != DRV_NORFLASH_CALIB_FLASH_ID) {
        return 0;
    }
#endif

    div = norflaship_get_div();

    if (div == 0) {
        return -1;
#if (CHIP_FLASH_CTRL_VER <= 1)
    } else if (div == 1) {
        return -2;
#endif
    }

    norflash_get_samdly_list(div, NULL, &size);

    for (i = 0; i < size; i++) {
        norflaship_busy_wait();

        norflash_set_sample_delay_index(i);

        if (type == DRV_NORFLASH_CALIB_FLASH_ID) {
            valid = norflash_calib_flash_id_valid();
        } else {
            valid = norflash_calib_magic_word_valid();
        }

        if (valid) {
            if (matched_cnt == 0) {
                matched_idx = i;
            }
            matched_cnt++;
        } else if (matched_cnt) {
            break;
        }
    }

#ifdef FLASH_CALIB_DEBUG
    calib_matched_idx[type] = matched_idx;
    calib_matched_cnt[type] = matched_cnt;
#endif

    if (matched_cnt) {
#ifdef SAMP_DELAY_PRIO_FALLING_EDGE
        if (matched_cnt == 2) {
            uint32_t falling_edge_idx;

            if (0) {
#if (CHIP_FLASH_CTRL_VER >= 2)
            } else if (div == 1) {
                falling_edge_idx = DIV1_SAMP_DELAY_FALLING_EDGE_IDX;
#endif
            } else if (div == 2) {
                falling_edge_idx = DIV2_SAMP_DELAY_FALLING_EDGE_IDX;
                if (falling_edge_adj) {
                    falling_edge_idx++;
                }
            } else {
                falling_edge_idx = DIVN_SAMP_DELAY_FALLING_EDGE_IDX;
            }
            if (matched_idx <= falling_edge_idx &&
                    falling_edge_idx < matched_idx + matched_cnt) {
                matched_idx = falling_edge_idx;
                matched_cnt = 1;
            }
        }
#endif
        matched_idx += matched_cnt / 2;
        norflash_set_sample_delay_index(matched_idx);

#ifdef FLASH_CALIB_DEBUG
        calib_final_idx[type] = matched_idx;
#endif

        return 0;
    }

#ifdef FLASH_CALIB_DEBUG
    calib_final_idx[type] = -1;
#endif

    return 1;
}

void norflash_show_calib_result(void)
{
#ifdef FLASH_CALIB_DEBUG
    union DRV_NORFLASH_SPEED_RATIO_T ratio;
    uint32_t div;
    uint32_t size;
    const uint8_t *list;
    int i;

    TRACE(0, "FLASH_CALIB_RESULT:");
    TRACE(0, "<FREQ>\nsource_clk=%u speed=%u flash_max=%u", norflash_source_clk, norflash_speed, flash_list[flash_idx]->max_speed);
    ratio = flash_list[flash_idx]->speed_ratio;
    TRACE(0, "<RATIO>\nstd_read=%u/8 others=%u/8", (ratio.s.std_read + 1), (ratio.s.others + 1));

    div = norflaship_get_div();
    TRACE(0, "<DIV>\ndiv=%u", div);
    norflash_get_samdly_list(div, &list, &size);
    TRACE(0, "<SAMDLY LIST>");
    if (list == NULL || size == 0) {
        TRACE(0, "NONE");
    } else {
        DUMP8("%02X ", list, size);
    }
    TRACE(0, "<CALIB RESULT>");
    for (i = 0; i < DRV_NORFLASH_CALIB_QTY; i++) {
        TRACE(0, "type=%d idx=%02u cnt=%02u final=%02u",
            i, calib_matched_idx[i], calib_matched_cnt[i], calib_final_idx[i]);
    }
    TRACE(0, "\t");
#endif
}

int norflash_init_sample_delay_by_div(uint32_t div)
{
    if (div == 0) {
        return -1;
    } if (div == 1) {
#if (CHIP_FLASH_CTRL_VER <= 1)
        return -2;
#else
        norflaship_samdly(1);
#endif
    } else if (div == 2 && !falling_edge_adj) {
        // Set sample delay to clock falling edge
#if (CHIP_FLASH_CTRL_VER <= 1)
        norflaship_pos_neg(1);
        norflaship_neg_phase(1);
        norflaship_samdly(2);
#else
        norflaship_samdly(3);
#endif
    } else {
        // Set sample delay to nearest to but not later than clock falling edge
#if (CHIP_FLASH_CTRL_VER <= 1)
        norflaship_pos_neg(0);
        norflaship_neg_phase(0);
        norflaship_samdly(2);
#else
        norflaship_samdly(4);
#endif
    }

    return 0;
}

int norflash_init_div(const struct HAL_NORFLASH_CONFIG_T *cfg)
{
    uint32_t max_speed;
    uint32_t std_read_speed;
    uint32_t others_speed;
    union DRV_NORFLASH_SPEED_RATIO_T ratio;
    uint32_t div;

#ifdef FLASH_CALIB_DEBUG
    norflash_source_clk = cfg->source_clk;
    norflash_speed = cfg->speed;
#endif

    max_speed = flash_list[flash_idx]->max_speed;
    ratio = flash_list[flash_idx]->speed_ratio;

    if (max_speed == 0) {
        max_speed = NORFLASH_DEFAULT_MAX_SPEED;
    }
    if (max_speed > cfg->speed) {
        max_speed = cfg->speed;
    }
    if (max_speed > cfg->source_clk) {
        max_speed = cfg->source_clk;
    }
    std_read_speed = max_speed * (1 + ratio.s.std_read) / NORFLASH_SPEED_RATIO_DENOMINATOR;
    others_speed = max_speed * (1 + ratio.s.others) / NORFLASH_SPEED_RATIO_DENOMINATOR;

    div = (cfg->source_clk + max_speed - 1) / max_speed;
    div_read = (div < NORFLASH_MAX_DIV) ? div : NORFLASH_MAX_DIV;
    div = (cfg->source_clk + std_read_speed - 1) / std_read_speed;
    div_std_read = (div < NORFLASH_MAX_DIV) ? div : NORFLASH_MAX_DIV;
    div = (cfg->source_clk + others_speed - 1) / others_speed;
    div_others = (div < NORFLASH_MAX_DIV) ? div : NORFLASH_MAX_DIV;

    if (div_read == 2 && max_speed >= FALLING_EDGE_SAMPLE_ADJ_FREQ) {
        falling_edge_adj = true;
    } else {
        falling_edge_adj = false;
    }

    if (div_read && div_std_read && div_others) {
#if (CHIP_FLASH_CTRL_VER <= 1)
        if (div_read == 1) {
            return -1;
        }
#else
        if (div_read == 1 && max_speed > NORFLASH_DIV1_MAX_SPEED) {
            return -1;
        }
#endif
        // Init sample delay according to div_read
        norflash_init_sample_delay_by_div(div_read);
        // Still in command mode
        norflaship_div(div_others);
        return 0;
    }

    return 1;
}

int norflash_match_chip(const uint8_t *id, uint32_t len)
{
    const uint8_t *cmp_id;

    if (len == NORFLASH_ID_LEN) {
        for (flash_idx = 0; flash_idx < ARRAY_SIZE(flash_list); flash_idx++) {
            cmp_id = flash_list[flash_idx]->id;
            if (id[0] == cmp_id[0] && id[1] == cmp_id[1] && id[2] == cmp_id[2]) {
                return true;
            }
        }
    }

    return false;
}

#ifdef PUYA_FLASH_ERASE_PAGE_ENABLE
static void norflaship_ext_cmd_addr(uint8_t cmd, uint32_t addr)
{
    uint8_t buff[3];

    buff[2] = (uint8_t)(addr & 0xff);
    buff[1] = (uint8_t)((addr >> 8) & 0xff);
    buff[0] = (uint8_t)((addr >> 16) & 0xff);

    norflaship_clear_txfifo();
    norflaship_write_txfifo(buff, 3);
    norflaship_ext_tx_cmd(cmd, 3);
}
#endif

enum HAL_NORFLASH_RET_T norflash_erase(uint32_t start_address, enum DRV_NORFLASH_ERASE_T type, int suspend)
{
    enum HAL_NORFLASH_RET_T ret;

    if (flash_list[flash_idx]->mode & HAL_NORFLASH_OP_MODE_ERASE_IN_STD) {
        if(norflash_op_mode & HAL_NORFLASH_OP_MODE_QUAD_IO) {
            norflash_set_quad_io_mode(0);
        } else if(norflash_op_mode & HAL_NORFLASH_OP_MODE_QUAD_OUTPUT) {
            norflash_set_quad_output_mode(0);
        }
    }

    norflaship_cmd_addr(GD25Q32C_CMD_WRITE_ENABLE, start_address);
    // Need 1us. Or norflash_status_WEL_0_wait(), which needs 6us.
    switch(type) {
#ifdef PUYA_FLASH_ERASE_PAGE_ENABLE
        case DRV_NORFLASH_ERASE_PAGE:
            norflaship_ext_cmd_addr(PUYA_FLASH_CMD_PAGE_ERASE, start_address);
            break;
#endif
        case DRV_NORFLASH_ERASE_SECTOR:
            norflaship_cmd_addr(GD25Q32C_CMD_SECTOR_ERASE, start_address);
            break;
        case DRV_NORFLASH_ERASE_BLOCK:
            norflaship_cmd_addr(GD25Q32C_CMD_BLOCK_ERASE, start_address);
            break;
        case DRV_NORFLASH_ERASE_CHIP:
            norflaship_cmd_addr(GD25Q32C_CMD_CHIP_ERASE, start_address);
            break;
    }

    norflaship_busy_wait();

#ifdef FLASH_SUSPEND
    // PUYA flash requires the first delay of erase >= 400us
    if (flash_list[flash_idx]->id[0] == NORFLASH_PUYA_ID_PREFIX) {
        norflash_delay(400);
    }
#endif

    ret = norflash_status_WIP_1_wait(suspend);

    if (flash_list[flash_idx]->mode & HAL_NORFLASH_OP_MODE_ERASE_IN_STD) {
        if(norflash_op_mode & HAL_NORFLASH_OP_MODE_QUAD_IO) {
            norflash_set_quad_io_mode(1);
        } else if(norflash_op_mode & HAL_NORFLASH_OP_MODE_QUAD_OUTPUT) {
            norflash_set_quad_output_mode(1);
        }
    }

    norflaship_cmd_done();

    return ret;
}

enum HAL_NORFLASH_RET_T norflash_write(uint32_t start_address, const uint8_t *buffer, uint32_t len, int suspend)
{
    enum HAL_NORFLASH_RET_T ret;
    uint32_t POSSIBLY_UNUSED remains;

    if (len > GD25Q32C_PAGE_SIZE) {
        return HAL_NORFLASH_ERR;
    }

    norflaship_clear_txfifo();

#if (CHIP_FLASH_CTRL_VER >= 2)
    norflaship_blksize(len);
#endif

    remains = norflaship_write_txfifo(buffer, len);

    norflaship_cmd_addr(GD25Q32C_CMD_WRITE_ENABLE, start_address);
    if (norflash_op_mode & HAL_NORFLASH_OP_MODE_QUAD_PAGE_PROGRAM) {
        norflaship_cmd_addr(GD25Q32C_CMD_QUAD_PAGE_PROGRAM, start_address);
    } else if (norflash_op_mode & HAL_NORFLASH_OP_MODE_DUAL_PAGE_PROGRAM) {
        norflaship_cmd_addr(GD25Q32C_CMD_DUAL_PAGE_PROGRAM, start_address);
    } else {
        norflaship_cmd_addr(GD25Q32C_CMD_PAGE_PROGRAM, start_address);
    }

#if (CHIP_FLASH_CTRL_VER >= 2)
    while (remains > 0) {
        buffer += len - remains;
        len = remains;
        remains = norflaship_write_txfifo(buffer, len);
    }
#endif

    norflaship_busy_wait();

#ifdef FLASH_SUSPEND
    // PUYA flash requires the first delay of byte program >= 450us
    if (flash_list[flash_idx]->id[0] == NORFLASH_PUYA_ID_PREFIX && len < GD25Q32C_PAGE_SIZE) {
        norflash_delay(450);
    }
#endif

    ret = norflash_status_WIP_1_wait(suspend);

    norflaship_cmd_done();

    return ret;
}

#ifdef FLASH_SUSPEND
enum HAL_NORFLASH_RET_T norflash_erase_resume(int suspend)
{
    // TODO: Need to check SUS1 bit in status reg?

    enum HAL_NORFLASH_RET_T ret;

    norflash_pre_operation();

    norflash_resume();

    ret = norflash_status_WIP_1_wait(suspend);

    norflash_post_operation();

    return ret;
}

enum HAL_NORFLASH_RET_T norflash_write_resume(int suspend)
{
    // TODO: Need to check SUS2 bit in status reg?

    return norflash_erase_resume(suspend);
}
#endif

int norflash_read(uint32_t start_address, uint8_t *buffer, uint32_t len)
{
    uint32_t index = 0;
    uint8_t val;

    if (len > NORFLASHIP_RXFIFO_SIZE) {
        return 1;
    }

    norflaship_clear_rxfifo();

    norflaship_busy_wait();

    norflaship_blksize(len);

    if (norflash_op_mode & HAL_NORFLASH_OP_MODE_QUAD_IO) {
        /* Quad , only fast */
        norflaship_cmd_addr(GD25Q32C_CMD_FAST_QUAD_IO_READ, start_address);
    } else if (norflash_op_mode & HAL_NORFLASH_OP_MODE_QUAD_OUTPUT) {
        /* Dual, only fast */
        norflaship_cmd_addr(GD25Q32C_CMD_FAST_QUAD_OUTPUT_READ, start_address);
    } else if (norflash_op_mode & HAL_NORFLASH_OP_MODE_DUAL_IO) {
        /* Dual, only fast */
        norflaship_cmd_addr(GD25Q32C_CMD_FAST_DUAL_IO_READ, start_address);
    } else if (norflash_op_mode & HAL_NORFLASH_OP_MODE_DUAL_OUTPUT) {
        /* Dual, only fast */
        norflaship_cmd_addr(GD25Q32C_CMD_FAST_DUAL_OUTPUT_READ, start_address);
    } else if (norflash_op_mode & HAL_NORFLASH_OP_MODE_FAST_SPI){
        /* fast */
        norflaship_cmd_addr(GD25Q32C_CMD_STANDARD_FAST_READ, start_address);
    } else {
        /* normal */
        norflaship_cmd_addr(GD25Q32C_CMD_STANDARD_READ, start_address);
    }

    while (1) {
        norflaship_rxfifo_empty_wait();

        val = norflaship_read_rxfifo();
        if (buffer) {
            buffer[index] = val;
        }

        ++index;
        if (index >= len) {
            break;
        }
    }

    norflaship_cmd_done();

    return 0;
}

void norflash_sleep(void)
{
    norflash_pre_operation();
#ifdef FLASH_HPM
    if (norflash_op_mode & HAL_NORFLASH_OP_MODE_HIGH_PERFORMANCE) {
        norflash_set_hpm(0);
    }
#endif
    norflaship_cmd_addr(GD25Q32C_CMD_DEEP_POWER_DOWN, 0);
}

void norflash_wakeup(void)
{
    norflaship_cmd_addr(GD25Q32C_CMD_RELEASE_FROM_DP, 0);
    // Wait 20us for flash to finish
    norflash_delay(40);
#ifdef FLASH_HPM
    if (norflash_op_mode & HAL_NORFLASH_OP_MODE_HIGH_PERFORMANCE) {
        norflash_set_hpm(1);
    }
#endif
    norflash_post_operation();
}

int norflash_init_status(uint32_t status)
{
    if (flash_list[flash_idx]->write_status == NULL) {
        return -1;
    }

    flash_list[flash_idx]->write_status(DRV_NORFLASH_W_STATUS_INIT, status);

    return 0;
}

int norflash_set_block_protection(uint32_t bp)
{
    if (flash_list[flash_idx]->write_status == NULL) {
        return -1;
    }

    flash_list[flash_idx]->write_status(DRV_NORFLASH_W_STATUS_BP, bp);

    return 0;
}

#ifdef FLASH_SECURITY_REGISTER
int norflash_security_register_lock(uint32_t id)
{
    if (flash_list[flash_idx]->write_status == NULL) {
        return -1;
    }

    flash_list[flash_idx]->write_status(DRV_NORFLASH_W_STATUS_LB, id);

    return 0;
}

enum HAL_NORFLASH_RET_T norflash_security_register_erase(uint32_t start_address)
{
    enum HAL_NORFLASH_RET_T ret;

    norflaship_cmd_addr(GD25Q32C_CMD_WRITE_ENABLE, start_address);
    // Need 1us. Or norflash_status_WEL_0_wait(), which needs 6us.

    norflaship_cmd_addr(GD25Q32C_CMD_SECURITY_REGISTER_ERASE, start_address);

    ret = norflash_status_WIP_1_wait(0);

    norflaship_cmd_done();

    return ret;
}

enum HAL_NORFLASH_RET_T norflash_security_register_write(uint32_t start_address, const uint8_t *buffer, uint32_t len)
{
    enum HAL_NORFLASH_RET_T ret;
    uint32_t remains;

    // Security register page size might be larger than normal page size
    // E.g., the size of P25Q32L and P25Q64L is 1024

    norflaship_clear_txfifo();

#if (CHIP_FLASH_CTRL_VER <= 1)
    uint32_t div = 0;

    if (len > NORFLASHIP_TXFIFO_SIZE) {
        div = norflaship_get_div();

        // Slow down to avoid tx fifo underflow (it takes about 10 cpu cycles to fill one byte)
        norflaship_div(16);
    }

    remains = norflaship_v1_write_txfifo_safe(buffer, len);
#else
    norflaship_blksize(len);

    remains = norflaship_write_txfifo(buffer, len);
#endif

    norflaship_cmd_addr(GD25Q32C_CMD_WRITE_ENABLE, start_address);

    norflaship_cmd_addr(GD25Q32C_CMD_SECURITY_REGISTER_PROGRAM, start_address);

#if (CHIP_FLASH_CTRL_VER <= 1)
    if (remains) {
        norflaship_v1_write_txfifo_all(buffer, len);
    }
#else
    while (remains > 0) {
        buffer += len - remains;
        len = remains;
        remains = norflaship_write_txfifo(buffer, len);
    }
#endif

    norflaship_busy_wait();

    ret = norflash_status_WIP_1_wait(0);

#if (CHIP_FLASH_CTRL_VER <= 1)
    if (div) {
        // Restore the old div
        norflaship_div(div);
    }
#endif

    norflaship_cmd_done();

    return ret;
}

int norflash_security_register_read(uint32_t start_address, uint8_t *buffer, uint32_t len)
{
    uint32_t index = 0;

    if (len > NORFLASHIP_RXFIFO_SIZE) {
        return 1;
    }

    norflaship_clear_rxfifo();

    norflaship_busy_wait();

    norflaship_blksize(len);

    norflaship_cmd_addr(GD25Q32C_CMD_SECURITY_REGISTER_READ, start_address);

    while (1) {
        norflaship_rxfifo_empty_wait();

        buffer[index] = norflaship_read_rxfifo();

        ++index;
        if (index >= len) {
            break;
        }
    }

    norflaship_cmd_done();

    return 0;
}

uint32_t norflash_security_register_enable_read(void)
{
    uint32_t mode;

    mode = norflash_op_mode;

    norflash_set_mode(0);

    norflaship_busy_wait();
#if (CHIP_FLASH_CTRL_VER <= 1)
    norflash_read(FLASH_NC_BASE, NULL, 1);
#endif
    norflaship_clear_rxfifo();
    norflaship_busy_wait();
    norflaship_rdcmd(GD25Q32C_CMD_SECURITY_REGISTER_READ);
    norflaship_busy_wait();

    return mode;
}

void norflash_security_register_disable_read(uint32_t mode)
{
    norflaship_busy_wait();
#if (CHIP_FLASH_CTRL_VER <= 1)
    norflash_read(FLASH_NC_BASE, NULL, 1);
#endif
    norflaship_clear_rxfifo();
    norflaship_busy_wait();
    norflaship_rdcmd(GD25Q32C_CMD_STANDARD_READ);
    norflaship_busy_wait();

    norflash_set_mode(mode);
}

#endif

