/******************************************************************************
* File Name:   dev_capsense.c
*
* Description:
*   This file implements the interface with CapSense button and slider.
*
* Related Document: See README.md
*
*******************************************************************************
* Copyright 2024-2025, Cypress Semiconductor Corporation (an Infineon company) or
* an affiliate of Cypress Semiconductor Corporation.  All rights reserved.
*
* This software, including source code, documentation and related
* materials ("Software") is owned by Cypress Semiconductor Corporation
* or one of its affiliates ("Cypress") and is protected by and subject to
* worldwide patent protection (United States and foreign),
* United States copyright laws and international treaty provisions.
* Therefore, you may use this Software only as provided in the license
* agreement accompanying the software package from which you
* obtained this Software ("EULA").
* If no EULA applies, Cypress hereby grants you a personal, non-exclusive,
* non-transferable license to copy, modify, and compile the Software
* source code solely for use in connection with Cypress's
* integrated circuit products.  Any reproduction, modification, translation,
* compilation, or representation of this Software except as specified
* above is prohibited without the express written permission of Cypress.
*
* Disclaimer: THIS SOFTWARE IS PROVIDED AS-IS, WITH NO WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT, IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Cypress
* reserves the right to make changes to the Software without notice. Cypress
* does not assume any liability arising out of the application or use of the
* Software or any product or circuit described in the Software. Cypress does
* not authorize its products for use in any products where a malfunction or
* failure of the Cypress product may reasonably be expected to result in
* significant property damage, injury or death ("High Risk Product"). By
* including Cypress's product in a High Risk Product, the manufacturer
* of such system or application assumes all risk of such use and in doing
* so agrees to indemnify Cypress against all liability.
*******************************************************************************/

#include "cy_pdl.h"
#include "protocol/protocol.h"
#include "protocol/pb_encode.h"
#include "common.h"
#include "dev_capsense.h"
#include "cycfg_capsense.h"

/*******************************************************************************
* Macros
*******************************************************************************/
#define CAPSENSE_OPTION_KEY_TYPE 1

#define SYSTICK_MAX_CNT_BITS 24u
#define SYSTICK_MAX_CNT        (1<<SYSTICK_MAX_CNT_BITS)-1
#define CAPSENSE_MSC0_INTR_PRIORITY      (3u)
#define MSC_CAPSENSE_WIDGET_INACTIVE     (0u)

/* EZI2C interrupt priority must be higher than CapSense interrupt. */
#define EZI2C_INTR_PRIORITY              (2u)

/*******************************************************************************
* Global Variables
*******************************************************************************/

cy_stc_scb_ezi2c_context_t ezi2c_context;

/*******************************************************************************
* Types
*******************************************************************************/
typedef struct {
    float data_buffer[9];
    int dropped;
    uint32_t sample_time_tick;
    uint32_t period_tick;
    int type;
} dev_capsense_t;

/*******************************************************************************
* Function Prototypes
*******************************************************************************/
static bool _init_hw(dev_capsense_t* dcapsense);
static bool _config_hw(dev_capsense_t* dcapsense, int rate);

static bool _configure_streams(protocol_t* protocol, int device, void* arg);
static void _start_streams(protocol_t* protocol, int device, pb_ostream_t* ostream, void* arg);
static void _stop_streams(protocol_t* protocol, int device, pb_ostream_t* ostream, void* arg);
static bool _write_payload(protocol_t* protocol, int device_id, int stream_id, int frame_count, int total_bytes, pb_ostream_t* ostream, void* arg);
static void _poll_streams(protocol_t* protocol, int device, pb_ostream_t* ostream, void* arg);

static void initialize_capsense(void);
static void capsense_msc0_isr(void);
static void ezi2c_isr(void);
static void initialize_capsense_tuner(void);

/*******************************************************************************
* Function Definitions
*******************************************************************************/

/*******************************************************************************
* Function Name: _init_hw
********************************************************************************
* Summary:
*    A function used to initialize the CapSense periphery.
*
* Parameters:
*  dcapsense: Pointer to the device struct (dev_capsense_t)
*
* Return:
*   True if initialization is successful, otherwise false.
*
*******************************************************************************/
static bool _init_hw(dev_capsense_t* dcapsense)
{
	Cy_SysTick_Init(CY_SYSTICK_CLOCK_SOURCE_CLK_CPU, SYSTICK_MAX_CNT);

	/* Initialize EZI2C */
	initialize_capsense_tuner();

	/* Initialize MSC CapSense */
	initialize_capsense();

    return true;
}

/*******************************************************************************
* Function Name: _config_hw
********************************************************************************
* Summary:
*    A function used to configure the CapSense periphery.
*
* Parameters:
*   dcapsense: Pointer to the device struct (dev_capsense_t)
*   rate: Sampling rate
*
* Return:
*   True if configuration is successful, otherwise false.
*
*******************************************************************************/
static bool _config_hw(dev_capsense_t* dcapsense, int rate)
{
    /* Clear data */
    memset(&dcapsense->data_buffer, 0, sizeof(float) * 2);

    /* Update sample period */
    dcapsense->sample_time_tick = 0;
    dcapsense->period_tick = CY_SYSTICK_CLOCK_SOURCE_CLK_CPU / rate;

    return true;
}

/*******************************************************************************
* Function Name: _configure_streams
********************************************************************************
* Summary:
*  Called when the device stream is configured or re-configured.
*
* Parameters:
*  protocol: pointer the protocol handle
*  device: the device index
*  arg: pointer the device struct (dev_capsense_t)
*
*  Return:
*    True to keep the connection open, false to close.
*
*******************************************************************************/
static bool _configure_streams(protocol_t* protocol, int device, void* arg)
{
    int type_index;
    int rate;
    int dsize;
    int status;
    dev_capsense_t* dcapsense = (dev_capsense_t*)arg;

    status = protocol_get_option_oneof(protocol, device, CAPSENSE_OPTION_KEY_TYPE, &type_index);
    if(status != PROTOCOL_STATUS_SUCCESS) {
        protocol_set_device_status(
                protocol,
                device,
                protocol_DeviceStatus_DEVICE_STATUS_ERROR,
                "Failed to get option frequency.");
        return true;
    }

    switch(type_index) {
        case 0:
        	rate = 500;
        	dsize = 2;
        	break;
        case 1:
        	rate = 500;
        	dsize = 9;
        	break;
        case 2:
        	rate = 50;
        	dsize = 3;
        	break;
        default: return false;
   }

    if(protocol_clear_streams(protocol, device) != PROTOCOL_STATUS_SUCCESS) {
        protocol_set_device_status(
                protocol,
                device,
                protocol_DeviceStatus_DEVICE_STATUS_ERROR,
                "Failed to clear streams.");
        return true;
    }

    int stream = protocol_add_stream(
        protocol,
        device,
        "Data",
        protocol_StreamDirection_STREAM_DIRECTION_OUTPUT,
        protocol_DataType_DATA_TYPE_F32,
        rate,
        1,
        NULL);

    if(stream < 0) {
        protocol_set_device_status(
                protocol,
                device,
                protocol_DeviceStatus_DEVICE_STATUS_ERROR,
                "Failed to add streams.");
        return true;
    }

    status = protocol_add_stream_rank(
            protocol,
            device,
            stream,
            "Channel",
            dsize,
            NULL);
    if(status != PROTOCOL_STATUS_SUCCESS) {
            protocol_set_device_status(
                    protocol,
                    device,
                    protocol_DeviceStatus_DEVICE_STATUS_ERROR,
                    "Failed to add stream dimension.");
            return true;
        }

    if(!_config_hw(dcapsense, rate)) {
        protocol_set_device_status(
                protocol,
                device,
                protocol_DeviceStatus_DEVICE_STATUS_ACTIVE,
                "Failed to configure device");
    } else {
        protocol_set_device_status(
                protocol,
                device,
                protocol_DeviceStatus_DEVICE_STATUS_READY,
                "Device is ready.");
    }
    return true;
}

/*******************************************************************************
* Function Name: _start_streams
********************************************************************************
* Summary:
*  Called when streaming is started. This may also initialize the device.
*
* Parameters:
*  protocol: pointer the protocol handle
*  device: the device index
*  ostream: Pointer to the output stream to write to
*  arg: pointer the device struct (dev_capsense_t)
*
*******************************************************************************/
static void _start_streams(protocol_t* protocol, int device, pb_ostream_t* ostream, void* arg)
{
    UNUSED(ostream);

    dev_capsense_t* dcapsense = (dev_capsense_t*)arg;
    uint32_t time = Cy_SysTick_GetValue();

    protocol_get_option_oneof(protocol, device, CAPSENSE_OPTION_KEY_TYPE, &dcapsense->type);

    protocol_set_device_status(
            protocol,
            device,
            protocol_DeviceStatus_DEVICE_STATUS_ACTIVE,
            "Device is streaming");

    /* Initialize this as the start time. One sample period will pass */
    /* before the first sample is taken. */
    dcapsense->sample_time_tick = time;
}

/*******************************************************************************
* Function Name: _stop_streams
********************************************************************************
* Summary:
*  Called when streaming is stopped.
*
* Parameters:
*  protocol: pointer the protocol handle
*  device: the device index
*  ostream: Pointer to the output stream to write to
*  arg: pointer the device struct (dev_capsense_t)
*
*******************************************************************************/
static void _stop_streams(protocol_t* protocol, int device, pb_ostream_t* ostream, void* arg)
{
    UNUSED(arg);
    UNUSED(ostream);

    protocol_set_device_status(
            protocol,
            device,
            protocol_DeviceStatus_DEVICE_STATUS_READY,
            "Device stopped");
}

/*******************************************************************************
* Function Name: _write_payload
********************************************************************************
* Summary:
*  Used by protocol_send_data_chunk to write the actual data.
*
* Parameters:
*  protocol: pointer the protocol handle
*  stream_id: the device index
*  stream_id: the stream index
*  frame_count: number of frames to write
*  total_bytes: total number of bytes to write (= frame_count * sizeof(type) * frame_shape.flat)
*  ostream: pointer to the output stream to write to
*  arg: pointer the device struct (dev_capsense_t)
*
*******************************************************************************/
static bool _write_payload(
    protocol_t* protocol,
    int device_id,
    int stream_id,
    int frame_count,
    int total_bytes,
    pb_ostream_t* ostream,
    void* arg)
{
    UNUSED(protocol);
    UNUSED(stream_id);
    UNUSED(frame_count);
    UNUSED(protocol);

    dev_capsense_t* dcapsense = (dev_capsense_t*)arg;

    if (!pb_write(ostream, (const pb_byte_t *)dcapsense->data_buffer, total_bytes))
    {
        return false;
    }

    return true;
}

/*******************************************************************************
* Function Name: _poll_streams
********************************************************************************
* Summary:
*  Called periodically to send data messages when the device is active.
*
* Parameters:
*  protocol: pointer the protocol handle
*  device: the device index
*  ostream: pointer to the output stream to write to
*  arg: pointer the device struct (dev_capsense_t)
*
*******************************************************************************/
static void _poll_streams(protocol_t* protocol, int device, pb_ostream_t* ostream, void* arg)
{
    dev_capsense_t* dcapsense = (dev_capsense_t*)arg;
    uint32_t time = Cy_SysTick_GetValue();
    static bool gesture_flag = false;
    float gest_x, gest_dir, gest_stat;

	Cy_CapSense_ScanAllSlots(&cy_capsense_context);
	while (CY_CAPSENSE_NOT_BUSY != Cy_CapSense_IsBusy(&cy_capsense_context)) {}
	/* Process all widgets */
	Cy_CapSense_ProcessAllWidgets(&cy_capsense_context);

	switch (dcapsense->type) {
	case 0:
		/* Button signals */
		dcapsense->data_buffer[0] = cy_capsense_context.ptrWdConfig[CY_CAPSENSE_BUTTON0_WDGT_ID].ptrSnsContext[0u].diff;
		dcapsense->data_buffer[1] = cy_capsense_context.ptrWdContext[CY_CAPSENSE_BUTTON0_WDGT_ID].status - 30;
		break;
	case 1:
		/* Slider position signals */
		if (cy_capsense_context.ptrWdContext[CY_CAPSENSE_LINEARSLIDER0_WDGT_ID].status-30 == 1 ){
			for (int idx_i = 0; idx_i < 8; idx_i++) {
				dcapsense->data_buffer[idx_i] = cy_capsense_context.ptrWdConfig[CY_CAPSENSE_LINEARSLIDER0_WDGT_ID].ptrSnsContext[idx_i].diff;
			}
			dcapsense->data_buffer[8] = cy_capsense_context.ptrWdContext[CY_CAPSENSE_LINEARSLIDER0_WDGT_ID].wdTouch.ptrPosition[0u].x;
		}
		break;
	case 2:
		/* Slider gesture signals */
		Cy_CapSense_DecodeWidgetGestures(CY_CAPSENSE_LINEARSLIDER0_WDGT_ID, &cy_capsense_context);
		Cy_SysLib_Delay(13); //to allign CapSense signals sampling rate during data collection and gesture recognition
		gest_x = cy_capsense_context.ptrWdContext[CY_CAPSENSE_LINEARSLIDER0_WDGT_ID].wdTouch.ptrPosition[0u].x;
		gest_dir = cy_capsense_context.ptrWdContext[CY_CAPSENSE_LINEARSLIDER0_WDGT_ID].gestureDirection;
		gest_stat =  cy_capsense_context.ptrWdContext[CY_CAPSENSE_LINEARSLIDER0_WDGT_ID].status-30;
		if ((gesture_flag == false) || (gest_dir > 0)) {
			dcapsense->data_buffer[0] = gest_x;
			dcapsense->data_buffer[1] = gest_dir;
			dcapsense->data_buffer[2] = gest_stat;
		}
		if (gest_dir > 0) {
			gesture_flag = true;
		}
		break;
	}

    if(time >= dcapsense->sample_time_tick + dcapsense->period_tick ) {
	    /* dcapsense->sample_time_tick = time;
	     * Use a fixed calculation for when this tick should have happened.
	     * This also mean that the dcapsense->sample_time_tick need to be initialized
	     * whenever a new streaming is started. */
	    dcapsense->sample_time_tick += dcapsense->period_tick;
		gesture_flag = false;

        protocol_send_data_chunk(protocol, device, 0, 1, dcapsense->dropped, ostream, _write_payload);
        dcapsense->dropped = 0;
    }
}

/*******************************************************************************
* Function Name: dev_capsense_register
********************************************************************************
* Summary:
*  Registers this device. This is the only exported symbol from this object.
*
* Parameters:
*  protocol: Pointer to the protocol handle.
*
* Returns:
*  True on success, else false.
*
*******************************************************************************/
bool dev_capsense_register(protocol_t* protocol)
{
    int status;

    dev_capsense_t* dcapsense = (dev_capsense_t*)malloc(sizeof(dev_capsense_t));
    if(dcapsense == NULL)
    {
        return false;
    }
    memset(dcapsense, 0, sizeof(dev_capsense_t));

    /* Initialize the dcapsense device with the provided I2C interface */
    if(!_init_hw(dcapsense))
    {
        free(dcapsense);
        return false;
    }

    device_manager_t manager = {
        .arg = dcapsense,
        .configure_streams = _configure_streams,
        .start = _start_streams,
        .stop = _stop_streams,
        .poll = _poll_streams,
        .data_received = NULL /* has no input streams */
    };

    /* Add the dcapsense device to the protocol and get its device identifier */
    int device = protocol_add_device(
        protocol,
        protocol_DeviceType_DEVICE_TYPE_SENSOR,
        "CapSense",
        "Button or Slider (CapSense)",
        manager);

    if(device < 0)
    {
        return false;
    }

    /* Add an option to configure the sampling frequency of the dcapsense device */
    status = protocol_add_option_oneof(
        protocol,
        device,
		CAPSENSE_OPTION_KEY_TYPE,
        "Stream Type",
        "Type of data to stream.",
        0,
        (const char* []) { "Button", "Slider", "Gesture" },
        3);

    if(status != PROTOCOL_STATUS_SUCCESS)
    {
        return false;
    }

    /* Configure the data streams for the dcapsense device using the protocol */
    if(!_configure_streams(protocol, device, manager.arg))
    {
        return false;
    }

    return true;
}

/*******************************************************************************
* Function Name: initialize_capsense
********************************************************************************
* Summary:
*  This function initializes the CapSense blocks and configures the CapSense
*  interrupt.
*
*******************************************************************************/
static void initialize_capsense(void)
{
    cy_capsense_status_t status = CY_CAPSENSE_STATUS_SUCCESS;

    /* CapSense interrupt configuration MSC 0 */
    const cy_stc_sysint_t capsense_msc0_interrupt_config =
    {
        .intrSrc = CY_MSC0_IRQ,
        .intrPriority = CAPSENSE_MSC0_INTR_PRIORITY,
    };

    /* Capture the MSC HW block and initialize it to the default state. */
    status = Cy_CapSense_Init(&cy_capsense_context);

    if (status != CY_CAPSENSE_STATUS_SUCCESS)
    {
        /* CapSense initialization failed, the middleware may not operate
         * as expected, and repeating of initialization is required.*/
    	halt_error(LED_CODE_SENSOR_CAPSENSE_ERROR);
    }

    if (CY_CAPSENSE_STATUS_SUCCESS == status)
    {
        /* Initialize CapSense interrupt for MSC 0 */
        Cy_SysInt_Init(&capsense_msc0_interrupt_config, capsense_msc0_isr);
        NVIC_ClearPendingIRQ(capsense_msc0_interrupt_config.intrSrc);
        NVIC_EnableIRQ(capsense_msc0_interrupt_config.intrSrc);

        /* Initialize the CapSense firmware modules. */
        status = Cy_CapSense_Enable(&cy_capsense_context);
    }

    if(status != CY_CAPSENSE_STATUS_SUCCESS)
    {
        /* This status could fail before tuning the sensors correctly.
         * Ensure that this function passes after the CapSense sensors are tuned
         * as per procedure give in the Readme.md file */
    	halt_error(LED_CODE_SENSOR_CAPSENSE_ERROR);
    }
}

/*******************************************************************************
* Function Name: capsense_msc0_isr
********************************************************************************
* Summary:
*  Wrapper function for handling interrupts from CapSense MSC0 block.
*
*******************************************************************************/
static void capsense_msc0_isr(void)
{
    Cy_CapSense_InterruptHandler(CY_MSC0_HW, &cy_capsense_context);
}

/*******************************************************************************
* Function Name: initialize_capsense_tuner
********************************************************************************
* Summary:
* EZI2C module to communicate with the CapSense Tuner tool.
*
*******************************************************************************/
static void initialize_capsense_tuner(void)
{
    cy_en_scb_ezi2c_status_t status = CY_SCB_EZI2C_SUCCESS;

    /* EZI2C interrupt configuration structure */
    const cy_stc_sysint_t ezi2c_intr_config =
    {
        .intrSrc = CYBSP_EZI2C_IRQ,
        .intrPriority = EZI2C_INTR_PRIORITY,
    };

    /* Initialize the EzI2C firmware module */
    status = Cy_SCB_EZI2C_Init(CYBSP_EZI2C_HW, &CYBSP_EZI2C_config, &ezi2c_context);

    if(status != CY_SCB_EZI2C_SUCCESS)
    {
    	halt_error(LED_CODE_SENSOR_CAPSENSE_ERROR);
    }

    Cy_SysInt_Init(&ezi2c_intr_config, ezi2c_isr);
    NVIC_EnableIRQ(ezi2c_intr_config.intrSrc);

    /* Set the CapSense data structure as the I2C buffer to be exposed to the
     * master on primary slave address interface. Any I2C host tools such as
     * the Tuner or the Bridge Control Panel can read this buffer but you can
     * connect only one tool at a time.
     */
    Cy_SCB_EZI2C_SetBuffer1(CYBSP_EZI2C_HW, (uint8_t *)&cy_capsense_tuner,
                            sizeof(cy_capsense_tuner), sizeof(cy_capsense_tuner),
                            &ezi2c_context);

    Cy_SCB_EZI2C_Enable(CYBSP_EZI2C_HW);
}

/*******************************************************************************
* Function Name: ezi2c_isr
********************************************************************************
* Summary:
* Wrapper function for handling interrupts from EZI2C block.
*
*******************************************************************************/
static void ezi2c_isr(void)
{
    Cy_SCB_EZI2C_Interrupt(CYBSP_EZI2C_HW, &ezi2c_context);
}

/* [] END OF FILE */
