#include "w25q64.h"

#if defined(USE_ST_HAL)
static void spi_init(void)
{
    // it will be called in main.
    // MX_SPIx_Init();
}

static void spi_cs_select(void)
{
    HAL_GPIO_WritePin(SPI_CS_PORT, SPI_CS_PIN, GPIO_PIN_RESET);
    
    /* tCHSH: /CS Active Hold Time relative to CLK, min is 5 ns. */
    for (int i = 0; i < 50; i++) {
        __NOP();
    }
}

static void spi_cs_deselect(void)
{
    HAL_GPIO_WritePin(SPI_CS_PORT, SPI_CS_PIN, GPIO_PIN_SET);
    
    /* tSHSL: /CS Deselect Time (for Array Read -> Array Read ->
        -> Erase or Program -> Read Status Registers), min is 10 ns. */
    for (int i = 0; i < 100; i++) {
        __NOP();
    }
}

static int spi_transmit(uint8_t *buf, uint16_t size)
{
   HAL_StatusTypeDef status;

   status = HAL_SPI_Transmit(&SPI_Handle, buf, size, 100);

   return status == HAL_OK ? 0 : -1;
}

static int spi_receive(uint8_t *buf, uint16_t size)
{
   HAL_StatusTypeDef status;

   status = HAL_SPI_Receive(&SPI_Handle, buf, size, 100);

   return status == HAL_OK ? 0 : -1;
}

#elif defined(USE_NXP_FSL)
static void spi_init(void)
{
    lpspi_master_config_t masterConfig;
    uint32_t srcClock_Hz;
    
    /* Set clock source for LPSPI */
    CLOCK_SetMux(kCLOCK_LpspiMux, LPSPI_CLOCK_SOURCE_SELECT);
    CLOCK_SetDiv(kCLOCK_LpspiDiv, LPSPI_CLOCK_SOURCE_DIVIDER);
    
    /* Master config */
    LPSPI_MasterGetDefaultConfig(&masterConfig);
    masterConfig.baudRate = TRANSFER_BAUDRATE;

    srcClock_Hz = LPSPI_MASTER_CLK_FREQ;
    LPSPI_MasterInit(SPI_Handle, &masterConfig, srcClock_Hz);
}

static void spi_cs_select(void)
{
    GPIO_PinWrite(SPI_CS_PORT, SPI_CS_PIN, 0);
    
    /* tCHSH: /CS Active Hold Time relative to CLK, min is 5 ns. */
    for (int i = 0; i < 500; i++) {
        __NOP();
    }
}

static void spi_cs_deselect(void)
{
    GPIO_PinWrite(SPI_CS_PORT, SPI_CS_PIN, 1);
    
    /* tSHSL: /CS Deselect Time (for Array Read -> Array Read ->
        -> Erase or Program -> Read Status Registers), min is 10 ns. */
    for (int i = 0; i < 1000; i++) {
        __NOP();
    }
}

static int spi_transmit(uint8_t *buf, uint16_t size)
{
    status_t status;
    lpspi_transfer_t masterXfer;
    
    /* Start master transfer, transfer data to slave. */
    masterXfer.txData   = buf;
    masterXfer.rxData   = NULL;
    masterXfer.dataSize = size;
    masterXfer.configFlags = kLPSPI_MasterByteSwap;

    status = LPSPI_MasterTransferBlocking(SPI_Handle, &masterXfer);
    
    return status == kStatus_Success ? 0 : -1;
}

static int spi_receive(uint8_t *buf, uint16_t size)
{
    status_t status;
    lpspi_transfer_t masterXfer;
    
    /* Start master transfer, receive data from slave */
    masterXfer.txData   = NULL;
    masterXfer.rxData   = buf;
    masterXfer.dataSize = size;
    masterXfer.configFlags = kLPSPI_MasterByteSwap;

    status = LPSPI_MasterTransferBlocking(SPI_Handle, &masterXfer);
    
    return status == kStatus_Success ? 0 : -1;
}
#endif /* USE_ST_HAL or USE_NXP_FSL */

int w25qxx_init(void)
{
    spi_init();
    
    return 0;
}

uint16_t w25qxx_read_deviceid(void)
{
    uint8_t recv_buf[2] = {0};
    uint16_t device_id = 0;
    uint8_t send_data[4] = {ManufactDeviceID_CMD, 0x00, 0x00, 0x00};

    spi_cs_select();
    if (spi_transmit(send_data, 4) == 0) {
        if (spi_receive(recv_buf, 2) == 0) {
            device_id = (recv_buf[0] << 8) | recv_buf[1];
        }
    }
    spi_cs_deselect();
    
    return device_id;
}

static void w25qxx_wait_busy(void)
{
    uint8_t cmd;
    uint8_t result;
   
    cmd = READ_STATU_REGISTER_1;
  
    spi_cs_select();
    spi_transmit(&cmd, 1);
    while (1) {
        spi_receive(&result, 1);
        if ((result & 0x01) != 0x01) {
            break;
        }
    }
    spi_cs_deselect();
    
    return;
}

int w25qxx_read(uint8_t* buffer, uint32_t start_addr, uint16_t nbytes)
{
    uint8_t cmd[4];

    cmd[0] = READ_DATA_CMD;
    cmd[1] = (uint8_t)(start_addr >> 16);
    cmd[2] = (uint8_t)(start_addr >> 8);
    cmd[3] = (uint8_t)(start_addr);
    
    spi_cs_select();
    if (spi_transmit(cmd, 4) == 0) {
        if (spi_receive(buffer, nbytes) == 0) {
            spi_cs_deselect();
            return 0;
        }
    }
    spi_cs_deselect();
    
    return -1;
}

void w25qxx_write_enable(void)
{
    uint8_t cmd = WRITE_ENABLE_CMD;
    
    spi_cs_select();
    spi_transmit(&cmd, 1);
    spi_cs_deselect();
}

void w25qxx_write_disable(void)
{
    uint8_t cmd = WRITE_DISABLE_CMD;
    
    spi_cs_select();
    spi_transmit(&cmd, 1);
    spi_cs_deselect();
}

int w25qxx_erase_sector(uint32_t sector_addr)
{
    uint8_t cmd[4];

    cmd[0] = SECTOR_ERASE_CMD;
    cmd[1] = (uint8_t)(sector_addr>>16);
    cmd[2] = (uint8_t)(sector_addr>>8);
    cmd[3] = (uint8_t)(sector_addr);
    
    w25qxx_wait_busy();
    
    w25qxx_write_enable();
   
    spi_cs_select();
    if (spi_transmit(cmd, 4) != 0) {
        spi_cs_deselect();
        return -1;
    }
    spi_cs_deselect();
    
    w25qxx_wait_busy();
    
    return 0;
}

int w25qxx_page_program(uint8_t* dat, uint32_t write_addr, uint16_t nbytes)
{
    uint8_t cmd[4];
    
    cmd[0] = PAGE_PROGRAM_CMD;
    cmd[1] = (uint8_t)(write_addr >> 16);
    cmd[2] = (uint8_t)(write_addr >> 8);
    cmd[3] = (uint8_t)(write_addr);
    
    w25qxx_wait_busy();
    
    w25qxx_write_enable();

    spi_cs_select();
    if (spi_transmit(cmd, 4) != 0) {
        spi_cs_deselect();
        return -1;
    }
    if (spi_transmit(dat, nbytes) != 0) {
        spi_cs_deselect();
        return -1;
    }
    
    spi_cs_deselect();
    
    w25qxx_wait_busy();

    return 0;
}
