/*
 * Copyright 2022 MindMotion Microelectronics Co., Ltd.
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "mem_port.h"

const mem_port_info_t mem_port_list[] =
{
    {0x30010000, 0x00010000}, /* sram base. */
    {0x90000000, 0x00100000}, /* qspi base. */
};
const uint32_t mem_port_num = sizeof(mem_port_list) / sizeof(mem_port_info_t);

/* the cmd of spiflash. */
#define MEM_SPIFLASH_ENABLE_WRITE           0x06 /* enable write func. */
#define MEM_SPIFLASH_DISABLE_WRITE          0x04 /* disable write func. */
#define MEM_SPIFLASH_READ_REGISTER_1        0x05 /* get spiflash register-1 status. */
#define MEM_SPIFLASH_ERASE_BLOCK            0x20 /* erase sector(4KB) */
#define MEM_SPIFLASH_WRITE_PAGE             0x02 /* page(256B) program. */
/* the bit mask of spiflash register. */
#define MEM_SPIFLASH_REGISTER1_BUSY_MASK    0x01 /* the busy bit mask on spiflash register-1. */
/* block & sector size. */
#define MEM_QSPI_BLOCK_SIZE (4 * 1024)
#define MEM_QSPI_SECOR_SIZE 256

/* busy check. */
static bool qspi_is_busy(void)
{
    QSPI_IndirectXferConf_Type xfer;
    xfer.CmdBusWidth    = QSPI_BusWidth_1b;
    xfer.CmdValue       = MEM_SPIFLASH_READ_REGISTER_1;
    xfer.AddrBusWidth   = QSPI_BusWidth_None;
    xfer.AltBusWidth    = QSPI_BusWidth_None;
    xfer.DummyCycles    = 0;
    xfer.DataBusWidth   = QSPI_BusWidth_1b;
    xfer.DataWordWidth  = QSPI_WordWidth_8b;
    xfer.DataLen        = 1;
    QSPI_ClearStatus(BOARD_QSPI_PORT, QSPI_STATUS_XFER_DONE);
    QSPI_SetIndirectReadConf(BOARD_QSPI_PORT, &xfer);

    while(0 == (QSPI_GetStatus(BOARD_QSPI_PORT) & QSPI_STATUS_XFER_DONE))
    {
    }

    uint32_t flags = QSPI_GetIndirectData(BOARD_QSPI_PORT);
    if (0 != (flags & MEM_SPIFLASH_REGISTER1_BUSY_MASK))
    {
        return true;
    }
    else
    {
        return false;
    }
}

/* enable & disable write. */
static void qspi_enable_write(bool enable)
{
    QSPI_IndirectXferConf_Type xfer;
    xfer.CmdBusWidth    = QSPI_BusWidth_1b;
    if (enable)
    {
        xfer.CmdValue       = MEM_SPIFLASH_ENABLE_WRITE;
    }
    else
    {
        xfer.CmdValue       = MEM_SPIFLASH_DISABLE_WRITE;
    }

    xfer.AddrBusWidth   = QSPI_BusWidth_None;
    xfer.AltBusWidth    = QSPI_BusWidth_None;
    xfer.DummyCycles    = 0;
    xfer.DataBusWidth   = QSPI_BusWidth_None;
    QSPI_ClearStatus(BOARD_QSPI_PORT, QSPI_STATUS_XFER_DONE);
    QSPI_SetIndirectReadConf(BOARD_QSPI_PORT, &xfer);

    while(0 == (QSPI_GetStatus(BOARD_QSPI_PORT) & QSPI_STATUS_XFER_DONE))
    {
    }
}

/* erase block. */
static void qspi_erase_block(uint32_t base)
{
    QSPI_IndirectXferConf_Type xfer;
    xfer.CmdBusWidth    = QSPI_BusWidth_1b;
    xfer.CmdValue       = MEM_SPIFLASH_ERASE_BLOCK;
    xfer.AddrBusWidth   = QSPI_BusWidth_1b;
    xfer.AddrWordWidth  = QSPI_WordWidth_24b;
    xfer.AddrValue      = base & 0x00FFF000;
    xfer.AltBusWidth    = QSPI_BusWidth_None;
    xfer.DummyCycles    = 0;
    xfer.DataBusWidth   = QSPI_BusWidth_None;
    QSPI_ClearStatus(BOARD_QSPI_PORT, QSPI_STATUS_XFER_DONE);
    QSPI_SetIndirectReadConf(BOARD_QSPI_PORT, &xfer);

    while(0 == (QSPI_GetStatus(BOARD_QSPI_PORT) & QSPI_STATUS_XFER_DONE))
    {
    }
}

/* write sector. */
static void qspi_write_page(uint32_t base, uint8_t * buf, uint32_t size)
{
    QSPI_IndirectXferConf_Type xfer;
    xfer.CmdBusWidth    = QSPI_BusWidth_1b;
    xfer.CmdValue       = MEM_SPIFLASH_WRITE_PAGE;
    xfer.AddrBusWidth   = QSPI_BusWidth_1b;
    xfer.AddrWordWidth  = QSPI_WordWidth_24b;
    xfer.AddrValue      = base & 0x00FFFFFF;
    xfer.AltBusWidth    = QSPI_BusWidth_None;
    xfer.DummyCycles    = 0;
    xfer.DataBusWidth   = QSPI_BusWidth_1b;
    xfer.DataWordWidth  = QSPI_WordWidth_8b;
    xfer.DataLen        = size;
    QSPI_ClearStatus(BOARD_QSPI_PORT, QSPI_STATUS_XFER_DONE);
    QSPI_SetIndirectWriteConf(BOARD_QSPI_PORT, &xfer);

    uint32_t has_xfer = 0;
    while(0 == (QSPI_GetStatus(BOARD_QSPI_PORT) & QSPI_STATUS_XFER_DONE))
    {
        while(0 == (QSPI_GetStatus(BOARD_QSPI_PORT) & QSPI_STATUS_FIFO_FULL) && has_xfer < size)
        {
            QSPI_PutIndirectData(BOARD_QSPI_PORT, buf[has_xfer]);
            has_xfer++;
        }
    }
}

/* write anywhere. */
static void qspi_write(uint32_t base, uint8_t * buf, uint32_t size)
{
    while(qspi_is_busy())
    {}

    /* page aligned. */
    uint32_t page_aligned = MEM_QSPI_SECOR_SIZE - (base % MEM_QSPI_SECOR_SIZE);
    if (page_aligned != MEM_QSPI_SECOR_SIZE)
    {
        if (page_aligned > size)
        {
            page_aligned = size;
        }
        qspi_enable_write(true);
        qspi_write_page(base, buf, page_aligned);
        base += page_aligned;
        buf  += page_aligned;
        size -= page_aligned;
        while(qspi_is_busy())
        {}
    }

    /* write page. */
    while(size > 0)
    {
        uint32_t write_size = size;
        if (write_size > MEM_QSPI_SECOR_SIZE)
        {
            write_size = MEM_QSPI_SECOR_SIZE;
        }
        qspi_enable_write(true);
        qspi_write_page(base, buf, write_size);
        base += write_size;
        buf  += write_size;
        size -= write_size;
        while(qspi_is_busy())
        {}
    }
}

uint32_t * app_vtor;
void mem_port_exec(uint32_t base)
{
    RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_USBOTGFS);
    RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_USBOTGFS, false);
    RCC_ResetAPB2Periphs(RCC_APB2_PERIPH_UART1);
    RCC_EnableAPB2Periphs(RCC_APB2_PERIPH_UART1, false);

    CLOCK_ResetToDefault();

    app_vtor = (uint32_t *)base;

    __disable_irq();
    SCB->VTOR = base;

    __ISB();
    __DSB();

    __set_MSP(app_vtor[0]);
    __set_PSP(app_vtor[0]);

    __enable_irq();
    ((void(*)(void))app_vtor[1])();
}

uint32_t mem_port_erase(uint32_t base, uint32_t size)
{
    /* SRAM BASE. */
    if (base >= mem_port_list[0].base && (base + size ) < (mem_port_list[0].base + mem_port_list[0].size))
    {
        memset((void*)base, 0, size);
        return size;
    } /* QSPI BASE. */
    else if (base >= mem_port_list[1].base && (base + size ) < (mem_port_list[1].base + mem_port_list[1].size))
    {
        if ((base % MEM_QSPI_BLOCK_SIZE) != 0)
        {
            return 0;
        }
        base &= 0x0FFFF000;
        uint32_t erase_size = 0;
        while(erase_size < size)
        {
            qspi_enable_write(true);
            qspi_erase_block(base);
            while(qspi_is_busy())
            {
            }
            qspi_enable_write(false);
            erase_size += MEM_QSPI_BLOCK_SIZE;
        }
        return erase_size;
    } /* INVAILD BASE. */
    else
    {
        return 0;
    }
}

uint32_t mem_port_write(uint32_t base, uint8_t * buf, uint32_t size)
{
    /* SRAM BASE. */
    if (base >= mem_port_list[0].base && (base + size ) < (mem_port_list[0].base + mem_port_list[0].size))
    {
        memcpy((void*)base, buf, size);
        return size;
    } /* QSPI BASE. */
    else if (base >= mem_port_list[1].base && (base + size ) < (mem_port_list[1].base + mem_port_list[1].size))
    {
        qspi_write(base, buf, size);
        return size;
    } /* INVAILD BASE. */
    else
    {
        return 0;
    }
}

/* EOF. */
