/*
 * 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 "cmac_driver/cmac_host.h"
#include "cmac_driver/cmac_shared.h"
#include "cmac_driver/cmac_diag.h"
#include "trng/trng.h"
#if MYNEWT_VAL(CMAC_DEBUG_COREDUMP_ENABLE)
#include "console/console.h"
#endif

/* CMAC data */
extern char _binary_cmac_rom_bin_start[];
extern char _binary_cmac_rom_bin_end;
extern char _binary_cmac_ram_bin_start[];
extern char _binary_cmac_ram_bin_end;

struct cmac_image_info {
    uint32_t magic;
    uint32_t size_rom;
    uint32_t size_ram;
    uint32_t offset_data;
    uint32_t offset_shared;
};

/* 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)
{
#if MYNEWT_VAL(CMAC_DEBUG_COREDUMP_ENABLE)
    volatile struct cmac_coredump *cd = &g_cmac_shared_data->coredump;
    const char *assert_file;
#endif

    os_trace_isr_enter();

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

    cmac_mbox_read();

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

        /* Spin if debugger is connected to CMAC to avoid resetting it */
        if (cd->CM_STAT_REG & 0x20) {
            for (;;);
        }
#endif

#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[CMAC_RAND_BUF_ELEMS];

    /* 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, CMAC_RAND_BUF_ELEMS * 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);
    }
}

#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

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

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

    cmac_shared_lock();
    g_cmac_shared_data->lp_clock_freq = freq;
    g_cmac_shared_data->pending_ops |= CMAC_PENDING_OP_LP_CLK;
    cmac_shared_unlock();

    cmac_host_signal2cmac();
}

#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

void
cmac_host_rf_calibrate(void)
{
    cmac_shared_lock();
    g_cmac_shared_data->pending_ops |= CMAC_PENDING_OP_RF_CAL;
    cmac_shared_unlock();

    cmac_host_signal2cmac();
}

void
cmac_host_init(void)
{
    struct trng_dev *trng;
    struct cmac_image_info ii;
    uint32_t cmac_rom_size;
    uint32_t cmac_ram_size;
#if !MYNEWT_VAL(CMAC_IMAGE_SINGLE)
    const struct flash_area *fa;
    int rc;
#endif
    struct cmac_trim *trim;

    /* 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

#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

    /*
     * Add PDC entry to wake up CMAC from M33
     *
     * XXX if MCU_DEBUG_GPIO_DEEP_SLEEP is enabled on CMAC, this should also
     *     enable PD_COM so CMAC can access GPIOs after wake up
     */
    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);

    /* 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);

    /* Calculate size of ROM and RAM area */
    cmac_rom_size = &_binary_cmac_rom_bin_end - &_binary_cmac_rom_bin_start[0];
    cmac_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, &_binary_cmac_rom_bin_start[128], 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 == 0xC3ACC3AC);
    assert(ii.size_rom == cmac_rom_size);
    assert(ii.size_ram <= cmac_ram_size);

    /* Copy CMAC image to RAM */
#if MYNEWT_VAL(CMAC_IMAGE_SINGLE)
    memset(&_binary_cmac_ram_bin_start, 0xaa, cmac_ram_size);
    memcpy(&_binary_cmac_ram_bin_start, &_binary_cmac_rom_bin_start, ii.size_rom);
#else
    memset(&_binary_cmac_ram_bin_start, 0xaa, cmac_ram_size);
    rc = flash_area_read(fa, 0, &_binary_cmac_ram_bin_start, ii.size_rom);
    assert(rc == 0);
#endif

    /* 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.offset_data;
    MEMCTRL->CMI_SHARED_BASE_REG = MEMCTRL->CMI_CODE_BASE_REG + ii.offset_shared;
    MEMCTRL->CMI_END_REG = MEMCTRL->CMI_CODE_BASE_REG + ii.size_ram - 1;

    /* Initialize shared memory */
    cmac_shared_init();

    trim = (struct cmac_trim *)&g_cmac_shared_data->trim;
    trim->rfcu_len = da1469x_trimv_group_read(6, trim->rfcu, ARRAY_SIZE(trim->rfcu));
    trim->rfcu_mode1_len = da1469x_trimv_group_read(8, trim->rfcu_mode1, ARRAY_SIZE(trim->rfcu_mode1));
    trim->rfcu_mode2_len = da1469x_trimv_group_read(10, trim->rfcu_mode2, ARRAY_SIZE(trim->rfcu_mode2));
    trim->synth_len = da1469x_trimv_group_read(7, trim->synth, ARRAY_SIZE(trim->synth));

#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

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

    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
}
