//-----------------------------------------------------------------------------
// F320_TimeCritical.c
//-----------------------------------------------------------------------------
// Copyright 2006 Silicon Laboratories, Inc.
// http://www.silabs.com
//
// Program Description:
//
// This module contains mixed C and assembly for time critical code portions.
// When it is compiled, it updates F320_TimeCritical.asm, which is included in
// the normal project build.  The code in this module is hand optimized to meet
// timing requirements for this specific application.  Modifications to this
// module should be kept to a minimum, or avoided entirely.
//
// FID:            32X000037
// Target:         C8051F320
// Tool chain:     KEIL C51 7.0.0.1 / KEIL A51 7.0.0.1
//                 Silicon Laboratories IDE version 2.3
// Command Line:   See Readme.txt
// Project Name:   F320_FM_Radio
//
// Release 1.1
//    -No changes made to this module (DM)
//    -16 JAN 2006
//
// Release 1.0
//    -Initial Revision (DM)
//    -05 AUG 2005
//
//
//-----------------------------------------------------------------------------
// Preprocessor Directives
//-----------------------------------------------------------------------------

#pragma SRC(F320_TimeCritical.asm)

//-----------------------------------------------------------------------------
// Includes
//-----------------------------------------------------------------------------

#include "c8051f320.h"                 // SFR declarations
#include "F320_FM_RadioMain.h"         // Main project header
#include "F320_USB_Register.h"         // USB core register header
#include "F320_USB_Common.h"           // USB protocol header
#include "F320_USB_Descriptor.h"       // USB descriptor definitions
#include "F320_LED_Control.h"          // LED control header

//-----------------------------------------------------------------------------
// Variable Declaration
//-----------------------------------------------------------------------------

// Placeholder for audio samples
xdata BYTE AudioSamples[1024] _at_ 0x0000;

//-----------------------------------------------------------------------------
// Timer2_ISR
//
// Return Value : None
// Parameters   : None
//
// Switches the ADC multiplexor value right after a conversion is started.
// This switches between the right and left audio channels.  This routine really
// uses bank 0, which is otherwise unused after initialization routines finish
// in the main program thread.  This interrupt is high priority, and uses
// EMI0CN and R1 to access xdata software buffer, while the low priority USB
// ISR routine Handle_In3 uses the data pointer to access the same buffer.
// Using this method for buffer access reduces the amount of required context
// saving/restoring between the two ISRs.  The audio samples are four bytes,
// spread across the four 256 byte xdata pages.  For instance, if byte 0 is at
// address 0, then byte 1 is at address 256, byte 2 is at address 512, and
// byte 3 is at address 768.
//
//-----------------------------------------------------------------------------
void Timer2_ISR (void) interrupt 5
{
   AMX0P ^= 0x01;                      // Switch between left and right channel

   #pragma asm
   USING 0;                            // Really using register bank 0

	PUSH 	 PSW;
	MOV    PSW,#00H;
   #pragma endasm

   if (!(AMX0P & 0x01))                // Determine which channel is selected
   {
      #pragma asm
      MOV   EMI0CN, #00H;              // Set EMI0CN to target xram page 0

      MOV   A,ADC0L;
      MOVX  @R1,A;                     // Move left sample low byte to buffer

      INC   EMI0CN;                    // EMI0CN now targets xram page 1

      MOV   A,ADC0H;
      XRL   A,#80H;                    // Convert to signed two's-complement
      MOVX  @R1,A;                     // Move left sample high byte to buffer
      #pragma endasm
   }
   else
   {
      #pragma asm
      INC   EMI0CN;                    // EMI0CN now targets xram page 2

      MOV   A,ADC0L;
      MOVX  @R1,A;                     // Move right sample low byte to buffer

      INC   EMI0CN;                    // EMI0CN now targets xram page 3

      MOV   A,ADC0H;
      XRL   A,#80H;                    // Convert to signed two's-complement
      MOVX  @R1,A;                     // Move right sample high byte to buffer

      INC   R1;                        // Increment write pointer

      MOV   A, R1;                     // Check to see if buffer is full
      XRL   A, R2;                     // by checking if R1 = R2
      JNZ   BUFFER_NOT_FULL;
         DEC   R1;                     // If so, throw away this sample
      BUFFER_NOT_FULL:
      #pragma endasm
   }

   TF2H = 0;                           // Clear Timer2 interrupt flag

   #pragma asm
	POP   PSW;
   #pragma endasm
}

//-----------------------------------------------------------------------------
// Handle_In3
//-----------------------------------------------------------------------------
//
// Return Value : None
// Parameters   : None
//
// This routine write up to 97 samples per millisecond from the software buffer
// to the USB fifo for endpoint 3.  The register usage is as follows:
//
// Write Pointer is R1
// Read Pointer is R2
// Number of bytes to write is R3
// Number of samples to write is stored in accumulator
//
// Each sample is 4 bytes and each byte of the sample is stored at the same
// 8-bit address on separate XRAM pages.  See Timer2_ISR comments for more
// detailed description.
//
//-----------------------------------------------------------------------------
void Handle_In3 (void)
{
   while (USB0ADR & 0x80);             // Wait for BUSY->'0'               
   USB0ADR = (FIFO_EP3);               // Set address

   #pragma asm
   USING 0;                            // Really using register bank 0

   PUSH  PSW;
   MOV   PSW,#00H;

   CLR   EA;                           // Turn off interrupts
   CLR   EA;

   CLR  	C;                            // Calculate number of samples in buffer
   MOV  	A,R1;                         // with interrupts off
   SUBB 	A,R2;
   MOV  	R3,A;

   SETB  EA;                           // Turn on interrupts

   MOV  	A,R3;                         // Only write up to 97 samples
   SETB 	C;
   SUBB 	A,#061H;
   JC   	ENOUGH_FIFO_SPACE;
         MOV  	R3,#061H;
   ENOUGH_FIFO_SPACE:

   BEGIN_WRITE_LOOP:
      MOV   DPL, R2;                   // Datapointer low byte = write pointer
      MOV   DPH, #00H;                 // Datapointer high byte = xram page 0
  
      POLL1:
         MOV   A,USB0ADR;
         JB ACC.7,POLL1;               // Wait for access to USB SIE

      MOVX  A,@DPTR;                   // Move left sample low byte
      MOV   USB0DAT, A;                // to USB FIFO

      INC   DPH;                       // Datapointer high byte = xram page 1

      POLL2:
         MOV   A,USB0ADR;
         JB ACC.7,POLL2;               // Wait for access to USB SIE
   
      MOVX  A,@DPTR;                   // Move left sample high byte
      MOV   USB0DAT, A;                // to USB FIFO

      INC   DPH;                       // Datapointer high byte = xram page 2

      POLL3:
         MOV   A,USB0ADR;
         JB ACC.7,POLL3;               // Wait for access to USB SIE
   
      MOVX  A,@DPTR;                   // Move right sample low byte
      MOV   USB0DAT, A;                // to USB FIFO

      INC   DPH;                       // Datapointer high byte = xram page 3

      POLL4:
         MOV   A,USB0ADR;
         JB ACC.7,POLL4;               // Wait for access to USB SIE
   
      MOVX  A,@DPTR;                   // Move right sample high byte
      MOV   USB0DAT, A;                // to USB FIFO

      INC   R2;                        // Increment write pointer

      DJNZ  R3, BEGIN_WRITE_LOOP;      // Loop if there is still data to write
   END_WRITE_LOOP:

   POP   PSW;
   #pragma endasm

   // Set rbInINPRDY to send data to host
   POLL_WRITE_BYTE (EINCSR1, rbInINPRDY);

   LED_StateMachine ();                // Update LED state
   In1_StateMachine ();
}

//-----------------------------------------------------------------------------
// Turn_On_Stream
//-----------------------------------------------------------------------------
//
// Return Value : None
// Parameters   : None
//
// This function purges the USB FIFO, and puts then puts the device's Vendor
// and product ID on the USB FIFO.  This ensures that a transmit complete
// interrupt will be generated while the F320 buffers the first millisecond of
// samples.  By putting this identification in the data stream, the host can
// be sure it is communicating with the correct USB audio device.
//
//-----------------------------------------------------------------------------
void Turn_On_Stream (void) using USB_REGISTER_BANK
{
    FIFO_Purge (3);                    // Index is set to 3 in this call

   #pragma asm

   USING 0;                            // Really using register bank 0

   PUSH  PSW;
   MOV   PSW,#00H;
   
   MOV   R1,#00H;                      // Reset read and write pointers
   MOV   R2,#00H;

   POLL10:
      MOV   A,USB0ADR;
      JB ACC.7,POLL10;                 // Wait for access to USB SIE
	MOV   USB0ADR,#023H;                // Configure to write endpoint 3 FIFO

   POLL11:
      MOV   A,USB0ADR;
      JB ACC.7,POLL11;                 // Wait for access to USB SIE
   MOV   A, #VID_LSB;
   MOV   USB0DAT, A;                   // Write vendor ID low byte

   POLL12:
      MOV   A,USB0ADR;
      JB ACC.7,POLL12;                 // Wait for access to USB SIE
   MOV   A, #VID_MSB;
   MOV   USB0DAT, A;                   // Write vendor ID high byte

   POLL13:
      MOV   A,USB0ADR;
      JB ACC.7,POLL13;                 // Wait for access to USB SIE
   MOV   A, #PID_LSB;
   MOV   USB0DAT, A;                   // Write product ID low byte

   POLL14:
      MOV   A,USB0ADR;
      JB ACC.7,POLL14;                 // Wait for access to USB SIE
   MOV   A, #PID_MSB;
   MOV   USB0DAT, A;                   // Write product ID high byte

   POP   PSW;

   #pragma endasm

   // Set rbInINPRDY to send data to host
   POLL_WRITE_BYTE (EINCSR1, rbInINPRDY);
               
   AMX0P = 0x09;                       // Positive input starts as P2.1, left
   ADC0L = 0x00;                       // Write zero values to ADC
   ADC0H = 0x80;

   // Enable Reset, and Suspend interrupts; Disable SOF interrupts
   POLL_WRITE_BYTE (CMIE, rbRSTINTE | rbSUSINTE);

   POLL_WRITE_BYTE (IN1IE,  0x09);     // Enable Endpoint 0 and 3 in interrupts

   Next_LED_Mode = ALL_COLOR_LED;      // Set next LED state

   TR2 = ON;                           // Start timer 2
}

//-----------------------------------------------------------------------------
// Turn_Off_Stream
//-----------------------------------------------------------------------------
//
// Return Value : None
// Parameters   : None
//
// This routine stops is called when the device should stop sampling audio and
// be able to accept control commands from the host.
//
//-----------------------------------------------------------------------------
void Turn_Off_Stream (void) using USB_REGISTER_BANK
{
   TR2 = OFF;

   POLL_WRITE_BYTE (IN1IE,  0x01);     // Enable Endpoint 0 interrupts only

   FIFO_Purge (3);                     // Index is set to 3 in this call

   Next_LED_Mode = BLINK_GREEN_LED;    // Set next LED state

   // Enable SOF, Reset, and Suspend interrupt
   POLL_WRITE_BYTE (CMIE, rbSOFE | rbRSTINTE | rbSUSINTE);
   
}

//-----------------------------------------------------------------------------
// End Of File
//-----------------------------------------------------------------------------