#include "qspi_flash.h"
#include "stdio.h"

QSPI_HandleTypeDef QSPIHandle;

/**
  * @brief  This function polling for release busy status.
  * @param  hqspi: QSPI handle
  * @retval error code
  */
static uint32_t QSPI_WaitforBusyRelease(QSPI_HandleTypeDef *hqspi)
{
    QSPI_CommandTypeDef     s_command;
    QSPI_AutoPollingTypeDef s_config;

    /* Configure automatic polling mode to wait for write enabling */
    s_config.Match           = 0x0;
    s_config.Mask            = (1 << 0); // BUSY
    s_config.MatchMode       = QSPI_MATCH_MODE_AND;
    s_config.StatusBytesSize = 1;
    s_config.Interval        = 0x10;
    s_config.AutomaticStop   = QSPI_AUTOMATIC_STOP_ENABLE;

    s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    s_command.Instruction       = READ_STATUS_REG_CMD;
    s_command.AddressMode       = QSPI_ADDRESS_NONE;
    s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    s_command.DataMode          = QSPI_DATA_1_LINE;
    s_command.DummyCycles       = 0;
    s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
    s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

    if (HAL_QSPI_AutoPolling(hqspi, &s_command, &s_config, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
        printf("%s: function %s, line %d, command \"qspi auto polling for release busy\" failed.\r\n", __FILE__, __FUNCTION__, __LINE__);
        return ((2 << 28) | s_config.Mask);
    }

    return 0;
}

/**
  * @brief  This function send a Write Enable and wait it is effective.
  * @param  hqspi: QSPI handle
  * @retval None
  */
static uint32_t QSPI_WriteEnable(QSPI_HandleTypeDef *hqspi)
{
    QSPI_CommandTypeDef     s_command;
    QSPI_AutoPollingTypeDef s_config;

    /* Enable write operations */
    s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    s_command.Instruction       = WRITE_ENABLE_CMD;
    s_command.AddressMode       = QSPI_ADDRESS_NONE;
    s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    s_command.DataMode          = QSPI_DATA_NONE;
    s_command.DummyCycles       = 0;
    s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
    s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

    if (HAL_QSPI_Command(hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
        printf("%s: function %s, line %d, command \"write enable\" failed.\r\n", __FILE__, __FUNCTION__, __LINE__);
        return ((1 << 28) | s_command.Instruction);
    }

    /* Configure automatic polling mode to wait for write enabling */
    s_config.Match           = 0x2;
    s_config.Mask            = (1 << 1) | (1 << 0); // WEL | BUSY
    s_config.MatchMode       = QSPI_MATCH_MODE_AND;
    s_config.StatusBytesSize = 1;
    s_config.Interval        = 0x10;
    s_config.AutomaticStop   = QSPI_AUTOMATIC_STOP_ENABLE;

    s_command.Instruction    = READ_STATUS_REG_CMD;
    s_command.DataMode       = QSPI_DATA_1_LINE;

    if (HAL_QSPI_AutoPolling(hqspi, &s_command, &s_config, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
        printf("%s: function %s, line %d, command \"qspi auto polling for write enable\" failed.\r\n", __FILE__, __FUNCTION__, __LINE__);
        return ((2 << 28) | s_config.Mask);
    }
    return 0;
}

void qspi_flash_init(uint32_t div)
{
    GPIO_InitTypeDef gpio_init_structure;

    /*##-1- Enable peripherals and GPIO Clocks #################################*/
    /* Enable the QuadSPI memory interface clock */
    QSPI_CLK_ENABLE();
    /* Reset the QuadSPI memory interface */
    QSPI_FORCE_RESET();
    QSPI_RELEASE_RESET();
    /* Enable GPIO clocks */
    QSPI_CLK_GPIO_CLK_ENABLE();
    QSPI_BK1_CS_GPIO_CLK_ENABLE();
    QSPI_BK1_D0_GPIO_CLK_ENABLE();
    QSPI_BK1_D1_GPIO_CLK_ENABLE();
    QSPI_BK1_D2_GPIO_CLK_ENABLE();
    QSPI_BK1_D3_GPIO_CLK_ENABLE();

    /* Enable DMA clock */
    QSPI_MDMA_CLK_ENABLE();

    /*##-2- Configure peripheral GPIO ##########################################*/
    /* QSPI CLK GPIO pin configuration  */
    gpio_init_structure.Pin       = QSPI_CLK_PIN;
    gpio_init_structure.Mode      = GPIO_MODE_AF_PP;
    gpio_init_structure.Speed     = GPIO_SPEED_FREQ_VERY_HIGH;
    gpio_init_structure.Pull      = GPIO_NOPULL;
    gpio_init_structure.Alternate = GPIO_AF9_QUADSPI;
    HAL_GPIO_Init(QSPI_CLK_GPIO_PORT, &gpio_init_structure);

    /* QSPI CS GPIO pin configuration  */
    gpio_init_structure.Pin       = QSPI_BK1_CS_PIN;
    gpio_init_structure.Pull      = GPIO_PULLUP;
    gpio_init_structure.Alternate = GPIO_AF10_QUADSPI;
    HAL_GPIO_Init(QSPI_BK1_CS_GPIO_PORT, &gpio_init_structure);

    /* QSPI D0 GPIO pin configuration  */
    gpio_init_structure.Pin       = QSPI_BK1_D0_PIN;
    gpio_init_structure.Pull      = GPIO_NOPULL;
    gpio_init_structure.Alternate = GPIO_AF9_QUADSPI;
    HAL_GPIO_Init(QSPI_BK1_D0_GPIO_PORT, &gpio_init_structure);

    /* QSPI D1 GPIO pin configuration  */
    gpio_init_structure.Pin       = QSPI_BK1_D1_PIN;
    gpio_init_structure.Pull      = GPIO_NOPULL;
    gpio_init_structure.Alternate = GPIO_AF9_QUADSPI;
    HAL_GPIO_Init(QSPI_BK1_D1_GPIO_PORT, &gpio_init_structure);

    /* QSPI D2 GPIO pin configuration  */
    gpio_init_structure.Pin       = QSPI_BK1_D2_PIN;
    gpio_init_structure.Pull      = GPIO_NOPULL;
    gpio_init_structure.Alternate = GPIO_AF9_QUADSPI;
    HAL_GPIO_Init(QSPI_BK1_D2_GPIO_PORT, &gpio_init_structure);

    /* QSPI D3 GPIO pin configuration  */
    gpio_init_structure.Pin       = QSPI_BK1_D3_PIN;
    gpio_init_structure.Pull      = GPIO_NOPULL;
    gpio_init_structure.Alternate = GPIO_AF9_QUADSPI;
    HAL_GPIO_Init(QSPI_BK1_D3_GPIO_PORT, &gpio_init_structure);

    /*##-3- Configure the NVIC for QSPI #########################################*/
    /* NVIC configuration for QSPI interrupt */
    // HAL_NVIC_SetPriority(QUADSPI_IRQn, 0x0F, 0);
    // HAL_NVIC_EnableIRQ(QUADSPI_IRQn);

    /* Initialize QuadSPI structures ------------------------------------------- */
    QSPIHandle.Instance = QUADSPI;
    /* QSPI initialization */
    /* ClockPrescaler set to 1, so QSPI clock = 200MHz / (1+1) = 100MHz */
    QSPIHandle.Init.ClockPrescaler     = div;
    QSPIHandle.Init.FifoThreshold      = 32;
    QSPIHandle.Init.SampleShifting     = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
    QSPIHandle.Init.FlashSize          = QSPI_FLASH_SIZE;
    QSPIHandle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_3_CYCLE;
    QSPIHandle.Init.ClockMode          = QSPI_CLOCK_MODE_0;
    QSPIHandle.Init.FlashID            = QSPI_FLASH_ID_1;
    QSPIHandle.Init.DualFlash          = QSPI_DUALFLASH_DISABLE;

    /* Initialize QuadSPI ------------------------------------------------ */
    HAL_QSPI_DeInit(&QSPIHandle);
    if (HAL_QSPI_Init(&QSPIHandle) != HAL_OK) {
        printf("%s: function %s, line %d, qspi init failed.\r\n", __FILE__, __FUNCTION__, __LINE__);
        while (1);
    } else {
        printf("qspi init succeed.\r\n");
    }
}

void qspi_flash_deinit(void)
{
    /*##-1- Disable the NVIC for QSPI ###########################################*/
    HAL_NVIC_DisableIRQ(QUADSPI_IRQn);

    /*##-1- Disable peripherals ################################################*/
    /* De-Configure QSPI pins */
    HAL_GPIO_DeInit(QSPI_CLK_GPIO_PORT, QSPI_CLK_PIN);
    HAL_GPIO_DeInit(QSPI_BK1_CS_GPIO_PORT, QSPI_BK1_CS_PIN);
    HAL_GPIO_DeInit(QSPI_BK1_D0_GPIO_PORT, QSPI_BK1_D0_PIN);
    HAL_GPIO_DeInit(QSPI_BK1_D1_GPIO_PORT, QSPI_BK1_D1_PIN);
    HAL_GPIO_DeInit(QSPI_BK1_D2_GPIO_PORT, QSPI_BK1_D2_PIN);
    HAL_GPIO_DeInit(QSPI_BK1_D3_GPIO_PORT, QSPI_BK1_D3_PIN);

    /*##-3- Reset peripherals ##################################################*/
    /* Reset the QuadSPI memory interface */
    QSPI_FORCE_RESET();
    QSPI_RELEASE_RESET();

    /* Disable the QuadSPI memory interface clock */
    QSPI_CLK_DISABLE();
}

uint32_t qspi_flash_write(uint32_t addr, uint8_t *data, uint32_t length)
{
    uint32_t ret;
    QSPI_CommandTypeDef s_command;

    ret = QSPI_WriteEnable(&QSPIHandle);
    if (ret) {
        return ret;
    }

    /* Initialize the read volatile configuration register command */
    s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    s_command.Instruction       = QUAD_IN_FAST_PROG_CMD;
    s_command.AddressMode       = QSPI_ADDRESS_1_LINE;
    s_command.AddressSize       = QSPI_ADDRESS_24_BITS;
    s_command.Address           = addr;
    s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    s_command.DataMode          = QSPI_DATA_4_LINES;
    s_command.DummyCycles       = 0;
    s_command.NbData            = length;
    s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
    s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

    /* Configure the command */
    if (HAL_QSPI_Command(&QSPIHandle, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
        printf("%s: function %s, line %d, command \"QUAD_IN_FAST_PROG_CMD\" failed.\r\n", __FILE__, __FUNCTION__, __LINE__);
        return ((1 << 28) | s_command.Instruction);
    }

    /* Writing of the data */
    if (HAL_QSPI_Transmit(&QSPIHandle, data, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
        printf("%s: function %s, line %d, command \"HAL_QSPI_Transmit\" failed.\r\n", __FILE__, __FUNCTION__, __LINE__);
        return ((3 << 28) | s_command.Instruction);
    }

    ret = QSPI_WaitforBusyRelease(&QSPIHandle);
    if (ret) {
        printf("%s: function %s, line %d, command \"busy after write\" failed.\r\n", __FILE__, __FUNCTION__, __LINE__);
        return ret;
    }

    return 0;
}

uint32_t qspi_flash_read(uint32_t addr, uint8_t *data, uint32_t length)
{
    QSPI_CommandTypeDef s_command;

    /* Initialize the read volatile configuration register command */
    s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    s_command.Instruction       = QUAD_OUT_FAST_READ_CMD;
    s_command.AddressMode       = QSPI_ADDRESS_1_LINE;
    s_command.AddressSize       = QSPI_ADDRESS_24_BITS;
    s_command.Address           = addr;
    s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    s_command.DataMode          = QSPI_DATA_4_LINES;
    s_command.DummyCycles       = 8;
    s_command.NbData            = length;
    s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
    s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

    /* Configure the command */
    if (HAL_QSPI_Command(&QSPIHandle, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
        printf("%s: function %s, line %d, command \"QUAD_OUT_FAST_READ_CMD\" failed.\r\n", __FILE__, __FUNCTION__, __LINE__);
        return ((1 << 28) | s_command.Instruction);
    }

    /* Reception of the data */
    if (HAL_QSPI_Receive(&QSPIHandle, data, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
        printf("%s: function %s, line %d, command \"HAL_QSPI_Receive\" failed.\r\n", __FILE__, __FUNCTION__, __LINE__);
        return ((3 << 28) | s_command.Instruction);
    }

    return 0;
}

uint32_t qspi_flash_erase_4kbytes(uint32_t addr)
{
    uint32_t ret;
    QSPI_CommandTypeDef s_command;

    ret = QSPI_WriteEnable(&QSPIHandle);
    if (ret) {
        return ret;
    }

    /* Initialize the read volatile configuration register command */
    s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    s_command.Instruction       = SECTOR_ERASE_CMD;
    s_command.AddressMode       = QSPI_ADDRESS_1_LINE;
    s_command.AddressSize       = QSPI_ADDRESS_24_BITS;
    s_command.Address           = addr;
    s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    s_command.DataMode          = QSPI_DATA_NONE;
    s_command.DummyCycles       = 0;
    s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
    s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

    /* Configure the command */
    if (HAL_QSPI_Command(&QSPIHandle, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
        printf("%s: function %s, line %d, command \"SECTOR_ERASE_CMD\" failed.\r\n", __FILE__, __FUNCTION__, __LINE__);
        return ((1 << 28) | s_command.Instruction);
    }

    ret = QSPI_WaitforBusyRelease(&QSPIHandle);
    if (ret) {
        printf("%s: function %s, line %d, command \"busy after erasen 4K\" failed.\r\n", __FILE__, __FUNCTION__, __LINE__);
        return ret;
    }

    return 0;
}

uint32_t qspi_flash_erase_32kbytes(uint32_t addr)
{
    uint32_t ret;
    QSPI_CommandTypeDef s_command;

    ret = QSPI_WriteEnable(&QSPIHandle);
    if (ret) {
        return ret;
    }

    /* Initialize the read volatile configuration register command */
    s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    s_command.Instruction       = SUBBLOCK_ERASE_CMD;
    s_command.AddressMode       = QSPI_ADDRESS_1_LINE;
    s_command.AddressSize       = QSPI_ADDRESS_24_BITS;
    s_command.Address           = addr;
    s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    s_command.DataMode          = QSPI_DATA_NONE;
    s_command.DummyCycles       = 0;
    s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
    s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

    /* Configure the command */
    if (HAL_QSPI_Command(&QSPIHandle, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
        printf("%s: function %s, line %d, command \"SUBBLOCK_ERASE_CMD\" failed.\r\n", __FILE__, __FUNCTION__, __LINE__);
        return ((1 << 28) | s_command.Instruction);
    }

    ret = QSPI_WaitforBusyRelease(&QSPIHandle);
    if (ret) {
        printf("%s: function %s, line %d, command \"busy after erasen 32K\" failed.\r\n", __FILE__, __FUNCTION__, __LINE__);
        return ret;
    }

    return 0;
}

uint32_t qspi_flash_erase_64kbytes(uint32_t addr)
{
    uint32_t ret;
    QSPI_CommandTypeDef s_command;

    ret = QSPI_WriteEnable(&QSPIHandle);
    if (ret) {
        return ret;
    }

    /* Initialize the read volatile configuration register command */
    s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    s_command.Instruction       = BLOCK_ERASE_CMD;
    s_command.AddressMode       = QSPI_ADDRESS_1_LINE;
    s_command.AddressSize       = QSPI_ADDRESS_24_BITS;
    s_command.Address           = addr;
    s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    s_command.DataMode          = QSPI_DATA_NONE;
    s_command.DummyCycles       = 0;
    s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
    s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

    /* Configure the command */
    if (HAL_QSPI_Command(&QSPIHandle, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
        printf("%s: function %s, line %d, command \"SUBBLOCK_ERASE_CMD\" failed.\r\n", __FILE__, __FUNCTION__, __LINE__);
        return ((1 << 28) | s_command.Instruction);
    }

    ret = QSPI_WaitforBusyRelease(&QSPIHandle);
    if (ret) {
        printf("%s: function %s, line %d, command \"busy after erasen 64K\" failed.\r\n", __FILE__, __FUNCTION__, __LINE__);
        return ret;
    }

    return 0;
}
