/*****************************************************************************
 * MPE ARM7D FLASH Loader Program. NOTE: This program does not fit into      *
 * internal SRAM in Debug configuration. In order to use Debug configuration *
 * the size of .comm_buffer section must be reduced.                         *
 *                                                                           *
 * Copyright (c) 2003 Rowley Associates Limited.                             *
 *                                                                           *
 * This file may be distributed under the terms of the License Agreement     *
 * provided with this software.                                              *
 *                                                                           *
 * THIS FILE IS PROVIDED AS IS WITH NO WARRANTY OF ANY KIND, INCLUDING THE   *
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. *
 *****************************************************************************/

#include "../loader/loader.h"

#define FLASH_START_ADDRESS (0x01000000)
#define FLASH_CHIP_SIZE (0x00800000)
#define FLASH_CHIP_COUNT (2)
#define FLASH_SECTOR_SIZE (0x10000)
#define FLASH_SIZE (FLASH_CHIP_SIZE * FLASH_CHIP_COUNT)
#define FLASH_END_ADDRESS (FLASH_START_ADDRESS + FLASH_SIZE - 1)
#define FLASH_CHIP_START_ADDRESS(addr) (addr & (~(FLASH_CHIP_SIZE - 1)))
#define FLASH_SECTOR_START_ADDRESS(addr) (addr & (~(FLASH_SECTOR_SIZE - 1)))
#define ADDRESS_IN_FLASH(addr) ((addr >= FLASH_START_ADDRESS) && (addr <= FLASH_END_ADDRESS))

#define FLASH_WRITE(addr, offset, val) ((volatile unsigned char *)addr)[offset] = val
#define FLASH_READ(addr, offset) ((volatile unsigned char *)addr)[offset]

#define FLASH_BUSY(addr) ((FLASH_READ(addr, 0) ^ FLASH_READ(addr, 0)) != 0)
#define FLASH_RESET() *(volatile unsigned char *)flashStartAddress = 0xF0;

static void
flash_erase_chip(unsigned int address)
{
  FLASH_WRITE(FLASH_CHIP_START_ADDRESS(address), 0x5555, 0xAA);
  FLASH_WRITE(FLASH_CHIP_START_ADDRESS(address), 0x2AAA, 0x55);
  FLASH_WRITE(FLASH_CHIP_START_ADDRESS(address), 0x5555, 0x80);
  FLASH_WRITE(FLASH_CHIP_START_ADDRESS(address), 0x5555, 0xAA);
  FLASH_WRITE(FLASH_CHIP_START_ADDRESS(address), 0x2AAA, 0x55);
  FLASH_WRITE(FLASH_CHIP_START_ADDRESS(address), 0x5555, 0x10);
  while (FLASH_BUSY(address));
  FLASH_WRITE(FLASH_CHIP_START_ADDRESS(address), 0, 0xF0);
}

static void
flash_erase_sector(unsigned int address)
{
  FLASH_WRITE(FLASH_CHIP_START_ADDRESS(address), 0x5555, 0xAA);
  FLASH_WRITE(FLASH_CHIP_START_ADDRESS(address), 0x2AAA, 0x55);
  FLASH_WRITE(FLASH_CHIP_START_ADDRESS(address), 0x5555, 0x80);
  FLASH_WRITE(FLASH_CHIP_START_ADDRESS(address), 0x5555, 0xAA);
  FLASH_WRITE(FLASH_CHIP_START_ADDRESS(address), 0x2AAA, 0x55);
  FLASH_WRITE(address, 0, 0x30);
  while (FLASH_BUSY(address));
  FLASH_WRITE(FLASH_CHIP_START_ADDRESS(address), 0, 0xF0);
}

static void
flash_write_byte(unsigned int address, unsigned char v)
{
  FLASH_WRITE(FLASH_CHIP_START_ADDRESS(address), 0x5555, 0xAA);
  FLASH_WRITE(FLASH_CHIP_START_ADDRESS(address), 0x2AAA, 0x55);
  FLASH_WRITE(FLASH_CHIP_START_ADDRESS(address), 0x5555, 0xA0);
  FLASH_WRITE(address, 0, v);
  while (FLASH_BUSY(address));
  FLASH_WRITE(FLASH_CHIP_START_ADDRESS(address), 0, 0xF0);
}

static int
is_erased(unsigned int address, unsigned int length)
{
  while (address & 3)
    {
      if (*(unsigned char *)address != 0xFF)
        return 0;
      length--;
      address++;
    }
  while (length > 4)
    {
      if (*(unsigned long *)address != 0xFFFFFFFF)
        return 0;
      length -= 4;
      address += 4;
    }
  while (length)
    {
      if (*(unsigned char *)address != 0xFF)
        return 0;
      length--;
      address++;
    }
  return 1;
}

void
loaderBegin()
{
}

void
loaderEnd()
{
}

int
loaderPoke(unsigned char *address, unsigned int length)
{
  while(length)
    {
      unsigned int data = loaderReadWord();
      int i;
      for (i = 4; i && length; --i)
        {
          if (ADDRESS_IN_FLASH((unsigned int)address))
            flash_write_byte((unsigned int)address++, (unsigned char)data);
          else           
            *address++ = (unsigned char)data;                  
          data >>= 8;
          length--;
        }
    }
  return 1;
}

int
loaderMemset(unsigned char *address, unsigned int length,  unsigned char c)
{
  while (length--)
    {
      if (ADDRESS_IN_FLASH((unsigned int)address))
        flash_write_byte((unsigned int)address++, (unsigned char)c);
      else
        *address++ = (unsigned char)c;
    }
  return 1;
}

int
loaderErase(unsigned char *address, unsigned int length)
{
  unsigned int sectorAddress = FLASH_SECTOR_START_ADDRESS((unsigned int)address);
  int sectors = ((length - 1 + ((unsigned int)address - sectorAddress)) / FLASH_SECTOR_SIZE) + 1;
  while (sectors--)
    {
      if ((sectorAddress >= FLASH_START_ADDRESS && sectorAddress <= FLASH_END_ADDRESS) &&
          (!is_erased((unsigned int)address, length < FLASH_SECTOR_SIZE ? length : FLASH_SECTOR_SIZE)))
        flash_erase_sector(sectorAddress);
      sectorAddress += FLASH_SECTOR_SIZE;
      address += FLASH_SECTOR_SIZE;
      length -= FLASH_SECTOR_SIZE;
    }
  return 1;
}

int
loaderEraseAll()
{
  int i;
  unsigned int address = FLASH_START_ADDRESS;
  for (i = FLASH_CHIP_COUNT; i; --i)
    {
      flash_erase_chip(address);
      address += FLASH_CHIP_SIZE;
    }
  return 1;
}

int
loaderSetParameter(unsigned int parameter, unsigned int value)
{
  return 1;
}

