Newer
Older
mbed-os / targets / TARGET_Cypress / TARGET_PSOC6 / mtb-hal-cat1 / source / cyhal_i2s.c
@Dustin Crossman Dustin Crossman on 4 Jun 2021 50 KB Fix file modes.
/*******************************************************************************
* File Name: cyhal_i2s.c
*
* Description:
* Provides a high level interface for interacting with the Cypress I2S. This is
* a wrapper around the lower level PDL API.
*
********************************************************************************
* \copyright
* Copyright 2018-2021 Cypress Semiconductor Corporation
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/

#include <math.h>
#include <stdlib.h>
#include "cyhal_i2s.h"
#include "cy_i2s.h"
#include "cyhal_clock.h"
#include "cyhal_gpio.h"
#include "cyhal_hw_resources.h"
#include "cyhal_system_impl.h"
#include "cyhal_hwmgr.h"
#include "cyhal_utils.h"
#include "cyhal_dma.h"
#include "cyhal_syspm.h"
#include "cy_device.h"

/**
* \addtogroup group_hal_impl_i2s I2S (Inter-IC Sound)
* \ingroup group_hal_impl
* \{
* The CAT1 (PSoC 6) I2S Supports the following values for word and channel lengths (with the
* constraint that word length must be less than or equal to channel length):
* - 8 bits
* - 16 bits
* - 18 bits
* - 20 bits
* - 24 bits
* - 32 bits
*
* The sclk signal is formed by integer division of the input clock source (either internally
* provided or from the mclk pin). The CAT1 I2S supports sclk divider values from 1 to 64.
* \} group_hal_impl_i2s
*/


#ifdef CY_IP_MXAUDIOSS

#if defined(__cplusplus)
extern "C"
{
#endif

#define _CYHAL_I2S_FIFO_DEPTH (256)
#define _CYHAL_I2S_DMA_BURST_SIZE (_CYHAL_I2S_FIFO_DEPTH / 2)

static I2S_Type *const _cyhal_i2s_base[] =
{
#if (CY_IP_MXAUDIOSS_INSTANCES == 1 && defined(AUDIOSS_I2S) && AUDIOSS_I2S)
    I2S,
#elif (CY_IP_MXAUDIOSS_INSTANCES >= 1 && defined(AUDIOSS0_I2S) && AUDIOSS0_I2S)
    I2S0,
#endif
#if (CY_IP_MXAUDIOSS_INSTANCES >= 2 && defined(AUDIOSS1_I2S) && AUDIOSS1_I2S)
    I2S1,
#endif

#if (CY_IP_MXS40AUDIOSS_INSTANCES > 2)
    #warning Unhandled audioss instance count
#endif
};

static cyhal_i2s_t* _cyhal_i2s_config_structs[CY_IP_MXAUDIOSS_INSTANCES];

static const IRQn_Type _cyhal_i2s_irq_n[] =
{
#if (CY_IP_MXAUDIOSS_INSTANCES == 1 && defined(AUDIOSS_I2S) && AUDIOSS_I2S) // Without index suffix
    audioss_interrupt_i2s_IRQn,
#elif (CY_IP_MXAUDIOSS_INSTANCES >= 1 && defined(AUDIOSS0_I2S) && AUDIOSS0_I2S)
    audioss_0_interrupt_i2s_IRQn,
#endif
#if (CY_IP_MXAUDIOSS_INSTANCES >= 2 && defined(AUDIOSS1_I2S) && AUDIOSS1_I2S)
    audioss_1_interrupt_i2s_IRQn,
#endif

#if (CY_IP_MXS40AUDIOSS_INSTANCES > 2)
    #warning Unhandled audioss instance count
#endif
};

static uint8_t _cyhal_i2s_get_block_from_irqn(IRQn_Type irqn) {
    switch (irqn)
    {
#if (CY_CPU_CORTEX_M4)
#if (CY_IP_MXAUDIOSS_INSTANCES == 1 && defined(AUDIOSS_I2S) && AUDIOSS_I2S) // Without index suffix
    case audioss_interrupt_i2s_IRQn:
        return 0;
#elif (CY_IP_MXAUDIOSS_INSTANCES >= 1 && defined(AUDIOSS0_I2S) && AUDIOSS0_I2S)
    case audioss_0_interrupt_i2s_IRQn:
        return 0;
#endif
#if (CY_IP_MXAUDIOSS_INSTANCES >= 2 && defined(AUDIOSS1_I2S) && AUDIOSS1_I2S)
    case audioss_1_interrupt_i2s_IRQn:
        return 1;
#endif
#if (CY_IP_MXS40AUDIOSS_INSTANCES > 2)
    #warning Unhandled audioss instance count
#endif
#endif /* (CY_CPU_CORTEX_M4) */
    default:
        CY_ASSERT(false); // Should never be called with a non-I2S IRQn
        return 0;
    }
}

static cyhal_i2s_event_t _cyhal_i2s_convert_interrupt_cause(uint32_t pdl_cause);
static uint32_t _cyhal_i2s_convert_event(cyhal_i2s_event_t event);
static cy_rslt_t _cyhal_i2s_convert_length(uint8_t user_length, cy_en_i2s_len_t *pdl_length);
static void _cyhal_i2s_irq_handler(void);
static void _cyhal_i2s_process_event(cyhal_i2s_t *obj, cyhal_i2s_event_t event);
static void _cyhal_i2s_update_enabled_events(cyhal_i2s_t* obj);
static void _cyhal_i2s_update_rx_trigger_level(cyhal_i2s_t* obj);
static cy_rslt_t _cyhal_i2s_dma_perform_rx(cyhal_i2s_t *obj);
static cy_rslt_t _cyhal_i2s_dma_perform_tx(cyhal_i2s_t *obj);
static void _cyhal_i2s_dma_handler_rx(void *callback_arg, cyhal_dma_event_t event);
static void _cyhal_i2s_dma_handler_tx(void *callback_arg, cyhal_dma_event_t event);
static uint8_t _cyhal_i2s_rounded_word_length(cyhal_i2s_t *obj);
static bool _cyhal_i2s_pm_callback(cyhal_syspm_callback_state_t state, cyhal_syspm_callback_mode_t mode, void* callback_arg);
static cy_rslt_t _cyhal_i2s_populate_pdl_config(cyhal_i2s_t *obj, cy_stc_i2s_config_t* pdl_config, uint8_t sclk_div);
static cy_rslt_t _cyhal_i2s_compute_sclk_div(cyhal_i2s_t *obj, uint32_t sample_rate_hz, uint8_t *sclk_div);

static const cy_stc_i2s_config_t _cyhal_i2s_default_config = {
    /* tx_enabled and rx_enabled set per-instance */
    .txDmaTrigger = false,
    .rxDmaTrigger = false,
    /* clkDiv set per-instance */
    /* extclk set per-instance */
    /* txMasterMode set per-instance */
    .txAlignment = CY_I2S_I2S_MODE,
    .txWsPulseWidth = CY_I2S_WS_ONE_CHANNEL_LENGTH, /* only supported value for I2S mode */
    .txWatchdogEnable = false,
    .txWatchdogValue = 0u,
    .txSdoLatchingTime = false, /* to match the I2S standard */
    .txSckoInversion = false,
    .txSckiInversion = false,
    .txChannels = 2, /* Only supported value for I2S mode */
    /* txChannelLength set per-instance */
    /* txWordLength set per-instance */
    .txOverheadValue = CY_I2S_OVHDATA_ZERO,
    .txFifoTriggerLevel = _CYHAL_I2S_FIFO_DEPTH / 2 + 1, // Trigger at half empty
    /* rxMasterMode set per-instance */
    .rxAlignment = CY_I2S_I2S_MODE,        /**< RX data alignment, see: #cy_en_i2s_alignment_t. */
    .rxWsPulseWidth = CY_I2S_WS_ONE_CHANNEL_LENGTH, /* only supported value for I2S mode */
    .rxWatchdogEnable = false,
    .rxWatchdogValue = 0u,
    .rxSdiLatchingTime = false, /* to match the I2S standard */
    .rxSckoInversion = false,
    .rxSckiInversion = false,
    .rxChannels = 2, /* Only supported value for I2s mode */
    /* rxChannelLength set per-instance */
    /* rxWordLength set per-instance */
    .rxSignExtension = false, /* All MSB are filled by zeros */
    .rxFifoTriggerLevel = _CYHAL_I2S_FIFO_DEPTH / 2 - 1, // Trigger at half full
};

cy_rslt_t cyhal_i2s_init(cyhal_i2s_t *obj, const cyhal_i2s_pins_t* tx_pins, const cyhal_i2s_pins_t* rx_pins, cyhal_gpio_t mclk,
                         const cyhal_i2s_config_t* config, cyhal_clock_t* clk)
{
    CY_ASSERT(NULL != obj);
    memset(obj, 0, sizeof(cyhal_i2s_t));
    /* Explicitly marked not allocated resources as invalid to prevent freeing them. */
    obj->resource.type = CYHAL_RSC_INVALID;
    obj->pin_tx_sck    = CYHAL_NC_PIN_VALUE;
    obj->pin_tx_ws     = CYHAL_NC_PIN_VALUE;
    obj->pin_tx_sdo    = CYHAL_NC_PIN_VALUE;
    obj->pin_rx_sck    = CYHAL_NC_PIN_VALUE;
    obj->pin_rx_ws     = CYHAL_NC_PIN_VALUE;
    obj->pin_rx_sdi    = CYHAL_NC_PIN_VALUE;
    obj->pin_mclk      = CYHAL_NC_PIN_VALUE;

    obj->is_tx_slave    = config->is_tx_slave;
    obj->is_rx_slave    = config->is_rx_slave;
    obj->mclk_hz        = config->mclk_hz;
    obj->channel_length = config->channel_length;
    obj->word_length    = config->word_length;
    obj->sample_rate_hz = config->sample_rate_hz;

    /*
     * We will update this to owned later if appropriate - for now set to false
     * so we don't try to free if we fail before allocating a clock
     */
    obj->is_clock_owned = false;

    obj->user_enabled_events = 0u;

    obj->callback_data.callback = NULL;
    obj->callback_data.callback_arg = NULL;
    obj->async_mode = CYHAL_ASYNC_SW;
    obj->async_tx_buff = NULL;
    obj->async_rx_buff = NULL;
    obj->tx_dma.resource.type = CYHAL_RSC_INVALID;
    obj->rx_dma.resource.type = CYHAL_RSC_INVALID;

    cy_rslt_t result = CY_RSLT_SUCCESS;

    /* Determine which I2S instance to use */
    const cyhal_resource_pin_mapping_t *tx_sck_map = (NULL != tx_pins) ? _CYHAL_UTILS_GET_RESOURCE(tx_pins->sck, cyhal_pin_map_audioss_tx_sck) : NULL;
    const cyhal_resource_pin_mapping_t *tx_ws_map  = (NULL != tx_pins) ? _CYHAL_UTILS_GET_RESOURCE(tx_pins->ws, cyhal_pin_map_audioss_tx_ws) : NULL;
    const cyhal_resource_pin_mapping_t *tx_sdo_map = (NULL != tx_pins) ? _CYHAL_UTILS_GET_RESOURCE(tx_pins->data, cyhal_pin_map_audioss_tx_sdo) : NULL;

    const cyhal_resource_pin_mapping_t *rx_sck_map = (NULL != rx_pins) ? _CYHAL_UTILS_GET_RESOURCE(rx_pins->sck, cyhal_pin_map_audioss_rx_sck) : NULL;
    const cyhal_resource_pin_mapping_t *rx_ws_map  = (NULL != rx_pins) ? _CYHAL_UTILS_GET_RESOURCE(rx_pins->ws, cyhal_pin_map_audioss_rx_ws) : NULL;
    const cyhal_resource_pin_mapping_t *rx_sdi_map = (NULL != rx_pins) ? _CYHAL_UTILS_GET_RESOURCE(rx_pins->data, cyhal_pin_map_audioss_rx_sdi) : NULL;

    const cyhal_resource_pin_mapping_t *mclk_map = _CYHAL_UTILS_GET_RESOURCE(mclk, cyhal_pin_map_audioss_clk_i2s_if);

    if(NULL != tx_pins) /* It is valid to leave either tx or rx empty */
    {
        if(NULL != tx_sck_map && NULL != tx_ws_map && NULL != tx_sdo_map
            && _cyhal_utils_resources_equal_all(3, tx_sck_map->inst, tx_ws_map->inst, tx_sdo_map->inst))
        {
            obj->resource = *(tx_sck_map->inst);
        }
        else
        {
            result = CYHAL_I2S_RSLT_ERR_INVALID_PIN;
        }
    }

    if(CY_RSLT_SUCCESS == result && NULL != rx_pins)
    {
        if(NULL == rx_sck_map || NULL == rx_ws_map || NULL == rx_sdi_map ||
            (false == _cyhal_utils_resources_equal_all(3, rx_sck_map->inst, rx_ws_map->inst, rx_sdi_map->inst)))
        {
            result = CYHAL_I2S_RSLT_ERR_INVALID_PIN;
        }
        else
        {
            if((obj->resource.type != CYHAL_RSC_INVALID)
                && (false == _cyhal_utils_resources_equal(&(obj->resource), rx_sck_map->inst)))
            {
                /* TX pins and RX pins don't map to the same instance */
                result = CYHAL_I2S_RSLT_ERR_INVALID_PIN;
            }
            obj->resource = *(rx_sck_map->inst);
        }
    }

    if(CYHAL_RSC_INVALID == obj->resource.type) /* If this happens it means neither rx nor tx was specified */
    {
        result = CYHAL_I2S_RSLT_ERR_INVALID_PIN;
    }

    if(CY_RSLT_SUCCESS == result && CYHAL_NC_PIN_VALUE != mclk )
    {
        if(NULL == mclk_map || (false == _cyhal_utils_resources_equal(&(obj->resource), mclk_map->inst)))
        {
            result = CYHAL_I2S_RSLT_ERR_INVALID_PIN;
        }
    }

    if(CY_RSLT_SUCCESS == result)
    {
        result = cyhal_hwmgr_reserve(&(obj->resource));
        obj->base = _cyhal_i2s_base[obj->resource.block_num];
    }

    /* Reserve the pins */
    if(CY_RSLT_SUCCESS == result && NULL != tx_pins)
    {
        result = _cyhal_utils_reserve_and_connect(tx_pins->sck, tx_sck_map);
        if(CY_RSLT_SUCCESS == result)
        {
            obj->pin_tx_sck = tx_pins->sck;
            result = _cyhal_utils_reserve_and_connect(tx_pins->ws, tx_ws_map);
        }
        if(CY_RSLT_SUCCESS == result)
        {
            obj->pin_tx_ws = tx_pins->ws;
            result = _cyhal_utils_reserve_and_connect(tx_pins->data, tx_sdo_map);
        }
        if(CY_RSLT_SUCCESS == result)
        {
            obj->pin_tx_sdo = tx_pins->data;
        }

        // In slave mode, the clock and word select pins are inputs
        if(CY_RSLT_SUCCESS == result && obj->is_tx_slave)
        {
            result = cyhal_gpio_configure(obj->pin_tx_sck, CYHAL_GPIO_DIR_INPUT, CYHAL_GPIO_DRIVE_NONE);
            if(CY_RSLT_SUCCESS == result)
            {
                result = cyhal_gpio_configure(obj->pin_tx_ws, CYHAL_GPIO_DIR_INPUT, CYHAL_GPIO_DRIVE_NONE);
            }
        }
    }

    if(CY_RSLT_SUCCESS == result && NULL != rx_pins)
    {
        result = _cyhal_utils_reserve_and_connect(rx_pins->sck, rx_sck_map);
        if(CY_RSLT_SUCCESS == result)
        {
            obj->pin_rx_sck = rx_pins->sck;
            result = _cyhal_utils_reserve_and_connect(rx_pins->ws, rx_ws_map);
        }
        if(CY_RSLT_SUCCESS == result)
        {
            obj->pin_rx_ws = rx_pins->ws;
            result = _cyhal_utils_reserve_and_connect(rx_pins->data, rx_sdi_map);
        }
        if(CY_RSLT_SUCCESS == result)
        {
            obj->pin_rx_sdi = rx_pins->data;
        }

        // In slave mode, the clock and word select pins are inputs
        if(CY_RSLT_SUCCESS == result && obj->is_rx_slave)
        {
            result = cyhal_gpio_configure(obj->pin_rx_sck, CYHAL_GPIO_DIR_INPUT, CYHAL_GPIO_DRIVE_NONE);
            if(CY_RSLT_SUCCESS == result)
            {
                result = cyhal_gpio_configure(obj->pin_rx_ws, CYHAL_GPIO_DIR_INPUT, CYHAL_GPIO_DRIVE_NONE);
            }
        }
    }

    if(CY_RSLT_SUCCESS == result && CYHAL_NC_PIN_VALUE != mclk)
    {
        if(obj->mclk_hz == 0)
        {
            // Must specify mclk frequency when using mclk
            result = CYHAL_I2S_RSLT_ERR_INVALID_ARG;
        }
        else
        {
            result = _cyhal_utils_reserve_and_connect(mclk, mclk_map);
            if(CY_RSLT_SUCCESS == result)
            {
                obj->pin_mclk = mclk;
            }
        }
    }

    if(CY_RSLT_SUCCESS == result && CYHAL_NC_PIN_VALUE == mclk)
    {
        // Must not specify mclk frequency when mclk pin is not in use
        if(obj->mclk_hz != 0)
        {
            result = CYHAL_I2S_RSLT_ERR_INVALID_ARG;
        }
    }

    if(CY_RSLT_SUCCESS == result && obj->word_length > obj->channel_length)
    {
        // Word length must be less than or equal to channel length
        result = CYHAL_I2S_RSLT_ERR_INVALID_ARG;
    }

    if (CY_RSLT_SUCCESS == result)
    {
        if (clk != NULL)
        {
            obj->clock = *clk;
        }
        else if (CYHAL_NC_PIN_VALUE == mclk) // No need to reserve a clock if we're using the mclk pin
        {
            // The hardware is generally going to be hardwired to an hfclk, which has very limited divider options. In the event
            // that we're hooked up a PERI divider, we don't have any particular expectations about its width - so just ask for 8-bit
            result = _cyhal_utils_allocate_clock(&(obj->clock), &(obj->resource), CYHAL_CLOCK_BLOCK_PERIPHERAL_16BIT, true);
            if(CY_RSLT_SUCCESS == result)
            {
                obj->is_clock_owned = true;
                result = cyhal_clock_set_enabled(&(obj->clock), true, true);
            }
        }
    }

    uint8_t sclk_div;
    if(CY_RSLT_SUCCESS == result)
    {
        result = _cyhal_i2s_compute_sclk_div(obj, obj->sample_rate_hz, &sclk_div);
    }

    cy_stc_i2s_config_t pdl_config;
    if (CY_RSLT_SUCCESS == result)
    {
        result = _cyhal_i2s_populate_pdl_config(obj, &pdl_config, sclk_div);
    }

    if (CY_RSLT_SUCCESS == result)
    {
        result = (cy_rslt_t)Cy_I2S_Init(obj->base, &pdl_config);
    }

    if (CY_RSLT_SUCCESS == result)
    {
        if(pdl_config.txEnabled)
        {
            Cy_I2S_ClearTxFifo(obj->base);
        }
        if(pdl_config.rxEnabled)
        {
            Cy_I2S_ClearRxFifo(obj->base);
        }

        obj->pm_callback.states = (cyhal_syspm_callback_state_t)(CYHAL_SYSPM_CB_CPU_DEEPSLEEP | CYHAL_SYSPM_CB_SYSTEM_HIBERNATE);
        obj->pm_callback.callback = &_cyhal_i2s_pm_callback;
        obj->pm_callback.next = NULL;
        obj->pm_callback.args = (void*)obj;
        obj->pm_callback.ignore_modes = CYHAL_SYSPM_BEFORE_TRANSITION;
        obj->pm_transition_ready = false;
        _cyhal_syspm_register_peripheral_callback(&(obj->pm_callback));

        _cyhal_i2s_config_structs[obj->resource.block_num] = obj;
        cy_stc_sysint_t irqCfg = { _cyhal_i2s_irq_n[obj->resource.block_num], CYHAL_ISR_PRIORITY_DEFAULT };
        Cy_SysInt_Init(&irqCfg, _cyhal_i2s_irq_handler);
        NVIC_EnableIRQ(_cyhal_i2s_irq_n[obj->resource.block_num]);
    }

    if (CY_RSLT_SUCCESS != result)
    {
        cyhal_i2s_free(obj);
    }
    return result;
}

void cyhal_i2s_free(cyhal_i2s_t *obj)
{
    CY_ASSERT(NULL != obj);

    if(CYHAL_RSC_INVALID != obj->resource.type)
    {
        IRQn_Type irqn = _cyhal_i2s_irq_n[obj->resource.block_num];
        NVIC_DisableIRQ(irqn);

        _cyhal_syspm_unregister_peripheral_callback(&(obj->pm_callback));
        cyhal_i2s_stop_rx(obj);
        cyhal_i2s_stop_tx(obj);
        cyhal_hwmgr_free(&(obj->resource));
        obj->base = NULL;
        obj->resource.type = CYHAL_RSC_INVALID;
    }

    _cyhal_utils_release_if_used(&(obj->pin_tx_sck));
    _cyhal_utils_release_if_used(&(obj->pin_tx_ws));
    _cyhal_utils_release_if_used(&(obj->pin_tx_sdo));
    _cyhal_utils_release_if_used(&(obj->pin_rx_sck));
    _cyhal_utils_release_if_used(&(obj->pin_rx_ws));
    _cyhal_utils_release_if_used(&(obj->pin_rx_sdi));
    _cyhal_utils_release_if_used(&(obj->pin_mclk));

    if(obj->is_clock_owned)
    {
        cyhal_hwmgr_free_clock(&(obj->clock));
    }

    if(CYHAL_RSC_INVALID != obj->rx_dma.resource.type)
    {
        cyhal_dma_free(&obj->rx_dma);
    }

    if(CYHAL_RSC_INVALID != obj->tx_dma.resource.type)
    {
        cyhal_dma_free(&obj->tx_dma);
    }
}

static cy_rslt_t _cyhal_i2s_compute_sclk_div(cyhal_i2s_t *obj, uint32_t sample_rate_hz, uint8_t *sclk_div)
{
    const uint8_t MAX_SCLK_DIVIDER = 64; // Divider value internal to the I2S block
    const cyhal_clock_tolerance_t SCLK_TOLERANCE = { .type = CYHAL_TOLERANCE_PERCENT, .value = 1 };
    uint32_t sclk_target = sample_rate_hz * obj->channel_length * 2 /* left + right channel */;
    *sclk_div = 0;

    if(obj->is_clock_owned)
    {
        // Try each of the divider values that we support internally, and see whether any of them gets us
        // within our tolerance of a frequency that our source clock can provide.
        for(uint8_t i = 1; i <= MAX_SCLK_DIVIDER; ++i)
        {
            uint32_t desired_source_freq = sclk_target * i * 8; // I2S hw has a hard-wired 8x divider
            cy_rslt_t freq_result = _cyhal_utils_set_clock_frequency(&(obj->clock), desired_source_freq, &SCLK_TOLERANCE);
            if(CY_RSLT_SUCCESS == freq_result)
            {
                *sclk_div = i;
                break;
            }
        }
    }
    else // Using user-provided clock, or using the mclk pin
    {
        // We can't change the clock, so just check if it's within tolerance
        uint32_t desired_divided_freq = sclk_target * 8; // I2S hw has a hard-wired 8x divider
        uint32_t actual_source_freq = (CYHAL_NC_PIN_VALUE == obj->pin_mclk) ? cyhal_clock_get_frequency(&obj->clock) : obj->mclk_hz;
        uint32_t best_divider = (actual_source_freq + (desired_divided_freq / 2)) / desired_divided_freq; // Round to nearest divider
        uint32_t desired_source_freq = desired_divided_freq * best_divider;
        uint32_t diff = (uint32_t)abs(_cyhal_utils_calculate_tolerance(SCLK_TOLERANCE.type, desired_source_freq, actual_source_freq));
        if(diff <= SCLK_TOLERANCE.value && best_divider <= MAX_SCLK_DIVIDER)
        {
            *sclk_div = (uint8_t)best_divider;
        }
    }

    return (0 == *sclk_div) ? CYHAL_I2S_RSLT_ERR_CLOCK : CY_RSLT_SUCCESS;
}

cy_rslt_t cyhal_i2s_set_sample_rate(cyhal_i2s_t *obj, uint32_t sample_rate_hz)
{
    uint8_t sclk_div;
    cy_stc_i2s_config_t pdl_config;

    cy_rslt_t result = _cyhal_i2s_compute_sclk_div(obj, sample_rate_hz, &sclk_div);
    if(CY_RSLT_SUCCESS == result)
    {
        result = _cyhal_i2s_populate_pdl_config(obj, &pdl_config, sclk_div);
    }
    if(CY_RSLT_SUCCESS == result)
    {
        Cy_I2S_DeInit(obj->base);
        result = (cy_rslt_t)Cy_I2S_Init(obj->base, &pdl_config);
    }
    if(CY_RSLT_SUCCESS == result)
    {
        obj->sample_rate_hz = sample_rate_hz;
    }

    return result;
}

void cyhal_i2s_register_callback(cyhal_i2s_t *obj, cyhal_i2s_event_callback_t callback, void *callback_arg)
{
    CY_ASSERT(NULL != obj);

    uint32_t savedIntrStatus = cyhal_system_critical_section_enter();
    obj->callback_data.callback = (cy_israddress) callback;
    obj->callback_data.callback_arg = callback_arg;
    cyhal_system_critical_section_exit(savedIntrStatus);
}

void cyhal_i2s_enable_event(cyhal_i2s_t *obj, cyhal_i2s_event_t event, uint8_t intr_priority, bool enable)
{
    CY_ASSERT(NULL != obj);

    if (enable)
    {
        obj->user_enabled_events |= event;
    }
    else
    {
        obj->user_enabled_events &= ~event;
    }

    _cyhal_i2s_update_enabled_events(obj);
    IRQn_Type irqn = _cyhal_i2s_irq_n[obj->resource.block_num];
    NVIC_SetPriority(irqn, intr_priority);
}

cy_rslt_t cyhal_i2s_start_tx(cyhal_i2s_t *obj)
{
    if (obj->pm_transition_ready)
    {
        return CYHAL_SYSPM_RSLT_ERR_PM_PENDING;
    }
    Cy_I2S_EnableTx(obj->base);
    return CY_RSLT_SUCCESS;
}

cy_rslt_t cyhal_i2s_stop_tx(cyhal_i2s_t *obj)
{
    Cy_I2S_DisableTx(obj->base);
    return CY_RSLT_SUCCESS;
}

cy_rslt_t cyhal_i2s_clear_tx(cyhal_i2s_t *obj)
{
    Cy_I2S_ClearTxFifo(obj->base);
    return CY_RSLT_SUCCESS;
}

cy_rslt_t cyhal_i2s_start_rx(cyhal_i2s_t *obj)
{
    if (obj->pm_transition_ready)
    {
        return CYHAL_SYSPM_RSLT_ERR_PM_PENDING;
    }
    Cy_I2S_EnableRx(obj->base);
    return CY_RSLT_SUCCESS;
}

cy_rslt_t cyhal_i2s_stop_rx(cyhal_i2s_t *obj)
{
    Cy_I2S_DisableRx(obj->base);
    return CY_RSLT_SUCCESS;
}

cy_rslt_t cyhal_i2s_clear_rx(cyhal_i2s_t *obj)
{
    Cy_I2S_ClearRxFifo(obj->base);
    return CY_RSLT_SUCCESS;
}

// Reads until empty, then updates the length and buffer address to their new locations
static void cyhal_i2s_read_until_empty(cyhal_i2s_t *obj, void** buffer, size_t* length)
{
    // The buffer is the smallest type that will hold the word length
    // The structure of this function deliberately accepts duplication of the outer loop
    // structure in order to avoid having to recheck the word length every time around,
    // because this function is in a performance sensitive code path.
    if(obj->word_length <= 8)
    {
        uint8_t *cast_buffer = (uint8_t*)(*buffer);

        while(*length > 0 && Cy_I2S_GetNumInRxFifo(obj->base) > 0)
        {
            *cast_buffer = (uint8_t)Cy_I2S_ReadRxData(obj->base);
            ++cast_buffer;
            --(*length);
        }
        *buffer = (void*)cast_buffer;
    }
    else if(obj->word_length <= 16)
    {
        uint16_t *cast_buffer = (uint16_t*)(*buffer);

        while(*length > 0 && Cy_I2S_GetNumInRxFifo(obj->base) > 0)
        {
            *cast_buffer = (uint16_t)Cy_I2S_ReadRxData(obj->base);
            ++cast_buffer;
            --(*length);
        }
        *buffer = (void*)cast_buffer;
    }
    else
    {
        CY_ASSERT(obj->word_length <= 32);
        uint32_t *cast_buffer = (uint32_t*)(*buffer);

        while(*length > 0 && Cy_I2S_GetNumInRxFifo(obj->base) > 0)
        {
            *cast_buffer = Cy_I2S_ReadRxData(obj->base);
            ++cast_buffer;
            --(*length);
        }
        *buffer = (void*)cast_buffer;
    }
}

cy_rslt_t cyhal_i2s_read(cyhal_i2s_t *obj, void *data, size_t* length)
{
    CY_ASSERT(NULL != obj);
    if (obj->pm_transition_ready)
    {
        return CYHAL_SYSPM_RSLT_ERR_PM_PENDING;
    }

    size_t remaining = *length;
    cyhal_i2s_read_until_empty(obj, &data, &remaining);
    *length -= remaining;
    return CY_RSLT_SUCCESS;
}

static void cyhal_i2s_write_until_full(cyhal_i2s_t *obj, const void** buffer, size_t *length)
{
    // The buffer is the smallest type that will hold the word length
    // The structure of this function deliberately accepts duplication of the outer loop
    // structure in order to avoid having to recheck the word length every time around,
    // because this function is in a performance sensitive code path.
    if(obj->word_length <= 8)
    {
        const uint8_t *cast_buffer = (const uint8_t*)(*buffer);

        while(*length > 0 && Cy_I2S_GetNumInTxFifo(obj->base) < _CYHAL_I2S_FIFO_DEPTH)
        {
            Cy_I2S_WriteTxData(obj->base, *cast_buffer);
            ++cast_buffer;
            --(*length);
        }
        *buffer = (void*)cast_buffer;
    }
    else if(obj->word_length <= 16)
    {
        const uint16_t *cast_buffer = (const uint16_t*)(*buffer);

        while(*length > 0 && Cy_I2S_GetNumInTxFifo(obj->base) < _CYHAL_I2S_FIFO_DEPTH)
        {
            Cy_I2S_WriteTxData(obj->base, *cast_buffer);
            ++cast_buffer;
            --(*length);
        }
        *buffer = (void*)cast_buffer;
    }
    else
    {
        CY_ASSERT(obj->word_length <= 32);
        const uint32_t *cast_buffer = (const uint32_t*)(*buffer);

        while(*length > 0 && Cy_I2S_GetNumInTxFifo(obj->base) < _CYHAL_I2S_FIFO_DEPTH)
        {
            Cy_I2S_WriteTxData(obj->base, *cast_buffer);
            ++cast_buffer;
            --(*length);
        }
        *buffer = (void*)cast_buffer;
    }
}

cy_rslt_t cyhal_i2s_write(cyhal_i2s_t *obj, const void *data, size_t *length)
{
    CY_ASSERT(NULL != obj);
    if (obj->pm_transition_ready)
    {
        return CYHAL_SYSPM_RSLT_ERR_PM_PENDING;
    }

    size_t remaining = *length;
    cyhal_i2s_write_until_full(obj, &data, &remaining);
    *length -= remaining;
    return CY_RSLT_SUCCESS;
}

bool cyhal_i2s_is_tx_enabled(cyhal_i2s_t *obj)
{
    CY_ASSERT(NULL != obj);

    return (0 != (CY_I2S_TX_START & Cy_I2S_GetCurrentState(obj->base)));
}

bool cyhal_i2s_is_tx_busy(cyhal_i2s_t *obj)
{
    CY_ASSERT(NULL != obj);

    return (0 != Cy_I2S_GetNumInTxFifo(obj->base)) || cyhal_i2s_is_write_pending(obj);
}

bool cyhal_i2s_is_rx_enabled(cyhal_i2s_t *obj)
{
    CY_ASSERT(NULL != obj);

    return (0 != (CY_I2S_RX_START & Cy_I2S_GetCurrentState(obj->base)));
}

bool cyhal_i2s_is_rx_busy(cyhal_i2s_t *obj)
{
    CY_ASSERT(NULL != obj);

    return (0 != Cy_I2S_GetNumInRxFifo(obj->base)) || cyhal_i2s_is_read_pending(obj);
}

cy_rslt_t cyhal_i2s_read_async(cyhal_i2s_t *obj, void *rx, size_t rx_length)
{
    CY_ASSERT(NULL != obj);
    if (obj->pm_transition_ready)
    {
        return CYHAL_SYSPM_RSLT_ERR_PM_PENDING;
    }

    uint32_t savedIntrStatus = cyhal_system_critical_section_enter();
    obj->async_rx_buff = rx;
    obj->async_rx_length = rx_length;
    cyhal_system_critical_section_exit(savedIntrStatus);
    switch(obj->async_mode)
    {
        case CYHAL_ASYNC_SW:
        {
            /* Read as much as we can now, then set up an interrupt to do the rest
             * This is a potentially long operation but we don't want other I2S operations to
             * interleave with it. So do a "mini critical section" and disable the interrupts for this block only.
             */
            uint32_t old_interrupt_mask = Cy_I2S_GetInterruptMask(obj->base);
            Cy_I2S_SetInterruptMask(obj->base, 0u);
            // Safe to cast away volatile here because we're calling read_until_empty from within
            // a critical section, so it should not change out from under us during this call
            cyhal_i2s_read_until_empty(obj, (void**)(&obj->async_rx_buff), (size_t*)(&obj->async_rx_length));
            _cyhal_i2s_update_rx_trigger_level(obj);
            Cy_I2S_SetInterruptMask(obj->base, old_interrupt_mask);
            if(obj->async_rx_length > 0)
            {
                _cyhal_i2s_update_enabled_events(obj);
            }
            else
            {
                _cyhal_i2s_process_event(obj, CYHAL_I2S_ASYNC_RX_COMPLETE);
            }
           break;
        }
        case CYHAL_ASYNC_DMA:
        {
            // Don't directly kick off the DMA here - it will be triggered
            // from the interrupt handler when the FIFO rised above the threshold
            // (which may have already happened by the time we get here if the
            // application already had the full or half-full event enabled)
            _cyhal_i2s_update_rx_trigger_level(obj);
            _cyhal_i2s_update_enabled_events(obj);
            break;
        }
        default:
           CY_ASSERT(0); /* Unrecognized async mode */
    }

    return CY_RSLT_SUCCESS;
}

static cy_rslt_t _cyhal_i2s_populate_pdl_config(cyhal_i2s_t *obj, cy_stc_i2s_config_t* pdl_config, uint8_t sclk_div)
{
    cy_en_i2s_len_t pdl_word_length, pdl_channel_length;
    cy_rslt_t result = _cyhal_i2s_convert_length(obj->channel_length, &pdl_channel_length);
    if (CY_RSLT_SUCCESS == result)
    {
        result = _cyhal_i2s_convert_length(obj->word_length, &pdl_word_length);
    }

    if(CY_RSLT_SUCCESS == result)
    {
        *pdl_config = _cyhal_i2s_default_config;
        pdl_config->txEnabled = (CYHAL_NC_PIN_VALUE != obj->pin_tx_sdo);
        pdl_config->rxEnabled = (CYHAL_NC_PIN_VALUE != obj->pin_rx_sdi);
        pdl_config->extClk = (CYHAL_NC_PIN_VALUE != obj->pin_mclk);
        pdl_config->clkDiv = sclk_div;
        pdl_config->txMasterMode = !obj->is_tx_slave;
        pdl_config->rxMasterMode = !obj->is_rx_slave;
        pdl_config->rxChannelLength = pdl_channel_length;
        pdl_config->rxWordLength = pdl_word_length;
        pdl_config->txChannelLength = pdl_channel_length;
        pdl_config->txWordLength = pdl_word_length;
    }
    return result;
}

// Round up the word length to the next power of 2
static uint8_t _cyhal_i2s_rounded_word_length(cyhal_i2s_t *obj)
{
    CY_ASSERT(obj->word_length <= 32);
    if(obj->word_length <= 8)
    {
        return 8u;
    }
    else if(obj->word_length <= 16)
    {
        return 16u;
    }

    return 32u;
}

cy_rslt_t cyhal_i2s_write_async(cyhal_i2s_t *obj, const void *tx, size_t tx_length)
{
    CY_ASSERT(NULL != obj);
    if (obj->pm_transition_ready)
    {
        return CYHAL_SYSPM_RSLT_ERR_PM_PENDING;
    }

    uint32_t savedIntrStatus = cyhal_system_critical_section_enter();
    obj->async_tx_buff = tx;
    obj->async_tx_length = tx_length;
    cyhal_system_critical_section_exit(savedIntrStatus);
    switch(obj->async_mode)
    {
        case CYHAL_ASYNC_SW:
        {
            /* Write as much as we can now, then set up an interrupt to do the rest
            * This is a potentially long operation but we don't want other I2S operations to
            * interleave with it. So do a "mini critical section" and disable the interrupts for this block only.
            */
            uint32_t old_interrupt_mask = Cy_I2S_GetInterruptMask(obj->base);
            Cy_I2S_SetInterruptMask(obj->base, 0u);
            // Safe to cast away volatile here because we're calling write_until_full from within
            // a critical section, so it should not change out from under us during this call
            cyhal_i2s_write_until_full(obj, (const void**)(&obj->async_tx_buff), (size_t *)(&obj->async_tx_length));
            Cy_I2S_SetInterruptMask(obj->base, old_interrupt_mask);
            if(obj->async_tx_length > 0)
            {
                _cyhal_i2s_update_enabled_events(obj);
            }
            else
            {
                _cyhal_i2s_process_event(obj, CYHAL_I2S_ASYNC_TX_COMPLETE);
            }
            break;
        }
        case CYHAL_ASYNC_DMA:
        {
            // Don't directly kick off the DMA here - it will be triggered
            // from the interrupt handler when the FIFO drops below the threshold
            // (which may have already happened by the time we get here if the
            // application already had the half-empty or empty event enabled)
            _cyhal_i2s_update_enabled_events(obj);
            break;
        }
        default:
            CY_ASSERT(0); /* Unrecognized async mode */
            break;
    }

    return CY_RSLT_SUCCESS;
}

cy_rslt_t cyhal_i2s_set_async_mode(cyhal_i2s_t *obj, cyhal_async_mode_t mode, uint8_t dma_priority)
{
    CY_ASSERT(NULL != obj);
    // We don't support swapping the async mode out from under a pending transfer.
    CY_ASSERT(false == cyhal_i2s_is_read_pending(obj) && false == cyhal_i2s_is_write_pending(obj));

    cy_rslt_t result = CY_RSLT_SUCCESS;

    if(mode == CYHAL_ASYNC_DMA)
    {
        // Reserve a DMA channel for each direction that is enabled
        if(CYHAL_NC_PIN_VALUE != obj->pin_tx_sck && CYHAL_RSC_INVALID == obj->tx_dma.resource.type)
        {
            /* Reserve a DMA channel for async transmit if tx is enabled */
            result = cyhal_dma_init(&obj->tx_dma, CYHAL_DMA_PRIORITY_DEFAULT, CYHAL_DMA_DIRECTION_MEM2PERIPH);
            cyhal_dma_register_callback(&obj->tx_dma, &_cyhal_i2s_dma_handler_tx, obj);
        }
        if(mode == CYHAL_ASYNC_DMA && CYHAL_NC_PIN_VALUE != obj->pin_rx_sck && CYHAL_RSC_INVALID == obj->rx_dma.resource.type)
        {
            /* Reserve a DMA channel for async receive if rx is enabled */
            result = cyhal_dma_init(&obj->rx_dma, CYHAL_DMA_PRIORITY_DEFAULT, CYHAL_DMA_DIRECTION_PERIPH2MEM);
            cyhal_dma_register_callback(&obj->rx_dma, &_cyhal_i2s_dma_handler_rx, obj);
        }
    }
    else
    {
        /* Free the DMA instances if we reserved them but don't need them anymore */
        if(CYHAL_RSC_INVALID != obj->tx_dma.resource.type)
        {
            cyhal_dma_free(&obj->tx_dma);
            obj->tx_dma.resource.type = CYHAL_RSC_INVALID;
        }
        if(CYHAL_RSC_INVALID != obj->rx_dma.resource.type)
        {
            cyhal_dma_free(&obj->rx_dma);
            obj->rx_dma.resource.type = CYHAL_RSC_INVALID;
        }
    }

    if(CY_RSLT_SUCCESS == result)
    {
        obj->async_mode = mode;
        obj->async_dma_priority = dma_priority;
    }
    return result;
}

bool cyhal_i2s_is_read_pending(cyhal_i2s_t *obj)
{
    return (NULL != obj->async_rx_buff);
}

bool cyhal_i2s_is_write_pending(cyhal_i2s_t *obj)
{
    return (NULL != obj->async_tx_buff);
}

cy_rslt_t cyhal_i2s_abort_read_async(cyhal_i2s_t *obj) {
    uint32_t saved_intr = cyhal_system_critical_section_enter();
    obj->async_rx_buff = NULL;
    _cyhal_i2s_update_enabled_events(obj);
    cyhal_system_critical_section_exit(saved_intr);
    return CY_RSLT_SUCCESS;
}

cy_rslt_t cyhal_i2s_abort_write_async(cyhal_i2s_t *obj)
{
    uint32_t saved_intr = cyhal_system_critical_section_enter();
    obj->async_tx_buff = NULL;
    _cyhal_i2s_update_enabled_events(obj);
    cyhal_system_critical_section_exit(saved_intr);
    return CY_RSLT_SUCCESS;
}

static cyhal_i2s_event_t _cyhal_i2s_convert_interrupt_cause(uint32_t pdl_cause)
{
    cyhal_i2s_event_t result = (cyhal_i2s_event_t)0u;
    if(0 != (pdl_cause & CY_I2S_INTR_TX_NOT_FULL))
    {
        result |= CYHAL_I2S_TX_NOT_FULL;
    }
    if(0 != (pdl_cause & CY_I2S_INTR_TX_TRIGGER))
    {
        result |= CYHAL_I2S_TX_HALF_EMPTY;
    }
    if(0 != (pdl_cause & CY_I2S_INTR_TX_EMPTY))
    {
        result |= CYHAL_I2S_TX_EMPTY;
    }
    if(0 != (pdl_cause & CY_I2S_INTR_TX_OVERFLOW))
    {
        result |= CYHAL_I2S_TX_OVERFLOW;
    }
    if(0 != (pdl_cause & CY_I2S_INTR_TX_UNDERFLOW))
    {
        result |= CYHAL_I2S_TX_UNDERFLOW ;
    }
    if(0 != (pdl_cause & CY_I2S_INTR_RX_NOT_EMPTY))
    {
        result |= CYHAL_I2S_RX_NOT_EMPTY;
    }
    if(0 != (pdl_cause & CY_I2S_INTR_RX_TRIGGER))
    {
        result |= CYHAL_I2S_RX_HALF_FULL;
    }
    if(0 != (pdl_cause & CY_I2S_INTR_RX_FULL))
    {
        result |= CYHAL_I2S_RX_FULL;
    }
    if(0 != (pdl_cause & CY_I2S_INTR_RX_OVERFLOW))
    {
        result |= CYHAL_I2S_RX_OVERFLOW;
    }
    if(0 != (pdl_cause & CY_I2S_INTR_RX_UNDERFLOW))
    {
        result |= CYHAL_I2S_RX_UNDERFLOW;
    }

    return result;
}

static uint32_t _cyhal_i2s_convert_event(cyhal_i2s_event_t event)
{
    uint32_t pdl_event = 0u;
    if(0 != (event & CYHAL_I2S_TX_NOT_FULL))
    {
        pdl_event |= CY_I2S_INTR_TX_NOT_FULL;
    }
    if(0 != (event & CYHAL_I2S_TX_HALF_EMPTY))
    {
        pdl_event |= CY_I2S_INTR_TX_TRIGGER;
    }
    if(0 != (event & CYHAL_I2S_TX_EMPTY))
    {
        pdl_event |= CY_I2S_INTR_TX_EMPTY;
    }
    if(0 != (event & CYHAL_I2S_TX_OVERFLOW))
    {
        pdl_event |= CY_I2S_INTR_TX_OVERFLOW;
    }
    if(0 != (event & CYHAL_I2S_TX_UNDERFLOW ))
    {
        pdl_event |= CY_I2S_INTR_TX_UNDERFLOW;
    }
    if(0 != (event & CYHAL_I2S_RX_NOT_EMPTY))
    {
        pdl_event |= CY_I2S_INTR_RX_NOT_EMPTY;
    }
    if(0 != (event & CYHAL_I2S_RX_HALF_FULL))
    {
        pdl_event |= CY_I2S_INTR_RX_TRIGGER;
    }
    if(0 != (event & CYHAL_I2S_RX_FULL))
    {
        pdl_event |= CY_I2S_INTR_RX_FULL;
    }
    if(0 != (event & CYHAL_I2S_RX_OVERFLOW))
    {
        pdl_event |= CY_I2S_INTR_RX_OVERFLOW;
    }
    if(0 != (event & CYHAL_I2S_RX_UNDERFLOW))
    {
        pdl_event |= CY_I2S_INTR_RX_UNDERFLOW;
    }

    return pdl_event;
}

static cy_rslt_t _cyhal_i2s_convert_length(uint8_t user_length, cy_en_i2s_len_t *pdl_length)
{
    cy_rslt_t result = CY_RSLT_SUCCESS;
    switch(user_length)
    {
        case 8u:
            *pdl_length = CY_I2S_LEN8;
            break;
        case 16u:
            *pdl_length = CY_I2S_LEN16;
            break;
        case 18u:
            *pdl_length = CY_I2S_LEN18;
            break;
        case 20u:
            *pdl_length = CY_I2S_LEN20;
            break;
        case 24u:
            *pdl_length = CY_I2S_LEN24;
            break;
        case 32u:
            *pdl_length = CY_I2S_LEN32;
            break;
        default:
            result = CYHAL_I2S_RSLT_ERR_INVALID_ARG;
    }
    return result;
}

static void _cyhal_i2s_irq_handler(void)
{
    IRQn_Type irqn = _CYHAL_UTILS_GET_CURRENT_IRQN();
    uint8_t block = _cyhal_i2s_get_block_from_irqn(irqn);
    cyhal_i2s_t* obj = _cyhal_i2s_config_structs[block];

    uint32_t interrupt_status = Cy_I2S_GetInterruptStatusMasked(obj->base);
    Cy_I2S_ClearInterrupt(obj->base, interrupt_status);
    cyhal_i2s_event_t event = _cyhal_i2s_convert_interrupt_cause(interrupt_status);
    _cyhal_i2s_process_event(obj, event);
}

static void _cyhal_i2s_update_enabled_events(cyhal_i2s_t *obj)
{
    cyhal_i2s_event_t events = (cyhal_i2s_event_t)obj->user_enabled_events;
    if(NULL != obj->async_tx_buff && obj->async_tx_length > 0)
    {
        events |= (CYHAL_I2S_TX_EMPTY | CYHAL_I2S_TX_HALF_EMPTY);
    }
    if(NULL != obj->async_rx_buff && obj->async_rx_length > 0)
    {
        events |= (CYHAL_I2S_RX_FULL | CYHAL_I2S_RX_HALF_FULL);
    }

    uint32_t mask = _cyhal_i2s_convert_event(events);
    // The register is 24 bits wide but the hardware pads the value out with 1's when read.
    // So mask down to just the bits that we actually care about.
    uint32_t old_mask = Cy_I2S_GetInterruptMask(obj->base) & CY_I2S_INTR_MASK;

    // Clear the interrupts that are about to be enabled to avoid spurious firing
    uint32_t new_interrupts = mask & (~old_mask);
    Cy_I2S_ClearInterrupt(obj->base, new_interrupts);

    Cy_I2S_SetInterruptMask(obj->base, mask);
}

static void _cyhal_i2s_update_rx_trigger_level(cyhal_i2s_t *obj)
{
    // If we're doing an async read and the amount remaining is less than
    // the standard trigger level, temporarily reduce it so that we get
    // an interrupt as soon as the amount the user requested is ready
    uint32_t savedIntrStatus = cyhal_system_critical_section_enter();
    uint8_t trigger_level = _CYHAL_I2S_FIFO_DEPTH / 2;
    if(NULL != obj->async_rx_buff
       && obj->async_rx_length < trigger_level
       && obj->async_rx_length > 0)
    {
        trigger_level = obj->async_rx_length;
    }

    // Safe to do a blind write of this register because the only other bits are
    // CLEAR, which is only set temporarily from clear_tx, and FREEZE, which is
    // never used by this driver (it exists for debugging purposes only)
    obj->base->RX_FIFO_CTL = (trigger_level << I2S_RX_FIFO_CTL_TRIGGER_LEVEL_Pos);
    cyhal_system_critical_section_exit(savedIntrStatus);
}

static cy_rslt_t _cyhal_i2s_dma_perform_rx(cyhal_i2s_t *obj)
{
    // We could have received an event after we started the DMA but before it
    // managed to bring the FIFO below the threshold
    if(cyhal_dma_is_busy(&(obj->rx_dma)))
        return CY_RSLT_SUCCESS;
    size_t transfer_size = _CYHAL_I2S_DMA_BURST_SIZE;
    if (transfer_size >= obj->async_rx_length)
    {
        transfer_size = obj->async_rx_length;
        // Only want the user callback to be call on the last dma transfer.
        cyhal_dma_enable_event(&(obj->rx_dma), CYHAL_DMA_TRANSFER_COMPLETE, obj->async_dma_priority, true);
    }

    cyhal_dma_cfg_t dma_cfg =
    {
        .src_addr = (uint32_t)(&(obj->base->RX_FIFO_RD)),
        .src_increment = 0,
        .dst_addr = (uint32_t)obj->async_rx_buff,
        .dst_increment = 1,
        .transfer_width = _cyhal_i2s_rounded_word_length(obj),
        .length = transfer_size,
        .burst_size = 0,
        .action = CYHAL_DMA_TRANSFER_FULL,
    };
    cy_rslt_t result = cyhal_dma_configure(&(obj->rx_dma), &dma_cfg);

    // Update the buffer first so that it's guaranteed to be correct whenever the DMA completes
    if(CY_RSLT_SUCCESS == result)
    {
        size_t increment_bytes = transfer_size * (_cyhal_i2s_rounded_word_length(obj) / 8);
        uint32_t savedIntrStatus = cyhal_system_critical_section_enter();
        obj->async_rx_buff = (void*)(((uint8_t*) obj->async_rx_buff) + increment_bytes);
        obj->async_rx_length -= transfer_size;
        _cyhal_i2s_update_rx_trigger_level(obj);
        _cyhal_i2s_update_enabled_events(obj);
        cyhal_system_critical_section_exit(savedIntrStatus);

        result = cyhal_dma_start_transfer(&(obj->rx_dma));
    }

    return result;
}

static cy_rslt_t _cyhal_i2s_dma_perform_tx(cyhal_i2s_t *obj)
{
    // We could have received an event after the DMA brought the FIFO below the
    // threshold but before the DMA is entirely complete
    if(cyhal_dma_is_busy(&(obj->tx_dma)))
        return CY_RSLT_SUCCESS;

    CY_ASSERT(NULL != obj->async_tx_buff);

    size_t transfer_size = _CYHAL_I2S_DMA_BURST_SIZE;
    if (transfer_size >= obj->async_tx_length)
    {
        transfer_size = obj->async_tx_length;
        // Only want the user callback to be call on the last dma transfer.
        cyhal_dma_enable_event(&(obj->tx_dma), CYHAL_DMA_TRANSFER_COMPLETE, obj->async_dma_priority, true);
    }

    cyhal_dma_cfg_t dma_cfg =
    {
        .src_addr = (uint32_t)obj->async_tx_buff,
        .src_increment = 1,
        .dst_addr = (uint32_t)(&(obj->base->TX_FIFO_WR)),
        .dst_increment = 0,
        .transfer_width = _cyhal_i2s_rounded_word_length(obj),
        .length = transfer_size,
        .burst_size = 0,
        .action = CYHAL_DMA_TRANSFER_FULL,
    };
    cy_rslt_t result = cyhal_dma_configure(&(obj->tx_dma), &dma_cfg);

    // Update the buffer first so that it's guaranteed to be correct whenever the DMA completes
    if(CY_RSLT_SUCCESS == result)
    {
        size_t increment_bytes = transfer_size * (_cyhal_i2s_rounded_word_length(obj) / 8);
        uint32_t savedIntrStatus = cyhal_system_critical_section_enter();
        obj->async_tx_buff = (void*)(((uint8_t*) obj->async_tx_buff) + increment_bytes);
        obj->async_tx_length -= transfer_size;

        // Do this after we've updated async_tx_buff/length because once we have kicked
        // off the final DMA transfer there is no further action we will take on the
        // half-empty/empty events, and we don't want those to wind upstarving the
        // DMA complete event
        _cyhal_i2s_update_enabled_events(obj);
        cyhal_system_critical_section_exit(savedIntrStatus);

        result = cyhal_dma_start_transfer(&(obj->tx_dma));
    }

    return result;
}

/* Callback argument is the I2S instance */
static void _cyhal_i2s_dma_handler_rx(void *callback_arg, cyhal_dma_event_t event)
{
    CY_UNUSED_PARAMETER(event);
    /* We only hook this handler up when we're doing the final transfer, so send the completed event */
    CY_ASSERT(CYHAL_DMA_TRANSFER_COMPLETE == event);

    cyhal_i2s_t *obj = (cyhal_i2s_t*)callback_arg;
    obj->async_rx_buff = NULL;
    cyhal_dma_enable_event(&obj->rx_dma, CYHAL_DMA_TRANSFER_COMPLETE, obj->async_dma_priority, false);
    _cyhal_i2s_process_event(obj, CYHAL_I2S_ASYNC_RX_COMPLETE);
}

/* Callback argument is the I2S instance */
static void _cyhal_i2s_dma_handler_tx(void *callback_arg, cyhal_dma_event_t event)
{
    CY_UNUSED_PARAMETER(event);
    /* We only hook this handler up when we're doing the final transfer, so send the completed event */
    CY_ASSERT(CYHAL_DMA_TRANSFER_COMPLETE == event);

    cyhal_i2s_t *obj = (cyhal_i2s_t*)callback_arg;
    obj->async_tx_buff = NULL;
    cyhal_dma_enable_event(&obj->tx_dma, CYHAL_DMA_TRANSFER_COMPLETE, obj->async_dma_priority, false);
    _cyhal_i2s_process_event(obj, CYHAL_I2S_ASYNC_TX_COMPLETE);
}

static void _cyhal_i2s_process_event(cyhal_i2s_t *obj, cyhal_i2s_event_t event)
{
    if(0 != (event & (CYHAL_I2S_TX_HALF_EMPTY | CYHAL_I2S_TX_EMPTY)))
    {
        /* We should normally not get the "empty" interrupt during an async transfer because we
         * should be topping the FIFO back up after each half-empty interrupt. But in case something
         * delays our response and the FIFO gets all the way to empty, listen for that as well
         */
        uint32_t savedIntrStatus = cyhal_system_critical_section_enter();
        if(NULL != obj->async_tx_buff && obj->async_tx_length > 0)
        {
            switch(obj->async_mode)
            {
                case CYHAL_ASYNC_SW:
                {
                    /* Write as much as we can out until the FIFO is full
                     * This is a potentially long operation but we don't want other I2S operations to
                     * interleave with it. So switch to a "mini critical section" and disable the
                     * interrupts for this block only while we're copying
                     */
                    uint32_t old_interrupt_mask = Cy_I2S_GetInterruptMask(obj->base);
                    Cy_I2S_SetInterruptMask(obj->base, 0u);
                    cyhal_system_critical_section_exit(savedIntrStatus);
                    // Safe to cast away volatile here because we're calling write_until_full from within
                    // a critical section, so it should not change out from under us during this call
                    cyhal_i2s_write_until_full(obj, (const void**)(&obj->async_tx_buff), (size_t *)(&obj->async_tx_length));

                    // Re-enter the global critical section so that the exit below behaves correctly
                    savedIntrStatus = cyhal_system_critical_section_enter();
                    Cy_I2S_SetInterruptMask(obj->base, old_interrupt_mask);
                    if(0 == obj->async_tx_length)
                    {
                        /* We finished the async transfer.  */
                        event |= CYHAL_I2S_ASYNC_TX_COMPLETE;
                    }
                    break;
                }
                case CYHAL_ASYNC_DMA:
                {
                    cy_rslt_t result = _cyhal_i2s_dma_perform_tx(obj);
                    CY_UNUSED_PARAMETER(result);
                    CY_ASSERT(CY_RSLT_SUCCESS == result);
                    break;
                }
                default:
                    CY_ASSERT(0); /* Unrecognized async mode */
                    break;
            }
        }

        cyhal_system_critical_section_exit(savedIntrStatus);
    }
    if(0 != (event & (CYHAL_I2S_RX_HALF_FULL | CYHAL_I2S_RX_FULL)))
    {
        /* Similar to TX, we don't expect to receive the "full" interrupt, but check for it out of caution */
        uint32_t savedIntrStatus = cyhal_system_critical_section_enter();
        if(NULL != obj->async_rx_buff && obj->async_rx_length > 0)
        {
            switch(obj->async_mode)
            {
                case CYHAL_ASYNC_SW:
                {
                   /* Read as much as we can until the FIFO is empty
                     * This is a potentially long operation but we don't want other I2S operations to
                     * interleave with it. So switch to a "mini critical section" and disable the
                     * interrupts for this block only while we're copying
                    */
                   uint32_t old_interrupt_mask = Cy_I2S_GetInterruptMask(obj->base);
                   Cy_I2S_SetInterruptMask(obj->base, 0u);
                   cyhal_system_critical_section_exit(savedIntrStatus);
                   // Safe to cast away volatile here because we're calling read_until_empty from within
                   // a critical section, so it should not change out from under us during this call
                   cyhal_i2s_read_until_empty(obj, (void**)(&obj->async_rx_buff), (size_t*)(&obj->async_rx_length));
                   // Re-enter the global critical section so that the exit below behaves correctly
                   savedIntrStatus = cyhal_system_critical_section_enter();
                   Cy_I2S_SetInterruptMask(obj->base, old_interrupt_mask);
                   _cyhal_i2s_update_enabled_events(obj);
                   if(0 == obj->async_rx_length)
                   {
                       /* We finished the async transfer.  */
                       event |= CYHAL_I2S_ASYNC_RX_COMPLETE;
                   }
                   break;
                }
                case CYHAL_ASYNC_DMA:
                   _cyhal_i2s_dma_perform_rx(obj);
                   break;

                default:
                   CY_ASSERT(0); /* Unrecognized async mode */
            }

            // During async rx transfers, we may temporarily set the trigger level below half-full.
            // So make sure that it's a real "half full" and skip propagating to the user if it isn't
            uint8_t trigger_level = (obj->base->TX_FIFO_CTL & I2S_TX_FIFO_CTL_TRIGGER_LEVEL_Msk) >> I2S_TX_FIFO_CTL_TRIGGER_LEVEL_Pos;
            if(trigger_level != _CYHAL_I2S_FIFO_DEPTH / 2)
            {
                event &= ~CYHAL_I2S_RX_HALF_FULL;
            }
        }

        cyhal_system_critical_section_exit(savedIntrStatus);
    }

    /* Mark async transfer as complete if we just finished one. */
    if(0 != (event & CYHAL_I2S_ASYNC_TX_COMPLETE))
    {
        obj->async_tx_buff = NULL;
        _cyhal_i2s_update_enabled_events(obj);
    }
    if(0 != (event & CYHAL_I2S_ASYNC_RX_COMPLETE))
    {
        obj->async_rx_buff = NULL;
        _cyhal_i2s_update_enabled_events(obj);
    }

    if(0 != (event & ((cyhal_i2s_event_t)obj->user_enabled_events)))
    {
        cyhal_i2s_event_callback_t callback = (cyhal_i2s_event_callback_t)obj->callback_data.callback;
        if(NULL != callback)
        {
            callback(obj->callback_data.callback_arg, (cyhal_i2s_event_t)(event & obj->user_enabled_events));
        }
    }
}

static bool _cyhal_i2s_pm_callback(cyhal_syspm_callback_state_t state, cyhal_syspm_callback_mode_t mode, void* callback_arg)
{
    cyhal_i2s_t *obj = (cyhal_i2s_t *)callback_arg;
    CY_UNUSED_PARAMETER(state);

    switch(mode)
    {
        case CYHAL_SYSPM_CHECK_READY:
            obj->pm_transition_ready = Cy_I2S_GetCurrentState(obj->base) == 0 && !(cyhal_i2s_is_read_pending(obj) || cyhal_i2s_is_tx_busy(obj));
            return obj->pm_transition_ready;
        case CYHAL_SYSPM_CHECK_FAIL:
        case CYHAL_SYSPM_AFTER_TRANSITION:
            obj->pm_transition_ready = false;
            return true;
        default:
            return true;
    }
}

#if defined(__cplusplus)
}
#endif

#endif /* CY_IP_MXSCB */