#include "omw_spis.h"

#define SR_TX_NOTFIN            0x1
#define SR_TRANS_EMPTY          0x4
#define SR_RCV_NOTEMPTY         0x8

// SPI
#define CTRLR0               0x00  //*(volatile int *)(OMW_SPIS_BASE + 0x00)
#define SSIENR               0x02  //*(volatile int *)(OMW_SPIS_BASE + 0x08)
#define BAUDR                0x05  //*(volatile int *)(OMW_SPIS_BASE + 0x14)
#define TXFLR                0x08  //*(volatile int *)(OMW_SPIS_BASE + 0x20)
#define RXFLR                0x09  //*(volatile int *)(OMW_SPIS_BASE + 0x24)
#define SR                   0x0A  //*(volatile int *)(OMW_SPIS_BASE + 0x28)
#define IMR                  0x0B  //*(volatile int *)(OMW_SPIS_BASE + 0x2C)
#define DR                   0x18  //*(volatile int *)(OMW_SPIS_BASE + 0x60)

#define SPI_DIR_TX        0x01
#define SPI_DIR_RX        0x02
#define SPI_DIR_BIT_POS   8
#define SPI_DIR_MASK      (3 << SPI_DIR_BIT_POS)

#define SPI_SLV_OE_BIT_POS    10
#define SPI_SLV_OE_MASK       (1 << SPI_SLV_OE_BIT_POS)
#define SPI_SLV_OE            1

#define SPI_TX_THR        8

#define SPI_SSIENR_VAL_HIGH   0x10

#define MAX_SPIS_PIN_NUM     4

#ifdef OMW_SPIS
const struct gpio_cfg_tag spis_pin_cfg[] = {
    {OMW_SPIS_PIN_NCS,  OMW_SPIS_FUN_CFG_NCS},
    {OMW_SPIS_PIN_SCK,  OMW_SPIS_FUN_CFG_SCK},
    {OMW_SPIS_PIN_MISO, OMW_SPIS_FUN_CFG_MISO},
    {OMW_SPIS_PIN_MOSI, OMW_SPIS_FUN_CFG_MOSI}
};

uint8_t g_spis_line_mode;  //0 3lines, 1 4lines
#endif

static uint32_t is_spis_ncs_high(uint32_t spis_base_addr)
{
    uint32_t val =  GPIO_INOUT->GPIO_I;
    const struct gpio_cfg_tag  * pin_cfg;
    #ifdef OMW_SPIS
    pin_cfg = spis_pin_cfg;
	#else
	pin_cfg = NULL;
    #endif
    ////TODO: if more than one SPIS, need to cmp spis base address, and get pin cfg info

    return (val >> pin_cfg[0].id) & 0x1;
}

static void spis_init(uint32_t spis_base_addr)
{
    volatile uint32_t * spi_cfg_addr = (volatile uint32_t *)spis_base_addr;
    uint8_t  lm = OMW_SPIS_4LINES;
    #ifdef OMW_SPIS
    lm = g_spis_line_mode;
    #endif
    ////TODO: if more than one SPIS, need to cmp spis base address, and get line mode

    //make spi device in idle state before starting to config
    spi_cfg_addr[SSIENR] = 0x0;   //disable
    spi_cfg_addr[CTRLR0] = (((SPI_DIR_RX << SPI_DIR_BIT_POS) | (SPI_SLV_OE << SPI_SLV_OE_BIT_POS)) | 0x7); //default: OE, RX
    spi_cfg_addr[IMR]    = 0x0;   //INT disable

    if (lm == OMW_SPIS_3LINES)
        spi_cfg_addr[SSIENR] = 0x07 | SPI_SSIENR_VAL_HIGH;  //enable
    else
        spi_cfg_addr[SSIENR] = 0x05;
}

void omw_spis_init(uint32_t spis_base_addr, uint8_t line_mode)
{
    const struct gpio_cfg_tag  * pin_cfg = NULL;
    #ifdef OMW_SPIS
    pin_cfg = spis_pin_cfg;
    g_spis_line_mode = line_mode;
    #endif
    ////TODO: if more than one SPIS, need to cmp spis base address, and get pin cfg info, and line mode info

    spis_init(spis_base_addr);
    omw_gpio_set_func_ex(pin_cfg, MAX_SPIS_PIN_NUM);
}

static void spis_tx_sw(uint32_t spis_base_addr, uint8_t first_byte)
{
    volatile uint32_t * spi_cfg_addr = (volatile uint32_t *)spis_base_addr;
    uint8_t  lm = OMW_SPIS_4LINES;
    #ifdef OMW_SPIS
    lm = g_spis_line_mode;
    #endif
    ////TODO: if more than one SPIS, need to cmp spis base address, and get line mode

    //make spi device in idle state before starting to config
    if (lm == OMW_SPIS_3LINES)
        spi_cfg_addr[SSIENR] = 0x2 | SPI_SSIENR_VAL_HIGH;  //b'010, fifo force en = 0, 3 lines, disable
    else
        spi_cfg_addr[SSIENR] = 0;

    spi_cfg_addr[CTRLR0] = ((SPI_DIR_TX << SPI_DIR_BIT_POS) | 0x7); //[9:8] 1 tx, 2 rx

    if (lm == OMW_SPIS_3LINES)
        spi_cfg_addr[SSIENR] = 0x03 | SPI_SSIENR_VAL_HIGH;  // b'011, fifo force en = 0, 3 lines, enable
    else
        spi_cfg_addr[SSIENR] = 0x01;

    if (lm == OMW_SPIS_3LINES)
        while(!is_spis_ncs_high(spis_base_addr));

    spi_cfg_addr[DR]  = first_byte;
}

static void spis_rx_sw(uint32_t spis_base_addr)
{
    volatile uint32_t * spi_cfg_addr = (volatile uint32_t *)spis_base_addr;
    uint8_t  lm = OMW_SPIS_4LINES;
    #ifdef OMW_SPIS
    lm = g_spis_line_mode;
    #endif
    ////TODO: if more than one SPIS, need to cmp spis base address, and get line mode

    //make spi device in idle state before starting to config
    if (lm == OMW_SPIS_3LINES)
        spi_cfg_addr[SSIENR] = 0x2 | SPI_SSIENR_VAL_HIGH;  //b'010, fifo force en=0, 3 lines, disable
    else
        spi_cfg_addr[SSIENR] = 0;

    spi_cfg_addr[CTRLR0] = (((SPI_DIR_RX << SPI_DIR_BIT_POS) | (SPI_SLV_OE << SPI_SLV_OE_BIT_POS)) | 0x7); //[9:8] 1 tx, 2 rx, [10] slv_oe

    if (lm == OMW_SPIS_3LINES)
        spi_cfg_addr[SSIENR] = 0x3 | SPI_SSIENR_VAL_HIGH;  // //b'011, fifo force en = 0, 3 lines, enable
    else
        spi_cfg_addr[SSIENR] = 0x01;
}

void omw_spis_send(uint32_t spis_base_addr, void * pdata, uint32_t len)
{
    uint8_t  *ptmp = pdata;
    uint32_t pos = 0;

    volatile uint32_t * spi_cfg_addr = (volatile uint32_t *)spis_base_addr;

    spis_tx_sw(spis_base_addr, ptmp[pos++]);

    while (pos < len)
    {
        while (spi_cfg_addr[TXFLR] > SPI_TX_THR);

        spi_cfg_addr[DR] = ptmp[pos++];
    }

    while(spi_cfg_addr[SR] & SR_TX_NOTFIN || (spi_cfg_addr[SR] & SR_TRANS_EMPTY) == 0);

    spis_rx_sw(spis_base_addr);
}

uint32_t omw_spis_rcv(uint32_t spis_base_addr, void * pbuf, uint32_t blen)
{
    uint32_t pos   = 0;
    uint8_t  *ptmp = pbuf;

    volatile uint32_t * spi_cfg_addr = (volatile uint32_t *)spis_base_addr;
    uint8_t  lm = OMW_SPIS_4LINES;
    #ifdef OMW_SPIS
    lm = g_spis_line_mode;
    #endif
    ////TODO: if more than one SPIS, need to cmp spis base address, and get line mode

    while ((pos < blen) && (((lm == OMW_SPIS_3LINES) && (spi_cfg_addr[SR] & SR_RCV_NOTEMPTY))
                            || ((lm == OMW_SPIS_4LINES) && spi_cfg_addr[RXFLR])))
    {
        ptmp[pos++] = spi_cfg_addr[DR];
    }

    return pos;
}
