/*****************************************************************************/
/** @file       esp_spi.c
*
*   @brief      ESP32 SPI interface definitions and functions
*   @ingroup    esp_wrapper Processor Layer Driver Wrapper
*
*   @copyright  Copyright(C) 2022 by Access Business Group (ABG)
*
*   @par Software License Agreement
*
*   @par
*   The software supplied herewith by ABG.
*   (the "Company") is intended and supplied to you, the Company's
*   supplier, for use solely and exclusively on ABG
*   products. The software is owned by the Company and is
*   protected under applicable copyright laws. All rights are reserved.
*   Any use in violation of the foregoing restrictions may subject the
*   user to criminal sanctions under applicable laws, as well as to
*   civil liability for the breach of the terms and conditions of this
*   license.
*
*   @par
*   THIS SOFTWARE IS PROVIDED IN AN "AS IS" CONDITION. NO WARRANTIES,
*   WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT NOT LIMITED
*   TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
*   PARTICULAR PURPOSE APPLY TO THIS SOFTWARE. THE COMPANY SHALL NOT,
*   IN ANY CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL OR
*   CONSEQUENTIAL DAMAGES, FOR ANY REASON WHATSOEVER.
*
*   @par
*   Copyright(C) 2022 by ABG.
*   All rights reserved.  No part of this software may be disclosed or
*   distributed in any form or by any means without the prior written
*   consent of ABG.
*
******************************************************************************/
/*****************************************************************************
*   IMPORTANT NOTES
*
*   The ESP32 device supports 4 SPI channels (SPI0, SPI1, SPI2, SPI3).
*   SPI0 is reserved for communications with external FLASH and RAM memory
*   (dedicated within the ESP32 WROOM module).  The SPI1 peripheral connections
*   are MUXed to the GPIO with the SPI0 peripheral connections through an
*   arbiter, therefore it does not appear the application code can utilize
*   SPI1 when SPI0 is being used by the infrastructure.
*   
*   SPI2 (HSPI) and SPI3 (VSPI) are both capable of supporting DMA transfers,
*   and can be configured as master or slave.  Data transfers can be done in 
*   multiples of 1 byte.  When not using DMA, the maximum length of data sent/
*   received is 64 bytes.
*
*   When configured for master mode operation, the SPI peripherals can support
*   a maximum of three slave devices using the automated Chip Select array.
*   
*   The SPI peripherals also support QSPI (Quad SPI) configuration, which is
*   not currently supported by this driver module.
*
******************************************************************************/

#include <string.h>     /* Required for memset() function usage */

#include "esp_spi.h"
#include "uController.h"

#include "ctlDebug.h"

#if SPI2_USES_RTOS_TASK || SPI3_USES_RTOS_TASK
#include "freertos\task.h"
#include "freertos\event_groups.h"
#endif


/** @ingroup    esp_wrapper
*   @{
*/


/******************************************************************************
*   DEFINITIONS
******************************************************************************/

/* The ESP32 hardware has defined limits for the number of bytes that can be
    transferred in any SPI transaction. */
#define SPI_MAX_DMA_TRANSFER_SIZE       ( 4095 )
#define SPI_MAX_NON_DMA_TRANSFER_SIZE   ( SOC_SPI_MAXIMUM_BUFFER_SIZE )

#define SPI_DMA_WRITE_XFER_SIZE_LIMIT   ( 512 )
#define SPI_DMA_READ_XFER_SIZE_LIMIT    ( 512 )
#define SPI_DMA_READ_XFER_NUM_PAD_BYTES ( 7 )

#define SPI_DMA_CHAN_SEL_REG_ADDR       ( DPORT_SPI_DMA_CHAN_SEL_REG )
#define SPI3_BASE_ADDR                  ( DR_REG_SPI3_BASE )

typedef struct
{
    uint32_t    spi1_dma_select : 2;
    uint32_t    spi2_dma_select : 2;
    uint32_t    spi3_dma_select : 2;
    uint32_t    reserved        : 26;
} spi_dma_chan_sel_reg;

/* Define the event bit definitions */
#define SPI_XFER_INITIALIZED                ( 1 << 0 )
#define SPI_XFER_COMPLETED                  ( 1 << 1 )




/******************************************************************************
*   LOCAL VARIABLES
******************************************************************************/

static uint8_t* pu8Spi3SendBuf = NULL;
static uint8_t* pu8Spi3RecvBuf = NULL;
static spi_slave_transaction_t Spi3SlaveTrans;
static bool bSpi3XferCompleted = false;

static EventGroupHandle_t spi3EventGroup;
static TaskHandle_t spi3TaskHandle = NULL;


/******************************************************************************
*   LOCAL FUNCTIONS
******************************************************************************/

static void IRAM_ATTR Spi3SlavePostSetupCallback( spi_slave_transaction_t *trans )
{
    /* Called after a transaction is queued and ready for pickup by master */

    #ifdef SPI3_SLAVE_READY_LINE
    /* Set the ready line high */
    GPIO_OUTPUT_SET( SPI3_SLAVE_READY_LINE, true );
    #endif
}

static void IRAM_ATTR Spi3SlavePostTransferCallback( spi_slave_transaction_t* pTrans )
{
    /* Called after transaction is sent/received */

    #ifdef SPI3_SLAVE_READY_LINE
    /* Set the ready line low */
    GPIO_OUTPUT_SET( SPI3_SLAVE_READY_LINE, false );
    #endif
    
    bSpi3XferCompleted = true;

    #if SPI3_USES_RTOS_TASK
    /* NOTE: xHigherPriorityTaskWoken must be initialized to pdFALSE */
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    BaseType_t xResult;

    /* Indicate xfer is complete */
    xResult = xEventGroupSetBitsFromISR( spi3EventGroup, SPI_XFER_COMPLETED, &xHigherPriorityTaskWoken );

    /* Was the message posted successfully? */
    if( xResult == pdPASS )
    {
        /* If xHigherPriorityTaskWoken is now set to pdTRUE then a context
         * switch should be requested.  The macro used is port specific and
         * will be either portYIELD_FROM_ISR() or portEND_SWITCHING_ISR() -
         * refer to the documentation page for the port being used. */
        portYIELD_FROM_ISR();
    }
    #endif
}


/******************************************************************************
*   PUBLIC FUNCTIONS
******************************************************************************/

/******************************************************************************
*   Spi3Init
*//**
*   @Brief      Initializes the SPI3 peripheral.
*
*   @params[in] None
*   @returns    None
******************************************************************************/
void Spi3Init( void )
{
    esp_err_t ret;
    spi_bus_config_t buscfg = {
        .miso_io_num        = SPI3_MISO_PIN,
        .mosi_io_num        = SPI3_MOSI_PIN,
        .sclk_io_num        = SPI3_SCLK_PIN,
        #ifdef SPI3_QUADWP_PIN
        .quadwp_io_num      = SPI3_QUADWP_PIN,
        #else
        .quadwp_io_num      = -1,
        #endif
        #ifdef SPI3_QUADHD_PIN
        .quadhd_io_num      = SPI3_QUADHD_PIN,
        #else
        .quadhd_io_num      = -1,
        #endif
        .max_transfer_sz    = SPI3_DMA_MAX_TRANSFER_SIZE_IN_BYTES
    };

    /* Clear local transfer flag */
    bSpi3XferCompleted = false;

    /* Initialize the SPI bus for slave (device) mode operations */
    spi_slave_interface_config_t slvcfg;

    slvcfg.mode             = SPI3_SLAVE_MODE;
    slvcfg.spics_io_num     = SPI3_CS_PIN;
    slvcfg.queue_size       = 1;                /* Queue one transaction at a time */
    slvcfg.flags            = SPI3_SLAVE_FLAGS;
    slvcfg.post_setup_cb    = Spi3SlavePostSetupCallback;
    slvcfg.post_trans_cb    = Spi3SlavePostTransferCallback;

    /* NOTE: xEventGroupCreate() call initializes events (clears bits) */
    spi3EventGroup = xEventGroupCreate();
    if( spi3EventGroup == NULL )
    {
        DEBUG_SPI_ERROR_PUT_STRING( "Error creating SPI3 event group\r\n" );
    }
    else
    {
        xTaskCreate( spi3Task, "SPI3", 1024, NULL, 1, &spi3TaskHandle );
    }

    /* Configuration for the ready line */
    gpio_config_t io_conf = {
        .intr_type = GPIO_INTR_DISABLE,
        .mode = GPIO_MODE_OUTPUT,
        .pin_bit_mask = ( 1 << SPI3_SLAVE_READY_LINE )
    };

    /* Configure handshake line as output */
    gpio_config(&io_conf);

    /* Set line inactive (low) */
    GPIO_OUTPUT_SET( SPI3_SLAVE_READY_LINE, false );

    /* Enable pull-ups on SPI lines so we don't detect rogue pulses when no master is connected. */
    gpio_set_pull_mode( SPI3_MOSI_PIN, GPIO_PULLUP_ONLY );
    gpio_set_pull_mode( SPI3_SCLK_PIN, GPIO_PULLUP_ONLY );
    gpio_set_pull_mode( SPI3_CS_PIN, GPIO_PULLUP_ONLY );

    /* Initialize SPI slave interface */
    ret = spi_slave_initialize( SPI3_HOST, &buscfg, &slvcfg, SPI3_DMA_CHANNEL );
    assert( ret==ESP_OK );

    /* IMPORTANT: The send and receive buffers MUST be declared in DMA-capable
        memory space and aligned to 32-bit boundary to ensure proper operation with DMA! */
    pu8Spi3SendBuf = heap_caps_malloc( SPI3_SLAVE_MAX_XFER_SIZE, ( MALLOC_CAP_DMA + MALLOC_CAP_32BIT ) );
    pu8Spi3RecvBuf = heap_caps_malloc( SPI3_SLAVE_MAX_XFER_SIZE, ( MALLOC_CAP_DMA + MALLOC_CAP_32BIT ) );

    /* Clear the local buffers */
    memset( pu8Spi3SendBuf, 0, SPI3_SLAVE_MAX_XFER_SIZE );
    memset( pu8Spi3RecvBuf, 0, SPI3_SLAVE_MAX_XFER_SIZE );

}


/******************************************************************************
*   Spi3SlaveQueueXfer
*//**
*   @Brief      Queues data for transfer on the SPI3 slave peripheral.
*
*   @params[in] pu8Data     Pointer to data to be sent
*   @params[in] pLength     Pointer to length of data to be sent
*   @params[in] callback    Pointer to callback function
*
*   @returns    ESP_OK if successful, error code otherwise
******************************************************************************/
esp_err_t Spi3SlaveQueueXfer( uint8_t* pu8Data, size_t* pLength, slaveTransCb callback )
{
    esp_err_t ret;

    if( ( NULL == pu8Data ) || ( *pLength > SPI3_SLAVE_MAX_XFER_SIZE ) )
    {
        ret = ESP_ERR_INVALID_ARG;
    }
    else if( NULL == pu8Spi3SendBuf )
    {
        ret = ESP_ERR_NO_MEM;
    }
    else
    {
        /* Copy data to local buffer */
        memcpy( pu8Spi3SendBuf, pu8Data, *pLength );

        /* Configure the slave transaction */
        memset( &Spi3SlaveTrans, 0, sizeof(Spi3SlaveTrans) );
        Spi3SlaveTrans.length       = *pLength * 8;     /* NOTE: Report length in bits! */
        Spi3SlaveTrans.rx_buffer    = pu8Spi3RecvBuf;
        Spi3SlaveTrans.tx_buffer    = pu8Spi3SendBuf;
        Spi3SlaveTrans.user         = (void*)callback;

        /* Queue the transaction to be executed by master
            NOTE: the spi_slave_queue_trans() call blocks until the transaction is queued until specified timeout! */
        ret = spi_slave_queue_trans( SPI3_HOST, &Spi3SlaveTrans, 0 );

        #if SPI3_USES_RTOS_TASK
        xEventGroupSetBits( spi3EventGroup, SPI_XFER_INITIALIZED );
        #endif

        #if DEBUG_SPI_OPERATION
        char chStr[20] = {0};
        DEBUG_SPI_PUT_STRING( "SPI3 xfer (" );
        DEBUG_SPI_PUT_DOUBLE_WORD_VALUE( *pLength );
        DEBUG_SPI_PUT_STRING( " bytes) initiated: " );
        DEBUG_SPI_PUT_STRING( esp_err_to_name_r( ret, chStr, sizeof(chStr) ) );
        DEBUG_SPI_PUT_STRING( "\r\n" );
        #endif
    }

    return ret;
}




/******************************************************************************
*   Spi3TimeSlice
*//**
*   @Brief      Processes post-transfer operations for the SPI3 slave peripheral.
*
*   @returns    none
******************************************************************************/
void Spi3TimeSlice( void )
{
    if( bSpi3XferCompleted )
    {
        /* Retrieve the results of the transfer */
        /* NOTE: MUST be done prior to queueing next transfer! */
        /* NOTE: This callback will fire as soon as the SPI xfer is terminated. */
        spi_slave_transaction_t* pTransRslt;
        #if SPI3_USES_RTOS_TASK
        TickType_t ticksToWait = (TickType_t)( (uint32_t)SPI3_SLAVE_XFER_DONE_WAIT_DELAY_MS / portTICK_PERIOD_MS );
        #else
        TickType_t ticksToWait = 0;     /* Do not block! */
        #endif
        esp_err_t ret = spi_slave_get_trans_result( SPI3_HOST, &pTransRslt, ticksToWait );

        if( ESP_ERR_TIMEOUT == ret )
        {
            /* Ignore timeouts, as core may not be ready yet */
        }
        else 
        {
            if( ret )
            {
                #if DEBUG_SPI_OPERATION
                char chStr[20] = {0};

                DEBUG_SPI_ERROR_PUT_STRING( "SPI3 slave get trans result return status: " );
                DEBUG_SPI_ERROR_PUT_STRING( esp_err_to_name_r( ret, chStr, sizeof(chStr) ) );
                DEBUG_SPI_ERROR_PUT_STRING( "\r\n" );
                #endif

                /* TODO: Add error handler */
            }

            if( pTransRslt )
            {
                /* Process application callback, if available */
                if( pTransRslt->user )
                {
                    size_t xferLengthBytes = ( pTransRslt->trans_len / 8 );
                    //if( 0 == xferLengthBytes )
                    {
                        DEBUG_SPI_ERROR_PUT_STRING( "SPI3 length: " );
                        DEBUG_SPI_ERROR_PUT_DOUBLE_WORD_VALUE( pTransRslt->length );
                        DEBUG_SPI_ERROR_PUT_STRING( ", trans length: " );
                        DEBUG_SPI_ERROR_PUT_DOUBLE_WORD_VALUE( pTransRslt->trans_len );
                        DEBUG_SPI_ERROR_PUT_STRING( ", pTx: " );
                        DEBUG_SPI_ERROR_PUT_DOUBLE_WORD_VALUE( (uint32_t)pTransRslt->tx_buffer );
                        DEBUG_SPI_ERROR_PUT_STRING( ", pRx: " );
                        DEBUG_SPI_ERROR_PUT_DOUBLE_WORD_VALUE( (uint32_t)pTransRslt->rx_buffer );
                        DEBUG_SPI_ERROR_PUT_STRING( "\r\n" );
                    }
                    slaveTransCb callback = (slaveTransCb)pTransRslt->user;
                    callback( pTransRslt->rx_buffer, xferLengthBytes );
                }
            }

            bSpi3XferCompleted = false;
        }
    }
}



/**
 * FreeRTOS task to perform SPI2 transfer management.
 * Waits for event bit to start measurement. Provides a callback
 * to transmit the arising raw data to a queue.
 * @param pvParameters Not used. Parameters given on task start.
 */
void spi3Task(void __unused *pvParameters)
{
    while( 1 )
    {
        xEventGroupWaitBits( spi3EventGroup,
                             SPI_XFER_COMPLETED,    /* wait for transfer complete signal */
                             pdTRUE,                /* clear bit on exit */
                             pdTRUE,                /* wait for all bits */
                             portMAX_DELAY );       /* wait forever */

        while( bSpi3XferCompleted )
        {
            /* Service the timeslice until we receive the transfer result */
            Spi3TimeSlice();
        }
    }

    /* Cleanup if we ever exit loop */
    vTaskDelete(NULL);
    spi3TaskHandle = NULL;
}

/** @} */ /* end of group */
