/* ==============================================================================

        36x_SPI.asm

        Serial Peripheral Interface boot-loader kernel for the
        ADSP-21365 processor

        Copyright (c) 2004 Analog Devices Inc.  All rights reserved.

        4/01/04 - initial version, B.Mitchell
------------------------------------------------------------------------------*/


#include <def21365.h>
.message/suppress 1130;


.SECTION/PM     seg_ldr;    // the kernel must reside in a single input_section

    nop;nop;nop;
reset:    
    nop;
final_init_loop:
    nop;

    Call user_init (DB);
    IMASK = 0;      // mask all interrupts since this code executes from within
    IRPTL = 0;      // the interrupt vector table.
    BIT SET MODE1 IRPTEN; //Enable Global Interrupts

    L0=0;   L4=0;           //Zero L's so I-registers can be used without wrap
    L8=0;   L12=0;  L15=0;
    M5=0;   M6=1;   M13=0;  M14=1;

    R11=DM(SYSCTL); // Read current SYSCTL setting
    R12=PASS R11;   // Hold Initial SYSCTL setting => restored in final init
    I4=SYSCTL;






// ==========================  READ_BOOT_INFO  ================================
// Read in section header (3 spi words that contain Data TAG, ADDRESS and COUNT)
// Places TAG in R8, Count in R2, and Destination Address in R3.  This code also
// analyzes the TAG and branches to the appropriate initialization code.
//
// Possible TAGS (the 'L' in each tag name signifying that the address and
// count are LOGICAL values, not physical):
//
//              0x0 FINAL_INIT
//              0x1 ZERO_LDATA
//              0x2 ZERO_L48
//              0x3 INIT_L16
//              0x4 INIT_L32
//              0x5 INIT_L48
//              0x6 INIT_L64
//              0x7 ZERO_EXT8
//              0x8 ZERO_EXT16
//              0x9 INIT_EXT8
//              0xA INIT_EXT16
//------------------------------------------------------------------------------
READ_BOOT_INFO:

    call READ_THREEx32;     // fetch three word header via SPI DMA

    						//r8 is already initialized with the tag
    R2=dm(0x98004);         //r2 passes count
    R3=dm(0x98005);         //r3 passes destination address

    R0=PASS R8;             // check TAG

    IF EQ JUMP final_init;  r0=r0-1;    // jump if fetched tag was 0, else tag--
    IF EQ JUMP ZERO_LDATA;  r0=r0-1;    // jump if fetched tag was 1, else tag--
    IF EQ JUMP ZERO_L48;    r0=r0-1;    // jump if fetched tag was 2, else tag--
    IF EQ JUMP INIT_L16;    r0=r0-1;    // jump if fetched tag was 3, else tag--
    IF EQ JUMP INIT_L32;    r0=r0-1;    // jump if fetched tag was 4, else tag--
    IF EQ JUMP INIT_L48;    r0=r0-1;    // jump if fetched tag was 5, else tag--
    IF EQ JUMP INIT_L64;    r0=r0-1;    // jump if fetched tag was 6, else tag--
    IF EQ JUMP ZERO_EXT8;   r0=r0-1;    // jump if fetched tag was 7, else tag--
    IF EQ JUMP ZERO_EXT16;  r0=r0-1;    // jump if fetched tag was 8, else tag--

    IF EQ JUMP INIT_EXT8;   r0=r0-1;    // jump if fetched tag was 9, else tag--
    IF EQ JUMP INIT_EXT16;  jump(pc,0); // jump if fetched tag was A, else it
                            jump(pc,0); // was an invalid TAG, so trap for debug
//==============================================================================

//============================== SPI_ISR =====================================
// This is the first instruction run after the kernel is booted. It returns to
// 0x90005 and starts the kernel. Instructions added or removed may require
// adding "NOP" instructions to ensure this instruction resolves to 0x90030.
// Check map file (kernel.map) to verify location.
//------------------------------------------------------------------------------
x30_SPI_ISR:            rti;


//============================== READ_Nx32 =====================================
// Load either one or three 32-bit words (via DMA) into scratch locations listed
// below for core access. For 32-bit accesses, the three words will be at
// 0x98003, 0x98004, and 0x98005.  For 48-bit accesses two words will be
// available at 0x90002 and 0x90003.
//------------------------------------------------------------------------------
READ_THREEx32:
    USTAT4 = 0x3;
    jump (pc,2);        //skip over next instruction

READ_ONEx32:
    USTAT4=M14;        //DMA count = one 32-bit word

    DM(IMSPI)=M14;       // Internal DMA modify = 1
    USTAT3=0x98003;     // 0x98003, 0x98004, 0x98005 2-col = 0x90002, 0x90003 3-col
    DM(IISPI)=USTAT3;    // Setup DMA destination address.
    
    DM(CSPI)=USTAT4;     //DMA count = three 32 bit words (two 48-bit words)

    //-------- Wait for SPI DMA to complete using polling------
    ustat4=dm(SPIDMAC);
    bit tst ustat4 SPIDMAS; // Check SPI DMA Status bit
    IF TF jump (pc,-2);     // SPIDMAS = 1 when DMA in progress
    //---------------------------------------------------------

    r8=dm(0x98003);         //place 32-bit word in R8
    rts;
//==============================================================================



// ============================== ZERO_LDATA ===================================
// Use core to init 16/32/64-bit internal buffers to zero (saves space in LDR)
//------------------------------------------------------------------------------
ZERO_LDATA:

    I0=R3;      //R3 passes destination address (LW, NW, or SW)
    R0=0;       //Value used to write zero's to memory
    R1=0;       //neighbor register used to write upper 32 bits if LW accesses

    LCNTR=R2, DO ZERO_WRITE_END UNTIL lce;      // R2 passes logical word count
ZERO_WRITE_END: DM(I0,M6)=R0;               // write zero to x16, x32, or x64 memory

    JUMP READ_BOOT_INFO;// fetch next section-header
//==============================================================================



//================================  ZERO_L48 ===================================
// Use core to initialize 48/40-bit internal memory to zero
//------------------------------------------------------------------------------
ZERO_L48:
    I0=R3;  //R3 passes destination address
    PX=0;
    LCNTR=R2, do L48_WRITE_END until lce;
L48_WRITE_END: dm(i0,m6)=PX;
    JUMP READ_BOOT_INFO;// fetch next section-header
//==============================================================================

//===============================  INIT_L16 ====================================
// Initialize Internal Short-word (16-bit) Memory
//------------------------------------------------------------------------------
INIT_L16:

    I0=R3;                  //R3 passes destination address
    ustat2=r2;              //R2 passes 16-bit word count

    r6=r2-1;                //If count==1 don't use loop
    if EQ jump LAST_16;
    jump FIRST_L16;
//------------------------------------------------------------------------------   

    
// Padding
jump(pc,0); jump(pc,0); jump(pc,0);
//============================== PP_ISR ========================================
// This is the instruction run after a parallel port interrupt occurs.
// Instructions added or removed may require adding padding instructions to ensure 
// this instruction resolves to 0x90050.
// Check map file (kernel.map) to verify location.
//------------------------------------------------------------------------------    
x50_PP_ISR:    rti;
//------------------------------------------------------------------------------   


FIRST_L16:
    //divide count by 2 since each pass through the loop init's two 16-bit words
    r2= lshift r2 by -1;
    lcntr = r2, do READ_WRITE_END until lce;
        call READ_ONEx32;
        r6=dm(0x130006);    //SW equivalent of NW address 0x98003 (two LSBs)
        r7=dm(0x130007);    //SW equivalent of NW address 0x98003 (two MSBs)
        dm(i0,m6)=r6;
READ_WRITE_END: dm(i0,m6)=r7;
    bit tst ustat2 1;       // count odd or even? (0x1 = mask, not bit number)
    if NOT TF JUMP READ_BOOT_INFO;  //if bit0 of count was NOT 0x1 then the
                                    //count's even, so fetch next section-header
                                    

LAST_16:call READ_ONEx32;       //else fetch and init last x16 word of this section
        r6=dm(0x130006);
        dm(i0,m6)=r6;
        JUMP READ_BOOT_INFO;    // fetch next section-header
//==============================================================================


//==============================  INIT_L32 =====================================
// initialize 32-bit internal memory
//------------------------------------------------------------------------------
INIT_L32:
    I0=R3;              //R3 passes destination address
    LCNTR=R2, DO L32_CALL_LOOP_END UNTIL LCE;
        CALL READ_ONEx32(DB);
        NOP;            // Branches such as CALLs cannot be in the last three
        NOP;            // instructions of a loop
L32_CALL_LOOP_END: DM(I0,M6)=R8;
    JUMP READ_BOOT_INFO;// fetch next section-header
//==============================================================================


//==============================  INIT_L48 =====================================
// Initialize instructions and 40-bit data
//------------------------------------------------------------------------------
INIT_L48:
    I0=R3;                  //R3 passes destination address
    ustat2=r2;              //R2 passes 16-bit word count

    r6=r2-1;                //If count==1 don't use loop
    if EQ jump LAST_48;

     r2= lshift r2 by -1;   //divide count by 2 since each pass through the loop
                            //      initializes two 48-bit words
    LCNTR=R2, DO L48_CALL_LOOP_END UNTIL LCE;
        CALL READ_THREEx32;         // fetch two 48-bit words (3x32)
        PX=dm(0x90002);
        dm(i0,M6)=PX;
        PX=dm(0x90003);
L48_CALL_LOOP_END: dm(i0,M6)=PX;

    bit tst ustat2 1;       // count odd or even? (0x1 = mask, not bit number)
    if NOT TF JUMP READ_BOOT_INFO;  //if bit0 of count was NOT 0x1 then the
                                    //count's even, so fetch next section-header

LAST_48:
    CALL READ_THREEx32;     //Else fetch and init last 48-bit word of section.
    //NOTE: Since DMA moves 32-bit words, the minimum number of instructions
    //accessible by DMA is 2 (3x32=2x48).  If there are an odd number of 48-bit
    //words, the loader inserts a dummy 48-bit word into the stream.  As one can
    //see below, only the first of the last 2 instructions is initialized: the
    //dummy word is ignored. (Specifically, it's left at the scratch location
    //0x90003 and is overwritten by subsequent data.
    PX=dm(0x90002);
    dm(i0,M6)=PX;
    JUMP READ_BOOT_INFO;            // fetch next section-header
//==============================================================================


//================================  INIT_L64 ===================================
INIT_L64:
    I0=R3;  //R3 passes destination address (LW, NW, or SW)
    LCNTR=R2, DO L64_CALL_LOOP_END UNTIL LCE;
        CALL READ_ONEx32;   // read 32 lsbs of 64-bit word
        PX1=R8;
        CALL READ_ONEx32;   // read 32 msbs of 64-bit word
        PX2=R8;
        nop;                // Call prohibited from last 3 loop instr's
L64_CALL_LOOP_END: DM(I0,M6)=PX (lw);  // write all 64-bits to longword address location
    JUMP READ_BOOT_INFO;            // fetch next section-header
//==============================================================================

// ============================== ZERO_EXT ===================================
// Use DMA to init external buffers to zero (saves space in LDR)
//------------------------------------------------------------------------------
ZERO_EXT8:
    R10=PPDUR20|PPBHC|PPTRAN;    //Set up PP to transfer 8 bit words
                                //Change the duration as needed.
    USTAT4=R2;              //External Count is 4x internal count
    R2=ROT R2 BY 2;

    JUMP (pc,4);

ZERO_EXT16:
    R10=PPDUR20|PPBHC|PPTRAN|PP16;   //Set up PP to transfer 16 bit words
                                    //Change the duration as needed.
    USTAT4=R2;              //External Count is 2x internal count
    R2=ROT R2 BY 1;

    I0=R3;    //R3 passes destination address
    DM(PPCTL)=R10;  //Set up PP
    DM(EIPP)=I0;
    DM(EMPP)=M14; //Set external modify to 1
    R0=0;   //Value used to write zero's to memory
    DM(0x98003)=R0;

    DM(ICPP)=USTAT4;        //Internal DMA count stored in USTAT4
    DM(ECPP)=R2;        //External DMA count = four times value in R2

    DM(IMPP)=M13;       // Internal DMA modify = 0
    CALL PP_DMA_WRITE;


    JUMP READ_BOOT_INFO;            // fetch next section-header
//==============================================================================

//==============================  INIT_EXT =====================================
// initialize external memory
//------------------------------------------------------------------------------
INIT_EXT:
INIT_EXT8:
    R10=PPDUR20|PPBHC|PPTRAN;    //Set up PP to transfer 8 bit words
                                //Change the duration as needed.
    R0=4;                   //External Count is always 4 8-bit words
    JUMP (pc,3);

INIT_EXT16:
    R10=PPDUR20|PPBHC|PPTRAN|PP16;   //Set up PP to transfer 16 bit words
                                    //Change the duration as needed.
    R0=2;                   //External Count is always 2 16-bit words

    LCNTR=R2, DO INIT_EXT.END UNTIL LCE;
        CALL READ_ONEx32(DB);
        NOP;            // Branches such as CALLs cannot be in the last three
        NOP;            // instructions of a loop

        DM(PPCTL)=R10;  //Set up PP
        DM(EIPP)=R3;    //R3 Passes Destination Address
        R3=R3+R0;        //Increment Destination Address

        DM(ICPP)=M14;        //Internal DMA count = one 32-bit word
        CALL PP_DMA_WRITE (DB);
        DM(ECPP)=R0;    //External DMA count = four 8-bit words
        DM(IMPP)=M14;       // Internal DMA modify = 1

INIT_EXT.END: NOP;

    JUMP READ_BOOT_INFO;// fetch next section-header
//==============================================================================
PP_DMA_WRITE:
    LIRPTL = 0;
    BIT SET LIRPTL PPIMSK;
    DM(EMPP)=M14;       // External DMA modify = 1
    USTAT1=0x98003;     // 0x98003, 0x98004, 0x98005 2-col = 0x90002, 0x90003 3-col
    DM(IIPP)=USTAT1;    // Setup DMA destination address.

    ustat1=dm(PPCTL);
    bit set ustat1 PPDEN|PPEN;  // enable DMA
    dm(PPCTL)=ustat1;

    idle;

    //-------- Wait for PPI DMA to complete using polling------
    ustat1=dm(PPCTL);
    bit tst ustat1 PPBS;
    if TF jump (pc,-2);
    //---------------------------------------------------------

    ustat1=dm(PPCTL);       //turn off PP DMA while kernel is executing
    bit clr ustat1 PPDEN|PPEN;  //clear DMA enable
    dm(PPCTL)=ustat1;
    BIT CLR LIRPTL PPIMSK;
    RTS;



//============================ FINAL_INIT ======================================
// This code is the very last piece run in the kernel.  It is self-modifying
// code.  It uses a DMA to overwrite itself; initializing the 256 instructions
// that reside in the interrupt vector table. (Additional explanation below)
//------------------------------------------------------------------------------
final_init:

    //---- Setup for IVT instruction patch -------------------------------------
    I8=0x90030;             // Point to SPI vector to patch from PX
    R9=0xb16b0000;          // Load opcode for "PM(0,I8)=PX" into R9
    PX=pm(0x90003);         // User-instruction destined for 0x90030 is passed
                            // in the section-header for FINAL_INIT. That instr.
                            // is initialized upon completion of this DMA (see
                            // comments below) using the PX register.
    R11=BSET R11 BY 9;      // Set IMDW to 1 for inst write
    DM(SYSCTL)=R11;         // Set IMDW to 1 for inst write


    //---- Setup loop for self-modifying instruction ---------------------------
    I12=0x90004;             // Point to 0x90004 for self modifing code inserted
                            // by the loader at 0x90004 in bootstream
    R9=pass R12, R11=R9;    // Clear AZ, copy power-on value of SYSCTL to R11
    DO final_init_loop UNTIL EQ;    // Set bottom-of-loop address (loopstack) to 0x90004
                            // and top-of-loop address (PC Stack) to the address
                            // of the next instruction.
    PCSTK=reset;          // Change top-of-loop value from the address of
                            // this instruction to 0x90004.

    //---- Enable SPI interrupt ------------------------------------------------
    bit clr IRPTL SPIHI;    // clear any pending SPI interrupt latch
    bit set IMASK SPIHI;    // enable SPI receive interrupt
    bit set MODE1 IRPTEN;   // enable global interrupts

    FLUSH CACHE;            // Remove any kernel instr's from cache
                            

    //---- Setup final DMA parameters ------------------------------------------
    R1=0x98000; DM(IISPI)=R1;// Setup DMA to load over ldr
                DM(IMSPI)=M6;// Set to increment internal ptr
    R2=0x180;   DM(CSPI)=R2; // Load internal count                

    //---- Initiate self-modifying sequence ------------------------------------
    JUMP reset (DB);      // Causes 0x90004 to be the return address when this
                            // DMA completes and the RTI at 0x90030 is executed.
        IDLE;               // After IDLE, patch then start
        IMASK=0;            // clear IMASK on way to 0x90004


//==============================================================================
// When this final DMA completes, the high-priority SPI interrupt is latched,
// which triggers the following chain of events:
//
//  1) The IDLE in the delayed branch to completes
//  2) IMASK is cleared
//  3) The PC (now 0x90003 due to the "JUMP RESET (db)") is pushed on the
//      PC-Stack and the DSP vectors to 0x90030 to service the interrupt.
//
//      - Meanwhile, the loader (anticipating this sequence) has automatically
//      inserted an "RTI" instruction at 0x90030. The user-instruction intended
//      for that address is instead placed in the FINAL_INIT section-header, and
//      was loaded into PX before the DMA was initiated.)
//
// 4) The DSP executes the RTI at 0x90030, and vectors to the address stored
//      on the PC-stack (0x90003).
//
//      - Again, the loader has inserted an instruction into the boot stream and
//      has placed it at 0x90004:
//
//          R0=R0-R0, DM(I4,M5)=R9, PM(I12,M13)=R11;   //opcode: 0x39732D802000
//
//      The loader has also placed a NOP at location 0x90003, opcode 0x000000000000
//      Locations 0x90000 - 0x90004 are reserved and should never be used in any user
//      application. Because of this, the two locations used in final init are never 
//      restored to the value set forth in the executable generated by the tools.
//
//
// 5) This instruction does three things:
//
//      A) Restores the power-up value of SYSCTL (held in R11).
//      B) It overwrites itself with the instruction "PM(0,I8)=PX;"
//          (The first instruction of FINAL_INIT places the opcode for this
//          new instruction, 0xB16B00000000, into R9.)
//      C) R0=R0-R0 causes the AZ flag to be set.
//
//      This satisfies the loop termination-condition of the loop set up
//      in FINAL_INIT ("DO final_init_loop UNTIL EQ;").  When a loop condition is
//      achieved within the last three instructions of a loop, the DSP branches
//      to the top-of-loop address (PCSTK) one final time.
//
// 6) We manually changed this top-of-loop address to 0x90003, and so to conclude
//      the kernel, the DSP executes the instructions at 0x90003 and 0x90004 *again*.
//
// 7) Now, there's a new instruction at 0x90004: "PM(0,I8)=PX;".  This
//      initializes the user-intended instruction at 0x90030 (the vector for
//      the High-Priority-SPI interrupt). //
//
//  At this point, the kernel is finished, and execution continues at 0x90005.
//==============================================================================









//==============================  USER_INIT  ===================================
// This space is provided for the user to insert code. This is only required
// when a system requires a custom configuration for the DSP.  
//------------------------------------------------------------------------------
user_init:

		if not FLAG1_IN jump skip;
		
		r0=TUNF|ROVF;
		dm(SPISTAT)=r0;
		
		r0=TXFLSH|RXFLSH;
		dm(SPICTL)=r0;
		
		r0=0x80;
		dm(SPIDMAC)=r0;
	
		r0=0x64;
		dm(SPIBAUD)=r0;
		
		
		r0=0xb8000;
		dm(IISPI)=r0;
		
		r0=1;
		dm(IMSPI)=r0;
		
		r0=1;
		dm(CSPI)=r0;
		
		r0=0xFF01;
		dm(SPIFLG)=r0;
		
		r0=SPIEN|SPIMS|WL32|SENDZ|CLKPL|CPHASE|TIMOD2;
		dm(SPICTL)=r0;

		r0=0x10000;
		
		r1=0x03000600;
		
		r0=r0 OR r1;
		
		i0=r0;
		
		bitrev(i0,0);
		
		r0=i0;
		dm(TXSPI)=r0;
	
		r0=0xFE01;
		dm(SPIFLG)=r0;
		
		r0=0x00000007;
		dm(SPIDMAC)=r0;
	
		ustat1=dm(SPIDMAC);
    	bit tst ustat1 SPIDMAS; // Check SPI DMA Status bit
    	IF TF jump (pc,-2);     // SPIDMAS = 1 when DMA in progress
                /**** INSERT CUSTOM CONFIGURATION CODE HERE****/
        skip:
        rts;
//==============================================================================/
