#define VERSION         "$Id: ihex_boot.c,v 1.2 2008/09/18 15:13:34 jack Exp jack $"
#define KERNEL_IS_AT    0xff810000

/*
 * Virtual Lab Linux Bootloader
 * Licensed under GPLv2.
 * Copyright 2008 (c) Jack Whitham
 * Copyright 2001-2004 (c) MontaVista, Software, Inc
 */

#include "xparameters.h"
#include "stdio.h"
#include "xutil.h"

#define LAUNCH          1
#define RUN             0

typedef void (* Target_Fn) ( int , int ) ;

#define input()             ( (unsigned) inbyte () )

#define SPIN_LINE_SHIFT     4   /* 2^4 = 16 lines received before spin */
#define SPIN_TABLE_SHIFT    2   /* 2^2 = 4 items in table */

static const char g_spin_table [ 1 << SPIN_TABLE_SHIFT ][ 4 ] = {
        "\r|", "\r/", "\r-", "\r\\" } ;
static unsigned             g_sum ;
static unsigned long        timebase_period_ns = 60 ;



static void crash ( void )
{
    print ( " Abort - 'R' to reboot.\r\n" ) ;
    while ( 1 )
    {
        if ( input () == 'R' )
        {
            /* soft reboot */
            asm volatile ( 
                ".global _start\n"
                "b _start\n" ) ;
        }
    }
}

static unsigned get_hex_nibble ( void )
{
    unsigned ch = input () ;
    char cstr [ 2 ] ;

    cstr [ 0 ] = (char) ch ;
    ch -= 'a' ;
    if ( ch < 6 ) return ch + 10 ;
    ch += 'a' - 'A' ;
    if ( ch < 6 ) return ch + 10 ;
    ch += 'A' - '0' ;
    if ( ch < 10 ) return ch ;

    print ( "Invalid hex value. " ) ;
    cstr [ 1 ] = 0 ;
    print ( cstr ) ;
    crash () ;
    return 0 ;
}

static unsigned get_hex_byte ( void )
{
    unsigned rc ;
    rc = ( get_hex_nibble () << 4 ) | get_hex_nibble () ;
    g_sum += rc ;
    return rc ;
}

static void inv_cache ( void )
{
    static const unsigned long line_size = 32;
    static const unsigned long congruence_classes = 256;
    unsigned long addr = 0 ;
    unsigned temp = 0 ;

    /* Ensure caches are disabled */
    asm volatile (
        "isync\n"
        "mtspr 1008,%0\n" /* HID0 */
        "sync\n"
        "isync\n" 
    /* Invalidate icache */
        "iccci %0, %0\n" : "=&r"(temp) : "0"(temp) ) ;

    /* Invalidate dcache */
    for ( addr = 0 ; 
            addr < ( congruence_classes * line_size ) ;
            addr += line_size ) 
    {
        asm volatile ( "dccci 0,%0" : : "b"(addr) ) ;
    }
}

static void led_out ( int value )
{
	XIo_Out32 ( XPAR_BASEBOARD_LEDS_3BIT_BASEADDR + 4, 0 ); /* tristate = output */
	XIo_Out32 ( XPAR_BASEBOARD_LEDS_3BIT_BASEADDR, ~value );
}


static void launch ( unsigned entrance )
{
    Target_Fn       fn ;

    print ( "launching\r\n" ) ;
    inv_cache () ;
	 led_out ( 3 ) ;
    fn = (Target_Fn) entrance ;
    fn ( 0 , 0 ) ;
    crash () ;
}

static int serial_tstc ( void )
{
    return (( XIo_In32 ( XPAR_UARTLITE_0_BASEADDR + 0x8 ) & 0x01 ) != 0 ) ;
}


void udelay ( unsigned microseconds ) ;


static void flash_loader ( void )
{
    print ( "Flash memory: " ) ;
    if (( (* ( (volatile unsigned *) ( KERNEL_IS_AT + 0x0 ) )) == 0x48000005 )
    && (  (* ( (volatile unsigned *) ( KERNEL_IS_AT + 0x4 ) )) == 0x7c13eaa6 ))
    {
        unsigned i ;

        print ( "Kernel found!\r\n" 
            "Press any key to abort the default boot process.\r\n" 
            "10 second timeout: " ) ;
        for ( i = 0 ; i < 25 ; i ++ )
        {
            udelay ( 400000 ) ; /* 200ms */
            print ( "." ) ;
            if ( serial_tstc () )
            {
                return ;
            }
        }
        /* Boot Flash */
        print ( "timeout.\r\nBooting from Flash: " ) ;
        launch ( KERNEL_IS_AT ) ;
    }
    print ( "No kernel found.\r\n" ) ;
}



int main ( void )
{
    unsigned        state , byte_count , address , spin_number ;
    unsigned        high_address , rec_type , entrance ;
    volatile unsigned char * memory ;
    unsigned char   byte ;

    inv_cache () ;
	 led_out ( 1 ) ;
    timebase_period_ns = 1000000000 / XPAR_CPU_PPC405_CORE_CLOCK_FREQ_HZ ;
    print ( 
       "\r\n\nVirtual Lab Linux Bootloader\r\n"
        "Version: " VERSION "\r\n"
        "Build: " __TIME__ " " __DATE__ "\r\n" ) ;

    /* Eat unwanted input */
    while ( serial_tstc () )
    {
        input () ;
    }

    /* Check flash. If flash is available,
     * wait for a period of time. If a hex record is
     * detected, break. Otherwise boot from flash.  */
    flash_loader () ;

	 led_out ( 2 ) ;
    print ( "Send Intel Hex records when ready.\r\n" ) ;

    high_address = 0 ;
    spin_number = 0 ;
    entrance = 0 ; /* (unsigned) no_program ; */

    state = RUN ;
    while ( state == RUN )
    {   
        while ( input () != ':' ) {} /* Wait for colon */

        g_sum = 0 ;
        byte_count = get_hex_byte () ;
        address = get_hex_byte () << 8 ;
        address |= get_hex_byte () | high_address ;
        memory = (unsigned char *) address ;
        rec_type = get_hex_byte () ;
        switch ( rec_type )
        {
            case 0 :    /* Data */
                while ( byte_count != 0 )
                {
                    byte = (unsigned char) get_hex_byte () ;
                    (* memory) = byte ;
                    if ( (* memory) != byte )
                    {
                        print ( "\r\nReadback failed. " ) ;
                        crash () ;
                    }
                    byte_count -- ;
                    memory ++ ;
                }
                if (( spin_number & (( 1 << SPIN_LINE_SHIFT ) - 1 )) == 0 )
                {
                    print ( g_spin_table [ spin_number >> SPIN_LINE_SHIFT ] ) ;
                }
                spin_number = (( spin_number + 1 ) & 
                    (( 1 << ( SPIN_LINE_SHIFT + SPIN_TABLE_SHIFT )) - 1 )) ;
                break ;
            case 1 :    /* EOF */
                state = LAUNCH ;
                break ;
            case 2 :    /* Either: 16 bit segment address */
            case 4 :    /* or: Address high word */
                high_address = 0 ;
                while ( byte_count != 0 )
                {
                    high_address = ( high_address << 8 ) | get_hex_byte () ;
                    byte_count -- ;
                }
                if ( rec_type == 2 )    /* 16 bit segment address */
                {
                    high_address = high_address << 4 ;
                } else {                /* address high word */
                    high_address = high_address << 16 ;
                }
                break ;
            case 3 :    /* CS:IP value: entrance point */
                byte_count -= 4 ;
                entrance = (( get_hex_byte () << 8 ) | get_hex_byte () ) << 4 ;
                entrance += (( get_hex_byte () << 8 ) | get_hex_byte () ) ;
                break ;
            case 5 :    /* Linear value: entrance point */
                entrance = 0 ;
                while ( byte_count != 0 )
                {
                    entrance = ( entrance << 8 ) | get_hex_byte () ;
                    byte_count -- ;
                }
                break ;
            default :
                print ( "\r\nUnknown ihex record type." ) ;
                crash () ;
                break ;
        }
        while ( byte_count != 0 )
        {
            (void) get_hex_byte () ;
            byte_count -- ;
        }
        (void) get_hex_byte () ; /* checksum */
        if (( g_sum & 0xff ) != 0 )
        {
            print ( "\r\nChecksum failed." ) ;
            crash () ;
        }
    }
    print ( "\r\nBooting from RAM: " ) ;
    launch ( entrance ) ;
    return 0 ;
}


asm (
    ".global udelay\n"
	"udelay:\n" /* From arch/ppc/boot/common/util.S - GPL code! */
	"    mulli   4,3,1000  /* nanoseconds */\n"
	"    /*  Change r4 to be the number of ticks using:\n"
	"     *  (nanoseconds + (timebase_period_ns - 1 )) / timebase_period_ns\n"
	"     *  timebase_period_ns defaults to 60 (16.6MHz) */\n"
	"    lis 5,timebase_period_ns@ha\n"
	"    lwz 5,timebase_period_ns@l(5)\n"
	"    add 4,4,5\n"
	"    addi    4,4,-1\n"
	"    divw    4,4,5    /* BUS ticks */\n"
	"1:  mftbu   5\n"
	"    mftb    6\n"
	"    mftbu   7\n"
	"    cmpw    0,5,7\n"
	"    bne 1b      /* Get [synced] base time */\n"
	"    addc    9,6,4    /* Compute end time */\n"
	"    addze   8,5\n"
	"2:  mftbu   5\n"
	"    cmpw    0,5,8\n"
	"    blt 2b\n"
	"    bgt 3f\n"
	"    mftb    6\n"
	"    cmpw    0,6,9\n"
	"    blt 2b \n"
	"3:  blr\n" ) ;






