/*****************************************************************************
 * Loader for Evaluator-7T with Flash memory located 0x00000000 - 0x0007FFFF *
 *                                                                           *
 * Copyright (c) 2001, 2002 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 ROMCON0 ((unsigned int *)0x003FF3014)

#define FLASH_SECTOR_SIZE   0x1000
#define FLASH_TOTAL_SIZE    0x80000
#define FLASH_BUSY(ptr) ((*ptr ^ *ptr) != 0)
#define FLASH_RESET() *(volatile unsigned short *)flashStartAddress = 0xF0;
#define FLASH_WRITE(addr, val) ((volatile unsigned short *)flashStartAddress)[addr] = val
#define ADDRESS_IN_FLASH(addr) (((unsigned int)addr >= flashStartAddress) && ((unsigned int)addr <= flashEndAddress))

#define LITTLE_ENDIAN

#include "__armlib.h"

static int i, flashStartAddress, flashEndAddress;

static void
flash_erase_chip(unsigned int *flashStartAddress)
{
  FLASH_WRITE(0x5555, 0xAA);
  FLASH_WRITE(0x2AAA, 0x55);
  FLASH_WRITE(0x5555, 0x80);
  FLASH_WRITE(0x5555, 0xAA);
  FLASH_WRITE(0x2AAA, 0x55);
  FLASH_WRITE(0x5555, 0x10);
  while(FLASH_BUSY((volatile unsigned short *)flashStartAddress));
  FLASH_RESET();
}

static void
flash_erase_sector(unsigned int *flashStartAddress, volatile unsigned short *sector_start)
{
  FLASH_WRITE(0x5555, 0xAA);
  FLASH_WRITE(0x2AAA, 0x55);
  FLASH_WRITE(0x5555, 0x80);
  FLASH_WRITE(0x5555, 0xAA);
  FLASH_WRITE(0x2AAA, 0x55);
  *sector_start = 0x30;
  while(FLASH_BUSY(sector_start));
  FLASH_RESET();
}

static void
flash_write_ushort(unsigned int *flashStartAddress, volatile unsigned short *address, unsigned short v)
{
  FLASH_WRITE(0x5555, 0xAA);
  FLASH_WRITE(0x2AAA, 0x55);
  FLASH_WRITE(0x5555, 0xA0);
  *address = v;
  while(FLASH_BUSY(address));
  FLASH_RESET();
}

static void
flash_write_uint(unsigned int *flashStartAddress, volatile unsigned int *address, unsigned int v)
{
  flash_write_ushort(flashStartAddress, (unsigned short *)address, (unsigned short)v);
  flash_write_ushort(flashStartAddress, (unsigned short *)(address) + 1, (unsigned short)(v >> 16));
}

static void
flash_write_byte(unsigned int *flashStartAddress, volatile unsigned char *address, unsigned char v)
{
  volatile unsigned short *usAddress = (unsigned short *)((unsigned int)address & ~1);
  unsigned short data = *usAddress;
#ifdef LITTLE_ENDIAN
  if(address == (unsigned char *)usAddress)
    data = (data & 0xFF00) | v;
  else
    data = (data & 0x00FF) | (v << 8);
#else
  if(address == (unsigned char *)usAddress)
    data = (data & 0x00FF) | (v << 8);
  else
    data = (data & 0xFF00) | v;
#endif
  flash_write_ushort(flashStartAddress, usAddress, data);  
}

static int
is_erased(unsigned int address, unsigned int highAddress)
{
  unsigned int length = highAddress - address + 1;
  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()
{
  /* Calculate FLASH's address range */
  flashStartAddress = (*ROMCON0 & 0x000FFC00) << 6;
  flashEndAddress = flashStartAddress + FLASH_TOTAL_SIZE - 1;
}

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(address))
            flash_write_byte((unsigned int *)flashStartAddress, 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(address))
        flash_write_byte((unsigned int *)flashStartAddress, address++, (unsigned char)c);
      else
        *address++ = (unsigned char)c;
    }
  return 1;
}

int
loaderErase(unsigned char *address, unsigned int length)
{
  unsigned int ss, se;
  unsigned int highAddress = (unsigned int)address + length;
  if (RANGE_OCCLUDES_RANGE((unsigned int)address, highAddress, flashStartAddress, flashEndAddress))
    {
      for(ss = flashStartAddress; ss < flashEndAddress; ss += FLASH_SECTOR_SIZE)
        {
          se = ss + FLASH_SECTOR_SIZE - 1;
          if (RANGE_OCCLUDES_RANGE((unsigned int)address, highAddress, ss, se))
            {
              if (!is_erased((unsigned int)address > ss ? (unsigned int)address : ss, highAddress < se ? highAddress : se))
                {
                  /* Sector needs erasing, erase it */
                  flash_erase_sector((unsigned int *)flashStartAddress, (unsigned short *)ss);
                } 
            }
        }
    }
  return 1;
}

int
loaderEraseAll()
{
  if (!is_erased(flashStartAddress, flashEndAddress))
    flash_erase_chip((unsigned int *)flashStartAddress);
  return 1;
}

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


