552 lines
15 KiB
C
552 lines
15 KiB
C
/**
|
|
******************************************************************************
|
|
* @file mx25l4006.c
|
|
* @Author MCD Application Team
|
|
* @brief This file provides the MX25L4006 drivers.
|
|
******************************************************************************
|
|
* @attention
|
|
*
|
|
* Copyright (c) 2022 STMicroelectronics.
|
|
* All rights reserved.
|
|
*
|
|
* This software is licensed under terms that can be found in the LICENSE file
|
|
* in the root directory of this software component.
|
|
* If no LICENSE file comes with this software, it is provided AS-IS.
|
|
*
|
|
******************************************************************************
|
|
*/
|
|
|
|
/* Includes ------------------------------------------------------------------*/
|
|
#include "mx25l4006.h"
|
|
|
|
/** @addtogroup BSP
|
|
* @{
|
|
*/
|
|
|
|
/** @addtogroup Components
|
|
* @{
|
|
*/
|
|
|
|
/** @defgroup MX25L4006 MX25L4006
|
|
* @{
|
|
*/
|
|
#define MX25L4006_SPI_TIMEOUT 1000U /* 1000ms */
|
|
|
|
|
|
/** @defgroup MX25L4006_Exported_Functions MX25L4006 Exported Functions
|
|
* @{
|
|
*/
|
|
|
|
/**
|
|
* @brief Get Flash information
|
|
* @param pInfo pointer to Device Info structure
|
|
* @retval Status
|
|
* - MX25L4006_OK
|
|
*/
|
|
int32_t MX25L4006_GetFlashInfo(MX25L4006_Info_t *pInfo)
|
|
{
|
|
/* Configure the structure with the memory configuration */
|
|
pInfo->FlashSize = MX25L4006_FLASH_SIZE;
|
|
pInfo->EraseSectorSize = MX25L4006_BLOCK_64K;
|
|
pInfo->EraseSectorsNumber = (MX25L4006_FLASH_SIZE / MX25L4006_BLOCK_64K);
|
|
pInfo->EraseSubSectorSize = MX25L4006_SECTOR_4K;
|
|
pInfo->EraseSubSectorNumber = (MX25L4006_FLASH_SIZE / MX25L4006_SECTOR_4K);
|
|
pInfo->EraseSubSector1Size = MX25L4006_SECTOR_4K;
|
|
pInfo->EraseSubSector1Number = (MX25L4006_FLASH_SIZE / MX25L4006_SECTOR_4K);
|
|
pInfo->ProgPageSize = MX25L4006_PAGE_SIZE;
|
|
pInfo->ProgPagesNumber = (MX25L4006_FLASH_SIZE / MX25L4006_PAGE_SIZE);
|
|
|
|
return MX25L4006_OK;
|
|
}
|
|
|
|
/**
|
|
* @brief Wait until Write In Progress (WIP) bit is equal to 0
|
|
* @param Ctx Component object pointer
|
|
* @retval Status
|
|
* - MX25L4006_ERROR_AUTOPOLLING
|
|
* - MX25L4006_ERROR_COMMAND
|
|
* - MX25L4006_OK
|
|
*/
|
|
int32_t MX25L4006_AutoPollingMemReady(SPI_HandleTypeDef *Ctx)
|
|
{
|
|
int32_t ret = MX25L4006_OK;
|
|
uint8_t statusRegister;
|
|
uint8_t cmd = MX25L4006_READ_STATUS_REG_CMD;
|
|
|
|
/* Send the command */
|
|
if (HAL_SPI_Transmit(Ctx, &cmd, 1U, MX25L4006_SPI_TIMEOUT) != HAL_OK)
|
|
{
|
|
ret = MX25L4006_ERROR_COMMAND;
|
|
}
|
|
else
|
|
{
|
|
do
|
|
{
|
|
if (HAL_SPI_Receive(Ctx, &statusRegister, 1U, MX25L4006_SPI_TIMEOUT) != HAL_OK)
|
|
{
|
|
ret = MX25L4006_ERROR_AUTOPOLLING;
|
|
}
|
|
} while ((ret == MX25L4006_OK) && ((statusRegister & MX25L4006_SR_WIP) != 0U));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Wait until Write Enable Latch (WEL) bit is set to 1
|
|
* @param Component object pointer
|
|
* @retval Status
|
|
* - MX25L4006_ERROR_RECEIVE
|
|
* - MX25L4006_ERROR_COMMAND
|
|
* - MX25L4006_OK
|
|
*/
|
|
int32_t MX25L4006_AutoPollingMemReadyToWrite(SPI_HandleTypeDef *Ctx)
|
|
{
|
|
int32_t ret = MX25L4006_OK;
|
|
uint8_t statusRegister;
|
|
uint8_t cmd = MX25L4006_READ_STATUS_REG_CMD;
|
|
|
|
/* Send the command */
|
|
if (HAL_SPI_Transmit(Ctx, &cmd, 1U, MX25L4006_SPI_TIMEOUT) != HAL_OK)
|
|
{
|
|
ret = MX25L4006_ERROR_COMMAND;
|
|
}
|
|
else
|
|
{
|
|
do
|
|
{
|
|
if (HAL_SPI_Receive(Ctx, &statusRegister, 1U, MX25L4006_SPI_TIMEOUT) != HAL_OK)
|
|
{
|
|
ret = MX25L4006_ERROR_RECEIVE;
|
|
}
|
|
} while ((ret == MX25L4006_OK) && ((statusRegister & MX25L4006_SR_WREN) != MX25L4006_SR_WREN));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Read/Write Array Commands (3 Byte Address Command Set) *********************/
|
|
/**
|
|
* @brief Reads an amount of data from the memory.
|
|
* @param Ctx Component object pointer
|
|
* @param pData Pointer to data to be read
|
|
* @param ReadAddr Read start address
|
|
* @param Size Size of data to read in Byte
|
|
* @retval Status
|
|
* - MX25L4006_ERROR_RECEIVE
|
|
* - MX25L4006_ERROR_COMMAND
|
|
* - MX25L4006_OK
|
|
*/
|
|
int32_t MX25L4006_Read(SPI_HandleTypeDef *Ctx, uint8_t *pData, uint32_t ReadAddr, uint16_t Size)
|
|
{
|
|
int32_t ret = MX25L4006_OK;
|
|
uint8_t cmd[4U];
|
|
|
|
/* Send the command */
|
|
cmd[0U] = MX25L4006_READ_CMD;
|
|
cmd[1U] = (uint8_t)((ReadAddr & 0x00FF0000U) >> 16);
|
|
cmd[2U] = (uint8_t)((ReadAddr & 0x0000FF00U) >> 8);
|
|
cmd[3U] = (uint8_t)(ReadAddr & 0x000000FFU);
|
|
|
|
if (HAL_SPI_Transmit(Ctx, cmd, 4U, MX25L4006_SPI_TIMEOUT) != HAL_OK)
|
|
{
|
|
ret = MX25L4006_ERROR_COMMAND;
|
|
}
|
|
else
|
|
{
|
|
if (HAL_SPI_Receive(Ctx, pData, Size, MX25L4006_SPI_TIMEOUT) != HAL_OK)
|
|
{
|
|
ret = MX25L4006_ERROR_RECEIVE;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Reads an amount of data from the memory.
|
|
* @param Ctx Component object pointer
|
|
* @param pData Pointer to data to be read
|
|
* @param ReadAddr Read start address
|
|
* @param Size Size of data to read in Byte
|
|
* @retval Status
|
|
* - MX25L4006_ERROR_RECEIVE
|
|
* - MX25L4006_ERROR_COMMAND
|
|
* - MX25L4006_OK
|
|
*/
|
|
int32_t MX25L4006_FastRead(SPI_HandleTypeDef *Ctx, uint8_t *pData, uint32_t ReadAddr, uint16_t Size)
|
|
{
|
|
int32_t ret = MX25L4006_OK;
|
|
uint8_t cmd[5U];
|
|
|
|
/* Send the command */
|
|
cmd[0U] = MX25L4006_FAST_READ_CMD;
|
|
cmd[1U] = (uint8_t)((ReadAddr & 0x00FF0000U) >> 16);
|
|
cmd[2U] = (uint8_t)((ReadAddr & 0x0000FF00U) >> 8);
|
|
cmd[3U] = (uint8_t)(ReadAddr & 0x000000FFU);
|
|
cmd[4U] = 0xAAU; /* Dummy */
|
|
|
|
if (HAL_SPI_Transmit(Ctx, cmd, 5U, MX25L4006_SPI_TIMEOUT) != HAL_OK)
|
|
{
|
|
ret = MX25L4006_ERROR_COMMAND;
|
|
}
|
|
else
|
|
{
|
|
if (HAL_SPI_Receive(Ctx, pData, Size, MX25L4006_SPI_TIMEOUT) != HAL_OK)
|
|
{
|
|
ret = MX25L4006_ERROR_RECEIVE;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Writes an amount of data to the SPI memory.
|
|
* For 256 bytes page program, the 8 least significant address bits byte
|
|
* should be set to 0 this function otherwise returns MX25L4006_ERROR_ADDRESS
|
|
* @param Ctx Component object pointer
|
|
* @param pData Pointer to data to be written
|
|
* @param WriteAddr Write start address
|
|
* @param Size Size of data to write. Range 1 ~ 256
|
|
* @retval Status
|
|
* - MX25L4006_ERROR_ADDRESS
|
|
* - MX25L4006_ERROR_COMMAND
|
|
* - MX25L4006_ERROR_TRANSMIT
|
|
* - MX25L4006_OK
|
|
*/
|
|
int32_t MX25L4006_PageProgram(SPI_HandleTypeDef *Ctx, uint8_t *pData, uint32_t WriteAddr, uint16_t Size)
|
|
{
|
|
int32_t ret = MX25L4006_OK;
|
|
uint8_t cmd[4U];
|
|
|
|
if ((Size >= MX25L4006_PAGE_SIZE) && ((WriteAddr & 0x000000FFU) != 0U))
|
|
{
|
|
ret = MX25L4006_ERROR_ADDRESS;
|
|
}
|
|
else
|
|
{
|
|
/* 1- Send Page Program (PP) command */
|
|
cmd[0U] = MX25L4006_PAGE_PROG_CMD;
|
|
cmd[1U] = (uint8_t)((WriteAddr & 0x00FF0000U) >> 16);
|
|
cmd[2U] = (uint8_t)((WriteAddr & 0x0000FF00U) >> 8);
|
|
cmd[3U] = (uint8_t)(WriteAddr & 0x000000FFU);
|
|
if (HAL_SPI_Transmit(Ctx, cmd, 4U, MX25L4006_SPI_TIMEOUT) != HAL_OK)
|
|
{
|
|
ret = MX25L4006_ERROR_COMMAND;
|
|
}
|
|
else
|
|
{
|
|
/* 2- Send the data */
|
|
if (HAL_SPI_Transmit(Ctx, pData, Size, MX25L4006_SPI_TIMEOUT) != HAL_OK)
|
|
{
|
|
ret = MX25L4006_ERROR_TRANSMIT;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Erases the specified sector of the SPI memory
|
|
* MX25L4006 support 4K size block erase command.
|
|
* @param Ctx Component object pointer
|
|
* @param SectorAddress Sector address to erase
|
|
* @retval Status
|
|
* - MX25L4006_ERROR_COMMAND
|
|
* - MX25L4006_OK
|
|
*/
|
|
int32_t MX25L4006_SectorErase(SPI_HandleTypeDef *Ctx, uint32_t SectorAddress)
|
|
{
|
|
int32_t ret = MX25L4006_OK;
|
|
uint8_t cmd[4U];
|
|
|
|
/* 1- Send Sector Erase (SE) for erasing the data of chosen block */
|
|
cmd[0U] = MX25L4006_SECTOR_ERASE_4K_CMD;
|
|
cmd[1U] = (uint8_t)((SectorAddress & 0x00FF0000U) >> 16);
|
|
cmd[2U] = (uint8_t)((SectorAddress & 0x0000FF00U) >> 8);
|
|
cmd[3U] = (uint8_t)(SectorAddress & 0x000000FFU);
|
|
if (HAL_SPI_Transmit(Ctx, cmd, 4U, MX25L4006_SPI_TIMEOUT) != HAL_OK)
|
|
{
|
|
return MX25L4006_ERROR_COMMAND;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Erases the specified block of the SPI memory
|
|
* MX25L4006 64K size block erase command.
|
|
* @param Ctx Component object pointer
|
|
* @param BlockAddress Block address to erase
|
|
* @param BlockSize Block size to erase
|
|
* @retval Status
|
|
* - MX25L4006_ERROR_COMMAND
|
|
* - MX25L4006_OK
|
|
*/
|
|
int32_t MX25L4006_BlockErase(SPI_HandleTypeDef *Ctx, uint32_t BlockAddress, MX25L4006_Erase_t BlockSize)
|
|
{
|
|
int32_t ret = MX25L4006_OK;
|
|
uint8_t cmd[4U];
|
|
|
|
/* Setup erase command */
|
|
switch (BlockSize)
|
|
{
|
|
default :
|
|
case MX25L4006_ERASE_4K :
|
|
cmd[0U] = MX25L4006_SECTOR_ERASE_4K_CMD;
|
|
break;
|
|
|
|
case MX25L4006_ERASE_64K :
|
|
cmd[0U] = MX25L4006_BLOCK_ERASE_64K_CMD;
|
|
break;
|
|
|
|
case MX25L4006_ERASE_CHIP :
|
|
return MX25L4006_ChipErase(Ctx);
|
|
break;
|
|
}
|
|
|
|
/* 1- Send Block Erase (BE) for erasing the data of chosen block */
|
|
cmd[1U] = (uint8_t)((BlockAddress & 0x00FF0000U) >> 16);
|
|
cmd[2U] = (uint8_t)((BlockAddress & 0x0000FF00U) >> 8);
|
|
cmd[3U] = (uint8_t)(BlockAddress & 0x000000FFU);
|
|
if (HAL_SPI_Transmit(Ctx, cmd, 4U, MX25L4006_SPI_TIMEOUT) != HAL_OK)
|
|
{
|
|
ret = MX25L4006_ERROR_COMMAND;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Whole chip erase of the SPI memory
|
|
* @param Ctx Component object pointer
|
|
* @retval Status
|
|
* - MX25L4006_ERROR_COMMAND
|
|
* - MX25L4006_OK
|
|
*/
|
|
int32_t MX25L4006_ChipErase(SPI_HandleTypeDef *Ctx)
|
|
{
|
|
int32_t ret = MX25L4006_OK;
|
|
uint8_t cmd = MX25L4006_CHIP_ERASE_CMD;
|
|
|
|
/* 1- Send Chip Erase (CE) command to erase whole chip */
|
|
if (HAL_SPI_Transmit(Ctx, &cmd, 1U, MX25L4006_SPI_TIMEOUT) != HAL_OK)
|
|
{
|
|
ret = MX25L4006_ERROR_COMMAND;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Register/Setting Commands **************************************************/
|
|
/**
|
|
* @brief This function sets the (WEL) Write Enable Latch bit
|
|
* @param Ctx Component object pointer
|
|
* @retval Status
|
|
* - MX25L4006_ERROR_COMMAND
|
|
* - MX25L4006_OK
|
|
*/
|
|
int32_t MX25L4006_WriteEnable(SPI_HandleTypeDef *Ctx)
|
|
{
|
|
int32_t ret = MX25L4006_OK;
|
|
uint8_t cmd = MX25L4006_WRITE_ENABLE_CMD;
|
|
|
|
/* Send Write Enable (WREN) command to set Write Enable Latch (WEL) bit in the Status Register */
|
|
if (HAL_SPI_Transmit(Ctx, &cmd, 1U, MX25L4006_SPI_TIMEOUT) != HAL_OK)
|
|
{
|
|
ret = MX25L4006_ERROR_COMMAND;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief This function resets the (WEL) Write Enable Latch bit
|
|
* @param Ctx Component object pointer
|
|
* @retval Status
|
|
* - MX25L4006_ERROR_COMMAND
|
|
* - MX25L4006_OK
|
|
*/
|
|
int32_t MX25L4006_WriteDisable(SPI_HandleTypeDef *Ctx)
|
|
{
|
|
int32_t ret = MX25L4006_OK;
|
|
uint8_t cmd = MX25L4006_WRITE_DISABLE_CMD;
|
|
|
|
/* Send Write Disable (WRDI) command to unset Write Enable Latch (WEL) bit in the Status Register */
|
|
if (HAL_SPI_Transmit(Ctx, &cmd, 1U, MX25L4006_SPI_TIMEOUT) != HAL_OK)
|
|
{
|
|
return MX25L4006_ERROR_COMMAND;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Read Flash Status register
|
|
* @param Ctx Component object pointer
|
|
* @param Value pointer to status register value
|
|
* @retval Status
|
|
* - MX25L4006_ERROR_COMMAND
|
|
* - MX25L4006_ERROR_RECEIVE
|
|
* - MX25L4006_OK
|
|
*/
|
|
int32_t MX25L4006_ReadStatusRegister(SPI_HandleTypeDef *Ctx, uint8_t *Value)
|
|
{
|
|
int32_t ret = MX25L4006_OK;
|
|
uint8_t cmd = MX25L4006_READ_STATUS_REG_CMD;
|
|
|
|
/* Send the command */
|
|
if (HAL_SPI_Transmit(Ctx, &cmd, 1U, MX25L4006_SPI_TIMEOUT) != HAL_OK)
|
|
{
|
|
ret = MX25L4006_ERROR_COMMAND;
|
|
}
|
|
else
|
|
{
|
|
if (HAL_SPI_Receive(Ctx, Value, 1U, MX25L4006_SPI_TIMEOUT) != HAL_OK)
|
|
{
|
|
ret = MX25L4006_ERROR_RECEIVE;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Write Flash Status register value
|
|
* @param Ctx Component object pointer
|
|
* @param Value Status register value
|
|
* @retval Status
|
|
* - MX25L4006_ERROR_COMMAND
|
|
* - MX25L4006_OK
|
|
*/
|
|
int32_t MX25L4006_WriteStatusRegister(SPI_HandleTypeDef *Ctx, uint8_t Value)
|
|
{
|
|
int32_t ret = MX25L4006_OK;
|
|
uint8_t cmd[2U];
|
|
|
|
/* Send the command */
|
|
cmd[0U] = MX25L4006_WRITE_STATUS_REG_CMD;
|
|
cmd[1U] = Value;
|
|
if (HAL_SPI_Transmit(Ctx, cmd, 2U, MX25L4006_SPI_TIMEOUT) != HAL_OK)
|
|
{
|
|
ret = MX25L4006_ERROR_COMMAND;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Deep power down
|
|
* The device is not active and all Write/Program/Erase instruction are ignored.
|
|
* @param Ctx Component object pointer
|
|
* @retval Status
|
|
* - MX25L4006_ERROR_COMMAND
|
|
* - MX25L4006_OK
|
|
*/
|
|
int32_t MX25L4006_EnterDeepPowerDown(SPI_HandleTypeDef *Ctx)
|
|
{
|
|
int32_t ret = MX25L4006_OK;
|
|
uint8_t cmd = MX25L4006_DEEP_POWER_DOWN_CMD;
|
|
|
|
/* Send Deep Powerdown (DP) command to enter deep powerdown mode */
|
|
if (HAL_SPI_Transmit(Ctx, &cmd, 1U, MX25L4006_SPI_TIMEOUT) != HAL_OK)
|
|
{
|
|
ret = MX25L4006_ERROR_COMMAND;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Release Deep power down
|
|
* The device is now active and all Write/Program/Erase instruction are available.
|
|
* @param Ctx Component object pointer
|
|
* @retval Status
|
|
* - MX25L4006_ERROR_COMMAND
|
|
* - MX25L4006_OK
|
|
*/
|
|
int32_t MX25L4006_ReleaseDeepPowerDown(SPI_HandleTypeDef *Ctx)
|
|
{
|
|
int32_t ret = MX25L4006_OK;
|
|
uint8_t cmd = MX25L4006_RELEASE_FROM_DEEP_POWER_DOWN_CMD;
|
|
|
|
/* Send Release from Deep Powerdown (RDP) command to exit from deep powerdown mode */
|
|
if (HAL_SPI_Transmit(Ctx, &cmd, 1U, MX25L4006_SPI_TIMEOUT) != HAL_OK)
|
|
{
|
|
ret = MX25L4006_ERROR_COMMAND;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* ID/Security Commands *******************************************************/
|
|
/**
|
|
* @brief Read Flash 3 Byte IDs
|
|
* Manufacturer ID, Memory type, Memory density
|
|
* @param Ctx Component object pointer
|
|
* @param ID pointer to flash id value
|
|
* @retval Status
|
|
* - MX25L4006_ERROR_COMMAND
|
|
* - MX25L4006_ERROR_RECEIVE
|
|
* - MX25L4006_OK
|
|
*/
|
|
int32_t MX25L4006_ReadID(SPI_HandleTypeDef *Ctx, uint8_t *ID)
|
|
{
|
|
int32_t ret = MX25L4006_OK;
|
|
uint8_t cmd = MX25L4006_READ_ID_CMD;
|
|
|
|
/* Send the command */
|
|
if (HAL_SPI_Transmit(Ctx, &cmd, 1U, MX25L4006_SPI_TIMEOUT) != HAL_OK)
|
|
{
|
|
ret = MX25L4006_ERROR_COMMAND;
|
|
}
|
|
else
|
|
{
|
|
if (HAL_SPI_Receive(Ctx, ID, 3U, MX25L4006_SPI_TIMEOUT) != HAL_OK)
|
|
{
|
|
ret = MX25L4006_ERROR_RECEIVE;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Reads an amount of data from the memory
|
|
* @param Ctx Component object pointer
|
|
* @param pData Pointer to data to be read
|
|
* @param ReadAddr Read start address
|
|
* @param Size Size of data to read in Byte
|
|
* @retval Status
|
|
* - MX25L4006_ERROR_COMMAND
|
|
* - MX25L4006_ERROR_RECEIVE
|
|
* - MX25L4006_OK
|
|
*/
|
|
int32_t MX25L4006_ReadSFDP(SPI_HandleTypeDef *Ctx, uint8_t *pData, uint32_t ReadAddr, uint16_t Size)
|
|
{
|
|
int32_t ret = MX25L4006_OK;
|
|
uint8_t cmd[5U];
|
|
|
|
/* Send the command */
|
|
cmd[0U] = MX25L4006_READ_SERIAL_FLASH_DISCO_PARAM_CMD;
|
|
cmd[1U] = (uint8_t)((ReadAddr & 0x00FF0000U) >> 16);
|
|
cmd[2U] = (uint8_t)(ReadAddr & 0x0000FF00U) >> 8;
|
|
cmd[3U] = (uint8_t)(ReadAddr & 0x000000FFU);
|
|
cmd[4U] = 0xAAU; /* Dummy */
|
|
if (HAL_SPI_Transmit(Ctx, cmd, 5U, MX25L4006_SPI_TIMEOUT) != HAL_OK)
|
|
{
|
|
ret = MX25L4006_ERROR_COMMAND;
|
|
}
|
|
else
|
|
{
|
|
if (HAL_SPI_Receive(Ctx, pData, Size, MX25L4006_SPI_TIMEOUT) != HAL_OK)
|
|
{
|
|
ret = MX25L4006_ERROR_RECEIVE;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|