/*******************************************************************************
MODULE      :   Adc.c
  Starting with JADE ADC is configured to run in EM2 without any CPU
  intervention. LETIMER0 is used to trigger ADC via PRS and LDMA transfers
  results into adcBuffer. LETIMER0 runs at 1kHz. So every 1ms new ADC samples
  are transferd to the buffer. Based on github adc_scan_letimer_prs_dma sample

  ToDo: Check aquisition time, accuracy and influence from neighbor channels

20110530 AB     Initial
20150407 AB     Adapted for USV_BatteryMonitor Slave, remove LEDs and Testpins
20191212 AB     JADE EFM32JG1B test with lot of channels
*******************************************************************************/

// --- Includes ---------------------------------------------------------------
#define LOGGING_TOKEN    ADC_LOGGING
#include "TraceLeUart.h"

#include "em_device.h"
#include <em_chip.h>
#include <em_emu.h>
#include <em_cmu.h>
//#include <em_dma.h>
#include <em_adc.h>
#include <em_gpio.h>
#include "em_letimer.h"
#include "em_ldma.h"
#include "em_prs.h"

#include "types.h"
#include "Adc.h"
#include "Leds.h"

// --- Pin Definitions --------------------------------------------------------
// Testpin PF6, pinX (for search in project - Testpin_on)
#define PORT      gpioPortF
#define PORT_BIT  6
#define TESTPIN_INIT()        { GPIO_PinModeSet(PORT, PORT_BIT, gpioModePushPull, 0);}
#define TESTPIN( x )          { if ( x ) GPIO->P[PORT].DOUT |=  ( 1 << PORT_BIT ); \
                                  else   GPIO->P[PORT].DOUT &= ~( 1 << PORT_BIT ); }

// --- Defines ----------------------------------------------------------------
#define ADC_BUFFER_SIZE 14        // Change this to set number of samples per interrupt
                                  // AB usually same as number ob ADC channels
#define ADC_DVL         1         // Change this to set how many samples get sent at once
                                  // AB means sample sets, 1 = one time ADC_BUFFER_SIZE channels
                                  // AB and then sent via DMA to BUFFER
#define ADC_FREQ        1000000   // 16000000 is max ADC clock for Series 1
#define letimerDesired  1000      // Desired letimer interrupt frequency (in Hz)
                                  // AB max: 2300 with channels=14, FREQ=1MHz, AquTim=4
                                  // AB max: ~490 with channels=14, FREQ=1MHz, AquTim=64
                                  // AB max: ~1600 with channels=14, FREQ=1MHz, AquTim=32
                                  // AB --> SM-C channels=10, ok

#define ADC_MV( x )     ( ((adcBuffer[ x ] + 1) * 1250 << 8) / ( 4095 << 8 ) ) // 32 bit needed

#define LDMA_CHANNEL    0
#define PRS_CHANNEL     0

LDMA_TransferCfg_t trans;
LDMA_Descriptor_t descr;

// --- Typedefs ---------------------------------------------------------------

// --- PUBLIC Variables -------------------------------------------------------

// --- Variables --------------------------------------------------------------
static volatile BOOL bAdcIsRunning;
static int iAdc[16];              // filtered values
static BOOL bAdcContinousTrace;

uint32_t adcBuffer[ADC_BUFFER_SIZE];
uint32_t topValue;

// --- Macros -----------------------------------------------------------------

// --- Functionprototypes -----------------------------------------------------
void AdcTrace (int iLevel);
;
// --- Code -------------------------------------------------------------------

/**************************************************************************//**
 * @brief LDMA Handler
 *****************************************************************************/
void LDMA_IRQHandler(void)
  {
  // Clear interrupt flag
  TESTPIN(1);
  LDMA_IntClear((1 << LDMA_CHANNEL) << _LDMA_IFC_DONE_SHIFT);
  TESTPIN(0);

  // check for DMA error
  EFM_ASSERT( (LDMA_IntGet() & 0x80000000) == 0);
  }

/**************************************************************************//**
 * @brief LETIMER initialization
 *****************************************************************************/
void initLetimer(void)
  {
  // Start LFRCO and wait until it is stable
  CMU_OscillatorEnable(cmuOsc_LFRCO, true, true);

  // Enable clock to the interface of the low energy modules
  CMU_ClockEnable(cmuClock_HFLE, true);

  // Route the LFRCO clock to LFA (LETIMER0)
  CMU_ClockSelectSet(cmuClock_LFA, cmuSelect_LFRCO);

  // Enable clock for LETIMER0
  CMU_ClockEnable(cmuClock_LETIMER0, true);

  LETIMER_Init_TypeDef letimerInit = LETIMER_INIT_DEFAULT;

  // Reload COMP0 on underflow, pulse output, and run in repeat mode
  letimerInit.comp0Top  = true;
  letimerInit.ufoa0     = letimerUFOAPulse;
  letimerInit.repMode   = letimerRepeatFree;

  // Initialize LETIMER
  LETIMER_Init(LETIMER0, &letimerInit);

  // Need REP0 != 0 to pulse on underflow
  LETIMER_RepeatSet(LETIMER0, 0, 1);

  // calculate the topValue
  topValue = CMU_ClockFreqGet(cmuClock_LETIMER0) / letimerDesired;

  // Compare on wake-up interval count
  LETIMER_CompareSet(LETIMER0, 0, topValue);

  // Use LETIMER0 as async PRS to trigger ADC in EM2
  CMU_ClockEnable(cmuClock_PRS, true);

  PRS_SourceAsyncSignalSet(PRS_CHANNEL, PRS_CH_CTRL_SOURCESEL_LETIMER0, PRS_CH_CTRL_SIGSEL_LETIMER0CH0);
  }

/**************************************************************************//**
 * @brief LDMA initialization
 *****************************************************************************/
void initLdma(void)
  {
  // Enable LDMA clock
  CMU_ClockEnable(cmuClock_LDMA, true);

  // Basic LDMA configuration
  LDMA_Init_t ldmaInit = LDMA_INIT_DEFAULT;

  LDMA_Init(&ldmaInit);

  // Transfer triggers on ADC Scan conversion complete
  trans = (LDMA_TransferCfg_t)LDMA_TRANSFER_CFG_PERIPHERAL(ldmaPeripheralSignal_ADC0_SCAN);

  descr = (LDMA_Descriptor_t)LDMA_DESCRIPTOR_LINKREL_P2M_BYTE(
      &(ADC0->SCANDATA),  // source
      adcBuffer,          // destination
      ADC_BUFFER_SIZE,    // data transfer size
      0);                 // link relative offset (links to self)

  descr.xfer.blockSize = ADC_DVL-1;     // transfers ADC_DVL number of units per arbitration cycle
  descr.xfer.ignoreSrec = true;         // ignores single requests to reduce energy usage
  descr.xfer.size = ldmaCtrlSizeWord;   // transfers words instead of bytes

  // Initialize LDMA transfer
  LDMA_StartTransfer(LDMA_CHANNEL, &trans, &descr);

  // Clear pending and enable interrupts for channel
  NVIC_ClearPendingIRQ(LDMA_IRQn);
  NVIC_SetPriority(LDMA_IRQn, 6);
  NVIC_EnableIRQ(LDMA_IRQn);
  }


/******************************************************************************
void AdcInit (void)
  Initializes ADC module
******************************************************************************/
void AdcInit (void)
  {
  uint32_t i;

  TRACE("ADC: init");
  TESTPIN_INIT();

  // Declare init structs
  ADC_Init_TypeDef init = ADC_INIT_DEFAULT;
  ADC_InitScan_TypeDef initScan = ADC_INITSCAN_DEFAULT;

  // Enable ADC clock
  CMU_ClockEnable(cmuClock_ADC0, true);
  CMU_ClockEnable(cmuClock_HFPER, true);

  // Select AUXHFRCO for ADC ASYNC mode so it can run in EM2
  CMU->ADCCTRL = CMU_ADCCTRL_ADC0CLKSEL_AUXHFRCO;

  // Set AUXHFRCO frequency and use it to setup the ADC
  CMU_AUXHFRCOFreqSet(cmuAUXHFRCOFreq_4M0Hz);
  init.timebase = ADC_TimebaseCalc(CMU_AUXHFRCOBandGet());
  init.prescale = ADC_PrescaleCalc(ADC_FREQ, CMU_AUXHFRCOBandGet());

  // Let the ADC enable its clock on demand in EM2
  init.em2ClockConfig = adcEm2ClockOnDemand;

  // add ADC channels, sorted by groups and channels, this is the same order as they show
  // up in the destination ADC buffer
  // PORT to GROUP assignment is restricted by chip hardware, see reference manual and
  //  check assertEFM() in ADC_ScanSingleEndedInputAdd() (debug builds only)
  ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup0, adcPosSelAPORT3XCH8);  /* PA0 */  // option
  ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup0, adcPosSelAPORT3YCH9);  /* PA1 */  // Ext. Driven on STK  //option
  ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup0, adcPosSelAPORT3XCH10); /* PA2 */
  ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup0, adcPosSelAPORT3YCH11); /* PA3 */  // Ext. Driven on STK
  ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup0, adcPosSelAPORT3XCH12); /* PA4 */
  ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup0, adcPosSelAPORT3YCH13); /* PA5 */

  ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup1, adcPosSelAPORT3YCH1);  /* PD9 */
  ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup1, adcPosSelAPORT3XCH2);  /* PD10 */
  ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup1, adcPosSelAPORT3YCH3);  /* PD11 */

  ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup2, adcPosSelAPORT1XCH6);  /* PC6 */  // option
  ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup2, adcPosSelAPORT1YCH7);  /* PC7 */

  ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup3, adcPosSelAPORT1XCH8);  /* PC8 */
  ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup3, adcPosSelAPORT1YCH9);  /* PC9 */
  ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup3, adcPosSelAPORT1XCH10); /* PC10 */ // Ext. Driven on STK

// not possible, no more groups  ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup3, adcPosSelAPORT1XCH22);  /* PF6 */  // option
// not possible, no more groups  ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup3, adcPosSelAPORT1YCH21);  /* PF5 */  // option
// not possible, no more groups  ADC_ScanSingleEndedInputAdd(&initScan, adcScanInputGroup3, adcPosSelAPORT1YCH23);  /* PF7 */  // option

  // Basic ADC scan configuration
  initScan.diff       = false;        // single-ended
  initScan.reference  = adcRef1V25;   // 1.25V reference
  initScan.resolution = adcRes12Bit;  // 12-bit resolution
  initScan.acqTime    = adcAcqTime32; // set acquisition time to meet minimum requirements
                                      // 64 to much for 14 channels and 1ms, use adcAcqTime32

  // DMA is available in EM2 for processing SCANFIFO DVL request
  initScan.scanDmaEm2Wu = 1;

  // Enable PRS trigger and select channel 0
  initScan.prsEnable = true;
  initScan.prsSel = (ADC_PRSSEL_TypeDef) PRS_CHANNEL;

  // Initialize ADC
  ADC_Init(ADC0, &init);
  ADC_InitScan(ADC0, &initScan);

  // Set scan data valid level (DVL)
  ADC0->SCANCTRLX = (ADC0->SCANCTRLX & ~_ADC_SCANCTRLX_DVL_MASK) | (((ADC_DVL - 1) << _ADC_SCANCTRLX_DVL_SHIFT) & _ADC_SCANCTRLX_DVL_MASK);

  // Clear the SCAN FIFO
  ADC0->SCANFIFOCLEAR = ADC_SCANFIFOCLEAR_SCANFIFOCLEAR;

  bAdcContinousTrace = ON;

  // fill buffers with known values, nice for debugging
  for (i = 0; i < (sizeof(adcBuffer) / sizeof(adcBuffer[0])); i++)
    {
    adcBuffer[i] = i;
    }
  for (i = 0; i < (sizeof(iAdc) / sizeof(iAdc[0])); i++)
    {
    iAdc[i] = i;
    }


  // Setup DMA to move ADC results to user memory
  initLdma();
  // Set up LETIMER to trigger ADC via PRS in periodic intervals
  initLetimer();

  }

/******************************************************************************
void AdcTrace (int iLevel)
  Traces ADC values to debug out, iLevel defines verbosity
  3 is continous logging
******************************************************************************/
void AdcTrace (int iLevel)
  {
  int i;

  bAdcContinousTrace = OFF;
  switch(iLevel)
    {
    case 0:
      TRACE("ADC:%6u%6u%6u%6u%6u%6u%6u%6u", adcBuffer[0], adcBuffer[1], adcBuffer[2], adcBuffer[3], \
                  adcBuffer[4], adcBuffer[5], adcBuffer[6], adcBuffer[7]);
      break;

    case 2:
      TRACE("ADC:   PA0   PA1   PA2   PA3   PA4   PA5   PD9  PD10  PD11   PC6   PC7   PC8   PC9  PC10");
      TRACE("ADC:%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u [mV]", ADC_MV(0), ADC_MV(1), ADC_MV(2), \
              ADC_MV(3), ADC_MV(4), ADC_MV(5), ADC_MV(6), ADC_MV(7), ADC_MV(8), ADC_MV(9), ADC_MV(10),\
              ADC_MV(11), ADC_MV(12), ADC_MV(13) );
      break;

    case 4:
      TRACE("ADC:%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u [mV]", ADC_MV(0), ADC_MV(1), ADC_MV(2), \
              ADC_MV(3), ADC_MV(4), ADC_MV(5), ADC_MV(6), ADC_MV(7), ADC_MV(8), ADC_MV(9), ADC_MV(10),\
              ADC_MV(11), ADC_MV(12), ADC_MV(13) );
      break;

    case 3:
      bAdcContinousTrace = ON;
      break;

    case 1:
      // fall through
    default:
      TESTPIN(1);
      TRACE("ADC:  CH 0  CH 1  CH 2  CH 3  CH 4  CH 5  CH 6  CH 7  CH 8  CH 9  CH10  CH11  CH12  CH13");
      TRACE("    %6u%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u%6u", adcBuffer[0], adcBuffer[1], adcBuffer[2], \
                adcBuffer[3], adcBuffer[4], adcBuffer[5], adcBuffer[6], adcBuffer[7], adcBuffer[8], \
                adcBuffer[9], adcBuffer[10], adcBuffer[11], adcBuffer[12], adcBuffer[13]);
      // takes less than 3ms for 14 channels
      TESTPIN(0);

      // test conversion speed
      TESTPIN(1);
      for (i = 0; i < 14; i++)
        {
        iAdc[i] = ADC_MV(i);
        }
      // converting 14 channels to mV takes 27us
      TESTPIN(0);
      break;
    }
  return;
  }

/******************************************************************************
void AdcTicker10ms (void)
  Periodically 10ms tasks
******************************************************************************/
void AdcTicker10ms (void)
  {
  return;
  }

/******************************************************************************
void AdcTicker50ms (void)
  Periodically 50ms tasks
******************************************************************************/
void AdcTicker50ms (void)
  {
  return;
  }

/******************************************************************************
void AdcTicker100ms (void)
  Periodically 100ms tasks
******************************************************************************/
void AdcTicker100ms (void)
  {
  return;
  }

/******************************************************************************
void AdcTicker200ms (void)
  Periodically 200ms tasks
******************************************************************************/
void AdcTicker200ms (void)
  {
  if (bAdcContinousTrace) {
    AdcTrace(4);
    bAdcContinousTrace = TRUE;
    }
  return;
  }

/******************************************************************************
void AdcTicker500ms (void)
  Periodically 500ms tasks
******************************************************************************/
void AdcTicker500ms (void)
  {
  return;
  }

/******************************************************************************
void AdcTicker1s (void)
  Periodically 1s tasks
******************************************************************************/
void AdcTicker1s (void)
  {

  //TRACE("ADC: PA3%5umV", ADC_MV(3));
  return;
  }

/******************************************************************************
int AdcGet (int iChannel)

Returns last conversion value for specified channel.
  Range 0..4095,  full scale..1.25V (reference set to internal 1.25V)
******************************************************************************/
int AdcGet (int iChannel)
  {
  return adcBuffer[iChannel];
  }

/******************************************************************************
int AdcGetMv (int iChannel)

Returns voltage on selected channel in mV
  Range 0..1250
******************************************************************************/
int AdcGetMv (int iChannel)
  {
  return ADC_MV(iChannel);
  }



