/*
 * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <inttypes.h>
#include "es7210.h"
#include "es7210_reg.h"
#include "esp_log.h"
#include "esp_check.h"
#include "Wire.h"

const static char *TAG = "ES7210";

#define IS_ES7210_I2S_FMT(val) (((val)==ES7210_I2S_FMT_I2S) || ((val)==ES7210_I2S_FMT_LJ) || \
        ((val)==ES7210_I2S_FMT_DSP_A) || ((val)==ES7210_I2S_FMT_DSP_B))

#define IS_ES7210_I2S_BITS(val) (((val)==ES7210_I2S_BITS_24B) || ((val)==ES7210_I2S_BITS_20B) || \
        ((val)==ES7210_I2S_BITS_18B) || ((val)==ES7210_I2S_BITS_16B) || ((val)==ES7210_I2S_BITS_32B))

#define IS_ES7210_MIC_GAIN(val) (((val) >= ES7210_MIC_GAIN_0DB) && ((val) <= ES7210_MIC_GAIN_37_5DB))

#define IS_ES7210_MIC_BIAS(val) (((val)==ES7210_MIC_BIAS_2V18) || ((val)==ES7210_MIC_BIAS_2V26) || \
        ((val)==ES7210_MIC_BIAS_2V36) || ((val)==ES7210_MIC_BIAS_2V45) || ((val)==ES7210_MIC_BIAS_2V55) || \
        ((val)==ES7210_MIC_BIAS_2V66) || ((val)==ES7210_MIC_BIAS_2V78) || ((val)==ES7210_MIC_BIAS_2V87))

#define ES7210_WRITE_REG(reg_addr, reg_value) do { \
    ESP_RETURN_ON_ERROR(es7210_write_reg(handle, (reg_addr), (reg_value)), \
                        TAG, "i2c communication error while writing "#reg_addr); \
} while(0)

struct es7210_dev_t {
    i2c_port_t  i2c_port;
    uint8_t     i2c_addr;
};

/**
 * @brief Clock coefficient structure
 *
 */
typedef struct {
    uint32_t mclk;            /*!< mclk frequency */
    uint32_t lrck;            /*!< lrck */
    uint8_t  ss_ds;           /*!< not used */
    uint8_t  adc_div;         /*!< adcclk divider */
    uint8_t  dll;             /*!< dll_bypass */
    uint8_t  doubler;         /*!< doubler enable */
    uint8_t  osr;             /*!< adc osr */
    uint8_t  mclk_src;        /*!< select mclk  source */
    uint32_t lrck_h;          /*!< The high 4 bits of lrck */
    uint32_t lrck_l;          /*!< The low 8 bits of lrck */
} coeff_div_t;

/**
 * @brief ES7210 clock coefficient lookup table
 *
 */
static const coeff_div_t es7210_coeff_div[] = {
//    mclk      lrck    ss_ds adc_div  dll  doubler osr  mclk_src  lrckh   lrckl
    /* 8k */
    {12288000,  8000,  0x00,  0x03,  0x01,  0x00,  0x20,  0x00,    0x06,  0x00},
    {16384000,  8000,  0x00,  0x04,  0x01,  0x00,  0x20,  0x00,    0x08,  0x00},
    {19200000,  8000,  0x00,  0x1e,  0x00,  0x01,  0x28,  0x00,    0x09,  0x60},
    {4096000,   8000,  0x00,  0x01,  0x01,  0x00,  0x20,  0x00,    0x02,  0x00},

    /* 11.025k */
    {11289600,  11025,  0x00,  0x02,  0x01,  0x00,  0x20,  0x00,    0x01,  0x00},

    /* 12k */
    {12288000,  12000,  0x00,  0x02,  0x01,  0x00,  0x20,  0x00,    0x04,  0x00},
    {19200000,  12000,  0x00,  0x14,  0x00,  0x01,  0x28,  0x00,    0x06,  0x40},

    /* 16k */
    {4096000,   16000,  0x00,  0x01,  0x01,  0x01,  0x20,  0x00,    0x01,  0x00},
    {19200000,  16000,  0x00,  0x0a,  0x00,  0x00,  0x1e,  0x00,    0x04,  0x80},
    {16384000,  16000,  0x00,  0x02,  0x01,  0x00,  0x20,  0x00,    0x04,  0x00},
    {12288000,  16000,  0x00,  0x03,  0x01,  0x01,  0x20,  0x00,    0x03,  0x00},

    /* 22.05k */
    {11289600,  22050,  0x00,  0x01,  0x01,  0x00,  0x20,  0x00,    0x02,  0x00},

    /* 24k */
    {12288000,  24000,  0x00,  0x01,  0x01,  0x00,  0x20,  0x00,    0x02,  0x00},
    {19200000,  24000,  0x00,  0x0a,  0x00,  0x01,  0x28,  0x00,    0x03,  0x20},

    /* 32k */
    {12288000,  32000,  0x00,  0x03,  0x00,  0x00,  0x20,  0x00,    0x01,  0x80},
    {16384000,  32000,  0x00,  0x01,  0x01,  0x00,  0x20,  0x00,    0x02,  0x00},
    {19200000,  32000,  0x00,  0x05,  0x00,  0x00,  0x1e,  0x00,    0x02,  0x58},

    /* 44.1k */
    {11289600,  44100,  0x00,  0x01,  0x01,  0x01,  0x20,  0x00,    0x01,  0x00},

    /* 48k */
    {12288000,  48000,  0x00,  0x01,  0x01,  0x01,  0x20,  0x00,    0x01,  0x00},
    {19200000,  48000,  0x00,  0x05,  0x00,  0x01,  0x28,  0x00,    0x01,  0x90},

    /* 64k */
    {16384000,  64000,  0x01,  0x01,  0x01,  0x00,  0x20,  0x00,    0x01,  0x00},
    {19200000,  64000,  0x00,  0x05,  0x00,  0x01,  0x1e,  0x00,    0x01,  0x2c},

    /* 88.2k */
    {11289600,  88200,  0x01,  0x01,  0x01,  0x01,  0x20,  0x00,    0x00,  0x80},

    /* 96k */
    {12288000,  96000,  0x01,  0x01,  0x01,  0x01,  0x20,  0x00,    0x00,  0x80},
    {19200000,  96000,  0x01,  0x05,  0x00,  0x01,  0x28,  0x00,    0x00,  0xc8},
};

/**
 * @brief Get coefficient from coefficient table
 *
 * @param mclk Desired MCLK value
 * @param lrck Desired LRCK vaule
 * @return Coefficient struct, NULL if desired value cannot be achieved
 */
static const coeff_div_t *es7210_get_coeff(uint32_t mclk, uint32_t lrck)
{
    for (int i = 0; i < sizeof(es7210_coeff_div) / sizeof(coeff_div_t); i++) {
        if (es7210_coeff_div[i].lrck == lrck && es7210_coeff_div[i].mclk == mclk) {
            return &es7210_coeff_div[i];
        }
    }
    return NULL;
}


static bool i2c_reg8_write(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t len) {
  uint8_t error;
  Wire.beginTransmission(dev_addr);
  Wire.write(reg_addr);
  for (int i = 0; i < len; i++) {
    Wire.write(*data++);
  }
  error = Wire.endTransmission(true);
  if (error) {
    printf("endTransmission: %u\n", error);
    return false;
  }
  return true;
}




static esp_err_t es7210_write_reg(es7210_dev_handle_t handle, uint8_t reg_addr, uint8_t reg_val)
{
    ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid device handle");
    if (i2c_reg8_write(handle->i2c_addr, reg_addr, &reg_val, 1))
      return ESP_OK;
    else
      return ESP_FAIL;



//     ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid device handle");
//     esp_err_t ret = ESP_OK;

//     i2c_cmd_handle_t cmd = i2c_cmd_link_create();
//     ESP_GOTO_ON_FALSE(cmd, ESP_ERR_NO_MEM, err, TAG, "memory allocation for i2c cmd handle failed");

//     ESP_GOTO_ON_ERROR(i2c_master_start(cmd), err, TAG, "error while appending i2c command");
//     ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd, handle->i2c_addr << 1 | I2C_MASTER_WRITE, true),
//                       err, TAG, "error while appending i2c command");
//     ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd, reg_addr, true), err,
//                       TAG, "error while appending i2c command");
//     ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd, reg_val, true), err,
//                       TAG, "error while appending i2c command");
//     ESP_GOTO_ON_ERROR(i2c_master_stop(cmd), err, TAG, "error while appending i2c command");

//     ESP_GOTO_ON_ERROR(i2c_master_cmd_begin(handle->i2c_port, cmd, pdMS_TO_TICKS(1000)),
//                       err, TAG, "error while writing register");
// err:
//     if (cmd) {
//         i2c_cmd_link_delete(cmd);
//     }
//     return ret;
}

static esp_err_t es7210_set_i2s_format(es7210_dev_handle_t handle, es7210_i2s_fmt_t i2s_format,
                                       es7210_i2s_bits_t bit_width, bool tdm_enable)
{
    ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid device handle pointer");
    ESP_RETURN_ON_FALSE(IS_ES7210_I2S_FMT(i2s_format), ESP_ERR_INVALID_ARG, TAG, "invalid i2s format argument");
    ESP_RETURN_ON_FALSE(IS_ES7210_I2S_BITS(bit_width), ESP_ERR_INVALID_ARG, TAG, "invalid i2s bit width argument");

    uint8_t reg_val = 0;

    switch (bit_width) {
    case ES7210_I2S_BITS_16B:
        reg_val = 0x60;
        break;
    case ES7210_I2S_BITS_18B:
        reg_val = 0x40;
        break;
    case ES7210_I2S_BITS_20B:
        reg_val = 0x20;
        break;
    case ES7210_I2S_BITS_24B:
        reg_val = 0x00;
        break;
    case ES7210_I2S_BITS_32B:
        reg_val = 0x80;
        break;
    default:
        abort();
    }
    ES7210_WRITE_REG(ES7210_SDP_INTERFACE1_REG11, i2s_format | reg_val);

    const char *mode_str = NULL;
    switch (i2s_format) {
    case ES7210_I2S_FMT_I2S:
        reg_val = 0x02;
        mode_str = "standard i2s";
        break;
    case ES7210_I2S_FMT_LJ:
        reg_val = 0x02;
        mode_str = "left justify";
        break;
    case ES7210_I2S_FMT_DSP_A:
        reg_val = 0x01;
        mode_str = "DSP-A";
        break;
    case ES7210_I2S_FMT_DSP_B:
        reg_val = 0x01;
        mode_str = "DSP-B";
        break;
    default:
        abort();
    }

    if (tdm_enable) { // enable 1xFS TDM
        ES7210_WRITE_REG(ES7210_SDP_INTERFACE2_REG12, reg_val);
    } else {
        ES7210_WRITE_REG(ES7210_SDP_INTERFACE2_REG12, 0x00);
    }

    ESP_LOGI(TAG, "format: %s, bit width: %d, tdm mode %s", mode_str, bit_width, tdm_enable ? "enabled" : "disabled");
    return ESP_OK;
}

static esp_err_t es7210_set_i2s_sample_rate(es7210_dev_handle_t handle, uint32_t sample_rate_hz, uint32_t mclk_ratio)
{
    ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid device handle pointer");

    uint32_t mclk_freq_hz = sample_rate_hz * mclk_ratio;
    const coeff_div_t *coeff_div = es7210_get_coeff(mclk_freq_hz, sample_rate_hz);
    ESP_RETURN_ON_FALSE(coeff_div, ESP_ERR_NOT_SUPPORTED, TAG,
                        "unable to set %"PRIu32"Hz sample rate with %"PRIu32"Hz MCLK", sample_rate_hz, mclk_freq_hz);
    /* Set osr */
    ES7210_WRITE_REG(ES7210_OSR_REG07, coeff_div->osr);
    /* Set adc_div & doubler & dll */
    ES7210_WRITE_REG(ES7210_MAINCLK_REG02, (coeff_div->adc_div) | (coeff_div->doubler << 6) | (coeff_div->dll << 7));
    /* Set lrck */
    ES7210_WRITE_REG(ES7210_LRCK_DIVH_REG04, coeff_div->lrck_h);
    ES7210_WRITE_REG(ES7210_LRCK_DIVL_REG05, coeff_div->lrck_l);

    ESP_LOGI(TAG, "sample rate: %"PRIu32"Hz, mclk frequency: %"PRIu32"Hz", sample_rate_hz, mclk_freq_hz);
    return ESP_OK;
}

static esp_err_t es7210_set_mic_gain(es7210_dev_handle_t handle, es7210_mic_gain_t mic_gain)
{
    ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid device handle pointer");
    ESP_RETURN_ON_FALSE(IS_ES7210_MIC_GAIN(mic_gain), ESP_ERR_INVALID_ARG, TAG, "invalid mic gain value");

    ES7210_WRITE_REG(ES7210_MIC1_GAIN_REG43, mic_gain | 0x10);
    ES7210_WRITE_REG(ES7210_MIC2_GAIN_REG44, mic_gain | 0x10);
    ES7210_WRITE_REG(ES7210_MIC3_GAIN_REG45, mic_gain | 0x10);
    ES7210_WRITE_REG(ES7210_MIC4_GAIN_REG46, mic_gain | 0x10);

    return ESP_OK;
}

static esp_err_t es7210_set_mic_bias(es7210_dev_handle_t handle, es7210_mic_bias_t mic_bias)
{
    ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid device handle pointer");
    ESP_RETURN_ON_FALSE(IS_ES7210_MIC_BIAS(mic_bias), ESP_ERR_INVALID_ARG, TAG, "invalid mic bias value");

    ES7210_WRITE_REG(ES7210_MIC12_BIAS_REG41, mic_bias);
    ES7210_WRITE_REG(ES7210_MIC34_BIAS_REG42, mic_bias);

    return ESP_OK;
}

esp_err_t es7210_new_codec(const es7210_i2c_config_t *i2c_conf, es7210_dev_handle_t *handle_out)
{
    ESP_RETURN_ON_FALSE(i2c_conf, ESP_ERR_INVALID_ARG, TAG, "invalid device config pointer");
    ESP_RETURN_ON_FALSE(handle_out, ESP_ERR_INVALID_ARG, TAG, "invalid device handle pointer");

    struct es7210_dev_t *handle = (struct es7210_dev_t *)calloc(1, sizeof(struct es7210_dev_t));
    ESP_RETURN_ON_FALSE(handle, ESP_ERR_NO_MEM, TAG, "memory allocation for device handler failed");

    handle->i2c_port = i2c_conf->i2c_port;
    handle->i2c_addr = i2c_conf->i2c_addr;

    *handle_out = handle;
    return ESP_OK;
}

esp_err_t es7210_del_codec(es7210_dev_handle_t handle)
{
    ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid device handle pointer");

    free(handle);

    return ESP_OK;
}

esp_err_t es7210_config_codec(es7210_dev_handle_t handle, const es7210_codec_config_t *codec_conf)
{
    ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid device handle pointer");
    ESP_RETURN_ON_FALSE(codec_conf, ESP_ERR_INVALID_ARG, TAG, "invalid codec config pointer");

    /* Perform software reset */
    ES7210_WRITE_REG(ES7210_RESET_REG00, 0xFF);
    ES7210_WRITE_REG(ES7210_RESET_REG00, 0x32);
    /* Set the initialization time when device powers up */
    ES7210_WRITE_REG(ES7210_TIME_CONTROL0_REG09, 0x30);
    ES7210_WRITE_REG(ES7210_TIME_CONTROL1_REG0A, 0x30);
    /* Configure HPF for ADC1-4 */
    ES7210_WRITE_REG(ES7210_ADC12_HPF1_REG23, 0x2A);
    ES7210_WRITE_REG(ES7210_ADC12_HPF2_REG22, 0x0A);
    ES7210_WRITE_REG(ES7210_ADC34_HPF1_REG21, 0x2A);
    ES7210_WRITE_REG(ES7210_ADC34_HPF2_REG20, 0x0A);
    /* Set bits per sample to 16, data protocal to I2S, enable 1xFS TDM */
    ESP_RETURN_ON_ERROR(es7210_set_i2s_format(handle, codec_conf->i2s_format, codec_conf->bit_width,
                        codec_conf->flags.tdm_enable), TAG, "error while setting i2s format");
    /* Configure analog power and VMID voltage */
    ES7210_WRITE_REG(ES7210_ANALOG_REG40, 0xC3);
    /* Set MIC14 bias to 2.87V */
    ESP_RETURN_ON_ERROR(es7210_set_mic_bias(handle, codec_conf->mic_bias), TAG, "error while setting mic bias");
    /* Set MIC1-4 gain to 30dB */
    ESP_RETURN_ON_ERROR(es7210_set_mic_gain(handle, codec_conf->mic_gain), TAG, "error while setting mic gain");
    /* Power on MIC1-4 */
    ES7210_WRITE_REG(ES7210_MIC1_POWER_REG47, 0x08);
    ES7210_WRITE_REG(ES7210_MIC2_POWER_REG48, 0x08);
    ES7210_WRITE_REG(ES7210_MIC3_POWER_REG49, 0x08);
    ES7210_WRITE_REG(ES7210_MIC4_POWER_REG4A, 0x08);
    /* Set ADC sample rate to 48kHz */
    ESP_RETURN_ON_ERROR(es7210_set_i2s_sample_rate(handle, codec_conf->sample_rate_hz, codec_conf->mclk_ratio),
                        TAG, "error while setting sample rate");
    /* Power down DLL */
    ES7210_WRITE_REG(ES7210_POWER_DOWN_REG06, 0x04);
    /* Power on MIC1-4 bias & ADC1-4 & PGA1-4 Power */
    ES7210_WRITE_REG(ES7210_MIC12_POWER_REG4B, 0x0F);
    ES7210_WRITE_REG(ES7210_MIC34_POWER_REG4C, 0x0F);
    /* Enable device */
    ES7210_WRITE_REG(ES7210_RESET_REG00, 0x71);
    ES7210_WRITE_REG(ES7210_RESET_REG00, 0x41);

    return ESP_OK;
}

esp_err_t es7210_config_volume(es7210_dev_handle_t handle, int8_t volume_db)
{
    ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "invalid device handle pointer");
    ESP_RETURN_ON_FALSE(volume_db >= -95 && volume_db <= 32, ESP_ERR_INVALID_ARG, TAG, "invalid volume range");

    /*
     * reg_val: 0x00 represents -95.5dB, 0xBF represents 0dB (default after reset),
     * and 0xFF represents +32dB, with a 0.5dB step
     */
    uint8_t reg_val = 191 + volume_db * 2;

    ES7210_WRITE_REG(ES7210_ADC1_DIRECT_DB_REG1B, reg_val);
    ES7210_WRITE_REG(ES7210_ADC2_DIRECT_DB_REG1C, reg_val);
    ES7210_WRITE_REG(ES7210_ADC3_DIRECT_DB_REG1D, reg_val);
    ES7210_WRITE_REG(ES7210_ADC4_DIRECT_DB_REG1E, reg_val);

    return ESP_OK;
}
