/*****************************************************************************
 * FLASH Loader for iSYSTEM ITPXA250 (Toshiba TC58FVB800)                    *
 *                                                                           *
 * Copyright (c) 2004 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 0x00100000
#define FLASH_LENGTH 0x00100000
#define FLASH_END_ADDRESS FLASH_START_ADDRESS + FLASH_LENGTH

#define FLASH_BUSY(ptr) ((*ptr ^ *ptr) != 0)
#define FLASH_RESET() *((volatile unsigned short *)FLASH_START_ADDRESS) = 0xF0
#define FLASH_WRITE(addr, val) *((volatile unsigned short *)addr) = val

#define ADDRESS_IN_FLASH(a) ADDRESS_IN_RANGE((unsigned long)a, FLASH_START_ADDRESS, FLASH_END_ADDRESS)

#define SECTORS 19

const unsigned int sectorAddresses[SECTORS + 1] =
{
  0x00000000, 0x00004000, 0x00006000, 0x00008000, 0x00010000, 0x00020000, 
  0x00030000, 0x00040000, 0x00050000, 0x00060000, 0x00070000, 0x00080000, 
  0x00090000, 0x000A0000, 0x000B0000, 0x000C0000, 0x000D0000, 0x000E0000, 
  0x000F0000, 
  0x00100000, 
};

static int
flash_write_uint16(volatile unsigned short *address, unsigned short data)
{
  FLASH_WRITE(FLASH_START_ADDRESS + 0x5555, 0xAA);
  FLASH_WRITE(FLASH_START_ADDRESS + 0x2AAA, 0x55);
  FLASH_WRITE(FLASH_START_ADDRESS + 0x5555, 0xA0);
  *address = data;
  while(FLASH_BUSY((volatile unsigned short *)FLASH_START_ADDRESS));
  FLASH_RESET();
  return 1;
}

static int
flash_write_byte(volatile unsigned char *address, unsigned char data)
{
  int result = 1;
  unsigned char tmp[2];
  unsigned short *alignedAddress = (unsigned short *)((unsigned int)address & ~1);
  *(unsigned short *)tmp = *alignedAddress;
  tmp[(unsigned int)address & 1] = data;
  if (*(unsigned short *)tmp != *alignedAddress)
    result = flash_write_uint16((unsigned short *)alignedAddress, *(unsigned short *)tmp);
  return result;
}

static int
flash_erase_sector(volatile unsigned short *address)
{
  FLASH_WRITE(FLASH_START_ADDRESS + 0x5555, 0xAA);
  FLASH_WRITE(FLASH_START_ADDRESS + 0x2AAA, 0x55);
  FLASH_WRITE(FLASH_START_ADDRESS + 0x5555, 0x80);
  FLASH_WRITE(FLASH_START_ADDRESS + 0x5555, 0xAA);
  FLASH_WRITE(FLASH_START_ADDRESS + 0x2AAA, 0x55);
  *address = 0x30;
  while(FLASH_BUSY(address));
  FLASH_RESET();
  return 1;
}

static int
flash_erase_all(unsigned short *address)
{
  FLASH_WRITE(FLASH_START_ADDRESS + 0x5555, 0xAA);
  FLASH_WRITE(FLASH_START_ADDRESS + 0x2AAA, 0x55);
  FLASH_WRITE(FLASH_START_ADDRESS + 0x5555, 0x80);
  FLASH_WRITE(FLASH_START_ADDRESS + 0x5555, 0xAA);
  FLASH_WRITE(FLASH_START_ADDRESS + 0x2AAA, 0x55);
  FLASH_WRITE(FLASH_START_ADDRESS + 0x5555, 0x10);
  while(FLASH_BUSY((volatile unsigned short *)FLASH_START_ADDRESS));
  FLASH_RESET();
  return 1;
}

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

void
loaderBegin()
{
}

void
loaderEnd()
{
}

int
loaderPoke(unsigned char *address, unsigned int length)
{
  int res = 1;
  if (ADDRESS_IN_FLASH(address))
    {
      // Write aligned data
      if (((unsigned int)address & 1) == 0)
        {
          while (length >= 4)
            {
              unsigned int data = loaderReadWord();
              res &= flash_write_uint16((unsigned short *)address,  (unsigned short)data);
              address += 2;
              data >>= 16;
              flash_write_uint16((unsigned short *)address,  (unsigned short)data);
              address += 2;
              length -= 4;
            }
        }
    
      // Write unaligned data
      while (length)
        {
          unsigned int data = loaderReadWord();
          int i;
          for(i = 4; i && length; --i)
            {
              res &= flash_write_byte(address++, (unsigned char)data);
              data >>= 8;
              length--;
            }
        }
    }
  else
    {
      while(length)
        {
          unsigned int data = loaderReadWord();
          int i;
          for(i = 4; i && length; --i)
            {
              *address++ = (unsigned char)data;
              data >>= 8;
              length--;
            }
        }
    }
  return res;
}

int
loaderMemset(unsigned char *address, unsigned int length,  unsigned char c)
{
  if (ADDRESS_IN_FLASH(address))
    {
      // Write aligned data
      unsigned short sc = c | (c << 8);
      if (((unsigned int)address & 1) == 0)
        {
          while (length >= 2)
            {
              if (!flash_write_uint16((unsigned short *)address,  sc))
                return 0;
              address += 2;
              length -= 2;
            }
        }
    
      // Write unaligned data
      while (length)
        {
          if (!flash_write_byte(address, c))
            return 0;
          ++address;
          --length;
        }
    }
  else
    {
      while(length--)
        *address++ = (unsigned char)c;
    }
  return 1;
}

int
loaderErase(unsigned char *address, unsigned int length)
{
  unsigned int i;
  unsigned int highAddress = (unsigned int)address + length - 1;
  for (i = 0; i < SECTORS; ++i)
    {
      unsigned int sectorStartAddress = FLASH_START_ADDRESS + sectorAddresses[i];
      unsigned int sectorEndAddress = FLASH_START_ADDRESS + sectorAddresses[i + 1] - 1;
      if (RANGE_OCCLUDES_RANGE((unsigned int)address, highAddress, sectorStartAddress, sectorEndAddress) &&
          !is_erased((unsigned int)address > sectorStartAddress ? (unsigned int)address : sectorStartAddress, highAddress < sectorEndAddress ? highAddress : sectorEndAddress))
        {
          /* Sector hasn't been erased and it needs to be, erase it. */
          if (!flash_erase_sector((unsigned short *)sectorStartAddress))
            return 0;
        }
    }
  return 1;
}

int
loaderEraseAll()
{
  return flash_erase_all((unsigned short *)FLASH_START_ADDRESS);
}

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

