/* x25q_port.c */

#include "x25q.h"
#include "board_init.h"

/* index table be used to X25Q_BusWidth_Type translate to QSPI_BusWidth_Type. */
const static QSPI_BusWidth_Type X25Q_BUSWIDTH[5] = {QSPI_BusWidth_None, QSPI_BusWidth_1b, QSPI_BusWidth_2b, QSPI_BusWidth_4b};
/* index table be used to X25Q_WordWidth_Type translate to QSPI_WordWidth_Type. */
const static QSPI_WordWidth_Type X25Q_WORDWIDTH[4] = {QSPI_WordWidth_8b, QSPI_WordWidth_16b, QSPI_WordWidth_24b, QSPI_WordWidth_32b};

static void X25Q_QSPI_EnableDirectRead(void); /* enable qspi direct read. */
static void X25Q_QSPI_Init(void); /* init qspi module. */
static void X25Q_QSPI_TranslateXfer(QSPI_IndirectXferConf_Type * xfer_conf, X25Q_Xfer_Type * x25q_xfer); /* translate X25Q_Xfer_Type to QSPI_IndirectXferConf_Type. */
static void X25Q_QSPI_Read(X25Q_Xfer_Type * xfer); /* QSPI read. */
static void X25Q_QSPI_Write(X25Q_Xfer_Type * xfer); /* QSPI write. */

/* init spi module. */
void X25Q_HAL_Init(void)
{
    X25Q_QSPI_Init();
    X25Q_QSPI_EnableDirectRead(); /* enable direct read mode. */
}

/* read from spi module. */
void X25Q_HAL_Read(X25Q_Xfer_Type * xfer)
{
    switch (xfer->DeviceIndex)
    {
        case X25Q_Device_W25Q64:
            X25Q_QSPI_Read(xfer);
            break;
        default: /* invalid device index. */
            break;
    }
}

/* write to spi module. */
void X25Q_HAL_Write(X25Q_Xfer_Type * xfer)
{
    switch (xfer->DeviceIndex)
    {
        case X25Q_Device_W25Q64:
            X25Q_QSPI_Write(xfer);
            break;
        default: /* invalid device index. */
            break;
    }
}

/* init qspi module. */
static void X25Q_QSPI_Init(void)
{
    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);
}

static void X25Q_QSPI_EnableDirectRead(void)
{
    /* enable QSPI direct read. */
    QSPI_DirectXferConf_Type direct_conf;
    direct_conf.CmdBusWidth     = BOARD_QSPI_DIRECT_CMD_BUSWIDTH; /* fast read quad, cmd phase bus is 1b. */
    direct_conf.CmdValue        = BOARD_QSPI_DIRECT_CMD_VALUE; /* instruction. */
    direct_conf.AddrBusWidth    = BOARD_QSPI_DIRECT_ADR_BUSWIDTH; /* fast read quad, addr phase bus is 1b. */
    direct_conf.AddrWordWidth   = BOARD_QSPI_DIRECT_ADR_WORDWIDTH; /* 24 bit addr. */
    direct_conf.AltBusWidth     = QSPI_BusWidth_None; /* no alt phase. */
    direct_conf.DummyCycles     = BOARD_QSPI_DIRECT_DMY_CYCLES;               /* wait 8 dummy cycles. */
    direct_conf.DataBusWidth    = BOARD_QSPI_DIRECT_DAT_BUSWIDTH; /* fast read quad, data phase bus is 4b. */

    QSPI_EnableDirectRead(BOARD_QSPI_PORT, &direct_conf);
}

/* QSPI read. */
static void X25Q_QSPI_Read(X25Q_Xfer_Type * xfer)
{
    QSPI_IndirectXferConf_Type xfer_conf;

    /* translate xfer content to xfer_conf. */
    X25Q_QSPI_TranslateXfer(&xfer_conf, xfer);

    /* prepare to read data. */
    QSPI_SetIndirectReadConf(BOARD_QSPI_PORT, &xfer_conf);

    uint32_t dummy_remain = xfer->DummyBytes;
    uint32_t data_count = 0u;

    /* read data & dummy bytes. */
    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) )
        {
            if (dummy_remain > 0u)
            {
                QSPI_GetIndirectData(BOARD_QSPI_PORT); /* skip dummy byte. */
                dummy_remain--;
            }
            else
            {
                xfer->DataBuf[data_count++] = QSPI_GetIndirectData(BOARD_QSPI_PORT); /* recv data. */
            }
        }
    }

    /* clear xfer done status. */
    QSPI_ClearStatus(BOARD_QSPI_PORT, QSPI_STATUS_XFER_DONE);
}

/* QSPI write. */
static void X25Q_QSPI_Write(X25Q_Xfer_Type * xfer)
{
    QSPI_IndirectXferConf_Type xfer_conf;

    /* translate xfer content to xfer_conf. */
    X25Q_QSPI_TranslateXfer(&xfer_conf, xfer);

    /* prepare to write data. */
    QSPI_SetIndirectWriteConf(BOARD_QSPI_PORT, &xfer_conf);

    uint32_t dummy_remain = xfer->DummyBytes;
    uint32_t data_count = 0u;

    /* write data & dummy bytes. */
    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) )
        {
            if (dummy_remain > 0u)
            {
                QSPI_PutIndirectData(BOARD_QSPI_PORT, 0xFF); /* skip dummy byte. */
                dummy_remain--;
            }
            else if (data_count < xfer->DataLen)
            {
                QSPI_PutIndirectData(BOARD_QSPI_PORT, xfer->DataBuf[data_count]); /* put data. */
                data_count++;
            }
        }
    }

    /* clear xfer done status. */
    QSPI_ClearStatus(BOARD_QSPI_PORT, QSPI_STATUS_XFER_DONE);
}

/* translate X25Q_Xfer_Type to QSPI_IndirectXferConf_Type. */
static void X25Q_QSPI_TranslateXfer(QSPI_IndirectXferConf_Type * xfer_conf, X25Q_Xfer_Type * x25q_xfer)
{
    /* cmd phase. */
    xfer_conf->CmdBusWidth      = X25Q_BUSWIDTH[x25q_xfer->CmdBusWidth];
    xfer_conf->CmdValue         = x25q_xfer->CmdValue;

    /* addr phase. */
    xfer_conf->AddrBusWidth     = X25Q_BUSWIDTH[x25q_xfer->AddrBusWidth];
    xfer_conf->AddrWordWidth    = X25Q_WORDWIDTH[x25q_xfer->AddrWordWidth];
    xfer_conf->AddrValue        = x25q_xfer->AddrValue;

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

    /* replace dummy phase by ignoring data. */
    xfer_conf->DummyCycles      = 0u;

    /* data phase, including dummy bytes. */
    xfer_conf->DataBusWidth     = X25Q_BUSWIDTH[x25q_xfer->DataBusWidth];
    xfer_conf->DataWordWidth    = QSPI_WordWidth_8b;
    xfer_conf->DataLen          = x25q_xfer->DataLen + x25q_xfer->DummyBytes;
}

/* EOF. */
