//
// Created by imi415 on 2019/12/19.
//

#include <stdint.h>

#include "stm32h7xx_hal.h"
#include "w25_qspi.h"

HAL_StatusTypeDef _W25_Write_Enable(w25_qspi_t *flash);
HAL_StatusTypeDef _W25_Write_Volatile_Enable(w25_qspi_t *flash);
HAL_StatusTypeDef _W25_Wait_Ready(w25_qspi_t *flash);
HAL_StatusTypeDef _W25_GetDefaultCommand(w25_qspi_t *flash, QSPI_CommandTypeDef *sCommand);
HAL_StatusTypeDef _W25_QSPI_Identify(w25_qspi_t *flash);

HAL_StatusTypeDef W25_QSPI_Init(w25_qspi_t *flash) {
    return _W25_QSPI_Identify(flash);
}

HAL_StatusTypeDef W25_QSPI_Erase_Sector(w25_qspi_t *flash, uint32_t address) {
    QSPI_CommandTypeDef sCommand;
    _W25_GetDefaultCommand(flash, &sCommand);
    sCommand.Instruction = W25_CMD_SECTOR_ERASE;
    sCommand.AddressMode = QSPI_ADDRESS_1_LINE;
    sCommand.DataMode = QSPI_DATA_NONE;
    sCommand.NbData = 0;
    sCommand.DummyCycles = 0;
    sCommand.Address = address;

    _W25_Write_Enable(flash);
    HAL_QSPI_Command(flash->interface, &sCommand, 1000);
    _W25_Wait_Ready(flash);

    return HAL_OK;
}

HAL_StatusTypeDef W25_QSPI_Program_Page(w25_qspi_t *flash, uint32_t address, uint8_t *data) {
    QSPI_CommandTypeDef sCommand;
    _W25_GetDefaultCommand(flash, &sCommand);
    sCommand.Instruction = W25_CMD_PAGE_PROGRAM;
    sCommand.AddressMode = QSPI_ADDRESS_1_LINE;
    sCommand.DataMode = QSPI_DATA_1_LINE;
    sCommand.DummyCycles = 0;
    sCommand.NbData = 256;
    sCommand.Address = address;

    _W25_Write_Enable(flash);
    if(HAL_QSPI_Command(flash->interface, &sCommand, 1000) != HAL_OK) {
        return HAL_ERROR;
    }
    if(HAL_QSPI_Transmit(flash->interface, data, 1000) != HAL_OK) {
        return HAL_ERROR;
    }
    _W25_Wait_Ready(flash);

    return HAL_OK;
}

HAL_StatusTypeDef W25_QSPI_Read(w25_qspi_t *flash, uint32_t address, uint8_t *data, uint32_t length) {
    QSPI_CommandTypeDef sCommand;
    _W25_GetDefaultCommand(flash, &sCommand);
    sCommand.Instruction = W25_CMD_READ;
    sCommand.AddressMode = QSPI_ADDRESS_1_LINE;
    sCommand.Address = address;
    sCommand.DataMode = QSPI_DATA_1_LINE;
    sCommand.DummyCycles = 0;
    sCommand.NbData = length;

    if(HAL_QSPI_Command(flash->interface, &sCommand, 1000) != HAL_OK) {
        return HAL_ERROR;
    }
    if(HAL_QSPI_Receive(flash->interface, data, 1000) != HAL_OK) {
        return HAL_ERROR;
    }
    return HAL_OK;
}

HAL_StatusTypeDef W25_QSPI_QuadEnable(w25_qspi_t *flash, uint8_t permanent) {
    QSPI_CommandTypeDef sCommand;
    _W25_GetDefaultCommand(flash, &sCommand);
    sCommand.Instruction = W25_CMD_READ_STATUS_2;
    sCommand.AddressMode = QSPI_ADDRESS_NONE;
    sCommand.DataMode = QSPI_DATA_1_LINE;
    sCommand.NbData = 1;
    sCommand.DummyCycles = 0;

    uint8_t status_2;

    if(HAL_QSPI_Command(flash->interface, &sCommand, 1000) != HAL_OK) {
        return HAL_ERROR;
    }
    if(HAL_QSPI_Receive(flash->interface, &status_2, 1000) != HAL_OK) {
        return HAL_ERROR;
    }

    if(permanent) {
        _W25_Write_Enable(flash);
    }
    else _W25_Write_Volatile_Enable(flash);

    sCommand.Instruction = W25_CMD_WRITE_STATUS_2;
    status_2 |= 0x02; // BIT 1 QE

    if(HAL_QSPI_Command(flash->interface, &sCommand, 1000) != HAL_OK) {
        return HAL_ERROR;
    }
    if(HAL_QSPI_Transmit(flash->interface, &status_2, 1000) != HAL_OK) {
        return HAL_ERROR;
    }

    return HAL_OK;

}

HAL_StatusTypeDef W25_QPI_Mode(w25_qspi_t *flash, uint8_t enable) {
    QSPI_CommandTypeDef sCommand;
    _W25_GetDefaultCommand(flash, &sCommand);
    if(enable) {
        sCommand.Instruction = W25_CMD_QPI_ENABLE;
    }
    else {
        sCommand.InstructionMode = QSPI_INSTRUCTION_4_LINES;
        sCommand.Instruction = W25_CMD_QPI_DISABLE;
    }
    sCommand.AddressMode = QSPI_ADDRESS_NONE;
    sCommand.DataMode = QSPI_DATA_NONE;
    sCommand.NbData = 0;
    sCommand.DummyCycles = 0;

    return HAL_QSPI_Command(flash->interface, &sCommand, 1000);
}

HAL_StatusTypeDef W25_QPI_ReadParams(w25_qspi_t *flash, w25_qpi_dummy_t dummy_clocks, w25_qpi_wrap_t wrap_length) {
    uint8_t rd_params = (dummy_clocks & 0x03) << 4 | (wrap_length & 0x03); // BIT 5-4: DUMMY, BIT 1-0 WRAP
    QSPI_CommandTypeDef sCommand;
    _W25_GetDefaultCommand(flash, &sCommand);
    sCommand.InstructionMode = QSPI_INSTRUCTION_4_LINES;
    sCommand.Instruction = W25_CMD_QPI_READ_PARAMS;
    sCommand.AddressMode = QSPI_ADDRESS_NONE;
    sCommand.DataMode = QSPI_DATA_4_LINES;
    sCommand.NbData = 1;
    sCommand.DummyCycles = 0;

    if(HAL_QSPI_Command(flash->interface, &sCommand, 1000) != HAL_OK) {
        return HAL_ERROR;
    }
    
    return HAL_QSPI_Transmit(flash->interface, &rd_params, 1000);
}

HAL_StatusTypeDef _W25_Write_Enable(w25_qspi_t *flash) {
    QSPI_CommandTypeDef sCommand;
    _W25_GetDefaultCommand(flash, &sCommand);
    sCommand.Instruction = W25_CMD_WRITE_ENABLE;
    sCommand.AddressMode = QSPI_ADDRESS_NONE;
    sCommand.DataMode = QSPI_DATA_NONE;
    sCommand.NbData = 0;
    sCommand.DummyCycles = 0;

    HAL_QSPI_Command(flash->interface, &sCommand, 1000);

    return HAL_OK;
}

HAL_StatusTypeDef _W25_Write_Volatile_Enable(w25_qspi_t *flash) {
    QSPI_CommandTypeDef sCommand;
    _W25_GetDefaultCommand(flash, &sCommand);
    sCommand.Instruction = W25_CMD_WRITE_VOLATILE_ENABLE;
    sCommand.AddressMode = QSPI_ADDRESS_NONE;
    sCommand.DataMode = QSPI_DATA_NONE;
    sCommand.NbData = 0;
    sCommand.DummyCycles = 0;

    HAL_QSPI_Command(flash->interface, &sCommand, 1000);

    return HAL_OK;
}

HAL_StatusTypeDef _W25_Wait_Ready(w25_qspi_t *flash) {
    QSPI_AutoPollingTypeDef sConfig;
    sConfig.Match = 0x00;
    sConfig.Mask = 0x01;
    sConfig.MatchMode = QSPI_MATCH_MODE_AND;
    sConfig.StatusBytesSize = 1;
    sConfig.Interval = 0x10;
    sConfig.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;

    QSPI_CommandTypeDef sCommand;
    _W25_GetDefaultCommand(flash, &sCommand);
    sCommand.DataMode = QSPI_DATA_1_LINE;
    sCommand.DummyCycles = 0;
    sCommand.Instruction = W25_CMD_READ_STATUS_1;
    sCommand.NbData = 1;
    sCommand.AddressMode = QSPI_ADDRESS_NONE;

    if(HAL_QSPI_AutoPolling_IT(flash->interface, &sCommand, &sConfig) != HAL_OK) {
        return HAL_ERROR;
    }
    return HAL_OK;
}

HAL_StatusTypeDef _W25_GetDefaultCommand(w25_qspi_t *flash, QSPI_CommandTypeDef *sCommand) {
    sCommand->InstructionMode = QSPI_INSTRUCTION_1_LINE;
    sCommand->AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    sCommand->DdrMode = QSPI_DDR_MODE_DISABLE;
    sCommand->DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
    sCommand->SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

    if(flash->address_size == W25_ADDRESS_32BITS) sCommand->AddressSize = QSPI_ADDRESS_32_BITS;
    else sCommand->AddressSize = QSPI_ADDRESS_24_BITS;

    return HAL_OK;
}

HAL_StatusTypeDef _W25_QSPI_Identify(w25_qspi_t *flash) {
    QSPI_CommandTypeDef sCommand;
    uint8_t jedec_id[3];
    _W25_GetDefaultCommand(flash, &sCommand);
    sCommand.AddressMode = QSPI_ADDRESS_NONE;
    sCommand.DataMode = QSPI_DATA_1_LINE;
    sCommand.DummyCycles = 0;
    sCommand.Instruction = W25_CMD_READ_JEDEC_ID;
    sCommand.NbData = 3;

    if(HAL_QSPI_Command(flash->interface, &sCommand, 1000) != HAL_OK) {
        return HAL_ERROR;
    }
    if(HAL_QSPI_Receive(flash->interface, jedec_id, 1000) != HAL_OK) {
        return HAL_ERROR;
    }

    flash->manufacturer = jedec_id[0];
    flash->memory_type = jedec_id[1];
    flash->capacity = 1U << jedec_id[2];

    return HAL_OK;
}