/*
 *  bdinit.S
 *  Joris van Rantwijk
 *
 *  This is a subroutine for the LEON3 boot loader generated by MKPROM2.
 *  It is responsible for tuning the DDR2 read timing.
 *
 *  To use this subroutine, MKPROM2 must be instructed to link this code
 *  into the final boot loader.  Use the "-bdinit" option to MKPROM2.
 *
 *  Compile as follows (produces a file bdinit.o):
 *    $ sparc-elf-gcc -c bdinit.S
 *
 *  Use as follows (bdinit.o must be in the current directory):
 *    $ mkprom2 ... -bdinit ...
 *
 *  This library 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.
 */


/* APB area of UART for messages. */
#define APBUART     0x80000100

/* Configuration area of DDR2SPA core. */
#define DDR2CFG     0xfff00100


.seg "text"
.global bdinit1
.global bdinit2


/*  ********  bdinit1  ********
 *
 *  bdinit1 is called by the boot loader after initialization of the CPU
 *  and peripheral controllers, but before anything is written to memory.
 *
 *  The task of bdinit1 is to tune the DDR2 data read timing.
 *
 *  The DDR2SPA controller itself should already have been initialized
 *  by the normal MKPROM2 boot loader.  We will therefore not touch the
 *  configuration registers, except to adjust the read timing.
 */

bdinit1:
    save

    /* Set %l7 = address of bdinit1. */
bdinit1_findaddr:
    call bdinit1_findaddr2
    nop
bdinit1_findaddr2:
    sub  %o7, bdinit1_findaddr-bdinit1, %l7

    /* Print hello */
    call printstr
    add  %l7, data_hello-bdinit1, %o0

    /* Reset input delay */
    call resetdelay
    nop
    call readcmp
    nop

    call printchar
    mov  '.', %o0

    /* Write test pattern to memory */
    call writemem
    nop

    call printchar
    mov  '.', %o0

    /* Scan over 160 delay steps */
    mov  %g0, %l1           /* %l1 = current data delay setting */
    mov  %g0, %l2           /* %l2 = current run length of good results */
    mov  -1,  %l3           /* %l3 = optimum delay setting */

bdinit1_scanloop:
    /* Test read from memory. */
    call readcmp
    nop

    /* force error during last iteration as sentinel */
    cmp  %l1, 160
    be,a bdinit1_scanupd
    mov  %g0, %o0

bdinit1_scanupd:
    cmp  %o0, 0
    bne  bdinit1_scannext
    inc  %l2
    cmp  %l2, 16
    ble,a bdinit1_scannext
    mov  %g0, %l2
    cmp  %l3, -1
    bne,a bdinit1_scannext
    mov  %g0, %l2

    /* Found first group of at least 16 correct delay settings.
       Take the middle of this group as the optimum setting. */
    srl  %l2, 1, %l2
    sub  %l1, %l2, %l3
    mov  %g0, %l2
 
bdinit1_scannext:
    /* Increment data delay. */
    call incdelay
    nop

    cmp  %l1, 160
    bne  bdinit1_scanloop
    inc  %l1

    cmp  %l3, -1
    be   bdinit1_failed
    nop

    /* Report result */
    call printstr
    add  %l7, data_delayrpt-bdinit1, %o0

    srl  %l3, 4, %o0
    call printchar
    add  %o0, '0', %o0
    and  %l3, 0x0f, %o0
    cmp  %o0, 10
    add  %o0, '0', %o0
    bge,a bdinit1_resultlsb
    add  %o0, 'a'-'0'-10, %o0
bdinit1_resultlsb:
    call printchar
    nop

    /* Set optimal delay (%l3 steps) */
    call resetdelay
    nop

bdinit1_setdelayloop:
    call readcmp
    nop

    cmp  %l3, 0
    be   bdinit1_done
    nop

    call incdelay
    nop

    ba   bdinit1_setdelayloop
    dec  %l3

bdinit1_done:
    call printstr
    add  %l7, data_done-bdinit1, %o0

    /* Return. */
    ret
    restore

bdinit1_failed:
    call printstr
    add  %l7, data_failed-bdinit1, %o0

    /* Return. */
    ret
    restore


/*  ********  bdinit2  ********
 *
 *  bdinit2 is called by the boot loader just before decompressing
 *  the main application.  It does not need to do anything.
 */

bdinit2:
    retl
    nop


/********  Internal subroutines  ********/

/* Reset DDR2 data delay. */
resetdelay:
    set  0x80000000, %g1
writeddrcfg:
    set  DDR2CFG, %o0
    ld   [%o0+0x8], %o1
    or   %o1, %g1, %o1
    st   %o1, [%o0+0x8]
    set  20, %g1
writeddrcfg_wait:
    deccc %g1
    bne  writeddrcfg_wait
    nop
    retl
    nop 


/* Increase DDR2 data delay. */
incdelay:
    set  0xffff, %g1
    ba   writeddrcfg
    nop
   

/* Print character from %o0 to serial port. */
printchar:
    set  APBUART, %g1
    ld   [%g1+4], %g1
    andcc %g1, 4, %g0
    beq  printchar
    set  APBUART, %g1
    st   %o0, [%g1]
    retl
    nop


/* Print string at %o0 to serial port. */
printstr:
    mov  %o7, %o5
    mov  %o0, %o1
printstr_loop:
    ldub [%o1], %o0
    cmp  %o0, %g0
    beq  printstr_end
    inc  %o1
    call printchar
    nop
    ba   printstr_loop
    nop
printstr_end:
    jmpl %o5+8, %g0
    nop


/* Copy test pattern to DDR memory */
writemem:
    set  0x40000000, %o0
    add  %l7, data_testwords-bdinit1, %o1
    mov  16, %o2
writemem_loop:
    ld   [%o1], %g1
    add  %o1, 4, %o1
    st   %g1, [%o0]
    deccc %o2
    bne  writemem_loop
    add  %o0, 4, %o0
    retl
    nop


/* Read from memory and compare.
   Return %o0=1 if correct, %o0=0 if bad. */
readcmp:
    set  0x40000000, %o0
    add  %l7, data_testwords-bdinit1, %o1
readcmp_loop:
    /* Use 64-bit load with forced cache miss. */
    ldda  [%o0] 1, %o2
    add  %o0, 8, %o0
    ld   [%o1], %g1
    cmp  %g1, %o2
    bne  readcmp_bad
    ld   [%o1+4], %g1
    cmp  %g1, %o3
    bne  readcmp_bad
    add  %o1, 8, %o1
    andcc %o0, 0x40, %g0
    beq  readcmp_loop
    nop
    retl
    mov  1, %o0
readcmp_bad:
    retl
    mov  0, %o0


/********  DATA  ********/

.align 4
data_hello:
    .asciz "\r\nDDRTUNE: "

data_delayrpt:
    .asciz " delay = 0x"

data_done:
    .asciz ", OK.\r\n"

data_failed:
    .asciz " FAILED\r\n"

.align 4
data_testwords:
    .word  0x12345678
    .word  0xf0c3a596
    .word  0x6789abcd
    .word  0xa6f1590e
    .word  0xedcba987
    .word  0x0f3c5a69
    .word  0x98765432
    .word  0x590ea6f1
    .word  0xffff0000
    .word  0x0000ffff
    .word  0x5ac3ffff
    .word  0x0000a53c
    .word  0x01510882 
    .word  0xf4d908fd
    .word  0x9b6f7a46
    .word  0xc721271d

/********  END  ********/

