Files
mbed-os/targets/TARGET_Cypress/TARGET_PSOC6/mtb-hal-cat1/source/cyhal_i2s.c
Beslan 0ef1717155
Some checks failed
Basic Checks / license-check (push) Has been cancelled
Basic Checks / include-check (push) Has been cancelled
Basic Checks / style-check (push) Has been cancelled
Basic Checks / docs-check (push) Has been cancelled
Basic Checks / python-tests (push) Has been cancelled
Basic Checks / pin-validation (push) Has been cancelled
Basic Checks / cmake-checks (push) Has been cancelled
Basic Checks / frozen-tools-check (push) Has been cancelled
Publish or Update docker image for head of branch / prepare-tags (push) Has been cancelled
Release or update docker image for a released mbed-os version / prepare-tags (push) Has been cancelled
Publish or Update docker image for head of branch / build-container (push) Has been cancelled
Publish or Update docker image for head of branch / test-container (linux/amd64) (push) Has been cancelled
Publish or Update docker image for head of branch / test-container (linux/arm64) (push) Has been cancelled
Publish or Update docker image for head of branch / deploy-container (push) Has been cancelled
Release or update docker image for a released mbed-os version / build-container (push) Has been cancelled
Release or update docker image for a released mbed-os version / test-container (linux/amd64) (push) Has been cancelled
Release or update docker image for a released mbed-os version / test-container (linux/arm64) (push) Has been cancelled
Release or update docker image for a released mbed-os version / deploy-container (push) Has been cancelled
Prune temporary docker images / prune-images (push) Has been cancelled
Mirror mbed-os-6.15.0
2026-07-10 18:42:39 +03:00

1435 lines
50 KiB
C

/*******************************************************************************
* 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 */