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

#pragma once

#include "driver/i2s_types.h"
#include "hal/i2s_types.h"

#include "esp_types.h"
#include "esp_err.h"

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief get default I2S property
 */
#define I2S_CHANNEL_DEFAULT_CONFIG(i2s_num, i2s_role) { \
    .id = i2s_num, \
    .role = i2s_role, \
    .dma_desc_num = 6, \
    .dma_frame_num = 240, \
    .auto_clear_after_cb = false, \
    .auto_clear_before_cb = false, \
    .allow_pd = false, \
    .intr_priority = 0, \
}

#define I2S_GPIO_UNUSED         GPIO_NUM_NC         /*!< Used in i2s_gpio_config_t for signals which are not used */

/**
 * @brief Group of I2S callbacks
 * @note The callbacks are all running under ISR environment
 * @note When CONFIG_I2S_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM.
 *       The variables used in the function should be in the SRAM as well.
 */
typedef struct {
    i2s_isr_callback_t on_recv;             /**< Callback of data received event, only for RX channel
                                             *   The event data includes DMA buffer address and size that just finished receiving data
                                             */
    i2s_isr_callback_t on_recv_q_ovf;       /**< Callback of receiving queue overflowed event, only for RX channel
                                             *   The event data includes buffer size that has been overwritten
                                             */
    i2s_isr_callback_t on_sent;             /**< Callback of data sent event, only for TX channel
                                             *   The event data includes DMA buffer address and size that just finished sending data
                                             */
    i2s_isr_callback_t on_send_q_ovf;       /**< Callback of sending queue overflowed event, only for TX channel
                                             *   The event data includes buffer size that has been overwritten
                                             */
} i2s_event_callbacks_t;

/**
 * @brief I2S controller channel configuration
*/
typedef struct {
    i2s_port_t          id;                 /*!< I2S port id */
    i2s_role_t          role;               /*!< I2S role, I2S_ROLE_MASTER or I2S_ROLE_SLAVE */

    /* DMA configurations */
    uint32_t            dma_desc_num;       /*!< I2S DMA buffer number, it is also the number of DMA descriptor */
    uint32_t            dma_frame_num;      /*!< I2S frame number in one DMA buffer. One frame means one-time sample data in all slots,
                                             *   it should be the multiple of `3` when the data bit width is 24.
                                             */
    union {
        bool            auto_clear;         /*!< Alias of `auto_clear_after_cb` */
        bool            auto_clear_after_cb; /*!< Set to auto clear DMA TX buffer after `on_sent` callback, I2S will always send zero automatically if no data to send.
                                             *   So that user can assign the data to the DMA buffers directly in the callback, and the data won't be cleared after quit the callback.
                                             */
    };
    bool                auto_clear_before_cb; /*!< Set to auto clear DMA TX buffer before `on_sent` callback, I2S will always send zero automatically if no data to send
                                             *   So that user can access data in the callback that just finished to send.
                                             */
    bool                allow_pd;           /*!< Set to allow power down. When this flag set, the driver will backup/restore the I2S registers before/after entering/exist sleep mode.
                                             * By this approach, the system can power off I2S's power domain.
                                             * This can save power, but at the expense of more RAM being consumed.
                                             */
    int                 intr_priority;      /*!< I2S interrupt priority, range [0, 7], if set to 0, the driver will try to allocate an interrupt with a relative low priority (1,2,3) */
} i2s_chan_config_t;

/**
 * @brief I2S channel information
 */
typedef struct {
    i2s_port_t          id;                 /*!< I2S port id */
    i2s_role_t          role;               /*!< I2S role, I2S_ROLE_MASTER or I2S_ROLE_SLAVE */
    i2s_dir_t           dir;                /*!< I2S channel direction */
    i2s_comm_mode_t     mode;               /*!< I2S channel communication mode */
    bool                is_enabled;         /*!< I2S channel is enabled or not */
    i2s_chan_handle_t   pair_chan;          /*!< I2S pair channel handle in duplex mode, always NULL in simplex mode */
    uint32_t            total_dma_buf_size; /*!< Total size of all the allocated DMA buffers
                                             *   - 0 if the channel has not been initialized
                                             *   - non-zero if the channel has been initialized
                                             */
    i2s_clock_src_t     clk_src;            /*!< Clock source of I2S */
    uint32_t            sclk_hz;            /*!< Source clock frequency */
    uint32_t            mclk_hz;            /*!< MCLK frequency */
    uint32_t            bclk_hz;            /*!< BCLK frequency */
    const void          *mode_cfg;          /*!< Mode configuration, it need to be casted to the corresponding type according to the communication mode
                                             *   - I2S_COMM_MODE_STD: i2s_std_config_t*
                                             *   - I2S_COMM_MODE_TDM: i2s_tdm_config_t*
                                             *   - I2S_COMM_MODE_PDM + I2S_DIR_RX: i2s_pdm_rx_config_t*
                                             *   - I2S_COMM_MODE_PDM + I2S_DIR_TX: i2s_pdm_tx_config_t*
                                             */
} i2s_chan_info_t;

/************************************************** Basic APIs ********************************************************/
/**
 * @brief Allocate new I2S channel(s)
 * @note  The new created I2S channel handle will be REGISTERED state after it is allocated successfully.
 * @note  When the port id in channel configuration is I2S_NUM_AUTO, driver will allocate I2S port automatically
 *        on one of the I2S controller, otherwise driver will try to allocate the new channel on the selected port.
 * @note  If both tx_handle and rx_handle are not NULL, it means this I2S controller will work at full-duplex mode,
 *        the RX and TX channels will be allocated on a same I2S port in this case.
 *        Note that some configurations of TX/RX channel are shared on ESP32 and ESP32S2,
 *        so please make sure they are working at same condition and under same status(start/stop).
 *        Currently, full-duplex mode can't guarantee TX/RX channels write/read synchronously,
 *        they can only share the clock signals for now.
 * @note  If tx_handle OR rx_handle is NULL, it means this I2S controller will work at simplex mode.
 *        For ESP32 and ESP32S2, the whole I2S controller (i.e. both RX and TX channel) will be occupied,
 *        even if only one of RX or TX channel is registered.
 *        For the other targets, another channel on this controller will still available.
 *
 * @param[in]   chan_cfg    I2S controller channel configurations
 * @param[out]  ret_tx_handle   I2S channel handler used for managing the sending channel(optional)
 * @param[out]  ret_rx_handle   I2S channel handler used for managing the receiving channel(optional)
 * @return
 *      - ESP_OK    Allocate new channel(s) success
 *      - ESP_ERR_NOT_SUPPORTED The communication mode is not supported on the current chip
 *      - ESP_ERR_INVALID_ARG   NULL pointer or illegal parameter in i2s_chan_config_t
 *      - ESP_ERR_NOT_FOUND     No available I2S channel found
 */
esp_err_t i2s_new_channel(const i2s_chan_config_t *chan_cfg, i2s_chan_handle_t *ret_tx_handle, i2s_chan_handle_t *ret_rx_handle);

/**
 * @brief Delete the I2S channel
 * @note  Only allowed to be called when the I2S channel is at REGISTERED or READY state (i.e., it should stop before deleting it).
 * @note  Resource will be free automatically if all channels in one port are deleted
 *
 * @param[in]   handle      I2S channel handler
 *      - ESP_OK    Delete successfully
 *      - ESP_ERR_INVALID_ARG   NULL pointer
 */
esp_err_t i2s_del_channel(i2s_chan_handle_t handle);

/**
 * @brief Get I2S channel information
 *
 * @param[in]   handle      I2S channel handler
 * @param[out]  chan_info   I2S channel basic information
 * @return
 *      - ESP_OK    Get I2S channel information success
 *      - ESP_ERR_NOT_FOUND     The input handle doesn't match any registered I2S channels, it may not an I2S channel handle or not available any more
 *      - ESP_ERR_INVALID_ARG   The input handle or chan_info pointer is NULL
 */
esp_err_t i2s_channel_get_info(i2s_chan_handle_t handle, i2s_chan_info_t *chan_info);

/**
 * @brief Enable the I2S channel
 * @note  Only allowed to be called when the channel state is READY, (i.e., channel has been initialized, but not started)
 *        the channel will enter RUNNING state once it is enabled successfully.
 * @note  Enable the channel can start the I2S communication on hardware. It will start outputting BCLK and WS signal.
 *        For MCLK signal, it will start to output when initialization is finished
 *
 * @param[in]   handle      I2S channel handler
 *      - ESP_OK    Start successfully
 *      - ESP_ERR_INVALID_ARG   NULL pointer
 *      - ESP_ERR_INVALID_STATE This channel has not initialized or already started
 */
esp_err_t i2s_channel_enable(i2s_chan_handle_t handle);

/**
 * @brief Disable the I2S channel
 * @note  Only allowed to be called when the channel state is RUNNING, (i.e., channel has been started)
 *        the channel will enter READY state once it is disabled successfully.
 * @note  Disable the channel can stop the I2S communication on hardware. It will stop BCLK and WS signal but not MCLK signal
 *
 * @param[in]   handle      I2S channel handler
 * @return
 *      - ESP_OK    Stop successfully
 *      - ESP_ERR_INVALID_ARG   NULL pointer
 *      - ESP_ERR_INVALID_STATE This channel has not stated
 */
esp_err_t i2s_channel_disable(i2s_chan_handle_t handle);

/**
 * @brief I2S write data
 * @note  Only allowed to be called when the channel state is RUNNING, (i.e., TX channel has been started and is not writing now)
 *        but the RUNNING only stands for the software state, it doesn't mean there is no the signal transporting on line.
 *
 * @param[in]   handle      I2S channel handler
 * @param[in]   src         The pointer of sent data buffer
 * @param[in]   size        Max data buffer length
 * @param[out]  bytes_written   Byte number that actually be sent, can be NULL if not needed
 * @param[in]   timeout_ms      Max block time
 * @return
 *      - ESP_OK    Write successfully
 *      - ESP_ERR_INVALID_ARG   NULL pointer or this handle is not TX handle
 *      - ESP_ERR_TIMEOUT       Writing timeout, no writing event received from ISR within ticks_to_wait
 *      - ESP_ERR_INVALID_STATE I2S is not ready to write
 */
esp_err_t i2s_channel_write(i2s_chan_handle_t handle, const void *src, size_t size, size_t *bytes_written, uint32_t timeout_ms);

/**
 * @brief I2S read data
 * @note  Only allowed to be called when the channel state is RUNNING
 *        but the RUNNING only stands for the software state, it doesn't mean there is no the signal transporting on line.
 *
 * @param[in]   handle      I2S channel handler
 * @param[in]   dest        The pointer of receiving data buffer
 * @param[in]   size        Max data buffer length
 * @param[out]  bytes_read      Byte number that actually be read, can be NULL if not needed
 * @param[in]   timeout_ms      Max block time
 * @return
 *      - ESP_OK    Read successfully
 *      - ESP_ERR_INVALID_ARG   NULL pointer or this handle is not RX handle
 *      - ESP_ERR_TIMEOUT       Reading timeout, no reading event received from ISR within ticks_to_wait
 *      - ESP_ERR_INVALID_STATE I2S is not ready to read
 */
esp_err_t i2s_channel_read(i2s_chan_handle_t handle, void *dest, size_t size, size_t *bytes_read, uint32_t timeout_ms);

/**
 * @brief Set event callbacks for I2S channel
 *
 * @note Only allowed to be called when the channel state is REGISTERED / READY, (i.e., before channel starts)
 * @note User can deregister a previously registered callback by calling this function and setting the callback member in the `callbacks` structure to NULL.
 * @note When CONFIG_I2S_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM.
 *       The variables used in the function should be in the SRAM as well. The `user_data` should also reside in SRAM or internal RAM as well.
 *
 * @param[in] handle        I2S channel handler
 * @param[in] callbacks     Group of callback functions
 * @param[in] user_data     User data, which will be passed to callback functions directly
 * @return
 *      - ESP_OK                Set event callbacks successfully
 *      - ESP_ERR_INVALID_ARG   Set event callbacks failed because of invalid argument
 *      - ESP_ERR_INVALID_STATE Set event callbacks failed because the current channel state is not REGISTERED or READY
 */
esp_err_t i2s_channel_register_event_callback(i2s_chan_handle_t handle, const i2s_event_callbacks_t *callbacks, void *user_data);

/************************************************ Advanced APIs *******************************************************/
/**
 * @brief Preload the data into TX DMA buffer
 * @note  Only allowed to be called when the channel state is READY, (i.e., channel has been initialized, but not started)
 * @note  As the initial DMA buffer has no data inside, it will transmit the empty buffer after enabled the channel,
 *        this function is used to preload the data into the DMA buffer, so that the valid data can be transmitted immediately
 *        after the channel is enabled.
 * @note  This function can be called multiple times before enabling the channel, the buffer that loaded later will be concatenated
 *        behind the former loaded buffer. But when all the DMA buffers have been loaded, no more data can be preload then, please
 *        check the `bytes_loaded` parameter to see how many bytes are loaded successfully, when the `bytes_loaded` is smaller than
 *        the `size`, it means the DMA buffers are full.
 *
 * @param[in]   tx_handle   I2S TX channel handler
 * @param[in]   src         The pointer of the source buffer to be loaded
 * @param[in]   size        The source buffer size
 * @param[out]  bytes_loaded    The bytes that successfully been loaded into the TX DMA buffer
 * @return
 *      - ESP_OK    Load data successful
 *      - ESP_FAIL  Failed to push the message queue
 *      - ESP_ERR_INVALID_ARG   NULL pointer or not TX direction
 *      - ESP_ERR_INVALID_STATE This channel has not stated
 */
esp_err_t i2s_channel_preload_data(i2s_chan_handle_t tx_handle, const void *src, size_t size, size_t *bytes_loaded);

/**
 * @brief Tune the I2S clock rate
 * @note  Only allowed to be called when the channel state is READY, (i.e., channel has been initialized, but not started)
 * @note  This function is mainly to fine-tuning the mclk to match the speed of producer and consumer.
 *        So that to avoid exsaust of the memory to store the data from producer.
 *        Please take care the how different the frequency error can be tolerant by your codec,
 *        otherwise the codec might stop working if the frequency changes a lot.
 *
 * @param[in]  handle       I2S channel handler
 * @param[in]  tune_cfg     The clock tuning configuration, can be NULL if only need the current clock result
 * @param[out] tune_info    The clock tuning information, can be NULL if not needed
 * @return
 *      - ESP_OK                 Tune the clock successfully
 *      - ESP_ERR_INVALID_ARG    Tune the clock failed because of the invalid argument like NULL pointer or out of range
 *      - ESP_ERR_NOT_SUPPORTED  Tune the clock failed because this function does not support to tune the external clock source
 */
esp_err_t i2s_channel_tune_rate(i2s_chan_handle_t handle, const i2s_tuning_config_t *tune_cfg, i2s_tuning_info_t *tune_info);

#ifdef __cplusplus
}
#endif
