/*
 * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#pragma once

#include "esp_err.h"
#include "driver/spi_master.h"

#include "esp_serial_slave_link/essl.h"

#ifdef __cplusplus
extern "C" {
#endif

/// Configuration of ESSL SPI device
typedef struct {
    spi_device_handle_t *spi;           ///< Pointer to SPI device handle.
    uint32_t            tx_buf_size;    ///< The pre-negotiated Master TX buffer size used by both the host and the slave.
    uint8_t             tx_sync_reg;    ///< The pre-negotiated register ID for Master-TX-SLAVE-RX synchronization. 1 word (4 Bytes) will be reserved for the synchronization.
    uint8_t             rx_sync_reg;    ///< The pre-negotiated register ID for Master-RX-Slave-TX synchronization. 1 word (4 Bytes) will be reserved for the synchronization.
} essl_spi_config_t;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// APIs for DMA Append Mode
// This mode has a better performance for continuous Half Duplex SPI transactions.
//
// * You can use the ``essl_spi_init_dev`` and ``essl_spi_deinit_dev`` together with APIs in ``essl.h`` to communicate
//   with ESP SPI Slaves in Half Duplex DMA Append Mode. See example for SPI SLAVE HALFDUPLEX APPEND MODE.
// * You can also use the following APIs to create your own logic.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
 * @brief Initialize the ESSL SPI device function list and get its handle
 *
 * @param[out] out_handle    Output of the handle
 * @param      init_config   Configuration for the ESSL SPI device
 * @return
 *        - ESP_OK:                On success
 *        - ESP_ERR_NO_MEM:        Memory exhausted
 *        - ESP_ERR_INVALID_STATE: SPI driver is not initialized
 *        - ESP_ERR_INVALID_ARG:   Wrong register ID
 */
esp_err_t essl_spi_init_dev(essl_handle_t *out_handle, const essl_spi_config_t *init_config);

/**
 * @brief Deinitialize the ESSL SPI device and free the memory used by the device
 *
 * @param handle    Handle of the ESSL SPI device
 * @return
 *        - ESP_OK:                On success
 *        - ESP_ERR_INVALID_STATE: ESSL SPI is not in use
 */
esp_err_t essl_spi_deinit_dev(essl_handle_t handle);

/**
 * @brief Read from the shared registers
 *
 * @note The registers for Master/Slave synchronization are reserved. Do not use them. (see `rx_sync_reg` in `essl_spi_config_t`)
 *
 * @param      arg        Context of the component. (Member ``arg`` from ``essl_handle_t``)
 * @param      addr       Address of the shared registers. (Valid: 0 ~ SOC_SPI_MAXIMUM_BUFFER_SIZE, registers for M/S sync are reserved, see note1).
 * @param[out] out_value  Read buffer for the shared registers.
 * @param      wait_ms    Time to wait before timeout (reserved for future use, user should set this to 0).
 * @return
 *        - ESP_OK:                success
 *        - ESP_ERR_INVALID_STATE: ESSL SPI has not been initialized.
 *        - ESP_ERR_INVALID_ARG:   The address argument is not valid. See note 1.
 *        - or other return value from :cpp:func:`spi_device_transmit`.
 */
esp_err_t essl_spi_read_reg(void *arg, uint8_t addr, uint8_t *out_value, uint32_t wait_ms);

/**
 * @brief Get a packet from Slave
 *
 * @param      arg         Context of the component. (Member ``arg`` from ``essl_handle_t``)
 * @param[out] out_data    Output data address
 * @param      size        The size of the output data.
 * @param      wait_ms     Time to wait before timeout (reserved for future use, user should set this to 0).
 * @return
 *        - ESP_OK:                On Success
 *        - ESP_ERR_INVALID_STATE: ESSL SPI has not been initialized.
 *        - ESP_ERR_INVALID_ARG:   The output data address is neither DMA capable nor 4 byte-aligned
 *        - ESP_ERR_INVALID_SIZE:  Master requires ``size`` bytes of data but Slave did not load enough bytes.
 */
esp_err_t essl_spi_get_packet(void *arg, void *out_data, size_t size, uint32_t wait_ms);

/**
 * @brief Write to the shared registers
 *
 * @note The registers for Master/Slave synchronization are reserved. Do not use them. (see `tx_sync_reg` in `essl_spi_config_t`)
 * @note Feature of checking the actual written value (``out_value``) is not supported.
 *
 * @param      arg        Context of the component. (Member ``arg`` from ``essl_handle_t``)
 * @param      addr       Address of the shared registers. (Valid: 0 ~ SOC_SPI_MAXIMUM_BUFFER_SIZE, registers for M/S sync are reserved, see note1)
 * @param      value      Buffer for data to send, should be align to 4.
 * @param[out] out_value  Not supported, should be set to NULL.
 * @param      wait_ms    Time to wait before timeout (reserved for future use, user should set this to 0).
 * @return
 *        - ESP_OK:                success
 *        - ESP_ERR_INVALID_STATE: ESSL SPI has not been initialized.
 *        - ESP_ERR_INVALID_ARG:   The address argument is not valid. See note 1.
 *        - ESP_ERR_NOT_SUPPORTED: Should set ``out_value`` to NULL. See note 2.
 *        - or other return value from :cpp:func:`spi_device_transmit`.
 *
 */
esp_err_t essl_spi_write_reg(void *arg, uint8_t addr, uint8_t value, uint8_t *out_value, uint32_t wait_ms);

/**
 * @brief Send a packet to Slave
 *
 * @param arg        Context of the component. (Member ``arg`` from ``essl_handle_t``)
 * @param data       Address of the data to send
 * @param size       Size of the data to send.
 * @param wait_ms    Time to wait before timeout (reserved for future use, user should set this to 0).
 * @return
 *        - ESP_OK:                On success
 *        - ESP_ERR_INVALID_STATE: ESSL SPI has not been initialized.
 *        - ESP_ERR_INVALID_ARG:   The data address is not DMA capable
 *        - ESP_ERR_INVALID_SIZE:  Master will send ``size`` bytes of data but Slave did not load enough RX buffer
 */
esp_err_t essl_spi_send_packet(void *arg, const void *data, size_t size, uint32_t wait_ms);

/**
 * @brief Reset the counter in Master context
 *
 * @note Shall only be called if the slave has reset its counter. Else, Slave and Master would be desynchronized
 *
 * @param arg    Context of the component. (Member ``arg`` from ``essl_handle_t``)
 */
void essl_spi_reset_cnt(void *arg);

////////////////////////////////////////////////////////////////////////////////
// Basic commands to communicate with the SPI Slave HD on ESP32-S2
////////////////////////////////////////////////////////////////////////////////
/**
 * @brief Read the shared buffer from the slave in ISR way
 *
 * @note The slave's HW doesn't guarantee the data in one SPI transaction is consistent. It sends data in unit of byte.
 * In other words, if the slave SW attempts to update the shared register when a rdbuf SPI transaction is in-flight,
 * the data got by the master will be the combination of bytes of different writes of slave SW.
 *
 * @note ``out_data`` should be prepared in words and in the DRAM. The buffer may be written in words
 *       by the DMA. When a byte is written, the remaining bytes in the same word will also be
 *       overwritten, even the ``len`` is shorter than a word.
 *
 * @param      spi       SPI device handle representing the slave
 * @param[out] out_data  Buffer for read data, strongly suggested to be in the DRAM and aligned to 4
 * @param      addr      Address of the slave shared buffer
 * @param      len       Length to read
 * @param      flags     `SPI_TRANS_*` flags to control the transaction mode of the transaction to send.
 * @return
 *      - ESP_OK: on success
 *      - or other return value from :cpp:func:`spi_device_transmit`.
 */
esp_err_t essl_spi_rdbuf(spi_device_handle_t spi, uint8_t *out_data, int addr, int len, uint32_t flags);

/**
 * @brief Read the shared buffer from the slave in polling way
 *
 * @note ``out_data`` should be prepared in words and in the DRAM. The buffer may be written in words
 *       by the DMA. When a byte is written, the remaining bytes in the same word will also be
 *       overwritten, even the ``len`` is shorter than a word.
 *
 * @param      spi       SPI device handle representing the slave
 * @param[out] out_data  Buffer for read data, strongly suggested to be in the DRAM and aligned to 4
 * @param      addr      Address of the slave shared buffer
 * @param      len       Length to read
 * @param      flags     `SPI_TRANS_*` flags to control the transaction mode of the transaction to send.
 * @return
 *      - ESP_OK: on success
 *      - or other return value from :cpp:func:`spi_device_transmit`.
 */
esp_err_t essl_spi_rdbuf_polling(spi_device_handle_t spi, uint8_t *out_data, int addr, int len, uint32_t flags);

/**
 * @brief Write the shared buffer of the slave in ISR way
 *
 * @note ``out_data`` should be prepared in words and in the DRAM. The buffer may be written in words
 *       by the DMA. When a byte is written, the remaining bytes in the same word will also be
 *       overwritten, even the ``len`` is shorter than a word.
 *
 * @param spi       SPI device handle representing the slave
 * @param data      Buffer for data to send, strongly suggested to be in the DRAM
 * @param addr      Address of the slave shared buffer,
 * @param len       Length to write
 * @param flags     `SPI_TRANS_*` flags to control the transaction mode of the transaction to send.
 * @return
 *      - ESP_OK: success
 *      - or other return value from :cpp:func:`spi_device_transmit`.
 */
esp_err_t essl_spi_wrbuf(spi_device_handle_t spi, const uint8_t *data, int addr, int len, uint32_t flags);

/**
 * @brief Write the shared buffer of the slave in polling way
 *
 * @note ``out_data`` should be prepared in words and in the DRAM. The buffer may be written in words
 *       by the DMA. When a byte is written, the remaining bytes in the same word will also be
 *       overwritten, even the ``len`` is shorter than a word.
 *
 * @param spi       SPI device handle representing the slave
 * @param data      Buffer for data to send, strongly suggested to be in the DRAM
 * @param addr      Address of the slave shared buffer,
 * @param len       Length to write
 * @param flags     `SPI_TRANS_*` flags to control the transaction mode of the transaction to send.
 * @return
 *      - ESP_OK: success
 *      - or other return value from :cpp:func:`spi_device_polling_transmit`.
 */
esp_err_t essl_spi_wrbuf_polling(spi_device_handle_t spi, const uint8_t *data, int addr, int len, uint32_t flags);

/**
 * @brief Receive long buffer in segments from the slave through its DMA.
 *
 * @note This function combines several :cpp:func:`essl_spi_rddma_seg` and one
 *       :cpp:func:`essl_spi_rddma_done` at the end. Used when the slave is working in segment mode.
 *
 * @param      spi       SPI device handle representing the slave
 * @param[out] out_data  Buffer to hold the received data, strongly suggested to be in the DRAM and aligned to 4
 * @param      len       Total length of data to receive.
 * @param      seg_len Length of each segment, which is not larger than the maximum transaction length
 *                allowed for the spi device. Suggested to be multiples of 4. When set < 0, means send
 *                all data in one segment (the ``rddma_done`` will still be sent.)
 * @param flags     `SPI_TRANS_*` flags to control the transaction mode of the transaction to send.
 * @return
 *      - ESP_OK: success
 *      - or other return value from :cpp:func:`spi_device_transmit`.
 */
esp_err_t essl_spi_rddma(spi_device_handle_t spi, uint8_t *out_data, int len, int seg_len, uint32_t flags);

/**
 * @brief Read one data segment from the slave through its DMA.
 *
 * @note To read long buffer, call :cpp:func:`essl_spi_rddma` instead.
 *
 * @param      spi       SPI device handle representing the slave
 * @param[out] out_data  Buffer to hold the received data. strongly suggested to be in the DRAM and aligned to 4
 * @param      seg_len   Length of this segment
 * @param      flags     `SPI_TRANS_*` flags to control the transaction mode of the transaction to send.
 * @return
 *      - ESP_OK: success
 *      - or other return value from :cpp:func:`spi_device_transmit`.
 */
esp_err_t essl_spi_rddma_seg(spi_device_handle_t spi, uint8_t *out_data, int seg_len, uint32_t flags);

/**
 * @brief Send the ``rddma_done`` command to the slave. Upon receiving this command, the slave will
 *        stop sending the current buffer even there are data unsent, and maybe prepare the next buffer to
 *        send.
 *
 * @note This is required only when the slave is working in segment mode.
 *
 * @param spi       SPI device handle representing the slave
 * @param flags     `SPI_TRANS_*` flags to control the transaction mode of the transaction to send.
 * @return
 *      - ESP_OK: success
 *      - or other return value from :cpp:func:`spi_device_transmit`.
 */
esp_err_t essl_spi_rddma_done(spi_device_handle_t spi, uint32_t flags);

/**
 * @brief Send long buffer in segments to the slave through its DMA.
 *
 * @note This function combines several :cpp:func:`essl_spi_wrdma_seg` and one
 *       :cpp:func:`essl_spi_wrdma_done` at the end. Used when the slave is working in segment mode.
 *
 * @param spi       SPI device handle representing the slave
 * @param data      Buffer for data to send, strongly suggested to be in the DRAM
 * @param len       Total length of data to send.
 * @param seg_len Length of each segment, which is not larger than the maximum transaction length
 *                allowed for the spi device. Suggested to be multiples of 4. When set < 0, means send
 *                all data in one segment (the ``wrdma_done`` will still be sent.)
 * @param flags     `SPI_TRANS_*` flags to control the transaction mode of the transaction to send.
 * @return
 *      - ESP_OK: success
 *      - or other return value from :cpp:func:`spi_device_transmit`.
 */
esp_err_t essl_spi_wrdma(spi_device_handle_t spi, const uint8_t *data, int len, int seg_len, uint32_t flags);

/**
 * @brief Send one data segment to the slave through its DMA.
 *
 * @note To send long buffer, call :cpp:func:`essl_spi_wrdma` instead.
 *
 * @param spi       SPI device handle representing the slave
 * @param data      Buffer for data to send, strongly suggested to be in the DRAM
 * @param seg_len   Length of this segment
 * @param flags     `SPI_TRANS_*` flags to control the transaction mode of the transaction to send.
 * @return
 *      - ESP_OK: success
 *      - or other return value from :cpp:func:`spi_device_transmit`.
 */
esp_err_t essl_spi_wrdma_seg(spi_device_handle_t spi, const uint8_t *data, int seg_len, uint32_t flags);

/**
 * @brief Send the ``wrdma_done`` command to the slave. Upon receiving this command, the slave will
 *        stop receiving, process the received data, and maybe prepare the next buffer to receive.
 *
 * @note This is required only when the slave is working in segment mode.
 *
 * @param spi       SPI device handle representing the slave
 * @param flags     `SPI_TRANS_*` flags to control the transaction mode of the transaction to send.
 * @return
 *      - ESP_OK: success
 *      - or other return value from :cpp:func:`spi_device_transmit`.
 */
esp_err_t essl_spi_wrdma_done(spi_device_handle_t spi, uint32_t flags);

#ifdef __cplusplus
}
#endif
