/*
  SinglePinI2SDriver  
  ESP8266Audio I2S Minimal driver

  Most of this code is taken and reworked from ESP8266 Arduino core,
  which itsef is reworked from Espessif's I2S examples.
  Original code is licensed under LGPL 2.1 or above
 
  Reasons for rewrite:
  - Non-configurable size of DMA buffers
  - We only need GPIO3 connected to I2SO_DATA
  - No API to queue data from ISR. Callbacks are unusable 
    as i2s_write_* functions are not in IRAM and may re-enable 
    SLC interrupt before returning
  - ISR overhead is not needed

  Copyright (C) 2020 Ivan Kostoski

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#if defined(ESP8266)

#include "SinglePinI2SDriver.h"

SinglePinI2SDriver::~SinglePinI2SDriver()
{
  stopI2S();
  freeBuffers(bufCount);
}

bool SinglePinI2SDriver::begin(uint8_t bufCount, uint16_t bufSize)
{
  if (bufCount < 4) return false;
  if ((bufSize < 32) || (bufSize > 1020) || ((bufSize % 4) != 0)) return false;

  stopSLC();

  if ((bufCount != this->bufCount) || (bufSize != this->bufSize)) freeBuffers(this->bufCount);
  this->bufCount = bufCount;
  this->bufSize = bufSize;
  if (!allocateBuffers()) return false;

  zeroBuffers();
  configureSLC();
  startI2S();

  // Send out at least one buffer, so we have valid address of 
  // finished descriptor in SLCRXEDA
  head = &descriptors[0];
  head->owner = 1;
  startSLC();
  while (!lastSentDescriptor()) delayMicroseconds(1);
  // By here, SLC should be stalled (next descriptor owner is 0)
  head->owner = 0;
  head = head->next_link_ptr;
  headPos = 0;
  underflowCount = -1;
  return true;
};

void SinglePinI2SDriver::stop()
{
  stopI2S();
}

bool SinglePinI2SDriver::allocateBuffers() 
{
  // Allocate output (RXLINK) descriptors and bufferss
  if (descriptors) return true;
  descriptors = (SLCDecriptor*) calloc(bufCount, sizeof(SLCDecriptor));
  if (!descriptors) return false;
  int allocated;
  for (allocated = 0; allocated < bufCount; allocated++) {
    uint32_t* buffer = (uint32_t*)malloc(bufSize * sizeof(uint32_t));
    if (!buffer) break;
    auto descriptor = &descriptors[allocated];
    descriptor->eof = 1; // Needed for SLC to update SLCRXEDA
    descriptor->datalen = bufSize * sizeof(uint32_t);
    descriptor->blocksize = bufSize * sizeof(uint32_t);
    descriptor->buf_ptr = buffer;
    descriptor->next_link_ptr = &descriptors[(allocated+1) % bufCount];
  }
  // Release memory if not all buffers were allocated
  if (allocated < bufCount) {
    freeBuffers(allocated);
    return false;
  }
  zeroBuffers();
  return true;
}

void SinglePinI2SDriver::freeBuffers(int allocated)
{
  if (!descriptors) return;
  while (--allocated >= 0) {
    if (descriptors[allocated].buf_ptr) {
      free(descriptors[allocated].buf_ptr);
      descriptors[allocated].buf_ptr = NULL;
    }
  }
  free(descriptors);
  descriptors = NULL;
}

void SinglePinI2SDriver::zeroBuffers()
{
  for(int i = 0; i < bufCount; i++) {    
    auto descriptor = &descriptors[i];
    ets_memset((void *)descriptor->buf_ptr, 0, bufSize * sizeof(uint32_t));
  }
}

inline void SinglePinI2SDriver::advanceHead(const SLCDecriptor *lastSent) 
{
  if (headPos >= bufSize) {
    head->owner = 1; // Owned by SLC
    head = head->next_link_ptr;
    head->owner = 0; // Owned by CPU
    headPos = 0;
    // If SLC stopped, fill-up the buffers before (re)starting
    if (isSLCStopped() && (head->next_link_ptr == lastSent)) { 
      underflowCount++;
      startSLC();
    }
  }
}

bool SinglePinI2SDriver::write(uint32_t sample) 
{
  auto lastSent = lastSentDescriptor();
  if (isSLCRunning() && (lastSent->next_link_ptr == head)) return false;
  head->buf_ptr[headPos++] = sample;
  advanceHead(lastSent);
  return true;
};

bool SinglePinI2SDriver::writeInterleaved(uint32_t samples[4]) 
{
  auto lastSent = lastSentDescriptor();
  if (isSLCRunning() && (lastSent->next_link_ptr == head)) return false;
  uint32_t* buf = head->buf_ptr;
  buf[headPos++] = samples[1];
  buf[headPos++] = samples[0];
  buf[headPos++] = samples[3];
  buf[headPos++] = samples[2];
  advanceHead(lastSent);
  return true;
};

void SinglePinI2SDriver::configureSLC()
{
  ETS_SLC_INTR_DISABLE();
  // Reset SLC, clear interrupts
  SLCC0 |= SLCRXLR | SLCTXLR;
  SLCC0 &= ~(SLCRXLR | SLCTXLR);
  SLCIC = 0xFFFFFFFF;
  // Configure SLC DMA in mode 1
  SLCC0 &= ~(SLCMM << SLCM);
  SLCC0 |= (1 << SLCM);
  SLCRXDC |= SLCBINR | SLCBTNR;
  SLCRXDC &= ~(SLCBRXFE | SLCBRXEM | SLCBRXFM);
  // RXLINK is output DMA, but TXLINK must be configured with valid descriptr
  SLCTXL &= ~(SLCTXLAM << SLCTXLA);
  SLCRXL &= ~(SLCRXLAM << SLCRXLA);
  SLCTXL |= (uint32)&descriptors[0] << SLCTXLA;
  SLCRXL |= (uint32)&descriptors[0] << SLCRXLA;
  // Not setting up interrupts. Not starting DMA here
}

void SinglePinI2SDriver::stopSLC()
{
  // Stop SLC, reset descriptors and clear interrupts
  ETS_SLC_INTR_DISABLE();
  SLCTXL |= SLCTXLE;
  SLCRXL |= SLCRXLE;
  SLCTXL &= ~(SLCTXLAM << SLCTXLA);
  SLCRXL &= ~(SLCRXLAM << SLCRXLA);
  SLCIC = 0xFFFFFFFF;
}

void SinglePinI2SDriver::setDividers(uint8_t div1, uint8_t div2)
{
  // Ensure dividers fit in bit fields
  div1 &= I2SBDM;
  div2 &= I2SCDM;
  // trans master(active low), recv master(active_low), !bits mod(==16 bits/chanel), clear clock dividers
  I2SC &= ~(I2STSM | I2SRSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD));
  // I2SRF = Send/recv right channel first
  // I2SMR = MSB recv/xmit first
  // I2SRMS, I2STMS = We don't really care about WS delay here
  // div1, div2 = Set I2S WS clock frequency. BCLK seems to be generated from 32x this
  I2SC |= I2SRF | I2SMR | I2SRMS | I2STMS | (div1 << I2SBD) | (div2 << I2SCD);
}

void SinglePinI2SDriver::setRate(const uint32_t rateHz)
{
  if (rateHz == currentRate) return;
  currentRate = rateHz;

  uint32_t scaledBaseFreq = I2SBASEFREQ / 32;
  float bestDelta = scaledBaseFreq;
  uint8_t sbdDivBest=1;
  uint8_t scdDivBest=1;
  for (uint8_t i = 1; i < 64; i++) {
    for (uint8_t j = i; j < 64; j++) {
      float delta = fabs(((float)scaledBaseFreq/i/j) - rateHz);
      if (delta < bestDelta){
      	bestDelta = delta;
        sbdDivBest = i;
        scdDivBest = j;
      }
    }
  }

  setDividers(sbdDivBest, scdDivBest);
}

float SinglePinI2SDriver::getActualRate()
{
  return (float)I2SBASEFREQ/32/((I2SC>>I2SBD) & I2SBDM)/((I2SC >> I2SCD) & I2SCDM);
}

void SinglePinI2SDriver::startI2S()
{
  // Setup only I2S SD pin for output
  pinMode(I2SO_DATA, FUNCTION_1);
  I2S_CLK_ENABLE();
  // Reset I2S
  I2SIC = 0x3F;
  I2SIE = 0;
  I2SC &= ~(I2SRST);
  I2SC |= I2SRST;
  I2SC &= ~(I2SRST);  
  // I2STXFMM, I2SRXFMM=0 => 16-bit, dual channel data 
  I2SFC &= ~(I2SDE | (I2STXFMM << I2STXFM) | (I2SRXFMM << I2SRXFM)); 
  I2SFC |= I2SDE; // Enable DMA
  // I2STXCMM, I2SRXCMM=0 => Dual channel mode, RX/TX CHAN_MOD=0
  I2SCC &= ~((I2STXCMM << I2STXCM) | (I2SRXCMM << I2SRXCM));
  // Set dividers to something resonable
  currentRate = 0;
  setRate(44100);
  // Start I2S peripheral
  I2SC |= I2STXS; 
}

void SinglePinI2SDriver::stopI2S()
{
  // Disable any I2S send or receive
  I2SC &= ~(I2STXS | I2SRXS);
  // Reset I2S
  I2SC &= ~(I2SRST);
  I2SC |= I2SRST;
  I2SC &= ~(I2SRST);

  stopSLC();
  // Reconnect RX0 ?
  //pinMode(I2SO_DATA, SPECIAL);
}

int SinglePinI2SDriver::getUnderflowCount()
{
  int count = underflowCount;
  underflowCount = 0;
  return count;
}

// Global instance
SinglePinI2SDriver I2SDriver;

#endif // defined(ESP8266)