
// ================================   EDMA.C    =====================================
//
// Name: TTO EDMA3 LLD Example3 (Single SYNC'd transfer with interrupt generation)
//
// Authors: Scott Specker and Eric Wilbur (Technical Training Organization)
//
// Date: 02/17/09
//
// Rev: 1.2
//
// Target: TMS320C6455 DSK
//
// BIOS ver: 5.31
//
// CCS ver: 3.3
//
// LLD version: edma3_lld_01_06_00_01
//
// To download the latest LLD release, go to the following URL:
//   https://www-a.ti.com/downloads/sds_support/TargetContent/psp/edma3_lld/index.html
//
// Abstract: The purpose of this example is to provide users with a simple example of
//           using the Low Level Driver (LLD) libraries to program and execute transfers
//           using the EDMA3 peripheral.
//
//           Also generates an EDMA interrupt that is sent to the CPU.
//
//           ** NEW **
//           This example uses the DSP/BIOS Clock Manager to trigger a Timer 1 interrupt
//           event to the EDMA to kick off the transfer. Make sure that the "Reset Timer..."
//           checkbox in the Clock Manager is CHECKED and that Timer 1 is selected.
//
//
// API:	     In effect, we have created a small API in edma.c and edma.h that
//	     encapsulates using the LLD functions. These edma_xxx functions allowed us
//           to simplify our example program (in main.c) and also let us partition the
//           LLD code in an easily digestable manner.
//
//	     The following two functions initialize the EDMA3 and channel resources
//	     - edma_config();
//           - edma_createChan();
//
//	     The following three functions execute and verify completion of the transfer
//           - edma_start();
//           - edma_check();
//           - edma_wait();
//
//           The following two functions free up the channel and EDMA3 resources
//           - edma_deleteChan();
//           - edma_release();
//
//           Our API creates a typedef (EDMA_T) in edma.h that allows the
//           calling program to define and use the following elements:
//           - edma: handle to the EDMA3 resources for this API
//           - channel: channel Id number (0-63 or EDMA3_DRV_CHANNEL_ANY or TEVTLO1)
//           - tcc: transfer completion code (0-63 or EDMA3_DRV_TCC_ANY)
//
// Usage:    The following libraries are required in your project:
//	     - edma3_drv_bios.lib
//	     - edma3_drv_bios_sample.lib
//           - edma3_rm_bios.lib
//
//	     These libraries are located in the following directories respectively:
//           - [lld_root]\packages\ti\sdo\edma3\drv\lib\Debug (can also use \Release)
//           - [lld_root]\packages\ti\sdo\edma3\drv\sample\lib\c6455\Debug (platform-specific .lib)
//	     - [lld_root]\packages\ti\sdo\edma3\rm\lib\c6455\Debug (platform-specific)
//	     where [lld_root] is the installation location of the LLD driver.
//
//	     The following header files must be included with your edma code for these
//           libraries to work properly:
//	     - <edma3_drv.h>:  LLD Driver header file
//           - <bios_edma3_drv_sample.h>:  OS-specific and device-specific header file
//	     All other header files used are already included in the above header files.
//           (Note, you will find them #included below these comments in this file; if you are
//            using our framework - i.e. this file - then we've already done this work for you.)
//
//	     For the header files, you will need to specify the include search paths (-i).
//           You can accomplish this by using CCS (Project->Build Options->Compiler->Preprocessor:
//           Add the following paths:
//           - [lld_root]\packages\ti\sdo\edma3\rm
//           - [lld_root]\packages\ti\sdo\edma3\drv
//           - [lld_root]C:\edma3_lld_1_05_00\packages\ti\sdo\edma3\drv\sample
//           - [lld_root]\packages   (this path is required since some of the #includes
//                                    found in edma3_drv.h are referenced from "packages")
//
//	     LLD is OS-agnostic and supports the following operating systems:
//	     - DSP/BIOS
//           - PROS
//           This is a BIOS project, hence the use of "bios" in the library names above. As you
//           can see, the second two libraries are platform-specific (i.e. C6455). The first
//           library (edma3_drv_bios.lib) encapsulates the "RM" (resource management) layer
//           underneath.
//
//           The user must provide a semaphore to the LLD to allow it to manage resources.
//           While the LLD provides a function to create this semaphore, we thought it was
//           easier to create it statically in the .tcf file (OsSem) and pass it to the LLD.
//           The LLD library requires a counting semaphore with an initial count of 1.
//           (Note: because semaphores are OS-specific, the OsSemCreate function must be
//            tuned for your OS - thus we thought it was easier to do directly in BIOS).
//
//
// EXAMPLE3 = EXAMPLE 2 + Timer 1 sync event to start the EDMA transfer
//
// Int Gen:  There are several steps required to get interrupts to work properly:
//
// For CPU:  1. Turn on the proper CPU IER bit (done in main.c, CPU-specific)
//	     2. Turn on global interrupts (GIE) - already done for us by DSP/BIOS when
//              we return from main() in main.c.
//           3. Create an HWI (we used HWI_INT5) in the .tcf file. You need to point this
//              HWI (i.e. the function to run) to the EDMA interrupt dispatcher which is
//              _lisrEdma3ComplHandler0. This handler checks all of the IPR bits and if it
//              finds one that is set, it calls the corresponding callback function you
//              sent via the _requestChannel API (see tccCb = &edma_isr). This handler
//              also clears the specific IPR bit that was set. AND don't forget to set the
//              interrupt selection number corresponding to the interrupt you are tying to
//              respong to. In our case, we are using the Region 1 interrupt from the EDMA
//              which just so happens to be selection number 72. You can find a table of
//              these in the datasheet (SPRS276, Section 7.5, about page 119).
//           4. Create an ISR (such as edma_isr located at the bottom of this file). We
//              simply put a LOG_printf() there for debug purposes.
// For EDMA: 5. The EDMA IER bit is set during the _requestChannel API call. So, the user
//              does NOT need to write to the IESR manually to set the corrersponding
//              IER bit for the TCC.
//           6. In the Options register, TCINTEN must be enabled (however it already was
//              enabled in our previous example - so no changes were made here).
//           7. Register a callack function (edma_isr) to be used by _requestChannel(). This
//              function will be placed in a table used by the EDMA3 Completion Handler 0.
//              See tccCb = &edma_isr in the edma_createChan() function.
//
// ***************************     NEW TO THIS EXAMPLE3    ***********************************
//
// Evt Sync: To get event synchronization to work, we added the following to this example:
//           1. In the .tcf file - Clock Manager, we set the period to 2 seconds on Timer 1.
//              When Timer 1 reaches zero, it will send an event trigger to the EDMA and set
//              the corresponding ER (Event Register) bit.
//           2. Reference the EDMA User Guide (SPRU966, about page 43) to see the list of
//              events tied to various channels. Timer 1 LO event is tied to channel 16 (we
//              use an enum for this).
//           3. In the edma_start() function, notice the change to _enableTransfer
//              paramaters. We changed EDMA3_DRV_TRIG_MODE_MANUAL to
//              EDMA3_DRV_TRIG_MODE_EVENT. This will set the EER bit corresponding to the
//              chosen channel (in our case, it is channel 16 which is passed via the EdmaObj
//              from main to edma_start()).
//           4. In edmaTsk (in main.c), with the 2 second timer interrupt, we were deleting
//              the channel resources before the EDMA completed its job. So, when the EDMA
//              finished the transfer, the EDMA ISR wasn't configured any longer. So, we added
//              a DSP/BIOS semaphore (SEM) to edmaTsk (in main.c) to PEND (i.e. wait) until
//              the EDMA had fully completed its job. A SEM_post was added to our ISR to tell
//              edmaTsk that the transfer was finished. This signaling between the ISR and
//              edmaTsk fixed the problem.
//
// *************************** END OF NEW TO THIS EXAMPLE3 ***********************************

//
// Diagrams: This simple diagram may help provide some context to how the LLD driver (DRV)
//           and Resource Manager (RM) work together. The user's responsibility is to
//           use the DRV APIs - the RM is accessed solely by DRV. Also notice that if you
//           are using ACPY3 & DMAN framework, it actually uses the LLD's Resource Manager.
//
//           	                        ------------------------
//           	     -------            | Framework Components |
//                   | APP |            |      DMAN, ACPY      |
//               	 -------            ------------------------
//                      |                          |
//                      |                          |
//            --------------------     -------------------------
//            | LLD Driver (DRV) | --> | Resource Manager (RM) |
//            --------------------     -------------------------
//                                                |
//                                                |
//                                       ------------------
//                                       | EDMA3 Hardware |
//                                       ------------------
//
//           PSP is the Platform Support Package (peripheral drivers) supplied by TI:
//           It includes the LLD libraries among other libraries
//
// Build
// Warnings: When we built this project, we saw several warnings that edma3Result
//           was declared but never used. We did not write in the error-checking code
//           to check the results returned from some functions. You may add that code
//           if so desired. In Build Options -> Compiler -> Diagnostics, we figured out
//           which warning id number it was by using -pden, then suppressed that warning
//           id (552) using -pds552.
//
// ==================================================================================


// ==============================   INCLUDES    =====================================
//#include "main.h" 					// contains BUFFSIZE, PSET_EDMA and externs for Src/Dst arrays

#include "edma3.h"
#include <rm/src/edma3_rl_cc.h> 		// needed for the EDMA3_CCRL_RegsOvly global (for debug)
#include <semaphore.h>

// ==============================  PROTOTYPES   =====================================
void edma_isr(); // our ISR routine at the bottom of this file


// ===============================   GLOBALS    =====================================

// used for EDMA3_DRV_create() API, edma3InstanceId also used in EDMA3_DRV_delete()
static unsigned int edma3InstanceId = 0, // Only one hardware EDMA3 instance
                    edma3RegionId = 1;

// declare sample Params for 6455 - this list of parameters is DEVICE SPECIFIC
extern EDMA3_DRV_GblConfigParams sampleEdma3GblCfgParams[];

// External Instance Specific Configuration Structure - resources owned by region 1 (PARAM sets, channels, TCCs, etc.)
// DEVICE-SPECIFIC
extern EDMA3_DRV_InstanceInitConfig sampleInstInitConfig[][EDMA3_MAX_REGIONS];

// Used for debug purposes and hence we kept it global. This pointer will point to the
// entire set of the CC Regs (like the EDMA's IPR and IER regs) so you can view them
// while debugging. Simply add CCRegsPtr to a watch window. We also use this pointer to
// write to the EDMA IER register via the EDMA IESR register in edma_createChan().
EDMA3_CCRL_RegsOvly CCRegsPtr;


// ============================    EDMA CONFIG    ===================================
// This function will:
// - create an instance (edma3InstanceId) for each EDMA3 hardware peripheral
// - provide a handle (hEdma) to the user instance of the EDMA mdoule
//
// DRV_create requires a globalConfig structure which contains the hardware resources
// available for this device (we used sampleEdma3GblCfgParams to accomplish this).
//
// DRV_open requires the initCfg structure to initialize the Driver Instance.
// ===================================================================================
void edma_config(EDMA_T *EdmaObj)
{
  EDMA3_DRV_Result edma3Result = EDMA3_DRV_SOK;
  // Set global config to defaults in sample Params (located in bios_edma3_drv_sample_C6455_cfg.c)
  EDMA3_DRV_GblConfigParams *globalConfig = &sampleEdma3GblCfgParams; // + edma3InstanceId;
  // Used in DRV_create() function to specify master/slave
  EDMA3_DRV_MiscParam miscParam;
  // Used for EDMA3_DRV_open() API and initCfg structure, defined in <edma3_drv.h>
  EDMA3_DRV_InitConfig initCfg;
  // Located in bios_edma3_drv_sample_C6455_cfg.c
  EDMA3_DRV_InstanceInitConfig *instanceConfig = &sampleInstInitConfig; // [edma3InstanceId] + edma3RegionId;
  // Declare Config structure used to initialize the Driver Instance (needed by DRV_open)
  initCfg.isMaster = TRUE;
  initCfg.regionId = edma3RegionId;			    // User must specify region (LLD/DRV does not support "global" region)
  initCfg.drvSemHandle = (void*)&os_sem; 	    // Os_sem create in semaphore.c
  initCfg.drvInstInitConfig = instanceConfig;   // Device-specific configuration - resources owned by region edma3regionId
  initCfg.gblerrCb = NULL; 			            // Callback function to catch channel controller errors such as TCC error,
                                                // queue threshold exceeded, etc.
  initCfg.gblerrData = NULL; 			        // any data required by the error callback function
  miscParam.isSlave = FALSE;                    // ARM + DSP, specify who is the master, single-chip = use FALSE

  // EDMA3 Driver Create (called only once for each EDMA3 hardware instance)
  edma3Result = EDMA3_DRV_create( edma3InstanceId, globalConfig, (void *)&miscParam );
  if( edma3Result != EDMA3_DRV_SOK )
  {
    Log_info0( "e@edma_config:EDMA3_DRV_create()" );
    return;
  }

  // Open user instance to EDMA Module (hEdma)
  EdmaObj->edma = EDMA3_DRV_open( edma3InstanceId, (void*)&initCfg, &edma3Result );

  // The following is used for debug and programming purposes - to be able to access the CC Regs.
  // To use, simply add CCRegsPtr to your watch window (to view the EDMA registers
  // such as IPR, IER, etc.). This pointer is set to the TOP of the CC Registers - the
  // location of this pointer (0x02A00000) is found in the Global Config Params for C6455.
  // This pointer is also used in edma_createChan to set the EDMA's IER bit to enable
  // EDMA interrupts after the corresponding IPR bit (TCC) is set when the transfer completes.
  // CCRegsPtr = sampleEdma3GblCfgParams.globalRegs;
}

// ============================    EDMA CREATE CHANNEL   ============================
// This function will:
// - allocate all the necessary channel resources and assign them to a channel Id
// - initialize the allocated PARAM registers (like ACNT, BCNT, etc.)
// - set the Options register to all default values
// - enable the IER register (inside EDMA3) to respond to allow an EDMA interrupt to be generated
//
// The following are the default values that are used for the OPT register and LINK
// fields. If you desire to override any of these defaults, copy this code and make the
// necessary modifications (as you can see we did below for the TCINTEN bit):
//
//   EDMA3_DRV_setOptField( edma, channel, EDMA3_DRV_OPT_FIELD_ITCCHEN, EDMA3_DRV_ITCCHEN_DIS );   // Intermediate Chaining DISABLED - default is DIS if not called
//   EDMA3_DRV_setOptField( edma, channel, EDMA3_DRV_OPT_FIELD_TCCHEN, EDMA3_DRV_TCCHEN_DIS );     // Chaining DISABLE - default is DIS if not called
//   EDMA3_DRV_setOptField( edma, channel, EDMA3_DRV_OPT_FIELD_ITCINTEN, EDMA3_DRV_ITCINTEN_DIS ); // Intermediate interrupt enable DISABLED - default is DIS if not called
//   EDMA3_DRV_setOptField( edma, channel, EDMA3_DRV_OPT_FIELD_TCINTEN, EDMA3_DRV_TCINTEN_DIS );   // Interrupt enable DISABLED - default is DIS if not called
//   EDMA3_DRV_setOptField( edma, channel, EDMA3_DRV_OPT_FIELD_TCC, 0u );                          // Already set in EDMA3_DRV_requestChannel
//                                                                                                 // TCC value - arbitrary
//   EDMA3_DRV_setOptField( edma, channel, EDMA3_DRV_OPT_FIELD_STATIC, EDMA3_DRV_STATIC_DIS );     // FIFO mode not used - so, we set it to zero - default is DIS if not called
//   EDMA3_DRV_setOptField( edma, channel, EDMA3_DRV_OPT_FIELD_SYNCDIM, EDMA3_DRV_SYNC_AB ); 	   // Sync set to AB (we want an AB transfer after the manual start - not needed - already set to AB
//   EDMA3_DRV_setOptField( edma, channel, EDMA3_DRV_OPT_FIELD_SAM, EDMA3_DRV_ADDR_MODE_INCR );    // Src Addr Mode = INCR - already set in EDMA3_DRV_setSrc/DstParams	//Address mode is INCR (not FIFO)
//   EDMA3_DRV_setOptField( edma, channel, EDMA3_DRV_OPT_FIELD_DAM, EDMA3_DRV_ADDR_MODE_INCR );    // Dst Addr Mode = INCR - already set in EDMA3_DRV_setSrc/DstParams
//
//   EDMA3_DRV_linkChannel( edma, channel, 0xFFFFu);	//Set link field to FFFF - no linking - this is the default if this API is not called
// ===================================================================================
void edma_createChan( EDMA_T *EdmaObj, int16_t *Src, int16_t *Dst, int16_t buffer_size )
{
  // Return value for some driver calls so they can return an error
  EDMA3_DRV_Result edma3Result = EDMA3_DRV_SOK;
  // Used for debug purposes in EDMA3_DRV_getPaRAM()
  EDMA3_DRV_PaRAMRegs currentParam;
  EDMA3_RM_EventQueue eventQ = 0; 	  // Queue (0-3) that you want your channel tied to
  EDMA3_RM_TccCallback tccCb = &edma_isr; // callback function from EDMA handler (see HWI_INT5 in .tcf file)
  // Get channel for transfer - channel
  edma3Result = EDMA3_DRV_requestChannel( EdmaObj->edma, &EdmaObj->channel, &EdmaObj->tcc, eventQ, tccCb, NULL );
  if( edma3Result != EDMA3_DRV_SOK )
  {
    Log_info0( "e@edma_createChan:EDMA3_DRV_requestChannel()" );
    return;
  }
  // Set source parameters - also assigns defaults to Options register
  EDMA3_DRV_setSrcParams( EdmaObj->edma, EdmaObj->channel, (unsigned int)Src, EDMA3_DRV_ADDR_MODE_INCR, EDMA3_DRV_W8BIT );
  // Set dest parameters - also assigns defaults to Options register
  EDMA3_DRV_setDestParams ( EdmaObj->edma, EdmaObj->channel, (unsigned int)Dst, EDMA3_DRV_ADDR_MODE_INCR, EDMA3_DRV_W8BIT);
  // Set SRCBIDX to 2, SRCCIDX to 0 (single AB transfer)
  EDMA3_DRV_setSrcIndex( EdmaObj->edma, EdmaObj->channel, 2, 0 );
  // Set DSTBIDX to 2, DSTCIDX to 0
  EDMA3_DRV_setDestIndex( EdmaObj->edma, EdmaObj->channel, 2, 0 );
  // ACNT=2, BCNT=BUFFSIZE, CCNT=1, BCNTRLD=0, SYNC=AB
  EDMA3_DRV_setTransferParams( EdmaObj->edma, EdmaObj->channel, 2, buffer_size, 1, 0, EDMA3_DRV_SYNC_AB );
  // Interrupt enable ENABLED for iChannel - required to generate interrupt when xfr is complete
  EDMA3_DRV_setOptField( EdmaObj->edma, EdmaObj->channel, EDMA3_DRV_OPT_FIELD_TCINTEN, EDMA3_DRV_TCINTEN_EN );
  // For debug purposes, get currentParam set so that we can add it to a watch window for debug
  // This will show you the entire PARAM set so that you can check values like Src, Dst, ACNT, etc.
  EDMA3_DRV_getPaRAM(EdmaObj->edma, EdmaObj->channel, &currentParam);
}

// ============================    EDMA START   =======================================
// This function will set the Event Enable Register (EER) bit corresponding to the
// channel we are using. To do this, we use EDMA3_DRV_TRIG_MODE_EVENT in the
// _enableTransfer call below.
//
// This function could be improved by returning the result.
// ===================================================================================
void edma_start( EDMA_T EdmaObj )
{
  EDMA3_DRV_Result edma3Result = EDMA3_DRV_SOK;
  edma3Result = EDMA3_DRV_enableTransfer( EdmaObj.edma, EdmaObj.channel, EDMA3_DRV_TRIG_MODE_EVENT );
  if( edma3Result != EDMA3_DRV_SOK )
  {
    Log_info0( "e@edma_start:EDMA3_DRV_enableTransfer()" );
    return;
  }
}

// ============================    EDMA WAIT   =======================================
// Check to see if the EDMA3 interrupt pending register (IPR) is set to one indicating
// that the transfer has completed. This is a blocking function - it will continue to
// poll the IPR bit and won't return until it becomes a 1.
//
// This function could be improved by returning the result. Also, adding a timeout
// feature would be an improvement.
// ===================================================================================
void edma_wait(EDMA_T EdmaObj)
{
  EDMA3_DRV_Result edma3Result = EDMA3_DRV_SOK;

  edma3Result = EDMA3_DRV_waitAndClearTcc( EdmaObj.edma, EdmaObj.tcc );
  if( edma3Result != EDMA3_DRV_SOK )
  {
    Log_info0( "e@edma_wait:EDMA3_DRV_waitAndClearTcc()" );
    return;
  }
}

// ============================    EDMA CHECK   ======================================
// Check to see if the EDMA3 interrupt pending register (IPR) is set to one indicating
// that the transfer has completed. This is a one-time check - not a blocking
// function. The calling code can check the status and respond accordingly.
//
// tccStatus = TRUE or FALSE
// ===================================================================================
unsigned short edma_check(EDMA_T EdmaObj)
{
  unsigned short tccStatus = FALSE;
  EDMA3_DRV_Result edma3Result = EDMA3_DRV_SOK;

  edma3Result = EDMA3_DRV_checkAndClearTcc( EdmaObj.edma, EdmaObj.tcc, &tccStatus );
  if( edma3Result != EDMA3_DRV_SOK )
    Log_info0( "e@edma_check:EDMA3_DRV_SOK()" );
  return tccStatus;
}

// ============================    EDMA DELETE CHANNEL   =============================
// Delete (free) the current channel from the resource pool
// ===================================================================================
void edma_deleteChan(EDMA_T EdmaObj)
{
  EDMA3_DRV_Result edma3Result = EDMA3_DRV_SOK;

  edma3Result = EDMA3_DRV_freeChannel( EdmaObj.edma, EdmaObj.channel );
  if( edma3Result != EDMA3_DRV_SOK )
  {
    Log_info0( "e@edma_deleteChan:EDMA3_DRV_freeChannel()" );
    return;
  }
}

void edma_release(EDMA_T EdmaObj)
{
  EDMA3_DRV_Result edma3Result = EDMA3_DRV_SOK;

  edma3Result = EDMA3_DRV_close(EdmaObj.edma, NULL); // EDMA3_DRV_close is used to close an already opened EDMA3 Driver instance.
  if( edma3Result != EDMA3_DRV_SOK )
  {
    Log_info0( "e@edma_release:EDMA3_DRV_close()" );
    return;
  }
  edma3Result = EDMA3_DRV_delete(edma3InstanceId, NULL); // EDMA3_DRV_delete deletes the driver instance and driver object.
  if( edma3Result != EDMA3_DRV_SOK )
  {
    Log_info0( "e@edma_release:EDMA3_DRV_delete()" );
    return;
  }
}

// ============================      EDMA ISR     ======================================
// This is just a place-holder for an Interrupt Service Routine (ISR). If you set a
// breakpoint inside this routine, run the code and you get here, the EDMA interrupts
// are working properly. And, we set up an instrumentation log object in the .tcf file
// to capture this msg in the Message Log. This can be opened and viewed by using:
// DSP/BIOS-->Message Log and opening the log object myLog.
//
// A SEM_post was added here so that edmaTsk (in main.c) waits for this semaphore to
// be posted before deleting the channel resources.
// =====================================================================================
void edma_isr()
{
  Log_info0( "WE GOT TO THE ISR" );
  Semaphore_post( wait_for_completion_sem );
}
