/*
 * (C) Copyright 2000
 * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
 *
 * Adapted from FADS and other board config files to GTH by thomas@corelatus.com
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

#include <ppcboot.h>
#include <config.h>
#include <watchdog.h>
#include "mpc8xx.h"
#include "ee_access.h"
#include "ee_dev.h"

#ifdef CONFIG_BDM
#undef printf
#define printf(a,...) /* nothing */
#endif


int checkboard (void){
  volatile immap_t *immap = (immap_t *)CFG_IMMR;
  int Id=0;
  int Rev=0;
  u32 Pbdat;
  /* Turn on leds and setup for reading rev and id */

#define PB_OUTS (PB_BLUE_LED|PB_ID_GND)
#define PB_INS  (PB_ID_0|PB_ID_1|PB_ID_2|PB_ID_3|PB_REV_1|PB_REV_0)

  immap->im_cpm.cp_pbpar &= ~(PB_OUTS|PB_INS);

  immap->im_cpm.cp_pbdir &= ~PB_INS;

  immap->im_cpm.cp_pbdir |= PB_OUTS;
  immap->im_cpm.cp_pbodr |= PB_OUTS;
  immap->im_cpm.cp_pbdat &= ~PB_OUTS;

  /* Hold 100 Mbit in reset until fpga is loaded */
  immap->im_ioport.iop_pcpar &= ~PC_ENET100_RESET;
  immap->im_ioport.iop_pcdir |= PC_ENET100_RESET;
  immap->im_ioport.iop_pcso  &= ~PC_ENET100_RESET;
  immap->im_ioport.iop_pcdat &= ~PC_ENET100_RESET;

  /* Turn on front led to show that we are alive */
  immap->im_ioport.iop_papar &= ~PA_FRONT_LED;
  immap->im_ioport.iop_padir |= PA_FRONT_LED;
  immap->im_ioport.iop_paodr |= PA_FRONT_LED;
  immap->im_ioport.iop_padat &= ~PA_FRONT_LED;

  Pbdat = immap->im_cpm.cp_pbdat;

  if(!(Pbdat&PB_ID_0)) Id+=1;
  if(!(Pbdat&PB_ID_1)) Id+=2;
  if(!(Pbdat&PB_ID_2)) Id+=4;
  if(!(Pbdat&PB_ID_3)) Id+=8;

  if(Pbdat&PB_REV_0) Rev +=1;
  if(Pbdat&PB_REV_1) Rev +=2;

  /* Turn ID off since we dont need it anymore */
  immap->im_cpm.cp_pbdat |= PB_ID_GND;

  printf("GTH board, rev %d, id=0x%01x\n",Rev,Id);
  return 0;
}

#define _NOT_USED_ 0xffffffff
const uint sdram_table[] = {
  /* Single read, offset 0 */
  0x0f3dfc04, 0x0eefbc04, 0x01bf7c04, 0x0feafc00,
  0x1fb5fc45, _NOT_USED_, _NOT_USED_, _NOT_USED_,

  /* Burst read, Offset 0x8, 4 reads */
  0x0f3dfc04, 0x0eefbc04, 0x00bf7c04, 0x00ffec00,
  0x00fffc00, 0x01eafc00, 0x1fb5fc00, 0xfffffc45,
  _NOT_USED_, _NOT_USED_, _NOT_USED_, _NOT_USED_,

  /* Not used part of burst read is used for MRS, Offset 0x14 */
  0xefeabc34, 0x1fb57c34, 0xfffffc05, _NOT_USED_,
  /* _NOT_USED_, _NOT_USED_, _NOT_USED_, _NOT_USED_, */

  /* Single write, Offset 0x18 */
  0x0f3dfc04, 0x0eebbc00, 0x01a27c04, 0x1fb5fc45,
  _NOT_USED_, _NOT_USED_, _NOT_USED_, _NOT_USED_,

  /* Burst write, Offset 0x20. 4 writes */
  0x0f3dfc04, 0x0eebbc00, 0x00b77c00, 0x00fffc00,
  0x00fffc00, 0x01eafc04, 0x1fb5fc45, _NOT_USED_,
  _NOT_USED_, _NOT_USED_, _NOT_USED_, _NOT_USED_,

  /* Not used part of burst write is used for precharge, Offset 0x2C */
  0x0ff5fc04, 0xfffffc05, _NOT_USED_, _NOT_USED_,
  /* _NOT_USED_, _NOT_USED_, _NOT_USED_, _NOT_USED_, */

  /* Period timer service. Offset 0x30. Refresh. Wait at least 70 ns after refresh command */
  0x1ffd7c04, 0xfffffc04, 0xfffffc04, 0xfffffc05,
  _NOT_USED_, _NOT_USED_, _NOT_USED_, _NOT_USED_,
  _NOT_USED_, _NOT_USED_, _NOT_USED_, _NOT_USED_,

  /* Exception, Offset 0x3C */
  0xfffffc04, 0xfffffc05, _NOT_USED_, _NOT_USED_ };

const uint fpga_table[] = {
  /* Single read, offset 0 */
  0x0cffec04, 0x00ffec04, 0x00ffec04, 0x00ffec04,
  0x00fffc04, 0x00fffc00, 0x00ffec04, 0xffffec05,

  /* Burst read, Offset 0x8 */
  _NOT_USED_, _NOT_USED_, _NOT_USED_, _NOT_USED_,
  _NOT_USED_, _NOT_USED_, _NOT_USED_, _NOT_USED_,
  _NOT_USED_, _NOT_USED_, _NOT_USED_, _NOT_USED_,
  _NOT_USED_, _NOT_USED_, _NOT_USED_, _NOT_USED_,

  /* Single write, Offset 0x18 */
  0x0cffec04, 0x00ffec04, 0x00ffec04, 0x00ffec04,
  0x00fffc04, 0x00fffc00, 0x00ffec04, 0xffffec05,

  /* Burst write, Offset 0x20. */
  _NOT_USED_, _NOT_USED_, _NOT_USED_, _NOT_USED_,
  _NOT_USED_, _NOT_USED_, _NOT_USED_, _NOT_USED_,
  _NOT_USED_, _NOT_USED_, _NOT_USED_, _NOT_USED_,
  _NOT_USED_, _NOT_USED_, _NOT_USED_, _NOT_USED_,

  /* Period timer service. Offset 0x30. */
  _NOT_USED_, _NOT_USED_, _NOT_USED_, _NOT_USED_,
  _NOT_USED_, _NOT_USED_, _NOT_USED_, _NOT_USED_,
  _NOT_USED_, _NOT_USED_, _NOT_USED_, _NOT_USED_,

  /* Exception, Offset 0x3C */
  0xfffffc04, 0xfffffc05, _NOT_USED_, _NOT_USED_ };

int _initsdram(uint base, uint *noMbytes){
  volatile immap_t *immap = (immap_t *)CFG_IMMR;
  volatile memctl8xx_t *mc = &immap->im_memctl;
  volatile u32 *memptr;

  mc->memc_mptpr  = MPTPR_PTP_DIV16; /* (16-17) */

  /*  SDRAM in UPMA 
      
      GPL_0 is connected instead of A19 to SDRAM.
      According to table 16-17, AMx should be 001, i.e. type 1
      and GPL_0 should hold address A10 when multiplexing */
  
  mc->memc_mamr = (0x2E<<MAMR_PTA_SHIFT)|MAMR_PTAE|MAMR_AMA_TYPE_1|MAMR_G0CLA_A10|
    MAMR_RLFA_1X|MAMR_WLFA_1X|MAMR_TLFA_1X; /* (16-13) */

  upmconfig(UPMA, (uint *)sdram_table,sizeof(sdram_table)/sizeof(uint));

  /* Perform init of sdram ( Datasheet Page 9 )
     Precharge */
  mc->memc_mcr = 0x8000212C; /* run upm a at 0x2C (16-15) */

  /* Run 2 refresh cycles */
  mc->memc_mcr = 0x80002130; /* run upm a at 0x30 (16-15) */
  mc->memc_mcr = 0x80002130; /* run upm a at 0x30 (16-15) */

  /* Set Mode register */
  mc->memc_mar = 0x00000088; /* set mode register (address) to 0x022 (16-17) */
  			     /* Lower 2 bits are not connected to chip */
  mc->memc_mcr = 0x80002114; /* run upm a at 0x14 (16-15) */

  /* CS1, base 0x0000000 - 64 Mbyte, use UPM A */
  mc->memc_or1 = 0xfc000000|OR_CSNT_SAM;
  mc->memc_br1 = BR_MS_UPMA|BR_V; /* SDRAM base always 0 */

  /* Test if we really have 64 MB SDRAM */
  memptr = (u32*)0;
  *memptr = 0;
 
  memptr = (u32*)0x2000000; /* First u32 in upper 32 MB */
  *memptr = 0x12345678;
  
  memptr = (u32*)0;
  if(*memptr==0x12345678){
    /* Wrapped, only have 32 MB */
    mc->memc_or1 = 0xfe000000|OR_CSNT_SAM;
    *noMbytes=32;
  }
  else{
    /* 64 MB */
    *noMbytes=64;
  }

  /* Setup FPGA in UPMB */
  upmconfig(UPMB, (uint *)fpga_table,sizeof(fpga_table)/sizeof(uint));

  /* Enable UPWAITB */
  mc->memc_mbmr = MAMR_GPL_B4DIS; /* (16-13) */

  /* CS2, base FPGA_2_BASE - 4 MByte, use UPM B 32 Bit */
  mc->memc_or2 = 0xffc00000| OR_BI;
  mc->memc_br2 = FPGA_2_BASE|BR_MS_UPMB|BR_V;

  /* CS3, base FPGA_3_BASE - 4 MByte, use UPM B 16 bit */
  mc->memc_or3 = 0xffc00000|OR_BI;
  mc->memc_br3 = FPGA_3_BASE|BR_MS_UPMB|BR_V|BR_PS_16;

  return 0;
}

/* ------------------------------------------------------------------------- */

void _sdramdisable(void)
{
  volatile immap_t     *immap = (immap_t *)CFG_IMMR;
  volatile memctl8xx_t *memctl = &immap->im_memctl;

  memctl->memc_br1 = 0x00000000;

  /* maybe we should turn off upmb here or something */
}

/* ------------------------------------------------------------------------- */

int initsdram(uint base, uint *noMbytes)
{
  *noMbytes = 32;

#ifdef CONFIG_START_IN_RAM
  /* SDRAM is already setup. Dont touch it */
  return 0;
#else

  if(!_initsdram(base, noMbytes))
    {

      return 0;
    }
  else
    {
      _sdramdisable();

      return -1;
    }
#endif
}

long int initdram (int board_type)
{
  u32 *i;
  u32 j;
  u32 k;
  /* GTH only have SDRAM */
  uint	sdramsz;

  if (!initsdram(0x00000000, &sdramsz)) {
    printf("(%u MB SDRAM) ", sdramsz);
  }
  else{
    /********************************
     *SDRAM ERROR, HALT PROCESSOR
     *********************************/
    printf("SDRAM ERROR\n");
    while(1);
  }

#ifndef CONFIG_START_IN_RAM

#define U32_S ((sdramsz<<18)-1)

#if 1
  /* Do a simple memory test */
  for(i=(u32*)0,j=0;(u32)i<U32_S;i+=2,j+=2){
    *i = j+(j<<17);
    *(i+1) = ~(j+(j<<18));
  }

  WATCHDOG_RESET();

  printf(".");

  for(i=(u32*)0,j=0;(u32)i<U32_S;i+=2,j+=2){
    k = *i;
    if(k != (j+(j<<17))){
      printf("Mem test error, i=0x%x, 0x%x\n, 0x%x",(u32)i,j,k);
      while(1);
    }
    k = *(i+1);
    if(k != ~(j+(j<<18))){
      printf("Mem test error(+1), i=0x%x, 0x%x\n, 0x%x",(u32)i+1,j,k);
      while(1);
    }
  }
#endif

  WATCHDOG_RESET();

  /* Clear memory */
  for(i=(u32*)0;(u32)i<U32_S;i++){
    *i = 0;
  }
#endif /* !start in ram */

  WATCHDOG_RESET();

  return (sdramsz << 20);
}

#define POWER_OFFSET    0xF0000
#define SW_WATCHDOG_REASON 13

#define BOOTDATA_OFFSET 0xF8000
#define MAX_ATTEMPTS 5

#define FAILSAFE_BOOT 1
#define SYSTEM_BOOT   2

#define WRITE_FLASH16(a, d)      \
do                              \
{                               \
  *((volatile u16 *) (a)) = (d);\
 } while(0)

static void write_bootdata( volatile u16* addr, u8 System, u8 Count ){
  u16 data;
  volatile u16 *flash = (u16*)(CFG_FLASH_BASE);

  if((System!=FAILSAFE_BOOT)&(System!=SYSTEM_BOOT)){
    printf("Invalid system data %u, setting failsafe\n",System);
    System=FAILSAFE_BOOT;
  }

  if((Count<1)|(Count>MAX_ATTEMPTS)){
    printf("Invalid boot count %u, setting 1\n",Count);
    Count=1;
  }

  if(System==FAILSAFE_BOOT){
    printf("Setting failsafe boot in flash\n");
  }
  else{
    printf("Setting system boot in flash\n");
  }
  printf("Boot attempt %d\n",Count); 
  
  data = (System<<8)|Count;
  /* AMD 16 bit */
  WRITE_FLASH16(&flash[0x555], 0xAAAA);
  WRITE_FLASH16(&flash[0x2AA], 0x5555);
  WRITE_FLASH16(&flash[0x555], 0xA0A0);
  
  WRITE_FLASH16(addr, data);
}

static void maybe_update_restart_reason(volatile u32 *addr32){
  /* Update addr if sw wd restart */
  volatile u16 *flash = (u16*)(CFG_FLASH_BASE);
  volatile u16 *addr_16 = (u16*)addr32;
  u32 rsr;

  /* Dont reset register now */
  rsr = ((volatile immap_t *)CFG_IMMR)->im_clkrst.car_rsr;

  rsr >>=24;
  
  if(rsr&0x10){
    /* Was really a sw wd restart, update reason */

    printf("Last restart by software watchdog\n");
    
    /* AMD 16 bit */
    WRITE_FLASH16(&flash[0x555], 0xAAAA);
    WRITE_FLASH16(&flash[0x2AA], 0x5555);
    WRITE_FLASH16(&flash[0x555], 0xA0A0);
    
    WRITE_FLASH16(addr_16, 0);

    udelay(1000);

    WATCHDOG_RESET();
    
    /* AMD 16 bit */
    WRITE_FLASH16(&flash[0x555], 0xAAAA);
    WRITE_FLASH16(&flash[0x2AA], 0x5555);
    WRITE_FLASH16(&flash[0x555], 0xA0A0);
    
    WRITE_FLASH16(addr_16+1, SW_WATCHDOG_REASON);

  }
}

static void check_restart_reason(void){
  /* Update restart reason if sw watchdog was
     triggered */

  int i;
  volatile u32 *raddr;

  raddr = (u32*)(CFG_FLASH_BASE+POWER_OFFSET);

  if(*raddr==0xFFFFFFFF){
    /* Nothing written */
    maybe_update_restart_reason(raddr);
  }
  else{
    /* Search for latest written reason */
    i=0;
    while((*(raddr+2)!=0xFFFFFFFF)&(i<2000)){
      raddr+=2;
      i++;
    }
    if(i>=2000){
      /* Whoa, dont write any more */
      printf("*** No free restart reason found ***\n");
    }
    else{
      /* Check if written */
      if(*raddr==0){
	/* Erased by kernel, no new reason written */
	maybe_update_restart_reason(raddr+2);
      }
    }
  }
}

static void check_boot_tries(void){
  /* Count the number of boot attemps
     switch system if too many */

  int i;
  volatile u16 *addr;
  volatile u16 data;
  int failsafe = 1;
  u8 system;
  u8 count;

  addr = (u16*)(CFG_FLASH_BASE+BOOTDATA_OFFSET);

  if(*addr==0xFFFF){
    printf("*** No bootdata exists. ***\n");
    write_bootdata( addr, FAILSAFE_BOOT, 1 );
  }
  else{
    /* Search for latest written bootdata */
    i=0;
    while((*(addr+1)!=0xFFFF)&(i<8000)){
      addr++;
      i++;
    }
    if(i>=8000){
      /* Whoa, dont write any more */
      printf("*** No bootdata found. Not updating flash***\n");
    }
    else{
      /* See how many times we have tried to boot real system */
      data = *addr;
      system=data>>8;
      count=data&0xFF;
      if((system!=SYSTEM_BOOT)&(system!=FAILSAFE_BOOT)){
	printf("*** Wrong system %d\n",system);
	system=FAILSAFE_BOOT;
	count=1;
      }
      else{
	switch(count){
	case 0:
	case 1:
	case 2:
	case 3:
	case 4:
	  /* Try same system again if needed */
	  count++;
	  break;
	  
	case 5:
	  /* Switch system and reset tries */
	  count=1;
	  system=3-system;
	  printf("***Too many boot attempts, switching system***\n"); 
	  break;
	default:
	  /* Switch system, start over and hope it works */
	  printf("***Unexpected data on addr 0x%x, %u***\n",(u32)addr,data);
	  count=1;
	  system=3-system;
	}
      }
      write_bootdata( addr+1, system, count );
      if(system==SYSTEM_BOOT){
	failsafe=0;
      }
    }
  }
  if(failsafe){
    printf("Booting failsafe system\n");
    setenv("bootargs","panic=1 root=/dev/hda7");
    setenv("bootcmd","disk 100000 0:5;bootm 100000");
  }
  else{
    printf("Using normal system\n");
    setenv("bootargs","panic=1 root=/dev/hda4");
    setenv("bootcmd","disk 100000 0:2;bootm 100000");
  }
}

void misc_init_r(bd_t *bd){
  u8 Rx[80];
  u8 Tx[5];
  int page;
  int read=0;
  volatile immap_t *immap = (immap_t *)CFG_IMMR;

  /* Kill fpga */
  immap->im_ioport.iop_papar &= ~(PA_FL_CONFIG|PA_FL_CE);
  immap->im_ioport.iop_padir |= (PA_FL_CONFIG|PA_FL_CE);
  immap->im_ioport.iop_paodr &= ~(PA_FL_CONFIG|PA_FL_CE);

  /* Enable fpga, active low */
  immap->im_ioport.iop_padat &= ~PA_FL_CE;

  /* Start configuration */
  immap->im_ioport.iop_padat &= ~PA_FL_CONFIG;
  udelay(2);

  immap->im_ioport.iop_padat |= (PA_FL_CONFIG|PA_FL_CE);

  /* Check if we need to boot failsafe system */
  check_boot_tries();

  /* Check if we need to update restart reason */
  check_restart_reason();
  
  if(ee_init_data()){
    printf("EEPROM init failed\n");
    return;
  }
  
  /* Read the pages where ethernet address is stored */

  for(page=EE_USER_PAGE_0;page<=EE_USER_PAGE_0+2;page++){
    /* Copy from nvram to scratchpad */
    Tx[0]=RECALL_MEMORY;
    Tx[1]=page;
    if(ee_do_command(Tx,2,NULL,0,TRUE)){
      printf("EE user page %d recall failed\n",page);
      return;
    }
    
    Tx[0]=READ_SCRATCHPAD;
    if(ee_do_command(Tx,2,Rx+read,9,TRUE)){
      printf("EE user page %d read failed\n",page);
      return;
    }
    /* Crc in 9:th byte */
    if(!ee_crc_ok(Rx+read,8,*(Rx+read+8))){
      printf("EE read failed, page %d. CRC error\n", page);
      return;
    }
    read+=8;
  }

  /* Add eos after eth addr*/
  Rx[17]=0;

  printf("Ethernet addr read from eeprom: %s\n\n",Rx);

  if((Rx[2]!=':')|
     (Rx[5]!=':')|
     (Rx[8]!=':')|
     (Rx[11]!=':')|
     (Rx[14]!=':')){
    printf("*** ethernet addr invalid, using default ***\n");
  }
  else{
    setenv("ethaddr",Rx);
  }
}


