

#include <kernel/dpl/DebugP.h>
#include <drivers/i2c.h>
#include <drivers/mcasp.h>
#include <board/ioexp/ioexp_tca6424.h>
#include <kernel/dpl/TaskP.h>
#include "ti_drivers_open_close.h"
#include "ti_board_open_close.h"
#include "FreeRTOS.h"
#include "task.h"

#include "audio_warning.h"
#include "cluster_mcasp.h"

#if defined(SOC_AM62AX) || (SOC_AM62PX)
/* AM62Ax CODEC I2C address */
#define APP_MCASP_CODEC_ADDR (0x1BU)

/* I2C address for IO expander */
#define IO_EXP_ADDR (0x22U)

/* Codec reset pin for I/O expander */
#define IO_EXP_CODEC_RESET_PIN (0x8U)
#endif

typedef struct
{
    const uint32_t *audio_buf;
    uint32_t size;
} McaspAudioData;

#define AIC31_PAGE_SEL_REG (0x0U)
#define AIC31_INTERFACE_REG (0x9U)

#define AIC31_CODEC_DATA_PATH_REG (0x7U)
#define AIC32_CLK_SRC_REG (0x8U)
#define AIC_OP_POWER_REG (0x25U)
#define AIC_DAC_OP_SWITCH_CTRL_REG (0x29U)
#define AIC_LDAC_VOL_CTRL (0x2BU)
#define AIC_RDAC_VOL_CTRL (0x2CU)
#define AIC_DAC_L1_HPLOUT_VOL_CTRL_REG (0x2FU)
#define AIC_HP_LOUT_LEVEL_CTRL_REG (0x33U)
#define AIC_DAC_R1_HPROUT_VOL_CTRL_REG (0x40U)
#define AIC_HPROUT_LEVEL_CTRL_REG (0x41U)

/* Reset codec */
static int32_t mcasp_codec_reset(void);
/* Configure codec TLV320AIC31 */
static int32_t mcasp_aic31_codec_config(void);
/* I2C register write for Codec */
static int32_t I2C_writeReg(I2C_Handle handle, uint8_t devAddr, uint8_t reg, uint8_t val);

MCASP_Transaction txTxn;

static SemaphoreP_Object gSemAudioChime;

static MCASP_Handle gMcaspAudioHandle;

static bool gMcaspAudioInitDone = false;

static const McaspAudioData gAudioTable[CLUSTER_AUDIO_COUNT] = {
    [CLUSTER_AUDIO_INDICATOR] = {.audio_buf = audio_sample, .size = audio_sample_size},
    [CLUSTER_AUDIO_SEATBELT] = {.audio_buf = audio_sample, .size = audio_sample_size},
    [CLUSTER_AUDIO_WARNING] = {.audio_buf = audio_sample, .size = audio_sample_size},
};

#define MCASP_LOOP_TASK_PRI (2U)
#define MCASP_LOOP_TASK_STACK_SIZE (2 * 1024U)

static uint8_t gMcaspLoopTaskStack[MCASP_LOOP_TASK_STACK_SIZE] __attribute__((aligned(32)));
static TaskP_Object gMcaspLoopTask;

static volatile bool gMcaspLoopRunning = false;

static ClusterAudioType gMcaspLoopType;

static SemaphoreP_Object gSemLoopStopped;

void mcasp_audio_init(void)
{
    int32_t codecStatus;
    if (gMcaspAudioInitDone)
    {
        DebugP_log("[MCASP] Audio alredy initilised Skipping \r\n");
        return;
    }

    SemaphoreP_constructBinary(&gSemAudioChime, 0);
    SemaphoreP_constructBinary(&gSemLoopStopped, 0);

    codecStatus = mcasp_aic31_codec_config();
    if (codecStatus != SystemP_SUCCESS)
    {
        DebugP_logError("[MCASP] codec config FAILED, status=%d — audio will not work. "
                        "Check I2C wiring/power and HDMI_I2C_CONFIG bus.\r\n",
                        codecStatus);
        return;
    }

    gMcaspAudioHandle = MCASP_getHandle(CONFIG_MCASP0);
    gMcaspAudioInitDone = true;

    DebugP_log("[MCASP] Audio init complete\r\n");
}

void mcasp_play_audio(ClusterAudioType type)
{
    DebugP_log("[MCASP] mcasp_play_audio \r\n");
    int32_t status = SystemP_SUCCESS;
    MCASP_Transaction *transaction;

    if (!gMcaspAudioInitDone)
    {
        DebugP_logError("[MCASP] mcasp_play_audio called before mcasp_audio_init — ignoring\r\n");
        return;
    }

    if (type >= CLUSTER_AUDIO_COUNT)
    {
        DebugP_logError("[MCASP] mcasp_play_audio: invalid type %d\r\n", type);
        return;
    }

    txTxn.buf = (void *)gAudioTable[type].audio_buf;
    txTxn.count = gAudioTable[type].size;
    txTxn.timeout = 0xFFFFFF;
    MCASP_submitTx(gMcaspAudioHandle, &txTxn);

    DebugP_log("[MCASP] Playing audio type=%d\r\n", type);

    status = MCASP_startTransferTx(gMcaspAudioHandle);
    DebugP_assert(status == SystemP_SUCCESS);

    SemaphoreP_pend(&gSemAudioChime, SystemP_WAIT_FOREVER);

    DebugP_log("[MCASP] Playback completed\r\n");

    do
    {
        transaction = MCASP_withdrawRx(gMcaspAudioHandle);
    } while (transaction != NULL);

    MCASP_stopTransferTx(gMcaspAudioHandle);
}

static void mcasp_loop_task_main(void *args)
{
    DebugP_log("[MCASP] mcasp_loop_task_main\r\n");
    MCASP_Transaction *transaction;

    txTxn.buf = (void *)gAudioTable[gMcaspLoopType].audio_buf;
    txTxn.count = gAudioTable[gMcaspLoopType].size;
    txTxn.timeout = 0xFFFFFF;
    MCASP_submitTx(gMcaspAudioHandle, &txTxn);

    DebugP_assert(MCASP_startTransferTx(gMcaspAudioHandle) == SystemP_SUCCESS);

    while (gMcaspLoopRunning)
    {
        /* wait for this buffer's Tx-complete — ISR just posts, all real
         * work happens here in task context */
        SemaphoreP_pend(&gSemAudioChime, SystemP_WAIT_FOREVER);

        if (gMcaspLoopRunning)
        {
            /* resubmit next buffer — transfer keeps running continuously,
             * no stop/start, so there's no gap between repeats */
            txTxn.buf = (void *)gAudioTable[gMcaspLoopType].audio_buf;
            txTxn.count = gAudioTable[gMcaspLoopType].size;
            txTxn.timeout = 0xFFFFFF;
            MCASP_submitTx(gMcaspAudioHandle, &txTxn);
        }
    }

    do
    {
        transaction = MCASP_withdrawRx(gMcaspAudioHandle);
    } while (transaction != NULL);

    MCASP_stopTransferTx(gMcaspAudioHandle);

    DebugP_log("[MCASP] Loop stopped\r\n");

    SemaphoreP_post(&gSemLoopStopped);
    vTaskDelete(NULL);
}

void mcasp_play_audio_start_loop(ClusterAudioType type)
{
    DebugP_log("[MCASP] mcasp_play_audio_start_loop\r\n");

    TaskP_Params taskParams;

    if (!gMcaspAudioInitDone)
    {
        DebugP_logError("[MCASP] mcasp_play_audio_start_loop called before mcasp_audio_init — ignoring\r\n");
        return;
    }

    if (type >= CLUSTER_AUDIO_COUNT)
    {
        DebugP_logError("[MCASP] mcasp_play_audio_start_loop: invalid type %d\r\n", type);
        return;
    }

    if (gMcaspLoopRunning)
    {
        DebugP_logError("[MCASP] loop already running — call mcasp_play_audio_stop_loop() first\r\n");
        return;
    }

    gMcaspLoopType = type;
    gMcaspLoopRunning = true;

    TaskP_Params_init(&taskParams);
    taskParams.name = "mcasp_loop_task";
    taskParams.stackSize = MCASP_LOOP_TASK_STACK_SIZE;
    taskParams.stack = gMcaspLoopTaskStack;
    taskParams.priority = MCASP_LOOP_TASK_PRI;
    taskParams.taskMain = mcasp_loop_task_main;

    DebugP_assert(TaskP_construct(&gMcaspLoopTask, &taskParams) == SystemP_SUCCESS);

    DebugP_log("[MCASP] Loop started, type=%d\r\n", type);
}

void mcasp_play_audio_stop_loop(void)
{
    if (!gMcaspLoopRunning)
    {
        DebugP_log("[MCASP] stop_loop called but no loop is running\r\n");
        return;
    }

    /* task checks this before resubmitting — clearing it here means the
     * buffer currently in flight is the last one */
    gMcaspLoopRunning = false;

    /* wait for the task to actually finish tearing down and exit */
    SemaphoreP_pend(&gSemLoopStopped, SystemP_WAIT_FOREVER);

    DebugP_log("[MCASP] Loop stopped\r\n");
}

static int32_t mcasp_codec_reset(void)
{
    int32_t status = SystemP_SUCCESS;

    TCA6424_Params TCA6424_IOexp_params =
        {
            .i2cInstance = 0,
            .i2cAddress = IO_EXP_ADDR};

    TCA6424_Config TCA6424_IOexp_config;

    status = TCA6424_open(&TCA6424_IOexp_config, &TCA6424_IOexp_params);

    if (status != SystemP_SUCCESS)
    {
        DebugP_logError("[MCASP] TCA6424_open failed, status=%d\r\n", status);
        return status;
    }

    status = TCA6424_config(&TCA6424_IOexp_config, IO_EXP_CODEC_RESET_PIN,
                            TCA6424_MODE_OUTPUT);

    if (status != SystemP_SUCCESS)
    {
        DebugP_logError("[MCASP] TCA6424_config failed, status=%d\r\n", status);
        return status;
    }

    status = TCA6424_setOutput(&TCA6424_IOexp_config, IO_EXP_CODEC_RESET_PIN, TCA6424_OUT_STATE_LOW);

    if (status != SystemP_SUCCESS)
    {
        DebugP_logError("[MCASP] TCA6424 reset-low failed, status=%d\r\n", status);
        return status;
    }
    /* Wait for codec to reset */
    ClockP_usleep(1);

    status = TCA6424_setOutput(&TCA6424_IOexp_config, IO_EXP_CODEC_RESET_PIN, TCA6424_OUT_STATE_HIGH);

    if (status != SystemP_SUCCESS)
    {
        DebugP_logError("[MCASP] TCA6424 reset-high failed, status=%d\r\n", status);
    }
    return status;
}

static int32_t mcasp_aic31_codec_config(void)
{
    int32_t status = SystemP_FAILURE;
    I2C_Handle i2cHandle;
    i2cHandle = gI2cHandle[HDMI_I2C_CONFIG];
    uint8_t deviceAddress = 0x1B;

    status = mcasp_codec_reset();
    DebugP_assert(status == SystemP_SUCCESS);

    status = I2C_probe(i2cHandle, deviceAddress);
    DebugP_assert(status == SystemP_SUCCESS);

#define AIC31_REG_WRITE(reg, val)                                                               \
    do                                                                                      \
    {                                                                                       \
        status = I2C_writeReg(i2cHandle, deviceAddress, (reg), (val));                      \
        if (status != SystemP_SUCCESS)                                                      \
        {                                                                                   \
            DebugP_logError("[MCASP] I2C write failed reg=0x%02X val=0x%02X status=%d\r\n", \
                            (reg), (val), status);                                          \
            return status;                                                                  \
        }                                                                                   \
    } while (0)

    /* Select Page0 */
    AIC31_REG_WRITE(AIC31_PAGE_SEL_REG, 0U);

    /* Select codec to be in master mode for FS and BCLK */
    AIC31_REG_WRITE(AIC32_CLK_SRC_REG, (1U << 6U) | (1U << 7U));

    /* I2S interface */
    AIC31_REG_WRITE(AIC31_INTERFACE_REG, (0x0U << 6U) | (0x3 << 4U));

    /* Configure data path */
    /* Left DAC datapath plays left and Right path datapath plays right */
    AIC31_REG_WRITE(AIC31_CODEC_DATA_PATH_REG, 0xA);

    /* DAC setup */
    {
        /* Power up Left and Right DAC with HPLCOM single ended o/p */
        AIC31_REG_WRITE(AIC_OP_POWER_REG, 0xC0);

        /* Right DAC volume follows left channel control register */
        AIC31_REG_WRITE(AIC_DAC_OP_SWITCH_CTRL_REG, 0x02);

        /* DAC L1 routed to HPLOUT with HPLOUT gain -8dB */
        AIC31_REG_WRITE(AIC_DAC_L1_HPLOUT_VOL_CTRL_REG, 0x80);

        /* Unmute HPLOUT and power up HPLOUT. HPLOUT is high impedance when powered down */
        AIC31_REG_WRITE(AIC_HP_LOUT_LEVEL_CTRL_REG, 0x0D);

        /* DAC R1 routed to HPROUT with HPLOUT gain -8dB */
        AIC31_REG_WRITE(AIC_DAC_R1_HPROUT_VOL_CTRL_REG, 0x80);

        /* Unmute HPROUT and power up HPROUT. HPROUT is high impedance when powered down */
        AIC31_REG_WRITE(AIC_HPROUT_LEVEL_CTRL_REG, 0x0D);

        /* Unmute Left DAC with gain -36 dB */
        AIC31_REG_WRITE(AIC_LDAC_VOL_CTRL, 0x00);

        /* Unmute Right DAC with gain -36 dB */
        AIC31_REG_WRITE(AIC_RDAC_VOL_CTRL, 0x00);
    }
#undef AIC31_REG_WRITE

    DebugP_log("[MCASP] Codec configured successfully\r\n");
    return SystemP_SUCCESS;
}

static int32_t I2C_writeReg(I2C_Handle handle, uint8_t devAddr, uint8_t reg, uint8_t val)
{
    I2C_Transaction i2cTransaction;
    uint8_t txBuffer[2];
    int32_t status;

    I2C_Transaction_init(&i2cTransaction);
    i2cTransaction.writeBuf = txBuffer;
    i2cTransaction.writeCount = 2;
    i2cTransaction.targetAddress = devAddr;
    txBuffer[0] = reg;
    txBuffer[1] = val;

    status = I2C_transfer(handle, &i2cTransaction);
    return status;
}

void mcasp_txcb(MCASP_Handle handle,
                MCASP_Transaction *transaction)
{
    DebugP_log("[MCASP] mcasp_txcb\r\n");
    SemaphoreP_post(&gSemAudioChime);
}

