/*
  support_esptool.ino - esptool support for Tasmota

  Copyright (C) 2021  Theo Arends

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifdef ESP8266
#define USE_ESPTOOL
#endif  // ESP8266

#ifdef USE_ESPTOOL
/*********************************************************************************************\
 * EspTool Erase function based on Version 2.8
 *
 * Source: https://github.com/espressif/esptool/blob/master/flasher_stub
\*********************************************************************************************/

// *** flasher_stub/include/soc_support.h
#define READ_REG(REG) (*((volatile uint32_t *)(REG)))
#define WRITE_REG(REG, VAL) *((volatile uint32_t *)(REG)) = (VAL)
#define REG_SET_MASK(reg, mask) WRITE_REG((reg), (READ_REG(reg)|(mask)))

#define SPI_BASE_REG       0x60000200             // SPI peripheral 0

#define SPI_CMD_REG       (SPI_BASE_REG + 0x00)
#define SPI_FLASH_RDSR    (1<<27)
#define SPI_FLASH_SE      (1<<24)
#define SPI_FLASH_BE      (1<<23)
#define SPI_FLASH_WREN    (1<<30)

#define SPI_ADDR_REG      (SPI_BASE_REG + 0x04)
#define SPI_CTRL_REG      (SPI_BASE_REG + 0x08)
#define SPI_RD_STATUS_REG (SPI_BASE_REG + 0x10)
#define SPI_W0_REG        (SPI_BASE_REG + 0x40)
#define SPI_EXT2_REG      (SPI_BASE_REG + 0xF8)

#define SPI_ST 0x7                                // Done state value

// *** flasher_stub/include/stub_flasher.h
#define SECTORS_PER_BLOCK (FLASH_BLOCK_SIZE / SPI_FLASH_SEC_SIZE)

// *** flasher_stub/stub_write_flash.c
static const uint32_t STATUS_WIP_BIT = (1 << 0);  // SPI status bits

// Wait for the SPI state machine to be ready, ie no command in progress in the internal host.
inline static void spi_wait_ready(void)
{
  while((READ_REG(SPI_EXT2_REG) & SPI_ST)) { }    // Wait for SPI state machine ready
}

// Returns true if the spiflash is ready for its next write operation.
// Doesn't block, except for the SPI state machine to finish any previous SPI host operation.
static bool spiflash_is_ready(void)
{
  spi_wait_ready();
  WRITE_REG(SPI_RD_STATUS_REG, 0);
  WRITE_REG(SPI_CMD_REG, SPI_FLASH_RDSR);         // Issue read status command
  while(READ_REG(SPI_CMD_REG) != 0) { }
  uint32_t status_value = READ_REG(SPI_RD_STATUS_REG);
  return (status_value & STATUS_WIP_BIT) == 0;
}

static void spi_write_enable(void)
{
  while(!spiflash_is_ready()) { }
  WRITE_REG(SPI_CMD_REG, SPI_FLASH_WREN);
  while(READ_REG(SPI_CMD_REG) != 0) { }
}

bool EsptoolEraseSector(uint32_t sector)
{
  spi_write_enable();
  spi_wait_ready();

  WRITE_REG(SPI_ADDR_REG, (sector * SPI_FLASH_SEC_SIZE) & 0xffffff);
  WRITE_REG(SPI_CMD_REG, SPI_FLASH_SE);           // Sector erase, 4KB
  while(READ_REG(SPI_CMD_REG) != 0) { }
  while(!spiflash_is_ready()) { }

  return true;
}

void EsptoolErase(uint32_t start_sector, uint32_t end_sector)
{
  AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_ERASE " from 0x%08X to 0x%08X"), start_sector * SPI_FLASH_SEC_SIZE, (end_sector * SPI_FLASH_SEC_SIZE) -1);

  int next_erase_sector = start_sector;
  int remaining_erase_sector = end_sector - start_sector;

  while (remaining_erase_sector > 0) {
    spi_write_enable();

    uint32_t command = SPI_FLASH_SE;              // Sector erase, 4kB
    uint32_t sectors_to_erase = 1;
    if (remaining_erase_sector >= SECTORS_PER_BLOCK &&
        next_erase_sector % SECTORS_PER_BLOCK == 0) {
      command = SPI_FLASH_BE;                     // Block erase 64kB if we have space for it
      sectors_to_erase = SECTORS_PER_BLOCK;
    }
    uint32_t addr = next_erase_sector * SPI_FLASH_SEC_SIZE;

    spi_wait_ready();
    WRITE_REG(SPI_ADDR_REG, addr & 0xffffff);
    WRITE_REG(SPI_CMD_REG, command);              // Perform erase, 4kB or 65kB
    while(READ_REG(SPI_CMD_REG) != 0) { }
    remaining_erase_sector -= sectors_to_erase;
    next_erase_sector += sectors_to_erase;

    while (!spiflash_is_ready()) { }
    yield();
    OsWatchLoop();
  }
}

#endif  // USE_ESPTOOL
