/*
 * Copyright (c) 2013 - 2014, Freescale Semiconductor, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * o Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * o Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * o Neither the name of Freescale Semiconductor, Inc. nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <string.h>
#include <stdio.h>
#include "device/fsl_device_registers.h"
#include "fsl_debug_uart.h"
#include "fsl_gpio_hal.h"
#include "fsl_port_hal.h"
#include "fsl_pit_driver.h"
#include "fsl_adc_driver.h"
#include "fsl_interrupt_manager.h"
#include "fsl_clock_manager.h"
#include "fsl_os_abstraction.h"
#include "board.h"

/*******************************************************************************
 * Defination
 ******************************************************************************/
#define ADC_INST 0  /*!< ADC instance */
#define MAX_CONSOLE_COL 100 /*!< the console max column size */
#define NR_SAMPLES MAX_CONSOLE_COL /*!< number of samples in one period */
#define FREQ_INPUT 50  /*!< input signal frequency, defult: 50HZ */
#define MAX_AMPLITUDE 32 /*!< print chart for the sampled data */
#define DEBUG_UART_BAUD 115200 /*!< debug uart baudrate */

/*! @brief Define the sparse matrix node for display wave */
typedef struct sparse_node
{
    struct sparse_node *next; /*!< next node */
    uint32_t value; /*!< the sample index */

} sparse_node_t, *sparse_node_ptr;

/*******************************************************************************
 * Variables
 ******************************************************************************/
static sync_object_t adc_result_sync; /*!< sync object for adc convert result */
static uint16_t result; /*!< result from ADC Rn */
static sparse_node_ptr chart_head[MAX_AMPLITUDE]; /*!< sparse matrix head */
static sparse_node_t chart_nodes[NR_SAMPLES]; /*!< sparse matrix nodes */
static uint32_t free_node = 0; /*!< free node slot index for chart_nodes[] */

/*******************************************************************************
 * Code
 ******************************************************************************/

/*!
 * @brief ADC0 interrupt handler for fetching sample data.
 */
static void adc_isr_callback(void)
{
    sync_signal(&adc_result_sync);
}

/*!
 * @brief Initialize the ADC0 for HW trigger.
 *
 * @param instance The ADC instance number
 */
static int32_t init_adc(uint32_t instance)
{
    adc_calibration_param_t adc_cal_param;
    adc_user_config_t adc_cfg =
    {
        .clockSourceMode        = kAdcClockSourceBusClk,
        .clockSourceDividerMode = kAdcClockDivider8,
        .resolutionMode         = kAdcSingleDiff16,
        .referenceVoltageMode   = kAdcVoltageVref,
        .isContinuousEnabled    = false
    };
    adc_extend_config_t adc_ext_cfg =
    {
        .isLowPowerEnabled      = false,
        .isLongSampleEnabled    = false,
        .isHighSpeedEnabled     = false,
        .isAsynClockEnabled     = false,
        .isHwTriggerEnabled     = true,
        .isHwCompareEnabled     = false,
        .isHwCompareGreaterEnabled = false,
        .isHwCompareRangeEnabled= false,
        .hwCompareValue1        = 0U,
        .hwCompareValue2        = 0U,
        .isHwAverageEnabled     = false,
        .isDmaEnabled           = false
    };

    /* do calibration before initialize the ADC */
    if (kStatus_ADC_Success != adc_auto_calibration(instance, &adc_cal_param))
    {
        return -1;
    }

    /* initialize the ADC and its channel. */
    if (kStatus_ADC_Success != adc_init(instance, &adc_cfg))
    {
        return -1;
    }

    /* initialize the ADC's extend configuration. */
    if (kStatus_ADC_Success != adc_init_extend(instance, &adc_ext_cfg))
    {
        return -1;
    }
    /* install the callback */
    adc_register_user_callback_isr(instance, adc_isr_callback);

    return 0;
}

/*!
 * @brief Initialize the PIT for period trigger.
 */
static void init_pit_period(void)
{
    pit_user_config_t pit_init_data = {
        .isInterruptEnabled = false,
        .isTimerChained = false,
        .periodUs = 1000000/FREQ_INPUT/NR_SAMPLES,
    };

    /* clock enable in the pit driver, and disable timer run in debug mode */
    pit_init_module(false);

    /* Init pit timer 0*/
    pit_init_channel(0, &pit_init_data);
}

/*!
 * @brief Reset the sparse matrix
 */
void sparse_reset(void)
{
    memset(chart_head, 0, sizeof(chart_head));
    memset(chart_nodes, 0, sizeof(chart_nodes));
    free_node = 0;
}

/*!
 * @brief insert a node into the sparse matrix
 *
 * @param index The amplitude index
 * @param value The sample count value
 */
void sparse_insert(uint32_t index, uint32_t value)
{
    sparse_node_ptr p = chart_head[index];

    assert(free_node < NR_SAMPLES);

    if (!p)
    {
        chart_head[index] = &chart_nodes[free_node++];
        chart_head[index]->value = value;
    }
    else
    {
        while (p->next != NULL)
        {
            p = p->next;
        }
        p->next = &chart_nodes[free_node++];
        p->next->value = value;
    }
}

/*!
 * @brief Main demo function
 */
void main(void)
{
    uint32_t cnt;
    int32_t col;
    uint16_t max_amp = 0, min_amp = 0xFFFF;
    double ratio;
    adc_channel_config_t adc_chan_cfg =
    {
        .channelId              = (adc_channel_mode_t)BOARD_ADC0_INPUT_CHAN,
        .isDifferentialEnabled  = false,
        .isInterruptEnabled     = true,
        .muxSelect              = kAdcChannelMuxA
    };

    hardware_init();
    dbg_uart_init();

    /* init sync object for result fetch */
    sync_create(&adc_result_sync, 0);

    /* initialize the adc0 */
    if (init_adc(ADC_INST))
    {
        printf("Failed to do the ADC init\n");
        return;
    }
    /* initialize the PIT for HW trigger */
    init_pit_period();
    /* configure the SIM to select trigger source */
    BW_SIM_SOPT7_ADC0TRGSEL(0x4);
    BW_SIM_SOPT7_ADC0ALTTRGEN(0x1);

    /* start timer for hw trigger */
    pit_timer_start(0);
    if (kStatus_ADC_Success != adc_start_conversion(ADC_INST, &adc_chan_cfg))
    {
        printf("Failed to start conversion\n");
        return;
    }

    /* analysis the first period for amplitude range */
    for (cnt = 0; cnt < NR_SAMPLES; cnt++)
    {
        fsl_rtos_status syncStatus;
        do
        {
            syncStatus = sync_poll(&adc_result_sync);
        } while(syncStatus != kSuccess);

        result = adc_get_conversion_value(ADC_INST, &adc_chan_cfg);
        /* getting the result */
        if (max_amp < result)
        {
            max_amp = result;
        }
        if (min_amp > result)
        {
            min_amp = result;
        }
    }

    /* stop the timer and disable ADC conversion */
    pit_timer_stop(0);
    adc_stop_conversion(ADC_INST, &adc_chan_cfg);

    /* reinit sync object for result fetch */
    sync_destroy(&adc_result_sync);
    sync_create(&adc_result_sync, 0);
    /* init the print chart array */
    sparse_reset();

    printf("High Amplitude -> %.2fV\r\n", (double)max_amp/0xFFFFU*3.3);

    ratio = (double)(MAX_AMPLITUDE)/0xFFFFU;

    /* restart the timer and enable ADC conversion */
    pit_timer_start(0);
    if (kStatus_ADC_Success != adc_start_conversion(ADC_INST, &adc_chan_cfg))
    {
        printf("Failed to start conversion\n");
        return;
    }

    for (cnt = 0; cnt < NR_SAMPLES; cnt++)
    {
        fsl_rtos_status syncStatus;
        uint16_t amp;
        double tmp_ratio;

        do
        {
            syncStatus = sync_poll(&adc_result_sync);
        } while(syncStatus != kSuccess);

        result = adc_get_conversion_value(ADC_INST, &adc_chan_cfg);
        /* insert the sample data into the sparse matrix */

        tmp_ratio = (double)result * ratio;
        amp = (uint16_t) tmp_ratio;
        if (amp >= MAX_AMPLITUDE)
        {
            amp = MAX_AMPLITUDE - 1;
        }
        /* fill one samples into sparse matrix */
        sparse_insert(amp, cnt);
    }

    /* print the chart */
    for (col = MAX_AMPLITUDE - 1; col >= 0; col --)
    {
        sparse_node_ptr p = chart_head[col];
        uint32_t last = 0;

        while (p)
        {
            for (; last < p->value; last++)
            {
                printf(" ");
            }
            printf("*");
            p = p->next;
            last++;
        }
        printf("\r\n");
    }

    /* stop pit */
    pit_timer_stop(0);
    /* stop conversion */
    adc_stop_conversion(ADC_INST, &adc_chan_cfg);
    /* disable the adc0 */
    adc_shutdown(ADC_INST);

    printf("Low Amplitude -> %.2fV\r\n", (double)min_amp/0xFFFFU*3.3);
}

/*******************************************************************************
 * EOF
 ******************************************************************************/
