/*  Retirado do link https://esp32.com/viewtopic.php?f=18&t=14185
    Project ESP32 Logic Analyzer     Testes com I2S Bit Clock
    ESP32 Dev Kit 38 pins - Arduino IDE 1.8.12 - ESP32 Arduino V1.0.4
    https://github.com/Gustavomurta/ESP32-Logic-analyzer
    Gustavo Murta and Rui Vianna - 14/jun/2020
    
    I2S0 MCLK pin = GPIO0 (derived from APLL Clock)
    
    using PLL_D2 clock - master clock up to 80 MHz
    using APLL clock - master clock up to 70 MHz
    I2S0.clkm_conf.clka_en >>> Set this bit to enable clk_apll or reset to enable PLL Clock!

    References:
    https://github.com/espressif/arduino-esp32/blob/master/tools/sdk/include/driver/driver/i2s.h
    https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/esp32-hal-cpu.h

    https://github.com/espressif/arduino-esp32/blob/master/tools/sdk/include/soc/soc/rtc.h
    https://github.com/espressif/arduino-esp32/blob/master/tools/sdk/include/soc/soc/rtc_cntl_reg.h
    https://github.com/espressif/arduino-esp32/blob/master/tools/sdk/include/soc/soc/dport_reg.h
    https://github.com/espressif/arduino-esp32/blob/master/tools/sdk/include/soc/soc/rtc_cntl_struct.h

    https://github.com/espressif/arduino-esp32/blob/master/tools/sdk/include/soc/soc/syscon_reg.h
    https://github.com/espressif/arduino-esp32/blob/master/tools/sdk/include/soc/soc/syscon_struct.h

    https://github.com/espressif/arduino-esp32/blob/master/tools/sdk/include/soc/soc/periph_defs.h
    https://github.com/espressif/arduino-esp32/blob/master/tools/sdk/include/soc/soc/i2s_struct.h
    https://github.com/espressif/arduino-esp32/blob/master/tools/sdk/include/soc/soc/i2s_reg.h

    https://github.com/espressif/arduino-esp32/blob/master/tools/sdk/include/soc/soc/io_mux_reg.h
*/

#include "soc/rtc_cntl_reg.h"
#include "soc/rtc.h"

#include "soc/syscon_reg.h"
#include "soc/rtc_cntl_struct.h"

//#include "esp32-hal-cpu.h"
#include "driver/i2s.h"

float APL_CLK;
int sdm0, sdm1, sdm2, o_div;
float fclk;
float  div_num, div_b, div_a;

void setup()
{
  Serial.begin(115200);                         // IDE Console 115200 bps
  socDetails ();                                // system on a chip details
  apllClock ();                                 // Read RTC Control Clock configuration
  enableAudioPLLClock ();                       // power up audio PLL clock
  configAPLclock ();                            // enable APLL clock 80 Mhz
  mclkI2S0config ();                            // configure I2S0 MCLK clock
  bckI2S0config ();                             // configure I2S0 BCK  clock
  mclkClock ();                                 // configure GPIO0 to CLock OUT 1 - I2S MCLK
}

void socDetails ()
{
  Serial.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
  Serial.println();
  Serial.print("ESP32 - Chip Revision: ");
  Serial.println(ESP.getChipRevision());                       // print ESP32 chip revision
  Serial.print("ESP32 - CPU frequency: ");
  Serial.println(ESP.getCpuFreqMHz());                         // print ESP32 cpu frequency
  Serial.print("ESP32 - RTC Crystal Clock frequency = ");
  Serial.println(rtc_clk_xtal_freq_get());                     // print ESP32 RTC Crystal Clock frequency      soc/rtc.h    40 MHz
  Serial.print("ESP32 - RTC APB frequency: ");
  Serial.println(rtc_clk_apb_freq_get());                      // print ESP32 RTC APB frequency                soc/rtc.h    80 MHz
  Serial.println();
}

void apllClock ()
{
  Serial.print("RTC Control Clock configuration (0x");
  Serial.print(RTC_CNTL_CLK_CONF_REG, HEX);                        // Read RTC Control Clock configuration (0x3FF48070) = 0x29580010
  Serial.print(") = 0x"); Serial.println (REG_READ(RTC_CNTL_CLK_CONF_REG), HEX);

  Serial.print("APLL Tick configuration (0x");
  Serial.print(SYSCON_APLL_TICK_CONF_REG, HEX);                    // Read APLL Tick configuration (0x3FF6603C) = 0x63
  Serial.print(") = 0x"); Serial.println (REG_READ(SYSCON_APLL_TICK_CONF_REG), HEX);

  Serial.print("RTC Optons0 control configuration (0x");
  Serial.print(RTC_CNTL_OPTIONS0_REG, HEX);                        // Read RTC Optons 0 control configuration (0x3FF48000) = 0x1C124000
  Serial.print(") = 0x"); Serial.println (REG_READ(RTC_CNTL_OPTIONS0_REG), HEX);
  Serial.println();
}

void rtcCntlAnaConfReg ()
{
  Serial.print("RTC power up/down configuration (0x");
  Serial.print(RTC_CNTL_ANA_CONF_REG, HEX);                        // Read RTC power up/down configuration (0x3FF48030)= 0x800000
  Serial.print(") = 0x"); Serial.println (REG_READ(RTC_CNTL_ANA_CONF_REG), HEX);
}

void enableAudioPLLClock ()
{
  rtcCntlAnaConfReg ();                                             // Read RTC power up/down configuration
  Serial.println("RTC PLLA power down");                            // Disable audio PLL clock
  RTCCNTL.ana_conf.plla_force_pd = 0;                               // RTC APLL power down
  rtcCntlAnaConfReg ();                                             // Read RTC power up/down configuration
  Serial.println("RTC PLLA power up");                              // Enable audio PLL clock
  RTCCNTL.ana_conf.plla_force_pu = 1;                               // RTC APLL power up
  rtcCntlAnaConfReg ();                                             // Read RTC power up/down configuration
}

void configAPLclock ()
{

  // @param sdm0  frequency adjustment parameter, 0..255
  // @param sdm1  frequency adjustment parameter, 0..255
  // @param sdm2  frequency adjustment parameter, 0..63 max = 10
  // @param o_div  frequency divider, 0..31
  // The dividend in this expression should be in the range of 240 and 560 MHz - tested 

  // apll_freq = xtal_freq * (4 + sdm0/65536 + sdm1/256 + sdm2)/((o_div + 2) * 2)
  // rtc_clk_apll_enable(bool enable, uint32_t sdm0, uint32_t sdm1,uint32_t sdm2, uint32_t o_div);

  // apll_freq = 40MHz * (4+0+0+6)/(0+2)*2
  // apll_freq = 40 * 10 / 4 = 100 MHz          // enable APLL clock 100 Mhz

  //sdm0 = 0; sdm1 = 0; sdm2 = 10; o_div = 0;   // Para APLL clock = 70 Mhz
  //sdm0 = 0; sdm1 = 0; sdm2 = 6; o_div = 0;    // Para APLL clock = 50 Mhz
  //sdm0 = 0; sdm1 = 0; sdm2 = 2; o_div = 0;    // Para APLL clock = 30 Mhz
  //sdm0 = 0; sdm1 = 0; sdm2 = 2; o_div = 3;    // Para APLL clock = 20 Mhz
  //sdm0 = 0; sdm1 = 0; sdm2 = 2; o_div = 10;      // Para APLL clock = 5 Mhz
  sdm0 = 0; sdm1 = 0; sdm2 = 2; o_div = 30;    // Para APLL clock = 1.875 Mhz

  rtc_clk_apll_enable(1, sdm0, sdm1, sdm2, o_div);            // enable APLL clock 

  //APL_CLK = 40 * (4 + (sdm0 / 65536) + (sdm1 / 256) + sdm2) / (2 * (o_div + 2));  // Mostra a frequencia inteira
  APL_CLK = 40.0 * (4 + (sdm0 / 65536) + (sdm1 / 256) + sdm2) / (2.0 * (o_div + 2));  // Mostra a frequencia exata (com decimal)
  // Tambem tem configuracao na linha 180 ( div_num = 2; div_b = 0; div_a = 1; )
  // Divisor do APLCLK 
  
  Serial.println("Configuracao:");
  Serial.print ("sdm0 = "); Serial.println (sdm0); 
  Serial.print ("sdm1 = "); Serial.println (sdm1); 
  Serial.print ("sdm2 = "); Serial.println (sdm2); 
  Serial.print ("o_div = "); Serial.println (o_div);
  Serial.println("");
  Serial.print ("APL_CLK =  "); Serial.print (APL_CLK, 3); Serial.println (" MHz");
}

void mclkClock ()
{
  // Enable I2S0 MCLK clock output at GPIO0 pin - ESP32 Tech Reference pag 70 and 71
  Serial.println();
  Serial.print("IO_MUX_PIN_CTRL ("); Serial.print(PIN_CTRL, HEX);                           // Clock output configuration register (0x3FF49000)
  Serial.print(") = 0x"); Serial.println (REG_READ(PIN_CTRL), HEX);                         // print register value = 0x3FF

  // PIN_FUNC_SELECT(PIN_CTRL, CLK_OUT1);                                                   // wrong function?
  REG_WRITE(PIN_CTRL, 0xFF0);                                                               // it works - Clock output 1 to I2S0

  Serial.print("IO_MUX_PIN_CTRL ("); Serial.print(PIN_CTRL, HEX);                           // Clock output configuration register (0x3FF49000)
  Serial.print(") = 0x"); Serial.print (REG_READ(PIN_CTRL), HEX);                           // print register value = 0xFF0
  Serial.println(" after configuration");

  Serial.println();
  Serial.print("PERIPHS_IO_MUX_GPIO0_U ("); Serial.print(PERIPHS_IO_MUX_GPIO0_U, HEX);      // Configuration register for pad GPIO 0 (0x3FF49044)
  Serial.print(") = 0x"); Serial.println (REG_READ(PERIPHS_IO_MUX_GPIO0_U), HEX);           // print register value = 0xB00

  PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);                             // Configuration register for pad GPIO0 => set function 2
  PIN_INPUT_DISABLE(PERIPHS_IO_MUX_GPIO0_U);                                                // disable input at GPIO 0

  Serial.print("PERIPHS_IO_MUX_GPIO0_U ("); Serial.print(PERIPHS_IO_MUX_GPIO0_U, HEX);      // Configuration register for pad GPIO 0 (0x3FF49044)
  Serial.print(") = 0x"); Serial.print (REG_READ(PERIPHS_IO_MUX_GPIO0_U), HEX);             // print register value = 0x1B00
  Serial.println(" after configuration");
}

void mclkI2S0config ()
{
  periph_module_enable(PERIPH_I2S0_MODULE);                                                 // enable peripheral I2S0 module (essential)

  Serial.println();
  Serial.print("I2S0 Bit clock configuration (0x");                                         // print register name and address (0x3FF4F0AC)
  Serial.print(I2S_CLKM_CONF_REG(0), HEX);                                                  // Register I2S0 Bit clock configuration
  Serial.print(") = 0x"); Serial.println (REG_READ(I2S_CLKM_CONF_REG(0)), HEX);             // print register value = 0x4 (default)

  div_num = 2; div_b = 0; div_a = 1;      // Divide o APL_CLK po 2 
  //div_num = 20; div_b = 0; div_a = 1;   // Divide o APL_CLK por 20

  I2S0.clkm_conf.clkm_div_num = div_num;                                                    // I2S clock divider’s integral value >= 2
  I2S0.clkm_conf.clkm_div_b = div_b;                                                        // Fractional clock divider’s numerator value
  I2S0.clkm_conf.clkm_div_a = div_a;                                                        // Fractional clock divider’s denominator value
  I2S0.clkm_conf.clk_en = 1;                                                                // I2S clock enable
  I2S0.clkm_conf.clka_en = 1;                                                               // Set this bit to enable clk_apll

  // Configure Bit Clock configuration - ESP32 Tech Reference page 308 and 337
  // fclk = fapll / (N + b/a)
  // fclk = 100 MHz / (2 +(0/1) = 50 MHz  (using APLL clock)

  fclk = APL_CLK / ( div_num + ( div_b / div_a));
  Serial.print("I2S0 master clock = "); Serial.print(fclk, 3); Serial.println (" MHz");
  Serial.println();

  Serial.print("I2S0 Bit clock configuration (0x");                                         // print register name and address (0x3FF4F0AC)
  Serial.print(I2S_CLKM_CONF_REG(0), HEX);                                                  // Register I2S0 Bit clock configuration
  Serial.print(") = 0x"); Serial.print (REG_READ(I2S_CLKM_CONF_REG(0)), HEX);               // print register value = 0x104008
  Serial.println(" after configuration");
}

void bckI2S0config ()
{
  Serial.println();                                                                         // ESP32 Tech Reference page 308
  Serial.print("I2S0 sample rate configuration (0x");                                       // print register name and address (0x3FF4F0B0)
  Serial.print(I2S_SAMPLE_RATE_CONF_REG(0), HEX);                                           // Register I2S0 sample rate configuration
  Serial.print(") = 0x"); Serial.println (REG_READ(I2S_SAMPLE_RATE_CONF_REG(0)), HEX);      // print register value = 0x410186 (default)

  I2S0.sample_rate_conf.tx_bck_div_num = 4;                                                 // TX BCK clock = MCLK / num
  I2S0.sample_rate_conf.rx_bck_div_num = 4;                                                 // RX BCK clock = MCLK / num

  Serial.print("I2S0 sample rate configuration (0x");                                       // print register name and address (0x3FF4F0B0)
  Serial.print(I2S_SAMPLE_RATE_CONF_REG(0), HEX);                                           // Register I2S0 sample rate configuration
  Serial.print(") = 0x"); Serial.print (REG_READ(I2S_SAMPLE_RATE_CONF_REG(0)), HEX);        // print register value = 0x410104
  Serial.println(" after configuration");
}

void loop()
{
  // put your main code here, to run repeatedly:
}