/* Teensy 3.x, LC, 4.0 ADC library
   https://github.com/pedvide/ADC
   Copyright (c) 2020 Pedro Villanueva

   Permission is hereby granted, free of charge, to any person obtaining
   a copy of this software and associated documentation files (the
   "Software"), to deal in the Software without restriction, including
   without limitation the rights to use, copy, modify, merge, publish,
   distribute, sublicense, and/or sell copies of the Software, and to
   permit persons to whom the Software is furnished to do so, subject to
   the following conditions:

   The above copyright notice and this permission notice shall be
   included in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   SOFTWARE.
*/

#ifdef ADC_USE_DMA

#include "AnalogBufferDMA.h"

//#define DEBUG_DUMP_DATA
// Global objects
AnalogBufferDMA *AnalogBufferDMA::_activeObjectPerADC[2] = {nullptr, nullptr};

#if defined(__IMXRT1062__) // Teensy 4.0
#define SOURCE_ADC_0 ADC1_R0
#define DMAMUX_ADC_0 DMAMUX_SOURCE_ADC1
#define SOURCE_ADC_1 ADC2_R0
#define DMAMUX_ADC_1 DMAMUX_SOURCE_ADC2
#elif defined(KINETISK)
#define SOURCE_ADC_0 ADC0_RA
#define DMAMUX_ADC_0 DMAMUX_SOURCE_ADC0
#ifdef ADC_DUAL_ADCS
#define SOURCE_ADC_1 ADC1_RA
#define DMAMUX_ADC_1 DMAMUX_SOURCE_ADC1
#endif
#elif defined(KINETISL)
#define SOURCE_ADC_0 ADC0_RA
#define DMAMUX_ADC_0 DMAMUX_SOURCE_ADC0
#endif

//=============================================================================
// Debug support
//=============================================================================

#ifdef DEBUG_DUMP_DATA
static void dumpDMA_TCD(DMABaseClass *dmabc)
{
#ifndef KINETISL
  Serial.printf("%x %x:", (uint32_t)dmabc, (uint32_t)dmabc->TCD);

  Serial.printf("SA:%x SO:%d AT:%x NB:%x SL:%d DA:%x DO: %d CI:%x DL:%x CS:%x BI:%x\n", (uint32_t)dmabc->TCD->SADDR,
                dmabc->TCD->SOFF, dmabc->TCD->ATTR, dmabc->TCD->NBYTES, dmabc->TCD->SLAST, (uint32_t)dmabc->TCD->DADDR,
                dmabc->TCD->DOFF, dmabc->TCD->CITER, dmabc->TCD->DLASTSGA, dmabc->TCD->CSR, dmabc->TCD->BITER);
#else
  Serial.printf("%x %x:", (uint32_t)dmabc, (uint32_t)dmabc->CFG);

  Serial.printf("SAR:%x DAR:%x DSR_BCR:%x DCR:%x\n", (uint32_t)dmabc->CFG->SAR, dmabc->CFG->DAR, dmabc->CFG->DSR_BCR,
                dmabc->CFG->DCR);
#endif
}
#endif

//=============================================================================
// Init - Initialize the object including setup DMA structures
//=============================================================================
void AnalogBufferDMA::init(ADC *adc, int8_t adc_num)
{
  // enable DMA and interrupts
#ifdef DEBUG_DUMP_DATA
  Serial.println("AnalogBufferDMA::init");
  Serial.flush();
#endif

#ifndef KINETISL
  // setup a DMA Channel.
  // Now lets see the different things that RingbufferDMA setup for us before
  // See if we were created with one or two buffers.  If one assume we stop on completion, else assume continuous.
  if (_buffer2 && _buffer2_count)
  {
#ifdef ADC_DUAL_ADCS
    _dmasettings_adc[0].source((volatile uint16_t &)((adc_num == 1) ? SOURCE_ADC_1 : SOURCE_ADC_0));
#else
    _dmasettings_adc[0].source((volatile uint16_t &)(SOURCE_ADC_0));
#endif
    _dmasettings_adc[0].destinationBuffer((uint16_t *)_buffer1, _buffer1_count * 2); // 2*b_size is necessary for some reason
    _dmasettings_adc[0].replaceSettingsOnCompletion(_dmasettings_adc[1]);            // go off and use second one...
    _dmasettings_adc[0].interruptAtCompletion();                                     //interruptAtHalf or interruptAtCompletion
#ifdef ADC_DUAL_ADCS
    _dmasettings_adc[1].source((volatile uint16_t &)((adc_num == 1) ? SOURCE_ADC_1 : SOURCE_ADC_0));
    _dmasettings_adc[1].destinationBuffer((uint16_t *)_buffer2, _buffer2_count * 2); // 2*b_size is necessary for some reason
    _dmasettings_adc[1].replaceSettingsOnCompletion(_dmasettings_adc[0]);            // Cycle back to the first one
    _dmasettings_adc[1].interruptAtCompletion();                                     //interruptAtHalf or interruptAtCompletion
#endif

    _dmachannel_adc = _dmasettings_adc[0];

    _stop_on_completion = false;
  }
  else
  {
// Only one buffer so lets just setup the dmachannel ...
// Serial.printf("AnalogBufferDMA::init Single buffer %d\n", adc_num);
#ifdef ADC_DUAL_ADCS
    _dmachannel_adc.source((volatile uint16_t &)((adc_num == 1) ? SOURCE_ADC_1 : SOURCE_ADC_0));
#else
    _dmachannel_adc.source((volatile uint16_t &)(SOURCE_ADC_0));
#endif
    _dmachannel_adc.destinationBuffer((uint16_t *)_buffer1, _buffer1_count * 2); // 2*b_size is necessary for some reason
    _dmachannel_adc.interruptAtCompletion();                                     //interruptAtHalf or interruptAtCompletion
    _dmachannel_adc.disableOnCompletion();                                       // we will disable on completion.
    _stop_on_completion = true;
  }

  if (adc_num == 1)
  {
#ifdef ADC_DUAL_ADCS
    _activeObjectPerADC[1] = this;
    _dmachannel_adc.attachInterrupt(&adc_1_dmaISR);
    _dmachannel_adc.triggerAtHardwareEvent(DMAMUX_ADC_1); // start DMA channel when ADC finishes a conversion
#endif
  }
  else
  {
    _activeObjectPerADC[0] = this;
    _dmachannel_adc.attachInterrupt(&adc_0_dmaISR);
    _dmachannel_adc.triggerAtHardwareEvent(DMAMUX_ADC_0); // start DMA channel when ADC finishes a conversion
  }
  //arm_dcache_flush((void*)dmaChannel, sizeof(dmaChannel));
  _dmachannel_adc.enable();

  adc->adc[adc_num]->continuousMode();
  adc->adc[adc_num]->enableDMA();

#ifdef DEBUG_DUMP_DATA
  dumpDMA_TCD(&_dmachannel_adc);
  dumpDMA_TCD(&_dmasettings_adc[0]);
  dumpDMA_TCD(&_dmasettings_adc[1]);
#if defined(__IMXRT1062__) // Teensy 4.0

  if (adc_num == 1)
  {
    Serial.printf("ADC2: HC0:%x HS:%x CFG:%x GC:%x GS:%x\n", ADC2_HC0, ADC2_HS, ADC2_CFG, ADC2_GC, ADC2_GS);
  }
  else
  {
    Serial.printf("ADC1: HC0:%x HS:%x CFG:%x GC:%x GS:%x\n", ADC1_HC0, ADC1_HS, ADC1_CFG, ADC1_GC, ADC1_GS);
  }
#endif
#endif
#else
  // Kinetisl (TLC)
  // setup a DMA Channel.
  // Now lets see the different things that RingbufferDMA setup for us before
  _dmachannel_adc.source((volatile uint16_t &)(SOURCE_ADC_0));
  ;
  _dmachannel_adc.destinationBuffer((uint16_t *)_buffer1, _buffer1_count * 2); // 2*b_size is necessary for some reason
  _dmachannel_adc.disableOnCompletion();                                       // ISR will hae to restart with other buffer
  _dmachannel_adc.interruptAtCompletion();                                     //interruptAtHalf or interruptAtCompletion
  _activeObjectPerADC[0] = this;
  _dmachannel_adc.attachInterrupt(&adc_0_dmaISR);
  _dmachannel_adc.triggerAtHardwareEvent(DMAMUX_ADC_0); // start DMA channel when ADC finishes a conversion
  _dmachannel_adc.enable();

  adc->startContinuous(adc_num);
  adc->enableDMA(adc_num);
#ifdef DEBUG_DUMP_DATA
  dumpDMA_TCD(&_dmachannel_adc);
#endif

#endif

  _last_isr_time = millis();
}

//=============================================================================
// stopOnCompletion: allows you to turn on or off stopping when a DMA buffer
//    has completed filling. Default is on when only one buffer passed in to the
//    constructor and off if two buffers passed in.
//=============================================================================
void AnalogBufferDMA::stopOnCompletion(bool stop_on_complete)
{
#ifndef KINETISL
  if (stop_on_complete)
    _dmachannel_adc.TCD->CSR |= DMA_TCD_CSR_DREQ;
  else
    _dmachannel_adc.TCD->CSR &= ~DMA_TCD_CSR_DREQ;
#else
  if (stop_on_complete)
    _dmachannel_adc.CFG->DCR |= DMA_DCR_D_REQ;
  else
    _dmachannel_adc.CFG->DCR &= ~DMA_DCR_D_REQ;
#endif
  _stop_on_completion = stop_on_complete;
}

//=============================================================================
// ClearCompletion: if we have stop on completion, then clear the completion state
//                  i.e. reenable the dma operation.  Note only valid if we are
//                  in the stopOnCompletion state.
//=============================================================================
bool AnalogBufferDMA::clearCompletion()
{
  if (!_stop_on_completion)
    return false;
  // should probably check to see if we are dsiable or not...
  _dmachannel_adc.enable();
  return true;
}

//=============================================================================
// processADC_DMAISR: Process the DMA completion ISR
//     common for both ISRs on those processors who have more than one ADC
//=============================================================================
void AnalogBufferDMA::processADC_DMAISR()
{
  //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN));
  uint32_t cur_time = millis();

  _interrupt_count++;
  _interrupt_delta_time = cur_time - _last_isr_time;
  _last_isr_time = cur_time;
  // update the internal buffer positions
  _dmachannel_adc.clearInterrupt();
#ifdef KINETISL
  // Lets try to clear the previous interrupt, change buffers
  // and restart
  if (_buffer2 && (_interrupt_count & 1))
  {
    _dmachannel_adc.destinationBuffer((uint16_t *)_buffer2, _buffer2_count * 2); // 2*b_size is necessary for some reason
  }
  else
  {
    _dmachannel_adc.destinationBuffer((uint16_t *)_buffer1, _buffer1_count * 2); // 2*b_size is necessary for some reason
  }

  // If we are not stopping on completion, then reenable...
  if (!_stop_on_completion)
    _dmachannel_adc.enable();

#endif
}

//=============================================================================
// adc_0_dmaISR: called for first ADC when DMA has completed filling a buffer.
//=============================================================================
void AnalogBufferDMA::adc_0_dmaISR()
{
  if (_activeObjectPerADC[0])
  {
    _activeObjectPerADC[0]->processADC_DMAISR();
  }
#if defined(__IMXRT1062__) // Teensy 4.0
  asm("DSB");
#endif
}

//=============================================================================
// adc_1_dmaISR - Used for processors that have a second ADC object
//=============================================================================
void AnalogBufferDMA::adc_1_dmaISR()
{
  if (_activeObjectPerADC[1])
  {
    _activeObjectPerADC[1]->processADC_DMAISR();
  }
#if defined(__IMXRT1062__) // Teensy 4.0
  asm("DSB");
#endif
}

#endif // ADC_USE_DMA