/* x25q.c */
#include "x25q.h"
#include "stdio.h"

static X25Q_DeviceInfo_Type X25Q_DeviceTable[] = X25Q_DEVICE_TABLE; /* record the device information. */

static void     X25Q_EnableWrite(uint32_t dev_index, bool enable); /* enable Write. */
static uint32_t X25Q_ReadRegisterValue(uint32_t dev_index, uint32_t reg_index); /* read register value, the index of register x is (x - 1). */
static void     X25Q_WriteRegisterValue(uint32_t dev_index, uint32_t reg_index, uint8_t value); /* write register value, the index of register x is (x - 1). */
static void     X25Q_WaitBusyBlocking(uint32_t dev_index); /* wait for chip idle. */
static void     X25Q_EnableQPI(uint32_t dev_index, bool enable); /* enable QPI mode. */
static void     X25Q_EnableQE(uint32_t dev_index, bool enable); /* enable Quad Mode (IO2 & IO3 are used). */

/* init x25q. */
void X25Q_Init(void)
{
    X25Q_HAL_Init(); /* init hardware. */

    /* init chip operation mode. */
    for (uint32_t i = 0; i < sizeof(X25Q_DeviceTable) / sizeof(X25Q_DeviceInfo_Type); i++)
    {
        if (0 == X25Q_DeviceTable[i].MemSize) /* invalid device info. */
        {
            continue;
        }
        X25Q_EnableQPI(i, false); /* close qpi mode, even if it is not enableed. */
        switch (X25Q_DeviceTable[i].OperationMode)
        {
            case X25Q_OperationMode_SPI:
                break; /* do nothing. */
            case X25Q_OperationMode_DSPI:
                break; /* do nothing. */
            case X25Q_OperationMode_QSPI:
                X25Q_EnableQE(i, true);
                break; /* need to enable QE mode. */
            case X25Q_OperationMode_QPI:
                X25Q_EnableQE(i, true); /* need to enable QE mode before enable QPI mode. */
                X25Q_EnableQPI(i, true); /* enable QPI mode. */
                break;
            default:
                break;
        }
    }
}

/* read manufactor id. */
uint32_t X25Q_ReadManuId(uint32_t dev_index)
{
    uint8_t buf[2u] = {0u};

    X25Q_Xfer_Type xfer;

    switch (X25Q_DeviceTable[dev_index].OperationMode) /* set the bus width. */
    {
        case X25Q_OperationMode_SPI: /* read manu id by spi. */
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            xfer.AddrBusWidth = X25Q_BusWidth_1b;
            xfer.DataBusWidth = X25Q_BusWidth_1b;
            break;
        case X25Q_OperationMode_DSPI: /* read manu id by spi. */
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            xfer.AddrBusWidth = X25Q_BusWidth_1b;
            xfer.DataBusWidth = X25Q_BusWidth_1b;
            break;
        case X25Q_OperationMode_QSPI: /* read manu id by spi. */
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            xfer.AddrBusWidth = X25Q_BusWidth_1b;
            xfer.DataBusWidth = X25Q_BusWidth_1b;
            break;
        case X25Q_OperationMode_QPI: /* read manu id by qpi. */
            xfer.CmdBusWidth  = X25Q_BusWidth_4b;
            xfer.AddrBusWidth = X25Q_BusWidth_4b;
            xfer.DataBusWidth = X25Q_BusWidth_4b;
            break;
        default: /* invalid operation mode, using spi mode. */
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            xfer.AddrBusWidth = X25Q_BusWidth_1b;
            xfer.DataBusWidth = X25Q_BusWidth_1b;
            break;
    }

    xfer.DeviceIndex    = dev_index;
    xfer.AddrWordWidth  = X25Q_WordWidth_24b; /* after 2 dummy bytes, need to send a 0x00 data, then can recv manu id. */
    xfer.AddrValue      = 0xFFFF00; /* use addr phase instead dummy phase. */
    xfer.DummyBytes     = 0u;
    xfer.CmdValue       = X25Q_Cmd_GetManuId;
    xfer.DataLen        = sizeof(buf);
    xfer.DataBuf        = buf;

    X25Q_HAL_Read(&xfer);

    return (buf[0u] << 8u) | buf[1u]; /* big endian. */
}

/* erase chip data. */
void X25Q_EraseChip(uint32_t dev_index)
{
    X25Q_EnableWrite(dev_index, true); /* must enable write before erase chip. */

    X25Q_Xfer_Type xfer;

    switch (X25Q_DeviceTable[dev_index].OperationMode)
    {
        case X25Q_OperationMode_SPI: /* cmd phase use 1b bus. */
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            break;
        case X25Q_OperationMode_DSPI: /* cmd phase use 1b bus. */
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            break;
        case X25Q_OperationMode_QSPI: /* cmd phase use 1b bus. */
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            break;
        case X25Q_OperationMode_QPI: /* cmd phase use 4b bus. */
            xfer.CmdBusWidth  = X25Q_BusWidth_4b;
            break;
        default: /* unknow the operation, using SPI mode. */
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            break;
    }

    xfer.DeviceIndex    = dev_index;
    xfer.CmdValue       = X25Q_Cmd_EraseChip;
    xfer.AddrBusWidth   = X25Q_BusWidth_None;
    xfer.DummyBytes     = 0u;
    xfer.DataBusWidth   = X25Q_BusWidth_None;
    xfer.DataLen        = 0;

    X25Q_HAL_Write(&xfer);

    X25Q_WaitBusyBlocking(dev_index); /* wait for erase chip done. */

    X25Q_EnableWrite(dev_index, false); /* disable write function. */
}

/* erase sector. */
void X25Q_EraseSector(uint32_t dev_index, uint32_t addr)
{
    if (addr > X25Q_DeviceTable[dev_index].MemSize - X25Q_DeviceTable[dev_index].SectorSize)
    {
        return; /* addr overflow. */
    }

    X25Q_EnableWrite(dev_index, true); /* must enable write before erase sector. */

    X25Q_Xfer_Type xfer;

    switch (X25Q_DeviceTable[dev_index].OperationMode)
    {
        case X25Q_OperationMode_SPI: /* cmd % addr phase use 1b bus. */
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            xfer.AddrBusWidth = X25Q_BusWidth_1b;
            break;
        case X25Q_OperationMode_DSPI: /* cmd % addr phase use 1b bus. */
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            xfer.AddrBusWidth = X25Q_BusWidth_1b;
            break;
        case X25Q_OperationMode_QSPI: /* cmd % addr phase use 1b bus. */
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            xfer.AddrBusWidth = X25Q_BusWidth_1b;
            break;
        case X25Q_OperationMode_QPI: /* all phase use 4b bus. */
            xfer.CmdBusWidth  = X25Q_BusWidth_4b;
            xfer.AddrBusWidth = X25Q_BusWidth_4b;
            break;
        default: /* unknow the operation, using SPI mode. */
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            xfer.AddrBusWidth = X25Q_BusWidth_1b;
            break;
    }

    xfer.DeviceIndex    = dev_index;
    xfer.AddrWordWidth  = X25Q_WordWidth_24b;
    xfer.CmdValue       = X25Q_Cmd_EraseSector;
    xfer.AddrValue      = addr;
    xfer.DummyBytes     = 0u;
    xfer.DataBusWidth   = X25Q_BusWidth_None;
    xfer.DataLen        = 0;

    X25Q_HAL_Write(&xfer);

    X25Q_WaitBusyBlocking(dev_index);  /* wait for erase sector done. */

    X25Q_EnableWrite(dev_index, false); /* close write. */
}

/* write page. */
void X25Q_WritePage(uint32_t dev_index, uint32_t addr, uint32_t size, uint8_t * buf)
{
    if (addr + size > X25Q_DeviceTable[dev_index].MemSize) /* addr overflow. */
    {
        return;
    }

    X25Q_EnableWrite(dev_index, true); /* must enable write before write page. */

    X25Q_Xfer_Type xfer;

    switch (X25Q_DeviceTable[dev_index].OperationMode)
    {
        case X25Q_OperationMode_SPI: /* 1b bus in data phase. */
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            xfer.CmdValue     = X25Q_Cmd_WritePage;
            xfer.AddrBusWidth = X25Q_BusWidth_1b;
            xfer.DataBusWidth = X25Q_BusWidth_1b;
            break;
        case X25Q_OperationMode_DSPI: /* DSPI use SPI mode to write data. */
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            xfer.CmdValue     = X25Q_Cmd_WritePage;
            xfer.AddrBusWidth = X25Q_BusWidth_1b;
            xfer.DataBusWidth = X25Q_BusWidth_1b;
            break;
        case X25Q_OperationMode_QSPI: /* 4b bus in data phase. */
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            xfer.CmdValue     = X25Q_Cmd_WritePageQuad;
            xfer.AddrBusWidth = X25Q_BusWidth_1b;
            xfer.DataBusWidth = X25Q_BusWidth_4b;
            break;
        case X25Q_OperationMode_QPI: /* 4b bus in all phase. */
            xfer.CmdBusWidth  = X25Q_BusWidth_4b;
            xfer.CmdValue     = X25Q_Cmd_WritePage;
            xfer.AddrBusWidth = X25Q_BusWidth_4b;
            xfer.DataBusWidth = X25Q_BusWidth_4b;
            break;
        default: /* unknow the operation, using SPI mode. */
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            xfer.CmdValue     = X25Q_Cmd_WritePage;
            xfer.AddrBusWidth = X25Q_BusWidth_1b;
            xfer.DataBusWidth = X25Q_BusWidth_1b;
            break;
    }

    xfer.DeviceIndex    = dev_index;
    xfer.AddrWordWidth  = X25Q_WordWidth_24b;
    xfer.AddrValue      = addr;
    xfer.DummyBytes     = 0u;
    xfer.DataLen        = size;
    xfer.DataBuf        = buf;

    X25Q_HAL_Write(&xfer);

    X25Q_WaitBusyBlocking(dev_index); /* wait for write page done (chip idle). */

    X25Q_EnableWrite(dev_index, false); /* close write. */
}

void X25Q_WriteData(uint32_t dev_index, uint32_t addr, uint32_t size, uint8_t * buf)
{
    if (addr + size > X25Q_DeviceTable[dev_index].MemSize)/* addr overflow. */
    {
        return;
    }

    uint32_t xfer_count  = 0u;
    uint32_t xfer_remain = size;
    uint32_t xfer_size   = 0u;

    /* align write. */
    if (0u != addr % X25Q_DeviceTable[dev_index].PageSize)
    {
        uint32_t xfer_align = X25Q_DeviceTable[dev_index].PageSize - (addr % X25Q_DeviceTable[dev_index].PageSize);

        xfer_size = (xfer_remain > xfer_align ? xfer_align : xfer_remain);

        X25Q_WritePage(dev_index, addr + xfer_count, xfer_size, &buf[xfer_count]);
        xfer_remain -= xfer_size;
        xfer_count += xfer_size;
    }

    /* page write. */
    while(xfer_remain > 0u)
    {
        xfer_size = (xfer_remain > X25Q_DeviceTable[dev_index].PageSize ? X25Q_DeviceTable[dev_index].PageSize : xfer_remain);
        X25Q_WritePage(dev_index, addr + xfer_count, xfer_size, &buf[xfer_count]);
        xfer_remain -= xfer_size;
        xfer_count += xfer_size;
    }
}

/* read data. */
void X25Q_ReadData(uint32_t dev_index, uint32_t addr, uint32_t size, uint8_t * buf)
{
    X25Q_Xfer_Type xfer;

    if (addr + size > X25Q_DeviceTable[dev_index].MemSize)
    {
        return; /* addr overflow. */
    }

    switch (X25Q_DeviceTable[dev_index].OperationMode) /* set the info of xfer. */
    {
        case X25Q_OperationMode_SPI: /* all phase use 1b bus. */
            xfer.CmdBusWidth    = X25Q_BusWidth_1b;
            xfer.CmdValue       = X25Q_Cmd_FaseRead;
            xfer.AddrBusWidth   = X25Q_BusWidth_1b;
            xfer.DummyBytes     = 1u;
            xfer.DataBusWidth   = X25Q_BusWidth_1b;
            break;
        case X25Q_OperationMode_DSPI: /* data phase use 2b bus. */
            xfer.CmdBusWidth    = X25Q_BusWidth_1b;
            xfer.CmdValue       = X25Q_Cmd_FaseReadDual;
            xfer.AddrBusWidth   = X25Q_BusWidth_1b;
            xfer.DummyBytes     = 2u;
            xfer.DataBusWidth   = X25Q_BusWidth_2b;
            break;
        case X25Q_OperationMode_QSPI: /* data phase use 4b bus. */
            xfer.CmdBusWidth    = X25Q_BusWidth_1b;
            xfer.CmdValue       = X25Q_Cmd_FaseReadQuad;
            xfer.AddrBusWidth   = X25Q_BusWidth_1b;
            xfer.DummyBytes     = 4u;
            xfer.DataBusWidth   = X25Q_BusWidth_4b;
            break;
        case X25Q_OperationMode_QPI: /* all phase use 4b bus. */
            xfer.CmdBusWidth    = X25Q_BusWidth_4b;
            xfer.CmdValue       = X25Q_Cmd_FaseRead;
            xfer.AddrBusWidth   = X25Q_BusWidth_4b;
            xfer.DummyBytes     = 1u;
            xfer.DataBusWidth   = X25Q_BusWidth_4b;
            break;
        default: /* unknow the operation, using SPI mode. */
            xfer.CmdBusWidth    = X25Q_BusWidth_1b;
            xfer.CmdValue       = X25Q_Cmd_FaseRead;
            xfer.AddrBusWidth   = X25Q_BusWidth_1b;
            xfer.DummyBytes     = 1u;
            xfer.DataBusWidth   = X25Q_BusWidth_1b;
            break;
    }

    xfer.DeviceIndex = dev_index;
    xfer.AddrWordWidth = X25Q_WordWidth_24b;
    xfer.AddrValue   = addr;
    xfer.DataLen     = size;
    xfer.DataBuf     = buf;
    X25Q_HAL_Read(&xfer); /* read data. */
}

/* write & erase must enable write function. */
static void X25Q_EnableWrite(uint32_t dev_index, bool enable)
{
    X25Q_Xfer_Type xfer;

    switch (X25Q_DeviceTable[dev_index].OperationMode)
    {
        case X25Q_OperationMode_SPI:
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            break;
        case X25Q_OperationMode_DSPI:
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            break;
        case X25Q_OperationMode_QSPI:
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            break;
        case X25Q_OperationMode_QPI:
            xfer.CmdBusWidth  = X25Q_BusWidth_4b;
            break;
        default:
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            break;
    }

    xfer.DeviceIndex  = dev_index;
    xfer.AddrBusWidth = X25Q_BusWidth_None; /* no addr phase. */
    xfer.DummyBytes   = 0u; /* no dummy phase. */
    xfer.DataBusWidth = X25Q_BusWidth_None; /* no data phase. */
    xfer.DataLen      = 0;

    if (true == enable)
    {
        xfer.CmdValue = X25Q_Cmd_EnableWrite; /* enable write. */
    }
    else
    {
        xfer.CmdValue = X25Q_Cmd_DisableWrite; /* disable write. */
    }

    X25Q_HAL_Write(&xfer);
}

/* read register value from device register n, n = reg_index + 1. */
static uint32_t X25Q_ReadRegisterValue(uint32_t dev_index, uint32_t reg_index)
{
    const X25Q_Cmd_Type cmd[3u] = {X25Q_Cmd_ReadRegister1, X25Q_Cmd_ReadRegister2, X25Q_Cmd_ReadRegister3};

    X25Q_Xfer_Type xfer;
    uint8_t buf[1u];

    switch (X25Q_DeviceTable[dev_index].OperationMode)
    {
        case X25Q_OperationMode_SPI: /* xfer by spi. */
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            xfer.DataBusWidth = X25Q_BusWidth_1b;
            break;
        case X25Q_OperationMode_DSPI: /* xfer by spi. */
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            xfer.DataBusWidth = X25Q_BusWidth_1b;
            break;
        case X25Q_OperationMode_QSPI: /* xfer by spi. */
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            xfer.DataBusWidth = X25Q_BusWidth_1b;
            break;
        case X25Q_OperationMode_QPI: /* xfer by qpi. */
            xfer.CmdBusWidth  = X25Q_BusWidth_4b;
            xfer.DataBusWidth = X25Q_BusWidth_4b;
            break;
        default: /* unknow operation mode, using spi mode. */
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            xfer.DataBusWidth = X25Q_BusWidth_1b;
            break;
    }

    xfer.DeviceIndex  = dev_index;
    xfer.CmdValue     = cmd[reg_index];
    xfer.AddrBusWidth = X25Q_BusWidth_None; /* no addr phase. */
    xfer.DummyBytes   = 0u; /* no dummy phase. */
    xfer.DataLen      = sizeof(buf);
    xfer.DataBuf      = buf;

    X25Q_HAL_Read(&xfer);

    return buf[0u];
}

/* write register value to device register n, n = reg_index + 1. */
static void X25Q_WriteRegisterValue(uint32_t dev_index, uint32_t reg_index, uint8_t value)
{
    const X25Q_Cmd_Type cmd[3u] = {X25Q_Cmd_WriteRegister1, X25Q_Cmd_WriteRegister2, X25Q_Cmd_WriteRegister3};

    X25Q_Xfer_Type xfer;

    X25Q_EnableWrite(dev_index, true); /* enable write. */

    switch (X25Q_DeviceTable[dev_index].OperationMode)
    {
        case X25Q_OperationMode_SPI: /* xfer by spi. */
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            xfer.DataBusWidth = X25Q_BusWidth_1b;
            break;
        case X25Q_OperationMode_DSPI: /* xfer by spi. */
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            xfer.DataBusWidth = X25Q_BusWidth_1b;
            break;
        case X25Q_OperationMode_QSPI: /* xfer by spi. */
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            xfer.DataBusWidth = X25Q_BusWidth_1b;
            break;
        case X25Q_OperationMode_QPI: /* xfer by qpi. */
            xfer.CmdBusWidth  = X25Q_BusWidth_4b;
            xfer.DataBusWidth = X25Q_BusWidth_4b;
            break;
        default: /* unknow operation mode, using spi mode. */
            xfer.CmdBusWidth  = X25Q_BusWidth_1b;
            xfer.DataBusWidth = X25Q_BusWidth_1b;
            break;
    }

    xfer.DeviceIndex  = dev_index;
    xfer.CmdValue     = cmd[reg_index];
    xfer.AddrBusWidth = X25Q_BusWidth_None; /* no addr phase. */
    xfer.DummyBytes   = 0u; /* no dummy phase. */
    xfer.DataLen      = 1u;
    xfer.DataBuf      = &value;

    X25Q_HAL_Write(&xfer);


    X25Q_EnableWrite(dev_index, false); /* disable write. */
}

/* wait write or erase done. */
static void X25Q_WaitBusyBlocking(uint32_t dev_index)
{
    while(0u != (X25Q_ReadRegisterValue(dev_index, 0u) & X25Q_REGISTER1_BUSY_MASK) )
    {
    }
}

/* enable qpi mode, all phase bus is 4b. */
static void X25Q_EnableQPI(uint32_t dev_index, bool enable)
{
    X25Q_Xfer_Type xfer;

    if (true == enable) /* xspi -> qpi */
    {
        xfer.CmdBusWidth     = X25Q_BusWidth_1b;
        xfer.CmdValue        = X25Q_Cmd_EnableQpi;
    }
    else /* qpi -> xspi. */
    {
        xfer.CmdBusWidth     = X25Q_BusWidth_4b;
        xfer.CmdValue        = X25Q_Cmd_DisableQpi;
    }

    xfer.DeviceIndex     = dev_index;
    xfer.AddrBusWidth    = X25Q_BusWidth_None; /* no addr. */
    xfer.DummyBytes      = 0u; /* no dummy. */
    xfer.DataBusWidth    = X25Q_BusWidth_None; /* no data. */
    xfer.DataLen        = 0u;

    /* write. */
    X25Q_HAL_Write(&xfer);
}

/* enable QE mode, WP & HOLD be used for IO2 & IO3. */
static void X25Q_EnableQE(uint32_t dev_index, bool enable)
{
    X25Q_Xfer_Type xfer;
    uint8_t buf[1];

    /* enable write by spi mode. */
    xfer.DeviceIndex    = dev_index;
    xfer.CmdBusWidth    = X25Q_BusWidth_1b;
    xfer.CmdValue       = X25Q_Cmd_EnableWrite;
    xfer.AddrBusWidth   = X25Q_BusWidth_None;
    xfer.DummyBytes     = 0u;
    xfer.DataBusWidth   = X25Q_BusWidth_None;
    xfer.DataLen        = 0u;
    X25Q_HAL_Write(&xfer);

    /* read register 2 value by spi mode. */
    xfer.CmdValue       = X25Q_Cmd_ReadRegister2;
    xfer.DataBusWidth   = X25Q_BusWidth_1b;
    xfer.DataLen        = sizeof(buf);
    xfer.DataBuf        = buf;
    X25Q_HAL_Read(&xfer);

    /* set value. */
    if (true == enable)
    {
        buf[0] |=  X25Q_REGISTER2_QE_MASK; /* set QE bit. */
    }
    else
    {
        buf[0] &= ~X25Q_REGISTER2_QE_MASK; /* clear QE bit. */
    }

    /* write register 2 by spi mode. */
    xfer.CmdValue       = X25Q_Cmd_WriteRegister2;
    X25Q_HAL_Write(&xfer);

    /* wait for write done. */
    xfer.CmdValue       = X25Q_Cmd_ReadRegister1;
    buf[0] = X25Q_REGISTER1_BUSY_MASK;
    while(0 != (buf[0] & X25Q_REGISTER1_BUSY_MASK) )
    {
        X25Q_HAL_Read(&xfer);
    }

    /* disable write. */
    xfer.CmdValue       = X25Q_Cmd_DisableWrite;
    xfer.DataBusWidth   = X25Q_BusWidth_None;
    xfer.DataLen        = 0u;
    X25Q_HAL_Write(&xfer);
}

/* EOF. */
