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

.extern ___lib_RSTI;

#define x80004 0x80004
.extern _app_offset;
.global _load_application;

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

_load_application:


    call USER_INIT;
    IMASK = 0;      // mask all interrupts since this code executes from within
    IRPTL = 0;      // clear any latched interrupts.
    LIRPTL = 0;
    BIT SET MODE1 IRPTEN;   //Enable global interrupts.

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

    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;

    BIT SET LIRPTL PPIMSK;


// ==========================  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=dm(0x80003);         //r8 passes data-type tag
    R2=dm(0x80004);         //r2 passes count
    R3=dm(0x80005);         //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

                                        // was an invalid TAG, so trap for debug
//==============================================================================


//============================== 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
// 0x80003, 0x80004, and 0x80005.  For 48-bit accesses two words will be
// available at 0x80002 and 0x80003.
//------------------------------------------------------------------------------
READ_THREEx32:
    USTAT1 = 3;
    DM(ICPP)=USTAT1;     //Internal DMA count = three 32 bit words (two 48-bit words)
    USTAT1 = 12;
    DM(ECPP)=USTAT1;    //External DMA count = twelve 8 bit words (two 48-bit words)
    jump (pc,4);        //skip over next three instructions

READ_ONEx32:
    DM(ICPP)=M14;        //Internal DMA count = one 32-bit word
    USTAT1=4;
    DM(ECPP)=USTAT1;    //External DMA count = four 8-bit words

    DM(IMPP)=M14;       // Internal DMA modify = 1
    DM(EMPP)=M14;       // External DMA modify = 1
    USTAT1=0x80003;     // 0x80003, 0x80004, 0x80005 2-col = 0x80002, 0x80003 3-col
    DM(IIPP)=USTAT1;    // Setup DMA destination address.

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


    //-------- Wait for PPI DMA to complete using polling------
    idle;
    //---------------------------------------------------------

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


    r8=dm(0x80003);         //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
//==============================================================================

//============================== PPI_ISR =====================================
// This is the first instruction run after the kernel is booted. It returns to
// 0x80005 and starts the kernel. Instructions added or removed may require
// adding "NOP" instructions to ensure this instruction resolves to 0x80050.
// Check map file (kernel.map) to verify location.
//------------------------------------------------------------------------------

x50_PPI_ISR: RTI;
//==============================================================================


//================================  ZERO_L48 ===================================
// User core to initialize 48/40-bit internal memory to zero
//------------------------------------------------------------------------------
ZERO_L48:
    I0=R3;  //R3 passes destination address
    PX=0;
    LCNTR=R2, do SAVE_PX_END until lce;
SAVE_PX_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
    r2= lshift r2 by -1;    //divide count by 2 since each pass through the loop
                            //      initializes two 16-bit words

    if sz jump LAST_WORD_16; // Detect if there is only one word to initialize

    lcntr = r2, do READ_WRITE_END until lce;
        call READ_ONEx32;
        r6=dm(0x100006);    //SW equivalent of NW address 0x80003 (two LSBs)
        r7=dm(0x100007);    //SW equivalent of NW address 0x80003 (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_WORD_16:
    call READ_ONEx32;       //else fetch and init last x16 word of this section
    r6=dm(0x100006);
    dm(i0,m0)=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 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
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
    r2= lshift r2 by -1;    //divide count by 2 since each pass through the loop
                            //      initializes two 48-bit words

    if sz jump LAST_WORD_48; // Detect if there is only one word to initialize

    LCNTR=R2, DO L48_CALL_LOOP_END UNTIL LCE;
        CALL READ_THREEx32;         // fetch two 48-bit words (3x32)
        PX=dm(0x80002);
        dm(i0,M6)=PX;
        PX=dm(0x80003);
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_WORD_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
    //0x80003 and is overwritten by subsequent data.

    PX=dm(0x80002);
    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 core 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 Wait States 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 Wait States as needed.
    USTAT4=R2;              //External Count is 2x internal count
    R2=ROT R2 BY 1;

    I0=R3;    //R3 passes destination address
    R5=DM(PPCTL);  //Save PPCTL setting
    DM(PPCTL)=R10;  //Set up PP
    R4=DM(EIPP);  //R4 stores next read address
    DM(EIPP)=I0;
    DM(EMPP)=M14; //Set external modify to 1
    R0=0;   //Value used to write zero's to memory
    DM(0x80003)=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 Wait States as needed.
    USTAT4=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 Wait States as needed.
    USTAT4=2;                   //External Count is always 2 16-bit words
    R5=DM(PPCTL);  //Save PPCTL setting
    I0=R3;  //R3 passes destination address

    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
        R4=DM(EIPP);  //R4 stores next read address
        DM(EIPP)=I0;

        DM(ICPP)=M14;        //Internal DMA count = one 32-bit word
        CALL PP_DMA_WRITE (DB);
        DM(ECPP)=USTAT4;    //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:
    DM(EMPP)=M14;       // External DMA modify = 1
    USTAT1=0x80003;     // 0x80003, 0x80004, 0x80005 2-col = 0x80002, 0x80003 3-col
    DM(IIPP)=USTAT1;    // Setup DMA destination address.

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


    //-------- Wait for PPI DMA to complete using polling------
    idle;
    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;

    I0=DM(EIPP);
    DM(EIPP)=R4;  //Restore next read address
    DM(PPCTL)=R5;  //Restore PPCTL setting
    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=0x80050;             // Point to PP vector to patch from PX
    R9=0xb16b0000;          // Load opcode for "PM(0,I8)=PX" into R9
    PX=pm(0x80003);         // User-instruction destined for 0x80050 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=0x80004;             // Point to 0x080004 for self modifing code inserted
                            // by the loader at 0x80004 in bootstream
    R9=pass R12, R11=R9;    // Clear AZ, copy power-on value of SYSCTL to R11

    //---- Setup final DMA parameters ------------------------------------------
    R1=0x80000; DM(IIPP)=R1;// Setup DMA to load over ldr
    R2=0x180;   DM(ICPP)=R2; // Load internal count
    R2=0x600;   DM(ECPP)=R2; // Load external count
                DM(EMPP)=M6;// Set to increment external prt
                DM(IMPP)=M6;// Set to increment internal ptr

    //---- Enable PPI interrupt ------------------------------------------------
    FLUSH CACHE;            // Remove any kernel instr's from cache

    DO ___lib_RSTI UNTIL EQ;    // Set bottom-of-loop address (loopstack) to 0x80004
                            // and top-of-loop address (PC Stack) to the address
                            // of the next instruction.
    PCSTK=x80004;          // Change top-of-loop value from the address of
                            // this instruction to 0x80004.

    //---- Begin final DMA to overwrite this code ------------------------------
    ustat1=dm(PPCTL);
    bit set ustat1 PPDEN|PPEN;
    dm(PPCTL)=ustat1;       // begin final DMA transfer

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


//==============================================================================
// When this final DMA completes, the PP 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 0x80004 due to the "JUMP RESET (db)") is pushed on the
//      PC-Stack and the DSP vectors to 0x80050 to service the interrupt.
//
//      - Meanwhile, the loader (anticipating this sequence) has automatically
//      inserted an "RTI" instruction at 0x80050. 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 0x80050, and vectors to the address stored
//      on the PC-stack (0x80004).
//
//      - Again, the loader has inserted an instruction into the boot stream and
//      has placed it at 0x40005:
//
//          R0=R0-R0, DM(I4,M5)=R9, PM(I12,M13)=R11;   //opcode: x39732D802000
//
// 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 RESET 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 0x80004, and so to conclude
//      the kernel, the DSP executes the instruction at 0x80004 *again*.
//
// 7) Now, there's a new instruction at 0x80004: "PM(0,I8)=PX;".  This
//      initializes the user-intended instruction at 0x80050 (the vector for
//      the Parallel Port interrupt). //
//
//  At this point, the kernel is finished, and execution continues at 0x80005,
//  with the only traceas if nothing happened!
//==============================================================================

//==============================  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.  This may include
// changing the number of waitstates on the PP.
//------------------------------------------------------------------------------

                /**** INSERT CUSTOM CONFIGURATION CODE HERE****/

USER_INIT:
	
	ustat1=PPDUR32;
	dm(PPCTL)=ustat1;
	
	r0=dm(_app_offset);
	r1=0x600;
	r0=r0 or r1;
	dm(EIPP)=r0;
	
//jump(pc,0);   // uncomment to use it for debugging the kernel
rts;
//==============================================================================
