
//------------------------------------------------------------------------------
// Copyright (c) 2012 by Silicon Laboratories. 
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Silicon Laboratories End User 
// License Agreement which accompanies this distribution, and is available at
// http://developer.silabs.com/legal/version/v10/License_Agreement_v10.htm
// Original content and implementation provided by Silicon Laboratories.
//------------------------------------------------------------------------------
// library
#include <si32McuComponent.h>
#include <si32PseudOsComponent.h>
// hal
// application
#include "myApplication.h"
#include "myControlPlane.h"
#include "myI2C0.h"

//------------------------------------------------------------------------------
static si32PseudoThreadObject _myControlPlane = si32PseudoThreadObject();
si32PseudoThreadObject* myControlPlane = &_myControlPlane;
// 0=left output, 1=right output, 2=input
int16 myControlPlaneVolume[3] = { 6, 6, 0 };
bool myControlPlaneMute[3] = { NO, NO, NO };
static bool _transmitted = NO;

//------------------------------------------------------------------------------
static void tx_callback(
  si32PortalObjectResultType result,
  uint32 actual,
  void* context0,
  void* context1)
{
  si32Assert(result == SI32_PORTAL_OBJECT_RESULT_SUCCESS);
  _transmitted = YES;
  si32PseudoThreadObject_resume(myControlPlane, 
                                SI32_PSEUDO_THREAD_PRIORITY_LOW);
}

//------------------------------------------------------------------------------
// this macro uses the si32PseudoThread_wait_until macro, and it is used in multiple
// locations in myControlPlaneFn, so the build option si32BuildOption_addressable_labels
// must be enabled in myBuildOptions.h for this to compile correctly.
// the WM8976 operating in 2wire mode has a fixed node address.
// This macro is anaphoric in terms of buffer, _transmitted, result.
//
// This could be rewritten using si32PseudoThread_wait_function() now that it exists.
// For now, the macro is adequate.
#define NODE_ADDR 0x34
#define write_codec_register(reg, val) \
{ \
  buffer[0] = NODE_ADDR; \
  buffer[1] = (reg << 1) | (((val) & 0x0100) >> 8); \
  buffer[2] = (val) & 0x00FF; \
  printf("[%02X %02X %02X]\n", buffer[0], buffer[1], buffer[2]); \
  _transmitted = NO; \
  result = si32IicAPortalObject_write_async(my_iic0_portal, \
                                            buffer, \
                                            3, \
                                            tx_callback, \
                                            NULL, \
                                            NULL); \
  si32Assert(result == SI32_PORTAL_OBJECT_RESULT_IN_PROGRESS); \
  si32PseudoThread_wait_until(_transmitted); \
}

//------------------------------------------------------------------------------
static 
uint32 myControlPlaneFn()
{
  si32LogPrologue();
  si32PseudoThread_begin();

  // TBD - next rev of audio board will use L/ROUT1 for headphone.
    
  // Wolfson WM8976 - DAC slave mode, I2S, 16 bit, LOUT2 and ROUT2 output.
  static uint8 buffer[3] = { NODE_ADDR, 0x00, 0x00 };
  static si32PortalObjectResultType result = SI32_PORTAL_OBJECT_RESULT_SUCCESS;
  
  // software reset
  write_codec_register(0x00, 0x000);  
  
  #define R1_BUFDCOPEN       0x0100
  #define R1_OUT4MIXEN       0x0010
  #define R1_OUT3MIXEN       0x0040
  #define R1_PLLEN           0x0020
  #define R1_MICBEN          0x0010
  #define R1_BIASEN          0x0008
  #define R1_BUFIOEN         0x0004
  #define R1_VMIDSEL_OFF     0x0000                          
  #define R1_VMIDSEL_75KOHM  0x0001
  #define R1_VMIDSEL_300KOHM 0x0002
  #define R1_VMIDSEL_5KOHM   0x0003
  // out only
  //write_codec_register(1, R1_BUFDCOPEN |R1_PLLEN |R1_BIASEN |R1_BUFIOEN |R1_VMIDSEL_300KOHM);
  // out + in
  write_codec_register(1, R1_BUFDCOPEN |R1_PLLEN |R1_MICBEN |R1_BIASEN |R1_BUFIOEN |R1_VMIDSEL_300KOHM);
 
  #define R2_ROUT1EN   0x0100
  #define R2_LOUT1EN   0x0080
  #define R2_SLEEP     0x0040
  #define R2_BOOSTENL  0x0010
  #define R2_IMPPGAENL 0x0004
  #define R2_ADCENL    0x0001
  // out only
  //write_codec_register(2, 0x000);  
  // out + in
  write_codec_register(2, R2_ROUT1EN |R2_LOUT1EN |R2_BOOSTENL |R2_IMPPGAENL |R2_ADCENL);  

  #define R3_OUT4EN  0x0100
  #define R3_OUT3EN  0x0080
  #define R3_L2OUTEN 0x0040
  #define R3_R2OUTEN 0x0020
  #define R3_RMIXEN  0x0008
  #define R3_LMIXEN  0x0004
  #define R3_DACENR  0x0002
  #define R3_DACENL  0x0001
  write_codec_register(3, R3_RMIXEN |R3_LMIXEN |R3_DACENR |R3_DACENL);
  
  #define R4_BCP       0x0100
  #define R4_LRP       0x0080
  #define R4_WL16      0x0000
  #define R4_WL20      0x0020
  #define R4_WL24      0x0040
  #define R4_WL32      0x0060
  #define R4_FMTR      0x0000
  #define R4_FMTL      0x0008
  #define R4_FMTI      0x0010
  #define R4_FMTP      0x0018
  #define R4_DATLRSWAP 0x0004
  #define R4_ADCLRSWAP 0x0002
  #define R4_DACMONO   0x0001
  write_codec_register(4, R4_FMTI);
  
  #define R5_WL8           0x0020
  #define R5_DAC_COMP_OFF  0x0000
  #define R5_DAC_COMP_ULAW 0x0010
  #define R5_DAC_COMP_ALAW 0x0018
  #define R5_LOOPBACK      0x0001
  write_codec_register(5, 0x000);
  
  #define R6_CLKSEL     0x0100
  #define R6_MCLKDIV1   0x0000
  #define R6_MCLKDIV1p5 0x0020
  #define R6_MCLKDIV2   0x0040
  #define R6_MCLKDIV3   0x0060
  #define R6_MCLKDIV4   0x0080
  #define R6_MCLKDIV6   0x00A0
  #define R6_MCLKDIV8   0x00C0
  #define R6_MCLKDIV12  0x00E0
  #define R6_BCLKDIV1   0x0000
  #define R6_BCLKDIV2   0x0004
  #define R6_BCLKDIV4   0x0008
  #define R6_BCLKDIV8   0x000C
  #define R6_BCLKDIV16  0x0010
  #define R6_BCLKDIV32  0x0014
  #define R6_MS         0x0001
  write_codec_register(6, R6_CLKSEL |R6_MCLKDIV2);

  #define R7_SR48K     0x0000
  #define R7_SR32K     0x0002
  #define R7_SR24K     0x0004
  #define R7_SR16K     0x0006
  #define R7_SR12K     0x0008
  #define R7_SR8K      0x000A
  #define R7_SLOWCLKEN 0x0001
  write_codec_register(7, R7_SR48K);

  // put the pll output onto GPIO1on pin 15, to confirm settings via scope.
  #define R8_OPCLKDIV1           0x0000
  #define R8_OPCLKDIV2           0x0010
  #define R8_OPCLKDIV3           0x0020
  #define R8_OPCLKDIV4           0x0030
  #define R8_GPIO1POL_INVERT     0x0008
  #define R8_GPIO1SEL_PLL_CLK_OP 0x0004
  #define R8_GPIO1SEL_PLL_LOCK   0x0005
  //write_codec_register(8, R8_OPCLKDIV4 |R8_GPIO1SEL_PLL_CLK_OP);

  #define R10_SOFRMUTE  0x0040
  #define R10_DACOSR128 0x0008
  #define R10_AMUTE     0x0004
  #define R10_DACPOLR   0x0002
  #define R10_DACPOLL   0x0001
  write_codec_register(10, R10_DACOSR128 |R10_DACPOLL);

  #define R11_DACVU 0x0100
  write_codec_register(11, R11_DACVU | 0x00FF);
  
  #define R12_DACVU 0x0100
  write_codec_register(12, R12_DACVU | 0x00FF);

  // in
  #define R14_HPFEN     0x0100
  #define R14_HPFAPP    0x0080
  #define R14_ADCOSR128 0x0080
  //write_codec_register(14, 0x0100 |R14_ADCOSR128 | R14_HPFAPP |0x0070);

  // in
  #define R15_ADCVU 0x0100
  // use por value
  //write_codec_register(15, R15_ADCVU | 0x00FF);

  // in - use automatic level control
  #define R32_ALCSEL 0x0180
  //write_codec_register(32, R32_ALCSEL);
  
  #define R36_PLL_PRESCALE 0x0010
  // PLLN = 8
  //write_codec_register(36, R36_PLL_PRESCALE |8);
  write_codec_register(36, 8);
  
  // PLLK = 0x3126e9 from Wolfson datasheet
  write_codec_register(37, (0x3126E9 >> 18));
  write_codec_register(38, (0x3126E9 >> 9) & 0x1FF );
  write_codec_register(39, 0x3126E9 & 0x1FF);

  #define R43_INVROUT2 0x0010
  //write_codec_register(43, R43_INVROUT2);

  // in
  #define R44_MBVSEL     0x0800
  #define R44_L2INPPGA   0x0004
  #define R44_LIN2INPPGA 0x0002
  #define R44_LIP2INPPGA 0x0001
  write_codec_register(44, R44_LIN2INPPGA |R44_LIP2INPPGA); 
  
  // in
  #define R45_INPPGAUPDATE 0x0100
  #define R45_INPPGAZCL    0x0080
  #define R45_IMPPGAMUTEL  0x0040
  write_codec_register(45, R45_INPPGAUPDATE |0x3F); 
  
  // in
  #define R47_PGABOOST 0x0800
  write_codec_register(47, R47_PGABOOST);

  #define R49_DACL2RMIX   0x0040
  #define R49_DACR2LMIX   0x0020
  #define R49_OUT4BOOST   0x0010
  #define R49_OUT3BOOST   0x0008
  #define R49_SPKBOOST    0x0004
  #define R49_TSDEN       0x0002
  #define R49_VROI_30KOHM 0x0001
  write_codec_register(49, R49_TSDEN); 

  #define R50_DACL2LMIX 0x0001
  write_codec_register(50, R50_DACL2LMIX);
  
  #define R51_DACR2RMIX 0x0001
  write_codec_register(51, R51_DACR2RMIX);

  #define R52_SPKVU     0x0100
  #define R52_LOUT2MUTE 0x0040  
  write_codec_register(52, R52_SPKVU |0x3F);
  
  #define R53_SPKVU     0x0100
  #define R53_ROUT2MUTE 0x0040  
  write_codec_register(53, R53_SPKVU |0x3F);

  // drive out3/4 mute to make them the DC level of the headphone.
  //write_codec_register(56, 0x40);
  //write_codec_register(57, 0X40);

  while (1)
  {
    si32PseudoThread_yield();
    // Resume occurs when a volume control or mute request arrives.
    uint16 mute = NO;
    int16 volume = 0;
    // left output
    mute = myControlPlaneMute[0] ? 0x0040 : 0x0000;
    volume = myControlPlaneVolume[0];
    volume = volume + 58;
    printf("m0: %c v0: %d\n", mute ? 'y' : 'n', volume);
    if (volume < 0) volume = 0;
    if (volume > 63) volume = 63;
    printf("v0: %d\n", volume);
    write_codec_register(52, mute |volume);
    // right output
    mute = myControlPlaneMute[1] ? 0x0040 : 0x0000;
    volume = myControlPlaneVolume[1];
    volume = volume + 58;
    printf("m1: %c v1: %d\n", mute ? 'y' : 'n', volume);
    if (volume < 0) volume = 0;
    if (volume > 63) volume = 63;
    printf("v1: %d\n", volume);
    write_codec_register(53, R53_SPKVU |mute |volume);
    // mic input
    // TBD
  }
  
  si32LogEpilogue();
  si32PseudoThread_end();
}

//------------------------------------------------------------------------------
void myControlPlane_initialize()
{
  // initialize a thread to run this test.
  si32PseudoThreadObject_initialize(myControlPlane);
  
  // start the thread.
  si32PseudoThreadObject_run(myControlPlane,
                             myControlPlaneFn);
}

//---eof------------------------------------------------------------------------
