/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/***************************************************************************
 *            wm8523.c
 *
 *  Tue Jun  4 20:47:15 CEST 2013
 *  Copyright 2013 Bent Bisballe Nyeng
 *  deva@aasimon.org
 ****************************************************************************/

/*
 *  This file is part of Pedal2Metal.
 *
 *  Pedal2Metal is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  Pedal2Metal 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Pedal2Metal; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
 */
#include "wm8523.h"

#include <ssp.h>
#include <GPIO.h>
#include <i2s.h>

#define READ_BIT  0b1
#define WRITE_BIT 0b0

// Registers:
#define R0 0
#define R1 1
#define R2 2
#define R3 3
#define R4 4
#define R5 5
#define R6 6
#define R7 7
#define R8 8

typedef struct __attribute__ ((packed)) {
  uint8_t reg:7;
  uint8_t rw:1;
  uint8_t msbyte;
  uint8_t lsbyte;
} _reg_t; 

static void reg_read(uint8_t portnum, char reg, uint8_t *buf, int size)
{
  _reg_t data;
  data.rw = READ_BIT;
  data.reg = reg;

	GPIOSetValue(0, 16, 0);
	SSPSend(portnum, (uint8_t *)&data, 1); // write register number and read bit
	SSPReceive(portnum, (uint8_t *)&data.msbyte, 2); // read 16bit register value
	GPIOSetValue(0, 16, 1);

  if(size == 1) {
    buf[0] = data.lsbyte;
  } else if(size == 2) {
    buf[0] = data.lsbyte;
    buf[1] = data.msbyte;
  } else {
    // Error
    return;
  }
}

static void reg_write(uint8_t portnum, char reg, const uint8_t *buf, int size)
{
  _reg_t data;
  data.rw = WRITE_BIT;
  data.reg = reg;
  switch(size) {
  case 0:
    data.lsbyte = 0;
    data.msbyte = 0;
    break;
  case 1:
    data.lsbyte = buf[0];
    data.msbyte = 0;
    break;
  case 2:
    data.lsbyte = buf[0];
    data.msbyte = buf[1];
    break;
  default:
    // Error...
    return;
  }

	GPIOSetValue(0, 16, 0);
	SSPSend(portnum, (uint8_t *)&data, sizeof(data));
	GPIOSetValue(0, 16, 1);
}

/**
 * Prepare I2S, SSP and GPIO.
 */
#include <LPC17xx.h>
void wm8523_init(uint8_t portnum, wm8523_samplerate_t fs)
{
	// Init SPI
	GPIOSetDir(0, 16, 1);
	GPIOSetValue(0, 16, 1);

  if(portnum == 0) SSP0Init();
  else SSP1Init();

  (void)fs;

#if 1

#else
  I2SInit();

  // pg. 482
  // I2STXMODE 0x400A 8030
  //  bit 3  Enable for the TX_MCLK output. When 0, output of TX_MCLK is not enabled. When 1, output of TX_MCLK is enabled.
  // set to 1, default is 0
  uint32_t *i2stxmode = (uint32_t*)0x400a8030;
  *i2stxmode |=
    (0b1 << 3) // Enable TX_MCLK output.
    ;

  // pg. 477
  // I2SDAO 0x400A 8000
  // bit 5  When 0, the interface is in master mode. When 1, the interface is in slave mode.
  // set to 0, default is 1
  uint32_t *i2sdao = (uint32_t*)0x400a8030;
  *i2sdao &=
    ~(0b1 << 5) // Set I2S in master mode.
    ;


  // pg. 481
  // I2STXBITRATE 0x400A 8028
  // bit 0-5  I2S transmit bit rate. This value plus one is used to divide TX_MCLK to produce the transmit bit clock.
  // set to 31 (divide by 32): stereo (2) * 16bit
  uint32_t *i2stxbitrate = (uint32_t *)0x400a8028;
  *i2stxbitrate = 7;

  // bitclock = mclock / (divider+1)
  // samplerate = mclock / bitclock


  (void)fs;
  /*
  WM8523_FS_8K,
  WM8523_FS_32K,
  WM8523_FS_44K1,
  WM8523_FS_48K,
  WM8523_FS_88K2,
  WM8523_FS_96K,
  WM8523_FS_176K4,
  WM8523_FS_192K,
  */
  uint32_t *pclksel1 = (uint32_t *)0x400fc1ac;
  *pclksel1 |= 
    (0b10 << 22) // Select I2S clock: PCLK/2 see pg. 57 table 41/42
    ;

  uint32_t *pinsel9 = (uint32_t *)0x4002c024;
  *pinsel9 |=
    (0b01 << 26) // Select p4.29 as TX_MCLK
    ;

  /*
  uint32_t *pinsel3 = (uint32_t *)0x4002c00c;
  *pinsel3 |=
    (0b01 << 22) // config p1.27 as CLKOUT 
    ;

  // Set up master clock see pg. 66
  uint32_t *clkcfg = (uint32_t *)0x400fc1c8;
  *clkcfg =
    (0b0000 << 0) | // Select RTC as clock source
    (0b1111 << 4) | // Divide by 16
    (0b1    << 8)   // Enable
    ;
  */ 
#endif   
}

unsigned short wm8523_get_chip_id(uint8_t portnum)
{
  unsigned short id;
  reg_read(portnum, R0, (uint8_t*)&id, sizeof(id));
  return id;
}

void wm8523_reset_registers(uint8_t portnum)
{
  reg_write(portnum, R0, 0, 0);
}

uint8_t wm8523_get_hardware_revision(uint8_t portnum)
{
  uint8_t rev;
  reg_read(portnum, R1, &rev, sizeof(rev));
  return rev;
}

wm8523_power_mode_t wm8523_get_power_mode(uint8_t portnum)
{
  wm8523_power_mode_t mode;
  reg_read(portnum, R2, (uint8_t*)&mode, sizeof(mode));
  return mode;
}

void wm8523_set_power_mode(uint8_t portnum, wm8523_power_mode_t mode)
{
  reg_write(portnum, R2, (uint8_t*)&mode, sizeof(mode));
}

void wm8523_set_aif_ctrl1(uint8_t portnum, wm8523_aif_ctrl1_t aif_ctrl1)
{
  aif_ctrl1.blank = aif_ctrl1.reserved = 0;
  reg_write(portnum, R3, (uint8_t*)&aif_ctrl1, sizeof(aif_ctrl1));
}

wm8523_aif_ctrl1_t wm8523_get_aif_ctrl1(uint8_t portnum)
{
  wm8523_aif_ctrl1_t aif_ctrl1;

  reg_read(portnum, R3, (uint8_t*)&aif_ctrl1, sizeof(aif_ctrl1));

  return aif_ctrl1;
}

void wm8523_set_aif_ctrl2(uint8_t portnum, wm8523_aif_ctrl2_t aif_ctrl2)
{
  reg_write(portnum, R4, (uint8_t*)&aif_ctrl2, sizeof(aif_ctrl2));
}

wm8523_aif_ctrl2_t wm8523_get_aif_ctrl2(uint8_t portnum)
{
  wm8523_aif_ctrl2_t aif_ctrl2;

  reg_read(portnum, R4, (uint8_t*)&aif_ctrl2, sizeof(aif_ctrl2));

  return aif_ctrl2;
}

void wm8523_set_dac_ctrl3(uint8_t portnum, wm8523_dac_ctrl3_t dac_ctrl3)
{
  reg_write(portnum, R5, (uint8_t*)&dac_ctrl3, sizeof(dac_ctrl3));
}

wm8523_dac_ctrl3_t wm8523_get_dac_ctrl3(uint8_t portnum)
{
  wm8523_dac_ctrl3_t dac_ctrl3;

  reg_read(portnum, R5, (uint8_t*)&dac_ctrl3, sizeof(dac_ctrl3));

  return dac_ctrl3;
}

#include "dma.h"
#include <LPC17xx.h>

extern volatile uint8_t *I2STXBuffer, *I2SRXBuffer;
extern volatile uint32_t I2SReadLength;
extern volatile uint32_t I2SWriteLength;
extern volatile uint32_t I2SRXDone, I2STXDone;
extern volatile uint32_t I2SDMA0Done, I2SDMA1Done;

short signal(int x)
{
  static short v = 0;
  if((x / 1000) % 2) v++;
  else v--;
  return x % 0xffff;
}

#if 0
//
// Sine approximation using taylor series
// http://en.wikipedia.org/wiki/Taylor_series
//
#define M_PI 3.14159265359
float expand(float x, int e)
{
  
  int fak = 1;
  while(e) {
    x *= x;
    fak *= e;
    e--;
  }

  return x / (float)fak;
}

float _sin(float x)
{
  return x - expand(x, 3) + expand(x, 5) - expand(x, 7);
}
#endif

void wm8523_tone()
{
  /*
  // I2SSTATE 0x400A 8010
  // bit 16-19  Reflects the current level of the Transmit FIFO.
  uint32_t *i2sstate = (uint32_t *)0x400a8010;
  (void)i2sstate;

  // I2STXFIFO 0x400A 8008
  // 8 × 32-bit transmit FIFO.
  uint32_t *i2stxfifo = (uint32_t*)0x400a8008;

  // Not DMA mode, enable I2S interrupts.
  NVIC_EnableIRQ(I2S_IRQn);

  // RX FIFO depth is 1, TX FIFO depth is 8.
  //I2SStart();
  //i2s_tx_start();
  //LPC_I2S->I2SIRQ = (8 << 16) | (1 << 8) | (0x01 << 0);

  //uint32_t val = 0;
  uint32_t cnt = 0;
  while(1) {
    while((*i2sstate & (0b1111 << 16)) != 0) { cli_printf("."); }
    if(cnt % 80 == 0) cli_printf("\n");
    cli_printf(".");
    *i2stxfifo = samples[(cnt ++) % sizeof(samples)];
  }
  */

#if 0
  int i;

  short *pcm = (short*)I2STXBuffer;

  /// Configure temp register before reading
  int pcm_size = BUFSIZE / sizeof(short) / 2; // 2 channels
  for ( i = 0; i < pcm_size; i++ )	{ // Clear buffer
    //float s = _sin((float)i / 44100 * 2 * M_PI * 440);
    short s = signal(i);
    pcm[2 * i] = s * 1234567890;//s * 32000;//samples[i];
    pcm[2 * i + 1] = s * 1234567890;//s * 32000;//samples[i];
    //I2SRXBuffer[i] = 0;
  }

  //  I2SInit();				// Initialize I2S

 #if I2S_DMA_ENABLED
  DMA_Init();

  // Select secondary function(I2S) in DMA channels 
  LPC_SC->DMAREQSEL = (0x1<<DMA_I2S_REQ0)|(0x1<<DMA_I2S_REQ1);

  // On DMA channel 0, Source is memory, destination is I2S TX FIFO, 
  // On DMA channel 1, source is I2S RX FIFO, Destination is memory

  // Enable channel and IE bit
  DMAChannel_Init( 0, M2P );
  LPC_GPDMACH0->DMACCConfig |= (0x18001|(0x00<<1)|(DMA_I2S_REQ0<<6)|(0x01<<11));

  //DMAChannel_Init( 1, P2M );
  //  LPC_GPDMACH1->CConfig |= (0x08001|(DMA_I2S_REQ1<<1)|(0x00<<6)|(0x02<<11));
 
  NVIC_EnableIRQ(DMA_IRQn);

  I2SStart();	  

  // Channel 2 is for RX, enable RX first.
  //LPC_I2S->I2SDMA2 = (0x01<<0) | (0x08<<8);
	
  // Channel 1 is for TX.
  LPC_I2S->I2SDMA1 = (0x01<<1) | (0x01<<16);

  // Wait for both DMA0 and DMA1 to finish before verifying.
  while(!I2SDMA0Done/* || !I2SDMA1Done*/);
#else

  //  this does not compile

  // Not DMA mode, enable I2S interrupts.
  NVIC_EnableIRQ(I2S_IRQn);

  // RX FIFO depth is 1, TX FIFO depth is 8.
  I2SStart();
  LPC_I2S->I2SIRQ = (8 << 16) | (1 << 8) | (0x01 << 0);

  while(I2SWriteLength < BUFSIZE) {
    while (((LPC_I2S->I2SSTATE >> 16) & 0xFF) == TXFIFO_FULL);
    LPC_I2S->I2STXFIFO = I2STXBuffer[I2SWriteLength++];
  }

  I2STXDone = 1;

  // Wait for RX and TX complete before comparison
  while(!I2SRXDone || !I2STXDone);
#endif
  /*
  // Validate TX and RX buffer
  for(i=1; i<BUFSIZE; i++) {
    if(I2SRXBuffer[i] != I2STXBuffer[i-1]) {
      while(1);	// Validation error
    }
  }
  while(1);	// Don't exit from main when finishing.
  */
#endif
}