/**
******************************************************************************
* @file memspi_core.c
* @brief Реализация работы с командами для внешней памяти
******************************************************************************
* @details
Модуль реализует функции для работы с FLASH/EEPROM по SPI
на уровне отдельных команд:
- MEMSPI_CMD_Read_Status_Register   Отправка комманды Read Status Register / Read Status Register 2 (0x05h / 0x35h)
- MEMSPI_CMD_Write_Status_Register  Отправка комманды Write Status Register (0x01h)
- MEMSPI_CMD_Write_Enable           Отправка комманды Write Enable (0x06h)
- MEMSPI_CMD_Read_Data              Отправка комманды Read Data (0x03h)
- MEMSPI_CMD_EEPROM_Write           Отправка комманды Write (eeprom) (0x02h)
- MEMSPI_CMD_FLASH_Page_Program     Отправка комманды Page Program (flash) (0x02h)
- MEMSPI_CMD_FLASH_Erase_Sector     Отправка комманды Erase Sector (flash) (0x20h)
- MEMSPI_CMD_Read_JEDEC_ID          Отправка комманды Read JEDEC ID (0x4Bh)
- MEMSPI_CMD_Read_Device_ID         Отправка комманды Read Manufacture / Device Id (0x90)
******************************************************************************/
#include "memspi_core.h"

//-------------------------------------------------------------
//----------------------COMMAND FUNCTIONS----------------------
/** 
    * @brief    Send command to read Status Register.
    * @param    hmemspi       Указатель на хендл внешней памяти.
    * @param    RequestedBits Какие биты запросить.
    * @param    EndCMD        Завершать комманду или нет. (очистка Chip Selected пина)
    * @param    Timeout       Время, за которое должно быть осуществлено чтение.
    * @return Заполняет Status Register в hmemspi.
    * @note     Всего есть две комманды: на запрос верхнего или нижниго байта. 
    *                   Функция в соответствии с RequestedBits определяет какой байт запросить, или два байта сразу.
    */
HAL_StatusTypeDef MEMSPI_CMD_Read_Status_Register(MEMSPI_HandleTypeDef *hmemspi, uint16_t RequestedBits, uint8_t EndCMD, uint32_t Timeout)
{
    HAL_StatusTypeDef SPI_RES;
    uint8_t command[2];
    uint8_t *pSRPtr = 0;
    uint8_t size = 1;
    
    if(RequestedBits >> 8)  // if its high byte of status register
    {       
#ifdef MEMSPI_READ_STATUS_REG_2
        command[0] = MEMSPI_READ_STATUS_REG_2;
        pSRPtr = (uint8_t *)(&hmemspi->SR) + 1; // set pointer to HI byte of SR register
        size = 1;
        if(RequestedBits & 0xFF) // if low byte also requester
        {
            size = 2;   // set size to 2 bytes
            command[1] = MEMSPI_READ_STATUS_REG;
        }
#endif // MEMSPI_READ_STATUS_REG_2
    }
    else                                        // of its low byte of status register
    {
        command[0] = MEMSPI_READ_STATUS_REG;
        pSRPtr = (uint8_t *)(&hmemspi->SR); // set pointer to LO byte of SR register
        size = 1;
    }    
    MEMSPI_Select(hmemspi);
  SPI_RES = MEMSPI_SPI_Transmit(hmemspi, command, 1, Timeout);  // send insctruction to read SR
  SPI_RES = MEMSPI_SPI_Receive(hmemspi, pSRPtr, 1, Timeout);            // receive response
    if(size > 1) // if 2 bytes are requested
    {       
        MEMSPI_Deselect(hmemspi);
        MEMSPI_Select(hmemspi);
        SPI_RES = MEMSPI_SPI_Transmit(hmemspi, command+1, 1, Timeout);  // send insctruction to read SR
        SPI_RES = MEMSPI_SPI_Receive(hmemspi, pSRPtr-1, 1, Timeout);            // receive response
        MEMSPI_Deselect(hmemspi);
    }
    if(EndCMD)
        MEMSPI_Deselect(hmemspi);
    return SPI_RES;
}

/** 
    * @brief    Send command to write bits in Status Register.
    * @param    hmemspi     Указатель на хендл внешней памяти.
    * @param    WrittenBits Какие биты запросить.
    * @param    Timeout     Время, за которое должно быть осуществлена запись.
    * @note     Данная команда посылает биты, как сдвинутые на 2 вправо. Т.е. 0-й бит в комманде - 2-й бит BP0.
                        Но биты указываются в также как они расположены и регистре. Функция сама выполняет сдвиг. 
    */
HAL_StatusTypeDef MEMSPI_CMD_Write_Status_Register(MEMSPI_HandleTypeDef *hmemspi, uint16_t WrittenBits, uint32_t Timeout)
{
    HAL_StatusTypeDef SPI_RES;
    uint8_t command[3];
    uint8_t size;
    
  command[0] = MEMSPI_WRITE_STATUS_REG;
    command[1] = WrittenBits;
    command[2] = WrittenBits >> 8;
    
    size = 3;

  MEMSPI_Select(hmemspi);
  SPI_RES = MEMSPI_SPI_Transmit(hmemspi, command, size, Timeout);
  MEMSPI_Deselect(hmemspi);
    
    return SPI_RES;
}

/** 
    * @brief    Send command to set Write Enable Latch (WEL) in Status Register.
    * @param    hmemspi Указатель на хендл внешней памяти.
    * @param    Timeout Время, за которое должно быть осуществлена запись.
    * @note     Разрешает запись в FLASH, путем высталения WEL в Status Register
    */
HAL_StatusTypeDef MEMSPI_CMD_Write_Enable(MEMSPI_HandleTypeDef *hmemspi, uint32_t Timeout)
{
    HAL_StatusTypeDef SPI_RES;
  uint8_t command[1];
  command[0] = MEMSPI_WRITE_ENABLE;

  MEMSPI_Select(hmemspi);
  SPI_RES = MEMSPI_SPI_Transmit(hmemspi, command, 1, Timeout);
  MEMSPI_Deselect(hmemspi);
    
    return SPI_RES;
}


/** 
    * @brief    Send command to read data from FLASH/EEPROM.
    * @param    hmemspi       Указатель на хендл внешней памяти.
    * @param    FLASH_Address Адресс откуда начинать считывание.
    * @param    pBuff         Куда записывать данные из FLASH.
    * @param    Size          Сколько байтов считывать.
    * @param    Timeout       Время, за которое должно быть осуществлено чтение.
    * @return pBuff.
    */
HAL_StatusTypeDef MEMSPI_CMD_Read_Data(MEMSPI_HandleTypeDef *hmemspi, uint32_t FLASH_Address, uint8_t *pBuff, uint16_t Size, uint32_t Timeout)
{
    HAL_StatusTypeDef SPI_RES;
    uint8_t command[4];
    uint8_t response[2] = {0};
    
  command[0] = MEMSPI_READ_DATA;
  command[1] = FLASH_Address >> 16  & 0xFF;
  command[2] = FLASH_Address >> 8   & 0xFF;
  command[3] = FLASH_Address                & 0xFF;
    
  MEMSPI_Select(hmemspi);
  SPI_RES = MEMSPI_SPI_Transmit(hmemspi, command, 4, Timeout);
  SPI_RES = MEMSPI_SPI_Receive(hmemspi, pBuff, Size, Timeout);  
  MEMSPI_Deselect(hmemspi);
    
    return SPI_RES;
}

/** 
    * @brief    Send command to write eeprom.
    * @param    hmemspi       Указатель на хендл внешней памяти.
    * @param    FLASH_Address Адресс куда начинать записывать.
    * @param    pData         Откуда брать данные для записи в FLASH.
    * @param    Size          Сколько байтов записать.
    * @param    Timeout       Время, за которое должна быть осуществлена запись.
    * @note     Рзамер данных для записи в EEPROM без ограничений. 
    */
HAL_StatusTypeDef MEMSPI_CMD_EEPROM_Write(MEMSPI_HandleTypeDef *hmemspi, uint32_t FLASH_Address, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
    HAL_StatusTypeDef SPI_RES;
    // 1 command byte + 3 address bytes + 256 data bytes
    uint8_t command[1+3+MEMSPI_PAGE_SIZE];
    FLASH_Address = FLASH_Address & 0xFFFFFF;
    
  command[0] = MEMSPI_WRITE_EEPROM;
    command[1] = FLASH_Address >> 16    & 0xFF;
    command[2] = FLASH_Address >> 8     & 0xFF;
    command[3] = FLASH_Address              & 0xFF;
    
  MEMSPI_Select(hmemspi);
  SPI_RES = MEMSPI_SPI_Transmit(hmemspi, command, 4, Timeout);  // send insctruction to write
  SPI_RES = MEMSPI_SPI_Transmit(hmemspi, pData, Size, Timeout); // send data to write
  MEMSPI_Deselect(hmemspi);
    
    return SPI_RES;
}
/** 
    * @brief    Send command to page program in FLASH.
    * @param    hmemspi       Указатель на хендл внешней памяти.
    * @param    FLASH_Address Адресс куда начинать записывать.
    * @param    pData         Откуда брать данные для записи в FLASH.
    * @param    Size          Сколько байтов записать.
    * @param    Timeout       Время, за которое должна быть осуществлена запись.
    * @note     Программирование FLASH только в пределах одной страницы. 
    *                   Т.е. если запись с 0x0, то не больше 256 байт. Если с 0ч40, то не больше 192 байт.
    */
HAL_StatusTypeDef MEMSPI_CMD_FLASH_Page_Program(MEMSPI_HandleTypeDef *hmemspi, uint32_t FLASH_Address, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
    HAL_StatusTypeDef SPI_RES;
    // 1 command byte + 3 address bytes + 256 data bytes
    uint8_t command[1+3+MEMSPI_PAGE_SIZE];
    FLASH_Address = FLASH_Address & 0xFFFFFF;
    
  command[0] = MEMSPI_PAGE_PROGRAM;
    command[1] = FLASH_Address >> 16    & 0xFF;
    command[2] = FLASH_Address >> 8     & 0xFF;
    command[3] = FLASH_Address              & 0xFF;
    
    // check if flash range is placed at one page
    if((FLASH_Address/MEMSPI_PAGE_SIZE) != ((FLASH_Address+Size-1)/MEMSPI_PAGE_SIZE)) // if page of first byte isnt equal page of last byte
        return HAL_ERROR;                                                                                                                                                   // return error
    
  MEMSPI_Select(hmemspi);
  SPI_RES = MEMSPI_SPI_Transmit(hmemspi, command, 4, Timeout);  // send insctruction to write
  SPI_RES = MEMSPI_SPI_Transmit(hmemspi, pData, Size, Timeout); // send data to write
  MEMSPI_Deselect(hmemspi);
    
    return SPI_RES;
}

/** 
    * @brief    Send command to erase sector.
    * @param    hmemspi       Указатель на хендл внешней памяти.
    * @param    FLASH_Address Адресс где надо данные стереть.
    * @param    Timeout       Время, за которое должна быть осуществлена очистка.
    * @note     Микросхема вроде сама высчитывает какой сектор ерейзнуть, в соответствии с заданным адресом.
    */
HAL_StatusTypeDef MEMSPI_CMD_FLASH_Erase_Sector(MEMSPI_HandleTypeDef *hmemspi, uint32_t FLASH_Address, uint32_t Timeout)
{
    HAL_StatusTypeDef SPI_RES;
    uint8_t command[4];
  uint8_t response[8];
    FLASH_Address = FLASH_Address & 0xFFFFFF;
    
  command[0] = MEMSPI_ERASE_SECTOR;
    command[1] = FLASH_Address >> 16;
    command[2] = FLASH_Address >> 8;
    command[3] = FLASH_Address;
    
  MEMSPI_Select(hmemspi);
  SPI_RES = MEMSPI_SPI_Transmit(hmemspi, command, 4, Timeout);
  MEMSPI_Deselect(hmemspi);
    
    return SPI_RES;
}


/** 
    * @brief    Send command to read JEDEC ID.
    * @param    hmemspi Указатель на хендл внешней памяти.
    * @param    Timeout Время, за которое должно быть осуществлено чтение.
    * @return   JEDEC ID микросхемы.
    */
uint32_t MEMSPI_CMD_Read_JEDEC_ID(MEMSPI_HandleTypeDef *hmemspi, uint32_t Timeout)
{
    HAL_StatusTypeDef SPI_RES;
  uint8_t command[1] = {0};
  uint8_t receive[4] = {0};
    uint32_t return_val;
    
  command[0] = MEMSPI_READ_JEDEC_ID;
  MEMSPI_Select(hmemspi);
  SPI_RES = MEMSPI_SPI_Transmit(hmemspi, command, 1, Timeout);
  SPI_RES = MEMSPI_SPI_Receive(hmemspi, &receive[1], 3, Timeout);
  MEMSPI_Deselect(hmemspi);
    
    return_val = (*(uint64_t *)receive);
  return __REV(return_val) & 0xFFFFFF;
}

/** 
    * @brief    Send command to read JEDEC ID.
    * @param    hmemspi Указатель на хендл внешней памяти.
    * @param    Timeout Время, за которое должно быть осуществлено чтение.
    * @return   Device ID микросхемы.
    */
uint64_t MEMSPI_CMD_Read_Device_ID(MEMSPI_HandleTypeDef *hmemspi, uint32_t Timeout)
{
    HAL_StatusTypeDef SPI_RES;
  uint8_t command[1] = {0};
  uint8_t receive[8] = {0};
    uint64_t return_val_LO;
    uint64_t return_val_HI;
  command[0] = MEMSPI_READ_UNIQUE_ID;
  MEMSPI_Select(hmemspi);
  SPI_RES = MEMSPI_SPI_Transmit(hmemspi, command, 1, Timeout);
  SPI_RES = MEMSPI_SPI_Receive(hmemspi, receive, 8, Timeout);
  MEMSPI_Deselect(hmemspi);
    
    return_val_LO = (*(uint64_t *)receive)  >> 32;
    return_val_HI = (*(uint64_t *)receive) & 0xFFFFFFFF;
  return ((uint64_t)__REV(return_val_HI) << 32) | __REV(return_val_LO);
}
/** 
    * @brief    Send command to fast read data from FLASH.
    * @param    hmemspi       Указатель на хендл внешней памяти.
    * @param    FLASH_Address Адресс откуда начинать считывание.
    * @param    pBuff         Куда записывать данные из FLASH.
    * @param    Size          Сколько байтов считывать.
    * @param    Timeout       Время, за которое должно быть осуществлено чтение.
    * @note     Данная функция предполагает отправку одного dummy байта после адресса, но у меня поч не работает пока :(
    */
HAL_StatusTypeDef MEMSPI_CMD_Fast_Read(MEMSPI_HandleTypeDef *hmemspi, uint32_t FLASH_Address, uint8_t *pBuff, uint16_t Size, uint32_t Timeout)
{
    HAL_StatusTypeDef SPI_RES;
    uint8_t command[5] = {0};
    uint8_t response[2] = {0};
    
  command[0] = MEMSPI_READ_DATA;
  command[1] = FLASH_Address >> 16  & 0xFF;
  command[2] = FLASH_Address >> 8   & 0xFF;
  command[3] = FLASH_Address                & 0xFF;
  command[4] = 0xFF;
    
  MEMSPI_Select(hmemspi);
  SPI_RES = MEMSPI_SPI_Transmit(hmemspi, command, 5, Timeout);
  SPI_RES = MEMSPI_SPI_Receive(hmemspi, pBuff, Size, Timeout);  
  MEMSPI_Deselect(hmemspi);
    
    return SPI_RES;
}
//-------------------------------------------------------------

