/*
 * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "unity.h"
#include "soc/soc_caps.h"
#include "esp_private/esp_clk.h"
#include "driver/mcpwm_cap.h"
#include "driver/mcpwm_sync.h"
#include "driver/gpio.h"
#include "test_mcpwm_utils.h"

TEST_CASE("mcpwm_capture_install_uninstall", "[mcpwm]")
{
    printf("install mcpwm capture timers\r\n");
    mcpwm_capture_timer_config_t cap_timer_config = {
        .clk_src = MCPWM_CAPTURE_CLK_SRC_DEFAULT,
    };
    int total_cap_timers = SOC_MCPWM_GROUPS * SOC_MCPWM_CAPTURE_TIMERS_PER_GROUP;
    mcpwm_cap_timer_handle_t cap_timers[total_cap_timers];
    int k = 0;
    for (int i = 0; i < SOC_MCPWM_GROUPS; i++) {
        cap_timer_config.group_id = i;
        for (int j = 0; j < SOC_MCPWM_CAPTURE_TIMERS_PER_GROUP; j++) {
            TEST_ESP_OK(mcpwm_new_capture_timer(&cap_timer_config, &cap_timers[k++]));
        }
        TEST_ESP_ERR(ESP_ERR_NOT_FOUND, mcpwm_new_capture_timer(&cap_timer_config, &cap_timers[0]));
    }

    printf("install mcpwm capture channels\r\n");
    mcpwm_capture_channel_config_t cap_chan_config = {
        .gpio_num = 0,
        .prescale = 2,
        .flags.pos_edge = true,
        .flags.pull_up = true,
    };
    mcpwm_cap_channel_handle_t cap_channels[total_cap_timers][SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER];
    for (int i = 0; i < total_cap_timers; i++) {
        for (int j = 0; j < SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER; j++) {
            TEST_ESP_OK(mcpwm_new_capture_channel(cap_timers[i], &cap_chan_config, &cap_channels[i][j]));
        }
        TEST_ESP_ERR(ESP_ERR_NOT_FOUND, mcpwm_new_capture_channel(cap_timers[i], &cap_chan_config, &cap_channels[i][0]));
    }

    printf("uninstall mcpwm capture channels and timers\r\n");
    for (int i = 0; i < total_cap_timers; i++) {
        for (int j = 0; j < SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER; j++) {
            TEST_ESP_OK(mcpwm_del_capture_channel(cap_channels[i][j]));
        }
        TEST_ESP_OK(mcpwm_del_capture_timer(cap_timers[i]));
    }
}

TEST_MCPWM_CALLBACK_ATTR
static bool test_capture_callback(mcpwm_cap_channel_handle_t cap_channel, const mcpwm_capture_event_data_t *edata, void *user_data)
{
    uint32_t *cap_value = (uint32_t *)user_data;
    if (edata->cap_edge == MCPWM_CAP_EDGE_NEG) {
        cap_value[1] = edata->cap_value;
    } else {
        cap_value[0] = edata->cap_value;
    }
    return false;
}

TEST_CASE("mcpwm_capture_ext_gpio", "[mcpwm]")
{
    printf("install mcpwm capture timer\r\n");
    mcpwm_cap_timer_handle_t cap_timer = NULL;
    mcpwm_capture_timer_config_t cap_timer_config = {
        .clk_src = MCPWM_CAPTURE_CLK_SRC_APB,
        .group_id = 0,
    };
    TEST_ESP_OK(mcpwm_new_capture_timer(&cap_timer_config, &cap_timer));

    const int cap_gpio = 0;
    // put the GPIO into a preset state
    gpio_set_level(cap_gpio, 0);

    printf("install mcpwm capture channel\r\n");
    mcpwm_cap_channel_handle_t pps_channel;
    mcpwm_capture_channel_config_t cap_chan_config = {
        .gpio_num = cap_gpio,
        .prescale = 1,
        .flags.pos_edge = true,
        .flags.neg_edge = true,
        .flags.io_loop_back = true, // so we can use GPIO functions to simulate the external capture signal
        .flags.pull_up = true,
    };
    TEST_ESP_OK(mcpwm_new_capture_channel(cap_timer, &cap_chan_config, &pps_channel));

    printf("install callback for capture channel\r\n");
    mcpwm_capture_event_callbacks_t cbs = {
        .on_cap = test_capture_callback,
    };
    uint32_t cap_value[2] = {0};
    TEST_ESP_OK(mcpwm_capture_channel_register_event_callbacks(pps_channel, &cbs, cap_value));

    printf("enable and start capture timer\r\n");
    TEST_ESP_OK(mcpwm_capture_timer_enable(cap_timer));
    TEST_ESP_OK(mcpwm_capture_timer_start(cap_timer));

    printf("simulate GPIO capture signal\r\n");
    gpio_set_level(cap_gpio, 1);
    vTaskDelay(pdMS_TO_TICKS(100));
    gpio_set_level(cap_gpio, 0);
    vTaskDelay(pdMS_TO_TICKS(100));
    printf("capture value: Pos=%u, Neg=%u\r\n", cap_value[0], cap_value[1]);
    // Capture timer is clocked from APB by default
    uint32_t clk_src_res = esp_clk_apb_freq();
    TEST_ASSERT_UINT_WITHIN(100000, clk_src_res / 10, cap_value[1] - cap_value[0]);

    printf("uninstall capture channel and timer\r\n");
    TEST_ESP_OK(mcpwm_del_capture_channel(pps_channel));
    TEST_ESP_OK(mcpwm_capture_timer_disable(cap_timer));
    TEST_ESP_OK(mcpwm_del_capture_timer(cap_timer));
}

typedef struct {
    uint32_t cap_data[2];
    int cap_data_index;
} test_soft_catch_user_data_t;

TEST_MCPWM_CALLBACK_ATTR
static bool soft_cap_callback(mcpwm_cap_channel_handle_t cap_channel, const mcpwm_capture_event_data_t *data, void *user_data)
{
    test_soft_catch_user_data_t *cbdata = (test_soft_catch_user_data_t *)user_data;
    cbdata->cap_data[cbdata->cap_data_index++] = data->cap_value;
    return false;
}

TEST_CASE("mcpwm_capture_software_catch", "[mcpwm]")
{
    printf("install mcpwm capture timer\r\n");
    mcpwm_cap_timer_handle_t cap_timer = NULL;
    mcpwm_capture_timer_config_t cap_timer_config = {
        .clk_src = MCPWM_CAPTURE_CLK_SRC_DEFAULT,
        .group_id = 0,
    };
    TEST_ESP_OK(mcpwm_new_capture_timer(&cap_timer_config, &cap_timer));

    printf("install mcpwm capture channel\r\n");
    mcpwm_cap_channel_handle_t cap_channel = NULL;
    mcpwm_capture_channel_config_t cap_chan_config = {
        .gpio_num = -1, // don't need any GPIO, we use software to trigger a catch
        .prescale = 2,
    };
    test_soft_catch_user_data_t test_callback_data = {};
    TEST_ESP_OK(mcpwm_new_capture_channel(cap_timer, &cap_chan_config, &cap_channel));

    printf("register event callback for capture channel\r\n");
    mcpwm_capture_event_callbacks_t cbs = {
        .on_cap = soft_cap_callback,
    };
    TEST_ESP_OK(mcpwm_capture_channel_register_event_callbacks(cap_channel, &cbs, &test_callback_data));

    printf("enable and start capture timer\r\n");
    TEST_ESP_OK(mcpwm_capture_timer_enable(cap_timer));
    TEST_ESP_OK(mcpwm_capture_timer_start(cap_timer));

    printf("trigger software catch\r\n");
    TEST_ESP_OK(mcpwm_capture_channel_trigger_soft_catch(cap_channel));
    vTaskDelay(pdMS_TO_TICKS(10));
    TEST_ESP_OK(mcpwm_capture_channel_trigger_soft_catch(cap_channel));
    vTaskDelay(pdMS_TO_TICKS(10));

    // check user data
    TEST_ASSERT_EQUAL(2, test_callback_data.cap_data_index);
    uint32_t delta = test_callback_data.cap_data[1] - test_callback_data.cap_data[0];
    esp_rom_printf("duration=%u ticks\r\n", delta);
    // Capture timer is clocked from APB by default
    uint32_t clk_src_res = esp_clk_apb_freq();
    TEST_ASSERT_UINT_WITHIN(80000, clk_src_res / 100, delta);

    printf("uninstall capture channel and timer\r\n");
    TEST_ESP_OK(mcpwm_capture_timer_disable(cap_timer));
    TEST_ESP_OK(mcpwm_del_capture_channel(cap_channel));
    TEST_ESP_OK(mcpwm_del_capture_timer(cap_timer));
}

TEST_MCPWM_CALLBACK_ATTR
static bool test_capture_after_sync_callback(mcpwm_cap_channel_handle_t cap_channel, const mcpwm_capture_event_data_t *data, void *user_data)
{
    uint32_t *cap_data = (uint32_t *)user_data;
    *cap_data = data->cap_value;
    return false;
}

TEST_CASE("mcpwm_capture_timer_sync_phase_lock", "[mcpwm]")
{
    mcpwm_capture_timer_config_t cap_timer_config = {
        .group_id = 0,
        .clk_src = MCPWM_CAPTURE_CLK_SRC_DEFAULT,
    };
    mcpwm_cap_timer_handle_t cap_timer = NULL;
    TEST_ESP_OK(mcpwm_new_capture_timer(&cap_timer_config, &cap_timer));

    mcpwm_sync_handle_t soft_sync = NULL;
    mcpwm_soft_sync_config_t soft_sync_config = {};
    TEST_ESP_OK(mcpwm_new_soft_sync_src(&soft_sync_config, &soft_sync));

    mcpwm_capture_timer_sync_phase_config_t sync_config = {
        .count_value = 1000,
        .direction = MCPWM_TIMER_DIRECTION_UP,
        .sync_src = soft_sync,
    };
    TEST_ESP_OK(mcpwm_capture_timer_set_phase_on_sync(cap_timer, &sync_config));
    mcpwm_cap_channel_handle_t cap_channel = NULL;
    mcpwm_capture_channel_config_t cap_chan_config = {
        .gpio_num = -1, // don't need any GPIO, we use software to trigger a catch
        .prescale = 1,
    };
    TEST_ESP_OK(mcpwm_new_capture_channel(cap_timer, &cap_chan_config, &cap_channel));

    mcpwm_capture_event_callbacks_t cbs = {
        .on_cap = test_capture_after_sync_callback,
    };
    uint32_t cap_data;
    TEST_ESP_OK(mcpwm_capture_channel_register_event_callbacks(cap_channel, &cbs, &cap_data));

    TEST_ESP_OK(mcpwm_capture_channel_trigger_soft_catch(cap_channel));
    vTaskDelay(pdMS_TO_TICKS(10));
    printf("capture data before sync: %u\r\n", cap_data);

    TEST_ESP_OK(mcpwm_soft_sync_activate(soft_sync));
    TEST_ESP_OK(mcpwm_capture_channel_trigger_soft_catch(cap_channel));
    vTaskDelay(pdMS_TO_TICKS(10));
    printf("capture data after sync: %u\r\n", cap_data);
    TEST_ASSERT_EQUAL(1000, cap_data);
    TEST_ESP_OK(mcpwm_del_capture_channel(cap_channel));
    TEST_ESP_OK(mcpwm_del_capture_timer(cap_timer));
    TEST_ESP_OK(mcpwm_del_sync_src(soft_sync));
}
