/*
* Copyright (c) 2023-2028, SpaceTouch Inc.
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date          Author      Notes
* 2023-06-07    weijiac     the first version
* 2023-06-26    weijiac     add interface for drv_asi.c
*/

#include <stdio.h>
#include <string.h>

//#include <hal_mem.h>
#include <hal_asi.h>
#include <hal_log.h>
#include <hal_common.h>
#include <interrupt.h>
#include <hal_gpio.h>
//#include "rtthread.h"
//#include "audio.h"
#define ASI_CLK 24560000

#define CPID_SPV6040B_A 		0x49004180
#define CPID_SPV6040B_B 		0x49014180
#define CPID_SPV6040C_A 		0x49104180
#define CPID_SPV6040C_B 		0x49114180

#define CPID_SPV7068_1  		0x49004280
#define CPID_SPV7068_2  		0x49014280
#define CPID_SPV7068B  			0x49014380

#define CPID_SPV5068 			0x49514380
#define CPID_SPV5068PRO     	0x49614380


/**
 * @brief       Initialize all settings of ASI
 * @param       asi_t                           asix                    ASI channel
 *              asi_config_type_t*           asi_config_struct       initialize ASI  configuration
 *              asi_pins_type_t*             pins                    initialize gpio pin
 *              asi_control_type_t*          asi_control_struct      initialize ASI  control option
 *              asi_interrupt_enable_type_t  enable_type             initialize interrupt setting
 *
 * @return      HAL_OK
 *              HAL_ERROR
 */
HAL_StatusTypeDef hal_asi_init(asi_t asix, asi_config_type_t *asi_config_struct,
                               asi_pins_type_t *pins, asi_control_type_t *asi_control_struct,
                               asi_interrupt_enable_type_t *enable_type)
{
    uint32_t result = HAL_OK;


    if((DEV_GLOBAL->DVDD_GLOBAL6 & (1 << 0)) == 0)
    {
    	uint32_t chip_id = DEV_CHIP->ID;
    	if((chip_id != CPID_SPV5068)&&(chip_id !=  CPID_SPV5068PRO)&&(chip_id !=  CPID_SPV6040B_A)&&(chip_id !=  CPID_SPV6040B_B)&&(chip_id !=  CPID_SPV6040C_A)&&(chip_id != CPID_SPV6040C_B)&&(chip_id != CPID_SPV7068_1)&&(chip_id != CPID_SPV7068_2)&&(chip_id != CPID_SPV7068B))
		{
			while(1);
		}


		if((chip_id == CPID_SPV6040B_A)||(chip_id == CPID_SPV6040B_B)||(chip_id == CPID_SPV6040C_A)||(chip_id == CPID_SPV6040C_B))
		{
           if(asi_config_struct->samplerate > 96000)
           {
        	   while(1);
           }
		}

		if((chip_id == CPID_SPV7068_1)||(chip_id == CPID_SPV7068_2)||(chip_id == CPID_SPV7068B))
		{
		   if(asi_config_struct->samplerate > 96000)
		   {
			   while(1);
		   }
		}
    }

    if (asix == ASI0)
    {
        /* turn on asi clk */
        DEV_RMU->RSTEN0 |= DEV_RMU_RSTEN0_ASI0;
        DEV_CMU->CLKEN0 |= DEV_CMU_CLKEN0_ASI0;
    }
    else if (asix == ASI1)
    {
        DEV_RMU->RSTEN0 |= DEV_RMU_RSTEN0_ASI1;
        DEV_CMU->CLKEN0 |= DEV_CMU_CLKEN0_ASI1;
    }
    else
    {
        return HAL_ERROR;
    }

    hal_asi_pins_init(pins);
    result = hal_asi_config_init(asix, asi_config_struct);
    hal_asi_control_init(asix, asi_control_struct);
    hal_asi_interrupt_enable_init(asix, enable_type);
    return result;
}


//HAL_StatusTypeDef hal_asi_clk_init(asi_t asix, asi_clk_typedef_t *asi_clk_struct)
//{
//    if(asix >1 )
//        return HAL_ERROR;
//    uint32_t div = 0;
//    uint32_t result = HAL_OK;
//
//    result = get_asi_clk_div(asi_clk_struct->clk_rate,
//                                asi_clk_struct->clk_samplerate,
//                                asi_clk_struct->chn_nums,
//                                asi_clk_struct->chn_width,
//                                &div);
//    if(result == HAL_ERROR)
//    {
//        return HAL_ERROR;
//    }
//
//    DEV_CMU->ASInCLK[asix] = ((div - 1) << 4)|(asi_clk_struct->clk_source);
//}

/**
 * @brief       initialize ASI configuration
 * @param       asi_t               asix
 *              asi_config_type*    asi_config_struct
 * @return      HAL_OK              success
 *              HAL_ERROR           samplerate error
 */
HAL_StatusTypeDef hal_asi_config_init(asi_t asix, asi_config_type_t *asi_config_struct)
{
    uint32_t result = HAL_OK;
    uint32_t temp;
    temp = 0;
    //set the fifo threshold
    temp |= ASI_RX_FIFO_THRESHOLD_8 | ASI_TX_FIFO_THRESHOLD_8;
    temp |= asi_config_struct->data_mode;
    temp |= asi_config_struct->mode;
    temp |= asi_config_struct->chn_nums;
    temp |= asi_config_struct->chn_width;
    temp |= asi_config_struct->data_format;
    //temp |= asi_config_struct->dsd_ctl;
    temp |= asi_config_struct->sample_bit;
    //temp |= asi_config_struct->data_format;
    //temp |= asi_config_struct->dsd_dat_fmt;
    //temp |= asi_config_struct->dsd_dat_mode;
    uint32_t chn_width = 16;

    if (asi_config_struct->chn_width == ASI_CHANNEL_WIDTH_32BITS)
        chn_width = 32;

    if ((asi_config_struct->data_mode & ASI_DSD_ON))
    {
        if (chn_width == 32)
            temp |= ASI_DSD_DAT_FMT_32BITS;
        else
            temp |= ASI_DSD_DAT_FMT_LOW_16BITS;
    }

	temp |= asi_config_struct->data_format;
    uint32_t chnx = (asi_config_struct->chn_nums >> 4) + 1;

    if (chnx % 2)
        chnx++;

    //will not modify clk setting in config init 
    // uint32_t samplerate =  asi_config_struct->asi_samplerate;
     uint32_t div = 0;
     result = get_asi_clk_div(asi_config_struct->clk_rate,asi_config_struct->samplerate, chnx, chn_width, &div);

    if (result != HAL_OK)
        return HAL_ERROR;

    ASIx(asix)->CFG = temp;
    //change clock configuration
    
     DEV_CMU->ASInCLK[asix] = ((div - 1) << 4);
     DEV_CMU->ASInCLK[asix] |= asi_config_struct->clk_source;
    return result;
}
/**
 * @brief       initialize ASI control option
 * @param       asi_t                   asix
 *              rt_asi_control_type_t*  asi_control_struct
 * @return      none
 */
void hal_asi_control_init(asi_t asix, asi_control_type_t *asi_control_struct)
{
    uint32_t temp;
    temp = 0;
    temp |= asi_control_struct->tx_enable;
    temp |= asi_control_struct->rx_enable;
    temp |= asi_control_struct->tx_dma_enable;
    temp |= asi_control_struct->rx_dma_enable;
    temp |= asi_control_struct->tx_stc_t;
    temp |= asi_control_struct->rx_stc_t;
    temp |= asi_control_struct->io_sta;
    temp |= (uint32_t)asi_control_struct->slot_enable;
    ASIx(asix)->CTL = temp;
}

/**
 * @brief       initialize asi pin
 * @param       asi_pins_type_t *pins
 * @return      none
 */
void hal_asi_pins_init(asi_pins_type_t *pins)
{
    hal_gpio_pinmux_set_function(pins->sck, GPIO_MUXSEL_FUNCTION6);
    //hal_gpio_set_pull(pins->sck,GPIO_PULL_DOWN);
    hal_gpio_pinmux_set_function(pins->ws, GPIO_MUXSEL_FUNCTION6);
    //hal_gpio_set_pull(pins->ws,GPIO_PULL_DOWN);
    hal_gpio_pinmux_set_function(pins->sdo, GPIO_MUXSEL_FUNCTION6);
    hal_gpio_pinmux_set_function(pins->sdi, GPIO_MUXSEL_FUNCTION6);
    //hal_gpio_set_pull(pins->sdi,GPIO_PULL_DOWN);
}

/**
 * @brief       initialize interrupt option
 * @param       asi_t                           asix
 *              asi_asi_interrupt_enable_type_t* enable_type
 * @return      none
 */
void hal_asi_interrupt_enable_init(asi_t asix, asi_interrupt_enable_type_t *enable_type)
{
    uint32_t temp;
    temp = 0;
    temp |= (uint32_t)enable_type->asi_tx_irq_enable << 0;
    temp |= (uint32_t)enable_type->asi_tx_fifo_of_enable << 1;
    temp |= (uint32_t)enable_type->asi_tx_fifo_uf_enable << 2;
    temp |= (uint32_t)enable_type->asi_rx_irq_enable << 4;
    temp |= (uint32_t)enable_type->asi_rx_fifo_of_enable << 5;
    temp |= (uint32_t)enable_type->asi_rx_fifo_uf_enable << 6;
    ASIx(asix)->IE = temp;
}


void hal_asi_tx_enable(asi_t asix,uint32_t option)
{
	if(option)
	{
		ASIx(asix)->CTL_f.TX_EN = 1;
	}
	else
	{
		ASIx(asix)->CTL_f.TX_EN = 0;
	}
}

void hal_asi_rx_enable(asi_t asix,uint32_t option)
{
	if(option)
	{
		ASIx(asix)->CTL_f.RX_EN = 1;
	}
	else
	{
		ASIx(asix)->CTL_f.RX_EN = 0;
	}
}

/***-------------------------------------------------------------------------------------***/

/**
 * @brief       set signal control option setting
 * @param       asi_t                asix           asi channel
 *              asi_control_option_t ctl_option     control option
 *              asi_enable_t   enable_option  set option enable/disable
 * @return      none
 */
inline void hal_asi_set_control_option(asi_t asix, asi_control_option_t ctl_option, asi_enable_t enable_option)
{
    ASIx(asix)->CTL &= ~ctl_option;

    if (enable_option == ASI_ENABLE)
        ASIx(asix)->CTL |= ctl_option;
}

/**
 * @brief       get signal control option setting
 * @param       asi_t                   asix
 *              asi_control_option_t    ctl_option
 * @return      0                       disable
 *              1                       enable
 */
inline uint32_t hal_asi_get_control_option(asi_t asix, asi_control_option_t ctl_option)
{
    return ASIx(asix)->CTL &= ctl_option;
}

/**
 * @brief       set signal interrupt enable option setting
 * @param       asi_t                           asix
 *              asi_interrupt_enable_option_t   interrupt_enbale_option
 *              asi_enable_t              enable_option
 * @return      none
 */
inline void hal_asi_set_interrupt_option(asi_t asix, asi_interrupt_enable_option_t interrupt_enbale_option, asi_enable_t enable_option)
{
    ASIx(asix)->IE &= ~interrupt_enbale_option;

    if (enable_option == ASI_ENABLE)
        ASIx(asix)->IE |= interrupt_enbale_option;
}

/**
 * @brief       get signal interrupt enable option setting
 * @param       asi_t                           asix
 *              asi_interrupt_enable_option_t   interrupt_enbale_option
 * @return      0   disable
 *              1   enable
 */
inline uint32_t hal_asi_get_interrupt_option(asi_t asix, asi_interrupt_enable_option_t interrupt_enbale_option)
{
    return ASIx(asix)->IE &= interrupt_enbale_option;
}

/**
 * @brief       get asi interrupt pending status
 * @param       asi_t                           asix
 *              asi_interrupt_status_pending_t  pdx
 * @return      1   trigger interrupt condition
 *              0   interrupt condition was not triggered
 */
inline uint32_t hal_asi_interrupt_get_interrupt_status(asi_t asix, asi_interrupt_status_pending_t pdx)
{
    return ASIx(asix)->PD & pdx ? (SET) : (RESET);
}

/**
 * @brief       clear asi interrupt pending bit
 * @param       asi_t asix
 *              asi_interrupt_status_pending_t  pdx
 * @return      none
 */
inline void hal_asi_interrupt_clear_interrupt_pending(asi_t asix, asi_interrupt_status_pending_t pdx)
{
    ASIx(asix)->PD = pdx;
}

/**
 * @brief       get asi status
 * @param       asi_t               asix
 *              asi_data_status_t   statux      asi status option
 * @return      ASI TX/RX fifo space level or other stutus
 */
inline uint32_t hal_asi_get_data_status(asi_t asix, asi_data_status_t statux)
{
    if (statux == ASI_TX_FIFO_WLVL)
        return ASIx(asix)->STA & ASI_TX_FIFO_WLVL;
    else if (statux == ASI_RX_FIFO_RLVL)
        return (ASIx(asix)->STA & ASI_RX_FIFO_RLVL) >> 16 ;
    else
    {
        return (bool)(ASIx(asix)->STA & statux);
    }
}

/**
 * @brief       get dividing ratio of asi clock
 * @param       int         samplerate
 *              uint32_t    chnx
 *              uint32_t    chn_width
 *              uint32_t*   addr        save the dividing ratio
 * @return      HAL_OK              success
 *              HAL_ERROR           samplerate error
 */
HAL_StatusTypeDef get_asi_clk_div(uint32_t clk_rate,uint32_t samplerate, uint32_t chnx, uint32_t chn_width, uint32_t *addr)
{
    uint32_t BCLK_RATE = 0;
    uint32_t clk_div = 0;
    uint32_t asi_clk = clk_rate;
    if(chnx >8 )
    {
        *addr = 0;
        return HAL_ERROR;
    }
    //expect PDM clock frequency = samplerate * channels number * channels width
    BCLK_RATE = samplerate * chnx * chn_width;
    if( chnx%2 )
        BCLK_RATE = samplerate * (chnx+1) * chn_width;
    if ((BCLK_RATE > asi_clk) || (BCLK_RATE == 0))
    {
        *addr = 0;
        return HAL_ERROR;
    }

    clk_div = asi_clk / BCLK_RATE;

    if (asi_clk % BCLK_RATE)
        clk_div++;
    if(clk_div > 256)
        clk_div = 256;
    *addr = clk_div;
    return HAL_OK;
}

void hal_asi_reset(asi_t asix)
{
    uint32_t asi_temp_cfg = 0;
    uint32_t asi_temp_ctl = 0;
    uint32_t asi_temp_ie = 0;
    uint32_t asi_temp_clk_cfg = 0;
    asi_temp_cfg = ASIx(asix)->CFG;
    asi_temp_ctl = ASIx(asix)->CTL;
    asi_temp_ie = ASIx(asix)->IE;
    asi_temp_clk_cfg = DEV_CMU->ASInCLK[asix];

    if (asix == ASI0)
    {
        DEV_RMU->RSTEN0 &= ~DEV_RMU_RSTEN0_ASI0;
        DEV_CMU->CLKEN0 &= ~DEV_CMU_CLKEN0_ASI0;

        while ((DEV_RMU->RSTEN0 & DEV_RMU_RSTEN0_ASI0) != 0);

        DEV_RMU->RSTEN0 |= DEV_RMU_RSTEN0_ASI0;
        DEV_CMU->CLKEN0 |= DEV_CMU_CLKEN0_ASI0;
    }
    else
    {
        DEV_RMU->RSTEN0 &= ~DEV_RMU_RSTEN0_ASI1;
        DEV_CMU->CLKEN0 &= ~DEV_CMU_CLKEN0_ASI1;

        while ((DEV_RMU->RSTEN0 & DEV_RMU_RSTEN0_ASI1) != 0);

        DEV_RMU->RSTEN0 |= DEV_RMU_RSTEN0_ASI1;
        DEV_CMU->CLKEN0 |= DEV_CMU_CLKEN0_ASI1;
    }

    DEV_CMU->ASInCLK[asix] = asi_temp_clk_cfg;
    ASIx(asix)->CFG = asi_temp_cfg;
    ASIx(asix)->CTL = asi_temp_ctl;
    ASIx(asix)->IE  = asi_temp_ie;
}


/**
 * @brief       turn off asi clock
 * @param       asi_t    hasi
 * @return      HAL_OK              success
 */

HAL_StatusTypeDef hal_audio_asi_close(asi_t asix)
{
    if (asix == ASI0)
    {
        DEV_RMU->RSTEN0 &= ~DEV_RMU_RSTEN0_ASI0;
        DEV_CMU->CLKEN0 &= ~DEV_CMU_CLKEN0_ASI0;
    }
    else if (asix == ASI1)
    {
        DEV_RMU->RSTEN0 &= ~DEV_RMU_RSTEN0_ASI1;
        DEV_CMU->CLKEN0 &= ~DEV_CMU_CLKEN0_ASI1;
    }

    return HAL_OK;
}


/**
 * @brief       turn on asi clock
 * @param       asi_t    hasi
 * @return      HAL_OK              success
 */

HAL_StatusTypeDef hal_audio_asi_open(asi_t asix)
{
    if (asix == ASI0)
    {
        DEV_RMU->RSTEN0 |= DEV_RMU_RSTEN0_ASI0;
        DEV_CMU->CLKEN0 |= DEV_CMU_CLKEN0_ASI0;
    }
    else if (asix == ASI1)
    {
        DEV_RMU->RSTEN0 |= DEV_RMU_RSTEN0_ASI1;
        DEV_CMU->CLKEN0 |= DEV_CMU_CLKEN0_ASI1;
    }

    return HAL_OK;
}

/**
 * @brief   if asi channel clock is on, recognizes it working
 * @param   asi_t asix
 * @return  HAL_OK      ASI free
 *          HAL_BUSY    ASI working
 */
HAL_StatusTypeDef hal_audio_asi_status(asi_t asix)
{
    if (asix == ASI0)
    {
        if (DEV_RMU->RSTEN0 & DEV_RMU_RSTEN0_ASI0)
            return HAL_BUSY;
    }
    else if (asix == ASI1)
    {
        if (DEV_RMU->RSTEN0 & DEV_RMU_RSTEN0_ASI1)
            return HAL_BUSY;
    }

    return HAL_OK;
}

HAL_StatusTypeDef hal_asi_fill_tx_fifo(asi_t asix)
{
    for (int i = 0; i < 24; i++)
    {
        if (ASIx(asix)->STA & ASI_TX_FIFO_AEMP)
        {
            //rt_kprintf("%d times, fifo is still aemp\n",i);
            ASIx(asix)->TX_BUF = 0;
        }
        else
            return HAL_ERROR;
    }

    return HAL_OK;
}


