/***************************************************************************
 *
 * Copyright 2015-2019 BES.
 * All rights reserved. All unpublished rights reserved.
 *
 * No part of this work may be used or reproduced in any form or by any
 * means, or stored in a database or retrieval system, without prior written
 * permission of BES.
 *
 * Use of this work is governed by a license granted by BES.
 * This work contains confidential and proprietary information of
 * BES. which is protected by copyright, trade secret,
 * trademark and other intellectual property rights.
 *
 ****************************************************************************/
#include "plat_addr_map.h"

#ifdef BT_CMU_BASE

#include "hal_intersys.h"
#include "hal_trace.h"
#include "hal_sleep.h"
#include CHIP_SPECIFIC_HDR(reg_cmu)
#include CHIP_SPECIFIC_HDR(reg_btcmu)
#include "cmsis_nvic.h"
#include "hal_timer.h"
#ifdef TX_RX_PCM_MASK
#include "hal_chipid.h"
#endif
#ifdef CHIP_BEST1400
#define PEER_IRQ_AUTO_CLEAR
#endif

#define MAX_SEND_RECORD_COUNT               3

// MCU-CMU ISIRQ_SET
#define CMU_BT2MCU_DATA_DONE_SET            (1 << 0)
#define CMU_BT2MCU_DATA1_DONE_SET           (1 << 1)
#define CMU_MCU2BT_DATA_IND_SET             (1 << 2)
#define CMU_MCU2BT_DATA1_IND_SET            (1 << 3)

// MCU-CMU ISIRQ_CLR
#define CMU_BT2MCU_DATA_DONE_CLR            (1 << 0)
#define CMU_BT2MCU_DATA1_DONE_CLR           (1 << 1)
#define CMU_MCU2BT_DATA_IND_CLR             (1 << 2)
#define CMU_MCU2BT_DATA1_IND_CLR            (1 << 3)

// BT-CMU ISIRQ_SET
#define BTCMU_MCU2BT_DATA_DONE_SET          (1 << 0)
#define BTCMU_MCU2BT_DATA1_DONE_SET         (1 << 1)
#define BTCMU_BT2MCU_DATA_IND_SET           (1 << 2)
#define BTCMU_BT2MCU_DATA1_IND_SET          (1 << 3)

// BT-CMU ISIRQ_CLR
#define BTCMU_MCU2BT_DATA_DONE_CLR          (1 << 0)
#define BTCMU_MCU2BT_DATA1_DONE_CLR         (1 << 1)
#define BTCMU_BT2MCU_DATA_IND_CLR           (1 << 2)
#define BTCMU_BT2MCU_DATA1_IND_CLR          (1 << 3)

enum HAL_INTERSYS_IRQ_TYPE_T {
    HAL_INTERSYS_IRQ_SEND_IND,
    HAL_INTERSYS_IRQ_RECV_DONE,

    HAL_INTERSYS_IRQ_TYPE_QTY
};

struct HAL_INTERSYS_MSG_T {
    struct HAL_INTERSYS_MSG_T *next;        // pointer to next element in the list
    enum HAL_INTERSYS_MSG_TYPE_T type;      // message type
    unsigned int len;                       // message data length in bytes
    const unsigned char *data;              // pointer to the message data
};

struct HAL_INTERSYS_SEND_RECORD_T {
    struct HAL_INTERSYS_MSG_T msg;
    bool in_use;
};

static const IRQn_Type rx_irq_id[HAL_INTERSYS_ID_QTY] = {
    ISDATA_IRQn,
    ISDATA1_IRQn,
};

static const IRQn_Type tx_irq_id[HAL_INTERSYS_ID_QTY] = {
    ISDONE_IRQn,
    ISDONE1_IRQn,
};

static struct HAL_INTERSYS_MSG_T *** const bt_recv_msg_list_ppp[HAL_INTERSYS_ID_QTY] = {
    (struct HAL_INTERSYS_MSG_T ***)(BT_RAM_BASE + BT_INTESYS_MEM_OFFSET + 0x4),
    (struct HAL_INTERSYS_MSG_T ***)(BT_RAM_BASE+ BT_INTESYS_MEM_OFFSET + 0xC),
};

static struct HAL_INTERSYS_MSG_T ** const recv_msg_list_p[HAL_INTERSYS_ID_QTY] = {
    (struct HAL_INTERSYS_MSG_T **)(BT_RAM_BASE + BT_INTESYS_MEM_OFFSET),
    (struct HAL_INTERSYS_MSG_T **)(BT_RAM_BASE + BT_INTESYS_MEM_OFFSET + 0x8),
};

static struct HAL_INTERSYS_MSG_T * send_msg_list_p[HAL_INTERSYS_ID_QTY];
static struct HAL_INTERSYS_MSG_T * send_pending_list_p[HAL_INTERSYS_ID_QTY];

static struct HAL_INTERSYS_MSG_T recv_pending_head[HAL_INTERSYS_ID_QTY];

static struct HAL_INTERSYS_SEND_RECORD_T send_msgs[HAL_INTERSYS_ID_QTY][MAX_SEND_RECORD_COUNT];

static HAL_INTERSYS_RX_IRQ_HANDLER rx_irq_handler[HAL_INTERSYS_ID_QTY][HAL_INTERSYS_MSG_TYPE_QTY];
static HAL_INTERSYS_TX_IRQ_HANDLER tx_irq_handler[HAL_INTERSYS_ID_QTY][HAL_INTERSYS_MSG_TYPE_QTY];

static uint8_t chan_opened[HAL_INTERSYS_ID_QTY] = { 0, 0, };
STATIC_ASSERT(sizeof(chan_opened[0]) * 8 >= HAL_INTERSYS_MSG_TYPE_QTY, "chan_opened size too small");

static bool need_flow_ctrl[HAL_INTERSYS_ID_QTY] = { false, false, };

static bool chan_busy[HAL_INTERSYS_ID_QTY] = { false, false, };

static bool busy_now = false;

#ifdef PEER_IRQ_AUTO_CLEAR
static bool peer_irq_auto_clear;
#endif

static struct CMU_T * const cmu = (struct CMU_T *)CMU_BASE;

static struct BTCMU_T * const btcmu = (struct BTCMU_T *)BT_CMU_BASE;

static void hal_intersys_busy(enum HAL_INTERSYS_ID_T id, bool busy)
{
    int i;
    bool new_state;

    if (chan_busy[id] == busy) {
        return;
    }

    chan_busy[id] = busy;

    if (busy_now == busy) {
        return;
    }

    if (busy) {
        hal_sys_wake_lock(HAL_SYS_WAKE_LOCK_USER_INTERSYS);
        busy_now = true;
    } else {
        new_state = false;
        for (i = 0; i < HAL_INTERSYS_ID_QTY; i++) {
            if (chan_busy[i]) {
                new_state = true;
                break;
            }
        }
        if (!new_state) {
            hal_sys_wake_unlock(HAL_SYS_WAKE_LOCK_USER_INTERSYS);
            busy_now = false;
        }
    }
}

#ifdef PEER_IRQ_AUTO_CLEAR
void hal_intersys_peer_irq_auto_clear(bool enable)
{
    peer_irq_auto_clear = enable;
}
#endif

static int hal_intersys_peer_irq_set(enum HAL_INTERSYS_ID_T id, enum HAL_INTERSYS_IRQ_TYPE_T type)
{
    uint32_t value;

    if (id == HAL_INTERSYS_ID_0) {
        if (type == HAL_INTERSYS_IRQ_SEND_IND) {
            value = CMU_MCU2BT_DATA_IND_SET;
        } else {
            value = CMU_BT2MCU_DATA_DONE_SET;
        }
    } else {
        if (type == HAL_INTERSYS_IRQ_SEND_IND) {
            value = CMU_MCU2BT_DATA1_IND_SET;
        } else {
            value = CMU_BT2MCU_DATA1_DONE_SET;
        }
    }

#ifdef PEER_IRQ_AUTO_CLEAR
    uint32_t ret;

    ret = int_lock();
    cmu->ISIRQ_SET = value;
    if (peer_irq_auto_clear) {
        if (CMU_MCU2BT_DATA_IND_SET == value) {
            cmu->ISIRQ_CLR |= CMU_MCU2BT_DATA_IND_CLR;
        } else {
            cmu->ISIRQ_CLR |= CMU_BT2MCU_DATA_DONE_CLR;
        }
        hal_sys_timer_delay(MS_TO_TICKS(2));
    }
    int_unlock(ret);
#else
    cmu->ISIRQ_SET = value;
#endif
    return 0;
}

static inline void btcmu_reg_update_wait(void)
{
    // Make sure BTCMU (26M clock domain) write opertions finish before return
    btcmu->ISIRQ_CLR;
}

static int hal_intersys_local_irq_clear(enum HAL_INTERSYS_ID_T id, enum HAL_INTERSYS_IRQ_TYPE_T type)
{
    uint32_t value;

    if (id == HAL_INTERSYS_ID_0) {
        if (type == HAL_INTERSYS_IRQ_SEND_IND) {
            value = BTCMU_BT2MCU_DATA_IND_CLR;
        } else {
            value = BTCMU_MCU2BT_DATA_DONE_CLR;
        }
    } else {
        if (type == HAL_INTERSYS_IRQ_SEND_IND) {
            value = BTCMU_BT2MCU_DATA1_IND_CLR;
        } else {
            value = BTCMU_MCU2BT_DATA1_DONE_CLR;
        }
    }

    btcmu->ISIRQ_CLR = value;
    btcmu_reg_update_wait();
    return 0;
}

static int hal_intersys_local_irq_set(enum HAL_INTERSYS_ID_T id, enum HAL_INTERSYS_IRQ_TYPE_T type)
{
    uint32_t value;

    if (id == HAL_INTERSYS_ID_0) {
        if (type == HAL_INTERSYS_IRQ_SEND_IND) {
            value = BTCMU_BT2MCU_DATA_IND_SET;
        } else {
            value = BTCMU_MCU2BT_DATA_DONE_SET;
        }
    } else {
        if (type == HAL_INTERSYS_IRQ_SEND_IND) {
            value = BTCMU_BT2MCU_DATA1_IND_SET;
        } else {
            value = BTCMU_MCU2BT_DATA1_DONE_SET;
        }
    }

    btcmu->ISIRQ_SET = value;
    btcmu_reg_update_wait();
    return 0;
}


//static void hal_intersys_wait_done_idle(void)
//{
//    while(hal_cmu_get_address()->ISIRQ_SET & (CMU_BT2MCU_DATA_DONE_SET |CMU_BT2MCU_DATA1_DONE_SET));
//}


//static void hal_intersys_wait_data_idle(void)
//{
//    while(hal_cmu_get_address()->ISIRQ_SET & (CMU_MCU2BT_DATA_IND_SET |CMU_MCU2BT_DATA1_IND_SET));
//}
debug_intersys_type g_debug_intersys;
static void hal_intersys_rx_irq(void)
{
    int id;
    struct HAL_INTERSYS_MSG_T *msg_ptr;
    enum HAL_INTERSYS_MSG_TYPE_T type;
    unsigned int processed;

    if (g_debug_intersys.cmd_opcode != 0xFFFF){
        g_debug_intersys.irq_happen += 1;
    }

    for (id = HAL_INTERSYS_ID_0; id < HAL_INTERSYS_ID_QTY; id++) {
        if (NVIC_GetActive(rx_irq_id[id])) {
            hal_intersys_local_irq_clear(id, HAL_INTERSYS_IRQ_SEND_IND);

            if (recv_pending_head[id].data) {
                // Previous unprocessed message
                msg_ptr = &recv_pending_head[id];
            } else {
                // New message
                msg_ptr = *recv_msg_list_p[id];
            }
            while (msg_ptr) {
                type = msg_ptr->type;
                if (type >= HAL_INTERSYS_MSG_TYPE_QTY) {
                    // Error
                    ASSERT(false, "INTERSYS-RX: Invalid msg type: %d", type);
                    break;
                }
                if (rx_irq_handler[id][type]) {
                    processed = rx_irq_handler[id][type](msg_ptr->data, msg_ptr->len);
                    // Check if flow control needed
                    if (processed < msg_ptr->len) {
                        recv_pending_head[id].next = msg_ptr->next;
                        recv_pending_head[id].type = msg_ptr->type;
                        recv_pending_head[id].len = msg_ptr->len - processed;
                        recv_pending_head[id].data = msg_ptr->data + processed;
                        break;
                    }
                } else {
                    // Error
                    ASSERT(false, "INTERSYS-RX: Handler missing");
                    break;
                }
                msg_ptr = msg_ptr->next;
            }

            if (msg_ptr == NULL) {
                if (!need_flow_ctrl[id]){
                    hal_intersys_peer_irq_set(id, HAL_INTERSYS_IRQ_RECV_DONE);
                }
                recv_pending_head[id].data = NULL;
            }
        }
    }
}

static void hal_intersys_tx_irq(void)
{
    int id;
    struct HAL_INTERSYS_MSG_T *msg_ptr;
    enum HAL_INTERSYS_MSG_TYPE_T type;

    for (id = HAL_INTERSYS_ID_0; id < HAL_INTERSYS_ID_QTY; id++) {
        if (NVIC_GetActive(tx_irq_id[id])) {
            hal_intersys_local_irq_clear(id, HAL_INTERSYS_IRQ_RECV_DONE);

            msg_ptr = send_msg_list_p[id];
            while (msg_ptr) {
                type = msg_ptr->type;
                if (type >= HAL_INTERSYS_MSG_TYPE_QTY) {
                    // Error
                    ASSERT(false, "INTERSYS-TX: Invalid msg type: %d", type);
                    break;
                }
                if (tx_irq_handler[id][type]) {
                    tx_irq_handler[id][type](msg_ptr->data, msg_ptr->len);
                };
                CONTAINER_OF(msg_ptr, struct HAL_INTERSYS_SEND_RECORD_T, msg)->in_use = false;
                msg_ptr = msg_ptr->next;
            }

            if (send_pending_list_p[id]) {
                send_msg_list_p[id] = send_pending_list_p[id];
                send_pending_list_p[id] = NULL;
                hal_intersys_peer_irq_set(id, HAL_INTERSYS_IRQ_SEND_IND);
            } else {
                send_msg_list_p[id] = NULL;
                // Allow sleep
                hal_intersys_busy(id, false);
            }
        }
    }
}

int hal_intersys_open(enum HAL_INTERSYS_ID_T id, enum HAL_INTERSYS_MSG_TYPE_T type,
                      HAL_INTERSYS_RX_IRQ_HANDLER rxhandler, HAL_INTERSYS_TX_IRQ_HANDLER txhandler, bool rx_flowctrl)
{
    int i;

    if (id >= HAL_INTERSYS_ID_QTY) {
        return 1;
    }
    if (type >= HAL_INTERSYS_MSG_TYPE_QTY) {
        return 2;
    }

    if (chan_opened[id] == 0) {
        if (id == HAL_INTERSYS_ID_0) {
            cmu->ISIRQ_CLR = CMU_BT2MCU_DATA_DONE_CLR | CMU_MCU2BT_DATA_IND_CLR;
        } else {
            cmu->ISIRQ_CLR = CMU_BT2MCU_DATA1_DONE_CLR | CMU_MCU2BT_DATA1_IND_CLR;
        }
        *bt_recv_msg_list_ppp[id] = &send_msg_list_p[id];

        NVIC_SetVector(rx_irq_id[id], (uint32_t)hal_intersys_rx_irq);
        NVIC_SetPriority(rx_irq_id[id], IRQ_PRIORITY_NORMAL);

        NVIC_SetVector(tx_irq_id[id], (uint32_t)hal_intersys_tx_irq);
        NVIC_SetPriority(tx_irq_id[id], IRQ_PRIORITY_NORMAL);

        // Stop IRQs by default
        NVIC_DisableIRQ(rx_irq_id[id]);
        NVIC_DisableIRQ(tx_irq_id[id]);

        send_msg_list_p[id] = NULL;
        send_pending_list_p[id] = NULL;
        recv_pending_head[id].data = NULL;
        for (i = 0; i < MAX_SEND_RECORD_COUNT; i++) {
            send_msgs[id][i].in_use = false;
        }
        need_flow_ctrl[id] = rx_flowctrl;
    } else {
        ASSERT(need_flow_ctrl[id] == rx_flowctrl, "INTERSYS-OPEN: rx_flowctrl=%d (should be %d)", rx_flowctrl, need_flow_ctrl[id]);
        return 3;
    }
    chan_opened[id] |= (1 << type);

    rx_irq_handler[id][type] = rxhandler;
    tx_irq_handler[id][type] = txhandler;

    return 0;
}

int hal_intersys_close(enum HAL_INTERSYS_ID_T id,enum HAL_INTERSYS_MSG_TYPE_T type)
{
    if (id >= HAL_INTERSYS_ID_QTY) {
        return 1;
    }

    chan_opened[id] &= ~(1 << type);
    rx_irq_handler[id][type] = NULL;
    tx_irq_handler[id][type] = NULL;

    if (chan_opened[id] == 0) {
        // Stop IRQs by default
        NVIC_DisableIRQ(rx_irq_id[id]);
        NVIC_DisableIRQ(tx_irq_id[id]);

        send_msg_list_p[id] = NULL;
        send_pending_list_p[id] = NULL;
        recv_pending_head[id].data = NULL;
        need_flow_ctrl[id] = false;
    }

    return 0;
}

int hal_intersys_start_recv(enum HAL_INTERSYS_ID_T id)
{
    if (id >= HAL_INTERSYS_ID_QTY) {
        return 1;
    }

    NVIC_EnableIRQ(rx_irq_id[id]);
    // Check if there is any previous unprocessed message
    if (recv_pending_head[id].data) {
        hal_intersys_local_irq_set(id, HAL_INTERSYS_IRQ_SEND_IND);
    }
    return 0;
}

int hal_intersys_stop_recv(enum HAL_INTERSYS_ID_T id)
{
    if (id >= HAL_INTERSYS_ID_QTY) {
        return 1;
    }

    NVIC_DisableIRQ(rx_irq_id[id]);
    return 0;
}

int hal_intersys_send(enum HAL_INTERSYS_ID_T id, enum HAL_INTERSYS_MSG_TYPE_T type,
                      const unsigned char *data, unsigned int len)
{
    uint32_t lock;
    int ret;
    struct HAL_INTERSYS_SEND_RECORD_T *record;
    struct HAL_INTERSYS_MSG_T *next;
    int i;

    if (id >= HAL_INTERSYS_ID_QTY) {
        return 1;
    }
    if (type >= HAL_INTERSYS_MSG_TYPE_QTY) {
        return 2;
    }
    if ((chan_opened[id] & (1 << type)) == 0) {
        return 3;
    }

    NVIC_EnableIRQ(tx_irq_id[id]);

    ret = -1;
    record = &send_msgs[id][0];

    lock = int_lock();
    for (i = 0; i < MAX_SEND_RECORD_COUNT; i++) {
        if (record->in_use) {
            record++;
            continue;
        }
        record->in_use = true;
        record->msg.next = NULL;
        record->msg.type = type;
        record->msg.len = len;
        record->msg.data = data;
        if (send_msg_list_p[id] == NULL) {
            send_msg_list_p[id] = &record->msg;
            hal_intersys_peer_irq_set(id, HAL_INTERSYS_IRQ_SEND_IND);
        } else if (send_pending_list_p[id] == NULL) {
            send_pending_list_p[id] = &record->msg;
        } else {
            next = send_pending_list_p[id];
            while (next->next) {
                next = next->next;
            }
            next->next = &record->msg;
        }
        ret = 0;
        // Prohibit sleep here
        hal_intersys_busy(id, true);
        break;
    }
    int_unlock(lock);

    return ret;
}

void hal_intersys_rx_done(enum HAL_INTERSYS_ID_T id)
{
    hal_intersys_peer_irq_set(id, HAL_INTERSYS_IRQ_RECV_DONE);
}

#endif

#ifdef TX_RX_PCM_MASK
static FRAME2BUFF_HANDLER DecQ;

void hal_intersys_mic_rx_irq()
{
    int id;
    id = HAL_INTERSYS_ID_1;
    if(NVIC_GetActive(rx_irq_id[id]))
    {
        hal_intersys_local_irq_clear(id, HAL_INTERSYS_IRQ_SEND_IND);
        //TRACE(0,"HAL_INTERSYS_ID_1 CLEAR");
        //output data to buff
        DecQ();
    }
}

int hal_intersys_mic_open(enum HAL_INTERSYS_ID_T id, FRAME2BUFF_HANDLER dch)
{
   // if(btdrv_is_pcm_mask_enable()==1)
    {
        DecQ= dch;
        NVIC_EnableIRQ(rx_irq_id[id]);
        if(id == HAL_INTERSYS_ID_1)
        {
            cmu->ISIRQ_CLR = CMU_BT2MCU_DATA1_DONE_CLR | CMU_MCU2BT_DATA1_IND_CLR;
            NVIC_SetVector(rx_irq_id[id], (uint32_t)hal_intersys_mic_rx_irq);
            NVIC_SetPriority(rx_irq_id[id], IRQ_PRIORITY_NORMAL);
        }
    }
    return 0;
}
#endif
