| /* |
| * Copyright (c) 2015 - 2019, Nordic Semiconductor ASA |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, this |
| * list of conditions and the following disclaimer. |
| * |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * 3. Neither the name of the copyright holder nor the names of its |
| * contributors may be used to endorse or promote products derived from this |
| * software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include <nrfx.h> |
| |
| #if NRFX_CHECK(NRFX_I2S_ENABLED) |
| |
| #include <nrfx_i2s.h> |
| #include <hal/nrf_gpio.h> |
| |
| #define NRFX_LOG_MODULE I2S |
| #include <nrfx_log.h> |
| |
| #define EVT_TO_STR(event) \ |
| (event == NRF_I2S_EVENT_RXPTRUPD ? "NRF_I2S_EVENT_RXPTRUPD" : \ |
| (event == NRF_I2S_EVENT_TXPTRUPD ? "NRF_I2S_EVENT_TXPTRUPD" : \ |
| (event == NRF_I2S_EVENT_STOPPED ? "NRF_I2S_EVENT_STOPPED" : \ |
| "UNKNOWN EVENT"))) |
| |
| #if !defined(USE_WORKAROUND_FOR_I2S_STOP_ANOMALY) && \ |
| (defined(NRF52832_XXAA) || defined(NRF52832_XXAB) || defined(NRF52840_XXAA) || \ |
| defined(NRF9160_XXAA)) |
| // Enable workaround for nRF52832 and nRF52840 anomaly 194 / nrf9160 anomaly 1 |
| // (STOP task does not switch off all resources). |
| #define USE_WORKAROUND_FOR_I2S_STOP_ANOMALY 1 |
| #endif |
| |
| // Control block - driver instance local data. |
| typedef struct |
| { |
| nrfx_i2s_data_handler_t handler; |
| nrfx_drv_state_t state; |
| |
| bool use_rx : 1; |
| bool use_tx : 1; |
| bool rx_ready : 1; |
| bool tx_ready : 1; |
| bool buffers_needed : 1; |
| bool buffers_reused : 1; |
| |
| uint16_t buffer_size; |
| nrfx_i2s_buffers_t next_buffers; |
| nrfx_i2s_buffers_t current_buffers; |
| } i2s_control_block_t; |
| static i2s_control_block_t m_cb; |
| |
| |
| static void configure_pins(nrfx_i2s_config_t const * p_config) |
| { |
| uint32_t mck_pin, sdout_pin, sdin_pin; |
| |
| // Configure pins used by the peripheral: |
| |
| // - SCK and LRCK (required) - depending on the mode of operation these |
| // pins are configured as outputs (in Master mode) or inputs (in Slave |
| // mode). |
| if (p_config->mode == NRF_I2S_MODE_MASTER) |
| { |
| nrf_gpio_cfg_output(p_config->sck_pin); |
| nrf_gpio_cfg_output(p_config->lrck_pin); |
| } |
| else |
| { |
| nrf_gpio_cfg_input(p_config->sck_pin, NRF_GPIO_PIN_NOPULL); |
| nrf_gpio_cfg_input(p_config->lrck_pin, NRF_GPIO_PIN_NOPULL); |
| } |
| |
| // - MCK (optional) - always output, |
| if (p_config->mck_pin != NRFX_I2S_PIN_NOT_USED) |
| { |
| mck_pin = p_config->mck_pin; |
| nrf_gpio_cfg_output(mck_pin); |
| } |
| else |
| { |
| mck_pin = NRF_I2S_PIN_NOT_CONNECTED; |
| } |
| |
| // - SDOUT (optional) - always output, |
| if (p_config->sdout_pin != NRFX_I2S_PIN_NOT_USED) |
| { |
| sdout_pin = p_config->sdout_pin; |
| nrf_gpio_cfg_output(sdout_pin); |
| } |
| else |
| { |
| sdout_pin = NRF_I2S_PIN_NOT_CONNECTED; |
| } |
| |
| // - SDIN (optional) - always input. |
| if (p_config->sdin_pin != NRFX_I2S_PIN_NOT_USED) |
| { |
| sdin_pin = p_config->sdin_pin; |
| nrf_gpio_cfg_input(sdin_pin, NRF_GPIO_PIN_NOPULL); |
| } |
| else |
| { |
| sdin_pin = NRF_I2S_PIN_NOT_CONNECTED; |
| } |
| |
| nrf_i2s_pins_set(NRF_I2S, |
| p_config->sck_pin, |
| p_config->lrck_pin, |
| mck_pin, |
| sdout_pin, |
| sdin_pin); |
| } |
| |
| |
| nrfx_err_t nrfx_i2s_init(nrfx_i2s_config_t const * p_config, |
| nrfx_i2s_data_handler_t handler) |
| { |
| NRFX_ASSERT(p_config); |
| NRFX_ASSERT(handler); |
| |
| nrfx_err_t err_code; |
| |
| if (m_cb.state != NRFX_DRV_STATE_UNINITIALIZED) |
| { |
| err_code = NRFX_ERROR_INVALID_STATE; |
| NRFX_LOG_WARNING("Function: %s, error code: %s.", |
| __func__, |
| NRFX_LOG_ERROR_STRING_GET(err_code)); |
| return err_code; |
| } |
| |
| if (!nrf_i2s_configure(NRF_I2S, |
| p_config->mode, |
| p_config->format, |
| p_config->alignment, |
| p_config->sample_width, |
| p_config->channels, |
| p_config->mck_setup, |
| p_config->ratio)) |
| { |
| err_code = NRFX_ERROR_INVALID_PARAM; |
| NRFX_LOG_WARNING("Function: %s, error code: %s.", |
| __func__, |
| NRFX_LOG_ERROR_STRING_GET(err_code)); |
| return err_code; |
| } |
| configure_pins(p_config); |
| |
| m_cb.handler = handler; |
| |
| NRFX_IRQ_PRIORITY_SET(I2S_IRQn, p_config->irq_priority); |
| NRFX_IRQ_ENABLE(I2S_IRQn); |
| |
| m_cb.state = NRFX_DRV_STATE_INITIALIZED; |
| |
| NRFX_LOG_INFO("Initialized."); |
| return NRFX_SUCCESS; |
| } |
| |
| |
| void nrfx_i2s_uninit(void) |
| { |
| NRFX_ASSERT(m_cb.state != NRFX_DRV_STATE_UNINITIALIZED); |
| |
| nrfx_i2s_stop(); |
| |
| NRFX_IRQ_DISABLE(I2S_IRQn); |
| |
| nrf_i2s_pins_set(NRF_I2S, |
| NRF_I2S_PIN_NOT_CONNECTED, |
| NRF_I2S_PIN_NOT_CONNECTED, |
| NRF_I2S_PIN_NOT_CONNECTED, |
| NRF_I2S_PIN_NOT_CONNECTED, |
| NRF_I2S_PIN_NOT_CONNECTED); |
| |
| m_cb.state = NRFX_DRV_STATE_UNINITIALIZED; |
| NRFX_LOG_INFO("Uninitialized."); |
| } |
| |
| |
| nrfx_err_t nrfx_i2s_start(nrfx_i2s_buffers_t const * p_initial_buffers, |
| uint16_t buffer_size, |
| uint8_t flags) |
| { |
| NRFX_ASSERT(p_initial_buffers != NULL); |
| NRFX_ASSERT(p_initial_buffers->p_rx_buffer != NULL || |
| p_initial_buffers->p_tx_buffer != NULL); |
| NRFX_ASSERT((p_initial_buffers->p_rx_buffer == NULL) || |
| (nrfx_is_in_ram(p_initial_buffers->p_rx_buffer) && |
| nrfx_is_word_aligned(p_initial_buffers->p_rx_buffer))); |
| NRFX_ASSERT((p_initial_buffers->p_tx_buffer == NULL) || |
| (nrfx_is_in_ram(p_initial_buffers->p_tx_buffer) && |
| nrfx_is_word_aligned(p_initial_buffers->p_tx_buffer))); |
| NRFX_ASSERT(buffer_size != 0); |
| (void)(flags); |
| |
| nrfx_err_t err_code; |
| |
| if (m_cb.state != NRFX_DRV_STATE_INITIALIZED) |
| { |
| err_code = NRFX_ERROR_INVALID_STATE; |
| NRFX_LOG_WARNING("Function: %s, error code: %s.", |
| __func__, |
| NRFX_LOG_ERROR_STRING_GET(err_code)); |
| return err_code; |
| } |
| |
| if (((p_initial_buffers->p_rx_buffer != NULL) |
| && !nrfx_is_in_ram(p_initial_buffers->p_rx_buffer)) |
| || |
| ((p_initial_buffers->p_tx_buffer != NULL) |
| && !nrfx_is_in_ram(p_initial_buffers->p_tx_buffer))) |
| { |
| err_code = NRFX_ERROR_INVALID_ADDR; |
| NRFX_LOG_WARNING("Function: %s, error code: %s.", |
| __func__, |
| NRFX_LOG_ERROR_STRING_GET(err_code)); |
| return err_code; |
| } |
| |
| m_cb.use_rx = (p_initial_buffers->p_rx_buffer != NULL); |
| m_cb.use_tx = (p_initial_buffers->p_tx_buffer != NULL); |
| m_cb.rx_ready = false; |
| m_cb.tx_ready = false; |
| m_cb.buffers_needed = false; |
| m_cb.buffer_size = buffer_size; |
| |
| // Set the provided initial buffers as next, they will become the current |
| // ones after the IRQ handler is called for the first time, what will occur |
| // right after the START task is triggered. |
| m_cb.next_buffers = *p_initial_buffers; |
| m_cb.current_buffers.p_rx_buffer = NULL; |
| m_cb.current_buffers.p_tx_buffer = NULL; |
| |
| nrf_i2s_transfer_set(NRF_I2S, |
| m_cb.buffer_size, |
| m_cb.next_buffers.p_rx_buffer, |
| m_cb.next_buffers.p_tx_buffer); |
| |
| nrf_i2s_enable(NRF_I2S); |
| |
| m_cb.state = NRFX_DRV_STATE_POWERED_ON; |
| |
| nrf_i2s_event_clear(NRF_I2S, NRF_I2S_EVENT_RXPTRUPD); |
| nrf_i2s_event_clear(NRF_I2S, NRF_I2S_EVENT_TXPTRUPD); |
| nrf_i2s_event_clear(NRF_I2S, NRF_I2S_EVENT_STOPPED); |
| nrf_i2s_int_enable(NRF_I2S, (m_cb.use_rx ? NRF_I2S_INT_RXPTRUPD_MASK : 0) | |
| (m_cb.use_tx ? NRF_I2S_INT_TXPTRUPD_MASK : 0) | |
| NRF_I2S_INT_STOPPED_MASK); |
| nrf_i2s_task_trigger(NRF_I2S, NRF_I2S_TASK_START); |
| |
| NRFX_LOG_INFO("Started."); |
| return NRFX_SUCCESS; |
| } |
| |
| |
| nrfx_err_t nrfx_i2s_next_buffers_set(nrfx_i2s_buffers_t const * p_buffers) |
| { |
| NRFX_ASSERT(m_cb.state == NRFX_DRV_STATE_POWERED_ON); |
| NRFX_ASSERT(p_buffers); |
| NRFX_ASSERT((p_buffers->p_rx_buffer == NULL) || |
| (nrfx_is_in_ram(p_buffers->p_rx_buffer) && |
| nrfx_is_word_aligned(p_buffers->p_rx_buffer))); |
| NRFX_ASSERT((p_buffers->p_tx_buffer == NULL) || |
| (nrfx_is_in_ram(p_buffers->p_tx_buffer) && |
| nrfx_is_word_aligned(p_buffers->p_tx_buffer))); |
| |
| nrfx_err_t err_code; |
| |
| if (!m_cb.buffers_needed) |
| { |
| err_code = NRFX_ERROR_INVALID_STATE; |
| NRFX_LOG_WARNING("Function: %s, error code: %s.", |
| __func__, |
| NRFX_LOG_ERROR_STRING_GET(err_code)); |
| return err_code; |
| } |
| |
| if (((p_buffers->p_rx_buffer != NULL) |
| && !nrfx_is_in_ram(p_buffers->p_rx_buffer)) |
| || |
| ((p_buffers->p_tx_buffer != NULL) |
| && !nrfx_is_in_ram(p_buffers->p_tx_buffer))) |
| { |
| err_code = NRFX_ERROR_INVALID_ADDR; |
| NRFX_LOG_WARNING("Function: %s, error code: %s.", |
| __func__, |
| NRFX_LOG_ERROR_STRING_GET(err_code)); |
| return err_code; |
| } |
| |
| if (m_cb.use_tx) |
| { |
| NRFX_ASSERT(p_buffers->p_tx_buffer != NULL); |
| nrf_i2s_tx_buffer_set(NRF_I2S, p_buffers->p_tx_buffer); |
| } |
| if (m_cb.use_rx) |
| { |
| NRFX_ASSERT(p_buffers->p_rx_buffer != NULL); |
| nrf_i2s_rx_buffer_set(NRF_I2S, p_buffers->p_rx_buffer); |
| } |
| |
| m_cb.next_buffers = *p_buffers; |
| m_cb.buffers_needed = false; |
| |
| return NRFX_SUCCESS; |
| } |
| |
| |
| void nrfx_i2s_stop(void) |
| { |
| NRFX_ASSERT(m_cb.state != NRFX_DRV_STATE_UNINITIALIZED); |
| |
| m_cb.buffers_needed = false; |
| |
| // First disable interrupts, then trigger the STOP task, so no spurious |
| // RXPTRUPD and TXPTRUPD events (see nRF52 anomaly 55) are processed. |
| nrf_i2s_int_disable(NRF_I2S, NRF_I2S_INT_RXPTRUPD_MASK | |
| NRF_I2S_INT_TXPTRUPD_MASK); |
| nrf_i2s_task_trigger(NRF_I2S, NRF_I2S_TASK_STOP); |
| |
| #if NRFX_CHECK(USE_WORKAROUND_FOR_I2S_STOP_ANOMALY) |
| *((volatile uint32_t *)(((uint32_t)NRF_I2S) + 0x38)) = 1; |
| *((volatile uint32_t *)(((uint32_t)NRF_I2S) + 0x3C)) = 1; |
| #endif |
| } |
| |
| |
| void nrfx_i2s_irq_handler(void) |
| { |
| if (nrf_i2s_event_check(NRF_I2S, NRF_I2S_EVENT_TXPTRUPD)) |
| { |
| nrf_i2s_event_clear(NRF_I2S, NRF_I2S_EVENT_TXPTRUPD); |
| m_cb.tx_ready = true; |
| if (m_cb.use_tx && m_cb.buffers_needed) |
| { |
| m_cb.buffers_reused = true; |
| } |
| } |
| if (nrf_i2s_event_check(NRF_I2S, NRF_I2S_EVENT_RXPTRUPD)) |
| { |
| nrf_i2s_event_clear(NRF_I2S, NRF_I2S_EVENT_RXPTRUPD); |
| m_cb.rx_ready = true; |
| if (m_cb.use_rx && m_cb.buffers_needed) |
| { |
| m_cb.buffers_reused = true; |
| } |
| } |
| |
| if (nrf_i2s_event_check(NRF_I2S, NRF_I2S_EVENT_STOPPED)) |
| { |
| nrf_i2s_event_clear(NRF_I2S, NRF_I2S_EVENT_STOPPED); |
| nrf_i2s_int_disable(NRF_I2S, NRF_I2S_INT_STOPPED_MASK); |
| nrf_i2s_disable(NRF_I2S); |
| |
| // When stopped, release all buffers, including these scheduled for |
| // the next transfer. |
| m_cb.handler(&m_cb.current_buffers, 0); |
| m_cb.handler(&m_cb.next_buffers, 0); |
| |
| m_cb.state = NRFX_DRV_STATE_INITIALIZED; |
| NRFX_LOG_INFO("Stopped."); |
| } |
| else |
| { |
| // Check if the requested transfer has been completed: |
| // - full-duplex mode |
| if ((m_cb.use_tx && m_cb.use_rx && m_cb.tx_ready && m_cb.rx_ready) || |
| // - TX only mode |
| (!m_cb.use_rx && m_cb.tx_ready) || |
| // - RX only mode |
| (!m_cb.use_tx && m_cb.rx_ready)) |
| { |
| m_cb.tx_ready = false; |
| m_cb.rx_ready = false; |
| |
| // If the application did not supply the buffers for the next |
| // part of the transfer until this moment, the current buffers |
| // cannot be released, since the I2S peripheral already started |
| // using them. Signal this situation to the application by |
| // passing NULL instead of the structure with released buffers. |
| if (m_cb.buffers_reused) |
| { |
| m_cb.buffers_reused = false; |
| // This will most likely be set at this point. However, there is |
| // a small time window between TXPTRUPD and RXPTRUPD events, |
| // and it is theoretically possible that next buffers will be |
| // set in this window, so to be sure this flag is set to true, |
| // set it explicitly. |
| m_cb.buffers_needed = true; |
| m_cb.handler(NULL, |
| NRFX_I2S_STATUS_NEXT_BUFFERS_NEEDED); |
| } |
| else |
| { |
| // Buffers that have been used by the I2S peripheral (current) |
| // are now released and will be returned to the application, |
| // and the ones scheduled to be used as next become the current |
| // ones. |
| nrfx_i2s_buffers_t released_buffers = m_cb.current_buffers; |
| m_cb.current_buffers = m_cb.next_buffers; |
| m_cb.next_buffers.p_rx_buffer = NULL; |
| m_cb.next_buffers.p_tx_buffer = NULL; |
| m_cb.buffers_needed = true; |
| m_cb.handler(&released_buffers, |
| NRFX_I2S_STATUS_NEXT_BUFFERS_NEEDED); |
| } |
| |
| } |
| } |
| } |
| |
| #endif // NRFX_CHECK(NRFX_I2S_ENABLED) |