/*
 * This file is part of the Serial Flash Universal Driver Library.
 *
 * Copyright (c) 2016-2018, Armink, <armink.ztl@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * 'Software'), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Function: Portable interface for each platform.
 * Created on: 2016-04-23
 */

#include <sfud.h>
#include <stdarg.h>
#include "board_init.h"

static char log_buf[256u];

/* format for SFUD spi lines to QSPI_BusWidth_Type. */
const QSPI_BusWidth_Type    SFUD_BUSWIDTH_FORMAT[5] = {QSPI_BusWidth_None, QSPI_BusWidth_1b, QSPI_BusWidth_2b, QSPI_BusWidth_None, QSPI_BusWidth_4b};
/* format for SFUD word size to QSPI_WordWidth_Type, the index vaule = (SFUD word size) / 8, and index value 0 is invalid. */
const QSPI_WordWidth_Type   SFUD_WORDWIDTH_FORMAT[5] = {QSPI_WordWidth_8b, QSPI_WordWidth_8b, QSPI_WordWidth_16b, QSPI_WordWidth_24b, QSPI_WordWidth_32b};

void sfud_log_debug(const char *file, const long line, const char *format, ...);
static bool qspi_spi_xfer(uint32_t send_len, const uint8_t * send_buf, uint32_t recv_len, uint8_t * recv_buf);
static void retry_delay_100us(void);

static void spi_lock(const sfud_spi *spi)
{
    __disable_irq();
}

static void spi_unlock(const sfud_spi *spi)
{
    __enable_irq();
}

/**
 * SPI write data then read data
 */
static sfud_err spi_write_read(const sfud_spi *spi, const uint8_t *write_buf, size_t write_size, uint8_t *read_buf,
        size_t read_size) {
    sfud_err result = SFUD_SUCCESS;

    if (write_size)
    {
        SFUD_ASSERT(write_buf);
    }
    if (read_size)
    {
        SFUD_ASSERT(read_buf);
    }

    /* xfer data. */
    if (false == qspi_spi_xfer(write_size, write_buf, read_size, read_buf) )
    {
        result = SFUD_ERR_READ;
    }

    return result;
}

#ifdef SFUD_USING_QSPI
/**
 * read flash data by QSPI
 */
static sfud_err qspi_read(const struct __sfud_spi *spi, uint32_t addr, sfud_qspi_read_cmd_format *qspi_read_cmd_format,
        uint8_t *read_buf, size_t read_size) {
    (void)spi;

    sfud_err result = SFUD_SUCCESS;

    QSPI_IndirectXferConf_Type xfer_conf;

    /* instruction phase. */
    xfer_conf.CmdBusWidth   = SFUD_BUSWIDTH_FORMAT[qspi_read_cmd_format->instruction_lines];
    xfer_conf.CmdValue      = qspi_read_cmd_format->instruction;

    /* addr phase. */
    xfer_conf.AddrBusWidth  = SFUD_BUSWIDTH_FORMAT[qspi_read_cmd_format->address_lines];
    xfer_conf.AddrWordWidth = SFUD_WORDWIDTH_FORMAT[qspi_read_cmd_format->address_size / 8u];
    xfer_conf.AddrValue     = addr;

    /* alt phase. */
    xfer_conf.AltBusWidth   = QSPI_BusWidth_None; /* no alt phase. */

    /* dummy phase. */
    xfer_conf.DummyCycles   = qspi_read_cmd_format->dummy_cycles;

    /* data phase. */
    xfer_conf.DataBusWidth  = SFUD_BUSWIDTH_FORMAT[qspi_read_cmd_format->data_lines];
    xfer_conf.DataWordWidth = QSPI_WordWidth_8b;
    xfer_conf.DataLen       = read_size;

    /* clear xfer done status. */
    QSPI_ClearStatus(BOARD_QSPI_PORT, QSPI_STATUS_XFER_DONE);
    /* start read. */
    QSPI_SetIndirectReadConf(BOARD_QSPI_PORT, &xfer_conf);
    uint32_t read_cnt = 0u;

    /* read data. */
    while( (QSPI_STATUS_XFER_DONE | QSPI_STATUS_FIFO_EMPTY) != (QSPI_GetStatus(BOARD_QSPI_PORT) & (QSPI_STATUS_XFER_DONE | QSPI_STATUS_FIFO_EMPTY) ) )
    {
        if ( 0u == (QSPI_GetStatus(BOARD_QSPI_PORT) & QSPI_STATUS_FIFO_EMPTY) )
        {
            read_buf[read_cnt++] = QSPI_GetIndirectData(BOARD_QSPI_PORT); /* recv data. */
        }
    }

    return result;
}
#endif /* SFUD_USING_QSPI */

sfud_err sfud_spi_port_init(sfud_flash *flash)
{
    sfud_err result = SFUD_SUCCESS;

    /* init qspi hardware. */
    QSPI_Init_Type qspi_init;
    qspi_init.SckDiv = BOARD_QSPI_SCKDIV;
    qspi_init.CsHighLevelCycles = BOARD_QSPI_CS_HIGH_CYCLE;
    qspi_init.SpiMode = QSPI_SpiMode_3; /* suggest using SPI Mode 3. */
    qspi_init.RxSampleDelay = BOARD_QSPI_RX_SAMPLE_DELAY;

    QSPI_Init(BOARD_QSPI_PORT, &qspi_init);

    /* init sfud spi obj. */
    flash->spi.wr           = spi_write_read;
    flash->spi.qspi_read    = qspi_read;
    flash->spi.lock         = spi_lock;
    flash->spi.unlock       = spi_unlock;
    flash->spi.user_data    = NULL;
    flash->retry.delay      = retry_delay_100us;
    flash->retry.times      = 60u * 10000u;

    return result;
}

/**
 * This function is print debug info.
 *
 * @param file the file which has call this function
 * @param line the line number which has call this function
 * @param format output format
 * @param ... args
 */
void sfud_log_debug(const char *file, const long line, const char *format, ...)
{
    va_list args;

    /* args point to the first variable parameter */
    va_start(args, format);
    printf("[SFUD](%s:%ld) ", file, line);
    /* must use vprintf to print */
    vsnprintf(log_buf, sizeof(log_buf), format, args);
    printf("%s\r\n", log_buf);
    va_end(args);
}

/**
 * This function is print routine info.
 *
 * @param format output format
 * @param ... args
 */
void sfud_log_info(const char *format, ...)
{
    va_list args;

    /* args point to the first variable parameter */
    va_start(args, format);
    printf("[SFUD]");
    /* must use vprintf to print */
    vsnprintf(log_buf, sizeof(log_buf), format, args);
    printf("%s\r\n", log_buf);
    va_end(args);
}

static bool qspi_spi_xfer(uint32_t send_len, const uint8_t * send_buf, uint32_t recv_len, uint8_t * recv_buf)
{
    QSPI_IndirectXferConf_Type xfer_conf;

    if (recv_len == 0) /* only send data. */
    {
        xfer_conf.CmdBusWidth   = QSPI_BusWidth_None;
        xfer_conf.AddrBusWidth  = QSPI_BusWidth_None;
        xfer_conf.AltBusWidth   = QSPI_BusWidth_None;
        xfer_conf.DummyCycles   = 0u;
        xfer_conf.DataBusWidth  = QSPI_BusWidth_1b;
        xfer_conf.DataWordWidth = QSPI_WordWidth_8b;
        xfer_conf.DataLen       = send_len;

        /* clear xfer done status. */
        QSPI_ClearStatus(BOARD_QSPI_PORT, QSPI_STATUS_XFER_DONE);
        QSPI_SetIndirectWriteConf(BOARD_QSPI_PORT, &xfer_conf);
        uint32_t send_cnt = 0;
        while( QSPI_STATUS_XFER_DONE != (QSPI_GetStatus(BOARD_QSPI_PORT) & QSPI_STATUS_XFER_DONE) )
        {
            if ( 0u == (QSPI_GetStatus(BOARD_QSPI_PORT) & QSPI_STATUS_FIFO_FULL) && send_cnt < send_len)
            {
                QSPI_PutIndirectData(BOARD_QSPI_PORT, send_buf[send_cnt]); /* put data. */
                send_cnt++;
            }
        }
    }
    else
    {
        switch (send_len)
        {
            case 1u: /* send 1 byte, use cmd phase. */
                xfer_conf.AddrBusWidth  = QSPI_BusWidth_None;
                xfer_conf.AltBusWidth   = QSPI_BusWidth_None;
                break;
            case 2u: /* send 2 byte, use addr(8b) send send_buf[1u]. */
                xfer_conf.AddrBusWidth  = QSPI_BusWidth_1b;
                xfer_conf.AddrWordWidth = QSPI_WordWidth_8b;
                xfer_conf.AddrValue     = send_buf[1u];
                xfer_conf.AltBusWidth   = QSPI_BusWidth_None;
                break;
            case 3u: /* send 3 byte, use addr(16b) send send_buf[1u ~ 2u]. */
                xfer_conf.AddrBusWidth  = QSPI_BusWidth_1b;
                xfer_conf.AddrWordWidth = QSPI_WordWidth_16b;
                xfer_conf.AddrValue     = ((uint32_t)send_buf[1u] << 8u) | send_buf[2u];
                xfer_conf.AltBusWidth   = QSPI_BusWidth_None;
                break;
            case 4u:  /* send 4 byte, use addr(24b) send send_buf[1u ~ 3u]. */
                xfer_conf.AddrBusWidth  = QSPI_BusWidth_1b;
                xfer_conf.AddrWordWidth = QSPI_WordWidth_24b;
                xfer_conf.AddrValue     = ((uint32_t)send_buf[1u] << 16u) | ((uint32_t)send_buf[2u] << 8u) | send_buf[3u];
                xfer_conf.AltBusWidth   = QSPI_BusWidth_None;
                break;
            case 5u: /* send 5 byte, use addr(32b) send send_buf[1u ~ 4u]. */
                xfer_conf.AddrBusWidth  = QSPI_BusWidth_1b;
                xfer_conf.AddrWordWidth = QSPI_WordWidth_32b;
                xfer_conf.AddrValue     = ((uint32_t)send_buf[1u] << 24u) | ((uint32_t)send_buf[2u] << 16u) | ((uint32_t)send_buf[3u] << 8u) | send_buf[4u];
                xfer_conf.AltBusWidth   = QSPI_BusWidth_None;
                break;
            case 6u: /* send 6 byte, use addr(8b) send send_buf[1u] & alt(32b) phase send send_buf[2u ~ 5u]. */
                xfer_conf.AddrBusWidth  = QSPI_BusWidth_1b;
                xfer_conf.AddrWordWidth = QSPI_WordWidth_8b;
                xfer_conf.AddrValue     = send_buf[1u];
                xfer_conf.AltBusWidth   = QSPI_BusWidth_1b;
                xfer_conf.AltWordWidth  = QSPI_WordWidth_32b;
                xfer_conf.AddrValue     = ((uint32_t)send_buf[2u] << 24u) | ((uint32_t)send_buf[3u] << 16u) | ((uint32_t)send_buf[4u] << 8u) | send_buf[5u];
                break;
            case 7u: /* send 7 byte, use addr(16b) send send_buf[1u ~ 2u] & alt(32b) phase send send_buf[3u ~ 6u]. */
                xfer_conf.AddrBusWidth  = QSPI_BusWidth_1b;
                xfer_conf.AddrWordWidth = QSPI_WordWidth_16b;
                xfer_conf.AddrValue     = ((uint32_t)send_buf[1u] << 8u) | send_buf[2u];
                xfer_conf.AltBusWidth   = QSPI_BusWidth_1b;
                xfer_conf.AltWordWidth  = QSPI_WordWidth_32b;
                xfer_conf.AddrValue     = ((uint32_t)send_buf[3u] << 24u) | ((uint32_t)send_buf[4u] << 16u) | ((uint32_t)send_buf[5u] << 8u) | send_buf[6u];
                break;
            case 8u: /* send 8 byte, use addr(24b) send send_buf[1u ~ 3u] & alt(32b) phase send send_buf[4u ~ 7u]. */
                xfer_conf.AddrBusWidth  = QSPI_BusWidth_1b;
                xfer_conf.AddrWordWidth = QSPI_WordWidth_24b;
                xfer_conf.AddrValue     = ((uint32_t)send_buf[1u] << 16u) | ((uint32_t)send_buf[2u] << 8u) | send_buf[3u];
                xfer_conf.AltBusWidth   = QSPI_BusWidth_1b;
                xfer_conf.AltWordWidth  = QSPI_WordWidth_32b;
                xfer_conf.AddrValue     = ((uint32_t)send_buf[4u] << 24u) | ((uint32_t)send_buf[5u] << 16u) | ((uint32_t)send_buf[6u] << 8u) | send_buf[7u];
                break;
            case 9u: /* send 8 byte, use addr(32b) send send_buf[1u ~ 4u] & alt(32b) phase send send_buf[5u ~ 8u]. */
                xfer_conf.AddrBusWidth  = QSPI_BusWidth_1b;
                xfer_conf.AddrWordWidth = QSPI_WordWidth_32b;
                xfer_conf.AddrValue     = ((uint32_t)send_buf[1u] << 24u) | ((uint32_t)send_buf[2u] << 16u) | ((uint32_t)send_buf[3u] << 8u) | send_buf[4u];
                xfer_conf.AltBusWidth   = QSPI_BusWidth_1b;
                xfer_conf.AltWordWidth  = QSPI_WordWidth_32b;
                xfer_conf.AddrValue     = ((uint32_t)send_buf[5u] << 24u) | ((uint32_t)send_buf[6u] << 16u) | ((uint32_t)send_buf[7u] << 8u) | send_buf[8u];
                break;
            default:
                return false;
        }

        /* cmd phase. */
        xfer_conf.CmdBusWidth   = QSPI_BusWidth_1b;
        xfer_conf.CmdValue      = send_buf[0];

        /* no dummy phase. */
        xfer_conf.DummyCycles   = 0u;

        /* data phase. */
        xfer_conf.DataBusWidth  = QSPI_BusWidth_1b;
        xfer_conf.DataWordWidth = QSPI_WordWidth_8b;
        xfer_conf.DataLen       = recv_len;

        /* clear xfer done status. */
        QSPI_ClearStatus(BOARD_QSPI_PORT, QSPI_STATUS_XFER_DONE);
        QSPI_SetIndirectReadConf(BOARD_QSPI_PORT, &xfer_conf);
        uint32_t recv_cnt = 0;

        /* recv data. */
        while( (QSPI_STATUS_XFER_DONE | QSPI_STATUS_FIFO_EMPTY) != (QSPI_GetStatus(BOARD_QSPI_PORT) & (QSPI_STATUS_XFER_DONE | QSPI_STATUS_FIFO_EMPTY) ) )
        {
            if ( 0u == (QSPI_GetStatus(BOARD_QSPI_PORT) & QSPI_STATUS_FIFO_EMPTY))
            {
                recv_buf[recv_cnt] = QSPI_GetIndirectData(BOARD_QSPI_PORT);
                recv_cnt++;
            }
        }
    }

    return true;
}

static void retry_delay_100us(void)
{
    for (uint32_t i = 0; i < CLOCK_SYS_FREQ / 10000u; i++)
    {
        __NOP();
    }
}

/* EOF. */
