/* ----------------------------------------------------------------------------
 * Copyright (c) Huawei Technologies Co., Ltd. 2013-2022. All rights reserved.
 * Description: Hal Qspi Flash
 * Author: Huawei LiteOS Team
 * Create: 2013-01-01
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright notice, this list of
 * conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
 * of conditions and the following disclaimer in the documentation and/or other materials
 * provided with the distribution.
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
 * to endorse or promote products derived from this software without specific prior written
 * permission.
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * --------------------------------------------------------------------------- */

#include "hal_qspi_flash.h"
#include "stm32l4xx.h"
#include "sys_init.h"

#ifdef HAL_QSPI_MODULE_ENABLED

#define CHECK_RET_RETURN(ret) \
    do {                      \
        if ((ret) < 0) {      \
            return ret;       \
        }                     \
    } while (0)

QSPI_HandleTypeDef g_hqspi;

/* This function is called by inner-HAL lib */
void HAL_QSPI_MspInit(QSPI_HandleTypeDef *qspiHandle)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    if (qspiHandle->Instance == QUADSPI) {
        /* USER CODE BEGIN QUADSPI_MspInit 0 */

        /* USER CODE END QUADSPI_MspInit 0 */
        /* QUADSPI clock enable */
        __HAL_RCC_QSPI_CLK_ENABLE();

        /**
        QUADSPI GPIO Configuration
        PB0     ------> QUADSPI_BK1_IO1
        PB1     ------> QUADSPI_BK1_IO0
        PB10     ------> QUADSPI_CLK
        PB11     ------> QUADSPI_BK1_NCS
        */
        GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_10 | GPIO_PIN_11;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
        GPIO_InitStruct.Alternate = GPIO_AF10_QUADSPI;
        HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

        /* USER CODE BEGIN QUADSPI_MspInit 1 */

        /* USER CODE END QUADSPI_MspInit 1 */
    }
}

/* This function is called by inner-HAL lib */
void HAL_QSPI_MspDeInit(QSPI_HandleTypeDef *qspiHandle)
{
    if (qspiHandle->Instance == QUADSPI) {
        /* USER CODE BEGIN QUADSPI_MspDeInit 0 */
        /* USER CODE END QUADSPI_MspDeInit 0 */
        /* Peripheral clock disable */
        __HAL_RCC_QSPI_CLK_DISABLE();

        /**
        QUADSPI GPIO Configuration
        PB0     ------> QUADSPI_BK1_IO1
        PB1     ------> QUADSPI_BK1_IO0
        PB10     ------> QUADSPI_CLK
        PB11     ------> QUADSPI_BK1_NCS
        */
        HAL_GPIO_DeInit(GPIOB, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_10 | GPIO_PIN_11);

        /* USER CODE BEGIN QUADSPI_MspDeInit 1 */

        /* USER CODE END QUADSPI_MspDeInit 1 */
    }
}
uint32_t QSPI_Send_CMD(uint32_t instruction, uint32_t address, uint32_t dummyCycles, uint32_t instructionMode,
                       uint32_t addressMode, uint32_t addressSize, uint32_t dataMode)
{
    QSPI_CommandTypeDef command;

    command.Instruction = instruction;
    command.Address = address;
    command.DummyCycles = dummyCycles;
    command.InstructionMode = instructionMode;
    command.AddressMode = addressMode;
    command.AddressSize = addressSize;
    command.DataMode = dataMode;
    command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
    command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    command.DdrMode = QSPI_DDR_MODE_DISABLE;
    command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;

    return HAL_QSPI_Command(&g_hqspi, &command, 5000);
}

uint8_t QSPI_Receive(const uint8_t *buf, uint32_t datalen)
{
    g_hqspi.Instance->DLR = datalen - 1; // Configuration data length
    if (HAL_QSPI_Receive(&g_hqspi, (uint8_t *)buf, 5000) == HAL_OK) {
        return 0; // receive data
    } else {
        return 1;
    }
}

uint8_t QSPI_Transmit(const int8_t *buf, uint32_t datalen)
{
    g_hqspi.Instance->DLR = datalen - 1; // Configuration data length
    if (HAL_QSPI_Transmit(&g_hqspi, (uint8_t *)buf, 5000) == HAL_OK) {
        return 0; // send data
    } else {
        return 1;
    }
}

static void prv_spi_flash_write_enable(void)
{
    QSPI_Send_CMD(QSPI_FLASH_WriteEnable, 0, 0, QSPI_INSTRUCTION_1_LINE,
                  QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE);
}

static void prv_spi_flash_wait_write_end(void)
{
    uint8_t status = 0;


    /* Loop as long as the memory is busy with a write cycle */
    do {
        /* Send a dummy byte to generate the clock needed by the FLASH
        and put the value of the status register in status variable */
        QSPI_Send_CMD(QSPI_FLASH_ReadStatusReg, 0, 0, QSPI_INSTRUCTION_1_LINE,
                      QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_1_LINE);
        QSPI_Receive(&status, 1);
    } while ((status & QSPI_FLASH_WIP_FLAG) == SET); /* Write in progress */
}

static int prv_spi_flash_write_page(const uint8_t *buf, uint32_t addr, int32_t len)
{
    int ret = 0;

    if (len == 0) {
        return 0;
    }

    prv_spi_flash_write_enable(); // Write enable

    QSPI_Send_CMD(QSPI_FLASH_PageProgram, addr, 0, QSPI_INSTRUCTION_1_LINE,
                  QSPI_ADDRESS_1_LINE, QSPI_ADDRESS_24_BITS, QSPI_DATA_1_LINE);
    QSPI_Transmit((const int8_t *)buf, len);

    prv_spi_flash_wait_write_end(); // Waiting for Writing to End

    return ret;
}

int prv_spi_flash_erase_sector(uint32_t addr)
{
    int ret = 0;
    prv_spi_flash_write_enable(); // Write enable
    prv_spi_flash_wait_write_end();

    ret = QSPI_Send_CMD(QSPI_FLASH_SectorErase, addr, 0, QSPI_INSTRUCTION_1_LINE,
                        QSPI_ADDRESS_1_LINE, QSPI_ADDRESS_24_BITS, QSPI_DATA_NONE);

    prv_spi_flash_wait_write_end(); // Waiting for Writing to End

    return ret;
}

void hal_spi_flash_config(void)
{
    g_hqspi.Instance = QUADSPI;
    g_hqspi.Init.ClockPrescaler = 0;
    g_hqspi.Init.FifoThreshold = 4;
    g_hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
    g_hqspi.Init.FlashSize = POSITION_VAL(0x1000000) - 1;
    g_hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_4_CYCLE;
    g_hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;
    g_hqspi.Init.FlashID = QSPI_FLASH_ID_1;
    g_hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
    if (HAL_QSPI_Init(&g_hqspi) != HAL_OK) {
        Error_Handler();
    }
}

int hal_spi_flash_erase(uint32_t addr, int32_t len)
{
    uint32_t begin;
    uint32_t end;
    int i;

    if (len < 0 || addr > QSPI_FLASH_TOTAL_SIZE || addr + len > QSPI_FLASH_TOTAL_SIZE) {
        return -1;
    }

    begin = addr / QSPI_FLASH_SECTOR * QSPI_FLASH_SECTOR;
    end = (addr + len - 1) / QSPI_FLASH_SECTOR * QSPI_FLASH_SECTOR;

    for (i = begin; i <= end; i += QSPI_FLASH_SECTOR) {
        if (prv_spi_flash_erase_sector(i) == -1) {
            return -1;
        }
    }

    return 0;
}

int hal_spi_flash_write(const void *buf, int32_t len, uint32_t *location)
{
    const uint8_t *pbuf = (const uint8_t *)buf;
    int page_cnt = 0;
    int remain_cnt = 0;
    int temp = 0;
    uint32_t loc_addr;
    uint8_t addr = 0;
    uint8_t count = 0;
    int i;
    int ret = 0;

    if (NULL == pbuf || NULL == location || len < 0 || *location > QSPI_FLASH_TOTAL_SIZE ||
        len + *location > QSPI_FLASH_TOTAL_SIZE) {
        return -1;
    }

    loc_addr = *location;
    addr = loc_addr % QSPI_FLASH_PAGESIZE;
    count = QSPI_FLASH_PAGESIZE - addr;
    page_cnt = len / QSPI_FLASH_PAGESIZE;
    remain_cnt = len % QSPI_FLASH_PAGESIZE;

    if (addr == 0) {         /* addr is aligned to SPI_FLASH_PAGESIZE */
        if (page_cnt == 0) { /* len < SPI_FLASH_PAGESIZE */
            ret = prv_spi_flash_write_page(pbuf, loc_addr, len);
            CHECK_RET_RETURN(ret);
        } else { /* len > SPI_FLASH_PAGESIZE */
            for (i = 0; i < page_cnt; ++i) {
                ret = prv_spi_flash_write_page(pbuf + i * QSPI_FLASH_PAGESIZE, loc_addr, QSPI_FLASH_PAGESIZE);
                CHECK_RET_RETURN(ret);
                loc_addr += QSPI_FLASH_PAGESIZE;
            }

            ret = prv_spi_flash_write_page(pbuf + page_cnt * QSPI_FLASH_PAGESIZE, loc_addr, remain_cnt);
            CHECK_RET_RETURN(ret);
        }
    } else {                          /* addr is not aligned to SPI_FLASH_PAGESIZE */
        if (page_cnt == 0) {          /* len < SPI_FLASH_PAGESIZE */
            if (remain_cnt > count) { /* (len + loc_addr) > SPI_FLASH_PAGESIZE */
                temp = remain_cnt - count;

                ret = prv_spi_flash_write_page(pbuf, loc_addr, count);
                CHECK_RET_RETURN(ret);

                ret = prv_spi_flash_write_page(pbuf + count, loc_addr + count, temp);
                CHECK_RET_RETURN(ret);
            } else {
                ret = prv_spi_flash_write_page(pbuf, loc_addr, len);
                CHECK_RET_RETURN(ret);
            }
        } else { /* len > SPI_FLASH_PAGESIZE */
            len -= count;
            page_cnt = len / QSPI_FLASH_PAGESIZE;
            remain_cnt = len % QSPI_FLASH_PAGESIZE;

            ret = prv_spi_flash_write_page(pbuf, loc_addr, count);
            CHECK_RET_RETURN(ret);
            loc_addr += count;

            for (i = 0; i < page_cnt; ++i) {
                ret = prv_spi_flash_write_page(pbuf + count + i * QSPI_FLASH_PAGESIZE, loc_addr, QSPI_FLASH_PAGESIZE);
                CHECK_RET_RETURN(ret);
                loc_addr += QSPI_FLASH_PAGESIZE;
            }

            if (remain_cnt != 0) {
                ret = prv_spi_flash_write_page(pbuf + count + page_cnt * QSPI_FLASH_PAGESIZE, loc_addr, remain_cnt);
                CHECK_RET_RETURN(ret);
            }
        }
    }

    *location += len;
    return ret;
}

int hal_spi_flash_erase_write(const void *buf, int32_t len, uint32_t location)
{
    int ret = 0;

    ret = hal_spi_flash_erase(location, len);
    CHECK_RET_RETURN(ret);
    ret = hal_spi_flash_write(buf, len, &location);

    return ret;
}

int hal_spi_flash_read(void *buf, int32_t len, uint32_t location)
{
    int ret = 0;
    uint8_t *pbuf = (uint8_t *)buf;

    if ((pbuf == NULL) || (len < 0) || (location > QSPI_FLASH_TOTAL_SIZE) ||
        ((len + location) > QSPI_FLASH_TOTAL_SIZE)) {
        return -1;
    }

    QSPI_Send_CMD(QSPI_FLASH_FastReadData, location, 8, QSPI_INSTRUCTION_1_LINE,
                  QSPI_ADDRESS_1_LINE, QSPI_ADDRESS_24_BITS, QSPI_DATA_1_LINE);
    QSPI_Receive(buf, len);

    return ret;
}

int hal_spi_flash_get_id(void)
{
    QSPI_CommandTypeDef command;
    uint8_t temp[3];
    uint32_t deviceid;

    command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
    command.Instruction = QSPI_FLASH_JedecDeviceID;
    command.AddressMode = QSPI_ADDRESS_1_LINE;
    command.AddressSize = QSPI_ADDRESS_24_BITS;
    command.DataMode = QSPI_DATA_1_LINE;
    command.AddressMode = QSPI_ADDRESS_NONE;
    command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    command.DummyCycles = 0;
    command.NbData = 3;
    command.DdrMode = QSPI_DDR_MODE_DISABLE;
    command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
    command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

    HAL_QSPI_Command(&g_hqspi, &command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE);

    QSPI_Receive(temp, 3); // 3, length of temp buffer
    deviceid = (temp[1] << 8)|( temp[0] << 16)|(temp[2]);
    return deviceid;
}

#endif /* HAL_QSPI_MODULE_ENABLED */
