/*****************************************************************************
  Freescale MC9328MXL Loader Program.

  Copyright (c) 2005 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 0x0C000000
#define FLASH_LENGTH 0x01000000
#define FLASH_END_ADDRESS (FLASH_START_ADDRESS + FLASH_LENGTH - 1)
#define ADDRESS_IN_FLASH(address) ADDRESS_IN_RANGE(address, FLASH_START_ADDRESS, FLASH_END_ADDRESS)

#define SDRAMC_SDCTL0 (*(volatile unsigned *)0x221000)
#define SDRAMC_SDCTL1 (*(volatile unsigned *)0x221004)
#define SDRAMC_SDRST (*(volatile unsigned *)0x221018)
#define SDRAMC_MISCELLANEOUS (*(volatile unsigned *)0x221014)

#define CMD_NORMAL     (0x81020300)            /* Normal Mode                */
#define CMD_PREC       (CMD_NORMAL+0x10000000) /* Precharge Command          */
#define CMD_AUTO       (CMD_NORMAL+0x20000000) /* Auto Refresh Command       */
#define CMD_LMR        (CMD_NORMAL+0x30000000) /* Load Mode Register Command */
#define CMD_LCR        (CMD_NORMAL+0x60000000) /* LCR Command                */
#define CMD_PROGRAM    (CMD_NORMAL+0x70000000)

/* LCR Command */
#define LCR_READSTATUS    (0x0001C000) // 0x70
#define LCR_ERASE_CONFIRM (0x00008000) // 0x20
#define LCR_ERASE_NVMODE  (0x0000C000) // 0x30
#define LCR_PROG_NVMODE   (0x00028000) // 0xA0
#define LCR_SR_CLEAR      (0x00014000)  //0x50
#define LCR_BLOCK_PROTECT (0x00020000)  //0x60

typedef struct
  {
    unsigned int n;
    unsigned int size;
  } sectdef_t;

static const sectdef_t sectors[] = 
{
  /* 16 * 1MB Sectors */
  { 16, 0x100000 },
  /* Terminator */
  {0, 0}
};

#define FLASH_WRITE(o, v) *(((volatile unsigned short *)FLASH_START_ADDRESS) + o) = v

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;
}


static void
syncflash_precharge_all(void)
{
  SDRAMC_SDCTL1 = CMD_PREC;
  *(volatile unsigned long *)(FLASH_START_ADDRESS + 0x00100000);
}

static unsigned long
syncflash_status(void)
{
  unsigned long sr;
  syncflash_precharge_all();
  SDRAMC_SDCTL1 = CMD_LCR;
  *(volatile unsigned long *)(FLASH_START_ADDRESS + LCR_READSTATUS) = 0;
  SDRAMC_SDCTL1 = CMD_NORMAL;
  sr = *(volatile unsigned long *)FLASH_START_ADDRESS;
  SDRAMC_SDCTL1 = CMD_LCR;
  *(volatile unsigned long *)(FLASH_START_ADDRESS + LCR_SR_CLEAR);
  return sr;
}

flash_write_word(volatile unsigned long *addr, unsigned long value)
{
  syncflash_precharge_all();
  SDRAMC_SDCTL1 = CMD_PROGRAM;
  *addr = value;
  while((syncflash_status() & 0x00800080) != 0x00800080);
  syncflash_precharge_all();
  SDRAMC_SDCTL1 = CMD_NORMAL;
}

static int
flash_erase_sector(volatile unsigned long *sectorAddress)
{
  syncflash_precharge_all(); //
  SDRAMC_SDCTL1 = CMD_NORMAL;
  *sectorAddress;
  SDRAMC_SDCTL1 = CMD_PREC;
  *sectorAddress;
  SDRAMC_SDCTL1 = CMD_LCR;
  *(sectorAddress + LCR_ERASE_CONFIRM / 4) = 0;
  SDRAMC_SDCTL1 = CMD_NORMAL;
  *sectorAddress = 0xD0D0D0D0 ;
  while((syncflash_status() & 0x00800080) != 0x00800080);
  syncflash_precharge_all();
  SDRAMC_SDCTL1 = CMD_NORMAL;

  return 1;
}                                       

static int
flash_erase(unsigned int address, unsigned int length)
{
  unsigned int highAddress = (unsigned int)address + length - 1;
  if (RANGE_OCCLUDES_RANGE((unsigned int)address, highAddress, FLASH_START_ADDRESS, FLASH_END_ADDRESS))
    {
      unsigned int sector = 0;
      const sectdef_t *sdptr = sectors;
      unsigned int sectorStartAddress = FLASH_START_ADDRESS;
      unsigned int i;
      while (sdptr->n)
        {
          for (i = sdptr->n; i ; --i)
            {
              unsigned int sectorEndAddress = sectorStartAddress + sdptr->size - 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((volatile unsigned long *)sectorStartAddress))
                    return 0;
                  
                }
              sectorStartAddress = sectorEndAddress + 1;
              sector++;
            }
          sdptr++;
        }
    }
  return 1;
}

static int
flash_write_byte(volatile unsigned char *addr, unsigned char value)
{ 
  int result = 1;
  unsigned char tmp[4];
  volatile unsigned long *alignedAddr = (unsigned long *)((unsigned int)addr & ~3); 
  *(unsigned long *)tmp = *alignedAddr;
  tmp[(unsigned int)addr & 3] = value;
  if (*(unsigned long *)tmp != *alignedAddr)
    result = flash_write_word(alignedAddr, *(unsigned long *)tmp);
  return result;
}

void
loaderBegin()
{
}

void
loaderEnd()
{
}

#ifdef __BIG_ENDIAN
static void
swapword(unsigned char *w)
{
  unsigned char tmp;
  tmp = w[0];
  w[0] = w[3];
  w[3] = tmp;
  tmp = w[1];
  w[1] = w[2];
  w[2] = tmp;
}
#endif

int
loaderPoke(unsigned char *address, unsigned int length)
{
  if (ADDRESS_IN_FLASH((unsigned int)address))
    {
      // Write aligned data
      if (((unsigned int)address & 3) == 0)
        {
          while (length >= 4)
            {
              unsigned long data = loaderReadWord();
#ifdef __BIG_ENDIAN
              swapword((unsigned char *)&data);
#endif
              flash_write_word((unsigned long *)address, data);
              address += 4;
              length -= 4;
            }
        }

      // Write unaligned data
      while (length)
        {
          unsigned int data = loaderReadWord();
#ifdef __BIG_ENDIAN
          swapword((unsigned char *)&data);
#endif
          int i;
          for(i = 4; i && length; --i)
            {
              flash_write_byte(address++, (unsigned char)data);
              data >>= 8;
              length--;
            }
        }
    }
  else
    {
      while (length)
        {
          unsigned int data = loaderReadWord();
#ifdef __BIG_ENDIAN
          swapword((unsigned char *)&data);
#endif
          int i;
          for(i = 4; i && length; --i)
            {
              *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(address++, c);
      else
        *address++ = c;
    }
  return 1;
}

int
loaderErase(unsigned char *address, unsigned int length)
{
  return flash_erase((unsigned int)address, length);
}

int
loaderEraseAll()
{
  flash_erase(FLASH_START_ADDRESS, FLASH_LENGTH);
  return 1;
}

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

