/* -------------------------------------------------------------------------- 
 * Copyright (c) 2013-2016 ARM Limited. All rights reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed 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
 *
 * 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.
 *
 *      Name:    main.c
 *      Purpose: RTX for Kneron
 *
 *---------------------------------------------------------------------------*/

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

#include "cmsis_os2.h"                    // ARM::CMSIS:RTOS2:Keil RTX5
#include "project.h"
#include "kdrv_ddr.h"
#include "kdrv_system.h"
#include "kdrv_uart.h"
#include "kmdw_memory.h"     //for ddr_malloc
#include "kmdw_console.h"
#include "kdrv_cmsis_core.h"

#include <stdlib.h>
#include "kdrv_gdma.h"
#include "kdrv_pwm.h"

typedef enum
{
    TEST_GDMA_KDP_MEMCPY,
    TEST_GDMA_KDP_MEMCPY_ASYNC,
    TEST_GDMA_KDP_TRANSFER,
    TEST_GDMA_KDP_TRANSFER_ASYNC,
    TEST_SPECIFIED_DMA_CHANNEL,
} GDMA_TEST_ITEM;

volatile static int dma_waiting = 1;

void dma_cb(kdrv_status_t status, void *arg)
{
    if (status != KDRV_STATUS_OK)
        kmdw_printf("kdrv_gdma_memcpy_async() failed\n");

    dma_waiting = 0;
}

static int one_dma_memcpy_test(uint32_t dstAddr, uint32_t srcAddr, uint32_t size,
                               uint32_t loop, uint32_t test_item, int ch)
{

    kdrv_pwmtimer_initialize(PWMTIMER1, PWMTMR_1MSEC_PERIOD);

    memset((void *)srcAddr, 0, size);
    memset((void *)dstAddr, 0, size);

    for (int i = 0; i < size; i++)
    {
        *(uint8_t *)(srcAddr + i) = (rand() % 0XFF);
    }

    uint32_t tick_start, tick_end;

    if (test_item == TEST_GDMA_KDP_MEMCPY)
    {
        tick_start = kdrv_current_t1_tick();

        for (uint32_t i = 0; i < loop; i++)
        {
            kdrv_status_t dma_sts = kdrv_gdma_memcpy(dstAddr, srcAddr, size);
            //kdrv_status_t dma_sts = kdrv_gdma_transfer(3, dstAddr, srcAddr, size);
            if (dma_sts != KDRV_STATUS_OK)
                kmdw_printf("kdrv_gdma_memcpy() failed at dst 0x%x src 0x%s loop %d\n", dstAddr, srcAddr, loop);
            //memcpy((void *)dstAddr, (void *)srcAddr, size);
        }

        tick_end = kdrv_current_t1_tick();
    }
    else if (test_item == TEST_GDMA_KDP_MEMCPY_ASYNC)
    {
        tick_start = kdrv_current_t1_tick();

        for (uint32_t i = 0; i < loop; i++)
        {
            dma_waiting = 1;
            kdrv_status_t dma_sts = kdrv_gdma_memcpy_async(dstAddr, srcAddr, size, dma_cb, NULL);
            if (dma_sts != KDRV_STATUS_OK)
                kmdw_printf("kdrv_gdma_memcpy_async() failed at dst 0x%x src 0x%s loop %d\n", dstAddr, srcAddr, loop);
            while (dma_waiting)
                ;
        }

        tick_end = kdrv_current_t1_tick();
    }
    else if (test_item == TEST_GDMA_KDP_TRANSFER)
    {
        kdrv_gdma_handle_t dma_handle;
        kdrv_status_t sts = kdrv_gdma_acquire_handle(&dma_handle);

        if(sts != KDRV_STATUS_OK)
        {
            kmdw_printf("acquire GDMA handle failed\n");
            return -1;
        }

        gdma_setting_t dma_setting;

        dma_setting.dst_width = GDMA_TXFER_WIDTH_32_BITS;
        dma_setting.src_width = GDMA_TXFER_WIDTH_32_BITS;
        dma_setting.burst_size = GDMA_BURST_SIZE_16;
        dma_setting.dst_addr_ctrl = GDMA_INCREMENT_ADDRESS;
        dma_setting.src_addr_ctrl = GDMA_INCREMENT_ADDRESS;
        dma_setting.dma_mode = GDMA_NORMAL_MODE;
        dma_setting.dma_dst_req = 0;
        dma_setting.dma_src_req = 0;

        kdrv_gdma_configure_setting(dma_handle, &dma_setting);

        tick_start = kdrv_current_t1_tick();

        for (uint32_t i = 0; i < loop; i++)
        {
            kdrv_status_t dma_sts = kdrv_gdma_transfer(dma_handle, dstAddr, srcAddr, size);
            if (dma_sts != KDRV_STATUS_OK)
                kmdw_printf("kdrv_gdma_transfer() failed at dst 0x%x src 0x%s loop %d\n", dstAddr, srcAddr, loop);
        }

        tick_end = kdrv_current_t1_tick();

        kdrv_gdma_release_handle(dma_handle);
    }
    else if (test_item == TEST_GDMA_KDP_TRANSFER_ASYNC)
    {
        kdrv_gdma_handle_t dma_handle;
        kdrv_status_t sts = kdrv_gdma_acquire_handle(&dma_handle);

        if(sts != KDRV_STATUS_OK)
        {
            kmdw_printf("acquire GDMA handle failed\n");
            return -1;
        }

        gdma_setting_t dma_setting;

        dma_setting.dst_width = GDMA_TXFER_WIDTH_32_BITS;
        dma_setting.src_width = GDMA_TXFER_WIDTH_32_BITS;
        dma_setting.burst_size = GDMA_BURST_SIZE_16;
        dma_setting.dst_addr_ctrl = GDMA_INCREMENT_ADDRESS;
        dma_setting.src_addr_ctrl = GDMA_INCREMENT_ADDRESS;
        dma_setting.dma_mode = GDMA_NORMAL_MODE;
        dma_setting.dma_dst_req = 0;
        dma_setting.dma_src_req = 0;

        kdrv_gdma_configure_setting(dma_handle, &dma_setting);

        tick_start = kdrv_current_t1_tick();

        for (uint32_t i = 0; i < loop; i++)
        {
            dma_waiting = 1;
            kdrv_status_t dma_sts = kdrv_gdma_transfer_async(dma_handle, dstAddr, srcAddr, size, dma_cb, NULL);
            if (dma_sts != KDRV_STATUS_OK)
                kmdw_printf("kdrv_gdma_memcpy_async() failed at dst 0x%x src 0x%s loop %d\n", dstAddr, srcAddr, loop);
            while (dma_waiting)
                ;
        }

        tick_end = kdrv_current_t1_tick();

        kdrv_gdma_release_handle(dma_handle);
    }
    else if (test_item == TEST_SPECIFIED_DMA_CHANNEL)
    {
        kdrv_gdma_handle_t dma_handle = ch;
        gdma_setting_t dma_setting;

        dma_setting.dst_width = GDMA_TXFER_WIDTH_32_BITS;
        dma_setting.src_width = GDMA_TXFER_WIDTH_32_BITS;
        dma_setting.burst_size = GDMA_BURST_SIZE_16;
        dma_setting.dst_addr_ctrl = GDMA_INCREMENT_ADDRESS;
        dma_setting.src_addr_ctrl = GDMA_INCREMENT_ADDRESS;
        dma_setting.dma_mode = GDMA_NORMAL_MODE;
        dma_setting.dma_dst_req = 0;
        dma_setting.dma_src_req = 0;

        kdrv_gdma_configure_setting(dma_handle, &dma_setting);

        tick_start = kdrv_current_t1_tick();

        for (uint32_t i = 0; i < loop; i++)
        {
            kdrv_status_t dma_sts = kdrv_gdma_transfer(dma_handle, dstAddr, srcAddr, size);
            if (dma_sts != KDRV_STATUS_OK)
                kmdw_printf("kdrv_gdma_transfer() failed at dst 0x%x src 0x%s loop %d\n", dstAddr, srcAddr, loop);
        }

        tick_end = kdrv_current_t1_tick();
    }

    int cmp_sts = memcmp((const void *)srcAddr, (const void *)dstAddr, size);

    float speed = (float)((size * loop) >> 10) / (tick_end - tick_start);

    kdrv_pwmtimer_close(PWMTIMER1);

    kmdw_printf("[memcpy] tid 0x%x copy 0x%x to 0x%x %d bytes x %d, spent %u ms speed: %1.f MB/s ... %s\n",
                osThreadGetId(), srcAddr, dstAddr, size, loop, (tick_end - tick_start), speed, (cmp_sts == 0) ? "PASSED" : "FAILED");

    return (cmp_sts == 0);
}

static void test_dma_functions()
{

    kmdw_printf("\n================= [%s context] =================\n\n", osThreadGetId() == 0 ? "Non-OS" : "OS");

    const uint32_t size_begin = 512;
    const uint32_t count_begin = 32768 / 2;

    {
        kmdw_printf("[ kdrv_gdma_memcpy() tests ]\n");

        uint32_t dstAddr = 0x62000000;
        uint32_t srcAddr = 0x62800000;

        int pass_count = 0;

        kmdw_printf("\nAaddress and transfer size are not divisiable by 4:\n");

        for (int i = 0; i < 4; i++)
            pass_count += one_dma_memcpy_test(dstAddr + i, srcAddr + i, (8 * 1024) + i, 1000, TEST_GDMA_KDP_MEMCPY, 0);

        kmdw_printf("\nDifferent transfer size:\n");

        uint32_t size = size_begin;
        uint32_t count = count_begin;
        for (int i = 0; i < 15; i++)
        {
            pass_count += one_dma_memcpy_test(dstAddr, srcAddr, size, count, TEST_GDMA_KDP_MEMCPY, 0);
            size *= 2;
            count /= 2;
        }

        kmdw_printf("Passed: %d, Failed: %d\n", pass_count, 19 - pass_count);
    }

    {
        uint32_t dstAddr = 0x62000000;
        uint32_t srcAddr = 0x62800000;

        int pass_count = 0;

        kmdw_printf("\n");
        kmdw_printf("[ kdrv_gdma_memcpy_async() tests ]\n");

        kmdw_printf("\nDifferent transfer size:\n");

        uint32_t size = size_begin;
        uint32_t count = count_begin;

        for (int i = 0; i < 15; i++)
        {
            pass_count += one_dma_memcpy_test(dstAddr, srcAddr, size, count, TEST_GDMA_KDP_MEMCPY_ASYNC, 0);
            size *= 2;
            count /= 2;
        }

        kmdw_printf("Passed: %d, Failed: %d\n", pass_count, 15 - pass_count);
    }

    {
        uint32_t dstAddr = 0x62000000;
        uint32_t srcAddr = 0x62800000;

        int pass_count = 0;

        kmdw_printf("\n");
        kmdw_printf("[ kdrv_gdma_transfer() tests ]\n");

        kmdw_printf("\nDifferent transfer size:\n");

        uint32_t size = size_begin;
        uint32_t count = count_begin;

        for (int i = 0; i < 15; i++)
        {
            pass_count += one_dma_memcpy_test(dstAddr, srcAddr, size, count, TEST_GDMA_KDP_TRANSFER, 0);
            size *= 2;
            count /= 2;
        }

        kmdw_printf("Passed: %d, Failed: %d\n", pass_count, 15 - pass_count);
    }

    {
        uint32_t dstAddr = 0x62000000;
        uint32_t srcAddr = 0x62800000;

        int pass_count = 0;

        kmdw_printf("\n");
        kmdw_printf("[ kdrv_gdma_transfer_async() tests ]\n");

        kmdw_printf("\nDifferent transfer size:\n");

        uint32_t size = size_begin;
        uint32_t count = count_begin;

        for (int i = 0; i < 15; i++)
        {
            pass_count += one_dma_memcpy_test(dstAddr, srcAddr, size, count, TEST_GDMA_KDP_TRANSFER_ASYNC, 0);
            size *= 2;
            count /= 2;
        }

        kmdw_printf("Passed: %d, Failed: %d\n", pass_count, 15 - pass_count);
    }

    {
        uint32_t dstAddr = 0x62000000;
        uint32_t srcAddr = 0x62800000;

        int pass_count = 0;

        kmdw_printf("\n");
        kmdw_printf("[ kdrv_gdma_transfer() test all 8 DMA channels ]\n");

        uint32_t size = 131072;
        uint32_t count = 4000;

        for (int i = 0; i < 8; i++)
        {
            kmdw_printf("Testing DMA CH %d:\n", i);
            pass_count += one_dma_memcpy_test(dstAddr, srcAddr, size, count, TEST_SPECIFIED_DMA_CHANNEL, i);
        }

        kmdw_printf("Passed: %d, Failed: %d\n", pass_count, 8 - pass_count);
    }

    kmdw_printf("All GDMA tests are done !!\n");
}

static void test_gdma_thread(void *arg)
{
    test_dma_functions();
}

/**
 * @brief main, main dispatch function
 */
int main(void)
{
    SystemCoreClockUpdate();            // System Initialization 
    osKernelInitialize();               // Initialize CMSIS-RTOS


    /* below is some primiary system init settings */
    kdrv_system_init();	// primary system init
    kdrv_system_init_ncpu(); // this is a must for ddr
    kdrv_ddr_system_init(DDR_INIT_ALL); // enable ddr


    /* below is some middleware init settings */
    kmdw_ddr_init(DDR_HEAP_BEGIN, DDR_HEAP_END); // for memory alloc from ddr
    kdrv_uart_initialize(); // for log printing
    kdrv_uart_console_init(MSG_PORT, MSG_PORT_BAUDRATE, NULL);   // enable console mdw for printing

    
    // init GDMA resources
    kdrv_gdma_initialize();

    // test GDMA functions in non-OS context
    test_dma_functions();

    // create a thread to test GDMA functions in OS context
    osThreadNew(test_gdma_thread, NULL, NULL);

    //application is triggered in host_com.c
    if (osKernelGetState() == osKernelReady)
    {
        osKernelStart();
    }

    while (1)
    {
    }
}
