#include <string.h>
#include "soc.h"
#include "csi_core.h"
#include <csi_config.h>
#include "drv/gpio.h"
#include "drv/codec.h"
#include "app_codec.h"
#include "app_gpio.h"
#include "board_config.h"
#include "asr_config.h"

#ifdef CONFIG_XIP
#define ATTRIBUTE_DATA __attribute__((section(".ram.code")))
#else
#define ATTRIBUTE_DATA
#endif

unsigned char codec_output_analog_volume = CODEC_OUT_MAX_VOLUME_DB;
unsigned char codec_input_analog_volume = CODEC_IN_MAX_VOLUME_DB;

static csi_codec_t codec;
csi_codec_output_t codec_output_ch;
csi_codec_input_t codec_input_ch;

static csi_dma_ch_t codec_dma_ch_output_handle;
static csi_dma_ch_t codec_dma_ch_input_handle;

#define OUTPUT_BUF_SIZE CODEC_TX_FRAME_BYTE_SIZE * 4
static unsigned int codec_output_buf[OUTPUT_BUF_SIZE / 4];

#define INPUT_BUF_SIZE CODEC_RX_FRAME_BYTE_SIZE * 4
static unsigned int codec_input_buf[INPUT_BUF_SIZE / 4];

static ringbuffer_t codec_output_ring_buffer;
static ringbuffer_t codec_input_ring_buffer;

static volatile uint32_t codec_init_timer_tick; // 用于延时初始化CODEC的记时器
static volatile uint8_t f_codec_init_complete;  // CODEC是否初始化完成 0:未完成  1:完成
static bool f_codec_dac_power_on;

volatile unsigned char cb_codec_output_transfer_flag = 0;
volatile unsigned char cb_codec_input_transfer_flag = 0;
volatile unsigned int deb_codec_tx_err_count = 0;
volatile unsigned int deb_codec_rx_err_count = 0;
volatile unsigned int deb_codec_tx_count = 0;
volatile unsigned int deb_codec_rx_count = 0;
static void codec_output_event_cb_fun(csi_codec_output_t *output, csi_codec_event_t event, void *arg)
{
    gpio3_toggle();
    if (event == CODEC_EVENT_PERIOD_WRITE_COMPLETE)
    {
        cb_codec_output_transfer_flag = 1;
        deb_codec_tx_count++;
    }
    else
    { //I2S_EVENT_FRAME_ERROR || I2S_EVENT_TX_BUFFER_EMPYT
        deb_codec_tx_err_count++;
    }
}

static void codec_input_event_cb_fun(csi_codec_input_t *i2s, csi_codec_event_t event, void *arg)
{
    //	 gpio2_toggle();
    if (event == CODEC_EVENT_PERIOD_READ_COMPLETE)
    {
        cb_codec_input_transfer_flag = 1;
        deb_codec_rx_count++;
    }
    else //I2S_EVENT_FRAME_ERROR
    {
        deb_codec_rx_err_count++;
    }
}

int8_t app_codec_init(void)
{
    csi_error_t ret;
    csi_codec_output_config_t output_config;
    csi_codec_input_config_t input_config;
    gpio0_toggle();

    ret = csi_codec_init(&codec, 0); //1 16K，0 48K

    if (ret != CSI_OK)
    {
#if APP_LOG_ENABLE
        printf("csi_codec_init error\n");
#endif
        return -1;
    }

    /* output ch config */
    codec_output_ch.ring_buf = &codec_output_ring_buffer;
    csi_codec_output_open(&codec, &codec_output_ch, 0);
    csi_codec_output_attach_callback(&codec_output_ch, codec_output_event_cb_fun, NULL);
    output_config.bit_width = 16;
    output_config.sample_rate = 48000;
    output_config.buffer = (unsigned char *)codec_output_buf;
    output_config.buffer_size = OUTPUT_BUF_SIZE;
    output_config.period = CODEC_TX_FRAME_BYTE_SIZE;
    // output_config.mode = CODEC_OUTPUT_SINGLE_ENDED;//CODEC_OUTPUT_SINGLE_ENDED CODEC_OUTPUT_DIFFERENCE
    csi_codec_output_config(&codec_output_ch, &output_config);
    csi_codec_output_buffer_reset(&codec_output_ch);
    csi_codec_output_link_dma(&codec_output_ch, &codec_dma_ch_output_handle);

    /* input ch config */
    codec_input_ch.ring_buf = &codec_input_ring_buffer;
    csi_codec_input_open(&codec, &codec_input_ch, 0);
    csi_codec_input_attach_callback(&codec_input_ch, codec_input_event_cb_fun, NULL);

    input_config.bit_width = 16;
    input_config.sample_rate = 48000;
    input_config.buffer = (unsigned char *)codec_input_buf;
    input_config.buffer_size = INPUT_BUF_SIZE;
    input_config.period = CODEC_RX_FRAME_BYTE_SIZE;
    // input_config.mode = CODEC_INPUT_DIFFERENCE;
    csi_codec_input_config(&codec_input_ch, &input_config);

    csi_codec_input_link_dma(&codec_input_ch, &codec_dma_ch_input_handle);

#if APP_LOG_ENABLE
    printf("start code input and output\n");
#endif
    csi_codec_output_start(&codec_output_ch);
    csi_codec_input_start(&codec_input_ch);
#if 1 //hp out控制pa，不需要延迟初始化去pop音
    csi_codec_output_analog_gain(&codec_output_ch, codec_output_analog_volume);
    csi_codec_input_analog_gain(&codec_input_ch, codec_input_analog_volume);

    f_codec_dac_power_on = true;
    app_codec_dac_power_control(false); //上电默认关闭DAC
    gpio_pa_set(1);//使能hp out外部pa
#else
    codec_init_timer_tick = 0x00; // 用于延时初始化CODEC的记时器
    f_codec_init_complete = 0x00; // CODEC是否初始化完成 0:未完成  1:完成
#endif
    gpio0_toggle();
    return 0;
}
void app_codec_dac_power_control(bool f_power_on)
{
#if 0
    if (f_codec_dac_power_on != f_power_on)
    {
        f_codec_dac_power_on = f_power_on;
        if (f_power_on == false)
        { //先关PA
            gpio_pa_set(0);
        }
        csi_codec_dac_power_control(f_power_on);
        if (f_power_on)
        { //后开PA
            gpio_pa_set(1);
        }
    }
#endif
}
/*
 *  延时初始化CODEC寄存器，解决HP单端上电，电容充电导致的“砰”声
 *  需要定时10mS调用一次
 *  输入参数：
 * 		无
 */
void app_codec_delay_init(void)
{
    if ((codec_init_timer_tick < 10000) && (f_codec_init_complete == 0))
    {
        gpio0_set(1);
        codec_init_timer_tick++;
        if (csi_codec_delay_init(codec_init_timer_tick))
        {
            f_codec_init_complete = 1;
            csi_codec_output_analog_gain(&codec_output_ch, codec_output_analog_volume);
            csi_codec_input_analog_gain(&codec_input_ch, codec_input_analog_volume);
        }
        gpio0_set(0);
    }
}
ATTRIBUTE_DATA unsigned int app_codec_adc_input(unsigned int *pdst, unsigned int b_len)
{
    unsigned int psr, r_len;
    if (ringbuffer_len(&codec_input_ring_buffer) >= b_len)
    {
        __ASM volatile("mfcr %0, psr"
                       : "=r"(psr));
        __ASM volatile("psrclr ie");
        r_len = csi_codec_input_read_async(&codec_input_ch, pdst, b_len);
        __ASM volatile("mtcr %0, psr"
                       :
                       : "r"(psr));

        return r_len;
    }
    return 0;
}
ATTRIBUTE_DATA unsigned int app_codec_dac_output(unsigned int *psrc, unsigned int b_len)
{
    unsigned int psr, r_len;
    __ASM volatile("mfcr %0, psr"
                   : "=r"(psr));
    __ASM volatile("psrclr ie");
    r_len = csi_codec_output_write_async(&codec_output_ch, psrc, b_len);
    __ASM volatile("mtcr %0, psr"
                   :
                   : "r"(psr));
    return r_len;
}
void app_codec_uninit(void)
{
#if APP_LOG_ENABLE
    printf("stop code input and output\n");
#endif
    csi_codec_input_stop(&codec_input_ch);
    csi_codec_output_stop(&codec_output_ch);

    csi_codec_input_link_dma(&codec_input_ch, NULL);
    csi_codec_output_link_dma(&codec_output_ch, NULL);

    csi_codec_output_detach_callback(&codec_output_ch);
    csi_codec_input_detach_callback(&codec_input_ch);

    csi_codec_uninit(&codec);
}
