/******************************************************************************
  * file    Src/flash_if.c 
  * author  YuLiang
  * version 1.0.0
  * date    10-Jun-2022
  * brief   This file provides all the memory related operation functions.
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; COPYRIGHT(c) 2019 LandPower</center></h2>
  *
  * 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 STMicroelectronics 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.
  *
  ******************************************************************************/

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "common.h"
#include "flash_if.h"

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* 1M0 flash 1 * 1024 * 1024 */
#define FLASH_START_ADRESS    0x08000000
#define FLASH_PAGE_NBPERBANK  256

/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
/* spi flash 写使能. */
static HAL_StatusTypeDef _spi_flash_write_enable(void)
{
    uint8_t cmd = SPI_CMD_WRITE_ENABLE;

    /* 片选使能. */
    SPI_FLASH_CS_ENABLE();

    HAL_E_RETURN(HAL_SPI_Transmit(SpiHandle, &cmd, 1, SPI_BUS_TIMEOUT));

    SPI_FLASH_CS_DISABLE();

    return HAL_OK;
}

/* 等待 flash 操作完成. */
static HAL_StatusTypeDef _spi_flash_wait_complete(void)
{ 
    uint8_t cmd = SPI_CMD_STATE_READ;
    uint8_t status = 0xFF;

    SPI_FLASH_CS_ENABLE();

    HAL_E_RETURN(HAL_SPI_Transmit(SpiHandle, &cmd, 1, SPI_BUS_TIMEOUT));
    /* 一直检测 FLASH 状态寄存器状态, 直到 Bit0 位 (BUSY 位) 为 0. */
    do
    {
        HAL_E_RETURN(HAL_SPI_Receive(SpiHandle, &status, 1, SPI_BUS_TIMEOUT));
    } while (SPI_FLASH_BUSY_MASK == (status & SPI_FLASH_BUSY_MASK));

    SPI_FLASH_CS_DISABLE();

    return HAL_OK;
}

/* 等待 flash 写使能. */
static HAL_StatusTypeDef _spi_flash_wait_write(void)
{ 
    uint8_t cmd = SPI_CMD_STATE_READ;
    uint8_t status = 0xFF;

    SPI_FLASH_CS_ENABLE();

    HAL_E_RETURN(HAL_SPI_Transmit(SpiHandle, &cmd, 1, SPI_BUS_TIMEOUT));
    /* 一直检测 FLASH 状态寄存器状态, 直到 Bit1 位 (WEL  位) 为 1. */
    do
    {
        HAL_E_RETURN(HAL_SPI_Receive(SpiHandle, &status, 1, SPI_BUS_TIMEOUT));
    } while (0 == (status & SPI_FLASH_WEL_MASK));

    SPI_FLASH_CS_DISABLE();

    return HAL_OK;
}

/* spi flash 页写入操作, size 最大为页大小 (256Byte). */
static HAL_StatusTypeDef _spi_flash_page_write(uint32_t addr, uint8_t *data, uint16_t size) 
{
    uint8_t cmd[SPI_CMD_HEADER_LEN] = {0};

    /* 写使能. */ 
    HAL_E_RETURN(_spi_flash_write_enable());

    /* 等待写使能完成. */
    HAL_E_RETURN(_spi_flash_wait_write());

    /* 片选使能. */
    SPI_FLASH_CS_ENABLE();

    cmd[0] = SPI_CMD_PAGE_WRITE;
    cmd[1] = (addr >> 16) & 0xFF;
    cmd[2] = (addr >> 8) & 0xFF;;
    cmd[3] = addr & 0xFF;;

    /* 发送页写入命令. */
    HAL_E_RETURN(HAL_SPI_Transmit(SpiHandle, cmd, SPI_CMD_HEADER_LEN, SPI_BUS_TIMEOUT));
    
    /* 确保写入大小不超过页 (256Byte) 大小. */ 
    if (size > SPI_FLASH_PAGE_SIZE) 
        size = SPI_FLASH_PAGE_SIZE; 

    /* 发送数据. */ 
    HAL_E_RETURN(HAL_SPI_Transmit(SpiHandle, data, size, SPI_BUS_TIMEOUT));
    
    /* 片选去使能. */
    SPI_FLASH_CS_DISABLE();
    
    /* 等待 flash busy 状态. */
    HAL_E_RETURN(_spi_flash_wait_complete());

    return HAL_OK;
}

/* Public functions ----------------------------------------------------------*/
/**
  * @brief  Unlocks Flash for write access
  * @param  None
  * @retval None
  */
void FLASH_If_Init(void)
{
  /* Unlock the Program memory */
  HAL_FLASH_Unlock();

  /* Clear all FLASH flags */
  __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_PGSERR | FLASH_FLAG_WRPERR | FLASH_FLAG_OPTVERR);
  /* Unlock the Program memory */
  HAL_FLASH_Lock();
}

/**
  * @brief  This function does an erase of all user flash area
  * @param  start: start of user flash area
  * @retval FLASHIF_OK : user flash area successfully erased
  *         FLASHIF_ERASEKO : error occurred
  */
uint32_t FLASH_If_Erase(uint32_t start)
{
  uint32_t NbrOfPages = 0;
  uint32_t PageError = 0;
  FLASH_EraseInitTypeDef pEraseInit;
  HAL_StatusTypeDef status = HAL_OK;

  /* Unlock the Flash to enable the flash control register access *************/ 
  HAL_FLASH_Unlock();

  /* Get the number of page to  erase */
  NbrOfPages = (FLASH_START_ADRESS + FLASH_SIZE);
  NbrOfPages = (NbrOfPages - start) / FLASH_PAGE_SIZE;

  if(NbrOfPages > FLASH_PAGE_NBPERBANK)
  {
    pEraseInit.Banks = FLASH_BANK_1;
    pEraseInit.NbPages = NbrOfPages % FLASH_PAGE_NBPERBANK;
    pEraseInit.Page = FLASH_PAGE_NBPERBANK - pEraseInit.NbPages;
    pEraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
    status = HAL_FLASHEx_Erase(&pEraseInit, &PageError);
  
    NbrOfPages = FLASH_PAGE_NBPERBANK;
  }
  
  if(status == HAL_OK)
  {
    pEraseInit.Banks = FLASH_BANK_2;
    pEraseInit.NbPages = NbrOfPages;
    pEraseInit.Page = FLASH_PAGE_NBPERBANK - pEraseInit.NbPages;
    pEraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
    status = HAL_FLASHEx_Erase(&pEraseInit, &PageError);
  }
  
  /* Lock the Flash to disable the flash control register access (recommended
     to protect the FLASH memory against possible unwanted operation) *********/
  HAL_FLASH_Lock();

  if (status != HAL_OK)
  {
    /* Error occurred while page erase */
    return FLASHIF_ERASEKO;
  }

  return FLASHIF_OK;
}

/**
  * @brief  This function writes a data buffer in flash (data are 32-bit aligned).
  * @note   After writing data buffer, the flash content is checked.
  * @param  destination: start address for target location
  * @param  p_source: pointer on buffer with data to write
  * @param  length: length of data buffer (unit is 32-bit word)
  * @retval uint32_t 0: Data successfully written to Flash memory
  *         1: Error occurred while writing data in Flash memory
  *         2: Written Data in flash memory is different from expected one
  */
uint32_t FLASH_If_Write(uint32_t destination, uint32_t *p_source, uint32_t length)
{
  uint32_t status = FLASHIF_OK;
  uint32_t i = 0;

  /* Unlock the Flash to enable the flash control register access *************/
  HAL_FLASH_Unlock();

  /* DataLength must be a multiple of 64 bit */
  for (i = 0; (i < length/2) && (destination <= (USER_FLASH_END_ADDRESS-8)); i++)
  {
    /* Device voltage range supposed to be [2.7V to 3.6V], the operation will
       be done by word */ 
    if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, destination, *((uint64_t *)(p_source+2*i))) == HAL_OK)      
    {
     /* Check the written value */
      if (*(uint64_t*)destination != *(uint64_t *)(p_source+2*i))
      {
        /* Flash content doesn't match SRAM content */
        status = FLASHIF_WRITINGCTRL_ERROR;
        break;
      }
      /* Increment FLASH destination address */
      destination += 8;
    }
    else
    {
      /* Error occurred while writing data in Flash memory */
      status = FLASHIF_WRITING_ERROR;
      break;
    }
  }

  /* Lock the Flash to disable the flash control register access (recommended
     to protect the FLASH memory against possible unwanted operation) *********/
  HAL_FLASH_Lock();

  return status;
}

/**
  * @brief  Returns the write protection status of application flash area.
  * @param  None
  * @retval If a sector in application area is write-protected returned value is a combinaison
            of the possible values : FLASHIF_PROTECTION_WRPENABLED, FLASHIF_PROTECTION_PCROPENABLED, ...
  *         If no sector is write-protected FLASHIF_PROTECTION_NONE is returned.
  */
uint32_t FLASH_If_GetWriteProtectionStatus(void)
{
  uint32_t ProtectedPAGE = FLASHIF_PROTECTION_NONE;
  FLASH_OBProgramInitTypeDef OptionsBytesStruct1, OptionsBytesStruct2, OptionsBytesStruct3, OptionsBytesStruct4;
  
  /* Unlock the Flash to enable the flash control register access *************/
  HAL_FLASH_Unlock();
 
  OptionsBytesStruct1.WRPArea = OB_WRPAREA_BANK1_AREAA;
  OptionsBytesStruct1.PCROPConfig = FLASH_BANK_1;
  OptionsBytesStruct2.WRPArea = OB_WRPAREA_BANK1_AREAB;
  OptionsBytesStruct2.PCROPConfig = FLASH_BANK_1;
  OptionsBytesStruct3.WRPArea = OB_WRPAREA_BANK2_AREAA;
  OptionsBytesStruct3.PCROPConfig = FLASH_BANK_2;
  OptionsBytesStruct4.WRPArea = OB_WRPAREA_BANK2_AREAB;
  OptionsBytesStruct4.PCROPConfig = FLASH_BANK_2;
  
  /* Check if there are write protected sectors inside the user flash area ***/
  HAL_FLASHEx_OBGetConfig(&OptionsBytesStruct1);
  HAL_FLASHEx_OBGetConfig(&OptionsBytesStruct2);
  HAL_FLASHEx_OBGetConfig(&OptionsBytesStruct3);
  HAL_FLASHEx_OBGetConfig(&OptionsBytesStruct4);
  
  /* Lock the Flash to disable the flash control register access (recommended
     to protect the FLASH memory against possible unwanted operation) *********/
  HAL_FLASH_Lock();
  
  /* Check PCROP areas */
  if(OptionsBytesStruct1.PCROPEndAddr > OptionsBytesStruct1.PCROPStartAddr)
  {
    /* check if user area are included inside this range */
    if(OptionsBytesStruct1.PCROPStartAddr > APPLICATION_ADDRESS) 
    {
      ProtectedPAGE|= FLASHIF_PROTECTION_PCROPENABLED;
    }
  }
  
  if(OptionsBytesStruct2.PCROPEndAddr > OptionsBytesStruct2.PCROPStartAddr)
  {
    /* check if user area are included inside this range */
    if(OptionsBytesStruct1.PCROPStartAddr > APPLICATION_ADDRESS)
    {
       ProtectedPAGE|= FLASHIF_PROTECTION_PCROPENABLED;
    }
  }
  
  /* check WRP */
  if(OptionsBytesStruct1.WRPEndOffset > OptionsBytesStruct1.WRPStartOffset)
  {
    /* check if area is inside the WRP Range */ 
    if((OptionsBytesStruct1.WRPStartOffset * FLASH_PAGE_SIZE + FLASH_BASE) >= APPLICATION_ADDRESS)
    {
       ProtectedPAGE|= FLASHIF_PROTECTION_WRPENABLED;
    }   
  }

  if(OptionsBytesStruct2.WRPEndOffset > OptionsBytesStruct2.WRPStartOffset)
  {
    /* check if area is inside the WRP Range */ 
    if((OptionsBytesStruct2.WRPStartOffset * FLASH_PAGE_SIZE + FLASH_BASE) >= APPLICATION_ADDRESS)
    {
       ProtectedPAGE|= FLASHIF_PROTECTION_WRPENABLED;
    }   
  }

  if(OptionsBytesStruct3.WRPEndOffset > OptionsBytesStruct3.WRPStartOffset)
  {
    /* check if area is inside the WRP Range */ 
    if((OptionsBytesStruct3.WRPStartOffset * FLASH_PAGE_SIZE + FLASH_BASE + FLASH_PAGE_SIZE * FLASH_PAGE_NBPERBANK) >= APPLICATION_ADDRESS)
    {
       ProtectedPAGE|= FLASHIF_PROTECTION_WRPENABLED;
    }   
  }  
  
  if(OptionsBytesStruct4.WRPEndOffset > OptionsBytesStruct4.WRPStartOffset)
  {
    /* check if area is inside the WRP Range */ 
    if((OptionsBytesStruct4.WRPStartOffset * FLASH_PAGE_SIZE + FLASH_BASE + FLASH_PAGE_SIZE * FLASH_PAGE_NBPERBANK) >= APPLICATION_ADDRESS)
    {
       ProtectedPAGE|= FLASHIF_PROTECTION_WRPENABLED;
    }   
  }
  
  if(OptionsBytesStruct4.RDPLevel == 1)
  {
    ProtectedPAGE|= FLASHIF_PROTECTION_RDPENABLED;
  }
    
  return ProtectedPAGE;
}

/**
  * @brief  Configure the write protection status of user flash area.
  * @param  protectionstate : FLASHIF_WRP_DISABLE or FLASHIF_WRP_ENABLE the protection
  * @retval uint32_t FLASHIF_OK if change is applied.
  */
uint32_t FLASH_If_WriteProtectionConfig(uint32_t protectionstate)
{
  FLASH_OBProgramInitTypeDef OptionsBytesStruct1;
  HAL_StatusTypeDef retr;
  
  /* Unlock the Flash to enable the flash control register access *************/
  retr = HAL_FLASH_Unlock();

  /* Unlock the Options Bytes *************************************************/
  retr|= HAL_FLASH_OB_Unlock();
  
  OptionsBytesStruct1.OptionType = OPTIONBYTE_WRP;
  OptionsBytesStruct1.WRPArea = OB_WRPAREA_BANK1_AREAA;
  if( protectionstate == FLASHIF_WRP_ENABLE)
  {
    /* Enable the WRP protection for all flash BANK1 */
    OptionsBytesStruct1.WRPEndOffset = FLASH_PAGE_NBPERBANK - 1;
    OptionsBytesStruct1.WRPStartOffset = 0x00;
  }
  else
  {
    /* Remove all WRP protection */
    OptionsBytesStruct1.WRPEndOffset = 0x00;
    OptionsBytesStruct1.WRPStartOffset = 0xFF;
  }
  retr|= HAL_FLASHEx_OBProgram(&OptionsBytesStruct1);
  
  OptionsBytesStruct1.OptionType = OPTIONBYTE_WRP;
  OptionsBytesStruct1.WRPArea = OB_WRPAREA_BANK1_AREAB;
  OptionsBytesStruct1.WRPEndOffset = 0x00;
  OptionsBytesStruct1.WRPStartOffset = 0xFF;
  retr|= HAL_FLASHEx_OBProgram(&OptionsBytesStruct1);
    
  OptionsBytesStruct1.OptionType = OPTIONBYTE_WRP;
  OptionsBytesStruct1.WRPArea = OB_WRPAREA_BANK2_AREAA;
  if( protectionstate == FLASHIF_WRP_ENABLE)
  {
    /* Enable the WRP protection for all flash BANK1 */
    OptionsBytesStruct1.WRPEndOffset = FLASH_PAGE_NBPERBANK - 1;
    OptionsBytesStruct1.WRPStartOffset = 0x00;
  }
  else
  {
    /* Remove all WRP protection */
    OptionsBytesStruct1.WRPEndOffset = 0x00;
    OptionsBytesStruct1.WRPStartOffset = 0xFF;
  }
  retr|= HAL_FLASHEx_OBProgram(&OptionsBytesStruct1);
  
  OptionsBytesStruct1.RDPLevel = OB_RDP_LEVEL_0;
  OptionsBytesStruct1.OptionType = OPTIONBYTE_WRP;
  OptionsBytesStruct1.WRPArea = OB_WRPAREA_BANK2_AREAB;
  OptionsBytesStruct1.WRPEndOffset = 0x00;
  OptionsBytesStruct1.WRPStartOffset = 0xFF;
  retr|= HAL_FLASHEx_OBProgram(&OptionsBytesStruct1);
  
  return (retr == HAL_OK ? FLASHIF_OK: FLASHIF_PROTECTION_ERRROR);
}

/* 读取spi flash id. */
uint32_t spi_flash_read_id(void)
{ 
    uint8_t cmd = SPI_CMD_READ_ID;
    uint8_t data[SPI_FLASH_ID_LEN] = {0};

    /* 片选使能. */
    SPI_FLASH_CS_ENABLE();

    /* 发送read id命令. */
    HAL_SPI_Transmit(SpiHandle, &cmd, 1, SPI_BUS_TIMEOUT);

    /* 读取3byte flash id. */
    HAL_SPI_Receive(SpiHandle, data, SPI_FLASH_ID_LEN, SPI_BUS_TIMEOUT);

    /* 片选去使能. */
    SPI_FLASH_CS_DISABLE();

    return (data[0] << 16) | (data[1] << 8) | (data[2]);
}

/* 擦除 spi flash, 根据 cmd 传的不一样, 可以擦除 4K, 32K, 64K数据.
   注意: 这里不检查 addr 和 cmd 的正确性, 请调用者自行保证. */
HAL_StatusTypeDef spi_flash_erase(uint32_t addr, uint8_t erase_cmd)
{
    uint8_t cmd[SPI_CMD_HEADER_LEN] = {0};
    
    /* 写使能. */ 
    HAL_E_RETURN(_spi_flash_write_enable()); 

    /* 等待写使能完成. */
    HAL_E_RETURN(_spi_flash_wait_write());

    /* 片选使能. */
    SPI_FLASH_CS_ENABLE();

    cmd[0] = erase_cmd;
    cmd[1] = (addr >> 16) & 0xFF;
    cmd[2] = (addr >> 8) & 0xFF;;
    cmd[3] = addr & 0xFF;;

    /* 发送擦除扇区命令. */
    HAL_E_RETURN(HAL_SPI_Transmit(SpiHandle, cmd, SPI_CMD_HEADER_LEN, SPI_BUS_TIMEOUT));

    /* 片选去使能. */
    SPI_FLASH_CS_DISABLE();
    
    /* 等待flash busy状态. */
    HAL_E_RETURN(_spi_flash_wait_complete());

    return HAL_OK;
} 

/* 擦除整片 spi flash. */
HAL_StatusTypeDef spi_flash_erase_chip(void)
{
    uint8_t cmd = SPI_CMD_CHIP_ERASE;
    
    /* 写使能. */ 
    HAL_E_RETURN(_spi_flash_write_enable()); 

    /* 等待写使能完成. */
    HAL_E_RETURN(_spi_flash_wait_write());

    /* 片选使能. */
    SPI_FLASH_CS_ENABLE();

    /* 发送擦除扇区命令. */
    HAL_E_RETURN(HAL_SPI_Transmit(SpiHandle, &cmd, 1, SPI_BUS_TIMEOUT));

    /* 片选去使能. */
    SPI_FLASH_CS_DISABLE();
    
    /* 等待flash busy状态. */
    HAL_E_RETURN(_spi_flash_wait_complete());

    return HAL_OK;
} 

/* 从 spi flash 中读取数据. */
HAL_StatusTypeDef spi_flash_read(uint32_t addr, uint8_t* data, uint16_t size)
{
    uint8_t cmd[SPI_CMD_HEADER_LEN] = {0};

    /* 设备刚启动的时候, 读 flash 是全 0, 因为刚上电 flash 自己还没有初始化完成,
       所以在这里等 flash busy 状态, 主要是保证读的时候 flash 是准备好的. */
    HAL_E_RETURN(_spi_flash_wait_complete());

    /* 片选使能. */
    SPI_FLASH_CS_ENABLE();
    
    cmd[0] = SPI_CMD_FLASH_READ;
    cmd[1] = (addr >> 16) & 0xFF;
    cmd[2] = (addr >> 8) & 0xFF;;
    cmd[3] = addr & 0xFF;;

    /* 发送 spi flash 读命令. */
    HAL_E_RETURN(HAL_SPI_Transmit(SpiHandle, cmd, SPI_CMD_HEADER_LEN, SPI_BUS_TIMEOUT));

    /* 读取size大小的数据到data. */
    HAL_E_RETURN(HAL_SPI_Receive(SpiHandle, data, size, SPI_BUS_TIMEOUT));
    
    /* 片选去使能. */
    SPI_FLASH_CS_DISABLE();

    return HAL_OK;
}

/* 向 spi flash 写入数据, 起始地址必须是页 (256Byte) 对齐的. */
HAL_StatusTypeDef spi_flash_write(uint32_t addr, uint8_t *data, uint16_t size)
{ 
    uint8_t pages = 0;
    uint8_t first_bytes = 0;
    uint8_t last_bytes = 0;
    
    /* 这里划分好数据需要写多少页, 写大小. */
    first_bytes = SPI_FLASH_PAGE_SIZE - (addr & (SPI_FLASH_PAGE_SIZE - 1));
    if (first_bytes > size)
        first_bytes = size;

    last_bytes = (size - first_bytes) & (SPI_FLASH_PAGE_SIZE - 1);
    pages = (size - first_bytes - last_bytes) / SPI_FLASH_PAGE_SIZE;

    /* 写第一页. */
    if (first_bytes > 0)
    {
        HAL_E_RETURN(_spi_flash_page_write(addr, data, first_bytes));
        addr += first_bytes;
        data += first_bytes;
    }

    /* 写中间页. */
    while(pages--)
    {
        HAL_E_RETURN(_spi_flash_page_write(addr, data, SPI_FLASH_PAGE_SIZE));
        addr += SPI_FLASH_PAGE_SIZE;
        data += SPI_FLASH_PAGE_SIZE;
    }

    /* 剩余不足一页的部分. */
    if (last_bytes > 0)
        HAL_E_RETURN(_spi_flash_page_write(addr, data, last_bytes));

    return HAL_OK;
}
/************************ (C) COPYRIGHT LandPower *****END OF FILE****/
