/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "syscfg/syscfg.h"
#include "sysflash/sysflash.h"
#include "os/os.h"
#include "mcu/mcu.h"
#include "mcu/cmsis_nvic.h"
#include "mcu/da1469x_hal.h"
#include "mcu/da1469x_lpclk.h"
#include "mcu/da1469x_clock.h"
#include "mcu/da1469x_trimv.h"
#include "mcu/da1469x_pdc.h"
#include <ipc_cmac/shm.h>
#include <ipc_cmac/mbox.h>
#include <ipc_cmac/rand.h>
#include "trng/trng.h"
#include "console/console.h"

extern char _binary_cmac_img_bin_start[];
extern char _binary_cmac_img_bin_end;
extern char _binary_cmac_ram_bin_start[];
extern char _binary_cmac_ram_bin_end;

struct cmac_img_info {
    uint32_t magic;
    uint32_t img_size;
    uint32_t ram_size;
    uint32_t data_offset;
    uint32_t shared_offset;
    uint32_t shared_addr;
};

struct cmac_img_hdr {
    uint32_t isr[32];
    struct cmac_img_info ii;
    struct cmac_shm_config *shm_config;
    struct cmac_shm_ctrl *shm_ctrl;
    struct cmac_shm_mbox *shm_mbox_s2c;
    struct cmac_shm_mbox *shm_mbox_c2s;
    struct cmac_shm_trim *shm_trim;
    struct cmac_shm_rand *shm_rand;
    struct cmac_shm_dcdc *shm_dcdc;
    struct cmac_shm_crashinfo *shm_crashinfo;
    struct cmac_shm_debugdata *shm_debugdata;
};

/* Note: this can be only used after image was copied to RAM area */
#define CMAC_RAM_HDR        ((struct cmac_img_hdr *)_binary_cmac_ram_bin_start)

#define CMAC_CODE_PTR(_ptr)     ((void *)((uint32_t)(_ptr) + \
                                          MCU_MEM_SYSRAM_START_ADDRESS + \
                                          MEMCTRL->CMI_CODE_BASE_REG))
#define CMAC_SHARED_PTR(_ptr)   ((void *)((uint32_t)(_ptr) - \
                                          CMAC_RAM_HDR->ii.shared_addr + \
                                          MCU_MEM_SYSRAM_START_ADDRESS + \
                                          MEMCTRL->CMI_SHARED_BASE_REG))

volatile struct cmac_shm_config *g_cmac_shm_config;
volatile struct cmac_shm_ctrl *g_cmac_shm_ctrl;
volatile struct cmac_shm_mbox *g_cmac_shm_mbox_s2c;
volatile struct cmac_shm_mbox *g_cmac_shm_mbox_c2s;
volatile struct cmac_shm_trim *g_cmac_shm_trim;
volatile struct cmac_shm_rand *g_cmac_shm_rand;
volatile struct cmac_shm_dcdc *g_cmac_shm_dcdc;
volatile struct cmac_shm_crashinfo *g_cmac_shm_crashinfo;
volatile struct cmac_shm_debugdata *g_cmac_shm_debugdata;

/* PDC entry for waking up CMAC */
static int8_t g_cmac_host_pdc_sys2cmac;
/* PDC entry for waking up M33 */
static int8_t g_cmac_host_pdc_cmac2sys;

static void cmac_host_rand_fill(struct os_event *ev);
static struct os_event g_cmac_host_rand_ev = {
    .ev_cb = cmac_host_rand_fill
};

static void cmac_host_rand_chk_fill(void);

#if MYNEWT_VAL_CHOICE(BLE_TRANSPORT_HS, uart)
static void cmac_host_error_w4flush(struct os_event *ev);
static struct os_event g_cmac_host_error_ev = {
    .ev_cb = cmac_host_error_w4flush
};
#endif

static void
cmac2sys_isr(void)
{
    volatile struct cmac_shm_crashinfo *ci = g_cmac_shm_crashinfo;

    os_trace_isr_enter();

    /* Clear CMAC2SYS interrupt */
    *(volatile uint32_t *)0x40002000 = 2;

    cmac_mbox_read();

    if (*(volatile uint32_t *)0x40002000 & 0x1c00) {
        if (ci) {
            console_blocking_mode();
            console_printf("CMAC error (0x%08lx)\n",
                           *(volatile uint32_t *)0x40002000);
            console_printf("  lr:0x%08lx  pc:0x%08lx\n", ci->lr, ci->pc);
            if (ci->assert) {
                console_printf("  assert:0x%08lx\n", ci->assert);
                if (ci->assert_file) {
                    /* Need to translate pointer from M0 code segment to M33 data */
                    ci->assert_file += MCU_MEM_SYSRAM_START_ADDRESS +
                                       MEMCTRL->CMI_CODE_BASE_REG;
                    console_printf("         %s:%d\n",
                                   ci->assert_file, (unsigned)ci->assert_line);
                }
            }
            console_printf("  0x%08lx CM_ERROR_REG\n", ci->CM_ERROR_REG);
            console_printf("  0x%08lx CM_EXC_STAT_REG\n", ci->CM_EXC_STAT_REG);
            console_printf("  0x%08lx CM_LL_TIMER1_36_10_REG\n",
                           ci->CM_LL_TIMER1_36_10_REG);
            console_printf("  0x%08lx CM_LL_TIMER1_9_0_REG\n",
                           ci->CM_LL_TIMER1_9_0_REG);

            /* Spin if debugger is connected to CMAC to avoid resetting it */
            if (ci->CM_STAT_REG & 0x20) {
                while (1) {
                }
            }
        }

#if MYNEWT_VAL_CHOICE(BLE_TRANSPORT_HS, uart)
        NVIC_DisableIRQ(CMAC2SYS_IRQn);
        /* Wait until UART is flushed and then assert */
        cmac_host_error_w4flush(NULL);
        return;
#endif

        /* XXX CMAC is in error state, need to recover */
        assert(0);
        return;
    }

    cmac_host_rand_chk_fill();

    os_trace_isr_exit();
}

static void
cmac_host_rand_fill(struct os_event *ev)
{
    size_t num_bytes;
    struct trng_dev *trng;
    uint32_t *rnum;
    uint32_t rnums[g_cmac_shm_config->rand_size];

    /* Check if full */
    if (!cmac_rand_is_active() || cmac_rand_is_full()) {
        return;
    }

    assert(ev->ev_arg != NULL);

    /* Fill buffer with random numbers even though we may not use all of them */
    trng = ev->ev_arg;
    rnum = &rnums[0];
    num_bytes = trng_read(trng, rnum, g_cmac_shm_config->rand_size * sizeof(uint32_t));

    cmac_rand_fill(rnum, num_bytes / 4);
    cmac_host_signal2cmac();
}

static void
cmac_host_rand_chk_fill(void)
{
    if (cmac_rand_is_active() && !cmac_rand_is_full()) {
        os_eventq_put(os_eventq_dflt_get(), &g_cmac_host_rand_ev);
    }
}

static void
cmac_host_lpclk_cb(uint32_t freq)
{
    /* No need to wakeup CMAC if LP clock frequency did not change */
    if (g_cmac_shm_ctrl->lp_clock_freq == freq) {
        return;
    }

    cmac_shm_lock();
    g_cmac_shm_ctrl->lp_clock_freq = freq;
    g_cmac_shm_ctrl->pending_ops |= CMAC_SHM_CB_PENDING_OP_LP_CLK;
    cmac_shm_unlock();

    cmac_host_signal2cmac();
}

static void
shm_init(void)
{
    struct cmac_img_hdr *ih = CMAC_RAM_HDR;

    g_cmac_shm_config = CMAC_CODE_PTR(ih->shm_config);
    g_cmac_shm_ctrl = CMAC_SHARED_PTR(ih->shm_ctrl);
    g_cmac_shm_mbox_s2c = CMAC_SHARED_PTR(ih->shm_mbox_s2c);
    g_cmac_shm_mbox_c2s = CMAC_SHARED_PTR(ih->shm_mbox_c2s);
    g_cmac_shm_trim = CMAC_SHARED_PTR(ih->shm_trim);
    g_cmac_shm_rand = CMAC_SHARED_PTR(ih->shm_rand);
    g_cmac_shm_dcdc = CMAC_SHARED_PTR(ih->shm_dcdc);
    g_cmac_shm_crashinfo = CMAC_SHARED_PTR(ih->shm_crashinfo);
    g_cmac_shm_debugdata = CMAC_SHARED_PTR(ih->shm_debugdata);
}

static void
shm_configure(void)
{
    struct cmac_shm_trim *trim;
    uint32_t *trim_data;

    g_cmac_shm_ctrl->xtal32m_settle_us =
        MYNEWT_VAL(MCU_CLOCK_XTAL32M_SETTLE_TIME_US);

    trim = (struct cmac_shm_trim *)g_cmac_shm_trim;
    trim_data = trim->data;

    trim->rfcu_len =
        da1469x_trimv_group_read(6, trim_data,
                                 g_cmac_shm_config->trim_rfcu_size);
    trim_data += g_cmac_shm_config->trim_rfcu_size;
    trim->rfcu_mode1_len =
        da1469x_trimv_group_read(8, trim_data,
                                 g_cmac_shm_config->trim_rfcu_mode1_size);
    trim_data += g_cmac_shm_config->trim_rfcu_mode1_size;
    trim->rfcu_mode2_len =
        da1469x_trimv_group_read(10, trim_data,
                                 g_cmac_shm_config->trim_rfcu_mode2_size);
    trim_data += g_cmac_shm_config->trim_rfcu_mode2_size;
    trim->synth_len =
        da1469x_trimv_group_read(7, trim_data,
                                 g_cmac_shm_config->trim_synth_size);

#if MYNEWT_VAL(CMAC_DEBUG_HOST_PRINT_ENABLE)
    cmac_host_print_trim("rfcu", trim->rfcu, trim->rfcu_len);
    cmac_host_print_trim("rfcu_mode1", trim->rfcu_mode1, trim->rfcu_mode1_len);
    cmac_host_print_trim("rfcu_mode2", trim->rfcu_mode2, trim->rfcu_mode2_len);
    cmac_host_print_trim("synth", trim->synth, trim->synth_len);
#endif

    g_cmac_shm_dcdc->enabled = DCDC->DCDC_CTRL1_REG & DCDC_DCDC_CTRL1_REG_DCDC_ENABLE_Msk;
    if (g_cmac_shm_dcdc->enabled) {
        g_cmac_shm_dcdc->v18 = DCDC->DCDC_V18_REG;
        g_cmac_shm_dcdc->v18p = DCDC->DCDC_V18P_REG;
        g_cmac_shm_dcdc->vdd = DCDC->DCDC_VDD_REG;
        g_cmac_shm_dcdc->v14 = DCDC->DCDC_V14_REG;
        g_cmac_shm_dcdc->ctrl1 = DCDC->DCDC_CTRL1_REG;
    }
}

#if MYNEWT_VAL_CHOICE(BLE_TRANSPORT_HS, uart)
#if MYNEWT_VAL(BLE_TRANSPORT_UART_PORT) < 0 || MYNEWT_VAL(BLE_TRANSPORT_UART_PORT) > 2
#error Invalid BLE_HCI_UART_PORT
#endif
static void
cmac_host_error_w4flush(struct os_event *ev)
{
    static UART_Type * const regs[] = {
        (void *)UART,
        (void *)UART2,
        (void *)UART3
    };

    if (!ev) {
        /* Move to task context, we do not want to spin in interrupt */
        os_eventq_put(os_eventq_dflt_get(), &g_cmac_host_error_ev);
        return;
    }

    do {
        cmac_mbox_read();
        while ((regs[MYNEWT_VAL(BLE_TRANSPORT_UART_PORT)]->UART_LSR_REG &
                UART_UART_LSR_REG_UART_TEMT_Msk) == 0) {
            /* Wait until both FIFO and shift registers are empty */
        }
    } while (cmac_mbox_has_data());

    /* Reset CMAC */
    CRG_TOP->CLK_RADIO_REG |= CRG_TOP_CLK_RADIO_REG_CMAC_SYNCH_RESET_Msk;

    assert(0);
}
#endif

#if MYNEWT_VAL(CMAC_DEBUG_HOST_PRINT_ENABLE)
static void
cmac_host_print_trim(const char *name, const uint32_t *tv, unsigned len)
{
    console_printf("[CMAC] Trim values for '%s'\n", name);

    while (len) {
        console_printf("       0x%08x = 0x%08x\n", (unsigned)tv[0], (unsigned)tv[1]);
        len -= 2;
        tv += 2;
    }
}
#endif

static void
cmac_load_image(void)
{
    struct cmac_img_hdr *ih = (struct cmac_img_hdr *)_binary_cmac_img_bin_start;
    struct cmac_img_info ii;
    uint32_t img_size;
    uint32_t ram_size;
#if !MYNEWT_VAL(CMAC_IMAGE_SINGLE)
    const struct flash_area *fa;
    int rc;
#endif

    /* Calculate size of image and RAM area */
    img_size = &_binary_cmac_img_bin_end - &_binary_cmac_img_bin_start[0];
    ram_size = &_binary_cmac_ram_bin_end - &_binary_cmac_ram_bin_start[0];

    /* Load image header and check if image can be loaded */
#if MYNEWT_VAL(CMAC_IMAGE_SINGLE)
    memcpy(&ii, &ih->ii, sizeof(ii));
#else
    rc = flash_area_open(FLASH_AREA_IMAGE_1, &fa);
    assert(rc == 0);
    rc = flash_area_read(fa, 128, &ii, sizeof(ii));
    assert(rc == 0);
#endif

    assert(ii.magic == 0xC3AC0001);
    assert(ii.img_size == img_size);
    assert(ii.ram_size <= ram_size);

    /* Setup CMAC memory addresses */
    MEMCTRL->CMI_CODE_BASE_REG = (uint32_t)&_binary_cmac_ram_bin_start;
    MEMCTRL->CMI_DATA_BASE_REG = MEMCTRL->CMI_CODE_BASE_REG + ii.data_offset;
    MEMCTRL->CMI_SHARED_BASE_REG = MEMCTRL->CMI_CODE_BASE_REG + ii.shared_offset;
    MEMCTRL->CMI_END_REG = MEMCTRL->CMI_CODE_BASE_REG + ii.ram_size - 1;

    /* Clear RAM area then copy image */
    memset(&_binary_cmac_ram_bin_start, 0, ram_size);
#if MYNEWT_VAL(CMAC_IMAGE_SINGLE)
    memcpy(&_binary_cmac_ram_bin_start, &_binary_cmac_img_bin_start, img_size);
#else
    rc = flash_area_read(fa, 0, &_binary_cmac_ram_bin_start, img_size);
    assert(rc == 0);
#endif
}

static void
cmac_configure(void)
{
    /* Add PDC entry to wake up CMAC from M33 */
    g_cmac_host_pdc_sys2cmac = da1469x_pdc_add(MCU_PDC_TRIGGER_MAC_TIMER,
                                               MCU_PDC_MASTER_CMAC,
                                               MCU_PDC_EN_XTAL);
    da1469x_pdc_set(g_cmac_host_pdc_sys2cmac);
    da1469x_pdc_ack(g_cmac_host_pdc_sys2cmac);

    /* Add PDC entry to wake up M33 from CMAC, if does not exist yet */
    g_cmac_host_pdc_cmac2sys = da1469x_pdc_find(MCU_PDC_TRIGGER_COMBO,
                                                MCU_PDC_MASTER_M33, 0);
    if (g_cmac_host_pdc_cmac2sys < 0) {
        g_cmac_host_pdc_cmac2sys = da1469x_pdc_add(MCU_PDC_TRIGGER_COMBO,
                                                   MCU_PDC_MASTER_M33,
                                                   MCU_PDC_EN_XTAL);
        da1469x_pdc_set(g_cmac_host_pdc_cmac2sys);
        da1469x_pdc_ack(g_cmac_host_pdc_cmac2sys);
    }

    /* Setup CMAC2SYS interrupt */
    NVIC_SetVector(CMAC2SYS_IRQn, (uint32_t)cmac2sys_isr);
    NVIC_SetPriority(CMAC2SYS_IRQn, MYNEWT_VAL(CMAC_CMAC2SYS_IRQ_PRIORITY));
    NVIC_DisableIRQ(CMAC2SYS_IRQn);
}

static void
cmac_start(void)
{
    /* Enable Radio LDO */
    CRG_TOP->POWER_CTRL_REG |= CRG_TOP_POWER_CTRL_REG_LDO_RADIO_ENABLE_Msk;

    /* Enable CMAC, but keep it in reset */
    CRG_TOP->CLK_RADIO_REG = (1 << CRG_TOP_CLK_RADIO_REG_RFCU_ENABLE_Pos) |
                             (1 << CRG_TOP_CLK_RADIO_REG_CMAC_SYNCH_RESET_Pos) |
                             (0 << CRG_TOP_CLK_RADIO_REG_CMAC_CLK_SEL_Pos) |
                             (1 << CRG_TOP_CLK_RADIO_REG_CMAC_CLK_ENABLE_Pos) |
                             (0 << CRG_TOP_CLK_RADIO_REG_CMAC_DIV_Pos);

#if MYNEWT_VAL(CMAC_DEBUG_SWD_ENABLE)
    /* Enable CMAC debugger */
    CRG_TOP->SYS_CTRL_REG |= 0x40; /* CRG_TOP_SYS_CTRL_REG_CMAC_DEBUGGER_ENABLE_Msk */
#endif

    /* Release CMAC from reset and sync */
    CRG_TOP->CLK_RADIO_REG &= ~CRG_TOP_CLK_RADIO_REG_CMAC_SYNCH_RESET_Msk;

    while (g_cmac_shm_ctrl->magic != CMAC_SHM_CB_MAGIC) {
        /* Wait for CMAC to initialize */
    }
    NVIC_EnableIRQ(CMAC2SYS_IRQn);
}

void
cmac_host_init(void)
{
    struct trng_dev *trng;

    /* Get trng os device */
    trng = (struct trng_dev *) os_dev_open("trng", OS_TIMEOUT_NEVER, NULL);
    assert(trng);
    g_cmac_host_rand_ev.ev_arg = trng;

#if MYNEWT_VAL(CMAC_DEBUG_DIAG_ENABLE)
    cmac_diag_setup_host();
#endif

    cmac_configure();
    cmac_load_image();

    shm_init();
    shm_configure();

    cmac_start();

    da1469x_lpclk_register_cmac_cb(cmac_host_lpclk_cb);

#if MYNEWT_VAL(CMAC_DEBUG_HOST_PRINT_ENABLE) && MYNEWT_VAL(CMAC_DEBUG_DATA_ENABLE)
    /* Trim values are calculated on RF init, so are valid after synced with CMAC */
    console_printf("[CMAC] Calculated trim_val1: 1=0x%08x 2=0x%08x\n",
                   (unsigned)g_cmac_shared_data->debug.trim_val1_tx_1,
                   (unsigned)g_cmac_shared_data->debug.trim_val1_tx_2);
    console_printf("[CMAC] Calculated trim_val2: tx=0x%08x rx=0x%08x\n",
                   (unsigned)g_cmac_shared_data->debug.trim_val2_tx,
                   (unsigned)g_cmac_shared_data->debug.trim_val2_rx);
#endif
}

void
cmac_host_signal2cmac(void)
{
    da1469x_pdc_set(g_cmac_host_pdc_sys2cmac);
}

void
cmac_host_rf_calibrate(void)
{
    cmac_shm_lock();
    g_cmac_shm_ctrl->pending_ops |= CMAC_SHM_CB_PENDING_OP_RF_CAL;
    cmac_shm_unlock();

    cmac_host_signal2cmac();
}
